Compare commits
165 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a4e0d9c7df | ||
|
|
4076cd9847 | ||
|
|
e3f4fc2967 | ||
|
|
bccefc6a10 | ||
|
|
821471f4ab | ||
|
|
1e242b6d06 | ||
|
|
4ca5176836 | ||
|
|
7f397d529b | ||
|
|
656f354fdc | ||
|
|
4cc3ce224c | ||
|
|
a4a285c074 | ||
|
|
a8f8580606 | ||
|
|
e24918044e | ||
|
|
28d346eafb | ||
|
|
cbd2f4c643 | ||
|
|
fcedc9e445 | ||
|
|
d2d3c4bb36 | ||
|
|
dc4acc0730 | ||
|
|
043e5ca880 | ||
|
|
5c437dd8f9 | ||
|
|
31b898b2c6 | ||
|
|
8bfb0b5088 | ||
|
|
30e0924bfb | ||
|
|
ccada08db5 | ||
|
|
6654dd2672 | ||
|
|
4b0a7cc4d3 | ||
|
|
04acc49154 | ||
|
|
3db8a25eb9 | ||
|
|
8324d010ae | ||
|
|
735ab8e118 | ||
|
|
b0861f4fe0 | ||
|
|
61b6779a31 | ||
|
|
416a058eab | ||
|
|
4227faa6b5 | ||
|
|
9d3c4598ac | ||
|
|
6aba701cca | ||
|
|
cf1b0c2f24 | ||
|
|
231c2fd281 | ||
|
|
e3f17b5420 | ||
|
|
56f1f71461 | ||
|
|
72f4578152 | ||
|
|
9c8125ffc1 | ||
|
|
85d7752350 | ||
|
|
63ba9fb5e0 | ||
|
|
14a59a26b2 | ||
|
|
178c154263 | ||
|
|
122488c2c1 | ||
|
|
ad3fbd7599 | ||
|
|
1ad1ca5385 | ||
|
|
2fc82ffa59 | ||
|
|
ed809474d6 | ||
|
|
a8c4a91001 | ||
|
|
1ffb1b4a5d | ||
|
|
9a00998930 | ||
|
|
c60f612e0e | ||
|
|
7839134532 | ||
|
|
714a2ef4fd | ||
|
|
7f98a65022 | ||
|
|
616e3af18f | ||
|
|
17ae197bc3 | ||
|
|
27cda7a437 | ||
|
|
54a2309d8f | ||
|
|
748c78e9c3 | ||
|
|
7ae0326f61 | ||
|
|
c15240abab | ||
|
|
2fe9e53766 | ||
|
|
7209ac0007 | ||
|
|
96adc7f61c | ||
|
|
7b4fd55aeb | ||
|
|
86c34bd87d | ||
|
|
41f7a7993d | ||
|
|
dfd94f67bd | ||
|
|
052c32ce78 | ||
|
|
004f10e73b | ||
|
|
f17608fa10 | ||
|
|
8de8a8a86a | ||
|
|
0486721b35 | ||
|
|
14bbc609d8 | ||
|
|
f450d71a25 | ||
|
|
921fca5e67 | ||
|
|
1f7e70fa16 | ||
|
|
3ca048bc76 | ||
|
|
37e36626ab | ||
|
|
07808a8664 | ||
|
|
16d490fbe3 | ||
|
|
b1cee71621 | ||
|
|
e55a4da2bc | ||
|
|
0fa0e64697 | ||
|
|
c54fffb51d | ||
|
|
294b6966bf | ||
|
|
9aa72f847c | ||
|
|
32a55f3c4f | ||
|
|
8e6a7f13a1 | ||
|
|
89e8fb715c | ||
|
|
21de4bbd1b | ||
|
|
d4cc7c88c3 | ||
|
|
0e9e29e650 | ||
|
|
176725a4b4 | ||
|
|
88560e7c43 | ||
|
|
2538899544 | ||
|
|
ae0fc32fe1 | ||
|
|
859875667a | ||
|
|
674dee5a1b | ||
|
|
0318e53fdc | ||
|
|
b768840d36 | ||
|
|
ccc8be009c | ||
|
|
59549c36de | ||
|
|
aeee8b4cb2 | ||
|
|
03acc33888 | ||
|
|
b2a943769b | ||
|
|
1501ed0c5d | ||
|
|
218eb5379e | ||
|
|
e596dd77bd | ||
|
|
1138c6e41a | ||
|
|
15f328eb9e | ||
|
|
01312ec286 | ||
|
|
0574350e6e | ||
|
|
7e297dcb75 | ||
|
|
d184486978 | ||
|
|
337e33eb8a | ||
|
|
988dd1bcf0 | ||
|
|
25fc7b83ec | ||
|
|
893fcfa6ee | ||
|
|
23f94538c8 | ||
|
|
7586b50c5a | ||
|
|
0bee0a6d90 | ||
|
|
f89f3c0b14 | ||
|
|
598e71eb8e | ||
|
|
4d14416a08 | ||
|
|
2657276a80 | ||
|
|
48538e6b96 | ||
|
|
9337ff4b41 | ||
|
|
02cd069bb2 | ||
|
|
6a71d311b3 | ||
|
|
0b8367c817 | ||
|
|
b20fc39e08 | ||
|
|
1dd7bdb100 | ||
|
|
591ea96285 | ||
|
|
ee6b290a0c | ||
|
|
a37476a09b | ||
|
|
d41805a3b0 | ||
|
|
5d6bb18679 | ||
|
|
a393ea4d68 | ||
|
|
26b95f1b9f | ||
|
|
34be2953d3 | ||
|
|
e04d5fa7e8 | ||
|
|
2df5a9d72d | ||
|
|
b6e111b835 | ||
|
|
6a4aa7e5f5 | ||
|
|
962fc5e9ff | ||
|
|
4d5ee861ec | ||
|
|
2aef4578b0 | ||
|
|
f8bfd1abc4 | ||
|
|
a75f4f02d6 | ||
|
|
5ae4a59060 | ||
|
|
896836b57d | ||
|
|
06bedc92dc | ||
|
|
8a05b32a30 | ||
|
|
5bca66fedf | ||
|
|
6fb071cf37 | ||
|
|
9488ce8585 | ||
|
|
458cff32fd | ||
|
|
c8bad4fc79 | ||
|
|
5ac105beca | ||
|
|
865409d725 |
32
.github/workflows/build.yaml
vendored
32
.github/workflows/build.yaml
vendored
@@ -27,9 +27,37 @@ jobs:
|
||||
run: |
|
||||
make build-frontend-amd64
|
||||
|
||||
build-frontend-ee:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
- name: Create .env file
|
||||
run: |
|
||||
echo 'INTERCOM_APP_ID="${{ secrets.INTERCOM_APP_ID }}"' > frontend/.env
|
||||
echo 'SEGMENT_ID="${{ secrets.SEGMENT_ID }}"' >> frontend/.env
|
||||
echo 'CLARITY_PROJECT_ID="${{ secrets.CLARITY_PROJECT_ID }}"' >> frontend/.env
|
||||
- name: Install dependencies
|
||||
run: cd frontend && yarn install
|
||||
- name: Run ESLint
|
||||
run: cd frontend && npm run lint
|
||||
- name: Run Jest
|
||||
run: cd frontend && npm run jest
|
||||
- name: TSC
|
||||
run: yarn tsc
|
||||
working-directory: ./frontend
|
||||
- name: Build frontend docker image
|
||||
shell: bash
|
||||
run: |
|
||||
make build-frontend-amd64
|
||||
|
||||
build-query-service:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Setup golang
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: "1.21"
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
- name: Run tests
|
||||
@@ -44,6 +72,10 @@ jobs:
|
||||
build-ee-query-service:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Setup golang
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: "1.21"
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
- name: Build EE query-service image
|
||||
|
||||
49
.github/workflows/push.yaml
vendored
49
.github/workflows/push.yaml
vendored
@@ -123,3 +123,52 @@ jobs:
|
||||
fi
|
||||
- name: Build and push docker image
|
||||
run: make build-push-frontend
|
||||
|
||||
image-build-and-push-frontend-ee:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
- name: Create .env file
|
||||
run: |
|
||||
echo 'INTERCOM_APP_ID="${{ secrets.INTERCOM_APP_ID }}"' > frontend/.env
|
||||
echo 'SEGMENT_ID="${{ secrets.SEGMENT_ID }}"' >> frontend/.env
|
||||
echo 'CLARITY_PROJECT_ID="${{ secrets.CLARITY_PROJECT_ID }}"' >> frontend/.env
|
||||
- name: Install dependencies
|
||||
working-directory: frontend
|
||||
run: yarn install
|
||||
- name: Run Prettier
|
||||
working-directory: frontend
|
||||
run: npm run prettify
|
||||
continue-on-error: true
|
||||
- name: Run ESLint
|
||||
working-directory: frontend
|
||||
run: npm run lint
|
||||
continue-on-error: true
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
with:
|
||||
version: latest
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- uses: benjlevesque/short-sha@v2.2
|
||||
id: short-sha
|
||||
- name: Get branch name
|
||||
id: branch-name
|
||||
uses: tj-actions/branch-names@v5.1
|
||||
- name: Set docker tag environment
|
||||
run: |
|
||||
if [ '${{ steps.branch-name.outputs.is_tag }}' == 'true' ]; then
|
||||
tag="${{ steps.branch-name.outputs.tag }}"
|
||||
tag="${tag:1}"
|
||||
echo "DOCKER_TAG=${tag}-ee" >> $GITHUB_ENV
|
||||
elif [ '${{ steps.branch-name.outputs.current_branch }}' == 'main' ]; then
|
||||
echo "DOCKER_TAG=latest-ee" >> $GITHUB_ENV
|
||||
else
|
||||
echo "DOCKER_TAG=${{ steps.branch-name.outputs.current_branch }}-ee" >> $GITHUB_ENV
|
||||
fi
|
||||
- name: Build and push docker image
|
||||
run: make build-push-frontend
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -37,7 +37,7 @@ frontend/src/constants/env.ts
|
||||
**/locust-scripts/__pycache__/
|
||||
**/__debug_bin
|
||||
|
||||
frontend/*.env
|
||||
frontend/.env
|
||||
pkg/query-service/signoz.db
|
||||
|
||||
pkg/query-service/tests/test-deploy/data/
|
||||
|
||||
4
Makefile
4
Makefile
@@ -19,7 +19,7 @@ LOCAL_GOOS ?= $(shell go env GOOS)
|
||||
LOCAL_GOARCH ?= $(shell go env GOARCH)
|
||||
|
||||
REPONAME ?= signoz
|
||||
DOCKER_TAG ?= latest
|
||||
DOCKER_TAG ?= $(subst v,,$(BUILD_VERSION))
|
||||
|
||||
FRONTEND_DOCKER_IMAGE ?= frontend
|
||||
QUERY_SERVICE_DOCKER_IMAGE ?= query-service
|
||||
@@ -151,3 +151,5 @@ test:
|
||||
go test ./pkg/query-service/app/querier/...
|
||||
go test ./pkg/query-service/converter/...
|
||||
go test ./pkg/query-service/formatter/...
|
||||
go test ./pkg/query-service/tests/integration/...
|
||||
go test ./pkg/query-service/rules/...
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<img alt="Downloads" src="https://img.shields.io/docker/pulls/signoz/query-service?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>
|
||||
<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>
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
version: "3.9"
|
||||
|
||||
x-clickhouse-defaults:
|
||||
&clickhouse-defaults
|
||||
image: clickhouse/clickhouse-server:22.8.8-alpine
|
||||
x-clickhouse-defaults: &clickhouse-defaults
|
||||
image: clickhouse/clickhouse-server:23.7.3-alpine
|
||||
tty: true
|
||||
deploy:
|
||||
restart_policy:
|
||||
@@ -34,8 +33,7 @@ x-clickhouse-defaults:
|
||||
soft: 262144
|
||||
hard: 262144
|
||||
|
||||
x-clickhouse-depend:
|
||||
&clickhouse-depend
|
||||
x-clickhouse-depend: &clickhouse-depend
|
||||
depends_on:
|
||||
- clickhouse
|
||||
# - clickhouse-2
|
||||
@@ -133,7 +131,7 @@ services:
|
||||
# - ./data/clickhouse-3/:/var/lib/clickhouse/
|
||||
|
||||
alertmanager:
|
||||
image: signoz/alertmanager:0.23.2
|
||||
image: signoz/alertmanager:0.23.4
|
||||
volumes:
|
||||
- ./data/alertmanager:/data
|
||||
command:
|
||||
@@ -146,8 +144,12 @@ services:
|
||||
condition: on-failure
|
||||
|
||||
query-service:
|
||||
image: signoz/query-service:0.26.0
|
||||
command: [ "-config=/root/config/prometheus.yml" ]
|
||||
image: signoz/query-service:0.30.0
|
||||
command:
|
||||
[
|
||||
"-config=/root/config/prometheus.yml",
|
||||
"--prefer-delta=true"
|
||||
]
|
||||
# ports:
|
||||
# - "6060:6060" # pprof port
|
||||
# - "8080:8080" # query-service port
|
||||
@@ -182,7 +184,7 @@ services:
|
||||
<<: *clickhouse-depend
|
||||
|
||||
frontend:
|
||||
image: signoz/frontend:0.26.0
|
||||
image: signoz/frontend:0.30.0
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
@@ -195,7 +197,7 @@ services:
|
||||
- ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf
|
||||
|
||||
otel-collector:
|
||||
image: signoz/signoz-otel-collector:0.79.5
|
||||
image: signoz/signoz-otel-collector:0.79.7
|
||||
command:
|
||||
[
|
||||
"--config=/etc/otel-collector-config.yaml",
|
||||
@@ -228,7 +230,7 @@ services:
|
||||
<<: *clickhouse-depend
|
||||
|
||||
otel-collector-metrics:
|
||||
image: signoz/signoz-otel-collector:0.79.5
|
||||
image: signoz/signoz-otel-collector:0.79.7
|
||||
command:
|
||||
[
|
||||
"--config=/etc/otel-collector-metrics-config.yaml",
|
||||
|
||||
@@ -24,8 +24,16 @@ server {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
location /api/alertmanager {
|
||||
proxy_pass http://alertmanager:9093/api/v2;
|
||||
location ~ ^/api/(v1|v3)/logs/(tail|livetail){
|
||||
proxy_pass http://query-service:8080;
|
||||
proxy_http_version 1.1;
|
||||
|
||||
# connection will be closed if no data is read for 600s between successive read operations
|
||||
proxy_read_timeout 600s;
|
||||
|
||||
# dont buffer the data send it directly to client.
|
||||
proxy_buffering off;
|
||||
proxy_cache off;
|
||||
}
|
||||
|
||||
location /api {
|
||||
|
||||
@@ -2,7 +2,7 @@ version: "2.4"
|
||||
|
||||
services:
|
||||
clickhouse:
|
||||
image: clickhouse/clickhouse-server:22.8.8-alpine
|
||||
image: clickhouse/clickhouse-server:23.7.3-alpine
|
||||
container_name: signoz-clickhouse
|
||||
# ports:
|
||||
# - "9000:9000"
|
||||
@@ -34,7 +34,7 @@ services:
|
||||
|
||||
alertmanager:
|
||||
container_name: signoz-alertmanager
|
||||
image: signoz/alertmanager:0.23.2
|
||||
image: signoz/alertmanager:0.23.4
|
||||
volumes:
|
||||
- ./data/alertmanager:/data
|
||||
depends_on:
|
||||
@@ -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`
|
||||
otel-collector:
|
||||
container_name: signoz-otel-collector
|
||||
image: signoz/signoz-otel-collector:0.79.5
|
||||
image: signoz/signoz-otel-collector:0.79.7
|
||||
command:
|
||||
[
|
||||
"--config=/etc/otel-collector-config.yaml",
|
||||
@@ -78,7 +78,7 @@ services:
|
||||
|
||||
otel-collector-metrics:
|
||||
container_name: signoz-otel-collector-metrics
|
||||
image: signoz/signoz-otel-collector:0.79.5
|
||||
image: signoz/signoz-otel-collector:0.79.7
|
||||
command:
|
||||
[
|
||||
"--config=/etc/otel-collector-metrics-config.yaml",
|
||||
|
||||
@@ -22,7 +22,11 @@ services:
|
||||
- ./prometheus.yml:/root/config/prometheus.yml
|
||||
- ../dashboards:/root/config/dashboards
|
||||
- ./data/signoz/:/var/lib/signoz/
|
||||
command: [ "-config=/root/config/prometheus.yml" ]
|
||||
command:
|
||||
[
|
||||
"-config=/root/config/prometheus.yml",
|
||||
"--prefer-delta=true"
|
||||
]
|
||||
ports:
|
||||
- "6060:6060"
|
||||
- "8080:8080"
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
version: "2.4"
|
||||
|
||||
x-clickhouse-defaults:
|
||||
&clickhouse-defaults
|
||||
x-clickhouse-defaults: &clickhouse-defaults
|
||||
restart: on-failure
|
||||
image: clickhouse/clickhouse-server:22.8.8-alpine
|
||||
# addding non LTS version due to this fix https://github.com/ClickHouse/ClickHouse/commit/32caf8716352f45c1b617274c7508c86b7d1afab
|
||||
image: clickhouse/clickhouse-server:23.7.3-alpine
|
||||
tty: true
|
||||
depends_on:
|
||||
- zookeeper-1
|
||||
@@ -32,8 +32,7 @@ x-clickhouse-defaults:
|
||||
soft: 262144
|
||||
hard: 262144
|
||||
|
||||
x-clickhouse-depend:
|
||||
&clickhouse-depend
|
||||
x-clickhouse-depend: &clickhouse-depend
|
||||
depends_on:
|
||||
clickhouse:
|
||||
condition: service_healthy
|
||||
@@ -148,7 +147,7 @@ services:
|
||||
# - ./user_scripts:/var/lib/clickhouse/user_scripts/
|
||||
|
||||
alertmanager:
|
||||
image: signoz/alertmanager:${ALERTMANAGER_TAG:-0.23.2}
|
||||
image: signoz/alertmanager:${ALERTMANAGER_TAG:-0.23.4}
|
||||
container_name: signoz-alertmanager
|
||||
volumes:
|
||||
- ./data/alertmanager:/data
|
||||
@@ -163,9 +162,13 @@ services:
|
||||
# Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md`
|
||||
|
||||
query-service:
|
||||
image: signoz/query-service:${DOCKER_TAG:-0.26.0}
|
||||
image: signoz/query-service:${DOCKER_TAG:-0.30.0}
|
||||
container_name: signoz-query-service
|
||||
command: [ "-config=/root/config/prometheus.yml" ]
|
||||
command:
|
||||
[
|
||||
"-config=/root/config/prometheus.yml",
|
||||
"--prefer-delta=true"
|
||||
]
|
||||
# ports:
|
||||
# - "6060:6060" # pprof port
|
||||
# - "8080:8080" # query-service port
|
||||
@@ -198,7 +201,7 @@ services:
|
||||
<<: *clickhouse-depend
|
||||
|
||||
frontend:
|
||||
image: signoz/frontend:${DOCKER_TAG:-0.26.0}
|
||||
image: signoz/frontend:${DOCKER_TAG:-0.30.0}
|
||||
container_name: signoz-frontend
|
||||
restart: on-failure
|
||||
depends_on:
|
||||
@@ -210,7 +213,7 @@ services:
|
||||
- ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf
|
||||
|
||||
otel-collector:
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.79.5}
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.79.7}
|
||||
container_name: signoz-otel-collector
|
||||
command:
|
||||
[
|
||||
@@ -241,7 +244,7 @@ services:
|
||||
<<: *clickhouse-depend
|
||||
|
||||
otel-collector-metrics:
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.79.5}
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.79.7}
|
||||
container_name: signoz-otel-collector-metrics
|
||||
command:
|
||||
[
|
||||
|
||||
@@ -24,8 +24,16 @@ server {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
location /api/alertmanager {
|
||||
proxy_pass http://alertmanager:9093/api/v2;
|
||||
location ~ ^/api/(v1|v3)/logs/(tail|livetail){
|
||||
proxy_pass http://query-service:8080;
|
||||
proxy_http_version 1.1;
|
||||
|
||||
# connection will be closed if no data is read for 600s between successive read operations
|
||||
proxy_read_timeout 600s;
|
||||
|
||||
# dont buffer the data send it directly to client.
|
||||
proxy_buffering off;
|
||||
proxy_cache off;
|
||||
}
|
||||
|
||||
location /api {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM golang:1.18-buster AS builder
|
||||
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
|
||||
@@ -22,7 +22,7 @@ RUN cd ee/query-service \
|
||||
|
||||
|
||||
# use a minimal alpine image
|
||||
FROM alpine:3.7
|
||||
FROM alpine:3.16.7
|
||||
|
||||
# Add Maintainer Info
|
||||
LABEL maintainer="signoz"
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"go.signoz.io/signoz/ee/query-service/license"
|
||||
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/cache"
|
||||
baseint "go.signoz.io/signoz/pkg/query-service/interfaces"
|
||||
basemodel "go.signoz.io/signoz/pkg/query-service/model"
|
||||
rules "go.signoz.io/signoz/pkg/query-service/rules"
|
||||
@@ -29,6 +30,9 @@ type APIHandlerOptions struct {
|
||||
FeatureFlags baseint.FeatureLookup
|
||||
LicenseManager *license.Manager
|
||||
LogsParsingPipelineController *logparsingpipeline.LogParsingPipelineController
|
||||
Cache cache.Cache
|
||||
// Querier Influx Interval
|
||||
FluxInterval time.Duration
|
||||
}
|
||||
|
||||
type APIHandler struct {
|
||||
@@ -51,6 +55,8 @@ func NewAPIHandler(opts APIHandlerOptions) (*APIHandler, error) {
|
||||
RuleManager: opts.RulesManager,
|
||||
FeatureFlags: opts.FeatureFlags,
|
||||
LogsParsingPipelineController: opts.LogsParsingPipelineController,
|
||||
Cache: opts.Cache,
|
||||
FluxInterval: opts.FluxInterval,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
|
||||
@@ -107,13 +107,13 @@ func (ah *APIHandler) registerUser(w http.ResponseWriter, r *http.Request) {
|
||||
RespondError(w, model.InternalError(basemodel.ErrSignupFailed{}), nil)
|
||||
}
|
||||
|
||||
precheckResp := &model.PrecheckResponse{
|
||||
precheckResp := &basemodel.PrecheckResponse{
|
||||
SSO: false,
|
||||
IsUser: false,
|
||||
}
|
||||
|
||||
if domain != nil && domain.SsoEnabled {
|
||||
// so is enabled, create user and respond precheck data
|
||||
// sso is enabled, create user and respond precheck data
|
||||
user, apierr := baseauth.RegisterInvitedUser(ctx, req, true)
|
||||
if apierr != nil {
|
||||
RespondError(w, apierr, nil)
|
||||
|
||||
@@ -137,8 +137,8 @@ func (ah *APIHandler) queryRangeMetricsV2(w http.ResponseWriter, r *http.Request
|
||||
var s basemodel.Series
|
||||
s.QueryName = name
|
||||
s.Labels = v.Metric.Copy().Map()
|
||||
for _, p := range v.Points {
|
||||
s.Points = append(s.Points, basemodel.MetricPoint{Timestamp: p.T, Value: p.V})
|
||||
for _, p := range v.Floats {
|
||||
s.Points = append(s.Points, basemodel.MetricPoint{Timestamp: p.T, Value: p.F})
|
||||
}
|
||||
seriesList = append(seriesList, &s)
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ import (
|
||||
"go.signoz.io/signoz/pkg/query-service/app/opamp"
|
||||
opAmpModel "go.signoz.io/signoz/pkg/query-service/app/opamp/model"
|
||||
baseauth "go.signoz.io/signoz/pkg/query-service/auth"
|
||||
"go.signoz.io/signoz/pkg/query-service/cache"
|
||||
baseconst "go.signoz.io/signoz/pkg/query-service/constants"
|
||||
"go.signoz.io/signoz/pkg/query-service/healthcheck"
|
||||
basealm "go.signoz.io/signoz/pkg/query-service/integrations/alertManager"
|
||||
@@ -62,6 +63,8 @@ type ServerOptions struct {
|
||||
MaxIdleConns int
|
||||
MaxOpenConns int
|
||||
DialTimeout time.Duration
|
||||
CacheConfigPath string
|
||||
FluxInterval string
|
||||
}
|
||||
|
||||
// Server runs HTTP api service
|
||||
@@ -189,6 +192,21 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
|
||||
|
||||
telemetry.GetInstance().SetReader(reader)
|
||||
|
||||
var c cache.Cache
|
||||
if serverOptions.CacheConfigPath != "" {
|
||||
cacheOpts, err := cache.LoadFromYAMLCacheConfigFile(serverOptions.CacheConfigPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c = cache.NewCache(cacheOpts)
|
||||
}
|
||||
|
||||
fluxInterval, err := time.ParseDuration(serverOptions.FluxInterval)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
apiOpts := api.APIHandlerOptions{
|
||||
DataConnector: reader,
|
||||
SkipConfig: skipConfig,
|
||||
@@ -202,6 +220,8 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
|
||||
FeatureFlags: lm,
|
||||
LicenseManager: lm,
|
||||
LogsParsingPipelineController: logParsingPipelineController,
|
||||
Cache: c,
|
||||
FluxInterval: fluxInterval,
|
||||
}
|
||||
|
||||
apiHandler, err := api.NewAPIHandler(apiOpts)
|
||||
@@ -548,7 +568,7 @@ func (s *Server) Start() error {
|
||||
|
||||
go func() {
|
||||
zap.S().Info("Starting OpAmp Websocket server", zap.String("addr", baseconst.OpAmpWsEndpoint))
|
||||
err := opamp.InitalizeServer(baseconst.OpAmpWsEndpoint, &opAmpModel.AllAgents)
|
||||
err := opamp.InitializeAndStartServer(baseconst.OpAmpWsEndpoint, &opAmpModel.AllAgents)
|
||||
if err != nil {
|
||||
zap.S().Info("opamp ws server failed to start", err)
|
||||
s.unavailableChannel <- healthcheck.Unavailable
|
||||
|
||||
@@ -21,7 +21,6 @@ type ModelDao interface {
|
||||
DB() *sqlx.DB
|
||||
|
||||
// auth methods
|
||||
PrecheckLogin(ctx context.Context, email, sourceUrl string) (*model.PrecheckResponse, basemodel.BaseApiError)
|
||||
CanUsePassword(ctx context.Context, email string) (bool, basemodel.BaseApiError)
|
||||
PrepareSsoRedirect(ctx context.Context, redirectUri, email string) (redirectURL string, apierr basemodel.BaseApiError)
|
||||
GetDomainFromSsoResponse(ctx context.Context, relayState *url.URL) (*model.OrgDomain, error)
|
||||
|
||||
@@ -5,16 +5,61 @@ import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"go.signoz.io/signoz/ee/query-service/constants"
|
||||
"go.signoz.io/signoz/ee/query-service/model"
|
||||
baseauth "go.signoz.io/signoz/pkg/query-service/auth"
|
||||
baseconst "go.signoz.io/signoz/pkg/query-service/constants"
|
||||
basemodel "go.signoz.io/signoz/pkg/query-service/model"
|
||||
baseauth "go.signoz.io/signoz/pkg/query-service/auth"
|
||||
"go.signoz.io/signoz/pkg/query-service/utils"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// PrepareSsoRedirect prepares redirect page link after SSO response
|
||||
func (m *modelDao) createUserForSAMLRequest(ctx context.Context, email string) (*basemodel.User, basemodel.BaseApiError) {
|
||||
// get auth domain from email domain
|
||||
domain, apierr := m.GetDomainByEmail(ctx, email)
|
||||
|
||||
if apierr != nil {
|
||||
zap.S().Errorf("failed to get domain from email", apierr)
|
||||
return nil, model.InternalErrorStr("failed to get domain from email")
|
||||
}
|
||||
|
||||
hash, err := baseauth.PasswordHash(utils.GeneratePassowrd())
|
||||
if err != nil {
|
||||
zap.S().Errorf("failed to generate password hash when registering a user via SSO redirect", zap.Error(err))
|
||||
return nil, model.InternalErrorStr("failed to generate password hash")
|
||||
}
|
||||
|
||||
group, apiErr := m.GetGroupByName(ctx, baseconst.ViewerGroup)
|
||||
if apiErr != nil {
|
||||
zap.S().Debugf("GetGroupByName failed, err: %v\n", apiErr.Err)
|
||||
return nil, apiErr
|
||||
}
|
||||
|
||||
user := &basemodel.User{
|
||||
Id: uuid.NewString(),
|
||||
Name: "",
|
||||
Email: email,
|
||||
Password: hash,
|
||||
CreatedAt: time.Now().Unix(),
|
||||
ProfilePictureURL: "", // Currently unused
|
||||
GroupId: group.Id,
|
||||
OrgId: domain.OrgId,
|
||||
}
|
||||
|
||||
user, apiErr = m.CreateUser(ctx, user, false)
|
||||
if apiErr != nil {
|
||||
zap.S().Debugf("CreateUser failed, err: %v\n", apiErr.Err)
|
||||
return nil, apiErr
|
||||
}
|
||||
|
||||
return user, nil
|
||||
|
||||
}
|
||||
|
||||
// PrepareSsoRedirect prepares redirect page link after SSO response
|
||||
// is successfully parsed (i.e. valid email is available)
|
||||
func (m *modelDao) PrepareSsoRedirect(ctx context.Context, redirectUri, email string) (redirectURL string, apierr basemodel.BaseApiError) {
|
||||
|
||||
@@ -24,7 +69,20 @@ func (m *modelDao) PrepareSsoRedirect(ctx context.Context, redirectUri, email st
|
||||
return "", model.BadRequestStr("invalid user email received from the auth provider")
|
||||
}
|
||||
|
||||
tokenStore, err := baseauth.GenerateJWTForUser(&userPayload.User)
|
||||
user := &basemodel.User{}
|
||||
|
||||
if userPayload == nil {
|
||||
newUser, apiErr := m.createUserForSAMLRequest(ctx, email)
|
||||
user = newUser
|
||||
if apiErr != nil {
|
||||
zap.S().Errorf("failed to create user with email received from auth provider: %v", apierr.Error())
|
||||
return "", apiErr
|
||||
}
|
||||
} else {
|
||||
user = &userPayload.User
|
||||
}
|
||||
|
||||
tokenStore, err := baseauth.GenerateJWTForUser(user)
|
||||
if err != nil {
|
||||
zap.S().Errorf("failed to generate token for SSO login user", err)
|
||||
return "", model.InternalErrorStr("failed to generate token for the user")
|
||||
@@ -33,7 +91,7 @@ func (m *modelDao) PrepareSsoRedirect(ctx context.Context, redirectUri, email st
|
||||
return fmt.Sprintf("%s?jwt=%s&usr=%s&refreshjwt=%s",
|
||||
redirectUri,
|
||||
tokenStore.AccessJwt,
|
||||
userPayload.User.Id,
|
||||
user.Id,
|
||||
tokenStore.RefreshJwt), nil
|
||||
}
|
||||
|
||||
@@ -62,10 +120,10 @@ func (m *modelDao) CanUsePassword(ctx context.Context, email string) (bool, base
|
||||
|
||||
// PrecheckLogin is called when the login or signup page is loaded
|
||||
// to check sso login is to be prompted
|
||||
func (m *modelDao) PrecheckLogin(ctx context.Context, email, sourceUrl string) (*model.PrecheckResponse, basemodel.BaseApiError) {
|
||||
func (m *modelDao) PrecheckLogin(ctx context.Context, email, sourceUrl string) (*basemodel.PrecheckResponse, basemodel.BaseApiError) {
|
||||
|
||||
// assume user is valid unless proven otherwise
|
||||
resp := &model.PrecheckResponse{IsUser: true, CanSelfRegister: false}
|
||||
resp := &basemodel.PrecheckResponse{IsUser: true, CanSelfRegister: false}
|
||||
|
||||
// check if email is a valid user
|
||||
userPayload, baseApiErr := m.GetUserByEmail(ctx, email)
|
||||
@@ -76,6 +134,7 @@ func (m *modelDao) PrecheckLogin(ctx context.Context, email, sourceUrl string) (
|
||||
if userPayload == nil {
|
||||
resp.IsUser = false
|
||||
}
|
||||
|
||||
ssoAvailable := true
|
||||
err := m.checkFeature(model.SSO)
|
||||
if err != nil {
|
||||
@@ -91,6 +150,8 @@ func (m *modelDao) PrecheckLogin(ctx context.Context, email, sourceUrl string) (
|
||||
|
||||
if ssoAvailable {
|
||||
|
||||
resp.IsUser = true
|
||||
|
||||
// find domain from email
|
||||
orgDomain, apierr := m.GetDomainByEmail(ctx, email)
|
||||
if apierr != nil {
|
||||
|
||||
@@ -4,8 +4,8 @@ import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"net/url"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -28,29 +28,70 @@ type StoredDomain struct {
|
||||
|
||||
// GetDomainFromSsoResponse uses relay state received from IdP to fetch
|
||||
// user domain. The domain is further used to process validity of the response.
|
||||
// when sending login request to IdP we send relay state as URL (site url)
|
||||
// with domainId as query parameter.
|
||||
// when sending login request to IdP we send relay state as URL (site url)
|
||||
// with domainId or domainName as query parameter.
|
||||
func (m *modelDao) GetDomainFromSsoResponse(ctx context.Context, relayState *url.URL) (*model.OrgDomain, error) {
|
||||
// derive domain id from relay state now
|
||||
var domainIdStr string
|
||||
var domainIdStr string
|
||||
var domainNameStr string
|
||||
var domain *model.OrgDomain
|
||||
|
||||
for k, v := range relayState.Query() {
|
||||
if k == "domainId" && len(v) > 0 {
|
||||
domainIdStr = strings.Replace(v[0], ":", "-", -1)
|
||||
}
|
||||
if k == "domainName" && len(v) > 0 {
|
||||
domainNameStr = v[0]
|
||||
}
|
||||
}
|
||||
|
||||
domainId, err := uuid.Parse(domainIdStr)
|
||||
if domainIdStr != "" {
|
||||
domainId, err := uuid.Parse(domainIdStr)
|
||||
if err != nil {
|
||||
zap.S().Errorf("failed to parse domainId from relay state", err)
|
||||
return nil, fmt.Errorf("failed to parse domainId from IdP response")
|
||||
}
|
||||
|
||||
domain, err = m.GetDomain(ctx, domainId)
|
||||
if (err != nil) || domain == nil {
|
||||
zap.S().Errorf("failed to find domain from domainId received in IdP response", err.Error())
|
||||
return nil, fmt.Errorf("invalid credentials")
|
||||
}
|
||||
}
|
||||
|
||||
if domainNameStr != "" {
|
||||
|
||||
domainFromDB, err := m.GetDomainByName(ctx, domainNameStr)
|
||||
domain = domainFromDB
|
||||
if (err != nil) || domain == nil {
|
||||
zap.S().Errorf("failed to find domain from domainName received in IdP response", err.Error())
|
||||
return nil, fmt.Errorf("invalid credentials")
|
||||
}
|
||||
}
|
||||
if domain != nil {
|
||||
return domain, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("failed to find domain received in IdP response")
|
||||
}
|
||||
|
||||
// GetDomainByName returns org domain for a given domain name
|
||||
func (m *modelDao) GetDomainByName(ctx context.Context, name string) (*model.OrgDomain, basemodel.BaseApiError) {
|
||||
|
||||
stored := StoredDomain{}
|
||||
err := m.DB().Get(&stored, `SELECT * FROM org_domains WHERE name=$1 LIMIT 1`, name)
|
||||
|
||||
if err != nil {
|
||||
zap.S().Errorf("failed to parse domain id from relay state", err)
|
||||
return nil, fmt.Errorf("failed to parse response from IdP response")
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, model.BadRequest(fmt.Errorf("invalid domain name"))
|
||||
}
|
||||
return nil, model.InternalError(err)
|
||||
}
|
||||
|
||||
domain, err := m.GetDomain(ctx, domainId)
|
||||
if (err != nil) || domain == nil {
|
||||
zap.S().Errorf("failed to find domain received in IdP response", err.Error())
|
||||
return nil, fmt.Errorf("invalid credentials")
|
||||
domain := &model.OrgDomain{Id: stored.Id, Name: stored.Name, OrgId: stored.OrgId}
|
||||
if err := domain.LoadConfig(stored.Data); err != nil {
|
||||
return nil, model.InternalError(err)
|
||||
}
|
||||
|
||||
return domain, nil
|
||||
}
|
||||
|
||||
@@ -69,7 +110,7 @@ func (m *modelDao) GetDomain(ctx context.Context, id uuid.UUID) (*model.OrgDomai
|
||||
|
||||
domain := &model.OrgDomain{Id: stored.Id, Name: stored.Name, OrgId: stored.OrgId}
|
||||
if err := domain.LoadConfig(stored.Data); err != nil {
|
||||
return domain, model.InternalError(err)
|
||||
return nil, model.InternalError(err)
|
||||
}
|
||||
return domain, nil
|
||||
}
|
||||
@@ -206,7 +247,7 @@ func (m *modelDao) GetDomainByEmail(ctx context.Context, email string) (*model.O
|
||||
|
||||
domain := &model.OrgDomain{Id: stored.Id, Name: stored.Name, OrgId: stored.OrgId}
|
||||
if err := domain.LoadConfig(stored.Data); err != nil {
|
||||
return domain, model.InternalError(err)
|
||||
return nil, model.InternalError(err)
|
||||
}
|
||||
return domain, nil
|
||||
}
|
||||
|
||||
@@ -82,6 +82,7 @@ func main() {
|
||||
// the url used to build link in the alert messages in slack and other systems
|
||||
var ruleRepoURL string
|
||||
|
||||
var cacheConfigPath, fluxInterval string
|
||||
var enableQueryServiceLogOTLPExport bool
|
||||
var preferDelta bool
|
||||
var preferSpanMetrics bool
|
||||
@@ -99,10 +100,14 @@ func main() {
|
||||
flag.IntVar(&maxOpenConns, "max-open-conns", 100, "(max connections for use at any time.)")
|
||||
flag.DurationVar(&dialTimeout, "dial-timeout", 5*time.Second, "(the maximum time to establish a connection.)")
|
||||
flag.StringVar(&ruleRepoURL, "rules.repo-url", baseconst.AlertHelpPage, "(host address used to build rule link in alert messages)")
|
||||
flag.StringVar(&cacheConfigPath, "experimental.cache-config", "", "(cache config to use)")
|
||||
flag.StringVar(&fluxInterval, "flux-interval", "5m", "(cache config to use)")
|
||||
flag.BoolVar(&enableQueryServiceLogOTLPExport, "enable.query.service.log.otlp.export", false, "(enable query service log otlp export)")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
loggerMgr := initZapLog(enableQueryServiceLogOTLPExport)
|
||||
|
||||
zap.ReplaceGlobals(loggerMgr)
|
||||
defer loggerMgr.Sync() // flushes buffer, if any
|
||||
|
||||
@@ -121,6 +126,8 @@ func main() {
|
||||
MaxIdleConns: maxIdleConns,
|
||||
MaxOpenConns: maxOpenConns,
|
||||
DialTimeout: dialTimeout,
|
||||
CacheConfigPath: cacheConfigPath,
|
||||
FluxInterval: fluxInterval,
|
||||
}
|
||||
|
||||
// Read the jwt secret key
|
||||
|
||||
@@ -4,18 +4,9 @@ import (
|
||||
basemodel "go.signoz.io/signoz/pkg/query-service/model"
|
||||
)
|
||||
|
||||
// PrecheckResponse contains login precheck response
|
||||
type PrecheckResponse struct {
|
||||
SSO bool `json:"sso"`
|
||||
SsoUrl string `json:"ssoUrl"`
|
||||
CanSelfRegister bool `json:"canSelfRegister"`
|
||||
IsUser bool `json:"isUser"`
|
||||
SsoError string `json:"ssoError"`
|
||||
}
|
||||
|
||||
// GettableInvitation overrides base object and adds precheck into
|
||||
// response
|
||||
type GettableInvitation struct {
|
||||
*basemodel.InvitationResponseObject
|
||||
Precheck *PrecheckResponse `json:"precheck"`
|
||||
Precheck *basemodel.PrecheckResponse `json:"precheck"`
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package model
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
basemodel "go.signoz.io/signoz/pkg/query-service/model"
|
||||
)
|
||||
|
||||
@@ -61,7 +62,6 @@ func InternalError(err error) *ApiError {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// InternalErrorStr returns a ApiError object of internal type for string input
|
||||
func InternalErrorStr(s string) *ApiError {
|
||||
return &ApiError{
|
||||
@@ -69,6 +69,7 @@ func InternalErrorStr(s string) *ApiError {
|
||||
Err: fmt.Errorf(s),
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
ErrorNone basemodel.ErrorType = ""
|
||||
ErrorTimeout basemodel.ErrorType = "timeout"
|
||||
|
||||
@@ -9,6 +9,8 @@ const Basic = "BASIC_PLAN"
|
||||
const Pro = "PRO_PLAN"
|
||||
const Enterprise = "ENTERPRISE_PLAN"
|
||||
const DisableUpsell = "DISABLE_UPSELL"
|
||||
const Onboarding = "ONBOARDING"
|
||||
const ChatSupport = "CHAT_SUPPORT"
|
||||
|
||||
var BasicPlan = basemodel.FeatureSet{
|
||||
basemodel.Feature{
|
||||
@@ -81,6 +83,13 @@ var BasicPlan = basemodel.FeatureSet{
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.AlertChannelOpsgenie,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.AlertChannelMsTeams,
|
||||
Active: false,
|
||||
@@ -161,6 +170,13 @@ var ProPlan = basemodel.FeatureSet{
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.AlertChannelOpsgenie,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.AlertChannelMsTeams,
|
||||
Active: true,
|
||||
@@ -241,6 +257,13 @@ var EnterprisePlan = basemodel.FeatureSet{
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.AlertChannelOpsgenie,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.AlertChannelMsTeams,
|
||||
Active: true,
|
||||
@@ -255,4 +278,18 @@ var EnterprisePlan = basemodel.FeatureSet{
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: Onboarding,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: ChatSupport,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
node_modules
|
||||
.vscode
|
||||
build
|
||||
.env
|
||||
.git
|
||||
|
||||
6
frontend/.prettierignore
Normal file
6
frontend/.prettierignore
Normal file
@@ -0,0 +1,6 @@
|
||||
# Ignore artifacts:
|
||||
build
|
||||
coverage
|
||||
|
||||
# Ignore all MD files:
|
||||
**/*.md
|
||||
@@ -24,7 +24,7 @@ COPY . .
|
||||
RUN yarn build
|
||||
|
||||
|
||||
FROM nginx:1.24.0-alpine
|
||||
FROM nginx:1.25.2-alpine
|
||||
|
||||
COPY conf/default.conf /etc/nginx/conf.d/default.conf
|
||||
|
||||
|
||||
7
frontend/example.env
Normal file
7
frontend/example.env
Normal file
@@ -0,0 +1,7 @@
|
||||
NODE_ENV="development"
|
||||
BUNDLE_ANALYSER="true"
|
||||
FRONTEND_API_ENDPOINT="http://localhost:3301/"
|
||||
INTERCOM_APP_ID="intercom-app-id"
|
||||
|
||||
PLAYWRIGHT_TEST_BASE_URL="http://localhost:3301"
|
||||
CI="1"
|
||||
@@ -7,7 +7,7 @@ const config: Config.InitialOptions = {
|
||||
moduleFileExtensions: ['ts', 'tsx', 'js', 'json'],
|
||||
modulePathIgnorePatterns: ['dist'],
|
||||
moduleNameMapper: {
|
||||
'\\.(css|less)$': '<rootDir>/__mocks__/cssMock.ts',
|
||||
'\\.(css|less|scss)$': '<rootDir>/__mocks__/cssMock.ts',
|
||||
},
|
||||
globals: {
|
||||
extensionsToTreatAsEsm: ['.ts'],
|
||||
|
||||
@@ -29,27 +29,31 @@
|
||||
"dependencies": {
|
||||
"@ant-design/colors": "6.0.0",
|
||||
"@ant-design/icons": "4.8.0",
|
||||
"@grafana/data": "^8.4.3",
|
||||
"@grafana/data": "^9.5.2",
|
||||
"@mdx-js/loader": "2.3.0",
|
||||
"@mdx-js/react": "2.3.0",
|
||||
"@monaco-editor/react": "^4.3.1",
|
||||
"@uiw/react-md-editor": "3.23.5",
|
||||
"@xstate/react": "^3.0.0",
|
||||
"ansi-to-html": "0.7.2",
|
||||
"antd": "5.0.5",
|
||||
"antd-table-saveas-excel": "2.2.1",
|
||||
"axios": "^0.21.0",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"babel-jest": "^26.6.0",
|
||||
"babel-loader": "8.1.0",
|
||||
"babel-jest": "^29.6.4",
|
||||
"babel-loader": "9.1.3",
|
||||
"babel-plugin-named-asset-import": "^0.3.7",
|
||||
"babel-preset-minify": "^0.5.1",
|
||||
"babel-preset-react-app": "^10.0.0",
|
||||
"babel-preset-react-app": "^10.0.1",
|
||||
"chart.js": "3.9.1",
|
||||
"chartjs-adapter-date-fns": "^2.0.0",
|
||||
"chartjs-plugin-annotation": "^1.4.0",
|
||||
"classnames": "2.3.2",
|
||||
"color": "^4.2.1",
|
||||
"color-alpha": "1.1.3",
|
||||
"cross-env": "^7.0.3",
|
||||
"css-loader": "4.3.0",
|
||||
"css-minimizer-webpack-plugin": "^3.2.0",
|
||||
"css-minimizer-webpack-plugin": "5.0.1",
|
||||
"dayjs": "^1.10.7",
|
||||
"dompurify": "3.0.0",
|
||||
"dotenv": "8.2.0",
|
||||
@@ -58,7 +62,7 @@
|
||||
"file-loader": "6.1.1",
|
||||
"fontfaceobserver": "2.3.0",
|
||||
"history": "4.10.1",
|
||||
"html-webpack-plugin": "5.1.0",
|
||||
"html-webpack-plugin": "5.5.0",
|
||||
"i18next": "^21.6.12",
|
||||
"i18next-browser-languagedetector": "^6.1.3",
|
||||
"i18next-http-backend": "^1.3.2",
|
||||
@@ -75,21 +79,23 @@
|
||||
"react-dnd-html5-backend": "16.0.1",
|
||||
"react-dom": "18.2.0",
|
||||
"react-drag-listview": "2.0.0",
|
||||
"react-force-graph": "^1.41.0",
|
||||
"react-force-graph": "^1.43.0",
|
||||
"react-grid-layout": "^1.3.4",
|
||||
"react-helmet-async": "1.3.0",
|
||||
"react-i18next": "^11.16.1",
|
||||
"react-intersection-observer": "9.4.1",
|
||||
"react-markdown": "8.0.7",
|
||||
"react-query": "^3.34.19",
|
||||
"react-redux": "^7.2.2",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"react-syntax-highlighter": "15.5.0",
|
||||
"react-use": "^17.3.2",
|
||||
"react-virtuoso": "4.0.3",
|
||||
"redux": "^4.0.5",
|
||||
"redux-thunk": "^2.3.0",
|
||||
"stream": "^0.0.2",
|
||||
"style-loader": "1.3.0",
|
||||
"styled-components": "^5.2.1",
|
||||
"styled-components": "^5.3.11",
|
||||
"terser-webpack-plugin": "^5.2.5",
|
||||
"timestamp-nano": "^1.0.0",
|
||||
"ts-node": "^10.2.1",
|
||||
@@ -97,8 +103,8 @@
|
||||
"typescript": "^4.0.5",
|
||||
"uuid": "^8.3.2",
|
||||
"web-vitals": "^0.2.4",
|
||||
"webpack": "^5.23.0",
|
||||
"webpack-dev-server": "^4.3.1",
|
||||
"webpack": "5.88.2",
|
||||
"webpack-dev-server": "^4.15.1",
|
||||
"xstate": "^4.31.0"
|
||||
},
|
||||
"browserslist": {
|
||||
@@ -114,13 +120,13 @@
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.12.3",
|
||||
"@babel/plugin-proposal-class-properties": "^7.12.13",
|
||||
"@babel/core": "^7.22.11",
|
||||
"@babel/plugin-proposal-class-properties": "^7.18.6",
|
||||
"@babel/plugin-syntax-jsx": "^7.12.13",
|
||||
"@babel/preset-env": "^7.12.17",
|
||||
"@babel/preset-env": "^7.22.14",
|
||||
"@babel/preset-react": "^7.12.13",
|
||||
"@babel/preset-typescript": "^7.12.17",
|
||||
"@commitlint/cli": "^16.2.4",
|
||||
"@babel/preset-typescript": "^7.21.4",
|
||||
"@commitlint/cli": "^16.3.0",
|
||||
"@commitlint/config-conventional": "^16.2.4",
|
||||
"@jest/globals": "^27.5.1",
|
||||
"@playwright/test": "^1.22.0",
|
||||
@@ -146,25 +152,25 @@
|
||||
"@types/react-redux": "^7.1.11",
|
||||
"@types/react-resizable": "3.0.3",
|
||||
"@types/react-router-dom": "^5.1.6",
|
||||
"@types/react-syntax-highlighter": "15.5.7",
|
||||
"@types/styled-components": "^5.1.4",
|
||||
"@types/uuid": "^8.3.1",
|
||||
"@types/webpack": "^5.28.0",
|
||||
"@types/webpack-dev-server": "^4.3.0",
|
||||
"@typescript-eslint/eslint-plugin": "^4.28.2",
|
||||
"@typescript-eslint/parser": "^4.28.2",
|
||||
"@welldone-software/why-did-you-render": "6.2.1",
|
||||
"@types/webpack-dev-server": "^4.7.2",
|
||||
"@typescript-eslint/eslint-plugin": "^4.33.0",
|
||||
"@typescript-eslint/parser": "^4.33.0",
|
||||
"autoprefixer": "^9.0.0",
|
||||
"babel-plugin-styled-components": "^1.12.0",
|
||||
"compression-webpack-plugin": "9.0.0",
|
||||
"copy-webpack-plugin": "^8.1.0",
|
||||
"critters-webpack-plugin": "^3.0.1",
|
||||
"eslint": "^7.30.0",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-config-airbnb": "^19.0.4",
|
||||
"eslint-config-airbnb-typescript": "^16.1.4",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-config-standard": "^16.0.3",
|
||||
"eslint-plugin-import": "^2.25.4",
|
||||
"eslint-plugin-jest": "^26.1.2",
|
||||
"eslint-plugin-import": "^2.28.1",
|
||||
"eslint-plugin-jest": "^26.9.0",
|
||||
"eslint-plugin-jsx-a11y": "^6.5.1",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-prettier": "^4.0.0",
|
||||
@@ -175,17 +181,20 @@
|
||||
"eslint-plugin-sonarjs": "^0.12.0",
|
||||
"husky": "^7.0.4",
|
||||
"is-ci": "^3.0.1",
|
||||
"jest-playwright-preset": "^1.7.0",
|
||||
"jest-playwright-preset": "^1.7.2",
|
||||
"jest-styled-components": "^7.0.8",
|
||||
"lint-staged": "^12.3.7",
|
||||
"lint-staged": "^12.5.0",
|
||||
"portfinder-sync": "^0.0.2",
|
||||
"prettier": "2.2.1",
|
||||
"raw-loader": "4.0.2",
|
||||
"react-hooks-testing-library": "0.6.0",
|
||||
"react-hot-loader": "^4.13.0",
|
||||
"react-resizable": "3.0.4",
|
||||
"ts-jest": "^27.1.4",
|
||||
"sass": "1.66.1",
|
||||
"sass-loader": "13.3.2",
|
||||
"ts-jest": "^27.1.5",
|
||||
"ts-node": "^10.2.1",
|
||||
"typescript-plugin-css-modules": "^3.4.0",
|
||||
"typescript-plugin-css-modules": "5.0.1",
|
||||
"webpack-bundle-analyzer": "^4.5.0",
|
||||
"webpack-cli": "^4.9.2"
|
||||
},
|
||||
@@ -196,6 +205,9 @@
|
||||
},
|
||||
"resolutions": {
|
||||
"@types/react": "18.0.26",
|
||||
"@types/react-dom": "18.0.10"
|
||||
"@types/react-dom": "18.0.10",
|
||||
"debug": "4.3.4",
|
||||
"semver": "7.5.4",
|
||||
"xml2js": "0.5.0"
|
||||
}
|
||||
}
|
||||
|
||||
1
frontend/public/Logos/cmd-terminal.svg
Normal file
1
frontend/public/Logos/cmd-terminal.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 122.88 103.53" style="enable-background:new 0 0 122.88 103.53" xml:space="preserve"><style type="text/css">.st0{fill-rule:evenodd;clip-rule:evenodd;} .st0{fill:#1668dc;} .st1{fill:#FFFFFF;}</style><g><path class="st0" d="M5.47,0h111.93c3.01,0,5.47,2.46,5.47,5.47v92.58c0,3.01-2.46,5.47-5.47,5.47H5.47 c-3.01,0-5.47-2.46-5.47-5.47V5.47C0,2.46,2.46,0,5.47,0L5.47,0z M31.84,38.55l17.79,18.42l2.14,2.13l-2.12,2.16L31.68,80.31 l-5.07-5l15.85-16.15L26.81,43.6L31.84,38.55L31.84,38.55z M94.1,79.41H54.69v-6.84H94.1V79.41L94.1,79.41z M38.19,9.83 c3.19,0,5.78,2.59,5.78,5.78s-2.59,5.78-5.78,5.78c-3.19,0-5.78-2.59-5.78-5.78S35,9.83,38.19,9.83L38.19,9.83z M18.95,9.83 c3.19,0,5.78,2.59,5.78,5.78s-2.59,5.78-5.78,5.78c-3.19,0-5.78-2.59-5.78-5.78S15.75,9.83,18.95,9.83L18.95,9.83z M7.49,5.41 h107.91c1.15,0,2.09,0.94,2.09,2.09v18.32H5.4V7.5C5.4,6.35,6.34,5.41,7.49,5.41L7.49,5.41z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
1
frontend/public/Logos/docker.svg
Normal file
1
frontend/public/Logos/docker.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 122.88 88.17" style="enable-background:new 0 0 122.88 88.17" xml:space="preserve"><style type="text/css">.st0{fill:#0091E2;}</style><g><path class="st0" d="M121.68,33.34c-0.34-0.28-3.42-2.62-10.03-2.62c-1.71,0-3.48,0.17-5.19,0.46c-1.25-8.72-8.49-12.94-8.78-13.16 l-1.77-1.03l-1.14,1.65c-1.42,2.22-2.51,4.73-3.13,7.29c-1.2,4.96-0.46,9.63,2.05,13.62c-3.02,1.71-7.92,2.11-8.95,2.17l-80.93,0 c-2.11,0-3.82,1.71-3.82,3.82c-0.11,7.07,1.08,14.13,3.53,20.8c2.79,7.29,6.95,12.71,12.31,16.01c6.04,3.7,15.9,5.81,27.01,5.81 c5.01,0,10.03-0.46,14.99-1.37c6.9-1.25,13.51-3.65,19.6-7.12c5.02-2.91,9.52-6.61,13.34-10.94c6.44-7.24,10.26-15.33,13.05-22.51 c0.4,0,0.74,0,1.14,0c7.01,0,11.34-2.79,13.73-5.19c1.6-1.48,2.79-3.31,3.65-5.36l0.51-1.48L121.68,33.34L121.68,33.34z M71.59,39.38h10.83c0.51,0,0.97-0.4,0.97-0.97v-9.69c0-0.51-0.4-0.97-0.97-0.97l0,0l-10.83,0c-0.51,0-0.97,0.4-0.97,0.97l0,0v9.69 C70.68,38.98,71.08,39.38,71.59,39.38L71.59,39.38z M56.49,11.63h10.83c0.51,0,0.97-0.4,0.97-0.97V0.97c0-0.51-0.46-0.97-0.97-0.97 L56.49,0c-0.51,0-0.97,0.4-0.97,0.97l0,0v9.69C55.52,11.17,55.97,11.63,56.49,11.63L56.49,11.63z M56.49,25.53h10.83 c0.51,0,0.97-0.46,0.97-0.97v-9.69c0-0.51-0.46-0.97-0.97-0.97H56.49c-0.51,0-0.97,0.4-0.97,0.97l0,0v9.69 C55.52,25.08,55.97,25.53,56.49,25.53L56.49,25.53z M41.5,25.53h10.83c0.51,0,0.97-0.46,0.97-0.97v-9.69c0-0.51-0.4-0.97-0.97-0.97 l0,0H41.5c-0.51,0-0.97,0.4-0.97,0.97l0,0v9.69C40.53,25.08,40.93,25.53,41.5,25.53L41.5,25.53z M26.28,25.53h10.83 c0.51,0,0.97-0.46,0.97-0.97v-9.69c0-0.51-0.4-0.97-0.97-0.97l0,0H26.28c-0.51,0-0.97,0.4-0.97,0.97v9.69 C25.37,25.08,25.77,25.53,26.28,25.53L26.28,25.53z M56.49,39.38h10.83c0.51,0,0.97-0.4,0.97-0.97v-9.69c0-0.51-0.4-0.97-0.97-0.97 l0,0l-10.83,0c-0.51,0-0.97,0.4-0.97,0.97l0,0v9.69C55.52,38.98,55.97,39.38,56.49,39.38L56.49,39.38L56.49,39.38z M41.5,39.38 h10.83c0.51,0,0.97-0.4,0.97-0.97l0,0v-9.69c0-0.51-0.4-0.97-0.97-0.97l0,0l-10.83,0c-0.51,0-0.97,0.4-0.97,0.97l0,0v9.69 C40.53,38.98,40.93,39.38,41.5,39.38L41.5,39.38L41.5,39.38z M26.28,39.38h10.83c0.51,0,0.97-0.4,0.97-0.97l0,0v-9.69 c0-0.51-0.4-0.97-0.97-0.97l0,0l-10.83,0c-0.51,0-0.97,0.4-0.97,0.97v9.69C25.37,38.98,25.77,39.38,26.28,39.38L26.28,39.38z M11.35,39.38h10.83c0.51,0,0.97-0.4,0.97-0.97l0,0v-9.69c0-0.51-0.4-0.97-0.97-0.97l0,0l-10.83,0c-0.51,0-0.97,0.4-0.97,0.97l0,0 v9.69C10.44,38.98,10.84,39.38,11.35,39.38L11.35,39.38L11.35,39.38z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
1
frontend/public/Logos/kubernetes.svg
Normal file
1
frontend/public/Logos/kubernetes.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 5.9 KiB |
1
frontend/public/Logos/node-js.svg
Normal file
1
frontend/public/Logos/node-js.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 109 122.88" style="enable-background:new 0 0 109 122.88" xml:space="preserve"><style type="text/css">.st0{fill-rule:evenodd;clip-rule:evenodd;fill:#689F63;}</style><g><path class="st0" d="M68.43,87.08c-19.7,0-23.83-9.04-23.83-16.63c0-0.72,0.58-1.3,1.3-1.3h5.82c0.64,0,1.18,0.47,1.28,1.1 c0.88,5.93,3.49,8.92,15.41,8.92c9.49,0,13.52-2.14,13.52-7.18c0-2.9-1.15-5.05-15.89-6.49c-12.33-1.22-19.95-3.93-19.95-13.8 c0-9.08,7.66-14.49,20.5-14.49c14.42,0,21.56,5,22.46,15.76c0.03,0.37-0.1,0.73-0.35,1c-0.25,0.26-0.6,0.42-0.96,0.42H81.9 c-0.61,0-1.14-0.43-1.26-1.01c-1.41-6.23-4.81-8.23-14.07-8.23c-10.36,0-11.56,3.61-11.56,6.31c0,3.28,1.42,4.24,15.4,6.09 c13.84,1.84,20.41,4.43,20.41,14.16c0,9.81-8.18,15.43-22.45,15.43L68.43,87.08L68.43,87.08z M54.52,122.88 c-1.65,0-3.28-0.43-4.72-1.26l-15.03-8.9c-2.25-1.26-1.15-1.7-0.41-1.96c2.99-1.05,3.6-1.28,6.8-3.1c0.34-0.19,0.78-0.12,1.12,0.08 l11.55,6.85c0.42,0.23,1.01,0.23,1.4,0l45.03-25.99c0.42-0.24,0.69-0.72,0.69-1.22V35.43c0-0.52-0.27-0.98-0.7-1.24L55.23,8.22 c-0.42-0.25-0.97-0.25-1.39,0l-45,25.97c-0.44,0.25-0.71,0.73-0.71,1.23v51.96c0,0.5,0.27,0.97,0.7,1.21l12.33,7.12 c6.69,3.35,10.79-0.6,10.79-4.56V39.86c0-0.73,0.57-1.3,1.31-1.3l5.7,0c0.71,0,1.3,0.56,1.3,1.3v51.31 c0,8.93-4.87,14.05-13.33,14.05c-2.6,0-4.66,0-10.38-2.82L4.72,95.59C1.8,93.9,0,90.75,0,87.38V35.42c0-3.38,1.8-6.54,4.72-8.21 l45.07-26c2.85-1.61,6.64-1.61,9.47,0l45.02,26.01c2.91,1.68,4.72,4.82,4.72,8.21v51.96c0,3.37-1.81,6.51-4.72,8.21l-45.02,26 c-1.44,0.83-3.08,1.26-4.74,1.26L54.52,122.88L54.52,122.88z M54.52,122.88L54.52,122.88L54.52,122.88L54.52,122.88z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
1
frontend/public/Logos/software-window.svg
Normal file
1
frontend/public/Logos/software-window.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 122.88 98.18" style="enable-background:new 0 0 122.88 98.18" xml:space="preserve"><style type="text/css">.st0{fill-rule:evenodd;clip-rule:evenodd;} .st0{fill:#1668dc;} .st1{fill:#FFFFFF;}</style><g><path class="st0" d="M3.42,0h116.05c1.88,0,3.41,1.54,3.41,3.41v91.36c0,1.88-1.54,3.41-3.41,3.41l-116.05,0 C1.54,98.18,0,96.65,0,94.77L0,3.41C0,1.53,1.54,0,3.42,0L3.42,0L3.42,0z M25.89,8.19c2.05,0,3.72,1.67,3.72,3.72 c0,2.05-1.67,3.72-3.72,3.72c-2.05,0-3.72-1.67-3.72-3.72C22.17,9.85,23.83,8.19,25.89,8.19L25.89,8.19z M103.07,7.69l2.52,2.77 l2.52-2.77l1.97,1.79l-2.69,2.96l2.69,2.96l-1.97,1.79l-2.52-2.77l-2.52,2.77l-1.97-1.79l2.69-2.96l-2.69-2.96L103.07,7.69 L103.07,7.69z M14.52,8.19c2.05,0,3.72,1.67,3.72,3.72c0,2.05-1.67,3.72-3.72,3.72c-2.05,0-3.72-1.67-3.72-3.72 C10.79,9.85,12.46,8.19,14.52,8.19L14.52,8.19z M37.26,8.19c2.05,0,3.72,1.67,3.72,3.72c0,2.05-1.67,3.72-3.72,3.72 c-2.05,0-3.72-1.67-3.72-3.72C33.54,9.85,35.21,8.19,37.26,8.19L37.26,8.19z M14.05,22.75h93.33c1.77,0,3.22,1.49,3.22,3.22v59.2 c0,1.73-1.49,3.22-3.22,3.22l-93.33,0c-1.73,0-3.22-1.45-3.22-3.22v-59.2C10.84,24.2,12.29,22.75,14.05,22.75L14.05,22.75 L14.05,22.75z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
9
frontend/public/Logos/syslogs.svg
Normal file
9
frontend/public/Logos/syslogs.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 6.2 KiB |
3
frontend/public/locales/en-GB/explorer.json
Normal file
3
frontend/public/locales/en-GB/explorer.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"name_of_the_view": "Name of the view"
|
||||
}
|
||||
1
frontend/public/locales/en-GB/logs.json
Normal file
1
frontend/public/locales/en-GB/logs.json
Normal file
@@ -0,0 +1 @@
|
||||
{ "fetching_log_lines": "Fetching log lines" }
|
||||
@@ -8,7 +8,7 @@
|
||||
"label_orgname": "Organization Name",
|
||||
"placeholder_orgname": "Your Company",
|
||||
"prompt_keepme_posted": "Keep me updated on new SigNoz features",
|
||||
"prompt_anonymise": "Anonymise my usage date. We collect data to measure product usage",
|
||||
"prompt_anonymise": "Anonymise my usage data. We collect data to measure product usage",
|
||||
"failed_confirm_password": "Passwords don’t match. Please try again",
|
||||
"unexpected_error": "Something went wrong",
|
||||
"failed_to_initiate_login": "Signup completed but failed to initiate login",
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"SIGN_UP": "SigNoz | Sign Up",
|
||||
"LOGIN": "SigNoz | Login",
|
||||
"GET_STARTED": "SigNoz | Get Started",
|
||||
"SERVICE_METRICS": "SigNoz | Service Metrics",
|
||||
"SERVICE_MAP": "SigNoz | Service Map",
|
||||
"TRACE": "SigNoz | Trace",
|
||||
"TRACE_DETAIL": "SigNoz | Trace Detail",
|
||||
"TRACES_EXPLORER": "SigNoz | Traces Explorer",
|
||||
"SETTINGS": "SigNoz | Settings",
|
||||
"INSTRUMENTATION": "SigNoz | Get Started",
|
||||
"USAGE_EXPLORER": "SigNoz | Usage Explorer",
|
||||
"APPLICATION": "SigNoz | Home",
|
||||
"ALL_DASHBOARD": "SigNoz | All Dashboards",
|
||||
@@ -29,6 +29,7 @@
|
||||
"NOT_FOUND": "SigNoz | Page Not Found",
|
||||
"LOGS": "SigNoz | Logs",
|
||||
"LOGS_EXPLORER": "SigNoz | Logs Explorer",
|
||||
"LIVE_LOGS": "SigNoz | Live Logs",
|
||||
"HOME_PAGE": "Open source Observability Platform | SigNoz",
|
||||
"PASSWORD_RESET": "SigNoz | Password Reset",
|
||||
"LIST_LICENSES": "SigNoz | List of Licenses",
|
||||
|
||||
@@ -20,6 +20,9 @@
|
||||
"field_slack_recipient": "Recipient",
|
||||
"field_slack_title": "Title",
|
||||
"field_slack_description": "Description",
|
||||
"field_opsgenie_api_key": "API Key",
|
||||
"field_opsgenie_description": "Description",
|
||||
"placeholder_opsgenie_description": "Description",
|
||||
"field_webhook_username": "User Name (optional)",
|
||||
"field_webhook_password": "Password (optional)",
|
||||
"field_pager_routing_key": "Routing Key",
|
||||
@@ -31,8 +34,12 @@
|
||||
"field_pager_class": "Class",
|
||||
"field_pager_client": "Client",
|
||||
"field_pager_client_url": "Client URL",
|
||||
"field_opsgenie_message": "Message",
|
||||
"field_opsgenie_priority": "Priority",
|
||||
"placeholder_slack_description": "Description",
|
||||
"placeholder_pager_description": "Description",
|
||||
"placeholder_opsgenie_message": "Message",
|
||||
"placeholder_opsgenie_priority": "Priority",
|
||||
"help_pager_client": "Shows up as event source in Pagerduty",
|
||||
"help_pager_client_url": "Shows up as event source link in Pagerduty",
|
||||
"help_pager_class": "The class/type of the event",
|
||||
@@ -43,6 +50,9 @@
|
||||
"help_webhook_username": "Leave empty for bearer auth or when authentication is not necessary.",
|
||||
"help_webhook_password": "Specify a password or bearer token",
|
||||
"help_pager_description": "Shows up as description in pagerduty",
|
||||
"help_opsgenie_message": "Shows up as message in opsgenie",
|
||||
"help_opsgenie_priority": "Priority of the incident",
|
||||
"help_opsgenie_description": "Shows up as description in opsgenie",
|
||||
"channel_creation_done": "Successfully created the channel",
|
||||
"channel_creation_failed": "An unexpected error occurred while creating this channel",
|
||||
"channel_edit_done": "Channels Edited Successfully",
|
||||
|
||||
3
frontend/public/locales/en/explorer.json
Normal file
3
frontend/public/locales/en/explorer.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"name_of_the_view": "Name of the view"
|
||||
}
|
||||
1
frontend/public/locales/en/logs.json
Normal file
1
frontend/public/locales/en/logs.json
Normal file
@@ -0,0 +1 @@
|
||||
{ "fetching_log_lines": "Fetching log lines" }
|
||||
@@ -25,6 +25,7 @@
|
||||
"delete_processor_description": "Logs are processed sequentially in processors. Deleting a processor may change content of data processed by other processors",
|
||||
"search_pipeline_placeholder": "Filter Pipelines",
|
||||
"pipeline_name_placeholder": "Name",
|
||||
"pipeline_filter_placeholder": "Filter for selecting logs to be processed by this pipeline. Example: service_name = billing",
|
||||
"pipeline_tags_placeholder": "Tags",
|
||||
"pipeline_description_placeholder": "Enter description for your pipeline",
|
||||
"processor_name_placeholder": "Name",
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"label_orgname": "Organization Name",
|
||||
"placeholder_orgname": "Your Company",
|
||||
"prompt_keepme_posted": "Keep me updated on new SigNoz features",
|
||||
"prompt_anonymise": "Anonymise my usage date. We collect data to measure product usage",
|
||||
"prompt_anonymise": "Anonymise my usage data. We collect data to measure product usage",
|
||||
"failed_confirm_password": "Passwords don’t match. Please try again",
|
||||
"unexpected_error": "Something went wrong",
|
||||
"failed_to_initiate_login": "Signup completed but failed to initiate login",
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
"LOGIN": "SigNoz | Login",
|
||||
"SERVICE_METRICS": "SigNoz | Service Metrics",
|
||||
"SERVICE_MAP": "SigNoz | Service Map",
|
||||
"GET_STARTED": "SigNoz | Get Started",
|
||||
"TRACE": "SigNoz | Trace",
|
||||
"TRACE_DETAIL": "SigNoz | Trace Detail",
|
||||
"TRACES_EXPLORER": "SigNoz | Traces Explorer",
|
||||
"SETTINGS": "SigNoz | Settings",
|
||||
"INSTRUMENTATION": "SigNoz | Get Started",
|
||||
"USAGE_EXPLORER": "SigNoz | Usage Explorer",
|
||||
"APPLICATION": "SigNoz | Home",
|
||||
"ALL_DASHBOARD": "SigNoz | All Dashboards",
|
||||
@@ -29,6 +29,7 @@
|
||||
"NOT_FOUND": "SigNoz | Page Not Found",
|
||||
"LOGS": "SigNoz | Logs",
|
||||
"LOGS_EXPLORER": "SigNoz | Logs Explorer",
|
||||
"LIVE_LOGS": "SigNoz | Live Logs",
|
||||
"HOME_PAGE": "Open source Observability Platform | SigNoz",
|
||||
"PASSWORD_RESET": "SigNoz | Password Reset",
|
||||
"LIST_LICENSES": "SigNoz | List of Licenses",
|
||||
|
||||
@@ -1,20 +1,107 @@
|
||||
import { ConfigProvider } from 'antd';
|
||||
import getLocalStorageApi from 'api/browser/localstorage/get';
|
||||
import setLocalStorageApi from 'api/browser/localstorage/set';
|
||||
import NotFound from 'components/NotFound';
|
||||
import Spinner from 'components/Spinner';
|
||||
import { FeatureKeys } from 'constants/features';
|
||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||
import ROUTES from 'constants/routes';
|
||||
import AppLayout from 'container/AppLayout';
|
||||
import { useThemeConfig } from 'hooks/useDarkMode';
|
||||
import useGetFeatureFlag from 'hooks/useGetFeatureFlag';
|
||||
import { NotificationProvider } from 'hooks/useNotifications';
|
||||
import { ResourceProvider } from 'hooks/useResourceAttribute';
|
||||
import history from 'lib/history';
|
||||
import { QueryBuilderProvider } from 'providers/QueryBuilder';
|
||||
import { Suspense } from 'react';
|
||||
import { Suspense, useEffect, useState } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { Route, Router, Switch } from 'react-router-dom';
|
||||
import { Dispatch } from 'redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import AppActions from 'types/actions';
|
||||
import { UPDATE_FEATURE_FLAG_RESPONSE } from 'types/actions/app';
|
||||
import AppReducer from 'types/reducer/app';
|
||||
import { trackPageView } from 'utils/segmentAnalytics';
|
||||
|
||||
import PrivateRoute from './Private';
|
||||
import routes from './routes';
|
||||
import defaultRoutes from './routes';
|
||||
|
||||
function App(): JSX.Element {
|
||||
const themeConfig = useThemeConfig();
|
||||
const [routes, setRoutes] = useState(defaultRoutes);
|
||||
const { isLoggedIn: isLoggedInState, user } = useSelector<
|
||||
AppState,
|
||||
AppReducer
|
||||
>((state) => state.app);
|
||||
|
||||
const dispatch = useDispatch<Dispatch<AppActions>>();
|
||||
|
||||
const { hostname, pathname } = window.location;
|
||||
|
||||
const featureResponse = useGetFeatureFlag((allFlags) => {
|
||||
const isOnboardingEnabled =
|
||||
allFlags.find((flag) => flag.name === FeatureKeys.ONBOARDING)?.active ||
|
||||
false;
|
||||
|
||||
const isChatSupportEnabled =
|
||||
allFlags.find((flag) => flag.name === FeatureKeys.CHAT_SUPPORT)?.active ||
|
||||
false;
|
||||
|
||||
dispatch({
|
||||
type: UPDATE_FEATURE_FLAG_RESPONSE,
|
||||
payload: {
|
||||
featureFlag: allFlags,
|
||||
refetch: featureResponse.refetch,
|
||||
},
|
||||
});
|
||||
|
||||
if (
|
||||
!isOnboardingEnabled ||
|
||||
!(hostname && hostname.endsWith('signoz.cloud'))
|
||||
) {
|
||||
const newRoutes = routes.filter(
|
||||
(route) => route?.path !== ROUTES.GET_STARTED,
|
||||
);
|
||||
|
||||
setRoutes(newRoutes);
|
||||
}
|
||||
|
||||
if (isLoggedInState && isChatSupportEnabled) {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
window.Intercom('boot', {
|
||||
app_id: process.env.INTERCOM_APP_ID,
|
||||
email: user?.email || '',
|
||||
name: user?.name || '',
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const isIdentifiedUser = getLocalStorageApi(LOCALSTORAGE.IS_IDENTIFIED_USER);
|
||||
|
||||
if (
|
||||
isLoggedInState &&
|
||||
user &&
|
||||
user.userId &&
|
||||
user.email &&
|
||||
!isIdentifiedUser
|
||||
) {
|
||||
setLocalStorageApi(LOCALSTORAGE.IS_IDENTIFIED_USER, 'true');
|
||||
|
||||
window.analytics.identify(user?.email, {
|
||||
email: user?.email,
|
||||
name: user?.name,
|
||||
});
|
||||
|
||||
window.clarity('identify', user.email, user.name);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [isLoggedInState, user]);
|
||||
|
||||
useEffect(() => {
|
||||
trackPageView(pathname);
|
||||
}, [pathname]);
|
||||
|
||||
return (
|
||||
<ConfigProvider theme={themeConfig}>
|
||||
|
||||
@@ -44,6 +44,10 @@ export const GettingStarted = Loadable(
|
||||
() => import(/* webpackChunkName: "GettingStarted" */ 'pages/GettingStarted'),
|
||||
);
|
||||
|
||||
export const Onboarding = Loadable(
|
||||
() => import(/* webpackChunkName: "Onboarding" */ 'pages/OnboardingPage'),
|
||||
);
|
||||
|
||||
export const DashboardPage = Loadable(
|
||||
() => import(/* webpackChunkName: "DashboardPage" */ 'pages/Dashboard'),
|
||||
);
|
||||
@@ -110,6 +114,10 @@ export const LogsExplorer = Loadable(
|
||||
() => import(/* webpackChunkName: "Logs Explorer" */ 'pages/LogsExplorer'),
|
||||
);
|
||||
|
||||
export const LiveLogs = Loadable(
|
||||
() => import(/* webpackChunkName: "Live Logs" */ 'pages/LiveLogs'),
|
||||
);
|
||||
|
||||
export const Login = Loadable(
|
||||
() => import(/* webpackChunkName: "Login" */ 'pages/Login'),
|
||||
);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import ROUTES from 'constants/routes';
|
||||
import DashboardWidget from 'pages/DashboardWidget';
|
||||
import { RouteProps } from 'react-router-dom';
|
||||
|
||||
import {
|
||||
@@ -8,18 +7,20 @@ import {
|
||||
CreateAlertChannelAlerts,
|
||||
CreateNewAlerts,
|
||||
DashboardPage,
|
||||
DashboardWidget,
|
||||
EditAlertChannelsAlerts,
|
||||
EditRulesPage,
|
||||
ErrorDetails,
|
||||
GettingStarted,
|
||||
LicensePage,
|
||||
ListAllALertsPage,
|
||||
LiveLogs,
|
||||
Login,
|
||||
Logs,
|
||||
LogsExplorer,
|
||||
LogsIndexToFields,
|
||||
MySettings,
|
||||
NewDashboardPage,
|
||||
Onboarding,
|
||||
OrganizationSettings,
|
||||
PasswordReset,
|
||||
PipelinePage,
|
||||
@@ -45,6 +46,13 @@ const routes: AppRoutes[] = [
|
||||
isPrivate: false,
|
||||
key: 'SIGN_UP',
|
||||
},
|
||||
{
|
||||
path: ROUTES.GET_STARTED,
|
||||
exact: true,
|
||||
component: Onboarding,
|
||||
isPrivate: true,
|
||||
key: 'GET_STARTED',
|
||||
},
|
||||
{
|
||||
component: LogsIndexToFields,
|
||||
path: ROUTES.LOGS_INDEX_FIELDS,
|
||||
@@ -94,13 +102,6 @@ const routes: AppRoutes[] = [
|
||||
isPrivate: true,
|
||||
key: 'USAGE_EXPLORER',
|
||||
},
|
||||
{
|
||||
path: ROUTES.INSTRUMENTATION,
|
||||
exact: true,
|
||||
component: GettingStarted,
|
||||
isPrivate: true,
|
||||
key: 'INSTRUMENTATION',
|
||||
},
|
||||
{
|
||||
path: ROUTES.ALL_DASHBOARD,
|
||||
exact: true,
|
||||
@@ -234,6 +235,13 @@ const routes: AppRoutes[] = [
|
||||
key: 'LOGS_EXPLORER',
|
||||
isPrivate: true,
|
||||
},
|
||||
{
|
||||
path: ROUTES.LIVE_LOGS,
|
||||
exact: true,
|
||||
component: LiveLogs,
|
||||
key: 'LIVE_LOGS',
|
||||
isPrivate: true,
|
||||
},
|
||||
{
|
||||
path: ROUTES.LOGIN,
|
||||
exact: true,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { AxiosAlertManagerInstance } from 'api';
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError } from 'axios';
|
||||
import convertObjectIntoParams from 'lib/query/convertObjectIntoParams';
|
||||
@@ -11,15 +11,15 @@ const getTriggered = async (
|
||||
try {
|
||||
const queryParams = convertObjectIntoParams(props);
|
||||
|
||||
const response = await AxiosAlertManagerInstance.get(
|
||||
`/alerts?${queryParams}`,
|
||||
);
|
||||
const response = await axios.get(`/alerts?${queryParams}`);
|
||||
|
||||
const amData = JSON.parse(response.data.data);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data,
|
||||
payload: amData.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
|
||||
37
frontend/src/api/channels/createOpsgenie.ts
Normal file
37
frontend/src/api/channels/createOpsgenie.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/channels/createOpsgenie';
|
||||
|
||||
const create = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
const response = await axios.post('/channels', {
|
||||
name: props.name,
|
||||
opsgenie_configs: [
|
||||
{
|
||||
api_key: props.api_key,
|
||||
description: props.description,
|
||||
priority: props.priority,
|
||||
message: props.message,
|
||||
details: {
|
||||
...props.detailsArray,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: 'Success',
|
||||
payload: response.data.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default create;
|
||||
38
frontend/src/api/channels/editOpsgenie.ts
Normal file
38
frontend/src/api/channels/editOpsgenie.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/channels/editOpsgenie';
|
||||
|
||||
const editOpsgenie = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
const response = await axios.put(`/channels/${props.id}`, {
|
||||
name: props.name,
|
||||
opsgenie_configs: [
|
||||
{
|
||||
send_resolved: true,
|
||||
api_key: props.api_key,
|
||||
description: props.description,
|
||||
priority: props.priority,
|
||||
message: props.message,
|
||||
details: {
|
||||
...props.detailsArray,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: 'Success',
|
||||
payload: response.data.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default editOpsgenie;
|
||||
37
frontend/src/api/channels/testOpsgenie.ts
Normal file
37
frontend/src/api/channels/testOpsgenie.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/channels/createOpsgenie';
|
||||
|
||||
const testOpsgenie = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
const response = await axios.post('/testChannel', {
|
||||
name: props.name,
|
||||
opsgenie_configs: [
|
||||
{
|
||||
api_key: props.api_key,
|
||||
description: props.description,
|
||||
priority: props.priority,
|
||||
message: props.message,
|
||||
details: {
|
||||
...props.detailsArray,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: 'Success',
|
||||
payload: response.data.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default testOpsgenie;
|
||||
@@ -1,24 +1,8 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps } from 'types/api/dashboard/getAll';
|
||||
import { ApiResponse } from 'types/api';
|
||||
import { Dashboard } from 'types/api/dashboard/getAll';
|
||||
|
||||
const getAll = async (): Promise<
|
||||
SuccessResponse<PayloadProps> | ErrorResponse
|
||||
> => {
|
||||
try {
|
||||
const response = await axios.get('/dashboards');
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.message,
|
||||
payload: response.data.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default getAll;
|
||||
export const getAllDashboardList = (): Promise<Dashboard[]> =>
|
||||
axios
|
||||
.get<ApiResponse<Dashboard[]>>('/dashboards')
|
||||
.then((res) => res.data.data);
|
||||
|
||||
@@ -1,24 +1,10 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps } from 'types/api/features/getFeaturesFlags';
|
||||
import { ApiResponse } from 'types/api';
|
||||
import { FeatureFlagProps } from 'types/api/features/getFeaturesFlags';
|
||||
|
||||
const getFeaturesFlags = async (): Promise<
|
||||
SuccessResponse<PayloadProps> | ErrorResponse
|
||||
> => {
|
||||
try {
|
||||
const response = await axios.get(`/featureFlags`);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
const getFeaturesFlags = (): Promise<FeatureFlagProps[]> =>
|
||||
axios
|
||||
.get<ApiResponse<FeatureFlagProps[]>>(`/featureFlags`)
|
||||
.then((response) => response.data.data);
|
||||
|
||||
export default getFeaturesFlags;
|
||||
|
||||
@@ -79,7 +79,6 @@ const interceptorRejected = async (
|
||||
|
||||
// when refresh token is expired
|
||||
if (response.status === 401 && response.config.url === '/login') {
|
||||
console.log('logging out ');
|
||||
Logout();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { AxiosError } from 'axios';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/logs/addToSelectedFields';
|
||||
|
||||
const AddToSelectedFields = async (
|
||||
const addToSelectedFields = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
@@ -16,8 +16,8 @@ const AddToSelectedFields = async (
|
||||
payload: data.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
return Promise.reject(ErrorResponseHandler(error as AxiosError));
|
||||
}
|
||||
};
|
||||
|
||||
export default AddToSelectedFields;
|
||||
export default addToSelectedFields;
|
||||
|
||||
@@ -4,7 +4,7 @@ import { AxiosError } from 'axios';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/logs/addToSelectedFields';
|
||||
|
||||
const RemoveSelectedField = async (
|
||||
const removeSelectedField = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
@@ -16,8 +16,8 @@ const RemoveSelectedField = async (
|
||||
payload: data.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
return Promise.reject(ErrorResponseHandler(error as AxiosError));
|
||||
}
|
||||
};
|
||||
|
||||
export default RemoveSelectedField;
|
||||
export default removeSelectedField;
|
||||
|
||||
@@ -4,11 +4,11 @@ import { AxiosError } from 'axios';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import {
|
||||
MetricRangePayloadV3,
|
||||
MetricsRangeProps,
|
||||
QueryRangePayload,
|
||||
} from 'types/api/metrics/getQueryRange';
|
||||
|
||||
export const getMetricsQueryRange = async (
|
||||
props: MetricsRangeProps,
|
||||
props: QueryRangePayload,
|
||||
): Promise<SuccessResponse<MetricRangePayloadV3> | ErrorResponse> => {
|
||||
try {
|
||||
const response = await axios.post('/query_range', props);
|
||||
|
||||
5
frontend/src/api/saveView/deleteView.ts
Normal file
5
frontend/src/api/saveView/deleteView.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import axios from 'api';
|
||||
import { DeleteViewPayloadProps } from 'types/api/saveViews/types';
|
||||
|
||||
export const deleteView = (uuid: string): Promise<DeleteViewPayloadProps> =>
|
||||
axios.delete(`explorer/views/${uuid}`);
|
||||
9
frontend/src/api/saveView/getAllViews.ts
Normal file
9
frontend/src/api/saveView/getAllViews.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import axios from 'api';
|
||||
import { AxiosResponse } from 'axios';
|
||||
import { AllViewsProps } from 'types/api/saveViews/types';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
export const getAllViews = (
|
||||
sourcepage: DataSource,
|
||||
): Promise<AxiosResponse<AllViewsProps>> =>
|
||||
axios.get(`explorer/views?sourcePage=${sourcepage}`);
|
||||
16
frontend/src/api/saveView/saveView.ts
Normal file
16
frontend/src/api/saveView/saveView.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import axios from 'api';
|
||||
import { AxiosResponse } from 'axios';
|
||||
import { SaveViewPayloadProps, SaveViewProps } from 'types/api/saveViews/types';
|
||||
|
||||
export const saveView = ({
|
||||
compositeQuery,
|
||||
sourcePage,
|
||||
viewName,
|
||||
extraData,
|
||||
}: SaveViewProps): Promise<AxiosResponse<SaveViewPayloadProps>> =>
|
||||
axios.post('explorer/views', {
|
||||
name: viewName,
|
||||
sourcePage,
|
||||
compositeQuery,
|
||||
extraData,
|
||||
});
|
||||
19
frontend/src/api/saveView/updateView.ts
Normal file
19
frontend/src/api/saveView/updateView.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import axios from 'api';
|
||||
import {
|
||||
UpdateViewPayloadProps,
|
||||
UpdateViewProps,
|
||||
} from 'types/api/saveViews/types';
|
||||
|
||||
export const updateView = ({
|
||||
compositeQuery,
|
||||
viewName,
|
||||
extraData,
|
||||
sourcePage,
|
||||
viewKey,
|
||||
}: UpdateViewProps): Promise<UpdateViewPayloadProps> =>
|
||||
axios.put(`explorer/views/${viewKey}`, {
|
||||
name: viewName,
|
||||
compositeQuery,
|
||||
extraData,
|
||||
sourcePage,
|
||||
});
|
||||
@@ -2,14 +2,12 @@ import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import * as loginPrecheck from 'types/api/user/loginPrecheck';
|
||||
import { PayloadProps } from 'types/api/user/loginPrecheck';
|
||||
import { Props } from 'types/api/user/signup';
|
||||
|
||||
const signup = async (
|
||||
props: Props,
|
||||
): Promise<
|
||||
SuccessResponse<null | loginPrecheck.PayloadProps> | ErrorResponse
|
||||
> => {
|
||||
): Promise<SuccessResponse<null | PayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
const response = await axios.post(`/register`, {
|
||||
...props,
|
||||
|
||||
@@ -14,7 +14,11 @@ import {
|
||||
export const Logout = (): void => {
|
||||
deleteLocalStorageKey(LOCALSTORAGE.AUTH_TOKEN);
|
||||
deleteLocalStorageKey(LOCALSTORAGE.IS_LOGGED_IN);
|
||||
deleteLocalStorageKey(LOCALSTORAGE.IS_IDENTIFIED_USER);
|
||||
deleteLocalStorageKey(LOCALSTORAGE.REFRESH_AUTH_TOKEN);
|
||||
deleteLocalStorageKey(LOCALSTORAGE.LOGGED_IN_USER_EMAIL);
|
||||
deleteLocalStorageKey(LOCALSTORAGE.LOGGED_IN_USER_NAME);
|
||||
deleteLocalStorageKey(LOCALSTORAGE.CHAT_SUPPORT);
|
||||
|
||||
store.dispatch({
|
||||
type: LOGGED_IN,
|
||||
@@ -60,5 +64,9 @@ export const Logout = (): void => {
|
||||
},
|
||||
});
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
window.Intercom('shutdown');
|
||||
|
||||
history.push(ROUTES.LOGIN);
|
||||
};
|
||||
|
||||
244
frontend/src/components/ExplorerCard/ExplorerCard.tsx
Normal file
244
frontend/src/components/ExplorerCard/ExplorerCard.tsx
Normal file
@@ -0,0 +1,244 @@
|
||||
import {
|
||||
DeleteOutlined,
|
||||
MoreOutlined,
|
||||
SaveOutlined,
|
||||
ShareAltOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
Col,
|
||||
Dropdown,
|
||||
MenuProps,
|
||||
Popover,
|
||||
Row,
|
||||
Select,
|
||||
Space,
|
||||
Typography,
|
||||
} from 'antd';
|
||||
import axios from 'axios';
|
||||
import TextToolTip from 'components/TextToolTip';
|
||||
import { SOMETHING_WENT_WRONG } from 'constants/api';
|
||||
import { QueryParams } from 'constants/query';
|
||||
import { useGetSearchQueryParam } from 'hooks/queryBuilder/useGetSearchQueryParam';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { useDeleteView } from 'hooks/saveViews/useDeleteView';
|
||||
import { useGetAllViews } from 'hooks/saveViews/useGetAllViews';
|
||||
import { useUpdateView } from 'hooks/saveViews/useUpdateView';
|
||||
import useErrorNotification from 'hooks/useErrorNotification';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import { mapCompositeQueryFromQuery } from 'lib/newQueryBuilder/queryBuilderMappers/mapCompositeQueryFromQuery';
|
||||
import { useState } from 'react';
|
||||
import { useCopyToClipboard } from 'react-use';
|
||||
import { popupContainer } from 'utils/selectPopupContainer';
|
||||
|
||||
import { ExploreHeaderToolTip, SaveButtonText } from './constants';
|
||||
import MenuItemGenerator from './MenuItemGenerator';
|
||||
import SaveViewWithName from './SaveViewWithName';
|
||||
import {
|
||||
DropDownOverlay,
|
||||
ExplorerCardHeadContainer,
|
||||
OffSetCol,
|
||||
} from './styles';
|
||||
import { ExplorerCardProps } from './types';
|
||||
import { deleteViewHandler } from './utils';
|
||||
|
||||
function ExplorerCard({
|
||||
sourcepage,
|
||||
children,
|
||||
}: ExplorerCardProps): JSX.Element {
|
||||
const [isOpen, setIsOpen] = useState<boolean>(false);
|
||||
const [, setCopyUrl] = useCopyToClipboard();
|
||||
const { notifications } = useNotifications();
|
||||
|
||||
const onCopyUrlHandler = (): void => {
|
||||
setCopyUrl(window.location.href);
|
||||
notifications.success({
|
||||
message: 'Copied to clipboard',
|
||||
});
|
||||
};
|
||||
|
||||
const {
|
||||
currentQuery,
|
||||
panelType,
|
||||
redirectWithQueryBuilderData,
|
||||
updateAllQueriesOperators,
|
||||
isStagedQueryUpdated,
|
||||
} = useQueryBuilder();
|
||||
|
||||
const {
|
||||
data: viewsData,
|
||||
isLoading,
|
||||
error,
|
||||
isRefetching,
|
||||
refetch: refetchAllView,
|
||||
} = useGetAllViews(sourcepage);
|
||||
|
||||
useErrorNotification(error);
|
||||
|
||||
const handleOpenChange = (newOpen = false): void => {
|
||||
setIsOpen(newOpen);
|
||||
};
|
||||
|
||||
const viewName = useGetSearchQueryParam(QueryParams.viewName) || '';
|
||||
|
||||
const viewKey = useGetSearchQueryParam(QueryParams.viewKey) || '';
|
||||
|
||||
const isQueryUpdated = isStagedQueryUpdated(viewsData?.data?.data, viewKey);
|
||||
|
||||
const { mutateAsync: updateViewAsync } = useUpdateView({
|
||||
compositeQuery: mapCompositeQueryFromQuery(currentQuery, panelType),
|
||||
viewKey,
|
||||
extraData: '',
|
||||
sourcePage: sourcepage,
|
||||
viewName,
|
||||
});
|
||||
|
||||
const { mutateAsync: deleteViewAsync } = useDeleteView(viewKey);
|
||||
|
||||
const showErrorNotification = (err: Error): void => {
|
||||
notifications.error({
|
||||
message: axios.isAxiosError(err) ? err.message : SOMETHING_WENT_WRONG,
|
||||
});
|
||||
};
|
||||
|
||||
const onDeleteHandler = (): void =>
|
||||
deleteViewHandler({
|
||||
deleteViewAsync,
|
||||
notifications,
|
||||
panelType,
|
||||
redirectWithQueryBuilderData,
|
||||
refetchAllView,
|
||||
viewId: viewKey,
|
||||
viewKey,
|
||||
updateAllQueriesOperators,
|
||||
sourcePage: sourcepage,
|
||||
});
|
||||
|
||||
const onUpdateQueryHandler = (): void => {
|
||||
updateViewAsync(
|
||||
{
|
||||
compositeQuery: mapCompositeQueryFromQuery(currentQuery, panelType),
|
||||
viewKey,
|
||||
extraData: '',
|
||||
sourcePage: sourcepage,
|
||||
viewName,
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
notifications.success({
|
||||
message: 'View Updated Successfully',
|
||||
});
|
||||
refetchAllView();
|
||||
},
|
||||
onError: (err) => {
|
||||
showErrorNotification(err);
|
||||
},
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
const moreOptionMenu: MenuProps = {
|
||||
items: [
|
||||
{
|
||||
key: 'delete',
|
||||
label: <Typography.Text strong>Delete</Typography.Text>,
|
||||
onClick: onDeleteHandler,
|
||||
icon: <DeleteOutlined />,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const saveButtonType = isQueryUpdated ? 'default' : 'primary';
|
||||
const saveButtonIcon = isQueryUpdated ? null : <SaveOutlined />;
|
||||
|
||||
return (
|
||||
<>
|
||||
<ExplorerCardHeadContainer size="small">
|
||||
<Row align="middle">
|
||||
<Col span={6}>
|
||||
<Space>
|
||||
<Typography>Query Builder</Typography>
|
||||
<TextToolTip
|
||||
url={ExploreHeaderToolTip.url}
|
||||
text={ExploreHeaderToolTip.text}
|
||||
useFilledIcon={false}
|
||||
/>
|
||||
</Space>
|
||||
</Col>
|
||||
<OffSetCol span={18}>
|
||||
<Space size="large">
|
||||
{viewsData?.data.data && viewsData?.data.data.length && (
|
||||
<Space>
|
||||
<Select
|
||||
getPopupContainer={popupContainer}
|
||||
loading={isLoading || isRefetching}
|
||||
showSearch
|
||||
placeholder="Select a view"
|
||||
dropdownStyle={DropDownOverlay}
|
||||
dropdownMatchSelectWidth={false}
|
||||
optionLabelProp="value"
|
||||
value={viewName || undefined}
|
||||
>
|
||||
{viewsData?.data.data.map((view) => (
|
||||
<Select.Option key={view.uuid} value={view.name}>
|
||||
<MenuItemGenerator
|
||||
viewName={view.name}
|
||||
viewKey={viewKey}
|
||||
createdBy={view.createdBy}
|
||||
uuid={view.uuid}
|
||||
refetchAllView={refetchAllView}
|
||||
viewData={viewsData.data.data}
|
||||
sourcePage={sourcepage}
|
||||
/>
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</Space>
|
||||
)}
|
||||
{isQueryUpdated && (
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<SaveOutlined />}
|
||||
onClick={onUpdateQueryHandler}
|
||||
>
|
||||
Save changes
|
||||
</Button>
|
||||
)}
|
||||
<Popover
|
||||
getPopupContainer={popupContainer}
|
||||
placement="bottomLeft"
|
||||
trigger="click"
|
||||
content={
|
||||
<SaveViewWithName
|
||||
sourcePage={sourcepage}
|
||||
handlePopOverClose={handleOpenChange}
|
||||
refetchAllView={refetchAllView}
|
||||
/>
|
||||
}
|
||||
showArrow={false}
|
||||
open={isOpen}
|
||||
onOpenChange={handleOpenChange}
|
||||
>
|
||||
<Button type={saveButtonType} icon={saveButtonIcon}>
|
||||
{isQueryUpdated
|
||||
? SaveButtonText.SAVE_AS_NEW_VIEW
|
||||
: SaveButtonText.SAVE_VIEW}
|
||||
</Button>
|
||||
</Popover>
|
||||
<ShareAltOutlined onClick={onCopyUrlHandler} />
|
||||
{viewKey && (
|
||||
<Dropdown trigger={['click']} menu={moreOptionMenu}>
|
||||
<MoreOutlined />
|
||||
</Dropdown>
|
||||
)}
|
||||
</Space>
|
||||
</OffSetCol>
|
||||
</Row>
|
||||
</ExplorerCardHeadContainer>
|
||||
<Card>{children}</Card>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default ExplorerCard;
|
||||
103
frontend/src/components/ExplorerCard/MenuItemGenerator.tsx
Normal file
103
frontend/src/components/ExplorerCard/MenuItemGenerator.tsx
Normal file
@@ -0,0 +1,103 @@
|
||||
import { DeleteOutlined } from '@ant-design/icons';
|
||||
import { Col, Row, Tooltip, Typography } from 'antd';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { useDeleteView } from 'hooks/saveViews/useDeleteView';
|
||||
import { useHandleExplorerTabChange } from 'hooks/useHandleExplorerTabChange';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import { MouseEvent, useCallback } from 'react';
|
||||
|
||||
import { MenuItemContainer } from './styles';
|
||||
import { MenuItemLabelGeneratorProps } from './types';
|
||||
import {
|
||||
deleteViewHandler,
|
||||
getViewDetailsUsingViewKey,
|
||||
trimViewName,
|
||||
} from './utils';
|
||||
|
||||
function MenuItemGenerator({
|
||||
viewName,
|
||||
viewKey,
|
||||
createdBy,
|
||||
uuid,
|
||||
viewData,
|
||||
refetchAllView,
|
||||
sourcePage,
|
||||
}: MenuItemLabelGeneratorProps): JSX.Element {
|
||||
const {
|
||||
panelType,
|
||||
redirectWithQueryBuilderData,
|
||||
updateAllQueriesOperators,
|
||||
} = useQueryBuilder();
|
||||
const { handleExplorerTabChange } = useHandleExplorerTabChange();
|
||||
|
||||
const { notifications } = useNotifications();
|
||||
|
||||
const { mutateAsync: deleteViewAsync } = useDeleteView(uuid);
|
||||
|
||||
const onDeleteHandler = (event: MouseEvent<HTMLElement>): void => {
|
||||
event.stopPropagation();
|
||||
deleteViewHandler({
|
||||
deleteViewAsync,
|
||||
notifications,
|
||||
panelType,
|
||||
redirectWithQueryBuilderData,
|
||||
refetchAllView,
|
||||
viewId: uuid,
|
||||
viewKey,
|
||||
updateAllQueriesOperators,
|
||||
sourcePage,
|
||||
});
|
||||
};
|
||||
|
||||
const onMenuItemSelectHandler = useCallback(
|
||||
({ key }: { key: string }): void => {
|
||||
const currentViewDetails = getViewDetailsUsingViewKey(key, viewData);
|
||||
if (!currentViewDetails) return;
|
||||
const {
|
||||
query,
|
||||
name,
|
||||
uuid,
|
||||
panelType: currentPanelType,
|
||||
} = currentViewDetails;
|
||||
|
||||
handleExplorerTabChange(currentPanelType, {
|
||||
query,
|
||||
name,
|
||||
uuid,
|
||||
});
|
||||
},
|
||||
[viewData, handleExplorerTabChange],
|
||||
);
|
||||
|
||||
const onLabelClickHandler = (): void => {
|
||||
onMenuItemSelectHandler({
|
||||
key: uuid,
|
||||
});
|
||||
};
|
||||
|
||||
const newViewName = trimViewName(viewName);
|
||||
|
||||
return (
|
||||
<MenuItemContainer onClick={onLabelClickHandler}>
|
||||
<Row justify="space-between">
|
||||
<Col span={22}>
|
||||
<Row>
|
||||
<Tooltip title={viewName}>
|
||||
<Typography.Text strong>{newViewName}</Typography.Text>
|
||||
</Tooltip>
|
||||
</Row>
|
||||
<Row>
|
||||
<Typography.Text type="secondary">Created by {createdBy}</Typography.Text>
|
||||
</Row>
|
||||
</Col>
|
||||
<Col span={2}>
|
||||
<Typography.Link>
|
||||
<DeleteOutlined onClick={onDeleteHandler} />
|
||||
</Typography.Link>
|
||||
</Col>
|
||||
</Row>
|
||||
</MenuItemContainer>
|
||||
);
|
||||
}
|
||||
|
||||
export default MenuItemGenerator;
|
||||
76
frontend/src/components/ExplorerCard/SaveViewWithName.tsx
Normal file
76
frontend/src/components/ExplorerCard/SaveViewWithName.tsx
Normal file
@@ -0,0 +1,76 @@
|
||||
import { Card, Form, Input, Typography } from 'antd';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { useSaveView } from 'hooks/saveViews/useSaveView';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import { mapCompositeQueryFromQuery } from 'lib/newQueryBuilder/queryBuilderMappers/mapCompositeQueryFromQuery';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { SaveButton } from './styles';
|
||||
import { SaveViewFormProps, SaveViewWithNameProps } from './types';
|
||||
import { saveViewHandler } from './utils';
|
||||
|
||||
function SaveViewWithName({
|
||||
sourcePage,
|
||||
handlePopOverClose,
|
||||
refetchAllView,
|
||||
}: SaveViewWithNameProps): JSX.Element {
|
||||
const [form] = Form.useForm<SaveViewFormProps>();
|
||||
const { t } = useTranslation(['explorer']);
|
||||
const {
|
||||
currentQuery,
|
||||
panelType,
|
||||
redirectWithQueryBuilderData,
|
||||
} = useQueryBuilder();
|
||||
const { notifications } = useNotifications();
|
||||
const compositeQuery = mapCompositeQueryFromQuery(currentQuery, panelType);
|
||||
|
||||
const { isLoading, mutateAsync: saveViewAsync } = useSaveView({
|
||||
viewName: form.getFieldValue('viewName'),
|
||||
compositeQuery,
|
||||
sourcePage,
|
||||
extraData: '',
|
||||
});
|
||||
|
||||
const onSaveHandler = (): void => {
|
||||
saveViewHandler({
|
||||
compositeQuery,
|
||||
handlePopOverClose,
|
||||
extraData: '',
|
||||
notifications,
|
||||
panelType: panelType || PANEL_TYPES.LIST,
|
||||
redirectWithQueryBuilderData,
|
||||
refetchAllView,
|
||||
saveViewAsync,
|
||||
sourcePage,
|
||||
viewName: form.getFieldValue('viewName'),
|
||||
form,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<Typography>{t('name_of_the_view')}</Typography>
|
||||
<Form form={form} onFinish={onSaveHandler}>
|
||||
<Form.Item
|
||||
name={['viewName']}
|
||||
required
|
||||
requiredMark
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: 'Please enter view name',
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input placeholder="Enter Name" />
|
||||
</Form.Item>
|
||||
<SaveButton htmlType="submit" type="primary" loading={isLoading}>
|
||||
Save
|
||||
</SaveButton>
|
||||
</Form>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
export default SaveViewWithName;
|
||||
32
frontend/src/components/ExplorerCard/__mock__/viewData.ts
Normal file
32
frontend/src/components/ExplorerCard/__mock__/viewData.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { ICompositeMetricQuery } from 'types/api/alerts/compositeQuery';
|
||||
import { ViewProps } from 'types/api/saveViews/types';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
export const viewMockData: ViewProps[] = [
|
||||
{
|
||||
uuid: 'view1',
|
||||
name: 'View 1',
|
||||
createdBy: 'User 1',
|
||||
category: 'category 1',
|
||||
compositeQuery: {} as ICompositeMetricQuery,
|
||||
createdAt: '2021-07-07T06:31:00.000Z',
|
||||
updatedAt: '2021-07-07T06:33:00.000Z',
|
||||
extraData: '',
|
||||
sourcePage: DataSource.TRACES,
|
||||
tags: [],
|
||||
updatedBy: 'User 1',
|
||||
},
|
||||
{
|
||||
uuid: 'view2',
|
||||
name: 'View 2',
|
||||
createdBy: 'User 2',
|
||||
category: 'category 2',
|
||||
compositeQuery: {} as ICompositeMetricQuery,
|
||||
createdAt: '2021-07-07T06:30:00.000Z',
|
||||
updatedAt: '2021-07-07T06:30:00.000Z',
|
||||
extraData: '',
|
||||
sourcePage: DataSource.TRACES,
|
||||
tags: [],
|
||||
updatedBy: 'User 2',
|
||||
},
|
||||
];
|
||||
14
frontend/src/components/ExplorerCard/constants.ts
Normal file
14
frontend/src/components/ExplorerCard/constants.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { QueryParams } from 'constants/query';
|
||||
|
||||
export const ExploreHeaderToolTip = {
|
||||
url:
|
||||
'https://signoz.io/docs/userguide/query-builder/?utm_source=product&utm_medium=new-query-builder',
|
||||
text: 'More details on how to use query builder',
|
||||
};
|
||||
|
||||
export const SaveButtonText = {
|
||||
SAVE_AS_NEW_VIEW: 'Save as new view',
|
||||
SAVE_VIEW: 'Save view',
|
||||
};
|
||||
|
||||
export type QuerySearchParamNames = QueryParams.viewName | QueryParams.viewKey;
|
||||
@@ -1,27 +0,0 @@
|
||||
import { Card, Space, Typography } from 'antd';
|
||||
import TextToolTip from 'components/TextToolTip';
|
||||
|
||||
function ExplorerCard({ children }: Props): JSX.Element {
|
||||
return (
|
||||
<Card
|
||||
size="small"
|
||||
title={
|
||||
<Space>
|
||||
<Typography>Query Builder</Typography>
|
||||
<TextToolTip
|
||||
url="https://signoz.io/docs/userguide/query-builder/?utm_source=product&utm_medium=new-query-builder"
|
||||
text="More details on how to use query builder"
|
||||
/>
|
||||
</Space>
|
||||
}
|
||||
>
|
||||
{children}
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
interface Props {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export default ExplorerCard;
|
||||
28
frontend/src/components/ExplorerCard/styles.ts
Normal file
28
frontend/src/components/ExplorerCard/styles.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { Button, Card, Col } from 'antd';
|
||||
import styled, { CSSProperties } from 'styled-components';
|
||||
|
||||
export const ExplorerCardHeadContainer = styled(Card)`
|
||||
margin: 1rem 0;
|
||||
`;
|
||||
|
||||
export const OffSetCol = styled(Col)`
|
||||
text-align: right;
|
||||
`;
|
||||
|
||||
export const SaveButton = styled(Button)`
|
||||
&&& {
|
||||
margin: 1rem 0;
|
||||
width: 5rem;
|
||||
}
|
||||
`;
|
||||
|
||||
export const DropDownOverlay: CSSProperties = {
|
||||
maxHeight: '20rem',
|
||||
overflowY: 'auto',
|
||||
width: '20rem',
|
||||
padding: 0,
|
||||
};
|
||||
|
||||
export const MenuItemContainer = styled(Card)`
|
||||
padding: 0;
|
||||
`;
|
||||
@@ -0,0 +1,73 @@
|
||||
import { fireEvent, render, screen } from '@testing-library/react';
|
||||
import ROUTES from 'constants/routes';
|
||||
import MockQueryClientProvider from 'providers/test/MockQueryClientProvider';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
import { viewMockData } from '../__mock__/viewData';
|
||||
import ExplorerCard from '../ExplorerCard';
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
...jest.requireActual('react-router-dom'),
|
||||
useLocation: (): { pathname: string } => ({
|
||||
pathname: `${process.env.FRONTEND_API_ENDPOINT}/${ROUTES.TRACES_EXPLORER}/`,
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock('hooks/queryBuilder/useGetPanelTypesQueryParam', () => ({
|
||||
useGetPanelTypesQueryParam: jest.fn(() => 'mockedPanelType'),
|
||||
}));
|
||||
|
||||
jest.mock('hooks/saveViews/useGetAllViews', () => ({
|
||||
useGetAllViews: jest.fn(() => ({
|
||||
data: { data: { data: viewMockData } },
|
||||
isLoading: false,
|
||||
error: null,
|
||||
isRefetching: false,
|
||||
refetch: jest.fn(),
|
||||
})),
|
||||
}));
|
||||
|
||||
jest.mock('hooks/saveViews/useUpdateView', () => ({
|
||||
useUpdateView: jest.fn(() => ({
|
||||
mutateAsync: jest.fn(),
|
||||
})),
|
||||
}));
|
||||
|
||||
jest.mock('hooks/saveViews/useDeleteView', () => ({
|
||||
useDeleteView: jest.fn(() => ({
|
||||
mutateAsync: jest.fn(),
|
||||
})),
|
||||
}));
|
||||
|
||||
describe('ExplorerCard', () => {
|
||||
it('renders a card with a title and a description', () => {
|
||||
render(
|
||||
<MockQueryClientProvider>
|
||||
<ExplorerCard sourcepage={DataSource.TRACES}>child</ExplorerCard>
|
||||
</MockQueryClientProvider>,
|
||||
);
|
||||
expect(screen.getByText('Query Builder')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders a save view button', () => {
|
||||
render(
|
||||
<MockQueryClientProvider>
|
||||
<ExplorerCard sourcepage={DataSource.TRACES}>child</ExplorerCard>
|
||||
</MockQueryClientProvider>,
|
||||
);
|
||||
expect(screen.getByText('Save view')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should see all the view listed in dropdown', async () => {
|
||||
const screen = render(
|
||||
<ExplorerCard sourcepage={DataSource.TRACES}>Mock Children</ExplorerCard>,
|
||||
);
|
||||
const selectPlaceholder = screen.getByText('Select a view');
|
||||
|
||||
fireEvent.mouseDown(selectPlaceholder);
|
||||
const viewNameText = await screen.getAllByText('View 1');
|
||||
viewNameText.forEach((element) => {
|
||||
expect(element).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,62 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import ROUTES from 'constants/routes';
|
||||
import MockQueryClientProvider from 'providers/test/MockQueryClientProvider';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
import { viewMockData } from '../__mock__/viewData';
|
||||
import MenuItemGenerator from '../MenuItemGenerator';
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
...jest.requireActual('react-router-dom'),
|
||||
useLocation: (): { pathname: string } => ({
|
||||
pathname: `${process.env.FRONTEND_API_ENDPOINT}${ROUTES.APPLICATION}/`,
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock('antd/es/form/Form', () => ({
|
||||
useForm: jest.fn().mockReturnValue({
|
||||
onFinish: jest.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('MenuItemGenerator', () => {
|
||||
it('should render MenuItemGenerator component', () => {
|
||||
const screen = render(
|
||||
<MockQueryClientProvider>
|
||||
<MenuItemGenerator
|
||||
viewName={viewMockData[0].name}
|
||||
viewKey={viewMockData[0].uuid}
|
||||
createdBy={viewMockData[0].createdBy}
|
||||
uuid={viewMockData[0].uuid}
|
||||
refetchAllView={jest.fn()}
|
||||
viewData={viewMockData}
|
||||
sourcePage={DataSource.TRACES}
|
||||
/>
|
||||
</MockQueryClientProvider>,
|
||||
);
|
||||
|
||||
expect(screen.getByText(viewMockData[0].name)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should call onMenuItemSelectHandler on click of MenuItemGenerator', () => {
|
||||
render(
|
||||
<MockQueryClientProvider>
|
||||
<MenuItemGenerator
|
||||
viewName={viewMockData[0].name}
|
||||
viewKey={viewMockData[0].uuid}
|
||||
createdBy={viewMockData[0].createdBy}
|
||||
uuid={viewMockData[0].uuid}
|
||||
refetchAllView={jest.fn()}
|
||||
viewData={viewMockData}
|
||||
sourcePage={DataSource.TRACES}
|
||||
/>
|
||||
</MockQueryClientProvider>,
|
||||
);
|
||||
|
||||
const spanElement = screen.getByRole('img', {
|
||||
name: 'delete',
|
||||
});
|
||||
|
||||
expect(spanElement).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,63 @@
|
||||
import { fireEvent, render } from '@testing-library/react';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { QueryClient, QueryClientProvider } from 'react-query';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
import SaveViewWithName from '../SaveViewWithName';
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
...jest.requireActual('react-router-dom'),
|
||||
useLocation: (): { pathname: string } => ({
|
||||
pathname: `${process.env.FRONTEND_API_ENDPOINT}${ROUTES.APPLICATION}/`,
|
||||
}),
|
||||
}));
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
refetchOnWindowFocus: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
jest.mock('hooks/queryBuilder/useGetPanelTypesQueryParam', () => ({
|
||||
useGetPanelTypesQueryParam: jest.fn(() => 'mockedPanelType'),
|
||||
}));
|
||||
|
||||
jest.mock('hooks/saveViews/useSaveView', () => ({
|
||||
useSaveView: jest.fn(() => ({
|
||||
mutateAsync: jest.fn(),
|
||||
})),
|
||||
}));
|
||||
|
||||
describe('SaveViewWithName', () => {
|
||||
it('should render SaveViewWithName component', () => {
|
||||
const screen = render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<SaveViewWithName
|
||||
sourcePage={DataSource.TRACES}
|
||||
handlePopOverClose={jest.fn()}
|
||||
refetchAllView={jest.fn()}
|
||||
/>
|
||||
</QueryClientProvider>,
|
||||
);
|
||||
|
||||
expect(screen.getByText('Save')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should call saveViewAsync on click of Save button', () => {
|
||||
const screen = render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<SaveViewWithName
|
||||
sourcePage={DataSource.TRACES}
|
||||
handlePopOverClose={jest.fn()}
|
||||
refetchAllView={jest.fn()}
|
||||
/>
|
||||
</QueryClientProvider>,
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByText('Save'));
|
||||
|
||||
expect(screen.getByText('Save')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
84
frontend/src/components/ExplorerCard/types.ts
Normal file
84
frontend/src/components/ExplorerCard/types.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
import { FormInstance } from 'antd';
|
||||
import { NotificationInstance } from 'antd/es/notification/interface';
|
||||
import { AxiosResponse } from 'axios';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { UseMutateAsyncFunction } from 'react-query';
|
||||
import { ICompositeMetricQuery } from 'types/api/alerts/compositeQuery';
|
||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import {
|
||||
DeleteViewPayloadProps,
|
||||
SaveViewPayloadProps,
|
||||
SaveViewProps,
|
||||
ViewProps,
|
||||
} from 'types/api/saveViews/types';
|
||||
import { DataSource, QueryBuilderContextType } from 'types/common/queryBuilder';
|
||||
|
||||
export interface ExplorerCardProps {
|
||||
sourcepage: DataSource;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export type GetViewDetailsUsingViewKey = (
|
||||
viewKey: string,
|
||||
data: ViewProps[] | undefined,
|
||||
) =>
|
||||
| { query: Query; name: string; uuid: string; panelType: PANEL_TYPES }
|
||||
| undefined;
|
||||
|
||||
export interface IsQueryUpdatedInViewProps {
|
||||
viewKey: string;
|
||||
data: ViewProps[] | undefined;
|
||||
stagedQuery: Query | null;
|
||||
currentPanelType: PANEL_TYPES | null;
|
||||
}
|
||||
|
||||
export interface SaveViewWithNameProps {
|
||||
sourcePage: ExplorerCardProps['sourcepage'];
|
||||
handlePopOverClose: VoidFunction;
|
||||
refetchAllView: VoidFunction;
|
||||
}
|
||||
|
||||
export interface SaveViewFormProps {
|
||||
viewName: string;
|
||||
}
|
||||
|
||||
export interface MenuItemLabelGeneratorProps {
|
||||
viewName: string;
|
||||
viewKey: string;
|
||||
createdBy: string;
|
||||
uuid: string;
|
||||
viewData: ViewProps[];
|
||||
refetchAllView: VoidFunction;
|
||||
sourcePage: ExplorerCardProps['sourcepage'];
|
||||
}
|
||||
|
||||
export interface SaveViewHandlerProps {
|
||||
viewName: string;
|
||||
compositeQuery: ICompositeMetricQuery;
|
||||
sourcePage: ExplorerCardProps['sourcepage'];
|
||||
extraData: string;
|
||||
panelType: PANEL_TYPES | null;
|
||||
notifications: NotificationInstance;
|
||||
refetchAllView: SaveViewWithNameProps['refetchAllView'];
|
||||
saveViewAsync: UseMutateAsyncFunction<
|
||||
AxiosResponse<SaveViewPayloadProps>,
|
||||
Error,
|
||||
SaveViewProps,
|
||||
SaveViewPayloadProps
|
||||
>;
|
||||
handlePopOverClose: SaveViewWithNameProps['handlePopOverClose'];
|
||||
redirectWithQueryBuilderData: QueryBuilderContextType['redirectWithQueryBuilderData'];
|
||||
form: FormInstance<SaveViewFormProps>;
|
||||
}
|
||||
|
||||
export interface DeleteViewHandlerProps {
|
||||
deleteViewAsync: UseMutateAsyncFunction<DeleteViewPayloadProps, Error, string>;
|
||||
refetchAllView: MenuItemLabelGeneratorProps['refetchAllView'];
|
||||
redirectWithQueryBuilderData: QueryBuilderContextType['redirectWithQueryBuilderData'];
|
||||
notifications: NotificationInstance;
|
||||
panelType: PANEL_TYPES | null;
|
||||
viewKey: string;
|
||||
viewId: string;
|
||||
updateAllQueriesOperators: QueryBuilderContextType['updateAllQueriesOperators'];
|
||||
sourcePage: ExplorerCardProps['sourcepage'];
|
||||
}
|
||||
183
frontend/src/components/ExplorerCard/utils.ts
Normal file
183
frontend/src/components/ExplorerCard/utils.ts
Normal file
@@ -0,0 +1,183 @@
|
||||
import { NotificationInstance } from 'antd/es/notification/interface';
|
||||
import axios from 'axios';
|
||||
import { SOMETHING_WENT_WRONG } from 'constants/api';
|
||||
import { QueryParams } from 'constants/query';
|
||||
import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { mapQueryDataFromApi } from 'lib/newQueryBuilder/queryBuilderMappers/mapQueryDataFromApi';
|
||||
import isEqual from 'lodash-es/isEqual';
|
||||
|
||||
import {
|
||||
DeleteViewHandlerProps,
|
||||
GetViewDetailsUsingViewKey,
|
||||
IsQueryUpdatedInViewProps,
|
||||
SaveViewHandlerProps,
|
||||
} from './types';
|
||||
|
||||
const showErrorNotification = (
|
||||
notifications: NotificationInstance,
|
||||
err: Error,
|
||||
): void => {
|
||||
notifications.error({
|
||||
message: axios.isAxiosError(err) ? err.message : SOMETHING_WENT_WRONG,
|
||||
});
|
||||
};
|
||||
|
||||
export const getViewDetailsUsingViewKey: GetViewDetailsUsingViewKey = (
|
||||
viewKey,
|
||||
data,
|
||||
) => {
|
||||
const selectedView = data?.find((view) => view.uuid === viewKey);
|
||||
if (selectedView) {
|
||||
const { compositeQuery, name, uuid } = selectedView;
|
||||
const query = mapQueryDataFromApi(compositeQuery);
|
||||
return { query, name, uuid, panelType: compositeQuery.panelType };
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
export const isQueryUpdatedInView = ({
|
||||
viewKey,
|
||||
data,
|
||||
stagedQuery,
|
||||
currentPanelType,
|
||||
}: IsQueryUpdatedInViewProps): boolean => {
|
||||
const currentViewDetails = getViewDetailsUsingViewKey(viewKey, data);
|
||||
if (!currentViewDetails) {
|
||||
return false;
|
||||
}
|
||||
const { query, panelType } = currentViewDetails;
|
||||
|
||||
// Omitting id from aggregateAttribute and groupBy
|
||||
const updatedCurrentQuery = {
|
||||
...stagedQuery,
|
||||
builder: {
|
||||
...stagedQuery?.builder,
|
||||
queryData: stagedQuery?.builder.queryData.map((queryData) => {
|
||||
const { id, ...rest } = queryData.aggregateAttribute;
|
||||
const newAggregateAttribute = rest;
|
||||
const newGroupByAttributes = queryData.groupBy.map((groupByAttribute) => {
|
||||
const { id, ...rest } = groupByAttribute;
|
||||
return rest;
|
||||
});
|
||||
const newItems = queryData.filters.items.map((item) => {
|
||||
const { id, ...newItem } = item;
|
||||
if (item.key) {
|
||||
const { id, ...rest } = item.key;
|
||||
return {
|
||||
...newItem,
|
||||
key: rest,
|
||||
};
|
||||
}
|
||||
return newItem;
|
||||
});
|
||||
return {
|
||||
...queryData,
|
||||
aggregateAttribute: newAggregateAttribute,
|
||||
groupBy: newGroupByAttributes,
|
||||
filters: {
|
||||
...queryData.filters,
|
||||
items: newItems,
|
||||
},
|
||||
limit: queryData.limit ? queryData.limit : 0,
|
||||
offset: queryData.offset ? queryData.offset : 0,
|
||||
pageSize: queryData.pageSize ? queryData.pageSize : 0,
|
||||
};
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
panelType !== currentPanelType ||
|
||||
!isEqual(query.builder, updatedCurrentQuery?.builder) ||
|
||||
!isEqual(query.clickhouse_sql, updatedCurrentQuery?.clickhouse_sql) ||
|
||||
!isEqual(query.promql, updatedCurrentQuery?.promql)
|
||||
);
|
||||
};
|
||||
|
||||
export const saveViewHandler = ({
|
||||
saveViewAsync,
|
||||
refetchAllView,
|
||||
notifications,
|
||||
handlePopOverClose,
|
||||
viewName,
|
||||
compositeQuery,
|
||||
sourcePage,
|
||||
extraData,
|
||||
redirectWithQueryBuilderData,
|
||||
panelType,
|
||||
form,
|
||||
}: SaveViewHandlerProps): void => {
|
||||
saveViewAsync(
|
||||
{
|
||||
viewName,
|
||||
compositeQuery,
|
||||
sourcePage,
|
||||
extraData,
|
||||
},
|
||||
{
|
||||
onSuccess: (data) => {
|
||||
refetchAllView();
|
||||
redirectWithQueryBuilderData(mapQueryDataFromApi(compositeQuery), {
|
||||
[QueryParams.panelTypes]: panelType,
|
||||
[QueryParams.viewName]: viewName,
|
||||
[QueryParams.viewKey]: data.data.data,
|
||||
});
|
||||
notifications.success({
|
||||
message: 'View Saved Successfully',
|
||||
});
|
||||
},
|
||||
onError: (err) => {
|
||||
showErrorNotification(notifications, err);
|
||||
},
|
||||
onSettled: () => {
|
||||
handlePopOverClose();
|
||||
form.resetFields();
|
||||
},
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
export const deleteViewHandler = ({
|
||||
deleteViewAsync,
|
||||
refetchAllView,
|
||||
redirectWithQueryBuilderData,
|
||||
notifications,
|
||||
panelType,
|
||||
viewKey,
|
||||
viewId,
|
||||
updateAllQueriesOperators,
|
||||
sourcePage,
|
||||
}: DeleteViewHandlerProps): void => {
|
||||
deleteViewAsync(viewKey, {
|
||||
onSuccess: () => {
|
||||
if (viewId === viewKey) {
|
||||
redirectWithQueryBuilderData(
|
||||
updateAllQueriesOperators(
|
||||
initialQueriesMap.traces,
|
||||
panelType || PANEL_TYPES.LIST,
|
||||
sourcePage,
|
||||
),
|
||||
{
|
||||
[QueryParams.viewName]: '',
|
||||
[QueryParams.panelTypes]: panelType,
|
||||
[QueryParams.viewKey]: '',
|
||||
},
|
||||
);
|
||||
}
|
||||
notifications.success({
|
||||
message: 'View Deleted Successfully',
|
||||
});
|
||||
refetchAllView();
|
||||
},
|
||||
onError: (err) => {
|
||||
showErrorNotification(notifications, err);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const trimViewName = (viewName: string): string => {
|
||||
if (viewName.length > 20) {
|
||||
return `${viewName.substring(0, 20)}...`;
|
||||
}
|
||||
return viewName;
|
||||
};
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Chart, ChartTypeRegistry, Plugin } from 'chart.js';
|
||||
import * as ChartHelpers from 'chart.js/helpers';
|
||||
import { getRelativePosition } from 'chart.js/helpers';
|
||||
|
||||
// utils
|
||||
import { ChartEventHandler, mergeDefaultOptions } from './utils';
|
||||
@@ -45,7 +45,7 @@ function createMousedownHandler(
|
||||
return (ev): void => {
|
||||
const { left, right } = chart.chartArea;
|
||||
|
||||
let { x: startDragPositionX } = ChartHelpers.getRelativePosition(ev, chart);
|
||||
let { x: startDragPositionX } = getRelativePosition(ev, chart);
|
||||
|
||||
if (left > startDragPositionX) {
|
||||
startDragPositionX = left;
|
||||
@@ -74,7 +74,7 @@ function createMousemoveHandler(
|
||||
|
||||
const { left, right } = chart.chartArea;
|
||||
|
||||
let { x: dragPositionX } = ChartHelpers.getRelativePosition(ev, chart);
|
||||
let { x: dragPositionX } = getRelativePosition(ev, chart);
|
||||
|
||||
if (left > dragPositionX) {
|
||||
dragPositionX = left;
|
||||
@@ -99,7 +99,7 @@ function createMouseupHandler(
|
||||
return (ev): void => {
|
||||
const { left, right } = chart.chartArea;
|
||||
|
||||
let { x: endRelativePostionX } = ChartHelpers.getRelativePosition(ev, chart);
|
||||
let { x: endRelativePostionX } = getRelativePosition(ev, chart);
|
||||
|
||||
if (left > endRelativePostionX) {
|
||||
endRelativePostionX = left;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Chart, ChartEvent, ChartTypeRegistry, Plugin } from 'chart.js';
|
||||
import * as ChartHelpers from 'chart.js/helpers';
|
||||
import { getRelativePosition } from 'chart.js/helpers';
|
||||
|
||||
// utils
|
||||
import { ChartEventHandler, mergeDefaultOptions } from './utils';
|
||||
@@ -42,7 +42,7 @@ function createMousemoveHandler(
|
||||
return (ev: ChartEvent | MouseEvent): void => {
|
||||
const { left, right, top, bottom } = chart.chartArea;
|
||||
|
||||
let { x, y } = ChartHelpers.getRelativePosition(ev, chart);
|
||||
let { x, y } = getRelativePosition(ev, chart);
|
||||
|
||||
if (left > x) {
|
||||
x = left;
|
||||
|
||||
@@ -21,8 +21,6 @@ import {
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react';
|
||||
// interfaces
|
||||
import { ILog } from 'types/api/logs/log';
|
||||
|
||||
// styles
|
||||
import {
|
||||
@@ -31,19 +29,17 @@ import {
|
||||
RawLogContent,
|
||||
RawLogViewContainer,
|
||||
} from './styles';
|
||||
import { RawLogViewProps } from './types';
|
||||
|
||||
const convert = new Convert();
|
||||
|
||||
interface RawLogViewProps {
|
||||
isActiveLog?: boolean;
|
||||
isReadOnly?: boolean;
|
||||
data: ILog;
|
||||
linesPerRow: number;
|
||||
}
|
||||
|
||||
function RawLogView(props: RawLogViewProps): JSX.Element {
|
||||
const { isActiveLog = false, isReadOnly = false, data, linesPerRow } = props;
|
||||
|
||||
function RawLogView({
|
||||
isActiveLog,
|
||||
isReadOnly,
|
||||
data,
|
||||
linesPerRow,
|
||||
isTextOverflowEllipsisDisabled,
|
||||
}: RawLogViewProps): JSX.Element {
|
||||
const { isHighlighted, isLogsExplorerPage, onLogCopy } = useCopyLogLink(
|
||||
data.id,
|
||||
);
|
||||
@@ -64,12 +60,14 @@ function RawLogView(props: RawLogViewProps): JSX.Element {
|
||||
const isDarkMode = useIsDarkMode();
|
||||
const isReadOnlyLog = !isLogsExplorerPage || isReadOnly;
|
||||
|
||||
const severityText = data.severity_text ? `${data.severity_text} |` : '';
|
||||
|
||||
const text = useMemo(
|
||||
() =>
|
||||
typeof data.timestamp === 'string'
|
||||
? `${dayjs(data.timestamp).format()} | ${data.body}`
|
||||
: `${dayjs(data.timestamp / 1e6).format()} | ${data.body}`,
|
||||
[data.timestamp, data.body],
|
||||
? `${dayjs(data.timestamp).format()} | ${severityText} ${data.body}`
|
||||
: `${dayjs(data.timestamp / 1e6).format()} | ${severityText} ${data.body}`,
|
||||
[data.timestamp, data.body, severityText],
|
||||
);
|
||||
|
||||
const handleClickExpand = useCallback(() => {
|
||||
@@ -118,11 +116,6 @@ function RawLogView(props: RawLogViewProps): JSX.Element {
|
||||
[text],
|
||||
);
|
||||
|
||||
const mouseActions = useMemo(
|
||||
() => ({ onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave }),
|
||||
[handleMouseEnter, handleMouseLeave],
|
||||
);
|
||||
|
||||
return (
|
||||
<RawLogViewContainer
|
||||
onClick={handleClickExpand}
|
||||
@@ -131,8 +124,8 @@ function RawLogView(props: RawLogViewProps): JSX.Element {
|
||||
$isDarkMode={isDarkMode}
|
||||
$isReadOnly={isReadOnly}
|
||||
$isActiveLog={isHighlighted}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...mouseActions}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
>
|
||||
{!isReadOnly && (
|
||||
<ExpandIconWrapper flex="30px">
|
||||
@@ -143,6 +136,7 @@ function RawLogView(props: RawLogViewProps): JSX.Element {
|
||||
<RawLogContent
|
||||
$isReadOnly={isReadOnly}
|
||||
$isActiveLog={isActiveLog}
|
||||
$isTextOverflowEllipsisDisabled={isTextOverflowEllipsisDisabled}
|
||||
linesPerRow={linesPerRow}
|
||||
dangerouslySetInnerHTML={html}
|
||||
/>
|
||||
@@ -181,6 +175,7 @@ function RawLogView(props: RawLogViewProps): JSX.Element {
|
||||
RawLogView.defaultProps = {
|
||||
isActiveLog: false,
|
||||
isReadOnly: false,
|
||||
isTextOverflowEllipsisDisabled: false,
|
||||
};
|
||||
|
||||
export default RawLogView;
|
||||
|
||||
@@ -3,10 +3,12 @@ import { Col, Row, Space } from 'antd';
|
||||
import styled from 'styled-components';
|
||||
import { getActiveLogBackground, getDefaultLogBackground } from 'utils/logs';
|
||||
|
||||
import { RawLogContentProps } from './types';
|
||||
|
||||
export const RawLogViewContainer = styled(Row)<{
|
||||
$isDarkMode: boolean;
|
||||
$isReadOnly: boolean;
|
||||
$isActiveLog: boolean;
|
||||
$isReadOnly?: boolean;
|
||||
$isActiveLog?: boolean;
|
||||
}>`
|
||||
position: relative;
|
||||
width: 100%;
|
||||
@@ -31,32 +33,29 @@ export const ExpandIconWrapper = styled(Col)`
|
||||
font-size: 12px;
|
||||
`;
|
||||
|
||||
interface RawLogContentProps {
|
||||
linesPerRow: number;
|
||||
$isReadOnly: boolean;
|
||||
$isActiveLog: boolean;
|
||||
}
|
||||
|
||||
export const RawLogContent = styled.div<RawLogContentProps>`
|
||||
margin-bottom: 0;
|
||||
font-family: Fira Code, monospace;
|
||||
font-weight: 300;
|
||||
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: ${(props): number => props.linesPerRow};
|
||||
line-clamp: ${(props): number => props.linesPerRow};
|
||||
-webkit-box-orient: vertical;
|
||||
${({ $isTextOverflowEllipsisDisabled, linesPerRow }): string =>
|
||||
$isTextOverflowEllipsisDisabled
|
||||
? 'white-space: nowrap'
|
||||
: `overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: ${linesPerRow};
|
||||
line-clamp: ${linesPerRow};
|
||||
-webkit-box-orient: vertical;`};
|
||||
|
||||
font-size: 1rem;
|
||||
line-height: 2rem;
|
||||
|
||||
cursor: ${(props): string =>
|
||||
props.$isActiveLog || props.$isReadOnly ? 'initial' : 'pointer'};
|
||||
cursor: ${({ $isActiveLog, $isReadOnly }): string =>
|
||||
$isActiveLog || $isReadOnly ? 'initial' : 'pointer'};
|
||||
|
||||
${(props): string =>
|
||||
props.$isReadOnly && !props.$isActiveLog ? 'padding: 0 1.5rem;' : ''}
|
||||
${({ $isActiveLog, $isReadOnly }): string =>
|
||||
$isReadOnly && $isActiveLog ? 'padding: 0 1.5rem;' : ''}
|
||||
`;
|
||||
|
||||
export const ActionButtonsWrapper = styled(Space)`
|
||||
|
||||
16
frontend/src/components/Logs/RawLogView/types.ts
Normal file
16
frontend/src/components/Logs/RawLogView/types.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { ILog } from 'types/api/logs/log';
|
||||
|
||||
export interface RawLogViewProps {
|
||||
isActiveLog?: boolean;
|
||||
isReadOnly?: boolean;
|
||||
isTextOverflowEllipsisDisabled?: boolean;
|
||||
data: ILog;
|
||||
linesPerRow: number;
|
||||
}
|
||||
|
||||
export interface RawLogContentProps {
|
||||
linesPerRow: number;
|
||||
$isReadOnly?: boolean;
|
||||
$isActiveLog?: boolean;
|
||||
$isTextOverflowEllipsisDisabled?: boolean;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
.code-snippet-container {
|
||||
position: relative;
|
||||
background-color: rgb(43, 43, 43);
|
||||
}
|
||||
|
||||
.code-copy-btn {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
|
||||
button {
|
||||
cursor: pointer;
|
||||
|
||||
background-color: rgba($color: #1d1d1d, $alpha: 0.7);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 8px;
|
||||
border-radius: 3px;
|
||||
transition: all 0.1s;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba($color: #1d1d1d, $alpha: 1);
|
||||
}
|
||||
}
|
||||
|
||||
&.copied {
|
||||
button {
|
||||
background-color: rgba($color: #52c41a, $alpha: 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import './CodeCopyBtn.scss';
|
||||
|
||||
import { CheckOutlined, CopyOutlined } from '@ant-design/icons';
|
||||
import cx from 'classnames';
|
||||
import { useState } from 'react';
|
||||
|
||||
export default function CodeCopyBtn({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}): JSX.Element {
|
||||
const [isSnippetCopied, setIsSnippetCopied] = useState(false);
|
||||
|
||||
const handleClick = (): void => {
|
||||
if (children && Array.isArray(children)) {
|
||||
setIsSnippetCopied(true);
|
||||
navigator.clipboard.writeText(children[0].props.children[0]).finally(() => {
|
||||
setTimeout(() => {
|
||||
setIsSnippetCopied(false);
|
||||
}, 1000);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={cx('code-copy-btn', isSnippetCopied ? 'copied' : '')}>
|
||||
<button type="button" onClick={handleClick}>
|
||||
{!isSnippetCopied ? <CopyOutlined /> : <CheckOutlined />}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/* eslint-disable react/jsx-props-no-spreading */
|
||||
import { CodeProps } from 'react-markdown/lib/ast-to-react';
|
||||
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
|
||||
import { a11yDark } from 'react-syntax-highlighter/dist/cjs/styles/prism';
|
||||
|
||||
import CodeCopyBtn from './CodeCopyBtn/CodeCopyBtn';
|
||||
|
||||
function Pre({ children }: { children: React.ReactNode }): JSX.Element {
|
||||
return (
|
||||
<pre className="code-snippet-container">
|
||||
<CodeCopyBtn>{children}</CodeCopyBtn>
|
||||
{children}
|
||||
</pre>
|
||||
);
|
||||
}
|
||||
|
||||
function Code({
|
||||
node,
|
||||
inline,
|
||||
className = 'blog-code',
|
||||
children,
|
||||
...props
|
||||
}: CodeProps): JSX.Element {
|
||||
const match = /language-(\w+)/.exec(className || '');
|
||||
return !inline && match ? (
|
||||
<SyntaxHighlighter
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
style={a11yDark}
|
||||
language={match[1]}
|
||||
PreTag="div"
|
||||
{...props}
|
||||
>
|
||||
{String(children).replace(/\n$/, '')}
|
||||
</SyntaxHighlighter>
|
||||
) : (
|
||||
<code className={className} {...props}>
|
||||
{children}
|
||||
</code>
|
||||
);
|
||||
}
|
||||
|
||||
export { Code, Pre };
|
||||
@@ -1,4 +1,17 @@
|
||||
import * as AntD from 'antd';
|
||||
import {
|
||||
Button,
|
||||
ButtonProps,
|
||||
Col,
|
||||
ColProps,
|
||||
Divider,
|
||||
DividerProps,
|
||||
Row,
|
||||
RowProps,
|
||||
Space,
|
||||
SpaceProps,
|
||||
TabsProps,
|
||||
Typography,
|
||||
} from 'antd';
|
||||
import { TextProps } from 'antd/lib/typography/Text';
|
||||
import { TitleProps } from 'antd/lib/typography/Title';
|
||||
import { HTMLAttributes } from 'react';
|
||||
@@ -9,43 +22,43 @@ import { IStyledClass } from './types';
|
||||
const styledClass = (props: IStyledClass): FlattenSimpleInterpolation | null =>
|
||||
props.styledclass || null;
|
||||
|
||||
type TStyledCol = AntD.ColProps & IStyledClass;
|
||||
const StyledCol = styled(AntD.Col)<TStyledCol>`
|
||||
type TStyledCol = ColProps & IStyledClass;
|
||||
const StyledCol = styled(Col)<TStyledCol>`
|
||||
${styledClass}
|
||||
`;
|
||||
|
||||
type TStyledRow = AntD.RowProps & IStyledClass;
|
||||
const StyledRow = styled(AntD.Row)<TStyledRow>`
|
||||
type TStyledRow = RowProps & IStyledClass;
|
||||
const StyledRow = styled(Row)<TStyledRow>`
|
||||
${styledClass}
|
||||
`;
|
||||
|
||||
type TStyledDivider = AntD.DividerProps & IStyledClass;
|
||||
const StyledDivider = styled(AntD.Divider)<TStyledDivider>`
|
||||
type TStyledDivider = DividerProps & IStyledClass;
|
||||
const StyledDivider = styled(Divider)<TStyledDivider>`
|
||||
${styledClass}
|
||||
`;
|
||||
|
||||
type TStyledSpace = AntD.SpaceProps & IStyledClass;
|
||||
const StyledSpace = styled(AntD.Space)<TStyledSpace>`
|
||||
type TStyledSpace = SpaceProps & IStyledClass;
|
||||
const StyledSpace = styled(Space)<TStyledSpace>`
|
||||
${styledClass}
|
||||
`;
|
||||
|
||||
type TStyledTabs = AntD.TabsProps & IStyledClass;
|
||||
const StyledTabs = styled(AntD.Divider)<TStyledTabs>`
|
||||
type TStyledTabs = TabsProps & IStyledClass;
|
||||
const StyledTabs = styled(Divider)<TStyledTabs>`
|
||||
${styledClass}
|
||||
`;
|
||||
|
||||
type TStyledButton = AntD.ButtonProps & IStyledClass;
|
||||
const StyledButton = styled(AntD.Button)<TStyledButton>`
|
||||
type TStyledButton = ButtonProps & IStyledClass;
|
||||
const StyledButton = styled(Button)<TStyledButton>`
|
||||
${styledClass}
|
||||
`;
|
||||
|
||||
const { Text } = AntD.Typography;
|
||||
const { Text } = Typography;
|
||||
type TStyledTypographyText = TextProps & IStyledClass;
|
||||
const StyledTypographyText = styled(Text)<TStyledTypographyText>`
|
||||
${styledClass}
|
||||
`;
|
||||
|
||||
const { Title } = AntD.Typography;
|
||||
const { Title } = Typography;
|
||||
type TStyledTypographyTitle = TitleProps & IStyledClass;
|
||||
const StyledTypographyTitle = styled(Title)<TStyledTypographyTitle>`
|
||||
${styledClass}
|
||||
|
||||
@@ -7,6 +7,7 @@ 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';
|
||||
|
||||
@@ -18,12 +19,24 @@ function TextToolTip({
|
||||
}: TextToolTipProps): JSX.Element {
|
||||
const isDarkMode = useIsDarkMode();
|
||||
|
||||
const onClickHandler = (
|
||||
event: React.MouseEvent<HTMLAnchorElement, MouseEvent>,
|
||||
): void => {
|
||||
event.stopPropagation();
|
||||
};
|
||||
|
||||
const overlay = useMemo(
|
||||
() => (
|
||||
<div>
|
||||
{`${text} `}
|
||||
{url && (
|
||||
<a href={url} rel="noopener noreferrer" target="_blank">
|
||||
<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>
|
||||
)}
|
||||
@@ -43,13 +56,13 @@ function TextToolTip({
|
||||
const iconOutlinedStyle = useMemo(
|
||||
() => ({
|
||||
...style,
|
||||
color: isDarkMode ? themeColors.navyBlue : blue[0],
|
||||
color: isDarkMode ? themeColors.navyBlue : blue[6],
|
||||
}),
|
||||
[isDarkMode],
|
||||
);
|
||||
|
||||
return (
|
||||
<Tooltip overlay={overlay}>
|
||||
<Tooltip getTooltipContainer={popupContainer} overlay={overlay}>
|
||||
{useFilledIcon ? (
|
||||
<QuestionCircleFilled style={iconStyle} />
|
||||
) : (
|
||||
|
||||
4
frontend/src/constants/card.ts
Normal file
4
frontend/src/constants/card.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export const CARD_BODY_STYLE = {
|
||||
padding: '0',
|
||||
height: '100%',
|
||||
};
|
||||
@@ -6,6 +6,7 @@ export enum FeatureKeys {
|
||||
ALERT_CHANNEL_SLACK = 'ALERT_CHANNEL_SLACK',
|
||||
ALERT_CHANNEL_WEBHOOK = 'ALERT_CHANNEL_WEBHOOK',
|
||||
ALERT_CHANNEL_PAGERDUTY = 'ALERT_CHANNEL_PAGERDUTY',
|
||||
ALERT_CHANNEL_OPSGENIE = 'ALERT_CHANNEL_OPSGENIE',
|
||||
ALERT_CHANNEL_MSTEAMS = 'ALERT_CHANNEL_MSTEAMS',
|
||||
DurationSort = 'DurationSort',
|
||||
TimestampSort = 'TimestampSort',
|
||||
@@ -16,4 +17,6 @@ export enum FeatureKeys {
|
||||
DISABLE_UPSELL = 'DISABLE_UPSELL',
|
||||
USE_SPAN_METRICS = 'USE_SPAN_METRICS',
|
||||
OSS = 'OSS',
|
||||
ONBOARDING = 'ONBOARDING',
|
||||
CHAT_SUPPORT = 'CHAT_SUPPORT',
|
||||
}
|
||||
|
||||
5
frontend/src/constants/liveTail.ts
Normal file
5
frontend/src/constants/liveTail.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export const LIVE_TAIL_HEARTBEAT_TIMEOUT = 600000;
|
||||
|
||||
export const LIVE_TAIL_GRAPH_INTERVAL = 60000;
|
||||
|
||||
export const MAX_LOGS_LIST_SIZE = 1000;
|
||||
@@ -11,4 +11,8 @@ export enum LOCALSTORAGE {
|
||||
GRAPH_VISIBILITY_STATES = 'GRAPH_VISIBILITY_STATES',
|
||||
TRACES_LIST_COLUMNS = 'TRACES_LIST_COLUMNS',
|
||||
LOGS_LIST_COLUMNS = 'LOGS_LIST_COLUMNS',
|
||||
LOGGED_IN_USER_NAME = 'LOGGED_IN_USER_NAME',
|
||||
LOGGED_IN_USER_EMAIL = 'LOGGED_IN_USER_EMAIL',
|
||||
CHAT_SUPPORT = 'CHAT_SUPPORT',
|
||||
IS_IDENTIFIED_USER = 'IS_IDENTIFIED_USER',
|
||||
}
|
||||
|
||||
5
frontend/src/constants/optionsFormatTypes.ts
Normal file
5
frontend/src/constants/optionsFormatTypes.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export enum OptionFormatTypes {
|
||||
RAW = 'raw',
|
||||
LIST = 'list',
|
||||
TABLE = 'table',
|
||||
}
|
||||
@@ -12,3 +12,8 @@ export const PANEL_TYPES_COMPONENT_MAP = {
|
||||
[PANEL_TYPES.LIST]: null,
|
||||
[PANEL_TYPES.EMPTY_WIDGET]: null,
|
||||
} as const;
|
||||
|
||||
export const AVAILABLE_EXPORT_PANEL_TYPES = [
|
||||
PANEL_TYPES.TIME_SERIES,
|
||||
PANEL_TYPES.TABLE,
|
||||
];
|
||||
|
||||
@@ -18,4 +18,12 @@ export enum QueryParams {
|
||||
q = 'q',
|
||||
activeLogId = 'activeLogId',
|
||||
timeRange = 'timeRange',
|
||||
compositeQuery = 'compositeQuery',
|
||||
panelTypes = 'panelTypes',
|
||||
pageSize = 'pageSize',
|
||||
viewMode = 'viewMode',
|
||||
selectedFields = 'selectedFields',
|
||||
linesPerRow = 'linesPerRow',
|
||||
viewName = 'viewName',
|
||||
viewKey = 'viewKey',
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { createNewBuilderItemName } from 'lib/newQueryBuilder/createNewBuilderIt
|
||||
import {
|
||||
AutocompleteType,
|
||||
BaseAutocompleteData,
|
||||
DataTypes,
|
||||
LocalDataType,
|
||||
} from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import {
|
||||
@@ -46,12 +47,13 @@ export const selectValueDivider = '__';
|
||||
|
||||
export const baseAutoCompleteIdKeysOrder: (keyof Omit<
|
||||
BaseAutocompleteData,
|
||||
'id'
|
||||
'id' | 'isJSON'
|
||||
>)[] = ['key', 'dataType', 'type', 'isColumn'];
|
||||
|
||||
export const autocompleteType: Record<AutocompleteType, AutocompleteType> = {
|
||||
resource: 'resource',
|
||||
tag: 'tag',
|
||||
'': '',
|
||||
};
|
||||
|
||||
export const formulasNames: string[] = Array.from(
|
||||
@@ -72,7 +74,7 @@ export const mapOfOperators = {
|
||||
traces: tracesAggregateOperatorOptions,
|
||||
};
|
||||
|
||||
export const mapOfFilters: Record<DataSource, QueryAdditionalFilter[]> = {
|
||||
export const mapOfQueryFilters: Record<DataSource, QueryAdditionalFilter[]> = {
|
||||
metrics: [
|
||||
// eslint-disable-next-line sonarjs/no-duplicate-string
|
||||
{ text: 'Aggregation interval', field: 'stepInterval' },
|
||||
@@ -92,6 +94,24 @@ export const mapOfFilters: Record<DataSource, QueryAdditionalFilter[]> = {
|
||||
],
|
||||
};
|
||||
|
||||
const commonFormulaFilters: QueryAdditionalFilter[] = [
|
||||
{
|
||||
text: 'Having',
|
||||
field: 'having',
|
||||
},
|
||||
{ text: 'Order by', field: 'orderBy' },
|
||||
{ text: 'Limit', field: 'limit' },
|
||||
];
|
||||
|
||||
export const mapOfFormulaToFilters: Record<
|
||||
DataSource,
|
||||
QueryAdditionalFilter[]
|
||||
> = {
|
||||
metrics: commonFormulaFilters,
|
||||
logs: commonFormulaFilters,
|
||||
traces: commonFormulaFilters,
|
||||
};
|
||||
|
||||
export const REDUCE_TO_VALUES: SelectOption<ReduceOperators, string>[] = [
|
||||
{ value: 'last', label: 'Latest of values in timeframe' },
|
||||
{ value: 'sum', label: 'Sum of values in timeframe' },
|
||||
@@ -111,10 +131,11 @@ export const initialAutocompleteData: BaseAutocompleteData = {
|
||||
{ dataType: null, key: '', isColumn: null, type: null },
|
||||
baseAutoCompleteIdKeysOrder,
|
||||
),
|
||||
dataType: null,
|
||||
dataType: DataTypes.EMPTY,
|
||||
key: '',
|
||||
isColumn: null,
|
||||
type: null,
|
||||
isColumn: false,
|
||||
type: '',
|
||||
isJSON: false,
|
||||
};
|
||||
|
||||
export const initialFilters: TagFilter = {
|
||||
@@ -260,6 +281,8 @@ export const OPERATORS = {
|
||||
NIN: 'NOT_IN',
|
||||
LIKE: 'LIKE',
|
||||
NLIKE: 'NOT_LIKE',
|
||||
REGEX: 'REGEX',
|
||||
NREGEX: 'NOT_REGEX',
|
||||
'=': '=',
|
||||
'!=': '!=',
|
||||
EXISTS: 'EXISTS',
|
||||
@@ -270,6 +293,8 @@ export const OPERATORS = {
|
||||
'>': '>',
|
||||
'<=': '<=',
|
||||
'<': '<',
|
||||
HAS: 'HAS',
|
||||
NHAS: 'NHAS',
|
||||
};
|
||||
|
||||
export const QUERY_BUILDER_OPERATORS_BY_TYPES = {
|
||||
@@ -284,6 +309,8 @@ export const QUERY_BUILDER_OPERATORS_BY_TYPES = {
|
||||
OPERATORS.NOT_CONTAINS,
|
||||
OPERATORS.EXISTS,
|
||||
OPERATORS.NOT_EXISTS,
|
||||
OPERATORS.REGEX,
|
||||
OPERATORS.NREGEX,
|
||||
],
|
||||
int64: [
|
||||
OPERATORS['='],
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
type QueryParamNames =
|
||||
| 'compositeQuery'
|
||||
| 'panelTypes'
|
||||
| 'pageSize'
|
||||
| 'viewMode'
|
||||
| 'selectedFields'
|
||||
| 'linesPerRow';
|
||||
|
||||
export const queryParamNamesMap: Record<QueryParamNames, QueryParamNames> = {
|
||||
compositeQuery: 'compositeQuery',
|
||||
panelTypes: 'panelTypes',
|
||||
pageSize: 'pageSize',
|
||||
viewMode: 'viewMode',
|
||||
selectedFields: 'selectedFields',
|
||||
linesPerRow: 'linesPerRow',
|
||||
};
|
||||
@@ -2,4 +2,6 @@ export const REACT_QUERY_KEY = {
|
||||
GET_ALL_LICENCES: 'GET_ALL_LICENCES',
|
||||
GET_QUERY_RANGE: 'GET_QUERY_RANGE',
|
||||
GET_ALL_DASHBOARDS: 'GET_ALL_DASHBOARDS',
|
||||
GET_TRIGGERED_ALERTS: 'GET_TRIGGERED_ALERTS',
|
||||
GET_FEATURES_FLAGS: 'GET_FEATURES_FLAGS',
|
||||
};
|
||||
|
||||
@@ -7,7 +7,7 @@ const ROUTES = {
|
||||
TRACE_DETAIL: '/trace/:id',
|
||||
TRACES_EXPLORER: '/traces-explorer',
|
||||
SETTINGS: '/settings',
|
||||
INSTRUMENTATION: '/get-started',
|
||||
GET_STARTED: '/get-started',
|
||||
USAGE_EXPLORER: '/usage-explorer',
|
||||
APPLICATION: '/services',
|
||||
ALL_DASHBOARD: '/dashboard',
|
||||
@@ -29,6 +29,7 @@ const ROUTES = {
|
||||
NOT_FOUND: '/not-found',
|
||||
LOGS: '/logs',
|
||||
LOGS_EXPLORER: '/logs-explorer',
|
||||
LIVE_LOGS: '/logs-explorer/live',
|
||||
HOME_PAGE: '/',
|
||||
PASSWORD_RESET: '/password-reset',
|
||||
LIST_LICENSES: '/licenses',
|
||||
|
||||
@@ -52,6 +52,8 @@ const themeColors = {
|
||||
gamboge: '#D89614',
|
||||
bckgGrey: '#1d1d1d',
|
||||
lightBlue: '#177ddc',
|
||||
buttonSuccessRgb: '73, 170, 25',
|
||||
red: '#E84749',
|
||||
};
|
||||
|
||||
export { themeColors };
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import getDynamicConfigs from 'api/dynamicConfigs/getDynamicConfigs';
|
||||
import getFeaturesFlags from 'api/features/getFeatureFlags';
|
||||
import getUserLatestVersion from 'api/user/getLatestVersion';
|
||||
import getUserVersion from 'api/user/getVersion';
|
||||
import ROUTES from 'constants/routes';
|
||||
import Header from 'container/Header';
|
||||
import SideNav from 'container/SideNav';
|
||||
import TopNav from 'container/TopNav';
|
||||
@@ -19,26 +19,25 @@ import {
|
||||
UPDATE_CONFIGS,
|
||||
UPDATE_CURRENT_ERROR,
|
||||
UPDATE_CURRENT_VERSION,
|
||||
UPDATE_FEATURE_FLAG_RESPONSE,
|
||||
UPDATE_LATEST_VERSION,
|
||||
UPDATE_LATEST_VERSION_ERROR,
|
||||
} from 'types/actions/app';
|
||||
import AppReducer from 'types/reducer/app';
|
||||
|
||||
import { ChildrenContainer, Layout } from './styles';
|
||||
import { ChildrenContainer, Layout, LayoutContent } from './styles';
|
||||
import { getRouteKey } from './utils';
|
||||
|
||||
function AppLayout(props: AppLayoutProps): JSX.Element {
|
||||
const { isLoggedIn, user } = useSelector<AppState, AppReducer>(
|
||||
(state) => state.app,
|
||||
);
|
||||
|
||||
const { pathname } = useLocation();
|
||||
const { t } = useTranslation(['titles']);
|
||||
|
||||
const [
|
||||
getUserVersionResponse,
|
||||
getUserLatestVersionResponse,
|
||||
getFeaturesResponse,
|
||||
getDynamicConfigsResponse,
|
||||
] = useQueries([
|
||||
{
|
||||
@@ -51,10 +50,6 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
|
||||
queryKey: ['getUserLatestVersion', user?.accessJwt],
|
||||
enabled: isLoggedIn,
|
||||
},
|
||||
{
|
||||
queryFn: getFeaturesFlags,
|
||||
queryKey: ['getFeatureFlags', user?.accessJwt],
|
||||
},
|
||||
{
|
||||
queryFn: getDynamicConfigs,
|
||||
queryKey: ['getDynamicConfigs', user?.accessJwt],
|
||||
@@ -62,10 +57,6 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (getFeaturesResponse.status === 'idle') {
|
||||
getFeaturesResponse.refetch();
|
||||
}
|
||||
|
||||
if (getUserLatestVersionResponse.status === 'idle' && isLoggedIn) {
|
||||
getUserLatestVersionResponse.refetch();
|
||||
}
|
||||
@@ -73,14 +64,10 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
|
||||
if (getUserVersionResponse.status === 'idle' && isLoggedIn) {
|
||||
getUserVersionResponse.refetch();
|
||||
}
|
||||
if (getFeaturesResponse.status === 'idle') {
|
||||
getFeaturesResponse.refetch();
|
||||
}
|
||||
if (getDynamicConfigsResponse.status === 'idle') {
|
||||
getDynamicConfigsResponse.refetch();
|
||||
}
|
||||
}, [
|
||||
getFeaturesResponse,
|
||||
getUserLatestVersionResponse,
|
||||
getUserVersionResponse,
|
||||
isLoggedIn,
|
||||
@@ -194,42 +181,17 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
|
||||
getUserLatestVersionResponse.isFetched,
|
||||
getUserVersionResponse.isFetched,
|
||||
getUserLatestVersionResponse.isSuccess,
|
||||
getFeaturesResponse.isFetched,
|
||||
getFeaturesResponse.isSuccess,
|
||||
getFeaturesResponse.data,
|
||||
getDynamicConfigsResponse.data,
|
||||
getDynamicConfigsResponse.isFetched,
|
||||
getDynamicConfigsResponse.isSuccess,
|
||||
notifications,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
getFeaturesResponse.isFetched &&
|
||||
getFeaturesResponse.isSuccess &&
|
||||
getFeaturesResponse.data &&
|
||||
getFeaturesResponse.data.payload
|
||||
) {
|
||||
dispatch({
|
||||
type: UPDATE_FEATURE_FLAG_RESPONSE,
|
||||
payload: {
|
||||
featureFlag: getFeaturesResponse.data.payload,
|
||||
refetch: getFeaturesResponse.refetch,
|
||||
},
|
||||
});
|
||||
}
|
||||
}, [
|
||||
dispatch,
|
||||
getFeaturesResponse.data,
|
||||
getFeaturesResponse.isFetched,
|
||||
getFeaturesResponse.isSuccess,
|
||||
getFeaturesResponse.refetch,
|
||||
]);
|
||||
|
||||
const isToDisplayLayout = isLoggedIn;
|
||||
|
||||
const routeKey = useMemo(() => getRouteKey(pathname), [pathname]);
|
||||
const pageTitle = t(routeKey);
|
||||
const renderFullScreen = pathname === ROUTES.GET_STARTED;
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
@@ -239,13 +201,13 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
|
||||
|
||||
{isToDisplayLayout && <Header />}
|
||||
<Layout>
|
||||
{isToDisplayLayout && <SideNav />}
|
||||
<Layout.Content>
|
||||
{isToDisplayLayout && !renderFullScreen && <SideNav />}
|
||||
<LayoutContent>
|
||||
<ChildrenContainer>
|
||||
{isToDisplayLayout && <TopNav />}
|
||||
{isToDisplayLayout && !renderFullScreen && <TopNav />}
|
||||
{children}
|
||||
</ChildrenContainer>
|
||||
</Layout.Content>
|
||||
</LayoutContent>
|
||||
</Layout>
|
||||
</Layout>
|
||||
);
|
||||
|
||||
@@ -7,9 +7,14 @@ export const Layout = styled(LayoutComponent)`
|
||||
position: relative;
|
||||
min-height: calc(100vh - 4rem);
|
||||
overflow: hidden;
|
||||
height: 100%;
|
||||
}
|
||||
`;
|
||||
|
||||
export const LayoutContent = styled(LayoutComponent.Content)`
|
||||
overflow-y: auto;
|
||||
`;
|
||||
|
||||
export const ChildrenContainer = styled.div`
|
||||
margin: 0 1rem;
|
||||
display: flex;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user