Compare commits

...

17 Commits

Author SHA1 Message Date
Jatinderjit Singh
7e50c82d89 refactor: pass MaintenanceMuter directly to pipelineBuilder
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 01:43:03 +05:30
Jatinderjit Singh
254c792a37 chore: replace SPDX tag with full Apache 2.0 license boilerplate
The full license text is unambiguously compliant with Apache 2.0 Section 4(a),
which requires giving recipients "a copy of this License".

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 01:43:03 +05:30
Jatinderjit Singh
23051468f6 chore: add license header to pipeline_builder.go
Copied code originates from Apache-2.0 licensed Prometheus Alertmanager;
add dual copyright + SPDX identifier following the repo's convention.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 01:43:03 +05:30
Jatinderjit Singh
707bc4d740 refactor: move maintenance mute stage into custom pipelineBuilder
Copy notify.PipelineBuilder locally so we can inject mms between the
silence stage and the receiver stage (GossipSettle → Inhibit →
TimeActive → TimeMute → Silence → mms → Receiver), matching the
correct suppression order the team requires.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 01:43:03 +05:30
Jatinderjit Singh
c26f9b9900 refactor: wrap routing pipeline once instead of per-route injection
Replace the per-route-entry loop with a single MultiStage wrap so
maintenance suppression runs once per dispatch group before routing.
2026-04-28 01:43:03 +05:30
Jatinderjit Singh
ddf09e5846 add maintenanceMuteStage to move planned maintenance to alertmanager
Rules previously skipped rule.Eval() entirely during maintenance windows.
This change moves suppression to MaintenanceMuter, injected as a Stage
in the alertmanager notification pipeline. Now rules always evaluate and
everys suppression is handled by alertmanager.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 01:43:03 +05:30
Jatinderjit Singh
61cd4e38dc fix: maintenance ignores recurrence when fixed times also set 2026-04-28 01:43:03 +05:30
Prakhar Dewan
d1c9864f52 chore: remove unused files (#11033)
Some checks failed
build-staging / prepare (push) Has been cancelled
build-staging / js-build (push) Has been cancelled
build-staging / go-build (push) Has been cancelled
build-staging / staging (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
* chore: remove unused files

* chore: add knip.json file
2026-04-27 19:23:12 +00:00
Manika Malhotra
f80b650390 chore(onboarding): add open-source tooling to interest in signoz option (#11083) 2026-04-27 14:55:09 +00:00
Nityananda Gohain
64c356b484 feat: types and handler for span attribute mapping (ai-o11y) (#10909)
Some checks failed
build-staging / prepare (push) Has been cancelled
build-staging / js-build (push) Has been cancelled
build-staging / go-build (push) Has been cancelled
build-staging / staging (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
* feat: 1.types and handler for ai-o11y attribute mapping

* feat: 1.types and handler for ai-o11y attribute mapping

* fix: cleanup

* fix: minor changes

* fix: remove stutters

* fix: remove lint issues

* fix: lint issues

* fix: address comments

* fix: address comments

* fix: address more comments

* fix: more changes

* fix: address comments

* fix: address comments

* fix: address comments

* fix: use mustnewuuid
2026-04-27 11:45:52 +00:00
swapnil-signoz
97885babe8 feat: adding cloud integration implementation details for Azure (#11058)
* refactor: moving types to cloud provider specific namespace/pkg

* refactor: separating cloud provider types

* refactor: using upper case key for AWS

* feat: adding cloud integration azure types

* feat: adding azure services

* refactor: updating omitempty tags

* refactor: updating azure integration config

* feat: completing azure types

* refactor: lint issues

* feat: adding service definitions for azure

* refactor: update service names for Azure Blob Storage telemetry

* refactor: updating definitions with metrics and strategy

* refactor: updating command key

* fix: handle optional connection URL in AWS integration

* feat: wip

* refactor: updating strategy struct

* refactor: updating telemetry strategy

* refactor: updating connection artifact struct

* refactor: updating blob storage service name

* refactor: updating azure blob storage service name

* refactor: update Azure service identifiers

* refactor: updating service defs

* fix: update integration account ID and add agent version to Azure CLI and PowerShell commands

* refactor: updating deny settings mode

* refactor: updating types

* refactor: adding missing case for azure service update

* feat: implement Azure connection commands and add unit tests

* refactor: using template for Azure connection artifact creation and update tests
2026-04-27 11:23:52 +00:00
Piyush Singariya
78e3916aea chore: replace JSONDataTypeIndex and bump otel-collector to v0.144.3 (#11110)
* refactor(telemetrytypes): replace JSONDataTypeIndex with TelemetryFieldKeySkipIndex

* revert: mcp related changes

* fix: openapi fails

---------

Co-authored-by: Ankit Nayan <ankit@signoz.io>
2026-04-27 15:40:50 +05:30
Yunus M
a4266fa703 feat: enable JSON body query support and add group by functionality (#11042)
* feat: enable JSON body query support and add group by functionality

* chore: update the FF
2026-04-27 07:24:03 +00:00
Shivam Gupta
44add7b7cd feat(onboarding): add OpenCode, Baseten, and DBOS datasources (#11109)
Some checks failed
build-staging / prepare (push) Has been cancelled
build-staging / js-build (push) Has been cancelled
build-staging / go-build (push) Has been cancelled
build-staging / staging (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
* feat(onboarding): add OpenCode, Baseten, and DBOS datasources

Adds three new datasources to the onboarding config with logos and
documentation links, closing signoz.io issues #3111, #3053, and #3040.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(onboarding): fix basetenUrl import alphabetical ordering

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-04-27 03:11:38 +00:00
Yunus M
feea9e9b36 refactor: remove light mode styles from various components and update… (#11080)
Some checks failed
build-staging / prepare (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
build-staging / js-build (push) Has been cancelled
build-staging / go-build (push) Has been cancelled
build-staging / staging (push) Has been cancelled
* refactor: remove light mode styles from various components and update color variables

* fix: remove hardcoded background in celery
2026-04-25 06:57:25 +00:00
Ashwin Bhatkal
e94767bda8 refactor(frontend): remove xstate and migrate to plain React state (#11059)
* refactor(frontend): remove xstate and migrate to plain React state

Replace xstate state machines with useState-based step tracking in the
three remaining consumers (labels form, dashboard search filter,
resource attribute provider). Drops xstate and @xstate/react from
dependencies and removes the corresponding no-restricted-imports
entries from oxlint config.

* refactor: clear list of dashboard

* chore: resolve comments
2026-04-25 05:11:39 +00:00
Vishal Sharma
bb10f51cc5 feat(settings): add SigNoz MCP Server setup page (#11025)
Some checks failed
build-staging / prepare (push) Has been cancelled
build-staging / js-build (push) Has been cancelled
build-staging / go-build (push) Has been cancelled
build-staging / staging (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
* feat(settings): add SigNoz MCP Server setup page

Add a new Settings page at /settings/mcp-server that guides Cloud users
through connecting AI assistants (Cursor, VS Code, Claude Desktop, Claude
Code, Codex) to SigNoz via the Model Context Protocol, and provides a
deep-link into Service Accounts for creating the API key the OAuth flow
needs.

* feat(settings): point MCP Server onboarding tile to in-app settings page

The onboarding-config-with-links entry for SigNoz MCP Server previously
linked out to the docs page. Now that we ship an in-product setup page
at /settings/mcp-server, route Cloud users there directly via the
existing internalRedirect pattern. Self-hosted users still see the
in-page fallback card with a docs link.

* fix(settings): fire MCP Server page-viewed event reliably on mount

Previously gated the PAGE_VIEWED analytics event on
isGlobalConfigFetched to avoid a double-fire when the async
ingestion_url resolved. Side effect: if /global/config was slow or
errored out, the event never fired. Fire once on mount instead with
hostname-derived region metadata, which is synchronous and reliable.

* feat(mcp-page): ui refactor and redesign

* feat(mcp-page): global config and page access changes

* feat(mcp-page): refactor and added fallback page

* feat(mcp-page): added test cases

* feat(mcp-page): formatting lint

* feat(mcp-page): code refactor

* feat(mcp-page): cleanup and migrated global config api to open api spec

* feat(mcp-page): removed translation json and add endpoint url to copy

* feat(mcp-page): added mcp server to menu always for cloud and enterprise

---------

Co-authored-by: SagarRajput-7 <sagar@signoz.io>
Co-authored-by: SagarRajput-7 <162284829+SagarRajput-7@users.noreply.github.com>
2026-04-24 21:25:00 +00:00
238 changed files with 5311 additions and 5629 deletions

View File

@@ -167,7 +167,7 @@ func runServer(ctx context.Context, config signoz.Config, logger *slog.Logger) e
if err != nil {
return nil, err
}
azureCloudProviderModule := implcloudprovider.NewAzureCloudProvider()
azureCloudProviderModule := implcloudprovider.NewAzureCloudProvider(defStore)
cloudProvidersMap := map[cloudintegrationtypes.CloudProviderType]cloudintegration.CloudProviderModule{
cloudintegrationtypes.CloudProviderTypeAWS: awsCloudProviderModule,
cloudintegrationtypes.CloudProviderTypeAzure: azureCloudProviderModule,

View File

@@ -2933,8 +2933,8 @@ components:
type: object
PromotetypesWrappedIndex:
properties:
column_type:
type: string
fieldDataType:
$ref: '#/components/schemas/TelemetrytypesFieldDataType'
granularity:
type: integer
type:
@@ -4496,6 +4496,184 @@ components:
type: object
Sigv4SigV4Config:
type: object
SpantypesFieldContext:
enum:
- attribute
- resource
type: string
SpantypesGettableSpanMapperGroups:
properties:
items:
items:
$ref: '#/components/schemas/SpantypesSpanMapperGroup'
type: array
required:
- items
type: object
SpantypesPostableSpanMapper:
properties:
config:
$ref: '#/components/schemas/SpantypesSpanMapperConfig'
enabled:
type: boolean
field_context:
$ref: '#/components/schemas/SpantypesFieldContext'
name:
type: string
required:
- name
- field_context
- config
type: object
SpantypesPostableSpanMapperGroup:
properties:
category:
$ref: '#/components/schemas/SpantypesSpanMapperGroupCategory'
condition:
$ref: '#/components/schemas/SpantypesSpanMapperGroupCondition'
enabled:
type: boolean
name:
type: string
required:
- name
- category
- condition
type: object
SpantypesSpanMapper:
properties:
config:
$ref: '#/components/schemas/SpantypesSpanMapperConfig'
createdAt:
format: date-time
type: string
createdBy:
type: string
enabled:
type: boolean
field_context:
$ref: '#/components/schemas/SpantypesFieldContext'
group_id:
type: string
id:
type: string
name:
type: string
updatedAt:
format: date-time
type: string
updatedBy:
type: string
required:
- id
- group_id
- name
- field_context
- config
- enabled
type: object
SpantypesSpanMapperConfig:
properties:
sources:
items:
$ref: '#/components/schemas/SpantypesSpanMapperSource'
nullable: true
type: array
required:
- sources
type: object
SpantypesSpanMapperGroup:
properties:
category:
$ref: '#/components/schemas/SpantypesSpanMapperGroupCategory'
condition:
$ref: '#/components/schemas/SpantypesSpanMapperGroupCondition'
createdAt:
format: date-time
type: string
createdBy:
type: string
enabled:
type: boolean
id:
type: string
name:
type: string
orgId:
type: string
updatedAt:
format: date-time
type: string
updatedBy:
type: string
required:
- id
- orgId
- name
- category
- condition
- enabled
type: object
SpantypesSpanMapperGroupCategory:
type: object
SpantypesSpanMapperGroupCondition:
properties:
attributes:
items:
type: string
nullable: true
type: array
resource:
items:
type: string
nullable: true
type: array
required:
- attributes
- resource
type: object
SpantypesSpanMapperOperation:
enum:
- move
- copy
type: string
SpantypesSpanMapperSource:
properties:
context:
$ref: '#/components/schemas/SpantypesFieldContext'
key:
type: string
operation:
$ref: '#/components/schemas/SpantypesSpanMapperOperation'
priority:
type: integer
required:
- key
- context
- operation
- priority
type: object
SpantypesUpdatableSpanMapper:
properties:
config:
$ref: '#/components/schemas/SpantypesSpanMapperConfig'
enabled:
nullable: true
type: boolean
field_context:
$ref: '#/components/schemas/SpantypesFieldContext'
type: object
SpantypesUpdatableSpanMapperGroup:
properties:
condition:
$ref: '#/components/schemas/SpantypesSpanMapperGroupCondition'
enabled:
nullable: true
type: boolean
name:
nullable: true
type: string
type: object
TelemetrytypesFieldContext:
enum:
- metric
@@ -9238,6 +9416,487 @@ paths:
summary: Updates my service account
tags:
- serviceaccount
/api/v1/span_mapper_groups:
get:
deprecated: false
description: Returns all span attribute mapping groups for the authenticated
org.
operationId: ListSpanMapperGroups
parameters:
- explode: true
in: query
name: category
schema:
$ref: '#/components/schemas/SpantypesSpanMapperGroupCategory'
style: deepObject
- in: query
name: enabled
schema:
nullable: true
type: boolean
responses:
"200":
content:
application/json:
schema:
properties:
data:
$ref: '#/components/schemas/SpantypesGettableSpanMapperGroups'
status:
type: string
required:
- status
- data
type: object
description: OK
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Bad Request
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Unauthorized
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Forbidden
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Internal Server Error
security:
- api_key:
- VIEWER
- tokenizer:
- VIEWER
summary: List span attribute mapping groups
tags:
- spanmapper
post:
deprecated: false
description: Creates a new span attribute mapping group for the org.
operationId: CreateSpanMapperGroup
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/SpantypesPostableSpanMapperGroup'
responses:
"201":
content:
application/json:
schema:
properties:
data:
$ref: '#/components/schemas/SpantypesSpanMapperGroup'
status:
type: string
required:
- status
- data
type: object
description: Created
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Bad Request
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Unauthorized
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Forbidden
"409":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Conflict
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Internal Server Error
security:
- api_key:
- ADMIN
- tokenizer:
- ADMIN
summary: Create a span attribute mapping group
tags:
- spanmapper
/api/v1/span_mapper_groups/{groupId}:
delete:
deprecated: false
description: Hard-deletes a mapping group and cascades to all its mappers.
operationId: DeleteSpanMapperGroup
parameters:
- in: path
name: groupId
required: true
schema:
type: string
responses:
"204":
description: No Content
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Unauthorized
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Forbidden
"404":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Not Found
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Internal Server Error
security:
- api_key:
- ADMIN
- tokenizer:
- ADMIN
summary: Delete a span attribute mapping group
tags:
- spanmapper
patch:
deprecated: false
description: Partially updates an existing mapping group's name, condition,
or enabled state.
operationId: UpdateSpanMapperGroup
parameters:
- in: path
name: groupId
required: true
schema:
type: string
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/SpantypesUpdatableSpanMapperGroup'
responses:
"204":
description: No Content
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Bad Request
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Unauthorized
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Forbidden
"404":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Not Found
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Internal Server Error
security:
- api_key:
- ADMIN
- tokenizer:
- ADMIN
summary: Update a span attribute mapping group
tags:
- spanmapper
/api/v1/span_mapper_groups/{groupId}/span_mappers:
get:
deprecated: false
description: Returns all mappers belonging to a mapping group.
operationId: ListSpanMappers
parameters:
- in: path
name: groupId
required: true
schema:
type: string
responses:
"200":
content:
application/json:
schema:
properties:
data:
$ref: '#/components/schemas/SpantypesGettableSpanMapperGroups'
status:
type: string
required:
- status
- data
type: object
description: OK
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Bad Request
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Unauthorized
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Forbidden
"404":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Not Found
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Internal Server Error
security:
- api_key:
- VIEWER
- tokenizer:
- VIEWER
summary: List span mappers for a group
tags:
- spanmapper
post:
deprecated: false
description: Adds a new mapper to the specified mapping group.
operationId: CreateSpanMapper
parameters:
- in: path
name: groupId
required: true
schema:
type: string
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/SpantypesPostableSpanMapper'
responses:
"201":
content:
application/json:
schema:
properties:
data:
$ref: '#/components/schemas/SpantypesSpanMapper'
status:
type: string
required:
- status
- data
type: object
description: Created
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Bad Request
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Unauthorized
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Forbidden
"404":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Not Found
"409":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Conflict
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Internal Server Error
security:
- api_key:
- ADMIN
- tokenizer:
- ADMIN
summary: Create a span mapper
tags:
- spanmapper
/api/v1/span_mapper_groups/{groupId}/span_mappers/{mapperId}:
delete:
deprecated: false
description: Hard-deletes a mapper from a mapping group.
operationId: DeleteSpanMapper
parameters:
- in: path
name: groupId
required: true
schema:
type: string
- in: path
name: mapperId
required: true
schema:
type: string
responses:
"204":
description: No Content
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Unauthorized
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Forbidden
"404":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Not Found
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Internal Server Error
security:
- api_key:
- ADMIN
- tokenizer:
- ADMIN
summary: Delete a span mapper
tags:
- spanmapper
patch:
deprecated: false
description: Partially updates an existing mapper's field context, config, or
enabled state.
operationId: UpdateSpanMapper
parameters:
- in: path
name: groupId
required: true
schema:
type: string
- in: path
name: mapperId
required: true
schema:
type: string
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/SpantypesUpdatableSpanMapper'
responses:
"204":
description: No Content
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Bad Request
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Unauthorized
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Forbidden
"404":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Not Found
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Internal Server Error
security:
- api_key:
- ADMIN
- tokenizer:
- ADMIN
summary: Update a span mapper
tags:
- spanmapper
/api/v1/testChannel:
post:
deprecated: true

View File

@@ -18,6 +18,7 @@ func NewAWSCloudProvider(defStore cloudintegrationtypes.ServiceDefinitionStore)
return &awscloudprovider{serviceDefinitions: defStore}, nil
}
// TODO: move URL construction logic to cloudintegrationtypes and add unit tests for it.
func (provider *awscloudprovider) GetConnectionArtifact(ctx context.Context, account *cloudintegrationtypes.Account, req *cloudintegrationtypes.GetConnectionArtifactRequest) (*cloudintegrationtypes.ConnectionArtifact, error) {
baseURL := fmt.Sprintf(cloudintegrationtypes.CloudFormationQuickCreateBaseURL.StringValue(), req.Config.AWS.DeploymentRegion)
u, _ := url.Parse(baseURL)

View File

@@ -2,27 +2,48 @@ package implcloudprovider
import (
"context"
"sort"
"github.com/SigNoz/signoz/pkg/modules/cloudintegration"
"github.com/SigNoz/signoz/pkg/types/cloudintegrationtypes"
)
type azurecloudprovider struct{}
type azurecloudprovider struct {
serviceDefinitions cloudintegrationtypes.ServiceDefinitionStore
}
func NewAzureCloudProvider() cloudintegration.CloudProviderModule {
return &azurecloudprovider{}
func NewAzureCloudProvider(defStore cloudintegrationtypes.ServiceDefinitionStore) cloudintegration.CloudProviderModule {
return &azurecloudprovider{
serviceDefinitions: defStore,
}
}
func (provider *azurecloudprovider) GetConnectionArtifact(ctx context.Context, account *cloudintegrationtypes.Account, req *cloudintegrationtypes.GetConnectionArtifactRequest) (*cloudintegrationtypes.ConnectionArtifact, error) {
panic("implement me")
connectionArtifact, err := cloudintegrationtypes.NewAzureConnectionArtifact(account.ID, req.Config.AgentVersion, req.Credentials, req.Config.Azure)
if err != nil {
return nil, err
}
return &cloudintegrationtypes.ConnectionArtifact{
Azure: connectionArtifact,
}, nil
}
func (provider *azurecloudprovider) ListServiceDefinitions(ctx context.Context) ([]*cloudintegrationtypes.ServiceDefinition, error) {
panic("implement me")
return provider.serviceDefinitions.List(ctx, cloudintegrationtypes.CloudProviderTypeAzure)
}
func (provider *azurecloudprovider) GetServiceDefinition(ctx context.Context, serviceID cloudintegrationtypes.ServiceID) (*cloudintegrationtypes.ServiceDefinition, error) {
panic("implement me")
serviceDef, err := provider.serviceDefinitions.Get(ctx, cloudintegrationtypes.CloudProviderTypeAzure, serviceID)
if err != nil {
return nil, err
}
// override cloud integration dashboard id.
for index, dashboard := range serviceDef.Assets.Dashboards {
serviceDef.Assets.Dashboards[index].ID = cloudintegrationtypes.GetCloudIntegrationDashboardID(cloudintegrationtypes.CloudProviderTypeAzure, serviceID.StringValue(), dashboard.ID)
}
return serviceDef, nil
}
func (provider *azurecloudprovider) BuildIntegrationConfig(
@@ -30,5 +51,56 @@ func (provider *azurecloudprovider) BuildIntegrationConfig(
account *cloudintegrationtypes.Account,
services []*cloudintegrationtypes.StorableCloudIntegrationService,
) (*cloudintegrationtypes.ProviderIntegrationConfig, error) {
panic("implement me")
sort.Slice(services, func(i, j int) bool {
return services[i].Type.StringValue() < services[j].Type.StringValue()
})
var strategies []*cloudintegrationtypes.AzureTelemetryCollectionStrategy
for _, storedSvc := range services {
svcCfg, err := cloudintegrationtypes.NewServiceConfigFromJSON(cloudintegrationtypes.CloudProviderTypeAzure, storedSvc.Config)
if err != nil {
return nil, err
}
svcDef, err := provider.GetServiceDefinition(ctx, storedSvc.Type)
if err != nil {
return nil, err
}
strategy := svcDef.TelemetryCollectionStrategy.Azure
if strategy == nil {
continue
}
logsEnabled := svcCfg.IsLogsEnabled(cloudintegrationtypes.CloudProviderTypeAzure)
metricsEnabled := svcCfg.IsMetricsEnabled(cloudintegrationtypes.CloudProviderTypeAzure)
if !logsEnabled && !metricsEnabled {
continue
}
entry := &cloudintegrationtypes.AzureTelemetryCollectionStrategy{
ResourceProvider: strategy.ResourceProvider,
ResourceType: strategy.ResourceType,
}
if metricsEnabled && strategy.Metrics != nil {
entry.Metrics = strategy.Metrics
}
if logsEnabled && strategy.Logs != nil {
entry.Logs = strategy.Logs
}
strategies = append(strategies, entry)
}
return &cloudintegrationtypes.ProviderIntegrationConfig{
Azure: cloudintegrationtypes.NewAzureIntegrationConfig(
account.Config.Azure.DeploymentRegion,
account.Config.Azure.ResourceGroups,
strategies,
),
}, nil
}

View File

@@ -429,9 +429,13 @@ func (module *module) Collect(ctx context.Context, orgID valuer.UUID) (map[strin
stats["cloudintegration.aws.connectedaccounts.count"] = awsAccountsCount
}
// NOTE: not adding stats for services for now.
// get connected accounts for Azure
azureAccountsCount, err := module.store.CountConnectedAccounts(ctx, orgID, cloudintegrationtypes.CloudProviderTypeAzure)
if err == nil {
stats["cloudintegration.azure.connectedaccounts.count"] = azureAccountsCount
}
// TODO: add more cloud providers when supported
// NOTE: not adding stats for services for now.
return stats, nil
}

View File

@@ -49,7 +49,7 @@ func PrepareTaskFunc(opts baserules.PrepareTaskOptions) (baserules.Task, error)
rules = append(rules, tr)
// create ch rule task for evaluation
task = newTask(baserules.TaskTypeCh, opts.TaskName, evaluation.GetFrequency().Duration(), rules, opts.ManagerOpts, opts.NotifyFunc, opts.MaintenanceStore, opts.OrgID)
task = newTask(baserules.TaskTypeCh, opts.TaskName, evaluation.GetFrequency().Duration(), rules, opts.ManagerOpts, opts.NotifyFunc, opts.OrgID)
} else if opts.Rule.RuleType == ruletypes.RuleTypeProm {
@@ -73,7 +73,7 @@ func PrepareTaskFunc(opts baserules.PrepareTaskOptions) (baserules.Task, error)
rules = append(rules, pr)
// create promql rule task for evaluation
task = newTask(baserules.TaskTypeProm, opts.TaskName, evaluation.GetFrequency().Duration(), rules, opts.ManagerOpts, opts.NotifyFunc, opts.MaintenanceStore, opts.OrgID)
task = newTask(baserules.TaskTypeProm, opts.TaskName, evaluation.GetFrequency().Duration(), rules, opts.ManagerOpts, opts.NotifyFunc, opts.OrgID)
} else if opts.Rule.RuleType == ruletypes.RuleTypeAnomaly {
// create anomaly rule
@@ -96,7 +96,7 @@ func PrepareTaskFunc(opts baserules.PrepareTaskOptions) (baserules.Task, error)
rules = append(rules, ar)
// create anomaly rule task for evaluation
task = newTask(baserules.TaskTypeCh, opts.TaskName, evaluation.GetFrequency().Duration(), rules, opts.ManagerOpts, opts.NotifyFunc, opts.MaintenanceStore, opts.OrgID)
task = newTask(baserules.TaskTypeCh, opts.TaskName, evaluation.GetFrequency().Duration(), rules, opts.ManagerOpts, opts.NotifyFunc, opts.OrgID)
} else {
return nil, errors.NewInvalidInputf(errors.CodeInvalidInput, "unsupported rule type %s. Supported types: %s, %s", opts.Rule.RuleType, ruletypes.RuleTypeProm, ruletypes.RuleTypeThreshold)
@@ -210,9 +210,9 @@ func TestNotification(opts baserules.PrepareTestRuleOptions) (int, error) {
}
// newTask returns an appropriate group for the rule type
func newTask(taskType baserules.TaskType, name string, frequency time.Duration, rules []baserules.Rule, opts *baserules.ManagerOptions, notify baserules.NotifyFunc, maintenanceStore ruletypes.MaintenanceStore, orgID valuer.UUID) baserules.Task {
func newTask(taskType baserules.TaskType, name string, frequency time.Duration, rules []baserules.Rule, opts *baserules.ManagerOptions, notify baserules.NotifyFunc, orgID valuer.UUID) baserules.Task {
if taskType == baserules.TaskTypeCh {
return baserules.NewRuleTask(name, "", frequency, rules, opts, notify, maintenanceStore, orgID)
return baserules.NewRuleTask(name, "", frequency, rules, opts, notify, orgID)
}
return baserules.NewPromRuleTask(name, "", frequency, rules, opts, notify, maintenanceStore, orgID)
return baserules.NewPromRuleTask(name, "", frequency, rules, opts, notify, orgID)
}

View File

@@ -313,14 +313,6 @@
"name": "react-redux",
"message": "[State mgmt] react-redux is deprecated. Migrate to Zustand, nuqs, or react-query."
},
{
"name": "xstate",
"message": "[State mgmt] xstate is deprecated. Migrate to Zustand or react-query."
},
{
"name": "@xstate/react",
"message": "[State mgmt] @xstate/react is deprecated. Migrate to Zustand or react-query."
},
{
"name": "react",
"importNames": [

5
frontend/knip.json Normal file
View File

@@ -0,0 +1,5 @@
{
"$schema": "https://unpkg.com/knip@5/schema.json",
"project": ["src/**/*.ts", "src/**/*.tsx"],
"ignore": ["src/api/generated/**/*.ts"]
}

View File

@@ -63,7 +63,6 @@
"@visx/shape": "3.5.0",
"@visx/tooltip": "3.3.0",
"@vitejs/plugin-react": "5.1.4",
"@xstate/react": "^3.0.0",
"ansi-to-html": "0.7.2",
"antd": "5.11.0",
"antd-table-saveas-excel": "2.2.1",
@@ -146,7 +145,6 @@
"vite": "npm:rolldown-vite@7.3.1",
"vite-plugin-html": "3.2.2",
"web-vitals": "^0.2.4",
"xstate": "^4.31.0",
"zod": "4.3.6",
"zustand": "5.0.11"
},

View File

@@ -16,5 +16,6 @@
"roles": "Roles",
"role_details": "Role Details",
"members": "Members",
"service_accounts": "Service Accounts"
"service_accounts": "Service Accounts",
"mcp_server": "MCP Server"
}

View File

@@ -53,5 +53,6 @@
"METER": "SigNoz | Meter",
"ROLES_SETTINGS": "SigNoz | Roles",
"MEMBERS_SETTINGS": "SigNoz | Members",
"SERVICE_ACCOUNTS_SETTINGS": "SigNoz | Service Accounts"
"SERVICE_ACCOUNTS_SETTINGS": "SigNoz | Service Accounts",
"MCP_SERVER": "SigNoz | MCP Server"
}

View File

@@ -16,5 +16,6 @@
"roles": "Roles",
"role_details": "Role Details",
"members": "Members",
"service_accounts": "Service Accounts"
"service_accounts": "Service Accounts",
"mcp_server": "MCP Server"
}

View File

@@ -76,5 +76,6 @@
"METER": "SigNoz | Meter",
"ROLES_SETTINGS": "SigNoz | Roles",
"MEMBERS_SETTINGS": "SigNoz | Members",
"SERVICE_ACCOUNTS_SETTINGS": "SigNoz | Service Accounts"
"SERVICE_ACCOUNTS_SETTINGS": "SigNoz | Service Accounts",
"MCP_SERVER": "SigNoz | MCP Server"
}

View File

@@ -3701,10 +3701,7 @@ export interface PromotetypesPromotePathDTO {
}
export interface PromotetypesWrappedIndexDTO {
/**
* @type string
*/
column_type?: string;
fieldDataType?: TelemetrytypesFieldDataTypeDTO;
/**
* @type integer
*/
@@ -5475,6 +5472,187 @@ export interface Sigv4SigV4ConfigDTO {
[key: string]: unknown;
}
export enum SpantypesFieldContextDTO {
attribute = 'attribute',
resource = 'resource',
}
export interface SpantypesGettableSpanMapperGroupsDTO {
/**
* @type array
*/
items: SpantypesSpanMapperGroupDTO[];
}
export interface SpantypesPostableSpanMapperDTO {
config: SpantypesSpanMapperConfigDTO;
/**
* @type boolean
*/
enabled?: boolean;
field_context: SpantypesFieldContextDTO;
/**
* @type string
*/
name: string;
}
export interface SpantypesPostableSpanMapperGroupDTO {
category: SpantypesSpanMapperGroupCategoryDTO;
condition: SpantypesSpanMapperGroupConditionDTO;
/**
* @type boolean
*/
enabled?: boolean;
/**
* @type string
*/
name: string;
}
export interface SpantypesSpanMapperDTO {
config: SpantypesSpanMapperConfigDTO;
/**
* @type string
* @format date-time
*/
createdAt?: Date;
/**
* @type string
*/
createdBy?: string;
/**
* @type boolean
*/
enabled: boolean;
field_context: SpantypesFieldContextDTO;
/**
* @type string
*/
group_id: string;
/**
* @type string
*/
id: string;
/**
* @type string
*/
name: string;
/**
* @type string
* @format date-time
*/
updatedAt?: Date;
/**
* @type string
*/
updatedBy?: string;
}
export interface SpantypesSpanMapperConfigDTO {
/**
* @type array
* @nullable true
*/
sources: SpantypesSpanMapperSourceDTO[] | null;
}
export interface SpantypesSpanMapperGroupDTO {
category: SpantypesSpanMapperGroupCategoryDTO;
condition: SpantypesSpanMapperGroupConditionDTO;
/**
* @type string
* @format date-time
*/
createdAt?: Date;
/**
* @type string
*/
createdBy?: string;
/**
* @type boolean
*/
enabled: boolean;
/**
* @type string
*/
id: string;
/**
* @type string
*/
name: string;
/**
* @type string
*/
orgId: string;
/**
* @type string
* @format date-time
*/
updatedAt?: Date;
/**
* @type string
*/
updatedBy?: string;
}
export interface SpantypesSpanMapperGroupCategoryDTO {
[key: string]: unknown;
}
export interface SpantypesSpanMapperGroupConditionDTO {
/**
* @type array
* @nullable true
*/
attributes: string[] | null;
/**
* @type array
* @nullable true
*/
resource: string[] | null;
}
export enum SpantypesSpanMapperOperationDTO {
move = 'move',
copy = 'copy',
}
export interface SpantypesSpanMapperSourceDTO {
context: SpantypesFieldContextDTO;
/**
* @type string
*/
key: string;
operation: SpantypesSpanMapperOperationDTO;
/**
* @type integer
*/
priority: number;
}
export interface SpantypesUpdatableSpanMapperDTO {
config?: SpantypesSpanMapperConfigDTO;
/**
* @type boolean
* @nullable true
*/
enabled?: boolean | null;
field_context?: SpantypesFieldContextDTO;
}
export interface SpantypesUpdatableSpanMapperGroupDTO {
condition?: SpantypesSpanMapperGroupConditionDTO;
/**
* @type boolean
* @nullable true
*/
enabled?: boolean | null;
/**
* @type string
* @nullable true
*/
name?: string | null;
}
export enum TelemetrytypesFieldContextDTO {
metric = 'metric',
log = 'log',
@@ -6925,6 +7103,71 @@ export type GetMyServiceAccount200 = {
status: string;
};
export type ListSpanMapperGroupsParams = {
/**
* @description undefined
*/
category?: SpantypesSpanMapperGroupCategoryDTO;
/**
* @type boolean
* @nullable true
* @description undefined
*/
enabled?: boolean | null;
};
export type ListSpanMapperGroups200 = {
data: SpantypesGettableSpanMapperGroupsDTO;
/**
* @type string
*/
status: string;
};
export type CreateSpanMapperGroup201 = {
data: SpantypesSpanMapperGroupDTO;
/**
* @type string
*/
status: string;
};
export type DeleteSpanMapperGroupPathParameters = {
groupId: string;
};
export type UpdateSpanMapperGroupPathParameters = {
groupId: string;
};
export type ListSpanMappersPathParameters = {
groupId: string;
};
export type ListSpanMappers200 = {
data: SpantypesGettableSpanMapperGroupsDTO;
/**
* @type string
*/
status: string;
};
export type CreateSpanMapperPathParameters = {
groupId: string;
};
export type CreateSpanMapper201 = {
data: SpantypesSpanMapperDTO;
/**
* @type string
*/
status: string;
};
export type DeleteSpanMapperPathParameters = {
groupId: string;
mapperId: string;
};
export type UpdateSpanMapperPathParameters = {
groupId: string;
mapperId: string;
};
export type ListUsersDeprecated200 = {
/**
* @type array

View File

@@ -0,0 +1,787 @@
/**
* ! Do not edit manually
* * The file has been auto-generated using Orval for SigNoz
* * regenerate with 'yarn generate:api'
* SigNoz
*/
import { useMutation, useQuery } from 'react-query';
import type {
InvalidateOptions,
MutationFunction,
QueryClient,
QueryFunction,
QueryKey,
UseMutationOptions,
UseMutationResult,
UseQueryOptions,
UseQueryResult,
} from 'react-query';
import type {
CreateSpanMapper201,
CreateSpanMapperGroup201,
CreateSpanMapperPathParameters,
DeleteSpanMapperGroupPathParameters,
DeleteSpanMapperPathParameters,
ListSpanMapperGroups200,
ListSpanMapperGroupsParams,
ListSpanMappers200,
ListSpanMappersPathParameters,
RenderErrorResponseDTO,
SpantypesPostableSpanMapperDTO,
SpantypesPostableSpanMapperGroupDTO,
SpantypesUpdatableSpanMapperDTO,
SpantypesUpdatableSpanMapperGroupDTO,
UpdateSpanMapperGroupPathParameters,
UpdateSpanMapperPathParameters,
} from '../sigNoz.schemas';
import { GeneratedAPIInstance } from '../../../generatedAPIInstance';
import type { ErrorType, BodyType } from '../../../generatedAPIInstance';
/**
* Returns all span attribute mapping groups for the authenticated org.
* @summary List span attribute mapping groups
*/
export const listSpanMapperGroups = (
params?: ListSpanMapperGroupsParams,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<ListSpanMapperGroups200>({
url: `/api/v1/span_mapper_groups`,
method: 'GET',
params,
signal,
});
};
export const getListSpanMapperGroupsQueryKey = (
params?: ListSpanMapperGroupsParams,
) => {
return [`/api/v1/span_mapper_groups`, ...(params ? [params] : [])] as const;
};
export const getListSpanMapperGroupsQueryOptions = <
TData = Awaited<ReturnType<typeof listSpanMapperGroups>>,
TError = ErrorType<RenderErrorResponseDTO>,
>(
params?: ListSpanMapperGroupsParams,
options?: {
query?: UseQueryOptions<
Awaited<ReturnType<typeof listSpanMapperGroups>>,
TError,
TData
>;
},
) => {
const { query: queryOptions } = options ?? {};
const queryKey =
queryOptions?.queryKey ?? getListSpanMapperGroupsQueryKey(params);
const queryFn: QueryFunction<
Awaited<ReturnType<typeof listSpanMapperGroups>>
> = ({ signal }) => listSpanMapperGroups(params, signal);
return { queryKey, queryFn, ...queryOptions } as UseQueryOptions<
Awaited<ReturnType<typeof listSpanMapperGroups>>,
TError,
TData
> & { queryKey: QueryKey };
};
export type ListSpanMapperGroupsQueryResult = NonNullable<
Awaited<ReturnType<typeof listSpanMapperGroups>>
>;
export type ListSpanMapperGroupsQueryError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary List span attribute mapping groups
*/
export function useListSpanMapperGroups<
TData = Awaited<ReturnType<typeof listSpanMapperGroups>>,
TError = ErrorType<RenderErrorResponseDTO>,
>(
params?: ListSpanMapperGroupsParams,
options?: {
query?: UseQueryOptions<
Awaited<ReturnType<typeof listSpanMapperGroups>>,
TError,
TData
>;
},
): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
const queryOptions = getListSpanMapperGroupsQueryOptions(params, options);
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
queryKey: QueryKey;
};
query.queryKey = queryOptions.queryKey;
return query;
}
/**
* @summary List span attribute mapping groups
*/
export const invalidateListSpanMapperGroups = async (
queryClient: QueryClient,
params?: ListSpanMapperGroupsParams,
options?: InvalidateOptions,
): Promise<QueryClient> => {
await queryClient.invalidateQueries(
{ queryKey: getListSpanMapperGroupsQueryKey(params) },
options,
);
return queryClient;
};
/**
* Creates a new span attribute mapping group for the org.
* @summary Create a span attribute mapping group
*/
export const createSpanMapperGroup = (
spantypesPostableSpanMapperGroupDTO: BodyType<SpantypesPostableSpanMapperGroupDTO>,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<CreateSpanMapperGroup201>({
url: `/api/v1/span_mapper_groups`,
method: 'POST',
headers: { 'Content-Type': 'application/json' },
data: spantypesPostableSpanMapperGroupDTO,
signal,
});
};
export const getCreateSpanMapperGroupMutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof createSpanMapperGroup>>,
TError,
{ data: BodyType<SpantypesPostableSpanMapperGroupDTO> },
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof createSpanMapperGroup>>,
TError,
{ data: BodyType<SpantypesPostableSpanMapperGroupDTO> },
TContext
> => {
const mutationKey = ['createSpanMapperGroup'];
const { mutation: mutationOptions } = options
? options.mutation &&
'mutationKey' in options.mutation &&
options.mutation.mutationKey
? options
: { ...options, mutation: { ...options.mutation, mutationKey } }
: { mutation: { mutationKey } };
const mutationFn: MutationFunction<
Awaited<ReturnType<typeof createSpanMapperGroup>>,
{ data: BodyType<SpantypesPostableSpanMapperGroupDTO> }
> = (props) => {
const { data } = props ?? {};
return createSpanMapperGroup(data);
};
return { mutationFn, ...mutationOptions };
};
export type CreateSpanMapperGroupMutationResult = NonNullable<
Awaited<ReturnType<typeof createSpanMapperGroup>>
>;
export type CreateSpanMapperGroupMutationBody =
BodyType<SpantypesPostableSpanMapperGroupDTO>;
export type CreateSpanMapperGroupMutationError =
ErrorType<RenderErrorResponseDTO>;
/**
* @summary Create a span attribute mapping group
*/
export const useCreateSpanMapperGroup = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof createSpanMapperGroup>>,
TError,
{ data: BodyType<SpantypesPostableSpanMapperGroupDTO> },
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof createSpanMapperGroup>>,
TError,
{ data: BodyType<SpantypesPostableSpanMapperGroupDTO> },
TContext
> => {
const mutationOptions = getCreateSpanMapperGroupMutationOptions(options);
return useMutation(mutationOptions);
};
/**
* Hard-deletes a mapping group and cascades to all its mappers.
* @summary Delete a span attribute mapping group
*/
export const deleteSpanMapperGroup = ({
groupId,
}: DeleteSpanMapperGroupPathParameters) => {
return GeneratedAPIInstance<void>({
url: `/api/v1/span_mapper_groups/${groupId}`,
method: 'DELETE',
});
};
export const getDeleteSpanMapperGroupMutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof deleteSpanMapperGroup>>,
TError,
{ pathParams: DeleteSpanMapperGroupPathParameters },
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof deleteSpanMapperGroup>>,
TError,
{ pathParams: DeleteSpanMapperGroupPathParameters },
TContext
> => {
const mutationKey = ['deleteSpanMapperGroup'];
const { mutation: mutationOptions } = options
? options.mutation &&
'mutationKey' in options.mutation &&
options.mutation.mutationKey
? options
: { ...options, mutation: { ...options.mutation, mutationKey } }
: { mutation: { mutationKey } };
const mutationFn: MutationFunction<
Awaited<ReturnType<typeof deleteSpanMapperGroup>>,
{ pathParams: DeleteSpanMapperGroupPathParameters }
> = (props) => {
const { pathParams } = props ?? {};
return deleteSpanMapperGroup(pathParams);
};
return { mutationFn, ...mutationOptions };
};
export type DeleteSpanMapperGroupMutationResult = NonNullable<
Awaited<ReturnType<typeof deleteSpanMapperGroup>>
>;
export type DeleteSpanMapperGroupMutationError =
ErrorType<RenderErrorResponseDTO>;
/**
* @summary Delete a span attribute mapping group
*/
export const useDeleteSpanMapperGroup = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof deleteSpanMapperGroup>>,
TError,
{ pathParams: DeleteSpanMapperGroupPathParameters },
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof deleteSpanMapperGroup>>,
TError,
{ pathParams: DeleteSpanMapperGroupPathParameters },
TContext
> => {
const mutationOptions = getDeleteSpanMapperGroupMutationOptions(options);
return useMutation(mutationOptions);
};
/**
* Partially updates an existing mapping group's name, condition, or enabled state.
* @summary Update a span attribute mapping group
*/
export const updateSpanMapperGroup = (
{ groupId }: UpdateSpanMapperGroupPathParameters,
spantypesUpdatableSpanMapperGroupDTO: BodyType<SpantypesUpdatableSpanMapperGroupDTO>,
) => {
return GeneratedAPIInstance<void>({
url: `/api/v1/span_mapper_groups/${groupId}`,
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
data: spantypesUpdatableSpanMapperGroupDTO,
});
};
export const getUpdateSpanMapperGroupMutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof updateSpanMapperGroup>>,
TError,
{
pathParams: UpdateSpanMapperGroupPathParameters;
data: BodyType<SpantypesUpdatableSpanMapperGroupDTO>;
},
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof updateSpanMapperGroup>>,
TError,
{
pathParams: UpdateSpanMapperGroupPathParameters;
data: BodyType<SpantypesUpdatableSpanMapperGroupDTO>;
},
TContext
> => {
const mutationKey = ['updateSpanMapperGroup'];
const { mutation: mutationOptions } = options
? options.mutation &&
'mutationKey' in options.mutation &&
options.mutation.mutationKey
? options
: { ...options, mutation: { ...options.mutation, mutationKey } }
: { mutation: { mutationKey } };
const mutationFn: MutationFunction<
Awaited<ReturnType<typeof updateSpanMapperGroup>>,
{
pathParams: UpdateSpanMapperGroupPathParameters;
data: BodyType<SpantypesUpdatableSpanMapperGroupDTO>;
}
> = (props) => {
const { pathParams, data } = props ?? {};
return updateSpanMapperGroup(pathParams, data);
};
return { mutationFn, ...mutationOptions };
};
export type UpdateSpanMapperGroupMutationResult = NonNullable<
Awaited<ReturnType<typeof updateSpanMapperGroup>>
>;
export type UpdateSpanMapperGroupMutationBody =
BodyType<SpantypesUpdatableSpanMapperGroupDTO>;
export type UpdateSpanMapperGroupMutationError =
ErrorType<RenderErrorResponseDTO>;
/**
* @summary Update a span attribute mapping group
*/
export const useUpdateSpanMapperGroup = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof updateSpanMapperGroup>>,
TError,
{
pathParams: UpdateSpanMapperGroupPathParameters;
data: BodyType<SpantypesUpdatableSpanMapperGroupDTO>;
},
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof updateSpanMapperGroup>>,
TError,
{
pathParams: UpdateSpanMapperGroupPathParameters;
data: BodyType<SpantypesUpdatableSpanMapperGroupDTO>;
},
TContext
> => {
const mutationOptions = getUpdateSpanMapperGroupMutationOptions(options);
return useMutation(mutationOptions);
};
/**
* Returns all mappers belonging to a mapping group.
* @summary List span mappers for a group
*/
export const listSpanMappers = (
{ groupId }: ListSpanMappersPathParameters,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<ListSpanMappers200>({
url: `/api/v1/span_mapper_groups/${groupId}/span_mappers`,
method: 'GET',
signal,
});
};
export const getListSpanMappersQueryKey = ({
groupId,
}: ListSpanMappersPathParameters) => {
return [`/api/v1/span_mapper_groups/${groupId}/span_mappers`] as const;
};
export const getListSpanMappersQueryOptions = <
TData = Awaited<ReturnType<typeof listSpanMappers>>,
TError = ErrorType<RenderErrorResponseDTO>,
>(
{ groupId }: ListSpanMappersPathParameters,
options?: {
query?: UseQueryOptions<
Awaited<ReturnType<typeof listSpanMappers>>,
TError,
TData
>;
},
) => {
const { query: queryOptions } = options ?? {};
const queryKey =
queryOptions?.queryKey ?? getListSpanMappersQueryKey({ groupId });
const queryFn: QueryFunction<Awaited<ReturnType<typeof listSpanMappers>>> = ({
signal,
}) => listSpanMappers({ groupId }, signal);
return {
queryKey,
queryFn,
enabled: !!groupId,
...queryOptions,
} as UseQueryOptions<
Awaited<ReturnType<typeof listSpanMappers>>,
TError,
TData
> & { queryKey: QueryKey };
};
export type ListSpanMappersQueryResult = NonNullable<
Awaited<ReturnType<typeof listSpanMappers>>
>;
export type ListSpanMappersQueryError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary List span mappers for a group
*/
export function useListSpanMappers<
TData = Awaited<ReturnType<typeof listSpanMappers>>,
TError = ErrorType<RenderErrorResponseDTO>,
>(
{ groupId }: ListSpanMappersPathParameters,
options?: {
query?: UseQueryOptions<
Awaited<ReturnType<typeof listSpanMappers>>,
TError,
TData
>;
},
): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
const queryOptions = getListSpanMappersQueryOptions({ groupId }, options);
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
queryKey: QueryKey;
};
query.queryKey = queryOptions.queryKey;
return query;
}
/**
* @summary List span mappers for a group
*/
export const invalidateListSpanMappers = async (
queryClient: QueryClient,
{ groupId }: ListSpanMappersPathParameters,
options?: InvalidateOptions,
): Promise<QueryClient> => {
await queryClient.invalidateQueries(
{ queryKey: getListSpanMappersQueryKey({ groupId }) },
options,
);
return queryClient;
};
/**
* Adds a new mapper to the specified mapping group.
* @summary Create a span mapper
*/
export const createSpanMapper = (
{ groupId }: CreateSpanMapperPathParameters,
spantypesPostableSpanMapperDTO: BodyType<SpantypesPostableSpanMapperDTO>,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<CreateSpanMapper201>({
url: `/api/v1/span_mapper_groups/${groupId}/span_mappers`,
method: 'POST',
headers: { 'Content-Type': 'application/json' },
data: spantypesPostableSpanMapperDTO,
signal,
});
};
export const getCreateSpanMapperMutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof createSpanMapper>>,
TError,
{
pathParams: CreateSpanMapperPathParameters;
data: BodyType<SpantypesPostableSpanMapperDTO>;
},
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof createSpanMapper>>,
TError,
{
pathParams: CreateSpanMapperPathParameters;
data: BodyType<SpantypesPostableSpanMapperDTO>;
},
TContext
> => {
const mutationKey = ['createSpanMapper'];
const { mutation: mutationOptions } = options
? options.mutation &&
'mutationKey' in options.mutation &&
options.mutation.mutationKey
? options
: { ...options, mutation: { ...options.mutation, mutationKey } }
: { mutation: { mutationKey } };
const mutationFn: MutationFunction<
Awaited<ReturnType<typeof createSpanMapper>>,
{
pathParams: CreateSpanMapperPathParameters;
data: BodyType<SpantypesPostableSpanMapperDTO>;
}
> = (props) => {
const { pathParams, data } = props ?? {};
return createSpanMapper(pathParams, data);
};
return { mutationFn, ...mutationOptions };
};
export type CreateSpanMapperMutationResult = NonNullable<
Awaited<ReturnType<typeof createSpanMapper>>
>;
export type CreateSpanMapperMutationBody =
BodyType<SpantypesPostableSpanMapperDTO>;
export type CreateSpanMapperMutationError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary Create a span mapper
*/
export const useCreateSpanMapper = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof createSpanMapper>>,
TError,
{
pathParams: CreateSpanMapperPathParameters;
data: BodyType<SpantypesPostableSpanMapperDTO>;
},
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof createSpanMapper>>,
TError,
{
pathParams: CreateSpanMapperPathParameters;
data: BodyType<SpantypesPostableSpanMapperDTO>;
},
TContext
> => {
const mutationOptions = getCreateSpanMapperMutationOptions(options);
return useMutation(mutationOptions);
};
/**
* Hard-deletes a mapper from a mapping group.
* @summary Delete a span mapper
*/
export const deleteSpanMapper = ({
groupId,
mapperId,
}: DeleteSpanMapperPathParameters) => {
return GeneratedAPIInstance<void>({
url: `/api/v1/span_mapper_groups/${groupId}/span_mappers/${mapperId}`,
method: 'DELETE',
});
};
export const getDeleteSpanMapperMutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof deleteSpanMapper>>,
TError,
{ pathParams: DeleteSpanMapperPathParameters },
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof deleteSpanMapper>>,
TError,
{ pathParams: DeleteSpanMapperPathParameters },
TContext
> => {
const mutationKey = ['deleteSpanMapper'];
const { mutation: mutationOptions } = options
? options.mutation &&
'mutationKey' in options.mutation &&
options.mutation.mutationKey
? options
: { ...options, mutation: { ...options.mutation, mutationKey } }
: { mutation: { mutationKey } };
const mutationFn: MutationFunction<
Awaited<ReturnType<typeof deleteSpanMapper>>,
{ pathParams: DeleteSpanMapperPathParameters }
> = (props) => {
const { pathParams } = props ?? {};
return deleteSpanMapper(pathParams);
};
return { mutationFn, ...mutationOptions };
};
export type DeleteSpanMapperMutationResult = NonNullable<
Awaited<ReturnType<typeof deleteSpanMapper>>
>;
export type DeleteSpanMapperMutationError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary Delete a span mapper
*/
export const useDeleteSpanMapper = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof deleteSpanMapper>>,
TError,
{ pathParams: DeleteSpanMapperPathParameters },
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof deleteSpanMapper>>,
TError,
{ pathParams: DeleteSpanMapperPathParameters },
TContext
> => {
const mutationOptions = getDeleteSpanMapperMutationOptions(options);
return useMutation(mutationOptions);
};
/**
* Partially updates an existing mapper's field context, config, or enabled state.
* @summary Update a span mapper
*/
export const updateSpanMapper = (
{ groupId, mapperId }: UpdateSpanMapperPathParameters,
spantypesUpdatableSpanMapperDTO: BodyType<SpantypesUpdatableSpanMapperDTO>,
) => {
return GeneratedAPIInstance<void>({
url: `/api/v1/span_mapper_groups/${groupId}/span_mappers/${mapperId}`,
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
data: spantypesUpdatableSpanMapperDTO,
});
};
export const getUpdateSpanMapperMutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof updateSpanMapper>>,
TError,
{
pathParams: UpdateSpanMapperPathParameters;
data: BodyType<SpantypesUpdatableSpanMapperDTO>;
},
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof updateSpanMapper>>,
TError,
{
pathParams: UpdateSpanMapperPathParameters;
data: BodyType<SpantypesUpdatableSpanMapperDTO>;
},
TContext
> => {
const mutationKey = ['updateSpanMapper'];
const { mutation: mutationOptions } = options
? options.mutation &&
'mutationKey' in options.mutation &&
options.mutation.mutationKey
? options
: { ...options, mutation: { ...options.mutation, mutationKey } }
: { mutation: { mutationKey } };
const mutationFn: MutationFunction<
Awaited<ReturnType<typeof updateSpanMapper>>,
{
pathParams: UpdateSpanMapperPathParameters;
data: BodyType<SpantypesUpdatableSpanMapperDTO>;
}
> = (props) => {
const { pathParams, data } = props ?? {};
return updateSpanMapper(pathParams, data);
};
return { mutationFn, ...mutationOptions };
};
export type UpdateSpanMapperMutationResult = NonNullable<
Awaited<ReturnType<typeof updateSpanMapper>>
>;
export type UpdateSpanMapperMutationBody =
BodyType<SpantypesUpdatableSpanMapperDTO>;
export type UpdateSpanMapperMutationError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary Update a span mapper
*/
export const useUpdateSpanMapper = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof updateSpanMapper>>,
TError,
{
pathParams: UpdateSpanMapperPathParameters;
data: BodyType<SpantypesUpdatableSpanMapperDTO>;
},
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof updateSpanMapper>>,
TError,
{
pathParams: UpdateSpanMapperPathParameters;
data: BodyType<SpantypesUpdatableSpanMapperDTO>;
},
TContext
> => {
const mutationOptions = getUpdateSpanMapperMutationOptions(options);
return useMutation(mutationOptions);
};

View File

@@ -1,25 +0,0 @@
import axios from 'api';
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
import { AxiosError } from 'axios';
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
import {
GlobalConfigData,
GlobalConfigDataProps,
} from 'types/api/globalConfig/types';
const getGlobalConfig = async (): Promise<
SuccessResponseV2<GlobalConfigData>
> => {
try {
const response = await axios.get<GlobalConfigDataProps>(`/global/config`);
return {
httpStatusCode: response.status,
data: response.data.data,
};
} catch (error) {
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
}
};
export default getGlobalConfig;

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" height="34" viewBox="0 0 131 34">
<path fill="currentColor" d="M.36 8.6h16.7v5.6H6.04c-.2 0-.35.16-.35.35v4.9c0 .2.16.35.35.35h11.02v5.6h-5.33c-.2 0-.35.16-.35.35v4.9c0 .2.16.35.35.35h4.98c.2 0 .35-.15.35-.35V25.4h5.34c.2 0 .35-.16.35-.35v-4.9c0-.2-.16-.35-.35-.35h-5.34v-5.6h5.34c.2 0 .35-.16.35-.35v-4.9c0-.2-.16-.35-.35-.35h-5.34V3.35c0-.2-.16-.35-.35-.35H.36c-.2 0-.36.16-.36.35v4.9c0 .2.16.35.36.35ZM44.41 14.7c-.5-.5-1.1-.9-1.76-1.18a5.62 5.62 0 0 0-4.6.17c-.73.37-1.32.91-1.75 1.62h-.17V8.59H34.1v16.83h2.04v-1.81h.17c.21.36.47.67.77.94.31.25.65.48 1.01.67.37.18.77.31 1.18.39a6.2 6.2 0 0 0 3.39-.24 5.36 5.36 0 0 0 3.02-3.1c.29-.75.44-1.62.44-2.6v-.47c0-.96-.16-1.83-.47-2.58-.3-.75-.7-1.4-1.23-1.9v-.01Zm-5.87.66a3.9 3.9 0 0 1 4.34.84c.36.35.64.8.83 1.3.2.5.3 1.07.3 1.7v.47c0 .64-.1 1.23-.3 1.74a3.75 3.75 0 0 1-2.06 2.15 4.27 4.27 0 0 1-3.12-.03 3.86 3.86 0 0 1-2.09-2.2c-.2-.52-.3-1.11-.3-1.75v-.29c0-.62.1-1.2.3-1.7v-.01c.21-.53.5-.99.84-1.36.36-.37.78-.66 1.26-.86ZM97.04 8.59H95v4.86h-2.94v1.86H95v8.17c0 .56.17 1.03.53 1.4.37.35.84.54 1.4.54h4.18v-1.87h-3.5c-.2 0-.33-.05-.43-.15-.1-.1-.14-.27-.14-.49v-7.6h4.65v-1.86h-4.65V8.59ZM114.61 15a5.48 5.48 0 0 0-1.8-1.33 5.6 5.6 0 0 0-2.57-.56 6.17 6.17 0 0 0-4.26 1.7 5.6 5.6 0 0 0-1.72 4.2v.57c0 .9.15 1.75.44 2.5a5.58 5.58 0 0 0 5.5 3.67c1.55 0 2.8-.35 3.72-1.04a5.35 5.35 0 0 0 1.91-2.73l.03-.07-1.94-.52-.02.07c-.11.33-.27.64-.46.94-.17.27-.4.52-.7.74-.28.22-.63.39-1.04.51-.41.13-.9.19-1.46.19a3.8 3.8 0 0 1-2.84-1.05 4.07 4.07 0 0 1-1.1-2.7h9.68v-1.6c0-.54-.11-1.12-.34-1.75a5.04 5.04 0 0 0-1.03-1.74Zm-8.25 3.21a3.8 3.8 0 0 1 1.22-2.25 4.19 4.19 0 0 1 3.99-.7c.44.16.83.38 1.17.66.34.27.62.62.82 1.02.21.38.34.8.38 1.27h-7.58ZM129.09 14.42a4.47 4.47 0 0 0-3.37-1.3c-.93 0-1.73.2-2.4.59-.64.39-1.15.97-1.52 1.74h-.17v-2h-2.04v11.97h2.04v-6.23c0-1.26.32-2.28.95-3.02a3.31 3.31 0 0 1 2.65-1.14c.94 0 1.7.3 2.24.9.56.6.83 1.52.83 2.74v6.75h2.04v-7.13c0-1.71-.42-3.02-1.25-3.87ZM88.1 15a5.48 5.48 0 0 0-1.78-1.33 5.6 5.6 0 0 0-2.58-.56 6.17 6.17 0 0 0-4.27 1.7 5.59 5.59 0 0 0-1.71 4.2v.56c0 .92.14 1.76.44 2.51a5.6 5.6 0 0 0 5.5 3.67c1.55 0 2.8-.35 3.72-1.04a5.36 5.36 0 0 0 1.91-2.73l.03-.07-1.94-.52-.03.07c-.1.32-.26.64-.45.94-.17.27-.4.52-.7.74-.29.21-.64.39-1.05.51-.4.12-.9.19-1.45.19a3.8 3.8 0 0 1-2.85-1.05 4.07 4.07 0 0 1-1.09-2.7h9.68v-1.61c0-.53-.12-1.12-.34-1.74A5.03 5.03 0 0 0 88.1 15Zm-8.24 3.21a3.83 3.83 0 0 1 1.22-2.25 4.2 4.2 0 0 1 3.99-.7c.44.16.83.38 1.16.66.35.27.62.62.83 1.02.2.38.33.8.37 1.27h-7.57ZM73.65 19.42a6.11 6.11 0 0 0-3.23-1.02 6.63 6.63 0 0 1-2.68-.58c-.47-.3-.7-.7-.7-1.25 0-.27.08-.5.21-.7.14-.2.33-.38.56-.52a4.05 4.05 0 0 1 1.78-.42c.85 0 1.54.21 2.06.63.53.41.83 1 .91 1.73l.01.1 1.95-.47-.01-.07c-.07-.45-.22-.91-.45-1.36a3.46 3.46 0 0 0-.94-1.2 4.6 4.6 0 0 0-1.52-.84 6.05 6.05 0 0 0-2.1-.34c-.58 0-1.13.07-1.65.22-.52.14-1 .36-1.43.65-.41.3-.74.66-.99 1.1a2.9 2.9 0 0 0-.37 1.49v.14c0 1.06.38 1.87 1.14 2.42a6.2 6.2 0 0 0 3.24.97c1.18.08 2.04.26 2.56.54.5.28.75.72.75 1.36 0 .6-.25 1.06-.78 1.4-.52.32-1.22.48-2.1.48a3.68 3.68 0 0 1-2.46-.79 3.13 3.13 0 0 1-1.04-2.14v-.09l-1.93.46h-.02v.07a4.4 4.4 0 0 0 3.1 4c.7.24 1.52.36 2.46.36.7 0 1.35-.09 1.93-.27.6-.16 1.12-.4 1.53-.72A3.38 3.38 0 0 0 74.8 22v-.14c0-1.07-.39-1.9-1.14-2.44ZM60.25 23.4c-.1-.1-.14-.27-.14-.49v-9.46h-2.05v1.85h-.16a3.78 3.78 0 0 0-1.61-1.63 4.62 4.62 0 0 0-2.26-.56c-.77 0-1.5.14-2.19.41a5.27 5.27 0 0 0-3.02 3.12c-.29.75-.44 1.63-.44 2.6v.38c0 .99.15 1.87.44 2.63.3.75.7 1.4 1.2 1.93a5.48 5.48 0 0 0 4.05 1.57c.8 0 1.51-.2 2.2-.58.69-.38 1.24-.97 1.63-1.75h.16v.06c0 .56.18 1.03.53 1.4.37.35.85.54 1.41.54h1.36v-1.87h-.68c-.2 0-.34-.05-.43-.15Zm-4.46.13c-.46.2-.97.3-1.52.3a3.68 3.68 0 0 1-2.75-1.09 4.42 4.42 0 0 1-1.05-3.12v-.38c0-.62.1-1.2.29-1.71a3.65 3.65 0 0 1 5-2.17c.47.2.88.49 1.21.86.35.37.62.83.8 1.36.2.5.3 1.08.3 1.7v.3c0 .63-.1 1.23-.3 1.76-.18.5-.45.96-.78 1.33-.33.37-.73.66-1.2.86Z"/>
</svg>

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@@ -0,0 +1,13 @@
<svg width="109" height="24" viewBox="0 0 109 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_125_22125)">
<path d="M0 -2.08616e-07V24H17.9352C22.9911 24 26.0999 21.0858 26.0999 17.04V6.96C26.0999 2.91432 22.9911 -2.08616e-07 17.9352 -2.08616e-07H0ZM6.76413 5.82864H19.1992V18.1714H6.76413V5.82864Z" fill="currentColor"/>
<path d="M46.7659 18.6172H35.0824V14.16H46.7659V18.6172ZM46.595 5.38296V9.5658H35.0824V5.38296H46.595ZM50.2846 12.1373V11.5886C52.5734 10.5258 53.7008 8.8458 53.7008 6.13728C53.7008 2.64012 50.9337 0.000116183 45.5361 0.000116183H28.3184V24H45.7752C51.1728 24 53.9399 21.8401 53.9399 18.0685C53.9399 15.0172 52.6418 13.2001 50.2846 12.1373Z" fill="currentColor"/>
<path d="M62.397 18.1714H74.8319V5.82864H62.397V18.1714ZM63.6609 24C58.6049 24 55.4961 21.0858 55.4961 17.04V6.96012C55.4961 2.91432 58.6049 0.000116183 63.6609 0.000116183H73.568C78.6238 0.000116183 81.7326 2.91432 81.7326 6.96012V17.04C81.7326 21.0858 78.6238 24 73.568 24H63.6609Z" fill="currentColor"/>
<path d="M101.66 15.12L90.8995 14.3658C85.5361 13.9886 83.418 11.1772 83.418 7.47432V6.96012C83.418 2.91432 86.5266 0.000116183 91.5827 0.000116183H100.157C105.214 0.000116183 108.323 2.91432 108.323 6.96012V7.98864H101.968V5.14284H90.2504V8.43432L100.601 9.18864C105.999 9.56568 108.493 12.8572 108.493 16.5257V17.04C108.493 20.7428 105.384 24 100.328 24H91.5827C86.5266 24 83.418 20.7428 83.418 17.04V16.0115H89.7722V18.8572H101.66V15.12Z" fill="currentColor"/>
</g>
<defs>
<clipPath id="clip0_125_22125">
<rect width="108.494" height="24" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -0,0 +1 @@
<svg width='234' height='42' viewBox='0 0 234 42' fill='none' xmlns='http://www.w3.org/2000/svg'><path d='M18 30H6V18H18V30Z' fill='#CFCECD'/><path d='M18 12H6V30H18V12ZM24 36H0V6H24V36Z' fill='#656363'/><path d='M48 30H36V18H48V30Z' fill='#CFCECD'/><path d='M36 30H48V12H36V30ZM54 36H36V42H30V6H54V36Z' fill='#656363'/><path d='M84 24V30H66V24H84Z' fill='#CFCECD'/><path d='M84 24H66V30H84V36H60V6H84V24ZM66 18H78V12H66V18Z' fill='#656363'/><path d='M108 36H96V18H108V36Z' fill='#CFCECD'/><path d='M108 12H96V36H90V6H108V12ZM114 36H108V12H114V36Z' fill='#656363'/><path d='M144 30H126V18H144V30Z' fill='#CFCECD'/><path d='M144 12H126V30H144V36H120V6H144V12Z' fill='#211E1E'/><path d='M168 30H156V18H168V30Z' fill='#CFCECD'/><path d='M168 12H156V30H168V12ZM174 36H150V6H174V36Z' fill='#211E1E'/><path d='M198 30H186V18H198V30Z' fill='#CFCECD'/><path d='M198 12H186V30H198V12ZM204 36H180V6H198V0H204V36Z' fill='#211E1E'/><path d='M234 24V30H216V24H234Z' fill='#CFCECD'/><path d='M216 12V18H228V12H216ZM234 24H216V30H234V36H210V6H234V24Z' fill='#211E1E'/></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -74,7 +74,7 @@
width: 100%;
height: 100%;
background: radial-gradient(circle, #fff 10%, transparent 0);
background: radial-gradient(circle, var(--l1-foreground) 10%, transparent 0);
background-size: 12px 12px;
opacity: 1;

View File

@@ -99,36 +99,6 @@
}
}
.lightMode {
.auth-error-container {
.error-content {
&__error-code {
color: var(--l2-foreground);
}
&__error-message {
color: var(--l1-foreground);
}
&__message-badge-label-text {
color: var(--l2-foreground);
}
&__message-item {
color: var(--l1-foreground);
&::before {
background: var(--l3-background);
}
}
&__scroll-hint-text {
color: var(--l2-foreground);
}
}
}
}
@keyframes horizontal-shaking {
0% {
transform: translateX(0);

View File

@@ -87,23 +87,3 @@
background: var(--l3-background);
flex-shrink: 0;
}
.lightMode {
.auth-footer-content {
box-shadow: 0px 1px 4px rgba(0, 0, 0, 0.08);
}
.auth-footer-icon {
filter: brightness(0) saturate(100%) invert(25%) sepia(8%) saturate(518%)
hue-rotate(192deg) brightness(80%) contrast(95%);
opacity: 0.9;
}
.auth-footer-text {
color: var(--text-neutral-light-200);
}
.auth-footer-link-icon {
color: var(--text-neutral-light-100);
}
}

View File

@@ -143,28 +143,3 @@
}
}
}
.lightMode {
.bg-dot-pattern {
background: radial-gradient(
circle,
var(--l3-background) 1px,
transparent 1px
);
background-size: 12px 12px;
}
.auth-page-gradient {
background: radial-gradient(
ellipse at center top,
color-mix(in srgb, var(--primary-background) 12%, transparent) 0%,
transparent 60%
);
opacity: 0.8;
filter: blur(200px);
@media (min-width: 768px) {
filter: blur(300px);
}
}
}

View File

@@ -41,7 +41,6 @@
justify-content: center;
align-items: center;
border: 1px solid var(--l1-border);
background: linear-gradient(0deg, transparent 0%, transparent 100%), #0b0c0e;
.ant-card-body {
height: 100%;
@@ -238,18 +237,7 @@
height: 2px;
bottom: 0;
left: 0;
background-color: var(--l1-foreground);
}
}
.lightMode {
.celery-task-graph-grid-container {
.celery-task-graph-worker-count {
background: unset;
}
}
.configure-option-Info {
border: 1px dashed var(--bg-robin-400);
background-color: var(--l1-background);
color: var(--l1-foreground);
}
}

View File

@@ -32,6 +32,7 @@ function CreateServiceAccountModal(): JSX.Element {
SA_QUERY_PARAMS.CREATE_SA,
parseAsBoolean.withDefault(false),
);
const [, setSelectedAccountId] = useQueryState(SA_QUERY_PARAMS.ACCOUNT);
const { showErrorModal, isErrorModalVisible } = useErrorModal();
@@ -50,11 +51,12 @@ function CreateServiceAccountModal(): JSX.Element {
const { mutate: createServiceAccount, isLoading: isSubmitting } =
useCreateServiceAccount({
mutation: {
onSuccess: async () => {
onSuccess: async (response) => {
toast.success('Service account created successfully');
reset();
await setIsOpen(null);
await invalidateListServiceAccounts(queryClient);
await setSelectedAccountId(response.data.id);
},
onError: (err) => {
const errMessage = convertToApiError(
@@ -67,7 +69,7 @@ function CreateServiceAccountModal(): JSX.Element {
function handleClose(): void {
reset();
setIsOpen(null);
void setIsOpen(null);
}
function handleCreate(values: FormValues): void {

View File

@@ -77,11 +77,11 @@
width: 280px;
&::placeholder {
color: white;
color: var(--l1-foreground);
}
&:focus::placeholder {
color: rgba($color: #ffffff, $alpha: 0.4);
color: rgba($color: var(--l1-foreground), $alpha: 0.4);
}
}
}
@@ -113,42 +113,6 @@
}
}
.lightMode {
.time-options-container {
.time-options-item {
&.active {
background-color: rgba($color: #ffffff, $alpha: 0.2);
&:hover {
cursor: pointer;
background-color: rgba($color: #ffffff, $alpha: 0.3);
}
}
&:hover {
cursor: pointer;
background-color: rgba($color: #ffffff, $alpha: 0.3);
}
}
}
.timeSelection-input {
display: flex;
gap: 8px;
align-items: center;
padding: 4px 8px;
padding-left: 0px !important;
input::placeholder {
color: var(---bg-ink-300);
}
input:focus::placeholder {
color: rgba($color: #000000, $alpha: 0.4);
}
}
}
.date-time-popover__footer {
border-top: 1px solid var(--l1-border);
padding: 8px 14px;
@@ -300,34 +264,3 @@
background: color-mix(in srgb, var(--bg-robin-200) 8%, transparent);
}
}
.lightMode {
.timezone-container {
.timezone {
background: rgb(179 179 179 / 15%);
&__icon {
stroke: var(--l1-foreground);
}
}
}
.custom-time-picker {
.timeSelection-input {
&:hover {
border-color: var(--l1-border) !important;
}
}
}
.timezone-badge {
background: rgb(179 179 179 / 15%);
}
.time-input-suffix-icon-badge {
background: rgb(179 179 179 / 15%);
&:hover {
background: rgb(179 179 179 / 20%);
}
}
}

View File

@@ -129,20 +129,3 @@ $item-spacing: 8px;
width: 15px;
}
}
.lightMode {
.timezone-picker {
&__search {
.search-icon {
stroke: var(--l1-foreground);
}
}
}
.timezone-name-wrapper {
&__selected-icon {
.check-icon {
stroke: var(--l1-foreground);
}
}
}
}

View File

@@ -1,9 +1,5 @@
.dropdown-button {
color: #fff;
}
.dropdown-button--dark {
color: #000;
color: var(--l1-foreground);
}
.dropdown-icon {

View File

@@ -1,7 +1,6 @@
import { useState } from 'react';
import { EllipsisOutlined } from '@ant-design/icons';
import { Button, Dropdown, MenuProps } from 'antd';
import { useIsDarkMode } from 'hooks/useDarkMode';
import './DropDown.styles.scss';
@@ -12,8 +11,6 @@ function DropDown({
element: JSX.Element[];
onDropDownItemClick?: MenuProps['onClick'];
}): JSX.Element {
const isDarkMode = useIsDarkMode();
const items: MenuProps['items'] = element.map(
(e: JSX.Element, index: number) => ({
label: e,
@@ -35,7 +32,7 @@ function DropDown({
>
<Button
type="link"
className={!isDarkMode ? 'dropdown-button--dark' : 'dropdown-button'}
className={`dropdown-button`}
onClick={(e): void => {
e.preventDefault();
setDdOpen(true);

View File

@@ -10,6 +10,7 @@ import cx from 'classnames';
import { LogType } from 'components/Logs/LogStateIndicator/LogStateIndicator';
import QuerySearch from 'components/QueryBuilderV2/QueryV2/QuerySearch/QuerySearch';
import { convertExpressionToFilters } from 'components/QueryBuilderV2/utils';
import { FeatureKeys } from 'constants/features';
import { LOCALSTORAGE } from 'constants/localStorage';
import { QueryParams } from 'constants/query';
import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder';
@@ -46,6 +47,7 @@ import {
TextSelect,
X,
} from 'lucide-react';
import { useAppContext } from 'providers/App/App';
import { AppState } from 'store/reducers';
import { Query, TagFilter } from 'types/api/queryBuilder/queryBuilderData';
import { DataSource, StringOperators } from 'types/common/queryBuilder';
@@ -79,6 +81,10 @@ function LogDetailInner({
const [selectedView, setSelectedView] = useState<VIEWS>(selectedTab);
const [isFilterVisible, setIsFilterVisible] = useState<boolean>(false);
const { featureFlags } = useAppContext();
const isBodyJsonQueryEnabled =
featureFlags?.find((flag) => flag.name === FeatureKeys.BODY_JSON_ENABLED)
?.active || false;
const [filters, setFilters] = useState<TagFilter | null>(null);
const [isEdit, setIsEdit] = useState<boolean>(false);
@@ -208,11 +214,29 @@ function LogDetailInner({
}
};
const logBody = useMemo(() => {
if (!isBodyJsonQueryEnabled) {
return log?.body || '';
}
try {
const json = JSON.parse(log?.body || '');
if (typeof json?.message === 'string' && json.message !== '') {
return json.message;
}
return log?.body || '';
} catch (error) {
return log?.body || '';
}
}, [isBodyJsonQueryEnabled, log?.body]);
const htmlBody = useMemo(
() => ({
__html: getSanitizedLogBody(log?.body || '', { shouldEscapeHtml: true }),
__html: getSanitizedLogBody(logBody || '', { shouldEscapeHtml: true }),
}),
[log?.body],
[logBody],
);
const handleJSONCopy = (): void => {
@@ -418,7 +442,7 @@ function LogDetailInner({
<div className="log-detail-drawer__log">
<Divider type="vertical" className={cx('log-type-indicator', logType)} />
<Tooltip
title={removeEscapeCharacters(log?.body)}
title={removeEscapeCharacters(logBody)}
placement="left"
mouseLeaveDelay={0}
>

View File

@@ -196,17 +196,3 @@
opacity: 0.5;
}
}
.lightMode {
.members-table {
.ant-table-tbody {
> tr.members-table-row--tinted > td {
background: rgba(0, 0, 0, 0.015);
}
> tr:hover > td {
background: rgba(0, 0, 0, 0.03) !important;
}
}
}
}

View File

@@ -167,22 +167,3 @@
padding-right: 8px;
}
}
.lightMode {
.config-btn {
&.missing-config-btn {
background: var(--bg-amber-100);
color: var(--bg-amber-500);
&:hover {
color: var(--bg-amber-600) !important;
}
}
.missing-config-btn {
.config-btn-content {
border-right: 1px solid var(--bg-amber-600);
}
}
}
}

View File

@@ -16,7 +16,7 @@ $custom-border-color: #2c3044;
}
.ant-select-selection-placeholder {
color: color-mix(in srgb, var(--border) 45%, transparent);
color: color-mix(in srgb, var(--l2-foreground) 45%, transparent);
}
// Base styles are for dark mode
@@ -48,10 +48,6 @@ $custom-border-color: #2c3044;
visibility: visible !important;
pointer-events: none;
z-index: 2;
.lightMode & {
color: rgba(0, 0, 0, 0.85) !important;
}
}
&.ant-select-focused .ant-select-selection-placeholder {
@@ -67,10 +63,6 @@ $custom-border-color: #2c3044;
color: var(--l2-foreground);
z-index: 1;
pointer-events: none;
.lightMode & {
color: rgba(0, 0, 0, 0.85);
}
}
.ant-select-selector {
@@ -114,7 +106,7 @@ $custom-border-color: #2c3044;
}
.ant-select-selection-placeholder {
color: color-mix(in srgb, var(--border) 45%, transparent);
color: color-mix(in srgb, var(--l2-foreground) 45%, transparent);
}
// Customize tags in multiselect (dark mode by default)
@@ -217,7 +209,7 @@ $custom-border-color: #2c3044;
.empty-message {
padding: 12px;
text-align: center;
color: color-mix(in srgb, var(--border) 45%, transparent);
color: color-mix(in srgb, var(--l1-foreground) 45%, transparent);
}
}
@@ -575,7 +567,7 @@ $custom-border-color: #2c3044;
.empty-message {
padding: 12px;
text-align: center;
color: color-mix(in srgb, var(--border) 45%, transparent);
color: color-mix(in srgb, var(--l1-foreground) 45%, transparent);
}
.status-message {
@@ -983,10 +975,6 @@ $custom-border-color: #2c3044;
transition:
opacity 0.2s ease,
visibility 0.2s ease;
.lightMode & {
color: rgba(0, 0, 0, 0.85);
}
}
&:focus-within .all-text {

View File

@@ -249,57 +249,6 @@
}
}
.lightMode {
.query-aggregation-container {
.aggregation-container {
.query-aggregation-select-container {
.query-aggregation-select-editor {
.cm-editor {
.cm-tooltip-autocomplete {
background: var(--l1-background) !important;
border: 1px solid var(--l1-border) !important;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1) !important;
backdrop-filter: none;
ul {
li {
&:hover,
&[aria-selected='true'] {
background: var(--l3-background) !important;
}
}
}
}
.cm-line {
::-moz-selection {
background: var(--l2-background) !important;
opacity: 0.5 !important;
}
::selection {
background: var(--l1-background) !important;
opacity: 0.5 !important;
}
.cm-function {
color: var(--primary-background) !important;
}
}
}
}
}
}
}
.query-aggregation-error-popover {
.ant-popover-inner {
background-color: var(--l1-background);
border: none;
box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.1);
}
}
}
.query-aggregation-error-popover {
.ant-popover-inner {
background-color: var(--l1-border);

View File

@@ -121,44 +121,3 @@
}
}
}
.lightMode {
.qb-trace-operator {
&-arrow {
&::before {
background: repeating-linear-gradient(
to right,
var(--l3-background),
var(--l3-background) 4px,
transparent 4px,
transparent 8px
);
}
&::after {
background-color: var(--l3-background);
}
}
&.non-list-view {
&::before {
background: repeating-linear-gradient(
to bottom,
var(--l3-background),
var(--l3-background) 4px,
transparent 4px,
transparent 8px
);
}
}
&-label-with-input {
border: 1px solid var(--l1-border) !important;
background: var(--l1-background) !important;
.label {
color: var(--l1-foreground) !important;
border-right: 1px solid var(--l1-border) !important;
background: var(--l1-background) !important;
}
}
}
}

View File

@@ -152,7 +152,7 @@
width: 100%;
height: 100%;
background: radial-gradient(circle, #fff 10%, transparent 0);
background: radial-gradient(circle, var(--l1-foreground) 10%, transparent 0);
background-size: 12px 12px;
opacity: 1;

View File

@@ -12,6 +12,12 @@
flex-shrink: 0;
}
&__layout {
display: flex;
flex-direction: column;
height: 100%;
}
&__tab-group {
[data-slot='toggle-group'] {
border-radius: 2px;

View File

@@ -197,17 +197,3 @@
background-color: var(--l1-border);
}
}
.lightMode {
.sa-table {
.ant-table-tbody {
> tr.sa-table-row--tinted > td {
background: rgba(0, 0, 0, 0.015);
}
> tr:hover > td {
background: rgba(0, 0, 0, 0.03) !important;
}
}
}
}

View File

@@ -147,12 +147,12 @@
left: 50%;
width: 4px;
transform: translateX(-50%);
background: var(--l2-background);
opacity: 1;
pointer-events: none;
transition:
background 120ms ease,
width 120ms ease;
background: transparent;
}
.cursorColResize:hover .tanstackResizeHandleLine {

View File

@@ -186,29 +186,3 @@
letter-spacing: -0.06px;
}
}
.lightMode {
.warning-content {
&__warning-code {
color: var(--l2-foreground);
}
&__warning-message {
color: var(--l1-foreground);
}
&__message-item {
color: var(--l1-foreground);
}
&__message-badge {
&-label-text {
color: var(--l1-foreground);
}
.key-value-label__value {
color: var(--l1-foreground);
}
}
&__docs-button {
background: var(--l1-background);
color: var(--l2-foreground);
}
}
}

View File

@@ -9,4 +9,5 @@ export enum FeatureKeys {
ANOMALY_DETECTION = 'anomaly_detection',
ONBOARDING_V3 = 'onboarding_v3',
DOT_METRICS_ENABLED = 'dot_metrics_enabled',
BODY_JSON_ENABLED = 'body_json_enabled',
}

View File

@@ -87,6 +87,7 @@ const ROUTES = {
HOME_PAGE: '/',
PUBLIC_DASHBOARD: '/public/dashboard/:dashboardId',
SERVICE_ACCOUNTS_SETTINGS: '/settings/service-accounts',
MCP_SERVER: '/settings/mcp-server',
} as const;
export default ROUTES;

View File

@@ -157,9 +157,3 @@
.view-all-drawer {
border-radius: 4px;
}
.lightMode {
.ant-table {
background: inherit;
}
}

View File

@@ -226,54 +226,3 @@
}
}
}
.lightMode {
.api-quick-filter-left-section {
.api-quick-filters-header {
border-bottom: 1px solid var(--bg-vanilla-300);
}
}
.api-module-right-section {
.toolbar {
border-bottom: 1px solid var(--bg-vanilla-300);
}
}
.no-filtered-domains-message-container {
.no-filtered-domains-message-content {
.no-filtered-domains-message {
.no-domain-title {
color: var(--l1-foreground);
}
.no-domain-subtitle {
color: var(--l2-foreground);
.attribute {
font-family: 'Space Mono';
}
}
}
}
}
.api-monitoring-domain-list-table {
.ant-table {
.ant-table-cell {
color: var(--l1-foreground);
}
.table-row-light {
background: none;
}
.table-row-dark {
background: none;
}
.round-metric-tag {
color: var(--l1-foreground);
}
}
}
}

View File

@@ -28,7 +28,7 @@
}
.dashboard-title {
color: #fff;
color: var(--l1-foreground);
font-family: Inter;
font-size: 16px;
font-style: normal;
@@ -463,135 +463,3 @@
}
}
}
.lightMode {
.dashboard-description-container {
color: var(--l1-foreground);
.dashboard-details {
.left-section {
.dashboard-title {
color: var(--l1-foreground);
}
}
.right-section {
.icons {
border: 1px solid var(--l1-border);
background: var(--l1-background);
color: var(--l1-foreground);
}
.configure-button {
border: 1px solid var(--l1-border);
background: var(--l1-background);
color: var(--l1-foreground);
}
}
}
.dashboard-description-section {
color: var(--l1-foreground);
}
}
.dashboard-settings {
.ant-popover-inner {
border: 1px solid var(--l1-border);
background: var(--l1-background) !important;
}
.menu-content {
display: flex;
flex-direction: column;
.section-1 {
border-bottom: 1px solid var(--l1-border);
.ant-btn {
color: var(--l1-foreground);
}
}
.section-2 {
border-bottom: 1px solid var(--l1-border);
.ant-btn {
color: var(--l1-foreground);
}
}
}
}
.rename-dashboard {
.ant-modal-content {
border: 1px solid var(--l1-border);
background: var(--l1-background);
.ant-modal-header {
background: var(--l1-background);
border-bottom: 1px solid var(--l1-border);
.ant-modal-title {
color: var(--l1-foreground);
}
}
.ant-modal-body {
.dashboard-content {
.name-text {
color: var(--l1-foreground);
}
.dashboard-name-input {
border: 1px solid var(--l1-border);
background: var(--l1-background);
}
}
}
.ant-modal-footer {
.dashboard-rename {
.cancel-btn {
background: var(--l3-background);
}
}
}
}
}
.section-naming {
.ant-modal-content {
border: 1px solid var(--l1-border);
background: var(--l1-background);
.ant-modal-header {
background: var(--l1-background);
border-bottom: 1px solid var(--l1-border);
.ant-modal-title {
color: var(--l1-foreground);
}
}
.ant-modal-body {
.section-naming-content {
.name-text {
color: var(--l1-foreground);
}
.section-name-input {
border: 1px solid var(--l1-border);
background: var(--l1-background);
}
}
}
.ant-modal-footer {
.dashboard-rename {
.cancel-btn {
background: var(--l3-background);
}
}
}
}
}
}

View File

@@ -141,58 +141,3 @@
}
}
}
.lightMode {
.overview-content {
.overview-settings {
border: 1px solid var(--l1-border);
.name-icon-input {
.dashboard-image-input {
.ant-select-selector {
border: 1px solid var(--l1-border);
background: var(--l3-background);
}
}
.dashboard-name-input {
border: 1px solid var(--l1-border);
background: var(--l3-background);
}
}
.dashboard-name {
color: var(--l1-foreground);
}
.description-text-area {
border: 1px solid var(--l1-border);
background: var(--l3-background);
}
}
.overview-settings-footer {
border-top: 1px solid var(--l1-border);
background: var(--l1-background);
.unsaved {
.unsaved-dot {
background: var(--primary-background);
}
.unsaved-changes {
color: var(--bg-robin-400);
}
}
.footer-action-btns {
.discard-btn {
color: var(--l1-foreground);
background-color: var(--l3-background);
}
.save-btn {
color: var(--l3-background);
}
}
}
}
}

View File

@@ -100,27 +100,6 @@
max-width: 500px;
}
.lightMode {
.variable-item {
.variable-name {
border: 1px solid var(--l1-border);
background: var(--l1-background);
color: var(--bg-robin-300);
}
.variable-value {
border: 1px solid var(--l1-border);
background: var(--l1-background);
color: var(--l1-foreground);
&:hover,
&:focus-within {
outline: 1px solid var(--bg-robin-400);
}
}
}
}
.cycle-error-alert {
margin-bottom: 12px;
padding: 4px 12px;

View File

@@ -116,50 +116,3 @@
}
}
}
.lightMode {
.panel-type-selection-modal {
.ant-modal-content {
border: 1px solid var(--l1-border);
background: var(--l1-background);
.ant-modal-header {
background: var(--l1-background);
border-bottom: 1px solid var(--l1-border);
.ant-modal-title {
color: var(--l1-foreground);
}
}
.ant-modal-body {
.panel-selection {
.selected {
background: var(--l2-background);
}
.ant-card {
border: 1px solid var(--l1-border);
.ant-card-body {
.ant-typography {
color: var(--l2-foreground);
}
}
}
}
}
.ant-modal-footer {
border-top: 1px solid var(--l1-border);
background: var(--l1-background);
.ant-btn {
color: var(--l1-foreground);
background: var(--primary-background);
}
}
}
}
}

View File

@@ -61,11 +61,3 @@
width: 14px;
}
}
.lightMode {
.dashboard-breadcrumbs {
.dashboard-btn {
color: var(--l1-foreground);
}
}
}

View File

@@ -57,32 +57,3 @@
}
}
}
.lightMode {
.download-logs-popover {
.ant-popover-inner {
border: 1px solid var(--l1-border);
background: linear-gradient(
139deg,
color-mix(in srgb, var(--card) 80%, transparent) 0%,
color-mix(in srgb, var(--card) 90%, transparent) 98.68%
);
box-shadow: 4px 10px 16px 2px rgba(255, 255, 255, 0.2);
.download-logs-content {
.action-btns {
color: var(--l1-foreground);
}
.action-btns:hover {
&.ant-btn-text {
background-color: var(--l3-background) !important;
}
}
.export-heading {
color: var(--l2-foreground);
}
}
}
}
}

View File

@@ -119,41 +119,6 @@
}
}
.lightMode {
.main-container {
.plot-tag {
background: var(--l3-background);
}
}
.ant-modal-content {
background-color: var(--l1-foreground);
.ant-modal-confirm-title {
color: var(--l1-foreground);
}
.ant-modal-confirm-content {
.ant-typography {
color: var(--l1-foreground);
}
}
.ant-modal-confirm-btns {
button:nth-of-type(1) {
background-color: var(--l3-background);
border: none;
color: var(--l1-foreground);
}
}
}
.info-help-btns {
.doc-redirection-btn {
color: var(--bg-aqua-600) !important;
border-color: var(--bg-aqua-600) !important;
}
}
}
.create-notification-btn {
box-shadow: none;
}

View File

@@ -1,50 +0,0 @@
// eslint-disable-next-line no-restricted-imports
import { createMachine } from 'xstate';
export const ResourceAttributesFilterMachine =
/** @xstate-layout N4IgpgJg5mDOIC5QBECGsAWAjA9qgThAAQDKYBAxhkQIIB2xAYgJYA2ALmPgHQAqqUANJgAngGIAcgFEAGr0SgADjljN2zHHQUgAHogAcAFgAM3AOz6ATAEYAzJdsA2Y4cOWAnABoQIxAFpDR2tuQ319AFYTcKdbFycAX3jvNExcAmIySmp6JjZOHn4hUTFNACFWAFd8bWVVdU1tPQQzY1MXY2tDdzNHM3dHd0NvXwR7biMTa313S0i+63DE5PRsPEJScnwqWgYiFg4uPgFhcQAlKRIpeSQQWrUNLRumx3Czbg8TR0sbS31jfUcw38fW47gBHmm4XCVms3SWIBSq3SGyyO1yBx4AHlFFxUOwcPhJLJrkoVPcGk9ENYFuF3i5YR0wtEHECEAEgiEmV8zH1DLYzHZ4Yi0utMltsrt9vluNjcfjCWVKtUbnd6o9QE1rMYBtxbGFvsZ3NrZj1WdYOfotUZLX0XEFHEKViKMpttjk9nlDrL8HiCWJzpcSbcyWrGoh3NCQj0zK53P1ph1WeFLLqnJZ2s5vmZLA6kginWsXaj3VLDoUAGqoSpgEp0cpVGohh5hhDWDy0sz8zruakzamWVm-Qyg362V5-AZOayO1KFlHitEejFHKCV6v+i5XRt1ZuU1s52zjNOOaZfdOWIY+RDZ0Hc6ZmKEXqyLPPCudit2Sz08ACSEFYNbSHI27kuquiIOEjiONwjJgrM3RWJYZisgEIJgnYPTmuEdi2OaiR5nQOAQHA2hvsiH4Sui0qFCcIGhnuLSmP0YJuJ2xjJsmKELG8XZTK0tjdHG06vgW5GupRS7St6vrKqSO4UhqVL8TBWp8o4eqdl0A5Xmy3G6gK56-B4uERDOSKiuJi6lgUAhrhUYB0buimtrEKZBDYrxaS0OZca8+ltheybOI4hivGZzrzp+VGHH+AGOQp4EIHy+ghNYnawtG4TsbYvk8QKfHGAJfQ9uF76WSW37xWBTSGJ0qXpd0vRZdEKGPqC2YeO2-zfO4+HxEAA */
createMachine({
tsTypes: {} as import('./Labels.machine.typegen').Typegen0,
initial: 'Idle',
states: {
LabelKey: {
on: {
NEXT: {
actions: 'onSelectLabelValue',
target: 'LabelValue',
},
onBlur: {
actions: 'onSelectLabelValue',
target: 'LabelValue',
},
RESET: {
target: 'Idle',
},
},
},
LabelValue: {
on: {
NEXT: {
actions: ['onValidateQuery'],
},
onBlur: {
actions: ['onValidateQuery'],
// target: 'Idle',
},
RESET: {
target: 'Idle',
},
},
},
Idle: {
on: {
NEXT: {
actions: 'onSelectLabelKey',
description: 'Enter a label key',
target: 'LabelKey',
},
},
},
},
id: 'Label Key Values',
});

View File

@@ -1,25 +0,0 @@
// This file was automatically generated. Edits will be overwritten
export interface Typegen0 {
'@@xstate/typegen': true;
eventsCausingActions: {
onSelectLabelValue: 'NEXT' | 'onBlur';
onValidateQuery: 'NEXT' | 'onBlur';
onSelectLabelKey: 'NEXT';
};
internalEvents: {
'xstate.init': { type: 'xstate.init' };
};
invokeSrcNameMap: {};
missingImplementations: {
actions: 'onSelectLabelValue' | 'onValidateQuery' | 'onSelectLabelKey';
services: never;
guards: never;
delays: never;
};
eventsCausingServices: {};
eventsCausingGuards: {};
eventsCausingDelays: {};
matchesStates: 'LabelKey' | 'LabelValue' | 'Idle';
tags: never;
}

View File

@@ -4,20 +4,20 @@ import {
CloseCircleFilled,
ExclamationCircleOutlined,
} from '@ant-design/icons';
// eslint-disable-next-line no-restricted-imports
import { useMachine } from '@xstate/react';
import { Button, Input, message, Modal } from 'antd';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { map } from 'lodash-es';
import { Labels } from 'types/api/alerts/def';
import { v4 as uuid } from 'uuid';
import { ResourceAttributesFilterMachine } from './Labels.machine';
import QueryChip from './QueryChip';
import { QueryChipItem, SearchContainer } from './styles';
import { ILabelRecord } from './types';
import { createQuery, flattenLabels, prepareLabels } from './utils';
type LabelStep = 'Idle' | 'LabelKey' | 'LabelValue';
type LabelEvent = 'NEXT' | 'onBlur' | 'RESET';
interface LabelSelectProps {
onSetLabels: (q: Labels) => void;
initialValues: Labels | undefined;
@@ -35,42 +35,65 @@ function LabelSelect({
const [queries, setQueries] = useState<ILabelRecord[]>(
initialValues ? flattenLabels(initialValues) : [],
);
const [step, setStep] = useState<LabelStep>('Idle');
const dispatchChanges = (updatedRecs: ILabelRecord[]): void => {
onSetLabels(prepareLabels(updatedRecs, initialValues));
setQueries(updatedRecs);
};
const [state, send] = useMachine(ResourceAttributesFilterMachine, {
actions: {
onSelectLabelKey: () => {},
onSelectLabelValue: () => {
if (currentVal !== '') {
setStaging((prevState) => [...prevState, currentVal]);
} else {
return;
}
setCurrentVal('');
},
onValidateQuery: (): void => {
if (currentVal === '') {
return;
}
const onSelectLabelValue = (): void => {
if (currentVal !== '') {
setStaging((prevState) => [...prevState, currentVal]);
} else {
return;
}
setCurrentVal('');
};
const generatedQuery = createQuery([...staging, currentVal]);
const onValidateQuery = (): void => {
if (currentVal === '') {
return;
}
if (generatedQuery) {
dispatchChanges([...queries, generatedQuery]);
setStaging([]);
setCurrentVal('');
send('RESET');
}
},
},
});
const generatedQuery = createQuery([...staging, currentVal]);
if (generatedQuery) {
dispatchChanges([...queries, generatedQuery]);
setStaging([]);
setCurrentVal('');
setStep('Idle');
}
};
const send = (event: LabelEvent): void => {
if (event === 'RESET') {
setStep('Idle');
return;
}
if (event === 'NEXT') {
if (step === 'Idle') {
setStep('LabelKey');
} else if (step === 'LabelKey') {
onSelectLabelValue();
setStep('LabelValue');
} else if (step === 'LabelValue') {
onValidateQuery();
}
return;
}
if (event === 'onBlur') {
if (step === 'LabelKey') {
onSelectLabelValue();
setStep('LabelValue');
} else if (step === 'LabelValue') {
onValidateQuery();
}
}
};
const handleFocus = (): void => {
if (state.value === 'Idle') {
if (step === 'Idle') {
send('NEXT');
}
};
@@ -79,7 +102,7 @@ function LabelSelect({
if (staging.length === 1 && staging[0] !== undefined) {
send('onBlur');
}
}, [send, staging]);
}, [staging]);
useEffect(() => {
handleBlur();
@@ -115,14 +138,14 @@ function LabelSelect({
});
};
const renderPlaceholder = useCallback((): string => {
if (state.value === 'LabelKey') {
if (step === 'LabelKey') {
return 'Enter a label key then press ENTER.';
}
if (state.value === 'LabelValue') {
if (step === 'LabelValue') {
return `Enter a value for label key(${staging[0]}) then press ENTER.`;
}
return t('placeholder_label_key_pair');
}, [t, state, staging]);
}, [t, step, staging]);
return (
<SearchContainer isDarkMode={isDarkMode} disabled={false}>
<div style={{ display: 'inline-flex', flexWrap: 'wrap' }}>
@@ -148,7 +171,7 @@ function LabelSelect({
if (e.key === 'Enter' || e.code === 'Enter' || e.key === ':') {
send('NEXT');
}
if (state.value === 'Idle') {
if (step === 'Idle') {
send('NEXT');
}
}}
@@ -159,7 +182,7 @@ function LabelSelect({
onBlur={handleBlur}
/>
{queries.length || staging.length || currentVal ? (
{queries.length > 0 || staging.length > 0 || currentVal ? (
<Button
onClick={handleClearAll}
icon={<CloseCircleFilled />}

View File

@@ -174,23 +174,3 @@
}
}
}
.lightMode {
.dashboard-empty-state {
.dashboard-content {
.heading {
.icons {
color: var(--l1-foreground);
}
.welcome {
color: var(--l1-foreground);
}
.welcome-info {
color: var(--l1-foreground);
}
}
}
}
}

View File

@@ -333,100 +333,3 @@
}
}
}
.lightMode {
.fullscreen-grid-container {
.react-grid-layout {
.row-panel {
background: var(--l2-background);
.settings-icon {
color: var(--l1-foreground);
}
.row-icon {
color: var(--l1-foreground);
}
.section-title {
color: var(--l1-foreground);
}
}
}
}
.widget-full-view {
.ant-modal-content {
background-color: var(--l1-foreground);
.ant-modal-header {
background-color: var(--l1-foreground);
}
}
}
.row-settings {
.ant-popover-inner {
border: 1px solid var(--l1-border);
background: var(--l1-background);
.menu-content {
.section-1 {
.rename-btn {
color: var(--l1-foreground);
}
}
.section-2 {
border-top: 1px solid var(--l1-border);
.remove-section {
color: var(--bg-cherry-400);
}
}
}
}
}
.rename-section {
.ant-modal-content {
border: 1px solid var(--l1-border);
background: var(--l1-background);
.ant-modal-header {
background: var(--l1-background);
border-bottom: 1px solid var(--l1-border);
.ant-modal-title {
color: var(--l1-foreground);
}
}
.ant-modal-body {
.typography {
color: var(--l2-foreground);
}
.ant-form-item {
.action-btns {
.cancel-btn {
color: var(--l1-foreground);
background: var(--l3-background);
}
}
}
}
}
}
.view-onclick-show-button {
background: var(--l2-background);
border-color: var(--l1-foreground);
color: var(--l1-foreground);
.menu-item {
&:hover {
background-color: var(--l2-foreground);
}
}
}
}

View File

@@ -55,14 +55,6 @@
padding-right: 0.25rem;
}
.lightMode {
.widget-header-container {
.ant-input-group-addon {
background-color: inherit;
}
}
}
.long-tooltip {
.ant-tooltip-content {
max-height: 500px;

View File

@@ -482,10 +482,10 @@
border-radius: 2px;
border: 1px solid var(--l1-border);
background: var(--l2-background);
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
&.selected {
background: var(--l2-background);
background: var(--l3-background);
color: var(--primary);
}
}
}

View File

@@ -21,9 +21,9 @@ import { DataSource } from 'types/common/queryBuilder';
import { USER_ROLES } from 'types/roles';
import floppyDiscUrl from '@/assets/Icons/floppy-disc.svg';
import logsUrl from '@/assets/Icons/logs.svg';
import { getItemIcon } from '../constants';
import { ScrollText } from '@signozhq/icons';
export default function SavedViews({
onUpdateChecklistDoneItem,
@@ -351,7 +351,7 @@ export default function SavedViews({
className={selectedEntity === 'logs' ? 'selected tab' : 'tab'}
onClick={(): void => handleTabChange('logs')}
>
<img src={logsUrl} alt="logs-icon" className="logs-icon" />
<ScrollText size={14} />
Logs
</Button>
<Button

View File

@@ -148,50 +148,3 @@
}
}
}
.lightMode {
.entity-metric-traces-header {
.filter-section {
border-top: 1px solid var(--l1-border);
border-bottom: 1px solid var(--l1-border);
.ant-select-selector {
border-color: var(--l1-border) !important;
color: var(--l2-foreground);
}
}
}
.entity-metric-traces-table {
.ant-table {
border-radius: 3px;
border: 1px solid var(--l1-border);
.ant-table-thead > tr > th {
background: var(--l1-foreground);
color: var(--l2-foreground);
}
.ant-table-thead > tr > th:has(.entityname-column-header) {
background: var(--l1-foreground);
}
.ant-table-cell {
background: var(--l1-foreground);
color: var(--l1-foreground);
}
.ant-table-cell:has(.entityname-column-value) {
background: var(--l1-foreground);
}
.entityname-column-value {
color: var(--l1-foreground);
}
.ant-table-tbody > tr:hover > td {
background: rgba(0, 0, 0, 0.04);
}
}
}
}

View File

@@ -192,63 +192,6 @@
}
}
.lightMode {
.ant-drawer-header {
border-bottom: 1px solid var(--l1-border);
background: var(--l1-foreground);
}
.entity-detail-drawer {
.title {
color: var(--l2-foreground);
}
.entity-detail-drawer__entity {
.ant-typography {
color: var(--l2-foreground);
background: transparent;
}
}
.radio-button {
border: 1px solid var(--l1-border);
background: var(--l1-foreground);
color: var(--l2-foreground);
}
.views-tabs {
.tab {
background: var(--l1-foreground);
}
.selected_view {
background: var(--l3-background);
border: 1px solid var(--l1-border);
color: var(--l2-foreground);
}
.selected_view::before {
background: var(--l3-background);
border-left: 1px solid var(--l1-border);
}
}
.compass-button {
border: 1px solid var(--l1-border);
background: var(--l1-foreground);
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
}
.tabs-and-search {
.action-btn {
border: 1px solid var(--l1-border);
background: var(--l1-foreground);
color: var(--l2-foreground);
}
}
}
}
.entity-metric-traces {
margin-top: 1rem;
@@ -399,53 +342,6 @@
}
}
.lightMode {
.entity-metric-traces-header {
.filter-section {
border-top: 1px solid var(--l1-border);
border-bottom: 1px solid var(--l1-border);
.ant-select-selector {
border-color: var(--l1-border) !important;
color: var(--l2-foreground);
}
}
}
.entity-metric-traces-table {
.ant-table {
border-radius: 3px;
border: 1px solid var(--l1-border);
.ant-table-thead > tr > th {
background: var(--l1-foreground);
color: var(--l2-foreground);
}
.ant-table-thead > tr > th:has(.entityname-column-header) {
background: var(--l1-foreground);
}
.ant-table-cell {
background: var(--l1-foreground);
color: var(--l1-foreground);
}
.ant-table-cell:has(.entityname-column-value) {
background: var(--l1-foreground);
}
.entityname-column-value {
color: var(--l3-background);
}
.ant-table-tbody > tr:hover > td {
background: rgba(0, 0, 0, 0.04);
}
}
}
}
.entity-metrics-logs-container {
margin-top: 1rem;

View File

@@ -773,206 +773,6 @@
box-shadow: 0px -4px 16px 2px rgba(0, 0, 0, 0.2);
}
.lightMode {
.ingestion-key-container {
.ingestion-key-content {
.title {
color: var(--l1-foreground);
}
.ant-table-row {
.ant-table-cell {
background: var(--l1-background);
}
&:hover {
.ant-table-cell {
background: var(--l1-background) !important;
}
}
.column-render {
border: 1px solid var(--l1-border);
background: var(--l1-background);
.ant-collapse {
border: none;
.ant-collapse-header {
background: var(--l1-background);
}
.ant-collapse-content {
border-top: 1px solid var(--l1-border);
}
}
.title-with-action {
.ingestion-key-title {
.ant-typography {
color: var(--l1-foreground);
}
}
.ingestion-key-value {
background: var(--l3-background);
.ant-typography {
color: var(--l2-foreground);
}
.copy-key-btn {
cursor: pointer;
}
}
.action-btn {
.ant-typography {
color: var(--l2-foreground);
}
}
}
.ingestion-key-details {
border-top: 1px solid var(--l1-border);
.ingestion-key-tag {
background: var(--l3-background);
.tag-text {
color: var(--l1-foreground);
}
}
.ingestion-key-created-by {
color: var(--l2-foreground);
}
.ingestion-key-last-used-at {
.ant-typography {
color: var(--l2-foreground);
}
}
}
}
}
}
}
.delete-ingestion-key-modal {
.ant-modal-content {
border: 1px solid var(--l1-border);
background: var(--l1-background);
.ant-modal-header {
background: var(--l1-background);
.title {
color: var(--l1-foreground);
}
}
.ant-modal-body {
.ant-typography {
color: var(--l2-foreground);
}
.ingestion-key-input {
.ant-input {
background: var(--l3-background);
color: var(--l1-foreground);
}
}
}
.ant-modal-footer {
.cancel-btn {
background: var(--l3-background);
color: var(--l1-foreground);
}
}
}
}
.ingestion-key-info-container {
.user-email {
background: var(--l3-background);
}
.limits-data {
border: 1px solid var(--l1-border);
}
}
.ingestion-key-modal {
.ant-modal-content {
border-radius: 4px;
border: 1px solid var(--l1-border);
background: var(--l1-background);
box-shadow: 0px -4px 16px 2px rgba(0, 0, 0, 0.2);
padding: 0;
.ant-modal-header {
background: none;
border-bottom: 1px solid var(--l1-border);
padding: 16px;
}
}
}
.ingestion-key-access-role {
.ant-radio-button-wrapper {
&.ant-radio-button-wrapper-checked {
color: var(--l1-foreground);
background: var(--l3-background);
border-color: var(--l1-border);
&:hover {
color: var(--l1-foreground);
background: var(--l3-background);
border-color: var(--l1-border);
&::before {
background-color: var(--l3-background);
}
}
&:focus {
color: var(--l1-foreground);
background: var(--l3-background);
border-color: var(--l1-border);
}
}
}
.tab {
border: 1px solid var(--l1-border);
&::before {
background: var(--l3-background);
}
&.selected {
background: var(--l3-background);
}
}
}
.copyable-text {
background: var(--l3-background);
}
.ingestion-key-expires-at {
border: 1px solid var(--l1-border);
background: var(--l1-background);
box-shadow: 0px -4px 16px 2px rgba(0, 0, 0, 0.2);
}
.expires-at .ant-picker {
border-color: var(--l1-border) !important;
}
}
.mt-8 {
margin-top: 8px;
}
@@ -1085,26 +885,3 @@
color: var(--l2-foreground);
}
}
.lightMode {
.ingestion-setup-details-links {
background: color-mix(in srgb, var(--primary-background) 10%, transparent);
color: var(--accent-primary);
.learn-more {
color: var(--accent-primary);
}
}
.ingestion-url-error-tooltip {
.ingestion-url-error-content {
.ingestion-url-error-code {
color: var(--bg-amber-500);
}
}
.ingestion-url-error-message {
color: var(--l1-foreground);
}
}
}

View File

@@ -48,7 +48,8 @@ import { initialQueryMeterWithType } from 'constants/queryBuilder';
import ROUTES from 'constants/routes';
import { INITIAL_ALERT_THRESHOLD_STATE } from 'container/CreateAlertV2/context/constants';
import dayjs from 'dayjs';
import { useGetGlobalConfig } from 'hooks/globalConfig/useGetGlobalConfig';
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
import { useGetGlobalConfig } from 'api/generated/services/global';
import useDebouncedFn from 'hooks/useDebouncedFunction';
import { useNotifications } from 'hooks/useNotifications';
import { cloneDeep, isNil, isUndefined } from 'lodash-es';
@@ -358,6 +359,8 @@ function MultiIngestionSettings(): JSX.Element {
error: globalConfigError,
} = useGetGlobalConfig();
const globalConfigApiError = convertToApiError(globalConfigError);
const { mutate: createIngestionKey, isLoading: isLoadingCreateAPIKey } =
useCreateIngestionKey<AxiosError<RenderErrorResponseDTO>>();
@@ -966,7 +969,7 @@ function MultiIngestionSettings(): JSX.Element {
<div className="ingestion-key-value">
<Typography.Text>
{APIKey?.value?.substring(0, 2)}********
{APIKey?.value?.slice(0, 2)}********
{APIKey?.value
?.substring(APIKey?.value?.length ? APIKey.value.length - 2 : 0)
?.trim()}
@@ -1604,11 +1607,11 @@ function MultiIngestionSettings(): JSX.Element {
title={
<div className="ingestion-url-error-content">
<Typography.Text className="ingestion-url-error-code">
{globalConfigError?.getErrorCode()}
{globalConfigApiError?.getErrorCode()}
</Typography.Text>
<Typography.Text className="ingestion-url-error-message">
{globalConfigError?.getErrorMessage()}
{globalConfigApiError?.getErrorMessage()}
</Typography.Text>
</div>
}

View File

@@ -9,6 +9,8 @@
.licenses-page-header-title {
color: var(--l1-foreground);
background: var(--l1-background);
border-right: 1px solid var(--l1-border);
text-align: center;
font-family: Inter;
font-size: 13px;
@@ -54,38 +56,3 @@
}
}
}
.lightMode {
.licenses-page {
.licenses-page-header {
border-bottom: 1px solid var(--l1-border);
background: var(--card);
backdrop-filter: blur(20px);
.licenses-page-header-title {
color: var(--l1-foreground);
background: var(--l1-background);
border-right: 1px solid var(--l1-border);
}
}
.licenses-page-content-container {
.licenses-page-content {
background: var(--l1-background);
&::-webkit-scrollbar-track {
background: transparent;
}
&::-webkit-scrollbar-thumb {
background: var(--l3-background);
}
&::-webkit-scrollbar-thumb:hover {
background: var(--l3-background);
}
}
}
}
}

View File

@@ -176,15 +176,3 @@
cursor: pointer;
}
}
.lightMode {
.info-text {
color: var(--bg-robin-600) !important;
}
.info-link-container {
.anticon {
color: var(--bg-robin-400);
}
}
}

View File

@@ -1114,305 +1114,6 @@
}
}
.lightMode {
.dashboards-list-container {
.dashboards-list-view-content {
.title {
color: var(--l1-foreground);
}
.subtitle {
color: var(--muted-foreground);
}
.ant-table-row {
.ant-table-cell {
background: var(--l1-background);
}
&:hover {
.ant-table-cell {
background: var(--l1-background) !important;
}
}
.dashboard-list-item {
border: 1px solid var(--l1-border);
background: var(--card);
.dashboard-title {
color: var(--l2-foreground);
.title {
color: var(--l1-foreground);
}
}
.title-with-action {
.dashboard-title {
.ant-typography {
color: var(--l1-foreground);
}
}
.action-btn {
.ant-typography {
color: var(--l1-foreground);
}
}
}
.dashboard-details {
.dashboard-tag {
background: var(--l3-background);
.tag-text {
color: var(--l1-foreground);
}
}
.created-by {
.dashboard-tag {
background: var(--l3-background);
.tag-text {
color: var(--l1-foreground);
}
}
.dashboard-created-by {
color: var(--l2-foreground);
}
}
.updated-by {
.text {
color: var(--l2-foreground);
}
.dashboard-tag {
background: var(--l3-background);
.tag-text {
color: var(--l1-foreground);
}
}
.dashboard-created-by {
color: var(--l2-foreground);
}
}
.dashboard-created-by {
color: var(--l1-foreground);
}
.dashboard-created-at {
.ant-typography {
color: var(--l1-foreground);
}
}
}
}
}
}
.no-search {
.text {
color: var(--muted-foreground);
}
}
.all-dashboards-header {
border: 1px solid var(--l1-border);
background: var(--l2-background);
.typography {
color: var(--l2-foreground);
}
.right-actions {
color: var(--l2-foreground);
}
}
.dashboard-empty-state {
.text {
.no-dashboard {
color: var(--l2-foreground);
}
.info {
color: var(--l2-foreground);
}
}
}
.dashboard-error-state {
.error-text {
color: var(--muted-foreground);
}
.action-btns {
.retry-btn {
background: var(--l3-background);
color: var(--l1-foreground);
}
}
}
}
.delete-view-modal {
.ant-modal-content {
border: 1px solid var(--l1-border);
background: var(--card);
.ant-modal-header {
background: var(--card);
.title {
color: var(--l1-foreground);
}
}
.ant-modal-body {
.ant-typography {
color: var(--l1-foreground);
}
.save-view-input {
.ant-input {
background: var(--l2-background);
color: var(--l1-foreground);
}
}
}
.ant-modal-footer {
.cancel-btn {
background: var(--l3-background);
color: var(--l2-foreground);
}
}
}
}
.dashboard-actions {
.ant-popover-inner {
border: 1px solid var(--l1-border);
background: var(--card);
.dashboard-action-content {
.section-1 {
.action-btn {
color: var(--l2-foreground);
}
}
.section-2 {
border-top: 1px solid var(--l1-border);
}
}
}
}
.sort-dashboards {
.ant-popover-inner {
border: 1px solid var(--l1-border);
background: var(--card);
.sort-content {
.sort-heading {
color: var(--l2-foreground);
}
.sort-btns {
color: var(--l2-foreground);
}
}
}
}
.configure-group {
.ant-popover-inner {
border: 1px solid var(--l1-border);
background: var(--card);
.configure-content {
.configure-btn {
color: var(--l2-foreground);
}
}
}
}
.configure-metadata-root {
.ant-modal-content {
border: 1px solid var(--l1-border);
background: var(--card);
.ant-modal-header {
background: var(--card);
border-bottom: 1px solid var(--l1-border);
}
.ant-modal-body {
.configure-content {
.configure-preview {
border: 0.915px solid var(--l1-border);
background: var(--l2-background);
.header {
.title {
color: var(--l2-foreground);
}
}
.details {
.createdAt {
.formatted-time {
color: var(--l2-foreground);
}
.user {
.user-tag {
color: var(--l2-foreground);
background-color: var(--l3-background);
}
.dashboard-created-by {
color: var(--l2-foreground);
}
}
}
.updatedAt {
.formatted-time {
color: var(--l2-foreground);
}
.user {
.user-tag {
color: var(--l2-foreground);
background-color: var(--l3-background);
}
.dashboard-created-by {
color: var(--l2-foreground);
}
}
}
}
}
.metadata-action {
.connection-line {
border: 1px dashed var(--l1-border);
}
}
}
}
.ant-modal-footer {
.save-changes {
border: 1px solid var(--l1-border);
background: var(--l2-background);
}
}
}
}
}
.title-toolip {
.ant-tooltip-content {
.ant-tooltip-inner {

View File

@@ -194,76 +194,3 @@
border-top: 1px solid var(--l1-border);
}
}
.lightMode {
.new-dashboard-templates-modal {
.ant-modal-content {
border: 1px solid var(--l1-border);
background: var(--l1-foreground);
}
.new-dashboard-templates-content-header {
border-bottom: 1px solid var(--l1-border);
}
.new-dashboard-templates-content {
.new-dashboard-templates-list {
border-right: 1px solid var(--l1-border);
.templates-list {
.template-list-item {
.template-name {
color: var(--l3-background);
}
&:hover {
background: color-mix(in srgb, var(--bg-robin-200) 8%, transparent);
}
&.active {
background: color-mix(in srgb, var(--bg-robin-200) 8%, transparent);
}
}
}
}
.new-dashboard-template-preview {
.template-preview-header {
.template-preview-title {
.template-preview-icon {
border: 1px solid var(--l1-border);
background: var(--l1-foreground);
}
.template-info {
.template-name {
color: var(--l3-background);
}
.template-description {
color: var(--l2-foreground);
}
}
}
.create-dashboard-btn {
.ant-btn {
box-shadow: none;
}
}
}
.template-preview-image {
img {
border: 1px solid var(--l1-border);
background: var(--l1-foreground);
}
}
}
}
.ant-modal-footer {
border-top: 1px solid var(--l1-border);
}
}
}

View File

@@ -1,51 +0,0 @@
// eslint-disable-next-line no-restricted-imports
import { createMachine } from 'xstate';
export const DashboardSearchAndFilter = createMachine({
tsTypes: {} as import('./Dashboard.machine.typegen').Typegen0,
initial: 'Idle',
states: {
Category: {
on: {
NEXT: {
actions: 'onSelectOperator',
target: 'Operator',
},
onBlur: {
actions: 'onBlurPurge',
target: 'Idle',
},
},
},
Operator: {
on: {
NEXT: {
actions: 'onSelectValue',
target: 'Value',
},
onBlur: {
actions: 'onBlurPurge',
target: 'Idle',
},
},
},
Value: {
on: {
onBlur: {
actions: ['onValidateQuery', 'onBlurPurge'],
target: 'Idle',
},
},
},
Idle: {
on: {
NEXT: {
actions: 'onSelectCategory',
description: 'Select Category',
target: 'Category',
},
},
},
},
id: 'Dashboard Search And Filter',
});

View File

@@ -1,32 +0,0 @@
// This file was automatically generated. Edits will be overwritten
export interface Typegen0 {
'@@xstate/typegen': true;
eventsCausingActions: {
onSelectOperator: 'NEXT';
onBlurPurge: 'onBlur';
onSelectValue: 'NEXT';
onValidateQuery: 'onBlur';
onSelectCategory: 'NEXT';
};
internalEvents: {
'xstate.init': { type: 'xstate.init' };
};
invokeSrcNameMap: {};
missingImplementations: {
actions:
| 'onSelectOperator'
| 'onBlurPurge'
| 'onSelectValue'
| 'onValidateQuery'
| 'onSelectCategory';
services: never;
guards: never;
delays: never;
};
eventsCausingServices: {};
eventsCausingGuards: {};
eventsCausingDelays: {};
matchesStates: 'Category' | 'Operator' | 'Value' | 'Idle';
tags: never;
}

View File

@@ -1,21 +0,0 @@
import { QueryChipContainer, QueryChipItem } from './styles';
import { IQueryStructure } from './types';
export default function QueryChip({
queryData,
onRemove,
}: {
queryData: IQueryStructure;
onRemove: (id: string) => void;
}): JSX.Element {
const { category, operator, value, id } = queryData;
return (
<QueryChipContainer>
<QueryChipItem>{category}</QueryChipItem>
<QueryChipItem>{operator}</QueryChipItem>
<QueryChipItem closable onClose={(): void => onRemove(id)}>
{Array.isArray(value) ? value.join(', ') : null}
</QueryChipItem>
</QueryChipContainer>
);
}

View File

@@ -1,64 +0,0 @@
import { Dashboard } from 'types/api/dashboard/getAll';
import { v4 as uuid } from 'uuid';
import { TOperator } from '../types';
import { executeSearchQueries } from '../utils';
describe('executeSearchQueries', () => {
const firstDashboard: Dashboard = {
id: uuid(),
createdAt: '',
updatedAt: '',
createdBy: '',
updatedBy: '',
data: {
title: 'first dashboard',
variables: {},
},
};
const secondDashboard: Dashboard = {
id: uuid(),
createdAt: '',
updatedAt: '',
createdBy: '',
updatedBy: '',
data: {
title: 'second dashboard',
variables: {},
},
};
const thirdDashboard: Dashboard = {
id: uuid(),
createdAt: '',
updatedAt: '',
createdBy: '',
updatedBy: '',
data: {
title: 'third dashboard (with special characters +?\\)',
variables: {},
},
};
const dashboards = [firstDashboard, secondDashboard, thirdDashboard];
it('should filter dashboards based on title', () => {
const query = {
category: 'title',
id: 'someid',
operator: '=' as TOperator,
value: 'first dashboard',
};
expect(executeSearchQueries([query], dashboards)).toEqual([firstDashboard]);
});
it('should filter dashboards with special characters', () => {
const query = {
category: 'title',
id: 'someid',
operator: '=' as TOperator,
value: 'third dashboard (with special characters +?\\)',
};
expect(executeSearchQueries([query], dashboards)).toEqual([thirdDashboard]);
});
});

View File

@@ -1,212 +0,0 @@
import {
MutableRefObject,
useCallback,
useEffect,
useRef,
useState,
} from 'react';
import { CloseCircleFilled } from '@ant-design/icons';
// eslint-disable-next-line no-restricted-imports
import { useMachine } from '@xstate/react';
import { Button, RefSelectProps, Select } from 'antd';
import history from 'lib/history';
import { filter, map } from 'lodash-es';
import { Dashboard } from 'types/api/dashboard/getAll';
import { v4 as uuidv4 } from 'uuid';
import { DashboardSearchAndFilter } from './Dashboard.machine';
import QueryChip from './QueryChip';
import { QueryChipItem, SearchContainer } from './styles';
import { IOptionsData, IQueryStructure, TCategory, TOperator } from './types';
import {
convertQueriesToURLQuery,
convertURLQueryStringToQuery,
executeSearchQueries,
OptionsSchemas,
OptionsValueResolution,
} from './utils';
function SearchFilter({
searchData,
filterDashboards,
}: {
searchData: Dashboard[];
filterDashboards: (filteredDashboards: Dashboard[]) => void;
}): JSX.Element {
const [category, setCategory] = useState<TCategory>();
const [optionsData, setOptionsData] = useState<IOptionsData>(
OptionsSchemas.attribute,
);
const selectRef = useRef() as MutableRefObject<RefSelectProps>;
const [selectedValues, setSelectedValues] = useState<string[]>([]);
const [staging, setStaging] = useState<string[] | string[][] | unknown[]>([]);
const [queries, setQueries] = useState<IQueryStructure[]>([]);
useEffect(() => {
const searchQueryString = new URLSearchParams(history.location.search).get(
'search',
);
if (searchQueryString) {
setQueries(convertURLQueryStringToQuery(searchQueryString) || []);
}
}, []);
useEffect(() => {
filterDashboards(executeSearchQueries(queries, searchData));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [queries, searchData]);
const updateURLWithQuery = useCallback(
(inputQueries?: IQueryStructure[]): void => {
history.push({
pathname: history.location.pathname,
search:
inputQueries || queries
? `?search=${convertQueriesToURLQuery(inputQueries || queries)}`
: '',
});
},
[queries],
);
useEffect(() => {
if (Array.isArray(queries) && queries.length > 0) {
updateURLWithQuery();
}
}, [queries, updateURLWithQuery]);
const [state, send] = useMachine(DashboardSearchAndFilter, {
actions: {
onSelectCategory: () => {
setOptionsData(OptionsSchemas.attribute);
},
onSelectOperator: () => {
setOptionsData(OptionsSchemas.operator);
},
onSelectValue: () => {
setOptionsData(
OptionsValueResolution(category as TCategory, searchData) as IOptionsData,
);
},
onBlurPurge: () => {
setSelectedValues([]);
setStaging([]);
},
onValidateQuery: () => {
if (staging.length <= 2 && selectedValues.length === 0) {
return;
}
setQueries([
...queries,
{
id: uuidv4(),
category: staging[0] as string,
operator: staging[1] as TOperator,
value: selectedValues,
},
]);
},
},
});
const nextState = (): void => {
send('NEXT');
};
const removeQueryById = (queryId: string): void => {
setQueries((queries) => {
const updatedQueries = filter(queries, ({ id }) => id !== queryId);
updateURLWithQuery(updatedQueries);
return updatedQueries;
});
};
const handleChange = (value: never | string[]): void => {
if (!value) {
return;
}
if (optionsData.mode) {
setSelectedValues(value.filter(Boolean));
return;
}
setStaging([...staging, value]);
if (state.value === 'Category') {
setCategory(`${value}`.toLowerCase() as TCategory);
}
nextState();
setSelectedValues([]);
};
const handleFocus = (): void => {
if (state.value === 'Idle') {
send('NEXT');
selectRef.current?.focus();
}
};
const handleBlur = (): void => {
send('onBlur');
selectRef?.current?.blur();
};
const clearQueries = (): void => {
setQueries([]);
history.push({
pathname: history.location.pathname,
search: ``,
});
};
return (
<SearchContainer>
<div>
{map(queries, (query) => (
<QueryChip key={query.id} queryData={query} onRemove={removeQueryById} />
))}
{map(staging, (value) => (
<QueryChipItem key={JSON.stringify(value)}>
{value as string}
</QueryChipItem>
))}
</div>
{optionsData && (
<Select
placeholder={
!queries.length &&
!staging.length &&
!selectedValues.length &&
'Search or Filter results'
}
size="small"
ref={selectRef}
mode={optionsData.mode as 'tags' | 'multiple'}
style={{ flex: 1 }}
onChange={handleChange}
bordered={false}
suffixIcon={null}
value={selectedValues}
onFocus={handleFocus}
onBlur={handleBlur}
showSearch
>
{optionsData.options &&
Array.isArray(optionsData.options) &&
optionsData.options.map(
(optionItem): JSX.Element => (
<Select.Option
key={(optionItem.value as string) || (optionItem.name as string)}
value={optionItem.value || optionItem.name}
>
{optionItem.name}
</Select.Option>
),
)}
</Select>
)}
{queries && queries.length > 0 && (
<Button icon={<CloseCircleFilled />} type="text" onClick={clearQueries} />
)}
</SearchContainer>
);
}
export default SearchFilter;

View File

@@ -1,27 +0,0 @@
import { grey } from '@ant-design/colors';
import { Tag } from 'antd';
import styled from 'styled-components';
export const SearchContainer = styled.div`
width: 100%;
display: flex;
align-items: center;
gap: 0.2rem;
padding: 0.2rem 0;
margin: 1rem 0;
border: 1px solid #ccc5;
`;
export const QueryChipContainer = styled.span`
display: flex;
align-items: center;
margin-right: 0.5rem;
&:hover {
& > * {
background: ${grey.primary}44;
}
}
`;
export const QueryChipItem = styled(Tag)`
margin-right: 0.1rem;
`;

View File

@@ -1,18 +0,0 @@
export type TOperator = '=' | '!=';
export type TCategory = 'title' | 'description' | 'tags';
export interface IQueryStructure {
category: string;
id: string;
operator: TOperator;
value: string | string[];
}
interface IOptions {
name: string;
value?: string;
}
export interface IOptionsData {
mode: undefined | 'tags' | 'multiple';
options: IOptions[] | [];
}

View File

@@ -1,153 +0,0 @@
/* eslint-disable sonarjs/cognitive-complexity */
import { decode, encode } from 'js-base64';
import { flattenDeep, map, uniqWith } from 'lodash-es';
import { Dashboard } from 'types/api/dashboard/getAll';
import { IOptionsData, IQueryStructure, TCategory, TOperator } from './types';
export const convertQueriesToURLQuery = (
queries: IQueryStructure[],
): string => {
if (!queries || !queries.length) {
return '';
}
return encode(JSON.stringify(queries));
};
export const convertURLQueryStringToQuery = (
queryString: string,
): IQueryStructure[] => JSON.parse(decode(queryString));
export const resolveOperator = (
result: unknown,
operator: TOperator,
): boolean => {
if (operator === '!=') {
return !result;
}
if (operator === '=') {
return !!result;
}
return !!result;
};
export const executeSearchQueries = (
queries: IQueryStructure[] = [],
searchData: Dashboard[] = [],
): Dashboard[] => {
if (!searchData.length || !queries.length) {
return searchData;
}
const escapeRegExp = (regExp: string): string =>
regExp.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
queries.forEach((query: IQueryStructure) => {
const { operator } = query;
let { value } = query;
const categoryLowercase: TCategory = `${query.category}`.toLowerCase() as
| 'title'
| 'description';
value = flattenDeep([value]);
searchData = searchData.filter(({ data: searchPayload }: Dashboard) => {
try {
const searchSpace =
flattenDeep([searchPayload[categoryLowercase]]).filter(Boolean) || null;
if (!searchSpace || !searchSpace.length) {
return resolveOperator(false, operator);
}
for (const searchSpaceItem of searchSpace) {
if (searchSpaceItem) {
for (const queryValue of value) {
if (searchSpaceItem.match(escapeRegExp(queryValue))) {
return resolveOperator(true, operator);
}
}
}
}
} catch (error) {
console.error(error);
}
return resolveOperator(false, operator);
});
});
return searchData;
};
export const OptionsSchemas = {
attribute: {
mode: undefined,
options: [
{
name: 'Title',
},
{
name: 'Description',
},
{
name: 'Tags',
},
],
},
operator: {
mode: undefined,
options: [
{
value: '=',
name: 'Equal',
},
{
name: 'Not Equal',
value: '!=',
},
],
},
};
export function OptionsValueResolution(
category: TCategory,
searchData: Dashboard[],
): Record<string, unknown> | IOptionsData {
const OptionsValueSchema = {
title: {
mode: 'tags',
options: uniqWith(
map(searchData, (searchItem) => ({ name: searchItem.data.title })),
(prev, next) => prev.name === next.name,
),
},
description: {
mode: 'tags',
options: uniqWith(
map(searchData, (searchItem) =>
searchItem.data.description
? {
name: searchItem.data.description,
value: searchItem.data.description,
}
: null,
).filter(Boolean),
(prev, next) => prev?.name === next?.name,
),
},
tags: {
mode: 'tags',
options: uniqWith(
map(
flattenDeep(
// @ts-ignore
map(searchData, (searchItem) => searchItem.data.tags).filter(Boolean),
),
(tag) => ({ name: tag }),
),
(prev, next) => prev.name === next.name,
),
},
};
return (
OptionsValueSchema[category] ||
({ mode: undefined, options: [] } as IOptionsData)
);
}

View File

@@ -7,9 +7,3 @@
.delete-btn:hover {
background-color: color-mix(in srgb, var(--l1-foreground) 12%, transparent);
}
.lightMode {
.delete-btn:hover {
background-color: rgba(0, 0, 0, 0.06) !important;
}
}

View File

@@ -8,8 +8,19 @@ import {
OPERATORS,
QUERY_BUILDER_FUNCTIONS,
} from 'constants/antlrQueryConstants';
import { FeatureKeys } from 'constants/features';
import { QueryParams } from 'constants/query';
import { useActiveLog } from 'hooks/logs/useActiveLog';
import { useGetSearchQueryParam } from 'hooks/queryBuilder/useGetSearchQueryParam';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { ICurrentQueryData } from 'hooks/useHandleExplorerTabChange';
import { useNotifications } from 'hooks/useNotifications';
import { ExplorerViews } from 'pages/LogsExplorer/utils';
import { useAppContext } from 'providers/App/App';
import {
BaseAutocompleteData,
DataTypes,
} from 'types/api/queryBuilder/queryAutocompleteResponse';
import { TitleWrapper } from './BodyTitleRenderer.styles';
import { DROPDOWN_KEY } from './constant';
@@ -25,17 +36,32 @@ function BodyTitleRenderer({
parentIsArray = false,
nodeKey,
value,
handleChangeSelectedView,
}: BodyTitleRendererProps): JSX.Element {
const { onAddToQuery } = useActiveLog();
const { stagedQuery, updateQueriesData } = useQueryBuilder();
const { featureFlags } = useAppContext();
const [, setCopy] = useCopyToClipboard();
const { notifications } = useNotifications();
const viewName = useGetSearchQueryParam(QueryParams.viewName) || '';
const cleanedNodeKey = removeObjectFromString(nodeKey);
const isBodyJsonQueryEnabled =
featureFlags?.find((flag) => flag.name === FeatureKeys.BODY_JSON_ENABLED)
?.active || false;
// Group by is supported only for body json query enabled and not for array elements
const isGroupBySupported =
isBodyJsonQueryEnabled && !cleanedNodeKey.includes('[]');
const filterHandler = (isFilterIn: boolean) => (): void => {
if (parentIsArray) {
onAddToQuery(
generateFieldKeyForArray(
removeObjectFromString(nodeKey),
cleanedNodeKey,
getDataTypes(value),
isBodyJsonQueryEnabled,
),
`${value}`,
isFilterIn
@@ -45,7 +71,7 @@ function BodyTitleRenderer({
);
} else {
onAddToQuery(
`body.${removeObjectFromString(nodeKey)}`,
`body.${cleanedNodeKey}`,
`${value}`,
isFilterIn ? OPERATORS['='] : OPERATORS['!='],
getDataTypes(value),
@@ -53,10 +79,67 @@ function BodyTitleRenderer({
}
};
const groupByHandler = useCallback((): void => {
if (!stagedQuery) {
return;
}
const groupByKey = parentIsArray
? generateFieldKeyForArray(
cleanedNodeKey,
getDataTypes(value),
isBodyJsonQueryEnabled,
)
: `body.${cleanedNodeKey}`;
const fieldDataType = getDataTypes(value);
const normalizedDataType: DataTypes | undefined = Object.values(
DataTypes,
).includes(fieldDataType as DataTypes)
? (fieldDataType as DataTypes)
: undefined;
const updatedQuery = updateQueriesData(
stagedQuery,
'queryData',
(item, index) => {
if (index === 0) {
const newGroupByItem: BaseAutocompleteData = {
key: groupByKey,
type: '',
dataType: normalizedDataType,
};
return { ...item, groupBy: [...(item.groupBy || []), newGroupByItem] };
}
return item;
},
);
const queryData: ICurrentQueryData = {
name: viewName,
id: updatedQuery.id,
query: updatedQuery,
};
handleChangeSelectedView?.(ExplorerViews.TIMESERIES, queryData);
}, [
cleanedNodeKey,
handleChangeSelectedView,
isBodyJsonQueryEnabled,
parentIsArray,
stagedQuery,
updateQueriesData,
value,
viewName,
]);
const onClickHandler: MenuProps['onClick'] = (props): void => {
const mapper = {
[DROPDOWN_KEY.FILTER_IN]: filterHandler(true),
[DROPDOWN_KEY.FILTER_OUT]: filterHandler(false),
[DROPDOWN_KEY.GROUP_BY]: groupByHandler,
};
const handler = mapper[props.key];
@@ -76,6 +159,14 @@ function BodyTitleRenderer({
key: DROPDOWN_KEY.FILTER_OUT,
label: `Filter out ${value}`,
},
...(isGroupBySupported
? [
{
key: DROPDOWN_KEY.GROUP_BY,
label: `Group by ${nodeKey}`,
},
]
: []),
],
onClick: onClickHandler,
};
@@ -84,7 +175,6 @@ function BodyTitleRenderer({
(e: React.MouseEvent): void => {
// Prevent tree node expansion/collapse
e.stopPropagation();
const cleanedKey = removeObjectFromString(nodeKey);
let copyText: string;
// Check if value is an object or array
@@ -106,8 +196,8 @@ function BodyTitleRenderer({
if (copyText) {
const notificationMessage = isObject
? `${cleanedKey} object copied to clipboard`
: `${cleanedKey} copied to clipboard`;
? `${cleanedNodeKey} object copied to clipboard`
: `${cleanedNodeKey} copied to clipboard`;
notifications.success({
message: notificationMessage,
@@ -115,7 +205,7 @@ function BodyTitleRenderer({
});
}
},
[nodeKey, parentIsArray, setCopy, value, notifications],
[cleanedNodeKey, parentIsArray, setCopy, value, notifications],
);
return (

View File

@@ -1,3 +1,4 @@
import { ChangeViewFunctionType } from 'container/ExplorerOptions/types';
import { MetricsType } from 'container/MetricsApplication/constant';
import { ILog } from 'types/api/logs/log';
@@ -6,6 +7,7 @@ export interface BodyTitleRendererProps {
nodeKey: string;
value: unknown;
parentIsArray?: boolean;
handleChangeSelectedView?: ChangeViewFunctionType;
}
export type AnyObject = { [key: string]: any };

View File

@@ -58,14 +58,3 @@
}
}
}
.lightMode {
.table-view-actions-content {
.ant-popover-inner {
border: 1px solid var(--l1-border);
background: var(--l1-background) !important;
backdrop-filter: none;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
}
}

View File

@@ -2,6 +2,7 @@ import React, { useCallback, useMemo, useState } from 'react';
import { useLocation } from 'react-router-dom';
import { Color } from '@signozhq/design-tokens';
import { Button, Popover, Spin, Tooltip, Tree } from 'antd';
import type { DataNode } from 'antd/es/tree';
import GroupByIcon from 'assets/CustomIcons/GroupByIcon';
import cx from 'classnames';
import CopyClipboardHOC from 'components/Logs/CopyClipboardHOC';
@@ -57,7 +58,7 @@ interface ITableViewActionsProps {
}
// Memoized Tree Component
const MemoizedTree = React.memo<{ treeData: any[] }>(({ treeData }) => (
const MemoizedTree = React.memo<{ treeData: DataNode[] }>(({ treeData }) => (
<Tree
defaultExpandAll
showLine
@@ -74,50 +75,54 @@ const BodyContent: React.FC<{
record: DataType;
bodyHtml: { __html: string };
textToCopy: string;
}> = React.memo(({ fieldData, record, bodyHtml, textToCopy }) => {
const { isLoading, treeData, error } = useAsyncJSONProcessing(
fieldData.value,
record.field === 'body',
);
// Show JSON tree if available, otherwise show HTML content
if (record.field === 'body' && treeData) {
return <MemoizedTree treeData={treeData} />;
}
if (record.field === 'body' && isLoading) {
return (
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
<Spin size="small" />
<span style={{ color: Color.BG_SIENNA_400 }}>Processing JSON...</span>
</div>
handleChangeSelectedView?: ChangeViewFunctionType;
}> = React.memo(
({ fieldData, record, bodyHtml, textToCopy, handleChangeSelectedView }) => {
const { isLoading, treeData, error } = useAsyncJSONProcessing(
fieldData.value,
record.field === 'body',
handleChangeSelectedView,
);
}
if (record.field === 'body' && error) {
return (
<span
style={{ color: Color.BG_SIENNA_400, whiteSpace: 'pre-wrap', tabSize: 4 }}
>
Error parsing Body JSON
</span>
);
}
// Show JSON tree if available, otherwise show HTML content
if (record.field === 'body' && treeData) {
return <MemoizedTree treeData={treeData} />;
}
if (record.field === 'body') {
return (
<CopyClipboardHOC entityKey="body" textToCopy={textToCopy}>
if (record.field === 'body' && isLoading) {
return (
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
<Spin size="small" />
<span style={{ color: Color.BG_SIENNA_400 }}>Processing JSON...</span>
</div>
);
}
if (record.field === 'body' && error) {
return (
<span
style={{ color: Color.BG_SIENNA_400, whiteSpace: 'pre-wrap', tabSize: 4 }}
>
<span dangerouslySetInnerHTML={bodyHtml} />
Error parsing Body JSON
</span>
</CopyClipboardHOC>
);
}
);
}
return null;
});
if (record.field === 'body') {
return (
<CopyClipboardHOC entityKey="body" textToCopy={textToCopy}>
<span
style={{ color: Color.BG_SIENNA_400, whiteSpace: 'pre-wrap', tabSize: 4 }}
>
<span dangerouslySetInnerHTML={bodyHtml} />
</span>
</CopyClipboardHOC>
);
}
return null;
},
);
BodyContent.displayName = 'BodyContent';
@@ -319,6 +324,7 @@ export default function TableViewActions(
record={record}
bodyHtml={bodyHtml}
textToCopy={textToCopy}
handleChangeSelectedView={handleChangeSelectedView}
/>
);
@@ -342,6 +348,7 @@ export default function TableViewActions(
fieldData,
bodyHtml,
textToCopy,
handleChangeSelectedView,
formatTimezoneAdjustedTimestamp,
cleanTimestamp,
]);
@@ -355,6 +362,7 @@ export default function TableViewActions(
record={record}
bodyHtml={bodyHtml}
textToCopy={textToCopy}
handleChangeSelectedView={handleChangeSelectedView}
/>
{!isListViewPanel &&
!RESTRICTED_SELECTED_FIELDS.includes(fieldFilterKey) && (

View File

@@ -1,5 +1,8 @@
import { useEffect, useRef, useState } from 'react';
import { FeatureKeys } from 'constants/features';
import { ChangeViewFunctionType } from 'container/ExplorerOptions/types';
import { isEmpty } from 'lodash-es';
import { useAppContext } from 'providers/App/App';
import { jsonToDataNodes, recursiveParseJSON } from '../utils';
@@ -9,6 +12,7 @@ const MAX_BODY_BYTES = 100 * 1024; // 100 KB
const useAsyncJSONProcessing = (
value: string,
shouldProcess: boolean,
handleChangeSelectedView?: ChangeViewFunctionType,
): {
isLoading: boolean;
treeData: any[] | null;
@@ -25,6 +29,10 @@ const useAsyncJSONProcessing = (
});
const processingRef = useRef<boolean>(false);
const { featureFlags } = useAppContext();
const isBodyJsonQueryEnabled =
featureFlags?.find((flag) => flag.name === FeatureKeys.BODY_JSON_ENABLED)
?.active || false;
// eslint-disable-next-line sonarjs/cognitive-complexity
useEffect((): (() => void) => {
@@ -47,7 +55,10 @@ const useAsyncJSONProcessing = (
try {
const parsedBody = recursiveParseJSON(value);
if (!isEmpty(parsedBody)) {
const treeData = jsonToDataNodes(parsedBody);
const treeData = jsonToDataNodes(parsedBody, {
isBodyJsonQueryEnabled,
handleChangeSelectedView,
});
setJsonState({ isLoading: false, treeData, error: null });
} else {
setJsonState({ isLoading: false, treeData: null, error: null });
@@ -73,7 +84,10 @@ const useAsyncJSONProcessing = (
try {
const parsedBody = recursiveParseJSON(value);
if (!isEmpty(parsedBody)) {
const treeData = jsonToDataNodes(parsedBody);
const treeData = jsonToDataNodes(parsedBody, {
isBodyJsonQueryEnabled,
handleChangeSelectedView,
});
setJsonState({ isLoading: false, treeData, error: null });
} else {
setJsonState({ isLoading: false, treeData: null, error: null });
@@ -101,7 +115,7 @@ const useAsyncJSONProcessing = (
return (): void => {
processingRef.current = false;
};
}, [value, shouldProcess]);
}, [value, shouldProcess, isBodyJsonQueryEnabled, handleChangeSelectedView]);
return jsonState;
};

View File

@@ -1,4 +1,5 @@
export const DROPDOWN_KEY = {
FILTER_IN: 'filterIn',
FILTER_OUT: 'filterOut',
GROUP_BY: 'groupBy',
};

View File

@@ -1,5 +1,6 @@
import Convert from 'ansi-to-html';
import type { DataNode } from 'antd/es/tree';
import { ChangeViewFunctionType } from 'container/ExplorerOptions/types';
import { MetricsType } from 'container/MetricsApplication/constant';
import dompurify from 'dompurify';
import { uniqueId } from 'lodash-es';
@@ -34,13 +35,32 @@ export const recursiveParseJSON = (obj: string): Record<string, unknown> => {
}
};
export const computeDataNode = (
key: string,
valueIsArray: boolean,
value: unknown,
nodeKey: string,
parentIsArray: boolean,
): DataNode => ({
type JsonToDataNodesOptions = {
parentKey?: string;
parentIsArray?: boolean;
isBodyJsonQueryEnabled?: boolean;
handleChangeSelectedView?: ChangeViewFunctionType;
};
type ComputeDataNodeOptions = {
key: string;
valueIsArray: boolean;
value: unknown;
nodeKey: string;
parentIsArray: boolean;
isBodyJsonQueryEnabled?: boolean;
handleChangeSelectedView?: ChangeViewFunctionType;
};
export const computeDataNode = ({
key,
valueIsArray,
value,
nodeKey,
parentIsArray,
isBodyJsonQueryEnabled = false,
handleChangeSelectedView,
}: ComputeDataNodeOptions): DataNode => ({
key: uniqueId(),
title: (
<BodyTitleRenderer
@@ -48,20 +68,30 @@ export const computeDataNode = (
nodeKey={nodeKey}
value={value}
parentIsArray={parentIsArray}
handleChangeSelectedView={handleChangeSelectedView}
/>
),
children: jsonToDataNodes(
value as Record<string, unknown>,
valueIsArray ? `${nodeKey}[*]` : nodeKey,
valueIsArray,
),
children: jsonToDataNodes(value as Record<string, unknown>, {
parentKey: valueIsArray
? `${nodeKey}${isBodyJsonQueryEnabled ? '[]' : '[*]'}`
: nodeKey,
parentIsArray: valueIsArray,
isBodyJsonQueryEnabled,
handleChangeSelectedView,
}),
});
export function jsonToDataNodes(
json: Record<string, unknown>,
parentKey = '',
parentIsArray = false,
options: JsonToDataNodesOptions = {},
): DataNode[] {
const {
parentKey = '',
parentIsArray = false,
isBodyJsonQueryEnabled = false,
handleChangeSelectedView,
} = options;
return Object.entries(json).map(([key, value]) => {
let nodeKey = parentKey || key;
if (parentIsArray) {
@@ -74,7 +104,15 @@ export function jsonToDataNodes(
if (parentIsArray) {
if (typeof value === 'object' && value !== null) {
return computeDataNode(key, valueIsArray, value, nodeKey, parentIsArray);
return computeDataNode({
key,
valueIsArray,
value,
nodeKey,
parentIsArray,
isBodyJsonQueryEnabled,
handleChangeSelectedView,
});
}
return {
@@ -85,14 +123,31 @@ export function jsonToDataNodes(
nodeKey={nodeKey}
value={value}
parentIsArray={parentIsArray}
handleChangeSelectedView={handleChangeSelectedView}
/>
),
children: jsonToDataNodes({}, nodeKey, valueIsArray),
children: jsonToDataNodes(
{},
{
parentKey: nodeKey,
parentIsArray: valueIsArray,
isBodyJsonQueryEnabled,
handleChangeSelectedView,
},
),
};
}
if (typeof value === 'object' && value !== null) {
return computeDataNode(key, valueIsArray, value, nodeKey, parentIsArray);
return computeDataNode({
key,
valueIsArray,
value,
nodeKey,
parentIsArray,
isBodyJsonQueryEnabled,
handleChangeSelectedView,
});
}
return {
key: uniqueId(),
@@ -102,6 +157,7 @@ export function jsonToDataNodes(
nodeKey={nodeKey}
value={value}
parentIsArray={parentIsArray}
handleChangeSelectedView={handleChangeSelectedView}
/>
),
};
@@ -123,6 +179,7 @@ export function flattenObject(obj: AnyObject, prefix = ''): AnyObject {
export const generateFieldKeyForArray = (
fieldKey: string,
dataType: DataTypes,
isBodyJsonQueryEnabled = false,
): string => {
let lastDotIndex = fieldKey.lastIndexOf('.');
let resultNodeKey = fieldKey;
@@ -138,6 +195,16 @@ export const generateFieldKeyForArray = (
newResultNodeKey = resultNodeKey.substring(0, lastDotIndex);
}
}
// When filtering for a value inside an array, the query builder expects the
// last array segment to be referenced without the trailing `[]`.
// Examples:
// - has(body.config.features, 'fast_checkout')
// - has(body.config.features[].items, 'pen')
// - has(body.config.features[].items[].variants, 'ballpen')
if (isBodyJsonQueryEnabled && newResultNodeKey.endsWith('[]')) {
newResultNodeKey = newResultNodeKey.slice(0, -2);
}
return `body.${newResultNodeKey}`;
};

View File

@@ -160,55 +160,6 @@
}
}
.lightMode {
.login-form-card {
background: var(--l2-background);
}
.login-error-container {
.error-content {
background: color-mix(in srgb, var(--danger-background) 10%, transparent);
border-color: color-mix(in srgb, var(--danger-background) 20%, transparent);
&__error-code {
color: var(--l2-foreground);
}
&__error-message {
color: var(--l1-foreground);
}
&__docs-button {
color: var(--l1-foreground);
border-color: var(--l1-border);
background: transparent;
&:hover {
color: var(--l2-foreground);
border-color: var(--l1-border);
background: transparent;
}
}
&__message-badge-label-text {
color: var(--l2-foreground);
}
&__message-item {
color: var(--l1-foreground);
&::before {
background: var(--l3-background);
}
}
&__scroll-hint-text {
color: var(--l2-foreground);
}
}
}
}
.password-label-container {
display: flex;
justify-content: space-between;

View File

@@ -10,10 +10,6 @@ export const Label = styled.label`
color: var(--l1-foreground);
margin-bottom: 12px;
display: block;
.lightMode & {
color: var(--text-ink-500);
}
`;
export const FormContainer = styled(Form)`
@@ -50,20 +46,10 @@ export const FormContainer = styled(Form)`
background: var(--l3-background) !important;
border-color: var(--l1-border) !important;
color: var(--l1-foreground) !important;
.lightMode & {
background: var(--bg-vanilla-200) !important;
border-color: var(--bg-vanilla-300) !important;
color: var(--text-ink-500) !important;
}
}
& .ant-input::placeholder {
color: var(--l3-foreground) !important;
.lightMode & {
color: var(--text-neutral-light-200) !important;
}
}
& .ant-input:focus,

View File

@@ -323,50 +323,3 @@
}
}
}
.lightMode {
.logs-list-view-container {
.logs-list-table-view-container {
table {
thead {
tr {
th {
color: var(--l1-foreground) !important;
}
border-bottom: 1px solid var(--l1-border) !important;
}
border-bottom: 1px solid var(--l1-border) !important;
background-color: var(--l1-background) !important;
tr {
&:hover {
background-color: var(--l3-background) !important;
}
}
}
tbody {
tr {
&:hover {
background-color: var(--l3-background) !important;
}
border-bottom: 1px solid var(--l1-border) !important;
}
}
}
.sticky-header-table-container {
&::-webkit-scrollbar-thumb {
background: var(--l3-background);
}
&::-webkit-scrollbar-thumb:hover {
background: var(--l1-background);
}
}
}
}
}

View File

@@ -263,24 +263,6 @@
}
}
.lightMode {
.logs-explorer-views-container {
.views-tabs-container {
.views-tabs {
.selected_view {
background: white;
color: var(--text-robin-400);
border: 1px solid var(--bg-robin-400);
}
.selected_view::before {
background: var(--bg-robin-400);
}
}
}
}
}
.order-by-container {
display: flex;
align-items: center;

View File

@@ -0,0 +1,83 @@
.mcp-auth-card {
display: flex;
flex-direction: column;
gap: var(--spacing-8);
padding: var(--padding-4) var(--padding-5);
border: 1px solid var(--l2-border);
border-radius: 8px;
background: var(--l2-background);
&__title {
display: flex;
align-items: center;
gap: var(--spacing-5);
font-size: var(--label-medium-500-font-size);
font-weight: var(--label-medium-500-font-weight);
color: var(--l1-foreground);
margin: 0;
}
&__description {
font-size: var(--font-size-xs);
color: var(--foreground);
line-height: var(--line-height-18);
margin: 0;
}
&__field {
display: flex;
flex-direction: column;
gap: var(--spacing-3);
}
&__field-label {
font-size: var(--font-size-xs);
font-weight: 500;
color: var(--foreground);
letter-spacing: 0.01em;
}
&__endpoint-value {
display: inline-flex;
align-items: center;
gap: var(--spacing-2);
padding: var(--padding-2) var(--padding-3);
border: 1px solid var(--l3-border);
border-radius: 6px;
background: var(--l3-background);
font-family: var(--font-family-sf-mono, monospace);
font-size: var(--paragraph-base-400-font-size);
color: var(--l1-foreground);
width: 100%;
justify-content: space-between;
}
&__cta-row {
display: flex;
align-items: center;
gap: var(--spacing-10);
flex-wrap: wrap;
}
&__helper-text {
font-size: var(--paragraph-small-400-font-size);
color: var(--foreground);
line-height: var(--paragraph-small-400-line-height);
}
&__info-banner {
display: flex;
align-items: flex-start;
gap: var(--spacing-2);
padding: var(--padding-2) var(--padding-3);
border-left: 3px solid var(--bg-robin-400);
background: var(--l3-background);
border-radius: 4px;
svg {
flex-shrink: 0;
color: var(--bg-robin-400);
margin-top: 2px;
}
}
}

View File

@@ -0,0 +1,70 @@
import { render, screen, userEvent } from 'tests/test-utils';
import AuthCard from './AuthCard';
const mockOnCopyInstanceUrl = jest.fn();
const mockOnCreateServiceAccount = jest.fn();
const defaultProps = {
instanceUrl: 'http://localhost',
onCopyInstanceUrl: mockOnCopyInstanceUrl,
onCreateServiceAccount: mockOnCreateServiceAccount,
};
describe('AuthCard', () => {
afterEach(() => {
jest.clearAllMocks();
});
it('renders the instance URL', () => {
render(<AuthCard {...defaultProps} isAdmin />);
expect(screen.getByTestId('mcp-instance-url')).toHaveTextContent(
'http://localhost',
);
});
it('shows Create Service Account button for admin', () => {
render(<AuthCard {...defaultProps} isAdmin />);
expect(screen.getByText('Create service account')).toBeInTheDocument();
expect(
screen.queryByText(
'Only admins can create API keys. Ask your workspace admin for a key with read access, then paste it into the API Key field.',
),
).not.toBeInTheDocument();
});
it('shows info banner for non-admin', () => {
render(<AuthCard {...defaultProps} isAdmin={false} />);
expect(
screen.getByText(
'Only admins can create API keys. Ask your workspace admin for a key with read access, then paste it into the API Key field.',
),
).toBeInTheDocument();
expect(screen.queryByText('Create service account')).not.toBeInTheDocument();
});
it('calls onCopyInstanceUrl when copy button is clicked', async () => {
const user = userEvent.setup({ pointerEventsCheck: 0 });
render(<AuthCard {...defaultProps} isAdmin />);
await user.click(
screen.getByRole('button', { name: 'Copy SigNoz instance URL' }),
);
expect(mockOnCopyInstanceUrl).toHaveBeenCalledTimes(1);
});
it('calls onCreateServiceAccount when admin clicks the CTA', async () => {
const user = userEvent.setup({ pointerEventsCheck: 0 });
render(<AuthCard {...defaultProps} isAdmin />);
await user.click(screen.getByText('Create service account'));
expect(mockOnCreateServiceAccount).toHaveBeenCalledTimes(1);
});
});

View File

@@ -0,0 +1,75 @@
import { Badge, Button } from '@signozhq/ui';
import { Info, KeyRound } from '@signozhq/icons';
import CopyIconButton from '../CopyIconButton';
import './AuthCard.styles.scss';
interface AuthCardProps {
isAdmin: boolean;
instanceUrl: string;
onCopyInstanceUrl: () => void;
onCreateServiceAccount: () => void;
}
function AuthCard({
isAdmin,
instanceUrl,
onCopyInstanceUrl,
onCreateServiceAccount,
}: AuthCardProps): JSX.Element {
return (
<section className="mcp-auth-card">
<h3 className="mcp-auth-card__title">
<Badge color="secondary" variant="default">
2
</Badge>
Authenticate from your client
</h3>
<p className="mcp-auth-card__description">
On first connect, your client opens a SigNoz authorization page asking for
two values:
</p>
<div className="mcp-auth-card__field">
<span className="mcp-auth-card__field-label">SigNoz Instance URL</span>
<div className="mcp-auth-card__endpoint-value">
<span data-testid="mcp-instance-url">{instanceUrl}</span>
<CopyIconButton
ariaLabel="Copy SigNoz instance URL"
onCopy={onCopyInstanceUrl}
/>
</div>
</div>
<div className="mcp-auth-card__field">
<span className="mcp-auth-card__field-label">API Key</span>
{isAdmin ? (
<div className="mcp-auth-card__cta-row">
<Button
variant="solid"
color="primary"
prefix={<KeyRound size={14} />}
onClick={onCreateServiceAccount}
>
Create service account
</Button>
<span className="mcp-auth-card__helper-text">
Create a service account, then add a new key inside it - paste that key
into the API Key field.
</span>
</div>
) : (
<div className="mcp-auth-card__info-banner">
<Info size={14} />
<span className="mcp-auth-card__helper-text">
Only admins can create API keys. Ask your workspace admin for a key with
read access, then paste it into the API Key field.
</span>
</div>
)}
</div>
</section>
);
}
export default AuthCard;

View File

@@ -0,0 +1,66 @@
.mcp-client-tabs-root {
button[data-variant='primary'] {
border: none;
background: transparent;
box-shadow: none;
}
// Remove default tab content padding/margin — the card provides spacing.
--tab-content-padding: 0;
--tab-content-margin: var(--spacing-4) 0 0;
}
.mcp-client-tabs {
&__snippet-wrapper {
display: flex;
flex-direction: column;
gap: var(--spacing-6);
margin-top: var(--spacing-2);
.learn-more {
width: fit-content;
font-size: var(--font-size-xs);
}
}
&__install-row {
display: flex;
align-items: center;
gap: var(--spacing-10);
flex-wrap: wrap;
}
&__helper-text {
font-size: var(--paragraph-small-400-font-size);
color: var(--foreground);
line-height: var(--paragraph-small-400-line-height);
}
&__endpoint-value {
display: inline-flex;
align-items: center;
gap: var(--spacing-2);
padding: var(--padding-2) var(--padding-3);
border: 1px solid var(--l3-border);
border-radius: 6px;
background: var(--l3-background);
font-family: var(--font-family-sf-mono, monospace);
font-size: var(--paragraph-base-400-font-size);
color: var(--l1-foreground);
width: 100%;
justify-content: space-between;
}
&__snippet-pre {
margin: 0;
white-space: pre-wrap;
word-break: break-all;
}
&__instructions {
font-size: var(--font-size-xs);
color: var(--foreground);
line-height: var(--line-height-20);
margin: 0;
}
}

View File

@@ -0,0 +1,108 @@
import { render, screen, userEvent } from 'tests/test-utils';
import ClientTabs from './ClientTabs';
import { MCP_CLIENTS } from '../clients';
jest.mock('utils/navigation', () => ({
openInNewTab: jest.fn(),
}));
const mockOnTabChange = jest.fn();
const mockOnCopySnippet = jest.fn();
const mockOnInstallClick = jest.fn();
const mockOnDocsLinkClick = jest.fn();
const MCP_ENDPOINT = 'https://mcp.us.signoz.cloud/mcp';
const defaultProps = {
endpoint: MCP_ENDPOINT,
activeTab: MCP_CLIENTS[0].key,
onTabChange: mockOnTabChange,
onCopySnippet: mockOnCopySnippet,
onInstallClick: mockOnInstallClick,
onDocsLinkClick: mockOnDocsLinkClick,
};
describe('ClientTabs', () => {
afterEach(() => {
jest.clearAllMocks();
});
it('renders a tab for each MCP client', () => {
render(<ClientTabs {...defaultProps} />);
MCP_CLIENTS.forEach((client) => {
expect(screen.getByText(client.label)).toBeInTheDocument();
});
});
it('renders the snippet for clients that provide one (Cursor)', () => {
render(<ClientTabs {...defaultProps} activeTab="cursor" />);
// The snippet is rendered inside a <pre> element; check its content
const snippetPre = document.querySelector('.mcp-client-tabs__snippet-pre');
expect(snippetPre).toBeInTheDocument();
expect(snippetPre?.textContent).toContain(MCP_ENDPOINT);
expect(snippetPre?.textContent).toContain('mcpServers');
});
it('renders endpoint block and instructions text for clients without a snippet (Claude Desktop)', () => {
render(<ClientTabs {...defaultProps} activeTab="claude-desktop" />);
const snippetPre = document.querySelector('.mcp-client-tabs__snippet-pre');
expect(snippetPre?.textContent).toBe(MCP_ENDPOINT);
expect(
screen.getByText(
'Open Claude Desktop, go to Settings → Connectors → Add custom connector, and paste the endpoint URL above. Claude Desktop does not read remote MCP servers from claude_desktop_config.json - the connector UI is the only supported path.',
),
).toBeInTheDocument();
});
it('shows enabled install button when endpoint is set (Cursor)', () => {
render(<ClientTabs {...defaultProps} activeTab="cursor" />);
const installBtn = screen.getByRole('button', {
name: 'Add to Cursor',
});
expect(installBtn).toBeEnabled();
});
it('shows disabled install button when endpoint is missing (Cursor)', () => {
render(<ClientTabs {...defaultProps} endpoint="" activeTab="cursor" />);
const installBtn = screen.getByRole('button', {
name: 'Add to Cursor',
});
expect(installBtn).toBeDisabled();
});
it('calls onCopySnippet with client key and snippet on copy', async () => {
const user = userEvent.setup({ pointerEventsCheck: 0 });
const cursorClient = MCP_CLIENTS.find((c) => c.key === 'cursor')!;
render(<ClientTabs {...defaultProps} activeTab="cursor" />);
await user.click(
screen.getByRole('button', {
name: `Copy ${cursorClient.label} config`,
}),
);
expect(mockOnCopySnippet).toHaveBeenCalledWith(
'cursor',
cursorClient.snippet!(MCP_ENDPOINT),
);
});
it('copy button is disabled when no endpoint', () => {
render(<ClientTabs {...defaultProps} endpoint="" activeTab="cursor" />);
const cursorClient = MCP_CLIENTS.find((c) => c.key === 'cursor')!;
const copyBtn = screen.getByRole('button', {
name: `Copy ${cursorClient.label} config`,
});
expect(copyBtn).toBeDisabled();
});
});

View File

@@ -0,0 +1,126 @@
import { useMemo } from 'react';
import { Button, Tabs } from '@signozhq/ui';
import LearnMore from 'components/LearnMore/LearnMore';
import { Download } from '@signozhq/icons';
import { openInNewTab } from 'utils/navigation';
import CopyIconButton from '../CopyIconButton';
import { docsUrl, MCP_CLIENTS, McpClient } from '../clients';
import './ClientTabs.styles.scss';
const ENDPOINT_PLACEHOLDER = 'https://mcp.<region>.signoz.cloud/mcp';
interface ClientTabsProps {
endpoint: string;
activeTab: string;
onTabChange: (key: string) => void;
onCopySnippet: (clientKey: string, snippet: string) => void;
onInstallClick: (clientKey: string) => void;
onDocsLinkClick: (target: string) => void;
}
function ClientTabs({
endpoint,
activeTab,
onTabChange,
onCopySnippet,
onInstallClick,
onDocsLinkClick,
}: ClientTabsProps): JSX.Element {
const items = useMemo(
() =>
MCP_CLIENTS.map((client: McpClient) => {
const snippet = client.snippet
? client.snippet(endpoint || ENDPOINT_PLACEHOLDER)
: null;
const installHref =
client.installUrl && endpoint ? client.installUrl(endpoint) : null;
const installLabel = client.installLabel ?? `Add to ${client.label}`;
return {
key: client.key,
label: client.label,
children: (
<div className="mcp-client-tabs__snippet-wrapper">
{snippet !== null ? (
<div className="mcp-client-tabs__endpoint-value mcp-client-tabs__snippet">
<pre className="mcp-client-tabs__snippet-pre">{snippet}</pre>
<CopyIconButton
ariaLabel={`Copy ${client.label} config`}
disabled={!endpoint}
onCopy={(): void => onCopySnippet(client.key, snippet)}
/>
</div>
) : (
<>
<div className="mcp-client-tabs__endpoint-value mcp-client-tabs__snippet">
<pre className="mcp-client-tabs__snippet-pre">
{endpoint || ENDPOINT_PLACEHOLDER}
</pre>
<CopyIconButton
ariaLabel="Copy MCP endpoint"
disabled={!endpoint}
onCopy={(): void => onCopySnippet(client.key, endpoint)}
/>
</div>
<p className="mcp-client-tabs__instructions">
{client.instructions ?? ''}
</p>
</>
)}
{client.installUrl && (
<div className="mcp-client-tabs__install-row">
{installHref ? (
<Button
variant="solid"
color="primary"
prefix={<Download size={14} />}
onClick={(): void => {
onInstallClick(client.key);
openInNewTab(installHref);
}}
>
{installLabel}
</Button>
) : (
<Button
variant="solid"
color="primary"
disabled
prefix={<Download size={14} />}
>
{installLabel}
</Button>
)}
<span className="mcp-client-tabs__helper-text">
Or copy the config below for manual setup.
</span>
</div>
)}
<LearnMore
text={`${client.label} setup docs`}
url={docsUrl(client.docsPath)}
onClick={(): void => onDocsLinkClick(`client-${client.key}`)}
/>
</div>
),
};
}),
// eslint-disable-next-line react-hooks/exhaustive-deps
[endpoint, onCopySnippet, onInstallClick, onDocsLinkClick],
);
return (
<Tabs
className="mcp-client-tabs-root"
value={activeTab}
onChange={onTabChange}
items={items}
/>
);
}
export default ClientTabs;

View File

@@ -0,0 +1,5 @@
.mcp-copy-btn {
&:hover {
background-color: var(--l3-background-hover) !important;
}
}

View File

@@ -0,0 +1,40 @@
import { Button, Tooltip, TooltipProvider } from '@signozhq/ui';
import { Copy } from '@signozhq/icons';
import './CopyIconButton.styles.scss';
interface CopyIconButtonProps {
ariaLabel: string;
onCopy: () => void;
disabled?: boolean;
}
function CopyIconButton({
ariaLabel,
onCopy,
disabled = false,
}: CopyIconButtonProps): JSX.Element {
const tooltipTitle = disabled
? 'Enter your Cloud region first'
: 'Copy to clipboard';
return (
<TooltipProvider>
<Tooltip title={tooltipTitle}>
<span>
<Button
color="secondary"
variant="ghost"
size="icon"
aria-label={ariaLabel}
disabled={disabled}
className="mcp-copy-btn"
prefix={<Copy size={14} />}
onClick={onCopy}
/>
</span>
</Tooltip>
</TooltipProvider>
);
}
export default CopyIconButton;

View File

@@ -0,0 +1,60 @@
.mcp-settings {
display: flex;
flex-direction: column;
gap: var(--spacing-12);
padding: var(--padding-8) var(--padding-2) var(--padding-6) var(--padding-4);
max-width: 880px;
margin: 0 auto;
width: 100%;
&__header {
display: flex;
flex-direction: column;
gap: var(--spacing-2);
}
&__header-title {
font-size: var(--label-large-500-font-size);
font-weight: var(--label-large-500-font-weight);
color: var(--l1-foreground);
letter-spacing: -0.09px;
line-height: var(--line-height-normal);
margin: 0;
}
&__header-subtitle {
font-size: var(--paragraph-base-400-font-size);
font-weight: var(--paragraph-base-400-font-weight);
color: var(--foreground);
letter-spacing: -0.07px;
line-height: var(--paragraph-base-400-line-height);
margin: 0;
}
}
.mcp-settings__card {
display: flex;
flex-direction: column;
gap: var(--spacing-8);
padding: var(--padding-4) var(--padding-5);
border: 1px solid var(--l2-border);
border-radius: 8px;
background: var(--l2-background);
&-title {
display: flex;
align-items: center;
gap: var(--spacing-5);
font-size: var(--label-medium-500-font-size);
font-weight: var(--label-medium-500-font-weight);
color: var(--l1-foreground);
margin: 0;
}
&-description {
font-size: var(--font-size-xs);
color: var(--foreground);
line-height: var(--line-height-18);
margin: 0;
}
}

View File

@@ -0,0 +1,161 @@
import { render, screen, userEvent } from 'tests/test-utils';
import MCPServerSettings from './MCPServerSettings';
const mockLogEvent = jest.fn();
const mockCopyToClipboard = jest.fn();
const mockHistoryPush = jest.fn();
const mockUseGetGlobalConfig = jest.fn();
const mockToastSuccess = jest.fn();
const mockToastWarning = jest.fn();
jest.mock('api/common/logEvent', () => ({
__esModule: true,
default: (...args: unknown[]): unknown => mockLogEvent(...args),
}));
jest.mock('api/generated/services/global', () => ({
useGetGlobalConfig: (...args: unknown[]): unknown =>
mockUseGetGlobalConfig(...args),
}));
jest.mock('react-use', () => ({
__esModule: true,
useCopyToClipboard: (): [unknown, jest.Mock] => [null, mockCopyToClipboard],
}));
jest.mock('@signozhq/ui', () => ({
...jest.requireActual('@signozhq/ui'),
toast: {
success: (...args: unknown[]): unknown => mockToastSuccess(...args),
warning: (...args: unknown[]): unknown => mockToastWarning(...args),
},
}));
jest.mock('lib/history', () => ({
__esModule: true,
default: {
push: (...args: unknown[]): unknown => mockHistoryPush(...args),
location: { pathname: '/', search: '', hash: '', state: null },
},
}));
jest.mock('utils/basePath', () => ({
getBaseUrl: (): string => 'http://localhost',
getBasePath: (): string => '/',
withBasePath: (p: string): string => p,
}));
const MCP_URL = 'https://mcp.us.signoz.cloud/mcp';
function setupGlobalConfig({ mcpUrl }: { mcpUrl: string | null }): void {
mockUseGetGlobalConfig.mockReturnValue({
data: { data: { mcp_url: mcpUrl, ingestion_url: '' }, status: 'success' },
isLoading: false,
});
}
describe('MCPServerSettings', () => {
afterEach(() => {
jest.clearAllMocks();
});
it('shows loading spinner while config is loading', () => {
mockUseGetGlobalConfig.mockReturnValue({
data: undefined,
isLoading: true,
});
render(<MCPServerSettings />);
expect(document.querySelector('.ant-spin-spinning')).toBeInTheDocument();
expect(screen.queryByTestId('mcp-settings')).not.toBeInTheDocument();
});
it('shows fallback page when mcp_url is not configured', () => {
setupGlobalConfig({ mcpUrl: null });
render(<MCPServerSettings />);
expect(
screen.getByText('MCP Server is available on SigNoz'),
).toBeInTheDocument();
expect(screen.queryByTestId('mcp-settings')).not.toBeInTheDocument();
});
it('renders main settings page when mcp_url is present', () => {
setupGlobalConfig({ mcpUrl: MCP_URL });
render(<MCPServerSettings />);
expect(screen.getByTestId('mcp-settings')).toBeInTheDocument();
expect(screen.getByText('SigNoz MCP Server')).toBeInTheDocument();
expect(screen.getByText('Configure your client')).toBeInTheDocument();
expect(screen.getByText('Authenticate from your client')).toBeInTheDocument();
});
it('fires PAGE_VIEWED analytics event on mount', () => {
setupGlobalConfig({ mcpUrl: MCP_URL });
render(<MCPServerSettings />, undefined, { role: 'ADMIN' });
expect(mockLogEvent).toHaveBeenCalledWith('MCP Settings: Page viewed', {
role: 'ADMIN',
});
});
it('admin sees the Create Service Account CTA', () => {
setupGlobalConfig({ mcpUrl: MCP_URL });
render(<MCPServerSettings />, undefined, { role: 'ADMIN' });
expect(screen.getByText('Create service account')).toBeInTheDocument();
expect(
screen.queryByText(
'Only admins can create API keys. Ask your workspace admin for a key with read access, then paste it into the API Key field.',
),
).not.toBeInTheDocument();
});
it('non-admin sees an info banner instead of the CTA', () => {
setupGlobalConfig({ mcpUrl: MCP_URL });
render(<MCPServerSettings />, undefined, { role: 'VIEWER' });
expect(
screen.getByText(
'Only admins can create API keys. Ask your workspace admin for a key with read access, then paste it into the API Key field.',
),
).toBeInTheDocument();
expect(screen.queryByText('Create service account')).not.toBeInTheDocument();
});
it('navigates to service accounts when admin clicks Create CTA', async () => {
setupGlobalConfig({ mcpUrl: MCP_URL });
const user = userEvent.setup({ pointerEventsCheck: 0 });
render(<MCPServerSettings />, undefined, { role: 'ADMIN' });
await user.click(screen.getByText('Create service account'));
expect(mockHistoryPush).toHaveBeenCalledWith(
'/settings/service-accounts?create-sa=true',
);
});
it('copies instance URL and shows success toast', async () => {
setupGlobalConfig({ mcpUrl: MCP_URL });
const user = userEvent.setup({ pointerEventsCheck: 0 });
render(<MCPServerSettings />);
await user.click(
screen.getByRole('button', { name: 'Copy SigNoz instance URL' }),
);
expect(mockCopyToClipboard).toHaveBeenCalledWith('http://localhost');
expect(mockToastSuccess).toHaveBeenCalledWith(
'Instance URL copied to clipboard',
);
});
});

View File

@@ -0,0 +1,144 @@
import { useCallback, useEffect, useState } from 'react';
import { useCopyToClipboard } from 'react-use';
import logEvent from 'api/common/logEvent';
import ROUTES from 'constants/routes';
import { SA_QUERY_PARAMS } from 'container/ServiceAccountsSettings/constants';
import { useGetGlobalConfig } from 'api/generated/services/global';
import history from 'lib/history';
import { useAppContext } from 'providers/App/App';
import { USER_ROLES } from 'types/roles';
import { getBaseUrl } from 'utils/basePath';
import { Badge, toast } from '@signozhq/ui';
import Spinner from 'components/Spinner';
import AuthCard from './AuthCard/AuthCard';
import ClientTabs from './ClientTabs/ClientTabs';
import NotCloudFallback from './NotCloudFallback/NotCloudFallback';
import UseCasesCard from './UseCasesCard/UseCasesCard';
import { MCP_CLIENTS } from './clients';
import './MCPServerSettings.styles.scss';
const ANALYTICS = {
PAGE_VIEWED: 'MCP Settings: Page viewed',
CREATE_SA_CLICKED: 'MCP Settings: Create service account clicked',
CLIENT_TAB_SELECTED: 'MCP Settings: Client tab selected',
SNIPPET_COPIED: 'MCP Settings: Client snippet copied',
ONE_CLICK_INSTALL_CLICKED: 'MCP Settings: One-click install clicked',
INSTANCE_URL_COPIED: 'MCP Settings: Instance URL copied',
DOCS_LINK_CLICKED: 'MCP Settings: Docs link clicked',
} as const;
function MCPServerSettings(): JSX.Element {
const { user } = useAppContext();
const [, copyToClipboard] = useCopyToClipboard();
const isAdmin = user.role === USER_ROLES.ADMIN;
const instanceUrl = getBaseUrl();
const { data: globalConfig, isLoading: isConfigLoading } =
useGetGlobalConfig();
const endpoint = globalConfig?.data?.mcp_url ?? '';
const [activeTab, setActiveTab] = useState<string>(MCP_CLIENTS[0]?.key ?? '');
useEffect(() => {
void logEvent(ANALYTICS.PAGE_VIEWED, {
role: user.role,
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const handleCopySnippet = useCallback(
(clientKey: string, snippet: string) => {
if (!endpoint) {
toast.warning('Enter your Cloud region before copying');
return;
}
copyToClipboard(snippet);
toast.success('Snippet copied to clipboard');
void logEvent(ANALYTICS.SNIPPET_COPIED, { client: clientKey });
},
[endpoint, copyToClipboard],
);
const handleCreateServiceAccount = useCallback(() => {
void logEvent(ANALYTICS.CREATE_SA_CLICKED, {});
history.push(
`${ROUTES.SERVICE_ACCOUNTS_SETTINGS}?${SA_QUERY_PARAMS.CREATE_SA}=true`,
);
}, []);
const handleCopyInstanceUrl = useCallback(() => {
copyToClipboard(instanceUrl);
toast.success('Instance URL copied to clipboard');
void logEvent(ANALYTICS.INSTANCE_URL_COPIED, {});
}, [copyToClipboard, instanceUrl]);
const handleDocsLinkClick = useCallback((target: string) => {
void logEvent(ANALYTICS.DOCS_LINK_CLICKED, { target });
}, []);
const handleInstallClick = useCallback((clientKey: string) => {
void logEvent(ANALYTICS.ONE_CLICK_INSTALL_CLICKED, { client: clientKey });
}, []);
const handleTabChange = useCallback((key: string) => {
setActiveTab(key);
void logEvent(ANALYTICS.CLIENT_TAB_SELECTED, { client: key });
}, []);
if (isConfigLoading) {
return <Spinner tip="Loading..." height="70vh" />;
}
if (!endpoint) {
return <NotCloudFallback />;
}
return (
<div className="mcp-settings" data-testid="mcp-settings">
<header className="mcp-settings__header">
<h1 className="mcp-settings__header-title">SigNoz MCP Server</h1>
<p className="mcp-settings__header-subtitle">
Connect AI assistants like Claude, Cursor, VS Code, and Codex to your
SigNoz data via the Model Context Protocol. Authenticate from your MCP
client with a service-account API key.
</p>
</header>
<section className="mcp-settings__card">
<h3 className="mcp-settings__card-title">
<Badge color="secondary" variant="default">
1
</Badge>
Configure your client
</h3>
<p className="mcp-settings__card-description">
Add SigNoz to your MCP client. Use a one-click install where available, or
copy the config for manual setup. On first connect, the client will open a
SigNoz authorization page - use the instance URL and API key from step 2.
</p>
<ClientTabs
endpoint={endpoint}
activeTab={activeTab}
onTabChange={handleTabChange}
onCopySnippet={handleCopySnippet}
onInstallClick={handleInstallClick}
onDocsLinkClick={handleDocsLinkClick}
/>
</section>
<AuthCard
isAdmin={isAdmin}
instanceUrl={instanceUrl}
onCopyInstanceUrl={handleCopyInstanceUrl}
onCreateServiceAccount={handleCreateServiceAccount}
/>
<UseCasesCard onDocsLinkClick={handleDocsLinkClick} />
</div>
);
}
export default MCPServerSettings;

View File

@@ -0,0 +1,41 @@
.not-cloud-fallback {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
min-height: 60vh;
&__content {
display: flex;
flex-direction: column;
gap: var(--spacing-6);
padding: var(--padding-6);
border: 1px dashed var(--l2-border);
border-radius: 8px;
background: var(--l2-background);
max-width: 50%;
.learn-more {
width: fit-content;
font-size: var(--font-size-xs);
}
}
&__title {
display: flex;
align-items: center;
gap: var(--spacing-2);
font-size: var(--label-large-500-font-size);
font-weight: var(--label-large-500-font-weight);
color: var(--l1-foreground);
margin: 0;
}
&__body {
font-size: var(--paragraph-base-400-font-size);
font-weight: var(--paragraph-base-400-font-weight);
color: var(--foreground);
line-height: var(--paragraph-base-400-line-height);
margin: 0;
}
}

View File

@@ -0,0 +1,35 @@
import { useCallback } from 'react';
import logEvent from 'api/common/logEvent';
import LearnMore from 'components/LearnMore/LearnMore';
import './NotCloudFallback.styles.scss';
import { MCP_DOCS_URL } from '../clients';
const DOCS_LINK_EVENT = 'MCP Settings: Docs link clicked';
function NotCloudFallback(): JSX.Element {
const handleDocsClick = useCallback(() => {
void logEvent(DOCS_LINK_EVENT, { target: 'fallback' });
}, []);
return (
<div className="not-cloud-fallback">
<div className="not-cloud-fallback__content">
<h2 className="not-cloud-fallback__title">
MCP Server is available on SigNoz
</h2>
<p className="not-cloud-fallback__body">
Users can follow the docs to setup the MCP server against their SigNoz
instance.
</p>
<LearnMore
text="View MCP Server docs"
url={MCP_DOCS_URL}
onClick={handleDocsClick}
/>
</div>
</div>
);
}
export default NotCloudFallback;

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