mirror of
https://github.com/SigNoz/signoz.git
synced 2026-04-23 12:20:31 +01:00
Compare commits
16 Commits
chore/run-
...
issue_4360
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
70e37817b9 | ||
|
|
c5645c38c4 | ||
|
|
5c7c262d4e | ||
|
|
93f5df9185 | ||
|
|
89b755a6b0 | ||
|
|
021f1c5775 | ||
|
|
30d3f754b5 | ||
|
|
d5dcdf382c | ||
|
|
ce5e3e7943 | ||
|
|
07cb56c548 | ||
|
|
6e382aa363 | ||
|
|
115ee70a9a | ||
|
|
a58a3d4a68 | ||
|
|
6899eb0124 | ||
|
|
de5bec0195 | ||
|
|
e359b03c25 |
70
.github/workflows/e2eci.yaml
vendored
Normal file
70
.github/workflows/e2eci.yaml
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
name: e2eci
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types:
|
||||
- labeled
|
||||
pull_request_target:
|
||||
types:
|
||||
- labeled
|
||||
|
||||
jobs:
|
||||
test:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
project:
|
||||
- chromium
|
||||
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
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: 3.13
|
||||
- name: uv
|
||||
uses: astral-sh/setup-uv@v4
|
||||
- name: node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: lts/*
|
||||
- name: python-install
|
||||
run: |
|
||||
cd tests && uv sync
|
||||
- name: yarn-install
|
||||
run: |
|
||||
cd tests/e2e && yarn install --frozen-lockfile
|
||||
- name: playwright-browsers
|
||||
run: |
|
||||
cd tests/e2e && yarn playwright install --with-deps ${{ matrix.project }}
|
||||
- name: bring-up-stack
|
||||
run: |
|
||||
cd tests && \
|
||||
uv run pytest \
|
||||
--basetemp=./tmp/ \
|
||||
-vv --reuse --with-web \
|
||||
e2e/bootstrap/setup.py::test_setup
|
||||
- name: playwright-test
|
||||
run: |
|
||||
cd tests/e2e && \
|
||||
yarn playwright test --project=${{ matrix.project }}
|
||||
- name: teardown-stack
|
||||
if: always()
|
||||
run: |
|
||||
cd tests && \
|
||||
uv run pytest \
|
||||
--basetemp=./tmp/ \
|
||||
-vv --teardown \
|
||||
e2e/bootstrap/setup.py::test_teardown
|
||||
- name: upload-artifacts
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: playwright-artifacts-${{ matrix.project }}
|
||||
path: tests/e2e/artifacts/
|
||||
retention-days: 5
|
||||
23
.github/workflows/integrationci.yaml
vendored
23
.github/workflows/integrationci.yaml
vendored
@@ -25,11 +25,11 @@ jobs:
|
||||
uses: astral-sh/setup-uv@v4
|
||||
- name: install
|
||||
run: |
|
||||
cd tests/integration && uv sync
|
||||
cd tests && uv sync
|
||||
- name: fmt
|
||||
run: |
|
||||
make py-fmt
|
||||
git diff --exit-code -- tests/integration/
|
||||
git diff --exit-code -- tests/
|
||||
- name: lint
|
||||
run: |
|
||||
make py-lint
|
||||
@@ -37,21 +37,21 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
src:
|
||||
- bootstrap
|
||||
- passwordauthn
|
||||
suite:
|
||||
- alerts
|
||||
- callbackauthn
|
||||
- cloudintegrations
|
||||
- dashboard
|
||||
- ingestionkeys
|
||||
- logspipelines
|
||||
- passwordauthn
|
||||
- preference
|
||||
- querier
|
||||
- rawexportdata
|
||||
- role
|
||||
- ttl
|
||||
- alerts
|
||||
- ingestionkeys
|
||||
- rootuser
|
||||
- serviceaccount
|
||||
- ttl
|
||||
sqlstore-provider:
|
||||
- postgres
|
||||
- sqlite
|
||||
@@ -79,8 +79,9 @@ jobs:
|
||||
uses: astral-sh/setup-uv@v4
|
||||
- name: install
|
||||
run: |
|
||||
cd tests/integration && uv sync
|
||||
cd tests && uv sync
|
||||
- name: webdriver
|
||||
if: matrix.suite == 'callbackauthn'
|
||||
run: |
|
||||
wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | sudo apt-key add -
|
||||
echo "deb http://dl.google.com/linux/chrome/deb/ stable main" | sudo tee -a /etc/apt/sources.list.d/google-chrome.list
|
||||
@@ -99,10 +100,10 @@ jobs:
|
||||
google-chrome-stable --version
|
||||
- name: run
|
||||
run: |
|
||||
cd tests/integration && \
|
||||
cd tests && \
|
||||
uv run pytest \
|
||||
--basetemp=./tmp/ \
|
||||
src/${{matrix.src}} \
|
||||
integration/tests/${{matrix.suite}} \
|
||||
--sqlstore-provider ${{matrix.sqlstore-provider}} \
|
||||
--sqlite-mode ${{matrix.sqlite-mode}} \
|
||||
--postgres-version ${{matrix.postgres-version}} \
|
||||
|
||||
62
.github/workflows/run-e2e.yaml
vendored
62
.github/workflows/run-e2e.yaml
vendored
@@ -1,62 +0,0 @@
|
||||
name: e2eci
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
userRole:
|
||||
description: "Role of the user (ADMIN, EDITOR, VIEWER)"
|
||||
required: true
|
||||
type: choice
|
||||
options:
|
||||
- ADMIN
|
||||
- EDITOR
|
||||
- VIEWER
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Run Playwright Tests
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: lts/*
|
||||
|
||||
- name: Mask secrets and input
|
||||
run: |
|
||||
echo "::add-mask::${{ secrets.BASE_URL }}"
|
||||
echo "::add-mask::${{ secrets.LOGIN_USERNAME }}"
|
||||
echo "::add-mask::${{ secrets.LOGIN_PASSWORD }}"
|
||||
echo "::add-mask::${{ github.event.inputs.userRole }}"
|
||||
|
||||
- name: Install dependencies
|
||||
working-directory: frontend
|
||||
run: |
|
||||
npm install -g yarn
|
||||
yarn
|
||||
|
||||
- name: Install Playwright Browsers
|
||||
working-directory: frontend
|
||||
run: yarn playwright install --with-deps
|
||||
|
||||
- name: Run Playwright Tests
|
||||
working-directory: frontend
|
||||
run: |
|
||||
BASE_URL="${{ secrets.BASE_URL }}" \
|
||||
LOGIN_USERNAME="${{ secrets.LOGIN_USERNAME }}" \
|
||||
LOGIN_PASSWORD="${{ secrets.LOGIN_PASSWORD }}" \
|
||||
USER_ROLE="${{ github.event.inputs.userRole }}" \
|
||||
yarn playwright test
|
||||
|
||||
- name: Upload Playwright Report
|
||||
uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
with:
|
||||
name: playwright-report
|
||||
path: frontend/playwright-report/
|
||||
retention-days: 30
|
||||
22
Makefile
22
Makefile
@@ -201,26 +201,26 @@ docker-buildx-enterprise: go-build-enterprise js-build
|
||||
# python commands
|
||||
##############################################################
|
||||
.PHONY: py-fmt
|
||||
py-fmt: ## Run black for integration tests
|
||||
@cd tests/integration && uv run black .
|
||||
py-fmt: ## Run black across the shared tests project
|
||||
@cd tests && uv run black .
|
||||
|
||||
.PHONY: py-lint
|
||||
py-lint: ## Run lint for integration tests
|
||||
@cd tests/integration && uv run isort .
|
||||
@cd tests/integration && uv run autoflake .
|
||||
@cd tests/integration && uv run pylint .
|
||||
py-lint: ## Run lint across the shared tests project
|
||||
@cd tests && uv run isort .
|
||||
@cd tests && uv run autoflake .
|
||||
@cd tests && uv run pylint .
|
||||
|
||||
.PHONY: py-test-setup
|
||||
py-test-setup: ## Runs integration tests
|
||||
@cd tests/integration && uv run pytest --basetemp=./tmp/ -vv --reuse --capture=no src/bootstrap/setup.py::test_setup
|
||||
py-test-setup: ## Bring up the shared SigNoz backend used by integration and e2e tests
|
||||
@cd tests && uv run pytest --basetemp=./tmp/ -vv --reuse --capture=no integration/bootstrap/setup.py::test_setup
|
||||
|
||||
.PHONY: py-test-teardown
|
||||
py-test-teardown: ## Runs integration tests with teardown
|
||||
@cd tests/integration && uv run pytest --basetemp=./tmp/ -vv --teardown --capture=no src/bootstrap/setup.py::test_teardown
|
||||
py-test-teardown: ## Tear down the shared SigNoz backend
|
||||
@cd tests && uv run pytest --basetemp=./tmp/ -vv --teardown --capture=no integration/bootstrap/setup.py::test_teardown
|
||||
|
||||
.PHONY: py-test
|
||||
py-test: ## Runs integration tests
|
||||
@cd tests/integration && uv run pytest --basetemp=./tmp/ -vv --capture=no src/
|
||||
@cd tests && uv run pytest --basetemp=./tmp/ -vv --capture=no integration/tests/
|
||||
|
||||
.PHONY: py-clean
|
||||
py-clean: ## Clear all pycache and pytest cache from tests directory recursively
|
||||
|
||||
@@ -2287,6 +2287,287 @@ components:
|
||||
enabled:
|
||||
type: boolean
|
||||
type: object
|
||||
InframonitoringtypesHostFilter:
|
||||
properties:
|
||||
expression:
|
||||
type: string
|
||||
filterByStatus:
|
||||
$ref: '#/components/schemas/InframonitoringtypesHostStatus'
|
||||
type: object
|
||||
InframonitoringtypesHostRecord:
|
||||
properties:
|
||||
activeHostCount:
|
||||
type: integer
|
||||
cpu:
|
||||
format: double
|
||||
type: number
|
||||
diskUsage:
|
||||
format: double
|
||||
type: number
|
||||
hostName:
|
||||
type: string
|
||||
inactiveHostCount:
|
||||
type: integer
|
||||
load15:
|
||||
format: double
|
||||
type: number
|
||||
memory:
|
||||
format: double
|
||||
type: number
|
||||
meta:
|
||||
additionalProperties: {}
|
||||
nullable: true
|
||||
type: object
|
||||
status:
|
||||
$ref: '#/components/schemas/InframonitoringtypesHostStatus'
|
||||
wait:
|
||||
format: double
|
||||
type: number
|
||||
required:
|
||||
- hostName
|
||||
- status
|
||||
- activeHostCount
|
||||
- inactiveHostCount
|
||||
- cpu
|
||||
- memory
|
||||
- wait
|
||||
- load15
|
||||
- diskUsage
|
||||
- meta
|
||||
type: object
|
||||
InframonitoringtypesHostStatus:
|
||||
enum:
|
||||
- active
|
||||
- inactive
|
||||
- ""
|
||||
type: string
|
||||
InframonitoringtypesHosts:
|
||||
properties:
|
||||
endTimeBeforeRetention:
|
||||
type: boolean
|
||||
records:
|
||||
items:
|
||||
$ref: '#/components/schemas/InframonitoringtypesHostRecord'
|
||||
nullable: true
|
||||
type: array
|
||||
requiredMetricsCheck:
|
||||
$ref: '#/components/schemas/InframonitoringtypesRequiredMetricsCheck'
|
||||
total:
|
||||
type: integer
|
||||
type:
|
||||
$ref: '#/components/schemas/InframonitoringtypesResponseType'
|
||||
warning:
|
||||
$ref: '#/components/schemas/Querybuildertypesv5QueryWarnData'
|
||||
required:
|
||||
- type
|
||||
- records
|
||||
- total
|
||||
- requiredMetricsCheck
|
||||
- endTimeBeforeRetention
|
||||
type: object
|
||||
InframonitoringtypesPostableHosts:
|
||||
properties:
|
||||
end:
|
||||
format: int64
|
||||
type: integer
|
||||
filter:
|
||||
$ref: '#/components/schemas/InframonitoringtypesHostFilter'
|
||||
groupBy:
|
||||
items:
|
||||
$ref: '#/components/schemas/Querybuildertypesv5GroupByKey'
|
||||
nullable: true
|
||||
type: array
|
||||
limit:
|
||||
type: integer
|
||||
offset:
|
||||
type: integer
|
||||
orderBy:
|
||||
$ref: '#/components/schemas/Querybuildertypesv5OrderBy'
|
||||
start:
|
||||
format: int64
|
||||
type: integer
|
||||
required:
|
||||
- start
|
||||
- end
|
||||
- limit
|
||||
type: object
|
||||
InframonitoringtypesRequiredMetricsCheck:
|
||||
properties:
|
||||
missingMetrics:
|
||||
items:
|
||||
type: string
|
||||
nullable: true
|
||||
type: array
|
||||
required:
|
||||
- missingMetrics
|
||||
type: object
|
||||
InframonitoringtypesResponseType:
|
||||
enum:
|
||||
- list
|
||||
- grouped_list
|
||||
type: string
|
||||
LlmpricingruletypesCacheMode:
|
||||
enum:
|
||||
- subtract
|
||||
- additive
|
||||
- unknown
|
||||
type: string
|
||||
LlmpricingruletypesGettablePricingRules:
|
||||
properties:
|
||||
items:
|
||||
items:
|
||||
$ref: '#/components/schemas/LlmpricingruletypesPricingRule'
|
||||
nullable: true
|
||||
type: array
|
||||
limit:
|
||||
type: integer
|
||||
offset:
|
||||
type: integer
|
||||
total:
|
||||
type: integer
|
||||
required:
|
||||
- items
|
||||
- total
|
||||
- offset
|
||||
- limit
|
||||
type: object
|
||||
LlmpricingruletypesPricingRule:
|
||||
properties:
|
||||
cacheMode:
|
||||
$ref: '#/components/schemas/LlmpricingruletypesCacheMode'
|
||||
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/LlmpricingruletypesUnit'
|
||||
updatedAt:
|
||||
format: date-time
|
||||
type: string
|
||||
updatedBy:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
- orgId
|
||||
- modelName
|
||||
- modelPattern
|
||||
- unit
|
||||
- cacheMode
|
||||
- costInput
|
||||
- costOutput
|
||||
- costCacheRead
|
||||
- costCacheWrite
|
||||
- isOverride
|
||||
- enabled
|
||||
type: object
|
||||
LlmpricingruletypesUnit:
|
||||
enum:
|
||||
- per_million_tokens
|
||||
type: string
|
||||
LlmpricingruletypesUpdatablePricingRule:
|
||||
properties:
|
||||
cacheMode:
|
||||
$ref: '#/components/schemas/LlmpricingruletypesCacheMode'
|
||||
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/LlmpricingruletypesUnit'
|
||||
required:
|
||||
- modelName
|
||||
- modelPattern
|
||||
- unit
|
||||
- cacheMode
|
||||
- costInput
|
||||
- costOutput
|
||||
- costCacheRead
|
||||
- costCacheWrite
|
||||
- enabled
|
||||
type: object
|
||||
LlmpricingruletypesUpdatablePricingRulesRequest:
|
||||
properties:
|
||||
rules:
|
||||
items:
|
||||
$ref: '#/components/schemas/LlmpricingruletypesUpdatablePricingRule'
|
||||
nullable: true
|
||||
type: array
|
||||
required:
|
||||
- rules
|
||||
type: object
|
||||
LlmpricingruletypesUpdatablePricingRulesResponse:
|
||||
properties:
|
||||
inserted:
|
||||
type: integer
|
||||
preserved:
|
||||
type: integer
|
||||
updated:
|
||||
type: integer
|
||||
required:
|
||||
- inserted
|
||||
- updated
|
||||
- preserved
|
||||
type: object
|
||||
MetricsexplorertypesInspectMetricsRequest:
|
||||
properties:
|
||||
end:
|
||||
@@ -6977,6 +7258,230 @@ 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:
|
||||
- llm-pricing-rules
|
||||
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/LlmpricingruletypesUpdatablePricingRulesRequest'
|
||||
responses:
|
||||
"200":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
properties:
|
||||
data:
|
||||
$ref: '#/components/schemas/LlmpricingruletypesUpdatablePricingRulesResponse'
|
||||
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:
|
||||
- ADMIN
|
||||
- tokenizer:
|
||||
- ADMIN
|
||||
summary: Bulk update pricing rules
|
||||
tags:
|
||||
- llm-pricing-rules
|
||||
/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:
|
||||
- llm-pricing-rules
|
||||
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/LlmpricingruletypesPricingRule'
|
||||
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:
|
||||
- llm-pricing-rules
|
||||
/api/v1/logs/promote_paths:
|
||||
get:
|
||||
deprecated: false
|
||||
@@ -9853,6 +10358,72 @@ paths:
|
||||
summary: Health check
|
||||
tags:
|
||||
- health
|
||||
/api/v2/infra_monitoring/hosts:
|
||||
post:
|
||||
deprecated: false
|
||||
description: 'Returns a paginated list of hosts with key infrastructure metrics:
|
||||
CPU usage (%), memory usage (%), I/O wait (%), disk usage (%), and 15-minute
|
||||
load average. Each host includes its current status (active/inactive based
|
||||
on metrics reported in the last 10 minutes) and metadata attributes (e.g.,
|
||||
os.type). Supports filtering via a filter expression, filtering by host status,
|
||||
custom groupBy to aggregate hosts by any attribute, ordering by any of the
|
||||
five metrics, and pagination via offset/limit. The response type is ''list''
|
||||
for the default host.name grouping or ''grouped_list'' for custom groupBy
|
||||
keys. Also reports missing required metrics and whether the requested time
|
||||
range falls before the data retention boundary.'
|
||||
operationId: ListHosts
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/InframonitoringtypesPostableHosts'
|
||||
responses:
|
||||
"200":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
properties:
|
||||
data:
|
||||
$ref: '#/components/schemas/InframonitoringtypesHosts'
|
||||
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 Hosts for Infra Monitoring
|
||||
tags:
|
||||
- inframonitoring
|
||||
/api/v2/livez:
|
||||
get:
|
||||
deprecated: false
|
||||
|
||||
@@ -1,216 +0,0 @@
|
||||
# Integration Tests
|
||||
|
||||
SigNoz uses integration tests to verify that different components work together correctly in a real environment. These tests run against actual services (ClickHouse, PostgreSQL, etc.) to ensure end-to-end functionality.
|
||||
|
||||
## How to set up the integration test environment?
|
||||
|
||||
### Prerequisites
|
||||
|
||||
Before running integration tests, ensure you have the following installed:
|
||||
|
||||
- Python 3.13+
|
||||
- [uv](https://docs.astral.sh/uv/getting-started/installation/)
|
||||
- Docker (for containerized services)
|
||||
|
||||
### Initial Setup
|
||||
|
||||
1. Navigate to the integration tests directory:
|
||||
```bash
|
||||
cd tests/integration
|
||||
```
|
||||
|
||||
2. Install dependencies using uv:
|
||||
```bash
|
||||
uv sync
|
||||
```
|
||||
|
||||
> **_NOTE:_** the build backend could throw an error while installing `psycopg2`, pleae see https://www.psycopg.org/docs/install.html#build-prerequisites
|
||||
|
||||
### Starting the Test Environment
|
||||
|
||||
To spin up all the containers necessary for writing integration tests and keep them running:
|
||||
|
||||
```bash
|
||||
uv run pytest --basetemp=./tmp/ -vv --reuse src/bootstrap/setup.py::test_setup
|
||||
```
|
||||
|
||||
This command will:
|
||||
- Start all required services (ClickHouse, PostgreSQL, Zookeeper, etc.)
|
||||
- Keep containers running due to the `--reuse` flag
|
||||
- Verify that the setup is working correctly
|
||||
|
||||
### Stopping the Test Environment
|
||||
|
||||
When you're done writing integration tests, clean up the environment:
|
||||
|
||||
```bash
|
||||
uv run pytest --basetemp=./tmp/ -vv --teardown -s src/bootstrap/setup.py::test_teardown
|
||||
```
|
||||
|
||||
This will destroy the running integration test setup and clean up resources.
|
||||
|
||||
## Understanding the Integration Test Framework
|
||||
|
||||
Python and pytest form the foundation of the integration testing framework. Testcontainers are used to spin up disposable integration environments. Wiremock is used to spin up **test doubles** of other services.
|
||||
|
||||
- **Why Python/pytest?** It's expressive, low-boilerplate, and has powerful fixture capabilities that make integration testing straightforward. Extensive libraries for HTTP requests, JSON handling, and data analysis (numpy) make it easier to test APIs and verify data
|
||||
- **Why testcontainers?** They let us spin up isolated dependencies that match our production environment without complex setup.
|
||||
- **Why wiremock?** Well maintained, documented and extensible.
|
||||
|
||||
```
|
||||
.
|
||||
├── conftest.py
|
||||
├── fixtures
|
||||
│ ├── __init__.py
|
||||
│ ├── auth.py
|
||||
│ ├── clickhouse.py
|
||||
│ ├── fs.py
|
||||
│ ├── http.py
|
||||
│ ├── migrator.py
|
||||
│ ├── network.py
|
||||
│ ├── postgres.py
|
||||
│ ├── signoz.py
|
||||
│ ├── sql.py
|
||||
│ ├── sqlite.py
|
||||
│ ├── types.py
|
||||
│ └── zookeeper.py
|
||||
├── uv.lock
|
||||
├── pyproject.toml
|
||||
└── src
|
||||
└── bootstrap
|
||||
├── __init__.py
|
||||
├── 01_database.py
|
||||
├── 02_register.py
|
||||
└── 03_license.py
|
||||
```
|
||||
|
||||
Each test suite follows some important principles:
|
||||
|
||||
1. **Organization**: Test suites live under `src/` in self-contained packages. Fixtures (a pytest concept) live inside `fixtures/`.
|
||||
2. **Execution Order**: Files are prefixed with two-digit numbers (`01_`, `02_`, `03_`) to ensure sequential execution.
|
||||
3. **Time Constraints**: Each suite should complete in under 10 minutes (setup takes ~4 mins).
|
||||
|
||||
### Test Suite Design
|
||||
|
||||
Test suites should target functional domains or subsystems within SigNoz. When designing a test suite, consider these principles:
|
||||
|
||||
- **Functional Cohesion**: Group tests around a specific capability or service boundary
|
||||
- **Data Flow**: Follow the path of data through related components
|
||||
- **Change Patterns**: Components frequently modified together should be tested together
|
||||
|
||||
The exact boundaries for modules are intentionally flexible, allowing teams to define logical groupings based on their specific context and knowledge of the system.
|
||||
|
||||
Eg: The **bootstrap** integration test suite validates core system functionality:
|
||||
|
||||
- Database initialization
|
||||
- Version check
|
||||
|
||||
Other test suites can be **pipelines, auth, querier.**
|
||||
|
||||
## How to write an integration test?
|
||||
|
||||
Now start writing an integration test. Create a new file `src/bootstrap/05_version.py` and paste the following:
|
||||
|
||||
```python
|
||||
import requests
|
||||
|
||||
from fixtures import types
|
||||
from fixtures.logger import setup_logger
|
||||
|
||||
logger = setup_logger(__name__)
|
||||
|
||||
def test_version(signoz: types.SigNoz) -> None:
|
||||
response = requests.get(signoz.self.host_config.get("/api/v1/version"), timeout=2)
|
||||
logger.info(response)
|
||||
```
|
||||
|
||||
We have written a simple test which calls the `version` endpoint of the container in step 1. In **order to just run this function, run the following command:**
|
||||
|
||||
```bash
|
||||
uv run pytest --basetemp=./tmp/ -vv --reuse src/bootstrap/05_version.py::test_version
|
||||
```
|
||||
|
||||
> Note: The `--reuse` flag is used to reuse the environment if it is already running. Always use this flag when writing and running integration tests. If you don't use this flag, the environment will be destroyed and recreated every time you run the test.
|
||||
|
||||
Here's another example of how to write a more comprehensive integration test:
|
||||
|
||||
```python
|
||||
from http import HTTPStatus
|
||||
import requests
|
||||
from fixtures import types
|
||||
from fixtures.logger import setup_logger
|
||||
|
||||
logger = setup_logger(__name__)
|
||||
|
||||
def test_user_registration(signoz: types.SigNoz) -> None:
|
||||
"""Test user registration functionality."""
|
||||
response = requests.post(
|
||||
signoz.self.host_configs["8080"].get("/api/v1/register"),
|
||||
json={
|
||||
"name": "testuser",
|
||||
"orgId": "",
|
||||
"orgName": "test.org",
|
||||
"email": "test@example.com",
|
||||
"password": "password123Z$",
|
||||
},
|
||||
timeout=2,
|
||||
)
|
||||
|
||||
assert response.status_code == HTTPStatus.OK
|
||||
assert response.json()["setupCompleted"] is True
|
||||
```
|
||||
|
||||
## How to run integration tests?
|
||||
|
||||
### Running All Tests
|
||||
|
||||
```bash
|
||||
uv run pytest --basetemp=./tmp/ -vv --reuse src/
|
||||
```
|
||||
|
||||
### Running Specific Test Categories
|
||||
|
||||
```bash
|
||||
uv run pytest --basetemp=./tmp/ -vv --reuse src/<suite>
|
||||
|
||||
# Run querier tests
|
||||
uv run pytest --basetemp=./tmp/ -vv --reuse src/querier/
|
||||
# Run auth tests
|
||||
uv run pytest --basetemp=./tmp/ -vv --reuse src/auth/
|
||||
```
|
||||
|
||||
### Running Individual Tests
|
||||
|
||||
```bash
|
||||
uv run pytest --basetemp=./tmp/ -vv --reuse src/<suite>/<file>.py::test_name
|
||||
|
||||
# Run test_register in file 01_register.py in passwordauthn suite
|
||||
uv run pytest --basetemp=./tmp/ -vv --reuse src/passwordauthn/01_register.py::test_register
|
||||
```
|
||||
|
||||
## How to configure different options for integration tests?
|
||||
|
||||
Tests can be configured using pytest options:
|
||||
|
||||
- `--sqlstore-provider` - Choose database provider (default: postgres)
|
||||
- `--sqlite-mode` - SQLite journal mode: `delete` or `wal` (default: delete). Only relevant when `--sqlstore-provider=sqlite`.
|
||||
- `--postgres-version` - PostgreSQL version (default: 15)
|
||||
- `--clickhouse-version` - ClickHouse version (default: 25.5.6)
|
||||
- `--zookeeper-version` - Zookeeper version (default: 3.7.1)
|
||||
|
||||
Example:
|
||||
```bash
|
||||
uv run pytest --basetemp=./tmp/ -vv --reuse --sqlstore-provider=postgres --postgres-version=14 src/auth/
|
||||
```
|
||||
|
||||
## What should I remember?
|
||||
|
||||
- **Always use the `--reuse` flag** when setting up the environment to keep containers running
|
||||
- **Use the `--teardown` flag** when cleaning up to avoid resource leaks
|
||||
- **Follow the naming convention** with two-digit numeric prefixes (`01_`, `02_`) for test execution order
|
||||
- **Use proper timeouts** in HTTP requests to avoid hanging tests
|
||||
- **Clean up test data** between tests to avoid interference
|
||||
- **Use descriptive test names** that clearly indicate what is being tested
|
||||
- **Leverage fixtures** for common setup and authentication
|
||||
- **Test both success and failure scenarios** to ensure robust functionality
|
||||
- **`--sqlite-mode=wal` does not work on macOS.** The integration test environment runs SigNoz inside a Linux container with the SQLite database file mounted from the macOS host. WAL mode requires shared memory between connections, and connections crossing the VM boundary (macOS host ↔ Linux container) cannot share the WAL index, resulting in `SQLITE_IOERR_SHORT_READ`. WAL mode is tested in CI on Linux only.
|
||||
@@ -15,7 +15,6 @@ We **recommend** (almost enforce) reviewing these guides before contributing to
|
||||
- [Endpoint](endpoint.md) - HTTP endpoint patterns
|
||||
- [Flagger](flagger.md) - Feature flag patterns
|
||||
- [Handler](handler.md) - HTTP handler patterns
|
||||
- [Integration](integration.md) - Integration testing
|
||||
- [Provider](provider.md) - Dependency injection and provider patterns
|
||||
- [Packages](packages.md) - Naming, layout, and conventions for `pkg/` packages
|
||||
- [Service](service.md) - Managed service lifecycle with `factory.Service`
|
||||
|
||||
261
docs/contributing/tests/e2e.md
Normal file
261
docs/contributing/tests/e2e.md
Normal file
@@ -0,0 +1,261 @@
|
||||
# E2E Tests
|
||||
|
||||
SigNoz uses end-to-end tests to verify the frontend works correctly against a real backend. These tests use Playwright to drive a real browser against a containerized SigNoz stack that pytest brings up — the same fixture graph integration tests use, with an extra HTTP seeder container for per-spec telemetry seeding.
|
||||
|
||||
## How to set up the E2E test environment?
|
||||
|
||||
### Prerequisites
|
||||
|
||||
Before running E2E tests, ensure you have the following installed:
|
||||
|
||||
- Python 3.13+
|
||||
- [uv](https://docs.astral.sh/uv/getting-started/installation/)
|
||||
- Docker (for containerized services)
|
||||
- Node 18+ and Yarn
|
||||
|
||||
### Initial Setup
|
||||
|
||||
1. Install Python deps for the shared tests project:
|
||||
```bash
|
||||
cd tests
|
||||
uv sync
|
||||
```
|
||||
|
||||
2. Install Node deps and Playwright browsers:
|
||||
```bash
|
||||
cd e2e
|
||||
yarn install
|
||||
yarn install:browsers # one-time Playwright browser install
|
||||
```
|
||||
|
||||
### Starting the Test Environment
|
||||
|
||||
To spin up the backend stack (SigNoz, ClickHouse, Postgres, Zookeeper, Zeus mock, gateway mock, seeder, migrator-with-web) and keep it running:
|
||||
|
||||
```bash
|
||||
cd tests
|
||||
uv run pytest --basetemp=./tmp/ -vv --reuse --with-web \
|
||||
e2e/bootstrap/setup.py::test_setup
|
||||
```
|
||||
|
||||
This command will:
|
||||
- Bring up all containers via pytest fixtures
|
||||
- Register the admin user (`admin@integration.test` / `password123Z$`)
|
||||
- Apply the enterprise license (via a WireMock stub of Zeus) and dismiss the org-onboarding prompt so specs can navigate directly to feature pages
|
||||
- Start the HTTP seeder container (`tests/seeder/` — exposing `/telemetry/{traces,logs,metrics}` POST + DELETE)
|
||||
- Write backend coordinates to `tests/e2e/.env.local` (loaded by `playwright.config.ts` via dotenv)
|
||||
- Keep containers running via the `--reuse` flag
|
||||
|
||||
The `--with-web` flag builds the frontend into the SigNoz container — required for E2E. The build takes ~4 mins on a cold start.
|
||||
|
||||
### Stopping the Test Environment
|
||||
|
||||
When you're done writing E2E tests, clean up the environment:
|
||||
|
||||
```bash
|
||||
cd tests
|
||||
uv run pytest --basetemp=./tmp/ -vv --teardown \
|
||||
e2e/bootstrap/setup.py::test_teardown
|
||||
```
|
||||
|
||||
## Understanding the E2E Test Framework
|
||||
|
||||
Playwright drives a real browser (Chromium / Firefox / WebKit) against the running SigNoz frontend. The backend is brought up by the same pytest fixture graph integration tests use, so both suites share one source of truth for container lifecycle, license seeding, and test-user accounts.
|
||||
|
||||
- **Why Playwright?** First-class TypeScript support, network interception, automatic wait-for-visibility, built-in trace viewer that captures every request/response the UI triggers — so specs rarely need separate API probes alongside UI clicks.
|
||||
- **Why pytest for lifecycle?** The integration suite already owns container bring-up. Reusing it keeps the E2E stack exactly in sync with the integration stack and avoids a parallel lifecycle framework.
|
||||
- **Why a separate seeder container?** Per-spec telemetry seeding (traces / logs / metrics) needs a thin HTTP wrapper around the ClickHouse insert helpers so a browser spec can POST from inside the test. The seeder lives at `tests/seeder/`, is built from `tests/Dockerfile.seeder`, and reuses the same `fixtures/{traces,logs,metrics}.py` as integration tests.
|
||||
|
||||
```
|
||||
tests/
|
||||
├── fixtures/ # shared with integration (see integration.md)
|
||||
├── integration/ # pytest integration suite
|
||||
├── seeder/ # standalone HTTP seeder container
|
||||
│ ├── __init__.py
|
||||
│ ├── Dockerfile
|
||||
│ └── server.py # FastAPI app wrapping fixtures.{traces,logs,metrics}
|
||||
└── e2e/
|
||||
├── package.json
|
||||
├── playwright.config.ts # loads .env + .env.local via dotenv
|
||||
├── .env.example # staging-mode template
|
||||
├── .env.local # generated by bootstrap/setup.py (gitignored)
|
||||
├── bootstrap/
|
||||
│ └── setup.py # test_setup / test_teardown — pytest lifecycle
|
||||
├── fixtures/
|
||||
│ └── auth.ts # authedPage Playwright fixture + per-worker storageState cache
|
||||
├── tests/ # Playwright .spec.ts files, one dir per feature area
|
||||
│ └── alerts/
|
||||
│ └── alerts.spec.ts
|
||||
└── artifacts/ # per-run output (gitignored)
|
||||
├── html/ # HTML reporter output
|
||||
├── json/ # JSON reporter output
|
||||
└── results/ # per-test traces / screenshots / videos on failure
|
||||
```
|
||||
|
||||
Each spec follows these principles:
|
||||
|
||||
1. **Directory per feature**: `tests/e2e/tests/<feature>/*.spec.ts`. Cross-resource junction concerns (e.g. cascade-delete) go in their own file, not packed into one giant spec.
|
||||
2. **Test titles use `TC-NN`**: `test('TC-01 alerts page — tabs render', ...)`. Preserves ordering at a glance and maps to external coverage tracking.
|
||||
3. **UI-first**: drive flows through the UI. Playwright traces capture every BE request/response the UI triggers, so asserting on UI outcomes implicitly validates BE contracts. Reach for direct `page.request.*` only when the test's *purpose* is asserting a response contract (use `page.waitForResponse` on a UI click) or when a specific UI step is structurally flaky (e.g. Ant DatePicker calendar-cell indices) — and even then try UI first.
|
||||
4. **Self-contained state**: each spec creates what it needs and cleans up in `try/finally`. No global pre-seeding fixtures.
|
||||
|
||||
## How to write an E2E test?
|
||||
|
||||
Create a new file `tests/e2e/tests/alerts/smoke.spec.ts`:
|
||||
|
||||
```typescript
|
||||
import { test, expect } from '../../fixtures/auth';
|
||||
|
||||
test('TC-01 alerts page — tabs render', async ({ authedPage: page }) => {
|
||||
await page.goto('/alerts');
|
||||
await expect(page.getByRole('tab', { name: /alert rules/i })).toBeVisible();
|
||||
await expect(page.getByRole('tab', { name: /configuration/i })).toBeVisible();
|
||||
});
|
||||
```
|
||||
|
||||
The `authedPage` fixture (from `tests/e2e/fixtures/auth.ts`) gives you a `Page` whose browser context is already authenticated as the admin user. First use per worker triggers one login; the resulting `storageState` is held in memory and reused for later requests.
|
||||
|
||||
To run just this test (assuming the stack is up via `test_setup`):
|
||||
|
||||
```bash
|
||||
cd tests/e2e
|
||||
npx playwright test tests/alerts/smoke.spec.ts --project=chromium
|
||||
```
|
||||
|
||||
Here's a more comprehensive example that exercises a CRUD flow via the UI:
|
||||
|
||||
```typescript
|
||||
import { test, expect } from '../../fixtures/auth';
|
||||
|
||||
test.describe.configure({ mode: 'serial' });
|
||||
|
||||
test('TC-02 alerts list — create, toggle, delete', async ({ authedPage: page }) => {
|
||||
await page.goto('/alerts?tab=AlertRules');
|
||||
const name = 'smoke-rule';
|
||||
|
||||
// Seed via UI — click "New Alert", fill form, save.
|
||||
await page.getByRole('button', { name: /new alert/i }).click();
|
||||
await page.getByTestId('alert-name-input').fill(name);
|
||||
// ... fill metric / threshold / save ...
|
||||
|
||||
// Find the row and exercise the action menu.
|
||||
const row = page.locator('tr', { hasText: name });
|
||||
await expect(row).toBeVisible();
|
||||
await row.locator('[data-testid="alert-actions"] button').first().click();
|
||||
|
||||
// waitForResponse captures the network call the UI triggers — no parallel fetch needed.
|
||||
const patchWait = page.waitForResponse(
|
||||
(r) => r.url().includes('/rules/') && r.request().method() === 'PATCH',
|
||||
);
|
||||
await page.getByRole('menuitem').filter({ hasText: /^disable$/i }).click();
|
||||
await patchWait;
|
||||
await expect(row).toContainText(/disabled/i);
|
||||
});
|
||||
```
|
||||
|
||||
### Locator priority
|
||||
|
||||
1. `getByRole('button', { name: 'Submit' })`
|
||||
2. `getByLabel('Email')`
|
||||
3. `getByPlaceholder('...')`
|
||||
4. `getByText('...')`
|
||||
5. `getByTestId('...')`
|
||||
6. `locator('.ant-select')` — last resort (Ant Design dropdowns often have no semantic alternative)
|
||||
|
||||
## How to run E2E tests?
|
||||
|
||||
### Running All Tests
|
||||
|
||||
With the stack already up, from `tests/e2e/`:
|
||||
|
||||
```bash
|
||||
yarn test # headless, all projects
|
||||
```
|
||||
|
||||
### Running Specific Projects
|
||||
|
||||
```bash
|
||||
yarn test:chromium # chromium only
|
||||
yarn test:firefox
|
||||
yarn test:webkit
|
||||
```
|
||||
|
||||
### Running Specific Tests
|
||||
|
||||
```bash
|
||||
cd tests/e2e
|
||||
|
||||
# Single feature dir
|
||||
npx playwright test tests/alerts/ --project=chromium
|
||||
|
||||
# Single file
|
||||
npx playwright test tests/alerts/alerts.spec.ts --project=chromium
|
||||
|
||||
# Single test by title grep
|
||||
npx playwright test --project=chromium -g "TC-01"
|
||||
```
|
||||
|
||||
### Iterative modes
|
||||
|
||||
```bash
|
||||
yarn test:ui # Playwright UI mode — watch + step through
|
||||
yarn test:headed # headed browser
|
||||
yarn test:debug # Playwright inspector, pause-on-breakpoint
|
||||
yarn codegen # record-and-replay locator generation
|
||||
yarn report # open the last HTML report (artifacts/html)
|
||||
```
|
||||
|
||||
### Staging fallback
|
||||
|
||||
Point `SIGNOZ_E2E_BASE_URL` at a remote env via `.env` — no local backend bring-up, no `.env.local` generated, Playwright hits the URL directly:
|
||||
|
||||
```bash
|
||||
cd tests/e2e
|
||||
cp .env.example .env # fill SIGNOZ_E2E_USERNAME / PASSWORD
|
||||
yarn test:staging
|
||||
```
|
||||
|
||||
## How to configure different options for E2E tests?
|
||||
|
||||
### Environment variables
|
||||
|
||||
| Variable | Description |
|
||||
|---|---|
|
||||
| `SIGNOZ_E2E_BASE_URL` | Base URL the browser targets. Written by `bootstrap/setup.py` for local mode; set manually for staging. |
|
||||
| `SIGNOZ_E2E_USERNAME` | Admin email. Bootstrap writes `admin@integration.test`. |
|
||||
| `SIGNOZ_E2E_PASSWORD` | Admin password. Bootstrap writes the integration-test default. |
|
||||
| `SIGNOZ_E2E_SEEDER_URL` | Seeder HTTP base URL — hit by specs that need per-test telemetry. |
|
||||
|
||||
Loading order in `playwright.config.ts`: `.env` first (user-provided, staging), then `.env.local` with `override: true` (bootstrap-generated, local mode). Anything already set in `process.env` at yarn-test time wins because dotenv doesn't touch vars that are already present.
|
||||
|
||||
### Playwright options
|
||||
|
||||
The full `playwright.config.ts` is the source of truth. Common things to tweak:
|
||||
|
||||
- `projects` — Chromium / Firefox / WebKit are enabled by default. Disable to speed up iteration.
|
||||
- `retries` — `2` on CI (`process.env.CI`), `0` locally.
|
||||
- `fullyParallel: true` — files run in parallel by worker; within a file, use `test.describe.configure({ mode: 'serial' })` if tests share list pages / mutate shared state.
|
||||
- `trace: 'on-first-retry'`, `screenshot: 'only-on-failure'`, `video: 'retain-on-failure'` — default diagnostic artifacts land in `artifacts/results/<test>/`.
|
||||
|
||||
### Pytest options (bootstrap side)
|
||||
|
||||
The same pytest flags integration tests expose work here, since E2E reuses the shared fixture graph:
|
||||
|
||||
- `--reuse` — keep containers warm between runs (required for all iteration).
|
||||
- `--teardown` — tear everything down.
|
||||
- `--with-web` — build the frontend into the SigNoz container. **Required for E2E**; integration tests don't need it.
|
||||
- `--sqlstore-provider`, `--postgres-version`, `--clickhouse-version`, etc. — see `docs/contributing/integration.md`.
|
||||
|
||||
## What should I remember?
|
||||
|
||||
- **Always use the `--reuse` flag** when setting up the E2E stack. `--with-web` adds a ~4 min frontend build; you only want to pay that once.
|
||||
- **Don't teardown before setup.** `--reuse` correctly handles partially-set-up state, so chaining teardown → setup wastes time.
|
||||
- **Prefer UI-driven flows.** Playwright captures BE requests in the trace; a parallel `fetch` probe is almost always redundant. Drop to `page.request.*` only when the UI can't reach what you need.
|
||||
- **Use `page.waitForResponse` on UI clicks** to assert BE contracts — it still exercises the UI trigger path.
|
||||
- **Title every test `TC-NN <short description>`** — keeps the suite navigable and reportable.
|
||||
- **Split by resource, not by regression suite.** One spec per feature resource; cross-resource junction concerns (cascade-delete, linked-edit) get their own file.
|
||||
- **Use short descriptive resource names** (`alerts-list-rule`, `labels-rule`, `downtime-once`) — no timestamp disambiguation. Each test owns its resources and cleans up in `try/finally`.
|
||||
- **Never commit `test.only`** — a pre-commit check or CI runs with `forbidOnly: true`.
|
||||
- **Prefer explicit waits over `page.waitForTimeout(ms)`.** `await expect(locator).toBeVisible()` is always better than `waitForTimeout(5000)`.
|
||||
- **Unique test names won't save you from shared-tenant state.** When two tests hit the same list page, either serialize (`describe.configure({ mode: 'serial' })`) or isolate cleanup religiously.
|
||||
- **Artifacts go to `tests/e2e/artifacts/`** — HTML report at `artifacts/html`, traces at `artifacts/results/<test>/`. All gitignored; archive the dir in CI.
|
||||
251
docs/contributing/tests/integration.md
Normal file
251
docs/contributing/tests/integration.md
Normal file
@@ -0,0 +1,251 @@
|
||||
# Integration Tests
|
||||
|
||||
SigNoz uses integration tests to verify that different components work together correctly in a real environment. These tests run against actual services (ClickHouse, PostgreSQL, SigNoz, Zeus mock, Keycloak, etc.) spun up as containers, so suites exercise the same code paths production does.
|
||||
|
||||
## How to set up the integration test environment?
|
||||
|
||||
### Prerequisites
|
||||
|
||||
Before running integration tests, ensure you have the following installed:
|
||||
|
||||
- Python 3.13+
|
||||
- [uv](https://docs.astral.sh/uv/getting-started/installation/)
|
||||
- Docker (for containerized services)
|
||||
|
||||
### Initial Setup
|
||||
|
||||
1. Navigate to the shared tests project:
|
||||
```bash
|
||||
cd tests
|
||||
```
|
||||
|
||||
2. Install dependencies using uv:
|
||||
```bash
|
||||
uv sync
|
||||
```
|
||||
|
||||
> **_NOTE:_** the build backend could throw an error while installing `psycopg2`, please see https://www.psycopg.org/docs/install.html#build-prerequisites
|
||||
|
||||
### Starting the Test Environment
|
||||
|
||||
To spin up all the containers necessary for writing integration tests and keep them running:
|
||||
|
||||
```bash
|
||||
make py-test-setup
|
||||
```
|
||||
|
||||
Under the hood this runs, from `tests/`:
|
||||
|
||||
```bash
|
||||
uv run pytest --basetemp=./tmp/ -vv --reuse integration/bootstrap/setup.py::test_setup
|
||||
```
|
||||
|
||||
This command will:
|
||||
- Start all required services (ClickHouse, PostgreSQL, Zookeeper, SigNoz, Zeus mock, gateway mock)
|
||||
- Register an admin user
|
||||
- Keep containers running via the `--reuse` flag
|
||||
|
||||
### Stopping the Test Environment
|
||||
|
||||
When you're done writing integration tests, clean up the environment:
|
||||
|
||||
```bash
|
||||
make py-test-teardown
|
||||
```
|
||||
|
||||
Which runs:
|
||||
|
||||
```bash
|
||||
uv run pytest --basetemp=./tmp/ -vv --teardown integration/bootstrap/setup.py::test_teardown
|
||||
```
|
||||
|
||||
This destroys the running integration test setup and cleans up resources.
|
||||
|
||||
## Understanding the Integration Test Framework
|
||||
|
||||
Python and pytest form the foundation of the integration testing framework. Testcontainers are used to spin up disposable integration environments. WireMock is used to spin up **test doubles** of external services (Zeus cloud API, gateway, etc.).
|
||||
|
||||
- **Why Python/pytest?** It's expressive, low-boilerplate, and has powerful fixture capabilities that make integration testing straightforward. Extensive libraries for HTTP requests, JSON handling, and data analysis (numpy) make it easier to test APIs and verify data.
|
||||
- **Why testcontainers?** They let us spin up isolated dependencies that match our production environment without complex setup.
|
||||
- **Why WireMock?** Well maintained, documented, and extensible.
|
||||
|
||||
```
|
||||
tests/
|
||||
├── conftest.py # pytest_plugins registration
|
||||
├── pyproject.toml
|
||||
├── uv.lock
|
||||
├── fixtures/ # shared fixture library (flat package)
|
||||
│ ├── __init__.py
|
||||
│ ├── auth.py # admin/editor/viewer users, tokens, license
|
||||
│ ├── clickhouse.py
|
||||
│ ├── http.py # WireMock helpers
|
||||
│ ├── keycloak.py # IdP container
|
||||
│ ├── postgres.py
|
||||
│ ├── signoz.py # SigNoz-backend container
|
||||
│ ├── sql.py
|
||||
│ ├── types.py
|
||||
│ └── ... # logs, metrics, traces, alerts, dashboards, ...
|
||||
├── integration/
|
||||
│ ├── bootstrap/
|
||||
│ │ └── setup.py # test_setup / test_teardown
|
||||
│ ├── testdata/ # JSON / JSONL / YAML inputs per suite
|
||||
│ └── tests/ # one directory per feature area
|
||||
│ ├── alerts/
|
||||
│ │ ├── 01_*.py # numbered suite files
|
||||
│ │ └── conftest.py # optional suite-local fixtures
|
||||
│ ├── auditquerier/
|
||||
│ ├── cloudintegrations/
|
||||
│ ├── dashboard/
|
||||
│ ├── passwordauthn/
|
||||
│ ├── querier/
|
||||
│ └── ...
|
||||
└── e2e/ # Playwright suite (see docs/contributing/e2e.md)
|
||||
```
|
||||
|
||||
Each test suite follows these principles:
|
||||
|
||||
1. **Organization**: Suites live under `tests/integration/tests/` in self-contained packages. Shared fixtures live in the top-level `tests/fixtures/` package so the e2e tree can reuse them.
|
||||
2. **Execution Order**: Files are prefixed with two-digit numbers (`01_`, `02_`, `03_`) to ensure sequential execution when tests depend on ordering.
|
||||
3. **Time Constraints**: Each suite should complete in under 10 minutes (setup takes ~4 mins).
|
||||
|
||||
### Test Suite Design
|
||||
|
||||
Test suites should target functional domains or subsystems within SigNoz. When designing a test suite, consider these principles:
|
||||
|
||||
- **Functional Cohesion**: Group tests around a specific capability or service boundary
|
||||
- **Data Flow**: Follow the path of data through related components
|
||||
- **Change Patterns**: Components frequently modified together should be tested together
|
||||
|
||||
The exact boundaries for suites are intentionally flexible, allowing contributors to define logical groupings based on their domain knowledge. Current suites cover alerts, audit querier, callback authn, cloud integrations, dashboards, ingestion keys, logs pipelines, password authn, preferences, querier, raw export data, roles, root user, service accounts, and TTL.
|
||||
|
||||
## How to write an integration test?
|
||||
|
||||
Now start writing an integration test. Create a new file `tests/integration/tests/bootstrap/01_version.py` and paste the following:
|
||||
|
||||
```python
|
||||
import requests
|
||||
|
||||
from fixtures import types
|
||||
from fixtures.logger import setup_logger
|
||||
|
||||
logger = setup_logger(__name__)
|
||||
|
||||
|
||||
def test_version(signoz: types.SigNoz) -> None:
|
||||
response = requests.get(
|
||||
signoz.self.host_configs["8080"].get("/api/v1/version"),
|
||||
timeout=2,
|
||||
)
|
||||
logger.info(response)
|
||||
```
|
||||
|
||||
We have written a simple test which calls the `version` endpoint of the SigNoz backend. **To run just this function, run the following command:**
|
||||
|
||||
```bash
|
||||
cd tests
|
||||
uv run pytest --basetemp=./tmp/ -vv --reuse \
|
||||
integration/tests/bootstrap/01_version.py::test_version
|
||||
```
|
||||
|
||||
> **Note:** The `--reuse` flag is used to reuse the environment if it is already running. Always use this flag when writing and running integration tests. Without it the environment is destroyed and recreated every run.
|
||||
|
||||
Here's another example of how to write a more comprehensive integration test:
|
||||
|
||||
```python
|
||||
from http import HTTPStatus
|
||||
import requests
|
||||
from fixtures import types
|
||||
from fixtures.logger import setup_logger
|
||||
|
||||
logger = setup_logger(__name__)
|
||||
|
||||
|
||||
def test_user_registration(signoz: types.SigNoz) -> None:
|
||||
"""Test user registration functionality."""
|
||||
response = requests.post(
|
||||
signoz.self.host_configs["8080"].get("/api/v1/register"),
|
||||
json={
|
||||
"name": "testuser",
|
||||
"orgId": "",
|
||||
"orgName": "test.org",
|
||||
"email": "test@example.com",
|
||||
"password": "password123Z$",
|
||||
},
|
||||
timeout=2,
|
||||
)
|
||||
|
||||
assert response.status_code == HTTPStatus.OK
|
||||
assert response.json()["setupCompleted"] is True
|
||||
```
|
||||
|
||||
Test inputs (JSON fixtures, expected payloads) go under `tests/integration/testdata/<suite>/` and are loaded via `fixtures.fs.get_testdata_file_path`.
|
||||
|
||||
## How to run integration tests?
|
||||
|
||||
### Running All Tests
|
||||
|
||||
```bash
|
||||
make py-test
|
||||
```
|
||||
|
||||
Which runs:
|
||||
|
||||
```bash
|
||||
uv run pytest --basetemp=./tmp/ -vv integration/tests/
|
||||
```
|
||||
|
||||
### Running Specific Test Categories
|
||||
|
||||
```bash
|
||||
cd tests
|
||||
uv run pytest --basetemp=./tmp/ -vv --reuse integration/tests/<suite>/
|
||||
|
||||
# Run querier tests
|
||||
uv run pytest --basetemp=./tmp/ -vv --reuse integration/tests/querier/
|
||||
# Run passwordauthn tests
|
||||
uv run pytest --basetemp=./tmp/ -vv --reuse integration/tests/passwordauthn/
|
||||
```
|
||||
|
||||
### Running Individual Tests
|
||||
|
||||
```bash
|
||||
uv run pytest --basetemp=./tmp/ -vv --reuse \
|
||||
integration/tests/<suite>/<file>.py::test_name
|
||||
|
||||
# Run test_register in 01_register.py in the passwordauthn suite
|
||||
uv run pytest --basetemp=./tmp/ -vv --reuse \
|
||||
integration/tests/passwordauthn/01_register.py::test_register
|
||||
```
|
||||
|
||||
## How to configure different options for integration tests?
|
||||
|
||||
Tests can be configured using pytest options:
|
||||
|
||||
- `--sqlstore-provider` — Choose the SQL store provider (default: `postgres`)
|
||||
- `--sqlite-mode` — SQLite journal mode: `delete` or `wal` (default: `delete`). Only relevant when `--sqlstore-provider=sqlite`.
|
||||
- `--postgres-version` — PostgreSQL version (default: `15`)
|
||||
- `--clickhouse-version` — ClickHouse version (default: `25.5.6`)
|
||||
- `--zookeeper-version` — Zookeeper version (default: `3.7.1`)
|
||||
- `--schema-migrator-version` — SigNoz schema migrator version (default: `v0.144.2`)
|
||||
|
||||
Example:
|
||||
|
||||
```bash
|
||||
uv run pytest --basetemp=./tmp/ -vv --reuse \
|
||||
--sqlstore-provider=postgres --postgres-version=14 \
|
||||
integration/tests/passwordauthn/
|
||||
```
|
||||
|
||||
## What should I remember?
|
||||
|
||||
- **Always use the `--reuse` flag** when setting up the environment or running tests to keep containers warm. Without it every run rebuilds the stack (~4 mins).
|
||||
- **Use the `--teardown` flag** only when cleaning up — mixing `--teardown` with `--reuse` is a contradiction.
|
||||
- **Do not pre-emptively teardown before setup.** If the stack is partially up, `--reuse` picks up from wherever it is. `make py-test-teardown` then `make py-test-setup` wastes minutes.
|
||||
- **Follow the naming convention** with two-digit numeric prefixes (`01_`, `02_`) for ordered test execution within a suite.
|
||||
- **Use proper timeouts** in HTTP requests to avoid hanging tests (`timeout=5` is typical).
|
||||
- **Clean up test data** between tests in the same suite to avoid interference — or rely on a fresh SigNoz container if you need full isolation.
|
||||
- **Use descriptive test names** that clearly indicate what is being tested.
|
||||
- **Leverage fixtures** for common setup. The shared fixture package is at `tests/fixtures/` — reuse before adding new ones.
|
||||
- **Test both success and failure scenarios** (4xx / 5xx paths) to ensure robust functionality.
|
||||
- **Run `make py-fmt` and `make py-lint` before committing** Python changes — black + isort + autoflake + pylint.
|
||||
- **`--sqlite-mode=wal` does not work on macOS.** The integration test environment runs SigNoz inside a Linux container with the SQLite database file mounted from the macOS host. WAL mode requires shared memory between connections, and connections crossing the VM boundary (macOS host ↔ Linux container) cannot share the WAL index, resulting in `SQLITE_IOERR_SHORT_READ`. WAL mode is tested in CI on Linux only.
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
@@ -37,6 +38,7 @@ func New(ctx context.Context, providerSettings factory.ProviderSettings, config
|
||||
providerSettings.MeterProvider,
|
||||
client.WithRequestResponseLog(true),
|
||||
client.WithRetryCount(3),
|
||||
client.WithTimeout(30*time.Second),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<!doctype html>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
|
||||
@@ -42,13 +42,13 @@ window.getComputedStyle = function (
|
||||
} catch {
|
||||
// Return a minimal CSSStyleDeclaration so callers (testing-library, Radix UI)
|
||||
// see the element as visible and without animations.
|
||||
return {
|
||||
return ({
|
||||
display: '',
|
||||
visibility: '',
|
||||
opacity: '1',
|
||||
animationName: 'none',
|
||||
getPropertyValue: () => '',
|
||||
} as unknown as CSSStyleDeclaration;
|
||||
} as unknown) as CSSStyleDeclaration;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"prettify": "oxfmt",
|
||||
"fmt": "oxfmt --check",
|
||||
"fmt": "echo 'Disabled due to migration' || oxfmt --check",
|
||||
"lint": "oxlint ./src && stylelint \"src/**/*.scss\"",
|
||||
"lint:js": "oxlint ./src",
|
||||
"lint:generated": "oxlint ./src/api/generated --fix",
|
||||
@@ -52,7 +52,7 @@
|
||||
"@signozhq/design-tokens": "2.1.4",
|
||||
"@signozhq/icons": "0.1.0",
|
||||
"@signozhq/resizable": "0.0.2",
|
||||
"@signozhq/ui": "0.0.9",
|
||||
"@signozhq/ui": "0.0.10",
|
||||
"@tanstack/react-table": "8.21.3",
|
||||
"@tanstack/react-virtual": "3.13.22",
|
||||
"@uiw/codemirror-theme-copilot": "4.23.11",
|
||||
@@ -241,7 +241,7 @@
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.(js|jsx|ts|tsx)": [
|
||||
"oxfmt --check",
|
||||
"echo 'Disabled due to migration' || oxfmt --check",
|
||||
"oxlint --fix",
|
||||
"sh scripts/typecheck-staged.sh"
|
||||
]
|
||||
@@ -266,4 +266,4 @@
|
||||
"tmp": "0.2.4",
|
||||
"vite": "npm:rolldown-vite@7.3.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
106
frontend/src/api/generated/services/inframonitoring/index.ts
Normal file
106
frontend/src/api/generated/services/inframonitoring/index.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
/**
|
||||
* ! Do not edit manually
|
||||
* * The file has been auto-generated using Orval for SigNoz
|
||||
* * regenerate with 'yarn generate:api'
|
||||
* SigNoz
|
||||
*/
|
||||
import { useMutation } from 'react-query';
|
||||
import type {
|
||||
MutationFunction,
|
||||
UseMutationOptions,
|
||||
UseMutationResult,
|
||||
} from 'react-query';
|
||||
|
||||
import type {
|
||||
InframonitoringtypesPostableHostsDTO,
|
||||
ListHosts200,
|
||||
RenderErrorResponseDTO,
|
||||
} from '../sigNoz.schemas';
|
||||
|
||||
import { GeneratedAPIInstance } from '../../../generatedAPIInstance';
|
||||
import type { ErrorType, BodyType } from '../../../generatedAPIInstance';
|
||||
|
||||
/**
|
||||
* Returns a paginated list of hosts with key infrastructure metrics: CPU usage (%), memory usage (%), I/O wait (%), disk usage (%), and 15-minute load average. Each host includes its current status (active/inactive based on metrics reported in the last 10 minutes) and metadata attributes (e.g., os.type). Supports filtering via a filter expression, filtering by host status, custom groupBy to aggregate hosts by any attribute, ordering by any of the five metrics, and pagination via offset/limit. The response type is 'list' for the default host.name grouping or 'grouped_list' for custom groupBy keys. Also reports missing required metrics and whether the requested time range falls before the data retention boundary.
|
||||
* @summary List Hosts for Infra Monitoring
|
||||
*/
|
||||
export const listHosts = (
|
||||
inframonitoringtypesPostableHostsDTO: BodyType<InframonitoringtypesPostableHostsDTO>,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<ListHosts200>({
|
||||
url: `/api/v2/infra_monitoring/hosts`,
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
data: inframonitoringtypesPostableHostsDTO,
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getListHostsMutationOptions = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof listHosts>>,
|
||||
TError,
|
||||
{ data: BodyType<InframonitoringtypesPostableHostsDTO> },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof listHosts>>,
|
||||
TError,
|
||||
{ data: BodyType<InframonitoringtypesPostableHostsDTO> },
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ['listHosts'];
|
||||
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 listHosts>>,
|
||||
{ data: BodyType<InframonitoringtypesPostableHostsDTO> }
|
||||
> = (props) => {
|
||||
const { data } = props ?? {};
|
||||
|
||||
return listHosts(data);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type ListHostsMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof listHosts>>
|
||||
>;
|
||||
export type ListHostsMutationBody =
|
||||
BodyType<InframonitoringtypesPostableHostsDTO>;
|
||||
export type ListHostsMutationError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary List Hosts for Infra Monitoring
|
||||
*/
|
||||
export const useListHosts = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof listHosts>>,
|
||||
TError,
|
||||
{ data: BodyType<InframonitoringtypesPostableHostsDTO> },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof listHosts>>,
|
||||
TError,
|
||||
{ data: BodyType<InframonitoringtypesPostableHostsDTO> },
|
||||
TContext
|
||||
> => {
|
||||
const mutationOptions = getListHostsMutationOptions(options);
|
||||
|
||||
return useMutation(mutationOptions);
|
||||
};
|
||||
399
frontend/src/api/generated/services/llm-pricing-rules/index.ts
Normal file
399
frontend/src/api/generated/services/llm-pricing-rules/index.ts
Normal file
@@ -0,0 +1,399 @@
|
||||
/**
|
||||
* ! 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,
|
||||
LlmpricingruletypesUpdatablePricingRulesRequestDTO,
|
||||
RenderErrorResponseDTO,
|
||||
UpdateLLMPricingRules200,
|
||||
} 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 = (
|
||||
llmpricingruletypesUpdatablePricingRulesRequestDTO: BodyType<LlmpricingruletypesUpdatablePricingRulesRequestDTO>,
|
||||
) => {
|
||||
return GeneratedAPIInstance<UpdateLLMPricingRules200>({
|
||||
url: `/api/v1/llm_pricing_rules`,
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
data: llmpricingruletypesUpdatablePricingRulesRequestDTO,
|
||||
});
|
||||
};
|
||||
|
||||
export const getUpdateLLMPricingRulesMutationOptions = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof updateLLMPricingRules>>,
|
||||
TError,
|
||||
{ data: BodyType<LlmpricingruletypesUpdatablePricingRulesRequestDTO> },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof updateLLMPricingRules>>,
|
||||
TError,
|
||||
{ data: BodyType<LlmpricingruletypesUpdatablePricingRulesRequestDTO> },
|
||||
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<LlmpricingruletypesUpdatablePricingRulesRequestDTO> }
|
||||
> = (props) => {
|
||||
const { data } = props ?? {};
|
||||
|
||||
return updateLLMPricingRules(data);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type UpdateLLMPricingRulesMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof updateLLMPricingRules>>
|
||||
>;
|
||||
export type UpdateLLMPricingRulesMutationBody =
|
||||
BodyType<LlmpricingruletypesUpdatablePricingRulesRequestDTO>;
|
||||
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<LlmpricingruletypesUpdatablePricingRulesRequestDTO> },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof updateLLMPricingRules>>,
|
||||
TError,
|
||||
{ data: BodyType<LlmpricingruletypesUpdatablePricingRulesRequestDTO> },
|
||||
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;
|
||||
};
|
||||
@@ -3053,6 +3053,313 @@ export interface GlobaltypesTokenizerConfigDTO {
|
||||
enabled?: boolean;
|
||||
}
|
||||
|
||||
export interface InframonitoringtypesHostFilterDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
expression?: string;
|
||||
filterByStatus?: InframonitoringtypesHostStatusDTO;
|
||||
}
|
||||
|
||||
/**
|
||||
* @nullable
|
||||
*/
|
||||
export type InframonitoringtypesHostRecordDTOMeta = {
|
||||
[key: string]: unknown;
|
||||
} | null;
|
||||
|
||||
export interface InframonitoringtypesHostRecordDTO {
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
activeHostCount: number;
|
||||
/**
|
||||
* @type number
|
||||
* @format double
|
||||
*/
|
||||
cpu: number;
|
||||
/**
|
||||
* @type number
|
||||
* @format double
|
||||
*/
|
||||
diskUsage: number;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
hostName: string;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
inactiveHostCount: number;
|
||||
/**
|
||||
* @type number
|
||||
* @format double
|
||||
*/
|
||||
load15: number;
|
||||
/**
|
||||
* @type number
|
||||
* @format double
|
||||
*/
|
||||
memory: number;
|
||||
/**
|
||||
* @type object
|
||||
* @nullable true
|
||||
*/
|
||||
meta: InframonitoringtypesHostRecordDTOMeta;
|
||||
status: InframonitoringtypesHostStatusDTO;
|
||||
/**
|
||||
* @type number
|
||||
* @format double
|
||||
*/
|
||||
wait: number;
|
||||
}
|
||||
|
||||
export enum InframonitoringtypesHostStatusDTO {
|
||||
active = 'active',
|
||||
inactive = 'inactive',
|
||||
'' = '',
|
||||
}
|
||||
export interface InframonitoringtypesHostsDTO {
|
||||
/**
|
||||
* @type boolean
|
||||
*/
|
||||
endTimeBeforeRetention: boolean;
|
||||
/**
|
||||
* @type array
|
||||
* @nullable true
|
||||
*/
|
||||
records: InframonitoringtypesHostRecordDTO[] | null;
|
||||
requiredMetricsCheck: InframonitoringtypesRequiredMetricsCheckDTO;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
total: number;
|
||||
type: InframonitoringtypesResponseTypeDTO;
|
||||
warning?: Querybuildertypesv5QueryWarnDataDTO;
|
||||
}
|
||||
|
||||
export interface InframonitoringtypesPostableHostsDTO {
|
||||
/**
|
||||
* @type integer
|
||||
* @format int64
|
||||
*/
|
||||
end: number;
|
||||
filter?: InframonitoringtypesHostFilterDTO;
|
||||
/**
|
||||
* @type array
|
||||
* @nullable true
|
||||
*/
|
||||
groupBy?: Querybuildertypesv5GroupByKeyDTO[] | null;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
limit: number;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
offset?: number;
|
||||
orderBy?: Querybuildertypesv5OrderByDTO;
|
||||
/**
|
||||
* @type integer
|
||||
* @format int64
|
||||
*/
|
||||
start: number;
|
||||
}
|
||||
|
||||
export interface InframonitoringtypesRequiredMetricsCheckDTO {
|
||||
/**
|
||||
* @type array
|
||||
* @nullable true
|
||||
*/
|
||||
missingMetrics: string[] | null;
|
||||
}
|
||||
|
||||
export enum InframonitoringtypesResponseTypeDTO {
|
||||
list = 'list',
|
||||
grouped_list = 'grouped_list',
|
||||
}
|
||||
export enum LlmpricingruletypesCacheModeDTO {
|
||||
subtract = 'subtract',
|
||||
additive = 'additive',
|
||||
unknown = 'unknown',
|
||||
}
|
||||
export interface LlmpricingruletypesGettablePricingRulesDTO {
|
||||
/**
|
||||
* @type array
|
||||
* @nullable true
|
||||
*/
|
||||
items: LlmpricingruletypesPricingRuleDTO[] | null;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
limit: number;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
offset: number;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
total: number;
|
||||
}
|
||||
|
||||
export interface LlmpricingruletypesPricingRuleDTO {
|
||||
cacheMode: LlmpricingruletypesCacheModeDTO;
|
||||
/**
|
||||
* @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: LlmpricingruletypesUnitDTO;
|
||||
/**
|
||||
* @type string
|
||||
* @format date-time
|
||||
*/
|
||||
updatedAt?: Date;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
updatedBy?: string;
|
||||
}
|
||||
|
||||
export enum LlmpricingruletypesUnitDTO {
|
||||
per_million_tokens = 'per_million_tokens',
|
||||
}
|
||||
export interface LlmpricingruletypesUpdatablePricingRuleDTO {
|
||||
cacheMode: LlmpricingruletypesCacheModeDTO;
|
||||
/**
|
||||
* @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: LlmpricingruletypesUnitDTO;
|
||||
}
|
||||
|
||||
export interface LlmpricingruletypesUpdatablePricingRulesRequestDTO {
|
||||
/**
|
||||
* @type array
|
||||
* @nullable true
|
||||
*/
|
||||
rules: LlmpricingruletypesUpdatablePricingRuleDTO[] | null;
|
||||
}
|
||||
|
||||
export interface LlmpricingruletypesUpdatablePricingRulesResponseDTO {
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
inserted: number;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
preserved: number;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
updated: number;
|
||||
}
|
||||
|
||||
export interface MetricsexplorertypesInspectMetricsRequestDTO {
|
||||
/**
|
||||
* @type integer
|
||||
@@ -6198,6 +6505,49 @@ 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 UpdateLLMPricingRules200 = {
|
||||
data: LlmpricingruletypesUpdatablePricingRulesResponseDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type DeleteLLMPricingRulePathParameters = {
|
||||
id: string;
|
||||
};
|
||||
export type GetLLMPricingRulePathParameters = {
|
||||
id: string;
|
||||
};
|
||||
export type GetLLMPricingRule200 = {
|
||||
data: LlmpricingruletypesPricingRuleDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type ListPromotedAndIndexedPaths200 = {
|
||||
/**
|
||||
* @type array
|
||||
@@ -6638,6 +6988,14 @@ export type Healthz503 = {
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type ListHosts200 = {
|
||||
data: InframonitoringtypesHostsDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type Livez200 = {
|
||||
data: FactoryResponseDTO;
|
||||
/**
|
||||
|
||||
@@ -39,8 +39,8 @@ jest.mock('axios', () => {
|
||||
describe('interceptorRejected', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
(axios as unknown as jest.Mock).mockResolvedValue({ data: 'success' });
|
||||
(axios.isAxiosError as unknown as jest.Mock).mockReturnValue(true);
|
||||
((axios as unknown) as jest.Mock).mockResolvedValue({ data: 'success' });
|
||||
((axios.isAxiosError as unknown) as jest.Mock).mockReturnValue(true);
|
||||
});
|
||||
|
||||
it('should preserve array payload structure when retrying a 401 request', async () => {
|
||||
@@ -49,7 +49,7 @@ describe('interceptorRejected', () => {
|
||||
{ relation: 'assignee', object: { resource: { name: 'editor' } } },
|
||||
];
|
||||
|
||||
const error = {
|
||||
const error = ({
|
||||
response: {
|
||||
status: 401,
|
||||
config: {
|
||||
@@ -67,7 +67,7 @@ describe('interceptorRejected', () => {
|
||||
headers: new AxiosHeaders(),
|
||||
data: JSON.stringify(arrayPayload),
|
||||
},
|
||||
} as unknown as AxiosResponse;
|
||||
} as unknown) as AxiosResponse;
|
||||
|
||||
try {
|
||||
await interceptorRejected(error);
|
||||
@@ -75,7 +75,7 @@ describe('interceptorRejected', () => {
|
||||
// Expected to reject after retry
|
||||
}
|
||||
|
||||
const mockAxiosFn = axios as unknown as jest.Mock;
|
||||
const mockAxiosFn = (axios as unknown) as jest.Mock;
|
||||
expect(mockAxiosFn.mock.calls.length).toBe(1);
|
||||
const retryCallConfig = mockAxiosFn.mock.calls[0][0];
|
||||
expect(Array.isArray(JSON.parse(retryCallConfig.data))).toBe(true);
|
||||
@@ -85,7 +85,7 @@ describe('interceptorRejected', () => {
|
||||
it('should preserve object payload structure when retrying a 401 request', async () => {
|
||||
const objectPayload = { key: 'value', nested: { data: 123 } };
|
||||
|
||||
const error = {
|
||||
const error = ({
|
||||
response: {
|
||||
status: 401,
|
||||
config: {
|
||||
@@ -103,7 +103,7 @@ describe('interceptorRejected', () => {
|
||||
headers: new AxiosHeaders(),
|
||||
data: JSON.stringify(objectPayload),
|
||||
},
|
||||
} as unknown as AxiosResponse;
|
||||
} as unknown) as AxiosResponse;
|
||||
|
||||
try {
|
||||
await interceptorRejected(error);
|
||||
@@ -111,14 +111,14 @@ describe('interceptorRejected', () => {
|
||||
// Expected to reject after retry
|
||||
}
|
||||
|
||||
const mockAxiosFn = axios as unknown as jest.Mock;
|
||||
const mockAxiosFn = (axios as unknown) as jest.Mock;
|
||||
expect(mockAxiosFn.mock.calls.length).toBe(1);
|
||||
const retryCallConfig = mockAxiosFn.mock.calls[0][0];
|
||||
expect(JSON.parse(retryCallConfig.data)).toEqual(objectPayload);
|
||||
});
|
||||
|
||||
it('should handle undefined data gracefully when retrying', async () => {
|
||||
const error = {
|
||||
const error = ({
|
||||
response: {
|
||||
status: 401,
|
||||
config: {
|
||||
@@ -136,7 +136,7 @@ describe('interceptorRejected', () => {
|
||||
headers: new AxiosHeaders(),
|
||||
data: undefined,
|
||||
},
|
||||
} as unknown as AxiosResponse;
|
||||
} as unknown) as AxiosResponse;
|
||||
|
||||
try {
|
||||
await interceptorRejected(error);
|
||||
@@ -144,7 +144,7 @@ describe('interceptorRejected', () => {
|
||||
// Expected to reject after retry
|
||||
}
|
||||
|
||||
const mockAxiosFn = axios as unknown as jest.Mock;
|
||||
const mockAxiosFn = (axios as unknown) as jest.Mock;
|
||||
expect(mockAxiosFn.mock.calls.length).toBe(1);
|
||||
const retryCallConfig = mockAxiosFn.mock.calls[0][0];
|
||||
expect(retryCallConfig.data).toBeUndefined();
|
||||
|
||||
@@ -9,8 +9,9 @@ const getRetentionV2 = async (): Promise<
|
||||
SuccessResponseV2<PayloadProps<'logs'>>
|
||||
> => {
|
||||
try {
|
||||
const response =
|
||||
await ApiV2Instance.get<PayloadProps<'logs'>>(`/settings/ttl`);
|
||||
const response = await ApiV2Instance.get<PayloadProps<'logs'>>(
|
||||
`/settings/ttl`,
|
||||
);
|
||||
|
||||
return {
|
||||
httpStatusCode: response.status,
|
||||
|
||||
@@ -6,15 +6,20 @@ import { PayloadProps, Props } from 'types/api/thirdPartyApis/listOverview';
|
||||
|
||||
const listOverview = async (
|
||||
props: Props,
|
||||
signal?: AbortSignal,
|
||||
): Promise<SuccessResponseV2<PayloadProps>> => {
|
||||
const { start, end, show_ip: showIp, filter } = props;
|
||||
try {
|
||||
const response = await axios.post(`/third-party-apis/overview/list`, {
|
||||
start,
|
||||
end,
|
||||
show_ip: showIp,
|
||||
filter,
|
||||
});
|
||||
const response = await axios.post(
|
||||
`/third-party-apis/overview/list`,
|
||||
{
|
||||
start,
|
||||
end,
|
||||
show_ip: showIp,
|
||||
filter,
|
||||
},
|
||||
{ signal },
|
||||
);
|
||||
|
||||
return {
|
||||
httpStatusCode: response.status,
|
||||
|
||||
@@ -52,18 +52,18 @@ describe('convertV5ResponseToLegacy', () => {
|
||||
alias: '__result_0',
|
||||
meta: {},
|
||||
series: [
|
||||
{
|
||||
({
|
||||
labels: [
|
||||
{
|
||||
key: { name: 'service.name' } as unknown as TelemetryFieldKey,
|
||||
key: ({ name: 'service.name' } as unknown) as TelemetryFieldKey,
|
||||
value: 'adservice',
|
||||
},
|
||||
],
|
||||
values: [
|
||||
{ timestamp: 1000, value: 10 } as unknown as TimeSeriesValue,
|
||||
{ timestamp: 2000, value: 12 } as unknown as TimeSeriesValue,
|
||||
({ timestamp: 1000, value: 10 } as unknown) as TimeSeriesValue,
|
||||
({ timestamp: 2000, value: 12 } as unknown) as TimeSeriesValue,
|
||||
],
|
||||
} as unknown as TimeSeries,
|
||||
} as unknown) as TimeSeries,
|
||||
],
|
||||
},
|
||||
],
|
||||
@@ -88,8 +88,10 @@ describe('convertV5ResponseToLegacy', () => {
|
||||
},
|
||||
]);
|
||||
|
||||
const input: SuccessResponse<MetricRangePayloadV5, QueryRangeRequestV5> =
|
||||
makeBaseSuccess({ data: v5Data }, params);
|
||||
const input: SuccessResponse<
|
||||
MetricRangePayloadV5,
|
||||
QueryRangeRequestV5
|
||||
> = makeBaseSuccess({ data: v5Data }, params);
|
||||
|
||||
const legendMap = { A: '{{service.name}}' };
|
||||
const result = convertV5ResponseToLegacy(input, legendMap, false);
|
||||
@@ -119,33 +121,33 @@ describe('convertV5ResponseToLegacy', () => {
|
||||
const scalar: ScalarData = {
|
||||
columns: [
|
||||
// group column
|
||||
{
|
||||
({
|
||||
name: 'service.name',
|
||||
queryName: 'A',
|
||||
aggregationIndex: 0,
|
||||
columnType: 'group',
|
||||
} as unknown as ScalarData['columns'][number],
|
||||
} as unknown) as ScalarData['columns'][number],
|
||||
// aggregation 0
|
||||
{
|
||||
({
|
||||
name: '__result_0',
|
||||
queryName: 'A',
|
||||
aggregationIndex: 0,
|
||||
columnType: 'aggregation',
|
||||
} as unknown as ScalarData['columns'][number],
|
||||
} as unknown) as ScalarData['columns'][number],
|
||||
// aggregation 1
|
||||
{
|
||||
({
|
||||
name: '__result_1',
|
||||
queryName: 'A',
|
||||
aggregationIndex: 1,
|
||||
columnType: 'aggregation',
|
||||
} as unknown as ScalarData['columns'][number],
|
||||
} as unknown) as ScalarData['columns'][number],
|
||||
// formula F1
|
||||
{
|
||||
({
|
||||
name: '__result',
|
||||
queryName: 'F1',
|
||||
aggregationIndex: 0,
|
||||
columnType: 'aggregation',
|
||||
} as unknown as ScalarData['columns'][number],
|
||||
} as unknown) as ScalarData['columns'][number],
|
||||
],
|
||||
data: [['adservice', 606, 1.452, 151.5]],
|
||||
};
|
||||
@@ -172,15 +174,17 @@ describe('convertV5ResponseToLegacy', () => {
|
||||
},
|
||||
{
|
||||
type: 'builder_formula',
|
||||
spec: {
|
||||
spec: ({
|
||||
name: 'F1',
|
||||
expression: 'A * 0.25',
|
||||
} as unknown as QueryBuilderFormula,
|
||||
} as unknown) as QueryBuilderFormula,
|
||||
},
|
||||
]);
|
||||
|
||||
const input: SuccessResponse<MetricRangePayloadV5, QueryRangeRequestV5> =
|
||||
makeBaseSuccess({ data: v5Data }, params);
|
||||
const input: SuccessResponse<
|
||||
MetricRangePayloadV5,
|
||||
QueryRangeRequestV5
|
||||
> = makeBaseSuccess({ data: v5Data }, params);
|
||||
const legendMap = { A: '{{service.name}}', F1: '' };
|
||||
const result = convertV5ResponseToLegacy(input, legendMap, false);
|
||||
|
||||
@@ -250,8 +254,10 @@ describe('convertV5ResponseToLegacy', () => {
|
||||
},
|
||||
]);
|
||||
|
||||
const input: SuccessResponse<MetricRangePayloadV5, QueryRangeRequestV5> =
|
||||
makeBaseSuccess({ data: v5Data }, params);
|
||||
const input: SuccessResponse<
|
||||
MetricRangePayloadV5,
|
||||
QueryRangeRequestV5
|
||||
> = makeBaseSuccess({ data: v5Data }, params);
|
||||
const legendMap = { A: '{{service.name}}' };
|
||||
const result = convertV5ResponseToLegacy(input, legendMap, true);
|
||||
|
||||
|
||||
@@ -93,7 +93,7 @@ function convertTimeSeriesData(
|
||||
labels: series.labels
|
||||
? Object.fromEntries(
|
||||
series.labels.map((label: any) => [label.key.name, label.value]),
|
||||
)
|
||||
)
|
||||
: {},
|
||||
labelsArray: series.labels
|
||||
? series.labels.map((label: any) => ({ [label.key.name]: label.value }))
|
||||
@@ -358,19 +358,16 @@ export function convertV5ResponseToLegacy(
|
||||
const aggregationPerQuery =
|
||||
(params as QueryRangeRequestV5)?.compositeQuery?.queries
|
||||
?.filter((query) => query.type === 'builder_query')
|
||||
.reduce(
|
||||
(acc, query) => {
|
||||
if (
|
||||
query.type === 'builder_query' &&
|
||||
'aggregations' in query.spec &&
|
||||
query.spec.name
|
||||
) {
|
||||
acc[query.spec.name] = query.spec.aggregations;
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, any>,
|
||||
) || {};
|
||||
.reduce((acc, query) => {
|
||||
if (
|
||||
query.type === 'builder_query' &&
|
||||
'aggregations' in query.spec &&
|
||||
query.spec.name
|
||||
) {
|
||||
acc[query.spec.name] = query.spec.aggregations;
|
||||
}
|
||||
return acc;
|
||||
}, {} as Record<string, any>) || {};
|
||||
|
||||
// If formatForWeb is true, return as-is (like existing logic)
|
||||
if (formatForWeb && v5Data?.type === 'scalar') {
|
||||
|
||||
@@ -713,7 +713,7 @@ describe('prepareQueryRangePayloadV5', () => {
|
||||
baseBuilderQuery({
|
||||
dataSource: DataSource.LOGS,
|
||||
filter: { expression: 'http.status_code >= 500' },
|
||||
filters: undefined as unknown as IBuilderQuery['filters'],
|
||||
filters: (undefined as unknown) as IBuilderQuery['filters'],
|
||||
}),
|
||||
],
|
||||
queryFormulas: [],
|
||||
@@ -746,7 +746,7 @@ describe('prepareQueryRangePayloadV5', () => {
|
||||
queryData: [
|
||||
baseBuilderQuery({
|
||||
dataSource: DataSource.LOGS,
|
||||
filter: undefined as unknown as IBuilderQuery['filter'],
|
||||
filter: (undefined as unknown) as IBuilderQuery['filter'],
|
||||
filters: {
|
||||
items: [
|
||||
{
|
||||
@@ -834,8 +834,8 @@ describe('prepareQueryRangePayloadV5', () => {
|
||||
queryData: [
|
||||
baseBuilderQuery({
|
||||
dataSource: DataSource.LOGS,
|
||||
filter: undefined as unknown as IBuilderQuery['filter'],
|
||||
filters: undefined as unknown as IBuilderQuery['filters'],
|
||||
filter: (undefined as unknown) as IBuilderQuery['filter'],
|
||||
filters: (undefined as unknown) as IBuilderQuery['filters'],
|
||||
}),
|
||||
],
|
||||
queryFormulas: [],
|
||||
|
||||
@@ -139,9 +139,10 @@ function createBaseSpec(
|
||||
requestType: RequestType,
|
||||
panelType?: PANEL_TYPES,
|
||||
): BaseBuilderQuery {
|
||||
const nonEmptySelectColumns = (
|
||||
queryData.selectColumns as (BaseAutocompleteData | TelemetryFieldKey)[]
|
||||
)?.filter((c) => ('key' in c ? c?.key : c?.name));
|
||||
const nonEmptySelectColumns = (queryData.selectColumns as (
|
||||
| BaseAutocompleteData
|
||||
| TelemetryFieldKey
|
||||
)[])?.filter((c) => ('key' in c ? c?.key : c?.name));
|
||||
|
||||
return {
|
||||
stepInterval: queryData?.stepInterval || null,
|
||||
@@ -159,7 +160,7 @@ function createBaseSpec(
|
||||
signal: item?.signal,
|
||||
materialized: item?.materialized,
|
||||
}),
|
||||
)
|
||||
)
|
||||
: undefined,
|
||||
limit:
|
||||
panelType === PANEL_TYPES.TABLE || panelType === PANEL_TYPES.LIST
|
||||
@@ -178,48 +179,52 @@ function createBaseSpec(
|
||||
},
|
||||
direction: order.order,
|
||||
}),
|
||||
)
|
||||
)
|
||||
: undefined,
|
||||
legend: isEmpty(queryData.legend) ? undefined : queryData.legend,
|
||||
having: isEmpty(queryData.having) ? undefined : (queryData?.having as Having),
|
||||
functions: isEmpty(queryData.functions)
|
||||
? undefined
|
||||
: queryData.functions.map((func: QueryFunction): QueryFunction => {
|
||||
// Normalize function name to handle case sensitivity
|
||||
const normalizedName = normalizeFunctionName(func?.name);
|
||||
return {
|
||||
name: normalizedName as FunctionName,
|
||||
args: isEmpty(func.namedArgs)
|
||||
? func.args?.map((arg) => ({
|
||||
value: arg?.value,
|
||||
}))
|
||||
: Object.entries(func?.namedArgs || {}).map(([name, value]) => ({
|
||||
name,
|
||||
value,
|
||||
})),
|
||||
};
|
||||
}),
|
||||
: queryData.functions.map(
|
||||
(func: QueryFunction): QueryFunction => {
|
||||
// Normalize function name to handle case sensitivity
|
||||
const normalizedName = normalizeFunctionName(func?.name);
|
||||
return {
|
||||
name: normalizedName as FunctionName,
|
||||
args: isEmpty(func.namedArgs)
|
||||
? func.args?.map((arg) => ({
|
||||
value: arg?.value,
|
||||
}))
|
||||
: Object.entries(func?.namedArgs || {}).map(([name, value]) => ({
|
||||
name,
|
||||
value,
|
||||
})),
|
||||
};
|
||||
},
|
||||
),
|
||||
selectFields: isEmpty(nonEmptySelectColumns)
|
||||
? undefined
|
||||
: nonEmptySelectColumns?.map((column: any): TelemetryFieldKey => {
|
||||
const fieldName = column.name ?? column.key;
|
||||
const isDeprecated = isDeprecatedField(fieldName);
|
||||
: nonEmptySelectColumns?.map(
|
||||
(column: any): TelemetryFieldKey => {
|
||||
const fieldName = column.name ?? column.key;
|
||||
const isDeprecated = isDeprecatedField(fieldName);
|
||||
|
||||
const fieldObj: TelemetryFieldKey = {
|
||||
name: fieldName,
|
||||
fieldDataType:
|
||||
column?.fieldDataType ?? (column?.dataType as FieldDataType),
|
||||
signal: column?.signal ?? undefined,
|
||||
};
|
||||
const fieldObj: TelemetryFieldKey = {
|
||||
name: fieldName,
|
||||
fieldDataType:
|
||||
column?.fieldDataType ?? (column?.dataType as FieldDataType),
|
||||
signal: column?.signal ?? undefined,
|
||||
};
|
||||
|
||||
// Only add fieldContext if the field is NOT deprecated
|
||||
if (!isDeprecated && fieldName !== 'name') {
|
||||
fieldObj.fieldContext =
|
||||
column?.fieldContext ?? (column?.type as FieldContext);
|
||||
}
|
||||
// Only add fieldContext if the field is NOT deprecated
|
||||
if (!isDeprecated && fieldName !== 'name') {
|
||||
fieldObj.fieldContext =
|
||||
column?.fieldContext ?? (column?.type as FieldContext);
|
||||
}
|
||||
|
||||
return fieldObj;
|
||||
}),
|
||||
return fieldObj;
|
||||
},
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -231,8 +236,7 @@ export function parseAggregations(
|
||||
const result: { expression: string; alias?: string }[] = [];
|
||||
// Matches function calls like "count()" or "sum(field)" with optional alias like "as 'alias'"
|
||||
// Handles quoted ('alias'), dash-separated (field-name), and unquoted values after "as" keyword
|
||||
const regex =
|
||||
/([a-zA-Z0-9_]+\([^)]*\))(?:\s*as\s+((?:'[^']*'|"[^"]*"|[a-zA-Z0-9_-]+)))?/g;
|
||||
const regex = /([a-zA-Z0-9_]+\([^)]*\))(?:\s*as\s+((?:'[^']*'|"[^"]*"|[a-zA-Z0-9_-]+)))?/g;
|
||||
let match = regex.exec(expression);
|
||||
while (match !== null) {
|
||||
const expr = match[1];
|
||||
@@ -361,9 +365,10 @@ function createTraceOperatorBaseSpec(
|
||||
requestType: RequestType,
|
||||
panelType?: PANEL_TYPES,
|
||||
): BaseBuilderQuery {
|
||||
const nonEmptySelectColumns = (
|
||||
queryData.selectColumns as (BaseAutocompleteData | TelemetryFieldKey)[]
|
||||
)?.filter((c) => ('key' in c ? c?.key : c?.name));
|
||||
const nonEmptySelectColumns = (queryData.selectColumns as (
|
||||
| BaseAutocompleteData
|
||||
| TelemetryFieldKey
|
||||
)[])?.filter((c) => ('key' in c ? c?.key : c?.name));
|
||||
|
||||
const {
|
||||
stepInterval,
|
||||
@@ -390,7 +395,7 @@ function createTraceOperatorBaseSpec(
|
||||
signal: item?.signal,
|
||||
materialized: item?.materialized,
|
||||
}),
|
||||
)
|
||||
)
|
||||
: undefined,
|
||||
limit:
|
||||
panelType === PANEL_TYPES.TABLE || panelType === PANEL_TYPES.LIST
|
||||
@@ -406,7 +411,7 @@ function createTraceOperatorBaseSpec(
|
||||
},
|
||||
direction: order.order,
|
||||
}),
|
||||
)
|
||||
)
|
||||
: undefined,
|
||||
legend: isEmpty(legend) ? undefined : legend,
|
||||
having: isEmpty(having) ? undefined : (having as Having),
|
||||
@@ -420,7 +425,7 @@ function createTraceOperatorBaseSpec(
|
||||
fieldContext: column?.fieldContext ?? (column?.type as FieldContext),
|
||||
signal: column?.signal ?? undefined,
|
||||
}),
|
||||
),
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -502,22 +507,18 @@ export function convertClickHouseQueriesToV5(
|
||||
/**
|
||||
* Helper function to reduce query arrays to objects
|
||||
*/
|
||||
function reduceQueriesToObject(queryArray: any[]): {
|
||||
queries: Record<string, any>;
|
||||
legends: Record<string, string>;
|
||||
} {
|
||||
function reduceQueriesToObject(
|
||||
queryArray: any[],
|
||||
): { queries: Record<string, any>; legends: Record<string, string> } {
|
||||
const legends: Record<string, string> = {};
|
||||
const queries = queryArray.reduce(
|
||||
(acc, queryItem) => {
|
||||
if (!queryItem.query) {
|
||||
return acc;
|
||||
}
|
||||
acc[queryItem.name] = queryItem;
|
||||
legends[queryItem.name] = queryItem.legend;
|
||||
const queries = queryArray.reduce((acc, queryItem) => {
|
||||
if (!queryItem.query) {
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, any>,
|
||||
);
|
||||
}
|
||||
acc[queryItem.name] = queryItem;
|
||||
legends[queryItem.name] = queryItem.legend;
|
||||
return acc;
|
||||
}, {} as Record<string, any>);
|
||||
|
||||
return { queries, legends };
|
||||
}
|
||||
@@ -553,7 +554,7 @@ export const prepareQueryRangePayloadV5 = ({
|
||||
queryTraceOperator && queryTraceOperator.length > 0
|
||||
? queryTraceOperator.filter((traceOperator) =>
|
||||
Boolean(traceOperator.expression.trim()),
|
||||
)
|
||||
)
|
||||
: [];
|
||||
|
||||
const currentTraceOperator = mapQueryDataToApi(
|
||||
@@ -647,18 +648,15 @@ export const prepareQueryRangePayloadV5 = ({
|
||||
: graphType === PANEL_TYPES.TABLE),
|
||||
fillGaps: fillGaps || false,
|
||||
},
|
||||
variables: Object.entries(variables).reduce(
|
||||
(acc, [key, value]) => {
|
||||
acc[key] = {
|
||||
value,
|
||||
type: dynamicVariables
|
||||
?.find((v) => v.name === key)
|
||||
?.type?.toLowerCase() as VariableType,
|
||||
};
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, VariableItem>,
|
||||
),
|
||||
variables: Object.entries(variables).reduce((acc, [key, value]) => {
|
||||
acc[key] = {
|
||||
value,
|
||||
type: dynamicVariables
|
||||
?.find((v) => v.name === key)
|
||||
?.type?.toLowerCase() as VariableType,
|
||||
};
|
||||
return acc;
|
||||
}, {} as Record<string, VariableItem>),
|
||||
};
|
||||
|
||||
return { legendMap, queryPayload };
|
||||
|
||||
@@ -9,7 +9,7 @@ jest.mock('../../../api/browser/localstorage/get', () => ({
|
||||
}));
|
||||
|
||||
// Access the mocked function
|
||||
const mockGet = getLocal as unknown as jest.Mock;
|
||||
const mockGet = (getLocal as unknown) as jest.Mock;
|
||||
|
||||
describe('AppLoading', () => {
|
||||
const SIGNOZ_TEXT = 'SigNoz';
|
||||
|
||||
@@ -31,8 +31,9 @@ export function FilterSelect({
|
||||
onChange,
|
||||
isMultiple,
|
||||
}: SelectOptionConfig): JSX.Element {
|
||||
const { handleSearch, isFetching, options } =
|
||||
useCeleryFilterOptions(filterType);
|
||||
const { handleSearch, isFetching, options } = useCeleryFilterOptions(
|
||||
filterType,
|
||||
);
|
||||
|
||||
const urlQuery = useUrlQuery();
|
||||
const history = useHistory();
|
||||
|
||||
@@ -280,28 +280,30 @@ function getTableData(data: QueueOverviewResponse['data']): RowData[] {
|
||||
];
|
||||
|
||||
const tableData: RowData[] =
|
||||
data?.map((row, index: number): RowData => {
|
||||
const rowData: Record<string, string | number> = {};
|
||||
columnOrder.forEach((key) => {
|
||||
const value = row.data[key as keyof typeof row.data];
|
||||
if (typeof value === 'string' || typeof value === 'number') {
|
||||
rowData[key] = value;
|
||||
}
|
||||
});
|
||||
Object.entries(row.data).forEach(([key, value]) => {
|
||||
if (
|
||||
!columnOrder.includes(key) &&
|
||||
(typeof value === 'string' || typeof value === 'number')
|
||||
) {
|
||||
rowData[key] = value;
|
||||
}
|
||||
});
|
||||
data?.map(
|
||||
(row, index: number): RowData => {
|
||||
const rowData: Record<string, string | number> = {};
|
||||
columnOrder.forEach((key) => {
|
||||
const value = row.data[key as keyof typeof row.data];
|
||||
if (typeof value === 'string' || typeof value === 'number') {
|
||||
rowData[key] = value;
|
||||
}
|
||||
});
|
||||
Object.entries(row.data).forEach(([key, value]) => {
|
||||
if (
|
||||
!columnOrder.includes(key) &&
|
||||
(typeof value === 'string' || typeof value === 'number')
|
||||
) {
|
||||
rowData[key] = value;
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
...rowData,
|
||||
key: index,
|
||||
};
|
||||
}) || [];
|
||||
return {
|
||||
...rowData,
|
||||
key: index,
|
||||
};
|
||||
},
|
||||
) || [];
|
||||
|
||||
return tableData;
|
||||
}
|
||||
@@ -478,10 +480,10 @@ export default function CeleryOverviewTable({
|
||||
[searchText],
|
||||
);
|
||||
|
||||
const filteredData = useMemo(
|
||||
() => getFilteredData(tableData),
|
||||
[getFilteredData, tableData],
|
||||
);
|
||||
const filteredData = useMemo(() => getFilteredData(tableData), [
|
||||
getFilteredData,
|
||||
tableData,
|
||||
]);
|
||||
|
||||
const prevTableDataRef = useRef<string>();
|
||||
|
||||
|
||||
@@ -13,8 +13,9 @@ import { useCeleryFilterOptions } from '../useCeleryFilterOptions';
|
||||
import './CeleryTaskConfigOptions.styles.scss';
|
||||
|
||||
function CeleryTaskConfigOptions(): JSX.Element {
|
||||
const { handleSearch, isFetching, options } =
|
||||
useCeleryFilterOptions('celery.task_name');
|
||||
const { handleSearch, isFetching, options } = useCeleryFilterOptions(
|
||||
'celery.task_name',
|
||||
);
|
||||
const history = useHistory();
|
||||
const location = useLocation();
|
||||
|
||||
|
||||
@@ -469,7 +469,8 @@ export const celeryActiveTasksWidgetData = (
|
||||
{
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.Float64,
|
||||
id: 'flower_worker_number_of_currently_executing_tasks--float64--Gauge--true',
|
||||
id:
|
||||
'flower_worker_number_of_currently_executing_tasks--float64--Gauge--true',
|
||||
key: 'flower_worker_number_of_currently_executing_tasks',
|
||||
type: 'Gauge',
|
||||
},
|
||||
|
||||
@@ -127,17 +127,22 @@ function CeleryTaskLatencyGraph({
|
||||
const onGraphClickHandler = useGraphClickHandler(handleSetTimeStamp);
|
||||
|
||||
const onGraphClick = useCallback(
|
||||
(type: string): OnClickPluginOpts['onClick'] =>
|
||||
(xValue, yValue, mouseX, mouseY, data): Promise<void> => {
|
||||
const [firstDataPoint] = Object.entries(data || {});
|
||||
const [entity, value] = firstDataPoint;
|
||||
setEntityData({
|
||||
entity,
|
||||
value,
|
||||
});
|
||||
(type: string): OnClickPluginOpts['onClick'] => (
|
||||
xValue,
|
||||
yValue,
|
||||
mouseX,
|
||||
mouseY,
|
||||
data,
|
||||
): Promise<void> => {
|
||||
const [firstDataPoint] = Object.entries(data || {});
|
||||
const [entity, value] = firstDataPoint;
|
||||
setEntityData({
|
||||
entity,
|
||||
value,
|
||||
});
|
||||
|
||||
return onGraphClickHandler(xValue, yValue, mouseX, mouseY, type);
|
||||
},
|
||||
return onGraphClickHandler(xValue, yValue, mouseX, mouseY, type);
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[handleSetTimeStamp],
|
||||
);
|
||||
|
||||
@@ -92,10 +92,10 @@ function CeleryTaskStateGraphConfig({
|
||||
{isLoading
|
||||
? '-'
|
||||
: isError
|
||||
? '-'
|
||||
: Number.isNaN(values[index])
|
||||
? '-'
|
||||
: Math.round(Number(values[index]))}
|
||||
? '-'
|
||||
: Number.isNaN(values[index])
|
||||
? '-'
|
||||
: Math.round(Number(values[index]))}
|
||||
</div>
|
||||
</div>
|
||||
{tab.key === barState && <div className="celery-task-states__indicator" />}
|
||||
|
||||
@@ -65,7 +65,7 @@ export function applyCeleryFilterOnWidgetData(
|
||||
items: [...(queryItem.filters?.items || []), ...filters],
|
||||
op: queryItem.filters?.op || 'AND',
|
||||
},
|
||||
}
|
||||
}
|
||||
: queryItem,
|
||||
),
|
||||
},
|
||||
|
||||
@@ -9,7 +9,7 @@ import { paths } from './CeleryUtils';
|
||||
|
||||
interface UseGetGraphCustomSeriesProps {
|
||||
isDarkMode: boolean;
|
||||
drawStyle?: (typeof drawStyles)[keyof typeof drawStyles];
|
||||
drawStyle?: typeof drawStyles[keyof typeof drawStyles];
|
||||
colorMapping?: Record<string, string>;
|
||||
}
|
||||
|
||||
|
||||
@@ -37,8 +37,9 @@ function ChangelogModal({ changelog, onClose }: Props): JSX.Element {
|
||||
preference.name === USER_PREFERENCES.LAST_SEEN_CHANGELOG_VERSION,
|
||||
)?.value as string;
|
||||
|
||||
const { mutate: updateUserPreferenceMutation } =
|
||||
useMutation(updateUserPreference);
|
||||
const { mutate: updateUserPreferenceMutation } = useMutation(
|
||||
updateUserPreference,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
// Update the seen version
|
||||
@@ -59,8 +60,11 @@ function ChangelogModal({ changelog, onClose }: Props): JSX.Element {
|
||||
|
||||
const checkScroll = useCallback((): void => {
|
||||
if (changelogContentSectionRef.current) {
|
||||
const { scrollHeight, clientHeight, scrollTop } =
|
||||
changelogContentSectionRef.current;
|
||||
const {
|
||||
scrollHeight,
|
||||
clientHeight,
|
||||
scrollTop,
|
||||
} = changelogContentSectionRef.current;
|
||||
const isAtBottom = scrollHeight - clientHeight - scrollTop <= 8;
|
||||
setHasScroll(scrollHeight > clientHeight + 24 && !isAtBottom); // 24px - buffer height to show show more
|
||||
}
|
||||
|
||||
@@ -13,8 +13,9 @@ import APIError from 'types/api/error';
|
||||
export default function ChatSupportGateway(): JSX.Element {
|
||||
const { notifications } = useNotifications();
|
||||
|
||||
const [isAddCreditCardModalOpen, setIsAddCreditCardModalOpen] =
|
||||
useState(false);
|
||||
const [isAddCreditCardModalOpen, setIsAddCreditCardModalOpen] = useState(
|
||||
false,
|
||||
);
|
||||
|
||||
const handleBillingOnSuccess = (
|
||||
data: SuccessResponseV2<CheckoutSuccessPayloadProps>,
|
||||
|
||||
@@ -388,7 +388,7 @@ function ClientSideQBSearch(
|
||||
({
|
||||
label: key.key,
|
||||
value: key,
|
||||
}) as Option,
|
||||
} as Option),
|
||||
) || [],
|
||||
);
|
||||
}
|
||||
@@ -462,7 +462,7 @@ function ClientSideQBSearch(
|
||||
({
|
||||
label: checkCommaInValue(String(val)),
|
||||
value: val,
|
||||
}) as Option,
|
||||
} as Option),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
@@ -490,7 +490,7 @@ function ClientSideQBSearch(
|
||||
Array.isArray(tag.value) &&
|
||||
tag.value[tag.value.length - 1] === ''
|
||||
? tag.value?.slice(0, -1)
|
||||
: (tag.value ?? '');
|
||||
: tag.value ?? '';
|
||||
filterTags.items.push({
|
||||
id: tag.id || uuid().slice(0, 8),
|
||||
key: tag.key,
|
||||
|
||||
@@ -47,23 +47,25 @@ function CreateServiceAccountModal(): JSX.Element {
|
||||
},
|
||||
});
|
||||
|
||||
const { mutate: createServiceAccount, isLoading: isSubmitting } =
|
||||
useCreateServiceAccount({
|
||||
mutation: {
|
||||
onSuccess: async () => {
|
||||
toast.success('Service account created successfully');
|
||||
reset();
|
||||
await setIsOpen(null);
|
||||
await invalidateListServiceAccounts(queryClient);
|
||||
},
|
||||
onError: (err) => {
|
||||
const errMessage = convertToApiError(
|
||||
err as AxiosError<RenderErrorResponseDTO, unknown> | null,
|
||||
);
|
||||
showErrorModal(errMessage as APIError);
|
||||
},
|
||||
const {
|
||||
mutate: createServiceAccount,
|
||||
isLoading: isSubmitting,
|
||||
} = useCreateServiceAccount({
|
||||
mutation: {
|
||||
onSuccess: async () => {
|
||||
toast.success('Service account created successfully');
|
||||
reset();
|
||||
await setIsOpen(null);
|
||||
await invalidateListServiceAccounts(queryClient);
|
||||
},
|
||||
});
|
||||
onError: (err) => {
|
||||
const errMessage = convertToApiError(
|
||||
err as AxiosError<RenderErrorResponseDTO, unknown> | null,
|
||||
);
|
||||
showErrorModal(errMessage as APIError);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
function handleClose(): void {
|
||||
reset();
|
||||
|
||||
@@ -96,8 +96,10 @@ function CustomTimePicker({
|
||||
maxTime,
|
||||
isModalTimeSelection = false,
|
||||
}: CustomTimePickerProps): JSX.Element {
|
||||
const [selectedTimePlaceholderValue, setSelectedTimePlaceholderValue] =
|
||||
useState('Select / Enter Time Range');
|
||||
const [
|
||||
selectedTimePlaceholderValue,
|
||||
setSelectedTimePlaceholderValue,
|
||||
] = useState('Select / Enter Time Range');
|
||||
|
||||
const [inputValue, setInputValue] = useState('');
|
||||
const [inputStatus, setInputStatus] = useState<CustomTimePickerInputStatus>(
|
||||
|
||||
@@ -116,10 +116,9 @@ function CustomTimePickerPopoverContent({
|
||||
}: CustomTimePickerPopoverContentProps): JSX.Element {
|
||||
const { pathname } = useLocation();
|
||||
|
||||
const isLogsExplorerPage = useMemo(
|
||||
() => pathname === ROUTES.LOGS_EXPLORER,
|
||||
[pathname],
|
||||
);
|
||||
const isLogsExplorerPage = useMemo(() => pathname === ROUTES.LOGS_EXPLORER, [
|
||||
pathname,
|
||||
]);
|
||||
|
||||
const url = new URLSearchParams(window.location.search);
|
||||
|
||||
@@ -155,8 +154,8 @@ function CustomTimePickerPopoverContent({
|
||||
if (!customDateTimeVisible) {
|
||||
const customTimeRanges = getCustomTimeRanges();
|
||||
|
||||
const formattedCustomTimeRanges: RecentlyUsedDateTimeRange[] =
|
||||
customTimeRanges.map((range) => ({
|
||||
const formattedCustomTimeRanges: RecentlyUsedDateTimeRange[] = customTimeRanges.map(
|
||||
(range) => ({
|
||||
label: `${dayjs(range.from)
|
||||
.tz(timezone.value)
|
||||
.format(DATE_TIME_FORMATS.DD_MMM_YYYY_HH_MM_SS)} - ${dayjs(range.to)
|
||||
@@ -166,7 +165,8 @@ function CustomTimePickerPopoverContent({
|
||||
to: range.to,
|
||||
value: range.timestamp,
|
||||
timestamp: range.timestamp,
|
||||
}));
|
||||
}),
|
||||
);
|
||||
|
||||
setRecentlyUsedTimeRanges(formattedCustomTimeRanges);
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ const TIMEZONE_TYPES = {
|
||||
STANDARD: 'STANDARD',
|
||||
} as const;
|
||||
|
||||
type TimezoneType = (typeof TIMEZONE_TYPES)[keyof typeof TIMEZONE_TYPES];
|
||||
type TimezoneType = typeof TIMEZONE_TYPES[keyof typeof TIMEZONE_TYPES];
|
||||
|
||||
export const UTC_TIMEZONE: Timezone = {
|
||||
name: 'Coordinated Universal Time — UTC, GMT',
|
||||
|
||||
@@ -41,7 +41,8 @@ function DraggableTableRow({
|
||||
);
|
||||
}
|
||||
|
||||
interface DraggableTableRowProps extends React.HTMLAttributes<HTMLTableRowElement> {
|
||||
interface DraggableTableRowProps
|
||||
extends React.HTMLAttributes<HTMLTableRowElement> {
|
||||
index: number;
|
||||
moveRow: (dragIndex: number, hoverIndex: number) => void;
|
||||
}
|
||||
|
||||
@@ -6,9 +6,9 @@ export function dropHandler(monitor: DropTargetMonitor): { isOver: boolean } {
|
||||
};
|
||||
}
|
||||
|
||||
export function dragHandler(monitor: DragSourceMonitor): {
|
||||
isDragging: boolean;
|
||||
} {
|
||||
export function dragHandler(
|
||||
monitor: DragSourceMonitor,
|
||||
): { isDragging: boolean } {
|
||||
return {
|
||||
isDragging: monitor.isDragging(),
|
||||
};
|
||||
|
||||
@@ -157,8 +157,10 @@ function EditMemberDrawer({
|
||||
new Date(String(existingToken.expiresAt)) < new Date();
|
||||
|
||||
// Create/regenerate token mutation
|
||||
const { mutateAsync: createTokenMutation, isLoading: isGeneratingLink } =
|
||||
useCreateResetPasswordToken();
|
||||
const {
|
||||
mutateAsync: createTokenMutation,
|
||||
isLoading: isGeneratingLink,
|
||||
} = useCreateResetPasswordToken();
|
||||
|
||||
const fetchedDisplayName =
|
||||
fetchedUser?.data?.displayName ?? member?.name ?? '';
|
||||
@@ -218,20 +220,22 @@ function EditMemberDrawer({
|
||||
});
|
||||
|
||||
const makeRoleRetry = useCallback(
|
||||
(context: string, rawRetry: () => Promise<void>) =>
|
||||
async (): Promise<void> => {
|
||||
try {
|
||||
await rawRetry();
|
||||
setSaveErrors((prev) => prev.filter((e) => e.context !== context));
|
||||
refetchUser();
|
||||
} catch (err) {
|
||||
setSaveErrors((prev) =>
|
||||
prev.map((e) =>
|
||||
e.context === context ? { ...e, apiError: toSaveApiError(err) } : e,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
(
|
||||
context: string,
|
||||
rawRetry: () => Promise<void>,
|
||||
) => async (): Promise<void> => {
|
||||
try {
|
||||
await rawRetry();
|
||||
setSaveErrors((prev) => prev.filter((e) => e.context !== context));
|
||||
refetchUser();
|
||||
} catch (err) {
|
||||
setSaveErrors((prev) =>
|
||||
prev.map((e) =>
|
||||
e.context === context ? { ...e, apiError: toSaveApiError(err) } : e,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
[refetchUser],
|
||||
);
|
||||
|
||||
@@ -275,7 +279,7 @@ function EditMemberDrawer({
|
||||
: updateUser({
|
||||
pathParams: { id: member.id },
|
||||
data: { displayName: localDisplayName },
|
||||
})
|
||||
})
|
||||
: Promise.resolve();
|
||||
|
||||
const [nameResult, rolesResult] = await Promise.allSettled([
|
||||
@@ -384,7 +388,7 @@ function EditMemberDrawer({
|
||||
? formatTimezoneAdjustedTimestamp(
|
||||
String(response.data.expiresAt),
|
||||
DATE_TIME_FORMATS.DASH_DATETIME,
|
||||
)
|
||||
)
|
||||
: null,
|
||||
);
|
||||
setHasCopiedResetLink(false);
|
||||
@@ -493,8 +497,8 @@ function EditMemberDrawer({
|
||||
isRootUser
|
||||
? ROOT_USER_TOOLTIP
|
||||
: isDeleted
|
||||
? undefined
|
||||
: 'You cannot modify your own role'
|
||||
? undefined
|
||||
: 'You cannot modify your own role'
|
||||
}
|
||||
>
|
||||
<div className="edit-member-drawer__input-wrapper edit-member-drawer__input-wrapper--disabled">
|
||||
@@ -617,13 +621,13 @@ function EditMemberDrawer({
|
||||
{isGeneratingLink
|
||||
? 'Generating...'
|
||||
: isInvited
|
||||
? getInviteButtonLabel(
|
||||
isLoadingTokenStatus,
|
||||
existingToken,
|
||||
isTokenExpired,
|
||||
tokenNotFound,
|
||||
)
|
||||
: 'Generate Password Reset Link'}
|
||||
? getInviteButtonLabel(
|
||||
isLoadingTokenStatus,
|
||||
existingToken,
|
||||
isTokenExpired,
|
||||
tokenNotFound,
|
||||
)
|
||||
: 'Generate Password Reset Link'}
|
||||
</Button>
|
||||
</span>
|
||||
</Tooltip>
|
||||
|
||||
@@ -168,12 +168,9 @@
|
||||
gap: 3px;
|
||||
background: var(--l1-border);
|
||||
border-radius: 20px;
|
||||
box-shadow:
|
||||
0px 103px 12px 0px rgba(0, 0, 0, 0.01),
|
||||
0px 66px 18px 0px rgba(0, 0, 0, 0.01),
|
||||
0px 37px 22px 0px rgba(0, 0, 0, 0.03),
|
||||
0px 17px 17px 0px rgba(0, 0, 0, 0.04),
|
||||
0px 4px 9px 0px rgba(0, 0, 0, 0.04);
|
||||
box-shadow: 0px 103px 12px 0px rgba(0, 0, 0, 0.01),
|
||||
0px 66px 18px 0px rgba(0, 0, 0, 0.01), 0px 37px 22px 0px rgba(0, 0, 0, 0.03),
|
||||
0px 17px 17px 0px rgba(0, 0, 0, 0.04), 0px 4px 9px 0px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
&__scroll-hint-text {
|
||||
|
||||
@@ -25,14 +25,15 @@ function ErrorContent({ error, icon }: ErrorContentProps): JSX.Element {
|
||||
errors: errorMessages,
|
||||
code: errorCode,
|
||||
message: errorMessage,
|
||||
} = error && 'error' in error
|
||||
? error?.error?.error || {}
|
||||
: {
|
||||
url: undefined,
|
||||
errors: [],
|
||||
code: error.code || 500,
|
||||
message: error.message || 'Something went wrong',
|
||||
};
|
||||
} =
|
||||
error && 'error' in error
|
||||
? error?.error?.error || {}
|
||||
: {
|
||||
url: undefined,
|
||||
errors: [],
|
||||
code: error.code || 500,
|
||||
message: error.message || 'Something went wrong',
|
||||
};
|
||||
return (
|
||||
<section className="error-content">
|
||||
{/* Summary Header */}
|
||||
|
||||
@@ -23,8 +23,11 @@ function MenuItemGenerator({
|
||||
refetchAllView,
|
||||
sourcePage,
|
||||
}: MenuItemLabelGeneratorProps): JSX.Element {
|
||||
const { panelType, redirectWithQueryBuilderData, updateAllQueriesOperators } =
|
||||
useQueryBuilder();
|
||||
const {
|
||||
panelType,
|
||||
redirectWithQueryBuilderData,
|
||||
updateAllQueriesOperators,
|
||||
} = useQueryBuilder();
|
||||
const { handleExplorerTabChange } = useHandleExplorerTabChange();
|
||||
|
||||
const { notifications } = useNotifications();
|
||||
|
||||
@@ -17,8 +17,11 @@ function SaveViewWithName({
|
||||
}: SaveViewWithNameProps): JSX.Element {
|
||||
const [form] = Form.useForm<SaveViewFormProps>();
|
||||
const { t } = useTranslation(['explorer']);
|
||||
const { currentQuery, panelType, redirectWithQueryBuilderData } =
|
||||
useQueryBuilder();
|
||||
const {
|
||||
currentQuery,
|
||||
panelType,
|
||||
redirectWithQueryBuilderData,
|
||||
} = useQueryBuilder();
|
||||
const { notifications } = useNotifications();
|
||||
const compositeQuery = mapCompositeQueryFromQuery(currentQuery, panelType);
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { QueryParams } from 'constants/query';
|
||||
|
||||
export const ExploreHeaderToolTip = {
|
||||
url: 'https://signoz.io/docs/userguide/query-builder/?utm_source=product&utm_medium=new-query-builder',
|
||||
url:
|
||||
'https://signoz.io/docs/userguide/query-builder/?utm_source=product&utm_medium=new-query-builder',
|
||||
text: 'More details on how to use query builder',
|
||||
};
|
||||
|
||||
|
||||
@@ -55,8 +55,9 @@ function createMousedownHandler(
|
||||
startDragPositionX = right;
|
||||
}
|
||||
|
||||
const startValuePositionX =
|
||||
chart.scales.x.getValueForPixel(startDragPositionX);
|
||||
const startValuePositionX = chart.scales.x.getValueForPixel(
|
||||
startDragPositionX,
|
||||
);
|
||||
|
||||
dragData.onDragStart(startDragPositionX, startValuePositionX);
|
||||
};
|
||||
@@ -108,8 +109,9 @@ function createMouseupHandler(
|
||||
endRelativePostionX = right;
|
||||
}
|
||||
|
||||
const endValuePositionX =
|
||||
chart.scales.x.getValueForPixel(endRelativePostionX);
|
||||
const endValuePositionX = chart.scales.x.getValueForPixel(
|
||||
endRelativePostionX,
|
||||
);
|
||||
|
||||
dragData.onDragEnd(endRelativePostionX, endValuePositionX);
|
||||
|
||||
|
||||
@@ -12,12 +12,11 @@ export type IntersectionCursorPluginOptions = {
|
||||
gapSize?: number;
|
||||
};
|
||||
|
||||
export const defaultIntersectionCursorPluginOptions: Required<IntersectionCursorPluginOptions> =
|
||||
{
|
||||
color: 'white',
|
||||
dashSize: 3,
|
||||
gapSize: 3,
|
||||
};
|
||||
export const defaultIntersectionCursorPluginOptions: Required<IntersectionCursorPluginOptions> = {
|
||||
color: 'white',
|
||||
dashSize: 3,
|
||||
gapSize: 3,
|
||||
};
|
||||
|
||||
export function createIntersectionCursorPluginOptions(
|
||||
isEnabled: boolean,
|
||||
|
||||
@@ -52,7 +52,7 @@ export const legend = (id: string, isLonger: boolean): Plugin<ChartType> => ({
|
||||
])
|
||||
? get(chart, ['options', 'plugins', 'legend', 'labels', 'generateLabels'])(
|
||||
chart,
|
||||
)
|
||||
)
|
||||
: null;
|
||||
|
||||
items?.forEach((item: Record<any, any>, index: number) => {
|
||||
|
||||
@@ -95,7 +95,7 @@ export const getGraphOptions = (
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
}
|
||||
: {}),
|
||||
title: {
|
||||
display: title !== undefined,
|
||||
|
||||
@@ -177,9 +177,7 @@
|
||||
color: var(--destructive);
|
||||
opacity: 0.6;
|
||||
padding: 0;
|
||||
transition:
|
||||
background-color 0.2s,
|
||||
opacity 0.2s;
|
||||
transition: background-color 0.2s, opacity 0.2s;
|
||||
box-shadow: none;
|
||||
|
||||
&:hover {
|
||||
|
||||
@@ -46,8 +46,9 @@ function LaunchChatSupport({
|
||||
featureFlagsFetchError,
|
||||
isLoggedIn,
|
||||
} = useAppContext();
|
||||
const [isAddCreditCardModalOpen, setIsAddCreditCardModalOpen] =
|
||||
useState(false);
|
||||
const [isAddCreditCardModalOpen, setIsAddCreditCardModalOpen] = useState(
|
||||
false,
|
||||
);
|
||||
|
||||
const { pathname } = useLocation();
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ export const VIEW_TYPES = {
|
||||
INFRAMETRICS: 'INFRAMETRICS',
|
||||
} as const;
|
||||
|
||||
export type VIEWS = (typeof VIEW_TYPES)[keyof typeof VIEW_TYPES];
|
||||
export type VIEWS = typeof VIEW_TYPES[keyof typeof VIEW_TYPES];
|
||||
|
||||
export const RESOURCE_KEYS = {
|
||||
CLUSTER_NAME: 'k8s.cluster.name',
|
||||
|
||||
@@ -261,7 +261,7 @@ function LogDetailInner({
|
||||
...query.filter,
|
||||
expression: value,
|
||||
},
|
||||
}
|
||||
}
|
||||
: query,
|
||||
),
|
||||
},
|
||||
|
||||
@@ -20,10 +20,9 @@ function AddToQueryHOC({
|
||||
onAddToQuery(fieldKey, fieldValue, OPERATORS['='], dataType);
|
||||
};
|
||||
|
||||
const popOverContent = useMemo(
|
||||
() => <span>Add to query: {fieldKey}</span>,
|
||||
[fieldKey],
|
||||
);
|
||||
const popOverContent = useMemo(() => <span>Add to query: {fieldKey}</span>, [
|
||||
fieldKey,
|
||||
]);
|
||||
|
||||
return (
|
||||
<div className={cx('addToQueryContainer', fontSize)} onClick={handleQueryAdd}>
|
||||
|
||||
@@ -104,7 +104,7 @@ type ListLogViewProps = {
|
||||
selectedFields: IField[];
|
||||
onSetActiveLog: (
|
||||
log: ILog,
|
||||
selectedTab?: (typeof VIEW_TYPES)[keyof typeof VIEW_TYPES],
|
||||
selectedTab?: typeof VIEW_TYPES[keyof typeof VIEW_TYPES],
|
||||
) => void;
|
||||
onAddToQuery: AddToQueryHOCProps['onAddToQuery'];
|
||||
activeLog?: ILog | null;
|
||||
@@ -166,11 +166,11 @@ function ListLogView({
|
||||
? formatTimezoneAdjustedTimestamp(
|
||||
flattenLogData.timestamp,
|
||||
DATE_TIME_FORMATS.ISO_DATETIME_MS,
|
||||
)
|
||||
)
|
||||
: formatTimezoneAdjustedTimestamp(
|
||||
flattenLogData.timestamp / 1e6,
|
||||
DATE_TIME_FORMATS.ISO_DATETIME_MS,
|
||||
),
|
||||
),
|
||||
[flattenLogData.timestamp, formatTimezoneAdjustedTimestamp],
|
||||
);
|
||||
|
||||
|
||||
@@ -24,10 +24,10 @@ export const Container = styled(Card)<{
|
||||
fontSize === FontSize.SMALL
|
||||
? `margin-bottom:0.1rem;`
|
||||
: fontSize === FontSize.MEDIUM
|
||||
? `margin-bottom: 0.2rem;`
|
||||
: fontSize === FontSize.LARGE
|
||||
? `margin-bottom:0.3rem;`
|
||||
: ``}
|
||||
? `margin-bottom: 0.2rem;`
|
||||
: fontSize === FontSize.LARGE
|
||||
? `margin-bottom:0.3rem;`
|
||||
: ``}
|
||||
cursor: pointer;
|
||||
|
||||
&:not(:hover) .log-line-action-buttons {
|
||||
@@ -41,10 +41,10 @@ export const Container = styled(Card)<{
|
||||
fontSize === FontSize.SMALL
|
||||
? `padding:0.1rem 0.6rem;`
|
||||
: fontSize === FontSize.MEDIUM
|
||||
? `padding: 0.2rem 0.6rem;`
|
||||
: fontSize === FontSize.LARGE
|
||||
? `padding:0.3rem 0.6rem;`
|
||||
: ``}
|
||||
? `padding: 0.2rem 0.6rem;`
|
||||
: fontSize === FontSize.LARGE
|
||||
? `padding:0.3rem 0.6rem;`
|
||||
: ``}
|
||||
|
||||
${({ $isActiveLog, $isDarkMode, $logType }): string =>
|
||||
getActiveLogBackground($isActiveLog, $isDarkMode, $logType)}
|
||||
@@ -65,8 +65,8 @@ export const LogContainer = styled.div<LogContainerProps>`
|
||||
fontSize === FontSize.SMALL
|
||||
? `gap: 2px;`
|
||||
: fontSize === FontSize.MEDIUM
|
||||
? ` gap:4px;`
|
||||
: `gap:6px;`}
|
||||
? ` gap:4px;`
|
||||
: `gap:6px;`}
|
||||
`;
|
||||
|
||||
export const LogText = styled.div<LogTextProps>`
|
||||
|
||||
@@ -88,11 +88,11 @@ function RawLogView({
|
||||
? formatTimezoneAdjustedTimestamp(
|
||||
data.timestamp,
|
||||
DATE_TIME_FORMATS.ISO_DATETIME_MS,
|
||||
)
|
||||
)
|
||||
: formatTimezoneAdjustedTimestamp(
|
||||
data.timestamp / 1e6,
|
||||
DATE_TIME_FORMATS.ISO_DATETIME_MS,
|
||||
);
|
||||
);
|
||||
parts.push(date);
|
||||
}
|
||||
|
||||
|
||||
@@ -39,22 +39,22 @@ export const RawLogViewContainer = styled(Row)<{
|
||||
fontSize === FontSize.SMALL
|
||||
? `margin: 1px 0;`
|
||||
: fontSize === FontSize.MEDIUM
|
||||
? `margin: 1px 0;`
|
||||
: `margin: 2px 0;`}
|
||||
? `margin: 1px 0;`
|
||||
: `margin: 2px 0;`}
|
||||
}
|
||||
|
||||
${({ $isReadOnly, $isActiveLog, $isDarkMode, $logType }): string =>
|
||||
$isActiveLog
|
||||
? getActiveLogBackground($isActiveLog, $isDarkMode, $logType)
|
||||
: !$isReadOnly
|
||||
? `&:hover { ${getActiveLogBackground(true, $isDarkMode, $logType)} }`
|
||||
: ''}
|
||||
? `&:hover { ${getActiveLogBackground(true, $isDarkMode, $logType)} }`
|
||||
: ''}
|
||||
|
||||
${({ $isHightlightedLog, $isDarkMode }): string =>
|
||||
$isHightlightedLog
|
||||
? `background-color: ${
|
||||
$isDarkMode ? Color.BG_ROBIN_600 : Color.BG_VANILLA_400
|
||||
};
|
||||
};
|
||||
transition: background-color 2s ease-in;`
|
||||
: ''}
|
||||
|
||||
@@ -104,8 +104,8 @@ export const RawLogContent = styled.div<RawLogContentProps>`
|
||||
fontSize === FontSize.SMALL
|
||||
? `font-size:11px; line-height:16px; padding:1px;`
|
||||
: fontSize === FontSize.MEDIUM
|
||||
? `font-size:13px; line-height:20px; padding:1px;`
|
||||
: `font-size:14px; line-height:24px; padding:2px;`}
|
||||
? `font-size:13px; line-height:20px; padding:1px;`
|
||||
: `font-size:14px; line-height:24px; padding:2px;`}
|
||||
|
||||
cursor: ${({ $isActiveLog, $isReadOnly }): string =>
|
||||
$isActiveLog || $isReadOnly ? 'initial' : 'pointer'};
|
||||
|
||||
@@ -19,7 +19,7 @@ export interface RawLogViewProps {
|
||||
handleChangeSelectedView?: ChangeViewFunctionType;
|
||||
onSetActiveLog?: (
|
||||
log: ILog,
|
||||
selectedTab?: (typeof VIEW_TYPES)[keyof typeof VIEW_TYPES],
|
||||
selectedTab?: typeof VIEW_TYPES[keyof typeof VIEW_TYPES],
|
||||
) => void;
|
||||
onClearActiveLog?: () => void;
|
||||
}
|
||||
|
||||
@@ -27,6 +27,6 @@ export const TableBodyContent = styled.div<TableBodyContentProps>`
|
||||
fontSize === FontSize.SMALL
|
||||
? `font-size:11px; line-height:16px;`
|
||||
: fontSize === FontSize.MEDIUM
|
||||
? `font-size:13px; line-height:20px;`
|
||||
: `font-size:14px; line-height:24px;`}
|
||||
? `font-size:13px; line-height:20px;`
|
||||
: `font-size:14px; line-height:24px;`}
|
||||
`;
|
||||
|
||||
@@ -34,10 +34,9 @@ export const useTableView = (props: UseTableViewProps): UseTableViewResult => {
|
||||
|
||||
const isDarkMode = useIsDarkMode();
|
||||
|
||||
const flattenLogData = useMemo(
|
||||
() => logs.map((log) => FlatLogData(log)),
|
||||
[logs],
|
||||
);
|
||||
const flattenLogData = useMemo(() => logs.map((log) => FlatLogData(log)), [
|
||||
logs,
|
||||
]);
|
||||
|
||||
const { formatTimezoneAdjustedTimestamp } = useTimezone();
|
||||
|
||||
@@ -116,11 +115,11 @@ export const useTableView = (props: UseTableViewProps): UseTableViewResult => {
|
||||
? formatTimezoneAdjustedTimestamp(
|
||||
field,
|
||||
DATE_TIME_FORMATS.ISO_DATETIME_MS,
|
||||
)
|
||||
)
|
||||
: formatTimezoneAdjustedTimestamp(
|
||||
field / 1e6,
|
||||
DATE_TIME_FORMATS.ISO_DATETIME_MS,
|
||||
);
|
||||
);
|
||||
return {
|
||||
children: (
|
||||
<div className="table-timestamp">
|
||||
@@ -130,7 +129,7 @@ export const useTableView = (props: UseTableViewProps): UseTableViewResult => {
|
||||
};
|
||||
},
|
||||
},
|
||||
]
|
||||
]
|
||||
: []),
|
||||
...(appendTo === 'center' ? fieldColumns : []),
|
||||
...(fields.some((field) => field.name === 'body')
|
||||
@@ -161,7 +160,7 @@ export const useTableView = (props: UseTableViewProps): UseTableViewResult => {
|
||||
),
|
||||
}),
|
||||
},
|
||||
]
|
||||
]
|
||||
: []),
|
||||
...(appendTo === 'end' ? fieldColumns : []),
|
||||
];
|
||||
|
||||
@@ -35,11 +35,13 @@ function OptionsMenu({
|
||||
const [fontSizeValue, setFontSizeValue] = useState<FontSize>(
|
||||
fontSize?.value || FontSize.SMALL,
|
||||
);
|
||||
const [isFontSizeOptionsOpen, setIsFontSizeOptionsOpen] =
|
||||
useState<boolean>(false);
|
||||
const [isFontSizeOptionsOpen, setIsFontSizeOptionsOpen] = useState<boolean>(
|
||||
false,
|
||||
);
|
||||
|
||||
const [showAddNewColumnContainer, setShowAddNewColumnContainer] =
|
||||
useState(false);
|
||||
const [showAddNewColumnContainer, setShowAddNewColumnContainer] = useState(
|
||||
false,
|
||||
);
|
||||
|
||||
const [selectedValue, setSelectedValue] = useState<string | null>(null);
|
||||
const listRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
@@ -260,28 +260,23 @@ const CustomMultiSelect: React.FC<CustomMultiSelectProps> = ({
|
||||
/**
|
||||
* Separates section and non-section options
|
||||
*/
|
||||
const splitOptions = useCallback(
|
||||
(
|
||||
options: OptionData[],
|
||||
): {
|
||||
sectionOptions: OptionData[];
|
||||
nonSectionOptions: OptionData[];
|
||||
} => {
|
||||
const sectionOptions: OptionData[] = [];
|
||||
const nonSectionOptions: OptionData[] = [];
|
||||
const splitOptions = useCallback((options: OptionData[]): {
|
||||
sectionOptions: OptionData[];
|
||||
nonSectionOptions: OptionData[];
|
||||
} => {
|
||||
const sectionOptions: OptionData[] = [];
|
||||
const nonSectionOptions: OptionData[] = [];
|
||||
|
||||
options.forEach((option) => {
|
||||
if ('options' in option && Array.isArray(option.options)) {
|
||||
sectionOptions.push(option);
|
||||
} else {
|
||||
nonSectionOptions.push(option);
|
||||
}
|
||||
});
|
||||
options.forEach((option) => {
|
||||
if ('options' in option && Array.isArray(option.options)) {
|
||||
sectionOptions.push(option);
|
||||
} else {
|
||||
nonSectionOptions.push(option);
|
||||
}
|
||||
});
|
||||
|
||||
return { sectionOptions, nonSectionOptions };
|
||||
},
|
||||
[],
|
||||
);
|
||||
return { sectionOptions, nonSectionOptions };
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* Apply search filtering to options
|
||||
@@ -1634,7 +1629,7 @@ const CustomMultiSelect: React.FC<CustomMultiSelectProps> = ({
|
||||
}}
|
||||
data={enhancedNonSectionOptions}
|
||||
itemContent={(index, item): React.ReactNode =>
|
||||
mapOptions([item]) as unknown as React.ReactElement
|
||||
(mapOptions([item]) as unknown) as React.ReactElement
|
||||
}
|
||||
totalCount={enhancedNonSectionOptions.length}
|
||||
itemSize={(): number => 40}
|
||||
@@ -1676,7 +1671,7 @@ const CustomMultiSelect: React.FC<CustomMultiSelectProps> = ({
|
||||
}}
|
||||
data={section.options || []}
|
||||
itemContent={(index, item): React.ReactNode =>
|
||||
mapOptions([item]) as unknown as React.ReactElement
|
||||
(mapOptions([item]) as unknown) as React.ReactElement
|
||||
}
|
||||
totalCount={section.options?.length || 0}
|
||||
itemSize={(): number => 40}
|
||||
@@ -1941,7 +1936,7 @@ const CustomMultiSelect: React.FC<CustomMultiSelectProps> = ({
|
||||
? {
|
||||
borderColor: Color.BG_ROBIN_500,
|
||||
backgroundColor: Color.BG_SLATE_400,
|
||||
}
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
|
||||
@@ -112,28 +112,23 @@ const CustomSelect: React.FC<CustomSelectProps> = ({
|
||||
/**
|
||||
* Separates section and non-section options
|
||||
*/
|
||||
const splitOptions = useCallback(
|
||||
(
|
||||
options: OptionData[],
|
||||
): {
|
||||
sectionOptions: OptionData[];
|
||||
nonSectionOptions: OptionData[];
|
||||
} => {
|
||||
const sectionOptions: OptionData[] = [];
|
||||
const nonSectionOptions: OptionData[] = [];
|
||||
const splitOptions = useCallback((options: OptionData[]): {
|
||||
sectionOptions: OptionData[];
|
||||
nonSectionOptions: OptionData[];
|
||||
} => {
|
||||
const sectionOptions: OptionData[] = [];
|
||||
const nonSectionOptions: OptionData[] = [];
|
||||
|
||||
options.forEach((option) => {
|
||||
if ('options' in option && Array.isArray(option.options)) {
|
||||
sectionOptions.push(option);
|
||||
} else {
|
||||
nonSectionOptions.push(option);
|
||||
}
|
||||
});
|
||||
options.forEach((option) => {
|
||||
if ('options' in option && Array.isArray(option.options)) {
|
||||
sectionOptions.push(option);
|
||||
} else {
|
||||
nonSectionOptions.push(option);
|
||||
}
|
||||
});
|
||||
|
||||
return { sectionOptions, nonSectionOptions };
|
||||
},
|
||||
[],
|
||||
);
|
||||
return { sectionOptions, nonSectionOptions };
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* Apply search filtering to options
|
||||
@@ -327,8 +322,9 @@ const CustomSelect: React.FC<CustomSelectProps> = ({
|
||||
processedOptions = filterOptionsBySearch(processedOptions, searchText);
|
||||
}
|
||||
|
||||
const { sectionOptions, nonSectionOptions } =
|
||||
splitOptions(processedOptions);
|
||||
const { sectionOptions, nonSectionOptions } = splitOptions(
|
||||
processedOptions,
|
||||
);
|
||||
|
||||
// Add custom option if needed
|
||||
if (
|
||||
|
||||
@@ -336,7 +336,7 @@ describe('CustomMultiSelect Component', () => {
|
||||
renderWithVirtuoso(
|
||||
<CustomMultiSelect
|
||||
options={mockGroupedOptions}
|
||||
value={'__ALL__' as unknown as string[]}
|
||||
value={('__ALL__' as unknown) as string[]}
|
||||
/>,
|
||||
);
|
||||
|
||||
|
||||
@@ -180,9 +180,7 @@ $custom-border-color: #2c3044;
|
||||
.custom-multiselect-dropdown-container {
|
||||
z-index: 1050 !important;
|
||||
padding: 0;
|
||||
box-shadow:
|
||||
0 3px 6px -4px rgba(0, 0, 0, 0.5),
|
||||
0 6px 16px 0 rgba(0, 0, 0, 0.4),
|
||||
box-shadow: 0 3px 6px -4px rgba(0, 0, 0, 0.5), 0 6px 16px 0 rgba(0, 0, 0, 0.4),
|
||||
0 9px 28px 8px rgba(0, 0, 0, 0.3);
|
||||
background-color: var(--l2-background);
|
||||
border: 1px solid var(--l1-border);
|
||||
@@ -806,10 +804,8 @@ $custom-border-color: #2c3044;
|
||||
.custom-multiselect-dropdown-container {
|
||||
background-color: var(--l1-background);
|
||||
border: 1px solid var(--l1-border);
|
||||
box-shadow:
|
||||
0 3px 6px -4px rgba(0, 0, 0, 0.12),
|
||||
0 6px 16px 0 rgba(0, 0, 0, 0.08),
|
||||
0 9px 28px 8px rgba(0, 0, 0, 0.05);
|
||||
box-shadow: 0 3px 6px -4px rgba(0, 0, 0, 0.12),
|
||||
0 6px 16px 0 rgba(0, 0, 0, 0.08), 0 9px 28px 8px rgba(0, 0, 0, 0.05);
|
||||
|
||||
.empty-message {
|
||||
color: var(--l2-foreground);
|
||||
@@ -980,9 +976,7 @@ $custom-border-color: #2c3044;
|
||||
font-weight: 500;
|
||||
z-index: 2;
|
||||
pointer-events: none;
|
||||
transition:
|
||||
opacity 0.2s ease,
|
||||
visibility 0.2s ease;
|
||||
transition: opacity 0.2s ease, visibility 0.2s ease;
|
||||
|
||||
.lightMode & {
|
||||
color: rgba(0, 0, 0, 0.85);
|
||||
|
||||
@@ -40,10 +40,8 @@ export interface CustomTagProps {
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export interface CustomMultiSelectProps extends Omit<
|
||||
SelectProps<string[] | string>,
|
||||
'options'
|
||||
> {
|
||||
export interface CustomMultiSelectProps
|
||||
extends Omit<SelectProps<string[] | string>, 'options'> {
|
||||
placeholder?: string;
|
||||
className?: string;
|
||||
loading?: boolean;
|
||||
|
||||
@@ -26,7 +26,7 @@ function OverlayScrollbar({
|
||||
theme: isDarkMode ? 'os-theme-light' : 'os-theme-dark',
|
||||
},
|
||||
...(customOptions || {}),
|
||||
}) as PartialOptions,
|
||||
} as PartialOptions),
|
||||
[customOptions, isDarkMode],
|
||||
);
|
||||
|
||||
|
||||
@@ -10,14 +10,8 @@
|
||||
border-bottom: 1px solid var(--l1-border);
|
||||
border-top: 1px solid var(--l1-border);
|
||||
|
||||
font-family:
|
||||
Inter,
|
||||
-apple-system,
|
||||
BlinkMacSystemFont,
|
||||
'Segoe UI',
|
||||
Roboto,
|
||||
'Helvetica Neue',
|
||||
sans-serif;
|
||||
font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
|
||||
'Helvetica Neue', sans-serif;
|
||||
|
||||
border-right: none;
|
||||
border-left: none;
|
||||
|
||||
@@ -76,34 +76,32 @@ export const QueryBuilderV2 = memo(function QueryBuilderV2({
|
||||
[showTraceOperator, isListViewPanel],
|
||||
);
|
||||
|
||||
const listViewLogFilterConfigs: QueryBuilderProps['filterConfigs'] =
|
||||
useMemo(() => {
|
||||
const config: QueryBuilderProps['filterConfigs'] = {
|
||||
stepInterval: { isHidden: true, isDisabled: true },
|
||||
having: { isHidden: true, isDisabled: true },
|
||||
filters: {
|
||||
customKey: 'body',
|
||||
customOp: OPERATORS.CONTAINS,
|
||||
},
|
||||
};
|
||||
const listViewLogFilterConfigs: QueryBuilderProps['filterConfigs'] = useMemo(() => {
|
||||
const config: QueryBuilderProps['filterConfigs'] = {
|
||||
stepInterval: { isHidden: true, isDisabled: true },
|
||||
having: { isHidden: true, isDisabled: true },
|
||||
filters: {
|
||||
customKey: 'body',
|
||||
customOp: OPERATORS.CONTAINS,
|
||||
},
|
||||
};
|
||||
|
||||
return config;
|
||||
}, []);
|
||||
return config;
|
||||
}, []);
|
||||
|
||||
const listViewTracesFilterConfigs: QueryBuilderProps['filterConfigs'] =
|
||||
useMemo(() => {
|
||||
const config: QueryBuilderProps['filterConfigs'] = {
|
||||
stepInterval: { isHidden: true, isDisabled: true },
|
||||
having: { isHidden: true, isDisabled: true },
|
||||
limit: { isHidden: true, isDisabled: true },
|
||||
filters: {
|
||||
customKey: 'body',
|
||||
customOp: OPERATORS.CONTAINS,
|
||||
},
|
||||
};
|
||||
const listViewTracesFilterConfigs: QueryBuilderProps['filterConfigs'] = useMemo(() => {
|
||||
const config: QueryBuilderProps['filterConfigs'] = {
|
||||
stepInterval: { isHidden: true, isDisabled: true },
|
||||
having: { isHidden: true, isDisabled: true },
|
||||
limit: { isHidden: true, isDisabled: true },
|
||||
filters: {
|
||||
customKey: 'body',
|
||||
customOp: OPERATORS.CONTAINS,
|
||||
},
|
||||
};
|
||||
|
||||
return config;
|
||||
}, []);
|
||||
return config;
|
||||
}, []);
|
||||
|
||||
const queryFilterConfigs = useMemo(() => {
|
||||
if (isListViewPanel) {
|
||||
|
||||
@@ -49,8 +49,11 @@ export const MetricsSelect = memo(function MetricsSelect({
|
||||
entityVersion: version,
|
||||
});
|
||||
|
||||
const { updateAllQueriesOperators, handleSetQueryData, panelType } =
|
||||
useQueryBuilder();
|
||||
const {
|
||||
updateAllQueriesOperators,
|
||||
handleSetQueryData,
|
||||
panelType,
|
||||
} = useQueryBuilder();
|
||||
|
||||
const source = useMemo(
|
||||
() => (signalSource === 'meter' ? 'meter' : 'metrics'),
|
||||
@@ -120,8 +123,9 @@ export const MetricsSelect = memo(function MetricsSelect({
|
||||
signalSource: newSignalSource,
|
||||
panelType: panelType || '',
|
||||
});
|
||||
const savedQuery: IBuilderQuery | null =
|
||||
getPreviousQueryFromKey(newQueryKey);
|
||||
const savedQuery: IBuilderQuery | null = getPreviousQueryFromKey(
|
||||
newQueryKey,
|
||||
);
|
||||
|
||||
// remove the new query key from session storage
|
||||
removeKeyFromPreviousQuery(newQueryKey);
|
||||
|
||||
@@ -464,37 +464,36 @@ function QueryAddOns({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{selectedViews.find((view) => view.key === 'reduce_to') &&
|
||||
showReduceTo && (
|
||||
<div className="add-on-content" data-testid="reduce-to-content">
|
||||
<div className="periscope-input-with-label">
|
||||
<Tooltip
|
||||
title={
|
||||
<TooltipContent
|
||||
label="Reduce to"
|
||||
description="Apply mathematical operations like sum, average, min, max, or percentiles to reduce multiple time series into a single value."
|
||||
docLink="https://signoz.io/docs/userguide/query-builder-v5/#reduce-operations"
|
||||
/>
|
||||
}
|
||||
placement="top"
|
||||
mouseEnterDelay={0.5}
|
||||
>
|
||||
<div className="label" style={{ cursor: 'help' }}>
|
||||
Reduce to
|
||||
</div>
|
||||
</Tooltip>
|
||||
<div className="input">
|
||||
<ReduceToFilter query={query} onChange={handleChangeReduceToV5} />
|
||||
{selectedViews.find((view) => view.key === 'reduce_to') && showReduceTo && (
|
||||
<div className="add-on-content" data-testid="reduce-to-content">
|
||||
<div className="periscope-input-with-label">
|
||||
<Tooltip
|
||||
title={
|
||||
<TooltipContent
|
||||
label="Reduce to"
|
||||
description="Apply mathematical operations like sum, average, min, max, or percentiles to reduce multiple time series into a single value."
|
||||
docLink="https://signoz.io/docs/userguide/query-builder-v5/#reduce-operations"
|
||||
/>
|
||||
}
|
||||
placement="top"
|
||||
mouseEnterDelay={0.5}
|
||||
>
|
||||
<div className="label" style={{ cursor: 'help' }}>
|
||||
Reduce to
|
||||
</div>
|
||||
|
||||
<Button
|
||||
className="close-btn periscope-btn ghost"
|
||||
icon={<ChevronUp size={16} />}
|
||||
onClick={(): void => handleRemoveView('reduce_to')}
|
||||
/>
|
||||
</Tooltip>
|
||||
<div className="input">
|
||||
<ReduceToFilter query={query} onChange={handleChangeReduceToV5} />
|
||||
</div>
|
||||
|
||||
<Button
|
||||
className="close-btn periscope-btn ghost"
|
||||
icon={<ChevronUp size={16} />}
|
||||
onClick={(): void => handleRemoveView('reduce_to')}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{selectedViews.find((view) => view.key === 'legend_format') && (
|
||||
<div className="add-on-content" data-testid="legend-format-content">
|
||||
|
||||
@@ -290,12 +290,12 @@ function QueryAggregationSelect({
|
||||
}
|
||||
|
||||
const regex = /([a-zA-Z_][\w]*)\s*\(([^)]*)\)/g;
|
||||
const oldMatches = [...tr.startState.doc.toString().matchAll(regex)].filter(
|
||||
(match) => validFunctions.includes(match[1].toLowerCase()),
|
||||
);
|
||||
const newMatches = [...tr.newDoc.toString().matchAll(regex)].filter(
|
||||
(match) => validFunctions.includes(match[1].toLowerCase()),
|
||||
);
|
||||
const oldMatches = [
|
||||
...tr.startState.doc.toString().matchAll(regex),
|
||||
].filter((match) => validFunctions.includes(match[1].toLowerCase()));
|
||||
const newMatches = [
|
||||
...tr.newDoc.toString().matchAll(regex),
|
||||
].filter((match) => validFunctions.includes(match[1].toLowerCase()));
|
||||
|
||||
if (
|
||||
newMatches.length > oldMatches.length &&
|
||||
@@ -558,7 +558,7 @@ function QueryAggregationSelect({
|
||||
? availableSuggestions
|
||||
: availableSuggestions.filter((suggestion) =>
|
||||
suggestion.label.toLowerCase().includes(inputText.toLowerCase()),
|
||||
);
|
||||
);
|
||||
|
||||
return {
|
||||
from: startOfArg,
|
||||
|
||||
@@ -5,14 +5,8 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
font-family:
|
||||
Inter,
|
||||
-apple-system,
|
||||
BlinkMacSystemFont,
|
||||
'Segoe UI',
|
||||
Roboto,
|
||||
'Helvetica Neue',
|
||||
sans-serif;
|
||||
font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
|
||||
'Helvetica Neue', sans-serif;
|
||||
|
||||
.query-where-clause-editor-container {
|
||||
position: relative;
|
||||
|
||||
@@ -194,8 +194,10 @@ function QuerySearch({
|
||||
|
||||
const [cursorPos, setCursorPos] = useState({ line: 0, ch: 0 });
|
||||
|
||||
const [isFetchingCompleteValuesList, setIsFetchingCompleteValuesList] =
|
||||
useState<boolean>(false);
|
||||
const [
|
||||
isFetchingCompleteValuesList,
|
||||
setIsFetchingCompleteValuesList,
|
||||
] = useState<boolean>(false);
|
||||
|
||||
const lastPosRef = useRef<{ line: number; ch: number }>({ line: 0, ch: 0 });
|
||||
|
||||
|
||||
@@ -93,10 +93,9 @@ export const QueryV2 = forwardRef(function QueryV2(
|
||||
[dataSource, panelType],
|
||||
);
|
||||
|
||||
const showSpanScopeSelector = useMemo(
|
||||
() => dataSource === DataSource.TRACES,
|
||||
[dataSource],
|
||||
);
|
||||
const showSpanScopeSelector = useMemo(() => dataSource === DataSource.TRACES, [
|
||||
dataSource,
|
||||
]);
|
||||
|
||||
const showInlineQuerySearch = useMemo(() => {
|
||||
if (!showTraceOperator) {
|
||||
@@ -213,7 +212,7 @@ export const QueryV2 = forwardRef(function QueryV2(
|
||||
icon: <Trash size={14} />,
|
||||
onClick: handleDeleteQuery,
|
||||
},
|
||||
]
|
||||
]
|
||||
: []),
|
||||
],
|
||||
}}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { IBuilderTraceOperator } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { getInvolvedQueriesInTraceOperator } from '../utils/utils';
|
||||
|
||||
const makeTraceOperator = (expression: string): IBuilderTraceOperator =>
|
||||
({ expression }) as unknown as IBuilderTraceOperator;
|
||||
(({ expression } as unknown) as IBuilderTraceOperator);
|
||||
|
||||
describe('getInvolvedQueriesInTraceOperator', () => {
|
||||
it('returns empty array for empty input', () => {
|
||||
|
||||
@@ -98,7 +98,9 @@ export function createTraceOperatorContext(
|
||||
}
|
||||
|
||||
// Helper to determine token context
|
||||
function determineTraceTokenContext(token: IToken): {
|
||||
function determineTraceTokenContext(
|
||||
token: IToken,
|
||||
): {
|
||||
isInAtom: boolean;
|
||||
isInOperator: boolean;
|
||||
isInParenthesis: boolean;
|
||||
|
||||
@@ -36,14 +36,14 @@ beforeAll(() => {
|
||||
const mockRange = {
|
||||
// CodeMirror uses these for text measurement
|
||||
getClientRects: (): DOMRectList =>
|
||||
({
|
||||
(({
|
||||
length: 1,
|
||||
item: (index: number): DOMRect | null => (index === 0 ? mockRect : null),
|
||||
0: mockRect,
|
||||
*[Symbol.iterator](): Generator<DOMRect> {
|
||||
yield mockRect;
|
||||
},
|
||||
}) as unknown as DOMRectList,
|
||||
} as unknown) as DOMRectList),
|
||||
getBoundingClientRect: (): DOMRect => mockRect,
|
||||
// CodeMirror calls these to set up text ranges
|
||||
setStart: (node: Node, offset: number): void => {
|
||||
@@ -72,7 +72,7 @@ beforeAll(() => {
|
||||
},
|
||||
commonAncestorContainer: document.body,
|
||||
};
|
||||
return mockRange as unknown as Range;
|
||||
return (mockRange as unknown) as Range;
|
||||
};
|
||||
|
||||
// Mock document.createRange to return a new Range instance each time
|
||||
|
||||
@@ -94,12 +94,13 @@ describe('QueryBuilderV2 + QueryV2 - base render', () => {
|
||||
},
|
||||
};
|
||||
|
||||
const updateAllQueriesOperators: QueryBuilderContextType['updateAllQueriesOperators'] =
|
||||
(q) => q;
|
||||
const updateAllQueriesOperators: QueryBuilderContextType['updateAllQueriesOperators'] = (
|
||||
q,
|
||||
) => q;
|
||||
const updateQueriesData: QueryBuilderContextType['updateQueriesData'] = (q) =>
|
||||
q;
|
||||
|
||||
const baseContext = {
|
||||
const baseContext = ({
|
||||
currentQuery: currentQueryObj,
|
||||
stagedQuery: null,
|
||||
lastUsedQuery: null,
|
||||
@@ -132,7 +133,7 @@ describe('QueryBuilderV2 + QueryV2 - base render', () => {
|
||||
initQueryBuilderData: jest.fn(),
|
||||
isStagedQueryUpdated: jest.fn(() => false),
|
||||
isDefaultQuery: jest.fn(() => false),
|
||||
} as unknown as QueryBuilderContextType;
|
||||
} as unknown) as QueryBuilderContextType;
|
||||
|
||||
baseQBContext = baseContext;
|
||||
mockedUseQueryBuilder.mockReturnValue(baseQBContext);
|
||||
@@ -148,8 +149,7 @@ describe('QueryBuilderV2 + QueryV2 - base render', () => {
|
||||
handleChangeAggregatorAttribute: jest.fn(),
|
||||
handleChangeDataSource: jest.fn(),
|
||||
handleDeleteQuery: jest.fn(),
|
||||
handleChangeQueryData:
|
||||
jest.fn() as unknown as ReturnType<UseQueryOperations>['handleChangeQueryData'],
|
||||
handleChangeQueryData: (jest.fn() as unknown) as ReturnType<UseQueryOperations>['handleChangeQueryData'],
|
||||
handleChangeFormulaData: jest.fn(),
|
||||
handleQueryFunctionsUpdates: handleQueryFunctionsUpdatesMock,
|
||||
listOfAdditionalFormulaFilters: [],
|
||||
|
||||
@@ -57,7 +57,7 @@ describe('MetricsSelect - signal source switching (standalone)', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
clearPreviousQuery();
|
||||
handleSetQueryDataMock = jest.fn() as unknown as jest.MockedFunction<
|
||||
handleSetQueryDataMock = (jest.fn() as unknown) as jest.MockedFunction<
|
||||
(index: number, q: IBuilderQuery) => void
|
||||
>;
|
||||
|
||||
@@ -222,7 +222,7 @@ describe('DataSource change - Logs to Traces', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
clearPreviousQuery();
|
||||
handleSetQueryDataMock = jest.fn() as unknown as jest.MockedFunction<
|
||||
handleSetQueryDataMock = (jest.fn() as unknown) as jest.MockedFunction<
|
||||
(i: number, q: IBuilderQuery) => void
|
||||
>;
|
||||
|
||||
|
||||
@@ -947,7 +947,7 @@ describe('convertAggregationToExpression', () => {
|
||||
it('should handle undefined aggregateAttribute parameter with traces', () => {
|
||||
const result = convertAggregationToExpression({
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: undefined as unknown as BaseAutocompleteData,
|
||||
aggregateAttribute: (undefined as unknown) as BaseAutocompleteData,
|
||||
dataSource: DataSource.TRACES,
|
||||
});
|
||||
|
||||
@@ -961,7 +961,7 @@ describe('convertAggregationToExpression', () => {
|
||||
it('should handle undefined aggregateAttribute parameter with logs', () => {
|
||||
const result = convertAggregationToExpression({
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: undefined as unknown as BaseAutocompleteData,
|
||||
aggregateAttribute: (undefined as unknown) as BaseAutocompleteData,
|
||||
dataSource: DataSource.LOGS,
|
||||
});
|
||||
|
||||
|
||||
@@ -210,8 +210,8 @@ export const convertExpressionToFilters = (
|
||||
type: '',
|
||||
},
|
||||
value: pair.isMultiValue
|
||||
? (formatValuesForFilter(pair.valueList as string[]) ?? [])
|
||||
: (formatValuesForFilter(pair.value as string) ?? ''),
|
||||
? formatValuesForFilter(pair.valueList as string[]) ?? []
|
||||
: formatValuesForFilter(pair.value as string) ?? '',
|
||||
});
|
||||
});
|
||||
|
||||
@@ -469,8 +469,8 @@ export const convertFiltersToExpressionWithExistingQuery = (
|
||||
type: '',
|
||||
},
|
||||
value: pair.isMultiValue
|
||||
? (formatValuesForFilter(pair.valueList as string[]) ?? '')
|
||||
: (formatValuesForFilter(pair.value as string) ?? ''),
|
||||
? formatValuesForFilter(pair.valueList as string[]) ?? ''
|
||||
: formatValuesForFilter(pair.value as string) ?? '',
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -554,7 +554,7 @@ export const removeKeysFromExpression = (
|
||||
}
|
||||
const value = pair.value?.toString().trim();
|
||||
return value && value.includes('$');
|
||||
})
|
||||
})
|
||||
: existingQueryPairs;
|
||||
|
||||
// Build a map for quick lookup of query pairs by their lowercase trimmed keys
|
||||
@@ -744,18 +744,15 @@ export function getQueryLabelWithAggregation(
|
||||
const labels: { label: string; value: string }[] = [];
|
||||
|
||||
const aggregationPerQuery =
|
||||
queryData.reduce(
|
||||
(acc, query) => {
|
||||
if (query.queryName && query.aggregations?.length) {
|
||||
acc[query.queryName] = createAggregation(query).map((a: any) => ({
|
||||
alias: a.alias,
|
||||
expression: a.expression,
|
||||
}));
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, any>,
|
||||
) || {};
|
||||
queryData.reduce((acc, query) => {
|
||||
if (query.queryName && query.aggregations?.length) {
|
||||
acc[query.queryName] = createAggregation(query).map((a: any) => ({
|
||||
alias: a.alias,
|
||||
expression: a.expression,
|
||||
}));
|
||||
}
|
||||
return acc;
|
||||
}, {} as Record<string, any>) || {};
|
||||
|
||||
Object.entries(aggregationPerQuery).forEach(([queryName, aggregations]) => {
|
||||
const isMultipleAggregations = aggregations.length > 1;
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
.placeholder {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
min-height: 240px;
|
||||
width: 100%;
|
||||
padding: 24px;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.emoji {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
.text {
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
color: var(--muted-foreground);
|
||||
}
|
||||
|
||||
.subText {
|
||||
color: var(--foreground);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { Typography } from 'antd';
|
||||
import eyesEmojiUrl from 'assets/Images/eyesEmoji.svg';
|
||||
|
||||
import styles from './QueryCancelledPlaceholder.module.scss';
|
||||
|
||||
interface QueryCancelledPlaceholderProps {
|
||||
subText?: string;
|
||||
}
|
||||
|
||||
function QueryCancelledPlaceholder({
|
||||
subText,
|
||||
}: QueryCancelledPlaceholderProps): JSX.Element {
|
||||
return (
|
||||
<div className={styles.placeholder}>
|
||||
<img className={styles.emoji} src={eyesEmojiUrl} alt="eyes emoji" />
|
||||
<Typography className={styles.text}>
|
||||
Query cancelled.
|
||||
<span className={styles.subText}>
|
||||
{' '}
|
||||
{subText || 'Click "Run Query" to load data.'}
|
||||
</span>
|
||||
</Typography>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
QueryCancelledPlaceholder.defaultProps = {
|
||||
subText: undefined,
|
||||
};
|
||||
|
||||
export default QueryCancelledPlaceholder;
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from './QueryCancelledPlaceholder';
|
||||
@@ -83,7 +83,7 @@ const createMockQueryBuilderData = (hasActiveFilters = false): any => ({
|
||||
op: 'in',
|
||||
value: [OTEL_DEMO, SAMPLE_FLASK],
|
||||
},
|
||||
]
|
||||
]
|
||||
: [],
|
||||
},
|
||||
},
|
||||
@@ -99,7 +99,7 @@ describe('CheckboxFilter - User Flows', () => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
// Default mock implementations for useGetAggregateValues
|
||||
mockUseGetAggregateValues.mockReturnValue({
|
||||
mockUseGetAggregateValues.mockReturnValue(({
|
||||
data: {
|
||||
payload: {
|
||||
stringAttributeValues: MOCK_SERVICE_NAMES,
|
||||
@@ -107,7 +107,7 @@ describe('CheckboxFilter - User Flows', () => {
|
||||
},
|
||||
isLoading: false,
|
||||
refetch: jest.fn(),
|
||||
} as unknown as UseQueryResult<SuccessResponse<IAttributeValuesResponse>>);
|
||||
} as unknown) as UseQueryResult<SuccessResponse<IAttributeValuesResponse>>);
|
||||
|
||||
// Default mock implementations for useGetQueryKeyValueSuggestions
|
||||
// Returns data in the format expected by the hook
|
||||
|
||||
@@ -82,8 +82,10 @@ export default function CheckboxFilter(props: ICheckboxProps): JSX.Element {
|
||||
// Check if this filter has active filters in the query
|
||||
const isSomeFilterPresentForCurrentAttribute = useMemo(
|
||||
() =>
|
||||
currentQuery.builder.queryData?.[activeQueryIndex]?.filters?.items?.some(
|
||||
(item) => isKeyMatch(item.key?.key, filter.attributeKey.key),
|
||||
currentQuery.builder.queryData?.[
|
||||
activeQueryIndex
|
||||
]?.filters?.items?.some((item) =>
|
||||
isKeyMatch(item.key?.key, filter.attributeKey.key),
|
||||
),
|
||||
[currentQuery.builder.queryData, activeQueryIndex, filter.attributeKey.key],
|
||||
);
|
||||
@@ -124,16 +126,18 @@ export default function CheckboxFilter(props: ICheckboxProps): JSX.Element {
|
||||
},
|
||||
);
|
||||
|
||||
const { data: keyValueSuggestions, isLoading: isLoadingKeyValueSuggestions } =
|
||||
useGetQueryKeyValueSuggestions({
|
||||
key: filter.attributeKey.key,
|
||||
signal: filter.dataSource || DataSource.LOGS,
|
||||
signalSource: 'meter',
|
||||
options: {
|
||||
enabled: isOpen && source === QuickFiltersSource.METER_EXPLORER,
|
||||
keepPreviousData: true,
|
||||
},
|
||||
});
|
||||
const {
|
||||
data: keyValueSuggestions,
|
||||
isLoading: isLoadingKeyValueSuggestions,
|
||||
} = useGetQueryKeyValueSuggestions({
|
||||
key: filter.attributeKey.key,
|
||||
signal: filter.dataSource || DataSource.LOGS,
|
||||
signalSource: 'meter',
|
||||
options: {
|
||||
enabled: isOpen && source === QuickFiltersSource.METER_EXPLORER,
|
||||
keepPreviousData: true,
|
||||
},
|
||||
});
|
||||
|
||||
const attributeValues: string[] = useMemo(() => {
|
||||
const dataType = filter.attributeKey.dataType || DataTypes.String;
|
||||
@@ -278,7 +282,7 @@ export default function CheckboxFilter(props: ICheckboxProps): JSX.Element {
|
||||
idx === activeQueryIndex
|
||||
? item.filters?.items?.filter(
|
||||
(fil) => !isKeyMatch(fil.key?.key, filter.attributeKey.key),
|
||||
) || []
|
||||
) || []
|
||||
: [...(item.filters?.items || [])],
|
||||
op: item.filters?.op || 'AND',
|
||||
},
|
||||
|
||||
@@ -36,13 +36,12 @@ function Duration({
|
||||
onFilterChange?: (query: Query) => void;
|
||||
source?: QuickFiltersSource;
|
||||
}): JSX.Element {
|
||||
const [selectedFilters, setSelectedFilters] =
|
||||
useState<
|
||||
Record<
|
||||
AllTraceFilterKeys,
|
||||
{ values: string[] | string; keys: BaseAutocompleteData }
|
||||
>
|
||||
>();
|
||||
const [selectedFilters, setSelectedFilters] = useState<
|
||||
Record<
|
||||
AllTraceFilterKeys,
|
||||
{ values: string[] | string; keys: BaseAutocompleteData }
|
||||
>
|
||||
>();
|
||||
const [activeKeys, setActiveKeys] = useState<string[]>([
|
||||
filter.defaultOpen ? 'durationNano' : '',
|
||||
]);
|
||||
|
||||
@@ -30,8 +30,13 @@ function SortableFilter({
|
||||
allowDrag: boolean;
|
||||
allowRemove: boolean;
|
||||
}): JSX.Element {
|
||||
const { attributes, listeners, setNodeRef, transform, transition } =
|
||||
useSortable({ id: filter.key });
|
||||
const {
|
||||
attributes,
|
||||
listeners,
|
||||
setNodeRef,
|
||||
transform,
|
||||
transition,
|
||||
} = useSortable({ id: filter.key });
|
||||
|
||||
const style = {
|
||||
transform: CSS.Transform.toString(transform),
|
||||
|
||||
@@ -48,46 +48,52 @@ function OtherFilters({
|
||||
[signal],
|
||||
);
|
||||
|
||||
const { data: suggestionsData, isFetching: isFetchingSuggestions } =
|
||||
useGetAttributeSuggestions(
|
||||
{
|
||||
searchText: inputValue,
|
||||
dataSource: SIGNAL_DATA_SOURCE_MAP[signal as SignalType],
|
||||
filters: {} as TagFilter,
|
||||
},
|
||||
{
|
||||
queryKey: [REACT_QUERY_KEY.GET_OTHER_FILTERS, inputValue],
|
||||
enabled: !!signal && isLogDataSource,
|
||||
},
|
||||
);
|
||||
const {
|
||||
data: suggestionsData,
|
||||
isFetching: isFetchingSuggestions,
|
||||
} = useGetAttributeSuggestions(
|
||||
{
|
||||
searchText: inputValue,
|
||||
dataSource: SIGNAL_DATA_SOURCE_MAP[signal as SignalType],
|
||||
filters: {} as TagFilter,
|
||||
},
|
||||
{
|
||||
queryKey: [REACT_QUERY_KEY.GET_OTHER_FILTERS, inputValue],
|
||||
enabled: !!signal && isLogDataSource,
|
||||
},
|
||||
);
|
||||
|
||||
const { data: aggregateKeysData, isFetching: isFetchingAggregateKeys } =
|
||||
useGetAggregateKeys(
|
||||
{
|
||||
searchText: inputValue,
|
||||
dataSource: SIGNAL_DATA_SOURCE_MAP[signal as SignalType],
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: '',
|
||||
tagType: '',
|
||||
},
|
||||
{
|
||||
queryKey: [REACT_QUERY_KEY.GET_OTHER_FILTERS, inputValue],
|
||||
enabled: !!signal && !isLogDataSource && !isMeterDataSource,
|
||||
},
|
||||
);
|
||||
const {
|
||||
data: aggregateKeysData,
|
||||
isFetching: isFetchingAggregateKeys,
|
||||
} = useGetAggregateKeys(
|
||||
{
|
||||
searchText: inputValue,
|
||||
dataSource: SIGNAL_DATA_SOURCE_MAP[signal as SignalType],
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: '',
|
||||
tagType: '',
|
||||
},
|
||||
{
|
||||
queryKey: [REACT_QUERY_KEY.GET_OTHER_FILTERS, inputValue],
|
||||
enabled: !!signal && !isLogDataSource && !isMeterDataSource,
|
||||
},
|
||||
);
|
||||
|
||||
const { data: fieldKeysData, isLoading: isLoadingFieldKeys } =
|
||||
useGetQueryKeySuggestions(
|
||||
{
|
||||
searchText: inputValue,
|
||||
signal: SIGNAL_DATA_SOURCE_MAP[signal as SignalType],
|
||||
signalSource: 'meter',
|
||||
},
|
||||
{
|
||||
queryKey: [REACT_QUERY_KEY.GET_OTHER_FILTERS, inputValue],
|
||||
enabled: !!signal && isMeterDataSource,
|
||||
},
|
||||
);
|
||||
const {
|
||||
data: fieldKeysData,
|
||||
isLoading: isLoadingFieldKeys,
|
||||
} = useGetQueryKeySuggestions(
|
||||
{
|
||||
searchText: inputValue,
|
||||
signal: SIGNAL_DATA_SOURCE_MAP[signal as SignalType],
|
||||
signalSource: 'meter',
|
||||
},
|
||||
{
|
||||
queryKey: [REACT_QUERY_KEY.GET_OTHER_FILTERS, inputValue],
|
||||
enabled: !!signal && isMeterDataSource,
|
||||
},
|
||||
);
|
||||
|
||||
const otherFilters = useMemo(() => {
|
||||
let filterAttributes;
|
||||
@@ -104,7 +110,7 @@ function OtherFilters({
|
||||
dataType: attr.fieldDataType,
|
||||
type: attr.fieldContext,
|
||||
signal: attr.signal,
|
||||
}) as BaseAutocompleteData,
|
||||
} as BaseAutocompleteData),
|
||||
);
|
||||
} else {
|
||||
filterAttributes = aggregateKeysData?.payload?.attributeKeys || [];
|
||||
|
||||
@@ -40,26 +40,28 @@ const useQuickFilterSettings = ({
|
||||
const [addedFilters, setAddedFilters] = useState<FilterType[]>(customFilters);
|
||||
const { notifications } = useNotifications();
|
||||
|
||||
const { mutate: updateCustomFilters, isLoading: isUpdatingCustomFilters } =
|
||||
useMutation(updateCustomFiltersAPI, {
|
||||
onSuccess: () => {
|
||||
setIsSettingsOpen(false);
|
||||
refetchCustomFilters();
|
||||
logEvent('Quick Filters Settings: changes saved', {
|
||||
addedFilters,
|
||||
});
|
||||
notifications.success({
|
||||
message: 'Quick filters updated successfully',
|
||||
placement: 'bottomRight',
|
||||
});
|
||||
},
|
||||
onError: (error: AxiosError) => {
|
||||
notifications.error({
|
||||
message: axios.isAxiosError(error) ? error.message : SOMETHING_WENT_WRONG,
|
||||
placement: 'bottomRight',
|
||||
});
|
||||
},
|
||||
});
|
||||
const {
|
||||
mutate: updateCustomFilters,
|
||||
isLoading: isUpdatingCustomFilters,
|
||||
} = useMutation(updateCustomFiltersAPI, {
|
||||
onSuccess: () => {
|
||||
setIsSettingsOpen(false);
|
||||
refetchCustomFilters();
|
||||
logEvent('Quick Filters Settings: changes saved', {
|
||||
addedFilters,
|
||||
});
|
||||
notifications.success({
|
||||
message: 'Quick filters updated successfully',
|
||||
placement: 'bottomRight',
|
||||
});
|
||||
},
|
||||
onError: (error: AxiosError) => {
|
||||
notifications.error({
|
||||
message: axios.isAxiosError(error) ? error.message : SOMETHING_WENT_WRONG,
|
||||
placement: 'bottomRight',
|
||||
});
|
||||
},
|
||||
});
|
||||
const debouncedUpdate = useDebouncedFn((value) => {
|
||||
setDebouncedInputValue(value as string);
|
||||
}, 400);
|
||||
|
||||
@@ -38,10 +38,9 @@ const useFilterConfig = ({
|
||||
},
|
||||
);
|
||||
|
||||
const isDynamicFilters = useMemo(
|
||||
() => customFilters.length > 0,
|
||||
[customFilters],
|
||||
);
|
||||
const isDynamicFilters = useMemo(() => customFilters.length > 0, [
|
||||
customFilters,
|
||||
]);
|
||||
|
||||
const filterConfig = useMemo(
|
||||
() => getFilterConfig(signal, customFilters, config),
|
||||
|
||||
@@ -55,6 +55,6 @@ export const getFilterConfig = (
|
||||
type: att.type,
|
||||
},
|
||||
defaultOpen: index < 2,
|
||||
}) as IQuickFiltersConfig,
|
||||
} as IQuickFiltersConfig),
|
||||
);
|
||||
};
|
||||
|
||||
@@ -52,38 +52,39 @@ function DynamicColumnTable({
|
||||
...prevColumns.slice(0, prevColumns.length - 1),
|
||||
...visibleColumns,
|
||||
prevColumns[prevColumns.length - 1],
|
||||
]
|
||||
]
|
||||
: undefined,
|
||||
);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [columns, dynamicColumns]);
|
||||
|
||||
const onToggleHandler =
|
||||
(index: number, column: ColumnGroupType<any> | ColumnType<any>) =>
|
||||
(checked: boolean, event: React.MouseEvent<HTMLButtonElement>): void => {
|
||||
event.stopPropagation();
|
||||
const onToggleHandler = (
|
||||
index: number,
|
||||
column: ColumnGroupType<any> | ColumnType<any>,
|
||||
) => (checked: boolean, event: React.MouseEvent<HTMLButtonElement>): void => {
|
||||
event.stopPropagation();
|
||||
|
||||
if (shouldSendAlertsLogEvent) {
|
||||
logEvent('Alert: Column toggled', {
|
||||
column: column?.title,
|
||||
action: checked ? 'Enable' : 'Disable',
|
||||
});
|
||||
}
|
||||
setVisibleColumns({
|
||||
tablesource,
|
||||
dynamicColumns,
|
||||
index,
|
||||
checked,
|
||||
if (shouldSendAlertsLogEvent) {
|
||||
logEvent('Alert: Column toggled', {
|
||||
column: column?.title,
|
||||
action: checked ? 'Enable' : 'Disable',
|
||||
});
|
||||
setColumnsData((prevColumns) =>
|
||||
getNewColumnData({
|
||||
checked,
|
||||
index,
|
||||
prevColumns,
|
||||
dynamicColumns,
|
||||
}),
|
||||
);
|
||||
};
|
||||
}
|
||||
setVisibleColumns({
|
||||
tablesource,
|
||||
dynamicColumns,
|
||||
index,
|
||||
checked,
|
||||
});
|
||||
setColumnsData((prevColumns) =>
|
||||
getNewColumnData({
|
||||
checked,
|
||||
index,
|
||||
prevColumns,
|
||||
dynamicColumns,
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const items: MenuProps['items'] =
|
||||
dynamicColumns?.map((column, index) => ({
|
||||
|
||||
@@ -45,18 +45,20 @@ function ResizeTable({
|
||||
}, [onColumnWidthsChange]);
|
||||
|
||||
const handleResize = useCallback(
|
||||
(index: number) =>
|
||||
(e: SyntheticEvent<Element>, { size }: ResizeCallbackData): void => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
(index: number) => (
|
||||
e: SyntheticEvent<Element>,
|
||||
{ size }: ResizeCallbackData,
|
||||
): void => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const newColumns = [...columnsData];
|
||||
newColumns[index] = {
|
||||
...newColumns[index],
|
||||
width: size.width,
|
||||
};
|
||||
setColumns(newColumns);
|
||||
},
|
||||
const newColumns = [...columnsData];
|
||||
newColumns[index] = {
|
||||
...newColumns[index],
|
||||
width: size.width,
|
||||
};
|
||||
setColumns(newColumns);
|
||||
},
|
||||
[columnsData],
|
||||
);
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ export interface ResizeTableProps extends TableProps<any> {
|
||||
onColumnWidthsChange?: (widths: ColumnWidths) => void;
|
||||
}
|
||||
export interface DynamicColumnTableProps extends TableProps<any> {
|
||||
tablesource: (typeof TableDataSource)[keyof typeof TableDataSource];
|
||||
tablesource: typeof TableDataSource[keyof typeof TableDataSource];
|
||||
dynamicColumns: TableProps<any>['columns'];
|
||||
onDragColumn?: (fromIndex: number, toIndex: number) => void;
|
||||
facingIssueBtn?: LaunchChatSupportProps;
|
||||
@@ -40,7 +40,7 @@ export type GetVisibleColumnsFunction = (
|
||||
) => (ColumnGroupType<any> | ColumnType<any>)[];
|
||||
|
||||
export type GetVisibleColumnProps = {
|
||||
tablesource: (typeof TableDataSource)[keyof typeof TableDataSource];
|
||||
tablesource: typeof TableDataSource[keyof typeof TableDataSource];
|
||||
dynamicColumns?: ColumnsType<any>;
|
||||
columnsData?: ColumnsType;
|
||||
};
|
||||
@@ -48,7 +48,7 @@ export type GetVisibleColumnProps = {
|
||||
export type SetVisibleColumnsProps = {
|
||||
checked: boolean;
|
||||
index: number;
|
||||
tablesource: (typeof TableDataSource)[keyof typeof TableDataSource];
|
||||
tablesource: typeof TableDataSource[keyof typeof TableDataSource];
|
||||
dynamicColumns?: ColumnsType<any>;
|
||||
};
|
||||
|
||||
|
||||
@@ -68,7 +68,7 @@ export const getNewColumnData: GetNewColumnDataFunction = ({
|
||||
...prevColumns.slice(0, prevColumns.length - 1),
|
||||
dynamicColumns[index],
|
||||
prevColumns[prevColumns.length - 1],
|
||||
]
|
||||
]
|
||||
: undefined;
|
||||
}
|
||||
return prevColumns && dynamicColumns
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user