mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-10-14 01:41:30 +08:00
207 lines
5.9 KiB
Go
207 lines
5.9 KiB
Go
package traceFunnels
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"encoding/json"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/SigNoz/signoz/pkg/sqlstore"
|
|
"github.com/SigNoz/signoz/pkg/types"
|
|
)
|
|
|
|
// SQLClient handles persistence of funnels to the database
|
|
type SQLClient struct {
|
|
store sqlstore.SQLStore
|
|
}
|
|
|
|
// NewSQLClient creates a new SQL client
|
|
func NewSQLClient(store sqlstore.SQLStore) (*SQLClient, error) {
|
|
return &SQLClient{store: store}, nil
|
|
}
|
|
|
|
// SaveFunnelRequest is used to save a funnel to the database
|
|
type SaveFunnelRequest struct {
|
|
FunnelID string `json:"funnel_id"` // Required: ID of the funnel to save
|
|
UserID string `json:"user_id,omitempty"` // Optional: will use existing user ID if not provided
|
|
OrgID string `json:"org_id,omitempty"` // Optional: will use existing org ID if not provided
|
|
Tags string `json:"tags,omitempty"` // Optional: comma-separated tags
|
|
Description string `json:"description,omitempty"` // Optional: human-readable description
|
|
Timestamp int64 `json:"timestamp,omitempty"` // Optional: timestamp for update in milliseconds (uses current time if not provided)
|
|
}
|
|
|
|
// SaveFunnel saves a funnel to the database in the saved_views table
|
|
// Handles both creating new funnels and updating existing ones
|
|
func (c *SQLClient) SaveFunnel(funnel *Funnel, userID, orgID string, tags, extraData string) error {
|
|
ctx := context.Background()
|
|
db := c.store.BunDB()
|
|
|
|
// Convert funnel to JSON for storage
|
|
funnelData, err := json.Marshal(funnel)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to marshal funnel data: %v", err)
|
|
}
|
|
|
|
// Format timestamps as RFC3339
|
|
// Convert nanoseconds to milliseconds for display, then to time.Time for formatting
|
|
createdAt := time.Unix(0, funnel.CreatedAt).UTC().Format(time.RFC3339)
|
|
updatedAt := createdAt
|
|
updatedBy := userID
|
|
|
|
// If funnel has update metadata, use it
|
|
if funnel.UpdatedAt > 0 {
|
|
updatedAt = time.Unix(0, funnel.UpdatedAt).UTC().Format(time.RFC3339)
|
|
}
|
|
|
|
if funnel.UpdatedBy != "" {
|
|
updatedBy = funnel.UpdatedBy
|
|
}
|
|
|
|
// Check if the funnel already exists
|
|
var count int
|
|
var existingCreatedBy string
|
|
var existingCreatedAt string
|
|
err = db.NewRaw("SELECT COUNT(*), IFNULL(created_by, ''), IFNULL(created_at, '') FROM saved_views WHERE uuid = ? AND category = 'funnel'", funnel.ID).
|
|
Scan(ctx, &count, &existingCreatedBy, &existingCreatedAt)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to check if funnel exists: %v", err)
|
|
}
|
|
|
|
if count > 0 {
|
|
// Update existing funnel - preserve created_by and created_at
|
|
_, err = db.NewRaw(
|
|
"UPDATE saved_views SET name = ?, data = ?, updated_by = ?, updated_at = ?, tags = ?, extra_data = ? WHERE uuid = ? AND category = 'funnel'",
|
|
funnel.Name, string(funnelData), updatedBy, updatedAt, tags, extraData, funnel.ID,
|
|
).Exec(ctx)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to update funnel: %v", err)
|
|
}
|
|
} else {
|
|
// Insert new funnel - set both created and updated fields
|
|
savedView := &types.SavedView{
|
|
TimeAuditable: types.TimeAuditable{
|
|
CreatedAt: time.Now(),
|
|
UpdatedAt: time.Now(),
|
|
},
|
|
UserAuditable: types.UserAuditable{
|
|
CreatedBy: userID,
|
|
UpdatedBy: updatedBy,
|
|
},
|
|
UUID: funnel.ID,
|
|
Name: funnel.Name,
|
|
Category: "funnel",
|
|
SourcePage: "trace-funnels",
|
|
OrgID: orgID,
|
|
Tags: tags,
|
|
Data: string(funnelData),
|
|
ExtraData: extraData,
|
|
}
|
|
|
|
_, err = db.NewInsert().Model(savedView).Exec(ctx)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to insert funnel: %v", err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetFunnelFromDB retrieves a funnel from the database
|
|
func (c *SQLClient) GetFunnelFromDB(funnelID string) (*Funnel, error) {
|
|
ctx := context.Background()
|
|
db := c.store.BunDB()
|
|
|
|
var savedView types.SavedView
|
|
err := db.NewSelect().
|
|
Model(&savedView).
|
|
Where("uuid = ? AND category = 'funnel'", funnelID).
|
|
Scan(ctx)
|
|
|
|
if err != nil {
|
|
if err == sql.ErrNoRows {
|
|
return nil, fmt.Errorf("funnel not found")
|
|
}
|
|
return nil, fmt.Errorf("failed to get funnel: %v", err)
|
|
}
|
|
|
|
var funnel Funnel
|
|
if err := json.Unmarshal([]byte(savedView.Data), &funnel); err != nil {
|
|
return nil, fmt.Errorf("failed to unmarshal funnel data: %v", err)
|
|
}
|
|
|
|
return &funnel, nil
|
|
}
|
|
|
|
// ListFunnelsFromDB lists all funnels from the database
|
|
func (c *SQLClient) ListFunnelsFromDB(orgID string) ([]*Funnel, error) {
|
|
ctx := context.Background()
|
|
db := c.store.BunDB()
|
|
|
|
var savedViews []types.SavedView
|
|
err := db.NewSelect().
|
|
Model(&savedViews).
|
|
Where("category = 'funnel' AND org_id = ?", orgID).
|
|
Scan(ctx)
|
|
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to list funnels: %v", err)
|
|
}
|
|
|
|
var funnels []*Funnel
|
|
for _, view := range savedViews {
|
|
var funnel Funnel
|
|
if err := json.Unmarshal([]byte(view.Data), &funnel); err != nil {
|
|
return nil, fmt.Errorf("failed to unmarshal funnel data: %v", err)
|
|
}
|
|
|
|
funnels = append(funnels, &funnel)
|
|
}
|
|
|
|
return funnels, nil
|
|
}
|
|
|
|
// ListAllFunnelsFromDB lists all funnels from the database without org_id filter
|
|
func (c *SQLClient) ListAllFunnelsFromDB() ([]*Funnel, error) {
|
|
ctx := context.Background()
|
|
db := c.store.BunDB()
|
|
|
|
var savedViews []types.SavedView
|
|
err := db.NewSelect().
|
|
Model(&savedViews).
|
|
Where("category = 'funnel'").
|
|
Scan(ctx)
|
|
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to list all funnels: %v", err)
|
|
}
|
|
|
|
var funnels []*Funnel
|
|
for _, view := range savedViews {
|
|
var funnel Funnel
|
|
if err := json.Unmarshal([]byte(view.Data), &funnel); err != nil {
|
|
return nil, fmt.Errorf("failed to unmarshal funnel data: %v", err)
|
|
}
|
|
|
|
funnels = append(funnels, &funnel)
|
|
}
|
|
|
|
return funnels, nil
|
|
}
|
|
|
|
// DeleteFunnelFromDB deletes a funnel from the database
|
|
func (c *SQLClient) DeleteFunnelFromDB(funnelID string) error {
|
|
ctx := context.Background()
|
|
db := c.store.BunDB()
|
|
|
|
_, err := db.NewDelete().
|
|
Model(&types.SavedView{}).
|
|
Where("uuid = ? AND category = 'funnel'", funnelID).
|
|
Exec(ctx)
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("failed to delete funnel: %v", err)
|
|
}
|
|
return nil
|
|
}
|