Compare commits

..

23 Commits

Author SHA1 Message Date
Naman Verma
79143be510 Revert "Merge branch 'nv/v2-public-dashboard' into nv/dashboard-create-aggregation"
This reverts commit e91785bfe3, reversing
changes made to fe838a3464.
2026-06-30 21:48:14 +05:30
Naman Verma
e91785bfe3 Merge branch 'nv/v2-public-dashboard' into nv/dashboard-create-aggregation 2026-06-30 21:41:37 +05:30
Naman Verma
fe838a3464 Merge branch 'main' into nv/dashboard-create-aggregation 2026-06-30 21:40:48 +05:30
Naman Verma
ff22facdd6 Merge branch 'main' into nv/v2-public-dashboard 2026-06-30 21:40:36 +05:30
Naman Verma
875432d7ec test: add integration test 2026-06-30 21:34:17 +05:30
Naman Verma
5f1e7a3a53 chore: remove unused err type 2026-06-30 19:15:31 +05:30
Naman Verma
6a4629a418 fix: handle multi aggregations in public dashboard panels 2026-06-30 19:03:50 +05:30
Naman Verma
72a6ca6516 Merge branch 'main' into nv/v2-public-dashboard 2026-06-25 20:07:16 +05:30
Naman Verma
bbd5cc380e Merge branch 'main' into nv/v2-public-dashboard 2026-06-25 02:02:16 +05:30
Naman Verma
6cd9b5bbd6 Merge branch 'main' into nv/v2-public-dashboard 2026-06-18 15:37:15 +05:30
Naman Verma
13f6c232a1 Merge branch 'main' into nv/v2-public-dashboard 2026-06-18 11:47:24 +05:30
Naman Verma
dac1489294 Merge branch 'main' into nv/v2-public-dashboard 2026-06-17 07:29:31 +05:30
Naman Verma
1d98e9ebf6 Merge branch 'main' into nv/v2-public-dashboard 2026-06-16 12:35:00 +05:30
Naman Verma
15e99e43ff test: add integration tests for new v2 public apis 2026-06-16 12:21:50 +05:30
Naman Verma
8c766f8c10 fix: generate api specs 2026-06-16 11:45:22 +05:30
Naman Verma
99b32f00b9 fix: add fill gaps to query 2026-06-16 02:30:59 +05:30
Naman Verma
76f8646c69 test: unit tests for GetPanelQuery 2026-06-16 02:18:01 +05:30
Naman Verma
28c00e298a chore: rename method name 2026-06-16 02:01:40 +05:30
Naman Verma
4592b12256 fix: remove fields that v1 also removes when redacting 2026-06-16 01:53:33 +05:30
Naman Verma
b990d40c5f chore: trim comments 2026-06-16 00:23:41 +05:30
Naman Verma
95a0d7c035 fix: fill fields that were in the data blob in v1 2026-06-15 23:04:21 +05:30
Naman Verma
e678728c61 fix: remove duplicate call to GetDashboardByPublicIDV2 in GetPublicWidgetQueryRangeV2 2026-06-15 22:31:45 +05:30
Naman Verma
42d3e7e0e4 feat: add first draft of v2 public dashboard apis 2026-06-15 15:00:44 +05:30
27 changed files with 572 additions and 634 deletions

View File

@@ -177,11 +177,9 @@ func runServer(ctx context.Context, config signoz.Config, logger *slog.Logger) e
return nil, err
}
azureCloudProviderModule := implcloudprovider.NewAzureCloudProvider(defStore)
gcpCloudProviderModule := implcloudprovider.NewGCPCloudProvider(defStore)
cloudProvidersMap := map[cloudintegrationtypes.CloudProviderType]cloudintegration.CloudProviderModule{
cloudintegrationtypes.CloudProviderTypeAWS: awsCloudProviderModule,
cloudintegrationtypes.CloudProviderTypeAzure: azureCloudProviderModule,
cloudintegrationtypes.CloudProviderTypeGCP: gcpCloudProviderModule,
}
return implcloudintegration.NewModule(pkgcloudintegration.NewStore(sqlStore), dashboardModule, global, zeus, gateway, licensing, serviceAccount, cloudProvidersMap, config)

View File

@@ -1024,8 +1024,6 @@ components:
$ref: '#/components/schemas/CloudintegrationtypesAWSAccountConfig'
azure:
$ref: '#/components/schemas/CloudintegrationtypesAzureAccountConfig'
gcp:
$ref: '#/components/schemas/CloudintegrationtypesGCPAccountConfig'
type: object
CloudintegrationtypesAgentReport:
nullable: true
@@ -1171,8 +1169,6 @@ components:
$ref: '#/components/schemas/CloudintegrationtypesAWSConnectionArtifact'
azure:
$ref: '#/components/schemas/CloudintegrationtypesAzureConnectionArtifact'
gcp:
$ref: '#/components/schemas/CloudintegrationtypesGCPConnectionArtifact'
type: object
CloudintegrationtypesCredentials:
properties:
@@ -1203,46 +1199,6 @@ components:
nullable: true
type: array
type: object
CloudintegrationtypesGCPAccountConfig:
properties:
deploymentProjectId:
type: string
deploymentRegion:
type: string
projectIds:
items:
type: string
type: array
required:
- deploymentProjectId
- deploymentRegion
- projectIds
type: object
CloudintegrationtypesGCPConnectionArtifact:
type: object
CloudintegrationtypesGCPIntegrationConfig:
type: object
CloudintegrationtypesGCPServiceConfig:
properties:
logs:
$ref: '#/components/schemas/CloudintegrationtypesGCPServiceLogsConfig'
metrics:
$ref: '#/components/schemas/CloudintegrationtypesGCPServiceMetricsConfig'
type: object
CloudintegrationtypesGCPServiceLogsConfig:
properties:
enabled:
type: boolean
required:
- enabled
type: object
CloudintegrationtypesGCPServiceMetricsConfig:
properties:
enabled:
type: boolean
required:
- enabled
type: object
CloudintegrationtypesGettableAccountWithConnectionArtifact:
properties:
connectionArtifact:
@@ -1375,8 +1331,6 @@ components:
$ref: '#/components/schemas/CloudintegrationtypesAWSPostableAccountConfig'
azure:
$ref: '#/components/schemas/CloudintegrationtypesAzureAccountConfig'
gcp:
$ref: '#/components/schemas/CloudintegrationtypesGCPAccountConfig'
type: object
CloudintegrationtypesPostableAgentCheckIn:
properties:
@@ -1401,8 +1355,6 @@ components:
$ref: '#/components/schemas/CloudintegrationtypesAWSIntegrationConfig'
azure:
$ref: '#/components/schemas/CloudintegrationtypesAzureIntegrationConfig'
gcp:
$ref: '#/components/schemas/CloudintegrationtypesGCPIntegrationConfig'
type: object
CloudintegrationtypesService:
properties:
@@ -1447,8 +1399,6 @@ components:
$ref: '#/components/schemas/CloudintegrationtypesAWSServiceConfig'
azure:
$ref: '#/components/schemas/CloudintegrationtypesAzureServiceConfig'
gcp:
$ref: '#/components/schemas/CloudintegrationtypesGCPServiceConfig'
type: object
CloudintegrationtypesServiceDashboard:
properties:
@@ -1491,7 +1441,6 @@ components:
- cosmosdb
- cassandradb
- redis
- cloudsql
type: string
CloudintegrationtypesServiceMetadata:
properties:
@@ -1553,8 +1502,6 @@ components:
$ref: '#/components/schemas/CloudintegrationtypesAWSAccountConfig'
azure:
$ref: '#/components/schemas/CloudintegrationtypesUpdatableAzureAccountConfig'
gcp:
$ref: '#/components/schemas/CloudintegrationtypesUpdatableGCPAccountConfig'
type: object
CloudintegrationtypesUpdatableAzureAccountConfig:
properties:
@@ -1565,22 +1512,6 @@ components:
required:
- resourceGroups
type: object
CloudintegrationtypesUpdatableGCPAccountConfig:
properties:
deploymentProjectId:
type: string
deploymentRegion:
type: string
projectIds:
items:
type: string
nullable: true
type: array
required:
- deploymentProjectId
- deploymentRegion
- projectIds
type: object
CloudintegrationtypesUpdatableService:
properties:
config:

View File

@@ -1,36 +0,0 @@
package implcloudprovider
import (
"context"
"github.com/SigNoz/signoz/pkg/modules/cloudintegration"
"github.com/SigNoz/signoz/pkg/types/cloudintegrationtypes"
)
type gcpcloudprovider struct {
serviceDefinitions cloudintegrationtypes.ServiceDefinitionStore
}
func NewGCPCloudProvider(defStore cloudintegrationtypes.ServiceDefinitionStore) cloudintegration.CloudProviderModule {
return &gcpcloudprovider{
serviceDefinitions: defStore,
}
}
func (g *gcpcloudprovider) BuildIntegrationConfig(ctx context.Context, account *cloudintegrationtypes.Account, services []*cloudintegrationtypes.StorableCloudIntegrationService) (*cloudintegrationtypes.ProviderIntegrationConfig, error) {
// for manual flow we don't have any integration config to return, so returning empty config for now.
return &cloudintegrationtypes.ProviderIntegrationConfig{}, nil
}
func (g *gcpcloudprovider) GetConnectionArtifact(ctx context.Context, account *cloudintegrationtypes.Account, req *cloudintegrationtypes.GetConnectionArtifactRequest) (*cloudintegrationtypes.ConnectionArtifact, error) {
// for manual flow we don't have any connection artifact to return, so returning empty artifact for now.
return &cloudintegrationtypes.ConnectionArtifact{}, nil
}
func (g *gcpcloudprovider) GetServiceDefinition(ctx context.Context, serviceID cloudintegrationtypes.ServiceID) (*cloudintegrationtypes.ServiceDefinition, error) {
return g.serviceDefinitions.Get(ctx, cloudintegrationtypes.CloudProviderTypeGCP, serviceID)
}
func (g *gcpcloudprovider) ListServiceDefinitions(ctx context.Context) ([]*cloudintegrationtypes.ServiceDefinition, error) {
return g.serviceDefinitions.List(ctx, cloudintegrationtypes.CloudProviderTypeGCP)
}

View File

@@ -2630,25 +2630,9 @@ export interface CloudintegrationtypesAzureAccountConfigDTO {
resourceGroups: string[];
}
export interface CloudintegrationtypesGCPAccountConfigDTO {
/**
* @type string
*/
deploymentProjectId: string;
/**
* @type string
*/
deploymentRegion: string;
/**
* @type array
*/
projectIds: string[];
}
export interface CloudintegrationtypesAccountConfigDTO {
aws?: CloudintegrationtypesAWSAccountConfigDTO;
azure?: CloudintegrationtypesAzureAccountConfigDTO;
gcp?: CloudintegrationtypesGCPAccountConfigDTO;
}
export interface CloudintegrationtypesAccountDTO {
@@ -2756,29 +2740,9 @@ export interface CloudintegrationtypesAzureServiceConfigDTO {
metrics: CloudintegrationtypesAzureServiceMetricsConfigDTO;
}
export interface CloudintegrationtypesGCPServiceLogsConfigDTO {
/**
* @type boolean
*/
enabled: boolean;
}
export interface CloudintegrationtypesGCPServiceMetricsConfigDTO {
/**
* @type boolean
*/
enabled: boolean;
}
export interface CloudintegrationtypesGCPServiceConfigDTO {
logs?: CloudintegrationtypesGCPServiceLogsConfigDTO;
metrics?: CloudintegrationtypesGCPServiceMetricsConfigDTO;
}
export interface CloudintegrationtypesServiceConfigDTO {
aws?: CloudintegrationtypesAWSServiceConfigDTO;
azure?: CloudintegrationtypesAzureServiceConfigDTO;
gcp?: CloudintegrationtypesGCPServiceConfigDTO;
}
export enum CloudintegrationtypesServiceIDDTO {
@@ -2809,7 +2773,6 @@ export enum CloudintegrationtypesServiceIDDTO {
cosmosdb = 'cosmosdb',
cassandradb = 'cassandradb',
redis = 'redis',
cloudsql = 'cloudsql',
}
export type CloudintegrationtypesCloudIntegrationServiceDTOAnyOf = {
/**
@@ -2874,14 +2837,9 @@ export interface CloudintegrationtypesCollectedMetricDTO {
unit?: string;
}
export interface CloudintegrationtypesGCPConnectionArtifactDTO {
[key: string]: unknown;
}
export interface CloudintegrationtypesConnectionArtifactDTO {
aws?: CloudintegrationtypesAWSConnectionArtifactDTO;
azure?: CloudintegrationtypesAzureConnectionArtifactDTO;
gcp?: CloudintegrationtypesGCPConnectionArtifactDTO;
}
export interface CloudintegrationtypesCredentialsDTO {
@@ -2914,10 +2872,6 @@ export interface CloudintegrationtypesDataCollectedDTO {
metrics?: CloudintegrationtypesCollectedMetricDTO[] | null;
}
export interface CloudintegrationtypesGCPIntegrationConfigDTO {
[key: string]: unknown;
}
export interface CloudintegrationtypesGettableAccountWithConnectionArtifactDTO {
connectionArtifact: CloudintegrationtypesConnectionArtifactDTO;
/**
@@ -3009,7 +2963,6 @@ export type CloudintegrationtypesIntegrationConfigDTO =
export interface CloudintegrationtypesProviderIntegrationConfigDTO {
aws?: CloudintegrationtypesAWSIntegrationConfigDTO;
azure?: CloudintegrationtypesAzureIntegrationConfigDTO;
gcp?: CloudintegrationtypesGCPIntegrationConfigDTO;
}
export interface CloudintegrationtypesGettableAgentCheckInDTO {
@@ -3072,7 +3025,6 @@ export interface CloudintegrationtypesGettableServicesMetadataDTO {
export interface CloudintegrationtypesPostableAccountConfigDTO {
aws?: CloudintegrationtypesAWSPostableAccountConfigDTO;
azure?: CloudintegrationtypesAzureAccountConfigDTO;
gcp?: CloudintegrationtypesGCPAccountConfigDTO;
}
export interface CloudintegrationtypesPostableAccountDTO {
@@ -3202,25 +3154,9 @@ export interface CloudintegrationtypesUpdatableAzureAccountConfigDTO {
resourceGroups: string[];
}
export interface CloudintegrationtypesUpdatableGCPAccountConfigDTO {
/**
* @type string
*/
deploymentProjectId: string;
/**
* @type string
*/
deploymentRegion: string;
/**
* @type array,null
*/
projectIds: string[] | null;
}
export interface CloudintegrationtypesUpdatableAccountConfigDTO {
aws?: CloudintegrationtypesAWSAccountConfigDTO;
azure?: CloudintegrationtypesUpdatableAzureAccountConfigDTO;
gcp?: CloudintegrationtypesUpdatableGCPAccountConfigDTO;
}
export interface CloudintegrationtypesUpdatableAccountDTO {

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24px" height="24px" viewBox="0 0 24 24"><defs><style>.cls-1{fill:#aecbfa;}.cls-1,.cls-2,.cls-3{fill-rule:evenodd;}.cls-2{fill:#669df6;}.cls-3{fill:#4285f4;}</style></defs><title>Icon_24px_SQL_Color</title><g data-name="Product Icons"><g ><polygon class="cls-1" points="4.67 10.44 4.67 13.45 12 17.35 12 14.34 4.67 10.44"/><polygon class="cls-1" points="4.67 15.09 4.67 18.1 12 22 12 18.99 4.67 15.09"/><polygon class="cls-2" points="12 17.35 19.33 13.45 19.33 10.44 12 14.34 12 17.35"/><polygon class="cls-2" points="12 22 19.33 18.1 19.33 15.09 12 18.99 12 22"/><polygon class="cls-3" points="19.33 8.91 19.33 5.9 12 2 12 5.01 19.33 8.91"/><polygon class="cls-2" points="12 2 4.67 5.9 4.67 8.91 12 5.01 12 2"/><polygon class="cls-1" points="4.67 5.87 4.67 8.89 12 12.79 12 9.77 4.67 5.87"/><polygon class="cls-2" points="12 12.79 19.33 8.89 19.33 5.87 12 9.77 12 12.79"/></g></g></svg>

Before

Width:  |  Height:  |  Size: 933 B

View File

@@ -1,27 +0,0 @@
{
"id": "cloudsql",
"title": "GCP Cloud SQL",
"icon": "file://icon.svg",
"overview": "file://overview.md",
"supportedSignals": {
"metrics": true,
"logs": true
},
"dataCollected": {
"metrics": [],
"logs": []
},
"telemetryCollectionStrategy": {
"gcp": {}
},
"assets": {
"dashboards": [
{
"id": "overview",
"title": "GCP Cloud SQL Overview",
"description": "Overview of GCP Cloud SQL metrics",
"definition": "file://assets/dashboards/overview.json"
}
]
}
}

View File

@@ -1,3 +0,0 @@
### Monitor GCP Cloud SQL with SigNoz
Collect key GCP Cloud SQL metrics and view them with an out of the box dashboard.

View File

@@ -481,7 +481,6 @@ func (handler *handler) UpdateService(rw http.ResponseWriter, r *http.Request) {
render.Success(rw, http.StatusNoContent, nil)
}
// TODO: Rename AgentCheckIn to just CheckIn.
func (handler *handler) AgentCheckIn(rw http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel()

View File

@@ -10,6 +10,7 @@ import (
"strings"
"github.com/SigNoz/signoz/pkg/telemetrytraces"
"github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
)
type migrateCommon struct {
@@ -23,119 +24,10 @@ func NewMigrateCommon(logger *slog.Logger) *migrateCommon {
}
}
// WrapInV5Envelope delegates to querybuildertypesv5.WrapInV5Envelope; the
// transform is stateless and shared with the v1→v2 dashboard conversion.
func (migration *migrateCommon) WrapInV5Envelope(name string, queryMap map[string]any, queryType string) map[string]any {
// Create a properly structured v5 query
v5Query := map[string]any{
"name": name,
"disabled": queryMap["disabled"],
"legend": queryMap["legend"],
}
if name != queryMap["expression"] {
// formula
queryType = "builder_formula"
v5Query["expression"] = queryMap["expression"]
if functions, ok := queryMap["functions"]; ok {
v5Query["functions"] = functions
}
return map[string]any{
"type": queryType,
"spec": v5Query,
}
}
// Add signal based on data source
if dataSource, ok := queryMap["dataSource"].(string); ok {
switch dataSource {
case "traces":
v5Query["signal"] = "traces"
case "logs":
v5Query["signal"] = "logs"
case "metrics":
v5Query["signal"] = "metrics"
}
}
if stepInterval, ok := queryMap["stepInterval"]; ok {
v5Query["stepInterval"] = stepInterval
}
if aggregations, ok := queryMap["aggregations"]; ok {
v5Query["aggregations"] = aggregations
}
if filter, ok := queryMap["filter"]; ok {
v5Query["filter"] = filter
}
// Copy groupBy with proper structure
if groupBy, ok := queryMap["groupBy"].([]any); ok {
v5GroupBy := make([]any, len(groupBy))
for i, gb := range groupBy {
if gbMap, ok := gb.(map[string]any); ok {
v5GroupBy[i] = map[string]any{
"name": gbMap["key"],
"fieldDataType": gbMap["dataType"],
"fieldContext": gbMap["type"],
}
}
}
v5Query["groupBy"] = v5GroupBy
}
// Copy orderBy with proper structure
if orderBy, ok := queryMap["orderBy"].([]any); ok {
v5OrderBy := make([]any, len(orderBy))
for i, ob := range orderBy {
if obMap, ok := ob.(map[string]any); ok {
v5OrderBy[i] = map[string]any{
"key": map[string]any{
"name": obMap["columnName"],
"fieldDataType": obMap["dataType"],
"fieldContext": obMap["type"],
},
"direction": obMap["order"],
}
}
}
v5Query["order"] = v5OrderBy
}
// Copy selectColumns as selectFields
if selectColumns, ok := queryMap["selectColumns"].([]any); ok {
v5SelectFields := make([]any, len(selectColumns))
for i, col := range selectColumns {
if colMap, ok := col.(map[string]any); ok {
v5SelectFields[i] = map[string]any{
"name": colMap["key"],
"fieldDataType": colMap["dataType"],
"fieldContext": colMap["type"],
}
}
}
v5Query["selectFields"] = v5SelectFields
}
// Copy limit and offset
if limit, ok := queryMap["limit"]; ok {
v5Query["limit"] = limit
}
if offset, ok := queryMap["offset"]; ok {
v5Query["offset"] = offset
}
if having, ok := queryMap["having"]; ok {
v5Query["having"] = having
}
if functions, ok := queryMap["functions"]; ok {
v5Query["functions"] = functions
}
return map[string]any{
"type": queryType,
"spec": v5Query,
}
return querybuildertypesv5.WrapInV5Envelope(name, queryMap, queryType)
}
func (mc *migrateCommon) updateQueryData(ctx context.Context, queryData map[string]any, version, widgetType string) bool {

View File

@@ -31,13 +31,11 @@ type AgentReport struct {
type AccountConfig struct {
AWS *AWSAccountConfig `json:"aws,omitempty" required:"false" nullable:"false"`
Azure *AzureAccountConfig `json:"azure,omitempty" required:"false" nullable:"false"`
GCP *GCPAccountConfig `json:"gcp,omitempty" required:"false" nullable:"false"`
}
type UpdatableAccountConfig struct {
AWS *UpdatableAWSAccountConfig `json:"aws,omitempty" required:"false" nullable:"false"`
Azure *UpdatableAzureAccountConfig `json:"azure,omitempty" required:"false" nullable:"false"`
GCP *UpdatableGCPAccountConfig `json:"gcp,omitempty" required:"false" nullable:"false"`
}
type PostableAccount struct {
@@ -50,7 +48,6 @@ type PostableAccountConfig struct {
AgentVersion string
AWS *AWSPostableAccountConfig `json:"aws,omitempty" required:"false" nullable:"false"`
Azure *AzurePostableAccountConfig `json:"azure,omitempty" required:"false" nullable:"false"`
GCP *GCPPostableAccountConfig `json:"gcp,omitempty" required:"false" nullable:"false"`
}
type Credentials struct {
@@ -69,7 +66,6 @@ type ConnectionArtifact struct {
// required till new providers are added
AWS *AWSConnectionArtifact `json:"aws,omitempty" required:"false" nullable:"false"`
Azure *AzureConnectionArtifact `json:"azure,omitempty" required:"false" nullable:"false"`
GCP *GCPConnectionArtifact `json:"gcp,omitempty" required:"false" nullable:"false"`
}
type GetConnectionArtifactRequest = PostableAccount
@@ -215,30 +211,6 @@ func NewAccountConfigFromPostable(provider CloudProviderType, config *PostableAc
}
return &AccountConfig{Azure: &AzureAccountConfig{DeploymentRegion: config.Azure.DeploymentRegion, ResourceGroups: config.Azure.ResourceGroups}}, nil
case CloudProviderTypeGCP:
if config.GCP == nil {
return nil, errors.NewInvalidInputf(ErrCodeInvalidInput, "GCP config can not be nil for GCP provider")
}
if config.GCP.DeploymentProjectID == "" {
return nil, errors.NewInvalidInputf(ErrCodeInvalidInput, "deployment project ID is required for GCP provider")
}
if err := validateGCPRegion(config.GCP.DeploymentRegion); err != nil {
return nil, err
}
if len(config.GCP.ProjectIDs) == 0 {
return nil, errors.NewInvalidInputf(ErrCodeInvalidInput, "at least one project id is required for GCP provider")
}
return &AccountConfig{
GCP: &GCPAccountConfig{
DeploymentProjectID: config.GCP.DeploymentProjectID,
ProjectIDs: config.GCP.ProjectIDs,
DeploymentRegion: config.GCP.DeploymentRegion,
},
}, nil
default:
return nil, errors.NewInvalidInputf(ErrCodeCloudProviderInvalidInput, "invalid cloud provider: %s", provider.StringValue())
}
@@ -272,30 +244,6 @@ func NewAccountConfigFromUpdatable(provider CloudProviderType, config *Updatable
}
return &AccountConfig{Azure: &AzureAccountConfig{ResourceGroups: config.Config.Azure.ResourceGroups}}, nil
case CloudProviderTypeGCP:
if config.Config.GCP == nil {
return nil, errors.NewInvalidInputf(ErrCodeInvalidInput, "GCP config can not be nil for GCP provider")
}
if err := validateGCPRegion(config.Config.GCP.DeploymentRegion); err != nil {
return nil, err
}
if len(config.Config.GCP.ProjectIDs) == 0 {
return nil, errors.NewInvalidInputf(ErrCodeInvalidInput, "at least one project id is required for GCP provider")
}
if config.Config.GCP.DeploymentProjectID == "" {
return nil, errors.NewInvalidInputf(ErrCodeInvalidInput, "deployment project ID is required for GCP provider")
}
return &AccountConfig{
GCP: &GCPAccountConfig{
DeploymentProjectID: config.Config.GCP.DeploymentProjectID,
ProjectIDs: config.Config.GCP.ProjectIDs,
DeploymentRegion: config.Config.GCP.DeploymentRegion,
},
}, nil
default:
return nil, errors.NewInvalidInputf(ErrCodeCloudProviderInvalidInput, "invalid cloud provider: %s", provider.StringValue())
}
@@ -384,16 +332,15 @@ func (config *PostableAccountConfig) SetAgentVersion(agentVersion string) {
// thats why not naming it MarshalJSON(), as it will interfere with default JSON marshalling of AccountConfig struct.
// NOTE: this entertains first non-null provider's config.
func (config *AccountConfig) ToJSON() ([]byte, error) {
switch {
case config.AWS != nil:
if config.AWS != nil {
return json.Marshal(config.AWS)
case config.Azure != nil:
return json.Marshal(config.Azure)
case config.GCP != nil:
return json.Marshal(config.GCP)
default:
return nil, errors.NewInternalf(errors.CodeInternal, "no provider account config found")
}
if config.Azure != nil {
return json.Marshal(config.Azure)
}
return nil, errors.NewInternalf(errors.CodeInternal, "no provider account config found")
}
func NewIngestionKeyName(provider CloudProviderType) string {

View File

@@ -50,7 +50,6 @@ type IntegrationConfig struct {
type ProviderIntegrationConfig struct {
AWS *AWSIntegrationConfig `json:"aws,omitempty" required:"false" nullable:"false"`
Azure *AzureIntegrationConfig `json:"azure,omitempty" required:"false" nullable:"false"`
GCP *GCPIntegrationConfig `json:"gcp,omitempty" required:"false" nullable:"false"`
}
// NewGettableAgentCheckIn constructs a backward-compatible response from an AgentCheckInResponse.

View File

@@ -63,7 +63,6 @@ type StorableCloudIntegrationService struct {
type StorableServiceConfig struct {
AWS *StorableAWSServiceConfig
Azure *StorableAzureServiceConfig
GCP *StorableGCPServiceConfig
}
type StorableAWSServiceConfig struct {
@@ -93,15 +92,6 @@ type StorableAzureMetricsServiceConfig struct {
Enabled bool `json:"enabled"`
}
type StorableGCPServiceConfig struct {
Logs *StorableGCPServiceLogsConfig `json:"logs,omitempty"`
Metrics *StorableGCPServiceMetricsConfig `json:"metrics,omitempty"`
}
type StorableGCPServiceLogsConfig = GCPServiceLogsConfig
type StorableGCPServiceMetricsConfig = GCPServiceMetricsConfig
// Scan scans value from DB.
func (r *StorableAgentReport) Scan(src any) error {
var data []byte
@@ -235,30 +225,6 @@ func newStorableServiceConfig(provider CloudProviderType, serviceID ServiceID, s
}
return &StorableServiceConfig{Azure: storableAzureServiceConfig}, nil
case CloudProviderTypeGCP:
storableGCPServiceConfig := new(StorableGCPServiceConfig)
if supportedSignals.Logs {
if serviceConfig.GCP.Logs == nil {
return nil, errors.NewInvalidInputf(ErrCodeCloudIntegrationInvalidConfig, "logs config is required for GCP service: %s", serviceID.StringValue())
}
storableGCPServiceConfig.Logs = &StorableGCPServiceLogsConfig{
Enabled: serviceConfig.GCP.Logs.Enabled,
}
}
if supportedSignals.Metrics {
if serviceConfig.GCP.Metrics == nil {
return nil, errors.NewInvalidInputf(ErrCodeCloudIntegrationInvalidConfig, "metrics config is required for GCP service: %s", serviceID.StringValue())
}
storableGCPServiceConfig.Metrics = &StorableGCPServiceMetricsConfig{
Enabled: serviceConfig.GCP.Metrics.Enabled,
}
}
return &StorableServiceConfig{GCP: storableGCPServiceConfig}, nil
default:
return nil, errors.NewInvalidInputf(ErrCodeCloudProviderInvalidInput, "invalid cloud provider: %s", provider.StringValue())
}
@@ -280,13 +246,6 @@ func newStorableServiceConfigFromJSON(provider CloudProviderType, jsonStr string
return nil, errors.WrapInternalf(err, errors.CodeInternal, "couldn't parse Azure service config JSON")
}
return &StorableServiceConfig{Azure: azureConfig}, nil
case CloudProviderTypeGCP:
gcpConfig := new(StorableGCPServiceConfig)
err := json.Unmarshal([]byte(jsonStr), gcpConfig)
if err != nil {
return nil, errors.WrapInternalf(err, errors.CodeInternal, "couldn't parse GCP service config JSON")
}
return &StorableServiceConfig{GCP: gcpConfig}, nil
default:
return nil, errors.NewInvalidInputf(ErrCodeCloudProviderInvalidInput, "invalid cloud provider: %s", provider.StringValue())
}
@@ -307,13 +266,6 @@ func (config *StorableServiceConfig) toJSON(provider CloudProviderType) ([]byte,
return nil, errors.WrapInternalf(err, errors.CodeInternal, "couldn't serialize Azure service config to JSON")
}
return jsonBytes, nil
case CloudProviderTypeGCP:
jsonBytes, err := json.Marshal(config.GCP)
if err != nil {
return nil, errors.WrapInternalf(err, errors.CodeInternal, "couldn't serialize GCP service config to JSON")
}
return jsonBytes, nil
default:
return nil, errors.NewInvalidInputf(ErrCodeCloudProviderInvalidInput, "invalid cloud provider: %s", provider.StringValue())

View File

@@ -11,7 +11,6 @@ var (
// cloud providers.
CloudProviderTypeAWS = CloudProviderType{valuer.NewString("aws")}
CloudProviderTypeAzure = CloudProviderType{valuer.NewString("azure")}
CloudProviderTypeGCP = CloudProviderType{valuer.NewString("gcp")}
ErrCodeCloudProviderInvalidInput = errors.MustNewCode("cloud_integration_invalid_cloud_provider")
)
@@ -22,8 +21,6 @@ func NewCloudProvider(provider string) (CloudProviderType, error) {
return CloudProviderTypeAWS, nil
case CloudProviderTypeAzure.StringValue():
return CloudProviderTypeAzure, nil
case CloudProviderTypeGCP.StringValue():
return CloudProviderTypeGCP, nil
default:
return CloudProviderType{}, errors.NewInvalidInputf(ErrCodeCloudProviderInvalidInput, "invalid cloud provider: %s", provider)
}

View File

@@ -1,40 +0,0 @@
package cloudintegrationtypes
type GCPAccountConfig struct {
// Project ID where central pub/sub for logs exist
DeploymentProjectID string `json:"deploymentProjectId" required:"true"`
// Project ID where otel collector will be deployed
DeploymentRegion string `json:"deploymentRegion" required:"true"`
// List of project IDs to monitor
ProjectIDs []string `json:"projectIds" required:"true" nullable:"false"`
}
type GCPPostableAccountConfig = GCPAccountConfig
type UpdatableGCPAccountConfig struct {
// Project ID where central pub/sub for logs exist
DeploymentProjectID string `json:"deploymentProjectId" required:"true"`
// Compute service region where otel collector will be deployed
DeploymentRegion string `json:"deploymentRegion" required:"true"`
// List of project IDs to monitor
ProjectIDs []string `json:"projectIds" required:"true"`
}
type GCPConnectionArtifact struct{}
type GCPIntegrationConfig struct{}
type GCPTelemetryCollectionStrategy struct{}
type GCPServiceConfig struct {
Logs *GCPServiceLogsConfig `json:"logs,omitempty" required:"false"`
Metrics *GCPServiceMetricsConfig `json:"metrics,omitempty" required:"false"`
}
type GCPServiceLogsConfig struct {
Enabled bool `json:"enabled" required:"true"`
}
type GCPServiceMetricsConfig struct {
Enabled bool `json:"enabled" required:"true"`
}

View File

@@ -102,51 +102,6 @@ var (
AzureRegionWestUS = CloudProviderRegion{valuer.NewString("westus")} // West US.
AzureRegionWestUS2 = CloudProviderRegion{valuer.NewString("westus2")} // West US 2.
AzureRegionWestUS3 = CloudProviderRegion{valuer.NewString("westus3")} // West US 3.
// GCP regions.
GCPRegionAfricaSouth1 = CloudProviderRegion{valuer.NewString("africa-south1")} // Johannesburg, South Africa. Africa.
GCPRegionAsiaEast1 = CloudProviderRegion{valuer.NewString("asia-east1")} // Changhua County, Taiwan. APAC.
GCPRegionAsiaEast2 = CloudProviderRegion{valuer.NewString("asia-east2")} // Hong Kong. APAC.
GCPRegionAsiaNortheast1 = CloudProviderRegion{valuer.NewString("asia-northeast1")} // Tokyo, Japan. APAC.
GCPRegionAsiaNortheast2 = CloudProviderRegion{valuer.NewString("asia-northeast2")} // Osaka, Japan. APAC.
GCPRegionAsiaNortheast3 = CloudProviderRegion{valuer.NewString("asia-northeast3")} // Seoul, South Korea. APAC.
GCPRegionAsiaSouth1 = CloudProviderRegion{valuer.NewString("asia-south1")} // Mumbai, India. APAC.
GCPRegionAsiaSouth2 = CloudProviderRegion{valuer.NewString("asia-south2")} // Delhi, India. APAC.
GCPRegionAsiaSoutheast1 = CloudProviderRegion{valuer.NewString("asia-southeast1")} // Jurong West, Singapore. APAC.
GCPRegionAsiaSoutheast2 = CloudProviderRegion{valuer.NewString("asia-southeast2")} // Jakarta, Indonesia. APAC.
GCPRegionAsiaSoutheast3 = CloudProviderRegion{valuer.NewString("asia-southeast3")} // Bangkok, Thailand. APAC.
GCPRegionAustraliaSoutheast1 = CloudProviderRegion{valuer.NewString("australia-southeast1")} // Sydney, Australia. APAC.
GCPRegionAustraliaSoutheast2 = CloudProviderRegion{valuer.NewString("australia-southeast2")} // Melbourne, Australia. APAC.
GCPRegionEuropeCentral2 = CloudProviderRegion{valuer.NewString("europe-central2")} // Warsaw, Poland. Europe.
GCPRegionEuropeNorth1 = CloudProviderRegion{valuer.NewString("europe-north1")} // Hamina, Finland. Europe.
GCPRegionEuropeNorth2 = CloudProviderRegion{valuer.NewString("europe-north2")} // Stockholm, Sweden. Europe.
GCPRegionEuropeSouthwest1 = CloudProviderRegion{valuer.NewString("europe-southwest1")} // Madrid, Spain. Europe.
GCPRegionEuropeWest1 = CloudProviderRegion{valuer.NewString("europe-west1")} // St. Ghislain, Belgium. Europe.
GCPRegionEuropeWest2 = CloudProviderRegion{valuer.NewString("europe-west2")} // London, England. Europe.
GCPRegionEuropeWest3 = CloudProviderRegion{valuer.NewString("europe-west3")} // Frankfurt, Germany. Europe.
GCPRegionEuropeWest4 = CloudProviderRegion{valuer.NewString("europe-west4")} // Eemshaven, Netherlands. Europe.
GCPRegionEuropeWest6 = CloudProviderRegion{valuer.NewString("europe-west6")} // Zurich, Switzerland. Europe.
GCPRegionEuropeWest8 = CloudProviderRegion{valuer.NewString("europe-west8")} // Milan, Italy. Europe.
GCPRegionEuropeWest9 = CloudProviderRegion{valuer.NewString("europe-west9")} // Paris, France. Europe.
GCPRegionEuropeWest10 = CloudProviderRegion{valuer.NewString("europe-west10")} // Berlin, Germany. Europe.
GCPRegionEuropeWest12 = CloudProviderRegion{valuer.NewString("europe-west12")} // Turin, Italy. Europe.
GCPRegionMECentral1 = CloudProviderRegion{valuer.NewString("me-central1")} // Doha, Qatar. Middle East.
GCPRegionMECentral2 = CloudProviderRegion{valuer.NewString("me-central2")} // Dammam, Saudi Arabia. Middle East.
GCPRegionMEWest1 = CloudProviderRegion{valuer.NewString("me-west1")} // Tel Aviv, Israel. Middle East.
GCPRegionNorthamericaNortheast1 = CloudProviderRegion{valuer.NewString("northamerica-northeast1")} // Montréal, Québec, Canada. North America.
GCPRegionNorthamericaNortheast2 = CloudProviderRegion{valuer.NewString("northamerica-northeast2")} // Toronto, Ontario, Canada. North America.
GCPRegionNorthamericaSouth1 = CloudProviderRegion{valuer.NewString("northamerica-south1")} // Querétaro, Mexico. North America.
GCPRegionSouthamericaEast1 = CloudProviderRegion{valuer.NewString("southamerica-east1")} // Osasco, São Paulo, Brazil. South America.
GCPRegionSouthamericaWest1 = CloudProviderRegion{valuer.NewString("southamerica-west1")} // Santiago, Chile. South America.
GCPRegionUSCentral1 = CloudProviderRegion{valuer.NewString("us-central1")} // Council Bluffs, Iowa. North America.
GCPRegionUSEast1 = CloudProviderRegion{valuer.NewString("us-east1")} // Moncks Corner, South Carolina. North America.
GCPRegionUSEast4 = CloudProviderRegion{valuer.NewString("us-east4")} // Ashburn, Virginia. North America.
GCPRegionUSEast5 = CloudProviderRegion{valuer.NewString("us-east5")} // Columbus, Ohio. North America.
GCPRegionUSSouth1 = CloudProviderRegion{valuer.NewString("us-south1")} // Dallas, Texas. North America.
GCPRegionUSWest1 = CloudProviderRegion{valuer.NewString("us-west1")} // The Dalles, Oregon. North America.
GCPRegionUSWest2 = CloudProviderRegion{valuer.NewString("us-west2")} // Los Angeles, California. North America.
GCPRegionUSWest3 = CloudProviderRegion{valuer.NewString("us-west3")} // Salt Lake City, Utah. North America.
GCPRegionUSWest4 = CloudProviderRegion{valuer.NewString("us-west4")} // Las Vegas, Nevada. North America.
)
func Enum() []any {
@@ -172,18 +127,6 @@ func Enum() []any {
AzureRegionSwedenCentral, AzureRegionSwitzerlandNorth, AzureRegionSwitzerlandWest,
AzureRegionUAECentral, AzureRegionUAENorth, AzureRegionUKSouth, AzureRegionUKWest,
AzureRegionWestCentralUS, AzureRegionWestEurope, AzureRegionWestIndia, AzureRegionWestUS, AzureRegionWestUS2, AzureRegionWestUS3,
// GCP regions.
GCPRegionAfricaSouth1, GCPRegionAsiaEast1, GCPRegionAsiaEast2, GCPRegionAsiaNortheast1, GCPRegionAsiaNortheast2, GCPRegionAsiaNortheast3,
GCPRegionAsiaSouth1, GCPRegionAsiaSouth2, GCPRegionAsiaSoutheast1, GCPRegionAsiaSoutheast2, GCPRegionAsiaSoutheast3,
GCPRegionAustraliaSoutheast1, GCPRegionAustraliaSoutheast2,
GCPRegionEuropeCentral2, GCPRegionEuropeNorth1, GCPRegionEuropeNorth2, GCPRegionEuropeSouthwest1,
GCPRegionEuropeWest1, GCPRegionEuropeWest2, GCPRegionEuropeWest3, GCPRegionEuropeWest4, GCPRegionEuropeWest6,
GCPRegionEuropeWest8, GCPRegionEuropeWest9, GCPRegionEuropeWest10, GCPRegionEuropeWest12,
GCPRegionMECentral1, GCPRegionMECentral2, GCPRegionMEWest1,
GCPRegionNorthamericaNortheast1, GCPRegionNorthamericaNortheast2, GCPRegionNorthamericaSouth1,
GCPRegionSouthamericaEast1, GCPRegionSouthamericaWest1,
GCPRegionUSCentral1, GCPRegionUSEast1, GCPRegionUSEast4, GCPRegionUSEast5, GCPRegionUSSouth1,
GCPRegionUSWest1, GCPRegionUSWest2, GCPRegionUSWest3, GCPRegionUSWest4,
}
}
@@ -211,19 +154,6 @@ var SupportedRegions = map[CloudProviderType][]CloudProviderRegion{
AzureRegionUAECentral, AzureRegionUAENorth, AzureRegionUKSouth, AzureRegionUKWest,
AzureRegionWestCentralUS, AzureRegionWestEurope, AzureRegionWestIndia, AzureRegionWestUS, AzureRegionWestUS2, AzureRegionWestUS3,
},
CloudProviderTypeGCP: {
GCPRegionAfricaSouth1, GCPRegionAsiaEast1, GCPRegionAsiaEast2, GCPRegionAsiaNortheast1, GCPRegionAsiaNortheast2, GCPRegionAsiaNortheast3,
GCPRegionAsiaSouth1, GCPRegionAsiaSouth2, GCPRegionAsiaSoutheast1, GCPRegionAsiaSoutheast2, GCPRegionAsiaSoutheast3,
GCPRegionAustraliaSoutheast1, GCPRegionAustraliaSoutheast2,
GCPRegionEuropeCentral2, GCPRegionEuropeNorth1, GCPRegionEuropeNorth2, GCPRegionEuropeSouthwest1,
GCPRegionEuropeWest1, GCPRegionEuropeWest2, GCPRegionEuropeWest3, GCPRegionEuropeWest4, GCPRegionEuropeWest6,
GCPRegionEuropeWest8, GCPRegionEuropeWest9, GCPRegionEuropeWest10, GCPRegionEuropeWest12,
GCPRegionMECentral1, GCPRegionMECentral2, GCPRegionMEWest1,
GCPRegionNorthamericaNortheast1, GCPRegionNorthamericaNortheast2, GCPRegionNorthamericaSouth1,
GCPRegionSouthamericaEast1, GCPRegionSouthamericaWest1,
GCPRegionUSCentral1, GCPRegionUSEast1, GCPRegionUSEast4, GCPRegionUSEast5, GCPRegionUSSouth1,
GCPRegionUSWest1, GCPRegionUSWest2, GCPRegionUSWest3, GCPRegionUSWest4,
},
}
func validateAWSRegion(region string) error {
@@ -245,13 +175,3 @@ func validateAzureRegion(region string) error {
return errors.NewInvalidInputf(ErrCodeInvalidCloudRegion, "invalid Azure region: %s", region)
}
func validateGCPRegion(region string) error {
for _, r := range SupportedRegions[CloudProviderTypeGCP] {
if r.StringValue() == region {
return nil
}
}
return errors.NewInvalidInputf(ErrCodeInvalidCloudRegion, "invalid GCP region: %s", region)
}

View File

@@ -21,7 +21,6 @@ type CloudIntegrationService struct {
type ServiceConfig struct {
AWS *AWSServiceConfig `json:"aws,omitempty" required:"false" nullable:"false"`
Azure *AzureServiceConfig `json:"azure,omitempty" required:"false" nullable:"false"`
GCP *GCPServiceConfig `json:"gcp,omitempty" required:"false" nullable:"false"`
}
// ServiceMetadata helps to quickly list available services and whether it is enabled or not.
@@ -97,7 +96,6 @@ type DataCollected struct {
type TelemetryCollectionStrategy struct {
AWS *AWSTelemetryCollectionStrategy `json:"aws,omitempty" required:"false" nullable:"false"`
Azure *AzureTelemetryCollectionStrategy `json:"azure,omitempty" required:"false" nullable:"false"`
GCP *GCPTelemetryCollectionStrategy `json:"gcp,omitempty" required:"false" nullable:"false"`
}
// Assets represents the collection of dashboards.
@@ -147,10 +145,6 @@ func NewCloudIntegrationService(serviceID ServiceID, cloudIntegrationID valuer.U
if config.Azure == nil {
return nil, errors.NewInvalidInputf(ErrCodeInvalidInput, "Azure config is required for Azure service")
}
case CloudProviderTypeGCP:
if config.GCP == nil {
return nil, errors.NewInvalidInputf(ErrCodeInvalidInput, "GCP config is required for GCP service")
}
}
return &CloudIntegrationService{
@@ -267,22 +261,6 @@ func NewServiceConfigFromJSON(provider CloudProviderType, jsonString string) (*S
}
return &ServiceConfig{Azure: azureServiceConfig}, nil
case CloudProviderTypeGCP:
gcpServiceConfig := new(GCPServiceConfig)
if storableServiceConfig.GCP.Logs != nil {
gcpServiceConfig.Logs = &GCPServiceLogsConfig{
Enabled: storableServiceConfig.GCP.Logs.Enabled,
}
}
if storableServiceConfig.GCP.Metrics != nil {
gcpServiceConfig.Metrics = &GCPServiceMetricsConfig{
Enabled: storableServiceConfig.GCP.Metrics.Enabled,
}
}
return &ServiceConfig{GCP: gcpServiceConfig}, nil
default:
return nil, errors.NewInvalidInputf(ErrCodeCloudProviderInvalidInput, "invalid cloud provider: %s", provider.StringValue())
}
@@ -307,10 +285,6 @@ func (service *CloudIntegrationService) Update(provider CloudProviderType, servi
if config.Azure == nil {
return errors.NewInvalidInputf(ErrCodeCloudProviderInvalidInput, "Azure config is required for Azure service")
}
case CloudProviderTypeGCP:
if config.GCP == nil {
return errors.NewInvalidInputf(ErrCodeCloudProviderInvalidInput, "GCP config is required for GCP service")
}
default:
return errors.NewInvalidInputf(ErrCodeCloudProviderInvalidInput, "invalid cloud provider: %s", provider.StringValue())
}
@@ -332,10 +306,6 @@ func (config *ServiceConfig) IsServiceEnabled(provider CloudProviderType) bool {
logsEnabled := config.Azure.Logs != nil && config.Azure.Logs.Enabled
metricsEnabled := config.Azure.Metrics != nil && config.Azure.Metrics.Enabled
return logsEnabled || metricsEnabled
case CloudProviderTypeGCP:
logsEnabled := config.GCP.Logs != nil && config.GCP.Logs.Enabled
metricsEnabled := config.GCP.Metrics != nil && config.GCP.Metrics.Enabled
return logsEnabled || metricsEnabled
default:
return false
}
@@ -349,8 +319,6 @@ func (config *ServiceConfig) IsMetricsEnabled(provider CloudProviderType) bool {
return config.AWS.Metrics != nil && config.AWS.Metrics.Enabled
case CloudProviderTypeAzure:
return config.Azure.Metrics != nil && config.Azure.Metrics.Enabled
case CloudProviderTypeGCP:
return config.GCP.Metrics != nil && config.GCP.Metrics.Enabled
default:
return false
}
@@ -363,8 +331,6 @@ func (config *ServiceConfig) IsLogsEnabled(provider CloudProviderType) bool {
return config.AWS.Logs != nil && config.AWS.Logs.Enabled
case CloudProviderTypeAzure:
return config.Azure.Logs != nil && config.Azure.Logs.Enabled
case CloudProviderTypeGCP:
return config.GCP.Logs != nil && config.GCP.Logs.Enabled
default:
return false
}

View File

@@ -39,9 +39,6 @@ var (
AzureServiceCosmosDB = ServiceID{valuer.NewString("cosmosdb")}
AzureServiceCassandraDB = ServiceID{valuer.NewString("cassandradb")}
AzureServiceRedis = ServiceID{valuer.NewString("redis")}
// GCP services.
GCPServiceCloudSQL = ServiceID{valuer.NewString("cloudsql")}
)
func (ServiceID) Enum() []any {
@@ -73,7 +70,6 @@ func (ServiceID) Enum() []any {
AzureServiceCosmosDB,
AzureServiceCassandraDB,
AzureServiceRedis,
GCPServiceCloudSQL,
}
}
@@ -110,9 +106,6 @@ var SupportedServices = map[CloudProviderType][]ServiceID{
AzureServiceCassandraDB,
AzureServiceRedis,
},
CloudProviderTypeGCP: {
GCPServiceCloudSQL,
},
}
func NewServiceID(provider CloudProviderType, service string) (ServiceID, error) {

View File

@@ -7,7 +7,6 @@ import (
"time"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/transition"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
"github.com/SigNoz/signoz/pkg/valuer"
@@ -406,27 +405,34 @@ func (dashboard *Dashboard) GetWidgetQuery(startTime, endTime, widgetIndex uint6
widgetData := data.Widgets[widgetIndex]
switch widgetData.Query.QueryType {
case "builder":
migrate := transition.NewMigrateCommon(logger)
isRawRequest := dashboard.getQueryRequestTypeFromPanelType(widgetData.PanelTypes) == querybuildertypesv5.RequestTypeRaw
for _, query := range widgetData.Query.Builder.QueryData {
queryName, ok := query["queryName"].(string)
if !ok {
return nil, errors.New(errors.TypeInvalidInput, ErrCodeDashboardInvalidWidgetQuery, "cannot type cast query name as string")
}
compositeQueries = append(compositeQueries, migrate.WrapInV5Envelope(queryName, query, "builder_query"))
// build aggregations the same way the frontend does before hitting the query
// range API; raw requests carry no aggregations.
if isRawRequest {
delete(query, "aggregations")
} else {
query["aggregations"] = querybuildertypesv5.CreateAggregation(query, widgetData.PanelTypes)
}
compositeQueries = append(compositeQueries, querybuildertypesv5.WrapInV5Envelope(queryName, query, "builder_query"))
}
for _, query := range widgetData.Query.Builder.QueryFormulas {
queryName, ok := query["queryName"].(string)
if !ok {
return nil, errors.New(errors.TypeInvalidInput, ErrCodeDashboardInvalidWidgetQuery, "cannot type cast query name as string")
}
compositeQueries = append(compositeQueries, migrate.WrapInV5Envelope(queryName, query, "builder_formula"))
compositeQueries = append(compositeQueries, querybuildertypesv5.WrapInV5Envelope(queryName, query, "builder_formula"))
}
for _, query := range widgetData.Query.Builder.QueryTraceOperator {
queryName, ok := query["queryName"].(string)
if !ok {
return nil, errors.New(errors.TypeInvalidInput, ErrCodeDashboardInvalidWidgetQuery, "cannot type cast query name as string")
}
compositeQueries = append(compositeQueries, migrate.WrapInV5Envelope(queryName, query, "builder_trace_operator"))
compositeQueries = append(compositeQueries, querybuildertypesv5.WrapInV5Envelope(queryName, query, "builder_trace_operator"))
}
case "clickhouse_sql":
for _, query := range widgetData.Query.ClickhouseSQL {

View File

@@ -0,0 +1,214 @@
package querybuildertypesv5
import (
"regexp"
"strings"
)
// WrapInV5Envelope translates a single v4 builder query/formula map into a
// v5 query envelope ({"type": ..., "spec": ...}). It is a pure shape transform
// over untyped maps: v4 builder field names (groupBy/orderBy/selectColumns/
// dataSource) are rewritten to their v5 equivalents and a `signal` is derived
// from the data source. queryType selects the envelope type, except a formula
// (detected when name != queryMap["expression"]) is always emitted as
// "builder_formula".
//
// Migration code (pkg/transition) and the v1→v2 dashboard conversion both
// produce v5 envelopes, so this lives here with the v5 query types rather than
// in an infra-level package.
func WrapInV5Envelope(name string, queryMap map[string]any, queryType string) map[string]any {
// Create a properly structured v5 query
v5Query := map[string]any{
"name": name,
"disabled": queryMap["disabled"],
"legend": queryMap["legend"],
}
if name != queryMap["expression"] {
// formula
queryType = "builder_formula"
v5Query["expression"] = queryMap["expression"]
if functions, ok := queryMap["functions"]; ok {
v5Query["functions"] = functions
}
return map[string]any{
"type": queryType,
"spec": v5Query,
}
}
// Add signal based on data source
if dataSource, ok := queryMap["dataSource"].(string); ok {
switch dataSource {
case "traces":
v5Query["signal"] = "traces"
case "logs":
v5Query["signal"] = "logs"
case "metrics":
v5Query["signal"] = "metrics"
}
}
if stepInterval, ok := queryMap["stepInterval"]; ok {
v5Query["stepInterval"] = stepInterval
}
if aggregations, ok := queryMap["aggregations"]; ok {
v5Query["aggregations"] = aggregations
}
if filter, ok := queryMap["filter"]; ok {
v5Query["filter"] = filter
}
// Copy groupBy with proper structure
if groupBy, ok := queryMap["groupBy"].([]any); ok {
v5GroupBy := make([]any, len(groupBy))
for i, gb := range groupBy {
if gbMap, ok := gb.(map[string]any); ok {
v5GroupBy[i] = map[string]any{
"name": gbMap["key"],
"fieldDataType": gbMap["dataType"],
"fieldContext": gbMap["type"],
}
}
}
v5Query["groupBy"] = v5GroupBy
}
// Copy orderBy with proper structure
if orderBy, ok := queryMap["orderBy"].([]any); ok {
v5OrderBy := make([]any, len(orderBy))
for i, ob := range orderBy {
if obMap, ok := ob.(map[string]any); ok {
v5OrderBy[i] = map[string]any{
"key": map[string]any{
"name": obMap["columnName"],
"fieldDataType": obMap["dataType"],
"fieldContext": obMap["type"],
},
"direction": obMap["order"],
}
}
}
v5Query["order"] = v5OrderBy
}
// Copy selectColumns as selectFields
if selectColumns, ok := queryMap["selectColumns"].([]any); ok {
v5SelectFields := make([]any, len(selectColumns))
for i, col := range selectColumns {
if colMap, ok := col.(map[string]any); ok {
v5SelectFields[i] = map[string]any{
"name": colMap["key"],
"fieldDataType": colMap["dataType"],
"fieldContext": colMap["type"],
}
}
}
v5Query["selectFields"] = v5SelectFields
}
// Copy limit and offset
if limit, ok := queryMap["limit"]; ok {
v5Query["limit"] = limit
}
if offset, ok := queryMap["offset"]; ok {
v5Query["offset"] = offset
}
if having, ok := queryMap["having"]; ok {
v5Query["having"] = having
}
if functions, ok := queryMap["functions"]; ok {
v5Query["functions"] = functions
}
return map[string]any{
"type": queryType,
"spec": v5Query,
}
}
// aggregationExprRegexp matches a function-style aggregation like `count()` or
// `sum(field)` with an optional `as <alias>`, as the frontend's parseAggregations does.
var aggregationExprRegexp = regexp.MustCompile(`([a-zA-Z0-9_]+\([^)]*\))(?:\s*as\s+((?:'[^']*'|"[^"]*"|[a-zA-Z0-9_-]+)))?`)
// CreateAggregation builds the v5 aggregations for a stored builder query, mirroring
// createAggregation in the frontend's prepareQueryRangePayloadV5.ts. Metrics yield a
// single structured aggregation; logs/traces split their comma-separated expression into
// one aggregation per call, defaulting to count() when nothing parses.
func CreateAggregation(queryData map[string]any, panelType string) []any {
if queryData == nil {
return []any{}
}
if dataSource, _ := queryData["dataSource"].(string); dataSource == "metrics" {
var first map[string]any
if aggs, ok := queryData["aggregations"].([]any); ok && len(aggs) > 0 {
first, _ = aggs[0].(map[string]any)
}
attribute, _ := queryData["aggregateAttribute"].(map[string]any)
metric := map[string]any{}
setFirstNonEmpty(metric, "metricName", first["metricName"], attribute["key"])
setFirstNonEmpty(metric, "temporality", first["temporality"], attribute["temporality"])
setFirstNonEmpty(metric, "timeAggregation", first["timeAggregation"], queryData["timeAggregation"])
setFirstNonEmpty(metric, "spaceAggregation", first["spaceAggregation"], queryData["spaceAggregation"])
if panelType == "table" || panelType == "pie" || panelType == "value" {
setFirstNonEmpty(metric, "reduceTo", first["reduceTo"], queryData["reduceTo"])
}
return []any{metric}
}
aggs, ok := queryData["aggregations"].([]any)
if !ok || len(aggs) == 0 {
return []any{map[string]any{"expression": "count()"}}
}
result := []any{}
for _, agg := range aggs {
aggMap, _ := agg.(map[string]any)
expression, _ := aggMap["expression"].(string)
alias, _ := aggMap["alias"].(string)
parsed := parseAggregations(expression, alias)
if len(parsed) == 0 {
result = append(result, map[string]any{"expression": "count()"})
continue
}
result = append(result, parsed...)
}
return result
}
// parseAggregations extracts each function-style call from a (possibly comma-separated)
// aggregation expression, attaching the inline `as` alias or the fallback alias.
func parseAggregations(expression, fallbackAlias string) []any {
result := []any{}
for _, match := range aggregationExprRegexp.FindAllStringSubmatch(expression, -1) {
agg := map[string]any{"expression": match[1]}
if alias := match[2]; alias != "" {
agg["alias"] = strings.Trim(alias, `'"`)
} else if fallbackAlias != "" {
agg["alias"] = fallbackAlias
}
result = append(result, agg)
}
return result
}
// setFirstNonEmpty sets key to the first value that is neither nil nor "", mirroring the
// JS `a || b` fallback the frontend uses for the metric aggregation fields.
func setFirstNonEmpty(target map[string]any, key string, values ...any) {
for _, v := range values {
if v == nil {
continue
}
if s, ok := v.(string); ok && s == "" {
continue
}
target[key] = v
return
}
}

View File

@@ -0,0 +1,176 @@
package querybuildertypesv5
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestCreateAggregation(t *testing.T) {
testCases := []struct {
description string
queryData map[string]any
panelType string
expectedOutput []any
}{
{
description: "nil query data yields no aggregations",
queryData: nil,
expectedOutput: []any{},
},
{
description: "single logs expression is left untouched",
queryData: map[string]any{"dataSource": "logs", "aggregations": []any{map[string]any{"expression": "count()"}}},
expectedOutput: []any{map[string]any{"expression": "count()"}},
},
{
description: "comma separated trace expressions are split into one object each",
queryData: map[string]any{"dataSource": "traces", "aggregations": []any{map[string]any{"expression": "count(), sum(price)"}}},
expectedOutput: []any{
map[string]any{"expression": "count()"},
map[string]any{"expression": "sum(price)"},
},
},
{
description: "inline alias is preserved and unquoted",
queryData: map[string]any{"dataSource": "logs", "aggregations": []any{map[string]any{"expression": "count() as 'total', sum(price) as revenue"}}},
expectedOutput: []any{
map[string]any{"expression": "count()", "alias": "total"},
map[string]any{"expression": "sum(price)", "alias": "revenue"},
},
},
{
description: "space separated expressions split with an unquoted alias on the first only",
queryData: map[string]any{"dataSource": "logs", "aggregations": []any{map[string]any{"expression": "count() as cnt avg(code.lineno) "}}},
expectedOutput: []any{
map[string]any{"expression": "count()", "alias": "cnt"},
map[string]any{"expression": "avg(code.lineno)"},
},
},
{
description: "fallback alias is applied when expression has no inline alias",
queryData: map[string]any{"dataSource": "logs", "aggregations": []any{map[string]any{"expression": "count()", "alias": "hits"}}},
expectedOutput: []any{map[string]any{"expression": "count()", "alias": "hits"}},
},
{
description: "commas inside function arguments do not split the expression",
queryData: map[string]any{"dataSource": "traces", "aggregations": []any{map[string]any{"expression": "countIf(day > 10, status)"}}},
expectedOutput: []any{map[string]any{"expression": "countIf(day > 10, status)"}},
},
{
description: "unparseable expression falls back to count()",
queryData: map[string]any{"dataSource": "logs", "aggregations": []any{map[string]any{"expression": "not-an-aggregation"}}},
expectedOutput: []any{map[string]any{"expression": "count()"}},
},
{
description: "empty aggregations fall back to count()",
queryData: map[string]any{"dataSource": "logs", "aggregations": []any{}},
expectedOutput: []any{map[string]any{"expression": "count()"}},
},
{
description: "missing aggregations fall back to count()",
queryData: map[string]any{"dataSource": "traces"},
expectedOutput: []any{map[string]any{"expression": "count()"}},
},
{
description: "metric aggregation is built from the first aggregation",
queryData: map[string]any{
"dataSource": "metrics",
"aggregations": []any{map[string]any{
"metricName": "http_requests_total",
"temporality": "delta",
"timeAggregation": "rate",
"spaceAggregation": "sum",
}},
},
expectedOutput: []any{map[string]any{
"metricName": "http_requests_total",
"temporality": "delta",
"timeAggregation": "rate",
"spaceAggregation": "sum",
}},
},
{
description: "metric omits temporality when empty, matching the frontend `|| undefined`",
panelType: "table",
queryData: map[string]any{
"dataSource": "metrics",
"timeAggregation": "sum",
"spaceAggregation": "avg",
"temporality": "",
"reduceTo": "avg",
"aggregations": []any{map[string]any{
"metricName": "cpu_usage",
"temporality": "",
"timeAggregation": "sum",
"spaceAggregation": "avg",
"reduceTo": "avg",
}},
},
expectedOutput: []any{map[string]any{
"metricName": "cpu_usage",
"timeAggregation": "sum",
"spaceAggregation": "avg",
"reduceTo": "avg",
}},
},
{
description: "metric includes reduceTo for table/pie/value panels",
panelType: "table",
queryData: map[string]any{
"dataSource": "metrics",
"aggregations": []any{map[string]any{
"metricName": "http_requests_total",
"timeAggregation": "rate",
"spaceAggregation": "sum",
"reduceTo": "avg",
}},
},
expectedOutput: []any{map[string]any{
"metricName": "http_requests_total",
"timeAggregation": "rate",
"spaceAggregation": "sum",
"reduceTo": "avg",
}},
},
{
description: "metric drops reduceTo for other panels even when query data has it",
panelType: "graph",
queryData: map[string]any{
"dataSource": "metrics",
"aggregations": []any{map[string]any{
"metricName": "http_requests_total",
"timeAggregation": "rate",
"spaceAggregation": "sum",
"reduceTo": "avg",
}},
},
expectedOutput: []any{map[string]any{
"metricName": "http_requests_total",
"timeAggregation": "rate",
"spaceAggregation": "sum",
}},
},
{
description: "metric falls back to legacy aggregateAttribute and top-level fields",
queryData: map[string]any{
"dataSource": "metrics",
"aggregateAttribute": map[string]any{"key": "legacy_metric", "temporality": "cumulative"},
"timeAggregation": "avg",
"spaceAggregation": "max",
},
expectedOutput: []any{map[string]any{
"metricName": "legacy_metric",
"temporality": "cumulative",
"timeAggregation": "avg",
"spaceAggregation": "max",
}},
},
}
for _, testCase := range testCases {
t.Run(testCase.description, func(t *testing.T) {
assert.Equal(t, testCase.expectedOutput, CreateAggregation(testCase.queryData, testCase.panelType))
})
}
}

View File

@@ -338,7 +338,6 @@ func isValidLabelValue(v string) bool {
// validate runs during UnmarshalJSON (read + write path).
// Preserves the original pre-existing checks only so that stored rules
// continue to load without errors.
// TODO(srikanthccv): remove this once v1 is deprecated and removed.
func (r *PostableRule) validate() error {
var errs []error
@@ -367,13 +366,9 @@ func (r *PostableRule) validate() error {
errs = append(errs, testTemplateParsing(r)...)
if len(errs) > 0 {
messages := make([]string, len(errs))
for i, e := range errs {
messages[i] = e.Error()
}
return errors.NewInvalidInputf(errors.CodeInvalidInput, "alert rule definition is not valid").
WithAdditional(messages...)
joined := errors.Join(errs...)
if joined != nil {
return errors.WrapInvalidInputf(joined, errors.CodeInvalidInput, "validation failed")
}
return nil
}
@@ -471,13 +466,9 @@ func (r *PostableRule) Validate() error {
errs = append(errs, testTemplateParsing(r)...)
if len(errs) > 0 {
messages := make([]string, len(errs))
for i, e := range errs {
messages[i] = e.Error()
}
return errors.NewInvalidInputf(errors.CodeInvalidInput, "alert rule is not valid").
WithAdditional(messages...)
joined := errors.Join(errs...)
if joined != nil {
return errors.WrapInvalidInputf(joined, errors.CodeInvalidInput, "validation failed")
}
return nil
}

View File

@@ -4,23 +4,8 @@ import (
"encoding/json"
"strings"
"testing"
"github.com/SigNoz/signoz/pkg/errors"
)
func errorContains(err error, substr string) bool {
j := errors.AsJSON(err)
if strings.Contains(j.Message, substr) {
return true
}
for _, e := range j.Errors {
if strings.Contains(e.Message, substr) {
return true
}
}
return false
}
// validV1Builder returns a minimal valid v1 builder rule JSON.
func validV1Builder() string {
return `{
@@ -509,7 +494,7 @@ func TestValidate_PostableRule_Common(t *testing.T) {
if tt.wantErr {
if err == nil {
t.Errorf("expected error containing %q, got nil", tt.errSubstr)
} else if tt.errSubstr != "" && !errorContains(err, tt.errSubstr) {
} else if tt.errSubstr != "" && !strings.Contains(err.Error(), tt.errSubstr) {
t.Errorf("expected error containing %q, got: %v", tt.errSubstr, err)
}
} else {
@@ -702,7 +687,7 @@ func TestValidate_V1_ConditionFields(t *testing.T) {
if tt.wantErr {
if validateErr == nil {
t.Errorf("expected Validate() error containing %q, got nil", tt.errSubstr)
} else if tt.errSubstr != "" && !errorContains(validateErr, tt.errSubstr) {
} else if tt.errSubstr != "" && !strings.Contains(validateErr.Error(), tt.errSubstr) {
t.Errorf("expected error containing %q, got: %v", tt.errSubstr, validateErr)
}
} else {
@@ -1044,7 +1029,7 @@ func TestValidate_V2Alpha1(t *testing.T) {
if tt.wantErr {
if err == nil {
t.Errorf("expected error containing %q, got nil", tt.errSubstr)
} else if tt.errSubstr != "" && !errorContains(err, tt.errSubstr) {
} else if tt.errSubstr != "" && !strings.Contains(err.Error(), tt.errSubstr) {
t.Errorf("expected error containing %q, got: %v", tt.errSubstr, err)
}
} else {
@@ -1352,7 +1337,7 @@ func TestValidate_MultipleErrors(t *testing.T) {
t.Fatal("expected unmarshal error for wrong version")
}
// The error should mention version
if !errorContains(err, "version") {
if !strings.Contains(err.Error(), "version") {
t.Errorf("expected error to mention version, got: %v", err)
}
})
@@ -1370,9 +1355,10 @@ func TestValidate_MultipleErrors(t *testing.T) {
if validateErr == nil {
t.Fatal("expected Validate() error")
}
errStr := validateErr.Error()
// Should contain errors for thresholds, evaluation, notificationSettings
for _, substr := range []string{"evaluation", "notificationSettings"} {
if !errorContains(validateErr, substr) {
if !strings.Contains(errStr, substr) {
t.Errorf("expected error to mention %q, got: %v", substr, validateErr)
}
}
@@ -1483,7 +1469,7 @@ func TestValidate_V2Alpha1_CumulativeEvaluation(t *testing.T) {
if tt.wantErr {
if err == nil {
t.Errorf("expected error containing %q, got nil", tt.errSubstr)
} else if !errorContains(err, tt.errSubstr) {
} else if !strings.Contains(err.Error(), tt.errSubstr) {
t.Errorf("expected error containing %q, got: %v", tt.errSubstr, err)
}
} else if err != nil {

View File

@@ -143,7 +143,7 @@ def test_get_credentials_unsupported_provider(
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
response = requests.get(
signoz.self.host_configs["8080"].get("/api/v1/cloud_integrations/unknown/credentials"),
signoz.self.host_configs["8080"].get("/api/v1/cloud_integrations/gcp/credentials"),
headers={"Authorization": f"Bearer {admin_token}"},
timeout=10,
)

View File

@@ -56,14 +56,14 @@ def test_create_account_unsupported_provider(
) -> None:
"""Test that creating an account with an unsupported cloud provider returns 400."""
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
cloud_provider = "unknown"
cloud_provider = "gcp"
endpoint = f"/api/v1/cloud_integrations/{cloud_provider}/accounts"
response = requests.post(
signoz.self.host_configs["8080"].get(endpoint),
headers={"Authorization": f"Bearer {admin_token}"},
json={
"config": {"unknown": {"deploymentRegion": "us-central1", "regions": ["us-central1"]}},
"config": {"gcp": {"deploymentRegion": "us-central1", "regions": ["us-central1"]}},
"credentials": {
"sigNozApiURL": "https://test.signoz.cloud",
"sigNozApiKey": "test-key",

View File

@@ -341,7 +341,7 @@ def test_list_services_unsupported_provider(
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
response = requests.get(
signoz.self.host_configs["8080"].get("/api/v1/cloud_integrations/unknown/services"),
signoz.self.host_configs["8080"].get("/api/v1/cloud_integrations/gcp/services"),
headers={"Authorization": f"Bearer {admin_token}"},
timeout=10,
)

View File

@@ -1,3 +1,4 @@
import uuid
from collections.abc import Callable
from datetime import UTC, datetime, timedelta
from http import HTTPStatus
@@ -8,6 +9,7 @@ from sqlalchemy import sql
from wiremock.resources.mappings import Mapping
from fixtures.auth import USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD, add_license
from fixtures.logs import Logs
from fixtures.metrics import Metrics
from fixtures.types import Operation, SigNoz, TestContainerDocker
@@ -205,6 +207,147 @@ def test_public_dashboard_widget_query_range(
assert resp.status_code == HTTPStatus.BAD_REQUEST
def test_public_dashboard_widget_query_range_multi_aggregation(
signoz: SigNoz,
create_user_admin: Operation, # pylint: disable=unused-argument
make_http_mocks: Callable[[TestContainerDocker, list[Mapping]], None],
get_token: Callable[[str, str], str],
insert_logs: Callable[[list[Logs]], None],
):
"""
A logs/traces widget stores several aggregations as one comma-separated expression
(e.g. "count(), sum(latency_ms)"). The public widget query path must split it into
one aggregation per call, mirroring the frontend, before handing it to the querier.
If the split does not happen the querier receives a single malformed aggregation and
the request fails - so a successful response with two aggregations proves the split.
"""
add_license(signoz, make_http_mocks, get_token)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
# Unique per-run service so the widget query only sees this run's logs.
service_name = f"multiagg-public-{uuid.uuid4()}"
now = datetime.now(tz=UTC).replace(second=0, microsecond=0)
insert_logs(
[
Logs(
timestamp=now - timedelta(minutes=5),
resources={"service.name": service_name},
attributes={"latency_ms": 100},
body="multi-agg log 1",
),
Logs(
timestamp=now - timedelta(minutes=3),
resources={"service.name": service_name},
attributes={"latency_ms": 200},
body="multi-agg log 2",
),
Logs(
timestamp=now - timedelta(minutes=1),
resources={"service.name": service_name},
attributes={"latency_ms": 300},
body="multi-agg log 3",
),
]
)
dashboard_req = {
"title": "Multi Aggregation Public Widget",
"description": "Comma-separated aggregations must be split on the public query path",
"version": "v5",
"widgets": [
{
"id": "b2c0a1d4-9f3e-4c2a-8a7b-1e2f3a4b5c6d",
"panelTypes": "graph",
"query": {
"builder": {
"queryData": [
{
"aggregations": [{"expression": "count(), sum(latency_ms)"}],
"dataSource": "logs",
"disabled": False,
"expression": "A",
"filter": {"expression": f"service.name = '{service_name}'"},
"functions": [],
"groupBy": [],
"having": {"expression": ""},
"legend": "",
"limit": 10,
"orderBy": [],
"queryName": "A",
"source": "",
"stepInterval": 60,
}
],
"queryFormulas": [],
"queryTraceOperator": [],
},
"clickhouse_sql": [{"disabled": False, "legend": "", "name": "A", "query": ""}],
"id": "c3d1b2e5-0a4f-4d3b-9b8c-2f3a4b5c6d7e",
"promql": [{"disabled": False, "legend": "", "name": "A", "query": ""}],
"queryType": "builder",
},
}
],
}
create_response = requests.post(
signoz.self.host_configs["8080"].get("/api/v1/dashboards"),
json=dashboard_req,
headers={"Authorization": f"Bearer {admin_token}"},
timeout=2,
)
assert create_response.status_code == HTTPStatus.CREATED
dashboard_id = create_response.json()["data"]["id"]
response = requests.post(
signoz.self.host_configs["8080"].get(f"/api/v1/dashboards/{dashboard_id}/public"),
json={"timeRangeEnabled": False, "defaultTimeRange": "30m"},
headers={"Authorization": f"Bearer {admin_token}"},
timeout=2,
)
assert response.status_code == HTTPStatus.CREATED
response = requests.get(
signoz.self.host_configs["8080"].get(f"/api/v1/dashboards/{dashboard_id}/public"),
headers={"Authorization": f"Bearer {admin_token}"},
timeout=2,
)
assert response.status_code == HTTPStatus.OK
public_path = response.json()["data"]["publicPath"]
public_dashboard_id = public_path.split("/public/dashboard/")[-1]
resp = requests.get(
signoz.self.host_configs["8080"].get(f"/api/v1/public/dashboards/{public_dashboard_id}/widgets/0/query_range"),
timeout=5,
)
assert resp.status_code == HTTPStatus.OK
body = resp.json()
assert body["status"] == "success"
# The single "count(), sum(latency_ms)" expression must have been split into two
# separate aggregations on the way to the querier.
results = body["data"]["data"]["results"]
assert len(results) == 1
aggregations = results[0]["aggregations"]
assert len(aggregations) == 2
# With no group-by each aggregation produces a single series.
for aggregation in aggregations:
assert len(aggregation["series"]) == 1
assert len(aggregation["series"][0]["values"]) > 0
# Each aggregation is computed independently over the three logs: count() totals 3,
# sum(latency_ms) totals 100 + 200 + 300 = 600. Summing each aggregation's points is
# robust to step bucketing and to the order the aggregations come back in.
aggregation_totals = sorted(
sum(point["value"] for series in aggregation["series"] for point in series["values"])
for aggregation in aggregations
)
assert aggregation_totals == [3, 600]
def test_anonymous_role_has_public_dashboard_permission(
request: pytest.FixtureRequest,
signoz: SigNoz,