fix: optimize funnel creation by combining insert and update operations

Signed-off-by: Shivanshu Raj Shrivastava <shivanshu1333@gmail.com>
This commit is contained in:
Shivanshu Raj Shrivastava 2025-04-29 18:25:03 +05:30
parent 7a7428d73e
commit f8341e8958
No known key found for this signature in database
GPG Key ID: D34D26C62AC3E9AE
5 changed files with 19 additions and 62 deletions

View File

@ -1,7 +1,6 @@
package impltracefunnel
import (
"context"
"encoding/json"
"net/http"
"time"
@ -21,25 +20,6 @@ func NewHandler(module tracefunnel.Module) tracefunnel.Handler {
return &handler{module: module}
}
// Helper function to check for duplicate funnel names
func (handler *handler) checkDuplicateName(ctx context.Context, orgID string, name string, excludeID string) error {
funnels, err := handler.module.List(ctx, orgID)
if err != nil {
return errors.Newf(errors.TypeInvalidInput,
errors.CodeInvalidInput,
"failed to list funnels: %v", err)
}
for _, f := range funnels {
if f.ID.String() != excludeID && f.Name == name {
return errors.Newf(errors.TypeInvalidInput,
errors.CodeInvalidInput,
"a funnel with name '%s' already exists in this organization", name)
}
}
return nil
}
func (handler *handler) New(rw http.ResponseWriter, r *http.Request) {
var req tf.FunnelRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
@ -53,16 +33,11 @@ func (handler *handler) New(rw http.ResponseWriter, r *http.Request) {
return
}
if err := handler.checkDuplicateName(r.Context(), claims.OrgID, req.Name, ""); err != nil {
render.Error(rw, err)
return
}
funnel, err := handler.module.Create(r.Context(), req.Timestamp, req.Name, claims.UserID, claims.OrgID)
if err != nil {
render.Error(rw, errors.Newf(errors.TypeInvalidInput,
errors.CodeInvalidInput,
"failed to create funnel"))
"failed to create funnel: %v", err))
return
}
@ -97,13 +72,6 @@ func (handler *handler) UpdateSteps(rw http.ResponseWriter, r *http.Request) {
return
}
if req.Name != "" && req.Name != funnel.Name {
if err := handler.checkDuplicateName(r.Context(), claims.OrgID, req.Name, funnel.ID.String()); err != nil {
render.Error(rw, err)
return
}
}
steps, err := tracefunnel.ProcessFunnelSteps(req.Steps)
if err != nil {
render.Error(rw, err)
@ -170,13 +138,6 @@ func (handler *handler) UpdateFunnel(rw http.ResponseWriter, r *http.Request) {
return
}
if req.Name != "" && req.Name != funnel.Name {
if err := handler.checkDuplicateName(r.Context(), claims.OrgID, req.Name, funnel.ID.String()); err != nil {
render.Error(rw, err)
return
}
}
funnel.UpdatedAt = updatedAt
funnel.UpdatedBy = claims.UserID
@ -300,9 +261,7 @@ func (handler *handler) Save(rw http.ResponseWriter, r *http.Request) {
}
funnel.UpdatedAt = updatedAt
if req.UserID != "" {
funnel.UpdatedBy = claims.UserID
}
funnel.Description = req.Description
if err := handler.module.Save(r.Context(), funnel, funnel.UpdatedBy, claims.OrgID); err != nil {

View File

@ -12,10 +12,10 @@ import (
)
type module struct {
store traceFunnels.TraceFunnelStore
store traceFunnels.FunnelStore
}
func NewModule(store traceFunnels.TraceFunnelStore) tracefunnel.Module {
func NewModule(store traceFunnels.FunnelStore) tracefunnel.Module {
return &module{
store: store,
}

View File

@ -3,6 +3,7 @@ package impltracefunnel
import (
"context"
"fmt"
"strings"
"time"
"github.com/SigNoz/signoz/pkg/sqlstore"
@ -14,7 +15,7 @@ type store struct {
sqlstore sqlstore.SQLStore
}
func NewStore(sqlstore sqlstore.SQLStore) traceFunnels.TraceFunnelStore {
func NewStore(sqlstore sqlstore.SQLStore) traceFunnels.FunnelStore {
return &store{sqlstore: sqlstore}
}
@ -30,6 +31,11 @@ func (store *store) Create(ctx context.Context, funnel *traceFunnels.Funnel) err
funnel.UpdatedAt = time.Now()
}
// Set created_by if CreatedByUser is present
if funnel.CreatedByUser != nil {
funnel.CreatedBy = funnel.CreatedByUser.ID
}
_, err := store.
sqlstore.
BunDB().
@ -37,20 +43,12 @@ func (store *store) Create(ctx context.Context, funnel *traceFunnels.Funnel) err
Model(funnel).
Exec(ctx)
if err != nil {
if strings.Contains(err.Error(), "idx_trace_funnel_org_id_name") {
return fmt.Errorf("a funnel with name '%s' already exists in this organization", funnel.Name)
}
return fmt.Errorf("failed to create funnel: %v", err)
}
if funnel.CreatedByUser != nil {
_, err = store.sqlstore.BunDB().NewUpdate().
Model(funnel).
Set("created_by = ?", funnel.CreatedByUser.ID).
Where("id = ?", funnel.ID).
Exec(ctx)
if err != nil {
return fmt.Errorf("failed to update funnel user relationship: %v", err)
}
}
return nil
}
@ -83,6 +81,9 @@ func (store *store) Update(ctx context.Context, funnel *traceFunnels.Funnel) err
WherePK().
Exec(ctx)
if err != nil {
if strings.Contains(err.Error(), "idx_trace_funnel_org_id_name") {
return fmt.Errorf("a funnel with name '%s' already exists in this organization", funnel.Name)
}
return fmt.Errorf("failed to update funnel: %v", err)
}
return nil

View File

@ -52,19 +52,16 @@ func ValidateFunnelSteps(steps []tracefunnel.FunnelStep) error {
// Returns a new slice with normalized step orders, leaving the input slice unchanged.
func NormalizeFunnelSteps(steps []tracefunnel.FunnelStep) []tracefunnel.FunnelStep {
if len(steps) == 0 {
return nil
return []tracefunnel.FunnelStep{}
}
// Create a copy of the input slice
newSteps := make([]tracefunnel.FunnelStep, len(steps))
copy(newSteps, steps)
// Sort the copy
sort.Slice(newSteps, func(i, j int) bool {
return newSteps[i].Order < newSteps[j].Order
})
// Update orders in the copy
for i := range newSteps {
newSteps[i].Order = int64(i + 1)
}

View File

@ -6,7 +6,7 @@ import (
"github.com/SigNoz/signoz/pkg/valuer"
)
type TraceFunnelStore interface {
type FunnelStore interface {
Create(context.Context, *Funnel) error
Get(context.Context, valuer.UUID) (*Funnel, error)
List(context.Context, valuer.UUID) ([]*Funnel, error)