mirror of
https://github.com/SigNoz/signoz.git
synced 2025-12-18 07:56:56 +00:00
* fix: prevent creation of funnels with duplicate names
- Fixed Update method to validate duplicate names before updating
- Added proper duplicate name validation that excludes the current funnel being updated
- Fixed incorrect error wrapping in Update method that was marking all errors as "already exists"
- Fixed typo in error message ("funnelr" -> "funnel")
- Added comprehensive tests for duplicate name validation in both Create and Update operations
- Used internal errors package for consistent error handling
The funnel API now properly prevents creating or updating funnels with duplicate names
within the same organization, resolving issues where duplicate funnels could be created
but would fail during retrieval.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
* fix: returning error instance
* fix: implement database transactions for funnel creation and updates
- Wrap check-and-create operations in Bun transactions to prevent race conditions
- Apply transaction pattern to both Create() and Update() methods
- Ensures atomic operations when checking for duplicate funnel names
- Prevents concurrent requests from creating duplicate funnels
- Follows existing transaction patterns from user store implementation
Addresses PR feedback for race condition prevention
---------
Co-authored-by: Ankit Nayan <ankitnayan@Ankits-MacBook-Pro.local>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Shaheer Kochai <ashaheerki@gmail.com>
99 lines
2.6 KiB
Go
99 lines
2.6 KiB
Go
package impltracefunnel
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/SigNoz/signoz/pkg/modules/tracefunnel"
|
|
"github.com/SigNoz/signoz/pkg/types"
|
|
traceFunnels "github.com/SigNoz/signoz/pkg/types/tracefunneltypes"
|
|
"github.com/SigNoz/signoz/pkg/valuer"
|
|
)
|
|
|
|
type module struct {
|
|
store traceFunnels.FunnelStore
|
|
}
|
|
|
|
func NewModule(store traceFunnels.FunnelStore) tracefunnel.Module {
|
|
return &module{
|
|
store: store,
|
|
}
|
|
}
|
|
|
|
func (module *module) Create(ctx context.Context, timestamp int64, name string, userID valuer.UUID, orgID valuer.UUID) (*traceFunnels.StorableFunnel, error) {
|
|
funnel := &traceFunnels.StorableFunnel{
|
|
Name: name,
|
|
OrgID: orgID,
|
|
}
|
|
funnel.CreatedAt = time.Unix(0, timestamp*1000000) // Convert to nanoseconds
|
|
funnel.CreatedBy = userID.String()
|
|
|
|
// Set up the user relationship
|
|
funnel.CreatedByUser = &types.User{
|
|
Identifiable: types.Identifiable{
|
|
ID: userID,
|
|
},
|
|
}
|
|
|
|
if funnel.ID.IsZero() {
|
|
funnel.ID = valuer.GenerateUUID()
|
|
}
|
|
|
|
if funnel.CreatedAt.IsZero() {
|
|
funnel.CreatedAt = time.Now()
|
|
}
|
|
if funnel.UpdatedAt.IsZero() {
|
|
funnel.UpdatedAt = time.Now()
|
|
}
|
|
|
|
// Set created_by if CreatedByUser is present
|
|
if funnel.CreatedByUser != nil {
|
|
funnel.CreatedBy = funnel.CreatedByUser.Identifiable.ID.String()
|
|
}
|
|
|
|
err := module.store.Create(ctx, funnel)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return funnel, nil
|
|
}
|
|
|
|
// Get gets a funnel by ID
|
|
func (module *module) Get(ctx context.Context, funnelID valuer.UUID, orgID valuer.UUID) (*traceFunnels.StorableFunnel, error) {
|
|
return module.store.Get(ctx, funnelID, orgID)
|
|
}
|
|
|
|
// Update updates a funnel
|
|
func (module *module) Update(ctx context.Context, funnel *traceFunnels.StorableFunnel, userID valuer.UUID) error {
|
|
funnel.UpdatedBy = userID.String()
|
|
return module.store.Update(ctx, funnel)
|
|
}
|
|
|
|
// List lists all funnels for an organization
|
|
func (module *module) List(ctx context.Context, orgID valuer.UUID) ([]*traceFunnels.StorableFunnel, error) {
|
|
funnels, err := module.store.List(ctx, orgID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to list funnels: %v", err)
|
|
}
|
|
|
|
return funnels, nil
|
|
}
|
|
|
|
// Delete deletes a funnel
|
|
func (module *module) Delete(ctx context.Context, funnelID valuer.UUID, orgID valuer.UUID) error {
|
|
return module.store.Delete(ctx, funnelID, orgID)
|
|
}
|
|
|
|
// GetFunnelMetadata gets metadata for a funnel
|
|
func (module *module) GetFunnelMetadata(ctx context.Context, funnelID valuer.UUID, orgID valuer.UUID) (int64, int64, string, error) {
|
|
funnel, err := module.store.Get(ctx, funnelID, orgID)
|
|
if err != nil {
|
|
return 0, 0, "", err
|
|
}
|
|
|
|
return funnel.CreatedAt.UnixNano() / 1000000, funnel.UpdatedAt.UnixNano() / 1000000, funnel.Description, nil
|
|
}
|