Compare commits
1 Commits
query-limi
...
fix/issue-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b0bbb9d318 |
7
.github/CODEOWNERS
vendored
7
.github/CODEOWNERS
vendored
@@ -1,10 +1,15 @@
|
||||
# CODEOWNERS info: https://help.github.com/en/articles/about-code-owners
|
||||
# Owners are automatically requested for review for PRs that changes code
|
||||
# that they own.
|
||||
* @ankitnayan
|
||||
|
||||
/frontend/ @YounixM
|
||||
/frontend/ @palashgdev @YounixM
|
||||
/frontend/src/container/MetricsApplication @srikanthccv
|
||||
/frontend/src/container/NewWidget/RightContainer/types.ts @srikanthccv
|
||||
/deploy/ @prashant-shahi
|
||||
/sample-apps/ @prashant-shahi
|
||||
**/query-service/ @srikanthccv
|
||||
Makefile @srikanthccv
|
||||
go.* @srikanthccv
|
||||
.git* @srikanthccv
|
||||
.github @prashant-shahi
|
||||
|
||||
8
.github/workflows/push.yaml
vendored
8
.github/workflows/push.yaml
vendored
@@ -34,7 +34,7 @@ jobs:
|
||||
id: short-sha
|
||||
- name: Get branch name
|
||||
id: branch-name
|
||||
uses: tj-actions/branch-names@v7.0.7
|
||||
uses: tj-actions/branch-names@v5.1
|
||||
- name: Set docker tag environment
|
||||
run: |
|
||||
if [ '${{ steps.branch-name.outputs.is_tag }}' == 'true' ]; then
|
||||
@@ -78,7 +78,7 @@ jobs:
|
||||
id: short-sha
|
||||
- name: Get branch name
|
||||
id: branch-name
|
||||
uses: tj-actions/branch-names@v7.0.7
|
||||
uses: tj-actions/branch-names@v5.1
|
||||
- name: Set docker tag environment
|
||||
run: |
|
||||
if [ '${{ steps.branch-name.outputs.is_tag }}' == 'true' ]; then
|
||||
@@ -127,7 +127,7 @@ jobs:
|
||||
id: short-sha
|
||||
- name: Get branch name
|
||||
id: branch-name
|
||||
uses: tj-actions/branch-names@v7.0.7
|
||||
uses: tj-actions/branch-names@v5.1
|
||||
- name: Set docker tag environment
|
||||
run: |
|
||||
if [ '${{ steps.branch-name.outputs.is_tag }}' == 'true' ]; then
|
||||
@@ -176,7 +176,7 @@ jobs:
|
||||
id: short-sha
|
||||
- name: Get branch name
|
||||
id: branch-name
|
||||
uses: tj-actions/branch-names@v7.0.7
|
||||
uses: tj-actions/branch-names@v5.1
|
||||
- name: Set docker tag environment
|
||||
run: |
|
||||
if [ '${{ steps.branch-name.outputs.is_tag }}' == 'true' ]; then
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
<img alt="tweet" src="https://img.shields.io/twitter/url/http/shields.io.svg?style=social"> </a>
|
||||
</p>
|
||||
|
||||
|
||||
<h3 align="center">
|
||||
<a href="https://signoz.io/docs"><b>Dokumentation</b></a> •
|
||||
<a href="https://github.com/SigNoz/signoz/blob/develop/README.md"><b>Readme auf Englisch </b></a> •
|
||||
@@ -39,13 +40,12 @@ SigNoz hilft Entwicklern, Anwendungen zu überwachen und Probleme in ihren berei
|
||||
👉 Einfache Einrichtung von Benachrichtigungen mit dem selbst erstellbaren Abfrage-Builder.
|
||||
|
||||
##
|
||||
|
||||
### Anwendung Metriken
|
||||
|
||||

|
||||
|
||||
### Verteiltes Tracing
|
||||
|
||||
### Verteiltes Tracing
|
||||
<img width="2068" alt="distributed_tracing_2 2" src="https://user-images.githubusercontent.com/83692067/226536447-bae58321-6a22-4ed3-af80-e3e964cb3489.png">
|
||||
|
||||
<img width="2068" alt="distributed_tracing_1" src="https://user-images.githubusercontent.com/83692067/226536462-939745b6-4f9d-45a6-8016-814837e7f7b4.png">
|
||||
@@ -62,18 +62,22 @@ SigNoz hilft Entwicklern, Anwendungen zu überwachen und Probleme in ihren berei
|
||||
|
||||

|
||||
|
||||
|
||||
### Alarme
|
||||
|
||||
<img width="2068" alt="alerts_management" src="https://user-images.githubusercontent.com/83692067/226536548-2c81e2e8-c12d-47e8-bad7-c6be79055def.png">
|
||||
|
||||
|
||||
<br /><br />
|
||||
|
||||
|
||||
## Werde Teil unserer Slack Community
|
||||
|
||||
Sag Hi zu uns auf [Slack](https://signoz.io/slack) 👋
|
||||
|
||||
<br /><br />
|
||||
|
||||
|
||||
## Funktionen:
|
||||
|
||||
- Einheitliche Benutzeroberfläche für Metriken, Traces und Logs. Keine Notwendigkeit, zwischen Prometheus und Jaeger zu wechseln, um Probleme zu debuggen oder ein separates Log-Tool wie Elastic neben Ihrer Metriken- und Traces-Stack zu verwenden.
|
||||
@@ -89,6 +93,7 @@ Sag Hi zu uns auf [Slack](https://signoz.io/slack) 👋
|
||||
|
||||
<br /><br />
|
||||
|
||||
|
||||
## Wieso SigNoz?
|
||||
|
||||
Als Entwickler fanden wir es anstrengend, uns für jede kleine Funktion, die wir haben wollten, auf Closed Source SaaS Anbieter verlassen zu müssen. Closed Source Anbieter überraschen ihre Kunden zum Monatsende oft mit hohen Rechnungen, die keine Transparenz bzgl. der Kostenaufteilung bieten.
|
||||
@@ -111,10 +116,12 @@ Wir unterstützen [OpenTelemetry](https://opentelemetry.io) als Bibliothek, mit
|
||||
- Elixir
|
||||
- Rust
|
||||
|
||||
|
||||
Hier findest du die vollständige Liste von unterstützten Programmiersprachen - https://opentelemetry.io/docs/
|
||||
|
||||
<br /><br />
|
||||
|
||||
|
||||
## Erste Schritte mit SigNoz
|
||||
|
||||
### Bereitstellung mit Docker
|
||||
@@ -131,6 +138,7 @@ Bitte folge den [hier](https://signoz.io/docs/deployment/helm_chart) aufgelistet
|
||||
|
||||
<br /><br />
|
||||
|
||||
|
||||
## Vergleiche mit bekannten Tools
|
||||
|
||||
### SigNoz vs Prometheus
|
||||
@@ -171,6 +179,7 @@ Wir haben Benchmarks veröffentlicht, die Loki mit SigNoz vergleichen. Schauen S
|
||||
|
||||
<br /><br />
|
||||
|
||||
|
||||
## Zum Projekt beitragen
|
||||
|
||||
Wir ❤️ Beiträge zum Projekt, egal ob große oder kleine. Bitte lies dir zuerst die [CONTRIBUTING.md](CONTRIBUTING.md), durch, bevor du anfängst, Beiträge zu SigNoz zu machen.
|
||||
@@ -188,8 +197,6 @@ Du bist dir nicht sicher, wie du anfangen sollst? Schreib uns einfach auf dem #c
|
||||
#### Frontend
|
||||
|
||||
- [Palash Gupta](https://github.com/palashgdev)
|
||||
- [Yunus M](https://github.com/YounixM)
|
||||
- [Rajat Dabade](https://github.com/Rajat-Dabade)
|
||||
|
||||
#### DevOps
|
||||
|
||||
@@ -197,12 +204,16 @@ Du bist dir nicht sicher, wie du anfangen sollst? Schreib uns einfach auf dem #c
|
||||
|
||||
<br /><br />
|
||||
|
||||
|
||||
## Dokumentation
|
||||
|
||||
Du findest unsere Dokumentation unter https://signoz.io/docs/. Falls etwas unverständlich ist oder fehlt, öffne gerne ein Github Issue mit dem Label `documentation` oder schreib uns über den Community Slack Channel.
|
||||
|
||||
|
||||
|
||||
<br /><br />
|
||||
|
||||
|
||||
## Gemeinschaft
|
||||
|
||||
Werde Teil der [slack community](https://signoz.io/slack) um mehr über verteilte Einzelschritt-Fehlersuche, Messung von Systemzuständen oder SigNoz zu erfahren und sich mit anderen Nutzern und Mitwirkenden in Verbindung zu setzen.
|
||||
|
||||
@@ -199,13 +199,10 @@ Not sure how to get started? Just ping us on `#contributing` in our [slack commu
|
||||
#### Frontend
|
||||
|
||||
- [Palash Gupta](https://github.com/palashgdev)
|
||||
- [Yunus M](https://github.com/YounixM)
|
||||
- [Rajat Dabade](https://github.com/Rajat-Dabade)
|
||||
|
||||
#### DevOps
|
||||
|
||||
- [Prashant Shahi](https://github.com/prashant-shahi)
|
||||
- [Dhawal Sanghvi](https://github.com/dhawal1248)
|
||||
|
||||
<br /><br />
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<a href="https://twitter.com/SigNozHq"><b>Twitter</b></a>
|
||||
</h3>
|
||||
|
||||
##
|
||||
##
|
||||
|
||||
SigNoz 帮助开发人员监控应用并排查已部署应用的问题。你可以使用 SigNoz 实现如下能力:
|
||||
|
||||
@@ -67,7 +67,7 @@ SigNoz 帮助开发人员监控应用并排查已部署应用的问题。你可
|
||||
|
||||
## 加入我们 Slack 社区
|
||||
|
||||
来 [Slack](https://signoz.io/slack) 和我们打招呼吧 👋
|
||||
来 [Slack](https://signoz.io/slack) 和我们打招呼吧 👋
|
||||
|
||||
<br /><br />
|
||||
|
||||
@@ -83,7 +83,7 @@ SigNoz 帮助开发人员监控应用并排查已部署应用的问题。你可
|
||||
|
||||
- 通过 服务名、操作方式、延迟、错误、标签/注释 过滤 traces 数据
|
||||
|
||||
- 通过聚合 trace 数据而获得业务相关的 metrics。 比如你可以通过 `customer_type: gold` 或者 `deployment_version: v2` 或者 `external_call: paypal` 获取错误率和 P99 延迟数据
|
||||
- 通过聚合 trace 数据而获得业务相关的 metrics。 比如你可以通过 `customer_type: gold` 或者 `deployment_version: v2` 或者 `external_call: paypal` 获取错误率和 P99 延迟数据
|
||||
|
||||
- 原生支持 OpenTelemetry 日志,高级日志查询,自动收集 k8s 相关日志
|
||||
|
||||
@@ -101,7 +101,7 @@ SigNoz 帮助开发人员监控应用并排查已部署应用的问题。你可
|
||||
|
||||
我们想做一个自托管并且可开源的工具,像 DataDog 和 NewRelic 那样, 为那些担心数据隐私和安全的公司提供第三方服务。
|
||||
|
||||
作为开源的项目,你完全可以自己掌控你的配置、样本和更新。你同样可以基于 SigNoz 拓展特定的业务模块。
|
||||
作为开源的项目,你完全可以自己掌控你的配置、样本和更新。你同样可以基于 SigNoz 拓展特定的业务模块。
|
||||
|
||||
### 支持的编程语言:
|
||||
|
||||
@@ -153,9 +153,9 @@ Jaeger 仅仅是一个分布式追踪系统。 但是 SigNoz 可以提供 metric
|
||||
|
||||
而且, SigNoz 相较于 Jaeger 拥有更对的高级功能:
|
||||
|
||||
- Jaegar UI 不能提供任何基于 traces 的 metrics 查询和过滤。
|
||||
- Jaegar UI 不能提供任何基于 traces 的 metrics 查询和过滤。
|
||||
|
||||
- Jaeger 不能针对过滤的 traces 做聚合。 比如, p99 延迟的请求有个标签是 customer_type='premium'。 而这些在 SigNoz 可以轻松做到。
|
||||
- Jaeger 不能针对过滤的 traces 做聚合。 比如, p99 延迟的请求有个标签是 customer_type='premium'。 而这些在 SigNoz 可以轻松做到。
|
||||
|
||||
<p>  </p>
|
||||
|
||||
@@ -185,7 +185,7 @@ Jaeger 仅仅是一个分布式追踪系统。 但是 SigNoz 可以提供 metric
|
||||
|
||||
我们 ❤️ 你的贡献,无论大小。 请先阅读 [CONTRIBUTING.md](CONTRIBUTING.md) 再开始给 SigNoz 做贡献。
|
||||
|
||||
如果你不知道如何开始? 只需要在 [slack 社区](https://signoz.io/slack) 通过 `#contributing` 频道联系我们。
|
||||
如果你不知道如何开始? 只需要在 [slack 社区](https://signoz.io/slack) 通过 `#contributing` 频道联系我们。
|
||||
|
||||
### 项目维护人员
|
||||
|
||||
@@ -199,8 +199,6 @@ Jaeger 仅仅是一个分布式追踪系统。 但是 SigNoz 可以提供 metric
|
||||
#### 前端
|
||||
|
||||
- [Palash Gupta](https://github.com/palashgdev)
|
||||
- [Yunus M](https://github.com/YounixM)
|
||||
- [Rajat Dabade](https://github.com/Rajat-Dabade)
|
||||
|
||||
#### 运维开发
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
version: "3.9"
|
||||
|
||||
x-clickhouse-defaults: &clickhouse-defaults
|
||||
image: clickhouse/clickhouse-server:23.11.1-alpine
|
||||
image: clickhouse/clickhouse-server:23.7.3-alpine
|
||||
tty: true
|
||||
deploy:
|
||||
restart_policy:
|
||||
@@ -146,7 +146,7 @@ services:
|
||||
condition: on-failure
|
||||
|
||||
query-service:
|
||||
image: signoz/query-service:0.36.2
|
||||
image: signoz/query-service:0.34.4
|
||||
command:
|
||||
[
|
||||
"-config=/root/config/prometheus.yml",
|
||||
@@ -186,7 +186,7 @@ services:
|
||||
<<: *db-depend
|
||||
|
||||
frontend:
|
||||
image: signoz/frontend:0.36.2
|
||||
image: signoz/frontend:0.34.4
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
@@ -199,7 +199,7 @@ services:
|
||||
- ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf
|
||||
|
||||
otel-collector:
|
||||
image: signoz/signoz-otel-collector:0.88.6
|
||||
image: signoz/signoz-otel-collector:0.88.1
|
||||
command:
|
||||
[
|
||||
"--config=/etc/otel-collector-config.yaml",
|
||||
@@ -237,7 +237,7 @@ services:
|
||||
- query-service
|
||||
|
||||
otel-collector-migrator:
|
||||
image: signoz/signoz-schema-migrator:0.88.6
|
||||
image: signoz/signoz-schema-migrator:0.88.1
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
@@ -250,7 +250,7 @@ services:
|
||||
# - clickhouse-3
|
||||
|
||||
otel-collector-metrics:
|
||||
image: signoz/signoz-otel-collector:0.88.6
|
||||
image: signoz/signoz-otel-collector:0.88.1
|
||||
command:
|
||||
[
|
||||
"--config=/etc/otel-collector-metrics-config.yaml",
|
||||
|
||||
@@ -66,7 +66,7 @@ services:
|
||||
- --storage.path=/data
|
||||
|
||||
otel-collector-migrator:
|
||||
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.88.6}
|
||||
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.88.1}
|
||||
container_name: otel-migrator
|
||||
command:
|
||||
- "--dsn=tcp://clickhouse:9000"
|
||||
@@ -81,7 +81,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.88.6
|
||||
image: signoz/signoz-otel-collector:0.88.1
|
||||
command:
|
||||
[
|
||||
"--config=/etc/otel-collector-config.yaml",
|
||||
@@ -118,7 +118,7 @@ services:
|
||||
|
||||
otel-collector-metrics:
|
||||
container_name: signoz-otel-collector-metrics
|
||||
image: signoz/signoz-otel-collector:0.88.6
|
||||
image: signoz/signoz-otel-collector:0.88.1
|
||||
command:
|
||||
[
|
||||
"--config=/etc/otel-collector-metrics-config.yaml",
|
||||
|
||||
@@ -3,7 +3,7 @@ version: "2.4"
|
||||
x-clickhouse-defaults: &clickhouse-defaults
|
||||
restart: on-failure
|
||||
# addding non LTS version due to this fix https://github.com/ClickHouse/ClickHouse/commit/32caf8716352f45c1b617274c7508c86b7d1afab
|
||||
image: clickhouse/clickhouse-server:23.11.1-alpine
|
||||
image: clickhouse/clickhouse-server:23.7.3-alpine
|
||||
tty: true
|
||||
depends_on:
|
||||
- zookeeper-1
|
||||
@@ -164,7 +164,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`
|
||||
|
||||
query-service:
|
||||
image: signoz/query-service:${DOCKER_TAG:-0.36.2}
|
||||
image: signoz/query-service:${DOCKER_TAG:-0.34.4}
|
||||
container_name: signoz-query-service
|
||||
command:
|
||||
[
|
||||
@@ -203,7 +203,7 @@ services:
|
||||
<<: *db-depend
|
||||
|
||||
frontend:
|
||||
image: signoz/frontend:${DOCKER_TAG:-0.36.2}
|
||||
image: signoz/frontend:${DOCKER_TAG:-0.34.4}
|
||||
container_name: signoz-frontend
|
||||
restart: on-failure
|
||||
depends_on:
|
||||
@@ -215,7 +215,7 @@ services:
|
||||
- ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf
|
||||
|
||||
otel-collector-migrator:
|
||||
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.88.6}
|
||||
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.88.1}
|
||||
container_name: otel-migrator
|
||||
command:
|
||||
- "--dsn=tcp://clickhouse:9000"
|
||||
@@ -229,7 +229,7 @@ services:
|
||||
|
||||
|
||||
otel-collector:
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.88.6}
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.88.1}
|
||||
container_name: signoz-otel-collector
|
||||
command:
|
||||
[
|
||||
@@ -269,7 +269,7 @@ services:
|
||||
condition: service_healthy
|
||||
|
||||
otel-collector-metrics:
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.88.6}
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.88.1}
|
||||
container_name: signoz-otel-collector-metrics
|
||||
command:
|
||||
[
|
||||
|
||||
@@ -18,7 +18,6 @@ COPY ee/query-service/bin/query-service-${TARGETOS}-${TARGETARCH} /root/query-se
|
||||
|
||||
# copy prometheus YAML config
|
||||
COPY pkg/query-service/config/prometheus.yml /root/config/prometheus.yml
|
||||
COPY pkg/query-service/templates /root/templates
|
||||
|
||||
# Make query-service executable for non-root users
|
||||
RUN chmod 755 /root /root/query-service
|
||||
|
||||
@@ -150,7 +150,6 @@ func (ah *APIHandler) RegisterRoutes(router *mux.Router, am *baseapp.AuthMiddlew
|
||||
router.HandleFunc("/api/v1/login", am.OpenAccess(ah.loginUser)).Methods(http.MethodPost)
|
||||
router.HandleFunc("/api/v1/traces/{traceId}", am.ViewAccess(ah.searchTraces)).Methods(http.MethodGet)
|
||||
router.HandleFunc("/api/v2/metrics/query_range", am.ViewAccess(ah.queryRangeMetricsV2)).Methods(http.MethodPost)
|
||||
router.HandleFunc("/api/v3/query_range", am.ViewAccess(ah.QueryRangeV3)).Methods(http.MethodPost)
|
||||
|
||||
// PAT APIs
|
||||
router.HandleFunc("/api/v1/pat", am.OpenAccess(ah.createPAT)).Methods(http.MethodPost)
|
||||
@@ -164,10 +163,6 @@ func (ah *APIHandler) RegisterRoutes(router *mux.Router, am *baseapp.AuthMiddlew
|
||||
router.HandleFunc("/api/v1/dashboards/{uuid}/lock", am.EditAccess(ah.lockDashboard)).Methods(http.MethodPut)
|
||||
router.HandleFunc("/api/v1/dashboards/{uuid}/unlock", am.EditAccess(ah.unlockDashboard)).Methods(http.MethodPut)
|
||||
|
||||
router.HandleFunc("/api/v1/limits", am.AdminAccess(ah.getQueryLimits)).Methods(http.MethodGet)
|
||||
router.HandleFunc("/api/v1/limits", am.AdminAccess(ah.addQueryLimits)).Methods(http.MethodPost)
|
||||
router.HandleFunc("/api/v1/limits", am.AdminAccess(ah.updateQueryLimits)).Methods(http.MethodPut)
|
||||
|
||||
router.HandleFunc("/api/v2/licenses",
|
||||
am.ViewAccess(ah.listLicensesV2)).
|
||||
Methods(http.MethodGet)
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"go.signoz.io/signoz/ee/query-service/model"
|
||||
basemodel "go.signoz.io/signoz/pkg/query-service/model"
|
||||
)
|
||||
|
||||
func (aH *APIHandler) getQueryLimits(w http.ResponseWriter, r *http.Request) {
|
||||
queryLimits, err := aH.AppDao().GetQueryLimits(r.Context())
|
||||
if err != nil {
|
||||
RespondError(w, &basemodel.ApiError{Typ: basemodel.ErrorInternal, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
aH.Respond(w, queryLimits)
|
||||
}
|
||||
|
||||
func (aH *APIHandler) addQueryLimits(w http.ResponseWriter, r *http.Request) {
|
||||
var queryLimits []*model.QueryLimit
|
||||
|
||||
if err := json.NewDecoder(r.Body).Decode(&queryLimits); err != nil {
|
||||
RespondError(w, &basemodel.ApiError{Typ: basemodel.ErrorBadData, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
err := aH.AppDao().AddQueryLimits(r.Context(), queryLimits)
|
||||
if err != nil {
|
||||
RespondError(w, &basemodel.ApiError{Typ: basemodel.ErrorInternal, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
aH.Respond(w, nil)
|
||||
|
||||
}
|
||||
|
||||
func (aH *APIHandler) updateQueryLimits(w http.ResponseWriter, r *http.Request) {
|
||||
var queryLimits []*model.QueryLimit
|
||||
|
||||
if err := json.NewDecoder(r.Body).Decode(&queryLimits); err != nil {
|
||||
RespondError(w, &basemodel.ApiError{Typ: basemodel.ErrorBadData, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
err := aH.AppDao().UpdateQueryLimits(r.Context(), queryLimits)
|
||||
if err != nil {
|
||||
RespondError(w, &basemodel.ApiError{Typ: basemodel.ErrorInternal, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
aH.Respond(w, nil)
|
||||
}
|
||||
@@ -4,17 +4,14 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"go.signoz.io/signoz/pkg/query-service/app"
|
||||
"go.signoz.io/signoz/pkg/query-service/app/metrics"
|
||||
"go.signoz.io/signoz/pkg/query-service/app/parser"
|
||||
"go.signoz.io/signoz/pkg/query-service/constants"
|
||||
basemodel "go.signoz.io/signoz/pkg/query-service/model"
|
||||
v3 "go.signoz.io/signoz/pkg/query-service/model/v3"
|
||||
querytemplate "go.signoz.io/signoz/pkg/query-service/utils/queryTemplate"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
@@ -237,52 +234,3 @@ func (ah *APIHandler) queryRangeMetricsV2(w http.ResponseWriter, r *http.Request
|
||||
resp := ResponseFormat{ResultType: "matrix", Result: seriesList, TableName: tableName}
|
||||
ah.Respond(w, resp)
|
||||
}
|
||||
|
||||
func (aH *APIHandler) QueryRangeV3(w http.ResponseWriter, r *http.Request) {
|
||||
queryRangeParams, apiErrorObj := app.ParseQueryRangeParams(r)
|
||||
|
||||
if apiErrorObj != nil {
|
||||
zap.S().Errorf(apiErrorObj.Err.Error())
|
||||
RespondError(w, apiErrorObj, nil)
|
||||
return
|
||||
}
|
||||
|
||||
limits, err := aH.AppDao().GetQueryLimits(r.Context())
|
||||
if err != nil {
|
||||
zap.S().Errorf("Error while getting query limits: %v", err)
|
||||
// We don't want to fail the request if we can't get the limits
|
||||
// iterate over the nil slice will not execute the loop
|
||||
// and the query will be executed without any limits
|
||||
// this shouldn't happen ideally but we don't want to fail the request
|
||||
}
|
||||
|
||||
var timeSeriesLimt int
|
||||
for _, limit := range limits {
|
||||
if limit.Name == "time_series_limit" {
|
||||
timeSeriesLimt = limit.UsageLimit
|
||||
}
|
||||
}
|
||||
|
||||
if len(queryRangeParams.CompositeQuery.BuilderQueries) > 0 {
|
||||
for idx := range queryRangeParams.CompositeQuery.BuilderQueries {
|
||||
queryRangeParams.CompositeQuery.BuilderQueries[idx].QueryLimits = v3.QueryLimits{
|
||||
MaxTimeSeries: timeSeriesLimt,
|
||||
}
|
||||
// TODO(srikanthccv): should apply to explore page also
|
||||
if !strings.Contains(queryRangeParams.SourcePage, "dashboard") {
|
||||
queryRangeParams.CompositeQuery.BuilderQueries[idx].QueryLimits.MaxTimeSeries = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// add temporality for each metric
|
||||
|
||||
temporalityErr := aH.APIHandler.AddTemporality(r.Context(), queryRangeParams)
|
||||
if temporalityErr != nil {
|
||||
zap.S().Errorf("Error while adding temporality for metrics: %v", temporalityErr)
|
||||
RespondError(w, &basemodel.ApiError{Typ: basemodel.ErrorInternal, Err: temporalityErr}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
aH.APIHandler.ExecQueryRangeV3(r.Context(), queryRangeParams, w, r)
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
"github.com/gorilla/mux"
|
||||
"go.signoz.io/signoz/ee/query-service/model"
|
||||
"go.signoz.io/signoz/pkg/query-service/auth"
|
||||
basemodel "go.signoz.io/signoz/pkg/query-service/model"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
@@ -48,18 +47,8 @@ func (ah *APIHandler) createPAT(w http.ResponseWriter, r *http.Request) {
|
||||
req.CreatedAt = time.Now().Unix()
|
||||
req.Token = generatePATToken()
|
||||
|
||||
// default expiry is 30 days
|
||||
if req.ExpiresAt == 0 {
|
||||
req.ExpiresAt = time.Now().AddDate(0, 0, 30).Unix()
|
||||
}
|
||||
// max expiry is 1 year
|
||||
if req.ExpiresAt > time.Now().AddDate(1, 0, 0).Unix() {
|
||||
req.ExpiresAt = time.Now().AddDate(1, 0, 0).Unix()
|
||||
}
|
||||
|
||||
zap.S().Debugf("Got PAT request: %+v", req)
|
||||
var apierr basemodel.BaseApiError
|
||||
if req, apierr = ah.AppDao().CreatePAT(ctx, req); apierr != nil {
|
||||
if apierr := ah.AppDao().CreatePAT(ctx, &req); apierr != nil {
|
||||
RespondError(w, apierr, nil)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@ import (
|
||||
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"
|
||||
baseint "go.signoz.io/signoz/pkg/query-service/interfaces"
|
||||
basemodel "go.signoz.io/signoz/pkg/query-service/model"
|
||||
pqle "go.signoz.io/signoz/pkg/query-service/pqlEngine"
|
||||
rules "go.signoz.io/signoz/pkg/query-service/rules"
|
||||
@@ -86,7 +87,7 @@ type Server struct {
|
||||
privateHTTP *http.Server
|
||||
|
||||
// feature flags
|
||||
featureLookup baseInterface.FeatureLookup
|
||||
featureLookup baseint.FeatureLookup
|
||||
|
||||
// Usage manager
|
||||
usageManager *usage.Manager
|
||||
@@ -479,7 +480,7 @@ func (s *Server) analyticsMiddleware(next http.Handler) http.Handler {
|
||||
}
|
||||
}
|
||||
|
||||
if _, ok := telemetry.EnabledPaths()[path]; ok {
|
||||
if _, ok := telemetry.IgnoredPaths()[path]; !ok {
|
||||
userEmail, err := auth.GetEmailFromJwt(r.Context())
|
||||
if err == nil {
|
||||
telemetry.GetInstance().SendEvent(telemetry.TELEMETRY_EVENT_PATH, data, userEmail)
|
||||
@@ -640,7 +641,7 @@ func makeRulesManager(
|
||||
alertManagerURL string,
|
||||
ruleRepoURL string,
|
||||
db *sqlx.DB,
|
||||
ch baseInterface.Reader,
|
||||
ch baseint.Reader,
|
||||
disableRules bool,
|
||||
fm baseInterface.FeatureLookup) (*rules.Manager, error) {
|
||||
|
||||
|
||||
@@ -33,14 +33,10 @@ type ModelDao interface {
|
||||
DeleteDomain(ctx context.Context, id uuid.UUID) basemodel.BaseApiError
|
||||
GetDomainByEmail(ctx context.Context, email string) (*model.OrgDomain, basemodel.BaseApiError)
|
||||
|
||||
CreatePAT(ctx context.Context, p model.PAT) (model.PAT, basemodel.BaseApiError)
|
||||
CreatePAT(ctx context.Context, p *model.PAT) basemodel.BaseApiError
|
||||
GetPAT(ctx context.Context, pat string) (*model.PAT, basemodel.BaseApiError)
|
||||
GetPATByID(ctx context.Context, id string) (*model.PAT, basemodel.BaseApiError)
|
||||
GetUserByPAT(ctx context.Context, token string) (*basemodel.UserPayload, basemodel.BaseApiError)
|
||||
ListPATs(ctx context.Context, userID string) ([]model.PAT, basemodel.BaseApiError)
|
||||
DeletePAT(ctx context.Context, id string) basemodel.BaseApiError
|
||||
|
||||
GetQueryLimits(ctx context.Context) ([]*model.QueryLimit, error)
|
||||
AddQueryLimits(ctx context.Context, queryLimits []*model.QueryLimit) error
|
||||
UpdateQueryLimits(ctx context.Context, queryLimits []*model.QueryLimit) error
|
||||
}
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
package sqlite
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"go.signoz.io/signoz/ee/query-service/model"
|
||||
)
|
||||
|
||||
// GetQueryLimits returns the query limits
|
||||
func (m *modelDao) GetQueryLimits(ctx context.Context) ([]*model.QueryLimit, error) {
|
||||
query := `SELECT name, title, usage_limit FROM resource_usage_limits`
|
||||
var queryLimits []*model.QueryLimit
|
||||
err := m.DB().Select(&queryLimits, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return queryLimits, nil
|
||||
}
|
||||
|
||||
// AddQueryLimits adds the query limits
|
||||
func (m *modelDao) AddQueryLimits(ctx context.Context, queryLimits []*model.QueryLimit) error {
|
||||
tx := m.DB().MustBegin()
|
||||
for _, queryLimit := range queryLimits {
|
||||
query := `INSERT INTO resource_usage_limits (name, title, usage_limit) VALUES (?, ?, ?)`
|
||||
_, err := tx.Exec(query, queryLimit.Name, queryLimit.Title, queryLimit.UsageLimit)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
}
|
||||
err := tx.Commit()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateQueryLimits updates the query limits
|
||||
func (m *modelDao) UpdateQueryLimits(ctx context.Context, queryLimits []*model.QueryLimit) error {
|
||||
tx := m.DB().MustBegin()
|
||||
for _, queryLimit := range queryLimits {
|
||||
query := `UPDATE resource_usage_limits SET usage_limit = ? WHERE name = ?`
|
||||
_, err := tx.Exec(query, queryLimit.UsageLimit, queryLimit.Name)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
}
|
||||
err := tx.Commit()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -38,7 +38,7 @@ func InitDB(dataSourceName string) (*modelDao, error) {
|
||||
basedao.SetDB(dao)
|
||||
m := &modelDao{ModelDaoSqlite: dao}
|
||||
|
||||
tableSchema := `
|
||||
table_schema := `
|
||||
PRAGMA foreign_keys = ON;
|
||||
CREATE TABLE IF NOT EXISTS org_domains(
|
||||
id TEXT PRIMARY KEY,
|
||||
@@ -60,23 +60,11 @@ func InitDB(dataSourceName string) (*modelDao, error) {
|
||||
);
|
||||
`
|
||||
|
||||
_, err = m.DB().Exec(tableSchema)
|
||||
_, err = m.DB().Exec(table_schema)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error in creating tables: %v", err.Error())
|
||||
}
|
||||
|
||||
// create resource_usage_limits table
|
||||
tableSchema = `CREATE TABLE IF NOT EXISTS resource_usage_limits (
|
||||
name TEXT PRIMARY KEY,
|
||||
title TEXT,
|
||||
usage_limit INTEGER DEFAULT 0
|
||||
);`
|
||||
|
||||
_, err = m.DB().Exec(tableSchema)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error in creating resource_usage_limits table: %s", err.Error())
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -3,15 +3,14 @@ package sqlite
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"go.signoz.io/signoz/ee/query-service/model"
|
||||
basemodel "go.signoz.io/signoz/pkg/query-service/model"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func (m *modelDao) CreatePAT(ctx context.Context, p model.PAT) (model.PAT, basemodel.BaseApiError) {
|
||||
result, err := m.DB().ExecContext(ctx,
|
||||
func (m *modelDao) CreatePAT(ctx context.Context, p *model.PAT) basemodel.BaseApiError {
|
||||
_, err := m.DB().ExecContext(ctx,
|
||||
"INSERT INTO personal_access_tokens (user_id, token, name, created_at, expires_at) VALUES ($1, $2, $3, $4, $5)",
|
||||
p.UserID,
|
||||
p.Token,
|
||||
@@ -20,15 +19,9 @@ func (m *modelDao) CreatePAT(ctx context.Context, p model.PAT) (model.PAT, basem
|
||||
p.ExpiresAt)
|
||||
if err != nil {
|
||||
zap.S().Errorf("Failed to insert PAT in db, err: %v", zap.Error(err))
|
||||
return model.PAT{}, model.InternalError(fmt.Errorf("PAT insertion failed"))
|
||||
return model.InternalError(fmt.Errorf("PAT insertion failed"))
|
||||
}
|
||||
id, err := result.LastInsertId()
|
||||
if err != nil {
|
||||
zap.S().Errorf("Failed to get last inserted id, err: %v", zap.Error(err))
|
||||
return model.PAT{}, model.InternalError(fmt.Errorf("PAT insertion failed"))
|
||||
}
|
||||
p.Id = strconv.Itoa(int(id))
|
||||
return p, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *modelDao) ListPATs(ctx context.Context, userID string) ([]model.PAT, basemodel.BaseApiError) {
|
||||
@@ -97,7 +90,7 @@ func (m *modelDao) GetUserByPAT(ctx context.Context, token string) (*basemodel.U
|
||||
u.org_id,
|
||||
u.group_id
|
||||
FROM users u, personal_access_tokens p
|
||||
WHERE u.id = p.user_id and p.token=? and p.expires_at >= strftime('%s', 'now');`
|
||||
WHERE u.id = p.user_id and p.token=?;`
|
||||
|
||||
if err := m.DB().Select(&users, query, token); err != nil {
|
||||
return nil, model.InternalError(fmt.Errorf("failed to fetch user from PAT, err: %v", err))
|
||||
|
||||
@@ -9,8 +9,8 @@ import (
|
||||
"github.com/google/uuid"
|
||||
"github.com/pkg/errors"
|
||||
saml2 "github.com/russellhaering/gosaml2"
|
||||
"go.signoz.io/signoz/ee/query-service/sso"
|
||||
"go.signoz.io/signoz/ee/query-service/sso/saml"
|
||||
"go.signoz.io/signoz/ee/query-service/sso"
|
||||
basemodel "go.signoz.io/signoz/pkg/query-service/model"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
@@ -24,16 +24,16 @@ const (
|
||||
|
||||
// OrgDomain identify org owned web domains for auth and other purposes
|
||||
type OrgDomain struct {
|
||||
Id uuid.UUID `json:"id"`
|
||||
Name string `json:"name"`
|
||||
OrgId string `json:"orgId"`
|
||||
SsoEnabled bool `json:"ssoEnabled"`
|
||||
SsoType SSOType `json:"ssoType"`
|
||||
Id uuid.UUID `json:"id"`
|
||||
Name string `json:"name"`
|
||||
OrgId string `json:"orgId"`
|
||||
SsoEnabled bool `json:"ssoEnabled"`
|
||||
SsoType SSOType `json:"ssoType"`
|
||||
|
||||
SamlConfig *SamlConfig `json:"samlConfig"`
|
||||
SamlConfig *SamlConfig `json:"samlConfig"`
|
||||
GoogleAuthConfig *GoogleOAuthConfig `json:"googleAuthConfig"`
|
||||
|
||||
Org *basemodel.Organization
|
||||
Org *basemodel.Organization
|
||||
}
|
||||
|
||||
func (od *OrgDomain) String() string {
|
||||
@@ -100,8 +100,8 @@ func (od *OrgDomain) GetSAMLCert() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// PrepareGoogleOAuthProvider creates GoogleProvider that is used in
|
||||
// requesting OAuth and also used in processing response from google
|
||||
// PrepareGoogleOAuthProvider creates GoogleProvider that is used in
|
||||
// requesting OAuth and also used in processing response from google
|
||||
func (od *OrgDomain) PrepareGoogleOAuthProvider(siteUrl *url.URL) (sso.OAuthCallbackProvider, error) {
|
||||
if od.GoogleAuthConfig == nil {
|
||||
return nil, fmt.Errorf("Google auth is not setup correctly for this domain")
|
||||
@@ -137,36 +137,38 @@ func (od *OrgDomain) PrepareSamlRequest(siteUrl *url.URL) (*saml2.SAMLServicePro
|
||||
}
|
||||
|
||||
func (od *OrgDomain) BuildSsoUrl(siteUrl *url.URL) (ssoUrl string, err error) {
|
||||
|
||||
|
||||
fmtDomainId := strings.Replace(od.Id.String(), "-", ":", -1)
|
||||
|
||||
|
||||
// build redirect url from window.location sent by frontend
|
||||
redirectURL := fmt.Sprintf("%s://%s%s", siteUrl.Scheme, siteUrl.Host, siteUrl.Path)
|
||||
|
||||
// prepare state that gets relayed back when the auth provider
|
||||
// calls back our url. here we pass the app url (where signoz runs)
|
||||
// and the domain Id. The domain Id helps in identifying sso config
|
||||
// when the call back occurs and the app url is useful in redirecting user
|
||||
// back to the right path.
|
||||
// when the call back occurs and the app url is useful in redirecting user
|
||||
// back to the right path.
|
||||
// why do we need to pass app url? the callback typically is handled by backend
|
||||
// and sometimes backend might right at a different port or is unaware of frontend
|
||||
// endpoint (unless SITE_URL param is set). hence, we receive this build sso request
|
||||
// along with frontend window.location and use it to relay the information through
|
||||
// auth provider to the backend (HandleCallback or HandleSSO method).
|
||||
// along with frontend window.location and use it to relay the information through
|
||||
// auth provider to the backend (HandleCallback or HandleSSO method).
|
||||
relayState := fmt.Sprintf("%s?domainId=%s", redirectURL, fmtDomainId)
|
||||
|
||||
|
||||
switch od.SsoType {
|
||||
switch (od.SsoType) {
|
||||
case SAML:
|
||||
|
||||
sp, err := od.PrepareSamlRequest(siteUrl)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
|
||||
return sp.BuildAuthURL(relayState)
|
||||
|
||||
|
||||
case GoogleAuth:
|
||||
|
||||
|
||||
googleProvider, err := od.PrepareGoogleOAuthProvider(siteUrl)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@@ -175,7 +177,8 @@ func (od *OrgDomain) BuildSsoUrl(siteUrl *url.URL) (ssoUrl string, err error) {
|
||||
|
||||
default:
|
||||
zap.S().Errorf("found unsupported SSO config for the org domain", zap.String("orgDomain", od.Name))
|
||||
return "", fmt.Errorf("unsupported SSO config for the domain")
|
||||
return "", fmt.Errorf("unsupported SSO config for the domain")
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
package model
|
||||
|
||||
type QueryLimit struct {
|
||||
// Name of the query limit
|
||||
Name string `db:"name" json:"name"`
|
||||
|
||||
// Title of the query limit
|
||||
Title string `db:"title" json:"title"`
|
||||
// UsageLimit indicates the usage limit of the query
|
||||
// If the usage limit is 0, then there is no limit
|
||||
UsageLimit int `db:"usage_limit" json:"usage_limit"`
|
||||
}
|
||||
@@ -6,5 +6,5 @@ type PAT struct {
|
||||
Token string `json:"token" db:"token"`
|
||||
Name string `json:"name" db:"name"`
|
||||
CreatedAt int64 `json:"createdAt" db:"created_at"`
|
||||
ExpiresAt int64 `json:"expiresAt" db:"expires_at"`
|
||||
ExpiresAt int64 `json:"expiresAt" db:"expires_at"` // unused as of now
|
||||
}
|
||||
|
||||
@@ -52,14 +52,14 @@ var BasicPlan = basemodel.FeatureSet{
|
||||
Name: basemodel.QueryBuilderPanels,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: 20,
|
||||
UsageLimit: 5,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.QueryBuilderAlerts,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: 10,
|
||||
UsageLimit: 5,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
|
||||
@@ -86,7 +86,6 @@ module.exports = {
|
||||
},
|
||||
],
|
||||
'import/no-extraneous-dependencies': ['error', { devDependencies: true }],
|
||||
'no-plusplus': 'off',
|
||||
'jsx-a11y/label-has-associated-control': [
|
||||
'error',
|
||||
{
|
||||
@@ -110,6 +109,7 @@ module.exports = {
|
||||
// eslint rules need to remove
|
||||
'@typescript-eslint/no-shadow': 'off',
|
||||
'import/no-cycle': 'off',
|
||||
|
||||
'prettier/prettier': [
|
||||
'error',
|
||||
{},
|
||||
|
||||
@@ -2,19 +2,3 @@
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
cd frontend && yarn run commitlint --edit $1
|
||||
|
||||
branch="$(git rev-parse --abbrev-ref HEAD)"
|
||||
|
||||
color_red="$(tput setaf 1)"
|
||||
bold="$(tput bold)"
|
||||
reset="$(tput sgr0)"
|
||||
|
||||
if [ "$branch" = "main" ]; then
|
||||
echo "${color_red}${bold}You can't commit directly to the main branch${reset}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$branch" = "develop" ]; then
|
||||
echo "${color_red}${bold}You can't commit directly to the develop branch${reset}"
|
||||
exit 1
|
||||
fi
|
||||
@@ -22,7 +22,7 @@ const config: Config.InitialOptions = {
|
||||
'^.+\\.(js|jsx)$': 'babel-jest',
|
||||
},
|
||||
transformIgnorePatterns: [
|
||||
'node_modules/(?!(lodash-es|react-dnd|core-dnd|@react-dnd|dnd-core|react-dnd-html5-backend|axios)/)',
|
||||
'node_modules/(?!(lodash-es|react-dnd|core-dnd|@react-dnd|dnd-core|react-dnd-html5-backend)/)',
|
||||
],
|
||||
setupFilesAfterEnv: ['<rootDir>jest.setup.ts'],
|
||||
testPathIgnorePatterns: ['/node_modules/', '/public/'],
|
||||
|
||||
@@ -29,9 +29,6 @@
|
||||
"dependencies": {
|
||||
"@ant-design/colors": "6.0.0",
|
||||
"@ant-design/icons": "4.8.0",
|
||||
"@dnd-kit/core": "6.1.0",
|
||||
"@dnd-kit/modifiers": "7.0.0",
|
||||
"@dnd-kit/sortable": "8.0.0",
|
||||
"@grafana/data": "^9.5.2",
|
||||
"@mdx-js/loader": "2.3.0",
|
||||
"@mdx-js/react": "2.3.0",
|
||||
@@ -41,7 +38,7 @@
|
||||
"ansi-to-html": "0.7.2",
|
||||
"antd": "5.11.0",
|
||||
"antd-table-saveas-excel": "2.2.1",
|
||||
"axios": "1.6.2",
|
||||
"axios": "^0.21.0",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"babel-jest": "^29.6.4",
|
||||
"babel-loader": "9.1.3",
|
||||
@@ -90,7 +87,7 @@
|
||||
"react-helmet-async": "1.3.0",
|
||||
"react-i18next": "^11.16.1",
|
||||
"react-markdown": "8.0.7",
|
||||
"react-query": "3.39.3",
|
||||
"react-query": "^3.34.19",
|
||||
"react-redux": "^7.2.2",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"react-syntax-highlighter": "15.5.0",
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 51 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 3.7 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 957 B |
Binary file not shown.
|
Before Width: | Height: | Size: 4.3 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 45 KiB |
@@ -21,9 +21,5 @@
|
||||
"error_while_updating_variable": "Error while updating variable",
|
||||
"dashboard_has_been_updated": "Dashboard has been updated",
|
||||
"do_you_want_to_refresh_the_dashboard": "Do you want to refresh the dashboard?",
|
||||
"delete_dashboard_success": "{{name}} dashboard deleted successfully",
|
||||
"dashboard_unsave_changes": "There are unsaved changes in the Query builder, please stage and run the query or the changes will be lost. Press OK to discard.",
|
||||
"dashboard_save_changes": "Your graph built with {{queryTag}} query will be saved. Press OK to confirm.",
|
||||
"your_graph_build_with": "Your graph built with",
|
||||
"dashboar_ok_confirm": "query will be saved. Press OK to confirm."
|
||||
"delete_dashboard_success": "{{name}} dashboard deleted successfully"
|
||||
}
|
||||
|
||||
@@ -14,6 +14,5 @@
|
||||
"delete_domain_message": "Are you sure you want to delete this domain?",
|
||||
"delete_domain": "Delete Domain",
|
||||
"add_domain": "Add Domains",
|
||||
"saml_settings": "Your SAML settings have been saved, please login from incognito window to confirm that it has been set up correctly",
|
||||
"invite_link_share_manually": "After inviting members, please copy the invite link and send them the link manually"
|
||||
"saml_settings":"Your SAML settings have been saved, please login from incognito window to confirm that it has been set up correctly"
|
||||
}
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"rps_over_100": "You are sending data at more than 100 RPS, your ingestion may be rate limited. Please reach out to us via Intercom support."
|
||||
}
|
||||
@@ -24,9 +24,5 @@
|
||||
"do_you_want_to_refresh_the_dashboard": "Do you want to refresh the dashboard?",
|
||||
"locked_dashboard_delete_tooltip_admin_author": "Dashboard is locked. Please unlock the dashboard to enable delete.",
|
||||
"locked_dashboard_delete_tooltip_editor": "Dashboard is locked. Please contact admin to delete the dashboard.",
|
||||
"delete_dashboard_success": "{{name}} dashboard deleted successfully",
|
||||
"dashboard_unsave_changes": "There are unsaved changes in the Query builder, please stage and run the query or the changes will be lost. Press OK to discard.",
|
||||
"dashboard_save_changes": "Your graph built with {{queryTag}} query will be saved. Press OK to confirm.",
|
||||
"your_graph_build_with": "Your graph built with",
|
||||
"dashboar_ok_confirm": "query will be saved. Press OK to confirm."
|
||||
"delete_dashboard_success": "{{name}} dashboard deleted successfully"
|
||||
}
|
||||
|
||||
@@ -14,6 +14,5 @@
|
||||
"delete_domain_message": "Are you sure you want to delete this domain?",
|
||||
"delete_domain": "Delete Domain",
|
||||
"add_domain": "Add Domains",
|
||||
"saml_settings": "Your SAML settings have been saved, please login from incognito window to confirm that it has been set up correctly",
|
||||
"invite_link_share_manually": "After inviting members, please copy the invite link and send them the link manually"
|
||||
"saml_settings":"Your SAML settings have been saved, please login from incognito window to confirm that it has been set up correctly"
|
||||
}
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"rps_over_100": "You are sending data at more than 100 RPS, your ingestion may be rate limited. Please reach out to us via Intercom support."
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { AxiosError, AxiosResponse } from 'axios';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorResponse } from 'types/api';
|
||||
import { ErrorStatusCode } from 'types/common';
|
||||
|
||||
@@ -10,7 +10,7 @@ export function ErrorResponseHandler(error: AxiosError): ErrorResponse {
|
||||
const statusCode = response.status as ErrorStatusCode;
|
||||
|
||||
if (statusCode >= 400 && statusCode < 500) {
|
||||
const { data } = response as AxiosResponse;
|
||||
const { data } = response;
|
||||
|
||||
if (statusCode === 404) {
|
||||
return {
|
||||
|
||||
@@ -3,9 +3,9 @@ import { ApiResponse } from 'types/api';
|
||||
import { Props } from 'types/api/dashboard/get';
|
||||
import { Dashboard } from 'types/api/dashboard/getAll';
|
||||
|
||||
const getDashboard = (props: Props): Promise<Dashboard> =>
|
||||
const get = (props: Props): Promise<Dashboard> =>
|
||||
axios
|
||||
.get<ApiResponse<Dashboard>>(`/dashboards/${props.uuid}`)
|
||||
.then((res) => res.data.data);
|
||||
|
||||
export default getDashboard;
|
||||
export default get;
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
import { ApiV3Instance as axios } from 'api';
|
||||
import { ApiResponse } from 'types/api';
|
||||
import { ICompositeMetricQuery } from 'types/api/alerts/compositeQuery';
|
||||
import { QueryRangePayload } from 'types/api/metrics/getQueryRange';
|
||||
|
||||
interface IQueryRangeFormat {
|
||||
compositeQuery: ICompositeMetricQuery;
|
||||
}
|
||||
|
||||
export const getQueryRangeFormat = (
|
||||
props?: Partial<QueryRangePayload>,
|
||||
): Promise<IQueryRangeFormat> =>
|
||||
axios
|
||||
.post<ApiResponse<IQueryRangeFormat>>('/query_range/format', props)
|
||||
.then((res) => res.data.data);
|
||||
@@ -4,7 +4,7 @@
|
||||
import getLocalStorageApi from 'api/browser/localstorage/get';
|
||||
import loginApi from 'api/user/login';
|
||||
import afterLogin from 'AppRoutes/utils';
|
||||
import axios, { AxiosResponse, InternalAxiosRequestConfig } from 'axios';
|
||||
import axios, { AxiosRequestConfig, AxiosResponse } from 'axios';
|
||||
import { ENVIRONMENT } from 'constants/env';
|
||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||
import store from 'store';
|
||||
@@ -17,16 +17,14 @@ const interceptorsResponse = (
|
||||
): Promise<AxiosResponse<any>> => Promise.resolve(value);
|
||||
|
||||
const interceptorsRequestResponse = (
|
||||
value: InternalAxiosRequestConfig,
|
||||
): InternalAxiosRequestConfig => {
|
||||
value: AxiosRequestConfig,
|
||||
): AxiosRequestConfig => {
|
||||
const token =
|
||||
store.getState().app.user?.accessJwt ||
|
||||
getLocalStorageApi(LOCALSTORAGE.AUTH_TOKEN) ||
|
||||
'';
|
||||
|
||||
if (value && value.headers) {
|
||||
value.headers.Authorization = token ? `Bearer ${token}` : '';
|
||||
}
|
||||
value.headers.Authorization = token ? `Bearer ${token}` : '';
|
||||
|
||||
return value;
|
||||
};
|
||||
@@ -94,8 +92,8 @@ const instance = axios.create({
|
||||
baseURL: `${ENVIRONMENT.baseURL}${apiV1}`,
|
||||
});
|
||||
|
||||
instance.interceptors.request.use(interceptorsRequestResponse);
|
||||
instance.interceptors.response.use(interceptorsResponse, interceptorRejected);
|
||||
instance.interceptors.request.use(interceptorsRequestResponse);
|
||||
|
||||
export const AxiosAlertManagerInstance = axios.create({
|
||||
baseURL: `${ENVIRONMENT.baseURL}${apiAlertManager}`,
|
||||
|
||||
@@ -9,10 +9,9 @@ import {
|
||||
|
||||
export const getMetricsQueryRange = async (
|
||||
props: QueryRangePayload,
|
||||
signal: AbortSignal,
|
||||
): Promise<SuccessResponse<MetricRangePayloadV3> | ErrorResponse> => {
|
||||
try {
|
||||
const response = await axios.post('/query_range', props, { signal });
|
||||
const response = await axios.post('/query_range', props);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
|
||||
@@ -5,7 +5,6 @@ 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 { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
import {
|
||||
DeleteViewHandlerProps,
|
||||
@@ -36,45 +35,6 @@ export const getViewDetailsUsingViewKey: GetViewDetailsUsingViewKey = (
|
||||
return undefined;
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export const omitIdFromQuery = (query: Query | null): any => ({
|
||||
...query,
|
||||
builder: {
|
||||
...query?.builder,
|
||||
queryData: query?.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,
|
||||
};
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
export const isQueryUpdatedInView = ({
|
||||
viewKey,
|
||||
data,
|
||||
@@ -88,7 +48,43 @@ export const isQueryUpdatedInView = ({
|
||||
const { query, panelType } = currentViewDetails;
|
||||
|
||||
// Omitting id from aggregateAttribute and groupBy
|
||||
const updatedCurrentQuery = omitIdFromQuery(stagedQuery);
|
||||
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 ||
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
} from '@ant-design/icons';
|
||||
import Convert from 'ansi-to-html';
|
||||
import { Button, Divider, Row, Typography } from 'antd';
|
||||
import LogDetail from 'components/LogDetail';
|
||||
import LogsExplorerContext from 'container/LogsExplorerContext';
|
||||
import dayjs from 'dayjs';
|
||||
import dompurify from 'dompurify';
|
||||
@@ -94,15 +95,11 @@ function LogSelectedField({
|
||||
type ListLogViewProps = {
|
||||
logData: ILog;
|
||||
selectedFields: IField[];
|
||||
onSetActiveLog: (log: ILog) => void;
|
||||
onAddToQuery: AddToQueryHOCProps['onAddToQuery'];
|
||||
};
|
||||
|
||||
function ListLogView({
|
||||
logData,
|
||||
selectedFields,
|
||||
onSetActiveLog,
|
||||
onAddToQuery,
|
||||
}: ListLogViewProps): JSX.Element {
|
||||
const flattenLogData = useMemo(() => FlatLogData(logData), [logData]);
|
||||
|
||||
@@ -116,6 +113,12 @@ function ListLogView({
|
||||
onSetActiveLog: handleSetActiveContextLog,
|
||||
onClearActiveLog: handleClearActiveContextLog,
|
||||
} = useActiveLog();
|
||||
const {
|
||||
activeLog,
|
||||
onSetActiveLog,
|
||||
onClearActiveLog,
|
||||
onAddToQuery,
|
||||
} = useActiveLog();
|
||||
|
||||
const handleDetailedView = useCallback(() => {
|
||||
onSetActiveLog(logData);
|
||||
@@ -220,6 +223,12 @@ function ListLogView({
|
||||
onClose={handleClearActiveContextLog}
|
||||
/>
|
||||
)}
|
||||
<LogDetail
|
||||
log={activeLog}
|
||||
onClose={onClearActiveLog}
|
||||
onAddToQuery={onAddToQuery}
|
||||
onClickActionItem={onAddToQuery}
|
||||
/>
|
||||
</Row>
|
||||
</Container>
|
||||
);
|
||||
|
||||
@@ -27,7 +27,6 @@ function DynamicColumnTable({
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setColumnsData(columns);
|
||||
const visibleColumns = getVisibleColumns({
|
||||
tablesource,
|
||||
columnsData: columns,
|
||||
@@ -43,7 +42,7 @@ function DynamicColumnTable({
|
||||
: undefined,
|
||||
);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [columns]);
|
||||
}, []);
|
||||
|
||||
const onToggleHandler = (index: number) => (
|
||||
checked: boolean,
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
/* eslint-disable sonarjs/cognitive-complexity */
|
||||
import './Uplot.styles.scss';
|
||||
import './uplot.scss';
|
||||
|
||||
import { Typography } from 'antd';
|
||||
import { ToggleGraphProps } from 'components/Graph/types';
|
||||
import { LineChart } from 'lucide-react';
|
||||
import ErrorBoundaryFallback from 'pages/ErrorBoundaryFallback/ErrorBoundaryFallback';
|
||||
import {
|
||||
forwardRef,
|
||||
@@ -128,16 +127,6 @@ const Uplot = forwardRef<ToggleGraphProps | undefined, UplotProps>(
|
||||
}
|
||||
}, [data, resetScales, create]);
|
||||
|
||||
if (data && data[0] && data[0]?.length === 0) {
|
||||
return (
|
||||
<div className="uplot-no-data not-found">
|
||||
<LineChart size={48} strokeWidth={0.5} />
|
||||
|
||||
<Typography>No Data</Typography>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ErrorBoundary FallbackComponent={ErrorBoundaryFallback}>
|
||||
<div className="uplot-graph-container" ref={targetRef}>
|
||||
|
||||
@@ -13,11 +13,3 @@
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.uplot-no-data {
|
||||
position: relative;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
const MAX_RPS_LIMIT = 100;
|
||||
export { MAX_RPS_LIMIT };
|
||||
@@ -10,7 +10,7 @@ import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import { useResizeObserver } from 'hooks/useDimensions';
|
||||
import { getUPlotChartOptions } from 'lib/uPlotLib/getUplotChartOptions';
|
||||
import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData';
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useMemo, useRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
@@ -18,7 +18,6 @@ import { AlertDef } from 'types/api/alerts/def';
|
||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import { getTimeRange } from 'utils/getTimeRange';
|
||||
|
||||
import { ChartContainer, FailedMessageContainer } from './styles';
|
||||
import { getThresholdLabel } from './utils';
|
||||
@@ -33,7 +32,6 @@ export interface ChartPreviewProps {
|
||||
alertDef?: AlertDef;
|
||||
userQueryKey?: string;
|
||||
allowSelectedIntervalForStepGen?: boolean;
|
||||
yAxisUnit: string;
|
||||
}
|
||||
|
||||
function ChartPreview({
|
||||
@@ -46,17 +44,12 @@ function ChartPreview({
|
||||
userQueryKey,
|
||||
allowSelectedIntervalForStepGen = false,
|
||||
alertDef,
|
||||
yAxisUnit,
|
||||
}: ChartPreviewProps): JSX.Element | null {
|
||||
const { t } = useTranslation('alerts');
|
||||
const threshold = alertDef?.condition.target || 0;
|
||||
const [minTimeScale, setMinTimeScale] = useState<number>();
|
||||
const [maxTimeScale, setMaxTimeScale] = useState<number>();
|
||||
|
||||
const { minTime, maxTime, selectedTime: globalSelectedInterval } = useSelector<
|
||||
AppState,
|
||||
GlobalReducer
|
||||
>((state) => state.globalTime);
|
||||
const { minTime, maxTime } = useSelector<AppState, GlobalReducer>(
|
||||
(state) => state.globalTime,
|
||||
);
|
||||
|
||||
const canQuery = useMemo((): boolean => {
|
||||
if (!query || query == null) {
|
||||
@@ -106,13 +99,6 @@ function ChartPreview({
|
||||
|
||||
const graphRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect((): void => {
|
||||
const { startTime, endTime } = getTimeRange(queryResponse);
|
||||
|
||||
setMinTimeScale(startTime);
|
||||
setMaxTimeScale(endTime);
|
||||
}, [maxTime, minTime, globalSelectedInterval, queryResponse]);
|
||||
|
||||
const chartData = getUPlotChartData(queryResponse?.data?.payload);
|
||||
|
||||
const containerDimensions = useResizeObserver(graphRef);
|
||||
@@ -126,11 +112,9 @@ function ChartPreview({
|
||||
() =>
|
||||
getUPlotChartOptions({
|
||||
id: 'alert_legend_widget',
|
||||
yAxisUnit,
|
||||
yAxisUnit: query?.unit,
|
||||
apiResponse: queryResponse?.data?.payload,
|
||||
dimensions: containerDimensions,
|
||||
minTimeScale,
|
||||
maxTimeScale,
|
||||
isDarkMode,
|
||||
thresholds: [
|
||||
{
|
||||
@@ -145,18 +129,16 @@ function ChartPreview({
|
||||
optionName,
|
||||
threshold,
|
||||
alertDef?.condition.targetUnit,
|
||||
yAxisUnit,
|
||||
query?.unit,
|
||||
)})`,
|
||||
thresholdUnit: alertDef?.condition.targetUnit,
|
||||
},
|
||||
],
|
||||
}),
|
||||
[
|
||||
yAxisUnit,
|
||||
query?.unit,
|
||||
queryResponse?.data?.payload,
|
||||
containerDimensions,
|
||||
minTimeScale,
|
||||
maxTimeScale,
|
||||
isDarkMode,
|
||||
threshold,
|
||||
t,
|
||||
@@ -186,7 +168,7 @@ function ChartPreview({
|
||||
name={name || 'Chart Preview'}
|
||||
panelData={queryResponse.data?.payload.data.newResult.data.result || []}
|
||||
query={query || initialQueriesMap.metrics}
|
||||
yAxisUnit={yAxisUnit}
|
||||
yAxisUnit={query?.unit}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -61,20 +61,8 @@ export const getThresholdLabel = (
|
||||
unit === MiscellaneousFormats.PercentUnit ||
|
||||
yAxisUnit === MiscellaneousFormats.PercentUnit
|
||||
) {
|
||||
if (unit === MiscellaneousFormats.Percent) {
|
||||
return `${value}%`;
|
||||
}
|
||||
return `${value * 100}%`;
|
||||
}
|
||||
if (
|
||||
unit === MiscellaneousFormats.Percent ||
|
||||
yAxisUnit === MiscellaneousFormats.Percent
|
||||
) {
|
||||
if (unit === MiscellaneousFormats.PercentUnit) {
|
||||
return `${value * 100}%`;
|
||||
}
|
||||
return `${value}%`;
|
||||
}
|
||||
return `${value} ${optionName}`;
|
||||
};
|
||||
|
||||
|
||||
@@ -82,7 +82,6 @@ function FormAlertRules({
|
||||
|
||||
// alertDef holds the form values to be posted
|
||||
const [alertDef, setAlertDef] = useState<AlertDef>(initialValue);
|
||||
const [yAxisUnit, setYAxisUnit] = useState<string>(currentQuery.unit || '');
|
||||
|
||||
// initQuery contains initial query when component was mounted
|
||||
const initQuery = useMemo(() => initialValue.condition.compositeQuery, [
|
||||
@@ -401,7 +400,6 @@ function FormAlertRules({
|
||||
query={stagedQuery}
|
||||
selectedInterval={globalSelectedInterval}
|
||||
alertDef={alertDef}
|
||||
yAxisUnit={yAxisUnit || ''}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -417,7 +415,6 @@ function FormAlertRules({
|
||||
query={stagedQuery}
|
||||
alertDef={alertDef}
|
||||
selectedInterval={globalSelectedInterval}
|
||||
yAxisUnit={yAxisUnit || ''}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -430,8 +427,7 @@ function FormAlertRules({
|
||||
currentQuery.queryType === EQueryType.QUERY_BUILDER &&
|
||||
alertType !== AlertTypes.METRICS_BASED_ALERT;
|
||||
|
||||
const onUnitChangeHandler = (value: string): void => {
|
||||
setYAxisUnit(value);
|
||||
const onUnitChangeHandler = (): void => {
|
||||
// reset target unit
|
||||
setAlertDef((def) => ({
|
||||
...def,
|
||||
@@ -461,10 +457,7 @@ function FormAlertRules({
|
||||
renderPromAndChQueryChartPreview()}
|
||||
|
||||
<StepContainer>
|
||||
<BuilderUnitsFilter
|
||||
onChange={onUnitChangeHandler}
|
||||
yAxisUnit={yAxisUnit}
|
||||
/>
|
||||
<BuilderUnitsFilter onChange={onUnitChangeHandler} />
|
||||
</StepContainer>
|
||||
|
||||
<QuerySection
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
.span-container {
|
||||
.spanDetails {
|
||||
position: absolute;
|
||||
height: 50px;
|
||||
padding: 8px;
|
||||
min-width: 150px;
|
||||
background: lightcyan;
|
||||
color: black;
|
||||
bottom: 24px;
|
||||
left: 0;
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
import '../GantChart.styles.scss';
|
||||
|
||||
import { Popover, Typography } from 'antd';
|
||||
import { convertTimeToRelevantUnit } from 'container/TraceDetail/utils';
|
||||
import dayjs from 'dayjs';
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import { useEffect } from 'react';
|
||||
import { toFixed } from 'utils/toFixed';
|
||||
|
||||
import { SpanBorder, SpanLine, SpanText, SpanWrapper } from './styles';
|
||||
|
||||
interface SpanLengthProps {
|
||||
globalStart: number;
|
||||
startTime: number;
|
||||
name: string;
|
||||
width: string;
|
||||
leftOffset: string;
|
||||
bgColor: string;
|
||||
inMsCount: number;
|
||||
}
|
||||
|
||||
function Span(props: SpanLengthProps): JSX.Element {
|
||||
const {
|
||||
width,
|
||||
leftOffset,
|
||||
bgColor,
|
||||
inMsCount,
|
||||
startTime,
|
||||
name,
|
||||
globalStart,
|
||||
} = props;
|
||||
const isDarkMode = useIsDarkMode();
|
||||
const { time, timeUnitName } = convertTimeToRelevantUnit(inMsCount);
|
||||
|
||||
useEffect(() => {
|
||||
document.documentElement.scrollTop = document.documentElement.clientHeight;
|
||||
document.documentElement.scrollLeft = document.documentElement.clientWidth;
|
||||
}, []);
|
||||
|
||||
const getContent = (): JSX.Element => {
|
||||
const timeStamp = dayjs(startTime).format('h:mm:ss:SSS A');
|
||||
const startTimeInMs = startTime - globalStart;
|
||||
return (
|
||||
<div>
|
||||
<Typography.Text style={{ marginBottom: '8px' }}>
|
||||
{' '}
|
||||
Duration : {inMsCount}
|
||||
</Typography.Text>
|
||||
<br />
|
||||
<Typography.Text style={{ marginBottom: '8px' }}>
|
||||
Start Time: {startTimeInMs}ms [{timeStamp}]{' '}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<SpanWrapper className="span-container">
|
||||
<SpanLine
|
||||
className="spanLine"
|
||||
isDarkMode={isDarkMode}
|
||||
bgColor={bgColor}
|
||||
leftOffset={leftOffset}
|
||||
width={width}
|
||||
/>
|
||||
|
||||
<div>
|
||||
<Popover
|
||||
style={{
|
||||
left: `${leftOffset}%`,
|
||||
}}
|
||||
title={name}
|
||||
content={getContent()}
|
||||
trigger="hover"
|
||||
placement="left"
|
||||
autoAdjustOverflow
|
||||
>
|
||||
<SpanBorder
|
||||
className="spanTrack"
|
||||
isDarkMode={isDarkMode}
|
||||
bgColor={bgColor}
|
||||
leftOffset={leftOffset}
|
||||
width={width}
|
||||
/>
|
||||
</Popover>
|
||||
</div>
|
||||
|
||||
<SpanText isDarkMode={isDarkMode} leftOffset={leftOffset}>{`${toFixed(
|
||||
time,
|
||||
2,
|
||||
)} ${timeUnitName}`}</SpanText>
|
||||
</SpanWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
export default Span;
|
||||
40
frontend/src/container/GantChart/SpanLength/index.tsx
Normal file
40
frontend/src/container/GantChart/SpanLength/index.tsx
Normal file
@@ -0,0 +1,40 @@
|
||||
import { convertTimeToRelevantUnit } from 'container/TraceDetail/utils';
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import { toFixed } from 'utils/toFixed';
|
||||
|
||||
import { SpanBorder, SpanLine, SpanText, SpanWrapper } from './styles';
|
||||
|
||||
interface SpanLengthProps {
|
||||
width: string;
|
||||
leftOffset: string;
|
||||
bgColor: string;
|
||||
inMsCount: number;
|
||||
}
|
||||
|
||||
function SpanLength(props: SpanLengthProps): JSX.Element {
|
||||
const { width, leftOffset, bgColor, inMsCount } = props;
|
||||
const isDarkMode = useIsDarkMode();
|
||||
const { time, timeUnitName } = convertTimeToRelevantUnit(inMsCount);
|
||||
return (
|
||||
<SpanWrapper>
|
||||
<SpanLine
|
||||
isDarkMode={isDarkMode}
|
||||
bgColor={bgColor}
|
||||
leftOffset={leftOffset}
|
||||
width={width}
|
||||
/>
|
||||
<SpanBorder
|
||||
isDarkMode={isDarkMode}
|
||||
bgColor={bgColor}
|
||||
leftOffset={leftOffset}
|
||||
width={width}
|
||||
/>
|
||||
<SpanText isDarkMode={isDarkMode} leftOffset={leftOffset}>{`${toFixed(
|
||||
time,
|
||||
2,
|
||||
)} ${timeUnitName}`}</SpanText>
|
||||
</SpanWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
export default SpanLength;
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
import { ITraceTree } from 'types/api/trace/getTraceItem';
|
||||
|
||||
import { ITraceMetaData } from '..';
|
||||
import Span from '../Span';
|
||||
import SpanLength from '../SpanLength';
|
||||
import SpanName from '../SpanName';
|
||||
import { getMetaDataFromSpanTree, getTopLeftFromBody } from '../utils';
|
||||
import {
|
||||
@@ -158,7 +158,7 @@ function Trace(props: TraceProps): JSX.Element {
|
||||
isDarkMode={isDarkMode}
|
||||
onClick={onClickTreeExpansion}
|
||||
>
|
||||
<Typography style={{ wordBreak: 'normal' }}>{totalSpans}</Typography>
|
||||
<Typography>{totalSpans}</Typography>
|
||||
<CaretContainer>{icon}</CaretContainer>
|
||||
</CardComponent>
|
||||
)}
|
||||
@@ -169,10 +169,7 @@ function Trace(props: TraceProps): JSX.Element {
|
||||
</StyledRow>
|
||||
</StyledCol>
|
||||
<Col flex="1">
|
||||
<Span
|
||||
globalStart={globalStart}
|
||||
startTime={startTime}
|
||||
name={name}
|
||||
<SpanLength
|
||||
leftOffset={nodeLeftOffset.toString()}
|
||||
width={width.toString()}
|
||||
bgColor={serviceColour}
|
||||
|
||||
@@ -41,11 +41,7 @@ function GanttChart(props: GanttChartProps): JSX.Element {
|
||||
onClick={handleCollapse}
|
||||
title={isExpandAll ? 'Collapse All' : 'Expand All'}
|
||||
>
|
||||
{isExpandAll ? (
|
||||
<MinusSquareOutlined style={{ fontSize: '16px', color: '#08c' }} />
|
||||
) : (
|
||||
<PlusSquareOutlined style={{ fontSize: '16px', color: '#08c' }} />
|
||||
)}
|
||||
{isExpandAll ? <MinusSquareOutlined /> : <PlusSquareOutlined />}
|
||||
</CollapseButton>
|
||||
<CardWrapper>
|
||||
<Trace
|
||||
|
||||
@@ -11,7 +11,6 @@ export const Wrapper = styled.ul`
|
||||
border-left: 1px solid #434343;
|
||||
padding-left: 1rem;
|
||||
width: 100%;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
ul li {
|
||||
@@ -45,4 +44,6 @@ export const CardContainer = styled.li`
|
||||
export const CollapseButton = styled.div`
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
font-size: 1.2rem;
|
||||
`;
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { Tooltip } from 'antd';
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
|
||||
import { LabelContainer } from '../styles';
|
||||
@@ -24,9 +23,7 @@ function Label({
|
||||
disabled={disabled}
|
||||
onClick={onClickHandler}
|
||||
>
|
||||
<Tooltip title={label} placement="topLeft">
|
||||
{getAbbreviatedLabel(label)}
|
||||
</Tooltip>
|
||||
{getAbbreviatedLabel(label)}
|
||||
</LabelContainer>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@ import { useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import uPlot from 'uplot';
|
||||
import { getTimeRange } from 'utils/getTimeRange';
|
||||
|
||||
import { PANEL_TYPES_VS_FULL_VIEW_TABLE } from './contants';
|
||||
import GraphManager from './GraphManager';
|
||||
@@ -93,21 +92,6 @@ function FullView({
|
||||
|
||||
const isDarkMode = useIsDarkMode();
|
||||
|
||||
const [minTimeScale, setMinTimeScale] = useState<number>();
|
||||
const [maxTimeScale, setMaxTimeScale] = useState<number>();
|
||||
|
||||
const { minTime, maxTime, selectedTime: globalSelectedInterval } = useSelector<
|
||||
AppState,
|
||||
GlobalReducer
|
||||
>((state) => state.globalTime);
|
||||
|
||||
useEffect((): void => {
|
||||
const { startTime, endTime } = getTimeRange(response);
|
||||
|
||||
setMinTimeScale(startTime);
|
||||
setMaxTimeScale(endTime);
|
||||
}, [maxTime, minTime, globalSelectedInterval, response]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!response.isFetching && fullViewRef.current) {
|
||||
const width = fullViewRef.current?.clientWidth
|
||||
@@ -130,8 +114,6 @@ function FullView({
|
||||
graphsVisibilityStates,
|
||||
setGraphsVisibilityStates,
|
||||
thresholds: widget.thresholds,
|
||||
minTimeScale,
|
||||
maxTimeScale,
|
||||
});
|
||||
|
||||
setChartOptions(newChartOptions);
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
import '../GridCardLayout.styles.scss';
|
||||
|
||||
import { Skeleton, Typography } from 'antd';
|
||||
import cx from 'classnames';
|
||||
import { ToggleGraphProps } from 'components/Graph/types';
|
||||
import { SOMETHING_WENT_WRONG } from 'constants/api';
|
||||
import { QueryParams } from 'constants/query';
|
||||
@@ -301,10 +298,7 @@ function WidgetGraphComponent({
|
||||
</div>
|
||||
{queryResponse.isLoading && <Skeleton />}
|
||||
{queryResponse.isSuccess && (
|
||||
<div
|
||||
className={cx('widget-graph-container', widget.panelTypes)}
|
||||
ref={graphRef}
|
||||
>
|
||||
<div style={{ height: '90%' }} ref={graphRef}>
|
||||
<GridPanelSwitch
|
||||
panelType={widget.panelTypes}
|
||||
data={data}
|
||||
|
||||
@@ -15,7 +15,6 @@ import { useDispatch, useSelector } from 'react-redux';
|
||||
import { UpdateTimeInterval } from 'store/actions';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import { getTimeRange } from 'utils/getTimeRange';
|
||||
|
||||
import EmptyWidget from '../EmptyWidget';
|
||||
import { MenuItemKeys } from '../WidgetHeader/contants';
|
||||
@@ -35,8 +34,6 @@ function GridCardGraph({
|
||||
const dispatch = useDispatch();
|
||||
const [errorMessage, setErrorMessage] = useState<string>();
|
||||
const { toScrollWidgetId, setToScrollWidgetId } = useDashboard();
|
||||
const [minTimeScale, setMinTimeScale] = useState<number>();
|
||||
const [maxTimeScale, setMaxTimeScale] = useState<number>();
|
||||
|
||||
const onDragSelect = useCallback(
|
||||
(start: number, end: number): void => {
|
||||
@@ -65,16 +62,16 @@ function GridCardGraph({
|
||||
}
|
||||
}, [toScrollWidgetId, setToScrollWidgetId, widget.id]);
|
||||
|
||||
const updatedQuery = useStepInterval(widget?.query);
|
||||
|
||||
const isEmptyWidget =
|
||||
widget?.id === PANEL_TYPES.EMPTY_WIDGET || isEmpty(widget);
|
||||
|
||||
const { minTime, maxTime, selectedTime: globalSelectedInterval } = useSelector<
|
||||
AppState,
|
||||
GlobalReducer
|
||||
>((state) => state.globalTime);
|
||||
|
||||
const updatedQuery = useStepInterval(widget?.query);
|
||||
|
||||
const isEmptyWidget =
|
||||
widget?.id === PANEL_TYPES.EMPTY_WIDGET || isEmpty(widget);
|
||||
|
||||
const queryResponse = useGetQueryRange(
|
||||
{
|
||||
selectedTime: widget?.timePreferance,
|
||||
@@ -106,13 +103,6 @@ function GridCardGraph({
|
||||
|
||||
const containerDimensions = useResizeObserver(graphRef);
|
||||
|
||||
useEffect((): void => {
|
||||
const { startTime, endTime } = getTimeRange(queryResponse);
|
||||
|
||||
setMinTimeScale(startTime);
|
||||
setMaxTimeScale(endTime);
|
||||
}, [maxTime, minTime, globalSelectedInterval, queryResponse]);
|
||||
|
||||
const chartData = getUPlotChartData(queryResponse?.data?.payload, fillSpans);
|
||||
|
||||
const isDarkMode = useIsDarkMode();
|
||||
@@ -133,8 +123,6 @@ function GridCardGraph({
|
||||
yAxisUnit: widget?.yAxisUnit,
|
||||
onClickHandler,
|
||||
thresholds: widget.thresholds,
|
||||
minTimeScale,
|
||||
maxTimeScale,
|
||||
}),
|
||||
[
|
||||
widget?.id,
|
||||
@@ -145,8 +133,6 @@ function GridCardGraph({
|
||||
isDarkMode,
|
||||
onDragSelect,
|
||||
onClickHandler,
|
||||
minTimeScale,
|
||||
maxTimeScale,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
.fullscreen-grid-container {
|
||||
overflow: auto;
|
||||
|
||||
|
||||
.react-grid-layout {
|
||||
border: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
.widget-graph-container {
|
||||
height: 100%;
|
||||
|
||||
&.graph {
|
||||
height: calc(100% - 30px);
|
||||
}
|
||||
.fullscreen-grid-container--light {
|
||||
@extend .fullscreen-grid-container;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ import './GridCardLayout.styles.scss';
|
||||
import { PlusOutlined, SaveFilled } from '@ant-design/icons';
|
||||
import { SOMETHING_WENT_WRONG } from 'constants/api';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { themeColors } from 'constants/theme';
|
||||
import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard';
|
||||
import useComponentPermission from 'hooks/useComponentPermission';
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
@@ -142,7 +141,14 @@ function GraphLayout({ onAddPanelHandler }: GraphLayoutProps): JSX.Element {
|
||||
)}
|
||||
</ButtonContainer>
|
||||
|
||||
<FullScreen handle={handle} className="fullscreen-grid-container">
|
||||
<FullScreen
|
||||
handle={handle}
|
||||
className={
|
||||
isDarkMode
|
||||
? 'fullscreen-grid-container'
|
||||
: 'fullscreen-grid-container--light'
|
||||
}
|
||||
>
|
||||
<ReactGridLayout
|
||||
cols={12}
|
||||
rowHeight={100}
|
||||
@@ -156,7 +162,6 @@ function GraphLayout({ onAddPanelHandler }: GraphLayoutProps): JSX.Element {
|
||||
onLayoutChange={setLayouts}
|
||||
draggableHandle=".drag-handle"
|
||||
layout={layouts}
|
||||
style={{ backgroundColor: isDarkMode ? '' : themeColors.snowWhite }}
|
||||
>
|
||||
{layouts.map((layout) => {
|
||||
const { i: id } = layout;
|
||||
|
||||
@@ -2,12 +2,9 @@
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
height: 40px;
|
||||
height: 30px;
|
||||
width: 100%;
|
||||
padding: 0.5rem;
|
||||
box-sizing: border-box;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.widget-header-title {
|
||||
@@ -22,10 +19,6 @@
|
||||
visibility: hidden;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
cursor: pointer;
|
||||
font: 14px;
|
||||
font-weight: 600;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.widget-header-hover {
|
||||
|
||||
@@ -10,11 +10,11 @@ import {
|
||||
MoreOutlined,
|
||||
WarningOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { Dropdown, MenuProps, Tooltip, Typography } from 'antd';
|
||||
import { Button, Dropdown, MenuProps, Tooltip, Typography } from 'antd';
|
||||
import Spinner from 'components/Spinner';
|
||||
import { QueryParams } from 'constants/query';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import useCreateAlerts from 'hooks/queryBuilder/useCreateAlerts';
|
||||
import ROUTES from 'constants/routes';
|
||||
import useComponentPermission from 'hooks/useComponentPermission';
|
||||
import history from 'lib/history';
|
||||
import { ReactNode, useCallback, useMemo } from 'react';
|
||||
@@ -71,7 +71,13 @@ function WidgetHeader({
|
||||
);
|
||||
}, [widget.id, widget.panelTypes, widget.query]);
|
||||
|
||||
const onCreateAlertsHandler = useCreateAlerts(widget);
|
||||
const onCreateAlertsHandler = useCallback(() => {
|
||||
history.push(
|
||||
`${ROUTES.ALERTS_NEW}?${QueryParams.compositeQuery}=${encodeURIComponent(
|
||||
JSON.stringify(widget.query),
|
||||
)}`,
|
||||
);
|
||||
}, [widget]);
|
||||
|
||||
const keyMethodMapping = useMemo(
|
||||
() => ({
|
||||
@@ -193,7 +199,9 @@ function WidgetHeader({
|
||||
</Tooltip>
|
||||
)}
|
||||
<Dropdown menu={menu} trigger={['hover']} placement="bottomRight">
|
||||
<MoreOutlined
|
||||
<Button
|
||||
type="default"
|
||||
icon={<MoreOutlined />}
|
||||
className={`widget-header-more-options ${
|
||||
parentHover ? 'widget-header-hover' : ''
|
||||
}`}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Button as ButtonComponent, Card as CardComponent, Space } from 'antd';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { StyledCSS } from 'container/GantChart/Trace/styles';
|
||||
import RGL, { WidthProvider } from 'react-grid-layout';
|
||||
import styled, { css } from 'styled-components';
|
||||
import styled, { css, FlattenSimpleInterpolation } from 'styled-components';
|
||||
|
||||
const ReactGridLayoutComponent = WidthProvider(RGL);
|
||||
|
||||
@@ -17,8 +17,14 @@ export const Card = styled(CardComponent)<CardProps>`
|
||||
}
|
||||
|
||||
.ant-card-body {
|
||||
height: calc(100% - 40px);
|
||||
height: 90%;
|
||||
padding: 0;
|
||||
${({ $panelType }): FlattenSimpleInterpolation =>
|
||||
$panelType === PANEL_TYPES.TABLE
|
||||
? css`
|
||||
padding-top: 1.8rem;
|
||||
`
|
||||
: css``}
|
||||
}
|
||||
`;
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const WrapperStyled = styled.div`
|
||||
height: 95%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
|
||||
& .ant-table-wrapper {
|
||||
@@ -19,13 +19,5 @@ export const WrapperStyled = styled.div`
|
||||
& .ant-table {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
|
||||
> .ant-table-container {
|
||||
> .ant-table-content {
|
||||
> table {
|
||||
min-width: 99% !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -8,18 +8,5 @@
|
||||
.upgrade-link {
|
||||
padding: 0px;
|
||||
padding-right: 4px;
|
||||
display: inline !important;
|
||||
color: white;
|
||||
text-decoration: underline;
|
||||
text-decoration-color: white;
|
||||
text-decoration-thickness: 2px;
|
||||
text-underline-offset: 2px;
|
||||
|
||||
&:hover {
|
||||
color: white;
|
||||
text-decoration: underline;
|
||||
text-decoration-color: white;
|
||||
text-decoration-thickness: 2px;
|
||||
text-underline-offset: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
/* eslint-disable jsx-a11y/no-static-element-interactions */
|
||||
/* eslint-disable jsx-a11y/anchor-is-valid */
|
||||
/* eslint-disable jsx-a11y/click-events-have-key-events */
|
||||
import './Header.styles.scss';
|
||||
|
||||
import {
|
||||
@@ -138,17 +135,16 @@ function HeaderContainer(): JSX.Element {
|
||||
<>
|
||||
{showTrialExpiryBanner && (
|
||||
<div className="trial-expiry-banner">
|
||||
You are in free trial period. Your free trial will end on{' '}
|
||||
You are in free trial period. Your free trial will end on
|
||||
<span>
|
||||
{getFormattedDate(licenseData?.payload?.trialEnd || Date.now())}.
|
||||
</span>
|
||||
{role === 'ADMIN' ? (
|
||||
<span>
|
||||
{' '}
|
||||
Please{' '}
|
||||
<a className="upgrade-link" onClick={handleUpgrade}>
|
||||
Please
|
||||
<Button className="upgrade-link" type="link" onClick={handleUpgrade}>
|
||||
upgrade
|
||||
</a>
|
||||
</Button>
|
||||
to continue using SigNoz features.
|
||||
</span>
|
||||
) : (
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* eslint-disable react/display-name */
|
||||
import { PlusOutlined } from '@ant-design/icons';
|
||||
import { Input, Typography } from 'antd';
|
||||
import type { ColumnsType } from 'antd/es/table/interface';
|
||||
import { Typography } from 'antd';
|
||||
import { ColumnsType } from 'antd/lib/table';
|
||||
import saveAlertApi from 'api/alerts/save';
|
||||
import DropDown from 'components/DropDown/DropDown';
|
||||
import {
|
||||
@@ -14,12 +14,9 @@ import LabelColumn from 'components/TableRenderer/LabelColumn';
|
||||
import TextToolTip from 'components/TextToolTip';
|
||||
import { QueryParams } from 'constants/query';
|
||||
import ROUTES from 'constants/routes';
|
||||
import useSortableTable from 'hooks/ResizeTable/useSortableTable';
|
||||
import useComponentPermission from 'hooks/useComponentPermission';
|
||||
import useDebouncedFn from 'hooks/useDebouncedFunction';
|
||||
import useInterval from 'hooks/useInterval';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import useUrlQuery from 'hooks/useUrlQuery';
|
||||
import history from 'lib/history';
|
||||
import { mapQueryDataFromApi } from 'lib/newQueryBuilder/queryBuilderMappers/mapQueryDataFromApi';
|
||||
import { useCallback, useState } from 'react';
|
||||
@@ -32,19 +29,12 @@ import { GettableAlert } from 'types/api/alerts/get';
|
||||
import AppReducer from 'types/reducer/app';
|
||||
|
||||
import DeleteAlert from './DeleteAlert';
|
||||
import {
|
||||
Button,
|
||||
ButtonContainer,
|
||||
ColumnButton,
|
||||
SearchContainer,
|
||||
} from './styles';
|
||||
import { Button, ButtonContainer, ColumnButton } from './styles';
|
||||
import Status from './TableComponents/Status';
|
||||
import ToggleAlertState from './ToggleAlertState';
|
||||
import { filterAlerts } from './utils';
|
||||
|
||||
const { Search } = Input;
|
||||
|
||||
function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
|
||||
const [data, setData] = useState<GettableAlert[]>(allAlertRules || []);
|
||||
const { t } = useTranslation('common');
|
||||
const { role, featureResponse } = useSelector<AppState, AppReducer>(
|
||||
(state) => state.app,
|
||||
@@ -54,39 +44,13 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
|
||||
role,
|
||||
);
|
||||
|
||||
const params = useUrlQuery();
|
||||
const orderColumnParam = params.get('columnKey');
|
||||
const orderQueryParam = params.get('order');
|
||||
const paginationParam = params.get('page');
|
||||
const searchParams = params.get('search');
|
||||
const [searchString, setSearchString] = useState<string>(searchParams || '');
|
||||
const [data, setData] = useState<GettableAlert[]>(() => {
|
||||
const value = searchString.toLowerCase();
|
||||
const filteredData = filterAlerts(allAlertRules, value);
|
||||
return filteredData || [];
|
||||
});
|
||||
|
||||
// Type asuring
|
||||
const sortingOrder: 'ascend' | 'descend' | null =
|
||||
orderQueryParam === 'ascend' || orderQueryParam === 'descend'
|
||||
? orderQueryParam
|
||||
: null;
|
||||
|
||||
const { sortedInfo, handleChange } = useSortableTable<GettableAlert>(
|
||||
sortingOrder,
|
||||
orderColumnParam || '',
|
||||
searchString,
|
||||
);
|
||||
|
||||
const { notifications: notificationsApi } = useNotifications();
|
||||
|
||||
useInterval(() => {
|
||||
(async (): Promise<void> => {
|
||||
const { data: refetchData, status } = await refetch();
|
||||
if (status === 'success') {
|
||||
const value = searchString.toLowerCase();
|
||||
const filteredData = filterAlerts(refetchData.payload || [], value);
|
||||
setData(filteredData || []);
|
||||
setData(refetchData?.payload || []);
|
||||
}
|
||||
if (status === 'error') {
|
||||
notificationsApi.error({
|
||||
@@ -164,13 +128,6 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
|
||||
}
|
||||
};
|
||||
|
||||
const handleSearch = useDebouncedFn((e: unknown) => {
|
||||
const value = (e as React.BaseSyntheticEvent).target.value.toLowerCase();
|
||||
setSearchString(value);
|
||||
const filteredData = filterAlerts(allAlertRules, value);
|
||||
setData(filteredData);
|
||||
});
|
||||
|
||||
const dynamicColumns: ColumnsType<GettableAlert> = [
|
||||
{
|
||||
title: 'Created At',
|
||||
@@ -185,10 +142,6 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
|
||||
return prev - next;
|
||||
},
|
||||
render: DateComponent,
|
||||
sortOrder:
|
||||
sortedInfo.columnKey === DynamicColumnsKey.CreatedAt
|
||||
? sortedInfo.order
|
||||
: null,
|
||||
},
|
||||
{
|
||||
title: 'Created By',
|
||||
@@ -210,10 +163,6 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
|
||||
return prev - next;
|
||||
},
|
||||
render: DateComponent,
|
||||
sortOrder:
|
||||
sortedInfo.columnKey === DynamicColumnsKey.UpdatedAt
|
||||
? sortedInfo.order
|
||||
: null,
|
||||
},
|
||||
{
|
||||
title: 'Updated By',
|
||||
@@ -234,7 +183,6 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
|
||||
(b.state ? b.state.charCodeAt(0) : 1000) -
|
||||
(a.state ? a.state.charCodeAt(0) : 1000),
|
||||
render: (value): JSX.Element => <Status status={value} />,
|
||||
sortOrder: sortedInfo.columnKey === 'state' ? sortedInfo.order : null,
|
||||
},
|
||||
{
|
||||
title: 'Alert Name',
|
||||
@@ -250,7 +198,6 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
|
||||
render: (value, record): JSX.Element => (
|
||||
<Typography.Link onClick={onEditHandler(record)}>{value}</Typography.Link>
|
||||
),
|
||||
sortOrder: sortedInfo.columnKey === 'name' ? sortedInfo.order : null,
|
||||
},
|
||||
{
|
||||
title: 'Severity',
|
||||
@@ -267,7 +214,6 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
|
||||
|
||||
return <Typography>{severityValue}</Typography>;
|
||||
},
|
||||
sortOrder: sortedInfo.columnKey === 'severity' ? sortedInfo.order : null,
|
||||
},
|
||||
{
|
||||
title: 'Labels',
|
||||
@@ -325,37 +271,26 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
|
||||
|
||||
return (
|
||||
<>
|
||||
<SearchContainer>
|
||||
<Search
|
||||
placeholder="Search by Alert Name, Severity and Labels"
|
||||
onChange={handleSearch}
|
||||
defaultValue={searchString}
|
||||
<ButtonContainer>
|
||||
<TextToolTip
|
||||
{...{
|
||||
text: `More details on how to create alerts`,
|
||||
url: 'https://signoz.io/docs/userguide/alerts-management/',
|
||||
}}
|
||||
/>
|
||||
<ButtonContainer>
|
||||
<TextToolTip
|
||||
{...{
|
||||
text: `More details on how to create alerts`,
|
||||
url: 'https://signoz.io/docs/userguide/alerts-management/',
|
||||
}}
|
||||
/>
|
||||
|
||||
{addNewAlert && (
|
||||
<Button onClick={onClickNewAlertHandler} icon={<PlusOutlined />}>
|
||||
New Alert
|
||||
</Button>
|
||||
)}
|
||||
</ButtonContainer>
|
||||
</SearchContainer>
|
||||
{addNewAlert && (
|
||||
<Button onClick={onClickNewAlertHandler} icon={<PlusOutlined />}>
|
||||
New Alert
|
||||
</Button>
|
||||
)}
|
||||
</ButtonContainer>
|
||||
<DynamicColumnTable
|
||||
tablesource={TableDataSource.Alert}
|
||||
columns={columns}
|
||||
rowKey="id"
|
||||
dataSource={data}
|
||||
dynamicColumns={dynamicColumns}
|
||||
onChange={handleChange}
|
||||
pagination={{
|
||||
defaultCurrent: Number(paginationParam) || 1,
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,17 +1,11 @@
|
||||
import { Button as ButtonComponent } from 'antd';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const SearchContainer = styled.div`
|
||||
&&& {
|
||||
display: flex;
|
||||
margin-bottom: 2rem;
|
||||
align-items: center;
|
||||
gap: 2rem;
|
||||
}
|
||||
`;
|
||||
export const ButtonContainer = styled.div`
|
||||
&&& {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-bottom: 2rem;
|
||||
align-items: center;
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
import { GettableAlert } from 'types/api/alerts/get';
|
||||
|
||||
export const filterAlerts = (
|
||||
allAlertRules: GettableAlert[],
|
||||
filter: string,
|
||||
): GettableAlert[] => {
|
||||
const value = filter.toLowerCase();
|
||||
return allAlertRules.filter((alert) => {
|
||||
const alertName = alert.alert.toLowerCase();
|
||||
const severity = alert.labels?.severity.toLowerCase();
|
||||
const labels = Object.keys(alert.labels || {})
|
||||
.filter((e) => e !== 'severity')
|
||||
.join(' ')
|
||||
.toLowerCase();
|
||||
|
||||
const labelValue = Object.values(alert.labels || {});
|
||||
|
||||
return (
|
||||
alertName.includes(value) ||
|
||||
severity?.includes(value) ||
|
||||
labels.includes(value) ||
|
||||
labelValue.includes(value)
|
||||
);
|
||||
});
|
||||
};
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Card, Typography } from 'antd';
|
||||
import LogDetail from 'components/LogDetail';
|
||||
import ListLogView from 'components/Logs/ListLogView';
|
||||
import RawLogView from 'components/Logs/RawLogView';
|
||||
import Spinner from 'components/Spinner';
|
||||
@@ -11,7 +10,6 @@ import { InfinityWrapperStyled } from 'container/LogsExplorerList/styles';
|
||||
import { convertKeysToColumnFields } from 'container/LogsExplorerList/utils';
|
||||
import { Heading } from 'container/LogsTable/styles';
|
||||
import { useOptionsMenu } from 'container/OptionsMenu';
|
||||
import { useActiveLog } from 'hooks/logs/useActiveLog';
|
||||
import { useCopyLogLink } from 'hooks/logs/useCopyLogLink';
|
||||
import useFontFaceObserver from 'hooks/useFontObserver';
|
||||
import { useEventSource } from 'providers/EventSource';
|
||||
@@ -33,13 +31,6 @@ function LiveLogsList({ logs }: LiveLogsListProps): JSX.Element {
|
||||
|
||||
const { activeLogId } = useCopyLogLink();
|
||||
|
||||
const {
|
||||
activeLog,
|
||||
onClearActiveLog,
|
||||
onAddToQuery,
|
||||
onSetActiveLog,
|
||||
} = useActiveLog();
|
||||
|
||||
const { options } = useOptionsMenu({
|
||||
storageKey: LOCALSTORAGE.LOGS_LIST_OPTIONS,
|
||||
dataSource: DataSource.LOGS,
|
||||
@@ -75,22 +66,10 @@ function LiveLogsList({ logs }: LiveLogsListProps): JSX.Element {
|
||||
}
|
||||
|
||||
return (
|
||||
<ListLogView
|
||||
key={log.id}
|
||||
logData={log}
|
||||
selectedFields={selectedFields}
|
||||
onAddToQuery={onAddToQuery}
|
||||
onSetActiveLog={onSetActiveLog}
|
||||
/>
|
||||
<ListLogView key={log.id} logData={log} selectedFields={selectedFields} />
|
||||
);
|
||||
},
|
||||
[
|
||||
onAddToQuery,
|
||||
onSetActiveLog,
|
||||
options.format,
|
||||
options.maxLines,
|
||||
selectedFields,
|
||||
],
|
||||
[options.format, options.maxLines, selectedFields],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -144,12 +123,6 @@ function LiveLogsList({ logs }: LiveLogsListProps): JSX.Element {
|
||||
)}
|
||||
</InfinityWrapperStyled>
|
||||
)}
|
||||
<LogDetail
|
||||
log={activeLog}
|
||||
onClose={onClearActiveLog}
|
||||
onAddToQuery={onAddToQuery}
|
||||
onClickActionItem={onAddToQuery}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ import { ILog } from 'types/api/logs/log';
|
||||
import ActionItem, { ActionItemProps } from './ActionItem';
|
||||
import FieldRenderer from './FieldRenderer';
|
||||
import {
|
||||
filterKeyForField,
|
||||
flattenObject,
|
||||
jsonToDataNodes,
|
||||
recursiveParseJSON,
|
||||
@@ -99,12 +98,11 @@ function TableView({
|
||||
title: 'Action',
|
||||
width: 11,
|
||||
render: (fieldData: Record<string, string>): JSX.Element | null => {
|
||||
const fieldFilterKey = filterKeyForField(fieldData.field);
|
||||
|
||||
if (!RESTRICTED_FIELDS.includes(fieldFilterKey)) {
|
||||
const fieldKey = fieldData.field.split('.').slice(-1);
|
||||
if (!RESTRICTED_FIELDS.includes(fieldKey[0])) {
|
||||
return (
|
||||
<ActionItem
|
||||
fieldKey={fieldFilterKey}
|
||||
fieldKey={fieldKey[0]}
|
||||
fieldValue={fieldData.value}
|
||||
onClickActionItem={onClickActionItem}
|
||||
/>
|
||||
@@ -121,6 +119,7 @@ function TableView({
|
||||
align: 'left',
|
||||
ellipsis: true,
|
||||
render: (field: string, record): JSX.Element => {
|
||||
const fieldKey = field.split('.').slice(-1);
|
||||
const renderedField = <FieldRenderer field={field} />;
|
||||
|
||||
if (record.field === 'trace_id') {
|
||||
@@ -149,11 +148,10 @@ function TableView({
|
||||
);
|
||||
}
|
||||
|
||||
const fieldFilterKey = filterKeyForField(field);
|
||||
if (!RESTRICTED_FIELDS.includes(fieldFilterKey)) {
|
||||
if (!RESTRICTED_FIELDS.includes(fieldKey[0])) {
|
||||
return (
|
||||
<AddToQueryHOC
|
||||
fieldKey={fieldFilterKey}
|
||||
fieldKey={fieldKey[0]}
|
||||
fieldValue={flattenLogData[field]}
|
||||
onAddToQuery={onAddToQuery}
|
||||
>
|
||||
|
||||
@@ -132,16 +132,6 @@ export const generateFieldKeyForArray = (
|
||||
export const removeObjectFromString = (str: string): string =>
|
||||
str.replace(/\[object Object\]./g, '');
|
||||
|
||||
// Split `str` on the first occurrence of `delimiter`
|
||||
// For example, will return `['a', 'b.c']` when splitting `'a.b.c'` at dots
|
||||
const splitOnce = (str: string, delimiter: string): string[] => {
|
||||
const parts = str.split(delimiter);
|
||||
if (parts.length < 2) {
|
||||
return parts;
|
||||
}
|
||||
return [parts[0], parts.slice(1).join(delimiter)];
|
||||
};
|
||||
|
||||
export const getFieldAttributes = (field: string): IFieldAttributes => {
|
||||
let dataType;
|
||||
let newField;
|
||||
@@ -150,30 +140,18 @@ export const getFieldAttributes = (field: string): IFieldAttributes => {
|
||||
if (field.startsWith('attributes_')) {
|
||||
logType = MetricsType.Tag;
|
||||
const stringWithoutPrefix = field.slice('attributes_'.length);
|
||||
const parts = splitOnce(stringWithoutPrefix, '.');
|
||||
const parts = stringWithoutPrefix.split('.');
|
||||
[dataType, newField] = parts;
|
||||
} else if (field.startsWith('resources_')) {
|
||||
logType = MetricsType.Resource;
|
||||
const stringWithoutPrefix = field.slice('resources_'.length);
|
||||
const parts = splitOnce(stringWithoutPrefix, '.');
|
||||
const parts = stringWithoutPrefix.split('.');
|
||||
[dataType, newField] = parts;
|
||||
}
|
||||
|
||||
return { dataType, newField, logType };
|
||||
};
|
||||
|
||||
// Returns key to be used when filtering for `field` via
|
||||
// the query builder. This is useful for powering filtering
|
||||
// by field values from log details view.
|
||||
export const filterKeyForField = (field: string): string => {
|
||||
// Must work for all 3 of the following types of cases
|
||||
// timestamp -> timestamp
|
||||
// attributes_string.log.file -> log.file
|
||||
// resources_string.k8s.pod.name -> k8s.pod.name
|
||||
const fieldAttribs = getFieldAttributes(field);
|
||||
return fieldAttribs?.newField || field;
|
||||
};
|
||||
|
||||
export const aggregateAttributesResourcesToString = (logData: ILog): string => {
|
||||
const outputJson: ILogAggregateAttributesResources = {
|
||||
body: logData.body,
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Card, Typography } from 'antd';
|
||||
import LogDetail from 'components/LogDetail';
|
||||
// components
|
||||
import ListLogView from 'components/Logs/ListLogView';
|
||||
import RawLogView from 'components/Logs/RawLogView';
|
||||
@@ -9,7 +8,6 @@ import { LOCALSTORAGE } from 'constants/localStorage';
|
||||
import ExplorerControlPanel from 'container/ExplorerControlPanel';
|
||||
import { Heading } from 'container/LogsTable/styles';
|
||||
import { useOptionsMenu } from 'container/OptionsMenu';
|
||||
import { useActiveLog } from 'hooks/logs/useActiveLog';
|
||||
import { useCopyLogLink } from 'hooks/logs/useCopyLogLink';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import useFontFaceObserver from 'hooks/useFontObserver';
|
||||
@@ -39,13 +37,6 @@ function LogsExplorerList({
|
||||
|
||||
const { activeLogId } = useCopyLogLink();
|
||||
|
||||
const {
|
||||
activeLog,
|
||||
onClearActiveLog,
|
||||
onAddToQuery,
|
||||
onSetActiveLog,
|
||||
} = useActiveLog();
|
||||
|
||||
const { options, config } = useOptionsMenu({
|
||||
storageKey: LOCALSTORAGE.LOGS_LIST_OPTIONS,
|
||||
dataSource: initialDataSource || DataSource.METRICS,
|
||||
@@ -85,22 +76,10 @@ function LogsExplorerList({
|
||||
}
|
||||
|
||||
return (
|
||||
<ListLogView
|
||||
key={log.id}
|
||||
logData={log}
|
||||
selectedFields={selectedFields}
|
||||
onAddToQuery={onAddToQuery}
|
||||
onSetActiveLog={onSetActiveLog}
|
||||
/>
|
||||
<ListLogView key={log.id} logData={log} selectedFields={selectedFields} />
|
||||
);
|
||||
},
|
||||
[
|
||||
onAddToQuery,
|
||||
onSetActiveLog,
|
||||
options.format,
|
||||
options.maxLines,
|
||||
selectedFields,
|
||||
],
|
||||
[options.format, options.maxLines, selectedFields],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -170,13 +149,6 @@ function LogsExplorerList({
|
||||
)}
|
||||
|
||||
<InfinityWrapperStyled>{renderContent}</InfinityWrapperStyled>
|
||||
|
||||
<LogDetail
|
||||
log={activeLog}
|
||||
onClose={onClearActiveLog}
|
||||
onAddToQuery={onAddToQuery}
|
||||
onClickActionItem={onAddToQuery}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -147,13 +147,13 @@ function LogsExplorerViews(): JSX.Element {
|
||||
[currentQuery, updateAllQueriesOperators],
|
||||
);
|
||||
|
||||
const {
|
||||
data: listChartData,
|
||||
isFetching: isFetchingListChartData,
|
||||
isLoading: isLoadingListChartData,
|
||||
} = useGetExplorerQueryRange(listChartQuery, PANEL_TYPES.TIME_SERIES, {
|
||||
enabled: !!listChartQuery && panelType === PANEL_TYPES.LIST,
|
||||
});
|
||||
const listChartData = useGetExplorerQueryRange(
|
||||
listChartQuery,
|
||||
PANEL_TYPES.TIME_SERIES,
|
||||
{
|
||||
enabled: !!listChartQuery && panelType === PANEL_TYPES.LIST,
|
||||
},
|
||||
);
|
||||
|
||||
const { data, isFetching, isError } = useGetExplorerQueryRange(
|
||||
requestData,
|
||||
@@ -445,8 +445,12 @@ function LogsExplorerViews(): JSX.Element {
|
||||
if (!stagedQuery) return [];
|
||||
|
||||
if (panelType === PANEL_TYPES.LIST) {
|
||||
if (listChartData && listChartData.payload.data.result.length > 0) {
|
||||
return listChartData.payload.data.result;
|
||||
if (
|
||||
listChartData &&
|
||||
listChartData.data &&
|
||||
listChartData.data.payload.data.result.length > 0
|
||||
) {
|
||||
return listChartData.data.payload.data.result;
|
||||
}
|
||||
return [];
|
||||
}
|
||||
@@ -468,10 +472,7 @@ function LogsExplorerViews(): JSX.Element {
|
||||
|
||||
return (
|
||||
<>
|
||||
<LogsExplorerChart
|
||||
isLoading={isFetchingListChartData || isLoadingListChartData}
|
||||
data={chartData}
|
||||
/>
|
||||
<LogsExplorerChart isLoading={isFetching} data={chartData} />
|
||||
{stagedQuery && (
|
||||
<ActionsWrapper>
|
||||
<ExportPanel
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import './logsTable.styles.scss';
|
||||
|
||||
import { Card, Typography } from 'antd';
|
||||
import LogDetail from 'components/LogDetail';
|
||||
// components
|
||||
import ListLogView from 'components/Logs/ListLogView';
|
||||
import RawLogView from 'components/Logs/RawLogView';
|
||||
@@ -30,12 +29,7 @@ type LogsTableProps = {
|
||||
function LogsTable(props: LogsTableProps): JSX.Element {
|
||||
const { viewMode, linesPerRow } = props;
|
||||
|
||||
const {
|
||||
activeLog,
|
||||
onClearActiveLog,
|
||||
onAddToQuery,
|
||||
onSetActiveLog,
|
||||
} = useActiveLog();
|
||||
const { onSetActiveLog } = useActiveLog();
|
||||
|
||||
useFontFaceObserver(
|
||||
[
|
||||
@@ -75,17 +69,9 @@ function LogsTable(props: LogsTableProps): JSX.Element {
|
||||
return <RawLogView key={log.id} data={log} linesPerRow={linesPerRow} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<ListLogView
|
||||
key={log.id}
|
||||
logData={log}
|
||||
selectedFields={selected}
|
||||
onAddToQuery={onAddToQuery}
|
||||
onSetActiveLog={onSetActiveLog}
|
||||
/>
|
||||
);
|
||||
return <ListLogView key={log.id} logData={log} selectedFields={selected} />;
|
||||
},
|
||||
[logs, viewMode, selected, onAddToQuery, onSetActiveLog, linesPerRow],
|
||||
[logs, viewMode, selected, linesPerRow],
|
||||
);
|
||||
|
||||
const renderContent = useMemo(() => {
|
||||
@@ -124,12 +110,6 @@ function LogsTable(props: LogsTableProps): JSX.Element {
|
||||
{isNoLogs && <Typography>No logs lines found</Typography>}
|
||||
|
||||
{renderContent}
|
||||
<LogDetail
|
||||
log={activeLog}
|
||||
onClose={onClearActiveLog}
|
||||
onAddToQuery={onAddToQuery}
|
||||
onClickActionItem={onAddToQuery}
|
||||
/>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -32,13 +32,19 @@ import {
|
||||
errorPercentage,
|
||||
operationPerSec,
|
||||
} from '../MetricsPageQueries/OverviewQueries';
|
||||
import { Col, ColApDexContainer, ColErrorContainer, Row } from '../styles';
|
||||
import {
|
||||
Card,
|
||||
Col,
|
||||
ColApDexContainer,
|
||||
ColErrorContainer,
|
||||
Row,
|
||||
} from '../styles';
|
||||
import ApDex from './Overview/ApDex';
|
||||
import ServiceOverview from './Overview/ServiceOverview';
|
||||
import TopLevelOperation from './Overview/TopLevelOperations';
|
||||
import TopOperation from './Overview/TopOperation';
|
||||
import TopOperationMetrics from './Overview/TopOperationMetrics';
|
||||
import { Button, Card } from './styles';
|
||||
import { Button } from './styles';
|
||||
import { IServiceName } from './types';
|
||||
import {
|
||||
handleNonInQueryRange,
|
||||
@@ -270,7 +276,7 @@ function Application(): JSX.Element {
|
||||
|
||||
<Col span={12}>
|
||||
<Card>
|
||||
{isSpanMetricEnabled ? <TopOperationMetrics /> : <TopOperation />}{' '}
|
||||
{isSpanMetricEnabled ? <TopOperationMetrics /> : <TopOperation />}
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Button as ButtonComponent, Card as CardComponent } from 'antd';
|
||||
import { Button as ButtonComponent } from 'antd';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const Button = styled(ButtonComponent)`
|
||||
@@ -8,9 +8,3 @@ export const Button = styled(ButtonComponent)`
|
||||
display: none;
|
||||
}
|
||||
`;
|
||||
|
||||
export const Card = styled(CardComponent)`
|
||||
.ant-card-body {
|
||||
padding: 10px;
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
import { TopOperationList } from '../TopOperationsTable';
|
||||
|
||||
interface TopOperation {
|
||||
numCalls: number;
|
||||
errorCount: number;
|
||||
}
|
||||
|
||||
export const getTopOperationList = ({
|
||||
errorCount,
|
||||
numCalls,
|
||||
}: TopOperation): TopOperationList =>
|
||||
({
|
||||
p50: 0,
|
||||
errorCount,
|
||||
name: 'test',
|
||||
numCalls,
|
||||
p95: 0,
|
||||
p99: 0,
|
||||
} as TopOperationList);
|
||||
@@ -8,13 +8,12 @@ import styled from 'styled-components';
|
||||
|
||||
export const Card = styled(CardComponent)`
|
||||
&&& {
|
||||
height: 40vh;
|
||||
overflow: hidden;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.ant-card-body {
|
||||
height: calc(100% - 40px);
|
||||
padding: 0;
|
||||
min-height: 40vh;
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -39,8 +38,7 @@ export const ColErrorContainer = styled(ColComponent)`
|
||||
`;
|
||||
|
||||
export const GraphContainer = styled.div`
|
||||
min-height: calc(40vh - 40px);
|
||||
height: calc(100% - 40px);
|
||||
height: 40vh;
|
||||
`;
|
||||
|
||||
export const GraphTitle = styled(Typography)`
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
import { getTopOperationList } from './__mocks__/getTopOperation';
|
||||
import { TopOperationList } from './TopOperationsTable';
|
||||
import {
|
||||
convertedTracesToDownloadData,
|
||||
getErrorRate,
|
||||
getNearestHighestBucketValue,
|
||||
} from './utils';
|
||||
|
||||
describe('Error Rate', () => {
|
||||
test('should return correct error rate', () => {
|
||||
const list: TopOperationList = getTopOperationList({
|
||||
errorCount: 10,
|
||||
numCalls: 100,
|
||||
});
|
||||
|
||||
expect(getErrorRate(list)).toBe(10);
|
||||
});
|
||||
|
||||
test('should handle no errors gracefully', () => {
|
||||
const list = getTopOperationList({ errorCount: 0, numCalls: 100 });
|
||||
expect(getErrorRate(list)).toBe(0);
|
||||
});
|
||||
|
||||
test('should handle zero calls', () => {
|
||||
const list = getTopOperationList({ errorCount: 0, numCalls: 0 });
|
||||
expect(getErrorRate(list)).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getNearestHighestBucketValue', () => {
|
||||
test('should return nearest higher bucket value', () => {
|
||||
expect(getNearestHighestBucketValue(50, [10, 20, 30, 40, 60, 70])).toBe('60');
|
||||
});
|
||||
|
||||
test('should return +Inf for value higher than any bucket', () => {
|
||||
expect(getNearestHighestBucketValue(80, [10, 20, 30, 40, 60, 70])).toBe(
|
||||
'+Inf',
|
||||
);
|
||||
});
|
||||
|
||||
test('should return the first bucket for value lower than all buckets', () => {
|
||||
expect(getNearestHighestBucketValue(5, [10, 20, 30, 40, 60, 70])).toBe('10');
|
||||
});
|
||||
});
|
||||
|
||||
describe('convertedTracesToDownloadData', () => {
|
||||
test('should convert trace data correctly', () => {
|
||||
const data = [
|
||||
{
|
||||
name: 'op1',
|
||||
p50: 50000000,
|
||||
p95: 95000000,
|
||||
p99: 99000000,
|
||||
numCalls: 100,
|
||||
errorCount: 10,
|
||||
},
|
||||
];
|
||||
|
||||
expect(convertedTracesToDownloadData(data)).toEqual([
|
||||
{
|
||||
Name: 'op1',
|
||||
'P50 (in ms)': '50.00',
|
||||
'P95 (in ms)': '95.00',
|
||||
'P99 (in ms)': '99.00',
|
||||
'Number of calls': '100',
|
||||
'Error Rate (%)': '10.00',
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -5,12 +5,8 @@ import history from 'lib/history';
|
||||
import { TopOperationList } from './TopOperationsTable';
|
||||
import { NavigateToTraceProps } from './types';
|
||||
|
||||
export const getErrorRate = (list: TopOperationList): number => {
|
||||
if (list.errorCount === 0 && list.numCalls === 0) {
|
||||
return 0;
|
||||
}
|
||||
return (list.errorCount / list.numCalls) * 100;
|
||||
};
|
||||
export const getErrorRate = (list: TopOperationList): number =>
|
||||
(list.errorCount / list.numCalls) * 100;
|
||||
|
||||
export const navigateToTrace = ({
|
||||
servicename,
|
||||
|
||||
@@ -29,7 +29,7 @@ function SettingsDrawer({ drawerTitle }: { drawerTitle: string }): JSX.Element {
|
||||
<DrawerContainer
|
||||
title={drawerTitle}
|
||||
placement="right"
|
||||
width="60%"
|
||||
width="50%"
|
||||
onClose={onClose}
|
||||
open={visible}
|
||||
>
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
.delete-variable-name {
|
||||
font-weight: 700;
|
||||
color: rgb(207, 19, 34);
|
||||
font-style: italic;
|
||||
}
|
||||
@@ -18,10 +18,10 @@ import {
|
||||
VariableQueryTypeArr,
|
||||
VariableSortTypeArr,
|
||||
} from 'types/api/dashboard/getAll';
|
||||
import { v4 as generateUUID } from 'uuid';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { variablePropsToPayloadVariables } from '../../../utils';
|
||||
import { TVariableMode } from '../types';
|
||||
import { TVariableViewMode } from '../types';
|
||||
import { LabelContainer, VariableItemRow } from './styles';
|
||||
|
||||
const { Option } = Select;
|
||||
@@ -30,9 +30,9 @@ interface VariableItemProps {
|
||||
variableData: IDashboardVariable;
|
||||
existingVariables: Record<string, IDashboardVariable>;
|
||||
onCancel: () => void;
|
||||
onSave: (mode: TVariableMode, variableData: IDashboardVariable) => void;
|
||||
onSave: (name: string, arg0: IDashboardVariable, arg1: string) => void;
|
||||
validateName: (arg0: string) => boolean;
|
||||
mode: TVariableMode;
|
||||
variableViewMode: TVariableViewMode;
|
||||
}
|
||||
function VariableItem({
|
||||
variableData,
|
||||
@@ -40,7 +40,7 @@ function VariableItem({
|
||||
onCancel,
|
||||
onSave,
|
||||
validateName,
|
||||
mode,
|
||||
variableViewMode,
|
||||
}: VariableItemProps): JSX.Element {
|
||||
const [variableName, setVariableName] = useState<string>(
|
||||
variableData.name || '',
|
||||
@@ -97,7 +97,7 @@ function VariableItem({
|
||||
]);
|
||||
|
||||
const handleSave = (): void => {
|
||||
const variable: IDashboardVariable = {
|
||||
const newVariableData: IDashboardVariable = {
|
||||
name: variableName,
|
||||
description: variableDescription,
|
||||
type: queryType,
|
||||
@@ -111,12 +111,16 @@ function VariableItem({
|
||||
selectedValue: (variableData.selectedValue ||
|
||||
variableTextboxValue) as never,
|
||||
}),
|
||||
modificationUUID: generateUUID(),
|
||||
id: variableData.id || generateUUID(),
|
||||
order: variableData.order,
|
||||
modificationUUID: v4(),
|
||||
};
|
||||
|
||||
onSave(mode, variable);
|
||||
onSave(
|
||||
variableName,
|
||||
newVariableData,
|
||||
(variableViewMode === 'EDIT' && variableName !== variableData.name
|
||||
? variableData.name
|
||||
: '') as string,
|
||||
);
|
||||
onCancel();
|
||||
};
|
||||
|
||||
// Fetches the preview values for the SQL variable query
|
||||
@@ -136,12 +140,11 @@ function VariableItem({
|
||||
enabled: false,
|
||||
queryFn: () =>
|
||||
dashboardVariablesQuery({
|
||||
query: variableQueryValue || '',
|
||||
query: variableData.queryValue || '',
|
||||
variables: variablePropsToPayloadVariables(existingVariables),
|
||||
}),
|
||||
refetchOnWindowFocus: false,
|
||||
onSuccess: (response) => {
|
||||
setErrorPreview(null);
|
||||
handleQueryResult(response);
|
||||
},
|
||||
onError: (error: {
|
||||
@@ -171,6 +174,7 @@ function VariableItem({
|
||||
return (
|
||||
<div className="variable-item-container">
|
||||
<div className="variable-item-content">
|
||||
{/* <Typography.Title level={3}>Add Variable</Typography.Title> */}
|
||||
<VariableItemRow>
|
||||
<LabelContainer>
|
||||
<Typography>Name</Typography>
|
||||
|
||||
@@ -1,78 +1,20 @@
|
||||
import '../DashboardSettings.styles.scss';
|
||||
|
||||
import { blue, red } from '@ant-design/colors';
|
||||
import { MenuOutlined, PlusOutlined } from '@ant-design/icons';
|
||||
import type { DragEndEvent, UniqueIdentifier } from '@dnd-kit/core';
|
||||
import {
|
||||
DndContext,
|
||||
PointerSensor,
|
||||
useSensor,
|
||||
useSensors,
|
||||
} from '@dnd-kit/core';
|
||||
import { restrictToVerticalAxis } from '@dnd-kit/modifiers';
|
||||
import { arrayMove, SortableContext, useSortable } from '@dnd-kit/sortable';
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import { CSS } from '@dnd-kit/utilities';
|
||||
import { Button, Modal, Row, Space, Table, Typography } from 'antd';
|
||||
import { RowProps } from 'antd/lib';
|
||||
import { convertVariablesToDbFormat } from 'container/NewDashboard/DashboardVariablesSelection/util';
|
||||
import { PlusOutlined } from '@ant-design/icons';
|
||||
import { Button, Modal, Row, Space, Tag } from 'antd';
|
||||
import { ResizeTable } from 'components/ResizeTable';
|
||||
import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import { PencilIcon, TrashIcon } from 'lucide-react';
|
||||
import { useDashboard } from 'providers/Dashboard/Dashboard';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Dashboard, IDashboardVariable } from 'types/api/dashboard/getAll';
|
||||
|
||||
import { TVariableMode } from './types';
|
||||
import { TVariableViewMode } from './types';
|
||||
import VariableItem from './VariableItem/VariableItem';
|
||||
|
||||
function TableRow({ children, ...props }: RowProps): JSX.Element {
|
||||
const {
|
||||
attributes,
|
||||
listeners,
|
||||
setNodeRef,
|
||||
setActivatorNodeRef,
|
||||
transform,
|
||||
transition,
|
||||
isDragging,
|
||||
} = useSortable({
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
id: props['data-row-key'],
|
||||
});
|
||||
|
||||
const style: React.CSSProperties = {
|
||||
...props.style,
|
||||
transform: CSS.Transform.toString(transform && { ...transform, scaleY: 1 }),
|
||||
transition,
|
||||
...(isDragging ? { position: 'relative', zIndex: 9999 } : {}),
|
||||
};
|
||||
|
||||
return (
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
<tr {...props} ref={setNodeRef} style={style} {...attributes}>
|
||||
{React.Children.map(children, (child) => {
|
||||
if ((child as React.ReactElement).key === 'sort') {
|
||||
return React.cloneElement(child as React.ReactElement, {
|
||||
children: (
|
||||
<MenuOutlined
|
||||
ref={setActivatorNodeRef}
|
||||
style={{ touchAction: 'none', cursor: 'move' }}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...listeners}
|
||||
/>
|
||||
),
|
||||
});
|
||||
}
|
||||
return child;
|
||||
})}
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
function VariablesSetting(): JSX.Element {
|
||||
const variableToDelete = useRef<IDashboardVariable | null>(null);
|
||||
const variableToDelete = useRef<string | null>(null);
|
||||
const [deleteVariableModal, setDeleteVariableModal] = useState(false);
|
||||
|
||||
const { t } = useTranslation(['dashboard']);
|
||||
@@ -83,15 +25,16 @@ function VariablesSetting(): JSX.Element {
|
||||
|
||||
const { variables = {} } = selectedDashboard?.data || {};
|
||||
|
||||
const [variablesTableData, setVariablesTableData] = useState<any>([]);
|
||||
const [variblesOrderArr, setVariablesOrderArr] = useState<number[]>([]);
|
||||
const [existingVariableNamesMap, setExistingVariableNamesMap] = useState<
|
||||
Record<string, string>
|
||||
>({});
|
||||
const variablesTableData = Object.keys(variables).map((variableName) => ({
|
||||
key: variableName,
|
||||
name: variableName,
|
||||
...variables[variableName],
|
||||
}));
|
||||
|
||||
const [variableViewMode, setVariableViewMode] = useState<null | TVariableMode>(
|
||||
null,
|
||||
);
|
||||
const [
|
||||
variableViewMode,
|
||||
setVariableViewMode,
|
||||
] = useState<null | TVariableViewMode>(null);
|
||||
|
||||
const [
|
||||
variableEditData,
|
||||
@@ -104,7 +47,7 @@ function VariablesSetting(): JSX.Element {
|
||||
};
|
||||
|
||||
const onVariableViewModeEnter = (
|
||||
viewType: TVariableMode,
|
||||
viewType: TVariableViewMode,
|
||||
varData: IDashboardVariable,
|
||||
): void => {
|
||||
setVariableEditData(varData);
|
||||
@@ -113,41 +56,6 @@ function VariablesSetting(): JSX.Element {
|
||||
|
||||
const updateMutation = useUpdateDashboard();
|
||||
|
||||
useEffect(() => {
|
||||
const tableRowData = [];
|
||||
const variableOrderArr = [];
|
||||
const variableNamesMap = {};
|
||||
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const [key, value] of Object.entries(variables)) {
|
||||
const { order, id, name } = value;
|
||||
|
||||
tableRowData.push({
|
||||
key,
|
||||
name: key,
|
||||
...variables[key],
|
||||
id,
|
||||
});
|
||||
|
||||
if (name) {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
variableNamesMap[name] = name;
|
||||
}
|
||||
|
||||
if (order) {
|
||||
variableOrderArr.push(order);
|
||||
}
|
||||
}
|
||||
|
||||
tableRowData.sort((a, b) => a.order - b.order);
|
||||
variableOrderArr.sort((a, b) => a - b);
|
||||
|
||||
setVariablesTableData(tableRowData);
|
||||
setVariablesOrderArr(variableOrderArr);
|
||||
setExistingVariableNamesMap(variableNamesMap);
|
||||
}, [variables]);
|
||||
|
||||
const updateVariables = (
|
||||
updatedVariablesData: Dashboard['data']['variables'],
|
||||
): void => {
|
||||
@@ -181,58 +89,34 @@ function VariablesSetting(): JSX.Element {
|
||||
);
|
||||
};
|
||||
|
||||
const getVariableOrder = (): number => {
|
||||
if (variblesOrderArr && variblesOrderArr.length > 0) {
|
||||
return variblesOrderArr[variblesOrderArr.length - 1] + 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
};
|
||||
|
||||
const onVariableSaveHandler = (
|
||||
mode: TVariableMode,
|
||||
name: string,
|
||||
variableData: IDashboardVariable,
|
||||
oldName: string,
|
||||
): void => {
|
||||
const updatedVariableData = {
|
||||
...variableData,
|
||||
order: variableData?.order >= 0 ? variableData.order : getVariableOrder(),
|
||||
};
|
||||
|
||||
const newVariablesArr = variablesTableData.map(
|
||||
(variable: IDashboardVariable) => {
|
||||
if (variable.id === updatedVariableData.id) {
|
||||
return updatedVariableData;
|
||||
}
|
||||
|
||||
return variable;
|
||||
},
|
||||
);
|
||||
|
||||
if (mode === 'ADD') {
|
||||
newVariablesArr.push(updatedVariableData);
|
||||
if (!variableData.name) {
|
||||
return;
|
||||
}
|
||||
|
||||
const variables = convertVariablesToDbFormat(newVariablesArr);
|
||||
const newVariables = { ...variables };
|
||||
newVariables[name] = variableData;
|
||||
|
||||
setVariablesTableData(newVariablesArr);
|
||||
updateVariables(variables);
|
||||
if (oldName) {
|
||||
delete newVariables[oldName];
|
||||
}
|
||||
updateVariables(newVariables);
|
||||
onDoneVariableViewMode();
|
||||
};
|
||||
|
||||
const onVariableDeleteHandler = (variable: IDashboardVariable): void => {
|
||||
variableToDelete.current = variable;
|
||||
const onVariableDeleteHandler = (variableName: string): void => {
|
||||
variableToDelete.current = variableName;
|
||||
setDeleteVariableModal(true);
|
||||
};
|
||||
|
||||
const handleDeleteConfirm = (): void => {
|
||||
const newVariablesArr = variablesTableData.filter(
|
||||
(variable: IDashboardVariable) =>
|
||||
variable.id !== variableToDelete?.current?.id,
|
||||
);
|
||||
|
||||
const updatedVariables = convertVariablesToDbFormat(newVariablesArr);
|
||||
|
||||
updateVariables(updatedVariables);
|
||||
const newVariables = { ...variables };
|
||||
if (variableToDelete?.current) delete newVariables[variableToDelete?.current];
|
||||
updateVariables(newVariables);
|
||||
variableToDelete.current = null;
|
||||
setDeleteVariableModal(false);
|
||||
};
|
||||
@@ -241,36 +125,31 @@ function VariablesSetting(): JSX.Element {
|
||||
setDeleteVariableModal(false);
|
||||
};
|
||||
|
||||
const validateVariableName = (name: string): boolean =>
|
||||
!existingVariableNamesMap[name];
|
||||
const validateVariableName = (name: string): boolean => !variables[name];
|
||||
|
||||
const columns = [
|
||||
{
|
||||
key: 'sort',
|
||||
width: '10%',
|
||||
},
|
||||
{
|
||||
title: 'Variable',
|
||||
dataIndex: 'name',
|
||||
width: '40%',
|
||||
width: 100,
|
||||
key: 'name',
|
||||
},
|
||||
{
|
||||
title: 'Description',
|
||||
dataIndex: 'description',
|
||||
width: '35%',
|
||||
width: 100,
|
||||
key: 'description',
|
||||
},
|
||||
{
|
||||
title: 'Actions',
|
||||
width: '15%',
|
||||
width: 50,
|
||||
key: 'action',
|
||||
render: (variable: IDashboardVariable): JSX.Element => (
|
||||
render: (_: IDashboardVariable): JSX.Element => (
|
||||
<Space>
|
||||
<Button
|
||||
type="text"
|
||||
style={{ padding: 8, cursor: 'pointer', color: blue[5] }}
|
||||
onClick={(): void => onVariableViewModeEnter('EDIT', variable)}
|
||||
onClick={(): void => onVariableViewModeEnter('EDIT', _)}
|
||||
>
|
||||
<PencilIcon size={14} />
|
||||
</Button>
|
||||
@@ -278,9 +157,7 @@ function VariablesSetting(): JSX.Element {
|
||||
type="text"
|
||||
style={{ padding: 8, color: red[6], cursor: 'pointer' }}
|
||||
onClick={(): void => {
|
||||
if (variable) {
|
||||
onVariableDeleteHandler(variable);
|
||||
}
|
||||
if (_.name) onVariableDeleteHandler(_.name);
|
||||
}}
|
||||
>
|
||||
<TrashIcon size={14} />
|
||||
@@ -290,51 +167,6 @@ function VariablesSetting(): JSX.Element {
|
||||
},
|
||||
];
|
||||
|
||||
const sensors = useSensors(
|
||||
useSensor(PointerSensor, {
|
||||
activationConstraint: {
|
||||
// https://docs.dndkit.com/api-documentation/sensors/pointer#activation-constraints
|
||||
distance: 1,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
const onDragEnd = ({ active, over }: DragEndEvent): void => {
|
||||
if (active.id !== over?.id) {
|
||||
const activeIndex = variablesTableData.findIndex(
|
||||
(i: { key: UniqueIdentifier }) => i.key === active.id,
|
||||
);
|
||||
const overIndex = variablesTableData.findIndex(
|
||||
(i: { key: UniqueIdentifier | undefined }) => i.key === over?.id,
|
||||
);
|
||||
|
||||
const updatedVariables: IDashboardVariable[] = arrayMove(
|
||||
variablesTableData,
|
||||
activeIndex,
|
||||
overIndex,
|
||||
);
|
||||
|
||||
const reArrangedVariables = {};
|
||||
|
||||
for (let index = 0; index < updatedVariables.length; index += 1) {
|
||||
const variableName = updatedVariables[index].name;
|
||||
|
||||
if (variableName) {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
reArrangedVariables[variableName] = {
|
||||
...updatedVariables[index],
|
||||
order: index,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
updateVariables(reArrangedVariables);
|
||||
|
||||
setVariablesTableData(updatedVariables);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{variableViewMode ? (
|
||||
@@ -344,17 +176,11 @@ function VariablesSetting(): JSX.Element {
|
||||
onSave={onVariableSaveHandler}
|
||||
onCancel={onDoneVariableViewMode}
|
||||
validateName={validateVariableName}
|
||||
mode={variableViewMode}
|
||||
variableViewMode={variableViewMode}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<Row
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'flex-end',
|
||||
padding: '0.5rem 0',
|
||||
}}
|
||||
>
|
||||
<Row style={{ flexDirection: 'row-reverse', padding: '0.5rem 0' }}>
|
||||
<Button
|
||||
data-testid="add-new-variable"
|
||||
type="primary"
|
||||
@@ -366,28 +192,7 @@ function VariablesSetting(): JSX.Element {
|
||||
</Button>
|
||||
</Row>
|
||||
|
||||
<DndContext
|
||||
sensors={sensors}
|
||||
modifiers={[restrictToVerticalAxis]}
|
||||
onDragEnd={onDragEnd}
|
||||
>
|
||||
<SortableContext
|
||||
// rowKey array
|
||||
items={variablesTableData.map((variable: { key: any }) => variable.key)}
|
||||
>
|
||||
<Table
|
||||
components={{
|
||||
body: {
|
||||
row: TableRow,
|
||||
},
|
||||
}}
|
||||
rowKey="key"
|
||||
columns={columns}
|
||||
pagination={false}
|
||||
dataSource={variablesTableData}
|
||||
/>
|
||||
</SortableContext>
|
||||
</DndContext>
|
||||
<ResizeTable columns={columns} dataSource={variablesTableData} />
|
||||
</>
|
||||
)}
|
||||
<Modal
|
||||
@@ -397,13 +202,8 @@ function VariablesSetting(): JSX.Element {
|
||||
onOk={handleDeleteConfirm}
|
||||
onCancel={handleDeleteCancel}
|
||||
>
|
||||
<Typography.Text>
|
||||
Are you sure you want to delete variable{' '}
|
||||
<span className="delete-variable-name">
|
||||
{variableToDelete?.current?.name}
|
||||
</span>
|
||||
?
|
||||
</Typography.Text>
|
||||
Are you sure you want to delete variable{' '}
|
||||
<Tag>{variableToDelete.current}</Tag>?
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,7 +1 @@
|
||||
export type TVariableMode = 'VIEW' | 'EDIT' | 'ADD';
|
||||
|
||||
export const VariableModes = {
|
||||
VIEW: 'VIEW',
|
||||
EDIT: 'EDIT',
|
||||
ADD: 'ADD',
|
||||
};
|
||||
export type TVariableViewMode = 'EDIT' | 'ADD';
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { Row } from 'antd';
|
||||
import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import { map, sortBy } from 'lodash-es';
|
||||
import { useDashboard } from 'providers/Dashboard/Dashboard';
|
||||
import { memo, useEffect, useState } from 'react';
|
||||
import { memo, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { Dashboard, IDashboardVariable } from 'types/api/dashboard/getAll';
|
||||
import AppReducer from 'types/reducer/app';
|
||||
|
||||
import { convertVariablesToDbFormat } from './util';
|
||||
import VariableItem from './VariableItem';
|
||||
|
||||
function DashboardVariableSelection(): JSX.Element | null {
|
||||
@@ -21,32 +21,8 @@ function DashboardVariableSelection(): JSX.Element | null {
|
||||
const [update, setUpdate] = useState<boolean>(false);
|
||||
const [lastUpdatedVar, setLastUpdatedVar] = useState<string>('');
|
||||
|
||||
const [variablesTableData, setVariablesTableData] = useState<any>([]);
|
||||
|
||||
const { role } = useSelector<AppState, AppReducer>((state) => state.app);
|
||||
|
||||
useEffect(() => {
|
||||
if (variables) {
|
||||
const tableRowData = [];
|
||||
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const [key, value] of Object.entries(variables)) {
|
||||
const { id } = value;
|
||||
|
||||
tableRowData.push({
|
||||
key,
|
||||
name: key,
|
||||
...variables[key],
|
||||
id,
|
||||
});
|
||||
}
|
||||
|
||||
tableRowData.sort((a, b) => a.order - b.order);
|
||||
|
||||
setVariablesTableData(tableRowData);
|
||||
}
|
||||
}, [variables]);
|
||||
|
||||
const onVarChanged = (name: string): void => {
|
||||
setLastUpdatedVar(name);
|
||||
setUpdate(!update);
|
||||
@@ -88,56 +64,40 @@ function DashboardVariableSelection(): JSX.Element | null {
|
||||
|
||||
const onValueUpdate = (
|
||||
name: string,
|
||||
id: string,
|
||||
value: IDashboardVariable['selectedValue'],
|
||||
allSelected: boolean,
|
||||
): void => {
|
||||
if (id) {
|
||||
const newVariablesArr = variablesTableData.map(
|
||||
(variable: IDashboardVariable) => {
|
||||
const variableCopy = { ...variable };
|
||||
const updatedVariablesData = { ...variables };
|
||||
updatedVariablesData[name].selectedValue = value;
|
||||
updatedVariablesData[name].allSelected = allSelected;
|
||||
|
||||
if (variableCopy.id === id) {
|
||||
variableCopy.selectedValue = value;
|
||||
variableCopy.allSelected = allSelected;
|
||||
}
|
||||
console.log('onValue Update', name);
|
||||
|
||||
return variableCopy;
|
||||
},
|
||||
);
|
||||
|
||||
const variables = convertVariablesToDbFormat(newVariablesArr);
|
||||
|
||||
if (role !== 'VIEWER' && selectedDashboard) {
|
||||
updateVariables(name, variables);
|
||||
}
|
||||
onVarChanged(name);
|
||||
|
||||
setUpdate(!update);
|
||||
if (role !== 'VIEWER' && selectedDashboard) {
|
||||
updateVariables(name, updatedVariablesData);
|
||||
}
|
||||
onVarChanged(name);
|
||||
|
||||
setUpdate(!update);
|
||||
};
|
||||
|
||||
if (!variables) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const orderBasedSortedVariables = variablesTableData.sort(
|
||||
(a: { order: number }, b: { order: number }) => a.order - b.order,
|
||||
);
|
||||
const variablesKeys = sortBy(Object.keys(variables));
|
||||
|
||||
return (
|
||||
<Row>
|
||||
{orderBasedSortedVariables &&
|
||||
Array.isArray(orderBasedSortedVariables) &&
|
||||
orderBasedSortedVariables.length > 0 &&
|
||||
orderBasedSortedVariables.map((variable) => (
|
||||
{variablesKeys &&
|
||||
map(variablesKeys, (variableName) => (
|
||||
<VariableItem
|
||||
key={`${variable.name}${variable.id}}${variable.order}`}
|
||||
key={`${variableName}${variables[variableName].modificationUUID}`}
|
||||
existingVariables={variables}
|
||||
lastUpdatedVar={lastUpdatedVar}
|
||||
variableData={{
|
||||
name: variable.name,
|
||||
...variable,
|
||||
name: variableName,
|
||||
...variables[variableName],
|
||||
change: update,
|
||||
}}
|
||||
onValueUpdate={onValueUpdate}
|
||||
|
||||
@@ -14,7 +14,6 @@ import { IDashboardVariable } from 'types/api/dashboard/getAll';
|
||||
import VariableItem from './VariableItem';
|
||||
|
||||
const mockVariableData: IDashboardVariable = {
|
||||
id: 'test_variable',
|
||||
description: 'Test Variable',
|
||||
type: 'TEXTBOX',
|
||||
textboxValue: 'defaultValue',
|
||||
@@ -96,7 +95,6 @@ describe('VariableItem', () => {
|
||||
// expect(mockOnValueUpdate).toHaveBeenCalledTimes(1);
|
||||
expect(mockOnValueUpdate).toHaveBeenCalledWith(
|
||||
'testVariable',
|
||||
'test_variable',
|
||||
'newValue',
|
||||
false,
|
||||
);
|
||||
|
||||
@@ -2,14 +2,13 @@ import './DashboardVariableSelection.styles.scss';
|
||||
|
||||
import { orange } from '@ant-design/colors';
|
||||
import { WarningOutlined } from '@ant-design/icons';
|
||||
import { Input, Popover, Select, Tooltip, Typography } from 'antd';
|
||||
import { Input, Popover, Select, Typography } from 'antd';
|
||||
import dashboardVariablesQuery from 'api/dashboard/variables/dashboardVariablesQuery';
|
||||
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
||||
import useDebounce from 'hooks/useDebounce';
|
||||
import { commaValuesParser } from 'lib/dashbaordVariables/customCommaValuesParser';
|
||||
import sortValues from 'lib/dashbaordVariables/sortVariableValues';
|
||||
import map from 'lodash-es/map';
|
||||
import { useDashboard } from 'providers/Dashboard/Dashboard';
|
||||
import { memo, useEffect, useMemo, useState } from 'react';
|
||||
import { useQuery } from 'react-query';
|
||||
import { IDashboardVariable } from 'types/api/dashboard/getAll';
|
||||
@@ -28,7 +27,6 @@ interface VariableItemProps {
|
||||
existingVariables: Record<string, IDashboardVariable>;
|
||||
onValueUpdate: (
|
||||
name: string,
|
||||
id: string,
|
||||
arg1: IDashboardVariable['selectedValue'],
|
||||
allSelected: boolean,
|
||||
) => void;
|
||||
@@ -50,7 +48,6 @@ function VariableItem({
|
||||
onValueUpdate,
|
||||
lastUpdatedVar,
|
||||
}: VariableItemProps): JSX.Element {
|
||||
const { isDashboardLocked } = useDashboard();
|
||||
const [optionsData, setOptionsData] = useState<(string | number | boolean)[]>(
|
||||
[],
|
||||
);
|
||||
@@ -140,9 +137,8 @@ function VariableItem({
|
||||
} else {
|
||||
[value] = newOptionsData;
|
||||
}
|
||||
|
||||
if (variableData && variableData?.name && variableData?.id) {
|
||||
onValueUpdate(variableData.name, variableData.id, value, allSelected);
|
||||
if (variableData.name) {
|
||||
onValueUpdate(variableData.name, value, allSelected);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,13 +149,14 @@ function VariableItem({
|
||||
console.error(e);
|
||||
}
|
||||
} else if (variableData.type === 'CUSTOM') {
|
||||
const optionsData = sortValues(
|
||||
commaValuesParser(variableData.customValue || ''),
|
||||
variableData.sort,
|
||||
) as never;
|
||||
|
||||
setOptionsData(optionsData);
|
||||
setOptionsData(
|
||||
sortValues(
|
||||
commaValuesParser(variableData.customValue || ''),
|
||||
variableData.sort,
|
||||
) as never,
|
||||
);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
};
|
||||
|
||||
const { isLoading } = useQuery(getQueryKey(variableData), {
|
||||
@@ -198,9 +195,9 @@ function VariableItem({
|
||||
(Array.isArray(value) && value.includes(ALL_SELECT_VALUE)) ||
|
||||
(Array.isArray(value) && value.length === 0)
|
||||
) {
|
||||
onValueUpdate(variableData.name, variableData.id, optionsData, true);
|
||||
onValueUpdate(variableData.name, optionsData, true);
|
||||
} else {
|
||||
onValueUpdate(variableData.name, variableData.id, value, false);
|
||||
onValueUpdate(variableData.name, value, false);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -227,85 +224,70 @@ function VariableItem({
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [debouncedVariableValue]);
|
||||
|
||||
useEffect(() => {
|
||||
// Fetch options for CUSTOM Type
|
||||
if (variableData.type === 'CUSTOM') {
|
||||
getOptions(null);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [variableData.type, variableData.customValue]);
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
placement="top"
|
||||
title={isDashboardLocked ? 'Dashboard is locked' : ''}
|
||||
>
|
||||
<VariableContainer>
|
||||
<Typography.Text className="variable-name" ellipsis>
|
||||
${variableData.name}
|
||||
</Typography.Text>
|
||||
<VariableValue>
|
||||
{variableData.type === 'TEXTBOX' ? (
|
||||
<Input
|
||||
placeholder="Enter value"
|
||||
disabled={isDashboardLocked}
|
||||
<VariableContainer>
|
||||
<Typography.Text className="variable-name" ellipsis>
|
||||
${variableData.name}
|
||||
</Typography.Text>
|
||||
<VariableValue>
|
||||
{variableData.type === 'TEXTBOX' ? (
|
||||
<Input
|
||||
placeholder="Enter value"
|
||||
bordered={false}
|
||||
value={variableValue}
|
||||
onChange={(e): void => {
|
||||
setVaribleValue(e.target.value || '');
|
||||
}}
|
||||
style={{
|
||||
width:
|
||||
50 + ((variableData.selectedValue?.toString()?.length || 0) * 7 || 50),
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
!errorMessage &&
|
||||
optionsData && (
|
||||
<Select
|
||||
value={selectValue}
|
||||
onChange={handleChange}
|
||||
bordered={false}
|
||||
value={variableValue}
|
||||
onChange={(e): void => {
|
||||
setVaribleValue(e.target.value || '');
|
||||
}}
|
||||
style={{
|
||||
width:
|
||||
50 + ((variableData.selectedValue?.toString()?.length || 0) * 7 || 50),
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
!errorMessage &&
|
||||
optionsData && (
|
||||
<Select
|
||||
value={selectValue}
|
||||
onChange={handleChange}
|
||||
bordered={false}
|
||||
placeholder="Select value"
|
||||
mode={mode}
|
||||
dropdownMatchSelectWidth={false}
|
||||
style={SelectItemStyle}
|
||||
loading={isLoading}
|
||||
showArrow
|
||||
showSearch
|
||||
data-testid="variable-select"
|
||||
disabled={isDashboardLocked}
|
||||
>
|
||||
{enableSelectAll && (
|
||||
<Select.Option data-testid="option-ALL" value={ALL_SELECT_VALUE}>
|
||||
ALL
|
||||
</Select.Option>
|
||||
)}
|
||||
{map(optionsData, (option) => (
|
||||
<Select.Option
|
||||
data-testid={`option-${option}`}
|
||||
key={option.toString()}
|
||||
value={option}
|
||||
>
|
||||
{option.toString()}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
)
|
||||
)}
|
||||
{variableData.type !== 'TEXTBOX' && errorMessage && (
|
||||
<span style={{ margin: '0 0.5rem' }}>
|
||||
<Popover
|
||||
placement="top"
|
||||
content={<Typography>{errorMessage}</Typography>}
|
||||
>
|
||||
<WarningOutlined style={{ color: orange[5] }} />
|
||||
</Popover>
|
||||
</span>
|
||||
)}
|
||||
</VariableValue>
|
||||
</VariableContainer>
|
||||
</Tooltip>
|
||||
placeholder="Select value"
|
||||
mode={mode}
|
||||
dropdownMatchSelectWidth={false}
|
||||
style={SelectItemStyle}
|
||||
loading={isLoading}
|
||||
showArrow
|
||||
showSearch
|
||||
data-testid="variable-select"
|
||||
>
|
||||
{enableSelectAll && (
|
||||
<Select.Option data-testid="option-ALL" value={ALL_SELECT_VALUE}>
|
||||
ALL
|
||||
</Select.Option>
|
||||
)}
|
||||
{map(optionsData, (option) => (
|
||||
<Select.Option
|
||||
data-testid={`option-${option}`}
|
||||
key={option.toString()}
|
||||
value={option}
|
||||
>
|
||||
{option.toString()}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
)
|
||||
)}
|
||||
{errorMessage && (
|
||||
<span style={{ margin: '0 0.5rem' }}>
|
||||
<Popover
|
||||
placement="top"
|
||||
content={<Typography>{errorMessage}</Typography>}
|
||||
>
|
||||
<WarningOutlined style={{ color: orange[5] }} />
|
||||
</Popover>
|
||||
</span>
|
||||
)}
|
||||
</VariableValue>
|
||||
</VariableContainer>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { Dashboard, IDashboardVariable } from 'types/api/dashboard/getAll';
|
||||
|
||||
export function areArraysEqual(
|
||||
a: (string | number | boolean)[],
|
||||
b: (string | number | boolean)[],
|
||||
@@ -16,16 +14,3 @@ export function areArraysEqual(
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
export const convertVariablesToDbFormat = (
|
||||
variblesArr: IDashboardVariable[],
|
||||
): Dashboard['data']['variables'] =>
|
||||
variblesArr.reduce((result, obj: IDashboardVariable) => {
|
||||
const { id } = obj;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
result[id] = obj;
|
||||
return result;
|
||||
}, {});
|
||||
|
||||
@@ -6,10 +6,8 @@ export function variablePropsToPayloadVariables(
|
||||
): PayloadVariables {
|
||||
const payloadVariables: PayloadVariables = {};
|
||||
|
||||
Object.entries(variables).forEach(([, value]) => {
|
||||
if (value?.name) {
|
||||
payloadVariables[value.name] = value?.selectedValue;
|
||||
}
|
||||
Object.entries(variables).forEach(([key, value]) => {
|
||||
payloadVariables[key] = value?.selectedValue;
|
||||
});
|
||||
|
||||
return payloadVariables;
|
||||
|
||||
@@ -6,16 +6,13 @@ import { useResizeObserver } from 'hooks/useDimensions';
|
||||
import useUrlQuery from 'hooks/useUrlQuery';
|
||||
import { getUPlotChartOptions } from 'lib/uPlotLib/getUplotChartOptions';
|
||||
import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useCallback, useMemo, useRef } from 'react';
|
||||
import { UseQueryResult } from 'react-query';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { UpdateTimeInterval } from 'store/actions';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { SuccessResponse } from 'types/api';
|
||||
import { Widgets } from 'types/api/dashboard/getAll';
|
||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import { getTimeRange } from 'utils/getTimeRange';
|
||||
|
||||
function WidgetGraph({
|
||||
getWidgetQueryRange,
|
||||
@@ -26,21 +23,6 @@ function WidgetGraph({
|
||||
}: WidgetGraphProps): JSX.Element {
|
||||
const { stagedQuery } = useQueryBuilder();
|
||||
|
||||
const { minTime, maxTime, selectedTime: globalSelectedInterval } = useSelector<
|
||||
AppState,
|
||||
GlobalReducer
|
||||
>((state) => state.globalTime);
|
||||
|
||||
const [minTimeScale, setMinTimeScale] = useState<number>();
|
||||
const [maxTimeScale, setMaxTimeScale] = useState<number>();
|
||||
|
||||
useEffect((): void => {
|
||||
const { startTime, endTime } = getTimeRange(getWidgetQueryRange);
|
||||
|
||||
setMinTimeScale(startTime);
|
||||
setMaxTimeScale(endTime);
|
||||
}, [getWidgetQueryRange, maxTime, minTime, globalSelectedInterval]);
|
||||
|
||||
const graphRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const containerDimensions = useResizeObserver(graphRef);
|
||||
@@ -81,8 +63,6 @@ function WidgetGraph({
|
||||
onDragSelect,
|
||||
thresholds,
|
||||
fillSpans,
|
||||
minTimeScale,
|
||||
maxTimeScale,
|
||||
}),
|
||||
[
|
||||
widgetId,
|
||||
@@ -93,8 +73,6 @@ function WidgetGraph({
|
||||
onDragSelect,
|
||||
thresholds,
|
||||
fillSpans,
|
||||
minTimeScale,
|
||||
maxTimeScale,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ export const Container = styled(Card)<Props>`
|
||||
padding: ${({ $panelType }): string =>
|
||||
$panelType === PANEL_TYPES.TABLE ? '0 0' : '1.5rem 0'};
|
||||
height: 57vh;
|
||||
overflow: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
@@ -27,10 +27,6 @@ import CustomColor from './CustomColor';
|
||||
import ShowCaseValue from './ShowCaseValue';
|
||||
import { ThresholdProps } from './types';
|
||||
|
||||
const wrapStyle = {
|
||||
flexWrap: 'wrap',
|
||||
} as React.CSSProperties;
|
||||
|
||||
function Threshold({
|
||||
index,
|
||||
thresholdOperator = '>',
|
||||
@@ -224,7 +220,7 @@ function Threshold({
|
||||
}
|
||||
>
|
||||
{selectedGraph === PANEL_TYPES.TIME_SERIES && (
|
||||
<Space style={wrapStyle}>
|
||||
<>
|
||||
<Typography.Text>Label</Typography.Text>
|
||||
{isEditMode ? (
|
||||
<Input
|
||||
@@ -236,7 +232,7 @@ function Threshold({
|
||||
) : (
|
||||
<ShowCaseValue width="180px" value={label || 'none'} />
|
||||
)}
|
||||
</Space>
|
||||
</>
|
||||
)}
|
||||
{(selectedGraph === PANEL_TYPES.VALUE ||
|
||||
selectedGraph === PANEL_TYPES.TABLE) && (
|
||||
@@ -247,7 +243,7 @@ function Threshold({
|
||||
{isEditMode ? (
|
||||
<>
|
||||
{selectedGraph === PANEL_TYPES.TABLE && (
|
||||
<Space style={wrapStyle}>
|
||||
<Space>
|
||||
<Select
|
||||
style={{
|
||||
minWidth: '150px',
|
||||
@@ -274,7 +270,7 @@ function Threshold({
|
||||
) : (
|
||||
<>
|
||||
{selectedGraph === PANEL_TYPES.TABLE && (
|
||||
<Space style={wrapStyle}>
|
||||
<Space>
|
||||
<ShowCaseValue width="150px" value={tableSelectedOption} />
|
||||
<Typography.Text>is</Typography.Text>
|
||||
</Space>
|
||||
@@ -287,7 +283,7 @@ function Threshold({
|
||||
</Space>
|
||||
</div>
|
||||
<div className="threshold-units-selector">
|
||||
<Space style={wrapStyle}>
|
||||
<Space>
|
||||
{isEditMode ? (
|
||||
<InputNumber
|
||||
style={{ backgroundColor }}
|
||||
@@ -315,7 +311,7 @@ function Threshold({
|
||||
<div>
|
||||
<Space direction="vertical">
|
||||
<Typography.Text>Show with</Typography.Text>
|
||||
<Space style={wrapStyle}>
|
||||
<Space>
|
||||
{isEditMode ? (
|
||||
<>
|
||||
<ColorSelector setColor={setColor} thresholdColor={color} />
|
||||
|
||||
@@ -78,7 +78,6 @@ export const alertsCategory = [
|
||||
name: CategoryNames.Miscellaneous,
|
||||
formats: [
|
||||
{ name: 'Percent (0.0-1.0)', id: MiscellaneousFormats.PercentUnit },
|
||||
{ name: 'Percent (0 - 100)', id: MiscellaneousFormats.Percent },
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -10,9 +10,11 @@ import {
|
||||
} from 'antd';
|
||||
import InputComponent from 'components/Input';
|
||||
import TimePreference from 'components/TimePreferenceDropDown';
|
||||
import { QueryParams } from 'constants/query';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import ROUTES from 'constants/routes';
|
||||
import GraphTypes from 'container/NewDashboard/ComponentsSlider/menuItems';
|
||||
import useCreateAlerts from 'hooks/queryBuilder/useCreateAlerts';
|
||||
import history from 'lib/history';
|
||||
import { Dispatch, SetStateAction, useCallback } from 'react';
|
||||
import { Widgets } from 'types/api/dashboard/getAll';
|
||||
|
||||
@@ -53,7 +55,15 @@ function RightContainer({
|
||||
const selectedGraphType =
|
||||
GraphTypes.find((e) => e.name === selectedGraph)?.display || '';
|
||||
|
||||
const onCreateAlertsHandler = useCreateAlerts(selectedWidget);
|
||||
const onCreateAlertsHandler = useCallback(() => {
|
||||
if (!selectedWidget) return;
|
||||
|
||||
history.push(
|
||||
`${ROUTES.ALERTS_NEW}?${QueryParams.compositeQuery}=${encodeURIComponent(
|
||||
JSON.stringify(selectedWidget?.query),
|
||||
)}`,
|
||||
);
|
||||
}, [selectedWidget]);
|
||||
|
||||
const allowThreshold = panelTypeVsThreshold[selectedGraph];
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user