mirror of
https://github.com/SigNoz/signoz.git
synced 2026-05-07 19:10:30 +01:00
Compare commits
10 Commits
test/split
...
feat/pnpm-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
29d6051254 | ||
|
|
fb40344fb5 | ||
|
|
ad1df54064 | ||
|
|
486733ca6a | ||
|
|
24f811fbb9 | ||
|
|
5648de1e5f | ||
|
|
0fd565d888 | ||
|
|
1e31e6a4f7 | ||
|
|
f2aceea1fc | ||
|
|
78dd489fd3 |
1
.github/CODEOWNERS
vendored
1
.github/CODEOWNERS
vendored
@@ -107,7 +107,6 @@ go.mod @therealpandey
|
||||
/pkg/modules/organization/ @therealpandey
|
||||
/pkg/modules/authdomain/ @therealpandey
|
||||
/pkg/modules/role/ @therealpandey
|
||||
/pkg/types/coretypes/ @therealpandey @vikrantgupta25
|
||||
|
||||
# IdentN Owners
|
||||
|
||||
|
||||
1
.github/workflows/build-community.yaml
vendored
1
.github/workflows/build-community.yaml
vendored
@@ -54,6 +54,7 @@ jobs:
|
||||
JS_SRC: frontend
|
||||
JS_OUTPUT_ARTIFACT_CACHE_KEY: community-jsbuild-${{ github.sha }}
|
||||
JS_OUTPUT_ARTIFACT_PATH: frontend/build
|
||||
JS_PKG_MANAGER: pnpm
|
||||
DOCKER_BUILD: false
|
||||
DOCKER_MANIFEST: false
|
||||
go-build:
|
||||
|
||||
1
.github/workflows/build-enterprise.yaml
vendored
1
.github/workflows/build-enterprise.yaml
vendored
@@ -87,6 +87,7 @@ jobs:
|
||||
JS_INPUT_ARTIFACT_PATH: frontend/.env
|
||||
JS_OUTPUT_ARTIFACT_CACHE_KEY: enterprise-jsbuild-${{ github.sha }}
|
||||
JS_OUTPUT_ARTIFACT_PATH: frontend/build
|
||||
JS_PKG_MANAGER: pnpm
|
||||
DOCKER_BUILD: false
|
||||
DOCKER_MANIFEST: false
|
||||
go-build:
|
||||
|
||||
1
.github/workflows/build-staging.yaml
vendored
1
.github/workflows/build-staging.yaml
vendored
@@ -86,6 +86,7 @@ jobs:
|
||||
JS_INPUT_ARTIFACT_PATH: frontend/.env
|
||||
JS_OUTPUT_ARTIFACT_CACHE_KEY: staging-jsbuild-${{ github.sha }}
|
||||
JS_OUTPUT_ARTIFACT_PATH: frontend/build
|
||||
JS_PKG_MANAGER: pnpm
|
||||
DOCKER_BUILD: false
|
||||
DOCKER_MANIFEST: false
|
||||
go-build:
|
||||
|
||||
22
.github/workflows/e2eci.yaml
vendored
22
.github/workflows/e2eci.yaml
vendored
@@ -21,15 +21,19 @@ jobs:
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: lts/*
|
||||
- name: install-pnpm
|
||||
uses: pnpm/action-setup@v6
|
||||
with:
|
||||
version: 10
|
||||
- name: install
|
||||
run: |
|
||||
cd tests/e2e && yarn install --frozen-lockfile
|
||||
cd tests/e2e && pnpm install
|
||||
- name: fmt
|
||||
run: |
|
||||
cd tests/e2e && yarn fmt:check
|
||||
cd tests/e2e && pnpm fmt:check
|
||||
- name: lint
|
||||
run: |
|
||||
cd tests/e2e && yarn lint
|
||||
cd tests/e2e && pnpm lint
|
||||
test:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -54,15 +58,19 @@ jobs:
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: lts/*
|
||||
- name: install-pnpm
|
||||
uses: pnpm/action-setup@v6
|
||||
with:
|
||||
version: 10
|
||||
- name: python-install
|
||||
run: |
|
||||
cd tests && uv sync
|
||||
- name: yarn-install
|
||||
- name: pnpm-install
|
||||
run: |
|
||||
cd tests/e2e && yarn install --frozen-lockfile
|
||||
cd tests/e2e && pnpm install --frozen-lockfile
|
||||
- name: playwright-browsers
|
||||
run: |
|
||||
cd tests/e2e && yarn playwright install --with-deps ${{ matrix.project }}
|
||||
cd tests/e2e && pnpm playwright install --with-deps ${{ matrix.project }}
|
||||
- name: bring-up-stack
|
||||
run: |
|
||||
cd tests && \
|
||||
@@ -73,7 +81,7 @@ jobs:
|
||||
- name: playwright-test
|
||||
run: |
|
||||
cd tests/e2e && \
|
||||
yarn playwright test --project=${{ matrix.project }}
|
||||
pnpm playwright test --project=${{ matrix.project }}
|
||||
- name: teardown-stack
|
||||
if: always()
|
||||
run: |
|
||||
|
||||
4
.github/workflows/goci.yaml
vendored
4
.github/workflows/goci.yaml
vendored
@@ -77,6 +77,10 @@ jobs:
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: "22"
|
||||
- name: setup-pnpm
|
||||
uses: pnpm/action-setup@v6
|
||||
with:
|
||||
version: 10
|
||||
- name: docker-community
|
||||
shell: bash
|
||||
run: |
|
||||
|
||||
4
.github/workflows/gor-signoz-community.yaml
vendored
4
.github/workflows/gor-signoz-community.yaml
vendored
@@ -25,6 +25,10 @@ jobs:
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: "22"
|
||||
- name: setup-pnpm
|
||||
uses: pnpm/action-setup@v6
|
||||
with:
|
||||
version: 10
|
||||
- name: build-frontend
|
||||
run: make js-build
|
||||
- name: upload-frontend-artifact
|
||||
|
||||
4
.github/workflows/gor-signoz.yaml
vendored
4
.github/workflows/gor-signoz.yaml
vendored
@@ -41,6 +41,10 @@ jobs:
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: "22"
|
||||
- name: setup-pnpm
|
||||
uses: pnpm/action-setup@v6
|
||||
with:
|
||||
version: 10
|
||||
- name: build-frontend
|
||||
run: make js-build
|
||||
- name: upload-frontend-artifact
|
||||
|
||||
14
.github/workflows/jsci.yaml
vendored
14
.github/workflows/jsci.yaml
vendored
@@ -22,6 +22,7 @@ jobs:
|
||||
with:
|
||||
PRIMUS_REF: main
|
||||
JS_SRC: frontend
|
||||
JS_PKG_MANAGER: pnpm
|
||||
test:
|
||||
if: |
|
||||
github.event_name == 'merge_group' ||
|
||||
@@ -32,6 +33,7 @@ jobs:
|
||||
with:
|
||||
PRIMUS_REF: main
|
||||
JS_SRC: frontend
|
||||
JS_PKG_MANAGER: pnpm
|
||||
fmt:
|
||||
if: |
|
||||
github.event_name == 'merge_group' ||
|
||||
@@ -42,6 +44,7 @@ jobs:
|
||||
with:
|
||||
PRIMUS_REF: main
|
||||
JS_SRC: frontend
|
||||
JS_PKG_MANAGER: pnpm
|
||||
lint:
|
||||
if: |
|
||||
github.event_name == 'merge_group' ||
|
||||
@@ -52,6 +55,7 @@ jobs:
|
||||
with:
|
||||
PRIMUS_REF: main
|
||||
JS_SRC: frontend
|
||||
JS_PKG_MANAGER: pnpm
|
||||
languages:
|
||||
if: |
|
||||
github.event_name == 'merge_group' ||
|
||||
@@ -76,9 +80,13 @@ jobs:
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: "22"
|
||||
- name: install-pnpm
|
||||
uses: pnpm/action-setup@v6
|
||||
with:
|
||||
version: 10
|
||||
- name: install-frontend
|
||||
run: cd frontend && yarn install
|
||||
run: cd frontend && pnpm install
|
||||
- name: generate-api-clients
|
||||
run: |
|
||||
cd frontend && yarn generate:api
|
||||
git diff --compact-summary --exit-code || (echo; echo "Unexpected difference in generated api clients. Run yarn generate:api in frontend/ locally and commit."; exit 1)
|
||||
cd frontend && pnpm generate:api
|
||||
git diff --compact-summary --exit-code || (echo; echo "Unexpected difference in generated api clients. Run pnpm generate:api in frontend/ locally and commit."; exit 1)
|
||||
|
||||
10
.gitpod.yml
10
.gitpod.yml
@@ -1,19 +1,21 @@
|
||||
# Please adjust to your needs (see https://www.gitpod.io/docs/config-gitpod-file)
|
||||
# and commit this file to your remote git repository to share the goodness with others.
|
||||
|
||||
|
||||
tasks:
|
||||
- name: Run Docker Images
|
||||
init: |
|
||||
cd ./deploy/docker
|
||||
sudo docker compose up -d
|
||||
|
||||
- name: Install pnpm
|
||||
init: |
|
||||
npm i -g pnpm
|
||||
|
||||
- name: Run Frontend
|
||||
init: |
|
||||
cd ./frontend
|
||||
yarn install
|
||||
command:
|
||||
yarn dev
|
||||
pnpm install
|
||||
command: pnpm dev
|
||||
|
||||
ports:
|
||||
- port: 8080
|
||||
|
||||
2
Makefile
2
Makefile
@@ -154,7 +154,7 @@ $(GO_BUILD_ARCHS_ENTERPRISE_RACE): go-build-enterprise-race-%: $(TARGET_DIR)
|
||||
.PHONY: js-build
|
||||
js-build: ## Builds the js frontend
|
||||
@echo ">> building js frontend"
|
||||
@cd $(JS_BUILD_CONTEXT) && CI=1 yarn install && yarn build
|
||||
@cd $(JS_BUILD_CONTEXT) && CI=1 pnpm install && pnpm build
|
||||
|
||||
##############################################################
|
||||
# docker commands
|
||||
|
||||
@@ -3,8 +3,9 @@ FROM node:22-bookworm AS build
|
||||
WORKDIR /opt/
|
||||
COPY ./frontend/ ./
|
||||
ENV NODE_OPTIONS=--max-old-space-size=8192
|
||||
RUN CI=1 yarn install
|
||||
RUN CI=1 yarn build
|
||||
RUN CI=1 npm i -g pnpm
|
||||
RUN CI=1 pnpm install
|
||||
RUN CI=1 pnpm build
|
||||
|
||||
FROM golang:1.25-bookworm
|
||||
|
||||
|
||||
@@ -190,7 +190,7 @@ services:
|
||||
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
|
||||
signoz:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz:v0.122.0
|
||||
image: signoz/signoz:v0.121.1
|
||||
ports:
|
||||
- "8080:8080" # signoz port
|
||||
# - "6060:6060" # pprof port
|
||||
|
||||
@@ -117,7 +117,7 @@ services:
|
||||
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
|
||||
signoz:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz:v0.122.0
|
||||
image: signoz/signoz:v0.121.1
|
||||
ports:
|
||||
- "8080:8080" # signoz port
|
||||
volumes:
|
||||
|
||||
@@ -181,7 +181,7 @@ services:
|
||||
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
|
||||
signoz:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz:${VERSION:-v0.122.0}
|
||||
image: signoz/signoz:${VERSION:-v0.121.1}
|
||||
container_name: signoz
|
||||
ports:
|
||||
- "8080:8080" # signoz port
|
||||
|
||||
@@ -109,7 +109,7 @@ services:
|
||||
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
|
||||
signoz:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz:${VERSION:-v0.122.0}
|
||||
image: signoz/signoz:${VERSION:-v0.121.1}
|
||||
container_name: signoz
|
||||
ports:
|
||||
- "8080:8080" # signoz port
|
||||
|
||||
@@ -2547,8 +2547,7 @@ components:
|
||||
format: double
|
||||
type: number
|
||||
meta:
|
||||
additionalProperties:
|
||||
type: string
|
||||
additionalProperties: {}
|
||||
nullable: true
|
||||
type: object
|
||||
status:
|
||||
@@ -2598,103 +2597,6 @@ components:
|
||||
- requiredMetricsCheck
|
||||
- endTimeBeforeRetention
|
||||
type: object
|
||||
InframonitoringtypesNodeCondition:
|
||||
enum:
|
||||
- ready
|
||||
- not_ready
|
||||
- no_data
|
||||
type: string
|
||||
InframonitoringtypesNodeCountsByReadiness:
|
||||
properties:
|
||||
notReady:
|
||||
type: integer
|
||||
ready:
|
||||
type: integer
|
||||
required:
|
||||
- ready
|
||||
- notReady
|
||||
type: object
|
||||
InframonitoringtypesNodeRecord:
|
||||
properties:
|
||||
condition:
|
||||
$ref: '#/components/schemas/InframonitoringtypesNodeCondition'
|
||||
meta:
|
||||
additionalProperties:
|
||||
type: string
|
||||
nullable: true
|
||||
type: object
|
||||
nodeCPU:
|
||||
format: double
|
||||
type: number
|
||||
nodeCPUAllocatable:
|
||||
format: double
|
||||
type: number
|
||||
nodeCountsByReadiness:
|
||||
$ref: '#/components/schemas/InframonitoringtypesNodeCountsByReadiness'
|
||||
nodeMemory:
|
||||
format: double
|
||||
type: number
|
||||
nodeMemoryAllocatable:
|
||||
format: double
|
||||
type: number
|
||||
nodeName:
|
||||
type: string
|
||||
podCountsByPhase:
|
||||
$ref: '#/components/schemas/InframonitoringtypesPodCountsByPhase'
|
||||
required:
|
||||
- nodeName
|
||||
- condition
|
||||
- nodeCountsByReadiness
|
||||
- podCountsByPhase
|
||||
- nodeCPU
|
||||
- nodeCPUAllocatable
|
||||
- nodeMemory
|
||||
- nodeMemoryAllocatable
|
||||
- meta
|
||||
type: object
|
||||
InframonitoringtypesNodes:
|
||||
properties:
|
||||
endTimeBeforeRetention:
|
||||
type: boolean
|
||||
records:
|
||||
items:
|
||||
$ref: '#/components/schemas/InframonitoringtypesNodeRecord'
|
||||
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
|
||||
InframonitoringtypesPodCountsByPhase:
|
||||
properties:
|
||||
failed:
|
||||
type: integer
|
||||
pending:
|
||||
type: integer
|
||||
running:
|
||||
type: integer
|
||||
succeeded:
|
||||
type: integer
|
||||
unknown:
|
||||
type: integer
|
||||
required:
|
||||
- pending
|
||||
- running
|
||||
- succeeded
|
||||
- failed
|
||||
- unknown
|
||||
type: object
|
||||
InframonitoringtypesPodPhase:
|
||||
enum:
|
||||
- pending
|
||||
@@ -2702,15 +2604,18 @@ components:
|
||||
- succeeded
|
||||
- failed
|
||||
- unknown
|
||||
- no_data
|
||||
- ""
|
||||
type: string
|
||||
InframonitoringtypesPodRecord:
|
||||
properties:
|
||||
failedPodCount:
|
||||
type: integer
|
||||
meta:
|
||||
additionalProperties:
|
||||
type: string
|
||||
additionalProperties: {}
|
||||
nullable: true
|
||||
type: object
|
||||
pendingPodCount:
|
||||
type: integer
|
||||
podAge:
|
||||
format: int64
|
||||
type: integer
|
||||
@@ -2723,8 +2628,6 @@ components:
|
||||
podCPURequest:
|
||||
format: double
|
||||
type: number
|
||||
podCountsByPhase:
|
||||
$ref: '#/components/schemas/InframonitoringtypesPodCountsByPhase'
|
||||
podMemory:
|
||||
format: double
|
||||
type: number
|
||||
@@ -2738,6 +2641,12 @@ components:
|
||||
$ref: '#/components/schemas/InframonitoringtypesPodPhase'
|
||||
podUID:
|
||||
type: string
|
||||
runningPodCount:
|
||||
type: integer
|
||||
succeededPodCount:
|
||||
type: integer
|
||||
unknownPodCount:
|
||||
type: integer
|
||||
required:
|
||||
- podUID
|
||||
- podCPU
|
||||
@@ -2747,7 +2656,11 @@ components:
|
||||
- podMemoryRequest
|
||||
- podMemoryLimit
|
||||
- podPhase
|
||||
- podCountsByPhase
|
||||
- pendingPodCount
|
||||
- runningPodCount
|
||||
- succeededPodCount
|
||||
- failedPodCount
|
||||
- unknownPodCount
|
||||
- podAge
|
||||
- meta
|
||||
type: object
|
||||
@@ -2801,32 +2714,6 @@ components:
|
||||
- end
|
||||
- limit
|
||||
type: object
|
||||
InframonitoringtypesPostableNodes:
|
||||
properties:
|
||||
end:
|
||||
format: int64
|
||||
type: integer
|
||||
filter:
|
||||
$ref: '#/components/schemas/Querybuildertypesv5Filter'
|
||||
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
|
||||
InframonitoringtypesPostablePods:
|
||||
properties:
|
||||
end:
|
||||
@@ -11731,83 +11618,12 @@ paths:
|
||||
summary: List Hosts for Infra Monitoring
|
||||
tags:
|
||||
- inframonitoring
|
||||
/api/v2/infra_monitoring/nodes:
|
||||
post:
|
||||
deprecated: false
|
||||
description: 'Returns a paginated list of Kubernetes nodes with key metrics:
|
||||
CPU usage, CPU allocatable, memory working set, memory allocatable, per-group
|
||||
nodeCountsByReadiness ({ ready, notReady } from each node''s latest k8s.node.condition_ready
|
||||
in the window) and per-group podCountsByPhase ({ pending, running, succeeded,
|
||||
failed, unknown } for pods scheduled on the listed nodes). Each node includes
|
||||
metadata attributes (k8s.node.uid, k8s.cluster.name). The response type is
|
||||
''list'' for the default k8s.node.name grouping (each row is one node with
|
||||
its current condition string: ready / not_ready / no_data) or ''grouped_list''
|
||||
for custom groupBy keys (each row aggregates nodes in the group; condition
|
||||
stays no_data). Supports filtering via a filter expression, custom groupBy,
|
||||
ordering by cpu / cpu_allocatable / memory / memory_allocatable, and pagination
|
||||
via offset/limit. Also reports missing required metrics and whether the requested
|
||||
time range falls before the data retention boundary. Numeric metric fields
|
||||
(nodeCPU, nodeCPUAllocatable, nodeMemory, nodeMemoryAllocatable) return -1
|
||||
as a sentinel when no data is available for that field.'
|
||||
operationId: ListNodes
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/InframonitoringtypesPostableNodes'
|
||||
responses:
|
||||
"200":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
properties:
|
||||
data:
|
||||
$ref: '#/components/schemas/InframonitoringtypesNodes'
|
||||
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 Nodes for Infra Monitoring
|
||||
tags:
|
||||
- inframonitoring
|
||||
/api/v2/infra_monitoring/pods:
|
||||
post:
|
||||
deprecated: false
|
||||
description: 'Returns a paginated list of Kubernetes pods with key metrics:
|
||||
CPU usage, CPU request/limit utilization, memory working set, memory request/limit
|
||||
utilization, current pod phase (pending/running/succeeded/failed/unknown/no_data),
|
||||
utilization, current pod phase (pending/running/succeeded/failed/unknown),
|
||||
and pod age (ms since start time). Each pod includes metadata attributes (namespace,
|
||||
node, workload owner such as deployment/statefulset/daemonset/job/cronjob,
|
||||
cluster). Supports filtering via a filter expression, custom groupBy to aggregate
|
||||
@@ -11815,13 +11631,13 @@ paths:
|
||||
cpu_limit, memory, memory_request, memory_limit), and pagination via offset/limit.
|
||||
The response type is ''list'' for the default k8s.pod.uid grouping (each row
|
||||
is one pod with its current phase) or ''grouped_list'' for custom groupBy
|
||||
keys (each row aggregates pods in the group with per-phase counts under podCountsByPhase:
|
||||
{ pending, running, succeeded, failed, unknown } derived from each pod''s
|
||||
latest phase in the window). Also reports missing required metrics and whether
|
||||
the requested time range falls before the data retention boundary. Numeric
|
||||
metric fields (podCPU, podCPURequest, podCPULimit, podMemory, podMemoryRequest,
|
||||
podMemoryLimit, podAge) return -1 as a sentinel when no data is available
|
||||
for that field.'
|
||||
keys (each row aggregates pods in the group with per-phase counts: pendingPodCount,
|
||||
runningPodCount, succeededPodCount, failedPodCount, unknownPodCount derived
|
||||
from each pod''s latest phase in the window). Also reports missing required
|
||||
metrics and whether the requested time range falls before the data retention
|
||||
boundary. Numeric metric fields (podCPU, podCPURequest, podCPULimit, podMemory,
|
||||
podMemoryRequest, podMemoryLimit, podAge) return -1 as a sentinel when no
|
||||
data is available for that field.'
|
||||
operationId: ListPods
|
||||
requestBody:
|
||||
content:
|
||||
|
||||
@@ -13,13 +13,12 @@ Before diving in, make sure you have these tools installed:
|
||||
- Download from [go.dev/dl](https://go.dev/dl/)
|
||||
- Check [go.mod](../../go.mod#L3) for the minimum version
|
||||
|
||||
|
||||
- **Node** - Powers our frontend
|
||||
- Download from [nodejs.org](https://nodejs.org)
|
||||
- Check [.nvmrc](../../frontend/.nvmrc) for the version
|
||||
|
||||
- **Yarn** - Our frontend package manager
|
||||
- Follow the [installation guide](https://yarnpkg.com/getting-started/install)
|
||||
- **Pnpm** - Our frontend package manager
|
||||
- Follow the [installation guide](https://pnpm.io/installation)
|
||||
|
||||
- **Docker** - For running Clickhouse and Postgres locally
|
||||
- Get it from [docs.docker.com/get-docker](https://docs.docker.com/get-docker/)
|
||||
@@ -95,7 +94,7 @@ This command:
|
||||
|
||||
2. Install dependencies:
|
||||
```bash
|
||||
yarn install
|
||||
pnpm install
|
||||
```
|
||||
|
||||
3. Create a `.env` file in this directory:
|
||||
@@ -105,10 +104,10 @@ This command:
|
||||
|
||||
4. Start the development server:
|
||||
```bash
|
||||
yarn dev
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
> 💡 **Tip**: `yarn dev` will automatically rebuild when you make changes to the code
|
||||
> 💡 **Tip**: `pnpm dev` will automatically rebuild when you make changes to the code
|
||||
|
||||
Now you're all set to start developing! Happy coding! 🎉
|
||||
|
||||
|
||||
@@ -304,7 +304,7 @@ import ec2Url from '@/assets/Logos/ec2.svg';
|
||||
|
||||
1. Add the logo SVG to `src/assets/Logos/` and add a top-level import in the config file (e.g., `import myServiceUrl from '@/assets/Logos/my-service.svg'`)
|
||||
2. Add your data source object to the `onboardingConfigWithLinks` array, referencing the imported variable for `imgUrl`
|
||||
3. Test the flow locally with `yarn dev`
|
||||
3. Test the flow locally with `pnpm dev`
|
||||
4. Validation:
|
||||
- Navigate to the [onboarding page](http://localhost:3301/get-started-with-signoz-cloud) on your local machine
|
||||
- Data source appears in the list
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
cd frontend && yarn run commitlint --edit $1
|
||||
cd frontend && pnpm run commitlint --edit $1
|
||||
|
||||
branch="$(git rev-parse --abbrev-ref HEAD)"
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
cd frontend && yarn lint-staged
|
||||
cd frontend && pnpm lint-staged
|
||||
|
||||
@@ -1 +1,4 @@
|
||||
registry = 'https://registry.npmjs.org/'
|
||||
registry = 'https://registry.npmjs.org/'
|
||||
|
||||
public-hoist-pattern[]=@commitlint*
|
||||
public-hoist-pattern[]=commitlint
|
||||
@@ -28,8 +28,8 @@ Follow the steps below
|
||||
1. ```git clone https://github.com/SigNoz/signoz.git && cd signoz/frontend```
|
||||
1. change baseURL to ```<test environment URL>``` in file ```src/constants/env.ts```
|
||||
|
||||
1. ```yarn install```
|
||||
1. ```yarn dev```
|
||||
1. ```pnpm install```
|
||||
1. ```pnpm dev```
|
||||
|
||||
```Note: Please ping us in #contributing channel in our slack community and we will DM you with <test environment URL>```
|
||||
|
||||
@@ -41,7 +41,7 @@ This project was bootstrapped with [Create React App](https://github.com/faceboo
|
||||
|
||||
In the project directory, you can run:
|
||||
|
||||
### `yarn start`
|
||||
### `pnpm start`
|
||||
|
||||
Runs the app in the development mode.\
|
||||
Open [http://localhost:3301](http://localhost:3301) to view it in the browser.
|
||||
@@ -49,12 +49,12 @@ Open [http://localhost:3301](http://localhost:3301) to view it in the browser.
|
||||
The page will reload if you make edits.\
|
||||
You will also see any lint errors in the console.
|
||||
|
||||
### `yarn test`
|
||||
### `pnpm test`
|
||||
|
||||
Launches the test runner in the interactive watch mode.\
|
||||
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
|
||||
|
||||
### `yarn build`
|
||||
### `pnpm build`
|
||||
|
||||
Builds the app for production to the `build` folder.\
|
||||
It correctly bundles React in production mode and optimizes the build for the best performance.
|
||||
@@ -64,7 +64,7 @@ Your app is ready to be deployed!
|
||||
|
||||
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
|
||||
|
||||
### `yarn eject`
|
||||
### `pnpm eject`
|
||||
|
||||
**Note: this is a one-way operation. Once you `eject`, you can’t go back!**
|
||||
|
||||
@@ -100,6 +100,6 @@ This section has moved here: [https://facebook.github.io/create-react-app/docs/a
|
||||
|
||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)
|
||||
|
||||
### `yarn build` fails to minify
|
||||
### `pnpm build` fails to minify
|
||||
|
||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)
|
||||
|
||||
@@ -3,7 +3,6 @@ import type { Config } from '@jest/types';
|
||||
const USE_SAFE_NAVIGATE_MOCK_PATH = '<rootDir>/__mocks__/useSafeNavigate.ts';
|
||||
|
||||
const config: Config.InitialOptions = {
|
||||
maxWorkers: '50%',
|
||||
silent: true,
|
||||
clearMocks: true,
|
||||
coverageDirectory: 'coverage',
|
||||
@@ -47,7 +46,11 @@ const config: Config.InitialOptions = {
|
||||
},
|
||||
transformIgnorePatterns: [
|
||||
// @chenglou/pretext is ESM-only; @signozhq/ui pulls it in via text-ellipsis.
|
||||
'node_modules/(?!(lodash-es|react-dnd|core-dnd|@react-dnd|dnd-core|react-dnd-html5-backend|axios|@chenglou/pretext|@signozhq/design-tokens|@signozhq/table|@signozhq/calendar|@signozhq/input|@signozhq/popover|@signozhq/*|date-fns|d3-interpolate|d3-color|api|@codemirror|@lezer|@marijn|@grafana|nuqs)/)',
|
||||
// Pattern 1: allow .pnpm virtual store through (handled by pattern 2), plus root-level ESM packages.
|
||||
'node_modules/(?!(\\.pnpm|lodash-es|react-dnd|core-dnd|@react-dnd|dnd-core|react-dnd-html5-backend|axios|@chenglou/pretext|@signozhq/design-tokens|@signozhq|date-fns|d3-interpolate|d3-color|api|@codemirror|@lezer|@marijn|@grafana|nuqs|uuid)/)',
|
||||
// Pattern 2: pnpm virtual store — ignore everything except ESM-only packages.
|
||||
// pnpm encodes scoped packages as @scope+name@version, so match on scope prefix.
|
||||
'node_modules/\\.pnpm/(?!(lodash-es|react-dnd|core-dnd|@react-dnd|dnd-core|react-dnd-html5-backend|axios|@chenglou|@signozhq|date-fns|d3-interpolate|d3-color|api|@codemirror|@lezer|@marijn|@grafana|nuqs|uuid)[^/]*/node_modules)',
|
||||
],
|
||||
setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
|
||||
testPathIgnorePatterns: ['/node_modules/', '/public/'],
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
* Adds custom matchers from the react testing library to all tests
|
||||
*/
|
||||
import '@testing-library/jest-dom';
|
||||
import '@testing-library/jest-dom/extend-expect';
|
||||
import 'jest-styled-components';
|
||||
|
||||
import { server } from './src/mocks-server/server';
|
||||
|
||||
@@ -80,7 +80,7 @@ export default defineConfig({
|
||||
header: (info: { title: string; version: string }): string[] => [
|
||||
`! Do not edit manually`,
|
||||
`* The file has been auto-generated using Orval for SigNoz`,
|
||||
`* regenerate with 'yarn generate:api'`,
|
||||
`* regenerate with 'pnpm generate:api'`,
|
||||
...(info.title ? [info.title] : []),
|
||||
...(info.version ? [`OpenAPI spec version: ${info.version}`] : []),
|
||||
],
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
"jest": "jest",
|
||||
"jest:coverage": "jest --coverage",
|
||||
"jest:watch": "jest --watch",
|
||||
"postinstall": "yarn i18n:generate-hash && (is-ci || yarn husky:configure) && node scripts/update-registry.cjs",
|
||||
"postinstall": "pnpm i18n:generate-hash && (is-ci || pnpm husky:configure) && node scripts/update-registry.cjs",
|
||||
"husky:configure": "cd .. && husky install frontend/.husky && cd frontend && chmod ug+x .husky/*",
|
||||
"commitlint": "commitlint --edit $1",
|
||||
"test": "jest",
|
||||
@@ -35,6 +35,8 @@
|
||||
"@ant-design/icons": "4.8.0",
|
||||
"@codemirror/autocomplete": "6.18.6",
|
||||
"@codemirror/lang-javascript": "6.2.3",
|
||||
"@codemirror/state": "6.5.2",
|
||||
"@codemirror/view": "6.36.6",
|
||||
"@dnd-kit/core": "6.1.0",
|
||||
"@dnd-kit/modifiers": "7.0.0",
|
||||
"@dnd-kit/sortable": "8.0.0",
|
||||
@@ -105,6 +107,7 @@
|
||||
"overlayscrollbars-react": "^0.5.6",
|
||||
"papaparse": "5.4.1",
|
||||
"posthog-js": "1.298.0",
|
||||
"rc-select": "14.10.0",
|
||||
"rc-tween-one": "3.0.6",
|
||||
"react": "18.2.0",
|
||||
"react-addons-update": "15.6.3",
|
||||
@@ -120,12 +123,10 @@
|
||||
"react-helmet-async": "1.3.0",
|
||||
"react-hook-form": "7.71.2",
|
||||
"react-i18next": "^11.16.1",
|
||||
"react-json-tree": "^0.20.0",
|
||||
"react-lottie": "1.2.10",
|
||||
"react-markdown": "8.0.7",
|
||||
"react-query": "3.39.3",
|
||||
"react-redux": "^7.2.2",
|
||||
"react-rnd": "^10.5.3",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"react-router-dom-v5-compat": "6.27.0",
|
||||
"react-syntax-highlighter": "15.5.0",
|
||||
@@ -168,8 +169,8 @@
|
||||
"@babel/preset-env": "^7.22.14",
|
||||
"@babel/preset-react": "^7.12.13",
|
||||
"@babel/preset-typescript": "^7.21.4",
|
||||
"@commitlint/cli": "^20.4.2",
|
||||
"@commitlint/config-conventional": "^20.4.2",
|
||||
"@commitlint/cli": "20.4.4",
|
||||
"@commitlint/config-conventional": "20.4.4",
|
||||
"@faker-js/faker": "9.3.0",
|
||||
"@jest/globals": "30.2.0",
|
||||
"@testing-library/jest-dom": "5.16.5",
|
||||
@@ -179,8 +180,11 @@
|
||||
"@types/crypto-js": "4.2.2",
|
||||
"@types/dompurify": "^2.4.0",
|
||||
"@types/event-source-polyfill": "^1.0.0",
|
||||
"@types/d3-hierarchy": "1.1.11",
|
||||
"@types/fontfaceobserver": "2.1.0",
|
||||
"@types/history": "4.7.11",
|
||||
"@types/jest": "30.0.0",
|
||||
"@jest/types": "30.2.0",
|
||||
"@types/lodash-es": "^4.17.4",
|
||||
"@types/mini-css-extract-plugin": "^2.5.1",
|
||||
"@types/node": "^16.10.3",
|
||||
@@ -198,11 +202,13 @@
|
||||
"@types/react-syntax-highlighter": "15.5.13",
|
||||
"@types/redux-mock-store": "1.0.4",
|
||||
"@types/styled-components": "^5.1.4",
|
||||
"@types/testing-library__jest-dom": "^5.14.5",
|
||||
"@types/uuid": "^8.3.1",
|
||||
"@typescript/native-preview": "7.0.0-dev.20260421.2",
|
||||
"@typescript/native-preview": "7.0.0-dev.20260430.1",
|
||||
"autoprefixer": "10.4.19",
|
||||
"babel-plugin-styled-components": "^1.12.0",
|
||||
"eslint-plugin-sonarjs": "4.0.2",
|
||||
"glob": "^13.0.6",
|
||||
"husky": "^7.0.4",
|
||||
"imagemin": "^8.0.1",
|
||||
"imagemin-svgo": "^10.0.1",
|
||||
@@ -241,12 +247,10 @@
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.(js|jsx|ts|tsx)": [
|
||||
"oxlint --fix",
|
||||
"oxfmt --write",
|
||||
"sh -c tsgo --noEmit"
|
||||
],
|
||||
"*.(js|jsx|ts|tsx|scss|css)": [
|
||||
"oxlint --fix --quiet --no-error-on-unmatched-pattern",
|
||||
"oxfmt --write"
|
||||
],
|
||||
"*.(scss|css)": [
|
||||
"stylelint"
|
||||
]
|
||||
|
||||
22845
frontend/pnpm-lock.yaml
generated
Normal file
22845
frontend/pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -16,7 +16,7 @@ echo "\n✅ Tag files renamed to index.ts"
|
||||
|
||||
# Format generated files
|
||||
echo "\n\n---\nRunning prettier...\n"
|
||||
if ! yarn prettify src/api/generated; then
|
||||
if ! pnpm prettify src/api/generated; then
|
||||
echo "Formatting failed!"
|
||||
exit 1
|
||||
fi
|
||||
@@ -25,7 +25,7 @@ echo "\n✅ Formatting successful"
|
||||
|
||||
# Fix linting issues
|
||||
echo "\n\n---\nRunning lint...\n"
|
||||
if ! yarn lint:generated; then
|
||||
if ! pnpm lint:generated; then
|
||||
echo "Lint check failed! Please fix linting errors before proceeding."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -65,13 +65,6 @@ export const TraceDetail = Loadable(
|
||||
),
|
||||
);
|
||||
|
||||
export const TraceDetailV3 = Loadable(
|
||||
() =>
|
||||
import(
|
||||
/* webpackChunkName: "TraceDetailV3 Page" */ 'pages/TraceDetailV3Page/index'
|
||||
),
|
||||
);
|
||||
|
||||
export const UsageExplorerPage = Loadable(
|
||||
() => import(/* webpackChunkName: "UsageExplorerPage" */ 'modules/Usage'),
|
||||
);
|
||||
|
||||
@@ -48,7 +48,6 @@ import {
|
||||
StatusPage,
|
||||
SupportPage,
|
||||
TraceDetail,
|
||||
TraceDetailV3,
|
||||
TraceFilter,
|
||||
TracesExplorer,
|
||||
TracesFunnelDetails,
|
||||
@@ -139,9 +138,6 @@ const routes: AppRoutes[] = [
|
||||
exact: true,
|
||||
key: 'LOGS_SAVE_VIEWS',
|
||||
},
|
||||
// V3 trace details is gated until release: /trace serves V2 (public),
|
||||
// /trace-old serves V3 (URL-only access). Flip the two `component`
|
||||
// values back to release V3.
|
||||
{
|
||||
path: ROUTES.TRACE_DETAIL,
|
||||
exact: true,
|
||||
@@ -149,13 +145,6 @@ const routes: AppRoutes[] = [
|
||||
isPrivate: true,
|
||||
key: 'TRACE_DETAIL',
|
||||
},
|
||||
{
|
||||
path: ROUTES.TRACE_DETAIL_OLD,
|
||||
exact: true,
|
||||
component: TraceDetailV3,
|
||||
isPrivate: true,
|
||||
key: 'TRACE_DETAIL_OLD',
|
||||
},
|
||||
{
|
||||
path: ROUTES.SETTINGS,
|
||||
exact: false,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* ! Do not edit manually
|
||||
* * The file has been auto-generated using Orval for SigNoz
|
||||
* * regenerate with 'yarn generate:api'
|
||||
* * regenerate with 'pnpm generate:api'
|
||||
* SigNoz
|
||||
*/
|
||||
import { useQuery } from 'react-query';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* ! Do not edit manually
|
||||
* * The file has been auto-generated using Orval for SigNoz
|
||||
* * regenerate with 'yarn generate:api'
|
||||
* * regenerate with 'pnpm generate:api'
|
||||
* SigNoz
|
||||
*/
|
||||
import { useMutation, useQuery } from 'react-query';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* ! Do not edit manually
|
||||
* * The file has been auto-generated using Orval for SigNoz
|
||||
* * regenerate with 'yarn generate:api'
|
||||
* * regenerate with 'pnpm generate:api'
|
||||
* SigNoz
|
||||
*/
|
||||
import { useMutation } from 'react-query';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* ! Do not edit manually
|
||||
* * The file has been auto-generated using Orval for SigNoz
|
||||
* * regenerate with 'yarn generate:api'
|
||||
* * regenerate with 'pnpm generate:api'
|
||||
* SigNoz
|
||||
*/
|
||||
import { useMutation, useQuery } from 'react-query';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* ! Do not edit manually
|
||||
* * The file has been auto-generated using Orval for SigNoz
|
||||
* * regenerate with 'yarn generate:api'
|
||||
* * regenerate with 'pnpm generate:api'
|
||||
* SigNoz
|
||||
*/
|
||||
import { useMutation, useQuery } from 'react-query';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* ! Do not edit manually
|
||||
* * The file has been auto-generated using Orval for SigNoz
|
||||
* * regenerate with 'yarn generate:api'
|
||||
* * regenerate with 'pnpm generate:api'
|
||||
* SigNoz
|
||||
*/
|
||||
import { useMutation, useQuery } from 'react-query';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* ! Do not edit manually
|
||||
* * The file has been auto-generated using Orval for SigNoz
|
||||
* * regenerate with 'yarn generate:api'
|
||||
* * regenerate with 'pnpm generate:api'
|
||||
* SigNoz
|
||||
*/
|
||||
import { useMutation, useQuery } from 'react-query';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* ! Do not edit manually
|
||||
* * The file has been auto-generated using Orval for SigNoz
|
||||
* * regenerate with 'yarn generate:api'
|
||||
* * regenerate with 'pnpm generate:api'
|
||||
* SigNoz
|
||||
*/
|
||||
import { useQuery } from 'react-query';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* ! Do not edit manually
|
||||
* * The file has been auto-generated using Orval for SigNoz
|
||||
* * regenerate with 'yarn generate:api'
|
||||
* * regenerate with 'pnpm generate:api'
|
||||
* SigNoz
|
||||
*/
|
||||
import { useQuery } from 'react-query';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* ! Do not edit manually
|
||||
* * The file has been auto-generated using Orval for SigNoz
|
||||
* * regenerate with 'yarn generate:api'
|
||||
* * regenerate with 'pnpm generate:api'
|
||||
* SigNoz
|
||||
*/
|
||||
import { useMutation, useQuery } from 'react-query';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* ! Do not edit manually
|
||||
* * The file has been auto-generated using Orval for SigNoz
|
||||
* * regenerate with 'yarn generate:api'
|
||||
* * regenerate with 'pnpm generate:api'
|
||||
* SigNoz
|
||||
*/
|
||||
import { useQuery } from 'react-query';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* ! Do not edit manually
|
||||
* * The file has been auto-generated using Orval for SigNoz
|
||||
* * regenerate with 'yarn generate:api'
|
||||
* * regenerate with 'pnpm generate:api'
|
||||
* SigNoz
|
||||
*/
|
||||
import { useQuery } from 'react-query';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* ! Do not edit manually
|
||||
* * The file has been auto-generated using Orval for SigNoz
|
||||
* * regenerate with 'yarn generate:api'
|
||||
* * regenerate with 'pnpm generate:api'
|
||||
* SigNoz
|
||||
*/
|
||||
import { useMutation } from 'react-query';
|
||||
@@ -13,10 +13,8 @@ import type {
|
||||
|
||||
import type {
|
||||
InframonitoringtypesPostableHostsDTO,
|
||||
InframonitoringtypesPostableNodesDTO,
|
||||
InframonitoringtypesPostablePodsDTO,
|
||||
ListHosts200,
|
||||
ListNodes200,
|
||||
ListPods200,
|
||||
RenderErrorResponseDTO,
|
||||
} from '../sigNoz.schemas';
|
||||
@@ -109,91 +107,7 @@ export const useListHosts = <
|
||||
return useMutation(mutationOptions);
|
||||
};
|
||||
/**
|
||||
* Returns a paginated list of Kubernetes nodes with key metrics: CPU usage, CPU allocatable, memory working set, memory allocatable, per-group nodeCountsByReadiness ({ ready, notReady } from each node's latest k8s.node.condition_ready in the window) and per-group podCountsByPhase ({ pending, running, succeeded, failed, unknown } for pods scheduled on the listed nodes). Each node includes metadata attributes (k8s.node.uid, k8s.cluster.name). The response type is 'list' for the default k8s.node.name grouping (each row is one node with its current condition string: ready / not_ready / no_data) or 'grouped_list' for custom groupBy keys (each row aggregates nodes in the group; condition stays no_data). Supports filtering via a filter expression, custom groupBy, ordering by cpu / cpu_allocatable / memory / memory_allocatable, and pagination via offset/limit. Also reports missing required metrics and whether the requested time range falls before the data retention boundary. Numeric metric fields (nodeCPU, nodeCPUAllocatable, nodeMemory, nodeMemoryAllocatable) return -1 as a sentinel when no data is available for that field.
|
||||
* @summary List Nodes for Infra Monitoring
|
||||
*/
|
||||
export const listNodes = (
|
||||
inframonitoringtypesPostableNodesDTO: BodyType<InframonitoringtypesPostableNodesDTO>,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<ListNodes200>({
|
||||
url: `/api/v2/infra_monitoring/nodes`,
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
data: inframonitoringtypesPostableNodesDTO,
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getListNodesMutationOptions = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof listNodes>>,
|
||||
TError,
|
||||
{ data: BodyType<InframonitoringtypesPostableNodesDTO> },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof listNodes>>,
|
||||
TError,
|
||||
{ data: BodyType<InframonitoringtypesPostableNodesDTO> },
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ['listNodes'];
|
||||
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 listNodes>>,
|
||||
{ data: BodyType<InframonitoringtypesPostableNodesDTO> }
|
||||
> = (props) => {
|
||||
const { data } = props ?? {};
|
||||
|
||||
return listNodes(data);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type ListNodesMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof listNodes>>
|
||||
>;
|
||||
export type ListNodesMutationBody =
|
||||
BodyType<InframonitoringtypesPostableNodesDTO>;
|
||||
export type ListNodesMutationError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary List Nodes for Infra Monitoring
|
||||
*/
|
||||
export const useListNodes = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof listNodes>>,
|
||||
TError,
|
||||
{ data: BodyType<InframonitoringtypesPostableNodesDTO> },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof listNodes>>,
|
||||
TError,
|
||||
{ data: BodyType<InframonitoringtypesPostableNodesDTO> },
|
||||
TContext
|
||||
> => {
|
||||
const mutationOptions = getListNodesMutationOptions(options);
|
||||
|
||||
return useMutation(mutationOptions);
|
||||
};
|
||||
/**
|
||||
* Returns a paginated list of Kubernetes pods with key metrics: CPU usage, CPU request/limit utilization, memory working set, memory request/limit utilization, current pod phase (pending/running/succeeded/failed/unknown/no_data), and pod age (ms since start time). Each pod includes metadata attributes (namespace, node, workload owner such as deployment/statefulset/daemonset/job/cronjob, cluster). Supports filtering via a filter expression, custom groupBy to aggregate pods by any attribute, ordering by any of the six metrics (cpu, cpu_request, cpu_limit, memory, memory_request, memory_limit), and pagination via offset/limit. The response type is 'list' for the default k8s.pod.uid grouping (each row is one pod with its current phase) or 'grouped_list' for custom groupBy keys (each row aggregates pods in the group with per-phase counts under podCountsByPhase: { pending, running, succeeded, failed, unknown } derived from each pod's latest phase in the window). Also reports missing required metrics and whether the requested time range falls before the data retention boundary. Numeric metric fields (podCPU, podCPURequest, podCPULimit, podMemory, podMemoryRequest, podMemoryLimit, podAge) return -1 as a sentinel when no data is available for that field.
|
||||
* Returns a paginated list of Kubernetes pods with key metrics: CPU usage, CPU request/limit utilization, memory working set, memory request/limit utilization, current pod phase (pending/running/succeeded/failed/unknown), and pod age (ms since start time). Each pod includes metadata attributes (namespace, node, workload owner such as deployment/statefulset/daemonset/job/cronjob, cluster). Supports filtering via a filter expression, custom groupBy to aggregate pods by any attribute, ordering by any of the six metrics (cpu, cpu_request, cpu_limit, memory, memory_request, memory_limit), and pagination via offset/limit. The response type is 'list' for the default k8s.pod.uid grouping (each row is one pod with its current phase) or 'grouped_list' for custom groupBy keys (each row aggregates pods in the group with per-phase counts: pendingPodCount, runningPodCount, succeededPodCount, failedPodCount, unknownPodCount derived from each pod's latest phase in the window). Also reports missing required metrics and whether the requested time range falls before the data retention boundary. Numeric metric fields (podCPU, podCPURequest, podCPULimit, podMemory, podMemoryRequest, podMemoryLimit, podAge) return -1 as a sentinel when no data is available for that field.
|
||||
* @summary List Pods for Infra Monitoring
|
||||
*/
|
||||
export const listPods = (
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* ! Do not edit manually
|
||||
* * The file has been auto-generated using Orval for SigNoz
|
||||
* * regenerate with 'yarn generate:api'
|
||||
* * regenerate with 'pnpm generate:api'
|
||||
* SigNoz
|
||||
*/
|
||||
import { useMutation, useQuery } from 'react-query';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* ! Do not edit manually
|
||||
* * The file has been auto-generated using Orval for SigNoz
|
||||
* * regenerate with 'yarn generate:api'
|
||||
* * regenerate with 'pnpm generate:api'
|
||||
* SigNoz
|
||||
*/
|
||||
import { useMutation, useQuery } from 'react-query';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* ! Do not edit manually
|
||||
* * The file has been auto-generated using Orval for SigNoz
|
||||
* * regenerate with 'yarn generate:api'
|
||||
* * regenerate with 'pnpm generate:api'
|
||||
* SigNoz
|
||||
*/
|
||||
import { useMutation, useQuery } from 'react-query';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* ! Do not edit manually
|
||||
* * The file has been auto-generated using Orval for SigNoz
|
||||
* * regenerate with 'yarn generate:api'
|
||||
* * regenerate with 'pnpm generate:api'
|
||||
* SigNoz
|
||||
*/
|
||||
import { useMutation, useQuery } from 'react-query';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* ! Do not edit manually
|
||||
* * The file has been auto-generated using Orval for SigNoz
|
||||
* * regenerate with 'yarn generate:api'
|
||||
* * regenerate with 'pnpm generate:api'
|
||||
* SigNoz
|
||||
*/
|
||||
import { useMutation, useQuery } from 'react-query';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* ! Do not edit manually
|
||||
* * The file has been auto-generated using Orval for SigNoz
|
||||
* * regenerate with 'yarn generate:api'
|
||||
* * regenerate with 'pnpm generate:api'
|
||||
* SigNoz
|
||||
*/
|
||||
import { useMutation } from 'react-query';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* ! Do not edit manually
|
||||
* * The file has been auto-generated using Orval for SigNoz
|
||||
* * regenerate with 'yarn generate:api'
|
||||
* * regenerate with 'pnpm generate:api'
|
||||
* SigNoz
|
||||
*/
|
||||
import { useMutation, useQuery } from 'react-query';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* ! Do not edit manually
|
||||
* * The file has been auto-generated using Orval for SigNoz
|
||||
* * regenerate with 'yarn generate:api'
|
||||
* * regenerate with 'pnpm generate:api'
|
||||
* SigNoz
|
||||
*/
|
||||
import { useMutation, useQuery } from 'react-query';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* ! Do not edit manually
|
||||
* * The file has been auto-generated using Orval for SigNoz
|
||||
* * regenerate with 'yarn generate:api'
|
||||
* * regenerate with 'pnpm generate:api'
|
||||
* SigNoz
|
||||
*/
|
||||
import { useMutation, useQuery } from 'react-query';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* ! Do not edit manually
|
||||
* * The file has been auto-generated using Orval for SigNoz
|
||||
* * regenerate with 'yarn generate:api'
|
||||
* * regenerate with 'pnpm generate:api'
|
||||
* SigNoz
|
||||
*/
|
||||
import { useMutation, useQuery } from 'react-query';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* ! Do not edit manually
|
||||
* * The file has been auto-generated using Orval for SigNoz
|
||||
* * regenerate with 'yarn generate:api'
|
||||
* * regenerate with 'pnpm generate:api'
|
||||
* SigNoz
|
||||
*/
|
||||
import { useMutation, useQuery } from 'react-query';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* ! Do not edit manually
|
||||
* * The file has been auto-generated using Orval for SigNoz
|
||||
* * regenerate with 'yarn generate:api'
|
||||
* * regenerate with 'pnpm generate:api'
|
||||
* SigNoz
|
||||
*/
|
||||
export interface AlertmanagertypesChannelDTO {
|
||||
@@ -4579,7 +4579,7 @@ export interface InframonitoringtypesHostFilterDTO {
|
||||
* @nullable
|
||||
*/
|
||||
export type InframonitoringtypesHostRecordDTOMeta = {
|
||||
[key: string]: string;
|
||||
[key: string]: unknown;
|
||||
} | null;
|
||||
|
||||
export interface InframonitoringtypesHostRecordDTO {
|
||||
@@ -4652,127 +4652,35 @@ export interface InframonitoringtypesHostsDTO {
|
||||
warning?: Querybuildertypesv5QueryWarnDataDTO;
|
||||
}
|
||||
|
||||
export enum InframonitoringtypesNodeConditionDTO {
|
||||
ready = 'ready',
|
||||
not_ready = 'not_ready',
|
||||
no_data = 'no_data',
|
||||
}
|
||||
export interface InframonitoringtypesNodeCountsByReadinessDTO {
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
notReady: number;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
ready: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* @nullable
|
||||
*/
|
||||
export type InframonitoringtypesNodeRecordDTOMeta = {
|
||||
[key: string]: string;
|
||||
} | null;
|
||||
|
||||
export interface InframonitoringtypesNodeRecordDTO {
|
||||
condition: InframonitoringtypesNodeConditionDTO;
|
||||
/**
|
||||
* @type object
|
||||
* @nullable true
|
||||
*/
|
||||
meta: InframonitoringtypesNodeRecordDTOMeta;
|
||||
/**
|
||||
* @type number
|
||||
* @format double
|
||||
*/
|
||||
nodeCPU: number;
|
||||
/**
|
||||
* @type number
|
||||
* @format double
|
||||
*/
|
||||
nodeCPUAllocatable: number;
|
||||
nodeCountsByReadiness: InframonitoringtypesNodeCountsByReadinessDTO;
|
||||
/**
|
||||
* @type number
|
||||
* @format double
|
||||
*/
|
||||
nodeMemory: number;
|
||||
/**
|
||||
* @type number
|
||||
* @format double
|
||||
*/
|
||||
nodeMemoryAllocatable: number;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
nodeName: string;
|
||||
podCountsByPhase: InframonitoringtypesPodCountsByPhaseDTO;
|
||||
}
|
||||
|
||||
export interface InframonitoringtypesNodesDTO {
|
||||
/**
|
||||
* @type boolean
|
||||
*/
|
||||
endTimeBeforeRetention: boolean;
|
||||
/**
|
||||
* @type array
|
||||
* @nullable true
|
||||
*/
|
||||
records: InframonitoringtypesNodeRecordDTO[] | null;
|
||||
requiredMetricsCheck: InframonitoringtypesRequiredMetricsCheckDTO;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
total: number;
|
||||
type: InframonitoringtypesResponseTypeDTO;
|
||||
warning?: Querybuildertypesv5QueryWarnDataDTO;
|
||||
}
|
||||
|
||||
export interface InframonitoringtypesPodCountsByPhaseDTO {
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
failed: number;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
pending: number;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
running: number;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
succeeded: number;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
unknown: number;
|
||||
}
|
||||
|
||||
export enum InframonitoringtypesPodPhaseDTO {
|
||||
pending = 'pending',
|
||||
running = 'running',
|
||||
succeeded = 'succeeded',
|
||||
failed = 'failed',
|
||||
unknown = 'unknown',
|
||||
no_data = 'no_data',
|
||||
'' = '',
|
||||
}
|
||||
/**
|
||||
* @nullable
|
||||
*/
|
||||
export type InframonitoringtypesPodRecordDTOMeta = {
|
||||
[key: string]: string;
|
||||
[key: string]: unknown;
|
||||
} | null;
|
||||
|
||||
export interface InframonitoringtypesPodRecordDTO {
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
failedPodCount: number;
|
||||
/**
|
||||
* @type object
|
||||
* @nullable true
|
||||
*/
|
||||
meta: InframonitoringtypesPodRecordDTOMeta;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
pendingPodCount: number;
|
||||
/**
|
||||
* @type integer
|
||||
* @format int64
|
||||
@@ -4793,7 +4701,6 @@ export interface InframonitoringtypesPodRecordDTO {
|
||||
* @format double
|
||||
*/
|
||||
podCPURequest: number;
|
||||
podCountsByPhase: InframonitoringtypesPodCountsByPhaseDTO;
|
||||
/**
|
||||
* @type number
|
||||
* @format double
|
||||
@@ -4814,6 +4721,18 @@ export interface InframonitoringtypesPodRecordDTO {
|
||||
* @type string
|
||||
*/
|
||||
podUID: string;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
runningPodCount: number;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
succeededPodCount: number;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
unknownPodCount: number;
|
||||
}
|
||||
|
||||
export interface InframonitoringtypesPodsDTO {
|
||||
@@ -4863,34 +4782,6 @@ export interface InframonitoringtypesPostableHostsDTO {
|
||||
start: number;
|
||||
}
|
||||
|
||||
export interface InframonitoringtypesPostableNodesDTO {
|
||||
/**
|
||||
* @type integer
|
||||
* @format int64
|
||||
*/
|
||||
end: number;
|
||||
filter?: Querybuildertypesv5FilterDTO;
|
||||
/**
|
||||
* @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 InframonitoringtypesPostablePodsDTO {
|
||||
/**
|
||||
* @type integer
|
||||
@@ -9242,14 +9133,6 @@ export type ListHosts200 = {
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type ListNodes200 = {
|
||||
data: InframonitoringtypesNodesDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type ListPods200 = {
|
||||
data: InframonitoringtypesPodsDTO;
|
||||
/**
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* ! Do not edit manually
|
||||
* * The file has been auto-generated using Orval for SigNoz
|
||||
* * regenerate with 'yarn generate:api'
|
||||
* * regenerate with 'pnpm generate:api'
|
||||
* SigNoz
|
||||
*/
|
||||
import { useMutation, useQuery } from 'react-query';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* ! Do not edit manually
|
||||
* * The file has been auto-generated using Orval for SigNoz
|
||||
* * regenerate with 'yarn generate:api'
|
||||
* * regenerate with 'pnpm generate:api'
|
||||
* SigNoz
|
||||
*/
|
||||
import { useMutation } from 'react-query';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* ! Do not edit manually
|
||||
* * The file has been auto-generated using Orval for SigNoz
|
||||
* * regenerate with 'yarn generate:api'
|
||||
* * regenerate with 'pnpm generate:api'
|
||||
* SigNoz
|
||||
*/
|
||||
import { useMutation, useQuery } from 'react-query';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* ! Do not edit manually
|
||||
* * The file has been auto-generated using Orval for SigNoz
|
||||
* * regenerate with 'yarn generate:api'
|
||||
* * regenerate with 'pnpm generate:api'
|
||||
* SigNoz
|
||||
*/
|
||||
import { useMutation, useQuery } from 'react-query';
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
import { ApiV3Instance as axios } from 'api';
|
||||
import { omit } from 'lodash-es';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import {
|
||||
GetTraceV3PayloadProps,
|
||||
GetTraceV3SuccessResponse,
|
||||
SpanV3,
|
||||
} from 'types/api/trace/getTraceV3';
|
||||
|
||||
const getTraceV3 = async (
|
||||
props: GetTraceV3PayloadProps,
|
||||
): Promise<SuccessResponse<GetTraceV3SuccessResponse> | ErrorResponse> => {
|
||||
let uncollapsedSpans = [...props.uncollapsedSpans];
|
||||
if (!props.isSelectedSpanIDUnCollapsed) {
|
||||
uncollapsedSpans = uncollapsedSpans.filter(
|
||||
(node) => node !== props.selectedSpanId,
|
||||
);
|
||||
} else if (
|
||||
props.selectedSpanId &&
|
||||
!uncollapsedSpans.includes(props.selectedSpanId)
|
||||
) {
|
||||
// V3 backend only uses uncollapsedSpans list (unlike V2 which also interprets
|
||||
// isSelectedSpanIDUnCollapsed server-side), so explicitly add the selected span
|
||||
uncollapsedSpans.push(props.selectedSpanId);
|
||||
}
|
||||
const postData: GetTraceV3PayloadProps = {
|
||||
...props,
|
||||
uncollapsedSpans,
|
||||
limit: 10000,
|
||||
};
|
||||
const response = await axios.post<GetTraceV3SuccessResponse>(
|
||||
`/traces/${props.traceId}/waterfall`,
|
||||
omit(postData, 'traceId'),
|
||||
);
|
||||
|
||||
// V3 API wraps response in { status, data }
|
||||
const rawPayload = (response.data as any).data || response.data;
|
||||
|
||||
// Derive 'service.name' from resource for convenience — only derived field
|
||||
const spans: SpanV3[] = (rawPayload.spans || []).map((span: any) => ({
|
||||
...span,
|
||||
'service.name': span.resource?.['service.name'] || '',
|
||||
}));
|
||||
|
||||
// V3 API returns startTimestampMillis/endTimestampMillis as relative durations (ms from epoch offset),
|
||||
// not absolute unix millis like V2. The span timestamps are absolute unix millis.
|
||||
// Convert by using the first span's timestamp as the base if there's a mismatch.
|
||||
let { startTimestampMillis, endTimestampMillis } = rawPayload;
|
||||
if (
|
||||
spans.length > 0 &&
|
||||
spans[0].timestamp > 0 &&
|
||||
startTimestampMillis < spans[0].timestamp / 10
|
||||
) {
|
||||
const durationMillis = endTimestampMillis - startTimestampMillis;
|
||||
startTimestampMillis = spans[0].timestamp;
|
||||
endTimestampMillis = startTimestampMillis + durationMillis;
|
||||
}
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: 'Success',
|
||||
payload: {
|
||||
...rawPayload,
|
||||
spans,
|
||||
startTimestampMillis,
|
||||
endTimestampMillis,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export default getTraceV3;
|
||||
@@ -1,37 +0,0 @@
|
||||
.details-header {
|
||||
// ghost + secondary missing hover bg token in @signozhq/button
|
||||
--button-ghost-hover-background: var(--l3-background);
|
||||
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 0 12px;
|
||||
border-bottom: 1px solid var(--l1-border);
|
||||
height: 36px;
|
||||
background: var(--l2-background);
|
||||
|
||||
&__title {
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: var(--l1-foreground);
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
&__actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
&__nav {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
import { ReactNode } from 'react';
|
||||
import { Button } from '@signozhq/ui';
|
||||
import { X } from '@signozhq/icons';
|
||||
|
||||
import './DetailsHeader.styles.scss';
|
||||
|
||||
export interface HeaderAction {
|
||||
key: string;
|
||||
component: ReactNode; // check later if we can use direct btn itself or not.
|
||||
}
|
||||
|
||||
export interface DetailsHeaderProps {
|
||||
title: string;
|
||||
onClose: () => void;
|
||||
actions?: HeaderAction[];
|
||||
closePosition?: 'left' | 'right';
|
||||
className?: string;
|
||||
}
|
||||
|
||||
function DetailsHeader({
|
||||
title,
|
||||
onClose,
|
||||
actions,
|
||||
closePosition = 'right',
|
||||
className,
|
||||
}: DetailsHeaderProps): JSX.Element {
|
||||
const closeButton = (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
color="secondary"
|
||||
onClick={onClose}
|
||||
aria-label="Close"
|
||||
prefix={<X size={14} />}
|
||||
></Button>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={`details-header ${className || ''}`}>
|
||||
{closePosition === 'left' && closeButton}
|
||||
|
||||
<span className="details-header__title">{title}</span>
|
||||
|
||||
{actions && (
|
||||
<div className="details-header__actions">
|
||||
{actions.map((action) => (
|
||||
<div key={action.key}>{action.component}</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{closePosition === 'right' && closeButton}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default DetailsHeader;
|
||||
@@ -1,7 +0,0 @@
|
||||
.details-panel-drawer {
|
||||
&__body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
import { DrawerWrapper } from '@signozhq/ui';
|
||||
|
||||
import './DetailsPanelDrawer.styles.scss';
|
||||
|
||||
interface DetailsPanelDrawerProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
function DetailsPanelDrawer({
|
||||
isOpen,
|
||||
onClose,
|
||||
children,
|
||||
className,
|
||||
}: DetailsPanelDrawerProps): JSX.Element {
|
||||
return (
|
||||
<DrawerWrapper
|
||||
open={isOpen}
|
||||
onOpenChange={(open): void => {
|
||||
if (!open) {
|
||||
onClose();
|
||||
}
|
||||
}}
|
||||
direction="right"
|
||||
showOverlay={false}
|
||||
className={`details-panel-drawer ${className || ''}`}
|
||||
>
|
||||
<div className="details-panel-drawer__body">{children}</div>
|
||||
</DrawerWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
export default DetailsPanelDrawer;
|
||||
@@ -1,8 +0,0 @@
|
||||
export type {
|
||||
DetailsHeaderProps,
|
||||
HeaderAction,
|
||||
} from './DetailsHeader/DetailsHeader';
|
||||
export { default as DetailsHeader } from './DetailsHeader/DetailsHeader';
|
||||
export { default as DetailsPanelDrawer } from './DetailsPanelDrawer';
|
||||
export type { DetailsPanelState, UseDetailsPanelOptions } from './types';
|
||||
export { default as useDetailsPanel } from './useDetailsPanel';
|
||||
@@ -1,10 +0,0 @@
|
||||
export interface DetailsPanelState {
|
||||
isOpen: boolean;
|
||||
open: () => void;
|
||||
close: () => void;
|
||||
}
|
||||
|
||||
export interface UseDetailsPanelOptions {
|
||||
entityId: string | undefined;
|
||||
onClose?: () => void;
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
|
||||
import { DetailsPanelState, UseDetailsPanelOptions } from './types';
|
||||
|
||||
function useDetailsPanel({
|
||||
entityId,
|
||||
onClose,
|
||||
}: UseDetailsPanelOptions): DetailsPanelState {
|
||||
const [isOpen, setIsOpen] = useState<boolean>(false);
|
||||
const prevEntityIdRef = useRef<string>('');
|
||||
|
||||
useEffect(() => {
|
||||
const currentId = entityId || '';
|
||||
if (currentId && currentId !== prevEntityIdRef.current) {
|
||||
setIsOpen(true);
|
||||
}
|
||||
prevEntityIdRef.current = currentId;
|
||||
}, [entityId]);
|
||||
|
||||
const open = useCallback(() => setIsOpen(true), []);
|
||||
const close = useCallback(() => {
|
||||
setIsOpen(false);
|
||||
onClose?.();
|
||||
}, [onClose]);
|
||||
|
||||
return { isOpen, open, close };
|
||||
}
|
||||
|
||||
export default useDetailsPanel;
|
||||
@@ -17,6 +17,7 @@ import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import ROUTES from 'constants/routes';
|
||||
import ContextView from 'container/LogDetailedView/ContextView/ContextView';
|
||||
import InfraMetrics from 'container/LogDetailedView/InfraMetrics/InfraMetrics';
|
||||
import JSONView from 'container/LogDetailedView/JsonView';
|
||||
import Overview from 'container/LogDetailedView/Overview';
|
||||
import {
|
||||
aggregateAttributesResourcesToString,
|
||||
@@ -46,7 +47,6 @@ import {
|
||||
TextSelect,
|
||||
X,
|
||||
} from 'lucide-react';
|
||||
import { JsonView } from 'periscope/components/JsonView';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { Query, TagFilter } from 'types/api/queryBuilder/queryBuilderData';
|
||||
@@ -562,9 +562,7 @@ function LogDetailInner({
|
||||
handleChangeSelectedView={handleChangeSelectedView}
|
||||
/>
|
||||
)}
|
||||
{selectedView === VIEW_TYPES.JSON && (
|
||||
<JsonView data={LogJsonData} height="68vh" />
|
||||
)}
|
||||
{selectedView === VIEW_TYPES.JSON && <JSONView logData={log} />}
|
||||
|
||||
{selectedView === VIEW_TYPES.CONTEXT && (
|
||||
<ContextView
|
||||
|
||||
@@ -589,16 +589,6 @@ function TanStackTableInner<TData>(
|
||||
{showPagination && pagination && (
|
||||
<div className={cx(viewStyles.paginationContainer, paginationClassname)}>
|
||||
{prefixPaginationContent}
|
||||
{pagination.showTotalCount && effectiveTotalCount > 0 && (
|
||||
<span
|
||||
className={viewStyles.paginationTotalCount}
|
||||
data-testid="pagination-total-count"
|
||||
>
|
||||
Showing {(page - 1) * limit + 1} -{' '}
|
||||
{Math.min(page * limit, effectiveTotalCount)} of {effectiveTotalCount}
|
||||
{pagination.totalCountLabel ? ` ${pagination.totalCountLabel}` : ''}
|
||||
</span>
|
||||
)}
|
||||
<Pagination
|
||||
current={page}
|
||||
pageSize={limit}
|
||||
|
||||
@@ -117,10 +117,6 @@
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
margin-top: 12px;
|
||||
|
||||
ul {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.paginationPageSize {
|
||||
@@ -128,11 +124,6 @@
|
||||
--combobox-trigger-height: 2rem;
|
||||
}
|
||||
|
||||
.paginationTotalCount {
|
||||
font-size: var(--periscope-font-size-base);
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
|
||||
.tanstackLoadingOverlay {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
|
||||
@@ -188,87 +188,6 @@ describe('TanStackTableView Integration', () => {
|
||||
expect(screen.getByTestId('suffix-content')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('renders total count when showTotalCount is true', async () => {
|
||||
renderTanStackTable({
|
||||
props: {
|
||||
pagination: {
|
||||
total: 100,
|
||||
defaultPage: 1,
|
||||
defaultLimit: 10,
|
||||
showTotalCount: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
const totalCount = screen.getByTestId('pagination-total-count');
|
||||
expect(totalCount).toBeInTheDocument();
|
||||
expect(totalCount).toHaveTextContent('Showing 1 - 10 of 100');
|
||||
});
|
||||
});
|
||||
|
||||
it('renders total count with label when totalCountLabel is provided', async () => {
|
||||
renderTanStackTable({
|
||||
props: {
|
||||
pagination: {
|
||||
total: 50,
|
||||
defaultPage: 1,
|
||||
defaultLimit: 10,
|
||||
showTotalCount: true,
|
||||
totalCountLabel: 'Pods',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
const totalCount = screen.getByTestId('pagination-total-count');
|
||||
expect(totalCount).toBeInTheDocument();
|
||||
expect(totalCount).toHaveTextContent('Showing 1 - 10 of 50 Pods');
|
||||
});
|
||||
});
|
||||
|
||||
it('does not render total count when showTotalCount is false', async () => {
|
||||
renderTanStackTable({
|
||||
props: {
|
||||
pagination: {
|
||||
total: 100,
|
||||
defaultPage: 1,
|
||||
defaultLimit: 10,
|
||||
showTotalCount: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole('navigation')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
expect(
|
||||
screen.queryByTestId('pagination-total-count'),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('does not render total count when total is 0', async () => {
|
||||
renderTanStackTable({
|
||||
props: {
|
||||
pagination: {
|
||||
total: 0,
|
||||
defaultPage: 1,
|
||||
defaultLimit: 10,
|
||||
showTotalCount: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole('table')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
expect(
|
||||
screen.queryByTestId('pagination-total-count'),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('sorting', () => {
|
||||
|
||||
@@ -398,7 +398,7 @@ describe('useTableParams (selective URL mode — partial config object)', () =>
|
||||
.filter(Boolean)
|
||||
.pop();
|
||||
expect(lastExpanded).toBeDefined();
|
||||
expect(JSON.parse(lastExpanded!)).toEqual(
|
||||
expect(JSON.parse(lastExpanded!)).toStrictEqual(
|
||||
expect.arrayContaining(['row-1', 'row-2']),
|
||||
);
|
||||
|
||||
|
||||
@@ -115,8 +115,6 @@ export type PaginationProps = {
|
||||
total: number;
|
||||
defaultPage?: number;
|
||||
defaultLimit?: number;
|
||||
showTotalCount?: boolean;
|
||||
totalCountLabel?: string;
|
||||
};
|
||||
|
||||
export type TanstackTableQueryParamsConfig = {
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
.timeline-v3-container {
|
||||
overflow: visible;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.timeline-v3-cursor-badge {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
transform: translateX(-50%);
|
||||
background: var(--l3-background);
|
||||
border: 1px solid var(--l2-border);
|
||||
border-radius: 4px;
|
||||
padding: 2px 8px;
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
color: var(--l1-foreground);
|
||||
white-space: nowrap;
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
}
|
||||
@@ -1,117 +0,0 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useMeasure } from 'react-use';
|
||||
import { resolveTimeFromInterval } from 'components/TimelineV2/utils';
|
||||
import { toFixed } from 'utils/toFixed';
|
||||
|
||||
import {
|
||||
getIntervals,
|
||||
getIntervalUnit,
|
||||
getMinimumIntervalsBasedOnWidth,
|
||||
Interval,
|
||||
} from './utils';
|
||||
|
||||
import './TimelineV3.styles.scss';
|
||||
|
||||
interface ITimelineV3Props {
|
||||
startTimestamp: number;
|
||||
endTimestamp: number;
|
||||
timelineHeight: number;
|
||||
offsetTimestamp: number;
|
||||
/** Cursor X as a fraction of the timeline width (0–1). null = no cursor. */
|
||||
cursorXPercent?: number | null;
|
||||
}
|
||||
|
||||
function TimelineV3(props: ITimelineV3Props): JSX.Element {
|
||||
const {
|
||||
startTimestamp,
|
||||
endTimestamp,
|
||||
timelineHeight,
|
||||
offsetTimestamp,
|
||||
cursorXPercent,
|
||||
} = props;
|
||||
const [intervals, setIntervals] = useState<Interval[]>([]);
|
||||
const [ref, { width }] = useMeasure<HTMLDivElement>();
|
||||
|
||||
const spread = endTimestamp - startTimestamp;
|
||||
|
||||
useEffect(() => {
|
||||
if (spread < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const minIntervals = getMinimumIntervalsBasedOnWidth(width);
|
||||
const intervalisedSpread = (spread / minIntervals) * 1.0;
|
||||
const newIntervals = getIntervals(
|
||||
intervalisedSpread,
|
||||
spread,
|
||||
offsetTimestamp,
|
||||
);
|
||||
|
||||
setIntervals(newIntervals);
|
||||
}, [startTimestamp, endTimestamp, width, offsetTimestamp, spread]);
|
||||
|
||||
// Compute cursor time label using the same unit as timeline ticks
|
||||
const cursorLabel = useMemo(() => {
|
||||
if (cursorXPercent == null || spread <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const timeAtCursor = offsetTimestamp + cursorXPercent * spread;
|
||||
const unit = getIntervalUnit(spread, offsetTimestamp);
|
||||
const formatted = toFixed(resolveTimeFromInterval(timeAtCursor, unit), 2);
|
||||
return `${formatted}${unit.name}`;
|
||||
}, [cursorXPercent, spread, offsetTimestamp]);
|
||||
|
||||
if (endTimestamp < startTimestamp) {
|
||||
console.error(
|
||||
'endTimestamp cannot be less than startTimestamp',
|
||||
startTimestamp,
|
||||
endTimestamp,
|
||||
);
|
||||
return <></>;
|
||||
}
|
||||
|
||||
const strokeColor = 'var(--l3-foreground)';
|
||||
const svgHeight = timelineHeight * 2.5;
|
||||
const cursorX = cursorXPercent != null ? cursorXPercent * width : null;
|
||||
|
||||
return (
|
||||
<div ref={ref as never} className="timeline-v3-container">
|
||||
<svg
|
||||
width={width}
|
||||
height={svgHeight}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
overflow="visible"
|
||||
>
|
||||
{intervals &&
|
||||
intervals.length > 0 &&
|
||||
intervals.map((interval, index) => (
|
||||
<g
|
||||
transform={`translate(${(interval.percentage * width) / 100},0)`}
|
||||
key={`${interval.percentage + interval.label + index}`}
|
||||
textAnchor="middle"
|
||||
fontSize="0.6rem"
|
||||
>
|
||||
<text
|
||||
x={index === intervals.length - 1 ? -10 : 0}
|
||||
y={timelineHeight * 2}
|
||||
fill={strokeColor}
|
||||
>
|
||||
{interval.label}
|
||||
</text>
|
||||
<line y1={0} y2={timelineHeight} stroke={strokeColor} strokeWidth="1" />
|
||||
</g>
|
||||
))}
|
||||
</svg>
|
||||
|
||||
{/* Cursor time badge — DOM element for easy CSS styling */}
|
||||
{cursorX !== null && cursorLabel && (
|
||||
<div className="timeline-v3-cursor-badge" style={{ left: cursorX }}>
|
||||
{cursorLabel}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default TimelineV3;
|
||||
@@ -1,109 +0,0 @@
|
||||
import {
|
||||
IIntervalUnit,
|
||||
Interval,
|
||||
INTERVAL_UNITS,
|
||||
resolveTimeFromInterval,
|
||||
} from 'components/TimelineV2/utils';
|
||||
import { toFixed } from 'utils/toFixed';
|
||||
|
||||
export type { Interval };
|
||||
|
||||
/**
|
||||
* Select the interval unit matching the timeline's logic.
|
||||
* Exported so crosshair labels use the same unit as timeline ticks.
|
||||
*/
|
||||
export function getIntervalUnit(
|
||||
spread: number,
|
||||
offsetTimestamp: number,
|
||||
): IIntervalUnit {
|
||||
const minIntervals = 6;
|
||||
const intervalSpread = spread / minIntervals;
|
||||
const valueForUnitSelection = Math.max(offsetTimestamp, intervalSpread);
|
||||
let unit: IIntervalUnit = INTERVAL_UNITS[0];
|
||||
for (let idx = INTERVAL_UNITS.length - 1; idx >= 0; idx -= 1) {
|
||||
if (valueForUnitSelection * INTERVAL_UNITS[idx].multiplier >= 1) {
|
||||
unit = INTERVAL_UNITS[idx];
|
||||
break;
|
||||
}
|
||||
}
|
||||
return unit;
|
||||
}
|
||||
|
||||
/** Fewer intervals than TimelineV2 for a cleaner flamegraph ruler. */
|
||||
export function getMinimumIntervalsBasedOnWidth(width: number): number {
|
||||
if (width < 640) {
|
||||
return 3;
|
||||
}
|
||||
if (width < 768) {
|
||||
return 4;
|
||||
}
|
||||
if (width < 1024) {
|
||||
return 5;
|
||||
}
|
||||
return 6;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes timeline intervals with offset-aware labels.
|
||||
* Labels reflect absolute time from trace start (offsetTimestamp + elapsed),
|
||||
* so when zoomed into a window, the first tick shows e.g. "50ms" not "0ms".
|
||||
*/
|
||||
export function getIntervals(
|
||||
intervalSpread: number,
|
||||
baseSpread: number,
|
||||
offsetTimestamp: number,
|
||||
): Interval[] {
|
||||
const integerPartString = intervalSpread.toString().split('.')[0];
|
||||
const integerPartLength = integerPartString.length;
|
||||
|
||||
const intervalSpreadNormalized =
|
||||
intervalSpread < 1.0
|
||||
? intervalSpread
|
||||
: Math.floor(Number(integerPartString) / 10 ** (integerPartLength - 1)) *
|
||||
10 ** (integerPartLength - 1);
|
||||
|
||||
// Unit must suit both: (1) tick granularity (intervalSpread) and (2) label magnitude
|
||||
// (offsetTimestamp). When zoomed deep into a trace, labels show offsetTimestamp + elapsed,
|
||||
// so we must pick a unit where that value is readable (e.g. "500.00s" not "500000.00ms").
|
||||
const valueForUnitSelection = Math.max(offsetTimestamp, intervalSpread);
|
||||
let intervalUnit: IIntervalUnit = INTERVAL_UNITS[0];
|
||||
for (let idx = INTERVAL_UNITS.length - 1; idx >= 0; idx -= 1) {
|
||||
const standardInterval = INTERVAL_UNITS[idx];
|
||||
if (valueForUnitSelection * standardInterval.multiplier >= 1) {
|
||||
intervalUnit = INTERVAL_UNITS[idx];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const intervals: Interval[] = [
|
||||
{
|
||||
label: `${toFixed(
|
||||
resolveTimeFromInterval(offsetTimestamp, intervalUnit),
|
||||
2,
|
||||
)}${intervalUnit.name}`,
|
||||
percentage: 0,
|
||||
},
|
||||
];
|
||||
|
||||
// Only show even-interval ticks — skip the trailing partial tick at the edge.
|
||||
// The last even tick sits before the full width, so it doesn't conflict with
|
||||
// span duration labels that may have sub-millisecond precision.
|
||||
let elapsedIntervals = 0;
|
||||
|
||||
while (
|
||||
elapsedIntervals + intervalSpreadNormalized <= baseSpread &&
|
||||
intervals.length < 20
|
||||
) {
|
||||
elapsedIntervals += intervalSpreadNormalized;
|
||||
const labelTime = offsetTimestamp + elapsedIntervals;
|
||||
|
||||
intervals.push({
|
||||
label: `${toFixed(resolveTimeFromInterval(labelTime, intervalUnit), 2)}${
|
||||
intervalUnit.name
|
||||
}`,
|
||||
percentage: (elapsedIntervals / baseSpread) * 100,
|
||||
});
|
||||
}
|
||||
|
||||
return intervals;
|
||||
}
|
||||
@@ -37,7 +37,6 @@ export enum LOCALSTORAGE {
|
||||
SHOW_FREQUENCY_CHART = 'SHOW_FREQUENCY_CHART',
|
||||
DISSMISSED_COST_METER_INFO = 'DISMISSED_COST_METER_INFO',
|
||||
DISMISSED_API_KEYS_DEPRECATION_BANNER = 'DISMISSED_API_KEYS_DEPRECATION_BANNER',
|
||||
TRACE_DETAILS_SPAN_DETAILS_POSITION = 'TRACE_DETAILS_SPAN_DETAILS_POSITION',
|
||||
LICENSE_KEY_CALLOUT_DISMISSED = 'LICENSE_KEY_CALLOUT_DISMISSED',
|
||||
DASHBOARD_PREFERENCES = 'DASHBOARD_PREFERENCES',
|
||||
}
|
||||
|
||||
@@ -33,7 +33,6 @@ export const REACT_QUERY_KEY = {
|
||||
UPDATE_ALERT_RULE: 'UPDATE_ALERT_RULE',
|
||||
GET_ACTIVE_LICENSE_V3: 'GET_ACTIVE_LICENSE_V3',
|
||||
GET_TRACE_V2_WATERFALL: 'GET_TRACE_V2_WATERFALL',
|
||||
GET_TRACE_V3_WATERFALL: 'GET_TRACE_V3_WATERFALL',
|
||||
GET_TRACE_V2_FLAMEGRAPH: 'GET_TRACE_V2_FLAMEGRAPH',
|
||||
GET_POD_LIST: 'GET_POD_LIST',
|
||||
GET_NODE_LIST: 'GET_NODE_LIST',
|
||||
|
||||
@@ -8,7 +8,6 @@ const ROUTES = {
|
||||
SERVICE_MAP: '/service-map',
|
||||
TRACE: '/trace',
|
||||
TRACE_DETAIL: '/trace/:id',
|
||||
TRACE_DETAIL_OLD: '/trace-old/:id',
|
||||
TRACES_EXPLORER: '/traces-explorer',
|
||||
ONBOARDING: '/onboarding',
|
||||
GET_STARTED: '/get-started',
|
||||
|
||||
@@ -33,102 +33,6 @@ const themeColors = {
|
||||
purple: '#800080',
|
||||
cyan: '#00FFFF',
|
||||
},
|
||||
traceDetailColorsV3: {
|
||||
// Blues
|
||||
blue1: '#2F80ED',
|
||||
blue2: '#3366E6',
|
||||
blue3: '#4682B4',
|
||||
blue4: '#1F63E0',
|
||||
blue5: '#3A7AED',
|
||||
blue6: '#5A9DF5',
|
||||
blue7: '#2874A6',
|
||||
blue8: '#2E86C1',
|
||||
blue9: '#3498DB',
|
||||
blue10: '#1E90FF',
|
||||
blue11: '#4169E1',
|
||||
|
||||
// Cyans / Teals
|
||||
cyan1: '#00CEC9',
|
||||
cyan2: '#22A6F2',
|
||||
cyan3: '#00B0AA',
|
||||
cyan4: '#33D6C2',
|
||||
cyan5: '#66E9DA',
|
||||
cyan6: '#48DBFB',
|
||||
cyan7: '#00BFFF',
|
||||
cyan8: '#63B8FF',
|
||||
teal1: '#009688',
|
||||
teal2: '#1ABC9C',
|
||||
teal3: '#48C9B0',
|
||||
teal4: '#76D7C4',
|
||||
teal5: '#20B2AA',
|
||||
|
||||
// Greens
|
||||
green1: '#27AE60',
|
||||
green2: '#3CB371',
|
||||
green3: '#1E8449',
|
||||
green4: '#2ECC71',
|
||||
green5: '#58D68D',
|
||||
green6: '#229954',
|
||||
green7: '#52BE80',
|
||||
green8: '#82E0AA',
|
||||
green9: '#73C6B6',
|
||||
|
||||
// Limes
|
||||
lime1: '#A3E635',
|
||||
lime2: '#B9F18D',
|
||||
lime3: '#84CC16',
|
||||
lime4: '#65A30D',
|
||||
|
||||
// Yellows
|
||||
yellow1: '#F1C40F',
|
||||
yellow2: '#F7DC6F',
|
||||
yellow3: '#F9E79F',
|
||||
yellow4: '#F4D03F',
|
||||
yellow5: '#D4AC0D',
|
||||
|
||||
// Golds / Ambers
|
||||
gold1: '#F2C94C',
|
||||
gold2: '#FFD93D',
|
||||
gold3: '#FFCA28',
|
||||
gold4: '#B7950B',
|
||||
gold5: '#D4A017',
|
||||
|
||||
// Oranges (non-red)
|
||||
orange1: '#F39C12',
|
||||
orange2: '#E67E22',
|
||||
orange3: '#F5B041',
|
||||
orange4: '#D35400',
|
||||
orange5: '#EB984E',
|
||||
orange6: '#FAD7A0',
|
||||
|
||||
// Purples / Violets
|
||||
purple1: '#BB6BD9',
|
||||
purple2: '#9B51E0',
|
||||
purple3: '#DA77F2',
|
||||
purple4: '#C77DFF',
|
||||
purple5: '#6C5CE7',
|
||||
purple6: '#8E44AD',
|
||||
purple7: '#9B59B6',
|
||||
purple8: '#BB8FCE',
|
||||
purple9: '#7D3C98',
|
||||
purple10: '#A569BD',
|
||||
|
||||
// Lavenders
|
||||
lavender1: '#AF7AC5',
|
||||
lavender2: '#C39BD3',
|
||||
lavender3: '#D2B4DE',
|
||||
|
||||
// Pinks / Magentas
|
||||
pink1: '#E91E8C',
|
||||
pink2: '#FF6FD8',
|
||||
pink3: '#F06292',
|
||||
pink4: '#CE93D8',
|
||||
|
||||
// Salmons / Corals (distinct from error red)
|
||||
salmon1: '#FF8A65',
|
||||
salmon2: '#FFAB91',
|
||||
salmon3: '#E0876A',
|
||||
},
|
||||
chartcolors: {
|
||||
// Blues (3)
|
||||
dodgerBlue: '#2F80ED',
|
||||
|
||||
@@ -24,43 +24,6 @@
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.crossPanelSyncSectionHeader {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
.crossPanelSyncInfoIcon {
|
||||
cursor: help;
|
||||
color: var(--l3-foreground);
|
||||
}
|
||||
|
||||
.crossPanelSyncTooltipContent {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
.crossPanelSyncTooltipTitle {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.crossPanelSyncTooltipDescription {
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.crossPanelSyncTooltipDocLink {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
color: var(--primary-background);
|
||||
font-size: 12px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.crossPanelSyncRow {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Col, Input, Radio, Select, Space, Tooltip, Typography } from 'antd';
|
||||
import { Col, Input, Radio, Select, Space, Typography } from 'antd';
|
||||
import AddTags from 'container/DashboardContainer/DashboardSettings/General/AddTags';
|
||||
import { useDashboardCursorSyncMode } from 'hooks/dashboard/useDashboardCursorSyncMode';
|
||||
import { useSyncTooltipFilterMode } from 'hooks/dashboard/useSyncTooltipFilterMode';
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
SyncTooltipFilterMode,
|
||||
} from 'lib/uPlotV2/plugins/TooltipPlugin/types';
|
||||
import { isEqual } from 'lodash-es';
|
||||
import { Check, ExternalLink, Info, X } from '@signozhq/icons';
|
||||
import { Check, X } from 'lucide-react';
|
||||
import { useDashboardStore } from 'providers/Dashboard/store/useDashboardStore';
|
||||
|
||||
import styles from './GeneralSettings.module.scss';
|
||||
@@ -173,36 +173,9 @@ function GeneralDashboardSettings(): JSX.Element {
|
||||
</Space>
|
||||
</Col>
|
||||
<Col className={`${styles.overviewSettings} ${styles.crossPanelSyncGroup}`}>
|
||||
<div className={styles.crossPanelSyncSectionHeader}>
|
||||
<Typography.Text className={styles.crossPanelSyncSectionTitle}>
|
||||
Cross-Panel Sync
|
||||
</Typography.Text>
|
||||
<Tooltip
|
||||
title={
|
||||
<div className={styles.crossPanelSyncTooltipContent}>
|
||||
<strong className={styles.crossPanelSyncTooltipTitle}>
|
||||
Cross-Panel Sync
|
||||
</strong>
|
||||
<span className={styles.crossPanelSyncTooltipDescription}>
|
||||
Sync crosshair and tooltip across all the dashboard panels
|
||||
</span>
|
||||
<a
|
||||
href="https://signoz.io/docs/dashboards/interactivity/#cross-panel-sync"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className={styles.crossPanelSyncTooltipDocLink}
|
||||
>
|
||||
Learn more
|
||||
<ExternalLink size={12} />
|
||||
</a>
|
||||
</div>
|
||||
}
|
||||
placement="top"
|
||||
mouseEnterDelay={0.5}
|
||||
>
|
||||
<Info size={14} className={styles.crossPanelSyncInfoIcon} />
|
||||
</Tooltip>
|
||||
</div>
|
||||
<Typography.Text className={styles.crossPanelSyncSectionTitle}>
|
||||
Cross-Panel Sync
|
||||
</Typography.Text>
|
||||
<div className={styles.crossPanelSyncRow}>
|
||||
<div className={styles.crossPanelSyncInfo}>
|
||||
<Typography.Text className={styles.crossPanelSyncTitle}>
|
||||
|
||||
@@ -123,7 +123,6 @@
|
||||
&__row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
align-items: flex-end;
|
||||
max-width: 825px;
|
||||
gap: 25px;
|
||||
|
||||
@@ -286,8 +286,6 @@ export function K8sBaseList<T extends K8sEntityData>({
|
||||
total: totalCount,
|
||||
defaultLimit: 10,
|
||||
defaultPage: 1,
|
||||
showTotalCount: true,
|
||||
totalCountLabel: entity.charAt(0).toUpperCase() + entity.slice(1),
|
||||
}}
|
||||
paginationClassname={styles.paginationContainer}
|
||||
/>
|
||||
|
||||
@@ -35,7 +35,7 @@ import { ILog } from 'types/api/logs/log';
|
||||
import { DataSource, StringOperators } from 'types/common/queryBuilder';
|
||||
|
||||
import loadingPlaneUrl from '@/assets/Icons/loading-plane.gif';
|
||||
import { getAbsoluteUrl } from '@/utils/basePath';
|
||||
import { getAbsoluteUrl } from 'utils/basePath';
|
||||
|
||||
import { LiveLogsListProps } from './types';
|
||||
|
||||
|
||||
@@ -11,12 +11,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.infra-metrics-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.infra-metrics-card {
|
||||
margin: 1rem 0;
|
||||
height: 300px;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useMemo, useRef } from 'react';
|
||||
import { useQueries, UseQueryResult } from 'react-query';
|
||||
import { Card, Skeleton, Typography } from 'antd';
|
||||
import { Card, Col, Row, Skeleton, Typography } from 'antd';
|
||||
import cx from 'classnames';
|
||||
import Uplot from 'components/Uplot';
|
||||
import { ENTITY_VERSION_V4 } from 'constants/app';
|
||||
@@ -163,16 +163,16 @@ function NodeMetrics({
|
||||
);
|
||||
};
|
||||
return (
|
||||
<div className="infra-metrics-grid">
|
||||
<Row gutter={24}>
|
||||
{queries.map((query, idx) => (
|
||||
<div key={widgetInfo[idx].title}>
|
||||
<Col span={12} key={widgetInfo[idx].title}>
|
||||
<Typography.Text>{widgetInfo[idx].title}</Typography.Text>
|
||||
<Card bordered className="infra-metrics-card" ref={graphRef}>
|
||||
{renderCardContent(query, idx)}
|
||||
</Card>
|
||||
</div>
|
||||
</Col>
|
||||
))}
|
||||
</div>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useMemo, useRef } from 'react';
|
||||
import { useQueries, UseQueryResult } from 'react-query';
|
||||
import { Card, Skeleton, Typography } from 'antd';
|
||||
import { Card, Col, Row, Skeleton, Typography } from 'antd';
|
||||
import cx from 'classnames';
|
||||
import Uplot from 'components/Uplot';
|
||||
import { ENTITY_VERSION_V4 } from 'constants/app';
|
||||
@@ -146,16 +146,16 @@ function PodMetrics({
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="infra-metrics-grid">
|
||||
<Row gutter={24}>
|
||||
{queries.map((query, idx) => (
|
||||
<div key={podWidgetInfo[idx].title}>
|
||||
<Col span={12} key={podWidgetInfo[idx].title}>
|
||||
<Typography.Text>{podWidgetInfo[idx].title}</Typography.Text>
|
||||
<Card bordered className="infra-metrics-card" ref={graphRef}>
|
||||
{renderCardContent(query, idx)}
|
||||
</Card>
|
||||
</div>
|
||||
</Col>
|
||||
))}
|
||||
</div>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -26,14 +26,13 @@ function JSONView({ logData }: JSONViewProps): JSX.Element {
|
||||
minimap: {
|
||||
enabled: false,
|
||||
},
|
||||
fontWeight: 400,
|
||||
fontWeight: '400',
|
||||
// fontFamily: 'SF Mono',
|
||||
fontFamily: 'Geist Mono',
|
||||
fontSize: 13,
|
||||
lineHeight: '18px',
|
||||
lineHeight: 18,
|
||||
colorDecorators: true,
|
||||
scrollBeyondLastLine: false,
|
||||
decorationsOverviewRuler: false,
|
||||
scrollbar: {
|
||||
vertical: 'hidden',
|
||||
horizontal: 'hidden',
|
||||
|
||||
@@ -49,15 +49,14 @@ function Overview({
|
||||
const options: EditorProps['options'] = {
|
||||
automaticLayout: true,
|
||||
readOnly: true,
|
||||
height: '40vh',
|
||||
wordWrap: isWrapWord ? 'on' : 'off',
|
||||
minimap: {
|
||||
enabled: false,
|
||||
},
|
||||
fontWeight: 400,
|
||||
fontWeight: '400',
|
||||
fontFamily: 'Geist Mono',
|
||||
fontSize: 13,
|
||||
lineHeight: '18px',
|
||||
lineHeight: 18,
|
||||
colorDecorators: true,
|
||||
scrollBeyondLastLine: false,
|
||||
scrollbar: {
|
||||
|
||||
@@ -1,241 +0,0 @@
|
||||
/**
|
||||
* Login - Session Context & Organization Tests
|
||||
*
|
||||
* Split from Login.test.tsx for better parallelization.
|
||||
* Tests session context fetching and organization selection.
|
||||
*/
|
||||
import { rest, server } from 'mocks-server/server';
|
||||
import { render, screen, userEvent, waitFor } from 'tests/test-utils';
|
||||
|
||||
import Login from '../index';
|
||||
import {
|
||||
CALLBACK_AUTHN_ORG,
|
||||
mockMultiOrgMixedAuth,
|
||||
mockSingleOrgPasswordAuth,
|
||||
mockVersionSetupCompleted,
|
||||
PASSWORD_AUTHN_EMAIL,
|
||||
PASSWORD_AUTHN_ORG,
|
||||
SESSIONS_CONTEXT_ENDPOINT,
|
||||
VERSION_ENDPOINT,
|
||||
} from './Login.test-utils';
|
||||
|
||||
// =============================================================================
|
||||
// MOCKS
|
||||
// =============================================================================
|
||||
|
||||
jest.mock('lib/history', () => ({
|
||||
__esModule: true,
|
||||
default: {
|
||||
push: jest.fn(),
|
||||
location: {
|
||||
search: '',
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
// =============================================================================
|
||||
// TESTS
|
||||
// =============================================================================
|
||||
|
||||
describe('Login - Session Context & Organization', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
server.use(
|
||||
rest.get(VERSION_ENDPOINT, (_, res, ctx) =>
|
||||
res(
|
||||
ctx.status(200),
|
||||
ctx.json({ data: mockVersionSetupCompleted, status: 'success' }),
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
server.resetHandlers();
|
||||
});
|
||||
|
||||
describe('Session Context Fetching', () => {
|
||||
it('fetches session context on next button click and enables password', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
|
||||
server.use(
|
||||
rest.get(SESSIONS_CONTEXT_ENDPOINT, (_, res, ctx) =>
|
||||
res(ctx.status(200), ctx.json({ data: mockSingleOrgPasswordAuth })),
|
||||
),
|
||||
);
|
||||
|
||||
const { getByTestId } = render(<Login />);
|
||||
|
||||
const emailInput = await waitFor(() => {
|
||||
const input = getByTestId('email');
|
||||
expect(input).not.toBeDisabled();
|
||||
return input;
|
||||
});
|
||||
|
||||
await user.type(emailInput, PASSWORD_AUTHN_EMAIL);
|
||||
|
||||
const nextButton = await waitFor(() => {
|
||||
const button = getByTestId('initiate_login');
|
||||
expect(button).not.toBeDisabled();
|
||||
return button;
|
||||
});
|
||||
|
||||
await user.click(nextButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(getByTestId('password')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('handles session context API errors', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
|
||||
server.use(
|
||||
rest.get(SESSIONS_CONTEXT_ENDPOINT, (_, res, ctx) =>
|
||||
res(
|
||||
ctx.status(500),
|
||||
ctx.json({
|
||||
error: {
|
||||
code: 'internal_server',
|
||||
message: 'couldnt fetch the sessions context',
|
||||
url: '',
|
||||
},
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
const { getByTestId, getByText } = render(<Login />);
|
||||
|
||||
const emailInput = await waitFor(() => {
|
||||
const input = getByTestId('email');
|
||||
expect(input).not.toBeDisabled();
|
||||
return input;
|
||||
});
|
||||
|
||||
await user.type(emailInput, PASSWORD_AUTHN_EMAIL);
|
||||
|
||||
const nextButton = await waitFor(() => {
|
||||
const button = getByTestId('initiate_login');
|
||||
expect(button).not.toBeDisabled();
|
||||
return button;
|
||||
});
|
||||
|
||||
await user.click(nextButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(getByText('couldnt fetch the sessions context')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('auto-selects organization when only one exists', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
|
||||
server.use(
|
||||
rest.get(SESSIONS_CONTEXT_ENDPOINT, (_, res, ctx) =>
|
||||
res(ctx.status(200), ctx.json({ data: mockSingleOrgPasswordAuth })),
|
||||
),
|
||||
);
|
||||
|
||||
const { getByTestId } = render(<Login />);
|
||||
|
||||
const emailInput = await waitFor(() => {
|
||||
const input = getByTestId('email');
|
||||
expect(input).not.toBeDisabled();
|
||||
return input;
|
||||
});
|
||||
|
||||
await user.type(emailInput, PASSWORD_AUTHN_EMAIL);
|
||||
|
||||
const nextButton = await waitFor(() => {
|
||||
const button = getByTestId('initiate_login');
|
||||
expect(button).not.toBeDisabled();
|
||||
return button;
|
||||
});
|
||||
|
||||
await user.click(nextButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(getByTestId('password')).toBeInTheDocument();
|
||||
expect(screen.queryByText(/organization name/i)).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Organization Selection', () => {
|
||||
it('shows organization dropdown when multiple orgs exist', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
|
||||
server.use(
|
||||
rest.get(SESSIONS_CONTEXT_ENDPOINT, (_, res, ctx) =>
|
||||
res(ctx.status(200), ctx.json({ data: mockMultiOrgMixedAuth })),
|
||||
),
|
||||
);
|
||||
|
||||
const { getByTestId, getByText } = render(<Login />);
|
||||
|
||||
const emailInput = await waitFor(() => {
|
||||
const input = getByTestId('email');
|
||||
expect(input).not.toBeDisabled();
|
||||
return input;
|
||||
});
|
||||
|
||||
await user.type(emailInput, PASSWORD_AUTHN_EMAIL);
|
||||
|
||||
const nextButton = await waitFor(() => {
|
||||
const button = getByTestId('initiate_login');
|
||||
expect(button).not.toBeDisabled();
|
||||
return button;
|
||||
});
|
||||
|
||||
await user.click(nextButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(getByText('Organization Name')).toBeInTheDocument();
|
||||
});
|
||||
await screen.findByRole('combobox');
|
||||
|
||||
await user.click(screen.getByRole('combobox'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(PASSWORD_AUTHN_ORG)).toBeInTheDocument();
|
||||
expect(screen.getByText(CALLBACK_AUTHN_ORG)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('updates selected organization on dropdown change', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
|
||||
server.use(
|
||||
rest.get(SESSIONS_CONTEXT_ENDPOINT, (_, res, ctx) =>
|
||||
res(ctx.status(200), ctx.json({ data: mockMultiOrgMixedAuth })),
|
||||
),
|
||||
);
|
||||
|
||||
render(<Login />);
|
||||
|
||||
const emailInput = await waitFor(() => {
|
||||
const input = screen.getByTestId('email');
|
||||
expect(input).not.toBeDisabled();
|
||||
return input;
|
||||
});
|
||||
|
||||
await user.type(emailInput, PASSWORD_AUTHN_EMAIL);
|
||||
|
||||
const nextButton = await waitFor(() => {
|
||||
const button = screen.getByTestId('initiate_login');
|
||||
expect(button).not.toBeDisabled();
|
||||
return button;
|
||||
});
|
||||
|
||||
await user.click(nextButton);
|
||||
|
||||
await screen.findByRole('combobox');
|
||||
|
||||
await user.click(screen.getByRole('combobox'));
|
||||
await user.click(screen.getByText(CALLBACK_AUTHN_ORG));
|
||||
|
||||
await screen.findByRole('button', { name: /sign in with sso/i });
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,127 +0,0 @@
|
||||
/**
|
||||
* Login - Initial Render & Setup Tests
|
||||
*
|
||||
* Split from Login.test.tsx for better parallelization.
|
||||
* Tests initial render, loading states, and setup validation.
|
||||
*/
|
||||
import ROUTES from 'constants/routes';
|
||||
import history from 'lib/history';
|
||||
import { rest, server } from 'mocks-server/server';
|
||||
import { render, screen, waitFor } from 'tests/test-utils';
|
||||
|
||||
import Login from '../index';
|
||||
import {
|
||||
mockVersionSetupCompleted,
|
||||
mockVersionSetupIncomplete,
|
||||
VERSION_ENDPOINT,
|
||||
} from './Login.test-utils';
|
||||
|
||||
// =============================================================================
|
||||
// MOCKS
|
||||
// =============================================================================
|
||||
|
||||
jest.mock('lib/history', () => ({
|
||||
__esModule: true,
|
||||
default: {
|
||||
push: jest.fn(),
|
||||
location: {
|
||||
search: '',
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
const mockHistoryPush = history.push as jest.MockedFunction<
|
||||
typeof history.push
|
||||
>;
|
||||
|
||||
// =============================================================================
|
||||
// TESTS
|
||||
// =============================================================================
|
||||
|
||||
describe('Login - Initial Render & Setup', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
server.use(
|
||||
rest.get(VERSION_ENDPOINT, (_, res, ctx) =>
|
||||
res(
|
||||
ctx.status(200),
|
||||
ctx.json({ data: mockVersionSetupCompleted, status: 'success' }),
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
server.resetHandlers();
|
||||
});
|
||||
|
||||
describe('Initial Render', () => {
|
||||
it('renders login form with email input and next button', () => {
|
||||
const { getByTestId, getByPlaceholderText } = render(<Login />);
|
||||
|
||||
expect(
|
||||
screen.getByText(/sign in to monitor, trace, and troubleshoot/i),
|
||||
).toBeInTheDocument();
|
||||
expect(getByTestId('email')).toBeInTheDocument();
|
||||
expect(getByTestId('initiate_login')).toBeInTheDocument();
|
||||
expect(getByPlaceholderText('e.g. john@signoz.io')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows loading state when version data is being fetched', () => {
|
||||
server.use(
|
||||
rest.get(VERSION_ENDPOINT, (_, res, ctx) =>
|
||||
res(
|
||||
ctx.delay(100),
|
||||
ctx.status(200),
|
||||
ctx.json({ data: mockVersionSetupCompleted, status: 'success' }),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
const { getByTestId } = render(<Login />);
|
||||
|
||||
expect(getByTestId('initiate_login')).toBeDisabled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Setup Check', () => {
|
||||
it('redirects to signup when setup is not completed', async () => {
|
||||
server.use(
|
||||
rest.get(VERSION_ENDPOINT, (_, res, ctx) =>
|
||||
res(
|
||||
ctx.status(200),
|
||||
ctx.json({ data: mockVersionSetupIncomplete, status: 'success' }),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
render(<Login />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockHistoryPush).toHaveBeenCalledWith(ROUTES.SIGN_UP);
|
||||
});
|
||||
});
|
||||
|
||||
it('stays on login page when setup is completed', async () => {
|
||||
render(<Login />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockHistoryPush).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it('handles version API error gracefully', async () => {
|
||||
server.use(
|
||||
rest.get(VERSION_ENDPOINT, (_, res, ctx) =>
|
||||
res(ctx.status(500), ctx.json({ error: 'Server error' })),
|
||||
),
|
||||
);
|
||||
|
||||
render(<Login />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockHistoryPush).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,172 +0,0 @@
|
||||
/**
|
||||
* Shared test utilities for Login tests.
|
||||
* Extract common mocks, data, and setup to avoid duplication across split test files.
|
||||
*/
|
||||
import { rest, server } from 'mocks-server/server';
|
||||
import { Info } from 'types/api/v1/version/get';
|
||||
import { SessionsContext } from 'types/api/v2/sessions/context/get';
|
||||
import { Token } from 'types/api/v2/sessions/email_password/post';
|
||||
import { ErrorV2 } from 'types/api';
|
||||
|
||||
// =============================================================================
|
||||
// CONSTANTS
|
||||
// =============================================================================
|
||||
|
||||
export const VERSION_ENDPOINT = '*/api/v1/version';
|
||||
export const SESSIONS_CONTEXT_ENDPOINT = '*/api/v2/sessions/context';
|
||||
export const EMAIL_PASSWORD_ENDPOINT = '*/api/v2/sessions/email_password';
|
||||
export const CALLBACK_AUTHN_ORG = 'callback_authn_org';
|
||||
export const CALLBACK_AUTHN_URL = 'https://sso.example.com/auth';
|
||||
export const PASSWORD_AUTHN_ORG = 'password_authn_org';
|
||||
export const PASSWORD_AUTHN_EMAIL = 'jest.test@signoz.io';
|
||||
|
||||
// =============================================================================
|
||||
// MOCK DATA
|
||||
// =============================================================================
|
||||
|
||||
export const mockVersionSetupCompleted: Info = {
|
||||
setupCompleted: true,
|
||||
ee: 'Y',
|
||||
version: '0.25.0',
|
||||
};
|
||||
|
||||
export const mockVersionSetupIncomplete: Info = {
|
||||
setupCompleted: false,
|
||||
ee: 'Y',
|
||||
version: '0.25.0',
|
||||
};
|
||||
|
||||
export const mockSingleOrgPasswordAuth: SessionsContext = {
|
||||
exists: true,
|
||||
orgs: [
|
||||
{
|
||||
id: 'org-1',
|
||||
name: 'Test Organization',
|
||||
authNSupport: {
|
||||
password: [{ provider: 'email_password' }],
|
||||
callback: [],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const mockSingleOrgCallbackAuth: SessionsContext = {
|
||||
exists: true,
|
||||
orgs: [
|
||||
{
|
||||
id: 'org-1',
|
||||
name: 'Test Organization',
|
||||
authNSupport: {
|
||||
password: [],
|
||||
callback: [{ provider: 'google', url: CALLBACK_AUTHN_URL }],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const mockMultiOrgMixedAuth: SessionsContext = {
|
||||
exists: true,
|
||||
orgs: [
|
||||
{
|
||||
id: 'org-1',
|
||||
name: PASSWORD_AUTHN_ORG,
|
||||
authNSupport: {
|
||||
password: [{ provider: 'email_password' }],
|
||||
callback: [],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'org-2',
|
||||
name: CALLBACK_AUTHN_ORG,
|
||||
authNSupport: {
|
||||
password: [],
|
||||
callback: [{ provider: 'google', url: CALLBACK_AUTHN_URL }],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const mockOrgWithWarning: SessionsContext = {
|
||||
exists: true,
|
||||
orgs: [
|
||||
{
|
||||
id: 'org-1',
|
||||
name: 'Warning Organization',
|
||||
authNSupport: {
|
||||
password: [{ provider: 'email_password' }],
|
||||
callback: [],
|
||||
},
|
||||
warning: {
|
||||
code: 'ORG_WARNING',
|
||||
message: 'Organization has limited access',
|
||||
url: 'https://example.com/warning',
|
||||
errors: [{ message: 'Contact admin for full access' }],
|
||||
} as ErrorV2,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const mockMultiOrgWithWarning: SessionsContext = {
|
||||
exists: true,
|
||||
orgs: [
|
||||
{
|
||||
id: 'org-1',
|
||||
name: 'Normal Organization',
|
||||
authNSupport: {
|
||||
password: [{ provider: 'email_password' }],
|
||||
callback: [],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'org-2',
|
||||
name: 'Warning Organization',
|
||||
authNSupport: {
|
||||
password: [{ provider: 'email_password' }],
|
||||
callback: [],
|
||||
},
|
||||
warning: {
|
||||
code: 'ORG_WARNING',
|
||||
message: 'Organization has limited access',
|
||||
url: 'https://example.com/warning',
|
||||
errors: [{ message: 'Contact admin for full access' }],
|
||||
} as ErrorV2,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const mockEmailPasswordResponse: Token = {
|
||||
accessToken: 'mock-access-token',
|
||||
refreshToken: 'mock-refresh-token',
|
||||
};
|
||||
|
||||
// =============================================================================
|
||||
// MOCK SETUP HELPERS
|
||||
// =============================================================================
|
||||
|
||||
export function setupVersionEndpoint(
|
||||
data: Info = mockVersionSetupCompleted,
|
||||
): void {
|
||||
server.use(
|
||||
rest.get(VERSION_ENDPOINT, (_, res, ctx) =>
|
||||
res(ctx.status(200), ctx.json({ data, status: 'success' })),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
export function setupSessionContextEndpoint(data: SessionsContext): void {
|
||||
server.use(
|
||||
rest.get(SESSIONS_CONTEXT_ENDPOINT, (_, res, ctx) =>
|
||||
res(ctx.status(200), ctx.json({ data })),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
export function setupEmailPasswordEndpoint(
|
||||
data: Token = mockEmailPasswordResponse,
|
||||
): void {
|
||||
server.use(
|
||||
rest.post(EMAIL_PASSWORD_ENDPOINT, (_, res, ctx) =>
|
||||
res(ctx.status(200), ctx.json({ data })),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -1,31 +1,20 @@
|
||||
/**
|
||||
* Login - Authentication & Form Tests
|
||||
*
|
||||
* Split from Login.test.tsx for better parallelization.
|
||||
* Tests password auth, callback auth, URL params, warnings, form state, and edge cases.
|
||||
*/
|
||||
import ROUTES from 'constants/routes';
|
||||
import history from 'lib/history';
|
||||
import { rest, server } from 'mocks-server/server';
|
||||
import { render, screen, userEvent, waitFor } from 'tests/test-utils';
|
||||
import { ErrorV2 } from 'types/api';
|
||||
import { Info } from 'types/api/v1/version/get';
|
||||
import { SessionsContext } from 'types/api/v2/sessions/context/get';
|
||||
import { Token } from 'types/api/v2/sessions/email_password/post';
|
||||
|
||||
import Login from '../index';
|
||||
import {
|
||||
CALLBACK_AUTHN_URL,
|
||||
EMAIL_PASSWORD_ENDPOINT,
|
||||
mockEmailPasswordResponse,
|
||||
mockMultiOrgWithWarning,
|
||||
mockOrgWithWarning,
|
||||
mockSingleOrgCallbackAuth,
|
||||
mockSingleOrgPasswordAuth,
|
||||
mockVersionSetupCompleted,
|
||||
PASSWORD_AUTHN_EMAIL,
|
||||
SESSIONS_CONTEXT_ENDPOINT,
|
||||
VERSION_ENDPOINT,
|
||||
} from './Login.test-utils';
|
||||
|
||||
// =============================================================================
|
||||
// MOCKS
|
||||
// =============================================================================
|
||||
const VERSION_ENDPOINT = '*/api/v1/version';
|
||||
const SESSIONS_CONTEXT_ENDPOINT = '*/api/v2/sessions/context';
|
||||
const CALLBACK_AUTHN_ORG = 'callback_authn_org';
|
||||
const CALLBACK_AUTHN_URL = 'https://sso.example.com/auth';
|
||||
const PASSWORD_AUTHN_ORG = 'password_authn_org';
|
||||
const PASSWORD_AUTHN_EMAIL = 'jest.test@signoz.io';
|
||||
|
||||
jest.mock('lib/history', () => ({
|
||||
__esModule: true,
|
||||
@@ -37,13 +26,102 @@ jest.mock('lib/history', () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
// =============================================================================
|
||||
// TESTS
|
||||
// =============================================================================
|
||||
const mockHistoryPush = history.push as jest.MockedFunction<
|
||||
typeof history.push
|
||||
>;
|
||||
|
||||
describe('Login - Authentication & Form', () => {
|
||||
// Mock data
|
||||
const mockVersionSetupCompleted: Info = {
|
||||
setupCompleted: true,
|
||||
ee: 'Y',
|
||||
version: '0.25.0',
|
||||
};
|
||||
|
||||
const mockVersionSetupIncomplete: Info = {
|
||||
setupCompleted: false,
|
||||
ee: 'Y',
|
||||
version: '0.25.0',
|
||||
};
|
||||
|
||||
const mockSingleOrgPasswordAuth: SessionsContext = {
|
||||
exists: true,
|
||||
orgs: [
|
||||
{
|
||||
id: 'org-1',
|
||||
name: 'Test Organization',
|
||||
authNSupport: {
|
||||
password: [{ provider: 'email_password' }],
|
||||
callback: [],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const mockSingleOrgCallbackAuth: SessionsContext = {
|
||||
exists: true,
|
||||
orgs: [
|
||||
{
|
||||
id: 'org-1',
|
||||
name: 'Test Organization',
|
||||
authNSupport: {
|
||||
password: [],
|
||||
callback: [{ provider: 'google', url: CALLBACK_AUTHN_URL }],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const mockMultiOrgMixedAuth: SessionsContext = {
|
||||
exists: true,
|
||||
orgs: [
|
||||
{
|
||||
id: 'org-1',
|
||||
name: PASSWORD_AUTHN_ORG,
|
||||
authNSupport: {
|
||||
password: [{ provider: 'email_password' }],
|
||||
callback: [],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'org-2',
|
||||
name: CALLBACK_AUTHN_ORG,
|
||||
authNSupport: {
|
||||
password: [],
|
||||
callback: [{ provider: 'google', url: CALLBACK_AUTHN_URL }],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const mockOrgWithWarning: SessionsContext = {
|
||||
exists: true,
|
||||
orgs: [
|
||||
{
|
||||
id: 'org-1',
|
||||
name: 'Warning Organization',
|
||||
authNSupport: {
|
||||
password: [{ provider: 'email_password' }],
|
||||
callback: [],
|
||||
},
|
||||
warning: {
|
||||
code: 'ORG_WARNING',
|
||||
message: 'Organization has limited access',
|
||||
url: 'https://example.com/warning',
|
||||
errors: [{ message: 'Contact admin for full access' }],
|
||||
} as ErrorV2,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const mockEmailPasswordResponse: Token = {
|
||||
accessToken: 'mock-access-token',
|
||||
refreshToken: 'mock-refresh-token',
|
||||
};
|
||||
|
||||
describe('Login Component', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
server.use(
|
||||
rest.get(VERSION_ENDPOINT, (_, res, ctx) =>
|
||||
res(
|
||||
@@ -58,6 +136,269 @@ describe('Login - Authentication & Form', () => {
|
||||
server.resetHandlers();
|
||||
});
|
||||
|
||||
describe('Initial Render', () => {
|
||||
it('renders login form with email input and next button', () => {
|
||||
const { getByTestId, getByPlaceholderText } = render(<Login />);
|
||||
|
||||
expect(
|
||||
screen.getByText(/sign in to monitor, trace, and troubleshoot/i),
|
||||
).toBeInTheDocument();
|
||||
expect(getByTestId('email')).toBeInTheDocument();
|
||||
expect(getByTestId('initiate_login')).toBeInTheDocument();
|
||||
expect(getByPlaceholderText('e.g. john@signoz.io')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows loading state when version data is being fetched', () => {
|
||||
server.use(
|
||||
rest.get(VERSION_ENDPOINT, (_, res, ctx) =>
|
||||
res(
|
||||
ctx.delay(100),
|
||||
ctx.status(200),
|
||||
ctx.json({ data: mockVersionSetupCompleted, status: 'success' }),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
const { getByTestId } = render(<Login />);
|
||||
|
||||
expect(getByTestId('initiate_login')).toBeDisabled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Setup Check', () => {
|
||||
it('redirects to signup when setup is not completed', async () => {
|
||||
server.use(
|
||||
rest.get(VERSION_ENDPOINT, (_, res, ctx) =>
|
||||
res(
|
||||
ctx.status(200),
|
||||
ctx.json({ data: mockVersionSetupIncomplete, status: 'success' }),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
render(<Login />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockHistoryPush).toHaveBeenCalledWith(ROUTES.SIGN_UP);
|
||||
});
|
||||
});
|
||||
|
||||
it('stays on login page when setup is completed', async () => {
|
||||
render(<Login />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockHistoryPush).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it('handles version API error gracefully', async () => {
|
||||
server.use(
|
||||
rest.get(VERSION_ENDPOINT, (req, res, ctx) =>
|
||||
res(ctx.status(500), ctx.json({ error: 'Server error' })),
|
||||
),
|
||||
);
|
||||
|
||||
render(<Login />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockHistoryPush).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Session Context Fetching', () => {
|
||||
it('fetches session context on next button click and enables password', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
|
||||
server.use(
|
||||
rest.get(SESSIONS_CONTEXT_ENDPOINT, (_, res, ctx) =>
|
||||
res(ctx.status(200), ctx.json({ data: mockSingleOrgPasswordAuth })),
|
||||
),
|
||||
);
|
||||
|
||||
const { getByTestId } = render(<Login />);
|
||||
|
||||
// Wait for version API to complete (email input becomes enabled)
|
||||
const emailInput = await waitFor(() => {
|
||||
const input = getByTestId('email');
|
||||
expect(input).not.toBeDisabled();
|
||||
return input;
|
||||
});
|
||||
|
||||
await user.type(emailInput, PASSWORD_AUTHN_EMAIL);
|
||||
|
||||
const nextButton = await waitFor(() => {
|
||||
const button = getByTestId('initiate_login');
|
||||
expect(button).not.toBeDisabled();
|
||||
return button;
|
||||
});
|
||||
|
||||
await user.click(nextButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(getByTestId('password')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('handles session context API errors', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
|
||||
server.use(
|
||||
rest.get(SESSIONS_CONTEXT_ENDPOINT, (_, res, ctx) =>
|
||||
res(
|
||||
ctx.status(500),
|
||||
ctx.json({
|
||||
error: {
|
||||
code: 'internal_server',
|
||||
message: 'couldnt fetch the sessions context',
|
||||
url: '',
|
||||
},
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
const { getByTestId, getByText } = render(<Login />);
|
||||
|
||||
// Wait for version API to complete (email input becomes enabled)
|
||||
const emailInput = await waitFor(() => {
|
||||
const input = getByTestId('email');
|
||||
expect(input).not.toBeDisabled();
|
||||
return input;
|
||||
});
|
||||
|
||||
await user.type(emailInput, PASSWORD_AUTHN_EMAIL);
|
||||
|
||||
const nextButton = await waitFor(() => {
|
||||
const button = getByTestId('initiate_login');
|
||||
expect(button).not.toBeDisabled();
|
||||
return button;
|
||||
});
|
||||
|
||||
await user.click(nextButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(getByText('couldnt fetch the sessions context')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('auto-selects organization when only one exists', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
|
||||
server.use(
|
||||
rest.get(SESSIONS_CONTEXT_ENDPOINT, (req, res, ctx) =>
|
||||
res(ctx.status(200), ctx.json({ data: mockSingleOrgPasswordAuth })),
|
||||
),
|
||||
);
|
||||
|
||||
const { getByTestId } = render(<Login />);
|
||||
|
||||
// Wait for version API to complete (email input becomes enabled)
|
||||
const emailInput = await waitFor(() => {
|
||||
const input = getByTestId('email');
|
||||
expect(input).not.toBeDisabled();
|
||||
return input;
|
||||
});
|
||||
|
||||
await user.type(emailInput, PASSWORD_AUTHN_EMAIL);
|
||||
|
||||
const nextButton = await waitFor(() => {
|
||||
const button = getByTestId('initiate_login');
|
||||
expect(button).not.toBeDisabled();
|
||||
return button;
|
||||
});
|
||||
|
||||
await user.click(nextButton);
|
||||
|
||||
await waitFor(() => {
|
||||
// Should show password field directly (no org selection needed)
|
||||
expect(getByTestId('password')).toBeInTheDocument();
|
||||
expect(screen.queryByText(/organization name/i)).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Organization Selection', () => {
|
||||
it('shows organization dropdown when multiple orgs exist', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
|
||||
server.use(
|
||||
rest.get(SESSIONS_CONTEXT_ENDPOINT, (_, res, ctx) =>
|
||||
res(ctx.status(200), ctx.json({ data: mockMultiOrgMixedAuth })),
|
||||
),
|
||||
);
|
||||
|
||||
const { getByTestId, getByText } = render(<Login />);
|
||||
|
||||
// Wait for version API to complete (email input becomes enabled)
|
||||
const emailInput = await waitFor(() => {
|
||||
const input = getByTestId('email');
|
||||
expect(input).not.toBeDisabled();
|
||||
return input;
|
||||
});
|
||||
|
||||
await user.type(emailInput, PASSWORD_AUTHN_EMAIL);
|
||||
|
||||
const nextButton = await waitFor(() => {
|
||||
const button = getByTestId('initiate_login');
|
||||
expect(button).not.toBeDisabled();
|
||||
return button;
|
||||
});
|
||||
|
||||
await user.click(nextButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(getByText('Organization Name')).toBeInTheDocument();
|
||||
});
|
||||
await screen.findByRole('combobox');
|
||||
|
||||
// Click on the dropdown to reveal the options
|
||||
await user.click(screen.getByRole('combobox'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(PASSWORD_AUTHN_ORG)).toBeInTheDocument();
|
||||
expect(screen.getByText(CALLBACK_AUTHN_ORG)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('updates selected organization on dropdown change', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
|
||||
server.use(
|
||||
rest.get(SESSIONS_CONTEXT_ENDPOINT, (req, res, ctx) =>
|
||||
res(ctx.status(200), ctx.json({ data: mockMultiOrgMixedAuth })),
|
||||
),
|
||||
);
|
||||
|
||||
render(<Login />);
|
||||
|
||||
// Wait for version API to complete (email input becomes enabled)
|
||||
const emailInput = await waitFor(() => {
|
||||
const input = screen.getByTestId('email');
|
||||
expect(input).not.toBeDisabled();
|
||||
return input;
|
||||
});
|
||||
|
||||
await user.type(emailInput, PASSWORD_AUTHN_EMAIL);
|
||||
|
||||
const nextButton = await waitFor(() => {
|
||||
const button = screen.getByTestId('initiate_login');
|
||||
expect(button).not.toBeDisabled();
|
||||
return button;
|
||||
});
|
||||
|
||||
await user.click(nextButton);
|
||||
|
||||
await screen.findByRole('combobox');
|
||||
|
||||
// Select CALLBACK_AUTHN_ORG
|
||||
await user.click(screen.getByRole('combobox'));
|
||||
await user.click(screen.getByText(CALLBACK_AUTHN_ORG));
|
||||
|
||||
await screen.findByRole('button', { name: /sign in with sso/i });
|
||||
});
|
||||
});
|
||||
|
||||
describe('Password Authentication', () => {
|
||||
it('shows password field when password auth is supported', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
@@ -70,6 +411,7 @@ describe('Login - Authentication & Form', () => {
|
||||
|
||||
const { getByTestId, getByText } = render(<Login />);
|
||||
|
||||
// Wait for version API to complete (email input becomes enabled)
|
||||
const emailInput = await waitFor(() => {
|
||||
const input = getByTestId('email');
|
||||
expect(input).not.toBeDisabled();
|
||||
@@ -106,6 +448,7 @@ describe('Login - Authentication & Form', () => {
|
||||
initialRoute: '/login?password=Y',
|
||||
});
|
||||
|
||||
// Wait for version API to complete (email input becomes enabled)
|
||||
const emailInput = await waitFor(() => {
|
||||
const input = getByTestId('email');
|
||||
expect(input).not.toBeDisabled();
|
||||
@@ -123,6 +466,7 @@ describe('Login - Authentication & Form', () => {
|
||||
await user.click(nextButton);
|
||||
|
||||
await waitFor(() => {
|
||||
// Should show password field even for SSO org due to password=Y override
|
||||
expect(getByTestId('password')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -140,6 +484,7 @@ describe('Login - Authentication & Form', () => {
|
||||
|
||||
const { getByTestId, queryByTestId } = render(<Login />);
|
||||
|
||||
// Wait for version API to complete (email input becomes enabled)
|
||||
const emailInput = await waitFor(() => {
|
||||
const input = getByTestId('email');
|
||||
expect(input).not.toBeDisabled();
|
||||
@@ -165,7 +510,10 @@ describe('Login - Authentication & Form', () => {
|
||||
it('redirects to callback URL on button click', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
|
||||
const mockLocation = { href: 'http://localhost/' };
|
||||
// Mock window.location.href
|
||||
const mockLocation = {
|
||||
href: 'http://localhost/',
|
||||
};
|
||||
Object.defineProperty(window, 'location', {
|
||||
value: mockLocation,
|
||||
writable: true,
|
||||
@@ -179,6 +527,7 @@ describe('Login - Authentication & Form', () => {
|
||||
|
||||
const { getByTestId, queryByTestId } = render(<Login />);
|
||||
|
||||
// Wait for version API to complete (email input becomes enabled)
|
||||
const emailInput = await waitFor(() => {
|
||||
const input = getByTestId('email');
|
||||
expect(input).not.toBeDisabled();
|
||||
@@ -203,6 +552,7 @@ describe('Login - Authentication & Form', () => {
|
||||
const callbackButton = getByTestId('callback_authn_submit');
|
||||
await user.click(callbackButton);
|
||||
|
||||
// Check that window.location.href was set to the callback URL
|
||||
await waitFor(() => {
|
||||
expect(window.location.href).toBe(CALLBACK_AUTHN_URL);
|
||||
});
|
||||
@@ -217,7 +567,7 @@ describe('Login - Authentication & Form', () => {
|
||||
rest.get(SESSIONS_CONTEXT_ENDPOINT, (_, res, ctx) =>
|
||||
res(ctx.status(200), ctx.json({ data: mockSingleOrgPasswordAuth })),
|
||||
),
|
||||
rest.post(EMAIL_PASSWORD_ENDPOINT, async (_, res, ctx) =>
|
||||
rest.post('*/api/v2/sessions/email_password', async (_, res, ctx) =>
|
||||
res(
|
||||
ctx.status(200),
|
||||
ctx.json({ status: 'success', data: mockEmailPasswordResponse }),
|
||||
@@ -227,6 +577,7 @@ describe('Login - Authentication & Form', () => {
|
||||
|
||||
const { getByTestId } = render(<Login />);
|
||||
|
||||
// Wait for version API to complete (email input becomes enabled)
|
||||
const emailInput = await waitFor(() => {
|
||||
const input = getByTestId('email');
|
||||
expect(input).not.toBeDisabled();
|
||||
@@ -253,6 +604,8 @@ describe('Login - Authentication & Form', () => {
|
||||
await user.type(passwordInput, 'testpassword');
|
||||
await user.click(loginButton);
|
||||
|
||||
// do not test for the request paramters here. Reference: https://mswjs.io/docs/best-practices/avoid-request-assertions
|
||||
// rather test for the effects of the request
|
||||
await waitFor(() => {
|
||||
expect(localStorage.getItem('AUTH_TOKEN')).toBe('mock-access-token');
|
||||
});
|
||||
@@ -265,7 +618,7 @@ describe('Login - Authentication & Form', () => {
|
||||
rest.get(SESSIONS_CONTEXT_ENDPOINT, (_, res, ctx) =>
|
||||
res(ctx.status(200), ctx.json({ data: mockSingleOrgPasswordAuth })),
|
||||
),
|
||||
rest.post(EMAIL_PASSWORD_ENDPOINT, (_, res, ctx) =>
|
||||
rest.post('*/api/v2/sessions/email_password', (_, res, ctx) =>
|
||||
res(
|
||||
ctx.status(401),
|
||||
ctx.json({
|
||||
@@ -281,6 +634,7 @@ describe('Login - Authentication & Form', () => {
|
||||
|
||||
const { getByTestId, getByText } = render(<Login />);
|
||||
|
||||
// Wait for version API to complete (email input becomes enabled)
|
||||
const emailInput = await waitFor(() => {
|
||||
const input = getByTestId('email');
|
||||
expect(input).not.toBeDisabled();
|
||||
@@ -354,13 +708,14 @@ describe('Login - Authentication & Form', () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
|
||||
server.use(
|
||||
rest.get(SESSIONS_CONTEXT_ENDPOINT, (_, res, ctx) =>
|
||||
rest.get(SESSIONS_CONTEXT_ENDPOINT, (req, res, ctx) =>
|
||||
res(ctx.status(200), ctx.json({ data: mockOrgWithWarning })),
|
||||
),
|
||||
);
|
||||
|
||||
render(<Login />);
|
||||
|
||||
// Wait for version API to complete (email input becomes enabled)
|
||||
const emailInput = await waitFor(() => {
|
||||
const input = screen.getByTestId('email');
|
||||
expect(input).not.toBeDisabled();
|
||||
@@ -387,6 +742,23 @@ describe('Login - Authentication & Form', () => {
|
||||
it('shows warning modal when a warning org is selected among multiple orgs', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
|
||||
// Mock multiple orgs including one with a warning
|
||||
const mockMultiOrgWithWarning = {
|
||||
orgs: [
|
||||
{ id: 'org1', name: 'Org 1' },
|
||||
{
|
||||
id: 'org2',
|
||||
name: 'Org 2',
|
||||
warning: {
|
||||
code: 'ORG_WARNING',
|
||||
message: 'Organization has limited access',
|
||||
url: 'https://example.com/warning',
|
||||
errors: [{ message: 'Contact admin for full access' }],
|
||||
} as ErrorV2,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
server.use(
|
||||
rest.get(SESSIONS_CONTEXT_ENDPOINT, (_, res, ctx) =>
|
||||
res(ctx.status(200), ctx.json({ data: mockMultiOrgWithWarning })),
|
||||
@@ -395,6 +767,7 @@ describe('Login - Authentication & Form', () => {
|
||||
|
||||
const { getByTestId } = render(<Login />);
|
||||
|
||||
// Wait for version API to complete (email input becomes enabled)
|
||||
const emailInput = await waitFor(() => {
|
||||
const input = getByTestId('email');
|
||||
expect(input).not.toBeDisabled();
|
||||
@@ -413,8 +786,9 @@ describe('Login - Authentication & Form', () => {
|
||||
|
||||
await screen.findByRole('combobox');
|
||||
|
||||
// Select the organization with a warning
|
||||
await user.click(screen.getByRole('combobox'));
|
||||
await user.click(screen.getByText('Warning Organization'));
|
||||
await user.click(screen.getByText('Org 2'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
@@ -429,7 +803,7 @@ describe('Login - Authentication & Form', () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
|
||||
server.use(
|
||||
rest.get(SESSIONS_CONTEXT_ENDPOINT, (_, res, ctx) =>
|
||||
rest.get(SESSIONS_CONTEXT_ENDPOINT, (req, res, ctx) =>
|
||||
res(
|
||||
ctx.delay(100),
|
||||
ctx.status(200),
|
||||
@@ -440,6 +814,7 @@ describe('Login - Authentication & Form', () => {
|
||||
|
||||
render(<Login />);
|
||||
|
||||
// Wait for version API to complete (email input becomes enabled)
|
||||
const emailInput = await waitFor(() => {
|
||||
const input = screen.getByTestId('email');
|
||||
expect(input).not.toBeDisabled();
|
||||
@@ -456,6 +831,7 @@ describe('Login - Authentication & Form', () => {
|
||||
|
||||
await user.click(nextButton);
|
||||
|
||||
// Button should be disabled during API call
|
||||
expect(nextButton).toBeDisabled();
|
||||
});
|
||||
|
||||
@@ -463,15 +839,17 @@ describe('Login - Authentication & Form', () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
|
||||
server.use(
|
||||
rest.get(SESSIONS_CONTEXT_ENDPOINT, (_, res, ctx) =>
|
||||
rest.get(SESSIONS_CONTEXT_ENDPOINT, (req, res, ctx) =>
|
||||
res(ctx.status(200), ctx.json({ data: mockSingleOrgPasswordAuth })),
|
||||
),
|
||||
);
|
||||
|
||||
render(<Login />);
|
||||
|
||||
// Initially shows "Next" button
|
||||
expect(screen.getByTestId('initiate_login')).toBeInTheDocument();
|
||||
|
||||
// Wait for version API to complete (email input becomes enabled)
|
||||
const emailInput = await waitFor(() => {
|
||||
const input = screen.getByTestId('email');
|
||||
expect(input).not.toBeDisabled();
|
||||
@@ -489,6 +867,7 @@ describe('Login - Authentication & Form', () => {
|
||||
await user.click(nextButton);
|
||||
|
||||
await waitFor(() => {
|
||||
// Should show "Sign in with Password" button for password auth
|
||||
expect(screen.getByTestId('password_authn_submit')).toBeInTheDocument();
|
||||
expect(screen.queryByTestId('initiate_login')).not.toBeInTheDocument();
|
||||
});
|
||||
@@ -505,13 +884,14 @@ describe('Login - Authentication & Form', () => {
|
||||
};
|
||||
|
||||
server.use(
|
||||
rest.get(SESSIONS_CONTEXT_ENDPOINT, (_, res, ctx) =>
|
||||
rest.get(SESSIONS_CONTEXT_ENDPOINT, (req, res, ctx) =>
|
||||
res(ctx.status(200), ctx.json({ data: mockNoOrgs })),
|
||||
),
|
||||
);
|
||||
|
||||
render(<Login />);
|
||||
|
||||
// Wait for version API to complete (email input becomes enabled)
|
||||
const emailInput = await waitFor(() => {
|
||||
const input = screen.getByTestId('email');
|
||||
expect(input).not.toBeDisabled();
|
||||
@@ -529,6 +909,7 @@ describe('Login - Authentication & Form', () => {
|
||||
await user.click(nextButton);
|
||||
|
||||
await waitFor(() => {
|
||||
// Should not show any auth method buttons
|
||||
expect(
|
||||
screen.queryByTestId('password_authn_submit'),
|
||||
).not.toBeInTheDocument();
|
||||
@@ -556,13 +937,14 @@ describe('Login - Authentication & Form', () => {
|
||||
};
|
||||
|
||||
server.use(
|
||||
rest.get(SESSIONS_CONTEXT_ENDPOINT, (_, res, ctx) =>
|
||||
rest.get(SESSIONS_CONTEXT_ENDPOINT, (req, res, ctx) =>
|
||||
res(ctx.status(200), ctx.json({ data: mockNoAuthSupport })),
|
||||
),
|
||||
);
|
||||
|
||||
render(<Login />);
|
||||
|
||||
// Wait for version API to complete (email input becomes enabled)
|
||||
const emailInput = await waitFor(() => {
|
||||
const input = screen.getByTestId('email');
|
||||
expect(input).not.toBeDisabled();
|
||||
@@ -580,6 +962,7 @@ describe('Login - Authentication & Form', () => {
|
||||
await user.click(nextButton);
|
||||
|
||||
await waitFor(() => {
|
||||
// Should not show any auth method buttons
|
||||
expect(
|
||||
screen.queryByTestId('password_authn_submit'),
|
||||
).not.toBeInTheDocument();
|
||||
@@ -38,7 +38,7 @@ import APIError from 'types/api/error';
|
||||
import { ILog } from 'types/api/logs/log';
|
||||
import { DataSource, StringOperators } from 'types/common/queryBuilder';
|
||||
|
||||
import { getAbsoluteUrl } from '@/utils/basePath';
|
||||
import { getAbsoluteUrl } from 'utils/basePath';
|
||||
|
||||
import NoLogs from '../NoLogs/NoLogs';
|
||||
import { LogsExplorerListProps } from './LogsExplorerList.interfaces';
|
||||
|
||||
@@ -66,14 +66,11 @@ exports[`PipelinePage container test should render PipelinePageLayout section 1`
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<defs>
|
||||
<style />
|
||||
</defs>
|
||||
<path
|
||||
d="M482 152h60q8 0 8 8v704q0 8-8 8h-60q-8 0-8-8V160q0-8 8-8z"
|
||||
/>
|
||||
<path
|
||||
d="M176 474h672q8 0 8 8v60q0 8-8 8H176q-8 0-8-8v-60q0-8 8-8z"
|
||||
d="M192 474h672q8 0 8 8v60q0 8-8 8H160q-8 0-8-8v-60q0-8 8-8z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { ApiRoutingPolicy } from 'api/routingPolicies/getRoutingPolicies';
|
||||
import { IAppContext } from 'providers/App/types';
|
||||
import { getAppContextMockMinimal } from 'tests/test-utils';
|
||||
import { IAppContext, IUser } from 'providers/App/types';
|
||||
import { Channels } from 'types/api/channels/getAll';
|
||||
|
||||
import { RoutingPolicy, UseRoutingPoliciesReturn } from '../types';
|
||||
@@ -79,14 +78,49 @@ export function getUseRoutingPoliciesMockData(
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use getAppContextMockMinimal from 'tests/test-utils' directly.
|
||||
* This is a backwards-compatible wrapper that will be removed in a future version.
|
||||
*/
|
||||
export function getAppContextMockState(
|
||||
overrides?: Partial<IAppContext['user']>,
|
||||
overrides?: Partial<IUser>,
|
||||
): IAppContext {
|
||||
return getAppContextMockMinimal(overrides);
|
||||
return {
|
||||
user: {
|
||||
accessJwt: 'some-token',
|
||||
refreshJwt: 'some-refresh-token',
|
||||
id: 'some-user-id',
|
||||
email: 'user@signoz.io',
|
||||
displayName: 'John Doe',
|
||||
createdAt: 1732544623,
|
||||
organization: 'Nightswatch',
|
||||
orgId: 'does-not-matter-id',
|
||||
role: 'ADMIN',
|
||||
...overrides,
|
||||
},
|
||||
activeLicense: null,
|
||||
trialInfo: null,
|
||||
featureFlags: null,
|
||||
orgPreferences: null,
|
||||
userPreferences: null,
|
||||
isLoggedIn: false,
|
||||
org: null,
|
||||
isFetchingUser: false,
|
||||
isFetchingActiveLicense: false,
|
||||
isFetchingFeatureFlags: false,
|
||||
isFetchingOrgPreferences: false,
|
||||
userFetchError: undefined,
|
||||
activeLicenseFetchError: null,
|
||||
featureFlagsFetchError: undefined,
|
||||
orgPreferencesFetchError: undefined,
|
||||
changelog: null,
|
||||
showChangelogModal: false,
|
||||
activeLicenseRefetch: jest.fn(),
|
||||
updateUser: jest.fn(),
|
||||
updateOrgPreferences: jest.fn(),
|
||||
updateUserPreferenceInContext: jest.fn(),
|
||||
updateOrg: jest.fn(),
|
||||
updateChangelog: jest.fn(),
|
||||
toggleChangelogModal: jest.fn(),
|
||||
versionData: null,
|
||||
hasEditPermission: false,
|
||||
};
|
||||
}
|
||||
|
||||
export function mockLocation(pathname: string): jest.Mock {
|
||||
|
||||
@@ -46,7 +46,6 @@ export const routeConfig: Record<string, QueryParams[]> = {
|
||||
[ROUTES.TRACES_EXPLORER]: [QueryParams.resourceAttributes],
|
||||
[ROUTES.TRACE]: [QueryParams.resourceAttributes],
|
||||
[ROUTES.TRACE_DETAIL]: [QueryParams.resourceAttributes],
|
||||
[ROUTES.TRACE_DETAIL_OLD]: [QueryParams.resourceAttributes],
|
||||
[ROUTES.UN_AUTHORIZED]: [QueryParams.resourceAttributes],
|
||||
[ROUTES.USAGE_EXPLORER]: [QueryParams.resourceAttributes],
|
||||
[ROUTES.VERSION]: [QueryParams.resourceAttributes],
|
||||
|
||||
@@ -51,10 +51,7 @@ import { Span } from 'types/api/trace/getTraceV2';
|
||||
import { formatEpochTimestamp } from 'utils/timeUtils';
|
||||
|
||||
import Attributes from './Attributes/Attributes';
|
||||
import {
|
||||
RelatedSignalsViews,
|
||||
SPAN_PERCENTILE_INITIAL_DELAY_MS,
|
||||
} from './constants';
|
||||
import { RelatedSignalsViews } from './constants';
|
||||
import EventAttribute from './Events/components/EventAttribute';
|
||||
import Events from './Events/Events';
|
||||
import LinkedSpans from './LinkedSpans/LinkedSpans';
|
||||
@@ -413,7 +410,7 @@ function SpanDetailsDrawer(props: ISpanDetailsDrawerProps): JSX.Element {
|
||||
|
||||
const timer = setTimeout(() => {
|
||||
setInitialWaitCompleted(true);
|
||||
}, SPAN_PERCENTILE_INITIAL_DELAY_MS);
|
||||
}, 2000); // 2-second delay
|
||||
|
||||
return (): void => {
|
||||
// clean the old state around span percentile data
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user