mirror of
https://github.com/SigNoz/signoz.git
synced 2026-03-07 14:22:05 +00:00
Compare commits
3 Commits
ci/better-
...
in-progres
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fc321f8acc | ||
|
|
154560086f | ||
|
|
f95a5432e3 |
4
.github/workflows/commitci.yaml
vendored
4
.github/workflows/commitci.yaml
vendored
@@ -7,14 +7,10 @@ on:
|
||||
pull_request_target:
|
||||
types:
|
||||
- labeled
|
||||
merge_group:
|
||||
types:
|
||||
- checks_requested
|
||||
|
||||
jobs:
|
||||
refcheck:
|
||||
if: |
|
||||
github.event_name == 'merge_group' ||
|
||||
(github.event_name == 'pull_request' && ! github.event.pull_request.head.repo.fork && github.event.pull_request.user.login != 'dependabot[bot]' && ! contains(github.event.pull_request.labels.*.name, 'safe-to-test')) ||
|
||||
(github.event_name == 'pull_request_target' && contains(github.event.pull_request.labels.*.name, 'safe-to-test'))
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
9
.github/workflows/goci.yaml
vendored
9
.github/workflows/goci.yaml
vendored
@@ -7,14 +7,10 @@ on:
|
||||
pull_request_target:
|
||||
types:
|
||||
- labeled
|
||||
merge_group:
|
||||
types:
|
||||
- checks_requested
|
||||
|
||||
jobs:
|
||||
test:
|
||||
if: |
|
||||
github.event_name == 'merge_group' ||
|
||||
(github.event_name == 'pull_request' && ! github.event.pull_request.head.repo.fork && github.event.pull_request.user.login != 'dependabot[bot]' && ! contains(github.event.pull_request.labels.*.name, 'safe-to-test')) ||
|
||||
(github.event_name == 'pull_request_target' && contains(github.event.pull_request.labels.*.name, 'safe-to-test'))
|
||||
uses: signoz/primus.workflows/.github/workflows/go-test.yaml@main
|
||||
@@ -25,7 +21,6 @@ jobs:
|
||||
GO_VERSION: 1.24
|
||||
fmt:
|
||||
if: |
|
||||
github.event_name == 'merge_group' ||
|
||||
(github.event_name == 'pull_request' && ! github.event.pull_request.head.repo.fork && github.event.pull_request.user.login != 'dependabot[bot]' && ! contains(github.event.pull_request.labels.*.name, 'safe-to-test')) ||
|
||||
(github.event_name == 'pull_request_target' && contains(github.event.pull_request.labels.*.name, 'safe-to-test'))
|
||||
uses: signoz/primus.workflows/.github/workflows/go-fmt.yaml@main
|
||||
@@ -35,7 +30,6 @@ jobs:
|
||||
GO_VERSION: 1.24
|
||||
lint:
|
||||
if: |
|
||||
github.event_name == 'merge_group' ||
|
||||
(github.event_name == 'pull_request' && ! github.event.pull_request.head.repo.fork && github.event.pull_request.user.login != 'dependabot[bot]' && ! contains(github.event.pull_request.labels.*.name, 'safe-to-test')) ||
|
||||
(github.event_name == 'pull_request_target' && contains(github.event.pull_request.labels.*.name, 'safe-to-test'))
|
||||
uses: signoz/primus.workflows/.github/workflows/go-lint.yaml@main
|
||||
@@ -45,7 +39,6 @@ jobs:
|
||||
GO_VERSION: 1.24
|
||||
deps:
|
||||
if: |
|
||||
github.event_name == 'merge_group' ||
|
||||
(github.event_name == 'pull_request' && ! github.event.pull_request.head.repo.fork && github.event.pull_request.user.login != 'dependabot[bot]' && ! contains(github.event.pull_request.labels.*.name, 'safe-to-test')) ||
|
||||
(github.event_name == 'pull_request_target' && contains(github.event.pull_request.labels.*.name, 'safe-to-test'))
|
||||
uses: signoz/primus.workflows/.github/workflows/go-deps.yaml@main
|
||||
@@ -55,7 +48,6 @@ jobs:
|
||||
GO_VERSION: 1.24
|
||||
build:
|
||||
if: |
|
||||
github.event_name == 'merge_group' ||
|
||||
(github.event_name == 'pull_request' && ! github.event.pull_request.head.repo.fork && github.event.pull_request.user.login != 'dependabot[bot]' && ! contains(github.event.pull_request.labels.*.name, 'safe-to-test')) ||
|
||||
(github.event_name == 'pull_request_target' && contains(github.event.pull_request.labels.*.name, 'safe-to-test'))
|
||||
runs-on: ubuntu-latest
|
||||
@@ -87,7 +79,6 @@ jobs:
|
||||
make docker-build-enterprise
|
||||
openapi:
|
||||
if: |
|
||||
github.event_name == 'merge_group' ||
|
||||
(github.event_name == 'pull_request' && ! github.event.pull_request.head.repo.fork && github.event.pull_request.user.login != 'dependabot[bot]' && ! contains(github.event.pull_request.labels.*.name, 'safe-to-test')) ||
|
||||
(github.event_name == 'pull_request_target' && contains(github.event.pull_request.labels.*.name, 'safe-to-test'))
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
54
.github/workflows/jsci.yaml
vendored
54
.github/workflows/jsci.yaml
vendored
@@ -7,14 +7,10 @@ on:
|
||||
pull_request_target:
|
||||
types:
|
||||
- labeled
|
||||
merge_group:
|
||||
types:
|
||||
- checks_requested
|
||||
|
||||
jobs:
|
||||
tsc:
|
||||
if: |
|
||||
github.event_name == 'merge_group' ||
|
||||
(github.event_name == 'pull_request' && ! github.event.pull_request.head.repo.fork && github.event.pull_request.user.login != 'dependabot[bot]' && ! contains(github.event.pull_request.labels.*.name, 'safe-to-test')) ||
|
||||
(github.event_name == 'pull_request_target' && contains(github.event.pull_request.labels.*.name, 'safe-to-test'))
|
||||
runs-on: ubuntu-latest
|
||||
@@ -31,7 +27,6 @@ jobs:
|
||||
run: cd frontend && yarn tsc
|
||||
tsc2:
|
||||
if: |
|
||||
github.event_name == 'merge_group' ||
|
||||
(github.event_name == 'pull_request' && ! github.event.pull_request.head.repo.fork && github.event.pull_request.user.login != 'dependabot[bot]' && ! contains(github.event.pull_request.labels.*.name, 'safe-to-test')) ||
|
||||
(github.event_name == 'pull_request_target' && contains(github.event.pull_request.labels.*.name, 'safe-to-test'))
|
||||
uses: signoz/primus.workflows/.github/workflows/js-tsc.yaml@main
|
||||
@@ -41,7 +36,6 @@ jobs:
|
||||
JS_SRC: frontend
|
||||
test:
|
||||
if: |
|
||||
github.event_name == 'merge_group' ||
|
||||
(github.event_name == 'pull_request' && ! github.event.pull_request.head.repo.fork && github.event.pull_request.user.login != 'dependabot[bot]' && ! contains(github.event.pull_request.labels.*.name, 'safe-to-test')) ||
|
||||
(github.event_name == 'pull_request_target' && contains(github.event.pull_request.labels.*.name, 'safe-to-test'))
|
||||
uses: signoz/primus.workflows/.github/workflows/js-test.yaml@main
|
||||
@@ -51,7 +45,6 @@ jobs:
|
||||
JS_SRC: frontend
|
||||
fmt:
|
||||
if: |
|
||||
github.event_name == 'merge_group' ||
|
||||
(github.event_name == 'pull_request' && ! github.event.pull_request.head.repo.fork && github.event.pull_request.user.login != 'dependabot[bot]' && ! contains(github.event.pull_request.labels.*.name, 'safe-to-test')) ||
|
||||
(github.event_name == 'pull_request_target' && contains(github.event.pull_request.labels.*.name, 'safe-to-test'))
|
||||
uses: signoz/primus.workflows/.github/workflows/js-fmt.yaml@main
|
||||
@@ -61,7 +54,6 @@ jobs:
|
||||
JS_SRC: frontend
|
||||
lint:
|
||||
if: |
|
||||
github.event_name == 'merge_group' ||
|
||||
(github.event_name == 'pull_request' && ! github.event.pull_request.head.repo.fork && github.event.pull_request.user.login != 'dependabot[bot]' && ! contains(github.event.pull_request.labels.*.name, 'safe-to-test')) ||
|
||||
(github.event_name == 'pull_request_target' && contains(github.event.pull_request.labels.*.name, 'safe-to-test'))
|
||||
uses: signoz/primus.workflows/.github/workflows/js-lint.yaml@main
|
||||
@@ -69,53 +61,8 @@ jobs:
|
||||
with:
|
||||
PRIMUS_REF: main
|
||||
JS_SRC: frontend
|
||||
lint-pnpm:
|
||||
if: |
|
||||
github.event_name == 'merge_group' ||
|
||||
(github.event_name == 'pull_request' && ! github.event.pull_request.head.repo.fork && github.event.pull_request.user.login != 'dependabot[bot]' && ! contains(github.event.pull_request.labels.*.name, 'safe-to-test')) ||
|
||||
(github.event_name == 'pull_request_target' && contains(github.event.pull_request.labels.*.name, 'safe-to-test'))
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 10
|
||||
- name: setup node
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: "22"
|
||||
- name: install
|
||||
run: cd frontend && pnpm install
|
||||
- name: lint
|
||||
run: echo 'done'
|
||||
lint-pnpm-cache:
|
||||
if: |
|
||||
github.event_name == 'merge_group' ||
|
||||
(github.event_name == 'pull_request' && ! github.event.pull_request.head.repo.fork && github.event.pull_request.user.login != 'dependabot[bot]' && ! contains(github.event.pull_request.labels.*.name, 'safe-to-test')) ||
|
||||
(github.event_name == 'pull_request_target' && contains(github.event.pull_request.labels.*.name, 'safe-to-test'))
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 10
|
||||
- name: setup node
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: "22"
|
||||
cache: 'pnpm'
|
||||
cache-dependency-path: frontend/pnpm-lock.yaml
|
||||
- name: install
|
||||
run: cd frontend && pnpm install
|
||||
- name: lint
|
||||
run: echo 'done'
|
||||
md-languages:
|
||||
if: |
|
||||
github.event_name == 'merge_group' ||
|
||||
(github.event_name == 'pull_request' && ! github.event.pull_request.head.repo.fork && github.event.pull_request.user.login != 'dependabot[bot]' && ! contains(github.event.pull_request.labels.*.name, 'safe-to-test')) ||
|
||||
(github.event_name == 'pull_request_target' && contains(github.event.pull_request.labels.*.name, 'safe-to-test'))
|
||||
runs-on: ubuntu-latest
|
||||
@@ -126,7 +73,6 @@ jobs:
|
||||
run: bash frontend/scripts/validate-md-languages.sh
|
||||
authz:
|
||||
if: |
|
||||
github.event_name == 'merge_group' ||
|
||||
(github.event_name == 'pull_request' && ! github.event.pull_request.head.repo.fork && github.event.pull_request.user.login != 'dependabot[bot]' && ! contains(github.event.pull_request.labels.*.name, 'safe-to-test')) ||
|
||||
(github.event_name == 'pull_request_target' && contains(github.event.pull_request.labels.*.name, 'safe-to-test'))
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -190,7 +190,7 @@ services:
|
||||
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
|
||||
signoz:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz:v0.114.1
|
||||
image: signoz/signoz:v0.113.0
|
||||
ports:
|
||||
- "8080:8080" # signoz port
|
||||
# - "6060:6060" # pprof port
|
||||
@@ -213,7 +213,7 @@ services:
|
||||
retries: 3
|
||||
otel-collector:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz-otel-collector:v0.144.2
|
||||
image: signoz/signoz-otel-collector:v0.144.1
|
||||
entrypoint:
|
||||
- /bin/sh
|
||||
command:
|
||||
@@ -241,7 +241,7 @@ services:
|
||||
replicas: 3
|
||||
signoz-telemetrystore-migrator:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz-otel-collector:v0.144.2
|
||||
image: signoz/signoz-otel-collector:v0.144.1
|
||||
environment:
|
||||
- SIGNOZ_OTEL_COLLECTOR_CLICKHOUSE_DSN=tcp://clickhouse:9000
|
||||
- SIGNOZ_OTEL_COLLECTOR_CLICKHOUSE_CLUSTER=cluster
|
||||
|
||||
@@ -117,7 +117,7 @@ services:
|
||||
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
|
||||
signoz:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz:v0.114.1
|
||||
image: signoz/signoz:v0.113.0
|
||||
ports:
|
||||
- "8080:8080" # signoz port
|
||||
volumes:
|
||||
@@ -139,7 +139,7 @@ services:
|
||||
retries: 3
|
||||
otel-collector:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz-otel-collector:v0.144.2
|
||||
image: signoz/signoz-otel-collector:v0.144.1
|
||||
entrypoint:
|
||||
- /bin/sh
|
||||
command:
|
||||
@@ -167,7 +167,7 @@ services:
|
||||
replicas: 3
|
||||
signoz-telemetrystore-migrator:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz-otel-collector:v0.144.2
|
||||
image: signoz/signoz-otel-collector:v0.144.1
|
||||
environment:
|
||||
- SIGNOZ_OTEL_COLLECTOR_CLICKHOUSE_DSN=tcp://clickhouse:9000
|
||||
- SIGNOZ_OTEL_COLLECTOR_CLICKHOUSE_CLUSTER=cluster
|
||||
|
||||
@@ -181,7 +181,7 @@ services:
|
||||
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
|
||||
signoz:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz:${VERSION:-v0.114.1}
|
||||
image: signoz/signoz:${VERSION:-v0.113.0}
|
||||
container_name: signoz
|
||||
ports:
|
||||
- "8080:8080" # signoz port
|
||||
@@ -204,7 +204,7 @@ services:
|
||||
retries: 3
|
||||
otel-collector:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.144.2}
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.144.1}
|
||||
container_name: signoz-otel-collector
|
||||
entrypoint:
|
||||
- /bin/sh
|
||||
@@ -229,7 +229,7 @@ services:
|
||||
- "4318:4318" # OTLP HTTP receiver
|
||||
signoz-telemetrystore-migrator:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.144.2}
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.144.1}
|
||||
container_name: signoz-telemetrystore-migrator
|
||||
environment:
|
||||
- SIGNOZ_OTEL_COLLECTOR_CLICKHOUSE_DSN=tcp://clickhouse:9000
|
||||
|
||||
@@ -109,7 +109,7 @@ services:
|
||||
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
|
||||
signoz:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz:${VERSION:-v0.114.1}
|
||||
image: signoz/signoz:${VERSION:-v0.113.0}
|
||||
container_name: signoz
|
||||
ports:
|
||||
- "8080:8080" # signoz port
|
||||
@@ -132,7 +132,7 @@ services:
|
||||
retries: 3
|
||||
otel-collector:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.144.2}
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.144.1}
|
||||
container_name: signoz-otel-collector
|
||||
entrypoint:
|
||||
- /bin/sh
|
||||
@@ -157,7 +157,7 @@ services:
|
||||
- "4318:4318" # OTLP HTTP receiver
|
||||
signoz-telemetrystore-migrator:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.144.2}
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.144.1}
|
||||
container_name: signoz-telemetrystore-migrator
|
||||
environment:
|
||||
- SIGNOZ_OTEL_COLLECTOR_CLICKHOUSE_DSN=tcp://clickhouse:9000
|
||||
|
||||
@@ -8,8 +8,6 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/querier"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/types/ctxtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/instrumentationtypes"
|
||||
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||
)
|
||||
|
||||
@@ -69,10 +67,6 @@ func (p *BaseSeasonalProvider) toTSResults(ctx context.Context, resp *qbtypes.Qu
|
||||
}
|
||||
|
||||
func (p *BaseSeasonalProvider) getResults(ctx context.Context, orgID valuer.UUID, params *anomalyQueryParams) (*anomalyQueryResults, error) {
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.CodeNamespace: "anomaly",
|
||||
instrumentationtypes.CodeFunctionName: "getResults",
|
||||
})
|
||||
// TODO(srikanthccv): parallelize this?
|
||||
p.logger.InfoContext(ctx, "fetching results for current period", "anomaly_current_period_query", params.CurrentPeriodQuery)
|
||||
currentPeriodResults, err := p.querier.QueryRange(ctx, orgID, ¶ms.CurrentPeriodQuery)
|
||||
|
||||
@@ -15,9 +15,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/queryparser"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/ctxtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/dashboardtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/instrumentationtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||
"github.com/SigNoz/signoz/pkg/types/roletypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
@@ -107,10 +105,6 @@ func (module *module) GetPublicDashboardSelectorsAndOrg(ctx context.Context, id
|
||||
}
|
||||
|
||||
func (module *module) GetPublicWidgetQueryRange(ctx context.Context, id valuer.UUID, widgetIdx, startTime, endTime uint64) (*querybuildertypesv5.QueryRangeResponse, error) {
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.CodeNamespace: "dashboard",
|
||||
instrumentationtypes.CodeFunctionName: "GetPublicWidgetQueryRange",
|
||||
})
|
||||
dashboard, err := module.GetDashboardByPublicID(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -10,8 +10,6 @@ import (
|
||||
v3 "github.com/SigNoz/signoz/pkg/query-service/model/v3"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/postprocess"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/utils/labels"
|
||||
"github.com/SigNoz/signoz/pkg/types/ctxtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/instrumentationtypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
@@ -63,10 +61,6 @@ func (p *BaseSeasonalProvider) getQueryParams(req *GetAnomaliesRequest) *anomaly
|
||||
}
|
||||
|
||||
func (p *BaseSeasonalProvider) getResults(ctx context.Context, orgID valuer.UUID, params *anomalyQueryParams) (*anomalyQueryResults, error) {
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.CodeNamespace: "anomaly",
|
||||
instrumentationtypes.CodeFunctionName: "getResults",
|
||||
})
|
||||
zap.L().Info("fetching results for current period", zap.Any("currentPeriodQuery", params.CurrentPeriodQuery))
|
||||
currentPeriodResults, _, err := p.querierV2.QueryRange(ctx, orgID, params.CurrentPeriodQuery)
|
||||
if err != nil {
|
||||
|
||||
@@ -37,10 +37,10 @@
|
||||
"@dnd-kit/modifiers": "7.0.0",
|
||||
"@dnd-kit/sortable": "8.0.0",
|
||||
"@dnd-kit/utilities": "3.2.2",
|
||||
"@grafana/data": "11.2.3",
|
||||
"@grafana/data": "^11.2.3",
|
||||
"@mdx-js/loader": "2.3.0",
|
||||
"@mdx-js/react": "2.3.0",
|
||||
"@monaco-editor/react": "4.3.1",
|
||||
"@monaco-editor/react": "^4.3.1",
|
||||
"@playwright/test": "1.55.1",
|
||||
"@radix-ui/react-tabs": "1.0.4",
|
||||
"@radix-ui/react-tooltip": "1.0.7",
|
||||
@@ -54,7 +54,7 @@
|
||||
"@signozhq/combobox": "0.0.2",
|
||||
"@signozhq/command": "0.0.0",
|
||||
"@signozhq/design-tokens": "2.1.1",
|
||||
"@signozhq/dialog": "0.0.2",
|
||||
"@signozhq/dialog": "^0.0.2",
|
||||
"@signozhq/icons": "0.1.0",
|
||||
"@signozhq/input": "0.0.2",
|
||||
"@signozhq/popover": "0.0.0",
|
||||
@@ -63,7 +63,7 @@
|
||||
"@signozhq/sonner": "0.1.0",
|
||||
"@signozhq/switch": "0.0.2",
|
||||
"@signozhq/table": "0.3.7",
|
||||
"@signozhq/toggle-group": "0.0.1",
|
||||
"@signozhq/toggle-group": "^0.0.1",
|
||||
"@signozhq/tooltip": "0.0.2",
|
||||
"@tanstack/react-table": "8.20.6",
|
||||
"@tanstack/react-virtual": "3.11.2",
|
||||
@@ -76,28 +76,28 @@
|
||||
"@visx/shape": "3.5.0",
|
||||
"@visx/tooltip": "3.3.0",
|
||||
"@vitejs/plugin-react": "5.1.4",
|
||||
"@xstate/react": "3.0.0",
|
||||
"@xstate/react": "^3.0.0",
|
||||
"ansi-to-html": "0.7.2",
|
||||
"antd": "5.11.0",
|
||||
"antd-table-saveas-excel": "2.2.1",
|
||||
"antlr4": "4.13.2",
|
||||
"axios": "1.12.0",
|
||||
"babel-eslint": "10.1.0",
|
||||
"babel-jest": "29.6.4",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"babel-jest": "^29.6.4",
|
||||
"babel-loader": "9.1.3",
|
||||
"babel-plugin-named-asset-import": "0.3.7",
|
||||
"babel-preset-minify": "0.5.1",
|
||||
"babel-preset-react-app": "10.0.1",
|
||||
"babel-plugin-named-asset-import": "^0.3.7",
|
||||
"babel-preset-minify": "^0.5.1",
|
||||
"babel-preset-react-app": "^10.0.1",
|
||||
"chart.js": "3.9.1",
|
||||
"chartjs-adapter-date-fns": "2.0.0",
|
||||
"chartjs-plugin-annotation": "1.4.0",
|
||||
"chartjs-adapter-date-fns": "^2.0.0",
|
||||
"chartjs-plugin-annotation": "^1.4.0",
|
||||
"classnames": "2.3.2",
|
||||
"color": "4.2.1",
|
||||
"color": "^4.2.1",
|
||||
"color-alpha": "2.0.0",
|
||||
"cross-env": "7.0.3",
|
||||
"cross-env": "^7.0.3",
|
||||
"crypto-js": "4.2.0",
|
||||
"d3-hierarchy": "3.1.2",
|
||||
"dayjs": "1.10.7",
|
||||
"dayjs": "^1.10.7",
|
||||
"dompurify": "3.2.4",
|
||||
"dotenv": "8.2.0",
|
||||
"event-source-polyfill": "1.0.31",
|
||||
@@ -106,19 +106,19 @@
|
||||
"history": "4.10.1",
|
||||
"http-proxy-middleware": "3.0.5",
|
||||
"http-status-codes": "2.3.0",
|
||||
"i18next": "21.6.12",
|
||||
"i18next-browser-languagedetector": "6.1.3",
|
||||
"i18next-http-backend": "1.3.2",
|
||||
"i18next": "^21.6.12",
|
||||
"i18next-browser-languagedetector": "^6.1.3",
|
||||
"i18next-http-backend": "^1.3.2",
|
||||
"immer": "11.1.3",
|
||||
"jest": "30.2.0",
|
||||
"js-base64": "3.7.2",
|
||||
"lodash-es": "4.17.21",
|
||||
"js-base64": "^3.7.2",
|
||||
"lodash-es": "^4.17.21",
|
||||
"lucide-react": "0.498.0",
|
||||
"mini-css-extract-plugin": "2.4.5",
|
||||
"motion": "12.4.13",
|
||||
"nuqs": "2.8.8",
|
||||
"overlayscrollbars": "2.8.1",
|
||||
"overlayscrollbars-react": "0.5.6",
|
||||
"overlayscrollbars": "^2.8.1",
|
||||
"overlayscrollbars-react": "^0.5.6",
|
||||
"papaparse": "5.4.1",
|
||||
"posthog-js": "1.298.0",
|
||||
"rc-tween-one": "3.0.6",
|
||||
@@ -130,36 +130,36 @@
|
||||
"react-dom": "18.2.0",
|
||||
"react-drag-listview": "2.0.0",
|
||||
"react-error-boundary": "4.0.11",
|
||||
"react-force-graph-2d": "1.29.1",
|
||||
"react-force-graph-2d": "^1.29.1",
|
||||
"react-full-screen": "1.1.1",
|
||||
"react-grid-layout": "1.3.4",
|
||||
"react-grid-layout": "^1.3.4",
|
||||
"react-helmet-async": "1.3.0",
|
||||
"react-i18next": "11.16.1",
|
||||
"react-i18next": "^11.16.1",
|
||||
"react-lottie": "1.2.10",
|
||||
"react-markdown": "8.0.7",
|
||||
"react-query": "3.39.3",
|
||||
"react-redux": "7.2.2",
|
||||
"react-router-dom": "5.2.0",
|
||||
"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-use": "^17.3.2",
|
||||
"react-virtuoso": "4.0.3",
|
||||
"redux": "4.0.5",
|
||||
"redux-thunk": "2.3.0",
|
||||
"redux": "^4.0.5",
|
||||
"redux-thunk": "^2.3.0",
|
||||
"rehype-raw": "7.0.0",
|
||||
"rollup-plugin-visualizer": "7.0.0",
|
||||
"rrule": "2.8.1",
|
||||
"stream": "0.0.2",
|
||||
"styled-components": "5.3.11",
|
||||
"timestamp-nano": "1.0.0",
|
||||
"ts-node": "10.2.1",
|
||||
"stream": "^0.0.2",
|
||||
"styled-components": "^5.3.11",
|
||||
"timestamp-nano": "^1.0.0",
|
||||
"ts-node": "^10.2.1",
|
||||
"typescript": "5.9.3",
|
||||
"uplot": "1.6.31",
|
||||
"uuid": "8.3.2",
|
||||
"uuid": "^8.3.2",
|
||||
"vite": "npm:rolldown-vite@7.3.1",
|
||||
"vite-plugin-html": "3.2.2",
|
||||
"web-vitals": "0.2.4",
|
||||
"xstate": "4.31.0",
|
||||
"web-vitals": "^0.2.4",
|
||||
"xstate": "^4.31.0",
|
||||
"zustand": "5.0.11"
|
||||
},
|
||||
"browserslist": {
|
||||
@@ -175,70 +175,69 @@
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.22.11",
|
||||
"@babel/plugin-proposal-class-properties": "7.18.6",
|
||||
"@babel/plugin-syntax-jsx": "7.12.13",
|
||||
"@babel/preset-env": "7.22.14",
|
||||
"@babel/preset-react": "7.12.13",
|
||||
"@babel/preset-typescript": "7.21.4",
|
||||
"@commitlint/cli": "20.4.2",
|
||||
"@commitlint/config-conventional": "20.4.2",
|
||||
"@babel/core": "^7.22.11",
|
||||
"@babel/plugin-proposal-class-properties": "^7.18.6",
|
||||
"@babel/plugin-syntax-jsx": "^7.12.13",
|
||||
"@babel/preset-env": "^7.22.14",
|
||||
"@babel/preset-react": "^7.12.13",
|
||||
"@babel/preset-typescript": "^7.21.4",
|
||||
"@commitlint/cli": "^20.4.2",
|
||||
"@commitlint/config-conventional": "^20.4.2",
|
||||
"@faker-js/faker": "9.3.0",
|
||||
"@jest/globals": "30.2.0",
|
||||
"@testing-library/jest-dom": "5.16.5",
|
||||
"@testing-library/react": "13.4.0",
|
||||
"@testing-library/user-event": "14.4.3",
|
||||
"@types/color": "3.0.3",
|
||||
"@types/color": "^3.0.3",
|
||||
"@types/crypto-js": "4.2.2",
|
||||
"@types/dompurify": "2.4.0",
|
||||
"@types/event-source-polyfill": "1.0.0",
|
||||
"@types/dompurify": "^2.4.0",
|
||||
"@types/event-source-polyfill": "^1.0.0",
|
||||
"@types/fontfaceobserver": "2.1.0",
|
||||
"@types/jest": "30.0.0",
|
||||
"@types/lodash-es": "4.17.4",
|
||||
"@types/mini-css-extract-plugin": "2.5.1",
|
||||
"@types/node": "16.10.3",
|
||||
"@types/lodash-es": "^4.17.4",
|
||||
"@types/mini-css-extract-plugin": "^2.5.1",
|
||||
"@types/node": "^16.10.3",
|
||||
"@types/papaparse": "5.3.7",
|
||||
"@types/react": "18.0.26",
|
||||
"@types/react-addons-update": "0.14.21",
|
||||
"@types/react-beautiful-dnd": "13.1.8",
|
||||
"@types/react-dom": "18.0.10",
|
||||
"@types/react-grid-layout": "1.1.2",
|
||||
"@types/react-grid-layout": "^1.1.2",
|
||||
"@types/react-helmet-async": "1.0.3",
|
||||
"@types/react-lottie": "1.2.10",
|
||||
"@types/react-redux": "7.1.11",
|
||||
"@types/react-redux": "^7.1.11",
|
||||
"@types/react-resizable": "3.0.3",
|
||||
"@types/react-router-dom": "5.1.6",
|
||||
"@types/react-router-dom": "^5.1.6",
|
||||
"@types/react-syntax-highlighter": "15.5.13",
|
||||
"@types/redux-mock-store": "1.0.4",
|
||||
"@types/styled-components": "5.1.4",
|
||||
"@types/uuid": "8.3.1",
|
||||
"@typescript-eslint/eslint-plugin": "4.33.0",
|
||||
"@typescript-eslint/parser": "4.33.0",
|
||||
"@types/styled-components": "^5.1.4",
|
||||
"@types/uuid": "^8.3.1",
|
||||
"@typescript-eslint/eslint-plugin": "^4.33.0",
|
||||
"@typescript-eslint/parser": "^4.33.0",
|
||||
"autoprefixer": "10.4.19",
|
||||
"babel-plugin-styled-components": "1.12.0",
|
||||
"eslint": "7.32.0",
|
||||
"eslint-config-prettier": "8.3.0",
|
||||
"eslint-plugin-import": "2.28.1",
|
||||
"eslint-plugin-jest": "29.15.0",
|
||||
"eslint-plugin-jsx-a11y": "6.5.1",
|
||||
"eslint-plugin-prettier": "4.0.0",
|
||||
"eslint-plugin-react": "7.24.0",
|
||||
"eslint-plugin-react-hooks": "4.3.0",
|
||||
"eslint-plugin-simple-import-sort": "7.0.0",
|
||||
"eslint-plugin-sonarjs": "0.12.0",
|
||||
"glob": "13.0.6",
|
||||
"husky": "7.0.4",
|
||||
"imagemin": "8.0.1",
|
||||
"imagemin-svgo": "10.0.1",
|
||||
"is-ci": "3.0.1",
|
||||
"babel-plugin-styled-components": "^1.12.0",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-plugin-import": "^2.28.1",
|
||||
"eslint-plugin-jest": "^29.15.0",
|
||||
"eslint-plugin-jsx-a11y": "^6.5.1",
|
||||
"eslint-plugin-prettier": "^4.0.0",
|
||||
"eslint-plugin-react": "^7.24.0",
|
||||
"eslint-plugin-react-hooks": "^4.3.0",
|
||||
"eslint-plugin-simple-import-sort": "^7.0.0",
|
||||
"eslint-plugin-sonarjs": "^0.12.0",
|
||||
"husky": "^7.0.4",
|
||||
"imagemin": "^8.0.1",
|
||||
"imagemin-svgo": "^10.0.1",
|
||||
"is-ci": "^3.0.1",
|
||||
"jest-environment-jsdom": "29.7.0",
|
||||
"jest-environment-node": "29.7.0",
|
||||
"jest-styled-components": "7.2.0",
|
||||
"lint-staged": "12.5.0",
|
||||
"jest-styled-components": "^7.2.0",
|
||||
"lint-staged": "^12.5.0",
|
||||
"msw": "1.3.2",
|
||||
"npm-run-all": "latest",
|
||||
"orval": "7.18.0",
|
||||
"portfinder-sync": "0.0.2",
|
||||
"portfinder-sync": "^0.0.2",
|
||||
"postcss": "8.5.6",
|
||||
"prettier": "2.2.1",
|
||||
"prop-types": "15.8.1",
|
||||
@@ -250,7 +249,7 @@
|
||||
"svgo": "4.0.0",
|
||||
"ts-api-utils": "2.4.0",
|
||||
"ts-jest": "29.4.6",
|
||||
"ts-node": "10.2.1",
|
||||
"ts-node": "^10.2.1",
|
||||
"typescript-plugin-css-modules": "5.2.0",
|
||||
"vite-plugin-checker": "0.12.0",
|
||||
"vite-plugin-compression": "0.5.1",
|
||||
@@ -269,40 +268,18 @@
|
||||
"debug": "4.3.4",
|
||||
"semver": "7.5.4",
|
||||
"xml2js": "0.5.0",
|
||||
"phin": "3.7.1",
|
||||
"phin": "^3.7.1",
|
||||
"body-parser": "1.20.3",
|
||||
"http-proxy-middleware": "3.0.5",
|
||||
"cross-spawn": "7.0.5",
|
||||
"cookie": "0.7.1",
|
||||
"cookie": "^0.7.1",
|
||||
"serialize-javascript": "6.0.2",
|
||||
"prismjs": "1.30.0",
|
||||
"got": "11.8.5",
|
||||
"form-data": "4.0.4",
|
||||
"brace-expansion": "2.0.2",
|
||||
"on-headers": "1.1.0",
|
||||
"brace-expansion": "^2.0.2",
|
||||
"on-headers": "^1.1.0",
|
||||
"tmp": "0.2.4",
|
||||
"vite": "npm:rolldown-vite@7.3.1"
|
||||
},
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
"@types/react": "18.0.26",
|
||||
"@types/react-dom": "18.0.10",
|
||||
"debug": "4.3.4",
|
||||
"semver": "7.5.4",
|
||||
"xml2js": "0.5.0",
|
||||
"phin": "3.7.1",
|
||||
"body-parser": "1.20.3",
|
||||
"http-proxy-middleware": "3.0.5",
|
||||
"cross-spawn": "7.0.5",
|
||||
"cookie": "0.7.1",
|
||||
"serialize-javascript": "6.0.2",
|
||||
"prismjs": "1.30.0",
|
||||
"got": "11.8.5",
|
||||
"form-data": "4.0.4",
|
||||
"brace-expansion": "2.0.2",
|
||||
"on-headers": "1.1.0",
|
||||
"tmp": "0.2.4",
|
||||
"vite": "npm:rolldown-vite@7.3.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
24227
frontend/pnpm-lock.yaml
generated
24227
frontend/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,6 @@
|
||||
import axios from 'api';
|
||||
import { AxiosResponse } from 'axios';
|
||||
import store from 'store';
|
||||
import {
|
||||
QueryKeyRequestProps,
|
||||
QueryKeySuggestionsResponseProps,
|
||||
@@ -17,6 +18,12 @@ export const getKeySuggestions = (
|
||||
signalSource = '',
|
||||
} = props;
|
||||
|
||||
const { globalTime } = store.getState();
|
||||
const resolvedTimeRange = {
|
||||
startUnixMilli: Math.floor(globalTime.minTime / 1000000),
|
||||
endUnixMilli: Math.floor(globalTime.maxTime / 1000000),
|
||||
};
|
||||
|
||||
const encodedSignal = encodeURIComponent(signal);
|
||||
const encodedSearchText = encodeURIComponent(searchText);
|
||||
const encodedMetricName = encodeURIComponent(metricName);
|
||||
@@ -24,7 +31,14 @@ export const getKeySuggestions = (
|
||||
const encodedFieldDataType = encodeURIComponent(fieldDataType);
|
||||
const encodedSource = encodeURIComponent(signalSource);
|
||||
|
||||
return axios.get(
|
||||
`/fields/keys?signal=${encodedSignal}&searchText=${encodedSearchText}&metricName=${encodedMetricName}&fieldContext=${encodedFieldContext}&fieldDataType=${encodedFieldDataType}&source=${encodedSource}`,
|
||||
);
|
||||
let url = `/fields/keys?signal=${encodedSignal}&searchText=${encodedSearchText}&metricName=${encodedMetricName}&fieldContext=${encodedFieldContext}&fieldDataType=${encodedFieldDataType}&source=${encodedSource}`;
|
||||
|
||||
if (resolvedTimeRange.startUnixMilli !== undefined) {
|
||||
url += `&startUnixMilli=${resolvedTimeRange.startUnixMilli}`;
|
||||
}
|
||||
if (resolvedTimeRange.endUnixMilli !== undefined) {
|
||||
url += `&endUnixMilli=${resolvedTimeRange.endUnixMilli}`;
|
||||
}
|
||||
|
||||
return axios.get(url);
|
||||
};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import axios from 'api';
|
||||
import { AxiosResponse } from 'axios';
|
||||
import store from 'store';
|
||||
import {
|
||||
QueryKeyValueRequestProps,
|
||||
QueryKeyValueSuggestionsResponseProps,
|
||||
@@ -8,7 +9,20 @@ import {
|
||||
export const getValueSuggestions = (
|
||||
props: QueryKeyValueRequestProps,
|
||||
): Promise<AxiosResponse<QueryKeyValueSuggestionsResponseProps>> => {
|
||||
const { signal, key, searchText, signalSource, metricName } = props;
|
||||
const {
|
||||
signal,
|
||||
key,
|
||||
searchText,
|
||||
signalSource,
|
||||
metricName,
|
||||
existingQuery,
|
||||
} = props;
|
||||
|
||||
const { globalTime } = store.getState();
|
||||
const resolvedTimeRange = {
|
||||
startUnixMilli: Math.floor(globalTime.minTime / 1000000),
|
||||
endUnixMilli: Math.floor(globalTime.maxTime / 1000000),
|
||||
};
|
||||
|
||||
const encodedSignal = encodeURIComponent(signal);
|
||||
const encodedKey = encodeURIComponent(key);
|
||||
@@ -16,7 +30,17 @@ export const getValueSuggestions = (
|
||||
const encodedSearchText = encodeURIComponent(searchText);
|
||||
const encodedSource = encodeURIComponent(signalSource || '');
|
||||
|
||||
return axios.get(
|
||||
`/fields/values?signal=${encodedSignal}&name=${encodedKey}&searchText=${encodedSearchText}&metricName=${encodedMetricName}&source=${encodedSource}`,
|
||||
);
|
||||
let url = `/fields/values?signal=${encodedSignal}&name=${encodedKey}&searchText=${encodedSearchText}&metricName=${encodedMetricName}&source=${encodedSource}`;
|
||||
|
||||
if (resolvedTimeRange.startUnixMilli !== undefined) {
|
||||
url += `&startUnixMilli=${resolvedTimeRange.startUnixMilli}`;
|
||||
}
|
||||
if (resolvedTimeRange.endUnixMilli !== undefined) {
|
||||
url += `&endUnixMilli=${resolvedTimeRange.endUnixMilli}`;
|
||||
}
|
||||
if (existingQuery) {
|
||||
url += `&existingQuery=${encodeURIComponent(existingQuery)}`;
|
||||
}
|
||||
|
||||
return axios.get(url);
|
||||
};
|
||||
|
||||
@@ -1,279 +0,0 @@
|
||||
import { useState } from 'react';
|
||||
import { fireEvent, render, screen } from '@testing-library/react';
|
||||
import dayjs from 'dayjs';
|
||||
import * as timeUtils from 'utils/timeUtils';
|
||||
|
||||
import CustomTimePicker from './CustomTimePicker';
|
||||
|
||||
jest.mock('react-router-dom', () => {
|
||||
const actual = jest.requireActual('react-router-dom');
|
||||
|
||||
return {
|
||||
...actual,
|
||||
useLocation: jest.fn().mockReturnValue({
|
||||
pathname: '/test-path',
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock('providers/Timezone', () => {
|
||||
const actual = jest.requireActual('providers/Timezone');
|
||||
|
||||
return {
|
||||
...actual,
|
||||
useTimezone: jest.fn().mockReturnValue({
|
||||
timezone: {
|
||||
value: 'UTC',
|
||||
offset: '+00:00',
|
||||
name: 'UTC',
|
||||
},
|
||||
browserTimezone: {
|
||||
value: 'UTC',
|
||||
offset: '+00:00',
|
||||
name: 'UTC',
|
||||
},
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
interface WrapperProps {
|
||||
initialValue?: string;
|
||||
showLiveLogs?: boolean;
|
||||
onValidCustomDateChange?: () => void;
|
||||
onError?: () => void;
|
||||
onSelect?: (value: string) => void;
|
||||
onCustomDateHandler?: () => void;
|
||||
onCustomTimeStatusUpdate?: () => void;
|
||||
}
|
||||
|
||||
function Wrapper({
|
||||
initialValue = '2024-01-01 00:00:00 - 2024-01-01 01:00:00',
|
||||
showLiveLogs = false,
|
||||
onValidCustomDateChange = (): void => {},
|
||||
onError = (): void => {},
|
||||
onSelect = (): void => {},
|
||||
onCustomDateHandler = (): void => {},
|
||||
onCustomTimeStatusUpdate = (): void => {},
|
||||
}: WrapperProps): JSX.Element {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [selectedTime, setSelectedTime] = useState('custom');
|
||||
const [selectedValue, setSelectedValue] = useState(initialValue);
|
||||
|
||||
const handleSelect = (value: string): void => {
|
||||
setSelectedTime(value);
|
||||
onSelect(value);
|
||||
};
|
||||
|
||||
return (
|
||||
<CustomTimePicker
|
||||
open={open}
|
||||
setOpen={setOpen}
|
||||
onSelect={handleSelect}
|
||||
onError={onError}
|
||||
selectedTime={selectedTime}
|
||||
selectedValue={selectedValue}
|
||||
onValidCustomDateChange={({ timeStr }): void => {
|
||||
setSelectedValue(timeStr);
|
||||
onValidCustomDateChange();
|
||||
}}
|
||||
onCustomDateHandler={(): void => {
|
||||
onCustomDateHandler();
|
||||
}}
|
||||
onCustomTimeStatusUpdate={(): void => {
|
||||
onCustomTimeStatusUpdate();
|
||||
}}
|
||||
items={[
|
||||
{ label: 'Last 5 minutes', value: '5m' },
|
||||
{ label: 'Custom', value: 'custom' },
|
||||
]}
|
||||
minTime={dayjs('2024-01-01 00:00:00').valueOf() * 1000_000}
|
||||
maxTime={dayjs('2024-01-01 01:00:00').valueOf() * 1000_000}
|
||||
showLiveLogs={showLiveLogs}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
describe('CustomTimePicker', () => {
|
||||
it('does not close or reset when clicking input while open', () => {
|
||||
render(<Wrapper />);
|
||||
|
||||
const input = screen.getByRole('textbox');
|
||||
|
||||
// Open popover
|
||||
fireEvent.focus(input);
|
||||
|
||||
// Type some text
|
||||
fireEvent.change(input, { target: { value: '5m' } });
|
||||
|
||||
// Click the input again while open
|
||||
fireEvent.mouseDown(input);
|
||||
fireEvent.click(input);
|
||||
|
||||
// Value should remain as typed
|
||||
expect((input as HTMLInputElement).value).toBe('5m');
|
||||
});
|
||||
|
||||
it('applies valid shorthand on Enter', () => {
|
||||
const onValid = jest.fn();
|
||||
const onError = jest.fn();
|
||||
|
||||
render(<Wrapper onValidCustomDateChange={onValid} onError={onError} />);
|
||||
|
||||
const input = screen.getByRole('textbox');
|
||||
|
||||
fireEvent.focus(input);
|
||||
fireEvent.change(input, { target: { value: '5m' } });
|
||||
fireEvent.keyDown(input, { key: 'Enter', code: 'Enter' });
|
||||
|
||||
expect(onValid).toHaveBeenCalledTimes(1);
|
||||
// onError(false) may be called by internal reset logic; we only assert that
|
||||
// it was never called with a truthy error state
|
||||
expect(onError).not.toHaveBeenCalledWith(true);
|
||||
});
|
||||
|
||||
it('sets error and updates custom time status for invalid shorthand exceeding max allowed window', () => {
|
||||
const onValid = jest.fn();
|
||||
const onError = jest.fn();
|
||||
const onCustomTimeStatusUpdate = jest.fn();
|
||||
|
||||
render(
|
||||
<Wrapper
|
||||
onValidCustomDateChange={onValid}
|
||||
onError={onError}
|
||||
onCustomTimeStatusUpdate={onCustomTimeStatusUpdate}
|
||||
/>,
|
||||
);
|
||||
|
||||
const input = screen.getByRole('textbox');
|
||||
|
||||
fireEvent.focus(input);
|
||||
// large number of days to ensure it exceeds the 15 months allowed window
|
||||
fireEvent.change(input, { target: { value: '9999d' } });
|
||||
fireEvent.keyDown(input, { key: 'Enter', code: 'Enter' });
|
||||
|
||||
expect(onError).toHaveBeenCalledWith(true);
|
||||
expect(onCustomTimeStatusUpdate).toHaveBeenCalledWith();
|
||||
expect(onValid).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('treats close after change like pressing Enter (blur + chevron)', () => {
|
||||
const onValid = jest.fn();
|
||||
const onError = jest.fn();
|
||||
|
||||
render(<Wrapper onValidCustomDateChange={onValid} onError={onError} />);
|
||||
|
||||
const input = screen.getByRole('textbox');
|
||||
|
||||
// Open and change value so "changed since open" is true
|
||||
fireEvent.focus(input);
|
||||
fireEvent.change(input, { target: { value: '5m' } });
|
||||
fireEvent.blur(input);
|
||||
|
||||
// Click the chevron (which triggers handleClose)
|
||||
const chevron = document.querySelector(
|
||||
'.time-input-suffix-icon-badge',
|
||||
) as HTMLElement;
|
||||
|
||||
fireEvent.click(chevron);
|
||||
|
||||
// Should have applied the value (same as Enter)
|
||||
expect(onValid).toHaveBeenCalledTimes(1);
|
||||
expect(onError).not.toHaveBeenCalledWith(true);
|
||||
});
|
||||
|
||||
it('applies epoch start/end range on Enter via onCustomDateHandler', () => {
|
||||
const onCustomDateHandler = jest.fn();
|
||||
const onError = jest.fn();
|
||||
|
||||
render(
|
||||
<Wrapper onCustomDateHandler={onCustomDateHandler} onError={onError} />,
|
||||
);
|
||||
|
||||
const now = dayjs().valueOf();
|
||||
const later = dayjs().add(1, 'hour').valueOf();
|
||||
const input = screen.getByRole('textbox');
|
||||
|
||||
fireEvent.focus(input);
|
||||
fireEvent.change(input, {
|
||||
target: { value: `${now} - ${later}` },
|
||||
});
|
||||
fireEvent.keyDown(input, { key: 'Enter', code: 'Enter' });
|
||||
|
||||
expect(onCustomDateHandler).toHaveBeenCalledTimes(1);
|
||||
expect(onError).not.toHaveBeenCalledWith(true);
|
||||
});
|
||||
|
||||
it('uses validateTimeRange result for generic formatted ranges (valid case)', () => {
|
||||
const validateTimeRangeSpy = jest.spyOn(timeUtils, 'validateTimeRange');
|
||||
const onCustomDateHandler = jest.fn();
|
||||
const onError = jest.fn();
|
||||
|
||||
validateTimeRangeSpy.mockReturnValue({
|
||||
isValid: true,
|
||||
errorDetails: undefined,
|
||||
startTimeMs: dayjs('2024-01-01 00:00:00').valueOf(),
|
||||
endTimeMs: dayjs('2024-01-01 01:00:00').valueOf(),
|
||||
});
|
||||
|
||||
render(
|
||||
<Wrapper onCustomDateHandler={onCustomDateHandler} onError={onError} />,
|
||||
);
|
||||
|
||||
const input = screen.getByRole('textbox');
|
||||
|
||||
fireEvent.focus(input);
|
||||
fireEvent.change(input, {
|
||||
target: { value: 'foo - bar' },
|
||||
});
|
||||
fireEvent.keyDown(input, { key: 'Enter', code: 'Enter' });
|
||||
|
||||
expect(validateTimeRangeSpy).toHaveBeenCalled();
|
||||
expect(onCustomDateHandler).toHaveBeenCalledTimes(1);
|
||||
expect(onError).not.toHaveBeenCalledWith(true);
|
||||
|
||||
validateTimeRangeSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('uses validateTimeRange result for generic formatted ranges (invalid case)', () => {
|
||||
const validateTimeRangeSpy = jest.spyOn(timeUtils, 'validateTimeRange');
|
||||
const onValid = jest.fn();
|
||||
const onError = jest.fn();
|
||||
|
||||
validateTimeRangeSpy.mockReturnValue({
|
||||
isValid: false,
|
||||
errorDetails: {
|
||||
message: 'Invalid range',
|
||||
code: 'INVALID_RANGE',
|
||||
description: 'Start must be before end',
|
||||
},
|
||||
startTimeMs: 0,
|
||||
endTimeMs: 0,
|
||||
});
|
||||
|
||||
render(<Wrapper onValidCustomDateChange={onValid} onError={onError} />);
|
||||
|
||||
const input = screen.getByRole('textbox');
|
||||
|
||||
fireEvent.focus(input);
|
||||
fireEvent.change(input, {
|
||||
target: { value: 'foo - bar' },
|
||||
});
|
||||
fireEvent.keyDown(input, { key: 'Enter', code: 'Enter' });
|
||||
|
||||
expect(validateTimeRangeSpy).toHaveBeenCalled();
|
||||
expect(onError).toHaveBeenCalledWith(true);
|
||||
expect(onValid).not.toHaveBeenCalled();
|
||||
|
||||
validateTimeRangeSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('opens live mode with correct label', () => {
|
||||
render(<Wrapper showLiveLogs />);
|
||||
|
||||
const input = screen.getByRole('textbox');
|
||||
|
||||
fireEvent.focus(input);
|
||||
|
||||
expect((input as HTMLInputElement).value).toBe('Live');
|
||||
});
|
||||
});
|
||||
@@ -104,10 +104,6 @@ function CustomTimePicker({
|
||||
const location = useLocation();
|
||||
|
||||
const inputRef = useRef<InputRef>(null);
|
||||
const initialInputValueOnOpenRef = useRef<string>('');
|
||||
const hasChangedSinceOpenRef = useRef<boolean>(false);
|
||||
// Tracks if the last pointer down was on the input so we don't close the popover when user clicks the input again
|
||||
const isClickFromInputRef = useRef(false);
|
||||
|
||||
const [activeView, setActiveView] = useState<ViewType>(DEFAULT_VIEW);
|
||||
|
||||
@@ -242,21 +238,6 @@ function CustomTimePicker({
|
||||
};
|
||||
|
||||
const handleOpenChange = (newOpen: boolean): void => {
|
||||
// Don't close when the user clicked the input (trigger); Ant Design treats trigger as "outside" overlay
|
||||
if (!newOpen && isClickFromInputRef.current) {
|
||||
isClickFromInputRef.current = false;
|
||||
return;
|
||||
}
|
||||
isClickFromInputRef.current = false;
|
||||
|
||||
// If the popover is trying to close and the value changed since opening,
|
||||
// treat it as if the user pressed Enter (attempt to apply the value)
|
||||
if (!newOpen && hasChangedSinceOpenRef.current) {
|
||||
hasChangedSinceOpenRef.current = false;
|
||||
handleInputPressEnter();
|
||||
return;
|
||||
}
|
||||
|
||||
setOpen(newOpen);
|
||||
|
||||
if (!newOpen) {
|
||||
@@ -425,18 +406,10 @@ function CustomTimePicker({
|
||||
const handleOpen = (e?: React.SyntheticEvent): void => {
|
||||
e?.stopPropagation?.();
|
||||
|
||||
// If the popover is already open, avoid resetting the input value
|
||||
// so that any in-progress edits are preserved.
|
||||
if (open) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (showLiveLogs) {
|
||||
setOpen(true);
|
||||
setSelectedTimePlaceholderValue('Live');
|
||||
setInputValue('Live');
|
||||
initialInputValueOnOpenRef.current = 'Live';
|
||||
hasChangedSinceOpenRef.current = false;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -451,21 +424,11 @@ function CustomTimePicker({
|
||||
.tz(timezone.value)
|
||||
.format(DATE_TIME_FORMATS.UK_DATETIME_SECONDS);
|
||||
|
||||
const nextValue = `${startTime} - ${endTime}`;
|
||||
setInputValue(nextValue);
|
||||
initialInputValueOnOpenRef.current = nextValue;
|
||||
hasChangedSinceOpenRef.current = false;
|
||||
setInputValue(`${startTime} - ${endTime}`);
|
||||
};
|
||||
|
||||
const handleClose = (e: React.MouseEvent): void => {
|
||||
e.stopPropagation();
|
||||
// If the value changed since opening, treat this like pressing Enter
|
||||
if (hasChangedSinceOpenRef.current) {
|
||||
hasChangedSinceOpenRef.current = false;
|
||||
handleInputPressEnter();
|
||||
return;
|
||||
}
|
||||
|
||||
setOpen(false);
|
||||
setCustomDTPickerVisible?.(false);
|
||||
|
||||
@@ -487,9 +450,6 @@ function CustomTimePicker({
|
||||
}, [location.pathname]);
|
||||
|
||||
const handleInputBlur = (): void => {
|
||||
// Track whether the value was changed since the input was opened for editing
|
||||
hasChangedSinceOpenRef.current =
|
||||
inputValue !== initialInputValueOnOpenRef.current;
|
||||
resetErrorStatus();
|
||||
};
|
||||
|
||||
@@ -592,12 +552,6 @@ function CustomTimePicker({
|
||||
readOnly={!open || showLiveLogs}
|
||||
placeholder={selectedTimePlaceholderValue}
|
||||
value={inputValue}
|
||||
onMouseDown={(e): void => {
|
||||
// Only treat as "click from input" when the actual input element is clicked (not suffix/chevron)
|
||||
if (e.target === inputRef.current?.input) {
|
||||
isClickFromInputRef.current = true;
|
||||
}
|
||||
}}
|
||||
onFocus={handleOpen}
|
||||
onClick={handleOpen}
|
||||
onChange={handleInputChange}
|
||||
|
||||
@@ -272,7 +272,6 @@ function QuerySearch({
|
||||
metricName: debouncedMetricName ?? undefined,
|
||||
signalSource: signalSource as 'meter' | '',
|
||||
});
|
||||
|
||||
if (response.data.data) {
|
||||
const { keys } = response.data.data;
|
||||
const options = generateOptions(keys);
|
||||
@@ -432,6 +431,7 @@ function QuerySearch({
|
||||
}
|
||||
|
||||
const sanitizedSearchText = searchText ? searchText?.trim() : '';
|
||||
const existingQuery = queryData.filter?.expression || '';
|
||||
|
||||
try {
|
||||
const response = await getValueSuggestions({
|
||||
@@ -440,9 +440,9 @@ function QuerySearch({
|
||||
signal: dataSource,
|
||||
signalSource: signalSource as 'meter' | '',
|
||||
metricName: debouncedMetricName ?? undefined,
|
||||
});
|
||||
existingQuery,
|
||||
}); // Skip updates if component unmounted or key changed
|
||||
|
||||
// Skip updates if component unmounted or key changed
|
||||
if (
|
||||
!isMountedRef.current ||
|
||||
lastKeyRef.current !== key ||
|
||||
@@ -454,7 +454,9 @@ function QuerySearch({
|
||||
// Process the response data
|
||||
const responseData = response.data as any;
|
||||
const values = responseData.data?.values || {};
|
||||
const stringValues = values.stringValues || [];
|
||||
const relatedValues = values.relatedValues || [];
|
||||
const stringValues =
|
||||
relatedValues.length > 0 ? relatedValues : values.stringValues || [];
|
||||
const numberValues = values.numberValues || [];
|
||||
|
||||
// Generate options from string values - explicitly handle empty strings
|
||||
@@ -529,11 +531,12 @@ function QuerySearch({
|
||||
},
|
||||
[
|
||||
activeKey,
|
||||
dataSource,
|
||||
isLoadingSuggestions,
|
||||
debouncedMetricName,
|
||||
signalSource,
|
||||
queryData.filter?.expression,
|
||||
toggleSuggestions,
|
||||
dataSource,
|
||||
signalSource,
|
||||
debouncedMetricName,
|
||||
],
|
||||
);
|
||||
|
||||
@@ -1240,19 +1243,17 @@ function QuerySearch({
|
||||
if (!queryContext) {
|
||||
return;
|
||||
}
|
||||
// Trigger suggestions based on context
|
||||
if (editorRef.current) {
|
||||
// Only trigger suggestions and fetch if editor is focused (i.e., user is interacting)
|
||||
if (isFocused && editorRef.current) {
|
||||
toggleSuggestions(10);
|
||||
}
|
||||
|
||||
// Handle value suggestions for value context
|
||||
if (queryContext.isInValue) {
|
||||
const { keyToken, currentToken } = queryContext;
|
||||
const key = keyToken || currentToken;
|
||||
|
||||
// Only fetch if needed and if we have a valid key
|
||||
if (key && key !== activeKey && !isLoadingSuggestions) {
|
||||
fetchValueSuggestions({ key });
|
||||
// Handle value suggestions for value context
|
||||
if (queryContext.isInValue) {
|
||||
const { keyToken, currentToken } = queryContext;
|
||||
const key = keyToken || currentToken;
|
||||
// Only fetch if needed and if we have a valid key
|
||||
if (key && key !== activeKey && !isLoadingSuggestions) {
|
||||
fetchValueSuggestions({ key });
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [
|
||||
@@ -1261,6 +1262,7 @@ function QuerySearch({
|
||||
isLoadingSuggestions,
|
||||
activeKey,
|
||||
fetchValueSuggestions,
|
||||
isFocused,
|
||||
]);
|
||||
|
||||
const getTooltipContent = (): JSX.Element => (
|
||||
|
||||
@@ -48,7 +48,12 @@
|
||||
.filter-separator {
|
||||
height: 1px;
|
||||
background-color: var(--bg-slate-400);
|
||||
margin: 4px 0;
|
||||
margin: 7px 0;
|
||||
|
||||
&.related-separator {
|
||||
opacity: 0.5;
|
||||
margin: 0.5px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.value {
|
||||
@@ -138,6 +143,93 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.search-prompt {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
padding: 10px 12px;
|
||||
margin-top: 4px;
|
||||
border: 1px dashed var(--bg-amber-500);
|
||||
border-radius: 10px;
|
||||
color: var(--bg-amber-200);
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
var(--bg-ink-500) 0%,
|
||||
var(--bg-ink-400) 100%
|
||||
);
|
||||
cursor: pointer;
|
||||
transition: all 0.16s ease, transform 0.12s ease;
|
||||
text-align: left;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.35);
|
||||
|
||||
&:hover {
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
var(--bg-ink-400) 0%,
|
||||
var(--bg-ink-300) 100%
|
||||
);
|
||||
box-shadow: 0 4px 18px rgba(0, 0, 0, 0.45);
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: translateY(1px);
|
||||
}
|
||||
|
||||
&__icon {
|
||||
color: var(--bg-amber-400);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
&__text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
&__title {
|
||||
color: var(--bg-amber-200);
|
||||
}
|
||||
|
||||
&__subtitle {
|
||||
color: var(--bg-amber-300);
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
.lightMode & {
|
||||
.search-prompt {
|
||||
border: 1px dashed var(--bg-amber-500);
|
||||
color: var(--bg-amber-800);
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
var(--bg-vanilla-200) 0%,
|
||||
var(--bg-vanilla-100) 100%
|
||||
);
|
||||
box-shadow: 0 2px 12px rgba(184, 107, 0, 0.08);
|
||||
|
||||
&:hover {
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
var(--bg-vanilla-100) 0%,
|
||||
var(--bg-vanilla-50) 100%
|
||||
);
|
||||
box-shadow: 0 4px 16px rgba(184, 107, 0, 0.15);
|
||||
}
|
||||
|
||||
&__icon {
|
||||
color: var(--bg-amber-600);
|
||||
}
|
||||
|
||||
&__title {
|
||||
color: var(--bg-amber-800);
|
||||
}
|
||||
|
||||
&__subtitle {
|
||||
color: var(--bg-amber-800);
|
||||
}
|
||||
}
|
||||
}
|
||||
.go-to-docs {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -150,7 +150,8 @@ describe('CheckboxFilter - User Flows', () => {
|
||||
// User should see the filter is automatically opened (not collapsed)
|
||||
expect(screen.getByText('Service Name')).toBeInTheDocument();
|
||||
await waitFor(() => {
|
||||
expect(screen.getByPlaceholderText('Filter values')).toBeInTheDocument();
|
||||
// eslint-disable-next-line sonarjs/no-duplicate-string
|
||||
expect(screen.getByPlaceholderText('Search values')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// User should see visual separator between checked and unchecked items
|
||||
@@ -184,7 +185,7 @@ describe('CheckboxFilter - User Flows', () => {
|
||||
|
||||
// Initially auto-opened due to active filters
|
||||
await waitFor(() => {
|
||||
expect(screen.getByPlaceholderText('Filter values')).toBeInTheDocument();
|
||||
expect(screen.getByPlaceholderText('Search values')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// User manually closes the filter
|
||||
@@ -192,7 +193,7 @@ describe('CheckboxFilter - User Flows', () => {
|
||||
|
||||
// User should see filter is now closed (respecting user preference)
|
||||
expect(
|
||||
screen.queryByPlaceholderText('Filter values'),
|
||||
screen.queryByPlaceholderText('Search values'),
|
||||
).not.toBeInTheDocument();
|
||||
|
||||
// User manually opens the filter again
|
||||
@@ -200,7 +201,7 @@ describe('CheckboxFilter - User Flows', () => {
|
||||
|
||||
// User should see filter is now open (respecting user preference)
|
||||
await waitFor(() => {
|
||||
expect(screen.getByPlaceholderText('Filter values')).toBeInTheDocument();
|
||||
expect(screen.getByPlaceholderText('Search values')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
/* eslint-disable sonarjs/no-identical-functions */
|
||||
import { Fragment, useMemo, useState } from 'react';
|
||||
import { Button, Checkbox, Input, Skeleton, Typography } from 'antd';
|
||||
/* eslint-disable jsx-a11y/no-static-element-interactions */
|
||||
/* eslint-disable jsx-a11y/click-events-have-key-events */
|
||||
import {
|
||||
Fragment,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { Button, Checkbox, Input, InputRef, Skeleton, Typography } from 'antd';
|
||||
import cx from 'classnames';
|
||||
import { removeKeysFromExpression } from 'components/QueryBuilderV2/utils';
|
||||
import {
|
||||
@@ -8,19 +17,14 @@ import {
|
||||
QuickFiltersSource,
|
||||
} from 'components/QuickFilters/types';
|
||||
import { OPERATORS } from 'constants/antlrQueryConstants';
|
||||
import {
|
||||
DATA_TYPE_VS_ATTRIBUTE_VALUES_KEY,
|
||||
PANEL_TYPES,
|
||||
} from 'constants/queryBuilder';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { DEBOUNCE_DELAY } from 'constants/queryBuilderFilterConfig';
|
||||
import { getOperatorValue } from 'container/QueryBuilder/filters/QueryBuilderSearch/utils';
|
||||
import { useGetAggregateValues } from 'hooks/queryBuilder/useGetAggregateValues';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { useGetQueryKeyValueSuggestions } from 'hooks/querySuggestions/useGetQueryKeyValueSuggestions';
|
||||
import useDebouncedFn from 'hooks/useDebouncedFunction';
|
||||
import { cloneDeep, isArray, isEqual, isFunction } from 'lodash-es';
|
||||
import { ChevronDown, ChevronRight } from 'lucide-react';
|
||||
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { AlertTriangle, ChevronDown, ChevronRight } from 'lucide-react';
|
||||
import { Query, TagFilterItem } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
@@ -57,6 +61,7 @@ export default function CheckboxFilter(props: ICheckboxProps): JSX.Element {
|
||||
// null = no user action, true = user opened, false = user closed
|
||||
const [userToggleState, setUserToggleState] = useState<boolean | null>(null);
|
||||
const [visibleItemsCount, setVisibleItemsCount] = useState<number>(10);
|
||||
const [visibleUncheckedCount, setVisibleUncheckedCount] = useState<number>(5);
|
||||
|
||||
const {
|
||||
lastUsedQuery,
|
||||
@@ -78,6 +83,12 @@ export default function CheckboxFilter(props: ICheckboxProps): JSX.Element {
|
||||
return lastUsedQuery || 0;
|
||||
}, [isListView, source, lastUsedQuery]);
|
||||
|
||||
// Extract current filter expression for the active query
|
||||
const currentFilterExpression = useMemo(() => {
|
||||
const queryData = currentQuery.builder.queryData?.[activeQueryIndex];
|
||||
return queryData?.filter?.expression || '';
|
||||
}, [currentQuery.builder.queryData, activeQueryIndex]);
|
||||
|
||||
// Check if this filter has active filters in the query
|
||||
const isSomeFilterPresentForCurrentAttribute = useMemo(
|
||||
() =>
|
||||
@@ -109,54 +120,125 @@ export default function CheckboxFilter(props: ICheckboxProps): JSX.Element {
|
||||
filter.defaultOpen,
|
||||
]);
|
||||
|
||||
const { data, isLoading } = useGetAggregateValues(
|
||||
{
|
||||
aggregateOperator: filter.aggregateOperator || 'noop',
|
||||
dataSource: filter.dataSource || DataSource.LOGS,
|
||||
aggregateAttribute: filter.aggregateAttribute || '',
|
||||
attributeKey: filter.attributeKey.key,
|
||||
filterAttributeKeyDataType: filter.attributeKey.dataType || DataTypes.EMPTY,
|
||||
tagType: filter.attributeKey.type || '',
|
||||
searchText: searchText ?? '',
|
||||
},
|
||||
{
|
||||
enabled: isOpen && source !== QuickFiltersSource.METER_EXPLORER,
|
||||
keepPreviousData: true,
|
||||
},
|
||||
);
|
||||
|
||||
const {
|
||||
data: keyValueSuggestions,
|
||||
isLoading: isLoadingKeyValueSuggestions,
|
||||
refetch: refetchKeyValueSuggestions,
|
||||
} = useGetQueryKeyValueSuggestions({
|
||||
key: filter.attributeKey.key,
|
||||
signal: filter.dataSource || DataSource.LOGS,
|
||||
signalSource: 'meter',
|
||||
searchText: searchText || '',
|
||||
existingQuery: currentFilterExpression,
|
||||
options: {
|
||||
enabled: isOpen && source === QuickFiltersSource.METER_EXPLORER,
|
||||
enabled: isOpen,
|
||||
keepPreviousData: true,
|
||||
},
|
||||
});
|
||||
|
||||
const attributeValues: string[] = useMemo(() => {
|
||||
const dataType = filter.attributeKey.dataType || DataTypes.String;
|
||||
const searchInputRef = useRef<InputRef | null>(null);
|
||||
const searchContainerRef = useRef<HTMLDivElement | null>(null);
|
||||
const previousFiltersItemsRef = useRef(
|
||||
currentQuery.builder.queryData?.[activeQueryIndex]?.filters?.items,
|
||||
);
|
||||
|
||||
if (source === QuickFiltersSource.METER_EXPLORER && keyValueSuggestions) {
|
||||
// Process the response data
|
||||
// Refetch when other filters change (not this filter)
|
||||
// Watch for when filters.items is different from previous value, indicating other filters changed
|
||||
useEffect(() => {
|
||||
const currentFiltersItems =
|
||||
currentQuery.builder.queryData?.[activeQueryIndex]?.filters?.items;
|
||||
|
||||
const previousFiltersItems = previousFiltersItemsRef.current;
|
||||
|
||||
// Check if filters items have changed (not the same)
|
||||
const filtersChanged = !isEqual(previousFiltersItems, currentFiltersItems);
|
||||
|
||||
if (isOpen && filtersChanged) {
|
||||
// Check if OTHER filters (not this filter) have changed
|
||||
const currentOtherFilters = currentFiltersItems?.filter(
|
||||
(item) => !isEqual(item.key?.key, filter.attributeKey.key),
|
||||
);
|
||||
const previousOtherFilters = previousFiltersItems?.filter(
|
||||
(item) => !isEqual(item.key?.key, filter.attributeKey.key),
|
||||
);
|
||||
|
||||
// Refetch if other filters changed (not just this filter's values)
|
||||
const otherFiltersChanged = !isEqual(
|
||||
currentOtherFilters,
|
||||
previousOtherFilters,
|
||||
);
|
||||
|
||||
// Only update ref if we have valid API data or if filters actually changed
|
||||
// Don't update if search returned 0 results to preserve unchecked values
|
||||
const hasValidData = keyValueSuggestions && !isLoadingKeyValueSuggestions;
|
||||
if (otherFiltersChanged || hasValidData) {
|
||||
previousFiltersItemsRef.current = currentFiltersItems;
|
||||
}
|
||||
|
||||
if (otherFiltersChanged) {
|
||||
refetchKeyValueSuggestions();
|
||||
}
|
||||
} else {
|
||||
previousFiltersItemsRef.current = currentFiltersItems;
|
||||
}
|
||||
}, [
|
||||
activeQueryIndex,
|
||||
isOpen,
|
||||
refetchKeyValueSuggestions,
|
||||
filter.attributeKey.key,
|
||||
currentQuery.builder.queryData,
|
||||
keyValueSuggestions,
|
||||
isLoadingKeyValueSuggestions,
|
||||
]);
|
||||
|
||||
const handleSearchPromptClick = useCallback((): void => {
|
||||
if (searchContainerRef.current) {
|
||||
searchContainerRef.current.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'center',
|
||||
});
|
||||
}
|
||||
if (searchInputRef.current) {
|
||||
setTimeout(() => searchInputRef.current?.focus({ cursor: 'end' }), 120);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const isDataComplete = useMemo(() => {
|
||||
if (keyValueSuggestions) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const responseData = keyValueSuggestions?.data as any;
|
||||
return responseData.data?.complete || false;
|
||||
}
|
||||
return false;
|
||||
}, [keyValueSuggestions]);
|
||||
|
||||
const previousUncheckedValuesRef = useRef<string[]>([]);
|
||||
|
||||
const { attributeValues, relatedValuesSet } = useMemo(() => {
|
||||
if (keyValueSuggestions) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const responseData = keyValueSuggestions?.data as any;
|
||||
const values = responseData.data?.values || {};
|
||||
const stringValues = values.stringValues || [];
|
||||
const numberValues = values.numberValues || [];
|
||||
const relatedValues: string[] = values.relatedValues || [];
|
||||
const stringValues: string[] = values.stringValues || [];
|
||||
const numberValues: number[] = values.numberValues || [];
|
||||
|
||||
// Generate options from string values - explicitly handle empty strings
|
||||
const stringOptions = stringValues
|
||||
// Strict filtering for empty string - we'll handle it as a special case if needed
|
||||
.filter(
|
||||
(value: string | null | undefined): value is string =>
|
||||
value !== null && value !== undefined && value !== '',
|
||||
);
|
||||
const valuesToUse = [
|
||||
...relatedValues,
|
||||
...stringValues.filter(
|
||||
(value: string | null | undefined) =>
|
||||
value !== null &&
|
||||
value !== undefined &&
|
||||
value !== '' &&
|
||||
!relatedValues.includes(value),
|
||||
),
|
||||
];
|
||||
|
||||
const stringOptions = valuesToUse.filter(
|
||||
(value: string | null | undefined): value is string =>
|
||||
value !== null && value !== undefined && value !== '',
|
||||
);
|
||||
|
||||
// Generate options from number values
|
||||
const numberOptions = numberValues
|
||||
.filter(
|
||||
(value: number | null | undefined): value is number =>
|
||||
@@ -164,15 +246,27 @@ export default function CheckboxFilter(props: ICheckboxProps): JSX.Element {
|
||||
)
|
||||
.map((value: number) => value.toString());
|
||||
|
||||
// Combine all options and make sure we don't have duplicate labels
|
||||
return [...stringOptions, ...numberOptions];
|
||||
}
|
||||
const filteredRelated = new Set(
|
||||
relatedValues.filter(
|
||||
(v): v is string => v !== null && v !== undefined && v !== '',
|
||||
),
|
||||
);
|
||||
|
||||
const key = DATA_TYPE_VS_ATTRIBUTE_VALUES_KEY[dataType];
|
||||
return (data?.payload?.[key] || []).filter(
|
||||
(val) => val !== undefined && val !== null,
|
||||
);
|
||||
}, [data?.payload, filter.attributeKey.dataType, keyValueSuggestions, source]);
|
||||
const baseValues = [...stringOptions, ...numberOptions];
|
||||
const previousUnchecked = previousUncheckedValuesRef.current || [];
|
||||
const preservedUnchecked = previousUnchecked.filter(
|
||||
(value) => !baseValues.includes(value),
|
||||
);
|
||||
return {
|
||||
attributeValues: [...baseValues, ...preservedUnchecked],
|
||||
relatedValuesSet: filteredRelated,
|
||||
};
|
||||
}
|
||||
return {
|
||||
attributeValues: [] as string[],
|
||||
relatedValuesSet: new Set<string>(),
|
||||
};
|
||||
}, [keyValueSuggestions]);
|
||||
|
||||
const setSearchTextDebounced = useDebouncedFn((...args) => {
|
||||
setSearchText(args[0] as string);
|
||||
@@ -246,22 +340,51 @@ export default function CheckboxFilter(props: ICheckboxProps): JSX.Element {
|
||||
const isMultipleValuesTrueForTheKey =
|
||||
Object.values(currentFilterState).filter((val) => val).length > 1;
|
||||
|
||||
// Sort checked items to the top, then unchecked items
|
||||
const currentAttributeKeys = useMemo(() => {
|
||||
// Sort checked items to the top; always show unchecked items beneath, regardless of pagination
|
||||
const {
|
||||
visibleCheckedValues,
|
||||
uncheckedValues,
|
||||
visibleUncheckedValues,
|
||||
visibleCheckedCount,
|
||||
hasMoreChecked,
|
||||
hasMoreUnchecked,
|
||||
checkedSeparatorIndex,
|
||||
} = useMemo(() => {
|
||||
const checkedValues = attributeValues.filter(
|
||||
(val) => currentFilterState[val],
|
||||
);
|
||||
const uncheckedValues = attributeValues.filter(
|
||||
(val) => !currentFilterState[val],
|
||||
);
|
||||
return [...checkedValues, ...uncheckedValues].slice(0, visibleItemsCount);
|
||||
}, [attributeValues, currentFilterState, visibleItemsCount]);
|
||||
const unchecked = attributeValues.filter((val) => !currentFilterState[val]);
|
||||
const visibleChecked = checkedValues.slice(0, visibleItemsCount);
|
||||
const visibleUnchecked = unchecked.slice(0, visibleUncheckedCount);
|
||||
|
||||
// Count of checked values in the currently visible items
|
||||
const checkedValuesCount = useMemo(
|
||||
() => currentAttributeKeys.filter((val) => currentFilterState[val]).length,
|
||||
[currentAttributeKeys, currentFilterState],
|
||||
);
|
||||
const findSeparatorIndex = (list: string[]): number => {
|
||||
if (relatedValuesSet.size === 0) {
|
||||
return -1;
|
||||
}
|
||||
const firstNonRelated = list.findIndex((v) => !relatedValuesSet.has(v));
|
||||
return firstNonRelated > 0 ? firstNonRelated : -1;
|
||||
};
|
||||
|
||||
return {
|
||||
visibleCheckedValues: visibleChecked,
|
||||
uncheckedValues: unchecked,
|
||||
visibleUncheckedValues: visibleUnchecked,
|
||||
visibleCheckedCount: visibleChecked.length,
|
||||
hasMoreChecked: checkedValues.length > visibleChecked.length,
|
||||
hasMoreUnchecked: unchecked.length > visibleUnchecked.length,
|
||||
checkedSeparatorIndex: findSeparatorIndex(visibleChecked),
|
||||
};
|
||||
}, [
|
||||
attributeValues,
|
||||
currentFilterState,
|
||||
visibleItemsCount,
|
||||
visibleUncheckedCount,
|
||||
relatedValuesSet,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
previousUncheckedValuesRef.current = uncheckedValues;
|
||||
}, [uncheckedValues]);
|
||||
|
||||
const handleClearFilterAttribute = (): void => {
|
||||
const preparedQuery: Query = {
|
||||
@@ -302,6 +425,7 @@ export default function CheckboxFilter(props: ICheckboxProps): JSX.Element {
|
||||
isOnlyOrAllClicked: boolean,
|
||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||
): void => {
|
||||
setVisibleUncheckedCount(5);
|
||||
const query = cloneDeep(currentQuery.builder.queryData?.[activeQueryIndex]);
|
||||
|
||||
// if only or all are clicked we do not need to worry about anything just override whatever we have
|
||||
@@ -562,6 +686,7 @@ export default function CheckboxFilter(props: ICheckboxProps): JSX.Element {
|
||||
if (isOpen) {
|
||||
setUserToggleState(false);
|
||||
setVisibleItemsCount(10);
|
||||
setVisibleUncheckedCount(5);
|
||||
} else {
|
||||
setUserToggleState(true);
|
||||
}
|
||||
@@ -590,35 +715,93 @@ export default function CheckboxFilter(props: ICheckboxProps): JSX.Element {
|
||||
)}
|
||||
</section>
|
||||
</section>
|
||||
{isOpen &&
|
||||
(isLoading || isLoadingKeyValueSuggestions) &&
|
||||
!attributeValues.length && (
|
||||
<section className="loading">
|
||||
<Skeleton paragraph={{ rows: 4 }} />
|
||||
</section>
|
||||
)}
|
||||
{isOpen && !isLoading && !isLoadingKeyValueSuggestions && (
|
||||
{isOpen && isLoadingKeyValueSuggestions && !attributeValues.length && (
|
||||
<section className="loading">
|
||||
<Skeleton paragraph={{ rows: 4 }} />
|
||||
</section>
|
||||
)}
|
||||
{isOpen && !isLoadingKeyValueSuggestions && (
|
||||
<>
|
||||
{!isEmptyStateWithDocsEnabled && (
|
||||
<section className="search">
|
||||
<section className="search" ref={searchContainerRef}>
|
||||
<Input
|
||||
placeholder="Filter values"
|
||||
placeholder="Search values"
|
||||
onChange={(e): void => setSearchTextDebounced(e.target.value)}
|
||||
disabled={isFilterDisabled}
|
||||
ref={searchInputRef}
|
||||
/>
|
||||
</section>
|
||||
)}
|
||||
{attributeValues.length > 0 ? (
|
||||
<section className="values">
|
||||
{currentAttributeKeys.map((value: string, index: number) => (
|
||||
{visibleCheckedValues.map((value: string, index: number) => (
|
||||
<Fragment key={value}>
|
||||
{index === checkedValuesCount && checkedValuesCount > 0 && (
|
||||
<div
|
||||
key="separator"
|
||||
className="filter-separator"
|
||||
data-testid="filter-separator"
|
||||
/>
|
||||
{index === checkedSeparatorIndex && (
|
||||
<div className="filter-separator related-separator" />
|
||||
)}
|
||||
<div className="value">
|
||||
<Checkbox
|
||||
onChange={(e): void => onChange(value, e.target.checked, false)}
|
||||
checked={currentFilterState[value]}
|
||||
disabled={isFilterDisabled}
|
||||
rootClassName="check-box"
|
||||
/>
|
||||
|
||||
<div
|
||||
className={cx(
|
||||
'checkbox-value-section',
|
||||
isFilterDisabled ? 'filter-disabled' : '',
|
||||
)}
|
||||
onClick={(): void => {
|
||||
if (isFilterDisabled) {
|
||||
return;
|
||||
}
|
||||
onChange(value, currentFilterState[value], true);
|
||||
}}
|
||||
>
|
||||
<div className={`${filter.title} label-${value}`} />
|
||||
{filter.customRendererForValue ? (
|
||||
filter.customRendererForValue(value)
|
||||
) : (
|
||||
<Typography.Text
|
||||
className="value-string"
|
||||
ellipsis={{ tooltip: { placement: 'top' } }}
|
||||
>
|
||||
{String(value)}
|
||||
</Typography.Text>
|
||||
)}
|
||||
<Button type="text" className="only-btn">
|
||||
{isSomeFilterPresentForCurrentAttribute
|
||||
? currentFilterState[value] && !isMultipleValuesTrueForTheKey
|
||||
? 'All'
|
||||
: 'Only'
|
||||
: 'Only'}
|
||||
</Button>
|
||||
<Button type="text" className="toggle-btn">
|
||||
Toggle
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Fragment>
|
||||
))}
|
||||
|
||||
{hasMoreChecked && (
|
||||
<section className="show-more">
|
||||
<Typography.Text
|
||||
className="show-more-text"
|
||||
onClick={(): void => setVisibleItemsCount((prev) => prev + 10)}
|
||||
>
|
||||
Show More...
|
||||
</Typography.Text>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{visibleCheckedCount > 0 && uncheckedValues.length > 0 && (
|
||||
<div className="filter-separator" data-testid="filter-separator" />
|
||||
)}
|
||||
|
||||
{visibleUncheckedValues.map((value: string) => (
|
||||
<Fragment key={value}>
|
||||
<div className="value">
|
||||
<Checkbox
|
||||
onChange={(e): void => onChange(value, e.target.checked, false)}
|
||||
@@ -670,6 +853,17 @@ export default function CheckboxFilter(props: ICheckboxProps): JSX.Element {
|
||||
</div>
|
||||
</Fragment>
|
||||
))}
|
||||
|
||||
{hasMoreUnchecked && (
|
||||
<section className="show-more">
|
||||
<Typography.Text
|
||||
className="show-more-text"
|
||||
onClick={(): void => setVisibleUncheckedCount((prev) => prev + 5)}
|
||||
>
|
||||
Show More...
|
||||
</Typography.Text>
|
||||
</section>
|
||||
)}
|
||||
</section>
|
||||
) : isEmptyStateWithDocsEnabled ? (
|
||||
<LogsQuickFilterEmptyState attributeKey={filter.attributeKey.key} />
|
||||
@@ -678,16 +872,18 @@ export default function CheckboxFilter(props: ICheckboxProps): JSX.Element {
|
||||
<Typography.Text>No values found</Typography.Text>{' '}
|
||||
</section>
|
||||
)}
|
||||
{visibleItemsCount < attributeValues?.length && (
|
||||
<section className="show-more">
|
||||
<Typography.Text
|
||||
className="show-more-text"
|
||||
onClick={(): void => setVisibleItemsCount((prev) => prev + 10)}
|
||||
>
|
||||
Show More...
|
||||
</Typography.Text>
|
||||
</section>
|
||||
)}
|
||||
{visibleItemsCount >= attributeValues?.length &&
|
||||
attributeValues?.length > 0 &&
|
||||
!isDataComplete && (
|
||||
<section className="search-prompt" onClick={handleSearchPromptClick}>
|
||||
<AlertTriangle size={16} className="search-prompt__icon" />
|
||||
<span className="search-prompt__text">
|
||||
<Typography.Text className="search-prompt__subtitle">
|
||||
Tap to search and load more suggestions.
|
||||
</Typography.Text>
|
||||
</span>
|
||||
</section>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -127,6 +127,34 @@
|
||||
align-items: center;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.filters-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 6px 10px 0 10px;
|
||||
color: var(--bg-vanilla-400);
|
||||
gap: 6px;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.filters-info-toggle {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 0;
|
||||
height: auto;
|
||||
color: var(--bg-vanilla-400);
|
||||
|
||||
&:hover {
|
||||
color: var(--bg-robin-500);
|
||||
}
|
||||
}
|
||||
|
||||
.filters-info-text {
|
||||
color: var(--bg-vanilla-400);
|
||||
font-size: 13px;
|
||||
line-height: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.perilin-bg {
|
||||
@@ -180,5 +208,30 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.filters-info {
|
||||
color: var(--bg-ink-400);
|
||||
|
||||
.filters-info-toggle {
|
||||
color: var(--bg-ink-400);
|
||||
|
||||
&:hover {
|
||||
color: var(--bg-ink-300);
|
||||
}
|
||||
}
|
||||
|
||||
.filters-info-text {
|
||||
color: var(--bg-ink-400);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.filters-info-tooltip-title {
|
||||
font-weight: var(--font-weight-bold);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.filters-info-tooltip-detail {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { useApiMonitoringParams } from 'container/ApiMonitoring/queryParams';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { isFunction, isNull } from 'lodash-es';
|
||||
import { Frown, Settings2 as SettingsIcon } from 'lucide-react';
|
||||
import { Frown, Lightbulb, Settings2 as SettingsIcon } from 'lucide-react';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { USER_ROLES } from 'types/roles';
|
||||
@@ -291,6 +291,27 @@ export default function QuickFilters(props: IQuickFiltersProps): JSX.Element {
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<section className="filters-info">
|
||||
<Tooltip
|
||||
title={
|
||||
<div className="filters-info-tooltip">
|
||||
<div className="filters-info-tooltip-title">Adaptive Filters</div>
|
||||
<div>Values update automatically as you apply filters.</div>
|
||||
<div className="filters-info-tooltip-detail">
|
||||
The most relevant values are shown first, followed by all other
|
||||
available options.
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
placement="right"
|
||||
mouseEnterDelay={0.3}
|
||||
>
|
||||
<Typography.Text className="filters-info-toggle">
|
||||
<Lightbulb size={15} />
|
||||
Adaptive filters
|
||||
</Typography.Text>
|
||||
</Tooltip>
|
||||
</section>
|
||||
<section className="filters">
|
||||
{filterConfig.map((filter) => {
|
||||
switch (filter.type) {
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
useApiMonitoringParams,
|
||||
} from 'container/ApiMonitoring/queryParams';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { useGetQueryKeyValueSuggestions } from 'hooks/querySuggestions/useGetQueryKeyValueSuggestions';
|
||||
import {
|
||||
otherFiltersResponse,
|
||||
quickFiltersAttributeValuesResponse,
|
||||
@@ -24,6 +25,8 @@ jest.mock('hooks/queryBuilder/useQueryBuilder', () => ({
|
||||
}));
|
||||
jest.mock('container/ApiMonitoring/queryParams');
|
||||
|
||||
jest.mock('hooks/querySuggestions/useGetQueryKeyValueSuggestions');
|
||||
|
||||
const handleFilterVisibilityChange = jest.fn();
|
||||
const redirectWithQueryBuilderData = jest.fn();
|
||||
const putHandler = jest.fn();
|
||||
@@ -32,13 +35,15 @@ const mockSetApiMonitoringParams = jest.fn() as jest.MockedFunction<
|
||||
>;
|
||||
const mockUseApiMonitoringParams = jest.mocked(useApiMonitoringParams);
|
||||
|
||||
const mockUseGetQueryKeyValueSuggestions = jest.mocked(
|
||||
useGetQueryKeyValueSuggestions,
|
||||
);
|
||||
|
||||
const BASE_URL = ENVIRONMENT.baseURL;
|
||||
const SIGNAL = SignalType.LOGS;
|
||||
const quickFiltersListURL = `${BASE_URL}/api/v1/orgs/me/filters/${SIGNAL}`;
|
||||
const saveQuickFiltersURL = `${BASE_URL}/api/v1/orgs/me/filters`;
|
||||
const quickFiltersSuggestionsURL = `${BASE_URL}/api/v3/filter_suggestions`;
|
||||
const quickFiltersAttributeValuesURL = `${BASE_URL}/api/v3/autocomplete/attribute_values`;
|
||||
const fieldsValuesURL = `${BASE_URL}/api/v1/fields/values`;
|
||||
|
||||
const FILTER_OS_DESCRIPTION = 'os.description';
|
||||
const FILTER_K8S_DEPLOYMENT_NAME = 'k8s.deployment.name';
|
||||
@@ -62,10 +67,7 @@ const setupServer = (): void => {
|
||||
putHandler(await req.json());
|
||||
return res(ctx.status(200), ctx.json({}));
|
||||
}),
|
||||
rest.get(quickFiltersAttributeValuesURL, (_req, res, ctx) =>
|
||||
res(ctx.status(200), ctx.json(quickFiltersAttributeValuesResponse)),
|
||||
),
|
||||
rest.get(fieldsValuesURL, (_req, res, ctx) =>
|
||||
rest.get('*/api/v1/fields/values*', (_req, res, ctx) =>
|
||||
res(ctx.status(200), ctx.json(quickFiltersAttributeValuesResponse)),
|
||||
),
|
||||
);
|
||||
@@ -135,18 +137,28 @@ beforeEach(() => {
|
||||
queryData: [
|
||||
{
|
||||
queryName: QUERY_NAME,
|
||||
filters: { items: [{ key: 'test', value: 'value' }] },
|
||||
filters: { items: [], op: 'AND' },
|
||||
filter: { expression: '' },
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
lastUsedQuery: 0,
|
||||
panelType: 'logs',
|
||||
redirectWithQueryBuilderData,
|
||||
});
|
||||
mockUseApiMonitoringParams.mockReturnValue([
|
||||
{ showIP: true } as ApiMonitoringParams,
|
||||
mockSetApiMonitoringParams,
|
||||
]);
|
||||
|
||||
// Mock the hook to return data with mq-kafka
|
||||
mockUseGetQueryKeyValueSuggestions.mockReturnValue({
|
||||
data: quickFiltersAttributeValuesResponse,
|
||||
isLoading: false,
|
||||
refetch: jest.fn(),
|
||||
} as any);
|
||||
|
||||
setupServer();
|
||||
});
|
||||
|
||||
@@ -259,8 +271,9 @@ describe('Quick Filters', () => {
|
||||
|
||||
render(<TestQuickFilters />);
|
||||
|
||||
// Prefer role if possible; if label text isn’t wired to input, clicking the label text is OK
|
||||
const target = await screen.findByText('mq-kafka');
|
||||
// Wait for the filter to load with data
|
||||
const target = await screen.findByText('mq-kafka', {}, { timeout: 5000 });
|
||||
|
||||
await user.click(target);
|
||||
|
||||
await waitFor(() => {
|
||||
|
||||
@@ -14,6 +14,8 @@ import cx from 'classnames';
|
||||
import { dragColumnParams } from 'hooks/useDragColumns/configs';
|
||||
import { getColumnWidth, RowData } from 'lib/query/createTableColumnsFromQuery';
|
||||
import { debounce, set } from 'lodash-es';
|
||||
import { useDashboard } from 'providers/Dashboard/Dashboard';
|
||||
import { Widgets } from 'types/api/dashboard/getAll';
|
||||
|
||||
import ResizableHeader from './ResizableHeader';
|
||||
import { DragSpanStyle } from './styles';
|
||||
@@ -24,26 +26,31 @@ function ResizeTable({
|
||||
columns,
|
||||
onDragColumn,
|
||||
pagination,
|
||||
columnWidths,
|
||||
onColumnWidthsChange,
|
||||
widgetId,
|
||||
shouldPersistColumnWidths = false,
|
||||
...restProps
|
||||
}: ResizeTableProps): JSX.Element {
|
||||
const [columnsData, setColumns] = useState<ColumnsType>([]);
|
||||
const onColumnWidthsChangeRef = useRef(onColumnWidthsChange);
|
||||
const { setColumnWidths, selectedDashboard } = useDashboard();
|
||||
|
||||
const columnWidths = shouldPersistColumnWidths
|
||||
? (selectedDashboard?.data?.widgets?.find(
|
||||
(widget) => widget.id === widgetId,
|
||||
) as Widgets)?.columnWidths
|
||||
: undefined;
|
||||
|
||||
const updateAllColumnWidths = useRef(
|
||||
debounce((widthsConfig: Record<string, number>) => {
|
||||
if (!onColumnWidthsChangeRef.current) {
|
||||
if (!widgetId || !shouldPersistColumnWidths) {
|
||||
return;
|
||||
}
|
||||
onColumnWidthsChangeRef.current(widthsConfig);
|
||||
setColumnWidths?.((prev) => ({
|
||||
...prev,
|
||||
[widgetId]: widthsConfig,
|
||||
}));
|
||||
}, 1000),
|
||||
).current;
|
||||
|
||||
useEffect(() => {
|
||||
onColumnWidthsChangeRef.current = onColumnWidthsChange;
|
||||
}, [onColumnWidthsChange]);
|
||||
|
||||
const handleResize = useCallback(
|
||||
(index: number) => (
|
||||
e: SyntheticEvent<Element>,
|
||||
@@ -68,7 +75,7 @@ function ResizeTable({
|
||||
...col,
|
||||
...(onDragColumn && {
|
||||
title: (
|
||||
<DragSpanStyle className="dragHandler" data-testid="drag-column-title">
|
||||
<DragSpanStyle className="dragHandler">
|
||||
{col?.title?.toString() || ''}
|
||||
</DragSpanStyle>
|
||||
),
|
||||
@@ -99,31 +106,31 @@ function ResizeTable({
|
||||
}, [mergedColumns, pagination, restProps]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!columns) {
|
||||
return;
|
||||
}
|
||||
|
||||
const columnsWithStoredWidths = columns.map((col) => {
|
||||
const dataIndex = (col as RowData).dataIndex as string;
|
||||
if (dataIndex && columnWidths) {
|
||||
const width = getColumnWidth(dataIndex, columnWidths);
|
||||
if (width) {
|
||||
return { ...col, width };
|
||||
if (columns) {
|
||||
// Apply stored column widths from widget configuration
|
||||
const columnsWithStoredWidths = columns.map((col) => {
|
||||
const dataIndex = (col as RowData).dataIndex as string;
|
||||
if (dataIndex && columnWidths) {
|
||||
const width = getColumnWidth(dataIndex, columnWidths);
|
||||
if (width) {
|
||||
return {
|
||||
...col,
|
||||
width, // Apply stored width
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
return col;
|
||||
});
|
||||
return col;
|
||||
});
|
||||
|
||||
setColumns(columnsWithStoredWidths);
|
||||
setColumns(columnsWithStoredWidths);
|
||||
}
|
||||
}, [columns, columnWidths]);
|
||||
|
||||
const lastReportedWidthsRef = useRef<Record<string, number>>({});
|
||||
|
||||
useEffect(() => {
|
||||
if (!onColumnWidthsChange) {
|
||||
if (!shouldPersistColumnWidths) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Collect all column widths in a single object
|
||||
const newColumnWidths: Record<string, number> = {};
|
||||
|
||||
mergedColumns.forEach((col) => {
|
||||
@@ -133,20 +140,11 @@ function ResizeTable({
|
||||
}
|
||||
});
|
||||
|
||||
if (Object.keys(newColumnWidths).length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const last = lastReportedWidthsRef.current;
|
||||
const hasChange =
|
||||
Object.keys(newColumnWidths).length !== Object.keys(last).length ||
|
||||
Object.keys(newColumnWidths).some((k) => newColumnWidths[k] !== last[k]);
|
||||
|
||||
if (hasChange) {
|
||||
lastReportedWidthsRef.current = newColumnWidths;
|
||||
// Only update if there are actual widths to set
|
||||
if (Object.keys(newColumnWidths).length > 0) {
|
||||
updateAllColumnWidths(newColumnWidths);
|
||||
}
|
||||
}, [mergedColumns, updateAllColumnWidths, onColumnWidthsChange]);
|
||||
}, [mergedColumns, updateAllColumnWidths, shouldPersistColumnWidths]);
|
||||
|
||||
return onDragColumn ? (
|
||||
<ReactDragListView.DragColumn {...dragColumnParams} onDragEnd={onDragColumn}>
|
||||
|
||||
@@ -1,244 +0,0 @@
|
||||
import { act } from '@testing-library/react';
|
||||
import { render, screen, userEvent } from 'tests/test-utils';
|
||||
|
||||
import ResizeTable from '../ResizeTable';
|
||||
|
||||
jest.mock('react-resizable', () => ({
|
||||
Resizable: ({
|
||||
children,
|
||||
onResize,
|
||||
width,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
onResize: (
|
||||
e: React.SyntheticEvent,
|
||||
data: { size: { width: number } },
|
||||
) => void;
|
||||
width: number;
|
||||
}): JSX.Element => (
|
||||
<div>
|
||||
{children}
|
||||
<button
|
||||
data-testid="resize-trigger"
|
||||
type="button"
|
||||
onClick={(e): void => onResize(e, { size: { width: width + 50 } })}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
}));
|
||||
|
||||
// Make debounce synchronous so onColumnWidthsChange fires immediately
|
||||
jest.mock('lodash-es', () => ({
|
||||
...jest.requireActual('lodash-es'),
|
||||
debounce: (fn: (...args: any[]) => any): ((...args: any[]) => any) => fn,
|
||||
}));
|
||||
|
||||
const baseColumns = [
|
||||
{ dataIndex: 'name', title: 'Name', width: 100 },
|
||||
{ dataIndex: 'value', title: 'Value', width: 100 },
|
||||
];
|
||||
|
||||
const baseDataSource = [
|
||||
{ key: '1', name: 'Alice', value: 42 },
|
||||
{ key: '2', name: 'Bob', value: 99 },
|
||||
];
|
||||
|
||||
describe('ResizeTable', () => {
|
||||
it('renders column headers and data rows', () => {
|
||||
render(
|
||||
<ResizeTable
|
||||
columns={baseColumns}
|
||||
dataSource={baseDataSource}
|
||||
rowKey="key"
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByText('Name')).toBeInTheDocument();
|
||||
expect(screen.getByText('Value')).toBeInTheDocument();
|
||||
expect(screen.getByText('Alice')).toBeInTheDocument();
|
||||
expect(screen.getByText('Bob')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('overrides column widths from columnWidths prop and reports them via onColumnWidthsChange', () => {
|
||||
const onColumnWidthsChange = jest.fn();
|
||||
|
||||
act(() => {
|
||||
render(
|
||||
<ResizeTable
|
||||
columns={baseColumns}
|
||||
dataSource={baseDataSource}
|
||||
rowKey="key"
|
||||
columnWidths={{ name: 250, value: 180 }}
|
||||
onColumnWidthsChange={onColumnWidthsChange}
|
||||
/>,
|
||||
);
|
||||
});
|
||||
|
||||
expect(onColumnWidthsChange).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ name: 250, value: 180 }),
|
||||
);
|
||||
});
|
||||
|
||||
it('reports original column widths via onColumnWidthsChange when columnWidths prop is not provided', () => {
|
||||
const onColumnWidthsChange = jest.fn();
|
||||
|
||||
act(() => {
|
||||
render(
|
||||
<ResizeTable
|
||||
columns={baseColumns}
|
||||
dataSource={baseDataSource}
|
||||
rowKey="key"
|
||||
onColumnWidthsChange={onColumnWidthsChange}
|
||||
/>,
|
||||
);
|
||||
});
|
||||
|
||||
expect(onColumnWidthsChange).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ name: 100, value: 100 }),
|
||||
);
|
||||
});
|
||||
|
||||
it('does not call onColumnWidthsChange when it is not provided', () => {
|
||||
// Should render without errors and without attempting to call an undefined callback
|
||||
expect(() => {
|
||||
render(
|
||||
<ResizeTable
|
||||
columns={baseColumns}
|
||||
dataSource={baseDataSource}
|
||||
rowKey="key"
|
||||
/>,
|
||||
);
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
it('only overrides the column that has a stored width, leaving others at their original width', () => {
|
||||
const onColumnWidthsChange = jest.fn();
|
||||
|
||||
act(() => {
|
||||
render(
|
||||
<ResizeTable
|
||||
columns={baseColumns}
|
||||
dataSource={baseDataSource}
|
||||
rowKey="key"
|
||||
columnWidths={{ name: 250 }}
|
||||
onColumnWidthsChange={onColumnWidthsChange}
|
||||
/>,
|
||||
);
|
||||
});
|
||||
|
||||
expect(onColumnWidthsChange).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ name: 250, value: 100 }),
|
||||
);
|
||||
});
|
||||
|
||||
it('does not call onColumnWidthsChange on re-render when widths have not changed', () => {
|
||||
const onColumnWidthsChange = jest.fn();
|
||||
|
||||
const { rerender } = render(
|
||||
<ResizeTable
|
||||
columns={baseColumns}
|
||||
dataSource={baseDataSource}
|
||||
rowKey="key"
|
||||
onColumnWidthsChange={onColumnWidthsChange}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(onColumnWidthsChange).toHaveBeenCalledTimes(1);
|
||||
onColumnWidthsChange.mockClear();
|
||||
|
||||
rerender(
|
||||
<ResizeTable
|
||||
columns={baseColumns}
|
||||
dataSource={baseDataSource}
|
||||
rowKey="key"
|
||||
onColumnWidthsChange={onColumnWidthsChange}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(onColumnWidthsChange).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('does not call onColumnWidthsChange when no column has a defined width', () => {
|
||||
const onColumnWidthsChange = jest.fn();
|
||||
|
||||
render(
|
||||
<ResizeTable
|
||||
columns={[
|
||||
{ dataIndex: 'name', title: 'Name' },
|
||||
{ dataIndex: 'value', title: 'Value' },
|
||||
]}
|
||||
dataSource={baseDataSource}
|
||||
rowKey="key"
|
||||
onColumnWidthsChange={onColumnWidthsChange}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(onColumnWidthsChange).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('calls onColumnWidthsChange with the new width after a column is resized', async () => {
|
||||
const user = userEvent.setup();
|
||||
const onColumnWidthsChange = jest.fn();
|
||||
|
||||
render(
|
||||
<ResizeTable
|
||||
columns={baseColumns}
|
||||
dataSource={baseDataSource}
|
||||
rowKey="key"
|
||||
onColumnWidthsChange={onColumnWidthsChange}
|
||||
/>,
|
||||
);
|
||||
|
||||
onColumnWidthsChange.mockClear();
|
||||
|
||||
// Click the first column's resize trigger — mock adds 50px to the current width (100 → 150)
|
||||
const [firstResizeTrigger] = screen.getAllByTestId('resize-trigger');
|
||||
await user.click(firstResizeTrigger);
|
||||
|
||||
expect(onColumnWidthsChange).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ name: 150, value: 100 }),
|
||||
);
|
||||
});
|
||||
|
||||
it('does not affect other columns when only one column is resized', async () => {
|
||||
const user = userEvent.setup();
|
||||
const onColumnWidthsChange = jest.fn();
|
||||
|
||||
render(
|
||||
<ResizeTable
|
||||
columns={baseColumns}
|
||||
dataSource={baseDataSource}
|
||||
rowKey="key"
|
||||
onColumnWidthsChange={onColumnWidthsChange}
|
||||
/>,
|
||||
);
|
||||
|
||||
onColumnWidthsChange.mockClear();
|
||||
|
||||
// Resize only the second column (value: 100 → 150), name should stay at 100
|
||||
const resizeTriggers = screen.getAllByTestId('resize-trigger');
|
||||
await user.click(resizeTriggers[1]);
|
||||
|
||||
expect(onColumnWidthsChange).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ name: 100, value: 150 }),
|
||||
);
|
||||
});
|
||||
|
||||
it('wraps column titles in drag handler spans when onDragColumn is provided', () => {
|
||||
const onDragColumn = jest.fn();
|
||||
|
||||
render(
|
||||
<ResizeTable
|
||||
columns={baseColumns}
|
||||
dataSource={baseDataSource}
|
||||
rowKey="key"
|
||||
onDragColumn={onDragColumn}
|
||||
/>,
|
||||
);
|
||||
|
||||
const dragTitles = screen.getAllByTestId('drag-column-title');
|
||||
expect(dragTitles).toHaveLength(baseColumns.length);
|
||||
expect(dragTitles[0]).toHaveTextContent('Name');
|
||||
expect(dragTitles[1]).toHaveTextContent('Value');
|
||||
});
|
||||
});
|
||||
@@ -6,25 +6,10 @@ import { LaunchChatSupportProps } from 'components/LaunchChatSupport/LaunchChatS
|
||||
|
||||
import { TableDataSource } from './contants';
|
||||
|
||||
type ColumnWidths = Record<string, number>;
|
||||
|
||||
export interface ResizeTableProps extends TableProps<any> {
|
||||
onDragColumn?: (fromIndex: number, toIndex: number) => void;
|
||||
/**
|
||||
* Pre-resolved column widths for this table, keyed by column dataIndex.
|
||||
* Use this to apply persisted widths on mount (e.g. from widget.columnWidths).
|
||||
* Do NOT pass a value that updates reactively on every resize — that creates a
|
||||
* feedback loop. Pass only stable / persisted values.
|
||||
*/
|
||||
columnWidths?: ColumnWidths;
|
||||
/**
|
||||
* Called (debounced) whenever the user finishes resizing a column.
|
||||
* The widths object contains all current column widths keyed by dataIndex.
|
||||
* Intended for persisting widths to an external store (e.g. dashboard context
|
||||
* staging buffer). The caller owns the storage; ResizeTable does not read back
|
||||
* whatever is written here.
|
||||
*/
|
||||
onColumnWidthsChange?: (widths: ColumnWidths) => void;
|
||||
widgetId?: string;
|
||||
shouldPersistColumnWidths?: boolean;
|
||||
}
|
||||
export interface DynamicColumnTableProps extends TableProps<any> {
|
||||
tablesource: typeof TableDataSource[keyof typeof TableDataSource];
|
||||
|
||||
@@ -10,15 +10,7 @@ import { useBarChartStacking } from '../../hooks/useBarChartStacking';
|
||||
import { BarChartProps } from '../types';
|
||||
|
||||
export default function BarChart(props: BarChartProps): JSX.Element {
|
||||
const {
|
||||
children,
|
||||
isStackedBarChart,
|
||||
customTooltip,
|
||||
config,
|
||||
data,
|
||||
pinnedTooltipElement,
|
||||
...rest
|
||||
} = props;
|
||||
const { children, isStackedBarChart, config, data, ...rest } = props;
|
||||
|
||||
const chartData = useBarChartStacking({
|
||||
data,
|
||||
@@ -28,9 +20,6 @@ export default function BarChart(props: BarChartProps): JSX.Element {
|
||||
|
||||
const renderTooltip = useCallback(
|
||||
(props: TooltipRenderArgs): React.ReactNode => {
|
||||
if (customTooltip) {
|
||||
return customTooltip(props);
|
||||
}
|
||||
const tooltipProps: BarTooltipProps = {
|
||||
...props,
|
||||
timezone: rest.timezone,
|
||||
@@ -40,13 +29,7 @@ export default function BarChart(props: BarChartProps): JSX.Element {
|
||||
};
|
||||
return <BarChartTooltip {...tooltipProps} />;
|
||||
},
|
||||
[
|
||||
customTooltip,
|
||||
rest.timezone,
|
||||
rest.yAxisUnit,
|
||||
rest.decimalPrecision,
|
||||
isStackedBarChart,
|
||||
],
|
||||
[rest.timezone, rest.yAxisUnit, rest.decimalPrecision, isStackedBarChart],
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -54,8 +37,7 @@ export default function BarChart(props: BarChartProps): JSX.Element {
|
||||
{...rest}
|
||||
config={config}
|
||||
data={chartData}
|
||||
customTooltip={renderTooltip}
|
||||
pinnedTooltipElement={pinnedTooltipElement}
|
||||
renderTooltip={renderTooltip}
|
||||
>
|
||||
{children}
|
||||
</ChartWrapper>
|
||||
|
||||
@@ -30,8 +30,7 @@ export default function ChartWrapper({
|
||||
onDestroy = noop,
|
||||
children,
|
||||
layoutChildren,
|
||||
customTooltip,
|
||||
pinnedTooltipElement,
|
||||
renderTooltip,
|
||||
'data-testid': testId,
|
||||
}: ChartProps): JSX.Element {
|
||||
const plotInstanceRef = useRef<uPlot | null>(null);
|
||||
@@ -54,12 +53,12 @@ export default function ChartWrapper({
|
||||
|
||||
const renderTooltipCallback = useCallback(
|
||||
(args: TooltipRenderArgs): React.ReactNode => {
|
||||
if (customTooltip) {
|
||||
return customTooltip(args);
|
||||
if (renderTooltip) {
|
||||
return renderTooltip(args);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
[customTooltip],
|
||||
[renderTooltip],
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -100,7 +99,6 @@ export default function ChartWrapper({
|
||||
)}
|
||||
syncKey={syncKey}
|
||||
render={renderTooltipCallback}
|
||||
pinnedTooltipElement={pinnedTooltipElement}
|
||||
/>
|
||||
)}
|
||||
</UPlotChart>
|
||||
|
||||
@@ -11,16 +11,15 @@ import { HistogramChartProps } from '../types';
|
||||
export default function Histogram(props: HistogramChartProps): JSX.Element {
|
||||
const {
|
||||
children,
|
||||
customTooltip,
|
||||
renderTooltip: customRenderTooltip,
|
||||
isQueriesMerged,
|
||||
pinnedTooltipElement,
|
||||
...rest
|
||||
} = props;
|
||||
|
||||
const renderTooltip = useCallback(
|
||||
(props: TooltipRenderArgs): React.ReactNode => {
|
||||
if (customTooltip) {
|
||||
return customTooltip(props);
|
||||
if (customRenderTooltip) {
|
||||
return customRenderTooltip(props);
|
||||
}
|
||||
const tooltipProps: HistogramTooltipProps = {
|
||||
...props,
|
||||
@@ -30,15 +29,14 @@ export default function Histogram(props: HistogramChartProps): JSX.Element {
|
||||
};
|
||||
return <HistogramTooltip {...tooltipProps} />;
|
||||
},
|
||||
[customTooltip, rest.timezone, rest.yAxisUnit, rest.decimalPrecision],
|
||||
[customRenderTooltip, rest.timezone, rest.yAxisUnit, rest.decimalPrecision],
|
||||
);
|
||||
|
||||
return (
|
||||
<ChartWrapper
|
||||
showLegend={!isQueriesMerged}
|
||||
{...rest}
|
||||
customTooltip={renderTooltip}
|
||||
pinnedTooltipElement={pinnedTooltipElement}
|
||||
renderTooltip={renderTooltip}
|
||||
>
|
||||
{children}
|
||||
</ChartWrapper>
|
||||
|
||||
@@ -9,12 +9,12 @@ import {
|
||||
import { TimeSeriesChartProps } from '../types';
|
||||
|
||||
export default function TimeSeries(props: TimeSeriesChartProps): JSX.Element {
|
||||
const { children, customTooltip, pinnedTooltipElement, ...rest } = props;
|
||||
const { children, renderTooltip: customRenderTooltip, ...rest } = props;
|
||||
|
||||
const renderTooltip = useCallback(
|
||||
(props: TooltipRenderArgs): React.ReactNode => {
|
||||
if (customTooltip) {
|
||||
return customTooltip(props);
|
||||
if (customRenderTooltip) {
|
||||
return customRenderTooltip(props);
|
||||
}
|
||||
const tooltipProps: TimeSeriesTooltipProps = {
|
||||
...props,
|
||||
@@ -24,15 +24,11 @@ export default function TimeSeries(props: TimeSeriesChartProps): JSX.Element {
|
||||
};
|
||||
return <TimeSeriesTooltip {...tooltipProps} />;
|
||||
},
|
||||
[customTooltip, rest.timezone, rest.yAxisUnit, rest.decimalPrecision],
|
||||
[customRenderTooltip, rest.timezone, rest.yAxisUnit, rest.decimalPrecision],
|
||||
);
|
||||
|
||||
return (
|
||||
<ChartWrapper
|
||||
{...rest}
|
||||
customTooltip={renderTooltip}
|
||||
pinnedTooltipElement={pinnedTooltipElement}
|
||||
>
|
||||
<ChartWrapper {...rest} renderTooltip={renderTooltip}>
|
||||
{children}
|
||||
</ChartWrapper>
|
||||
);
|
||||
|
||||
@@ -2,10 +2,7 @@ import { Timezone } from 'components/CustomTimePicker/timezoneUtils';
|
||||
import { PrecisionOption } from 'components/Graph/types';
|
||||
import { LegendConfig, TooltipRenderArgs } from 'lib/uPlotV2/components/types';
|
||||
import { UPlotConfigBuilder } from 'lib/uPlotV2/config/UPlotConfigBuilder';
|
||||
import {
|
||||
DashboardCursorSync,
|
||||
TooltipClickData,
|
||||
} from 'lib/uPlotV2/plugins/TooltipPlugin/types';
|
||||
import { DashboardCursorSync } from 'lib/uPlotV2/plugins/TooltipPlugin/types';
|
||||
|
||||
interface BaseChartProps {
|
||||
width: number;
|
||||
@@ -16,8 +13,7 @@ interface BaseChartProps {
|
||||
canPinTooltip?: boolean;
|
||||
yAxisUnit?: string;
|
||||
decimalPrecision?: PrecisionOption;
|
||||
pinnedTooltipElement?: (clickData: TooltipClickData) => React.ReactNode;
|
||||
customTooltip?: (props: TooltipRenderArgs) => React.ReactNode;
|
||||
renderTooltip?: (props: TooltipRenderArgs) => React.ReactNode;
|
||||
'data-testid'?: string;
|
||||
}
|
||||
interface UPlotBasedChartProps {
|
||||
|
||||
@@ -66,7 +66,7 @@ export const prepareUPlotConfig = ({
|
||||
widget: Widgets;
|
||||
isDarkMode: boolean;
|
||||
currentQuery: Query;
|
||||
onClick?: OnClickPluginOpts['onClick'];
|
||||
onClick: OnClickPluginOpts['onClick'];
|
||||
onDragSelect: (startTime: number, endTime: number) => void;
|
||||
apiResponse: MetricRangePayloadProps;
|
||||
timezone: Timezone;
|
||||
|
||||
@@ -674,7 +674,7 @@ function GeneralSettings({
|
||||
return (
|
||||
<div className="general-settings-page">
|
||||
<div className="general-settings-header">
|
||||
<span className="general-settings-title">Workspace</span>
|
||||
<span className="general-settings-title">General</span>
|
||||
<span className="general-settings-subtitle">
|
||||
Manage your workspace settings.
|
||||
</span>
|
||||
|
||||
@@ -81,18 +81,7 @@ function FullView({
|
||||
setCurrentGraphRef(fullViewRef);
|
||||
}, [setCurrentGraphRef]);
|
||||
|
||||
const {
|
||||
selectedDashboard,
|
||||
isDashboardLocked,
|
||||
setColumnWidths,
|
||||
} = useDashboard();
|
||||
|
||||
const onColumnWidthsChange = useCallback(
|
||||
(widths: Record<string, number>) => {
|
||||
setColumnWidths((prev) => ({ ...prev, [widget.id]: widths }));
|
||||
},
|
||||
[setColumnWidths, widget.id],
|
||||
);
|
||||
const { selectedDashboard, isDashboardLocked } = useDashboard();
|
||||
const { dashboardVariables } = useDashboardVariables();
|
||||
const { user } = useAppContext();
|
||||
|
||||
@@ -392,7 +381,6 @@ function FullView({
|
||||
onClickHandler={onClickHandler}
|
||||
enableDrillDown={enableDrillDown}
|
||||
selectedGraph={selectedPanelType}
|
||||
onColumnWidthsChange={onColumnWidthsChange}
|
||||
/>
|
||||
</GraphContainer>
|
||||
</div>
|
||||
|
||||
@@ -168,9 +168,6 @@ jest.mock('providers/Dashboard/Dashboard', () => ({
|
||||
variables: [],
|
||||
},
|
||||
},
|
||||
setLayouts: jest.fn(),
|
||||
setSelectedDashboard: jest.fn(),
|
||||
setColumnWidths: jest.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
|
||||
@@ -101,19 +101,7 @@ function WidgetGraphComponent({
|
||||
|
||||
const navigateToExplorerPages = useNavigateToExplorerPages();
|
||||
|
||||
const {
|
||||
setLayouts,
|
||||
selectedDashboard,
|
||||
setSelectedDashboard,
|
||||
setColumnWidths,
|
||||
} = useDashboard();
|
||||
|
||||
const onColumnWidthsChange = useCallback(
|
||||
(widths: Record<string, number>) => {
|
||||
setColumnWidths((prev) => ({ ...prev, [widget.id]: widths }));
|
||||
},
|
||||
[setColumnWidths, widget.id],
|
||||
);
|
||||
const { setLayouts, selectedDashboard, setSelectedDashboard } = useDashboard();
|
||||
|
||||
const onToggleModal = useCallback(
|
||||
(func: Dispatch<SetStateAction<boolean>>) => {
|
||||
@@ -436,7 +424,6 @@ function WidgetGraphComponent({
|
||||
customSeries={customSeries}
|
||||
customOnRowClick={customOnRowClick}
|
||||
enableDrillDown={enableDrillDown}
|
||||
onColumnWidthsChange={onColumnWidthsChange}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -45,8 +45,6 @@ function GridTableComponent({
|
||||
onOpenTraceBtnClick,
|
||||
customOnRowClick,
|
||||
widgetId,
|
||||
columnWidths,
|
||||
onColumnWidthsChange,
|
||||
panelType,
|
||||
queryRangeRequest,
|
||||
decimalPrecision,
|
||||
@@ -286,8 +284,6 @@ function GridTableComponent({
|
||||
dataSource={dataSource}
|
||||
sticky={sticky}
|
||||
widgetId={widgetId}
|
||||
columnWidths={columnWidths}
|
||||
onColumnWidthsChange={onColumnWidthsChange}
|
||||
panelType={panelType}
|
||||
queryRangeRequest={queryRangeRequest}
|
||||
onRow={
|
||||
|
||||
@@ -24,8 +24,6 @@ export type GridTableComponentProps = {
|
||||
onOpenTraceBtnClick?: (record: RowData) => void;
|
||||
customOnRowClick?: (record: RowData) => void;
|
||||
widgetId?: string;
|
||||
columnWidths?: Record<string, number>;
|
||||
onColumnWidthsChange?: (widths: Record<string, number>) => void;
|
||||
renderColumnCell?: QueryTableProps['renderColumnCell'];
|
||||
customColTitles?: Record<string, string>;
|
||||
enableDrillDown?: boolean;
|
||||
|
||||
@@ -33,7 +33,6 @@ function LogsPanelComponent({
|
||||
widget,
|
||||
setRequestData,
|
||||
queryResponse,
|
||||
onColumnWidthsChange,
|
||||
}: LogsPanelComponentProps): JSX.Element {
|
||||
const [pageSize, setPageSize] = useState<number>(10);
|
||||
const [offset, setOffset] = useState<number>(0);
|
||||
@@ -146,8 +145,8 @@ function LogsPanelComponent({
|
||||
columns={columns}
|
||||
onRow={handleRow}
|
||||
rowKey={(record): string => record.id}
|
||||
columnWidths={widget.columnWidths}
|
||||
onColumnWidthsChange={onColumnWidthsChange}
|
||||
widgetId={widget.id}
|
||||
shouldPersistColumnWidths
|
||||
/>
|
||||
</OverlayScrollbar>
|
||||
</div>
|
||||
@@ -190,7 +189,6 @@ export type LogsPanelComponentProps = {
|
||||
Error
|
||||
>;
|
||||
widget: Widgets;
|
||||
onColumnWidthsChange?: (widths: Record<string, number>) => void;
|
||||
};
|
||||
|
||||
export default LogsPanelComponent;
|
||||
|
||||
@@ -161,7 +161,7 @@ function MySettings(): JSX.Element {
|
||||
<div className="my-settings-container">
|
||||
<div className="user-info-section">
|
||||
<div className="user-info-section-header">
|
||||
<div className="user-info-section-title">Account </div>
|
||||
<div className="user-info-section-title">General </div>
|
||||
|
||||
<div className="user-info-section-subtitle">
|
||||
Manage your account settings.
|
||||
|
||||
@@ -8,7 +8,6 @@ function ListPanelWrapper({
|
||||
widget,
|
||||
queryResponse,
|
||||
setRequestData,
|
||||
onColumnWidthsChange,
|
||||
}: PanelWrapperProps): JSX.Element {
|
||||
const dataSource = widget.query.builder?.queryData[0]?.dataSource;
|
||||
|
||||
@@ -22,7 +21,6 @@ function ListPanelWrapper({
|
||||
widget={widget}
|
||||
queryResponse={queryResponse}
|
||||
setRequestData={setRequestData}
|
||||
onColumnWidthsChange={onColumnWidthsChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -31,7 +29,6 @@ function ListPanelWrapper({
|
||||
widget={widget}
|
||||
queryResponse={queryResponse}
|
||||
setRequestData={setRequestData}
|
||||
onColumnWidthsChange={onColumnWidthsChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@ function PanelWrapper({
|
||||
customOnRowClick,
|
||||
panelMode,
|
||||
enableDrillDown = false,
|
||||
onColumnWidthsChange,
|
||||
}: PanelWrapperProps): JSX.Element {
|
||||
const Component = PanelTypeVsPanelWrapper[
|
||||
selectedGraph || widget.panelTypes
|
||||
@@ -59,7 +58,6 @@ function PanelWrapper({
|
||||
customOnRowClick={customOnRowClick}
|
||||
customSeries={customSeries}
|
||||
enableDrillDown={enableDrillDown}
|
||||
onColumnWidthsChange={onColumnWidthsChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ function TablePanelWrapper({
|
||||
onOpenTraceBtnClick,
|
||||
customOnRowClick,
|
||||
enableDrillDown = false,
|
||||
onColumnWidthsChange,
|
||||
}: PanelWrapperProps): JSX.Element {
|
||||
const panelData =
|
||||
(queryResponse.data?.payload?.data?.result?.[0] as any)?.table || [];
|
||||
@@ -35,8 +34,6 @@ function TablePanelWrapper({
|
||||
onOpenTraceBtnClick={onOpenTraceBtnClick}
|
||||
customOnRowClick={customOnRowClick}
|
||||
widgetId={widget.id}
|
||||
columnWidths={widget.columnWidths}
|
||||
onColumnWidthsChange={onColumnWidthsChange}
|
||||
renderColumnCell={widget.renderColumnCell}
|
||||
customColTitles={widget.customColTitles}
|
||||
contextLinks={widget.contextLinks}
|
||||
|
||||
@@ -29,7 +29,6 @@ export type PanelWrapperProps = {
|
||||
customSeries?: (data: QueryData[]) => uPlot.Series[];
|
||||
enableDrillDown?: boolean;
|
||||
panelMode: PanelMode;
|
||||
onColumnWidthsChange?: (widths: Record<string, number>) => void;
|
||||
};
|
||||
|
||||
export type TooltipData = {
|
||||
|
||||
@@ -24,8 +24,6 @@ export type QueryTableProps = Omit<
|
||||
sticky?: TableProps<RowData>['sticky'];
|
||||
searchTerm?: string;
|
||||
widgetId?: string;
|
||||
columnWidths?: Record<string, number>;
|
||||
onColumnWidthsChange?: (widths: Record<string, number>) => void;
|
||||
enableDrillDown?: boolean;
|
||||
contextLinks?: ContextLinksData;
|
||||
panelType?: PANEL_TYPES;
|
||||
|
||||
@@ -28,8 +28,6 @@ export function QueryTable({
|
||||
sticky,
|
||||
searchTerm,
|
||||
widgetId,
|
||||
columnWidths,
|
||||
onColumnWidthsChange,
|
||||
panelType,
|
||||
...props
|
||||
}: QueryTableProps): JSX.Element {
|
||||
@@ -177,8 +175,8 @@ export function QueryTable({
|
||||
dataSource={filterTable === null ? newDataSource : filterTable}
|
||||
scroll={{ x: 'max-content' }}
|
||||
pagination={paginationConfig}
|
||||
columnWidths={columnWidths}
|
||||
onColumnWidthsChange={onColumnWidthsChange}
|
||||
widgetId={widgetId}
|
||||
shouldPersistColumnWidths
|
||||
sticky={sticky}
|
||||
{...props}
|
||||
/>
|
||||
|
||||
@@ -1057,20 +1057,21 @@
|
||||
gap: 8px;
|
||||
|
||||
.user-settings-dropdown-label-text {
|
||||
color: var(--l3-foreground);
|
||||
color: var(--bg-slate-50, #62687c);
|
||||
font-family: Inter;
|
||||
font-size: var(--uppercase-small-500-font-size);
|
||||
font-weight: var(--uppercase-small-500-font-weight);
|
||||
font-size: 10px;
|
||||
font-family: Inter;
|
||||
font-weight: 600;
|
||||
font-style: normal;
|
||||
line-height: 18px;
|
||||
line-height: 18px; /* 163.636% */
|
||||
letter-spacing: 0.88px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.user-settings-dropdown-label-email {
|
||||
color: var(--l1-foreground);
|
||||
color: var(--bg-vanilla-400, #c0c1c3);
|
||||
font-family: Inter;
|
||||
font-size: var(--font-size-xs);
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
line-height: normal;
|
||||
letter-spacing: 0.14px;
|
||||
@@ -1078,7 +1079,7 @@
|
||||
}
|
||||
|
||||
.ant-dropdown-menu-item-divider {
|
||||
background-color: var(--secondary) !important;
|
||||
background-color: var(--bg-slate-500, #161922) !important;
|
||||
}
|
||||
|
||||
.ant-dropdown-menu-item-disabled {
|
||||
@@ -1094,14 +1095,6 @@
|
||||
.help-support-dropdown {
|
||||
.ant-dropdown-menu-item {
|
||||
min-height: 32px;
|
||||
|
||||
.ant-dropdown-menu-title-content {
|
||||
color: var(--l1-foreground) !important;
|
||||
}
|
||||
|
||||
.user-settings-dropdown-logout-section {
|
||||
color: var(--danger-background);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1278,7 +1271,7 @@
|
||||
}
|
||||
|
||||
.help-support-dropdown li.ant-dropdown-menu-item-divider {
|
||||
background-color: var(--secondary) !important;
|
||||
background-color: var(--bg-slate-500, #161922) !important;
|
||||
}
|
||||
|
||||
.lightMode {
|
||||
@@ -1438,6 +1431,22 @@
|
||||
}
|
||||
}
|
||||
|
||||
.settings-dropdown {
|
||||
.user-settings-dropdown-logged-in-section {
|
||||
.user-settings-dropdown-label-text {
|
||||
color: var(--bg-ink-400);
|
||||
}
|
||||
|
||||
.user-settings-dropdown-label-email {
|
||||
color: var(--bg-ink-300);
|
||||
}
|
||||
}
|
||||
|
||||
.ant-dropdown-menu-item-divider {
|
||||
background-color: var(--bg-vanilla-300) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.reorder-shortcut-nav-items-modal {
|
||||
.ant-modal-content {
|
||||
border: 1px solid var(--bg-vanilla-300);
|
||||
@@ -1494,6 +1503,10 @@
|
||||
color: var(--bg-ink-400);
|
||||
}
|
||||
}
|
||||
|
||||
.help-support-dropdown li.ant-dropdown-menu-item-divider {
|
||||
background-color: var(--bg-vanilla-300) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.version-tooltip-overlay {
|
||||
|
||||
@@ -69,7 +69,6 @@ import { routeConfig } from './config';
|
||||
import { getQueryString } from './helper';
|
||||
import {
|
||||
defaultMoreMenuItems,
|
||||
getUserSettingsDropdownMenuItems,
|
||||
helpSupportDropdownMenuItems as DefaultHelpSupportDropdownMenuItems,
|
||||
helpSupportMenuItem,
|
||||
primaryMenuItems,
|
||||
@@ -486,12 +485,48 @@ function SideNav({ isPinned }: { isPinned: boolean }): JSX.Element {
|
||||
|
||||
const userSettingsDropdownMenuItems: MenuProps['items'] = useMemo(
|
||||
() =>
|
||||
getUserSettingsDropdownMenuItems({
|
||||
userEmail: user.email,
|
||||
isWorkspaceBlocked,
|
||||
isEnterpriseSelfHostedUser,
|
||||
isCommunityEnterpriseUser,
|
||||
}),
|
||||
[
|
||||
{
|
||||
key: 'label',
|
||||
label: (
|
||||
<div className="user-settings-dropdown-logged-in-section">
|
||||
<span className="user-settings-dropdown-label-text">LOGGED IN AS</span>
|
||||
<span className="user-settings-dropdown-label-email">{user.email}</span>
|
||||
</div>
|
||||
),
|
||||
disabled: true,
|
||||
dataTestId: 'logged-in-as-nav-item',
|
||||
},
|
||||
{ type: 'divider' as const },
|
||||
{
|
||||
key: 'account',
|
||||
label: 'Account Settings',
|
||||
dataTestId: 'account-settings-nav-item',
|
||||
},
|
||||
{
|
||||
key: 'workspace',
|
||||
label: 'Workspace Settings',
|
||||
disabled: isWorkspaceBlocked,
|
||||
dataTestId: 'workspace-settings-nav-item',
|
||||
},
|
||||
...(isEnterpriseSelfHostedUser || isCommunityEnterpriseUser
|
||||
? [
|
||||
{
|
||||
key: 'license',
|
||||
label: 'Manage License',
|
||||
dataTestId: 'manage-license-nav-item',
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{ type: 'divider' as const },
|
||||
{
|
||||
key: 'logout',
|
||||
label: (
|
||||
<span className="user-settings-dropdown-logout-section">Sign out</span>
|
||||
),
|
||||
dataTestId: 'logout-nav-item',
|
||||
},
|
||||
].filter(Boolean),
|
||||
[
|
||||
isEnterpriseSelfHostedUser,
|
||||
isCommunityEnterpriseUser,
|
||||
@@ -821,6 +856,9 @@ function SideNav({ isPinned }: { isPinned: boolean }): JSX.Element {
|
||||
});
|
||||
|
||||
switch (item.key) {
|
||||
case ROUTES.SHORTCUTS:
|
||||
history.push(ROUTES.SHORTCUTS);
|
||||
break;
|
||||
case 'invite-collaborators':
|
||||
history.push(`${ROUTES.ORG_SETTINGS}#invite-team-members`);
|
||||
break;
|
||||
@@ -840,7 +878,7 @@ function SideNav({ isPinned }: { isPinned: boolean }): JSX.Element {
|
||||
};
|
||||
|
||||
const handleSettingsMenuItemClick = (info: SidebarItem): void => {
|
||||
const item = (userSettingsDropdownMenuItems ?? []).find(
|
||||
const item = userSettingsDropdownMenuItems.find(
|
||||
(item) => item?.key === info.key,
|
||||
);
|
||||
let menuLabel = '';
|
||||
@@ -866,9 +904,6 @@ function SideNav({ isPinned }: { isPinned: boolean }): JSX.Element {
|
||||
case 'license':
|
||||
history.push(ROUTES.LIST_LICENSES);
|
||||
break;
|
||||
case 'keyboard-shortcuts':
|
||||
history.push(ROUTES.SHORTCUTS);
|
||||
break;
|
||||
case 'logout':
|
||||
Logout();
|
||||
break;
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
import { getUserSettingsDropdownMenuItems } from 'container/SideNav/menuItems';
|
||||
|
||||
const BASE_PARAMS = {
|
||||
userEmail: 'test@signoz.io',
|
||||
isWorkspaceBlocked: false,
|
||||
isEnterpriseSelfHostedUser: false,
|
||||
isCommunityEnterpriseUser: false,
|
||||
};
|
||||
|
||||
describe('getUserSettingsDropdownMenuItems', () => {
|
||||
it('always includes logged-in-as label, workspace, account, keyboard shortcuts, and sign out', () => {
|
||||
const items = getUserSettingsDropdownMenuItems(BASE_PARAMS);
|
||||
const keys = items?.map((item) => item?.key);
|
||||
|
||||
expect(keys).toContain('label');
|
||||
expect(keys).toContain('workspace');
|
||||
expect(keys).toContain('account');
|
||||
expect(keys).toContain('keyboard-shortcuts');
|
||||
expect(keys).toContain('logout');
|
||||
|
||||
// workspace item is enabled when workspace is not blocked
|
||||
const workspaceItem = items?.find(
|
||||
(item: any) => item.key === 'workspace',
|
||||
) as any;
|
||||
|
||||
expect(workspaceItem?.disabled).toBe(false);
|
||||
|
||||
// does not include license item for regular cloud user
|
||||
expect(keys).not.toContain('license');
|
||||
});
|
||||
|
||||
it('includes manage license item for enterprise self-hosted users', () => {
|
||||
const items = getUserSettingsDropdownMenuItems({
|
||||
...BASE_PARAMS,
|
||||
isEnterpriseSelfHostedUser: true,
|
||||
});
|
||||
const keys = items?.map((item) => item?.key);
|
||||
|
||||
expect(keys).toContain('license');
|
||||
});
|
||||
|
||||
it('includes manage license item for community enterprise users', () => {
|
||||
const items = getUserSettingsDropdownMenuItems({
|
||||
...BASE_PARAMS,
|
||||
isCommunityEnterpriseUser: true,
|
||||
});
|
||||
const keys = items?.map((item) => item?.key);
|
||||
|
||||
expect(keys).toContain('license');
|
||||
});
|
||||
|
||||
it('workspace item is disabled when workspace is blocked', () => {
|
||||
const items = getUserSettingsDropdownMenuItems({
|
||||
...BASE_PARAMS,
|
||||
isWorkspaceBlocked: true,
|
||||
});
|
||||
const workspaceItem = items?.find(
|
||||
(item: any) => item.key === 'workspace',
|
||||
) as any;
|
||||
|
||||
expect(workspaceItem?.disabled).toBe(true);
|
||||
});
|
||||
|
||||
it('returns items in correct order: label, divider, workspace, account, ..., shortcuts, divider, logout', () => {
|
||||
const items = getUserSettingsDropdownMenuItems(BASE_PARAMS) ?? [];
|
||||
const keys = items.map((item: any) => item.key ?? item.type);
|
||||
|
||||
expect(keys[0]).toBe('label');
|
||||
expect(keys[1]).toBe('divider');
|
||||
expect(keys[2]).toBe('workspace');
|
||||
expect(keys[3]).toBe('account');
|
||||
expect(keys[keys.length - 1]).toBe('logout');
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,4 @@
|
||||
import { RocketOutlined } from '@ant-design/icons';
|
||||
import { Style } from '@signozhq/design-tokens';
|
||||
import { MenuProps } from 'antd';
|
||||
import ROUTES from 'constants/routes';
|
||||
import {
|
||||
ArrowUpRight,
|
||||
@@ -10,7 +8,6 @@ import {
|
||||
Book,
|
||||
Boxes,
|
||||
BugIcon,
|
||||
Building2,
|
||||
ChartArea,
|
||||
Cloudy,
|
||||
DraftingCompass,
|
||||
@@ -23,12 +20,12 @@ import {
|
||||
Layers2,
|
||||
LayoutGrid,
|
||||
ListMinus,
|
||||
LogOut,
|
||||
MessageSquareText,
|
||||
Plus,
|
||||
Receipt,
|
||||
Route,
|
||||
ScrollText,
|
||||
Search,
|
||||
Settings,
|
||||
Shield,
|
||||
Slack,
|
||||
@@ -37,11 +34,7 @@ import {
|
||||
UserPlus,
|
||||
} from 'lucide-react';
|
||||
|
||||
import {
|
||||
SecondaryMenuItemKey,
|
||||
SettingsNavSection,
|
||||
SidebarItem,
|
||||
} from './sideNav.types';
|
||||
import { SecondaryMenuItemKey, SidebarItem } from './sideNav.types';
|
||||
|
||||
export const getStartedMenuItem = {
|
||||
key: ROUTES.GET_STARTED,
|
||||
@@ -195,6 +188,12 @@ export const primaryMenuItems: SidebarItem[] = [
|
||||
icon: <Home size={16} />,
|
||||
itemKey: 'home',
|
||||
},
|
||||
{
|
||||
key: 'quick-search',
|
||||
label: 'Search',
|
||||
icon: <Search size={16} />,
|
||||
itemKey: 'quick-search',
|
||||
},
|
||||
{
|
||||
key: ROUTES.LIST_ALL_ALERT,
|
||||
label: 'Alerts',
|
||||
@@ -297,100 +296,77 @@ export const defaultMoreMenuItems: SidebarItem[] = [
|
||||
},
|
||||
];
|
||||
|
||||
export const settingsNavSections: SettingsNavSection[] = [
|
||||
export const settingsMenuItems: SidebarItem[] = [
|
||||
{
|
||||
key: 'general',
|
||||
items: [
|
||||
{
|
||||
key: ROUTES.SETTINGS,
|
||||
label: 'Workspace',
|
||||
icon: <Settings size={16} />,
|
||||
isEnabled: true,
|
||||
itemKey: 'workspace',
|
||||
},
|
||||
{
|
||||
key: ROUTES.MY_SETTINGS,
|
||||
label: 'Account',
|
||||
icon: <User size={16} />,
|
||||
isEnabled: true,
|
||||
itemKey: 'account',
|
||||
},
|
||||
{
|
||||
key: ROUTES.ALL_CHANNELS,
|
||||
label: 'Notification Channels',
|
||||
icon: <FileKey2 size={16} />,
|
||||
isEnabled: true,
|
||||
itemKey: 'notification-channels',
|
||||
},
|
||||
{
|
||||
key: ROUTES.BILLING,
|
||||
label: 'Billing',
|
||||
icon: <Receipt size={16} />,
|
||||
isEnabled: false,
|
||||
itemKey: 'billing',
|
||||
},
|
||||
{
|
||||
key: ROUTES.INTEGRATIONS,
|
||||
label: 'Integrations',
|
||||
icon: <Unplug size={16} />,
|
||||
isEnabled: false,
|
||||
itemKey: 'integrations',
|
||||
},
|
||||
],
|
||||
key: ROUTES.SETTINGS,
|
||||
label: 'General',
|
||||
icon: <Settings size={16} />,
|
||||
isEnabled: true,
|
||||
itemKey: 'general',
|
||||
},
|
||||
{
|
||||
key: ROUTES.BILLING,
|
||||
label: 'Billing',
|
||||
icon: <Receipt size={16} />,
|
||||
isEnabled: false,
|
||||
itemKey: 'billing',
|
||||
},
|
||||
{
|
||||
key: ROUTES.ROLES_SETTINGS,
|
||||
label: 'Roles',
|
||||
icon: <Shield size={16} />,
|
||||
isEnabled: false,
|
||||
itemKey: 'roles',
|
||||
},
|
||||
{
|
||||
key: ROUTES.ORG_SETTINGS,
|
||||
label: 'Members & SSO',
|
||||
icon: <User size={16} />,
|
||||
isEnabled: false,
|
||||
itemKey: 'members-sso',
|
||||
},
|
||||
|
||||
{
|
||||
key: 'identity-access',
|
||||
title: 'Identity & Access',
|
||||
items: [
|
||||
{
|
||||
key: ROUTES.ROLES_SETTINGS,
|
||||
label: 'Roles',
|
||||
icon: <Shield size={16} />,
|
||||
isEnabled: false,
|
||||
itemKey: 'roles',
|
||||
},
|
||||
{
|
||||
key: ROUTES.API_KEYS,
|
||||
label: 'API Keys',
|
||||
icon: <Key size={16} />,
|
||||
isEnabled: false,
|
||||
itemKey: 'api-keys',
|
||||
},
|
||||
{
|
||||
key: ROUTES.INGESTION_SETTINGS,
|
||||
label: 'Ingestion',
|
||||
icon: <RocketOutlined rotate={45} />,
|
||||
isEnabled: false,
|
||||
itemKey: 'ingestion',
|
||||
},
|
||||
],
|
||||
key: ROUTES.INTEGRATIONS,
|
||||
label: 'Integrations',
|
||||
icon: <Unplug size={16} />,
|
||||
isEnabled: false,
|
||||
itemKey: 'integrations',
|
||||
},
|
||||
{
|
||||
key: 'authentication',
|
||||
title: 'Authentication',
|
||||
items: [
|
||||
{
|
||||
key: ROUTES.ORG_SETTINGS,
|
||||
label: 'Members & SSO',
|
||||
icon: <User size={16} />,
|
||||
isEnabled: false,
|
||||
itemKey: 'members-sso',
|
||||
},
|
||||
],
|
||||
key: ROUTES.ALL_CHANNELS,
|
||||
label: 'Notification Channels',
|
||||
icon: <FileKey2 size={16} />,
|
||||
isEnabled: true,
|
||||
itemKey: 'notification-channels',
|
||||
},
|
||||
{
|
||||
key: 'shortcuts',
|
||||
hasDivider: true,
|
||||
items: [
|
||||
{
|
||||
key: ROUTES.SHORTCUTS,
|
||||
label: 'Keyboard Shortcuts',
|
||||
icon: <Keyboard size={16} />,
|
||||
isEnabled: true,
|
||||
itemKey: 'keyboard-shortcuts',
|
||||
},
|
||||
],
|
||||
key: ROUTES.API_KEYS,
|
||||
label: 'API Keys',
|
||||
icon: <Key size={16} />,
|
||||
isEnabled: false,
|
||||
itemKey: 'api-keys',
|
||||
},
|
||||
{
|
||||
key: ROUTES.INGESTION_SETTINGS,
|
||||
label: 'Ingestion',
|
||||
icon: <RocketOutlined rotate={45} />,
|
||||
isEnabled: false,
|
||||
itemKey: 'ingestion',
|
||||
},
|
||||
{
|
||||
key: ROUTES.MY_SETTINGS,
|
||||
label: 'Account Settings',
|
||||
icon: <User size={16} />,
|
||||
isEnabled: true,
|
||||
itemKey: 'account-settings',
|
||||
},
|
||||
{
|
||||
key: ROUTES.SHORTCUTS,
|
||||
label: 'Keyboard Shortcuts',
|
||||
icon: <Layers2 size={16} />,
|
||||
isEnabled: true,
|
||||
itemKey: 'keyboard-shortcuts',
|
||||
},
|
||||
];
|
||||
|
||||
@@ -441,6 +417,12 @@ export const helpSupportDropdownMenuItems: SidebarItem[] = [
|
||||
icon: <MessageSquareText size={14} />,
|
||||
itemKey: 'chat-support',
|
||||
},
|
||||
{
|
||||
key: ROUTES.SHORTCUTS,
|
||||
label: 'Keyboard Shortcuts',
|
||||
icon: <Keyboard size={14} />,
|
||||
itemKey: 'keyboard-shortcuts',
|
||||
},
|
||||
{
|
||||
key: 'invite-collaborators',
|
||||
label: 'Invite a Team Member',
|
||||
@@ -449,78 +431,6 @@ export const helpSupportDropdownMenuItems: SidebarItem[] = [
|
||||
},
|
||||
];
|
||||
|
||||
export interface UserSettingsMenuItemsParams {
|
||||
userEmail: string;
|
||||
isWorkspaceBlocked: boolean;
|
||||
isEnterpriseSelfHostedUser: boolean;
|
||||
isCommunityEnterpriseUser: boolean;
|
||||
}
|
||||
|
||||
export const getUserSettingsDropdownMenuItems = ({
|
||||
userEmail,
|
||||
isWorkspaceBlocked,
|
||||
isEnterpriseSelfHostedUser,
|
||||
isCommunityEnterpriseUser,
|
||||
}: UserSettingsMenuItemsParams): MenuProps['items'] =>
|
||||
[
|
||||
{
|
||||
key: 'label',
|
||||
label: (
|
||||
<div className="user-settings-dropdown-logged-in-section">
|
||||
<span className="user-settings-dropdown-label-text">LOGGED IN AS</span>
|
||||
<span className="user-settings-dropdown-label-email">{userEmail}</span>
|
||||
</div>
|
||||
),
|
||||
disabled: true,
|
||||
dataTestId: 'logged-in-as-nav-item',
|
||||
},
|
||||
{ type: 'divider' as const },
|
||||
{
|
||||
key: 'workspace',
|
||||
label: 'Workspace Settings',
|
||||
icon: <Building2 size={14} color={Style.L1_FOREGROUND} />,
|
||||
disabled: isWorkspaceBlocked,
|
||||
dataTestId: 'workspace-settings-nav-item',
|
||||
},
|
||||
{
|
||||
key: 'account',
|
||||
label: 'Account Settings',
|
||||
icon: <User size={14} color={Style.L1_FOREGROUND} />,
|
||||
dataTestId: 'account-settings-nav-item',
|
||||
},
|
||||
...(isEnterpriseSelfHostedUser || isCommunityEnterpriseUser
|
||||
? [
|
||||
{
|
||||
key: 'license',
|
||||
label: 'Manage License',
|
||||
icon: <Shield size={14} color={Style.L1_FOREGROUND} />,
|
||||
dataTestId: 'manage-license-nav-item',
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
key: 'keyboard-shortcuts',
|
||||
label: 'Keyboard Shortcuts',
|
||||
icon: <Keyboard size={14} color={Style.L1_FOREGROUND} />,
|
||||
dataTestId: 'keyboard-shortcuts-nav-item',
|
||||
},
|
||||
{ type: 'divider' as const },
|
||||
{
|
||||
key: 'logout',
|
||||
label: (
|
||||
<span className="user-settings-dropdown-logout-section">Sign out</span>
|
||||
),
|
||||
icon: (
|
||||
<LogOut
|
||||
size={14}
|
||||
className="user-settings-dropdown-logout-section"
|
||||
color={Style.DANGER_BACKGROUND}
|
||||
/>
|
||||
),
|
||||
dataTestId: 'logout-nav-item',
|
||||
},
|
||||
].filter(Boolean);
|
||||
|
||||
/** Mapping of some newly added routes and their corresponding active sidebar menu key */
|
||||
export const NEW_ROUTES_MENU_ITEM_KEY_MAP: Record<string, string> = {
|
||||
[ROUTES.TRACE]: ROUTES.TRACES_EXPLORER,
|
||||
|
||||
@@ -24,13 +24,6 @@ export interface SidebarItem {
|
||||
|
||||
export const CHANGELOG_LABEL = 'Full Changelog';
|
||||
|
||||
export interface SettingsNavSection {
|
||||
title?: string;
|
||||
items: SidebarItem[];
|
||||
key: string;
|
||||
hasDivider?: boolean;
|
||||
}
|
||||
|
||||
export interface DropdownSeparator {
|
||||
type: 'divider' | 'group';
|
||||
label?: ReactNode;
|
||||
|
||||
@@ -35,7 +35,6 @@ function TracesTableComponent({
|
||||
widget,
|
||||
queryResponse,
|
||||
setRequestData,
|
||||
onColumnWidthsChange,
|
||||
}: TracesTableComponentProps): JSX.Element {
|
||||
const [pagination, setPagination] = useState<Pagination>({
|
||||
offset: 0,
|
||||
@@ -132,8 +131,8 @@ function TracesTableComponent({
|
||||
columns={columns}
|
||||
onRow={handleRow}
|
||||
sticky
|
||||
columnWidths={widget.columnWidths}
|
||||
onColumnWidthsChange={onColumnWidthsChange}
|
||||
widgetId={widget.id}
|
||||
shouldPersistColumnWidths
|
||||
/>
|
||||
</OverlayScrollbar>
|
||||
</div>
|
||||
@@ -176,7 +175,6 @@ export type TracesTableComponentProps = {
|
||||
>;
|
||||
widget: Widgets;
|
||||
setRequestData: Dispatch<SetStateAction<GetQueryResultsProps>>;
|
||||
onColumnWidthsChange?: (widths: Record<string, number>) => void;
|
||||
};
|
||||
|
||||
export default TracesTableComponent;
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
/* eslint-disable no-restricted-imports */
|
||||
import { useQuery, UseQueryOptions, UseQueryResult } from 'react-query';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { getValueSuggestions } from 'api/querySuggestions/getValueSuggestion';
|
||||
import { AxiosError, AxiosResponse } from 'axios';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { QueryKeyValueSuggestionsResponseProps } from 'types/api/querySuggestions/types';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
|
||||
export const useGetQueryKeyValueSuggestions = ({
|
||||
key,
|
||||
@@ -9,6 +13,7 @@ export const useGetQueryKeyValueSuggestions = ({
|
||||
searchText,
|
||||
signalSource,
|
||||
metricName,
|
||||
existingQuery,
|
||||
options,
|
||||
}: {
|
||||
key: string;
|
||||
@@ -20,11 +25,24 @@ export const useGetQueryKeyValueSuggestions = ({
|
||||
AxiosError
|
||||
>;
|
||||
metricName?: string;
|
||||
existingQuery?: string;
|
||||
}): UseQueryResult<
|
||||
AxiosResponse<QueryKeyValueSuggestionsResponseProps>,
|
||||
AxiosError
|
||||
> =>
|
||||
useQuery<AxiosResponse<QueryKeyValueSuggestionsResponseProps>, AxiosError>({
|
||||
> => {
|
||||
const { minTime, maxTime } = useSelector<AppState, GlobalReducer>(
|
||||
(state) => state.globalTime,
|
||||
);
|
||||
|
||||
const timeRangeKey =
|
||||
minTime != null && maxTime != null
|
||||
? `${Math.floor(minTime / 1e9)}-${Math.floor(maxTime / 1e9)}`
|
||||
: null;
|
||||
|
||||
return useQuery<
|
||||
AxiosResponse<QueryKeyValueSuggestionsResponseProps>,
|
||||
AxiosError
|
||||
>({
|
||||
queryKey: [
|
||||
'queryKeyValueSuggestions',
|
||||
key,
|
||||
@@ -32,6 +50,7 @@ export const useGetQueryKeyValueSuggestions = ({
|
||||
searchText,
|
||||
signalSource,
|
||||
metricName,
|
||||
timeRangeKey,
|
||||
],
|
||||
queryFn: () =>
|
||||
getValueSuggestions({
|
||||
@@ -40,6 +59,8 @@ export const useGetQueryKeyValueSuggestions = ({
|
||||
searchText: searchText || '',
|
||||
signalSource: signalSource as 'meter' | '',
|
||||
metricName: metricName || '',
|
||||
existingQuery,
|
||||
}),
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -92,7 +92,6 @@ function renderTooltip(props: Partial<TooltipTestProps> = {}): RenderResult {
|
||||
isPinned: false,
|
||||
dismiss: jest.fn(),
|
||||
viaSync: false,
|
||||
clickData: null,
|
||||
} as TooltipTestProps;
|
||||
|
||||
return render(
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useLayoutEffect, useRef, useState } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import cx from 'classnames';
|
||||
import { getFocusedSeriesAtPosition } from 'lib/uPlotLib/plugins/onClickPlugin';
|
||||
import uPlot from 'uplot';
|
||||
|
||||
import {
|
||||
@@ -16,7 +15,6 @@ import {
|
||||
} from './tooltipController';
|
||||
import {
|
||||
DashboardCursorSync,
|
||||
TooltipClickData,
|
||||
TooltipControllerContext,
|
||||
TooltipControllerState,
|
||||
TooltipLayoutInfo,
|
||||
@@ -40,18 +38,15 @@ export default function TooltipPlugin({
|
||||
maxHeight = 400,
|
||||
syncMode = DashboardCursorSync.None,
|
||||
syncKey = '_tooltip_sync_global_',
|
||||
pinnedTooltipElement,
|
||||
canPinTooltip = false,
|
||||
}: TooltipPluginProps): JSX.Element | null {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const portalRoot = useRef<HTMLElement>(document.body);
|
||||
const rafId = useRef<number | null>(null);
|
||||
const dismissTimeoutId = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
const layoutRef = useRef<TooltipLayoutInfo>();
|
||||
const renderRef = useRef(render);
|
||||
renderRef.current = render;
|
||||
const [portalRoot, setPortalRoot] = useState<HTMLElement>(
|
||||
(document.fullscreenElement as HTMLElement) ?? document.body,
|
||||
);
|
||||
|
||||
// React-managed snapshot of what should be rendered. The controller
|
||||
// owns the interaction state and calls `updateState` when a visible
|
||||
@@ -78,6 +73,11 @@ export default function TooltipPlugin({
|
||||
layoutRef.current?.observer.disconnect();
|
||||
layoutRef.current = createLayoutObserver(layoutRef);
|
||||
|
||||
/**
|
||||
* Plot lifecycle and GC: viewState uses hasPlot (boolean), not the plot
|
||||
* reference; clearPlotReferences runs in cleanup so
|
||||
* detached canvases can be garbage collected.
|
||||
*/
|
||||
// Controller holds the mutable interaction state for this tooltip
|
||||
// instance. It is intentionally *not* React state so uPlot hooks
|
||||
// and DOM listeners can update it freely without triggering a
|
||||
@@ -85,9 +85,7 @@ export default function TooltipPlugin({
|
||||
const controller: TooltipControllerState = createInitialControllerState();
|
||||
|
||||
/**
|
||||
* Plot lifecycle and GC: viewState uses hasPlot (boolean), not the plot
|
||||
* reference; clearPlotReferences runs in cleanup so
|
||||
* detached canvases can be garbage collected.
|
||||
* Clear plot references so detached canvases can be garbage collected.
|
||||
*/
|
||||
const clearPlotReferences = (): void => {
|
||||
controller.plot = null;
|
||||
@@ -159,7 +157,6 @@ export default function TooltipPlugin({
|
||||
const isPinnedBeforeDismiss = controller.pinned;
|
||||
controller.pinned = false;
|
||||
controller.hoverActive = false;
|
||||
controller.clickData = null;
|
||||
const plot = getPlot(controller);
|
||||
if (plot) {
|
||||
plot.setCursor({ left: -10, top: -10 });
|
||||
@@ -199,7 +196,6 @@ export default function TooltipPlugin({
|
||||
style: controller.style,
|
||||
isPinned: controller.pinned,
|
||||
isHovering: controller.hoverActive,
|
||||
clickData: controller.clickData,
|
||||
contents: createTooltipContents(),
|
||||
dismiss: dismissTooltip,
|
||||
});
|
||||
@@ -272,37 +268,6 @@ export default function TooltipPlugin({
|
||||
!controller.pinned &&
|
||||
controller.focusedSeriesIndex != null
|
||||
) {
|
||||
const xValue = plot.posToVal(event.offsetX, 'x');
|
||||
const yValue = plot.posToVal(event.offsetY, 'y');
|
||||
const focusedSeries = getFocusedSeriesAtPosition(event, plot);
|
||||
|
||||
let clickedDataTimestamp = xValue;
|
||||
if (focusedSeries) {
|
||||
const dataIndex = plot.posToIdx(event.offsetX);
|
||||
const xSeriesData = plot.data[0];
|
||||
if (
|
||||
xSeriesData &&
|
||||
dataIndex >= 0 &&
|
||||
dataIndex < xSeriesData.length &&
|
||||
xSeriesData[dataIndex] !== undefined
|
||||
) {
|
||||
clickedDataTimestamp = xSeriesData[dataIndex];
|
||||
}
|
||||
}
|
||||
|
||||
const clickData: TooltipClickData = {
|
||||
xValue,
|
||||
yValue,
|
||||
focusedSeries,
|
||||
clickedDataTimestamp,
|
||||
mouseX: event.offsetX,
|
||||
mouseY: event.offsetY,
|
||||
absoluteMouseX: event.clientX,
|
||||
absoluteMouseY: event.clientY,
|
||||
};
|
||||
|
||||
controller.clickData = clickData;
|
||||
|
||||
setTimeout(() => {
|
||||
controller.pinned = true;
|
||||
scheduleRender(true);
|
||||
@@ -391,19 +356,6 @@ export default function TooltipPlugin({
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [config]);
|
||||
|
||||
const resolvePortalRoot = useCallback((): void => {
|
||||
setPortalRoot((document.fullscreenElement as HTMLElement) ?? document.body);
|
||||
}, []);
|
||||
|
||||
useLayoutEffect((): (() => void) => {
|
||||
resolvePortalRoot();
|
||||
document.addEventListener('fullscreenchange', resolvePortalRoot);
|
||||
|
||||
return (): void => {
|
||||
document.removeEventListener('fullscreenchange', resolvePortalRoot);
|
||||
};
|
||||
}, [resolvePortalRoot]);
|
||||
|
||||
useLayoutEffect((): void => {
|
||||
if (!hasPlot || !layoutRef.current) {
|
||||
return;
|
||||
@@ -422,25 +374,6 @@ export default function TooltipPlugin({
|
||||
}
|
||||
}, [isHovering, hasPlot]);
|
||||
|
||||
const tooltipBody = useMemo(() => {
|
||||
if (isPinned && pinnedTooltipElement != null && viewState.clickData != null) {
|
||||
return pinnedTooltipElement(viewState.clickData);
|
||||
}
|
||||
|
||||
if (isHovering) {
|
||||
return contents;
|
||||
}
|
||||
|
||||
return null;
|
||||
}, [
|
||||
isPinned,
|
||||
pinnedTooltipElement,
|
||||
viewState.clickData,
|
||||
isHovering,
|
||||
contents,
|
||||
]);
|
||||
const isTooltipVisible = isHovering || tooltipBody != null;
|
||||
|
||||
if (!hasPlot) {
|
||||
return null;
|
||||
}
|
||||
@@ -449,7 +382,7 @@ export default function TooltipPlugin({
|
||||
<div
|
||||
className={cx('tooltip-plugin-container', {
|
||||
pinned: isPinned,
|
||||
visible: isTooltipVisible,
|
||||
visible: isHovering,
|
||||
})}
|
||||
style={{
|
||||
...style,
|
||||
@@ -458,12 +391,12 @@ export default function TooltipPlugin({
|
||||
}}
|
||||
aria-live="polite"
|
||||
aria-atomic="true"
|
||||
aria-hidden={!isTooltipVisible}
|
||||
aria-hidden={!isHovering}
|
||||
ref={containerRef}
|
||||
data-testid="tooltip-plugin-container"
|
||||
>
|
||||
{tooltipBody}
|
||||
{isHovering ? contents : null}
|
||||
</div>,
|
||||
portalRoot,
|
||||
portalRoot.current,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ export function createInitialControllerState(): TooltipControllerState {
|
||||
hoverActive: false,
|
||||
isAnySeriesActive: false,
|
||||
pinned: false,
|
||||
clickData: null,
|
||||
style: { transform: '', pointerEvents: 'none' },
|
||||
horizontalOffset: 0,
|
||||
verticalOffset: 0,
|
||||
|
||||
@@ -24,7 +24,6 @@ export interface TooltipViewState {
|
||||
isHovering: boolean;
|
||||
isPinned: boolean;
|
||||
dismiss: () => void;
|
||||
clickData: TooltipClickData | null;
|
||||
contents?: ReactNode;
|
||||
}
|
||||
|
||||
@@ -40,27 +39,10 @@ export interface TooltipPluginProps {
|
||||
syncMode?: DashboardCursorSync;
|
||||
syncKey?: string;
|
||||
render: (args: TooltipRenderArgs) => ReactNode;
|
||||
pinnedTooltipElement?: (clickData: TooltipClickData) => ReactNode;
|
||||
maxWidth?: number;
|
||||
maxHeight?: number;
|
||||
}
|
||||
|
||||
export interface TooltipClickData {
|
||||
xValue: number;
|
||||
yValue: number;
|
||||
focusedSeries: {
|
||||
seriesIndex: number;
|
||||
seriesName: string;
|
||||
value: number;
|
||||
color: string;
|
||||
} | null;
|
||||
clickedDataTimestamp: number;
|
||||
mouseX: number;
|
||||
mouseY: number;
|
||||
absoluteMouseX: number;
|
||||
absoluteMouseY: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mutable, non-React state that drives tooltip behaviour:
|
||||
* - whether the tooltip is active / pinned
|
||||
@@ -77,7 +59,6 @@ export interface TooltipControllerState {
|
||||
hoverActive: boolean;
|
||||
isAnySeriesActive: boolean;
|
||||
pinned: boolean;
|
||||
clickData: TooltipClickData | null;
|
||||
style: TooltipViewState['style'];
|
||||
horizontalOffset: number;
|
||||
verticalOffset: number;
|
||||
|
||||
@@ -125,7 +125,6 @@ export function createInitialViewState(): TooltipViewState {
|
||||
contents: null,
|
||||
hasPlot: false,
|
||||
dismiss: (): void => {},
|
||||
clickData: null,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -9,12 +9,6 @@ import { UPlotConfigBuilder } from '../../config/UPlotConfigBuilder';
|
||||
import TooltipPlugin from '../TooltipPlugin/TooltipPlugin';
|
||||
import { DashboardCursorSync } from '../TooltipPlugin/types';
|
||||
|
||||
// Avoid depending on the full uPlot + onClickPlugin behaviour in these tests.
|
||||
// We only care that pinning logic runs without throwing, not which series is focused.
|
||||
jest.mock('lib/uPlotLib/plugins/onClickPlugin', () => ({
|
||||
getFocusedSeriesAtPosition: jest.fn(() => null),
|
||||
}));
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Mock helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -61,21 +55,11 @@ function createFakePlot(): {
|
||||
over: HTMLDivElement;
|
||||
setCursor: jest.Mock<void, [uPlot.Cursor]>;
|
||||
cursor: { event: Record<string, unknown> };
|
||||
posToVal: jest.Mock<number, [value: number]>;
|
||||
posToIdx: jest.Mock<number, []>;
|
||||
data: [number[], number[]];
|
||||
} {
|
||||
const over = document.createElement('div');
|
||||
|
||||
// Provide the minimal uPlot surface used by TooltipPlugin's pin logic.
|
||||
return {
|
||||
over,
|
||||
over: document.createElement('div'),
|
||||
setCursor: jest.fn(),
|
||||
cursor: { event: {} },
|
||||
// In real uPlot these map overlay coordinates to data-space values.
|
||||
posToVal: jest.fn((value: number) => value),
|
||||
posToIdx: jest.fn(() => 0),
|
||||
data: [[0], [0]],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -188,58 +172,6 @@ describe('TooltipPlugin', () => {
|
||||
expect(container).not.toBeNull();
|
||||
expect(container.parentElement).toBe(document.body);
|
||||
});
|
||||
|
||||
it('moves tooltip portal root to fullscreen element and back on exit', async () => {
|
||||
const config = createConfigMock();
|
||||
let mockedFullscreenElement: Element | null = null;
|
||||
const originalFullscreenElementDescriptor = Object.getOwnPropertyDescriptor(
|
||||
Document.prototype,
|
||||
'fullscreenElement',
|
||||
);
|
||||
Object.defineProperty(Document.prototype, 'fullscreenElement', {
|
||||
configurable: true,
|
||||
get: () => mockedFullscreenElement,
|
||||
});
|
||||
|
||||
renderAndActivateHover(config);
|
||||
|
||||
const container = document.querySelector(
|
||||
'.tooltip-plugin-container',
|
||||
) as HTMLElement;
|
||||
expect(container.parentElement).toBe(document.body);
|
||||
|
||||
const fullscreenRoot = document.createElement('div');
|
||||
document.body.appendChild(fullscreenRoot);
|
||||
|
||||
act(() => {
|
||||
mockedFullscreenElement = fullscreenRoot;
|
||||
document.dispatchEvent(new Event('fullscreenchange'));
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
const updatedContainer = screen.getByTestId('tooltip-plugin-container');
|
||||
expect(updatedContainer.parentElement).toBe(fullscreenRoot);
|
||||
});
|
||||
|
||||
act(() => {
|
||||
mockedFullscreenElement = null;
|
||||
document.dispatchEvent(new Event('fullscreenchange'));
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
const updatedContainer = screen.getByTestId('tooltip-plugin-container');
|
||||
expect(updatedContainer.parentElement).toBe(document.body);
|
||||
});
|
||||
|
||||
if (originalFullscreenElementDescriptor) {
|
||||
Object.defineProperty(
|
||||
Document.prototype,
|
||||
'fullscreenElement',
|
||||
originalFullscreenElementDescriptor,
|
||||
);
|
||||
}
|
||||
fullscreenRoot.remove();
|
||||
});
|
||||
});
|
||||
|
||||
// ---- Pin behaviour ----------------------------------------------------------
|
||||
@@ -266,34 +198,6 @@ describe('TooltipPlugin', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('renders pinnedTooltipElement after pinning and hides hover content', async () => {
|
||||
const config = createConfigMock();
|
||||
const pinnedTooltipElement = jest.fn(() =>
|
||||
React.createElement('div', null, 'pinned-tooltip'),
|
||||
);
|
||||
|
||||
const fakePlot = renderAndActivateHover(
|
||||
config,
|
||||
() => React.createElement('div', null, 'hover-tooltip'),
|
||||
{
|
||||
canPinTooltip: true,
|
||||
pinnedTooltipElement,
|
||||
},
|
||||
);
|
||||
|
||||
expect(screen.getByText('hover-tooltip')).toBeInTheDocument();
|
||||
|
||||
act(() => {
|
||||
fakePlot.over.dispatchEvent(new MouseEvent('click', { bubbles: true }));
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(pinnedTooltipElement).toHaveBeenCalled();
|
||||
expect(screen.getByText('pinned-tooltip')).toBeInTheDocument();
|
||||
expect(screen.queryByText('hover-tooltip')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('dismisses a pinned tooltip via the dismiss callback', async () => {
|
||||
const config = createConfigMock();
|
||||
|
||||
|
||||
@@ -118,13 +118,13 @@ export const otherFiltersResponse = {
|
||||
export const quickFiltersAttributeValuesResponse = {
|
||||
status: 'success',
|
||||
data: {
|
||||
stringAttributeValues: [
|
||||
'mq-kafka',
|
||||
'otel-demo',
|
||||
'otlp-python',
|
||||
'sample-flask',
|
||||
],
|
||||
numberAttributeValues: null,
|
||||
boolAttributeValues: null,
|
||||
data: {
|
||||
values: {
|
||||
relatedValues: ['mq-kafka', 'otel-demo', 'otlp-python', 'sample-flask'],
|
||||
stringValues: ['mq-kafka', 'otel-demo', 'otlp-python', 'sample-flask'],
|
||||
numberValues: [],
|
||||
},
|
||||
complete: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -31,33 +31,9 @@
|
||||
.settings-page-sidenav {
|
||||
width: 240px;
|
||||
height: calc(100vh - 48px);
|
||||
border-right: 1px solid var(--secondary);
|
||||
background: var(--sidebar-primary-foreground);
|
||||
padding-top: var(--padding-1);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-12);
|
||||
overflow-y: auto;
|
||||
|
||||
.settings-nav-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.settings-nav-section--with-divider {
|
||||
border-top: 1px solid var(--secondary);
|
||||
padding-top: var(--padding-4);
|
||||
}
|
||||
|
||||
.settings-nav-section-title {
|
||||
font-size: var(--uppercase-small-600-font-size);
|
||||
font-weight: var(--uppercase-small-600-font-weight);
|
||||
letter-spacing: 0.88px;
|
||||
text-transform: uppercase;
|
||||
color: var(--l3-foreground);
|
||||
margin-bottom: var(--margin-2);
|
||||
padding: var(--padding-1) var(--padding-3);
|
||||
}
|
||||
border-right: 1px solid var(--Slate-500, #161922);
|
||||
background: var(--Ink-500, #0b0c0e);
|
||||
margin-top: 4px;
|
||||
|
||||
.nav-item {
|
||||
.nav-item-data {
|
||||
|
||||
@@ -7,7 +7,7 @@ import { FeatureKeys } from 'constants/features';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { routeConfig } from 'container/SideNav/config';
|
||||
import { getQueryString } from 'container/SideNav/helper';
|
||||
import { settingsNavSections } from 'container/SideNav/menuItems';
|
||||
import { settingsMenuItems as defaultSettingsMenuItems } from 'container/SideNav/menuItems';
|
||||
import NavItem from 'container/SideNav/NavItem/NavItem';
|
||||
import { SidebarItem } from 'container/SideNav/sideNav.types';
|
||||
import useComponentPermission from 'hooks/useComponentPermission';
|
||||
@@ -33,7 +33,7 @@ function SettingsPage(): JSX.Element {
|
||||
const { isCloudUser, isEnterpriseSelfHostedUser } = useGetTenantLicense();
|
||||
|
||||
const [settingsMenuItems, setSettingsMenuItems] = useState<SidebarItem[]>(
|
||||
settingsNavSections.flatMap((section) => section.items),
|
||||
defaultSettingsMenuItems,
|
||||
);
|
||||
|
||||
const isAdmin = user.role === USER_ROLES.ADMIN;
|
||||
@@ -252,45 +252,25 @@ function SettingsPage(): JSX.Element {
|
||||
|
||||
<div className="settings-page-content-container">
|
||||
<div className="settings-page-sidenav" data-testid="settings-page-sidenav">
|
||||
{settingsNavSections.map((section) => {
|
||||
const enabledItems = section.items.filter((sectionItem) =>
|
||||
settingsMenuItems.some(
|
||||
(item) => item.key === sectionItem.key && item.isEnabled,
|
||||
),
|
||||
);
|
||||
if (enabledItems.length === 0) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<div
|
||||
key={section.key}
|
||||
className={`settings-nav-section${
|
||||
section.hasDivider ? ' settings-nav-section--with-divider' : ''
|
||||
}`}
|
||||
>
|
||||
{section.title && (
|
||||
<div className="settings-nav-section-title">{section.title}</div>
|
||||
)}
|
||||
{enabledItems.map((item) => (
|
||||
<NavItem
|
||||
key={item.key}
|
||||
item={item}
|
||||
isActive={isActiveNavItem(item.key as string)}
|
||||
isDisabled={false}
|
||||
showIcon={false}
|
||||
onClick={(event): void => {
|
||||
logEvent('Settings V2: Menu clicked', {
|
||||
menuLabel: item.label,
|
||||
menuRoute: item.key,
|
||||
});
|
||||
handleMenuItemClick((event as unknown) as MouseEvent, item);
|
||||
}}
|
||||
dataTestId={item.itemKey}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{settingsMenuItems
|
||||
.filter((item) => item.isEnabled)
|
||||
.map((item) => (
|
||||
<NavItem
|
||||
key={item.key}
|
||||
item={item}
|
||||
isActive={isActiveNavItem(item.key as string)}
|
||||
isDisabled={false}
|
||||
showIcon={false}
|
||||
onClick={(event): void => {
|
||||
logEvent('Settings V2: Menu clicked', {
|
||||
menuLabel: item.label,
|
||||
menuRoute: item.key,
|
||||
});
|
||||
handleMenuItemClick((event as unknown) as MouseEvent, item);
|
||||
}}
|
||||
dataTestId={item.itemKey}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="settings-page-content">
|
||||
|
||||
@@ -1,137 +0,0 @@
|
||||
import React from 'react';
|
||||
import SettingsPage from 'pages/Settings/Settings';
|
||||
import { render, screen, within } from 'tests/test-utils';
|
||||
import { LicensePlatform } from 'types/api/licensesV3/getActive';
|
||||
import { USER_ROLES } from 'types/roles';
|
||||
|
||||
jest.mock('components/MarkdownRenderer/MarkdownRenderer', () => ({
|
||||
__esModule: true,
|
||||
default: ({ children }: { children: React.ReactNode }): React.ReactNode =>
|
||||
children,
|
||||
}));
|
||||
|
||||
jest.mock('api/common/logEvent', () => ({
|
||||
__esModule: true,
|
||||
default: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('lib/history', () => ({
|
||||
push: jest.fn(),
|
||||
listen: jest.fn(() => jest.fn()),
|
||||
location: { pathname: '/settings', search: '' },
|
||||
}));
|
||||
|
||||
const getCloudAdminOverrides = (): any => ({
|
||||
activeLicense: {
|
||||
key: 'test-key',
|
||||
platform: LicensePlatform.CLOUD,
|
||||
},
|
||||
});
|
||||
|
||||
const getSelfHostedAdminOverrides = (): any => ({
|
||||
activeLicense: {
|
||||
key: 'test-key',
|
||||
platform: LicensePlatform.SELF_HOSTED,
|
||||
},
|
||||
});
|
||||
|
||||
describe('SettingsPage nav sections', () => {
|
||||
describe('Cloud Admin', () => {
|
||||
beforeEach(() => {
|
||||
render(<SettingsPage />, undefined, {
|
||||
role: USER_ROLES.ADMIN,
|
||||
appContextOverrides: getCloudAdminOverrides(),
|
||||
initialRoute: '/settings',
|
||||
});
|
||||
});
|
||||
|
||||
it.each([
|
||||
'settings-page-sidenav',
|
||||
'workspace',
|
||||
'account',
|
||||
'notification-channels',
|
||||
'billing',
|
||||
'roles',
|
||||
'api-keys',
|
||||
'members-sso',
|
||||
'integrations',
|
||||
'ingestion',
|
||||
])('renders "%s" element', (id) => {
|
||||
expect(screen.getByTestId(id)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it.each(['Identity & Access', 'Authentication'])(
|
||||
'renders "%s" section title',
|
||||
(text) => {
|
||||
expect(screen.getByText(text)).toBeInTheDocument();
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe('Cloud Viewer', () => {
|
||||
beforeEach(() => {
|
||||
render(<SettingsPage />, undefined, {
|
||||
role: USER_ROLES.VIEWER,
|
||||
appContextOverrides: getCloudAdminOverrides(),
|
||||
initialRoute: '/settings',
|
||||
});
|
||||
});
|
||||
|
||||
it.each(['workspace', 'account'])('renders "%s" element', (id) => {
|
||||
expect(screen.getByTestId(id)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it.each(['billing', 'roles', 'api-keys'])(
|
||||
'does not render "%s" element',
|
||||
(id) => {
|
||||
expect(screen.queryByTestId(id)).not.toBeInTheDocument();
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe('Self-hosted Admin', () => {
|
||||
beforeEach(() => {
|
||||
render(<SettingsPage />, undefined, {
|
||||
role: USER_ROLES.ADMIN,
|
||||
appContextOverrides: getSelfHostedAdminOverrides(),
|
||||
initialRoute: '/settings',
|
||||
});
|
||||
});
|
||||
|
||||
it.each(['roles', 'api-keys', 'integrations', 'members-sso', 'ingestion'])(
|
||||
'renders "%s" element',
|
||||
(id) => {
|
||||
expect(screen.getByTestId(id)).toBeInTheDocument();
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe('section structure', () => {
|
||||
it('renders items in correct sections for cloud admin', () => {
|
||||
const { container } = render(<SettingsPage />, undefined, {
|
||||
role: USER_ROLES.ADMIN,
|
||||
appContextOverrides: getCloudAdminOverrides(),
|
||||
initialRoute: '/settings',
|
||||
});
|
||||
|
||||
const sidenav = within(container).getByTestId('settings-page-sidenav');
|
||||
const sections = sidenav.querySelectorAll('.settings-nav-section');
|
||||
|
||||
// Should have at least 2 sections (general + identity-access) for cloud admin
|
||||
expect(sections.length).toBeGreaterThanOrEqual(2);
|
||||
});
|
||||
|
||||
it('hides section entirely when all items in it are disabled', () => {
|
||||
// Community user has very limited access — identity section should be hidden
|
||||
render(<SettingsPage />, undefined, {
|
||||
role: USER_ROLES.VIEWER,
|
||||
appContextOverrides: {
|
||||
activeLicense: null,
|
||||
},
|
||||
initialRoute: '/settings',
|
||||
});
|
||||
|
||||
expect(screen.queryByText('IDENTITY & ACCESS')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -592,6 +592,39 @@ body {
|
||||
}
|
||||
}
|
||||
|
||||
.ant-btn-text:hover,
|
||||
.ant-btn-text:focus-visible {
|
||||
background-color: var(--bg-vanilla-200) !important;
|
||||
}
|
||||
|
||||
.ant-btn-link:hover,
|
||||
.ant-btn-link:focus-visible {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
.ant-btn-default:hover,
|
||||
.ant-btn-default:focus-visible,
|
||||
.ant-btn-text:not(.ant-btn-primary):hover,
|
||||
.ant-btn-text:not(.ant-btn-primary):focus-visible,
|
||||
.ant-btn:not(.ant-btn-primary):not(.ant-btn-dangerous):hover,
|
||||
.ant-btn:not(.ant-btn-primary):not(.ant-btn-dangerous):focus-visible {
|
||||
background-color: var(--bg-vanilla-200) !important;
|
||||
}
|
||||
|
||||
.ant-typography:hover,
|
||||
.ant-typography:focus-visible {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
.ant-tooltip {
|
||||
--antd-arrow-background-color: var(--bg-vanilla-300);
|
||||
|
||||
.ant-tooltip-inner {
|
||||
background-color: var(--bg-vanilla-200);
|
||||
color: var(--bg-ink-500);
|
||||
}
|
||||
}
|
||||
|
||||
// Enhanced legend light mode styles
|
||||
.u-legend-enhanced {
|
||||
// Light mode scrollbar styling
|
||||
|
||||
@@ -47,6 +47,7 @@ export interface QueryKeyValueRequestProps {
|
||||
searchText: string;
|
||||
signalSource?: 'meter' | '';
|
||||
metricName?: string;
|
||||
existingQuery?: string;
|
||||
}
|
||||
|
||||
export type SignalType = 'traces' | 'logs' | 'metrics';
|
||||
|
||||
@@ -18,7 +18,6 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/telemetrymetrics"
|
||||
"github.com/SigNoz/signoz/pkg/telemetrystore"
|
||||
"github.com/SigNoz/signoz/pkg/types/ctxtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/instrumentationtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/metricsexplorertypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/metrictypes"
|
||||
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||
@@ -60,8 +59,6 @@ func NewModule(ts telemetrystore.TelemetryStore, telemetryMetadataStore telemetr
|
||||
|
||||
// TODO(srikanthccv): use metadata store to fetch metric metadata
|
||||
func (m *module) ListMetrics(ctx context.Context, orgID valuer.UUID, params *metricsexplorertypes.ListMetricsParams) (*metricsexplorertypes.ListMetricsResponse, error) {
|
||||
ctx = m.withMetricsExplorerContext(ctx, "ListMetrics")
|
||||
|
||||
if err := params.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -291,7 +288,6 @@ func (m *module) GetTreemap(ctx context.Context, orgID valuer.UUID, req *metrics
|
||||
}
|
||||
|
||||
func (m *module) GetMetricMetadataMulti(ctx context.Context, orgID valuer.UUID, metricNames []string) (map[string]*metricsexplorertypes.MetricMetadata, error) {
|
||||
|
||||
if len(metricNames) == 0 {
|
||||
return map[string]*metricsexplorertypes.MetricMetadata{}, nil
|
||||
}
|
||||
@@ -480,8 +476,6 @@ func (m *module) GetMetricAttributes(ctx context.Context, orgID valuer.UUID, req
|
||||
}
|
||||
|
||||
func (m *module) CheckMetricExists(ctx context.Context, orgID valuer.UUID, metricName string) (bool, error) {
|
||||
ctx = m.withMetricsExplorerContext(ctx, "CheckMetricExists")
|
||||
|
||||
sb := sqlbuilder.NewSelectBuilder()
|
||||
sb.Select("count(*) > 0 as metricExists")
|
||||
sb.From(fmt.Sprintf("%s.%s", telemetrymetrics.DBName, telemetrymetrics.AttributesMetadataTableName))
|
||||
@@ -518,8 +512,6 @@ func (m *module) fetchMetadataFromCache(ctx context.Context, orgID valuer.UUID,
|
||||
}
|
||||
|
||||
func (m *module) fetchUpdatedMetadata(ctx context.Context, orgID valuer.UUID, metricNames []string) (map[string]*metricsexplorertypes.MetricMetadata, error) {
|
||||
ctx = m.withMetricsExplorerContext(ctx, "fetchUpdatedMetadata")
|
||||
|
||||
if len(metricNames) == 0 {
|
||||
return map[string]*metricsexplorertypes.MetricMetadata{}, nil
|
||||
}
|
||||
@@ -578,8 +570,6 @@ func (m *module) fetchUpdatedMetadata(ctx context.Context, orgID valuer.UUID, me
|
||||
}
|
||||
|
||||
func (m *module) fetchTimeseriesMetadata(ctx context.Context, orgID valuer.UUID, metricNames []string) (map[string]*metricsexplorertypes.MetricMetadata, error) {
|
||||
ctx = m.withMetricsExplorerContext(ctx, "fetchTimeseriesMetadata")
|
||||
|
||||
if len(metricNames) == 0 {
|
||||
return map[string]*metricsexplorertypes.MetricMetadata{}, nil
|
||||
}
|
||||
@@ -708,8 +698,6 @@ func (m *module) validateMetricLabels(ctx context.Context, req *metricsexplorert
|
||||
}
|
||||
|
||||
func (m *module) checkForLabelInMetric(ctx context.Context, metricName string, label string) (bool, error) {
|
||||
ctx = m.withMetricsExplorerContext(ctx, "checkForLabelInMetric")
|
||||
|
||||
sb := sqlbuilder.NewSelectBuilder()
|
||||
sb.Select("count(*) > 0 AS has_label")
|
||||
sb.From(fmt.Sprintf("%s.%s", telemetrymetrics.DBName, telemetrymetrics.AttributesMetadataTableName))
|
||||
@@ -731,7 +719,6 @@ func (m *module) checkForLabelInMetric(ctx context.Context, metricName string, l
|
||||
}
|
||||
|
||||
func (m *module) insertMetricsMetadata(ctx context.Context, orgID valuer.UUID, req *metricsexplorertypes.UpdateMetricMetadataRequest) error {
|
||||
ctx = m.withMetricsExplorerContext(ctx, "insertMetricsMetadata")
|
||||
createdAt := time.Now().UnixMilli()
|
||||
|
||||
ib := sqlbuilder.NewInsertBuilder()
|
||||
@@ -825,7 +812,6 @@ func (m *module) fetchMetricsStatsWithSamples(
|
||||
normalized bool,
|
||||
orderBy *qbtypes.OrderBy,
|
||||
) ([]metricsexplorertypes.Stat, uint64, error) {
|
||||
ctx = m.withMetricsExplorerContext(ctx, "fetchMetricsStatsWithSamples")
|
||||
|
||||
start, end, distributedTsTable, localTsTable := telemetrymetrics.WhichTSTableToUse(uint64(req.Start), uint64(req.End), nil)
|
||||
samplesTable := telemetrymetrics.WhichSamplesTableToUse(uint64(req.Start), uint64(req.End), metrictypes.UnspecifiedType, metrictypes.TimeAggregationUnspecified, nil)
|
||||
@@ -933,8 +919,6 @@ func (m *module) fetchMetricsStatsWithSamples(
|
||||
}
|
||||
|
||||
func (m *module) computeTimeseriesTreemap(ctx context.Context, req *metricsexplorertypes.TreemapRequest, filterWhereClause *sqlbuilder.WhereClause) ([]metricsexplorertypes.TreemapEntry, error) {
|
||||
ctx = m.withMetricsExplorerContext(ctx, "computeTimeseriesTreemap")
|
||||
|
||||
start, end, distributedTsTable, _ := telemetrymetrics.WhichTSTableToUse(uint64(req.Start), uint64(req.End), nil)
|
||||
|
||||
totalTSBuilder := sqlbuilder.NewSelectBuilder()
|
||||
@@ -999,8 +983,6 @@ func (m *module) computeTimeseriesTreemap(ctx context.Context, req *metricsexplo
|
||||
}
|
||||
|
||||
func (m *module) computeSamplesTreemap(ctx context.Context, req *metricsexplorertypes.TreemapRequest, filterWhereClause *sqlbuilder.WhereClause) ([]metricsexplorertypes.TreemapEntry, error) {
|
||||
ctx = m.withMetricsExplorerContext(ctx, "computeSamplesTreemap")
|
||||
|
||||
start, end, distributedTsTable, localTsTable := telemetrymetrics.WhichTSTableToUse(uint64(req.Start), uint64(req.End), nil)
|
||||
samplesTable := telemetrymetrics.WhichSamplesTableToUse(uint64(req.Start), uint64(req.End), metrictypes.UnspecifiedType, metrictypes.TimeAggregationUnspecified, nil)
|
||||
countExp := telemetrymetrics.CountExpressionForSamplesTable(samplesTable)
|
||||
@@ -1102,8 +1084,6 @@ func (m *module) computeSamplesTreemap(ctx context.Context, req *metricsexplorer
|
||||
|
||||
// getMetricDataPoints returns the total number of data points (samples) for a metric.
|
||||
func (m *module) getMetricDataPoints(ctx context.Context, metricName string) (uint64, error) {
|
||||
ctx = m.withMetricsExplorerContext(ctx, "getMetricDataPoints")
|
||||
|
||||
sb := sqlbuilder.NewSelectBuilder()
|
||||
sb.Select("sum(count) AS data_points")
|
||||
sb.From(fmt.Sprintf("%s.%s", telemetrymetrics.DBName, telemetrymetrics.SamplesV4Agg30mTableName))
|
||||
@@ -1124,8 +1104,6 @@ func (m *module) getMetricDataPoints(ctx context.Context, metricName string) (ui
|
||||
|
||||
// getMetricLastReceived returns the last received timestamp for a metric.
|
||||
func (m *module) getMetricLastReceived(ctx context.Context, metricName string) (uint64, error) {
|
||||
ctx = m.withMetricsExplorerContext(ctx, "getMetricLastReceived")
|
||||
|
||||
sb := sqlbuilder.NewSelectBuilder()
|
||||
sb.Select("MAX(last_reported_unix_milli) AS last_received_time")
|
||||
sb.From(fmt.Sprintf("%s.%s", telemetrymetrics.DBName, telemetrymetrics.AttributesMetadataTableName))
|
||||
@@ -1149,8 +1127,6 @@ func (m *module) getMetricLastReceived(ctx context.Context, metricName string) (
|
||||
|
||||
// getTotalTimeSeriesForMetricName returns the total number of unique time series for a metric.
|
||||
func (m *module) getTotalTimeSeriesForMetricName(ctx context.Context, metricName string) (uint64, error) {
|
||||
ctx = m.withMetricsExplorerContext(ctx, "getTotalTimeSeriesForMetricName")
|
||||
|
||||
sb := sqlbuilder.NewSelectBuilder()
|
||||
sb.Select("uniq(fingerprint) AS time_series_count")
|
||||
sb.From(fmt.Sprintf("%s.%s", telemetrymetrics.DBName, telemetrymetrics.TimeseriesV41weekTableName))
|
||||
@@ -1171,8 +1147,6 @@ func (m *module) getTotalTimeSeriesForMetricName(ctx context.Context, metricName
|
||||
|
||||
// getActiveTimeSeriesForMetricName returns the number of active time series for a metric within the given duration.
|
||||
func (m *module) getActiveTimeSeriesForMetricName(ctx context.Context, metricName string, duration time.Duration) (uint64, error) {
|
||||
ctx = m.withMetricsExplorerContext(ctx, "getActiveTimeSeriesForMetricName")
|
||||
|
||||
milli := time.Now().Add(-duration).UnixMilli()
|
||||
|
||||
sb := sqlbuilder.NewSelectBuilder()
|
||||
@@ -1194,8 +1168,6 @@ func (m *module) getActiveTimeSeriesForMetricName(ctx context.Context, metricNam
|
||||
}
|
||||
|
||||
func (m *module) fetchMetricAttributes(ctx context.Context, metricName string, start, end *int64) ([]metricsexplorertypes.MetricAttribute, error) {
|
||||
ctx = m.withMetricsExplorerContext(ctx, "fetchMetricAttributes")
|
||||
|
||||
// Build query using sqlbuilder
|
||||
sb := sqlbuilder.NewSelectBuilder()
|
||||
sb.Select(
|
||||
@@ -1244,12 +1216,3 @@ func (m *module) fetchMetricAttributes(ctx context.Context, metricName string, s
|
||||
|
||||
return attributes, nil
|
||||
}
|
||||
|
||||
func (m *module) withMetricsExplorerContext(ctx context.Context, functionName string) context.Context {
|
||||
comments := map[string]string{
|
||||
instrumentationtypes.TelemetrySignal: telemetrytypes.SignalMetrics.StringValue(),
|
||||
instrumentationtypes.CodeNamespace: "metrics-explorer",
|
||||
instrumentationtypes.CodeFunctionName: functionName,
|
||||
}
|
||||
return ctxtypes.NewContextWithCommentVals(ctx, comments)
|
||||
}
|
||||
|
||||
@@ -11,8 +11,6 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/modules/promote"
|
||||
"github.com/SigNoz/signoz/pkg/telemetrylogs"
|
||||
"github.com/SigNoz/signoz/pkg/telemetrystore"
|
||||
"github.com/SigNoz/signoz/pkg/types/ctxtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/instrumentationtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/promotetypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
)
|
||||
@@ -107,11 +105,6 @@ func (m *module) PromotePaths(ctx context.Context, paths []string) error {
|
||||
|
||||
// createIndexes creates string ngram + token filter indexes on JSON path subcolumns for LIKE queries.
|
||||
func (m *module) createIndexes(ctx context.Context, indexes []schemamigrator.Index) error {
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.TelemetrySignal: telemetrytypes.SignalLogs.StringValue(),
|
||||
instrumentationtypes.CodeNamespace: "promote",
|
||||
instrumentationtypes.CodeFunctionName: "createIndexes",
|
||||
})
|
||||
if len(indexes) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/modules/rawdataexport"
|
||||
"github.com/SigNoz/signoz/pkg/querier"
|
||||
"github.com/SigNoz/signoz/pkg/types/ctxtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/instrumentationtypes"
|
||||
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
@@ -23,10 +22,6 @@ func NewModule(querier querier.Querier) rawdataexport.Module {
|
||||
}
|
||||
|
||||
func (m *Module) ExportRawData(ctx context.Context, orgID valuer.UUID, rangeRequest *qbtypes.QueryRangeRequest, doneChan chan any) (chan *qbtypes.RawRow, chan error) {
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.CodeNamespace: "rawdataexport",
|
||||
instrumentationtypes.CodeFunctionName: "ExportRawData",
|
||||
})
|
||||
|
||||
spec := rangeRequest.CompositeQuery.Queries[0].Spec.(qbtypes.QueryBuilderQuery[qbtypes.LogAggregation])
|
||||
rowCountLimit := spec.Limit
|
||||
|
||||
@@ -13,8 +13,6 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/querier"
|
||||
"github.com/SigNoz/signoz/pkg/telemetrystore"
|
||||
"github.com/SigNoz/signoz/pkg/telemetrytraces"
|
||||
"github.com/SigNoz/signoz/pkg/types/ctxtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/instrumentationtypes"
|
||||
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||
"github.com/SigNoz/signoz/pkg/types/servicetypes/servicetypesv1"
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
@@ -36,8 +34,6 @@ func NewModule(q querier.Querier, ts telemetrystore.TelemetryStore) services.Mod
|
||||
|
||||
// FetchTopLevelOperations returns top-level operations per service using db query
|
||||
func (m *module) FetchTopLevelOperations(ctx context.Context, start time.Time, services []string) (map[string][]string, error) {
|
||||
ctx = m.withServicesContext(ctx, "FetchTopLevelOperations")
|
||||
|
||||
db := m.TelemetryStore.ClickhouseDB()
|
||||
query := fmt.Sprintf("SELECT name, serviceName, max(time) as ts FROM %s.%s WHERE time >= @start", telemetrytraces.DBName, telemetrytraces.TopLevelOperationsTableName)
|
||||
args := []any{clickhouse.Named("start", start)}
|
||||
@@ -74,7 +70,6 @@ func (m *module) FetchTopLevelOperations(ctx context.Context, start time.Time, s
|
||||
// Get implements services.Module
|
||||
// Builds a QBv5 traces aggregation grouped by service.name and maps results to ResponseItem.
|
||||
func (m *module) Get(ctx context.Context, orgUUID valuer.UUID, req *servicetypesv1.Request) ([]*servicetypesv1.ResponseItem, error) {
|
||||
ctx = m.withServicesContext(ctx, "Get")
|
||||
if req == nil {
|
||||
return nil, errors.NewInvalidInputf(errors.CodeInvalidInput, "request is nil")
|
||||
}
|
||||
@@ -109,7 +104,6 @@ func (m *module) Get(ctx context.Context, orgUUID valuer.UUID, req *servicetypes
|
||||
|
||||
// GetTopOperations implements services.Module for QBV5 based top ops
|
||||
func (m *module) GetTopOperations(ctx context.Context, orgUUID valuer.UUID, req *servicetypesv1.OperationsRequest) ([]servicetypesv1.OperationItem, error) {
|
||||
ctx = m.withServicesContext(ctx, "GetTopOperations")
|
||||
if req == nil {
|
||||
return nil, errors.NewInvalidInputf(errors.CodeInvalidInput, "request is nil")
|
||||
}
|
||||
@@ -130,7 +124,6 @@ func (m *module) GetTopOperations(ctx context.Context, orgUUID valuer.UUID, req
|
||||
|
||||
// GetEntryPointOperations implements services.Module for QBV5 based entry point ops
|
||||
func (m *module) GetEntryPointOperations(ctx context.Context, orgUUID valuer.UUID, req *servicetypesv1.OperationsRequest) ([]servicetypesv1.OperationItem, error) {
|
||||
ctx = m.withServicesContext(ctx, "GetEntryPointOperations")
|
||||
if req == nil {
|
||||
return nil, errors.NewInvalidInputf(errors.CodeInvalidInput, "request is nil")
|
||||
}
|
||||
@@ -524,12 +517,3 @@ func (m *module) mapEntryPointOpsQueryRangeResp(resp *qbtypes.QueryRangeResponse
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (m *module) withServicesContext(ctx context.Context, functionName string) context.Context {
|
||||
comments := map[string]string{
|
||||
instrumentationtypes.TelemetrySignal: telemetrytypes.SignalTraces.StringValue(),
|
||||
instrumentationtypes.CodeNamespace: "services",
|
||||
instrumentationtypes.CodeFunctionName: functionName,
|
||||
}
|
||||
return ctxtypes.NewContextWithCommentVals(ctx, comments)
|
||||
}
|
||||
|
||||
@@ -8,8 +8,6 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/modules/spanpercentile"
|
||||
"github.com/SigNoz/signoz/pkg/querier"
|
||||
"github.com/SigNoz/signoz/pkg/types/ctxtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/instrumentationtypes"
|
||||
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||
"github.com/SigNoz/signoz/pkg/types/spanpercentiletypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
@@ -29,10 +27,6 @@ func NewModule(
|
||||
}
|
||||
|
||||
func (m *module) GetSpanPercentile(ctx context.Context, orgID valuer.UUID, userID valuer.UUID, req *spanpercentiletypes.SpanPercentileRequest) (*spanpercentiletypes.SpanPercentileResponse, error) {
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.CodeNamespace: "spanpercentile",
|
||||
instrumentationtypes.CodeFunctionName: "GetSpanPercentile",
|
||||
})
|
||||
queryRangeRequest, err := buildSpanPercentileQuery(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -11,9 +11,6 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/constants"
|
||||
"github.com/SigNoz/signoz/pkg/telemetrystore"
|
||||
"github.com/SigNoz/signoz/pkg/types/ctxtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/instrumentationtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
promValue "github.com/prometheus/prometheus/model/value"
|
||||
"github.com/prometheus/prometheus/prompb"
|
||||
"github.com/prometheus/prometheus/storage"
|
||||
@@ -140,7 +137,6 @@ func (client *client) queryToClickhouseQuery(_ context.Context, query *prompb.Qu
|
||||
}
|
||||
|
||||
func (client *client) getFingerprintsFromClickhouseQuery(ctx context.Context, query string, args []any) (map[uint64][]prompb.Label, error) {
|
||||
ctx = client.withClickhousePrometheusContext(ctx, "getFingerprintsFromClickhouseQuery")
|
||||
rows, err := client.telemetryStore.ClickhouseDB().Query(ctx, query, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -172,7 +168,6 @@ func (client *client) getFingerprintsFromClickhouseQuery(ctx context.Context, qu
|
||||
}
|
||||
|
||||
func (client *client) querySamples(ctx context.Context, start int64, end int64, fingerprints map[uint64][]prompb.Label, metricName string, subQuery string, args []any) ([]*prompb.TimeSeries, error) {
|
||||
ctx = client.withClickhousePrometheusContext(ctx, "querySamples")
|
||||
argCount := len(args)
|
||||
|
||||
query := fmt.Sprintf(`
|
||||
@@ -249,8 +244,6 @@ func (client *client) querySamples(ctx context.Context, start int64, end int64,
|
||||
}
|
||||
|
||||
func (client *client) queryRaw(ctx context.Context, query string, ts int64) (*prompb.QueryResult, error) {
|
||||
ctx = client.withClickhousePrometheusContext(ctx, "queryRaw")
|
||||
|
||||
rows, err := client.telemetryStore.ClickhouseDB().Query(ctx, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -298,12 +291,3 @@ func (client *client) queryRaw(ctx context.Context, query string, ts int64) (*pr
|
||||
|
||||
return &res, nil
|
||||
}
|
||||
|
||||
func (client *client) withClickhousePrometheusContext(ctx context.Context, functionName string) context.Context {
|
||||
comments := map[string]string{
|
||||
instrumentationtypes.TelemetrySignal: telemetrytypes.SignalMetrics.StringValue(),
|
||||
instrumentationtypes.CodeNamespace: "clickhouse-prometheus",
|
||||
instrumentationtypes.CodeFunctionName: functionName,
|
||||
}
|
||||
return ctxtypes.NewContextWithCommentVals(ctx, comments)
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/http/render"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/ctxtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/instrumentationtypes"
|
||||
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
@@ -34,10 +33,6 @@ func NewHandler(set factory.ProviderSettings, querier Querier, analytics analyti
|
||||
|
||||
func (handler *handler) QueryRange(rw http.ResponseWriter, req *http.Request) {
|
||||
ctx := req.Context()
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.CodeNamespace: "querier",
|
||||
instrumentationtypes.CodeFunctionName: "QueryRange",
|
||||
})
|
||||
|
||||
claims, err := authtypes.ClaimsFromContext(ctx)
|
||||
if err != nil {
|
||||
|
||||
@@ -12,8 +12,6 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/telemetrylogs"
|
||||
"github.com/SigNoz/signoz/pkg/telemetrystore"
|
||||
"github.com/SigNoz/signoz/pkg/types/ctxtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/instrumentationtypes"
|
||||
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
"github.com/bytedance/sonic"
|
||||
@@ -214,11 +212,6 @@ func (q *builderQuery[T]) Execute(ctx context.Context) (*qbtypes.Result, error)
|
||||
|
||||
// executeWithContext executes the query with query window and step context for partial value detection
|
||||
func (q *builderQuery[T]) executeWithContext(ctx context.Context, query string, args []any) (*qbtypes.Result, error) {
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.TelemetrySignal: q.spec.Signal.StringValue(),
|
||||
instrumentationtypes.QueryDuration: instrumentationtypes.DurationBucket(q.fromMS, q.toMS),
|
||||
})
|
||||
|
||||
totalRows := uint64(0)
|
||||
totalBytes := uint64(0)
|
||||
elapsed := time.Duration(0)
|
||||
|
||||
@@ -14,8 +14,6 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/querybuilder"
|
||||
"github.com/SigNoz/signoz/pkg/telemetrystore"
|
||||
"github.com/SigNoz/signoz/pkg/types/ctxtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/instrumentationtypes"
|
||||
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||
)
|
||||
|
||||
@@ -100,9 +98,6 @@ func (q *chSQLQuery) renderVars(query string, vars map[string]qbtypes.VariableIt
|
||||
}
|
||||
|
||||
func (q *chSQLQuery) Execute(ctx context.Context) (*qbtypes.Result, error) {
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.QueryDuration: instrumentationtypes.DurationBucket(q.fromMS, q.toMS),
|
||||
})
|
||||
|
||||
totalRows := uint64(0)
|
||||
totalBytes := uint64(0)
|
||||
|
||||
@@ -14,8 +14,6 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/prometheus"
|
||||
"github.com/SigNoz/signoz/pkg/querybuilder"
|
||||
"github.com/SigNoz/signoz/pkg/types/ctxtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/instrumentationtypes"
|
||||
qbv5 "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
"github.com/prometheus/prometheus/promql"
|
||||
@@ -189,11 +187,6 @@ func (q *promqlQuery) renderVars(query string, vars map[string]qbv5.VariableItem
|
||||
|
||||
func (q *promqlQuery) Execute(ctx context.Context) (*qbv5.Result, error) {
|
||||
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.TelemetrySignal: telemetrytypes.SignalMetrics.StringValue(),
|
||||
instrumentationtypes.QueryDuration: instrumentationtypes.DurationBucket(q.tr.From, q.tr.To),
|
||||
})
|
||||
|
||||
start := int64(querybuilder.ToNanoSecs(q.tr.From))
|
||||
end := int64(querybuilder.ToNanoSecs(q.tr.To))
|
||||
|
||||
@@ -244,21 +237,11 @@ func (q *promqlQuery) Execute(ctx context.Context) (*qbv5.Result, error) {
|
||||
return nil, errors.WrapInternalf(promErr, errors.CodeInternal, "error getting matrix from promql query %q", query)
|
||||
}
|
||||
|
||||
excludeLabel := func(labelName string) bool {
|
||||
if labelName == "__name__" {
|
||||
return false
|
||||
}
|
||||
return strings.HasPrefix(labelName, "__") || labelName == "fingerprint"
|
||||
}
|
||||
|
||||
var series []*qbv5.TimeSeries
|
||||
for _, v := range matrix {
|
||||
var s qbv5.TimeSeries
|
||||
lbls := make([]*qbv5.Label, 0, len(v.Metric))
|
||||
for name, value := range v.Metric.Copy().Map() {
|
||||
if excludeLabel(name) {
|
||||
continue
|
||||
}
|
||||
lbls = append(lbls, &qbv5.Label{
|
||||
Key: telemetrytypes.TelemetryFieldKey{Name: name},
|
||||
Value: value,
|
||||
|
||||
@@ -16,8 +16,6 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/query-service/utils"
|
||||
"github.com/SigNoz/signoz/pkg/querybuilder"
|
||||
"github.com/SigNoz/signoz/pkg/telemetrystore"
|
||||
"github.com/SigNoz/signoz/pkg/types/ctxtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/instrumentationtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/metrictypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
"golang.org/x/exp/maps"
|
||||
@@ -528,11 +526,6 @@ func (q *querier) run(
|
||||
steps map[string]qbtypes.Step,
|
||||
qbEvent *qbtypes.QBEvent,
|
||||
) (*qbtypes.QueryRangeResponse, error) {
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.PanelType: qbEvent.PanelType,
|
||||
instrumentationtypes.QueryType: qbEvent.QueryType,
|
||||
})
|
||||
|
||||
results := make(map[string]any)
|
||||
warnings := make([]string, 0)
|
||||
warningsDocURL := ""
|
||||
|
||||
@@ -6,10 +6,7 @@ import (
|
||||
|
||||
"github.com/ClickHouse/clickhouse-go/v2"
|
||||
"github.com/SigNoz/signoz/pkg/telemetrystore"
|
||||
"github.com/SigNoz/signoz/pkg/types/ctxtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/instrumentationtypes"
|
||||
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
)
|
||||
|
||||
type traceOperatorQuery struct {
|
||||
@@ -55,11 +52,6 @@ func (q *traceOperatorQuery) Execute(ctx context.Context) (*qbtypes.Result, erro
|
||||
}
|
||||
|
||||
func (q *traceOperatorQuery) executeWithContext(ctx context.Context, query string, args []any) (*qbtypes.Result, error) {
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.TelemetrySignal: telemetrytypes.SignalTraces.StringValue(),
|
||||
instrumentationtypes.QueryDuration: instrumentationtypes.DurationBucket(q.fromMS, q.toMS),
|
||||
})
|
||||
|
||||
totalRows := uint64(0)
|
||||
totalBytes := uint64(0)
|
||||
elapsed := time.Duration(0)
|
||||
|
||||
@@ -114,7 +114,6 @@ func (r *Repo) GetLatestVersion(
|
||||
func (r *Repo) insertConfig(
|
||||
ctx context.Context, orgId valuer.UUID, userId valuer.UUID, c *opamptypes.AgentConfigVersion, elements []string,
|
||||
) error {
|
||||
|
||||
if c.ElementType.StringValue() == "" {
|
||||
return errors.NewInvalidInputf(CodeElementTypeRequired, "element type is required for creating agent config version")
|
||||
}
|
||||
@@ -228,6 +227,55 @@ func (r *Repo) updateDeployStatus(ctx context.Context,
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetDeployStatusByHash returns the DeployStatus for the given config hash
|
||||
// (stored with orgId prefix). Returns DeployStatusUnknown when no matching row exists.
|
||||
func (r *Repo) GetDeployStatusByHash(ctx context.Context, orgId valuer.UUID, configHash string) (opamptypes.DeployStatus, error) {
|
||||
var version opamptypes.AgentConfigVersion
|
||||
err := r.store.BunDB().NewSelect().
|
||||
Model(&version).
|
||||
ColumnExpr("deploy_status").
|
||||
Where("hash = ?", configHash).
|
||||
Where("org_id = ?", orgId).
|
||||
Scan(ctx)
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return opamptypes.DeployStatusUnknown, nil
|
||||
}
|
||||
return opamptypes.DeployStatusUnknown, errors.WrapInternalf(err, errors.CodeInternal, "failed to query deploy status by hash")
|
||||
}
|
||||
return version.DeployStatus, nil
|
||||
}
|
||||
|
||||
// GetPendingDeployments returns all config versions with in_progress deploy status.
|
||||
// Used on server startup to rehydrate coordinator subscribers that were lost on crash/restart.
|
||||
func (r *Repo) GetPendingDeployments(ctx context.Context) ([]opamptypes.AgentConfigVersion, error) {
|
||||
var versions []opamptypes.AgentConfigVersion
|
||||
err := r.store.BunDB().NewSelect().
|
||||
Model(&versions).
|
||||
ColumnExpr("org_id, hash").
|
||||
// Only consider non-terminal deployment states (i.e. anything except failed or deployed).
|
||||
Where("deploy_status NOT IN (?, ?)",
|
||||
opamptypes.DeployFailed.StringValue(),
|
||||
opamptypes.Deployed.StringValue(),
|
||||
).
|
||||
// For each org, keep only the latest pending version.
|
||||
Where("version = (SELECT MAX(version) FROM agent_config_version WHERE org_id = acv.org_id AND deploy_status NOT IN (?, ?))",
|
||||
opamptypes.DeployFailed.StringValue(),
|
||||
opamptypes.Deployed.StringValue(),
|
||||
).
|
||||
// Exclude any pending version that is before a terminal (deployed/failed) version for the same org.
|
||||
Where("NOT EXISTS (SELECT 1 FROM agent_config_version acv2 WHERE acv2.org_id = acv.org_id AND acv2.version > acv.version AND acv2.deploy_status IN (?, ?))",
|
||||
opamptypes.Deployed.StringValue(),
|
||||
opamptypes.DeployFailed.StringValue(),
|
||||
).
|
||||
Where("hash IS NOT NULL AND hash != ''").
|
||||
Scan(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.WrapInternalf(err, errors.CodeInternal, "failed to query pending deployments")
|
||||
}
|
||||
return versions, nil
|
||||
}
|
||||
|
||||
func (r *Repo) updateDeployStatusByHash(
|
||||
ctx context.Context, orgId valuer.UUID, confighash string, status string, result string,
|
||||
) error {
|
||||
|
||||
@@ -178,6 +178,31 @@ func (m *Manager) ReportConfigDeploymentStatus(
|
||||
}
|
||||
}
|
||||
|
||||
// Implements model.AgentConfigProvider
|
||||
func (m *Manager) GetDeployStatusByHash(ctx context.Context, orgId valuer.UUID, configHash string) (opamptypes.DeployStatus, error) {
|
||||
return m.Repo.GetDeployStatusByHash(ctx, orgId, configHash)
|
||||
}
|
||||
|
||||
// Implements opamp.AgentConfigProvider
|
||||
func (m *Manager) GetPendingDeployments(ctx context.Context) ([]opamp.PendingDeployment, error) {
|
||||
versions, err := m.Repo.GetPendingDeployments(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := make([]opamp.PendingDeployment, 0, len(versions))
|
||||
for _, v := range versions {
|
||||
rawHash := strings.TrimPrefix(v.Hash, v.OrgID.String())
|
||||
if rawHash == "" {
|
||||
continue
|
||||
}
|
||||
result = append(result, opamp.PendingDeployment{
|
||||
OrgID: v.OrgID,
|
||||
RawConfigHash: rawHash,
|
||||
})
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func GetLatestVersion(
|
||||
ctx context.Context, orgId valuer.UUID, elementType opamptypes.ElementType,
|
||||
) (*opamptypes.AgentConfigVersion, error) {
|
||||
|
||||
@@ -20,9 +20,6 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
"github.com/SigNoz/signoz/pkg/telemetrystore"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/ctxtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/instrumentationtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/uptrace/bun"
|
||||
|
||||
@@ -272,12 +269,6 @@ func (r *ClickHouseReader) GetQueryRangeResult(ctx context.Context, query *model
|
||||
}
|
||||
|
||||
func (r *ClickHouseReader) GetServicesList(ctx context.Context) (*[]string, error) {
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.TelemetrySignal: telemetrytypes.SignalTraces.StringValue(),
|
||||
instrumentationtypes.CodeNamespace: "clickhouse-reader",
|
||||
instrumentationtypes.CodeFunctionName: "GetServicesList",
|
||||
})
|
||||
|
||||
services := []string{}
|
||||
rows, err := r.db.Query(ctx, fmt.Sprintf(`SELECT DISTINCT resource_string_service$$name FROM %s.%s WHERE ts_bucket_start > (toUnixTimestamp(now() - INTERVAL 1 DAY) - 1800) AND toDate(timestamp) > now() - INTERVAL 1 DAY`, r.TraceDB, r.traceTableName))
|
||||
if err != nil {
|
||||
@@ -297,12 +288,6 @@ func (r *ClickHouseReader) GetServicesList(ctx context.Context) (*[]string, erro
|
||||
}
|
||||
|
||||
func (r *ClickHouseReader) GetTopLevelOperations(ctx context.Context, start, end time.Time, services []string) (*map[string][]string, *model.ApiError) {
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.TelemetrySignal: telemetrytypes.SignalTraces.StringValue(),
|
||||
instrumentationtypes.CodeNamespace: "clickhouse-reader",
|
||||
instrumentationtypes.CodeFunctionName: "GetTopLevelOperations",
|
||||
})
|
||||
|
||||
start = start.In(time.UTC)
|
||||
|
||||
// The `top_level_operations` that have `time` >= start
|
||||
@@ -398,12 +383,6 @@ func (r *ClickHouseReader) buildResourceSubQuery(tags []model.TagQueryParam, svc
|
||||
|
||||
func (r *ClickHouseReader) GetServices(ctx context.Context, queryParams *model.GetServicesParams) (*[]model.ServiceItem, *model.ApiError) {
|
||||
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.TelemetrySignal: telemetrytypes.SignalTraces.StringValue(),
|
||||
instrumentationtypes.CodeNamespace: "clickhouse-reader",
|
||||
instrumentationtypes.CodeFunctionName: "GetServices",
|
||||
})
|
||||
|
||||
if r.indexTable == "" {
|
||||
return nil, &model.ApiError{Typ: model.ErrorExec, Err: ErrNoIndexTable}
|
||||
}
|
||||
@@ -760,11 +739,6 @@ func (r *ClickHouseReader) GetEntryPointOperations(ctx context.Context, queryPar
|
||||
|
||||
func (r *ClickHouseReader) GetTopOperations(ctx context.Context, queryParams *model.GetTopOperationsParams) (*[]model.TopOperationsItem, *model.ApiError) {
|
||||
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.TelemetrySignal: telemetrytypes.SignalTraces.StringValue(),
|
||||
instrumentationtypes.CodeNamespace: "clickhouse-reader",
|
||||
instrumentationtypes.CodeFunctionName: "GetTopOperations",
|
||||
})
|
||||
namedArgs := []interface{}{
|
||||
clickhouse.Named("start", strconv.FormatInt(queryParams.Start.UnixNano(), 10)),
|
||||
clickhouse.Named("end", strconv.FormatInt(queryParams.End.UnixNano(), 10)),
|
||||
@@ -820,11 +794,6 @@ func (r *ClickHouseReader) GetTopOperations(ctx context.Context, queryParams *mo
|
||||
|
||||
func (r *ClickHouseReader) GetUsage(ctx context.Context, queryParams *model.GetUsageParams) (*[]model.UsageItem, error) {
|
||||
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.TelemetrySignal: telemetrytypes.SignalTraces.StringValue(),
|
||||
instrumentationtypes.CodeNamespace: "clickhouse-reader",
|
||||
instrumentationtypes.CodeFunctionName: "GetUsage",
|
||||
})
|
||||
var usageItems []model.UsageItem
|
||||
namedArgs := []interface{}{
|
||||
clickhouse.Named("interval", queryParams.StepHour),
|
||||
@@ -860,13 +829,6 @@ func (r *ClickHouseReader) GetUsage(ctx context.Context, queryParams *model.GetU
|
||||
}
|
||||
|
||||
func (r *ClickHouseReader) GetSpansForTrace(ctx context.Context, traceID string, traceDetailsQuery string) ([]model.SpanItemV2, *model.ApiError) {
|
||||
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.TelemetrySignal: telemetrytypes.SignalTraces.StringValue(),
|
||||
instrumentationtypes.CodeNamespace: "clickhouse-reader",
|
||||
instrumentationtypes.CodeFunctionName: "GetSpansForTrace",
|
||||
})
|
||||
|
||||
var traceSummary model.TraceSummary
|
||||
summaryQuery := fmt.Sprintf("SELECT trace_id, min(start) AS start, max(end) AS end, sum(num_spans) AS num_spans FROM %s.%s WHERE trace_id=$1 GROUP BY trace_id", r.TraceDB, r.traceSummaryTable)
|
||||
err := r.db.QueryRow(ctx, summaryQuery, traceID).Scan(&traceSummary.TraceID, &traceSummary.Start, &traceSummary.End, &traceSummary.NumSpans)
|
||||
@@ -1265,11 +1227,6 @@ func (r *ClickHouseReader) GetFlamegraphSpansForTrace(ctx context.Context, orgID
|
||||
|
||||
func (r *ClickHouseReader) GetDependencyGraph(ctx context.Context, queryParams *model.GetServicesParams) (*[]model.ServiceMapDependencyResponseItem, error) {
|
||||
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.TelemetrySignal: telemetrytypes.SignalTraces.StringValue(),
|
||||
instrumentationtypes.CodeNamespace: "clickhouse-reader",
|
||||
instrumentationtypes.CodeFunctionName: "GetDependencyGraph",
|
||||
})
|
||||
response := []model.ServiceMapDependencyResponseItem{}
|
||||
|
||||
args := []interface{}{}
|
||||
@@ -1324,11 +1281,6 @@ func getLocalTableName(tableName string) string {
|
||||
}
|
||||
|
||||
func (r *ClickHouseReader) setTTLLogs(ctx context.Context, orgID string, params *model.TTLParams) (*model.SetTTLResponseItem, *model.ApiError) {
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.TelemetrySignal: telemetrytypes.SignalLogs.StringValue(),
|
||||
instrumentationtypes.CodeNamespace: "clickhouse-reader",
|
||||
instrumentationtypes.CodeFunctionName: "setTTLLogs",
|
||||
})
|
||||
hasCustomRetention, err := r.hasCustomRetentionColumn(ctx)
|
||||
if hasCustomRetention {
|
||||
return nil, &model.ApiError{Typ: model.ErrorExec, Err: fmt.Errorf("SetTTLV2 only supported")}
|
||||
@@ -1492,11 +1444,6 @@ func (r *ClickHouseReader) setTTLLogs(ctx context.Context, orgID string, params
|
||||
}
|
||||
|
||||
func (r *ClickHouseReader) setTTLTraces(ctx context.Context, orgID string, params *model.TTLParams) (*model.SetTTLResponseItem, *model.ApiError) {
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.TelemetrySignal: telemetrytypes.SignalTraces.StringValue(),
|
||||
instrumentationtypes.CodeNamespace: "clickhouse-reader",
|
||||
instrumentationtypes.CodeFunctionName: "setTTLTraces",
|
||||
})
|
||||
// uuid is used as transaction id
|
||||
uuidWithHyphen := uuid.New()
|
||||
uuid := strings.Replace(uuidWithHyphen.String(), "-", "", -1)
|
||||
@@ -1642,12 +1589,6 @@ func (r *ClickHouseReader) setTTLTraces(ctx context.Context, orgID string, param
|
||||
}
|
||||
|
||||
func (r *ClickHouseReader) hasCustomRetentionColumn(ctx context.Context) (bool, error) {
|
||||
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.CodeNamespace: "clickhouse-reader",
|
||||
instrumentationtypes.CodeFunctionName: "hasCustomRetentionColumn",
|
||||
})
|
||||
|
||||
// Directly query for the _retention_days column existence
|
||||
query := fmt.Sprintf("SELECT 1 FROM system.columns WHERE database = '%s' AND table = '%s' AND name = '_retention_days' LIMIT 1", r.logsDB, r.logsLocalTableV2)
|
||||
|
||||
@@ -1669,11 +1610,6 @@ func (r *ClickHouseReader) hasCustomRetentionColumn(ctx context.Context) (bool,
|
||||
|
||||
func (r *ClickHouseReader) SetTTLV2(ctx context.Context, orgID string, params *model.CustomRetentionTTLParams) (*model.CustomRetentionTTLResponse, error) {
|
||||
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.TelemetrySignal: telemetrytypes.SignalLogs.StringValue(),
|
||||
instrumentationtypes.CodeNamespace: "clickhouse-reader",
|
||||
instrumentationtypes.CodeFunctionName: "SetTTLV2",
|
||||
})
|
||||
hasCustomRetention, err := r.hasCustomRetentionColumn(ctx)
|
||||
if err != nil {
|
||||
return nil, errorsV2.Wrapf(err, errorsV2.TypeInternal, errorsV2.CodeInternal, "custom retention not supported")
|
||||
@@ -2063,10 +1999,6 @@ func (r *ClickHouseReader) updateCustomRetentionTTLStatus(ctx context.Context, o
|
||||
|
||||
// Enhanced validation function with duplicate detection and efficient key validation
|
||||
func (r *ClickHouseReader) validateTTLConditions(ctx context.Context, ttlConditions []model.CustomRetentionRule) error {
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.CodeNamespace: "clickhouse-reader",
|
||||
instrumentationtypes.CodeFunctionName: "validateTTLConditions",
|
||||
})
|
||||
if len(ttlConditions) == 0 {
|
||||
return nil
|
||||
}
|
||||
@@ -2184,11 +2116,6 @@ func (r *ClickHouseReader) SetTTL(ctx context.Context, orgID string, params *mod
|
||||
}
|
||||
|
||||
func (r *ClickHouseReader) setTTLMetrics(ctx context.Context, orgID string, params *model.TTLParams) (*model.SetTTLResponseItem, *model.ApiError) {
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.TelemetrySignal: telemetrytypes.SignalMetrics.StringValue(),
|
||||
instrumentationtypes.CodeNamespace: "clickhouse-reader",
|
||||
instrumentationtypes.CodeFunctionName: "setTTLMetrics",
|
||||
})
|
||||
// uuid is used as transaction id
|
||||
uuidWithHyphen := uuid.New()
|
||||
uuid := strings.Replace(uuidWithHyphen.String(), "-", "", -1)
|
||||
@@ -2397,10 +2324,6 @@ func (r *ClickHouseReader) getTTLQueryStatus(ctx context.Context, orgID string,
|
||||
|
||||
func (r *ClickHouseReader) setColdStorage(ctx context.Context, tableName string, coldStorageVolume string) *model.ApiError {
|
||||
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.CodeNamespace: "clickhouse-reader",
|
||||
instrumentationtypes.CodeFunctionName: "setColdStorage",
|
||||
})
|
||||
// Set the storage policy for the required table. If it is already set, then setting it again
|
||||
// will not a problem.
|
||||
if len(coldStorageVolume) > 0 {
|
||||
@@ -2417,10 +2340,6 @@ func (r *ClickHouseReader) setColdStorage(ctx context.Context, tableName string,
|
||||
|
||||
// GetDisks returns a list of disks {name, type} configured in clickhouse DB.
|
||||
func (r *ClickHouseReader) GetDisks(ctx context.Context) (*[]model.DiskItem, *model.ApiError) {
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.CodeNamespace: "clickhouse-reader",
|
||||
instrumentationtypes.CodeFunctionName: "GetDisks",
|
||||
})
|
||||
diskItems := []model.DiskItem{}
|
||||
|
||||
query := "SELECT name,type FROM system.disks"
|
||||
@@ -2444,10 +2363,6 @@ func getLocalTableNameArray(tableNames []string) []string {
|
||||
// GetTTL returns current ttl, expected ttl and past setTTL status for metrics/traces.
|
||||
func (r *ClickHouseReader) GetTTL(ctx context.Context, orgID string, ttlParams *model.GetTTLParams) (*model.GetTTLResponseItem, *model.ApiError) {
|
||||
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.CodeNamespace: "clickhouse-reader",
|
||||
instrumentationtypes.CodeFunctionName: "GetTTL",
|
||||
})
|
||||
parseTTL := func(queryResp string) (int, int) {
|
||||
|
||||
zap.L().Info("Parsing TTL from: ", zap.String("queryResp", queryResp))
|
||||
@@ -2617,11 +2532,6 @@ func (r *ClickHouseReader) GetTTL(ctx context.Context, orgID string, ttlParams *
|
||||
|
||||
func (r *ClickHouseReader) ListErrors(ctx context.Context, queryParams *model.ListErrorsParams) (*[]model.Error, *model.ApiError) {
|
||||
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.TelemetrySignal: telemetrytypes.SignalTraces.StringValue(),
|
||||
instrumentationtypes.CodeNamespace: "clickhouse-reader",
|
||||
instrumentationtypes.CodeFunctionName: "ListErrors",
|
||||
})
|
||||
var getErrorResponses []model.Error
|
||||
|
||||
query := "SELECT any(exceptionMessage) as exceptionMessage, count() AS exceptionCount, min(timestamp) as firstSeen, max(timestamp) as lastSeen, groupID"
|
||||
@@ -2694,12 +2604,6 @@ func (r *ClickHouseReader) ListErrors(ctx context.Context, queryParams *model.Li
|
||||
|
||||
func (r *ClickHouseReader) CountErrors(ctx context.Context, queryParams *model.CountErrorsParams) (uint64, *model.ApiError) {
|
||||
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.TelemetrySignal: telemetrytypes.SignalTraces.StringValue(),
|
||||
instrumentationtypes.CodeNamespace: "clickhouse-reader",
|
||||
instrumentationtypes.CodeFunctionName: "CountErrors",
|
||||
})
|
||||
|
||||
var errorCount uint64
|
||||
|
||||
query := fmt.Sprintf("SELECT count(distinct(groupID)) FROM %s.%s WHERE timestamp >= @timestampL AND timestamp <= @timestampU", r.TraceDB, r.errorTable)
|
||||
@@ -2737,11 +2641,6 @@ func (r *ClickHouseReader) CountErrors(ctx context.Context, queryParams *model.C
|
||||
|
||||
func (r *ClickHouseReader) GetErrorFromErrorID(ctx context.Context, queryParams *model.GetErrorParams) (*model.ErrorWithSpan, *model.ApiError) {
|
||||
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.TelemetrySignal: telemetrytypes.SignalTraces.StringValue(),
|
||||
instrumentationtypes.CodeNamespace: "clickhouse-reader",
|
||||
instrumentationtypes.CodeFunctionName: "GetErrorFromErrorID",
|
||||
})
|
||||
if queryParams.ErrorID == "" {
|
||||
zap.L().Error("errorId missing from params")
|
||||
return nil, &model.ApiError{Typ: model.ErrorBadData, Err: fmt.Errorf("ErrorID missing from params")}
|
||||
@@ -2769,11 +2668,6 @@ func (r *ClickHouseReader) GetErrorFromErrorID(ctx context.Context, queryParams
|
||||
|
||||
func (r *ClickHouseReader) GetErrorFromGroupID(ctx context.Context, queryParams *model.GetErrorParams) (*model.ErrorWithSpan, *model.ApiError) {
|
||||
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.TelemetrySignal: telemetrytypes.SignalTraces.StringValue(),
|
||||
instrumentationtypes.CodeNamespace: "clickhouse-reader",
|
||||
instrumentationtypes.CodeFunctionName: "GetErrorFromGroupID",
|
||||
})
|
||||
var getErrorWithSpanReponse []model.ErrorWithSpan
|
||||
|
||||
query := fmt.Sprintf("SELECT errorID, exceptionType, exceptionStacktrace, exceptionEscaped, exceptionMessage, timestamp, spanID, traceID, serviceName, groupID FROM %s.%s WHERE timestamp = @timestamp AND groupID = @groupID LIMIT 1", r.TraceDB, r.errorTable)
|
||||
@@ -2822,11 +2716,6 @@ func (r *ClickHouseReader) GetNextPrevErrorIDs(ctx context.Context, queryParams
|
||||
|
||||
func (r *ClickHouseReader) getNextErrorID(ctx context.Context, queryParams *model.GetErrorParams) (string, time.Time, *model.ApiError) {
|
||||
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.TelemetrySignal: telemetrytypes.SignalTraces.StringValue(),
|
||||
instrumentationtypes.CodeNamespace: "clickhouse-reader",
|
||||
instrumentationtypes.CodeFunctionName: "getNextErrorID",
|
||||
})
|
||||
var getNextErrorIDReponse []model.NextPrevErrorIDsDBResponse
|
||||
|
||||
query := fmt.Sprintf("SELECT errorID as nextErrorID, timestamp as nextTimestamp FROM %s.%s WHERE groupID = @groupID AND timestamp >= @timestamp AND errorID != @errorID ORDER BY timestamp ASC LIMIT 2", r.TraceDB, r.errorTable)
|
||||
@@ -2896,11 +2785,6 @@ func (r *ClickHouseReader) getNextErrorID(ctx context.Context, queryParams *mode
|
||||
|
||||
func (r *ClickHouseReader) getPrevErrorID(ctx context.Context, queryParams *model.GetErrorParams) (string, time.Time, *model.ApiError) {
|
||||
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.TelemetrySignal: telemetrytypes.SignalTraces.StringValue(),
|
||||
instrumentationtypes.CodeNamespace: "clickhouse-reader",
|
||||
instrumentationtypes.CodeFunctionName: "getPrevErrorID",
|
||||
})
|
||||
var getPrevErrorIDReponse []model.NextPrevErrorIDsDBResponse
|
||||
|
||||
query := fmt.Sprintf("SELECT errorID as prevErrorID, timestamp as prevTimestamp FROM %s.%s WHERE groupID = @groupID AND timestamp <= @timestamp AND errorID != @errorID ORDER BY timestamp DESC LIMIT 2", r.TraceDB, r.errorTable)
|
||||
@@ -2992,11 +2876,6 @@ func (r *ClickHouseReader) FetchTemporality(ctx context.Context, orgID valuer.UU
|
||||
}
|
||||
|
||||
func (r *ClickHouseReader) GetLogFields(ctx context.Context) (*model.GetFieldsResponse, *model.ApiError) {
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.TelemetrySignal: telemetrytypes.SignalLogs.StringValue(),
|
||||
instrumentationtypes.CodeNamespace: "clickhouse-reader",
|
||||
instrumentationtypes.CodeFunctionName: "GetLogFields",
|
||||
})
|
||||
// response will contain top level fields from the otel log model
|
||||
response := model.GetFieldsResponse{
|
||||
Selected: constants.StaticSelectedLogFields,
|
||||
@@ -3033,11 +2912,6 @@ func (r *ClickHouseReader) GetLogFields(ctx context.Context) (*model.GetFieldsRe
|
||||
}
|
||||
|
||||
func (r *ClickHouseReader) GetLogFieldsFromNames(ctx context.Context, fieldNames []string) (*model.GetFieldsResponse, *model.ApiError) {
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.TelemetrySignal: telemetrytypes.SignalLogs.StringValue(),
|
||||
instrumentationtypes.CodeNamespace: "clickhouse-reader",
|
||||
instrumentationtypes.CodeFunctionName: "GetLogFieldsFromNames",
|
||||
})
|
||||
// response will contain top level fields from the otel log model
|
||||
response := model.GetFieldsResponse{
|
||||
Selected: constants.StaticSelectedLogFields,
|
||||
@@ -3088,10 +2962,6 @@ func (r *ClickHouseReader) extractSelectedAndInterestingFields(tableStatement st
|
||||
}
|
||||
|
||||
func (r *ClickHouseReader) UpdateLogField(ctx context.Context, field *model.UpdateField) *model.ApiError {
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.CodeNamespace: "clickhouse-reader",
|
||||
instrumentationtypes.CodeFunctionName: "UpdateLogField",
|
||||
})
|
||||
if !field.Selected {
|
||||
return model.ForbiddenError(errors.New("removing a selected field is not allowed, please reach out to support."))
|
||||
}
|
||||
@@ -3158,10 +3028,6 @@ func (r *ClickHouseReader) UpdateLogField(ctx context.Context, field *model.Upda
|
||||
}
|
||||
|
||||
func (r *ClickHouseReader) GetTraceFields(ctx context.Context) (*model.GetFieldsResponse, *model.ApiError) {
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.CodeNamespace: "clickhouse-reader",
|
||||
instrumentationtypes.CodeFunctionName: "GetTraceFields",
|
||||
})
|
||||
// response will contain top level fields from the otel trace model
|
||||
response := model.GetFieldsResponse{
|
||||
Selected: []model.Field{},
|
||||
@@ -3217,11 +3083,6 @@ func (r *ClickHouseReader) GetTraceFields(ctx context.Context) (*model.GetFields
|
||||
}
|
||||
|
||||
func (r *ClickHouseReader) UpdateTraceField(ctx context.Context, field *model.UpdateField) *model.ApiError {
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.TelemetrySignal: telemetrytypes.SignalTraces.StringValue(),
|
||||
instrumentationtypes.CodeNamespace: "clickhouse-reader",
|
||||
instrumentationtypes.CodeFunctionName: "UpdateTraceField",
|
||||
})
|
||||
if !field.Selected {
|
||||
return model.ForbiddenError(errors.New("removing a selected field is not allowed, please reach out to support."))
|
||||
}
|
||||
@@ -3313,10 +3174,6 @@ func (r *ClickHouseReader) UpdateTraceField(ctx context.Context, field *model.Up
|
||||
return nil
|
||||
}
|
||||
func (r *ClickHouseReader) QueryDashboardVars(ctx context.Context, query string) (*model.DashboardVar, error) {
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.CodeNamespace: "clickhouse-reader",
|
||||
instrumentationtypes.CodeFunctionName: "QueryDashboardVars",
|
||||
})
|
||||
var result = model.DashboardVar{VariableValues: make([]interface{}, 0)}
|
||||
rows, err := r.db.Query(ctx, query)
|
||||
|
||||
@@ -3353,11 +3210,6 @@ func (r *ClickHouseReader) QueryDashboardVars(ctx context.Context, query string)
|
||||
}
|
||||
|
||||
func (r *ClickHouseReader) GetMetricAggregateAttributes(ctx context.Context, orgID valuer.UUID, req *v3.AggregateAttributeRequest, skipSignozMetrics bool) (*v3.AggregateAttributeResponse, error) {
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.TelemetrySignal: telemetrytypes.SignalMetrics.StringValue(),
|
||||
instrumentationtypes.CodeNamespace: "clickhouse-reader",
|
||||
instrumentationtypes.CodeFunctionName: "GetMetricAggregateAttributes",
|
||||
})
|
||||
var response v3.AggregateAttributeResponse
|
||||
normalized := true
|
||||
if constants.IsDotMetricsEnabled {
|
||||
@@ -3436,11 +3288,6 @@ func (r *ClickHouseReader) GetMetricAggregateAttributes(ctx context.Context, org
|
||||
}
|
||||
|
||||
func (r *ClickHouseReader) GetMeterAggregateAttributes(ctx context.Context, orgID valuer.UUID, req *v3.AggregateAttributeRequest) (*v3.AggregateAttributeResponse, error) {
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.TelemetrySignal: telemetrytypes.SignalMetrics.StringValue(),
|
||||
instrumentationtypes.CodeNamespace: "clickhouse-reader",
|
||||
instrumentationtypes.CodeFunctionName: "GetMeterAggregateAttributes",
|
||||
})
|
||||
var response v3.AggregateAttributeResponse
|
||||
// Query all relevant metric names from time_series_v4, but leave metadata retrieval to cache/db
|
||||
query := fmt.Sprintf(
|
||||
@@ -3489,11 +3336,6 @@ func (r *ClickHouseReader) GetMeterAggregateAttributes(ctx context.Context, orgI
|
||||
}
|
||||
|
||||
func (r *ClickHouseReader) GetMetricAttributeKeys(ctx context.Context, req *v3.FilterAttributeKeyRequest) (*v3.FilterAttributeKeyResponse, error) {
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.TelemetrySignal: telemetrytypes.SignalMetrics.StringValue(),
|
||||
instrumentationtypes.CodeNamespace: "clickhouse-reader",
|
||||
instrumentationtypes.CodeFunctionName: "GetMetricAttributeKeys",
|
||||
})
|
||||
var query string
|
||||
var err error
|
||||
var rows driver.Rows
|
||||
@@ -3534,11 +3376,6 @@ func (r *ClickHouseReader) GetMetricAttributeKeys(ctx context.Context, req *v3.F
|
||||
}
|
||||
|
||||
func (r *ClickHouseReader) GetMeterAttributeKeys(ctx context.Context, req *v3.FilterAttributeKeyRequest) (*v3.FilterAttributeKeyResponse, error) {
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.TelemetrySignal: telemetrytypes.SignalMetrics.StringValue(),
|
||||
instrumentationtypes.CodeNamespace: "clickhouse-reader",
|
||||
instrumentationtypes.CodeFunctionName: "GetMeterAttributeKeys",
|
||||
})
|
||||
var query string
|
||||
var err error
|
||||
var rows driver.Rows
|
||||
@@ -3575,11 +3412,6 @@ func (r *ClickHouseReader) GetMeterAttributeKeys(ctx context.Context, req *v3.Fi
|
||||
|
||||
func (r *ClickHouseReader) GetMetricAttributeValues(ctx context.Context, req *v3.FilterAttributeValueRequest) (*v3.FilterAttributeValueResponse, error) {
|
||||
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.TelemetrySignal: telemetrytypes.SignalMetrics.StringValue(),
|
||||
instrumentationtypes.CodeNamespace: "clickhouse-reader",
|
||||
instrumentationtypes.CodeFunctionName: "GetMetricAttributeValues",
|
||||
})
|
||||
var query string
|
||||
var err error
|
||||
var rows driver.Rows
|
||||
@@ -3620,11 +3452,6 @@ func (r *ClickHouseReader) GetMetricAttributeValues(ctx context.Context, req *v3
|
||||
|
||||
func (r *ClickHouseReader) GetMetricMetadata(ctx context.Context, orgID valuer.UUID, metricName, serviceName string) (*v3.MetricMetadataResponse, error) {
|
||||
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.TelemetrySignal: telemetrytypes.SignalMetrics.StringValue(),
|
||||
instrumentationtypes.CodeNamespace: "clickhouse-reader",
|
||||
instrumentationtypes.CodeFunctionName: "GetMetricMetadata",
|
||||
})
|
||||
unixMilli := common.PastDayRoundOff()
|
||||
|
||||
// 1. Fetch metadata from cache/db using unified function
|
||||
@@ -3706,10 +3533,6 @@ func (r *ClickHouseReader) GetMetricMetadata(ctx context.Context, orgID valuer.U
|
||||
// GetCountOfThings returns the count of things in the query
|
||||
// This is a generic function that can be used to check if any data exists for a given query
|
||||
func (r *ClickHouseReader) GetCountOfThings(ctx context.Context, query string) (uint64, error) {
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.CodeNamespace: "clickhouse-reader",
|
||||
instrumentationtypes.CodeFunctionName: "GetCountOfThings",
|
||||
})
|
||||
var count uint64
|
||||
err := r.db.QueryRow(ctx, query).Scan(&count)
|
||||
if err != nil {
|
||||
@@ -3760,11 +3583,6 @@ func (r *ClickHouseReader) GetActiveHostsFromMetricMetadata(ctx context.Context,
|
||||
func (r *ClickHouseReader) GetLatestReceivedMetric(
|
||||
ctx context.Context, metricNames []string, labelValues map[string]string,
|
||||
) (*model.MetricStatus, *model.ApiError) {
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.TelemetrySignal: telemetrytypes.SignalMetrics.StringValue(),
|
||||
instrumentationtypes.CodeNamespace: "clickhouse-reader",
|
||||
instrumentationtypes.CodeFunctionName: "GetLatestReceivedMetric",
|
||||
})
|
||||
// at least 1 metric name must be specified.
|
||||
// this query can be too slow otherwise.
|
||||
if len(metricNames) < 1 {
|
||||
@@ -3849,11 +3667,6 @@ func isColumn(tableStatement, attrType, field, datType string) bool {
|
||||
|
||||
func (r *ClickHouseReader) GetLogAggregateAttributes(ctx context.Context, req *v3.AggregateAttributeRequest) (*v3.AggregateAttributeResponse, error) {
|
||||
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.TelemetrySignal: telemetrytypes.SignalLogs.StringValue(),
|
||||
instrumentationtypes.CodeNamespace: "clickhouse-reader",
|
||||
instrumentationtypes.CodeFunctionName: "GetLogAggregateAttributes",
|
||||
})
|
||||
var query string
|
||||
var err error
|
||||
var rows driver.Rows
|
||||
@@ -3938,11 +3751,6 @@ func (r *ClickHouseReader) GetLogAggregateAttributes(ctx context.Context, req *v
|
||||
}
|
||||
|
||||
func (r *ClickHouseReader) GetLogAttributeKeys(ctx context.Context, req *v3.FilterAttributeKeyRequest) (*v3.FilterAttributeKeyResponse, error) {
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.TelemetrySignal: telemetrytypes.SignalLogs.StringValue(),
|
||||
instrumentationtypes.CodeNamespace: "clickhouse-reader",
|
||||
instrumentationtypes.CodeFunctionName: "GetLogAttributeKeys",
|
||||
})
|
||||
var query string
|
||||
var err error
|
||||
var rows driver.Rows
|
||||
@@ -4009,10 +3817,6 @@ func (r *ClickHouseReader) GetLogAttributeKeys(ctx context.Context, req *v3.Filt
|
||||
}
|
||||
|
||||
func (r *ClickHouseReader) FetchRelatedValues(ctx context.Context, req *v3.FilterAttributeValueRequest) ([]string, error) {
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.CodeNamespace: "clickhouse-reader",
|
||||
instrumentationtypes.CodeFunctionName: "FetchRelatedValues",
|
||||
})
|
||||
var andConditions []string
|
||||
|
||||
andConditions = append(andConditions, fmt.Sprintf("unix_milli >= %d", req.StartTimeMillis))
|
||||
@@ -4104,11 +3908,6 @@ func (r *ClickHouseReader) FetchRelatedValues(ctx context.Context, req *v3.Filte
|
||||
}
|
||||
|
||||
func (r *ClickHouseReader) GetLogAttributeValues(ctx context.Context, req *v3.FilterAttributeValueRequest) (*v3.FilterAttributeValueResponse, error) {
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.TelemetrySignal: telemetrytypes.SignalLogs.StringValue(),
|
||||
instrumentationtypes.CodeNamespace: "clickhouse-reader",
|
||||
instrumentationtypes.CodeFunctionName: "GetLogAttributeValues",
|
||||
})
|
||||
var err error
|
||||
var filterValueColumn string
|
||||
var rows driver.Rows
|
||||
@@ -4426,10 +4225,6 @@ func readRowsForTimeSeriesResult(rows driver.Rows, vars []interface{}, columnNam
|
||||
|
||||
// GetTimeSeriesResultV3 runs the query and returns list of time series
|
||||
func (r *ClickHouseReader) GetTimeSeriesResultV3(ctx context.Context, query string) ([]*v3.Series, error) {
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.CodeNamespace: "clickhouse-reader",
|
||||
instrumentationtypes.CodeFunctionName: "GetTimeSeriesResultV3",
|
||||
})
|
||||
// Hook up query progress reporting if requested.
|
||||
queryId := ctx.Value("queryId")
|
||||
if queryId != nil {
|
||||
@@ -4493,10 +4288,6 @@ func (r *ClickHouseReader) GetTimeSeriesResultV3(ctx context.Context, query stri
|
||||
|
||||
// GetListResultV3 runs the query and returns list of rows
|
||||
func (r *ClickHouseReader) GetListResultV3(ctx context.Context, query string) ([]*v3.Row, error) {
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.CodeNamespace: "clickhouse-reader",
|
||||
instrumentationtypes.CodeFunctionName: "GetListResultV3",
|
||||
})
|
||||
rows, err := r.db.Query(ctx, query)
|
||||
if err != nil {
|
||||
zap.L().Error("error while reading time series result", zap.Error(err))
|
||||
@@ -4559,11 +4350,6 @@ func (r *ClickHouseReader) GetListResultV3(ctx context.Context, query string) ([
|
||||
// GetHostMetricsExistenceAndEarliestTime returns (count, minFirstReportedUnixMilli, error) for the given host metric names
|
||||
// from distributed_metadata. When count is 0, minFirstReportedUnixMilli is 0.
|
||||
func (r *ClickHouseReader) GetMetricsExistenceAndEarliestTime(ctx context.Context, metricNames []string) (uint64, uint64, error) {
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.TelemetrySignal: telemetrytypes.SignalMetrics.StringValue(),
|
||||
instrumentationtypes.CodeNamespace: "clickhouse-reader",
|
||||
instrumentationtypes.CodeFunctionName: "GetMetricsExistenceAndEarliestTime",
|
||||
})
|
||||
if len(metricNames) == 0 {
|
||||
return 0, 0, nil
|
||||
}
|
||||
@@ -4599,10 +4385,6 @@ func getPersonalisedError(err error) error {
|
||||
}
|
||||
|
||||
func (r *ClickHouseReader) CheckClickHouse(ctx context.Context) error {
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.CodeNamespace: "clickhouse-reader",
|
||||
instrumentationtypes.CodeFunctionName: "CheckClickHouse",
|
||||
})
|
||||
rows, err := r.db.Query(ctx, "SELECT 1")
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -4613,11 +4395,6 @@ func (r *ClickHouseReader) CheckClickHouse(ctx context.Context) error {
|
||||
}
|
||||
|
||||
func (r *ClickHouseReader) GetTraceAggregateAttributes(ctx context.Context, req *v3.AggregateAttributeRequest) (*v3.AggregateAttributeResponse, error) {
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.TelemetrySignal: telemetrytypes.SignalTraces.StringValue(),
|
||||
instrumentationtypes.CodeNamespace: "clickhouse-reader",
|
||||
instrumentationtypes.CodeFunctionName: "GetTraceAggregateAttributes",
|
||||
})
|
||||
var query string
|
||||
var err error
|
||||
var rows driver.Rows
|
||||
@@ -4711,11 +4488,6 @@ func (r *ClickHouseReader) GetTraceAggregateAttributes(ctx context.Context, req
|
||||
|
||||
func (r *ClickHouseReader) GetTraceAttributeKeys(ctx context.Context, req *v3.FilterAttributeKeyRequest) (*v3.FilterAttributeKeyResponse, error) {
|
||||
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.TelemetrySignal: telemetrytypes.SignalTraces.StringValue(),
|
||||
instrumentationtypes.CodeNamespace: "clickhouse-reader",
|
||||
instrumentationtypes.CodeFunctionName: "GetTraceAttributeKeys",
|
||||
})
|
||||
var query string
|
||||
var err error
|
||||
var rows driver.Rows
|
||||
@@ -4784,11 +4556,6 @@ func (r *ClickHouseReader) GetTraceAttributeKeys(ctx context.Context, req *v3.Fi
|
||||
}
|
||||
|
||||
func (r *ClickHouseReader) GetTraceAttributeValues(ctx context.Context, req *v3.FilterAttributeValueRequest) (*v3.FilterAttributeValueResponse, error) {
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.TelemetrySignal: telemetrytypes.SignalTraces.StringValue(),
|
||||
instrumentationtypes.CodeNamespace: "clickhouse-reader",
|
||||
instrumentationtypes.CodeFunctionName: "GetTraceAttributeValues",
|
||||
})
|
||||
var query string
|
||||
var filterValueColumn string
|
||||
var err error
|
||||
@@ -4882,11 +4649,6 @@ func (r *ClickHouseReader) GetTraceAttributeValues(ctx context.Context, req *v3.
|
||||
}
|
||||
|
||||
func (r *ClickHouseReader) GetSpanAttributeKeysByNames(ctx context.Context, names []string) (map[string]v3.AttributeKey, error) {
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.TelemetrySignal: telemetrytypes.SignalTraces.StringValue(),
|
||||
instrumentationtypes.CodeNamespace: "clickhouse-reader",
|
||||
instrumentationtypes.CodeFunctionName: "GetSpanAttributeKeysByNames",
|
||||
})
|
||||
var query string
|
||||
var err error
|
||||
var rows driver.Rows
|
||||
@@ -4935,10 +4697,6 @@ func (r *ClickHouseReader) GetSpanAttributeKeysByNames(ctx context.Context, name
|
||||
}
|
||||
|
||||
func (r *ClickHouseReader) AddRuleStateHistory(ctx context.Context, ruleStateHistory []model.RuleStateHistory) error {
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.CodeNamespace: "clickhouse-reader",
|
||||
instrumentationtypes.CodeFunctionName: "AddRuleStateHistory",
|
||||
})
|
||||
var statement driver.Batch
|
||||
var err error
|
||||
|
||||
@@ -4970,10 +4728,6 @@ func (r *ClickHouseReader) AddRuleStateHistory(ctx context.Context, ruleStateHis
|
||||
}
|
||||
|
||||
func (r *ClickHouseReader) GetLastSavedRuleStateHistory(ctx context.Context, ruleID string) ([]model.RuleStateHistory, error) {
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.CodeNamespace: "clickhouse-reader",
|
||||
instrumentationtypes.CodeFunctionName: "GetLastSavedRuleStateHistory",
|
||||
})
|
||||
query := fmt.Sprintf("SELECT * FROM %s.%s WHERE rule_id = '%s' AND state_changed = true ORDER BY unix_milli DESC LIMIT 1 BY fingerprint",
|
||||
signozHistoryDBName, ruleStateHistoryTableName, ruleID)
|
||||
|
||||
@@ -4988,10 +4742,6 @@ func (r *ClickHouseReader) GetLastSavedRuleStateHistory(ctx context.Context, rul
|
||||
func (r *ClickHouseReader) ReadRuleStateHistoryByRuleID(
|
||||
ctx context.Context, ruleID string, params *model.QueryRuleStateHistory) (*model.RuleStateTimeline, error) {
|
||||
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.CodeNamespace: "clickhouse-reader",
|
||||
instrumentationtypes.CodeFunctionName: "ReadRuleStateHistoryByRuleID",
|
||||
})
|
||||
var conditions []string
|
||||
|
||||
conditions = append(conditions, fmt.Sprintf("rule_id = '%s'", ruleID))
|
||||
@@ -5106,10 +4856,6 @@ func (r *ClickHouseReader) ReadRuleStateHistoryByRuleID(
|
||||
|
||||
func (r *ClickHouseReader) ReadRuleStateHistoryTopContributorsByRuleID(
|
||||
ctx context.Context, ruleID string, params *model.QueryRuleStateHistory) ([]model.RuleStateHistoryContributor, error) {
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.CodeNamespace: "clickhouse-reader",
|
||||
instrumentationtypes.CodeFunctionName: "ReadRuleStateHistoryTopContributorsByRuleID",
|
||||
})
|
||||
query := fmt.Sprintf(`SELECT
|
||||
fingerprint,
|
||||
any(labels) as labels,
|
||||
@@ -5134,10 +4880,6 @@ func (r *ClickHouseReader) ReadRuleStateHistoryTopContributorsByRuleID(
|
||||
|
||||
func (r *ClickHouseReader) GetOverallStateTransitions(ctx context.Context, ruleID string, params *model.QueryRuleStateHistory) ([]model.ReleStateItem, error) {
|
||||
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.CodeNamespace: "clickhouse-reader",
|
||||
instrumentationtypes.CodeFunctionName: "GetOverallStateTransitions",
|
||||
})
|
||||
tmpl := `WITH firing_events AS (
|
||||
SELECT
|
||||
rule_id,
|
||||
@@ -5265,10 +5007,6 @@ ORDER BY firing_time ASC;`
|
||||
|
||||
func (r *ClickHouseReader) GetAvgResolutionTime(ctx context.Context, ruleID string, params *model.QueryRuleStateHistory) (float64, error) {
|
||||
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.CodeNamespace: "clickhouse-reader",
|
||||
instrumentationtypes.CodeFunctionName: "GetAvgResolutionTime",
|
||||
})
|
||||
tmpl := `
|
||||
WITH firing_events AS (
|
||||
SELECT
|
||||
@@ -5380,10 +5118,6 @@ ORDER BY ts ASC;`
|
||||
}
|
||||
|
||||
func (r *ClickHouseReader) GetTotalTriggers(ctx context.Context, ruleID string, params *model.QueryRuleStateHistory) (uint64, error) {
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.CodeNamespace: "clickhouse-reader",
|
||||
instrumentationtypes.CodeFunctionName: "GetTotalTriggers",
|
||||
})
|
||||
query := fmt.Sprintf("SELECT count(*) FROM %s.%s WHERE rule_id = '%s' AND (state_changed = true) AND (state = '%s') AND unix_milli >= %d AND unix_milli <= %d",
|
||||
signozHistoryDBName, ruleStateHistoryTableName, ruleID, model.StateFiring.String(), params.Start, params.End)
|
||||
|
||||
@@ -5412,11 +5146,6 @@ func (r *ClickHouseReader) GetTriggersByInterval(ctx context.Context, ruleID str
|
||||
}
|
||||
|
||||
func (r *ClickHouseReader) GetMinAndMaxTimestampForTraceID(ctx context.Context, traceID []string) (int64, int64, error) {
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.TelemetrySignal: telemetrytypes.SignalTraces.StringValue(),
|
||||
instrumentationtypes.CodeNamespace: "clickhouse-reader",
|
||||
instrumentationtypes.CodeFunctionName: "GetMinAndMaxTimestampForTraceID",
|
||||
})
|
||||
var minTime, maxTime time.Time
|
||||
|
||||
query := fmt.Sprintf("SELECT min(timestamp), max(timestamp) FROM %s.%s WHERE traceID IN ('%s')",
|
||||
@@ -5454,11 +5183,6 @@ func (r *ClickHouseReader) SubscribeToQueryProgress(
|
||||
}
|
||||
|
||||
func (r *ClickHouseReader) GetAllMetricFilterAttributeKeys(ctx context.Context, req *metrics_explorer.FilterKeyRequest) (*[]v3.AttributeKey, *model.ApiError) {
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.TelemetrySignal: telemetrytypes.SignalMetrics.StringValue(),
|
||||
instrumentationtypes.CodeNamespace: "clickhouse-reader",
|
||||
instrumentationtypes.CodeFunctionName: "GetAllMetricFilterAttributeKeys",
|
||||
})
|
||||
var rows driver.Rows
|
||||
var response []v3.AttributeKey
|
||||
normalized := true
|
||||
@@ -5496,11 +5220,6 @@ func (r *ClickHouseReader) GetAllMetricFilterAttributeKeys(ctx context.Context,
|
||||
}
|
||||
|
||||
func (r *ClickHouseReader) GetAllMetricFilterAttributeValues(ctx context.Context, req *metrics_explorer.FilterValueRequest) ([]string, *model.ApiError) {
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.TelemetrySignal: telemetrytypes.SignalMetrics.StringValue(),
|
||||
instrumentationtypes.CodeNamespace: "clickhouse-reader",
|
||||
instrumentationtypes.CodeFunctionName: "GetAllMetricFilterAttributeValues",
|
||||
})
|
||||
var query string
|
||||
var err error
|
||||
var rows driver.Rows
|
||||
@@ -5537,11 +5256,6 @@ func (r *ClickHouseReader) GetAllMetricFilterAttributeValues(ctx context.Context
|
||||
}
|
||||
|
||||
func (r *ClickHouseReader) GetAllMetricFilterUnits(ctx context.Context, req *metrics_explorer.FilterValueRequest) ([]string, *model.ApiError) {
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.TelemetrySignal: telemetrytypes.SignalMetrics.StringValue(),
|
||||
instrumentationtypes.CodeNamespace: "clickhouse-reader",
|
||||
instrumentationtypes.CodeFunctionName: "GetAllMetricFilterUnits",
|
||||
})
|
||||
var rows driver.Rows
|
||||
var response []string
|
||||
query := fmt.Sprintf("SELECT DISTINCT unit FROM %s.%s WHERE unit ILIKE $1 AND unit IS NOT NULL ORDER BY unit", signozMetricDBName, signozTSTableNameV41Day)
|
||||
@@ -5569,11 +5283,6 @@ func (r *ClickHouseReader) GetAllMetricFilterUnits(ctx context.Context, req *met
|
||||
return response, nil
|
||||
}
|
||||
func (r *ClickHouseReader) GetAllMetricFilterTypes(ctx context.Context, req *metrics_explorer.FilterValueRequest) ([]string, *model.ApiError) {
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.TelemetrySignal: telemetrytypes.SignalMetrics.StringValue(),
|
||||
instrumentationtypes.CodeNamespace: "clickhouse-reader",
|
||||
instrumentationtypes.CodeFunctionName: "GetAllMetricFilterTypes",
|
||||
})
|
||||
var rows driver.Rows
|
||||
var response []string
|
||||
query := fmt.Sprintf("SELECT DISTINCT type FROM %s.%s WHERE type ILIKE $1 AND type IS NOT NULL ORDER BY type", signozMetricDBName, signozTSTableNameV41Day)
|
||||
@@ -5601,11 +5310,6 @@ func (r *ClickHouseReader) GetAllMetricFilterTypes(ctx context.Context, req *met
|
||||
}
|
||||
|
||||
func (r *ClickHouseReader) GetMetricsDataPoints(ctx context.Context, metricName string) (uint64, *model.ApiError) {
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.TelemetrySignal: telemetrytypes.SignalMetrics.StringValue(),
|
||||
instrumentationtypes.CodeNamespace: "clickhouse-reader",
|
||||
instrumentationtypes.CodeFunctionName: "GetMetricsDataPoints",
|
||||
})
|
||||
query := fmt.Sprintf(`SELECT
|
||||
sum(count) as data_points
|
||||
FROM %s.%s
|
||||
@@ -5621,11 +5325,6 @@ WHERE metric_name = ?
|
||||
}
|
||||
|
||||
func (r *ClickHouseReader) GetMetricsLastReceived(ctx context.Context, metricName string) (int64, *model.ApiError) {
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.TelemetrySignal: telemetrytypes.SignalMetrics.StringValue(),
|
||||
instrumentationtypes.CodeNamespace: "clickhouse-reader",
|
||||
instrumentationtypes.CodeFunctionName: "GetMetricsLastReceived",
|
||||
})
|
||||
query := fmt.Sprintf(`SELECT
|
||||
MAX(unix_milli) AS last_received_time
|
||||
FROM %s.%s
|
||||
@@ -5651,11 +5350,6 @@ WHERE metric_name = ? and unix_milli > ?
|
||||
}
|
||||
|
||||
func (r *ClickHouseReader) GetTotalTimeSeriesForMetricName(ctx context.Context, metricName string) (uint64, *model.ApiError) {
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.TelemetrySignal: telemetrytypes.SignalMetrics.StringValue(),
|
||||
instrumentationtypes.CodeNamespace: "clickhouse-reader",
|
||||
instrumentationtypes.CodeFunctionName: "GetTotalTimeSeriesForMetricName",
|
||||
})
|
||||
query := fmt.Sprintf(`SELECT
|
||||
uniq(fingerprint) AS timeSeriesCount
|
||||
FROM %s.%s
|
||||
@@ -5670,11 +5364,6 @@ WHERE metric_name = ?;`, signozMetricDBName, signozTSTableNameV41Week)
|
||||
}
|
||||
|
||||
func (r *ClickHouseReader) GetAttributesForMetricName(ctx context.Context, metricName string, start, end *int64, filters *v3.FilterSet) (*[]metrics_explorer.Attribute, *model.ApiError) {
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.TelemetrySignal: telemetrytypes.SignalMetrics.StringValue(),
|
||||
instrumentationtypes.CodeNamespace: "clickhouse-reader",
|
||||
instrumentationtypes.CodeFunctionName: "GetAttributesForMetricName",
|
||||
})
|
||||
whereClause := ""
|
||||
if filters != nil {
|
||||
conditions, _ := utils.BuildFilterConditions(filters, "t")
|
||||
@@ -5742,11 +5431,6 @@ WHERE metric_name = ? AND __normalized=? %s`
|
||||
}
|
||||
|
||||
func (r *ClickHouseReader) GetActiveTimeSeriesForMetricName(ctx context.Context, metricName string, duration time.Duration) (uint64, *model.ApiError) {
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.TelemetrySignal: telemetrytypes.SignalMetrics.StringValue(),
|
||||
instrumentationtypes.CodeNamespace: "clickhouse-reader",
|
||||
instrumentationtypes.CodeFunctionName: "GetActiveTimeSeriesForMetricName",
|
||||
})
|
||||
milli := time.Now().Add(-duration).UnixMilli()
|
||||
query := fmt.Sprintf("SELECT uniq(fingerprint) FROM %s.%s WHERE metric_name = '%s' and unix_milli >= ?", signozMetricDBName, signozTSTableNameV4, metricName)
|
||||
var timeSeries uint64
|
||||
@@ -5760,11 +5444,6 @@ func (r *ClickHouseReader) GetActiveTimeSeriesForMetricName(ctx context.Context,
|
||||
}
|
||||
|
||||
func (r *ClickHouseReader) ListSummaryMetrics(ctx context.Context, orgID valuer.UUID, req *metrics_explorer.SummaryListMetricsRequest) (*metrics_explorer.SummaryListMetricsResponse, *model.ApiError) {
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.TelemetrySignal: telemetrytypes.SignalMetrics.StringValue(),
|
||||
instrumentationtypes.CodeNamespace: "clickhouse-reader",
|
||||
instrumentationtypes.CodeFunctionName: "ListSummaryMetrics",
|
||||
})
|
||||
var args []interface{}
|
||||
|
||||
// Build filter conditions (if any)
|
||||
@@ -5983,11 +5662,6 @@ func (r *ClickHouseReader) ListSummaryMetrics(ctx context.Context, orgID valuer.
|
||||
}
|
||||
|
||||
func (r *ClickHouseReader) GetMetricsTimeSeriesPercentage(ctx context.Context, req *metrics_explorer.TreeMapMetricsRequest) (*[]metrics_explorer.TreeMapResponseItem, *model.ApiError) {
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.TelemetrySignal: telemetrytypes.SignalMetrics.StringValue(),
|
||||
instrumentationtypes.CodeNamespace: "clickhouse-reader",
|
||||
instrumentationtypes.CodeFunctionName: "GetMetricsTimeSeriesPercentage",
|
||||
})
|
||||
var args []interface{}
|
||||
|
||||
normalized := true
|
||||
@@ -6068,11 +5742,6 @@ func (r *ClickHouseReader) GetMetricsTimeSeriesPercentage(ctx context.Context, r
|
||||
|
||||
func (r *ClickHouseReader) GetMetricsSamplesPercentage(ctx context.Context, req *metrics_explorer.TreeMapMetricsRequest) (*[]metrics_explorer.TreeMapResponseItem, *model.ApiError) {
|
||||
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.TelemetrySignal: telemetrytypes.SignalMetrics.StringValue(),
|
||||
instrumentationtypes.CodeNamespace: "clickhouse-reader",
|
||||
instrumentationtypes.CodeFunctionName: "GetMetricsSamplesPercentage",
|
||||
})
|
||||
conditions, _ := utils.BuildFilterConditions(&req.Filters, "ts")
|
||||
whereClause := ""
|
||||
if conditions != nil {
|
||||
@@ -6232,11 +5901,6 @@ func (r *ClickHouseReader) GetMetricsSamplesPercentage(ctx context.Context, req
|
||||
}
|
||||
|
||||
func (r *ClickHouseReader) GetNameSimilarity(ctx context.Context, req *metrics_explorer.RelatedMetricsRequest) (map[string]metrics_explorer.RelatedMetricsScore, *model.ApiError) {
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.TelemetrySignal: telemetrytypes.SignalMetrics.StringValue(),
|
||||
instrumentationtypes.CodeNamespace: "clickhouse-reader",
|
||||
instrumentationtypes.CodeFunctionName: "GetNameSimilarity",
|
||||
})
|
||||
start, end, tsTable, _ := utils.WhichTSTableToUse(req.Start, req.End)
|
||||
|
||||
normalized := true
|
||||
@@ -6290,11 +5954,6 @@ func (r *ClickHouseReader) GetNameSimilarity(ctx context.Context, req *metrics_e
|
||||
}
|
||||
|
||||
func (r *ClickHouseReader) GetAttributeSimilarity(ctx context.Context, req *metrics_explorer.RelatedMetricsRequest) (map[string]metrics_explorer.RelatedMetricsScore, *model.ApiError) {
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.TelemetrySignal: telemetrytypes.SignalMetrics.StringValue(),
|
||||
instrumentationtypes.CodeNamespace: "clickhouse-reader",
|
||||
instrumentationtypes.CodeFunctionName: "GetAttributeSimilarity",
|
||||
})
|
||||
start, end, tsTable, _ := utils.WhichTSTableToUse(req.Start, req.End)
|
||||
|
||||
normalized := true
|
||||
@@ -6453,11 +6112,6 @@ func (r *ClickHouseReader) GetAttributeSimilarity(ctx context.Context, req *metr
|
||||
}
|
||||
|
||||
func (r *ClickHouseReader) GetMetricsAllResourceAttributes(ctx context.Context, start int64, end int64) (map[string]uint64, *model.ApiError) {
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.TelemetrySignal: telemetrytypes.SignalMetrics.StringValue(),
|
||||
instrumentationtypes.CodeNamespace: "clickhouse-reader",
|
||||
instrumentationtypes.CodeFunctionName: "GetMetricsAllResourceAttributes",
|
||||
})
|
||||
start, end, attTable, _ := utils.WhichAttributesTableToUse(start, end)
|
||||
query := fmt.Sprintf(`SELECT
|
||||
key,
|
||||
@@ -6494,11 +6148,6 @@ ORDER BY distinct_value_count DESC;`, signozMetadataDbName, attTable)
|
||||
}
|
||||
|
||||
func (r *ClickHouseReader) GetInspectMetrics(ctx context.Context, req *metrics_explorer.InspectMetricsRequest, fingerprints []string) (*metrics_explorer.InspectMetricsResponse, *model.ApiError) {
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.TelemetrySignal: telemetrytypes.SignalMetrics.StringValue(),
|
||||
instrumentationtypes.CodeNamespace: "clickhouse-reader",
|
||||
instrumentationtypes.CodeFunctionName: "GetInspectMetrics",
|
||||
})
|
||||
start, end, _, localTsTable := utils.WhichTSTableToUse(req.Start, req.End)
|
||||
fingerprintsString := strings.Join(fingerprints, ",")
|
||||
query := fmt.Sprintf(`SELECT
|
||||
@@ -6593,11 +6242,6 @@ func (r *ClickHouseReader) GetInspectMetrics(ctx context.Context, req *metrics_e
|
||||
}
|
||||
|
||||
func (r *ClickHouseReader) GetInspectMetricsFingerprints(ctx context.Context, attributes []string, req *metrics_explorer.InspectMetricsRequest) ([]string, *model.ApiError) {
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.TelemetrySignal: telemetrytypes.SignalMetrics.StringValue(),
|
||||
instrumentationtypes.CodeNamespace: "clickhouse-reader",
|
||||
instrumentationtypes.CodeFunctionName: "GetInspectMetricsFingerprints",
|
||||
})
|
||||
// Build dynamic key selections and JSON extracts
|
||||
var jsonExtracts []string
|
||||
var groupBys []string
|
||||
@@ -6679,11 +6323,6 @@ LIMIT 40`, // added rand to get diff value every time we run this query
|
||||
}
|
||||
|
||||
func (r *ClickHouseReader) UpdateMetricsMetadata(ctx context.Context, orgID valuer.UUID, req *model.UpdateMetricsMetadata) *model.ApiError {
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.TelemetrySignal: telemetrytypes.SignalMetrics.StringValue(),
|
||||
instrumentationtypes.CodeNamespace: "clickhouse-reader",
|
||||
instrumentationtypes.CodeFunctionName: "UpdateMetricsMetadata",
|
||||
})
|
||||
if req.MetricType == v3.MetricTypeHistogram {
|
||||
labels := []string{"le"}
|
||||
hasLabels, apiError := r.CheckForLabelsInMetric(ctx, req.MetricName, labels)
|
||||
@@ -6728,11 +6367,6 @@ VALUES ( ?, ?, ?, ?, ?, ?, ?);`, signozMetricDBName, signozUpdatedMetricsMetadat
|
||||
}
|
||||
|
||||
func (r *ClickHouseReader) CheckForLabelsInMetric(ctx context.Context, metricName string, labels []string) (bool, *model.ApiError) {
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.TelemetrySignal: telemetrytypes.SignalMetrics.StringValue(),
|
||||
instrumentationtypes.CodeNamespace: "clickhouse-reader",
|
||||
instrumentationtypes.CodeFunctionName: "CheckForLabelsInMetric",
|
||||
})
|
||||
if len(labels) == 0 {
|
||||
return true, nil
|
||||
}
|
||||
@@ -6767,11 +6401,6 @@ func (r *ClickHouseReader) CheckForLabelsInMetric(ctx context.Context, metricNam
|
||||
}
|
||||
|
||||
func (r *ClickHouseReader) GetUpdatedMetricsMetadata(ctx context.Context, orgID valuer.UUID, metricNames ...string) (map[string]*model.UpdateMetricsMetadata, *model.ApiError) {
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.TelemetrySignal: telemetrytypes.SignalMetrics.StringValue(),
|
||||
instrumentationtypes.CodeNamespace: "clickhouse-reader",
|
||||
instrumentationtypes.CodeFunctionName: "GetUpdatedMetricsMetadata",
|
||||
})
|
||||
cachedMetadata := make(map[string]*model.UpdateMetricsMetadata)
|
||||
var missingMetrics []string
|
||||
|
||||
@@ -6881,11 +6510,6 @@ func (r *ClickHouseReader) GetUpdatedMetricsMetadata(ctx context.Context, orgID
|
||||
}
|
||||
|
||||
func (r *ClickHouseReader) SearchTraces(ctx context.Context, params *model.SearchTracesParams) (*[]model.SearchSpansResult, error) {
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.TelemetrySignal: telemetrytypes.SignalTraces.StringValue(),
|
||||
instrumentationtypes.CodeNamespace: "clickhouse-reader",
|
||||
instrumentationtypes.CodeFunctionName: "SearchTraces",
|
||||
})
|
||||
searchSpansResult := []model.SearchSpansResult{
|
||||
{
|
||||
Columns: []string{"__time", "SpanId", "TraceId", "ServiceName", "Name", "Kind", "DurationNano", "TagsKeys", "TagsValues", "References", "Events", "HasError", "StatusMessage", "StatusCodeString", "SpanKind"},
|
||||
@@ -6997,11 +6621,6 @@ func (r *ClickHouseReader) GetNormalizedStatus(
|
||||
metricNames []string,
|
||||
) (map[string]bool, error) {
|
||||
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.TelemetrySignal: telemetrytypes.SignalMetrics.StringValue(),
|
||||
instrumentationtypes.CodeNamespace: "clickhouse-reader",
|
||||
instrumentationtypes.CodeFunctionName: "GetNormalizedStatus",
|
||||
})
|
||||
if len(metricNames) == 0 {
|
||||
return map[string]bool{}, nil
|
||||
}
|
||||
|
||||
@@ -62,10 +62,8 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/query-service/postprocess"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/ctxtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/dashboardtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/featuretypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/instrumentationtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/licensetypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/opamptypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/pipelinetypes"
|
||||
@@ -2414,12 +2412,7 @@ func (aH *APIHandler) onboardKafka(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
ctx := ctxtypes.NewContextWithCommentVals(r.Context(), map[string]string{
|
||||
instrumentationtypes.CodeNamespace: "app",
|
||||
instrumentationtypes.CodeFunctionName: "onboardKafka",
|
||||
})
|
||||
|
||||
results, errQueriesByName, err := aH.querierV2.QueryRange(ctx, orgID, queryRangeParams)
|
||||
results, errQueriesByName, err := aH.querierV2.QueryRange(r.Context(), orgID, queryRangeParams)
|
||||
if err != nil {
|
||||
apiErrObj := &model.ApiError{Typ: model.ErrorBadData, Err: err}
|
||||
RespondError(w, apiErrObj, errQueriesByName)
|
||||
@@ -2531,12 +2524,7 @@ func (aH *APIHandler) getNetworkData(w http.ResponseWriter, r *http.Request) {
|
||||
var result []*v3.Result
|
||||
var errQueriesByName map[string]error
|
||||
|
||||
ctx := ctxtypes.NewContextWithCommentVals(r.Context(), map[string]string{
|
||||
instrumentationtypes.CodeNamespace: "app",
|
||||
instrumentationtypes.CodeFunctionName: "getNetworkData",
|
||||
})
|
||||
|
||||
result, errQueriesByName, err = aH.querierV2.QueryRange(ctx, orgID, queryRangeParams)
|
||||
result, errQueriesByName, err = aH.querierV2.QueryRange(r.Context(), orgID, queryRangeParams)
|
||||
if err != nil {
|
||||
apiErrObj := &model.ApiError{Typ: model.ErrorBadData, Err: err}
|
||||
RespondError(w, apiErrObj, errQueriesByName)
|
||||
@@ -2572,7 +2560,7 @@ func (aH *APIHandler) getNetworkData(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
resultFetchLatency, errQueriesByNameFetchLatency, err := aH.querierV2.QueryRange(ctx, orgID, queryRangeParams)
|
||||
resultFetchLatency, errQueriesByNameFetchLatency, err := aH.querierV2.QueryRange(r.Context(), orgID, queryRangeParams)
|
||||
if err != nil {
|
||||
apiErrObj := &model.ApiError{Typ: model.ErrorBadData, Err: err}
|
||||
RespondError(w, apiErrObj, errQueriesByNameFetchLatency)
|
||||
@@ -2628,11 +2616,7 @@ func (aH *APIHandler) getProducerData(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
evalCtx := featuretypes.NewFlaggerEvaluationContext(orgID)
|
||||
ctx := ctxtypes.NewContextWithCommentVals(r.Context(), map[string]string{
|
||||
instrumentationtypes.CodeNamespace: "app",
|
||||
instrumentationtypes.CodeFunctionName: "getProducerData",
|
||||
})
|
||||
kafkaSpanEval := aH.Signoz.Flagger.BooleanOrEmpty(ctx, flagger.FeatureKafkaSpanEval, evalCtx)
|
||||
kafkaSpanEval := aH.Signoz.Flagger.BooleanOrEmpty(r.Context(), flagger.FeatureKafkaSpanEval, evalCtx)
|
||||
|
||||
queryRangeParams, err := kafka.BuildQueryRangeParams(messagingQueue, "producer", kafkaSpanEval)
|
||||
if err != nil {
|
||||
@@ -2650,7 +2634,7 @@ func (aH *APIHandler) getProducerData(w http.ResponseWriter, r *http.Request) {
|
||||
var result []*v3.Result
|
||||
var errQueriesByName map[string]error
|
||||
|
||||
result, errQueriesByName, err = aH.querierV2.QueryRange(ctx, orgID, queryRangeParams)
|
||||
result, errQueriesByName, err = aH.querierV2.QueryRange(r.Context(), orgID, queryRangeParams)
|
||||
if err != nil {
|
||||
apiErrObj := &model.ApiError{Typ: model.ErrorBadData, Err: err}
|
||||
RespondError(w, apiErrObj, errQueriesByName)
|
||||
@@ -2703,11 +2687,7 @@ func (aH *APIHandler) getConsumerData(w http.ResponseWriter, r *http.Request) {
|
||||
var result []*v3.Result
|
||||
var errQueriesByName map[string]error
|
||||
|
||||
ctx := ctxtypes.NewContextWithCommentVals(r.Context(), map[string]string{
|
||||
instrumentationtypes.CodeNamespace: "app",
|
||||
instrumentationtypes.CodeFunctionName: "getConsumerData",
|
||||
})
|
||||
result, errQueriesByName, err = aH.querierV2.QueryRange(ctx, orgID, queryRangeParams)
|
||||
result, errQueriesByName, err = aH.querierV2.QueryRange(r.Context(), orgID, queryRangeParams)
|
||||
if err != nil {
|
||||
apiErrObj := &model.ApiError{Typ: model.ErrorBadData, Err: err}
|
||||
RespondError(w, apiErrObj, errQueriesByName)
|
||||
@@ -2761,11 +2741,7 @@ func (aH *APIHandler) getPartitionOverviewLatencyData(w http.ResponseWriter, r *
|
||||
var result []*v3.Result
|
||||
var errQueriesByName map[string]error
|
||||
|
||||
ctx := ctxtypes.NewContextWithCommentVals(r.Context(), map[string]string{
|
||||
instrumentationtypes.CodeNamespace: "app",
|
||||
instrumentationtypes.CodeFunctionName: "getPartitionOverviewLatencyData",
|
||||
})
|
||||
result, errQueriesByName, err = aH.querierV2.QueryRange(ctx, orgID, queryRangeParams)
|
||||
result, errQueriesByName, err = aH.querierV2.QueryRange(r.Context(), orgID, queryRangeParams)
|
||||
if err != nil {
|
||||
apiErrObj := &model.ApiError{Typ: model.ErrorBadData, Err: err}
|
||||
RespondError(w, apiErrObj, errQueriesByName)
|
||||
@@ -2819,11 +2795,7 @@ func (aH *APIHandler) getConsumerPartitionLatencyData(w http.ResponseWriter, r *
|
||||
var result []*v3.Result
|
||||
var errQueriesByName map[string]error
|
||||
|
||||
ctx := ctxtypes.NewContextWithCommentVals(r.Context(), map[string]string{
|
||||
instrumentationtypes.CodeNamespace: "app",
|
||||
instrumentationtypes.CodeFunctionName: "getConsumerPartitionLatencyData",
|
||||
})
|
||||
result, errQueriesByName, err = aH.querierV2.QueryRange(ctx, orgID, queryRangeParams)
|
||||
result, errQueriesByName, err = aH.querierV2.QueryRange(r.Context(), orgID, queryRangeParams)
|
||||
if err != nil {
|
||||
apiErrObj := &model.ApiError{Typ: model.ErrorBadData, Err: err}
|
||||
RespondError(w, apiErrObj, errQueriesByName)
|
||||
@@ -2880,11 +2852,7 @@ func (aH *APIHandler) getProducerThroughputOverview(w http.ResponseWriter, r *ht
|
||||
var result []*v3.Result
|
||||
var errQueriesByName map[string]error
|
||||
|
||||
ctx := ctxtypes.NewContextWithCommentVals(r.Context(), map[string]string{
|
||||
instrumentationtypes.CodeNamespace: "app",
|
||||
instrumentationtypes.CodeFunctionName: "getProducerThroughputOverview",
|
||||
})
|
||||
result, errQueriesByName, err = aH.querierV2.QueryRange(ctx, orgID, producerQueryRangeParams)
|
||||
result, errQueriesByName, err = aH.querierV2.QueryRange(r.Context(), orgID, producerQueryRangeParams)
|
||||
if err != nil {
|
||||
apiErrObj := &model.ApiError{Typ: model.ErrorBadData, Err: err}
|
||||
RespondError(w, apiErrObj, errQueriesByName)
|
||||
@@ -2918,7 +2886,7 @@ func (aH *APIHandler) getProducerThroughputOverview(w http.ResponseWriter, r *ht
|
||||
return
|
||||
}
|
||||
|
||||
resultFetchLatency, errQueriesByNameFetchLatency, err := aH.querierV2.QueryRange(ctx, orgID, queryRangeParams)
|
||||
resultFetchLatency, errQueriesByNameFetchLatency, err := aH.querierV2.QueryRange(r.Context(), orgID, queryRangeParams)
|
||||
if err != nil {
|
||||
apiErrObj := &model.ApiError{Typ: model.ErrorBadData, Err: err}
|
||||
RespondError(w, apiErrObj, errQueriesByNameFetchLatency)
|
||||
@@ -2995,11 +2963,7 @@ func (aH *APIHandler) getProducerThroughputDetails(w http.ResponseWriter, r *htt
|
||||
var result []*v3.Result
|
||||
var errQueriesByName map[string]error
|
||||
|
||||
ctx := ctxtypes.NewContextWithCommentVals(r.Context(), map[string]string{
|
||||
instrumentationtypes.CodeNamespace: "app",
|
||||
instrumentationtypes.CodeFunctionName: "getProducerThroughputDetails",
|
||||
})
|
||||
result, errQueriesByName, err = aH.querierV2.QueryRange(ctx, orgID, queryRangeParams)
|
||||
result, errQueriesByName, err = aH.querierV2.QueryRange(r.Context(), orgID, queryRangeParams)
|
||||
if err != nil {
|
||||
apiErrObj := &model.ApiError{Typ: model.ErrorBadData, Err: err}
|
||||
RespondError(w, apiErrObj, errQueriesByName)
|
||||
@@ -3053,11 +3017,7 @@ func (aH *APIHandler) getConsumerThroughputOverview(w http.ResponseWriter, r *ht
|
||||
var result []*v3.Result
|
||||
var errQueriesByName map[string]error
|
||||
|
||||
ctx := ctxtypes.NewContextWithCommentVals(r.Context(), map[string]string{
|
||||
instrumentationtypes.CodeNamespace: "app",
|
||||
instrumentationtypes.CodeFunctionName: "getConsumerThroughputOverview",
|
||||
})
|
||||
result, errQueriesByName, err = aH.querierV2.QueryRange(ctx, orgID, queryRangeParams)
|
||||
result, errQueriesByName, err = aH.querierV2.QueryRange(r.Context(), orgID, queryRangeParams)
|
||||
if err != nil {
|
||||
apiErrObj := &model.ApiError{Typ: model.ErrorBadData, Err: err}
|
||||
RespondError(w, apiErrObj, errQueriesByName)
|
||||
@@ -3111,11 +3071,7 @@ func (aH *APIHandler) getConsumerThroughputDetails(w http.ResponseWriter, r *htt
|
||||
var result []*v3.Result
|
||||
var errQueriesByName map[string]error
|
||||
|
||||
ctx := ctxtypes.NewContextWithCommentVals(r.Context(), map[string]string{
|
||||
instrumentationtypes.CodeNamespace: "app",
|
||||
instrumentationtypes.CodeFunctionName: "getConsumerThroughputDetails",
|
||||
})
|
||||
result, errQueriesByName, err = aH.querierV2.QueryRange(ctx, orgID, queryRangeParams)
|
||||
result, errQueriesByName, err = aH.querierV2.QueryRange(r.Context(), orgID, queryRangeParams)
|
||||
if err != nil {
|
||||
apiErrObj := &model.ApiError{Typ: model.ErrorBadData, Err: err}
|
||||
RespondError(w, apiErrObj, errQueriesByName)
|
||||
@@ -3175,11 +3131,7 @@ func (aH *APIHandler) getProducerConsumerEval(w http.ResponseWriter, r *http.Req
|
||||
var result []*v3.Result
|
||||
var errQueriesByName map[string]error
|
||||
|
||||
ctx := ctxtypes.NewContextWithCommentVals(r.Context(), map[string]string{
|
||||
instrumentationtypes.CodeNamespace: "app",
|
||||
instrumentationtypes.CodeFunctionName: "getProducerConsumerEval",
|
||||
})
|
||||
result, errQueriesByName, err = aH.querierV2.QueryRange(ctx, orgID, queryRangeParams)
|
||||
result, errQueriesByName, err = aH.querierV2.QueryRange(r.Context(), orgID, queryRangeParams)
|
||||
if err != nil {
|
||||
apiErrObj := &model.ApiError{Typ: model.ErrorBadData, Err: err}
|
||||
RespondError(w, apiErrObj, errQueriesByName)
|
||||
@@ -3440,10 +3392,6 @@ func (aH *APIHandler) calculateLogsConnectionStatus(ctx context.Context, orgID v
|
||||
},
|
||||
},
|
||||
}
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.CodeNamespace: "app",
|
||||
instrumentationtypes.CodeFunctionName: "calculateLogsConnectionStatus",
|
||||
})
|
||||
queryRes, _, err := aH.querier.QueryRange(ctx, orgID, qrParams)
|
||||
if err != nil {
|
||||
return nil, model.InternalError(fmt.Errorf(
|
||||
@@ -3996,10 +3944,6 @@ func (aH *APIHandler) calculateAWSIntegrationSvcLogsConnectionStatus(
|
||||
},
|
||||
},
|
||||
}
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.CodeNamespace: "app",
|
||||
instrumentationtypes.CodeFunctionName: "calculateLogsConnectionStatus",
|
||||
})
|
||||
queryRes, _, err := aH.querier.QueryRange(
|
||||
ctx, orgID, qrParams,
|
||||
)
|
||||
@@ -4548,10 +4492,6 @@ func (aH *APIHandler) queryRangeV3(ctx context.Context, queryRangeParams *v3.Que
|
||||
}
|
||||
}
|
||||
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.CodeNamespace: "app",
|
||||
instrumentationtypes.CodeFunctionName: "QueryRange",
|
||||
})
|
||||
result, errQueriesByName, err = aH.querier.QueryRange(ctx, orgID, queryRangeParams)
|
||||
|
||||
if err != nil {
|
||||
@@ -4946,11 +4886,7 @@ func (aH *APIHandler) QueryRangeV4(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
ctx := ctxtypes.NewContextWithCommentVals(r.Context(), map[string]string{
|
||||
instrumentationtypes.CodeNamespace: "app",
|
||||
instrumentationtypes.CodeFunctionName: "QueryRangeV4",
|
||||
})
|
||||
aH.queryRangeV4(ctx, queryRangeParams, w, r)
|
||||
aH.queryRangeV4(r.Context(), queryRangeParams, w, r)
|
||||
}
|
||||
|
||||
func (aH *APIHandler) traceFields(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -5043,12 +4979,8 @@ func (aH *APIHandler) getDomainList(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
ctx := ctxtypes.NewContextWithCommentVals(r.Context(), map[string]string{
|
||||
instrumentationtypes.CodeNamespace: "app",
|
||||
instrumentationtypes.CodeFunctionName: "getDomainList",
|
||||
})
|
||||
// Execute the query using the v5 querier
|
||||
result, err := aH.Signoz.Querier.QueryRange(ctx, orgID, queryRangeRequest)
|
||||
result, err := aH.Signoz.Querier.QueryRange(r.Context(), orgID, queryRangeRequest)
|
||||
if err != nil {
|
||||
zap.L().Error("Query execution failed", zap.Error(err))
|
||||
apiErrObj := errorsV2.New(errorsV2.TypeInvalidInput, errorsV2.CodeInvalidInput, err.Error())
|
||||
@@ -5103,12 +5035,8 @@ func (aH *APIHandler) getDomainInfo(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
ctx := ctxtypes.NewContextWithCommentVals(r.Context(), map[string]string{
|
||||
instrumentationtypes.CodeNamespace: "app",
|
||||
instrumentationtypes.CodeFunctionName: "getDomainInfo",
|
||||
})
|
||||
// Execute the query using the v5 querier
|
||||
result, err := aH.Signoz.Querier.QueryRange(ctx, orgID, queryRangeRequest)
|
||||
result, err := aH.Signoz.Querier.QueryRange(r.Context(), orgID, queryRangeRequest)
|
||||
if err != nil {
|
||||
zap.L().Error("Query execution failed", zap.Error(err))
|
||||
apiErrObj := errorsV2.New(errorsV2.TypeInvalidInput, errorsV2.CodeInvalidInput, err.Error())
|
||||
|
||||
@@ -11,8 +11,6 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/query-service/model"
|
||||
v3 "github.com/SigNoz/signoz/pkg/query-service/model/v3"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/postprocess"
|
||||
"github.com/SigNoz/signoz/pkg/types/ctxtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/instrumentationtypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
@@ -165,10 +163,6 @@ func (p *ClustersRepo) getTopClusterGroups(ctx context.Context, orgID valuer.UUI
|
||||
topClusterGroupsQueryRangeParams.CompositeQuery.BuilderQueries[queryName] = query
|
||||
}
|
||||
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.CodeNamespace: "inframetrics",
|
||||
instrumentationtypes.CodeFunctionName: "getTopClusterGroups",
|
||||
})
|
||||
queryResponse, _, err := p.querierV2.QueryRange(ctx, orgID, topClusterGroupsQueryRangeParams)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
@@ -284,10 +278,6 @@ func (p *ClustersRepo) GetClusterList(ctx context.Context, orgID valuer.UUID, re
|
||||
}
|
||||
}
|
||||
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.CodeNamespace: "inframetrics",
|
||||
instrumentationtypes.CodeFunctionName: "GetClusterList",
|
||||
})
|
||||
queryResponse, _, err := p.querierV2.QueryRange(ctx, orgID, query)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
|
||||
@@ -11,8 +11,6 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/query-service/model"
|
||||
v3 "github.com/SigNoz/signoz/pkg/query-service/model/v3"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/postprocess"
|
||||
"github.com/SigNoz/signoz/pkg/types/ctxtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/instrumentationtypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
@@ -232,10 +230,6 @@ func (d *DaemonSetsRepo) getTopDaemonSetGroups(ctx context.Context, orgID valuer
|
||||
topDaemonSetGroupsQueryRangeParams.CompositeQuery.BuilderQueries[queryName] = query
|
||||
}
|
||||
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.CodeNamespace: "inframetrics",
|
||||
instrumentationtypes.CodeFunctionName: "getTopDaemonSetGroups",
|
||||
})
|
||||
queryResponse, _, err := d.querierV2.QueryRange(ctx, orgID, topDaemonSetGroupsQueryRangeParams)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
@@ -361,10 +355,6 @@ func (d *DaemonSetsRepo) GetDaemonSetList(ctx context.Context, orgID valuer.UUID
|
||||
}
|
||||
}
|
||||
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.CodeNamespace: "inframetrics",
|
||||
instrumentationtypes.CodeFunctionName: "GetDaemonSetList",
|
||||
})
|
||||
queryResponse, _, err := d.querierV2.QueryRange(ctx, orgID, query)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
|
||||
@@ -11,8 +11,6 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/query-service/model"
|
||||
v3 "github.com/SigNoz/signoz/pkg/query-service/model/v3"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/postprocess"
|
||||
"github.com/SigNoz/signoz/pkg/types/ctxtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/instrumentationtypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
@@ -232,10 +230,6 @@ func (d *DeploymentsRepo) getTopDeploymentGroups(ctx context.Context, orgID valu
|
||||
topDeploymentGroupsQueryRangeParams.CompositeQuery.BuilderQueries[queryName] = query
|
||||
}
|
||||
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.CodeNamespace: "inframetrics",
|
||||
instrumentationtypes.CodeFunctionName: "getTopDeploymentGroups",
|
||||
})
|
||||
queryResponse, _, err := d.querierV2.QueryRange(ctx, orgID, topDeploymentGroupsQueryRangeParams)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
|
||||
@@ -16,8 +16,6 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/query-service/model"
|
||||
v3 "github.com/SigNoz/signoz/pkg/query-service/model/v3"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/postprocess"
|
||||
"github.com/SigNoz/signoz/pkg/types/ctxtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/instrumentationtypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"go.uber.org/zap"
|
||||
"golang.org/x/exp/maps"
|
||||
@@ -274,10 +272,6 @@ func (h *HostsRepo) getTopHostGroups(ctx context.Context, orgID valuer.UUID, req
|
||||
topHostGroupsQueryRangeParams.CompositeQuery.BuilderQueries[queryName] = query
|
||||
}
|
||||
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.CodeNamespace: "inframetrics",
|
||||
instrumentationtypes.CodeFunctionName: "getTopHostGroups",
|
||||
})
|
||||
queryResponse, _, err := h.querierV2.QueryRange(ctx, orgID, topHostGroupsQueryRangeParams)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
@@ -488,10 +482,6 @@ func (h *HostsRepo) GetHostList(ctx context.Context, orgID valuer.UUID, req mode
|
||||
}
|
||||
}
|
||||
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.CodeNamespace: "inframetrics",
|
||||
instrumentationtypes.CodeFunctionName: "GetHostList",
|
||||
})
|
||||
queryResponse, _, err := h.querierV2.QueryRange(ctx, orgID, query)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
|
||||
@@ -11,8 +11,6 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/query-service/model"
|
||||
v3 "github.com/SigNoz/signoz/pkg/query-service/model/v3"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/postprocess"
|
||||
"github.com/SigNoz/signoz/pkg/types/ctxtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/instrumentationtypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
@@ -276,10 +274,6 @@ func (d *JobsRepo) getTopJobGroups(ctx context.Context, orgID valuer.UUID, req m
|
||||
topJobGroupsQueryRangeParams.CompositeQuery.BuilderQueries[queryName] = query
|
||||
}
|
||||
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.CodeNamespace: "inframetrics",
|
||||
instrumentationtypes.CodeFunctionName: "getTopJobGroups",
|
||||
})
|
||||
queryResponse, _, err := d.querierV2.QueryRange(ctx, orgID, topJobGroupsQueryRangeParams)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
@@ -405,10 +399,6 @@ func (d *JobsRepo) GetJobList(ctx context.Context, orgID valuer.UUID, req model.
|
||||
}
|
||||
}
|
||||
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.CodeNamespace: "inframetrics",
|
||||
instrumentationtypes.CodeFunctionName: "GetJobList",
|
||||
})
|
||||
queryResponse, _, err := d.querierV2.QueryRange(ctx, orgID, query)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
|
||||
@@ -11,8 +11,6 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/query-service/model"
|
||||
v3 "github.com/SigNoz/signoz/pkg/query-service/model/v3"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/postprocess"
|
||||
"github.com/SigNoz/signoz/pkg/types/ctxtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/instrumentationtypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
@@ -159,10 +157,6 @@ func (p *NamespacesRepo) getTopNamespaceGroups(ctx context.Context, orgID valuer
|
||||
topNamespaceGroupsQueryRangeParams.CompositeQuery.BuilderQueries[queryName] = query
|
||||
}
|
||||
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.CodeNamespace: "inframetrics",
|
||||
instrumentationtypes.CodeFunctionName: "getTopNamespaceGroups",
|
||||
})
|
||||
queryResponse, _, err := p.querierV2.QueryRange(ctx, orgID, topNamespaceGroupsQueryRangeParams)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
@@ -283,10 +277,6 @@ func (p *NamespacesRepo) GetNamespaceList(ctx context.Context, orgID valuer.UUID
|
||||
}
|
||||
}
|
||||
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.CodeNamespace: "inframetrics",
|
||||
instrumentationtypes.CodeFunctionName: "GetNamespaceList",
|
||||
})
|
||||
queryResponse, _, err := p.querierV2.QueryRange(ctx, orgID, query)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
|
||||
@@ -14,8 +14,6 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/query-service/model"
|
||||
v3 "github.com/SigNoz/signoz/pkg/query-service/model/v3"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/postprocess"
|
||||
"github.com/SigNoz/signoz/pkg/types/ctxtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/instrumentationtypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
@@ -189,10 +187,6 @@ func (p *NodesRepo) getTopNodeGroups(ctx context.Context, orgID valuer.UUID, req
|
||||
topNodeGroupsQueryRangeParams.CompositeQuery.BuilderQueries[queryName] = query
|
||||
}
|
||||
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.CodeNamespace: "inframetrics",
|
||||
instrumentationtypes.CodeFunctionName: "getTopNodeGroups",
|
||||
})
|
||||
queryResponse, _, err := p.querierV2.QueryRange(ctx, orgID, topNodeGroupsQueryRangeParams)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
@@ -308,10 +302,6 @@ func (p *NodesRepo) GetNodeList(ctx context.Context, orgID valuer.UUID, req mode
|
||||
}
|
||||
}
|
||||
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.CodeNamespace: "inframetrics",
|
||||
instrumentationtypes.CodeFunctionName: "GetNodeList",
|
||||
})
|
||||
queryResponse, _, err := p.querierV2.QueryRange(ctx, orgID, query)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
|
||||
@@ -14,8 +14,6 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/query-service/model"
|
||||
v3 "github.com/SigNoz/signoz/pkg/query-service/model/v3"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/postprocess"
|
||||
"github.com/SigNoz/signoz/pkg/types/ctxtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/instrumentationtypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
@@ -334,10 +332,6 @@ func (p *PodsRepo) getTopPodGroups(ctx context.Context, orgID valuer.UUID, req m
|
||||
topPodGroupsQueryRangeParams.CompositeQuery.BuilderQueries[queryName] = query
|
||||
}
|
||||
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.CodeNamespace: "inframetrics",
|
||||
instrumentationtypes.CodeFunctionName: "getTopPodGroups",
|
||||
})
|
||||
queryResponse, _, err := p.querierV2.QueryRange(ctx, orgID, topPodGroupsQueryRangeParams)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
@@ -453,10 +447,6 @@ func (p *PodsRepo) GetPodList(ctx context.Context, orgID valuer.UUID, req model.
|
||||
}
|
||||
}
|
||||
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.CodeNamespace: "inframetrics",
|
||||
instrumentationtypes.CodeFunctionName: "GetPodList",
|
||||
})
|
||||
queryResponse, _, err := p.querierV2.QueryRange(ctx, orgID, query)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
|
||||
@@ -11,8 +11,6 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/query-service/model"
|
||||
v3 "github.com/SigNoz/signoz/pkg/query-service/model/v3"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/postprocess"
|
||||
"github.com/SigNoz/signoz/pkg/types/ctxtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/instrumentationtypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
@@ -173,10 +171,6 @@ func (p *ProcessesRepo) getTopProcessGroups(ctx context.Context, orgID valuer.UU
|
||||
topProcessGroupsQueryRangeParams.CompositeQuery.BuilderQueries[queryName] = query
|
||||
}
|
||||
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.CodeNamespace: "inframetrics",
|
||||
instrumentationtypes.CodeFunctionName: "getTopProcessGroups",
|
||||
})
|
||||
queryResponse, _, err := p.querierV2.QueryRange(ctx, orgID, topProcessGroupsQueryRangeParams)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
@@ -290,10 +284,6 @@ func (p *ProcessesRepo) GetProcessList(ctx context.Context, orgID valuer.UUID, r
|
||||
}
|
||||
}
|
||||
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.CodeNamespace: "inframetrics",
|
||||
instrumentationtypes.CodeFunctionName: "GetProcessList",
|
||||
})
|
||||
queryResponse, _, err := p.querierV2.QueryRange(ctx, orgID, query)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
|
||||
@@ -11,8 +11,6 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/query-service/model"
|
||||
v3 "github.com/SigNoz/signoz/pkg/query-service/model/v3"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/postprocess"
|
||||
"github.com/SigNoz/signoz/pkg/types/ctxtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/instrumentationtypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
@@ -192,10 +190,6 @@ func (p *PvcsRepo) getTopVolumeGroups(ctx context.Context, orgID valuer.UUID, re
|
||||
topVolumeGroupsQueryRangeParams.CompositeQuery.BuilderQueries[queryName] = query
|
||||
}
|
||||
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.CodeNamespace: "inframetrics",
|
||||
instrumentationtypes.CodeFunctionName: "getTopVolumeGroups",
|
||||
})
|
||||
queryResponse, _, err := p.querierV2.QueryRange(ctx, orgID, topVolumeGroupsQueryRangeParams)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
@@ -311,10 +305,6 @@ func (p *PvcsRepo) GetPvcList(ctx context.Context, orgID valuer.UUID, req model.
|
||||
}
|
||||
}
|
||||
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.CodeNamespace: "inframetrics",
|
||||
instrumentationtypes.CodeFunctionName: "GetPvcList",
|
||||
})
|
||||
queryResponse, _, err := p.querierV2.QueryRange(ctx, orgID, query)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
|
||||
@@ -11,8 +11,6 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/query-service/model"
|
||||
v3 "github.com/SigNoz/signoz/pkg/query-service/model/v3"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/postprocess"
|
||||
"github.com/SigNoz/signoz/pkg/types/ctxtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/instrumentationtypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
@@ -232,10 +230,6 @@ func (d *StatefulSetsRepo) getTopStatefulSetGroups(ctx context.Context, orgID va
|
||||
topStatefulSetGroupsQueryRangeParams.CompositeQuery.BuilderQueries[queryName] = query
|
||||
}
|
||||
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.CodeNamespace: "inframetrics",
|
||||
instrumentationtypes.CodeFunctionName: "getTopStatefulSetGroups",
|
||||
})
|
||||
queryResponse, _, err := d.querierV2.QueryRange(ctx, orgID, topStatefulSetGroupsQueryRangeParams)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
@@ -361,10 +355,6 @@ func (d *StatefulSetsRepo) GetStatefulSetList(ctx context.Context, orgID valuer.
|
||||
}
|
||||
}
|
||||
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.CodeNamespace: "inframetrics",
|
||||
instrumentationtypes.CodeFunctionName: "GetStatefulSetList",
|
||||
})
|
||||
queryResponse, _, err := d.querierV2.QueryRange(ctx, orgID, query)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
|
||||
@@ -1,6 +1,21 @@
|
||||
package opamp
|
||||
|
||||
import "github.com/SigNoz/signoz/pkg/query-service/app/opamp/model"
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/query-service/app/opamp/model"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
|
||||
// PendingDeployment is an agent config deployment still in the in_progress state.
|
||||
// These are re-registered in the coordinator on server startup so that
|
||||
// notifySubscribers can find them when the agent reconnects after a crash.
|
||||
type PendingDeployment struct {
|
||||
OrgID valuer.UUID
|
||||
// RawConfigHash is the hash without the orgId prefix, matching the
|
||||
// ConfigHash sent in AgentRemoteConfig and reported back by the agent.
|
||||
RawConfigHash string
|
||||
}
|
||||
|
||||
// Interface for a source of otel collector config recommendations.
|
||||
type AgentConfigProvider interface {
|
||||
@@ -9,4 +24,9 @@ type AgentConfigProvider interface {
|
||||
// Subscribe to be notified on changes in config provided by this source.
|
||||
// Used for rolling out latest config recommendation to all connected agents when settings change
|
||||
SubscribeToConfigUpdates(callback func()) (unsubscribe func())
|
||||
|
||||
// GetPendingDeployments returns all config deployments currently in_progress.
|
||||
// Called on server startup to re-register coordinator subscribers that were
|
||||
// lost when the server previously crashed or restarted.
|
||||
GetPendingDeployments(ctx context.Context) ([]PendingDeployment, error)
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"log"
|
||||
"net"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/types/opamptypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/google/uuid"
|
||||
"github.com/knadh/koanf"
|
||||
@@ -127,6 +128,16 @@ func (ta *MockAgentConfigProvider) HasReportedDeploymentStatus(orgID valuer.UUID
|
||||
return exists
|
||||
}
|
||||
|
||||
// AgentConfigProvider interface
|
||||
func (ta *MockAgentConfigProvider) GetDeployStatusByHash(_ context.Context, _ valuer.UUID, _ string) (opamptypes.DeployStatus, error) {
|
||||
return opamptypes.DeployStatusUnknown, nil
|
||||
}
|
||||
|
||||
// AgentConfigProvider interface
|
||||
func (ta *MockAgentConfigProvider) GetPendingDeployments(_ context.Context) ([]PendingDeployment, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// AgentConfigProvider interface
|
||||
func (ta *MockAgentConfigProvider) SubscribeToConfigUpdates(callback func()) func() {
|
||||
subscriberId := uuid.NewString()
|
||||
|
||||
@@ -112,53 +112,71 @@ func ExtractLbFlag(agentDescr *protobufs.AgentDescription) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (agent *Agent) updateAgentDescription(newStatus *protobufs.AgentToServer) (agentDescrChanged bool) {
|
||||
prevStatus := agent.Status
|
||||
|
||||
func (agent *Agent) updateAgentDescription(newStatus *protobufs.AgentToServer, configProvider AgentConfigProvider) (agentDescrChanged bool) {
|
||||
if agent.Status == nil {
|
||||
// First time this Agent reports a status, remember it.
|
||||
agent.Status = newStatus
|
||||
agentDescrChanged = true
|
||||
} else {
|
||||
// Not a new Agent. Update the Status.
|
||||
agent.Status.SequenceNum = newStatus.SequenceNum
|
||||
|
||||
// Check what's changed in the AgentDescription.
|
||||
if newStatus.AgentDescription != nil {
|
||||
// If the AgentDescription field is set it means the Agent tells us
|
||||
// something is changed in the field since the last status report
|
||||
// (or this is the first report).
|
||||
// Make full comparison of previous and new descriptions to see if it
|
||||
// really is different.
|
||||
if prevStatus != nil && proto.Equal(prevStatus.AgentDescription, newStatus.AgentDescription) {
|
||||
// Agent description didn't change.
|
||||
agentDescrChanged = false
|
||||
} else {
|
||||
// Yes, the description is different, update it.
|
||||
agent.Status.AgentDescription = newStatus.AgentDescription
|
||||
agentDescrChanged = true
|
||||
}
|
||||
} else {
|
||||
// AgentDescription field is not set, which means description didn't change.
|
||||
agentDescrChanged = false
|
||||
// initialize the remote config status to unset
|
||||
agent.Status = &protobufs.AgentToServer{
|
||||
RemoteConfigStatus: &protobufs.RemoteConfigStatus{
|
||||
Status: protobufs.RemoteConfigStatuses_RemoteConfigStatuses_UNSET,
|
||||
},
|
||||
}
|
||||
|
||||
// Update remote config status if it is included and is different from what we have.
|
||||
if newStatus.RemoteConfigStatus != nil &&
|
||||
!proto.Equal(agent.Status.RemoteConfigStatus, newStatus.RemoteConfigStatus) {
|
||||
agent.Status.RemoteConfigStatus = newStatus.RemoteConfigStatus
|
||||
rawHash := string(newStatus.RemoteConfigStatus.LastRemoteConfigHash)
|
||||
dbHash := agent.OrgID.String() + rawHash
|
||||
deployStatus, err := configProvider.GetDeployStatusByHash(context.Background(), agent.OrgID, dbHash)
|
||||
if err == nil {
|
||||
// Set the agent config status to the status from the database
|
||||
agent.Status.RemoteConfigStatus.Status = opamptypes.DeployStatusToProtoStatus[deployStatus]
|
||||
}
|
||||
|
||||
// todo: need to address multiple agent scenario here
|
||||
// for now, the first response will be sent back to the UI
|
||||
if agent.Status.RemoteConfigStatus.Status == protobufs.RemoteConfigStatuses_RemoteConfigStatuses_APPLIED {
|
||||
onConfigSuccess(agent.OrgID, agent.AgentID, string(agent.Status.RemoteConfigStatus.LastRemoteConfigHash))
|
||||
}
|
||||
// First message from this agent instance (new connect or server restart).
|
||||
// If the agent brings a RemoteConfigStatus, consult the DB to decide
|
||||
// whether this resolves a pending deployment. This is the authoritative
|
||||
// answer: if DB says in_progress and agent says APPLIED/FAILED, we notify.
|
||||
// No mock status, no guessing — the DB IS the source of truth.
|
||||
if newStatus.RemoteConfigStatus != nil {
|
||||
// Agent just started, i.e. it doesn't have a remote config status yet.
|
||||
if newStatus.RemoteConfigStatus.Status == protobufs.RemoteConfigStatuses_RemoteConfigStatuses_UNSET {
|
||||
agentDescrChanged = true
|
||||
agent.Status.RemoteConfigStatus.Status = protobufs.RemoteConfigStatuses_RemoteConfigStatuses_UNSET
|
||||
} else {
|
||||
// else Agent was already running, Server just reconnected.
|
||||
agent.Status.AgentDescription = newStatus.AgentDescription
|
||||
|
||||
if agent.Status.RemoteConfigStatus.Status == protobufs.RemoteConfigStatuses_RemoteConfigStatuses_FAILED {
|
||||
onConfigFailure(agent.OrgID, agent.AgentID, string(agent.Status.RemoteConfigStatus.LastRemoteConfigHash), agent.Status.RemoteConfigStatus.ErrorMessage)
|
||||
// database has already recorded the final status of the deployment, So here we don't need to prepare status for the agent
|
||||
// Instead we directly Copy it from newStatus
|
||||
switch agent.Status.RemoteConfigStatus.Status {
|
||||
case protobufs.RemoteConfigStatuses_RemoteConfigStatuses_APPLIED,
|
||||
protobufs.RemoteConfigStatuses_RemoteConfigStatuses_FAILED:
|
||||
agent.Status.RemoteConfigStatus.Status = newStatus.RemoteConfigStatus.Status
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Subsequent message — update sequence number and diff fields.
|
||||
agent.Status.SequenceNum = newStatus.SequenceNum
|
||||
|
||||
if newStatus.AgentDescription != nil {
|
||||
if proto.Equal(agent.Status.AgentDescription, newStatus.AgentDescription) {
|
||||
agentDescrChanged = false
|
||||
} else {
|
||||
agent.Status.AgentDescription = newStatus.AgentDescription
|
||||
agentDescrChanged = true
|
||||
}
|
||||
}
|
||||
|
||||
// Notify subscribers when RemoteConfigStatus changes.
|
||||
if newStatus.RemoteConfigStatus != nil &&
|
||||
!proto.Equal(agent.Status.RemoteConfigStatus, newStatus.RemoteConfigStatus) {
|
||||
agent.Status.RemoteConfigStatus = newStatus.RemoteConfigStatus
|
||||
hash := string(agent.Status.RemoteConfigStatus.LastRemoteConfigHash)
|
||||
switch agent.Status.RemoteConfigStatus.Status {
|
||||
case protobufs.RemoteConfigStatuses_RemoteConfigStatuses_APPLIED:
|
||||
onConfigSuccess(agent.OrgID, agent.AgentID, hash)
|
||||
case protobufs.RemoteConfigStatuses_RemoteConfigStatuses_FAILED:
|
||||
onConfigFailure(agent.OrgID, agent.AgentID, hash, agent.Status.RemoteConfigStatus.ErrorMessage)
|
||||
}
|
||||
}
|
||||
|
||||
if agentDescrChanged {
|
||||
agent.CanLB = ExtractLbFlag(newStatus.AgentDescription)
|
||||
@@ -186,14 +204,8 @@ func (agent *Agent) updateRemoteConfigStatus(newStatus *protobufs.AgentToServer)
|
||||
}
|
||||
}
|
||||
|
||||
func (agent *Agent) updateStatusField(newStatus *protobufs.AgentToServer) (agentDescrChanged bool) {
|
||||
if agent.Status == nil {
|
||||
// First time this Agent reports a status, remember it.
|
||||
agent.Status = newStatus
|
||||
agentDescrChanged = true
|
||||
}
|
||||
|
||||
agentDescrChanged = agent.updateAgentDescription(newStatus) || agentDescrChanged
|
||||
func (agent *Agent) updateStatusField(newStatus *protobufs.AgentToServer, configProvider AgentConfigProvider) (agentDescrChanged bool) {
|
||||
agentDescrChanged = agent.updateAgentDescription(newStatus, configProvider)
|
||||
agent.updateRemoteConfigStatus(newStatus)
|
||||
agent.updateHealth(newStatus)
|
||||
return agentDescrChanged
|
||||
@@ -238,7 +250,7 @@ func (agent *Agent) processStatusUpdate(
|
||||
// current status is not up-to-date.
|
||||
lostPreviousUpdate := (agent.Status == nil) || (agent.Status != nil && agent.Status.SequenceNum+1 != newStatus.SequenceNum)
|
||||
|
||||
agentDescrChanged := agent.updateStatusField(newStatus)
|
||||
agentDescrChanged := agent.updateStatusField(newStatus, configProvider)
|
||||
|
||||
// Check if any fields were omitted in the status report.
|
||||
effectiveConfigOmitted := newStatus.EffectiveConfig == nil &&
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
package model
|
||||
|
||||
import "github.com/SigNoz/signoz/pkg/valuer"
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/types/opamptypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
|
||||
// Interface for source of otel collector config recommendations.
|
||||
type AgentConfigProvider interface {
|
||||
@@ -20,4 +25,10 @@ type AgentConfigProvider interface {
|
||||
configId string,
|
||||
err error,
|
||||
)
|
||||
|
||||
// GetDeployStatusByHash returns the DeployStatus for the given config hash
|
||||
// (with orgId prefix as stored in the DB). Returns DeployStatusUnknown when
|
||||
// no matching row exists. Used by the agent's first-connect handler to
|
||||
// determine whether the reported RemoteConfigStatus resolves a pending deployment.
|
||||
GetDeployStatusByHash(ctx context.Context, orgId valuer.UUID, configHash string) (opamptypes.DeployStatus, error)
|
||||
}
|
||||
|
||||
@@ -41,7 +41,6 @@ func onConfigFailure(orgId valuer.UUID, agentId string, hash string, errorMessag
|
||||
notifySubscribers(orgId, agentId, key, errors.New(errorMessage))
|
||||
}
|
||||
|
||||
// OnSuccess listens to config changes and notifies subscribers
|
||||
func notifySubscribers(orgId valuer.UUID, agentId string, key string, err error) {
|
||||
// this method currently does not handle multi-agent scenario.
|
||||
// as soon as a message is delivered, we release all the subscribers
|
||||
@@ -66,6 +65,7 @@ func ListenToConfigUpdate(orgId valuer.UUID, agentId string, hash string, ss OnC
|
||||
defer coordinator.mutex.Unlock()
|
||||
|
||||
key := getSubscriberKey(orgId, hash)
|
||||
|
||||
if subs, ok := coordinator.subscribers[key]; ok {
|
||||
subs = append(subs, ss)
|
||||
coordinator.subscribers[key] = subs
|
||||
|
||||
@@ -66,6 +66,17 @@ func (srv *Server) Start(listener string) error {
|
||||
ListenEndpoint: listener,
|
||||
}
|
||||
|
||||
// Re-register coordinator subscribers for any deployments that were in_progress
|
||||
// when the server last shut down or crashed. Without this, notifySubscribers
|
||||
// would find an empty map and the deployment status would stay stuck in_progress.
|
||||
if pending, err := srv.agentConfigProvider.GetPendingDeployments(context.Background()); err != nil {
|
||||
return err
|
||||
} else {
|
||||
for _, dep := range pending {
|
||||
model.ListenToConfigUpdate(dep.OrgID, "", dep.RawConfigHash, srv.agentConfigProvider.ReportConfigDeploymentStatus)
|
||||
}
|
||||
}
|
||||
|
||||
// This will have to send request to all the agents of all tenants
|
||||
unsubscribe := srv.agentConfigProvider.SubscribeToConfigUpdates(func() {
|
||||
err := srv.agents.RecommendLatestConfigToAll(srv.agentConfigProvider)
|
||||
|
||||
@@ -17,8 +17,6 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/query-service/model"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/postprocess"
|
||||
"github.com/SigNoz/signoz/pkg/transition"
|
||||
"github.com/SigNoz/signoz/pkg/types/ctxtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/instrumentationtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/ruletypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
@@ -435,10 +433,7 @@ func (r *ThresholdRule) buildAndRunQuery(ctx context.Context, orgID valuer.UUID,
|
||||
|
||||
var results []*v3.Result
|
||||
var queryErrors map[string]error
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.CodeNamespace: "rules",
|
||||
instrumentationtypes.CodeFunctionName: "buildAndRunQuery",
|
||||
})
|
||||
|
||||
if r.version == "v4" {
|
||||
results, queryErrors, err = r.querierV2.QueryRange(ctx, orgID, params)
|
||||
} else {
|
||||
@@ -506,11 +501,6 @@ func (r *ThresholdRule) buildAndRunQueryV5(ctx context.Context, orgID valuer.UUI
|
||||
|
||||
var results []*v3.Result
|
||||
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.CodeNamespace: "rules",
|
||||
instrumentationtypes.CodeFunctionName: "buildAndRunQueryV5",
|
||||
})
|
||||
|
||||
v5Result, err := r.querierV5.QueryRange(ctx, orgID, params)
|
||||
if err != nil {
|
||||
r.logger.ErrorContext(ctx, "failed to get alert query result", "rule_name", r.Name(), "error", err)
|
||||
|
||||
@@ -176,10 +176,9 @@ func NewSQLMigrationProviderFactories(
|
||||
func NewTelemetryStoreProviderFactories() factory.NamedMap[factory.ProviderFactory[telemetrystore.TelemetryStore, telemetrystore.Config]] {
|
||||
return factory.MustNewNamedMap(
|
||||
clickhousetelemetrystore.NewFactory(
|
||||
telemetrystorehook.NewLoggingFactory(),
|
||||
// adding instrumentation factory before settings as we are starting the query span here
|
||||
telemetrystorehook.NewInstrumentationFactory(),
|
||||
telemetrystorehook.NewSettingsFactory(),
|
||||
telemetrystorehook.NewLoggingFactory(),
|
||||
telemetrystorehook.NewInstrumentationFactory(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user