mirror of
https://github.com/SigNoz/signoz.git
synced 2026-05-06 02:20:31 +01:00
Compare commits
9 Commits
fixProduce
...
v0.57.0-cl
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5b3839dca9 | ||
|
|
767d7fc513 | ||
|
|
58f3ab377b | ||
|
|
8da1817061 | ||
|
|
d1205953c7 | ||
|
|
a846caccd9 | ||
|
|
9ac5e48ac2 | ||
|
|
fc9219b0ab | ||
|
|
f4cbe854b1 |
83
.github/workflows/docs.yml
vendored
83
.github/workflows/docs.yml
vendored
@@ -1,83 +0,0 @@
|
||||
name: "Update PR labels and Block PR until related docs are shipped for the feature"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- develop
|
||||
types: [opened, edited, labeled, unlabeled]
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
docs_label_check:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check PR Title and Manage Labels
|
||||
uses: actions/github-script@v6
|
||||
with:
|
||||
script: |
|
||||
const prTitle = context.payload.pull_request.title;
|
||||
const prNumber = context.payload.pull_request.number;
|
||||
const owner = context.repo.owner;
|
||||
const repo = context.repo.repo;
|
||||
|
||||
// Fetch the current PR details to get labels
|
||||
const pr = await github.rest.pulls.get({
|
||||
owner,
|
||||
repo,
|
||||
pull_number: prNumber
|
||||
});
|
||||
|
||||
const labels = pr.data.labels.map(label => label.name);
|
||||
|
||||
if (prTitle.startsWith('feat:')) {
|
||||
const hasDocsRequired = labels.includes('docs required');
|
||||
const hasDocsShipped = labels.includes('docs shipped');
|
||||
const hasDocsNotRequired = labels.includes('docs not required');
|
||||
|
||||
// If "docs not required" is present, skip the checks
|
||||
if (hasDocsNotRequired && !hasDocsRequired) {
|
||||
console.log("Skipping checks due to 'docs not required' label.");
|
||||
return; // Exit the script early
|
||||
}
|
||||
|
||||
// If "docs shipped" is present, remove "docs required" if it exists
|
||||
if (hasDocsShipped && hasDocsRequired) {
|
||||
await github.rest.issues.removeLabel({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: prNumber,
|
||||
name: 'docs required'
|
||||
});
|
||||
console.log("Removed 'docs required' label.");
|
||||
}
|
||||
|
||||
// Add "docs required" label if neither "docs shipped" nor "docs required" are present
|
||||
if (!hasDocsRequired && !hasDocsShipped) {
|
||||
await github.rest.issues.addLabels({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: prNumber,
|
||||
labels: ['docs required']
|
||||
});
|
||||
console.log("Added 'docs required' label.");
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch the updated labels after any changes
|
||||
const updatedPr = await github.rest.pulls.get({
|
||||
owner,
|
||||
repo,
|
||||
pull_number: prNumber
|
||||
});
|
||||
|
||||
const updatedLabels = updatedPr.data.labels.map(label => label.name);
|
||||
const updatedHasDocsRequired = updatedLabels.includes('docs required');
|
||||
const updatedHasDocsShipped = updatedLabels.includes('docs shipped');
|
||||
|
||||
// Block PR if "docs required" is still present and "docs shipped" is missing
|
||||
if (updatedHasDocsRequired && !updatedHasDocsShipped) {
|
||||
core.setFailed("This PR requires documentation. Please remove the 'docs required' label and add the 'docs shipped' label to proceed.");
|
||||
}
|
||||
1
.github/workflows/staging-deployment.yaml
vendored
1
.github/workflows/staging-deployment.yaml
vendored
@@ -38,7 +38,6 @@ jobs:
|
||||
export DOCKER_TAG="${GITHUB_SHA:0:7}" # needed for child process to access it
|
||||
export OTELCOL_TAG="main"
|
||||
export PATH="/usr/local/go/bin/:$PATH" # needed for Golang to work
|
||||
export KAFKA_SPAN_EVAL="true"
|
||||
docker system prune --force
|
||||
docker pull signoz/signoz-otel-collector:main
|
||||
docker pull signoz/signoz-schema-migrator:main
|
||||
|
||||
@@ -199,7 +199,7 @@ services:
|
||||
- ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf
|
||||
|
||||
otel-collector:
|
||||
image: signoz/signoz-otel-collector:0.111.5
|
||||
image: signoz/signoz-otel-collector:0.102.12
|
||||
command:
|
||||
[
|
||||
"--config=/etc/otel-collector-config.yaml",
|
||||
@@ -214,6 +214,7 @@ services:
|
||||
- /:/hostfs:ro
|
||||
environment:
|
||||
- OTEL_RESOURCE_ATTRIBUTES=host.name={{.Node.Hostname}},os.type={{.Node.Platform.OS}},dockerswarm.service.name={{.Service.Name}},dockerswarm.task.name={{.Task.Name}}
|
||||
- DOCKER_MULTI_NODE_CLUSTER=false
|
||||
- LOW_CARDINAL_EXCEPTION_GROUPING=false
|
||||
ports:
|
||||
# - "1777:1777" # pprof extension
|
||||
@@ -237,7 +238,7 @@ services:
|
||||
- query-service
|
||||
|
||||
otel-collector-migrator:
|
||||
image: signoz/signoz-schema-migrator:0.111.5
|
||||
image: signoz/signoz-schema-migrator:0.102.10
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
|
||||
@@ -131,6 +131,7 @@ processors:
|
||||
exporters:
|
||||
clickhousetraces:
|
||||
datasource: tcp://clickhouse:9000/signoz_traces
|
||||
docker_multi_node_cluster: ${env:DOCKER_MULTI_NODE_CLUSTER}
|
||||
low_cardinal_exception_grouping: ${env:LOW_CARDINAL_EXCEPTION_GROUPING}
|
||||
clickhousemetricswrite:
|
||||
endpoint: tcp://clickhouse:9000/signoz_metrics
|
||||
@@ -141,6 +142,7 @@ exporters:
|
||||
# logging: {}
|
||||
clickhouselogsexporter:
|
||||
dsn: tcp://clickhouse:9000/signoz_logs
|
||||
docker_multi_node_cluster: ${env:DOCKER_MULTI_NODE_CLUSTER}
|
||||
timeout: 10s
|
||||
use_new_schema: true
|
||||
extensions:
|
||||
|
||||
@@ -69,7 +69,7 @@ services:
|
||||
- --storage.path=/data
|
||||
|
||||
otel-collector-migrator:
|
||||
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.111.5}
|
||||
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.102.10}
|
||||
container_name: otel-migrator
|
||||
command:
|
||||
- "--dsn=tcp://clickhouse:9000"
|
||||
@@ -84,7 +84,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.111.5
|
||||
image: signoz/signoz-otel-collector:0.102.12
|
||||
command:
|
||||
[
|
||||
"--config=/etc/otel-collector-config.yaml",
|
||||
|
||||
@@ -34,7 +34,7 @@ x-db-depend: &db-depend
|
||||
depends_on:
|
||||
clickhouse:
|
||||
condition: service_healthy
|
||||
otel-collector-migrator-sync:
|
||||
otel-collector-migrator:
|
||||
condition: service_completed_successfully
|
||||
# clickhouse-2:
|
||||
# condition: service_healthy
|
||||
@@ -212,13 +212,11 @@ services:
|
||||
volumes:
|
||||
- ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf
|
||||
|
||||
otel-collector-migrator-sync:
|
||||
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.111.5}
|
||||
container_name: otel-migrator-sync
|
||||
otel-collector-migrator:
|
||||
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.102.10}
|
||||
container_name: otel-migrator
|
||||
command:
|
||||
- "sync"
|
||||
- "--dsn=tcp://clickhouse:9000"
|
||||
- "--up="
|
||||
depends_on:
|
||||
clickhouse:
|
||||
condition: service_healthy
|
||||
@@ -227,25 +225,9 @@ services:
|
||||
# clickhouse-3:
|
||||
# condition: service_healthy
|
||||
|
||||
otel-collector-migrator-async:
|
||||
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.111.5}
|
||||
container_name: otel-migrator-async
|
||||
command:
|
||||
- "async"
|
||||
- "--dsn=tcp://clickhouse:9000"
|
||||
- "--up="
|
||||
depends_on:
|
||||
clickhouse:
|
||||
condition: service_healthy
|
||||
otel-collector-migrator-sync:
|
||||
condition: service_completed_successfully
|
||||
# clickhouse-2:
|
||||
# condition: service_healthy
|
||||
# clickhouse-3:
|
||||
# condition: service_healthy
|
||||
|
||||
otel-collector:
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.111.5}
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.102.12}
|
||||
container_name: signoz-otel-collector
|
||||
command:
|
||||
[
|
||||
@@ -262,6 +244,7 @@ services:
|
||||
- /:/hostfs:ro
|
||||
environment:
|
||||
- OTEL_RESOURCE_ATTRIBUTES=host.name=signoz-host,os.type=linux
|
||||
- DOCKER_MULTI_NODE_CLUSTER=false
|
||||
- LOW_CARDINAL_EXCEPTION_GROUPING=false
|
||||
ports:
|
||||
# - "1777:1777" # pprof extension
|
||||
@@ -279,7 +262,7 @@ services:
|
||||
depends_on:
|
||||
clickhouse:
|
||||
condition: service_healthy
|
||||
otel-collector-migrator-sync:
|
||||
otel-collector-migrator:
|
||||
condition: service_completed_successfully
|
||||
query-service:
|
||||
condition: service_healthy
|
||||
|
||||
@@ -191,7 +191,6 @@ services:
|
||||
- GODEBUG=netdns=go
|
||||
- TELEMETRY_ENABLED=true
|
||||
- DEPLOYMENT_TYPE=docker-standalone-amd
|
||||
- KAFKA_SPAN_EVAL=${KAFKA_SPAN_EVAL:-false}
|
||||
restart: on-failure
|
||||
healthcheck:
|
||||
test:
|
||||
@@ -220,7 +219,7 @@ services:
|
||||
- ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf
|
||||
|
||||
otel-collector-migrator:
|
||||
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.111.5}
|
||||
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.102.10}
|
||||
container_name: otel-migrator
|
||||
command:
|
||||
- "--dsn=tcp://clickhouse:9000"
|
||||
@@ -234,7 +233,7 @@ services:
|
||||
|
||||
|
||||
otel-collector:
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.111.5}
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.102.12}
|
||||
container_name: signoz-otel-collector
|
||||
command:
|
||||
[
|
||||
@@ -251,6 +250,7 @@ services:
|
||||
- /:/hostfs:ro
|
||||
environment:
|
||||
- OTEL_RESOURCE_ATTRIBUTES=host.name=signoz-host,os.type=linux
|
||||
- DOCKER_MULTI_NODE_CLUSTER=false
|
||||
- LOW_CARDINAL_EXCEPTION_GROUPING=false
|
||||
ports:
|
||||
# - "1777:1777" # pprof extension
|
||||
|
||||
@@ -142,6 +142,7 @@ extensions:
|
||||
exporters:
|
||||
clickhousetraces:
|
||||
datasource: tcp://clickhouse:9000/signoz_traces
|
||||
docker_multi_node_cluster: ${env:DOCKER_MULTI_NODE_CLUSTER}
|
||||
low_cardinal_exception_grouping: ${env:LOW_CARDINAL_EXCEPTION_GROUPING}
|
||||
clickhousemetricswrite:
|
||||
endpoint: tcp://clickhouse:9000/signoz_metrics
|
||||
@@ -151,6 +152,7 @@ exporters:
|
||||
endpoint: tcp://clickhouse:9000/signoz_metrics
|
||||
clickhouselogsexporter:
|
||||
dsn: tcp://clickhouse:9000/signoz_logs
|
||||
docker_multi_node_cluster: ${env:DOCKER_MULTI_NODE_CLUSTER}
|
||||
timeout: 10s
|
||||
use_new_schema: true
|
||||
# logging: {}
|
||||
|
||||
@@ -40,7 +40,6 @@ type APIHandlerOptions struct {
|
||||
// Querier Influx Interval
|
||||
FluxInterval time.Duration
|
||||
UseLogsNewSchema bool
|
||||
UseLicensesV3 bool
|
||||
}
|
||||
|
||||
type APIHandler struct {
|
||||
@@ -66,7 +65,6 @@ func NewAPIHandler(opts APIHandlerOptions) (*APIHandler, error) {
|
||||
Cache: opts.Cache,
|
||||
FluxInterval: opts.FluxInterval,
|
||||
UseLogsNewSchema: opts.UseLogsNewSchema,
|
||||
UseLicensesV3: opts.UseLicensesV3,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
@@ -161,6 +159,7 @@ func (ah *APIHandler) RegisterRoutes(router *mux.Router, am *baseapp.AuthMiddlew
|
||||
router.HandleFunc("/api/v1/register", am.OpenAccess(ah.registerUser)).Methods(http.MethodPost)
|
||||
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/traces/{traceId}", am.ViewAccess(ah.searchTracesV2)).Methods(http.MethodPost)
|
||||
|
||||
// PAT APIs
|
||||
router.HandleFunc("/api/v1/pats", am.AdminAccess(ah.createPAT)).Methods(http.MethodPost)
|
||||
@@ -175,25 +174,10 @@ 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)
|
||||
|
||||
// v2
|
||||
router.HandleFunc("/api/v2/licenses",
|
||||
am.ViewAccess(ah.listLicensesV2)).
|
||||
Methods(http.MethodGet)
|
||||
|
||||
// v3
|
||||
router.HandleFunc("/api/v3/licenses",
|
||||
am.ViewAccess(ah.listLicensesV3)).
|
||||
Methods(http.MethodGet)
|
||||
|
||||
router.HandleFunc("/api/v3/licenses",
|
||||
am.AdminAccess(ah.applyLicenseV3)).
|
||||
Methods(http.MethodPost)
|
||||
|
||||
router.HandleFunc("/api/v3/licenses",
|
||||
am.AdminAccess(ah.refreshLicensesV3)).
|
||||
Methods(http.MethodPut)
|
||||
|
||||
// v4
|
||||
router.HandleFunc("/api/v4/query_range", am.ViewAccess(ah.queryRangeV4)).Methods(http.MethodPost)
|
||||
|
||||
// Gateway
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
|
||||
"go.signoz.io/signoz/ee/query-service/constants"
|
||||
"go.signoz.io/signoz/ee/query-service/model"
|
||||
"go.signoz.io/signoz/pkg/http/render"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
@@ -60,21 +59,6 @@ type billingDetails struct {
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
type ApplyLicenseRequest struct {
|
||||
LicenseKey string `json:"key"`
|
||||
}
|
||||
|
||||
type ListLicenseResponse map[string]interface{}
|
||||
|
||||
func convertLicenseV3ToListLicenseResponse(licensesV3 []*model.LicenseV3) []ListLicenseResponse {
|
||||
listLicenses := []ListLicenseResponse{}
|
||||
|
||||
for _, license := range licensesV3 {
|
||||
listLicenses = append(listLicenses, license.Data)
|
||||
}
|
||||
return listLicenses
|
||||
}
|
||||
|
||||
func (ah *APIHandler) listLicenses(w http.ResponseWriter, r *http.Request) {
|
||||
licenses, apiError := ah.LM().GetLicenses(context.Background())
|
||||
if apiError != nil {
|
||||
@@ -104,51 +88,6 @@ func (ah *APIHandler) applyLicense(w http.ResponseWriter, r *http.Request) {
|
||||
ah.Respond(w, license)
|
||||
}
|
||||
|
||||
func (ah *APIHandler) listLicensesV3(w http.ResponseWriter, r *http.Request) {
|
||||
licenses, apiError := ah.LM().GetLicensesV3(r.Context())
|
||||
|
||||
if apiError != nil {
|
||||
RespondError(w, apiError, nil)
|
||||
return
|
||||
}
|
||||
|
||||
ah.Respond(w, convertLicenseV3ToListLicenseResponse(licenses))
|
||||
}
|
||||
|
||||
// this function is called by zeus when inserting licenses in the query-service
|
||||
func (ah *APIHandler) applyLicenseV3(w http.ResponseWriter, r *http.Request) {
|
||||
var licenseKey ApplyLicenseRequest
|
||||
|
||||
if err := json.NewDecoder(r.Body).Decode(&licenseKey); err != nil {
|
||||
RespondError(w, model.BadRequest(err), nil)
|
||||
return
|
||||
}
|
||||
|
||||
if licenseKey.LicenseKey == "" {
|
||||
RespondError(w, model.BadRequest(fmt.Errorf("license key is required")), nil)
|
||||
return
|
||||
}
|
||||
|
||||
_, apiError := ah.LM().ActivateV3(r.Context(), licenseKey.LicenseKey)
|
||||
if apiError != nil {
|
||||
RespondError(w, apiError, nil)
|
||||
return
|
||||
}
|
||||
|
||||
render.Success(w, http.StatusAccepted, nil)
|
||||
}
|
||||
|
||||
func (ah *APIHandler) refreshLicensesV3(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
apiError := ah.LM().RefreshLicense(r.Context())
|
||||
if apiError != nil {
|
||||
RespondError(w, apiError, nil)
|
||||
return
|
||||
}
|
||||
|
||||
render.Success(w, http.StatusNoContent, nil)
|
||||
}
|
||||
|
||||
func (ah *APIHandler) checkout(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
type checkoutResponse struct {
|
||||
@@ -215,45 +154,11 @@ func (ah *APIHandler) getBilling(w http.ResponseWriter, r *http.Request) {
|
||||
ah.Respond(w, billingResponse.Data)
|
||||
}
|
||||
|
||||
func convertLicenseV3ToLicenseV2(licenses []*model.LicenseV3) []model.License {
|
||||
licensesV2 := []model.License{}
|
||||
for _, l := range licenses {
|
||||
licenseV2 := model.License{
|
||||
Key: l.Key,
|
||||
ActivationId: "",
|
||||
PlanDetails: "",
|
||||
FeatureSet: l.Features,
|
||||
ValidationMessage: "",
|
||||
IsCurrent: l.IsCurrent,
|
||||
LicensePlan: model.LicensePlan{
|
||||
PlanKey: l.PlanName,
|
||||
ValidFrom: l.ValidFrom,
|
||||
ValidUntil: l.ValidUntil,
|
||||
Status: l.Status},
|
||||
}
|
||||
licensesV2 = append(licensesV2, licenseV2)
|
||||
}
|
||||
return licensesV2
|
||||
}
|
||||
|
||||
func (ah *APIHandler) listLicensesV2(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
var licenses []model.License
|
||||
|
||||
if ah.UseLicensesV3 {
|
||||
licensesV3, err := ah.LM().GetLicensesV3(r.Context())
|
||||
if err != nil {
|
||||
RespondError(w, err, nil)
|
||||
return
|
||||
}
|
||||
licenses = convertLicenseV3ToLicenseV2(licensesV3)
|
||||
} else {
|
||||
_licenses, apiError := ah.LM().GetLicenses(r.Context())
|
||||
if apiError != nil {
|
||||
RespondError(w, apiError, nil)
|
||||
return
|
||||
}
|
||||
licenses = _licenses
|
||||
licenses, apiError := ah.LM().GetLicenses(context.Background())
|
||||
if apiError != nil {
|
||||
RespondError(w, apiError, nil)
|
||||
}
|
||||
|
||||
resp := model.Licenses{
|
||||
|
||||
@@ -31,3 +31,19 @@ func (ah *APIHandler) searchTraces(w http.ResponseWriter, r *http.Request) {
|
||||
ah.WriteJSON(w, r, result)
|
||||
|
||||
}
|
||||
|
||||
func (ah *APIHandler) searchTracesV2(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
searchTracesParams, err := baseapp.ParseSearchTracesV2Params(r)
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: err}, "Error reading params")
|
||||
return
|
||||
}
|
||||
|
||||
result, err := ah.opts.DataConnector.SearchTracesV2(r.Context(), searchTracesParams)
|
||||
if ah.HandleError(w, err, http.StatusBadRequest) {
|
||||
return
|
||||
}
|
||||
|
||||
ah.WriteJSON(w, r, result)
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ import (
|
||||
"go.signoz.io/signoz/ee/query-service/rules"
|
||||
baseauth "go.signoz.io/signoz/pkg/query-service/auth"
|
||||
"go.signoz.io/signoz/pkg/query-service/migrate"
|
||||
"go.signoz.io/signoz/pkg/query-service/model"
|
||||
v3 "go.signoz.io/signoz/pkg/query-service/model/v3"
|
||||
|
||||
licensepkg "go.signoz.io/signoz/ee/query-service/license"
|
||||
@@ -77,7 +78,6 @@ type ServerOptions struct {
|
||||
Cluster string
|
||||
GatewayUrl string
|
||||
UseLogsNewSchema bool
|
||||
UseLicensesV3 bool
|
||||
}
|
||||
|
||||
// Server runs HTTP api service
|
||||
@@ -134,7 +134,7 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
|
||||
}
|
||||
|
||||
// initiate license manager
|
||||
lm, err := licensepkg.StartManager("sqlite", localDB, serverOptions.UseLicensesV3)
|
||||
lm, err := licensepkg.StartManager("sqlite", localDB)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -270,7 +270,6 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
|
||||
FluxInterval: fluxInterval,
|
||||
Gateway: gatewayProxy,
|
||||
UseLogsNewSchema: serverOptions.UseLogsNewSchema,
|
||||
UseLicensesV3: serverOptions.UseLicensesV3,
|
||||
}
|
||||
|
||||
apiHandler, err := api.NewAPIHandler(apiOpts)
|
||||
@@ -349,7 +348,7 @@ func (s *Server) createPublicServer(apiHandler *api.APIHandler) (*http.Server, e
|
||||
}
|
||||
|
||||
if user.User.OrgId == "" {
|
||||
return nil, basemodel.UnauthorizedError(errors.New("orgId is missing in the claims"))
|
||||
return nil, model.UnauthorizedError(errors.New("orgId is missing in the claims"))
|
||||
}
|
||||
|
||||
return user, nil
|
||||
@@ -766,9 +765,8 @@ func makeRulesManager(
|
||||
Cache: cache,
|
||||
EvalDelay: baseconst.GetEvalDelay(),
|
||||
|
||||
PrepareTaskFunc: rules.PrepareTaskFunc,
|
||||
PrepareTestRuleFunc: rules.TestNotification,
|
||||
UseLogsNewSchema: useLogsNewSchema,
|
||||
PrepareTaskFunc: rules.PrepareTaskFunc,
|
||||
UseLogsNewSchema: useLogsNewSchema,
|
||||
}
|
||||
|
||||
// create Manager
|
||||
|
||||
@@ -13,7 +13,6 @@ var LicenseAPIKey = GetOrDefaultEnv("SIGNOZ_LICENSE_API_KEY", "")
|
||||
var SaasSegmentKey = GetOrDefaultEnv("SIGNOZ_SAAS_SEGMENT_KEY", "")
|
||||
var FetchFeatures = GetOrDefaultEnv("FETCH_FEATURES", "false")
|
||||
var ZeusFeaturesURL = GetOrDefaultEnv("ZEUS_FEATURES_URL", "ZeusFeaturesURL")
|
||||
var ZeusURL = GetOrDefaultEnv("ZEUS_URL", "ZeusURL")
|
||||
|
||||
func GetOrDefaultEnv(key string, fallback string) string {
|
||||
v := os.Getenv(key)
|
||||
|
||||
@@ -9,8 +9,8 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
RoutePrefix string = "/api/gateway"
|
||||
AllowedPrefix []string = []string{"/v1/workspaces/me", "/v2/profiles/me", "/v2/deployments/me"}
|
||||
RoutePrefix string = "/api/gateway"
|
||||
AllowedPrefix []string = []string{"/v1/workspaces/me", "/v2/profiles/me"}
|
||||
)
|
||||
|
||||
type proxy struct {
|
||||
|
||||
@@ -13,8 +13,3 @@ type ActivationResponse struct {
|
||||
ActivationId string `json:"ActivationId"`
|
||||
PlanDetails string `json:"PlanDetails"`
|
||||
}
|
||||
|
||||
type ValidateLicenseResponse struct {
|
||||
Status status `json:"status"`
|
||||
Data map[string]interface{} `json:"data"`
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"go.uber.org/zap"
|
||||
@@ -24,14 +23,12 @@ const (
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
Prefix string
|
||||
GatewayUrl string
|
||||
Prefix string
|
||||
}
|
||||
|
||||
func New() *Client {
|
||||
return &Client{
|
||||
Prefix: constants.LicenseSignozIo,
|
||||
GatewayUrl: constants.ZeusURL,
|
||||
Prefix: constants.LicenseSignozIo,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,60 +116,6 @@ func ValidateLicense(activationId string) (*ActivationResponse, *model.ApiError)
|
||||
|
||||
}
|
||||
|
||||
func ValidateLicenseV3(licenseKey string) (*model.LicenseV3, *model.ApiError) {
|
||||
|
||||
// Creating an HTTP client with a timeout for better control
|
||||
client := &http.Client{
|
||||
Timeout: 10 * time.Second,
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("GET", C.GatewayUrl+"/v2/licenses/me", nil)
|
||||
if err != nil {
|
||||
return nil, model.BadRequest(errors.Wrap(err, fmt.Sprintf("failed to create request: %w", err)))
|
||||
}
|
||||
|
||||
// Setting the custom header
|
||||
req.Header.Set("X-Signoz-Cloud-Api-Key", licenseKey)
|
||||
|
||||
response, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, model.BadRequest(errors.Wrap(err, fmt.Sprintf("failed to make post request: %w", err)))
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return nil, model.BadRequest(errors.Wrap(err, fmt.Sprintf("failed to read validation response from %v", C.GatewayUrl)))
|
||||
}
|
||||
|
||||
defer response.Body.Close()
|
||||
|
||||
switch response.StatusCode {
|
||||
case 200:
|
||||
a := ValidateLicenseResponse{}
|
||||
err = json.Unmarshal(body, &a)
|
||||
if err != nil {
|
||||
return nil, model.BadRequest(errors.Wrap(err, "failed to marshal license validation response"))
|
||||
}
|
||||
|
||||
license, err := model.NewLicenseV3(a.Data)
|
||||
if err != nil {
|
||||
return nil, model.BadRequest(errors.Wrap(err, "failed to generate new license v3"))
|
||||
}
|
||||
|
||||
return license, nil
|
||||
case 400:
|
||||
return nil, model.BadRequest(errors.Wrap(fmt.Errorf(string(body)),
|
||||
fmt.Sprintf("bad request error received from %v", C.GatewayUrl)))
|
||||
case 401:
|
||||
return nil, model.Unauthorized(errors.Wrap(fmt.Errorf(string(body)),
|
||||
fmt.Sprintf("unauthorized request error received from %v", C.GatewayUrl)))
|
||||
default:
|
||||
return nil, model.InternalError(errors.Wrap(fmt.Errorf(string(body)),
|
||||
fmt.Sprintf("internal request error received from %v", C.GatewayUrl)))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func NewPostRequestWithCtx(ctx context.Context, url string, contentType string, body io.Reader) (*http.Request, error) {
|
||||
req, err := http.NewRequestWithContext(ctx, POST, url, body)
|
||||
if err != nil {
|
||||
|
||||
@@ -3,7 +3,6 @@ package license
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
@@ -49,34 +48,6 @@ func (r *Repo) GetLicenses(ctx context.Context) ([]model.License, error) {
|
||||
return licenses, nil
|
||||
}
|
||||
|
||||
func (r *Repo) GetLicensesV3(ctx context.Context) ([]*model.LicenseV3, error) {
|
||||
licensesData := []model.LicenseDB{}
|
||||
licenseV3Data := []*model.LicenseV3{}
|
||||
|
||||
query := "SELECT id,key,data FROM licenses_v3"
|
||||
|
||||
err := r.db.Select(&licensesData, query)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get licenses from db: %v", err)
|
||||
}
|
||||
|
||||
for _, l := range licensesData {
|
||||
var licenseData map[string]interface{}
|
||||
err := json.Unmarshal([]byte(l.Data), &licenseData)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal data into licenseData : %v", err)
|
||||
}
|
||||
|
||||
license, err := model.NewLicenseV3WithIDAndKey(l.ID, l.Key, licenseData)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get licenses v3 schema : %v", err)
|
||||
}
|
||||
licenseV3Data = append(licenseV3Data, license)
|
||||
}
|
||||
|
||||
return licenseV3Data, nil
|
||||
}
|
||||
|
||||
// GetActiveLicense fetches the latest active license from DB.
|
||||
// If the license is not present, expect a nil license and a nil error in the output.
|
||||
func (r *Repo) GetActiveLicense(ctx context.Context) (*model.License, *basemodel.ApiError) {
|
||||
@@ -108,45 +79,6 @@ func (r *Repo) GetActiveLicense(ctx context.Context) (*model.License, *basemodel
|
||||
return active, nil
|
||||
}
|
||||
|
||||
func (r *Repo) GetActiveLicenseV3(ctx context.Context) (*model.LicenseV3, error) {
|
||||
var err error
|
||||
licenses := []model.LicenseDB{}
|
||||
|
||||
query := "SELECT id,key,data FROM licenses_v3"
|
||||
|
||||
err = r.db.Select(&licenses, query)
|
||||
if err != nil {
|
||||
return nil, basemodel.InternalError(fmt.Errorf("failed to get active licenses from db: %v", err))
|
||||
}
|
||||
|
||||
var active *model.LicenseV3
|
||||
for _, l := range licenses {
|
||||
var licenseData map[string]interface{}
|
||||
err := json.Unmarshal([]byte(l.Data), &licenseData)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal data into licenseData : %v", err)
|
||||
}
|
||||
|
||||
license, err := model.NewLicenseV3WithIDAndKey(l.ID, l.Key, licenseData)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get licenses v3 schema : %v", err)
|
||||
}
|
||||
|
||||
if active == nil &&
|
||||
(license.ValidFrom != 0) &&
|
||||
(license.ValidUntil == -1 || license.ValidUntil > time.Now().Unix()) {
|
||||
active = license
|
||||
}
|
||||
if active != nil &&
|
||||
license.ValidFrom > active.ValidFrom &&
|
||||
(license.ValidUntil == -1 || license.ValidUntil > time.Now().Unix()) {
|
||||
active = license
|
||||
}
|
||||
}
|
||||
|
||||
return active, nil
|
||||
}
|
||||
|
||||
// InsertLicense inserts a new license in db
|
||||
func (r *Repo) InsertLicense(ctx context.Context, l *model.License) error {
|
||||
|
||||
@@ -272,53 +204,3 @@ func (r *Repo) InitFeatures(req basemodel.FeatureSet) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// InsertLicenseV3 inserts a new license v3 in db
|
||||
func (r *Repo) InsertLicenseV3(ctx context.Context, l *model.LicenseV3) error {
|
||||
|
||||
query := `INSERT INTO licenses_v3 (id, key, data) VALUES ($1, $2, $3)`
|
||||
|
||||
// licsense is the entity of zeus so putting the entire license here without defining schema
|
||||
licenseData, err := json.Marshal(l.Data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("insert license failed: license marshal error")
|
||||
}
|
||||
|
||||
_, err = r.db.ExecContext(ctx,
|
||||
query,
|
||||
l.ID,
|
||||
l.Key,
|
||||
string(licenseData),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
zap.L().Error("error in inserting license data: ", zap.Error(err))
|
||||
return fmt.Errorf("failed to insert license in db: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateLicenseV3 updates a new license v3 in db
|
||||
func (r *Repo) UpdateLicenseV3(ctx context.Context, l *model.LicenseV3) error {
|
||||
|
||||
// the key and id for the license can't change so only update the data here!
|
||||
query := `UPDATE licenses_v3 SET data=$1 WHERE id=$2;`
|
||||
|
||||
license, err := json.Marshal(l.Data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("insert license failed: license marshal error")
|
||||
}
|
||||
_, err = r.db.ExecContext(ctx,
|
||||
query,
|
||||
license,
|
||||
l.ID,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
zap.L().Error("error in updating license data: ", zap.Error(err))
|
||||
return fmt.Errorf("failed to update license in db: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"sync"
|
||||
|
||||
@@ -46,12 +45,11 @@ type Manager struct {
|
||||
failedAttempts uint64
|
||||
|
||||
// keep track of active license and features
|
||||
activeLicense *model.License
|
||||
activeLicenseV3 *model.LicenseV3
|
||||
activeFeatures basemodel.FeatureSet
|
||||
activeLicense *model.License
|
||||
activeFeatures basemodel.FeatureSet
|
||||
}
|
||||
|
||||
func StartManager(dbType string, db *sqlx.DB, useLicensesV3 bool, features ...basemodel.Feature) (*Manager, error) {
|
||||
func StartManager(dbType string, db *sqlx.DB, features ...basemodel.Feature) (*Manager, error) {
|
||||
if LM != nil {
|
||||
return LM, nil
|
||||
}
|
||||
@@ -67,7 +65,7 @@ func StartManager(dbType string, db *sqlx.DB, useLicensesV3 bool, features ...ba
|
||||
repo: &repo,
|
||||
}
|
||||
|
||||
if err := m.start(useLicensesV3, features...); err != nil {
|
||||
if err := m.start(features...); err != nil {
|
||||
return m, err
|
||||
}
|
||||
LM = m
|
||||
@@ -75,14 +73,8 @@ func StartManager(dbType string, db *sqlx.DB, useLicensesV3 bool, features ...ba
|
||||
}
|
||||
|
||||
// start loads active license in memory and initiates validator
|
||||
func (lm *Manager) start(useLicensesV3 bool, features ...basemodel.Feature) error {
|
||||
|
||||
var err error
|
||||
if useLicensesV3 {
|
||||
err = lm.LoadActiveLicenseV3(features...)
|
||||
} else {
|
||||
err = lm.LoadActiveLicense(features...)
|
||||
}
|
||||
func (lm *Manager) start(features ...basemodel.Feature) error {
|
||||
err := lm.LoadActiveLicense(features...)
|
||||
|
||||
return err
|
||||
}
|
||||
@@ -116,31 +108,6 @@ func (lm *Manager) SetActive(l *model.License, features ...basemodel.Feature) {
|
||||
go lm.Validator(context.Background())
|
||||
}
|
||||
|
||||
}
|
||||
func (lm *Manager) SetActiveV3(l *model.LicenseV3, features ...basemodel.Feature) {
|
||||
lm.mutex.Lock()
|
||||
defer lm.mutex.Unlock()
|
||||
|
||||
if l == nil {
|
||||
return
|
||||
}
|
||||
|
||||
lm.activeLicenseV3 = l
|
||||
lm.activeFeatures = append(l.Features, features...)
|
||||
// set default features
|
||||
setDefaultFeatures(lm)
|
||||
|
||||
err := lm.InitFeatures(lm.activeFeatures)
|
||||
if err != nil {
|
||||
zap.L().Panic("Couldn't activate features", zap.Error(err))
|
||||
}
|
||||
if !lm.validatorRunning {
|
||||
// we want to make sure only one validator runs,
|
||||
// we already have lock() so good to go
|
||||
lm.validatorRunning = true
|
||||
go lm.ValidatorV3(context.Background())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func setDefaultFeatures(lm *Manager) {
|
||||
@@ -170,28 +137,6 @@ func (lm *Manager) LoadActiveLicense(features ...basemodel.Feature) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (lm *Manager) LoadActiveLicenseV3(features ...basemodel.Feature) error {
|
||||
active, err := lm.repo.GetActiveLicenseV3(context.Background())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if active != nil {
|
||||
lm.SetActiveV3(active, features...)
|
||||
} else {
|
||||
zap.L().Info("No active license found, defaulting to basic plan")
|
||||
// if no active license is found, we default to basic(free) plan with all default features
|
||||
lm.activeFeatures = model.BasicPlan
|
||||
setDefaultFeatures(lm)
|
||||
err := lm.InitFeatures(lm.activeFeatures)
|
||||
if err != nil {
|
||||
zap.L().Error("Couldn't initialize features", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (lm *Manager) GetLicenses(ctx context.Context) (response []model.License, apiError *model.ApiError) {
|
||||
|
||||
licenses, err := lm.repo.GetLicenses(ctx)
|
||||
@@ -218,23 +163,6 @@ func (lm *Manager) GetLicenses(ctx context.Context) (response []model.License, a
|
||||
return
|
||||
}
|
||||
|
||||
func (lm *Manager) GetLicensesV3(ctx context.Context) (response []*model.LicenseV3, apiError *model.ApiError) {
|
||||
|
||||
licenses, err := lm.repo.GetLicensesV3(ctx)
|
||||
if err != nil {
|
||||
return nil, model.InternalError(err)
|
||||
}
|
||||
|
||||
for _, l := range licenses {
|
||||
if lm.activeLicenseV3 != nil && l.Key == lm.activeLicenseV3.Key {
|
||||
l.IsCurrent = true
|
||||
}
|
||||
response = append(response, l)
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// Validator validates license after an epoch of time
|
||||
func (lm *Manager) Validator(ctx context.Context) {
|
||||
defer close(lm.terminated)
|
||||
@@ -259,30 +187,6 @@ func (lm *Manager) Validator(ctx context.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
// Validator validates license after an epoch of time
|
||||
func (lm *Manager) ValidatorV3(ctx context.Context) {
|
||||
defer close(lm.terminated)
|
||||
tick := time.NewTicker(validationFrequency)
|
||||
defer tick.Stop()
|
||||
|
||||
lm.ValidateV3(ctx)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-lm.done:
|
||||
return
|
||||
default:
|
||||
select {
|
||||
case <-lm.done:
|
||||
return
|
||||
case <-tick.C:
|
||||
lm.ValidateV3(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Validate validates the current active license
|
||||
func (lm *Manager) Validate(ctx context.Context) (reterr error) {
|
||||
zap.L().Info("License validation started")
|
||||
@@ -350,54 +254,6 @@ func (lm *Manager) Validate(ctx context.Context) (reterr error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// todo[vikrantgupta25]: check the comparison here between old and new license!
|
||||
func (lm *Manager) RefreshLicense(ctx context.Context) *model.ApiError {
|
||||
|
||||
license, apiError := validate.ValidateLicenseV3(lm.activeLicenseV3.Key)
|
||||
if apiError != nil {
|
||||
zap.L().Error("failed to validate license", zap.Error(apiError.Err))
|
||||
return apiError
|
||||
}
|
||||
|
||||
err := lm.repo.UpdateLicenseV3(ctx, license)
|
||||
if err != nil {
|
||||
return model.BadRequest(errors.Wrap(err, "failed to update the new license"))
|
||||
}
|
||||
lm.SetActiveV3(license)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (lm *Manager) ValidateV3(ctx context.Context) (reterr error) {
|
||||
zap.L().Info("License validation started")
|
||||
if lm.activeLicenseV3 == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
defer func() {
|
||||
lm.mutex.Lock()
|
||||
|
||||
lm.lastValidated = time.Now().Unix()
|
||||
if reterr != nil {
|
||||
zap.L().Error("License validation completed with error", zap.Error(reterr))
|
||||
atomic.AddUint64(&lm.failedAttempts, 1)
|
||||
telemetry.GetInstance().SendEvent(telemetry.TELEMETRY_LICENSE_CHECK_FAILED,
|
||||
map[string]interface{}{"err": reterr.Error()}, "", true, false)
|
||||
} else {
|
||||
zap.L().Info("License validation completed with no errors")
|
||||
}
|
||||
|
||||
lm.mutex.Unlock()
|
||||
}()
|
||||
|
||||
err := lm.RefreshLicense(ctx)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Activate activates a license key with signoz server
|
||||
func (lm *Manager) Activate(ctx context.Context, key string) (licenseResponse *model.License, errResponse *model.ApiError) {
|
||||
defer func() {
|
||||
@@ -442,35 +298,6 @@ func (lm *Manager) Activate(ctx context.Context, key string) (licenseResponse *m
|
||||
return l, nil
|
||||
}
|
||||
|
||||
func (lm *Manager) ActivateV3(ctx context.Context, licenseKey string) (licenseResponse *model.LicenseV3, errResponse *model.ApiError) {
|
||||
defer func() {
|
||||
if errResponse != nil {
|
||||
userEmail, err := auth.GetEmailFromJwt(ctx)
|
||||
if err == nil {
|
||||
telemetry.GetInstance().SendEvent(telemetry.TELEMETRY_LICENSE_ACT_FAILED,
|
||||
map[string]interface{}{"err": errResponse.Err.Error()}, userEmail, true, false)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
license, apiError := validate.ValidateLicenseV3(licenseKey)
|
||||
if apiError != nil {
|
||||
zap.L().Error("failed to get the license", zap.Error(apiError.Err))
|
||||
return nil, apiError
|
||||
}
|
||||
|
||||
// insert the new license to the sqlite db
|
||||
err := lm.repo.InsertLicenseV3(ctx, license)
|
||||
if err != nil {
|
||||
zap.L().Error("failed to activate license", zap.Error(err))
|
||||
return nil, model.InternalError(err)
|
||||
}
|
||||
|
||||
// license is valid, activate it
|
||||
lm.SetActiveV3(license)
|
||||
return license, nil
|
||||
}
|
||||
|
||||
// CheckFeature will be internally used by backend routines
|
||||
// for feature gating
|
||||
func (lm *Manager) CheckFeature(featureKey string) error {
|
||||
|
||||
@@ -48,16 +48,5 @@ func InitDB(db *sqlx.DB) error {
|
||||
return fmt.Errorf("error in creating feature_status table: %s", err.Error())
|
||||
}
|
||||
|
||||
table_schema = `CREATE TABLE IF NOT EXISTS licenses_v3 (
|
||||
id TEXT PRIMARY KEY,
|
||||
key TEXT NOT NULL UNIQUE,
|
||||
data TEXT
|
||||
);`
|
||||
|
||||
_, err = db.Exec(table_schema)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error in creating licenses_v3 table: %s", err.Error())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -94,7 +94,6 @@ func main() {
|
||||
var cluster string
|
||||
|
||||
var useLogsNewSchema bool
|
||||
var useLicensesV3 bool
|
||||
var cacheConfigPath, fluxInterval string
|
||||
var enableQueryServiceLogOTLPExport bool
|
||||
var preferSpanMetrics bool
|
||||
@@ -105,7 +104,6 @@ func main() {
|
||||
var gatewayUrl string
|
||||
|
||||
flag.BoolVar(&useLogsNewSchema, "use-logs-new-schema", false, "use logs_v2 schema for logs")
|
||||
flag.BoolVar(&useLicensesV3, "use-licenses-v3", false, "use licenses_v3 schema for licenses")
|
||||
flag.StringVar(&promConfigPath, "config", "./config/prometheus.yml", "(prometheus config to read metrics)")
|
||||
flag.StringVar(&skipTopLvlOpsPath, "skip-top-level-ops", "", "(config file to skip top level operations)")
|
||||
flag.BoolVar(&disableRules, "rules.disable", false, "(disable rule evaluation)")
|
||||
@@ -145,7 +143,6 @@ func main() {
|
||||
Cluster: cluster,
|
||||
GatewayUrl: gatewayUrl,
|
||||
UseLogsNewSchema: useLogsNewSchema,
|
||||
UseLicensesV3: useLicensesV3,
|
||||
}
|
||||
|
||||
// Read the jwt secret key
|
||||
|
||||
@@ -46,13 +46,6 @@ func BadRequest(err error) *ApiError {
|
||||
}
|
||||
}
|
||||
|
||||
func Unauthorized(err error) *ApiError {
|
||||
return &ApiError{
|
||||
Typ: basemodel.ErrorUnauthorized,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
// BadRequestStr returns a ApiError object of bad request for string input
|
||||
func BadRequestStr(s string) *ApiError {
|
||||
return &ApiError{
|
||||
|
||||
@@ -3,8 +3,6 @@ package model
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
@@ -106,144 +104,3 @@ type SubscriptionServerResp struct {
|
||||
Status string `json:"status"`
|
||||
Data Licenses `json:"data"`
|
||||
}
|
||||
|
||||
type Plan struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type LicenseDB struct {
|
||||
ID string `json:"id"`
|
||||
Key string `json:"key"`
|
||||
Data string `json:"data"`
|
||||
}
|
||||
type LicenseV3 struct {
|
||||
ID string
|
||||
Key string
|
||||
Data map[string]interface{}
|
||||
PlanName string
|
||||
Features basemodel.FeatureSet
|
||||
Status string
|
||||
IsCurrent bool
|
||||
ValidFrom int64
|
||||
ValidUntil int64
|
||||
}
|
||||
|
||||
func extractKeyFromMapStringInterface[T any](data map[string]interface{}, key string) (T, error) {
|
||||
var zeroValue T
|
||||
if val, ok := data[key]; ok {
|
||||
if value, ok := val.(T); ok {
|
||||
return value, nil
|
||||
}
|
||||
return zeroValue, fmt.Errorf("%s key is not a valid %s", key, reflect.TypeOf(zeroValue))
|
||||
}
|
||||
return zeroValue, fmt.Errorf("%s key is missing", key)
|
||||
}
|
||||
|
||||
func NewLicenseV3(data map[string]interface{}) (*LicenseV3, error) {
|
||||
var features basemodel.FeatureSet
|
||||
|
||||
// extract id from data
|
||||
licenseID, err := extractKeyFromMapStringInterface[string](data, "id")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
delete(data, "id")
|
||||
|
||||
// extract key from data
|
||||
licenseKey, err := extractKeyFromMapStringInterface[string](data, "key")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
delete(data, "key")
|
||||
|
||||
// extract status from data
|
||||
status, err := extractKeyFromMapStringInterface[string](data, "status")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
planMap, err := extractKeyFromMapStringInterface[map[string]any](data, "plan")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
planName, err := extractKeyFromMapStringInterface[string](planMap, "name")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// if license status is inactive then default it to basic
|
||||
if status == LicenseStatusInactive {
|
||||
planName = PlanNameBasic
|
||||
}
|
||||
|
||||
featuresFromZeus := basemodel.FeatureSet{}
|
||||
if _features, ok := data["features"]; ok {
|
||||
featuresData, err := json.Marshal(_features)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to marshal features data")
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(featuresData, &featuresFromZeus); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to unmarshal features data")
|
||||
}
|
||||
}
|
||||
|
||||
switch planName {
|
||||
case PlanNameTeams:
|
||||
features = append(features, ProPlan...)
|
||||
case PlanNameEnterprise:
|
||||
features = append(features, EnterprisePlan...)
|
||||
case PlanNameBasic:
|
||||
features = append(features, BasicPlan...)
|
||||
default:
|
||||
features = append(features, BasicPlan...)
|
||||
}
|
||||
|
||||
if len(featuresFromZeus) > 0 {
|
||||
for _, feature := range featuresFromZeus {
|
||||
exists := false
|
||||
for i, existingFeature := range features {
|
||||
if existingFeature.Name == feature.Name {
|
||||
features[i] = feature // Replace existing feature
|
||||
exists = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !exists {
|
||||
features = append(features, feature) // Append if it doesn't exist
|
||||
}
|
||||
}
|
||||
}
|
||||
data["features"] = features
|
||||
|
||||
_validFrom, err := extractKeyFromMapStringInterface[float64](data, "valid_from")
|
||||
if err != nil {
|
||||
_validFrom = 0
|
||||
}
|
||||
validFrom := int64(_validFrom)
|
||||
|
||||
_validUntil, err := extractKeyFromMapStringInterface[float64](data, "valid_until")
|
||||
if err != nil {
|
||||
_validUntil = 0
|
||||
}
|
||||
validUntil := int64(_validUntil)
|
||||
|
||||
return &LicenseV3{
|
||||
ID: licenseID,
|
||||
Key: licenseKey,
|
||||
Data: data,
|
||||
PlanName: planName,
|
||||
Features: features,
|
||||
ValidFrom: validFrom,
|
||||
ValidUntil: validUntil,
|
||||
Status: status,
|
||||
}, nil
|
||||
|
||||
}
|
||||
|
||||
func NewLicenseV3WithIDAndKey(id string, key string, data map[string]interface{}) (*LicenseV3, error) {
|
||||
licenseDataWithIdAndKey := data
|
||||
licenseDataWithIdAndKey["id"] = id
|
||||
licenseDataWithIdAndKey["key"] = key
|
||||
return NewLicenseV3(licenseDataWithIdAndKey)
|
||||
}
|
||||
|
||||
@@ -1,170 +0,0 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.signoz.io/signoz/pkg/query-service/model"
|
||||
)
|
||||
|
||||
func TestNewLicenseV3(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
data []byte
|
||||
pass bool
|
||||
expected *LicenseV3
|
||||
error error
|
||||
}{
|
||||
{
|
||||
name: "Error for missing license id",
|
||||
data: []byte(`{}`),
|
||||
pass: false,
|
||||
error: errors.New("id key is missing"),
|
||||
},
|
||||
{
|
||||
name: "Error for license id not being a valid string",
|
||||
data: []byte(`{"id": 10}`),
|
||||
pass: false,
|
||||
error: errors.New("id key is not a valid string"),
|
||||
},
|
||||
{
|
||||
name: "Error for missing license key",
|
||||
data: []byte(`{"id":"does-not-matter"}`),
|
||||
pass: false,
|
||||
error: errors.New("key key is missing"),
|
||||
},
|
||||
{
|
||||
name: "Error for invalid string license key",
|
||||
data: []byte(`{"id":"does-not-matter","key":10}`),
|
||||
pass: false,
|
||||
error: errors.New("key key is not a valid string"),
|
||||
},
|
||||
{
|
||||
name: "Error for missing license status",
|
||||
data: []byte(`{"id":"does-not-matter", "key": "does-not-matter","category":"FREE"}`),
|
||||
pass: false,
|
||||
error: errors.New("status key is missing"),
|
||||
},
|
||||
{
|
||||
name: "Error for invalid string license status",
|
||||
data: []byte(`{"id":"does-not-matter","key": "does-not-matter", "category":"FREE", "status":10}`),
|
||||
pass: false,
|
||||
error: errors.New("status key is not a valid string"),
|
||||
},
|
||||
{
|
||||
name: "Error for missing license plan",
|
||||
data: []byte(`{"id":"does-not-matter","key":"does-not-matter-key","category":"FREE","status":"ACTIVE"}`),
|
||||
pass: false,
|
||||
error: errors.New("plan key is missing"),
|
||||
},
|
||||
{
|
||||
name: "Error for invalid json license plan",
|
||||
data: []byte(`{"id":"does-not-matter","key":"does-not-matter-key","category":"FREE","status":"ACTIVE","plan":10}`),
|
||||
pass: false,
|
||||
error: errors.New("plan key is not a valid map[string]interface {}"),
|
||||
},
|
||||
{
|
||||
name: "Error for invalid license plan",
|
||||
data: []byte(`{"id":"does-not-matter","key":"does-not-matter-key","category":"FREE","status":"ACTIVE","plan":{}}`),
|
||||
pass: false,
|
||||
error: errors.New("name key is missing"),
|
||||
},
|
||||
{
|
||||
name: "Parse the entire license properly",
|
||||
data: []byte(`{"id":"does-not-matter","key":"does-not-matter-key","category":"FREE","status":"ACTIVE","plan":{"name":"TEAMS"},"valid_from": 1730899309,"valid_until": -1}`),
|
||||
pass: true,
|
||||
expected: &LicenseV3{
|
||||
ID: "does-not-matter",
|
||||
Key: "does-not-matter-key",
|
||||
Data: map[string]interface{}{
|
||||
"plan": map[string]interface{}{
|
||||
"name": "TEAMS",
|
||||
},
|
||||
"category": "FREE",
|
||||
"status": "ACTIVE",
|
||||
"valid_from": float64(1730899309),
|
||||
"valid_until": float64(-1),
|
||||
},
|
||||
PlanName: PlanNameTeams,
|
||||
ValidFrom: 1730899309,
|
||||
ValidUntil: -1,
|
||||
Status: "ACTIVE",
|
||||
IsCurrent: false,
|
||||
Features: model.FeatureSet{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Fallback to basic plan if license status is inactive",
|
||||
data: []byte(`{"id":"does-not-matter","key":"does-not-matter-key","category":"FREE","status":"INACTIVE","plan":{"name":"TEAMS"},"valid_from": 1730899309,"valid_until": -1}`),
|
||||
pass: true,
|
||||
expected: &LicenseV3{
|
||||
ID: "does-not-matter",
|
||||
Key: "does-not-matter-key",
|
||||
Data: map[string]interface{}{
|
||||
"plan": map[string]interface{}{
|
||||
"name": "TEAMS",
|
||||
},
|
||||
"category": "FREE",
|
||||
"status": "INACTIVE",
|
||||
"valid_from": float64(1730899309),
|
||||
"valid_until": float64(-1),
|
||||
},
|
||||
PlanName: PlanNameBasic,
|
||||
ValidFrom: 1730899309,
|
||||
ValidUntil: -1,
|
||||
Status: "INACTIVE",
|
||||
IsCurrent: false,
|
||||
Features: model.FeatureSet{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "fallback states for validFrom and validUntil",
|
||||
data: []byte(`{"id":"does-not-matter","key":"does-not-matter-key","category":"FREE","status":"ACTIVE","plan":{"name":"TEAMS"},"valid_from":1234.456,"valid_until":5678.567}`),
|
||||
pass: true,
|
||||
expected: &LicenseV3{
|
||||
ID: "does-not-matter",
|
||||
Key: "does-not-matter-key",
|
||||
Data: map[string]interface{}{
|
||||
"plan": map[string]interface{}{
|
||||
"name": "TEAMS",
|
||||
},
|
||||
"valid_from": 1234.456,
|
||||
"valid_until": 5678.567,
|
||||
"category": "FREE",
|
||||
"status": "ACTIVE",
|
||||
},
|
||||
PlanName: PlanNameTeams,
|
||||
ValidFrom: 1234,
|
||||
ValidUntil: 5678,
|
||||
Status: "ACTIVE",
|
||||
IsCurrent: false,
|
||||
Features: model.FeatureSet{},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
var licensePayload map[string]interface{}
|
||||
err := json.Unmarshal(tc.data, &licensePayload)
|
||||
require.NoError(t, err)
|
||||
license, err := NewLicenseV3(licensePayload)
|
||||
if license != nil {
|
||||
license.Features = make(model.FeatureSet, 0)
|
||||
delete(license.Data, "features")
|
||||
}
|
||||
|
||||
if tc.pass {
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, license)
|
||||
assert.Equal(t, tc.expected, license)
|
||||
} else {
|
||||
require.Error(t, err)
|
||||
assert.EqualError(t, err, tc.error.Error())
|
||||
require.Nil(t, license)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"go.signoz.io/signoz/pkg/query-service/constants"
|
||||
basemodel "go.signoz.io/signoz/pkg/query-service/model"
|
||||
)
|
||||
|
||||
@@ -9,17 +8,6 @@ const SSO = "SSO"
|
||||
const Basic = "BASIC_PLAN"
|
||||
const Pro = "PRO_PLAN"
|
||||
const Enterprise = "ENTERPRISE_PLAN"
|
||||
|
||||
var (
|
||||
PlanNameEnterprise = "ENTERPRISE"
|
||||
PlanNameTeams = "TEAMS"
|
||||
PlanNameBasic = "BASIC"
|
||||
)
|
||||
|
||||
var (
|
||||
LicenseStatusInactive = "INACTIVE"
|
||||
)
|
||||
|
||||
const DisableUpsell = "DISABLE_UPSELL"
|
||||
const Onboarding = "ONBOARDING"
|
||||
const ChatSupport = "CHAT_SUPPORT"
|
||||
@@ -146,13 +134,6 @@ var BasicPlan = basemodel.FeatureSet{
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.HostsInfraMonitoring,
|
||||
Active: constants.EnableHostsInfraMonitoring(),
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
}
|
||||
|
||||
var ProPlan = basemodel.FeatureSet{
|
||||
@@ -268,13 +249,6 @@ var ProPlan = basemodel.FeatureSet{
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.HostsInfraMonitoring,
|
||||
Active: constants.EnableHostsInfraMonitoring(),
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
}
|
||||
|
||||
var EnterprisePlan = basemodel.FeatureSet{
|
||||
@@ -399,14 +373,7 @@ var EnterprisePlan = basemodel.FeatureSet{
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.AnomalyDetection,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.HostsInfraMonitoring,
|
||||
Active: constants.EnableHostsInfraMonitoring(),
|
||||
Active: false,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
|
||||
@@ -1,15 +1,10 @@
|
||||
package rules
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
basemodel "go.signoz.io/signoz/pkg/query-service/model"
|
||||
baserules "go.signoz.io/signoz/pkg/query-service/rules"
|
||||
"go.signoz.io/signoz/pkg/query-service/utils/labels"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func PrepareTaskFunc(opts baserules.PrepareTaskOptions) (baserules.Task, error) {
|
||||
@@ -84,106 +79,6 @@ func PrepareTaskFunc(opts baserules.PrepareTaskOptions) (baserules.Task, error)
|
||||
return task, nil
|
||||
}
|
||||
|
||||
// TestNotification prepares a dummy rule for given rule parameters and
|
||||
// sends a test notification. returns alert count and error (if any)
|
||||
func TestNotification(opts baserules.PrepareTestRuleOptions) (int, *basemodel.ApiError) {
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
if opts.Rule == nil {
|
||||
return 0, basemodel.BadRequest(fmt.Errorf("rule is required"))
|
||||
}
|
||||
|
||||
parsedRule := opts.Rule
|
||||
var alertname = parsedRule.AlertName
|
||||
if alertname == "" {
|
||||
// alertname is not mandatory for testing, so picking
|
||||
// a random string here
|
||||
alertname = uuid.New().String()
|
||||
}
|
||||
|
||||
// append name to indicate this is test alert
|
||||
parsedRule.AlertName = fmt.Sprintf("%s%s", alertname, baserules.TestAlertPostFix)
|
||||
|
||||
var rule baserules.Rule
|
||||
var err error
|
||||
|
||||
if parsedRule.RuleType == baserules.RuleTypeThreshold {
|
||||
|
||||
// add special labels for test alerts
|
||||
parsedRule.Annotations[labels.AlertSummaryLabel] = fmt.Sprintf("The rule threshold is set to %.4f, and the observed metric value is {{$value}}.", *parsedRule.RuleCondition.Target)
|
||||
parsedRule.Labels[labels.RuleSourceLabel] = ""
|
||||
parsedRule.Labels[labels.AlertRuleIdLabel] = ""
|
||||
|
||||
// create a threshold rule
|
||||
rule, err = baserules.NewThresholdRule(
|
||||
alertname,
|
||||
parsedRule,
|
||||
opts.FF,
|
||||
opts.Reader,
|
||||
opts.UseLogsNewSchema,
|
||||
baserules.WithSendAlways(),
|
||||
baserules.WithSendUnmatched(),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
zap.L().Error("failed to prepare a new threshold rule for test", zap.String("name", rule.Name()), zap.Error(err))
|
||||
return 0, basemodel.BadRequest(err)
|
||||
}
|
||||
|
||||
} else if parsedRule.RuleType == baserules.RuleTypeProm {
|
||||
|
||||
// create promql rule
|
||||
rule, err = baserules.NewPromRule(
|
||||
alertname,
|
||||
parsedRule,
|
||||
opts.Logger,
|
||||
opts.Reader,
|
||||
opts.ManagerOpts.PqlEngine,
|
||||
baserules.WithSendAlways(),
|
||||
baserules.WithSendUnmatched(),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
zap.L().Error("failed to prepare a new promql rule for test", zap.String("name", rule.Name()), zap.Error(err))
|
||||
return 0, basemodel.BadRequest(err)
|
||||
}
|
||||
} else if parsedRule.RuleType == baserules.RuleTypeAnomaly {
|
||||
// create anomaly rule
|
||||
rule, err = NewAnomalyRule(
|
||||
alertname,
|
||||
parsedRule,
|
||||
opts.FF,
|
||||
opts.Reader,
|
||||
opts.Cache,
|
||||
baserules.WithSendAlways(),
|
||||
baserules.WithSendUnmatched(),
|
||||
)
|
||||
if err != nil {
|
||||
zap.L().Error("failed to prepare a new anomaly rule for test", zap.String("name", rule.Name()), zap.Error(err))
|
||||
return 0, basemodel.BadRequest(err)
|
||||
}
|
||||
} else {
|
||||
return 0, basemodel.BadRequest(fmt.Errorf("failed to derive ruletype with given information"))
|
||||
}
|
||||
|
||||
// set timestamp to current utc time
|
||||
ts := time.Now().UTC()
|
||||
|
||||
count, err := rule.Eval(ctx, ts)
|
||||
if err != nil {
|
||||
zap.L().Error("evaluating rule failed", zap.String("rule", rule.Name()), zap.Error(err))
|
||||
return 0, basemodel.InternalError(fmt.Errorf("rule evaluation failed"))
|
||||
}
|
||||
alertsFound, ok := count.(int)
|
||||
if !ok {
|
||||
return 0, basemodel.InternalError(fmt.Errorf("something went wrong"))
|
||||
}
|
||||
rule.SendAlerts(ctx, ts, 0, time.Duration(1*time.Minute), opts.NotifyFunc)
|
||||
|
||||
return alertsFound, nil
|
||||
}
|
||||
|
||||
// newTask returns an appropriate group for
|
||||
// rule type
|
||||
func newTask(taskType baserules.TaskType, name string, frequency time.Duration, rules []baserules.Rule, opts *baserules.ManagerOptions, notify baserules.NotifyFunc, ruleDB baserules.RuleDB) baserules.Task {
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
"@dnd-kit/core": "6.1.0",
|
||||
"@dnd-kit/modifiers": "7.0.0",
|
||||
"@dnd-kit/sortable": "8.0.0",
|
||||
"@grafana/data": "^11.2.3",
|
||||
"@grafana/data": "^9.5.2",
|
||||
"@mdx-js/loader": "2.3.0",
|
||||
"@mdx-js/react": "2.3.0",
|
||||
"@monaco-editor/react": "^4.3.1",
|
||||
@@ -42,7 +42,7 @@
|
||||
"@radix-ui/react-tooltip": "1.0.7",
|
||||
"@sentry/react": "7.102.1",
|
||||
"@sentry/webpack-plugin": "2.16.0",
|
||||
"@signozhq/design-tokens": "1.1.4",
|
||||
"@signozhq/design-tokens": "0.0.8",
|
||||
"@uiw/react-md-editor": "3.23.5",
|
||||
"@visx/group": "3.3.0",
|
||||
"@visx/shape": "3.5.0",
|
||||
@@ -51,7 +51,7 @@
|
||||
"ansi-to-html": "0.7.2",
|
||||
"antd": "5.11.0",
|
||||
"antd-table-saveas-excel": "2.2.1",
|
||||
"axios": "1.7.7",
|
||||
"axios": "1.7.4",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"babel-jest": "^29.6.4",
|
||||
"babel-loader": "9.1.3",
|
||||
@@ -76,7 +76,7 @@
|
||||
"fontfaceobserver": "2.3.0",
|
||||
"history": "4.10.1",
|
||||
"html-webpack-plugin": "5.5.0",
|
||||
"http-proxy-middleware": "2.0.7",
|
||||
"http-proxy-middleware": "2.0.6",
|
||||
"i18next": "^21.6.12",
|
||||
"i18next-browser-languagedetector": "^6.1.3",
|
||||
"i18next-http-backend": "^1.3.2",
|
||||
@@ -87,8 +87,6 @@
|
||||
"lodash-es": "^4.17.21",
|
||||
"lucide-react": "0.379.0",
|
||||
"mini-css-extract-plugin": "2.4.5",
|
||||
"overlayscrollbars": "^2.8.1",
|
||||
"overlayscrollbars-react": "^0.5.6",
|
||||
"papaparse": "5.4.1",
|
||||
"posthog-js": "1.160.3",
|
||||
"rc-tween-one": "3.0.6",
|
||||
@@ -109,10 +107,11 @@
|
||||
"react-query": "3.39.3",
|
||||
"react-redux": "^7.2.2",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"react-router-dom-v5-compat": "6.27.0",
|
||||
"react-syntax-highlighter": "15.5.0",
|
||||
"react-use": "^17.3.2",
|
||||
"react-virtuoso": "4.0.3",
|
||||
"overlayscrollbars-react": "^0.5.6",
|
||||
"overlayscrollbars": "^2.8.1",
|
||||
"redux": "^4.0.5",
|
||||
"redux-thunk": "^2.3.0",
|
||||
"rehype-raw": "7.0.0",
|
||||
@@ -124,10 +123,10 @@
|
||||
"ts-node": "^10.2.1",
|
||||
"tsconfig-paths-webpack-plugin": "^3.5.1",
|
||||
"typescript": "^4.0.5",
|
||||
"uplot": "1.6.31",
|
||||
"uplot": "1.6.26",
|
||||
"uuid": "^8.3.2",
|
||||
"web-vitals": "^0.2.4",
|
||||
"webpack": "5.94.0",
|
||||
"webpack": "5.88.2",
|
||||
"webpack-dev-server": "^4.15.1",
|
||||
"webpack-retry-chunk-load-plugin": "3.1.1",
|
||||
"xstate": "^4.31.0"
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 62 KiB |
1
frontend/public/css/uPlot.min.css
vendored
1
frontend/public/css/uPlot.min.css
vendored
@@ -1 +0,0 @@
|
||||
.uplot, .uplot *, .uplot *::before, .uplot *::after {box-sizing: border-box;}.uplot {font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";line-height: 1.5;width: min-content;}.u-title {text-align: center;font-size: 18px;font-weight: bold;}.u-wrap {position: relative;user-select: none;}.u-over, .u-under {position: absolute;}.u-under {overflow: hidden;}.uplot canvas {display: block;position: relative;width: 100%;height: 100%;}.u-axis {position: absolute;}.u-legend {font-size: 14px;margin: auto;text-align: center;}.u-inline {display: block;}.u-inline * {display: inline-block;}.u-inline tr {margin-right: 16px;}.u-legend th {font-weight: 600;}.u-legend th > * {vertical-align: middle;display: inline-block;}.u-legend .u-marker {width: 1em;height: 1em;margin-right: 4px;background-clip: padding-box !important;}.u-inline.u-live th::after {content: ":";vertical-align: middle;}.u-inline:not(.u-live) .u-value {display: none;}.u-series > * {padding: 4px;}.u-series th {cursor: pointer;}.u-legend .u-off > * {opacity: 0.3;}.u-select {background: rgba(0,0,0,0.07);position: absolute;pointer-events: none;}.u-cursor-x, .u-cursor-y {position: absolute;left: 0;top: 0;pointer-events: none;will-change: transform;}.u-hz .u-cursor-x, .u-vt .u-cursor-y {height: 100%;border-right: 1px dashed #607D8B;}.u-hz .u-cursor-y, .u-vt .u-cursor-x {width: 100%;border-bottom: 1px dashed #607D8B;}.u-cursor-pt {position: absolute;top: 0;left: 0;border-radius: 50%;border: 0 solid;pointer-events: none;will-change: transform;/*this has to be !important since we set inline "background" shorthand */background-clip: padding-box !important;}.u-axis.u-off, .u-select.u-off, .u-cursor-x.u-off, .u-cursor-y.u-off, .u-cursor-pt.u-off {display: none;}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,24 +0,0 @@
|
||||
{
|
||||
"metricGraphCategory": {
|
||||
"brokerMetrics": {
|
||||
"title": "Broker Metrics",
|
||||
"description": "The Kafka Broker metrics here inform you of data loss/delay through unclean leader elections and network throughputs, as well as request fails through request purgatories and timeouts metrics"
|
||||
},
|
||||
"consumerMetrics": {
|
||||
"title": "Consumer Metrics",
|
||||
"description": "Kafka Consumer metrics provide insights into lag between message production and consumption, success rates and latency of message delivery, and the volume of data consumed."
|
||||
},
|
||||
"producerMetrics": {
|
||||
"title": "Producer Metrics",
|
||||
"description": "Kafka Producers send messages to brokers for storage and distribution by topic. These metrics inform you of the volume and rate of data sent, and the success rate of message delivery."
|
||||
},
|
||||
"brokerJVMMetrics": {
|
||||
"title": "Broker JVM Metrics",
|
||||
"description": "Kafka brokers are Java applications that expose JVM metrics to inform on the broker's system health. Garbage collection metrics like those below provide key insights into free memory, broker performance, and heap size. You need to enable new_gc_metrics for this section to populate."
|
||||
},
|
||||
"partitionMetrics": {
|
||||
"title": "Partition Metrics",
|
||||
"description": "Kafka partitions are the unit of parallelism in Kafka. These metrics inform you of the number of partitions per topic, the current offset of each partition, the oldest offset, and the number of in-sync replicas."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,54 +1,30 @@
|
||||
{
|
||||
"breadcrumb": "Messaging Queues",
|
||||
"header": "Kafka / Overview",
|
||||
"overview": {
|
||||
"title": "Start sending data in as little as 20 minutes",
|
||||
"subtitle": "Connect and Monitor Your Data Streams"
|
||||
},
|
||||
"configureConsumer": {
|
||||
"title": "Configure Consumer",
|
||||
"description": "Add consumer data sources to gain insights and enhance monitoring.",
|
||||
"button": "Get Started"
|
||||
},
|
||||
"configureProducer": {
|
||||
"title": "Configure Producer",
|
||||
"description": "Add producer data sources to gain insights and enhance monitoring.",
|
||||
"button": "Get Started"
|
||||
},
|
||||
"monitorKafka": {
|
||||
"title": "Monitor kafka",
|
||||
"description": "Add your Kafka source to gain insights and enhance activity tracking.",
|
||||
"button": "Get Started"
|
||||
},
|
||||
"summarySection": {
|
||||
"viewDetailsButton": "View Details",
|
||||
"consumer": {
|
||||
"title": "Consumer lag view",
|
||||
"description": "Connect and Monitor Your Data Streams"
|
||||
},
|
||||
"producer": {
|
||||
"title": "Producer latency view",
|
||||
"description": "Connect and Monitor Your Data Streams"
|
||||
},
|
||||
"partition": {
|
||||
"title": "Partition Latency view",
|
||||
"description": "Connect and Monitor Your Data Streams"
|
||||
},
|
||||
"dropRate": {
|
||||
"title": "Drop Rate view",
|
||||
"description": "Connect and Monitor Your Data Streams"
|
||||
},
|
||||
"metricPage": {
|
||||
"title": "Metric View",
|
||||
"description": "Connect and Monitor Your Data Streams"
|
||||
}
|
||||
},
|
||||
"confirmModal": {
|
||||
"content": "Before navigating to the details page, please make sure you have configured all the required setup to ensure correct data monitoring.",
|
||||
"okText": "Proceed"
|
||||
},
|
||||
"overviewSummarySection": {
|
||||
"title": "Monitor Your Data Streams",
|
||||
"subtitle": "Monitor key Kafka metrics like consumer lag and latency to ensure efficient data flow and troubleshoot in real time."
|
||||
}
|
||||
}
|
||||
"breadcrumb": "Messaging Queues",
|
||||
"header": "Kafka / Overview",
|
||||
"overview": {
|
||||
"title": "Start sending data in as little as 20 minutes",
|
||||
"subtitle": "Connect and Monitor Your Data Streams"
|
||||
},
|
||||
"configureConsumer": {
|
||||
"title": "Configure Consumer",
|
||||
"description": "Add consumer data sources to gain insights and enhance monitoring.",
|
||||
"button": "Get Started"
|
||||
},
|
||||
"configureProducer": {
|
||||
"title": "Configure Producer",
|
||||
"description": "Add producer data sources to gain insights and enhance monitoring.",
|
||||
"button": "Get Started"
|
||||
},
|
||||
"monitorKafka": {
|
||||
"title": "Monitor kafka",
|
||||
"description": "Add your Kafka source to gain insights and enhance activity tracking.",
|
||||
"button": "Get Started"
|
||||
},
|
||||
"summarySection": {
|
||||
"viewDetailsButton": "View Details"
|
||||
},
|
||||
"confirmModal": {
|
||||
"content": "Before navigating to the details page, please make sure you have configured all the required setup to ensure correct data monitoring.",
|
||||
"okText": "Proceed"
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
{
|
||||
"metricGraphCategory": {
|
||||
"brokerMetrics": {
|
||||
"title": "Broker Metrics",
|
||||
"description": "The Kafka Broker metrics here inform you of data loss/delay through unclean leader elections and network throughputs, as well as request fails through request purgatories and timeouts metrics"
|
||||
},
|
||||
"consumerMetrics": {
|
||||
"title": "Consumer Metrics",
|
||||
"description": "Kafka Consumer metrics provide insights into lag between message production and consumption, success rates and latency of message delivery, and the volume of data consumed."
|
||||
},
|
||||
"producerMetrics": {
|
||||
"title": "Producer Metrics",
|
||||
"description": "Kafka Producers send messages to brokers for storage and distribution by topic. These metrics inform you of the volume and rate of data sent, and the success rate of message delivery."
|
||||
},
|
||||
"brokerJVMMetrics": {
|
||||
"title": "Broker JVM Metrics",
|
||||
"description": "Kafka brokers are Java applications that expose JVM metrics to inform on the broker's system health. Garbage collection metrics like those below provide key insights into free memory, broker performance, and heap size. You need to enable new_gc_metrics for this section to populate."
|
||||
},
|
||||
"partitionMetrics": {
|
||||
"title": "Partition Metrics",
|
||||
"description": "Kafka partitions are the unit of parallelism in Kafka. These metrics inform you of the number of partitions per topic, the current offset of each partition, the oldest offset, and the number of in-sync replicas."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,54 +1,30 @@
|
||||
{
|
||||
"breadcrumb": "Messaging Queues",
|
||||
"header": "Kafka / Overview",
|
||||
"overview": {
|
||||
"title": "Start sending data in as little as 20 minutes",
|
||||
"subtitle": "Connect and Monitor Your Data Streams"
|
||||
},
|
||||
"configureConsumer": {
|
||||
"title": "Configure Consumer",
|
||||
"description": "Add consumer data sources to gain insights and enhance monitoring.",
|
||||
"button": "Get Started"
|
||||
},
|
||||
"configureProducer": {
|
||||
"title": "Configure Producer",
|
||||
"description": "Add producer data sources to gain insights and enhance monitoring.",
|
||||
"button": "Get Started"
|
||||
},
|
||||
"monitorKafka": {
|
||||
"title": "Monitor kafka",
|
||||
"description": "Add your Kafka source to gain insights and enhance activity tracking.",
|
||||
"button": "Get Started"
|
||||
},
|
||||
"summarySection": {
|
||||
"viewDetailsButton": "View Details",
|
||||
"consumer": {
|
||||
"title": "Consumer lag view",
|
||||
"description": "Connect and Monitor Your Data Streams"
|
||||
},
|
||||
"producer": {
|
||||
"title": "Producer latency view",
|
||||
"description": "Connect and Monitor Your Data Streams"
|
||||
},
|
||||
"partition": {
|
||||
"title": "Partition Latency view",
|
||||
"description": "Connect and Monitor Your Data Streams"
|
||||
},
|
||||
"dropRate": {
|
||||
"title": "Drop Rate view",
|
||||
"description": "Connect and Monitor Your Data Streams"
|
||||
},
|
||||
"metricPage": {
|
||||
"title": "Metric View",
|
||||
"description": "Connect and Monitor Your Data Streams"
|
||||
}
|
||||
},
|
||||
"confirmModal": {
|
||||
"content": "Before navigating to the details page, please make sure you have configured all the required setup to ensure correct data monitoring.",
|
||||
"okText": "Proceed"
|
||||
},
|
||||
"overviewSummarySection": {
|
||||
"title": "Monitor Your Data Streams",
|
||||
"subtitle": "Monitor key Kafka metrics like consumer lag and latency to ensure efficient data flow and troubleshoot in real time."
|
||||
}
|
||||
}
|
||||
"breadcrumb": "Messaging Queues",
|
||||
"header": "Kafka / Overview",
|
||||
"overview": {
|
||||
"title": "Start sending data in as little as 20 minutes",
|
||||
"subtitle": "Connect and Monitor Your Data Streams"
|
||||
},
|
||||
"configureConsumer": {
|
||||
"title": "Configure Consumer",
|
||||
"description": "Add consumer data sources to gain insights and enhance monitoring.",
|
||||
"button": "Get Started"
|
||||
},
|
||||
"configureProducer": {
|
||||
"title": "Configure Producer",
|
||||
"description": "Add producer data sources to gain insights and enhance monitoring.",
|
||||
"button": "Get Started"
|
||||
},
|
||||
"monitorKafka": {
|
||||
"title": "Monitor kafka",
|
||||
"description": "Add your Kafka source to gain insights and enhance activity tracking.",
|
||||
"button": "Get Started"
|
||||
},
|
||||
"summarySection": {
|
||||
"viewDetailsButton": "View Details"
|
||||
},
|
||||
"confirmModal": {
|
||||
"content": "Before navigating to the details page, please make sure you have configured all the required setup to ensure correct data monitoring.",
|
||||
"okText": "Proceed"
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@
|
||||
"SERVICE_METRICS": "SigNoz | Service Metrics",
|
||||
"SERVICE_MAP": "SigNoz | Service Map",
|
||||
"GET_STARTED": "SigNoz | Get Started",
|
||||
"ONBOARDING": "SigNoz | Get Started",
|
||||
"GET_STARTED_APPLICATION_MONITORING": "SigNoz | Get Started | APM",
|
||||
"GET_STARTED_LOGS_MANAGEMENT": "SigNoz | Get Started | Logs",
|
||||
"GET_STARTED_INFRASTRUCTURE_MONITORING": "SigNoz | Get Started | Infrastructure",
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import getLocalStorageApi from 'api/browser/localstorage/get';
|
||||
import getOrgUser from 'api/user/getOrgUser';
|
||||
import loginApi from 'api/user/login';
|
||||
import { Logout } from 'api/utils';
|
||||
import Spinner from 'components/Spinner';
|
||||
@@ -9,10 +8,8 @@ import ROUTES from 'constants/routes';
|
||||
import useLicense from 'hooks/useLicense';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import history from 'lib/history';
|
||||
import { isEmpty, isNull } from 'lodash-es';
|
||||
import { ReactChild, useEffect, useMemo, useState } from 'react';
|
||||
import { ReactChild, useEffect, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useQuery } from 'react-query';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { matchPath, Redirect, useLocation } from 'react-router-dom';
|
||||
import { Dispatch } from 'redux';
|
||||
@@ -20,7 +17,6 @@ import { AppState } from 'store/reducers';
|
||||
import { getInitialUserTokenRefreshToken } from 'store/utils';
|
||||
import AppActions from 'types/actions';
|
||||
import { UPDATE_USER_IS_FETCH } from 'types/actions/app';
|
||||
import { Organization } from 'types/api/user/getOrganization';
|
||||
import AppReducer from 'types/reducer/app';
|
||||
import { routePermission } from 'utils/permission';
|
||||
|
||||
@@ -35,19 +31,6 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
|
||||
const location = useLocation();
|
||||
const { pathname } = location;
|
||||
|
||||
const [isLoading, setIsLoading] = useState<boolean>(true);
|
||||
|
||||
const {
|
||||
org,
|
||||
orgPreferences,
|
||||
user,
|
||||
role,
|
||||
isUserFetching,
|
||||
isUserFetchingError,
|
||||
isLoggedIn: isLoggedInState,
|
||||
isFetchingOrgPreferences,
|
||||
} = useSelector<AppState, AppReducer>((state) => state.app);
|
||||
|
||||
const mapRoutes = useMemo(
|
||||
() =>
|
||||
new Map(
|
||||
@@ -61,21 +44,18 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
|
||||
[pathname],
|
||||
);
|
||||
|
||||
const isOnboardingComplete = useMemo(
|
||||
() =>
|
||||
orgPreferences?.find(
|
||||
(preference: Record<string, any>) => preference.key === 'ORG_ONBOARDING',
|
||||
)?.value,
|
||||
[orgPreferences],
|
||||
);
|
||||
|
||||
const {
|
||||
data: licensesData,
|
||||
isFetching: isFetchingLicensesData,
|
||||
} = useLicense();
|
||||
|
||||
const { t } = useTranslation(['common']);
|
||||
const {
|
||||
isUserFetching,
|
||||
isUserFetchingError,
|
||||
isLoggedIn: isLoggedInState,
|
||||
} = useSelector<AppState, AppReducer>((state) => state.app);
|
||||
|
||||
const { t } = useTranslation(['common']);
|
||||
const localStorageUserAuthToken = getInitialUserTokenRefreshToken();
|
||||
|
||||
const dispatch = useDispatch<Dispatch<AppActions>>();
|
||||
@@ -86,8 +66,6 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
|
||||
|
||||
const isOldRoute = oldRoutes.indexOf(pathname) > -1;
|
||||
|
||||
const [orgData, setOrgData] = useState<Organization | undefined>(undefined);
|
||||
|
||||
const isLocalStorageLoggedIn =
|
||||
getLocalStorageApi(LOCALSTORAGE.IS_LOGGED_IN) === 'true';
|
||||
|
||||
@@ -103,63 +81,6 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
|
||||
}
|
||||
};
|
||||
|
||||
const { data: orgUsers, isLoading: isLoadingOrgUsers } = useQuery({
|
||||
queryFn: () => {
|
||||
if (orgData && orgData.id !== undefined) {
|
||||
return getOrgUser({
|
||||
orgId: orgData.id,
|
||||
});
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
queryKey: ['getOrgUser'],
|
||||
enabled: !isEmpty(orgData),
|
||||
});
|
||||
|
||||
const checkFirstTimeUser = (): boolean => {
|
||||
const users = orgUsers?.payload || [];
|
||||
|
||||
const remainingUsers = users.filter(
|
||||
(user) => user.email !== 'admin@signoz.cloud',
|
||||
);
|
||||
|
||||
return remainingUsers.length === 1;
|
||||
};
|
||||
|
||||
// Check if the onboarding should be shown based on the org users and onboarding completion status, wait for org users and preferences to load
|
||||
const shouldShowOnboarding = (): boolean => {
|
||||
// Only run this effect if the org users and preferences are loaded
|
||||
|
||||
if (!isLoadingOrgUsers && !isFetchingOrgPreferences) {
|
||||
const isFirstUser = checkFirstTimeUser();
|
||||
|
||||
// Redirect to get started if it's not the first user or if the onboarding is complete
|
||||
return isFirstUser && !isOnboardingComplete;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
const handleRedirectForOrgOnboarding = (key: string): void => {
|
||||
if (
|
||||
isLoggedInState &&
|
||||
!isFetchingOrgPreferences &&
|
||||
!isLoadingOrgUsers &&
|
||||
!isEmpty(orgUsers?.payload) &&
|
||||
!isNull(orgPreferences)
|
||||
) {
|
||||
if (key === 'ONBOARDING' && isOnboardingComplete) {
|
||||
history.push(ROUTES.APPLICATION);
|
||||
}
|
||||
|
||||
const isFirstTimeUser = checkFirstTimeUser();
|
||||
|
||||
if (isFirstTimeUser && !isOnboardingComplete) {
|
||||
history.push(ROUTES.ONBOARDING);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleUserLoginIfTokenPresent = async (
|
||||
key: keyof typeof ROUTES,
|
||||
): Promise<void> => {
|
||||
@@ -181,8 +102,6 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
|
||||
response.payload.refreshJwt,
|
||||
);
|
||||
|
||||
handleRedirectForOrgOnboarding(key);
|
||||
|
||||
if (
|
||||
userResponse &&
|
||||
route &&
|
||||
@@ -210,7 +129,7 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
|
||||
) {
|
||||
handleUserLoginIfTokenPresent(key);
|
||||
} else {
|
||||
handleRedirectForOrgOnboarding(key);
|
||||
// user does have localstorage values
|
||||
|
||||
navigateToLoginIfNotLoggedIn(isLocalStorageLoggedIn);
|
||||
}
|
||||
@@ -241,45 +160,6 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
|
||||
}
|
||||
}, [isFetchingLicensesData]);
|
||||
|
||||
useEffect(() => {
|
||||
if (org && org.length > 0 && org[0].id !== undefined) {
|
||||
setOrgData(org[0]);
|
||||
}
|
||||
}, [org]);
|
||||
|
||||
const handleRouting = (): void => {
|
||||
const showOrgOnboarding = shouldShowOnboarding();
|
||||
|
||||
if (showOrgOnboarding && !isOnboardingComplete) {
|
||||
history.push(ROUTES.ONBOARDING);
|
||||
} else {
|
||||
history.push(ROUTES.APPLICATION);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const { isPrivate } = currentRoute || {
|
||||
isPrivate: false,
|
||||
};
|
||||
|
||||
if (isLoggedInState && role && role !== 'ADMIN') {
|
||||
setIsLoading(false);
|
||||
}
|
||||
|
||||
if (!isPrivate) {
|
||||
setIsLoading(false);
|
||||
}
|
||||
|
||||
if (
|
||||
!isEmpty(user) &&
|
||||
!isFetchingOrgPreferences &&
|
||||
!isEmpty(orgUsers?.payload) &&
|
||||
!isNull(orgPreferences)
|
||||
) {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [currentRoute, user, role, orgUsers, orgPreferences]);
|
||||
|
||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||
useEffect(() => {
|
||||
(async (): Promise<void> => {
|
||||
@@ -301,8 +181,9 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
|
||||
handlePrivateRoutes(key);
|
||||
} else {
|
||||
// no need to fetch the user and make user fetching false
|
||||
|
||||
if (getLocalStorageApi(LOCALSTORAGE.IS_LOGGED_IN) === 'true') {
|
||||
handleRouting();
|
||||
history.push(ROUTES.APPLICATION);
|
||||
}
|
||||
dispatch({
|
||||
type: UPDATE_USER_IS_FETCH,
|
||||
@@ -314,7 +195,7 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
|
||||
} else if (pathname === ROUTES.HOME_PAGE) {
|
||||
// routing to application page over root page
|
||||
if (isLoggedInState) {
|
||||
handleRouting();
|
||||
history.push(ROUTES.APPLICATION);
|
||||
} else {
|
||||
navigateToLoginIfNotLoggedIn();
|
||||
}
|
||||
@@ -327,20 +208,13 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
|
||||
history.push(ROUTES.SOMETHING_WENT_WRONG);
|
||||
}
|
||||
})();
|
||||
}, [
|
||||
dispatch,
|
||||
isLoggedInState,
|
||||
currentRoute,
|
||||
licensesData,
|
||||
orgUsers,
|
||||
orgPreferences,
|
||||
]);
|
||||
}, [dispatch, isLoggedInState, currentRoute, licensesData]);
|
||||
|
||||
if (isUserFetchingError) {
|
||||
return <Redirect to={ROUTES.SOMETHING_WENT_WRONG} />;
|
||||
}
|
||||
|
||||
if (isUserFetching || isLoading) {
|
||||
if (isUserFetching) {
|
||||
return <Spinner tip="Loading..." />;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ import { ConfigProvider } from 'antd';
|
||||
import getLocalStorageApi from 'api/browser/localstorage/get';
|
||||
import setLocalStorageApi from 'api/browser/localstorage/set';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import getAllOrgPreferences from 'api/preferences/getAllOrgPreferences';
|
||||
import NotFound from 'components/NotFound';
|
||||
import Spinner from 'components/Spinner';
|
||||
import { FeatureKeys } from 'constants/features';
|
||||
@@ -25,20 +24,13 @@ import AlertRuleProvider from 'providers/Alert';
|
||||
import { DashboardProvider } from 'providers/Dashboard/Dashboard';
|
||||
import { QueryBuilderProvider } from 'providers/QueryBuilder';
|
||||
import { Suspense, useEffect, useState } from 'react';
|
||||
import { useQuery } from 'react-query';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { Route, Router, Switch } from 'react-router-dom';
|
||||
import { CompatRouter } from 'react-router-dom-v5-compat';
|
||||
import { Dispatch } from 'redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import AppActions from 'types/actions';
|
||||
import {
|
||||
UPDATE_FEATURE_FLAG_RESPONSE,
|
||||
UPDATE_IS_FETCHING_ORG_PREFERENCES,
|
||||
UPDATE_ORG_PREFERENCES,
|
||||
} from 'types/actions/app';
|
||||
import { UPDATE_FEATURE_FLAG_RESPONSE } from 'types/actions/app';
|
||||
import AppReducer, { User } from 'types/reducer/app';
|
||||
import { USER_ROLES } from 'types/roles';
|
||||
import { extractDomain, isCloudUser, isEECloudUser } from 'utils/app';
|
||||
|
||||
import PrivateRoute from './Private';
|
||||
@@ -73,41 +65,6 @@ function App(): JSX.Element {
|
||||
const isPremiumSupportEnabled =
|
||||
useFeatureFlags(FeatureKeys.PREMIUM_SUPPORT)?.active || false;
|
||||
|
||||
const { data: orgPreferences, isLoading: isLoadingOrgPreferences } = useQuery({
|
||||
queryFn: () => getAllOrgPreferences(),
|
||||
queryKey: ['getOrgPreferences'],
|
||||
enabled: isLoggedInState && role === USER_ROLES.ADMIN,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (orgPreferences && !isLoadingOrgPreferences) {
|
||||
dispatch({
|
||||
type: UPDATE_IS_FETCHING_ORG_PREFERENCES,
|
||||
payload: {
|
||||
isFetchingOrgPreferences: false,
|
||||
},
|
||||
});
|
||||
|
||||
dispatch({
|
||||
type: UPDATE_ORG_PREFERENCES,
|
||||
payload: {
|
||||
orgPreferences: orgPreferences.payload?.data || null,
|
||||
},
|
||||
});
|
||||
}
|
||||
}, [orgPreferences, dispatch, isLoadingOrgPreferences]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isLoggedInState && role !== USER_ROLES.ADMIN) {
|
||||
dispatch({
|
||||
type: UPDATE_IS_FETCHING_ORG_PREFERENCES,
|
||||
payload: {
|
||||
isFetchingOrgPreferences: false,
|
||||
},
|
||||
});
|
||||
}
|
||||
}, [isLoggedInState, role, dispatch]);
|
||||
|
||||
const featureResponse = useGetFeatureFlag((allFlags) => {
|
||||
dispatch({
|
||||
type: UPDATE_FEATURE_FLAG_RESPONSE,
|
||||
@@ -225,16 +182,6 @@ function App(): JSX.Element {
|
||||
}, [isLoggedInState, isOnBasicPlan, user]);
|
||||
|
||||
useEffect(() => {
|
||||
if (pathname === ROUTES.ONBOARDING) {
|
||||
window.Intercom('update', {
|
||||
hide_default_launcher: true,
|
||||
});
|
||||
} else {
|
||||
window.Intercom('update', {
|
||||
hide_default_launcher: false,
|
||||
});
|
||||
}
|
||||
|
||||
trackPageView(pathname);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [pathname]);
|
||||
@@ -257,7 +204,6 @@ function App(): JSX.Element {
|
||||
user,
|
||||
licenseData,
|
||||
isPremiumSupportEnabled,
|
||||
pathname,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -293,38 +239,36 @@ function App(): JSX.Element {
|
||||
return (
|
||||
<ConfigProvider theme={themeConfig}>
|
||||
<Router history={history}>
|
||||
<CompatRouter>
|
||||
<NotificationProvider>
|
||||
<PrivateRoute>
|
||||
<ResourceProvider>
|
||||
<QueryBuilderProvider>
|
||||
<DashboardProvider>
|
||||
<KeyboardHotkeysProvider>
|
||||
<AlertRuleProvider>
|
||||
<AppLayout>
|
||||
<Suspense fallback={<Spinner size="large" tip="Loading..." />}>
|
||||
<Switch>
|
||||
{routes.map(({ path, component, exact }) => (
|
||||
<Route
|
||||
key={`${path}`}
|
||||
exact={exact}
|
||||
path={path}
|
||||
component={component}
|
||||
/>
|
||||
))}
|
||||
<NotificationProvider>
|
||||
<PrivateRoute>
|
||||
<ResourceProvider>
|
||||
<QueryBuilderProvider>
|
||||
<DashboardProvider>
|
||||
<KeyboardHotkeysProvider>
|
||||
<AlertRuleProvider>
|
||||
<AppLayout>
|
||||
<Suspense fallback={<Spinner size="large" tip="Loading..." />}>
|
||||
<Switch>
|
||||
{routes.map(({ path, component, exact }) => (
|
||||
<Route
|
||||
key={`${path}`}
|
||||
exact={exact}
|
||||
path={path}
|
||||
component={component}
|
||||
/>
|
||||
))}
|
||||
|
||||
<Route path="*" component={NotFound} />
|
||||
</Switch>
|
||||
</Suspense>
|
||||
</AppLayout>
|
||||
</AlertRuleProvider>
|
||||
</KeyboardHotkeysProvider>
|
||||
</DashboardProvider>
|
||||
</QueryBuilderProvider>
|
||||
</ResourceProvider>
|
||||
</PrivateRoute>
|
||||
</NotificationProvider>
|
||||
</CompatRouter>
|
||||
<Route path="*" component={NotFound} />
|
||||
</Switch>
|
||||
</Suspense>
|
||||
</AppLayout>
|
||||
</AlertRuleProvider>
|
||||
</KeyboardHotkeysProvider>
|
||||
</DashboardProvider>
|
||||
</QueryBuilderProvider>
|
||||
</ResourceProvider>
|
||||
</PrivateRoute>
|
||||
</NotificationProvider>
|
||||
</Router>
|
||||
</ConfigProvider>
|
||||
);
|
||||
|
||||
@@ -43,7 +43,8 @@ export const TraceFilter = Loadable(
|
||||
);
|
||||
|
||||
export const TraceDetail = Loadable(
|
||||
() => import(/* webpackChunkName: "TraceDetail Page" */ 'pages/TraceDetail'),
|
||||
() =>
|
||||
import(/* webpackChunkName: "TraceDetail Page" */ 'pages/TraceDetailsV2'),
|
||||
);
|
||||
|
||||
export const UsageExplorerPage = Loadable(
|
||||
@@ -66,10 +67,6 @@ export const Onboarding = Loadable(
|
||||
() => import(/* webpackChunkName: "Onboarding" */ 'pages/OnboardingPage'),
|
||||
);
|
||||
|
||||
export const OrgOnboarding = Loadable(
|
||||
() => import(/* webpackChunkName: "OrgOnboarding" */ 'pages/OrgOnboarding'),
|
||||
);
|
||||
|
||||
export const DashboardPage = Loadable(
|
||||
() =>
|
||||
import(/* webpackChunkName: "DashboardPage" */ 'pages/DashboardsListPage'),
|
||||
|
||||
@@ -32,7 +32,6 @@ import {
|
||||
OldLogsExplorer,
|
||||
Onboarding,
|
||||
OrganizationSettings,
|
||||
OrgOnboarding,
|
||||
PasswordReset,
|
||||
PipelinePage,
|
||||
ServiceMapPage,
|
||||
@@ -69,13 +68,6 @@ const routes: AppRoutes[] = [
|
||||
isPrivate: true,
|
||||
key: 'GET_STARTED',
|
||||
},
|
||||
{
|
||||
path: ROUTES.ONBOARDING,
|
||||
exact: false,
|
||||
component: OrgOnboarding,
|
||||
isPrivate: true,
|
||||
key: 'ONBOARDING',
|
||||
},
|
||||
{
|
||||
component: LogsIndexToFields,
|
||||
path: ROUTES.LOGS_INDEX_FIELDS,
|
||||
|
||||
@@ -7,6 +7,7 @@ const create = async (
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
const response = await axios.post('/rules', {
|
||||
...props.data,
|
||||
version: 'v4',
|
||||
});
|
||||
|
||||
return {
|
||||
|
||||
@@ -4,7 +4,6 @@ export const apiV2 = '/api/v2/';
|
||||
export const apiV3 = '/api/v3/';
|
||||
export const apiV4 = '/api/v4/';
|
||||
export const gatewayApiV1 = '/api/gateway/v1/';
|
||||
export const gatewayApiV2 = '/api/gateway/v2/';
|
||||
export const apiAlertManager = '/api/alertmanager/';
|
||||
|
||||
export default apiV1;
|
||||
|
||||
@@ -15,7 +15,6 @@ import apiV1, {
|
||||
apiV3,
|
||||
apiV4,
|
||||
gatewayApiV1,
|
||||
gatewayApiV2,
|
||||
} from './apiV1';
|
||||
import { Logout } from './utils';
|
||||
|
||||
@@ -170,19 +169,6 @@ GatewayApiV1Instance.interceptors.response.use(
|
||||
GatewayApiV1Instance.interceptors.request.use(interceptorsRequestResponse);
|
||||
//
|
||||
|
||||
// gateway Api V2
|
||||
export const GatewayApiV2Instance = axios.create({
|
||||
baseURL: `${ENVIRONMENT.baseURL}${gatewayApiV2}`,
|
||||
});
|
||||
|
||||
GatewayApiV2Instance.interceptors.response.use(
|
||||
interceptorsResponse,
|
||||
interceptorRejected,
|
||||
);
|
||||
|
||||
GatewayApiV2Instance.interceptors.request.use(interceptorsRequestResponse);
|
||||
//
|
||||
|
||||
AxiosAlertManagerInstance.interceptors.response.use(
|
||||
interceptorsResponse,
|
||||
interceptorRejected,
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
import { ApiBaseInstance } from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError } from 'axios';
|
||||
import { SOMETHING_WENT_WRONG } from 'constants/api';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
|
||||
export interface OnboardingStatusResponse {
|
||||
status: string;
|
||||
data: {
|
||||
attribute?: string;
|
||||
error_message?: string;
|
||||
status?: string;
|
||||
}[];
|
||||
}
|
||||
|
||||
const getOnboardingStatus = async (props: {
|
||||
start: number;
|
||||
end: number;
|
||||
endpointService?: string;
|
||||
}): Promise<SuccessResponse<OnboardingStatusResponse> | ErrorResponse> => {
|
||||
const { endpointService, ...rest } = props;
|
||||
try {
|
||||
const response = await ApiBaseInstance.post(
|
||||
`/messaging-queues/kafka/onboarding/${endpointService || 'consumers'}`,
|
||||
rest,
|
||||
);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler((error as AxiosError) || SOMETHING_WENT_WRONG);
|
||||
}
|
||||
};
|
||||
|
||||
export default getOnboardingStatus;
|
||||
@@ -1,20 +0,0 @@
|
||||
import { GatewayApiV2Instance } from 'api';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { UpdateProfileProps } from 'types/api/onboarding/types';
|
||||
|
||||
const updateProfile = async (
|
||||
props: UpdateProfileProps,
|
||||
): Promise<SuccessResponse<UpdateProfileProps> | ErrorResponse> => {
|
||||
const response = await GatewayApiV2Instance.put('/profiles/me', {
|
||||
...props,
|
||||
});
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data.data,
|
||||
};
|
||||
};
|
||||
|
||||
export default updateProfile;
|
||||
@@ -1,18 +0,0 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { GetAllOrgPreferencesResponseProps } from 'types/api/preferences/userOrgPreferences';
|
||||
|
||||
const getAllOrgPreferences = async (): Promise<
|
||||
SuccessResponse<GetAllOrgPreferencesResponseProps> | ErrorResponse
|
||||
> => {
|
||||
const response = await axios.get(`/org/preferences`);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data,
|
||||
};
|
||||
};
|
||||
|
||||
export default getAllOrgPreferences;
|
||||
@@ -1,18 +0,0 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { GetAllUserPreferencesResponseProps } from 'types/api/preferences/userOrgPreferences';
|
||||
|
||||
const getAllUserPreferences = async (): Promise<
|
||||
SuccessResponse<GetAllUserPreferencesResponseProps> | ErrorResponse
|
||||
> => {
|
||||
const response = await axios.get(`/user/preferences`);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data,
|
||||
};
|
||||
};
|
||||
|
||||
export default getAllUserPreferences;
|
||||
@@ -1,20 +0,0 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { GetOrgPreferenceResponseProps } from 'types/api/preferences/userOrgPreferences';
|
||||
|
||||
const getOrgPreference = async ({
|
||||
preferenceID,
|
||||
}: {
|
||||
preferenceID: string;
|
||||
}): Promise<SuccessResponse<GetOrgPreferenceResponseProps> | ErrorResponse> => {
|
||||
const response = await axios.get(`/org/preferences/${preferenceID}`);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data,
|
||||
};
|
||||
};
|
||||
|
||||
export default getOrgPreference;
|
||||
@@ -1,22 +0,0 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { GetUserPreferenceResponseProps } from 'types/api/preferences/userOrgPreferences';
|
||||
|
||||
const getUserPreference = async ({
|
||||
preferenceID,
|
||||
}: {
|
||||
preferenceID: string;
|
||||
}): Promise<
|
||||
SuccessResponse<GetUserPreferenceResponseProps> | ErrorResponse
|
||||
> => {
|
||||
const response = await axios.get(`/user/preferences/${preferenceID}`);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data,
|
||||
};
|
||||
};
|
||||
|
||||
export default getUserPreference;
|
||||
@@ -1,28 +0,0 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import {
|
||||
UpdateOrgPreferenceProps,
|
||||
UpdateOrgPreferenceResponseProps,
|
||||
} from 'types/api/preferences/userOrgPreferences';
|
||||
|
||||
const updateOrgPreference = async (
|
||||
preferencePayload: UpdateOrgPreferenceProps,
|
||||
): Promise<
|
||||
SuccessResponse<UpdateOrgPreferenceResponseProps> | ErrorResponse
|
||||
> => {
|
||||
const response = await axios.put(
|
||||
`/org/preferences/${preferencePayload.preferenceID}`,
|
||||
{
|
||||
preference_value: preferencePayload.value,
|
||||
},
|
||||
);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data.data,
|
||||
};
|
||||
};
|
||||
|
||||
export default updateOrgPreference;
|
||||
@@ -1,25 +0,0 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import {
|
||||
UpdateUserPreferenceProps,
|
||||
UpdateUserPreferenceResponseProps,
|
||||
} from 'types/api/preferences/userOrgPreferences';
|
||||
|
||||
const updateUserPreference = async (
|
||||
preferencePayload: UpdateUserPreferenceProps,
|
||||
): Promise<
|
||||
SuccessResponse<UpdateUserPreferenceResponseProps> | ErrorResponse
|
||||
> => {
|
||||
const response = await axios.put(`/user/preferences`, {
|
||||
preference_value: preferencePayload.value,
|
||||
});
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data.data,
|
||||
};
|
||||
};
|
||||
|
||||
export default updateUserPreference;
|
||||
33
frontend/src/api/trace/getTraceDetails.ts
Normal file
33
frontend/src/api/trace/getTraceDetails.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { ApiV2Instance } from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import {
|
||||
GetTraceDetailsProps,
|
||||
GetTraceDetailsSuccessResponse,
|
||||
} from 'types/api/trace/getTraceDetails';
|
||||
|
||||
const getTraceDetails = async (
|
||||
props: GetTraceDetailsProps,
|
||||
): Promise<SuccessResponse<GetTraceDetailsSuccessResponse> | ErrorResponse> => {
|
||||
try {
|
||||
const response = await ApiV2Instance.post<GetTraceDetailsSuccessResponse>(
|
||||
`/traces/${props.traceID}`,
|
||||
{
|
||||
spanId: props.spanID,
|
||||
uncollapsedNodes: props.uncollapsedNodes,
|
||||
},
|
||||
);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: 'Success',
|
||||
payload: response.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default getTraceDetails;
|
||||
@@ -1,18 +0,0 @@
|
||||
import axios from 'api';
|
||||
import { SuccessResponse } from 'types/api';
|
||||
import { InviteUsersResponse, UsersProps } from 'types/api/user/inviteUsers';
|
||||
|
||||
const inviteUsers = async (
|
||||
users: UsersProps,
|
||||
): Promise<SuccessResponse<InviteUsersResponse>> => {
|
||||
const response = await axios.post(`/invite/bulk`, users);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data,
|
||||
};
|
||||
};
|
||||
|
||||
export default inviteUsers;
|
||||
@@ -1,3 +1,46 @@
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { AlertDef } from 'types/api/alerts/def';
|
||||
import { Dashboard, DashboardData } from 'types/api/dashboard/getAll';
|
||||
|
||||
export const chartHelpMessage = (
|
||||
selectedDashboard: Dashboard | undefined,
|
||||
graphType: PANEL_TYPES,
|
||||
): string => `
|
||||
Hi Team,
|
||||
|
||||
I need help in creating this chart. Here are my dashboard details
|
||||
|
||||
Name: ${selectedDashboard?.data.title || ''}
|
||||
Panel type: ${graphType}
|
||||
Dashboard Id: ${selectedDashboard?.uuid || ''}
|
||||
|
||||
Thanks`;
|
||||
|
||||
export const dashboardHelpMessage = (
|
||||
data: DashboardData | undefined,
|
||||
selectedDashboard: Dashboard | undefined,
|
||||
): string => `
|
||||
Hi Team,
|
||||
|
||||
I need help with this dashboard. Here are my dashboard details
|
||||
|
||||
Name: ${data?.title || ''}
|
||||
Dashboard Id: ${selectedDashboard?.uuid || ''}
|
||||
|
||||
Thanks`;
|
||||
|
||||
export const dashboardListMessage = `Hi Team,
|
||||
|
||||
I need help with dashboards.
|
||||
|
||||
Thanks`;
|
||||
|
||||
export const listAlertMessage = `Hi Team,
|
||||
|
||||
I need help with managing alerts.
|
||||
|
||||
Thanks`;
|
||||
|
||||
export const onboardingHelpMessage = (
|
||||
dataSourceName: string,
|
||||
moduleId: string,
|
||||
@@ -12,3 +55,35 @@ Module: ${moduleId}
|
||||
|
||||
Thanks
|
||||
`;
|
||||
|
||||
export const alertHelpMessage = (
|
||||
alertDef: AlertDef,
|
||||
ruleId: number,
|
||||
): string => `
|
||||
Hi Team,
|
||||
|
||||
I need help in configuring this alert. Here are my alert rule details
|
||||
|
||||
Name: ${alertDef?.alert || ''}
|
||||
Alert Type: ${alertDef?.alertType || ''}
|
||||
State: ${(alertDef as any)?.state || ''}
|
||||
Alert Id: ${ruleId}
|
||||
|
||||
Thanks`;
|
||||
|
||||
export const integrationsListMessage = `Hi Team,
|
||||
|
||||
I need help with Integrations.
|
||||
|
||||
Thanks`;
|
||||
|
||||
export const integrationDetailMessage = (
|
||||
selectedIntegration: string,
|
||||
): string => `
|
||||
Hi Team,
|
||||
|
||||
I need help in configuring this integration.
|
||||
|
||||
Integration Id: ${selectedIntegration}
|
||||
|
||||
Thanks`;
|
||||
|
||||
@@ -17,7 +17,6 @@ describe('getLogIndicatorType', () => {
|
||||
body: 'Sample log Message',
|
||||
resources_string: {},
|
||||
attributesString: {},
|
||||
scope_string: {},
|
||||
attributes_string: {},
|
||||
attributesInt: {},
|
||||
attributesFloat: {},
|
||||
@@ -41,7 +40,6 @@ describe('getLogIndicatorType', () => {
|
||||
body: 'Sample log Message',
|
||||
resources_string: {},
|
||||
attributesString: {},
|
||||
scope_string: {},
|
||||
attributes_string: {},
|
||||
attributesInt: {},
|
||||
attributesFloat: {},
|
||||
@@ -64,7 +62,6 @@ describe('getLogIndicatorType', () => {
|
||||
body: 'Sample log Message',
|
||||
resources_string: {},
|
||||
attributesString: {},
|
||||
scope_string: {},
|
||||
attributes_string: {},
|
||||
attributesInt: {},
|
||||
attributesFloat: {},
|
||||
@@ -86,7 +83,6 @@ describe('getLogIndicatorType', () => {
|
||||
body: 'Sample log',
|
||||
resources_string: {},
|
||||
attributesString: {},
|
||||
scope_string: {},
|
||||
attributes_string: {
|
||||
log_level: 'INFO' as never,
|
||||
},
|
||||
@@ -116,7 +112,6 @@ describe('getLogIndicatorTypeForTable', () => {
|
||||
attributesString: {},
|
||||
attributes_string: {},
|
||||
attributesInt: {},
|
||||
scope_string: {},
|
||||
attributesFloat: {},
|
||||
severity_text: 'WARN',
|
||||
};
|
||||
@@ -135,7 +130,6 @@ describe('getLogIndicatorTypeForTable', () => {
|
||||
severity_number: 0,
|
||||
body: 'Sample log message',
|
||||
resources_string: {},
|
||||
scope_string: {},
|
||||
attributesString: {},
|
||||
attributes_string: {},
|
||||
attributesInt: {},
|
||||
@@ -172,7 +166,6 @@ describe('logIndicatorBySeverityNumber', () => {
|
||||
body: 'Sample log Message',
|
||||
resources_string: {},
|
||||
attributesString: {},
|
||||
scope_string: {},
|
||||
attributes_string: {},
|
||||
attributesInt: {},
|
||||
attributesFloat: {},
|
||||
|
||||
@@ -37,8 +37,4 @@ export enum QueryParams {
|
||||
partition = 'partition',
|
||||
selectedTimelineQuery = 'selectedTimelineQuery',
|
||||
ruleType = 'ruleType',
|
||||
configDetail = 'configDetail',
|
||||
getStartedSource = 'getStartedSource',
|
||||
getStartedSourceService = 'getStartedSourceService',
|
||||
mqServiceView = 'mqServiceView',
|
||||
}
|
||||
|
||||
@@ -3,6 +3,10 @@ import { QueryFunctionsTypes } from 'types/common/queryBuilder';
|
||||
import { SelectOption } from 'types/common/select';
|
||||
|
||||
export const metricQueryFunctionOptions: SelectOption<string, string>[] = [
|
||||
{
|
||||
value: QueryFunctionsTypes.ANOMALY,
|
||||
label: 'Anomaly',
|
||||
},
|
||||
{
|
||||
value: QueryFunctionsTypes.CUTOFF_MIN,
|
||||
label: 'Cut Off Min',
|
||||
|
||||
@@ -18,5 +18,4 @@ export const REACT_QUERY_KEY = {
|
||||
GET_ALL_ALLERTS: 'GET_ALL_ALLERTS',
|
||||
REMOVE_ALERT_RULE: 'REMOVE_ALERT_RULE',
|
||||
DUPLICATE_ALERT_RULE: 'DUPLICATE_ALERT_RULE',
|
||||
UPDATE_ALERT_RULE: 'UPDATE_ALERT_RULE',
|
||||
};
|
||||
|
||||
@@ -8,7 +8,6 @@ const ROUTES = {
|
||||
TRACE_DETAIL: '/trace/:id',
|
||||
TRACES_EXPLORER: '/traces-explorer',
|
||||
GET_STARTED: '/get-started',
|
||||
ONBOARDING: '/onboarding',
|
||||
GET_STARTED_APPLICATION_MONITORING: '/get-started/application-monitoring',
|
||||
GET_STARTED_LOGS_MANAGEMENT: '/get-started/logs-management',
|
||||
GET_STARTED_INFRASTRUCTURE_MONITORING:
|
||||
|
||||
@@ -63,16 +63,6 @@
|
||||
flex-direction: row;
|
||||
gap: 8px;
|
||||
|
||||
.anomaly-alert-evaluation-view-series-list-item-color {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
|
||||
display: inline-flex;
|
||||
margin-right: 8px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@@ -118,63 +108,3 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.uplot-tooltip {
|
||||
background-color: rgba(0, 0, 0, 0.9);
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
color: #ddd;
|
||||
font-size: 13px;
|
||||
line-height: 1.4;
|
||||
padding: 8px 12px;
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
z-index: 100;
|
||||
max-height: 500px;
|
||||
width: 280px;
|
||||
overflow-y: auto;
|
||||
display: none; /* Hide tooltip by default */
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 0.3rem;
|
||||
}
|
||||
&::-webkit-scrollbar-corner {
|
||||
background: transparent;
|
||||
}
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: rgb(136, 136, 136);
|
||||
border-radius: 0.625rem;
|
||||
}
|
||||
&::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.uplot-tooltip-title {
|
||||
font-weight: bold;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.uplot-tooltip-series {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
padding: 4px 0px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.uplot-tooltip-series-name {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.uplot-tooltip-band {
|
||||
font-style: italic;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.uplot-tooltip-marker {
|
||||
display: inline-block;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
margin-right: 8px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
@@ -13,8 +13,6 @@ import { LineChart } from 'lucide-react';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import uPlot from 'uplot';
|
||||
|
||||
import tooltipPlugin from './tooltipPlugin';
|
||||
|
||||
function UplotChart({
|
||||
data,
|
||||
options,
|
||||
@@ -74,13 +72,13 @@ function AnomalyAlertEvaluationView({
|
||||
const dimensions = useResizeObserver(graphRef);
|
||||
|
||||
useEffect(() => {
|
||||
const chartData = getUplotChartDataForAnomalyDetection(data, isDarkMode);
|
||||
const chartData = getUplotChartDataForAnomalyDetection(data);
|
||||
setSeriesData(chartData);
|
||||
|
||||
setAllSeries(Object.keys(chartData));
|
||||
|
||||
setFilteredSeriesKeys(Object.keys(chartData));
|
||||
}, [data, isDarkMode]);
|
||||
}, [data]);
|
||||
|
||||
useEffect(() => {
|
||||
const seriesKeys = Object.keys(seriesData);
|
||||
@@ -151,38 +149,10 @@ function AnomalyAlertEvaluationView({
|
||||
const options = {
|
||||
width: dimensions.width,
|
||||
height: dimensions.height - 36,
|
||||
plugins: [bandsPlugin, tooltipPlugin(isDarkMode)],
|
||||
plugins: [bandsPlugin],
|
||||
focus: {
|
||||
alpha: 0.3,
|
||||
},
|
||||
legend: {
|
||||
show: true,
|
||||
live: false,
|
||||
isolate: true,
|
||||
},
|
||||
cursor: {
|
||||
lock: false,
|
||||
focus: {
|
||||
prox: 1e6,
|
||||
bias: 1,
|
||||
},
|
||||
points: {
|
||||
size: (
|
||||
u: { series: { [x: string]: { points: { size: number } } } },
|
||||
seriesIdx: string | number,
|
||||
): number => u.series[seriesIdx].points.size * 3,
|
||||
width: (u: any, seriesIdx: any, size: number): number => size / 4,
|
||||
stroke: (
|
||||
u: {
|
||||
series: {
|
||||
[x: string]: { points: { stroke: (arg0: any, arg1: any) => any } };
|
||||
};
|
||||
},
|
||||
seriesIdx: string | number,
|
||||
): string => `${u.series[seriesIdx].points.stroke(u, seriesIdx)}90`,
|
||||
fill: (): string => '#fff',
|
||||
},
|
||||
},
|
||||
series: [
|
||||
{
|
||||
label: 'Time',
|
||||
@@ -195,7 +165,6 @@ function AnomalyAlertEvaluationView({
|
||||
width: 2,
|
||||
show: true,
|
||||
paths: _spline,
|
||||
spanGaps: true,
|
||||
},
|
||||
{
|
||||
label: `Predicted Value`,
|
||||
@@ -204,29 +173,18 @@ function AnomalyAlertEvaluationView({
|
||||
dash: [2, 2],
|
||||
show: true,
|
||||
paths: _spline,
|
||||
spanGaps: true,
|
||||
},
|
||||
{
|
||||
label: `Upper Band`,
|
||||
stroke: 'transparent',
|
||||
show: true,
|
||||
show: false,
|
||||
paths: _spline,
|
||||
spanGaps: true,
|
||||
points: {
|
||||
show: false,
|
||||
size: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
label: `Lower Band`,
|
||||
stroke: 'transparent',
|
||||
show: true,
|
||||
show: false,
|
||||
paths: _spline,
|
||||
spanGaps: true,
|
||||
points: {
|
||||
show: false,
|
||||
size: 1,
|
||||
},
|
||||
},
|
||||
]
|
||||
: allSeries.map((seriesKey) => ({
|
||||
@@ -235,13 +193,11 @@ function AnomalyAlertEvaluationView({
|
||||
width: 2,
|
||||
show: true,
|
||||
paths: _spline,
|
||||
spanGaps: true,
|
||||
}))),
|
||||
],
|
||||
scales: {
|
||||
x: {
|
||||
time: true,
|
||||
spanGaps: true,
|
||||
},
|
||||
y: {
|
||||
...getYAxisScaleForAnomalyDetection({
|
||||
@@ -255,6 +211,9 @@ function AnomalyAlertEvaluationView({
|
||||
grid: {
|
||||
show: true,
|
||||
},
|
||||
legend: {
|
||||
show: true,
|
||||
},
|
||||
axes: getAxes(isDarkMode, yAxisUnit),
|
||||
};
|
||||
|
||||
@@ -328,24 +287,17 @@ function AnomalyAlertEvaluationView({
|
||||
)}
|
||||
|
||||
{filteredSeriesKeys.map((seriesKey) => (
|
||||
<div key={seriesKey}>
|
||||
<Checkbox
|
||||
className="anomaly-alert-evaluation-view-series-list-item"
|
||||
key={seriesKey}
|
||||
type="checkbox"
|
||||
name="series"
|
||||
value={seriesKey}
|
||||
checked={selectedSeries === seriesKey}
|
||||
onChange={(): void => handleSeriesChange(seriesKey)}
|
||||
>
|
||||
<div
|
||||
className="anomaly-alert-evaluation-view-series-list-item-color"
|
||||
style={{ backgroundColor: seriesData[seriesKey].color }}
|
||||
/>
|
||||
|
||||
{seriesKey}
|
||||
</Checkbox>
|
||||
</div>
|
||||
<Checkbox
|
||||
className="anomaly-alert-evaluation-view-series-list-item"
|
||||
key={seriesKey}
|
||||
type="checkbox"
|
||||
name="series"
|
||||
value={seriesKey}
|
||||
checked={selectedSeries === seriesKey}
|
||||
onChange={(): void => handleSeriesChange(seriesKey)}
|
||||
>
|
||||
{seriesKey}
|
||||
</Checkbox>
|
||||
))}
|
||||
|
||||
{filteredSeriesKeys.length === 0 && (
|
||||
|
||||
@@ -1,148 +0,0 @@
|
||||
import { themeColors } from 'constants/theme';
|
||||
import { generateColor } from 'lib/uPlotLib/utils/generateColor';
|
||||
|
||||
const tooltipPlugin = (
|
||||
isDarkMode: boolean,
|
||||
): { hooks: { init: (u: any) => void } } => {
|
||||
let tooltip: HTMLDivElement;
|
||||
const tooltipLeftOffset = 10;
|
||||
const tooltipTopOffset = 10;
|
||||
let isMouseOverPlot = false;
|
||||
|
||||
function formatValue(value: string | number | Date): string | number | Date {
|
||||
if (typeof value === 'string' && !Number.isNaN(parseFloat(value))) {
|
||||
return parseFloat(value).toFixed(3);
|
||||
}
|
||||
if (typeof value === 'number') {
|
||||
return value.toFixed(3);
|
||||
}
|
||||
if (value instanceof Date) {
|
||||
return value.toLocaleString();
|
||||
}
|
||||
if (value == null) {
|
||||
return 'N/A';
|
||||
}
|
||||
|
||||
return String(value);
|
||||
}
|
||||
|
||||
function updateTooltip(u: any, left: number, top: number): void {
|
||||
const idx = u.posToIdx(left);
|
||||
const xVal = u.data[0][idx];
|
||||
|
||||
if (xVal == null) {
|
||||
tooltip.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
|
||||
const xDate = new Date(xVal * 1000);
|
||||
const formattedXDate = formatValue(xDate);
|
||||
|
||||
let tooltipContent = `<div class="uplot-tooltip-title">Time: ${formattedXDate}</div>`;
|
||||
|
||||
let mainValue;
|
||||
let upperBand;
|
||||
let lowerBand;
|
||||
|
||||
let color = null;
|
||||
|
||||
// Loop through all series (excluding the x-axis series)
|
||||
for (let i = 1; i < u.series.length; i++) {
|
||||
const series = u.series[i];
|
||||
|
||||
const yVal = u.data[i][idx];
|
||||
const formattedYVal = formatValue(yVal);
|
||||
|
||||
color = generateColor(
|
||||
series.label,
|
||||
isDarkMode ? themeColors.chartcolors : themeColors.lightModeColor,
|
||||
);
|
||||
|
||||
// Create the round marker for the series
|
||||
const marker = `<span class="uplot-tooltip-marker" style="background-color: ${color};"></span>`;
|
||||
|
||||
if (series.label.toLowerCase().includes('upper band')) {
|
||||
upperBand = formattedYVal;
|
||||
} else if (series.label.toLowerCase().includes('lower band')) {
|
||||
lowerBand = formattedYVal;
|
||||
} else if (series.label.toLowerCase().includes('main series')) {
|
||||
mainValue = formattedYVal;
|
||||
} else {
|
||||
tooltipContent += `
|
||||
<div class="uplot-tooltip-series">
|
||||
${marker}
|
||||
<span class="uplot-tooltip-series-name">${series.label}:</span>
|
||||
<span class="uplot-tooltip-series-value">${formattedYVal}</span>
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
// Add main value, upper band, and lower band to the tooltip
|
||||
if (mainValue !== undefined) {
|
||||
const marker = `<span class="uplot-tooltip-marker"></span>`;
|
||||
tooltipContent += `
|
||||
<div class="uplot-tooltip-series">
|
||||
${marker}
|
||||
<span class="uplot-tooltip-series-name">Main Series:</span>
|
||||
<span class="uplot-tooltip-series-value">${mainValue}</span>
|
||||
</div>`;
|
||||
}
|
||||
if (upperBand !== undefined) {
|
||||
const marker = `<span class="uplot-tooltip-marker"></span>`;
|
||||
tooltipContent += `
|
||||
<div class="uplot-tooltip-series">
|
||||
${marker}
|
||||
<span class="uplot-tooltip-series-name">Upper Band:</span>
|
||||
<span class="uplot-tooltip-series-value">${upperBand}</span>
|
||||
</div>`;
|
||||
}
|
||||
if (lowerBand !== undefined) {
|
||||
const marker = `<span class="uplot-tooltip-marker"></span>`;
|
||||
tooltipContent += `
|
||||
<div class="uplot-tooltip-series">
|
||||
${marker}
|
||||
<span class="uplot-tooltip-series-name">Lower Band:</span>
|
||||
<span class="uplot-tooltip-series-value">${lowerBand}</span>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
tooltip.innerHTML = tooltipContent;
|
||||
tooltip.style.display = 'block';
|
||||
tooltip.style.left = `${left + tooltipLeftOffset}px`;
|
||||
tooltip.style.top = `${top + tooltipTopOffset}px`;
|
||||
}
|
||||
|
||||
function init(u: any): void {
|
||||
tooltip = document.createElement('div');
|
||||
tooltip.className = 'uplot-tooltip';
|
||||
tooltip.style.display = 'none';
|
||||
u.over.appendChild(tooltip);
|
||||
|
||||
// Add event listeners
|
||||
u.over.addEventListener('mouseenter', () => {
|
||||
isMouseOverPlot = true;
|
||||
});
|
||||
|
||||
u.over.addEventListener('mouseleave', () => {
|
||||
isMouseOverPlot = false;
|
||||
tooltip.style.display = 'none';
|
||||
});
|
||||
|
||||
u.over.addEventListener('mousemove', (e: MouseEvent) => {
|
||||
if (isMouseOverPlot) {
|
||||
const rect = u.over.getBoundingClientRect();
|
||||
const left = e.clientX - rect.left;
|
||||
const top = e.clientY - rect.top;
|
||||
updateTooltip(u, left, top);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
hooks: {
|
||||
init,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export default tooltipPlugin;
|
||||
@@ -7,8 +7,6 @@
|
||||
width: calc(100% - 64px);
|
||||
z-index: 0;
|
||||
|
||||
margin: 0 auto;
|
||||
|
||||
.content-container {
|
||||
position: relative;
|
||||
margin: 0 1rem;
|
||||
|
||||
@@ -191,7 +191,6 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
|
||||
const pageTitle = t(routeKey);
|
||||
const renderFullScreen =
|
||||
pathname === ROUTES.GET_STARTED ||
|
||||
pathname === ROUTES.ONBOARDING ||
|
||||
pathname === ROUTES.GET_STARTED_APPLICATION_MONITORING ||
|
||||
pathname === ROUTES.GET_STARTED_INFRASTRUCTURE_MONITORING ||
|
||||
pathname === ROUTES.GET_STARTED_LOGS_MANAGEMENT ||
|
||||
|
||||
@@ -4,7 +4,6 @@ import {
|
||||
initialQueryPromQLData,
|
||||
PANEL_TYPES,
|
||||
} from 'constants/queryBuilder';
|
||||
import { AlertDetectionTypes } from 'container/FormAlertRules';
|
||||
import { AlertTypes } from 'types/api/alerts/alertTypes';
|
||||
import {
|
||||
AlertDef,
|
||||
@@ -57,57 +56,10 @@ export const alertDefaults: AlertDef = {
|
||||
},
|
||||
annotations: defaultAnnotations,
|
||||
evalWindow: defaultEvalWindow,
|
||||
alert: '',
|
||||
};
|
||||
|
||||
export const anamolyAlertDefaults: AlertDef = {
|
||||
alertType: AlertTypes.METRICS_BASED_ALERT,
|
||||
version: ENTITY_VERSION_V4,
|
||||
ruleType: AlertDetectionTypes.ANOMALY_DETECTION_ALERT,
|
||||
condition: {
|
||||
compositeQuery: {
|
||||
builderQueries: {
|
||||
A: {
|
||||
...initialQueryBuilderFormValuesMap.metrics,
|
||||
functions: [
|
||||
{
|
||||
name: 'anomaly',
|
||||
args: [],
|
||||
namedArgs: { z_score_threshold: 3 },
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
promQueries: { A: initialQueryPromQLData },
|
||||
chQueries: {
|
||||
A: {
|
||||
name: 'A',
|
||||
query: ``,
|
||||
legend: '',
|
||||
disabled: false,
|
||||
},
|
||||
},
|
||||
queryType: EQueryType.QUERY_BUILDER,
|
||||
panelType: PANEL_TYPES.TIME_SERIES,
|
||||
unit: undefined,
|
||||
},
|
||||
op: defaultCompareOp,
|
||||
matchType: defaultMatchType,
|
||||
algorithm: defaultAlgorithm,
|
||||
seasonality: defaultSeasonality,
|
||||
target: 3,
|
||||
},
|
||||
labels: {
|
||||
severity: 'warning',
|
||||
},
|
||||
annotations: defaultAnnotations,
|
||||
evalWindow: defaultEvalWindow,
|
||||
alert: '',
|
||||
};
|
||||
|
||||
export const logAlertDefaults: AlertDef = {
|
||||
alertType: AlertTypes.LOGS_BASED_ALERT,
|
||||
version: ENTITY_VERSION_V4,
|
||||
condition: {
|
||||
compositeQuery: {
|
||||
builderQueries: {
|
||||
@@ -134,12 +86,10 @@ export const logAlertDefaults: AlertDef = {
|
||||
},
|
||||
annotations: defaultAnnotations,
|
||||
evalWindow: defaultEvalWindow,
|
||||
alert: '',
|
||||
};
|
||||
|
||||
export const traceAlertDefaults: AlertDef = {
|
||||
alertType: AlertTypes.TRACES_BASED_ALERT,
|
||||
version: ENTITY_VERSION_V4,
|
||||
condition: {
|
||||
compositeQuery: {
|
||||
builderQueries: {
|
||||
@@ -166,12 +116,10 @@ export const traceAlertDefaults: AlertDef = {
|
||||
},
|
||||
annotations: defaultAnnotations,
|
||||
evalWindow: defaultEvalWindow,
|
||||
alert: '',
|
||||
};
|
||||
|
||||
export const exceptionAlertDefaults: AlertDef = {
|
||||
alertType: AlertTypes.EXCEPTIONS_BASED_ALERT,
|
||||
version: ENTITY_VERSION_V4,
|
||||
condition: {
|
||||
compositeQuery: {
|
||||
builderQueries: {
|
||||
@@ -198,11 +146,10 @@ export const exceptionAlertDefaults: AlertDef = {
|
||||
},
|
||||
annotations: defaultAnnotations,
|
||||
evalWindow: defaultEvalWindow,
|
||||
alert: '',
|
||||
};
|
||||
|
||||
export const ALERTS_VALUES_MAP: Record<AlertTypes, AlertDef> = {
|
||||
[AlertTypes.ANOMALY_BASED_ALERT]: anamolyAlertDefaults,
|
||||
[AlertTypes.ANOMALY_BASED_ALERT]: alertDefaults,
|
||||
[AlertTypes.METRICS_BASED_ALERT]: alertDefaults,
|
||||
[AlertTypes.LOGS_BASED_ALERT]: logAlertDefaults,
|
||||
[AlertTypes.TRACES_BASED_ALERT]: traceAlertDefaults,
|
||||
|
||||
@@ -13,7 +13,6 @@ import { AlertDef } from 'types/api/alerts/def';
|
||||
import { ALERT_TYPE_VS_SOURCE_MAPPING } from './config';
|
||||
import {
|
||||
alertDefaults,
|
||||
anamolyAlertDefaults,
|
||||
exceptionAlertDefaults,
|
||||
logAlertDefaults,
|
||||
traceAlertDefaults,
|
||||
@@ -25,12 +24,8 @@ function CreateRules(): JSX.Element {
|
||||
|
||||
const location = useLocation();
|
||||
const queryParams = new URLSearchParams(location.search);
|
||||
const alertTypeFromURL = queryParams.get(QueryParams.ruleType);
|
||||
const version = queryParams.get('version');
|
||||
const alertTypeFromParams =
|
||||
alertTypeFromURL === AlertDetectionTypes.ANOMALY_DETECTION_ALERT
|
||||
? AlertTypes.ANOMALY_BASED_ALERT
|
||||
: queryParams.get(QueryParams.alertType);
|
||||
const alertTypeFromParams = queryParams.get(QueryParams.alertType);
|
||||
|
||||
const compositeQuery = useGetCompositeQueryParam();
|
||||
function getAlertTypeFromDataSource(): AlertTypes | null {
|
||||
@@ -63,7 +58,7 @@ function CreateRules(): JSX.Element {
|
||||
break;
|
||||
case AlertTypes.ANOMALY_BASED_ALERT:
|
||||
setInitValues({
|
||||
...anamolyAlertDefaults,
|
||||
...alertDefaults,
|
||||
version: version || ENTITY_VERSION_V4,
|
||||
ruleType: AlertDetectionTypes.ANOMALY_DETECTION_ALERT,
|
||||
});
|
||||
@@ -83,10 +78,7 @@ function CreateRules(): JSX.Element {
|
||||
: typ,
|
||||
);
|
||||
|
||||
if (
|
||||
typ === AlertTypes.ANOMALY_BASED_ALERT ||
|
||||
alertTypeFromURL === AlertDetectionTypes.ANOMALY_DETECTION_ALERT
|
||||
) {
|
||||
if (typ === AlertTypes.ANOMALY_BASED_ALERT) {
|
||||
queryParams.set(
|
||||
QueryParams.ruleType,
|
||||
AlertDetectionTypes.ANOMALY_DETECTION_ALERT,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Color, ColorType } from '@signozhq/design-tokens';
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { showErrorNotification } from 'components/ExplorerCard/utils';
|
||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||
import { QueryParams } from 'constants/query';
|
||||
@@ -8,7 +8,7 @@ import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
import { SaveNewViewHandlerProps } from './types';
|
||||
|
||||
export const getRandomColor = (): ColorType => {
|
||||
export const getRandomColor = (): Color => {
|
||||
const colorKeys = Object.keys(Color) as (keyof typeof Color)[];
|
||||
const randomKey = colorKeys[Math.floor(Math.random() * colorKeys.length)];
|
||||
return Color[randomKey];
|
||||
|
||||
@@ -160,15 +160,6 @@ function RuleOptions({
|
||||
});
|
||||
};
|
||||
|
||||
const onChangeDeviation = (value: number): void => {
|
||||
const target = value || alertDef.condition.target || 3;
|
||||
|
||||
setAlertDef({
|
||||
...alertDef,
|
||||
condition: { ...alertDef.condition, target: Number(target) },
|
||||
});
|
||||
};
|
||||
|
||||
const renderEvalWindows = (): JSX.Element => (
|
||||
<InlineSelect
|
||||
getPopupContainer={popupContainer}
|
||||
@@ -212,28 +203,6 @@ function RuleOptions({
|
||||
</InlineSelect>
|
||||
);
|
||||
|
||||
const renderDeviationOpts = (): JSX.Element => (
|
||||
<InlineSelect
|
||||
getPopupContainer={popupContainer}
|
||||
defaultValue={3}
|
||||
style={{ minWidth: '120px' }}
|
||||
value={alertDef.condition.target}
|
||||
onChange={(value: number | unknown): void => {
|
||||
if (typeof value === 'number') {
|
||||
onChangeDeviation(value);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Select.Option value={1}>1</Select.Option>
|
||||
<Select.Option value={2}>2</Select.Option>
|
||||
<Select.Option value={3}>3</Select.Option>
|
||||
<Select.Option value={4}>4</Select.Option>
|
||||
<Select.Option value={5}>5</Select.Option>
|
||||
<Select.Option value={6}>6</Select.Option>
|
||||
<Select.Option value={7}>7</Select.Option>
|
||||
</InlineSelect>
|
||||
);
|
||||
|
||||
const renderSeasonality = (): JSX.Element => (
|
||||
<InlineSelect
|
||||
getPopupContainer={popupContainer}
|
||||
@@ -268,6 +237,39 @@ function RuleOptions({
|
||||
</Form.Item>
|
||||
);
|
||||
|
||||
const renderAnomalyRuleOpts = (
|
||||
onChange: InputNumberProps['onChange'],
|
||||
): JSX.Element => (
|
||||
<Form.Item>
|
||||
<Typography.Text className="rule-definition">
|
||||
{t('text_condition1_anomaly')}
|
||||
<InlineSelect
|
||||
getPopupContainer={popupContainer}
|
||||
allowClear
|
||||
showSearch
|
||||
options={queryOptions}
|
||||
placeholder={t('selected_query_placeholder')}
|
||||
value={alertDef.condition.selectedQueryName}
|
||||
onChange={onChangeSelectedQueryName}
|
||||
/>
|
||||
{t('text_condition3')} {renderEvalWindows()}
|
||||
<Typography.Text>is</Typography.Text>
|
||||
<InputNumber
|
||||
value={alertDef?.condition?.target}
|
||||
onChange={onChange}
|
||||
type="number"
|
||||
onWheel={(e): void => e.currentTarget.blur()}
|
||||
/>
|
||||
<Typography.Text>deviations</Typography.Text>
|
||||
{renderCompareOps()}
|
||||
<Typography.Text>the predicted data</Typography.Text>
|
||||
{renderMatchOpts()}
|
||||
using the {renderAlgorithms()} algorithm with {renderSeasonality()}{' '}
|
||||
seasonality
|
||||
</Typography.Text>
|
||||
</Form.Item>
|
||||
);
|
||||
|
||||
const renderPromRuleOptions = (): JSX.Element => (
|
||||
<Form.Item>
|
||||
<Typography.Text>
|
||||
@@ -318,32 +320,6 @@ function RuleOptions({
|
||||
});
|
||||
};
|
||||
|
||||
const renderAnomalyRuleOpts = (): JSX.Element => (
|
||||
<Form.Item>
|
||||
<Typography.Text className="rule-definition">
|
||||
{t('text_condition1_anomaly')}
|
||||
<InlineSelect
|
||||
getPopupContainer={popupContainer}
|
||||
allowClear
|
||||
showSearch
|
||||
options={queryOptions}
|
||||
placeholder={t('selected_query_placeholder')}
|
||||
value={alertDef.condition.selectedQueryName}
|
||||
onChange={onChangeSelectedQueryName}
|
||||
/>
|
||||
{t('text_condition3')} {renderEvalWindows()}
|
||||
<Typography.Text>is</Typography.Text>
|
||||
{renderDeviationOpts()}
|
||||
<Typography.Text>deviations</Typography.Text>
|
||||
{renderCompareOps()}
|
||||
<Typography.Text>the predicted data</Typography.Text>
|
||||
{renderMatchOpts()}
|
||||
using the {renderAlgorithms()} algorithm with {renderSeasonality()}{' '}
|
||||
seasonality
|
||||
</Typography.Text>
|
||||
</Form.Item>
|
||||
);
|
||||
|
||||
const renderFrequency = (): JSX.Element => (
|
||||
<InlineSelect
|
||||
getPopupContainer={popupContainer}
|
||||
@@ -378,7 +354,7 @@ function RuleOptions({
|
||||
{queryCategory === EQueryType.PROM && renderPromRuleOptions()}
|
||||
{queryCategory !== EQueryType.PROM &&
|
||||
ruleType === AlertDetectionTypes.ANOMALY_DETECTION_ALERT && (
|
||||
<>{renderAnomalyRuleOpts()}</>
|
||||
<>{renderAnomalyRuleOpts(onChange)}</>
|
||||
)}
|
||||
|
||||
{queryCategory !== EQueryType.PROM &&
|
||||
@@ -386,31 +362,32 @@ function RuleOptions({
|
||||
renderThresholdRuleOpts()}
|
||||
|
||||
<Space direction="vertical" size="large">
|
||||
{ruleType !== AlertDetectionTypes.ANOMALY_DETECTION_ALERT && (
|
||||
<Space direction="horizontal" align="center">
|
||||
<Form.Item noStyle name={['condition', 'target']}>
|
||||
<InputNumber
|
||||
addonBefore={t('field_threshold')}
|
||||
value={alertDef?.condition?.target}
|
||||
onChange={onChange}
|
||||
type="number"
|
||||
onWheel={(e): void => e.currentTarget.blur()}
|
||||
/>
|
||||
</Form.Item>
|
||||
{queryCategory !== EQueryType.PROM &&
|
||||
ruleType !== AlertDetectionTypes.ANOMALY_DETECTION_ALERT && (
|
||||
<Space direction="horizontal" align="center">
|
||||
<Form.Item noStyle name={['condition', 'target']}>
|
||||
<InputNumber
|
||||
addonBefore={t('field_threshold')}
|
||||
value={alertDef?.condition?.target}
|
||||
onChange={onChange}
|
||||
type="number"
|
||||
onWheel={(e): void => e.currentTarget.blur()}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item noStyle>
|
||||
<Select
|
||||
getPopupContainer={popupContainer}
|
||||
allowClear
|
||||
showSearch
|
||||
options={categorySelectOptions}
|
||||
placeholder={t('field_unit')}
|
||||
value={alertDef.condition.targetUnit}
|
||||
onChange={onChangeAlertUnit}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Space>
|
||||
)}
|
||||
<Form.Item noStyle>
|
||||
<Select
|
||||
getPopupContainer={popupContainer}
|
||||
allowClear
|
||||
showSearch
|
||||
options={categorySelectOptions}
|
||||
placeholder={t('field_unit')}
|
||||
value={alertDef.condition.targetUnit}
|
||||
onChange={onChangeAlertUnit}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Space>
|
||||
)}
|
||||
|
||||
<Collapse>
|
||||
<Collapse.Panel header={t('More options')} key="1">
|
||||
|
||||
@@ -39,7 +39,6 @@ import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useQueryClient } from 'react-query';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { AlertTypes } from 'types/api/alerts/alertTypes';
|
||||
import {
|
||||
@@ -53,7 +52,6 @@ import {
|
||||
QueryFunctionProps,
|
||||
} from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
|
||||
import BasicInfo from './BasicInfo';
|
||||
@@ -74,19 +72,6 @@ export enum AlertDetectionTypes {
|
||||
ANOMALY_DETECTION_ALERT = 'anomaly_rule',
|
||||
}
|
||||
|
||||
const ALERT_SETUP_GUIDE_URLS: Record<AlertTypes, string> = {
|
||||
[AlertTypes.METRICS_BASED_ALERT]:
|
||||
'https://signoz.io/docs/alerts-management/metrics-based-alerts/?utm_source=product&utm_medium=alert-creation-page',
|
||||
[AlertTypes.LOGS_BASED_ALERT]:
|
||||
'https://signoz.io/docs/alerts-management/log-based-alerts/?utm_source=product&utm_medium=alert-creation-page',
|
||||
[AlertTypes.TRACES_BASED_ALERT]:
|
||||
'https://signoz.io/docs/alerts-management/trace-based-alerts/?utm_source=product&utm_medium=alert-creation-page',
|
||||
[AlertTypes.EXCEPTIONS_BASED_ALERT]:
|
||||
'https://signoz.io/docs/alerts-management/exceptions-based-alerts/?utm_source=product&utm_medium=alert-creation-page',
|
||||
[AlertTypes.ANOMALY_BASED_ALERT]:
|
||||
'https://signoz.io/docs/alerts-management/anomaly-based-alerts/?utm_source=product&utm_medium=alert-creation-page',
|
||||
};
|
||||
|
||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||
function FormAlertRules({
|
||||
alertType,
|
||||
@@ -103,13 +88,6 @@ function FormAlertRules({
|
||||
>((state) => state.globalTime);
|
||||
|
||||
const urlQuery = useUrlQuery();
|
||||
const location = useLocation();
|
||||
const queryParams = new URLSearchParams(location.search);
|
||||
|
||||
const dataSource = useMemo(
|
||||
() => urlQuery.get(QueryParams.alertType) as DataSource,
|
||||
[urlQuery],
|
||||
);
|
||||
|
||||
// In case of alert the panel types should always be "Graph" only
|
||||
const panelType = PANEL_TYPES.TIME_SERIES;
|
||||
@@ -120,12 +98,13 @@ function FormAlertRules({
|
||||
handleSetQueryData,
|
||||
handleRunQuery,
|
||||
handleSetConfig,
|
||||
initialDataSource,
|
||||
redirectWithQueryBuilderData,
|
||||
} = useQueryBuilder();
|
||||
|
||||
useEffect(() => {
|
||||
handleSetConfig(panelType || PANEL_TYPES.TIME_SERIES, dataSource);
|
||||
}, [handleSetConfig, dataSource, panelType]);
|
||||
handleSetConfig(panelType || PANEL_TYPES.TIME_SERIES, initialDataSource);
|
||||
}, [handleSetConfig, initialDataSource, panelType]);
|
||||
|
||||
// use query client
|
||||
const ruleCache = useQueryClient();
|
||||
@@ -141,7 +120,9 @@ function FormAlertRules({
|
||||
|
||||
const alertTypeFromURL = urlQuery.get(QueryParams.ruleType);
|
||||
|
||||
const [detectionMethod, setDetectionMethod] = useState<string | null>(null);
|
||||
const [detectionMethod, setDetectionMethod] = useState<string>(
|
||||
AlertDetectionTypes.THRESHOLD_ALERT,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isEqual(currentQuery.unit, yAxisUnit)) {
|
||||
@@ -173,24 +154,11 @@ function FormAlertRules({
|
||||
|
||||
useShareBuilderUrl(sq);
|
||||
|
||||
const handleDetectionMethodChange = (value: string): void => {
|
||||
setAlertDef((def) => ({
|
||||
...def,
|
||||
ruleType: value,
|
||||
}));
|
||||
|
||||
logEvent(`Alert: Detection method changed`, {
|
||||
detectionMethod: value,
|
||||
});
|
||||
|
||||
setDetectionMethod(value);
|
||||
};
|
||||
|
||||
const updateFunctions = (data: IBuilderQuery): QueryFunctionProps[] => {
|
||||
const anomalyFunction = {
|
||||
name: 'anomaly',
|
||||
args: [],
|
||||
namedArgs: { z_score_threshold: alertDef.condition.target || 3 },
|
||||
namedArgs: { z_score_threshold: 9 },
|
||||
};
|
||||
const functions = data.functions || [];
|
||||
|
||||
@@ -198,19 +166,6 @@ function FormAlertRules({
|
||||
// Add anomaly if not already present
|
||||
if (!functions.some((func) => func.name === 'anomaly')) {
|
||||
functions.push(anomalyFunction);
|
||||
} else {
|
||||
const anomalyFuncIndex = functions.findIndex(
|
||||
(func) => func.name === 'anomaly',
|
||||
);
|
||||
|
||||
if (anomalyFuncIndex !== -1) {
|
||||
const anomalyFunc = {
|
||||
...functions[anomalyFuncIndex],
|
||||
namedArgs: { z_score_threshold: alertDef.condition.target || 3 },
|
||||
};
|
||||
functions.splice(anomalyFuncIndex, 1);
|
||||
functions.push(anomalyFunc);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Remove anomaly if present
|
||||
@@ -223,20 +178,6 @@ function FormAlertRules({
|
||||
return functions;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const ruleType =
|
||||
detectionMethod === AlertDetectionTypes.ANOMALY_DETECTION_ALERT
|
||||
? AlertDetectionTypes.ANOMALY_DETECTION_ALERT
|
||||
: AlertDetectionTypes.THRESHOLD_ALERT;
|
||||
|
||||
queryParams.set(QueryParams.ruleType, ruleType);
|
||||
|
||||
const generatedUrl = `${location.pathname}?${queryParams.toString()}`;
|
||||
|
||||
history.replace(generatedUrl);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [detectionMethod]);
|
||||
|
||||
const updateFunctionsBasedOnAlertType = (): void => {
|
||||
for (let index = 0; index < currentQuery.builder.queryData.length; index++) {
|
||||
const queryData = currentQuery.builder.queryData[index];
|
||||
@@ -250,11 +191,7 @@ function FormAlertRules({
|
||||
useEffect(() => {
|
||||
updateFunctionsBasedOnAlertType();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [
|
||||
detectionMethod,
|
||||
alertDef.condition.target,
|
||||
currentQuery.builder.queryData.length,
|
||||
]);
|
||||
}, [detectionMethod, alertDef, currentQuery.builder.queryData.length]);
|
||||
|
||||
useEffect(() => {
|
||||
const broadcastToSpecificChannels =
|
||||
@@ -278,8 +215,7 @@ function FormAlertRules({
|
||||
});
|
||||
|
||||
setDetectionMethod(ruleType);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [initialValue, isNewRule]);
|
||||
}, [initialValue, isNewRule, alertTypeFromURL]);
|
||||
|
||||
useEffect(() => {
|
||||
// Set selectedQueryName based on the length of queryOptions
|
||||
@@ -399,11 +335,7 @@ function FormAlertRules({
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
alertDef.ruleType !== AlertDetectionTypes.ANOMALY_DETECTION_ALERT &&
|
||||
alertDef.condition?.target !== 0 &&
|
||||
!alertDef.condition?.target
|
||||
) {
|
||||
if (alertDef.condition?.target !== 0 && !alertDef.condition?.target) {
|
||||
notifications.error({
|
||||
message: 'Error',
|
||||
description: t('target_missing'),
|
||||
@@ -561,7 +493,6 @@ function FormAlertRules({
|
||||
queryType: currentQuery.queryType,
|
||||
alertId: postableAlert?.id,
|
||||
alertName: postableAlert?.alert,
|
||||
ruleType: postableAlert?.ruleType,
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [
|
||||
@@ -646,7 +577,6 @@ function FormAlertRules({
|
||||
queryType: currentQuery.queryType,
|
||||
status: statusResponse.status,
|
||||
statusMessage: statusResponse.message,
|
||||
ruleType: postableAlert.ruleType,
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [t, isFormValid, memoizedPreparePostData, notifications]);
|
||||
@@ -720,29 +650,6 @@ function FormAlertRules({
|
||||
|
||||
const isRuleCreated = !ruleId || ruleId === 0;
|
||||
|
||||
function handleRedirection(option: AlertTypes): void {
|
||||
let url;
|
||||
if (
|
||||
option === AlertTypes.METRICS_BASED_ALERT &&
|
||||
alertTypeFromURL === AlertDetectionTypes.ANOMALY_DETECTION_ALERT
|
||||
) {
|
||||
url = ALERT_SETUP_GUIDE_URLS[AlertTypes.ANOMALY_BASED_ALERT];
|
||||
} else {
|
||||
url = ALERT_SETUP_GUIDE_URLS[option];
|
||||
}
|
||||
|
||||
if (url) {
|
||||
logEvent('Alert: Check example alert clicked', {
|
||||
dataSource: ALERTS_DATA_SOURCE_MAP[alertDef?.alertType as AlertTypes],
|
||||
isNewRule: !ruleId || ruleId === 0,
|
||||
ruleId,
|
||||
queryType: currentQuery.queryType,
|
||||
link: url,
|
||||
});
|
||||
window.open(url, '_blank');
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!isRuleCreated) {
|
||||
logEvent('Alert: Edit page visited', {
|
||||
@@ -765,6 +672,15 @@ function FormAlertRules({
|
||||
},
|
||||
];
|
||||
|
||||
const handleDetectionMethodChange = (value: any): void => {
|
||||
setAlertDef((def) => ({
|
||||
...def,
|
||||
ruleType: value,
|
||||
}));
|
||||
|
||||
setDetectionMethod(value);
|
||||
};
|
||||
|
||||
const isAnomalyDetectionEnabled =
|
||||
useFeatureFlag(FeatureKeys.ANOMALY_DETECTION)?.active || false;
|
||||
|
||||
@@ -793,11 +709,7 @@ function FormAlertRules({
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Button
|
||||
className="periscope-btn"
|
||||
onClick={(): void => handleRedirection(alertDef.alertType as AlertTypes)}
|
||||
icon={<ExternalLink size={14} />}
|
||||
>
|
||||
<Button className="periscope-btn" icon={<ExternalLink size={14} />}>
|
||||
Alert Setup Guide
|
||||
</Button>
|
||||
</div>
|
||||
@@ -833,7 +745,7 @@ function FormAlertRules({
|
||||
<Tabs2
|
||||
key={detectionMethod}
|
||||
tabs={tabs}
|
||||
initialSelectedTab={detectionMethod || ''}
|
||||
initialSelectedTab={detectionMethod}
|
||||
onSelectTab={handleDetectionMethodChange}
|
||||
/>
|
||||
|
||||
|
||||
@@ -138,9 +138,6 @@ function LabelSelect({
|
||||
if (e.key === 'Enter' || e.code === 'Enter' || e.key === ':') {
|
||||
send('NEXT');
|
||||
}
|
||||
if (state.value === 'Idle') {
|
||||
send('NEXT');
|
||||
}
|
||||
}}
|
||||
bordered={false}
|
||||
value={currentVal as never}
|
||||
|
||||
@@ -18,7 +18,6 @@ import { QueryParams } from 'constants/query';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import useCreateAlerts from 'hooks/queryBuilder/useCreateAlerts';
|
||||
import useComponentPermission from 'hooks/useComponentPermission';
|
||||
import useUrlQuery from 'hooks/useUrlQuery';
|
||||
import history from 'lib/history';
|
||||
import { RowData } from 'lib/query/createTableColumnsFromQuery';
|
||||
import { isEmpty } from 'lodash-es';
|
||||
@@ -73,18 +72,16 @@ function WidgetHeader({
|
||||
tableProcessedDataRef,
|
||||
setSearchTerm,
|
||||
}: IWidgetHeaderProps): JSX.Element | null {
|
||||
const urlQuery = useUrlQuery();
|
||||
const onEditHandler = useCallback((): void => {
|
||||
const widgetId = widget.id;
|
||||
urlQuery.set(QueryParams.widgetId, widgetId);
|
||||
urlQuery.set(QueryParams.graphType, widget.panelTypes);
|
||||
urlQuery.set(
|
||||
QueryParams.compositeQuery,
|
||||
encodeURIComponent(JSON.stringify(widget.query)),
|
||||
history.push(
|
||||
`${window.location.pathname}/new?widgetId=${widgetId}&graphType=${
|
||||
widget.panelTypes
|
||||
}&${QueryParams.compositeQuery}=${encodeURIComponent(
|
||||
JSON.stringify(widget.query),
|
||||
)}`,
|
||||
);
|
||||
const generatedUrl = `${window.location.pathname}/new?${urlQuery}`;
|
||||
history.push(generatedUrl);
|
||||
}, [urlQuery, widget.id, widget.panelTypes, widget.query]);
|
||||
}, [widget.id, widget.panelTypes, widget.query]);
|
||||
|
||||
const onCreateAlertsHandler = useCreateAlerts(widget, 'dashboardView');
|
||||
|
||||
|
||||
@@ -97,19 +97,13 @@ function GridTableComponent({
|
||||
|
||||
const newColumnData = columns.map((e) => ({
|
||||
...e,
|
||||
render: (text: string, ...rest: any): ReactNode => {
|
||||
let textForThreshold = text;
|
||||
if (columnUnits && columnUnits?.[e.title as string]) {
|
||||
textForThreshold = rest[0][`${e.title}_without_unit`];
|
||||
}
|
||||
const isNumber = !Number.isNaN(Number(textForThreshold));
|
||||
|
||||
render: (text: string): ReactNode => {
|
||||
const isNumber = !Number.isNaN(Number(text));
|
||||
if (thresholds && isNumber) {
|
||||
const { hasMultipleMatches, threshold } = findMatchingThreshold(
|
||||
thresholds,
|
||||
e.title as string,
|
||||
Number(textForThreshold),
|
||||
columnUnits?.[e.title as string],
|
||||
Number(text),
|
||||
);
|
||||
|
||||
const idx = thresholds.findIndex(
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
/* eslint-disable sonarjs/cognitive-complexity */
|
||||
import { ColumnsType, ColumnType } from 'antd/es/table';
|
||||
import { convertUnit } from 'container/NewWidget/RightContainer/dataFormatCategories';
|
||||
import { ThresholdProps } from 'container/NewWidget/RightContainer/Threshold/types';
|
||||
import { QUERY_TABLE_CONFIG } from 'container/QueryTable/config';
|
||||
import { QueryTableProps } from 'container/QueryTable/QueryTable.intefaces';
|
||||
@@ -31,39 +30,10 @@ function evaluateCondition(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluates whether a given value meets a specified threshold condition.
|
||||
* It first converts the value to the appropriate unit if a threshold unit is provided,
|
||||
* and then checks the condition using the specified operator.
|
||||
*
|
||||
* @param value - The value to be evaluated.
|
||||
* @param thresholdValue - The threshold value to compare against.
|
||||
* @param thresholdOperator - The operator used for comparison (e.g., '>', '<', '==').
|
||||
* @param thresholdUnit - The unit to which the value should be converted.
|
||||
* @param columnUnit - The current unit of the value.
|
||||
* @returns A boolean indicating whether the value meets the threshold condition.
|
||||
*/
|
||||
function evaluateThresholdWithConvertedValue(
|
||||
value: number,
|
||||
thresholdValue: number,
|
||||
thresholdOperator?: string,
|
||||
thresholdUnit?: string,
|
||||
columnUnit?: string,
|
||||
): boolean {
|
||||
const convertedValue = convertUnit(value, columnUnit, thresholdUnit);
|
||||
|
||||
if (convertedValue) {
|
||||
return evaluateCondition(thresholdOperator, convertedValue, thresholdValue);
|
||||
}
|
||||
|
||||
return evaluateCondition(thresholdOperator, value, thresholdValue);
|
||||
}
|
||||
|
||||
export function findMatchingThreshold(
|
||||
thresholds: ThresholdProps[],
|
||||
label: string,
|
||||
value: number,
|
||||
columnUnit?: string,
|
||||
): {
|
||||
threshold: ThresholdProps;
|
||||
hasMultipleMatches: boolean;
|
||||
@@ -75,12 +45,10 @@ export function findMatchingThreshold(
|
||||
if (
|
||||
threshold.thresholdValue !== undefined &&
|
||||
threshold.thresholdTableOptions === label &&
|
||||
evaluateThresholdWithConvertedValue(
|
||||
value,
|
||||
threshold?.thresholdValue,
|
||||
evaluateCondition(
|
||||
threshold.thresholdOperator,
|
||||
threshold.thresholdUnit,
|
||||
columnUnit,
|
||||
value,
|
||||
threshold.thresholdValue,
|
||||
)
|
||||
) {
|
||||
matchingThresholds.push(threshold);
|
||||
|
||||
@@ -5,6 +5,7 @@ import type { ColumnsType } from 'antd/es/table/interface';
|
||||
import saveAlertApi from 'api/alerts/save';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import DropDown from 'components/DropDown/DropDown';
|
||||
import { listAlertMessage } from 'components/LaunchChatSupport/util';
|
||||
import {
|
||||
DynamicColumnsKey,
|
||||
TableDataSource,
|
||||
@@ -396,6 +397,15 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
|
||||
dynamicColumns={dynamicColumns}
|
||||
onChange={handleChange}
|
||||
pagination={paginationConfig}
|
||||
facingIssueBtn={{
|
||||
attributes: {
|
||||
screen: 'Alert list page',
|
||||
},
|
||||
eventName: 'Alert: Facing Issues in alert',
|
||||
buttonText: 'Facing issues with alerts?',
|
||||
message: listAlertMessage,
|
||||
onHoverText: 'Click here to get help with alerts',
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -5,17 +5,6 @@
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
|
||||
// overridding the request integration style to fix the spacing for dashboard list
|
||||
.request-entity-container {
|
||||
margin-bottom: 16px !important;
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
|
||||
.integrations-content {
|
||||
max-width: 100% !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.dashboards-list-view-content {
|
||||
width: calc(100% - 30px);
|
||||
max-width: 836px;
|
||||
|
||||
@@ -25,6 +25,8 @@ import logEvent from 'api/common/logEvent';
|
||||
import createDashboard from 'api/dashboard/create';
|
||||
import { AxiosError } from 'axios';
|
||||
import cx from 'classnames';
|
||||
import LaunchChatSupport from 'components/LaunchChatSupport/LaunchChatSupport';
|
||||
import { dashboardListMessage } from 'components/LaunchChatSupport/util';
|
||||
import { ENTITY_VERSION_V4 } from 'constants/app';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { Base64Icons } from 'container/NewDashboard/DashboardSettings/General/utils';
|
||||
@@ -77,7 +79,6 @@ import { isCloudUser } from 'utils/app';
|
||||
|
||||
import DashboardTemplatesModal from './DashboardTemplates/DashboardTemplatesModal';
|
||||
import ImportJSON from './ImportJSON';
|
||||
import { RequestDashboardBtn } from './RequestDashboardBtn';
|
||||
import { DeleteButton } from './TableComponents/DeleteButton';
|
||||
import {
|
||||
DashboardDynamicColumns,
|
||||
@@ -692,14 +693,17 @@ function DashboardsList(): JSX.Element {
|
||||
<Typography.Text className="subtitle">
|
||||
Create and manage dashboards for your workspace.
|
||||
</Typography.Text>
|
||||
<LaunchChatSupport
|
||||
attributes={{
|
||||
screen: 'Dashboard list page',
|
||||
}}
|
||||
eventName="Dashboard: Facing Issues in dashboard"
|
||||
message={dashboardListMessage}
|
||||
buttonText="Need help with dashboards?"
|
||||
onHoverText="Click here to get help with dashboards"
|
||||
intercomMessageDisabled
|
||||
/>
|
||||
</Flex>
|
||||
{isCloudUser() && (
|
||||
<div className="integrations-container">
|
||||
<div className="integrations-content">
|
||||
<RequestDashboardBtn />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{isDashboardListLoading ||
|
||||
|
||||
@@ -82,12 +82,6 @@ function ImportJSON({
|
||||
|
||||
const dashboardData = JSON.parse(editorValue) as DashboardData;
|
||||
|
||||
// Add validation for uuid
|
||||
if (dashboardData.uuid !== undefined && dashboardData.uuid.trim() === '') {
|
||||
// silently remove uuid if it is empty
|
||||
delete dashboardData.uuid;
|
||||
}
|
||||
|
||||
if (dashboardData?.layout) {
|
||||
dashboardData.layout = getUpdatedLayout(dashboardData.layout);
|
||||
} else {
|
||||
@@ -129,14 +123,11 @@ function ImportJSON({
|
||||
});
|
||||
}
|
||||
setDashboardCreating(false);
|
||||
} catch (error) {
|
||||
} catch {
|
||||
setDashboardCreating(false);
|
||||
setIsFeatureAlert(false);
|
||||
|
||||
setIsCreateDashboardError(true);
|
||||
notifications.error({
|
||||
message: error instanceof Error ? error.message : t('error_loading_json'),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,95 +0,0 @@
|
||||
import '../../pages/Integrations/Integrations.styles.scss';
|
||||
|
||||
import { LoadingOutlined } from '@ant-design/icons';
|
||||
import { Button, Input, Space, Typography } from 'antd';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import { Check } from 'lucide-react';
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export function RequestDashboardBtn(): JSX.Element {
|
||||
const [
|
||||
isSubmittingRequestForDashboard,
|
||||
setIsSubmittingRequestForDashboard,
|
||||
] = useState(false);
|
||||
|
||||
const [requestedDashboardName, setRequestedDashboardName] = useState('');
|
||||
|
||||
const { notifications } = useNotifications();
|
||||
const { t } = useTranslation(['common']);
|
||||
|
||||
const handleRequestDashboardSubmit = async (): Promise<void> => {
|
||||
try {
|
||||
setIsSubmittingRequestForDashboard(true);
|
||||
const response = await logEvent('Dashboard Requested', {
|
||||
screen: 'Dashboard list page',
|
||||
dashboard: requestedDashboardName,
|
||||
});
|
||||
|
||||
if (response.statusCode === 200) {
|
||||
notifications.success({
|
||||
message: 'Dashboard Request Submitted',
|
||||
});
|
||||
|
||||
setIsSubmittingRequestForDashboard(false);
|
||||
} else {
|
||||
notifications.error({
|
||||
message:
|
||||
response.error ||
|
||||
t('something_went_wrong', {
|
||||
ns: 'common',
|
||||
}),
|
||||
});
|
||||
|
||||
setIsSubmittingRequestForDashboard(false);
|
||||
}
|
||||
} catch (error) {
|
||||
notifications.error({
|
||||
message: t('something_went_wrong', {
|
||||
ns: 'common',
|
||||
}),
|
||||
});
|
||||
|
||||
setIsSubmittingRequestForDashboard(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="request-entity-container">
|
||||
<Typography.Text>
|
||||
Can't find the dashboard you need? Request a new Dashboard.
|
||||
</Typography.Text>
|
||||
|
||||
<div className="form-section">
|
||||
<Space.Compact style={{ width: '100%' }}>
|
||||
<Input
|
||||
placeholder="Enter dashboard name..."
|
||||
style={{ width: 300, marginBottom: 0 }}
|
||||
value={requestedDashboardName}
|
||||
onChange={(e): void => setRequestedDashboardName(e.target.value)}
|
||||
/>
|
||||
<Button
|
||||
className="periscope-btn primary"
|
||||
icon={
|
||||
isSubmittingRequestForDashboard ? (
|
||||
<LoadingOutlined />
|
||||
) : (
|
||||
<Check size={12} />
|
||||
)
|
||||
}
|
||||
type="primary"
|
||||
onClick={handleRequestDashboardSubmit}
|
||||
disabled={
|
||||
isSubmittingRequestForDashboard ||
|
||||
!requestedDashboardName ||
|
||||
requestedDashboardName?.trim().length === 0
|
||||
}
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
</Space.Compact>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -157,11 +157,6 @@ export const getFieldAttributes = (field: string): IFieldAttributes => {
|
||||
const stringWithoutPrefix = field.slice('resources_'.length);
|
||||
const parts = splitOnce(stringWithoutPrefix, '.');
|
||||
[dataType, newField] = parts;
|
||||
} else if (field.startsWith('scope_string')) {
|
||||
logType = MetricsType.Scope;
|
||||
const stringWithoutPrefix = field.slice('scope_'.length);
|
||||
const parts = splitOnce(stringWithoutPrefix, '.');
|
||||
[dataType, newField] = parts;
|
||||
}
|
||||
|
||||
return { dataType, newField, logType };
|
||||
@@ -192,7 +187,6 @@ export const aggregateAttributesResourcesToString = (logData: ILog): string => {
|
||||
traceId: logData.traceId,
|
||||
attributes: {},
|
||||
resources: {},
|
||||
scope: {},
|
||||
severity_text: logData.severity_text,
|
||||
severity_number: logData.severity_number,
|
||||
};
|
||||
@@ -204,9 +198,6 @@ export const aggregateAttributesResourcesToString = (logData: ILog): string => {
|
||||
} else if (key.startsWith('resources_')) {
|
||||
outputJson.resources = outputJson.resources || {};
|
||||
Object.assign(outputJson.resources, logData[key as keyof ILog]);
|
||||
} else if (key.startsWith('scope_string')) {
|
||||
outputJson.scope = outputJson.scope || {};
|
||||
Object.assign(outputJson.scope, logData[key as keyof ILog]);
|
||||
} else {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
|
||||
@@ -53,7 +53,6 @@ export enum KeyOperationTableHeader {
|
||||
export enum MetricsType {
|
||||
Tag = 'tag',
|
||||
Resource = 'resource',
|
||||
Scope = 'scope',
|
||||
}
|
||||
|
||||
export enum WidgetKeys {
|
||||
|
||||
@@ -12,6 +12,8 @@ import {
|
||||
Typography,
|
||||
} from 'antd';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import LaunchChatSupport from 'components/LaunchChatSupport/LaunchChatSupport';
|
||||
import { dashboardHelpMessage } from 'components/LaunchChatSupport/util';
|
||||
import { SOMETHING_WENT_WRONG } from 'constants/api';
|
||||
import { QueryParams } from 'constants/query';
|
||||
import { PANEL_GROUP_TYPES, PANEL_TYPES } from 'constants/queryBuilder';
|
||||
@@ -45,11 +47,7 @@ import { useTranslation } from 'react-i18next';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useCopyToClipboard } from 'react-use';
|
||||
import { AppState } from 'store/reducers';
|
||||
import {
|
||||
Dashboard,
|
||||
DashboardData,
|
||||
IDashboardVariable,
|
||||
} from 'types/api/dashboard/getAll';
|
||||
import { Dashboard, DashboardData } from 'types/api/dashboard/getAll';
|
||||
import AppReducer from 'types/reducer/app';
|
||||
import { ROLES, USER_ROLES } from 'types/roles';
|
||||
import { ComponentTypes } from 'utils/permission';
|
||||
@@ -65,30 +63,6 @@ interface DashboardDescriptionProps {
|
||||
handle: FullScreenHandle;
|
||||
}
|
||||
|
||||
function sanitizeDashboardData(
|
||||
selectedData: DashboardData,
|
||||
): Omit<DashboardData, 'uuid'> {
|
||||
if (!selectedData?.variables) {
|
||||
const { uuid, ...rest } = selectedData;
|
||||
return rest;
|
||||
}
|
||||
|
||||
const updatedVariables = Object.entries(selectedData.variables).reduce(
|
||||
(acc, [key, value]) => {
|
||||
const { selectedValue, ...rest } = value;
|
||||
acc[key] = rest;
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, IDashboardVariable>,
|
||||
);
|
||||
|
||||
const { uuid, ...restData } = selectedData;
|
||||
return {
|
||||
...restData,
|
||||
variables: updatedVariables,
|
||||
};
|
||||
}
|
||||
|
||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||
function DashboardDescription(props: DashboardDescriptionProps): JSX.Element {
|
||||
const { handle } = props;
|
||||
@@ -354,6 +328,18 @@ function DashboardDescription(props: DashboardDescriptionProps): JSX.Element {
|
||||
{isDashboardLocked && <LockKeyhole size={14} />}
|
||||
</div>
|
||||
<div className="right-section">
|
||||
<LaunchChatSupport
|
||||
attributes={{
|
||||
uuid: selectedDashboard?.uuid,
|
||||
title: updatedTitle,
|
||||
screen: 'Dashboard Details',
|
||||
}}
|
||||
eventName="Dashboard: Facing Issues in dashboard"
|
||||
message={dashboardHelpMessage(selectedDashboard?.data, selectedDashboard)}
|
||||
buttonText="Need help with this dashboard?"
|
||||
onHoverText="Click here to get help with dashboard"
|
||||
intercomMessageDisabled
|
||||
/>
|
||||
<DateTimeSelectionV2 showAutoRefresh hideShareModal />
|
||||
<Popover
|
||||
open={isDashboardSettingsOpen}
|
||||
@@ -421,10 +407,7 @@ function DashboardDescription(props: DashboardDescriptionProps): JSX.Element {
|
||||
type="text"
|
||||
icon={<FileJson size={14} />}
|
||||
onClick={(): void => {
|
||||
downloadObjectAsJson(
|
||||
sanitizeDashboardData(selectedData),
|
||||
selectedData.title,
|
||||
);
|
||||
downloadObjectAsJson(selectedData, selectedData.title);
|
||||
setIsDashbordSettingsOpen(false);
|
||||
}}
|
||||
>
|
||||
@@ -434,9 +417,7 @@ function DashboardDescription(props: DashboardDescriptionProps): JSX.Element {
|
||||
type="text"
|
||||
icon={<ClipboardCopy size={14} />}
|
||||
onClick={(): void => {
|
||||
setCopy(
|
||||
JSON.stringify(sanitizeDashboardData(selectedData), null, 2),
|
||||
);
|
||||
setCopy(JSON.stringify(selectedData, null, 2));
|
||||
setIsDashbordSettingsOpen(false);
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Color } from '@signozhq/design-tokens';
|
||||
import { Button, Tabs, Typography } from 'antd';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import PromQLIcon from 'assets/Dashboard/PromQl';
|
||||
import LaunchChatSupport from 'components/LaunchChatSupport/LaunchChatSupport';
|
||||
import TextToolTip from 'components/TextToolTip';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { QBShortcuts } from 'constants/shortcuts/QBShortcuts';
|
||||
@@ -234,6 +235,21 @@ function QuerySection({
|
||||
onChange={handleQueryCategoryChange}
|
||||
tabBarExtraContent={
|
||||
<span style={{ display: 'flex', gap: '1rem', alignItems: 'center' }}>
|
||||
<LaunchChatSupport
|
||||
attributes={{
|
||||
uuid: selectedDashboard?.uuid,
|
||||
title: selectedDashboard?.data.title,
|
||||
screen: 'Dashboard widget',
|
||||
panelType: selectedGraph,
|
||||
widgetId: query.id,
|
||||
queryType: currentQuery.queryType,
|
||||
}}
|
||||
eventName="Dashboard: Facing Issues in dashboard"
|
||||
buttonText="Need help with this chart?"
|
||||
// message={chartHelpMessage(selectedDashboard, graphType)}
|
||||
onHoverText="Click here to get help with this dashboard widget"
|
||||
intercomMessageDisabled
|
||||
/>
|
||||
<TextToolTip
|
||||
text="This will temporarily save the current query and graph state. This will persist across tab change"
|
||||
url="https://signoz.io/docs/userguide/query-builder?utm_source=product&utm_medium=query-builder"
|
||||
|
||||
@@ -297,16 +297,6 @@
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
.invalid-unit {
|
||||
color: var(--bg-vanilla-400);
|
||||
font-family: 'Giest Mono';
|
||||
font-size: 11px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 16px;
|
||||
letter-spacing: 0.48px;
|
||||
}
|
||||
}
|
||||
|
||||
.threshold-card-container:hover {
|
||||
|
||||
@@ -3,18 +3,17 @@ import './Threshold.styles.scss';
|
||||
|
||||
import { Button, Input, InputNumber, Select, Space, Typography } from 'antd';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { unitOptions } from 'container/NewWidget/utils';
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import { Check, Pencil, Trash2, X } from 'lucide-react';
|
||||
import { useMemo, useRef, useState } from 'react';
|
||||
import { useRef, useState } from 'react';
|
||||
import { useDrag, useDrop, XYCoord } from 'react-dnd';
|
||||
|
||||
import {
|
||||
operatorOptions,
|
||||
panelTypeVsDragAndDrop,
|
||||
showAsOptions,
|
||||
unitOptions,
|
||||
} from '../constants';
|
||||
import { convertUnit } from '../dataFormatCategories';
|
||||
import ColorSelector from './ColorSelector';
|
||||
import CustomColor from './CustomColor';
|
||||
import ShowCaseValue from './ShowCaseValue';
|
||||
@@ -41,7 +40,6 @@ function Threshold({
|
||||
thresholdLabel = '',
|
||||
tableOptions,
|
||||
thresholdTableOptions = '',
|
||||
columnUnits,
|
||||
}: ThresholdProps): JSX.Element {
|
||||
const [isEditMode, setIsEditMode] = useState<boolean>(isEditEnabled);
|
||||
const [operator, setOperator] = useState<string | number>(
|
||||
@@ -194,13 +192,6 @@ function Threshold({
|
||||
|
||||
const allowDragAndDrop = panelTypeVsDragAndDrop[selectedGraph];
|
||||
|
||||
const isInvalidUnitComparison = useMemo(
|
||||
() =>
|
||||
unit !== 'none' &&
|
||||
convertUnit(value, unit, columnUnits?.[tableSelectedOption]) === null,
|
||||
[unit, value, columnUnits, tableSelectedOption],
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={allowDragAndDrop ? ref : null}
|
||||
@@ -312,7 +303,7 @@ function Threshold({
|
||||
{isEditMode ? (
|
||||
<Select
|
||||
defaultValue={unit}
|
||||
options={unitOptions(columnUnits?.[tableSelectedOption] || '')}
|
||||
options={unitOptions}
|
||||
onChange={handleUnitChange}
|
||||
showSearch
|
||||
className="unit-selection"
|
||||
@@ -348,12 +339,6 @@ function Threshold({
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
{isInvalidUnitComparison && (
|
||||
<Typography.Text className="invalid-unit">
|
||||
Threshold unit ({unit}) is not valid in comparison with the column unit (
|
||||
{columnUnits?.[tableSelectedOption] || 'none'})
|
||||
</Typography.Text>
|
||||
)}
|
||||
{isEditMode && (
|
||||
<div className="threshold-action-button">
|
||||
<Button
|
||||
|
||||
@@ -3,12 +3,14 @@
|
||||
import './ThresholdSelector.styles.scss';
|
||||
|
||||
import { Typography } from 'antd';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { ColumnsType } from 'antd/es/table';
|
||||
import { Events } from 'constants/events';
|
||||
import { RowData } from 'lib/query/createTableColumnsFromQuery';
|
||||
import { Antenna, Plus } from 'lucide-react';
|
||||
import { useCallback } from 'react';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { DndProvider } from 'react-dnd';
|
||||
import { HTML5Backend } from 'react-dnd-html5-backend';
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
import { eventEmitter } from 'utils/getEventEmitter';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
import Threshold from './Threshold';
|
||||
@@ -19,23 +21,22 @@ function ThresholdSelector({
|
||||
setThresholds,
|
||||
yAxisUnit,
|
||||
selectedGraph,
|
||||
columnUnits,
|
||||
}: ThresholdSelectorProps): JSX.Element {
|
||||
const { currentQuery } = useQueryBuilder();
|
||||
|
||||
function getAggregateColumnsNamesAndLabels(): string[] {
|
||||
if (currentQuery.queryType === EQueryType.QUERY_BUILDER) {
|
||||
const queries = currentQuery.builder.queryData.map((q) => q.queryName);
|
||||
const formulas = currentQuery.builder.queryFormulas.map((q) => q.queryName);
|
||||
return [...queries, ...formulas];
|
||||
}
|
||||
if (currentQuery.queryType === EQueryType.CLICKHOUSE) {
|
||||
return currentQuery.clickhouse_sql.map((q) => q.name);
|
||||
}
|
||||
return currentQuery.promql.map((q) => q.name);
|
||||
}
|
||||
|
||||
const aggregationQueries = getAggregateColumnsNamesAndLabels();
|
||||
const [tableOptions, setTableOptions] = useState<
|
||||
Array<{ value: string; label: string }>
|
||||
>([]);
|
||||
useEffect(() => {
|
||||
eventEmitter.on(
|
||||
Events.TABLE_COLUMNS_DATA,
|
||||
(data: { columns: ColumnsType<RowData>; dataSource: RowData[] }) => {
|
||||
const newTableOptions = data.columns.map((e) => ({
|
||||
value: e.title as string,
|
||||
label: e.title as string,
|
||||
}));
|
||||
setTableOptions([...newTableOptions]);
|
||||
},
|
||||
);
|
||||
}, []);
|
||||
|
||||
const moveThreshold = useCallback(
|
||||
(dragIndex: number, hoverIndex: number) => {
|
||||
@@ -65,7 +66,7 @@ function ThresholdSelector({
|
||||
moveThreshold,
|
||||
keyIndex: thresholds.length,
|
||||
selectedGraph,
|
||||
thresholdTableOptions: aggregationQueries[0] || '',
|
||||
thresholdTableOptions: tableOptions[0]?.value || '',
|
||||
},
|
||||
...thresholds,
|
||||
]);
|
||||
@@ -104,12 +105,8 @@ function ThresholdSelector({
|
||||
moveThreshold={moveThreshold}
|
||||
selectedGraph={selectedGraph}
|
||||
thresholdLabel={threshold.thresholdLabel}
|
||||
tableOptions={aggregationQueries.map((query) => ({
|
||||
value: query,
|
||||
label: query,
|
||||
}))}
|
||||
tableOptions={tableOptions}
|
||||
thresholdTableOptions={threshold.thresholdTableOptions}
|
||||
columnUnits={columnUnits}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { Dispatch, ReactNode, SetStateAction } from 'react';
|
||||
import { ColumnUnit } from 'types/api/dashboard/getAll';
|
||||
|
||||
export type ThresholdOperators = '>' | '<' | '>=' | '<=' | '=';
|
||||
|
||||
@@ -20,7 +19,6 @@ export type ThresholdProps = {
|
||||
moveThreshold: (dragIndex: number, hoverIndex: number) => void;
|
||||
selectedGraph: PANEL_TYPES;
|
||||
tableOptions?: Array<{ value: string; label: string }>;
|
||||
columnUnits?: ColumnUnit;
|
||||
};
|
||||
|
||||
export type ShowCaseValueProps = {
|
||||
@@ -38,5 +36,4 @@ export type ThresholdSelectorProps = {
|
||||
thresholds: ThresholdProps[];
|
||||
setThresholds: Dispatch<SetStateAction<ThresholdProps[]>>;
|
||||
selectedGraph: PANEL_TYPES;
|
||||
columnUnits: ColumnUnit;
|
||||
};
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { DefaultOptionType } from 'antd/es/select';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { categoryToSupport } from 'container/QueryBuilder/filters/BuilderUnitsFilter/config';
|
||||
|
||||
import { getCategorySelectOptionByName } from './alertFomatCategories';
|
||||
|
||||
export const operatorOptions: DefaultOptionType[] = [
|
||||
{ value: '>', label: '>' },
|
||||
@@ -8,6 +11,11 @@ export const operatorOptions: DefaultOptionType[] = [
|
||||
{ value: '<=', label: '<=' },
|
||||
];
|
||||
|
||||
export const unitOptions = categoryToSupport.map((category) => ({
|
||||
label: category,
|
||||
options: getCategorySelectOptionByName(category),
|
||||
}));
|
||||
|
||||
export const showAsOptions: DefaultOptionType[] = [
|
||||
{ value: 'Text', label: 'Text' },
|
||||
{ value: 'Background', label: 'Background' },
|
||||
|
||||
@@ -438,168 +438,3 @@ export const dataTypeCategories: DataTypeCategories = [
|
||||
export const flattenedCategories = flattenDeep(
|
||||
dataTypeCategories.map((category) => category.formats),
|
||||
);
|
||||
|
||||
type ConversionFactors = {
|
||||
[key: string]: {
|
||||
[key: string]: number | null;
|
||||
};
|
||||
};
|
||||
|
||||
// Object containing conversion factors for various categories and formats
|
||||
const conversionFactors: ConversionFactors = {
|
||||
[CategoryNames.Time]: {
|
||||
[TimeFormats.Hertz]: 1,
|
||||
[TimeFormats.Nanoseconds]: 1e-9,
|
||||
[TimeFormats.Microseconds]: 1e-6,
|
||||
[TimeFormats.Milliseconds]: 1e-3,
|
||||
[TimeFormats.Seconds]: 1,
|
||||
[TimeFormats.Minutes]: 60,
|
||||
[TimeFormats.Hours]: 3600,
|
||||
[TimeFormats.Days]: 86400,
|
||||
[TimeFormats.DurationMs]: 1e-3,
|
||||
[TimeFormats.DurationS]: 1,
|
||||
[TimeFormats.DurationHms]: null, // Requires special handling
|
||||
[TimeFormats.DurationDhms]: null, // Requires special handling
|
||||
[TimeFormats.Timeticks]: null, // Requires special handling
|
||||
[TimeFormats.ClockMs]: 1e-3,
|
||||
[TimeFormats.ClockS]: 1,
|
||||
},
|
||||
[CategoryNames.Throughput]: {
|
||||
[ThroughputFormats.CountsPerSec]: 1,
|
||||
[ThroughputFormats.OpsPerSec]: 1,
|
||||
[ThroughputFormats.RequestsPerSec]: 1,
|
||||
[ThroughputFormats.ReadsPerSec]: 1,
|
||||
[ThroughputFormats.WritesPerSec]: 1,
|
||||
[ThroughputFormats.IOOpsPerSec]: 1,
|
||||
[ThroughputFormats.CountsPerMin]: 1 / 60,
|
||||
[ThroughputFormats.OpsPerMin]: 1 / 60,
|
||||
[ThroughputFormats.ReadsPerMin]: 1 / 60,
|
||||
[ThroughputFormats.WritesPerMin]: 1 / 60,
|
||||
},
|
||||
[CategoryNames.Data]: {
|
||||
[DataFormats.BytesIEC]: 1,
|
||||
[DataFormats.BytesSI]: 1,
|
||||
[DataFormats.BitsIEC]: 0.125,
|
||||
[DataFormats.BitsSI]: 0.125,
|
||||
[DataFormats.KibiBytes]: 1024,
|
||||
[DataFormats.KiloBytes]: 1000,
|
||||
[DataFormats.MebiBytes]: 1048576,
|
||||
[DataFormats.MegaBytes]: 1000000,
|
||||
[DataFormats.GibiBytes]: 1073741824,
|
||||
[DataFormats.GigaBytes]: 1000000000,
|
||||
[DataFormats.TebiBytes]: 1099511627776,
|
||||
[DataFormats.TeraBytes]: 1000000000000,
|
||||
[DataFormats.PebiBytes]: 1125899906842624,
|
||||
[DataFormats.PetaBytes]: 1000000000000000,
|
||||
},
|
||||
[CategoryNames.DataRate]: {
|
||||
[DataRateFormats.PacketsPerSec]: null, // Cannot convert directly to other data rates
|
||||
[DataRateFormats.BytesPerSecIEC]: 1,
|
||||
[DataRateFormats.BytesPerSecSI]: 1,
|
||||
[DataRateFormats.BitsPerSecIEC]: 0.125,
|
||||
[DataRateFormats.BitsPerSecSI]: 0.125,
|
||||
[DataRateFormats.KibiBytesPerSec]: 1024,
|
||||
[DataRateFormats.KibiBitsPerSec]: 128,
|
||||
[DataRateFormats.KiloBytesPerSec]: 1000,
|
||||
[DataRateFormats.KiloBitsPerSec]: 125,
|
||||
[DataRateFormats.MebiBytesPerSec]: 1048576,
|
||||
[DataRateFormats.MebiBitsPerSec]: 131072,
|
||||
[DataRateFormats.MegaBytesPerSec]: 1000000,
|
||||
[DataRateFormats.MegaBitsPerSec]: 125000,
|
||||
[DataRateFormats.GibiBytesPerSec]: 1073741824,
|
||||
[DataRateFormats.GibiBitsPerSec]: 134217728,
|
||||
[DataRateFormats.GigaBytesPerSec]: 1000000000,
|
||||
[DataRateFormats.GigaBitsPerSec]: 125000000,
|
||||
[DataRateFormats.TebiBytesPerSec]: 1099511627776,
|
||||
[DataRateFormats.TebiBitsPerSec]: 137438953472,
|
||||
[DataRateFormats.TeraBytesPerSec]: 1000000000000,
|
||||
[DataRateFormats.TeraBitsPerSec]: 125000000000,
|
||||
[DataRateFormats.PebiBytesPerSec]: 1125899906842624,
|
||||
[DataRateFormats.PebiBitsPerSec]: 140737488355328,
|
||||
[DataRateFormats.PetaBytesPerSec]: 1000000000000000,
|
||||
[DataRateFormats.PetaBitsPerSec]: 125000000000000,
|
||||
},
|
||||
[CategoryNames.Miscellaneous]: {
|
||||
[MiscellaneousFormats.None]: null,
|
||||
[MiscellaneousFormats.String]: null,
|
||||
[MiscellaneousFormats.Short]: null,
|
||||
[MiscellaneousFormats.Percent]: 1,
|
||||
[MiscellaneousFormats.PercentUnit]: 100,
|
||||
[MiscellaneousFormats.Humidity]: 1,
|
||||
[MiscellaneousFormats.Decibel]: null,
|
||||
[MiscellaneousFormats.Hexadecimal0x]: null,
|
||||
[MiscellaneousFormats.Hexadecimal]: null,
|
||||
[MiscellaneousFormats.ScientificNotation]: null,
|
||||
[MiscellaneousFormats.LocaleFormat]: null,
|
||||
[MiscellaneousFormats.Pixels]: null,
|
||||
},
|
||||
[CategoryNames.Boolean]: {
|
||||
[BooleanFormats.TRUE_FALSE]: null, // Not convertible
|
||||
[BooleanFormats.YES_NO]: null, // Not convertible
|
||||
[BooleanFormats.ON_OFF]: null, // Not convertible
|
||||
},
|
||||
};
|
||||
|
||||
// Function to get the conversion factor between two units in a specific category
|
||||
function getConversionFactor(
|
||||
fromUnit: string,
|
||||
toUnit: string,
|
||||
category: CategoryNames,
|
||||
): number | null {
|
||||
// Retrieves the conversion factors for the specified category
|
||||
const categoryFactors = conversionFactors[category];
|
||||
if (!categoryFactors) {
|
||||
return null; // Returns null if the category does not exist
|
||||
}
|
||||
const fromFactor = categoryFactors[fromUnit];
|
||||
const toFactor = categoryFactors[toUnit];
|
||||
if (
|
||||
fromFactor === undefined ||
|
||||
toFactor === undefined ||
|
||||
fromFactor === null ||
|
||||
toFactor === null
|
||||
) {
|
||||
return null; // Returns null if either unit does not exist or is not convertible
|
||||
}
|
||||
return fromFactor / toFactor; // Returns the conversion factor ratio
|
||||
}
|
||||
|
||||
// Function to convert a value from one unit to another
|
||||
export function convertUnit(
|
||||
value: number,
|
||||
fromUnitId?: string,
|
||||
toUnitId?: string,
|
||||
): number | null {
|
||||
let fromUnit: string | undefined;
|
||||
let toUnit: string | undefined;
|
||||
|
||||
// Finds the category that contains the specified units and extracts fromUnit and toUnit using array methods
|
||||
const category = dataTypeCategories.find((category) =>
|
||||
category.formats.some((format) => {
|
||||
if (format.id === fromUnitId) fromUnit = format.id;
|
||||
if (format.id === toUnitId) toUnit = format.id;
|
||||
return fromUnit && toUnit; // Break out early if both units are found
|
||||
}),
|
||||
);
|
||||
|
||||
if (!category || !fromUnit || !toUnit) return null; // Return null if category or units are not found
|
||||
|
||||
// Gets the conversion factor for the specified units
|
||||
const conversionFactor = getConversionFactor(
|
||||
fromUnit,
|
||||
toUnit,
|
||||
category.name as any,
|
||||
);
|
||||
if (conversionFactor === null) return null; // Return null if conversion is not possible
|
||||
|
||||
return value * conversionFactor;
|
||||
}
|
||||
|
||||
// Function to get the category name for a given unit ID
|
||||
export const getCategoryName = (unitId: string): CategoryNames | null => {
|
||||
// Finds the category that contains the specified unit ID
|
||||
const foundCategory = dataTypeCategories.find((category) =>
|
||||
category.formats.some((format) => format.id === unitId),
|
||||
);
|
||||
return foundCategory ? (foundCategory.name as CategoryNames) : null;
|
||||
};
|
||||
|
||||
@@ -311,7 +311,6 @@ function RightContainer({
|
||||
setThresholds={setThresholds}
|
||||
yAxisUnit={yAxisUnit}
|
||||
selectedGraph={selectedGraph}
|
||||
columnUnits={columnUnits}
|
||||
/>
|
||||
</section>
|
||||
)}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { DefaultOptionType } from 'antd/es/select';
|
||||
import { omitIdFromQuery } from 'components/ExplorerCard/utils';
|
||||
import {
|
||||
initialQueryBuilderFormValuesMap,
|
||||
@@ -9,19 +8,12 @@ import {
|
||||
listViewInitialTraceQuery,
|
||||
PANEL_TYPES_INITIAL_QUERY,
|
||||
} from 'container/NewDashboard/ComponentsSlider/constants';
|
||||
import { categoryToSupport } from 'container/QueryBuilder/filters/BuilderUnitsFilter/config';
|
||||
import { cloneDeep, isEmpty, isEqual, set, unset } from 'lodash-es';
|
||||
import { cloneDeep, isEqual, set, unset } from 'lodash-es';
|
||||
import { Widgets } from 'types/api/dashboard/getAll';
|
||||
import { IBuilderQuery, Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
import {
|
||||
dataTypeCategories,
|
||||
getCategoryName,
|
||||
} from './RightContainer/dataFormatCategories';
|
||||
import { CategoryNames } from './RightContainer/types';
|
||||
|
||||
export const getIsQueryModified = (
|
||||
currentQuery: Query,
|
||||
stagedQuery: Query | null,
|
||||
@@ -537,41 +529,3 @@ export const PANEL_TYPE_TO_QUERY_TYPES: Record<PANEL_TYPES, EQueryType[]> = {
|
||||
EQueryType.PROM,
|
||||
],
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves a list of category select options based on the provided category name.
|
||||
* If the category is found, it maps the formats to an array of objects containing
|
||||
* the label and value for each format.
|
||||
*/
|
||||
export const getCategorySelectOptionByName = (
|
||||
name?: CategoryNames | string,
|
||||
): DefaultOptionType[] =>
|
||||
dataTypeCategories
|
||||
.find((category) => category.name === name)
|
||||
?.formats.map((format) => ({
|
||||
label: format.name,
|
||||
value: format.id,
|
||||
})) || [];
|
||||
|
||||
/**
|
||||
* Generates unit options based on the provided column unit.
|
||||
* It first retrieves the category name associated with the column unit.
|
||||
* If the category is empty, it maps all supported categories to their respective
|
||||
* select options. If a valid category is found, it filters the supported categories
|
||||
* to return only the options for the matched category.
|
||||
*/
|
||||
export const unitOptions = (columnUnit: string): DefaultOptionType[] => {
|
||||
const category = getCategoryName(columnUnit);
|
||||
if (isEmpty(category)) {
|
||||
return categoryToSupport.map((category) => ({
|
||||
label: category,
|
||||
options: getCategorySelectOptionByName(category),
|
||||
}));
|
||||
}
|
||||
return categoryToSupport
|
||||
.filter((supportedCategory) => supportedCategory === category)
|
||||
.map((filteredCategory) => ({
|
||||
label: filteredCategory,
|
||||
options: getCategorySelectOptionByName(filteredCategory),
|
||||
}));
|
||||
};
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
|
||||
|
||||
Once you are done instrumenting your Java application, you can run it using the below commands
|
||||
|
||||
**Note:**
|
||||
- Ensure you have Java and Maven installed. Compile your Java consumer applications: Ensure your consumer apps are compiled and ready to run.
|
||||
|
||||
**Run Consumer App with Java Agent:**
|
||||
|
||||
```bash
|
||||
java -javaagent:/path/to/opentelemetry-javaagent.jar \
|
||||
-Dotel.service.name=consumer-svc \
|
||||
-Dotel.traces.exporter=otlp \
|
||||
-Dotel.metrics.exporter=otlp \
|
||||
-Dotel.logs.exporter=otlp \
|
||||
-Dotel.instrumentation.kafka.producer-propagation.enabled=true \
|
||||
-Dotel.instrumentation.kafka.experimental-span-attributes=true \
|
||||
-Dotel.instrumentation.kafka.metric-reporter.enabled=true \
|
||||
-jar /path/to/your/consumer.jar
|
||||
```
|
||||
|
||||
<path> - update it to the path where you downloaded the Java JAR agent in previous step
|
||||
<my-app> - Jar file of your application
|
||||
|
||||
|
||||
|
||||
**Note:**
|
||||
- In case you're dockerising your application, make sure to dockerise it along with OpenTelemetry instrumentation done in previous step.
|
||||
|
||||
|
||||
|
||||
If you encounter any difficulties, please consult the [troubleshooting section](https://signoz.io/docs/instrumentation/springboot/#troubleshooting-your-installation) for assistance.
|
||||
@@ -1,29 +0,0 @@
|
||||
|
||||
|
||||
Once you are done instrumenting your Java application, you can run it using the below commands
|
||||
|
||||
**Note:**
|
||||
- Ensure you have Java and Maven installed. Compile your Java producer applications: Ensure your producer apps are compiled and ready to run.
|
||||
|
||||
**Run Producer App with Java Agent:**
|
||||
|
||||
```bash
|
||||
java -javaagent:/path/to/opentelemetry-javaagent.jar \
|
||||
-Dotel.service.name=producer-svc \
|
||||
-Dotel.traces.exporter=otlp \
|
||||
-Dotel.metrics.exporter=otlp \
|
||||
-Dotel.logs.exporter=otlp \
|
||||
-jar /path/to/your/producer.jar
|
||||
```
|
||||
|
||||
<path> - update it to the path where you downloaded the Java JAR agent in previous step
|
||||
<my-app> - Jar file of your application
|
||||
|
||||
|
||||
|
||||
**Note:**
|
||||
- In case you're dockerising your application, make sure to dockerise it along with OpenTelemetry instrumentation done in previous step.
|
||||
|
||||
|
||||
|
||||
If you encounter any difficulties, please consult the [troubleshooting section](https://signoz.io/docs/instrumentation/springboot/#troubleshooting-your-installation) for assistance.
|
||||
@@ -312,7 +312,7 @@ export default function Onboarding(): JSX.Element {
|
||||
<div
|
||||
onClick={(): void => {
|
||||
logEvent('Onboarding V2: Skip Button Clicked', {});
|
||||
history.push(ROUTES.APPLICATION);
|
||||
history.push('/');
|
||||
}}
|
||||
className="skip-to-console"
|
||||
>
|
||||
|
||||
@@ -6,16 +6,11 @@ import {
|
||||
LoadingOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import { QueryParams } from 'constants/query';
|
||||
import Header from 'container/OnboardingContainer/common/Header/Header';
|
||||
import { useOnboardingContext } from 'container/OnboardingContainer/context/OnboardingContext';
|
||||
import { useOnboardingStatus } from 'hooks/messagingQueue / onboarding/useOnboardingStatus';
|
||||
import { useQueryService } from 'hooks/useQueryService';
|
||||
import useResourceAttribute from 'hooks/useResourceAttribute';
|
||||
import { convertRawQueriesToTraceSelectedTags } from 'hooks/useResourceAttribute/utils';
|
||||
import useUrlQuery from 'hooks/useUrlQuery';
|
||||
import MessagingQueueHealthCheck from 'pages/MessagingQueues/MessagingQueueHealthCheck/MessagingQueueHealthCheck';
|
||||
import { getAttributeDataFromOnboardingStatus } from 'pages/MessagingQueues/MessagingQueuesUtils';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
@@ -32,12 +27,6 @@ export default function ConnectionStatus(): JSX.Element {
|
||||
GlobalReducer
|
||||
>((state) => state.globalTime);
|
||||
|
||||
const urlQuery = useUrlQuery();
|
||||
const getStartedSource = urlQuery.get(QueryParams.getStartedSource);
|
||||
const getStartedSourceService = urlQuery.get(
|
||||
QueryParams.getStartedSourceService,
|
||||
);
|
||||
|
||||
const {
|
||||
serviceName,
|
||||
selectedDataSource,
|
||||
@@ -68,69 +57,8 @@ export default function ConnectionStatus(): JSX.Element {
|
||||
maxTime,
|
||||
selectedTime,
|
||||
selectedTags,
|
||||
options: {
|
||||
enabled: getStartedSource !== 'kafka',
|
||||
},
|
||||
});
|
||||
|
||||
const [pollInterval, setPollInterval] = useState<number | false>(10000);
|
||||
const {
|
||||
data: onbData,
|
||||
error: onbErr,
|
||||
isFetching: onbFetching,
|
||||
} = useOnboardingStatus(
|
||||
{
|
||||
enabled: getStartedSource === 'kafka',
|
||||
refetchInterval: pollInterval,
|
||||
},
|
||||
getStartedSourceService || '',
|
||||
'query-key-onboarding-status',
|
||||
);
|
||||
|
||||
const [
|
||||
shouldRetryOnboardingCall,
|
||||
setShouldRetryOnboardingCall,
|
||||
] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
// runs only when the caller is coming from 'kafka' i.e. coming from Messaging Queues - setup helper
|
||||
if (getStartedSource === 'kafka') {
|
||||
if (onbData?.statusCode !== 200) {
|
||||
setShouldRetryOnboardingCall(true);
|
||||
} else if (onbData?.payload?.status === 'success') {
|
||||
const attributeData = getAttributeDataFromOnboardingStatus(
|
||||
onbData?.payload,
|
||||
);
|
||||
if (attributeData.overallStatus === 'success') {
|
||||
setLoading(false);
|
||||
setIsReceivingData(true);
|
||||
setPollInterval(false);
|
||||
} else {
|
||||
setShouldRetryOnboardingCall(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [
|
||||
shouldRetryOnboardingCall,
|
||||
onbData,
|
||||
onbErr,
|
||||
onbFetching,
|
||||
getStartedSource,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (retryCount < 0 && getStartedSource === 'kafka') {
|
||||
setPollInterval(false);
|
||||
setLoading(false);
|
||||
}
|
||||
}, [retryCount, getStartedSource]);
|
||||
|
||||
useEffect(() => {
|
||||
if (getStartedSource === 'kafka' && !onbFetching) {
|
||||
setRetryCount((prevCount) => prevCount - 1);
|
||||
}
|
||||
}, [getStartedSource, onbData, onbFetching]);
|
||||
|
||||
const renderDocsReference = (): JSX.Element => {
|
||||
switch (selectedDataSource?.name) {
|
||||
case 'java':
|
||||
@@ -264,27 +192,25 @@ export default function ConnectionStatus(): JSX.Element {
|
||||
useEffect(() => {
|
||||
let pollingTimer: string | number | NodeJS.Timer | undefined;
|
||||
|
||||
if (getStartedSource !== 'kafka') {
|
||||
if (loading) {
|
||||
pollingTimer = setInterval(() => {
|
||||
// Trigger a refetch with the updated parameters
|
||||
const updatedMinTime = (Date.now() - 15 * 60 * 1000) * 1000000;
|
||||
const updatedMaxTime = Date.now() * 1000000;
|
||||
if (loading) {
|
||||
pollingTimer = setInterval(() => {
|
||||
// Trigger a refetch with the updated parameters
|
||||
const updatedMinTime = (Date.now() - 15 * 60 * 1000) * 1000000;
|
||||
const updatedMaxTime = Date.now() * 1000000;
|
||||
|
||||
const payload = {
|
||||
maxTime: updatedMaxTime,
|
||||
minTime: updatedMinTime,
|
||||
selectedTime,
|
||||
};
|
||||
const payload = {
|
||||
maxTime: updatedMaxTime,
|
||||
minTime: updatedMinTime,
|
||||
selectedTime,
|
||||
};
|
||||
|
||||
dispatch({
|
||||
type: UPDATE_TIME_INTERVAL,
|
||||
payload,
|
||||
});
|
||||
}, pollingInterval); // Same interval as pollingInterval
|
||||
} else if (!loading && pollingTimer) {
|
||||
clearInterval(pollingTimer);
|
||||
}
|
||||
dispatch({
|
||||
type: UPDATE_TIME_INTERVAL,
|
||||
payload,
|
||||
});
|
||||
}, pollingInterval); // Same interval as pollingInterval
|
||||
} else if (!loading && pollingTimer) {
|
||||
clearInterval(pollingTimer);
|
||||
}
|
||||
|
||||
// Clean up the interval when the component unmounts
|
||||
@@ -295,24 +221,15 @@ export default function ConnectionStatus(): JSX.Element {
|
||||
}, [refetch, selectedTags, selectedTime, loading]);
|
||||
|
||||
useEffect(() => {
|
||||
if (getStartedSource !== 'kafka') {
|
||||
verifyApplicationData(data);
|
||||
}
|
||||
verifyApplicationData(data);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [isServiceLoading, data, error, isError]);
|
||||
|
||||
useEffect(() => {
|
||||
if (getStartedSource !== 'kafka') {
|
||||
refetch();
|
||||
}
|
||||
refetch();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const isQueryServiceLoading = useMemo(
|
||||
() => isServiceLoading || loading || onbFetching,
|
||||
[isServiceLoading, loading, onbFetching],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="connection-status-container">
|
||||
<div className="full-docs-link">{renderDocsReference()}</div>
|
||||
@@ -333,42 +250,30 @@ export default function ConnectionStatus(): JSX.Element {
|
||||
<div className="label"> Status </div>
|
||||
|
||||
<div className="status">
|
||||
{isQueryServiceLoading && <LoadingOutlined />}
|
||||
{!isQueryServiceLoading &&
|
||||
isReceivingData &&
|
||||
(getStartedSource !== 'kafka' ? (
|
||||
<>
|
||||
<CheckCircleTwoTone twoToneColor="#52c41a" />
|
||||
<span> Success </span>
|
||||
</>
|
||||
) : (
|
||||
<MessagingQueueHealthCheck
|
||||
serviceToInclude={[getStartedSourceService || '']}
|
||||
/>
|
||||
))}
|
||||
{!isQueryServiceLoading &&
|
||||
!isReceivingData &&
|
||||
(getStartedSource !== 'kafka' ? (
|
||||
<>
|
||||
<CloseCircleTwoTone twoToneColor="#e84749" />
|
||||
<span> Failed </span>
|
||||
</>
|
||||
) : (
|
||||
<MessagingQueueHealthCheck
|
||||
serviceToInclude={[getStartedSourceService || '']}
|
||||
/>
|
||||
))}
|
||||
{(loading || isServiceLoading) && <LoadingOutlined />}
|
||||
{!(loading || isServiceLoading) && isReceivingData && (
|
||||
<>
|
||||
<CheckCircleTwoTone twoToneColor="#52c41a" />
|
||||
<span> Success </span>
|
||||
</>
|
||||
)}
|
||||
{!(loading || isServiceLoading) && !isReceivingData && (
|
||||
<>
|
||||
<CloseCircleTwoTone twoToneColor="#e84749" />
|
||||
<span> Failed </span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="details-info">
|
||||
<div className="label"> Details </div>
|
||||
|
||||
<div className="details">
|
||||
{isQueryServiceLoading && <div> Waiting for Update </div>}
|
||||
{!isQueryServiceLoading && isReceivingData && (
|
||||
{(loading || isServiceLoading) && <div> Waiting for Update </div>}
|
||||
{!(loading || isServiceLoading) && isReceivingData && (
|
||||
<div> Received data from the application successfully. </div>
|
||||
)}
|
||||
{!isQueryServiceLoading && !isReceivingData && (
|
||||
{!(loading || isServiceLoading) && !isReceivingData && (
|
||||
<div> Could not detect the install </div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -74,11 +74,4 @@ div[class*='-setup-instructions-container'] {
|
||||
.dataSourceName {
|
||||
color: var(--bg-slate-500);
|
||||
}
|
||||
}
|
||||
|
||||
.supported-languages-container {
|
||||
.disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,21 +6,15 @@ import { LoadingOutlined } from '@ant-design/icons';
|
||||
import { Button, Card, Form, Input, Select, Space, Typography } from 'antd';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import cx from 'classnames';
|
||||
import { QueryParams } from 'constants/query';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { useOnboardingContext } from 'container/OnboardingContainer/context/OnboardingContext';
|
||||
import {
|
||||
ModulesMap,
|
||||
useCases,
|
||||
} from 'container/OnboardingContainer/OnboardingContainer';
|
||||
import { useCases } from 'container/OnboardingContainer/OnboardingContainer';
|
||||
import {
|
||||
getDataSources,
|
||||
getSupportedFrameworks,
|
||||
hasFrameworks,
|
||||
messagingQueueKakfaSupportedDataSources,
|
||||
} from 'container/OnboardingContainer/utils/dataSourceUtils';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import useUrlQuery from 'hooks/useUrlQuery';
|
||||
import { Blocks, Check } from 'lucide-react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@@ -39,8 +33,6 @@ export default function DataSource(): JSX.Element {
|
||||
const { t } = useTranslation(['common']);
|
||||
const history = useHistory();
|
||||
|
||||
const getStartedSource = useUrlQuery().get(QueryParams.getStartedSource);
|
||||
|
||||
const {
|
||||
serviceName,
|
||||
selectedModule,
|
||||
@@ -52,9 +44,6 @@ export default function DataSource(): JSX.Element {
|
||||
updateSelectedFramework,
|
||||
} = useOnboardingContext();
|
||||
|
||||
const isKafkaAPM =
|
||||
getStartedSource === 'kafka' && selectedModule?.id === ModulesMap.APM;
|
||||
|
||||
const [supportedDataSources, setSupportedDataSources] = useState<
|
||||
DataSourceType[]
|
||||
>([]);
|
||||
@@ -161,19 +150,13 @@ export default function DataSource(): JSX.Element {
|
||||
className={cx(
|
||||
'supported-language',
|
||||
selectedDataSource?.name === dataSource.name ? 'selected' : '',
|
||||
isKafkaAPM &&
|
||||
!messagingQueueKakfaSupportedDataSources.includes(dataSource?.id || '')
|
||||
? 'disabled'
|
||||
: '',
|
||||
)}
|
||||
key={dataSource.name}
|
||||
onClick={(): void => {
|
||||
if (!isKafkaAPM) {
|
||||
updateSelectedFramework(null);
|
||||
updateSelectedEnvironment(null);
|
||||
updateSelectedDataSource(dataSource);
|
||||
form.setFieldsValue({ selectFramework: null });
|
||||
}
|
||||
updateSelectedFramework(null);
|
||||
updateSelectedEnvironment(null);
|
||||
updateSelectedDataSource(dataSource);
|
||||
form.setFieldsValue({ selectFramework: null });
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||||
import { MarkdownRenderer } from 'components/MarkdownRenderer/MarkdownRenderer';
|
||||
import { QueryParams } from 'constants/query';
|
||||
import { ApmDocFilePaths } from 'container/OnboardingContainer/constants/apmDocFilePaths';
|
||||
import { AwsMonitoringDocFilePaths } from 'container/OnboardingContainer/constants/awsMonitoringDocFilePaths';
|
||||
import { AzureMonitoringDocFilePaths } from 'container/OnboardingContainer/constants/azureMonitoringDocFilePaths';
|
||||
@@ -11,7 +10,6 @@ import {
|
||||
useOnboardingContext,
|
||||
} from 'container/OnboardingContainer/context/OnboardingContext';
|
||||
import { ModulesMap } from 'container/OnboardingContainer/OnboardingContainer';
|
||||
import useUrlQuery from 'hooks/useUrlQuery';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
export interface IngestionInfoProps {
|
||||
@@ -33,12 +31,6 @@ export default function MarkdownStep(): JSX.Element {
|
||||
|
||||
const [markdownContent, setMarkdownContent] = useState('');
|
||||
|
||||
const urlQuery = useUrlQuery();
|
||||
const getStartedSource = urlQuery.get(QueryParams.getStartedSource);
|
||||
const getStartedSourceService = urlQuery.get(
|
||||
QueryParams.getStartedSourceService,
|
||||
);
|
||||
|
||||
const { step } = activeStep;
|
||||
|
||||
const getFilePath = (): any => {
|
||||
@@ -62,12 +54,6 @@ export default function MarkdownStep(): JSX.Element {
|
||||
|
||||
path += `_${step?.id}`;
|
||||
|
||||
if (
|
||||
getStartedSource === 'kafka' &&
|
||||
path === 'APM_java_springBoot_kubernetes_recommendedSteps_runApplication' // todo: Sagar - Make this generic logic in followup PRs
|
||||
) {
|
||||
path += `_${getStartedSourceService}`;
|
||||
}
|
||||
return path;
|
||||
};
|
||||
|
||||
|
||||
@@ -252,8 +252,6 @@ import APM_java_springBoot_docker_recommendedSteps_runApplication from '../Modul
|
||||
import APM_java_springBoot_kubernetes_recommendedSteps_setupOtelCollector from '../Modules/APM/Java/md-docs/SpringBoot/Kubernetes/springBoot-kubernetes-installOtelCollector.md';
|
||||
import APM_java_springBoot_kubernetes_recommendedSteps_instrumentApplication from '../Modules/APM/Java/md-docs/SpringBoot/Kubernetes/springBoot-kubernetes-instrumentApplication.md';
|
||||
import APM_java_springBoot_kubernetes_recommendedSteps_runApplication from '../Modules/APM/Java/md-docs/SpringBoot/Kubernetes/springBoot-kubernetes-runApplication.md';
|
||||
import APM_java_springBoot_kubernetes_recommendedSteps_runApplication_consumers from '../Modules/APM/Java/md-docs/SpringBoot/Kubernetes/springBoot-kubernetes-runApplication-consumers.md';
|
||||
import APM_java_springBoot_kubernetes_recommendedSteps_runApplication_producers from '../Modules/APM/Java/md-docs/SpringBoot/Kubernetes/springBoot-kubernetes-runApplication-producers.md';
|
||||
// SpringBoot-LinuxAMD64-quickstart
|
||||
import APM_java_springBoot_linuxAMD64_quickStart_instrumentApplication from '../Modules/APM/Java/md-docs/SpringBoot/LinuxAMD64/QuickStart/springBoot-linuxamd64-quickStart-instrumentApplication.md';
|
||||
import APM_java_springBoot_linuxAMD64_quickStart_runApplication from '../Modules/APM/Java/md-docs/SpringBoot/LinuxAMD64/QuickStart/springBoot-linuxamd64-quickStart-runApplication.md';
|
||||
@@ -1055,8 +1053,6 @@ export const ApmDocFilePaths = {
|
||||
APM_java_springBoot_kubernetes_recommendedSteps_setupOtelCollector,
|
||||
APM_java_springBoot_kubernetes_recommendedSteps_instrumentApplication,
|
||||
APM_java_springBoot_kubernetes_recommendedSteps_runApplication,
|
||||
APM_java_springBoot_kubernetes_recommendedSteps_runApplication_producers,
|
||||
APM_java_springBoot_kubernetes_recommendedSteps_runApplication_consumers,
|
||||
|
||||
// SpringBoot-LinuxAMD64-recommended
|
||||
APM_java_springBoot_linuxAMD64_recommendedSteps_setupOtelCollector,
|
||||
|
||||
@@ -399,5 +399,3 @@ export const moduleRouteMap = {
|
||||
[ModulesMap.AwsMonitoring]: ROUTES.GET_STARTED_AWS_MONITORING,
|
||||
[ModulesMap.AzureMonitoring]: ROUTES.GET_STARTED_AZURE_MONITORING,
|
||||
};
|
||||
|
||||
export const messagingQueueKakfaSupportedDataSources = ['java'];
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user