mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-07-24 02:14:26 +08:00
commit
3c63d66591
12
.github/workflows/build.yaml
vendored
12
.github/workflows/build.yaml
vendored
@ -12,7 +12,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: cd frontend && yarn install
|
run: cd frontend && yarn install
|
||||||
- name: Run ESLint
|
- name: Run ESLint
|
||||||
@ -31,7 +31,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
- name: Create .env file
|
- name: Create .env file
|
||||||
run: |
|
run: |
|
||||||
echo 'INTERCOM_APP_ID="${{ secrets.INTERCOM_APP_ID }}"' > frontend/.env
|
echo 'INTERCOM_APP_ID="${{ secrets.INTERCOM_APP_ID }}"' > frontend/.env
|
||||||
@ -54,12 +54,12 @@ jobs:
|
|||||||
build-query-service:
|
build-query-service:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
- name: Setup golang
|
- name: Setup golang
|
||||||
uses: actions/setup-go@v4
|
uses: actions/setup-go@v4
|
||||||
with:
|
with:
|
||||||
go-version: "1.21"
|
go-version: "1.21"
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
@ -72,12 +72,12 @@ jobs:
|
|||||||
build-ee-query-service:
|
build-ee-query-service:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
- name: Setup golang
|
- name: Setup golang
|
||||||
uses: actions/setup-go@v4
|
uses: actions/setup-go@v4
|
||||||
with:
|
with:
|
||||||
go-version: "1.21"
|
go-version: "1.21"
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
- name: Build EE query-service image
|
- name: Build EE query-service image
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
|
2
.github/workflows/codeql.yaml
vendored
2
.github/workflows/codeql.yaml
vendored
@ -39,7 +39,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
|
2
.github/workflows/commitlint.yml
vendored
2
.github/workflows/commitlint.yml
vendored
@ -7,7 +7,7 @@ jobs:
|
|||||||
lint-commits:
|
lint-commits:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- uses: wagoid/commitlint-github-action@v5
|
- uses: wagoid/commitlint-github-action@v5
|
||||||
|
@ -12,11 +12,11 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Codebase
|
- name: Checkout Codebase
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
repository: signoz/gh-bot
|
repository: signoz/gh-bot
|
||||||
- name: Use Node v16
|
- name: Use Node v16
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 16
|
node-version: 16
|
||||||
- name: Setup Cache & Install Dependencies
|
- name: Setup Cache & Install Dependencies
|
||||||
|
2
.github/workflows/dependency-review.yml
vendored
2
.github/workflows/dependency-review.yml
vendored
@ -15,7 +15,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: 'Checkout Repository'
|
- name: 'Checkout Repository'
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
- name: 'Dependency Review'
|
- name: 'Dependency Review'
|
||||||
with:
|
with:
|
||||||
fail-on-severity: high
|
fail-on-severity: high
|
||||||
|
2
.github/workflows/e2e-k3s.yaml
vendored
2
.github/workflows/e2e-k3s.yaml
vendored
@ -13,7 +13,7 @@ jobs:
|
|||||||
DOCKER_TAG: pull-${{ github.event.number }}
|
DOCKER_TAG: pull-${{ github.event.number }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Build query-service image
|
- name: Build query-service image
|
||||||
env:
|
env:
|
||||||
|
4
.github/workflows/playwright.yaml
vendored
4
.github/workflows/playwright.yaml
vendored
@ -9,8 +9,8 @@ jobs:
|
|||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-node@v3
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: "16.x"
|
node-version: "16.x"
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
|
26
.github/workflows/push.yaml
vendored
26
.github/workflows/push.yaml
vendored
@ -14,7 +14,11 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
- name: Setup golang
|
||||||
|
uses: actions/setup-go@v4
|
||||||
|
with:
|
||||||
|
go-version: "1.21"
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v2
|
uses: docker/setup-qemu-action@v2
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
@ -42,6 +46,11 @@ jobs:
|
|||||||
else
|
else
|
||||||
echo "DOCKER_TAG=${{ steps.branch-name.outputs.current_branch }}-oss" >> $GITHUB_ENV
|
echo "DOCKER_TAG=${{ steps.branch-name.outputs.current_branch }}-oss" >> $GITHUB_ENV
|
||||||
fi
|
fi
|
||||||
|
- name: Install cross-compilation tools
|
||||||
|
run: |
|
||||||
|
set -ex
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y gcc-aarch64-linux-gnu musl-tools
|
||||||
- name: Build and push docker image
|
- name: Build and push docker image
|
||||||
run: make build-push-query-service
|
run: make build-push-query-service
|
||||||
|
|
||||||
@ -49,7 +58,11 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
- name: Setup golang
|
||||||
|
uses: actions/setup-go@v4
|
||||||
|
with:
|
||||||
|
go-version: "1.21"
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v2
|
uses: docker/setup-qemu-action@v2
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
@ -77,6 +90,11 @@ jobs:
|
|||||||
else
|
else
|
||||||
echo "DOCKER_TAG=${{ steps.branch-name.outputs.current_branch }}" >> $GITHUB_ENV
|
echo "DOCKER_TAG=${{ steps.branch-name.outputs.current_branch }}" >> $GITHUB_ENV
|
||||||
fi
|
fi
|
||||||
|
- name: Install cross-compilation tools
|
||||||
|
run: |
|
||||||
|
set -ex
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y gcc-aarch64-linux-gnu musl-tools
|
||||||
- name: Build and push docker image
|
- name: Build and push docker image
|
||||||
run: make build-push-ee-query-service
|
run: make build-push-ee-query-service
|
||||||
|
|
||||||
@ -84,7 +102,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
working-directory: frontend
|
working-directory: frontend
|
||||||
run: yarn install
|
run: yarn install
|
||||||
@ -128,7 +146,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
- name: Create .env file
|
- name: Create .env file
|
||||||
run: |
|
run: |
|
||||||
echo 'INTERCOM_APP_ID="${{ secrets.INTERCOM_APP_ID }}"' > frontend/.env
|
echo 'INTERCOM_APP_ID="${{ secrets.INTERCOM_APP_ID }}"' > frontend/.env
|
||||||
|
2
.github/workflows/sonar.yml
vendored
2
.github/workflows/sonar.yml
vendored
@ -14,7 +14,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Sonar analysis
|
- name: Sonar analysis
|
||||||
|
1
.github/workflows/staging-deployment.yaml
vendored
1
.github/workflows/staging-deployment.yaml
vendored
@ -26,6 +26,7 @@ jobs:
|
|||||||
echo "GITHUB_SHA: ${GITHUB_SHA}"
|
echo "GITHUB_SHA: ${GITHUB_SHA}"
|
||||||
export DOCKER_TAG="${GITHUB_SHA:0:7}" # needed for child process to access it
|
export DOCKER_TAG="${GITHUB_SHA:0:7}" # needed for child process to access it
|
||||||
export OTELCOL_TAG="main"
|
export OTELCOL_TAG="main"
|
||||||
|
export PATH="/usr/local/go/bin/:$PATH" # needed for Golang to work
|
||||||
docker system prune --force
|
docker system prune --force
|
||||||
docker pull signoz/signoz-otel-collector:main
|
docker pull signoz/signoz-otel-collector:main
|
||||||
cd ~/signoz
|
cd ~/signoz
|
||||||
|
1
.github/workflows/testing-deployment.yaml
vendored
1
.github/workflows/testing-deployment.yaml
vendored
@ -26,6 +26,7 @@ jobs:
|
|||||||
echo "GITHUB_SHA: ${GITHUB_SHA}"
|
echo "GITHUB_SHA: ${GITHUB_SHA}"
|
||||||
export DOCKER_TAG="${GITHUB_SHA:0:7}" # needed for child process to access it
|
export DOCKER_TAG="${GITHUB_SHA:0:7}" # needed for child process to access it
|
||||||
export DEV_BUILD="1"
|
export DEV_BUILD="1"
|
||||||
|
export PATH="/usr/local/go/bin/:$PATH" # needed for Golang to work
|
||||||
docker system prune --force
|
docker system prune --force
|
||||||
cd ~/signoz
|
cd ~/signoz
|
||||||
git status
|
git status
|
||||||
|
82
Makefile
82
Makefile
@ -8,6 +8,7 @@ BUILD_HASH ?= $(shell git rev-parse --short HEAD)
|
|||||||
BUILD_TIME ?= $(shell date -u +"%Y-%m-%dT%H:%M:%SZ")
|
BUILD_TIME ?= $(shell date -u +"%Y-%m-%dT%H:%M:%SZ")
|
||||||
BUILD_BRANCH ?= $(shell git rev-parse --abbrev-ref HEAD)
|
BUILD_BRANCH ?= $(shell git rev-parse --abbrev-ref HEAD)
|
||||||
DEV_LICENSE_SIGNOZ_IO ?= https://staging-license.signoz.io/api/v1
|
DEV_LICENSE_SIGNOZ_IO ?= https://staging-license.signoz.io/api/v1
|
||||||
|
DEV_BUILD ?= "" # set to any non-empty value to enable dev build
|
||||||
|
|
||||||
# Internal variables or constants.
|
# Internal variables or constants.
|
||||||
FRONTEND_DIRECTORY ?= frontend
|
FRONTEND_DIRECTORY ?= frontend
|
||||||
@ -15,15 +16,15 @@ QUERY_SERVICE_DIRECTORY ?= pkg/query-service
|
|||||||
EE_QUERY_SERVICE_DIRECTORY ?= ee/query-service
|
EE_QUERY_SERVICE_DIRECTORY ?= ee/query-service
|
||||||
STANDALONE_DIRECTORY ?= deploy/docker/clickhouse-setup
|
STANDALONE_DIRECTORY ?= deploy/docker/clickhouse-setup
|
||||||
SWARM_DIRECTORY ?= deploy/docker-swarm/clickhouse-setup
|
SWARM_DIRECTORY ?= deploy/docker-swarm/clickhouse-setup
|
||||||
LOCAL_GOOS ?= $(shell go env GOOS)
|
|
||||||
LOCAL_GOARCH ?= $(shell go env GOARCH)
|
GOOS ?= $(shell go env GOOS)
|
||||||
|
GOARCH ?= $(shell go env GOARCH)
|
||||||
|
GOPATH ?= $(shell go env GOPATH)
|
||||||
|
|
||||||
REPONAME ?= signoz
|
REPONAME ?= signoz
|
||||||
DOCKER_TAG ?= $(subst v,,$(BUILD_VERSION))
|
DOCKER_TAG ?= $(subst v,,$(BUILD_VERSION))
|
||||||
|
|
||||||
FRONTEND_DOCKER_IMAGE ?= frontend
|
FRONTEND_DOCKER_IMAGE ?= frontend
|
||||||
QUERY_SERVICE_DOCKER_IMAGE ?= query-service
|
QUERY_SERVICE_DOCKER_IMAGE ?= query-service
|
||||||
DEV_BUILD ?= ""
|
|
||||||
|
|
||||||
# Build-time Go variables
|
# Build-time Go variables
|
||||||
PACKAGE?=go.signoz.io/signoz
|
PACKAGE?=go.signoz.io/signoz
|
||||||
@ -37,10 +38,22 @@ LD_FLAGS=-X ${buildHash}=${BUILD_HASH} -X ${buildTime}=${BUILD_TIME} -X ${buildV
|
|||||||
DEV_LD_FLAGS=-X ${licenseSignozIo}=${DEV_LICENSE_SIGNOZ_IO}
|
DEV_LD_FLAGS=-X ${licenseSignozIo}=${DEV_LICENSE_SIGNOZ_IO}
|
||||||
|
|
||||||
all: build-push-frontend build-push-query-service
|
all: build-push-frontend build-push-query-service
|
||||||
|
|
||||||
|
# Steps to build static files of frontend
|
||||||
|
build-frontend-static:
|
||||||
|
@echo "------------------"
|
||||||
|
@echo "--> Building frontend static files"
|
||||||
|
@echo "------------------"
|
||||||
|
@cd $(FRONTEND_DIRECTORY) && \
|
||||||
|
rm -rf build && \
|
||||||
|
CI=1 yarn install && \
|
||||||
|
yarn build && \
|
||||||
|
ls -l build
|
||||||
|
|
||||||
# Steps to build and push docker image of frontend
|
# Steps to build and push docker image of frontend
|
||||||
.PHONY: build-frontend-amd64 build-push-frontend
|
.PHONY: build-frontend-amd64 build-push-frontend
|
||||||
# Step to build docker image of frontend in amd64 (used in build pipeline)
|
# Step to build docker image of frontend in amd64 (used in build pipeline)
|
||||||
build-frontend-amd64:
|
build-frontend-amd64: build-frontend-static
|
||||||
@echo "------------------"
|
@echo "------------------"
|
||||||
@echo "--> Building frontend docker image for amd64"
|
@echo "--> Building frontend docker image for amd64"
|
||||||
@echo "------------------"
|
@echo "------------------"
|
||||||
@ -49,7 +62,7 @@ build-frontend-amd64:
|
|||||||
--build-arg TARGETPLATFORM="linux/amd64" .
|
--build-arg TARGETPLATFORM="linux/amd64" .
|
||||||
|
|
||||||
# Step to build and push docker image of frontend(used in push pipeline)
|
# Step to build and push docker image of frontend(used in push pipeline)
|
||||||
build-push-frontend:
|
build-push-frontend: build-frontend-static
|
||||||
@echo "------------------"
|
@echo "------------------"
|
||||||
@echo "--> Building and pushing frontend docker image"
|
@echo "--> Building and pushing frontend docker image"
|
||||||
@echo "------------------"
|
@echo "------------------"
|
||||||
@ -57,24 +70,52 @@ build-push-frontend:
|
|||||||
docker buildx build --file Dockerfile --progress plain --push --platform linux/arm64,linux/amd64 \
|
docker buildx build --file Dockerfile --progress plain --push --platform linux/arm64,linux/amd64 \
|
||||||
--tag $(REPONAME)/$(FRONTEND_DOCKER_IMAGE):$(DOCKER_TAG) .
|
--tag $(REPONAME)/$(FRONTEND_DOCKER_IMAGE):$(DOCKER_TAG) .
|
||||||
|
|
||||||
|
# Steps to build static binary of query service
|
||||||
|
.PHONY: build-query-service-static
|
||||||
|
build-query-service-static:
|
||||||
|
@echo "------------------"
|
||||||
|
@echo "--> Building query-service static binary"
|
||||||
|
@echo "------------------"
|
||||||
|
@if [ $(DEV_BUILD) != "" ]; then \
|
||||||
|
cd $(QUERY_SERVICE_DIRECTORY) && \
|
||||||
|
CGO_ENABLED=1 go build -tags timetzdata -a -o ./bin/query-service-${GOOS}-${GOARCH} \
|
||||||
|
-ldflags "-linkmode external -extldflags '-static' -s -w ${LD_FLAGS} ${DEV_LD_FLAGS}"; \
|
||||||
|
else \
|
||||||
|
cd $(QUERY_SERVICE_DIRECTORY) && \
|
||||||
|
CGO_ENABLED=1 go build -tags timetzdata -a -o ./bin/query-service-${GOOS}-${GOARCH} \
|
||||||
|
-ldflags "-linkmode external -extldflags '-static' -s -w ${LD_FLAGS}"; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
.PHONY: build-query-service-static-amd64
|
||||||
|
build-query-service-static-amd64:
|
||||||
|
make GOARCH=amd64 build-query-service-static
|
||||||
|
|
||||||
|
.PHONY: build-query-service-static-arm64
|
||||||
|
build-query-service-static-arm64:
|
||||||
|
make CC=aarch64-linux-gnu-gcc GOARCH=arm64 build-query-service-static
|
||||||
|
|
||||||
|
# Steps to build static binary of query service for all platforms
|
||||||
|
.PHONY: build-query-service-static-all
|
||||||
|
build-query-service-static-all: build-query-service-static-amd64 build-query-service-static-arm64
|
||||||
|
|
||||||
# Steps to build and push docker image of query service
|
# Steps to build and push docker image of query service
|
||||||
.PHONY: build-query-service-amd64 build-push-query-service
|
.PHONY: build-query-service-amd64 build-push-query-service
|
||||||
# Step to build docker image of query service in amd64 (used in build pipeline)
|
# Step to build docker image of query service in amd64 (used in build pipeline)
|
||||||
build-query-service-amd64:
|
build-query-service-amd64: build-query-service-static-amd64
|
||||||
@echo "------------------"
|
@echo "------------------"
|
||||||
@echo "--> Building query-service docker image for amd64"
|
@echo "--> Building query-service docker image for amd64"
|
||||||
@echo "------------------"
|
@echo "------------------"
|
||||||
@docker build --file $(QUERY_SERVICE_DIRECTORY)/Dockerfile \
|
@docker build --file $(QUERY_SERVICE_DIRECTORY)/Dockerfile \
|
||||||
-t $(REPONAME)/$(QUERY_SERVICE_DOCKER_IMAGE):$(DOCKER_TAG) \
|
--tag $(REPONAME)/$(QUERY_SERVICE_DOCKER_IMAGE):$(DOCKER_TAG) \
|
||||||
--build-arg TARGETPLATFORM="linux/amd64" --build-arg LD_FLAGS="$(LD_FLAGS)" .
|
--build-arg TARGETPLATFORM="linux/amd64" .
|
||||||
|
|
||||||
# Step to build and push docker image of query in amd64 and arm64 (used in push pipeline)
|
# Step to build and push docker image of query in amd64 and arm64 (used in push pipeline)
|
||||||
build-push-query-service:
|
build-push-query-service: build-query-service-static-all
|
||||||
@echo "------------------"
|
@echo "------------------"
|
||||||
@echo "--> Building and pushing query-service docker image"
|
@echo "--> Building and pushing query-service docker image"
|
||||||
@echo "------------------"
|
@echo "------------------"
|
||||||
@docker buildx build --file $(QUERY_SERVICE_DIRECTORY)/Dockerfile --progress plain \
|
@docker buildx build --file $(QUERY_SERVICE_DIRECTORY)/Dockerfile --progress plain \
|
||||||
--push --platform linux/arm64,linux/amd64 --build-arg LD_FLAGS="$(LD_FLAGS)" \
|
--push --platform linux/arm64,linux/amd64 \
|
||||||
--tag $(REPONAME)/$(QUERY_SERVICE_DOCKER_IMAGE):$(DOCKER_TAG) .
|
--tag $(REPONAME)/$(QUERY_SERVICE_DOCKER_IMAGE):$(DOCKER_TAG) .
|
||||||
|
|
||||||
# Step to build EE docker image of query service in amd64 (used in build pipeline)
|
# Step to build EE docker image of query service in amd64 (used in build pipeline)
|
||||||
@ -82,24 +123,14 @@ build-ee-query-service-amd64:
|
|||||||
@echo "------------------"
|
@echo "------------------"
|
||||||
@echo "--> Building query-service docker image for amd64"
|
@echo "--> Building query-service docker image for amd64"
|
||||||
@echo "------------------"
|
@echo "------------------"
|
||||||
@if [ $(DEV_BUILD) != "" ]; then \
|
make QUERY_SERVICE_DIRECTORY=${EE_QUERY_SERVICE_DIRECTORY} build-query-service-amd64
|
||||||
docker build --file $(EE_QUERY_SERVICE_DIRECTORY)/Dockerfile \
|
|
||||||
-t $(REPONAME)/$(QUERY_SERVICE_DOCKER_IMAGE):$(DOCKER_TAG) \
|
|
||||||
--build-arg TARGETPLATFORM="linux/amd64" --build-arg LD_FLAGS="${LD_FLAGS} ${DEV_LD_FLAGS}" .; \
|
|
||||||
else \
|
|
||||||
docker build --file $(EE_QUERY_SERVICE_DIRECTORY)/Dockerfile \
|
|
||||||
-t $(REPONAME)/$(QUERY_SERVICE_DOCKER_IMAGE):$(DOCKER_TAG) \
|
|
||||||
--build-arg TARGETPLATFORM="linux/amd64" --build-arg LD_FLAGS="$(LD_FLAGS)" .; \
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Step to build and push EE docker image of query in amd64 and arm64 (used in push pipeline)
|
# Step to build and push EE docker image of query in amd64 and arm64 (used in push pipeline)
|
||||||
build-push-ee-query-service:
|
build-push-ee-query-service:
|
||||||
@echo "------------------"
|
@echo "------------------"
|
||||||
@echo "--> Building and pushing query-service docker image"
|
@echo "--> Building and pushing query-service docker image"
|
||||||
@echo "------------------"
|
@echo "------------------"
|
||||||
@docker buildx build --file $(EE_QUERY_SERVICE_DIRECTORY)/Dockerfile \
|
make QUERY_SERVICE_DIRECTORY=${EE_QUERY_SERVICE_DIRECTORY} build-push-query-service
|
||||||
--progress plain --push --platform linux/arm64,linux/amd64 \
|
|
||||||
--build-arg LD_FLAGS="$(LD_FLAGS)" --tag $(REPONAME)/$(QUERY_SERVICE_DOCKER_IMAGE):$(DOCKER_TAG) .
|
|
||||||
|
|
||||||
dev-setup:
|
dev-setup:
|
||||||
mkdir -p /var/lib/signoz
|
mkdir -p /var/lib/signoz
|
||||||
@ -110,7 +141,7 @@ dev-setup:
|
|||||||
@echo "------------------"
|
@echo "------------------"
|
||||||
|
|
||||||
run-local:
|
run-local:
|
||||||
@LOCAL_GOOS=$(LOCAL_GOOS) LOCAL_GOARCH=$(LOCAL_GOARCH) docker-compose -f \
|
@docker-compose -f \
|
||||||
$(STANDALONE_DIRECTORY)/docker-compose-core.yaml -f $(STANDALONE_DIRECTORY)/docker-compose-local.yaml \
|
$(STANDALONE_DIRECTORY)/docker-compose-core.yaml -f $(STANDALONE_DIRECTORY)/docker-compose-local.yaml \
|
||||||
up --build -d
|
up --build -d
|
||||||
|
|
||||||
@ -153,3 +184,4 @@ test:
|
|||||||
go test ./pkg/query-service/formatter/...
|
go test ./pkg/query-service/formatter/...
|
||||||
go test ./pkg/query-service/tests/integration/...
|
go test ./pkg/query-service/tests/integration/...
|
||||||
go test ./pkg/query-service/rules/...
|
go test ./pkg/query-service/rules/...
|
||||||
|
go test ./pkg/query-service/collectorsimulator/...
|
||||||
|
203
README.zh-cn.md
203
README.zh-cn.md
@ -1,170 +1,225 @@
|
|||||||
<p align="center">
|
<img src="https://res.cloudinary.com/dcv3epinx/image/upload/v1618904450/signoz-images/LogoGithub_sigfbu.svg" alt="SigNoz-logo" width="240" />
|
||||||
<img src="https://res.cloudinary.com/dcv3epinx/image/upload/v1618904450/signoz-images/LogoGithub_sigfbu.svg" alt="SigNoz-logo" width="240" />
|
|
||||||
|
|
||||||
<p align="center">监视你的应用,并可排查已部署应用中的问题,这是一个开源的可替代DataDog、NewRelic的方案</p>
|
<p align="center">监控你的应用,并且可排查已部署应用的问题,这是一个可替代 DataDog、NewRelic 的开源方案</p>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img alt="Downloads" src="https://img.shields.io/docker/pulls/signoz/frontend?label=Downloads"> </a>
|
<img alt="Downloads" src="https://img.shields.io/docker/pulls/signoz/query-service?label=Docker Downloads"> </a>
|
||||||
<img alt="GitHub issues" src="https://img.shields.io/github/issues/signoz/signoz"> </a>
|
<img alt="GitHub issues" src="https://img.shields.io/github/issues/signoz/signoz"> </a>
|
||||||
<a href="https://twitter.com/intent/tweet?text=Monitor%20your%20applications%20and%20troubleshoot%20problems%20with%20SigNoz,%20an%20open-source%20alternative%20to%20DataDog,%20NewRelic.&url=https://signoz.io/&via=SigNozHQ&hashtags=opensource,signoz,observability">
|
<a href="https://twitter.com/intent/tweet?text=Monitor%20your%20applications%20and%20troubleshoot%20problems%20with%20SigNoz,%20an%20open-source%20alternative%20to%20DataDog,%20NewRelic.&url=https://signoz.io/&via=SigNozHQ&hashtags=opensource,signoz,observability">
|
||||||
<img alt="tweet" src="https://img.shields.io/twitter/url/http/shields.io.svg?style=social"> </a>
|
<img alt="tweet" src="https://img.shields.io/twitter/url/http/shields.io.svg?style=social"> </a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
##
|
<h3 align="center">
|
||||||
|
<a href="https://signoz.io/docs"><b>文档</b></a> •
|
||||||
|
<a href="https://github.com/SigNoz/signoz/blob/develop/README.zh-cn.md"><b>中文ReadMe</b></a> •
|
||||||
|
<a href="https://github.com/SigNoz/signoz/blob/develop/README.de-de.md"><b>德文ReadMe</b></a> •
|
||||||
|
<a href="https://github.com/SigNoz/signoz/blob/develop/README.pt-br.md"><b>葡萄牙语ReadMe</b></a> •
|
||||||
|
<a href="https://signoz.io/slack"><b>Slack 社区</b></a> •
|
||||||
|
<a href="https://twitter.com/SigNozHq"><b>Twitter</b></a>
|
||||||
|
</h3>
|
||||||
|
|
||||||
SigNoz帮助开发人员监控应用并排查已部署应用中的问题。SigNoz使用分布式追踪来增加软件技术栈的可见性。
|
##
|
||||||
|
|
||||||
👉 你能看到一些性能指标,服务、外部api调用、每个终端(endpoint)的p99延迟和错误率。
|
SigNoz 帮助开发人员监控应用并排查已部署应用的问题。你可以使用 SigNoz 实现如下能力:
|
||||||
|
|
||||||
👉 通过准确的追踪来确定是什么引起了问题,并且可以看到每个独立请求的帧图(framegraph),这样你就能找到根本原因。
|
👉 在同一块面板上,可视化 Metrics, Traces 和 Logs 内容。
|
||||||
|
|
||||||
👉 聚合trace数据来获得业务相关指标。
|
👉 你可以关注服务的 p99 延迟和错误率, 包括外部 API 调用和个别的端点。
|
||||||
|
|
||||||

|
👉 你可以找到问题的根因,通过提取相关问题的 traces 日志、单独查看请求 traces 的火焰图详情。
|
||||||
<br />
|
|
||||||

|
👉 执行 trace 数据聚合,以获取业务相关的 metrics
|
||||||
<br />
|
|
||||||

|
👉 对日志过滤和查询,通过日志的属性建立看板和告警
|
||||||
|
|
||||||
|
👉 通过 Python,java,Ruby 和 Javascript 自动记录异常
|
||||||
|
|
||||||
|
👉 轻松的自定义查询和设置告警
|
||||||
|
|
||||||
|
### 应用 Metrics 展示
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### 分布式追踪
|
||||||
|
|
||||||
|
<img width="2068" alt="distributed_tracing_2 2" src="https://user-images.githubusercontent.com/83692067/226536447-bae58321-6a22-4ed3-af80-e3e964cb3489.png">
|
||||||
|
|
||||||
|
<img width="2068" alt="distributed_tracing_1" src="https://user-images.githubusercontent.com/83692067/226536462-939745b6-4f9d-45a6-8016-814837e7f7b4.png">
|
||||||
|
|
||||||
|
### 日志管理
|
||||||
|
|
||||||
|
<img width="2068" alt="logs_management" src="https://user-images.githubusercontent.com/83692067/226536482-b8a5c4af-b69c-43d5-969c-338bd5eaf1a5.png">
|
||||||
|
|
||||||
|
### 基础设施监控
|
||||||
|
|
||||||
|
<img width="2068" alt="infrastructure_monitoring" src="https://user-images.githubusercontent.com/83692067/226536496-f38c4dbf-e03c-4158-8be0-32d4a61158c7.png">
|
||||||
|
|
||||||
|
### 异常监控
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### 告警
|
||||||
|
|
||||||
|
<img width="2068" alt="alerts_management" src="https://user-images.githubusercontent.com/83692067/226536548-2c81e2e8-c12d-47e8-bad7-c6be79055def.png">
|
||||||
|
|
||||||
<br /><br />
|
<br /><br />
|
||||||
|
|
||||||
<img align="left" src="https://signoz-public.s3.us-east-2.amazonaws.com/Contributing.svg" width="50px" />
|
## 加入我们 Slack 社区
|
||||||
|
|
||||||
## 加入我们的Slack社区
|
来 [Slack](https://signoz.io/slack) 和我们打招呼吧 👋
|
||||||
|
|
||||||
来[Slack](https://signoz.io/slack) 跟我们打声招呼👋
|
|
||||||
|
|
||||||
<br /><br />
|
<br /><br />
|
||||||
|
|
||||||
<img align="left" src="https://signoz-public.s3.us-east-2.amazonaws.com/Features.svg" width="50px" />
|
## 特性:
|
||||||
|
|
||||||
## 功能:
|
- 为 metrics, traces and logs 制定统一的 UI。 无需切换 Prometheus 到 Jaeger 去查找问题,也无需使用想 Elastic 这样的日志工具分开你的 metrics 和 traces
|
||||||
|
|
||||||
- 应用概览指标(metrics),如RPS, p50/p90/p99延迟率分位值,错误率等。
|
- 默认统计应用的 metrics 数据,像 RPS (每秒请求数), 50th/90th/99th 的分位数延迟数据,还有相关的错误率
|
||||||
- 应用中最慢的终端(endpoint)
|
|
||||||
- 查看特定请求的trace数据来分析下游服务问题、慢数据库查询问题 及调用第三方服务如支付网关的问题
|
- 找到应用中最慢的端点
|
||||||
- 通过服务名称、操作、延迟、错误、标签来过滤traces。
|
|
||||||
- 聚合trace数据(events/spans)来得到业务相关指标。比如,你可以通过过滤条件`customer_type: gold` or `deployment_version: v2` or `external_call: paypal` 来获取指定业务的错误率和p99延迟
|
- 查看准确的请求跟踪数据,找到下游服务的问题了,比如 DB 慢查询,或者调用第三方的支付网关等
|
||||||
- 为metrics和trace提供统一的UI。排查问题不需要在Prometheus和Jaeger之间切换。
|
|
||||||
|
- 通过 服务名、操作方式、延迟、错误、标签/注释 过滤 traces 数据
|
||||||
|
|
||||||
|
- 通过聚合 trace 数据而获得业务相关的 metrics。 比如你可以通过 `customer_type: gold` 或者 `deployment_version: v2` 或者 `external_call: paypal` 获取错误率和 P99 延迟数据
|
||||||
|
|
||||||
|
- 原生支持 OpenTelemetry 日志,高级日志查询,自动收集 k8s 相关日志
|
||||||
|
|
||||||
|
- 快如闪电的日志分析 ([Logs Perf. Benchmark](https://signoz.io/blog/logs-performance-benchmark/))
|
||||||
|
|
||||||
|
- 可视化点到点的基础设施性能,提取有所有类型机器的 metrics 数据
|
||||||
|
|
||||||
|
- 轻易自定义告警查询
|
||||||
|
|
||||||
<br /><br />
|
<br /><br />
|
||||||
|
|
||||||
<img align="left" src="https://signoz-public.s3.us-east-2.amazonaws.com/WhatsCool.svg" width="50px" />
|
## 为什么使用 SigNoz?
|
||||||
|
|
||||||
## 为何选择SigNoz?
|
作为开发者, 我们发现 SaaS 厂商对一些大家想要的小功能都是闭源的,这种行为真的让人有点恼火。 闭源厂商还会在月底给你一张没有明细的巨额账单。
|
||||||
|
|
||||||
作为开发人员,我们发现依赖闭源的SaaS厂商提供的每个小功能有些麻烦,闭源厂商通常会给你一份巨额月付账单,但不提供足够的透明度,你不知道你为哪些功能付费。
|
我们想做一个自托管并且可开源的工具,像 DataDog 和 NewRelic 那样, 为那些担心数据隐私和安全的公司提供第三方服务。
|
||||||
|
|
||||||
我们想做一个自服务的开源版本的工具,类似于DataDog和NewRelic,用于那些对客户数据流入第三方有隐私和安全担忧的厂商。
|
作为开源的项目,你完全可以自己掌控你的配置、样本和更新。你同样可以基于 SigNoz 拓展特定的业务模块。
|
||||||
|
|
||||||
开源也让你对配置、采样和正常运行时间有完整的控制,你可以在SigNoz基础上构建模块来满足特定的商业需求。
|
### 支持的编程语言:
|
||||||
|
|
||||||
### 语言支持
|
我们支持 [OpenTelemetry](https://opentelemetry.io)。作为一个观测你应用的库文件。所以任何 OpenTelemetry 支持的框架和语言,对于 SigNoz 也同样支持。 一些主要支持的语言如下:
|
||||||
|
|
||||||
我们支持[OpenTelemetry](https://opentelemetry.io)库,你可以使用它来装备应用。也就是说SigNoz支持任何支持OpenTelemetry库的框架和语言。 主要支持语言包括:
|
|
||||||
|
|
||||||
- Java
|
- Java
|
||||||
- Python
|
- Python
|
||||||
- NodeJS
|
- NodeJS
|
||||||
- Go
|
- Go
|
||||||
|
- PHP
|
||||||
|
- .NET
|
||||||
|
- Ruby
|
||||||
|
- Elixir
|
||||||
|
- Rust
|
||||||
|
|
||||||
你可以在这个文档里找到完整的语言列表 - https://opentelemetry.io/docs/
|
你可以在这里找到全部支持的语言列表 - https://opentelemetry.io/docs/
|
||||||
|
|
||||||
<br /><br />
|
<br /><br />
|
||||||
|
|
||||||
<img align="left" src="https://signoz-public.s3.us-east-2.amazonaws.com/Philosophy.svg" width="50px" />
|
## 让我们开始吧
|
||||||
|
|
||||||
## 入门
|
### 使用 Docker 部署
|
||||||
|
|
||||||
|
请一步步跟随 [这里](https://signoz.io/docs/install/docker/) 通过 docker 来安装。
|
||||||
|
|
||||||
### 使用Docker部署
|
这个 [排障说明书](https://signoz.io/docs/install/troubleshooting/) 可以帮助你解决碰到的问题。
|
||||||
|
|
||||||
请按照[这里](https://signoz.io/docs/install/docker/)列出的步骤使用Docker来安装
|
|
||||||
|
|
||||||
如果你遇到任何问题,这个[排查指南](https://signoz.io/docs/install/troubleshooting/)会对你有帮助。
|
|
||||||
|
|
||||||
<p>  </p>
|
<p>  </p>
|
||||||
|
|
||||||
|
### 使用 Helm 在 Kubernetes 部署
|
||||||
|
|
||||||
### 使用Helm在Kubernetes上部署
|
请一步步跟随 [这里](https://signoz.io/docs/deployment/helm_chart) 通过 helm 来安装
|
||||||
|
|
||||||
请跟着[这里](https://signoz.io/docs/deployment/helm_chart)的步骤使用helm charts安装
|
|
||||||
|
|
||||||
<br /><br />
|
<br /><br />
|
||||||
|
|
||||||
<img align="left" src="https://signoz-public.s3.us-east-2.amazonaws.com/UseSigNoz.svg" width="50px" />
|
## 比较相似的工具
|
||||||
|
|
||||||
## 与其他方案的比较
|
|
||||||
|
|
||||||
### SigNoz vs Prometheus
|
### SigNoz vs Prometheus
|
||||||
|
|
||||||
如果你只是需要监控指标(metrics),那Prometheus是不错的,但如果你要无缝的在metrics和traces之间切换,那目前把Prometheus & Jaeger串起来的体验并不好。
|
Prometheus 是一个针对 metrics 监控的强大工具。但是如果你想无缝的切换 metrics 和 traces 查询,你当前大概率需要在 Prometheus 和 Jaeger 之间切换。
|
||||||
|
|
||||||
我们的目标是为metrics和traces提供统一的UI - 类似于Datadog这样的Saas厂提供的方案。并且能够对trace进行过滤和聚合,这是目前Jaeger缺失的功能。
|
我们的目标是提供一个客户观测 metrics 和 traces 整合的 UI。就像 SaaS 供应商 DataDog,它提供很多 jaeger 缺失的功能,比如针对 traces 过滤功能和聚合功能。
|
||||||
|
|
||||||
<p>  </p>
|
<p>  </p>
|
||||||
|
|
||||||
### SigNoz vs Jaeger
|
### SigNoz vs Jaeger
|
||||||
|
|
||||||
Jaeger只做分布式追踪(distributed tracing),SigNoz则支持metrics,traces,logs ,即可视化的三大支柱。
|
Jaeger 仅仅是一个分布式追踪系统。 但是 SigNoz 可以提供 metrics, traces 和 logs 所有的观测。
|
||||||
|
|
||||||
并且SigNoz有一些Jaeger没有的高级功能:
|
而且, SigNoz 相较于 Jaeger 拥有更对的高级功能:
|
||||||
|
|
||||||
- Jaegar UI无法在traces或过滤的traces上展示metrics。
|
- Jaegar UI 不能提供任何基于 traces 的 metrics 查询和过滤。
|
||||||
- Jaeger不能对过滤的traces做聚合操作。例如,拥有tag为customer_type='premium'的所有请求的p99延迟。而这个功能在SigNoz这儿是很容易实现。
|
|
||||||
|
- Jaeger 不能针对过滤的 traces 做聚合。 比如, p99 延迟的请求有个标签是 customer_type='premium'。 而这些在 SigNoz 可以轻松做到。
|
||||||
|
|
||||||
|
<p>  </p>
|
||||||
|
|
||||||
|
### SigNoz vs Elastic
|
||||||
|
|
||||||
|
- SigNoz 的日志管理是基于 ClickHouse 实现的,可以使日志的聚合更加高效,因为它是基于 OLAP 的数据仓储。
|
||||||
|
|
||||||
|
- 与 Elastic 相比,可以节省 50% 的资源成本
|
||||||
|
|
||||||
|
我们已经公布了 Elastic 和 SigNoz 的性能对比。 请点击 [这里](https://signoz.io/blog/logs-performance-benchmark/?utm_source=github-readme&utm_medium=logs-benchmark)
|
||||||
|
|
||||||
|
<p>  </p>
|
||||||
|
|
||||||
|
### SigNoz vs Loki
|
||||||
|
|
||||||
|
- SigNoz 支持大容量高基数的聚合,但是 loki 是不支持的。
|
||||||
|
|
||||||
|
- SigNoz 支持索引的高基数查询,并且对索引没有数量限制,而 Loki 会在添加部分索引后到达最大上限。
|
||||||
|
|
||||||
|
- 相较于 SigNoz,Loki 在搜索大量数据下既困难又缓慢。
|
||||||
|
|
||||||
|
我们已经发布了基准测试对比 Loki 和 SigNoz 性能。请点击 [这里](https://signoz.io/blog/logs-performance-benchmark/?utm_source=github-readme&utm_medium=logs-benchmark)
|
||||||
|
|
||||||
<br /><br />
|
<br /><br />
|
||||||
|
|
||||||
<img align="left" src="https://signoz-public.s3.us-east-2.amazonaws.com/Contributors.svg" width="50px" />
|
|
||||||
|
|
||||||
## 贡献
|
## 贡献
|
||||||
|
|
||||||
|
我们 ❤️ 你的贡献,无论大小。 请先阅读 [CONTRIBUTING.md](CONTRIBUTING.md) 再开始给 SigNoz 做贡献。
|
||||||
|
|
||||||
我们 ❤️ 任何贡献无论大小。 请阅读 [CONTRIBUTING.md](CONTRIBUTING.md) 然后开始给Signoz做贡献。
|
如果你不知道如何开始? 只需要在 [slack 社区](https://signoz.io/slack) 通过 `#contributing` 频道联系我们。
|
||||||
|
|
||||||
还不清楚怎么开始? 只需在[slack社区](https://signoz.io/slack)的`#contributing`频道里ping我们。
|
### 项目维护人员
|
||||||
|
|
||||||
### Project maintainers
|
#### 后端
|
||||||
|
|
||||||
#### Backend
|
|
||||||
|
|
||||||
- [Ankit Nayan](https://github.com/ankitnayan)
|
- [Ankit Nayan](https://github.com/ankitnayan)
|
||||||
- [Nityananda Gohain](https://github.com/nityanandagohain)
|
- [Nityananda Gohain](https://github.com/nityanandagohain)
|
||||||
- [Srikanth Chekuri](https://github.com/srikanthccv)
|
- [Srikanth Chekuri](https://github.com/srikanthccv)
|
||||||
- [Vishal Sharma](https://github.com/makeavish)
|
- [Vishal Sharma](https://github.com/makeavish)
|
||||||
|
|
||||||
#### Frontend
|
#### 前端
|
||||||
|
|
||||||
- [Palash Gupta](https://github.com/palashgdev)
|
- [Palash Gupta](https://github.com/palashgdev)
|
||||||
|
|
||||||
#### DevOps
|
#### 运维开发
|
||||||
|
|
||||||
- [Prashant Shahi](https://github.com/prashant-shahi)
|
- [Prashant Shahi](https://github.com/prashant-shahi)
|
||||||
|
|
||||||
<br /><br />
|
<br /><br />
|
||||||
|
|
||||||
<img align="left" src="https://signoz-public.s3.us-east-2.amazonaws.com/DevelopingLocally.svg" width="50px" />
|
|
||||||
|
|
||||||
## 文档
|
## 文档
|
||||||
|
|
||||||
文档在这里:https://signoz.io/docs/. 如果你觉得有任何不清楚或者有文档缺失,请在Github里发一个问题,并使用标签 `documentation` 或者在社区stack频道里告诉我们。
|
你可以通过 https://signoz.io/docs/ 找到相关文档。如果你需要阐述问题或者发现一些确实的事件, 通过标签为 `documentation` 提交 Github 问题。或者通过 slack 社区频道。
|
||||||
|
|
||||||
<br /><br />
|
<br /><br />
|
||||||
|
|
||||||
<img align="left" src="https://signoz-public.s3.us-east-2.amazonaws.com/Contributing.svg" width="50px" />
|
|
||||||
|
|
||||||
## 社区
|
## 社区
|
||||||
|
|
||||||
加入[slack community](https://signoz.io/slack),了解更多关于分布式跟踪、可观察性(observability),以及SigNoz。同时与其他用户和贡献者一起交流。
|
加入 [slack 社区](https://signoz.io/slack) 去了解更多关于分布式追踪、可观测性系统 。或者与 SigNoz 其他用户和贡献者交流。
|
||||||
|
|
||||||
如果你有任何想法、问题或者反馈,请在[Github Discussions](https://github.com/SigNoz/signoz/discussions)分享给我们。
|
如果你有任何想法、问题、或者任何反馈, 请通过 [Github Discussions](https://github.com/SigNoz/signoz/discussions) 分享。
|
||||||
|
|
||||||
最后,感谢我们这些优秀的贡献者们。
|
不管怎么样,感谢这个项目的所有贡献者!
|
||||||
|
|
||||||
<a href="https://github.com/signoz/signoz/graphs/contributors">
|
<a href="https://github.com/signoz/signoz/graphs/contributors">
|
||||||
<img src="https://contrib.rocks/image?repo=signoz/signoz" />
|
<img src="https://contrib.rocks/image?repo=signoz/signoz" />
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -144,7 +144,7 @@ services:
|
|||||||
condition: on-failure
|
condition: on-failure
|
||||||
|
|
||||||
query-service:
|
query-service:
|
||||||
image: signoz/query-service:0.30.0
|
image: signoz/query-service:0.31.0
|
||||||
command:
|
command:
|
||||||
[
|
[
|
||||||
"-config=/root/config/prometheus.yml",
|
"-config=/root/config/prometheus.yml",
|
||||||
@ -184,7 +184,7 @@ services:
|
|||||||
<<: *clickhouse-depend
|
<<: *clickhouse-depend
|
||||||
|
|
||||||
frontend:
|
frontend:
|
||||||
image: signoz/frontend:0.30.0
|
image: signoz/frontend:0.31.0
|
||||||
deploy:
|
deploy:
|
||||||
restart_policy:
|
restart_policy:
|
||||||
condition: on-failure
|
condition: on-failure
|
||||||
@ -197,7 +197,7 @@ services:
|
|||||||
- ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf
|
- ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf
|
||||||
|
|
||||||
otel-collector:
|
otel-collector:
|
||||||
image: signoz/signoz-otel-collector:0.79.7
|
image: signoz/signoz-otel-collector:0.79.8
|
||||||
command:
|
command:
|
||||||
[
|
[
|
||||||
"--config=/etc/otel-collector-config.yaml",
|
"--config=/etc/otel-collector-config.yaml",
|
||||||
@ -230,7 +230,7 @@ services:
|
|||||||
<<: *clickhouse-depend
|
<<: *clickhouse-depend
|
||||||
|
|
||||||
otel-collector-metrics:
|
otel-collector-metrics:
|
||||||
image: signoz/signoz-otel-collector:0.79.7
|
image: signoz/signoz-otel-collector:0.79.8
|
||||||
command:
|
command:
|
||||||
[
|
[
|
||||||
"--config=/etc/otel-collector-metrics-config.yaml",
|
"--config=/etc/otel-collector-metrics-config.yaml",
|
||||||
|
@ -48,7 +48,7 @@ services:
|
|||||||
# Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md`
|
# Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md`
|
||||||
otel-collector:
|
otel-collector:
|
||||||
container_name: signoz-otel-collector
|
container_name: signoz-otel-collector
|
||||||
image: signoz/signoz-otel-collector:0.79.7
|
image: signoz/signoz-otel-collector:0.79.8
|
||||||
command:
|
command:
|
||||||
[
|
[
|
||||||
"--config=/etc/otel-collector-config.yaml",
|
"--config=/etc/otel-collector-config.yaml",
|
||||||
@ -78,7 +78,7 @@ services:
|
|||||||
|
|
||||||
otel-collector-metrics:
|
otel-collector-metrics:
|
||||||
container_name: signoz-otel-collector-metrics
|
container_name: signoz-otel-collector-metrics
|
||||||
image: signoz/signoz-otel-collector:0.79.7
|
image: signoz/signoz-otel-collector:0.79.8
|
||||||
command:
|
command:
|
||||||
[
|
[
|
||||||
"--config=/etc/otel-collector-metrics-config.yaml",
|
"--config=/etc/otel-collector-metrics-config.yaml",
|
||||||
|
@ -8,7 +8,7 @@ services:
|
|||||||
dockerfile: "./Dockerfile"
|
dockerfile: "./Dockerfile"
|
||||||
args:
|
args:
|
||||||
LDFLAGS: ""
|
LDFLAGS: ""
|
||||||
TARGETPLATFORM: "${LOCAL_GOOS}/${LOCAL_GOARCH}"
|
TARGETPLATFORM: "${GOOS}/${GOARCH}"
|
||||||
container_name: signoz-query-service
|
container_name: signoz-query-service
|
||||||
environment:
|
environment:
|
||||||
- ClickHouseUrl=tcp://clickhouse:9000
|
- ClickHouseUrl=tcp://clickhouse:9000
|
||||||
@ -52,8 +52,8 @@ services:
|
|||||||
context: "../../../frontend"
|
context: "../../../frontend"
|
||||||
dockerfile: "./Dockerfile"
|
dockerfile: "./Dockerfile"
|
||||||
args:
|
args:
|
||||||
TARGETOS: "${LOCAL_GOOS}"
|
TARGETOS: "${GOOS}"
|
||||||
TARGETPLATFORM: "${LOCAL_GOARCH}"
|
TARGETPLATFORM: "${GOARCH}"
|
||||||
container_name: signoz-frontend
|
container_name: signoz-frontend
|
||||||
environment:
|
environment:
|
||||||
- FRONTEND_API_ENDPOINT=http://query-service:8080
|
- FRONTEND_API_ENDPOINT=http://query-service:8080
|
||||||
|
@ -162,7 +162,7 @@ services:
|
|||||||
# Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md`
|
# Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md`
|
||||||
|
|
||||||
query-service:
|
query-service:
|
||||||
image: signoz/query-service:${DOCKER_TAG:-0.30.0}
|
image: signoz/query-service:${DOCKER_TAG:-0.31.0}
|
||||||
container_name: signoz-query-service
|
container_name: signoz-query-service
|
||||||
command:
|
command:
|
||||||
[
|
[
|
||||||
@ -201,7 +201,7 @@ services:
|
|||||||
<<: *clickhouse-depend
|
<<: *clickhouse-depend
|
||||||
|
|
||||||
frontend:
|
frontend:
|
||||||
image: signoz/frontend:${DOCKER_TAG:-0.30.0}
|
image: signoz/frontend:${DOCKER_TAG:-0.31.0}
|
||||||
container_name: signoz-frontend
|
container_name: signoz-frontend
|
||||||
restart: on-failure
|
restart: on-failure
|
||||||
depends_on:
|
depends_on:
|
||||||
@ -213,7 +213,7 @@ services:
|
|||||||
- ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf
|
- ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf
|
||||||
|
|
||||||
otel-collector:
|
otel-collector:
|
||||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.79.7}
|
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.79.8}
|
||||||
container_name: signoz-otel-collector
|
container_name: signoz-otel-collector
|
||||||
command:
|
command:
|
||||||
[
|
[
|
||||||
@ -244,7 +244,7 @@ services:
|
|||||||
<<: *clickhouse-depend
|
<<: *clickhouse-depend
|
||||||
|
|
||||||
otel-collector-metrics:
|
otel-collector-metrics:
|
||||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.79.7}
|
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.79.8}
|
||||||
container_name: signoz-otel-collector-metrics
|
container_name: signoz-otel-collector-metrics
|
||||||
command:
|
command:
|
||||||
[
|
[
|
||||||
|
@ -1,40 +1,20 @@
|
|||||||
FROM golang:1.21-bookworm AS builder
|
|
||||||
|
|
||||||
# LD_FLAGS is passed as argument from Makefile. It will be empty, if no argument passed
|
|
||||||
ARG LD_FLAGS
|
|
||||||
ARG TARGETPLATFORM
|
|
||||||
|
|
||||||
ENV CGO_ENABLED=1
|
|
||||||
ENV GOPATH=/go
|
|
||||||
|
|
||||||
RUN export GOOS=$(echo ${TARGETPLATFORM} | cut -d / -f1) && \
|
|
||||||
export GOARCH=$(echo ${TARGETPLATFORM} | cut -d / -f2)
|
|
||||||
|
|
||||||
# Prepare and enter src directory
|
|
||||||
WORKDIR /go/src/github.com/signoz/signoz
|
|
||||||
|
|
||||||
# Add the sources and proceed with build
|
|
||||||
ADD . .
|
|
||||||
RUN cd ee/query-service \
|
|
||||||
&& go build -tags timetzdata -a -o ./bin/query-service \
|
|
||||||
-ldflags "-linkmode external -extldflags '-static' -s -w $LD_FLAGS" \
|
|
||||||
&& chmod +x ./bin/query-service
|
|
||||||
|
|
||||||
|
|
||||||
# use a minimal alpine image
|
# use a minimal alpine image
|
||||||
FROM alpine:3.16.7
|
FROM alpine:3.17
|
||||||
|
|
||||||
# Add Maintainer Info
|
# Add Maintainer Info
|
||||||
LABEL maintainer="signoz"
|
LABEL maintainer="signoz"
|
||||||
|
|
||||||
|
# define arguments that can be passed during build time
|
||||||
|
ARG TARGETOS TARGETARCH
|
||||||
|
|
||||||
# add ca-certificates in case you need them
|
# add ca-certificates in case you need them
|
||||||
RUN apk update && apk add ca-certificates && rm -rf /var/cache/apk/*
|
RUN apk update && apk add ca-certificates && rm -rf /var/cache/apk/*
|
||||||
|
|
||||||
# set working directory
|
# set working directory
|
||||||
WORKDIR /root
|
WORKDIR /root
|
||||||
|
|
||||||
# copy the binary from builder
|
# copy the query-service binary
|
||||||
COPY --from=builder /go/src/github.com/signoz/signoz/ee/query-service/bin/query-service .
|
COPY ee/query-service/bin/query-service-${TARGETOS}-${TARGETARCH} /root/query-service
|
||||||
|
|
||||||
# copy prometheus YAML config
|
# copy prometheus YAML config
|
||||||
COPY pkg/query-service/config/prometheus.yml /root/config/prometheus.yml
|
COPY pkg/query-service/config/prometheus.yml /root/config/prometheus.yml
|
||||||
@ -45,7 +25,6 @@ RUN chmod 755 /root /root/query-service
|
|||||||
# run the binary
|
# run the binary
|
||||||
ENTRYPOINT ["./query-service"]
|
ENTRYPOINT ["./query-service"]
|
||||||
|
|
||||||
CMD ["-config", "../config/prometheus.yml"]
|
CMD ["-config", "/root/config/prometheus.yml"]
|
||||||
# CMD ["./query-service -config /root/config/prometheus.yml"]
|
|
||||||
|
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
|
@ -8,6 +8,7 @@ import (
|
|||||||
"go.signoz.io/signoz/ee/query-service/dao"
|
"go.signoz.io/signoz/ee/query-service/dao"
|
||||||
"go.signoz.io/signoz/ee/query-service/interfaces"
|
"go.signoz.io/signoz/ee/query-service/interfaces"
|
||||||
"go.signoz.io/signoz/ee/query-service/license"
|
"go.signoz.io/signoz/ee/query-service/license"
|
||||||
|
"go.signoz.io/signoz/ee/query-service/usage"
|
||||||
baseapp "go.signoz.io/signoz/pkg/query-service/app"
|
baseapp "go.signoz.io/signoz/pkg/query-service/app"
|
||||||
"go.signoz.io/signoz/pkg/query-service/app/logparsingpipeline"
|
"go.signoz.io/signoz/pkg/query-service/app/logparsingpipeline"
|
||||||
"go.signoz.io/signoz/pkg/query-service/cache"
|
"go.signoz.io/signoz/pkg/query-service/cache"
|
||||||
@ -27,6 +28,7 @@ type APIHandlerOptions struct {
|
|||||||
DialTimeout time.Duration
|
DialTimeout time.Duration
|
||||||
AppDao dao.ModelDao
|
AppDao dao.ModelDao
|
||||||
RulesManager *rules.Manager
|
RulesManager *rules.Manager
|
||||||
|
UsageManager *usage.Manager
|
||||||
FeatureFlags baseint.FeatureLookup
|
FeatureFlags baseint.FeatureLookup
|
||||||
LicenseManager *license.Manager
|
LicenseManager *license.Manager
|
||||||
LogsParsingPipelineController *logparsingpipeline.LogParsingPipelineController
|
LogsParsingPipelineController *logparsingpipeline.LogParsingPipelineController
|
||||||
@ -82,6 +84,10 @@ func (ah *APIHandler) LM() *license.Manager {
|
|||||||
return ah.opts.LicenseManager
|
return ah.opts.LicenseManager
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ah *APIHandler) UM() *usage.Manager {
|
||||||
|
return ah.opts.UsageManager
|
||||||
|
}
|
||||||
|
|
||||||
func (ah *APIHandler) AppDao() dao.ModelDao {
|
func (ah *APIHandler) AppDao() dao.ModelDao {
|
||||||
return ah.opts.AppDao
|
return ah.opts.AppDao
|
||||||
}
|
}
|
||||||
@ -150,6 +156,13 @@ func (ah *APIHandler) RegisterRoutes(router *mux.Router, am *baseapp.AuthMiddlew
|
|||||||
router.HandleFunc("/api/v1/pat", am.OpenAccess(ah.getPATs)).Methods(http.MethodGet)
|
router.HandleFunc("/api/v1/pat", am.OpenAccess(ah.getPATs)).Methods(http.MethodGet)
|
||||||
router.HandleFunc("/api/v1/pat/{id}", am.OpenAccess(ah.deletePAT)).Methods(http.MethodDelete)
|
router.HandleFunc("/api/v1/pat/{id}", am.OpenAccess(ah.deletePAT)).Methods(http.MethodDelete)
|
||||||
|
|
||||||
|
router.HandleFunc("/api/v1/checkout", am.AdminAccess(ah.checkout)).Methods(http.MethodPost)
|
||||||
|
router.HandleFunc("/api/v1/billing", am.AdminAccess(ah.getBilling)).Methods(http.MethodGet)
|
||||||
|
|
||||||
|
router.HandleFunc("/api/v2/licenses",
|
||||||
|
am.ViewAccess(ah.listLicensesV2)).
|
||||||
|
Methods(http.MethodGet)
|
||||||
|
|
||||||
ah.APIHandler.RegisterRoutes(router, am)
|
ah.APIHandler.RegisterRoutes(router, am)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -4,10 +4,44 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"go.signoz.io/signoz/ee/query-service/model"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"go.signoz.io/signoz/ee/query-service/constants"
|
||||||
|
"go.signoz.io/signoz/ee/query-service/model"
|
||||||
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type tierBreakdown struct {
|
||||||
|
UnitPrice float64 `json:"unitPrice"`
|
||||||
|
Quantity int64 `json:"quantity"`
|
||||||
|
TierStart int64 `json:"tierStart"`
|
||||||
|
TierEnd int64 `json:"tierEnd"`
|
||||||
|
TierCost float64 `json:"tierCost"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type usageResponse struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Unit string `json:"unit"`
|
||||||
|
Tiers []tierBreakdown `json:"tiers"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type details struct {
|
||||||
|
Total float64 `json:"total"`
|
||||||
|
Breakdown []usageResponse `json:"breakdown"`
|
||||||
|
BaseFee float64 `json:"baseFee"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type billingDetails struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Data struct {
|
||||||
|
BillingPeriodStart int64 `json:"billingPeriodStart"`
|
||||||
|
BillingPeriodEnd int64 `json:"billingPeriodEnd"`
|
||||||
|
Details details `json:"details"`
|
||||||
|
Discount float64 `json:"discount"`
|
||||||
|
} `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
func (ah *APIHandler) listLicenses(w http.ResponseWriter, r *http.Request) {
|
func (ah *APIHandler) listLicenses(w http.ResponseWriter, r *http.Request) {
|
||||||
licenses, apiError := ah.LM().GetLicenses(context.Background())
|
licenses, apiError := ah.LM().GetLicenses(context.Background())
|
||||||
if apiError != nil {
|
if apiError != nil {
|
||||||
@ -38,3 +72,150 @@ func (ah *APIHandler) applyLicense(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
ah.Respond(w, license)
|
ah.Respond(w, license)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ah *APIHandler) checkout(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
|
type checkoutResponse struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Data struct {
|
||||||
|
RedirectURL string `json:"redirectURL"`
|
||||||
|
} `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
hClient := &http.Client{}
|
||||||
|
req, err := http.NewRequest("POST", constants.LicenseSignozIo+"/checkout", r.Body)
|
||||||
|
if err != nil {
|
||||||
|
RespondError(w, model.InternalError(err), nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
req.Header.Add("X-SigNoz-SecretKey", constants.LicenseAPIKey)
|
||||||
|
licenseResp, err := hClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
RespondError(w, model.InternalError(err), nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// decode response body
|
||||||
|
var resp checkoutResponse
|
||||||
|
if err := json.NewDecoder(licenseResp.Body).Decode(&resp); err != nil {
|
||||||
|
RespondError(w, model.InternalError(err), nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ah.Respond(w, resp.Data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ah *APIHandler) getBilling(w http.ResponseWriter, r *http.Request) {
|
||||||
|
licenseKey := r.URL.Query().Get("licenseKey")
|
||||||
|
|
||||||
|
if licenseKey == "" {
|
||||||
|
RespondError(w, model.BadRequest(fmt.Errorf("license key is required")), nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
billingURL := fmt.Sprintf("%s/usage?licenseKey=%s", constants.LicenseSignozIo, licenseKey)
|
||||||
|
|
||||||
|
hClient := &http.Client{}
|
||||||
|
req, err := http.NewRequest("GET", billingURL, nil)
|
||||||
|
if err != nil {
|
||||||
|
RespondError(w, model.InternalError(err), nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
req.Header.Add("X-SigNoz-SecretKey", constants.LicenseAPIKey)
|
||||||
|
billingResp, err := hClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
RespondError(w, model.InternalError(err), nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// decode response body
|
||||||
|
var billingResponse billingDetails
|
||||||
|
if err := json.NewDecoder(billingResp.Body).Decode(&billingResponse); err != nil {
|
||||||
|
RespondError(w, model.InternalError(err), nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(srikanthccv):Fetch the current day usage and add it to the response
|
||||||
|
ah.Respond(w, billingResponse.Data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ah *APIHandler) listLicensesV2(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
|
licenses, apiError := ah.LM().GetLicenses(context.Background())
|
||||||
|
if apiError != nil {
|
||||||
|
RespondError(w, apiError, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := model.Licenses{
|
||||||
|
TrialStart: -1,
|
||||||
|
TrialEnd: -1,
|
||||||
|
OnTrial: false,
|
||||||
|
WorkSpaceBlock: false,
|
||||||
|
Licenses: licenses,
|
||||||
|
}
|
||||||
|
|
||||||
|
var currentActiveLicenseKey string
|
||||||
|
|
||||||
|
for _, license := range licenses {
|
||||||
|
if license.IsCurrent {
|
||||||
|
currentActiveLicenseKey = license.Key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// For the case when no license is applied i.e community edition
|
||||||
|
// There will be no trial details or license details
|
||||||
|
if currentActiveLicenseKey == "" {
|
||||||
|
ah.Respond(w, resp)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch trial details
|
||||||
|
hClient := &http.Client{}
|
||||||
|
url := fmt.Sprintf("%s/trial?licenseKey=%s", constants.LicenseSignozIo, currentActiveLicenseKey)
|
||||||
|
req, err := http.NewRequest("GET", url, nil)
|
||||||
|
if err != nil {
|
||||||
|
zap.S().Error("Error while creating request for trial details", err)
|
||||||
|
// If there is an error in fetching trial details, we will still return the license details
|
||||||
|
// to avoid blocking the UI
|
||||||
|
ah.Respond(w, resp)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
req.Header.Add("X-SigNoz-SecretKey", constants.LicenseAPIKey)
|
||||||
|
trialResp, err := hClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
zap.S().Error("Error while fetching trial details", err)
|
||||||
|
// If there is an error in fetching trial details, we will still return the license details
|
||||||
|
// to avoid incorrectly blocking the UI
|
||||||
|
ah.Respond(w, resp)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer trialResp.Body.Close()
|
||||||
|
|
||||||
|
trialRespBody, err := io.ReadAll(trialResp.Body)
|
||||||
|
|
||||||
|
if err != nil || trialResp.StatusCode != http.StatusOK {
|
||||||
|
zap.S().Error("Error while fetching trial details", err)
|
||||||
|
// If there is an error in fetching trial details, we will still return the license details
|
||||||
|
// to avoid incorrectly blocking the UI
|
||||||
|
ah.Respond(w, resp)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// decode response body
|
||||||
|
var trialRespData model.SubscriptionServerResp
|
||||||
|
|
||||||
|
if err := json.Unmarshal(trialRespBody, &trialRespData); err != nil {
|
||||||
|
zap.S().Error("Error while decoding trial details", err)
|
||||||
|
// If there is an error in fetching trial details, we will still return the license details
|
||||||
|
// to avoid incorrectly blocking the UI
|
||||||
|
ah.Respond(w, resp)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp.TrialStart = trialRespData.Data.TrialStart
|
||||||
|
resp.TrialEnd = trialRespData.Data.TrialEnd
|
||||||
|
resp.OnTrial = trialRespData.Data.OnTrial
|
||||||
|
resp.WorkSpaceBlock = trialRespData.Data.WorkSpaceBlock
|
||||||
|
|
||||||
|
ah.Respond(w, resp)
|
||||||
|
}
|
||||||
|
@ -217,6 +217,7 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
|
|||||||
DialTimeout: serverOptions.DialTimeout,
|
DialTimeout: serverOptions.DialTimeout,
|
||||||
AppDao: modelDao,
|
AppDao: modelDao,
|
||||||
RulesManager: rm,
|
RulesManager: rm,
|
||||||
|
UsageManager: usageManager,
|
||||||
FeatureFlags: lm,
|
FeatureFlags: lm,
|
||||||
LicenseManager: lm,
|
LicenseManager: lm,
|
||||||
LogsParsingPipelineController: logParsingPipelineController,
|
LogsParsingPipelineController: logParsingPipelineController,
|
||||||
|
@ -9,6 +9,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var LicenseSignozIo = "https://license.signoz.io/api/v1"
|
var LicenseSignozIo = "https://license.signoz.io/api/v1"
|
||||||
|
var LicenseAPIKey = GetOrDefaultEnv("SIGNOZ_LICENSE_API_KEY", "")
|
||||||
|
|
||||||
var SpanLimitStr = GetOrDefaultEnv("SPAN_LIMIT", "5000")
|
var SpanLimitStr = GetOrDefaultEnv("SPAN_LIMIT", "5000")
|
||||||
|
|
||||||
|
@ -89,3 +89,16 @@ func (l *License) ParseFeatures() {
|
|||||||
l.FeatureSet = BasicPlan
|
l.FeatureSet = BasicPlan
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Licenses struct {
|
||||||
|
TrialStart int64 `json:"trialStart"`
|
||||||
|
TrialEnd int64 `json:"trialEnd"`
|
||||||
|
OnTrial bool `json:"onTrial"`
|
||||||
|
WorkSpaceBlock bool `json:"workSpaceBlock"`
|
||||||
|
Licenses []License `json:"licenses"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SubscriptionServerResp struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Data Licenses `json:"data"`
|
||||||
|
}
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
node_modules
|
node_modules
|
||||||
.vscode
|
.vscode
|
||||||
build
|
|
||||||
.git
|
.git
|
||||||
|
@ -1,38 +1,17 @@
|
|||||||
# Builder stage
|
FROM nginx:1.25.2-alpine
|
||||||
FROM node:16.15.0 as builder
|
|
||||||
|
|
||||||
# Add Maintainer Info
|
# Add Maintainer Info
|
||||||
LABEL maintainer="signoz"
|
LABEL maintainer="signoz"
|
||||||
|
|
||||||
ARG TARGETOS=linux
|
# Set working directory
|
||||||
ARG TARGETARCH
|
|
||||||
|
|
||||||
WORKDIR /frontend
|
WORKDIR /frontend
|
||||||
|
|
||||||
# Copy the package.json and .yarnrc files prior to install dependencies
|
|
||||||
COPY package.json ./
|
|
||||||
# Copy lock file
|
|
||||||
COPY yarn.lock ./
|
|
||||||
COPY .yarnrc ./
|
|
||||||
|
|
||||||
# Install the dependencies and make the folder
|
|
||||||
RUN CI=1 yarn install
|
|
||||||
|
|
||||||
COPY . .
|
|
||||||
|
|
||||||
# Build the project and copy the files
|
|
||||||
RUN yarn build
|
|
||||||
|
|
||||||
|
|
||||||
FROM nginx:1.25.2-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/*
|
RUN rm -rf /usr/share/nginx/html/*
|
||||||
|
|
||||||
# Copy from the stahg 1
|
# Copy custom nginx config and static files
|
||||||
COPY --from=builder /frontend/build /usr/share/nginx/html
|
COPY conf/default.conf /etc/nginx/conf.d/default.conf
|
||||||
|
COPY build /usr/share/nginx/html
|
||||||
|
|
||||||
EXPOSE 3301
|
EXPOSE 3301
|
||||||
|
|
||||||
|
@ -13,5 +13,12 @@
|
|||||||
"import_dashboard_by_pasting": "Import dashboard by pasting JSON or importing JSON file",
|
"import_dashboard_by_pasting": "Import dashboard by pasting JSON or importing JSON file",
|
||||||
"error_loading_json": "Error loading JSON file",
|
"error_loading_json": "Error loading JSON file",
|
||||||
"empty_json_not_allowed": "Empty JSON is not allowed",
|
"empty_json_not_allowed": "Empty JSON is not allowed",
|
||||||
"new_dashboard_title": "Sample Title"
|
"new_dashboard_title": "Sample Title",
|
||||||
|
"layout_saved_successfully": "Layout saved successfully",
|
||||||
|
"add_panel": "Add Panel",
|
||||||
|
"save_layout": "Save Layout",
|
||||||
|
"variable_updated_successfully": "Variable updated successfully",
|
||||||
|
"error_while_updating_variable": "Error while updating variable",
|
||||||
|
"dashboard_has_been_updated": "Dashboard has been updated",
|
||||||
|
"do_you_want_to_refresh_the_dashboard": "Do you want to refresh the dashboard?"
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
{
|
{
|
||||||
"general": "General",
|
"general": "General",
|
||||||
"alert_channels": "Alert Channels",
|
"alert_channels": "Alert Channels",
|
||||||
"organization_settings": "Organization Settings",
|
"organization_settings": "Organization Settings",
|
||||||
"my_settings": "My Settings",
|
"ingestion_settings": "Ingestion Settings",
|
||||||
"overview_metrics": "Overview Metrics",
|
"my_settings": "My Settings",
|
||||||
"dbcall_metrics": "Database Calls",
|
"overview_metrics": "Overview Metrics",
|
||||||
"external_metrics": "External Calls",
|
"dbcall_metrics": "Database Calls",
|
||||||
"pipeline": "Pipeline",
|
"external_metrics": "External Calls",
|
||||||
"pipelines": "Pipelines",
|
"pipeline": "Pipeline",
|
||||||
"archives": "Archives",
|
"pipelines": "Pipelines",
|
||||||
"logs_to_metrics": "Logs To Metrics"
|
"archives": "Archives",
|
||||||
}
|
"logs_to_metrics": "Logs To Metrics"
|
||||||
|
}
|
||||||
|
@ -1,37 +1,38 @@
|
|||||||
{
|
{
|
||||||
"SIGN_UP": "SigNoz | Sign Up",
|
"SIGN_UP": "SigNoz | Sign Up",
|
||||||
"LOGIN": "SigNoz | Login",
|
"LOGIN": "SigNoz | Login",
|
||||||
"GET_STARTED": "SigNoz | Get Started",
|
"GET_STARTED": "SigNoz | Get Started",
|
||||||
"SERVICE_METRICS": "SigNoz | Service Metrics",
|
"SERVICE_METRICS": "SigNoz | Service Metrics",
|
||||||
"SERVICE_MAP": "SigNoz | Service Map",
|
"SERVICE_MAP": "SigNoz | Service Map",
|
||||||
"TRACE": "SigNoz | Trace",
|
"TRACE": "SigNoz | Trace",
|
||||||
"TRACE_DETAIL": "SigNoz | Trace Detail",
|
"TRACE_DETAIL": "SigNoz | Trace Detail",
|
||||||
"TRACES_EXPLORER": "SigNoz | Traces Explorer",
|
"TRACES_EXPLORER": "SigNoz | Traces Explorer",
|
||||||
"SETTINGS": "SigNoz | Settings",
|
"SETTINGS": "SigNoz | Settings",
|
||||||
"USAGE_EXPLORER": "SigNoz | Usage Explorer",
|
"USAGE_EXPLORER": "SigNoz | Usage Explorer",
|
||||||
"APPLICATION": "SigNoz | Home",
|
"APPLICATION": "SigNoz | Home",
|
||||||
"ALL_DASHBOARD": "SigNoz | All Dashboards",
|
"ALL_DASHBOARD": "SigNoz | All Dashboards",
|
||||||
"DASHBOARD": "SigNoz | Dashboard",
|
"DASHBOARD": "SigNoz | Dashboard",
|
||||||
"DASHBOARD_WIDGET": "SigNoz | Dashboard Widget",
|
"DASHBOARD_WIDGET": "SigNoz | Dashboard Widget",
|
||||||
"EDIT_ALERTS": "SigNoz | Edit Alerts",
|
"EDIT_ALERTS": "SigNoz | Edit Alerts",
|
||||||
"LIST_ALL_ALERT": "SigNoz | All Alerts",
|
"LIST_ALL_ALERT": "SigNoz | All Alerts",
|
||||||
"ALERTS_NEW": "SigNoz | New Alert",
|
"ALERTS_NEW": "SigNoz | New Alert",
|
||||||
"ALL_CHANNELS": "SigNoz | All Channels",
|
"ALL_CHANNELS": "SigNoz | All Channels",
|
||||||
"CHANNELS_NEW": "SigNoz | New Channel",
|
"CHANNELS_NEW": "SigNoz | New Channel",
|
||||||
"CHANNELS_EDIT": "SigNoz | Edit Channel",
|
"CHANNELS_EDIT": "SigNoz | Edit Channel",
|
||||||
"ALL_ERROR": "SigNoz | All Errors",
|
"ALL_ERROR": "SigNoz | All Errors",
|
||||||
"ERROR_DETAIL": "SigNoz | Error Detail",
|
"ERROR_DETAIL": "SigNoz | Error Detail",
|
||||||
"VERSION": "SigNoz | Version",
|
"VERSION": "SigNoz | Version",
|
||||||
"MY_SETTINGS": "SigNoz | My Settings",
|
"MY_SETTINGS": "SigNoz | My Settings",
|
||||||
"ORG_SETTINGS": "SigNoz | Organization Settings",
|
"ORG_SETTINGS": "SigNoz | Organization Settings",
|
||||||
"SOMETHING_WENT_WRONG": "SigNoz | Something Went Wrong",
|
"INGESTION_SETTINGS": "SigNoz | Ingestion Settings",
|
||||||
"UN_AUTHORIZED": "SigNoz | Unauthorized",
|
"SOMETHING_WENT_WRONG": "SigNoz | Something Went Wrong",
|
||||||
"NOT_FOUND": "SigNoz | Page Not Found",
|
"UN_AUTHORIZED": "SigNoz | Unauthorized",
|
||||||
"LOGS": "SigNoz | Logs",
|
"NOT_FOUND": "SigNoz | Page Not Found",
|
||||||
"LOGS_EXPLORER": "SigNoz | Logs Explorer",
|
"LOGS": "SigNoz | Logs",
|
||||||
"LIVE_LOGS": "SigNoz | Live Logs",
|
"LOGS_EXPLORER": "SigNoz | Logs Explorer",
|
||||||
"HOME_PAGE": "Open source Observability Platform | SigNoz",
|
"LIVE_LOGS": "SigNoz | Live Logs",
|
||||||
"PASSWORD_RESET": "SigNoz | Password Reset",
|
"HOME_PAGE": "Open source Observability Platform | SigNoz",
|
||||||
"LIST_LICENSES": "SigNoz | List of Licenses",
|
"PASSWORD_RESET": "SigNoz | Password Reset",
|
||||||
"DEFAULT": "Open source Observability Platform | SigNoz"
|
"LIST_LICENSES": "SigNoz | List of Licenses",
|
||||||
}
|
"DEFAULT": "Open source Observability Platform | SigNoz"
|
||||||
|
}
|
||||||
|
@ -13,5 +13,12 @@
|
|||||||
"import_dashboard_by_pasting": "Import dashboard by pasting JSON or importing JSON file",
|
"import_dashboard_by_pasting": "Import dashboard by pasting JSON or importing JSON file",
|
||||||
"error_loading_json": "Error loading JSON file",
|
"error_loading_json": "Error loading JSON file",
|
||||||
"empty_json_not_allowed": "Empty JSON is not allowed",
|
"empty_json_not_allowed": "Empty JSON is not allowed",
|
||||||
"new_dashboard_title": "Sample Title"
|
"new_dashboard_title": "Sample Title",
|
||||||
|
"layout_saved_successfully": "Layout saved successfully",
|
||||||
|
"add_panel": "Add Panel",
|
||||||
|
"save_layout": "Save Layout",
|
||||||
|
"variable_updated_successfully": "Variable updated successfully",
|
||||||
|
"error_while_updating_variable": "Error while updating variable",
|
||||||
|
"dashboard_has_been_updated": "Dashboard has been updated",
|
||||||
|
"do_you_want_to_refresh_the_dashboard": "Do you want to refresh the dashboard?"
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
{
|
{
|
||||||
"general": "General",
|
"general": "General",
|
||||||
"alert_channels": "Alert Channels",
|
"alert_channels": "Alert Channels",
|
||||||
"organization_settings": "Organization Settings",
|
"organization_settings": "Organization Settings",
|
||||||
"my_settings": "My Settings",
|
"ingestion_settings": "Ingestion Settings",
|
||||||
"overview_metrics": "Overview Metrics",
|
"my_settings": "My Settings",
|
||||||
"dbcall_metrics": "Database Calls",
|
"overview_metrics": "Overview Metrics",
|
||||||
"external_metrics": "External Calls",
|
"dbcall_metrics": "Database Calls",
|
||||||
"pipeline": "Pipeline",
|
"external_metrics": "External Calls",
|
||||||
"pipelines": "Pipelines",
|
"pipeline": "Pipeline",
|
||||||
"archives": "Archives",
|
"pipelines": "Pipelines",
|
||||||
"logs_to_metrics": "Logs To Metrics"
|
"archives": "Archives",
|
||||||
}
|
"logs_to_metrics": "Logs To Metrics"
|
||||||
|
}
|
||||||
|
@ -1,37 +1,38 @@
|
|||||||
{
|
{
|
||||||
"SIGN_UP": "SigNoz | Sign Up",
|
"SIGN_UP": "SigNoz | Sign Up",
|
||||||
"LOGIN": "SigNoz | Login",
|
"LOGIN": "SigNoz | Login",
|
||||||
"SERVICE_METRICS": "SigNoz | Service Metrics",
|
"SERVICE_METRICS": "SigNoz | Service Metrics",
|
||||||
"SERVICE_MAP": "SigNoz | Service Map",
|
"SERVICE_MAP": "SigNoz | Service Map",
|
||||||
"GET_STARTED": "SigNoz | Get Started",
|
"GET_STARTED": "SigNoz | Get Started",
|
||||||
"TRACE": "SigNoz | Trace",
|
"TRACE": "SigNoz | Trace",
|
||||||
"TRACE_DETAIL": "SigNoz | Trace Detail",
|
"TRACE_DETAIL": "SigNoz | Trace Detail",
|
||||||
"TRACES_EXPLORER": "SigNoz | Traces Explorer",
|
"TRACES_EXPLORER": "SigNoz | Traces Explorer",
|
||||||
"SETTINGS": "SigNoz | Settings",
|
"SETTINGS": "SigNoz | Settings",
|
||||||
"USAGE_EXPLORER": "SigNoz | Usage Explorer",
|
"USAGE_EXPLORER": "SigNoz | Usage Explorer",
|
||||||
"APPLICATION": "SigNoz | Home",
|
"APPLICATION": "SigNoz | Home",
|
||||||
"ALL_DASHBOARD": "SigNoz | All Dashboards",
|
"ALL_DASHBOARD": "SigNoz | All Dashboards",
|
||||||
"DASHBOARD": "SigNoz | Dashboard",
|
"DASHBOARD": "SigNoz | Dashboard",
|
||||||
"DASHBOARD_WIDGET": "SigNoz | Dashboard Widget",
|
"DASHBOARD_WIDGET": "SigNoz | Dashboard Widget",
|
||||||
"EDIT_ALERTS": "SigNoz | Edit Alerts",
|
"EDIT_ALERTS": "SigNoz | Edit Alerts",
|
||||||
"LIST_ALL_ALERT": "SigNoz | All Alerts",
|
"LIST_ALL_ALERT": "SigNoz | All Alerts",
|
||||||
"ALERTS_NEW": "SigNoz | New Alert",
|
"ALERTS_NEW": "SigNoz | New Alert",
|
||||||
"ALL_CHANNELS": "SigNoz | All Channels",
|
"ALL_CHANNELS": "SigNoz | All Channels",
|
||||||
"CHANNELS_NEW": "SigNoz | New Channel",
|
"CHANNELS_NEW": "SigNoz | New Channel",
|
||||||
"CHANNELS_EDIT": "SigNoz | Edit Channel",
|
"CHANNELS_EDIT": "SigNoz | Edit Channel",
|
||||||
"ALL_ERROR": "SigNoz | All Errors",
|
"ALL_ERROR": "SigNoz | All Errors",
|
||||||
"ERROR_DETAIL": "SigNoz | Error Detail",
|
"ERROR_DETAIL": "SigNoz | Error Detail",
|
||||||
"VERSION": "SigNoz | Version",
|
"VERSION": "SigNoz | Version",
|
||||||
"MY_SETTINGS": "SigNoz | My Settings",
|
"MY_SETTINGS": "SigNoz | My Settings",
|
||||||
"ORG_SETTINGS": "SigNoz | Organization Settings",
|
"ORG_SETTINGS": "SigNoz | Organization Settings",
|
||||||
"SOMETHING_WENT_WRONG": "SigNoz | Something Went Wrong",
|
"INGESTION_SETTINGS": "SigNoz | Ingestion Settings",
|
||||||
"UN_AUTHORIZED": "SigNoz | Unauthorized",
|
"SOMETHING_WENT_WRONG": "SigNoz | Something Went Wrong",
|
||||||
"NOT_FOUND": "SigNoz | Page Not Found",
|
"UN_AUTHORIZED": "SigNoz | Unauthorized",
|
||||||
"LOGS": "SigNoz | Logs",
|
"NOT_FOUND": "SigNoz | Page Not Found",
|
||||||
"LOGS_EXPLORER": "SigNoz | Logs Explorer",
|
"LOGS": "SigNoz | Logs",
|
||||||
"LIVE_LOGS": "SigNoz | Live Logs",
|
"LOGS_EXPLORER": "SigNoz | Logs Explorer",
|
||||||
"HOME_PAGE": "Open source Observability Platform | SigNoz",
|
"LIVE_LOGS": "SigNoz | Live Logs",
|
||||||
"PASSWORD_RESET": "SigNoz | Password Reset",
|
"HOME_PAGE": "Open source Observability Platform | SigNoz",
|
||||||
"LIST_LICENSES": "SigNoz | List of Licenses",
|
"PASSWORD_RESET": "SigNoz | Password Reset",
|
||||||
"DEFAULT": "Open source Observability Platform | SigNoz"
|
"LIST_LICENSES": "SigNoz | List of Licenses",
|
||||||
}
|
"DEFAULT": "Open source Observability Platform | SigNoz"
|
||||||
|
}
|
||||||
|
@ -12,6 +12,7 @@ import useGetFeatureFlag from 'hooks/useGetFeatureFlag';
|
|||||||
import { NotificationProvider } from 'hooks/useNotifications';
|
import { NotificationProvider } from 'hooks/useNotifications';
|
||||||
import { ResourceProvider } from 'hooks/useResourceAttribute';
|
import { ResourceProvider } from 'hooks/useResourceAttribute';
|
||||||
import history from 'lib/history';
|
import history from 'lib/history';
|
||||||
|
import { DashboardProvider } from 'providers/Dashboard/Dashboard';
|
||||||
import { QueryBuilderProvider } from 'providers/QueryBuilder';
|
import { QueryBuilderProvider } from 'providers/QueryBuilder';
|
||||||
import { Suspense, useEffect, useState } from 'react';
|
import { Suspense, useEffect, useState } from 'react';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
@ -110,22 +111,24 @@ function App(): JSX.Element {
|
|||||||
<PrivateRoute>
|
<PrivateRoute>
|
||||||
<ResourceProvider>
|
<ResourceProvider>
|
||||||
<QueryBuilderProvider>
|
<QueryBuilderProvider>
|
||||||
<AppLayout>
|
<DashboardProvider>
|
||||||
<Suspense fallback={<Spinner size="large" tip="Loading..." />}>
|
<AppLayout>
|
||||||
<Switch>
|
<Suspense fallback={<Spinner size="large" tip="Loading..." />}>
|
||||||
{routes.map(({ path, component, exact }) => (
|
<Switch>
|
||||||
<Route
|
{routes.map(({ path, component, exact }) => (
|
||||||
key={`${path}`}
|
<Route
|
||||||
exact={exact}
|
key={`${path}`}
|
||||||
path={path}
|
exact={exact}
|
||||||
component={component}
|
path={path}
|
||||||
/>
|
component={component}
|
||||||
))}
|
/>
|
||||||
|
))}
|
||||||
|
|
||||||
<Route path="*" component={NotFound} />
|
<Route path="*" component={NotFound} />
|
||||||
</Switch>
|
</Switch>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</AppLayout>
|
</AppLayout>
|
||||||
|
</DashboardProvider>
|
||||||
</QueryBuilderProvider>
|
</QueryBuilderProvider>
|
||||||
</ResourceProvider>
|
</ResourceProvider>
|
||||||
</PrivateRoute>
|
</PrivateRoute>
|
||||||
|
@ -102,6 +102,10 @@ export const OrganizationSettings = Loadable(
|
|||||||
() => import(/* webpackChunkName: "All Settings" */ 'pages/Settings'),
|
() => import(/* webpackChunkName: "All Settings" */ 'pages/Settings'),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const IngestionSettings = Loadable(
|
||||||
|
() => import(/* webpackChunkName: "Ingestion Settings" */ 'pages/Settings'),
|
||||||
|
);
|
||||||
|
|
||||||
export const MySettings = Loadable(
|
export const MySettings = Loadable(
|
||||||
() => import(/* webpackChunkName: "All MySettings" */ 'pages/MySettings'),
|
() => import(/* webpackChunkName: "All MySettings" */ 'pages/MySettings'),
|
||||||
);
|
);
|
||||||
|
@ -11,6 +11,7 @@ import {
|
|||||||
EditAlertChannelsAlerts,
|
EditAlertChannelsAlerts,
|
||||||
EditRulesPage,
|
EditRulesPage,
|
||||||
ErrorDetails,
|
ErrorDetails,
|
||||||
|
IngestionSettings,
|
||||||
LicensePage,
|
LicensePage,
|
||||||
ListAllALertsPage,
|
ListAllALertsPage,
|
||||||
LiveLogs,
|
LiveLogs,
|
||||||
@ -214,6 +215,13 @@ const routes: AppRoutes[] = [
|
|||||||
isPrivate: true,
|
isPrivate: true,
|
||||||
key: 'ORG_SETTINGS',
|
key: 'ORG_SETTINGS',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: ROUTES.INGESTION_SETTINGS,
|
||||||
|
exact: true,
|
||||||
|
component: IngestionSettings,
|
||||||
|
isPrivate: true,
|
||||||
|
key: 'INGESTION_SETTINGS',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: ROUTES.MY_SETTINGS,
|
path: ROUTES.MY_SETTINGS,
|
||||||
exact: true,
|
exact: true,
|
||||||
|
@ -1,24 +1,9 @@
|
|||||||
import axios from 'api';
|
import axios from 'api';
|
||||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
import { PayloadProps, Props } from 'types/api/dashboard/delete';
|
||||||
import { AxiosError } from 'axios';
|
|
||||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
|
||||||
import { Props } from 'types/api/dashboard/delete';
|
|
||||||
|
|
||||||
const deleteDashboard = async (
|
const deleteDashboard = (props: Props): Promise<PayloadProps> =>
|
||||||
props: Props,
|
axios
|
||||||
): Promise<SuccessResponse<undefined> | ErrorResponse> => {
|
.delete<PayloadProps>(`/dashboards/${props.uuid}`)
|
||||||
try {
|
.then((response) => response.data);
|
||||||
const response = await axios.delete(`/dashboards/${props.uuid}`);
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: 200,
|
|
||||||
error: null,
|
|
||||||
message: response.data.status,
|
|
||||||
payload: response.data.data,
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
return ErrorResponseHandler(error as AxiosError);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export default deleteDashboard;
|
export default deleteDashboard;
|
||||||
|
@ -1,24 +1,11 @@
|
|||||||
import axios from 'api';
|
import axios from 'api';
|
||||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
import { ApiResponse } from 'types/api';
|
||||||
import { AxiosError } from 'axios';
|
import { Props } from 'types/api/dashboard/get';
|
||||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
import { Dashboard } from 'types/api/dashboard/getAll';
|
||||||
import { PayloadProps, Props } from 'types/api/dashboard/get';
|
|
||||||
|
|
||||||
const get = async (
|
const get = (props: Props): Promise<Dashboard> =>
|
||||||
props: Props,
|
axios
|
||||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
.get<ApiResponse<Dashboard>>(`/dashboards/${props.uuid}`)
|
||||||
try {
|
.then((res) => res.data.data);
|
||||||
const response = await axios.get(`/dashboards/${props.uuid}`);
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: 200,
|
|
||||||
error: null,
|
|
||||||
message: response.data.status,
|
|
||||||
payload: response.data.data,
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
return ErrorResponseHandler(error as AxiosError);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export default get;
|
export default get;
|
||||||
|
21
frontend/src/api/pipeline/preview.ts
Normal file
21
frontend/src/api/pipeline/preview.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import axios from 'api';
|
||||||
|
import { ILog } from 'types/api/logs/log';
|
||||||
|
import { PipelineData } from 'types/api/pipeline/def';
|
||||||
|
|
||||||
|
export interface PipelineSimulationRequest {
|
||||||
|
logs: ILog[];
|
||||||
|
pipelines: PipelineData[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PipelineSimulationResponse {
|
||||||
|
logs: ILog[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const simulatePipelineProcessing = async (
|
||||||
|
requestBody: PipelineSimulationRequest,
|
||||||
|
): Promise<PipelineSimulationResponse> =>
|
||||||
|
axios
|
||||||
|
.post('/logs/pipelines/preview', requestBody)
|
||||||
|
.then((res) => res.data.data);
|
||||||
|
|
||||||
|
export default simulatePipelineProcessing;
|
24
frontend/src/api/settings/getIngestionData.ts
Normal file
24
frontend/src/api/settings/getIngestionData.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import axios from 'api';
|
||||||
|
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||||
|
import { AxiosError } from 'axios';
|
||||||
|
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||||
|
import { IngestionResponseProps } from 'types/api/settings/ingestion';
|
||||||
|
|
||||||
|
const getIngestionData = async (): Promise<
|
||||||
|
SuccessResponse<IngestionResponseProps> | ErrorResponse
|
||||||
|
> => {
|
||||||
|
try {
|
||||||
|
const response = await axios.get(`/settings/ingestion_key`);
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 200,
|
||||||
|
error: null,
|
||||||
|
message: 'Success',
|
||||||
|
payload: response.data,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return ErrorResponseHandler(error as AxiosError);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default getIngestionData;
|
@ -1,10 +1,18 @@
|
|||||||
|
/* eslint-disable no-restricted-syntax */
|
||||||
/* eslint-disable react/jsx-props-no-spreading */
|
/* eslint-disable react/jsx-props-no-spreading */
|
||||||
|
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
||||||
|
import ReactMarkdown from 'react-markdown';
|
||||||
import { CodeProps } from 'react-markdown/lib/ast-to-react';
|
import { CodeProps } from 'react-markdown/lib/ast-to-react';
|
||||||
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
|
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
|
||||||
import { a11yDark } from 'react-syntax-highlighter/dist/cjs/styles/prism';
|
import { a11yDark } from 'react-syntax-highlighter/dist/cjs/styles/prism';
|
||||||
|
|
||||||
import CodeCopyBtn from './CodeCopyBtn/CodeCopyBtn';
|
import CodeCopyBtn from './CodeCopyBtn/CodeCopyBtn';
|
||||||
|
|
||||||
|
interface LinkProps {
|
||||||
|
href: string;
|
||||||
|
children: React.ReactElement;
|
||||||
|
}
|
||||||
|
|
||||||
function Pre({ children }: { children: React.ReactNode }): JSX.Element {
|
function Pre({ children }: { children: React.ReactNode }): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<pre className="code-snippet-container">
|
<pre className="code-snippet-container">
|
||||||
@ -40,4 +48,54 @@ function Code({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Code, Pre };
|
function Link({ href, children }: LinkProps): JSX.Element {
|
||||||
|
return (
|
||||||
|
<a href={href} target="_blank" rel="noopener noreferrer">
|
||||||
|
{children}
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const interpolateMarkdown = (
|
||||||
|
markdownContent: any,
|
||||||
|
variables: { [s: string]: unknown } | ArrayLike<unknown>,
|
||||||
|
) => {
|
||||||
|
let interpolatedContent = markdownContent;
|
||||||
|
|
||||||
|
const variableEntries = Object.entries(variables);
|
||||||
|
|
||||||
|
// Loop through variables and replace placeholders with values
|
||||||
|
for (const [key, value] of variableEntries) {
|
||||||
|
const placeholder = `{{${key}}}`;
|
||||||
|
const regex = new RegExp(placeholder, 'g');
|
||||||
|
interpolatedContent = interpolatedContent.replace(regex, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return interpolatedContent;
|
||||||
|
};
|
||||||
|
|
||||||
|
function MarkdownRenderer({
|
||||||
|
markdownContent,
|
||||||
|
variables,
|
||||||
|
}: {
|
||||||
|
markdownContent: any;
|
||||||
|
variables: any;
|
||||||
|
}): JSX.Element {
|
||||||
|
const interpolatedMarkdown = interpolateMarkdown(markdownContent, variables);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ReactMarkdown
|
||||||
|
components={{
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
a: Link,
|
||||||
|
pre: Pre,
|
||||||
|
code: Code,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{interpolatedMarkdown}
|
||||||
|
</ReactMarkdown>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Code, Link, MarkdownRenderer, Pre };
|
||||||
|
@ -0,0 +1,3 @@
|
|||||||
|
.overlay--text-wrap {
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
89
frontend/src/components/TextToolTip/TextToolTip.tsx
Normal file
89
frontend/src/components/TextToolTip/TextToolTip.tsx
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
import './TextToolTip.style.scss';
|
||||||
|
|
||||||
|
import { blue, grey } from '@ant-design/colors';
|
||||||
|
import {
|
||||||
|
QuestionCircleFilled,
|
||||||
|
QuestionCircleOutlined,
|
||||||
|
} from '@ant-design/icons';
|
||||||
|
import { Tooltip } from 'antd';
|
||||||
|
import { themeColors } from 'constants/theme';
|
||||||
|
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
import { popupContainer } from 'utils/selectPopupContainer';
|
||||||
|
|
||||||
|
import { style } from './constant';
|
||||||
|
|
||||||
|
function TextToolTip({
|
||||||
|
text,
|
||||||
|
url,
|
||||||
|
useFilledIcon = true,
|
||||||
|
urlText,
|
||||||
|
}: TextToolTipProps): JSX.Element {
|
||||||
|
const isDarkMode = useIsDarkMode();
|
||||||
|
|
||||||
|
const onClickHandler = (
|
||||||
|
event: React.MouseEvent<HTMLAnchorElement, MouseEvent>,
|
||||||
|
): void => {
|
||||||
|
event.stopPropagation();
|
||||||
|
};
|
||||||
|
|
||||||
|
const overlay = useMemo(
|
||||||
|
() => (
|
||||||
|
<div className="overlay--text-wrap">
|
||||||
|
{`${text} `}
|
||||||
|
{url && (
|
||||||
|
<a
|
||||||
|
// Stopping event propagation on click so that parent click listener are not triggered
|
||||||
|
onClick={onClickHandler}
|
||||||
|
href={url}
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
{urlText || 'here'}
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
[text, url, urlText],
|
||||||
|
);
|
||||||
|
|
||||||
|
const iconStyle = useMemo(
|
||||||
|
() => ({
|
||||||
|
...style,
|
||||||
|
color: isDarkMode ? themeColors.whiteCream : grey[0],
|
||||||
|
}),
|
||||||
|
[isDarkMode],
|
||||||
|
);
|
||||||
|
|
||||||
|
const iconOutlinedStyle = useMemo(
|
||||||
|
() => ({
|
||||||
|
...style,
|
||||||
|
color: isDarkMode ? themeColors.navyBlue : blue[6],
|
||||||
|
}),
|
||||||
|
[isDarkMode],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tooltip getTooltipContainer={popupContainer} overlay={overlay}>
|
||||||
|
{useFilledIcon ? (
|
||||||
|
<QuestionCircleFilled style={iconStyle} />
|
||||||
|
) : (
|
||||||
|
<QuestionCircleOutlined style={iconOutlinedStyle} />
|
||||||
|
)}
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
TextToolTip.defaultProps = {
|
||||||
|
url: '',
|
||||||
|
urlText: '',
|
||||||
|
useFilledIcon: true,
|
||||||
|
};
|
||||||
|
interface TextToolTipProps {
|
||||||
|
url?: string;
|
||||||
|
text: string;
|
||||||
|
useFilledIcon?: boolean;
|
||||||
|
urlText?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TextToolTip;
|
@ -1,87 +1,3 @@
|
|||||||
import { blue, grey } from '@ant-design/colors';
|
import TextToolTip from './TextToolTip';
|
||||||
import {
|
|
||||||
QuestionCircleFilled,
|
|
||||||
QuestionCircleOutlined,
|
|
||||||
} from '@ant-design/icons';
|
|
||||||
import { Tooltip } from 'antd';
|
|
||||||
import { themeColors } from 'constants/theme';
|
|
||||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
|
||||||
import { useMemo } from 'react';
|
|
||||||
import { popupContainer } from 'utils/selectPopupContainer';
|
|
||||||
|
|
||||||
import { style } from './styles';
|
|
||||||
|
|
||||||
function TextToolTip({
|
|
||||||
text,
|
|
||||||
url,
|
|
||||||
useFilledIcon = true,
|
|
||||||
urlText,
|
|
||||||
}: TextToolTipProps): JSX.Element {
|
|
||||||
const isDarkMode = useIsDarkMode();
|
|
||||||
|
|
||||||
const onClickHandler = (
|
|
||||||
event: React.MouseEvent<HTMLAnchorElement, MouseEvent>,
|
|
||||||
): void => {
|
|
||||||
event.stopPropagation();
|
|
||||||
};
|
|
||||||
|
|
||||||
const overlay = useMemo(
|
|
||||||
() => (
|
|
||||||
<div>
|
|
||||||
{`${text} `}
|
|
||||||
{url && (
|
|
||||||
<a
|
|
||||||
// Stopping event propagation on click so that parent click listener are not triggered
|
|
||||||
onClick={onClickHandler}
|
|
||||||
href={url}
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
{urlText || 'here'}
|
|
||||||
</a>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
[text, url, urlText],
|
|
||||||
);
|
|
||||||
|
|
||||||
const iconStyle = useMemo(
|
|
||||||
() => ({
|
|
||||||
...style,
|
|
||||||
color: isDarkMode ? themeColors.whiteCream : grey[0],
|
|
||||||
}),
|
|
||||||
[isDarkMode],
|
|
||||||
);
|
|
||||||
|
|
||||||
const iconOutlinedStyle = useMemo(
|
|
||||||
() => ({
|
|
||||||
...style,
|
|
||||||
color: isDarkMode ? themeColors.navyBlue : blue[6],
|
|
||||||
}),
|
|
||||||
[isDarkMode],
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Tooltip getTooltipContainer={popupContainer} overlay={overlay}>
|
|
||||||
{useFilledIcon ? (
|
|
||||||
<QuestionCircleFilled style={iconStyle} />
|
|
||||||
) : (
|
|
||||||
<QuestionCircleOutlined style={iconOutlinedStyle} />
|
|
||||||
)}
|
|
||||||
</Tooltip>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
TextToolTip.defaultProps = {
|
|
||||||
url: '',
|
|
||||||
urlText: '',
|
|
||||||
useFilledIcon: true,
|
|
||||||
};
|
|
||||||
interface TextToolTipProps {
|
|
||||||
url?: string;
|
|
||||||
text: string;
|
|
||||||
useFilledIcon?: boolean;
|
|
||||||
urlText?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default TextToolTip;
|
export default TextToolTip;
|
||||||
|
@ -3,5 +3,8 @@ export const REACT_QUERY_KEY = {
|
|||||||
GET_QUERY_RANGE: 'GET_QUERY_RANGE',
|
GET_QUERY_RANGE: 'GET_QUERY_RANGE',
|
||||||
GET_ALL_DASHBOARDS: 'GET_ALL_DASHBOARDS',
|
GET_ALL_DASHBOARDS: 'GET_ALL_DASHBOARDS',
|
||||||
GET_TRIGGERED_ALERTS: 'GET_TRIGGERED_ALERTS',
|
GET_TRIGGERED_ALERTS: 'GET_TRIGGERED_ALERTS',
|
||||||
|
DASHBOARD_BY_ID: 'DASHBOARD_BY_ID',
|
||||||
GET_FEATURES_FLAGS: 'GET_FEATURES_FLAGS',
|
GET_FEATURES_FLAGS: 'GET_FEATURES_FLAGS',
|
||||||
|
DELETE_DASHBOARD: 'DELETE_DASHBOARD',
|
||||||
|
LOGS_PIPELINE_PREVIEW: 'LOGS_PIPELINE_PREVIEW',
|
||||||
};
|
};
|
||||||
|
@ -24,6 +24,7 @@ const ROUTES = {
|
|||||||
VERSION: '/status',
|
VERSION: '/status',
|
||||||
MY_SETTINGS: '/my-settings',
|
MY_SETTINGS: '/my-settings',
|
||||||
ORG_SETTINGS: '/settings/org-settings',
|
ORG_SETTINGS: '/settings/org-settings',
|
||||||
|
INGESTION_SETTINGS: '/settings/ingestion-settings',
|
||||||
SOMETHING_WENT_WRONG: '/something-went-wrong',
|
SOMETHING_WENT_WRONG: '/something-went-wrong',
|
||||||
UN_AUTHORIZED: '/un-authorized',
|
UN_AUTHORIZED: '/un-authorized',
|
||||||
NOT_FOUND: '/not-found',
|
NOT_FOUND: '/not-found',
|
||||||
|
@ -3,6 +3,7 @@ import {
|
|||||||
initialQueryPromQLData,
|
initialQueryPromQLData,
|
||||||
PANEL_TYPES,
|
PANEL_TYPES,
|
||||||
} from 'constants/queryBuilder';
|
} from 'constants/queryBuilder';
|
||||||
|
import ROUTES from 'constants/routes';
|
||||||
import { AlertTypes } from 'types/api/alerts/alertTypes';
|
import { AlertTypes } from 'types/api/alerts/alertTypes';
|
||||||
import {
|
import {
|
||||||
AlertDef,
|
AlertDef,
|
||||||
@ -77,7 +78,7 @@ export const logAlertDefaults: AlertDef = {
|
|||||||
},
|
},
|
||||||
labels: {
|
labels: {
|
||||||
severity: 'warning',
|
severity: 'warning',
|
||||||
details: `${window.location.protocol}//${window.location.host}/logs`,
|
details: `${window.location.protocol}//${window.location.host}${ROUTES.LOGS_EXPLORER}`,
|
||||||
},
|
},
|
||||||
annotations: defaultAnnotations,
|
annotations: defaultAnnotations,
|
||||||
evalWindow: defaultEvalWindow,
|
evalWindow: defaultEvalWindow,
|
||||||
|
@ -25,6 +25,7 @@ export interface ChartPreviewProps {
|
|||||||
headline?: JSX.Element;
|
headline?: JSX.Element;
|
||||||
alertDef?: AlertDef;
|
alertDef?: AlertDef;
|
||||||
userQueryKey?: string;
|
userQueryKey?: string;
|
||||||
|
allowSelectedIntervalForStepGen?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
function ChartPreview({
|
function ChartPreview({
|
||||||
@ -35,6 +36,7 @@ function ChartPreview({
|
|||||||
selectedInterval = '5min',
|
selectedInterval = '5min',
|
||||||
headline,
|
headline,
|
||||||
userQueryKey,
|
userQueryKey,
|
||||||
|
allowSelectedIntervalForStepGen = false,
|
||||||
alertDef,
|
alertDef,
|
||||||
}: ChartPreviewProps): JSX.Element | null {
|
}: ChartPreviewProps): JSX.Element | null {
|
||||||
const { t } = useTranslation('alerts');
|
const { t } = useTranslation('alerts');
|
||||||
@ -89,6 +91,9 @@ function ChartPreview({
|
|||||||
globalSelectedInterval: selectedInterval,
|
globalSelectedInterval: selectedInterval,
|
||||||
graphType,
|
graphType,
|
||||||
selectedTime,
|
selectedTime,
|
||||||
|
params: {
|
||||||
|
allowSelectedIntervalForStepGen,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
queryKey: [
|
queryKey: [
|
||||||
@ -127,7 +132,7 @@ function ChartPreview({
|
|||||||
<GridPanelSwitch
|
<GridPanelSwitch
|
||||||
panelType={graphType}
|
panelType={graphType}
|
||||||
title={name}
|
title={name}
|
||||||
data={chartDataSet}
|
data={chartDataSet.data}
|
||||||
isStacked
|
isStacked
|
||||||
name={name || 'Chart Preview'}
|
name={name || 'Chart Preview'}
|
||||||
staticLine={staticLine}
|
staticLine={staticLine}
|
||||||
@ -146,6 +151,7 @@ ChartPreview.defaultProps = {
|
|||||||
selectedInterval: '5min',
|
selectedInterval: '5min',
|
||||||
headline: undefined,
|
headline: undefined,
|
||||||
userQueryKey: '',
|
userQueryKey: '',
|
||||||
|
allowSelectedIntervalForStepGen: false,
|
||||||
alertDef: undefined,
|
alertDef: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -44,7 +44,7 @@ import {
|
|||||||
StyledLeftContainer,
|
StyledLeftContainer,
|
||||||
} from './styles';
|
} from './styles';
|
||||||
import UserGuide from './UserGuide';
|
import UserGuide from './UserGuide';
|
||||||
import { toChartInterval } from './utils';
|
import { getUpdatedStepInterval, toChartInterval } from './utils';
|
||||||
|
|
||||||
function FormAlertRules({
|
function FormAlertRules({
|
||||||
alertType,
|
alertType,
|
||||||
@ -354,6 +354,16 @@ function FormAlertRules({
|
|||||||
<BasicInfo alertDef={alertDef} setAlertDef={setAlertDef} />
|
<BasicInfo alertDef={alertDef} setAlertDef={setAlertDef} />
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const updatedStagedQuery = useMemo((): Query | null => {
|
||||||
|
const newQuery: Query | null = stagedQuery;
|
||||||
|
if (newQuery) {
|
||||||
|
newQuery.builder.queryData[0].stepInterval = getUpdatedStepInterval(
|
||||||
|
alertDef.evalWindow,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return newQuery;
|
||||||
|
}, [alertDef.evalWindow, stagedQuery]);
|
||||||
|
|
||||||
const renderQBChartPreview = (): JSX.Element => (
|
const renderQBChartPreview = (): JSX.Element => (
|
||||||
<ChartPreview
|
<ChartPreview
|
||||||
headline={
|
headline={
|
||||||
@ -363,9 +373,10 @@ function FormAlertRules({
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
name=""
|
name=""
|
||||||
query={stagedQuery}
|
query={updatedStagedQuery}
|
||||||
selectedInterval={toChartInterval(alertDef.evalWindow)}
|
selectedInterval={toChartInterval(alertDef.evalWindow)}
|
||||||
alertDef={alertDef}
|
alertDef={alertDef}
|
||||||
|
allowSelectedIntervalForStepGen
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
14
frontend/src/container/FormAlertRules/utils.test.ts
Normal file
14
frontend/src/container/FormAlertRules/utils.test.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
// Write a test for getUpdatedStepInterval function in src/container/FormAlertRules/utils.ts
|
||||||
|
|
||||||
|
import { getUpdatedStepInterval } from './utils';
|
||||||
|
|
||||||
|
describe('getUpdatedStepInterval', () => {
|
||||||
|
it('should return 60', () => {
|
||||||
|
const result = getUpdatedStepInterval('5m0s');
|
||||||
|
expect(result).toEqual(60);
|
||||||
|
});
|
||||||
|
it('should return 60 for 10m0s', () => {
|
||||||
|
const result = getUpdatedStepInterval('10m0s');
|
||||||
|
expect(result).toEqual(60);
|
||||||
|
});
|
||||||
|
});
|
@ -1,4 +1,6 @@
|
|||||||
import { Time } from 'container/TopNav/DateTimeSelection/config';
|
import { Time } from 'container/TopNav/DateTimeSelection/config';
|
||||||
|
import getStartEndRangeTime from 'lib/getStartEndRangeTime';
|
||||||
|
import getStep from 'lib/getStep';
|
||||||
|
|
||||||
// toChartInterval converts eval window to chart selection time interval
|
// toChartInterval converts eval window to chart selection time interval
|
||||||
export const toChartInterval = (evalWindow: string | undefined): Time => {
|
export const toChartInterval = (evalWindow: string | undefined): Time => {
|
||||||
@ -21,3 +23,15 @@ export const toChartInterval = (evalWindow: string | undefined): Time => {
|
|||||||
return '5min';
|
return '5min';
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getUpdatedStepInterval = (evalWindow?: string): number => {
|
||||||
|
const { start, end } = getStartEndRangeTime({
|
||||||
|
type: 'GLOBAL_TIME',
|
||||||
|
interval: toChartInterval(evalWindow),
|
||||||
|
});
|
||||||
|
return getStep({
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
inputFormat: 'ns',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
@ -0,0 +1,21 @@
|
|||||||
|
.graph-manager-container {
|
||||||
|
margin-top: 1.25rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
overflow-x: scroll;
|
||||||
|
|
||||||
|
.filter-table-container {
|
||||||
|
flex-basis: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.save-cancel-container {
|
||||||
|
flex-basis: 20%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.save-cancel-button {
|
||||||
|
margin: 0 0.313rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,136 @@
|
|||||||
|
import './GraphManager.styles.scss';
|
||||||
|
|
||||||
|
import { Button, Input } from 'antd';
|
||||||
|
import { CheckboxChangeEvent } from 'antd/es/checkbox';
|
||||||
|
import { ResizeTable } from 'components/ResizeTable';
|
||||||
|
import { useNotifications } from 'hooks/useNotifications';
|
||||||
|
import { memo, useCallback, useState } from 'react';
|
||||||
|
|
||||||
|
import { getGraphManagerTableColumns } from './TableRender/GraphManagerColumns';
|
||||||
|
import { ExtendedChartDataset, GraphManagerProps } from './types';
|
||||||
|
import {
|
||||||
|
getDefaultTableDataSet,
|
||||||
|
saveLegendEntriesToLocalStorage,
|
||||||
|
} from './utils';
|
||||||
|
|
||||||
|
function GraphManager({
|
||||||
|
data,
|
||||||
|
name,
|
||||||
|
yAxisUnit,
|
||||||
|
onToggleModelHandler,
|
||||||
|
setGraphsVisibilityStates,
|
||||||
|
graphsVisibilityStates = [],
|
||||||
|
lineChartRef,
|
||||||
|
parentChartRef,
|
||||||
|
}: GraphManagerProps): JSX.Element {
|
||||||
|
const [tableDataSet, setTableDataSet] = useState<ExtendedChartDataset[]>(
|
||||||
|
getDefaultTableDataSet(data),
|
||||||
|
);
|
||||||
|
|
||||||
|
const { notifications } = useNotifications();
|
||||||
|
|
||||||
|
const checkBoxOnChangeHandler = useCallback(
|
||||||
|
(e: CheckboxChangeEvent, index: number): void => {
|
||||||
|
const newStates = [...graphsVisibilityStates];
|
||||||
|
|
||||||
|
newStates[index] = e.target.checked;
|
||||||
|
|
||||||
|
lineChartRef?.current?.toggleGraph(index, e.target.checked);
|
||||||
|
|
||||||
|
setGraphsVisibilityStates([...newStates]);
|
||||||
|
},
|
||||||
|
[graphsVisibilityStates, setGraphsVisibilityStates, lineChartRef],
|
||||||
|
);
|
||||||
|
|
||||||
|
const labelClickedHandler = useCallback(
|
||||||
|
(labelIndex: number): void => {
|
||||||
|
const newGraphVisibilityStates = Array<boolean>(data.datasets.length).fill(
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
newGraphVisibilityStates[labelIndex] = true;
|
||||||
|
|
||||||
|
newGraphVisibilityStates.forEach((state, index) => {
|
||||||
|
lineChartRef?.current?.toggleGraph(index, state);
|
||||||
|
parentChartRef?.current?.toggleGraph(index, state);
|
||||||
|
});
|
||||||
|
setGraphsVisibilityStates(newGraphVisibilityStates);
|
||||||
|
},
|
||||||
|
[
|
||||||
|
data.datasets.length,
|
||||||
|
setGraphsVisibilityStates,
|
||||||
|
lineChartRef,
|
||||||
|
parentChartRef,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
const columns = getGraphManagerTableColumns({
|
||||||
|
data,
|
||||||
|
checkBoxOnChangeHandler,
|
||||||
|
graphVisibilityState: graphsVisibilityStates || [],
|
||||||
|
labelClickedHandler,
|
||||||
|
yAxisUnit,
|
||||||
|
});
|
||||||
|
|
||||||
|
const filterHandler = useCallback(
|
||||||
|
(event: React.ChangeEvent<HTMLInputElement>): void => {
|
||||||
|
const value = event.target.value.toString().toLowerCase();
|
||||||
|
const updatedDataSet = tableDataSet.map((item) => {
|
||||||
|
if (item.label?.toLocaleLowerCase().includes(value)) {
|
||||||
|
return { ...item, show: true };
|
||||||
|
}
|
||||||
|
return { ...item, show: false };
|
||||||
|
});
|
||||||
|
setTableDataSet(updatedDataSet);
|
||||||
|
},
|
||||||
|
[tableDataSet],
|
||||||
|
);
|
||||||
|
|
||||||
|
const saveHandler = useCallback((): void => {
|
||||||
|
saveLegendEntriesToLocalStorage({
|
||||||
|
data,
|
||||||
|
graphVisibilityState: graphsVisibilityStates || [],
|
||||||
|
name,
|
||||||
|
});
|
||||||
|
notifications.success({
|
||||||
|
message: 'The updated graphs & legends are saved',
|
||||||
|
});
|
||||||
|
if (onToggleModelHandler) {
|
||||||
|
onToggleModelHandler();
|
||||||
|
}
|
||||||
|
}, [data, graphsVisibilityStates, name, notifications, onToggleModelHandler]);
|
||||||
|
|
||||||
|
const dataSource = tableDataSet.filter((item) => item.show);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="graph-manager-container">
|
||||||
|
<div className="filter-table-container">
|
||||||
|
<Input onChange={filterHandler} placeholder="Filter Series" />
|
||||||
|
<ResizeTable
|
||||||
|
columns={columns}
|
||||||
|
dataSource={dataSource}
|
||||||
|
rowKey="index"
|
||||||
|
pagination={false}
|
||||||
|
scroll={{ y: 240 }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="save-cancel-container">
|
||||||
|
<span className="save-cancel-button">
|
||||||
|
<Button type="default" onClick={onToggleModelHandler}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</span>
|
||||||
|
<span className="save-cancel-button">
|
||||||
|
<Button onClick={saveHandler} type="primary">
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
GraphManager.defaultProps = {
|
||||||
|
graphVisibilityStateHandler: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(GraphManager);
|
@ -1,3 +1,4 @@
|
|||||||
|
import { grey } from '@ant-design/colors';
|
||||||
import { Checkbox, ConfigProvider } from 'antd';
|
import { Checkbox, ConfigProvider } from 'antd';
|
||||||
import { CheckboxChangeEvent } from 'antd/es/checkbox';
|
import { CheckboxChangeEvent } from 'antd/es/checkbox';
|
||||||
|
|
||||||
@ -6,7 +7,7 @@ import { CheckBoxProps } from '../types';
|
|||||||
function CustomCheckBox({
|
function CustomCheckBox({
|
||||||
data,
|
data,
|
||||||
index,
|
index,
|
||||||
graphVisibilityState,
|
graphVisibilityState = [],
|
||||||
checkBoxOnChangeHandler,
|
checkBoxOnChangeHandler,
|
||||||
}: CheckBoxProps): JSX.Element {
|
}: CheckBoxProps): JSX.Element {
|
||||||
const { datasets } = data;
|
const { datasets } = data;
|
||||||
@ -15,17 +16,21 @@ function CustomCheckBox({
|
|||||||
checkBoxOnChangeHandler(e, index);
|
checkBoxOnChangeHandler(e, index);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const color = datasets[index]?.borderColor?.toString() || grey[0];
|
||||||
|
|
||||||
|
const isChecked = graphVisibilityState[index] || false;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ConfigProvider
|
<ConfigProvider
|
||||||
theme={{
|
theme={{
|
||||||
token: {
|
token: {
|
||||||
colorPrimary: datasets[index].borderColor?.toString(),
|
colorPrimary: color,
|
||||||
colorBorder: datasets[index].borderColor?.toString(),
|
colorBorder: color,
|
||||||
colorBgContainer: datasets[index].borderColor?.toString(),
|
colorBgContainer: color,
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Checkbox onChange={onChangeHandler} checked={graphVisibilityState[index]} />
|
<Checkbox onChange={onChangeHandler} checked={isChecked} />
|
||||||
</ConfigProvider>
|
</ConfigProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
@ -5,7 +5,7 @@ import { ChartData } from 'chart.js';
|
|||||||
import { ColumnsKeyAndDataIndex, ColumnsTitle } from '../contants';
|
import { ColumnsKeyAndDataIndex, ColumnsTitle } from '../contants';
|
||||||
import { DataSetProps } from '../types';
|
import { DataSetProps } from '../types';
|
||||||
import { getGraphManagerTableHeaderTitle } from '../utils';
|
import { getGraphManagerTableHeaderTitle } from '../utils';
|
||||||
import { getCheckBox } from './GetCheckBox';
|
import CustomCheckBox from './CustomCheckBox';
|
||||||
import { getLabel } from './GetLabel';
|
import { getLabel } from './GetLabel';
|
||||||
|
|
||||||
export const getGraphManagerTableColumns = ({
|
export const getGraphManagerTableColumns = ({
|
||||||
@ -20,11 +20,14 @@ export const getGraphManagerTableColumns = ({
|
|||||||
width: 50,
|
width: 50,
|
||||||
dataIndex: ColumnsKeyAndDataIndex.Index,
|
dataIndex: ColumnsKeyAndDataIndex.Index,
|
||||||
key: ColumnsKeyAndDataIndex.Index,
|
key: ColumnsKeyAndDataIndex.Index,
|
||||||
...getCheckBox({
|
render: (_: string, __: DataSetProps, index: number): JSX.Element => (
|
||||||
checkBoxOnChangeHandler,
|
<CustomCheckBox
|
||||||
graphVisibilityState,
|
data={data}
|
||||||
data,
|
index={index}
|
||||||
}),
|
checkBoxOnChangeHandler={checkBoxOnChangeHandler}
|
||||||
|
graphVisibilityState={graphVisibilityState}
|
||||||
|
/>
|
||||||
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: ColumnsTitle[ColumnsKeyAndDataIndex.Label],
|
title: ColumnsTitle[ColumnsKeyAndDataIndex.Label],
|
@ -12,17 +12,16 @@ import { useStepInterval } from 'hooks/queryBuilder/useStepInterval';
|
|||||||
import { useChartMutable } from 'hooks/useChartMutable';
|
import { useChartMutable } from 'hooks/useChartMutable';
|
||||||
import { getDashboardVariables } from 'lib/dashbaordVariables/getDashboardVariables';
|
import { getDashboardVariables } from 'lib/dashbaordVariables/getDashboardVariables';
|
||||||
import getChartData from 'lib/getChartData';
|
import getChartData from 'lib/getChartData';
|
||||||
|
import { useDashboard } from 'providers/Dashboard/Dashboard';
|
||||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { AppState } from 'store/reducers';
|
import { AppState } from 'store/reducers';
|
||||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||||
|
|
||||||
import { toggleGraphsVisibilityInChart } from '../utils';
|
|
||||||
import { PANEL_TYPES_VS_FULL_VIEW_TABLE } from './contants';
|
import { PANEL_TYPES_VS_FULL_VIEW_TABLE } from './contants';
|
||||||
import GraphManager from './GraphManager';
|
import GraphManager from './GraphManager';
|
||||||
import { GraphContainer, TimeContainer } from './styles';
|
import { GraphContainer, TimeContainer } from './styles';
|
||||||
import { FullViewProps } from './types';
|
import { FullViewProps } from './types';
|
||||||
import { getIsGraphLegendToggleAvailable } from './utils';
|
|
||||||
|
|
||||||
function FullView({
|
function FullView({
|
||||||
widget,
|
widget,
|
||||||
@ -34,45 +33,29 @@ function FullView({
|
|||||||
isDependedDataLoaded = false,
|
isDependedDataLoaded = false,
|
||||||
graphsVisibilityStates,
|
graphsVisibilityStates,
|
||||||
onToggleModelHandler,
|
onToggleModelHandler,
|
||||||
|
setGraphsVisibilityStates,
|
||||||
|
parentChartRef,
|
||||||
}: FullViewProps): JSX.Element {
|
}: FullViewProps): JSX.Element {
|
||||||
const { selectedTime: globalSelectedTime } = useSelector<
|
const { selectedTime: globalSelectedTime } = useSelector<
|
||||||
AppState,
|
AppState,
|
||||||
GlobalReducer
|
GlobalReducer
|
||||||
>((state) => state.globalTime);
|
>((state) => state.globalTime);
|
||||||
|
|
||||||
|
const { selectedDashboard } = useDashboard();
|
||||||
|
|
||||||
const getSelectedTime = useCallback(
|
const getSelectedTime = useCallback(
|
||||||
() =>
|
() =>
|
||||||
timeItems.find((e) => e.enum === (widget?.timePreferance || 'GLOBAL_TIME')),
|
timeItems.find((e) => e.enum === (widget?.timePreferance || 'GLOBAL_TIME')),
|
||||||
[widget],
|
[widget],
|
||||||
);
|
);
|
||||||
|
|
||||||
const canModifyChart = useChartMutable({
|
|
||||||
panelType: widget.panelTypes,
|
|
||||||
panelTypeAndGraphManagerVisibility: PANEL_TYPES_VS_FULL_VIEW_TABLE,
|
|
||||||
});
|
|
||||||
|
|
||||||
const lineChartRef = useRef<ToggleGraphProps>();
|
const lineChartRef = useRef<ToggleGraphProps>();
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (graphsVisibilityStates && canModifyChart && lineChartRef.current) {
|
|
||||||
toggleGraphsVisibilityInChart({
|
|
||||||
graphsVisibilityStates,
|
|
||||||
lineChartRef,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [graphsVisibilityStates, canModifyChart]);
|
|
||||||
|
|
||||||
const [selectedTime, setSelectedTime] = useState<timePreferance>({
|
const [selectedTime, setSelectedTime] = useState<timePreferance>({
|
||||||
name: getSelectedTime()?.name || '',
|
name: getSelectedTime()?.name || '',
|
||||||
enum: widget?.timePreferance || 'GLOBAL_TIME',
|
enum: widget?.timePreferance || 'GLOBAL_TIME',
|
||||||
});
|
});
|
||||||
|
|
||||||
const queryKey = useMemo(
|
|
||||||
() =>
|
|
||||||
`FullViewGetMetricsQueryRange-${selectedTime.enum}-${globalSelectedTime}-${widget.id}`,
|
|
||||||
[selectedTime, globalSelectedTime, widget],
|
|
||||||
);
|
|
||||||
|
|
||||||
const updatedQuery = useStepInterval(widget?.query);
|
const updatedQuery = useStepInterval(widget?.query);
|
||||||
|
|
||||||
const response = useGetQueryRange(
|
const response = useGetQueryRange(
|
||||||
@ -81,14 +64,19 @@ function FullView({
|
|||||||
graphType: widget.panelTypes,
|
graphType: widget.panelTypes,
|
||||||
query: updatedQuery,
|
query: updatedQuery,
|
||||||
globalSelectedInterval: globalSelectedTime,
|
globalSelectedInterval: globalSelectedTime,
|
||||||
variables: getDashboardVariables(),
|
variables: getDashboardVariables(selectedDashboard?.data.variables),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
queryKey,
|
queryKey: `FullViewGetMetricsQueryRange-${selectedTime.enum}-${globalSelectedTime}-${widget.id}`,
|
||||||
enabled: !isDependedDataLoaded,
|
enabled: !isDependedDataLoaded,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const canModifyChart = useChartMutable({
|
||||||
|
panelType: widget.panelTypes,
|
||||||
|
panelTypeAndGraphManagerVisibility: PANEL_TYPES_VS_FULL_VIEW_TABLE,
|
||||||
|
});
|
||||||
|
|
||||||
const chartDataSet = useMemo(
|
const chartDataSet = useMemo(
|
||||||
() =>
|
() =>
|
||||||
getChartData({
|
getChartData({
|
||||||
@ -101,9 +89,14 @@ function FullView({
|
|||||||
[response],
|
[response],
|
||||||
);
|
);
|
||||||
|
|
||||||
const isGraphLegendToggleAvailable = getIsGraphLegendToggleAvailable(
|
useEffect(() => {
|
||||||
widget.panelTypes,
|
if (!response.isFetching && lineChartRef.current) {
|
||||||
);
|
graphsVisibilityStates?.forEach((e, i) => {
|
||||||
|
lineChartRef?.current?.toggleGraph(i, e);
|
||||||
|
parentChartRef?.current?.toggleGraph(i, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [graphsVisibilityStates, parentChartRef, response.isFetching]);
|
||||||
|
|
||||||
if (response.isFetching) {
|
if (response.isFetching) {
|
||||||
return <Spinner height="100%" size="large" tip="Loading..." />;
|
return <Spinner height="100%" size="large" tip="Loading..." />;
|
||||||
@ -128,10 +121,10 @@ function FullView({
|
|||||||
</TimeContainer>
|
</TimeContainer>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<GraphContainer isGraphLegendToggleAvailable={isGraphLegendToggleAvailable}>
|
<GraphContainer isGraphLegendToggleAvailable={canModifyChart}>
|
||||||
<GridPanelSwitch
|
<GridPanelSwitch
|
||||||
panelType={widget.panelTypes}
|
panelType={widget.panelTypes}
|
||||||
data={chartDataSet}
|
data={chartDataSet.data}
|
||||||
isStacked={widget.isStacked}
|
isStacked={widget.isStacked}
|
||||||
opacity={widget.opacity}
|
opacity={widget.opacity}
|
||||||
title={widget.title}
|
title={widget.title}
|
||||||
@ -147,10 +140,14 @@ function FullView({
|
|||||||
|
|
||||||
{canModifyChart && (
|
{canModifyChart && (
|
||||||
<GraphManager
|
<GraphManager
|
||||||
data={chartDataSet}
|
data={chartDataSet.data}
|
||||||
name={name}
|
name={name}
|
||||||
yAxisUnit={yAxisUnit}
|
yAxisUnit={yAxisUnit}
|
||||||
onToggleModelHandler={onToggleModelHandler}
|
onToggleModelHandler={onToggleModelHandler}
|
||||||
|
setGraphsVisibilityStates={setGraphsVisibilityStates}
|
||||||
|
graphsVisibilityStates={graphsVisibilityStates}
|
||||||
|
lineChartRef={lineChartRef}
|
||||||
|
parentChartRef={parentChartRef}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
@ -31,26 +31,6 @@ export const GraphContainer = styled.div<GraphContainerProps>`
|
|||||||
isGraphLegendToggleAvailable ? '50%' : '100%'};
|
isGraphLegendToggleAvailable ? '50%' : '100%'};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const FilterTableAndSaveContainer = styled.div`
|
|
||||||
margin-top: 1.875rem;
|
|
||||||
display: flex;
|
|
||||||
align-items: flex-end;
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const FilterTableContainer = styled.div`
|
|
||||||
flex-basis: 80%;
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const SaveContainer = styled.div`
|
|
||||||
flex-basis: 20%;
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const SaveCancelButtonContainer = styled.span`
|
|
||||||
margin: 0 0.313rem;
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const LabelContainer = styled.button`
|
export const LabelContainer = styled.button`
|
||||||
max-width: 18.75rem;
|
max-width: 18.75rem;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
@ -1,7 +1,8 @@
|
|||||||
import { CheckboxChangeEvent } from 'antd/es/checkbox';
|
import { CheckboxChangeEvent } from 'antd/es/checkbox';
|
||||||
import { ChartData, ChartDataset } from 'chart.js';
|
import { ChartData, ChartDataset } from 'chart.js';
|
||||||
import { GraphOnClickHandler } from 'components/Graph/types';
|
import { GraphOnClickHandler, ToggleGraphProps } from 'components/Graph/types';
|
||||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||||
|
import { MutableRefObject } from 'react';
|
||||||
import { Widgets } from 'types/api/dashboard/getAll';
|
import { Widgets } from 'types/api/dashboard/getAll';
|
||||||
|
|
||||||
export interface DataSetProps {
|
export interface DataSetProps {
|
||||||
@ -40,20 +41,6 @@ export interface LabelProps {
|
|||||||
label: string;
|
label: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GraphManagerProps {
|
|
||||||
data: ChartData;
|
|
||||||
name: string;
|
|
||||||
yAxisUnit?: string;
|
|
||||||
onToggleModelHandler?: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CheckBoxProps {
|
|
||||||
data: ChartData;
|
|
||||||
index: number;
|
|
||||||
graphVisibilityState: boolean[];
|
|
||||||
checkBoxOnChangeHandler: (e: CheckboxChangeEvent, index: number) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface FullViewProps {
|
export interface FullViewProps {
|
||||||
widget: Widgets;
|
widget: Widgets;
|
||||||
fullViewOptions?: boolean;
|
fullViewOptions?: boolean;
|
||||||
@ -64,6 +51,26 @@ export interface FullViewProps {
|
|||||||
isDependedDataLoaded?: boolean;
|
isDependedDataLoaded?: boolean;
|
||||||
graphsVisibilityStates?: boolean[];
|
graphsVisibilityStates?: boolean[];
|
||||||
onToggleModelHandler?: GraphManagerProps['onToggleModelHandler'];
|
onToggleModelHandler?: GraphManagerProps['onToggleModelHandler'];
|
||||||
|
setGraphsVisibilityStates: (graphsVisibilityStates: boolean[]) => void;
|
||||||
|
parentChartRef: GraphManagerProps['lineChartRef'];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GraphManagerProps {
|
||||||
|
data: ChartData;
|
||||||
|
name: string;
|
||||||
|
yAxisUnit?: string;
|
||||||
|
onToggleModelHandler?: () => void;
|
||||||
|
setGraphsVisibilityStates: FullViewProps['setGraphsVisibilityStates'];
|
||||||
|
graphsVisibilityStates: FullViewProps['graphsVisibilityStates'];
|
||||||
|
lineChartRef?: MutableRefObject<ToggleGraphProps | undefined>;
|
||||||
|
parentChartRef?: MutableRefObject<ToggleGraphProps | undefined>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CheckBoxProps {
|
||||||
|
data: ChartData;
|
||||||
|
index: number;
|
||||||
|
graphVisibilityState: boolean[];
|
||||||
|
checkBoxOnChangeHandler: (e: CheckboxChangeEvent, index: number) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SaveLegendEntriesToLocalStoreProps {
|
export interface SaveLegendEntriesToLocalStoreProps {
|
@ -1,6 +1,5 @@
|
|||||||
import { ChartData, ChartDataset } from 'chart.js';
|
import { ChartData, ChartDataset } from 'chart.js';
|
||||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ExtendedChartDataset,
|
ExtendedChartDataset,
|
||||||
@ -110,10 +109,6 @@ export const saveLegendEntriesToLocalStorage = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getIsGraphLegendToggleAvailable = (
|
|
||||||
panelType: PANEL_TYPES,
|
|
||||||
): boolean => panelType === PANEL_TYPES.TIME_SERIES;
|
|
||||||
|
|
||||||
export const getGraphManagerTableHeaderTitle = (
|
export const getGraphManagerTableHeaderTitle = (
|
||||||
title: string,
|
title: string,
|
||||||
yAxisUnit?: string,
|
yAxisUnit?: string,
|
@ -0,0 +1,277 @@
|
|||||||
|
import { Typography } from 'antd';
|
||||||
|
import { ToggleGraphProps } from 'components/Graph/types';
|
||||||
|
import { SOMETHING_WENT_WRONG } from 'constants/api';
|
||||||
|
import GridPanelSwitch from 'container/GridPanelSwitch';
|
||||||
|
import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard';
|
||||||
|
import { useNotifications } from 'hooks/useNotifications';
|
||||||
|
import createQueryParams from 'lib/createQueryParams';
|
||||||
|
import history from 'lib/history';
|
||||||
|
import { useDashboard } from 'providers/Dashboard/Dashboard';
|
||||||
|
import {
|
||||||
|
Dispatch,
|
||||||
|
SetStateAction,
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from 'react';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
|
import { useLocation } from 'react-router-dom';
|
||||||
|
import { AppState } from 'store/reducers';
|
||||||
|
import { Dashboard } from 'types/api/dashboard/getAll';
|
||||||
|
import AppReducer from 'types/reducer/app';
|
||||||
|
import { v4 } from 'uuid';
|
||||||
|
|
||||||
|
import WidgetHeader from '../WidgetHeader';
|
||||||
|
import FullView from './FullView';
|
||||||
|
import { FullViewContainer, Modal } from './styles';
|
||||||
|
import { WidgetGraphComponentProps } from './types';
|
||||||
|
import { getGraphVisibilityStateOnDataChange } from './utils';
|
||||||
|
|
||||||
|
function WidgetGraphComponent({
|
||||||
|
data,
|
||||||
|
widget,
|
||||||
|
queryResponse,
|
||||||
|
errorMessage,
|
||||||
|
name,
|
||||||
|
onDragSelect,
|
||||||
|
onClickHandler,
|
||||||
|
threshold,
|
||||||
|
headerMenuList,
|
||||||
|
isWarning,
|
||||||
|
}: WidgetGraphComponentProps): JSX.Element {
|
||||||
|
const [deleteModal, setDeleteModal] = useState(false);
|
||||||
|
const [modal, setModal] = useState<boolean>(false);
|
||||||
|
const [hovered, setHovered] = useState(false);
|
||||||
|
const { notifications } = useNotifications();
|
||||||
|
const { pathname } = useLocation();
|
||||||
|
|
||||||
|
const lineChartRef = useRef<ToggleGraphProps>();
|
||||||
|
|
||||||
|
const { graphVisibilityStates: localStoredVisibilityStates } = useMemo(
|
||||||
|
() =>
|
||||||
|
getGraphVisibilityStateOnDataChange({
|
||||||
|
data,
|
||||||
|
isExpandedName: true,
|
||||||
|
name,
|
||||||
|
}),
|
||||||
|
[data, name],
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!lineChartRef.current) return;
|
||||||
|
|
||||||
|
localStoredVisibilityStates.forEach((state, index) => {
|
||||||
|
lineChartRef.current?.toggleGraph(index, state);
|
||||||
|
});
|
||||||
|
}, [localStoredVisibilityStates]);
|
||||||
|
|
||||||
|
const { setLayouts, selectedDashboard, setSelectedDashboard } = useDashboard();
|
||||||
|
|
||||||
|
const [graphsVisibilityStates, setGraphsVisibilityStates] = useState<
|
||||||
|
boolean[]
|
||||||
|
>(localStoredVisibilityStates);
|
||||||
|
|
||||||
|
const { featureResponse } = useSelector<AppState, AppReducer>(
|
||||||
|
(state) => state.app,
|
||||||
|
);
|
||||||
|
const onToggleModal = useCallback(
|
||||||
|
(func: Dispatch<SetStateAction<boolean>>) => {
|
||||||
|
func((value) => !value);
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
const updateDashboardMutation = useUpdateDashboard();
|
||||||
|
|
||||||
|
const onDeleteHandler = (): void => {
|
||||||
|
if (!selectedDashboard) return;
|
||||||
|
|
||||||
|
const updatedWidgets = selectedDashboard?.data?.widgets?.filter(
|
||||||
|
(e) => e.id !== widget.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
const updatedLayout =
|
||||||
|
selectedDashboard.data.layout?.filter((e) => e.i !== widget.id) || [];
|
||||||
|
|
||||||
|
const updatedSelectedDashboard: Dashboard = {
|
||||||
|
...selectedDashboard,
|
||||||
|
data: {
|
||||||
|
...selectedDashboard.data,
|
||||||
|
widgets: updatedWidgets,
|
||||||
|
layout: updatedLayout,
|
||||||
|
},
|
||||||
|
uuid: selectedDashboard.uuid,
|
||||||
|
};
|
||||||
|
|
||||||
|
updateDashboardMutation.mutateAsync(updatedSelectedDashboard, {
|
||||||
|
onSuccess: (updatedDashboard) => {
|
||||||
|
if (setLayouts) setLayouts(updatedDashboard.payload?.data?.layout || []);
|
||||||
|
if (setSelectedDashboard && updatedDashboard.payload) {
|
||||||
|
setSelectedDashboard(updatedDashboard.payload);
|
||||||
|
}
|
||||||
|
featureResponse.refetch();
|
||||||
|
},
|
||||||
|
onError: () => {
|
||||||
|
notifications.error({
|
||||||
|
message: SOMETHING_WENT_WRONG,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const onCloneHandler = async (): Promise<void> => {
|
||||||
|
if (!selectedDashboard) return;
|
||||||
|
|
||||||
|
const uuid = v4();
|
||||||
|
|
||||||
|
const layout = [
|
||||||
|
...(selectedDashboard.data.layout || []),
|
||||||
|
{
|
||||||
|
i: uuid,
|
||||||
|
w: 6,
|
||||||
|
x: 0,
|
||||||
|
h: 2,
|
||||||
|
y: 0,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
updateDashboardMutation.mutateAsync(
|
||||||
|
{
|
||||||
|
...selectedDashboard,
|
||||||
|
data: {
|
||||||
|
...selectedDashboard.data,
|
||||||
|
layout,
|
||||||
|
widgets: [
|
||||||
|
...(selectedDashboard.data.widgets || []),
|
||||||
|
{
|
||||||
|
...{
|
||||||
|
...widget,
|
||||||
|
id: uuid,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
onSuccess: () => {
|
||||||
|
notifications.success({
|
||||||
|
message: 'Panel cloned successfully, redirecting to new copy.',
|
||||||
|
});
|
||||||
|
const queryParams = {
|
||||||
|
graphType: widget?.panelTypes,
|
||||||
|
widgetId: uuid,
|
||||||
|
};
|
||||||
|
history.push(`${pathname}/new?${createQueryParams(queryParams)}`);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOnView = (): void => {
|
||||||
|
onToggleModal(setModal);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOnDelete = (): void => {
|
||||||
|
onToggleModal(setDeleteModal);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onDeleteModelHandler = (): void => {
|
||||||
|
onToggleModal(setDeleteModal);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onToggleModelHandler = (): void => {
|
||||||
|
onToggleModal(setModal);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
onMouseOver={(): void => {
|
||||||
|
setHovered(true);
|
||||||
|
}}
|
||||||
|
onFocus={(): void => {
|
||||||
|
setHovered(true);
|
||||||
|
}}
|
||||||
|
onMouseOut={(): void => {
|
||||||
|
setHovered(false);
|
||||||
|
}}
|
||||||
|
onBlur={(): void => {
|
||||||
|
setHovered(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Modal
|
||||||
|
destroyOnClose
|
||||||
|
onCancel={onDeleteModelHandler}
|
||||||
|
open={deleteModal}
|
||||||
|
title="Delete"
|
||||||
|
height="10vh"
|
||||||
|
onOk={onDeleteHandler}
|
||||||
|
centered
|
||||||
|
>
|
||||||
|
<Typography>Are you sure you want to delete this widget</Typography>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
<Modal
|
||||||
|
title="View"
|
||||||
|
footer={[]}
|
||||||
|
centered
|
||||||
|
open={modal}
|
||||||
|
onCancel={onToggleModelHandler}
|
||||||
|
width="85%"
|
||||||
|
destroyOnClose
|
||||||
|
>
|
||||||
|
<FullViewContainer>
|
||||||
|
<FullView
|
||||||
|
name={`${name}expanded`}
|
||||||
|
widget={widget}
|
||||||
|
yAxisUnit={widget.yAxisUnit}
|
||||||
|
graphsVisibilityStates={graphsVisibilityStates}
|
||||||
|
onToggleModelHandler={onToggleModelHandler}
|
||||||
|
setGraphsVisibilityStates={setGraphsVisibilityStates}
|
||||||
|
parentChartRef={lineChartRef}
|
||||||
|
/>
|
||||||
|
</FullViewContainer>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
<div className="drag-handle">
|
||||||
|
<WidgetHeader
|
||||||
|
parentHover={hovered}
|
||||||
|
title={widget?.title}
|
||||||
|
widget={widget}
|
||||||
|
onView={handleOnView}
|
||||||
|
onDelete={handleOnDelete}
|
||||||
|
onClone={onCloneHandler}
|
||||||
|
queryResponse={queryResponse}
|
||||||
|
errorMessage={errorMessage}
|
||||||
|
threshold={threshold}
|
||||||
|
headerMenuList={headerMenuList}
|
||||||
|
isWarning={isWarning}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<GridPanelSwitch
|
||||||
|
panelType={widget.panelTypes}
|
||||||
|
data={data}
|
||||||
|
isStacked={widget.isStacked}
|
||||||
|
opacity={widget.opacity}
|
||||||
|
title={' '}
|
||||||
|
name={name}
|
||||||
|
yAxisUnit={widget.yAxisUnit}
|
||||||
|
onClickHandler={onClickHandler}
|
||||||
|
onDragSelect={onDragSelect}
|
||||||
|
panelData={queryResponse.data?.payload?.data.newResult.data.result || []}
|
||||||
|
query={widget.query}
|
||||||
|
ref={lineChartRef}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
WidgetGraphComponent.defaultProps = {
|
||||||
|
yAxisUnit: undefined,
|
||||||
|
setLayout: undefined,
|
||||||
|
onDragSelect: undefined,
|
||||||
|
onClickHandler: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default WidgetGraphComponent;
|
134
frontend/src/container/GridCardLayout/GridCard/index.tsx
Normal file
134
frontend/src/container/GridCardLayout/GridCard/index.tsx
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
import { Skeleton } from 'antd';
|
||||||
|
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||||
|
import { useGetQueryRange } from 'hooks/queryBuilder/useGetQueryRange';
|
||||||
|
import { useStepInterval } from 'hooks/queryBuilder/useStepInterval';
|
||||||
|
import { getDashboardVariables } from 'lib/dashbaordVariables/getDashboardVariables';
|
||||||
|
import getChartData from 'lib/getChartData';
|
||||||
|
import isEmpty from 'lodash-es/isEmpty';
|
||||||
|
import { useDashboard } from 'providers/Dashboard/Dashboard';
|
||||||
|
import { memo, useMemo, useState } from 'react';
|
||||||
|
import { useInView } from 'react-intersection-observer';
|
||||||
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
import { UpdateTimeInterval } from 'store/actions';
|
||||||
|
import { AppState } from 'store/reducers';
|
||||||
|
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||||
|
|
||||||
|
import EmptyWidget from '../EmptyWidget';
|
||||||
|
import { MenuItemKeys } from '../WidgetHeader/contants';
|
||||||
|
import { GridCardGraphProps } from './types';
|
||||||
|
import WidgetGraphComponent from './WidgetGraphComponent';
|
||||||
|
|
||||||
|
function GridCardGraph({
|
||||||
|
widget,
|
||||||
|
name,
|
||||||
|
onClickHandler,
|
||||||
|
headerMenuList = [MenuItemKeys.View],
|
||||||
|
isQueryEnabled,
|
||||||
|
threshold,
|
||||||
|
}: GridCardGraphProps): JSX.Element {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const [errorMessage, setErrorMessage] = useState<string>();
|
||||||
|
|
||||||
|
const onDragSelect = (start: number, end: number): void => {
|
||||||
|
const startTimestamp = Math.trunc(start);
|
||||||
|
const endTimestamp = Math.trunc(end);
|
||||||
|
|
||||||
|
if (startTimestamp !== endTimestamp) {
|
||||||
|
dispatch(UpdateTimeInterval('custom', [startTimestamp, endTimestamp]));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const { ref: graphRef, inView: isGraphVisible } = useInView({
|
||||||
|
threshold: 0,
|
||||||
|
triggerOnce: true,
|
||||||
|
initialInView: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { selectedDashboard } = useDashboard();
|
||||||
|
|
||||||
|
const { minTime, maxTime, selectedTime: globalSelectedInterval } = useSelector<
|
||||||
|
AppState,
|
||||||
|
GlobalReducer
|
||||||
|
>((state) => state.globalTime);
|
||||||
|
|
||||||
|
const updatedQuery = useStepInterval(widget?.query);
|
||||||
|
|
||||||
|
const isEmptyWidget =
|
||||||
|
widget?.id === PANEL_TYPES.EMPTY_WIDGET || isEmpty(widget);
|
||||||
|
|
||||||
|
const queryResponse = useGetQueryRange(
|
||||||
|
{
|
||||||
|
selectedTime: widget?.timePreferance,
|
||||||
|
graphType: widget?.panelTypes,
|
||||||
|
query: updatedQuery,
|
||||||
|
globalSelectedInterval,
|
||||||
|
variables: getDashboardVariables(selectedDashboard?.data.variables),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
queryKey: [
|
||||||
|
maxTime,
|
||||||
|
minTime,
|
||||||
|
globalSelectedInterval,
|
||||||
|
selectedDashboard?.data?.variables,
|
||||||
|
widget?.query,
|
||||||
|
widget?.panelTypes,
|
||||||
|
widget.timePreferance,
|
||||||
|
],
|
||||||
|
keepPreviousData: true,
|
||||||
|
enabled: isGraphVisible && !isEmptyWidget && isQueryEnabled,
|
||||||
|
refetchOnMount: false,
|
||||||
|
onError: (error) => {
|
||||||
|
setErrorMessage(error.message);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const chartData = useMemo(
|
||||||
|
() =>
|
||||||
|
getChartData({
|
||||||
|
queryData: [
|
||||||
|
{
|
||||||
|
queryData: queryResponse?.data?.payload?.data?.result || [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
createDataset: undefined,
|
||||||
|
isWarningLimit: true,
|
||||||
|
}),
|
||||||
|
[queryResponse],
|
||||||
|
);
|
||||||
|
|
||||||
|
const isEmptyLayout = widget?.id === PANEL_TYPES.EMPTY_WIDGET;
|
||||||
|
|
||||||
|
if (queryResponse.isLoading) {
|
||||||
|
return <Skeleton />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span ref={graphRef}>
|
||||||
|
<WidgetGraphComponent
|
||||||
|
widget={widget}
|
||||||
|
queryResponse={queryResponse}
|
||||||
|
errorMessage={errorMessage}
|
||||||
|
data={chartData.data}
|
||||||
|
isWarning={chartData.isWarning}
|
||||||
|
name={name}
|
||||||
|
onDragSelect={onDragSelect}
|
||||||
|
threshold={threshold}
|
||||||
|
headerMenuList={headerMenuList}
|
||||||
|
onClickHandler={onClickHandler}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{isEmptyLayout && <EmptyWidget />}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
GridCardGraph.defaultProps = {
|
||||||
|
onDragSelect: undefined,
|
||||||
|
onClickHandler: undefined,
|
||||||
|
isQueryEnabled: true,
|
||||||
|
threshold: undefined,
|
||||||
|
headerMenuList: [MenuItemKeys.View],
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(GridCardGraph);
|
@ -1,15 +1,11 @@
|
|||||||
import { ChartData } from 'chart.js';
|
import { ChartData } from 'chart.js';
|
||||||
import { GraphOnClickHandler, ToggleGraphProps } from 'components/Graph/types';
|
import { GraphOnClickHandler, ToggleGraphProps } from 'components/Graph/types';
|
||||||
import { Dispatch, MutableRefObject, ReactNode, SetStateAction } from 'react';
|
import { MutableRefObject, ReactNode } from 'react';
|
||||||
import { Layout } from 'react-grid-layout';
|
|
||||||
import { UseQueryResult } from 'react-query';
|
import { UseQueryResult } from 'react-query';
|
||||||
import { DeleteWidgetProps } from 'store/actions/dashboard/deleteWidget';
|
|
||||||
import AppActions from 'types/actions';
|
|
||||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||||
import { Widgets } from 'types/api/dashboard/getAll';
|
import { Widgets } from 'types/api/dashboard/getAll';
|
||||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||||
|
|
||||||
import { LayoutProps } from '..';
|
|
||||||
import { MenuItemKeys } from '../WidgetHeader/contants';
|
import { MenuItemKeys } from '../WidgetHeader/contants';
|
||||||
import { LegendEntryProps } from './FullView/types';
|
import { LegendEntryProps } from './FullView/types';
|
||||||
|
|
||||||
@ -18,15 +14,7 @@ export interface GraphVisibilityLegendEntryProps {
|
|||||||
legendEntry: LegendEntryProps[];
|
legendEntry: LegendEntryProps[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DispatchProps {
|
export interface WidgetGraphComponentProps {
|
||||||
deleteWidget: ({
|
|
||||||
widgetId,
|
|
||||||
}: DeleteWidgetProps) => (dispatch: Dispatch<AppActions>) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface WidgetGraphComponentProps extends DispatchProps {
|
|
||||||
enableModel: boolean;
|
|
||||||
enableWidgetHeader: boolean;
|
|
||||||
widget: Widgets;
|
widget: Widgets;
|
||||||
queryResponse: UseQueryResult<
|
queryResponse: UseQueryResult<
|
||||||
SuccessResponse<MetricRangePayloadProps> | ErrorResponse
|
SuccessResponse<MetricRangePayloadProps> | ErrorResponse
|
||||||
@ -34,21 +22,16 @@ export interface WidgetGraphComponentProps extends DispatchProps {
|
|||||||
errorMessage: string | undefined;
|
errorMessage: string | undefined;
|
||||||
data: ChartData;
|
data: ChartData;
|
||||||
name: string;
|
name: string;
|
||||||
yAxisUnit?: string;
|
|
||||||
layout?: Layout[];
|
|
||||||
setLayout?: Dispatch<SetStateAction<LayoutProps[]>>;
|
|
||||||
onDragSelect?: (start: number, end: number) => void;
|
onDragSelect?: (start: number, end: number) => void;
|
||||||
onClickHandler?: GraphOnClickHandler;
|
onClickHandler?: GraphOnClickHandler;
|
||||||
threshold?: ReactNode;
|
threshold?: ReactNode;
|
||||||
headerMenuList: MenuItemKeys[];
|
headerMenuList: MenuItemKeys[];
|
||||||
|
isWarning: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GridCardGraphProps {
|
export interface GridCardGraphProps {
|
||||||
widget: Widgets;
|
widget: Widgets;
|
||||||
name: string;
|
name: string;
|
||||||
yAxisUnit: string | undefined;
|
|
||||||
layout?: Layout[];
|
|
||||||
setLayout?: Dispatch<SetStateAction<LayoutProps[]>>;
|
|
||||||
onDragSelect?: (start: number, end: number) => void;
|
onDragSelect?: (start: number, end: number) => void;
|
||||||
onClickHandler?: GraphOnClickHandler;
|
onClickHandler?: GraphOnClickHandler;
|
||||||
threshold?: ReactNode;
|
threshold?: ReactNode;
|
141
frontend/src/container/GridCardLayout/GridCardLayout.tsx
Normal file
141
frontend/src/container/GridCardLayout/GridCardLayout.tsx
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
import { PlusOutlined, SaveFilled } from '@ant-design/icons';
|
||||||
|
import { SOMETHING_WENT_WRONG } from 'constants/api';
|
||||||
|
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||||
|
import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard';
|
||||||
|
import useComponentPermission from 'hooks/useComponentPermission';
|
||||||
|
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||||
|
import { useNotifications } from 'hooks/useNotifications';
|
||||||
|
import { useDashboard } from 'providers/Dashboard/Dashboard';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
|
import { AppState } from 'store/reducers';
|
||||||
|
import { Dashboard, Widgets } from 'types/api/dashboard/getAll';
|
||||||
|
import AppReducer from 'types/reducer/app';
|
||||||
|
|
||||||
|
import { headerMenuList } from './config';
|
||||||
|
import GridCard from './GridCard';
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
ButtonContainer,
|
||||||
|
Card,
|
||||||
|
CardContainer,
|
||||||
|
ReactGridLayout,
|
||||||
|
} from './styles';
|
||||||
|
import { GraphLayoutProps } from './types';
|
||||||
|
|
||||||
|
function GraphLayout({
|
||||||
|
onAddPanelHandler,
|
||||||
|
widgets,
|
||||||
|
}: GraphLayoutProps): JSX.Element {
|
||||||
|
const {
|
||||||
|
selectedDashboard,
|
||||||
|
layouts,
|
||||||
|
setLayouts,
|
||||||
|
setSelectedDashboard,
|
||||||
|
} = useDashboard();
|
||||||
|
const { t } = useTranslation(['dashboard']);
|
||||||
|
|
||||||
|
const { featureResponse, role } = useSelector<AppState, AppReducer>(
|
||||||
|
(state) => state.app,
|
||||||
|
);
|
||||||
|
|
||||||
|
const isDarkMode = useIsDarkMode();
|
||||||
|
|
||||||
|
const updateDashboardMutation = useUpdateDashboard();
|
||||||
|
|
||||||
|
const { notifications } = useNotifications();
|
||||||
|
|
||||||
|
const [saveLayoutPermission, addPanelPermission] = useComponentPermission(
|
||||||
|
['save_layout', 'add_panel'],
|
||||||
|
role,
|
||||||
|
);
|
||||||
|
|
||||||
|
const onSaveHandler = (): void => {
|
||||||
|
if (!selectedDashboard) return;
|
||||||
|
|
||||||
|
const updatedDashboard: Dashboard = {
|
||||||
|
...selectedDashboard,
|
||||||
|
data: {
|
||||||
|
...selectedDashboard.data,
|
||||||
|
layout: layouts.filter((e) => e.i !== PANEL_TYPES.EMPTY_WIDGET),
|
||||||
|
},
|
||||||
|
uuid: selectedDashboard.uuid,
|
||||||
|
};
|
||||||
|
|
||||||
|
updateDashboardMutation.mutate(updatedDashboard, {
|
||||||
|
onSuccess: (updatedDashboard) => {
|
||||||
|
if (updatedDashboard.payload) {
|
||||||
|
if (updatedDashboard.payload.data.layout)
|
||||||
|
setLayouts(updatedDashboard.payload.data.layout);
|
||||||
|
setSelectedDashboard(updatedDashboard.payload);
|
||||||
|
}
|
||||||
|
notifications.success({
|
||||||
|
message: t('dashboard:layout_saved_successfully'),
|
||||||
|
});
|
||||||
|
|
||||||
|
featureResponse.refetch();
|
||||||
|
},
|
||||||
|
onError: () => {
|
||||||
|
notifications.error({
|
||||||
|
message: SOMETHING_WENT_WRONG,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ButtonContainer>
|
||||||
|
{saveLayoutPermission && (
|
||||||
|
<Button
|
||||||
|
loading={updateDashboardMutation.isLoading}
|
||||||
|
onClick={onSaveHandler}
|
||||||
|
icon={<SaveFilled />}
|
||||||
|
disabled={updateDashboardMutation.isLoading}
|
||||||
|
>
|
||||||
|
{t('dashboard:save_layout')}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{addPanelPermission && (
|
||||||
|
<Button onClick={onAddPanelHandler} icon={<PlusOutlined />}>
|
||||||
|
{t('dashboard:add_panel')}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</ButtonContainer>
|
||||||
|
|
||||||
|
<ReactGridLayout
|
||||||
|
cols={12}
|
||||||
|
rowHeight={100}
|
||||||
|
autoSize
|
||||||
|
width={100}
|
||||||
|
isDraggable={addPanelPermission}
|
||||||
|
isDroppable={addPanelPermission}
|
||||||
|
isResizable={addPanelPermission}
|
||||||
|
allowOverlap={false}
|
||||||
|
onLayoutChange={setLayouts}
|
||||||
|
draggableHandle=".drag-handle"
|
||||||
|
layout={layouts}
|
||||||
|
>
|
||||||
|
{layouts.map((layout) => {
|
||||||
|
const { i: id } = layout;
|
||||||
|
const currentWidget = (widgets || [])?.find((e) => e.id === id);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CardContainer isDarkMode={isDarkMode} key={id} data-grid={layout}>
|
||||||
|
<Card $panelType={currentWidget?.panelTypes || PANEL_TYPES.TIME_SERIES}>
|
||||||
|
<GridCard
|
||||||
|
widget={currentWidget || ({ id } as Widgets)}
|
||||||
|
name={currentWidget?.id || ''}
|
||||||
|
headerMenuList={headerMenuList}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
</CardContainer>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</ReactGridLayout>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default GraphLayout;
|
@ -1,9 +1,14 @@
|
|||||||
import { themeColors } from 'constants/theme';
|
import { themeColors } from 'constants/theme';
|
||||||
|
import { limit } from 'lib/getChartData';
|
||||||
import { CSSProperties } from 'react';
|
import { CSSProperties } from 'react';
|
||||||
|
|
||||||
const positionCss: CSSProperties['position'] = 'absolute';
|
const positionCss: CSSProperties['position'] = 'absolute';
|
||||||
|
|
||||||
export const spinnerStyles = { position: positionCss, right: '0.5rem' };
|
export const spinnerStyles = {
|
||||||
|
position: positionCss,
|
||||||
|
top: '0',
|
||||||
|
right: '0',
|
||||||
|
};
|
||||||
export const tooltipStyles = {
|
export const tooltipStyles = {
|
||||||
fontSize: '1rem',
|
fontSize: '1rem',
|
||||||
top: '0.313rem',
|
top: '0.313rem',
|
||||||
@ -21,3 +26,5 @@ export const overlayStyles: CSSProperties = {
|
|||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const WARNING_MESSAGE = `Too many timeseries in the result. UI has restricted to showing the top ${limit}. Please check the query if this is needed and contact support@signoz.io if you need to show >${limit} timeseries in the panel`;
|
@ -5,10 +5,12 @@ import {
|
|||||||
EditFilled,
|
EditFilled,
|
||||||
ExclamationCircleOutlined,
|
ExclamationCircleOutlined,
|
||||||
FullscreenOutlined,
|
FullscreenOutlined,
|
||||||
|
WarningOutlined,
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import { Dropdown, MenuProps, Tooltip, Typography } from 'antd';
|
import { Dropdown, MenuProps, Tooltip, Typography } from 'antd';
|
||||||
import Spinner from 'components/Spinner';
|
import Spinner from 'components/Spinner';
|
||||||
import { QueryParams } from 'constants/query';
|
import { QueryParams } from 'constants/query';
|
||||||
|
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||||
import ROUTES from 'constants/routes';
|
import ROUTES from 'constants/routes';
|
||||||
import useComponentPermission from 'hooks/useComponentPermission';
|
import useComponentPermission from 'hooks/useComponentPermission';
|
||||||
import history from 'lib/history';
|
import history from 'lib/history';
|
||||||
@ -27,6 +29,7 @@ import {
|
|||||||
overlayStyles,
|
overlayStyles,
|
||||||
spinnerStyles,
|
spinnerStyles,
|
||||||
tooltipStyles,
|
tooltipStyles,
|
||||||
|
WARNING_MESSAGE,
|
||||||
} from './config';
|
} from './config';
|
||||||
import { MENUITEM_KEYS_VS_LABELS, MenuItemKeys } from './contants';
|
import { MENUITEM_KEYS_VS_LABELS, MenuItemKeys } from './contants';
|
||||||
import {
|
import {
|
||||||
@ -52,6 +55,7 @@ interface IWidgetHeaderProps {
|
|||||||
errorMessage: string | undefined;
|
errorMessage: string | undefined;
|
||||||
threshold?: ReactNode;
|
threshold?: ReactNode;
|
||||||
headerMenuList?: MenuItemKeys[];
|
headerMenuList?: MenuItemKeys[];
|
||||||
|
isWarning: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
function WidgetHeader({
|
function WidgetHeader({
|
||||||
@ -65,7 +69,8 @@ function WidgetHeader({
|
|||||||
errorMessage,
|
errorMessage,
|
||||||
threshold,
|
threshold,
|
||||||
headerMenuList,
|
headerMenuList,
|
||||||
}: IWidgetHeaderProps): JSX.Element {
|
isWarning,
|
||||||
|
}: IWidgetHeaderProps): JSX.Element | null {
|
||||||
const [localHover, setLocalHover] = useState(false);
|
const [localHover, setLocalHover] = useState(false);
|
||||||
const [isOpen, setIsOpen] = useState<boolean>(false);
|
const [isOpen, setIsOpen] = useState<boolean>(false);
|
||||||
|
|
||||||
@ -126,7 +131,7 @@ function WidgetHeader({
|
|||||||
icon: <FullscreenOutlined />,
|
icon: <FullscreenOutlined />,
|
||||||
label: MENUITEM_KEYS_VS_LABELS[MenuItemKeys.View],
|
label: MENUITEM_KEYS_VS_LABELS[MenuItemKeys.View],
|
||||||
isVisible: headerMenuList?.includes(MenuItemKeys.View) || false,
|
isVisible: headerMenuList?.includes(MenuItemKeys.View) || false,
|
||||||
disabled: queryResponse.isLoading,
|
disabled: queryResponse.isFetching,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: MenuItemKeys.Edit,
|
key: MenuItemKeys.Edit,
|
||||||
@ -158,7 +163,7 @@ function WidgetHeader({
|
|||||||
disabled: false,
|
disabled: false,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[queryResponse.isLoading, headerMenuList, editWidget, deleteWidget],
|
[headerMenuList, queryResponse.isFetching, editWidget, deleteWidget],
|
||||||
);
|
);
|
||||||
|
|
||||||
const updatedMenuList = useMemo(() => generateMenuList(actions), [actions]);
|
const updatedMenuList = useMemo(() => generateMenuList(actions), [actions]);
|
||||||
@ -175,6 +180,10 @@ function WidgetHeader({
|
|||||||
[updatedMenuList, onMenuItemSelectHandler],
|
[updatedMenuList, onMenuItemSelectHandler],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (widget.id === PANEL_TYPES.EMPTY_WIDGET) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<WidgetHeaderContainer>
|
<WidgetHeaderContainer>
|
||||||
<Dropdown
|
<Dropdown
|
||||||
@ -202,6 +211,7 @@ function WidgetHeader({
|
|||||||
</HeaderContentContainer>
|
</HeaderContentContainer>
|
||||||
</HeaderContainer>
|
</HeaderContainer>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
|
|
||||||
<ThesholdContainer>{threshold}</ThesholdContainer>
|
<ThesholdContainer>{threshold}</ThesholdContainer>
|
||||||
{queryResponse.isFetching && !queryResponse.isError && (
|
{queryResponse.isFetching && !queryResponse.isError && (
|
||||||
<Spinner height="5vh" style={spinnerStyles} />
|
<Spinner height="5vh" style={spinnerStyles} />
|
||||||
@ -211,6 +221,12 @@ function WidgetHeader({
|
|||||||
<ExclamationCircleOutlined style={tooltipStyles} />
|
<ExclamationCircleOutlined style={tooltipStyles} />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{isWarning && (
|
||||||
|
<Tooltip title={WARNING_MESSAGE} placement={errorTooltipPosition}>
|
||||||
|
<WarningOutlined style={tooltipStyles} />
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
</WidgetHeaderContainer>
|
</WidgetHeaderContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
17
frontend/src/container/GridCardLayout/config.ts
Normal file
17
frontend/src/container/GridCardLayout/config.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||||
|
import { MenuItemKeys } from 'container/GridCardLayout/WidgetHeader/contants';
|
||||||
|
|
||||||
|
export const headerMenuList = [
|
||||||
|
MenuItemKeys.View,
|
||||||
|
MenuItemKeys.Clone,
|
||||||
|
MenuItemKeys.Delete,
|
||||||
|
MenuItemKeys.Edit,
|
||||||
|
];
|
||||||
|
|
||||||
|
export const EMPTY_WIDGET_LAYOUT = {
|
||||||
|
i: PANEL_TYPES.EMPTY_WIDGET,
|
||||||
|
w: 6,
|
||||||
|
x: 0,
|
||||||
|
h: 2,
|
||||||
|
y: 0,
|
||||||
|
};
|
35
frontend/src/container/GridCardLayout/index.tsx
Normal file
35
frontend/src/container/GridCardLayout/index.tsx
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import { useDashboard } from 'providers/Dashboard/Dashboard';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
import { Layout } from 'react-grid-layout';
|
||||||
|
|
||||||
|
import { EMPTY_WIDGET_LAYOUT } from './config';
|
||||||
|
import GraphLayoutContainer from './GridCardLayout';
|
||||||
|
|
||||||
|
function GridGraph(): JSX.Element {
|
||||||
|
const {
|
||||||
|
selectedDashboard,
|
||||||
|
setLayouts,
|
||||||
|
handleToggleDashboardSlider,
|
||||||
|
} = useDashboard();
|
||||||
|
|
||||||
|
const { data } = selectedDashboard || {};
|
||||||
|
const { widgets } = data || {};
|
||||||
|
|
||||||
|
const onEmptyWidgetHandler = useCallback(() => {
|
||||||
|
handleToggleDashboardSlider(true);
|
||||||
|
|
||||||
|
setLayouts((preLayout: Layout[]) => [
|
||||||
|
EMPTY_WIDGET_LAYOUT,
|
||||||
|
...(preLayout || []),
|
||||||
|
]);
|
||||||
|
}, [handleToggleDashboardSlider, setLayouts]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<GraphLayoutContainer
|
||||||
|
onAddPanelHandler={onEmptyWidgetHandler}
|
||||||
|
widgets={widgets}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default GridGraph;
|
6
frontend/src/container/GridCardLayout/types.ts
Normal file
6
frontend/src/container/GridCardLayout/types.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import { Widgets } from 'types/api/dashboard/getAll';
|
||||||
|
|
||||||
|
export interface GraphLayoutProps {
|
||||||
|
onAddPanelHandler: VoidFunction;
|
||||||
|
widgets?: Widgets[];
|
||||||
|
}
|
@ -1,207 +0,0 @@
|
|||||||
import { Button, Input } from 'antd';
|
|
||||||
import { CheckboxChangeEvent } from 'antd/es/checkbox';
|
|
||||||
import { ResizeTable } from 'components/ResizeTable';
|
|
||||||
import { Events } from 'constants/events';
|
|
||||||
import { useNotifications } from 'hooks/useNotifications';
|
|
||||||
import isEqual from 'lodash-es/isEqual';
|
|
||||||
import { memo, useCallback, useEffect, useMemo, useState } from 'react';
|
|
||||||
import { eventEmitter } from 'utils/getEventEmitter';
|
|
||||||
|
|
||||||
import { getGraphVisibilityStateOnDataChange } from '../utils';
|
|
||||||
import {
|
|
||||||
FilterTableAndSaveContainer,
|
|
||||||
FilterTableContainer,
|
|
||||||
SaveCancelButtonContainer,
|
|
||||||
SaveContainer,
|
|
||||||
} from './styles';
|
|
||||||
import { getGraphManagerTableColumns } from './TableRender/GraphManagerColumns';
|
|
||||||
import { ExtendedChartDataset, GraphManagerProps } from './types';
|
|
||||||
import {
|
|
||||||
getDefaultTableDataSet,
|
|
||||||
saveLegendEntriesToLocalStorage,
|
|
||||||
} from './utils';
|
|
||||||
|
|
||||||
function GraphManager({
|
|
||||||
data,
|
|
||||||
name,
|
|
||||||
yAxisUnit,
|
|
||||||
onToggleModelHandler,
|
|
||||||
}: GraphManagerProps): JSX.Element {
|
|
||||||
const {
|
|
||||||
graphVisibilityStates: localstoredVisibilityStates,
|
|
||||||
legendEntry,
|
|
||||||
} = useMemo(
|
|
||||||
() =>
|
|
||||||
getGraphVisibilityStateOnDataChange({
|
|
||||||
data,
|
|
||||||
isExpandedName: false,
|
|
||||||
name,
|
|
||||||
}),
|
|
||||||
[data, name],
|
|
||||||
);
|
|
||||||
|
|
||||||
const [graphVisibilityState, setGraphVisibilityState] = useState<boolean[]>(
|
|
||||||
localstoredVisibilityStates,
|
|
||||||
);
|
|
||||||
|
|
||||||
const [tableDataSet, setTableDataSet] = useState<ExtendedChartDataset[]>(
|
|
||||||
getDefaultTableDataSet(data),
|
|
||||||
);
|
|
||||||
|
|
||||||
const { notifications } = useNotifications();
|
|
||||||
|
|
||||||
// useEffect for updating graph visibility state on data change
|
|
||||||
useEffect(() => {
|
|
||||||
const newGraphVisibilityStates = Array<boolean>(data.datasets.length).fill(
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
data.datasets.forEach((dataset, i) => {
|
|
||||||
const index = legendEntry.findIndex(
|
|
||||||
(entry) => entry.label === dataset.label,
|
|
||||||
);
|
|
||||||
if (index !== -1) {
|
|
||||||
newGraphVisibilityStates[i] = legendEntry[index].show;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
eventEmitter.emit(Events.UPDATE_GRAPH_VISIBILITY_STATE, {
|
|
||||||
name,
|
|
||||||
graphVisibilityStates: newGraphVisibilityStates,
|
|
||||||
});
|
|
||||||
setGraphVisibilityState(newGraphVisibilityStates);
|
|
||||||
}, [data, name, legendEntry]);
|
|
||||||
|
|
||||||
// useEffect for listening to events event graph legend is clicked
|
|
||||||
useEffect(() => {
|
|
||||||
const eventListener = eventEmitter.on(
|
|
||||||
Events.UPDATE_GRAPH_MANAGER_TABLE,
|
|
||||||
(data) => {
|
|
||||||
if (data.name === name) {
|
|
||||||
const newGraphVisibilityStates = graphVisibilityState;
|
|
||||||
newGraphVisibilityStates[data.index] = !newGraphVisibilityStates[
|
|
||||||
data.index
|
|
||||||
];
|
|
||||||
eventEmitter.emit(Events.UPDATE_GRAPH_VISIBILITY_STATE, {
|
|
||||||
name,
|
|
||||||
graphVisibilityStates: newGraphVisibilityStates,
|
|
||||||
});
|
|
||||||
setGraphVisibilityState([...newGraphVisibilityStates]);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
return (): void => {
|
|
||||||
eventListener.off(Events.UPDATE_GRAPH_MANAGER_TABLE);
|
|
||||||
};
|
|
||||||
}, [graphVisibilityState, name]);
|
|
||||||
|
|
||||||
const checkBoxOnChangeHandler = useCallback(
|
|
||||||
(e: CheckboxChangeEvent, index: number): void => {
|
|
||||||
graphVisibilityState[index] = e.target.checked;
|
|
||||||
setGraphVisibilityState([...graphVisibilityState]);
|
|
||||||
eventEmitter.emit(Events.UPDATE_GRAPH_VISIBILITY_STATE, {
|
|
||||||
name,
|
|
||||||
graphVisibilityStates: [...graphVisibilityState],
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[graphVisibilityState, name],
|
|
||||||
);
|
|
||||||
|
|
||||||
const labelClickedHandler = useCallback(
|
|
||||||
(labelIndex: number): void => {
|
|
||||||
const newGraphVisibilityStates = Array<boolean>(data.datasets.length).fill(
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
newGraphVisibilityStates[labelIndex] = true;
|
|
||||||
setGraphVisibilityState([...newGraphVisibilityStates]);
|
|
||||||
eventEmitter.emit(Events.UPDATE_GRAPH_VISIBILITY_STATE, {
|
|
||||||
name,
|
|
||||||
graphVisibilityStates: newGraphVisibilityStates,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[data.datasets.length, name],
|
|
||||||
);
|
|
||||||
|
|
||||||
const columns = useMemo(
|
|
||||||
() =>
|
|
||||||
getGraphManagerTableColumns({
|
|
||||||
data,
|
|
||||||
checkBoxOnChangeHandler,
|
|
||||||
graphVisibilityState,
|
|
||||||
labelClickedHandler,
|
|
||||||
yAxisUnit,
|
|
||||||
}),
|
|
||||||
[
|
|
||||||
checkBoxOnChangeHandler,
|
|
||||||
data,
|
|
||||||
graphVisibilityState,
|
|
||||||
labelClickedHandler,
|
|
||||||
yAxisUnit,
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
const filterHandler = useCallback(
|
|
||||||
(event: React.ChangeEvent<HTMLInputElement>): void => {
|
|
||||||
const value = event.target.value.toString().toLowerCase();
|
|
||||||
const updatedDataSet = tableDataSet.map((item) => {
|
|
||||||
if (item.label?.toLocaleLowerCase().includes(value)) {
|
|
||||||
return { ...item, show: true };
|
|
||||||
}
|
|
||||||
return { ...item, show: false };
|
|
||||||
});
|
|
||||||
setTableDataSet(updatedDataSet);
|
|
||||||
},
|
|
||||||
[tableDataSet],
|
|
||||||
);
|
|
||||||
|
|
||||||
const saveHandler = useCallback((): void => {
|
|
||||||
saveLegendEntriesToLocalStorage({
|
|
||||||
data,
|
|
||||||
graphVisibilityState,
|
|
||||||
name,
|
|
||||||
});
|
|
||||||
notifications.success({
|
|
||||||
message: 'The updated graphs & legends are saved',
|
|
||||||
});
|
|
||||||
if (onToggleModelHandler) {
|
|
||||||
onToggleModelHandler();
|
|
||||||
}
|
|
||||||
}, [data, graphVisibilityState, name, notifications, onToggleModelHandler]);
|
|
||||||
|
|
||||||
const dataSource = tableDataSet.filter((item) => item.show);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<FilterTableAndSaveContainer>
|
|
||||||
<FilterTableContainer>
|
|
||||||
<Input onChange={filterHandler} placeholder="Filter Series" />
|
|
||||||
<ResizeTable
|
|
||||||
columns={columns}
|
|
||||||
dataSource={dataSource}
|
|
||||||
rowKey="index"
|
|
||||||
pagination={false}
|
|
||||||
scroll={{ y: 240 }}
|
|
||||||
/>
|
|
||||||
</FilterTableContainer>
|
|
||||||
<SaveContainer>
|
|
||||||
<SaveCancelButtonContainer>
|
|
||||||
<Button type="default" onClick={onToggleModelHandler}>
|
|
||||||
Cancel
|
|
||||||
</Button>
|
|
||||||
</SaveCancelButtonContainer>
|
|
||||||
<SaveCancelButtonContainer>
|
|
||||||
<Button onClick={saveHandler} type="primary">
|
|
||||||
Save
|
|
||||||
</Button>
|
|
||||||
</SaveCancelButtonContainer>
|
|
||||||
</SaveContainer>
|
|
||||||
</FilterTableAndSaveContainer>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
GraphManager.defaultProps = {
|
|
||||||
graphVisibilityStateHandler: undefined,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default memo(
|
|
||||||
GraphManager,
|
|
||||||
(prevProps, nextProps) =>
|
|
||||||
isEqual(prevProps.data, nextProps.data) && prevProps.name === nextProps.name,
|
|
||||||
);
|
|
@ -1,27 +0,0 @@
|
|||||||
import { CheckboxChangeEvent } from 'antd/es/checkbox';
|
|
||||||
import { ColumnType } from 'antd/es/table';
|
|
||||||
import { ChartData } from 'chart.js';
|
|
||||||
|
|
||||||
import { DataSetProps } from '../types';
|
|
||||||
import CustomCheckBox from './CustomCheckBox';
|
|
||||||
|
|
||||||
export const getCheckBox = ({
|
|
||||||
data,
|
|
||||||
checkBoxOnChangeHandler,
|
|
||||||
graphVisibilityState,
|
|
||||||
}: GetCheckBoxProps): ColumnType<DataSetProps> => ({
|
|
||||||
render: (index: number): JSX.Element => (
|
|
||||||
<CustomCheckBox
|
|
||||||
data={data}
|
|
||||||
index={index}
|
|
||||||
checkBoxOnChangeHandler={checkBoxOnChangeHandler}
|
|
||||||
graphVisibilityState={graphVisibilityState}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
});
|
|
||||||
|
|
||||||
interface GetCheckBoxProps {
|
|
||||||
data: ChartData;
|
|
||||||
checkBoxOnChangeHandler: (e: CheckboxChangeEvent, index: number) => void;
|
|
||||||
graphVisibilityState: boolean[];
|
|
||||||
}
|
|
@ -1,334 +0,0 @@
|
|||||||
import { Typography } from 'antd';
|
|
||||||
import { ToggleGraphProps } from 'components/Graph/types';
|
|
||||||
import { Events } from 'constants/events';
|
|
||||||
import GridPanelSwitch from 'container/GridPanelSwitch';
|
|
||||||
import { useChartMutable } from 'hooks/useChartMutable';
|
|
||||||
import { useNotifications } from 'hooks/useNotifications';
|
|
||||||
import createQueryParams from 'lib/createQueryParams';
|
|
||||||
import history from 'lib/history';
|
|
||||||
import { isEmpty, isEqual } from 'lodash-es';
|
|
||||||
import {
|
|
||||||
Dispatch,
|
|
||||||
memo,
|
|
||||||
SetStateAction,
|
|
||||||
useCallback,
|
|
||||||
useEffect,
|
|
||||||
useMemo,
|
|
||||||
useRef,
|
|
||||||
useState,
|
|
||||||
} from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { connect, useSelector } from 'react-redux';
|
|
||||||
import { useLocation } from 'react-router-dom';
|
|
||||||
import { bindActionCreators } from 'redux';
|
|
||||||
import { ThunkDispatch } from 'redux-thunk';
|
|
||||||
import { DeleteWidget } from 'store/actions/dashboard/deleteWidget';
|
|
||||||
import { AppState } from 'store/reducers';
|
|
||||||
import AppActions from 'types/actions';
|
|
||||||
import AppReducer from 'types/reducer/app';
|
|
||||||
import DashboardReducer from 'types/reducer/dashboards';
|
|
||||||
import { eventEmitter } from 'utils/getEventEmitter';
|
|
||||||
import { v4 } from 'uuid';
|
|
||||||
|
|
||||||
import { UpdateDashboard } from '../utils';
|
|
||||||
import WidgetHeader from '../WidgetHeader';
|
|
||||||
import FullView from './FullView';
|
|
||||||
import { PANEL_TYPES_VS_FULL_VIEW_TABLE } from './FullView/contants';
|
|
||||||
import { FullViewContainer, Modal } from './styles';
|
|
||||||
import { DispatchProps, WidgetGraphComponentProps } from './types';
|
|
||||||
import {
|
|
||||||
getGraphVisibilityStateOnDataChange,
|
|
||||||
toggleGraphsVisibilityInChart,
|
|
||||||
} from './utils';
|
|
||||||
|
|
||||||
function WidgetGraphComponent({
|
|
||||||
enableModel,
|
|
||||||
enableWidgetHeader,
|
|
||||||
data,
|
|
||||||
widget,
|
|
||||||
queryResponse,
|
|
||||||
errorMessage,
|
|
||||||
name,
|
|
||||||
yAxisUnit,
|
|
||||||
layout = [],
|
|
||||||
deleteWidget,
|
|
||||||
setLayout,
|
|
||||||
onDragSelect,
|
|
||||||
onClickHandler,
|
|
||||||
threshold,
|
|
||||||
headerMenuList,
|
|
||||||
}: WidgetGraphComponentProps): JSX.Element {
|
|
||||||
const [deleteModal, setDeleteModal] = useState(false);
|
|
||||||
const [modal, setModal] = useState<boolean>(false);
|
|
||||||
const [hovered, setHovered] = useState(false);
|
|
||||||
const { notifications } = useNotifications();
|
|
||||||
const { t } = useTranslation(['common']);
|
|
||||||
const { pathname } = useLocation();
|
|
||||||
|
|
||||||
const { graphVisibilityStates: localstoredVisibilityStates } = useMemo(
|
|
||||||
() =>
|
|
||||||
getGraphVisibilityStateOnDataChange({
|
|
||||||
data,
|
|
||||||
isExpandedName: true,
|
|
||||||
name,
|
|
||||||
}),
|
|
||||||
[data, name],
|
|
||||||
);
|
|
||||||
|
|
||||||
const [graphsVisibilityStates, setGraphsVisilityStates] = useState<boolean[]>(
|
|
||||||
localstoredVisibilityStates,
|
|
||||||
);
|
|
||||||
|
|
||||||
const { dashboards } = useSelector<AppState, DashboardReducer>(
|
|
||||||
(state) => state.dashboards,
|
|
||||||
);
|
|
||||||
const [selectedDashboard] = dashboards;
|
|
||||||
|
|
||||||
const canModifyChart = useChartMutable({
|
|
||||||
panelType: widget.panelTypes,
|
|
||||||
panelTypeAndGraphManagerVisibility: PANEL_TYPES_VS_FULL_VIEW_TABLE,
|
|
||||||
});
|
|
||||||
|
|
||||||
const lineChartRef = useRef<ToggleGraphProps>();
|
|
||||||
|
|
||||||
// Updating the visibility state of the graph on data change according to global time range
|
|
||||||
useEffect(() => {
|
|
||||||
if (canModifyChart) {
|
|
||||||
const newGraphVisibilityState = getGraphVisibilityStateOnDataChange({
|
|
||||||
data,
|
|
||||||
isExpandedName: true,
|
|
||||||
name,
|
|
||||||
});
|
|
||||||
setGraphsVisilityStates(newGraphVisibilityState.graphVisibilityStates);
|
|
||||||
}
|
|
||||||
}, [canModifyChart, data, name]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const eventListener = eventEmitter.on(
|
|
||||||
Events.UPDATE_GRAPH_VISIBILITY_STATE,
|
|
||||||
(data) => {
|
|
||||||
if (data.name === `${name}expanded` && canModifyChart) {
|
|
||||||
setGraphsVisilityStates([...data.graphVisibilityStates]);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
return (): void => {
|
|
||||||
eventListener.off(Events.UPDATE_GRAPH_VISIBILITY_STATE);
|
|
||||||
};
|
|
||||||
}, [canModifyChart, name]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (canModifyChart && lineChartRef.current) {
|
|
||||||
toggleGraphsVisibilityInChart({
|
|
||||||
graphsVisibilityStates,
|
|
||||||
lineChartRef,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [graphsVisibilityStates, canModifyChart]);
|
|
||||||
|
|
||||||
const { featureResponse } = useSelector<AppState, AppReducer>(
|
|
||||||
(state) => state.app,
|
|
||||||
);
|
|
||||||
const onToggleModal = useCallback(
|
|
||||||
(func: Dispatch<SetStateAction<boolean>>) => {
|
|
||||||
func((value) => !value);
|
|
||||||
},
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
const onDeleteHandler = useCallback(() => {
|
|
||||||
const isEmptyWidget = widget?.id === 'empty' || isEmpty(widget);
|
|
||||||
const widgetId = isEmptyWidget ? layout[0].i : widget?.id;
|
|
||||||
|
|
||||||
featureResponse
|
|
||||||
.refetch()
|
|
||||||
.then(() => {
|
|
||||||
deleteWidget({ widgetId, setLayout });
|
|
||||||
onToggleModal(setDeleteModal);
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
notifications.error({
|
|
||||||
message: t('common:something_went_wrong'),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}, [
|
|
||||||
widget,
|
|
||||||
layout,
|
|
||||||
featureResponse,
|
|
||||||
deleteWidget,
|
|
||||||
setLayout,
|
|
||||||
onToggleModal,
|
|
||||||
notifications,
|
|
||||||
t,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const onCloneHandler = async (): Promise<void> => {
|
|
||||||
const uuid = v4();
|
|
||||||
|
|
||||||
const layout = [
|
|
||||||
{
|
|
||||||
i: uuid,
|
|
||||||
w: 6,
|
|
||||||
x: 0,
|
|
||||||
h: 2,
|
|
||||||
y: 0,
|
|
||||||
},
|
|
||||||
...(selectedDashboard.data.layout || []),
|
|
||||||
];
|
|
||||||
|
|
||||||
if (widget) {
|
|
||||||
await UpdateDashboard(
|
|
||||||
{
|
|
||||||
data: selectedDashboard.data,
|
|
||||||
generateWidgetId: uuid,
|
|
||||||
graphType: widget?.panelTypes,
|
|
||||||
selectedDashboard,
|
|
||||||
layout,
|
|
||||||
widgetData: widget,
|
|
||||||
isRedirected: false,
|
|
||||||
},
|
|
||||||
notifications,
|
|
||||||
).then(() => {
|
|
||||||
notifications.success({
|
|
||||||
message: 'Panel cloned successfully, redirecting to new copy.',
|
|
||||||
});
|
|
||||||
|
|
||||||
const queryParams = {
|
|
||||||
graphType: widget?.panelTypes,
|
|
||||||
widgetId: uuid,
|
|
||||||
};
|
|
||||||
history.push(`${pathname}/new?${createQueryParams(queryParams)}`);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleOnView = (): void => {
|
|
||||||
onToggleModal(setModal);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleOnDelete = (): void => {
|
|
||||||
onToggleModal(setDeleteModal);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onDeleteModelHandler = (): void => {
|
|
||||||
onToggleModal(setDeleteModal);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onToggleModelHandler = (): void => {
|
|
||||||
onToggleModal(setModal);
|
|
||||||
};
|
|
||||||
|
|
||||||
const getModals = (): JSX.Element => (
|
|
||||||
<>
|
|
||||||
<Modal
|
|
||||||
destroyOnClose
|
|
||||||
onCancel={onDeleteModelHandler}
|
|
||||||
open={deleteModal}
|
|
||||||
title="Delete"
|
|
||||||
height="10vh"
|
|
||||||
onOk={onDeleteHandler}
|
|
||||||
centered
|
|
||||||
>
|
|
||||||
<Typography>Are you sure you want to delete this widget</Typography>
|
|
||||||
</Modal>
|
|
||||||
|
|
||||||
<Modal
|
|
||||||
title="View"
|
|
||||||
footer={[]}
|
|
||||||
centered
|
|
||||||
open={modal}
|
|
||||||
onCancel={onToggleModelHandler}
|
|
||||||
width="85%"
|
|
||||||
destroyOnClose
|
|
||||||
>
|
|
||||||
<FullViewContainer>
|
|
||||||
<FullView
|
|
||||||
name={`${name}expanded`}
|
|
||||||
widget={widget}
|
|
||||||
yAxisUnit={yAxisUnit}
|
|
||||||
graphsVisibilityStates={graphsVisibilityStates}
|
|
||||||
onToggleModelHandler={onToggleModelHandler}
|
|
||||||
/>
|
|
||||||
</FullViewContainer>
|
|
||||||
</Modal>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<span
|
|
||||||
onMouseOver={(): void => {
|
|
||||||
setHovered(true);
|
|
||||||
}}
|
|
||||||
onFocus={(): void => {
|
|
||||||
setHovered(true);
|
|
||||||
}}
|
|
||||||
onMouseOut={(): void => {
|
|
||||||
setHovered(false);
|
|
||||||
}}
|
|
||||||
onBlur={(): void => {
|
|
||||||
setHovered(false);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{enableModel && getModals()}
|
|
||||||
{!isEmpty(widget) && data && (
|
|
||||||
<>
|
|
||||||
{enableWidgetHeader && (
|
|
||||||
<div className="drag-handle">
|
|
||||||
<WidgetHeader
|
|
||||||
parentHover={hovered}
|
|
||||||
title={widget?.title}
|
|
||||||
widget={widget}
|
|
||||||
onView={handleOnView}
|
|
||||||
onDelete={handleOnDelete}
|
|
||||||
onClone={onCloneHandler}
|
|
||||||
queryResponse={queryResponse}
|
|
||||||
errorMessage={errorMessage}
|
|
||||||
threshold={threshold}
|
|
||||||
headerMenuList={headerMenuList}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<GridPanelSwitch
|
|
||||||
panelType={widget.panelTypes}
|
|
||||||
data={data}
|
|
||||||
isStacked={widget.isStacked}
|
|
||||||
opacity={widget.opacity}
|
|
||||||
title={' '}
|
|
||||||
name={name}
|
|
||||||
yAxisUnit={yAxisUnit}
|
|
||||||
onClickHandler={onClickHandler}
|
|
||||||
onDragSelect={onDragSelect}
|
|
||||||
panelData={queryResponse.data?.payload?.data.newResult.data.result || []}
|
|
||||||
query={widget.query}
|
|
||||||
ref={lineChartRef}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
WidgetGraphComponent.defaultProps = {
|
|
||||||
yAxisUnit: undefined,
|
|
||||||
layout: undefined,
|
|
||||||
setLayout: undefined,
|
|
||||||
onDragSelect: undefined,
|
|
||||||
onClickHandler: undefined,
|
|
||||||
};
|
|
||||||
|
|
||||||
const mapDispatchToProps = (
|
|
||||||
dispatch: ThunkDispatch<unknown, unknown, AppActions>,
|
|
||||||
): DispatchProps => ({
|
|
||||||
deleteWidget: bindActionCreators(DeleteWidget, dispatch),
|
|
||||||
});
|
|
||||||
|
|
||||||
export default connect(
|
|
||||||
null,
|
|
||||||
mapDispatchToProps,
|
|
||||||
)(
|
|
||||||
memo(
|
|
||||||
WidgetGraphComponent,
|
|
||||||
(prevProps, nextProps) =>
|
|
||||||
isEqual(prevProps.data, nextProps.data) && prevProps.name === nextProps.name,
|
|
||||||
),
|
|
||||||
);
|
|
@ -1,187 +0,0 @@
|
|||||||
import { ChartData } from 'chart.js';
|
|
||||||
import Spinner from 'components/Spinner';
|
|
||||||
import { useGetQueryRange } from 'hooks/queryBuilder/useGetQueryRange';
|
|
||||||
import { useStepInterval } from 'hooks/queryBuilder/useStepInterval';
|
|
||||||
import usePreviousValue from 'hooks/usePreviousValue';
|
|
||||||
import { getDashboardVariables } from 'lib/dashbaordVariables/getDashboardVariables';
|
|
||||||
import getChartData from 'lib/getChartData';
|
|
||||||
import isEmpty from 'lodash-es/isEmpty';
|
|
||||||
import { memo, useMemo, useState } from 'react';
|
|
||||||
import { useInView } from 'react-intersection-observer';
|
|
||||||
import { useSelector } from 'react-redux';
|
|
||||||
import { AppState } from 'store/reducers';
|
|
||||||
import DashboardReducer from 'types/reducer/dashboards';
|
|
||||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
|
||||||
import { getSelectedDashboardVariable } from 'utils/dashboard/selectedDashboard';
|
|
||||||
|
|
||||||
import EmptyWidget from '../EmptyWidget';
|
|
||||||
import { MenuItemKeys } from '../WidgetHeader/contants';
|
|
||||||
import { GridCardGraphProps } from './types';
|
|
||||||
import WidgetGraphComponent from './WidgetGraphComponent';
|
|
||||||
|
|
||||||
function GridCardGraph({
|
|
||||||
widget,
|
|
||||||
name,
|
|
||||||
yAxisUnit,
|
|
||||||
layout = [],
|
|
||||||
setLayout,
|
|
||||||
onDragSelect,
|
|
||||||
onClickHandler,
|
|
||||||
headerMenuList = [MenuItemKeys.View],
|
|
||||||
isQueryEnabled,
|
|
||||||
threshold,
|
|
||||||
}: GridCardGraphProps): JSX.Element {
|
|
||||||
const { isAddWidget } = useSelector<AppState, DashboardReducer>(
|
|
||||||
(state) => state.dashboards,
|
|
||||||
);
|
|
||||||
|
|
||||||
const { ref: graphRef, inView: isGraphVisible } = useInView({
|
|
||||||
threshold: 0,
|
|
||||||
triggerOnce: true,
|
|
||||||
initialInView: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
const [errorMessage, setErrorMessage] = useState<string | undefined>('');
|
|
||||||
|
|
||||||
const { minTime, maxTime, selectedTime: globalSelectedInterval } = useSelector<
|
|
||||||
AppState,
|
|
||||||
GlobalReducer
|
|
||||||
>((state) => state.globalTime);
|
|
||||||
const { dashboards } = useSelector<AppState, DashboardReducer>(
|
|
||||||
(state) => state.dashboards,
|
|
||||||
);
|
|
||||||
|
|
||||||
const variables = getSelectedDashboardVariable(dashboards);
|
|
||||||
|
|
||||||
const updatedQuery = useStepInterval(widget?.query);
|
|
||||||
|
|
||||||
const isEmptyWidget = useMemo(
|
|
||||||
() => widget?.id === 'empty' || isEmpty(widget),
|
|
||||||
[widget],
|
|
||||||
);
|
|
||||||
|
|
||||||
const queryResponse = useGetQueryRange(
|
|
||||||
{
|
|
||||||
selectedTime: widget?.timePreferance,
|
|
||||||
graphType: widget?.panelTypes,
|
|
||||||
query: updatedQuery,
|
|
||||||
globalSelectedInterval,
|
|
||||||
variables: getDashboardVariables(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
queryKey: [
|
|
||||||
`GetMetricsQueryRange-${widget?.timePreferance}-${globalSelectedInterval}-${widget?.id}`,
|
|
||||||
maxTime,
|
|
||||||
minTime,
|
|
||||||
globalSelectedInterval,
|
|
||||||
variables,
|
|
||||||
widget?.query,
|
|
||||||
widget?.panelTypes,
|
|
||||||
],
|
|
||||||
keepPreviousData: true,
|
|
||||||
enabled: isGraphVisible && !isEmptyWidget && isQueryEnabled && !isAddWidget,
|
|
||||||
refetchOnMount: false,
|
|
||||||
onError: (error) => {
|
|
||||||
setErrorMessage(error.message);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const chartData = useMemo(
|
|
||||||
() =>
|
|
||||||
getChartData({
|
|
||||||
queryData: [
|
|
||||||
{
|
|
||||||
queryData: queryResponse?.data?.payload?.data?.result || [],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
[queryResponse],
|
|
||||||
);
|
|
||||||
|
|
||||||
const prevChartDataSetRef = usePreviousValue<ChartData>(chartData);
|
|
||||||
|
|
||||||
const isEmptyLayout = widget?.id === 'empty' || isEmpty(widget);
|
|
||||||
|
|
||||||
if (queryResponse.isRefetching || queryResponse.isLoading) {
|
|
||||||
return <Spinner height="20vh" tip="Loading..." />;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((queryResponse.isError && !isEmptyLayout) || !isQueryEnabled) {
|
|
||||||
return (
|
|
||||||
<span ref={graphRef}>
|
|
||||||
{!isEmpty(widget) && prevChartDataSetRef && (
|
|
||||||
<WidgetGraphComponent
|
|
||||||
enableModel
|
|
||||||
enableWidgetHeader
|
|
||||||
widget={widget}
|
|
||||||
queryResponse={queryResponse}
|
|
||||||
errorMessage={errorMessage}
|
|
||||||
data={prevChartDataSetRef}
|
|
||||||
name={name}
|
|
||||||
yAxisUnit={yAxisUnit}
|
|
||||||
layout={layout}
|
|
||||||
setLayout={setLayout}
|
|
||||||
threshold={threshold}
|
|
||||||
headerMenuList={headerMenuList}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isEmpty(widget) && prevChartDataSetRef?.labels) {
|
|
||||||
return (
|
|
||||||
<span ref={graphRef}>
|
|
||||||
<WidgetGraphComponent
|
|
||||||
enableModel
|
|
||||||
enableWidgetHeader
|
|
||||||
widget={widget}
|
|
||||||
queryResponse={queryResponse}
|
|
||||||
errorMessage={errorMessage}
|
|
||||||
data={prevChartDataSetRef}
|
|
||||||
name={name}
|
|
||||||
yAxisUnit={yAxisUnit}
|
|
||||||
layout={layout}
|
|
||||||
setLayout={setLayout}
|
|
||||||
threshold={threshold}
|
|
||||||
headerMenuList={headerMenuList}
|
|
||||||
onClickHandler={onClickHandler}
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<span ref={graphRef}>
|
|
||||||
{!isEmpty(widget) && !!queryResponse.data?.payload && (
|
|
||||||
<WidgetGraphComponent
|
|
||||||
enableModel={!isEmptyLayout}
|
|
||||||
enableWidgetHeader={!isEmptyLayout}
|
|
||||||
widget={widget}
|
|
||||||
queryResponse={queryResponse}
|
|
||||||
errorMessage={errorMessage}
|
|
||||||
data={chartData}
|
|
||||||
name={name}
|
|
||||||
yAxisUnit={yAxisUnit}
|
|
||||||
onDragSelect={onDragSelect}
|
|
||||||
threshold={threshold}
|
|
||||||
headerMenuList={headerMenuList}
|
|
||||||
onClickHandler={onClickHandler}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{isEmptyLayout && <EmptyWidget />}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
GridCardGraph.defaultProps = {
|
|
||||||
onDragSelect: undefined,
|
|
||||||
onClickHandler: undefined,
|
|
||||||
isQueryEnabled: true,
|
|
||||||
threshold: undefined,
|
|
||||||
headerMenuList: [MenuItemKeys.View],
|
|
||||||
};
|
|
||||||
|
|
||||||
export default memo(GridCardGraph);
|
|
@ -1,113 +0,0 @@
|
|||||||
import { PlusOutlined, SaveFilled } from '@ant-design/icons';
|
|
||||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
|
||||||
import useComponentPermission from 'hooks/useComponentPermission';
|
|
||||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
|
||||||
import { Dispatch, SetStateAction } 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 DashboardReducer from 'types/reducer/dashboards';
|
|
||||||
|
|
||||||
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 { isAddWidget } = useSelector<AppState, DashboardReducer>(
|
|
||||||
(state) => state.dashboards,
|
|
||||||
);
|
|
||||||
const { role } = useSelector<AppState, AppReducer>((state) => state.app);
|
|
||||||
const isDarkMode = useIsDarkMode();
|
|
||||||
|
|
||||||
const [saveLayoutPermission, addPanelPermission] = useComponentPermission(
|
|
||||||
['save_layout', 'add_panel'],
|
|
||||||
role,
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<ButtonContainer>
|
|
||||||
{saveLayoutPermission && (
|
|
||||||
<Button
|
|
||||||
loading={saveLayoutState.loading}
|
|
||||||
onClick={(): Promise<void> => onLayoutSaveHandler(layouts)}
|
|
||||||
icon={<SaveFilled />}
|
|
||||||
danger={saveLayoutState.error}
|
|
||||||
>
|
|
||||||
Save Layout
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{addPanelPermission && (
|
|
||||||
<Button
|
|
||||||
loading={addPanelLoading}
|
|
||||||
disabled={addPanelLoading || isAddWidget}
|
|
||||||
onClick={onAddPanelHandler}
|
|
||||||
icon={<PlusOutlined />}
|
|
||||||
>
|
|
||||||
Add Panel
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</ButtonContainer>
|
|
||||||
|
|
||||||
<ReactGridLayout
|
|
||||||
cols={12}
|
|
||||||
rowHeight={100}
|
|
||||||
autoSize
|
|
||||||
width={100}
|
|
||||||
isDraggable={addPanelPermission}
|
|
||||||
isDroppable={addPanelPermission}
|
|
||||||
isResizable={addPanelPermission}
|
|
||||||
useCSSTransforms
|
|
||||||
allowOverlap={false}
|
|
||||||
onLayoutChange={onLayoutChangeHandler}
|
|
||||||
draggableHandle=".drag-handle"
|
|
||||||
>
|
|
||||||
{layouts.map(({ Component, ...rest }) => {
|
|
||||||
const currentWidget = (widgets || [])?.find((e) => e.id === rest.i);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<CardContainer
|
|
||||||
isDarkMode={isDarkMode}
|
|
||||||
key={currentWidget?.id || 'empty'} // don't change this key
|
|
||||||
data-grid={rest}
|
|
||||||
>
|
|
||||||
<Card $panelType={currentWidget?.panelTypes || PANEL_TYPES.TIME_SERIES}>
|
|
||||||
<Component setLayout={setLayout} />
|
|
||||||
</Card>
|
|
||||||
</CardContainer>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</ReactGridLayout>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
interface GraphLayoutProps {
|
|
||||||
layouts: LayoutProps[];
|
|
||||||
saveLayoutState: State;
|
|
||||||
onLayoutSaveHandler: (layout: Layout[]) => Promise<void>;
|
|
||||||
addPanelLoading: boolean;
|
|
||||||
onAddPanelHandler: VoidFunction;
|
|
||||||
onLayoutChangeHandler: (layout: Layout[]) => Promise<void>;
|
|
||||||
widgets: Widgets[] | undefined;
|
|
||||||
setLayout: Dispatch<SetStateAction<LayoutProps[]>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default GraphLayout;
|
|
@ -1,8 +0,0 @@
|
|||||||
import { MenuItemKeys } from 'container/GridGraphLayout/WidgetHeader/contants';
|
|
||||||
|
|
||||||
export const headerMenuList = [
|
|
||||||
MenuItemKeys.View,
|
|
||||||
MenuItemKeys.Clone,
|
|
||||||
MenuItemKeys.Delete,
|
|
||||||
MenuItemKeys.Edit,
|
|
||||||
];
|
|
@ -1,383 +0,0 @@
|
|||||||
/* eslint-disable react/no-unstable-nested-components */
|
|
||||||
|
|
||||||
import updateDashboardApi from 'api/dashboard/update';
|
|
||||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
|
||||||
import useComponentPermission from 'hooks/useComponentPermission';
|
|
||||||
import { useNotifications } from 'hooks/useNotifications';
|
|
||||||
import {
|
|
||||||
Dispatch,
|
|
||||||
SetStateAction,
|
|
||||||
useCallback,
|
|
||||||
useEffect,
|
|
||||||
useState,
|
|
||||||
} from 'react';
|
|
||||||
import { Layout } from 'react-grid-layout';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { connect, useDispatch, useSelector } from 'react-redux';
|
|
||||||
import { bindActionCreators, Dispatch as ReduxDispatch } from 'redux';
|
|
||||||
import { ThunkDispatch } from 'redux-thunk';
|
|
||||||
import { AppDispatch } from 'store';
|
|
||||||
import { UpdateTimeInterval } from 'store/actions';
|
|
||||||
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 { headerMenuList } from './config';
|
|
||||||
import Graph from './Graph';
|
|
||||||
import GraphLayoutContainer from './GraphLayout';
|
|
||||||
import { UpdateDashboard } from './utils';
|
|
||||||
|
|
||||||
export const getPreLayouts = (
|
|
||||||
widgets: Widgets[] | undefined,
|
|
||||||
layout: Layout[],
|
|
||||||
): LayoutProps[] =>
|
|
||||||
layout.map((e, index) => ({
|
|
||||||
...e,
|
|
||||||
Component: ({ setLayout }: ComponentProps): JSX.Element => {
|
|
||||||
const widget = widgets?.find((widget) => widget.id === e.i);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Graph
|
|
||||||
name={e.i + index}
|
|
||||||
widget={widget as Widgets}
|
|
||||||
yAxisUnit={widget?.yAxisUnit}
|
|
||||||
layout={layout}
|
|
||||||
setLayout={setLayout}
|
|
||||||
headerMenuList={headerMenuList}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
function GridGraph(props: Props): JSX.Element {
|
|
||||||
const { toggleAddWidget } = props;
|
|
||||||
const [addPanelLoading, setAddPanelLoading] = useState(false);
|
|
||||||
const { t } = useTranslation(['common']);
|
|
||||||
const { dashboards, isAddWidget } = useSelector<AppState, DashboardReducer>(
|
|
||||||
(state) => state.dashboards,
|
|
||||||
);
|
|
||||||
const { role } = useSelector<AppState, AppReducer>((state) => state.app);
|
|
||||||
|
|
||||||
const [saveLayoutPermission] = useComponentPermission(['save_layout'], role);
|
|
||||||
const [saveLayoutState, setSaveLayoutState] = useState<State>({
|
|
||||||
loading: false,
|
|
||||||
error: false,
|
|
||||||
errorMessage: '',
|
|
||||||
payload: [],
|
|
||||||
});
|
|
||||||
const [selectedDashboard] = dashboards;
|
|
||||||
const { data } = selectedDashboard;
|
|
||||||
const { widgets } = data;
|
|
||||||
const dispatch: AppDispatch = useDispatch<ReduxDispatch<AppActions>>();
|
|
||||||
|
|
||||||
const [layouts, setLayout] = useState<LayoutProps[]>(
|
|
||||||
getPreLayouts(widgets, selectedDashboard.data.layout || []),
|
|
||||||
);
|
|
||||||
|
|
||||||
const onDragSelect = useCallback(
|
|
||||||
(start: number, end: number) => {
|
|
||||||
const startTimestamp = Math.trunc(start);
|
|
||||||
const endTimestamp = Math.trunc(end);
|
|
||||||
|
|
||||||
if (startTimestamp !== endTimestamp) {
|
|
||||||
dispatch(UpdateTimeInterval('custom', [startTimestamp, endTimestamp]));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[dispatch],
|
|
||||||
);
|
|
||||||
|
|
||||||
const { notifications } = useNotifications();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
(async (): Promise<void> => {
|
|
||||||
if (!isAddWidget) {
|
|
||||||
const isEmptyLayoutPresent = layouts.find((e) => e.i === 'empty');
|
|
||||||
if (isEmptyLayoutPresent) {
|
|
||||||
// non empty layout
|
|
||||||
const updatedLayout = layouts.filter((e) => e.i !== 'empty');
|
|
||||||
// non widget
|
|
||||||
const updatedWidget = widgets?.filter((e) => e.id !== 'empty');
|
|
||||||
setLayout(updatedLayout);
|
|
||||||
|
|
||||||
const updatedDashboard: Dashboard = {
|
|
||||||
...selectedDashboard,
|
|
||||||
data: {
|
|
||||||
...selectedDashboard.data,
|
|
||||||
layout: updatedLayout,
|
|
||||||
widgets: updatedWidget,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
await updateDashboardApi({
|
|
||||||
data: updatedDashboard.data,
|
|
||||||
uuid: updatedDashboard.uuid,
|
|
||||||
});
|
|
||||||
|
|
||||||
dispatch({
|
|
||||||
type: UPDATE_DASHBOARD,
|
|
||||||
payload: updatedDashboard,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
}, [dispatch, isAddWidget, layouts, selectedDashboard, widgets]);
|
|
||||||
|
|
||||||
const { featureResponse } = useSelector<AppState, AppReducer>(
|
|
||||||
(state) => state.app,
|
|
||||||
);
|
|
||||||
|
|
||||||
const errorMessage = t('common:something_went_wrong');
|
|
||||||
|
|
||||||
const onLayoutSaveHandler = useCallback(
|
|
||||||
async (layout: Layout[]) => {
|
|
||||||
try {
|
|
||||||
setSaveLayoutState((state) => ({
|
|
||||||
...state,
|
|
||||||
error: false,
|
|
||||||
errorMessage: '',
|
|
||||||
loading: true,
|
|
||||||
}));
|
|
||||||
|
|
||||||
featureResponse
|
|
||||||
.refetch()
|
|
||||||
.then(async () => {
|
|
||||||
const updatedDashboard: Dashboard = {
|
|
||||||
...selectedDashboard,
|
|
||||||
data: {
|
|
||||||
title: data.title,
|
|
||||||
description: data.description,
|
|
||||||
name: data.name,
|
|
||||||
tags: data.tags,
|
|
||||||
widgets: data.widgets,
|
|
||||||
variables: data.variables,
|
|
||||||
layout,
|
|
||||||
},
|
|
||||||
uuid: selectedDashboard.uuid,
|
|
||||||
};
|
|
||||||
// Save layout only when users has the has the permission to do so.
|
|
||||||
if (saveLayoutPermission) {
|
|
||||||
const response = await updateDashboardApi(updatedDashboard);
|
|
||||||
if (response.statusCode === 200) {
|
|
||||||
setSaveLayoutState((state) => ({
|
|
||||||
...state,
|
|
||||||
error: false,
|
|
||||||
errorMessage: '',
|
|
||||||
loading: false,
|
|
||||||
}));
|
|
||||||
dispatch({
|
|
||||||
type: UPDATE_DASHBOARD,
|
|
||||||
payload: updatedDashboard,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
setSaveLayoutState((state) => ({
|
|
||||||
...state,
|
|
||||||
error: true,
|
|
||||||
errorMessage: response.error || errorMessage,
|
|
||||||
loading: false,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
setSaveLayoutState((state) => ({
|
|
||||||
...state,
|
|
||||||
error: true,
|
|
||||||
errorMessage,
|
|
||||||
loading: false,
|
|
||||||
}));
|
|
||||||
notifications.error({
|
|
||||||
message: errorMessage,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
notifications.error({
|
|
||||||
message: errorMessage,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[
|
|
||||||
data.description,
|
|
||||||
data.name,
|
|
||||||
data.tags,
|
|
||||||
data.title,
|
|
||||||
data.variables,
|
|
||||||
data.widgets,
|
|
||||||
dispatch,
|
|
||||||
errorMessage,
|
|
||||||
featureResponse,
|
|
||||||
notifications,
|
|
||||||
saveLayoutPermission,
|
|
||||||
selectedDashboard,
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
const setLayoutFunction = useCallback(
|
|
||||||
(layout: Layout[]) => {
|
|
||||||
setLayout(
|
|
||||||
layout.map((e) => {
|
|
||||||
const currentWidget =
|
|
||||||
widgets?.find((widget) => widget.id === e.i) || ({} as Widgets);
|
|
||||||
|
|
||||||
return {
|
|
||||||
...e,
|
|
||||||
Component: (): JSX.Element => (
|
|
||||||
<Graph
|
|
||||||
name={currentWidget.id}
|
|
||||||
widget={currentWidget}
|
|
||||||
yAxisUnit={currentWidget?.yAxisUnit}
|
|
||||||
layout={layout}
|
|
||||||
setLayout={setLayout}
|
|
||||||
onDragSelect={onDragSelect}
|
|
||||||
headerMenuList={headerMenuList}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
[widgets, onDragSelect],
|
|
||||||
);
|
|
||||||
|
|
||||||
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: PANEL_TYPES.EMPTY_WIDGET,
|
|
||||||
selectedDashboard,
|
|
||||||
layout,
|
|
||||||
isRedirected: false,
|
|
||||||
},
|
|
||||||
notifications,
|
|
||||||
);
|
|
||||||
|
|
||||||
setLayoutFunction(layout);
|
|
||||||
} catch (error) {
|
|
||||||
notifications.error({
|
|
||||||
message: error instanceof Error ? error.toString() : errorMessage,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [data, selectedDashboard, setLayoutFunction, notifications, errorMessage]);
|
|
||||||
|
|
||||||
const onLayoutChangeHandler = async (layout: Layout[]): Promise<void> => {
|
|
||||||
setLayoutFunction(layout);
|
|
||||||
|
|
||||||
// await onLayoutSaveHandler(layout);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onAddPanelHandler = useCallback(() => {
|
|
||||||
try {
|
|
||||||
setAddPanelLoading(true);
|
|
||||||
featureResponse
|
|
||||||
.refetch()
|
|
||||||
.then(() => {
|
|
||||||
const isEmptyLayoutPresent =
|
|
||||||
layouts.find((e) => e.i === 'empty') !== undefined;
|
|
||||||
|
|
||||||
if (!isEmptyLayoutPresent) {
|
|
||||||
onEmptyWidgetHandler()
|
|
||||||
.then(() => {
|
|
||||||
setAddPanelLoading(false);
|
|
||||||
toggleAddWidget(true);
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
notifications.error({
|
|
||||||
message: errorMessage,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
toggleAddWidget(true);
|
|
||||||
setAddPanelLoading(false);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(() =>
|
|
||||||
notifications.error({
|
|
||||||
message: errorMessage,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
} catch (error) {
|
|
||||||
notifications.error({
|
|
||||||
message: errorMessage,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [
|
|
||||||
featureResponse,
|
|
||||||
layouts,
|
|
||||||
onEmptyWidgetHandler,
|
|
||||||
toggleAddWidget,
|
|
||||||
notifications,
|
|
||||||
errorMessage,
|
|
||||||
]);
|
|
||||||
|
|
||||||
useEffect(
|
|
||||||
() => (): void => {
|
|
||||||
toggleAddWidget(false);
|
|
||||||
},
|
|
||||||
[toggleAddWidget],
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<GraphLayoutContainer
|
|
||||||
addPanelLoading={addPanelLoading}
|
|
||||||
layouts={layouts}
|
|
||||||
onAddPanelHandler={onAddPanelHandler}
|
|
||||||
onLayoutChangeHandler={onLayoutChangeHandler}
|
|
||||||
onLayoutSaveHandler={onLayoutSaveHandler}
|
|
||||||
saveLayoutState={saveLayoutState}
|
|
||||||
setLayout={setLayout}
|
|
||||||
widgets={widgets}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ComponentProps {
|
|
||||||
setLayout: Dispatch<SetStateAction<LayoutProps[]>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface LayoutProps extends Layout {
|
|
||||||
Component: (props: ComponentProps) => JSX.Element;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface State {
|
|
||||||
loading: boolean;
|
|
||||||
error: boolean;
|
|
||||||
payload: Layout[];
|
|
||||||
errorMessage: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface DispatchProps {
|
|
||||||
toggleAddWidget: (
|
|
||||||
props: ToggleAddWidgetProps,
|
|
||||||
) => (dispatch: ReduxDispatch<AppActions>) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const mapDispatchToProps = (
|
|
||||||
dispatch: ThunkDispatch<unknown, unknown, AppActions>,
|
|
||||||
): DispatchProps => ({
|
|
||||||
toggleAddWidget: bindActionCreators(ToggleAddWidget, dispatch),
|
|
||||||
});
|
|
||||||
|
|
||||||
type Props = DispatchProps;
|
|
||||||
|
|
||||||
export default connect(null, mapDispatchToProps)(GridGraph);
|
|
@ -1,78 +0,0 @@
|
|||||||
import { NotificationInstance } from 'antd/es/notification/interface';
|
|
||||||
import updateDashboardApi from 'api/dashboard/update';
|
|
||||||
import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder';
|
|
||||||
import { Layout } from 'react-grid-layout';
|
|
||||||
import store from 'store';
|
|
||||||
import { Dashboard, Widgets } from 'types/api/dashboard/getAll';
|
|
||||||
|
|
||||||
export const UpdateDashboard = async (
|
|
||||||
{
|
|
||||||
data,
|
|
||||||
graphType,
|
|
||||||
generateWidgetId,
|
|
||||||
layout,
|
|
||||||
selectedDashboard,
|
|
||||||
isRedirected,
|
|
||||||
widgetData,
|
|
||||||
}: UpdateDashboardProps,
|
|
||||||
notify: NotificationInstance,
|
|
||||||
): Promise<Dashboard | undefined> => {
|
|
||||||
const copyTitle = `${widgetData?.title} - Copy`;
|
|
||||||
const updatedSelectedDashboard: Dashboard = {
|
|
||||||
...selectedDashboard,
|
|
||||||
data: {
|
|
||||||
title: data.title,
|
|
||||||
description: data.description,
|
|
||||||
name: data.name,
|
|
||||||
tags: data.tags,
|
|
||||||
variables: data.variables,
|
|
||||||
widgets: [
|
|
||||||
...(data.widgets || []),
|
|
||||||
{
|
|
||||||
description: widgetData?.description || '',
|
|
||||||
id: generateWidgetId,
|
|
||||||
isStacked: false,
|
|
||||||
nullZeroValues: widgetData?.nullZeroValues || '',
|
|
||||||
opacity: '',
|
|
||||||
panelTypes: graphType,
|
|
||||||
query: widgetData?.query || initialQueriesMap.metrics,
|
|
||||||
timePreferance: widgetData?.timePreferance || 'GLOBAL_TIME',
|
|
||||||
title: widgetData ? copyTitle : '',
|
|
||||||
yAxisUnit: widgetData?.yAxisUnit,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
layout,
|
|
||||||
},
|
|
||||||
uuid: selectedDashboard.uuid,
|
|
||||||
};
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
notify.error({
|
|
||||||
message: response.error || 'Something went wrong',
|
|
||||||
});
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
interface UpdateDashboardProps {
|
|
||||||
data: Dashboard['data'];
|
|
||||||
graphType: PANEL_TYPES;
|
|
||||||
generateWidgetId: string;
|
|
||||||
layout: Layout[];
|
|
||||||
selectedDashboard: Dashboard;
|
|
||||||
isRedirected: boolean;
|
|
||||||
widgetData?: Widgets;
|
|
||||||
}
|
|
@ -0,0 +1,3 @@
|
|||||||
|
.ingestion-settings-container {
|
||||||
|
color: white;
|
||||||
|
}
|
@ -0,0 +1,82 @@
|
|||||||
|
import './IngestionSettings.styles.scss';
|
||||||
|
|
||||||
|
import { Table, Typography } from 'antd';
|
||||||
|
import type { ColumnsType } from 'antd/es/table';
|
||||||
|
import getIngestionData from 'api/settings/getIngestionData';
|
||||||
|
import { useQuery } from 'react-query';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
|
import { AppState } from 'store/reducers';
|
||||||
|
import { IngestionDataType } from 'types/api/settings/ingestion';
|
||||||
|
import AppReducer from 'types/reducer/app';
|
||||||
|
|
||||||
|
export default function IngestionSettings(): JSX.Element {
|
||||||
|
const { user } = useSelector<AppState, AppReducer>((state) => state.app);
|
||||||
|
|
||||||
|
const { data: ingestionData } = useQuery({
|
||||||
|
queryFn: getIngestionData,
|
||||||
|
queryKey: ['getIngestionData', user?.userId],
|
||||||
|
});
|
||||||
|
|
||||||
|
const columns: ColumnsType<IngestionDataType> = [
|
||||||
|
{
|
||||||
|
title: 'Name',
|
||||||
|
dataIndex: 'name',
|
||||||
|
key: 'name',
|
||||||
|
render: (text): JSX.Element => <Typography.Text> {text} </Typography.Text>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Value',
|
||||||
|
dataIndex: 'value',
|
||||||
|
key: 'value',
|
||||||
|
render: (text): JSX.Element => (
|
||||||
|
<Typography.Text copyable>{text}</Typography.Text>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const injectionDataPayload =
|
||||||
|
ingestionData &&
|
||||||
|
ingestionData.payload &&
|
||||||
|
Array.isArray(ingestionData.payload) &&
|
||||||
|
ingestionData?.payload[0];
|
||||||
|
|
||||||
|
const data: IngestionDataType[] = [
|
||||||
|
{
|
||||||
|
key: '1',
|
||||||
|
name: 'Ingestion URL',
|
||||||
|
value: injectionDataPayload?.ingestionURL,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '2',
|
||||||
|
name: 'Ingestion Key',
|
||||||
|
value: injectionDataPayload?.ingestionKey,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '3',
|
||||||
|
name: 'Ingestion Region',
|
||||||
|
value: injectionDataPayload?.dataRegion,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="ingestion-settings-container">
|
||||||
|
<Typography
|
||||||
|
style={{
|
||||||
|
margin: '16px 0px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
You can use the following ingestion credentials to start sending your
|
||||||
|
telemetry data to SigNoz
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Table
|
||||||
|
style={{
|
||||||
|
margin: '16px 0px',
|
||||||
|
}}
|
||||||
|
pagination={false}
|
||||||
|
columns={columns}
|
||||||
|
dataSource={data}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -9,11 +9,7 @@ import { useNotifications } from 'hooks/useNotifications';
|
|||||||
import history from 'lib/history';
|
import history from 'lib/history';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useDispatch } from 'react-redux';
|
|
||||||
import { generatePath } from 'react-router-dom';
|
import { generatePath } from 'react-router-dom';
|
||||||
import { Dispatch } from 'redux';
|
|
||||||
import AppActions from 'types/actions';
|
|
||||||
import { FLUSH_DASHBOARD } from 'types/actions/dashboard';
|
|
||||||
import { DashboardData } from 'types/api/dashboard/getAll';
|
import { DashboardData } from 'types/api/dashboard/getAll';
|
||||||
|
|
||||||
import { EditorContainer, FooterContainer } from './styles';
|
import { EditorContainer, FooterContainer } from './styles';
|
||||||
@ -31,8 +27,6 @@ function ImportJSON({
|
|||||||
);
|
);
|
||||||
const [isFeatureAlert, setIsFeatureAlert] = useState<boolean>(false);
|
const [isFeatureAlert, setIsFeatureAlert] = useState<boolean>(false);
|
||||||
|
|
||||||
const dispatch = useDispatch<Dispatch<AppActions>>();
|
|
||||||
|
|
||||||
const [dashboardCreating, setDashboardCreating] = useState<boolean>(false);
|
const [dashboardCreating, setDashboardCreating] = useState<boolean>(false);
|
||||||
|
|
||||||
const [editorValue, setEditorValue] = useState<string>('');
|
const [editorValue, setEditorValue] = useState<string>('');
|
||||||
@ -77,16 +71,11 @@ function ImportJSON({
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (response.statusCode === 200) {
|
if (response.statusCode === 200) {
|
||||||
dispatch({
|
history.push(
|
||||||
type: FLUSH_DASHBOARD,
|
generatePath(ROUTES.DASHBOARD, {
|
||||||
});
|
dashboardId: response.payload.uuid,
|
||||||
setTimeout(() => {
|
}),
|
||||||
history.push(
|
);
|
||||||
generatePath(ROUTES.DASHBOARD, {
|
|
||||||
dashboardId: response.payload.uuid,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}, 10);
|
|
||||||
} else if (response.error === 'feature usage exceeded') {
|
} else if (response.error === 'feature usage exceeded') {
|
||||||
setIsFeatureAlert(true);
|
setIsFeatureAlert(true);
|
||||||
notifications.error({
|
notifications.error({
|
||||||
|
@ -1,37 +1,36 @@
|
|||||||
import { ExclamationCircleOutlined } from '@ant-design/icons';
|
import { ExclamationCircleOutlined } from '@ant-design/icons';
|
||||||
import { Modal } from 'antd';
|
import { Modal } from 'antd';
|
||||||
|
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
||||||
|
import { useDeleteDashboard } from 'hooks/dashboard/useDeleteDashboard';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { useQueryClient } from 'react-query';
|
||||||
import { bindActionCreators, Dispatch } from 'redux';
|
|
||||||
import { ThunkDispatch } from 'redux-thunk';
|
|
||||||
import { DeleteDashboard, DeleteDashboardProps } from 'store/actions';
|
|
||||||
import AppActions from 'types/actions';
|
|
||||||
|
|
||||||
import { Data } from '../index';
|
import { Data } from '../index';
|
||||||
import { TableLinkText } from './styles';
|
import { TableLinkText } from './styles';
|
||||||
|
|
||||||
function DeleteButton({
|
function DeleteButton({ id }: Data): JSX.Element {
|
||||||
deleteDashboard,
|
|
||||||
id,
|
|
||||||
refetchDashboardList,
|
|
||||||
}: DeleteButtonProps): JSX.Element {
|
|
||||||
const [modal, contextHolder] = Modal.useModal();
|
const [modal, contextHolder] = Modal.useModal();
|
||||||
|
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
const deleteDashboardMutation = useDeleteDashboard(id);
|
||||||
|
|
||||||
const openConfirmationDialog = useCallback((): void => {
|
const openConfirmationDialog = useCallback((): void => {
|
||||||
modal.confirm({
|
modal.confirm({
|
||||||
title: 'Do you really want to delete this dashboard?',
|
title: 'Do you really want to delete this dashboard?',
|
||||||
icon: <ExclamationCircleOutlined style={{ color: '#e42b35' }} />,
|
icon: <ExclamationCircleOutlined style={{ color: '#e42b35' }} />,
|
||||||
onOk() {
|
onOk() {
|
||||||
deleteDashboard({
|
deleteDashboardMutation.mutateAsync(undefined, {
|
||||||
uuid: id,
|
onSuccess: () => {
|
||||||
refetch: refetchDashboardList,
|
queryClient.invalidateQueries([REACT_QUERY_KEY.GET_ALL_DASHBOARDS]);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
okText: 'Delete',
|
okText: 'Delete',
|
||||||
okButtonProps: { danger: true },
|
okButtonProps: { danger: true },
|
||||||
centered: true,
|
centered: true,
|
||||||
});
|
});
|
||||||
}, [modal, deleteDashboard, id, refetchDashboardList]);
|
}, [modal, deleteDashboardMutation, queryClient]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -44,37 +43,12 @@ function DeleteButton({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DispatchProps {
|
|
||||||
deleteDashboard: ({
|
|
||||||
uuid,
|
|
||||||
}: DeleteDashboardProps) => (dispatch: Dispatch<AppActions>) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const mapDispatchToProps = (
|
|
||||||
dispatch: ThunkDispatch<unknown, unknown, AppActions>,
|
|
||||||
): DispatchProps => ({
|
|
||||||
deleteDashboard: bindActionCreators(DeleteDashboard, dispatch),
|
|
||||||
});
|
|
||||||
|
|
||||||
export type DeleteButtonProps = Data & DispatchProps;
|
|
||||||
|
|
||||||
const WrapperDeleteButton = connect(null, mapDispatchToProps)(DeleteButton);
|
|
||||||
|
|
||||||
// This is to avoid the type collision
|
// This is to avoid the type collision
|
||||||
function Wrapper(props: Data): JSX.Element {
|
function Wrapper(props: Data): JSX.Element {
|
||||||
const {
|
const { createdBy, description, id, key, lastUpdatedTime, name, tags } = props;
|
||||||
createdBy,
|
|
||||||
description,
|
|
||||||
id,
|
|
||||||
key,
|
|
||||||
refetchDashboardList,
|
|
||||||
lastUpdatedTime,
|
|
||||||
name,
|
|
||||||
tags,
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<WrapperDeleteButton
|
<DeleteButton
|
||||||
{...{
|
{...{
|
||||||
createdBy,
|
createdBy,
|
||||||
description,
|
description,
|
||||||
@ -83,7 +57,6 @@ function Wrapper(props: Data): JSX.Element {
|
|||||||
lastUpdatedTime,
|
lastUpdatedTime,
|
||||||
name,
|
name,
|
||||||
tags,
|
tags,
|
||||||
refetchDashboardList,
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -17,21 +17,11 @@ import SearchFilter from 'container/ListOfDashboard/SearchFilter';
|
|||||||
import { useGetAllDashboard } from 'hooks/dashboard/useGetAllDashboard';
|
import { useGetAllDashboard } from 'hooks/dashboard/useGetAllDashboard';
|
||||||
import useComponentPermission from 'hooks/useComponentPermission';
|
import useComponentPermission from 'hooks/useComponentPermission';
|
||||||
import history from 'lib/history';
|
import history from 'lib/history';
|
||||||
import {
|
import { Key, useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
Dispatch,
|
|
||||||
Key,
|
|
||||||
useCallback,
|
|
||||||
useEffect,
|
|
||||||
useMemo,
|
|
||||||
useState,
|
|
||||||
} from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { UseQueryResult } from 'react-query';
|
import { useSelector } from 'react-redux';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
|
||||||
import { generatePath } from 'react-router-dom';
|
import { generatePath } from 'react-router-dom';
|
||||||
import { AppState } from 'store/reducers';
|
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 { Dashboard } from 'types/api/dashboard/getAll';
|
||||||
import AppReducer from 'types/reducer/app';
|
import AppReducer from 'types/reducer/app';
|
||||||
import { popupContainer } from 'utils/selectPopupContainer';
|
import { popupContainer } from 'utils/selectPopupContainer';
|
||||||
@ -40,9 +30,7 @@ import ImportJSON from './ImportJSON';
|
|||||||
import { ButtonContainer, NewDashboardButton, TableContainer } from './styles';
|
import { ButtonContainer, NewDashboardButton, TableContainer } from './styles';
|
||||||
import Createdby from './TableComponents/CreatedBy';
|
import Createdby from './TableComponents/CreatedBy';
|
||||||
import DateComponent from './TableComponents/Date';
|
import DateComponent from './TableComponents/Date';
|
||||||
import DeleteButton, {
|
import DeleteButton from './TableComponents/DeleteButton';
|
||||||
DeleteButtonProps,
|
|
||||||
} from './TableComponents/DeleteButton';
|
|
||||||
import Name from './TableComponents/Name';
|
import Name from './TableComponents/Name';
|
||||||
import Tags from './TableComponents/Tags';
|
import Tags from './TableComponents/Tags';
|
||||||
|
|
||||||
@ -53,7 +41,6 @@ function ListOfAllDashboard(): JSX.Element {
|
|||||||
refetch: refetchDashboardList,
|
refetch: refetchDashboardList,
|
||||||
} = useGetAllDashboard();
|
} = useGetAllDashboard();
|
||||||
|
|
||||||
const dispatch = useDispatch<Dispatch<AppActions>>();
|
|
||||||
const { role } = useSelector<AppState, AppReducer>((state) => state.app);
|
const { role } = useSelector<AppState, AppReducer>((state) => state.app);
|
||||||
|
|
||||||
const [action, createNewDashboard, newDashboard] = useComponentPermission(
|
const [action, createNewDashboard, newDashboard] = useComponentPermission(
|
||||||
@ -134,31 +121,12 @@ function ListOfAllDashboard(): JSX.Element {
|
|||||||
title: 'Action',
|
title: 'Action',
|
||||||
dataIndex: '',
|
dataIndex: '',
|
||||||
width: 40,
|
width: 40,
|
||||||
render: ({
|
render: DeleteButton,
|
||||||
createdBy,
|
|
||||||
description,
|
|
||||||
id,
|
|
||||||
key,
|
|
||||||
lastUpdatedTime,
|
|
||||||
name,
|
|
||||||
tags,
|
|
||||||
}: DeleteButtonProps) => (
|
|
||||||
<DeleteButton
|
|
||||||
description={description}
|
|
||||||
id={id}
|
|
||||||
key={key}
|
|
||||||
lastUpdatedTime={lastUpdatedTime}
|
|
||||||
name={name}
|
|
||||||
tags={tags}
|
|
||||||
createdBy={createdBy}
|
|
||||||
refetchDashboardList={refetchDashboardList}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return tableColumns;
|
return tableColumns;
|
||||||
}, [action, refetchDashboardList]);
|
}, [action]);
|
||||||
|
|
||||||
const data: Data[] =
|
const data: Data[] =
|
||||||
filteredDashboards?.map((e) => ({
|
filteredDashboards?.map((e) => ({
|
||||||
@ -186,10 +154,6 @@ function ListOfAllDashboard(): JSX.Element {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (response.statusCode === 200) {
|
if (response.statusCode === 200) {
|
||||||
dispatch({
|
|
||||||
type: GET_ALL_DASHBOARD_SUCCESS,
|
|
||||||
payload: [],
|
|
||||||
});
|
|
||||||
history.push(
|
history.push(
|
||||||
generatePath(ROUTES.DASHBOARD, {
|
generatePath(ROUTES.DASHBOARD, {
|
||||||
dashboardId: response.payload.uuid,
|
dashboardId: response.payload.uuid,
|
||||||
@ -210,7 +174,7 @@ function ListOfAllDashboard(): JSX.Element {
|
|||||||
errorMessage: (error as AxiosError).toString() || 'Something went Wrong',
|
errorMessage: (error as AxiosError).toString() || 'Something went Wrong',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [newDashboardState, t, dispatch]);
|
}, [newDashboardState, t]);
|
||||||
|
|
||||||
const getText = useCallback(() => {
|
const getText = useCallback(() => {
|
||||||
if (!newDashboardState.error && !newDashboardState.loading) {
|
if (!newDashboardState.error && !newDashboardState.loading) {
|
||||||
@ -352,7 +316,6 @@ export interface Data {
|
|||||||
createdBy: string;
|
createdBy: string;
|
||||||
lastUpdatedTime: string;
|
lastUpdatedTime: string;
|
||||||
id: string;
|
id: string;
|
||||||
refetchDashboardList: UseQueryResult['refetch'];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ListOfAllDashboard;
|
export default ListOfAllDashboard;
|
||||||
|
@ -11,11 +11,11 @@ import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
|||||||
import useDebouncedFn from 'hooks/useDebouncedFunction';
|
import useDebouncedFn from 'hooks/useDebouncedFunction';
|
||||||
import { useEventSourceEvent } from 'hooks/useEventSourceEvent';
|
import { useEventSourceEvent } from 'hooks/useEventSourceEvent';
|
||||||
import { useNotifications } from 'hooks/useNotifications';
|
import { useNotifications } from 'hooks/useNotifications';
|
||||||
|
import { prepareQueryRangePayload } from 'lib/dashboard/prepareQueryRangePayload';
|
||||||
import { useEventSource } from 'providers/EventSource';
|
import { useEventSource } from 'providers/EventSource';
|
||||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { useLocation } from 'react-router-dom';
|
import { useLocation } from 'react-router-dom';
|
||||||
import { prepareQueryRangePayload } from 'store/actions/dashboard/prepareQueryRangePayload';
|
|
||||||
import { AppState } from 'store/reducers';
|
import { AppState } from 'store/reducers';
|
||||||
import { ILog } from 'types/api/logs/log';
|
import { ILog } from 'types/api/logs/log';
|
||||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||||
|
@ -21,7 +21,12 @@ import { ILog } from 'types/api/logs/log';
|
|||||||
|
|
||||||
import ActionItem, { ActionItemProps } from './ActionItem';
|
import ActionItem, { ActionItemProps } from './ActionItem';
|
||||||
import FieldRenderer from './FieldRenderer';
|
import FieldRenderer from './FieldRenderer';
|
||||||
import { flattenObject, jsonToDataNodes, recursiveParseJSON } from './utils';
|
import {
|
||||||
|
flattenObject,
|
||||||
|
jsonToDataNodes,
|
||||||
|
recursiveParseJSON,
|
||||||
|
removeEscapeCharacters,
|
||||||
|
} from './utils';
|
||||||
|
|
||||||
// Fields which should be restricted from adding it to query
|
// Fields which should be restricted from adding it to query
|
||||||
const RESTRICTED_FIELDS = ['timestamp'];
|
const RESTRICTED_FIELDS = ['timestamp'];
|
||||||
@ -58,7 +63,7 @@ function TableView({
|
|||||||
.map((key) => ({
|
.map((key) => ({
|
||||||
key,
|
key,
|
||||||
field: key,
|
field: key,
|
||||||
value: JSON.stringify(flattenLogData[key]),
|
value: removeEscapeCharacters(JSON.stringify(flattenLogData[key])),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const onTraceHandler = (record: DataType) => (): void => {
|
const onTraceHandler = (record: DataType) => (): void => {
|
||||||
@ -164,6 +169,8 @@ function TableView({
|
|||||||
width: 70,
|
width: 70,
|
||||||
ellipsis: false,
|
ellipsis: false,
|
||||||
render: (field, record): JSX.Element => {
|
render: (field, record): JSX.Element => {
|
||||||
|
const textToCopy = field.slice(1, -1);
|
||||||
|
|
||||||
if (record.field === 'body') {
|
if (record.field === 'body') {
|
||||||
const parsedBody = recursiveParseJSON(field);
|
const parsedBody = recursiveParseJSON(field);
|
||||||
if (!isEmpty(parsedBody)) {
|
if (!isEmpty(parsedBody)) {
|
||||||
@ -174,7 +181,7 @@ function TableView({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CopyClipboardHOC textToCopy={field}>
|
<CopyClipboardHOC textToCopy={textToCopy}>
|
||||||
<span style={{ color: orange[6] }}>{field}</span>
|
<span style={{ color: orange[6] }}>{field}</span>
|
||||||
</CopyClipboardHOC>
|
</CopyClipboardHOC>
|
||||||
);
|
);
|
||||||
|
13
frontend/src/container/LogDetailedView/config.ts
Normal file
13
frontend/src/container/LogDetailedView/config.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||||
|
|
||||||
|
export const typeToArrayTypeMapper: { [key in DataTypes]: DataTypes } = {
|
||||||
|
[DataTypes.String]: DataTypes.ArrayString,
|
||||||
|
[DataTypes.Float64]: DataTypes.ArrayFloat64,
|
||||||
|
[DataTypes.Int64]: DataTypes.ArrayInt64,
|
||||||
|
[DataTypes.bool]: DataTypes.ArrayBool,
|
||||||
|
[DataTypes.EMPTY]: DataTypes.EMPTY,
|
||||||
|
[DataTypes.ArrayFloat64]: DataTypes.ArrayFloat64,
|
||||||
|
[DataTypes.ArrayInt64]: DataTypes.ArrayInt64,
|
||||||
|
[DataTypes.ArrayString]: DataTypes.ArrayString,
|
||||||
|
[DataTypes.ArrayBool]: DataTypes.ArrayBool,
|
||||||
|
};
|
@ -176,8 +176,8 @@ describe('Get Data Types utils', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Edge cases
|
// Edge cases
|
||||||
it('should return Int64 for empty array input', () => {
|
it('should return Empty for empty array input', () => {
|
||||||
expect(getDataTypes([])).toBe(DataTypes.Int64);
|
expect(getDataTypes([])).toBe(DataTypes.EMPTY);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle mixed array (return based on first element)', () => {
|
it('should handle mixed array (return based on first element)', () => {
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user