mirror of
https://github.com/SigNoz/signoz.git
synced 2026-10-15 22:50:48 +01:00
Compare commits
39 Commits
test/explo
...
refactor/r
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c615baac03 | ||
|
|
32444ac89b | ||
|
|
10587de709 | ||
|
|
11a813b2b7 | ||
|
|
b6712d2c7f | ||
|
|
5b995b5bc1 | ||
|
|
9a8e795f31 | ||
|
|
3374261d1f | ||
|
|
019c9a6591 | ||
|
|
77d3a832ea | ||
|
|
db08d7792a | ||
|
|
b4e83ed7e3 | ||
|
|
0542d37adb | ||
|
|
5cfef312eb | ||
|
|
ca011493ea | ||
|
|
969d1c0840 | ||
|
|
ef71ee3510 | ||
|
|
498c89fb14 | ||
|
|
268c67d34b | ||
|
|
206349b75c | ||
|
|
11ae922ff7 | ||
|
|
3e087fe7bd | ||
|
|
9ec39f2494 | ||
|
|
ef67a47cf3 | ||
|
|
441f7e2927 | ||
|
|
4aff26f696 | ||
|
|
47a773245b | ||
|
|
375b3f8305 | ||
|
|
aaec7c89c5 | ||
|
|
57c193ec8f | ||
|
|
da556abac1 | ||
|
|
cc8440248b | ||
|
|
e84ddfb61e | ||
|
|
dc9fda2916 | ||
|
|
670677bae2 | ||
|
|
e4d66c5413 | ||
|
|
1371cb0beb | ||
|
|
140498cb11 | ||
|
|
ba01bc0418 |
@@ -17,3 +17,4 @@ For the Python integration suite under `tests/`. Setup, running, and suite layou
|
||||
- **snake_case parametrize ids.** `ids=["fill_gaps", "fill_zero"]`, not camelCase.
|
||||
- **Name suite files with the two-digit prefix (`NN_*.py`).** `pyproject.toml` restricts collection to `[0-9][0-9]_*.py` (plus the bootstrap `setup.py` / `run.py`) — a file that doesn't match is silently never collected.
|
||||
- **Always run pytest from `tests/`.** `--import-mode=importlib` is what allows same-basename files across suites (`querier/01_logs.py` vs `rawexportdata/01_logs.py`), but it disables pytest's implicit `sys.path` injection — `import fixtures` only resolves via `pythonpath = ["."]` from that rootdir.
|
||||
- **After changing any `.py` file, run `make py-fmt` and `make py-lint`** from the repo root and fix what they report.
|
||||
|
||||
1
.gitattributes
vendored
1
.gitattributes
vendored
@@ -1,3 +1,4 @@
|
||||
*.css linguist-detectable=false
|
||||
*.stories.mocks.tsx linguist-generated=true
|
||||
**/__story_mockdata__/** linguist-generated=true
|
||||
tests/e2e/drivers/** linguist-generated=true
|
||||
|
||||
@@ -1,5 +1,219 @@
|
||||
components:
|
||||
schemas:
|
||||
AiobservabilitytypesBlobPart:
|
||||
properties:
|
||||
content:
|
||||
type: string
|
||||
mime_type:
|
||||
nullable: true
|
||||
type: string
|
||||
modality:
|
||||
type: string
|
||||
type:
|
||||
type: string
|
||||
required:
|
||||
- type
|
||||
- modality
|
||||
- content
|
||||
type: object
|
||||
AiobservabilitytypesChatMessage:
|
||||
properties:
|
||||
name:
|
||||
nullable: true
|
||||
type: string
|
||||
parts:
|
||||
$ref: '#/components/schemas/AiobservabilitytypesParts'
|
||||
role:
|
||||
type: string
|
||||
required:
|
||||
- role
|
||||
- parts
|
||||
type: object
|
||||
AiobservabilitytypesCompactionPart:
|
||||
properties:
|
||||
content:
|
||||
nullable: true
|
||||
type: string
|
||||
id:
|
||||
nullable: true
|
||||
type: string
|
||||
type:
|
||||
type: string
|
||||
required:
|
||||
- type
|
||||
type: object
|
||||
AiobservabilitytypesFilePart:
|
||||
properties:
|
||||
file_id:
|
||||
type: string
|
||||
mime_type:
|
||||
nullable: true
|
||||
type: string
|
||||
modality:
|
||||
type: string
|
||||
type:
|
||||
type: string
|
||||
required:
|
||||
- type
|
||||
- modality
|
||||
- file_id
|
||||
type: object
|
||||
AiobservabilitytypesGenericPart:
|
||||
additionalProperties: {}
|
||||
type: object
|
||||
AiobservabilitytypesGenericServerToolCall:
|
||||
additionalProperties: {}
|
||||
type: object
|
||||
AiobservabilitytypesGenericServerToolCallResponse:
|
||||
additionalProperties: {}
|
||||
type: object
|
||||
AiobservabilitytypesInputMessages:
|
||||
items:
|
||||
$ref: '#/components/schemas/AiobservabilitytypesChatMessage'
|
||||
type: array
|
||||
AiobservabilitytypesOutputMessage:
|
||||
properties:
|
||||
finish_reason:
|
||||
nullable: true
|
||||
type: string
|
||||
name:
|
||||
nullable: true
|
||||
type: string
|
||||
parts:
|
||||
$ref: '#/components/schemas/AiobservabilitytypesParts'
|
||||
role:
|
||||
type: string
|
||||
required:
|
||||
- role
|
||||
- parts
|
||||
type: object
|
||||
AiobservabilitytypesOutputMessages:
|
||||
items:
|
||||
$ref: '#/components/schemas/AiobservabilitytypesOutputMessage'
|
||||
type: array
|
||||
AiobservabilitytypesPart:
|
||||
discriminator:
|
||||
mapping:
|
||||
blob: '#/components/schemas/AiobservabilitytypesBlobPart'
|
||||
compaction: '#/components/schemas/AiobservabilitytypesCompactionPart'
|
||||
file: '#/components/schemas/AiobservabilitytypesFilePart'
|
||||
reasoning: '#/components/schemas/AiobservabilitytypesReasoningPart'
|
||||
server_tool_call: '#/components/schemas/AiobservabilitytypesServerToolCallPart'
|
||||
server_tool_call_response: '#/components/schemas/AiobservabilitytypesServerToolCallResponsePart'
|
||||
text: '#/components/schemas/AiobservabilitytypesTextPart'
|
||||
tool_call: '#/components/schemas/AiobservabilitytypesToolCallRequestPart'
|
||||
tool_call_response: '#/components/schemas/AiobservabilitytypesToolCallResponsePart'
|
||||
uri: '#/components/schemas/AiobservabilitytypesUriPart'
|
||||
propertyName: type
|
||||
oneOf:
|
||||
- $ref: '#/components/schemas/AiobservabilitytypesTextPart'
|
||||
- $ref: '#/components/schemas/AiobservabilitytypesToolCallRequestPart'
|
||||
- $ref: '#/components/schemas/AiobservabilitytypesToolCallResponsePart'
|
||||
- $ref: '#/components/schemas/AiobservabilitytypesServerToolCallPart'
|
||||
- $ref: '#/components/schemas/AiobservabilitytypesServerToolCallResponsePart'
|
||||
- $ref: '#/components/schemas/AiobservabilitytypesBlobPart'
|
||||
- $ref: '#/components/schemas/AiobservabilitytypesFilePart'
|
||||
- $ref: '#/components/schemas/AiobservabilitytypesUriPart'
|
||||
- $ref: '#/components/schemas/AiobservabilitytypesReasoningPart'
|
||||
- $ref: '#/components/schemas/AiobservabilitytypesCompactionPart'
|
||||
- $ref: '#/components/schemas/AiobservabilitytypesGenericPart'
|
||||
type: object
|
||||
AiobservabilitytypesParts:
|
||||
items:
|
||||
$ref: '#/components/schemas/AiobservabilitytypesPart'
|
||||
type: array
|
||||
AiobservabilitytypesReasoningPart:
|
||||
properties:
|
||||
content:
|
||||
type: string
|
||||
type:
|
||||
type: string
|
||||
required:
|
||||
- type
|
||||
- content
|
||||
type: object
|
||||
AiobservabilitytypesServerToolCallPart:
|
||||
properties:
|
||||
id:
|
||||
nullable: true
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
server_tool_call:
|
||||
$ref: '#/components/schemas/AiobservabilitytypesGenericServerToolCall'
|
||||
type:
|
||||
type: string
|
||||
required:
|
||||
- type
|
||||
- name
|
||||
- server_tool_call
|
||||
type: object
|
||||
AiobservabilitytypesServerToolCallResponsePart:
|
||||
properties:
|
||||
id:
|
||||
nullable: true
|
||||
type: string
|
||||
server_tool_call_response:
|
||||
$ref: '#/components/schemas/AiobservabilitytypesGenericServerToolCallResponse'
|
||||
type:
|
||||
type: string
|
||||
required:
|
||||
- type
|
||||
- server_tool_call_response
|
||||
type: object
|
||||
AiobservabilitytypesTextPart:
|
||||
properties:
|
||||
content:
|
||||
type: string
|
||||
type:
|
||||
type: string
|
||||
required:
|
||||
- type
|
||||
- content
|
||||
type: object
|
||||
AiobservabilitytypesToolCallRequestPart:
|
||||
properties:
|
||||
arguments: {}
|
||||
id:
|
||||
nullable: true
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
type:
|
||||
type: string
|
||||
required:
|
||||
- type
|
||||
- name
|
||||
type: object
|
||||
AiobservabilitytypesToolCallResponsePart:
|
||||
properties:
|
||||
id:
|
||||
nullable: true
|
||||
type: string
|
||||
response:
|
||||
nullable: true
|
||||
type:
|
||||
type: string
|
||||
required:
|
||||
- type
|
||||
- response
|
||||
type: object
|
||||
AiobservabilitytypesUriPart:
|
||||
properties:
|
||||
mime_type:
|
||||
nullable: true
|
||||
type: string
|
||||
modality:
|
||||
type: string
|
||||
type:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
required:
|
||||
- type
|
||||
- modality
|
||||
- uri
|
||||
type: object
|
||||
AlertmanagertypesChannel:
|
||||
properties:
|
||||
createdAt:
|
||||
@@ -3367,6 +3581,69 @@ components:
|
||||
repeatVariable:
|
||||
type: string
|
||||
type: object
|
||||
DashboardtypesAIBuilderQuerySpec:
|
||||
properties:
|
||||
aggregations:
|
||||
items:
|
||||
$ref: '#/components/schemas/Querybuildertypesv5TraceAggregation'
|
||||
nullable: true
|
||||
type: array
|
||||
bucketOptions:
|
||||
$ref: '#/components/schemas/Querybuildertypesv5BucketOptions'
|
||||
cursor:
|
||||
type: string
|
||||
disabled:
|
||||
type: boolean
|
||||
filter:
|
||||
$ref: '#/components/schemas/Querybuildertypesv5Filter'
|
||||
functions:
|
||||
items:
|
||||
$ref: '#/components/schemas/Querybuildertypesv5Function'
|
||||
nullable: true
|
||||
type: array
|
||||
groupBy:
|
||||
items:
|
||||
$ref: '#/components/schemas/Querybuildertypesv5GroupByKey'
|
||||
nullable: true
|
||||
type: array
|
||||
having:
|
||||
$ref: '#/components/schemas/Querybuildertypesv5Having'
|
||||
legend:
|
||||
type: string
|
||||
limit:
|
||||
type: integer
|
||||
limitBy:
|
||||
$ref: '#/components/schemas/Querybuildertypesv5LimitBy'
|
||||
name:
|
||||
type: string
|
||||
offset:
|
||||
type: integer
|
||||
order:
|
||||
items:
|
||||
$ref: '#/components/schemas/Querybuildertypesv5OrderBy'
|
||||
nullable: true
|
||||
type: array
|
||||
secondaryAggregations:
|
||||
items:
|
||||
$ref: '#/components/schemas/Querybuildertypesv5SecondaryAggregation'
|
||||
nullable: true
|
||||
type: array
|
||||
selectFields:
|
||||
items:
|
||||
$ref: '#/components/schemas/TelemetrytypesTelemetryFieldKey'
|
||||
nullable: true
|
||||
type: array
|
||||
signal:
|
||||
enum:
|
||||
- traces
|
||||
type: string
|
||||
source:
|
||||
$ref: '#/components/schemas/TelemetrytypesSource'
|
||||
stepInterval:
|
||||
$ref: '#/components/schemas/Querybuildertypesv5Step'
|
||||
required:
|
||||
- signal
|
||||
type: object
|
||||
DashboardtypesAreaChartAppearance:
|
||||
properties:
|
||||
fillMode:
|
||||
@@ -4420,6 +4697,7 @@ components:
|
||||
DashboardtypesQueryPlugin:
|
||||
discriminator:
|
||||
mapping:
|
||||
signoz/AIBuilderQuery: '#/components/schemas/DashboardtypesQueryPluginVariantGithubComSigNozSignozPkgTypesDashboardtypesAIBuilderQuerySpec'
|
||||
signoz/BuilderQuery: '#/components/schemas/DashboardtypesQueryPluginVariantGithubComSigNozSignozPkgTypesDashboardtypesBuilderQuerySpec'
|
||||
signoz/ClickHouseSQL: '#/components/schemas/DashboardtypesQueryPluginVariantGithubComSigNozSignozPkgTypesQuerybuildertypesQuerybuildertypesv5ClickHouseQuery'
|
||||
signoz/CompositeQuery: '#/components/schemas/DashboardtypesQueryPluginVariantGithubComSigNozSignozPkgTypesQuerybuildertypesQuerybuildertypesv5CompositeQuery'
|
||||
@@ -4429,6 +4707,7 @@ components:
|
||||
propertyName: kind
|
||||
oneOf:
|
||||
- $ref: '#/components/schemas/DashboardtypesQueryPluginVariantGithubComSigNozSignozPkgTypesDashboardtypesBuilderQuerySpec'
|
||||
- $ref: '#/components/schemas/DashboardtypesQueryPluginVariantGithubComSigNozSignozPkgTypesDashboardtypesAIBuilderQuerySpec'
|
||||
- $ref: '#/components/schemas/DashboardtypesQueryPluginVariantGithubComSigNozSignozPkgTypesQuerybuildertypesQuerybuildertypesv5CompositeQuery'
|
||||
- $ref: '#/components/schemas/DashboardtypesQueryPluginVariantGithubComSigNozSignozPkgTypesQuerybuildertypesQuerybuildertypesv5QueryBuilderFormula'
|
||||
- $ref: '#/components/schemas/DashboardtypesQueryPluginVariantGithubComSigNozSignozPkgTypesQuerybuildertypesQuerybuildertypesv5PromQuery'
|
||||
@@ -4438,12 +4717,25 @@ components:
|
||||
DashboardtypesQueryPluginKind:
|
||||
enum:
|
||||
- signoz/BuilderQuery
|
||||
- signoz/AIBuilderQuery
|
||||
- signoz/CompositeQuery
|
||||
- signoz/Formula
|
||||
- signoz/PromQLQuery
|
||||
- signoz/ClickHouseSQL
|
||||
- signoz/TraceOperator
|
||||
type: string
|
||||
DashboardtypesQueryPluginVariantGithubComSigNozSignozPkgTypesDashboardtypesAIBuilderQuerySpec:
|
||||
properties:
|
||||
kind:
|
||||
enum:
|
||||
- signoz/AIBuilderQuery
|
||||
type: string
|
||||
spec:
|
||||
$ref: '#/components/schemas/DashboardtypesAIBuilderQuerySpec'
|
||||
required:
|
||||
- kind
|
||||
- spec
|
||||
type: object
|
||||
DashboardtypesQueryPluginVariantGithubComSigNozSignozPkgTypesDashboardtypesBuilderQuerySpec:
|
||||
properties:
|
||||
kind:
|
||||
@@ -10225,6 +10517,14 @@ components:
|
||||
items:
|
||||
$ref: '#/components/schemas/SpantypesEvent'
|
||||
type: array
|
||||
formattedInput:
|
||||
$ref: '#/components/schemas/AiobservabilitytypesInputMessages'
|
||||
formattedOutput:
|
||||
$ref: '#/components/schemas/AiobservabilitytypesOutputMessages'
|
||||
formatterWarnings:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
has_error:
|
||||
type: boolean
|
||||
kind_string:
|
||||
@@ -10267,6 +10567,9 @@ components:
|
||||
- attributes
|
||||
- events
|
||||
- references
|
||||
- formattedInput
|
||||
- formattedOutput
|
||||
- formatterWarnings
|
||||
type: object
|
||||
SpantypesTraceAISummary:
|
||||
properties:
|
||||
@@ -10347,6 +10650,14 @@ components:
|
||||
flags:
|
||||
minimum: 0
|
||||
type: integer
|
||||
formattedInput:
|
||||
$ref: '#/components/schemas/AiobservabilitytypesInputMessages'
|
||||
formattedOutput:
|
||||
$ref: '#/components/schemas/AiobservabilitytypesOutputMessages'
|
||||
formatterWarnings:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
has_children:
|
||||
type: boolean
|
||||
has_error:
|
||||
@@ -16134,9 +16445,12 @@ paths:
|
||||
/api/v1/traces/{traceID}/thread:
|
||||
get:
|
||||
deprecated: false
|
||||
description: Returns the spans carrying gen_ai input or output messages in timestamp
|
||||
order. Pass nextCursor as after or prevCursor as before to page, or spanId
|
||||
to open the page around a span.
|
||||
description: Returns the gen_ai spans of a trace, model calls, tool calls and
|
||||
agent spans, in timestamp order. The messages a span carries are decoded into
|
||||
formattedInput and formattedOutput, with formatterWarnings holding what the
|
||||
converter could not resolve; a span without messages has both empty. Pass
|
||||
nextCursor as after or prevCursor as before to page, or spanId to open the
|
||||
page around a span.
|
||||
operationId: GetTraceThread
|
||||
parameters:
|
||||
- description: Page size, at most 100. 0 means 20.
|
||||
@@ -28280,7 +28594,9 @@ paths:
|
||||
deprecated: false
|
||||
description: Returns the waterfall view of spans including all spans if total
|
||||
spans are under a limit, a max count otherwise. Aggregations are dropped compared
|
||||
to v3
|
||||
to v3. A span carrying gen_ai input or output messages also has them decoded
|
||||
into formattedInput and formattedOutput, with formatterWarnings holding what
|
||||
the converter could not resolve.
|
||||
operationId: GetWaterfallV4
|
||||
parameters:
|
||||
- in: path
|
||||
|
||||
@@ -20,6 +20,7 @@ We **recommend** (almost enforce) reviewing these guides before contributing to
|
||||
- [Provider](provider.md) - Dependency injection and provider patterns
|
||||
- [Packages](packages.md) - Naming, layout, and conventions for `pkg/` packages
|
||||
- [Service](service.md) - Managed service lifecycle with `factory.Service`
|
||||
- [Span Mapper](spanmapper.md) - Provenance of the shipped span mapper definitions and what is left unmapped
|
||||
- [SQL](sql.md) - Database and SQL patterns
|
||||
- [DSL Filtering to SQL](dslfilteringtosql.md) - Compiling the list filter DSL to relational-store WHERE clauses
|
||||
- [Types](types.md) - Domain types, request/response bodies, and storage rows in `pkg/types/`
|
||||
|
||||
62
docs/contributing/go/spanmapper.md
Normal file
62
docs/contributing/go/spanmapper.md
Normal file
@@ -0,0 +1,62 @@
|
||||
# Span Mapper
|
||||
|
||||
Where each source key in the shipped definitions under `pkg/modules/spanmapper/implspanmapper/fs/definitions/` comes from. A group runs on spans with an attribute key containing one of its condition substrings; sources are tried by priority and the first present wins; an existing target is never overwritten. Bump `version` on every change.
|
||||
|
||||
## gen_ai.llm
|
||||
|
||||
Condition: `model`, `llm.input_messages` (OpenInference sets `llm.model_name` from the response, so a failed call has no model key).
|
||||
|
||||
| Source | Emitted by |
|
||||
|---|---|
|
||||
| `llm.model_name`, `llm.provider`, `llm.system`, `llm.token_count.prompt`, `llm.token_count.completion`, `llm.token_count.prompt_details.cache_read`, `llm.token_count.prompt_details.cache_write`, `llm.token_count.completion_details.reasoning`, `llm.finish_reason`, `input.value`, `output.value`, `embedding.model_name`, `reranker.model_name`, `openinference.span.kind` | OpenInference |
|
||||
| `llm.request.model`, `llm.response.model`, `llm.vendor`, `llm.usage.prompt_tokens`, `llm.usage.completion_tokens`, `llm.response.finish_reason`, `llm.response.stop_reason` | OpenLLMetry before the `gen_ai` rename |
|
||||
| `gen_ai.system`, `gen_ai.usage.prompt_tokens`, `gen_ai.usage.completion_tokens`, `gen_ai.usage.cache_read_input_tokens`, `gen_ai.usage.cache_creation_input_tokens`, `gen_ai.usage.reasoning_tokens`, `gen_ai.prompt`, `gen_ai.completion`, `llm.request.type`, `traceloop.span.kind` | OpenLLMetry, LiteLLM, CrewAI, Mistral, LangSmith, OpenRouter gateway |
|
||||
| `gen_ai.usage.cache_write.input_tokens` | OTel semconv draft spelling, Codex |
|
||||
| `gen_ai.response.finish_reason`, `gen_ai.usage.prompt_token_details.cached_read_tokens`, `gen_ai.usage.completion_token_details.reasoning_tokens` | Bifrost gateway |
|
||||
| `gen_ai.usage.input_tokens.cached` | OpenRouter gateway |
|
||||
| `gen_ai.response.model`, last source for the request model | vLLM, which writes only the response model |
|
||||
| `ai.model.id`, `ai.model.provider`, `ai.response.model`, `ai.response.finishReason`, `ai.prompt.messages`, `ai.response.text`, `ai.response.toolCalls`, `ai.usage.promptTokens`, `ai.usage.completionTokens` | Vercel AI SDK v4, Mastra |
|
||||
| `ai.usage.inputTokens`, `ai.usage.outputTokens`, `ai.usage.cachedInputTokens`, `ai.usage.reasoningTokens` | Vercel AI SDK v5 |
|
||||
| `langfuse.observation.model.name`, `langfuse.session.id` | Langfuse SDK v3 |
|
||||
| `session.id` | OpenInference, Langfuse, Claude Code |
|
||||
| `gen_ai.client.session_id` | opentelemetry-hooks runner (Cursor, Copilot, Claude Code hooks) |
|
||||
| `model`, `input_tokens`, `output_tokens`, `cache_read_tokens`, `cache_creation_tokens` | Claude Code `claude_code.llm_request` spans ([docs](https://code.claude.com/docs/en/monitoring-usage#span-attributes)) |
|
||||
|
||||
## gen_ai.tool
|
||||
|
||||
Condition: `tool.name` (OpenInference), `ai.toolCall.` (Vercel, Mastra), `tool_call_args`, `tool_response` (Google ADK), `tool_use_id` (Claude Code, opentelemetry-hooks). Not `tool_name`: Codex records it on the model call span.
|
||||
|
||||
| Source | Emitted by |
|
||||
|---|---|
|
||||
| `tool.name`, `tool.id`, `tool.description`, `input.value`, `output.value`, `openinference.span.kind` | OpenInference |
|
||||
| `ai.toolCall.name`, `ai.toolCall.id`, `ai.toolCall.args`, `ai.toolCall.result` | Vercel AI SDK, Mastra |
|
||||
| `traceloop.entity.input`, `traceloop.entity.output`, `traceloop.span.kind`, `llm.request.type` | OpenLLMetry |
|
||||
| `gcp.vertex.agent.tool_call_args`, `gcp.vertex.agent.tool_response` | Google ADK |
|
||||
| `tool_name` | Claude Code `claude_code.tool` spans |
|
||||
| `gen_ai.client.tool_name`, `gen_ai.client.tool_use_id` | opentelemetry-hooks runner (Cursor, Copilot, Claude Code hooks) |
|
||||
|
||||
## gen_ai.agent
|
||||
|
||||
Condition: `agent`.
|
||||
|
||||
| Source | Emitted by |
|
||||
|---|---|
|
||||
| `agent.name`, `agent.id`, `agent.description`, `openinference.span.kind` | OpenInference; `agent.id` and `agent.description` seen in no capture |
|
||||
| `agent_name`, `final_result` | Pydantic AI |
|
||||
| `crewai.agent.role`, `crewai.agent.id` | CrewAI |
|
||||
| `traceloop.span.kind`, `llm.request.type` | OpenLLMetry |
|
||||
| `agent_id` | Claude Code |
|
||||
|
||||
## Coverage of documented integrations
|
||||
|
||||
Every integration under [signoz.io/docs/llm-observability](https://signoz.io/docs/llm-observability/) was checked by looking at the instrumentation its page installs and the keys that instrumentation writes.
|
||||
|
||||
- Most pages install OpenInference or OpenLLMetry, or an SDK that writes OTel GenAI semconv itself (OpenAI, OpenAI Agents, Pydantic AI, AutoGen, Microsoft Agent Framework, Semantic Kernel, Copilot Chat, LiteLLM, OpenRouter). Their keys are the rows above.
|
||||
- Three write their own keys and have their own rows: Claude Code and Claude Agent SDK, Cursor through opentelemetry-hooks, vLLM (response model only).
|
||||
- Codex cannot be priced by renaming: `model` sits on the sampling function spans, `gen_ai.usage.*` on the child `handle_responses` span, `conversation.id` on the `codex.sampling` phase span, never together on one span.
|
||||
|
||||
To check a new integration, capture a trace and look for a model, usage, tool or messages key that no row above lists.
|
||||
|
||||
## Left unmapped on purpose
|
||||
|
||||
LiteLLM `llm.openai.messages` and `llm.openai.choices` (debug echo of the parent span's messages, Python repr). Total tokens and vendor cost keys (no target; the pricing processor owns cost). `ai.response.msToFirstChunk` and `gen_ai.latency.time_to_first_token` (milliseconds and seconds under one target).
|
||||
@@ -86,6 +86,9 @@ tests/
|
||||
├── .env.local # generated by bootstrap/setup.py (gitignored)
|
||||
├── bootstrap/
|
||||
│ └── setup.py # test_setup / test_teardown — pytest lifecycle
|
||||
├── drivers/ # the driver kit and one driver per feature
|
||||
│ ├── kit.ts
|
||||
│ └── routing.ts
|
||||
├── fixtures/ # Playwright test fixtures (test.extend) only
|
||||
│ └── auth.ts
|
||||
├── helpers/ # function helpers + the constants they share with tests
|
||||
@@ -112,6 +115,24 @@ These two folders look similar but mean different things:
|
||||
|
||||
Rule of thumb: if it's a `test.extend` fixture, put it in `fixtures/`. If it's a function you call explicitly (or a constant the function uses), put it in `helpers/`. If it's a static file the helpers read, put it in `testdata/`.
|
||||
|
||||
### Drivers
|
||||
|
||||
New suites use a driver rather than loose helpers. A driver is a function that closes over the page and returns the feature's actions and locators. `drivers/kit.ts` turns it into a fixture, so a spec imports one module and never passes a `page` around:
|
||||
|
||||
```ts
|
||||
import { expect, PATH, test } from '../../drivers/routing';
|
||||
|
||||
test('TC-09 RouteTab switches the url', async ({ routing }) => {
|
||||
await routing.goto(PATH.metricsSummary);
|
||||
await expect(routing.activeRouteTab(PATH.metricsSummary)).toBeVisible();
|
||||
});
|
||||
```
|
||||
|
||||
- `driver(name, build)` adds a test-scoped fixture built on `authedPage`.
|
||||
- `seeded(name, open, close)` adds a worker-scoped seed. `open` and `close` run on a throwaway admin page.
|
||||
- A driver reuses helpers by calling them, never by reaching for another fixture.
|
||||
- `drivers/` is AI-owned: coding agents generate and maintain it with the specs, and `.gitattributes` collapses it in diffs.
|
||||
|
||||
### Extended fixtures
|
||||
|
||||
For features needing complex setup (API-seeded data, ruler evaluation waits, cleanup), create domain-specific fixtures that extend `auth`. Group them in `fixtures/<domain>/`.
|
||||
|
||||
@@ -297,6 +297,8 @@
|
||||
// Forces useDashboardFetchRequired() outside the root V2 pages (allowlisted in overrides below)
|
||||
"signoz/no-msw-in-story-file": "error",
|
||||
// Bans msw imports in *.stories.tsx; handlers/mock data belong in the sibling .stories.mocks.tsx
|
||||
"signoz/no-direct-react-router-import": "error",
|
||||
// Steers call sites to the src/lib/router facade (allowlisted in overrides below)
|
||||
"no-restricted-globals": [
|
||||
"error",
|
||||
{
|
||||
@@ -582,6 +584,23 @@
|
||||
"rules": {
|
||||
"signoz/no-dashboard-fetch-outside-root": "off"
|
||||
}
|
||||
},
|
||||
{
|
||||
// The react-router allowlist: the facade itself, the route table, `useSafeNavigate`,
|
||||
// the history singleton and the test and story router mounts. Test files are
|
||||
// deliberately absent: they render at a route or use tests/router-mocks.
|
||||
"files": [
|
||||
"src/lib/router/**",
|
||||
"src/AppRoutes/**",
|
||||
"src/hooks/useSafeNavigate.ts",
|
||||
"src/lib/history.ts",
|
||||
"src/tests/router.tsx",
|
||||
"src/storybook/providers/StorybookProviders.tsx",
|
||||
"src/storybook/renderAtRoute.tsx"
|
||||
],
|
||||
"rules": {
|
||||
"signoz/no-direct-react-router-import": "off"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ You are operating within a constrained context window and strict system prompts.
|
||||
- Never create barrel files.
|
||||
- When writing new css, prefer CSS Modules
|
||||
- Use ./docs/css-modules-guide.md as reference on how to write good CSS Modules.
|
||||
- Before styling a `@signozhq/ui` component, check its props in `node_modules/@signozhq/ui/dist/<component>/*.d.ts` for `className`/`style` support; some omit them. Avoid overriding the component's CSS vars unless explicitly needed.
|
||||
- When writing code that could need authorization checks, read ./src/lib/authz/README.md
|
||||
|
||||
3. FORCED VERIFICATION: Your internal tools mark file writes as successful even if the code does not compile. You are FORBIDDEN from reporting a task as complete until you have:
|
||||
|
||||
@@ -41,6 +41,12 @@ These hold for every page. The per-pattern sections below only add to them.
|
||||
`create` on API Keys, so blocking the outer container would hide work they are allowed to do.
|
||||
7. **Verbs not covered here** (`attach`, `detach`, `assignee`) behave like `delete`: gate the control that triggers
|
||||
them, not the surrounding content.
|
||||
8. **Expose the selector.** A role is scoped to a resource by pasting its selector into the role editor, so every
|
||||
resource a role can be scoped to shows that selector with a copy control on its detail view or drawer. The selector
|
||||
is whatever the editor accepts for the resource (dashboard ID, service account ID, role name, ...), listed per
|
||||
resource in [permissions.config.ts](../src/container/RolesSettings/permissions.config.ts). It is already known from
|
||||
the row or URL, so it stays visible and copyable without `read`. Reference: the ID field in
|
||||
[ServiceAccountDrawer/OverviewTab.tsx](../src/components/ServiceAccountDrawer/OverviewTab.tsx).
|
||||
|
||||
## Page patterns
|
||||
|
||||
@@ -136,6 +142,8 @@ Use the existing components. Create a new one only if none fit.
|
||||
- [ ] Am I gating a shared component rather than a page or section?
|
||||
- If so, it must stay functional with no permission. Example: the Query Builder works without field suggestions when
|
||||
the user cannot read them.
|
||||
- [ ] Does the page expose the resource's selector with a copy control (rule 8)?
|
||||
- The expected format is the `selectorPlaceholder` for the resource in `permissions.config.ts`.
|
||||
|
||||
## Testing
|
||||
|
||||
|
||||
@@ -41,6 +41,20 @@ if (!HTMLElement.prototype.releasePointerCapture) {
|
||||
HTMLElement.prototype.releasePointerCapture = function (): void {};
|
||||
}
|
||||
|
||||
// jsdom has no PointerEvent; Base UI Switch constructs one on click.
|
||||
if (typeof window.PointerEvent === 'undefined') {
|
||||
class PointerEventMock extends MouseEvent {
|
||||
pointerId: number;
|
||||
pointerType: string;
|
||||
constructor(type: string, init: PointerEventInit = {}) {
|
||||
super(type, init);
|
||||
this.pointerId = init.pointerId ?? 0;
|
||||
this.pointerType = init.pointerType ?? '';
|
||||
}
|
||||
}
|
||||
(window as any).PointerEvent = PointerEventMock;
|
||||
}
|
||||
|
||||
if (typeof window.IntersectionObserver === 'undefined') {
|
||||
class IntersectionObserverMock {
|
||||
observe(): void {}
|
||||
|
||||
@@ -49,12 +49,13 @@
|
||||
"@dnd-kit/sortable": "8.0.0",
|
||||
"@dnd-kit/utilities": "3.2.2",
|
||||
"@grafana/data": "^11.6.15",
|
||||
"@lezer/highlight": "1.2.1",
|
||||
"@monaco-editor/react": "^4.7.0",
|
||||
"@sentry/react": "10.57.0",
|
||||
"@sentry/vite-plugin": "5.3.0",
|
||||
"@signozhq/design-tokens": "2.1.6",
|
||||
"@signozhq/design-tokens": "2.2.1",
|
||||
"@signozhq/icons": "0.4.0",
|
||||
"@signozhq/ui": "0.1.0",
|
||||
"@signozhq/ui": "0.2.0",
|
||||
"@tanstack/react-table": "8.21.3",
|
||||
"@tanstack/react-virtual": "3.13.22",
|
||||
"@uiw/codemirror-theme-copilot": "4.23.11",
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
import { ruleTester } from './rule-tester.mjs';
|
||||
|
||||
const FACADE = 'src/lib/router facade';
|
||||
const HISTORY = 'lib/history singleton';
|
||||
const MOCK = 'tests/router-mocks helpers';
|
||||
|
||||
await ruleTester({
|
||||
rule: 'no-direct-react-router-import',
|
||||
valid: [
|
||||
{
|
||||
name: 'facade import',
|
||||
code: "import { useAppNavigate } from 'lib/router/useAppNavigate';",
|
||||
},
|
||||
{
|
||||
name:
|
||||
'the history package itself is a version-bump concern, not a facade one',
|
||||
code: "import { createBrowserHistory } from 'history';",
|
||||
},
|
||||
{
|
||||
name: 'unrelated module whose name contains history',
|
||||
code: "import { useHistoryPanel } from 'container/HistoryPanel';",
|
||||
},
|
||||
{
|
||||
name: 'mocking a facade module',
|
||||
code: "jest.mock('lib/router/navigation');",
|
||||
},
|
||||
{
|
||||
name: 'jest call that does not name a module',
|
||||
code: 'jest.fn();',
|
||||
},
|
||||
{
|
||||
name: 'require of an unrelated module',
|
||||
code: "const x = require('lib/dashboardVariables');",
|
||||
},
|
||||
{
|
||||
name: 'export without a source',
|
||||
code: 'const a = 1;\nexport { a };',
|
||||
},
|
||||
],
|
||||
invalid: [
|
||||
{
|
||||
name: 'jest.mock of react-router-dom',
|
||||
code: "jest.mock('react-router-dom', () => ({}));",
|
||||
errors: [{ message: MOCK, line: 1, column: 11 }],
|
||||
},
|
||||
{
|
||||
name: 'jest.requireActual of react-router-dom',
|
||||
code: "const actual = jest.requireActual('react-router-dom');",
|
||||
errors: [{ message: MOCK }],
|
||||
},
|
||||
{
|
||||
name: 'jest.mock of lib/history',
|
||||
code: "jest.mock('lib/history');",
|
||||
errors: [{ message: MOCK }],
|
||||
},
|
||||
{
|
||||
name: 'jest.doMock of react-router-dom-v5-compat',
|
||||
code: "jest.doMock('react-router-dom-v5-compat');",
|
||||
errors: [{ message: MOCK }],
|
||||
},
|
||||
{
|
||||
name: 'react-router-dom named import',
|
||||
code: "import { useHistory } from 'react-router-dom';",
|
||||
errors: [{ message: FACADE, line: 1, column: 28 }],
|
||||
},
|
||||
{
|
||||
name: 'react-router named import',
|
||||
code: "import { useLocation } from 'react-router';",
|
||||
errors: [{ message: FACADE }],
|
||||
},
|
||||
{
|
||||
name: 'react-router-dom type-only import',
|
||||
code: "import type { RouteProps } from 'react-router-dom';",
|
||||
errors: [{ message: FACADE }],
|
||||
},
|
||||
{
|
||||
name: 'react-router-dom-v5-compat',
|
||||
code: "import { useNavigate } from 'react-router-dom-v5-compat';",
|
||||
errors: [{ message: 'compat package is an implementation detail' }],
|
||||
},
|
||||
{
|
||||
name: 'lib/history default import',
|
||||
code: "import history from 'lib/history';",
|
||||
errors: [{ message: HISTORY }],
|
||||
},
|
||||
{
|
||||
name: 'lib/history through the @/ alias',
|
||||
code: "import history from '@/lib/history';",
|
||||
errors: [{ message: HISTORY }],
|
||||
},
|
||||
{
|
||||
name: 'jest.mock of lib/history through the @/ alias',
|
||||
code: "jest.mock('@/lib/history');",
|
||||
errors: [{ message: MOCK }],
|
||||
},
|
||||
{
|
||||
name: 'lib/history require',
|
||||
code: "const history = require('lib/history').default;",
|
||||
errors: [{ message: HISTORY }],
|
||||
},
|
||||
{
|
||||
name: 'dynamic import',
|
||||
code: "const mod = await import('react-router-dom');",
|
||||
errors: [{ message: FACADE }],
|
||||
},
|
||||
{
|
||||
name: 're-export',
|
||||
code: "export { Link } from 'react-router-dom';",
|
||||
errors: [{ message: FACADE }],
|
||||
},
|
||||
{
|
||||
name: 'export all',
|
||||
code: "export * from 'react-router-dom';",
|
||||
errors: [{ message: FACADE }],
|
||||
},
|
||||
{
|
||||
name: 'one report per import statement',
|
||||
code:
|
||||
"import { Link } from 'react-router-dom';\nimport history from 'lib/history';",
|
||||
errors: [
|
||||
{ message: FACADE, line: 1 },
|
||||
{ message: HISTORY, line: 2 },
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
123
frontend/plugins/rules/no-direct-react-router-import.mjs
Normal file
123
frontend/plugins/rules/no-direct-react-router-import.mjs
Normal file
@@ -0,0 +1,123 @@
|
||||
/**
|
||||
* Rule: no-direct-react-router-import
|
||||
*
|
||||
* Every router concern goes through the `src/lib/router/*` facade, so a router
|
||||
* upgrade is a change to one directory instead of ~300 call sites. This rule
|
||||
* keeps new call sites, and test mocks, from reaching past it.
|
||||
*
|
||||
* `history` (the package) is deliberately not flagged: it is a transitive concern of
|
||||
* the version bump, not something the facade replaces.
|
||||
*
|
||||
* The allowlist (the facade itself, the route table, `useSafeNavigate`, the history
|
||||
* singleton and the test and story router mounts) is applied via overrides in
|
||||
* .oxlintrc.json, not here, so the file list stays visible next to the severity.
|
||||
*/
|
||||
|
||||
import path from 'node:path';
|
||||
|
||||
const HISTORY_MODULE_SUFFIX = path.join('src', 'lib', 'history');
|
||||
|
||||
const JEST_MODULE_METHODS = new Set([
|
||||
'mock',
|
||||
'doMock',
|
||||
'unmock',
|
||||
'dontMock',
|
||||
'setMock',
|
||||
'requireActual',
|
||||
'requireMock',
|
||||
'createMockFromModule',
|
||||
]);
|
||||
|
||||
const MESSAGE_IDS = {
|
||||
'react-router': 'v5Router',
|
||||
'react-router-dom': 'v5Router',
|
||||
'react-router-dom-v5-compat': 'v6Compat',
|
||||
'lib/history': 'historySingleton',
|
||||
'@/lib/history': 'historySingleton',
|
||||
};
|
||||
|
||||
/** Resolves `./history` / `../history` so files inside src/lib count too. */
|
||||
function isHistoryModule(specifier, filename) {
|
||||
if (!specifier.startsWith('.') || !filename) {
|
||||
return false;
|
||||
}
|
||||
const resolved = path.resolve(path.dirname(filename), specifier);
|
||||
return (
|
||||
resolved.endsWith(HISTORY_MODULE_SUFFIX) ||
|
||||
resolved.endsWith(`${HISTORY_MODULE_SUFFIX}.ts`)
|
||||
);
|
||||
}
|
||||
|
||||
function messageIdFor(specifier, filename) {
|
||||
if (MESSAGE_IDS[specifier] !== undefined) {
|
||||
return MESSAGE_IDS[specifier];
|
||||
}
|
||||
return isHistoryModule(specifier, filename) ? 'historySingleton' : null;
|
||||
}
|
||||
|
||||
export default {
|
||||
meta: {
|
||||
type: 'suggestion',
|
||||
docs: {
|
||||
description:
|
||||
'Disallow direct react-router / lib/history imports; import from src/lib/router instead',
|
||||
category: 'React Router migration',
|
||||
},
|
||||
schema: [],
|
||||
messages: {
|
||||
v5Router:
|
||||
'Do not import react-router-dom directly. Use the src/lib/router facade — useAppNavigate, useAppLocation, useAppParams, AppLink, Redirect, matchRoute — so a router upgrade stays contained.',
|
||||
v6Compat:
|
||||
'Do not import react-router-dom-v5-compat directly. Use the src/lib/router facade instead; the compat package is an implementation detail of the migration and disappears with it.',
|
||||
historySingleton:
|
||||
'Do not import the lib/history singleton. Use useAppNavigate() inside components, or the imperative helpers in src/lib/router/navigation.ts outside them — history loses basename handling once the router owns it.',
|
||||
mock:
|
||||
'Do not mock the router directly. Render at a route with tests/test-utils, or spy on the facade with the tests/router-mocks helpers.',
|
||||
},
|
||||
},
|
||||
|
||||
create(context) {
|
||||
const report = (sourceNode, overrideMessageId) => {
|
||||
if (
|
||||
sourceNode === null ||
|
||||
sourceNode === undefined ||
|
||||
typeof sourceNode.value !== 'string'
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const messageId = messageIdFor(sourceNode.value, context.filename);
|
||||
if (messageId !== null) {
|
||||
context.report({
|
||||
node: sourceNode,
|
||||
messageId: overrideMessageId ?? messageId,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
ImportDeclaration: (node) => report(node.source),
|
||||
ImportExpression: (node) => report(node.source),
|
||||
ExportAllDeclaration: (node) => report(node.source),
|
||||
ExportNamedDeclaration: (node) => report(node.source),
|
||||
CallExpression(node) {
|
||||
const { callee } = node;
|
||||
if (
|
||||
(callee.type === 'Identifier' && callee.name === 'require') ||
|
||||
callee.type === 'Import'
|
||||
) {
|
||||
report(node.arguments[0]);
|
||||
return;
|
||||
}
|
||||
if (
|
||||
callee.type === 'MemberExpression' &&
|
||||
callee.object.type === 'Identifier' &&
|
||||
callee.object.name === 'jest' &&
|
||||
callee.property.type === 'Identifier' &&
|
||||
JEST_MODULE_METHODS.has(callee.property.name)
|
||||
) {
|
||||
report(node.arguments[0], 'mock');
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -16,6 +16,7 @@ import noDashboardFetchOutsideRoot from './rules/no-dashboard-fetch-outside-root
|
||||
import noConditionalTextNodesWithSiblings from './rules/no-conditional-text-nodes-with-siblings.mjs';
|
||||
import noReturnTextNodes from './rules/no-return-text-nodes.mjs';
|
||||
import noMswInStoryFile from './rules/no-msw-in-story-file.mjs';
|
||||
import noDirectReactRouterImport from './rules/no-direct-react-router-import.mjs';
|
||||
|
||||
export default {
|
||||
meta: {
|
||||
@@ -33,5 +34,6 @@ export default {
|
||||
'no-conditional-text-nodes-with-siblings': noConditionalTextNodesWithSiblings,
|
||||
'no-return-text-nodes': noReturnTextNodes,
|
||||
'no-msw-in-story-file': noMswInStoryFile,
|
||||
'no-direct-react-router-import': noDirectReactRouterImport,
|
||||
},
|
||||
};
|
||||
|
||||
441
frontend/pnpm-lock.yaml
generated
441
frontend/pnpm-lock.yaml
generated
@@ -62,6 +62,9 @@ importers:
|
||||
'@grafana/data':
|
||||
specifier: ^11.6.15
|
||||
version: 11.6.15(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
'@lezer/highlight':
|
||||
specifier: 1.2.1
|
||||
version: 1.2.1
|
||||
'@monaco-editor/react':
|
||||
specifier: ^4.7.0
|
||||
version: 4.7.0(monaco-editor@0.55.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
@@ -72,14 +75,14 @@ importers:
|
||||
specifier: 5.3.0
|
||||
version: 5.3.0
|
||||
'@signozhq/design-tokens':
|
||||
specifier: 2.1.6
|
||||
version: 2.1.6
|
||||
specifier: 2.2.1
|
||||
version: 2.2.1
|
||||
'@signozhq/icons':
|
||||
specifier: 0.4.0
|
||||
version: 0.4.0
|
||||
'@signozhq/ui':
|
||||
specifier: 0.1.0
|
||||
version: 0.1.0(@emotion/is-prop-valid@1.2.0)(@signozhq/icons@0.4.0)(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react-router-dom@5.3.4(react@18.2.0))(react-router@6.30.6(react@18.2.0))(react@18.2.0)
|
||||
specifier: 0.2.0
|
||||
version: 0.2.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.2.0)(@signozhq/icons@0.4.0)(@types/react-dom@18.0.10)(@types/react@18.0.26)(date-fns@4.1.0)(react-dom@18.2.0(react@18.2.0))(react-router-dom@5.3.4(react@18.2.0))(react-router@6.30.6(react@18.2.0))(react@18.2.0)
|
||||
'@tanstack/react-table':
|
||||
specifier: 8.21.3
|
||||
version: 8.21.3(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
@@ -1284,6 +1287,33 @@ packages:
|
||||
resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@base-ui/react@1.7.0':
|
||||
resolution: {integrity: sha512-j+8QjX44C32jrXD/qyEAGpFr70FRpGL2CY61mQd9nBPWN737CK0xxD1ceJ055rW4RtdvFDT1e7otzdlfxvsYug==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
peerDependencies:
|
||||
'@date-fns/tz': ^1.2.0
|
||||
'@types/react': ^17 || ^18 || ^19
|
||||
date-fns: ^4.0.0
|
||||
react: ^17 || ^18 || ^19
|
||||
react-dom: ^17 || ^18 || ^19
|
||||
peerDependenciesMeta:
|
||||
'@date-fns/tz':
|
||||
optional: true
|
||||
'@types/react':
|
||||
optional: true
|
||||
date-fns:
|
||||
optional: true
|
||||
|
||||
'@base-ui/utils@0.3.2':
|
||||
resolution: {integrity: sha512-oWy1aq/I2GmYjpl4PhEAhzflF8VPGKgZeq0xAWTbfD5KBWyxcN0ZP2+WHSUm/5Z6lVMBDLReLcoXwSYoRc/zNQ==}
|
||||
peerDependencies:
|
||||
'@types/react': ^17 || ^18 || ^19
|
||||
react: ^17 || ^18 || ^19
|
||||
react-dom: ^17 || ^18 || ^19
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@bcoe/v8-coverage@0.2.3':
|
||||
resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==}
|
||||
|
||||
@@ -1726,20 +1756,20 @@ packages:
|
||||
resolution: {integrity: sha512-rZAP3aVgB9ds9KOeUSL+zZ21hPmo8dh6fnIFwRQj5EAZl9gzR7wxYbYXYysAM8CTqGmUGyp2S4kUdV17MnGuWQ==}
|
||||
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
|
||||
|
||||
'@floating-ui/core@1.5.2':
|
||||
resolution: {integrity: sha512-Ii3MrfY/GAIN3OhXNzpCKaLxHQfJF9qvwq/kEJYdqDxeIHa01K8sldugal6TmeeXl+WMvhv9cnVzUTaFFJF09A==}
|
||||
'@floating-ui/core@1.8.0':
|
||||
resolution: {integrity: sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==}
|
||||
|
||||
'@floating-ui/dom@1.5.3':
|
||||
resolution: {integrity: sha512-ClAbQnEqJAKCJOEbbLo5IUlZHkNszqhuxS4fHAVxRPXPya6Ysf2G8KypnYcOTpx6I8xcgF9bbHb6g/2KpbV8qA==}
|
||||
'@floating-ui/dom@1.8.0':
|
||||
resolution: {integrity: sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg==}
|
||||
|
||||
'@floating-ui/react-dom@2.0.4':
|
||||
resolution: {integrity: sha512-CF8k2rgKeh/49UrnIBs4BdxPUV6vize/Db1d/YbCLyp9GiVZ0BEwf5AiDSxJRCr6yOkGqTFHtmrULxkEfYZ7dQ==}
|
||||
'@floating-ui/react-dom@2.1.9':
|
||||
resolution: {integrity: sha512-JDjEFGCpImxDCA7JJKviA0M9+RtmJdj0m/NVU5IMgBK+AmZouAQQ7/+2GLH0GXXY0YMw9oXPB8hKdbPYg5QLYg==}
|
||||
peerDependencies:
|
||||
react: '>=16.8.0'
|
||||
react-dom: '>=16.8.0'
|
||||
|
||||
'@floating-ui/utils@0.1.6':
|
||||
resolution: {integrity: sha512-OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A==}
|
||||
'@floating-ui/utils@0.2.12':
|
||||
resolution: {integrity: sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==}
|
||||
|
||||
'@gerrit0/mini-shiki@3.23.0':
|
||||
resolution: {integrity: sha512-bEMORlG0cqdjVyCEuU0cDQbORWX+kYCeo0kV1lbxF5bt4r7SID2l9bqsxJEM0zndaxpOUT7riCyIVEuqq/Ynxg==}
|
||||
@@ -2920,19 +2950,6 @@ packages:
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-checkbox@1.3.11':
|
||||
resolution: {integrity: sha512-Gnptr9pDDQxD3hgq2dtPbtrp/c2qH1mBwIzw3X/ivrMb2e1t0jMTi606fVEqFPaQR1ggXIVQWKj3P2WW9v7zGQ==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-collection@1.1.15':
|
||||
resolution: {integrity: sha512-9W+B9NPF0NaaPh/1NJd3+KqsnlLqU9H7T2rvww+fp+T/evVXdNAyYcnfRQZFOjkR1ajQp3yORlqnI8soawLvNA==}
|
||||
peerDependencies:
|
||||
@@ -2946,15 +2963,6 @@ packages:
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-compose-refs@1.1.2':
|
||||
resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-compose-refs@1.1.5':
|
||||
resolution: {integrity: sha512-+48PbAAbq3didjJxa+OaWY2ZwgAKsNiRGyeHKszblZMQ+kcpd9pAaT11cMkGEie0vsOi3QdeTE6d5Fe3Gn61kA==}
|
||||
peerDependencies:
|
||||
@@ -3008,19 +3016,6 @@ packages:
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-dropdown-menu@2.1.24':
|
||||
resolution: {integrity: sha512-geq8l2rJkxvkXsT9RMgtUE3P8pITFpTsvYpbySi1IH4fZEABD/Gp85myayFgxk0ktljGMJnCbeFkyTusvSvv7g==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-focus-guards@1.1.6':
|
||||
resolution: {integrity: sha512-RNOJjfZMTyBM6xYmV3IVGXkPjIhcBAuv48POevAXwrGJhkWZ9p1rFoIS1JFooPuT193AZmRsCPhpoVJxx6OPoQ==}
|
||||
peerDependencies:
|
||||
@@ -3048,15 +3043,6 @@ packages:
|
||||
peerDependencies:
|
||||
react: ^16.x || ^17.x || ^18.x || ^19.0.0 || ^19.0.0-rc
|
||||
|
||||
'@radix-ui/react-id@1.1.1':
|
||||
resolution: {integrity: sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-id@1.1.4':
|
||||
resolution: {integrity: sha512-TMQp2llA+RYn7JcjnrMnz7wN4pcVttPZnRZo52PLQsoLVKzNlVwUeHmfePgTgRluXFvlD3GD5g5MOVVTJCO0qA==}
|
||||
peerDependencies:
|
||||
@@ -3066,19 +3052,6 @@ packages:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-menu@2.1.24':
|
||||
resolution: {integrity: sha512-uW7RVuU6Lp/ZtfeY4b3kL32zccgEWvPv1+cf17ubYzHa9cL8AHokmk36cG/XEiH/smbQvumnieXX9j/e9RqJWA==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-popover@1.1.23':
|
||||
resolution: {integrity: sha512-mw58MrBlyHWFisTOYignD0vf/3gdcgAR+9of1s9G/38CbFiUwH1nCDkc0AUM9IrXFgN5Ue8n45j9WCgyM1sbiQ==}
|
||||
peerDependencies:
|
||||
@@ -3144,19 +3117,6 @@ packages:
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-primitive@2.1.4':
|
||||
resolution: {integrity: sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-progress@1.1.16':
|
||||
resolution: {integrity: sha512-5XnomAsoZZCY+KNTxbIghpGqPruZvKFNlvcAljVAOdDRDsH4/OZQxhtwo5wdtoDM5R6MhJBb2sPnDuRFep3lzg==}
|
||||
peerDependencies:
|
||||
@@ -3222,15 +3182,6 @@ packages:
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-slot@1.2.4':
|
||||
resolution: {integrity: sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-slot@1.3.3':
|
||||
resolution: {integrity: sha512-qx7oqnYbxnK9kYI9m317qmFmEgo6ywqWvbTogdj7cL9p3/yx4M48p7Rnw5z3H890cL/ow/EeWJsuTykeZVXP5Q==}
|
||||
peerDependencies:
|
||||
@@ -3240,19 +3191,6 @@ packages:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-switch@1.3.7':
|
||||
resolution: {integrity: sha512-48tB/4dn2UVLBCYhTu9AuR63IHl73l/qLbLgxd86noTUor4/K4LFDAcYjK+isP5313qxaFpjPVogE7+Y0/V3Kw==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-tabs@1.1.21':
|
||||
resolution: {integrity: sha512-UKxJlZid7FVtsk/WTxj4i4uSEgj2Au+KBbS7SQyTlzMhhn+86Cz3tISZdTa87bfEfcuvZezf2ZsxD4xuEKtkog==}
|
||||
peerDependencies:
|
||||
@@ -3266,45 +3204,6 @@ packages:
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-toggle-group@1.1.19':
|
||||
resolution: {integrity: sha512-OtnwuSVjd1Ofi+AdnvhsjQdyuhCDwYs1w9RyB5BN/OavXOVQo42SYqQjwUnbPnaiPFBpQ9aX70dWeee+v2oBLA==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-toggle@1.1.18':
|
||||
resolution: {integrity: sha512-7lonPlKfSacd20GlOBx2ltuVKz9oqWYZz+oMQyOltw6t1y2nyftj2ZmwwUHYn49kqfDWcp8dNZm5NgV+5Z+mug==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-tooltip@1.2.16':
|
||||
resolution: {integrity: sha512-6EamKFRRnlpdadndbZ6LMwycfwkwPte1B42hs6QA0gYhjaOKqW4PZ4pjaW9UrlDX5eVt/OjncE7BFTPL5nmZhg==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-use-callback-ref@1.1.4':
|
||||
resolution: {integrity: sha512-R6OUY2e2fA6Yn6s+VSx5KBV6Nx8LQEhu+cz7LCej18rQ1HLyg9PSC9jP/ZNx0o6FAIK9c0F1kHylzSxKsdlkrQ==}
|
||||
peerDependencies:
|
||||
@@ -3341,15 +3240,6 @@ packages:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-use-layout-effect@1.1.1':
|
||||
resolution: {integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-use-layout-effect@1.1.4':
|
||||
resolution: {integrity: sha512-K20DkRkUwDnxEYMBPcg3Y6voLkEy5p5QQmszZgLngKKiC7dzBR/aEuK3w1qlx2JWDUNH6FluahYdgR3BP+QbYw==}
|
||||
peerDependencies:
|
||||
@@ -3721,14 +3611,14 @@ packages:
|
||||
'@sideway/pinpoint@2.0.0':
|
||||
resolution: {integrity: sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==}
|
||||
|
||||
'@signozhq/design-tokens@2.1.6':
|
||||
resolution: {integrity: sha512-n0cH4TRS1Xj7BR38HGiwIBDekIOAnDgl2JyYmAyUvws0L1nlE0txSHO5NiOBJ22Dm8uMva0v5/rOHxpKsGQbsw==}
|
||||
'@signozhq/design-tokens@2.2.1':
|
||||
resolution: {integrity: sha512-ulf6lhWUP6qxqsyemQySUMfOIKRvefvZ5rBRv+wlhOpPfYPk3uatPmPkKEhAjISpX8f9g1Gg8wgRZxUCysf20Q==}
|
||||
|
||||
'@signozhq/icons@0.4.0':
|
||||
resolution: {integrity: sha512-QctNvNPlQlsefFdIVmSy/P/7a1IcQ3IryKP0KPMqtoqew3q89FGUmFa5IfEJ7Z3CHhtG1zaXwBKqP72QkLyA3g==}
|
||||
|
||||
'@signozhq/ui@0.1.0':
|
||||
resolution: {integrity: sha512-FjqmQhz+WtpGl29gPGZey5YwNe4p++TCyX4jaGUhAgvlBGRmxmAKW0lFRrSOYTPbgFL5UaDCW0wR62aiGHZ+FA==}
|
||||
'@signozhq/ui@0.2.0':
|
||||
resolution: {integrity: sha512-j20ldiOkR150ON0BCDhlq6fXGoPN++7w6e4t2HtM2XMM4buJO7aHzPzNKvDRZ2UUf/LGPULPAW8w6sg6gUVHyw==}
|
||||
peerDependencies:
|
||||
'@signozhq/icons': 0.3.0
|
||||
react: ^18.2.0
|
||||
@@ -8520,6 +8410,11 @@ packages:
|
||||
react: ^16.8.5 || ^17.0.0 || ^18.0.0
|
||||
react-dom: ^16.8.5 || ^17.0.0 || ^18.0.0
|
||||
|
||||
react-compiler-runtime@1.0.0:
|
||||
resolution: {integrity: sha512-rRfjYv66HlG8896yPUDONgKzG5BxZD1nV9U6rkm+7VCuvQc903C4MjcoZR4zPw53IKSOX9wMQVpA1IAbRtzQ7w==}
|
||||
peerDependencies:
|
||||
react: ^17.0.0 || ^18.0.0 || ^19.0.0 || ^0.0.0-experimental
|
||||
|
||||
react-day-picker@9.9.0:
|
||||
resolution: {integrity: sha512-NtkJbuX6cl/VaGNb3sVVhmMA6LSMnL5G3xNL+61IyoZj0mUZFWTg4hmj7PHjIQ8MXN9dHWhUHFoJWG6y60DKSg==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -8907,6 +8802,9 @@ packages:
|
||||
requires-port@1.0.0:
|
||||
resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==}
|
||||
|
||||
reselect@5.3.0:
|
||||
resolution: {integrity: sha512-XGoLeRAVzUTcJ1qkxPQhDJyIZ5d6zzZD9nT7AEZOaaU9UbWclhycElmhO+VD5bFeLuzhPBaOV2oXC8uG35ZSpg==}
|
||||
|
||||
reserved-words@0.1.2:
|
||||
resolution: {integrity: sha512-0S5SrIUJ9LfpbVl4Yzij6VipUdafHrOTzvmfazSw/jeZrZtQK303OPZW+obtkaw7jQlTQppy0UvZWm9872PbRw==}
|
||||
|
||||
@@ -11154,6 +11052,31 @@ snapshots:
|
||||
'@babel/helper-string-parser': 7.29.7
|
||||
'@babel/helper-validator-identifier': 7.29.7
|
||||
|
||||
'@base-ui/react@1.7.0(@date-fns/tz@1.4.1)(@types/react@18.0.26)(date-fns@4.1.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.29.2
|
||||
'@base-ui/utils': 0.3.2(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
'@floating-ui/react-dom': 2.1.9(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
'@floating-ui/utils': 0.2.12
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
use-sync-external-store: 1.6.0(react@18.2.0)
|
||||
optionalDependencies:
|
||||
'@date-fns/tz': 1.4.1
|
||||
'@types/react': 18.0.26
|
||||
date-fns: 4.1.0
|
||||
|
||||
'@base-ui/utils@0.3.2(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.29.2
|
||||
'@floating-ui/utils': 0.2.12
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
reselect: 5.3.0
|
||||
use-sync-external-store: 1.6.0(react@18.2.0)
|
||||
optionalDependencies:
|
||||
'@types/react': 18.0.26
|
||||
|
||||
'@bcoe/v8-coverage@0.2.3': {}
|
||||
|
||||
'@braintree/sanitize-url@7.0.1': {}
|
||||
@@ -11598,22 +11521,22 @@ snapshots:
|
||||
'@eslint/core': 1.2.1
|
||||
levn: 0.4.1
|
||||
|
||||
'@floating-ui/core@1.5.2':
|
||||
'@floating-ui/core@1.8.0':
|
||||
dependencies:
|
||||
'@floating-ui/utils': 0.1.6
|
||||
'@floating-ui/utils': 0.2.12
|
||||
|
||||
'@floating-ui/dom@1.5.3':
|
||||
'@floating-ui/dom@1.8.0':
|
||||
dependencies:
|
||||
'@floating-ui/core': 1.5.2
|
||||
'@floating-ui/utils': 0.1.6
|
||||
'@floating-ui/core': 1.8.0
|
||||
'@floating-ui/utils': 0.2.12
|
||||
|
||||
'@floating-ui/react-dom@2.0.4(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
|
||||
'@floating-ui/react-dom@2.1.9(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
|
||||
dependencies:
|
||||
'@floating-ui/dom': 1.5.3
|
||||
'@floating-ui/dom': 1.8.0
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
|
||||
'@floating-ui/utils@0.1.6': {}
|
||||
'@floating-ui/utils@0.2.12': {}
|
||||
|
||||
'@gerrit0/mini-shiki@3.23.0':
|
||||
dependencies:
|
||||
@@ -12763,21 +12686,6 @@ snapshots:
|
||||
'@types/react': 18.0.26
|
||||
'@types/react-dom': 18.0.10
|
||||
|
||||
'@radix-ui/react-checkbox@1.3.11(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
|
||||
dependencies:
|
||||
'@radix-ui/primitive': 1.1.7
|
||||
'@radix-ui/react-compose-refs': 1.1.5(@types/react@18.0.26)(react@18.2.0)
|
||||
'@radix-ui/react-context': 1.2.2(@types/react@18.0.26)(react@18.2.0)
|
||||
'@radix-ui/react-presence': 1.1.10(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
'@radix-ui/react-primitive': 2.1.10(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
'@radix-ui/react-use-controllable-state': 1.2.6(@types/react@18.0.26)(react@18.2.0)
|
||||
'@radix-ui/react-use-size': 1.1.4(@types/react@18.0.26)(react@18.2.0)
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
optionalDependencies:
|
||||
'@types/react': 18.0.26
|
||||
'@types/react-dom': 18.0.10
|
||||
|
||||
'@radix-ui/react-collection@1.1.15(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
|
||||
dependencies:
|
||||
'@radix-ui/react-compose-refs': 1.1.5(@types/react@18.0.26)(react@18.2.0)
|
||||
@@ -12790,12 +12698,6 @@ snapshots:
|
||||
'@types/react': 18.0.26
|
||||
'@types/react-dom': 18.0.10
|
||||
|
||||
'@radix-ui/react-compose-refs@1.1.2(@types/react@18.0.26)(react@18.2.0)':
|
||||
dependencies:
|
||||
react: 18.2.0
|
||||
optionalDependencies:
|
||||
'@types/react': 18.0.26
|
||||
|
||||
'@radix-ui/react-compose-refs@1.1.5(@types/react@18.0.26)(react@18.2.0)':
|
||||
dependencies:
|
||||
react: 18.2.0
|
||||
@@ -12850,21 +12752,6 @@ snapshots:
|
||||
'@types/react': 18.0.26
|
||||
'@types/react-dom': 18.0.10
|
||||
|
||||
'@radix-ui/react-dropdown-menu@2.1.24(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
|
||||
dependencies:
|
||||
'@radix-ui/primitive': 1.1.7
|
||||
'@radix-ui/react-compose-refs': 1.1.5(@types/react@18.0.26)(react@18.2.0)
|
||||
'@radix-ui/react-context': 1.2.2(@types/react@18.0.26)(react@18.2.0)
|
||||
'@radix-ui/react-id': 1.1.4(@types/react@18.0.26)(react@18.2.0)
|
||||
'@radix-ui/react-menu': 2.1.24(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
'@radix-ui/react-primitive': 2.1.10(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
'@radix-ui/react-use-controllable-state': 1.2.6(@types/react@18.0.26)(react@18.2.0)
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
optionalDependencies:
|
||||
'@types/react': 18.0.26
|
||||
'@types/react-dom': 18.0.10
|
||||
|
||||
'@radix-ui/react-focus-guards@1.1.6(@types/react@18.0.26)(react@18.2.0)':
|
||||
dependencies:
|
||||
react: 18.2.0
|
||||
@@ -12886,13 +12773,6 @@ snapshots:
|
||||
dependencies:
|
||||
react: 18.2.0
|
||||
|
||||
'@radix-ui/react-id@1.1.1(@types/react@18.0.26)(react@18.2.0)':
|
||||
dependencies:
|
||||
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.0.26)(react@18.2.0)
|
||||
react: 18.2.0
|
||||
optionalDependencies:
|
||||
'@types/react': 18.0.26
|
||||
|
||||
'@radix-ui/react-id@1.1.4(@types/react@18.0.26)(react@18.2.0)':
|
||||
dependencies:
|
||||
'@radix-ui/react-use-layout-effect': 1.1.4(@types/react@18.0.26)(react@18.2.0)
|
||||
@@ -12900,32 +12780,6 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@types/react': 18.0.26
|
||||
|
||||
'@radix-ui/react-menu@2.1.24(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
|
||||
dependencies:
|
||||
'@radix-ui/primitive': 1.1.7
|
||||
'@radix-ui/react-collection': 1.1.15(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
'@radix-ui/react-compose-refs': 1.1.5(@types/react@18.0.26)(react@18.2.0)
|
||||
'@radix-ui/react-context': 1.2.2(@types/react@18.0.26)(react@18.2.0)
|
||||
'@radix-ui/react-direction': 1.1.4(@types/react@18.0.26)(react@18.2.0)
|
||||
'@radix-ui/react-dismissable-layer': 1.1.19(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
'@radix-ui/react-focus-guards': 1.1.6(@types/react@18.0.26)(react@18.2.0)
|
||||
'@radix-ui/react-focus-scope': 1.1.16(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
'@radix-ui/react-id': 1.1.4(@types/react@18.0.26)(react@18.2.0)
|
||||
'@radix-ui/react-popper': 1.3.7(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
'@radix-ui/react-portal': 1.1.17(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
'@radix-ui/react-presence': 1.1.10(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
'@radix-ui/react-primitive': 2.1.10(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
'@radix-ui/react-roving-focus': 1.1.19(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
'@radix-ui/react-slot': 1.3.3(@types/react@18.0.26)(react@18.2.0)
|
||||
'@radix-ui/react-use-callback-ref': 1.1.4(@types/react@18.0.26)(react@18.2.0)
|
||||
aria-hidden: 1.2.6
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
react-remove-scroll: 2.7.2(@types/react@18.0.26)(react@18.2.0)
|
||||
optionalDependencies:
|
||||
'@types/react': 18.0.26
|
||||
'@types/react-dom': 18.0.10
|
||||
|
||||
'@radix-ui/react-popover@1.1.23(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
|
||||
dependencies:
|
||||
'@radix-ui/primitive': 1.1.7
|
||||
@@ -12951,7 +12805,7 @@ snapshots:
|
||||
|
||||
'@radix-ui/react-popper@1.3.7(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
|
||||
dependencies:
|
||||
'@floating-ui/react-dom': 2.0.4(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
'@floating-ui/react-dom': 2.1.9(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
'@radix-ui/react-arrow': 1.1.15(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
'@radix-ui/react-compose-refs': 1.1.5(@types/react@18.0.26)(react@18.2.0)
|
||||
'@radix-ui/react-context': 1.2.2(@types/react@18.0.26)(react@18.2.0)
|
||||
@@ -12995,15 +12849,6 @@ snapshots:
|
||||
'@types/react': 18.0.26
|
||||
'@types/react-dom': 18.0.10
|
||||
|
||||
'@radix-ui/react-primitive@2.1.4(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
|
||||
dependencies:
|
||||
'@radix-ui/react-slot': 1.2.4(@types/react@18.0.26)(react@18.2.0)
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
optionalDependencies:
|
||||
'@types/react': 18.0.26
|
||||
'@types/react-dom': 18.0.10
|
||||
|
||||
'@radix-ui/react-progress@1.1.16(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
|
||||
dependencies:
|
||||
'@radix-ui/react-context': 1.2.2(@types/react@18.0.26)(react@18.2.0)
|
||||
@@ -13099,13 +12944,6 @@ snapshots:
|
||||
'@types/react': 18.0.26
|
||||
'@types/react-dom': 18.0.10
|
||||
|
||||
'@radix-ui/react-slot@1.2.4(@types/react@18.0.26)(react@18.2.0)':
|
||||
dependencies:
|
||||
'@radix-ui/react-compose-refs': 1.1.2(@types/react@18.0.26)(react@18.2.0)
|
||||
react: 18.2.0
|
||||
optionalDependencies:
|
||||
'@types/react': 18.0.26
|
||||
|
||||
'@radix-ui/react-slot@1.3.3(@types/react@18.0.26)(react@18.2.0)':
|
||||
dependencies:
|
||||
'@radix-ui/react-compose-refs': 1.1.5(@types/react@18.0.26)(react@18.2.0)
|
||||
@@ -13113,20 +12951,6 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@types/react': 18.0.26
|
||||
|
||||
'@radix-ui/react-switch@1.3.7(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
|
||||
dependencies:
|
||||
'@radix-ui/primitive': 1.1.7
|
||||
'@radix-ui/react-compose-refs': 1.1.5(@types/react@18.0.26)(react@18.2.0)
|
||||
'@radix-ui/react-context': 1.2.2(@types/react@18.0.26)(react@18.2.0)
|
||||
'@radix-ui/react-primitive': 2.1.10(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
'@radix-ui/react-use-controllable-state': 1.2.6(@types/react@18.0.26)(react@18.2.0)
|
||||
'@radix-ui/react-use-size': 1.1.4(@types/react@18.0.26)(react@18.2.0)
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
optionalDependencies:
|
||||
'@types/react': 18.0.26
|
||||
'@types/react-dom': 18.0.10
|
||||
|
||||
'@radix-ui/react-tabs@1.1.21(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
|
||||
dependencies:
|
||||
'@radix-ui/primitive': 1.1.7
|
||||
@@ -13143,53 +12967,6 @@ snapshots:
|
||||
'@types/react': 18.0.26
|
||||
'@types/react-dom': 18.0.10
|
||||
|
||||
'@radix-ui/react-toggle-group@1.1.19(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
|
||||
dependencies:
|
||||
'@radix-ui/primitive': 1.1.7
|
||||
'@radix-ui/react-context': 1.2.2(@types/react@18.0.26)(react@18.2.0)
|
||||
'@radix-ui/react-direction': 1.1.4(@types/react@18.0.26)(react@18.2.0)
|
||||
'@radix-ui/react-primitive': 2.1.10(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
'@radix-ui/react-roving-focus': 1.1.19(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
'@radix-ui/react-toggle': 1.1.18(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
'@radix-ui/react-use-controllable-state': 1.2.6(@types/react@18.0.26)(react@18.2.0)
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
optionalDependencies:
|
||||
'@types/react': 18.0.26
|
||||
'@types/react-dom': 18.0.10
|
||||
|
||||
'@radix-ui/react-toggle@1.1.18(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
|
||||
dependencies:
|
||||
'@radix-ui/primitive': 1.1.7
|
||||
'@radix-ui/react-primitive': 2.1.10(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
'@radix-ui/react-use-controllable-state': 1.2.6(@types/react@18.0.26)(react@18.2.0)
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
optionalDependencies:
|
||||
'@types/react': 18.0.26
|
||||
'@types/react-dom': 18.0.10
|
||||
|
||||
'@radix-ui/react-tooltip@1.2.16(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
|
||||
dependencies:
|
||||
'@radix-ui/primitive': 1.1.7
|
||||
'@radix-ui/react-compose-refs': 1.1.5(@types/react@18.0.26)(react@18.2.0)
|
||||
'@radix-ui/react-context': 1.2.2(@types/react@18.0.26)(react@18.2.0)
|
||||
'@radix-ui/react-dismissable-layer': 1.1.19(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
'@radix-ui/react-id': 1.1.4(@types/react@18.0.26)(react@18.2.0)
|
||||
'@radix-ui/react-popper': 1.3.7(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
'@radix-ui/react-portal': 1.1.17(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
'@radix-ui/react-presence': 1.1.10(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
'@radix-ui/react-primitive': 2.1.10(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
'@radix-ui/react-slot': 1.3.3(@types/react@18.0.26)(react@18.2.0)
|
||||
'@radix-ui/react-use-controllable-state': 1.2.6(@types/react@18.0.26)(react@18.2.0)
|
||||
'@radix-ui/react-use-layout-effect': 1.1.4(@types/react@18.0.26)(react@18.2.0)
|
||||
'@radix-ui/react-visually-hidden': 1.2.11(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
optionalDependencies:
|
||||
'@types/react': 18.0.26
|
||||
'@types/react-dom': 18.0.10
|
||||
|
||||
'@radix-ui/react-use-callback-ref@1.1.4(@types/react@18.0.26)(react@18.2.0)':
|
||||
dependencies:
|
||||
react: 18.2.0
|
||||
@@ -13218,12 +12995,6 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@types/react': 18.0.26
|
||||
|
||||
'@radix-ui/react-use-layout-effect@1.1.1(@types/react@18.0.26)(react@18.2.0)':
|
||||
dependencies:
|
||||
react: 18.2.0
|
||||
optionalDependencies:
|
||||
'@types/react': 18.0.26
|
||||
|
||||
'@radix-ui/react-use-layout-effect@1.1.4(@types/react@18.0.26)(react@18.2.0)':
|
||||
dependencies:
|
||||
react: 18.2.0
|
||||
@@ -13272,14 +13043,14 @@ snapshots:
|
||||
|
||||
'@rc-component/context@1.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.2
|
||||
'@babel/runtime': 7.29.2
|
||||
rc-util: 5.38.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
|
||||
'@rc-component/mini-decimal@1.0.1':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.2
|
||||
'@babel/runtime': 7.29.2
|
||||
|
||||
'@rc-component/mutate-observer@1.1.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
|
||||
dependencies:
|
||||
@@ -13291,7 +13062,7 @@ snapshots:
|
||||
|
||||
'@rc-component/portal@1.1.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.2
|
||||
'@babel/runtime': 7.29.2
|
||||
classnames: 2.3.2
|
||||
rc-util: 5.38.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
react: 18.2.0
|
||||
@@ -13559,16 +13330,15 @@ snapshots:
|
||||
|
||||
'@sideway/pinpoint@2.0.0': {}
|
||||
|
||||
'@signozhq/design-tokens@2.1.6': {}
|
||||
'@signozhq/design-tokens@2.2.1': {}
|
||||
|
||||
'@signozhq/icons@0.4.0': {}
|
||||
|
||||
'@signozhq/ui@0.1.0(@emotion/is-prop-valid@1.2.0)(@signozhq/icons@0.4.0)(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react-router-dom@5.3.4(react@18.2.0))(react-router@6.30.6(react@18.2.0))(react@18.2.0)':
|
||||
'@signozhq/ui@0.2.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.2.0)(@signozhq/icons@0.4.0)(@types/react-dom@18.0.10)(@types/react@18.0.26)(date-fns@4.1.0)(react-dom@18.2.0(react@18.2.0))(react-router-dom@5.3.4(react@18.2.0))(react-router@6.30.6(react@18.2.0))(react@18.2.0)':
|
||||
dependencies:
|
||||
'@base-ui/react': 1.7.0(@date-fns/tz@1.4.1)(@types/react@18.0.26)(date-fns@4.1.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
'@chenglou/pretext': 0.0.8
|
||||
'@radix-ui/react-checkbox': 1.3.11(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
'@radix-ui/react-dialog': 1.1.23(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
'@radix-ui/react-dropdown-menu': 2.1.24(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
'@radix-ui/react-icons': 1.3.2(react@18.2.0)
|
||||
'@radix-ui/react-popover': 1.1.23(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
'@radix-ui/react-progress': 1.1.16(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
@@ -13576,11 +13346,7 @@ snapshots:
|
||||
'@radix-ui/react-select': 2.3.7(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
'@radix-ui/react-slider': 1.4.7(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
'@radix-ui/react-slot': 1.3.3(@types/react@18.0.26)(react@18.2.0)
|
||||
'@radix-ui/react-switch': 1.3.7(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
'@radix-ui/react-tabs': 1.1.21(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
'@radix-ui/react-toggle': 1.1.18(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
'@radix-ui/react-toggle-group': 1.1.19(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
'@radix-ui/react-tooltip': 1.2.16(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
'@signozhq/icons': 0.4.0
|
||||
'@tanstack/react-table': 8.21.3(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
'@tanstack/react-virtual': 3.14.9(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
@@ -13594,16 +13360,19 @@ snapshots:
|
||||
next-themes: 0.4.6(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
nuqs: 2.9.6(react-router-dom@5.3.4(react@18.2.0))(react-router@6.30.6(react@18.2.0))(react@18.2.0)
|
||||
react: 18.2.0
|
||||
react-compiler-runtime: 1.0.0(react@18.2.0)
|
||||
react-day-picker: 9.9.0(react@18.2.0)
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
react-resizable-panels: 4.12.3(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
sonner: 2.0.7(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
transitivePeerDependencies:
|
||||
- '@date-fns/tz'
|
||||
- '@emotion/is-prop-valid'
|
||||
- '@remix-run/react'
|
||||
- '@tanstack/react-router'
|
||||
- '@types/react'
|
||||
- '@types/react-dom'
|
||||
- date-fns
|
||||
- next
|
||||
- react-router
|
||||
- react-router-dom
|
||||
@@ -15129,10 +14898,10 @@ snapshots:
|
||||
|
||||
cmdk@1.1.1(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
|
||||
dependencies:
|
||||
'@radix-ui/react-compose-refs': 1.1.2(@types/react@18.0.26)(react@18.2.0)
|
||||
'@radix-ui/react-compose-refs': 1.1.5(@types/react@18.0.26)(react@18.2.0)
|
||||
'@radix-ui/react-dialog': 1.1.23(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
'@radix-ui/react-id': 1.1.1(@types/react@18.0.26)(react@18.2.0)
|
||||
'@radix-ui/react-primitive': 2.1.4(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
'@radix-ui/react-id': 1.1.4(@types/react@18.0.26)(react@18.2.0)
|
||||
'@radix-ui/react-primitive': 2.1.10(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
transitivePeerDependencies:
|
||||
@@ -19277,6 +19046,10 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- react-native
|
||||
|
||||
react-compiler-runtime@1.0.0(react@18.2.0):
|
||||
dependencies:
|
||||
react: 18.2.0
|
||||
|
||||
react-day-picker@9.9.0(react@18.2.0):
|
||||
dependencies:
|
||||
'@date-fns/tz': 1.4.1
|
||||
@@ -19762,6 +19535,8 @@ snapshots:
|
||||
|
||||
requires-port@1.0.0: {}
|
||||
|
||||
reselect@5.3.0: {}
|
||||
|
||||
reserved-words@0.1.2: {}
|
||||
|
||||
resize-observer-polyfill@1.5.1: {}
|
||||
@@ -19867,7 +19642,7 @@ snapshots:
|
||||
|
||||
rtl-css-js@1.16.1:
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.2
|
||||
'@babel/runtime': 7.29.2
|
||||
|
||||
run-applescript@7.1.0: {}
|
||||
|
||||
@@ -20679,7 +20454,7 @@ snapshots:
|
||||
|
||||
unload@2.2.0:
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.2
|
||||
'@babel/runtime': 7.29.2
|
||||
detect-node: 2.1.0
|
||||
|
||||
unplugin@2.3.11:
|
||||
|
||||
@@ -41,17 +41,16 @@ export function ErrorResponseHandler(error: AxiosError): ErrorResponse {
|
||||
message: data?.error,
|
||||
};
|
||||
}
|
||||
// Avoid logging error when the request was just cancelled for whatever reason
|
||||
if (isCancel(error)) {
|
||||
return {
|
||||
statusCode: 500,
|
||||
payload: null,
|
||||
error: 'Something went wrong',
|
||||
message: null,
|
||||
};
|
||||
}
|
||||
if (request) {
|
||||
// Avoid logging error when the request was just cancelled for whatever reason
|
||||
if (isCancel(error)) {
|
||||
return {
|
||||
statusCode: 500,
|
||||
payload: null,
|
||||
error: 'Something went wrong',
|
||||
message: null,
|
||||
};
|
||||
}
|
||||
|
||||
// client never received a response, or request never left
|
||||
console.error('client never received a response, or request never left');
|
||||
|
||||
|
||||
22
frontend/src/api/__tests__/ErrorResponseHandler.test.ts
Normal file
22
frontend/src/api/__tests__/ErrorResponseHandler.test.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError, CanceledError } from 'axios';
|
||||
|
||||
describe('ErrorResponseHandler', () => {
|
||||
// axios aborts a request whose signal fired before it was sent without ever
|
||||
// creating one, so the error carries neither `response` nor `request`.
|
||||
it('treats a request cancelled before it was sent as a cancellation', () => {
|
||||
const consoleError = jest
|
||||
.spyOn(console, 'error')
|
||||
.mockImplementation(() => undefined);
|
||||
|
||||
expect(ErrorResponseHandler(new CanceledError() as AxiosError)).toEqual({
|
||||
statusCode: 500,
|
||||
payload: null,
|
||||
error: 'Something went wrong',
|
||||
message: null,
|
||||
});
|
||||
expect(consoleError).not.toHaveBeenCalled();
|
||||
|
||||
consoleError.mockRestore();
|
||||
});
|
||||
});
|
||||
@@ -4,6 +4,264 @@
|
||||
* * regenerate with 'pnpm generate:api'
|
||||
* SigNoz
|
||||
*/
|
||||
export enum AiobservabilitytypesBlobPartDTOType {
|
||||
blob = 'blob',
|
||||
}
|
||||
export interface AiobservabilitytypesBlobPartDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
content: string;
|
||||
/**
|
||||
* @type string,null
|
||||
*/
|
||||
mime_type?: string | null;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
modality: string;
|
||||
/**
|
||||
* @type string
|
||||
* @enum blob
|
||||
*/
|
||||
type: AiobservabilitytypesBlobPartDTOType;
|
||||
}
|
||||
|
||||
export enum AiobservabilitytypesTextPartDTOType {
|
||||
text = 'text',
|
||||
}
|
||||
export interface AiobservabilitytypesTextPartDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
content: string;
|
||||
/**
|
||||
* @type string
|
||||
* @enum text
|
||||
*/
|
||||
type: AiobservabilitytypesTextPartDTOType;
|
||||
}
|
||||
|
||||
export enum AiobservabilitytypesToolCallRequestPartDTOType {
|
||||
tool_call = 'tool_call',
|
||||
}
|
||||
export interface AiobservabilitytypesToolCallRequestPartDTO {
|
||||
arguments?: unknown;
|
||||
/**
|
||||
* @type string,null
|
||||
*/
|
||||
id?: string | null;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* @type string
|
||||
* @enum tool_call
|
||||
*/
|
||||
type: AiobservabilitytypesToolCallRequestPartDTOType;
|
||||
}
|
||||
|
||||
export enum AiobservabilitytypesToolCallResponsePartDTOType {
|
||||
tool_call_response = 'tool_call_response',
|
||||
}
|
||||
export type AiobservabilitytypesToolCallResponsePartDTOResponse =
|
||||
| unknown
|
||||
| null;
|
||||
|
||||
export interface AiobservabilitytypesToolCallResponsePartDTO {
|
||||
/**
|
||||
* @type string,null
|
||||
*/
|
||||
id?: string | null;
|
||||
/**
|
||||
* @nullable true
|
||||
*/
|
||||
response: AiobservabilitytypesToolCallResponsePartDTOResponse;
|
||||
/**
|
||||
* @type string
|
||||
* @enum tool_call_response
|
||||
*/
|
||||
type: AiobservabilitytypesToolCallResponsePartDTOType;
|
||||
}
|
||||
|
||||
export interface AiobservabilitytypesGenericServerToolCallDTO {
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export enum AiobservabilitytypesServerToolCallPartDTOType {
|
||||
server_tool_call = 'server_tool_call',
|
||||
}
|
||||
export interface AiobservabilitytypesServerToolCallPartDTO {
|
||||
/**
|
||||
* @type string,null
|
||||
*/
|
||||
id?: string | null;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
name: string;
|
||||
server_tool_call: AiobservabilitytypesGenericServerToolCallDTO;
|
||||
/**
|
||||
* @type string
|
||||
* @enum server_tool_call
|
||||
*/
|
||||
type: AiobservabilitytypesServerToolCallPartDTOType;
|
||||
}
|
||||
|
||||
export interface AiobservabilitytypesGenericServerToolCallResponseDTO {
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export enum AiobservabilitytypesServerToolCallResponsePartDTOType {
|
||||
server_tool_call_response = 'server_tool_call_response',
|
||||
}
|
||||
export interface AiobservabilitytypesServerToolCallResponsePartDTO {
|
||||
/**
|
||||
* @type string,null
|
||||
*/
|
||||
id?: string | null;
|
||||
server_tool_call_response: AiobservabilitytypesGenericServerToolCallResponseDTO;
|
||||
/**
|
||||
* @type string
|
||||
* @enum server_tool_call_response
|
||||
*/
|
||||
type: AiobservabilitytypesServerToolCallResponsePartDTOType;
|
||||
}
|
||||
|
||||
export enum AiobservabilitytypesFilePartDTOType {
|
||||
file = 'file',
|
||||
}
|
||||
export interface AiobservabilitytypesFilePartDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
file_id: string;
|
||||
/**
|
||||
* @type string,null
|
||||
*/
|
||||
mime_type?: string | null;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
modality: string;
|
||||
/**
|
||||
* @type string
|
||||
* @enum file
|
||||
*/
|
||||
type: AiobservabilitytypesFilePartDTOType;
|
||||
}
|
||||
|
||||
export enum AiobservabilitytypesUriPartDTOType {
|
||||
uri = 'uri',
|
||||
}
|
||||
export interface AiobservabilitytypesUriPartDTO {
|
||||
/**
|
||||
* @type string,null
|
||||
*/
|
||||
mime_type?: string | null;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
modality: string;
|
||||
/**
|
||||
* @type string
|
||||
* @enum uri
|
||||
*/
|
||||
type: AiobservabilitytypesUriPartDTOType;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
uri: string;
|
||||
}
|
||||
|
||||
export enum AiobservabilitytypesReasoningPartDTOType {
|
||||
reasoning = 'reasoning',
|
||||
}
|
||||
export interface AiobservabilitytypesReasoningPartDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
content: string;
|
||||
/**
|
||||
* @type string
|
||||
* @enum reasoning
|
||||
*/
|
||||
type: AiobservabilitytypesReasoningPartDTOType;
|
||||
}
|
||||
|
||||
export enum AiobservabilitytypesCompactionPartDTOType {
|
||||
compaction = 'compaction',
|
||||
}
|
||||
export interface AiobservabilitytypesCompactionPartDTO {
|
||||
/**
|
||||
* @type string,null
|
||||
*/
|
||||
content?: string | null;
|
||||
/**
|
||||
* @type string,null
|
||||
*/
|
||||
id?: string | null;
|
||||
/**
|
||||
* @type string
|
||||
* @enum compaction
|
||||
*/
|
||||
type: AiobservabilitytypesCompactionPartDTOType;
|
||||
}
|
||||
|
||||
export interface AiobservabilitytypesGenericPartDTO {
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export type AiobservabilitytypesPartDTO =
|
||||
| AiobservabilitytypesTextPartDTO
|
||||
| AiobservabilitytypesToolCallRequestPartDTO
|
||||
| AiobservabilitytypesToolCallResponsePartDTO
|
||||
| AiobservabilitytypesServerToolCallPartDTO
|
||||
| AiobservabilitytypesServerToolCallResponsePartDTO
|
||||
| AiobservabilitytypesBlobPartDTO
|
||||
| AiobservabilitytypesFilePartDTO
|
||||
| AiobservabilitytypesUriPartDTO
|
||||
| AiobservabilitytypesReasoningPartDTO
|
||||
| AiobservabilitytypesCompactionPartDTO
|
||||
| AiobservabilitytypesGenericPartDTO;
|
||||
|
||||
export type AiobservabilitytypesPartsDTO = AiobservabilitytypesPartDTO[];
|
||||
|
||||
export interface AiobservabilitytypesChatMessageDTO {
|
||||
/**
|
||||
* @type string,null
|
||||
*/
|
||||
name?: string | null;
|
||||
parts: AiobservabilitytypesPartsDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
role: string;
|
||||
}
|
||||
|
||||
export type AiobservabilitytypesInputMessagesDTO =
|
||||
AiobservabilitytypesChatMessageDTO[];
|
||||
|
||||
export interface AiobservabilitytypesOutputMessageDTO {
|
||||
/**
|
||||
* @type string,null
|
||||
*/
|
||||
finish_reason?: string | null;
|
||||
/**
|
||||
* @type string,null
|
||||
*/
|
||||
name?: string | null;
|
||||
parts: AiobservabilitytypesPartsDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
role: string;
|
||||
}
|
||||
|
||||
export type AiobservabilitytypesOutputMessagesDTO =
|
||||
AiobservabilitytypesOutputMessageDTO[];
|
||||
|
||||
export interface AlertmanagertypesChannelDTO {
|
||||
/**
|
||||
* @type string
|
||||
@@ -4211,195 +4469,10 @@ export interface DashboardGridLayoutSpecDTO {
|
||||
repeatVariable?: string;
|
||||
}
|
||||
|
||||
export enum DashboardtypesAreaFillModeDTO {
|
||||
solid = 'solid',
|
||||
gradient = 'gradient',
|
||||
export enum DashboardtypesAIBuilderQuerySpecDTOSignal {
|
||||
traces = 'traces',
|
||||
}
|
||||
/**
|
||||
* @minimum 0
|
||||
* @maximum 1
|
||||
* @nullable
|
||||
*/
|
||||
export type DashboardtypesFillOpacityDTO = number | null;
|
||||
|
||||
export enum DashboardtypesLineInterpolationDTO {
|
||||
linear = 'linear',
|
||||
spline = 'spline',
|
||||
step_after = 'step_after',
|
||||
step_before = 'step_before',
|
||||
}
|
||||
export enum DashboardtypesLineStyleDTO {
|
||||
solid = 'solid',
|
||||
dashed = 'dashed',
|
||||
}
|
||||
export interface DashboardtypesSpanGapsDTO {
|
||||
/**
|
||||
* @type string
|
||||
* @description The maximum gap size to connect when fillOnlyBelow is true. Gaps larger than this duration are left disconnected.
|
||||
*/
|
||||
fillLessThan?: string;
|
||||
/**
|
||||
* @type boolean
|
||||
* @description Controls whether lines connect across null values. When false (default), all gaps are connected. When true, only gaps smaller than fillLessThan are connected.
|
||||
*/
|
||||
fillOnlyBelow?: boolean;
|
||||
}
|
||||
|
||||
export interface DashboardtypesAreaChartAppearanceDTO {
|
||||
fillMode?: DashboardtypesAreaFillModeDTO;
|
||||
fillOpacity?: DashboardtypesFillOpacityDTO | null;
|
||||
lineInterpolation?: DashboardtypesLineInterpolationDTO;
|
||||
lineStyle?: DashboardtypesLineStyleDTO;
|
||||
/**
|
||||
* @type boolean
|
||||
*/
|
||||
showPoints?: boolean;
|
||||
spanGaps?: DashboardtypesSpanGapsDTO;
|
||||
}
|
||||
|
||||
export interface DashboardtypesAxesDTO {
|
||||
/**
|
||||
* @type boolean
|
||||
*/
|
||||
isLogScale?: boolean;
|
||||
/**
|
||||
* @type number,null
|
||||
*/
|
||||
softMax?: number | null;
|
||||
/**
|
||||
* @type number,null
|
||||
*/
|
||||
softMin?: number | null;
|
||||
}
|
||||
|
||||
export enum DashboardtypesPrecisionOptionDTO {
|
||||
NUMBER_0 = '0',
|
||||
NUMBER_1 = '1',
|
||||
NUMBER_2 = '2',
|
||||
NUMBER_3 = '3',
|
||||
NUMBER_4 = '4',
|
||||
full = 'full',
|
||||
}
|
||||
export interface DashboardtypesPanelFormattingDTO {
|
||||
decimalPrecision?: DashboardtypesPrecisionOptionDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
unit?: string;
|
||||
}
|
||||
|
||||
export enum DashboardtypesLegendModeDTO {
|
||||
list = 'list',
|
||||
}
|
||||
export enum DashboardtypesLegendPositionDTO {
|
||||
bottom = 'bottom',
|
||||
right = 'right',
|
||||
}
|
||||
export type DashboardtypesLegendDTOCustomColorsAnyOf = {
|
||||
[key: string]: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* @nullable
|
||||
*/
|
||||
export type DashboardtypesLegendDTOCustomColors =
|
||||
DashboardtypesLegendDTOCustomColorsAnyOf | null;
|
||||
|
||||
export interface DashboardtypesLegendDTO {
|
||||
/**
|
||||
* @type object,null
|
||||
*/
|
||||
customColors?: DashboardtypesLegendDTOCustomColors;
|
||||
mode?: DashboardtypesLegendModeDTO;
|
||||
position?: DashboardtypesLegendPositionDTO;
|
||||
}
|
||||
|
||||
export interface DashboardtypesThresholdWithLabelDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
color: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
label?: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
unit?: string;
|
||||
/**
|
||||
* @type number
|
||||
* @format double
|
||||
*/
|
||||
value: number;
|
||||
}
|
||||
|
||||
export enum DashboardtypesStackModeDTO {
|
||||
none = 'none',
|
||||
normal = 'normal',
|
||||
percent = 'percent',
|
||||
}
|
||||
export enum DashboardtypesTimePreferenceDTO {
|
||||
global_time = 'global_time',
|
||||
last_5_min = 'last_5_min',
|
||||
last_15_min = 'last_15_min',
|
||||
last_30_min = 'last_30_min',
|
||||
last_1_hr = 'last_1_hr',
|
||||
last_6_hr = 'last_6_hr',
|
||||
last_1_day = 'last_1_day',
|
||||
last_3_days = 'last_3_days',
|
||||
last_1_week = 'last_1_week',
|
||||
last_1_month = 'last_1_month',
|
||||
}
|
||||
export interface DashboardtypesAreaChartVisualizationDTO {
|
||||
/**
|
||||
* @type boolean
|
||||
*/
|
||||
fillSpans?: boolean;
|
||||
stack?: DashboardtypesStackModeDTO;
|
||||
timePreference?: DashboardtypesTimePreferenceDTO;
|
||||
}
|
||||
|
||||
export interface DashboardtypesAreaChartPanelSpecDTO {
|
||||
axes?: DashboardtypesAxesDTO;
|
||||
chartAppearance?: DashboardtypesAreaChartAppearanceDTO;
|
||||
formatting?: DashboardtypesPanelFormattingDTO;
|
||||
legend?: DashboardtypesLegendDTO;
|
||||
/**
|
||||
* @type array,null
|
||||
*/
|
||||
thresholds?: DashboardtypesThresholdWithLabelDTO[] | null;
|
||||
visualization?: DashboardtypesAreaChartVisualizationDTO;
|
||||
}
|
||||
|
||||
export interface DashboardtypesBarChartVisualizationDTO {
|
||||
/**
|
||||
* @type boolean
|
||||
*/
|
||||
fillSpans?: boolean;
|
||||
/**
|
||||
* @type boolean
|
||||
*/
|
||||
stackedBarChart?: boolean;
|
||||
timePreference?: DashboardtypesTimePreferenceDTO;
|
||||
}
|
||||
|
||||
export interface DashboardtypesBarChartPanelSpecDTO {
|
||||
axes?: DashboardtypesAxesDTO;
|
||||
formatting?: DashboardtypesPanelFormattingDTO;
|
||||
legend?: DashboardtypesLegendDTO;
|
||||
/**
|
||||
* @type array,null
|
||||
*/
|
||||
thresholds?: DashboardtypesThresholdWithLabelDTO[] | null;
|
||||
visualization?: DashboardtypesBarChartVisualizationDTO;
|
||||
}
|
||||
|
||||
export interface DashboardtypesBasicVisualizationDTO {
|
||||
timePreference?: DashboardtypesTimePreferenceDTO;
|
||||
}
|
||||
|
||||
export interface Querybuildertypesv5LogAggregationDTO {
|
||||
export interface Querybuildertypesv5TraceAggregationDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
@@ -4638,13 +4711,274 @@ export interface TelemetrytypesTelemetryFieldKeyDTO {
|
||||
unit?: string;
|
||||
}
|
||||
|
||||
export enum Querybuildertypesv5QueryBuilderQueryGithubComSigNozSignozPkgTypesQuerybuildertypesQuerybuildertypesv5LogAggregationDTOSignal {
|
||||
logs = 'logs',
|
||||
}
|
||||
export enum TelemetrytypesSourceDTO {
|
||||
meter = 'meter',
|
||||
'' = '',
|
||||
}
|
||||
export interface DashboardtypesAIBuilderQuerySpecDTO {
|
||||
/**
|
||||
* @type array,null
|
||||
*/
|
||||
aggregations?: Querybuildertypesv5TraceAggregationDTO[] | null;
|
||||
bucketOptions?: Querybuildertypesv5BucketOptionsDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
cursor?: string;
|
||||
/**
|
||||
* @type boolean
|
||||
*/
|
||||
disabled?: boolean;
|
||||
filter?: Querybuildertypesv5FilterDTO;
|
||||
/**
|
||||
* @type array,null
|
||||
*/
|
||||
functions?: Querybuildertypesv5FunctionDTO[] | null;
|
||||
/**
|
||||
* @type array,null
|
||||
*/
|
||||
groupBy?: Querybuildertypesv5GroupByKeyDTO[] | null;
|
||||
having?: Querybuildertypesv5HavingDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
legend?: string;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
limit?: number;
|
||||
limitBy?: Querybuildertypesv5LimitByDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
name?: string;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
offset?: number;
|
||||
/**
|
||||
* @type array,null
|
||||
*/
|
||||
order?: Querybuildertypesv5OrderByDTO[] | null;
|
||||
/**
|
||||
* @type array,null
|
||||
*/
|
||||
secondaryAggregations?: Querybuildertypesv5SecondaryAggregationDTO[] | null;
|
||||
/**
|
||||
* @type array,null
|
||||
*/
|
||||
selectFields?: TelemetrytypesTelemetryFieldKeyDTO[] | null;
|
||||
/**
|
||||
* @enum traces
|
||||
* @type string
|
||||
*/
|
||||
signal: DashboardtypesAIBuilderQuerySpecDTOSignal;
|
||||
source?: TelemetrytypesSourceDTO;
|
||||
stepInterval?: Querybuildertypesv5StepDTO;
|
||||
}
|
||||
|
||||
export enum DashboardtypesAreaFillModeDTO {
|
||||
solid = 'solid',
|
||||
gradient = 'gradient',
|
||||
}
|
||||
/**
|
||||
* @minimum 0
|
||||
* @maximum 1
|
||||
* @nullable
|
||||
*/
|
||||
export type DashboardtypesFillOpacityDTO = number | null;
|
||||
|
||||
export enum DashboardtypesLineInterpolationDTO {
|
||||
linear = 'linear',
|
||||
spline = 'spline',
|
||||
step_after = 'step_after',
|
||||
step_before = 'step_before',
|
||||
}
|
||||
export enum DashboardtypesLineStyleDTO {
|
||||
solid = 'solid',
|
||||
dashed = 'dashed',
|
||||
}
|
||||
export interface DashboardtypesSpanGapsDTO {
|
||||
/**
|
||||
* @type string
|
||||
* @description The maximum gap size to connect when fillOnlyBelow is true. Gaps larger than this duration are left disconnected.
|
||||
*/
|
||||
fillLessThan?: string;
|
||||
/**
|
||||
* @type boolean
|
||||
* @description Controls whether lines connect across null values. When false (default), all gaps are connected. When true, only gaps smaller than fillLessThan are connected.
|
||||
*/
|
||||
fillOnlyBelow?: boolean;
|
||||
}
|
||||
|
||||
export interface DashboardtypesAreaChartAppearanceDTO {
|
||||
fillMode?: DashboardtypesAreaFillModeDTO;
|
||||
fillOpacity?: DashboardtypesFillOpacityDTO | null;
|
||||
lineInterpolation?: DashboardtypesLineInterpolationDTO;
|
||||
lineStyle?: DashboardtypesLineStyleDTO;
|
||||
/**
|
||||
* @type boolean
|
||||
*/
|
||||
showPoints?: boolean;
|
||||
spanGaps?: DashboardtypesSpanGapsDTO;
|
||||
}
|
||||
|
||||
export interface DashboardtypesAxesDTO {
|
||||
/**
|
||||
* @type boolean
|
||||
*/
|
||||
isLogScale?: boolean;
|
||||
/**
|
||||
* @type number,null
|
||||
*/
|
||||
softMax?: number | null;
|
||||
/**
|
||||
* @type number,null
|
||||
*/
|
||||
softMin?: number | null;
|
||||
}
|
||||
|
||||
export enum DashboardtypesPrecisionOptionDTO {
|
||||
NUMBER_0 = '0',
|
||||
NUMBER_1 = '1',
|
||||
NUMBER_2 = '2',
|
||||
NUMBER_3 = '3',
|
||||
NUMBER_4 = '4',
|
||||
full = 'full',
|
||||
}
|
||||
export interface DashboardtypesPanelFormattingDTO {
|
||||
decimalPrecision?: DashboardtypesPrecisionOptionDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
unit?: string;
|
||||
}
|
||||
|
||||
export enum DashboardtypesLegendModeDTO {
|
||||
list = 'list',
|
||||
}
|
||||
export enum DashboardtypesLegendPositionDTO {
|
||||
bottom = 'bottom',
|
||||
right = 'right',
|
||||
}
|
||||
export type DashboardtypesLegendDTOCustomColorsAnyOf = {
|
||||
[key: string]: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* @nullable
|
||||
*/
|
||||
export type DashboardtypesLegendDTOCustomColors =
|
||||
DashboardtypesLegendDTOCustomColorsAnyOf | null;
|
||||
|
||||
export interface DashboardtypesLegendDTO {
|
||||
/**
|
||||
* @type object,null
|
||||
*/
|
||||
customColors?: DashboardtypesLegendDTOCustomColors;
|
||||
mode?: DashboardtypesLegendModeDTO;
|
||||
position?: DashboardtypesLegendPositionDTO;
|
||||
}
|
||||
|
||||
export interface DashboardtypesThresholdWithLabelDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
color: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
label?: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
unit?: string;
|
||||
/**
|
||||
* @type number
|
||||
* @format double
|
||||
*/
|
||||
value: number;
|
||||
}
|
||||
|
||||
export enum DashboardtypesStackModeDTO {
|
||||
none = 'none',
|
||||
normal = 'normal',
|
||||
percent = 'percent',
|
||||
}
|
||||
export enum DashboardtypesTimePreferenceDTO {
|
||||
global_time = 'global_time',
|
||||
last_5_min = 'last_5_min',
|
||||
last_15_min = 'last_15_min',
|
||||
last_30_min = 'last_30_min',
|
||||
last_1_hr = 'last_1_hr',
|
||||
last_6_hr = 'last_6_hr',
|
||||
last_1_day = 'last_1_day',
|
||||
last_3_days = 'last_3_days',
|
||||
last_1_week = 'last_1_week',
|
||||
last_1_month = 'last_1_month',
|
||||
}
|
||||
export interface DashboardtypesAreaChartVisualizationDTO {
|
||||
/**
|
||||
* @type boolean
|
||||
*/
|
||||
fillSpans?: boolean;
|
||||
stack?: DashboardtypesStackModeDTO;
|
||||
timePreference?: DashboardtypesTimePreferenceDTO;
|
||||
}
|
||||
|
||||
export interface DashboardtypesAreaChartPanelSpecDTO {
|
||||
axes?: DashboardtypesAxesDTO;
|
||||
chartAppearance?: DashboardtypesAreaChartAppearanceDTO;
|
||||
formatting?: DashboardtypesPanelFormattingDTO;
|
||||
legend?: DashboardtypesLegendDTO;
|
||||
/**
|
||||
* @type array,null
|
||||
*/
|
||||
thresholds?: DashboardtypesThresholdWithLabelDTO[] | null;
|
||||
visualization?: DashboardtypesAreaChartVisualizationDTO;
|
||||
}
|
||||
|
||||
export interface DashboardtypesBarChartVisualizationDTO {
|
||||
/**
|
||||
* @type boolean
|
||||
*/
|
||||
fillSpans?: boolean;
|
||||
/**
|
||||
* @type boolean
|
||||
*/
|
||||
stackedBarChart?: boolean;
|
||||
timePreference?: DashboardtypesTimePreferenceDTO;
|
||||
}
|
||||
|
||||
export interface DashboardtypesBarChartPanelSpecDTO {
|
||||
axes?: DashboardtypesAxesDTO;
|
||||
formatting?: DashboardtypesPanelFormattingDTO;
|
||||
legend?: DashboardtypesLegendDTO;
|
||||
/**
|
||||
* @type array,null
|
||||
*/
|
||||
thresholds?: DashboardtypesThresholdWithLabelDTO[] | null;
|
||||
visualization?: DashboardtypesBarChartVisualizationDTO;
|
||||
}
|
||||
|
||||
export interface DashboardtypesBasicVisualizationDTO {
|
||||
timePreference?: DashboardtypesTimePreferenceDTO;
|
||||
}
|
||||
|
||||
export interface Querybuildertypesv5LogAggregationDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
alias?: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
expression?: string;
|
||||
}
|
||||
|
||||
export enum Querybuildertypesv5QueryBuilderQueryGithubComSigNozSignozPkgTypesQuerybuildertypesQuerybuildertypesv5LogAggregationDTOSignal {
|
||||
logs = 'logs',
|
||||
}
|
||||
export interface Querybuildertypesv5QueryBuilderQueryGithubComSigNozSignozPkgTypesQuerybuildertypesQuerybuildertypesv5LogAggregationDTO {
|
||||
/**
|
||||
* @type array,null
|
||||
@@ -4835,17 +5169,6 @@ export interface Querybuildertypesv5QueryBuilderQueryGithubComSigNozSignozPkgTyp
|
||||
stepInterval?: Querybuildertypesv5StepDTO;
|
||||
}
|
||||
|
||||
export interface Querybuildertypesv5TraceAggregationDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
alias?: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
expression?: string;
|
||||
}
|
||||
|
||||
export enum Querybuildertypesv5QueryBuilderQueryGithubComSigNozSignozPkgTypesQuerybuildertypesQuerybuildertypesv5TraceAggregationDTOSignal {
|
||||
traces = 'traces',
|
||||
}
|
||||
@@ -5404,6 +5727,18 @@ export interface DashboardtypesQueryPluginVariantGithubComSigNozSignozPkgTypesDa
|
||||
spec: DashboardtypesBuilderQuerySpecDTO;
|
||||
}
|
||||
|
||||
export enum DashboardtypesQueryPluginVariantGithubComSigNozSignozPkgTypesDashboardtypesAIBuilderQuerySpecDTOKind {
|
||||
'signoz/AIBuilderQuery' = 'signoz/AIBuilderQuery',
|
||||
}
|
||||
export interface DashboardtypesQueryPluginVariantGithubComSigNozSignozPkgTypesDashboardtypesAIBuilderQuerySpecDTO {
|
||||
/**
|
||||
* @enum signoz/AIBuilderQuery
|
||||
* @type string
|
||||
*/
|
||||
kind: DashboardtypesQueryPluginVariantGithubComSigNozSignozPkgTypesDashboardtypesAIBuilderQuerySpecDTOKind;
|
||||
spec: DashboardtypesAIBuilderQuerySpecDTO;
|
||||
}
|
||||
|
||||
export enum DashboardtypesQueryPluginVariantGithubComSigNozSignozPkgTypesQuerybuildertypesQuerybuildertypesv5CompositeQueryDTOKind {
|
||||
'signoz/CompositeQuery' = 'signoz/CompositeQuery',
|
||||
}
|
||||
@@ -5695,6 +6030,7 @@ export interface DashboardtypesQueryPluginVariantGithubComSigNozSignozPkgTypesQu
|
||||
|
||||
export type DashboardtypesQueryPluginDTO =
|
||||
| DashboardtypesQueryPluginVariantGithubComSigNozSignozPkgTypesDashboardtypesBuilderQuerySpecDTO
|
||||
| DashboardtypesQueryPluginVariantGithubComSigNozSignozPkgTypesDashboardtypesAIBuilderQuerySpecDTO
|
||||
| DashboardtypesQueryPluginVariantGithubComSigNozSignozPkgTypesQuerybuildertypesQuerybuildertypesv5CompositeQueryDTO
|
||||
| DashboardtypesQueryPluginVariantGithubComSigNozSignozPkgTypesQuerybuildertypesQuerybuildertypesv5QueryBuilderFormulaDTO
|
||||
| DashboardtypesQueryPluginVariantGithubComSigNozSignozPkgTypesQuerybuildertypesQuerybuildertypesv5PromQueryDTO
|
||||
@@ -6373,6 +6709,7 @@ export interface DashboardtypesPostablePublicDashboardDTO {
|
||||
|
||||
export enum DashboardtypesQueryPluginKindDTO {
|
||||
'signoz/BuilderQuery' = 'signoz/BuilderQuery',
|
||||
'signoz/AIBuilderQuery' = 'signoz/AIBuilderQuery',
|
||||
'signoz/CompositeQuery' = 'signoz/CompositeQuery',
|
||||
'signoz/Formula' = 'signoz/Formula',
|
||||
'signoz/PromQLQuery' = 'signoz/PromQLQuery',
|
||||
@@ -11452,6 +11789,12 @@ export interface SpantypesThreadSpanDTO {
|
||||
* @type array
|
||||
*/
|
||||
events: SpantypesEventDTO[];
|
||||
formattedInput: AiobservabilitytypesInputMessagesDTO;
|
||||
formattedOutput: AiobservabilitytypesOutputMessagesDTO;
|
||||
/**
|
||||
* @type array
|
||||
*/
|
||||
formatterWarnings: string[];
|
||||
/**
|
||||
* @type boolean
|
||||
*/
|
||||
@@ -11567,6 +11910,12 @@ export interface SpantypesWaterfallSpanDTO {
|
||||
* @minimum 0
|
||||
*/
|
||||
flags?: number;
|
||||
formattedInput?: AiobservabilitytypesInputMessagesDTO;
|
||||
formattedOutput?: AiobservabilitytypesOutputMessagesDTO;
|
||||
/**
|
||||
* @type array
|
||||
*/
|
||||
formatterWarnings?: string[];
|
||||
/**
|
||||
* @type boolean
|
||||
*/
|
||||
|
||||
@@ -261,7 +261,7 @@ export const invalidateGetTraceSummary = async (
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the spans carrying gen_ai input or output messages in timestamp order. Pass nextCursor as after or prevCursor as before to page, or spanId to open the page around a span.
|
||||
* Returns the gen_ai spans of a trace, model calls, tool calls and agent spans, in timestamp order. The messages a span carries are decoded into formattedInput and formattedOutput, with formatterWarnings holding what the converter could not resolve; a span without messages has both empty. Pass nextCursor as after or prevCursor as before to page, or spanId to open the page around a span.
|
||||
* @summary Get thread view for a trace
|
||||
*/
|
||||
export const getTraceThread = (
|
||||
@@ -475,7 +475,7 @@ export const useGetFlamegraph = <
|
||||
return useMutation(getGetFlamegraphMutationOptions(options));
|
||||
};
|
||||
/**
|
||||
* Returns the waterfall view of spans including all spans if total spans are under a limit, a max count otherwise. Aggregations are dropped compared to v3
|
||||
* Returns the waterfall view of spans including all spans if total spans are under a limit, a max count otherwise. Aggregations are dropped compared to v3. A span carrying gen_ai input or output messages also has them decoded into formattedInput and formattedOutput, with formatterWarnings holding what the converter could not resolve.
|
||||
* @summary Get waterfall view for a trace
|
||||
*/
|
||||
export const getWaterfallV4 = (
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import deleteLocalStorageKey from 'api/browser/localstorage/remove';
|
||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||
import ROUTES from 'constants/routes';
|
||||
import history from 'lib/history';
|
||||
import { navigate } from 'lib/router/navigation';
|
||||
|
||||
import deleteSession from './v2/sessions/delete';
|
||||
|
||||
@@ -21,5 +21,5 @@ export const Logout = async (): Promise<void> => {
|
||||
deleteLocalStorageKey(LOCALSTORAGE.CHAT_SUPPORT);
|
||||
deleteLocalStorageKey(LOCALSTORAGE.USER_ID);
|
||||
window.dispatchEvent(new CustomEvent('LOGOUT'));
|
||||
history.push(ROUTES.LOGIN);
|
||||
navigate(ROUTES.LOGIN);
|
||||
};
|
||||
|
||||
@@ -17,7 +17,10 @@ function ModalFixture(): JSX.Element {
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
data-testid="open-add-credit-card"
|
||||
variant="solid"
|
||||
color="primary"
|
||||
size="md"
|
||||
testId="open-add-credit-card"
|
||||
onClick={(): void => setOpen(true)}
|
||||
>
|
||||
Contact support
|
||||
|
||||
@@ -8,6 +8,10 @@
|
||||
:global(.ant-breadcrumb-separator) {
|
||||
color: var(--muted-foreground);
|
||||
}
|
||||
|
||||
[data-slot='button'] {
|
||||
padding: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
.divider {
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
.item {
|
||||
--button-padding: 0;
|
||||
--button-font-size: var(--periscope-font-size-base);
|
||||
}
|
||||
|
||||
.itemLast {
|
||||
color: var(--muted-foreground);
|
||||
font-size: var(--periscope-font-size-base);
|
||||
color: var(--button-secondary-label);
|
||||
font-size: var(--button-font-size, var(--periscope-font-size-base));
|
||||
line-height: var(--button-size-line-height, 16px);
|
||||
}
|
||||
|
||||
@@ -26,9 +26,9 @@ function BreadcrumbItem({
|
||||
|
||||
return (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="md"
|
||||
variant="link"
|
||||
color="secondary"
|
||||
className={styles.item}
|
||||
onClick={(e: React.MouseEvent): void => {
|
||||
if (!('route' in props) || !props.route) {
|
||||
return;
|
||||
|
||||
@@ -34,21 +34,23 @@ function ErrorEmptyState({
|
||||
</div>
|
||||
<div className={styles.actions}>
|
||||
<Button
|
||||
size="md"
|
||||
variant="solid"
|
||||
color="secondary"
|
||||
prefix={<LifeBuoy size={14} />}
|
||||
onClick={onContactSupport}
|
||||
data-testid="error-contact-support-button"
|
||||
testId="error-contact-support-button"
|
||||
>
|
||||
Contact Support
|
||||
</Button>
|
||||
{onRefresh && (
|
||||
<Button
|
||||
size="md"
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
prefix={<RefreshCw size={14} />}
|
||||
onClick={onRefresh}
|
||||
data-testid="error-refresh-button"
|
||||
testId="error-refresh-button"
|
||||
>
|
||||
Refresh
|
||||
</Button>
|
||||
|
||||
@@ -1,20 +1,32 @@
|
||||
.labelColumn {
|
||||
// Badges fill their wrapper; the wrapper, as the flex item, owns the cap.
|
||||
--badge-max-width: 100%;
|
||||
|
||||
position: relative;
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
max-width: 100%;
|
||||
width: 100%;
|
||||
|
||||
> * {
|
||||
flex-shrink: 0;
|
||||
max-width: 180px;
|
||||
}
|
||||
}
|
||||
|
||||
.labelBadge {
|
||||
cursor: default;
|
||||
font-size: 12px;
|
||||
// Kept mounted so its width can still be measured.
|
||||
.overflowed {
|
||||
position: absolute;
|
||||
visibility: hidden;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
--badge-display: inline;
|
||||
|
||||
max-width: 180px;
|
||||
text-overflow: ellipsis;
|
||||
// The only visible label truncates instead of pushing `+N` out of view.
|
||||
.labelColumn > .shrinkable {
|
||||
flex-shrink: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.overflowTrigger {
|
||||
@@ -22,11 +34,6 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.overflowBadge {
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.labelPopover {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import { TooltipProvider } from '@signozhq/ui/tooltip';
|
||||
import { act, render, screen } from '@testing-library/react';
|
||||
|
||||
import LabelColumn from './LabelColumn';
|
||||
|
||||
let resizeCallback: ResizeObserverCallback | null = null;
|
||||
const BADGE_WIDTH = 60;
|
||||
|
||||
let resizeCallbacks: ResizeObserverCallback[] = [];
|
||||
|
||||
class MockResizeObserver {
|
||||
constructor(callback: ResizeObserverCallback) {
|
||||
resizeCallback = callback;
|
||||
resizeCallbacks.push(callback);
|
||||
}
|
||||
|
||||
observe = jest.fn();
|
||||
@@ -15,15 +16,23 @@ class MockResizeObserver {
|
||||
disconnect = jest.fn();
|
||||
}
|
||||
|
||||
function triggerResize(width: number): void {
|
||||
if (resizeCallback) {
|
||||
act(() => {
|
||||
resizeCallback?.(
|
||||
[{ contentRect: { width } } as ResizeObserverEntry],
|
||||
{} as ResizeObserver,
|
||||
);
|
||||
// jsdom has no layout: every badge measures BADGE_WIDTH, the column `containerWidth`.
|
||||
function mockLayout(containerWidth: number): void {
|
||||
jest
|
||||
.spyOn(HTMLElement.prototype, 'clientWidth', 'get')
|
||||
.mockImplementation(function getClientWidth(this: HTMLElement) {
|
||||
return this.dataset.testid === 'label-column' ? containerWidth : 0;
|
||||
});
|
||||
}
|
||||
jest
|
||||
.spyOn(HTMLElement.prototype, 'getBoundingClientRect')
|
||||
.mockReturnValue({ width: BADGE_WIDTH } as DOMRect);
|
||||
}
|
||||
|
||||
function triggerResize(containerWidth: number): void {
|
||||
mockLayout(containerWidth);
|
||||
act(() => {
|
||||
resizeCallbacks.forEach((callback) => callback([], {} as ResizeObserver));
|
||||
});
|
||||
}
|
||||
|
||||
beforeAll(() => {
|
||||
@@ -31,13 +40,14 @@ beforeAll(() => {
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
resizeCallback = null;
|
||||
resizeCallbacks = [];
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
function renderWithProviders(
|
||||
ui: React.ReactElement,
|
||||
): ReturnType<typeof render> {
|
||||
return render(<TooltipProvider>{ui}</TooltipProvider>);
|
||||
return render(ui);
|
||||
}
|
||||
|
||||
describe('LabelColumn', () => {
|
||||
@@ -54,13 +64,10 @@ describe('LabelColumn', () => {
|
||||
it('should truncate labels and show +N badge when container is narrow', () => {
|
||||
const labels = ['env', 'service', 'region', 'team', 'owner', 'version'];
|
||||
|
||||
// 3 badges (180px) + overflow reserve (40px)
|
||||
mockLayout(220);
|
||||
renderWithProviders(<LabelColumn labels={labels} />);
|
||||
|
||||
// Simulate narrow container that fits ~3 badges
|
||||
// Badge widths: env=37, service=65, region=58, team=44, owner=51, version=65
|
||||
// 220px available = 3 badges (160px) + gaps (8px) + overflow (44px)
|
||||
triggerResize(220);
|
||||
|
||||
// First 3 visible
|
||||
expect(screen.getByTestId('label-tag-env')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('label-tag-service')).toBeInTheDocument();
|
||||
@@ -93,19 +100,37 @@ describe('LabelColumn', () => {
|
||||
expect(screen.getByTestId('label-tag-service')).toHaveTextContent('service');
|
||||
});
|
||||
|
||||
it('should show overflow badge with remaining count when container is narrow', () => {
|
||||
it('should update the overflow count when the container resizes', () => {
|
||||
const labels = ['env', 'service', 'region', 'team', 'owner', 'version'];
|
||||
|
||||
mockLayout(220);
|
||||
renderWithProviders(<LabelColumn labels={labels} />);
|
||||
expect(screen.getByTestId('label-overflow-badge')).toHaveTextContent('+3');
|
||||
|
||||
// 2 badges (120px) + overflow reserve (40px)
|
||||
triggerResize(160);
|
||||
expect(screen.getByTestId('label-overflow-badge')).toHaveTextContent('+4');
|
||||
|
||||
triggerResize(1000);
|
||||
expect(screen.queryByTestId('label-overflow-badge')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should skip the overflow reserve when every label fits', () => {
|
||||
const labels = ['env', 'service', 'region', 'team', 'owner', 'version'];
|
||||
|
||||
mockLayout(labels.length * BADGE_WIDTH);
|
||||
renderWithProviders(<LabelColumn labels={labels} />);
|
||||
|
||||
// Simulate narrow container to trigger overflow (shows 3 labels)
|
||||
// 220px fits first 3 badges before overflow
|
||||
triggerResize(220);
|
||||
expect(screen.queryByTestId('label-overflow-badge')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Overflow badge shows +3 (remaining labels)
|
||||
const overflowBadge = screen.getByTestId('label-overflow-badge');
|
||||
expect(overflowBadge).toBeInTheDocument();
|
||||
expect(overflowBadge).toHaveTextContent('+3');
|
||||
it('should keep one label visible when even that one does not fit', () => {
|
||||
const labels = ['env', 'service'];
|
||||
|
||||
mockLayout(50);
|
||||
renderWithProviders(<LabelColumn labels={labels} />);
|
||||
|
||||
expect(screen.getByTestId('label-overflow-badge')).toHaveTextContent('+1');
|
||||
});
|
||||
|
||||
it('should render empty when no labels provided', () => {
|
||||
@@ -126,11 +151,9 @@ describe('LabelColumn', () => {
|
||||
it('should show all labels when container is wide enough', () => {
|
||||
const labels = ['env', 'service', 'region', 'team', 'owner', 'version'];
|
||||
|
||||
mockLayout(1000);
|
||||
renderWithProviders(<LabelColumn labels={labels} />);
|
||||
|
||||
// Simulate wide container
|
||||
triggerResize(1000);
|
||||
|
||||
// All labels visible
|
||||
labels.forEach((label) => {
|
||||
expect(screen.getByTestId(`label-tag-${label}`)).toBeInTheDocument();
|
||||
|
||||
@@ -1,35 +1,19 @@
|
||||
import { Copy } from '@signozhq/icons';
|
||||
import { Badge } from '@signozhq/ui/badge';
|
||||
import { Badge, type BadgeColorType } from '@signozhq/ui/badge';
|
||||
import { toast } from '@signozhq/ui/sonner';
|
||||
import {
|
||||
TooltipContent,
|
||||
TooltipRoot,
|
||||
TooltipTrigger,
|
||||
} from '@signozhq/ui/tooltip';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { Tooltip } from '@signozhq/ui/tooltip';
|
||||
import cx from 'classnames';
|
||||
import { useLayoutEffect, useRef, useState } from 'react';
|
||||
import { useCopyToClipboard } from 'react-use';
|
||||
|
||||
import LabelTag from './LabelTag';
|
||||
|
||||
import styles from './LabelColumn.module.scss';
|
||||
import { BADGE_GAP, estimateBadgeWidth, OVERFLOW_BADGE_WIDTH } from './utils';
|
||||
import { getVisibleCount } from './utils';
|
||||
|
||||
export interface LabelColumnProps {
|
||||
labels: string[];
|
||||
color?:
|
||||
| 'primary'
|
||||
| 'secondary'
|
||||
| 'success'
|
||||
| 'error'
|
||||
| 'warning'
|
||||
| 'robin'
|
||||
| 'forest'
|
||||
| 'amber'
|
||||
| 'sienna'
|
||||
| 'cherry'
|
||||
| 'sakura'
|
||||
| 'aqua'
|
||||
| 'vanilla';
|
||||
color?: BadgeColorType;
|
||||
value?: { [key: string]: string };
|
||||
}
|
||||
|
||||
@@ -39,60 +23,37 @@ function LabelColumn({
|
||||
color = 'primary',
|
||||
}: LabelColumnProps): JSX.Element {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const [maxVisibleCount, setMaxVisibleCount] = useState(labels.length);
|
||||
const [visibleCount, setVisibleCount] = useState(labels.length);
|
||||
const [, copyToClipboard] = useCopyToClipboard();
|
||||
|
||||
const calculateMaxVisible = useCallback(
|
||||
(width: number): number => {
|
||||
if (width <= 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
const availableWidth = width - OVERFLOW_BADGE_WIDTH - BADGE_GAP;
|
||||
let usedWidth = 0;
|
||||
let count = 0;
|
||||
|
||||
for (const label of labels) {
|
||||
const badgeWidth = estimateBadgeWidth(label, value?.[label]) + BADGE_GAP;
|
||||
if (usedWidth + badgeWidth > availableWidth && count > 0) {
|
||||
break;
|
||||
}
|
||||
usedWidth += badgeWidth;
|
||||
count++;
|
||||
}
|
||||
|
||||
return Math.max(1, count);
|
||||
},
|
||||
[labels, value],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
useLayoutEffect(() => {
|
||||
const container = containerRef.current;
|
||||
if (!container) {
|
||||
return;
|
||||
}
|
||||
|
||||
const observer = new ResizeObserver((entries) => {
|
||||
const entry = entries[0];
|
||||
if (entry && entry.contentRect.width > 0) {
|
||||
setMaxVisibleCount(calculateMaxVisible(entry.contentRect.width));
|
||||
const measure = (): void => {
|
||||
const available = container.clientWidth;
|
||||
if (available <= 0) {
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
const widths = Array.from(container.children)
|
||||
.slice(0, labels.length)
|
||||
.map((item) => item.getBoundingClientRect().width);
|
||||
const gap = parseFloat(getComputedStyle(container).columnGap) || 0;
|
||||
|
||||
setVisibleCount(getVisibleCount(widths, available, gap));
|
||||
};
|
||||
|
||||
const observer = new ResizeObserver(measure);
|
||||
observer.observe(container);
|
||||
|
||||
if (container.clientWidth > 0) {
|
||||
setMaxVisibleCount(calculateMaxVisible(container.clientWidth));
|
||||
}
|
||||
measure();
|
||||
|
||||
return (): void => observer.disconnect();
|
||||
}, [calculateMaxVisible]);
|
||||
}, [labels, value]);
|
||||
|
||||
const needsOverflow = labels.length > maxVisibleCount;
|
||||
const visibleLabels = needsOverflow
|
||||
? labels.slice(0, maxVisibleCount)
|
||||
: labels;
|
||||
const remainingLabels = needsOverflow ? labels.slice(maxVisibleCount) : [];
|
||||
const remainingLabels = labels.slice(visibleCount);
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -100,24 +61,23 @@ function LabelColumn({
|
||||
className={styles.labelColumn}
|
||||
data-testid="label-column"
|
||||
>
|
||||
{visibleLabels.map((label) => (
|
||||
<LabelTag key={label} label={label} color={color} value={value?.[label]} />
|
||||
{labels.map((label, index) => (
|
||||
<LabelTag
|
||||
key={label}
|
||||
label={label}
|
||||
color={color}
|
||||
value={value?.[label]}
|
||||
className={cx({
|
||||
[styles.overflowed]: index >= visibleCount,
|
||||
[styles.shrinkable]: index === 0 && visibleCount === 1,
|
||||
})}
|
||||
/>
|
||||
))}
|
||||
{remainingLabels.length > 0 && (
|
||||
<TooltipRoot>
|
||||
<TooltipTrigger asChild>
|
||||
<span>
|
||||
<Badge
|
||||
color={color}
|
||||
className={styles.overflowBadge}
|
||||
variant="outline"
|
||||
data-testid="label-overflow-badge"
|
||||
>
|
||||
+{remainingLabels.length}
|
||||
</Badge>
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom" align="end">
|
||||
<Tooltip
|
||||
side="bottom"
|
||||
align="end"
|
||||
title={
|
||||
<div className={styles.tooltipContent}>
|
||||
<span>
|
||||
{remainingLabels
|
||||
@@ -140,8 +100,14 @@ function LabelColumn({
|
||||
<Copy size={12} />
|
||||
</button>
|
||||
</div>
|
||||
</TooltipContent>
|
||||
</TooltipRoot>
|
||||
}
|
||||
>
|
||||
<span>
|
||||
<Badge color={color} variant="outlined" testId="label-overflow-badge">
|
||||
+{remainingLabels.length}
|
||||
</Badge>
|
||||
</span>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,11 +1,3 @@
|
||||
.labelBadge {
|
||||
cursor: default;
|
||||
font-size: 12px;
|
||||
|
||||
max-width: 180px;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.labelValue {
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
|
||||
@@ -1,35 +1,24 @@
|
||||
import { Copy } from '@signozhq/icons';
|
||||
import { Badge } from '@signozhq/ui/badge';
|
||||
import { Badge, type BadgeColorType } from '@signozhq/ui/badge';
|
||||
import { toast } from '@signozhq/ui/sonner';
|
||||
import {
|
||||
TooltipContent,
|
||||
TooltipRoot,
|
||||
TooltipTrigger,
|
||||
} from '@signozhq/ui/tooltip';
|
||||
import { Tooltip } from '@signozhq/ui/tooltip';
|
||||
import { useCopyToClipboard } from 'react-use';
|
||||
|
||||
import styles from './LabelTag.module.scss';
|
||||
|
||||
export interface LabelTagProps {
|
||||
label: string;
|
||||
color?:
|
||||
| 'primary'
|
||||
| 'secondary'
|
||||
| 'success'
|
||||
| 'error'
|
||||
| 'warning'
|
||||
| 'robin'
|
||||
| 'forest'
|
||||
| 'amber'
|
||||
| 'sienna'
|
||||
| 'cherry'
|
||||
| 'sakura'
|
||||
| 'aqua'
|
||||
| 'vanilla';
|
||||
color?: BadgeColorType;
|
||||
value?: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
function LabelTag({ label, value, color }: LabelTagProps): JSX.Element {
|
||||
function LabelTag({
|
||||
label,
|
||||
value,
|
||||
color,
|
||||
className,
|
||||
}: LabelTagProps): JSX.Element {
|
||||
const [, copyToClipboard] = useCopyToClipboard();
|
||||
const displayText = value ? `${label}: ${value}` : label;
|
||||
const searchFormat = value ? `${label} ${value}` : label;
|
||||
@@ -41,20 +30,8 @@ function LabelTag({ label, value, color }: LabelTagProps): JSX.Element {
|
||||
};
|
||||
|
||||
return (
|
||||
<TooltipRoot>
|
||||
<TooltipTrigger asChild>
|
||||
<span>
|
||||
<Badge
|
||||
color={color}
|
||||
className={styles.labelBadge}
|
||||
variant="outline"
|
||||
data-testid={`label-tag-${label}`}
|
||||
>
|
||||
<span className={styles.labelValue}>{displayText}</span>
|
||||
</Badge>
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<Tooltip
|
||||
title={
|
||||
<div className={styles.tooltipContent}>
|
||||
<span>{displayText}</span>
|
||||
<button
|
||||
@@ -66,8 +43,19 @@ function LabelTag({ label, value, color }: LabelTagProps): JSX.Element {
|
||||
<Copy size={12} />
|
||||
</button>
|
||||
</div>
|
||||
</TooltipContent>
|
||||
</TooltipRoot>
|
||||
}
|
||||
>
|
||||
<span className={className}>
|
||||
<Badge
|
||||
textTransform="none"
|
||||
color={color ?? 'secondary'}
|
||||
variant="outlined"
|
||||
testId={`label-tag-${label}`}
|
||||
>
|
||||
<span className={styles.labelValue}>{displayText}</span>
|
||||
</Badge>
|
||||
</span>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,33 @@
|
||||
export const BADGE_GAP = 4;
|
||||
export const OVERFLOW_BADGE_WIDTH = 40;
|
||||
|
||||
export const BADGE_MAX_WIDTH = 180;
|
||||
export const BADGE_PADDING = 16;
|
||||
export const CHAR_WIDTH = 7;
|
||||
|
||||
export function estimateBadgeWidth(label: string, value?: string): number {
|
||||
const displayText = value ? `${label}: ${value}` : label;
|
||||
return Math.min(
|
||||
displayText.length * CHAR_WIDTH + BADGE_PADDING,
|
||||
BADGE_MAX_WIDTH,
|
||||
/**
|
||||
* How many leading labels fit in `available` px, reserving room for the `+N` badge
|
||||
* whenever some are left out. At least one label is always shown.
|
||||
*/
|
||||
export function getVisibleCount(
|
||||
widths: number[],
|
||||
available: number,
|
||||
gap: number,
|
||||
): number {
|
||||
const total = widths.reduce(
|
||||
(sum, width, index) => sum + width + (index > 0 ? gap : 0),
|
||||
0,
|
||||
);
|
||||
if (total <= available) {
|
||||
return widths.length;
|
||||
}
|
||||
|
||||
const limit = available - OVERFLOW_BADGE_WIDTH - gap;
|
||||
let used = 0;
|
||||
let count = 0;
|
||||
|
||||
for (const width of widths) {
|
||||
used += width + (count > 0 ? gap : 0);
|
||||
if (used > limit) {
|
||||
break;
|
||||
}
|
||||
count++;
|
||||
}
|
||||
|
||||
return Math.max(1, count);
|
||||
}
|
||||
|
||||
@@ -30,21 +30,23 @@ function NoResultsEmptyState({
|
||||
<div className={styles.actions}>
|
||||
{onClear && (
|
||||
<Button
|
||||
size="md"
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
onClick={onClear}
|
||||
data-testid="no-results-clear-button"
|
||||
testId="no-results-clear-button"
|
||||
>
|
||||
{clearButtonText}
|
||||
</Button>
|
||||
)}
|
||||
{onRefresh && (
|
||||
<Button
|
||||
size="md"
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
prefix={<RefreshCw size={14} />}
|
||||
onClick={onRefresh}
|
||||
data-testid="no-results-refresh-button"
|
||||
testId="no-results-refresh-button"
|
||||
>
|
||||
Refresh
|
||||
</Button>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { BadgeColor } from '@signozhq/ui/badge';
|
||||
import type { BadgeColorType } from '@signozhq/ui/badge';
|
||||
|
||||
export const STATE_ORDER = ['firing', 'pending', 'inactive', 'disabled'];
|
||||
export const SEVERITY_ORDER = ['critical', 'error', 'warning', 'info'];
|
||||
@@ -24,9 +24,9 @@ export const SEVERITY_COLORS: Record<string, string> = {
|
||||
info: 'var(--bg-robin-500)',
|
||||
};
|
||||
|
||||
export const SEVERITY_BADGE_COLORS: Record<string, BadgeColor> = {
|
||||
critical: 'error',
|
||||
error: 'error',
|
||||
export const SEVERITY_BADGE_COLORS: Record<string, BadgeColorType> = {
|
||||
critical: 'danger',
|
||||
error: 'danger',
|
||||
warning: 'warning',
|
||||
info: 'primary',
|
||||
};
|
||||
|
||||
@@ -33,36 +33,3 @@
|
||||
color: var(--l1-foreground);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.auth-header-help-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
height: 32px;
|
||||
padding: 10px 16px;
|
||||
background: var(--l2-background);
|
||||
color: var(--l2-foreground);
|
||||
border: none;
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
transition: opacity 0.2s ease;
|
||||
|
||||
span {
|
||||
font-family: var(--font-family-inter, Inter, sans-serif);
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
line-height: 1;
|
||||
color: var(--l2-foreground);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
svg {
|
||||
flex-shrink: 0;
|
||||
color: var(--l2-foreground);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,11 +22,11 @@ function AuthHeader(): JSX.Element {
|
||||
<span className="auth-header-logo-text">SigNoz</span>
|
||||
</div>
|
||||
<Button
|
||||
className="auth-header-help-button"
|
||||
size="md"
|
||||
prefix={<LifeBuoy size={12} />}
|
||||
onClick={handleGetHelp}
|
||||
variant="solid"
|
||||
color="none"
|
||||
color="secondary"
|
||||
>
|
||||
Get Help
|
||||
</Button>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { Dispatch, SetStateAction, useState } from 'react';
|
||||
import { Check, Plus, X } from '@signozhq/icons';
|
||||
import { Button, Flex } from 'antd';
|
||||
import { Badge } from '@signozhq/ui/badge';
|
||||
import { Pill } from '@signozhq/ui/pill';
|
||||
import Input from 'components/Input';
|
||||
|
||||
import './Badges.styles.scss';
|
||||
@@ -47,18 +47,13 @@ function Badges({ tags, setTags }: AddTagsProps): JSX.Element {
|
||||
return (
|
||||
<div className="tags-container">
|
||||
{tags.map<React.ReactNode>((tag) => (
|
||||
<Badge
|
||||
<Pill.Closeable
|
||||
textTransform="none"
|
||||
key={tag}
|
||||
color="vanilla"
|
||||
style={{ userSelect: 'none' }}
|
||||
closable
|
||||
onClose={(e): void => {
|
||||
e.preventDefault();
|
||||
handleClose(tag);
|
||||
}}
|
||||
onClose={() => handleClose(tag)}
|
||||
>
|
||||
{tag}
|
||||
</Badge>
|
||||
</Pill.Closeable>
|
||||
))}
|
||||
|
||||
{inputVisible && (
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useHistory, useLocation } from 'react-router-dom';
|
||||
import { Row, Select, Spin } from 'antd';
|
||||
import { navigate } from 'lib/router/navigation';
|
||||
import { useAppLocation } from 'lib/router/useAppLocation';
|
||||
import {
|
||||
getValuesFromQueryParams,
|
||||
setQueryParamsFromOptions,
|
||||
@@ -35,8 +36,7 @@ export function FilterSelect({
|
||||
useCeleryFilterOptions(filterType);
|
||||
|
||||
const urlQuery = useUrlQuery();
|
||||
const history = useHistory();
|
||||
const location = useLocation();
|
||||
const location = useAppLocation();
|
||||
|
||||
// Add state to track the current search input
|
||||
const [searchValue, setSearchValue] = useState<string>('');
|
||||
@@ -66,7 +66,7 @@ export function FilterSelect({
|
||||
setQueryParamsFromOptions(
|
||||
value as string[],
|
||||
urlQuery,
|
||||
history,
|
||||
navigate,
|
||||
location,
|
||||
queryParam,
|
||||
);
|
||||
@@ -77,7 +77,6 @@ export function FilterSelect({
|
||||
handleSearch,
|
||||
shouldSetQueryParams,
|
||||
urlQuery,
|
||||
history,
|
||||
location,
|
||||
queryParam,
|
||||
onChange,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useHistory, useLocation } from 'react-router-dom';
|
||||
import { Select, Spin } from 'antd';
|
||||
import { navigate } from 'lib/router/navigation';
|
||||
import { useAppLocation } from 'lib/router/useAppLocation';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import { SelectMaxTagPlaceholder } from 'components/MessagingQueues/MQCommon/MQCommon';
|
||||
import { QueryParams } from 'constants/query';
|
||||
@@ -16,8 +17,7 @@ import './CeleryTaskConfigOptions.styles.scss';
|
||||
function CeleryTaskConfigOptions(): JSX.Element {
|
||||
const { handleSearch, isFetching, options } =
|
||||
useCeleryFilterOptions('celery.task_name');
|
||||
const history = useHistory();
|
||||
const location = useLocation();
|
||||
const location = useAppLocation();
|
||||
|
||||
const urlQuery = useUrlQuery();
|
||||
|
||||
@@ -52,7 +52,7 @@ function CeleryTaskConfigOptions(): JSX.Element {
|
||||
setQueryParamsFromOptions(
|
||||
value,
|
||||
urlQuery,
|
||||
history,
|
||||
navigate,
|
||||
location,
|
||||
QueryParams.taskName,
|
||||
);
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useHistory, useLocation } from 'react-router-dom';
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { navigate } from 'lib/router/navigation';
|
||||
import { useAppLocation } from 'lib/router/useAppLocation';
|
||||
import { QueryParams } from 'constants/query';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { ViewMenuAction } from 'container/WidgetCard/config';
|
||||
@@ -50,8 +51,7 @@ function CeleryTaskBar({
|
||||
queryEnabled: boolean;
|
||||
checkIfDataExists?: (isDataAvailable: boolean) => void;
|
||||
}): JSX.Element {
|
||||
const history = useHistory();
|
||||
const { pathname } = useLocation();
|
||||
const { pathname } = useAppLocation();
|
||||
const dispatch = useDispatch();
|
||||
const urlQuery = useUrlQuery();
|
||||
const isDarkMode = useIsDarkMode();
|
||||
@@ -68,13 +68,13 @@ function CeleryTaskBar({
|
||||
urlQuery.set(QueryParams.startTime, startTimestamp.toString());
|
||||
urlQuery.set(QueryParams.endTime, endTimestamp.toString());
|
||||
const generatedUrl = `${pathname}?${urlQuery.toString()}`;
|
||||
history.push(generatedUrl);
|
||||
navigate(generatedUrl);
|
||||
|
||||
if (startTimestamp !== endTimestamp) {
|
||||
dispatch(UpdateTimeInterval('custom', [startTimestamp, endTimestamp]));
|
||||
}
|
||||
},
|
||||
[dispatch, history, pathname, urlQuery],
|
||||
[dispatch, pathname, urlQuery],
|
||||
);
|
||||
|
||||
const [barState, setBarState] = useState<CeleryTaskState>(CeleryTaskState.All);
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { useCallback, useMemo } from 'react';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { useHistory, useLocation } from 'react-router-dom';
|
||||
import { ENTITY_VERSION_V4 } from 'constants/app';
|
||||
import { navigate } from 'lib/router/navigation';
|
||||
import { useAppLocation } from 'lib/router/useAppLocation';
|
||||
import { QueryParams } from 'constants/query';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { ViewMenuAction } from 'container/WidgetCard/config';
|
||||
@@ -54,8 +55,7 @@ function CeleryTaskGraph({
|
||||
checkIfDataExists?: (isDataAvailable: boolean) => void;
|
||||
analyticsEvent?: string;
|
||||
}): JSX.Element {
|
||||
const history = useHistory();
|
||||
const { pathname } = useLocation();
|
||||
const { pathname } = useAppLocation();
|
||||
const dispatch = useDispatch();
|
||||
const urlQuery = useUrlQuery();
|
||||
const isDarkMode = useIsDarkMode();
|
||||
@@ -83,13 +83,13 @@ function CeleryTaskGraph({
|
||||
urlQuery.set(QueryParams.startTime, startTimestamp.toString());
|
||||
urlQuery.set(QueryParams.endTime, endTimestamp.toString());
|
||||
const generatedUrl = `${pathname}?${urlQuery.toString()}`;
|
||||
history.push(generatedUrl);
|
||||
navigate(generatedUrl);
|
||||
|
||||
if (startTimestamp !== endTimestamp) {
|
||||
dispatch(UpdateTimeInterval('custom', [startTimestamp, endTimestamp]));
|
||||
}
|
||||
},
|
||||
[dispatch, history, pathname, urlQuery],
|
||||
[dispatch, pathname, urlQuery],
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useHistory, useLocation } from 'react-router-dom';
|
||||
import { Col, Row } from 'antd';
|
||||
import { navigate } from 'lib/router/navigation';
|
||||
import { useAppLocation } from 'lib/router/useAppLocation';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import { QueryParams } from 'constants/query';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
@@ -48,8 +49,7 @@ function CeleryTaskLatencyGraph({
|
||||
queryEnabled: boolean;
|
||||
checkIfDataExists?: (isDataAvailable: boolean) => void;
|
||||
}): JSX.Element {
|
||||
const history = useHistory();
|
||||
const { pathname } = useLocation();
|
||||
const { pathname } = useAppLocation();
|
||||
const dispatch = useDispatch();
|
||||
const urlQuery = useUrlQuery();
|
||||
const isDarkMode = useIsDarkMode();
|
||||
@@ -80,13 +80,13 @@ function CeleryTaskLatencyGraph({
|
||||
urlQuery.set(QueryParams.startTime, startTimestamp.toString());
|
||||
urlQuery.set(QueryParams.endTime, endTimestamp.toString());
|
||||
const generatedUrl = `${pathname}?${urlQuery.toString()}`;
|
||||
history.push(generatedUrl);
|
||||
navigate(generatedUrl);
|
||||
|
||||
if (startTimestamp !== endTimestamp) {
|
||||
dispatch(UpdateTimeInterval('custom', [startTimestamp, endTimestamp]));
|
||||
}
|
||||
},
|
||||
[dispatch, history, pathname, urlQuery],
|
||||
[dispatch, pathname, urlQuery],
|
||||
);
|
||||
|
||||
const selectedFilters = useMemo(
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { QueryParams } from 'constants/query';
|
||||
import { History, Location } from 'history';
|
||||
import type { AppLocation, NavigateFn } from 'lib/router/types';
|
||||
import getRenderer from 'lib/uPlotLib/utils/getRenderer';
|
||||
import { Widgets } from 'types/api/widgets/widget';
|
||||
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
@@ -17,13 +17,13 @@ export function getValuesFromQueryParams(
|
||||
export function setQueryParamsFromOptions(
|
||||
value: string[],
|
||||
urlQuery: URLSearchParams,
|
||||
history: History<unknown>,
|
||||
location: Location<unknown>,
|
||||
navigate: NavigateFn,
|
||||
location: AppLocation,
|
||||
queryParams: QueryParams,
|
||||
): void {
|
||||
urlQuery.set(queryParams, value.join(','));
|
||||
const generatedUrl = `${location.pathname}?${urlQuery.toString()}`;
|
||||
history.replace(generatedUrl);
|
||||
navigate(generatedUrl, { replace: true });
|
||||
}
|
||||
|
||||
export function getFiltersFromQueryParams(
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useState } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { useAppLocation } from 'lib/router/useAppLocation';
|
||||
import { Button } from 'antd';
|
||||
import AddCreditCardModal from 'components/AddCreditCardModal/AddCreditCardModal';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
@@ -8,7 +8,7 @@ import { MessageSquareText } from '@signozhq/icons';
|
||||
export default function ChatSupportGateway(): JSX.Element {
|
||||
const [isAddCreditCardModalOpen, setIsAddCreditCardModalOpen] =
|
||||
useState(false);
|
||||
const { pathname } = useLocation();
|
||||
const { pathname } = useAppLocation();
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -39,9 +39,3 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.cloud-service-data-collected-table-tooltip {
|
||||
max-width: 280px;
|
||||
white-space: normal;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
CloudintegrationtypesCollectedMetricDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import { BarChart, Info, ScrollText } from '@signozhq/icons';
|
||||
import { TooltipProvider, TooltipSimple } from '@signozhq/ui/tooltip';
|
||||
import { Tooltip } from '@signozhq/ui/tooltip';
|
||||
|
||||
import './CloudServiceDataCollected.styles.scss';
|
||||
|
||||
@@ -88,23 +88,15 @@ function CloudServiceDataCollected({
|
||||
<BarChart size={14} />
|
||||
Metrics
|
||||
{metricsInfoTooltip && (
|
||||
<TooltipProvider>
|
||||
<TooltipSimple
|
||||
title={metricsInfoTooltip}
|
||||
side="top"
|
||||
tooltipContentProps={{
|
||||
className: 'cloud-service-data-collected-table-tooltip',
|
||||
}}
|
||||
<Tooltip title={metricsInfoTooltip} side="top">
|
||||
<span
|
||||
className="cloud-service-data-collected-table-heading-info"
|
||||
aria-label="About the metrics listed below"
|
||||
data-testid="data-collected-metrics-info"
|
||||
>
|
||||
<span
|
||||
className="cloud-service-data-collected-table-heading-info"
|
||||
aria-label="About the metrics listed below"
|
||||
data-testid="data-collected-metrics-info"
|
||||
>
|
||||
<Info size={12} />
|
||||
</span>
|
||||
</TooltipSimple>
|
||||
</TooltipProvider>
|
||||
<Info size={12} />
|
||||
</span>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
<Table
|
||||
|
||||
@@ -2,6 +2,13 @@
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.copyButton {
|
||||
position: absolute;
|
||||
right: 8px;
|
||||
top: 8px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.codeBlockSyntaxHighlighter {
|
||||
background-color: var(--l2-background) !important;
|
||||
border-radius: 4px !important;
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useMemo, useState } from 'react';
|
||||
import { useCopyToClipboard } from 'react-use';
|
||||
import { Check, Copy } from '@signozhq/icons';
|
||||
import { Button } from '@signozhq/ui/button';
|
||||
import { Tooltip } from '@signozhq/ui/tooltip';
|
||||
import SyntaxHighlighter, {
|
||||
a11yDark,
|
||||
} from 'components/MarkdownRenderer/syntaxHighlighter';
|
||||
@@ -52,16 +53,20 @@ function CodeBlock({
|
||||
data-testid="code-block-container"
|
||||
>
|
||||
{showCopyButton ? (
|
||||
<Button
|
||||
variant="ghost"
|
||||
color="secondary"
|
||||
size="sm"
|
||||
onClick={handleCopy}
|
||||
prefix={isCopied ? <Check size={14} /> : <Copy size={14} />}
|
||||
aria-label="Copy code"
|
||||
title={isCopied ? 'Copied' : 'Copy'}
|
||||
style={{ position: 'absolute', right: 8, top: 8, zIndex: 1 }}
|
||||
/>
|
||||
<div className={styles.copyButton}>
|
||||
<Tooltip title={isCopied ? 'Copied' : 'Copy'}>
|
||||
<Button
|
||||
variant="ghost"
|
||||
color="secondary"
|
||||
size="sm"
|
||||
icon
|
||||
onClick={handleCopy}
|
||||
aria-label="Copy code"
|
||||
>
|
||||
{isCopied ? <Check size={14} /> : <Copy size={14} />}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
) : null}
|
||||
<SyntaxHighlighter
|
||||
style={a11yDark}
|
||||
|
||||
@@ -134,26 +134,27 @@ function CreateServiceAccountModal(): JSX.Element {
|
||||
|
||||
<DialogFooter className="create-sa-modal__footer">
|
||||
<Button
|
||||
size="md"
|
||||
type="button"
|
||||
variant="solid"
|
||||
color="secondary"
|
||||
onClick={handleClose}
|
||||
data-testid="create-sa-cancel-btn"
|
||||
testId="create-sa-cancel-btn"
|
||||
prefix={<X size={12} />}
|
||||
>
|
||||
<X size={12} />
|
||||
Cancel
|
||||
</Button>
|
||||
|
||||
<AuthZButton
|
||||
size="md"
|
||||
checks={[SACreatePermission]}
|
||||
withPortal={false}
|
||||
type="submit"
|
||||
form="create-sa-form"
|
||||
variant="solid"
|
||||
color="primary"
|
||||
loading={isSubmitting}
|
||||
disabled={!isValid}
|
||||
data-testid="create-sa-submit-btn"
|
||||
testId="create-sa-submit-btn"
|
||||
>
|
||||
Create Service Account
|
||||
</AuthZButton>
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
} from 'lib/authz/utils/authz-test-utils';
|
||||
import { rest, server } from 'mocks-server/server';
|
||||
import { NuqsTestingAdapter } from 'nuqs/adapters/testing';
|
||||
import { submitImplicitly } from 'tests/submitImplicitly';
|
||||
import { render, screen, userEvent, waitFor } from 'tests/test-utils';
|
||||
|
||||
import CreateServiceAccountModal from '../CreateServiceAccountModal';
|
||||
@@ -60,7 +61,10 @@ describe('CreateServiceAccountModal', () => {
|
||||
await screen.findByTestId('create-sa-name-input');
|
||||
|
||||
await waitFor(() =>
|
||||
expect(screen.getByTestId('create-sa-submit-btn')).toBeDisabled(),
|
||||
expect(screen.getByTestId('create-sa-submit-btn')).toHaveAttribute(
|
||||
'aria-disabled',
|
||||
'true',
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -72,10 +76,14 @@ describe('CreateServiceAccountModal', () => {
|
||||
const submitBtn = await screen.findByTestId('create-sa-submit-btn');
|
||||
|
||||
await user.type(nameInput, 'test');
|
||||
await waitFor(() => expect(submitBtn).not.toBeDisabled());
|
||||
await waitFor(() =>
|
||||
expect(submitBtn).not.toHaveAttribute('aria-disabled', 'true'),
|
||||
);
|
||||
|
||||
await user.clear(nameInput);
|
||||
await waitFor(() => expect(submitBtn).toBeDisabled());
|
||||
await waitFor(() =>
|
||||
expect(submitBtn).toHaveAttribute('aria-disabled', 'true'),
|
||||
);
|
||||
});
|
||||
|
||||
it('successful submit shows toast.success and closes modal', async () => {
|
||||
@@ -86,7 +94,9 @@ describe('CreateServiceAccountModal', () => {
|
||||
await user.type(nameInput, 'Deploy Bot');
|
||||
|
||||
const submitBtn = screen.getByTestId('create-sa-submit-btn');
|
||||
await waitFor(() => expect(submitBtn).not.toBeDisabled());
|
||||
await waitFor(() =>
|
||||
expect(submitBtn).not.toHaveAttribute('aria-disabled', 'true'),
|
||||
);
|
||||
await user.click(submitBtn);
|
||||
|
||||
await waitFor(() => {
|
||||
@@ -102,6 +112,40 @@ describe('CreateServiceAccountModal', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('pressing Enter again while creating sends one request', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
let requests = 0;
|
||||
server.use(
|
||||
rest.post(SERVICE_ACCOUNTS_ENDPOINT, (_, res, ctx) => {
|
||||
requests += 1;
|
||||
return res(
|
||||
ctx.delay(100),
|
||||
ctx.status(201),
|
||||
ctx.json({ status: 'success', data: {} }),
|
||||
);
|
||||
}),
|
||||
);
|
||||
renderModal();
|
||||
|
||||
const nameInput = await screen.findByTestId<HTMLInputElement>(
|
||||
'create-sa-name-input',
|
||||
);
|
||||
await user.type(nameInput, 'Deploy Bot');
|
||||
const submitBtn = screen.getByTestId('create-sa-submit-btn');
|
||||
await waitFor(() =>
|
||||
expect(submitBtn).not.toHaveAttribute('aria-disabled', 'true'),
|
||||
);
|
||||
|
||||
submitImplicitly(nameInput);
|
||||
await waitFor(() => expect(submitBtn).toHaveAttribute('aria-busy', 'true'));
|
||||
submitImplicitly(nameInput);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockToast.success).toHaveBeenCalled();
|
||||
});
|
||||
expect(requests).toBe(1);
|
||||
});
|
||||
|
||||
it('shows toast.error on API error and keeps modal open', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
|
||||
@@ -120,7 +164,9 @@ describe('CreateServiceAccountModal', () => {
|
||||
await user.type(nameInput, 'Dupe Bot');
|
||||
|
||||
const submitBtn = screen.getByTestId('create-sa-submit-btn');
|
||||
await waitFor(() => expect(submitBtn).not.toBeDisabled());
|
||||
await waitFor(() =>
|
||||
expect(submitBtn).not.toHaveAttribute('aria-disabled', 'true'),
|
||||
);
|
||||
await user.click(submitBtn);
|
||||
|
||||
await waitFor(() => {
|
||||
@@ -180,7 +226,10 @@ describe('CreateServiceAccountModal', () => {
|
||||
).resolves.toBeInTheDocument();
|
||||
|
||||
// The footer lives outside the guard: submit is gated, Cancel still works.
|
||||
expect(screen.getByTestId('create-sa-submit-btn')).toBeDisabled();
|
||||
expect(screen.getByTestId('create-sa-submit-btn')).toHaveAttribute(
|
||||
'aria-disabled',
|
||||
'true',
|
||||
);
|
||||
|
||||
await user.click(screen.getByTestId('create-sa-cancel-btn'));
|
||||
|
||||
|
||||
@@ -4,30 +4,6 @@
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
|
||||
.zoom-out-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
color: var(--secondary-foreground);
|
||||
background-color: var(--secondary-background);
|
||||
border: 1px solid var(--secondary-border);
|
||||
border-radius: 2px;
|
||||
box-shadow: none;
|
||||
padding: 10px;
|
||||
height: 33px;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
color: var(--primary-foreground);
|
||||
background: var(--primary-background);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
.timeSelection-input {
|
||||
&:hover {
|
||||
border-color: var(--l1-border) !important;
|
||||
|
||||
@@ -1,20 +1,12 @@
|
||||
import { useState } from 'react';
|
||||
import { fireEvent, render, screen } from '@testing-library/react';
|
||||
import dayjs from 'dayjs';
|
||||
import { mockAppLocation } from 'tests/router-mocks';
|
||||
import * as timeUtils from 'utils/timeUtils';
|
||||
|
||||
import CustomTimePicker from './CustomTimePicker';
|
||||
|
||||
jest.mock('react-router-dom', () => {
|
||||
const actual = jest.requireActual('react-router-dom');
|
||||
|
||||
return {
|
||||
...actual,
|
||||
useLocation: jest.fn().mockReturnValue({
|
||||
pathname: '/test-path',
|
||||
}),
|
||||
};
|
||||
});
|
||||
mockAppLocation({ pathname: '/test-path' });
|
||||
|
||||
jest.mock('react-redux', () => ({
|
||||
...jest.requireActual('react-redux'),
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { useAppLocation } from 'lib/router/useAppLocation';
|
||||
import { Button } from '@signozhq/ui/button';
|
||||
import { Input, InputRef, Popover, Tooltip } from 'antd';
|
||||
import cx from 'classnames';
|
||||
@@ -72,6 +72,8 @@ interface CustomTimePickerProps {
|
||||
maxTime: number;
|
||||
/** When true, zoom-out button is hidden (e.g. in drawer/modal time selection) */
|
||||
isModalTimeSelection?: boolean;
|
||||
/** Lands on the trigger input. Not spread — the component takes no rest props. */
|
||||
'data-testid'?: string;
|
||||
}
|
||||
|
||||
function CustomTimePicker({
|
||||
@@ -95,6 +97,7 @@ function CustomTimePicker({
|
||||
minTime,
|
||||
maxTime,
|
||||
isModalTimeSelection = false,
|
||||
'data-testid': dataTestId,
|
||||
}: CustomTimePickerProps): JSX.Element {
|
||||
const [selectedTimePlaceholderValue, setSelectedTimePlaceholderValue] =
|
||||
useState('Select / Enter Time Range');
|
||||
@@ -106,7 +109,7 @@ function CustomTimePicker({
|
||||
const [inputErrorDetails, setInputErrorDetails] = useState<
|
||||
TimeRangeValidationResult['errorDetails'] | null
|
||||
>(null);
|
||||
const location = useLocation();
|
||||
const location = useAppLocation();
|
||||
|
||||
const inputRef = useRef<InputRef>(null);
|
||||
const initialInputValueOnOpenRef = useRef<string>('');
|
||||
@@ -596,6 +599,7 @@ function CustomTimePicker({
|
||||
>
|
||||
<Input
|
||||
ref={inputRef}
|
||||
data-testid={dataTestId}
|
||||
autoComplete="off"
|
||||
className={cx(
|
||||
'timeSelection-input',
|
||||
@@ -650,20 +654,20 @@ function CustomTimePicker({
|
||||
</Popover>
|
||||
</Tooltip>
|
||||
{!showLiveLogs && !isModalTimeSelection && (
|
||||
<Tooltip
|
||||
title={
|
||||
zoomOutDisabled ? 'Zoom out time range is limited to 1 month' : 'Zoom out'
|
||||
}
|
||||
>
|
||||
<Tooltip title={zoomOutDisabled ? undefined : 'Zoom out'}>
|
||||
<Button
|
||||
className="zoom-out-btn"
|
||||
disabledTooltip="Zoom out time range is limited to 1 month"
|
||||
size="md"
|
||||
onClick={handleZoomOut}
|
||||
disabled={zoomOutDisabled}
|
||||
data-testid="zoom-out-btn"
|
||||
prefix={<ZoomOut size={14} />}
|
||||
testId="zoom-out-btn"
|
||||
icon
|
||||
aria-label="Zoom out"
|
||||
variant="solid"
|
||||
color="none"
|
||||
/>
|
||||
color="secondary"
|
||||
>
|
||||
<ZoomOut size={14} />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
@@ -682,4 +686,5 @@ CustomTimePicker.defaultProps = {
|
||||
onExitLiveLogs: noop,
|
||||
showLiveLogs: false,
|
||||
showRecentlyUsed: true,
|
||||
'data-testid': undefined,
|
||||
};
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { useAppLocation } from 'lib/router/useAppLocation';
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Button } from 'antd';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
@@ -114,7 +114,7 @@ function CustomTimePickerPopoverContent({
|
||||
customDateTimeInputStatus = CustomTimePickerInputStatus.UNSET,
|
||||
inputErrorDetails,
|
||||
}: CustomTimePickerPopoverContentProps): JSX.Element {
|
||||
const { pathname } = useLocation();
|
||||
const { pathname } = useAppLocation();
|
||||
|
||||
const isLogsExplorerPage = useMemo(
|
||||
() => pathname === ROUTES.LOGS_EXPLORER,
|
||||
@@ -180,6 +180,7 @@ function CustomTimePickerPopoverContent({
|
||||
type="text"
|
||||
className="time-btns"
|
||||
key={option.label + option.value}
|
||||
data-testid={`time-chip-${option.value}`}
|
||||
onClick={(): void => {
|
||||
handleExitLiveLogs();
|
||||
onSelectHandler(option.label, option.value);
|
||||
@@ -259,6 +260,7 @@ function CustomTimePickerPopoverContent({
|
||||
<Button
|
||||
type="text"
|
||||
key={option.label + option.value}
|
||||
data-testid={`time-option-${option.value}`}
|
||||
onClick={(e: React.MouseEvent<HTMLButtonElement>): void => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { QueryParams } from 'constants/query';
|
||||
import { mockAppLocation } from 'tests/router-mocks';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
|
||||
import CustomTimePicker from '../CustomTimePicker';
|
||||
@@ -59,10 +60,6 @@ jest.mock('providers/Timezone', () => ({
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
useLocation: (): { pathname: string } => ({ pathname: '/logs-explorer' }),
|
||||
}));
|
||||
|
||||
const MS_PER_DAY = 24 * 60 * 60 * 1000;
|
||||
const now = Date.now();
|
||||
const defaultProps = {
|
||||
@@ -85,6 +82,7 @@ describe('CustomTimePicker - zoom out button', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
jest.spyOn(Date, 'now').mockReturnValue(NOW_MS);
|
||||
mockAppLocation({ pathname: '/logs-explorer' });
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -164,6 +162,6 @@ describe('CustomTimePicker - zoom out button', () => {
|
||||
);
|
||||
|
||||
const zoomOutBtn = screen.getByTestId('zoom-out-btn');
|
||||
expect(zoomOutBtn).toBeDisabled();
|
||||
expect(zoomOutBtn).toHaveAttribute('aria-disabled', 'true');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -27,12 +27,14 @@ function DetailsHeader({
|
||||
const closeButton = (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
size="sm"
|
||||
icon
|
||||
color="secondary"
|
||||
onClick={onClose}
|
||||
aria-label="Close"
|
||||
prefix={<X size={14} />}
|
||||
></Button>
|
||||
>
|
||||
<X size={14} />
|
||||
</Button>
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@@ -42,12 +42,5 @@
|
||||
height: 1px;
|
||||
background: var(--l1-border);
|
||||
}
|
||||
|
||||
.export-button {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -308,13 +308,16 @@ describe.each([
|
||||
|
||||
fireEvent.click(screen.getByText('Export'));
|
||||
|
||||
expect(screen.getByTestId(testId)).toBeDisabled();
|
||||
expect(screen.getByTestId(testId)).toHaveAttribute('aria-disabled', 'true');
|
||||
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
|
||||
|
||||
resolveDownload!();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId(testId)).not.toBeDisabled();
|
||||
expect(screen.getByTestId(testId)).not.toHaveAttribute(
|
||||
'aria-disabled',
|
||||
'true',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { Popover, Tooltip } from 'antd';
|
||||
import { Button } from '@signozhq/ui/button';
|
||||
import { RadioGroup, RadioGroupItem } from '@signozhq/ui/radio-group';
|
||||
import { RadioGroup } from '@signozhq/ui/radio-group';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import { TelemetryFieldKey } from 'api/v5/v5';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
@@ -69,10 +69,15 @@ export default function DownloadOptionsMenu({
|
||||
>
|
||||
<div className="export-format">
|
||||
<Typography.Text className="title">FORMAT</Typography.Text>
|
||||
<RadioGroup value={exportFormat} onChange={setExportFormat}>
|
||||
<RadioGroupItem value={DownloadFormats.CSV}>csv</RadioGroupItem>
|
||||
<RadioGroupItem value={DownloadFormats.JSONL}>jsonl</RadioGroupItem>
|
||||
</RadioGroup>
|
||||
<RadioGroup
|
||||
color="primary"
|
||||
value={exportFormat}
|
||||
onChange={setExportFormat}
|
||||
items={[
|
||||
{ value: DownloadFormats.CSV, label: 'csv' },
|
||||
{ value: DownloadFormats.JSONL, label: 'jsonl' },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="horizontal-line" />
|
||||
@@ -80,19 +85,15 @@ export default function DownloadOptionsMenu({
|
||||
<div className="row-limit">
|
||||
<Typography.Text className="title">Number of Rows</Typography.Text>
|
||||
<RadioGroup
|
||||
color="primary"
|
||||
value={String(rowLimit)}
|
||||
onChange={(value): void => setRowLimit(Number(value))}
|
||||
>
|
||||
<RadioGroupItem value={String(DownloadRowCounts.TEN_K)}>
|
||||
10k
|
||||
</RadioGroupItem>
|
||||
<RadioGroupItem value={String(DownloadRowCounts.THIRTY_K)}>
|
||||
30k
|
||||
</RadioGroupItem>
|
||||
<RadioGroupItem value={String(DownloadRowCounts.FIFTY_K)}>
|
||||
50k
|
||||
</RadioGroupItem>
|
||||
</RadioGroup>
|
||||
items={[
|
||||
{ value: String(DownloadRowCounts.TEN_K), label: '10k' },
|
||||
{ value: String(DownloadRowCounts.THIRTY_K), label: '30k' },
|
||||
{ value: String(DownloadRowCounts.FIFTY_K), label: '50k' },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{dataSource !== DataSource.TRACES && (
|
||||
@@ -101,12 +102,15 @@ export default function DownloadOptionsMenu({
|
||||
|
||||
<div className="columns-scope">
|
||||
<Typography.Text className="title">Columns</Typography.Text>
|
||||
<RadioGroup value={columnsScope} onChange={setColumnsScope}>
|
||||
<RadioGroupItem value={DownloadColumnsScopes.ALL}>All</RadioGroupItem>
|
||||
<RadioGroupItem value={DownloadColumnsScopes.SELECTED}>
|
||||
Selected
|
||||
</RadioGroupItem>
|
||||
</RadioGroup>
|
||||
<RadioGroup
|
||||
color="primary"
|
||||
value={columnsScope}
|
||||
onChange={setColumnsScope}
|
||||
items={[
|
||||
{ value: DownloadColumnsScopes.ALL, label: 'All' },
|
||||
{ value: DownloadColumnsScopes.SELECTED, label: 'Selected' },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
@@ -115,9 +119,9 @@ export default function DownloadOptionsMenu({
|
||||
variant="solid"
|
||||
color="primary"
|
||||
prefix={<Download size={16} />}
|
||||
size="md"
|
||||
width="100%"
|
||||
onClick={handleExport}
|
||||
className="export-button"
|
||||
disabled={isDownloading}
|
||||
loading={isDownloading}
|
||||
>
|
||||
Export
|
||||
@@ -148,13 +152,14 @@ export default function DownloadOptionsMenu({
|
||||
<Button
|
||||
variant="ghost"
|
||||
color="secondary"
|
||||
size="icon"
|
||||
prefix={<Download size={14} />}
|
||||
size="sm"
|
||||
icon
|
||||
aria-label="Download"
|
||||
data-testid={`periscope-btn-download-${dataSource}`}
|
||||
disabled={isDownloading}
|
||||
testId={`periscope-btn-download-${dataSource}`}
|
||||
loading={isDownloading}
|
||||
/>
|
||||
>
|
||||
<Download size={14} />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</Popover>
|
||||
);
|
||||
|
||||
@@ -38,18 +38,23 @@ function DeleteMemberDialog({
|
||||
|
||||
const footer = (
|
||||
<>
|
||||
<Button variant="solid" color="secondary" onClick={onClose}>
|
||||
<X size={12} />
|
||||
<Button
|
||||
size="md"
|
||||
variant="solid"
|
||||
color="secondary"
|
||||
onClick={onClose}
|
||||
prefix={<X size={12} />}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
size="md"
|
||||
variant="solid"
|
||||
color="destructive"
|
||||
disabled={isDeleting}
|
||||
color="danger"
|
||||
onClick={onConfirm}
|
||||
loading={isDeleting}
|
||||
prefix={<Trash2 size={12} />}
|
||||
>
|
||||
<Trash2 size={12} />
|
||||
{isDeleting ? 'Processing...' : title}
|
||||
</Button>
|
||||
</>
|
||||
|
||||
@@ -90,7 +90,6 @@
|
||||
font-weight: var(--uppercase-small-500-font-weight);
|
||||
line-height: 100%;
|
||||
letter-spacing: 0.44px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,6 +112,7 @@
|
||||
&__footer-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-4);
|
||||
}
|
||||
|
||||
&__footer-right {
|
||||
@@ -128,10 +128,6 @@
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
&__tooltip-wrapper {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
&__footer-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
@@ -200,6 +196,7 @@
|
||||
background: var(--l2-background);
|
||||
border: 1px solid var(--l1-border);
|
||||
border-radius: 2px;
|
||||
padding-right: var(--spacing-4);
|
||||
}
|
||||
|
||||
&__link-text-wrap {
|
||||
@@ -220,8 +217,4 @@
|
||||
line-height: var(--line-height-18);
|
||||
letter-spacing: -0.07px;
|
||||
}
|
||||
|
||||
&__copy-btn {
|
||||
border-left: 1px solid var(--l1-border);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -519,7 +519,12 @@ function EditMemberDrawer({
|
||||
localRoles.map((roleId) => {
|
||||
const role = availableRoles.find((r) => r.id === roleId);
|
||||
return (
|
||||
<Badge key={roleId} color="vanilla">
|
||||
<Badge
|
||||
textTransform="none"
|
||||
variant="solid"
|
||||
key={roleId}
|
||||
color="secondary"
|
||||
>
|
||||
{role?.name ?? roleId}
|
||||
</Badge>
|
||||
);
|
||||
@@ -559,15 +564,15 @@ function EditMemberDrawer({
|
||||
<div className="edit-member-drawer__meta-item">
|
||||
<span className="edit-member-drawer__meta-label">Status</span>
|
||||
{member?.status === MemberStatus.Active ? (
|
||||
<Badge color="forest" variant="outline">
|
||||
<Badge color="success" variant="outlined">
|
||||
ACTIVE
|
||||
</Badge>
|
||||
) : member?.status === MemberStatus.Deleted ? (
|
||||
<Badge color="cherry" variant="outline">
|
||||
<Badge color="danger" variant="outlined">
|
||||
DELETED
|
||||
</Badge>
|
||||
) : (
|
||||
<Badge color="amber" variant="outline">
|
||||
<Badge color="warning" variant="outlined">
|
||||
INVITED
|
||||
</Badge>
|
||||
)}
|
||||
@@ -575,12 +580,16 @@ function EditMemberDrawer({
|
||||
|
||||
<div className="edit-member-drawer__meta-item">
|
||||
<span className="edit-member-drawer__meta-label">{joinedOnLabel}</span>
|
||||
<Badge color="vanilla">{formatTimestamp(member?.joinedOn)}</Badge>
|
||||
<Badge textTransform="none" variant="solid" color="secondary">
|
||||
{formatTimestamp(member?.joinedOn)}
|
||||
</Badge>
|
||||
</div>
|
||||
{!isInvited && (
|
||||
<div className="edit-member-drawer__meta-item">
|
||||
<span className="edit-member-drawer__meta-label">Last Modified</span>
|
||||
<Badge color="vanilla">{formatTimestamp(member?.updatedAt)}</Badge>
|
||||
<Badge textTransform="none" variant="solid" color="secondary">
|
||||
{formatTimestamp(member?.updatedAt)}
|
||||
</Badge>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -611,55 +620,59 @@ function EditMemberDrawer({
|
||||
{!isDeleted && (
|
||||
<>
|
||||
<div className="edit-member-drawer__footer-left">
|
||||
<Tooltip title={getDeleteTooltip(isRootUser, isSelf)}>
|
||||
<span className="edit-member-drawer__tooltip-wrapper">
|
||||
<Button
|
||||
onClick={(): void => setShowDeleteConfirm(true)}
|
||||
disabled={isRootUser || isSelf}
|
||||
variant="link"
|
||||
color="destructive"
|
||||
>
|
||||
<Trash2 size={12} />
|
||||
{isInvited ? 'Revoke Invite' : 'Delete Member'}
|
||||
</Button>
|
||||
</span>
|
||||
</Tooltip>
|
||||
<Button
|
||||
disabledTooltip={getDeleteTooltip(isRootUser, isSelf)}
|
||||
size="md"
|
||||
onClick={(): void => setShowDeleteConfirm(true)}
|
||||
disabled={isRootUser || isSelf}
|
||||
variant="link"
|
||||
color="danger"
|
||||
prefix={<Trash2 size={12} />}
|
||||
>
|
||||
{isInvited ? 'Revoke Invite' : 'Delete Member'}
|
||||
</Button>
|
||||
|
||||
<div className="edit-member-drawer__footer-divider" />
|
||||
<Tooltip title={isRootUser ? ROOT_USER_TOOLTIP : undefined}>
|
||||
<span className="edit-member-drawer__tooltip-wrapper">
|
||||
<Button
|
||||
onClick={handleGenerateResetLink}
|
||||
disabled={isGeneratingLink || isRootUser || isLoadingTokenStatus}
|
||||
variant="link"
|
||||
color="warning"
|
||||
>
|
||||
<RefreshCw size={12} />
|
||||
{isGeneratingLink
|
||||
? 'Generating...'
|
||||
: isInvited
|
||||
? getInviteButtonLabel(
|
||||
isLoadingTokenStatus,
|
||||
existingToken,
|
||||
isTokenExpired,
|
||||
tokenNotFound,
|
||||
)
|
||||
: 'Generate Password Reset Link'}
|
||||
</Button>
|
||||
</span>
|
||||
</Tooltip>
|
||||
<Button
|
||||
disabledTooltip={ROOT_USER_TOOLTIP}
|
||||
size="md"
|
||||
onClick={handleGenerateResetLink}
|
||||
disabled={isRootUser}
|
||||
loading={isGeneratingLink || isLoadingTokenStatus}
|
||||
variant="link"
|
||||
color="warning"
|
||||
prefix={<RefreshCw size={12} />}
|
||||
>
|
||||
{isGeneratingLink
|
||||
? 'Generating...'
|
||||
: isInvited
|
||||
? getInviteButtonLabel(
|
||||
isLoadingTokenStatus,
|
||||
existingToken,
|
||||
isTokenExpired,
|
||||
tokenNotFound,
|
||||
)
|
||||
: 'Generate Password Reset Link'}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="edit-member-drawer__footer-right">
|
||||
<Button variant="outlined" color="secondary" onClick={handleClose}>
|
||||
<X size={14} />
|
||||
<Button
|
||||
size="md"
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
onClick={handleClose}
|
||||
prefix={<X size={14} />}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
disabledTooltip={isRootUser ? ROOT_USER_TOOLTIP : 'No changes to save'}
|
||||
size="md"
|
||||
variant="solid"
|
||||
color="primary"
|
||||
disabled={!isDirty || isSaving || isRootUser}
|
||||
disabled={!isDirty || isRootUser}
|
||||
onClick={handleSave}
|
||||
loading={isSaving}
|
||||
>
|
||||
|
||||
@@ -45,11 +45,11 @@ function ResetLinkDialog({
|
||||
<span className="reset-link-dialog__link-text">{resetLink}</span>
|
||||
</div>
|
||||
<Button
|
||||
size="md"
|
||||
variant="link"
|
||||
color="secondary"
|
||||
onClick={onCopy}
|
||||
prefix={hasCopied ? <Check size={12} /> : <Copy size={12} />}
|
||||
className="reset-link-dialog__copy-btn"
|
||||
>
|
||||
{hasCopied ? 'Copied!' : 'Copy'}
|
||||
</Button>
|
||||
|
||||
@@ -251,7 +251,7 @@ describe('EditMemberDrawer', () => {
|
||||
expect(screen.getByText('ACTIVE')).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByRole('button', { name: /save member details/i }),
|
||||
).toBeDisabled();
|
||||
).toHaveAttribute('aria-disabled', 'true');
|
||||
});
|
||||
|
||||
it('enables Save after editing name and calls updateUser on confirm', async () => {
|
||||
@@ -271,7 +271,9 @@ describe('EditMemberDrawer', () => {
|
||||
await user.type(nameInput, 'Alice Updated');
|
||||
|
||||
const saveBtn = screen.getByRole('button', { name: /save member details/i });
|
||||
await waitFor(() => expect(saveBtn).not.toBeDisabled());
|
||||
await waitFor(() =>
|
||||
expect(saveBtn).not.toHaveAttribute('aria-disabled', 'true'),
|
||||
);
|
||||
|
||||
await user.click(saveBtn);
|
||||
|
||||
@@ -295,7 +297,9 @@ describe('EditMemberDrawer', () => {
|
||||
await user.type(nameInput, 'Alice Updated');
|
||||
|
||||
const saveBtn = screen.getByRole('button', { name: /save member details/i });
|
||||
await waitFor(() => expect(saveBtn).not.toBeDisabled());
|
||||
await waitFor(() =>
|
||||
expect(saveBtn).not.toHaveAttribute('aria-disabled', 'true'),
|
||||
);
|
||||
await user.click(saveBtn);
|
||||
|
||||
await waitFor(() => {
|
||||
@@ -323,7 +327,9 @@ describe('EditMemberDrawer', () => {
|
||||
await user.click(await screen.findByTitle('signoz-editor'));
|
||||
|
||||
const saveBtn = screen.getByRole('button', { name: /save member details/i });
|
||||
await waitFor(() => expect(saveBtn).not.toBeDisabled());
|
||||
await waitFor(() =>
|
||||
expect(saveBtn).not.toHaveAttribute('aria-disabled', 'true'),
|
||||
);
|
||||
await user.click(saveBtn);
|
||||
|
||||
await waitFor(() => {
|
||||
@@ -349,7 +355,9 @@ describe('EditMemberDrawer', () => {
|
||||
await user.click(removeBtn);
|
||||
|
||||
const saveBtn = screen.getByRole('button', { name: /save member details/i });
|
||||
await waitFor(() => expect(saveBtn).not.toBeDisabled());
|
||||
await waitFor(() =>
|
||||
expect(saveBtn).not.toHaveAttribute('aria-disabled', 'true'),
|
||||
);
|
||||
await user.click(saveBtn);
|
||||
|
||||
await waitFor(() => {
|
||||
@@ -505,7 +513,9 @@ describe('EditMemberDrawer', () => {
|
||||
await user.type(nameInput, 'Bob Updated');
|
||||
|
||||
const saveBtn = screen.getByRole('button', { name: /save member details/i });
|
||||
await waitFor(() => expect(saveBtn).not.toBeDisabled());
|
||||
await waitFor(() =>
|
||||
expect(saveBtn).not.toHaveAttribute('aria-disabled', 'true'),
|
||||
);
|
||||
await user.click(saveBtn);
|
||||
|
||||
await waitFor(() => {
|
||||
@@ -541,7 +551,9 @@ describe('EditMemberDrawer', () => {
|
||||
await user.type(nameInput, 'Alice Updated');
|
||||
|
||||
const saveBtn = screen.getByRole('button', { name: /save member details/i });
|
||||
await waitFor(() => expect(saveBtn).not.toBeDisabled());
|
||||
await waitFor(() =>
|
||||
expect(saveBtn).not.toHaveAttribute('aria-disabled', 'true'),
|
||||
);
|
||||
await user.click(saveBtn);
|
||||
|
||||
await waitFor(() => {
|
||||
@@ -619,7 +631,7 @@ describe('EditMemberDrawer', () => {
|
||||
renderDrawer({ member: selfMember });
|
||||
expect(
|
||||
screen.getByRole('button', { name: /delete member/i }),
|
||||
).toBeDisabled();
|
||||
).toHaveAttribute('aria-disabled', 'true');
|
||||
});
|
||||
|
||||
it('does not open delete confirm dialog when Delete is clicked while disabled (isSelf)', async () => {
|
||||
@@ -642,7 +654,7 @@ describe('EditMemberDrawer', () => {
|
||||
renderDrawer({ member: selfMember });
|
||||
expect(
|
||||
screen.getByRole('button', { name: /generate password reset link/i }),
|
||||
).not.toBeDisabled();
|
||||
).not.toHaveAttribute('aria-disabled', 'true');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -664,21 +676,21 @@ describe('EditMemberDrawer', () => {
|
||||
renderDrawer();
|
||||
expect(
|
||||
screen.getByRole('button', { name: /delete member/i }),
|
||||
).toBeDisabled();
|
||||
).toHaveAttribute('aria-disabled', 'true');
|
||||
});
|
||||
|
||||
it('disables Reset Link button for root user', () => {
|
||||
renderDrawer();
|
||||
expect(
|
||||
screen.getByRole('button', { name: /generate password reset link/i }),
|
||||
).toBeDisabled();
|
||||
).toHaveAttribute('aria-disabled', 'true');
|
||||
});
|
||||
|
||||
it('disables Save button for root user', () => {
|
||||
renderDrawer();
|
||||
expect(
|
||||
screen.getByRole('button', { name: /save member details/i }),
|
||||
).toBeDisabled();
|
||||
).toHaveAttribute('aria-disabled', 'true');
|
||||
});
|
||||
|
||||
it('does not open delete confirm dialog when Delete is clicked while disabled (root)', async () => {
|
||||
|
||||
@@ -53,7 +53,7 @@ function ErrorModal({
|
||||
onClick={(): void => setVisible(true)}
|
||||
onKeyDown={undefined}
|
||||
>
|
||||
<Badge color="error">
|
||||
<Badge variant="solid" color="danger">
|
||||
<CircleAlert size={14} color={Color.BG_CHERRY_500} /> error
|
||||
</Badge>
|
||||
</span>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useState } from 'react';
|
||||
import { useCopyToClipboard } from 'react-use';
|
||||
import { Button, Col, Popover, Row, Select, Space } from 'antd';
|
||||
import { DropdownMenuSimple, type MenuProps } from '@signozhq/ui/dropdown-menu';
|
||||
import { Dropdown, type DropdownItemType } from '@signozhq/ui/dropdown';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import axios from 'axios';
|
||||
import TextToolTip from 'components/TextToolTip';
|
||||
@@ -137,16 +137,15 @@ function ExplorerCard({
|
||||
);
|
||||
};
|
||||
|
||||
const moreOptionMenu: MenuProps = {
|
||||
items: [
|
||||
{
|
||||
key: 'delete',
|
||||
label: <Typography.Text strong>Delete</Typography.Text>,
|
||||
onClick: onDeleteHandler,
|
||||
icon: <Trash2 size="md" />,
|
||||
},
|
||||
],
|
||||
};
|
||||
const moreOptionItems: DropdownItemType[] = [
|
||||
{
|
||||
type: 'item',
|
||||
value: 'delete',
|
||||
label: <Typography.Text strong>Delete</Typography.Text>,
|
||||
onClick: onDeleteHandler,
|
||||
prefix: <Trash2 size="md" />,
|
||||
},
|
||||
];
|
||||
|
||||
const saveButtonType = isQueryUpdated ? 'default' : 'primary';
|
||||
const saveButtonIcon = isQueryUpdated ? null : <Save size="md" />;
|
||||
@@ -230,9 +229,14 @@ function ExplorerCard({
|
||||
</Popover>
|
||||
<Share2 onClick={onCopyUrlHandler} size="md" />
|
||||
{viewKey && (
|
||||
<DropdownMenuSimple menu={moreOptionMenu}>
|
||||
<Dropdown
|
||||
items={moreOptionItems}
|
||||
nativeButton
|
||||
align="end"
|
||||
side="bottom"
|
||||
>
|
||||
<Button type="text" size="small" icon={<Ellipsis size="md" />} />
|
||||
</DropdownMenuSimple>
|
||||
</Dropdown>
|
||||
)}
|
||||
</Space>
|
||||
</OffSetCol>
|
||||
|
||||
@@ -2,23 +2,15 @@ import { render, screen } from '@testing-library/react';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { PreferenceContextProvider } from 'providers/preferences/context/PreferenceContextProvider';
|
||||
import MockQueryClientProvider from 'providers/test/MockQueryClientProvider';
|
||||
import { mockAppLocation } from 'tests/router-mocks';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
import { viewMockData } from '../__mock__/viewData';
|
||||
import ExplorerCard from '../ExplorerCard';
|
||||
|
||||
const historyReplace = jest.fn();
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
...jest.requireActual('react-router-dom'),
|
||||
useLocation: (): { pathname: string } => ({
|
||||
pathname: `${process.env.FRONTEND_API_ENDPOINT}/${ROUTES.TRACES_EXPLORER}/`,
|
||||
}),
|
||||
useHistory: (): any => ({
|
||||
...jest.requireActual('react-router-dom').useHistory(),
|
||||
replace: historyReplace,
|
||||
}),
|
||||
}));
|
||||
mockAppLocation({
|
||||
pathname: `${process.env.FRONTEND_API_ENDPOINT}/${ROUTES.TRACES_EXPLORER}/`,
|
||||
});
|
||||
|
||||
jest.mock('hooks/useSafeNavigate', () => ({
|
||||
useSafeNavigate: (): any => ({
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import ROUTES from 'constants/routes';
|
||||
import MockQueryClientProvider from 'providers/test/MockQueryClientProvider';
|
||||
import { mockAppLocation } from 'tests/router-mocks';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
import { viewMockData } from '../__mock__/viewData';
|
||||
import MenuItemGenerator from '../MenuItemGenerator';
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
...jest.requireActual('react-router-dom'),
|
||||
useLocation: (): { pathname: string } => ({
|
||||
pathname: `${process.env.FRONTEND_API_ENDPOINT}${ROUTES.APPLICATION}/`,
|
||||
}),
|
||||
}));
|
||||
mockAppLocation({
|
||||
pathname: `${process.env.FRONTEND_API_ENDPOINT}${ROUTES.APPLICATION}/`,
|
||||
});
|
||||
|
||||
jest.mock('antd', () => ({
|
||||
...jest.requireActual('antd'),
|
||||
|
||||
@@ -1,17 +1,9 @@
|
||||
import { QueryClient, QueryClientProvider } from 'react-query';
|
||||
import { fireEvent, render } from '@testing-library/react';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
import SaveViewWithName from '../SaveViewWithName';
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
...jest.requireActual('react-router-dom'),
|
||||
useLocation: (): { pathname: string } => ({
|
||||
pathname: `${process.env.FRONTEND_API_ENDPOINT}${ROUTES.APPLICATION}/`,
|
||||
}),
|
||||
}));
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
|
||||
@@ -26,8 +26,4 @@
|
||||
font-size: var(--periscope-font-size-base);
|
||||
}
|
||||
}
|
||||
|
||||
.export-button {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Download } from '@signozhq/icons';
|
||||
import { Button } from '@signozhq/ui/button';
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@signozhq/ui/popover';
|
||||
import { RadioGroup, RadioGroupItem } from '@signozhq/ui/radio-group';
|
||||
import { TooltipSimple } from '@signozhq/ui/tooltip';
|
||||
import { RadioGroup } from '@signozhq/ui/radio-group';
|
||||
import { Tooltip } from '@signozhq/ui/tooltip';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import {
|
||||
ClientExportData,
|
||||
@@ -51,36 +51,41 @@ export default function ExportMenu({
|
||||
|
||||
return (
|
||||
<Popover open={isPopoverOpen} onOpenChange={setIsPopoverOpen}>
|
||||
<TooltipSimple title="Download">
|
||||
<Tooltip title="Download">
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
color="secondary"
|
||||
size="icon"
|
||||
size="sm"
|
||||
icon
|
||||
aria-label="Download"
|
||||
data-testid={`export-menu-${dataSource}`}
|
||||
disabled={isExporting}
|
||||
testId={`export-menu-${dataSource}`}
|
||||
loading={isExporting}
|
||||
>
|
||||
<Download size={14} />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
</TooltipSimple>
|
||||
</Tooltip>
|
||||
<PopoverContent align="end" className="export-menu-popover">
|
||||
<div className="export-format">
|
||||
<Typography.Text className="title">FORMAT</Typography.Text>
|
||||
<RadioGroup value={exportFormat} onChange={setExportFormat}>
|
||||
<RadioGroupItem value={ExportFormat.Csv}>csv</RadioGroupItem>
|
||||
<RadioGroupItem value={ExportFormat.Jsonl}>jsonl</RadioGroupItem>
|
||||
</RadioGroup>
|
||||
<RadioGroup
|
||||
color="primary"
|
||||
value={exportFormat}
|
||||
onChange={setExportFormat}
|
||||
items={[
|
||||
{ value: ExportFormat.Csv, label: 'csv' },
|
||||
{ value: ExportFormat.Jsonl, label: 'jsonl' },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
size="md"
|
||||
variant="solid"
|
||||
color="primary"
|
||||
className="export-button"
|
||||
width="100%"
|
||||
onClick={handleExport}
|
||||
disabled={isExporting}
|
||||
loading={isExporting}
|
||||
prefix={<Download size={16} />}
|
||||
>
|
||||
|
||||
@@ -80,6 +80,6 @@ describe('ExportMenu', () => {
|
||||
mockIsExporting = true;
|
||||
renderMenu();
|
||||
|
||||
expect(screen.getByTestId(TEST_ID)).toBeDisabled();
|
||||
expect(screen.getByTestId(TEST_ID)).toHaveAttribute('aria-disabled', 'true');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -57,9 +57,8 @@ function SortableField({
|
||||
</div>
|
||||
{!isRequired && (
|
||||
<Button
|
||||
className={cx(styles.removeBtn, 'periscope-btn')}
|
||||
variant="outlined"
|
||||
color="destructive"
|
||||
variant="solid"
|
||||
color="danger"
|
||||
size="sm"
|
||||
onClick={(): void => onRemove(field)}
|
||||
>
|
||||
|
||||
@@ -98,11 +98,15 @@
|
||||
user-select: none;
|
||||
font-size: 13px;
|
||||
|
||||
> [data-slot='button'] {
|
||||
opacity: 0;
|
||||
transition: opacity 0.15s ease-in-out;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--l2-background);
|
||||
|
||||
.removeBtn,
|
||||
.addBtn {
|
||||
> [data-slot='button'] {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
@@ -137,14 +141,6 @@
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.removeBtn,
|
||||
.addBtn {
|
||||
padding: 4px 10px;
|
||||
opacity: 0;
|
||||
transition: opacity 0.15s ease-in-out;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.footer {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
|
||||
@@ -173,6 +173,7 @@ function FieldsSelectorContent({
|
||||
{hasUnsavedChanges && (
|
||||
<div className={styles.footer}>
|
||||
<Button
|
||||
size="md"
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
onClick={handleDiscard}
|
||||
@@ -181,6 +182,7 @@ function FieldsSelectorContent({
|
||||
Discard
|
||||
</Button>
|
||||
<Button
|
||||
size="md"
|
||||
variant="solid"
|
||||
color="primary"
|
||||
onClick={handleSave}
|
||||
|
||||
@@ -138,7 +138,6 @@ function OtherFields({
|
||||
<span className={styles.fieldKey}>{attr.name}</span>
|
||||
{!isAtLimit && (
|
||||
<Button
|
||||
className={cx(styles.addBtn, 'periscope-btn')}
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
size="sm"
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { useAppLocation } from 'lib/router/useAppLocation';
|
||||
import { toast } from '@signozhq/ui/sonner';
|
||||
import { Button, Input } from 'antd';
|
||||
import { ToggleGroupSimple } from '@signozhq/ui/toggle-group';
|
||||
import { ToggleGroup } from '@signozhq/ui/toggle-group';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import { handleContactSupport } from 'container/Integrations/utils';
|
||||
@@ -11,7 +11,7 @@ import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
|
||||
function FeedbackModal({ onClose }: { onClose: () => void }): JSX.Element {
|
||||
const [activeTab, setActiveTab] = useState('feedback');
|
||||
const [feedback, setFeedback] = useState('');
|
||||
const location = useLocation();
|
||||
const location = useAppLocation();
|
||||
const { isCloudUser: isCloudUserVal } = useGetTenantLicense();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
@@ -102,10 +102,12 @@ function FeedbackModal({ onClose }: { onClose: () => void }): JSX.Element {
|
||||
return (
|
||||
<div className="feedback-modal-container">
|
||||
<div className="feedback-modal-header">
|
||||
<ToggleGroupSimple
|
||||
<ToggleGroup
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
size="sm"
|
||||
type="single"
|
||||
value={activeTab}
|
||||
className="feedback-modal-tabs"
|
||||
onChange={setActiveTab}
|
||||
items={items}
|
||||
/>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
.header-right-section-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
gap: var(--spacing-8);
|
||||
padding-right: var(--spacing-8);
|
||||
}
|
||||
|
||||
.header-ai-assistant-btn-container {
|
||||
@@ -121,78 +122,26 @@
|
||||
}
|
||||
|
||||
.feedback-modal-container {
|
||||
.feedback-modal-tabs {
|
||||
width: 100%;
|
||||
.feedback-modal-tab-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
.ant-radio-button-wrapper {
|
||||
flex: 1;
|
||||
margin: 0px !important;
|
||||
|
||||
border: 1px solid var(--l1-border);
|
||||
|
||||
&:before {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ant-radio-button-checked {
|
||||
background-color: var(--l3-background);
|
||||
}
|
||||
.tab-icon {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
.feedback-modal-tab-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
.tab-icon {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
.feedback-tab {
|
||||
background-color: var(--danger-background);
|
||||
}
|
||||
|
||||
.bug-tab {
|
||||
background-color: var(--warning-background);
|
||||
}
|
||||
|
||||
.feature-tab {
|
||||
background-color: var(--primary-background);
|
||||
}
|
||||
.feedback-tab {
|
||||
background-color: var(--danger-background);
|
||||
}
|
||||
|
||||
.ant-tabs-nav-list {
|
||||
.ant-tabs-tab {
|
||||
padding: 6px 16px;
|
||||
.bug-tab {
|
||||
background-color: var(--warning-background);
|
||||
}
|
||||
|
||||
border-radius: 2px;
|
||||
background: var(--l2-background);
|
||||
box-shadow: 0 0 8px 0 rgba(0, 0, 0, 0.1);
|
||||
border: 1px solid var(--l1-border);
|
||||
|
||||
margin: 0 !important;
|
||||
|
||||
.ant-tabs-tab-btn {
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 20px; /* 166.667% */
|
||||
letter-spacing: -0.06px;
|
||||
}
|
||||
|
||||
&-active {
|
||||
background: var(--l3-background);
|
||||
color: var(--l1-foreground);
|
||||
|
||||
border-bottom: none !important;
|
||||
|
||||
.ant-tabs-tab-btn {
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
}
|
||||
}
|
||||
.feature-tab {
|
||||
background-color: var(--primary-background);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { useAppLocation } from 'lib/router/useAppLocation';
|
||||
import { Dot } from '@signozhq/icons';
|
||||
import { Button } from '@signozhq/ui/button';
|
||||
import { TooltipSimple } from '@signozhq/ui/tooltip';
|
||||
import { Tooltip } from '@signozhq/ui/tooltip';
|
||||
import Noz from 'components/Noz/Noz';
|
||||
import { NOZ_TOOLTIP_TITLE } from 'components/Noz/Noz.constants';
|
||||
import { Popover } from 'antd';
|
||||
@@ -39,7 +39,7 @@ function HeaderRightSection({
|
||||
enableFeedback,
|
||||
shareModalExtraOption,
|
||||
}: HeaderRightSectionProps): JSX.Element | null {
|
||||
const location = useLocation();
|
||||
const location = useAppLocation();
|
||||
|
||||
const [openFeedbackModal, setOpenFeedbackModal] = useState(false);
|
||||
const [openShareURLModal, setOpenShareURLModal] = useState(false);
|
||||
@@ -113,24 +113,26 @@ function HeaderRightSection({
|
||||
</span>
|
||||
) : null}
|
||||
|
||||
<TooltipSimple title={NOZ_TOOLTIP_TITLE}>
|
||||
<Button
|
||||
variant="solid"
|
||||
color="secondary"
|
||||
className="noz-wave"
|
||||
onClick={handleOpenAIAssistant}
|
||||
aria-label={
|
||||
showHeaderPendingBadge
|
||||
? pendingUserInputCount === 1
|
||||
? 'Open Noz, 1 action needs your response'
|
||||
: `Open Noz, ${pendingUserInputCount} actions need your response`
|
||||
: 'Open Noz'
|
||||
}
|
||||
prefix={<Noz size={20} />}
|
||||
>
|
||||
<Typography.Text>Noz</Typography.Text>
|
||||
</Button>
|
||||
</TooltipSimple>
|
||||
<span className="noz-wave">
|
||||
<Tooltip title={NOZ_TOOLTIP_TITLE}>
|
||||
<Button
|
||||
size="md"
|
||||
variant="ghost"
|
||||
color="secondary"
|
||||
onClick={handleOpenAIAssistant}
|
||||
aria-label={
|
||||
showHeaderPendingBadge
|
||||
? pendingUserInputCount === 1
|
||||
? 'Open Noz, 1 action needs your response'
|
||||
: `Open Noz, ${pendingUserInputCount} actions need your response`
|
||||
: 'Open Noz'
|
||||
}
|
||||
prefix={<Noz size={20} />}
|
||||
>
|
||||
<Typography.Text>Noz</Typography.Text>
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -147,13 +149,15 @@ function HeaderRightSection({
|
||||
onOpenChange={handleOpenFeedbackModalChange}
|
||||
>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="share-feedback-btn"
|
||||
color="primary"
|
||||
variant="link"
|
||||
size="md"
|
||||
icon
|
||||
aria-label="Feedback"
|
||||
prefix={<SquarePen size={14} />}
|
||||
onClick={handleOpenFeedbackModal}
|
||||
/>
|
||||
>
|
||||
<SquarePen size={14} />
|
||||
</Button>
|
||||
</Popover>
|
||||
)}
|
||||
|
||||
@@ -170,16 +174,19 @@ function HeaderRightSection({
|
||||
onOpenChange={handleOpenAnnouncementsModalChange}
|
||||
>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
color="primary"
|
||||
variant="link"
|
||||
size="md"
|
||||
icon
|
||||
aria-label="Announcements"
|
||||
prefix={<Inbox size={14} />}
|
||||
onClick={(): void => {
|
||||
logEvent('Announcements: Clicked', {
|
||||
page: location.pathname,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
>
|
||||
<Inbox size={14} />
|
||||
</Button>
|
||||
</Popover>
|
||||
)}
|
||||
|
||||
@@ -196,12 +203,15 @@ function HeaderRightSection({
|
||||
onOpenChange={handleOpenShareURLModalChange}
|
||||
>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
color="primary"
|
||||
variant="link"
|
||||
size="md"
|
||||
icon
|
||||
aria-label="Share"
|
||||
prefix={<Globe size={14} />}
|
||||
onClick={handleOpenShareURLModal}
|
||||
/>
|
||||
>
|
||||
<Globe size={14} />
|
||||
</Button>
|
||||
</Popover>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { useMemo, useState } from 'react';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { useSelector } from 'react-redux';
|
||||
import { matchPath, useLocation } from 'react-router-dom';
|
||||
import { useAppLocation } from 'lib/router/useAppLocation';
|
||||
import { matchRoute } from 'lib/router/matchRoute';
|
||||
import { useCopyToClipboard } from 'react-use';
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Button } from 'antd';
|
||||
@@ -41,7 +42,7 @@ interface ShareURLModalProps {
|
||||
|
||||
function ShareURLModal({ extraOption }: ShareURLModalProps): JSX.Element {
|
||||
const urlQuery = useUrlQuery();
|
||||
const location = useLocation();
|
||||
const location = useAppLocation();
|
||||
const { selectedTime } = useSelector<AppState, GlobalReducer>(
|
||||
(state) => state.globalTime,
|
||||
);
|
||||
@@ -75,7 +76,7 @@ function ShareURLModal({ extraOption }: ShareURLModalProps): JSX.Element {
|
||||
const isRouteToBeSharedWithTime = useMemo(
|
||||
() =>
|
||||
routesToBeSharedWithTime.some((route) =>
|
||||
matchPath(location.pathname, { path: route, exact: true }),
|
||||
matchRoute(location.pathname, route, { exact: true }),
|
||||
),
|
||||
[location.pathname],
|
||||
);
|
||||
@@ -149,6 +150,9 @@ function ShareURLModal({ extraOption }: ShareURLModalProps): JSX.Element {
|
||||
<Info size={14} color={Color.BG_AMBER_600} />
|
||||
)}
|
||||
<Switch
|
||||
color="primary"
|
||||
textPlacement="right"
|
||||
disabledTooltip="Please select / enter valid relative time to toggle."
|
||||
value={enableAbsoluteTime}
|
||||
disabled={!isValidateRelativeTime}
|
||||
onChange={(): void => {
|
||||
@@ -173,6 +177,8 @@ function ShareURLModal({ extraOption }: ShareURLModalProps): JSX.Element {
|
||||
</Typography.Text>
|
||||
<div className="absolute-relative-time-toggler">
|
||||
<Switch
|
||||
color="primary"
|
||||
textPlacement="right"
|
||||
value={enableExtraOption}
|
||||
onChange={(): void => setEnableExtraOption((prev) => !prev)}
|
||||
/>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Mock dependencies before imports
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { useAppLocation } from 'lib/router/useAppLocation';
|
||||
import { toast } from '@signozhq/ui/sonner';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
@@ -9,9 +9,8 @@ import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
|
||||
|
||||
import FeedbackModal from '../FeedbackModal';
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
...jest.requireActual('react-router-dom'),
|
||||
useLocation: jest.fn(),
|
||||
jest.mock('lib/router/useAppLocation', () => ({
|
||||
useAppLocation: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('@signozhq/ui/sonner', () => ({
|
||||
@@ -30,7 +29,7 @@ jest.mock('container/Integrations/utils', () => ({
|
||||
handleContactSupport: jest.fn(),
|
||||
}));
|
||||
|
||||
const mockUseLocation = useLocation as jest.Mock;
|
||||
const mockUseAppLocation = useAppLocation as jest.Mock;
|
||||
const mockUseGetTenantLicense = useGetTenantLicense as jest.Mock;
|
||||
const mockHandleContactSupport = handleContactSupport as jest.Mock;
|
||||
const mockToast = toast as jest.Mocked<typeof toast>;
|
||||
@@ -45,7 +44,7 @@ describe('FeedbackModal', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
logEventMock.mockReturnValue(Promise.resolve() as never);
|
||||
mockUseLocation.mockReturnValue(mockLocation);
|
||||
mockUseAppLocation.mockReturnValue(mockLocation);
|
||||
mockUseGetTenantLicense.mockReturnValue({
|
||||
isCloudUser: false,
|
||||
});
|
||||
@@ -69,23 +68,23 @@ describe('FeedbackModal', () => {
|
||||
const user = userEvent.setup();
|
||||
render(<FeedbackModal onClose={mockOnClose} />);
|
||||
|
||||
// Initially, feedback radio should be active
|
||||
const feedbackRadio = screen.getByRole('radio', { name: 'Feedback' });
|
||||
expect(feedbackRadio).toBeChecked();
|
||||
// Initially, feedback button should be active
|
||||
const feedbackButton = screen.getByRole('button', { name: 'Feedback' });
|
||||
expect(feedbackButton).toHaveAttribute('aria-pressed', 'true');
|
||||
|
||||
const bugTab = screen.getByText('Report a bug');
|
||||
await user.click(bugTab);
|
||||
|
||||
// Bug radio should now be active
|
||||
const bugRadio = screen.getByRole('radio', { name: 'Report a bug' });
|
||||
expect(bugRadio).toBeChecked();
|
||||
// Bug button should now be active
|
||||
const bugButton = screen.getByRole('button', { name: 'Report a bug' });
|
||||
expect(bugButton).toHaveAttribute('aria-pressed', 'true');
|
||||
|
||||
const featureTab = screen.getByText('Feature request');
|
||||
await user.click(featureTab);
|
||||
|
||||
// Feature radio should now be active
|
||||
const featureRadio = screen.getByRole('radio', { name: 'Feature request' });
|
||||
expect(featureRadio).toBeChecked();
|
||||
// Feature button should now be active
|
||||
const featureButton = screen.getByRole('button', { name: 'Feature request' });
|
||||
expect(featureButton).toHaveAttribute('aria-pressed', 'true');
|
||||
});
|
||||
|
||||
it('should update feedback text when typing in textarea', async () => {
|
||||
@@ -133,9 +132,9 @@ describe('FeedbackModal', () => {
|
||||
const bugTab = screen.getByText('Report a bug');
|
||||
await user.click(bugTab);
|
||||
|
||||
// Verify bug report radio is now active
|
||||
const bugRadio = screen.getByRole('radio', { name: 'Report a bug' });
|
||||
expect(bugRadio).toBeChecked();
|
||||
// Verify bug report button is now active
|
||||
const bugButton = screen.getByRole('button', { name: 'Report a bug' });
|
||||
expect(bugButton).toHaveAttribute('aria-pressed', 'true');
|
||||
|
||||
const textarea = screen.getByPlaceholderText('Write your feedback here...');
|
||||
const submitButton = screen.getByRole('button', { name: /submit/i });
|
||||
@@ -166,9 +165,9 @@ describe('FeedbackModal', () => {
|
||||
const featureTab = screen.getByText('Feature request');
|
||||
await user.click(featureTab);
|
||||
|
||||
// Verify feature request radio is now active
|
||||
const featureRadio = screen.getByRole('radio', { name: 'Feature request' });
|
||||
expect(featureRadio).toBeChecked();
|
||||
// Verify feature request button is now active
|
||||
const featureButton = screen.getByRole('button', { name: 'Feature request' });
|
||||
expect(featureButton).toHaveAttribute('aria-pressed', 'true');
|
||||
|
||||
const textarea = screen.getByPlaceholderText('Write your feedback here...');
|
||||
const submitButton = screen.getByRole('button', { name: /submit/i });
|
||||
@@ -262,8 +261,8 @@ describe('FeedbackModal', () => {
|
||||
);
|
||||
expect(newTextArea).toHaveValue(''); // Should be empty
|
||||
|
||||
// Verify active radio is reset to default (Feedback radio)
|
||||
const feedbackRadio = screen.getByRole('radio', { name: 'Feedback' });
|
||||
expect(feedbackRadio).toBeChecked();
|
||||
// Verify active button is reset to default (Feedback button)
|
||||
const feedbackButton = screen.getByRole('button', { name: 'Feedback' });
|
||||
expect(feedbackButton).toHaveAttribute('aria-pressed', 'true');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Mock dependencies before imports
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { useAppLocation } from 'lib/router/useAppLocation';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { logEventMock } from '__tests__/logEventMock';
|
||||
@@ -7,9 +7,8 @@ import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
|
||||
|
||||
import HeaderRightSection from '../HeaderRightSection';
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
...jest.requireActual('react-router-dom'),
|
||||
useLocation: jest.fn(),
|
||||
jest.mock('lib/router/useAppLocation', () => ({
|
||||
useAppLocation: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('../FeedbackModal', () => ({
|
||||
@@ -45,7 +44,7 @@ jest.mock('hooks/useIsAIAssistantEnabled', () => ({
|
||||
useIsAIAssistantEnabled: (): boolean => false,
|
||||
}));
|
||||
|
||||
const mockUseLocation = useLocation as jest.Mock;
|
||||
const mockUseAppLocation = useAppLocation as jest.Mock;
|
||||
const mockUseGetTenantLicense = useGetTenantLicense as jest.Mock;
|
||||
|
||||
const defaultProps = {
|
||||
@@ -61,7 +60,7 @@ const mockLocation = {
|
||||
describe('HeaderRightSection', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mockUseLocation.mockReturnValue(mockLocation);
|
||||
mockUseAppLocation.mockReturnValue(mockLocation);
|
||||
// Default to licensed user (Enterprise or Cloud)
|
||||
mockUseGetTenantLicense.mockReturnValue({
|
||||
isCloudUser: true,
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
// Mock dependencies before imports
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { useSelector } from 'react-redux';
|
||||
import { matchPath, useLocation } from 'react-router-dom';
|
||||
import { useAppLocation } from 'lib/router/useAppLocation';
|
||||
import { matchRoute } from 'lib/router/matchRoute';
|
||||
import { useCopyToClipboard } from 'react-use';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
@@ -12,10 +13,12 @@ import GetMinMax from 'lib/getMinMax';
|
||||
|
||||
import ShareURLModal from '../ShareURLModal';
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
...jest.requireActual('react-router-dom'),
|
||||
useLocation: jest.fn(),
|
||||
matchPath: jest.fn(),
|
||||
jest.mock('lib/router/useAppLocation', () => ({
|
||||
useAppLocation: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('lib/router/matchRoute', () => ({
|
||||
matchRoute: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('hooks/useUrlQuery', () => ({
|
||||
@@ -48,12 +51,12 @@ Object.defineProperty(window, 'location', {
|
||||
writable: true,
|
||||
});
|
||||
|
||||
const mockUseLocation = useLocation as jest.Mock;
|
||||
const mockUseAppLocation = useAppLocation as jest.Mock;
|
||||
const mockUseUrlQuery = useUrlQuery as jest.Mock;
|
||||
const mockUseSelector = useSelector as jest.Mock;
|
||||
const mockGetMinMax = GetMinMax as jest.Mock;
|
||||
const mockUseCopyToClipboard = useCopyToClipboard as jest.Mock;
|
||||
const mockMatchPath = matchPath as jest.Mock;
|
||||
const mockMatchRoute = matchRoute as jest.Mock;
|
||||
|
||||
const mockUrlQuery = {
|
||||
get: jest.fn(),
|
||||
@@ -71,7 +74,7 @@ describe('ShareURLModal', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
mockUseLocation.mockReturnValue({
|
||||
mockUseAppLocation.mockReturnValue({
|
||||
pathname: TEST_PATH,
|
||||
});
|
||||
|
||||
@@ -88,7 +91,7 @@ describe('ShareURLModal', () => {
|
||||
|
||||
mockUseCopyToClipboard.mockReturnValue([null, mockHandleCopyToClipboard]);
|
||||
|
||||
mockMatchPath.mockReturnValue(false);
|
||||
mockMatchRoute.mockReturnValue(null);
|
||||
|
||||
// Reset URL query mocks - all return null by default
|
||||
mockUrlQuery.get.mockReturnValue(null);
|
||||
@@ -126,7 +129,7 @@ describe('ShareURLModal', () => {
|
||||
});
|
||||
|
||||
it('should show absolute time toggle when on time-enabled route', () => {
|
||||
mockMatchPath.mockReturnValue(true); // Simulate being on a route that supports time
|
||||
mockMatchRoute.mockReturnValue({}); // Simulate being on a route that supports time
|
||||
|
||||
render(<ShareURLModal />);
|
||||
|
||||
@@ -146,7 +149,7 @@ describe('ShareURLModal', () => {
|
||||
|
||||
it('should toggle absolute time switch', async () => {
|
||||
const user = userEvent.setup();
|
||||
mockMatchPath.mockReturnValue(true);
|
||||
mockMatchRoute.mockReturnValue({});
|
||||
mockUseSelector.mockReturnValue({
|
||||
selectedTime: '5min', // Non-custom time should enable absolute time by default
|
||||
});
|
||||
@@ -169,19 +172,19 @@ describe('ShareURLModal', () => {
|
||||
// Invalid - missing start and end time for custom
|
||||
mockUrlQuery.get.mockReturnValue(null);
|
||||
|
||||
mockMatchPath.mockReturnValue(true);
|
||||
mockMatchRoute.mockReturnValue({});
|
||||
|
||||
render(<ShareURLModal />);
|
||||
|
||||
expect(
|
||||
screen.getByText('Please select / enter valid relative time to toggle.'),
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByRole('switch')).toBeDisabled();
|
||||
expect(screen.getByRole('switch')).toHaveAttribute('aria-disabled', 'true');
|
||||
});
|
||||
|
||||
it('should process URL with absolute time for non-custom time', async () => {
|
||||
const user = userEvent.setup();
|
||||
mockMatchPath.mockReturnValue(true);
|
||||
mockMatchRoute.mockReturnValue({});
|
||||
mockUseSelector.mockReturnValue({
|
||||
selectedTime: '5min',
|
||||
});
|
||||
@@ -200,7 +203,7 @@ describe('ShareURLModal', () => {
|
||||
|
||||
it('should process URL with custom time parameters', async () => {
|
||||
const user = userEvent.setup();
|
||||
mockMatchPath.mockReturnValue(true);
|
||||
mockMatchRoute.mockReturnValue({});
|
||||
mockUseSelector.mockReturnValue({
|
||||
selectedTime: 'custom',
|
||||
});
|
||||
@@ -228,7 +231,7 @@ describe('ShareURLModal', () => {
|
||||
|
||||
it('should process URL with relative time when absolute time is disabled', async () => {
|
||||
const user = userEvent.setup();
|
||||
mockMatchPath.mockReturnValue(true);
|
||||
mockMatchRoute.mockReturnValue({});
|
||||
mockUseSelector.mockReturnValue({
|
||||
selectedTime: '5min',
|
||||
});
|
||||
@@ -249,12 +252,12 @@ describe('ShareURLModal', () => {
|
||||
|
||||
it('should handle routes that should be shared with time', async () => {
|
||||
const user = userEvent.setup();
|
||||
mockUseLocation.mockReturnValue({
|
||||
mockUseAppLocation.mockReturnValue({
|
||||
pathname: ROUTES.LOGS_EXPLORER,
|
||||
});
|
||||
|
||||
mockMatchPath.mockImplementation(
|
||||
(pathname: string, options: any) => options.path === ROUTES.LOGS_EXPLORER,
|
||||
mockMatchRoute.mockImplementation((pathname: string, route: string) =>
|
||||
route === ROUTES.LOGS_EXPLORER ? {} : null,
|
||||
);
|
||||
|
||||
render(<ShareURLModal />);
|
||||
|
||||
@@ -1,44 +1,32 @@
|
||||
import { Badge } from '@signozhq/ui/badge';
|
||||
import { Badge, type BadgeColorType } from '@signozhq/ui/badge';
|
||||
|
||||
type BadgeColor =
|
||||
| 'vanilla'
|
||||
| 'robin'
|
||||
| 'forest'
|
||||
| 'amber'
|
||||
| 'sienna'
|
||||
| 'cherry'
|
||||
| 'sakura'
|
||||
| 'aqua';
|
||||
function getStatusCodeColor(statusCode: number): BadgeColorType {
|
||||
if (statusCode >= 200 && statusCode < 300) {
|
||||
return 'success';
|
||||
}
|
||||
if (statusCode >= 300 && statusCode < 400) {
|
||||
return 'primary';
|
||||
}
|
||||
if (statusCode >= 400 && statusCode < 500) {
|
||||
return 'warning';
|
||||
}
|
||||
if (statusCode >= 500) {
|
||||
return 'danger';
|
||||
}
|
||||
if (statusCode >= 100 && statusCode < 200) {
|
||||
return 'secondary';
|
||||
}
|
||||
return 'primary';
|
||||
}
|
||||
|
||||
interface HttpStatusBadgeProps {
|
||||
statusCode: string | number;
|
||||
testId?: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
function getStatusCodeColor(statusCode: number): BadgeColor {
|
||||
if (statusCode >= 200 && statusCode < 300) {
|
||||
return 'forest'; // Success - green
|
||||
}
|
||||
if (statusCode >= 300 && statusCode < 400) {
|
||||
return 'robin'; // Redirect - blue
|
||||
}
|
||||
if (statusCode >= 400 && statusCode < 500) {
|
||||
return 'amber'; // Client error - amber
|
||||
}
|
||||
if (statusCode >= 500) {
|
||||
return 'cherry'; // Server error - red
|
||||
}
|
||||
if (statusCode >= 100 && statusCode < 200) {
|
||||
return 'vanilla'; // Informational - neutral
|
||||
}
|
||||
return 'robin'; // Default fallback
|
||||
}
|
||||
|
||||
function HttpStatusBadge({
|
||||
statusCode,
|
||||
testId,
|
||||
className,
|
||||
}: HttpStatusBadgeProps): JSX.Element | null {
|
||||
const numericStatusCode = Number(statusCode);
|
||||
|
||||
@@ -49,12 +37,7 @@ function HttpStatusBadge({
|
||||
const color = getStatusCodeColor(numericStatusCode);
|
||||
|
||||
return (
|
||||
<Badge
|
||||
color={color}
|
||||
variant="outline"
|
||||
data-testid={testId}
|
||||
className={className}
|
||||
>
|
||||
<Badge color={color} variant="outlined" testId={testId}>
|
||||
{statusCode}
|
||||
</Badge>
|
||||
);
|
||||
|
||||
@@ -119,11 +119,13 @@ function InviteMembers({
|
||||
<div className={styles.cellAction}>
|
||||
{canRemoveRow && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
color="destructive"
|
||||
size="md"
|
||||
variant="solid"
|
||||
color="danger"
|
||||
onClick={(): void => removeRow(row.id)}
|
||||
aria-label="Remove row"
|
||||
data-testid={`invite-remove-${row.id}`}
|
||||
testId={`invite-remove-${row.id}`}
|
||||
icon
|
||||
>
|
||||
<Trash2 size={12} />
|
||||
</Button>
|
||||
@@ -136,11 +138,12 @@ function InviteMembers({
|
||||
{showAddButton && (
|
||||
<div className={styles.addRow}>
|
||||
<Button
|
||||
size="md"
|
||||
variant="dashed"
|
||||
color="secondary"
|
||||
prefix={<Plus size={12} />}
|
||||
onClick={addRow}
|
||||
data-testid="invite-add-row"
|
||||
testId="invite-add-row"
|
||||
>
|
||||
Add another
|
||||
</Button>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { useAppLocation } from 'lib/router/useAppLocation';
|
||||
import { Button, Tooltip } from 'antd';
|
||||
import AddCreditCardModal from 'components/AddCreditCardModal/AddCreditCardModal';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
@@ -43,7 +43,7 @@ function LaunchChatSupport({
|
||||
const [isAddCreditCardModalOpen, setIsAddCreditCardModalOpen] =
|
||||
useState(false);
|
||||
|
||||
const { pathname } = useLocation();
|
||||
const { pathname } = useAppLocation();
|
||||
|
||||
const isChatSupportEnabled = useMemo(() => {
|
||||
if (!isFetchingFeatureFlags && (featureFlags || featureFlagsFetchError)) {
|
||||
|
||||
@@ -161,37 +161,14 @@
|
||||
}
|
||||
}
|
||||
|
||||
.views-tabs {
|
||||
color: var(--l2-foreground);
|
||||
|
||||
.view-title {
|
||||
display: flex;
|
||||
gap: var(--margin-2);
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: var(--font-size-xs);
|
||||
font-style: normal;
|
||||
font-weight: var(--font-weight-normal);
|
||||
}
|
||||
|
||||
> button {
|
||||
border: 1px solid var(--l1-border);
|
||||
width: 114px;
|
||||
|
||||
&::before {
|
||||
background: var(--l1-border);
|
||||
}
|
||||
|
||||
&[data-state='on'] {
|
||||
background: var(--l3-background);
|
||||
color: var(--l1-foreground);
|
||||
border: 1px solid var(--l1-border);
|
||||
|
||||
&::before {
|
||||
background: var(--l1-border);
|
||||
}
|
||||
}
|
||||
}
|
||||
.view-title {
|
||||
display: flex;
|
||||
gap: var(--margin-2);
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: var(--font-size-xs);
|
||||
font-style: normal;
|
||||
font-weight: var(--font-weight-normal);
|
||||
}
|
||||
|
||||
.search-input {
|
||||
@@ -242,42 +219,4 @@
|
||||
align-items: center;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.log-arrow-btn {
|
||||
padding: 0;
|
||||
min-width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 4px;
|
||||
background: var(--l2-background);
|
||||
color: var(--l2-foreground);
|
||||
border: 1px solid var(--l1-border);
|
||||
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.08);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: background-color 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.log-arrow-btn-up,
|
||||
.log-arrow-btn-down {
|
||||
background: var(--l2-background);
|
||||
}
|
||||
|
||||
.log-arrow-btn:active,
|
||||
.log-arrow-btn:focus {
|
||||
background: var(--l3-background);
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
|
||||
.log-arrow-btn[disabled] {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
background: var(--l1-background);
|
||||
color: var(--l3-foreground);
|
||||
|
||||
.log-arrow-btn:hover:not([disabled]) {
|
||||
background: var(--l3-background);
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,14 +6,6 @@
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.tooltipContent {
|
||||
--tooltip-z-index: 2100;
|
||||
}
|
||||
|
||||
.dropdownContent {
|
||||
--dropdown-menu-content-z-index: 2100;
|
||||
}
|
||||
|
||||
.leftSection {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Button } from '@signozhq/ui/button';
|
||||
import { Divider } from '@signozhq/ui/divider';
|
||||
import { DropdownMenuSimple as Dropdown } from '@signozhq/ui/dropdown-menu';
|
||||
import { Dropdown, type DropdownItemType } from '@signozhq/ui/dropdown';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import { TooltipSimple } from '@signozhq/ui/tooltip';
|
||||
import { Tooltip } from '@signozhq/ui/tooltip';
|
||||
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
|
||||
import { aggregateAttributesResourcesToString } from 'container/LogDetailedView/utils';
|
||||
import { toast } from '@signozhq/ui/sonner';
|
||||
@@ -23,8 +23,6 @@ import { useCopyToClipboard } from 'react-use';
|
||||
|
||||
import styles from './LogDetailsHeader.module.scss';
|
||||
|
||||
const TOOLTIP_CONTENT_PROPS = { className: styles.tooltipContent };
|
||||
|
||||
interface LogDetailsHeaderProps {
|
||||
log: ILog;
|
||||
onNavigatePrev: () => void;
|
||||
@@ -53,17 +51,19 @@ function LogDetailsHeader({
|
||||
toast.success('Copied to clipboard', { position: 'bottom-right' });
|
||||
};
|
||||
|
||||
const menuItems = [
|
||||
const menuItems: DropdownItemType[] = [
|
||||
{
|
||||
key: 'copy-log',
|
||||
type: 'item',
|
||||
value: 'copy-log',
|
||||
label: 'Copy log',
|
||||
icon: <Copy size={14} />,
|
||||
prefix: <Copy size={14} />,
|
||||
onClick: handleCopyLog,
|
||||
},
|
||||
{
|
||||
key: 'copy-link',
|
||||
type: 'item',
|
||||
value: 'copy-link',
|
||||
label: 'Copy link to log',
|
||||
icon: <Link size={14} />,
|
||||
prefix: <Link size={14} />,
|
||||
onClick: (): void => onLogCopy(),
|
||||
},
|
||||
];
|
||||
@@ -91,6 +91,7 @@ function LogDetailsHeader({
|
||||
<div className={styles.actions}>
|
||||
{showOpenInExplorer && (
|
||||
<Button
|
||||
size="md"
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
prefix={<Compass size={16} />}
|
||||
@@ -100,51 +101,57 @@ function LogDetailsHeader({
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<Dropdown
|
||||
menu={{ items: menuItems }}
|
||||
align="end"
|
||||
className={styles.dropdownContent}
|
||||
onClick={(e: MouseEvent): void => e.stopPropagation()}
|
||||
>
|
||||
<Dropdown items={menuItems} nativeButton align="end" side="bottom">
|
||||
<Button
|
||||
size="md"
|
||||
variant="link"
|
||||
color="secondary"
|
||||
prefix={<Ellipsis size={16} />}
|
||||
data-testid="log-details-header-menu"
|
||||
/>
|
||||
icon
|
||||
aria-label="Log actions"
|
||||
testId="log-details-header-menu"
|
||||
onClick={(e: MouseEvent): void => e.stopPropagation()}
|
||||
>
|
||||
<Ellipsis size={16} />
|
||||
</Button>
|
||||
</Dropdown>
|
||||
|
||||
<div className={styles.arrows}>
|
||||
<TooltipSimple
|
||||
title="Move to previous log"
|
||||
<Tooltip
|
||||
title={isPrevDisabled ? undefined : 'Move to previous log'}
|
||||
side="top"
|
||||
open={isPrevDisabled ? false : undefined}
|
||||
tooltipContentProps={TOOLTIP_CONTENT_PROPS}
|
||||
>
|
||||
<Button
|
||||
disabledTooltip="No previous log"
|
||||
size="md"
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
prefix={<ChevronUp size={14} />}
|
||||
icon
|
||||
aria-label="Move to previous log"
|
||||
disabled={isPrevDisabled}
|
||||
onClick={onNavigatePrev}
|
||||
data-testid="log-details-header-prev"
|
||||
/>
|
||||
</TooltipSimple>
|
||||
<TooltipSimple
|
||||
title="Move to next log"
|
||||
testId="log-details-header-prev"
|
||||
>
|
||||
<ChevronUp size={14} />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
title={isNextDisabled ? undefined : 'Move to next log'}
|
||||
side="top"
|
||||
open={isNextDisabled ? false : undefined}
|
||||
tooltipContentProps={TOOLTIP_CONTENT_PROPS}
|
||||
>
|
||||
<Button
|
||||
disabledTooltip="No next log"
|
||||
size="md"
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
prefix={<ChevronDown size={14} />}
|
||||
icon
|
||||
aria-label="Move to next log"
|
||||
disabled={isNextDisabled}
|
||||
onClick={onNavigateNext}
|
||||
data-testid="log-details-header-next"
|
||||
/>
|
||||
</TooltipSimple>
|
||||
testId="log-details-header-next"
|
||||
>
|
||||
<ChevronDown size={14} />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -12,13 +12,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.valueBadge {
|
||||
--badge-font-size: 13px;
|
||||
box-sizing: border-box;
|
||||
max-width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
// Truncating text inside a badge
|
||||
.badgeText {
|
||||
min-width: 0;
|
||||
@@ -29,11 +22,14 @@
|
||||
|
||||
.serviceDot {
|
||||
width: 6px;
|
||||
min-width: 6px;
|
||||
height: 6px;
|
||||
min-height: 6px;
|
||||
border-radius: 50%;
|
||||
background: var(--accent-forest);
|
||||
flex-shrink: 0;
|
||||
margin-right: 4px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.traceLink {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Link } from 'react-router-dom';
|
||||
import { AppLink } from 'lib/router/AppLink';
|
||||
|
||||
import styles from './LogHighlights.module.scss';
|
||||
|
||||
@@ -8,7 +8,7 @@ interface TraceIdFieldProps {
|
||||
|
||||
function TraceIdField({ traceId }: TraceIdFieldProps): JSX.Element {
|
||||
return (
|
||||
<Link
|
||||
<AppLink
|
||||
to={{ pathname: `/trace/${traceId}` }}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
@@ -16,7 +16,7 @@ function TraceIdField({ traceId }: TraceIdFieldProps): JSX.Element {
|
||||
title={traceId}
|
||||
>
|
||||
{traceId}
|
||||
</Link>
|
||||
</AppLink>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ReactNode } from 'react';
|
||||
import { Badge, BadgeColor } from '@signozhq/ui/badge';
|
||||
import { Badge, type BadgeColorType } from '@signozhq/ui/badge';
|
||||
import { LogType } from 'components/Logs/LogStateIndicator/LogStateIndicator';
|
||||
import { getLogIndicatorType } from 'components/Logs/LogStateIndicator/utils';
|
||||
import { ILog } from 'types/api/logs/log';
|
||||
@@ -8,13 +8,13 @@ import styles from './LogHighlights.module.scss';
|
||||
import TraceIdField from './TraceIdField';
|
||||
|
||||
// Severity badge color mirrors the LogStateIndicator bar
|
||||
const SEVERITY_COLOR: Record<string, BadgeColor> = {
|
||||
[LogType.TRACE]: 'forest',
|
||||
[LogType.DEBUG]: 'aqua',
|
||||
[LogType.INFO]: 'robin',
|
||||
[LogType.WARN]: 'amber',
|
||||
[LogType.ERROR]: 'cherry',
|
||||
[LogType.FATAL]: 'sakura',
|
||||
const SEVERITY_COLOR: Record<string, BadgeColorType> = {
|
||||
[LogType.TRACE]: 'success',
|
||||
[LogType.DEBUG]: 'info',
|
||||
[LogType.INFO]: 'primary',
|
||||
[LogType.WARN]: 'warning',
|
||||
[LogType.ERROR]: 'danger',
|
||||
[LogType.FATAL]: 'highlight-danger',
|
||||
};
|
||||
|
||||
export interface LogHighlightConfig {
|
||||
@@ -32,9 +32,13 @@ const getAttr = (log: ILog, key: string): string =>
|
||||
|
||||
const valueBadge = (
|
||||
value: string,
|
||||
options?: { prefix?: ReactNode; color?: BadgeColor },
|
||||
options?: { prefix?: ReactNode; color?: BadgeColorType },
|
||||
): ReactNode => (
|
||||
<Badge color={options?.color ?? 'vanilla'} className={styles.valueBadge}>
|
||||
<Badge
|
||||
variant="solid"
|
||||
color={options?.color ?? 'secondary'}
|
||||
textTransform="none"
|
||||
>
|
||||
{options?.prefix}
|
||||
<span className={styles.badgeText} title={value}>
|
||||
{value}
|
||||
|
||||
@@ -129,6 +129,18 @@ describe('LogDetail drawer — header', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('keeps the drawer open when a ⋯ menu item is clicked', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
const onClose = jest.fn();
|
||||
|
||||
renderDrawer({ onClose });
|
||||
|
||||
await user.click(screen.getByTestId('log-details-header-menu'));
|
||||
await user.click(await screen.findByText('Copy log'));
|
||||
|
||||
expect(onClose).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('shows "Open in Explorer" when a handleOpenInExplorer handler is provided', () => {
|
||||
renderDrawer({ handleOpenInExplorer: jest.fn() });
|
||||
|
||||
@@ -217,8 +229,14 @@ describe('LogDetail drawer — header', () => {
|
||||
// Active log is the first one.
|
||||
renderDrawer({ log: logs[0], logs, onNavigateLog });
|
||||
|
||||
expect(screen.getByTestId('log-details-header-prev')).toBeDisabled();
|
||||
expect(screen.getByTestId('log-details-header-next')).toBeEnabled();
|
||||
expect(screen.getByTestId('log-details-header-prev')).toHaveAttribute(
|
||||
'aria-disabled',
|
||||
'true',
|
||||
);
|
||||
expect(screen.getByTestId('log-details-header-next')).not.toHaveAttribute(
|
||||
'aria-disabled',
|
||||
'true',
|
||||
);
|
||||
|
||||
await user.click(screen.getByTestId('log-details-header-next'));
|
||||
expect(onNavigateLog).toHaveBeenLastCalledWith(logs[1]);
|
||||
|
||||
@@ -2,7 +2,7 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { Color, Spacing } from '@signozhq/design-tokens';
|
||||
import { Button } from '@signozhq/ui/button';
|
||||
import { Drawer, Tooltip } from 'antd';
|
||||
import { ToggleGroupSimple } from '@signozhq/ui/toggle-group';
|
||||
import { ToggleGroup } from '@signozhq/ui/toggle-group';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import LogStateIndicator from 'components/Logs/LogStateIndicator/LogStateIndicator';
|
||||
import OverlayScrollbar from 'components/OverlayScrollbar/OverlayScrollbar';
|
||||
@@ -87,7 +87,8 @@ function LogDetailInner({
|
||||
target.closest('.cm-tooltip-autocomplete') ||
|
||||
target.closest('.drawer-popover') ||
|
||||
target.closest('.query-status-popover') ||
|
||||
target.closest('[data-radix-popper-content-wrapper]')
|
||||
target.closest('[data-radix-popper-content-wrapper]') ||
|
||||
target.closest('[data-base-ui-portal]')
|
||||
) {
|
||||
return;
|
||||
}
|
||||
@@ -317,9 +318,12 @@ function LogDetailInner({
|
||||
<div className="log-detail-drawer__section-divider" />
|
||||
|
||||
<div className="tabs-and-search">
|
||||
<ToggleGroupSimple
|
||||
<ToggleGroup
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
size="sm"
|
||||
type="single"
|
||||
className="views-tabs"
|
||||
testId="log-detail-views-tabs"
|
||||
onChange={handleModeChange}
|
||||
value={selectedView}
|
||||
items={[
|
||||
@@ -365,9 +369,12 @@ function LogDetailInner({
|
||||
variant="link"
|
||||
color="secondary"
|
||||
size="sm"
|
||||
prefix={<Filter size="lg" />}
|
||||
icon
|
||||
aria-label="Show Filters"
|
||||
onClick={handleFilterVisible}
|
||||
/>
|
||||
>
|
||||
<Filter size="lg" />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { ReactNode } from 'react';
|
||||
import type { ReactElement, ReactNode } from 'react';
|
||||
import {
|
||||
Bold,
|
||||
CodeXml,
|
||||
@@ -11,16 +11,17 @@ import {
|
||||
Type,
|
||||
} from '@signozhq/icons';
|
||||
import { Button } from '@signozhq/ui/button';
|
||||
import { TooltipSimple } from '@signozhq/ui/tooltip';
|
||||
import { Tooltip } from '@signozhq/ui/tooltip';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
|
||||
import { READ_ONLY_TOOLTIP } from './constants';
|
||||
import InsertVariableMenu from './InsertVariableMenu';
|
||||
import MarkdownHelp from './MarkdownHelp';
|
||||
import type { EditorCommand, EditorVariable } from './types';
|
||||
|
||||
import styles from './MarkdownEditor.module.scss';
|
||||
|
||||
const COMMAND_ICONS: Record<string, ReactNode> = {
|
||||
const COMMAND_ICONS: Record<string, ReactElement> = {
|
||||
heading: <Heading size={14} />,
|
||||
bold: <Bold size={14} />,
|
||||
italic: <Italic size={14} />,
|
||||
@@ -61,20 +62,22 @@ function EditorToolbar({
|
||||
<span className={styles.toolbarDivider} />
|
||||
<div className={styles.commands}>
|
||||
{commands.map((command) => (
|
||||
<TooltipSimple key={command.id} title={command.label}>
|
||||
<Tooltip key={command.id} title={disabled ? undefined : command.label}>
|
||||
<Button
|
||||
disabledTooltip={READ_ONLY_TOOLTIP}
|
||||
type="button"
|
||||
variant="ghost"
|
||||
color="secondary"
|
||||
size="icon"
|
||||
size="sm"
|
||||
icon
|
||||
disabled={disabled}
|
||||
aria-label={command.label}
|
||||
data-testid={`markdown-command-${command.id}`}
|
||||
testId={`markdown-command-${command.id}`}
|
||||
onClick={(): void => onRunCommand(command)}
|
||||
>
|
||||
{COMMAND_ICONS[command.id]}
|
||||
</Button>
|
||||
</TooltipSimple>
|
||||
</Tooltip>
|
||||
))}
|
||||
</div>
|
||||
<div className={styles.toolbarEnd}>
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { useMemo, useState } from 'react';
|
||||
import { ChevronDown, DollarSign, Search } from '@signozhq/icons';
|
||||
import { ChevronDown, DollarSign } from '@signozhq/icons';
|
||||
import { Button } from '@signozhq/ui/button';
|
||||
import { DropdownMenuSimple, type MenuItem } from '@signozhq/ui/dropdown-menu';
|
||||
import { Dropdown, type DropdownActionItemType } from '@signozhq/ui/dropdown';
|
||||
|
||||
import { READ_ONLY_TOOLTIP } from './constants';
|
||||
import type { EditorVariable } from './types';
|
||||
|
||||
import styles from './MarkdownEditor.module.scss';
|
||||
@@ -17,9 +18,10 @@ interface InsertVariableMenuProps {
|
||||
function toMenuItems(
|
||||
variables: EditorVariable[],
|
||||
onSelect: (name: string) => void,
|
||||
): MenuItem[] {
|
||||
): DropdownActionItemType[] {
|
||||
return variables.map((variable) => ({
|
||||
key: variable.name,
|
||||
type: 'item',
|
||||
value: variable.name,
|
||||
label: (
|
||||
<span
|
||||
className={styles.variableRow}
|
||||
@@ -60,15 +62,17 @@ function InsertVariableMenu({
|
||||
}
|
||||
|
||||
return (
|
||||
<DropdownMenuSimple
|
||||
className={styles.variableMenu}
|
||||
menu={{
|
||||
items,
|
||||
search: {
|
||||
placeholder: 'Search variables',
|
||||
searchIcon: <Search size={14} />,
|
||||
onSearchChange: setSearch,
|
||||
},
|
||||
<Dropdown
|
||||
items={items}
|
||||
nativeButton
|
||||
align="end"
|
||||
side="bottom"
|
||||
contentMaxWidth={320}
|
||||
disabled={disabled}
|
||||
disabledTooltip={READ_ONLY_TOOLTIP}
|
||||
searchInputProps={{
|
||||
placeholder: 'Search variables',
|
||||
onChange: setSearch,
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
@@ -76,15 +80,13 @@ function InsertVariableMenu({
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
size="sm"
|
||||
disabled={disabled}
|
||||
prefix={<DollarSign size={14} className={styles.insertVariableIcon} />}
|
||||
suffix={<ChevronDown size={14} />}
|
||||
className={styles.insertVariable}
|
||||
data-testid="markdown-insert-variable"
|
||||
testId="markdown-insert-variable"
|
||||
>
|
||||
Insert variable
|
||||
</Button>
|
||||
</DropdownMenuSimple>
|
||||
</Dropdown>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -74,27 +74,10 @@
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.insertVariable {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.insertVariableIcon {
|
||||
color: var(--text-amber-400);
|
||||
}
|
||||
|
||||
// The ui library's dropdown assumes a global border-box reset this app doesn't
|
||||
// have (`box-sizing` is set on `body` only and doesn't inherit): its items are
|
||||
// `width: 100%` + padding, so in the portal they lay out content-box and
|
||||
// overflow the popup by the padding — clipping the flush-right badge.
|
||||
.variableMenu,
|
||||
.variableMenu * {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.variableMenu {
|
||||
width: 320px;
|
||||
}
|
||||
|
||||
// Shrinkable, so a clamped popup truncates the name instead of clipping the
|
||||
// badge at the content's `overflow: hidden` edge.
|
||||
.variableRow {
|
||||
|
||||
@@ -15,9 +15,10 @@ function MarkdownHelp(): JSX.Element {
|
||||
type="button"
|
||||
variant="ghost"
|
||||
color="secondary"
|
||||
size="icon"
|
||||
size="sm"
|
||||
icon
|
||||
aria-label="Markdown syntax help"
|
||||
data-testid="markdown-help-trigger"
|
||||
testId="markdown-help-trigger"
|
||||
>
|
||||
<CircleHelp size={14} />
|
||||
</Button>
|
||||
|
||||
@@ -256,8 +256,14 @@ describe('MarkdownEditor', () => {
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByTestId('markdown-command-bold')).toBeDisabled();
|
||||
expect(screen.getByTestId('markdown-insert-variable')).toBeDisabled();
|
||||
expect(screen.getByTestId('markdown-command-bold')).toHaveAttribute(
|
||||
'aria-disabled',
|
||||
'true',
|
||||
);
|
||||
expect(screen.getByTestId('markdown-insert-variable')).toHaveAttribute(
|
||||
'aria-disabled',
|
||||
'true',
|
||||
);
|
||||
});
|
||||
|
||||
it('hides the insert-variable control when none are available', () => {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// The body is persisted inline in the dashboard JSON, so its length is capped.
|
||||
export const MARKDOWN_MAX_LENGTH = 16000;
|
||||
|
||||
export const READ_ONLY_TOOLTIP = 'The editor is read-only';
|
||||
|
||||
/** The canonical syntax; the renderer resolves the other three too. */
|
||||
export const formatVariableToken = (name: string): string => `$${name}`;
|
||||
|
||||
|
||||
@@ -95,7 +95,6 @@
|
||||
font-weight: var(--uppercase-small-500-font-weight);
|
||||
line-height: 100%;
|
||||
letter-spacing: 0.44px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user