mirror of
https://github.com/SigNoz/signoz.git
synced 2026-08-07 21:50:39 +01:00
Compare commits
7 Commits
feat/alert
...
feat/semco
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a6594120f5 | ||
|
|
7a2e5eb40a | ||
|
|
5b005706d5 | ||
|
|
a34e4343c7 | ||
|
|
1e530643ff | ||
|
|
08f4e0ea77 | ||
|
|
7e703723a4 |
@@ -1,19 +0,0 @@
|
||||
---
|
||||
paths:
|
||||
- "tests/**/*.py"
|
||||
---
|
||||
|
||||
# pytest conventions
|
||||
|
||||
For the Python integration suite under `tests/`. Setup, running, and suite layout live in [`docs/contributing/tests/integration.md`](../../docs/contributing/tests/integration.md).
|
||||
|
||||
- **No `_`-prefixed helper functions in test modules — this is the rule that matters most.** A reader must be able to see what a test does in its body alone, without chasing private helpers that scatter the meaning across the file. Inline the logic: an expression, a comprehension, a few repeated lines are all fine — repetition across tests is cheaper than indirection. When several tests genuinely share non-trivial setup or assertions, that is what fixtures are for — in `tests/fixtures/`, see the next rule. A module-level `_helper()` is never the answer.
|
||||
- **Fixtures live in `tests/fixtures/` — never under `integration/tests/`.** Not in test modules, not in suite `conftest.py` files. `tests/fixtures/` is the shared library (auth, signoz, clickhouse, logs/metrics/traces seeding, …): reuse what's there before writing anything new; when a new fixture is genuinely needed, add it to the matching `tests/fixtures/` module and register new modules in `tests/conftest.py` `pytest_plugins`. **The one exception: SigNoz-level fixtures in a suite's `conftest.py`.** A suite that needs its own SigNoz spun up with different envs (`create_signoz`/`create_migrator` with `env_overrides` + `cache_key` — e.g. basepath, metricreduction, querier_json_body) keeps that in its `conftest.py`; that is always okay.
|
||||
- **Fixture only when there is a lifecycle; otherwise a plain function.** A fixture earns its indirection by owning setup/teardown (`yield` + cleanup — `insert_metrics` truncating on teardown) or by provisioning a resource (containers, SigNoz instances). A stateless action or lookup (`create_saved_view`, `find_saved_view_by_name`, wiping a resource list) is a plain importable function in the matching `tests/fixtures/` module, taking `signoz`/`token` as ordinary arguments — never wrap a plain callable in a fixture-factory just to inject `signoz`.
|
||||
- **Fixtures own their cleanup.** When a test needs seeded state, put the seed + cleanup pair in a fixture (`yield`, then tear down) so tests in the same suite don't interfere — the pattern `insert_metrics` sets: yield a callable, truncate on teardown.
|
||||
- **Fixture-factory over indirect parametrization.** A fixture that yields a callable (e.g. `insert_metrics(metrics)`) is clearer than `@pytest.mark.parametrize(..., indirect=True)` + `request.param` — the value is an explicit argument, not resolved by magic.
|
||||
- **Skip at collection, not inside the test body.** Use `pytest.param(..., marks=pytest.mark.skip(reason="…"))` so a skipped case shows as SKIPPED-with-reason **and** short-circuits before its fixtures run (no environment spin-up for a test that won't execute).
|
||||
- **Test config comes from explicit `--flags`, not the environment.** Wire configuration as pytest options declared in `tests/conftest.py` (`pytest_addoption` — e.g. `--sqlstore-provider`, `--clickhouse-version`); do **not** add `os.environ` fallbacks inside tests or fixtures.
|
||||
- **snake_case parametrize ids.** `ids=["fill_gaps", "fill_zero"]`, not camelCase.
|
||||
- **Name suite files with the two-digit prefix (`NN_*.py`).** `pyproject.toml` restricts collection to `[0-9][0-9]_*.py` (plus the bootstrap `setup.py` / `run.py`) — a file that doesn't match is silently never collected.
|
||||
- **Always run pytest from `tests/`.** `--import-mode=importlib` is what allows same-basename files across suites (`querier/01_logs.py` vs `rawexportdata/01_logs.py`), but it disables pytest's implicit `sys.path` injection — `import fixtures` only resolves via `pythonpath = ["."]` from that rootdir.
|
||||
4
.github/workflows/goci.yaml
vendored
4
.github/workflows/goci.yaml
vendored
@@ -66,8 +66,8 @@ jobs:
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "1.24"
|
||||
- name: check-semconv-generated-files
|
||||
run: go run ./scripts/semconv -check
|
||||
- name: check-semconv-generated-files-and-product-literals
|
||||
run: make semconv-check
|
||||
build:
|
||||
if: |
|
||||
github.event_name == 'merge_group' ||
|
||||
|
||||
23
Makefile
23
Makefile
@@ -220,6 +220,25 @@ py-test-teardown: ## Tear down the shared SigNoz backend
|
||||
py-test: ## Runs integration tests
|
||||
@cd tests && uv run pytest --basetemp=./tmp/ -vv --capture=no integration/tests/
|
||||
|
||||
.PHONY: py-test-semconv-phase1
|
||||
py-test-semconv-phase1: py-test-setup ## Rebuild the shared stack and run the semantic-convention Phase 1 matrix
|
||||
@cd tests && uv run pytest --basetemp=./tmp/ -vv --reuse --capture=no integration/tests/queriertraces/13_semconv_evolution.py
|
||||
|
||||
.PHONY: py-test-semconv-phase2
|
||||
py-test-semconv-phase2: py-test-setup ## Rebuild the shared stack and run the Phase 1-2 cross-signal matrices
|
||||
@cd tests && uv run pytest --basetemp=./tmp/ -vv --reuse --capture=no integration/tests/queriertraces/13_semconv_evolution.py integration/tests/queriersemconv/02_cross_signal.py
|
||||
|
||||
.PHONY: py-test-semconv-phase3
|
||||
py-test-semconv-phase3: py-test-setup ## Rebuild the shared stack and run the Phase 1-3 compatibility and migration-report matrices
|
||||
@cd tests && uv run pytest --basetemp=./tmp/ -vv --reuse --capture=no integration/tests/queriertraces/13_semconv_evolution.py integration/tests/queriersemconv/02_cross_signal.py
|
||||
|
||||
.PHONY: py-test-semconv-phase4
|
||||
py-test-semconv-phase4: py-test-setup ## Rebuild the shared stack and run all semantic-convention compatibility matrices
|
||||
@cd tests && uv run pytest --basetemp=./tmp/ -vv --reuse --capture=no integration/tests/queriertraces/13_semconv_evolution.py integration/tests/queriersemconv/02_cross_signal.py integration/tests/queriersemconv/04_phase4_families.py
|
||||
|
||||
.PHONY: py-test-semconv
|
||||
py-test-semconv: py-test-semconv-phase4 ## Run the complete semantic-convention evolution closure gate
|
||||
|
||||
.PHONY: py-clean
|
||||
py-clean: ## Clear all pycache and pytest cache from tests directory recursively
|
||||
@echo ">> cleaning python cache files from tests directory"
|
||||
@@ -237,6 +256,10 @@ py-clean: ## Clear all pycache and pytest cache from tests directory recursively
|
||||
semconv-generate: ## Regenerate semantic-convention families for Go and TypeScript
|
||||
@go run ./scripts/semconv
|
||||
|
||||
.PHONY: semconv-check
|
||||
semconv-check: ## Verify generated semantic-convention files and reject old-name product literals
|
||||
@go run ./scripts/semconv -check -lint
|
||||
|
||||
.PHONY: gen-mocks
|
||||
gen-mocks:
|
||||
@echo ">> Generating mocks"
|
||||
|
||||
@@ -6401,6 +6401,8 @@ components:
|
||||
$ref: '#/components/schemas/TelemetrytypesFieldContext'
|
||||
fieldDataType:
|
||||
$ref: '#/components/schemas/TelemetrytypesFieldDataType'
|
||||
fieldResolution:
|
||||
$ref: '#/components/schemas/TelemetrytypesFieldResolution'
|
||||
meta:
|
||||
properties:
|
||||
unit:
|
||||
@@ -6445,6 +6447,10 @@ components:
|
||||
rowsScanned:
|
||||
minimum: 0
|
||||
type: integer
|
||||
semconvResolutions:
|
||||
items:
|
||||
$ref: '#/components/schemas/Querybuildertypesv5SemconvResolution'
|
||||
type: array
|
||||
stepIntervals:
|
||||
additionalProperties:
|
||||
minimum: 0
|
||||
@@ -6511,6 +6517,8 @@ components:
|
||||
$ref: '#/components/schemas/TelemetrytypesFieldContext'
|
||||
fieldDataType:
|
||||
$ref: '#/components/schemas/TelemetrytypesFieldDataType'
|
||||
fieldResolution:
|
||||
$ref: '#/components/schemas/TelemetrytypesFieldResolution'
|
||||
name:
|
||||
type: string
|
||||
signal:
|
||||
@@ -6582,6 +6590,8 @@ components:
|
||||
$ref: '#/components/schemas/TelemetrytypesFieldContext'
|
||||
fieldDataType:
|
||||
$ref: '#/components/schemas/TelemetrytypesFieldDataType'
|
||||
fieldResolution:
|
||||
$ref: '#/components/schemas/TelemetrytypesFieldResolution'
|
||||
name:
|
||||
type: string
|
||||
signal:
|
||||
@@ -7149,6 +7159,20 @@ components:
|
||||
stepInterval:
|
||||
$ref: '#/components/schemas/Querybuildertypesv5Step'
|
||||
type: object
|
||||
Querybuildertypesv5SemconvResolution:
|
||||
properties:
|
||||
current:
|
||||
type: string
|
||||
kind:
|
||||
type: string
|
||||
members:
|
||||
items:
|
||||
type: string
|
||||
nullable: true
|
||||
type: array
|
||||
requested:
|
||||
type: string
|
||||
type: object
|
||||
Querybuildertypesv5Step:
|
||||
description: Step interval. Accepts a Go duration string (e.g., "60s", "1m",
|
||||
"1h") or a number representing seconds (e.g., 60).
|
||||
@@ -8660,6 +8684,11 @@ components:
|
||||
- number
|
||||
- ""
|
||||
type: string
|
||||
TelemetrytypesFieldResolution:
|
||||
enum:
|
||||
- exact
|
||||
- ""
|
||||
type: string
|
||||
TelemetrytypesGettableFieldKeys:
|
||||
properties:
|
||||
complete:
|
||||
@@ -8685,6 +8714,42 @@ components:
|
||||
- values
|
||||
- complete
|
||||
type: object
|
||||
TelemetrytypesGettableSemconvMigrationReport:
|
||||
properties:
|
||||
endUnixMilli:
|
||||
format: int64
|
||||
type: integer
|
||||
entries:
|
||||
items:
|
||||
$ref: '#/components/schemas/TelemetrytypesSemconvMigrationReportEntry'
|
||||
nullable: true
|
||||
type: array
|
||||
startUnixMilli:
|
||||
format: int64
|
||||
type: integer
|
||||
required:
|
||||
- entries
|
||||
type: object
|
||||
TelemetrytypesSemconvMigrationReportEntry:
|
||||
properties:
|
||||
current:
|
||||
type: string
|
||||
lastSeenUnixMilli:
|
||||
format: int64
|
||||
type: integer
|
||||
old:
|
||||
type: string
|
||||
resourceSets:
|
||||
minimum: 0
|
||||
type: integer
|
||||
services:
|
||||
items:
|
||||
type: string
|
||||
nullable: true
|
||||
type: array
|
||||
signal:
|
||||
type: string
|
||||
type: object
|
||||
TelemetrytypesSignal:
|
||||
enum:
|
||||
- traces
|
||||
@@ -8705,6 +8770,8 @@ components:
|
||||
$ref: '#/components/schemas/TelemetrytypesFieldContext'
|
||||
fieldDataType:
|
||||
$ref: '#/components/schemas/TelemetrytypesFieldDataType'
|
||||
fieldResolution:
|
||||
$ref: '#/components/schemas/TelemetrytypesFieldResolution'
|
||||
name:
|
||||
type: string
|
||||
signal:
|
||||
@@ -11201,6 +11268,64 @@ paths:
|
||||
summary: Get field keys
|
||||
tags:
|
||||
- fields
|
||||
/api/v1/fields/semconv-migration:
|
||||
get:
|
||||
deprecated: false
|
||||
description: Returns services that still emit old semantic-convention names
|
||||
without the current family name
|
||||
operationId: GetSemconvMigrationReport
|
||||
parameters:
|
||||
- in: query
|
||||
name: startUnixMilli
|
||||
schema:
|
||||
format: int64
|
||||
type: integer
|
||||
- in: query
|
||||
name: endUnixMilli
|
||||
schema:
|
||||
format: int64
|
||||
type: integer
|
||||
responses:
|
||||
"200":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
properties:
|
||||
data:
|
||||
$ref: '#/components/schemas/TelemetrytypesGettableSemconvMigrationReport'
|
||||
status:
|
||||
type: string
|
||||
required:
|
||||
- status
|
||||
- data
|
||||
type: object
|
||||
description: OK
|
||||
"401":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Unauthorized
|
||||
"403":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Forbidden
|
||||
"500":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- VIEWER
|
||||
- tokenizer:
|
||||
- VIEWER
|
||||
summary: Get semantic-convention migration report
|
||||
tags:
|
||||
- fields
|
||||
/api/v1/fields/values:
|
||||
get:
|
||||
deprecated: false
|
||||
@@ -18786,8 +18911,8 @@ paths:
|
||||
alert: Payments-api error log rate above 1%
|
||||
alertType: LOGS_BASED_ALERT
|
||||
annotations:
|
||||
description: Error log rate in {{$deployment.environment}} is
|
||||
{{$value}}%
|
||||
description: Error log rate in {{$deployment.environment.name}}
|
||||
is {{$value}}%
|
||||
summary: Payments-api error rate above {{$threshold}}%
|
||||
condition:
|
||||
compositeQuery:
|
||||
@@ -18803,7 +18928,7 @@ paths:
|
||||
groupBy:
|
||||
- fieldContext: resource
|
||||
fieldDataType: string
|
||||
name: deployment.environment
|
||||
name: deployment.environment.name
|
||||
name: A
|
||||
signal: logs
|
||||
stepInterval: 60
|
||||
@@ -18817,14 +18942,14 @@ paths:
|
||||
groupBy:
|
||||
- fieldContext: resource
|
||||
fieldDataType: string
|
||||
name: deployment.environment
|
||||
name: deployment.environment.name
|
||||
name: B
|
||||
signal: logs
|
||||
stepInterval: 60
|
||||
type: builder_query
|
||||
- spec:
|
||||
expression: (A / B) * 100
|
||||
legend: '{{deployment.environment}}'
|
||||
legend: '{{deployment.environment.name}}'
|
||||
name: F1
|
||||
type: builder_formula
|
||||
queryType: builder
|
||||
@@ -18850,7 +18975,7 @@ paths:
|
||||
team: payments
|
||||
notificationSettings:
|
||||
groupBy:
|
||||
- deployment.environment
|
||||
- deployment.environment.name
|
||||
renotify:
|
||||
alertStates:
|
||||
- firing
|
||||
@@ -18869,7 +18994,7 @@ paths:
|
||||
alertType: LOGS_BASED_ALERT
|
||||
annotations:
|
||||
description: '{{$k8s.pod.name}} emitted {{$value}} panic log(s)
|
||||
in {{$deployment.environment}}.'
|
||||
in {{$deployment.environment.name}}.'
|
||||
summary: Payments service panic
|
||||
condition:
|
||||
compositeQuery:
|
||||
@@ -18887,8 +19012,8 @@ paths:
|
||||
name: k8s.pod.name
|
||||
- fieldContext: resource
|
||||
fieldDataType: string
|
||||
name: deployment.environment
|
||||
legend: '{{k8s.pod.name}} ({{deployment.environment}})'
|
||||
name: deployment.environment.name
|
||||
legend: '{{k8s.pod.name}} ({{deployment.environment.name}})'
|
||||
name: A
|
||||
signal: logs
|
||||
stepInterval: 60
|
||||
@@ -18917,7 +19042,7 @@ paths:
|
||||
notificationSettings:
|
||||
groupBy:
|
||||
- k8s.pod.name
|
||||
- deployment.environment
|
||||
- deployment.environment.name
|
||||
renotify:
|
||||
alertStates:
|
||||
- firing
|
||||
@@ -18987,8 +19112,9 @@ paths:
|
||||
version: v5
|
||||
metric_promql:
|
||||
description: PromQL expression instead of the builder. Dotted OTEL
|
||||
resource attributes are quoted ("deployment.environment"). Useful
|
||||
for queries that combine series with group_right or other Prom operators.
|
||||
resource attributes are quoted ("deployment.environment.name").
|
||||
Useful for queries that combine series with group_right or other
|
||||
Prom operators.
|
||||
summary: Metric threshold PromQL rule
|
||||
value:
|
||||
alert: Kafka consumer group lag above 1000
|
||||
@@ -19004,9 +19130,9 @@ paths:
|
||||
- spec:
|
||||
legend: '{{topic}}/{{partition}} ({{group}})'
|
||||
name: A
|
||||
query: (max by(topic, partition, "deployment.environment")(kafka_log_end_offset)
|
||||
- on(topic, partition, "deployment.environment") group_right
|
||||
max by(group, topic, partition, "deployment.environment")(kafka_consumer_committed_offset))
|
||||
query: (max by(topic, partition, "deployment.environment.name")(kafka_log_end_offset)
|
||||
- on(topic, partition, "deployment.environment.name")
|
||||
group_right max by(group, topic, partition, "deployment.environment.name")(kafka_consumer_committed_offset))
|
||||
> 0
|
||||
type: promql
|
||||
queryType: promql
|
||||
@@ -19142,7 +19268,7 @@ paths:
|
||||
alertType: METRIC_BASED_ALERT
|
||||
annotations:
|
||||
description: Pod {{$k8s.pod.name}} CPU is at {{$value}} of request
|
||||
in {{$deployment.environment}}.
|
||||
in {{$deployment.environment.name}}.
|
||||
summary: Pod CPU above {{$threshold}} of request
|
||||
condition:
|
||||
compositeQuery:
|
||||
@@ -19161,8 +19287,8 @@ paths:
|
||||
name: k8s.pod.name
|
||||
- fieldContext: resource
|
||||
fieldDataType: string
|
||||
name: deployment.environment
|
||||
legend: '{{k8s.pod.name}} ({{deployment.environment}})'
|
||||
name: deployment.environment.name
|
||||
legend: '{{k8s.pod.name}} ({{deployment.environment.name}})'
|
||||
name: A
|
||||
signal: metrics
|
||||
stepInterval: 60
|
||||
@@ -19193,7 +19319,7 @@ paths:
|
||||
notificationSettings:
|
||||
groupBy:
|
||||
- k8s.pod.name
|
||||
- deployment.environment
|
||||
- deployment.environment.name
|
||||
renotify:
|
||||
alertStates:
|
||||
- firing
|
||||
@@ -19217,7 +19343,7 @@ paths:
|
||||
alert: API 5xx error rate above 1%
|
||||
alertType: TRACES_BASED_ALERT
|
||||
annotations:
|
||||
description: '{{$service.name}} 5xx rate in {{$deployment.environment}}
|
||||
description: '{{$service.name}} 5xx rate in {{$deployment.environment.name}}
|
||||
is {{$value}}%.'
|
||||
summary: API service error rate elevated
|
||||
condition:
|
||||
@@ -19229,7 +19355,7 @@ paths:
|
||||
- expression: count()
|
||||
disabled: true
|
||||
filter:
|
||||
expression: service.name CONTAINS 'api' AND http.status_code
|
||||
expression: service.name CONTAINS 'api' AND http.response.status_code
|
||||
>= 500
|
||||
groupBy:
|
||||
- fieldContext: resource
|
||||
@@ -19237,7 +19363,7 @@ paths:
|
||||
name: service.name
|
||||
- fieldContext: resource
|
||||
fieldDataType: string
|
||||
name: deployment.environment
|
||||
name: deployment.environment.name
|
||||
name: A
|
||||
signal: traces
|
||||
stepInterval: 60
|
||||
@@ -19254,14 +19380,14 @@ paths:
|
||||
name: service.name
|
||||
- fieldContext: resource
|
||||
fieldDataType: string
|
||||
name: deployment.environment
|
||||
name: deployment.environment.name
|
||||
name: B
|
||||
signal: traces
|
||||
stepInterval: 60
|
||||
type: builder_query
|
||||
- spec:
|
||||
expression: (A / B) * 100
|
||||
legend: '{{service.name}} ({{deployment.environment}})'
|
||||
legend: '{{service.name}} ({{deployment.environment.name}})'
|
||||
name: F1
|
||||
type: builder_formula
|
||||
queryType: builder
|
||||
@@ -19289,7 +19415,7 @@ paths:
|
||||
notificationSettings:
|
||||
groupBy:
|
||||
- service.name
|
||||
- deployment.environment
|
||||
- deployment.environment.name
|
||||
newGroupEvalDelay: 2m
|
||||
renotify:
|
||||
alertStates:
|
||||
@@ -19735,8 +19861,8 @@ paths:
|
||||
alert: Payments-api error log rate above 1%
|
||||
alertType: LOGS_BASED_ALERT
|
||||
annotations:
|
||||
description: Error log rate in {{$deployment.environment}} is
|
||||
{{$value}}%
|
||||
description: Error log rate in {{$deployment.environment.name}}
|
||||
is {{$value}}%
|
||||
summary: Payments-api error rate above {{$threshold}}%
|
||||
condition:
|
||||
compositeQuery:
|
||||
@@ -19752,7 +19878,7 @@ paths:
|
||||
groupBy:
|
||||
- fieldContext: resource
|
||||
fieldDataType: string
|
||||
name: deployment.environment
|
||||
name: deployment.environment.name
|
||||
name: A
|
||||
signal: logs
|
||||
stepInterval: 60
|
||||
@@ -19766,14 +19892,14 @@ paths:
|
||||
groupBy:
|
||||
- fieldContext: resource
|
||||
fieldDataType: string
|
||||
name: deployment.environment
|
||||
name: deployment.environment.name
|
||||
name: B
|
||||
signal: logs
|
||||
stepInterval: 60
|
||||
type: builder_query
|
||||
- spec:
|
||||
expression: (A / B) * 100
|
||||
legend: '{{deployment.environment}}'
|
||||
legend: '{{deployment.environment.name}}'
|
||||
name: F1
|
||||
type: builder_formula
|
||||
queryType: builder
|
||||
@@ -19799,7 +19925,7 @@ paths:
|
||||
team: payments
|
||||
notificationSettings:
|
||||
groupBy:
|
||||
- deployment.environment
|
||||
- deployment.environment.name
|
||||
renotify:
|
||||
alertStates:
|
||||
- firing
|
||||
@@ -19818,7 +19944,7 @@ paths:
|
||||
alertType: LOGS_BASED_ALERT
|
||||
annotations:
|
||||
description: '{{$k8s.pod.name}} emitted {{$value}} panic log(s)
|
||||
in {{$deployment.environment}}.'
|
||||
in {{$deployment.environment.name}}.'
|
||||
summary: Payments service panic
|
||||
condition:
|
||||
compositeQuery:
|
||||
@@ -19836,8 +19962,8 @@ paths:
|
||||
name: k8s.pod.name
|
||||
- fieldContext: resource
|
||||
fieldDataType: string
|
||||
name: deployment.environment
|
||||
legend: '{{k8s.pod.name}} ({{deployment.environment}})'
|
||||
name: deployment.environment.name
|
||||
legend: '{{k8s.pod.name}} ({{deployment.environment.name}})'
|
||||
name: A
|
||||
signal: logs
|
||||
stepInterval: 60
|
||||
@@ -19866,7 +19992,7 @@ paths:
|
||||
notificationSettings:
|
||||
groupBy:
|
||||
- k8s.pod.name
|
||||
- deployment.environment
|
||||
- deployment.environment.name
|
||||
renotify:
|
||||
alertStates:
|
||||
- firing
|
||||
@@ -19936,8 +20062,9 @@ paths:
|
||||
version: v5
|
||||
metric_promql:
|
||||
description: PromQL expression instead of the builder. Dotted OTEL
|
||||
resource attributes are quoted ("deployment.environment"). Useful
|
||||
for queries that combine series with group_right or other Prom operators.
|
||||
resource attributes are quoted ("deployment.environment.name").
|
||||
Useful for queries that combine series with group_right or other
|
||||
Prom operators.
|
||||
summary: Metric threshold PromQL rule
|
||||
value:
|
||||
alert: Kafka consumer group lag above 1000
|
||||
@@ -19953,9 +20080,9 @@ paths:
|
||||
- spec:
|
||||
legend: '{{topic}}/{{partition}} ({{group}})'
|
||||
name: A
|
||||
query: (max by(topic, partition, "deployment.environment")(kafka_log_end_offset)
|
||||
- on(topic, partition, "deployment.environment") group_right
|
||||
max by(group, topic, partition, "deployment.environment")(kafka_consumer_committed_offset))
|
||||
query: (max by(topic, partition, "deployment.environment.name")(kafka_log_end_offset)
|
||||
- on(topic, partition, "deployment.environment.name")
|
||||
group_right max by(group, topic, partition, "deployment.environment.name")(kafka_consumer_committed_offset))
|
||||
> 0
|
||||
type: promql
|
||||
queryType: promql
|
||||
@@ -20091,7 +20218,7 @@ paths:
|
||||
alertType: METRIC_BASED_ALERT
|
||||
annotations:
|
||||
description: Pod {{$k8s.pod.name}} CPU is at {{$value}} of request
|
||||
in {{$deployment.environment}}.
|
||||
in {{$deployment.environment.name}}.
|
||||
summary: Pod CPU above {{$threshold}} of request
|
||||
condition:
|
||||
compositeQuery:
|
||||
@@ -20110,8 +20237,8 @@ paths:
|
||||
name: k8s.pod.name
|
||||
- fieldContext: resource
|
||||
fieldDataType: string
|
||||
name: deployment.environment
|
||||
legend: '{{k8s.pod.name}} ({{deployment.environment}})'
|
||||
name: deployment.environment.name
|
||||
legend: '{{k8s.pod.name}} ({{deployment.environment.name}})'
|
||||
name: A
|
||||
signal: metrics
|
||||
stepInterval: 60
|
||||
@@ -20142,7 +20269,7 @@ paths:
|
||||
notificationSettings:
|
||||
groupBy:
|
||||
- k8s.pod.name
|
||||
- deployment.environment
|
||||
- deployment.environment.name
|
||||
renotify:
|
||||
alertStates:
|
||||
- firing
|
||||
@@ -20166,7 +20293,7 @@ paths:
|
||||
alert: API 5xx error rate above 1%
|
||||
alertType: TRACES_BASED_ALERT
|
||||
annotations:
|
||||
description: '{{$service.name}} 5xx rate in {{$deployment.environment}}
|
||||
description: '{{$service.name}} 5xx rate in {{$deployment.environment.name}}
|
||||
is {{$value}}%.'
|
||||
summary: API service error rate elevated
|
||||
condition:
|
||||
@@ -20178,7 +20305,7 @@ paths:
|
||||
- expression: count()
|
||||
disabled: true
|
||||
filter:
|
||||
expression: service.name CONTAINS 'api' AND http.status_code
|
||||
expression: service.name CONTAINS 'api' AND http.response.status_code
|
||||
>= 500
|
||||
groupBy:
|
||||
- fieldContext: resource
|
||||
@@ -20186,7 +20313,7 @@ paths:
|
||||
name: service.name
|
||||
- fieldContext: resource
|
||||
fieldDataType: string
|
||||
name: deployment.environment
|
||||
name: deployment.environment.name
|
||||
name: A
|
||||
signal: traces
|
||||
stepInterval: 60
|
||||
@@ -20203,14 +20330,14 @@ paths:
|
||||
name: service.name
|
||||
- fieldContext: resource
|
||||
fieldDataType: string
|
||||
name: deployment.environment
|
||||
name: deployment.environment.name
|
||||
name: B
|
||||
signal: traces
|
||||
stepInterval: 60
|
||||
type: builder_query
|
||||
- spec:
|
||||
expression: (A / B) * 100
|
||||
legend: '{{service.name}} ({{deployment.environment}})'
|
||||
legend: '{{service.name}} ({{deployment.environment.name}})'
|
||||
name: F1
|
||||
type: builder_formula
|
||||
queryType: builder
|
||||
@@ -20238,7 +20365,7 @@ paths:
|
||||
notificationSettings:
|
||||
groupBy:
|
||||
- service.name
|
||||
- deployment.environment
|
||||
- deployment.environment.name
|
||||
newGroupEvalDelay: 2m
|
||||
renotify:
|
||||
alertStates:
|
||||
@@ -20587,8 +20714,8 @@ paths:
|
||||
alert: Payments-api error log rate above 1%
|
||||
alertType: LOGS_BASED_ALERT
|
||||
annotations:
|
||||
description: Error log rate in {{$deployment.environment}} is
|
||||
{{$value}}%
|
||||
description: Error log rate in {{$deployment.environment.name}}
|
||||
is {{$value}}%
|
||||
summary: Payments-api error rate above {{$threshold}}%
|
||||
condition:
|
||||
compositeQuery:
|
||||
@@ -20604,7 +20731,7 @@ paths:
|
||||
groupBy:
|
||||
- fieldContext: resource
|
||||
fieldDataType: string
|
||||
name: deployment.environment
|
||||
name: deployment.environment.name
|
||||
name: A
|
||||
signal: logs
|
||||
stepInterval: 60
|
||||
@@ -20618,14 +20745,14 @@ paths:
|
||||
groupBy:
|
||||
- fieldContext: resource
|
||||
fieldDataType: string
|
||||
name: deployment.environment
|
||||
name: deployment.environment.name
|
||||
name: B
|
||||
signal: logs
|
||||
stepInterval: 60
|
||||
type: builder_query
|
||||
- spec:
|
||||
expression: (A / B) * 100
|
||||
legend: '{{deployment.environment}}'
|
||||
legend: '{{deployment.environment.name}}'
|
||||
name: F1
|
||||
type: builder_formula
|
||||
queryType: builder
|
||||
@@ -20651,7 +20778,7 @@ paths:
|
||||
team: payments
|
||||
notificationSettings:
|
||||
groupBy:
|
||||
- deployment.environment
|
||||
- deployment.environment.name
|
||||
renotify:
|
||||
alertStates:
|
||||
- firing
|
||||
@@ -20670,7 +20797,7 @@ paths:
|
||||
alertType: LOGS_BASED_ALERT
|
||||
annotations:
|
||||
description: '{{$k8s.pod.name}} emitted {{$value}} panic log(s)
|
||||
in {{$deployment.environment}}.'
|
||||
in {{$deployment.environment.name}}.'
|
||||
summary: Payments service panic
|
||||
condition:
|
||||
compositeQuery:
|
||||
@@ -20688,8 +20815,8 @@ paths:
|
||||
name: k8s.pod.name
|
||||
- fieldContext: resource
|
||||
fieldDataType: string
|
||||
name: deployment.environment
|
||||
legend: '{{k8s.pod.name}} ({{deployment.environment}})'
|
||||
name: deployment.environment.name
|
||||
legend: '{{k8s.pod.name}} ({{deployment.environment.name}})'
|
||||
name: A
|
||||
signal: logs
|
||||
stepInterval: 60
|
||||
@@ -20718,7 +20845,7 @@ paths:
|
||||
notificationSettings:
|
||||
groupBy:
|
||||
- k8s.pod.name
|
||||
- deployment.environment
|
||||
- deployment.environment.name
|
||||
renotify:
|
||||
alertStates:
|
||||
- firing
|
||||
@@ -20788,8 +20915,9 @@ paths:
|
||||
version: v5
|
||||
metric_promql:
|
||||
description: PromQL expression instead of the builder. Dotted OTEL
|
||||
resource attributes are quoted ("deployment.environment"). Useful
|
||||
for queries that combine series with group_right or other Prom operators.
|
||||
resource attributes are quoted ("deployment.environment.name").
|
||||
Useful for queries that combine series with group_right or other
|
||||
Prom operators.
|
||||
summary: Metric threshold PromQL rule
|
||||
value:
|
||||
alert: Kafka consumer group lag above 1000
|
||||
@@ -20805,9 +20933,9 @@ paths:
|
||||
- spec:
|
||||
legend: '{{topic}}/{{partition}} ({{group}})'
|
||||
name: A
|
||||
query: (max by(topic, partition, "deployment.environment")(kafka_log_end_offset)
|
||||
- on(topic, partition, "deployment.environment") group_right
|
||||
max by(group, topic, partition, "deployment.environment")(kafka_consumer_committed_offset))
|
||||
query: (max by(topic, partition, "deployment.environment.name")(kafka_log_end_offset)
|
||||
- on(topic, partition, "deployment.environment.name")
|
||||
group_right max by(group, topic, partition, "deployment.environment.name")(kafka_consumer_committed_offset))
|
||||
> 0
|
||||
type: promql
|
||||
queryType: promql
|
||||
@@ -20943,7 +21071,7 @@ paths:
|
||||
alertType: METRIC_BASED_ALERT
|
||||
annotations:
|
||||
description: Pod {{$k8s.pod.name}} CPU is at {{$value}} of request
|
||||
in {{$deployment.environment}}.
|
||||
in {{$deployment.environment.name}}.
|
||||
summary: Pod CPU above {{$threshold}} of request
|
||||
condition:
|
||||
compositeQuery:
|
||||
@@ -20962,8 +21090,8 @@ paths:
|
||||
name: k8s.pod.name
|
||||
- fieldContext: resource
|
||||
fieldDataType: string
|
||||
name: deployment.environment
|
||||
legend: '{{k8s.pod.name}} ({{deployment.environment}})'
|
||||
name: deployment.environment.name
|
||||
legend: '{{k8s.pod.name}} ({{deployment.environment.name}})'
|
||||
name: A
|
||||
signal: metrics
|
||||
stepInterval: 60
|
||||
@@ -20994,7 +21122,7 @@ paths:
|
||||
notificationSettings:
|
||||
groupBy:
|
||||
- k8s.pod.name
|
||||
- deployment.environment
|
||||
- deployment.environment.name
|
||||
renotify:
|
||||
alertStates:
|
||||
- firing
|
||||
@@ -21018,7 +21146,7 @@ paths:
|
||||
alert: API 5xx error rate above 1%
|
||||
alertType: TRACES_BASED_ALERT
|
||||
annotations:
|
||||
description: '{{$service.name}} 5xx rate in {{$deployment.environment}}
|
||||
description: '{{$service.name}} 5xx rate in {{$deployment.environment.name}}
|
||||
is {{$value}}%.'
|
||||
summary: API service error rate elevated
|
||||
condition:
|
||||
@@ -21030,7 +21158,7 @@ paths:
|
||||
- expression: count()
|
||||
disabled: true
|
||||
filter:
|
||||
expression: service.name CONTAINS 'api' AND http.status_code
|
||||
expression: service.name CONTAINS 'api' AND http.response.status_code
|
||||
>= 500
|
||||
groupBy:
|
||||
- fieldContext: resource
|
||||
@@ -21038,7 +21166,7 @@ paths:
|
||||
name: service.name
|
||||
- fieldContext: resource
|
||||
fieldDataType: string
|
||||
name: deployment.environment
|
||||
name: deployment.environment.name
|
||||
name: A
|
||||
signal: traces
|
||||
stepInterval: 60
|
||||
@@ -21055,14 +21183,14 @@ paths:
|
||||
name: service.name
|
||||
- fieldContext: resource
|
||||
fieldDataType: string
|
||||
name: deployment.environment
|
||||
name: deployment.environment.name
|
||||
name: B
|
||||
signal: traces
|
||||
stepInterval: 60
|
||||
type: builder_query
|
||||
- spec:
|
||||
expression: (A / B) * 100
|
||||
legend: '{{service.name}} ({{deployment.environment}})'
|
||||
legend: '{{service.name}} ({{deployment.environment.name}})'
|
||||
name: F1
|
||||
type: builder_formula
|
||||
queryType: builder
|
||||
@@ -21090,7 +21218,7 @@ paths:
|
||||
notificationSettings:
|
||||
groupBy:
|
||||
- service.name
|
||||
- deployment.environment
|
||||
- deployment.environment.name
|
||||
newGroupEvalDelay: 2m
|
||||
renotify:
|
||||
alertStates:
|
||||
@@ -21942,8 +22070,8 @@ paths:
|
||||
alert: Payments-api error log rate above 1%
|
||||
alertType: LOGS_BASED_ALERT
|
||||
annotations:
|
||||
description: Error log rate in {{$deployment.environment}} is
|
||||
{{$value}}%
|
||||
description: Error log rate in {{$deployment.environment.name}}
|
||||
is {{$value}}%
|
||||
summary: Payments-api error rate above {{$threshold}}%
|
||||
condition:
|
||||
compositeQuery:
|
||||
@@ -21959,7 +22087,7 @@ paths:
|
||||
groupBy:
|
||||
- fieldContext: resource
|
||||
fieldDataType: string
|
||||
name: deployment.environment
|
||||
name: deployment.environment.name
|
||||
name: A
|
||||
signal: logs
|
||||
stepInterval: 60
|
||||
@@ -21973,14 +22101,14 @@ paths:
|
||||
groupBy:
|
||||
- fieldContext: resource
|
||||
fieldDataType: string
|
||||
name: deployment.environment
|
||||
name: deployment.environment.name
|
||||
name: B
|
||||
signal: logs
|
||||
stepInterval: 60
|
||||
type: builder_query
|
||||
- spec:
|
||||
expression: (A / B) * 100
|
||||
legend: '{{deployment.environment}}'
|
||||
legend: '{{deployment.environment.name}}'
|
||||
name: F1
|
||||
type: builder_formula
|
||||
queryType: builder
|
||||
@@ -22006,7 +22134,7 @@ paths:
|
||||
team: payments
|
||||
notificationSettings:
|
||||
groupBy:
|
||||
- deployment.environment
|
||||
- deployment.environment.name
|
||||
renotify:
|
||||
alertStates:
|
||||
- firing
|
||||
@@ -22025,7 +22153,7 @@ paths:
|
||||
alertType: LOGS_BASED_ALERT
|
||||
annotations:
|
||||
description: '{{$k8s.pod.name}} emitted {{$value}} panic log(s)
|
||||
in {{$deployment.environment}}.'
|
||||
in {{$deployment.environment.name}}.'
|
||||
summary: Payments service panic
|
||||
condition:
|
||||
compositeQuery:
|
||||
@@ -22043,8 +22171,8 @@ paths:
|
||||
name: k8s.pod.name
|
||||
- fieldContext: resource
|
||||
fieldDataType: string
|
||||
name: deployment.environment
|
||||
legend: '{{k8s.pod.name}} ({{deployment.environment}})'
|
||||
name: deployment.environment.name
|
||||
legend: '{{k8s.pod.name}} ({{deployment.environment.name}})'
|
||||
name: A
|
||||
signal: logs
|
||||
stepInterval: 60
|
||||
@@ -22073,7 +22201,7 @@ paths:
|
||||
notificationSettings:
|
||||
groupBy:
|
||||
- k8s.pod.name
|
||||
- deployment.environment
|
||||
- deployment.environment.name
|
||||
renotify:
|
||||
alertStates:
|
||||
- firing
|
||||
@@ -22143,8 +22271,9 @@ paths:
|
||||
version: v5
|
||||
metric_promql:
|
||||
description: PromQL expression instead of the builder. Dotted OTEL
|
||||
resource attributes are quoted ("deployment.environment"). Useful
|
||||
for queries that combine series with group_right or other Prom operators.
|
||||
resource attributes are quoted ("deployment.environment.name").
|
||||
Useful for queries that combine series with group_right or other
|
||||
Prom operators.
|
||||
summary: Metric threshold PromQL rule
|
||||
value:
|
||||
alert: Kafka consumer group lag above 1000
|
||||
@@ -22160,9 +22289,9 @@ paths:
|
||||
- spec:
|
||||
legend: '{{topic}}/{{partition}} ({{group}})'
|
||||
name: A
|
||||
query: (max by(topic, partition, "deployment.environment")(kafka_log_end_offset)
|
||||
- on(topic, partition, "deployment.environment") group_right
|
||||
max by(group, topic, partition, "deployment.environment")(kafka_consumer_committed_offset))
|
||||
query: (max by(topic, partition, "deployment.environment.name")(kafka_log_end_offset)
|
||||
- on(topic, partition, "deployment.environment.name")
|
||||
group_right max by(group, topic, partition, "deployment.environment.name")(kafka_consumer_committed_offset))
|
||||
> 0
|
||||
type: promql
|
||||
queryType: promql
|
||||
@@ -22298,7 +22427,7 @@ paths:
|
||||
alertType: METRIC_BASED_ALERT
|
||||
annotations:
|
||||
description: Pod {{$k8s.pod.name}} CPU is at {{$value}} of request
|
||||
in {{$deployment.environment}}.
|
||||
in {{$deployment.environment.name}}.
|
||||
summary: Pod CPU above {{$threshold}} of request
|
||||
condition:
|
||||
compositeQuery:
|
||||
@@ -22317,8 +22446,8 @@ paths:
|
||||
name: k8s.pod.name
|
||||
- fieldContext: resource
|
||||
fieldDataType: string
|
||||
name: deployment.environment
|
||||
legend: '{{k8s.pod.name}} ({{deployment.environment}})'
|
||||
name: deployment.environment.name
|
||||
legend: '{{k8s.pod.name}} ({{deployment.environment.name}})'
|
||||
name: A
|
||||
signal: metrics
|
||||
stepInterval: 60
|
||||
@@ -22349,7 +22478,7 @@ paths:
|
||||
notificationSettings:
|
||||
groupBy:
|
||||
- k8s.pod.name
|
||||
- deployment.environment
|
||||
- deployment.environment.name
|
||||
renotify:
|
||||
alertStates:
|
||||
- firing
|
||||
@@ -22373,7 +22502,7 @@ paths:
|
||||
alert: API 5xx error rate above 1%
|
||||
alertType: TRACES_BASED_ALERT
|
||||
annotations:
|
||||
description: '{{$service.name}} 5xx rate in {{$deployment.environment}}
|
||||
description: '{{$service.name}} 5xx rate in {{$deployment.environment.name}}
|
||||
is {{$value}}%.'
|
||||
summary: API service error rate elevated
|
||||
condition:
|
||||
@@ -22385,7 +22514,7 @@ paths:
|
||||
- expression: count()
|
||||
disabled: true
|
||||
filter:
|
||||
expression: service.name CONTAINS 'api' AND http.status_code
|
||||
expression: service.name CONTAINS 'api' AND http.response.status_code
|
||||
>= 500
|
||||
groupBy:
|
||||
- fieldContext: resource
|
||||
@@ -22393,7 +22522,7 @@ paths:
|
||||
name: service.name
|
||||
- fieldContext: resource
|
||||
fieldDataType: string
|
||||
name: deployment.environment
|
||||
name: deployment.environment.name
|
||||
name: A
|
||||
signal: traces
|
||||
stepInterval: 60
|
||||
@@ -22410,14 +22539,14 @@ paths:
|
||||
name: service.name
|
||||
- fieldContext: resource
|
||||
fieldDataType: string
|
||||
name: deployment.environment
|
||||
name: deployment.environment.name
|
||||
name: B
|
||||
signal: traces
|
||||
stepInterval: 60
|
||||
type: builder_query
|
||||
- spec:
|
||||
expression: (A / B) * 100
|
||||
legend: '{{service.name}} ({{deployment.environment}})'
|
||||
legend: '{{service.name}} ({{deployment.environment.name}})'
|
||||
name: F1
|
||||
type: builder_formula
|
||||
queryType: builder
|
||||
@@ -22445,7 +22574,7 @@ paths:
|
||||
notificationSettings:
|
||||
groupBy:
|
||||
- service.name
|
||||
- deployment.environment
|
||||
- deployment.environment.name
|
||||
newGroupEvalDelay: 2m
|
||||
renotify:
|
||||
alertStates:
|
||||
|
||||
@@ -232,14 +232,11 @@ cd tests/e2e
|
||||
# Single feature dir
|
||||
npx playwright test tests/alerts/ --project=chromium
|
||||
|
||||
# Single sub-area
|
||||
npx playwright test tests/alerts/history/ --project=chromium
|
||||
|
||||
# Single file
|
||||
npx playwright test tests/alerts/page.spec.ts --project=chromium
|
||||
npx playwright test tests/alerts/alerts.spec.ts --project=chromium
|
||||
|
||||
# Single test by title grep
|
||||
npx playwright test --project=chromium -g "AL-01"
|
||||
npx playwright test --project=chromium -g "TC-01"
|
||||
```
|
||||
|
||||
### Iterative modes
|
||||
@@ -273,14 +270,7 @@ yarn test:staging
|
||||
| `SIGNOZ_E2E_PASSWORD` | Admin password. Bootstrap writes the integration-test default. |
|
||||
| `SIGNOZ_E2E_SEEDER_URL` | Seeder HTTP base URL — hit by specs that need per-test telemetry. |
|
||||
|
||||
Precedence in `playwright.config.ts`, lowest to highest: `.env` (user-provided, staging) → `.env.local` (bootstrap-generated, local mode) → whatever is already in `process.env`. The config parses both files itself and only fills in keys the environment does not already define, so exporting a variable always wins:
|
||||
|
||||
```bash
|
||||
# runs against a locally served frontend, not whatever .env.local points at
|
||||
SIGNOZ_E2E_BASE_URL=http://127.0.0.1:3301 pnpm test tests/alerts
|
||||
```
|
||||
|
||||
This is deliberately not `dotenv.config({ override: true })`. That flag makes the *file* beat `process.env`, which silently discarded exported values — including the `SIGNOZ_E2E_BASE_URL` in `pnpm test:staging`, whenever a `.env.local` happened to exist.
|
||||
Loading order in `playwright.config.ts`: `.env` first (user-provided, staging), then `.env.local` with `override: true` (bootstrap-generated, local mode). Anything already set in `process.env` at yarn-test time wins because dotenv doesn't touch vars that are already present.
|
||||
|
||||
### Playwright options
|
||||
|
||||
|
||||
@@ -176,7 +176,7 @@ func (m *module) Create(ctx context.Context, orgID valuer.UUID, userEmail string
|
||||
if err := m.checkAccess(ctx, orgID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := req.Validate(); err != nil {
|
||||
if err := metricreductionrule.ValidatePostableReductionRule(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := m.validateMetricForReduction(ctx, orgID, req.MetricName); err != nil {
|
||||
@@ -218,7 +218,7 @@ func (m *module) UpdateByID(ctx context.Context, orgID valuer.UUID, userEmail st
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := req.Validate(); err != nil {
|
||||
if err := metricreductionrule.ValidateUpdatableReductionRule(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -543,7 +543,7 @@ func resolveDroppedKept(matchType metricreductionruletypes.MatchType, ruleLabels
|
||||
}
|
||||
|
||||
for _, k := range keys {
|
||||
if metricreductionruletypes.IsProtectedLabel(k) {
|
||||
if metricreductionrule.IsProtectedLabel(k) {
|
||||
kept = append(kept, k)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -19,6 +19,8 @@ import type {
|
||||
GetFieldsKeysParams,
|
||||
GetFieldsValues200,
|
||||
GetFieldsValuesParams,
|
||||
GetSemconvMigrationReport200,
|
||||
GetSemconvMigrationReportParams,
|
||||
RenderErrorResponseDTO,
|
||||
} from '../sigNoz.schemas';
|
||||
|
||||
@@ -120,6 +122,108 @@ export const invalidateGetFieldsKeys = async (
|
||||
return queryClient;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns services that still emit old semantic-convention names without the current family name
|
||||
* @summary Get semantic-convention migration report
|
||||
*/
|
||||
export const getSemconvMigrationReport = (
|
||||
params?: GetSemconvMigrationReportParams,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<GetSemconvMigrationReport200>({
|
||||
url: `/api/v1/fields/semconv-migration`,
|
||||
method: 'GET',
|
||||
params,
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getGetSemconvMigrationReportQueryKey = (
|
||||
params?: GetSemconvMigrationReportParams,
|
||||
) => {
|
||||
return [
|
||||
`/api/v1/fields/semconv-migration`,
|
||||
...(params ? [params] : []),
|
||||
] as const;
|
||||
};
|
||||
|
||||
export const getGetSemconvMigrationReportQueryOptions = <
|
||||
TData = Awaited<ReturnType<typeof getSemconvMigrationReport>>,
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
>(
|
||||
params?: GetSemconvMigrationReportParams,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getSemconvMigrationReport>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
},
|
||||
) => {
|
||||
const { query: queryOptions } = options ?? {};
|
||||
|
||||
const queryKey =
|
||||
queryOptions?.queryKey ?? getGetSemconvMigrationReportQueryKey(params);
|
||||
|
||||
const queryFn: QueryFunction<
|
||||
Awaited<ReturnType<typeof getSemconvMigrationReport>>
|
||||
> = ({ signal }) => getSemconvMigrationReport(params, signal);
|
||||
|
||||
return { queryKey, queryFn, ...queryOptions } as UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getSemconvMigrationReport>>,
|
||||
TError,
|
||||
TData
|
||||
> & { queryKey: QueryKey };
|
||||
};
|
||||
|
||||
export type GetSemconvMigrationReportQueryResult = NonNullable<
|
||||
Awaited<ReturnType<typeof getSemconvMigrationReport>>
|
||||
>;
|
||||
export type GetSemconvMigrationReportQueryError =
|
||||
ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary Get semantic-convention migration report
|
||||
*/
|
||||
|
||||
export function useGetSemconvMigrationReport<
|
||||
TData = Awaited<ReturnType<typeof getSemconvMigrationReport>>,
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
>(
|
||||
params?: GetSemconvMigrationReportParams,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getSemconvMigrationReport>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
},
|
||||
): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
|
||||
const queryOptions = getGetSemconvMigrationReportQueryOptions(params, options);
|
||||
|
||||
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
|
||||
queryKey: QueryKey;
|
||||
};
|
||||
|
||||
return { ...query, queryKey: queryOptions.queryKey };
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Get semantic-convention migration report
|
||||
*/
|
||||
export const invalidateGetSemconvMigrationReport = async (
|
||||
queryClient: QueryClient,
|
||||
params?: GetSemconvMigrationReportParams,
|
||||
options?: InvalidateOptions,
|
||||
): Promise<QueryClient> => {
|
||||
await queryClient.invalidateQueries(
|
||||
{ queryKey: getGetSemconvMigrationReportQueryKey(params) },
|
||||
options,
|
||||
);
|
||||
|
||||
return queryClient;
|
||||
};
|
||||
|
||||
/**
|
||||
* This endpoint returns field values
|
||||
* @summary Get field values
|
||||
|
||||
@@ -3482,6 +3482,10 @@ export enum TelemetrytypesFieldDataTypeDTO {
|
||||
number = 'number',
|
||||
'' = '',
|
||||
}
|
||||
export enum TelemetrytypesFieldResolutionDTO {
|
||||
exact = 'exact',
|
||||
'' = '',
|
||||
}
|
||||
export enum TelemetrytypesSignalDTO {
|
||||
traces = 'traces',
|
||||
logs = 'logs',
|
||||
@@ -3495,6 +3499,7 @@ export interface Querybuildertypesv5GroupByKeyDTO {
|
||||
description?: string;
|
||||
fieldContext?: TelemetrytypesFieldContextDTO;
|
||||
fieldDataType?: TelemetrytypesFieldDataTypeDTO;
|
||||
fieldResolution?: TelemetrytypesFieldResolutionDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
@@ -3535,6 +3540,7 @@ export interface Querybuildertypesv5OrderByKeyDTO {
|
||||
description?: string;
|
||||
fieldContext?: TelemetrytypesFieldContextDTO;
|
||||
fieldDataType?: TelemetrytypesFieldDataTypeDTO;
|
||||
fieldResolution?: TelemetrytypesFieldResolutionDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
@@ -3588,6 +3594,7 @@ export interface TelemetrytypesTelemetryFieldKeyDTO {
|
||||
description?: string;
|
||||
fieldContext?: TelemetrytypesFieldContextDTO;
|
||||
fieldDataType?: TelemetrytypesFieldDataTypeDTO;
|
||||
fieldResolution?: TelemetrytypesFieldResolutionDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
@@ -7936,6 +7943,7 @@ export interface Querybuildertypesv5ColumnDescriptorDTO {
|
||||
description?: string;
|
||||
fieldContext?: TelemetrytypesFieldContextDTO;
|
||||
fieldDataType?: TelemetrytypesFieldDataTypeDTO;
|
||||
fieldResolution?: TelemetrytypesFieldResolutionDTO;
|
||||
/**
|
||||
* @type object
|
||||
*/
|
||||
@@ -7959,6 +7967,25 @@ export type Querybuildertypesv5ExecStatsDTOStepIntervals = {
|
||||
[key: string]: number;
|
||||
};
|
||||
|
||||
export interface Querybuildertypesv5SemconvResolutionDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
current?: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
kind?: string;
|
||||
/**
|
||||
* @type array,null
|
||||
*/
|
||||
members?: string[] | null;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
requested?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execution statistics for the query, including rows scanned, bytes scanned, and duration.
|
||||
*/
|
||||
@@ -7978,6 +8005,10 @@ export interface Querybuildertypesv5ExecStatsDTO {
|
||||
* @minimum 0
|
||||
*/
|
||||
rowsScanned?: number;
|
||||
/**
|
||||
* @type array
|
||||
*/
|
||||
semconvResolutions?: Querybuildertypesv5SemconvResolutionDTO[];
|
||||
/**
|
||||
* @type object
|
||||
*/
|
||||
@@ -9816,6 +9847,52 @@ export interface TelemetrytypesGettableFieldValuesDTO {
|
||||
values: TelemetrytypesTelemetryFieldValuesDTO;
|
||||
}
|
||||
|
||||
export interface TelemetrytypesSemconvMigrationReportEntryDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
current?: string;
|
||||
/**
|
||||
* @type integer
|
||||
* @format int64
|
||||
*/
|
||||
lastSeenUnixMilli?: number;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
old?: string;
|
||||
/**
|
||||
* @type integer
|
||||
* @minimum 0
|
||||
*/
|
||||
resourceSets?: number;
|
||||
/**
|
||||
* @type array,null
|
||||
*/
|
||||
services?: string[] | null;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
signal?: string;
|
||||
}
|
||||
|
||||
export interface TelemetrytypesGettableSemconvMigrationReportDTO {
|
||||
/**
|
||||
* @type integer
|
||||
* @format int64
|
||||
*/
|
||||
endUnixMilli?: number;
|
||||
/**
|
||||
* @type array,null
|
||||
*/
|
||||
entries: TelemetrytypesSemconvMigrationReportEntryDTO[] | null;
|
||||
/**
|
||||
* @type integer
|
||||
* @format int64
|
||||
*/
|
||||
startUnixMilli?: number;
|
||||
}
|
||||
|
||||
export interface TypesChangePasswordRequestDTO {
|
||||
/**
|
||||
* @type string
|
||||
@@ -10582,6 +10659,29 @@ export type GetFieldsKeys200 = {
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type GetSemconvMigrationReportParams = {
|
||||
/**
|
||||
* @type integer
|
||||
* @format int64
|
||||
* @description undefined
|
||||
*/
|
||||
startUnixMilli?: number;
|
||||
/**
|
||||
* @type integer
|
||||
* @format int64
|
||||
* @description undefined
|
||||
*/
|
||||
endUnixMilli?: number;
|
||||
};
|
||||
|
||||
export type GetSemconvMigrationReport200 = {
|
||||
data: TelemetrytypesGettableSemconvMigrationReportDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type GetFieldsValuesParams = {
|
||||
/**
|
||||
* @description undefined
|
||||
|
||||
9
frontend/src/api/semconv/getMigrationReport.ts
Normal file
9
frontend/src/api/semconv/getMigrationReport.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import axios from 'api';
|
||||
import { SemconvMigrationReport } from 'types/api/semconvMigration';
|
||||
|
||||
async function getSemconvMigrationReport(): Promise<SemconvMigrationReport> {
|
||||
const response = await axios.get('/fields/semconv-migration');
|
||||
return response.data.data;
|
||||
}
|
||||
|
||||
export default getSemconvMigrationReport;
|
||||
@@ -64,12 +64,12 @@ export const COMMON_FILTERS = {
|
||||
SERVER_SPANS: "kind_string = 'Server'",
|
||||
CLIENT_SPANS: "kind_string = 'Client'",
|
||||
INTERNAL_SPANS: "kind_string = 'Internal'",
|
||||
ERROR_SPANS: 'http.status_code >= 400',
|
||||
SUCCESS_SPANS: 'http.status_code < 400',
|
||||
ERROR_SPANS: 'http.response.status_code >= 400',
|
||||
SUCCESS_SPANS: 'http.response.status_code < 400',
|
||||
|
||||
// Common service filters
|
||||
EXCLUDE_HEALTH_CHECKS: "http.route != '/health' AND http.route != '/ping'",
|
||||
HTTP_REQUESTS: "http.method != ''",
|
||||
HTTP_REQUESTS: "http.request.method != ''",
|
||||
|
||||
// Log filters
|
||||
ERROR_LOGS: "severity_text = 'ERROR'",
|
||||
@@ -87,7 +87,7 @@ export const COMMON_GROUP_BY_FIELDS = {
|
||||
fieldContext: 'resource' as const,
|
||||
},
|
||||
HTTP_METHOD: {
|
||||
name: 'http.method',
|
||||
name: 'http.request.method',
|
||||
fieldDataType: 'string' as const,
|
||||
fieldContext: 'attribute' as const,
|
||||
},
|
||||
@@ -97,7 +97,7 @@ export const COMMON_GROUP_BY_FIELDS = {
|
||||
fieldContext: 'attribute' as const,
|
||||
},
|
||||
HTTP_STATUS_CODE: {
|
||||
name: 'http.status_code',
|
||||
name: 'http.response.status_code',
|
||||
fieldDataType: 'int64' as const,
|
||||
fieldContext: 'attribute' as const,
|
||||
},
|
||||
|
||||
@@ -145,7 +145,7 @@ function CeleryOverviewConfigOptions(): JSX.Element {
|
||||
{
|
||||
placeholder: 'Destination',
|
||||
queryParam: QueryParams.destination,
|
||||
filterType: ['messaging.destination.name', 'messaging.destination'],
|
||||
filterType: ['messaging.destination.name'],
|
||||
},
|
||||
{
|
||||
placeholder: 'Kind',
|
||||
|
||||
@@ -1746,7 +1746,7 @@ QuerySearch.defaultProps = {
|
||||
signalSource: '',
|
||||
hardcodedAttributeKeys: undefined,
|
||||
placeholder:
|
||||
"Enter your filter query (e.g., http.status_code >= 500 AND service.name = 'frontend')",
|
||||
"Enter your filter query (e.g., http.response.status_code >= 500 AND service.name = 'frontend')",
|
||||
showFilterSuggestionsWithoutMetric: false,
|
||||
initialExpression: undefined,
|
||||
};
|
||||
|
||||
@@ -152,15 +152,18 @@ describe('QuerySearch (Integration with Real CodeMirror)', () => {
|
||||
/>,
|
||||
);
|
||||
|
||||
// Wait for debounced API call (300ms debounce + some buffer)
|
||||
await waitFor(() => expect(mockedGetKeysOnMount).toHaveBeenCalled(), {
|
||||
timeout: 2000,
|
||||
});
|
||||
|
||||
const lastArgs = mockedGetKeysOnMount.mock.calls[
|
||||
mockedGetKeysOnMount.mock.calls.length - 1
|
||||
]?.[0] as { signal: unknown; searchText: string };
|
||||
expect(lastArgs).toMatchObject({ signal: DataSource.LOGS, searchText: '' });
|
||||
// Wait for this mount's debounced call. A debounce from the preceding
|
||||
// real-CodeMirror test can finish after mockClear(), so do not assume the
|
||||
// first or last recorded call belongs to this render.
|
||||
await waitFor(
|
||||
() =>
|
||||
expect(
|
||||
mockedGetKeysOnMount.mock.calls.some(
|
||||
([args]) => args.signal === DataSource.LOGS && args.searchText === '',
|
||||
),
|
||||
).toBe(true),
|
||||
{ timeout: 2000 },
|
||||
);
|
||||
});
|
||||
|
||||
it('calls provided onRun on Mod-Enter', async () => {
|
||||
|
||||
@@ -995,6 +995,16 @@ describe('removeKeysFromExpression', () => {
|
||||
expect(result).toBe("status = 'success'");
|
||||
});
|
||||
|
||||
it('should remove a comparison that uses the exact field wrapper', () => {
|
||||
const expression =
|
||||
"exact(resource.deployment.environment) EXISTS AND service.name = 'api-gateway'";
|
||||
const result = removeKeysFromExpression(expression, [
|
||||
'resource.deployment.environment',
|
||||
]);
|
||||
|
||||
expect(result).toBe("service.name = 'api-gateway'");
|
||||
});
|
||||
|
||||
it('should remove multiple keys from expression', () => {
|
||||
const expression =
|
||||
"service.name = 'api-gateway' AND status = 'success' AND region = 'us-east-1'";
|
||||
|
||||
@@ -652,7 +652,16 @@ export const removeKeysFromExpression = (
|
||||
}
|
||||
|
||||
function visitComparison(ctx: ComparisonContext): string | null {
|
||||
const keyText = ctx.key().getText().trim().toLowerCase();
|
||||
const field = ctx.field();
|
||||
// The runtime returns null for the inactive field alternative even though
|
||||
// the generated TypeScript signature is non-nullable.
|
||||
const exactCall = field.exactCall() as unknown as ReturnType<
|
||||
typeof field.exactCall
|
||||
> | null;
|
||||
const keyText = (exactCall ? exactCall.key() : field.key())
|
||||
.getText()
|
||||
.trim()
|
||||
.toLowerCase();
|
||||
|
||||
if (!keysSet.has(keyText)) {
|
||||
return src(ctx);
|
||||
|
||||
@@ -5,7 +5,7 @@ import { ArrowUpRight } from '@signozhq/icons';
|
||||
|
||||
const QUICK_FILTER_DOC_PATHS: Record<string, string> = {
|
||||
severity_text: 'severity-text',
|
||||
'deployment.environment': 'environment',
|
||||
'deployment.environment.name': 'environment',
|
||||
'service.name': 'service-name',
|
||||
'host.name': 'hostname',
|
||||
'k8s.cluster.name': 'k8s-cluster-name',
|
||||
|
||||
32
frontend/src/components/Semconv/SemconvEditorWarning.tsx
Normal file
32
frontend/src/components/Semconv/SemconvEditorWarning.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import { Alert } from 'antd';
|
||||
import { findOldSemconvNames } from 'utils/semconv';
|
||||
|
||||
interface SemconvEditorWarningProps {
|
||||
value: unknown;
|
||||
editor: string;
|
||||
}
|
||||
|
||||
function SemconvEditorWarning({
|
||||
value,
|
||||
editor,
|
||||
}: SemconvEditorWarningProps): JSX.Element | null {
|
||||
const text = typeof value === 'string' ? value : JSON.stringify(value ?? '');
|
||||
const renames = findOldSemconvNames(text);
|
||||
if (renames.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Alert
|
||||
type="warning"
|
||||
showIcon
|
||||
data-testid="semconv-editor-warning"
|
||||
message={`${editor} contains renamed OpenTelemetry fields`}
|
||||
description={renames
|
||||
.map(({ old, current }) => `${old} → ${current}`)
|
||||
.join(', ')}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default SemconvEditorWarning;
|
||||
23
frontend/src/components/Semconv/SemconvOldNameBadge.tsx
Normal file
23
frontend/src/components/Semconv/SemconvOldNameBadge.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import { Badge } from '@signozhq/ui/badge';
|
||||
import { getSemconvRename } from 'utils/semconv';
|
||||
|
||||
interface SemconvOldNameBadgeProps {
|
||||
name: string;
|
||||
}
|
||||
|
||||
function SemconvOldNameBadge({
|
||||
name,
|
||||
}: SemconvOldNameBadgeProps): JSX.Element | null {
|
||||
const rename = getSemconvRename(name);
|
||||
if (!rename || rename.family.kind !== 'attribute') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Badge color="amber" variant="outline" data-testid="semconv-old-name-badge">
|
||||
old name, renamed to {rename.current}
|
||||
</Badge>
|
||||
);
|
||||
}
|
||||
|
||||
export default SemconvOldNameBadge;
|
||||
39
frontend/src/components/Semconv/__tests__/Semconv.test.tsx
Normal file
39
frontend/src/components/Semconv/__tests__/Semconv.test.tsx
Normal file
@@ -0,0 +1,39 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
|
||||
import SemconvEditorWarning from '../SemconvEditorWarning';
|
||||
import SemconvOldNameBadge from '../SemconvOldNameBadge';
|
||||
|
||||
describe('semantic convention product hints', () => {
|
||||
it('badges an old raw attribute with its current name', () => {
|
||||
render(<SemconvOldNameBadge name="deployment.environment" />);
|
||||
|
||||
expect(screen.getByTestId('semconv-old-name-badge')).toHaveTextContent(
|
||||
'old name, renamed to deployment.environment.name',
|
||||
);
|
||||
});
|
||||
|
||||
it('does not badge a current raw attribute', () => {
|
||||
render(<SemconvOldNameBadge name="deployment.environment.name" />);
|
||||
|
||||
expect(
|
||||
screen.queryByTestId('semconv-old-name-badge'),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows an informational editor warning without disabling the editor', () => {
|
||||
render(
|
||||
<>
|
||||
<input aria-label="query" defaultValue="db.system = 'postgresql'" />
|
||||
<SemconvEditorWarning
|
||||
value="db.system = 'postgresql'"
|
||||
editor="ClickHouse SQL"
|
||||
/>
|
||||
</>,
|
||||
);
|
||||
|
||||
expect(screen.getByLabelText('query')).not.toBeDisabled();
|
||||
expect(screen.getByTestId('semconv-editor-warning')).toHaveTextContent(
|
||||
'db.system → db.system.name',
|
||||
);
|
||||
});
|
||||
});
|
||||
2
frontend/src/components/Semconv/index.ts
Normal file
2
frontend/src/components/Semconv/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { default as SemconvEditorWarning } from './SemconvEditorWarning';
|
||||
export { default as SemconvOldNameBadge } from './SemconvOldNameBadge';
|
||||
@@ -12,8 +12,8 @@ export type SemconvFamily = {
|
||||
|
||||
export const SEMCONV_FAMILIES: readonly SemconvFamily[] = [
|
||||
{
|
||||
current: 'db.system.name',
|
||||
old: ['db.system'],
|
||||
current: 'code.file.path',
|
||||
old: ['code.filepath'],
|
||||
kind: 'attribute',
|
||||
contexts: [],
|
||||
signals: [],
|
||||
@@ -21,12 +21,215 @@ export const SEMCONV_FAMILIES: readonly SemconvFamily[] = [
|
||||
valueMap: {},
|
||||
},
|
||||
{
|
||||
current: 'deployment.environment.name',
|
||||
old: ['deployment.environment'],
|
||||
current: 'code.function.name',
|
||||
old: ['code.function'],
|
||||
kind: 'attribute',
|
||||
contexts: [],
|
||||
signals: [],
|
||||
applyToMetrics: [],
|
||||
valueMap: {},
|
||||
},
|
||||
{
|
||||
current: 'code.line.number',
|
||||
old: ['code.lineno'],
|
||||
kind: 'attribute',
|
||||
contexts: [],
|
||||
signals: [],
|
||||
applyToMetrics: [],
|
||||
valueMap: {},
|
||||
},
|
||||
{
|
||||
current: 'container.cpu.usage',
|
||||
old: ['container.cpu.utilization'],
|
||||
kind: 'metric',
|
||||
contexts: ['metric'],
|
||||
signals: ['metrics'],
|
||||
applyToMetrics: [],
|
||||
valueMap: {},
|
||||
},
|
||||
{
|
||||
current: 'container.runtime.name',
|
||||
old: ['container.runtime'],
|
||||
kind: 'attribute',
|
||||
contexts: [],
|
||||
signals: [],
|
||||
applyToMetrics: [],
|
||||
valueMap: {},
|
||||
},
|
||||
{
|
||||
current: 'db.namespace',
|
||||
old: [
|
||||
'db.elasticsearch.cluster.name',
|
||||
'db.name',
|
||||
'db.cassandra.keyspace',
|
||||
'db.hbase.namespace',
|
||||
],
|
||||
kind: 'attribute',
|
||||
contexts: ['attribute'],
|
||||
signals: ['traces'],
|
||||
applyToMetrics: [],
|
||||
valueMap: {},
|
||||
},
|
||||
{
|
||||
current: 'db.operation.name',
|
||||
old: ['db.operation'],
|
||||
kind: 'attribute',
|
||||
contexts: ['attribute'],
|
||||
signals: ['traces'],
|
||||
applyToMetrics: [],
|
||||
valueMap: {},
|
||||
},
|
||||
{
|
||||
current: 'db.query.text',
|
||||
old: ['db.statement'],
|
||||
kind: 'attribute',
|
||||
contexts: ['attribute'],
|
||||
signals: ['traces'],
|
||||
applyToMetrics: [],
|
||||
valueMap: {},
|
||||
},
|
||||
{
|
||||
current: 'db.system.name',
|
||||
old: ['db.system'],
|
||||
kind: 'attribute',
|
||||
contexts: ['attribute', 'resource'],
|
||||
signals: ['logs', 'metrics', 'traces'],
|
||||
applyToMetrics: [],
|
||||
valueMap: {},
|
||||
},
|
||||
{
|
||||
current: 'deployment.environment.name',
|
||||
old: ['deployment.environment'],
|
||||
kind: 'attribute',
|
||||
contexts: ['attribute', 'resource'],
|
||||
signals: ['logs', 'metrics', 'traces'],
|
||||
applyToMetrics: [],
|
||||
valueMap: {},
|
||||
},
|
||||
{
|
||||
current: 'http.request.method',
|
||||
old: ['http.method'],
|
||||
kind: 'attribute',
|
||||
contexts: ['attribute'],
|
||||
signals: ['logs', 'traces'],
|
||||
applyToMetrics: [],
|
||||
valueMap: {},
|
||||
},
|
||||
{
|
||||
current: 'http.response.status_code',
|
||||
old: ['http.status_code'],
|
||||
kind: 'attribute',
|
||||
contexts: ['attribute'],
|
||||
signals: ['logs', 'traces'],
|
||||
applyToMetrics: [],
|
||||
valueMap: {},
|
||||
},
|
||||
{
|
||||
current: 'k8s.node.cpu.usage',
|
||||
old: ['k8s.node.cpu.utilization'],
|
||||
kind: 'metric',
|
||||
contexts: ['metric'],
|
||||
signals: ['metrics'],
|
||||
applyToMetrics: [],
|
||||
valueMap: {},
|
||||
},
|
||||
{
|
||||
current: 'k8s.pod.cpu.usage',
|
||||
old: ['k8s.pod.cpu.utilization'],
|
||||
kind: 'metric',
|
||||
contexts: ['metric'],
|
||||
signals: ['metrics'],
|
||||
applyToMetrics: [],
|
||||
valueMap: {},
|
||||
},
|
||||
{
|
||||
current: 'messaging.client.id',
|
||||
old: [
|
||||
'messaging.client_id',
|
||||
'messaging.kafka.client_id',
|
||||
'messaging.rocketmq.client_id',
|
||||
],
|
||||
kind: 'attribute',
|
||||
contexts: ['attribute'],
|
||||
signals: ['metrics', 'traces'],
|
||||
applyToMetrics: [],
|
||||
valueMap: {},
|
||||
},
|
||||
{
|
||||
current: 'messaging.consumer.group.name',
|
||||
old: [
|
||||
'messaging.eventhubs.consumer.group',
|
||||
'messaging.kafka.consumer.group',
|
||||
'messaging.rocketmq.client_group',
|
||||
'messaging.kafka.consumer_group',
|
||||
],
|
||||
kind: 'attribute',
|
||||
contexts: [],
|
||||
signals: [],
|
||||
applyToMetrics: [],
|
||||
valueMap: {},
|
||||
},
|
||||
{
|
||||
current: 'messaging.destination.name',
|
||||
old: ['messaging.destination'],
|
||||
kind: 'attribute',
|
||||
contexts: ['attribute'],
|
||||
signals: ['traces'],
|
||||
applyToMetrics: [],
|
||||
valueMap: {},
|
||||
},
|
||||
{
|
||||
current: 'messaging.operation.type',
|
||||
old: ['messaging.operation'],
|
||||
kind: 'attribute',
|
||||
contexts: ['attribute'],
|
||||
signals: ['traces'],
|
||||
applyToMetrics: [],
|
||||
valueMap: {},
|
||||
},
|
||||
{
|
||||
current: 'rpc.system.name',
|
||||
old: ['rpc.system'],
|
||||
kind: 'attribute',
|
||||
contexts: [],
|
||||
signals: [],
|
||||
applyToMetrics: [],
|
||||
valueMap: {},
|
||||
},
|
||||
{
|
||||
current: 'service.peer.name',
|
||||
old: ['peer.service'],
|
||||
kind: 'attribute',
|
||||
contexts: [],
|
||||
signals: [],
|
||||
applyToMetrics: [],
|
||||
valueMap: {},
|
||||
},
|
||||
{
|
||||
current: 'url.full',
|
||||
old: ['http.url'],
|
||||
kind: 'attribute',
|
||||
contexts: ['attribute'],
|
||||
signals: ['logs', 'traces'],
|
||||
applyToMetrics: [],
|
||||
valueMap: {},
|
||||
},
|
||||
{
|
||||
current: 'url.scheme',
|
||||
old: ['http.scheme'],
|
||||
kind: 'attribute',
|
||||
contexts: ['attribute'],
|
||||
signals: ['logs', 'traces'],
|
||||
applyToMetrics: [],
|
||||
valueMap: {},
|
||||
},
|
||||
{
|
||||
current: 'user_agent.original',
|
||||
old: ['browser.user_agent', 'http.user_agent'],
|
||||
kind: 'attribute',
|
||||
contexts: ['attribute'],
|
||||
signals: ['logs', 'traces'],
|
||||
applyToMetrics: [],
|
||||
valueMap: {},
|
||||
},
|
||||
] as const;
|
||||
|
||||
@@ -29,7 +29,6 @@ function PopoverContent({
|
||||
<Link
|
||||
to={`${ROUTES.LOGS_EXPLORER}?${relatedLogsLink}`}
|
||||
className="contributor-row-popover-buttons__button"
|
||||
data-testid="alert-popover-view-logs"
|
||||
>
|
||||
<div className="icon">
|
||||
<LogsIcon />
|
||||
@@ -41,7 +40,6 @@ function PopoverContent({
|
||||
<Link
|
||||
to={`${ROUTES.TRACES_EXPLORER}?${relatedTracesLink}`}
|
||||
className="contributor-row-popover-buttons__button"
|
||||
data-testid="alert-popover-view-traces"
|
||||
>
|
||||
<div className="icon">
|
||||
<DraftingCompass
|
||||
|
||||
@@ -26,10 +26,7 @@ function ChangePercentage({
|
||||
}: ChangePercentageProps): JSX.Element {
|
||||
if (direction > 0) {
|
||||
return (
|
||||
<div
|
||||
className="change-percentage change-percentage--success"
|
||||
data-testid="stats-card-change"
|
||||
>
|
||||
<div className="change-percentage change-percentage--success">
|
||||
<div className="change-percentage__icon">
|
||||
<ArrowDownLeft size={14} color={Color.BG_FOREST_500} />
|
||||
</div>
|
||||
@@ -41,10 +38,7 @@ function ChangePercentage({
|
||||
}
|
||||
if (direction < 0) {
|
||||
return (
|
||||
<div
|
||||
className="change-percentage change-percentage--error"
|
||||
data-testid="stats-card-change"
|
||||
>
|
||||
<div className="change-percentage change-percentage--error">
|
||||
<div className="change-percentage__icon">
|
||||
<ArrowUpRight size={14} color={Color.BG_CHERRY_500} />
|
||||
</div>
|
||||
@@ -56,10 +50,7 @@ function ChangePercentage({
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className="change-percentage change-percentage--no-previous-data"
|
||||
data-testid="stats-card-change"
|
||||
>
|
||||
<div className="change-percentage change-percentage--no-previous-data">
|
||||
<div className="change-percentage__label">no previous data</div>
|
||||
</div>
|
||||
);
|
||||
@@ -112,12 +103,7 @@ function StatsCard({
|
||||
const formattedEndTimeForTooltip = convertTimestampToLocaleDateString(endTime);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`stats-card ${isEmpty ? 'stats-card--empty' : ''}`}
|
||||
data-testid="stats-card"
|
||||
data-stats-title={title}
|
||||
data-empty={isEmpty ? 'true' : 'false'}
|
||||
>
|
||||
<div className={`stats-card ${isEmpty ? 'stats-card--empty' : ''}`}>
|
||||
<div className="stats-card__title-wrapper">
|
||||
<div className="title">{title}</div>
|
||||
<div className="duration-indicator">
|
||||
@@ -137,7 +123,7 @@ function StatsCard({
|
||||
</div>
|
||||
|
||||
<div className="stats-card__stats">
|
||||
<div className="count-label" data-testid="stats-card-value">
|
||||
<div className="count-label">
|
||||
{isEmpty ? emptyMessage : displayValue || totalCurrentCount}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -81,11 +81,7 @@ function StatsGraph({ timeSeries, changeDirection }: Props): JSX.Element {
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{ height: '100%', width: '100%' }}
|
||||
ref={graphRef}
|
||||
data-testid="stats-card-sparkline"
|
||||
>
|
||||
<div style={{ height: '100%', width: '100%' }} ref={graphRef}>
|
||||
<Uplot data={[xData, yData]} options={options} />
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -48,16 +48,11 @@ function TopContributorsCard({
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="top-contributors-card" data-testid="top-contributors-card">
|
||||
<div className="top-contributors-card">
|
||||
<div className="top-contributors-card__header">
|
||||
<div className="title">top contributors</div>
|
||||
{topContributorsData.length > 3 && (
|
||||
<Button
|
||||
type="text"
|
||||
className="view-all"
|
||||
onClick={toggleViewAllDrawer}
|
||||
data-testid="top-contributors-view-all"
|
||||
>
|
||||
<Button type="text" className="view-all" onClick={toggleViewAllDrawer}>
|
||||
<div className="label">View all</div>
|
||||
<div className="icon">
|
||||
<ArrowRight
|
||||
|
||||
@@ -68,10 +68,7 @@ function TopContributorsRows({
|
||||
relatedTracesLink={record.relatedTracesLink}
|
||||
relatedLogsLink={record.relatedLogsLink}
|
||||
>
|
||||
<div
|
||||
className="total-contribution"
|
||||
data-testid="top-contributors-row-count"
|
||||
>
|
||||
<div className="total-contribution">
|
||||
{count}/{totalCurrentTriggers}
|
||||
</div>
|
||||
</ConditionalAlertPopover>
|
||||
@@ -81,10 +78,7 @@ function TopContributorsRows({
|
||||
|
||||
const handleRowClick = (
|
||||
record: AlertRuleTopContributors,
|
||||
): HTMLAttributes<AlertRuleTimelineTableResponse> & {
|
||||
'data-testid': string;
|
||||
} => ({
|
||||
'data-testid': 'top-contributors-row',
|
||||
): HTMLAttributes<AlertRuleTimelineTableResponse> => ({
|
||||
onClick: (): void => {
|
||||
logEvent('Alert history: Top contributors row: Clicked', {
|
||||
labels: record.labels,
|
||||
|
||||
@@ -31,10 +31,7 @@ function ViewAllDrawer({
|
||||
}}
|
||||
title="Viewing All Contributors"
|
||||
>
|
||||
<div
|
||||
className="top-contributors-card--view-all"
|
||||
data-testid="top-contributors-drawer"
|
||||
>
|
||||
<div className="top-contributors-card--view-all">
|
||||
<div className="top-contributors-card__content">
|
||||
<TopContributorsRows
|
||||
topContributors={topContributorsData}
|
||||
|
||||
@@ -32,8 +32,8 @@ function GraphWrapper({
|
||||
}, [data?.data]);
|
||||
|
||||
return (
|
||||
<div className="timeline-graph" data-testid="timeline-graph">
|
||||
<div className="timeline-graph__title" data-testid="timeline-graph-title">
|
||||
<div className="timeline-graph">
|
||||
<div className="timeline-graph__title">
|
||||
{totalCurrentTriggers} triggers in {relativeTime}
|
||||
</div>
|
||||
<div className="timeline-graph__chart">
|
||||
|
||||
@@ -118,10 +118,7 @@ function TimelineTableContent(): JSX.Element {
|
||||
|
||||
const handleRowClick = (
|
||||
record: AlertRuleTimelineTableResponse,
|
||||
): HTMLAttributes<AlertRuleTimelineTableResponse> & {
|
||||
'data-testid': string;
|
||||
} => ({
|
||||
'data-testid': 'timeline-row',
|
||||
): HTMLAttributes<AlertRuleTimelineTableResponse> => ({
|
||||
onClick: (): void => {
|
||||
void logEvent('Alert history: Timeline table row: Clicked', {
|
||||
ruleId: record.ruleID,
|
||||
@@ -131,15 +128,12 @@ function TimelineTableContent(): JSX.Element {
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="timeline-table" data-testid="timeline-table">
|
||||
<div className="timeline-table">
|
||||
{/* If we don't wait to have the keys, the QuerySearch will not render them at first usage */}
|
||||
{!isLoadingKeys && hardcodedAttributeKeys ? (
|
||||
<div className="timeline-table__filter">
|
||||
<div className="timeline-table__filter-row">
|
||||
<div
|
||||
className="timeline-table__filter-search"
|
||||
data-testid="timeline-filter-search"
|
||||
>
|
||||
<div className="timeline-table__filter-search">
|
||||
<QuerySearch
|
||||
onChange={querySearchOnChange}
|
||||
queryData={queryData}
|
||||
@@ -161,7 +155,6 @@ function TimelineTableContent(): JSX.Element {
|
||||
<Skeleton.Input
|
||||
className="timeline-table__filter--loading-skeleton"
|
||||
active
|
||||
data-testid="timeline-filter-skeleton"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
@@ -179,17 +172,14 @@ function TimelineTableContent(): JSX.Element {
|
||||
locale={{
|
||||
emptyText:
|
||||
isError && apiError ? (
|
||||
<div className="timeline-table__error" data-testid="timeline-error">
|
||||
<div className="timeline-table__error">
|
||||
<ErrorContent error={apiError} />
|
||||
</div>
|
||||
) : undefined,
|
||||
}}
|
||||
footer={(): JSX.Element => (
|
||||
<div className="timeline-table__pagination">
|
||||
<div
|
||||
className="timeline-table__pagination-info"
|
||||
data-testid="timeline-footer-range"
|
||||
>
|
||||
<div className="timeline-table__pagination-info">
|
||||
{paginationConfig.showTotal?.(totalItems, [
|
||||
totalItems === 0
|
||||
? 0
|
||||
|
||||
@@ -21,7 +21,7 @@ export const timelineTableColumns = ({
|
||||
sorter: true,
|
||||
width: 140,
|
||||
render: (value): JSX.Element => (
|
||||
<div className="alert-rule-state" data-testid="timeline-row-state">
|
||||
<div className="alert-rule-state">
|
||||
<AlertState state={value} showLabel />
|
||||
</div>
|
||||
),
|
||||
@@ -30,7 +30,7 @@ export const timelineTableColumns = ({
|
||||
title: 'LABELS',
|
||||
dataIndex: 'labels',
|
||||
render: (labels): JSX.Element => (
|
||||
<div className="alert-rule-labels" data-testid="timeline-row-labels">
|
||||
<div className="alert-rule-labels">
|
||||
<AlertLabels labels={labels} />
|
||||
</div>
|
||||
),
|
||||
@@ -40,10 +40,7 @@ export const timelineTableColumns = ({
|
||||
dataIndex: 'unixMilli',
|
||||
width: 200,
|
||||
render: (value): JSX.Element => (
|
||||
<div
|
||||
className="alert-rule__created-at"
|
||||
data-testid="timeline-row-created-at"
|
||||
>
|
||||
<div className="alert-rule__created-at">
|
||||
{formatTimezoneAdjustedTimestamp(value, DATE_TIME_FORMATS.DASH_DATETIME)}
|
||||
</div>
|
||||
),
|
||||
@@ -56,7 +53,7 @@ export const timelineTableColumns = ({
|
||||
if (!record.relatedTracesLink && !record.relatedLogsLink) {
|
||||
return (
|
||||
<Tooltip title="No links available for this item">
|
||||
<Button type="text" ghost disabled data-testid="timeline-row-actions">
|
||||
<Button type="text" ghost disabled>
|
||||
<Ellipsis className="dropdown-icon" size="md" />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
@@ -68,7 +65,7 @@ export const timelineTableColumns = ({
|
||||
relatedTracesLink={record.relatedTracesLink ?? ''}
|
||||
relatedLogsLink={record.relatedLogsLink ?? ''}
|
||||
>
|
||||
<Button type="text" ghost data-testid="timeline-row-actions">
|
||||
<Button type="text" ghost>
|
||||
<Ellipsis className="dropdown-icon" size="md" />
|
||||
</Button>
|
||||
</ConditionalAlertPopover>
|
||||
|
||||
@@ -23,7 +23,6 @@ function TimelineTabs(): JSX.Element {
|
||||
{
|
||||
value: TimelineTab.OVERALL_STATUS,
|
||||
label: 'Overall Status',
|
||||
testId: 'timeline-tab-overall-status',
|
||||
},
|
||||
{
|
||||
value: TimelineTab.TOP_5_CONTRIBUTORS,
|
||||
@@ -34,7 +33,6 @@ function TimelineTabs(): JSX.Element {
|
||||
</div>
|
||||
),
|
||||
disabled: true,
|
||||
testId: 'timeline-tab-top-contributors',
|
||||
},
|
||||
];
|
||||
|
||||
@@ -59,17 +57,14 @@ function TimelineFilters(): JSX.Element {
|
||||
{
|
||||
value: TimelineFilter.ALL,
|
||||
label: 'All',
|
||||
testId: 'timeline-filter-all',
|
||||
},
|
||||
{
|
||||
value: TimelineFilter.FIRED,
|
||||
label: 'Fired',
|
||||
testId: 'timeline-filter-fired',
|
||||
},
|
||||
{
|
||||
value: TimelineFilter.RESOLVED,
|
||||
label: 'Resolved',
|
||||
testId: 'timeline-filter-resolved',
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -155,7 +155,7 @@ function DomainList(): JSX.Element {
|
||||
dataSource={DataSource.TRACES}
|
||||
queryData={query}
|
||||
onChange={handleSearchChange}
|
||||
placeholder="Enter your filter query (e.g., deployment.environment = 'otel-demo' AND service.name = 'frontend')"
|
||||
placeholder="Enter your filter query (e.g., deployment.environment.name = 'otel-demo' AND service.name = 'frontend')"
|
||||
hardcodedAttributeKeys={ApiMonitoringHardcodedAttributeKeys}
|
||||
/>
|
||||
</div>
|
||||
@@ -180,9 +180,8 @@ function DomainList(): JSX.Element {
|
||||
</div>
|
||||
<div className="no-domain-subtitle">
|
||||
Ensure all HTTP client spans are being sent with kind as{' '}
|
||||
<span className="attribute">Client</span> and url set in{' '}
|
||||
<span className="attribute">url.full</span> or{' '}
|
||||
<span className="attribute">http.url</span> attribute.
|
||||
<span className="attribute">Client</span> and the URL set in the{' '}
|
||||
<span className="attribute">url.full</span> attribute.
|
||||
</div>
|
||||
<a
|
||||
href={DOCLINKS.EXTERNAL_API_MONITORING}
|
||||
|
||||
@@ -6,9 +6,9 @@ import { SPAN_ATTRIBUTES } from './Explorer/Domains/DomainDetails/constants';
|
||||
export const ApiMonitoringHardcodedAttributeKeys: QueryKeyDataSuggestionsProps[] =
|
||||
[
|
||||
{
|
||||
label: 'deployment.environment',
|
||||
label: 'deployment.environment.name',
|
||||
type: 'resource',
|
||||
name: 'deployment.environment',
|
||||
name: 'deployment.environment.name',
|
||||
signal: 'traces',
|
||||
fieldDataType: QUERY_BUILDER_KEY_TYPES.STRING,
|
||||
},
|
||||
|
||||
@@ -87,7 +87,7 @@ export const ApiMonitoringQuickFiltersConfig: IQuickFiltersConfig[] = [
|
||||
title: 'Environment',
|
||||
|
||||
attributeKey: {
|
||||
key: 'deployment.environment',
|
||||
key: 'deployment.environment.name',
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
},
|
||||
|
||||
@@ -34,7 +34,6 @@ function AdvancedOptions(): JSX.Element {
|
||||
})
|
||||
}
|
||||
value={advancedOptions.sendNotificationIfDataIsMissing.toleranceLimit}
|
||||
testId="send-notification-if-data-is-missing-input"
|
||||
/>
|
||||
<Typography.Text>Minutes</Typography.Text>
|
||||
</div>
|
||||
@@ -67,7 +66,6 @@ function AdvancedOptions(): JSX.Element {
|
||||
})
|
||||
}
|
||||
value={advancedOptions.enforceMinimumDatapoints.minimumDatapoints}
|
||||
testId="enforce-minimum-datapoints-input"
|
||||
/>
|
||||
<Typography.Text>Datapoints</Typography.Text>
|
||||
</div>
|
||||
|
||||
@@ -66,7 +66,6 @@ function EvaluationWindowPopover({
|
||||
tabIndex={0}
|
||||
data-value={option.value}
|
||||
data-section-id={sectionId}
|
||||
data-testid={`${sectionId}-option-${option.value}`}
|
||||
onClick={(): void => onChange(option.value)}
|
||||
onKeyDown={(e): void => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
|
||||
@@ -186,7 +186,6 @@ function Footer(): JSX.Element {
|
||||
color="primary"
|
||||
onClick={handleSaveAlert}
|
||||
disabled={disableButtons || Boolean(alertValidationMessage)}
|
||||
testId="save-alert-rule-button"
|
||||
>
|
||||
{isCreatingAlertRule || isUpdatingAlertRule ? (
|
||||
<Loader data-testid="save-alert-rule-loader-icon" size={14} />
|
||||
@@ -219,7 +218,6 @@ function Footer(): JSX.Element {
|
||||
color="secondary"
|
||||
onClick={handleTestNotification}
|
||||
disabled={disableButtons || Boolean(alertValidationMessage)}
|
||||
testId="test-notification-button"
|
||||
>
|
||||
{isTestingAlertRule ? (
|
||||
<Loader data-testid="test-notification-loader-icon" size={14} />
|
||||
@@ -251,7 +249,6 @@ function Footer(): JSX.Element {
|
||||
color="secondary"
|
||||
onClick={handleDiscard}
|
||||
disabled={disableButtons}
|
||||
testId="discard-alert-rule-button"
|
||||
>
|
||||
<X size={14} /> Discard
|
||||
</Button>
|
||||
|
||||
@@ -119,7 +119,6 @@ function BasicInfo({
|
||||
<SeveritySelect
|
||||
getPopupContainer={popupContainer}
|
||||
defaultValue="critical"
|
||||
data-testid="alert-severity-select"
|
||||
onChange={(value: unknown | string): void => {
|
||||
const s = (value as string) || 'critical';
|
||||
setAlertDef({
|
||||
@@ -148,7 +147,6 @@ function BasicInfo({
|
||||
]}
|
||||
>
|
||||
<InputSmall
|
||||
data-testid="alert-name-input-v1"
|
||||
onChange={(e): void => {
|
||||
setAlertDef({
|
||||
...alertDef,
|
||||
@@ -163,7 +161,6 @@ function BasicInfo({
|
||||
name={['annotations', 'description']}
|
||||
>
|
||||
<TextareaMedium
|
||||
data-testid="alert-description-input"
|
||||
onChange={(e): void => {
|
||||
setAlertDef({
|
||||
...alertDef,
|
||||
|
||||
@@ -105,7 +105,7 @@ function QuerySection({
|
||||
{
|
||||
label: (
|
||||
<Tooltip title="Query Builder">
|
||||
<Button className="nav-btns" data-testid="query-builder-tab">
|
||||
<Button className="nav-btns">
|
||||
<Atom size={14} />
|
||||
<Typography.Text>Query Builder</Typography.Text>
|
||||
</Button>
|
||||
@@ -122,11 +122,7 @@ function QuerySection({
|
||||
: 'ClickHouse'
|
||||
}
|
||||
>
|
||||
<Button
|
||||
className="nav-btns"
|
||||
disabled={isAnomalyDetection}
|
||||
data-testid="clickhouse-tab"
|
||||
>
|
||||
<Button className="nav-btns" disabled={isAnomalyDetection}>
|
||||
<Terminal size={14} />
|
||||
<Typography.Text>ClickHouse Query</Typography.Text>
|
||||
</Button>
|
||||
@@ -166,11 +162,7 @@ function QuerySection({
|
||||
: 'ClickHouse'
|
||||
}
|
||||
>
|
||||
<Button
|
||||
className="nav-btns"
|
||||
disabled={isAnomalyDetection}
|
||||
data-testid="clickhouse-tab"
|
||||
>
|
||||
<Button className="nav-btns" disabled={isAnomalyDetection}>
|
||||
<Terminal size={14} />
|
||||
<Typography.Text>ClickHouse Query</Typography.Text>
|
||||
</Button>
|
||||
@@ -188,11 +180,7 @@ function QuerySection({
|
||||
: 'PromQL'
|
||||
}
|
||||
>
|
||||
<Button
|
||||
className="nav-btns"
|
||||
disabled={isAnomalyDetection}
|
||||
data-testid="promql-tab"
|
||||
>
|
||||
<Button className="nav-btns" disabled={isAnomalyDetection}>
|
||||
<PromQLIcon
|
||||
fillColor={isDarkMode ? Color.BG_VANILLA_200 : Color.BG_INK_300}
|
||||
/>
|
||||
|
||||
@@ -80,7 +80,6 @@ function RuleOptions({
|
||||
defaultValue={defaultCompareOp}
|
||||
value={alertDef.condition?.op}
|
||||
style={{ minWidth: '120px' }}
|
||||
data-testid="alert-threshold-op-select"
|
||||
onChange={(value: string | unknown): void => {
|
||||
const newOp = (value as string) || '';
|
||||
|
||||
@@ -117,7 +116,6 @@ function RuleOptions({
|
||||
defaultValue={defaultMatchType}
|
||||
style={{ minWidth: '130px' }}
|
||||
value={alertDef.condition?.matchType}
|
||||
data-testid="alert-threshold-match-type-select-v1"
|
||||
onChange={(value: string | unknown): void => handleMatchOptChange(value)}
|
||||
>
|
||||
<Select.Option value="1">{t('option_atleastonce')}</Select.Option>
|
||||
@@ -179,7 +177,6 @@ function RuleOptions({
|
||||
style={{ minWidth: '120px' }}
|
||||
value={alertDef.evalWindow}
|
||||
onChange={onChangeEvalWindow}
|
||||
data-testid="alert-eval-window-select"
|
||||
>
|
||||
<Select.Option value="5m0s">{t('option_5min')}</Select.Option>
|
||||
<Select.Option value="10m0s">{t('option_10min')}</Select.Option>
|
||||
@@ -197,7 +194,6 @@ function RuleOptions({
|
||||
style={{ minWidth: '120px' }}
|
||||
value={alertDef.evalWindow}
|
||||
onChange={onChangeEvalWindow}
|
||||
data-testid="alert-eval-window-select"
|
||||
>
|
||||
<Select.Option value="5m0s">{t('option_5min')}</Select.Option>
|
||||
<Select.Option value="10m0s">{t('option_10min')}</Select.Option>
|
||||
@@ -399,7 +395,6 @@ function RuleOptions({
|
||||
value={alertDef?.condition?.target}
|
||||
onChange={onChange}
|
||||
type="number"
|
||||
data-testid="alert-threshold-target-input"
|
||||
onWheel={(e): void => e.currentTarget.blur()}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
@@ -844,6 +844,8 @@ function FormAlertRules({
|
||||
|
||||
return (
|
||||
<>
|
||||
{Element}
|
||||
|
||||
<div
|
||||
id="top"
|
||||
className={`form-alert-rules-container ${
|
||||
@@ -966,7 +968,6 @@ function FormAlertRules({
|
||||
!isChannelConfigurationValid ||
|
||||
queryStatus === 'error'
|
||||
}
|
||||
data-testid="alert-save-button"
|
||||
>
|
||||
{isNewRule ? t('button_createrule') : t('button_savechanges')}
|
||||
</ActionButton>
|
||||
@@ -980,7 +981,6 @@ function FormAlertRules({
|
||||
}
|
||||
type="default"
|
||||
onClick={onTestRuleHandler}
|
||||
data-testid="alert-test-button"
|
||||
>
|
||||
{' '}
|
||||
{t('button_testrule')}
|
||||
@@ -989,7 +989,6 @@ function FormAlertRules({
|
||||
disabled={loading || false}
|
||||
type="default"
|
||||
onClick={onCancelHandler}
|
||||
data-testid="alert-cancel-button"
|
||||
>
|
||||
{isNewRule && t('button_cancelchanges')}
|
||||
{ruleId && !isEmpty(ruleId) && t('button_discard')}
|
||||
@@ -999,7 +998,6 @@ function FormAlertRules({
|
||||
</div>
|
||||
|
||||
<ConfirmDialog
|
||||
testId="alert-save-confirm-dialog"
|
||||
open={isConfirmSaveOpen}
|
||||
onOpenChange={setIsConfirmSaveOpen}
|
||||
title={t('confirm_save_title')}
|
||||
|
||||
@@ -174,7 +174,6 @@ function LabelSelect({
|
||||
|
||||
<div style={{ display: 'flex', width: '100%' }}>
|
||||
<Input
|
||||
data-testid="alert-labels-input-v1"
|
||||
placeholder={renderPlaceholder()}
|
||||
onChange={handleLabelChange}
|
||||
onKeyUp={(e): void => {
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
import { RowData } from 'lib/query/createTableColumnsFromQuery';
|
||||
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { FormatTimezoneAdjustedTimestamp } from 'hooks/useTimezoneFormatter/useTimezoneFormatter';
|
||||
import { getSemconvMembers } from 'utils/semconv';
|
||||
import styles from './traceListColumns.module.scss';
|
||||
|
||||
const keyToLabelMap: Record<string, string> = {
|
||||
@@ -27,7 +28,12 @@ const keyToLabelMap: Record<string, string> = {
|
||||
const keyAliases: Record<string, string[]> = {
|
||||
serviceName: ['serviceName', 'service.name', 'service_name'],
|
||||
durationNano: ['durationNano', 'duration.nano', 'duration_nano'],
|
||||
httpMethod: ['httpMethod', 'http.method', 'http_method'],
|
||||
httpMethod: [
|
||||
'httpMethod',
|
||||
...getSemconvMembers('http.request.method'),
|
||||
'http_request_method',
|
||||
'http_method',
|
||||
],
|
||||
responseStatusCode: [
|
||||
'response_status_code',
|
||||
'response.status.code',
|
||||
|
||||
@@ -112,7 +112,7 @@ export const INFRA_MONITORING_ATTR_KEYS = {
|
||||
K8S_OBJECT_NAME: 'k8s.object.name',
|
||||
|
||||
// Environment
|
||||
DEPLOYMENT_ENVIRONMENT: 'deployment.environment',
|
||||
DEPLOYMENT_ENVIRONMENT: 'deployment.environment.name',
|
||||
|
||||
// Host System
|
||||
OS_TYPE: 'os.type',
|
||||
@@ -733,7 +733,7 @@ export const ENTITY_FILTER_PLACEHOLDERS: Record<InfraMonitoringEntity, string> =
|
||||
[InfraMonitoringEntity.NAMESPACES]:
|
||||
"Enter your filter query (e.g., k8s.namespace.name = 'production' AND k8s.cluster.name = 'prod-cluster')",
|
||||
[InfraMonitoringEntity.CLUSTERS]:
|
||||
"Enter your filter query (e.g., k8s.cluster.name = 'prod-cluster' AND deployment.environment = 'production')",
|
||||
"Enter your filter query (e.g., k8s.cluster.name = 'prod-cluster' AND deployment.environment.name = 'production')",
|
||||
[InfraMonitoringEntity.DEPLOYMENTS]:
|
||||
"Enter your filter query (e.g., k8s.deployment.name = 'api-server' AND k8s.namespace.name = 'production')",
|
||||
[InfraMonitoringEntity.STATEFULSETS]:
|
||||
|
||||
@@ -2,6 +2,17 @@
|
||||
color: white;
|
||||
}
|
||||
|
||||
.semconv-migration-report {
|
||||
margin-top: 32px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
|
||||
.ant-table-wrapper {
|
||||
margin-top: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.ingestion-key-container {
|
||||
margin-top: 24px;
|
||||
display: flex;
|
||||
|
||||
@@ -5,6 +5,8 @@ import getIngestionData from 'api/settings/getIngestionData';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
import { IngestionDataType } from 'types/api/settings/ingestion';
|
||||
|
||||
import SemconvMigrationReport from './SemconvMigrationReport';
|
||||
|
||||
import './IngestionSettings.styles.scss';
|
||||
|
||||
export default function IngestionSettings(): JSX.Element {
|
||||
@@ -84,6 +86,7 @@ export default function IngestionSettings(): JSX.Element {
|
||||
dataSource={data}
|
||||
bordered
|
||||
/>
|
||||
<SemconvMigrationReport />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -83,6 +83,8 @@ import { MeterAggregateOperator } from 'types/common/queryBuilder';
|
||||
import { USER_ROLES } from 'types/roles';
|
||||
import { getDaysUntilExpiry } from 'utils/timeUtils';
|
||||
|
||||
import SemconvMigrationReport from './SemconvMigrationReport';
|
||||
|
||||
import './IngestionSettings.styles.scss';
|
||||
|
||||
const { Option } = Select;
|
||||
@@ -1705,6 +1707,7 @@ function MultiIngestionSettings(): JSX.Element {
|
||||
}}
|
||||
className="ingestion-keys-table"
|
||||
/>
|
||||
<SemconvMigrationReport />
|
||||
</div>
|
||||
|
||||
{/* Delete Key Modal */}
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
import { useQuery } from 'react-query';
|
||||
import { Alert, Table, TableColumnsType } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import getSemconvMigrationReport from 'api/semconv/getMigrationReport';
|
||||
import dayjs from 'dayjs';
|
||||
import { SemconvMigrationReportEntry } from 'types/api/semconvMigration';
|
||||
|
||||
function SemconvMigrationReport(): JSX.Element {
|
||||
const { data, isLoading, isError } = useQuery({
|
||||
queryKey: ['semconv-migration-report'],
|
||||
queryFn: getSemconvMigrationReport,
|
||||
});
|
||||
|
||||
const columns: TableColumnsType<SemconvMigrationReportEntry> = [
|
||||
{
|
||||
title: 'Old name',
|
||||
dataIndex: 'old',
|
||||
key: 'old',
|
||||
},
|
||||
{
|
||||
title: 'Current name',
|
||||
dataIndex: 'current',
|
||||
key: 'current',
|
||||
},
|
||||
{
|
||||
title: 'Signal',
|
||||
dataIndex: 'signal',
|
||||
key: 'signal',
|
||||
},
|
||||
{
|
||||
title: 'Services still sending only the old name',
|
||||
dataIndex: 'services',
|
||||
key: 'services',
|
||||
render: (services: string[]): string => services.join(', '),
|
||||
},
|
||||
{
|
||||
title: 'Last seen',
|
||||
dataIndex: 'lastSeenUnixMilli',
|
||||
key: 'lastSeenUnixMilli',
|
||||
render: (value: number): string =>
|
||||
dayjs(value).format('YYYY-MM-DD HH:mm:ss'),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<section className="semconv-migration-report">
|
||||
<Typography.Title level={4}>Semantic convention migration</Typography.Title>
|
||||
<Typography.Text>
|
||||
Services in this report sent an old OpenTelemetry field during the last 24
|
||||
hours without sending its current replacement. Update their SDK or
|
||||
instrumentation when practical; SigNoz queries remain backward compatible.
|
||||
</Typography.Text>
|
||||
{isError && (
|
||||
<Alert
|
||||
type="error"
|
||||
showIcon
|
||||
message="Could not load the semantic convention migration report"
|
||||
/>
|
||||
)}
|
||||
<Table
|
||||
loading={isLoading}
|
||||
columns={columns}
|
||||
dataSource={data?.entries ?? []}
|
||||
rowKey={(entry): string => `${entry.current}-${entry.old}-${entry.signal}`}
|
||||
pagination={false}
|
||||
locale={{ emptyText: 'No old-only services found in the last 24 hours' }}
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
export default SemconvMigrationReport;
|
||||
@@ -15,7 +15,7 @@ export const SAMPLE_SPAN_JSON = `{
|
||||
},
|
||||
"resource": {
|
||||
"service.name": "llm-gateway",
|
||||
"deployment.environment": "production"
|
||||
"deployment.environment.name": "production"
|
||||
}
|
||||
}`;
|
||||
|
||||
|
||||
@@ -1120,7 +1120,7 @@
|
||||
"plugin": {
|
||||
"kind": "signoz/QueryVariable",
|
||||
"spec": {
|
||||
"queryValue": "SELECT DISTINCT resources_string['deployment.environment'] AS environment FROM signoz_traces.distributed_signoz_index_v3 WHERE mapContains(resources_string, 'deployment.environment') AND timestamp >= now() - INTERVAL 1 DAY"
|
||||
"queryValue": "SELECT DISTINCT resources_string['deployment.environment.name'] AS environment FROM signoz_traces.distributed_signoz_index_v3 WHERE mapContains(resources_string, 'deployment.environment.name') AND timestamp >= now() - INTERVAL 1 DAY"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Divider } from '@signozhq/ui/divider';
|
||||
import { TooltipSimple } from '@signozhq/ui/tooltip';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import { SemconvOldNameBadge } from 'components/Semconv';
|
||||
|
||||
import { TagContainer, TagLabel, TagValue } from './FieldRenderer.styles';
|
||||
import { FieldRendererProps } from './LogDetailedView.types';
|
||||
@@ -28,6 +29,7 @@ function FieldRenderer({ field }: FieldRendererProps): JSX.Element {
|
||||
<Typography.Text truncate={1} className="label">
|
||||
{newField}{' '}
|
||||
</Typography.Text>
|
||||
<SemconvOldNameBadge name={newField} />
|
||||
</TooltipSimple>
|
||||
|
||||
<div className="tags">
|
||||
@@ -47,7 +49,10 @@ function FieldRenderer({ field }: FieldRendererProps): JSX.Element {
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<span className="label">{field}</span>
|
||||
<>
|
||||
<span className="label">{field}</span>
|
||||
<SemconvOldNameBadge name={field} />
|
||||
</>
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
|
||||
@@ -164,7 +164,9 @@ describe('useInitialQuery - Priority-Based Resource Filtering', () => {
|
||||
value: 'frontend-service',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
key: expect.objectContaining({ key: 'deployment.environment' }),
|
||||
key: expect.objectContaining({
|
||||
key: 'deployment.environment.name',
|
||||
}),
|
||||
value: 'production',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
@@ -286,7 +288,9 @@ describe('useInitialQuery - Priority-Based Resource Filtering', () => {
|
||||
value: 'legacy-app',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
key: expect.objectContaining({ key: 'deployment.environment' }),
|
||||
key: expect.objectContaining({
|
||||
key: 'deployment.environment.name',
|
||||
}),
|
||||
value: 'production',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
|
||||
@@ -6,13 +6,14 @@ import {
|
||||
TagFilterItem,
|
||||
} from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { getSemconvRename } from 'utils/semconv';
|
||||
|
||||
const FALLBACK_STARTS_WITH_REGEX = /^(k8s|cloud|host|deployment)/; // regex to filter out resources that start with the specified keywords
|
||||
const FALLBACK_CONTAINS_REGEX = /(env|service|file|container|tenant)/; // regex to filter out resources that contains the specified keywords
|
||||
|
||||
// Priority categories for filter selection
|
||||
// Strategy:
|
||||
// - Always include: service.name, deployment.environment, env, environment
|
||||
// - Always include: service.name, deployment.environment.name, env, environment
|
||||
// - Select ONE category only: stops at the first category with a matching attribute
|
||||
// - Within category: picks the first available attribute by order
|
||||
// - Order (highest to lowest priority): Kubernetes > Cloud > Host > Container
|
||||
@@ -26,27 +27,36 @@ const PRIORITY_CATEGORIES = [
|
||||
|
||||
const SERVICE_AND_ENVIRONMENT_KEYS = [
|
||||
'service.name',
|
||||
'deployment.environment',
|
||||
'deployment.environment.name',
|
||||
'env',
|
||||
'environment',
|
||||
];
|
||||
|
||||
export const getFiltersFromResources = (
|
||||
resources: ILog['resources_string'],
|
||||
): TagFilterItem[] =>
|
||||
Object.keys(resources).map((key: string) => {
|
||||
): TagFilterItem[] => {
|
||||
const items = new Map<string, TagFilterItem>();
|
||||
Object.keys(resources).forEach((key: string) => {
|
||||
const currentKey = getSemconvRename(key)?.current ?? key;
|
||||
const resourceValue = resources[key] as string;
|
||||
return {
|
||||
const item = {
|
||||
id: uuid(),
|
||||
key: {
|
||||
key,
|
||||
key: currentKey,
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
},
|
||||
op: OPERATORS['='],
|
||||
value: resourceValue,
|
||||
};
|
||||
// If raw data contains both names, retain the current value just like the
|
||||
// backend's current-first resolver.
|
||||
if (!items.has(currentKey) || key === currentKey) {
|
||||
items.set(currentKey, item);
|
||||
}
|
||||
});
|
||||
return Array.from(items.values());
|
||||
};
|
||||
|
||||
export const isServiceOrEnvironmentAttribute = (key: string): boolean =>
|
||||
SERVICE_AND_ENVIRONMENT_KEYS.includes(key);
|
||||
|
||||
@@ -94,7 +94,7 @@ function DBCall(): JSX.Element {
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||
?.active || false;
|
||||
|
||||
const legend = dotMetricsEnabled ? '{{db.system}}' : '{{db_system}}';
|
||||
const legend = dotMetricsEnabled ? '{{db.system.name}}' : '{{db_system_name}}';
|
||||
|
||||
const databaseCallsRPSWidget = useMemo(
|
||||
() =>
|
||||
|
||||
@@ -28,7 +28,7 @@ import { v4 as uuid } from 'uuid';
|
||||
|
||||
export const dbSystemTags: Tags[] = [
|
||||
{
|
||||
Key: 'db.system.(string)',
|
||||
Key: 'db.system.name.(string)',
|
||||
StringValues: [''],
|
||||
NumberValues: [],
|
||||
BoolValues: [],
|
||||
|
||||
@@ -103,7 +103,7 @@ export enum WidgetKeys {
|
||||
SignozExternalCallLatencySum = 'signoz_external_call_latency_sum',
|
||||
Signoz_latency_bucket_norm = 'signoz_latency_bucket',
|
||||
Signoz_latency_bucket = 'signoz_latency.bucket',
|
||||
Db_system = 'db.system',
|
||||
Db_system = 'db.system.name',
|
||||
Db_system_norm = 'db_system',
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { ChangeEvent, useCallback } from 'react';
|
||||
import MEditor, { Monaco } from '@monaco-editor/react';
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Input } from 'antd';
|
||||
import { SemconvEditorWarning } from 'components/Semconv';
|
||||
import { LEGEND } from 'constants/global';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
@@ -118,6 +119,7 @@ function ClickHouseQueryBuilder({
|
||||
theme={isDarkMode ? 'my-theme' : 'light'}
|
||||
beforeMount={setEditorTheme}
|
||||
/>
|
||||
<SemconvEditorWarning value={queryData?.query} editor="ClickHouse SQL" />
|
||||
<Input
|
||||
onChange={handleUpdateInput}
|
||||
name="legend"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ChangeEvent, useCallback } from 'react';
|
||||
import { Input } from 'antd';
|
||||
import { LEGEND } from 'constants/global';
|
||||
import { SemconvEditorWarning } from 'components/Semconv';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { IPromQLQuery } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
@@ -66,6 +67,7 @@ function PromQLQueryBuilder({
|
||||
style={{ marginBottom: '0.5rem' }}
|
||||
data-testid="promql-query-input"
|
||||
/>
|
||||
<SemconvEditorWarning value={queryData?.query} editor="PromQL" />
|
||||
|
||||
<Input
|
||||
onChange={handleUpdateQuery}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Form } from 'antd';
|
||||
import { initialQueryBuilderFormValuesMap } from 'constants/queryBuilder';
|
||||
import { SemconvEditorWarning } from 'components/Semconv';
|
||||
import QueryBuilderSearchV2 from 'container/QueryBuilder/filters/QueryBuilderSearchV2/QueryBuilderSearchV2';
|
||||
import isEqual from 'lodash-es/isEqual';
|
||||
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
|
||||
@@ -55,6 +56,7 @@ function TagFilterInputWithLogsResultPreview({
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
/>
|
||||
<SemconvEditorWarning value={value} editor="Pipeline filter" />
|
||||
<div className="pipeline-filter-input-preview-container">
|
||||
<LogsFilterPreview filter={value} />
|
||||
</div>
|
||||
|
||||
@@ -424,7 +424,7 @@ describe('ResourceProvider', () => {
|
||||
await waitFor(() => {
|
||||
expect(result.current.queries).toHaveLength(1);
|
||||
expect(result.current.queries[0]).toMatchObject({
|
||||
tagKey: 'resource_deployment_environment',
|
||||
tagKey: 'resource_deployment_environment_name',
|
||||
operator: 'IN',
|
||||
tagValue: ['production'],
|
||||
});
|
||||
@@ -435,7 +435,7 @@ describe('ResourceProvider', () => {
|
||||
const seeded = [
|
||||
{
|
||||
id: 'env',
|
||||
tagKey: 'resource_deployment_environment',
|
||||
tagKey: 'resource_deployment_environment_name',
|
||||
operator: 'IN',
|
||||
tagValue: ['production'],
|
||||
},
|
||||
@@ -459,7 +459,7 @@ describe('ResourceProvider', () => {
|
||||
|
||||
await waitFor(() => {
|
||||
const tagKeys = result.current.queries.map((q) => q.tagKey);
|
||||
expect(tagKeys).not.toContain('resource_deployment_environment');
|
||||
expect(tagKeys).not.toContain('resource_deployment_environment_name');
|
||||
expect(tagKeys).toContain('resource_service_name');
|
||||
});
|
||||
});
|
||||
@@ -468,7 +468,7 @@ describe('ResourceProvider', () => {
|
||||
const seeded = [
|
||||
{
|
||||
id: 'env',
|
||||
tagKey: 'resource_deployment_environment',
|
||||
tagKey: 'resource_deployment_environment_name',
|
||||
operator: 'IN',
|
||||
tagValue: ['production'],
|
||||
},
|
||||
@@ -486,7 +486,7 @@ describe('ResourceProvider', () => {
|
||||
|
||||
await waitFor(() => {
|
||||
const envQueries = result.current.queries.filter(
|
||||
(q) => q.tagKey === 'resource_deployment_environment',
|
||||
(q) => q.tagKey === 'resource_deployment_environment_name',
|
||||
);
|
||||
expect(envQueries).toHaveLength(1);
|
||||
expect(envQueries[0].tagValue).toStrictEqual(['staging']);
|
||||
@@ -518,7 +518,7 @@ describe('ResourceProvider', () => {
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.queries[0].tagKey).toBe(
|
||||
'resource_deployment.environment',
|
||||
'resource_deployment.environment.name',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,13 +6,13 @@ import { mappingWithRoutesAndKeys } from '../utils';
|
||||
describe('useResourceAttribute config', () => {
|
||||
describe('whilelistedKeys', () => {
|
||||
it('should include underscore-notation keys (DOT_METRICS_ENABLED=false)', () => {
|
||||
expect(whilelistedKeys).toContain('resource_deployment_environment');
|
||||
expect(whilelistedKeys).toContain('resource_deployment_environment_name');
|
||||
expect(whilelistedKeys).toContain('resource_k8s_cluster_name');
|
||||
expect(whilelistedKeys).toContain('resource_k8s_cluster_namespace');
|
||||
});
|
||||
|
||||
it('should include dot-notation keys (DOT_METRICS_ENABLED=true)', () => {
|
||||
expect(whilelistedKeys).toContain('resource_deployment.environment');
|
||||
expect(whilelistedKeys).toContain('resource_deployment.environment.name');
|
||||
expect(whilelistedKeys).toContain('resource_k8s.cluster.name');
|
||||
expect(whilelistedKeys).toContain('resource_k8s.cluster.namespace');
|
||||
});
|
||||
@@ -21,8 +21,8 @@ describe('useResourceAttribute config', () => {
|
||||
describe('mappingWithRoutesAndKeys', () => {
|
||||
const dotNotationFilters = [
|
||||
{
|
||||
label: 'deployment.environment',
|
||||
value: 'resource_deployment.environment',
|
||||
label: 'deployment.environment.name',
|
||||
value: 'resource_deployment.environment.name',
|
||||
},
|
||||
{ label: 'k8s.cluster.name', value: 'resource_k8s.cluster.name' },
|
||||
{ label: 'k8s.cluster.namespace', value: 'resource_k8s.cluster.namespace' },
|
||||
@@ -30,8 +30,8 @@ describe('useResourceAttribute config', () => {
|
||||
|
||||
const underscoreNotationFilters = [
|
||||
{
|
||||
label: 'deployment.environment',
|
||||
value: 'resource_deployment_environment',
|
||||
label: 'deployment.environment.name',
|
||||
value: 'resource_deployment_environment_name',
|
||||
},
|
||||
{ label: 'k8s.cluster.name', value: 'resource_k8s_cluster_name' },
|
||||
{ label: 'k8s.cluster.namespace', value: 'resource_k8s_cluster_namespace' },
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
export const whilelistedKeys = [
|
||||
'resource_deployment_environment',
|
||||
'resource_deployment.environment',
|
||||
'resource_deployment_environment_name',
|
||||
'resource_deployment.environment.name',
|
||||
'resource_k8s_cluster_name',
|
||||
'resource_k8s.cluster.name',
|
||||
'resource_k8s_cluster_namespace',
|
||||
|
||||
@@ -148,9 +148,9 @@ export const getResourceDeploymentKeys = (
|
||||
dotMetricsEnabled: boolean,
|
||||
): string => {
|
||||
if (dotMetricsEnabled) {
|
||||
return 'resource_deployment.environment';
|
||||
return 'resource_deployment.environment.name';
|
||||
}
|
||||
return 'resource_deployment_environment';
|
||||
return 'resource_deployment_environment_name';
|
||||
};
|
||||
|
||||
export const GetTagKeys = async (
|
||||
|
||||
@@ -94,8 +94,6 @@ function AlertDetails(): JSX.Element {
|
||||
>
|
||||
<div
|
||||
className={classNames('alert-details', { 'alert-details-v2': isV2Alert })}
|
||||
data-testid="alert-details-root"
|
||||
data-schema-version={isV2Alert ? NEW_ALERT_SCHEMA_VERSION : 'v1'}
|
||||
>
|
||||
<AlertBreadcrumb
|
||||
className="alert-details__breadcrumb"
|
||||
|
||||
@@ -117,11 +117,7 @@ function AlertActionButtons({
|
||||
<div className="alert-action-buttons">
|
||||
<Tooltip title={isAlertRuleDisabled ? 'Enable alert' : 'Disable alert'}>
|
||||
{isAlertRuleDisabled !== undefined && (
|
||||
<Switch
|
||||
onChange={toggleAlertRule}
|
||||
value={!isAlertRuleDisabled}
|
||||
testId="alert-actions-toggle"
|
||||
/>
|
||||
<Switch onChange={toggleAlertRule} value={!isAlertRuleDisabled} />
|
||||
)}
|
||||
</Tooltip>
|
||||
<CopyToClipboard textToCopy={window.location.href} />
|
||||
@@ -133,7 +129,6 @@ function AlertActionButtons({
|
||||
<Tooltip title="More options">
|
||||
<Button
|
||||
type="text"
|
||||
data-testid="alert-actions-menu"
|
||||
icon={
|
||||
<Ellipsis
|
||||
size={16}
|
||||
|
||||
@@ -47,29 +47,21 @@ function AlertHeader({ alertDetails }: AlertHeaderProps): JSX.Element {
|
||||
<div className="alert-info__info-wrapper">
|
||||
<div className="top-section">
|
||||
<div className="alert-title-wrapper">
|
||||
<div data-testid="alert-header-state">
|
||||
<AlertState state={alertRuleState ?? state ?? ''} />
|
||||
</div>
|
||||
<div className="alert-title" data-testid="alert-header-title">
|
||||
<AlertState state={alertRuleState ?? state ?? ''} />
|
||||
<div className="alert-title">
|
||||
<LineClampedText text={displayName || ''} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bottom-section">
|
||||
{labels?.severity && (
|
||||
<div data-testid="alert-header-severity">
|
||||
<AlertSeverity severity={labels.severity} />
|
||||
</div>
|
||||
)}
|
||||
{labels?.severity && <AlertSeverity severity={labels.severity} />}
|
||||
|
||||
{/* // TODO(shaheer): Get actual data when we are able to get alert firing from state from API */}
|
||||
{/* <AlertStatus
|
||||
status="firing"
|
||||
timestamp={dayjs().subtract(1, 'd').valueOf()}
|
||||
/> */}
|
||||
<div data-testid="alert-header-labels">
|
||||
<AlertLabels labels={labelsWithoutSeverity} />
|
||||
</div>
|
||||
<AlertLabels labels={labelsWithoutSeverity} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -127,7 +127,7 @@ export const useRouteTabUtils = (): { routes: TabRoutes[] } => {
|
||||
{
|
||||
Component: EditRules,
|
||||
name: (
|
||||
<div className="tab-item" data-testid="alert-details-tab-overview">
|
||||
<div className="tab-item">
|
||||
<Table size={14} />
|
||||
Overview
|
||||
</div>
|
||||
@@ -138,7 +138,7 @@ export const useRouteTabUtils = (): { routes: TabRoutes[] } => {
|
||||
{
|
||||
Component: AlertHistory,
|
||||
name: (
|
||||
<div className="tab-item" data-testid="alert-details-tab-history">
|
||||
<div className="tab-item">
|
||||
<History size={14} />
|
||||
History
|
||||
<BetaTag />
|
||||
|
||||
@@ -35,13 +35,7 @@ export default function CeleryOverviewDetails({
|
||||
? undefined
|
||||
: getFiltersFromKeyValue('messaging.system', value, 'tag');
|
||||
case 'destination':
|
||||
return getFiltersFromKeyValue(
|
||||
details.messaging_system === 'celery'
|
||||
? 'messaging.destination'
|
||||
: 'messaging.destination.name',
|
||||
value,
|
||||
'tag',
|
||||
);
|
||||
return getFiltersFromKeyValue('messaging.destination.name', value, 'tag');
|
||||
case 'kind_string':
|
||||
return getFiltersFromKeyValue('kind_string', value, '');
|
||||
default:
|
||||
|
||||
@@ -233,7 +233,7 @@ describe('Logs Explorer Tests', () => {
|
||||
);
|
||||
|
||||
const queries = queryAllByText(
|
||||
"Enter your filter query (e.g., http.status_code >= 500 AND service.name = 'frontend')",
|
||||
"Enter your filter query (e.g., http.response.status_code >= 500 AND service.name = 'frontend')",
|
||||
);
|
||||
expect(queries).toHaveLength(1);
|
||||
});
|
||||
|
||||
@@ -40,7 +40,7 @@ export const LogsQuickFiltersConfig: IQuickFiltersConfig[] = [
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'Environment',
|
||||
attributeKey: {
|
||||
key: 'deployment.environment',
|
||||
key: 'deployment.environment.name',
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
},
|
||||
|
||||
@@ -11,6 +11,7 @@ import { Skeleton } from 'antd';
|
||||
import { DetailsHeader, DetailsPanelDrawer } from 'components/DetailsPanel';
|
||||
import { HeaderAction } from 'components/DetailsPanel/DetailsHeader/DetailsHeader';
|
||||
import { DetailsPanelState } from 'components/DetailsPanel/types';
|
||||
import { SemconvOldNameBadge } from 'components/Semconv';
|
||||
import { QueryParams } from 'constants/query';
|
||||
import {
|
||||
initialQueryBuilderFormValuesMap,
|
||||
@@ -108,6 +109,12 @@ function SpanDetailsContent({
|
||||
() => getSpanDisplayData(selectedSpan),
|
||||
[selectedSpan],
|
||||
);
|
||||
const semconvLabelSuffix = useCallback(
|
||||
(fieldKey: string): React.ReactNode => (
|
||||
<SemconvOldNameBadge name={fieldKey} />
|
||||
),
|
||||
[],
|
||||
);
|
||||
|
||||
// Map span attribute actions to PrettyView actions format.
|
||||
// Use the last key in fieldKeyPath (the actual attribute key), not the full display path.
|
||||
@@ -329,6 +336,7 @@ function SpanDetailsContent({
|
||||
visibleActions: VISIBLE_ACTIONS,
|
||||
pinnedFieldsValue,
|
||||
onPinnedFieldsChange,
|
||||
labelSuffixRenderer: semconvLabelSuffix,
|
||||
}}
|
||||
/>
|
||||
</TabsContent>
|
||||
|
||||
@@ -29,7 +29,7 @@ export const KEY_ATTRIBUTE_KEYS: Record<string, string[]> = {
|
||||
traces: [
|
||||
'service.name',
|
||||
'service.namespace',
|
||||
'deployment.environment',
|
||||
'deployment.environment.name',
|
||||
'timestamp',
|
||||
'duration_nano',
|
||||
'kind_string',
|
||||
|
||||
@@ -359,7 +359,7 @@ function Filters({
|
||||
onChange={handleExpressionChange}
|
||||
onRun={handleRunQuery}
|
||||
dataSource={DataSource.TRACES}
|
||||
placeholder="Enter your filter query (e.g., http.status_code >= 500 AND service.name = 'frontend')"
|
||||
placeholder="Enter your filter query (e.g., http.response.status_code >= 500 AND service.name = 'frontend')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -22,8 +22,8 @@ export const SPAN_CATEGORIES: readonly SpanCategory[] = [
|
||||
|
||||
// Map each category to the attribute key it filters on
|
||||
const CATEGORY_KEYS: Record<Exclude<SpanCategory, 'All'>, string> = {
|
||||
Database: 'db.system',
|
||||
HTTP: 'http.method',
|
||||
Database: 'db.system.name',
|
||||
HTTP: 'http.request.method',
|
||||
Functions: 'kind_string',
|
||||
Jobs: 'messaging.system',
|
||||
LLM: 'gen_ai.request.model',
|
||||
@@ -34,8 +34,8 @@ const ALL_CATEGORY_KEYS = Object.values(CATEGORY_KEYS);
|
||||
|
||||
// The expression clause to add for each category
|
||||
const CATEGORY_EXPRESSIONS: Record<Exclude<SpanCategory, 'All'>, string> = {
|
||||
Database: 'db.system exists',
|
||||
HTTP: 'http.method exists',
|
||||
Database: 'db.system.name exists',
|
||||
HTTP: 'http.request.method exists',
|
||||
Functions: "kind_string = 'Internal'",
|
||||
Jobs: 'messaging.system exists',
|
||||
LLM: 'gen_ai.request.model exists',
|
||||
|
||||
@@ -38,7 +38,7 @@ export function Section(props: SectionProps): JSX.Element {
|
||||
'hasError',
|
||||
'durationNano',
|
||||
'serviceName',
|
||||
'deployment.environment',
|
||||
'deployment.environment.name',
|
||||
]),
|
||||
),
|
||||
[selectedFilters],
|
||||
|
||||
@@ -14,7 +14,7 @@ export const AllTraceFilterKeyValue: Record<string, string> = {
|
||||
durationNano: 'Duration',
|
||||
duration_nano: 'Duration',
|
||||
durationNanoMax: 'Duration',
|
||||
'deployment.environment': 'Environment',
|
||||
'deployment.environment.name': 'Environment',
|
||||
hasError: 'Status',
|
||||
has_error: 'Status',
|
||||
serviceName: 'Service Name',
|
||||
@@ -208,11 +208,11 @@ export const traceFilterKeys: Record<AllTraceFilterKeys, BaseAutocompleteData> =
|
||||
id: 'serviceName--string--tag--true',
|
||||
},
|
||||
|
||||
'deployment.environment': {
|
||||
key: 'deployment.environment',
|
||||
'deployment.environment.name': {
|
||||
key: 'deployment.environment.name',
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
id: 'deployment.environment--string--resource--false',
|
||||
id: 'deployment.environment.name--string--resource--false',
|
||||
},
|
||||
name: {
|
||||
key: 'name',
|
||||
|
||||
@@ -74,7 +74,8 @@ export function tracesRunQueryAction(
|
||||
properties: {
|
||||
key: {
|
||||
type: 'string',
|
||||
description: 'Attribute key, e.g. service.name, http.status_code',
|
||||
description:
|
||||
'Attribute key, e.g. service.name, http.response.status_code',
|
||||
},
|
||||
op: {
|
||||
type: 'string',
|
||||
@@ -143,7 +144,7 @@ export function tracesAddFilterAction(
|
||||
properties: {
|
||||
key: {
|
||||
type: 'string',
|
||||
description: 'Attribute key, e.g. service.name, http.status_code',
|
||||
description: 'Attribute key, e.g. service.name, http.response.status_code',
|
||||
},
|
||||
op: {
|
||||
type: 'string',
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -24,12 +24,14 @@ HASTOKEN=23
|
||||
HAS=24
|
||||
HASANY=25
|
||||
HASALL=26
|
||||
BOOL=27
|
||||
NUMBER=28
|
||||
QUOTED_TEXT=29
|
||||
KEY=30
|
||||
WS=31
|
||||
FREETEXT=32
|
||||
SEARCH=27
|
||||
EXACT=28
|
||||
BOOL=29
|
||||
NUMBER=30
|
||||
QUOTED_TEXT=31
|
||||
KEY=32
|
||||
WS=33
|
||||
FREETEXT=34
|
||||
'('=1
|
||||
')'=2
|
||||
'['=3
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -24,12 +24,14 @@ HASTOKEN=23
|
||||
HAS=24
|
||||
HASANY=25
|
||||
HASALL=26
|
||||
BOOL=27
|
||||
NUMBER=28
|
||||
QUOTED_TEXT=29
|
||||
KEY=30
|
||||
WS=31
|
||||
FREETEXT=32
|
||||
SEARCH=27
|
||||
EXACT=28
|
||||
BOOL=29
|
||||
NUMBER=30
|
||||
QUOTED_TEXT=31
|
||||
KEY=32
|
||||
WS=33
|
||||
FREETEXT=34
|
||||
'('=1
|
||||
')'=2
|
||||
'['=3
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Generated from FilterQuery.g4 by ANTLR 4.13.1
|
||||
// Generated from grammar/FilterQuery.g4 by ANTLR 4.13.2
|
||||
// noinspection ES6UnusedImports,JSUnusedGlobalSymbols,JSUnusedLocalSymbols
|
||||
import {
|
||||
ATN,
|
||||
@@ -38,12 +38,14 @@ export default class FilterQueryLexer extends Lexer {
|
||||
public static readonly HAS = 24;
|
||||
public static readonly HASANY = 25;
|
||||
public static readonly HASALL = 26;
|
||||
public static readonly BOOL = 27;
|
||||
public static readonly NUMBER = 28;
|
||||
public static readonly QUOTED_TEXT = 29;
|
||||
public static readonly KEY = 30;
|
||||
public static readonly WS = 31;
|
||||
public static readonly FREETEXT = 32;
|
||||
public static readonly SEARCH = 27;
|
||||
public static readonly EXACT = 28;
|
||||
public static readonly BOOL = 29;
|
||||
public static readonly NUMBER = 30;
|
||||
public static readonly QUOTED_TEXT = 31;
|
||||
public static readonly KEY = 32;
|
||||
public static readonly WS = 33;
|
||||
public static readonly FREETEXT = 34;
|
||||
public static readonly EOF = Token.EOF;
|
||||
|
||||
public static readonly channelNames: string[] = [ "DEFAULT_TOKEN_CHANNEL", "HIDDEN" ];
|
||||
@@ -68,7 +70,8 @@ export default class FilterQueryLexer extends Lexer {
|
||||
"AND", "OR",
|
||||
"HASTOKEN",
|
||||
"HAS", "HASANY",
|
||||
"HASALL", "BOOL",
|
||||
"HASALL", "SEARCH",
|
||||
"EXACT", "BOOL",
|
||||
"NUMBER", "QUOTED_TEXT",
|
||||
"KEY", "WS",
|
||||
"FREETEXT" ];
|
||||
@@ -78,8 +81,8 @@ export default class FilterQueryLexer extends Lexer {
|
||||
"LPAREN", "RPAREN", "LBRACK", "RBRACK", "COMMA", "EQUALS", "NOT_EQUALS",
|
||||
"NEQ", "LT", "LE", "GT", "GE", "LIKE", "ILIKE", "BETWEEN", "EXISTS", "REGEXP",
|
||||
"CONTAINS", "IN", "NOT", "AND", "OR", "HASTOKEN", "HAS", "HASANY", "HASALL",
|
||||
"BOOL", "SIGN", "NUMBER", "QUOTED_TEXT", "SEGMENT", "EMPTY_BRACKS", "OLD_JSON_BRACKS",
|
||||
"KEY", "WS", "DIGIT", "FREETEXT",
|
||||
"SEARCH", "EXACT", "BOOL", "SIGN", "NUMBER", "QUOTED_TEXT", "SEGMENT",
|
||||
"EMPTY_BRACKS", "OLD_JSON_BRACKS", "KEY", "WS", "DIGIT", "FREETEXT",
|
||||
];
|
||||
|
||||
|
||||
@@ -100,119 +103,124 @@ export default class FilterQueryLexer extends Lexer {
|
||||
|
||||
public get modeNames(): string[] { return FilterQueryLexer.modeNames; }
|
||||
|
||||
public static readonly _serializedATN: number[] = [4,0,32,320,6,-1,2,0,
|
||||
public static readonly _serializedATN: number[] = [4,0,34,337,6,-1,2,0,
|
||||
7,0,2,1,7,1,2,2,7,2,2,3,7,3,2,4,7,4,2,5,7,5,2,6,7,6,2,7,7,7,2,8,7,8,2,9,
|
||||
7,9,2,10,7,10,2,11,7,11,2,12,7,12,2,13,7,13,2,14,7,14,2,15,7,15,2,16,7,
|
||||
16,2,17,7,17,2,18,7,18,2,19,7,19,2,20,7,20,2,21,7,21,2,22,7,22,2,23,7,23,
|
||||
2,24,7,24,2,25,7,25,2,26,7,26,2,27,7,27,2,28,7,28,2,29,7,29,2,30,7,30,2,
|
||||
31,7,31,2,32,7,32,2,33,7,33,2,34,7,34,2,35,7,35,2,36,7,36,1,0,1,0,1,1,1,
|
||||
1,1,2,1,2,1,3,1,3,1,4,1,4,1,5,1,5,1,5,3,5,89,8,5,1,6,1,6,1,6,1,7,1,7,1,
|
||||
7,1,8,1,8,1,9,1,9,1,9,1,10,1,10,1,11,1,11,1,11,1,12,1,12,1,12,1,12,1,12,
|
||||
1,13,1,13,1,13,1,13,1,13,1,13,1,14,1,14,1,14,1,14,1,14,1,14,1,14,1,14,1,
|
||||
15,1,15,1,15,1,15,1,15,1,15,3,15,132,8,15,1,16,1,16,1,16,1,16,1,16,1,16,
|
||||
1,16,1,17,1,17,1,17,1,17,1,17,1,17,1,17,1,17,3,17,149,8,17,1,18,1,18,1,
|
||||
18,1,19,1,19,1,19,1,19,1,20,1,20,1,20,1,20,1,21,1,21,1,21,1,22,1,22,1,22,
|
||||
1,22,1,22,1,22,1,22,1,22,1,22,1,23,1,23,1,23,1,23,1,24,1,24,1,24,1,24,1,
|
||||
24,1,24,1,24,1,25,1,25,1,25,1,25,1,25,1,25,1,25,1,26,1,26,1,26,1,26,1,26,
|
||||
1,26,1,26,1,26,1,26,3,26,201,8,26,1,27,1,27,1,28,3,28,206,8,28,1,28,4,28,
|
||||
209,8,28,11,28,12,28,210,1,28,1,28,5,28,215,8,28,10,28,12,28,218,9,28,3,
|
||||
28,220,8,28,1,28,1,28,3,28,224,8,28,1,28,4,28,227,8,28,11,28,12,28,228,
|
||||
3,28,231,8,28,1,28,3,28,234,8,28,1,28,1,28,4,28,238,8,28,11,28,12,28,239,
|
||||
1,28,1,28,3,28,244,8,28,1,28,4,28,247,8,28,11,28,12,28,248,3,28,251,8,28,
|
||||
3,28,253,8,28,1,29,1,29,1,29,1,29,5,29,259,8,29,10,29,12,29,262,9,29,1,
|
||||
29,1,29,1,29,1,29,1,29,5,29,269,8,29,10,29,12,29,272,9,29,1,29,3,29,275,
|
||||
8,29,1,30,1,30,5,30,279,8,30,10,30,12,30,282,9,30,1,31,1,31,1,31,1,32,1,
|
||||
32,1,32,1,32,1,33,1,33,1,33,1,33,1,33,1,33,1,33,4,33,298,8,33,11,33,12,
|
||||
33,299,5,33,302,8,33,10,33,12,33,305,9,33,1,34,4,34,308,8,34,11,34,12,34,
|
||||
309,1,34,1,34,1,35,1,35,1,36,4,36,317,8,36,11,36,12,36,318,0,0,37,1,1,3,
|
||||
2,5,3,7,4,9,5,11,6,13,7,15,8,17,9,19,10,21,11,23,12,25,13,27,14,29,15,31,
|
||||
16,33,17,35,18,37,19,39,20,41,21,43,22,45,23,47,24,49,25,51,26,53,27,55,
|
||||
0,57,28,59,29,61,0,63,0,65,0,67,30,69,31,71,0,73,32,1,0,29,2,0,76,76,108,
|
||||
108,2,0,73,73,105,105,2,0,75,75,107,107,2,0,69,69,101,101,2,0,66,66,98,
|
||||
98,2,0,84,84,116,116,2,0,87,87,119,119,2,0,78,78,110,110,2,0,88,88,120,
|
||||
120,2,0,83,83,115,115,2,0,82,82,114,114,2,0,71,71,103,103,2,0,80,80,112,
|
||||
112,2,0,67,67,99,99,2,0,79,79,111,111,2,0,65,65,97,97,2,0,68,68,100,100,
|
||||
2,0,72,72,104,104,2,0,89,89,121,121,2,0,85,85,117,117,2,0,70,70,102,102,
|
||||
2,0,43,43,45,45,2,0,34,34,92,92,2,0,39,39,92,92,4,0,35,36,64,90,95,95,97,
|
||||
123,7,0,35,36,45,45,47,58,64,90,95,95,97,123,125,125,3,0,9,10,13,13,32,
|
||||
32,1,0,48,57,8,0,9,10,13,13,32,34,39,41,44,44,60,62,91,91,93,93,344,0,1,
|
||||
1,0,0,0,0,3,1,0,0,0,0,5,1,0,0,0,0,7,1,0,0,0,0,9,1,0,0,0,0,11,1,0,0,0,0,
|
||||
13,1,0,0,0,0,15,1,0,0,0,0,17,1,0,0,0,0,19,1,0,0,0,0,21,1,0,0,0,0,23,1,0,
|
||||
0,0,0,25,1,0,0,0,0,27,1,0,0,0,0,29,1,0,0,0,0,31,1,0,0,0,0,33,1,0,0,0,0,
|
||||
35,1,0,0,0,0,37,1,0,0,0,0,39,1,0,0,0,0,41,1,0,0,0,0,43,1,0,0,0,0,45,1,0,
|
||||
0,0,0,47,1,0,0,0,0,49,1,0,0,0,0,51,1,0,0,0,0,53,1,0,0,0,0,57,1,0,0,0,0,
|
||||
59,1,0,0,0,0,67,1,0,0,0,0,69,1,0,0,0,0,73,1,0,0,0,1,75,1,0,0,0,3,77,1,0,
|
||||
0,0,5,79,1,0,0,0,7,81,1,0,0,0,9,83,1,0,0,0,11,88,1,0,0,0,13,90,1,0,0,0,
|
||||
15,93,1,0,0,0,17,96,1,0,0,0,19,98,1,0,0,0,21,101,1,0,0,0,23,103,1,0,0,0,
|
||||
25,106,1,0,0,0,27,111,1,0,0,0,29,117,1,0,0,0,31,125,1,0,0,0,33,133,1,0,
|
||||
0,0,35,140,1,0,0,0,37,150,1,0,0,0,39,153,1,0,0,0,41,157,1,0,0,0,43,161,
|
||||
1,0,0,0,45,164,1,0,0,0,47,173,1,0,0,0,49,177,1,0,0,0,51,184,1,0,0,0,53,
|
||||
200,1,0,0,0,55,202,1,0,0,0,57,252,1,0,0,0,59,274,1,0,0,0,61,276,1,0,0,0,
|
||||
63,283,1,0,0,0,65,286,1,0,0,0,67,290,1,0,0,0,69,307,1,0,0,0,71,313,1,0,
|
||||
0,0,73,316,1,0,0,0,75,76,5,40,0,0,76,2,1,0,0,0,77,78,5,41,0,0,78,4,1,0,
|
||||
0,0,79,80,5,91,0,0,80,6,1,0,0,0,81,82,5,93,0,0,82,8,1,0,0,0,83,84,5,44,
|
||||
0,0,84,10,1,0,0,0,85,89,5,61,0,0,86,87,5,61,0,0,87,89,5,61,0,0,88,85,1,
|
||||
0,0,0,88,86,1,0,0,0,89,12,1,0,0,0,90,91,5,33,0,0,91,92,5,61,0,0,92,14,1,
|
||||
0,0,0,93,94,5,60,0,0,94,95,5,62,0,0,95,16,1,0,0,0,96,97,5,60,0,0,97,18,
|
||||
1,0,0,0,98,99,5,60,0,0,99,100,5,61,0,0,100,20,1,0,0,0,101,102,5,62,0,0,
|
||||
102,22,1,0,0,0,103,104,5,62,0,0,104,105,5,61,0,0,105,24,1,0,0,0,106,107,
|
||||
7,0,0,0,107,108,7,1,0,0,108,109,7,2,0,0,109,110,7,3,0,0,110,26,1,0,0,0,
|
||||
111,112,7,1,0,0,112,113,7,0,0,0,113,114,7,1,0,0,114,115,7,2,0,0,115,116,
|
||||
7,3,0,0,116,28,1,0,0,0,117,118,7,4,0,0,118,119,7,3,0,0,119,120,7,5,0,0,
|
||||
120,121,7,6,0,0,121,122,7,3,0,0,122,123,7,3,0,0,123,124,7,7,0,0,124,30,
|
||||
1,0,0,0,125,126,7,3,0,0,126,127,7,8,0,0,127,128,7,1,0,0,128,129,7,9,0,0,
|
||||
129,131,7,5,0,0,130,132,7,9,0,0,131,130,1,0,0,0,131,132,1,0,0,0,132,32,
|
||||
1,0,0,0,133,134,7,10,0,0,134,135,7,3,0,0,135,136,7,11,0,0,136,137,7,3,0,
|
||||
0,137,138,7,8,0,0,138,139,7,12,0,0,139,34,1,0,0,0,140,141,7,13,0,0,141,
|
||||
142,7,14,0,0,142,143,7,7,0,0,143,144,7,5,0,0,144,145,7,15,0,0,145,146,7,
|
||||
1,0,0,146,148,7,7,0,0,147,149,7,9,0,0,148,147,1,0,0,0,148,149,1,0,0,0,149,
|
||||
36,1,0,0,0,150,151,7,1,0,0,151,152,7,7,0,0,152,38,1,0,0,0,153,154,7,7,0,
|
||||
0,154,155,7,14,0,0,155,156,7,5,0,0,156,40,1,0,0,0,157,158,7,15,0,0,158,
|
||||
159,7,7,0,0,159,160,7,16,0,0,160,42,1,0,0,0,161,162,7,14,0,0,162,163,7,
|
||||
10,0,0,163,44,1,0,0,0,164,165,7,17,0,0,165,166,7,15,0,0,166,167,7,9,0,0,
|
||||
167,168,7,5,0,0,168,169,7,14,0,0,169,170,7,2,0,0,170,171,7,3,0,0,171,172,
|
||||
7,7,0,0,172,46,1,0,0,0,173,174,7,17,0,0,174,175,7,15,0,0,175,176,7,9,0,
|
||||
0,176,48,1,0,0,0,177,178,7,17,0,0,178,179,7,15,0,0,179,180,7,9,0,0,180,
|
||||
181,7,15,0,0,181,182,7,7,0,0,182,183,7,18,0,0,183,50,1,0,0,0,184,185,7,
|
||||
17,0,0,185,186,7,15,0,0,186,187,7,9,0,0,187,188,7,15,0,0,188,189,7,0,0,
|
||||
0,189,190,7,0,0,0,190,52,1,0,0,0,191,192,7,5,0,0,192,193,7,10,0,0,193,194,
|
||||
7,19,0,0,194,201,7,3,0,0,195,196,7,20,0,0,196,197,7,15,0,0,197,198,7,0,
|
||||
0,0,198,199,7,9,0,0,199,201,7,3,0,0,200,191,1,0,0,0,200,195,1,0,0,0,201,
|
||||
54,1,0,0,0,202,203,7,21,0,0,203,56,1,0,0,0,204,206,3,55,27,0,205,204,1,
|
||||
0,0,0,205,206,1,0,0,0,206,208,1,0,0,0,207,209,3,71,35,0,208,207,1,0,0,0,
|
||||
209,210,1,0,0,0,210,208,1,0,0,0,210,211,1,0,0,0,211,219,1,0,0,0,212,216,
|
||||
5,46,0,0,213,215,3,71,35,0,214,213,1,0,0,0,215,218,1,0,0,0,216,214,1,0,
|
||||
0,0,216,217,1,0,0,0,217,220,1,0,0,0,218,216,1,0,0,0,219,212,1,0,0,0,219,
|
||||
220,1,0,0,0,220,230,1,0,0,0,221,223,7,3,0,0,222,224,3,55,27,0,223,222,1,
|
||||
0,0,0,223,224,1,0,0,0,224,226,1,0,0,0,225,227,3,71,35,0,226,225,1,0,0,0,
|
||||
227,228,1,0,0,0,228,226,1,0,0,0,228,229,1,0,0,0,229,231,1,0,0,0,230,221,
|
||||
1,0,0,0,230,231,1,0,0,0,231,253,1,0,0,0,232,234,3,55,27,0,233,232,1,0,0,
|
||||
0,233,234,1,0,0,0,234,235,1,0,0,0,235,237,5,46,0,0,236,238,3,71,35,0,237,
|
||||
236,1,0,0,0,238,239,1,0,0,0,239,237,1,0,0,0,239,240,1,0,0,0,240,250,1,0,
|
||||
0,0,241,243,7,3,0,0,242,244,3,55,27,0,243,242,1,0,0,0,243,244,1,0,0,0,244,
|
||||
246,1,0,0,0,245,247,3,71,35,0,246,245,1,0,0,0,247,248,1,0,0,0,248,246,1,
|
||||
0,0,0,248,249,1,0,0,0,249,251,1,0,0,0,250,241,1,0,0,0,250,251,1,0,0,0,251,
|
||||
253,1,0,0,0,252,205,1,0,0,0,252,233,1,0,0,0,253,58,1,0,0,0,254,260,5,34,
|
||||
0,0,255,259,8,22,0,0,256,257,5,92,0,0,257,259,9,0,0,0,258,255,1,0,0,0,258,
|
||||
256,1,0,0,0,259,262,1,0,0,0,260,258,1,0,0,0,260,261,1,0,0,0,261,263,1,0,
|
||||
0,0,262,260,1,0,0,0,263,275,5,34,0,0,264,270,5,39,0,0,265,269,8,23,0,0,
|
||||
266,267,5,92,0,0,267,269,9,0,0,0,268,265,1,0,0,0,268,266,1,0,0,0,269,272,
|
||||
1,0,0,0,270,268,1,0,0,0,270,271,1,0,0,0,271,273,1,0,0,0,272,270,1,0,0,0,
|
||||
273,275,5,39,0,0,274,254,1,0,0,0,274,264,1,0,0,0,275,60,1,0,0,0,276,280,
|
||||
7,24,0,0,277,279,7,25,0,0,278,277,1,0,0,0,279,282,1,0,0,0,280,278,1,0,0,
|
||||
0,280,281,1,0,0,0,281,62,1,0,0,0,282,280,1,0,0,0,283,284,5,91,0,0,284,285,
|
||||
5,93,0,0,285,64,1,0,0,0,286,287,5,91,0,0,287,288,5,42,0,0,288,289,5,93,
|
||||
0,0,289,66,1,0,0,0,290,303,3,61,30,0,291,292,5,46,0,0,292,302,3,61,30,0,
|
||||
293,302,3,63,31,0,294,302,3,65,32,0,295,297,5,46,0,0,296,298,3,71,35,0,
|
||||
297,296,1,0,0,0,298,299,1,0,0,0,299,297,1,0,0,0,299,300,1,0,0,0,300,302,
|
||||
1,0,0,0,301,291,1,0,0,0,301,293,1,0,0,0,301,294,1,0,0,0,301,295,1,0,0,0,
|
||||
302,305,1,0,0,0,303,301,1,0,0,0,303,304,1,0,0,0,304,68,1,0,0,0,305,303,
|
||||
1,0,0,0,306,308,7,26,0,0,307,306,1,0,0,0,308,309,1,0,0,0,309,307,1,0,0,
|
||||
0,309,310,1,0,0,0,310,311,1,0,0,0,311,312,6,34,0,0,312,70,1,0,0,0,313,314,
|
||||
7,27,0,0,314,72,1,0,0,0,315,317,8,28,0,0,316,315,1,0,0,0,317,318,1,0,0,
|
||||
0,318,316,1,0,0,0,318,319,1,0,0,0,319,74,1,0,0,0,29,0,88,131,148,200,205,
|
||||
210,216,219,223,228,230,233,239,243,248,250,252,258,260,268,270,274,280,
|
||||
299,301,303,309,318,1,6,0,0];
|
||||
31,7,31,2,32,7,32,2,33,7,33,2,34,7,34,2,35,7,35,2,36,7,36,2,37,7,37,2,38,
|
||||
7,38,1,0,1,0,1,1,1,1,1,2,1,2,1,3,1,3,1,4,1,4,1,5,1,5,1,5,3,5,93,8,5,1,6,
|
||||
1,6,1,6,1,7,1,7,1,7,1,8,1,8,1,9,1,9,1,9,1,10,1,10,1,11,1,11,1,11,1,12,1,
|
||||
12,1,12,1,12,1,12,1,13,1,13,1,13,1,13,1,13,1,13,1,14,1,14,1,14,1,14,1,14,
|
||||
1,14,1,14,1,14,1,15,1,15,1,15,1,15,1,15,1,15,3,15,136,8,15,1,16,1,16,1,
|
||||
16,1,16,1,16,1,16,1,16,1,17,1,17,1,17,1,17,1,17,1,17,1,17,1,17,3,17,153,
|
||||
8,17,1,18,1,18,1,18,1,19,1,19,1,19,1,19,1,20,1,20,1,20,1,20,1,21,1,21,1,
|
||||
21,1,22,1,22,1,22,1,22,1,22,1,22,1,22,1,22,1,22,1,23,1,23,1,23,1,23,1,24,
|
||||
1,24,1,24,1,24,1,24,1,24,1,24,1,25,1,25,1,25,1,25,1,25,1,25,1,25,1,26,1,
|
||||
26,1,26,1,26,1,26,1,26,1,26,1,27,1,27,1,27,1,27,1,27,1,27,1,28,1,28,1,28,
|
||||
1,28,1,28,1,28,1,28,1,28,1,28,3,28,218,8,28,1,29,1,29,1,30,3,30,223,8,30,
|
||||
1,30,4,30,226,8,30,11,30,12,30,227,1,30,1,30,5,30,232,8,30,10,30,12,30,
|
||||
235,9,30,3,30,237,8,30,1,30,1,30,3,30,241,8,30,1,30,4,30,244,8,30,11,30,
|
||||
12,30,245,3,30,248,8,30,1,30,3,30,251,8,30,1,30,1,30,4,30,255,8,30,11,30,
|
||||
12,30,256,1,30,1,30,3,30,261,8,30,1,30,4,30,264,8,30,11,30,12,30,265,3,
|
||||
30,268,8,30,3,30,270,8,30,1,31,1,31,1,31,1,31,5,31,276,8,31,10,31,12,31,
|
||||
279,9,31,1,31,1,31,1,31,1,31,1,31,5,31,286,8,31,10,31,12,31,289,9,31,1,
|
||||
31,3,31,292,8,31,1,32,1,32,5,32,296,8,32,10,32,12,32,299,9,32,1,33,1,33,
|
||||
1,33,1,34,1,34,1,34,1,34,1,35,1,35,1,35,1,35,1,35,1,35,1,35,4,35,315,8,
|
||||
35,11,35,12,35,316,5,35,319,8,35,10,35,12,35,322,9,35,1,36,4,36,325,8,36,
|
||||
11,36,12,36,326,1,36,1,36,1,37,1,37,1,38,4,38,334,8,38,11,38,12,38,335,
|
||||
0,0,39,1,1,3,2,5,3,7,4,9,5,11,6,13,7,15,8,17,9,19,10,21,11,23,12,25,13,
|
||||
27,14,29,15,31,16,33,17,35,18,37,19,39,20,41,21,43,22,45,23,47,24,49,25,
|
||||
51,26,53,27,55,28,57,29,59,0,61,30,63,31,65,0,67,0,69,0,71,32,73,33,75,
|
||||
0,77,34,1,0,29,2,0,76,76,108,108,2,0,73,73,105,105,2,0,75,75,107,107,2,
|
||||
0,69,69,101,101,2,0,66,66,98,98,2,0,84,84,116,116,2,0,87,87,119,119,2,0,
|
||||
78,78,110,110,2,0,88,88,120,120,2,0,83,83,115,115,2,0,82,82,114,114,2,0,
|
||||
71,71,103,103,2,0,80,80,112,112,2,0,67,67,99,99,2,0,79,79,111,111,2,0,65,
|
||||
65,97,97,2,0,68,68,100,100,2,0,72,72,104,104,2,0,89,89,121,121,2,0,85,85,
|
||||
117,117,2,0,70,70,102,102,2,0,43,43,45,45,2,0,34,34,92,92,2,0,39,39,92,
|
||||
92,4,0,35,36,64,90,95,95,97,123,7,0,35,36,45,45,47,58,64,90,95,95,97,123,
|
||||
125,125,3,0,9,10,13,13,32,32,1,0,48,57,8,0,9,10,13,13,32,34,39,41,44,44,
|
||||
60,62,91,91,93,93,361,0,1,1,0,0,0,0,3,1,0,0,0,0,5,1,0,0,0,0,7,1,0,0,0,0,
|
||||
9,1,0,0,0,0,11,1,0,0,0,0,13,1,0,0,0,0,15,1,0,0,0,0,17,1,0,0,0,0,19,1,0,
|
||||
0,0,0,21,1,0,0,0,0,23,1,0,0,0,0,25,1,0,0,0,0,27,1,0,0,0,0,29,1,0,0,0,0,
|
||||
31,1,0,0,0,0,33,1,0,0,0,0,35,1,0,0,0,0,37,1,0,0,0,0,39,1,0,0,0,0,41,1,0,
|
||||
0,0,0,43,1,0,0,0,0,45,1,0,0,0,0,47,1,0,0,0,0,49,1,0,0,0,0,51,1,0,0,0,0,
|
||||
53,1,0,0,0,0,55,1,0,0,0,0,57,1,0,0,0,0,61,1,0,0,0,0,63,1,0,0,0,0,71,1,0,
|
||||
0,0,0,73,1,0,0,0,0,77,1,0,0,0,1,79,1,0,0,0,3,81,1,0,0,0,5,83,1,0,0,0,7,
|
||||
85,1,0,0,0,9,87,1,0,0,0,11,92,1,0,0,0,13,94,1,0,0,0,15,97,1,0,0,0,17,100,
|
||||
1,0,0,0,19,102,1,0,0,0,21,105,1,0,0,0,23,107,1,0,0,0,25,110,1,0,0,0,27,
|
||||
115,1,0,0,0,29,121,1,0,0,0,31,129,1,0,0,0,33,137,1,0,0,0,35,144,1,0,0,0,
|
||||
37,154,1,0,0,0,39,157,1,0,0,0,41,161,1,0,0,0,43,165,1,0,0,0,45,168,1,0,
|
||||
0,0,47,177,1,0,0,0,49,181,1,0,0,0,51,188,1,0,0,0,53,195,1,0,0,0,55,202,
|
||||
1,0,0,0,57,217,1,0,0,0,59,219,1,0,0,0,61,269,1,0,0,0,63,291,1,0,0,0,65,
|
||||
293,1,0,0,0,67,300,1,0,0,0,69,303,1,0,0,0,71,307,1,0,0,0,73,324,1,0,0,0,
|
||||
75,330,1,0,0,0,77,333,1,0,0,0,79,80,5,40,0,0,80,2,1,0,0,0,81,82,5,41,0,
|
||||
0,82,4,1,0,0,0,83,84,5,91,0,0,84,6,1,0,0,0,85,86,5,93,0,0,86,8,1,0,0,0,
|
||||
87,88,5,44,0,0,88,10,1,0,0,0,89,93,5,61,0,0,90,91,5,61,0,0,91,93,5,61,0,
|
||||
0,92,89,1,0,0,0,92,90,1,0,0,0,93,12,1,0,0,0,94,95,5,33,0,0,95,96,5,61,0,
|
||||
0,96,14,1,0,0,0,97,98,5,60,0,0,98,99,5,62,0,0,99,16,1,0,0,0,100,101,5,60,
|
||||
0,0,101,18,1,0,0,0,102,103,5,60,0,0,103,104,5,61,0,0,104,20,1,0,0,0,105,
|
||||
106,5,62,0,0,106,22,1,0,0,0,107,108,5,62,0,0,108,109,5,61,0,0,109,24,1,
|
||||
0,0,0,110,111,7,0,0,0,111,112,7,1,0,0,112,113,7,2,0,0,113,114,7,3,0,0,114,
|
||||
26,1,0,0,0,115,116,7,1,0,0,116,117,7,0,0,0,117,118,7,1,0,0,118,119,7,2,
|
||||
0,0,119,120,7,3,0,0,120,28,1,0,0,0,121,122,7,4,0,0,122,123,7,3,0,0,123,
|
||||
124,7,5,0,0,124,125,7,6,0,0,125,126,7,3,0,0,126,127,7,3,0,0,127,128,7,7,
|
||||
0,0,128,30,1,0,0,0,129,130,7,3,0,0,130,131,7,8,0,0,131,132,7,1,0,0,132,
|
||||
133,7,9,0,0,133,135,7,5,0,0,134,136,7,9,0,0,135,134,1,0,0,0,135,136,1,0,
|
||||
0,0,136,32,1,0,0,0,137,138,7,10,0,0,138,139,7,3,0,0,139,140,7,11,0,0,140,
|
||||
141,7,3,0,0,141,142,7,8,0,0,142,143,7,12,0,0,143,34,1,0,0,0,144,145,7,13,
|
||||
0,0,145,146,7,14,0,0,146,147,7,7,0,0,147,148,7,5,0,0,148,149,7,15,0,0,149,
|
||||
150,7,1,0,0,150,152,7,7,0,0,151,153,7,9,0,0,152,151,1,0,0,0,152,153,1,0,
|
||||
0,0,153,36,1,0,0,0,154,155,7,1,0,0,155,156,7,7,0,0,156,38,1,0,0,0,157,158,
|
||||
7,7,0,0,158,159,7,14,0,0,159,160,7,5,0,0,160,40,1,0,0,0,161,162,7,15,0,
|
||||
0,162,163,7,7,0,0,163,164,7,16,0,0,164,42,1,0,0,0,165,166,7,14,0,0,166,
|
||||
167,7,10,0,0,167,44,1,0,0,0,168,169,7,17,0,0,169,170,7,15,0,0,170,171,7,
|
||||
9,0,0,171,172,7,5,0,0,172,173,7,14,0,0,173,174,7,2,0,0,174,175,7,3,0,0,
|
||||
175,176,7,7,0,0,176,46,1,0,0,0,177,178,7,17,0,0,178,179,7,15,0,0,179,180,
|
||||
7,9,0,0,180,48,1,0,0,0,181,182,7,17,0,0,182,183,7,15,0,0,183,184,7,9,0,
|
||||
0,184,185,7,15,0,0,185,186,7,7,0,0,186,187,7,18,0,0,187,50,1,0,0,0,188,
|
||||
189,7,17,0,0,189,190,7,15,0,0,190,191,7,9,0,0,191,192,7,15,0,0,192,193,
|
||||
7,0,0,0,193,194,7,0,0,0,194,52,1,0,0,0,195,196,7,9,0,0,196,197,7,3,0,0,
|
||||
197,198,7,15,0,0,198,199,7,10,0,0,199,200,7,13,0,0,200,201,7,17,0,0,201,
|
||||
54,1,0,0,0,202,203,7,3,0,0,203,204,7,8,0,0,204,205,7,15,0,0,205,206,7,13,
|
||||
0,0,206,207,7,5,0,0,207,56,1,0,0,0,208,209,7,5,0,0,209,210,7,10,0,0,210,
|
||||
211,7,19,0,0,211,218,7,3,0,0,212,213,7,20,0,0,213,214,7,15,0,0,214,215,
|
||||
7,0,0,0,215,216,7,9,0,0,216,218,7,3,0,0,217,208,1,0,0,0,217,212,1,0,0,0,
|
||||
218,58,1,0,0,0,219,220,7,21,0,0,220,60,1,0,0,0,221,223,3,59,29,0,222,221,
|
||||
1,0,0,0,222,223,1,0,0,0,223,225,1,0,0,0,224,226,3,75,37,0,225,224,1,0,0,
|
||||
0,226,227,1,0,0,0,227,225,1,0,0,0,227,228,1,0,0,0,228,236,1,0,0,0,229,233,
|
||||
5,46,0,0,230,232,3,75,37,0,231,230,1,0,0,0,232,235,1,0,0,0,233,231,1,0,
|
||||
0,0,233,234,1,0,0,0,234,237,1,0,0,0,235,233,1,0,0,0,236,229,1,0,0,0,236,
|
||||
237,1,0,0,0,237,247,1,0,0,0,238,240,7,3,0,0,239,241,3,59,29,0,240,239,1,
|
||||
0,0,0,240,241,1,0,0,0,241,243,1,0,0,0,242,244,3,75,37,0,243,242,1,0,0,0,
|
||||
244,245,1,0,0,0,245,243,1,0,0,0,245,246,1,0,0,0,246,248,1,0,0,0,247,238,
|
||||
1,0,0,0,247,248,1,0,0,0,248,270,1,0,0,0,249,251,3,59,29,0,250,249,1,0,0,
|
||||
0,250,251,1,0,0,0,251,252,1,0,0,0,252,254,5,46,0,0,253,255,3,75,37,0,254,
|
||||
253,1,0,0,0,255,256,1,0,0,0,256,254,1,0,0,0,256,257,1,0,0,0,257,267,1,0,
|
||||
0,0,258,260,7,3,0,0,259,261,3,59,29,0,260,259,1,0,0,0,260,261,1,0,0,0,261,
|
||||
263,1,0,0,0,262,264,3,75,37,0,263,262,1,0,0,0,264,265,1,0,0,0,265,263,1,
|
||||
0,0,0,265,266,1,0,0,0,266,268,1,0,0,0,267,258,1,0,0,0,267,268,1,0,0,0,268,
|
||||
270,1,0,0,0,269,222,1,0,0,0,269,250,1,0,0,0,270,62,1,0,0,0,271,277,5,34,
|
||||
0,0,272,276,8,22,0,0,273,274,5,92,0,0,274,276,9,0,0,0,275,272,1,0,0,0,275,
|
||||
273,1,0,0,0,276,279,1,0,0,0,277,275,1,0,0,0,277,278,1,0,0,0,278,280,1,0,
|
||||
0,0,279,277,1,0,0,0,280,292,5,34,0,0,281,287,5,39,0,0,282,286,8,23,0,0,
|
||||
283,284,5,92,0,0,284,286,9,0,0,0,285,282,1,0,0,0,285,283,1,0,0,0,286,289,
|
||||
1,0,0,0,287,285,1,0,0,0,287,288,1,0,0,0,288,290,1,0,0,0,289,287,1,0,0,0,
|
||||
290,292,5,39,0,0,291,271,1,0,0,0,291,281,1,0,0,0,292,64,1,0,0,0,293,297,
|
||||
7,24,0,0,294,296,7,25,0,0,295,294,1,0,0,0,296,299,1,0,0,0,297,295,1,0,0,
|
||||
0,297,298,1,0,0,0,298,66,1,0,0,0,299,297,1,0,0,0,300,301,5,91,0,0,301,302,
|
||||
5,93,0,0,302,68,1,0,0,0,303,304,5,91,0,0,304,305,5,42,0,0,305,306,5,93,
|
||||
0,0,306,70,1,0,0,0,307,320,3,65,32,0,308,309,5,46,0,0,309,319,3,65,32,0,
|
||||
310,319,3,67,33,0,311,319,3,69,34,0,312,314,5,46,0,0,313,315,3,75,37,0,
|
||||
314,313,1,0,0,0,315,316,1,0,0,0,316,314,1,0,0,0,316,317,1,0,0,0,317,319,
|
||||
1,0,0,0,318,308,1,0,0,0,318,310,1,0,0,0,318,311,1,0,0,0,318,312,1,0,0,0,
|
||||
319,322,1,0,0,0,320,318,1,0,0,0,320,321,1,0,0,0,321,72,1,0,0,0,322,320,
|
||||
1,0,0,0,323,325,7,26,0,0,324,323,1,0,0,0,325,326,1,0,0,0,326,324,1,0,0,
|
||||
0,326,327,1,0,0,0,327,328,1,0,0,0,328,329,6,36,0,0,329,74,1,0,0,0,330,331,
|
||||
7,27,0,0,331,76,1,0,0,0,332,334,8,28,0,0,333,332,1,0,0,0,334,335,1,0,0,
|
||||
0,335,333,1,0,0,0,335,336,1,0,0,0,336,78,1,0,0,0,29,0,92,135,152,217,222,
|
||||
227,233,236,240,245,247,250,256,260,265,267,269,275,277,285,287,291,297,
|
||||
316,318,320,326,335,1,6,0,0];
|
||||
|
||||
private static __ATN: ATN;
|
||||
public static get _ATN(): ATN {
|
||||
@@ -225,4 +233,4 @@ export default class FilterQueryLexer extends Lexer {
|
||||
|
||||
|
||||
static DecisionsToDFA = FilterQueryLexer._ATN.decisionToState.map( (ds: DecisionState, index: number) => new DFA(ds, index) );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +1,28 @@
|
||||
// Generated from FilterQuery.g4 by ANTLR 4.13.1
|
||||
// Generated from grammar/FilterQuery.g4 by ANTLR 4.13.2
|
||||
|
||||
import {ParseTreeListener} from "antlr4";
|
||||
|
||||
|
||||
import { QueryContext } from "./FilterQueryParser";
|
||||
import { ExpressionContext } from "./FilterQueryParser";
|
||||
import { OrExpressionContext } from "./FilterQueryParser";
|
||||
import { AndExpressionContext } from "./FilterQueryParser";
|
||||
import { UnaryExpressionContext } from "./FilterQueryParser";
|
||||
import { PrimaryContext } from "./FilterQueryParser";
|
||||
import { ComparisonContext } from "./FilterQueryParser";
|
||||
import { InClauseContext } from "./FilterQueryParser";
|
||||
import { NotInClauseContext } from "./FilterQueryParser";
|
||||
import { ValueListContext } from "./FilterQueryParser";
|
||||
import { FullTextContext } from "./FilterQueryParser";
|
||||
import { FunctionCallContext } from "./FilterQueryParser";
|
||||
import { FunctionParamListContext } from "./FilterQueryParser";
|
||||
import { FunctionParamContext } from "./FilterQueryParser";
|
||||
import { ArrayContext } from "./FilterQueryParser";
|
||||
import { ValueContext } from "./FilterQueryParser";
|
||||
import { KeyContext } from "./FilterQueryParser";
|
||||
import { QueryContext } from "./FilterQueryParser.js";
|
||||
import { ExpressionContext } from "./FilterQueryParser.js";
|
||||
import { OrExpressionContext } from "./FilterQueryParser.js";
|
||||
import { AndExpressionContext } from "./FilterQueryParser.js";
|
||||
import { UnaryExpressionContext } from "./FilterQueryParser.js";
|
||||
import { PrimaryContext } from "./FilterQueryParser.js";
|
||||
import { ComparisonContext } from "./FilterQueryParser.js";
|
||||
import { InClauseContext } from "./FilterQueryParser.js";
|
||||
import { NotInClauseContext } from "./FilterQueryParser.js";
|
||||
import { ValueListContext } from "./FilterQueryParser.js";
|
||||
import { FullTextContext } from "./FilterQueryParser.js";
|
||||
import { FunctionCallContext } from "./FilterQueryParser.js";
|
||||
import { SearchCallContext } from "./FilterQueryParser.js";
|
||||
import { FunctionParamListContext } from "./FilterQueryParser.js";
|
||||
import { FunctionParamContext } from "./FilterQueryParser.js";
|
||||
import { ArrayContext } from "./FilterQueryParser.js";
|
||||
import { ValueContext } from "./FilterQueryParser.js";
|
||||
import { KeyContext } from "./FilterQueryParser.js";
|
||||
import { FieldContext } from "./FilterQueryParser.js";
|
||||
import { ExactCallContext } from "./FilterQueryParser.js";
|
||||
|
||||
|
||||
/**
|
||||
@@ -147,6 +150,16 @@ export default class FilterQueryListener extends ParseTreeListener {
|
||||
* @param ctx the parse tree
|
||||
*/
|
||||
exitFunctionCall?: (ctx: FunctionCallContext) => void;
|
||||
/**
|
||||
* Enter a parse tree produced by `FilterQueryParser.searchCall`.
|
||||
* @param ctx the parse tree
|
||||
*/
|
||||
enterSearchCall?: (ctx: SearchCallContext) => void;
|
||||
/**
|
||||
* Exit a parse tree produced by `FilterQueryParser.searchCall`.
|
||||
* @param ctx the parse tree
|
||||
*/
|
||||
exitSearchCall?: (ctx: SearchCallContext) => void;
|
||||
/**
|
||||
* Enter a parse tree produced by `FilterQueryParser.functionParamList`.
|
||||
* @param ctx the parse tree
|
||||
@@ -197,5 +210,25 @@ export default class FilterQueryListener extends ParseTreeListener {
|
||||
* @param ctx the parse tree
|
||||
*/
|
||||
exitKey?: (ctx: KeyContext) => void;
|
||||
/**
|
||||
* Enter a parse tree produced by `FilterQueryParser.field`.
|
||||
* @param ctx the parse tree
|
||||
*/
|
||||
enterField?: (ctx: FieldContext) => void;
|
||||
/**
|
||||
* Exit a parse tree produced by `FilterQueryParser.field`.
|
||||
* @param ctx the parse tree
|
||||
*/
|
||||
exitField?: (ctx: FieldContext) => void;
|
||||
/**
|
||||
* Enter a parse tree produced by `FilterQueryParser.exactCall`.
|
||||
* @param ctx the parse tree
|
||||
*/
|
||||
enterExactCall?: (ctx: ExactCallContext) => void;
|
||||
/**
|
||||
* Exit a parse tree produced by `FilterQueryParser.exactCall`.
|
||||
* @param ctx the parse tree
|
||||
*/
|
||||
exitExactCall?: (ctx: ExactCallContext) => void;
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,25 +1,28 @@
|
||||
// Generated from FilterQuery.g4 by ANTLR 4.13.1
|
||||
// Generated from grammar/FilterQuery.g4 by ANTLR 4.13.2
|
||||
|
||||
import {ParseTreeVisitor} from 'antlr4';
|
||||
|
||||
|
||||
import { QueryContext } from "./FilterQueryParser";
|
||||
import { ExpressionContext } from "./FilterQueryParser";
|
||||
import { OrExpressionContext } from "./FilterQueryParser";
|
||||
import { AndExpressionContext } from "./FilterQueryParser";
|
||||
import { UnaryExpressionContext } from "./FilterQueryParser";
|
||||
import { PrimaryContext } from "./FilterQueryParser";
|
||||
import { ComparisonContext } from "./FilterQueryParser";
|
||||
import { InClauseContext } from "./FilterQueryParser";
|
||||
import { NotInClauseContext } from "./FilterQueryParser";
|
||||
import { ValueListContext } from "./FilterQueryParser";
|
||||
import { FullTextContext } from "./FilterQueryParser";
|
||||
import { FunctionCallContext } from "./FilterQueryParser";
|
||||
import { FunctionParamListContext } from "./FilterQueryParser";
|
||||
import { FunctionParamContext } from "./FilterQueryParser";
|
||||
import { ArrayContext } from "./FilterQueryParser";
|
||||
import { ValueContext } from "./FilterQueryParser";
|
||||
import { KeyContext } from "./FilterQueryParser";
|
||||
import { QueryContext } from "./FilterQueryParser.js";
|
||||
import { ExpressionContext } from "./FilterQueryParser.js";
|
||||
import { OrExpressionContext } from "./FilterQueryParser.js";
|
||||
import { AndExpressionContext } from "./FilterQueryParser.js";
|
||||
import { UnaryExpressionContext } from "./FilterQueryParser.js";
|
||||
import { PrimaryContext } from "./FilterQueryParser.js";
|
||||
import { ComparisonContext } from "./FilterQueryParser.js";
|
||||
import { InClauseContext } from "./FilterQueryParser.js";
|
||||
import { NotInClauseContext } from "./FilterQueryParser.js";
|
||||
import { ValueListContext } from "./FilterQueryParser.js";
|
||||
import { FullTextContext } from "./FilterQueryParser.js";
|
||||
import { FunctionCallContext } from "./FilterQueryParser.js";
|
||||
import { SearchCallContext } from "./FilterQueryParser.js";
|
||||
import { FunctionParamListContext } from "./FilterQueryParser.js";
|
||||
import { FunctionParamContext } from "./FilterQueryParser.js";
|
||||
import { ArrayContext } from "./FilterQueryParser.js";
|
||||
import { ValueContext } from "./FilterQueryParser.js";
|
||||
import { KeyContext } from "./FilterQueryParser.js";
|
||||
import { FieldContext } from "./FilterQueryParser.js";
|
||||
import { ExactCallContext } from "./FilterQueryParser.js";
|
||||
|
||||
|
||||
/**
|
||||
@@ -102,6 +105,12 @@ export default class FilterQueryVisitor<Result> extends ParseTreeVisitor<Result>
|
||||
* @return the visitor result
|
||||
*/
|
||||
visitFunctionCall?: (ctx: FunctionCallContext) => Result;
|
||||
/**
|
||||
* Visit a parse tree produced by `FilterQueryParser.searchCall`.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
visitSearchCall?: (ctx: SearchCallContext) => Result;
|
||||
/**
|
||||
* Visit a parse tree produced by `FilterQueryParser.functionParamList`.
|
||||
* @param ctx the parse tree
|
||||
@@ -132,5 +141,17 @@ export default class FilterQueryVisitor<Result> extends ParseTreeVisitor<Result>
|
||||
* @return the visitor result
|
||||
*/
|
||||
visitKey?: (ctx: KeyContext) => Result;
|
||||
/**
|
||||
* Visit a parse tree produced by `FilterQueryParser.field`.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
visitField?: (ctx: FieldContext) => Result;
|
||||
/**
|
||||
* Visit a parse tree produced by `FilterQueryParser.exactCall`.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
visitExactCall?: (ctx: ExactCallContext) => Result;
|
||||
}
|
||||
|
||||
|
||||
@@ -223,6 +223,12 @@
|
||||
padding-left: 6px !important;
|
||||
}
|
||||
|
||||
&__label {
|
||||
display: inline-flex;
|
||||
align-items: baseline;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
&__pinned-icon {
|
||||
flex-shrink: 0;
|
||||
color: var(--text-robin-400);
|
||||
|
||||
@@ -67,6 +67,7 @@ export interface PrettyViewProps {
|
||||
*/
|
||||
pinnedFieldsValue?: string[];
|
||||
onPinnedFieldsChange?: (next: string[]) => void;
|
||||
labelSuffixRenderer?: (fieldKey: string) => React.ReactNode;
|
||||
}
|
||||
|
||||
function PrettyView({
|
||||
@@ -78,6 +79,7 @@ function PrettyView({
|
||||
drawerKey = 'default',
|
||||
pinnedFieldsValue,
|
||||
onPinnedFieldsChange,
|
||||
labelSuffixRenderer,
|
||||
}: PrettyViewProps): JSX.Element {
|
||||
const isDarkMode = useIsDarkMode();
|
||||
const [, setCopy] = useCopyToClipboard();
|
||||
@@ -305,10 +307,24 @@ function PrettyView({
|
||||
}}
|
||||
/>
|
||||
<span>{displayKey}</span>
|
||||
{labelSuffixRenderer?.(displayKey)}
|
||||
</span>
|
||||
);
|
||||
},
|
||||
[togglePin, pinnedEntries],
|
||||
[togglePin, pinnedEntries, labelSuffixRenderer],
|
||||
);
|
||||
|
||||
const labelRenderer = useCallback(
|
||||
(keyPath: KeyPath): React.ReactNode => {
|
||||
const displayKey = String(keyPath[0]);
|
||||
return (
|
||||
<span className="pretty-view__label">
|
||||
<span>{displayKey}</span>
|
||||
{labelSuffixRenderer?.(displayKey)}
|
||||
</span>
|
||||
);
|
||||
},
|
||||
[labelSuffixRenderer],
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -351,6 +367,7 @@ function PrettyView({
|
||||
shouldExpandNodeInitially={shouldExpandNodeInitially}
|
||||
valueRenderer={valueRenderer}
|
||||
getItemString={getItemString}
|
||||
labelRenderer={labelRenderer}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -13,8 +13,6 @@ interface Tab {
|
||||
disabled?: boolean;
|
||||
icon?: string | JSX.Element;
|
||||
isBeta?: boolean;
|
||||
/** Optional `data-testid` for the tab button. */
|
||||
testId?: string;
|
||||
}
|
||||
|
||||
interface TimelineTabsProps {
|
||||
@@ -65,7 +63,6 @@ function Tabs2({
|
||||
disabled={tab.disabled}
|
||||
icon={tab.icon}
|
||||
style={{ minWidth: buttonMinWidth }}
|
||||
data-testid={tab.testId}
|
||||
>
|
||||
{tab.label}
|
||||
|
||||
|
||||
14
frontend/src/types/api/semconvMigration.ts
Normal file
14
frontend/src/types/api/semconvMigration.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
export interface SemconvMigrationReportEntry {
|
||||
current: string;
|
||||
old: string;
|
||||
signal: string;
|
||||
services: string[];
|
||||
resourceSets: number;
|
||||
lastSeenUnixMilli: number;
|
||||
}
|
||||
|
||||
export interface SemconvMigrationReport {
|
||||
startUnixMilli: number;
|
||||
endUnixMilli: number;
|
||||
entries: SemconvMigrationReportEntry[];
|
||||
}
|
||||
@@ -117,6 +117,8 @@ export type SpaceAggregation =
|
||||
|
||||
export type ColumnType = 'group' | 'aggregation';
|
||||
|
||||
export type FieldResolution = 'exact';
|
||||
|
||||
// ===================== Variable Types =====================
|
||||
|
||||
export type VariableType = 'query' | 'dynamic' | 'custom' | 'text';
|
||||
@@ -136,6 +138,7 @@ export interface TelemetryFieldKey {
|
||||
signal?: SignalType;
|
||||
fieldContext?: FieldContext;
|
||||
fieldDataType?: FieldDataType;
|
||||
fieldResolution?: FieldResolution;
|
||||
materialized?: boolean;
|
||||
isIndexed?: boolean;
|
||||
}
|
||||
|
||||
40
frontend/src/utils/__tests__/semconv.test.ts
Normal file
40
frontend/src/utils/__tests__/semconv.test.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import {
|
||||
findOldSemconvNames,
|
||||
getSemconvMembers,
|
||||
getSemconvRename,
|
||||
} from 'utils/semconv';
|
||||
|
||||
describe('semantic convention helpers', () => {
|
||||
it('returns the current name for an old attribute', () => {
|
||||
expect(getSemconvRename('deployment.environment')).toMatchObject({
|
||||
old: 'deployment.environment',
|
||||
current: 'deployment.environment.name',
|
||||
});
|
||||
});
|
||||
|
||||
it('finds old names in editor text without matching larger custom names', () => {
|
||||
expect(
|
||||
findOldSemconvNames(
|
||||
"deployment.environment = 'prod' AND custom.db.system.value = 'x'",
|
||||
),
|
||||
).toStrictEqual([
|
||||
expect.objectContaining({
|
||||
old: 'deployment.environment',
|
||||
current: 'deployment.environment.name',
|
||||
}),
|
||||
]);
|
||||
});
|
||||
|
||||
it('does not warn for current names', () => {
|
||||
expect(
|
||||
findOldSemconvNames('deployment.environment.name = prod'),
|
||||
).toStrictEqual([]);
|
||||
});
|
||||
|
||||
it('returns current-first members for compatibility readers', () => {
|
||||
expect(getSemconvMembers('http.request.method')).toStrictEqual([
|
||||
'http.request.method',
|
||||
'http.method',
|
||||
]);
|
||||
});
|
||||
});
|
||||
61
frontend/src/utils/semconv.ts
Normal file
61
frontend/src/utils/semconv.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import {
|
||||
SEMCONV_FAMILIES,
|
||||
SemconvFamily,
|
||||
} from 'constants/generated/semconvFamilies.gen';
|
||||
|
||||
export type SemconvRename = {
|
||||
old: string;
|
||||
current: string;
|
||||
family: SemconvFamily;
|
||||
};
|
||||
|
||||
const OLD_NAMES = SEMCONV_FAMILIES.flatMap((family) =>
|
||||
family.old.map((old) => ({ old, current: family.current, family })),
|
||||
);
|
||||
|
||||
const OLD_NAME_INDEX = new Map(OLD_NAMES.map((rename) => [rename.old, rename]));
|
||||
const FAMILY_BY_NAME = new Map(
|
||||
SEMCONV_FAMILIES.flatMap((family) =>
|
||||
[family.current, ...family.old].map((name) => [name, family] as const),
|
||||
),
|
||||
);
|
||||
|
||||
export function getSemconvRename(name: string): SemconvRename | undefined {
|
||||
return OLD_NAME_INDEX.get(name);
|
||||
}
|
||||
|
||||
/** Returns the current name first, followed by every historical spelling. */
|
||||
export function getSemconvMembers(name: string): readonly string[] {
|
||||
const family = FAMILY_BY_NAME.get(name);
|
||||
return family ? [family.current, ...family.old] : [name];
|
||||
}
|
||||
|
||||
export function findOldSemconvNames(text: string): SemconvRename[] {
|
||||
if (!text) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return OLD_NAMES.filter(({ old }) => containsSemconvName(text, old));
|
||||
}
|
||||
|
||||
function containsSemconvName(text: string, name: string): boolean {
|
||||
let offset = 0;
|
||||
while (offset < text.length) {
|
||||
const index = text.indexOf(name, offset);
|
||||
if (index === -1) {
|
||||
return false;
|
||||
}
|
||||
const before = index === 0 ? '' : text[index - 1];
|
||||
const afterIndex = index + name.length;
|
||||
const after = afterIndex === text.length ? '' : text[afterIndex];
|
||||
if (!isSemconvNameCharacter(before) && !isSemconvNameCharacter(after)) {
|
||||
return true;
|
||||
}
|
||||
offset = index + 1;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function isSemconvNameCharacter(value: string): boolean {
|
||||
return /[A-Za-z0-9_.-]/.test(value);
|
||||
}
|
||||
@@ -50,30 +50,30 @@ primary
|
||||
* [NOT] BETWEEN, [NOT] IN, [NOT] EXISTS, [NOT] REGEXP, [NOT] CONTAINS, etc.
|
||||
*/
|
||||
comparison
|
||||
: key EQUALS value
|
||||
| key (NOT_EQUALS | NEQ) value
|
||||
| key LT value
|
||||
| key LE value
|
||||
| key GT value
|
||||
| key GE value
|
||||
: field EQUALS value
|
||||
| field (NOT_EQUALS | NEQ) value
|
||||
| field LT value
|
||||
| field LE value
|
||||
| field GT value
|
||||
| field GE value
|
||||
|
||||
| key (LIKE | ILIKE) value
|
||||
| key NOT (LIKE | ILIKE) value
|
||||
| field (LIKE | ILIKE) value
|
||||
| field NOT (LIKE | ILIKE) value
|
||||
|
||||
| key BETWEEN value AND value
|
||||
| key NOT BETWEEN value AND value
|
||||
| field BETWEEN value AND value
|
||||
| field NOT BETWEEN value AND value
|
||||
|
||||
| key inClause
|
||||
| key notInClause
|
||||
| field inClause
|
||||
| field notInClause
|
||||
|
||||
| key EXISTS
|
||||
| key NOT EXISTS
|
||||
| field EXISTS
|
||||
| field NOT EXISTS
|
||||
|
||||
| key REGEXP value
|
||||
| key NOT REGEXP value
|
||||
| field REGEXP value
|
||||
| field NOT REGEXP value
|
||||
|
||||
| key CONTAINS value
|
||||
| key NOT CONTAINS value
|
||||
| field CONTAINS value
|
||||
| field NOT CONTAINS value
|
||||
;
|
||||
|
||||
// in(...) or in[...]
|
||||
@@ -126,7 +126,7 @@ functionParamList
|
||||
;
|
||||
|
||||
functionParam
|
||||
: key
|
||||
: field
|
||||
| value
|
||||
| array
|
||||
;
|
||||
@@ -155,6 +155,17 @@ key
|
||||
: KEY
|
||||
;
|
||||
|
||||
// exact(key) disables semantic-convention family resolution for this field.
|
||||
// It is deliberately a field wrapper rather than a general function.
|
||||
field
|
||||
: key
|
||||
| exactCall
|
||||
;
|
||||
|
||||
exactCall
|
||||
: EXACT LPAREN key RPAREN
|
||||
;
|
||||
|
||||
|
||||
/*
|
||||
* Lexer Rules
|
||||
@@ -195,6 +206,7 @@ HAS : [Hh][Aa][Ss] ;
|
||||
HASANY : [Hh][Aa][Ss][Aa][Nn][Yy] ;
|
||||
HASALL : [Hh][Aa][Ss][Aa][Ll][Ll] ;
|
||||
SEARCH : [Ss][Ee][Aa][Rr][Cc][Hh] ;
|
||||
EXACT : [Ee][Xx][Aa][Cc][Tt] ;
|
||||
|
||||
// Potential boolean constants
|
||||
BOOL
|
||||
|
||||
@@ -46,5 +46,23 @@ func (provider *provider) addFieldsRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/fields/semconv-migration", handler.New(provider.authzMiddleware.ViewAccess(provider.fieldsHandler.GetSemconvMigrationReport), handler.OpenAPIDef{
|
||||
ID: "GetSemconvMigrationReport",
|
||||
Tags: []string{"fields"},
|
||||
Summary: "Get semantic-convention migration report",
|
||||
Description: "Returns services that still emit old semantic-convention names without the current family name",
|
||||
Request: nil,
|
||||
RequestQuery: new(telemetrytypes.PostableSemconvMigrationReportParams),
|
||||
RequestContentType: "",
|
||||
Response: new(telemetrytypes.GettableSemconvMigrationReport),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
|
||||
})).Methods(http.MethodGet).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -51,9 +51,9 @@ func postableRuleExamples() []handler.OpenAPIExample {
|
||||
"filter": map[string]any{"expression": "k8s.deployment.name = 'api-service'"},
|
||||
"groupBy": []any{
|
||||
map[string]any{"name": "k8s.pod.name", "fieldContext": "resource", "fieldDataType": "string"},
|
||||
map[string]any{"name": "deployment.environment", "fieldContext": "resource", "fieldDataType": "string"},
|
||||
map[string]any{"name": "deployment.environment.name", "fieldContext": "resource", "fieldDataType": "string"},
|
||||
},
|
||||
"legend": "{{k8s.pod.name}} ({{deployment.environment}})",
|
||||
"legend": "{{k8s.pod.name}} ({{deployment.environment.name}})",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -74,12 +74,12 @@ func postableRuleExamples() []handler.OpenAPIExample {
|
||||
},
|
||||
"evaluation": rolling("15m", "1m"),
|
||||
"notificationSettings": map[string]any{
|
||||
"groupBy": []any{"k8s.pod.name", "deployment.environment"},
|
||||
"groupBy": []any{"k8s.pod.name", "deployment.environment.name"},
|
||||
"renotify": renotify("4h", "firing"),
|
||||
},
|
||||
"labels": map[string]any{"severity": "critical", "team": "platform"},
|
||||
"annotations": map[string]any{
|
||||
"description": "Pod {{$k8s.pod.name}} CPU is at {{$value}} of request in {{$deployment.environment}}.",
|
||||
"description": "Pod {{$k8s.pod.name}} CPU is at {{$value}} of request in {{$deployment.environment.name}}.",
|
||||
"summary": "Pod CPU above {{$threshold}} of request",
|
||||
},
|
||||
},
|
||||
@@ -170,7 +170,7 @@ func postableRuleExamples() []handler.OpenAPIExample {
|
||||
{
|
||||
Name: "metric_promql",
|
||||
Summary: "Metric threshold PromQL rule",
|
||||
Description: "PromQL expression instead of the builder. Dotted OTEL resource attributes are quoted (\"deployment.environment\"). Useful for queries that combine series with group_right or other Prom operators.",
|
||||
Description: "PromQL expression instead of the builder. Dotted OTEL resource attributes are quoted (\"deployment.environment.name\"). Useful for queries that combine series with group_right or other Prom operators.",
|
||||
Value: map[string]any{
|
||||
"alert": "Kafka consumer group lag above 1000",
|
||||
"alertType": "METRIC_BASED_ALERT",
|
||||
@@ -187,7 +187,7 @@ func postableRuleExamples() []handler.OpenAPIExample {
|
||||
"type": "promql",
|
||||
"spec": map[string]any{
|
||||
"name": "A",
|
||||
"query": "(max by(topic, partition, \"deployment.environment\")(kafka_log_end_offset) - on(topic, partition, \"deployment.environment\") group_right max by(group, topic, partition, \"deployment.environment\")(kafka_consumer_committed_offset)) > 0",
|
||||
"query": "(max by(topic, partition, \"deployment.environment.name\")(kafka_log_end_offset) - on(topic, partition, \"deployment.environment.name\") group_right max by(group, topic, partition, \"deployment.environment.name\")(kafka_consumer_committed_offset)) > 0",
|
||||
"legend": "{{topic}}/{{partition}} ({{group}})",
|
||||
},
|
||||
},
|
||||
@@ -299,9 +299,9 @@ func postableRuleExamples() []handler.OpenAPIExample {
|
||||
"filter": map[string]any{"expression": "service.name = 'payments-api' AND severity_text = 'ERROR' AND body CONTAINS 'panic'"},
|
||||
"groupBy": []any{
|
||||
map[string]any{"name": "k8s.pod.name", "fieldContext": "resource", "fieldDataType": "string"},
|
||||
map[string]any{"name": "deployment.environment", "fieldContext": "resource", "fieldDataType": "string"},
|
||||
map[string]any{"name": "deployment.environment.name", "fieldContext": "resource", "fieldDataType": "string"},
|
||||
},
|
||||
"legend": "{{k8s.pod.name}} ({{deployment.environment}})",
|
||||
"legend": "{{k8s.pod.name}} ({{deployment.environment.name}})",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -322,12 +322,12 @@ func postableRuleExamples() []handler.OpenAPIExample {
|
||||
},
|
||||
"evaluation": rolling("5m", "1m"),
|
||||
"notificationSettings": map[string]any{
|
||||
"groupBy": []any{"k8s.pod.name", "deployment.environment"},
|
||||
"groupBy": []any{"k8s.pod.name", "deployment.environment.name"},
|
||||
"renotify": renotify("15m", "firing"),
|
||||
},
|
||||
"labels": map[string]any{"severity": "critical", "team": "payments"},
|
||||
"annotations": map[string]any{
|
||||
"description": "{{$k8s.pod.name}} emitted {{$value}} panic log(s) in {{$deployment.environment}}.",
|
||||
"description": "{{$k8s.pod.name}} emitted {{$value}} panic log(s) in {{$deployment.environment.name}}.",
|
||||
"summary": "Payments service panic",
|
||||
},
|
||||
},
|
||||
@@ -358,7 +358,7 @@ func postableRuleExamples() []handler.OpenAPIExample {
|
||||
"disabled": true,
|
||||
"aggregations": []any{map[string]any{"expression": "count()"}},
|
||||
"filter": map[string]any{"expression": "service.name = 'payments-api' AND severity_text IN ['ERROR', 'FATAL']"},
|
||||
"groupBy": []any{map[string]any{"name": "deployment.environment", "fieldContext": "resource", "fieldDataType": "string"}},
|
||||
"groupBy": []any{map[string]any{"name": "deployment.environment.name", "fieldContext": "resource", "fieldDataType": "string"}},
|
||||
},
|
||||
},
|
||||
map[string]any{
|
||||
@@ -370,7 +370,7 @@ func postableRuleExamples() []handler.OpenAPIExample {
|
||||
"disabled": true,
|
||||
"aggregations": []any{map[string]any{"expression": "count()"}},
|
||||
"filter": map[string]any{"expression": "service.name = 'payments-api'"},
|
||||
"groupBy": []any{map[string]any{"name": "deployment.environment", "fieldContext": "resource", "fieldDataType": "string"}},
|
||||
"groupBy": []any{map[string]any{"name": "deployment.environment.name", "fieldContext": "resource", "fieldDataType": "string"}},
|
||||
},
|
||||
},
|
||||
map[string]any{
|
||||
@@ -378,7 +378,7 @@ func postableRuleExamples() []handler.OpenAPIExample {
|
||||
"spec": map[string]any{
|
||||
"name": "F1",
|
||||
"expression": "(A / B) * 100",
|
||||
"legend": "{{deployment.environment}}",
|
||||
"legend": "{{deployment.environment.name}}",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -399,12 +399,12 @@ func postableRuleExamples() []handler.OpenAPIExample {
|
||||
},
|
||||
"evaluation": rolling("5m", "1m"),
|
||||
"notificationSettings": map[string]any{
|
||||
"groupBy": []any{"deployment.environment"},
|
||||
"groupBy": []any{"deployment.environment.name"},
|
||||
"renotify": renotify("30m", "firing"),
|
||||
},
|
||||
"labels": map[string]any{"severity": "critical", "team": "payments"},
|
||||
"annotations": map[string]any{
|
||||
"description": "Error log rate in {{$deployment.environment}} is {{$value}}%",
|
||||
"description": "Error log rate in {{$deployment.environment.name}} is {{$value}}%",
|
||||
"summary": "Payments-api error rate above {{$threshold}}%",
|
||||
},
|
||||
},
|
||||
@@ -669,10 +669,10 @@ func postableRuleExamples() []handler.OpenAPIExample {
|
||||
"stepInterval": 60,
|
||||
"disabled": true,
|
||||
"aggregations": []any{map[string]any{"expression": "count()"}},
|
||||
"filter": map[string]any{"expression": "service.name CONTAINS 'api' AND http.status_code >= 500"},
|
||||
"filter": map[string]any{"expression": "service.name CONTAINS 'api' AND http.response.status_code >= 500"},
|
||||
"groupBy": []any{
|
||||
map[string]any{"name": "service.name", "fieldContext": "resource", "fieldDataType": "string"},
|
||||
map[string]any{"name": "deployment.environment", "fieldContext": "resource", "fieldDataType": "string"},
|
||||
map[string]any{"name": "deployment.environment.name", "fieldContext": "resource", "fieldDataType": "string"},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -687,7 +687,7 @@ func postableRuleExamples() []handler.OpenAPIExample {
|
||||
"filter": map[string]any{"expression": "service.name CONTAINS 'api'"},
|
||||
"groupBy": []any{
|
||||
map[string]any{"name": "service.name", "fieldContext": "resource", "fieldDataType": "string"},
|
||||
map[string]any{"name": "deployment.environment", "fieldContext": "resource", "fieldDataType": "string"},
|
||||
map[string]any{"name": "deployment.environment.name", "fieldContext": "resource", "fieldDataType": "string"},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -696,7 +696,7 @@ func postableRuleExamples() []handler.OpenAPIExample {
|
||||
"spec": map[string]any{
|
||||
"name": "F1",
|
||||
"expression": "(A / B) * 100",
|
||||
"legend": "{{service.name}} ({{deployment.environment}})",
|
||||
"legend": "{{service.name}} ({{deployment.environment.name}})",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -717,14 +717,14 @@ func postableRuleExamples() []handler.OpenAPIExample {
|
||||
},
|
||||
"evaluation": rolling("5m", "1m"),
|
||||
"notificationSettings": map[string]any{
|
||||
"groupBy": []any{"service.name", "deployment.environment"},
|
||||
"groupBy": []any{"service.name", "deployment.environment.name"},
|
||||
"newGroupEvalDelay": "2m",
|
||||
"usePolicy": false,
|
||||
"renotify": renotify("30m", "firing", "nodata"),
|
||||
},
|
||||
"labels": map[string]any{"team": "platform"},
|
||||
"annotations": map[string]any{
|
||||
"description": "{{$service.name}} 5xx rate in {{$deployment.environment}} is {{$value}}%.",
|
||||
"description": "{{$service.name}} 5xx rate in {{$deployment.environment.name}} is {{$value}}%.",
|
||||
"summary": "API service error rate elevated",
|
||||
},
|
||||
},
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"strings"
|
||||
|
||||
parser "github.com/SigNoz/signoz/pkg/parser/filterquery/grammar"
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
"github.com/antlr4-go/antlr/v4"
|
||||
"golang.org/x/exp/maps"
|
||||
|
||||
@@ -187,25 +188,35 @@ func (r *WhereClauseRewriter) VisitPrimary(ctx *parser.PrimaryContext) any {
|
||||
|
||||
// VisitComparison visits comparison expressions.
|
||||
func (r *WhereClauseRewriter) VisitComparison(ctx *parser.ComparisonContext) any {
|
||||
if ctx.Key() == nil {
|
||||
if ctx.Field() == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
key := ctx.Key().GetText()
|
||||
field := ctx.Field().GetText()
|
||||
key := field
|
||||
if exactCall := ctx.Field().ExactCall(); exactCall != nil {
|
||||
key = exactCall.Key().GetText()
|
||||
}
|
||||
r.keysSeen[key] = struct{}{}
|
||||
parsedKey := telemetrytypes.GetFieldKeyFromKeyText(key)
|
||||
r.keysSeen[parsedKey.Name] = struct{}{}
|
||||
labelKey := key
|
||||
if _, exists := r.labels[labelKey]; !exists {
|
||||
labelKey = parsedKey.Name
|
||||
}
|
||||
|
||||
// Check if this key is in the labels and was part of group by
|
||||
if value, exists := r.labels[key]; exists {
|
||||
if _, partOfGroup := r.groupBySet[key]; partOfGroup {
|
||||
if value, exists := r.labels[labelKey]; exists {
|
||||
if _, partOfGroup := r.groupBySet[labelKey]; partOfGroup {
|
||||
// Case 1: Replace with actual value
|
||||
escapedValue := escapeValueIfNeeded(value)
|
||||
fmt.Fprintf(&r.rewritten, "%s=%s", key, escapedValue)
|
||||
fmt.Fprintf(&r.rewritten, "%s=%s", field, escapedValue)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, keep the original comparison
|
||||
r.rewritten.WriteString(key)
|
||||
r.rewritten.WriteString(field)
|
||||
|
||||
if ctx.EQUALS() != nil {
|
||||
r.rewritten.WriteString("=")
|
||||
@@ -408,8 +419,8 @@ func (r *WhereClauseRewriter) VisitFunctionParamList(ctx *parser.FunctionParamLi
|
||||
|
||||
// VisitFunctionParam visits function parameters.
|
||||
func (r *WhereClauseRewriter) VisitFunctionParam(ctx *parser.FunctionParamContext) any {
|
||||
if ctx.Key() != nil {
|
||||
ctx.Key().Accept(r)
|
||||
if ctx.Field() != nil {
|
||||
r.rewritten.WriteString(ctx.Field().GetText())
|
||||
} else if ctx.Value() != nil {
|
||||
ctx.Value().Accept(r)
|
||||
} else if ctx.Array() != nil {
|
||||
|
||||
@@ -234,6 +234,18 @@ func TestPrepareFiltersV5(t *testing.T) {
|
||||
expected: "(error_details EXISTS) AND service.name='serviceA'",
|
||||
description: "Should preserve EXISTS operator",
|
||||
},
|
||||
{
|
||||
name: "exact_field_label_replacement",
|
||||
labels: map[string]string{
|
||||
"deployment.environment": "production",
|
||||
},
|
||||
whereClause: "exact(resource.deployment.environment) = 'staging'",
|
||||
groupByItems: []qbtypes.GroupByKey{
|
||||
{TelemetryFieldKey: telemetrytypes.TelemetryFieldKey{Name: "deployment.environment"}},
|
||||
},
|
||||
expected: "exact(resource.deployment.environment)='production'",
|
||||
description: "Should keep the exact wrapper when replacing a grouped label",
|
||||
},
|
||||
|
||||
{
|
||||
name: "empty_where_clause_with_labels",
|
||||
|
||||
@@ -23,13 +23,13 @@ func TestSource(t *testing.T) {
|
||||
err := json.Unmarshal(buf.Bytes(), &m)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Contains(t, m, "code.filepath")
|
||||
assert.Contains(t, m, "code.function")
|
||||
assert.Contains(t, m, "code.lineno")
|
||||
assert.Contains(t, m, "code.file.path")
|
||||
assert.Contains(t, m, "code.function.name")
|
||||
assert.Contains(t, m, "code.line.number")
|
||||
|
||||
assert.Contains(t, m["code.filepath"], "source_test.go")
|
||||
assert.Contains(t, m["code.function"], "TestSource")
|
||||
assert.NotZero(t, m["code.lineno"])
|
||||
assert.Contains(t, m["code.file.path"], "source_test.go")
|
||||
assert.Contains(t, m["code.function.name"], "TestSource")
|
||||
assert.NotZero(t, m["code.line.number"])
|
||||
|
||||
// Ensure the nested "source" key is not present.
|
||||
assert.NotContains(t, m, "source")
|
||||
|
||||
@@ -136,7 +136,12 @@ func (v *visitor) VisitPrimary(ctx *grammar.PrimaryContext) any {
|
||||
// predicate; any other identifier is treated as a tag key — the operator
|
||||
// applies to the tag's value, with a case-insensitive match on the tag's key.
|
||||
func (v *visitor) VisitComparison(ctx *grammar.ComparisonContext) any {
|
||||
key := strings.ToLower(strings.TrimSpace(ctx.Key().GetText()))
|
||||
field := ctx.Field()
|
||||
keyText := field.GetText()
|
||||
if exactCall := field.ExactCall(); exactCall != nil {
|
||||
keyText = exactCall.Key().GetText()
|
||||
}
|
||||
key := strings.ToLower(strings.TrimSpace(keyText))
|
||||
|
||||
operation, ok := v.extractOperation(ctx)
|
||||
if !ok {
|
||||
@@ -427,7 +432,7 @@ func (v *visitor) buildFreeTextTerm(value string) string {
|
||||
}
|
||||
|
||||
// buildFreeTextContains emits a case-insensitive contains as
|
||||
// LOWER(COALESCE(col, '')) LIKE LOWER(?), identical on SQLite and Postgres.
|
||||
// LOWER(COALESCE(col, ”)) LIKE LOWER(?), identical on SQLite and Postgres.
|
||||
// COALESCE keeps a NULL column (an absent description) false rather than NULL —
|
||||
// otherwise `NOT (…)` goes NULL and drops every description-less dashboard. The
|
||||
// value's % and _ are escaped, and ESCAPE pins backslash as the escape char.
|
||||
|
||||
@@ -8,4 +8,7 @@ type Handler interface {
|
||||
|
||||
// Gets the fields values for the given field value selector
|
||||
GetFieldsValues(http.ResponseWriter, *http.Request)
|
||||
|
||||
// Gets services that still emit only historical semantic-convention names.
|
||||
GetSemconvMigrationReport(http.ResponseWriter, *http.Request)
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user