Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: aws integrations: dashboards #7058

Merged
merged 11 commits into from
Feb 13, 2025
14 changes: 3 additions & 11 deletions pkg/query-service/app/cloudintegrations/availableServices.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,18 +190,10 @@ func validateServiceDefinition(s *CloudServiceDetails) error {
// Validate dashboard data
seenDashboardIds := map[string]interface{}{}
for _, dd := range s.Assets.Dashboards {
did, exists := dd["id"]
if !exists {
return fmt.Errorf("id is required. not specified in dashboard titled %v", dd["title"])
if _, seen := seenDashboardIds[dd.Id]; seen {
return fmt.Errorf("multiple dashboards found with id %s", dd.Id)
}
dashboardId, ok := did.(string)
if !ok {
return fmt.Errorf("id must be string in dashboard titled %v", dd["title"])
}
if _, seen := seenDashboardIds[dashboardId]; seen {
return fmt.Errorf("multiple dashboards found with id %s", dashboardId)
}
seenDashboardIds[dashboardId] = nil
seenDashboardIds[dd.Id] = nil
}

if s.TelemetryCollectionStrategy == nil {
Expand Down
168 changes: 166 additions & 2 deletions pkg/query-service/app/cloudintegrations/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@ package cloudintegrations
import (
"context"
"fmt"
"net/url"
"slices"
"strings"
"time"

"github.com/jmoiron/sqlx"
"go.signoz.io/signoz/pkg/query-service/app/dashboards"
"go.signoz.io/signoz/pkg/query-service/model"
"golang.org/x/exp/maps"
)
Expand Down Expand Up @@ -120,12 +123,30 @@ func (c *Controller) GenerateConnectionUrl(
return nil, model.WrapApiError(apiErr, "couldn't upsert cloud account")
}

// TODO(Raj): Add actual cloudformation template for AWS integration after it has been shipped.
// TODO(Raj): parameterized this in follow up changes
agentVersion := "latest"

connectionUrl := fmt.Sprintf(
"https://%s.console.aws.amazon.com/cloudformation/home?region=%s#/stacks/quickcreate?stackName=SigNozIntegration/",
"https://%s.console.aws.amazon.com/cloudformation/home?region=%s#/stacks/quickcreate?",
req.AgentConfig.Region, req.AgentConfig.Region,
)

for qp, value := range map[string]string{
"param_SigNozIntegrationAgentVersion": agentVersion,
"param_SigNozApiUrl": req.AgentConfig.SigNozAPIUrl,
"param_SigNozApiKey": req.AgentConfig.SigNozAPIKey,
"param_SigNozAccountId": account.Id,
"param_IngestionUrl": req.AgentConfig.IngestionUrl,
"param_IngestionKey": req.AgentConfig.IngestionKey,
"stackName": "signoz-integration",
"templateURL": fmt.Sprintf(
"https://signoz-integrations.s3.us-east-1.amazonaws.com/aws-quickcreate-template-%s.json",
agentVersion,
),
} {
connectionUrl += fmt.Sprintf("&%s=%s", qp, url.QueryEscape(value))
}

return &GenerateConnectionUrlResponse{
AccountId: account.Id,
ConnectionUrl: connectionUrl,
Expand Down Expand Up @@ -403,6 +424,18 @@ func (c *Controller) GetServiceDetails(

if config != nil {
service.Config = config

if config.Metrics != nil && config.Metrics.Enabled {
// add links to service dashboards, making them clickable.
for i, d := range service.Assets.Dashboards {
dashboardUuid := c.dashboardUuid(
cloudProvider, serviceId, d.Id,
)
service.Assets.Dashboards[i].Url = fmt.Sprintf(
"/dashboard/%s", dashboardUuid,
)
}
}
}
}

Expand Down Expand Up @@ -456,3 +489,134 @@ func (c *Controller) UpdateServiceConfig(
Config: *updatedConfig,
}, nil
}

// All dashboards that are available based on cloud integrations configuration
// across all cloud providers
func (c *Controller) AvailableDashboards(ctx context.Context) (
[]dashboards.Dashboard, *model.ApiError,
) {
allDashboards := []dashboards.Dashboard{}

for _, provider := range []string{"aws"} {
providerDashboards, apiErr := c.AvailableDashboardsForCloudProvider(ctx, provider)
if apiErr != nil {
return nil, model.WrapApiError(
apiErr, fmt.Sprintf("couldn't get available dashboards for %s", provider),
)
}

allDashboards = append(allDashboards, providerDashboards...)
}

return allDashboards, nil
}

func (c *Controller) AvailableDashboardsForCloudProvider(
ctx context.Context, cloudProvider string,
) ([]dashboards.Dashboard, *model.ApiError) {

accountRecords, apiErr := c.accountsRepo.listConnected(ctx, cloudProvider)
if apiErr != nil {
return nil, model.WrapApiError(apiErr, "couldn't list connected cloud accounts")
}

// for v0, service dashboards are only available when metrics are enabled.
servicesWithAvailableMetrics := map[string]*time.Time{}

for _, ar := range accountRecords {
if ar.CloudAccountId != nil {
configsBySvcId, apiErr := c.serviceConfigRepo.getAllForAccount(
ctx, cloudProvider, *ar.CloudAccountId,
)
if apiErr != nil {
return nil, apiErr
}

for svcId, config := range configsBySvcId {
if config.Metrics != nil && config.Metrics.Enabled {
servicesWithAvailableMetrics[svcId] = &ar.CreatedAt
}
}
}
}

allServices, apiErr := listCloudProviderServices(cloudProvider)
if apiErr != nil {
return nil, apiErr
}

svcDashboards := []dashboards.Dashboard{}
for _, svc := range allServices {
serviceDashboardsCreatedAt := servicesWithAvailableMetrics[svc.Id]
if serviceDashboardsCreatedAt != nil {
for _, d := range svc.Assets.Dashboards {
isLocked := 1
author := fmt.Sprintf("%s-integration", cloudProvider)
svcDashboards = append(svcDashboards, dashboards.Dashboard{
Uuid: c.dashboardUuid(cloudProvider, svc.Id, d.Id),
Locked: &isLocked,
Data: *d.Definition,
CreatedAt: *serviceDashboardsCreatedAt,
CreateBy: &author,
UpdatedAt: *serviceDashboardsCreatedAt,
UpdateBy: &author,
})
}
servicesWithAvailableMetrics[svc.Id] = nil
}
}

return svcDashboards, nil
}
func (c *Controller) GetDashboardById(
ctx context.Context,
dashboardUuid string,
) (*dashboards.Dashboard, *model.ApiError) {
cloudProvider, _, _, apiErr := c.parseDashboardUuid(dashboardUuid)
if apiErr != nil {
return nil, apiErr
}

allDashboards, apiErr := c.AvailableDashboardsForCloudProvider(ctx, cloudProvider)
if apiErr != nil {
return nil, model.WrapApiError(
apiErr, fmt.Sprintf("couldn't list available dashboards"),
)
}

for _, d := range allDashboards {
if d.Uuid == dashboardUuid {
return &d, nil
}
}

return nil, model.NotFoundError(fmt.Errorf(
"couldn't find dashboard with uuid: %s", dashboardUuid,
))
}

func (c *Controller) dashboardUuid(
cloudProvider string, svcId string, dashboardId string,
) string {
return fmt.Sprintf(
"cloud-integration--%s--%s--%s", cloudProvider, svcId, dashboardId,
)
}

func (c *Controller) parseDashboardUuid(dashboardUuid string) (
cloudProvider string, svcId string, dashboardId string, apiErr *model.ApiError,
) {
parts := strings.SplitN(dashboardUuid, "--", 4)
if len(parts) != 4 || parts[0] != "cloud-integration" {
return "", "", "", model.BadRequest(fmt.Errorf(
"invalid cloud integration dashboard id",
))
}

return parts[1], parts[2], parts[3], nil
}

func (c *Controller) IsCloudIntegrationDashboardUuid(dashboardUuid string) bool {
_, _, _, apiErr := c.parseDashboardUuid(dashboardUuid)
return apiErr == nil
}
11 changes: 10 additions & 1 deletion pkg/query-service/app/cloudintegrations/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,16 @@ type CloudServiceMetricsConfig struct {
}

type CloudServiceAssets struct {
Dashboards []dashboards.Data `json:"dashboards"`
Dashboards []CloudServiceDashboard `json:"dashboards"`
}

type CloudServiceDashboard struct {
Id string `json:"id"`
Url string `json:"url"`
Title string `json:"title"`
Description string `json:"description"`
Image string `json:"image"`
Definition *dashboards.Data `json:"definition,omitempty"`
}

type SupportedSignals struct {
Expand Down
Loading
Loading