Compare commits

..

8 Commits

Author SHA1 Message Date
srikanthccv
a896731c5d refactor: inline canonical infrastructure metric names 2026-08-07 22:24:16 +05:30
srikanthccv
5b8067eeac chore: remove normalized metrics compatibility 2026-08-07 22:12:28 +05:30
Srikanth Chekuri
e0b278e8e2 test(querier): pin keyless-row semantics for filter operators (#12456)
## Summary

Adds an integration-test matrix that pins the deliberate keyless-row
contract for filter operators, independent of any feature work:

- **Negative operators are a set complement over all rows.** A row that
does not carry the key at all must match `!=`, `NOT IN`, `NOT LIKE`, and
`NOT CONTAINS`. Users opt into presence explicitly with `AND key
EXISTS`.
- **Positive operators carry an implicit existence guard**
(`FilterOperator.AddDefaultExistsFilter`), so keyless rows never
false-positive against sentinel defaults.
- **`EXISTS` / `NOT EXISTS` partition rows exactly** by key presence,
and `!= x AND EXISTS` is the documented composition for "present and not
x".
- **Numeric attributes inherit the map-default sentinel**: a missing key
reads as `0`, so `num != 5` includes keyless rows while `num != 0`
excludes them. This conflation is deliberate and pinned by name
(`numeric_neq_zero_sentinel_conflation`) as the reference point for any
value-expression change.

Coverage: 46 cases — one shared matrix over traces and logs (resource
and attribute contexts), metric labels (series without the label), the
numeric sentinel, and the EXISTS composition. The contract, matrix, seed
data, and assertions live together in one file so the contract reads top
to bottom.

## Why

These semantics were enforced only implicitly by the operator list in
`AddDefaultExistsFilter`, with no test naming the intent. That gap
allows an implementation change to alter negative-filter results
silently and lets new tests calibrate expectations against the
implementation instead of the contract. The attribute names used here
are deliberately outside every semantic-convention family, so this file
pins the base contract regardless of the semconv overlay state and
serves as the oracle that family-field behavior
(`queriertraces/13_semconv_evolution.py` in the semconv stack) must
mirror.

## Testing

- `uv run pytest --basetemp=./tmp/ --reuse
integration/tests/queriercommon/06_keyless_semantics.py` — 46/46 passed
against a stack built from main-based sources, and 2×46 against a
long-lived shared stack, confirming the set-based assertions stay stable
under environment reuse.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-07 15:30:07 +00:00
Srikanth Chekuri
a711cda7ba feat: generate semantic convention families (#12441)
## Summary

Adds the semantic-convention evolution foundation:

- vendors the OpenTelemetry schema and SigNoz overlay
- generates Go and TypeScript family tables deterministically
- exposes the Go resolver API for family members, current names, and
historical names
- adds generation checks and unit tests

Related to #6143.

## Stack

1. #12441 — Foundations (base: main)
2. #12442 — Phase 1 query (base: #12441)
3. #12443 — Phase 1 services (base: #12442)
4. #12444 — Phase 1 closure gate (base: #12443)
5. #12445 — Phase 2 signals (base: #12444)
6. #12446 — Phase 3 migration UX (base: #12445)
7. #12447 — Phase 4 rollout (base: #12446)

**Current layer:** #12441

## Testing

- `go test ./scripts/semconv`
- `go test ./pkg/types/telemetrytypes/semconv`
- `make semconv-check`

## Risk and rollback

This layer is additive apart from CI generation checks. Roll back by
reverting this PR; no stored telemetry is changed.
2026-08-07 15:27:50 +00:00
Nikhil Soni
e08ef01170 refactor(savedview): restructure api and storage to spec based (#12342)
## Summary

- Saved views now persist a versioned, typed spec (`schemaVersion` +
`spec{compositeQuery, selectedFields, display}`) instead of a bare
composite-query blob plus an opaque, frontend-owned `extraData` string
-- mirroring the pattern dashboards already use for their v2/perses
schema.
- `/api/v1/explorer/views` keeps working exactly as before: a thin
conversion layer translates to/from the legacy wire format, including
folding `extraData`'s ad hoc JSON into the typed spec and back for
backward compatibility.
- A one-time migration rewrites existing rows into the new shape and
drops the now-unused `extra_data`/`category`/`tags` columns.

### Scaffolding decisions 
- Using v2 for new handlers instead of renaming old handlers to
something else for these reasons - keep the diff minimum for easier
reviews, avoiding any git history or last updated at change in old route
registration.
- Keeping the conversion to old saved view type in handler itself rather
than `savedviewtypes` package to keep it un-exported and not let them be
available anywhere else to be used. It also enables `savedviewtypes` to
be independent on query-service models.
- Modified the existing handler and it's interface to include the v2
methods instead of adding another handlerV2 since apiserver already had
handler wired in, so don't want to pass on 2 version simultaneously.

### Breaking change
- Any unknown key in the `ExtraData` will be rejected and dropped
silently in the old APIs and give error in new version.
- If there was any way to add tag or category in saved view earlier,
that data will be lost.
- Old APIs will not support the old QB request payload, only v5 format
is supported.

---

Closes SigNoz/engineering-pod#4651

Alternative discarded https://github.com/SigNoz/signoz/pull/12208

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-07 13:37:56 +00:00
Pandey
00b7ecbd71 chore: remove the docs-check workflow (#12459)
#### Description

- Remove `.github/workflows/docs.yml`, which labeled `feat:` PRs with
"docs required" and failed the check until "docs shipped" was added.
- The job is not a required status check on `main`, and nothing else
references the workflow or its labels.
2026-08-07 13:12:22 +00:00
Pandey
7243560d8d chore: simplify PR template and add agent rules (#12457)
#### Description

- Replace the multi-section PR template (change type, risk assessment,
changelog, checklist) with four concise headings: Description, Issues
closed, Screenshots, Additional Information.
- Add `.claude/rules/` with agent rules for comments (repo-wide, Go,
Python) and pull requests.
- Ignore `.dev/` and `.claude/worktrees/` in `.gitignore`.
2026-08-07 12:39:27 +00:00
Pandey
53ab4546bc chore(deps): bump clickhouse-sql-parser to v0.5.5 (#12454)
Some checks failed
build-staging / prepare (push) Has been cancelled
build-staging / js-build (push) Has been cancelled
build-staging / go-build (push) Has been cancelled
build-staging / staging (push) Has been cancelled
cacheci / tests (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
Bumps `clickhouse-sql-parser` to v0.5.5, fixes the false rejection that
was left over once it landed, and closes three holes in the same
validator that the first two changes brought to light.

## The bump

**Reserved keywords as expression operands**
([#305](https://github.com/AfterShip/clickhouse-sql-parser/pull/305)).
`interval` was fixed in v0.5.4, but the same defect affected 36 other
keywords once the column appeared as an operand rather than bare.
Sweeping 94 candidates against ClickHouse 26.8.1.337, only `on` still
rejects — and ClickHouse runs that too. This one was live: `sum(limit)`
on a metric label.

**Panic on an unparseable `DEFAULT` expression**
([#306](https://github.com/AfterShip/clickhouse-sql-parser/pull/306)).
Both known cases return a parse error now instead of dereferencing nil.
The `recover` in `ErrIfStatementIsNotValid` stays — it guards the next
one of these, not these two.

[#307](https://github.com/AfterShip/clickhouse-sql-parser/pull/307) also
allows `CAST` in a table function's argument list.

## Table functions are only table functions in a table position

The parser types a call inside a table function's argument list as a
`TableFunctionExpr` as well, so the generator allow list only ever
cleared a generator whose argument was a literal. Every real dashboard
computes its row count — `numbers(greatest(1, intDiv(end_ns - start_ns,
step_ns) + 1))` — and every one was refused, on `intDiv` rather than on
`numbers`.

`TableExpr.Expr` is the only table position a SELECT can reach, so the
allow list asks that instead. Of the four places the parser builds a
`TableFunctionExpr`, two are `CREATE TABLE` paths rejected as
not-a-SELECT before the walk starts, one is `parseTableArgPrimaryExpr`,
and one is the `FROM`/`JOIN` path that wraps into a `TableExpr`.

## Three holes that were already open

Skipping argument position is only safe if nothing there can read, and
that turned out not to be true — not because of this change, but
independently of it.

**Reading functions.** `file` is both a table function and a scalar
function, and the validator never inspected scalar calls at all. On
`main` today, `SELECT file('/etc/passwd')` is accepted and returns the
file. A numeric wrapper passes ClickHouse's type check, so the row count
alone is an oracle: `numbers(length(file(x)))` yields one row per byte.
The same applies to the 42 dictionary accessors, which can be backed by
HTTP, ODBC or another database, to `catboostEvaluate`, and to the
introspection functions. All are now refused by name wherever they
appear, under `clickhouse_sql_reading_function`.

**`x IN db.table`.** ClickHouse reads this as `x IN (SELECT * FROM
db.table)`, and a qualified name on the right of `IN` parses as a
`Path`, not a `TableIdentifier` — so `SELECT * FROM t WHERE a IN
system.users` bypassed the internal-database rule entirely. Now checked,
including the `GLOBAL IN` and `NOT IN` forms.

**Quoted generator names.** The allow list matched on the formatted
name, which carries the quoting, so ``SELECT * FROM `numbers`(31)`` was
refused. It now reads the identifier the way the internal-database
branch already did.

## Effect

Replaying 72 distinct shapes of production `clickhouse_sql` that the
validator currently rejects: **64 pass, up from 59 on v0.5.4**. Two came
from the bump, three from the table-position change, and those three are
379 of the 1390 sampled occurrences. The three new rules add no false
positives to the corpus.

Of the eight left, four are correct rejections (`system` reads, `SHOW
TABLES`), one is a dashboard variable rendering as the literal `<no
value>`, one is SQL ClickHouse also rejects, and two are an open
upstream gap.

## Tests

`TestErrIfStatementIsNotValid_ShouldPassButFails` is back, holding what
remains: three forms of a parenthesised left operand of a set operator,
and `on` as a column name. It also stopped panicking — `errors.Asc`
dereferences the error it is given, so a case starting to pass took the
suite out with a SIGSEGV instead of reporting. Both refusal tables now
share one harness, bounded by the same timeout the passing table uses.

Known gap: no input is currently known to panic the parser, so the
`recover` has no test exercising it.
2026-08-07 10:39:21 +00:00
129 changed files with 7572 additions and 2414 deletions

11
.claude/rules/comments.md Normal file
View File

@@ -0,0 +1,11 @@
# Comments
Applies to everything in the repo — code, config, workflows.
- **No unnecessary comments.** Do not comment where the code is self-explanatory; never restate what the code already says.
- **Document only** non-obvious behavior, constraints, formats, and edge cases.
- **Rationale goes in prose, not source.** Why a version is pinned, why a job exists, how a subsystem fits together — that belongs in the README or the PR.
- **Never remove pre-existing comments** when editing code. The bar above applies to comments you write, not comments already there.
- **Never talk to the reviewer.** No comments about where a change came from, what was changed, or why the change is correct — that belongs in the PR description and is noise the moment it merges.
Language rules build on this one: [`go-comments`](go-comments.md), [`py-comments`](py-comments.md).

View File

@@ -0,0 +1,12 @@
---
paths:
- "**/*.go"
---
# Go comments
The bar is the [`comments`](comments.md) rule: nothing where the code is self-explanatory.
- **Names carry the meaning.** Make function, type, and variable names self-explanatory so the comment is unnecessary in the first place. If a comment is needed to explain what a function does, fix the name, not the comment.
- **Godoc**: Skip comments that merely restate the identifier. Document only non-obvious behavior, constraints, formats, and edge cases.
- **Generated code**: If the comment is emitted by an external codegen tool, leave it as-is — do not add or trim comments in generated files.

View File

@@ -0,0 +1,7 @@
# Pull requests
- **Follow the template** (`.github/pull_request_template.md`): fill in its headings (Description / Issues closed by this PR / Screenshots / Additional Information). Don't add sections the template doesn't have.
- **Keep only the headings that apply.** Delete every heading that has nothing under it, along with its `<!--...-->` placeholder comment. The body must never contain an empty heading — if only Description applies, the body has exactly that one heading.
- **Keep the description concise and human-readable.** A few plain bullets saying what changed and why, for a reviewer skimming it — not a wall of text, not a restatement of the diff, not generated boilerplate.
- **Reference issues with `Closes #issue-number`** under "Issues closed by this PR" so they auto-close on merge. This goes in the PR description only — never in commit messages.
- **AI assistance in commits may optionally be disclosed with an `Assisted-by:` trailer** naming the model (e.g. `Assisted-by: Claude Opus 4.5`) — do NOT use a `Co-authored-by:` trailer for this.

View File

@@ -0,0 +1,13 @@
---
paths:
- "**/*.py"
---
# Python comments
The bar is the [`comments`](comments.md) rule: nothing where the code is self-explanatory.
- **Names carry the meaning.** Make function and variable names self-explanatory so the comment or docstring is unnecessary in the first place. If a docstring is needed to explain what a function does, fix the name, not the docstring.
- **No file-level docstring.** The filename says what the module is for — `tool_bin.py` gets the tool binary. A module docstring restating that is noise, and a paragraph of design prose at the top of a file goes stale where nobody is looking. A constraint belongs next to the code it constrains, not in a preamble.
- **Docstrings**: only when they say something the name and signature don't — drop them otherwise. Keep them short. A contract that genuinely needs a few lines (interacting flags, retry semantics, an edge case) is fine; a narrative is not.
- **No song and dance.** Comment the constraint or the edge case. Not the narrative, not the rationale, not what the next line does.

View File

@@ -1,85 +1,13 @@
## Pull Request
---
### 📄 Summary
> Why does this change exist?
> What problem does it solve, and why is this the right approach?
#### Screenshots / Screen Recordings (if applicable)
> Include screenshots or screen recordings that clearly show the behavior before the change and the result after the change. This helps reviewers quickly understand the impact and verify the update.
<!--A few plain bullets saying what changed and why, for a reviewer skimming it - not a wall of text, not a restatement of the diff, not generated boilerplate.-->
#### Description
<!--Reference issues using `Closes #issue-number` to enable automatic closure on merge. -->
#### Issues closed by this PR
> Reference issues using `Closes #issue-number` to enable automatic closure on merge.
---
<!--If applicable, include screenshots or screen recordings that clearly show the behavior before the change and the result after the change. -->
#### Screenshots / Screen Recordings
### ✅ Change Type
_Select all that apply_
<!--Anything reviewers should keep in mind while reviewing -->
#### Additional Information
- [ ] ✨ Feature
- [ ] 🐛 Bug fix
- [ ] ♻️ Refactor
- [ ] 🛠️ Infra / Tooling
- [ ] 🧪 Test-only
---
### 🐛 Bug Context
> Required if this PR fixes a bug
#### Root Cause
> What caused the issue?
> Regression, faulty assumption, edge case, refactor, etc.
#### Fix Strategy
> How does this PR address the root cause?
---
### 🧪 Testing Strategy
> How was this change validated?
- Tests added/updated:
- Manual verification:
- Edge cases covered:
---
### ⚠️ Risk & Impact Assessment
> What could break? How do we recover?
- Blast radius:
- Potential regressions:
- Rollback plan:
---
### 📝 Changelog
> Fill only if this affects users, APIs, UI, or documented behavior
> Use **N/A** for internal or non-user-facing changes
| Field | Value |
|------|-------|
| Deployment Type | Cloud / OSS / Enterprise |
| Change Type | Feature / Bug Fix / Maintenance |
| Description | User-facing summary |
---
### 📋 Checklist
- [ ] Tests added or explicitly not required
- [ ] Manually tested
- [ ] Breaking changes documented
- [ ] Backward compatibility considered
---
## 👀 Notes for Reviewers
<!-- Anything reviewers should keep in mind while reviewing -->
---
<!--Please delete paragraphs that you did not use before submitting.-->

View File

@@ -1,83 +0,0 @@
name: "Update PR labels and Block PR until related docs are shipped for the feature"
on:
pull_request:
branches:
- main
types: [opened, edited, labeled, unlabeled]
permissions:
pull-requests: write
contents: read
jobs:
docs_label_check:
runs-on: ubuntu-latest
steps:
- name: Check PR Title and Manage Labels
uses: actions/github-script@v6
with:
script: |
const prTitle = context.payload.pull_request.title;
const prNumber = context.payload.pull_request.number;
const owner = context.repo.owner;
const repo = context.repo.repo;
// Fetch the current PR details to get labels
const pr = await github.rest.pulls.get({
owner,
repo,
pull_number: prNumber
});
const labels = pr.data.labels.map(label => label.name);
if (prTitle.startsWith('feat:')) {
const hasDocsRequired = labels.includes('docs required');
const hasDocsShipped = labels.includes('docs shipped');
const hasDocsNotRequired = labels.includes('docs not required');
// If "docs not required" is present, skip the checks
if (hasDocsNotRequired && !hasDocsRequired) {
console.log("Skipping checks due to 'docs not required' label.");
return; // Exit the script early
}
// If "docs shipped" is present, remove "docs required" if it exists
if (hasDocsShipped && hasDocsRequired) {
await github.rest.issues.removeLabel({
owner,
repo,
issue_number: prNumber,
name: 'docs required'
});
console.log("Removed 'docs required' label.");
}
// Add "docs required" label if neither "docs shipped" nor "docs required" are present
if (!hasDocsRequired && !hasDocsShipped) {
await github.rest.issues.addLabels({
owner,
repo,
issue_number: prNumber,
labels: ['docs required']
});
console.log("Added 'docs required' label.");
}
}
// Fetch the updated labels after any changes
const updatedPr = await github.rest.pulls.get({
owner,
repo,
pull_number: prNumber
});
const updatedLabels = updatedPr.data.labels.map(label => label.name);
const updatedHasDocsRequired = updatedLabels.includes('docs required');
const updatedHasDocsShipped = updatedLabels.includes('docs shipped');
// Block PR if "docs required" is still present and "docs shipped" is missing
if (updatedHasDocsRequired && !updatedHasDocsShipped) {
core.setFailed("This PR requires documentation. Please remove the 'docs required' label and add the 'docs shipped' label to proceed.");
}

View File

@@ -53,6 +53,21 @@ jobs:
with:
PRIMUS_REF: main
GO_VERSION: 1.24
semconv-generated:
if: |
github.event_name == 'merge_group' ||
(github.event_name == 'pull_request' && ! github.event.pull_request.head.repo.fork && github.event.pull_request.user.login != 'dependabot[bot]' && ! contains(github.event.pull_request.labels.*.name, 'safe-to-test')) ||
(github.event_name == 'pull_request_target' && contains(github.event.pull_request.labels.*.name, 'safe-to-test'))
runs-on: ubuntu-latest
steps:
- name: self-checkout
uses: actions/checkout@v4
- name: go-install
uses: actions/setup-go@v5
with:
go-version: "1.24"
- name: check-semconv-generated-files
run: go run ./scripts/semconv -check
build:
if: |
github.event_name == 'merge_group' ||

6
.gitignore vendored
View File

@@ -90,8 +90,6 @@ queries.active
.devenv/**/tmp/**
.qodo
.dev
### Python ###
# Byte-compiled / optimized / DLL files
__pycache__/
@@ -231,4 +229,6 @@ cython_debug/
# LSP config files
pyrightconfig.json
# dev
.dev/
.claude/worktrees/

View File

@@ -233,6 +233,10 @@ py-clean: ## Clear all pycache and pytest cache from tests directory recursively
##############################################################
# generate commands
##############################################################
.PHONY: semconv-generate
semconv-generate: ## Regenerate semantic-convention families for Go and TypeScript
@go run ./scripts/semconv
.PHONY: gen-mocks
gen-mocks:
@echo ">> Generating mocks"

View File

@@ -7759,6 +7759,112 @@ components:
enum:
- basic
type: string
SavedviewtypesDisplay:
properties:
color:
type: string
fontSize:
type: string
format:
type: string
maxLines:
type: integer
type: object
SavedviewtypesPanelType:
enum:
- value
- graph
- table
- list
- trace
type: string
SavedviewtypesPostableSavedView:
properties:
data:
$ref: '#/components/schemas/SavedviewtypesSavedViewData'
generateName:
type: boolean
name:
type: string
source:
$ref: '#/components/schemas/SavedviewtypesSource'
required:
- source
- data
type: object
SavedviewtypesSavedView:
properties:
createdAt:
format: date-time
type: string
createdBy:
type: string
data:
$ref: '#/components/schemas/SavedviewtypesSavedViewData'
id:
type: string
name:
type: string
source:
$ref: '#/components/schemas/SavedviewtypesSource'
updatedAt:
format: date-time
type: string
updatedBy:
type: string
required:
- id
type: object
SavedviewtypesSavedViewData:
properties:
schemaVersion:
type: string
spec:
$ref: '#/components/schemas/SavedviewtypesSavedViewSpec'
required:
- schemaVersion
- spec
type: object
SavedviewtypesSavedViewSpec:
properties:
display:
$ref: '#/components/schemas/SavedviewtypesDisplay'
displayName:
type: string
panelType:
$ref: '#/components/schemas/SavedviewtypesPanelType'
queries:
items:
$ref: '#/components/schemas/Querybuildertypesv5QueryEnvelope'
type: array
selectedFields:
items:
$ref: '#/components/schemas/TelemetrytypesTelemetryFieldKey'
type: array
required:
- displayName
- panelType
- queries
- selectedFields
- display
type: object
SavedviewtypesSource:
enum:
- traces
- logs
- metrics
- meter
type: string
SavedviewtypesUpdatableSavedView:
properties:
data:
$ref: '#/components/schemas/SavedviewtypesSavedViewData'
source:
$ref: '#/components/schemas/SavedviewtypesSource'
required:
- source
- data
type: object
ServiceaccounttypesDeprecatedPostableServiceAccountRole:
properties:
id:
@@ -22659,6 +22765,298 @@ paths:
summary: Test alert rule
tags:
- rules
/api/v2/saved_views:
get:
deprecated: false
description: Returns saved views, optionally filtered by source and name.
operationId: ListSavedViews
parameters:
- in: query
name: source
schema:
$ref: '#/components/schemas/SavedviewtypesSource'
- in: query
name: name
schema:
type: string
responses:
"200":
content:
application/json:
schema:
properties:
data:
items:
$ref: '#/components/schemas/SavedviewtypesSavedView'
nullable: true
type: array
status:
type: string
required:
- status
- data
type: object
description: OK
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Bad Request
"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:
- saved-view:list
- tokenizer:
- saved-view:list
summary: List saved views
tags:
- saved_view
post:
deprecated: false
description: Persists a saved view for the explore page. Returns the id of the
created view.
operationId: CreateSavedView
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/SavedviewtypesPostableSavedView'
responses:
"201":
content:
application/json:
schema:
properties:
data:
$ref: '#/components/schemas/TypesIdentifiable'
status:
type: string
required:
- status
- data
type: object
description: Created
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Bad Request
"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:
- saved-view:create
- tokenizer:
- saved-view:create
summary: Create saved view
tags:
- saved_view
/api/v2/saved_views/{id}:
delete:
deprecated: false
description: Deletes a saved view by id.
operationId: DeleteSavedView
parameters:
- in: path
name: id
required: true
schema:
type: string
responses:
"204":
description: No Content
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Bad Request
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Unauthorized
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Forbidden
"404":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Not Found
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Internal Server Error
security:
- api_key:
- saved-view:delete
- tokenizer:
- saved-view:delete
summary: Delete saved view
tags:
- saved_view
get:
deprecated: false
description: Returns a saved view by id.
operationId: GetSavedView
parameters:
- in: path
name: id
required: true
schema:
type: string
responses:
"200":
content:
application/json:
schema:
properties:
data:
$ref: '#/components/schemas/SavedviewtypesSavedView'
status:
type: string
required:
- status
- data
type: object
description: OK
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Bad Request
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Unauthorized
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Forbidden
"404":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Not Found
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Internal Server Error
security:
- api_key:
- saved-view:read
- tokenizer:
- saved-view:read
summary: Get saved view
tags:
- saved_view
put:
deprecated: false
description: Replaces a saved view's name and query.
operationId: UpdateSavedView
parameters:
- in: path
name: id
required: true
schema:
type: string
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/SavedviewtypesUpdatableSavedView'
responses:
"204":
description: No Content
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Bad Request
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Unauthorized
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Forbidden
"404":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Not Found
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Internal Server Error
security:
- api_key:
- saved-view:update
- tokenizer:
- saved-view:update
summary: Update saved view
tags:
- saved_view
/api/v2/sessions:
delete:
deprecated: false

View File

@@ -98,14 +98,6 @@ func (ah *APIHandler) getFeatureFlags(w http.ResponseWriter, r *http.Request) {
Route: "",
})
if constants.IsDotMetricsEnabled {
for idx, feature := range featureSet {
if feature.Name == licensetypes.DotMetricsEnabled {
featureSet[idx].Active = true
}
}
}
ah.Respond(w, featureSet)
}

View File

@@ -17,15 +17,3 @@ func GetOrDefaultEnv(key string, fallback string) string {
}
return v
}
// constant functions that override env vars
const DotMetricsEnabled = "DOT_METRICS_ENABLED"
var IsDotMetricsEnabled = false
func init() {
if GetOrDefaultEnv(DotMetricsEnabled, "true") == "true" {
IsDotMetricsEnabled = true
}
}

View File

@@ -0,0 +1,490 @@
/**
* ! Do not edit manually
* * The file has been auto-generated using Orval for SigNoz
* * regenerate with 'pnpm generate:api'
* SigNoz
*/
import { useMutation, useQuery } from 'react-query';
import type {
InvalidateOptions,
MutationFunction,
QueryClient,
QueryFunction,
QueryKey,
UseMutationOptions,
UseMutationResult,
UseQueryOptions,
UseQueryResult,
} from 'react-query';
import type {
CreateSavedView201,
DeleteSavedViewPathParameters,
GetSavedView200,
GetSavedViewPathParameters,
ListSavedViews200,
ListSavedViewsParams,
RenderErrorResponseDTO,
SavedviewtypesPostableSavedViewDTO,
SavedviewtypesUpdatableSavedViewDTO,
UpdateSavedViewPathParameters,
} from '../sigNoz.schemas';
import { GeneratedAPIInstance } from '../../../generatedAPIInstance';
import type { ErrorType, BodyType } from '../../../generatedAPIInstance';
/**
* Returns saved views, optionally filtered by source and name.
* @summary List saved views
*/
export const listSavedViews = (
params?: ListSavedViewsParams,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<ListSavedViews200>({
url: `/api/v2/saved_views`,
method: 'GET',
params,
signal,
});
};
export const getListSavedViewsQueryKey = (params?: ListSavedViewsParams) => {
return [`/api/v2/saved_views`, ...(params ? [params] : [])] as const;
};
export const getListSavedViewsQueryOptions = <
TData = Awaited<ReturnType<typeof listSavedViews>>,
TError = ErrorType<RenderErrorResponseDTO>,
>(
params?: ListSavedViewsParams,
options?: {
query?: UseQueryOptions<
Awaited<ReturnType<typeof listSavedViews>>,
TError,
TData
>;
},
) => {
const { query: queryOptions } = options ?? {};
const queryKey = queryOptions?.queryKey ?? getListSavedViewsQueryKey(params);
const queryFn: QueryFunction<Awaited<ReturnType<typeof listSavedViews>>> = ({
signal,
}) => listSavedViews(params, signal);
return { queryKey, queryFn, ...queryOptions } as UseQueryOptions<
Awaited<ReturnType<typeof listSavedViews>>,
TError,
TData
> & { queryKey: QueryKey };
};
export type ListSavedViewsQueryResult = NonNullable<
Awaited<ReturnType<typeof listSavedViews>>
>;
export type ListSavedViewsQueryError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary List saved views
*/
export function useListSavedViews<
TData = Awaited<ReturnType<typeof listSavedViews>>,
TError = ErrorType<RenderErrorResponseDTO>,
>(
params?: ListSavedViewsParams,
options?: {
query?: UseQueryOptions<
Awaited<ReturnType<typeof listSavedViews>>,
TError,
TData
>;
},
): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
const queryOptions = getListSavedViewsQueryOptions(params, options);
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
queryKey: QueryKey;
};
return { ...query, queryKey: queryOptions.queryKey };
}
/**
* @summary List saved views
*/
export const invalidateListSavedViews = async (
queryClient: QueryClient,
params?: ListSavedViewsParams,
options?: InvalidateOptions,
): Promise<QueryClient> => {
await queryClient.invalidateQueries(
{ queryKey: getListSavedViewsQueryKey(params) },
options,
);
return queryClient;
};
/**
* Persists a saved view for the explore page. Returns the id of the created view.
* @summary Create saved view
*/
export const createSavedView = (
savedviewtypesPostableSavedViewDTO?: BodyType<SavedviewtypesPostableSavedViewDTO>,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<CreateSavedView201>({
url: `/api/v2/saved_views`,
method: 'POST',
headers: { 'Content-Type': 'application/json' },
data: savedviewtypesPostableSavedViewDTO,
signal,
});
};
export const getCreateSavedViewMutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof createSavedView>>,
TError,
{ data?: BodyType<SavedviewtypesPostableSavedViewDTO> },
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof createSavedView>>,
TError,
{ data?: BodyType<SavedviewtypesPostableSavedViewDTO> },
TContext
> => {
const mutationKey = ['createSavedView'];
const { mutation: mutationOptions } = options
? options.mutation &&
'mutationKey' in options.mutation &&
options.mutation.mutationKey
? options
: { ...options, mutation: { ...options.mutation, mutationKey } }
: { mutation: { mutationKey } };
const mutationFn: MutationFunction<
Awaited<ReturnType<typeof createSavedView>>,
{ data?: BodyType<SavedviewtypesPostableSavedViewDTO> }
> = (props) => {
const { data } = props ?? {};
return createSavedView(data);
};
return { mutationFn, ...mutationOptions };
};
export type CreateSavedViewMutationResult = NonNullable<
Awaited<ReturnType<typeof createSavedView>>
>;
export type CreateSavedViewMutationBody =
| BodyType<SavedviewtypesPostableSavedViewDTO>
| undefined;
export type CreateSavedViewMutationError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary Create saved view
*/
export const useCreateSavedView = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof createSavedView>>,
TError,
{ data?: BodyType<SavedviewtypesPostableSavedViewDTO> },
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof createSavedView>>,
TError,
{ data?: BodyType<SavedviewtypesPostableSavedViewDTO> },
TContext
> => {
return useMutation(getCreateSavedViewMutationOptions(options));
};
/**
* Deletes a saved view by id.
* @summary Delete saved view
*/
export const deleteSavedView = (
{ id }: DeleteSavedViewPathParameters,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<void>({
url: `/api/v2/saved_views/${id}`,
method: 'DELETE',
signal,
});
};
export const getDeleteSavedViewMutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof deleteSavedView>>,
TError,
{ pathParams: DeleteSavedViewPathParameters },
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof deleteSavedView>>,
TError,
{ pathParams: DeleteSavedViewPathParameters },
TContext
> => {
const mutationKey = ['deleteSavedView'];
const { mutation: mutationOptions } = options
? options.mutation &&
'mutationKey' in options.mutation &&
options.mutation.mutationKey
? options
: { ...options, mutation: { ...options.mutation, mutationKey } }
: { mutation: { mutationKey } };
const mutationFn: MutationFunction<
Awaited<ReturnType<typeof deleteSavedView>>,
{ pathParams: DeleteSavedViewPathParameters }
> = (props) => {
const { pathParams } = props ?? {};
return deleteSavedView(pathParams);
};
return { mutationFn, ...mutationOptions };
};
export type DeleteSavedViewMutationResult = NonNullable<
Awaited<ReturnType<typeof deleteSavedView>>
>;
export type DeleteSavedViewMutationError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary Delete saved view
*/
export const useDeleteSavedView = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof deleteSavedView>>,
TError,
{ pathParams: DeleteSavedViewPathParameters },
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof deleteSavedView>>,
TError,
{ pathParams: DeleteSavedViewPathParameters },
TContext
> => {
return useMutation(getDeleteSavedViewMutationOptions(options));
};
/**
* Returns a saved view by id.
* @summary Get saved view
*/
export const getSavedView = (
{ id }: GetSavedViewPathParameters,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<GetSavedView200>({
url: `/api/v2/saved_views/${id}`,
method: 'GET',
signal,
});
};
export const getGetSavedViewQueryKey = ({ id }: GetSavedViewPathParameters) => {
return [`/api/v2/saved_views/${id}`] as const;
};
export const getGetSavedViewQueryOptions = <
TData = Awaited<ReturnType<typeof getSavedView>>,
TError = ErrorType<RenderErrorResponseDTO>,
>(
{ id }: GetSavedViewPathParameters,
options?: {
query?: UseQueryOptions<
Awaited<ReturnType<typeof getSavedView>>,
TError,
TData
>;
},
) => {
const { query: queryOptions } = options ?? {};
const queryKey = queryOptions?.queryKey ?? getGetSavedViewQueryKey({ id });
const queryFn: QueryFunction<Awaited<ReturnType<typeof getSavedView>>> = ({
signal,
}) => getSavedView({ id }, signal);
return {
queryKey,
queryFn,
enabled: !!id,
...queryOptions,
} as UseQueryOptions<
Awaited<ReturnType<typeof getSavedView>>,
TError,
TData
> & { queryKey: QueryKey };
};
export type GetSavedViewQueryResult = NonNullable<
Awaited<ReturnType<typeof getSavedView>>
>;
export type GetSavedViewQueryError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary Get saved view
*/
export function useGetSavedView<
TData = Awaited<ReturnType<typeof getSavedView>>,
TError = ErrorType<RenderErrorResponseDTO>,
>(
{ id }: GetSavedViewPathParameters,
options?: {
query?: UseQueryOptions<
Awaited<ReturnType<typeof getSavedView>>,
TError,
TData
>;
},
): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
const queryOptions = getGetSavedViewQueryOptions({ id }, options);
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
queryKey: QueryKey;
};
return { ...query, queryKey: queryOptions.queryKey };
}
/**
* @summary Get saved view
*/
export const invalidateGetSavedView = async (
queryClient: QueryClient,
{ id }: GetSavedViewPathParameters,
options?: InvalidateOptions,
): Promise<QueryClient> => {
await queryClient.invalidateQueries(
{ queryKey: getGetSavedViewQueryKey({ id }) },
options,
);
return queryClient;
};
/**
* Replaces a saved view's name and query.
* @summary Update saved view
*/
export const updateSavedView = (
{ id }: UpdateSavedViewPathParameters,
savedviewtypesUpdatableSavedViewDTO?: BodyType<SavedviewtypesUpdatableSavedViewDTO>,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<void>({
url: `/api/v2/saved_views/${id}`,
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
data: savedviewtypesUpdatableSavedViewDTO,
signal,
});
};
export const getUpdateSavedViewMutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof updateSavedView>>,
TError,
{
pathParams: UpdateSavedViewPathParameters;
data?: BodyType<SavedviewtypesUpdatableSavedViewDTO>;
},
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof updateSavedView>>,
TError,
{
pathParams: UpdateSavedViewPathParameters;
data?: BodyType<SavedviewtypesUpdatableSavedViewDTO>;
},
TContext
> => {
const mutationKey = ['updateSavedView'];
const { mutation: mutationOptions } = options
? options.mutation &&
'mutationKey' in options.mutation &&
options.mutation.mutationKey
? options
: { ...options, mutation: { ...options.mutation, mutationKey } }
: { mutation: { mutationKey } };
const mutationFn: MutationFunction<
Awaited<ReturnType<typeof updateSavedView>>,
{
pathParams: UpdateSavedViewPathParameters;
data?: BodyType<SavedviewtypesUpdatableSavedViewDTO>;
}
> = (props) => {
const { pathParams, data } = props ?? {};
return updateSavedView(pathParams, data);
};
return { mutationFn, ...mutationOptions };
};
export type UpdateSavedViewMutationResult = NonNullable<
Awaited<ReturnType<typeof updateSavedView>>
>;
export type UpdateSavedViewMutationBody =
| BodyType<SavedviewtypesUpdatableSavedViewDTO>
| undefined;
export type UpdateSavedViewMutationError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary Update saved view
*/
export const useUpdateSavedView = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof updateSavedView>>,
TError,
{
pathParams: UpdateSavedViewPathParameters;
data?: BodyType<SavedviewtypesUpdatableSavedViewDTO>;
},
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof updateSavedView>>,
TError,
{
pathParams: UpdateSavedViewPathParameters;
data?: BodyType<SavedviewtypesUpdatableSavedViewDTO>;
},
TContext
> => {
return useMutation(getUpdateSavedViewMutationOptions(options));
};

View File

@@ -8858,6 +8858,112 @@ export interface RuletypesRuleDTO {
export enum RuletypesThresholdKindDTO {
basic = 'basic',
}
export interface SavedviewtypesDisplayDTO {
/**
* @type string
*/
color?: string;
/**
* @type string
*/
fontSize?: string;
/**
* @type string
*/
format?: string;
/**
* @type integer
*/
maxLines?: number;
}
export enum SavedviewtypesPanelTypeDTO {
value = 'value',
graph = 'graph',
table = 'table',
list = 'list',
trace = 'trace',
}
export interface SavedviewtypesSavedViewSpecDTO {
display: SavedviewtypesDisplayDTO;
/**
* @type string
*/
displayName: string;
panelType: SavedviewtypesPanelTypeDTO;
/**
* @type array
*/
queries: Querybuildertypesv5QueryEnvelopeDTO[];
/**
* @type array
*/
selectedFields: TelemetrytypesTelemetryFieldKeyDTO[];
}
export interface SavedviewtypesSavedViewDataDTO {
/**
* @type string
*/
schemaVersion: string;
spec: SavedviewtypesSavedViewSpecDTO;
}
export enum SavedviewtypesSourceDTO {
traces = 'traces',
logs = 'logs',
metrics = 'metrics',
meter = 'meter',
}
export interface SavedviewtypesPostableSavedViewDTO {
data: SavedviewtypesSavedViewDataDTO;
/**
* @type boolean
*/
generateName?: boolean;
/**
* @type string
*/
name?: string;
source: SavedviewtypesSourceDTO;
}
export interface SavedviewtypesSavedViewDTO {
/**
* @type string
* @format date-time
*/
createdAt?: string;
/**
* @type string
*/
createdBy?: string;
data?: SavedviewtypesSavedViewDataDTO;
/**
* @type string
*/
id: string;
/**
* @type string
*/
name?: string;
source?: SavedviewtypesSourceDTO;
/**
* @type string
* @format date-time
*/
updatedAt?: string;
/**
* @type string
*/
updatedBy?: string;
}
export interface SavedviewtypesUpdatableSavedViewDTO {
data: SavedviewtypesSavedViewDataDTO;
source: SavedviewtypesSourceDTO;
}
export interface ServiceaccounttypesDeprecatedPostableServiceAccountRoleDTO {
/**
* @type string
@@ -12056,6 +12162,54 @@ export type TestRule200 = {
status: string;
};
export type ListSavedViewsParams = {
/**
* @description undefined
*/
source?: SavedviewtypesSourceDTO;
/**
* @type string
* @description undefined
*/
name?: string;
};
export type ListSavedViews200 = {
/**
* @type array,null
*/
data: SavedviewtypesSavedViewDTO[] | null;
/**
* @type string
*/
status: string;
};
export type CreateSavedView201 = {
data: TypesIdentifiableDTO;
/**
* @type string
*/
status: string;
};
export type DeleteSavedViewPathParameters = {
id: string;
};
export type GetSavedViewPathParameters = {
id: string;
};
export type GetSavedView200 = {
data: SavedviewtypesSavedViewDTO;
/**
* @type string
*/
status: string;
};
export type UpdateSavedViewPathParameters = {
id: string;
};
export type GetSessionContext200 = {
data: AuthtypesSessionContextDTO;
/**

View File

@@ -24,19 +24,3 @@ export const Logout = async (): Promise<void> => {
window.dispatchEvent(new CustomEvent('LOGOUT'));
history.push(ROUTES.LOGIN);
};
export const UnderscoreToDotMap: Record<string, string> = {
k8s_cluster_name: 'k8s.cluster.name',
k8s_cluster_uid: 'k8s.cluster.uid',
k8s_namespace_name: 'k8s.namespace.name',
k8s_node_name: 'k8s.node.name',
k8s_node_uid: 'k8s.node.uid',
k8s_pod_name: 'k8s.pod.name',
k8s_pod_uid: 'k8s.pod.uid',
k8s_deployment_name: 'k8s.deployment.name',
k8s_daemonset_name: 'k8s.daemonset.name',
k8s_statefulset_name: 'k8s.statefulset.name',
k8s_cronjob_name: 'k8s.cronjob.name',
k8s_job_name: 'k8s.job.name',
k8s_persistentvolumeclaim_name: 'k8s.persistentvolumeclaim.name',
};

View File

@@ -7,7 +7,6 @@ export enum FeatureKeys {
GATEWAY = 'gateway',
PREMIUM_SUPPORT = 'premium_support',
ANOMALY_DETECTION = 'anomaly_detection',
DOT_METRICS_ENABLED = 'dot_metrics_enabled',
USE_JSON_BODY = 'use_json_body',
ENABLE_AI_OBSERVABILITY = 'enable_ai_observability',
ENABLE_METRICS_REDUCTION = 'enable_metrics_reduction',

View File

@@ -0,0 +1,32 @@
// Code generated by scripts/semconv. DO NOT EDIT.
export type SemconvFamily = {
readonly current: string;
readonly old: readonly string[];
readonly kind: 'attribute' | 'metric';
readonly contexts: readonly string[];
readonly signals: readonly string[];
readonly applyToMetrics: readonly string[];
readonly valueMap: Readonly<Record<string, string>>;
};
export const SEMCONV_FAMILIES: readonly SemconvFamily[] = [
{
current: 'db.system.name',
old: ['db.system'],
kind: 'attribute',
contexts: [],
signals: [],
applyToMetrics: [],
valueMap: {},
},
{
current: 'deployment.environment.name',
old: ['deployment.environment'],
kind: 'attribute',
contexts: [],
signals: [],
applyToMetrics: [],
valueMap: {},
},
] as const;

View File

@@ -37,8 +37,6 @@ import { ErrorResponse, SuccessResponse } from 'types/api';
import { Exception, PayloadProps } from 'types/api/errors/getAll';
import { GlobalReducer } from 'types/reducer/globalTime';
import { FeatureKeys } from '../../constants/features';
import { useAppContext } from '../../providers/App/App';
import { FilterDropdownExtendsProps } from './types';
import {
extractFilterValues,
@@ -418,11 +416,6 @@ function AllErrors(): JSX.Element {
},
];
const { featureFlags } = useAppContext();
const dotMetricsEnabled =
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
?.active || false;
const onChangeHandler: TableProps<Exception>['onChange'] = useCallback(
(
paginations: TablePaginationConfig,
@@ -458,7 +451,7 @@ function AllErrors(): JSX.Element {
useEffect(() => {
if (!isUndefined(errorCountResponse.data?.payload)) {
const selectedEnvironments = queries.find(
(val) => val.tagKey === getResourceDeploymentKeys(dotMetricsEnabled),
(val) => val.tagKey === getResourceDeploymentKeys(),
)?.tagValue;
logEvent('Exception: List page visited', {

View File

@@ -35,7 +35,6 @@ import { openInNewTab } from 'utils/navigation';
import triangleRulerUrl from '@/assets/Icons/triangle-ruler.svg';
import { FeatureKeys } from '../../../constants/features';
import { DOCS_LINKS } from '../constants';
import { columns, TIME_PICKER_OPTIONS } from './constants';
@@ -212,19 +211,13 @@ function ServiceMetrics({
const topLevelOperations = useMemo(() => Object.entries(data || {}), [data]);
const { featureFlags } = useAppContext();
const dotMetricsEnabled =
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
?.active || false;
const queryRangeRequestData = useMemo(
() =>
getQueryRangeRequestData({
topLevelOperations,
globalSelectedInterval,
dotMetricsEnabled,
}),
[globalSelectedInterval, topLevelOperations, dotMetricsEnabled],
[globalSelectedInterval, topLevelOperations],
);
const dataQueries = useGetQueriesRange(

View File

@@ -82,7 +82,7 @@ export function getHostMetricsQueryPayload(
start: number,
end: number,
): ReturnType<typeof getHostQueryPayload> {
return getHostQueryPayload(host.hostName, start, end, true);
return getHostQueryPayload(host.hostName, start, end);
}
export { hostWidgetInfo };

View File

@@ -121,12 +121,6 @@ jest.spyOn(appContextHooks, 'useAppContext').mockReturnValue({
plan_version: 'test-plan-version',
},
},
featureFlags: [
{
name: 'DOT_METRICS_ENABLED',
active: false,
},
],
} as any);
const mockEntity = {

View File

@@ -17,8 +17,6 @@ import { SuccessResponse } from 'types/api';
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
import uPlot from 'uplot';
import { FeatureKeys } from '../../../constants/features';
import { useAppContext } from '../../../providers/App/App';
import {
getHostQueryPayload,
getNodeQueryPayload,
@@ -53,23 +51,12 @@ function NodeMetrics({
};
}, [timestamp]);
const { featureFlags } = useAppContext();
const dotMetricsEnabled =
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
?.active || false;
const queryPayloads = useMemo(() => {
if (nodeName) {
return getNodeQueryPayload(
clusterName,
nodeName,
start,
end,
dotMetricsEnabled,
);
return getNodeQueryPayload(clusterName, nodeName, start, end);
}
return getHostQueryPayload(hostName, start, end, dotMetricsEnabled);
}, [nodeName, hostName, clusterName, start, end, dotMetricsEnabled]);
return getHostQueryPayload(hostName, start, end);
}, [nodeName, hostName, clusterName, start, end]);
const widgetInfo = nodeName ? nodeWidgetInfo : hostWidgetInfo;
const queries = useQueries(

View File

@@ -12,13 +12,11 @@ import { useResizeObserver } from 'hooks/useDimensions';
import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults';
import { getUPlotChartOptions } from 'lib/uPlotLib/getUplotChartOptions';
import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData';
import { useAppContext } from 'providers/App/App';
import { useTimezone } from 'providers/Timezone';
import { SuccessResponse } from 'types/api';
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
import uPlot from 'uplot';
import { FeatureKeys } from '../../../constants/features';
import { getPodQueryPayload, podWidgetInfo } from './constants';
function PodMetrics({
@@ -54,14 +52,9 @@ function PodMetrics({
scrollLeft: 0,
});
const { featureFlags } = useAppContext();
const dotMetricsEnabled =
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
?.active || false;
const queryPayloads = useMemo(
() => getPodQueryPayload(clusterName, podName, start, end, dotMetricsEnabled),
[clusterName, end, podName, start, dotMetricsEnabled],
() => getPodQueryPayload(clusterName, podName, start, end),
[clusterName, end, podName, start],
);
const queries = useQueries(
queryPayloads.map((payload) => ({

View File

@@ -9,48 +9,21 @@ export const getPodQueryPayload = (
podName: string,
start: number,
end: number,
dotMetricsEnabled: boolean,
): GetQueryResultsProps[] => {
const k8sClusterNameKey = dotMetricsEnabled
? 'k8s.cluster.name'
: 'k8s_cluster_name';
const k8sPodNameKey = dotMetricsEnabled ? 'k8s.pod.name' : 'k8s_pod_name';
const containerCpuUtilKey = dotMetricsEnabled
? 'container.cpu.usage'
: 'container_cpu_usage';
const containerMemUsageKey = dotMetricsEnabled
? 'container.memory.usage'
: 'container_memory_usage';
const k8sContainerCpuReqKey = dotMetricsEnabled
? 'k8s.container.cpu_request'
: 'k8s_container_cpu_request';
const k8sContainerCpuLimitKey = dotMetricsEnabled
? 'k8s.container.cpu_limit'
: 'k8s_container_cpu_limit';
const k8sContainerMemReqKey = dotMetricsEnabled
? 'k8s.container.memory_request'
: 'k8s_container_memory_request';
const k8sContainerMemLimitKey = dotMetricsEnabled
? 'k8s.container.memory_limit'
: 'k8s_container_memory_limit';
const k8sPodFsAvailKey = dotMetricsEnabled
? 'k8s.pod.filesystem.available'
: 'k8s_pod_filesystem_available';
const k8sPodFsCapKey = dotMetricsEnabled
? 'k8s.pod.filesystem.capacity'
: 'k8s_pod_filesystem_capacity';
const k8sPodNetIoKey = dotMetricsEnabled
? 'k8s.pod.network.io'
: 'k8s_pod_network_io';
const podLegendTemplate = dotMetricsEnabled
? '{{k8s.pod.name}}'
: '{{k8s_pod_name}}';
const podLegendUsage = dotMetricsEnabled
? 'usage - {{k8s.pod.name}}'
: 'usage - {{k8s_pod_name}}';
const podLegendLimit = dotMetricsEnabled
? 'limit - {{k8s.pod.name}}'
: 'limit - {{k8s_pod_name}}';
const k8sClusterNameKey = 'k8s.cluster.name';
const k8sPodNameKey = 'k8s.pod.name';
const containerCpuUtilKey = 'container.cpu.usage';
const containerMemUsageKey = 'container.memory.usage';
const k8sContainerCpuReqKey = 'k8s.container.cpu_request';
const k8sContainerCpuLimitKey = 'k8s.container.cpu_limit';
const k8sContainerMemReqKey = 'k8s.container.memory_request';
const k8sContainerMemLimitKey = 'k8s.container.memory_limit';
const k8sPodFsAvailKey = 'k8s.pod.filesystem.available';
const k8sPodFsCapKey = 'k8s.pod.filesystem.capacity';
const k8sPodNetIoKey = 'k8s.pod.network.io';
const podLegendTemplate = '{{k8s.pod.name}}';
const podLegendUsage = 'usage - {{k8s.pod.name}}';
const podLegendLimit = 'limit - {{k8s.pod.name}}';
return [
{
@@ -1027,36 +1000,17 @@ export const getNodeQueryPayload = (
nodeName: string,
start: number,
end: number,
dotMetricsEnabled: boolean,
): GetQueryResultsProps[] => {
const k8sClusterNameKey = dotMetricsEnabled
? 'k8s.cluster.name'
: 'k8s_cluster_name';
const k8sNodeNameKey = dotMetricsEnabled ? 'k8s.node.name' : 'k8s_node_name';
const k8sNodeCpuTimeKey = dotMetricsEnabled
? 'k8s.node.cpu.time'
: 'k8s_node_cpu_time';
const k8sNodeAllocCpuKey = dotMetricsEnabled
? 'k8s.node.allocatable_cpu'
: 'k8s_node_allocatable_cpu';
const k8sNodeMemWsKey = dotMetricsEnabled
? 'k8s.node.memory.working_set'
: 'k8s_node_memory_working_set';
const k8sNodeAllocMemKey = dotMetricsEnabled
? 'k8s.node.allocatable_memory'
: 'k8s_node_allocatable_memory';
const k8sNodeNetIoKey = dotMetricsEnabled
? 'k8s.node.network.io'
: 'k8s_node_network_io';
const k8sNodeFsAvailKey = dotMetricsEnabled
? 'k8s.node.filesystem.available'
: 'k8s_node_filesystem_available';
const k8sNodeFsCapKey = dotMetricsEnabled
? 'k8s.node.filesystem.capacity'
: 'k8s_node_filesystem_capacity';
const podLegend = dotMetricsEnabled
? '{{k8s.node.name}}'
: '{{k8s_node_name}}';
const k8sClusterNameKey = 'k8s.cluster.name';
const k8sNodeNameKey = 'k8s.node.name';
const k8sNodeCpuTimeKey = 'k8s.node.cpu.time';
const k8sNodeAllocCpuKey = 'k8s.node.allocatable_cpu';
const k8sNodeMemWsKey = 'k8s.node.memory.working_set';
const k8sNodeAllocMemKey = 'k8s.node.allocatable_memory';
const k8sNodeNetIoKey = 'k8s.node.network.io';
const k8sNodeFsAvailKey = 'k8s.node.filesystem.available';
const k8sNodeFsCapKey = 'k8s.node.filesystem.capacity';
const podLegend = '{{k8s.node.name}}';
return [
{
@@ -1586,48 +1540,23 @@ export const getHostQueryPayload = (
hostName: string,
start: number,
end: number,
dotMetricsEnabled: boolean,
): GetQueryResultsProps[] => {
const hostNameKey = dotMetricsEnabled ? 'host.name' : 'host_name';
const cpuTimeKey = dotMetricsEnabled ? 'system.cpu.time' : 'system_cpu_time';
const memUsageKey = dotMetricsEnabled
? 'system.memory.usage'
: 'system_memory_usage';
const load1mKey = dotMetricsEnabled
? 'system.cpu.load_average.1m'
: 'system_cpu_load_average_1m';
const load5mKey = dotMetricsEnabled
? 'system.cpu.load_average.5m'
: 'system_cpu_load_average_5m';
const load15mKey = dotMetricsEnabled
? 'system.cpu.load_average.15m'
: 'system_cpu_load_average_15m';
const netIoKey = dotMetricsEnabled ? 'system.network.io' : 'system_network_io';
const netPktsKey = dotMetricsEnabled
? 'system.network.packets'
: 'system_network_packets';
const netErrKey = dotMetricsEnabled
? 'system.network.errors'
: 'system_network_errors';
const netDropKey = dotMetricsEnabled
? 'system.network.dropped'
: 'system_network_dropped';
const netConnKey = dotMetricsEnabled
? 'system.network.connections'
: 'system_network_connections';
const diskIoKey = dotMetricsEnabled ? 'system.disk.io' : 'system_disk_io';
const diskOpTimeKey = dotMetricsEnabled
? 'system.disk.operation_time'
: 'system_disk_operation_time';
const diskOpsKey = dotMetricsEnabled
? 'system.disk.operations'
: 'system_disk_operations';
const diskPendingKey = dotMetricsEnabled
? 'system.disk.pending_operations'
: 'system_disk_pending_operations';
const fsUsageKey = dotMetricsEnabled
? 'system.filesystem.usage'
: 'system_filesystem_usage';
const hostNameKey = 'host.name';
const cpuTimeKey = 'system.cpu.time';
const memUsageKey = 'system.memory.usage';
const load1mKey = 'system.cpu.load_average.1m';
const load5mKey = 'system.cpu.load_average.5m';
const load15mKey = 'system.cpu.load_average.15m';
const netIoKey = 'system.network.io';
const netPktsKey = 'system.network.packets';
const netErrKey = 'system.network.errors';
const netDropKey = 'system.network.dropped';
const netConnKey = 'system.network.connections';
const diskIoKey = 'system.disk.io';
const diskOpTimeKey = 'system.disk.operation_time';
const diskOpsKey = 'system.disk.operations';
const diskPendingKey = 'system.disk.pending_operations';
const fsUsageKey = 'system.filesystem.usage';
return [
{

View File

@@ -21,7 +21,6 @@ export const databaseCallsRPS = ({
servicename,
legend,
tagFilterItems,
dotMetricsEnabled,
}: DatabaseCallsRPSProps): QueryBuilderData => {
const autocompleteData: BaseAutocompleteData[] = [
{
@@ -33,7 +32,7 @@ export const databaseCallsRPS = ({
const groupBy: BaseAutocompleteData[] = [
{
dataType: DataTypes.String,
key: dotMetricsEnabled ? WidgetKeys.Db_system : WidgetKeys.Db_system_norm,
key: WidgetKeys.DbSystem,
type: 'tag',
},
];
@@ -42,9 +41,7 @@ export const databaseCallsRPS = ({
{
id: '',
key: {
key: dotMetricsEnabled
? WidgetKeys.Service_name
: WidgetKeys.Service_name_norm,
key: WidgetKeys.OTelServiceName,
dataType: DataTypes.String,
type: MetricsType.Resource,
},
@@ -75,7 +72,6 @@ export const databaseCallsRPS = ({
export const databaseCallsAvgDuration = ({
servicename,
tagFilterItems,
dotMetricsEnabled,
}: DatabaseCallProps): QueryBuilderData => {
const autocompleteDataA: BaseAutocompleteData = {
key: WidgetKeys.SignozDbLatencySum,
@@ -92,9 +88,7 @@ export const databaseCallsAvgDuration = ({
{
id: '',
key: {
key: dotMetricsEnabled
? WidgetKeys.Service_name
: WidgetKeys.Service_name_norm,
key: WidgetKeys.OTelServiceName,
dataType: DataTypes.String,
type: MetricsType.Resource,
},

View File

@@ -32,7 +32,6 @@ export const externalCallErrorPercent = ({
servicename,
legend,
tagFilterItems,
dotMetricsEnabled,
}: ExternalCallDurationByAddressProps): QueryBuilderData => {
const autocompleteDataA: BaseAutocompleteData = {
key: WidgetKeys.SignozExternalCallLatencyCount,
@@ -49,9 +48,7 @@ export const externalCallErrorPercent = ({
{
id: '',
key: {
key: dotMetricsEnabled
? WidgetKeys.Service_name
: WidgetKeys.Service_name_norm,
key: WidgetKeys.OTelServiceName,
dataType: DataTypes.String,
type: MetricsType.Resource,
},
@@ -61,7 +58,7 @@ export const externalCallErrorPercent = ({
{
id: '',
key: {
key: dotMetricsEnabled ? WidgetKeys.StatusCode : WidgetKeys.StatusCodeNorm,
key: WidgetKeys.StatusCode,
dataType: DataTypes.Int64,
type: MetricsType.Tag,
},
@@ -74,9 +71,7 @@ export const externalCallErrorPercent = ({
{
id: '',
key: {
key: dotMetricsEnabled
? WidgetKeys.Service_name
: WidgetKeys.Service_name_norm,
key: WidgetKeys.OTelServiceName,
dataType: DataTypes.String,
type: MetricsType.Resource,
},
@@ -120,7 +115,6 @@ export const externalCallErrorPercent = ({
export const externalCallDuration = ({
servicename,
tagFilterItems,
dotMetricsEnabled,
}: ExternalCallProps): QueryBuilderData => {
const autocompleteDataA: BaseAutocompleteData = {
dataType: DataTypes.Float64,
@@ -141,9 +135,7 @@ export const externalCallDuration = ({
id: '',
key: {
dataType: DataTypes.String,
key: dotMetricsEnabled
? WidgetKeys.Service_name
: WidgetKeys.Service_name_norm,
key: WidgetKeys.OTelServiceName,
type: MetricsType.Resource,
},
op: OPERATORS.IN,
@@ -183,7 +175,6 @@ export const externalCallRpsByAddress = ({
servicename,
legend,
tagFilterItems,
dotMetricsEnabled,
}: ExternalCallDurationByAddressProps): QueryBuilderData => {
const autocompleteData: BaseAutocompleteData[] = [
{
@@ -198,9 +189,7 @@ export const externalCallRpsByAddress = ({
id: '',
key: {
dataType: DataTypes.String,
key: dotMetricsEnabled
? WidgetKeys.Service_name
: WidgetKeys.Service_name_norm,
key: WidgetKeys.OTelServiceName,
type: MetricsType.Resource,
},
op: OPERATORS.IN,
@@ -231,7 +220,6 @@ export const externalCallDurationByAddress = ({
servicename,
legend,
tagFilterItems,
dotMetricsEnabled,
}: ExternalCallDurationByAddressProps): QueryBuilderData => {
const autocompleteDataA: BaseAutocompleteData = {
dataType: DataTypes.Float64,
@@ -251,9 +239,7 @@ export const externalCallDurationByAddress = ({
id: '',
key: {
dataType: DataTypes.String,
key: dotMetricsEnabled
? WidgetKeys.Service_name
: WidgetKeys.Service_name_norm,
key: WidgetKeys.OTelServiceName,
type: MetricsType.Resource,
},
op: OPERATORS.IN,

View File

@@ -37,15 +37,10 @@ export const latency = ({
tagFilterItems,
isSpanMetricEnable = false,
topLevelOperationsRoute,
dotMetricsEnabled,
}: LatencyProps): QueryBuilderData => {
const signozLatencyBucketMetrics = dotMetricsEnabled
? WidgetKeys.Signoz_latency_bucket
: WidgetKeys.Signoz_latency_bucket_norm;
const signozLatencyBucketMetrics = WidgetKeys.SignozLatencyBucket;
const signozMetricsServiceName = dotMetricsEnabled
? WidgetKeys.Service_name
: WidgetKeys.Service_name_norm;
const signozMetricsServiceName = WidgetKeys.OTelServiceName;
const newAutoCompleteData: BaseAutocompleteData = {
key: isSpanMetricEnable
? signozLatencyBucketMetrics
@@ -287,28 +282,21 @@ export const apDexMetricsQueryBuilderQueries = ({
threashold,
delta,
metricsBuckets,
dotMetricsEnabled,
}: ApDexMetricsQueryBuilderQueriesProps): QueryBuilderData => {
const autoCompleteDataA: BaseAutocompleteData = {
key: dotMetricsEnabled
? WidgetKeys.SignozLatencyCount
: WidgetKeys.SignozLatencyCountNorm,
key: WidgetKeys.SignozLatencyCount,
dataType: DataTypes.Float64,
type: '',
};
const autoCompleteDataB: BaseAutocompleteData = {
key: dotMetricsEnabled
? WidgetKeys.Signoz_latency_bucket
: WidgetKeys.Signoz_latency_bucket_norm,
key: WidgetKeys.SignozLatencyBucket,
dataType: DataTypes.Float64,
type: '',
};
const autoCompleteDataC: BaseAutocompleteData = {
key: dotMetricsEnabled
? WidgetKeys.Signoz_latency_bucket
: WidgetKeys.Signoz_latency_bucket_norm,
key: WidgetKeys.SignozLatencyBucket,
dataType: DataTypes.Float64,
type: '',
};
@@ -317,9 +305,7 @@ export const apDexMetricsQueryBuilderQueries = ({
{
id: '',
key: {
key: dotMetricsEnabled
? WidgetKeys.Service_name
: WidgetKeys.Service_name_norm,
key: WidgetKeys.OTelServiceName,
dataType: DataTypes.String,
type: MetricsType.Tag,
},
@@ -343,7 +329,7 @@ export const apDexMetricsQueryBuilderQueries = ({
{
id: '',
key: {
key: dotMetricsEnabled ? WidgetKeys.StatusCode : WidgetKeys.StatusCodeNorm,
key: WidgetKeys.StatusCode,
dataType: DataTypes.String,
type: MetricsType.Tag,
},
@@ -363,9 +349,7 @@ export const apDexMetricsQueryBuilderQueries = ({
{
id: '',
key: {
key: dotMetricsEnabled
? WidgetKeys.Service_name
: WidgetKeys.Service_name_norm,
key: WidgetKeys.OTelServiceName,
dataType: DataTypes.String,
type: MetricsType.Tag,
},
@@ -399,7 +383,7 @@ export const apDexMetricsQueryBuilderQueries = ({
{
id: '',
key: {
key: dotMetricsEnabled ? WidgetKeys.StatusCode : WidgetKeys.StatusCodeNorm,
key: WidgetKeys.StatusCode,
dataType: DataTypes.String,
type: MetricsType.Tag,
},
@@ -409,9 +393,7 @@ export const apDexMetricsQueryBuilderQueries = ({
{
id: '',
key: {
key: dotMetricsEnabled
? WidgetKeys.Service_name
: WidgetKeys.Service_name_norm,
key: WidgetKeys.OTelServiceName,
dataType: DataTypes.String,
type: MetricsType.Tag,
},
@@ -474,13 +456,10 @@ export const operationPerSec = ({
servicename,
tagFilterItems,
topLevelOperations,
dotMetricsEnabled,
}: OperationPerSecProps): QueryBuilderData => {
const autocompleteData: BaseAutocompleteData[] = [
{
key: dotMetricsEnabled
? WidgetKeys.SignozLatencyCount
: WidgetKeys.SignozLatencyCountNorm,
key: WidgetKeys.SignozLatencyCount,
dataType: DataTypes.Float64,
type: '',
},
@@ -491,9 +470,7 @@ export const operationPerSec = ({
{
id: '',
key: {
key: dotMetricsEnabled
? WidgetKeys.Service_name
: WidgetKeys.Service_name_norm,
key: WidgetKeys.OTelServiceName,
dataType: DataTypes.String,
type: MetricsType.Resource,
},
@@ -534,7 +511,6 @@ export const errorPercentage = ({
servicename,
tagFilterItems,
topLevelOperations,
dotMetricsEnabled,
}: OperationPerSecProps): QueryBuilderData => {
const autocompleteDataA: BaseAutocompleteData = {
key: WidgetKeys.SignozCallsTotal,
@@ -553,9 +529,7 @@ export const errorPercentage = ({
{
id: '',
key: {
key: dotMetricsEnabled
? WidgetKeys.Service_name
: WidgetKeys.Service_name_norm,
key: WidgetKeys.OTelServiceName,
dataType: DataTypes.String,
type: MetricsType.Resource,
},
@@ -575,7 +549,7 @@ export const errorPercentage = ({
{
id: '',
key: {
key: dotMetricsEnabled ? WidgetKeys.StatusCode : WidgetKeys.StatusCodeNorm,
key: WidgetKeys.StatusCode,
dataType: DataTypes.Int64,
type: MetricsType.Tag,
},
@@ -589,9 +563,7 @@ export const errorPercentage = ({
{
id: '',
key: {
key: dotMetricsEnabled
? WidgetKeys.Service_name
: WidgetKeys.Service_name_norm,
key: WidgetKeys.OTelServiceName,
dataType: DataTypes.String,
type: MetricsType.Resource,
},

View File

@@ -21,12 +21,9 @@ import { getQueryBuilderQuerieswithFormula } from './MetricsPageQueriesFactory';
export const topOperationQueries = ({
servicename,
dotMetricsEnabled,
}: TopOperationQueryFactoryProps): QueryBuilderData => {
const latencyAutoCompleteData: BaseAutocompleteData = {
key: dotMetricsEnabled
? WidgetKeys.Signoz_latency_bucket
: WidgetKeys.Signoz_latency_bucket_norm,
key: WidgetKeys.SignozLatencyBucket,
dataType: DataTypes.Float64,
type: '',
};
@@ -38,9 +35,7 @@ export const topOperationQueries = ({
};
const numOfCallAutoCompleteData: BaseAutocompleteData = {
key: dotMetricsEnabled
? WidgetKeys.SignozLatencyCount
: WidgetKeys.SignozLatencyCountNorm,
key: WidgetKeys.SignozLatencyCount,
dataType: DataTypes.Float64,
type: '',
};
@@ -49,9 +44,7 @@ export const topOperationQueries = ({
{
id: '',
key: {
key: dotMetricsEnabled
? WidgetKeys.Service_name
: WidgetKeys.Service_name_norm,
key: WidgetKeys.OTelServiceName,
dataType: DataTypes.String,
type: MetricsType.Resource,
},
@@ -65,9 +58,7 @@ export const topOperationQueries = ({
id: '',
key: {
dataType: DataTypes.String,
key: dotMetricsEnabled
? WidgetKeys.Service_name
: WidgetKeys.Service_name_norm,
key: WidgetKeys.OTelServiceName,
type: MetricsType.Resource,
},
op: OPERATORS.IN,
@@ -77,7 +68,7 @@ export const topOperationQueries = ({
id: '',
key: {
dataType: DataTypes.Int64,
key: dotMetricsEnabled ? WidgetKeys.StatusCode : WidgetKeys.StatusCodeNorm,
key: WidgetKeys.StatusCode,
type: MetricsType.Tag,
},
op: OPERATORS.IN,

View File

@@ -28,8 +28,6 @@ import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData';
import { EQueryType } from 'types/common/dashboard';
import { v4 as uuid } from 'uuid';
import { FeatureKeys } from '../../../constants/features';
import { useAppContext } from '../../../providers/App/App';
import {
GraphTitle,
MENU_ITEMS,
@@ -89,12 +87,7 @@ function DBCall(): JSX.Element {
[queries],
);
const { featureFlags } = useAppContext();
const dotMetricsEnabled =
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
?.active || false;
const legend = dotMetricsEnabled ? '{{db.system}}' : '{{db_system}}';
const legend = '{{db.system}}';
const databaseCallsRPSWidget = useMemo(
() =>
@@ -106,7 +99,6 @@ function DBCall(): JSX.Element {
servicename,
legend,
tagFilterItems,
dotMetricsEnabled,
}),
clickhouse_sql: [],
id: uuid(),
@@ -117,7 +109,7 @@ function DBCall(): JSX.Element {
id: SERVICE_CHART_ID.dbCallsRPS,
fillSpans: false,
}),
[servicename, tagFilterItems, dotMetricsEnabled, legend],
[servicename, tagFilterItems, legend],
);
const databaseCallsAverageDurationWidget = useMemo(
() =>
@@ -128,7 +120,6 @@ function DBCall(): JSX.Element {
builder: databaseCallsAvgDuration({
servicename,
tagFilterItems,
dotMetricsEnabled,
}),
clickhouse_sql: [],
id: uuid(),
@@ -139,7 +130,7 @@ function DBCall(): JSX.Element {
id: GraphTitle.DATABASE_CALLS_AVG_DURATION,
fillSpans: true,
}),
[servicename, tagFilterItems, dotMetricsEnabled],
[servicename, tagFilterItems],
);
const stepInterval = useMemo(
@@ -157,7 +148,7 @@ function DBCall(): JSX.Element {
useEffect(() => {
if (!logEventCalledRef.current) {
const selectedEnvironments = queries.find(
(val) => val.tagKey === getResourceDeploymentKeys(dotMetricsEnabled),
(val) => val.tagKey === getResourceDeploymentKeys(),
)?.tagValue;
logEvent('APM: Service detail page visited', {

View File

@@ -30,8 +30,6 @@ import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { EQueryType } from 'types/common/dashboard';
import { v4 as uuid } from 'uuid';
import { FeatureKeys } from '../../../constants/features';
import { useAppContext } from '../../../providers/App/App';
import {
GraphTitle,
legend,
@@ -84,10 +82,6 @@ function External(): JSX.Element {
handleNonInQueryRange(resourceAttributesToTagFilterItems(queries)) || [],
[queries],
);
const { featureFlags } = useAppContext();
const dotMetricsEnabled =
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
?.active || false;
const externalCallErrorWidget = useMemo(
() =>
@@ -99,7 +93,6 @@ function External(): JSX.Element {
servicename,
legend: legend.address,
tagFilterItems,
dotMetricsEnabled,
}),
clickhouse_sql: [],
id: uuid(),
@@ -109,7 +102,7 @@ function External(): JSX.Element {
yAxisUnit: '%',
id: GraphTitle.EXTERNAL_CALL_ERROR_PERCENTAGE,
}),
[servicename, tagFilterItems, dotMetricsEnabled],
[servicename, tagFilterItems],
);
const selectedTraceTags = useMemo(
@@ -126,7 +119,6 @@ function External(): JSX.Element {
builder: externalCallDuration({
servicename,
tagFilterItems,
dotMetricsEnabled,
}),
clickhouse_sql: [],
id: uuid(),
@@ -137,7 +129,7 @@ function External(): JSX.Element {
id: GraphTitle.EXTERNAL_CALL_DURATION,
fillSpans: true,
}),
[servicename, tagFilterItems, dotMetricsEnabled],
[servicename, tagFilterItems],
);
const errorApmToTraceQuery = useGetAPMToTracesQueries({
@@ -171,7 +163,7 @@ function External(): JSX.Element {
useEffect(() => {
if (!logEventCalledRef.current) {
const selectedEnvironments = queries.find(
(val) => val.tagKey === getResourceDeploymentKeys(dotMetricsEnabled),
(val) => val.tagKey === getResourceDeploymentKeys(),
)?.tagValue;
logEvent('APM: Service detail page visited', {
@@ -194,7 +186,6 @@ function External(): JSX.Element {
servicename,
legend: legend.address,
tagFilterItems,
dotMetricsEnabled,
}),
clickhouse_sql: [],
id: uuid(),
@@ -205,7 +196,7 @@ function External(): JSX.Element {
id: GraphTitle.EXTERNAL_CALL_RPS_BY_ADDRESS,
fillSpans: true,
}),
[servicename, tagFilterItems, dotMetricsEnabled],
[servicename, tagFilterItems],
);
const externalCallDurationAddressWidget = useMemo(
@@ -218,7 +209,6 @@ function External(): JSX.Element {
servicename,
legend: legend.address,
tagFilterItems,
dotMetricsEnabled,
}),
clickhouse_sql: [],
id: uuid(),
@@ -229,7 +219,7 @@ function External(): JSX.Element {
id: GraphTitle.EXTERNAL_CALL_DURATION_BY_ADDRESS,
fillSpans: true,
}),
[servicename, tagFilterItems, dotMetricsEnabled],
[servicename, tagFilterItems],
);
const apmToTraceQuery = useGetAPMToTracesQueries({

View File

@@ -93,15 +93,12 @@ function Application(): JSX.Element {
// eslint-disable-next-line react-hooks/exhaustive-deps
[handleSetTimeStamp],
);
const dotMetricsEnabled =
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
?.active || false;
const logEventCalledRef = useRef(false);
useEffect(() => {
if (!logEventCalledRef.current) {
const selectedEnvironments = queries.find(
(val) => val.tagKey === getResourceDeploymentKeys(dotMetricsEnabled),
(val) => val.tagKey === getResourceDeploymentKeys(),
)?.tagValue;
logEvent('APM: Service detail page visited', {
@@ -159,7 +156,6 @@ function Application(): JSX.Element {
servicename,
tagFilterItems,
topLevelOperations: topLevelOperationsRoute,
dotMetricsEnabled,
}),
clickhouse_sql: [],
id: uuid(),
@@ -169,7 +165,7 @@ function Application(): JSX.Element {
yAxisUnit: 'ops',
id: SERVICE_CHART_ID.rps,
}),
[servicename, tagFilterItems, topLevelOperationsRoute, dotMetricsEnabled],
[servicename, tagFilterItems, topLevelOperationsRoute],
);
const errorPercentageWidget = useMemo(
@@ -182,7 +178,6 @@ function Application(): JSX.Element {
servicename,
tagFilterItems,
topLevelOperations: topLevelOperationsRoute,
dotMetricsEnabled,
}),
clickhouse_sql: [],
id: uuid(),
@@ -193,7 +188,7 @@ function Application(): JSX.Element {
id: SERVICE_CHART_ID.errorPercentage,
fillSpans: true,
}),
[servicename, tagFilterItems, topLevelOperationsRoute, dotMetricsEnabled],
[servicename, tagFilterItems, topLevelOperationsRoute],
);
const stepInterval = useMemo(

View File

@@ -22,8 +22,6 @@ import { apDexMetricsQueryBuilderQueries } from 'container/MetricsApplication/Me
import { EQueryType } from 'types/common/dashboard';
import { v4 as uuid } from 'uuid';
import { FeatureKeys } from '../../../../../constants/features';
import { useAppContext } from '../../../../../providers/App/App';
import { IServiceName } from '../../types';
import { ApDexMetricsProps } from './types';
@@ -38,10 +36,6 @@ function ApDexMetrics({
}: ApDexMetricsProps): JSX.Element {
const { servicename: encodedServiceName } = useParams<IServiceName>();
const servicename = decodeURIComponent(encodedServiceName);
const { featureFlags } = useAppContext();
const dotMetricsEnabled =
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
?.active || false;
const apDexMetricsWidget = useMemo(
() =>
getWidgetQueryBuilder({
@@ -55,7 +49,6 @@ function ApDexMetrics({
threashold: thresholdValue || 0,
delta: delta || false,
metricsBuckets: metricsBuckets || [],
dotMetricsEnabled,
}),
clickhouse_sql: [],
id: uuid(),
@@ -81,7 +74,6 @@ function ApDexMetrics({
tagFilterItems,
thresholdValue,
topLevelOperationsRoute,
dotMetricsEnabled,
],
);

View File

@@ -3,8 +3,6 @@ import Spinner from 'components/Spinner';
import { useGetMetricMeta } from 'hooks/apDex/useGetMetricMeta';
import useErrorNotification from 'hooks/useErrorNotification';
import { FeatureKeys } from '../../../../../constants/features';
import { useAppContext } from '../../../../../providers/App/App';
import { WidgetKeys } from '../../../constant';
import { IServiceName } from '../../types';
import ApDexMetrics from './ApDexMetrics';
@@ -20,17 +18,8 @@ function ApDexMetricsApplication({
const { servicename: encodedServiceName } = useParams<IServiceName>();
const servicename = decodeURIComponent(encodedServiceName);
const { featureFlags } = useAppContext();
const dotMetricsEnabled =
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
?.active || false;
const signozLatencyBucketMetrics = dotMetricsEnabled
? WidgetKeys.Signoz_latency_bucket
: WidgetKeys.Signoz_latency_bucket_norm;
const { data, isLoading, error } = useGetMetricMeta(
signozLatencyBucketMetrics,
WidgetKeys.SignozLatencyBucket,
servicename,
);
useErrorNotification(error);

View File

@@ -56,10 +56,6 @@ function ServiceOverview({
[isSpanMetricEnable, queries],
);
const dotMetricsEnabled =
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
?.active || false;
const latencyWidget = useMemo(
() =>
getWidgetQueryBuilder({
@@ -71,7 +67,6 @@ function ServiceOverview({
tagFilterItems,
isSpanMetricEnable,
topLevelOperationsRoute,
dotMetricsEnabled,
}),
clickhouse_sql: [],
id: uuid(),
@@ -81,13 +76,7 @@ function ServiceOverview({
yAxisUnit: 'ns',
id: SERVICE_CHART_ID.latency,
}),
[
isSpanMetricEnable,
servicename,
tagFilterItems,
topLevelOperationsRoute,
dotMetricsEnabled,
],
[isSpanMetricEnable, servicename, tagFilterItems, topLevelOperationsRoute],
);
const isQueryEnabled =

View File

@@ -19,8 +19,6 @@ import { EQueryType } from 'types/common/dashboard';
import { GlobalReducer } from 'types/reducer/globalTime';
import { v4 as uuid } from 'uuid';
import { FeatureKeys } from '../../../../constants/features';
import { useAppContext } from '../../../../providers/App/App';
import { IServiceName } from '../types';
import { title } from './config';
import ColumnWithLink from './TableRenderer/ColumnWithLink';
@@ -44,11 +42,6 @@ function TopOperationMetrics(): JSX.Element {
convertRawQueriesToTraceSelectedTags(queries) || [],
);
const { featureFlags } = useAppContext();
const dotMetricsEnabled =
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
?.active || false;
const keyOperationWidget = useMemo(
() =>
getWidgetQueryBuilder({
@@ -57,14 +50,13 @@ function TopOperationMetrics(): JSX.Element {
promql: [],
builder: topOperationQueries({
servicename,
dotMetricsEnabled,
}),
clickhouse_sql: [],
id: uuid(),
},
panelTypes: PANEL_TYPES.TABLE,
}),
[servicename, dotMetricsEnabled],
[servicename],
);
const updatedQuery = updateStepInterval(keyOperationWidget.query);

View File

@@ -10,7 +10,6 @@ export interface IServiceName {
export interface TopOperationQueryFactoryProps {
servicename: IServiceName['servicename'];
dotMetricsEnabled: boolean;
}
export interface ExternalCallDurationByAddressProps extends ExternalCallProps {
@@ -20,7 +19,6 @@ export interface ExternalCallDurationByAddressProps extends ExternalCallProps {
export interface ExternalCallProps {
servicename: IServiceName['servicename'];
tagFilterItems: TagFilterItem[];
dotMetricsEnabled: boolean;
}
export interface BuilderQueriesProps {
@@ -52,7 +50,6 @@ export interface OperationPerSecProps {
servicename: IServiceName['servicename'];
tagFilterItems: TagFilterItem[];
topLevelOperations: string[];
dotMetricsEnabled: boolean;
}
export interface LatencyProps {
@@ -60,7 +57,6 @@ export interface LatencyProps {
tagFilterItems: TagFilterItem[];
isSpanMetricEnable?: boolean;
topLevelOperationsRoute: string[];
dotMetricsEnabled: boolean;
}
export interface ApDexProps {
@@ -78,5 +74,4 @@ export interface TableRendererProps {
export interface ApDexMetricsQueryBuilderQueriesProps extends ApDexProps {
delta: boolean;
metricsBuckets: number[];
dotMetricsEnabled: boolean;
}

View File

@@ -85,14 +85,11 @@ export enum WidgetKeys {
HasError = 'hasError',
Address = 'address',
DurationNano = 'durationNano',
StatusCodeNorm = 'status_code',
StatusCode = 'status.code',
Operation = 'operation',
OperationName = 'operationName',
Service_name_norm = 'service_name',
Service_name = 'service.name',
OTelServiceName = 'service.name',
ServiceName = 'serviceName',
SignozLatencyCountNorm = 'signoz_latency_count',
SignozLatencyCount = 'signoz_latency.count',
SignozDBLatencyCount = 'signoz_db_latency_count',
DatabaseCallCount = 'signoz_database_call_count',
@@ -101,10 +98,8 @@ export enum WidgetKeys {
SignozCallsTotal = 'signoz_calls_total',
SignozExternalCallLatencyCount = 'signoz_external_call_latency_count',
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_norm = 'db_system',
SignozLatencyBucket = 'signoz_latency.bucket',
DbSystem = 'db.system',
}
export const topOperationMetricsDownloadOptions: DownloadOptions = {

View File

@@ -32,5 +32,4 @@ export interface DatabaseCallsRPSProps extends DatabaseCallProps {
export interface DatabaseCallProps {
servicename: IServiceName['servicename'];
tagFilterItems: TagFilterItem[];
dotMetricsEnabled: boolean;
}

View File

@@ -53,8 +53,6 @@ import { getUserOperatingSystem, UserOperatingSystem } from 'utils/getUserOS';
import { useSelectPopupContainer } from 'utils/selectPopupContainer';
import { v4 as uuid } from 'uuid';
import { FeatureKeys } from '../../../../constants/features';
import { useAppContext } from '../../../../providers/App/App';
import { selectStyle } from './config';
import { PLACEHOLDER } from './constant';
import ExampleQueriesRendererForLogs from './ExampleQueriesRendererForLogs';
@@ -104,11 +102,6 @@ function QueryBuilderSearch({
const [isEditingTag, setIsEditingTag] = useState(false);
const { featureFlags } = useAppContext();
const dotMetricsEnabled =
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
?.active || false;
const {
updateTag,
handleClearTag,
@@ -128,7 +121,6 @@ function QueryBuilderSearch({
exampleQueries,
} = useAutoComplete(
query,
dotMetricsEnabled,
whereClauseConfig,
isLogsExplorerPage,
isInfraMonitoring,
@@ -146,7 +138,6 @@ function QueryBuilderSearch({
const { sourceKeys, handleRemoveSourceKey } = useFetchKeysAndValues(
searchValue,
query,
dotMetricsEnabled,
searchKey,
isLogsExplorerPage,
isInfraMonitoring,

View File

@@ -14,8 +14,6 @@ import { SelectOption } from 'types/common/select';
import { popupContainer } from 'utils/selectPopupContainer';
import { v4 as uuid } from 'uuid';
import { FeatureKeys } from '../../constants/features';
import { useAppContext } from '../../providers/App/App';
import QueryChip from './components/QueryChip';
import { QueryChipItem, SearchContainer } from './styles';
@@ -42,12 +40,7 @@ function ResourceAttributesFilter({
SelectOption<string, string>[]
>([]);
const { featureFlags } = useAppContext();
const dotMetricsEnabled =
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
?.active || false;
const resourceDeploymentKey = getResourceDeploymentKeys(dotMetricsEnabled);
const resourceDeploymentKey = getResourceDeploymentKeys();
const [selectedEnvironments, setSelectedEnvironments] = useState<string[]>([]);
@@ -73,14 +66,14 @@ function ResourceAttributesFilter({
}, [queries, resourceDeploymentKey]);
useEffect(() => {
getEnvironmentTagKeys(dotMetricsEnabled).then((tagKeys) => {
getEnvironmentTagKeys().then((tagKeys) => {
if (tagKeys && Array.isArray(tagKeys) && tagKeys.length > 0) {
getEnvironmentTagValues(dotMetricsEnabled).then((tagValues) => {
getEnvironmentTagValues().then((tagValues) => {
setEnvironments(tagValues);
});
}
});
}, [dotMetricsEnabled]);
}, []);
return (
<div className="resourceAttributesFilter-container">

View File

@@ -3,8 +3,6 @@ import {
getResourceDeploymentKeys,
} from 'hooks/useResourceAttribute/utils';
import { FeatureKeys } from '../../../../constants/features';
import { useAppContext } from '../../../../providers/App/App';
import { QueryChipContainer, QueryChipItem } from '../../styles';
import { IQueryChipProps } from './types';
@@ -13,13 +11,7 @@ function QueryChip({ queryData, onClose }: IQueryChipProps): JSX.Element {
onClose(queryData.id);
};
const { featureFlags } = useAppContext();
const dotMetricsEnabled =
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
?.active || false;
const isClosable =
queryData.tagKey !== getResourceDeploymentKeys(dotMetricsEnabled);
const isClosable = queryData.tagKey !== getResourceDeploymentKeys();
return (
<QueryChipContainer>

View File

@@ -4,8 +4,6 @@ import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
import { GlobalReducer } from 'types/reducer/globalTime';
import { FeatureKeys } from '../../../constants/features';
import { useAppContext } from '../../../providers/App/App';
import { ServiceMetricsProps } from '../types';
import { getQueryRangeRequestData } from '../utils';
import ServiceMetricTable from './ServiceMetricTable';
@@ -18,19 +16,13 @@ function ServiceMetricsApplication({
GlobalReducer
>((state) => state.globalTime);
const { featureFlags } = useAppContext();
const dotMetricsEnabled =
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
?.active || false;
const queryRangeRequestData = useMemo(
() =>
getQueryRangeRequestData({
topLevelOperations,
globalSelectedInterval,
dotMetricsEnabled,
}),
[globalSelectedInterval, topLevelOperations, dotMetricsEnabled],
[globalSelectedInterval, topLevelOperations],
);
return (
<ServiceMetricTable

View File

@@ -19,13 +19,10 @@ import {
export const serviceMetricsQuery = (
topLevelOperation: [keyof ServiceDataProps, string[]],
dotMetricsEnabled: boolean,
): QueryBuilderData => {
const p99AutoCompleteData: BaseAutocompleteData = {
dataType: DataTypes.Float64,
key: dotMetricsEnabled
? WidgetKeys.Signoz_latency_bucket
: WidgetKeys.Signoz_latency_bucket_norm,
key: WidgetKeys.SignozLatencyBucket,
type: '',
};
@@ -53,9 +50,7 @@ export const serviceMetricsQuery = (
id: '',
key: {
dataType: DataTypes.String,
key: dotMetricsEnabled
? WidgetKeys.Service_name
: WidgetKeys.Service_name_norm,
key: WidgetKeys.OTelServiceName,
type: MetricsType.Resource,
},
op: OPERATORS.IN,
@@ -78,9 +73,7 @@ export const serviceMetricsQuery = (
id: '',
key: {
dataType: DataTypes.String,
key: dotMetricsEnabled
? WidgetKeys.Service_name
: WidgetKeys.Service_name_norm,
key: WidgetKeys.OTelServiceName,
type: MetricsType.Resource,
},
op: OPERATORS.IN,
@@ -90,7 +83,7 @@ export const serviceMetricsQuery = (
id: '',
key: {
dataType: DataTypes.Int64,
key: dotMetricsEnabled ? WidgetKeys.StatusCode : WidgetKeys.StatusCodeNorm,
key: WidgetKeys.StatusCode,
type: MetricsType.Tag,
},
op: OPERATORS.IN,
@@ -113,9 +106,7 @@ export const serviceMetricsQuery = (
id: '',
key: {
dataType: DataTypes.String,
key: dotMetricsEnabled
? WidgetKeys.Service_name
: WidgetKeys.Service_name_norm,
key: WidgetKeys.OTelServiceName,
type: MetricsType.Resource,
},
op: OPERATORS.IN,
@@ -138,9 +129,7 @@ export const serviceMetricsQuery = (
id: '',
key: {
dataType: DataTypes.String,
key: dotMetricsEnabled
? WidgetKeys.Service_name
: WidgetKeys.Service_name_norm,
key: WidgetKeys.OTelServiceName,
type: MetricsType.Resource,
},
op: OPERATORS.IN,
@@ -193,9 +182,7 @@ export const serviceMetricsQuery = (
const groupBy: BaseAutocompleteData[] = [
{
dataType: DataTypes.String,
key: dotMetricsEnabled
? WidgetKeys.Service_name
: WidgetKeys.Service_name_norm,
key: WidgetKeys.OTelServiceName,
type: MetricsType.Tag,
},
];

View File

@@ -17,8 +17,6 @@ import { AppState } from 'store/reducers';
import { GlobalReducer } from 'types/reducer/globalTime';
import { Tags } from 'types/reducer/trace';
import { FeatureKeys } from '../../../constants/features';
import { useAppContext } from '../../../providers/App/App';
import SkipOnBoardingModal from '../SkipOnBoardModal';
import ServiceTraceTable from './ServiceTracesTable';
@@ -40,11 +38,6 @@ function ServiceTraces(): JSX.Element {
selectedTags,
});
const { featureFlags } = useAppContext();
const dotMetricsEnabled =
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
?.active || false;
useErrorNotification(error);
const services = data || [];
@@ -62,7 +55,7 @@ function ServiceTraces(): JSX.Element {
useEffect(() => {
if (!logEventCalledRef.current && !isUndefined(data)) {
const selectedEnvironments = queries.find(
(val) => val.tagKey === getResourceDeploymentKeys(dotMetricsEnabled),
(val) => val.tagKey === getResourceDeploymentKeys(),
)?.tagValue;
const rps = data.reduce((total, service) => total + service.callRate, 0);

View File

@@ -26,7 +26,6 @@ export interface ServiceMetricsTableProps {
export interface GetQueryRangeRequestDataProps {
topLevelOperations: [keyof ServiceDataProps, string[]][];
globalSelectedInterval: Time | CustomTimeType;
dotMetricsEnabled: boolean;
}
export interface GetServiceListFromQueryProps {

View File

@@ -26,7 +26,6 @@ export function getSeriesValue(
export const getQueryRangeRequestData = ({
topLevelOperations,
globalSelectedInterval,
dotMetricsEnabled,
}: GetQueryRangeRequestDataProps): GetQueryResultsProps[] => {
const requestData: GetQueryResultsProps[] = [];
topLevelOperations.forEach((operation) => {
@@ -34,7 +33,7 @@ export const getQueryRangeRequestData = ({
query: {
queryType: EQueryType.QUERY_BUILDER,
promql: [],
builder: serviceMetricsQuery(operation, dotMetricsEnabled),
builder: serviceMetricsQuery(operation),
clickhouse_sql: [],
id: uuid(),
},

View File

@@ -27,7 +27,6 @@ export type WhereClauseConfig = {
export const useAutoComplete = (
query: IBuilderQuery,
dotMetricsEnabled: boolean,
whereClauseConfig?: WhereClauseConfig,
shouldUseSuggestions?: boolean,
isInfraMonitoring?: boolean,
@@ -40,7 +39,6 @@ export const useAutoComplete = (
const { keys, results, isFetching, exampleQueries } = useFetchKeysAndValues(
searchValue,
query,
dotMetricsEnabled,
searchKey,
shouldUseSuggestions,
isInfraMonitoring,

View File

@@ -48,7 +48,6 @@ type IuseFetchKeysAndValues = {
export const useFetchKeysAndValues = (
searchValue: string,
query: IBuilderQuery,
dotMetricsEnabled: boolean,
searchKey: string,
shouldUseSuggestions?: boolean,
isInfraMonitoring?: boolean,

View File

@@ -6,8 +6,6 @@ import { useSafeNavigate } from 'hooks/useSafeNavigate';
import useUrlQuery from 'hooks/useUrlQuery';
import { encode } from 'js-base64';
import { FeatureKeys } from '../../constants/features';
import { useAppContext } from '../../providers/App/App';
import { whilelistedKeys } from './config';
import { ResourceContext } from './context';
import {
@@ -58,11 +56,6 @@ function ResourceProvider({ children }: Props): JSX.Element {
}
};
const { featureFlags } = useAppContext();
const dotMetricsEnabled =
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
?.active || false;
const dispatchQueries = useCallback(
(queries: IResourceAttribute[]): void => {
urlQuery.set(
@@ -78,7 +71,7 @@ function ResourceProvider({ children }: Props): JSX.Element {
const loadTagKeys = (): void => {
handleLoading(true);
GetTagKeys(dotMetricsEnabled)
GetTagKeys()
.then((tagKeys) => {
const options = mappingWithRoutesAndKeys(pathname, tagKeys);
setOptionsData({ options, mode: undefined });
@@ -161,15 +154,15 @@ function ResourceProvider({ children }: Props): JSX.Element {
setSelectedQueries([...value]);
},
[optionsData.mode, step, staging, dotMetricsEnabled, pathname],
[optionsData.mode, step, staging, pathname],
);
const handleEnvironmentChange = useCallback(
(environments: string[]): void => {
const staging = [getResourceDeploymentKeys(dotMetricsEnabled), 'IN'];
const staging = [getResourceDeploymentKeys(), 'IN'];
const queriesCopy = queries.filter(
(query) => query.tagKey !== getResourceDeploymentKeys(dotMetricsEnabled),
(query) => query.tagKey !== getResourceDeploymentKeys(),
);
if (environments && Array.isArray(environments) && environments.length > 0) {
@@ -184,7 +177,7 @@ function ResourceProvider({ children }: Props): JSX.Element {
setStep('Idle');
},
[dispatchQueries, dotMetricsEnabled, queries],
[dispatchQueries, queries],
);
const handleClose = useCallback(

View File

@@ -2,13 +2,9 @@ import { ReactNode } from 'react';
import { QueryClient, QueryClientProvider } from 'react-query';
import { Router } from 'react-router-dom';
import { act, renderHook, waitFor } from '@testing-library/react';
import { FeatureKeys } from 'constants/features';
import ROUTES from 'constants/routes';
import { createMemoryHistory, MemoryHistory } from 'history';
import { encode } from 'js-base64';
import { AppContext } from 'providers/App/App';
import { IAppContext } from 'providers/App/types';
import { getAppContextMock } from 'tests/test-utils';
import ResourceProvider from '../ResourceProvider';
import useResourceAttribute from '../useResourceAttribute';
@@ -55,10 +51,8 @@ const mockTagValues = getResourceAttributesTagValues as jest.MockedFunction<
function createWrapper({
routerHistory,
appContextOverrides,
}: {
routerHistory: MemoryHistory;
appContextOverrides?: Partial<IAppContext>;
}): ({ children }: { children: ReactNode }) => JSX.Element {
const queryClient = new QueryClient({
defaultOptions: { queries: { retry: false } },
@@ -66,13 +60,9 @@ function createWrapper({
return function Wrapper({ children }: { children: ReactNode }): JSX.Element {
return (
<QueryClientProvider client={queryClient}>
<AppContext.Provider
value={getAppContextMock('ADMIN', appContextOverrides)}
>
<Router history={routerHistory}>
<ResourceProvider>{children}</ResourceProvider>
</Router>
</AppContext.Provider>
<Router history={routerHistory}>
<ResourceProvider>{children}</ResourceProvider>
</Router>
</QueryClientProvider>
);
};
@@ -411,7 +401,7 @@ describe('ResourceProvider', () => {
});
describe('handleEnvironmentChange', () => {
it('adds an environment query when envs are provided', async () => {
it('adds a dotted environment query when envs are provided', async () => {
const routerHistory = createMemoryHistory({ initialEntries: ['/'] });
const { result } = renderHook(() => useResourceAttribute(), {
wrapper: createWrapper({ routerHistory }),
@@ -424,7 +414,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',
operator: 'IN',
tagValue: ['production'],
});
@@ -435,7 +425,7 @@ describe('ResourceProvider', () => {
const seeded = [
{
id: 'env',
tagKey: 'resource_deployment_environment',
tagKey: 'resource_deployment.environment',
operator: 'IN',
tagValue: ['production'],
},
@@ -459,7 +449,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');
expect(tagKeys).toContain('resource_service_name');
});
});
@@ -468,7 +458,7 @@ describe('ResourceProvider', () => {
const seeded = [
{
id: 'env',
tagKey: 'resource_deployment_environment',
tagKey: 'resource_deployment.environment',
operator: 'IN',
tagValue: ['production'],
},
@@ -486,43 +476,13 @@ describe('ResourceProvider', () => {
await waitFor(() => {
const envQueries = result.current.queries.filter(
(q) => q.tagKey === 'resource_deployment_environment',
(q) => q.tagKey === 'resource_deployment.environment',
);
expect(envQueries).toHaveLength(1);
expect(envQueries[0].tagValue).toStrictEqual(['staging']);
});
});
it('uses the dotted deployment env key when DOT_METRICS_ENABLED is active', async () => {
const routerHistory = createMemoryHistory({ initialEntries: ['/'] });
const { result } = renderHook(() => useResourceAttribute(), {
wrapper: createWrapper({
routerHistory,
appContextOverrides: {
featureFlags: [
{
name: FeatureKeys.DOT_METRICS_ENABLED,
active: true,
usage: 0,
usage_limit: -1,
route: '',
},
],
},
}),
});
act(() => {
result.current.handleEnvironmentChange(['production']);
});
await waitFor(() => {
expect(result.current.queries[0].tagKey).toBe(
'resource_deployment.environment',
);
});
});
it('preserves unrelated query params when dispatching', async () => {
const routerHistory = createMemoryHistory({
initialEntries: ['/?tab=overview'],

View File

@@ -5,13 +5,13 @@ import { mappingWithRoutesAndKeys } from '../utils';
describe('useResourceAttribute config', () => {
describe('whilelistedKeys', () => {
it('should include underscore-notation keys (DOT_METRICS_ENABLED=false)', () => {
it('should include underscore-notation keys', () => {
expect(whilelistedKeys).toContain('resource_deployment_environment');
expect(whilelistedKeys).toContain('resource_k8s_cluster_name');
expect(whilelistedKeys).toContain('resource_k8s_cluster_namespace');
});
it('should include dot-notation keys (DOT_METRICS_ENABLED=true)', () => {
it('should include dot-notation keys', () => {
expect(whilelistedKeys).toContain('resource_deployment.environment');
expect(whilelistedKeys).toContain('resource_k8s.cluster.name');
expect(whilelistedKeys).toContain('resource_k8s.cluster.namespace');

View File

@@ -144,19 +144,11 @@ export const OperatorSchema: IOption[] = OperatorConversions.map(
}),
);
export const getResourceDeploymentKeys = (
dotMetricsEnabled: boolean,
): string => {
if (dotMetricsEnabled) {
return 'resource_deployment.environment';
}
return 'resource_deployment_environment';
};
export const getResourceDeploymentKeys = (): string =>
'resource_deployment.environment';
export const GetTagKeys = async (
dotMetricsEnabled: boolean,
): Promise<IOption[]> => {
const resourceDeploymentKey = getResourceDeploymentKeys(dotMetricsEnabled);
export const GetTagKeys = async (): Promise<IOption[]> => {
const resourceDeploymentKey = getResourceDeploymentKeys();
const { payload } = await getResourceAttributesTagKeys({
metricName: 'signoz_calls_total',
match: 'resource_',
@@ -176,12 +168,10 @@ export const GetTagKeys = async (
}));
};
export const getEnvironmentTagKeys = async (
dotMetricsEnabled: boolean,
): Promise<IOption[]> => {
export const getEnvironmentTagKeys = async (): Promise<IOption[]> => {
const { payload } = await getResourceAttributesTagKeys({
metricName: 'signoz_calls_total',
match: getResourceDeploymentKeys(dotMetricsEnabled),
match: getResourceDeploymentKeys(),
});
if (!payload || !payload?.data) {
return [];
@@ -194,11 +184,9 @@ export const getEnvironmentTagKeys = async (
}));
};
export const getEnvironmentTagValues = async (
dotMetricsEnabled: boolean,
): Promise<IOption[]> => {
export const getEnvironmentTagValues = async (): Promise<IOption[]> => {
const { payload } = await getResourceAttributesTagValues({
tagKey: getResourceDeploymentKeys(dotMetricsEnabled),
tagKey: getResourceDeploymentKeys(),
metricName: 'signoz_calls_total',
});

View File

@@ -4,8 +4,6 @@ import { CardContainer } from 'container/GridCardLayout/styles';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { Widgets } from 'types/api/dashboard/getAll';
import { FeatureKeys } from '../../../../constants/features';
import { useAppContext } from '../../../../providers/App/App';
import MetricPageGridGraph from './MetricPageGraph';
import {
getAverageRequestLatencyWidgetData,
@@ -73,20 +71,15 @@ function MetricColumnGraphs({
}): JSX.Element {
const { t } = useTranslation('messagingQueues');
const { featureFlags } = useAppContext();
const dotMetricsEnabled =
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
?.active || false;
const metricsData = [
{
title: t('metricGraphCategory.brokerMetrics.title'),
description: t('metricGraphCategory.brokerMetrics.description'),
graphCount: [
getBrokerCountWidgetData(dotMetricsEnabled),
getRequestTimesWidgetData(dotMetricsEnabled),
getProducerFetchRequestPurgatoryWidgetData(dotMetricsEnabled),
getBrokerNetworkThroughputWidgetData(dotMetricsEnabled),
getBrokerCountWidgetData(),
getRequestTimesWidgetData(),
getProducerFetchRequestPurgatoryWidgetData(),
getBrokerNetworkThroughputWidgetData(),
],
id: 'broker-metrics',
},
@@ -94,11 +87,11 @@ function MetricColumnGraphs({
title: t('metricGraphCategory.producerMetrics.title'),
description: t('metricGraphCategory.producerMetrics.description'),
graphCount: [
getIoWaitTimeWidgetData(dotMetricsEnabled),
getRequestResponseWidgetData(dotMetricsEnabled),
getAverageRequestLatencyWidgetData(dotMetricsEnabled),
getKafkaProducerByteRateWidgetData(dotMetricsEnabled),
getBytesConsumedWidgetData(dotMetricsEnabled),
getIoWaitTimeWidgetData(),
getRequestResponseWidgetData(),
getAverageRequestLatencyWidgetData(),
getKafkaProducerByteRateWidgetData(),
getBytesConsumedWidgetData(),
],
id: 'producer-metrics',
},
@@ -106,11 +99,11 @@ function MetricColumnGraphs({
title: t('metricGraphCategory.consumerMetrics.title'),
description: t('metricGraphCategory.consumerMetrics.description'),
graphCount: [
getConsumerOffsetWidgetData(dotMetricsEnabled),
getConsumerGroupMemberWidgetData(dotMetricsEnabled),
getConsumerLagByGroupWidgetData(dotMetricsEnabled),
getConsumerFetchRateWidgetData(dotMetricsEnabled),
getMessagesConsumedWidgetData(dotMetricsEnabled),
getConsumerOffsetWidgetData(),
getConsumerGroupMemberWidgetData(),
getConsumerLagByGroupWidgetData(),
getConsumerFetchRateWidgetData(),
getMessagesConsumedWidgetData(),
],
id: 'consumer-metrics',
},

View File

@@ -8,8 +8,6 @@ import { useIsDarkMode } from 'hooks/useDarkMode';
import { ChevronDown, ChevronUp } from '@signozhq/icons';
import { Widgets } from 'types/api/dashboard/getAll';
import { FeatureKeys } from '../../../../constants/features';
import { useAppContext } from '../../../../providers/App/App';
import MetricColumnGraphs from './MetricColumnGraphs';
import MetricPageGridGraph from './MetricPageGraph';
import {
@@ -97,11 +95,6 @@ function MetricPage(): JSX.Element {
}));
};
const { featureFlags } = useAppContext();
const dotMetricsEnabled =
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
?.active || false;
const { t } = useTranslation('messagingQueues');
const metricSections = [
@@ -110,10 +103,10 @@ function MetricPage(): JSX.Element {
title: t('metricGraphCategory.brokerJVMMetrics.title'),
description: t('metricGraphCategory.brokerJVMMetrics.description'),
graphCount: [
getJvmGCCountWidgetData(dotMetricsEnabled),
getJvmGcCollectionsElapsedWidgetData(dotMetricsEnabled),
getCpuRecentUtilizationWidgetData(dotMetricsEnabled),
getJvmMemoryHeapWidgetData(dotMetricsEnabled),
getJvmGCCountWidgetData(),
getJvmGcCollectionsElapsedWidgetData(),
getCpuRecentUtilizationWidgetData(),
getJvmMemoryHeapWidgetData(),
],
},
{
@@ -121,10 +114,10 @@ function MetricPage(): JSX.Element {
title: t('metricGraphCategory.partitionMetrics.title'),
description: t('metricGraphCategory.partitionMetrics.description'),
graphCount: [
getPartitionCountPerTopicWidgetData(dotMetricsEnabled),
getCurrentOffsetPartitionWidgetData(dotMetricsEnabled),
getOldestOffsetWidgetData(dotMetricsEnabled),
getInsyncReplicasWidgetData(dotMetricsEnabled),
getPartitionCountPerTopicWidgetData(),
getCurrentOffsetPartitionWidgetData(),
getOldestOffsetWidgetData(),
getInsyncReplicasWidgetData(),
],
},
];
@@ -138,7 +131,7 @@ function MetricPage(): JSX.Element {
// Only log when first graph has rendered and we haven't logged yet
if (renderedGraphCountRef.current === 1 && !hasLoggedRef.current) {
logEvent('MQ Kafka: Metric view', {
void logEvent('MQ Kafka: Metric view', {
graphRendered: true,
});
hasLoggedRef.current = true;

View File

@@ -78,21 +78,15 @@ export function getWidgetQuery(
};
}
export const getRequestTimesWidgetData = (
dotMetricsEnabled: boolean,
): Widgets =>
export const getRequestTimesWidgetData = (): Widgets =>
getWidgetQueryBuilder(
getWidgetQuery({
queryData: [
{
aggregateAttribute: {
dataType: DataTypes.Float64,
// choose key based on flag
key: dotMetricsEnabled
? 'kafka.request.time.avg'
: 'kafka_request_time_avg',
// mirror into the id as well
id: 'kafka_request_time_avg--float64--Gauge--true',
key: 'kafka.request.time.avg',
id: 'kafka.request.time.avg--float64--Gauge--true',
type: 'Gauge',
},
aggregateOperator: 'avg',
@@ -122,15 +116,15 @@ export const getRequestTimesWidgetData = (
}),
);
export const getBrokerCountWidgetData = (dotMetricsEnabled: boolean): Widgets =>
export const getBrokerCountWidgetData = (): Widgets =>
getWidgetQueryBuilder(
getWidgetQuery({
queryData: [
{
aggregateAttribute: {
dataType: DataTypes.Float64,
key: dotMetricsEnabled ? 'kafka.brokers' : 'kafka_brokers',
id: 'kafka_brokers--float64--Gauge--true',
key: 'kafka.brokers',
id: 'kafka.brokers--float64--Gauge--true',
type: 'Gauge',
},
aggregateOperator: 'sum',
@@ -156,20 +150,15 @@ export const getBrokerCountWidgetData = (dotMetricsEnabled: boolean): Widgets =>
}),
);
export const getProducerFetchRequestPurgatoryWidgetData = (
dotMetricsEnabled: boolean,
): Widgets =>
export const getProducerFetchRequestPurgatoryWidgetData = (): Widgets =>
getWidgetQueryBuilder(
getWidgetQuery({
queryData: [
{
aggregateAttribute: {
dataType: DataTypes.Float64,
// inline ternary based on dotMetricsEnabled
key: dotMetricsEnabled ? 'kafka.purgatory.size' : 'kafka_purgatory_size',
id: `${
dotMetricsEnabled ? 'kafka.purgatory.size' : 'kafka_purgatory_size'
}--float64--Gauge--true`,
key: 'kafka.purgatory.size',
id: 'kafka.purgatory.size--float64--Gauge--true',
type: 'Gauge',
},
aggregateOperator: 'avg',
@@ -196,24 +185,15 @@ export const getProducerFetchRequestPurgatoryWidgetData = (
}),
);
export const getBrokerNetworkThroughputWidgetData = (
dotMetricsEnabled: boolean,
): Widgets =>
export const getBrokerNetworkThroughputWidgetData = (): Widgets =>
getWidgetQueryBuilder(
getWidgetQuery({
queryData: [
{
aggregateAttribute: {
dataType: DataTypes.Float64,
// inline ternary based on dotMetricsEnabled
key: dotMetricsEnabled
? 'kafka_server_brokertopicmetrics_total_replicationbytesinpersec_oneminuterate'
: 'kafka_server_brokertopicmetrics_bytesoutpersec_oneminuterate',
id: `${
dotMetricsEnabled
? 'kafka_server_brokertopicmetrics_total_replicationbytesinpersec_oneminuterate'
: 'kafka_server_brokertopicmetrics_bytesoutpersec_oneminuterate'
}--float64--Gauge--true`,
key: 'kafka_server_brokertopicmetrics_total_replicationbytesinpersec_oneminuterate',
id: 'kafka_server_brokertopicmetrics_total_replicationbytesinpersec_oneminuterate--float64--Gauge--true',
type: 'Gauge',
},
aggregateOperator: 'avg',
@@ -240,22 +220,15 @@ export const getBrokerNetworkThroughputWidgetData = (
}),
);
export const getIoWaitTimeWidgetData = (dotMetricsEnabled: boolean): Widgets =>
export const getIoWaitTimeWidgetData = (): Widgets =>
getWidgetQueryBuilder(
getWidgetQuery({
queryData: [
{
aggregateAttribute: {
dataType: DataTypes.Float64,
// inline ternary based on dotMetricsEnabled
key: dotMetricsEnabled
? 'kafka.producer.io_waittime_total'
: 'kafka_producer_io_waittime_total',
id: `${
dotMetricsEnabled
? 'kafka.producer.io_waittime_total'
: 'kafka_producer_io_waittime_total'
}--float64--Sum--true`,
key: 'kafka.producer.io_waittime_total',
id: 'kafka.producer.io_waittime_total--float64--Sum--true',
type: 'Sum',
},
aggregateOperator: 'rate',
@@ -282,23 +255,15 @@ export const getIoWaitTimeWidgetData = (dotMetricsEnabled: boolean): Widgets =>
}),
);
export const getRequestResponseWidgetData = (
dotMetricsEnabled: boolean,
): Widgets =>
export const getRequestResponseWidgetData = (): Widgets =>
getWidgetQueryBuilder(
getWidgetQuery({
queryData: [
{
aggregateAttribute: {
dataType: DataTypes.Float64,
key: dotMetricsEnabled
? 'kafka.producer.request_rate'
: 'kafka_producer_request_rate',
id: `${
dotMetricsEnabled
? 'kafka.producer.request_rate'
: 'kafka_producer_request_rate'
}--float64--Gauge--true`,
key: 'kafka.producer.request_rate',
id: 'kafka.producer.request_rate--float64--Gauge--true',
type: 'Gauge',
},
aggregateOperator: 'avg',
@@ -321,14 +286,8 @@ export const getRequestResponseWidgetData = (
{
aggregateAttribute: {
dataType: DataTypes.Float64,
key: dotMetricsEnabled
? 'kafka.producer.response_rate'
: 'kafka_producer_response_rate',
id: `${
dotMetricsEnabled
? 'kafka.producer.response_rate'
: 'kafka_producer_response_rate'
}--float64--Gauge--true`,
key: 'kafka.producer.response_rate',
id: 'kafka.producer.response_rate--float64--Gauge--true',
type: 'Gauge',
},
aggregateOperator: 'avg',
@@ -355,23 +314,15 @@ export const getRequestResponseWidgetData = (
}),
);
export const getAverageRequestLatencyWidgetData = (
dotMetricsEnabled: boolean,
): Widgets =>
export const getAverageRequestLatencyWidgetData = (): Widgets =>
getWidgetQueryBuilder(
getWidgetQuery({
queryData: [
{
aggregateAttribute: {
dataType: DataTypes.Float64,
key: dotMetricsEnabled
? 'kafka.producer.request_latency_avg'
: 'kafka_producer_request_latency_avg',
id: `${
dotMetricsEnabled
? 'kafka.producer.request_latency_avg'
: 'kafka_producer_request_latency_avg'
}--float64--Gauge--true`,
key: 'kafka.producer.request_latency_avg',
id: 'kafka.producer.request_latency_avg--float64--Gauge--true',
type: 'Gauge',
},
aggregateOperator: 'avg',
@@ -398,23 +349,15 @@ export const getAverageRequestLatencyWidgetData = (
}),
);
export const getKafkaProducerByteRateWidgetData = (
dotMetricsEnabled: boolean,
): Widgets =>
export const getKafkaProducerByteRateWidgetData = (): Widgets =>
getWidgetQueryBuilder(
getWidgetQuery({
queryData: [
{
aggregateAttribute: {
dataType: DataTypes.Float64,
key: dotMetricsEnabled
? 'kafka.producer.byte_rate'
: 'kafka_producer_byte_rate',
id: `${
dotMetricsEnabled
? 'kafka.producer.byte_rate'
: 'kafka_producer_byte_rate'
}--float64--Gauge--true`,
key: 'kafka.producer.byte_rate',
id: 'kafka.producer.byte_rate--float64--Gauge--true',
type: 'Gauge',
},
aggregateOperator: 'avg',
@@ -442,31 +385,21 @@ export const getKafkaProducerByteRateWidgetData = (
timeAggregation: 'avg',
},
],
title: dotMetricsEnabled
? 'kafka.producer.byte_rate'
: 'kafka_producer_byte_rate',
title: 'kafka.producer.byte_rate',
description:
'Helps measure the data output rate from the producer, indicating the load a producer is placing on Kafka brokers.',
}),
);
export const getBytesConsumedWidgetData = (
dotMetricsEnabled: boolean,
): Widgets =>
export const getBytesConsumedWidgetData = (): Widgets =>
getWidgetQueryBuilder(
getWidgetQuery({
queryData: [
{
aggregateAttribute: {
dataType: DataTypes.Float64,
key: dotMetricsEnabled
? 'kafka.consumer.bytes_consumed_rate'
: 'kafka_consumer_bytes_consumed_rate',
id: `${
dotMetricsEnabled
? 'kafka.consumer.bytes_consumed_rate'
: 'kafka_consumer_bytes_consumed_rate'
}--float64--Gauge--true`,
key: 'kafka.consumer.bytes_consumed_rate',
id: 'kafka.consumer.bytes_consumed_rate--float64--Gauge--true',
type: 'Gauge',
},
aggregateOperator: 'avg',
@@ -494,23 +427,15 @@ export const getBytesConsumedWidgetData = (
}),
);
export const getConsumerOffsetWidgetData = (
dotMetricsEnabled: boolean,
): Widgets =>
export const getConsumerOffsetWidgetData = (): Widgets =>
getWidgetQueryBuilder(
getWidgetQuery({
queryData: [
{
aggregateAttribute: {
dataType: DataTypes.Float64,
key: dotMetricsEnabled
? 'kafka.consumer_group.offset'
: 'kafka_consumer_group_offset',
id: `${
dotMetricsEnabled
? 'kafka.consumer_group.offset'
: 'kafka_consumer_group_offset'
}--float64--Gauge--true`,
key: 'kafka.consumer_group.offset',
id: 'kafka.consumer_group.offset--float64--Gauge--true',
type: 'Gauge',
},
aggregateOperator: 'avg',
@@ -556,23 +481,15 @@ export const getConsumerOffsetWidgetData = (
}),
);
export const getConsumerGroupMemberWidgetData = (
dotMetricsEnabled: boolean,
): Widgets =>
export const getConsumerGroupMemberWidgetData = (): Widgets =>
getWidgetQueryBuilder(
getWidgetQuery({
queryData: [
{
aggregateAttribute: {
dataType: DataTypes.Float64,
key: dotMetricsEnabled
? 'kafka.consumer_group.members'
: 'kafka_consumer_group_members',
id: `${
dotMetricsEnabled
? 'kafka.consumer_group.members'
: 'kafka_consumer_group_members'
}--float64--Gauge--true`,
key: 'kafka.consumer_group.members',
id: 'kafka.consumer_group.members--float64--Gauge--true',
type: 'Gauge',
},
aggregateOperator: 'sum',
@@ -605,23 +522,15 @@ export const getConsumerGroupMemberWidgetData = (
}),
);
export const getConsumerLagByGroupWidgetData = (
dotMetricsEnabled: boolean,
): Widgets =>
export const getConsumerLagByGroupWidgetData = (): Widgets =>
getWidgetQueryBuilder(
getWidgetQuery({
queryData: [
{
aggregateAttribute: {
dataType: DataTypes.Float64,
key: dotMetricsEnabled
? 'kafka.consumer_group.lag'
: 'kafka_consumer_group_lag',
id: `${
dotMetricsEnabled
? 'kafka.consumer_group.lag'
: 'kafka_consumer_group_lag'
}--float64--Gauge--true`,
key: 'kafka.consumer_group.lag',
id: 'kafka.consumer_group.lag--float64--Gauge--true',
type: 'Gauge',
},
aggregateOperator: 'avg',
@@ -667,23 +576,15 @@ export const getConsumerLagByGroupWidgetData = (
}),
);
export const getConsumerFetchRateWidgetData = (
dotMetricsEnabled: boolean,
): Widgets =>
export const getConsumerFetchRateWidgetData = (): Widgets =>
getWidgetQueryBuilder(
getWidgetQuery({
queryData: [
{
aggregateAttribute: {
dataType: DataTypes.Float64,
key: dotMetricsEnabled
? 'kafka.consumer.fetch_rate'
: 'kafka_consumer_fetch_rate',
id: `${
dotMetricsEnabled
? 'kafka.consumer.fetch_rate'
: 'kafka_consumer_fetch_rate'
}--float64--Gauge--true`,
key: 'kafka.consumer.fetch_rate',
id: 'kafka.consumer.fetch_rate--float64--Gauge--true',
type: 'Gauge',
},
aggregateOperator: 'avg',
@@ -696,7 +597,7 @@ export const getConsumerFetchRateWidgetData = (
{
dataType: DataTypes.String,
id: 'service_name--string--tag--false',
key: dotMetricsEnabled ? 'service.name' : 'service_name',
key: 'service.name',
type: 'tag',
},
],
@@ -717,23 +618,15 @@ export const getConsumerFetchRateWidgetData = (
}),
);
export const getMessagesConsumedWidgetData = (
dotMetricsEnabled: boolean,
): Widgets =>
export const getMessagesConsumedWidgetData = (): Widgets =>
getWidgetQueryBuilder(
getWidgetQuery({
queryData: [
{
aggregateAttribute: {
dataType: DataTypes.Float64,
key: dotMetricsEnabled
? 'kafka.consumer.records_consumed_rate'
: 'kafka_consumer_records_consumed_rate',
id: `${
dotMetricsEnabled
? 'kafka.consumer.records_consumed_rate'
: 'kafka_consumer_records_consumed_rate'
}--float64--Gauge--true`,
key: 'kafka.consumer.records_consumed_rate',
id: 'kafka.consumer.records_consumed_rate--float64--Gauge--true',
type: 'Gauge',
},
aggregateOperator: 'avg',
@@ -760,21 +653,15 @@ export const getMessagesConsumedWidgetData = (
}),
);
export const getJvmGCCountWidgetData = (dotMetricsEnabled: boolean): Widgets =>
export const getJvmGCCountWidgetData = (): Widgets =>
getWidgetQueryBuilder(
getWidgetQuery({
queryData: [
{
aggregateAttribute: {
dataType: DataTypes.Float64,
key: dotMetricsEnabled
? 'jvm.gc.collections.count'
: 'jvm_gc_collections_count',
id: `${
dotMetricsEnabled
? 'jvm.gc.collections.count'
: 'jvm_gc_collections_count'
}--float64--Sum--true`,
key: 'jvm.gc.collections.count',
id: 'jvm.gc.collections.count--float64--Sum--true',
type: 'Sum',
},
aggregateOperator: 'rate',
@@ -801,23 +688,15 @@ export const getJvmGCCountWidgetData = (dotMetricsEnabled: boolean): Widgets =>
}),
);
export const getJvmGcCollectionsElapsedWidgetData = (
dotMetricsEnabled: boolean,
): Widgets =>
export const getJvmGcCollectionsElapsedWidgetData = (): Widgets =>
getWidgetQueryBuilder(
getWidgetQuery({
queryData: [
{
aggregateAttribute: {
dataType: DataTypes.Float64,
key: dotMetricsEnabled
? 'jvm.gc.collections.elapsed'
: 'jvm_gc_collections_elapsed',
id: `${
dotMetricsEnabled
? 'jvm.gc.collections.elapsed'
: 'jvm_gc_collections_elapsed'
}--float64--Sum--true`,
key: 'jvm.gc.collections.elapsed',
id: 'jvm.gc.collections.elapsed--float64--Sum--true',
type: 'Sum',
},
aggregateOperator: 'rate',
@@ -838,31 +717,21 @@ export const getJvmGcCollectionsElapsedWidgetData = (
timeAggregation: 'rate',
},
],
title: dotMetricsEnabled
? 'jvm.gc.collections.elapsed'
: 'jvm_gc_collections_elapsed',
title: 'jvm.gc.collections.elapsed',
description:
'Measures the total time (usually in milliseconds) spent on garbage collection (GC) events in the Java Virtual Machine (JVM).',
}),
);
export const getCpuRecentUtilizationWidgetData = (
dotMetricsEnabled: boolean,
): Widgets =>
export const getCpuRecentUtilizationWidgetData = (): Widgets =>
getWidgetQueryBuilder(
getWidgetQuery({
queryData: [
{
aggregateAttribute: {
dataType: DataTypes.Float64,
key: dotMetricsEnabled
? 'jvm.cpu.recent_utilization'
: 'jvm_cpu_recent_utilization',
id: `${
dotMetricsEnabled
? 'jvm.cpu.recent_utilization'
: 'jvm_cpu_recent_utilization'
}--float64--Gauge--true`,
key: 'jvm.cpu.recent_utilization',
id: 'jvm.cpu.recent_utilization--float64--Gauge--true',
type: 'Gauge',
},
aggregateOperator: 'avg',
@@ -889,19 +758,15 @@ export const getCpuRecentUtilizationWidgetData = (
}),
);
export const getJvmMemoryHeapWidgetData = (
dotMetricsEnabled: boolean,
): Widgets =>
export const getJvmMemoryHeapWidgetData = (): Widgets =>
getWidgetQueryBuilder(
getWidgetQuery({
queryData: [
{
aggregateAttribute: {
dataType: DataTypes.Float64,
key: dotMetricsEnabled ? 'jvm.memory.heap.max' : 'jvm_memory_heap_max',
id: `${
dotMetricsEnabled ? 'jvm.memory.heap.max' : 'jvm_memory_heap_max'
}--float64--Gauge--true`,
key: 'jvm.memory.heap.max',
id: 'jvm.memory.heap.max--float64--Gauge--true',
type: 'Gauge',
},
aggregateOperator: 'avg',
@@ -928,21 +793,15 @@ export const getJvmMemoryHeapWidgetData = (
}),
);
export const getPartitionCountPerTopicWidgetData = (
dotMetricsEnabled: boolean,
): Widgets =>
export const getPartitionCountPerTopicWidgetData = (): Widgets =>
getWidgetQueryBuilder(
getWidgetQuery({
queryData: [
{
aggregateAttribute: {
dataType: DataTypes.Float64,
key: dotMetricsEnabled
? 'kafka.topic.partitions'
: 'kafka_topic_partitions',
id: `${
dotMetricsEnabled ? 'kafka.topic.partitions' : 'kafka_topic_partitions'
}--float64--Gauge--true`,
key: 'kafka.topic.partitions',
id: 'kafka.topic.partitions--float64--Gauge--true',
type: 'Gauge',
},
aggregateOperator: 'sum',
@@ -975,23 +834,15 @@ export const getPartitionCountPerTopicWidgetData = (
}),
);
export const getCurrentOffsetPartitionWidgetData = (
dotMetricsEnabled: boolean,
): Widgets =>
export const getCurrentOffsetPartitionWidgetData = (): Widgets =>
getWidgetQueryBuilder(
getWidgetQuery({
queryData: [
{
aggregateAttribute: {
dataType: DataTypes.Float64,
key: dotMetricsEnabled
? 'kafka.partition.current_offset'
: 'kafka_partition_current_offset',
id: `${
dotMetricsEnabled
? 'kafka.partition.current_offset'
: 'kafka_partition_current_offset'
}--float64--Gauge--true`,
key: 'kafka.partition.current_offset',
id: 'kafka.partition.current_offset--float64--Gauge--true',
type: 'Gauge',
},
aggregateOperator: 'avg',
@@ -1031,23 +882,15 @@ export const getCurrentOffsetPartitionWidgetData = (
}),
);
export const getOldestOffsetWidgetData = (
dotMetricsEnabled: boolean,
): Widgets =>
export const getOldestOffsetWidgetData = (): Widgets =>
getWidgetQueryBuilder(
getWidgetQuery({
queryData: [
{
aggregateAttribute: {
dataType: DataTypes.Float64,
key: dotMetricsEnabled
? 'kafka.partition.oldest_offset'
: 'kafka_partition_oldest_offset',
id: `${
dotMetricsEnabled
? 'kafka.partition.oldest_offset'
: 'kafka_partition_oldest_offset'
}--float64--Gauge--true`,
key: 'kafka.partition.oldest_offset',
id: 'kafka.partition.oldest_offset--float64--Gauge--true',
type: 'Gauge',
},
aggregateOperator: 'avg',
@@ -1087,23 +930,15 @@ export const getOldestOffsetWidgetData = (
}),
);
export const getInsyncReplicasWidgetData = (
dotMetricsEnabled: boolean,
): Widgets =>
export const getInsyncReplicasWidgetData = (): Widgets =>
getWidgetQueryBuilder(
getWidgetQuery({
queryData: [
{
aggregateAttribute: {
dataType: DataTypes.Float64,
key: dotMetricsEnabled
? 'kafka.partition.replicas_in_sync'
: 'kafka_partition_replicas_in_sync',
id: `${
dotMetricsEnabled
? 'kafka.partition.replicas_in_sync'
: 'kafka_partition_replicas_in_sync'
}--float64--Gauge--true`,
key: 'kafka.partition.replicas_in_sync',
id: 'kafka.partition.replicas_in_sync--float64--Gauge--true',
type: 'Gauge',
},
aggregateOperator: 'avg',

View File

@@ -11,8 +11,6 @@ import useDebouncedFn from 'hooks/useDebouncedFunction';
import useUrlQuery from 'hooks/useUrlQuery';
import { Check, Share2 } from '@signozhq/icons';
import { FeatureKeys } from '../../../constants/features';
import { useAppContext } from '../../../providers/App/App';
import { useGetAllConfigOptions } from './useGetAllConfigOptions';
import './MQConfigOptions.styles.scss';
@@ -40,19 +38,11 @@ const useConfigOptions = (
isFetching: boolean;
options: DefaultOptionType[];
} => {
const { featureFlags } = useAppContext();
const dotMetricsEnabled =
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
?.active || false;
const [searchText, setSearchText] = useState<string>('');
const { isFetching, options } = useGetAllConfigOptions(
{
attributeKey: type,
searchText,
},
dotMetricsEnabled,
);
const { isFetching, options } = useGetAllConfigOptions({
attributeKey: type,
searchText,
});
const handleDebouncedSearch = useDebouncedFn((searchText): void => {
setSearchText(searchText as string);
}, 500);

View File

@@ -3,7 +3,6 @@ import { useCallback, useMemo, useRef } from 'react';
import { useDispatch } from 'react-redux';
import { useHistory, useLocation } from 'react-router-dom';
import logEvent from 'api/common/logEvent';
import { FeatureKeys } from 'constants/features';
import { QueryParams } from 'constants/query';
import { PANEL_TYPES } from 'constants/queryBuilder';
import { ViewMenuAction } from 'container/GridCardLayout/config';
@@ -12,7 +11,6 @@ import { Card } from 'container/GridCardLayout/styles';
import { getWidgetQueryBuilder } from 'container/MetricsApplication/MetricsApplication.factory';
import { useIsDarkMode } from 'hooks/useDarkMode';
import useUrlQuery from 'hooks/useUrlQuery';
import { useAppContext } from 'providers/App/App';
import { UpdateTimeInterval } from 'store/actions';
import {
@@ -34,15 +32,9 @@ function MessagingQueuesGraph(): JSX.Element {
[consumerGrp, topic, partition],
);
const { featureFlags } = useAppContext();
const dotMetricsEnabled =
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
?.active || false;
const widgetData = useMemo(
() =>
getWidgetQueryBuilder(getWidgetQuery({ filterItems, dotMetricsEnabled })),
[filterItems, dotMetricsEnabled],
() => getWidgetQueryBuilder(getWidgetQuery({ filterItems })),
[filterItems],
);
const history = useHistory();
@@ -81,7 +73,7 @@ function MessagingQueuesGraph(): JSX.Element {
const checkIfDataExists = (isDataAvailable: boolean): void => {
if (!isLogEventCalled.current) {
isLogEventCalled.current = true;
logEvent('Messaging Queues: Graph data fetched', {
void logEvent('Messaging Queues: Graph data fetched', {
isDataAvailable,
});
}

View File

@@ -16,7 +16,6 @@ export interface GetAllConfigOptionsResponse {
export function useGetAllConfigOptions(
props: ConfigOptions,
dotMetricsEnabled: boolean,
): GetAllConfigOptionsResponse {
const { attributeKey, searchText } = props;
@@ -26,9 +25,7 @@ export function useGetAllConfigOptions(
const { payload } = await getAttributesValues({
aggregateOperator: 'avg',
dataSource: DataSource.METRICS,
aggregateAttribute: dotMetricsEnabled
? 'kafka.consumer_group.lag'
: 'kafka_consumer_group_lag',
aggregateAttribute: 'kafka.consumer_group.lag',
attributeKey,
searchText: searchText ?? '',
filterAttributeKeyDataType: DataTypes.String,

View File

@@ -94,10 +94,8 @@ export function getFiltersFromConfigOptions(
export function getWidgetQuery({
filterItems,
dotMetricsEnabled,
}: {
filterItems: TagFilterItem[];
dotMetricsEnabled: boolean;
}): GetWidgetQueryBuilderProps {
return {
title: 'Consumer Lag',
@@ -112,14 +110,8 @@ export function getWidgetQuery({
{
aggregateAttribute: {
dataType: DataTypes.Float64,
id: `${
dotMetricsEnabled
? 'kafka.consumer_group.lag'
: 'kafka_consumer_group_lag'
}--float64--Gauge--true`,
key: dotMetricsEnabled
? 'kafka.consumer_group.lag'
: 'kafka_consumer_group_lag',
id: 'kafka.consumer_group.lag--float64--Gauge--true',
key: 'kafka.consumer_group.lag',
type: 'Gauge',
},
aggregateOperator: 'max',

2
go.mod
View File

@@ -4,7 +4,7 @@ go 1.25.7
require (
dario.cat/mergo v1.0.2
github.com/AfterShip/clickhouse-sql-parser v0.5.4
github.com/AfterShip/clickhouse-sql-parser v0.5.5
github.com/ClickHouse/clickhouse-go/v2 v2.44.0
github.com/DATA-DOG/go-sqlmock v1.5.2
github.com/SigNoz/clickhouse-go-mock v0.14.0

4
go.sum
View File

@@ -66,8 +66,8 @@ dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo=
filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc=
github.com/AfterShip/clickhouse-sql-parser v0.5.4 h1:yiCQaMq8EO+dpKdnpP9YYd/ne6MSuOXgsMsNL33NiTI=
github.com/AfterShip/clickhouse-sql-parser v0.5.4/go.mod h1:Qi3qvPTfZb/aFwI5V4WFOahgjsLJa4MzVijIAfwOhDw=
github.com/AfterShip/clickhouse-sql-parser v0.5.5 h1:LCA23yAA4GgF73PoYXb67yzCdC4sXsj4geQz1Oij3U8=
github.com/AfterShip/clickhouse-sql-parser v0.5.5/go.mod h1:Qi3qvPTfZb/aFwI5V4WFOahgjsLJa4MzVijIAfwOhDw=
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0 h1:fou+2+WFTib47nS+nz/ozhEBnvU96bKHy6LjRsY4E28=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0/go.mod h1:t76Ruy8AHvUAC8GfMWJMa0ElSbuIcO03NLpynfbgsPA=

View File

@@ -25,6 +25,7 @@ import (
"github.com/SigNoz/signoz/pkg/modules/promote"
"github.com/SigNoz/signoz/pkg/modules/rawdataexport"
"github.com/SigNoz/signoz/pkg/modules/rulestatehistory"
"github.com/SigNoz/signoz/pkg/modules/savedview"
"github.com/SigNoz/signoz/pkg/modules/serviceaccount"
"github.com/SigNoz/signoz/pkg/modules/session"
"github.com/SigNoz/signoz/pkg/modules/spanmapper"
@@ -75,6 +76,7 @@ type provider struct {
rulerHandler ruler.Handler
llmPricingRuleHandler llmpricingrule.Handler
statsHandler statsreporter.Handler
savedViewHandler savedview.Handler
}
func NewFactory(
@@ -110,6 +112,7 @@ func NewFactory(
traceDetailHandler tracedetail.Handler,
rulerHandler ruler.Handler,
statsHandler statsreporter.Handler,
savedViewHandler savedview.Handler,
) factory.ProviderFactory[apiserver.APIServer, apiserver.Config] {
return factory.NewProviderFactory(factory.MustNewName("signoz"), func(ctx context.Context, providerSettings factory.ProviderSettings, config apiserver.Config) (apiserver.APIServer, error) {
return newProvider(
@@ -148,6 +151,7 @@ func NewFactory(
traceDetailHandler,
rulerHandler,
statsHandler,
savedViewHandler,
)
})
}
@@ -188,6 +192,7 @@ func newProvider(
traceDetailHandler tracedetail.Handler,
rulerHandler ruler.Handler,
statsHandler statsreporter.Handler,
savedViewHandler savedview.Handler,
) (apiserver.APIServer, error) {
settings := factory.NewScopedProviderSettings(providerSettings, "github.com/SigNoz/signoz/pkg/apiserver/signozapiserver")
router := mux.NewRouter().UseEncodedPath()
@@ -227,6 +232,7 @@ func newProvider(
rulerHandler: rulerHandler,
llmPricingRuleHandler: llmPricingRuleHandler,
statsHandler: statsHandler,
savedViewHandler: savedViewHandler,
}
provider.authzMiddleware = middleware.NewAuthZ(settings.Logger(), orgGetter, authzService)
@@ -359,6 +365,10 @@ func (provider *provider) AddToRouter(router *mux.Router) error {
return err
}
if err := provider.addSavedViewRoutes(router); err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,151 @@
package signozapiserver
import (
"net/http"
"github.com/SigNoz/signoz/pkg/http/handler"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/types/coretypes"
"github.com/SigNoz/signoz/pkg/types/savedviewtypes"
"github.com/gorilla/mux"
)
func (provider *provider) addSavedViewRoutes(router *mux.Router) error {
if err := router.Handle("/api/v2/saved_views", handler.New(
provider.authzMiddleware.CheckResources(provider.savedViewHandler.ListV2, authtypes.SigNozAdminRoleName, authtypes.SigNozEditorRoleName, authtypes.SigNozViewerRoleName),
handler.OpenAPIDef{
ID: "ListSavedViews",
Tags: []string{"saved_view"},
Summary: "List saved views",
Description: "Returns saved views, optionally filtered by source and name.",
Request: nil,
RequestQuery: new(savedviewtypes.ListSavedViewsParams),
RequestContentType: "",
Response: new([]*savedviewtypes.SavedView),
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{http.StatusBadRequest},
Deprecated: false,
SecuritySchemes: newScopedSecuritySchemes([]string{coretypes.ResourceMetaResourceSavedView.Scope(coretypes.VerbList)}),
},
handler.WithResourceDefs(handler.BasicResourceDef{
Resource: coretypes.ResourceMetaResourceSavedView,
Verb: coretypes.VerbList,
Category: coretypes.ActionCategoryDataAccess,
Selector: coretypes.WildcardSelector,
}),
)).Methods(http.MethodGet).GetError(); err != nil {
return err
}
if err := router.Handle("/api/v2/saved_views", handler.New(
provider.authzMiddleware.CheckResources(provider.savedViewHandler.CreateV2, authtypes.SigNozAdminRoleName, authtypes.SigNozEditorRoleName),
handler.OpenAPIDef{
ID: "CreateSavedView",
Tags: []string{"saved_view"},
Summary: "Create saved view",
Description: "Persists a saved view for the explore page. Returns the id of the created view.",
Request: new(savedviewtypes.PostableSavedView),
RequestContentType: "application/json",
Response: new(types.Identifiable),
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusCreated,
ErrorStatusCodes: []int{http.StatusBadRequest},
Deprecated: false,
SecuritySchemes: newScopedSecuritySchemes([]string{coretypes.ResourceMetaResourceSavedView.Scope(coretypes.VerbCreate)}),
},
handler.WithResourceDefs(handler.BasicResourceDef{
Resource: coretypes.ResourceMetaResourceSavedView,
Verb: coretypes.VerbCreate,
Category: coretypes.ActionCategoryDataAccess,
ID: coretypes.ResponseJSONPath("data.id"),
Selector: coretypes.WildcardSelector,
}),
)).Methods(http.MethodPost).GetError(); err != nil {
return err
}
if err := router.Handle("/api/v2/saved_views/{id}", handler.New(
provider.authzMiddleware.CheckResources(provider.savedViewHandler.GetV2, authtypes.SigNozAdminRoleName, authtypes.SigNozEditorRoleName, authtypes.SigNozViewerRoleName),
handler.OpenAPIDef{
ID: "GetSavedView",
Tags: []string{"saved_view"},
Summary: "Get saved view",
Description: "Returns a saved view by id.",
Request: nil,
RequestContentType: "",
Response: new(savedviewtypes.SavedView),
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
Deprecated: false,
SecuritySchemes: newScopedSecuritySchemes([]string{coretypes.ResourceMetaResourceSavedView.Scope(coretypes.VerbRead)}),
},
handler.WithResourceDefs(handler.BasicResourceDef{
Resource: coretypes.ResourceMetaResourceSavedView,
Verb: coretypes.VerbRead,
Category: coretypes.ActionCategoryDataAccess,
ID: coretypes.PathParam("id"),
Selector: coretypes.IDSelector,
}),
)).Methods(http.MethodGet).GetError(); err != nil {
return err
}
if err := router.Handle("/api/v2/saved_views/{id}", handler.New(
provider.authzMiddleware.CheckResources(provider.savedViewHandler.UpdateV2, authtypes.SigNozAdminRoleName, authtypes.SigNozEditorRoleName),
handler.OpenAPIDef{
ID: "UpdateSavedView",
Tags: []string{"saved_view"},
Summary: "Update saved view",
Description: "Replaces a saved view's name and query.",
Request: new(savedviewtypes.UpdatableSavedView),
RequestContentType: "application/json",
Response: nil,
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusNoContent,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
Deprecated: false,
SecuritySchemes: newScopedSecuritySchemes([]string{coretypes.ResourceMetaResourceSavedView.Scope(coretypes.VerbUpdate)}),
},
handler.WithResourceDefs(handler.BasicResourceDef{
Resource: coretypes.ResourceMetaResourceSavedView,
Verb: coretypes.VerbUpdate,
Category: coretypes.ActionCategoryDataAccess,
ID: coretypes.PathParam("id"),
Selector: coretypes.IDSelector,
}),
)).Methods(http.MethodPut).GetError(); err != nil {
return err
}
if err := router.Handle("/api/v2/saved_views/{id}", handler.New(
provider.authzMiddleware.CheckResources(provider.savedViewHandler.Delete, authtypes.SigNozAdminRoleName, authtypes.SigNozEditorRoleName),
handler.OpenAPIDef{
ID: "DeleteSavedView",
Tags: []string{"saved_view"},
Summary: "Delete saved view",
Description: "Deletes a saved view by id.",
Request: nil,
RequestContentType: "",
Response: nil,
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusNoContent,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
Deprecated: false,
SecuritySchemes: newScopedSecuritySchemes([]string{coretypes.ResourceMetaResourceSavedView.Scope(coretypes.VerbDelete)}),
},
handler.WithResourceDefs(handler.BasicResourceDef{
Resource: coretypes.ResourceMetaResourceSavedView,
Verb: coretypes.VerbDelete,
Category: coretypes.ActionCategoryDataAccess,
ID: coretypes.PathParam("id"),
Selector: coretypes.IDSelector,
}),
)).Methods(http.MethodDelete).GetError(); err != nil {
return err
}
return nil
}

View File

@@ -11,6 +11,8 @@ import (
"github.com/SigNoz/signoz/pkg/modules/savedview"
v3 "github.com/SigNoz/signoz/pkg/query-service/model/v3"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/types/savedviewtypes"
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
"github.com/SigNoz/signoz/pkg/valuer"
"github.com/gorilla/mux"
)
@@ -23,6 +25,116 @@ func NewHandler(module savedview.Module) savedview.Handler {
return &handler{module: module}
}
// legacyExtraData mirrors the frontend's extraData JSON shape so /api/v1
// responses can synthesize the same shape back for the legacy frontend.
type legacyExtraData struct {
Color string `json:"color,omitempty"`
SelectColumns []telemetrytypes.TelemetryFieldKey `json:"selectColumns,omitempty"`
Format string `json:"format,omitempty"`
MaxLines int `json:"maxLines,omitempty"`
FontSize string `json:"fontSize,omitempty"`
}
// newPostableSavedViewFromLegacyView builds a create payload for a v1 request.
func newPostableSavedViewFromLegacyView(v *v3.SavedView) savedviewtypes.PostableSavedView {
var legacy legacyExtraData
if v.ExtraData != "" {
// Best-effort: malformed/older extraData shapes never fail the request
_ = json.Unmarshal([]byte(v.ExtraData), &legacy)
}
return savedviewtypes.PostableSavedView{
GenerateName: true,
Source: savedviewtypes.Source{String: valuer.NewString(v.SourcePage)},
Data: savedviewtypes.SavedViewData{
SchemaVersion: savedviewtypes.SavedViewSchemaVersion,
Spec: savedviewtypes.SavedViewSpec{
DisplayName: v.Name,
PanelType: savedviewtypes.PanelType{String: valuer.NewString(string(v.CompositeQuery.PanelType))},
Queries: v.CompositeQuery.Queries,
SelectedFields: legacy.SelectColumns,
Display: savedviewtypes.Display{
MaxLines: legacy.MaxLines,
FontSize: legacy.FontSize,
Format: legacy.Format,
Color: legacy.Color,
},
},
},
}
}
// newUpdatableSavedViewFromLegacyView builds an update payload for a v1 request.
func newUpdatableSavedViewFromLegacyView(v *v3.SavedView) savedviewtypes.UpdatableSavedView {
var legacy legacyExtraData
if v.ExtraData != "" {
// Best-effort: malformed/older extraData shapes never fail the request
_ = json.Unmarshal([]byte(v.ExtraData), &legacy)
}
return savedviewtypes.UpdatableSavedView{
Source: savedviewtypes.Source{String: valuer.NewString(v.SourcePage)},
Data: savedviewtypes.SavedViewData{
SchemaVersion: savedviewtypes.SavedViewSchemaVersion,
Spec: savedviewtypes.SavedViewSpec{
DisplayName: v.Name,
PanelType: savedviewtypes.PanelType{String: valuer.NewString(string(v.CompositeQuery.PanelType))},
Queries: v.CompositeQuery.Queries,
SelectedFields: legacy.SelectColumns,
Display: savedviewtypes.Display{
MaxLines: legacy.MaxLines,
FontSize: legacy.FontSize,
Format: legacy.Format,
Color: legacy.Color,
},
},
},
}
}
// newLegacyViewFromSavedView renders a v2 SavedView back into the v1 shape.
func newLegacyViewFromSavedView(v *savedviewtypes.SavedView) (*v3.SavedView, error) {
extraData, err := json.Marshal(legacyExtraData{
Color: v.Data.Spec.Display.Color,
SelectColumns: v.Data.Spec.SelectedFields,
Format: v.Data.Spec.Display.Format,
MaxLines: v.Data.Spec.Display.MaxLines,
FontSize: v.Data.Spec.Display.FontSize,
})
if err != nil {
return nil, errors.WrapInternalf(err, errors.CodeInternal, "error in marshalling extra data")
}
return &v3.SavedView{
ID: v.ID,
Name: v.Data.Spec.DisplayName,
CreatedAt: v.CreatedAt,
CreatedBy: v.CreatedBy,
UpdatedAt: v.UpdatedAt,
UpdatedBy: v.UpdatedBy,
SourcePage: v.Source.StringValue(),
CompositeQuery: &v3.CompositeQuery{
PanelType: v3.PanelType(v.Data.Spec.PanelType.StringValue()),
// Saved views are only ever created from the explorer's builder mode.
QueryType: v3.QueryTypeBuilder,
Queries: v.Data.Spec.Queries,
},
ExtraData: string(extraData),
}, nil
}
func newLegacyViewsFromSavedViews(views []*savedviewtypes.SavedView) ([]*v3.SavedView, error) {
out := make([]*v3.SavedView, 0, len(views))
for _, view := range views {
legacyView, err := newLegacyViewFromSavedView(view)
if err != nil {
return nil, err
}
out = append(out, legacyView)
}
return out, nil
}
func (handler *handler) Create(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel()
@@ -44,7 +156,7 @@ func (handler *handler) Create(w http.ResponseWriter, r *http.Request) {
return
}
uuid, err := handler.module.CreateView(ctx, claims.OrgID, view)
uuid, err := handler.module.CreateView(ctx, claims.OrgID, newPostableSavedViewFromLegacyView(&view))
if err != nil {
render.Error(w, err)
return
@@ -63,7 +175,7 @@ func (handler *handler) Get(w http.ResponseWriter, r *http.Request) {
return
}
viewID := mux.Vars(r)["viewId"]
viewID := mux.Vars(r)["id"]
viewUUID, err := valuer.NewUUID(viewID)
if err != nil {
render.Error(w, errors.Wrapf(err, errors.TypeInvalidInput, errors.CodeInvalidInput, "failed to parse view id"))
@@ -76,7 +188,13 @@ func (handler *handler) Get(w http.ResponseWriter, r *http.Request) {
return
}
render.Success(w, http.StatusOK, view)
legacyView, err := newLegacyViewFromSavedView(view)
if err != nil {
render.Error(w, err)
return
}
render.Success(w, http.StatusOK, legacyView)
}
func (handler *handler) Update(w http.ResponseWriter, r *http.Request) {
@@ -89,7 +207,7 @@ func (handler *handler) Update(w http.ResponseWriter, r *http.Request) {
return
}
viewID := mux.Vars(r)["viewId"]
viewID := mux.Vars(r)["id"]
viewUUID, err := valuer.NewUUID(viewID)
if err != nil {
render.Error(w, errors.Wrapf(err, errors.TypeInvalidInput, errors.CodeInvalidInput, "failed to parse view id"))
@@ -106,7 +224,7 @@ func (handler *handler) Update(w http.ResponseWriter, r *http.Request) {
return
}
err = handler.module.UpdateView(ctx, claims.OrgID, viewUUID, view)
err = handler.module.UpdateView(ctx, claims.OrgID, viewUUID, newUpdatableSavedViewFromLegacyView(&view))
if err != nil {
render.Error(w, err)
return
@@ -125,7 +243,7 @@ func (handler *handler) Delete(w http.ResponseWriter, r *http.Request) {
return
}
viewID := mux.Vars(r)["viewId"]
viewID := mux.Vars(r)["id"]
viewUUID, err := valuer.NewUUID(viewID)
if err != nil {
render.Error(w, errors.Wrapf(err, errors.TypeInvalidInput, errors.CodeInvalidInput, "failed to parse view id"))
@@ -138,7 +256,7 @@ func (handler *handler) Delete(w http.ResponseWriter, r *http.Request) {
return
}
render.Success(w, http.StatusOK, nil)
render.Success(w, http.StatusNoContent, nil)
}
func (handler *handler) List(w http.ResponseWriter, r *http.Request) {
@@ -153,13 +271,18 @@ func (handler *handler) List(w http.ResponseWriter, r *http.Request) {
sourcePage := r.URL.Query().Get("sourcePage")
name := r.URL.Query().Get("name")
category := r.URL.Query().Get("category")
queries, err := handler.module.GetViewsForFilters(r.Context(), claims.OrgID, sourcePage, name, category)
views, err := handler.module.GetViewsForFilters(r.Context(), claims.OrgID, savedviewtypes.Source{String: valuer.NewString(sourcePage)}, name)
if err != nil {
render.Error(w, err)
return
}
render.Success(w, http.StatusOK, queries)
legacyViews, err := newLegacyViewsFromSavedViews(views)
if err != nil {
render.Error(w, err)
return
}
render.Success(w, http.StatusOK, legacyViews)
}

View File

@@ -0,0 +1,197 @@
package implsavedview
import (
"encoding/json"
"testing"
"time"
v3 "github.com/SigNoz/signoz/pkg/query-service/model/v3"
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
"github.com/SigNoz/signoz/pkg/types/savedviewtypes"
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
"github.com/SigNoz/signoz/pkg/valuer"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func testQueries() []qbtypes.QueryEnvelope {
return []qbtypes.QueryEnvelope{
{
Type: qbtypes.QueryTypeBuilder,
Spec: qbtypes.QueryBuilderQuery[qbtypes.LogAggregation]{
Signal: telemetrytypes.SignalLogs,
Aggregations: []qbtypes.LogAggregation{{Expression: "count()"}},
},
},
}
}
func TestNewPostableSavedViewFromLegacyView(t *testing.T) {
t.Run("all fields carried over", func(t *testing.T) {
legacy := &v3.SavedView{
Name: "my view",
SourcePage: "logs",
CompositeQuery: &v3.CompositeQuery{
PanelType: v3.PanelTypeGraph,
Queries: testQueries(),
},
ExtraData: `{"color":"blue","selectColumns":[{"name":"service.name"}],"format":"table","maxLines":10,"fontSize":"large"}`,
}
postable := newPostableSavedViewFromLegacyView(legacy)
assert.Empty(t, postable.Name, "v1 has no slug concept -- name must always be generated")
assert.True(t, postable.GenerateName, "v1 has no slug concept -- name must always be generated")
assert.Equal(t, "my view", postable.Data.Spec.DisplayName)
assert.Equal(t, savedviewtypes.SourceLogs, postable.Source)
assert.Equal(t, savedviewtypes.SavedViewSchemaVersion, postable.Data.SchemaVersion)
assert.Equal(t, savedviewtypes.PanelTypeGraph, postable.Data.Spec.PanelType)
assert.Equal(t, legacy.CompositeQuery.Queries, postable.Data.Spec.Queries)
assert.Equal(t, []telemetrytypes.TelemetryFieldKey{{Name: "service.name"}}, postable.Data.Spec.SelectedFields)
assert.Equal(t, savedviewtypes.Display{MaxLines: 10, FontSize: "large", Format: "table", Color: "blue"}, postable.Data.Spec.Display)
})
t.Run("empty extra data leaves display and selected fields zero-valued", func(t *testing.T) {
legacy := &v3.SavedView{
Name: "no extra data",
SourcePage: "traces",
CompositeQuery: &v3.CompositeQuery{
PanelType: v3.PanelTypeTable,
Queries: testQueries(),
},
ExtraData: "",
}
postable := newPostableSavedViewFromLegacyView(legacy)
assert.Equal(t, savedviewtypes.Display{}, postable.Data.Spec.Display)
assert.Nil(t, postable.Data.Spec.SelectedFields)
})
t.Run("malformed extra data is ignored, not an error", func(t *testing.T) {
legacy := &v3.SavedView{
Name: "malformed extra data",
SourcePage: "metrics",
CompositeQuery: &v3.CompositeQuery{
PanelType: v3.PanelTypeList,
Queries: testQueries(),
},
ExtraData: `{not valid json`,
}
postable := newPostableSavedViewFromLegacyView(legacy)
assert.Equal(t, "malformed extra data", postable.Data.Spec.DisplayName)
assert.Equal(t, savedviewtypes.Display{}, postable.Data.Spec.Display)
})
}
func TestNewUpdatableSavedViewFromLegacyView(t *testing.T) {
legacy := &v3.SavedView{
Name: "renamed view",
SourcePage: "traces",
CompositeQuery: &v3.CompositeQuery{
PanelType: v3.PanelTypeTable,
Queries: testQueries(),
},
ExtraData: `{"color":"red"}`,
}
updatable := newUpdatableSavedViewFromLegacyView(legacy)
assert.Equal(t, "renamed view", updatable.Data.Spec.DisplayName)
assert.Equal(t, savedviewtypes.SourceTraces, updatable.Source)
}
func TestNewLegacyViewFromSavedView(t *testing.T) {
now := time.Now()
savedView := &savedviewtypes.SavedView{
Name: "my-view-abc123ef",
Source: savedviewtypes.SourceLogs,
Data: savedviewtypes.SavedViewData{
SchemaVersion: savedviewtypes.SavedViewSchemaVersion,
Spec: savedviewtypes.SavedViewSpec{
DisplayName: "my view",
PanelType: savedviewtypes.PanelTypeGraph,
Queries: testQueries(),
SelectedFields: []telemetrytypes.TelemetryFieldKey{{Name: "service.name"}},
Display: savedviewtypes.Display{MaxLines: 10, FontSize: "large", Format: "table", Color: "blue"},
},
},
}
savedView.ID = valuer.GenerateUUID()
savedView.CreatedAt = now
savedView.UpdatedAt = now
savedView.CreatedBy = "creator@signoz.io"
savedView.UpdatedBy = "updater@signoz.io"
legacy, err := newLegacyViewFromSavedView(savedView)
require.NoError(t, err)
assert.Equal(t, savedView.ID, legacy.ID)
assert.Equal(t, savedView.Data.Spec.DisplayName, legacy.Name)
assert.Equal(t, savedView.CreatedAt, legacy.CreatedAt)
assert.Equal(t, savedView.CreatedBy, legacy.CreatedBy)
assert.Equal(t, savedView.UpdatedAt, legacy.UpdatedAt)
assert.Equal(t, savedView.UpdatedBy, legacy.UpdatedBy)
assert.Equal(t, "logs", legacy.SourcePage)
assert.Equal(t, v3.PanelTypeGraph, legacy.CompositeQuery.PanelType)
assert.Equal(t, v3.QueryTypeBuilder, legacy.CompositeQuery.QueryType)
assert.Equal(t, savedView.Data.Spec.Queries, legacy.CompositeQuery.Queries)
var extra legacyExtraData
require.NoError(t, json.Unmarshal([]byte(legacy.ExtraData), &extra))
assert.Equal(t, "blue", extra.Color)
assert.Equal(t, savedView.Data.Spec.SelectedFields, extra.SelectColumns)
assert.Equal(t, "table", extra.Format)
assert.Equal(t, 10, extra.MaxLines)
assert.Equal(t, "large", extra.FontSize)
}
func TestNewLegacyViewsFromSavedViews(t *testing.T) {
a := &savedviewtypes.SavedView{Name: "a-slug", Source: savedviewtypes.SourceLogs, Data: savedviewtypes.SavedViewData{Spec: savedviewtypes.SavedViewSpec{DisplayName: "a", PanelType: savedviewtypes.PanelTypeGraph, Queries: testQueries()}}}
b := &savedviewtypes.SavedView{Name: "b-slug", Source: savedviewtypes.SourceTraces, Data: savedviewtypes.SavedViewData{Spec: savedviewtypes.SavedViewSpec{DisplayName: "b", PanelType: savedviewtypes.PanelTypeTable, Queries: testQueries()}}}
legacyViews, err := newLegacyViewsFromSavedViews([]*savedviewtypes.SavedView{a, b})
require.NoError(t, err)
require.Len(t, legacyViews, 2)
assert.Equal(t, "a", legacyViews[0].Name)
assert.Equal(t, "b", legacyViews[1].Name)
}
// TestLegacyViewRoundTrip guards the whole v1<->v2 bridge: converting a
// SavedView to its legacy shape and back must recover the fields the legacy
// frontend round-trips through (displayName, source, panelType, queries,
// selectedFields, display) -- these two functions are each other's inverse
// on the API surface, so a regression in either should fail this. The internal
// slug (Name) is deliberately NOT part of this contract -- v1 never sees it.
func TestLegacyViewRoundTrip(t *testing.T) {
original := &savedviewtypes.SavedView{
Name: "round-trip-abc123ef",
Source: savedviewtypes.SourceMetrics,
Data: savedviewtypes.SavedViewData{
SchemaVersion: savedviewtypes.SavedViewSchemaVersion,
Spec: savedviewtypes.SavedViewSpec{
DisplayName: "round trip",
PanelType: savedviewtypes.PanelTypeTable,
Queries: testQueries(),
SelectedFields: []telemetrytypes.TelemetryFieldKey{{Name: "service.name"}},
Display: savedviewtypes.Display{MaxLines: 5, FontSize: "small", Format: "list", Color: "red"},
},
},
}
legacy, err := newLegacyViewFromSavedView(original)
require.NoError(t, err)
roundTripped := newPostableSavedViewFromLegacyView(legacy)
assert.Empty(t, roundTripped.Name)
assert.True(t, roundTripped.GenerateName)
assert.Equal(t, original.Data.Spec.DisplayName, roundTripped.Data.Spec.DisplayName)
assert.Equal(t, original.Source, roundTripped.Source)
assert.Equal(t, original.Data.Spec.PanelType, roundTripped.Data.Spec.PanelType)
assert.Equal(t, original.Data.Spec.Queries, roundTripped.Data.Spec.Queries)
assert.Equal(t, original.Data.Spec.SelectedFields, roundTripped.Data.Spec.SelectedFields)
assert.Equal(t, original.Data.Spec.Display, roundTripped.Data.Spec.Display)
}

View File

@@ -0,0 +1,135 @@
package implsavedview
import (
"context"
"net/http"
"time"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/http/binding"
"github.com/SigNoz/signoz/pkg/http/render"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/types/savedviewtypes"
"github.com/SigNoz/signoz/pkg/valuer"
"github.com/gorilla/mux"
)
func (handler *handler) CreateV2(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel()
claims, err := authtypes.ClaimsFromContext(ctx)
if err != nil {
render.Error(w, err)
return
}
var view savedviewtypes.PostableSavedView
if err := binding.JSON.BindBody(r.Body, &view, binding.WithDisallowUnknownFields(true)); err != nil {
render.Error(w, err)
return
}
if err := view.Validate(); err != nil {
render.Error(w, err)
return
}
uuid, err := handler.module.CreateView(ctx, claims.OrgID, view)
if err != nil {
render.Error(w, err)
return
}
render.Success(w, http.StatusCreated, types.Identifiable{ID: uuid})
}
func (handler *handler) GetV2(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel()
claims, err := authtypes.ClaimsFromContext(ctx)
if err != nil {
render.Error(w, err)
return
}
viewID := mux.Vars(r)["id"]
viewUUID, err := valuer.NewUUID(viewID)
if err != nil {
render.Error(w, errors.Wrapf(err, errors.TypeInvalidInput, errors.CodeInvalidInput, "failed to parse view id"))
return
}
view, err := handler.module.GetView(ctx, claims.OrgID, viewUUID)
if err != nil {
render.Error(w, err)
return
}
render.Success(w, http.StatusOK, view)
}
func (handler *handler) UpdateV2(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel()
claims, err := authtypes.ClaimsFromContext(ctx)
if err != nil {
render.Error(w, err)
return
}
viewID := mux.Vars(r)["id"]
viewUUID, err := valuer.NewUUID(viewID)
if err != nil {
render.Error(w, errors.Wrapf(err, errors.TypeInvalidInput, errors.CodeInvalidInput, "failed to parse view id"))
return
}
var view savedviewtypes.UpdatableSavedView
if err := binding.JSON.BindBody(r.Body, &view, binding.WithDisallowUnknownFields(true)); err != nil {
render.Error(w, err)
return
}
if err := view.Validate(); err != nil {
render.Error(w, err)
return
}
err = handler.module.UpdateView(ctx, claims.OrgID, viewUUID, view)
if err != nil {
render.Error(w, err)
return
}
render.Success(w, http.StatusNoContent, nil)
}
func (handler *handler) ListV2(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel()
claims, err := authtypes.ClaimsFromContext(ctx)
if err != nil {
render.Error(w, err)
return
}
params := new(savedviewtypes.ListSavedViewsParams)
if err := binding.Query.BindQuery(r.URL.Query(), params); err != nil {
render.Error(w, err)
return
}
if err := params.Validate(); err != nil {
render.Error(w, err)
return
}
queries, err := handler.module.GetViewsForFilters(r.Context(), claims.OrgID, params.Source, params.Name)
if err != nil {
render.Error(w, err)
return
}
render.Success(w, http.StatusOK, queries)
}

View File

@@ -2,185 +2,59 @@ package implsavedview
import (
"context"
"encoding/json"
"strings"
"time"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/modules/savedview"
v3 "github.com/SigNoz/signoz/pkg/query-service/model/v3"
"github.com/SigNoz/signoz/pkg/sqlstore"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/types/savedviewtypes"
"github.com/SigNoz/signoz/pkg/valuer"
)
type module struct {
sqlstore sqlstore.SQLStore
store savedviewtypes.Store
}
func NewModule(sqlstore sqlstore.SQLStore) savedview.Module {
return &module{sqlstore: sqlstore}
func NewModule(store savedviewtypes.Store) savedview.Module {
return &module{store: store}
}
func (module *module) GetViewsForFilters(ctx context.Context, orgID string, sourcePage string, name string, category string) ([]*v3.SavedView, error) {
var views []savedviewtypes.SavedView
var err error
if len(category) == 0 {
err = module.sqlstore.BunDB().NewSelect().Model(&views).Where("org_id = ? AND source_page = ? AND name LIKE ?", orgID, sourcePage, "%"+name+"%").Scan(ctx)
} else {
err = module.sqlstore.BunDB().NewSelect().Model(&views).Where("org_id = ? AND source_page = ? AND category LIKE ? AND name LIKE ?", orgID, sourcePage, "%"+category+"%", "%"+name+"%").Scan(ctx)
}
func (module *module) GetViewsForFilters(ctx context.Context, orgID string, source savedviewtypes.Source, name string) ([]*savedviewtypes.SavedView, error) {
return module.store.List(ctx, orgID, source, name)
}
func (module *module) CreateView(ctx context.Context, orgID string, view savedviewtypes.PostableSavedView) (valuer.UUID, error) {
claims, err := authtypes.ClaimsFromContext(ctx)
if err != nil {
return nil, errors.WrapInternalf(err, errors.CodeInternal, "error in getting saved views")
}
var savedViews []*v3.SavedView
for _, view := range views {
var compositeQuery v3.CompositeQuery
err = json.Unmarshal([]byte(view.Data), &compositeQuery)
if err != nil {
return nil, errors.WrapInternalf(err, errors.CodeInternal, "error in unmarshalling explorer query data: %s", err.Error())
}
savedViews = append(savedViews, &v3.SavedView{
ID: view.ID,
Name: view.Name,
CreatedAt: view.CreatedAt,
CreatedBy: view.CreatedBy,
UpdatedAt: view.UpdatedAt,
UpdatedBy: view.UpdatedBy,
Tags: strings.Split(view.Tags, ","),
SourcePage: view.SourcePage,
CompositeQuery: &compositeQuery,
ExtraData: view.ExtraData,
})
}
return savedViews, nil
}
func (module *module) CreateView(ctx context.Context, orgID string, view v3.SavedView) (valuer.UUID, error) {
data, err := json.Marshal(view.CompositeQuery)
if err != nil {
return valuer.UUID{}, errors.WrapInternalf(err, errors.CodeInternal, "error in marshalling explorer query data")
}
uuid := valuer.GenerateUUID()
createdAt := time.Now()
updatedAt := time.Now()
claims, errv2 := authtypes.ClaimsFromContext(ctx)
if errv2 != nil {
return valuer.UUID{}, errors.NewInternalf(errors.CodeInternal, "error in getting email from context")
}
createBy := claims.Email
updatedBy := claims.Email
dbView := view.ToSavedView(orgID, claims.Email)
dbView := savedviewtypes.SavedView{
TimeAuditable: types.TimeAuditable{
CreatedAt: createdAt,
UpdatedAt: updatedAt,
},
UserAuditable: types.UserAuditable{
CreatedBy: createBy,
UpdatedBy: updatedBy,
},
OrgID: orgID,
Identifiable: types.Identifiable{
ID: uuid,
},
Name: view.Name,
Category: view.Category,
SourcePage: view.SourcePage,
Tags: strings.Join(view.Tags, ","),
Data: string(data),
ExtraData: view.ExtraData,
if err := module.store.Create(ctx, dbView); err != nil {
return valuer.UUID{}, err
}
_, err = module.sqlstore.BunDB().NewInsert().Model(&dbView).Exec(ctx)
if err != nil {
return valuer.UUID{}, errors.WrapInternalf(err, errors.CodeInternal, "error in creating saved view")
}
return uuid, nil
return dbView.ID, nil
}
func (module *module) GetView(ctx context.Context, orgID string, uuid valuer.UUID) (*v3.SavedView, error) {
var view savedviewtypes.SavedView
err := module.sqlstore.BunDB().NewSelect().Model(&view).Where("org_id = ? AND id = ?", orgID, uuid.StringValue()).Scan(ctx)
if err != nil {
return nil, errors.WrapInternalf(err, errors.CodeInternal, "error in getting saved view")
}
var compositeQuery v3.CompositeQuery
err = json.Unmarshal([]byte(view.Data), &compositeQuery)
if err != nil {
return nil, errors.WrapInternalf(err, errors.CodeInternal, "error in unmarshalling explorer query data")
}
return &v3.SavedView{
ID: view.ID,
Name: view.Name,
Category: view.Category,
CreatedAt: view.CreatedAt,
CreatedBy: view.CreatedBy,
UpdatedAt: view.UpdatedAt,
UpdatedBy: view.UpdatedBy,
SourcePage: view.SourcePage,
Tags: strings.Split(view.Tags, ","),
CompositeQuery: &compositeQuery,
ExtraData: view.ExtraData,
}, nil
func (module *module) GetView(ctx context.Context, orgID string, uuid valuer.UUID) (*savedviewtypes.SavedView, error) {
return module.store.Get(ctx, orgID, uuid)
}
func (module *module) UpdateView(ctx context.Context, orgID string, uuid valuer.UUID, view v3.SavedView) error {
data, err := json.Marshal(view.CompositeQuery)
func (module *module) UpdateView(ctx context.Context, orgID string, uuid valuer.UUID, view savedviewtypes.UpdatableSavedView) error {
claims, err := authtypes.ClaimsFromContext(ctx)
if err != nil {
return errors.WrapInternalf(err, errors.CodeInternal, "error in marshalling explorer query data")
}
claims, errv2 := authtypes.ClaimsFromContext(ctx)
if errv2 != nil {
return errors.NewInternalf(errors.CodeInternal, "error in getting email from context")
}
updatedAt := time.Now()
updatedBy := claims.Email
_, err = module.sqlstore.BunDB().NewUpdate().
Model(&savedviewtypes.SavedView{}).
Set("updated_at = ?, updated_by = ?, name = ?, category = ?, source_page = ?, tags = ?, data = ?, extra_data = ?",
updatedAt, updatedBy, view.Name, view.Category, view.SourcePage, strings.Join(view.Tags, ","), data, view.ExtraData).
Where("id = ?", uuid.StringValue()).
Where("org_id = ?", orgID).
Exec(ctx)
if err != nil {
return errors.WrapInternalf(err, errors.CodeInternal, "error in updating saved view")
}
return nil
return module.store.Update(ctx, view.ToSavedView(uuid, orgID, claims.Email))
}
func (module *module) DeleteView(ctx context.Context, orgID string, uuid valuer.UUID) error {
_, err := module.sqlstore.BunDB().NewDelete().
Model(&savedviewtypes.SavedView{}).
Where("id = ?", uuid.StringValue()).
Where("org_id = ?", orgID).
Exec(ctx)
if err != nil {
return errors.WrapInternalf(err, errors.CodeInternal, "error in deleting explorer query")
}
return nil
return module.store.Delete(ctx, orgID, uuid)
}
func (module *module) Collect(ctx context.Context, orgID valuer.UUID) (map[string]any, error) {
savedViews := []*savedviewtypes.SavedView{}
err := module.
sqlstore.
BunDB().
NewSelect().
Model(&savedViews).
Where("org_id = ?", orgID).
Scan(ctx)
savedViews, err := module.store.List(ctx, orgID.StringValue(), savedviewtypes.Source{}, "")
if err != nil {
return nil, err
}

View File

@@ -0,0 +1,299 @@
package implsavedview_test
import (
"context"
"testing"
"github.com/DATA-DOG/go-sqlmock"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/modules/savedview"
"github.com/SigNoz/signoz/pkg/modules/savedview/implsavedview"
"github.com/SigNoz/signoz/pkg/sqlstore"
"github.com/SigNoz/signoz/pkg/sqlstore/sqlstoretest"
"github.com/SigNoz/signoz/pkg/types/authtypes"
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
"github.com/SigNoz/signoz/pkg/types/savedviewtypes"
"github.com/SigNoz/signoz/pkg/types/savedviewtypes/savedviewtypestest"
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
"github.com/SigNoz/signoz/pkg/valuer"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func newTestStore() (savedview.Module, *savedviewtypestest.StoreTest) {
sqlStore := sqlstoretest.New(sqlstore.Config{Provider: "sqlite"}, sqlmock.QueryMatcherRegexp)
store := implsavedview.NewStore(sqlStore)
return implsavedview.NewModule(store), savedviewtypestest.New(store, sqlStore.Mock())
}
func testPostableSavedView(name string, source savedviewtypes.Source) savedviewtypes.PostableSavedView {
return savedviewtypes.PostableSavedView{
Name: name,
Source: source,
Data: savedviewtypes.SavedViewData{
SchemaVersion: savedviewtypes.SavedViewSchemaVersion,
Spec: savedviewtypes.SavedViewSpec{
DisplayName: name,
PanelType: savedviewtypes.PanelTypeGraph,
Queries: []qbtypes.QueryEnvelope{
{
Type: qbtypes.QueryTypeBuilder,
Spec: qbtypes.QueryBuilderQuery[qbtypes.LogAggregation]{
Signal: telemetrytypes.SignalLogs,
Aggregations: []qbtypes.LogAggregation{{Expression: "count()"}},
},
},
},
SelectedFields: []telemetrytypes.TelemetryFieldKey{},
},
},
}
}
func testUpdatableSavedView(displayName string, source savedviewtypes.Source) savedviewtypes.UpdatableSavedView {
postable := testPostableSavedView(displayName, source)
return savedviewtypes.UpdatableSavedView{
Source: postable.Source,
Data: postable.Data,
}
}
func testSavedView(orgID string, id valuer.UUID, updatedBy string, view savedviewtypes.PostableSavedView) *savedviewtypes.SavedView {
savedView := view.ToSavedView(orgID, "creator@signoz.io")
savedView.ID = id
savedView.UpdatedBy = updatedBy
return savedView
}
func contextWithClaims(orgID, email string) context.Context {
return authtypes.NewContextWithClaims(context.Background(), authtypes.Claims{
OrgID: orgID,
Email: email,
})
}
func TestModule_CreateAndGetView(t *testing.T) {
m, st := newTestStore()
orgID := valuer.GenerateUUID().StringValue()
ctx := contextWithClaims(orgID, "creator@signoz.io")
view := testPostableSavedView("my view", savedviewtypes.SourceLogs)
st.ExpectCreate()
id, err := m.CreateView(ctx, orgID, view)
require.NoError(t, err)
require.False(t, id.IsZero())
stored := testSavedView(orgID, id, "creator@signoz.io", view)
st.ExpectGet(orgID, id, stored)
got, err := m.GetView(ctx, orgID, id)
require.NoError(t, err)
assert.Equal(t, id, got.ID)
assert.Equal(t, "my view", got.Name)
assert.Equal(t, savedviewtypes.SourceLogs, got.Source)
assert.Equal(t, "creator@signoz.io", got.CreatedBy)
assert.Equal(t, "creator@signoz.io", got.UpdatedBy)
assert.Equal(t, savedviewtypes.PanelTypeGraph, got.Data.Spec.PanelType)
require.NoError(t, st.AssertExpectations())
}
func TestModule_GetView_NotFound(t *testing.T) {
m, st := newTestStore()
orgID := valuer.GenerateUUID().StringValue()
id := valuer.GenerateUUID()
st.ExpectGet(orgID, id, nil)
_, err := m.GetView(contextWithClaims(orgID, "someone@signoz.io"), orgID, id)
require.Error(t, err)
assert.True(t, errors.Ast(err, errors.TypeNotFound), "expected a not-found error, got %v", err)
require.NoError(t, st.AssertExpectations())
}
func TestModule_GetView_ScopedToOrg(t *testing.T) {
m, st := newTestStore()
orgB := valuer.GenerateUUID().StringValue()
id := valuer.GenerateUUID()
// The mock only has an expectation for orgB's WHERE clause; a lookup
// scoped to org A's real id must not accidentally match it.
st.ExpectGet(orgB, id, nil)
_, err := m.GetView(contextWithClaims(orgB, "b@signoz.io"), orgB, id)
require.Error(t, err, "a view created under org A must not be visible to org B")
assert.True(t, errors.Ast(err, errors.TypeNotFound), "expected a not-found error, got %v", err)
require.NoError(t, st.AssertExpectations())
}
func TestModule_UpdateView(t *testing.T) {
m, st := newTestStore()
orgID := valuer.GenerateUUID().StringValue()
id := valuer.GenerateUUID()
existing := testSavedView(orgID, id, "creator@signoz.io", testPostableSavedView("my-view", savedviewtypes.SourceLogs))
existingName := existing.Name
updated := testUpdatableSavedView("renamed", savedviewtypes.SourceTraces)
updated.Data.Spec.PanelType = savedviewtypes.PanelTypeTable
st.ExpectUpdate(orgID, id, 1)
require.NoError(t, m.UpdateView(contextWithClaims(orgID, "updater@signoz.io"), orgID, id, updated))
stored := testSavedView(orgID, id, "updater@signoz.io", testPostableSavedView("renamed", savedviewtypes.SourceTraces))
stored.Name = existingName
stored.Data.Spec.PanelType = savedviewtypes.PanelTypeTable
st.ExpectGet(orgID, id, stored)
got, err := m.GetView(contextWithClaims(orgID, "creator@signoz.io"), orgID, id)
require.NoError(t, err)
assert.Equal(t, existingName, got.Name, "name must not change on update")
assert.Equal(t, "renamed", got.Data.Spec.DisplayName)
assert.Equal(t, savedviewtypes.SourceTraces, got.Source)
assert.Equal(t, savedviewtypes.PanelTypeTable, got.Data.Spec.PanelType)
assert.Equal(t, "updater@signoz.io", got.UpdatedBy)
require.NoError(t, st.AssertExpectations())
}
func TestModule_UpdateView_NotFound(t *testing.T) {
m, st := newTestStore()
orgID := valuer.GenerateUUID().StringValue()
ctx := contextWithClaims(orgID, "someone@signoz.io")
id := valuer.GenerateUUID()
st.ExpectUpdate(orgID, id, 0)
err := m.UpdateView(ctx, orgID, id, testUpdatableSavedView("does-not-exist", savedviewtypes.SourceLogs))
require.Error(t, err)
assert.True(t, errors.Ast(err, errors.TypeNotFound), "expected a not-found error, got %v", err)
require.NoError(t, st.AssertExpectations())
}
func TestModule_UpdateView_ScopedToOrg(t *testing.T) {
m, st := newTestStore()
orgB := valuer.GenerateUUID().StringValue()
id := valuer.GenerateUUID()
// Only an Update scoped to orgB's WHERE clause is registered; updating org
// A's view while authenticated as org B must not match it.
st.ExpectUpdate(orgB, id, 0)
err := m.UpdateView(contextWithClaims(orgB, "b@signoz.io"), orgB, id, testUpdatableSavedView("hijacked", savedviewtypes.SourceLogs))
require.Error(t, err, "org B must not be able to update org A's view")
assert.True(t, errors.Ast(err, errors.TypeNotFound))
require.NoError(t, st.AssertExpectations())
}
func TestModule_DeleteView(t *testing.T) {
m, st := newTestStore()
orgID := valuer.GenerateUUID().StringValue()
ctx := contextWithClaims(orgID, "creator@signoz.io")
id := valuer.GenerateUUID()
st.ExpectDelete(orgID, id, 1)
require.NoError(t, m.DeleteView(ctx, orgID, id))
require.NoError(t, st.AssertExpectations())
}
func TestModule_DeleteView_NotFound(t *testing.T) {
m, st := newTestStore()
orgID := valuer.GenerateUUID().StringValue()
ctx := contextWithClaims(orgID, "someone@signoz.io")
id := valuer.GenerateUUID()
st.ExpectDelete(orgID, id, 0)
err := m.DeleteView(ctx, orgID, id)
require.Error(t, err)
assert.True(t, errors.Ast(err, errors.TypeNotFound), "expected a not-found error, got %v", err)
require.NoError(t, st.AssertExpectations())
}
func TestModule_DeleteView_ScopedToOrg(t *testing.T) {
m, st := newTestStore()
orgB := valuer.GenerateUUID().StringValue()
id := valuer.GenerateUUID()
st.ExpectDelete(orgB, id, 0)
err := m.DeleteView(contextWithClaims(orgB, "b@signoz.io"), orgB, id)
require.Error(t, err, "org B must not be able to delete org A's view")
assert.True(t, errors.Ast(err, errors.TypeNotFound))
require.NoError(t, st.AssertExpectations())
}
func TestModule_GetViewsForFilters(t *testing.T) {
m, st := newTestStore()
orgID := valuer.GenerateUUID().StringValue()
ctx := contextWithClaims(orgID, "creator@signoz.io")
logsOverview := testSavedView(orgID, valuer.GenerateUUID(), "creator@signoz.io", testPostableSavedView("logs overview", savedviewtypes.SourceLogs))
logsErrors := testSavedView(orgID, valuer.GenerateUUID(), "creator@signoz.io", testPostableSavedView("logs errors", savedviewtypes.SourceLogs))
tracesOverview := testSavedView(orgID, valuer.GenerateUUID(), "creator@signoz.io", testPostableSavedView("traces overview", savedviewtypes.SourceTraces))
t.Run("filters by source page", func(t *testing.T) {
st.ExpectList(orgID, []*savedviewtypes.SavedView{logsOverview, logsErrors})
views, err := m.GetViewsForFilters(ctx, orgID, savedviewtypes.SourceLogs, "")
require.NoError(t, err)
assert.Len(t, views, 2)
})
t.Run("filters by name substring", func(t *testing.T) {
st.ExpectList(orgID, []*savedviewtypes.SavedView{logsErrors})
views, err := m.GetViewsForFilters(ctx, orgID, savedviewtypes.SourceLogs, "errors")
require.NoError(t, err)
require.Len(t, views, 1)
assert.Equal(t, "logs errors", views[0].Name)
})
t.Run("omitted source page returns everything, not nothing", func(t *testing.T) {
// Fixes a bug: source used to be an unconditional exact-match
// clause, so a zero-value source matched zero rows -- even though
// ListSavedViewsParams.Validate() treats a zero Source as valid
// ("no filter"). Store.List now only applies the source clause
// when it's non-zero.
st.ExpectList(orgID, []*savedviewtypes.SavedView{logsOverview, logsErrors, tracesOverview})
views, err := m.GetViewsForFilters(ctx, orgID, savedviewtypes.Source{}, "")
require.NoError(t, err)
assert.Len(t, views, 3)
})
t.Run("scoped to org", func(t *testing.T) {
otherOrgID := valuer.GenerateUUID().StringValue()
st.ExpectList(otherOrgID, nil)
views, err := m.GetViewsForFilters(ctx, otherOrgID, savedviewtypes.SourceLogs, "")
require.NoError(t, err)
assert.Empty(t, views)
})
require.NoError(t, st.AssertExpectations())
}
func TestModule_Collect(t *testing.T) {
m, st := newTestStore()
orgID := valuer.GenerateUUID()
logsA := testSavedView(orgID.StringValue(), valuer.GenerateUUID(), "creator@signoz.io", testPostableSavedView("logs a", savedviewtypes.SourceLogs))
logsB := testSavedView(orgID.StringValue(), valuer.GenerateUUID(), "creator@signoz.io", testPostableSavedView("logs b", savedviewtypes.SourceLogs))
tracesA := testSavedView(orgID.StringValue(), valuer.GenerateUUID(), "creator@signoz.io", testPostableSavedView("traces a", savedviewtypes.SourceTraces))
st.ExpectList(orgID.StringValue(), []*savedviewtypes.SavedView{logsA, logsB, tracesA})
stats, err := m.Collect(context.Background(), orgID)
require.NoError(t, err)
assert.Equal(t, int64(3), stats["savedview.count"])
assert.Equal(t, int64(2), stats["savedview.source.logs.count"])
assert.Equal(t, int64(1), stats["savedview.source.traces.count"])
require.NoError(t, st.AssertExpectations())
}

View File

@@ -0,0 +1,109 @@
package implsavedview
import (
"context"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/sqlstore"
"github.com/SigNoz/signoz/pkg/types/savedviewtypes"
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
"github.com/SigNoz/signoz/pkg/valuer"
)
type store struct {
sqlstore sqlstore.SQLStore
}
func NewStore(sqlstore sqlstore.SQLStore) savedviewtypes.Store {
return &store{sqlstore: sqlstore}
}
func (store *store) Create(ctx context.Context, view *savedviewtypes.SavedView) error {
_, err := store.sqlstore.BunDB().NewInsert().Model(view).Exec(ctx)
if err != nil {
return store.sqlstore.WrapAlreadyExistsErrf(err, errors.CodeAlreadyExists, "saved view with name %s already exists", view.Name)
}
return nil
}
func (store *store) Get(ctx context.Context, orgID string, id valuer.UUID) (*savedviewtypes.SavedView, error) {
var view savedviewtypes.SavedView
err := store.sqlstore.BunDB().NewSelect().Model(&view).Where("org_id = ? AND id = ?", orgID, id.StringValue()).Scan(ctx)
if err != nil {
return nil, store.sqlstore.WrapNotFoundErrf(err, savedviewtypes.ErrCodeSavedViewNotFound, "saved view %s not found", id.StringValue())
}
normalizeSelectedFields(&view)
return &view, nil
}
func (store *store) Update(ctx context.Context, view *savedviewtypes.SavedView) error {
res, err := store.sqlstore.BunDB().NewUpdate().
Model(&savedviewtypes.SavedView{}).
Set("updated_at = ?, updated_by = ?, source = ?, data = ?",
view.UpdatedAt, view.UpdatedBy, view.Source, view.Data).
Where("id = ?", view.ID.StringValue()).
Where("org_id = ?", view.OrgID).
Exec(ctx)
if err != nil {
return errors.WrapInternalf(err, errors.CodeInternal, "error in updating saved view")
}
rowsAffected, err := res.RowsAffected()
if err != nil {
return errors.WrapInternalf(err, errors.CodeInternal, "error in verifying the updated saved view")
}
if rowsAffected == 0 {
return errors.NewNotFoundf(savedviewtypes.ErrCodeSavedViewNotFound, "saved view %s not found", view.ID.StringValue())
}
return nil
}
func (store *store) Delete(ctx context.Context, orgID string, id valuer.UUID) error {
res, err := store.sqlstore.BunDB().NewDelete().
Model(&savedviewtypes.SavedView{}).
Where("id = ?", id.StringValue()).
Where("org_id = ?", orgID).
Exec(ctx)
if err != nil {
return errors.WrapInternalf(err, errors.CodeInternal, "error in deleting saved view")
}
rowsAffected, err := res.RowsAffected()
if err != nil {
return errors.WrapInternalf(err, errors.CodeInternal, "error in verifying the deleted saved view")
}
if rowsAffected == 0 {
return errors.NewNotFoundf(savedviewtypes.ErrCodeSavedViewNotFound, "saved view %s not found", id.StringValue())
}
return nil
}
func (store *store) List(ctx context.Context, orgID string, source savedviewtypes.Source, name string) ([]*savedviewtypes.SavedView, error) {
var views []*savedviewtypes.SavedView
q := store.sqlstore.BunDB().NewSelect().Model(&views).
Where("org_id = ?", orgID).
Where("name LIKE ?", "%"+name+"%")
if !source.IsZero() {
q = q.Where("source = ?", source)
}
if err := q.Scan(ctx); err != nil {
return nil, errors.WrapInternalf(err, errors.CodeInternal, "error in getting saved views")
}
for _, view := range views {
normalizeSelectedFields(view)
}
return views, nil
}
// normalizeSelectedFields fixes up a scanned row's nil SelectedFields.
func normalizeSelectedFields(view *savedviewtypes.SavedView) {
if view.Data.Spec.SelectedFields == nil {
view.Data.Spec.SelectedFields = []telemetrytypes.TelemetryFieldKey{}
}
}

View File

@@ -4,19 +4,19 @@ import (
"context"
"net/http"
v3 "github.com/SigNoz/signoz/pkg/query-service/model/v3"
"github.com/SigNoz/signoz/pkg/statsreporter"
"github.com/SigNoz/signoz/pkg/types/savedviewtypes"
"github.com/SigNoz/signoz/pkg/valuer"
)
type Module interface {
GetViewsForFilters(ctx context.Context, orgID string, sourcePage string, name string, category string) ([]*v3.SavedView, error)
GetViewsForFilters(ctx context.Context, orgID string, source savedviewtypes.Source, name string) ([]*savedviewtypes.SavedView, error)
CreateView(ctx context.Context, orgID string, view v3.SavedView) (valuer.UUID, error)
CreateView(ctx context.Context, orgID string, view savedviewtypes.PostableSavedView) (valuer.UUID, error)
GetView(ctx context.Context, orgID string, uuid valuer.UUID) (*v3.SavedView, error)
GetView(ctx context.Context, orgID string, uuid valuer.UUID) (*savedviewtypes.SavedView, error)
UpdateView(ctx context.Context, orgID string, uuid valuer.UUID, view v3.SavedView) error
UpdateView(ctx context.Context, orgID string, uuid valuer.UUID, view savedviewtypes.UpdatableSavedView) error
DeleteView(ctx context.Context, orgID string, uuid valuer.UUID) error
@@ -33,9 +33,22 @@ type Handler interface {
// Updates the saved view
Update(http.ResponseWriter, *http.Request)
// Deletes the saved view
// Deletes the saved view. Shared by both API generations -- delete has no
// request/response body to reshape.
Delete(http.ResponseWriter, *http.Request)
// Lists the saved views
List(http.ResponseWriter, *http.Request)
// CreateV2 is the /api/v2/saved_views typed-spec variant of Create.
CreateV2(http.ResponseWriter, *http.Request)
// GetV2 is the /api/v2/saved_views typed-spec variant of Get.
GetV2(http.ResponseWriter, *http.Request)
// UpdateV2 is the /api/v2/saved_views typed-spec variant of Update.
UpdateV2(http.ResponseWriter, *http.Request)
// ListV2 is the /api/v2/saved_views typed-spec variant of List.
ListV2(http.ResponseWriter, *http.Request)
}

View File

@@ -3190,11 +3190,6 @@ func (r *ClickHouseReader) GetMetricAttributeValues(ctx context.Context, orgID v
var rows driver.Rows
var attributeValues v3.FilterAttributeValueResponse
normalized := true
if constants.IsDotMetricsEnabled {
normalized = false
}
reductionEnabled := r.fl.BooleanOrEmpty(ctx, flagger.FeatureEnableMetricsReduction, featuretypes.NewFlaggerEvaluationContext(orgID))
if reductionEnabled {
@@ -3206,7 +3201,7 @@ func (r *ClickHouseReader) GetMetricAttributeValues(ctx context.Context, orgID v
query = query + fmt.Sprintf(" LIMIT %d;", req.Limit)
}
names := []string{req.AggregateAttribute}
names = append(names, metrics.GetTransitionedMetric(req.AggregateAttribute, normalized))
names = append(names, metrics.GetTransitionedMetric(req.AggregateAttribute))
rows, err = r.db.Query(ctx, query, req.FilterAttributeKey, names, req.FilterAttributeKey, fmt.Sprintf("%%%s%%", req.SearchText), common.PastDayRoundOff())
@@ -5448,112 +5443,3 @@ func (r *ClickHouseReader) SearchTraces(ctx context.Context, params *model.Searc
return &searchSpansResult, nil
}
func (r *ClickHouseReader) GetNormalizedStatus(
ctx context.Context,
orgID valuer.UUID,
metricNames []string,
) (map[string]bool, error) {
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
instrumentationtypes.TelemetrySignal: telemetrytypes.SignalMetrics.StringValue(),
instrumentationtypes.CodeNamespace: "clickhouse-reader",
instrumentationtypes.CodeFunctionName: "GetNormalizedStatus",
})
if len(metricNames) == 0 {
return map[string]bool{}, nil
}
result := make(map[string]bool, len(metricNames))
buildKey := func(name string) string {
return constants.NormalizedMetricsMapCacheKey + ":" + name
}
uncached := make([]string, 0, len(metricNames))
for _, m := range metricNames {
var status model.MetricsNormalizedMap
if err := r.cache.Get(ctx, orgID, buildKey(m), &status); err == nil {
result[m] = status.IsUnNormalized
} else {
uncached = append(uncached, m)
}
}
if len(uncached) == 0 {
return result, nil
}
placeholders := "'" + strings.Join(uncached, "', '") + "'"
reductionEnabled := r.fl.BooleanOrEmpty(ctx, flagger.FeatureEnableMetricsReduction, featuretypes.NewFlaggerEvaluationContext(orgID))
var q string
if reductionEnabled {
q = fmt.Sprintf(
`SELECT metric_name, toUInt8(__normalized)
FROM (
SELECT metric_name, __normalized FROM %s.%s WHERE metric_name IN (%s)
UNION ALL
SELECT metric_name, __normalized FROM %s.%s WHERE metric_name IN (%s)
)
GROUP BY metric_name, __normalized`,
signozMetricDBName, signozTSTableNameV41Day, placeholders,
signozMetricDBName, signozTSTableNameV4Reduced, placeholders,
)
} else {
q = fmt.Sprintf(
`SELECT metric_name, toUInt8(__normalized)
FROM %s.%s
WHERE metric_name IN (%s)
GROUP BY metric_name, __normalized`,
signozMetricDBName, signozTSTableNameV41Day, placeholders,
)
}
rows, err := r.db.Query(ctx, q)
if err != nil {
return nil, err
}
defer rows.Close()
// tmp[m] collects the set {0,1} for a metric name, truth table
tmp := make(map[string]map[uint8]struct{}, len(uncached))
for rows.Next() {
var (
name string
normalized uint8
)
if err := rows.Scan(&name, &normalized); err != nil {
return nil, err
}
if _, ok := tmp[name]; !ok {
tmp[name] = make(map[uint8]struct{}, 2)
}
tmp[name][normalized] = struct{}{}
}
if err := rows.Err(); err != nil {
return nil, err
}
for _, m := range uncached {
set := tmp[m]
switch {
case len(set) == 0:
return nil, fmt.Errorf("metric %q not found in ClickHouse", m)
case len(set) == 2:
result[m] = true
default:
_, hasUnnorm := set[0]
result[m] = hasUnnorm
}
status := model.MetricsNormalizedMap{
MetricName: m,
IsUnNormalized: result[m],
}
_ = r.cache.Set(ctx, orgID, buildKey(m), &status, 0)
}
return result, nil
}

View File

@@ -56,7 +56,6 @@ import (
"github.com/SigNoz/signoz/pkg/query-service/app/queryBuilder"
tracesV3 "github.com/SigNoz/signoz/pkg/query-service/app/traces/v3"
tracesV4 "github.com/SigNoz/signoz/pkg/query-service/app/traces/v4"
"github.com/SigNoz/signoz/pkg/query-service/constants"
v3 "github.com/SigNoz/signoz/pkg/query-service/model/v3"
"github.com/SigNoz/signoz/pkg/query-service/postprocess"
"github.com/SigNoz/signoz/pkg/types"
@@ -507,9 +506,9 @@ func (aH *APIHandler) RegisterRoutes(router *mux.Router, am *middleware.AuthZ) {
router.HandleFunc("/api/v1/explorer/views", am.ViewAccess(aH.Signoz.Handlers.SavedView.List)).Methods(http.MethodGet)
router.HandleFunc("/api/v1/explorer/views", am.EditAccess(aH.Signoz.Handlers.SavedView.Create)).Methods(http.MethodPost)
router.HandleFunc("/api/v1/explorer/views/{viewId}", am.ViewAccess(aH.Signoz.Handlers.SavedView.Get)).Methods(http.MethodGet)
router.HandleFunc("/api/v1/explorer/views/{viewId}", am.EditAccess(aH.Signoz.Handlers.SavedView.Update)).Methods(http.MethodPut)
router.HandleFunc("/api/v1/explorer/views/{viewId}", am.EditAccess(aH.Signoz.Handlers.SavedView.Delete)).Methods(http.MethodDelete)
router.HandleFunc("/api/v1/explorer/views/{id}", am.ViewAccess(aH.Signoz.Handlers.SavedView.Get)).Methods(http.MethodGet)
router.HandleFunc("/api/v1/explorer/views/{id}", am.EditAccess(aH.Signoz.Handlers.SavedView.Update)).Methods(http.MethodPut)
router.HandleFunc("/api/v1/explorer/views/{id}", am.EditAccess(aH.Signoz.Handlers.SavedView.Delete)).Methods(http.MethodDelete)
router.HandleFunc("/api/v1/event", am.ViewAccess(aH.registerEvent)).Methods(http.MethodPost)
router.HandleFunc("/api/v1/services", am.ViewAccess(aH.getServices)).Methods(http.MethodPost) // Deprecated Usage, use the below endpoint /v2/services
@@ -1052,10 +1051,6 @@ func prepareQuery(r *http.Request) (string, error) {
return "", tmplErr
}
if !constants.IsDotMetricsEnabled {
return queryBuf.String(), nil
}
query = queryBuf.String()
// Now handle $var replacements (simple string replace)
@@ -1608,13 +1603,6 @@ func (aH *APIHandler) getFeatureFlags(w http.ResponseWriter, r *http.Request) {
Route: "",
})
if constants.IsDotMetricsEnabled {
for idx, feature := range featureSet {
if feature.Name == licensetypes.DotMetricsEnabled {
featureSet[idx].Active = true
}
}
}
aH.Respond(w, featureSet)
}
@@ -2055,12 +2043,8 @@ func (aH *APIHandler) onboardKafka(w http.ResponseWriter, r *http.Request) {
}
}
}
var kafkaConsumerFetchLatencyAvg string = "kafka_consumer_fetch_latency_avg"
var kafkaConsumerLag string = "kafka_consumer_group_lag"
if constants.IsDotMetricsEnabled {
kafkaConsumerLag = "kafka.consumer_group.lag"
kafkaConsumerFetchLatencyAvg = "kafka.consumer.fetch_latency_avg"
}
var kafkaConsumerFetchLatencyAvg string = "kafka.consumer.fetch_latency_avg"
var kafkaConsumerLag string = "kafka.consumer_group.lag"
if !fetchLatencyState && !consumerLagState {
entries = append(entries, kafka.OnboardingResponse{

View File

@@ -18,12 +18,12 @@ import (
)
var (
metricToUseForClusters = GetDotMetrics("k8s_node_cpu_usage")
metricToUseForClusters = "k8s.node.cpu.usage"
clusterAttrsToEnrich = []string{GetDotMetrics("k8s_cluster_name")}
clusterAttrsToEnrich = []string{"k8s.cluster.name"}
// TODO(srikanthccv): change this to k8s_cluster_uid after showing the missing data banner
k8sClusterUIDAttrKey = GetDotMetrics("k8s_cluster_name")
k8sClusterUIDAttrKey = "k8s.cluster.name"
queryNamesForClusters = map[string][]string{
"cpu": {"A"},

View File

@@ -9,250 +9,6 @@ import (
"github.com/SigNoz/signoz/pkg/query-service/model"
)
var dotMetricMap = map[string]string{
"system_uptime": "system.uptime",
"system_cpu_physical_count": "system.cpu.physical.count",
"system_cpu_logical_count": "system.cpu.logical.count",
"system_cpu_time": "system.cpu.time",
"system_cpu_frequency": "system.cpu.frequency",
"system_cpu_utilization": "system.cpu.utilization",
"system_cpu_load_average_15m": "system.cpu.load_average.15m",
"system_memory_usage": "system.memory.usage",
"system_memory_limit": "system.memory.limit",
"system_memory_utilization": "system.memory.utilization",
"system_memory_linux_available": "system.memory.linux.available",
"system_memory_linux_shared": "system.memory.linux.shared",
"system_memory_linux_slab_usage": "system.memory.linux.slab.usage",
"system_paging_usage": "system.paging.usage",
"system_paging_utilization": "system.paging.utilization",
"system_paging_faults": "system.paging.faults",
"system_paging_operations": "system.paging.operations",
"system_disk_io": "system.disk.io",
"system_disk_operations": "system.disk.operations",
"system_disk_io_time": "system.disk.io_time",
"system_disk_operation_time": "system.disk.operation_time",
"system_disk_merged": "system.disk.merged",
"system_disk_limit": "system.disk.limit",
"system_filesystem_usage": "system.filesystem.usage",
"system_filesystem_utilization": "system.filesystem.utilization",
"system_filesystem_limit": "system.filesystem.limit",
"system_network_errors": "system.network.errors",
"system_network_io": "system.network.io",
"system_network_connections": "system.network.connections",
"system_network_dropped": "system.network.dropped",
"system_network_packets": "system.network.packets",
"system_processes_count": "system.processes.count",
"system_processes_created": "system.processes.created",
"system_disk_pending_operations": "system.disk.pending_operations",
"system_disk_weighted_io_time": "system.disk.weighted_io_time",
"system_filesystem_inodes_usage": "system.filesystem.inodes.usage",
"system_network_conntrack_count": "system.network.conntrack.count",
"system_network_conntrack_max": "system.network.conntrack.max",
"system_cpu_load_average_1m": "system.cpu.load_average.1m",
"system_cpu_load_average_5m": "system.cpu.load_average.5m",
"host_name": "host.name",
"k8s_cluster_name": "k8s.cluster.name",
"k8s_node_name": "k8s.node.name",
"k8s_pod_memory_usage": "k8s.pod.memory.usage",
"k8s_pod_cpu_request_utilization": "k8s.pod.cpu_request_utilization",
"k8s_pod_memory_request_utilization": "k8s.pod.memory_request_utilization",
"k8s_pod_cpu_limit_utilization": "k8s.pod.cpu_limit_utilization",
"k8s_pod_memory_limit_utilization": "k8s.pod.memory_limit_utilization",
"k8s_container_restarts": "k8s.container.restarts",
"k8s_pod_phase": "k8s.pod.phase",
"k8s_node_allocatable_cpu": "k8s.node.allocatable_cpu",
"k8s_node_allocatable_memory": "k8s.node.allocatable_memory",
"k8s_node_memory_usage": "k8s.node.memory.usage",
"k8s_node_condition_ready": "k8s.node.condition_ready",
"k8s_daemonset_desired_scheduled_nodes": "k8s.daemonset.desired_scheduled_nodes",
"k8s_daemonset_current_scheduled_nodes": "k8s.daemonset.current_scheduled_nodes",
"k8s_deployment_desired": "k8s.deployment.desired",
"k8s_deployment_available": "k8s.deployment.available",
"k8s_job_desired_successful_pods": "k8s.job.desired_successful_pods",
"k8s_job_active_pods": "k8s.job.active_pods",
"k8s_job_failed_pods": "k8s.job.failed_pods",
"k8s_job_successful_pods": "k8s.job.successful_pods",
"k8s_statefulset_desired_pods": "k8s.statefulset.desired_pods",
"k8s_statefulset_current_pods": "k8s.statefulset.current_pods",
"k8s_namespace_name": "k8s.namespace.name",
"k8s_deployment_name": "k8s.deployment.name",
"k8s_cronjob_name": "k8s.cronjob.name",
"k8s_job_name": "k8s.job.name",
"k8s_daemonset_name": "k8s.daemonset.name",
"os_type": "os.type",
"process_cgroup": "process.cgroup",
"process_pid": "process.pid",
"process_parent_pid": "process.parent_pid",
"process_owner": "process.owner",
"process_executable_path": "process.executable.path",
"process_executable_name": "process.executable.name",
"process_command_line": "process.command_line",
"process_command": "process.command",
"process_memory_usage": "process.memory.usage",
"process_memory_virtual": "process.memory.virtual",
"process_cpu_time": "process.cpu.time",
"process_disk_io": "process.disk.io",
"nfs_client_net_count": "nfs.client.net.count",
"nfs_client_net_tcp_connection_accepted": "nfs.client.net.tcp.connection.accepted",
"nfs_client_operation_count": "nfs.client.operation.count",
"nfs_client_procedure_count": "nfs.client.procedure.count",
"nfs_client_rpc_authrefresh_count": "nfs.client.rpc.authrefresh.count",
"nfs_client_rpc_count": "nfs.client.rpc.count",
"nfs_client_rpc_retransmit_count": "nfs.client.rpc.retransmit.count",
"nfs_server_fh_stale_count": "nfs.server.fh.stale.count",
"nfs_server_io": "nfs.server.io",
"nfs_server_net_count": "nfs.server.net.count",
"nfs_server_net_tcp_connection_accepted": "nfs.server.net.tcp.connection.accepted",
"nfs_server_operation_count": "nfs.server.operation.count",
"nfs_server_procedure_count": "nfs.server.procedure.count",
"nfs_server_repcache_requests": "nfs.server.repcache.requests",
"nfs_server_rpc_count": "nfs.server.rpc.count",
"nfs_server_thread_count": "nfs.server.thread.count",
"k8s_persistentvolumeclaim_name": "k8s.persistentvolumeclaim.name",
"k8s_volume_available": "k8s.volume.available",
"k8s_volume_capacity": "k8s.volume.capacity",
"k8s_volume_inodes": "k8s.volume.inodes",
"k8s_volume_inodes_free": "k8s.volume.inodes.free",
"k8s_pod_uid": "k8s.pod.uid",
"k8s_pod_name": "k8s.pod.name",
"k8s_container_name": "k8s.container.name",
"container_id": "container.id",
"k8s_volume_name": "k8s.volume.name",
"k8s_volume_type": "k8s.volume.type",
"aws_volume_id": "aws.volume.id",
"fs_type": "fs.type",
"partition": "partition",
"gce_pd_name": "gce.pd.name",
"glusterfs_endpoints_name": "glusterfs.endpoints.name",
"glusterfs_path": "glusterfs.path",
"interface": "interface",
"direction": "direction",
"k8s_node_cpu_usage": "k8s.node.cpu.usage",
"k8s_node_cpu_time": "k8s.node.cpu.time",
"k8s_node_memory_available": "k8s.node.memory.available",
"k8s_node_memory_rss": "k8s.node.memory.rss",
"k8s_node_memory_working_set": "k8s.node.memory.working_set",
"k8s_node_memory_page_faults": "k8s.node.memory.page_faults",
"k8s_node_memory_major_page_faults": "k8s.node.memory.major_page_faults",
"k8s_node_filesystem_available": "k8s.node.filesystem.available",
"k8s_node_filesystem_capacity": "k8s.node.filesystem.capacity",
"k8s_node_filesystem_usage": "k8s.node.filesystem.usage",
"k8s_node_network_io": "k8s.node.network.io",
"k8s_node_network_errors": "k8s.node.network.errors",
"k8s_node_uptime": "k8s.node.uptime",
"k8s_pod_cpu_usage": "k8s.pod.cpu.usage",
"k8s_pod_cpu_time": "k8s.pod.cpu.time",
"k8s_pod_memory_available": "k8s.pod.memory.available",
"k8s_pod_cpu_node_utilization": "k8s.pod.cpu.node.utilization",
"k8s_pod_memory_node_utilization": "k8s.pod.memory.node.utilization",
"k8s_pod_memory_rss": "k8s.pod.memory.rss",
"k8s_pod_memory_working_set": "k8s.pod.memory.working_set",
"k8s_pod_memory_page_faults": "k8s.pod.memory.page_faults",
"k8s_pod_memory_major_page_faults": "k8s.pod.memory.major_page_faults",
"k8s_pod_filesystem_available": "k8s.pod.filesystem.available",
"k8s_pod_filesystem_capacity": "k8s.pod.filesystem.capacity",
"k8s_pod_filesystem_usage": "k8s.pod.filesystem.usage",
"k8s_pod_network_io": "k8s.pod.network.io",
"k8s_pod_network_errors": "k8s.pod.network.errors",
"k8s_pod_uptime": "k8s.pod.uptime",
"container_cpu_usage": "container.cpu.usage",
"container_cpu_time": "container.cpu.time",
"container_memory_available": "container.memory.available",
"container_memory_usage": "container.memory.usage",
"k8s_container_cpu_node_utilization": "k8s.container.cpu.node.utilization",
"k8s_container_cpu_limit_utilization": "k8s.container.cpu_limit_utilization",
"k8s_container_cpu_request_utilization": "k8s.container.cpu_request_utilization",
"k8s_container_memory_node_utilization": "k8s.container.memory.node.utilization",
"k8s_container_memory_limit_utilization": "k8s.container.memory_limit_utilization",
"k8s_container_memory_request_utilization": "k8s.container.memory_request_utilization",
"container_memory_rss": "container.memory.rss",
"container_memory_working_set": "container.memory.working_set",
"container_memory_page_faults": "container.memory.page_faults",
"container_memory_major_page_faults": "container.memory.major_page_faults",
"container_filesystem_available": "container.filesystem.available",
"container_filesystem_capacity": "container.filesystem.capacity",
"container_filesystem_usage": "container.filesystem.usage",
"container_uptime": "container.uptime",
"k8s_volume_inodes_used": "k8s.volume.inodes.used",
"k8s_namespace_uid": "k8s.namespace.uid",
"container_image_name": "container.image.name",
"container_image_tag": "container.image.tag",
"k8s_pod_qos_class": "k8s.pod.qos_class",
"k8s_replicaset_name": "k8s.replicaset.name",
"k8s_replicaset_uid": "k8s.replicaset.uid",
"k8s_replicationcontroller_name": "k8s.replicationcontroller.name",
"k8s_replicationcontroller_uid": "k8s.replicationcontroller.uid",
"k8s_resourcequota_uid": "k8s.resourcequota.uid",
"k8s_resourcequota_name": "k8s.resourcequota.name",
"k8s_statefulset_uid": "k8s.statefulset.uid",
"k8s_statefulset_name": "k8s.statefulset.name",
"k8s_deployment_uid": "k8s.deployment.uid",
"k8s_cronjob_uid": "k8s.cronjob.uid",
"k8s_daemonset_uid": "k8s.daemonset.uid",
"k8s_hpa_uid": "k8s.hpa.uid",
"k8s_hpa_name": "k8s.hpa.name",
"k8s_hpa_scaletargetref_kind": "k8s.hpa.scaletargetref.kind",
"k8s_hpa_scaletargetref_name": "k8s.hpa.scaletargetref.name",
"k8s_hpa_scaletargetref_apiversion": "k8s.hpa.scaletargetref.apiversion",
"k8s_job_uid": "k8s.job.uid",
"k8s_kubelet_version": "k8s.kubelet.version",
"container_runtime": "container.runtime",
"container_runtime_version": "container.runtime.version",
"os_description": "os.description",
"openshift_clusterquota_uid": "openshift.clusterquota.uid",
"openshift_clusterquota_name": "openshift.clusterquota.name",
"k8s_container_status_last_terminated_reason": "k8s.container.status.last_terminated_reason",
"resource": "resource",
"condition": "condition",
"k8s_container_cpu_request": "k8s.container.cpu_request",
"k8s_container_cpu_limit": "k8s.container.cpu_limit",
"k8s_container_memory_request": "k8s.container.memory_request",
"k8s_container_memory_limit": "k8s.container.memory_limit",
"k8s_container_storage_request": "k8s.container.storage_request",
"k8s_container_storage_limit": "k8s.container.storage_limit",
"k8s_container_ephemeralstorage_request": "k8s.container.ephemeralstorage_request",
"k8s_container_ephemeralstorage_limit": "k8s.container.ephemeralstorage_limit",
"k8s_container_ready": "k8s.container.ready",
"k8s_pod_status_reason": "k8s.pod.status_reason",
"k8s_cronjob_active_jobs": "k8s.cronjob.active_jobs",
"k8s_daemonset_misscheduled_nodes": "k8s.daemonset.misscheduled_nodes",
"k8s_daemonset_ready_nodes": "k8s.daemonset.ready_nodes",
"k8s_hpa_max_replicas": "k8s.hpa.max_replicas",
"k8s_hpa_min_replicas": "k8s.hpa.min_replicas",
"k8s_hpa_current_replicas": "k8s.hpa.current_replicas",
"k8s_hpa_desired_replicas": "k8s.hpa.desired_replicas",
"k8s_job_max_parallel_pods": "k8s.job.max_parallel_pods",
"k8s_namespace_phase": "k8s.namespace.phase",
"k8s_replicaset_desired": "k8s.replicaset.desired",
"k8s_replicaset_available": "k8s.replicaset.available",
"k8s_replication_controller_desired": "k8s.replication_controller.desired",
"k8s_replication_controller_available": "k8s.replication_controller.available",
"k8s_resource_quota_hard_limit": "k8s.resource_quota.hard_limit",
"k8s_resource_quota_used": "k8s.resource_quota.used",
"k8s_statefulset_updated_pods": "k8s.statefulset.updated_pods",
"k8s_node_condition": "k8s.node.condition",
}
const fromWhereQuery = `
FROM %s.%s
WHERE metric_name IN (%s)
@@ -262,39 +18,39 @@ WHERE metric_name IN (%s)
var (
// TODO(srikanthccv): import metadata yaml from receivers and use generated files to check the metrics
podMetricNamesToCheck = []string{
GetDotMetrics("k8s_pod_cpu_usage"),
GetDotMetrics("k8s_pod_memory_working_set"),
GetDotMetrics("k8s_pod_cpu_request_utilization"),
GetDotMetrics("k8s_pod_memory_request_utilization"),
GetDotMetrics("k8s_pod_cpu_limit_utilization"),
GetDotMetrics("k8s_pod_memory_limit_utilization"),
GetDotMetrics("k8s_container_restarts"),
GetDotMetrics("k8s_pod_phase"),
"k8s.pod.cpu.usage",
"k8s.pod.memory.working_set",
"k8s.pod.cpu_request_utilization",
"k8s.pod.memory_request_utilization",
"k8s.pod.cpu_limit_utilization",
"k8s.pod.memory_limit_utilization",
"k8s.container.restarts",
"k8s.pod.phase",
}
nodeMetricNamesToCheck = []string{
GetDotMetrics("k8s_node_cpu_usage"),
GetDotMetrics("k8s_node_allocatable_cpu"),
GetDotMetrics("k8s_node_memory_working_set"),
GetDotMetrics("k8s_node_allocatable_memory"),
GetDotMetrics("k8s_node_condition_ready"),
"k8s.node.cpu.usage",
"k8s.node.allocatable_cpu",
"k8s.node.memory.working_set",
"k8s.node.allocatable_memory",
"k8s.node.condition_ready",
}
clusterMetricNamesToCheck = []string{
GetDotMetrics("k8s_daemonset_desired_scheduled_nodes"),
GetDotMetrics("k8s_daemonset_current_scheduled_nodes"),
GetDotMetrics("k8s_deployment_desired"),
GetDotMetrics("k8s_deployment_available"),
GetDotMetrics("k8s_job_desired_successful_pods"),
GetDotMetrics("k8s_job_active_pods"),
GetDotMetrics("k8s_job_failed_pods"),
GetDotMetrics("k8s_job_successful_pods"),
GetDotMetrics("k8s_statefulset_desired_pods"),
GetDotMetrics("k8s_statefulset_current_pods"),
"k8s.daemonset.desired_scheduled_nodes",
"k8s.daemonset.current_scheduled_nodes",
"k8s.deployment.desired",
"k8s.deployment.available",
"k8s.job.desired_successful_pods",
"k8s.job.active_pods",
"k8s.job.failed_pods",
"k8s.job.successful_pods",
"k8s.statefulset.desired_pods",
"k8s.statefulset.current_pods",
}
optionalPodMetricNamesToCheck = []string{
GetDotMetrics("k8s_pod_cpu_request_utilization"),
GetDotMetrics("k8s_pod_memory_request_utilization"),
GetDotMetrics("k8s_pod_cpu_limit_utilization"),
GetDotMetrics("k8s_pod_memory_limit_utilization"),
"k8s.pod.cpu_request_utilization",
"k8s.pod.memory_request_utilization",
"k8s.pod.cpu_limit_utilization",
"k8s.pod.memory_limit_utilization",
}
// did they ever send _any_ pod metrics?
@@ -332,15 +88,15 @@ SELECT
any(JSONExtractString(labels, '%s')) as k8s_job_name,
JSONExtractString(labels, '%s') as k8s_pod_name
`,
GetDotMetrics("k8s_cluster_name"),
GetDotMetrics("k8s_node_name"),
GetDotMetrics("k8s_namespace_name"),
GetDotMetrics("k8s_deployment_name"),
GetDotMetrics("k8s_statefulset_name"),
GetDotMetrics("k8s_daemonset_name"),
GetDotMetrics("k8s_cronjob_name"),
GetDotMetrics("k8s_job_name"),
GetDotMetrics("k8s_pod_name"),
"k8s.cluster.name",
"k8s.node.name",
"k8s.namespace.name",
"k8s.deployment.name",
"k8s.statefulset.name",
"k8s.daemonset.name",
"k8s.cronjob.name",
"k8s.job.name",
"k8s.pod.name",
)
filterGroupQuery = fmt.Sprintf(`
@@ -349,7 +105,7 @@ AND JSONExtractString(labels, '%s')
GROUP BY k8s_pod_name
LIMIT 1 BY k8s_cluster_name, k8s_node_name, k8s_namespace_name
`,
GetDotMetrics("k8s_namespace_name"),
"k8s.namespace.name",
)
isSendingRequiredMetadataQuery = selectQuery + fromWhereQuery + filterGroupQuery
@@ -450,12 +206,3 @@ func getParamsForTopVolumes(req model.VolumeListRequest) (int64, string, string)
func localQueryToDistributedQuery(query string) string {
return strings.Replace(query, ".time_series_v4", ".distributed_time_series_v4", 1)
}
func GetDotMetrics(key string) string {
if constants.IsDotMetricsEnabled {
if _, ok := dotMetricMap[key]; ok {
return dotMetricMap[key]
}
}
return key
}

View File

@@ -18,18 +18,18 @@ import (
)
var (
metricToUseForDaemonSets = GetDotMetrics("k8s_pod_cpu_usage")
k8sDaemonSetNameAttrKey = GetDotMetrics("k8s_daemonset_name")
metricToUseForDaemonSets = "k8s.pod.cpu.usage"
k8sDaemonSetNameAttrKey = "k8s.daemonset.name"
metricNamesForDaemonSets = map[string]string{
"desired_nodes": GetDotMetrics("k8s_daemonset_desired_scheduled_nodes"),
"available_nodes": GetDotMetrics("k8s_daemonset_current_scheduled_nodes"),
"desired_nodes": "k8s.daemonset.desired_scheduled_nodes",
"available_nodes": "k8s.daemonset.current_scheduled_nodes",
}
daemonSetAttrsToEnrich = []string{
GetDotMetrics("k8s_daemonset_name"),
GetDotMetrics("k8s_namespace_name"),
GetDotMetrics("k8s_cluster_name"),
"k8s.daemonset.name",
"k8s.namespace.name",
"k8s.cluster.name",
}
queryNamesForDaemonSets = map[string][]string{

View File

@@ -18,18 +18,18 @@ import (
)
var (
metricToUseForDeployments = GetDotMetrics("k8s_pod_cpu_usage")
k8sDeploymentNameAttrKey = GetDotMetrics("k8s_deployment_name")
metricToUseForDeployments = "k8s.pod.cpu.usage"
k8sDeploymentNameAttrKey = "k8s.deployment.name"
metricNamesForDeployments = map[string]string{
"desired_pods": GetDotMetrics("k8s_deployment_desired"),
"available_pods": GetDotMetrics("k8s_deployment_available"),
"desired_pods": "k8s.deployment.desired",
"available_pods": "k8s.deployment.available",
}
deploymentAttrsToEnrich = []string{
GetDotMetrics("k8s_deployment_name"),
GetDotMetrics("k8s_namespace_name"),
GetDotMetrics("k8s_cluster_name"),
"k8s.deployment.name",
"k8s.namespace.name",
"k8s.cluster.name",
}
queryNamesForDeployments = map[string][]string{

View File

@@ -45,15 +45,15 @@ var (
"mode",
"mountpoint",
"type",
GetDotMetrics("os_type"),
GetDotMetrics("process_cgroup"),
GetDotMetrics("process_command"),
GetDotMetrics("process_command_line"),
GetDotMetrics("process_executable_name"),
GetDotMetrics("process_executable_path"),
GetDotMetrics("process_owner"),
GetDotMetrics("process_parent_pid"),
GetDotMetrics("process_pid"),
"os.type",
"process.cgroup",
"process.command",
"process.command_line",
"process.executable.name",
"process.executable.path",
"process.owner",
"process.parent_pid",
"process.pid",
}
queryNamesForTopHosts = map[string][]string{
@@ -64,65 +64,65 @@ var (
}
// TODO(srikanthccv): remove hardcoded metric name and support keys from any system metric
metricToUseForHostAttributes = GetDotMetrics("system_cpu_load_average_15m")
hostNameAttrKey = GetDotMetrics("host_name")
metricToUseForHostAttributes = "system.cpu.load_average.15m"
hostNameAttrKey = "host.name"
agentNameToIgnore = "k8s-infra-otel-agent"
hostAttrsToEnrich = []string{
GetDotMetrics("os_type"),
"os.type",
}
metricNamesForHosts = map[string]string{
"filesystem": GetDotMetrics("system_filesystem_usage"),
"cpu": GetDotMetrics("system_cpu_time"),
"memory": GetDotMetrics("system_memory_usage"),
"load15": GetDotMetrics("system_cpu_load_average_15m"),
"wait": GetDotMetrics("system_cpu_time"),
"filesystem": "system.filesystem.usage",
"cpu": "system.cpu.time",
"memory": "system.memory.usage",
"load15": "system.cpu.load_average.15m",
"wait": "system.cpu.time",
}
uniqueMetricNamesForHosts = []string{
GetDotMetrics("system_uptime"),
GetDotMetrics("system_cpu_time"),
GetDotMetrics("system_cpu_load_average_1m"),
GetDotMetrics("system_cpu_load_average_5m"),
GetDotMetrics("system_cpu_load_average_15m"),
GetDotMetrics("system_memory_usage"),
GetDotMetrics("system_paging_usage"),
GetDotMetrics("system_paging_faults"),
GetDotMetrics("system_paging_operations"),
GetDotMetrics("system_disk_io"),
GetDotMetrics("system_disk_operations"),
GetDotMetrics("system_disk_io_time"),
GetDotMetrics("system_disk_operation_time"),
GetDotMetrics("system_disk_merged"),
GetDotMetrics("system_disk_pending_operations"),
GetDotMetrics("system_disk_weighted_io_time"),
GetDotMetrics("system_filesystem_usage"),
GetDotMetrics("system_filesystem_inodes_usage"),
GetDotMetrics("system_network_io"),
GetDotMetrics("system_network_errors"),
GetDotMetrics("system_network_connections"),
GetDotMetrics("system_network_dropped"),
GetDotMetrics("system_network_packets"),
GetDotMetrics("system_processes_count"),
GetDotMetrics("system_processes_created"),
GetDotMetrics("process_cpu_time"),
GetDotMetrics("process_disk_io"),
GetDotMetrics("process_memory_usage"),
GetDotMetrics("process_memory_virtual"),
GetDotMetrics("nfs_client_net_count"),
GetDotMetrics("nfs_client_net_tcp_connection_accepted"),
GetDotMetrics("nfs_client_operation_count"),
GetDotMetrics("nfs_client_procedure_count"),
GetDotMetrics("nfs_client_rpc_authrefresh_count"),
GetDotMetrics("nfs_client_rpc_count"),
GetDotMetrics("nfs_client_rpc_retransmit_count"),
GetDotMetrics("nfs_server_fh_stale_count"),
GetDotMetrics("nfs_server_io"),
GetDotMetrics("nfs_server_net_count"),
GetDotMetrics("nfs_server_net_tcp_connection_accepted"),
GetDotMetrics("nfs_server_operation_count"),
GetDotMetrics("nfs_server_procedure_count"),
GetDotMetrics("nfs_server_repcache_requests"),
GetDotMetrics("nfs_server_rpc_count"),
GetDotMetrics("nfs_server_thread_count"),
"system.uptime",
"system.cpu.time",
"system.cpu.load_average.1m",
"system.cpu.load_average.5m",
"system.cpu.load_average.15m",
"system.memory.usage",
"system.paging.usage",
"system.paging.faults",
"system.paging.operations",
"system.disk.io",
"system.disk.operations",
"system.disk.io_time",
"system.disk.operation_time",
"system.disk.merged",
"system.disk.pending_operations",
"system.disk.weighted_io_time",
"system.filesystem.usage",
"system.filesystem.inodes.usage",
"system.network.io",
"system.network.errors",
"system.network.connections",
"system.network.dropped",
"system.network.packets",
"system.processes.count",
"system.processes.created",
"process.cpu.time",
"process.disk.io",
"process.memory.usage",
"process.memory.virtual",
"nfs.client.net.count",
"nfs.client.net.tcp.connection.accepted",
"nfs.client.operation.count",
"nfs.client.procedure.count",
"nfs.client.rpc.authrefresh.count",
"nfs.client.rpc.count",
"nfs.client.rpc.retransmit.count",
"nfs.server.fh.stale.count",
"nfs.server.io",
"nfs.server.net.count",
"nfs.server.net.tcp.connection.accepted",
"nfs.server.operation.count",
"nfs.server.procedure.count",
"nfs.server.repcache.requests",
"nfs.server.rpc.count",
"nfs.server.thread.count",
}
)
@@ -351,8 +351,8 @@ func (h *HostsRepo) IsSendingK8SAgentMetrics(ctx context.Context, req model.Host
AND unix_milli >= toUnixTimestamp(now() - INTERVAL 60 MINUTE) * 1000
AND JSONExtractString(labels, '%s') LIKE '%%-otel-agent%%'
AND fingerprint GLOBAL IN (%s)`,
GetDotMetrics("k8s_cluster_name"), GetDotMetrics("k8s_node_name"),
constants.SIGNOZ_METRIC_DBNAME, constants.SIGNOZ_TIMESERIES_V4_TABLENAME, namesStr, GetDotMetrics("host_name"), queryForRecentFingerprints)
"k8s.cluster.name", "k8s.node.name",
constants.SIGNOZ_METRIC_DBNAME, constants.SIGNOZ_TIMESERIES_V4_TABLENAME, namesStr, "host.name", queryForRecentFingerprints)
result, err := h.reader.GetListResultV3(ctx, query)
if err != nil {
@@ -363,13 +363,13 @@ func (h *HostsRepo) IsSendingK8SAgentMetrics(ctx context.Context, req model.Host
nodeNames := make(map[string]struct{})
for _, row := range result {
switch v := row.Data[GetDotMetrics("k8s_cluster_name")].(type) {
switch v := row.Data["k8s.cluster.name"].(type) {
case string:
clusterNames[v] = struct{}{}
case *string:
clusterNames[*v] = struct{}{}
}
switch v := row.Data[GetDotMetrics("k8s_node_name")].(type) {
switch v := row.Data["k8s.node.name"].(type) {
case string:
nodeNames[v] = struct{}{}
case *string:
@@ -535,7 +535,7 @@ func (h *HostsRepo) GetHostList(ctx context.Context, orgID valuer.UUID, req mode
if _, ok := hostAttrs[record.HostName]; ok {
record.Meta = hostAttrs[record.HostName]
}
if osType, ok := record.Meta[GetDotMetrics("os_type")]; ok {
if osType, ok := record.Meta["os.type"]; ok {
record.OS = osType
}
record.Active = activeHosts[record.HostName]

View File

@@ -18,20 +18,20 @@ import (
)
var (
metricToUseForJobs = GetDotMetrics("k8s_job_desired_successful_pods")
k8sJobNameAttrKey = GetDotMetrics("k8s_job_name")
metricToUseForJobs = "k8s.job.desired_successful_pods"
k8sJobNameAttrKey = "k8s.job.name"
metricNamesForJobs = map[string]string{
"desired_successful_pods": GetDotMetrics("k8s_job_desired_successful_pods"),
"active_pods": GetDotMetrics("k8s_job_active_pods"),
"failed_pods": GetDotMetrics("k8s_job_failed_pods"),
"successful_pods": GetDotMetrics("k8s_job_successful_pods"),
"desired_successful_pods": "k8s.job.desired_successful_pods",
"active_pods": "k8s.job.active_pods",
"failed_pods": "k8s.job.failed_pods",
"successful_pods": "k8s.job.successful_pods",
}
jobAttrsToEnrich = []string{
GetDotMetrics("k8s_job_name"),
GetDotMetrics("k8s_namespace_name"),
GetDotMetrics("k8s_cluster_name"),
"k8s.job.name",
"k8s.namespace.name",
"k8s.cluster.name",
}
queryNamesForJobs = map[string][]string{
@@ -54,7 +54,7 @@ var (
QueryName: "H",
DataSource: v3.DataSourceMetrics,
AggregateAttribute: v3.AttributeKey{
Key: GetDotMetrics(metricNamesForJobs["desired_successful_pods"]),
Key: metricNamesForJobs["desired_successful_pods"],
DataType: v3.AttributeKeyDataTypeFloat64,
},
Temporality: v3.Unspecified,
@@ -74,7 +74,7 @@ var (
QueryName: "I",
DataSource: v3.DataSourceMetrics,
AggregateAttribute: v3.AttributeKey{
Key: GetDotMetrics(metricNamesForJobs["active_pods"]),
Key: metricNamesForJobs["active_pods"],
DataType: v3.AttributeKeyDataTypeFloat64,
},
Temporality: v3.Unspecified,
@@ -94,7 +94,7 @@ var (
QueryName: "J",
DataSource: v3.DataSourceMetrics,
AggregateAttribute: v3.AttributeKey{
Key: GetDotMetrics(metricNamesForJobs["failed_pods"]),
Key: metricNamesForJobs["failed_pods"],
DataType: v3.AttributeKeyDataTypeFloat64,
},
Temporality: v3.Unspecified,
@@ -114,7 +114,7 @@ var (
QueryName: "K",
DataSource: v3.DataSourceMetrics,
AggregateAttribute: v3.AttributeKey{
Key: GetDotMetrics(metricNamesForJobs["successful_pods"]),
Key: metricNamesForJobs["successful_pods"],
DataType: v3.AttributeKeyDataTypeFloat64,
},
Temporality: v3.Unspecified,
@@ -327,7 +327,7 @@ func (d *JobsRepo) GetJobList(ctx context.Context, orgID valuer.UUID, req model.
}
if req.OrderBy == nil {
req.OrderBy = &v3.OrderBy{ColumnName: GetDotMetrics("desired_pods"), Order: v3.DirectionDesc}
req.OrderBy = &v3.OrderBy{ColumnName: "desired_pods", Order: v3.DirectionDesc}
}
if req.GroupBy == nil {

View File

@@ -18,11 +18,11 @@ import (
)
var (
metricToUseForNamespaces = GetDotMetrics("k8s_pod_cpu_usage")
metricToUseForNamespaces = "k8s.pod.cpu.usage"
namespaceAttrsToEnrich = []string{
GetDotMetrics("k8s_namespace_name"),
GetDotMetrics("k8s_cluster_name"),
"k8s.namespace.name",
"k8s.cluster.name",
}
queryNamesForNamespaces = map[string][]string{
@@ -33,11 +33,11 @@ var (
namespaceQueryNames = []string{"A", "D", "H", "I", "J", "K"}
attributesKeysForNamespaces = []v3.AttributeKey{
{Key: GetDotMetrics("k8s_namespace_name")},
{Key: GetDotMetrics("k8s_cluster_name")},
{Key: "k8s.namespace.name"},
{Key: "k8s.cluster.name"},
}
k8sNamespaceNameAttrKey = GetDotMetrics("k8s_namespace_name")
k8sNamespaceNameAttrKey = "k8s.namespace.name"
)
type NamespacesRepo struct {

View File

@@ -21,11 +21,11 @@ import (
)
var (
metricToUseForNodes = GetDotMetrics("k8s_node_cpu_usage")
metricToUseForNodes = "k8s.node.cpu.usage"
nodeAttrsToEnrich = []string{GetDotMetrics("k8s_node_name"), GetDotMetrics("k8s_node_uid"), GetDotMetrics("k8s_cluster_name")}
nodeAttrsToEnrich = []string{"k8s.node.name", "k8s.node.uid", "k8s.cluster.name"}
k8sNodeGroupAttrKey = GetDotMetrics("k8s_node_name")
k8sNodeGroupAttrKey = "k8s.node.name"
queryNamesForNodes = map[string][]string{
"cpu": {"A"},
@@ -36,11 +36,11 @@ var (
nodeQueryNames = []string{"A", "B", "C", "D", "E", "F"}
metricNamesForNodes = map[string]string{
"cpu": GetDotMetrics("k8s_node_cpu_usage"),
"cpu_allocatable": GetDotMetrics("k8s_node_allocatable_cpu"),
"memory": GetDotMetrics("k8s_node_memory_working_set"),
"memory_allocatable": GetDotMetrics("k8s_node_allocatable_memory"),
"node_condition": GetDotMetrics("k8s_node_condition_ready"),
"cpu": "k8s.node.cpu.usage",
"cpu_allocatable": "k8s.node.allocatable_cpu",
"memory": "k8s.node.memory.working_set",
"memory_allocatable": "k8s.node.allocatable_memory",
"node_condition": "k8s.node.condition_ready",
}
)

View File

@@ -21,22 +21,22 @@ import (
)
var (
metricToUseForPods = GetDotMetrics("k8s_pod_cpu_usage")
metricToUseForPods = "k8s.pod.cpu.usage"
podAttrsToEnrich = []string{
GetDotMetrics("k8s_pod_uid"),
GetDotMetrics("k8s_pod_name"),
GetDotMetrics("k8s_namespace_name"),
GetDotMetrics("k8s_node_name"),
GetDotMetrics("k8s_deployment_name"),
GetDotMetrics("k8s_statefulset_name"),
GetDotMetrics("k8s_daemonset_name"),
GetDotMetrics("k8s_job_name"),
GetDotMetrics("k8s_cronjob_name"),
GetDotMetrics("k8s_cluster_name"),
"k8s.pod.uid",
"k8s.pod.name",
"k8s.namespace.name",
"k8s.node.name",
"k8s.deployment.name",
"k8s.statefulset.name",
"k8s.daemonset.name",
"k8s.job.name",
"k8s.cronjob.name",
"k8s.cluster.name",
}
k8sPodUIDAttrKey = GetDotMetrics("k8s_pod_uid")
k8sPodUIDAttrKey = "k8s.pod.uid"
queryNamesForPods = map[string][]string{
"cpu": {"A"},
@@ -51,14 +51,14 @@ var (
podQueryNames = []string{"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K"}
metricNamesForPods = map[string]string{
"cpu": GetDotMetrics("k8s_pod_cpu_usage"),
"cpu_request": GetDotMetrics("k8s_pod_cpu_request_utilization"),
"cpu_limit": GetDotMetrics("k8s_pod_cpu_limit_utilization"),
"memory": GetDotMetrics("k8s_pod_memory_working_set"),
"memory_request": GetDotMetrics("k8s_pod_memory_request_utilization"),
"memory_limit": GetDotMetrics("k8s_pod_memory_limit_utilization"),
"restarts": GetDotMetrics("k8s_container_restarts"),
"pod_phase": GetDotMetrics("k8s_pod_phase"),
"cpu": "k8s.pod.cpu.usage",
"cpu_request": "k8s.pod.cpu_request_utilization",
"cpu_limit": "k8s.pod.cpu_limit_utilization",
"memory": "k8s.pod.memory.working_set",
"memory_request": "k8s.pod.memory_request_utilization",
"memory_limit": "k8s.pod.memory_limit_utilization",
"restarts": "k8s.container.restarts",
"pod_phase": "k8s.pod.phase",
}
)
@@ -169,7 +169,7 @@ func (p *PodsRepo) SendingRequiredMetadata(ctx context.Context) ([]model.PodOnbo
// for each pod, check if we have all the required metadata
for _, row := range result {
status := model.PodOnboardingStatus{}
switch v := row.Data[GetDotMetrics("k8s_cluster_name")].(type) {
switch v := row.Data["k8s.cluster.name"].(type) {
case string:
status.HasClusterName = true
status.ClusterName = v
@@ -177,7 +177,7 @@ func (p *PodsRepo) SendingRequiredMetadata(ctx context.Context) ([]model.PodOnbo
status.HasClusterName = *v != ""
status.ClusterName = *v
}
switch v := row.Data[GetDotMetrics("k8s_node_name")].(type) {
switch v := row.Data["k8s.node.name"].(type) {
case string:
status.HasNodeName = true
status.NodeName = v
@@ -185,7 +185,7 @@ func (p *PodsRepo) SendingRequiredMetadata(ctx context.Context) ([]model.PodOnbo
status.HasNodeName = *v != ""
status.NodeName = *v
}
switch v := row.Data[GetDotMetrics("k8s_namespace_name")].(type) {
switch v := row.Data["k8s.namespace.name"].(type) {
case string:
status.HasNamespaceName = true
status.NamespaceName = v
@@ -193,38 +193,38 @@ func (p *PodsRepo) SendingRequiredMetadata(ctx context.Context) ([]model.PodOnbo
status.HasNamespaceName = *v != ""
status.NamespaceName = *v
}
switch v := row.Data[GetDotMetrics("k8s_deployment_name")].(type) {
switch v := row.Data["k8s.deployment.name"].(type) {
case string:
status.HasDeploymentName = true
case *string:
status.HasDeploymentName = *v != ""
}
switch v := row.Data[GetDotMetrics("k8s_statefulset_name")].(type) {
switch v := row.Data["k8s.statefulset.name"].(type) {
case string:
status.HasStatefulsetName = true
case *string:
status.HasStatefulsetName = *v != ""
}
switch v := row.Data[GetDotMetrics("k8s_daemonset_name")].(type) {
switch v := row.Data["k8s.daemonset.name"].(type) {
case string:
status.HasDaemonsetName = true
case *string:
status.HasDaemonsetName = *v != ""
}
switch v := row.Data[GetDotMetrics("k8s_cronjob_name")].(type) {
switch v := row.Data["k8s.cronjob.name"].(type) {
case string:
status.HasCronjobName = true
case *string:
status.HasCronjobName = *v != ""
}
switch v := row.Data[GetDotMetrics("k8s_job_name")].(type) {
switch v := row.Data["k8s.job.name"].(type) {
case string:
status.HasJobName = true
case *string:
status.HasJobName = *v != ""
}
switch v := row.Data[GetDotMetrics("k8s_pod_name")].(type) {
switch v := row.Data["k8s.pod.name"].(type) {
case string:
status.PodName = v
case *string:

View File

@@ -23,15 +23,15 @@ var (
"memory": {"C"},
}
processPIDAttrKey = GetDotMetrics("process_pid")
processPIDAttrKey = "process.pid"
metricNamesForProcesses = map[string]string{
"cpu": GetDotMetrics("process_cpu_time"),
"memory": GetDotMetrics("process_memory_usage"),
"cpu": "process.cpu.time",
"memory": "process.memory.usage",
}
metricToUseForProcessAttributes = GetDotMetrics("process_memory_usage")
processNameAttrKey = GetDotMetrics("process_executable_name")
processCMDAttrKey = GetDotMetrics("process_command")
processCMDLineAttrKey = GetDotMetrics("process_command_line")
metricToUseForProcessAttributes = "process.memory.usage"
processNameAttrKey = "process.executable.name"
processCMDAttrKey = "process.command"
processCMDLineAttrKey = "process.command_line"
)
type ProcessesRepo struct {
@@ -46,7 +46,7 @@ func NewProcessesRepo(reader interfaces.Reader, querierV2 interfaces.Querier) *P
func (p *ProcessesRepo) GetProcessAttributeKeys(ctx context.Context, orgID valuer.UUID, req v3.FilterAttributeKeyRequest) (*v3.FilterAttributeKeyResponse, error) {
// TODO(srikanthccv): remove hardcoded metric name and support keys from any system metric
req.DataSource = v3.DataSourceMetrics
req.AggregateAttribute = GetDotMetrics("process_memory_usage")
req.AggregateAttribute = "process.memory.usage"
if req.Limit == 0 {
req.Limit = 50
}
@@ -71,7 +71,7 @@ func (p *ProcessesRepo) GetProcessAttributeKeys(ctx context.Context, orgID value
func (p *ProcessesRepo) GetProcessAttributeValues(ctx context.Context, orgID valuer.UUID, req v3.FilterAttributeValueRequest) (*v3.FilterAttributeValueResponse, error) {
req.DataSource = v3.DataSourceMetrics
req.AggregateAttribute = GetDotMetrics("process_memory_usage")
req.AggregateAttribute = "process.memory.usage"
if req.Limit == 0 {
req.Limit = 50
}
@@ -87,7 +87,7 @@ func (p *ProcessesRepo) getMetadataAttributes(ctx context.Context,
req model.ProcessListRequest) (map[string]map[string]string, error) {
processAttrs := map[string]map[string]string{}
keysToAdd := []string{GetDotMetrics("process_pid"), GetDotMetrics("process_executable_name"), GetDotMetrics("process_command"), GetDotMetrics("process_command_line")}
keysToAdd := []string{"process.pid", "process.executable.name", "process.command", "process.command_line"}
for _, key := range keysToAdd {
hasKey := false
for _, groupByKey := range req.GroupBy {

View File

@@ -18,19 +18,19 @@ import (
)
var (
metricToUseForVolumes = GetDotMetrics("k8s_volume_available")
metricToUseForVolumes = "k8s.volume.available"
volumeAttrsToEnrich = []string{
GetDotMetrics("k8s_pod_uid"),
GetDotMetrics("k8s_pod_name"),
GetDotMetrics("k8s_namespace_name"),
GetDotMetrics("k8s_node_name"),
GetDotMetrics("k8s_statefulset_name"),
GetDotMetrics("k8s_cluster_name"),
GetDotMetrics("k8s_persistentvolumeclaim_name"),
"k8s.pod.uid",
"k8s.pod.name",
"k8s.namespace.name",
"k8s.node.name",
"k8s.statefulset.name",
"k8s.cluster.name",
"k8s.persistentvolumeclaim.name",
}
k8sPersistentVolumeClaimNameAttrKey = GetDotMetrics("k8s_persistentvolumeclaim_name")
k8sPersistentVolumeClaimNameAttrKey = "k8s.persistentvolumeclaim.name"
queryNamesForVolumes = map[string][]string{
"available": {"A"},
@@ -44,11 +44,11 @@ var (
volumeQueryNames = []string{"A", "B", "C", "D", "E", "F1"}
metricNamesForVolumes = map[string]string{
"available": GetDotMetrics("k8s_volume_available"),
"capacity": GetDotMetrics("k8s_volume_capacity"),
"inodes": GetDotMetrics("k8s_volume_inodes"),
"inodes_free": GetDotMetrics("k8s_volume_inodes_free"),
"inodes_used": GetDotMetrics("k8s_volume_inodes_used"),
"available": "k8s.volume.available",
"capacity": "k8s.volume.capacity",
"inodes": "k8s.volume.inodes",
"inodes_free": "k8s.volume.inodes.free",
"inodes_used": "k8s.volume.inodes.used",
}
)

View File

@@ -18,18 +18,18 @@ import (
)
var (
metricToUseForStatefulSets = GetDotMetrics("k8s_pod_cpu_usage")
k8sStatefulSetNameAttrKey = GetDotMetrics("k8s_statefulset_name")
metricToUseForStatefulSets = "k8s.pod.cpu.usage"
k8sStatefulSetNameAttrKey = "k8s.statefulset.name"
metricNamesForStatefulSets = map[string]string{
"desired_pods": GetDotMetrics("k8s_statefulset_desired_pods"),
"available_pods": GetDotMetrics("k8s_statefulset_current_pods"),
"desired_pods": "k8s.statefulset.desired_pods",
"available_pods": "k8s.statefulset.current_pods",
}
statefulSetAttrsToEnrich = []string{
GetDotMetrics("k8s_statefulset_name"),
GetDotMetrics("k8s_namespace_name"),
GetDotMetrics("k8s_cluster_name"),
"k8s.statefulset.name",
"k8s.namespace.name",
"k8s.cluster.name",
}
queryNamesForStatefulSets = map[string][]string{

View File

@@ -4,13 +4,13 @@ import v3 "github.com/SigNoz/signoz/pkg/query-service/model/v3"
var (
metricNamesForWorkloads = map[string]string{
"cpu": GetDotMetrics("k8s_pod_cpu_usage"),
"cpu_request": GetDotMetrics("k8s_pod_cpu_request_utilization"),
"cpu_limit": GetDotMetrics("k8s_pod_cpu_limit_utilization"),
"memory": GetDotMetrics("k8s_pod_memory_working_set"),
"memory_request": GetDotMetrics("k8s_pod_memory_request_utilization"),
"memory_limit": GetDotMetrics("k8s_pod_memory_limit_utilization"),
"restarts": GetDotMetrics("k8s_container_restarts"),
"cpu": "k8s.pod.cpu.usage",
"cpu_request": "k8s.pod.cpu_request_utilization",
"cpu_limit": "k8s.pod.cpu_limit_utilization",
"memory": "k8s.pod.memory.working_set",
"memory_request": "k8s.pod.memory_request_utilization",
"memory_limit": "k8s.pod.memory_limit_utilization",
"restarts": "k8s.container.restarts",
}
)

View File

@@ -4,7 +4,6 @@ import (
"fmt"
"github.com/SigNoz/signoz/pkg/query-service/common"
"github.com/SigNoz/signoz/pkg/query-service/constants"
v3 "github.com/SigNoz/signoz/pkg/query-service/model/v3"
)
@@ -67,11 +66,6 @@ func buildBuilderQueriesProducerBytes(
attributeCache *Clients,
) (map[string]*v3.BuilderQuery, error) {
normalized := true
if constants.IsDotMetricsEnabled {
normalized = false
}
bq := make(map[string]*v3.BuilderQuery)
queryName := "byte_rate"
@@ -80,7 +74,7 @@ func buildBuilderQueriesProducerBytes(
StepInterval: common.MinAllowedStepInterval(unixMilliStart, unixMilliEnd),
DataSource: v3.DataSourceMetrics,
AggregateAttribute: v3.AttributeKey{
Key: getDotMetrics("kafka_producer_byte_rate", normalized),
Key: "kafka.producer.byte-rate",
DataType: v3.AttributeKeyDataTypeFloat64,
Type: v3.AttributeKeyType("Gauge"),
IsColumn: true,
@@ -94,7 +88,7 @@ func buildBuilderQueriesProducerBytes(
Items: []v3.FilterItem{
{
Key: v3.AttributeKey{
Key: getDotMetrics("service_name", normalized),
Key: "service.name",
Type: v3.AttributeKeyTypeTag,
DataType: v3.AttributeKeyDataTypeString,
},
@@ -116,7 +110,7 @@ func buildBuilderQueriesProducerBytes(
ReduceTo: v3.ReduceToOperatorAvg,
GroupBy: []v3.AttributeKey{
{
Key: getDotMetrics("service_name", normalized),
Key: "service.name",
DataType: v3.AttributeKeyDataTypeString,
Type: v3.AttributeKeyTypeTag,
},
@@ -139,17 +133,12 @@ func buildBuilderQueriesNetwork(
bq := make(map[string]*v3.BuilderQuery)
queryName := "latency"
normalized := true
if constants.IsDotMetricsEnabled {
normalized = false
}
chq := &v3.BuilderQuery{
QueryName: queryName,
StepInterval: common.MinAllowedStepInterval(unixMilliStart, unixMilliEnd),
DataSource: v3.DataSourceMetrics,
AggregateAttribute: v3.AttributeKey{
Key: getDotMetrics("kafka_consumer_fetch_latency_avg", normalized),
Key: "kafka.consumer.fetch_latency_avg",
},
AggregateOperator: v3.AggregateOperatorAvg,
Temporality: v3.Unspecified,
@@ -160,7 +149,7 @@ func buildBuilderQueriesNetwork(
Items: []v3.FilterItem{
{
Key: v3.AttributeKey{
Key: getDotMetrics("service_name", normalized),
Key: "service.name",
Type: v3.AttributeKeyTypeTag,
DataType: v3.AttributeKeyDataTypeString,
},
@@ -169,7 +158,7 @@ func buildBuilderQueriesNetwork(
},
{
Key: v3.AttributeKey{
Key: getDotMetrics("client_id", normalized),
Key: "client-id",
Type: v3.AttributeKeyTypeTag,
DataType: v3.AttributeKeyDataTypeString,
},
@@ -178,7 +167,7 @@ func buildBuilderQueriesNetwork(
},
{
Key: v3.AttributeKey{
Key: getDotMetrics("service_instance_id", normalized),
Key: "service.instance.id",
Type: v3.AttributeKeyTypeTag,
DataType: v3.AttributeKeyDataTypeString,
},
@@ -191,17 +180,17 @@ func buildBuilderQueriesNetwork(
ReduceTo: v3.ReduceToOperatorAvg,
GroupBy: []v3.AttributeKey{
{
Key: getDotMetrics("service_name", normalized),
Key: "service.name",
DataType: v3.AttributeKeyDataTypeString,
Type: v3.AttributeKeyTypeTag,
},
{
Key: getDotMetrics("client_id", normalized),
Key: "client-id",
DataType: v3.AttributeKeyDataTypeString,
Type: v3.AttributeKeyTypeTag,
},
{
Key: getDotMetrics("service_instance_id", normalized),
Key: "service.instance.id",
DataType: v3.AttributeKeyDataTypeString,
Type: v3.AttributeKeyTypeTag,
},
@@ -218,17 +207,12 @@ func BuildBuilderQueriesKafkaOnboarding(messagingQueue *MessagingQueue) (*v3.Que
unixMilliStart := messagingQueue.Start / 1000000
unixMilliEnd := messagingQueue.End / 1000000
normalized := true
if constants.IsDotMetricsEnabled {
normalized = false
}
buiderQuery := &v3.BuilderQuery{
QueryName: "fetch_latency",
StepInterval: common.MinAllowedStepInterval(unixMilliStart, unixMilliEnd),
DataSource: v3.DataSourceMetrics,
AggregateAttribute: v3.AttributeKey{
Key: getDotMetrics("kafka_consumer_fetch_latency_avg", normalized),
Key: "kafka.consumer.fetch_latency_avg",
},
AggregateOperator: v3.AggregateOperatorCount,
Temporality: v3.Unspecified,
@@ -243,7 +227,7 @@ func BuildBuilderQueriesKafkaOnboarding(messagingQueue *MessagingQueue) (*v3.Que
StepInterval: common.MinAllowedStepInterval(unixMilliStart, unixMilliEnd),
DataSource: v3.DataSourceMetrics,
AggregateAttribute: v3.AttributeKey{
Key: getDotMetrics("kafka_consumer_group_lag", normalized),
Key: "kafka.consumer_group.lag",
},
AggregateOperator: v3.AggregateOperatorCount,
Temporality: v3.Unspecified,
@@ -427,19 +411,3 @@ func buildCompositeQuery(chq *v3.ClickHouseQuery, queryContext string) (*v3.Comp
PanelType: v3.PanelTypeTable,
}, nil
}
func getDotMetrics(metricName string, normalized bool) string {
dotMetricsMap := map[string]string{
"kafka_producer_byte_rate": "kafka.producer.byte-rate",
"service_name": "service.name",
"kafka_consumer_fetch_latency_avg": "kafka.consumer.fetch_latency_avg",
"service_instance_id": "service.instance.id",
"client_id": "client-id",
"kafka_consumer_group_lag": "kafka.consumer_group.lag",
}
if _, ok := dotMetricsMap[metricName]; ok && !normalized {
return dotMetricsMap[metricName]
} else {
return metricName
}
}

View File

@@ -258,11 +258,7 @@ func PrepareTimeseriesFilterQuery(start, end int64, mq *v3.BuilderQuery) (string
conditions = append(conditions, fmt.Sprintf("metric_name IN %s", utils.ClickHouseFormattedMetricNames(mq.AggregateAttribute.Key)))
conditions = append(conditions, fmt.Sprintf("temporality = '%s'", mq.Temporality))
if constants.IsDotMetricsEnabled {
conditions = append(conditions, "__normalized = false")
} else {
conditions = append(conditions, "__normalized = true")
}
conditions = append(conditions, "__normalized = false")
start, end, tableName := whichTSTableToUse(start, end, mq)
@@ -354,11 +350,7 @@ func PrepareTimeseriesFilterQueryV3(start, end int64, mq *v3.BuilderQuery) (stri
conditions = append(conditions, fmt.Sprintf("metric_name IN %s", utils.ClickHouseFormattedMetricNames(mq.AggregateAttribute.Key)))
conditions = append(conditions, fmt.Sprintf("temporality = '%s'", mq.Temporality))
if constants.IsDotMetricsEnabled {
conditions = append(conditions, "__normalized = false")
} else {
conditions = append(conditions, "__normalized = true")
}
conditions = append(conditions, "__normalized = false")
start, end, tableName := whichTSTableToUse(start, end, mq)

View File

@@ -6,9 +6,6 @@ import (
"strings"
"sync"
"github.com/prometheus/prometheus/promql/parser"
"github.com/SigNoz/signoz/pkg/errors"
logsV4 "github.com/SigNoz/signoz/pkg/query-service/app/logs/v4"
metricsV3 "github.com/SigNoz/signoz/pkg/query-service/app/metrics/v3"
metricsV4 "github.com/SigNoz/signoz/pkg/query-service/app/metrics/v4"
@@ -278,59 +275,3 @@ func (q *querier) runBuilderQuery(
Series: resultSeries,
}
}
// ValidateMetricNames function is used to print all those queries who are still using old normalized metrics and not new metrics.
func (q *querier) ValidateMetricNames(ctx context.Context, query *v3.CompositeQuery, orgID valuer.UUID) {
var metricNames []string
switch query.QueryType {
case v3.QueryTypePromQL:
for _, query := range query.PromQueries {
expr, err := q.parser.ParseExpr(query.Query)
if err != nil {
q.logger.DebugContext(ctx, "error parsing promql expression", "query", query.Query, errors.Attr(err))
continue
}
parser.Inspect(expr, func(node parser.Node, path []parser.Node) error {
if vs, ok := node.(*parser.VectorSelector); ok {
for _, m := range vs.LabelMatchers {
if m.Name == "__name__" {
metricNames = append(metricNames, m.Value)
}
}
}
return nil
})
}
metrics, err := q.reader.GetNormalizedStatus(ctx, orgID, metricNames)
if err != nil {
q.logger.DebugContext(ctx, "error getting corresponding normalized metrics", errors.Attr(err))
return
}
for metricName, metricPresent := range metrics {
if metricPresent {
continue
} else {
q.logger.WarnContext(ctx, "using normalized metric name", "metrics", metricName)
continue
}
}
case v3.QueryTypeBuilder:
for _, query := range query.BuilderQueries {
metricName := query.AggregateAttribute.Key
metricNames = append(metricNames, metricName)
}
metrics, err := q.reader.GetNormalizedStatus(ctx, orgID, metricNames)
if err != nil {
q.logger.DebugContext(ctx, "error getting corresponding normalized metrics", errors.Attr(err))
return
}
for metricName, metricPresent := range metrics {
if metricPresent {
continue
} else {
q.logger.WarnContext(ctx, "using normalized metric name", "metrics", metricName)
continue
}
}
}
}

View File

@@ -515,9 +515,6 @@ func (q *querier) QueryRange(ctx context.Context, orgID valuer.UUID, params *v3.
var results []*v3.Result
var err error
var errQueriesByName map[string]error
if !q.testingMode && q.reader != nil {
q.ValidateMetricNames(ctx, params.CompositeQuery, orgID)
}
if params.CompositeQuery != nil {
switch params.CompositeQuery.QueryType {
case v3.QueryTypeBuilder:

View File

@@ -212,7 +212,7 @@ func TestBuildQueryWithThreeOrMoreQueriesRefAndFormula(t *testing.T) {
// So(queries["F5"], ShouldContainSubstring, "SELECT A.ts as ts, ((A.value - B.value) / B.value) * 100")
// So(strings.Count(queries["F5"], " ON "), ShouldEqual, 1)
})
t.Run("TestBuildQueryWithDotMetricNameAndAttribute", func(t *testing.T) {
t.Run("TestBuildQueryWithMetricNameAndAttribute", func(t *testing.T) {
q := &v3.QueryRangeParamsV3{
Start: 1735036101000,
End: 1735637901000,

View File

@@ -3,7 +3,6 @@ package constants
import (
"maps"
"os"
"regexp"
"strconv"
"github.com/SigNoz/signoz/pkg/query-service/model"
@@ -26,12 +25,6 @@ const OrderBySpanCount = "span_count"
var MetricsExplorerClickhouseThreads = GetOrDefaultEnvInt("METRICS_EXPLORER_CLICKHOUSE_THREADS", 8)
var UpdatedMetricsMetadataCachePrefix = GetOrDefaultEnv("METRICS_UPDATED_METADATA_CACHE_KEY", "UPDATED_METRICS_METADATA")
const NormalizedMetricsMapCacheKey = "NORMALIZED_METRICS_MAP_CACHE_KEY"
const NormalizedMetricsMapQueryThreads = 10
var NormalizedMetricsMapRegex = regexp.MustCompile(`[^a-zA-Z0-9]`)
var NormalizedMetricsMapQuantileRegex = regexp.MustCompile(`(?i)([._-]?quantile.*)$`)
func GetEvalDelay() valuer.TextDuration {
evalDelayStr := GetOrDefaultEnv("RULES_EVAL_DELAY", "2m")
evalDelayDuration, err := valuer.ParseTextDuration(evalDelayStr)
@@ -671,16 +664,11 @@ var OldToNewTraceFieldsMap = map[string]string{
var StaticFieldsTraces = map[string]v3.AttributeKey{}
var IsDotMetricsEnabled = false
var MaxJSONFlatteningDepth = 1
func init() {
StaticFieldsTraces = maps.Clone(NewStaticFieldsTraces)
maps.Copy(StaticFieldsTraces, DeprecatedStaticFieldsTraces)
if GetOrDefaultEnv(DotMetricsEnabled, "true") == "true" {
IsDotMetricsEnabled = true
}
// set max flattening depth
depth, err := strconv.Atoi(GetOrDefaultEnv(maxJSONFlatteningDepth, "1"))
if err == nil {
@@ -708,5 +696,4 @@ var MaterializedDataTypeMap = map[string]string{
const InspectMetricsMaxTimeDiff = 1800000
const DotMetricsEnabled = "DOT_METRICS_ENABLED"
const maxJSONFlatteningDepth = "MAX_JSON_FLATTENING_DEPTH"

View File

@@ -108,7 +108,6 @@ type Reader interface {
GetUpdatedMetricsMetadata(ctx context.Context, orgID valuer.UUID, metricNames ...string) (map[string]*model.UpdateMetricsMetadata, *model.ApiError)
CheckForLabelsInMetric(ctx context.Context, orgID valuer.UUID, metricName string, labels []string) (bool, *model.ApiError)
GetNormalizedStatus(ctx context.Context, orgID valuer.UUID, metricNames []string) (map[string]bool, error)
}
type Querier interface {

View File

@@ -1,27 +1,14 @@
package metrics
var MetricsUnderTransition = map[string]string{
"k8s_pod_cpu_utilization": "k8s_pod_cpu_usage",
"k8s_node_cpu_utilization": "k8s_node_cpu_usage",
"container_cpu_utilization": "container_cpu_usage",
}
var DotMetricsUnderTransition = map[string]string{
"k8s.pod.cpu.utilization": "k8s.pod.cpu.usage",
"k8s.node.cpu.utilization": "k8s.node.cpu.usage",
"container.cpu.utilization": "container.cpu.usage",
}
func GetTransitionedMetric(metric string, normalized bool) string {
if normalized {
if _, ok := MetricsUnderTransition[metric]; ok {
return MetricsUnderTransition[metric]
}
return metric
} else {
if _, ok := DotMetricsUnderTransition[metric]; ok {
return DotMetricsUnderTransition[metric]
}
return metric
func GetTransitionedMetric(metric string) string {
if transitionedMetric, ok := MetricsUnderTransition[metric]; ok {
return transitionedMetric
}
return metric
}

View File

@@ -1,15 +0,0 @@
package model
import "encoding/json"
type MetricsNormalizedMap struct {
MetricName string `json:"metricName"`
IsUnNormalized bool `json:"isUnNormalized"`
}
func (c *MetricsNormalizedMap) MarshalBinary() (data []byte, err error) {
return json.Marshal(c)
}
func (c *MetricsNormalizedMap) UnmarshalBinary(data []byte) error {
return json.Unmarshal(data, c)
}

View File

@@ -234,7 +234,7 @@ func ClickHouseFormattedValue(v interface{}) string {
func ClickHouseFormattedMetricNames(v interface{}) string {
if name, ok := v.(string); ok {
transitionedMetrics := metrics.GetTransitionedMetric(name, !constants.IsDotMetricsEnabled)
transitionedMetrics := metrics.GetTransitionedMetric(name)
if transitionedMetrics != name {
return ClickHouseFormattedValue([]interface{}{transitionedMetrics})
} else {

View File

@@ -17,6 +17,7 @@ var (
CodeClickHouseSQLNotSingleStatement = errors.MustNewCode("clickhouse_sql_not_single_statement")
CodeClickHouseSQLNotSelect = errors.MustNewCode("clickhouse_sql_not_select")
CodeClickHouseSQLTableFunction = errors.MustNewCode("clickhouse_sql_table_function")
CodeClickHouseSQLReadingFunction = errors.MustNewCode("clickhouse_sql_reading_function")
CodeClickHouseSQLInternalDatabase = errors.MustNewCode("clickhouse_sql_internal_database")
CodeClickHouseSQLReadonlyOverride = errors.MustNewCode("clickhouse_sql_readonly_override")
)
@@ -43,6 +44,25 @@ var generatorTableFunctions = map[string]string{
var generatorTableFunctionsMessage = "allowed table functions are " + strings.Join(slices.Sorted(maps.Values(generatorTableFunctions)), ", ")
// readingFunctions reach a file, a model or the server binary while looking like ordinary
// scalar functions. They name no table and no database, so neither of the rules above sees
// them, and a wrapper that returns a number leaks what they read through the row count alone:
// numbers(length(file(x))) yields one row per byte.
//
// Keyed by the lowercased name, since ClickHouse resolves function names case-insensitively.
var readingFunctions = map[string]struct{}{
"file": {},
"catboostevaluate": {},
"demangle": {},
"addresstoline": {},
"addresstolinewithinlines": {},
"addresstosymbol": {},
}
// A dictionary can be backed by HTTP, ODBC or another database, and every one of the 42
// accessors carries this prefix.
const dictionaryFunctionPrefix = "dict"
// The parser's grammar has gaps against SQL that ClickHouse itself accepts.
func ErrIfStatementIsNotValid(query string) (err error) {
defer func() {
@@ -69,11 +89,23 @@ func ErrIfStatementIsNotValid(query string) (err error) {
visitor := &chparser.DefaultASTVisitor{Visit: func(node chparser.Expr) error {
switch expr := node.(type) {
case *chparser.TableFunctionExpr:
// Source table functions remain usable in ClickHouse read-only mode. Arguments are
// visited before this, so a read smuggled into one is already refused by the time
// an allowed generator gets here.
name := chparser.Format(expr.Name)
case *chparser.TableExpr:
// Source table functions remain usable in ClickHouse read-only mode, and only a
// table position can be one. The parser also types a call inside a table function's
// argument list as a TableFunctionExpr, so asking every one of those refuses the
// numbers(intDiv(...)) that every dashboard writes. What can read from an argument
// is caught by name below instead.
source := expr.Expr
if alias, ok := source.(*chparser.AliasExpr); ok {
source = alias.Expr
}
tableFunction, ok := source.(*chparser.TableFunctionExpr)
if !ok {
return nil
}
name := functionName(tableFunction.Name)
if _, ok := generatorTableFunctions[strings.ToLower(name)]; ok {
return nil
}
@@ -82,6 +114,25 @@ func ErrIfStatementIsNotValid(query string) (err error) {
NewInvalidInputf(CodeClickHouseSQLTableFunction, "ClickHouse table functions are not allowed in SQL queries: %s", name).
WithAdditional(generatorTableFunctionsMessage)
case *chparser.FunctionExpr:
return errIfFunctionReads(expr.Name.Name)
case *chparser.TableFunctionExpr:
// Reached for a call in an argument list, and for a table position ahead of the
// TableExpr above, since a node is visited after its children.
return errIfFunctionReads(functionName(expr.Name))
case *chparser.Path:
// ClickHouse reads `x IN db.table` as a select from that table, and a qualified name
// on the right of IN is a Path rather than a TableIdentifier.
if len(expr.Fields) < 2 {
return nil
}
if _, ok := internalDatabases[strings.ToLower(expr.Fields[0].Name)]; ok {
return errors.NewInvalidInputf(CodeClickHouseSQLInternalDatabase, "the ClickHouse %s database is not allowed in SQL queries", expr.Fields[0].Name)
}
case *chparser.TableIdentifier:
// Reading these is unaffected by ClickHouse read-only mode.
if expr.Database == nil {
@@ -111,3 +162,22 @@ func LogIfStatementIsNotValid(ctx context.Context, logger *slog.Logger, query st
logger.WarnContext(ctx, "clickhouse sql is not valid", errors.Attr(err), slog.String("query", query))
}
}
func errIfFunctionReads(name string) error {
lowered := strings.ToLower(name)
if _, ok := readingFunctions[lowered]; !ok && !strings.HasPrefix(lowered, dictionaryFunctionPrefix) {
return nil
}
return errors.NewInvalidInputf(CodeClickHouseSQLReadingFunction, "ClickHouse functions that read outside the telemetry tables are not allowed in SQL queries: %s", name)
}
// The parser spells a call's name as an Ident everywhere it can. Reading the field rather than
// formatting the node keeps the quoting out, so `numbers`(1) matches numbers.
func functionName(expr chparser.Expr) string {
if ident, ok := expr.(*chparser.Ident); ok {
return ident.Name
}
return chparser.Format(expr)
}

View File

@@ -7,6 +7,7 @@ import (
"github.com/SigNoz/signoz/pkg/errors"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestErrIfStatementIsNotValid_Pass(t *testing.T) {
@@ -14,13 +15,12 @@ func TestErrIfStatementIsNotValid_Pass(t *testing.T) {
name string
query string
}{
// Shapes a telemetry read is allowed to take.
{"Select", "SELECT region AS r, zone FROM metrics WHERE metric_name = 'cpu' GROUP BY region, zone"},
{"TrailingSemicolon", "SELECT count() FROM signoz_logs.distributed_logs_v2;"},
{"CommonTableExpression", "WITH t AS (SELECT fingerprint FROM signoz_metrics.time_series_v4) SELECT * FROM t"},
{"Join", "SELECT * FROM t1 LEFT JOIN t2 ON t1.a = t2.b"},
{"GlobalIn", "SELECT a FROM t WHERE a GLOBAL IN (SELECT b FROM t2)"},
// GLOBAL parsed only when the join type was omitted, and only before IN. https://github.com/AfterShip/clickhouse-sql-parser/pull/293
// https://github.com/AfterShip/clickhouse-sql-parser/pull/293
{"GlobalLeftJoin", "SELECT * FROM t1 GLOBAL LEFT JOIN t2 ON t1.a = t2.a"},
{"GlobalNotIn", "SELECT a FROM t WHERE a GLOBAL NOT IN (SELECT b FROM t2)"},
{"Union", "SELECT * FROM t UNION ALL SELECT * FROM t2"},
@@ -29,32 +29,34 @@ func TestErrIfStatementIsNotValid_Pass(t *testing.T) {
{"UnrelatedSetting", "SELECT * FROM t SETTINGS max_threads = 4"},
{"TerminatedBlockComment", "SELECT /* keep me */ count() FROM t"},
{"BlockCommentMarkerInsideStringLiteral", "SELECT count() FROM t WHERE body = '/* not a comment'"},
// The parser used to loop forever on this; it now reads the comment to the end of
// the input, so this doubles as a canary for that regression.
// Looped forever before v0.5.2.
{"TrailingUnterminatedBlockComment", "SELECT count() FROM t /* unterminated"},
// The rule keys on the database, not on the table name.
// Keyed on the database, not on the table name.
{"TableNamedSystemInTelemetryDatabase", "SELECT * FROM signoz_logs.system"},
{"SignedLiteralAfterClosingParenSpaced", "SELECT (toUnixTimestamp(now()) - 3600)*1000000000"},
// order by interval
{"OrderByInterval", "SELECT toStartOfInterval(timestamp, INTERVAL 1 MINUTE) AS interval ORDER BY interval"},
{"OrderByIntervalAndDirection", "SELECT toStartOfInterval(timestamp, INTERVAL 1 MINUTE) AS `interval` ORDER BY `interval` ASC"},
// `interval` is a unit keyword, so unquoting it was rejected everywhere the parser
// expected a plain identifier. https://github.com/AfterShip/clickhouse-sql-parser/pull/296
// https://github.com/AfterShip/clickhouse-sql-parser/pull/296
{"OrderByUnquotedIntervalAsc", "SELECT toStartOfInterval(timestamp, INTERVAL 1 MINUTE) AS interval FROM t GROUP BY interval ORDER BY interval ASC"},
{"OrderByUnquotedIntervalDesc", "SELECT toStartOfInterval(timestamp, INTERVAL 1 MINUTE) AS interval FROM t GROUP BY interval ORDER BY interval DESC"},
{"UnquotedIntervalInGroupByTuple", "SELECT a FROM t GROUP BY (`service.name`, `service.version`, interval)"},
{"UnquotedIntervalProductionQuery", "SELECT toStartOfInterval(timestamp, INTERVAL 1 MINUTE) AS interval, resource_string_service$$name AS `service.name`, attributes_string['http.route'] AS `http.route`, quantile(0.95)(duration_nano) / 1000000000 AS value FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_string_service$$name = 'svc-a' AND resources_string['deployment.environment'] = 'dev' AND attributes_string['http.route'] = '/v1' AND http_method = 'POST' AND timestamp BETWEEN toDateTime(1784601720) AND toDateTime(1784602620) AND ts_bucket_start BETWEEN 1784601720 - 1800 AND 1784602620 GROUP BY `service.name`, `http.route`, interval ORDER BY interval ASC"},
// Separating the two readings of INTERVAL needs backtracking as per the current implementation which could have performance regressions.
// https://github.com/AfterShip/clickhouse-sql-parser/pull/296#issuecomment-5150316367
// The fix backtracks, so this bounds the cost. https://github.com/AfterShip/clickhouse-sql-parser/pull/296#issuecomment-5150316367
{"UnquotedIntervalRepeatedThirtyTimes", "SELECT interval + interval + interval + interval + interval + interval + interval + interval + interval + interval + interval + interval + interval + interval + interval + interval + interval + interval + interval + interval + interval + interval + interval + interval + interval + interval + interval + interval + interval + interval AS total FROM t WHERE interval > 0 ORDER BY interval ASC"},
// `interval` was one of 37 such keywords. https://github.com/AfterShip/clickhouse-sql-parser/pull/305
{"UnquotedLimitInFunctionArgument", "SELECT sum(limit) FROM t"},
{"UnquotedLimitInArithmetic", "SELECT limit + 1 FROM t"},
{"UnquotedLimitInNegation", "SELECT abs(-limit) FROM t"},
{"UnquotedKeywordOperands", "SELECT sum(offset) + sum(format) + sum(settings) FROM t"},
{"UnquotedLimitProductionQuery", "WITH limit_value AS (SELECT cluster, region, value AS limit FROM t) SELECT region AS `Region`, sum(limit) AS `Capacity` FROM limit_value GROUP BY Region"},
{"SignedLiteralAfterClosingParenUnspaced", "SELECT now() AS ts, toFloat64(count()) AS value FROM ( SELECT attributes_string['TableName'] AS T, attributes_string['MissingId'] AS M, max(fromUnixTimestamp64Nano(timestamp)) AS last_seen, dateDiff('minute', min(fromUnixTimestamp64Nano(timestamp)), max(fromUnixTimestamp64Nano(timestamp))) AS age_min FROM signoz_logs.distributed_logs_v2 WHERE body='missing_map_record' AND timestamp >= (toUnixTimestamp(now())-3600)*1000000000 GROUP BY T, M ) WHERE age_min >= 20 AND last_seen >= now() - toIntervalMinute(8)"},
{"SignedLiteralAfterClosingParenMinimal", "SELECT (1)-1"},
{"TrimFunction", "SELECT trimBoth('/api/endpoint/', '/');"},
// The SQL-standard keyword-separated argument forms, which took commas only. https://github.com/AfterShip/clickhouse-sql-parser/pull/290
// https://github.com/AfterShip/clickhouse-sql-parser/pull/290
{"StandardTrimSyntax", "SELECT trim(BOTH ' ' FROM body) FROM t"},
{"StandardSubstringSyntax", "SELECT substring(body FROM 2 FOR 3) FROM t"},
{"StandardOverlaySyntax", "SELECT overlay(body PLACING 'x' FROM 2) FROM t"},
// Row generators compute their rows from their arguments, so they read through nothing. This is the shape they get used for: a dense interval axis to CROSS JOIN a sparse series against.
// The shape row generators get used for: a dense interval axis to CROSS JOIN a sparse series against.
{"NumbersTableFunction", "SELECT intervals.interval AS interval, active.cluster AS cluster, toFloat64(if(ts_data.has_data = 0, 0, 1)) AS value FROM ( SELECT DISTINCT JSONExtractString(labels, 'k8s.cluster.name') AS cluster FROM signoz_metrics.distributed_time_series_v4 WHERE metric_name = 'my_metric' AND unix_milli >= toUnixTimestamp(now() - INTERVAL 30 DAY) * 1000 HAVING cluster != '' ) AS active CROSS JOIN ( SELECT toStartOfInterval( toDateTime(toUnixTimestamp(now() - INTERVAL 30 MINUTE) + number * 60), INTERVAL 1 MINUTE ) AS interval FROM numbers(31) ) AS intervals LEFT JOIN ( SELECT toStartOfInterval( toDateTime(intDiv(s.unix_milli, 1000)), INTERVAL 1 MINUTE ) AS interval, JSONExtractString(ts.labels, 'k8s.cluster.name') AS cluster, 1 AS has_data FROM signoz_metrics.distributed_samples_v4 s INNER JOIN ( SELECT DISTINCT fingerprint, labels FROM signoz_metrics.distributed_time_series_v4 WHERE metric_name = 'my_metric' ) AS ts ON s.fingerprint = ts.fingerprint WHERE s.metric_name = 'my_metric' AND s.unix_milli >= toUnixTimestamp(now() - INTERVAL 30 MINUTE) * 1000 GROUP BY interval, cluster ) AS ts_data ON active.cluster = ts_data.cluster AND intervals.interval = ts_data.interval ORDER BY interval ASC"},
{"NumbersMtTableFunction", "SELECT * FROM numbers_mt(31)"},
{"ZerosTableFunction", "SELECT * FROM zeros(31)"},
@@ -63,6 +65,16 @@ func TestErrIfStatementIsNotValid_Pass(t *testing.T) {
{"GenerateSeriesSnakeCaseTableFunction", "SELECT * FROM generate_series(1, 10)"},
{"GeneratorTableFunctionUppercase", "SELECT * FROM NUMBERS(31)"},
{"GeneratorTableFunctionParenthesisedArgument", "SELECT * FROM NUMBERS((31))"},
// CAST in an argument was itself read as a table function. https://github.com/AfterShip/clickhouse-sql-parser/pull/307
{"CastInGeneratorTableFunctionArgument", "SELECT * FROM numbers(CAST(10 AS UInt64))"},
{"ScalarCallInGeneratorTableFunctionArgument", "SELECT * FROM numbers(intDiv(100, 2))"},
{"NestedScalarCallInGeneratorTableFunctionArgument", "SELECT * FROM numbers(greatest(1, intDiv(100, 2) + 1))"},
{"GeneratorTableFunctionProductionQuery", "WITH toInt64(1786029960000000000) AS start_ns, toInt64(1786031760000000000) AS end_ns, 300000000000 AS step_ns SELECT ts, toFloat64(sum(value)) AS value FROM (SELECT fromUnixTimestamp64Nano(start_ns + toInt64(number) * step_ns) AS ts, 0 AS value FROM numbers(greatest(1, intDiv(end_ns - start_ns, step_ns) + 1)) UNION ALL SELECT toStartOfInterval(fromUnixTimestamp64Nano(timestamp), INTERVAL 5 minute) AS ts, count() AS value FROM signoz_logs.distributed_logs_v2 WHERE timestamp >= 1786029960000000000 AND timestamp <= 1786031760000000000 GROUP BY ts) GROUP BY ts ORDER BY ts"},
// The allow list keys on the bare name, so quoting must not hide a generator from it.
{"BacktickQuotedGeneratorTableFunction", "SELECT * FROM `numbers`(31)"},
{"DoubleQuotedGeneratorTableFunction", "SELECT * FROM \"numbers\"(31)"},
// Reads nothing: format builds a string, and shares its name with a table function.
{"ScalarFunctionNamedAfterATableFunction", "SELECT format('{} {}', a, b) FROM t"},
{"GeneratorTableFunctionInJoin", "SELECT * FROM signoz_logs.distributed_logs_v2 AS l CROSS JOIN numbers(31) AS n"},
{"GeneratorTableFunctionInCommonTableExpression", "WITH axis AS (SELECT number FROM numbers(31)) SELECT * FROM axis"},
{"GeneratorTableFunctionInWhereSubquery", "SELECT * FROM t WHERE a IN (SELECT number FROM numbers(31))"},
@@ -71,8 +83,7 @@ func TestErrIfStatementIsNotValid_Pass(t *testing.T) {
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
// Bounded rather than called directly: a parser that backtracks without memoising
// hangs instead of returning. Every case here parses in well under a millisecond.
// Bounded because a parser that backtracks without memoising hangs rather than returning.
errC := make(chan error, 1)
go func() { errC <- ErrIfStatementIsNotValid(testCase.query) }()
@@ -92,46 +103,57 @@ func TestErrIfStatementIsNotValid_Fail(t *testing.T) {
query string
expectedCode errors.Code
}{
// Not a single statement, or not a statement at all.
{"Empty", "", CodeClickHouseSQLNotSingleStatement},
{"UnterminatedBlockCommentOnly", "/* x", CodeClickHouseSQLUnparseable},
{"Unparseable", "SELECT FROM WHERE", CodeClickHouseSQLUnparseable},
{"MultipleStatements", "SELECT 1; DROP TABLE signoz_logs.logs_v2", CodeClickHouseSQLNotSingleStatement},
// Parses, but is not a SELECT.
{"Drop", "DROP TABLE signoz_logs.logs_v2", CodeClickHouseSQLNotSelect},
{"Insert", "INSERT INTO signoz_logs.logs_v2 SELECT * FROM signoz_logs.logs_v2", CodeClickHouseSQLNotSelect},
{"AlterDelete", "ALTER TABLE signoz_logs.logs_v2 DELETE WHERE 1 = 1", CodeClickHouseSQLNotSelect},
{"CreateTable", "CREATE TABLE evil (a Int) ENGINE = Memory", CodeClickHouseSQLNotSelect},
{"Grant", "GRANT ALL ON *.* TO admin", CodeClickHouseSQLNotSelect},
{"Set", "SET readonly = 0", CodeClickHouseSQLNotSelect},
// The parser still dereferences nil on a DEFAULT expression it cannot read, so the recover is what turns this into a rejection rather than a crash.
{"UnparseableDefaultExpression", "CREATE TABLE t (a String DEFAULT foo(b FROM 2)) ENGINE = Memory", CodeClickHouseSQLParserPanic},
// These the parser rejects outright rather than classifying.
// Both panicked before v0.5.5. https://github.com/AfterShip/clickhouse-sql-parser/pull/306
{"UnparseableDefaultExpression", "CREATE TABLE t (a String DEFAULT foo(b FROM 2)) ENGINE = Memory", CodeClickHouseSQLUnparseable},
{"TrailingOperatorInDefaultExpression", "CREATE TABLE t (a String DEFAULT 1 +) ENGINE = Memory", CodeClickHouseSQLUnparseable},
// Rejected outright rather than classified.
{"ShowGrants", "SHOW GRANTS", CodeClickHouseSQLUnparseable},
{"IntoOutfile", "SELECT * FROM t INTO OUTFILE '/tmp/x.csv'", CodeClickHouseSQLUnparseable},
// Table functions, which read through something other than a telemetry table.
{"UrlTableFunction", "SELECT * FROM url('http://attacker.example/x', CSV, 'a String')", CodeClickHouseSQLTableFunction},
{"FileTableFunction", "SELECT * FROM file('/etc/passwd', CSV, 'a String')", CodeClickHouseSQLTableFunction},
// file is also a scalar function, so the reading rule reaches it before the table rule does.
{"FileTableFunction", "SELECT * FROM file('/etc/passwd', CSV, 'a String')", CodeClickHouseSQLReadingFunction},
{"ExecutableTableFunction", "SELECT * FROM executable('script.sh', CSV, 'a String')", CodeClickHouseSQLTableFunction},
{"TableFunctionInJoin", "SELECT * FROM t1 JOIN url('http://x', CSV, 'a String') u ON 1 = 1", CodeClickHouseSQLTableFunction},
{"TableFunctionInCommonTableExpression", "WITH c AS (SELECT * FROM url('http://x', CSV, 'a String')) SELECT * FROM c", CodeClickHouseSQLTableFunction},
{"TableFunctionInWhereSubquery", "SELECT * FROM t WHERE a IN (SELECT * FROM file('/etc/passwd', CSV, 'a String'))", CodeClickHouseSQLTableFunction},
{"TableFunctionInWhereSubquery", "SELECT * FROM t WHERE a IN (SELECT * FROM url('http://x', CSV, 'a String'))", CodeClickHouseSQLTableFunction},
{"TableFunctionInUnion", "SELECT * FROM t UNION ALL SELECT * FROM url('http://x', CSV, 'a String')", CodeClickHouseSQLTableFunction},
// These reach the internal databases without ever naming one, so the table-function rule is the only thing that sees them.
// Reach an internal database without naming one, so only the table-function rule sees them.
{"MergeTableFunction", "SELECT * FROM merge('system', '.*')", CodeClickHouseSQLTableFunction},
{"RemoteTableFunction", "SELECT * FROM remote('other-host', 'system.users')", CodeClickHouseSQLTableFunction},
{"ClusterTableFunction", "SELECT * FROM cluster('c', 'system.users')", CodeClickHouseSQLTableFunction},
// Pure, but excluded: generateRandom streams rows the arguments do not bound, and values has no use here that an array literal does not already cover.
// Pure, but excluded: generateRandom is unbounded, and values adds nothing over an array literal.
{"GenerateRandomTableFunction", "SELECT * FROM generateRandom('a UInt64')", CodeClickHouseSQLTableFunction},
{"ValuesTableFunction", "SELECT * FROM values('a UInt64', 1, 2)", CodeClickHouseSQLTableFunction},
// Arguments are visited before the table function itself, so allowing a generator does not give anyone a wrapper to smuggle a read through.
// Arguments are visited first, so an allowed generator is not a wrapper to smuggle a read through.
{"InternalDatabaseInsideAllowedTableFunction", "SELECT * FROM numbers((SELECT count() FROM system.users))", CodeClickHouseSQLInternalDatabase},
{"InternalDatabaseJoinedOntoAllowedTableFunction", "SELECT * FROM numbers(31) AS n JOIN system.users AS u ON 1 = 1", CodeClickHouseSQLInternalDatabase},
{"InternalDatabaseUnionedWithAllowedTableFunction", "SELECT number FROM numbers(31) UNION ALL SELECT name FROM system.users", CodeClickHouseSQLInternalDatabase},
{"RefusedTableFunctionJoinedOntoAllowedTableFunction", "SELECT * FROM numbers(31) AS n JOIN url('http://x', CSV, 'a String') AS u ON 1 = 1", CodeClickHouseSQLTableFunction},
{"RefusedTableFunctionInsideAllowedTableFunction", "SELECT * FROM numbers((SELECT count() FROM file('/etc/passwd', CSV, 'a String')))", CodeClickHouseSQLTableFunction},
{"RefusedTableFunctionInsideAllowedTableFunction", "SELECT * FROM numbers((SELECT count() FROM url('http://x', CSV, 'a String')))", CodeClickHouseSQLTableFunction},
{"InternalDatabaseInsideAllowedTableFunctionCommonTableExpression", "WITH axis AS (SELECT * FROM numbers((SELECT count() FROM system.users))) SELECT * FROM axis", CodeClickHouseSQLInternalDatabase},
// Internal databases, which hold grants and server metadata rather than telemetry.
// Read a file, a dictionary or the server binary without naming a table, so neither the table rule nor the database rule sees them. The row count alone is an oracle: numbers(length(file(x))) returns one row per byte.
{"ScalarFileFunction", "SELECT file('/etc/passwd')", CodeClickHouseSQLReadingFunction},
{"ScalarFileFunctionInWhere", "SELECT * FROM t WHERE length(file('/etc/passwd')) > 0", CodeClickHouseSQLReadingFunction},
{"ScalarFileFunctionInGeneratorTableFunctionArgument", "SELECT * FROM numbers(length(file('/etc/passwd')))", CodeClickHouseSQLReadingFunction},
{"DictionaryFunction", "SELECT dictGetUInt64('d', 'k', toUInt64(1))", CodeClickHouseSQLReadingFunction},
{"DictionaryFunctionUppercase", "SELECT DICTGETSTRING('d', 'k', toUInt64(1))", CodeClickHouseSQLReadingFunction},
{"DictionaryFunctionInGeneratorTableFunctionArgument", "SELECT * FROM numbers(dictGetUInt64('d', 'k', toUInt64(1)))", CodeClickHouseSQLReadingFunction},
{"IntrospectionFunction", "SELECT demangle(addressToSymbol(toUInt64(1)))", CodeClickHouseSQLReadingFunction},
{"ModelEvaluationFunction", "SELECT catboostEvaluate('/model.bin', 1)", CodeClickHouseSQLReadingFunction},
// ClickHouse reads `x IN table` as `x IN (SELECT * FROM table)`, and a qualified name there is a Path rather than a TableIdentifier.
{"InternalDatabaseInInOperator", "SELECT * FROM t WHERE a IN system.users", CodeClickHouseSQLInternalDatabase},
{"InternalDatabaseInGlobalInOperator", "SELECT * FROM t WHERE a GLOBAL IN system.users", CodeClickHouseSQLInternalDatabase},
{"InternalDatabaseInNotInOperator", "SELECT * FROM t WHERE a NOT IN system.users", CodeClickHouseSQLInternalDatabase},
{"SystemUsers", "SELECT * FROM system.users", CodeClickHouseSQLInternalDatabase},
{"SystemUppercase", "SELECT * FROM SYSTEM.USERS", CodeClickHouseSQLInternalDatabase},
{"SystemQuoted", "SELECT count() FROM `system`.`tables`", CodeClickHouseSQLInternalDatabase},
@@ -139,7 +161,7 @@ func TestErrIfStatementIsNotValid_Fail(t *testing.T) {
{"SystemInJoin", "SELECT * FROM signoz_logs.distributed_logs_v2 AS l JOIN system.users AS u ON 1 = 1", CodeClickHouseSQLInternalDatabase},
{"SystemInIntersect", "SELECT * FROM t INTERSECT SELECT * FROM system.users", CodeClickHouseSQLInternalDatabase},
{"InformationSchema", "SELECT * FROM information_schema.tables", CodeClickHouseSQLInternalDatabase},
// A query-level setting takes precedence over the one the caller applies.
// Takes precedence over the setting the caller applies.
{"ReadonlySettingOverride", "SELECT * FROM t SETTINGS readonly = 0", CodeClickHouseSQLReadonlyOverride},
{"ReadonlySettingOverrideAmongOthers", "SELECT * FROM t SETTINGS max_threads = 4, readonly = 0", CodeClickHouseSQLReadonlyOverride},
}
@@ -148,7 +170,33 @@ func TestErrIfStatementIsNotValid_Fail(t *testing.T) {
t.Run(testCase.name, func(t *testing.T) {
err := ErrIfStatementIsNotValid(testCase.query)
assert.Error(t, err)
// Required rather than asserted: errors.Asc dereferences the error it is given.
require.Error(t, err)
assert.True(t, errors.Asc(err, testCase.expectedCode), "expected code %s, got %v", testCase.expectedCode, err)
})
}
}
func TestErrIfStatementIsNotValid_ShouldPassButFails(t *testing.T) {
testCases := []struct {
name string
query string
expectedCode errors.Code
}{
// The left operand commits the parser to a subquery, leaving the operator nowhere to bind. Parenthesising only the right operand is fine.
{"ParenthesisedUnionLeftOperand", "SELECT a FROM ((SELECT 1 AS a) UNION ALL (SELECT 2 AS a))", CodeClickHouseSQLUnparseable},
{"ParenthesisedExceptLeftOperand", "SELECT a FROM ((SELECT 1 AS a) EXCEPT (SELECT 2 AS a))", CodeClickHouseSQLUnparseable},
{"ParenthesisedUnionLeftOperandAtStatementLevel", "(SELECT 1 AS a) UNION ALL (SELECT 2 AS a)", CodeClickHouseSQLUnparseable},
// The one keyword PR 305 left behind, because ON also opens a join condition.
{"UnquotedOnAsColumnName", "SELECT on + 1 FROM t", CodeClickHouseSQLUnparseable},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
err := ErrIfStatementIsNotValid(testCase.query)
// Required rather than asserted: errors.Asc dereferences the error it is given.
require.Error(t, err)
assert.True(t, errors.Asc(err, testCase.expectedCode), "expected code %s, got %v", testCase.expectedCode, err)
})
}

View File

@@ -0,0 +1,22 @@
// Code generated by scripts/semconv. DO NOT EDIT.
package semconv
var families = []Family{
{
Current: "db.system.name",
Old: []string{"db.system"},
Kind: KindAttribute,
Contexts: nil,
Signals: nil,
ApplyToMetrics: nil,
},
{
Current: "deployment.environment.name",
Old: []string{"deployment.environment"},
Kind: KindAttribute,
Contexts: nil,
Signals: nil,
ApplyToMetrics: nil,
},
}

127
pkg/semconv/semconv.go Normal file
View File

@@ -0,0 +1,127 @@
package semconv
import (
"slices"
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
"github.com/SigNoz/signoz/pkg/valuer"
)
//go:generate go run ../../scripts/semconv
// Kind identifies whether a family describes an attribute or a metric name.
type Kind struct {
valuer.String
}
// Family is one logical telemetry field. Old is ordered from the most recent
// predecessor to the oldest one and therefore also defines fallback order.
type Family struct {
Current string
Old []string
Kind Kind
Contexts []telemetrytypes.FieldContext
Signals []telemetrytypes.Signal
ApplyToMetrics []string
ValueMap map[string]string
}
var (
KindAttribute = Kind{String: valuer.NewString("attribute")}
KindMetric = Kind{String: valuer.NewString("metric")}
)
var memberToFamilies, familyMembers = buildIndexes()
// Enum returns the acceptable values for Kind.
func (Kind) Enum() []any {
return []any{KindAttribute, KindMetric}
}
// Lookup returns the enabled family containing selector.Name for kind. The
// returned family must not be modified.
func Lookup(kind Kind, selector telemetrytypes.FieldKeySelector) (Family, bool) {
idx, ok := lookupIndex(kind, selector)
if !ok {
return Family{}, false
}
return families[idx], true
}
// Members returns the current name first, followed by historical names in
// fallback order. A name outside an enabled family is returned unchanged. The
// returned slice must not be modified.
func Members(kind Kind, selector telemetrytypes.FieldKeySelector) []string {
idx, ok := lookupIndex(kind, selector)
if !ok {
return []string{selector.Name}
}
return familyMembers[idx]
}
// Current returns the current name for selector.Name, or the input name when
// it does not belong to an enabled family.
func Current(kind Kind, selector telemetrytypes.FieldKeySelector) string {
idx, ok := lookupIndex(kind, selector)
if !ok {
return selector.Name
}
return families[idx].Current
}
// All returns every enabled family. The returned slice and families must not be
// modified.
func All() []Family {
return families
}
func buildIndexes() (map[string][]int, [][]string) {
index := make(map[string][]int)
members := make([][]string, len(families))
for i, family := range families {
members[i] = make([]string, 0, len(family.Old)+1)
members[i] = append(members[i], family.Current)
members[i] = append(members[i], family.Old...)
index[family.Current] = append(index[family.Current], i)
for _, old := range family.Old {
index[old] = append(index[old], i)
}
}
return index, members
}
func lookupIndex(kind Kind, selector telemetrytypes.FieldKeySelector) (int, bool) {
for _, idx := range memberToFamilies[selector.Name] {
if matchesSelector(families[idx], kind, selector) {
return idx, true
}
}
return 0, false
}
func matchesSelector(family Family, kind Kind, selector telemetrytypes.FieldKeySelector) bool {
if family.Kind != kind {
return false
}
if selector.Signal != telemetrytypes.SignalUnspecified && len(family.Signals) > 0 {
if !slices.Contains(family.Signals, selector.Signal) {
return false
}
}
if selector.FieldContext != telemetrytypes.FieldContextUnspecified && len(family.Contexts) > 0 {
if !slices.Contains(family.Contexts, selector.FieldContext) {
return false
}
}
if selector.Signal == telemetrytypes.SignalMetrics && len(family.ApplyToMetrics) > 0 {
if selector.MetricContext == nil {
return false
}
return slices.Contains(family.ApplyToMetrics, selector.MetricContext.MetricName)
}
return true
}

View File

@@ -0,0 +1,80 @@
package semconv
import (
"testing"
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
"github.com/stretchr/testify/assert"
)
func TestMembersReturnsCurrentBeforeHistoricalName(t *testing.T) {
selector := telemetrytypes.FieldKeySelector{
Name: "deployment.environment",
Signal: telemetrytypes.SignalTraces,
FieldContext: telemetrytypes.FieldContextResource,
}
assert.Equal(t,
[]string{"deployment.environment.name", "deployment.environment"},
Members(KindAttribute, selector),
"members should use current-first fallback order",
)
}
func TestCurrentReturnsCanonicalName(t *testing.T) {
selector := telemetrytypes.FieldKeySelector{
Name: "deployment.environment",
Signal: telemetrytypes.SignalTraces,
FieldContext: telemetrytypes.FieldContextResource,
}
assert.Equal(t,
"deployment.environment.name",
Current(KindAttribute, selector),
"historical name should resolve to the current family name",
)
}
func TestAllScopedFamilyMatchesSupportedScopes(t *testing.T) {
tests := []struct {
name string
signal telemetrytypes.Signal
fieldContext telemetrytypes.FieldContext
}{
{name: "trace resource", signal: telemetrytypes.SignalTraces, fieldContext: telemetrytypes.FieldContextResource},
{name: "trace attribute", signal: telemetrytypes.SignalTraces, fieldContext: telemetrytypes.FieldContextAttribute},
{name: "log resource", signal: telemetrytypes.SignalLogs, fieldContext: telemetrytypes.FieldContextResource},
{name: "log attribute", signal: telemetrytypes.SignalLogs, fieldContext: telemetrytypes.FieldContextAttribute},
{name: "metric resource", signal: telemetrytypes.SignalMetrics, fieldContext: telemetrytypes.FieldContextResource},
{name: "metric attribute", signal: telemetrytypes.SignalMetrics, fieldContext: telemetrytypes.FieldContextAttribute},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
selector := telemetrytypes.FieldKeySelector{
Name: "deployment.environment",
Signal: test.signal,
FieldContext: test.fieldContext,
}
assert.Equal(t,
"deployment.environment.name",
Current(KindAttribute, selector),
"an all-scoped family should match every supported signal and attribute context",
)
})
}
}
func TestMembersReturnsInputWhenKindDoesNotMatch(t *testing.T) {
selector := telemetrytypes.FieldKeySelector{
Name: "deployment.environment",
Signal: telemetrytypes.SignalTraces,
}
assert.Equal(t,
[]string{"deployment.environment"},
Members(KindMetric, selector),
"an attribute family must not match a metric-name lookup",
)
}

Some files were not shown because too many files have changed in this diff Show More