mirror of
https://github.com/SigNoz/signoz.git
synced 2026-04-24 04:40:29 +01:00
Compare commits
18 Commits
issue_4360
...
test/body-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e40547db73 | ||
|
|
603adc557e | ||
|
|
2a8b2a3ea2 | ||
|
|
9192703a0a | ||
|
|
36337d60f0 | ||
|
|
82a471346d | ||
|
|
aeadeacc70 | ||
|
|
6996d41b01 | ||
|
|
6ac41b7483 | ||
|
|
f62024ad3f | ||
|
|
9689408a99 | ||
|
|
2d918c95e0 | ||
|
|
efb415960c | ||
|
|
412b497bdf | ||
|
|
67d95e1d1e | ||
|
|
57cccba19f | ||
|
|
c5c2525ff0 | ||
|
|
1e4f865a15 |
21
.github/workflows/e2eci.yaml
vendored
21
.github/workflows/e2eci.yaml
vendored
@@ -9,6 +9,27 @@ on:
|
||||
- labeled
|
||||
|
||||
jobs:
|
||||
fmtlint:
|
||||
if: |
|
||||
((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'))) && contains(github.event.pull_request.labels.*.name, 'safe-to-e2e')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: lts/*
|
||||
- name: install
|
||||
run: |
|
||||
cd tests/e2e && yarn install --frozen-lockfile
|
||||
- name: fmt
|
||||
run: |
|
||||
cd tests/e2e && yarn fmt:check
|
||||
- name: lint
|
||||
run: |
|
||||
cd tests/e2e && yarn lint
|
||||
test:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
|
||||
10
Makefile
10
Makefile
@@ -201,14 +201,12 @@ docker-buildx-enterprise: go-build-enterprise js-build
|
||||
# python commands
|
||||
##############################################################
|
||||
.PHONY: py-fmt
|
||||
py-fmt: ## Run black across the shared tests project
|
||||
@cd tests && uv run black .
|
||||
py-fmt: ## Run ruff format across the shared tests project
|
||||
@cd tests && uv run ruff format .
|
||||
|
||||
.PHONY: py-lint
|
||||
py-lint: ## Run lint across the shared tests project
|
||||
@cd tests && uv run isort .
|
||||
@cd tests && uv run autoflake .
|
||||
@cd tests && uv run pylint .
|
||||
py-lint: ## Run ruff check across the shared tests project
|
||||
@cd tests && uv run ruff check --fix .
|
||||
|
||||
.PHONY: py-test-setup
|
||||
py-test-setup: ## Bring up the shared SigNoz backend used by integration and e2e tests
|
||||
|
||||
@@ -2406,155 +2406,6 @@ components:
|
||||
- list
|
||||
- grouped_list
|
||||
type: string
|
||||
LlmpricingruletypesGettablePricingRules:
|
||||
properties:
|
||||
items:
|
||||
items:
|
||||
$ref: '#/components/schemas/LlmpricingruletypesLLMPricingRule'
|
||||
nullable: true
|
||||
type: array
|
||||
limit:
|
||||
type: integer
|
||||
offset:
|
||||
type: integer
|
||||
total:
|
||||
type: integer
|
||||
required:
|
||||
- items
|
||||
- total
|
||||
- offset
|
||||
- limit
|
||||
type: object
|
||||
LlmpricingruletypesLLMPricingRule:
|
||||
properties:
|
||||
cacheMode:
|
||||
$ref: '#/components/schemas/LlmpricingruletypesLLMPricingRuleCacheMode'
|
||||
costCacheRead:
|
||||
format: double
|
||||
type: number
|
||||
costCacheWrite:
|
||||
format: double
|
||||
type: number
|
||||
costInput:
|
||||
format: double
|
||||
type: number
|
||||
costOutput:
|
||||
format: double
|
||||
type: number
|
||||
createdAt:
|
||||
format: date-time
|
||||
type: string
|
||||
createdBy:
|
||||
type: string
|
||||
enabled:
|
||||
type: boolean
|
||||
id:
|
||||
type: string
|
||||
isOverride:
|
||||
type: boolean
|
||||
modelName:
|
||||
type: string
|
||||
modelPattern:
|
||||
items:
|
||||
type: string
|
||||
nullable: true
|
||||
type: array
|
||||
orgId:
|
||||
type: string
|
||||
sourceId:
|
||||
type: string
|
||||
syncedAt:
|
||||
format: date-time
|
||||
nullable: true
|
||||
type: string
|
||||
unit:
|
||||
$ref: '#/components/schemas/LlmpricingruletypesLLMPricingRuleUnit'
|
||||
updatedAt:
|
||||
format: date-time
|
||||
type: string
|
||||
updatedBy:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
- orgId
|
||||
- modelName
|
||||
- modelPattern
|
||||
- unit
|
||||
- cacheMode
|
||||
- costInput
|
||||
- costOutput
|
||||
- costCacheRead
|
||||
- costCacheWrite
|
||||
- isOverride
|
||||
- enabled
|
||||
type: object
|
||||
LlmpricingruletypesLLMPricingRuleCacheMode:
|
||||
enum:
|
||||
- subtract
|
||||
- additive
|
||||
- unknown
|
||||
type: string
|
||||
LlmpricingruletypesLLMPricingRuleUnit:
|
||||
enum:
|
||||
- per_million_tokens
|
||||
type: string
|
||||
LlmpricingruletypesUpdatableLLMPricingRule:
|
||||
properties:
|
||||
cacheMode:
|
||||
$ref: '#/components/schemas/LlmpricingruletypesLLMPricingRuleCacheMode'
|
||||
costCacheRead:
|
||||
format: double
|
||||
type: number
|
||||
costCacheWrite:
|
||||
format: double
|
||||
type: number
|
||||
costInput:
|
||||
format: double
|
||||
type: number
|
||||
costOutput:
|
||||
format: double
|
||||
type: number
|
||||
enabled:
|
||||
type: boolean
|
||||
id:
|
||||
nullable: true
|
||||
type: string
|
||||
isOverride:
|
||||
nullable: true
|
||||
type: boolean
|
||||
modelName:
|
||||
type: string
|
||||
modelPattern:
|
||||
items:
|
||||
type: string
|
||||
nullable: true
|
||||
type: array
|
||||
sourceId:
|
||||
nullable: true
|
||||
type: string
|
||||
unit:
|
||||
$ref: '#/components/schemas/LlmpricingruletypesLLMPricingRuleUnit'
|
||||
required:
|
||||
- modelName
|
||||
- modelPattern
|
||||
- unit
|
||||
- cacheMode
|
||||
- costInput
|
||||
- costOutput
|
||||
- costCacheRead
|
||||
- costCacheWrite
|
||||
- enabled
|
||||
type: object
|
||||
LlmpricingruletypesUpdatableLLMPricingRules:
|
||||
properties:
|
||||
rules:
|
||||
items:
|
||||
$ref: '#/components/schemas/LlmpricingruletypesUpdatableLLMPricingRule'
|
||||
nullable: true
|
||||
type: array
|
||||
required:
|
||||
- rules
|
||||
type: object
|
||||
MetricsexplorertypesInspectMetricsRequest:
|
||||
properties:
|
||||
end:
|
||||
@@ -7245,218 +7096,6 @@ paths:
|
||||
summary: Create bulk invite
|
||||
tags:
|
||||
- users
|
||||
/api/v1/llm_pricing_rules:
|
||||
get:
|
||||
deprecated: false
|
||||
description: Returns all LLM pricing rules for the authenticated org, with pagination.
|
||||
operationId: ListLLMPricingRules
|
||||
parameters:
|
||||
- in: query
|
||||
name: offset
|
||||
schema:
|
||||
type: integer
|
||||
- in: query
|
||||
name: limit
|
||||
schema:
|
||||
type: integer
|
||||
responses:
|
||||
"200":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
properties:
|
||||
data:
|
||||
$ref: '#/components/schemas/LlmpricingruletypesGettablePricingRules'
|
||||
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:
|
||||
- VIEWER
|
||||
- tokenizer:
|
||||
- VIEWER
|
||||
summary: List pricing rules
|
||||
tags:
|
||||
- llmpricingrules
|
||||
put:
|
||||
deprecated: false
|
||||
description: Single write endpoint used by both the user and the Zeus sync job.
|
||||
Per-rule match is by id, then sourceId, then insert. Override rows (is_override=true)
|
||||
are fully preserved when the request does not provide isOverride; only synced_at
|
||||
is stamped.
|
||||
operationId: UpdateLLMPricingRules
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/LlmpricingruletypesUpdatableLLMPricingRules'
|
||||
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
|
||||
"500":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- ADMIN
|
||||
- tokenizer:
|
||||
- ADMIN
|
||||
summary: Bulk update pricing rules
|
||||
tags:
|
||||
- llmpricingrules
|
||||
/api/v1/llm_pricing_rules/{id}:
|
||||
delete:
|
||||
deprecated: false
|
||||
description: Hard-deletes a pricing rule. If auto-synced, it will be recreated
|
||||
on the next sync cycle.
|
||||
operationId: DeleteLLMPricingRule
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"204":
|
||||
description: No Content
|
||||
"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:
|
||||
- ADMIN
|
||||
- tokenizer:
|
||||
- ADMIN
|
||||
summary: Delete a pricing rule
|
||||
tags:
|
||||
- llmpricingrules
|
||||
get:
|
||||
deprecated: false
|
||||
description: Returns a single LLM pricing rule by ID.
|
||||
operationId: GetLLMPricingRule
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
properties:
|
||||
data:
|
||||
$ref: '#/components/schemas/LlmpricingruletypesLLMPricingRule'
|
||||
status:
|
||||
type: string
|
||||
required:
|
||||
- status
|
||||
- data
|
||||
type: object
|
||||
description: OK
|
||||
"401":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Unauthorized
|
||||
"403":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Forbidden
|
||||
"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:
|
||||
- VIEWER
|
||||
- tokenizer:
|
||||
- VIEWER
|
||||
summary: Get a pricing rule
|
||||
tags:
|
||||
- llmpricingrules
|
||||
/api/v1/logs/promote_paths:
|
||||
get:
|
||||
deprecated: false
|
||||
@@ -8168,7 +7807,7 @@ paths:
|
||||
summary: Patch role
|
||||
tags:
|
||||
- role
|
||||
/api/v1/roles/{id}/relation/{relation}/objects:
|
||||
/api/v1/roles/{id}/relations/{relation}/objects:
|
||||
get:
|
||||
deprecated: false
|
||||
description: Gets all objects connected to the specified role via a given relation
|
||||
|
||||
@@ -71,6 +71,15 @@ func (ah *APIHandler) getFeatureFlags(w http.ResponseWriter, r *http.Request) {
|
||||
Route: "",
|
||||
})
|
||||
|
||||
bodyJSONQuery := ah.Signoz.Flagger.BooleanOrEmpty(ctx, flagger.FeatureBodyJSONQuery, evalCtx)
|
||||
featureSet = append(featureSet, &licensetypes.Feature{
|
||||
Name: valuer.NewString(flagger.FeatureBodyJSONQuery.String()),
|
||||
Active: bodyJSONQuery,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
})
|
||||
|
||||
if constants.IsDotMetricsEnabled {
|
||||
for idx, feature := range featureSet {
|
||||
if feature.Name == licensetypes.DotMetricsEnabled {
|
||||
|
||||
@@ -42,8 +42,8 @@ import (
|
||||
|
||||
// Server runs HTTP, Mux and a grpc server
|
||||
type Server struct {
|
||||
config signoz.Config
|
||||
signoz *signoz.SigNoz
|
||||
config signoz.Config
|
||||
signoz *signoz.SigNoz
|
||||
|
||||
// public http router
|
||||
httpConn net.Listener
|
||||
@@ -105,6 +105,7 @@ func NewServer(config signoz.Config, signoz *signoz.SigNoz) (*Server, error) {
|
||||
signoz.SQLStore,
|
||||
integrationsController.GetPipelinesForInstalledIntegrations,
|
||||
reader,
|
||||
signoz.Flagger,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -147,7 +148,7 @@ func NewServer(config signoz.Config, signoz *signoz.SigNoz) (*Server, error) {
|
||||
|
||||
s := &Server{
|
||||
config: config,
|
||||
signoz: signoz,
|
||||
signoz: signoz,
|
||||
httpHostPort: baseconst.HTTPHostPort,
|
||||
unavailableChannel: make(chan healthcheck.Status),
|
||||
usageManager: usageManager,
|
||||
@@ -316,4 +317,3 @@ func (s *Server) Stop(ctx context.Context) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
# SigNoz E2E Test Plan
|
||||
|
||||
This directory contains the structured test plan for the SigNoz application. Each subfolder corresponds to a main module or feature area, and contains scenario files for all user journeys, edge cases, and cross-module flows. These documents serve as the basis for generating Playwright MCP-driven E2E tests.
|
||||
|
||||
## Structure
|
||||
|
||||
- Each main module (e.g., logs, traces, dashboards, alerts, settings, etc.) has its own folder or markdown file.
|
||||
- Each file contains detailed scenario templates, including preconditions, step-by-step actions, and expected outcomes.
|
||||
- Use these documents to write, review, and update test cases as the application evolves.
|
||||
|
||||
## Folders & Files
|
||||
|
||||
- `logs/` — Logs module scenarios
|
||||
- `traces/` — Traces module scenarios
|
||||
- `metrics/` — Metrics module scenarios
|
||||
- `dashboards/` — Dashboards module scenarios
|
||||
- `alerts/` — Alerts module scenarios
|
||||
- `services/` — Services module scenarios
|
||||
- `settings/` — Settings and all sub-settings scenarios
|
||||
- `onboarding/` — Onboarding and signup flows
|
||||
- `navigation/` — Navigation, sidebar, and cross-module flows
|
||||
- `exceptions/` — Exception and error handling scenarios
|
||||
- `external-apis/` — External API monitoring scenarios
|
||||
- `messaging-queues/` — Messaging queue scenarios
|
||||
- `infrastructure/` — Infrastructure monitoring scenarios
|
||||
- `help-support/` — Help & support scenarios
|
||||
- `user-preferences/` — User preferences and personalization scenarios
|
||||
- `service-map/` — Service map scenarios
|
||||
- `saved-views/` — Saved views scenarios
|
||||
@@ -1,16 +0,0 @@
|
||||
# Settings Module Test Plan
|
||||
|
||||
This folder contains E2E test scenarios for the Settings module and all sub-settings.
|
||||
|
||||
## Scenario Categories
|
||||
|
||||
- General settings (org/workspace, branding, version info)
|
||||
- Billing settings
|
||||
- Members & SSO
|
||||
- Custom domain
|
||||
- Integrations
|
||||
- Notification channels
|
||||
- API keys
|
||||
- Ingestion
|
||||
- Account settings (profile, password, preferences)
|
||||
- Keyboard shortcuts
|
||||
@@ -1,43 +0,0 @@
|
||||
# Account Settings E2E Scenarios (Updated)
|
||||
|
||||
## 1. Update Name
|
||||
|
||||
- **Precondition:** User is logged in
|
||||
- **Steps:**
|
||||
1. Click 'Update name' button
|
||||
2. Edit name field in the modal/dialog
|
||||
3. Save changes
|
||||
- **Expected:** Name is updated in the UI
|
||||
|
||||
## 2. Update Email
|
||||
|
||||
- **Note:** The email field is not editable in the current UI.
|
||||
|
||||
## 3. Reset Password
|
||||
|
||||
- **Precondition:** User is logged in
|
||||
- **Steps:**
|
||||
1. Click 'Reset password' button
|
||||
2. Complete reset flow (modal/dialog or external flow)
|
||||
- **Expected:** Password is reset
|
||||
|
||||
## 4. Toggle 'Adapt to my timezone'
|
||||
|
||||
- **Precondition:** User is logged in
|
||||
- **Steps:**
|
||||
1. Toggle 'Adapt to my timezone' switch
|
||||
- **Expected:** Timezone adapts accordingly (UI feedback/confirmation should be checked)
|
||||
|
||||
## 5. Toggle Theme (Dark/Light)
|
||||
|
||||
- **Precondition:** User is logged in
|
||||
- **Steps:**
|
||||
1. Toggle theme radio buttons ('Dark', 'Light Beta')
|
||||
- **Expected:** Theme changes
|
||||
|
||||
## 6. Toggle Sidebar Always Open
|
||||
|
||||
- **Precondition:** User is logged in
|
||||
- **Steps:**
|
||||
1. Toggle 'Keep the primary sidebar always open' switch
|
||||
- **Expected:** Sidebar remains open/closed as per toggle
|
||||
@@ -1,26 +0,0 @@
|
||||
# API Keys E2E Scenarios (Updated)
|
||||
|
||||
## 1. Create a New API Key
|
||||
|
||||
- **Precondition:** User is admin
|
||||
- **Steps:**
|
||||
1. Click 'New Key' button
|
||||
2. Enter details in the modal/dialog
|
||||
3. Click 'Save'
|
||||
- **Expected:** API key is created and listed in the table
|
||||
|
||||
## 2. Revoke an API Key
|
||||
|
||||
- **Precondition:** API key exists
|
||||
- **Steps:**
|
||||
1. In the table, locate the API key row
|
||||
2. Click the revoke/delete button (icon button in the Action column)
|
||||
3. Confirm if prompted
|
||||
- **Expected:** API key is revoked/removed from the table
|
||||
|
||||
## 3. View API Key Usage
|
||||
|
||||
- **Precondition:** API key exists
|
||||
- **Steps:**
|
||||
1. View the 'Last used' and 'Expired' columns in the table
|
||||
- **Expected:** Usage data is displayed for each API key
|
||||
@@ -1,17 +0,0 @@
|
||||
# Billing Settings E2E Scenarios (Updated)
|
||||
|
||||
## 1. View Billing Information
|
||||
|
||||
- **Precondition:** User is admin
|
||||
- **Steps:**
|
||||
1. Navigate to Billing Settings
|
||||
2. Wait for the billing chart/data to finish loading
|
||||
- **Expected:**
|
||||
- Billing heading and subheading are displayed
|
||||
- Usage/cost table is visible with columns: Unit, Data Ingested, Price per Unit, Cost (Billing period to date)
|
||||
- "Download CSV" and "Manage Billing" buttons are present and enabled after loading
|
||||
- Test clicking "Download CSV" and "Manage Billing" for expected behavior (e.g., file download, navigation, or modal)
|
||||
|
||||
> Note: If these features are expected to trigger specific flows, document the observed behavior for each button.
|
||||
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
# Custom Domain E2E Scenarios (Updated)
|
||||
|
||||
## 1. Add or Update Custom Domain
|
||||
|
||||
- **Precondition:** User is admin
|
||||
- **Steps:**
|
||||
1. Click 'Customize team’s URL' button
|
||||
2. In the 'Customize your team’s URL' dialog, enter the preferred subdomain
|
||||
3. Click 'Apply Changes'
|
||||
- **Expected:** Domain is set/updated for the team (UI feedback/confirmation should be checked)
|
||||
|
||||
## 2. Verify Domain Ownership
|
||||
|
||||
- **Note:** No explicit 'Verify' button or flow is present in the current UI. If verification is required, it may be handled automatically or via support.
|
||||
|
||||
## 3. Remove a Custom Domain
|
||||
|
||||
- **Note:** No explicit 'Remove' button or flow is present in the current UI. The only available action is to update the subdomain.
|
||||
@@ -1,31 +0,0 @@
|
||||
# General Settings E2E Scenarios
|
||||
|
||||
## 1. View General Settings
|
||||
|
||||
- **Precondition:** User is logged in
|
||||
- **Steps:**
|
||||
1. Navigate to General Settings
|
||||
- **Expected:** General settings are displayed
|
||||
|
||||
## 2. Update Organization/Workspace Name
|
||||
|
||||
- **Precondition:** User is admin
|
||||
- **Steps:**
|
||||
1. Edit organization/workspace name
|
||||
2. Save changes
|
||||
- **Expected:** Name is updated and visible
|
||||
|
||||
## 3. Update Logo or Branding
|
||||
|
||||
- **Precondition:** User is admin
|
||||
- **Steps:**
|
||||
1. Upload new logo/branding
|
||||
2. Save changes
|
||||
- **Expected:** Branding is updated
|
||||
|
||||
## 4. View Version/Build Info
|
||||
|
||||
- **Precondition:** User is logged in
|
||||
- **Steps:**
|
||||
1. View version/build info section
|
||||
- **Expected:** Version/build info is displayed
|
||||
@@ -1,20 +0,0 @@
|
||||
# Ingestion E2E Scenarios (Updated)
|
||||
|
||||
## 1. View Ingestion Sources
|
||||
|
||||
- **Precondition:** User is admin
|
||||
- **Steps:**
|
||||
1. Navigate to the Integrations page
|
||||
- **Expected:** List of available data sources/integrations is displayed
|
||||
|
||||
## 2. Configure Ingestion Sources
|
||||
|
||||
- **Precondition:** User is admin
|
||||
- **Steps:**
|
||||
1. Click 'Configure' for a data source/integration
|
||||
2. Complete the configuration flow (modal or page, as available)
|
||||
- **Expected:** Source is configured (UI feedback/confirmation should be checked)
|
||||
|
||||
## 3. Disable/Enable Ingestion
|
||||
|
||||
- **Note:** No visible enable/disable toggle for ingestion sources in the current UI. Ingestion is managed via the Integrations configuration flows.
|
||||
@@ -1,51 +0,0 @@
|
||||
# Integrations E2E Scenarios (Updated)
|
||||
|
||||
## 1. View List of Available Integrations
|
||||
|
||||
- **Precondition:** User is logged in
|
||||
- **Steps:**
|
||||
1. Navigate to Integrations
|
||||
- **Expected:** List of integrations is displayed, each with a name, description, and 'Configure' button
|
||||
|
||||
## 2. Search Integrations by Name/Type
|
||||
|
||||
- **Precondition:** Integrations exist
|
||||
- **Steps:**
|
||||
1. Enter search/filter criteria in the 'Search for an integration...' box
|
||||
- **Expected:** Only matching integrations are shown
|
||||
|
||||
## 3. Connect a New Integration
|
||||
|
||||
- **Precondition:** User is admin
|
||||
- **Steps:**
|
||||
1. Click 'Configure' for an integration
|
||||
2. Complete the configuration flow (modal or page, as available)
|
||||
- **Expected:** Integration is connected/configured (UI feedback/confirmation should be checked)
|
||||
|
||||
## 4. Disconnect an Integration
|
||||
|
||||
- **Note:** No visible 'Disconnect' button in the main list. This may be available in the configuration flow for a connected integration.
|
||||
|
||||
## 5. Configure Integration Settings
|
||||
|
||||
- **Note:** Configuration is handled in the flow after clicking 'Configure' for an integration.
|
||||
|
||||
## 6. Test Integration Connection
|
||||
|
||||
- **Note:** No visible 'Test Connection' button in the main list. This may be available in the configuration flow.
|
||||
|
||||
## 7. View Integration Status/Logs
|
||||
|
||||
- **Note:** No visible status/logs section in the main list. This may be available in the configuration flow.
|
||||
|
||||
## 8. Filter Integrations by Category
|
||||
|
||||
- **Note:** No explicit category filter in the current UI, only a search box.
|
||||
|
||||
## 9. View Integration Documentation/Help
|
||||
|
||||
- **Note:** No visible 'Help/Docs' button in the main list. This may be available in the configuration flow.
|
||||
|
||||
## 10. Update Integration Configuration
|
||||
|
||||
- **Note:** Configuration is handled in the flow after clicking 'Configure' for an integration.
|
||||
@@ -1,19 +0,0 @@
|
||||
# Keyboard Shortcuts E2E Scenarios (Updated)
|
||||
|
||||
## 1. View Keyboard Shortcuts
|
||||
|
||||
- **Precondition:** User is logged in
|
||||
- **Steps:**
|
||||
1. Navigate to Keyboard Shortcuts
|
||||
- **Expected:** Shortcuts are displayed in categorized tables (Global, Logs Explorer, Query Builder, Dashboard)
|
||||
|
||||
## 2. Customize Keyboard Shortcuts (if supported)
|
||||
|
||||
- **Note:** Customization is not available in the current UI. Shortcuts are view-only.
|
||||
|
||||
## 3. Use Keyboard Shortcuts for Navigation/Actions
|
||||
|
||||
- **Precondition:** User is logged in
|
||||
- **Steps:**
|
||||
1. Use shortcut for navigation/action (e.g., shift+s for Services, cmd+enter for running query)
|
||||
- **Expected:** Navigation/action is performed as per shortcut
|
||||
@@ -1,49 +0,0 @@
|
||||
# Members & SSO E2E Scenarios (Updated)
|
||||
|
||||
## 1. Invite a New Member
|
||||
|
||||
- **Precondition:** User is admin
|
||||
- **Steps:**
|
||||
1. Click 'Invite Members' button
|
||||
2. In the 'Invite team members' dialog, enter email address, name (optional), and select role
|
||||
3. (Optional) Click 'Add another team member' to invite more
|
||||
4. Click 'Invite team members' to send invite(s)
|
||||
- **Expected:** Pending invite appears in the 'Pending Invites' table
|
||||
|
||||
## 2. Remove a Member
|
||||
|
||||
- **Precondition:** User is admin, member exists
|
||||
- **Steps:**
|
||||
1. In the 'Members' table, locate the member row
|
||||
2. Click 'Delete' in the Action column
|
||||
3. Confirm removal if prompted
|
||||
- **Expected:** Member is removed from the table
|
||||
|
||||
## 3. Update Member Roles
|
||||
|
||||
- **Precondition:** User is admin, member exists
|
||||
- **Steps:**
|
||||
1. In the 'Members' table, locate the member row
|
||||
2. Click 'Edit' in the Action column
|
||||
3. Change role in the edit dialog/modal
|
||||
4. Save changes
|
||||
- **Expected:** Member role is updated in the table
|
||||
|
||||
## 4. Configure SSO
|
||||
|
||||
- **Precondition:** User is admin
|
||||
- **Steps:**
|
||||
1. In the 'Authenticated Domains' section, locate the domain row
|
||||
2. Click 'Configure SSO' or 'Edit Google Auth' as available
|
||||
3. Complete SSO provider configuration in the modal/dialog
|
||||
4. Save settings
|
||||
- **Expected:** SSO is configured for the domain
|
||||
|
||||
## 5. Login via SSO
|
||||
|
||||
- **Precondition:** SSO is configured
|
||||
- **Steps:**
|
||||
1. Log out from the app
|
||||
2. On the login page, click 'Login with SSO'
|
||||
3. Complete SSO login flow
|
||||
- **Expected:** User is logged in via SSO
|
||||
@@ -1,39 +0,0 @@
|
||||
# Notification Channels E2E Scenarios (Updated)
|
||||
|
||||
## 1. Add a New Notification Channel
|
||||
|
||||
- **Precondition:** User is admin
|
||||
- **Steps:**
|
||||
1. Click 'New Alert Channel' button
|
||||
2. In the 'New Notification Channel' form, fill in required fields (Name, Type, Webhook URL, etc.)
|
||||
3. (Optional) Toggle 'Send resolved alerts'
|
||||
4. (Optional) Click 'Test' to send a test notification
|
||||
5. Click 'Save' to add the channel
|
||||
- **Expected:** Channel is added and listed in the table
|
||||
|
||||
## 2. Test Notification Channel
|
||||
|
||||
- **Precondition:** Channel is being created or edited
|
||||
- **Steps:**
|
||||
1. In the 'New Notification Channel' or 'Edit Notification Channel' form, click 'Test'
|
||||
- **Expected:** Test notification is sent (UI feedback/confirmation should be checked)
|
||||
|
||||
## 3. Remove a Notification Channel
|
||||
|
||||
- **Precondition:** Channel is added
|
||||
- **Steps:**
|
||||
1. In the table, locate the channel row
|
||||
2. Click 'Delete' in the Action column
|
||||
3. Confirm removal if prompted
|
||||
- **Expected:** Channel is removed from the table
|
||||
|
||||
## 4. Update Notification Channel Settings
|
||||
|
||||
- **Precondition:** Channel is added
|
||||
- **Steps:**
|
||||
1. In the table, locate the channel row
|
||||
2. Click 'Edit' in the Action column
|
||||
3. In the 'Edit Notification Channel' form, update fields as needed
|
||||
4. (Optional) Click 'Test' to send a test notification
|
||||
5. Click 'Save' to update the channel
|
||||
- **Expected:** Settings are updated
|
||||
@@ -1,199 +0,0 @@
|
||||
# SigNoz Test Plan Validation Report
|
||||
|
||||
This report documents the validation of the E2E test plan against the current live application using Playwright MCP. Each module is reviewed for coverage, gaps, and required updates.
|
||||
|
||||
---
|
||||
|
||||
## Home Module
|
||||
|
||||
- **Coverage:**
|
||||
- Widgets for logs, traces, metrics, dashboards, alerts, services, saved views, onboarding checklist
|
||||
- Quick access buttons: Explore Logs, Create dashboard, Create an alert
|
||||
- **Gaps/Updates:**
|
||||
- Add scenarios for checklist interactions (e.g., “I’ll do this later”, progress tracking)
|
||||
- Add scenarios for Saved Views and cross-module links
|
||||
- Add scenario for onboarding checklist completion
|
||||
|
||||
---
|
||||
|
||||
## Logs Module
|
||||
|
||||
- **Coverage:**
|
||||
- Explorer, Pipelines, Views tabs
|
||||
- Filtering by service, environment, severity, host, k8s, etc.
|
||||
- Search, save view, create alert, add to dashboard, export, view mode switching
|
||||
- **Gaps/Updates:**
|
||||
- Add scenario for quick filter customization
|
||||
- Add scenario for “Old Explorer” button
|
||||
- Add scenario for frequency chart toggle
|
||||
- Add scenario for “Stage & Run Query” workflow
|
||||
|
||||
---
|
||||
|
||||
## Traces Module
|
||||
|
||||
- **Coverage:**
|
||||
- Tabs: Explorer, Funnels, Views
|
||||
- Filtering by name, error status, duration, environment, function, service, RPC, status code, HTTP, trace ID, etc.
|
||||
- Search, save view, create alert, add to dashboard, export, view mode switching (List, Traces, Time Series, Table)
|
||||
- Pagination, quick filter customization, group by, aggregation
|
||||
- **Gaps/Updates:**
|
||||
- Add scenario for quick filter customization
|
||||
- Add scenario for “Stage & Run Query” workflow
|
||||
- Add scenario for all view modes (List, Traces, Time Series, Table)
|
||||
- Add scenario for group by/aggregation
|
||||
- Add scenario for trace detail navigation (clicking on trace row)
|
||||
- Add scenario for Funnels tab (create/edit/delete funnel)
|
||||
- Add scenario for Views tab (manage saved views)
|
||||
|
||||
---
|
||||
|
||||
## Metrics Module
|
||||
|
||||
- **Coverage:**
|
||||
- Tabs: Summary, Explorer, Views
|
||||
- Filtering by metric, type, unit, etc.
|
||||
- Search, save view, add to dashboard, export, view mode switching (chart, table, proportion view)
|
||||
- Pagination, group by, aggregation, custom queries
|
||||
- **Gaps/Updates:**
|
||||
- Add scenario for Proportion View in Summary
|
||||
- Add scenario for all view modes (chart, table, proportion)
|
||||
- Add scenario for group by/aggregation
|
||||
- Add scenario for custom queries in Explorer
|
||||
- Add scenario for Views tab (manage saved views)
|
||||
|
||||
---
|
||||
|
||||
## Dashboards Module
|
||||
|
||||
- **Coverage:**
|
||||
- List, search, and filter dashboards
|
||||
- Create new dashboard (button and template link)
|
||||
- Edit, delete, and view dashboard details
|
||||
- Add/edit/delete widgets (implied by dashboard detail)
|
||||
- Pagination through dashboards
|
||||
- **Gaps/Updates:**
|
||||
- Add scenario for browsing dashboard templates (external link)
|
||||
- Add scenario for requesting new template
|
||||
- Add scenario for dashboard owner and creation info
|
||||
- Add scenario for dashboard tags and filtering by tags
|
||||
- Add scenario for dashboard sharing (if available)
|
||||
- Add scenario for dashboard image/preview
|
||||
|
||||
---
|
||||
|
||||
## Messaging Queues Module
|
||||
|
||||
- **Coverage:**
|
||||
- Overview tab: queue metrics, filters (Service Name, Span Name, Msg System, Destination, Kind)
|
||||
- Search across all columns
|
||||
- Pagination of queue data
|
||||
- Sync and Share buttons
|
||||
- Tabs for Kafka and Celery
|
||||
- **Gaps/Updates:**
|
||||
- Add scenario for Kafka tab (detailed metrics, actions)
|
||||
- Add scenario for Celery tab (detailed metrics, actions)
|
||||
- Add scenario for filter combinations and edge cases
|
||||
- Add scenario for sharing queue data
|
||||
- Add scenario for time range selection
|
||||
|
||||
---
|
||||
|
||||
## External APIs Module
|
||||
|
||||
- **Coverage:**
|
||||
- Accessed via side navigation under MORE
|
||||
- Explorer tab: domain, endpoints, last used, rate, error %, avg. latency
|
||||
- Filters: Deployment Environment, Service Name, Rpc Method, Show IP addresses
|
||||
- Table pagination
|
||||
- Share and Stage & Run Query buttons
|
||||
- **Gaps/Updates:**
|
||||
- Add scenario for customizing quick filters
|
||||
- Add scenario for running and staging queries
|
||||
- Add scenario for sharing API data
|
||||
- Add scenario for edge cases in filters and table data
|
||||
|
||||
---
|
||||
|
||||
## Alerts Module
|
||||
|
||||
- **Coverage:**
|
||||
- Alert Rules tab: list, search, create (New Alert), edit, delete, enable/disable, severity, labels, actions
|
||||
- Triggered Alerts tab (visible in tablist)
|
||||
- Configuration tab (visible in tablist)
|
||||
- Table pagination
|
||||
- **Gaps/Updates:**
|
||||
- Add scenario for triggered alerts (view, acknowledge, resolve)
|
||||
- Add scenario for alert configuration (settings, integrations)
|
||||
- Add scenario for edge cases in alert creation and management
|
||||
- Add scenario for searching and filtering alerts
|
||||
|
||||
---
|
||||
|
||||
## Integrations Module
|
||||
|
||||
- **Coverage:**
|
||||
- Integrations tab: list, search, configure (e.g., AWS), request new integration
|
||||
- One-click setup for AWS monitoring
|
||||
- Request more integrations (form)
|
||||
- **Gaps/Updates:**
|
||||
- Add scenario for configuring integrations (step-by-step)
|
||||
- Add scenario for searching and filtering integrations
|
||||
- Add scenario for requesting new integrations
|
||||
- Add scenario for edge cases (e.g., failed configuration)
|
||||
|
||||
---
|
||||
|
||||
## Exceptions Module
|
||||
|
||||
- **Coverage:**
|
||||
- All Exceptions: list, search, filter (Deployment Environment, Service Name, Host Name, K8s Cluster/Deployment/Namespace, Net Peer Name)
|
||||
- Table: Exception Type, Error Message, Count, Last Seen, First Seen, Application
|
||||
- Pagination
|
||||
- Exception detail links
|
||||
- Share and Stage & Run Query buttons
|
||||
- **Gaps/Updates:**
|
||||
- Add scenario for exception detail view
|
||||
- Add scenario for advanced filtering and edge cases
|
||||
- Add scenario for sharing and running queries
|
||||
- Add scenario for error grouping and navigation
|
||||
|
||||
---
|
||||
|
||||
## Service Map Module
|
||||
|
||||
- **Coverage:**
|
||||
- Service Map visualization (main graph)
|
||||
- Filters: environment, resource attributes
|
||||
- Time range selection
|
||||
- Sync and Share buttons
|
||||
- **Gaps/Updates:**
|
||||
- Add scenario for interacting with the map (zoom, pan, select service)
|
||||
- Add scenario for filtering and edge cases
|
||||
- Add scenario for sharing the map
|
||||
- Add scenario for time range and environment combinations
|
||||
|
||||
---
|
||||
|
||||
## Billing Module
|
||||
|
||||
- **Coverage:**
|
||||
- Billing overview: cost monitoring, invoices, CSV download (disabled), manage billing (disabled)
|
||||
- Teams Cloud section
|
||||
- Billing table: Unit, Data Ingested, Price per Unit, Cost (Billing period to date)
|
||||
- **Gaps/Updates:**
|
||||
- Add scenario for invoice download and management (when enabled)
|
||||
- Add scenario for cost monitoring and edge cases
|
||||
- Add scenario for billing table data validation
|
||||
- Add scenario for permissions and access control
|
||||
|
||||
---
|
||||
|
||||
## Usage Explorer Module
|
||||
|
||||
- **Status:**
|
||||
- Not accessible in the current environment. Removing from test plan flows.
|
||||
|
||||
---
|
||||
|
||||
## [Next modules will be filled as validation proceeds]
|
||||
@@ -1,42 +0,0 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
import { ensureLoggedIn } from '../../../utils/login.util';
|
||||
|
||||
test('Account Settings - View and Assert Static Controls', async ({ page }) => {
|
||||
await ensureLoggedIn(page);
|
||||
|
||||
// 1. Open the sidebar settings menu using data-testid
|
||||
await page.getByTestId('settings-nav-item').click();
|
||||
|
||||
// 2. Click Account Settings in the dropdown (by role/name or data-testid if available)
|
||||
await page.getByRole('menuitem', { name: 'Account Settings' }).click();
|
||||
|
||||
// Assert the main tabpanel/heading (confirmed by DOM)
|
||||
await expect(page.getByTestId('settings-page-title')).toBeVisible();
|
||||
|
||||
// Assert General section and controls (confirmed by DOM)
|
||||
await expect(
|
||||
page.getByLabel('My Settings').getByText('General'),
|
||||
).toBeVisible();
|
||||
await expect(page.getByText('Manage your account settings.')).toBeVisible();
|
||||
await expect(page.getByRole('button', { name: 'Update name' })).toBeVisible();
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Reset password' }),
|
||||
).toBeVisible();
|
||||
|
||||
// Assert User Preferences section and controls (confirmed by DOM)
|
||||
await expect(page.getByText('User Preferences')).toBeVisible();
|
||||
await expect(
|
||||
page.getByText('Tailor the SigNoz console to work according to your needs.'),
|
||||
).toBeVisible();
|
||||
await expect(page.getByText('Select your theme')).toBeVisible();
|
||||
|
||||
const themeSelector = page.getByTestId('theme-selector');
|
||||
|
||||
await expect(themeSelector.getByText('Dark')).toBeVisible();
|
||||
await expect(themeSelector.getByText('Light')).toBeVisible();
|
||||
await expect(themeSelector.getByText('System')).toBeVisible();
|
||||
|
||||
await expect(page.getByTestId('timezone-adaptation-switch')).toBeVisible();
|
||||
await expect(page.getByTestId('side-nav-pinned-switch')).toBeVisible();
|
||||
});
|
||||
@@ -1,42 +0,0 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
import { ensureLoggedIn } from '../../../utils/login.util';
|
||||
|
||||
test('API Keys Settings - View and Interact', async ({ page }) => {
|
||||
await ensureLoggedIn(page);
|
||||
|
||||
// 1. Open the sidebar settings menu using data-testid
|
||||
await page.getByTestId('settings-nav-item').click();
|
||||
|
||||
// 2. Click Account Settings in the dropdown (by role/name or data-testid if available)
|
||||
await page.getByRole('menuitem', { name: 'Account Settings' }).click();
|
||||
|
||||
// Assert the main tabpanel/heading (confirmed by DOM)
|
||||
await expect(page.getByTestId('settings-page-title')).toBeVisible();
|
||||
|
||||
// Focus on the settings page sidenav
|
||||
await page.getByTestId('settings-page-sidenav').focus();
|
||||
|
||||
// Click API Keys tab in the settings sidebar (by data-testid)
|
||||
await page.getByTestId('api-keys').click();
|
||||
|
||||
// Assert heading and subheading
|
||||
await expect(page.getByRole('heading', { name: 'API Keys' })).toBeVisible();
|
||||
await expect(
|
||||
page.getByText('Create and manage API keys for the SigNoz API'),
|
||||
).toBeVisible();
|
||||
|
||||
// Assert presence of New Key button
|
||||
const newKeyBtn = page.getByRole('button', { name: 'New Key' });
|
||||
await expect(newKeyBtn).toBeVisible();
|
||||
|
||||
// Assert table columns
|
||||
await expect(page.getByText('Last used').first()).toBeVisible();
|
||||
await expect(page.getByText('Expired').first()).toBeVisible();
|
||||
|
||||
// Assert at least one API key row with action buttons
|
||||
// Select the first action cell's first button (icon button)
|
||||
const firstActionCell = page.locator('table tr').nth(1).locator('td').last();
|
||||
const deleteBtn = firstActionCell.locator('button').first();
|
||||
await expect(deleteBtn).toBeVisible();
|
||||
});
|
||||
@@ -1,71 +0,0 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
import { ensureLoggedIn } from '../../../utils/login.util';
|
||||
|
||||
// E2E: Billing Settings - View Billing Information and Button Actions
|
||||
|
||||
test('View Billing Information and Button Actions', async ({
|
||||
page,
|
||||
context,
|
||||
}) => {
|
||||
// Ensure user is logged in
|
||||
await ensureLoggedIn(page);
|
||||
|
||||
// 1. Open the sidebar settings menu using data-testid
|
||||
await page.getByTestId('settings-nav-item').click();
|
||||
|
||||
// 2. Click Account Settings in the dropdown (by role/name or data-testid if available)
|
||||
await page.getByRole('menuitem', { name: 'Account Settings' }).click();
|
||||
|
||||
// Assert the main tabpanel/heading (confirmed by DOM)
|
||||
await expect(page.getByTestId('settings-page-title')).toBeVisible();
|
||||
|
||||
// Focus on the settings page sidenav
|
||||
await page.getByTestId('settings-page-sidenav').focus();
|
||||
|
||||
// Click Billing tab in the settings sidebar (by data-testid)
|
||||
await page.getByTestId('billing').click();
|
||||
|
||||
// Wait for billing chart/data to finish loading
|
||||
await page.getByText('loading').first().waitFor({ state: 'hidden' });
|
||||
|
||||
// Assert visibility of subheading (unique)
|
||||
await expect(
|
||||
page.getByText(
|
||||
'Manage your billing information, invoices, and monitor costs.',
|
||||
),
|
||||
).toBeVisible();
|
||||
// Assert visibility of Teams Cloud heading
|
||||
await expect(page.getByRole('heading', { name: 'Teams Cloud' })).toBeVisible();
|
||||
|
||||
// Assert presence of summary and detailed tables
|
||||
await expect(page.getByText('TOTAL SPENT')).toBeVisible();
|
||||
await expect(page.getByText('Data Ingested')).toBeVisible();
|
||||
await expect(page.getByText('Price per Unit')).toBeVisible();
|
||||
await expect(page.getByText('Cost (Billing period to date)')).toBeVisible();
|
||||
|
||||
// Assert presence of alert and note
|
||||
await expect(
|
||||
page.getByText('Your current billing period is from', { exact: false }),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
page.getByText('Billing metrics are updated once every 24 hours.'),
|
||||
).toBeVisible();
|
||||
|
||||
// Test Download CSV button
|
||||
const [download] = await Promise.all([
|
||||
page.waitForEvent('download'),
|
||||
page.getByRole('button', { name: 'cloud-download Download CSV' }).click(),
|
||||
]);
|
||||
// Optionally, check download file name
|
||||
expect(download.suggestedFilename()).toContain('billing_usage');
|
||||
|
||||
// Test Manage Billing button (opens Stripe in new tab)
|
||||
const [newPage] = await Promise.all([
|
||||
context.waitForEvent('page'),
|
||||
page.getByTestId('header-billing-button').click(),
|
||||
]);
|
||||
await newPage.waitForLoadState();
|
||||
expect(newPage.url()).toContain('stripe.com');
|
||||
await newPage.close();
|
||||
});
|
||||
@@ -1,52 +0,0 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
import { ensureLoggedIn } from '../../../utils/login.util';
|
||||
|
||||
test('Custom Domain Settings - View and Interact', async ({ page }) => {
|
||||
await ensureLoggedIn(page);
|
||||
|
||||
// 1. Open the sidebar settings menu using data-testid
|
||||
await page.getByTestId('settings-nav-item').click();
|
||||
|
||||
// 2. Click Account Settings in the dropdown (by role/name or data-testid if available)
|
||||
await page.getByRole('menuitem', { name: 'Account Settings' }).click();
|
||||
|
||||
// Assert the main tabpanel/heading (confirmed by DOM)
|
||||
await expect(page.getByTestId('settings-page-title')).toBeVisible();
|
||||
|
||||
// Focus on the settings page sidenav
|
||||
await page.getByTestId('settings-page-sidenav').focus();
|
||||
|
||||
// Click Custom Domain tab in the settings sidebar (by data-testid)
|
||||
await page.getByTestId('custom-domain').click();
|
||||
|
||||
// Wait for custom domain chart/data to finish loading
|
||||
await page.getByText('loading').first().waitFor({ state: 'hidden' });
|
||||
|
||||
// Assert heading and subheading
|
||||
await expect(
|
||||
page.getByRole('heading', { name: 'Custom Domain Settings' }),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
page.getByText('Personalize your workspace domain effortlessly.'),
|
||||
).toBeVisible();
|
||||
|
||||
// Assert presence of Customize team’s URL button
|
||||
const customizeBtn = page.getByRole('button', {
|
||||
name: 'Customize team’s URL',
|
||||
});
|
||||
await expect(customizeBtn).toBeVisible();
|
||||
await customizeBtn.click();
|
||||
|
||||
// Assert modal/dialog fields and buttons
|
||||
await expect(
|
||||
page.getByRole('dialog', { name: 'Customize your team’s URL' }),
|
||||
).toBeVisible();
|
||||
await expect(page.getByLabel('Team’s URL subdomain')).toBeVisible();
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Apply Changes' }),
|
||||
).toBeVisible();
|
||||
await expect(page.getByRole('button', { name: 'Close' })).toBeVisible();
|
||||
// Close the modal
|
||||
await page.getByRole('button', { name: 'Close' }).click();
|
||||
});
|
||||
@@ -1,32 +0,0 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
import { ensureLoggedIn } from '../../../utils/login.util';
|
||||
|
||||
test('View General Settings', async ({ page }) => {
|
||||
await ensureLoggedIn(page);
|
||||
|
||||
// 1. Open the sidebar settings menu using data-testid
|
||||
await page.getByTestId('settings-nav-item').click();
|
||||
|
||||
// 2. Click Account Settings in the dropdown (by role/name or data-testid if available)
|
||||
await page.getByRole('menuitem', { name: 'Account Settings' }).click();
|
||||
|
||||
// Assert the main tabpanel/heading (confirmed by DOM)
|
||||
await expect(page.getByTestId('settings-page-title')).toBeVisible();
|
||||
|
||||
// Focus on the settings page sidenav
|
||||
await page.getByTestId('settings-page-sidenav').focus();
|
||||
|
||||
// Click General tab in the settings sidebar (by data-testid)
|
||||
await page.getByTestId('general').click();
|
||||
|
||||
// Wait for General tab to be visible
|
||||
await page.getByRole('tabpanel', { name: 'General' }).waitFor();
|
||||
|
||||
// Assert visibility of definitive/static elements
|
||||
await expect(page.getByRole('heading', { name: 'Metrics' })).toBeVisible();
|
||||
await expect(page.getByRole('heading', { name: 'Traces' })).toBeVisible();
|
||||
await expect(page.getByRole('heading', { name: 'Logs' })).toBeVisible();
|
||||
await expect(page.getByText('Please')).toBeVisible();
|
||||
await expect(page.getByRole('link', { name: 'email us' })).toBeVisible();
|
||||
});
|
||||
@@ -1,48 +0,0 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
import { ensureLoggedIn } from '../../../utils/login.util';
|
||||
|
||||
test('Ingestion Settings - View and Interact', async ({ page }) => {
|
||||
await ensureLoggedIn(page);
|
||||
|
||||
// 1. Open the sidebar settings menu using data-testid
|
||||
await page.getByTestId('settings-nav-item').click();
|
||||
|
||||
// 2. Click Account Settings in the dropdown (by role/name or data-testid if available)
|
||||
await page.getByRole('menuitem', { name: 'Account Settings' }).click();
|
||||
|
||||
// Assert the main tabpanel/heading (confirmed by DOM)
|
||||
await expect(page.getByTestId('settings-page-title')).toBeVisible();
|
||||
|
||||
// Focus on the settings page sidenav
|
||||
await page.getByTestId('settings-page-sidenav').focus();
|
||||
|
||||
// Click Ingestion tab in the settings sidebar (by data-testid)
|
||||
await page.getByTestId('ingestion').click();
|
||||
|
||||
// Assert heading and subheading (Integrations page)
|
||||
await expect(
|
||||
page.getByRole('heading', { name: 'Integrations' }),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
page.getByText('Manage Integrations for this workspace'),
|
||||
).toBeVisible();
|
||||
|
||||
// Assert presence of search box
|
||||
await expect(
|
||||
page.getByPlaceholder('Search for an integration...'),
|
||||
).toBeVisible();
|
||||
|
||||
// Assert at least one data source with Configure button
|
||||
const configureBtn = page.getByRole('button', { name: 'Configure' }).first();
|
||||
await expect(configureBtn).toBeVisible();
|
||||
|
||||
// Assert Request more integrations section
|
||||
await expect(
|
||||
page.getByText(
|
||||
"Can't find what you’re looking for? Request more integrations",
|
||||
),
|
||||
).toBeVisible();
|
||||
await expect(page.getByPlaceholder('Enter integration name...')).toBeVisible();
|
||||
await expect(page.getByRole('button', { name: 'Submit' })).toBeVisible();
|
||||
});
|
||||
@@ -1,48 +0,0 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
import { ensureLoggedIn } from '../../../utils/login.util';
|
||||
|
||||
test('Integrations Settings - View and Interact', async ({ page }) => {
|
||||
await ensureLoggedIn(page);
|
||||
|
||||
// 1. Open the sidebar settings menu using data-testid
|
||||
await page.getByTestId('settings-nav-item').click();
|
||||
|
||||
// 2. Click Account Settings in the dropdown (by role/name or data-testid if available)
|
||||
await page.getByRole('menuitem', { name: 'Account Settings' }).click();
|
||||
|
||||
// Assert the main tabpanel/heading (confirmed by DOM)
|
||||
await expect(page.getByTestId('settings-page-title')).toBeVisible();
|
||||
|
||||
// Focus on the settings page sidenav
|
||||
await page.getByTestId('settings-page-sidenav').focus();
|
||||
|
||||
// Click Integrations tab in the settings sidebar (by data-testid)
|
||||
await page.getByTestId('integrations').click();
|
||||
|
||||
// Assert heading and subheading
|
||||
await expect(
|
||||
page.getByRole('heading', { name: 'Integrations' }),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
page.getByText('Manage Integrations for this workspace'),
|
||||
).toBeVisible();
|
||||
|
||||
// Assert presence of search box
|
||||
await expect(
|
||||
page.getByPlaceholder('Search for an integration...'),
|
||||
).toBeVisible();
|
||||
|
||||
// Assert at least one integration with Configure button
|
||||
const configureBtn = page.getByRole('button', { name: 'Configure' }).first();
|
||||
await expect(configureBtn).toBeVisible();
|
||||
|
||||
// Assert Request more integrations section
|
||||
await expect(
|
||||
page.getByText(
|
||||
"Can't find what you’re looking for? Request more integrations",
|
||||
),
|
||||
).toBeVisible();
|
||||
await expect(page.getByPlaceholder('Enter integration name...')).toBeVisible();
|
||||
await expect(page.getByRole('button', { name: 'Submit' })).toBeVisible();
|
||||
});
|
||||
@@ -1,56 +0,0 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
import { ensureLoggedIn } from '../../../utils/login.util';
|
||||
|
||||
test('Members & SSO Settings - View and Interact', async ({ page }) => {
|
||||
await ensureLoggedIn(page);
|
||||
|
||||
// 1. Open the sidebar settings menu using data-testid
|
||||
await page.getByTestId('settings-nav-item').click();
|
||||
|
||||
// 2. Click Account Settings in the dropdown (by role/name or data-testid if available)
|
||||
await page.getByRole('menuitem', { name: 'Account Settings' }).click();
|
||||
|
||||
// Assert the main tabpanel/heading (confirmed by DOM)
|
||||
await expect(page.getByTestId('settings-page-title')).toBeVisible();
|
||||
|
||||
// Focus on the settings page sidenav
|
||||
await page.getByTestId('settings-page-sidenav').focus();
|
||||
|
||||
// Click Members & SSO tab in the settings sidebar (by data-testid)
|
||||
await page.getByTestId('members-sso').click();
|
||||
|
||||
// Assert headings and tables
|
||||
await expect(
|
||||
page.getByRole('heading', { name: /Members \(\d+\)/ }),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
page.getByRole('heading', { name: /Pending Invites \(\d+\)/ }),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
page.getByRole('heading', { name: 'Authenticated Domains' }),
|
||||
).toBeVisible();
|
||||
|
||||
// Assert Invite Members button is visible and clickable
|
||||
const inviteBtn = page.getByRole('button', { name: /Invite Members/ });
|
||||
await expect(inviteBtn).toBeVisible();
|
||||
await inviteBtn.click();
|
||||
// Assert Invite Members modal/dialog appears (modal title is unique)
|
||||
await expect(page.getByText('Invite team members').first()).toBeVisible();
|
||||
// Close the modal (use unique 'Close' button)
|
||||
await page.getByRole('button', { name: 'Close' }).click();
|
||||
|
||||
// Assert Edit and Delete buttons are present for at least one member
|
||||
const editBtn = page.getByRole('button', { name: /Edit/ }).first();
|
||||
const deleteBtn = page.getByRole('button', { name: /Delete/ }).first();
|
||||
await expect(editBtn).toBeVisible();
|
||||
await expect(deleteBtn).toBeVisible();
|
||||
|
||||
// Assert Add Domains button is visible
|
||||
await expect(page.getByRole('button', { name: /Add Domains/ })).toBeVisible();
|
||||
// Assert Configure SSO or Edit Google Auth button is visible for at least one domain
|
||||
const ssoBtn = page
|
||||
.getByRole('button', { name: /Configure SSO|Edit Google Auth/ })
|
||||
.first();
|
||||
await expect(ssoBtn).toBeVisible();
|
||||
});
|
||||
@@ -1,57 +0,0 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
import { ensureLoggedIn } from '../../../utils/login.util';
|
||||
|
||||
test('Notification Channels Settings - View and Interact', async ({ page }) => {
|
||||
await ensureLoggedIn(page);
|
||||
|
||||
// 1. Open the sidebar settings menu using data-testid
|
||||
await page.getByTestId('settings-nav-item').click();
|
||||
|
||||
// 2. Click Account Settings in the dropdown (by role/name or data-testid if available)
|
||||
await page.getByRole('menuitem', { name: 'Account Settings' }).click();
|
||||
|
||||
// Assert the main tabpanel/heading (confirmed by DOM)
|
||||
await expect(page.getByTestId('settings-page-title')).toBeVisible();
|
||||
|
||||
// Focus on the settings page sidenav
|
||||
await page.getByTestId('settings-page-sidenav').focus();
|
||||
|
||||
// Click Notification Channels tab in the settings sidebar (by data-testid)
|
||||
await page.getByTestId('notification-channels').click();
|
||||
|
||||
// Wait for loading to finish
|
||||
await page.getByText('loading').first().waitFor({ state: 'hidden' });
|
||||
|
||||
// Assert presence of New Alert Channel button
|
||||
const newChannelBtn = page.getByRole('button', { name: /New Alert Channel/ });
|
||||
await expect(newChannelBtn).toBeVisible();
|
||||
|
||||
// Assert table columns
|
||||
await expect(page.getByText('Name')).toBeVisible();
|
||||
await expect(page.getByText('Type')).toBeVisible();
|
||||
await expect(page.getByText('Action')).toBeVisible();
|
||||
|
||||
// Click New Alert Channel and assert modal fields/buttons
|
||||
await newChannelBtn.click();
|
||||
await expect(
|
||||
page.getByRole('heading', { name: 'New Notification Channel' }),
|
||||
).toBeVisible();
|
||||
await expect(page.getByLabel('Name')).toBeVisible();
|
||||
await expect(page.getByLabel('Type')).toBeVisible();
|
||||
await expect(page.getByLabel('Webhook URL')).toBeVisible();
|
||||
await expect(
|
||||
page.getByRole('switch', { name: 'Send resolved alerts' }),
|
||||
).toBeVisible();
|
||||
await expect(page.getByRole('button', { name: 'Save' })).toBeVisible();
|
||||
await expect(page.getByRole('button', { name: 'Test' })).toBeVisible();
|
||||
await expect(page.getByRole('button', { name: 'Back' })).toBeVisible();
|
||||
// Close modal
|
||||
await page.getByRole('button', { name: 'Back' }).click();
|
||||
|
||||
// Assert Edit and Delete buttons for at least one channel
|
||||
const editBtn = page.getByRole('button', { name: 'Edit' }).first();
|
||||
const deleteBtn = page.getByRole('button', { name: 'Delete' }).first();
|
||||
await expect(editBtn).toBeVisible();
|
||||
await expect(deleteBtn).toBeVisible();
|
||||
});
|
||||
@@ -1,35 +0,0 @@
|
||||
import { Page } from '@playwright/test';
|
||||
|
||||
// Read credentials from environment variables
|
||||
const username = process.env.LOGIN_USERNAME;
|
||||
const password = process.env.LOGIN_PASSWORD;
|
||||
const baseURL = process.env.BASE_URL;
|
||||
|
||||
/**
|
||||
* Ensures the user is logged in. If not, performs the login steps.
|
||||
* Follows the MCP process step-by-step.
|
||||
*/
|
||||
export async function ensureLoggedIn(page: Page): Promise<void> {
|
||||
// if already in home page, return
|
||||
if (await page.url().includes('/home')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!username || !password) {
|
||||
throw new Error(
|
||||
'E2E_EMAIL and E2E_PASSWORD environment variables must be set.',
|
||||
);
|
||||
}
|
||||
|
||||
await page.goto(`${baseURL}/login`);
|
||||
await page.getByTestId('email').click();
|
||||
await page.getByTestId('email').fill(username);
|
||||
await page.getByTestId('initiate_login').click();
|
||||
await page.getByTestId('password').click();
|
||||
await page.getByTestId('password').fill(password);
|
||||
await page.getByRole('button', { name: 'Login' }).click();
|
||||
|
||||
await page
|
||||
.getByText('Hello there, Welcome to your')
|
||||
.waitFor({ state: 'visible' });
|
||||
}
|
||||
@@ -44,7 +44,6 @@
|
||||
"@mdx-js/loader": "2.3.0",
|
||||
"@mdx-js/react": "2.3.0",
|
||||
"@monaco-editor/react": "^4.3.1",
|
||||
"@playwright/test": "1.55.1",
|
||||
"@radix-ui/react-tabs": "1.0.4",
|
||||
"@radix-ui/react-tooltip": "1.0.7",
|
||||
"@sentry/react": "8.41.0",
|
||||
|
||||
@@ -1,95 +0,0 @@
|
||||
import { defineConfig, devices } from '@playwright/test';
|
||||
import dotenv from 'dotenv';
|
||||
import path from 'path';
|
||||
|
||||
// Read from ".env" file.
|
||||
dotenv.config({ path: path.resolve(__dirname, '.env') });
|
||||
|
||||
/**
|
||||
* Read environment variables from file.
|
||||
* https://github.com/motdotla/dotenv
|
||||
*/
|
||||
// import dotenv from 'dotenv';
|
||||
// import path from 'path';
|
||||
// dotenv.config({ path: path.resolve(__dirname, '.env') });
|
||||
|
||||
/**
|
||||
* See https://playwright.dev/docs/test-configuration.
|
||||
*/
|
||||
export default defineConfig({
|
||||
testDir: './e2e/tests',
|
||||
/* Run tests in files in parallel */
|
||||
fullyParallel: true,
|
||||
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
||||
forbidOnly: !!process.env.CI,
|
||||
/* Retry on CI only */
|
||||
retries: process.env.CI ? 2 : 0,
|
||||
/* Run tests in parallel even in CI - optimized for GitHub Actions free tier */
|
||||
workers: process.env.CI ? 2 : undefined,
|
||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||
reporter: 'html',
|
||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||
use: {
|
||||
/* Base URL to use in actions like `await page.goto('/')`. */
|
||||
baseURL:
|
||||
process.env.SIGNOZ_E2E_BASE_URL || 'https://app.us.staging.signoz.cloud',
|
||||
|
||||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||
trace: 'on-first-retry',
|
||||
colorScheme: 'dark',
|
||||
locale: 'en-US',
|
||||
viewport: { width: 1280, height: 720 },
|
||||
},
|
||||
|
||||
/* Configure projects for major browsers */
|
||||
projects: [
|
||||
{
|
||||
name: 'chromium',
|
||||
use: {
|
||||
launchOptions: { args: ['--start-maximized'] },
|
||||
viewport: null,
|
||||
colorScheme: 'dark',
|
||||
locale: 'en-US',
|
||||
baseURL: 'https://app.us.staging.signoz.cloud',
|
||||
trace: 'on-first-retry',
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: 'firefox',
|
||||
use: { ...devices['Desktop Firefox'] },
|
||||
},
|
||||
|
||||
{
|
||||
name: 'webkit',
|
||||
use: { ...devices['Desktop Safari'] },
|
||||
},
|
||||
|
||||
/* Test against mobile viewports. */
|
||||
// {
|
||||
// name: 'Mobile Chrome',
|
||||
// use: { ...devices['Pixel 5'] },
|
||||
// },
|
||||
// {
|
||||
// name: 'Mobile Safari',
|
||||
// use: { ...devices['iPhone 12'] },
|
||||
// },
|
||||
|
||||
/* Test against branded browsers. */
|
||||
// {
|
||||
// name: 'Microsoft Edge',
|
||||
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
|
||||
// },
|
||||
// {
|
||||
// name: 'Google Chrome',
|
||||
// use: { ...devices['Desktop Chrome'], channel: 'chrome' },
|
||||
// },
|
||||
],
|
||||
|
||||
/* Run your local dev server before starting the tests */
|
||||
// webServer: {
|
||||
// command: 'npm run start',
|
||||
// url: 'http://localhost:3000',
|
||||
// reuseExistingServer: !process.env.CI,
|
||||
// },
|
||||
});
|
||||
@@ -1,16 +0,0 @@
|
||||
RULE: All test code for this repo must be generated by following the step-by-step Playwright MCP process as described below.
|
||||
|
||||
- You are a playwright test generator.
|
||||
- You are given a scenario and you need to generate a playwright test for it.
|
||||
- Use login util if not logged in.
|
||||
- DO NOT generate test code based on the scenario alone.
|
||||
- DO run steps one by one using the tools provided by the Playwright MCP.
|
||||
- Only after all steps are completed, emit a Playwright TypeScript test that uses @playwright/test based on message history
|
||||
- Gather correct selectors before writing the test
|
||||
- DO NOT valiate for dynamic content in the tests, only validate for the correctness with meta data
|
||||
- Always inspect the DOM at each navigation or interaction step to determine the correct selector for the next action. Do not assume selectors, confirm via inspection before proceeding.
|
||||
- Assert visibility of definitive/static elements in the UI (such as labels, headings, or section titles) rather than dynamic values or content that may change between runs.
|
||||
- Save generated test file in the tests directory
|
||||
- Execute the test file and iterate until the test passes
|
||||
|
||||
|
||||
@@ -1,398 +0,0 @@
|
||||
/**
|
||||
* ! Do not edit manually
|
||||
* * The file has been auto-generated using Orval for SigNoz
|
||||
* * regenerate with 'yarn 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 {
|
||||
DeleteLLMPricingRulePathParameters,
|
||||
GetLLMPricingRule200,
|
||||
GetLLMPricingRulePathParameters,
|
||||
ListLLMPricingRules200,
|
||||
ListLLMPricingRulesParams,
|
||||
LlmpricingruletypesUpdatableLLMPricingRulesDTO,
|
||||
RenderErrorResponseDTO,
|
||||
} from '../sigNoz.schemas';
|
||||
|
||||
import { GeneratedAPIInstance } from '../../../generatedAPIInstance';
|
||||
import type { ErrorType, BodyType } from '../../../generatedAPIInstance';
|
||||
|
||||
/**
|
||||
* Returns all LLM pricing rules for the authenticated org, with pagination.
|
||||
* @summary List pricing rules
|
||||
*/
|
||||
export const listLLMPricingRules = (
|
||||
params?: ListLLMPricingRulesParams,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<ListLLMPricingRules200>({
|
||||
url: `/api/v1/llm_pricing_rules`,
|
||||
method: 'GET',
|
||||
params,
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getListLLMPricingRulesQueryKey = (
|
||||
params?: ListLLMPricingRulesParams,
|
||||
) => {
|
||||
return [`/api/v1/llm_pricing_rules`, ...(params ? [params] : [])] as const;
|
||||
};
|
||||
|
||||
export const getListLLMPricingRulesQueryOptions = <
|
||||
TData = Awaited<ReturnType<typeof listLLMPricingRules>>,
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
>(
|
||||
params?: ListLLMPricingRulesParams,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof listLLMPricingRules>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
},
|
||||
) => {
|
||||
const { query: queryOptions } = options ?? {};
|
||||
|
||||
const queryKey =
|
||||
queryOptions?.queryKey ?? getListLLMPricingRulesQueryKey(params);
|
||||
|
||||
const queryFn: QueryFunction<
|
||||
Awaited<ReturnType<typeof listLLMPricingRules>>
|
||||
> = ({ signal }) => listLLMPricingRules(params, signal);
|
||||
|
||||
return { queryKey, queryFn, ...queryOptions } as UseQueryOptions<
|
||||
Awaited<ReturnType<typeof listLLMPricingRules>>,
|
||||
TError,
|
||||
TData
|
||||
> & { queryKey: QueryKey };
|
||||
};
|
||||
|
||||
export type ListLLMPricingRulesQueryResult = NonNullable<
|
||||
Awaited<ReturnType<typeof listLLMPricingRules>>
|
||||
>;
|
||||
export type ListLLMPricingRulesQueryError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary List pricing rules
|
||||
*/
|
||||
|
||||
export function useListLLMPricingRules<
|
||||
TData = Awaited<ReturnType<typeof listLLMPricingRules>>,
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
>(
|
||||
params?: ListLLMPricingRulesParams,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof listLLMPricingRules>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
},
|
||||
): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
|
||||
const queryOptions = getListLLMPricingRulesQueryOptions(params, options);
|
||||
|
||||
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
|
||||
queryKey: QueryKey;
|
||||
};
|
||||
|
||||
query.queryKey = queryOptions.queryKey;
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary List pricing rules
|
||||
*/
|
||||
export const invalidateListLLMPricingRules = async (
|
||||
queryClient: QueryClient,
|
||||
params?: ListLLMPricingRulesParams,
|
||||
options?: InvalidateOptions,
|
||||
): Promise<QueryClient> => {
|
||||
await queryClient.invalidateQueries(
|
||||
{ queryKey: getListLLMPricingRulesQueryKey(params) },
|
||||
options,
|
||||
);
|
||||
|
||||
return queryClient;
|
||||
};
|
||||
|
||||
/**
|
||||
* Single write endpoint used by both the user and the Zeus sync job. Per-rule match is by id, then sourceId, then insert. Override rows (is_override=true) are fully preserved when the request does not provide isOverride; only synced_at is stamped.
|
||||
* @summary Bulk update pricing rules
|
||||
*/
|
||||
export const updateLLMPricingRules = (
|
||||
llmpricingruletypesUpdatableLLMPricingRulesDTO: BodyType<LlmpricingruletypesUpdatableLLMPricingRulesDTO>,
|
||||
) => {
|
||||
return GeneratedAPIInstance<void>({
|
||||
url: `/api/v1/llm_pricing_rules`,
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
data: llmpricingruletypesUpdatableLLMPricingRulesDTO,
|
||||
});
|
||||
};
|
||||
|
||||
export const getUpdateLLMPricingRulesMutationOptions = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof updateLLMPricingRules>>,
|
||||
TError,
|
||||
{ data: BodyType<LlmpricingruletypesUpdatableLLMPricingRulesDTO> },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof updateLLMPricingRules>>,
|
||||
TError,
|
||||
{ data: BodyType<LlmpricingruletypesUpdatableLLMPricingRulesDTO> },
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ['updateLLMPricingRules'];
|
||||
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 updateLLMPricingRules>>,
|
||||
{ data: BodyType<LlmpricingruletypesUpdatableLLMPricingRulesDTO> }
|
||||
> = (props) => {
|
||||
const { data } = props ?? {};
|
||||
|
||||
return updateLLMPricingRules(data);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type UpdateLLMPricingRulesMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof updateLLMPricingRules>>
|
||||
>;
|
||||
export type UpdateLLMPricingRulesMutationBody =
|
||||
BodyType<LlmpricingruletypesUpdatableLLMPricingRulesDTO>;
|
||||
export type UpdateLLMPricingRulesMutationError =
|
||||
ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary Bulk update pricing rules
|
||||
*/
|
||||
export const useUpdateLLMPricingRules = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof updateLLMPricingRules>>,
|
||||
TError,
|
||||
{ data: BodyType<LlmpricingruletypesUpdatableLLMPricingRulesDTO> },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof updateLLMPricingRules>>,
|
||||
TError,
|
||||
{ data: BodyType<LlmpricingruletypesUpdatableLLMPricingRulesDTO> },
|
||||
TContext
|
||||
> => {
|
||||
const mutationOptions = getUpdateLLMPricingRulesMutationOptions(options);
|
||||
|
||||
return useMutation(mutationOptions);
|
||||
};
|
||||
/**
|
||||
* Hard-deletes a pricing rule. If auto-synced, it will be recreated on the next sync cycle.
|
||||
* @summary Delete a pricing rule
|
||||
*/
|
||||
export const deleteLLMPricingRule = ({
|
||||
id,
|
||||
}: DeleteLLMPricingRulePathParameters) => {
|
||||
return GeneratedAPIInstance<void>({
|
||||
url: `/api/v1/llm_pricing_rules/${id}`,
|
||||
method: 'DELETE',
|
||||
});
|
||||
};
|
||||
|
||||
export const getDeleteLLMPricingRuleMutationOptions = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof deleteLLMPricingRule>>,
|
||||
TError,
|
||||
{ pathParams: DeleteLLMPricingRulePathParameters },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof deleteLLMPricingRule>>,
|
||||
TError,
|
||||
{ pathParams: DeleteLLMPricingRulePathParameters },
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ['deleteLLMPricingRule'];
|
||||
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 deleteLLMPricingRule>>,
|
||||
{ pathParams: DeleteLLMPricingRulePathParameters }
|
||||
> = (props) => {
|
||||
const { pathParams } = props ?? {};
|
||||
|
||||
return deleteLLMPricingRule(pathParams);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type DeleteLLMPricingRuleMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof deleteLLMPricingRule>>
|
||||
>;
|
||||
|
||||
export type DeleteLLMPricingRuleMutationError =
|
||||
ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary Delete a pricing rule
|
||||
*/
|
||||
export const useDeleteLLMPricingRule = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof deleteLLMPricingRule>>,
|
||||
TError,
|
||||
{ pathParams: DeleteLLMPricingRulePathParameters },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof deleteLLMPricingRule>>,
|
||||
TError,
|
||||
{ pathParams: DeleteLLMPricingRulePathParameters },
|
||||
TContext
|
||||
> => {
|
||||
const mutationOptions = getDeleteLLMPricingRuleMutationOptions(options);
|
||||
|
||||
return useMutation(mutationOptions);
|
||||
};
|
||||
/**
|
||||
* Returns a single LLM pricing rule by ID.
|
||||
* @summary Get a pricing rule
|
||||
*/
|
||||
export const getLLMPricingRule = (
|
||||
{ id }: GetLLMPricingRulePathParameters,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<GetLLMPricingRule200>({
|
||||
url: `/api/v1/llm_pricing_rules/${id}`,
|
||||
method: 'GET',
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getGetLLMPricingRuleQueryKey = ({
|
||||
id,
|
||||
}: GetLLMPricingRulePathParameters) => {
|
||||
return [`/api/v1/llm_pricing_rules/${id}`] as const;
|
||||
};
|
||||
|
||||
export const getGetLLMPricingRuleQueryOptions = <
|
||||
TData = Awaited<ReturnType<typeof getLLMPricingRule>>,
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
>(
|
||||
{ id }: GetLLMPricingRulePathParameters,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getLLMPricingRule>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
},
|
||||
) => {
|
||||
const { query: queryOptions } = options ?? {};
|
||||
|
||||
const queryKey =
|
||||
queryOptions?.queryKey ?? getGetLLMPricingRuleQueryKey({ id });
|
||||
|
||||
const queryFn: QueryFunction<
|
||||
Awaited<ReturnType<typeof getLLMPricingRule>>
|
||||
> = ({ signal }) => getLLMPricingRule({ id }, signal);
|
||||
|
||||
return {
|
||||
queryKey,
|
||||
queryFn,
|
||||
enabled: !!id,
|
||||
...queryOptions,
|
||||
} as UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getLLMPricingRule>>,
|
||||
TError,
|
||||
TData
|
||||
> & { queryKey: QueryKey };
|
||||
};
|
||||
|
||||
export type GetLLMPricingRuleQueryResult = NonNullable<
|
||||
Awaited<ReturnType<typeof getLLMPricingRule>>
|
||||
>;
|
||||
export type GetLLMPricingRuleQueryError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary Get a pricing rule
|
||||
*/
|
||||
|
||||
export function useGetLLMPricingRule<
|
||||
TData = Awaited<ReturnType<typeof getLLMPricingRule>>,
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
>(
|
||||
{ id }: GetLLMPricingRulePathParameters,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getLLMPricingRule>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
},
|
||||
): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
|
||||
const queryOptions = getGetLLMPricingRuleQueryOptions({ id }, options);
|
||||
|
||||
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
|
||||
queryKey: QueryKey;
|
||||
};
|
||||
|
||||
query.queryKey = queryOptions.queryKey;
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Get a pricing rule
|
||||
*/
|
||||
export const invalidateGetLLMPricingRule = async (
|
||||
queryClient: QueryClient,
|
||||
{ id }: GetLLMPricingRulePathParameters,
|
||||
options?: InvalidateOptions,
|
||||
): Promise<QueryClient> => {
|
||||
await queryClient.invalidateQueries(
|
||||
{ queryKey: getGetLLMPricingRuleQueryKey({ id }) },
|
||||
options,
|
||||
);
|
||||
|
||||
return queryClient;
|
||||
};
|
||||
@@ -471,7 +471,7 @@ export const getObjects = (
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<GetObjects200>({
|
||||
url: `/api/v1/roles/${id}/relation/${relation}/objects`,
|
||||
url: `/api/v1/roles/${id}/relations/${relation}/objects`,
|
||||
method: 'GET',
|
||||
signal,
|
||||
});
|
||||
@@ -481,7 +481,7 @@ export const getGetObjectsQueryKey = ({
|
||||
id,
|
||||
relation,
|
||||
}: GetObjectsPathParameters) => {
|
||||
return [`/api/v1/roles/${id}/relation/${relation}/objects`] as const;
|
||||
return [`/api/v1/roles/${id}/relations/${relation}/objects`] as const;
|
||||
};
|
||||
|
||||
export const getGetObjectsQueryOptions = <
|
||||
@@ -574,7 +574,7 @@ export const patchObjects = (
|
||||
authtypesPatchableObjectsDTO: BodyType<AuthtypesPatchableObjectsDTO>,
|
||||
) => {
|
||||
return GeneratedAPIInstance<string>({
|
||||
url: `/api/v1/roles/${id}/relation/${relation}/objects`,
|
||||
url: `/api/v1/roles/${id}/relations/${relation}/objects`,
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
data: authtypesPatchableObjectsDTO,
|
||||
|
||||
@@ -3178,173 +3178,6 @@ export enum InframonitoringtypesResponseTypeDTO {
|
||||
list = 'list',
|
||||
grouped_list = 'grouped_list',
|
||||
}
|
||||
export interface LlmpricingruletypesGettablePricingRulesDTO {
|
||||
/**
|
||||
* @type array
|
||||
* @nullable true
|
||||
*/
|
||||
items: LlmpricingruletypesLLMPricingRuleDTO[] | null;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
limit: number;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
offset: number;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
total: number;
|
||||
}
|
||||
|
||||
export interface LlmpricingruletypesLLMPricingRuleDTO {
|
||||
cacheMode: LlmpricingruletypesLLMPricingRuleCacheModeDTO;
|
||||
/**
|
||||
* @type number
|
||||
* @format double
|
||||
*/
|
||||
costCacheRead: number;
|
||||
/**
|
||||
* @type number
|
||||
* @format double
|
||||
*/
|
||||
costCacheWrite: number;
|
||||
/**
|
||||
* @type number
|
||||
* @format double
|
||||
*/
|
||||
costInput: number;
|
||||
/**
|
||||
* @type number
|
||||
* @format double
|
||||
*/
|
||||
costOutput: number;
|
||||
/**
|
||||
* @type string
|
||||
* @format date-time
|
||||
*/
|
||||
createdAt?: Date;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
createdBy?: string;
|
||||
/**
|
||||
* @type boolean
|
||||
*/
|
||||
enabled: boolean;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* @type boolean
|
||||
*/
|
||||
isOverride: boolean;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
modelName: string;
|
||||
/**
|
||||
* @type array
|
||||
* @nullable true
|
||||
*/
|
||||
modelPattern: string[] | null;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
orgId: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
sourceId?: string;
|
||||
/**
|
||||
* @type string
|
||||
* @format date-time
|
||||
* @nullable true
|
||||
*/
|
||||
syncedAt?: Date | null;
|
||||
unit: LlmpricingruletypesLLMPricingRuleUnitDTO;
|
||||
/**
|
||||
* @type string
|
||||
* @format date-time
|
||||
*/
|
||||
updatedAt?: Date;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
updatedBy?: string;
|
||||
}
|
||||
|
||||
export enum LlmpricingruletypesLLMPricingRuleCacheModeDTO {
|
||||
subtract = 'subtract',
|
||||
additive = 'additive',
|
||||
unknown = 'unknown',
|
||||
}
|
||||
export enum LlmpricingruletypesLLMPricingRuleUnitDTO {
|
||||
per_million_tokens = 'per_million_tokens',
|
||||
}
|
||||
export interface LlmpricingruletypesUpdatableLLMPricingRuleDTO {
|
||||
cacheMode: LlmpricingruletypesLLMPricingRuleCacheModeDTO;
|
||||
/**
|
||||
* @type number
|
||||
* @format double
|
||||
*/
|
||||
costCacheRead: number;
|
||||
/**
|
||||
* @type number
|
||||
* @format double
|
||||
*/
|
||||
costCacheWrite: number;
|
||||
/**
|
||||
* @type number
|
||||
* @format double
|
||||
*/
|
||||
costInput: number;
|
||||
/**
|
||||
* @type number
|
||||
* @format double
|
||||
*/
|
||||
costOutput: number;
|
||||
/**
|
||||
* @type boolean
|
||||
*/
|
||||
enabled: boolean;
|
||||
/**
|
||||
* @type string
|
||||
* @nullable true
|
||||
*/
|
||||
id?: string | null;
|
||||
/**
|
||||
* @type boolean
|
||||
* @nullable true
|
||||
*/
|
||||
isOverride?: boolean | null;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
modelName: string;
|
||||
/**
|
||||
* @type array
|
||||
* @nullable true
|
||||
*/
|
||||
modelPattern: string[] | null;
|
||||
/**
|
||||
* @type string
|
||||
* @nullable true
|
||||
*/
|
||||
sourceId?: string | null;
|
||||
unit: LlmpricingruletypesLLMPricingRuleUnitDTO;
|
||||
}
|
||||
|
||||
export interface LlmpricingruletypesUpdatableLLMPricingRulesDTO {
|
||||
/**
|
||||
* @type array
|
||||
* @nullable true
|
||||
*/
|
||||
rules: LlmpricingruletypesUpdatableLLMPricingRuleDTO[] | null;
|
||||
}
|
||||
|
||||
export interface MetricsexplorertypesInspectMetricsRequestDTO {
|
||||
/**
|
||||
* @type integer
|
||||
@@ -6490,41 +6323,6 @@ export type CreateInvite201 = {
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type ListLLMPricingRulesParams = {
|
||||
/**
|
||||
* @type integer
|
||||
* @description undefined
|
||||
*/
|
||||
offset?: number;
|
||||
/**
|
||||
* @type integer
|
||||
* @description undefined
|
||||
*/
|
||||
limit?: number;
|
||||
};
|
||||
|
||||
export type ListLLMPricingRules200 = {
|
||||
data: LlmpricingruletypesGettablePricingRulesDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type DeleteLLMPricingRulePathParameters = {
|
||||
id: string;
|
||||
};
|
||||
export type GetLLMPricingRulePathParameters = {
|
||||
id: string;
|
||||
};
|
||||
export type GetLLMPricingRule200 = {
|
||||
data: LlmpricingruletypesLLMPricingRuleDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type ListPromotedAndIndexedPaths200 = {
|
||||
/**
|
||||
* @type array
|
||||
|
||||
@@ -66,8 +66,6 @@
|
||||
"./vite.config.ts",
|
||||
"./jest.setup.ts",
|
||||
"./tests/**.ts",
|
||||
"./**/*.d.ts",
|
||||
"./playwright.config.ts",
|
||||
"./e2e/**/*.ts"
|
||||
"./**/*.d.ts"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -4540,13 +4540,6 @@
|
||||
resolved "https://registry.yarnpkg.com/@pkgr/core/-/core-0.2.9.tgz#d229a7b7f9dac167a156992ef23c7f023653f53b"
|
||||
integrity sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==
|
||||
|
||||
"@playwright/test@1.55.1":
|
||||
version "1.55.1"
|
||||
resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.55.1.tgz#80f775d5f948cd3ef550fcc45ef99986d3ffb36c"
|
||||
integrity sha512-IVAh/nOJaw6W9g+RJVlIQJ6gSiER+ae6mKQ5CX1bERzQgbC1VSeBlwdvczT7pxb0GWiyrxH4TGKbMfDb4Sq/ig==
|
||||
dependencies:
|
||||
playwright "1.55.1"
|
||||
|
||||
"@posthog/core@1.6.0":
|
||||
version "1.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@posthog/core/-/core-1.6.0.tgz#a5b63a30950a8dfe87d4bf335ab24005c7ce1278"
|
||||
@@ -10568,7 +10561,7 @@ fscreen@^1.0.2:
|
||||
resolved "https://registry.yarnpkg.com/fscreen/-/fscreen-1.2.0.tgz#1a8c88e06bc16a07b473ad96196fb06d6657f59e"
|
||||
integrity sha512-hlq4+BU0hlPmwsFjwGGzZ+OZ9N/wq9Ljg/sq3pX+2CD7hrJsX9tJgWWK/wiNTFM212CLHWhicOoqwXyZGGetJg==
|
||||
|
||||
fsevents@2.3.2, fsevents@^2.3.2, fsevents@~2.3.2:
|
||||
fsevents@^2.3.2, fsevents@~2.3.2:
|
||||
version "2.3.2"
|
||||
resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz"
|
||||
integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
|
||||
@@ -15468,20 +15461,6 @@ pkg-dir@^7.0.0:
|
||||
dependencies:
|
||||
find-up "^6.3.0"
|
||||
|
||||
playwright-core@1.55.1:
|
||||
version "1.55.1"
|
||||
resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.55.1.tgz#5d3bb1846bc4289d364ea1a9dcb33f14545802e9"
|
||||
integrity sha512-Z6Mh9mkwX+zxSlHqdr5AOcJnfp+xUWLCt9uKV18fhzA8eyxUd8NUWzAjxUh55RZKSYwDGX0cfaySdhZJGMoJ+w==
|
||||
|
||||
playwright@1.55.1:
|
||||
version "1.55.1"
|
||||
resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.55.1.tgz#8a9954e9e61ed1ab479212af9be336888f8b3f0e"
|
||||
integrity sha512-cJW4Xd/G3v5ovXtJJ52MAOclqeac9S/aGGgRzLabuF8TnIb6xHvMzKIa6JmrRzUkeXJgfL1MhukP0NK6l39h3A==
|
||||
dependencies:
|
||||
playwright-core "1.55.1"
|
||||
optionalDependencies:
|
||||
fsevents "2.3.2"
|
||||
|
||||
pony-cause@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/pony-cause/-/pony-cause-1.1.1.tgz#f795524f83bebbf1878bd3587b45f69143cbf3f9"
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
package signozapiserver
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/http/handler"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/llmpricingruletypes"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
func (provider *provider) addLLMPricingRuleRoutes(router *mux.Router) error {
|
||||
if err := router.Handle("/api/v1/llm_pricing_rules", handler.New(
|
||||
provider.authZ.ViewAccess(provider.llmPricingRuleHandler.List),
|
||||
handler.OpenAPIDef{
|
||||
ID: "ListLLMPricingRules",
|
||||
Tags: []string{"llmpricingrules"},
|
||||
Summary: "List pricing rules",
|
||||
Description: "Returns all LLM pricing rules for the authenticated org, with pagination.",
|
||||
Request: nil,
|
||||
RequestContentType: "",
|
||||
RequestQuery: new(llmpricingruletypes.ListPricingRulesQuery),
|
||||
Response: new(llmpricingruletypes.GettablePricingRules),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
|
||||
},
|
||||
)).Methods(http.MethodGet).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/llm_pricing_rules", handler.New(
|
||||
provider.authZ.AdminAccess(provider.llmPricingRuleHandler.Update),
|
||||
handler.OpenAPIDef{
|
||||
ID: "UpdateLLMPricingRules",
|
||||
Tags: []string{"llmpricingrules"},
|
||||
Summary: "Bulk update pricing rules",
|
||||
Description: "Single write endpoint used by both the user and the Zeus sync job. Per-rule match is by id, then sourceId, then insert. Override rows (is_override=true) are fully preserved when the request does not provide isOverride; only synced_at is stamped.",
|
||||
Request: new(llmpricingruletypes.UpdatableLLMPricingRules),
|
||||
RequestContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
},
|
||||
)).Methods(http.MethodPut).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/llm_pricing_rules/{id}", handler.New(
|
||||
provider.authZ.ViewAccess(provider.llmPricingRuleHandler.Get),
|
||||
handler.OpenAPIDef{
|
||||
ID: "GetLLMPricingRule",
|
||||
Tags: []string{"llmpricingrules"},
|
||||
Summary: "Get a pricing rule",
|
||||
Description: "Returns a single LLM pricing rule by ID.",
|
||||
Request: nil,
|
||||
RequestContentType: "",
|
||||
Response: new(llmpricingruletypes.GettableLLMPricingRule),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{http.StatusNotFound},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
|
||||
},
|
||||
)).Methods(http.MethodGet).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/llm_pricing_rules/{id}", handler.New(
|
||||
provider.authZ.AdminAccess(provider.llmPricingRuleHandler.Delete),
|
||||
handler.OpenAPIDef{
|
||||
ID: "DeleteLLMPricingRule",
|
||||
Tags: []string{"llmpricingrules"},
|
||||
Summary: "Delete a pricing rule",
|
||||
Description: "Hard-deletes a pricing rule. If auto-synced, it will be recreated on the next sync cycle.",
|
||||
Request: nil,
|
||||
RequestContentType: "",
|
||||
Response: nil,
|
||||
ResponseContentType: "",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusNotFound},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
},
|
||||
)).Methods(http.MethodDelete).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -16,9 +16,8 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/modules/cloudintegration"
|
||||
"github.com/SigNoz/signoz/pkg/modules/dashboard"
|
||||
"github.com/SigNoz/signoz/pkg/modules/fields"
|
||||
"github.com/SigNoz/signoz/pkg/modules/inframonitoring"
|
||||
"github.com/SigNoz/signoz/pkg/modules/llmpricingrule"
|
||||
"github.com/SigNoz/signoz/pkg/modules/metricsexplorer"
|
||||
"github.com/SigNoz/signoz/pkg/modules/inframonitoring"
|
||||
"github.com/SigNoz/signoz/pkg/modules/organization"
|
||||
"github.com/SigNoz/signoz/pkg/modules/preference"
|
||||
"github.com/SigNoz/signoz/pkg/modules/promote"
|
||||
@@ -64,7 +63,6 @@ type provider struct {
|
||||
ruleStateHistoryHandler rulestatehistory.Handler
|
||||
alertmanagerHandler alertmanager.Handler
|
||||
rulerHandler ruler.Handler
|
||||
llmPricingRuleHandler llmpricingrule.Handler
|
||||
}
|
||||
|
||||
func NewFactory(
|
||||
@@ -93,7 +91,6 @@ func NewFactory(
|
||||
cloudIntegrationHandler cloudintegration.Handler,
|
||||
ruleStateHistoryHandler rulestatehistory.Handler,
|
||||
alertmanagerHandler alertmanager.Handler,
|
||||
llmPricingRuleHandler llmpricingrule.Handler,
|
||||
rulerHandler ruler.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) {
|
||||
@@ -126,7 +123,6 @@ func NewFactory(
|
||||
cloudIntegrationHandler,
|
||||
ruleStateHistoryHandler,
|
||||
alertmanagerHandler,
|
||||
llmPricingRuleHandler,
|
||||
rulerHandler,
|
||||
)
|
||||
})
|
||||
@@ -161,8 +157,6 @@ func newProvider(
|
||||
cloudIntegrationHandler cloudintegration.Handler,
|
||||
ruleStateHistoryHandler rulestatehistory.Handler,
|
||||
alertmanagerHandler alertmanager.Handler,
|
||||
llmPricingRuleHandler llmpricingrule.Handler,
|
||||
|
||||
rulerHandler ruler.Handler,
|
||||
) (apiserver.APIServer, error) {
|
||||
settings := factory.NewScopedProviderSettings(providerSettings, "github.com/SigNoz/signoz/pkg/apiserver/signozapiserver")
|
||||
@@ -196,7 +190,6 @@ func newProvider(
|
||||
ruleStateHistoryHandler: ruleStateHistoryHandler,
|
||||
alertmanagerHandler: alertmanagerHandler,
|
||||
rulerHandler: rulerHandler,
|
||||
llmPricingRuleHandler: llmPricingRuleHandler,
|
||||
}
|
||||
|
||||
provider.authZ = middleware.NewAuthZ(settings.Logger(), orgGetter, authz)
|
||||
@@ -305,10 +298,6 @@ func (provider *provider) AddToRouter(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := provider.addLLMPricingRuleRoutes(router); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := provider.addRulerRoutes(router); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ func (provider *provider) addRoleRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/roles/{id}/relation/{relation}/objects", handler.New(provider.authZ.AdminAccess(provider.authzHandler.GetObjects), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v1/roles/{id}/relations/{relation}/objects", handler.New(provider.authZ.AdminAccess(provider.authzHandler.GetObjects), handler.OpenAPIDef{
|
||||
ID: "GetObjects",
|
||||
Tags: []string{"role"},
|
||||
Summary: "Get objects for a role by relation",
|
||||
@@ -95,7 +95,7 @@ func (provider *provider) addRoleRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/roles/{id}/relation/{relation}/objects", handler.New(provider.authZ.AdminAccess(provider.authzHandler.PatchObjects), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v1/roles/{id}/relations/{relation}/objects", handler.New(provider.authZ.AdminAccess(provider.authzHandler.PatchObjects), handler.OpenAPIDef{
|
||||
ID: "PatchObjects",
|
||||
Tags: []string{"role"},
|
||||
Summary: "Patch objects for a role by relation",
|
||||
|
||||
29
pkg/flagger/flaggertest/flaggertest.go
Normal file
29
pkg/flagger/flaggertest/flaggertest.go
Normal file
@@ -0,0 +1,29 @@
|
||||
// Package flaggertest provides helpers for creating Flagger instances in tests.
|
||||
package flaggertest
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/flagger"
|
||||
"github.com/SigNoz/signoz/pkg/flagger/configflagger"
|
||||
"github.com/SigNoz/signoz/pkg/instrumentation/instrumentationtest"
|
||||
)
|
||||
|
||||
// New returns a Flagger with all flags at their registry defaults (all disabled).
|
||||
// Use this in tests that do not need any feature flag enabled.
|
||||
func New(t *testing.T) flagger.Flagger {
|
||||
t.Helper()
|
||||
registry := flagger.MustNewRegistry()
|
||||
fl, err := flagger.New(
|
||||
context.Background(),
|
||||
instrumentationtest.New().ToProviderSettings(),
|
||||
flagger.Config{},
|
||||
registry,
|
||||
configflagger.NewFactory(registry),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("flaggertest.New: %v", err)
|
||||
}
|
||||
return fl
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
package flagger
|
||||
|
||||
import "github.com/SigNoz/signoz/pkg/types/featuretypes"
|
||||
import (
|
||||
"github.com/SigNoz/signoz/pkg/types/featuretypes"
|
||||
)
|
||||
|
||||
var (
|
||||
FeatureUseSpanMetrics = featuretypes.MustNewName("use_span_metrics")
|
||||
@@ -8,6 +10,7 @@ var (
|
||||
FeatureHideRootUser = featuretypes.MustNewName("hide_root_user")
|
||||
FeatureGetMetersFromZeus = featuretypes.MustNewName("get_meters_from_zeus")
|
||||
FeaturePutMetersInZeus = featuretypes.MustNewName("put_meters_in_zeus")
|
||||
FeatureBodyJSONQuery = featuretypes.MustNewName("body_json_enabled") // Note: JSON Enabled is not restrictive of orgID, it is tenant level featureflag
|
||||
)
|
||||
|
||||
func MustNewRegistry() featuretypes.Registry {
|
||||
@@ -52,6 +55,14 @@ func MustNewRegistry() featuretypes.Registry {
|
||||
DefaultVariant: featuretypes.MustNewName("disabled"),
|
||||
Variants: featuretypes.NewBooleanVariants(),
|
||||
},
|
||||
&featuretypes.Feature{
|
||||
Name: FeatureBodyJSONQuery,
|
||||
Kind: featuretypes.KindBoolean,
|
||||
Stage: featuretypes.StageExperimental,
|
||||
Description: "Controls whether body JSON querying is enabled",
|
||||
DefaultVariant: featuretypes.MustNewName("disabled"),
|
||||
Variants: featuretypes.NewBooleanVariants(),
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
||||
@@ -1,158 +0,0 @@
|
||||
package impllmpricingrule
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/http/binding"
|
||||
"github.com/SigNoz/signoz/pkg/http/render"
|
||||
"github.com/SigNoz/signoz/pkg/modules/llmpricingrule"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/llmpricingruletypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
const maxLimit = 100
|
||||
|
||||
type handler struct {
|
||||
module llmpricingrule.Module
|
||||
providerSettings factory.ProviderSettings
|
||||
}
|
||||
|
||||
func NewHandler(module llmpricingrule.Module, providerSettings factory.ProviderSettings) llmpricingrule.Handler {
|
||||
return &handler{module: module, providerSettings: providerSettings}
|
||||
}
|
||||
|
||||
// List handles GET /api/v1/llm_pricing_rules.
|
||||
func (h *handler) List(rw 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(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
orgID := valuer.MustNewUUID(claims.OrgID)
|
||||
|
||||
var q llmpricingruletypes.ListPricingRulesQuery
|
||||
if err := binding.Query.BindQuery(r.URL.Query(), &q); err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
if q.Limit <= 0 {
|
||||
q.Limit = 20
|
||||
} else if q.Limit > maxLimit {
|
||||
q.Limit = maxLimit
|
||||
}
|
||||
if q.Offset < 0 {
|
||||
render.Error(rw, errors.Newf(errors.TypeInvalidInput, llmpricingruletypes.ErrCodePricingRuleInvalidInput, "offset must be a non-negative integer"))
|
||||
return
|
||||
}
|
||||
|
||||
rules, total, err := h.module.List(ctx, orgID, q.Offset, q.Limit)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
render.Success(rw, http.StatusOK, llmpricingruletypes.NewGettableLLMPricingRulesFromLLMPricingRules(rules, total, q.Offset, q.Limit))
|
||||
}
|
||||
|
||||
// Get handles GET /api/v1/llm_pricing_rules/{id}.
|
||||
func (h *handler) Get(rw 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(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
orgID := valuer.MustNewUUID(claims.OrgID)
|
||||
|
||||
id, err := ruleIDFromPath(r)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
rule, err := h.module.Get(ctx, orgID, id)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
render.Success(rw, http.StatusOK, rule)
|
||||
}
|
||||
|
||||
func (h *handler) Update(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
claims, err := authtypes.ClaimsFromContext(ctx)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
orgID := valuer.MustNewUUID(claims.OrgID)
|
||||
|
||||
req := new(llmpricingruletypes.UpdatableLLMPricingRules)
|
||||
if err := binding.JSON.BindBody(r.Body, req); err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
err = h.module.Update(ctx, orgID, claims.Email, req.Rules)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
render.Success(rw, http.StatusNoContent, nil)
|
||||
}
|
||||
|
||||
// Delete handles DELETE /api/v1/llm_pricing_rules/{id}.
|
||||
func (h *handler) Delete(rw 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(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
orgID := valuer.MustNewUUID(claims.OrgID)
|
||||
|
||||
id, err := ruleIDFromPath(r)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.module.Delete(ctx, orgID, id); err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
render.Success(rw, http.StatusNoContent, nil)
|
||||
}
|
||||
|
||||
// ruleIDFromPath extracts and validates the {id} path variable.
|
||||
func ruleIDFromPath(r *http.Request) (valuer.UUID, error) {
|
||||
raw := mux.Vars(r)["id"]
|
||||
id, err := valuer.NewUUID(raw)
|
||||
if err != nil {
|
||||
return valuer.UUID{}, errors.Wrapf(err, errors.TypeInvalidInput, llmpricingruletypes.ErrCodePricingRuleInvalidInput, "id is not a valid uuid")
|
||||
}
|
||||
return id, nil
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
package llmpricingrule
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/types/llmpricingruletypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
|
||||
type Module interface {
|
||||
List(ctx context.Context, orgID valuer.UUID, offset, limit int) ([]*llmpricingruletypes.LLMPricingRule, int, error)
|
||||
Get(ctx context.Context, orgID valuer.UUID, id valuer.UUID) (*llmpricingruletypes.LLMPricingRule, error)
|
||||
Update(ctx context.Context, orgID valuer.UUID, userEmail string, rules []llmpricingruletypes.UpdatableLLMPricingRule) (err error)
|
||||
Delete(ctx context.Context, orgID, id valuer.UUID) error
|
||||
}
|
||||
|
||||
// Handler defines the HTTP handler interface for pricing rule endpoints.
|
||||
type Handler interface {
|
||||
List(rw http.ResponseWriter, r *http.Request)
|
||||
Get(rw http.ResponseWriter, r *http.Request)
|
||||
Update(rw http.ResponseWriter, r *http.Request)
|
||||
Delete(rw http.ResponseWriter, r *http.Request)
|
||||
}
|
||||
@@ -16,6 +16,8 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/telemetrymetrics"
|
||||
"github.com/SigNoz/signoz/pkg/telemetrystore"
|
||||
"github.com/SigNoz/signoz/pkg/telemetrytraces"
|
||||
"github.com/SigNoz/signoz/pkg/types/featuretypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
|
||||
// NewFactory creates a new factory for the signoz querier provider.
|
||||
@@ -38,13 +40,13 @@ func NewFactory(
|
||||
}
|
||||
|
||||
func newProvider(
|
||||
_ context.Context,
|
||||
ctx context.Context,
|
||||
settings factory.ProviderSettings,
|
||||
cfg querier.Config,
|
||||
telemetryStore telemetrystore.TelemetryStore,
|
||||
prometheus prometheus.Prometheus,
|
||||
cache cache.Cache,
|
||||
flagger flagger.Flagger,
|
||||
fl flagger.Flagger,
|
||||
) (querier.Querier, error) {
|
||||
|
||||
// Create telemetry metadata store
|
||||
@@ -72,13 +74,14 @@ func newProvider(
|
||||
telemetrymetadata.DBName,
|
||||
telemetrymetadata.AttributesMetadataLocalTableName,
|
||||
telemetrymetadata.ColumnEvolutionMetadataTableName,
|
||||
fl,
|
||||
)
|
||||
|
||||
// Create trace statement builder
|
||||
traceFieldMapper := telemetrytraces.NewFieldMapper()
|
||||
traceConditionBuilder := telemetrytraces.NewConditionBuilder(traceFieldMapper)
|
||||
|
||||
traceAggExprRewriter := querybuilder.NewAggExprRewriter(settings, nil, traceFieldMapper, traceConditionBuilder, nil)
|
||||
traceAggExprRewriter := querybuilder.NewAggExprRewriter(settings, nil, traceFieldMapper, traceConditionBuilder, nil, false)
|
||||
traceStmtBuilder := telemetrytraces.NewTraceQueryStatementBuilder(
|
||||
settings,
|
||||
telemetryMetadataStore,
|
||||
@@ -98,15 +101,17 @@ func newProvider(
|
||||
traceAggExprRewriter,
|
||||
)
|
||||
|
||||
bodyJSONEnabled := fl.BooleanOrEmpty(ctx, flagger.FeatureBodyJSONQuery, featuretypes.NewFlaggerEvaluationContext(valuer.UUID{}))
|
||||
// Create log statement builder
|
||||
logFieldMapper := telemetrylogs.NewFieldMapper()
|
||||
logConditionBuilder := telemetrylogs.NewConditionBuilder(logFieldMapper)
|
||||
logFieldMapper := telemetrylogs.NewFieldMapper(bodyJSONEnabled)
|
||||
logConditionBuilder := telemetrylogs.NewConditionBuilder(logFieldMapper, bodyJSONEnabled)
|
||||
logAggExprRewriter := querybuilder.NewAggExprRewriter(
|
||||
settings,
|
||||
telemetrylogs.DefaultFullTextColumn,
|
||||
logFieldMapper,
|
||||
logConditionBuilder,
|
||||
telemetrylogs.GetBodyJSONKey,
|
||||
bodyJSONEnabled,
|
||||
)
|
||||
logStmtBuilder := telemetrylogs.NewLogQueryStatementBuilder(
|
||||
settings,
|
||||
@@ -116,6 +121,7 @@ func newProvider(
|
||||
logAggExprRewriter,
|
||||
telemetrylogs.DefaultFullTextColumn,
|
||||
telemetrylogs.GetBodyJSONKey,
|
||||
bodyJSONEnabled,
|
||||
)
|
||||
|
||||
// Create audit statement builder
|
||||
@@ -127,6 +133,7 @@ func newProvider(
|
||||
auditFieldMapper,
|
||||
auditConditionBuilder,
|
||||
nil,
|
||||
false,
|
||||
)
|
||||
auditStmtBuilder := telemetryaudit.NewAuditQueryStatementBuilder(
|
||||
settings,
|
||||
@@ -146,7 +153,7 @@ func newProvider(
|
||||
telemetryMetadataStore,
|
||||
metricFieldMapper,
|
||||
metricConditionBuilder,
|
||||
flagger,
|
||||
fl,
|
||||
)
|
||||
|
||||
// Create meter statement builder
|
||||
|
||||
@@ -1774,6 +1774,15 @@ func (aH *APIHandler) getFeatureFlags(w http.ResponseWriter, r *http.Request) {
|
||||
Route: "",
|
||||
})
|
||||
|
||||
bodyJSONQuery := aH.Signoz.Flagger.BooleanOrEmpty(r.Context(), flagger.FeatureBodyJSONQuery, evalCtx)
|
||||
featureSet = append(featureSet, &licensetypes.Feature{
|
||||
Name: valuer.NewString(flagger.FeatureBodyJSONQuery.String()),
|
||||
Active: bodyJSONQuery,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
})
|
||||
|
||||
if constants.IsDotMetricsEnabled {
|
||||
for idx, feature := range featureSet {
|
||||
if feature.Name == licensetypes.DotMetricsEnabled {
|
||||
|
||||
@@ -11,15 +11,16 @@ import (
|
||||
"github.com/google/uuid"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/flagger"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/agentConf"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/constants"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/interfaces"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/model"
|
||||
v3 "github.com/SigNoz/signoz/pkg/query-service/model/v3"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/utils"
|
||||
"github.com/SigNoz/signoz/pkg/querybuilder"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/featuretypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/opamptypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/pipelinetypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
@@ -38,18 +39,21 @@ type LogParsingPipelineController struct {
|
||||
GetIntegrationPipelines func(context.Context, string) ([]pipelinetypes.GettablePipeline, error)
|
||||
// TODO(Piyush): remove with qbv5 migration
|
||||
reader interfaces.Reader
|
||||
fl flagger.Flagger
|
||||
}
|
||||
|
||||
func NewLogParsingPipelinesController(
|
||||
sqlStore sqlstore.SQLStore,
|
||||
getIntegrationPipelines func(context.Context, string) ([]pipelinetypes.GettablePipeline, error),
|
||||
reader interfaces.Reader,
|
||||
fl flagger.Flagger,
|
||||
) (*LogParsingPipelineController, error) {
|
||||
repo := NewRepo(sqlStore)
|
||||
return &LogParsingPipelineController{
|
||||
Repo: repo,
|
||||
GetIntegrationPipelines: getIntegrationPipelines,
|
||||
reader: reader,
|
||||
fl: fl,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -363,14 +367,14 @@ func (pc *LogParsingPipelineController) AgentFeatureType() agentConf.AgentFeatur
|
||||
|
||||
// Implements agentConf.AgentFeature interface.
|
||||
// RecommendAgentConfig generates the collector config to be sent to agents.
|
||||
// The normalize pipeline (when BodyJSONQueryEnabled) is injected here, after
|
||||
// The normalize pipeline (when body_json_enabled feature flag is on) is injected here, after
|
||||
// rawPipelineData is serialized. So it is only present in the config sent to
|
||||
// the collector and never persisted to the database as part of the user's pipeline list.
|
||||
//
|
||||
// NOTE: The configId sent to agents is derived from the pipeline version number
|
||||
// (e.g. "LogPipelines:5"), not the YAML content. If server-side logic changes
|
||||
// the generated YAML without bumping the version (e.g. toggling BodyJSONQueryEnabled
|
||||
// or updating operator IfExpressions), agents that already applied that version will
|
||||
// the generated YAML without bumping the version (e.g. toggling the body_json_enabled
|
||||
// flag or updating operator IfExpressions), agents that already applied that version will
|
||||
// not re-apply the new config. In such cases, users must save a new pipeline version
|
||||
// via the API to force agents to pick up the change.
|
||||
func (pc *LogParsingPipelineController) RecommendAgentConfig(
|
||||
@@ -398,7 +402,7 @@ func (pc *LogParsingPipelineController) RecommendAgentConfig(
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
if querybuilder.BodyJSONQueryEnabled {
|
||||
if pc.fl.BooleanOrEmpty(ctx, flagger.FeatureBodyJSONQuery, featuretypes.NewFlaggerEvaluationContext(orgId)) {
|
||||
// add default normalize pipeline at the beginning, only for sending to collector
|
||||
enrichedPipelines = append([]pipelinetypes.GettablePipeline{pc.getNormalizePipeline()}, enrichedPipelines...)
|
||||
}
|
||||
|
||||
@@ -93,6 +93,7 @@ func NewServer(config signoz.Config, signoz *signoz.SigNoz) (*Server, error) {
|
||||
signoz.SQLStore,
|
||||
integrationsController.GetPipelinesForInstalledIntegrations,
|
||||
reader,
|
||||
signoz.Flagger,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -114,7 +115,7 @@ func NewServer(config signoz.Config, signoz *signoz.SigNoz) (*Server, error) {
|
||||
|
||||
s := &Server{
|
||||
config: config,
|
||||
signoz: signoz,
|
||||
signoz: signoz,
|
||||
httpHostPort: constants.HTTPHostPort,
|
||||
unavailableChannel: make(chan healthcheck.Status),
|
||||
}
|
||||
@@ -296,4 +297,3 @@ func (s *Server) Stop(ctx context.Context) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
package rules
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/flagger"
|
||||
"github.com/SigNoz/signoz/pkg/flagger/flaggertest"
|
||||
"github.com/SigNoz/signoz/pkg/instrumentation/instrumentationtest"
|
||||
"github.com/SigNoz/signoz/pkg/querier"
|
||||
"github.com/SigNoz/signoz/pkg/querybuilder"
|
||||
@@ -14,21 +13,12 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/telemetrytraces"
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes/telemetrytypestest"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func prepareQuerierForMetrics(t *testing.T, telemetryStore telemetrystore.TelemetryStore) (querier.Querier, *telemetrytypestest.MockMetadataStore) {
|
||||
providerSettings := instrumentationtest.New().ToProviderSettings()
|
||||
metadataStore := telemetrytypestest.NewMockMetadataStore()
|
||||
|
||||
flagger, err := flagger.New(
|
||||
context.Background(),
|
||||
instrumentationtest.New().ToProviderSettings(),
|
||||
flagger.Config{},
|
||||
flagger.MustNewRegistry(),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
metricFieldMapper := telemetrymetrics.NewFieldMapper()
|
||||
metricConditionBuilder := telemetrymetrics.NewConditionBuilder(metricFieldMapper)
|
||||
metricStmtBuilder := telemetrymetrics.NewMetricQueryStatementBuilder(
|
||||
@@ -36,7 +26,7 @@ func prepareQuerierForMetrics(t *testing.T, telemetryStore telemetrystore.Teleme
|
||||
metadataStore,
|
||||
metricFieldMapper,
|
||||
metricConditionBuilder,
|
||||
flagger,
|
||||
flaggertest.New(t),
|
||||
)
|
||||
|
||||
return querier.New(
|
||||
@@ -54,8 +44,8 @@ func prepareQuerierForMetrics(t *testing.T, telemetryStore telemetrystore.Teleme
|
||||
), metadataStore
|
||||
}
|
||||
|
||||
func prepareQuerierForLogs(telemetryStore telemetrystore.TelemetryStore, keysMap map[string][]*telemetrytypes.TelemetryFieldKey) querier.Querier {
|
||||
|
||||
func prepareQuerierForLogs(t *testing.T, telemetryStore telemetrystore.TelemetryStore, keysMap map[string][]*telemetrytypes.TelemetryFieldKey) querier.Querier {
|
||||
t.Helper()
|
||||
providerSettings := instrumentationtest.New().ToProviderSettings()
|
||||
metadataStore := telemetrytypestest.NewMockMetadataStore()
|
||||
|
||||
@@ -66,14 +56,15 @@ func prepareQuerierForLogs(telemetryStore telemetrystore.TelemetryStore, keysMap
|
||||
}
|
||||
metadataStore.KeysMap = keysMap
|
||||
|
||||
logFieldMapper := telemetrylogs.NewFieldMapper()
|
||||
logConditionBuilder := telemetrylogs.NewConditionBuilder(logFieldMapper)
|
||||
logFieldMapper := telemetrylogs.NewFieldMapper(false)
|
||||
logConditionBuilder := telemetrylogs.NewConditionBuilder(logFieldMapper, false)
|
||||
logAggExprRewriter := querybuilder.NewAggExprRewriter(
|
||||
providerSettings,
|
||||
telemetrylogs.DefaultFullTextColumn,
|
||||
logFieldMapper,
|
||||
logConditionBuilder,
|
||||
telemetrylogs.GetBodyJSONKey,
|
||||
false,
|
||||
)
|
||||
logStmtBuilder := telemetrylogs.NewLogQueryStatementBuilder(
|
||||
providerSettings,
|
||||
@@ -83,6 +74,7 @@ func prepareQuerierForLogs(telemetryStore telemetrystore.TelemetryStore, keysMap
|
||||
logAggExprRewriter,
|
||||
telemetrylogs.DefaultFullTextColumn,
|
||||
telemetrylogs.GetBodyJSONKey,
|
||||
false,
|
||||
)
|
||||
|
||||
return querier.New(
|
||||
@@ -100,7 +92,8 @@ func prepareQuerierForLogs(telemetryStore telemetrystore.TelemetryStore, keysMap
|
||||
)
|
||||
}
|
||||
|
||||
func prepareQuerierForTraces(telemetryStore telemetrystore.TelemetryStore, keysMap map[string][]*telemetrytypes.TelemetryFieldKey) querier.Querier {
|
||||
func prepareQuerierForTraces(t *testing.T, telemetryStore telemetrystore.TelemetryStore, keysMap map[string][]*telemetrytypes.TelemetryFieldKey) querier.Querier {
|
||||
t.Helper()
|
||||
|
||||
providerSettings := instrumentationtest.New().ToProviderSettings()
|
||||
metadataStore := telemetrytypestest.NewMockMetadataStore()
|
||||
@@ -116,7 +109,7 @@ func prepareQuerierForTraces(telemetryStore telemetrystore.TelemetryStore, keysM
|
||||
traceFieldMapper := telemetrytraces.NewFieldMapper()
|
||||
traceConditionBuilder := telemetrytraces.NewConditionBuilder(traceFieldMapper)
|
||||
|
||||
traceAggExprRewriter := querybuilder.NewAggExprRewriter(providerSettings, nil, traceFieldMapper, traceConditionBuilder, nil)
|
||||
traceAggExprRewriter := querybuilder.NewAggExprRewriter(providerSettings, nil, traceFieldMapper, traceConditionBuilder, nil, false)
|
||||
traceStmtBuilder := telemetrytraces.NewTraceQueryStatementBuilder(
|
||||
providerSettings,
|
||||
metadataStore,
|
||||
|
||||
@@ -829,7 +829,7 @@ func TestThresholdRuleTracesLink(t *testing.T) {
|
||||
WithArgs(nil, nil, nil, nil, nil, nil, nil).
|
||||
WillReturnRows(rows)
|
||||
|
||||
querier := prepareQuerierForTraces(telemetryStore, keysMap)
|
||||
querier := prepareQuerierForTraces(t, telemetryStore, keysMap)
|
||||
|
||||
postableRule.RuleCondition.CompareOperator = c.compareOperator
|
||||
postableRule.RuleCondition.MatchType = c.matchType
|
||||
@@ -946,7 +946,7 @@ func TestThresholdRuleLogsLink(t *testing.T) {
|
||||
WithArgs(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil).
|
||||
WillReturnRows(rows)
|
||||
|
||||
querier := prepareQuerierForLogs(telemetryStore, keysMap)
|
||||
querier := prepareQuerierForLogs(t, telemetryStore, keysMap)
|
||||
|
||||
postableRule.RuleCondition.CompareOperator = c.compareOperator
|
||||
postableRule.RuleCondition.MatchType = c.matchType
|
||||
|
||||
@@ -21,6 +21,7 @@ type aggExprRewriter struct {
|
||||
fieldMapper qbtypes.FieldMapper
|
||||
conditionBuilder qbtypes.ConditionBuilder
|
||||
jsonKeyToKey qbtypes.JsonKeyToFieldFunc
|
||||
bodyJSONEnabled bool
|
||||
}
|
||||
|
||||
var _ qbtypes.AggExprRewriter = (*aggExprRewriter)(nil)
|
||||
@@ -31,6 +32,7 @@ func NewAggExprRewriter(
|
||||
fieldMapper qbtypes.FieldMapper,
|
||||
conditionBuilder qbtypes.ConditionBuilder,
|
||||
jsonKeyToKey qbtypes.JsonKeyToFieldFunc,
|
||||
bodyJSONEnabled bool,
|
||||
) *aggExprRewriter {
|
||||
set := factory.NewScopedProviderSettings(settings, "github.com/SigNoz/signoz/pkg/querybuilder/agg_rewrite")
|
||||
|
||||
@@ -40,6 +42,7 @@ func NewAggExprRewriter(
|
||||
fieldMapper: fieldMapper,
|
||||
conditionBuilder: conditionBuilder,
|
||||
jsonKeyToKey: jsonKeyToKey,
|
||||
bodyJSONEnabled: bodyJSONEnabled,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,6 +89,7 @@ func (r *aggExprRewriter) Rewrite(
|
||||
r.fieldMapper,
|
||||
r.conditionBuilder,
|
||||
r.jsonKeyToKey,
|
||||
r.bodyJSONEnabled,
|
||||
)
|
||||
// Rewrite the first select item (our expression)
|
||||
if err := sel.SelectItems[0].Accept(visitor); err != nil {
|
||||
@@ -138,6 +142,7 @@ type exprVisitor struct {
|
||||
fieldMapper qbtypes.FieldMapper
|
||||
conditionBuilder qbtypes.ConditionBuilder
|
||||
jsonKeyToKey qbtypes.JsonKeyToFieldFunc
|
||||
bodyJSONEnabled bool
|
||||
Modified bool
|
||||
chArgs []any
|
||||
isRate bool
|
||||
@@ -153,6 +158,7 @@ func newExprVisitor(
|
||||
fieldMapper qbtypes.FieldMapper,
|
||||
conditionBuilder qbtypes.ConditionBuilder,
|
||||
jsonKeyToKey qbtypes.JsonKeyToFieldFunc,
|
||||
bodyJSONEnabled bool,
|
||||
) *exprVisitor {
|
||||
return &exprVisitor{
|
||||
ctx: ctx,
|
||||
@@ -164,6 +170,7 @@ func newExprVisitor(
|
||||
fieldMapper: fieldMapper,
|
||||
conditionBuilder: conditionBuilder,
|
||||
jsonKeyToKey: jsonKeyToKey,
|
||||
bodyJSONEnabled: bodyJSONEnabled,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -237,7 +244,7 @@ func (v *exprVisitor) VisitFunctionExpr(fn *chparser.FunctionExpr) error {
|
||||
for i := 0; i < len(args)-1; i++ {
|
||||
origVal := args[i].String()
|
||||
fieldKey := telemetrytypes.GetFieldKeyFromKeyText(origVal)
|
||||
expr, exprArgs, err := CollisionHandledFinalExpr(v.ctx, v.startNs, v.endNs, &fieldKey, v.fieldMapper, v.conditionBuilder, v.fieldKeys, dataType, v.jsonKeyToKey)
|
||||
expr, exprArgs, err := CollisionHandledFinalExpr(v.ctx, v.startNs, v.endNs, &fieldKey, v.fieldMapper, v.conditionBuilder, v.fieldKeys, dataType, v.jsonKeyToKey, v.bodyJSONEnabled)
|
||||
if err != nil {
|
||||
return errors.WrapInvalidInputf(err, errors.CodeInvalidInput, "failed to get table field name for %q", origVal)
|
||||
}
|
||||
@@ -255,7 +262,7 @@ func (v *exprVisitor) VisitFunctionExpr(fn *chparser.FunctionExpr) error {
|
||||
for i, arg := range args {
|
||||
orig := arg.String()
|
||||
fieldKey := telemetrytypes.GetFieldKeyFromKeyText(orig)
|
||||
expr, exprArgs, err := CollisionHandledFinalExpr(v.ctx, v.startNs, v.endNs, &fieldKey, v.fieldMapper, v.conditionBuilder, v.fieldKeys, dataType, v.jsonKeyToKey)
|
||||
expr, exprArgs, err := CollisionHandledFinalExpr(v.ctx, v.startNs, v.endNs, &fieldKey, v.fieldMapper, v.conditionBuilder, v.fieldKeys, dataType, v.jsonKeyToKey, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
package querybuilder
|
||||
|
||||
import (
|
||||
"os"
|
||||
)
|
||||
|
||||
const (
|
||||
TrueConditionLiteral = "true"
|
||||
SkipConditionLiteral = "__skip__"
|
||||
@@ -13,15 +9,3 @@ const (
|
||||
var (
|
||||
SkippableConditionLiterals = []string{SkipConditionLiteral, ErrorConditionLiteral}
|
||||
)
|
||||
|
||||
var (
|
||||
BodyJSONQueryEnabled = GetOrDefaultEnv("BODY_JSON_QUERY_ENABLED", "false") == "true"
|
||||
)
|
||||
|
||||
func GetOrDefaultEnv(key string, fallback string) string {
|
||||
v := os.Getenv(key)
|
||||
if len(v) == 0 {
|
||||
return fallback
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ func CollisionHandledFinalExpr(
|
||||
keys map[string][]*telemetrytypes.TelemetryFieldKey,
|
||||
requiredDataType telemetrytypes.FieldDataType,
|
||||
jsonKeyToKey qbtypes.JsonKeyToFieldFunc,
|
||||
bodyJSONEnabled bool,
|
||||
) (string, []any, error) {
|
||||
|
||||
if requiredDataType != telemetrytypes.FieldDataTypeString &&
|
||||
@@ -106,7 +107,7 @@ func CollisionHandledFinalExpr(
|
||||
}
|
||||
|
||||
// first if condition covers the older tests and second if condition covers the array conditions
|
||||
if !BodyJSONQueryEnabled && field.FieldContext == telemetrytypes.FieldContextBody && jsonKeyToKey != nil {
|
||||
if !bodyJSONEnabled && field.FieldContext == telemetrytypes.FieldContextBody && jsonKeyToKey != nil {
|
||||
return "", nil, errors.NewInvalidInputf(errors.CodeInvalidInput, "Group by/Aggregation isn't available for the body column")
|
||||
} else if strings.Contains(field.Name, telemetrytypes.ArraySep) || strings.Contains(field.Name, telemetrytypes.ArrayAnyIndex) {
|
||||
return "", nil, errors.NewInvalidInputf(errors.CodeInvalidInput, "Group by/Aggregation isn't available for the Array Paths: %s", field.Name)
|
||||
|
||||
@@ -36,6 +36,7 @@ type filterExpressionVisitor struct {
|
||||
builder *sqlbuilder.SelectBuilder
|
||||
fullTextColumn *telemetrytypes.TelemetryFieldKey
|
||||
jsonKeyToKey qbtypes.JsonKeyToFieldFunc
|
||||
bodyJSONEnabled bool
|
||||
skipResourceFilter bool
|
||||
skipFullTextFilter bool
|
||||
skipFunctionCalls bool
|
||||
@@ -56,6 +57,7 @@ type FilterExprVisitorOpts struct {
|
||||
Builder *sqlbuilder.SelectBuilder
|
||||
FullTextColumn *telemetrytypes.TelemetryFieldKey
|
||||
JsonKeyToKey qbtypes.JsonKeyToFieldFunc
|
||||
BodyJSONEnabled bool
|
||||
SkipResourceFilter bool
|
||||
SkipFullTextFilter bool
|
||||
SkipFunctionCalls bool
|
||||
@@ -76,6 +78,7 @@ func newFilterExpressionVisitor(opts FilterExprVisitorOpts) *filterExpressionVis
|
||||
builder: opts.Builder,
|
||||
fullTextColumn: opts.FullTextColumn,
|
||||
jsonKeyToKey: opts.JsonKeyToKey,
|
||||
bodyJSONEnabled: opts.BodyJSONEnabled,
|
||||
skipResourceFilter: opts.SkipResourceFilter,
|
||||
skipFullTextFilter: opts.SkipFullTextFilter,
|
||||
skipFunctionCalls: opts.SkipFunctionCalls,
|
||||
@@ -751,8 +754,7 @@ func (v *filterExpressionVisitor) VisitFunctionCall(ctx *grammar.FunctionCallCon
|
||||
return ErrorConditionLiteral
|
||||
}
|
||||
|
||||
// filter arrays from keys
|
||||
if BodyJSONQueryEnabled && functionName != "hasToken" {
|
||||
if v.bodyJSONEnabled && functionName != "hasToken" {
|
||||
filteredKeys := []*telemetrytypes.TelemetryFieldKey{}
|
||||
for _, key := range keys {
|
||||
if key.FieldDataType.IsArray() {
|
||||
@@ -793,7 +795,7 @@ func (v *filterExpressionVisitor) VisitFunctionCall(ctx *grammar.FunctionCallCon
|
||||
// this is that all other functions only support array fields
|
||||
if key.FieldContext == telemetrytypes.FieldContextBody {
|
||||
var err error
|
||||
if BodyJSONQueryEnabled {
|
||||
if v.bodyJSONEnabled {
|
||||
fieldName, err = v.fieldMapper.FieldFor(v.context, v.startNs, v.endNs, key)
|
||||
if err != nil {
|
||||
v.errors = append(v.errors, fmt.Sprintf("failed to get field name for key %s: %s", key.Name, err.Error()))
|
||||
@@ -936,7 +938,7 @@ func (v *filterExpressionVisitor) VisitKey(ctx *grammar.KeyContext) any {
|
||||
// Note: Skip this logic if body json query is enabled so we can look up the key inside fields
|
||||
//
|
||||
// TODO(Piyush): After entire migration this is supposed to be removed.
|
||||
if !BodyJSONQueryEnabled && fieldKey.FieldContext == telemetrytypes.FieldContextBody {
|
||||
if fieldKey.FieldContext == telemetrytypes.FieldContextBody && !v.bodyJSONEnabled {
|
||||
fieldKeysForName = append(fieldKeysForName, &fieldKey)
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@ package signoz
|
||||
import (
|
||||
"github.com/SigNoz/signoz/pkg/alertmanager"
|
||||
"github.com/SigNoz/signoz/pkg/alertmanager/signozalertmanager"
|
||||
"github.com/SigNoz/signoz/pkg/ruler"
|
||||
"github.com/SigNoz/signoz/pkg/ruler/signozruler"
|
||||
"github.com/SigNoz/signoz/pkg/analytics"
|
||||
"github.com/SigNoz/signoz/pkg/authz"
|
||||
"github.com/SigNoz/signoz/pkg/authz/signozauthzapi"
|
||||
@@ -22,8 +24,6 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/modules/fields/implfields"
|
||||
"github.com/SigNoz/signoz/pkg/modules/inframonitoring"
|
||||
"github.com/SigNoz/signoz/pkg/modules/inframonitoring/implinframonitoring"
|
||||
"github.com/SigNoz/signoz/pkg/modules/llmpricingrule"
|
||||
"github.com/SigNoz/signoz/pkg/modules/llmpricingrule/impllmpricingrule"
|
||||
"github.com/SigNoz/signoz/pkg/modules/metricsexplorer"
|
||||
"github.com/SigNoz/signoz/pkg/modules/metricsexplorer/implmetricsexplorer"
|
||||
"github.com/SigNoz/signoz/pkg/modules/quickfilter"
|
||||
@@ -43,8 +43,6 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/modules/tracefunnel"
|
||||
"github.com/SigNoz/signoz/pkg/modules/tracefunnel/impltracefunnel"
|
||||
"github.com/SigNoz/signoz/pkg/querier"
|
||||
"github.com/SigNoz/signoz/pkg/ruler"
|
||||
"github.com/SigNoz/signoz/pkg/ruler/signozruler"
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
"github.com/SigNoz/signoz/pkg/zeus"
|
||||
)
|
||||
@@ -73,7 +71,6 @@ type Handlers struct {
|
||||
RuleStateHistory rulestatehistory.Handler
|
||||
AlertmanagerHandler alertmanager.Handler
|
||||
RulerHandler ruler.Handler
|
||||
LLMPricingRuleHandler llmpricingrule.Handler
|
||||
}
|
||||
|
||||
func NewHandlers(
|
||||
@@ -116,6 +113,5 @@ func NewHandlers(
|
||||
CloudIntegrationHandler: implcloudintegration.NewHandler(modules.CloudIntegration),
|
||||
AlertmanagerHandler: signozalertmanager.NewHandler(alertmanagerService),
|
||||
RulerHandler: signozruler.NewHandler(rulerService),
|
||||
LLMPricingRuleHandler: impllmpricingrule.NewHandler(nil, providerSettings),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ func TestNewHandlers(t *testing.T) {
|
||||
userRoleStore := impluser.NewUserRoleStore(sqlstore, providerSettings)
|
||||
|
||||
userGetter := impluser.NewGetter(impluser.NewStore(sqlstore, providerSettings), userRoleStore, flagger)
|
||||
modules := NewModules(sqlstore, tokenizer, emailing, providerSettings, orgGetter, alertmanager, nil, nil, nil, nil, nil, nil, nil, queryParser, Config{}, dashboardModule, userGetter, userRoleStore, nil, nil)
|
||||
modules := NewModules(sqlstore, tokenizer, emailing, providerSettings, orgGetter, alertmanager, nil, nil, nil, nil, nil, nil, nil, queryParser, Config{}, dashboardModule, userGetter, userRoleStore, nil, nil, flagger)
|
||||
|
||||
querierHandler := querier.NewHandler(providerSettings, nil, nil)
|
||||
registryHandler := factory.NewHandler(nil)
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/cache"
|
||||
"github.com/SigNoz/signoz/pkg/emailing"
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/flagger"
|
||||
"github.com/SigNoz/signoz/pkg/modules/apdex"
|
||||
"github.com/SigNoz/signoz/pkg/modules/apdex/implapdex"
|
||||
"github.com/SigNoz/signoz/pkg/modules/authdomain"
|
||||
@@ -99,6 +100,7 @@ func NewModules(
|
||||
userRoleStore authtypes.UserRoleStore,
|
||||
serviceAccount serviceaccount.Module,
|
||||
cloudIntegrationModule cloudintegration.Module,
|
||||
fl flagger.Flagger,
|
||||
) Modules {
|
||||
quickfilter := implquickfilter.NewModule(implquickfilter.NewStore(sqlstore))
|
||||
orgSetter := implorganization.NewSetter(implorganization.NewStore(sqlstore), alertmanager, quickfilter)
|
||||
|
||||
@@ -56,7 +56,7 @@ func TestNewModules(t *testing.T) {
|
||||
|
||||
serviceAccount := implserviceaccount.NewModule(implserviceaccount.NewStore(sqlstore), nil, nil, nil, providerSettings, serviceaccount.Config{})
|
||||
|
||||
modules := NewModules(sqlstore, tokenizer, emailing, providerSettings, orgGetter, alertmanager, nil, nil, nil, nil, nil, nil, nil, queryParser, Config{}, dashboardModule, userGetter, userRoleStore, serviceAccount, implcloudintegration.NewModule())
|
||||
modules := NewModules(sqlstore, tokenizer, emailing, providerSettings, orgGetter, alertmanager, nil, nil, nil, nil, nil, nil, nil, queryParser, Config{}, dashboardModule, userGetter, userRoleStore, serviceAccount, implcloudintegration.NewModule(), flagger)
|
||||
|
||||
reflectVal := reflect.ValueOf(modules)
|
||||
for i := 0; i < reflectVal.NumField(); i++ {
|
||||
|
||||
@@ -17,7 +17,6 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/global"
|
||||
"github.com/SigNoz/signoz/pkg/http/handler"
|
||||
"github.com/SigNoz/signoz/pkg/instrumentation"
|
||||
"github.com/SigNoz/signoz/pkg/modules/llmpricingrule"
|
||||
"github.com/SigNoz/signoz/pkg/modules/authdomain"
|
||||
"github.com/SigNoz/signoz/pkg/modules/cloudintegration"
|
||||
"github.com/SigNoz/signoz/pkg/modules/dashboard"
|
||||
@@ -75,7 +74,6 @@ func NewOpenAPI(ctx context.Context, instrumentation instrumentation.Instrumenta
|
||||
struct{ cloudintegration.Handler }{},
|
||||
struct{ rulestatehistory.Handler }{},
|
||||
struct{ alertmanager.Handler }{},
|
||||
struct{ llmpricingrule.Handler }{},
|
||||
struct{ ruler.Handler }{},
|
||||
).New(ctx, instrumentation.ToProviderSettings(), apiserver.Config{})
|
||||
if err != nil {
|
||||
|
||||
@@ -282,7 +282,6 @@ func NewAPIServerProviderFactories(orgGetter organization.Getter, authz authz.Au
|
||||
handlers.CloudIntegrationHandler,
|
||||
handlers.RuleStateHistory,
|
||||
handlers.AlertmanagerHandler,
|
||||
handlers.LLMPricingRuleHandler,
|
||||
handlers.RulerHandler,
|
||||
),
|
||||
)
|
||||
|
||||
@@ -410,6 +410,7 @@ func New(
|
||||
telemetrymetadata.DBName,
|
||||
telemetrymetadata.AttributesMetadataLocalTableName,
|
||||
telemetrymetadata.ColumnEvolutionMetadataTableName,
|
||||
flagger,
|
||||
)
|
||||
|
||||
global, err := factory.NewProviderFromNamedMap(
|
||||
@@ -431,7 +432,7 @@ func New(
|
||||
}
|
||||
|
||||
// Initialize all modules
|
||||
modules := NewModules(sqlstore, tokenizer, emailing, providerSettings, orgGetter, alertmanager, analytics, querier, telemetrystore, telemetryMetadataStore, authNs, authz, cache, queryParser, config, dashboard, userGetter, userRoleStore, serviceAccount, cloudIntegrationModule)
|
||||
modules := NewModules(sqlstore, tokenizer, emailing, providerSettings, orgGetter, alertmanager, analytics, querier, telemetrystore, telemetryMetadataStore, authNs, authz, cache, queryParser, config, dashboard, userGetter, userRoleStore, serviceAccount, cloudIntegrationModule, flagger)
|
||||
|
||||
// Initialize ruler from the variant-specific provider factories
|
||||
rulerInstance, err := factory.NewProviderFromNamedMap(ctx, providerSettings, config.Ruler, rulerProviderFactories(cache, alertmanager, sqlstore, telemetrystore, telemetryMetadataStore, prometheus, orgGetter, modules.RuleStateHistory, querier, queryParser), "signoz")
|
||||
|
||||
@@ -319,7 +319,7 @@ func (b *auditQueryStatementBuilder) buildTimeSeriesQuery(
|
||||
|
||||
fieldNames := make([]string, 0, len(query.GroupBy))
|
||||
for _, gb := range query.GroupBy {
|
||||
expr, args, err := querybuilder.CollisionHandledFinalExpr(ctx, start, end, &gb.TelemetryFieldKey, b.fm, b.cb, keys, telemetrytypes.FieldDataTypeString, b.jsonKeyToKey)
|
||||
expr, args, err := querybuilder.CollisionHandledFinalExpr(ctx, start, end, &gb.TelemetryFieldKey, b.fm, b.cb, keys, telemetrytypes.FieldDataTypeString, b.jsonKeyToKey, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -456,7 +456,7 @@ func (b *auditQueryStatementBuilder) buildScalarQuery(
|
||||
var allGroupByArgs []any
|
||||
|
||||
for _, gb := range query.GroupBy {
|
||||
expr, args, err := querybuilder.CollisionHandledFinalExpr(ctx, start, end, &gb.TelemetryFieldKey, b.fm, b.cb, keys, telemetrytypes.FieldDataTypeString, b.jsonKeyToKey)
|
||||
expr, args, err := querybuilder.CollisionHandledFinalExpr(ctx, start, end, &gb.TelemetryFieldKey, b.fm, b.cb, keys, telemetrytypes.FieldDataTypeString, b.jsonKeyToKey, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -46,13 +46,14 @@ func auditFieldKeyMap() map[string][]*telemetrytypes.TelemetryFieldKey {
|
||||
}
|
||||
}
|
||||
|
||||
func newTestAuditStatementBuilder() *auditQueryStatementBuilder {
|
||||
func newTestAuditStatementBuilder(t *testing.T) *auditQueryStatementBuilder {
|
||||
t.Helper()
|
||||
mockMetadataStore := telemetrytypestest.NewMockMetadataStore()
|
||||
mockMetadataStore.KeysMap = auditFieldKeyMap()
|
||||
|
||||
fm := NewFieldMapper()
|
||||
cb := NewConditionBuilder(fm)
|
||||
aggExprRewriter := querybuilder.NewAggExprRewriter(instrumentationtest.New().ToProviderSettings(), nil, fm, cb, nil)
|
||||
aggExprRewriter := querybuilder.NewAggExprRewriter(instrumentationtest.New().ToProviderSettings(), nil, fm, cb, nil, false)
|
||||
|
||||
return NewAuditQueryStatementBuilder(
|
||||
instrumentationtest.New().ToProviderSettings(),
|
||||
@@ -66,7 +67,7 @@ func newTestAuditStatementBuilder() *auditQueryStatementBuilder {
|
||||
}
|
||||
|
||||
func TestStatementBuilder(t *testing.T) {
|
||||
statementBuilder := newTestAuditStatementBuilder()
|
||||
statementBuilder := newTestAuditStatementBuilder(t)
|
||||
ctx := context.Background()
|
||||
|
||||
testCases := []struct {
|
||||
|
||||
@@ -14,11 +14,12 @@ import (
|
||||
)
|
||||
|
||||
type conditionBuilder struct {
|
||||
fm qbtypes.FieldMapper
|
||||
fm qbtypes.FieldMapper
|
||||
bodyJSONEnabled bool
|
||||
}
|
||||
|
||||
func NewConditionBuilder(fm qbtypes.FieldMapper) *conditionBuilder {
|
||||
return &conditionBuilder{fm: fm}
|
||||
func NewConditionBuilder(fm qbtypes.FieldMapper, bodyJSONEnabled bool) *conditionBuilder {
|
||||
return &conditionBuilder{fm: fm, bodyJSONEnabled: bodyJSONEnabled}
|
||||
}
|
||||
|
||||
func (c *conditionBuilder) conditionFor(
|
||||
@@ -36,7 +37,7 @@ func (c *conditionBuilder) conditionFor(
|
||||
|
||||
// TODO(Piyush): Update this to support multiple JSON columns based on evolutions
|
||||
for _, column := range columns {
|
||||
if column.Type.GetType() == schema.ColumnTypeEnumJSON && querybuilder.BodyJSONQueryEnabled && key.Name != messageSubField {
|
||||
if column.Type.GetType() == schema.ColumnTypeEnumJSON && c.bodyJSONEnabled && key.Name != messageSubField {
|
||||
valueType, value := InferDataType(value, operator, key)
|
||||
cond, err := NewJSONConditionBuilder(key, valueType).buildJSONCondition(operator, value, sb)
|
||||
if err != nil {
|
||||
@@ -56,7 +57,7 @@ func (c *conditionBuilder) conditionFor(
|
||||
}
|
||||
|
||||
// Check if this is a body JSON search - either by FieldContext
|
||||
if key.FieldContext == telemetrytypes.FieldContextBody && !querybuilder.BodyJSONQueryEnabled {
|
||||
if key.FieldContext == telemetrytypes.FieldContextBody && !c.bodyJSONEnabled {
|
||||
fieldExpression, value = GetBodyJSONKey(ctx, key, operator, value)
|
||||
}
|
||||
|
||||
@@ -167,7 +168,7 @@ func (c *conditionBuilder) conditionFor(
|
||||
// in the UI based query builder, `exists` and `not exists` are used for
|
||||
// key membership checks, so depending on the column type, the condition changes
|
||||
case qbtypes.FilterOperatorExists, qbtypes.FilterOperatorNotExists:
|
||||
if key.FieldContext == telemetrytypes.FieldContextBody && !querybuilder.BodyJSONQueryEnabled {
|
||||
if key.FieldContext == telemetrytypes.FieldContextBody && !c.bodyJSONEnabled {
|
||||
if operator == qbtypes.FilterOperatorExists {
|
||||
return GetBodyJSONKeyForExists(ctx, key, operator, value), nil
|
||||
} else {
|
||||
@@ -287,7 +288,7 @@ func (c *conditionBuilder) ConditionFor(
|
||||
case telemetrytypes.FieldContextBody:
|
||||
// Querying JSON fields already account for Nullability of fields
|
||||
// so additional exists checks are not needed
|
||||
if querybuilder.BodyJSONQueryEnabled {
|
||||
if c.bodyJSONEnabled {
|
||||
return condition, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,8 +122,8 @@ func TestExistsConditionForWithEvolutions(t *testing.T) {
|
||||
expectedError: nil,
|
||||
},
|
||||
}
|
||||
fm := NewFieldMapper()
|
||||
conditionBuilder := NewConditionBuilder(fm)
|
||||
fm := NewFieldMapper(false)
|
||||
conditionBuilder := NewConditionBuilder(fm, false)
|
||||
ctx := context.Background()
|
||||
|
||||
for _, tc := range testCases {
|
||||
@@ -513,8 +513,8 @@ func TestConditionFor(t *testing.T) {
|
||||
expectedError: qbtypes.ErrColumnNotFound,
|
||||
},
|
||||
}
|
||||
fm := NewFieldMapper()
|
||||
conditionBuilder := NewConditionBuilder(fm)
|
||||
fm := NewFieldMapper(false)
|
||||
conditionBuilder := NewConditionBuilder(fm, false)
|
||||
for _, tc := range testCases {
|
||||
sb := sqlbuilder.NewSelectBuilder()
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
@@ -566,8 +566,8 @@ func TestConditionForMultipleKeys(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
fm := NewFieldMapper()
|
||||
conditionBuilder := NewConditionBuilder(fm)
|
||||
fm := NewFieldMapper(false)
|
||||
conditionBuilder := NewConditionBuilder(fm, false)
|
||||
|
||||
for _, tc := range testCases {
|
||||
sb := sqlbuilder.NewSelectBuilder()
|
||||
@@ -825,8 +825,8 @@ func TestConditionForJSONBodySearch(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
fm := NewFieldMapper()
|
||||
conditionBuilder := NewConditionBuilder(fm)
|
||||
fm := NewFieldMapper(false)
|
||||
conditionBuilder := NewConditionBuilder(fm, false)
|
||||
|
||||
for _, tc := range testCases {
|
||||
sb := sqlbuilder.NewSelectBuilder()
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package telemetrylogs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/SigNoz/signoz-otel-collector/constants"
|
||||
"github.com/SigNoz/signoz/pkg/querybuilder"
|
||||
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
)
|
||||
@@ -41,7 +41,7 @@ const (
|
||||
BodyPromotedColumnPrefix = constants.BodyPromotedColumnPrefix
|
||||
|
||||
// messageSubColumn is the ClickHouse sub-column that body searches map to
|
||||
// when BodyJSONQueryEnabled is true.
|
||||
// when body_json_enabled feature flag is true.
|
||||
messageSubField = "message"
|
||||
messageSubColumn = "body_v2.message"
|
||||
bodySearchDefaultWarning = "body searches default to `body.message:string`. Use `body.<key>` to search a different field inside body"
|
||||
@@ -128,8 +128,8 @@ var (
|
||||
}
|
||||
)
|
||||
|
||||
func bodyAliasExpression() string {
|
||||
if !querybuilder.BodyJSONQueryEnabled {
|
||||
func bodyAliasExpression(ctx context.Context, bodyJSONEnabled bool) string {
|
||||
if !bodyJSONEnabled {
|
||||
return LogsV2BodyColumn
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
schema "github.com/SigNoz/signoz-otel-collector/cmd/signozschemamigrator/schema_migrator"
|
||||
"github.com/SigNoz/signoz-otel-collector/utils"
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/querybuilder"
|
||||
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
"github.com/huandu/go-sqlbuilder"
|
||||
@@ -66,13 +65,15 @@ var (
|
||||
}
|
||||
)
|
||||
|
||||
type fieldMapper struct{}
|
||||
|
||||
func NewFieldMapper() qbtypes.FieldMapper {
|
||||
return &fieldMapper{}
|
||||
type fieldMapper struct {
|
||||
bodyJSONEnabled bool
|
||||
}
|
||||
|
||||
func (m *fieldMapper) getColumn(_ context.Context, key *telemetrytypes.TelemetryFieldKey) ([]*schema.Column, error) {
|
||||
func NewFieldMapper(bodyJSONEnabled bool) qbtypes.FieldMapper {
|
||||
return &fieldMapper{bodyJSONEnabled: bodyJSONEnabled}
|
||||
}
|
||||
|
||||
func (m *fieldMapper) getColumn(ctx context.Context, key *telemetrytypes.TelemetryFieldKey) ([]*schema.Column, error) {
|
||||
switch key.FieldContext {
|
||||
case telemetrytypes.FieldContextResource:
|
||||
columns := []*schema.Column{logsV2Columns["resources_string"], logsV2Columns["resource"]}
|
||||
@@ -96,7 +97,7 @@ func (m *fieldMapper) getColumn(_ context.Context, key *telemetrytypes.Telemetry
|
||||
}
|
||||
case telemetrytypes.FieldContextBody:
|
||||
// Body context is for JSON body fields. Use body_v2 if feature flag is enabled.
|
||||
if querybuilder.BodyJSONQueryEnabled {
|
||||
if m.bodyJSONEnabled {
|
||||
if key.Name == messageSubField {
|
||||
return []*schema.Column{logsV2Columns[messageSubColumn]}, nil
|
||||
}
|
||||
@@ -105,7 +106,7 @@ func (m *fieldMapper) getColumn(_ context.Context, key *telemetrytypes.Telemetry
|
||||
// Fall back to legacy body column
|
||||
return []*schema.Column{logsV2Columns["body"]}, nil
|
||||
case telemetrytypes.FieldContextLog, telemetrytypes.FieldContextUnspecified:
|
||||
if key.Name == LogsV2BodyColumn && querybuilder.BodyJSONQueryEnabled {
|
||||
if key.Name == LogsV2BodyColumn && m.bodyJSONEnabled {
|
||||
return []*schema.Column{logsV2Columns[messageSubColumn]}, nil
|
||||
}
|
||||
col, ok := logsV2Columns[key.Name]
|
||||
@@ -113,7 +114,7 @@ func (m *fieldMapper) getColumn(_ context.Context, key *telemetrytypes.Telemetry
|
||||
// check if the key has body JSON search
|
||||
if strings.HasPrefix(key.Name, telemetrytypes.BodyJSONStringSearchPrefix) {
|
||||
// Use body_v2 if feature flag is enabled and we have a body condition builder
|
||||
if querybuilder.BodyJSONQueryEnabled {
|
||||
if m.bodyJSONEnabled {
|
||||
// TODO(Piyush): Update this to support multiple JSON columns based on evolutions
|
||||
// i.e return both the body json and body json promoted and let the evolutions decide which one to use
|
||||
// based on the query range time.
|
||||
|
||||
@@ -165,7 +165,7 @@ func TestGetColumn(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
fm := NewFieldMapper()
|
||||
fm := NewFieldMapper(false)
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
@@ -273,7 +273,7 @@ func TestGetFieldKeyName(t *testing.T) {
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
fm := NewFieldMapper()
|
||||
fm := NewFieldMapper(false)
|
||||
result, err := fm.FieldFor(ctx, 0, 0, &tc.key)
|
||||
|
||||
if tc.expectedError != nil {
|
||||
@@ -514,7 +514,7 @@ func TestFieldForWithEvolutions(t *testing.T) {
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
fm := NewFieldMapper()
|
||||
fm := NewFieldMapper(false)
|
||||
|
||||
tsStart := uint64(tc.tsStartTime.UnixNano())
|
||||
tsEnd := uint64(tc.tsEndTime.UnixNano())
|
||||
@@ -963,7 +963,7 @@ func TestFieldForWithMaterialized(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
fm := NewFieldMapper()
|
||||
fm := NewFieldMapper(false)
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
|
||||
@@ -13,8 +13,8 @@ import (
|
||||
// TestLikeAndILikeWithoutWildcards_Warns Tests that LIKE/ILIKE without wildcards add warnings and include docs URL.
|
||||
func TestLikeAndILikeWithoutWildcards_Warns(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
fm := NewFieldMapper()
|
||||
cb := NewConditionBuilder(fm)
|
||||
fm := NewFieldMapper(false)
|
||||
cb := NewConditionBuilder(fm, false)
|
||||
|
||||
releaseTime := time.Date(2024, 1, 15, 10, 0, 0, 0, time.UTC)
|
||||
keys := buildCompleteFieldKeyMap(releaseTime)
|
||||
@@ -51,8 +51,8 @@ func TestLikeAndILikeWithoutWildcards_Warns(t *testing.T) {
|
||||
|
||||
// TestLikeAndILikeWithWildcards_NoWarn Tests that LIKE/ILIKE with wildcards do not add warnings.
|
||||
func TestLikeAndILikeWithWildcards_NoWarn(t *testing.T) {
|
||||
fm := NewFieldMapper()
|
||||
cb := NewConditionBuilder(fm)
|
||||
fm := NewFieldMapper(false)
|
||||
cb := NewConditionBuilder(fm, false)
|
||||
|
||||
releaseTime := time.Date(2024, 1, 15, 10, 0, 0, 0, time.UTC)
|
||||
keys := buildCompleteFieldKeyMap(releaseTime)
|
||||
|
||||
@@ -15,8 +15,8 @@ import (
|
||||
|
||||
// TestFilterExprLogsBodyJSON tests a comprehensive set of query patterns for body JSON search.
|
||||
func TestFilterExprLogsBodyJSON(t *testing.T) {
|
||||
fm := NewFieldMapper()
|
||||
cb := NewConditionBuilder(fm)
|
||||
fm := NewFieldMapper(false)
|
||||
cb := NewConditionBuilder(fm, false)
|
||||
// Define a comprehensive set of field keys to support all test cases
|
||||
releaseTime := time.Date(2024, 1, 15, 10, 0, 0, 0, time.UTC)
|
||||
keys := buildCompleteFieldKeyMap(releaseTime)
|
||||
@@ -27,10 +27,8 @@ func TestFilterExprLogsBodyJSON(t *testing.T) {
|
||||
FieldMapper: fm,
|
||||
ConditionBuilder: cb,
|
||||
FieldKeys: keys,
|
||||
FullTextColumn: &telemetrytypes.TelemetryFieldKey{
|
||||
Name: "body",
|
||||
},
|
||||
JsonKeyToKey: GetBodyJSONKey,
|
||||
FullTextColumn: &telemetrytypes.TelemetryFieldKey{Name: "body"},
|
||||
JsonKeyToKey: GetBodyJSONKey,
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
|
||||
@@ -19,8 +19,8 @@ import (
|
||||
func TestFilterExprLogs(t *testing.T) {
|
||||
releaseTime := time.Date(2024, 1, 15, 10, 0, 0, 0, time.UTC)
|
||||
ctx := context.Background()
|
||||
fm := NewFieldMapper()
|
||||
cb := NewConditionBuilder(fm)
|
||||
fm := NewFieldMapper(false)
|
||||
cb := NewConditionBuilder(fm, false)
|
||||
|
||||
// Define a comprehensive set of field keys to support all test cases
|
||||
keys := buildCompleteFieldKeyMap(releaseTime)
|
||||
@@ -2429,8 +2429,8 @@ func TestFilterExprLogs(t *testing.T) {
|
||||
|
||||
// TestFilterExprLogs tests a comprehensive set of query patterns for logs search.
|
||||
func TestFilterExprLogsConflictNegation(t *testing.T) {
|
||||
fm := NewFieldMapper()
|
||||
cb := NewConditionBuilder(fm)
|
||||
fm := NewFieldMapper(false)
|
||||
cb := NewConditionBuilder(fm, false)
|
||||
|
||||
// Define a comprehensive set of field keys to support all test cases
|
||||
releaseTime := time.Date(2024, 1, 15, 10, 0, 0, 0, time.UTC)
|
||||
|
||||
@@ -32,9 +32,6 @@ func (t TestExpected) GetQuery() string {
|
||||
}
|
||||
|
||||
func TestJSONStmtBuilder_TimeSeries(t *testing.T) {
|
||||
enable, disable := jsonQueryTestUtil(t)
|
||||
enable()
|
||||
defer disable()
|
||||
statementBuilder := buildJSONTestStatementBuilder(t, false)
|
||||
|
||||
cases := []struct {
|
||||
@@ -115,9 +112,6 @@ func TestJSONStmtBuilder_TimeSeries(t *testing.T) {
|
||||
not a body_promoted.* column. These tests assumed the old coalesce(body_promoted.x, body_v2.x) path.
|
||||
|
||||
func TestStmtBuilderTimeSeriesBodyGroupByPromoted(t *testing.T) {
|
||||
enable, disable := jsonQueryTestUtil(t)
|
||||
enable()
|
||||
defer disable()
|
||||
statementBuilder := buildJSONTestStatementBuilder(t, "user.age", "user.name")
|
||||
|
||||
cases := []struct {
|
||||
@@ -176,10 +170,6 @@ func TestStmtBuilderTimeSeriesBodyGroupByPromoted(t *testing.T) {
|
||||
*/
|
||||
|
||||
func TestJSONStmtBuilder_PrimitivePaths(t *testing.T) {
|
||||
enable, disable := jsonQueryTestUtil(t)
|
||||
enable()
|
||||
defer disable()
|
||||
|
||||
statementBuilder := buildJSONTestStatementBuilder(t, false)
|
||||
cases := []struct {
|
||||
name string
|
||||
@@ -340,10 +330,6 @@ func TestJSONStmtBuilder_PrimitivePaths(t *testing.T) {
|
||||
(direct sub-column access), not a body_promoted.* column.
|
||||
|
||||
func TestStatementBuilderListQueryBodyPromoted(t *testing.T) {
|
||||
enable, disable := jsonQueryTestUtil(t)
|
||||
enable()
|
||||
defer disable()
|
||||
|
||||
statementBuilder := buildJSONTestStatementBuilder(t, "education", "tags")
|
||||
cases := []struct {
|
||||
name string
|
||||
@@ -507,10 +493,6 @@ func TestStatementBuilderListQueryBodyPromoted(t *testing.T) {
|
||||
*/
|
||||
|
||||
func TestJSONStmtBuilder_ArrayPaths(t *testing.T) {
|
||||
enable, disable := jsonQueryTestUtil(t)
|
||||
enable()
|
||||
defer disable()
|
||||
|
||||
statementBuilder := buildJSONTestStatementBuilder(t, false)
|
||||
cases := []struct {
|
||||
name string
|
||||
@@ -816,10 +798,6 @@ func TestJSONStmtBuilder_ArrayPaths(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestJSONStmtBuilder_IndexedPaths(t *testing.T) {
|
||||
enable, disable := jsonQueryTestUtil(t)
|
||||
enable()
|
||||
defer disable()
|
||||
|
||||
statementBuilder := buildJSONTestStatementBuilder(t, true)
|
||||
cases := []struct {
|
||||
name string
|
||||
@@ -939,9 +917,6 @@ func TestJSONStmtBuilder_IndexedPaths(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestJSONStmtBuilder_SelectField(t *testing.T) {
|
||||
enable, disable := jsonQueryTestUtil(t)
|
||||
enable()
|
||||
defer disable()
|
||||
statementBuilder := buildJSONTestStatementBuilder(t, false)
|
||||
|
||||
cases := []struct {
|
||||
@@ -1030,9 +1005,6 @@ func TestJSONStmtBuilder_SelectField(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestJSONStmtBuilder_OrderBy(t *testing.T) {
|
||||
enable, disable := jsonQueryTestUtil(t)
|
||||
enable()
|
||||
defer disable()
|
||||
statementBuilder := buildJSONTestStatementBuilder(t, false)
|
||||
|
||||
cases := []struct {
|
||||
@@ -1148,11 +1120,13 @@ func buildTestTelemetryMetadataStore(t *testing.T, addIndexes bool) *telemetryty
|
||||
}
|
||||
|
||||
func buildJSONTestStatementBuilder(t *testing.T, addIndexes bool) *logQueryStatementBuilder {
|
||||
mockMetadataStore := buildTestTelemetryMetadataStore(t, addIndexes)
|
||||
fm := NewFieldMapper()
|
||||
cb := NewConditionBuilder(fm)
|
||||
t.Helper()
|
||||
|
||||
aggExprRewriter := querybuilder.NewAggExprRewriter(instrumentationtest.New().ToProviderSettings(), nil, fm, cb, nil)
|
||||
mockMetadataStore := buildTestTelemetryMetadataStore(t, addIndexes)
|
||||
fm := NewFieldMapper(true)
|
||||
cb := NewConditionBuilder(fm, true)
|
||||
|
||||
aggExprRewriter := querybuilder.NewAggExprRewriter(instrumentationtest.New().ToProviderSettings(), nil, fm, cb, nil, true)
|
||||
|
||||
statementBuilder := NewLogQueryStatementBuilder(
|
||||
instrumentationtest.New().ToProviderSettings(),
|
||||
@@ -1162,18 +1136,8 @@ func buildJSONTestStatementBuilder(t *testing.T, addIndexes bool) *logQueryState
|
||||
aggExprRewriter,
|
||||
DefaultFullTextColumn,
|
||||
GetBodyJSONKey,
|
||||
true,
|
||||
)
|
||||
|
||||
return statementBuilder
|
||||
}
|
||||
|
||||
func jsonQueryTestUtil(_ *testing.T) (func(), func()) {
|
||||
enable := func() {
|
||||
querybuilder.BodyJSONQueryEnabled = true
|
||||
}
|
||||
disable := func() {
|
||||
querybuilder.BodyJSONQueryEnabled = false
|
||||
}
|
||||
|
||||
return enable, disable
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ type logQueryStatementBuilder struct {
|
||||
cb qbtypes.ConditionBuilder
|
||||
resourceFilterStmtBuilder qbtypes.StatementBuilder[qbtypes.LogAggregation]
|
||||
aggExprRewriter qbtypes.AggExprRewriter
|
||||
bodyJSONEnabled bool
|
||||
|
||||
fullTextColumn *telemetrytypes.TelemetryFieldKey
|
||||
jsonKeyToKey qbtypes.JsonKeyToFieldFunc
|
||||
@@ -37,6 +38,7 @@ func NewLogQueryStatementBuilder(
|
||||
aggExprRewriter qbtypes.AggExprRewriter,
|
||||
fullTextColumn *telemetrytypes.TelemetryFieldKey,
|
||||
jsonKeyToKey qbtypes.JsonKeyToFieldFunc,
|
||||
bodyJSONEnabled bool,
|
||||
) *logQueryStatementBuilder {
|
||||
logsSettings := factory.NewScopedProviderSettings(settings, "github.com/SigNoz/signoz/pkg/telemetrylogs")
|
||||
|
||||
@@ -60,6 +62,7 @@ func NewLogQueryStatementBuilder(
|
||||
aggExprRewriter: aggExprRewriter,
|
||||
fullTextColumn: fullTextColumn,
|
||||
jsonKeyToKey: jsonKeyToKey,
|
||||
bodyJSONEnabled: bodyJSONEnabled,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,11 +75,9 @@ func (b *logQueryStatementBuilder) Build(
|
||||
query qbtypes.QueryBuilderQuery[qbtypes.LogAggregation],
|
||||
variables map[string]qbtypes.VariableItem,
|
||||
) (*qbtypes.Statement, error) {
|
||||
|
||||
start = querybuilder.ToNanoSecs(start)
|
||||
end = querybuilder.ToNanoSecs(end)
|
||||
|
||||
keySelectors, warnings := getKeySelectors(query)
|
||||
keySelectors, warnings := getKeySelectors(ctx, query, b.bodyJSONEnabled)
|
||||
keys, _, err := b.metadataStore.GetKeysMulti(ctx, keySelectors)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -107,7 +108,7 @@ func (b *logQueryStatementBuilder) Build(
|
||||
return stmt, nil
|
||||
}
|
||||
|
||||
func getKeySelectors(query qbtypes.QueryBuilderQuery[qbtypes.LogAggregation]) ([]*telemetrytypes.FieldKeySelector, []string) {
|
||||
func getKeySelectors(ctx context.Context, query qbtypes.QueryBuilderQuery[qbtypes.LogAggregation], bodyJSONEnabled bool) ([]*telemetrytypes.FieldKeySelector, []string) {
|
||||
var keySelectors []*telemetrytypes.FieldKeySelector
|
||||
var warnings []string
|
||||
|
||||
@@ -159,7 +160,7 @@ func getKeySelectors(query qbtypes.QueryBuilderQuery[qbtypes.LogAggregation]) ([
|
||||
// When the new JSON body experience is enabled, warn the user if they use the bare
|
||||
// "body" key in the filter — queries on plain "body" default to body.message:string.
|
||||
// TODO(Piyush): Setup better for coming FTS support.
|
||||
if querybuilder.BodyJSONQueryEnabled {
|
||||
if bodyJSONEnabled {
|
||||
for _, sel := range keySelectors {
|
||||
if sel.Name == LogsV2BodyColumn {
|
||||
warnings = append(warnings, bodySearchDefaultWarning)
|
||||
@@ -279,7 +280,7 @@ func (b *logQueryStatementBuilder) buildListQuery(
|
||||
sb.SelectMore(LogsV2SeverityNumberColumn)
|
||||
sb.SelectMore(LogsV2ScopeNameColumn)
|
||||
sb.SelectMore(LogsV2ScopeVersionColumn)
|
||||
sb.SelectMore(bodyAliasExpression())
|
||||
sb.SelectMore(bodyAliasExpression(ctx, b.bodyJSONEnabled))
|
||||
sb.SelectMore(LogsV2AttributesStringColumn)
|
||||
sb.SelectMore(LogsV2AttributesNumberColumn)
|
||||
sb.SelectMore(LogsV2AttributesBoolColumn)
|
||||
@@ -379,7 +380,8 @@ func (b *logQueryStatementBuilder) buildTimeSeriesQuery(
|
||||
// Keep original column expressions so we can build the tuple
|
||||
fieldNames := make([]string, 0, len(query.GroupBy))
|
||||
for _, gb := range query.GroupBy {
|
||||
expr, args, err := querybuilder.CollisionHandledFinalExpr(ctx, start, end, &gb.TelemetryFieldKey, b.fm, b.cb, keys, telemetrytypes.FieldDataTypeString, b.jsonKeyToKey)
|
||||
expr, args, err := querybuilder.CollisionHandledFinalExpr(ctx, start, end, &gb.TelemetryFieldKey, b.fm, b.cb, keys,
|
||||
telemetrytypes.FieldDataTypeString, b.jsonKeyToKey, b.bodyJSONEnabled)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -532,7 +534,8 @@ func (b *logQueryStatementBuilder) buildScalarQuery(
|
||||
var allGroupByArgs []any
|
||||
|
||||
for _, gb := range query.GroupBy {
|
||||
expr, args, err := querybuilder.CollisionHandledFinalExpr(ctx, start, end, &gb.TelemetryFieldKey, b.fm, b.cb, keys, telemetrytypes.FieldDataTypeString, b.jsonKeyToKey)
|
||||
expr, args, err := querybuilder.CollisionHandledFinalExpr(ctx, start, end, &gb.TelemetryFieldKey, b.fm, b.cb, keys,
|
||||
telemetrytypes.FieldDataTypeString, b.jsonKeyToKey, b.bodyJSONEnabled)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -644,6 +647,7 @@ func (b *logQueryStatementBuilder) addFilterCondition(
|
||||
FieldMapper: b.fm,
|
||||
ConditionBuilder: b.cb,
|
||||
FieldKeys: keys,
|
||||
BodyJSONEnabled: b.bodyJSONEnabled,
|
||||
SkipResourceFilter: true,
|
||||
FullTextColumn: b.fullTextColumn,
|
||||
JsonKeyToKey: b.jsonKeyToKey,
|
||||
|
||||
@@ -15,7 +15,6 @@ import (
|
||||
)
|
||||
|
||||
func TestStatementBuilderTimeSeries(t *testing.T) {
|
||||
|
||||
// Create a test release time
|
||||
releaseTime := time.Date(2024, 1, 15, 10, 0, 0, 0, time.UTC)
|
||||
releaseTimeNano := uint64(releaseTime.UnixNano())
|
||||
@@ -197,10 +196,10 @@ func TestStatementBuilderTimeSeries(t *testing.T) {
|
||||
|
||||
mockMetadataStore.KeysMap = keysMap
|
||||
|
||||
fm := NewFieldMapper()
|
||||
cb := NewConditionBuilder(fm)
|
||||
fm := NewFieldMapper(false)
|
||||
cb := NewConditionBuilder(fm, false)
|
||||
|
||||
aggExprRewriter := querybuilder.NewAggExprRewriter(instrumentationtest.New().ToProviderSettings(), nil, fm, cb, nil)
|
||||
aggExprRewriter := querybuilder.NewAggExprRewriter(instrumentationtest.New().ToProviderSettings(), nil, fm, cb, nil, false)
|
||||
|
||||
statementBuilder := NewLogQueryStatementBuilder(
|
||||
instrumentationtest.New().ToProviderSettings(),
|
||||
@@ -210,6 +209,7 @@ func TestStatementBuilderTimeSeries(t *testing.T) {
|
||||
aggExprRewriter,
|
||||
DefaultFullTextColumn,
|
||||
GetBodyJSONKey,
|
||||
false,
|
||||
)
|
||||
|
||||
for _, c := range cases {
|
||||
@@ -313,15 +313,16 @@ func TestStatementBuilderListQuery(t *testing.T) {
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
mockMetadataStore := telemetrytypestest.NewMockMetadataStore()
|
||||
fm := NewFieldMapper()
|
||||
fm := NewFieldMapper(false)
|
||||
|
||||
// Create a test release time
|
||||
releaseTime := time.Date(2024, 1, 15, 10, 0, 0, 0, time.UTC)
|
||||
mockMetadataStore.KeysMap = buildCompleteFieldKeyMap(releaseTime)
|
||||
cb := NewConditionBuilder(fm)
|
||||
cb := NewConditionBuilder(fm, false)
|
||||
|
||||
aggExprRewriter := querybuilder.NewAggExprRewriter(instrumentationtest.New().ToProviderSettings(), nil, fm, cb, nil)
|
||||
aggExprRewriter := querybuilder.NewAggExprRewriter(instrumentationtest.New().ToProviderSettings(), nil, fm, cb, nil, false)
|
||||
|
||||
statementBuilder := NewLogQueryStatementBuilder(
|
||||
instrumentationtest.New().ToProviderSettings(),
|
||||
@@ -331,6 +332,7 @@ func TestStatementBuilderListQuery(t *testing.T) {
|
||||
aggExprRewriter,
|
||||
DefaultFullTextColumn,
|
||||
GetBodyJSONKey,
|
||||
false,
|
||||
)
|
||||
|
||||
for _, c := range cases {
|
||||
@@ -454,14 +456,15 @@ func TestStatementBuilderListQueryResourceTests(t *testing.T) {
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
mockMetadataStore := telemetrytypestest.NewMockMetadataStore()
|
||||
fm := NewFieldMapper()
|
||||
fm := NewFieldMapper(false)
|
||||
// Create a test release time
|
||||
releaseTime := time.Date(2024, 1, 15, 10, 0, 0, 0, time.UTC)
|
||||
mockMetadataStore.KeysMap = buildCompleteFieldKeyMap(releaseTime)
|
||||
cb := NewConditionBuilder(fm)
|
||||
cb := NewConditionBuilder(fm, false)
|
||||
|
||||
aggExprRewriter := querybuilder.NewAggExprRewriter(instrumentationtest.New().ToProviderSettings(), nil, fm, cb, nil)
|
||||
aggExprRewriter := querybuilder.NewAggExprRewriter(instrumentationtest.New().ToProviderSettings(), nil, fm, cb, nil, false)
|
||||
|
||||
statementBuilder := NewLogQueryStatementBuilder(
|
||||
instrumentationtest.New().ToProviderSettings(),
|
||||
@@ -471,6 +474,7 @@ func TestStatementBuilderListQueryResourceTests(t *testing.T) {
|
||||
aggExprRewriter,
|
||||
DefaultFullTextColumn,
|
||||
GetBodyJSONKey,
|
||||
false,
|
||||
)
|
||||
|
||||
for _, c := range cases {
|
||||
@@ -528,14 +532,15 @@ func TestStatementBuilderTimeSeriesBodyGroupBy(t *testing.T) {
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
mockMetadataStore := telemetrytypestest.NewMockMetadataStore()
|
||||
fm := NewFieldMapper()
|
||||
fm := NewFieldMapper(false)
|
||||
// Create a test release time
|
||||
releaseTime := time.Date(2024, 1, 15, 10, 0, 0, 0, time.UTC)
|
||||
mockMetadataStore.KeysMap = buildCompleteFieldKeyMap(releaseTime)
|
||||
cb := NewConditionBuilder(fm)
|
||||
cb := NewConditionBuilder(fm, false)
|
||||
|
||||
aggExprRewriter := querybuilder.NewAggExprRewriter(instrumentationtest.New().ToProviderSettings(), nil, fm, cb, nil)
|
||||
aggExprRewriter := querybuilder.NewAggExprRewriter(instrumentationtest.New().ToProviderSettings(), nil, fm, cb, nil, false)
|
||||
|
||||
statementBuilder := NewLogQueryStatementBuilder(
|
||||
instrumentationtest.New().ToProviderSettings(),
|
||||
@@ -545,6 +550,7 @@ func TestStatementBuilderTimeSeriesBodyGroupBy(t *testing.T) {
|
||||
aggExprRewriter,
|
||||
DefaultFullTextColumn,
|
||||
GetBodyJSONKey,
|
||||
false,
|
||||
)
|
||||
|
||||
for _, c := range cases {
|
||||
@@ -624,11 +630,12 @@ func TestStatementBuilderListQueryServiceCollision(t *testing.T) {
|
||||
|
||||
ctx := context.Background()
|
||||
mockMetadataStore := telemetrytypestest.NewMockMetadataStore()
|
||||
fm := NewFieldMapper()
|
||||
mockMetadataStore.KeysMap = buildCompleteFieldKeyMapCollision()
|
||||
cb := NewConditionBuilder(fm)
|
||||
|
||||
aggExprRewriter := querybuilder.NewAggExprRewriter(instrumentationtest.New().ToProviderSettings(), nil, fm, cb, nil)
|
||||
fm := NewFieldMapper(false)
|
||||
mockMetadataStore.KeysMap = buildCompleteFieldKeyMapCollision()
|
||||
cb := NewConditionBuilder(fm, false)
|
||||
|
||||
aggExprRewriter := querybuilder.NewAggExprRewriter(instrumentationtest.New().ToProviderSettings(), nil, fm, cb, nil, false)
|
||||
|
||||
statementBuilder := NewLogQueryStatementBuilder(
|
||||
instrumentationtest.New().ToProviderSettings(),
|
||||
@@ -638,6 +645,7 @@ func TestStatementBuilderListQueryServiceCollision(t *testing.T) {
|
||||
aggExprRewriter,
|
||||
DefaultFullTextColumn,
|
||||
GetBodyJSONKey,
|
||||
false,
|
||||
)
|
||||
|
||||
for _, c := range cases {
|
||||
@@ -845,12 +853,12 @@ func TestAdjustKey(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
fm := NewFieldMapper()
|
||||
fm := NewFieldMapper(false)
|
||||
mockMetadataStore := telemetrytypestest.NewMockMetadataStore()
|
||||
mockMetadataStore.KeysMap = buildCompleteFieldKeyMapCollision()
|
||||
cb := NewConditionBuilder(fm)
|
||||
cb := NewConditionBuilder(fm, false)
|
||||
|
||||
aggExprRewriter := querybuilder.NewAggExprRewriter(instrumentationtest.New().ToProviderSettings(), nil, fm, cb, nil)
|
||||
aggExprRewriter := querybuilder.NewAggExprRewriter(instrumentationtest.New().ToProviderSettings(), nil, fm, cb, nil, false)
|
||||
|
||||
statementBuilder := NewLogQueryStatementBuilder(
|
||||
instrumentationtest.New().ToProviderSettings(),
|
||||
@@ -860,6 +868,7 @@ func TestAdjustKey(t *testing.T) {
|
||||
aggExprRewriter,
|
||||
DefaultFullTextColumn,
|
||||
GetBodyJSONKey,
|
||||
false,
|
||||
)
|
||||
|
||||
for _, c := range cases {
|
||||
@@ -984,25 +993,17 @@ func TestStmtBuilderBodyField(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
fm := NewFieldMapper()
|
||||
cb := NewConditionBuilder(fm)
|
||||
|
||||
enable, disable := jsonQueryTestUtil(t)
|
||||
defer disable()
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
if c.enableBodyJSONQuery {
|
||||
enable()
|
||||
} else {
|
||||
disable()
|
||||
}
|
||||
// build the key map after enabling/disabling body JSON query
|
||||
fm := NewFieldMapper(c.enableBodyJSONQuery)
|
||||
cb := NewConditionBuilder(fm, c.enableBodyJSONQuery)
|
||||
// build the key map
|
||||
mockMetadataStore := telemetrytypestest.NewMockMetadataStore()
|
||||
for _, field := range IntrinsicFields {
|
||||
f := field
|
||||
mockMetadataStore.KeysMap[field.Name] = append(mockMetadataStore.KeysMap[field.Name], &f)
|
||||
}
|
||||
aggExprRewriter := querybuilder.NewAggExprRewriter(instrumentationtest.New().ToProviderSettings(), nil, fm, cb, nil)
|
||||
aggExprRewriter := querybuilder.NewAggExprRewriter(instrumentationtest.New().ToProviderSettings(), nil, fm, cb, nil, c.enableBodyJSONQuery)
|
||||
statementBuilder := NewLogQueryStatementBuilder(
|
||||
instrumentationtest.New().ToProviderSettings(),
|
||||
mockMetadataStore,
|
||||
@@ -1011,6 +1012,7 @@ func TestStmtBuilderBodyField(t *testing.T) {
|
||||
aggExprRewriter,
|
||||
DefaultFullTextColumn,
|
||||
GetBodyJSONKey,
|
||||
c.enableBodyJSONQuery,
|
||||
)
|
||||
|
||||
q, err := statementBuilder.Build(context.Background(), 1747947419000, 1747983448000, c.requestType, c.query, nil)
|
||||
@@ -1072,25 +1074,17 @@ func TestStmtBuilderBodyFullTextSearch(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
fm := NewFieldMapper()
|
||||
cb := NewConditionBuilder(fm)
|
||||
|
||||
enable, disable := jsonQueryTestUtil(t)
|
||||
defer disable()
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
if c.enableBodyJSONQuery {
|
||||
enable()
|
||||
} else {
|
||||
disable()
|
||||
}
|
||||
// build the key map after enabling/disabling body JSON query
|
||||
fm := NewFieldMapper(c.enableBodyJSONQuery)
|
||||
cb := NewConditionBuilder(fm, c.enableBodyJSONQuery)
|
||||
// build the key map
|
||||
mockMetadataStore := telemetrytypestest.NewMockMetadataStore()
|
||||
for _, field := range IntrinsicFields {
|
||||
f := field
|
||||
mockMetadataStore.KeysMap[field.Name] = append(mockMetadataStore.KeysMap[field.Name], &f)
|
||||
}
|
||||
aggExprRewriter := querybuilder.NewAggExprRewriter(instrumentationtest.New().ToProviderSettings(), nil, fm, cb, nil)
|
||||
aggExprRewriter := querybuilder.NewAggExprRewriter(instrumentationtest.New().ToProviderSettings(), nil, fm, cb, nil, c.enableBodyJSONQuery)
|
||||
statementBuilder := NewLogQueryStatementBuilder(
|
||||
instrumentationtest.New().ToProviderSettings(),
|
||||
mockMetadataStore,
|
||||
@@ -1099,6 +1093,7 @@ func TestStmtBuilderBodyFullTextSearch(t *testing.T) {
|
||||
aggExprRewriter,
|
||||
DefaultFullTextColumn,
|
||||
GetBodyJSONKey,
|
||||
c.enableBodyJSONQuery,
|
||||
)
|
||||
|
||||
q, err := statementBuilder.Build(context.Background(), 1747947419000, 1747983448000, c.requestType, c.query, nil)
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/flagger"
|
||||
"github.com/SigNoz/signoz/pkg/querybuilder"
|
||||
"github.com/SigNoz/signoz/pkg/telemetryaudit"
|
||||
"github.com/SigNoz/signoz/pkg/telemetrylogs"
|
||||
@@ -19,10 +20,12 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/telemetrystore"
|
||||
"github.com/SigNoz/signoz/pkg/telemetrytraces"
|
||||
"github.com/SigNoz/signoz/pkg/types/ctxtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/featuretypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/instrumentationtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/metrictypes"
|
||||
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -63,6 +66,7 @@ type telemetryMetaStore struct {
|
||||
|
||||
fm qbtypes.FieldMapper
|
||||
conditionBuilder qbtypes.ConditionBuilder
|
||||
fl flagger.Flagger
|
||||
jsonColumnMetadata map[telemetrytypes.Signal]map[telemetrytypes.FieldContext]telemetrytypes.JSONColumnMetadata
|
||||
}
|
||||
|
||||
@@ -94,9 +98,13 @@ func NewTelemetryMetaStore(
|
||||
relatedMetadataDBName string,
|
||||
relatedMetadataTblName string,
|
||||
columnEvolutionMetadataTblName string,
|
||||
fl flagger.Flagger,
|
||||
) telemetrytypes.MetadataStore {
|
||||
metadataSettings := factory.NewScopedProviderSettings(settings, "github.com/SigNoz/signoz/pkg/telemetrymetadata")
|
||||
|
||||
fm := NewFieldMapper()
|
||||
conditionBuilder := NewConditionBuilder(fm)
|
||||
|
||||
t := &telemetryMetaStore{
|
||||
logger: metadataSettings.Logger(),
|
||||
telemetrystore: telemetrystore,
|
||||
@@ -129,14 +137,11 @@ func NewTelemetryMetaStore(
|
||||
},
|
||||
},
|
||||
},
|
||||
fl: fl,
|
||||
fm: fm,
|
||||
conditionBuilder: conditionBuilder,
|
||||
}
|
||||
|
||||
fm := NewFieldMapper()
|
||||
conditionBuilder := NewConditionBuilder(fm)
|
||||
|
||||
t.fm = fm
|
||||
t.conditionBuilder = conditionBuilder
|
||||
|
||||
return t
|
||||
}
|
||||
|
||||
@@ -416,7 +421,7 @@ func (t *telemetryMetaStore) getLogsKeys(ctx context.Context, fieldKeySelectors
|
||||
}
|
||||
|
||||
// body keys are gated behind the feature flag
|
||||
queryBodyTable = queryBodyTable && querybuilder.BodyJSONQueryEnabled
|
||||
queryBodyTable = queryBodyTable && t.fl.BooleanOrEmpty(ctx, flagger.FeatureBodyJSONQuery, featuretypes.NewFlaggerEvaluationContext(valuer.UUID{}))
|
||||
|
||||
// requestedFieldKeySelectors is the set of names the user explicitly asked for.
|
||||
// Used to ensure a name that is both a parent path AND a directly requested field still surfaces
|
||||
@@ -676,7 +681,7 @@ func (t *telemetryMetaStore) getLogsKeys(ctx context.Context, fieldKeySelectors
|
||||
}
|
||||
|
||||
// enrich body keys with promoted paths, indexes, and JSON access plans
|
||||
if querybuilder.BodyJSONQueryEnabled {
|
||||
if t.fl.BooleanOrEmpty(ctx, flagger.FeatureBodyJSONQuery, featuretypes.NewFlaggerEvaluationContext(valuer.UUID{})) {
|
||||
if err := t.enrichJSONKeys(ctx, fieldKeySelectors, keys, parentTypes); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/flagger/flaggertest"
|
||||
"github.com/SigNoz/signoz/pkg/instrumentation/instrumentationtest"
|
||||
"github.com/SigNoz/signoz/pkg/telemetryaudit"
|
||||
"github.com/SigNoz/signoz/pkg/telemetrylogs"
|
||||
@@ -46,6 +47,7 @@ func TestGetFirstSeenFromMetricMetadata(t *testing.T) {
|
||||
DBName,
|
||||
AttributesMetadataLocalTableName,
|
||||
ColumnEvolutionMetadataTableName,
|
||||
flaggertest.New(t),
|
||||
)
|
||||
|
||||
lookupKeys := []telemetrytypes.MetricMetadataLookupKey{
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/flagger/flaggertest"
|
||||
"github.com/SigNoz/signoz/pkg/instrumentation/instrumentationtest"
|
||||
"github.com/SigNoz/signoz/pkg/telemetryaudit"
|
||||
"github.com/SigNoz/signoz/pkg/telemetrylogs"
|
||||
@@ -20,7 +21,8 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func newTestTelemetryMetaStoreTestHelper(store telemetrystore.TelemetryStore) telemetrytypes.MetadataStore {
|
||||
func newTestTelemetryMetaStoreTestHelper(t *testing.T, store telemetrystore.TelemetryStore) telemetrytypes.MetadataStore {
|
||||
t.Helper()
|
||||
return NewTelemetryMetaStore(
|
||||
instrumentationtest.New().ToProviderSettings(),
|
||||
store,
|
||||
@@ -45,6 +47,7 @@ func newTestTelemetryMetaStoreTestHelper(store telemetrystore.TelemetryStore) te
|
||||
DBName,
|
||||
AttributesMetadataLocalTableName,
|
||||
ColumnEvolutionMetadataTableName,
|
||||
flaggertest.New(t),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -66,7 +69,7 @@ func TestGetKeys(t *testing.T) {
|
||||
mockTelemetryStore := telemetrystoretest.New(telemetrystore.Config{}, ®exMatcher{})
|
||||
mock := mockTelemetryStore.Mock()
|
||||
|
||||
metadata := newTestTelemetryMetaStoreTestHelper(mockTelemetryStore)
|
||||
metadata := newTestTelemetryMetaStoreTestHelper(t, mockTelemetryStore)
|
||||
|
||||
rows := cmock.NewRows([]cmock.ColumnType{
|
||||
{Name: "statement", Type: "String"},
|
||||
@@ -176,7 +179,7 @@ func TestApplyBackwardCompatibleKeys(t *testing.T) {
|
||||
mockTelemetryStore := telemetrystoretest.New(telemetrystore.Config{}, ®exMatcher{})
|
||||
mock := mockTelemetryStore.Mock()
|
||||
|
||||
metadata := newTestTelemetryMetaStoreTestHelper(mockTelemetryStore)
|
||||
metadata := newTestTelemetryMetaStoreTestHelper(t, mockTelemetryStore)
|
||||
|
||||
hasTraces := false
|
||||
hasLogs := false
|
||||
@@ -340,7 +343,7 @@ func TestGetMetricFieldValuesIntrinsicMetricName(t *testing.T) {
|
||||
mockTelemetryStore := telemetrystoretest.New(telemetrystore.Config{}, ®exMatcher{})
|
||||
mock := mockTelemetryStore.Mock()
|
||||
|
||||
metadata := newTestTelemetryMetaStoreTestHelper(mockTelemetryStore)
|
||||
metadata := newTestTelemetryMetaStoreTestHelper(t, mockTelemetryStore)
|
||||
|
||||
valueRows := cmock.NewRows([]cmock.ColumnType{
|
||||
{Name: "metric_name", Type: "String"},
|
||||
@@ -379,7 +382,7 @@ func TestGetMetricFieldValuesIntrinsicBoolReturnsEmpty(t *testing.T) {
|
||||
mockTelemetryStore := telemetrystoretest.New(telemetrystore.Config{}, ®exMatcher{})
|
||||
mock := mockTelemetryStore.Mock()
|
||||
|
||||
metadata := newTestTelemetryMetaStoreTestHelper(mockTelemetryStore)
|
||||
metadata := newTestTelemetryMetaStoreTestHelper(t, mockTelemetryStore)
|
||||
|
||||
metadataRows := cmock.NewRows([]cmock.ColumnType{
|
||||
{Name: "attr_string_value", Type: "String"},
|
||||
@@ -411,7 +414,7 @@ func TestGetMetricFieldValuesAppliesMetricNamespace(t *testing.T) {
|
||||
mockTelemetryStore := telemetrystoretest.New(telemetrystore.Config{}, ®exMatcher{})
|
||||
mock := mockTelemetryStore.Mock()
|
||||
|
||||
metadata := newTestTelemetryMetaStoreTestHelper(mockTelemetryStore)
|
||||
metadata := newTestTelemetryMetaStoreTestHelper(t, mockTelemetryStore)
|
||||
|
||||
valueRows := cmock.NewRows([]cmock.ColumnType{
|
||||
{Name: "attr_string_value", Type: "String"},
|
||||
@@ -443,7 +446,7 @@ func TestGetMetricFieldValuesIntrinsicMetricNameAppliesMetricNamespace(t *testin
|
||||
mockTelemetryStore := telemetrystoretest.New(telemetrystore.Config{}, ®exMatcher{})
|
||||
mock := mockTelemetryStore.Mock()
|
||||
|
||||
metadata := newTestTelemetryMetaStoreTestHelper(mockTelemetryStore)
|
||||
metadata := newTestTelemetryMetaStoreTestHelper(t, mockTelemetryStore)
|
||||
|
||||
valueRows := cmock.NewRows([]cmock.ColumnType{
|
||||
{Name: "metric_name", Type: "String"},
|
||||
@@ -483,7 +486,7 @@ func TestGetMeterSourceMetricFieldValuesAppliesMetricNamespace(t *testing.T) {
|
||||
mockTelemetryStore := telemetrystoretest.New(telemetrystore.Config{}, ®exMatcher{})
|
||||
mock := mockTelemetryStore.Mock()
|
||||
|
||||
metadata := newTestTelemetryMetaStoreTestHelper(mockTelemetryStore)
|
||||
metadata := newTestTelemetryMetaStoreTestHelper(t, mockTelemetryStore)
|
||||
|
||||
rows := cmock.NewRows([]cmock.ColumnType{
|
||||
{Name: "attr", Type: "Array(String)"},
|
||||
@@ -514,7 +517,7 @@ func TestGetMetricsKeysAppliesMetricNamespace(t *testing.T) {
|
||||
mockTelemetryStore := telemetrystoretest.New(telemetrystore.Config{}, ®exMatcher{})
|
||||
mock := mockTelemetryStore.Mock()
|
||||
|
||||
metadata := newTestTelemetryMetaStoreTestHelper(mockTelemetryStore)
|
||||
metadata := newTestTelemetryMetaStoreTestHelper(t, mockTelemetryStore)
|
||||
|
||||
rows := cmock.NewRows([]cmock.ColumnType{
|
||||
{Name: "name", Type: "String"},
|
||||
@@ -549,7 +552,7 @@ func TestGetMeterSourceMetricKeysAppliesMetricNamespace(t *testing.T) {
|
||||
mockTelemetryStore := telemetrystoretest.New(telemetrystore.Config{}, ®exMatcher{})
|
||||
mock := mockTelemetryStore.Mock()
|
||||
|
||||
metadata := newTestTelemetryMetaStoreTestHelper(mockTelemetryStore)
|
||||
metadata := newTestTelemetryMetaStoreTestHelper(t, mockTelemetryStore)
|
||||
|
||||
rows := cmock.NewRows([]cmock.ColumnType{
|
||||
{Name: "attr_name", Type: "String"},
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/querybuilder"
|
||||
"github.com/SigNoz/signoz/pkg/telemetrymetrics"
|
||||
"github.com/SigNoz/signoz/pkg/types/metrictypes"
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/flagger"
|
||||
"github.com/SigNoz/signoz/pkg/flagger/flaggertest"
|
||||
"github.com/SigNoz/signoz/pkg/instrumentation/instrumentationtest"
|
||||
"github.com/SigNoz/signoz/pkg/telemetrymetrics"
|
||||
"github.com/SigNoz/signoz/pkg/types/metrictypes"
|
||||
@@ -166,12 +166,7 @@ func TestStatementBuilder(t *testing.T) {
|
||||
}
|
||||
mockMetadataStore.KeysMap = keys
|
||||
|
||||
flagger, err := flagger.New(context.Background(), instrumentationtest.New().ToProviderSettings(), flagger.Config{}, flagger.MustNewRegistry())
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create flagger: %v", err)
|
||||
}
|
||||
|
||||
metricStmtBuilder := telemetrymetrics.NewMetricQueryStatementBuilder(instrumentationtest.New().ToProviderSettings(), mockMetadataStore, fm, cb, flagger)
|
||||
metricStmtBuilder := telemetrymetrics.NewMetricQueryStatementBuilder(instrumentationtest.New().ToProviderSettings(), mockMetadataStore, fm, cb, flaggertest.New(t))
|
||||
|
||||
statementBuilder := NewMeterQueryStatementBuilder(
|
||||
instrumentationtest.New().ToProviderSettings(),
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/flagger"
|
||||
"github.com/SigNoz/signoz/pkg/flagger/flaggertest"
|
||||
"github.com/SigNoz/signoz/pkg/instrumentation/instrumentationtest"
|
||||
"github.com/SigNoz/signoz/pkg/types/metrictypes"
|
||||
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||
@@ -235,17 +235,12 @@ func TestStatementBuilder(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
flagger, err := flagger.New(context.Background(), instrumentationtest.New().ToProviderSettings(), flagger.Config{}, flagger.MustNewRegistry())
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create flagger: %v", err)
|
||||
}
|
||||
|
||||
statementBuilder := NewMetricQueryStatementBuilder(
|
||||
instrumentationtest.New().ToProviderSettings(),
|
||||
mockMetadataStore,
|
||||
fm,
|
||||
cb,
|
||||
flagger,
|
||||
flaggertest.New(t),
|
||||
)
|
||||
|
||||
for _, c := range cases {
|
||||
|
||||
@@ -19,7 +19,6 @@ type TelemetryStoreHook interface {
|
||||
AfterQuery(ctx context.Context, event *QueryEvent)
|
||||
}
|
||||
|
||||
|
||||
func WrapBeforeQuery(hooks []TelemetryStoreHook, ctx context.Context, event *QueryEvent) context.Context {
|
||||
for _, hook := range hooks {
|
||||
ctx = hook.BeforeQuery(ctx, event)
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
)
|
||||
|
||||
type provider struct {
|
||||
settings telemetrystore.QuerySettings
|
||||
settings telemetrystore.QuerySettings
|
||||
}
|
||||
|
||||
func NewSettingsFactory() factory.ProviderFactory[telemetrystore.TelemetryStoreHook, telemetrystore.Config] {
|
||||
@@ -21,7 +21,7 @@ func NewSettingsFactory() factory.ProviderFactory[telemetrystore.TelemetryStoreH
|
||||
|
||||
func NewSettings(ctx context.Context, providerSettings factory.ProviderSettings, config telemetrystore.Config) (telemetrystore.TelemetryStoreHook, error) {
|
||||
return &provider{
|
||||
settings: config.Clickhouse.QuerySettings,
|
||||
settings: config.Clickhouse.QuerySettings,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -510,7 +510,7 @@ func (b *traceQueryStatementBuilder) buildTimeSeriesQuery(
|
||||
// Keep original column expressions so we can build the tuple
|
||||
fieldNames := make([]string, 0, len(query.GroupBy))
|
||||
for _, gb := range query.GroupBy {
|
||||
expr, args, err := querybuilder.CollisionHandledFinalExpr(ctx, start, end, &gb.TelemetryFieldKey, b.fm, b.cb, keys, telemetrytypes.FieldDataTypeString, nil)
|
||||
expr, args, err := querybuilder.CollisionHandledFinalExpr(ctx, start, end, &gb.TelemetryFieldKey, b.fm, b.cb, keys, telemetrytypes.FieldDataTypeString, nil, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -658,7 +658,7 @@ func (b *traceQueryStatementBuilder) buildScalarQuery(
|
||||
|
||||
var allGroupByArgs []any
|
||||
for _, gb := range query.GroupBy {
|
||||
expr, args, err := querybuilder.CollisionHandledFinalExpr(ctx, start, end, &gb.TelemetryFieldKey, b.fm, b.cb, keys, telemetrytypes.FieldDataTypeString, nil)
|
||||
expr, args, err := querybuilder.CollisionHandledFinalExpr(ctx, start, end, &gb.TelemetryFieldKey, b.fm, b.cb, keys, telemetrytypes.FieldDataTypeString, nil, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -355,7 +355,7 @@ func TestStatementBuilder(t *testing.T) {
|
||||
cb := NewConditionBuilder(fm)
|
||||
mockMetadataStore := telemetrytypestest.NewMockMetadataStore()
|
||||
mockMetadataStore.KeysMap = buildCompleteFieldKeyMap()
|
||||
aggExprRewriter := querybuilder.NewAggExprRewriter(instrumentationtest.New().ToProviderSettings(), nil, fm, cb, nil)
|
||||
aggExprRewriter := querybuilder.NewAggExprRewriter(instrumentationtest.New().ToProviderSettings(), nil, fm, cb, nil, false)
|
||||
|
||||
statementBuilder := NewTraceQueryStatementBuilder(
|
||||
instrumentationtest.New().ToProviderSettings(),
|
||||
@@ -648,7 +648,7 @@ func TestStatementBuilderListQuery(t *testing.T) {
|
||||
cb := NewConditionBuilder(fm)
|
||||
mockMetadataStore := telemetrytypestest.NewMockMetadataStore()
|
||||
mockMetadataStore.KeysMap = buildCompleteFieldKeyMap()
|
||||
aggExprRewriter := querybuilder.NewAggExprRewriter(instrumentationtest.New().ToProviderSettings(), nil, fm, cb, nil)
|
||||
aggExprRewriter := querybuilder.NewAggExprRewriter(instrumentationtest.New().ToProviderSettings(), nil, fm, cb, nil, false)
|
||||
|
||||
statementBuilder := NewTraceQueryStatementBuilder(
|
||||
instrumentationtest.New().ToProviderSettings(),
|
||||
@@ -755,7 +755,7 @@ func TestStatementBuilderListQueryWithCorruptData(t *testing.T) {
|
||||
if mockMetadataStore.KeysMap == nil {
|
||||
mockMetadataStore.KeysMap = buildCompleteFieldKeyMap()
|
||||
}
|
||||
aggExprRewriter := querybuilder.NewAggExprRewriter(instrumentationtest.New().ToProviderSettings(), nil, fm, cb, nil)
|
||||
aggExprRewriter := querybuilder.NewAggExprRewriter(instrumentationtest.New().ToProviderSettings(), nil, fm, cb, nil, false)
|
||||
|
||||
statementBuilder := NewTraceQueryStatementBuilder(
|
||||
instrumentationtest.New().ToProviderSettings(),
|
||||
@@ -905,7 +905,7 @@ func TestStatementBuilderTraceQuery(t *testing.T) {
|
||||
cb := NewConditionBuilder(fm)
|
||||
mockMetadataStore := telemetrytypestest.NewMockMetadataStore()
|
||||
mockMetadataStore.KeysMap = buildCompleteFieldKeyMap()
|
||||
aggExprRewriter := querybuilder.NewAggExprRewriter(instrumentationtest.New().ToProviderSettings(), nil, fm, cb, nil)
|
||||
aggExprRewriter := querybuilder.NewAggExprRewriter(instrumentationtest.New().ToProviderSettings(), nil, fm, cb, nil, false)
|
||||
|
||||
statementBuilder := NewTraceQueryStatementBuilder(
|
||||
instrumentationtest.New().ToProviderSettings(),
|
||||
@@ -1119,7 +1119,7 @@ func TestAdjustKey(t *testing.T) {
|
||||
fm := NewFieldMapper()
|
||||
cb := NewConditionBuilder(fm)
|
||||
mockMetadataStore := telemetrytypestest.NewMockMetadataStore()
|
||||
aggExprRewriter := querybuilder.NewAggExprRewriter(instrumentationtest.New().ToProviderSettings(), nil, fm, cb, nil)
|
||||
aggExprRewriter := querybuilder.NewAggExprRewriter(instrumentationtest.New().ToProviderSettings(), nil, fm, cb, nil, false)
|
||||
statementBuilder := NewTraceQueryStatementBuilder(
|
||||
instrumentationtest.New().ToProviderSettings(),
|
||||
mockMetadataStore,
|
||||
@@ -1391,7 +1391,7 @@ func TestAdjustKeys(t *testing.T) {
|
||||
fm := NewFieldMapper()
|
||||
cb := NewConditionBuilder(fm)
|
||||
mockMetadataStore := telemetrytypestest.NewMockMetadataStore()
|
||||
aggExprRewriter := querybuilder.NewAggExprRewriter(instrumentationtest.New().ToProviderSettings(), nil, fm, cb, nil)
|
||||
aggExprRewriter := querybuilder.NewAggExprRewriter(instrumentationtest.New().ToProviderSettings(), nil, fm, cb, nil, false)
|
||||
statementBuilder := NewTraceQueryStatementBuilder(
|
||||
instrumentationtest.New().ToProviderSettings(),
|
||||
mockMetadataStore,
|
||||
|
||||
@@ -560,6 +560,7 @@ func (b *traceOperatorCTEBuilder) buildTimeSeriesQuery(ctx context.Context, sele
|
||||
keys,
|
||||
telemetrytypes.FieldDataTypeString,
|
||||
nil,
|
||||
false,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, errors.NewInvalidInputf(
|
||||
@@ -676,6 +677,7 @@ func (b *traceOperatorCTEBuilder) buildTraceQuery(ctx context.Context, selectFro
|
||||
keys,
|
||||
telemetrytypes.FieldDataTypeString,
|
||||
nil,
|
||||
false,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, errors.NewInvalidInputf(
|
||||
@@ -822,6 +824,7 @@ func (b *traceOperatorCTEBuilder) buildScalarQuery(ctx context.Context, selectFr
|
||||
keys,
|
||||
telemetrytypes.FieldDataTypeString,
|
||||
nil,
|
||||
false,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, errors.NewInvalidInputf(
|
||||
|
||||
@@ -390,7 +390,7 @@ func TestTraceOperatorStatementBuilder(t *testing.T) {
|
||||
cb := NewConditionBuilder(fm)
|
||||
mockMetadataStore := telemetrytypestest.NewMockMetadataStore()
|
||||
mockMetadataStore.KeysMap = buildCompleteFieldKeyMap()
|
||||
aggExprRewriter := querybuilder.NewAggExprRewriter(instrumentationtest.New().ToProviderSettings(), nil, fm, cb, nil)
|
||||
aggExprRewriter := querybuilder.NewAggExprRewriter(instrumentationtest.New().ToProviderSettings(), nil, fm, cb, nil, false)
|
||||
|
||||
traceStmtBuilder := NewTraceQueryStatementBuilder(
|
||||
instrumentationtest.New().ToProviderSettings(),
|
||||
@@ -503,7 +503,7 @@ func TestTraceOperatorStatementBuilderErrors(t *testing.T) {
|
||||
cb := NewConditionBuilder(fm)
|
||||
mockMetadataStore := telemetrytypestest.NewMockMetadataStore()
|
||||
mockMetadataStore.KeysMap = buildCompleteFieldKeyMap()
|
||||
aggExprRewriter := querybuilder.NewAggExprRewriter(instrumentationtest.New().ToProviderSettings(), nil, fm, cb, nil)
|
||||
aggExprRewriter := querybuilder.NewAggExprRewriter(instrumentationtest.New().ToProviderSettings(), nil, fm, cb, nil, false)
|
||||
|
||||
traceStmtBuilder := NewTraceQueryStatementBuilder(
|
||||
instrumentationtest.New().ToProviderSettings(),
|
||||
|
||||
@@ -34,7 +34,7 @@ func TestTraceTimeRangeOptimization(t *testing.T) {
|
||||
Signal: telemetrytypes.SignalTraces,
|
||||
}}
|
||||
|
||||
aggExprRewriter := querybuilder.NewAggExprRewriter(instrumentationtest.New().ToProviderSettings(), nil, fm, cb, nil)
|
||||
aggExprRewriter := querybuilder.NewAggExprRewriter(instrumentationtest.New().ToProviderSettings(), nil, fm, cb, nil, false)
|
||||
|
||||
statementBuilder := NewTraceQueryStatementBuilder(
|
||||
instrumentationtest.New().ToProviderSettings(),
|
||||
|
||||
@@ -1,144 +0,0 @@
|
||||
package llmpricingruletypes
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrCodePricingRuleNotFound = errors.MustNewCode("pricing_rule_not_found")
|
||||
ErrCodePricingRuleInvalidInput = errors.MustNewCode("pricing_rule_invalid_input")
|
||||
)
|
||||
|
||||
type LLMPricingRuleUnit struct {
|
||||
valuer.String
|
||||
}
|
||||
|
||||
var (
|
||||
UnitPerMillionTokens = LLMPricingRuleUnit{valuer.NewString("per_million_tokens")}
|
||||
)
|
||||
|
||||
type LLMPricingRuleCacheMode struct {
|
||||
valuer.String
|
||||
}
|
||||
|
||||
var (
|
||||
// LLMPricingRuleCacheModeSubtract: cached tokens are inside input_tokens (OpenAI-style).
|
||||
LLMPricingRuleCacheModeSubtract = LLMPricingRuleCacheMode{valuer.NewString("subtract")}
|
||||
// LLMPricingRuleCacheModeAdditive: cached tokens are reported separately (Anthropic-style).
|
||||
LLMPricingRuleCacheModeAdditive = LLMPricingRuleCacheMode{valuer.NewString("additive")}
|
||||
// LLMPricingRuleCacheModeUnknown: provider behaviour is unknown; falls back to subtract.
|
||||
LLMPricingRuleCacheModeUnknown = LLMPricingRuleCacheMode{valuer.NewString("unknown")}
|
||||
)
|
||||
|
||||
// LLMPricingRule is the domain model for an LLM pricing rule.
|
||||
// It also doubles as the HTTP response shape; see GettablePricingRule.
|
||||
type LLMPricingRule struct {
|
||||
types.TimeAuditable
|
||||
types.UserAuditable
|
||||
|
||||
ID valuer.UUID `json:"id" required:"true"`
|
||||
OrgID valuer.UUID `json:"orgId" required:"true"`
|
||||
SourceID *valuer.UUID `json:"sourceId,omitempty"`
|
||||
Model string `json:"modelName" required:"true"`
|
||||
ModelPattern []string `json:"modelPattern" required:"true"`
|
||||
Unit LLMPricingRuleUnit `json:"unit" required:"true"`
|
||||
CacheMode LLMPricingRuleCacheMode `json:"cacheMode" required:"true"`
|
||||
CostInput float64 `json:"costInput" required:"true"`
|
||||
CostOutput float64 `json:"costOutput" required:"true"`
|
||||
CostCacheRead float64 `json:"costCacheRead" required:"true"`
|
||||
CostCacheWrite float64 `json:"costCacheWrite" required:"true"`
|
||||
IsOverride bool `json:"isOverride" required:"true"`
|
||||
SyncedAt *time.Time `json:"syncedAt,omitempty"`
|
||||
Enabled bool `json:"enabled" required:"true"`
|
||||
}
|
||||
|
||||
// GettablePricingRule is a type alias for PricingRule — the response shape is
|
||||
// identical to the core type, so per pkg/types conventions we do not mint a
|
||||
// separate flavor.
|
||||
type GettableLLMPricingRule = LLMPricingRule
|
||||
|
||||
// UpdatablePricingRule is one entry in the bulk upsert batch.
|
||||
//
|
||||
// Identification:
|
||||
// - ID set → match by id (user editing a known row).
|
||||
// - SourceID set → match by source_id (Zeus sync, or user editing a Zeus-synced row).
|
||||
// - neither set → insert a new row with source_id = NULL (user-created custom rule).
|
||||
//
|
||||
// IsOverride is a pointer so the caller can distinguish "not sent" from "set to false".
|
||||
// When IsOverride is nil AND the matched row has is_override = true, the row is fully
|
||||
// preserved — only synced_at is stamped.
|
||||
type UpdatableLLMPricingRule struct {
|
||||
ID *valuer.UUID `json:"id,omitempty"`
|
||||
SourceID *valuer.UUID `json:"sourceId,omitempty"`
|
||||
Model string `json:"modelName" required:"true"`
|
||||
ModelPattern []string `json:"modelPattern" required:"true"`
|
||||
Unit LLMPricingRuleUnit `json:"unit" required:"true"`
|
||||
CacheMode LLMPricingRuleCacheMode `json:"cacheMode" required:"true"`
|
||||
CostInput float64 `json:"costInput" required:"true"`
|
||||
CostOutput float64 `json:"costOutput" required:"true"`
|
||||
CostCacheRead float64 `json:"costCacheRead" required:"true"`
|
||||
CostCacheWrite float64 `json:"costCacheWrite" required:"true"`
|
||||
IsOverride *bool `json:"isOverride,omitempty"`
|
||||
Enabled bool `json:"enabled" required:"true"`
|
||||
}
|
||||
|
||||
type UpdatableLLMPricingRules struct {
|
||||
Rules []UpdatableLLMPricingRule `json:"rules" required:"true"`
|
||||
}
|
||||
|
||||
type ListPricingRulesQuery struct {
|
||||
Offset int `query:"offset" json:"offset"`
|
||||
Limit int `query:"limit" json:"limit"`
|
||||
}
|
||||
|
||||
type GettablePricingRules struct {
|
||||
Items []*GettableLLMPricingRule `json:"items" required:"true"`
|
||||
Total int `json:"total" required:"true"`
|
||||
Offset int `json:"offset" required:"true"`
|
||||
Limit int `json:"limit" required:"true"`
|
||||
}
|
||||
|
||||
func (LLMPricingRuleUnit) Enum() []any {
|
||||
return []any{UnitPerMillionTokens}
|
||||
}
|
||||
|
||||
func (LLMPricingRuleCacheMode) Enum() []any {
|
||||
return []any{LLMPricingRuleCacheModeSubtract, LLMPricingRuleCacheModeAdditive, LLMPricingRuleCacheModeUnknown}
|
||||
}
|
||||
|
||||
func NewLLMPricingRuleFromStorable(s *StorableLLMPricingRule) *LLMPricingRule {
|
||||
pattern := make([]string, len(s.ModelPattern))
|
||||
copy(pattern, s.ModelPattern)
|
||||
|
||||
return &LLMPricingRule{
|
||||
TimeAuditable: s.TimeAuditable,
|
||||
UserAuditable: s.UserAuditable,
|
||||
ID: s.ID,
|
||||
OrgID: s.OrgID,
|
||||
SourceID: s.SourceID,
|
||||
Model: s.Model,
|
||||
ModelPattern: pattern,
|
||||
Unit: s.Unit,
|
||||
CacheMode: s.CacheMode,
|
||||
CostInput: s.CostInput,
|
||||
CostOutput: s.CostOutput,
|
||||
CostCacheRead: s.CostCacheRead,
|
||||
CostCacheWrite: s.CostCacheWrite,
|
||||
IsOverride: s.IsOverride,
|
||||
SyncedAt: s.SyncedAt,
|
||||
Enabled: s.Enabled,
|
||||
}
|
||||
}
|
||||
|
||||
func NewGettableLLMPricingRulesFromLLMPricingRules(items []*LLMPricingRule, total, offset, limit int) *GettablePricingRules {
|
||||
return &GettablePricingRules{
|
||||
Items: items,
|
||||
Total: total,
|
||||
Offset: offset,
|
||||
Limit: limit,
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
package llmpricingruletypes
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
// StringSlice is a []string that is stored as a JSON text column.
|
||||
// It is compatible with both SQLite and PostgreSQL.
|
||||
type StringSlice []string
|
||||
|
||||
// StorableLLMPricingRule is the bun/DB representation of an LLM pricing rule.
|
||||
type StorableLLMPricingRule struct {
|
||||
bun.BaseModel `bun:"table:llm_pricing_rules,alias:llm_pricing_rules"`
|
||||
|
||||
types.Identifiable
|
||||
types.TimeAuditable
|
||||
types.UserAuditable
|
||||
|
||||
OrgID valuer.UUID `bun:"org_id,type:text,notnull"`
|
||||
SourceID *valuer.UUID `bun:"source_id,type:text"`
|
||||
Model string `bun:"model,type:text,notnull"`
|
||||
ModelPattern StringSlice `bun:"model_pattern,type:text,notnull"`
|
||||
Unit LLMPricingRuleUnit `bun:"unit,type:text,notnull"`
|
||||
CacheMode LLMPricingRuleCacheMode `bun:"cache_mode,type:text,notnull"`
|
||||
CostInput float64 `bun:"cost_input,notnull"`
|
||||
CostOutput float64 `bun:"cost_output,notnull"`
|
||||
CostCacheRead float64 `bun:"cost_cache_read,notnull"`
|
||||
CostCacheWrite float64 `bun:"cost_cache_write,notnull"`
|
||||
// IsOverride marks the row as user-pinned. When true, Zeus skips it entirely.
|
||||
IsOverride bool `bun:"is_override,notnull,default:false"`
|
||||
SyncedAt *time.Time `bun:"synced_at"`
|
||||
Enabled bool `bun:"enabled,notnull,default:true"`
|
||||
}
|
||||
|
||||
func (s StringSlice) Value() (driver.Value, error) {
|
||||
if s == nil {
|
||||
return "[]", nil
|
||||
}
|
||||
b, err := json.Marshal(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return string(b), nil
|
||||
}
|
||||
|
||||
func (s *StringSlice) Scan(src any) error {
|
||||
var raw []byte
|
||||
switch v := src.(type) {
|
||||
case string:
|
||||
raw = []byte(v)
|
||||
case []byte:
|
||||
raw = v
|
||||
case nil:
|
||||
*s = nil
|
||||
return nil
|
||||
default:
|
||||
return errors.NewInternalf(errors.CodeInternal, "llmpricingruletypes: cannot scan %T into StringSlice", src)
|
||||
}
|
||||
return json.Unmarshal(raw, s)
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package llmpricingruletypes
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
|
||||
type Store interface {
|
||||
List(ctx context.Context, orgID valuer.UUID, offset, limit int) ([]*StorableLLMPricingRule, int, error)
|
||||
Get(ctx context.Context, orgID, id valuer.UUID) (*StorableLLMPricingRule, error)
|
||||
GetBySourceID(ctx context.Context, orgID, sourceID valuer.UUID) (*StorableLLMPricingRule, error)
|
||||
Create(ctx context.Context, rule *StorableLLMPricingRule) error
|
||||
Update(ctx context.Context, rule *StorableLLMPricingRule) error
|
||||
Delete(ctx context.Context, orgID, id valuer.UUID) error
|
||||
}
|
||||
@@ -17,13 +17,12 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
ErrCodeServiceAccountInvalidConfig = errors.MustNewCode("service_account_invalid_config")
|
||||
ErrCodeServiceAccountInvalidInput = errors.MustNewCode("service_account_invalid_input")
|
||||
ErrCodeServiceAccountAlreadyExists = errors.MustNewCode("service_account_already_exists")
|
||||
ErrCodeServiceAccountNotFound = errors.MustNewCode("service_account_not_found")
|
||||
ErrCodeServiceAccountRoleAlreadyExists = errors.MustNewCode("service_account_role_already_exists")
|
||||
ErrCodeServiceAccountOperationUnsupported = errors.MustNewCode("service_account_operation_unsupported")
|
||||
errInvalidServiceAccountName = errors.New(errors.TypeInvalidInput, ErrCodeServiceAccountInvalidInput, "name must start with a lowercase letter (a-z), contain only lowercase letters, numbers (0-9), and hyphens (-), and be at most 50 characters long")
|
||||
ErrCodeServiceAccountInvalidConfig = errors.MustNewCode("service_account_invalid_config")
|
||||
ErrCodeServiceAccountInvalidInput = errors.MustNewCode("service_account_invalid_input")
|
||||
ErrCodeServiceAccountAlreadyExists = errors.MustNewCode("service_account_already_exists")
|
||||
ErrCodeServiceAccountNotFound = errors.MustNewCode("service_account_not_found")
|
||||
ErrCodeServiceAccountRoleAlreadyExists = errors.MustNewCode("service_account_role_already_exists")
|
||||
errInvalidServiceAccountName = errors.New(errors.TypeInvalidInput, ErrCodeServiceAccountInvalidInput, "name must start with a lowercase letter (a-z), contain only lowercase letters, numbers (0-9), and hyphens (-), and be at most 50 characters long")
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -120,7 +119,7 @@ func (serviceAccount *ServiceAccount) UpdateStatus(status ServiceAccountStatus)
|
||||
|
||||
func (serviceAccount *ServiceAccount) ErrIfDeleted() error {
|
||||
if serviceAccount.Status == ServiceAccountStatusDeleted {
|
||||
return errors.New(errors.TypeUnsupported, ErrCodeServiceAccountOperationUnsupported, "this operation is not supported for disabled service account")
|
||||
return errors.Newf(errors.TypeNotFound, ErrCodeServiceAccountNotFound, "an active service account with id: %s does not exist", serviceAccount.ID)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -51,7 +51,7 @@ func TestJSONTypeSet() (map[string][]FieldDataType, MetadataStore) {
|
||||
// ── interests[] ───────────────────────────────────────────────────
|
||||
"interests": {FieldDataTypeArrayJSON},
|
||||
"interests[].entities": {FieldDataTypeArrayJSON},
|
||||
"interests[].entities[].product_codes": {FieldDataTypeArrayDynamic},
|
||||
"interests[].entities[].product_codes": {FieldDataTypeArrayDynamic},
|
||||
"interests[].entities[].reviews": {FieldDataTypeArrayJSON},
|
||||
"interests[].entities[].reviews[].entries": {FieldDataTypeArrayJSON},
|
||||
"interests[].entities[].reviews[].entries[].metadata": {FieldDataTypeArrayJSON},
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
# Dependencies
|
||||
node_modules/
|
||||
|
||||
# Build outputs
|
||||
dist/
|
||||
build/
|
||||
|
||||
# Test results
|
||||
test-results/
|
||||
playwright-report/
|
||||
coverage/
|
||||
|
||||
# Environment files
|
||||
.env
|
||||
.env.local
|
||||
.env.production
|
||||
|
||||
# Editor files
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# OS files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
@@ -1,68 +0,0 @@
|
||||
module.exports = {
|
||||
parser: '@typescript-eslint/parser',
|
||||
parserOptions: {
|
||||
ecmaVersion: 2022,
|
||||
sourceType: 'module',
|
||||
},
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:playwright/recommended',
|
||||
],
|
||||
env: {
|
||||
node: true,
|
||||
es2022: true,
|
||||
},
|
||||
rules: {
|
||||
// Code Quality
|
||||
'@typescript-eslint/no-unused-vars': 'error',
|
||||
'@typescript-eslint/no-explicit-any': 'warn',
|
||||
'prefer-const': 'error',
|
||||
'no-var': 'error',
|
||||
|
||||
// Formatting Rules (ESLint handles formatting)
|
||||
'semi': ['error', 'always'],
|
||||
'quotes': ['error', 'single', { avoidEscape: true }],
|
||||
'comma-dangle': ['error', 'always-multiline'],
|
||||
'indent': ['error', 2, { SwitchCase: 1 }],
|
||||
'object-curly-spacing': ['error', 'always'],
|
||||
'array-bracket-spacing': ['error', 'never'],
|
||||
'space-before-function-paren': ['error', {
|
||||
anonymous: 'always',
|
||||
named: 'never',
|
||||
asyncArrow: 'always',
|
||||
}],
|
||||
'keyword-spacing': 'error',
|
||||
'space-infix-ops': 'error',
|
||||
'eol-last': 'error',
|
||||
'no-trailing-spaces': 'error',
|
||||
'no-multiple-empty-lines': ['error', { max: 2, maxEOF: 1 }],
|
||||
|
||||
// Playwright-specific (enhanced)
|
||||
'playwright/expect-expect': 'error',
|
||||
'playwright/no-conditional-in-test': 'error',
|
||||
'playwright/no-page-pause': 'error',
|
||||
'playwright/no-wait-for-timeout': 'warn',
|
||||
'playwright/prefer-web-first-assertions': 'error',
|
||||
|
||||
// Console usage
|
||||
'no-console': ['warn', { allow: ['warn', 'error'] }],
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
// Config files can use console and have relaxed formatting
|
||||
files: ['*.config.{js,ts}', 'playwright.config.ts'],
|
||||
rules: {
|
||||
'no-console': 'off',
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
// Test files specific rules
|
||||
files: ['**/*.spec.ts', '**/*.test.ts'],
|
||||
rules: {
|
||||
'@typescript-eslint/no-explicit-any': 'off', // Page objects often need any
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
26
tests/e2e/.oxfmtrc.json
Normal file
26
tests/e2e/.oxfmtrc.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"$schema": "./node_modules/oxfmt/configuration_schema.json",
|
||||
"trailingComma": "all",
|
||||
"useTabs": true,
|
||||
"tabWidth": 1,
|
||||
"singleQuote": true,
|
||||
"jsxSingleQuote": false,
|
||||
"semi": true,
|
||||
"printWidth": 80,
|
||||
"bracketSpacing": true,
|
||||
"jsxBracketSameLine": false,
|
||||
"arrowParens": "always",
|
||||
"endOfLine": "lf",
|
||||
"quoteProps": "as-needed",
|
||||
"proseWrap": "preserve",
|
||||
"htmlWhitespaceSensitivity": "css",
|
||||
"embeddedLanguageFormatting": "auto",
|
||||
"sortPackageJson": false,
|
||||
"ignorePatterns": [
|
||||
"artifacts",
|
||||
"node_modules",
|
||||
"playwright-report",
|
||||
"**/*.md",
|
||||
"**/*.json"
|
||||
]
|
||||
}
|
||||
45
tests/e2e/.oxlintrc.json
Normal file
45
tests/e2e/.oxlintrc.json
Normal file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"$schema": "./node_modules/oxlint/configuration_schema.json",
|
||||
"jsPlugins": ["eslint-plugin-playwright"],
|
||||
"plugins": ["eslint", "typescript", "unicorn", "import", "promise"],
|
||||
"categories": {
|
||||
"correctness": "warn"
|
||||
},
|
||||
"env": {
|
||||
"builtin": true,
|
||||
"es2022": true,
|
||||
"node": true
|
||||
},
|
||||
"options": {
|
||||
"typeAware": true,
|
||||
"typeCheck": false
|
||||
},
|
||||
"rules": {
|
||||
"prefer-const": "error",
|
||||
"no-var": "error",
|
||||
"no-console": ["warn", { "allow": ["warn", "error"] }],
|
||||
"typescript/no-unused-vars": "error",
|
||||
"typescript/no-explicit-any": "warn",
|
||||
"playwright/expect-expect": "error",
|
||||
"playwright/no-conditional-in-test": "error",
|
||||
"playwright/no-page-pause": "error",
|
||||
"playwright/no-wait-for-timeout": "warn",
|
||||
"playwright/prefer-web-first-assertions": "error"
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.config.{js,ts}", "playwright.config.ts"],
|
||||
"rules": {
|
||||
"no-console": "off",
|
||||
"typescript/no-explicit-any": "off"
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": ["**/*.spec.ts", "**/*.test.ts"],
|
||||
"rules": {
|
||||
"typescript/no-explicit-any": "off"
|
||||
}
|
||||
}
|
||||
],
|
||||
"ignorePatterns": ["node_modules", "artifacts", "playwright-report"]
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
# Dependencies
|
||||
node_modules/
|
||||
|
||||
# Generated test outputs
|
||||
artifacts/
|
||||
playwright/.cache/
|
||||
|
||||
# Build outputs
|
||||
dist/
|
||||
|
||||
# Environment files
|
||||
.env
|
||||
.env.local
|
||||
.env*.local
|
||||
|
||||
# Lock files
|
||||
yarn.lock
|
||||
package-lock.json
|
||||
pnpm-lock.yaml
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
yarn-error.log
|
||||
|
||||
# IDE
|
||||
.vscode/
|
||||
.idea/
|
||||
|
||||
# Other
|
||||
.DS_Store
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"useTabs": false,
|
||||
"tabWidth": 2,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "all"
|
||||
}
|
||||
@@ -26,13 +26,12 @@ def test_setup(
|
||||
seeder_cfg = seeder.host_configs["8080"]
|
||||
out = _env_file(pytestconfig)
|
||||
out.parent.mkdir(parents=True, exist_ok=True)
|
||||
out.write_text(
|
||||
"# Generated by tests/e2e/bootstrap/setup.py — do not edit.\n"
|
||||
f"SIGNOZ_E2E_BASE_URL={host_cfg.base()}\n"
|
||||
f"SIGNOZ_E2E_USERNAME={USER_ADMIN_EMAIL}\n"
|
||||
f"SIGNOZ_E2E_PASSWORD={USER_ADMIN_PASSWORD}\n"
|
||||
f"SIGNOZ_E2E_SEEDER_URL={seeder_cfg.base()}\n"
|
||||
)
|
||||
with out.open("w") as f:
|
||||
f.write("# Generated by tests/e2e/bootstrap/setup.py — do not edit.\n")
|
||||
f.write(f"SIGNOZ_E2E_BASE_URL={host_cfg.base()}\n")
|
||||
f.write(f"SIGNOZ_E2E_USERNAME={USER_ADMIN_EMAIL}\n")
|
||||
f.write(f"SIGNOZ_E2E_PASSWORD={USER_ADMIN_PASSWORD}\n")
|
||||
f.write(f"SIGNOZ_E2E_SEEDER_URL={seeder_cfg.base()}\n")
|
||||
|
||||
|
||||
def test_teardown(
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import {
|
||||
test as base,
|
||||
expect,
|
||||
type Browser,
|
||||
type BrowserContext,
|
||||
type Page,
|
||||
test as base,
|
||||
expect,
|
||||
type Browser,
|
||||
type BrowserContext,
|
||||
type Page,
|
||||
} from '@playwright/test';
|
||||
|
||||
export type User = { email: string; password: string };
|
||||
|
||||
// Default user — admin from the pytest bootstrap (.env.local) or staging .env.
|
||||
export const ADMIN: User = {
|
||||
email: process.env.SIGNOZ_E2E_USERNAME!,
|
||||
password: process.env.SIGNOZ_E2E_PASSWORD!,
|
||||
email: process.env.SIGNOZ_E2E_USERNAME!,
|
||||
password: process.env.SIGNOZ_E2E_PASSWORD!,
|
||||
};
|
||||
|
||||
// Per-worker storageState cache. One login per unique user per worker.
|
||||
@@ -21,65 +21,65 @@ type StorageState = Awaited<ReturnType<BrowserContext['storageState']>>;
|
||||
const storageByUser = new Map<string, Promise<StorageState>>();
|
||||
|
||||
async function storageFor(browser: Browser, user: User): Promise<StorageState> {
|
||||
const cached = storageByUser.get(user.email);
|
||||
if (cached) return cached;
|
||||
const cached = storageByUser.get(user.email);
|
||||
if (cached) return cached;
|
||||
|
||||
const task = (async () => {
|
||||
const ctx = await browser.newContext();
|
||||
const page = await ctx.newPage();
|
||||
await login(page, user);
|
||||
const state = await ctx.storageState();
|
||||
await ctx.close();
|
||||
return state;
|
||||
})();
|
||||
const task = (async () => {
|
||||
const ctx = await browser.newContext();
|
||||
const page = await ctx.newPage();
|
||||
await login(page, user);
|
||||
const state = await ctx.storageState();
|
||||
await ctx.close();
|
||||
return state;
|
||||
})();
|
||||
|
||||
storageByUser.set(user.email, task);
|
||||
return task;
|
||||
storageByUser.set(user.email, task);
|
||||
return task;
|
||||
}
|
||||
|
||||
async function login(page: Page, user: User): Promise<void> {
|
||||
if (!user.email || !user.password) {
|
||||
throw new Error(
|
||||
'User credentials missing. Set SIGNOZ_E2E_USERNAME / SIGNOZ_E2E_PASSWORD ' +
|
||||
'(pytest bootstrap writes them to .env.local), or pass a User via test.use({ user: ... }).',
|
||||
);
|
||||
}
|
||||
await page.goto('/login?password=Y');
|
||||
await page.getByTestId('email').fill(user.email);
|
||||
await page.getByTestId('initiate_login').click();
|
||||
await page.getByTestId('password').fill(user.password);
|
||||
await page.getByRole('button', { name: 'Sign in with Password' }).click();
|
||||
// Post-login lands somewhere different depending on whether the org is
|
||||
// licensed (onboarding flow on ENTERPRISE) or not (legacy "Hello there"
|
||||
// welcome). Wait for URL to move off /login — whichever page follows
|
||||
// is fine, each spec navigates to the feature under test anyway.
|
||||
await page.waitForURL((url) => !url.pathname.startsWith('/login'));
|
||||
if (!user.email || !user.password) {
|
||||
throw new Error(
|
||||
'User credentials missing. Set SIGNOZ_E2E_USERNAME / SIGNOZ_E2E_PASSWORD ' +
|
||||
'(pytest bootstrap writes them to .env.local), or pass a User via test.use({ user: ... }).',
|
||||
);
|
||||
}
|
||||
await page.goto('/login?password=Y');
|
||||
await page.getByTestId('email').fill(user.email);
|
||||
await page.getByTestId('initiate_login').click();
|
||||
await page.getByTestId('password').fill(user.password);
|
||||
await page.getByRole('button', { name: 'Sign in with Password' }).click();
|
||||
// Post-login lands somewhere different depending on whether the org is
|
||||
// licensed (onboarding flow on ENTERPRISE) or not (legacy "Hello there"
|
||||
// welcome). Wait for URL to move off /login — whichever page follows
|
||||
// is fine, each spec navigates to the feature under test anyway.
|
||||
await page.waitForURL((url) => !url.pathname.startsWith('/login'));
|
||||
}
|
||||
|
||||
export const test = base.extend<{
|
||||
/**
|
||||
* User identity for this test. Override with `test.use({ user: ... })` at
|
||||
* the describe or test level to run the suite as a different user.
|
||||
* Defaults to ADMIN (the pytest-bootstrap-seeded admin).
|
||||
*/
|
||||
user: User;
|
||||
/**
|
||||
* User identity for this test. Override with `test.use({ user: ... })` at
|
||||
* the describe or test level to run the suite as a different user.
|
||||
* Defaults to ADMIN (the pytest-bootstrap-seeded admin).
|
||||
*/
|
||||
user: User;
|
||||
|
||||
/**
|
||||
* A Page whose context is already authenticated as `user`. First request
|
||||
* for a given user triggers one login per worker; the resulting
|
||||
* storageState is held in memory and reused for all later requests.
|
||||
*/
|
||||
authedPage: Page;
|
||||
/**
|
||||
* A Page whose context is already authenticated as `user`. First request
|
||||
* for a given user triggers one login per worker; the resulting
|
||||
* storageState is held in memory and reused for all later requests.
|
||||
*/
|
||||
authedPage: Page;
|
||||
}>({
|
||||
user: [ADMIN, { option: true }],
|
||||
user: [ADMIN, { option: true }],
|
||||
|
||||
authedPage: async ({ browser, user }, use) => {
|
||||
const storageState = await storageFor(browser, user);
|
||||
const ctx = await browser.newContext({ storageState });
|
||||
const page = await ctx.newPage();
|
||||
await use(page);
|
||||
await ctx.close();
|
||||
},
|
||||
authedPage: async ({ browser, user }, use) => {
|
||||
const storageState = await storageFor(browser, user);
|
||||
const ctx = await browser.newContext({ storageState });
|
||||
const page = await ctx.newPage();
|
||||
await use(page);
|
||||
await ctx.close();
|
||||
},
|
||||
});
|
||||
|
||||
export { expect };
|
||||
|
||||
@@ -16,8 +16,10 @@
|
||||
"codegen": "playwright codegen",
|
||||
"install:browsers": "playwright install",
|
||||
"install:cli": "npm install -g @playwright/cli@latest && playwright-cli install --skills",
|
||||
"lint": "eslint . --ext .ts,.js",
|
||||
"lint:fix": "eslint . --ext .ts,.js --fix",
|
||||
"fmt": "oxfmt .",
|
||||
"fmt:check": "oxfmt --check .",
|
||||
"lint": "oxlint .",
|
||||
"lint:fix": "oxlint . --fix",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"keywords": [
|
||||
@@ -31,11 +33,11 @@
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.57.0-alpha-2025-10-09",
|
||||
"@types/node": "^20.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.0.0",
|
||||
"@typescript-eslint/parser": "^6.0.0",
|
||||
"dotenv": "^16.0.0",
|
||||
"eslint": "^9.26.0",
|
||||
"eslint-plugin-playwright": "^0.16.0",
|
||||
"eslint-plugin-playwright": "^2.10.2",
|
||||
"oxfmt": "^0.41.0",
|
||||
"oxlint": "^1.59.0",
|
||||
"oxlint-tsgolint": "^0.20.0",
|
||||
"typescript": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user