fix: review comments

Signed-off-by: Shivanshu Raj Shrivastava <shivanshu1333@gmail.com>
This commit is contained in:
Shivanshu Raj Shrivastava 2025-05-22 14:44:56 +05:30
parent ea1f4e8253
commit 78a5d7e39e
No known key found for this signature in database
GPG Key ID: D34D26C62AC3E9AE
12 changed files with 202 additions and 341 deletions

View File

@ -21,13 +21,13 @@ func NewHandler(module tracefunnel.Module) tracefunnel.Handler {
} }
func (handler *handler) New(rw http.ResponseWriter, r *http.Request) { func (handler *handler) New(rw http.ResponseWriter, r *http.Request) {
var req tf.FunnelRequest var req tf.PostableFunnel
if err := json.NewDecoder(r.Body).Decode(&req); err != nil { if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
render.Error(rw, err) render.Error(rw, err)
return return
} }
claims, err := tracefunnel.GetClaims(r) claims, err := tf.GetClaims(r)
if err != nil { if err != nil {
render.Error(rw, err) render.Error(rw, err)
return return
@ -41,24 +41,24 @@ func (handler *handler) New(rw http.ResponseWriter, r *http.Request) {
return return
} }
response := tracefunnel.ConstructFunnelResponse(funnel, claims) response := tf.ConstructFunnelResponse(funnel, claims)
render.Success(rw, http.StatusOK, response) render.Success(rw, http.StatusOK, response)
} }
func (handler *handler) UpdateSteps(rw http.ResponseWriter, r *http.Request) { func (handler *handler) UpdateSteps(rw http.ResponseWriter, r *http.Request) {
var req tf.FunnelRequest var req tf.PostableFunnel
if err := json.NewDecoder(r.Body).Decode(&req); err != nil { if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
render.Error(rw, err) render.Error(rw, err)
return return
} }
claims, err := tracefunnel.GetClaims(r) claims, err := tf.GetClaims(r)
if err != nil { if err != nil {
render.Error(rw, err) render.Error(rw, err)
return return
} }
updatedAt, err := tracefunnel.ValidateAndConvertTimestamp(req.Timestamp) updatedAt, err := tf.ValidateAndConvertTimestamp(req.Timestamp)
if err != nil { if err != nil {
render.Error(rw, err) render.Error(rw, err)
return return
@ -72,7 +72,7 @@ func (handler *handler) UpdateSteps(rw http.ResponseWriter, r *http.Request) {
return return
} }
steps, err := tracefunnel.ProcessFunnelSteps(req.Steps) steps, err := tf.ProcessFunnelSteps(req.Steps)
if err != nil { if err != nil {
render.Error(rw, err) render.Error(rw, err)
return return
@ -104,24 +104,24 @@ func (handler *handler) UpdateSteps(rw http.ResponseWriter, r *http.Request) {
return return
} }
response := tracefunnel.ConstructFunnelResponse(updatedFunnel, claims) response := tf.ConstructFunnelResponse(updatedFunnel, claims)
render.Success(rw, http.StatusOK, response) render.Success(rw, http.StatusOK, response)
} }
func (handler *handler) UpdateFunnel(rw http.ResponseWriter, r *http.Request) { func (handler *handler) UpdateFunnel(rw http.ResponseWriter, r *http.Request) {
var req tf.FunnelRequest var req tf.PostableFunnel
if err := json.NewDecoder(r.Body).Decode(&req); err != nil { if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
render.Error(rw, err) render.Error(rw, err)
return return
} }
claims, err := tracefunnel.GetClaims(r) claims, err := tf.GetClaims(r)
if err != nil { if err != nil {
render.Error(rw, err) render.Error(rw, err)
return return
} }
updatedAt, err := tracefunnel.ValidateAndConvertTimestamp(req.Timestamp) updatedAt, err := tf.ValidateAndConvertTimestamp(req.Timestamp)
if err != nil { if err != nil {
render.Error(rw, err) render.Error(rw, err)
return return
@ -163,12 +163,12 @@ func (handler *handler) UpdateFunnel(rw http.ResponseWriter, r *http.Request) {
return return
} }
response := tracefunnel.ConstructFunnelResponse(updatedFunnel, claims) response := tf.ConstructFunnelResponse(updatedFunnel, claims)
render.Success(rw, http.StatusOK, response) render.Success(rw, http.StatusOK, response)
} }
func (handler *handler) List(rw http.ResponseWriter, r *http.Request) { func (handler *handler) List(rw http.ResponseWriter, r *http.Request) {
claims, err := tracefunnel.GetClaims(r) claims, err := tf.GetClaims(r)
if err != nil { if err != nil {
render.Error(rw, err) render.Error(rw, err)
return return
@ -182,9 +182,9 @@ func (handler *handler) List(rw http.ResponseWriter, r *http.Request) {
return return
} }
var response []tf.FunnelResponse var response []tf.GettableFunnel
for _, f := range funnels { for _, f := range funnels {
response = append(response, tracefunnel.ConstructFunnelResponse(f, claims)) response = append(response, tf.ConstructFunnelResponse(f, claims))
} }
render.Success(rw, http.StatusOK, response) render.Success(rw, http.StatusOK, response)
@ -202,8 +202,8 @@ func (handler *handler) Get(rw http.ResponseWriter, r *http.Request) {
return return
} }
claims, _ := tracefunnel.GetClaims(r) // Ignore error as email is optional claims, _ := tf.GetClaims(r) // Ignore error as email is optional
response := tracefunnel.ConstructFunnelResponse(funnel, claims) response := tf.ConstructFunnelResponse(funnel, claims)
render.Success(rw, http.StatusOK, response) render.Success(rw, http.StatusOK, response)
} }
@ -222,7 +222,7 @@ func (handler *handler) Delete(rw http.ResponseWriter, r *http.Request) {
} }
func (handler *handler) Save(rw http.ResponseWriter, r *http.Request) { func (handler *handler) Save(rw http.ResponseWriter, r *http.Request) {
var req tf.FunnelRequest var req tf.PostableFunnel
if err := json.NewDecoder(r.Body).Decode(&req); err != nil { if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
render.Error(rw, errors.Newf(errors.TypeInvalidInput, render.Error(rw, errors.Newf(errors.TypeInvalidInput,
errors.CodeInvalidInput, errors.CodeInvalidInput,
@ -230,7 +230,7 @@ func (handler *handler) Save(rw http.ResponseWriter, r *http.Request) {
return return
} }
claims, err := tracefunnel.GetClaims(r) claims, err := tf.GetClaims(r)
if err != nil { if err != nil {
render.Error(rw, err) render.Error(rw, err)
return return
@ -247,14 +247,14 @@ func (handler *handler) Save(rw http.ResponseWriter, r *http.Request) {
updateTimestamp := req.Timestamp updateTimestamp := req.Timestamp
if updateTimestamp == 0 { if updateTimestamp == 0 {
updateTimestamp = time.Now().UnixMilli() updateTimestamp = time.Now().UnixMilli()
} else if !tracefunnel.ValidateTimestampIsMilliseconds(updateTimestamp) { } else if !tf.ValidateTimestampIsMilliseconds(updateTimestamp) {
render.Error(rw, errors.Newf(errors.TypeInvalidInput, render.Error(rw, errors.Newf(errors.TypeInvalidInput,
errors.CodeInvalidInput, errors.CodeInvalidInput,
"timestamp must be in milliseconds format (13 digits)")) "timestamp must be in milliseconds format (13 digits)"))
return return
} }
updatedAt, err := tracefunnel.ValidateAndConvertTimestamp(updateTimestamp) updatedAt, err := tf.ValidateAndConvertTimestamp(updateTimestamp)
if err != nil { if err != nil {
render.Error(rw, err) render.Error(rw, err)
return return
@ -279,7 +279,7 @@ func (handler *handler) Save(rw http.ResponseWriter, r *http.Request) {
return return
} }
resp := tf.FunnelResponse{ resp := tf.GettableFunnel{
FunnelName: funnel.Name, FunnelName: funnel.Name,
CreatedAt: createdAtMillis, CreatedAt: createdAtMillis,
UpdatedAt: updatedAtMillis, UpdatedAt: updatedAtMillis,

View File

@ -22,24 +22,24 @@ type MockModule struct {
mock.Mock mock.Mock
} }
func (m *MockModule) Create(ctx context.Context, timestamp int64, name string, userID string, orgID string) (*traceFunnels.Funnel, error) { func (m *MockModule) Create(ctx context.Context, timestamp int64, name string, userID string, orgID string) (*traceFunnels.StorableFunnel, error) {
args := m.Called(ctx, timestamp, name, userID, orgID) args := m.Called(ctx, timestamp, name, userID, orgID)
return args.Get(0).(*traceFunnels.Funnel), args.Error(1) return args.Get(0).(*traceFunnels.StorableFunnel), args.Error(1)
} }
func (m *MockModule) Get(ctx context.Context, funnelID string) (*traceFunnels.Funnel, error) { func (m *MockModule) Get(ctx context.Context, funnelID string) (*traceFunnels.StorableFunnel, error) {
args := m.Called(ctx, funnelID) args := m.Called(ctx, funnelID)
return args.Get(0).(*traceFunnels.Funnel), args.Error(1) return args.Get(0).(*traceFunnels.StorableFunnel), args.Error(1)
} }
func (m *MockModule) Update(ctx context.Context, funnel *traceFunnels.Funnel, userID string) error { func (m *MockModule) Update(ctx context.Context, funnel *traceFunnels.StorableFunnel, userID string) error {
args := m.Called(ctx, funnel, userID) args := m.Called(ctx, funnel, userID)
return args.Error(0) return args.Error(0)
} }
func (m *MockModule) List(ctx context.Context, orgID string) ([]*traceFunnels.Funnel, error) { func (m *MockModule) List(ctx context.Context, orgID string) ([]*traceFunnels.StorableFunnel, error) {
args := m.Called(ctx, orgID) args := m.Called(ctx, orgID)
return args.Get(0).([]*traceFunnels.Funnel), args.Error(1) return args.Get(0).([]*traceFunnels.StorableFunnel), args.Error(1)
} }
func (m *MockModule) Delete(ctx context.Context, funnelID string) error { func (m *MockModule) Delete(ctx context.Context, funnelID string) error {
@ -47,7 +47,7 @@ func (m *MockModule) Delete(ctx context.Context, funnelID string) error {
return args.Error(0) return args.Error(0)
} }
func (m *MockModule) Save(ctx context.Context, funnel *traceFunnels.Funnel, userID string, orgID string) error { func (m *MockModule) Save(ctx context.Context, funnel *traceFunnels.StorableFunnel, userID string, orgID string) error {
args := m.Called(ctx, funnel, userID, orgID) args := m.Called(ctx, funnel, userID, orgID)
return args.Error(0) return args.Error(0)
} }
@ -61,7 +61,7 @@ func TestHandler_New(t *testing.T) {
mockModule := new(MockModule) mockModule := new(MockModule)
handler := NewHandler(mockModule) handler := NewHandler(mockModule)
reqBody := traceFunnels.FunnelRequest{ reqBody := traceFunnels.PostableFunnel{
Name: "test-funnel", Name: "test-funnel",
Timestamp: time.Now().UnixMilli(), Timestamp: time.Now().UnixMilli(),
} }
@ -81,7 +81,7 @@ func TestHandler_New(t *testing.T) {
rr := httptest.NewRecorder() rr := httptest.NewRecorder()
funnelID := valuer.GenerateUUID() funnelID := valuer.GenerateUUID()
expectedFunnel := &traceFunnels.Funnel{ expectedFunnel := &traceFunnels.StorableFunnel{
BaseMetadata: traceFunnels.BaseMetadata{ BaseMetadata: traceFunnels.BaseMetadata{
Identifiable: types.Identifiable{ Identifiable: types.Identifiable{
ID: funnelID, ID: funnelID,
@ -91,7 +91,7 @@ func TestHandler_New(t *testing.T) {
}, },
} }
mockModule.On("List", req.Context(), orgID).Return([]*traceFunnels.Funnel{}, nil) mockModule.On("List", req.Context(), orgID).Return([]*traceFunnels.StorableFunnel{}, nil)
mockModule.On("Create", req.Context(), reqBody.Timestamp, reqBody.Name, "user-123", orgID).Return(expectedFunnel, nil) mockModule.On("Create", req.Context(), reqBody.Timestamp, reqBody.Name, "user-123", orgID).Return(expectedFunnel, nil)
handler.New(rr, req) handler.New(rr, req)
@ -100,7 +100,7 @@ func TestHandler_New(t *testing.T) {
var response struct { var response struct {
Status string `json:"status"` Status string `json:"status"`
Data traceFunnels.FunnelResponse `json:"data"` Data traceFunnels.GettableFunnel `json:"data"`
} }
err := json.Unmarshal(rr.Body.Bytes(), &response) err := json.Unmarshal(rr.Body.Bytes(), &response)
assert.NoError(t, err) assert.NoError(t, err)
@ -120,10 +120,10 @@ func TestHandler_Update(t *testing.T) {
funnelID := valuer.GenerateUUID() funnelID := valuer.GenerateUUID()
orgID := valuer.GenerateUUID().String() orgID := valuer.GenerateUUID().String()
reqBody := traceFunnels.FunnelRequest{ reqBody := traceFunnels.PostableFunnel{
FunnelID: funnelID, FunnelID: funnelID,
Name: "updated-funnel", Name: "updated-funnel",
Steps: []traceFunnels.FunnelStep{ Steps: []*traceFunnels.FunnelStep{
{ {
ID: valuer.GenerateUUID(), ID: valuer.GenerateUUID(),
Name: "Step 1", Name: "Step 1",
@ -161,7 +161,7 @@ func TestHandler_Update(t *testing.T) {
rr := httptest.NewRecorder() rr := httptest.NewRecorder()
// Set up mock expectations // Set up mock expectations
existingFunnel := &traceFunnels.Funnel{ existingFunnel := &traceFunnels.StorableFunnel{
BaseMetadata: traceFunnels.BaseMetadata{ BaseMetadata: traceFunnels.BaseMetadata{
Identifiable: types.Identifiable{ Identifiable: types.Identifiable{
ID: funnelID, ID: funnelID,
@ -183,7 +183,7 @@ func TestHandler_Update(t *testing.T) {
}, },
} }
updatedFunnel := &traceFunnels.Funnel{ updatedFunnel := &traceFunnels.StorableFunnel{
BaseMetadata: traceFunnels.BaseMetadata{ BaseMetadata: traceFunnels.BaseMetadata{
Identifiable: types.Identifiable{ Identifiable: types.Identifiable{
ID: funnelID, ID: funnelID,
@ -209,9 +209,9 @@ func TestHandler_Update(t *testing.T) {
// First Get call to validate the funnel exists // First Get call to validate the funnel exists
mockModule.On("Get", req.Context(), funnelID.String()).Return(existingFunnel, nil).Once() mockModule.On("Get", req.Context(), funnelID.String()).Return(existingFunnel, nil).Once()
// List call to check for name conflicts // List call to check for name conflicts
mockModule.On("List", req.Context(), orgID).Return([]*traceFunnels.Funnel{}, nil).Once() mockModule.On("List", req.Context(), orgID).Return([]*traceFunnels.StorableFunnel{}, nil).Once()
// Update call to save the changes // Update call to save the changes
mockModule.On("Update", req.Context(), mock.MatchedBy(func(f *traceFunnels.Funnel) bool { mockModule.On("Update", req.Context(), mock.MatchedBy(func(f *traceFunnels.StorableFunnel) bool {
return f.Name == reqBody.Name && return f.Name == reqBody.Name &&
f.ID.String() == funnelID.String() && f.ID.String() == funnelID.String() &&
len(f.Steps) == len(reqBody.Steps) && len(f.Steps) == len(reqBody.Steps) &&
@ -233,7 +233,7 @@ func TestHandler_Update(t *testing.T) {
var response struct { var response struct {
Status string `json:"status"` Status string `json:"status"`
Data traceFunnels.FunnelResponse `json:"data"` Data traceFunnels.GettableFunnel `json:"data"`
} }
err = json.Unmarshal(rr.Body.Bytes(), &response) err = json.Unmarshal(rr.Body.Bytes(), &response)
assert.NoError(t, err) assert.NoError(t, err)
@ -261,7 +261,7 @@ func TestHandler_List(t *testing.T) {
funnel1ID := valuer.GenerateUUID() funnel1ID := valuer.GenerateUUID()
funnel2ID := valuer.GenerateUUID() funnel2ID := valuer.GenerateUUID()
expectedFunnels := []*traceFunnels.Funnel{ expectedFunnels := []*traceFunnels.StorableFunnel{
{ {
BaseMetadata: traceFunnels.BaseMetadata{ BaseMetadata: traceFunnels.BaseMetadata{
Identifiable: types.Identifiable{ Identifiable: types.Identifiable{
@ -290,7 +290,7 @@ func TestHandler_List(t *testing.T) {
var response struct { var response struct {
Status string `json:"status"` Status string `json:"status"`
Data []traceFunnels.FunnelResponse `json:"data"` Data []traceFunnels.GettableFunnel `json:"data"`
} }
err := json.Unmarshal(rr.Body.Bytes(), &response) err := json.Unmarshal(rr.Body.Bytes(), &response)
assert.NoError(t, err) assert.NoError(t, err)
@ -312,7 +312,7 @@ func TestHandler_Get(t *testing.T) {
rr := httptest.NewRecorder() rr := httptest.NewRecorder()
expectedFunnel := &traceFunnels.Funnel{ expectedFunnel := &traceFunnels.StorableFunnel{
BaseMetadata: traceFunnels.BaseMetadata{ BaseMetadata: traceFunnels.BaseMetadata{
Identifiable: types.Identifiable{ Identifiable: types.Identifiable{
ID: funnelID, ID: funnelID,
@ -330,7 +330,7 @@ func TestHandler_Get(t *testing.T) {
var response struct { var response struct {
Status string `json:"status"` Status string `json:"status"`
Data traceFunnels.FunnelResponse `json:"data"` Data traceFunnels.GettableFunnel `json:"data"`
} }
err := json.Unmarshal(rr.Body.Bytes(), &response) err := json.Unmarshal(rr.Body.Bytes(), &response)
assert.NoError(t, err) assert.NoError(t, err)
@ -364,7 +364,7 @@ func TestHandler_Save(t *testing.T) {
mockModule := new(MockModule) mockModule := new(MockModule)
handler := NewHandler(mockModule) handler := NewHandler(mockModule)
reqBody := traceFunnels.FunnelRequest{ reqBody := traceFunnels.PostableFunnel{
FunnelID: valuer.GenerateUUID(), FunnelID: valuer.GenerateUUID(),
Description: "updated description", Description: "updated description",
Timestamp: time.Now().UnixMilli(), Timestamp: time.Now().UnixMilli(),
@ -384,7 +384,7 @@ func TestHandler_Save(t *testing.T) {
rr := httptest.NewRecorder() rr := httptest.NewRecorder()
existingFunnel := &traceFunnels.Funnel{ existingFunnel := &traceFunnels.StorableFunnel{
BaseMetadata: traceFunnels.BaseMetadata{ BaseMetadata: traceFunnels.BaseMetadata{
Identifiable: types.Identifiable{ Identifiable: types.Identifiable{
ID: reqBody.FunnelID, ID: reqBody.FunnelID,
@ -395,7 +395,7 @@ func TestHandler_Save(t *testing.T) {
} }
mockModule.On("Get", req.Context(), reqBody.FunnelID.String()).Return(existingFunnel, nil) mockModule.On("Get", req.Context(), reqBody.FunnelID.String()).Return(existingFunnel, nil)
mockModule.On("Save", req.Context(), mock.MatchedBy(func(f *traceFunnels.Funnel) bool { mockModule.On("Save", req.Context(), mock.MatchedBy(func(f *traceFunnels.StorableFunnel) bool {
return f.ID.String() == reqBody.FunnelID.String() && return f.ID.String() == reqBody.FunnelID.String() &&
f.Name == existingFunnel.Name && f.Name == existingFunnel.Name &&
f.Description == reqBody.Description && f.Description == reqBody.Description &&
@ -410,7 +410,7 @@ func TestHandler_Save(t *testing.T) {
var response struct { var response struct {
Status string `json:"status"` Status string `json:"status"`
Data traceFunnels.FunnelResponse `json:"data"` Data traceFunnels.GettableFunnel `json:"data"`
} }
err := json.Unmarshal(rr.Body.Bytes(), &response) err := json.Unmarshal(rr.Body.Bytes(), &response)
assert.NoError(t, err) assert.NoError(t, err)

View File

@ -21,13 +21,13 @@ func NewModule(store traceFunnels.FunnelStore) tracefunnel.Module {
} }
} }
func (module *module) Create(ctx context.Context, timestamp int64, name string, userID string, orgID string) (*traceFunnels.Funnel, error) { func (module *module) Create(ctx context.Context, timestamp int64, name string, userID string, orgID string) (*traceFunnels.StorableFunnel, error) {
orgUUID, err := valuer.NewUUID(orgID) orgUUID, err := valuer.NewUUID(orgID)
if err != nil { if err != nil {
return nil, fmt.Errorf("invalid org ID: %v", err) return nil, fmt.Errorf("invalid org ID: %v", err)
} }
funnel := &traceFunnels.Funnel{ funnel := &traceFunnels.StorableFunnel{
BaseMetadata: traceFunnels.BaseMetadata{ BaseMetadata: traceFunnels.BaseMetadata{
Name: name, Name: name,
OrgID: orgUUID, OrgID: orgUUID,
@ -43,6 +43,22 @@ func (module *module) Create(ctx context.Context, timestamp int64, name string,
}, },
} }
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()
}
if err := module.store.Create(ctx, funnel); err != nil { if err := module.store.Create(ctx, funnel); err != nil {
return nil, fmt.Errorf("failed to create funnel: %v", err) return nil, fmt.Errorf("failed to create funnel: %v", err)
} }
@ -51,7 +67,7 @@ func (module *module) Create(ctx context.Context, timestamp int64, name string,
} }
// Get gets a funnel by ID // Get gets a funnel by ID
func (module *module) Get(ctx context.Context, funnelID string) (*traceFunnels.Funnel, error) { func (module *module) Get(ctx context.Context, funnelID string) (*traceFunnels.StorableFunnel, error) {
uuid, err := valuer.NewUUID(funnelID) uuid, err := valuer.NewUUID(funnelID)
if err != nil { if err != nil {
return nil, fmt.Errorf("invalid funnel ID: %v", err) return nil, fmt.Errorf("invalid funnel ID: %v", err)
@ -60,13 +76,13 @@ func (module *module) Get(ctx context.Context, funnelID string) (*traceFunnels.F
} }
// Update updates a funnel // Update updates a funnel
func (module *module) Update(ctx context.Context, funnel *traceFunnels.Funnel, userID string) error { func (module *module) Update(ctx context.Context, funnel *traceFunnels.StorableFunnel, userID string) error {
funnel.UpdatedBy = userID funnel.UpdatedBy = userID
return module.store.Update(ctx, funnel) return module.store.Update(ctx, funnel)
} }
// List lists all funnels for an organization // List lists all funnels for an organization
func (module *module) List(ctx context.Context, orgID string) ([]*traceFunnels.Funnel, error) { func (module *module) List(ctx context.Context, orgID string) ([]*traceFunnels.StorableFunnel, error) {
orgUUID, err := valuer.NewUUID(orgID) orgUUID, err := valuer.NewUUID(orgID)
if err != nil { if err != nil {
return nil, fmt.Errorf("invalid org ID: %v", err) return nil, fmt.Errorf("invalid org ID: %v", err)
@ -90,7 +106,7 @@ func (module *module) Delete(ctx context.Context, funnelID string) error {
} }
// Save saves a funnel // Save saves a funnel
func (module *module) Save(ctx context.Context, funnel *traceFunnels.Funnel, userID string, orgID string) error { func (module *module) Save(ctx context.Context, funnel *traceFunnels.StorableFunnel, userID string, orgID string) error {
orgUUID, err := valuer.NewUUID(orgID) orgUUID, err := valuer.NewUUID(orgID)
if err != nil { if err != nil {
return fmt.Errorf("invalid org ID: %v", err) return fmt.Errorf("invalid org ID: %v", err)

View File

@ -18,23 +18,7 @@ func NewStore(sqlstore sqlstore.SQLStore) traceFunnels.FunnelStore {
return &store{sqlstore: sqlstore} return &store{sqlstore: sqlstore}
} }
func (store *store) Create(ctx context.Context, funnel *traceFunnels.Funnel) error { func (store *store) Create(ctx context.Context, funnel *traceFunnels.StorableFunnel) error {
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 := store. _, err := store.
sqlstore. sqlstore.
BunDB(). BunDB().
@ -49,8 +33,8 @@ func (store *store) Create(ctx context.Context, funnel *traceFunnels.Funnel) err
} }
// Get retrieves a funnel by ID // Get retrieves a funnel by ID
func (store *store) Get(ctx context.Context, uuid valuer.UUID) (*traceFunnels.Funnel, error) { func (store *store) Get(ctx context.Context, uuid valuer.UUID) (*traceFunnels.StorableFunnel, error) {
funnel := &traceFunnels.Funnel{} funnel := &traceFunnels.StorableFunnel{}
err := store. err := store.
sqlstore. sqlstore.
BunDB(). BunDB().
@ -66,7 +50,7 @@ func (store *store) Get(ctx context.Context, uuid valuer.UUID) (*traceFunnels.Fu
} }
// Update updates an existing funnel // Update updates an existing funnel
func (store *store) Update(ctx context.Context, funnel *traceFunnels.Funnel) error { func (store *store) Update(ctx context.Context, funnel *traceFunnels.StorableFunnel) error {
funnel.UpdatedAt = time.Now() funnel.UpdatedAt = time.Now()
_, err := store. _, err := store.
@ -83,8 +67,8 @@ func (store *store) Update(ctx context.Context, funnel *traceFunnels.Funnel) err
} }
// List retrieves all funnels for a given organization // List retrieves all funnels for a given organization
func (store *store) List(ctx context.Context, orgID valuer.UUID) ([]*traceFunnels.Funnel, error) { func (store *store) List(ctx context.Context, orgID valuer.UUID) ([]*traceFunnels.StorableFunnel, error) {
var funnels []*traceFunnels.Funnel var funnels []*traceFunnels.StorableFunnel
err := store. err := store.
sqlstore. sqlstore.
BunDB(). BunDB().
@ -105,7 +89,7 @@ func (store *store) Delete(ctx context.Context, uuid valuer.UUID) error {
sqlstore. sqlstore.
BunDB(). BunDB().
NewDelete(). NewDelete().
Model((*traceFunnels.Funnel)(nil)). Model((*traceFunnels.StorableFunnel)(nil)).
Where("id = ?", uuid).Exec(ctx) Where("id = ?", uuid).Exec(ctx)
if err != nil { if err != nil {
return fmt.Errorf("failed to delete funnel: %v", err) return fmt.Errorf("failed to delete funnel: %v", err)

View File

@ -9,17 +9,17 @@ import (
// Module defines the interface for trace funnel operations // Module defines the interface for trace funnel operations
type Module interface { type Module interface {
Create(ctx context.Context, timestamp int64, name string, userID string, orgID string) (*traceFunnels.Funnel, error) Create(ctx context.Context, timestamp int64, name string, userID string, orgID string) (*traceFunnels.StorableFunnel, error)
Get(ctx context.Context, funnelID string) (*traceFunnels.Funnel, error) Get(ctx context.Context, funnelID string) (*traceFunnels.StorableFunnel, error)
Update(ctx context.Context, funnel *traceFunnels.Funnel, userID string) error Update(ctx context.Context, funnel *traceFunnels.StorableFunnel, userID string) error
List(ctx context.Context, orgID string) ([]*traceFunnels.Funnel, error) List(ctx context.Context, orgID string) ([]*traceFunnels.StorableFunnel, error)
Delete(ctx context.Context, funnelID string) error Delete(ctx context.Context, funnelID string) error
Save(ctx context.Context, funnel *traceFunnels.Funnel, userID string, orgID string) error Save(ctx context.Context, funnel *traceFunnels.StorableFunnel, userID string, orgID string) error
GetFunnelMetadata(ctx context.Context, funnelID string) (int64, int64, string, error) GetFunnelMetadata(ctx context.Context, funnelID string) (int64, int64, string, error)
} }

View File

@ -17,22 +17,22 @@ type MockStore struct {
mock.Mock mock.Mock
} }
func (m *MockStore) Create(ctx context.Context, funnel *traceFunnels.Funnel) error { func (m *MockStore) Create(ctx context.Context, funnel *traceFunnels.StorableFunnel) error {
args := m.Called(ctx, funnel) args := m.Called(ctx, funnel)
return args.Error(0) return args.Error(0)
} }
func (m *MockStore) Get(ctx context.Context, uuid valuer.UUID) (*traceFunnels.Funnel, error) { func (m *MockStore) Get(ctx context.Context, uuid valuer.UUID) (*traceFunnels.StorableFunnel, error) {
args := m.Called(ctx, uuid) args := m.Called(ctx, uuid)
return args.Get(0).(*traceFunnels.Funnel), args.Error(1) return args.Get(0).(*traceFunnels.StorableFunnel), args.Error(1)
} }
func (m *MockStore) List(ctx context.Context, orgID valuer.UUID) ([]*traceFunnels.Funnel, error) { func (m *MockStore) List(ctx context.Context, orgID valuer.UUID) ([]*traceFunnels.StorableFunnel, error) {
args := m.Called(ctx, orgID) args := m.Called(ctx, orgID)
return args.Get(0).([]*traceFunnels.Funnel), args.Error(1) return args.Get(0).([]*traceFunnels.StorableFunnel), args.Error(1)
} }
func (m *MockStore) Update(ctx context.Context, funnel *traceFunnels.Funnel) error { func (m *MockStore) Update(ctx context.Context, funnel *traceFunnels.StorableFunnel) error {
args := m.Called(ctx, funnel) args := m.Called(ctx, funnel)
return args.Error(0) return args.Error(0)
} }
@ -52,7 +52,7 @@ func TestModule_Create(t *testing.T) {
userID := valuer.GenerateUUID() userID := valuer.GenerateUUID()
orgID := valuer.GenerateUUID().String() orgID := valuer.GenerateUUID().String()
mockStore.On("Create", ctx, mock.MatchedBy(func(f *traceFunnels.Funnel) bool { mockStore.On("Create", ctx, mock.MatchedBy(func(f *traceFunnels.StorableFunnel) bool {
return f.Name == name && return f.Name == name &&
f.CreatedBy == userID.String() && f.CreatedBy == userID.String() &&
f.OrgID.String() == orgID && f.OrgID.String() == orgID &&
@ -79,7 +79,7 @@ func TestModule_Get(t *testing.T) {
ctx := context.Background() ctx := context.Background()
funnelID := valuer.GenerateUUID().String() funnelID := valuer.GenerateUUID().String()
expectedFunnel := &traceFunnels.Funnel{ expectedFunnel := &traceFunnels.StorableFunnel{
BaseMetadata: traceFunnels.BaseMetadata{ BaseMetadata: traceFunnels.BaseMetadata{
Name: "test-funnel", Name: "test-funnel",
}, },
@ -100,7 +100,7 @@ func TestModule_Update(t *testing.T) {
ctx := context.Background() ctx := context.Background()
userID := "user-123" userID := "user-123"
funnel := &traceFunnels.Funnel{ funnel := &traceFunnels.StorableFunnel{
BaseMetadata: traceFunnels.BaseMetadata{ BaseMetadata: traceFunnels.BaseMetadata{
Name: "test-funnel", Name: "test-funnel",
}, },
@ -122,7 +122,7 @@ func TestModule_List(t *testing.T) {
ctx := context.Background() ctx := context.Background()
orgID := valuer.GenerateUUID().String() orgID := valuer.GenerateUUID().String()
orgUUID := valuer.MustNewUUID(orgID) orgUUID := valuer.MustNewUUID(orgID)
expectedFunnels := []*traceFunnels.Funnel{ expectedFunnels := []*traceFunnels.StorableFunnel{
{ {
BaseMetadata: traceFunnels.BaseMetadata{ BaseMetadata: traceFunnels.BaseMetadata{
Name: "funnel-1", Name: "funnel-1",
@ -169,7 +169,7 @@ func TestModule_Save(t *testing.T) {
ctx := context.Background() ctx := context.Background()
userID := "user-123" userID := "user-123"
orgID := valuer.GenerateUUID().String() orgID := valuer.GenerateUUID().String()
funnel := &traceFunnels.Funnel{ funnel := &traceFunnels.StorableFunnel{
BaseMetadata: traceFunnels.BaseMetadata{ BaseMetadata: traceFunnels.BaseMetadata{
Name: "test-funnel", Name: "test-funnel",
}, },
@ -192,7 +192,7 @@ func TestModule_GetFunnelMetadata(t *testing.T) {
ctx := context.Background() ctx := context.Background()
funnelID := valuer.GenerateUUID().String() funnelID := valuer.GenerateUUID().String()
now := time.Now() now := time.Now()
expectedFunnel := &traceFunnels.Funnel{ expectedFunnel := &traceFunnels.StorableFunnel{
BaseMetadata: traceFunnels.BaseMetadata{ BaseMetadata: traceFunnels.BaseMetadata{
Description: "test description", Description: "test description",
TimeAuditable: types.TimeAuditable{ TimeAuditable: types.TimeAuditable{

View File

@ -1,111 +0,0 @@
package sqlmigration
import (
"context"
"fmt"
"github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/sqlstore"
traceFunnels "github.com/SigNoz/signoz/pkg/types/tracefunnel"
"github.com/uptrace/bun"
"github.com/uptrace/bun/migrate"
)
type addTraceFunnels struct {
sqlstore sqlstore.SQLStore
}
func NewAddTraceFunnelsFactory(sqlstore sqlstore.SQLStore) factory.ProviderFactory[SQLMigration, Config] {
return factory.
NewProviderFactory(factory.
MustNewName("add_trace_funnels"),
func(ctx context.Context, providerSettings factory.ProviderSettings, config Config) (SQLMigration, error) {
return newAddTraceFunnels(ctx, providerSettings, config, sqlstore)
})
}
func newAddTraceFunnels(_ context.Context, _ factory.ProviderSettings, _ Config, sqlstore sqlstore.SQLStore) (SQLMigration, error) {
return &addTraceFunnels{sqlstore: sqlstore}, nil
}
func (migration *addTraceFunnels) Register(migrations *migrate.Migrations) error {
if err := migrations.
Register(migration.Up, migration.Down); err != nil {
return err
}
return nil
}
func (migration *addTraceFunnels) Up(ctx context.Context, db *bun.DB) error {
tx, err := db.BeginTx(ctx, nil)
if err != nil {
return err
}
defer tx.Rollback()
// Create trace_funnel table with foreign key constraint inline
_, err = tx.NewCreateTable().
Model((*traceFunnels.Funnel)(nil)).
ForeignKey(`("org_id") REFERENCES "organizations" ("id") ON DELETE CASCADE`).
IfNotExists().
Exec(ctx)
if err != nil {
return fmt.Errorf("failed to create trace_funnel table: %v", err)
}
// Add unique constraint for org_id and name
_, err = tx.NewRaw(`
CREATE UNIQUE INDEX IF NOT EXISTS idx_trace_funnel_org_id_name
ON trace_funnel (org_id, name)
`).Exec(ctx)
if err != nil {
return fmt.Errorf("failed to create unique constraint: %v", err)
}
// Create indexes
_, err = tx.NewCreateIndex().
Model((*traceFunnels.Funnel)(nil)).
Index("idx_trace_funnel_org_id").
Column("org_id").
Exec(ctx)
if err != nil {
return fmt.Errorf("failed to create org_id index: %v", err)
}
_, err = tx.NewCreateIndex().
Model((*traceFunnels.Funnel)(nil)).
Index("idx_trace_funnel_created_at").
Column("created_at").Exec(ctx)
if err != nil {
return fmt.Errorf("failed to create created_at index: %v", err)
}
if err := tx.Commit(); err != nil {
return err
}
return nil
}
func (migration *addTraceFunnels) Down(ctx context.Context, db *bun.DB) error {
tx, err := db.BeginTx(ctx, nil)
if err != nil {
return err
}
defer tx.Rollback()
// Drop trace_funnel table
_, err = tx.NewDropTable().
Model((*traceFunnels.Funnel)(nil)).
IfExists().
Exec(ctx)
if err != nil {
return fmt.Errorf("failed to drop trace_funnel table: %v", err)
}
if err := tx.Commit(); err != nil {
return err
}
return nil
}

View File

@ -3,25 +3,54 @@ package sqlmigration
import ( import (
"context" "context"
"fmt" "fmt"
"github.com/SigNoz/signoz/pkg/factory" "github.com/SigNoz/signoz/pkg/factory"
v3 "github.com/SigNoz/signoz/pkg/query-service/model/v3"
"github.com/SigNoz/signoz/pkg/sqlstore" "github.com/SigNoz/signoz/pkg/sqlstore"
traceFunnels "github.com/SigNoz/signoz/pkg/types/tracefunnel" "github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/valuer"
"github.com/uptrace/bun" "github.com/uptrace/bun"
"github.com/uptrace/bun/migrate" "github.com/uptrace/bun/migrate"
) )
type BaseMetadata struct {
types.Identifiable // funnel id
types.TimeAuditable
types.UserAuditable
Name string `json:"funnel_name" bun:"name,type:text,notnull"` // funnel name
Description string `json:"description" bun:"description,type:text"` // funnel description
OrgID valuer.UUID `json:"org_id" bun:"org_id,type:varchar,notnull"`
}
// Funnel Core Data Structure (Funnel and FunnelStep)
type Funnel struct {
bun.BaseModel `bun:"table:trace_funnel"`
BaseMetadata
Steps []FunnelStep `json:"steps" bun:"steps,type:text,notnull"`
Tags string `json:"tags" bun:"tags,type:text"`
CreatedByUser *types.User `json:"user" bun:"rel:belongs-to,join:created_by=id"`
}
type FunnelStep struct {
ID valuer.UUID `json:"id,omitempty"`
Name string `json:"name,omitempty"` // step name
Description string `json:"description,omitempty"` // step description
Order int64 `json:"step_order"`
ServiceName string `json:"service_name"`
SpanName string `json:"span_name"`
Filters *v3.FilterSet `json:"filters,omitempty"`
LatencyPointer string `json:"latency_pointer,omitempty"`
LatencyType string `json:"latency_type,omitempty"`
HasErrors bool `json:"has_errors"`
}
type addTraceFunnels struct { type addTraceFunnels struct {
sqlstore sqlstore.SQLStore sqlstore sqlstore.SQLStore
} }
func NewAddTraceFunnelsFactory(sqlstore sqlstore.SQLStore) factory.ProviderFactory[SQLMigration, Config] { func NewAddTraceFunnelsFactory(sqlstore sqlstore.SQLStore) factory.ProviderFactory[SQLMigration, Config] {
return factory. return factory.NewProviderFactory(factory.MustNewName("add_trace_funnels"), func(ctx context.Context, providerSettings factory.ProviderSettings, config Config) (SQLMigration, error) {
NewProviderFactory(factory. return newAddTraceFunnels(ctx, providerSettings, config, sqlstore)
MustNewName("add_trace_funnels"), })
func(ctx context.Context, providerSettings factory.ProviderSettings, config Config) (SQLMigration, error) {
return newAddTraceFunnels(ctx, providerSettings, config, sqlstore)
})
} }
func newAddTraceFunnels(_ context.Context, _ factory.ProviderSettings, _ Config, sqlstore sqlstore.SQLStore) (SQLMigration, error) { func newAddTraceFunnels(_ context.Context, _ factory.ProviderSettings, _ Config, sqlstore sqlstore.SQLStore) (SQLMigration, error) {
@ -29,8 +58,7 @@ func newAddTraceFunnels(_ context.Context, _ factory.ProviderSettings, _ Config,
} }
func (migration *addTraceFunnels) Register(migrations *migrate.Migrations) error { func (migration *addTraceFunnels) Register(migrations *migrate.Migrations) error {
if err := migrations. if err := migrations.Register(migration.Up, migration.Down); err != nil {
Register(migration.Up, migration.Down); err != nil {
return err return err
} }
return nil return nil
@ -43,9 +71,8 @@ func (migration *addTraceFunnels) Up(ctx context.Context, db *bun.DB) error {
} }
defer tx.Rollback() defer tx.Rollback()
// Create trace_funnel table with foreign key constraint inline
_, err = tx.NewCreateTable(). _, err = tx.NewCreateTable().
Model((*traceFunnels.Funnel)(nil)). Model((*Funnel)(nil)).
ForeignKey(`("org_id") REFERENCES "organizations" ("id") ON DELETE CASCADE`). ForeignKey(`("org_id") REFERENCES "organizations" ("id") ON DELETE CASCADE`).
IfNotExists(). IfNotExists().
Exec(ctx) Exec(ctx)
@ -53,62 +80,9 @@ func (migration *addTraceFunnels) Up(ctx context.Context, db *bun.DB) error {
return fmt.Errorf("failed to create trace_funnel table: %v", err) return fmt.Errorf("failed to create trace_funnel table: %v", err)
} }
// Add unique constraint for org_id and name
//_, err = tx.NewRaw(`
// CREATE UNIQUE INDEX IF NOT EXISTS idx_trace_funnel_org_id_name
// ON trace_funnel (org_id, name)
//`).Exec(ctx)
//if err != nil {
// return fmt.Errorf("failed to create unique constraint: %v", err)
//}
// Create indexes
_, err = tx.NewCreateIndex().
Model((*traceFunnels.Funnel)(nil)).
Index("idx_trace_funnel_org_id").
Column("org_id").
IfNotExists().
Exec(ctx)
if err != nil {
return fmt.Errorf("failed to create org_id index: %v", err)
}
_, err = tx.NewCreateIndex().
Model((*traceFunnels.Funnel)(nil)).
Index("idx_trace_funnel_created_at").
Column("created_at").
IfNotExists().
Exec(ctx)
if err != nil {
return fmt.Errorf("failed to create created_at index: %v", err)
}
if err := tx.Commit(); err != nil {
return err
}
return nil return nil
} }
func (migration *addTraceFunnels) Down(ctx context.Context, db *bun.DB) error { func (migration *addTraceFunnels) Down(ctx context.Context, db *bun.DB) error {
//tx, err := db.BeginTx(ctx, nil)
//if err != nil {
// return err
//}
//defer tx.Rollback()
//
//// Drop trace_funnel table
//_, err = tx.NewDropTable().
// Model((*traceFunnels.Funnel)(nil)).
// IfExists().
// Exec(ctx)
//if err != nil {
// return fmt.Errorf("failed to drop trace_funnel table: %v", err)
//}
//
//if err := tx.Commit(); err != nil {
// return err
//}
return nil return nil
} }

View File

@ -7,9 +7,9 @@ import (
) )
type FunnelStore interface { type FunnelStore interface {
Create(context.Context, *Funnel) error Create(context.Context, *StorableFunnel) error
Get(context.Context, valuer.UUID) (*Funnel, error) Get(context.Context, valuer.UUID) (*StorableFunnel, error)
List(context.Context, valuer.UUID) ([]*Funnel, error) List(context.Context, valuer.UUID) ([]*StorableFunnel, error)
Update(context.Context, *Funnel) error Update(context.Context, *StorableFunnel) error
Delete(context.Context, valuer.UUID) error Delete(context.Context, valuer.UUID) error
} }

View File

@ -22,13 +22,13 @@ type BaseMetadata struct {
OrgID valuer.UUID `json:"org_id" bun:"org_id,type:varchar,notnull"` OrgID valuer.UUID `json:"org_id" bun:"org_id,type:varchar,notnull"`
} }
// Funnel Core Data Structure (Funnel and FunnelStep) // StorableFunnel Core Data Structure (StorableFunnel and FunnelStep)
type Funnel struct { type StorableFunnel struct {
bun.BaseModel `bun:"table:trace_funnel"` bun.BaseModel `bun:"table:trace_funnel"`
BaseMetadata BaseMetadata
Steps []FunnelStep `json:"steps" bun:"steps,type:text,notnull"` Steps []*FunnelStep `json:"steps" bun:"steps,type:text,notnull"`
Tags string `json:"tags" bun:"tags,type:text"` Tags string `json:"tags" bun:"tags,type:text"`
CreatedByUser *types.User `json:"user" bun:"rel:belongs-to,join:created_by=id"` CreatedByUser *types.User `json:"user" bun:"rel:belongs-to,join:created_by=id"`
} }
type FunnelStep struct { type FunnelStep struct {
@ -44,14 +44,14 @@ type FunnelStep struct {
HasErrors bool `json:"has_errors"` HasErrors bool `json:"has_errors"`
} }
// FunnelRequest represents all possible funnel-related requests // PostableFunnel represents all possible funnel-related requests
type FunnelRequest struct { type PostableFunnel struct {
FunnelID valuer.UUID `json:"funnel_id,omitempty"` FunnelID valuer.UUID `json:"funnel_id,omitempty"`
Name string `json:"funnel_name,omitempty"` Name string `json:"funnel_name,omitempty"`
Timestamp int64 `json:"timestamp,omitempty"` Timestamp int64 `json:"timestamp,omitempty"`
Description string `json:"description,omitempty"` Description string `json:"description,omitempty"`
Steps []FunnelStep `json:"steps,omitempty"` Steps []*FunnelStep `json:"steps,omitempty"`
UserID string `json:"user_id,omitempty"` UserID string `json:"user_id,omitempty"`
// Analytics specific fields // Analytics specific fields
StartTime int64 `json:"start_time,omitempty"` StartTime int64 `json:"start_time,omitempty"`
@ -60,19 +60,19 @@ type FunnelRequest struct {
StepBOrder int64 `json:"step_b_order,omitempty"` StepBOrder int64 `json:"step_b_order,omitempty"`
} }
// FunnelResponse represents all possible funnel-related responses // GettableFunnel represents all possible funnel-related responses
type FunnelResponse struct { type GettableFunnel struct {
FunnelID string `json:"funnel_id,omitempty"` FunnelID string `json:"funnel_id,omitempty"`
FunnelName string `json:"funnel_name,omitempty"` FunnelName string `json:"funnel_name,omitempty"`
Description string `json:"description,omitempty"` Description string `json:"description,omitempty"`
CreatedAt int64 `json:"created_at,omitempty"` CreatedAt int64 `json:"created_at,omitempty"`
CreatedBy string `json:"created_by,omitempty"` CreatedBy string `json:"created_by,omitempty"`
UpdatedAt int64 `json:"updated_at,omitempty"` UpdatedAt int64 `json:"updated_at,omitempty"`
UpdatedBy string `json:"updated_by,omitempty"` UpdatedBy string `json:"updated_by,omitempty"`
OrgID string `json:"org_id,omitempty"` OrgID string `json:"org_id,omitempty"`
UserEmail string `json:"user_email,omitempty"` UserEmail string `json:"user_email,omitempty"`
Funnel *Funnel `json:"funnel,omitempty"` Funnel *StorableFunnel `json:"funnel,omitempty"`
Steps []FunnelStep `json:"steps,omitempty"` Steps []*FunnelStep `json:"steps,omitempty"`
} }
// TimeRange represents a time range for analytics // TimeRange represents a time range for analytics

View File

@ -8,7 +8,6 @@ import (
"github.com/SigNoz/signoz/pkg/errors" "github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/types/authtypes" "github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/types/tracefunnel"
"github.com/SigNoz/signoz/pkg/valuer" "github.com/SigNoz/signoz/pkg/valuer"
) )
@ -28,7 +27,7 @@ func ValidateTimestampIsMilliseconds(timestamp int64) bool {
return timestamp >= 1000000000000 && timestamp <= 9999999999999 return timestamp >= 1000000000000 && timestamp <= 9999999999999
} }
func ValidateFunnelSteps(steps []tracefunnel.FunnelStep) error { func ValidateFunnelSteps(steps []FunnelStep) error {
if len(steps) < 2 { if len(steps) < 2 {
return fmt.Errorf("funnel must have at least 2 steps") return fmt.Errorf("funnel must have at least 2 steps")
} }
@ -50,12 +49,12 @@ func ValidateFunnelSteps(steps []tracefunnel.FunnelStep) error {
// NormalizeFunnelSteps normalizes step orders to be sequential starting from 1. // NormalizeFunnelSteps normalizes step orders to be sequential starting from 1.
// Returns a new slice with normalized step orders, leaving the input slice unchanged. // Returns a new slice with normalized step orders, leaving the input slice unchanged.
func NormalizeFunnelSteps(steps []tracefunnel.FunnelStep) []tracefunnel.FunnelStep { func NormalizeFunnelSteps(steps []FunnelStep) []FunnelStep {
if len(steps) == 0 { if len(steps) == 0 {
return []tracefunnel.FunnelStep{} return []FunnelStep{}
} }
newSteps := make([]tracefunnel.FunnelStep, len(steps)) newSteps := make([]FunnelStep, len(steps))
copy(newSteps, steps) copy(newSteps, steps)
sort.Slice(newSteps, func(i, j int) bool { sort.Slice(newSteps, func(i, j int) bool {
@ -88,8 +87,8 @@ func ValidateAndConvertTimestamp(timestamp int64) (time.Time, error) {
return time.Unix(0, timestamp*1000000), nil // Convert to nanoseconds return time.Unix(0, timestamp*1000000), nil // Convert to nanoseconds
} }
func ConstructFunnelResponse(funnel *tracefunnel.Funnel, claims *authtypes.Claims) tracefunnel.FunnelResponse { func ConstructFunnelResponse(funnel *StorableFunnel, claims *authtypes.Claims) GettableFunnel {
resp := tracefunnel.FunnelResponse{ resp := GettableFunnel{
FunnelName: funnel.Name, FunnelName: funnel.Name,
FunnelID: funnel.ID.String(), FunnelID: funnel.ID.String(),
Steps: funnel.Steps, Steps: funnel.Steps,
@ -110,7 +109,7 @@ func ConstructFunnelResponse(funnel *tracefunnel.Funnel, claims *authtypes.Claim
return resp return resp
} }
func ProcessFunnelSteps(steps []tracefunnel.FunnelStep) ([]tracefunnel.FunnelStep, error) { func ProcessFunnelSteps(steps []FunnelStep) ([]FunnelStep, error) {
// First validate the steps // First validate the steps
if err := ValidateFunnelSteps(steps); err != nil { if err := ValidateFunnelSteps(steps); err != nil {
return nil, errors.Newf(errors.TypeInvalidInput, return nil, errors.Newf(errors.TypeInvalidInput,

View File

@ -8,7 +8,6 @@ import (
"github.com/SigNoz/signoz/pkg/types" "github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/authtypes" "github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/types/tracefunnel"
"github.com/SigNoz/signoz/pkg/valuer" "github.com/SigNoz/signoz/pkg/valuer"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -91,12 +90,12 @@ func TestValidateTimestampIsMilliseconds(t *testing.T) {
func TestValidateFunnelSteps(t *testing.T) { func TestValidateFunnelSteps(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
steps []tracefunnel.FunnelStep steps []FunnelStep
expectError bool expectError bool
}{ }{
{ {
name: "valid steps", name: "valid steps",
steps: []tracefunnel.FunnelStep{ steps: []FunnelStep{
{ {
ID: valuer.GenerateUUID(), ID: valuer.GenerateUUID(),
Name: "Step 1", Name: "Step 1",
@ -116,7 +115,7 @@ func TestValidateFunnelSteps(t *testing.T) {
}, },
{ {
name: "too few steps", name: "too few steps",
steps: []tracefunnel.FunnelStep{ steps: []FunnelStep{
{ {
ID: valuer.GenerateUUID(), ID: valuer.GenerateUUID(),
Name: "Step 1", Name: "Step 1",
@ -129,7 +128,7 @@ func TestValidateFunnelSteps(t *testing.T) {
}, },
{ {
name: "missing service name", name: "missing service name",
steps: []tracefunnel.FunnelStep{ steps: []FunnelStep{
{ {
ID: valuer.GenerateUUID(), ID: valuer.GenerateUUID(),
Name: "Step 1", Name: "Step 1",
@ -148,7 +147,7 @@ func TestValidateFunnelSteps(t *testing.T) {
}, },
{ {
name: "missing span name", name: "missing span name",
steps: []tracefunnel.FunnelStep{ steps: []FunnelStep{
{ {
ID: valuer.GenerateUUID(), ID: valuer.GenerateUUID(),
Name: "Step 1", Name: "Step 1",
@ -167,7 +166,7 @@ func TestValidateFunnelSteps(t *testing.T) {
}, },
{ {
name: "negative order", name: "negative order",
steps: []tracefunnel.FunnelStep{ steps: []FunnelStep{
{ {
ID: valuer.GenerateUUID(), ID: valuer.GenerateUUID(),
Name: "Step 1", Name: "Step 1",
@ -202,12 +201,12 @@ func TestValidateFunnelSteps(t *testing.T) {
func TestNormalizeFunnelSteps(t *testing.T) { func TestNormalizeFunnelSteps(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
steps []tracefunnel.FunnelStep steps []FunnelStep
expected []tracefunnel.FunnelStep expected []FunnelStep
}{ }{
{ {
name: "already normalized steps", name: "already normalized steps",
steps: []tracefunnel.FunnelStep{ steps: []FunnelStep{
{ {
ID: valuer.GenerateUUID(), ID: valuer.GenerateUUID(),
Name: "Step 1", Name: "Step 1",
@ -223,7 +222,7 @@ func TestNormalizeFunnelSteps(t *testing.T) {
Order: 2, Order: 2,
}, },
}, },
expected: []tracefunnel.FunnelStep{ expected: []FunnelStep{
{ {
Name: "Step 1", Name: "Step 1",
ServiceName: "test-service", ServiceName: "test-service",
@ -240,7 +239,7 @@ func TestNormalizeFunnelSteps(t *testing.T) {
}, },
{ {
name: "unordered steps", name: "unordered steps",
steps: []tracefunnel.FunnelStep{ steps: []FunnelStep{
{ {
ID: valuer.GenerateUUID(), ID: valuer.GenerateUUID(),
Name: "Step 2", Name: "Step 2",
@ -256,7 +255,7 @@ func TestNormalizeFunnelSteps(t *testing.T) {
Order: 1, Order: 1,
}, },
}, },
expected: []tracefunnel.FunnelStep{ expected: []FunnelStep{
{ {
Name: "Step 1", Name: "Step 1",
ServiceName: "test-service", ServiceName: "test-service",
@ -273,7 +272,7 @@ func TestNormalizeFunnelSteps(t *testing.T) {
}, },
{ {
name: "steps with gaps in order", name: "steps with gaps in order",
steps: []tracefunnel.FunnelStep{ steps: []FunnelStep{
{ {
ID: valuer.GenerateUUID(), ID: valuer.GenerateUUID(),
Name: "Step 1", Name: "Step 1",
@ -296,7 +295,7 @@ func TestNormalizeFunnelSteps(t *testing.T) {
Order: 2, Order: 2,
}, },
}, },
expected: []tracefunnel.FunnelStep{ expected: []FunnelStep{
{ {
Name: "Step 1", Name: "Step 1",
ServiceName: "test-service", ServiceName: "test-service",
@ -322,7 +321,7 @@ func TestNormalizeFunnelSteps(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
// Make a copy of the steps to avoid modifying the original // Make a copy of the steps to avoid modifying the original
steps := make([]tracefunnel.FunnelStep, len(tt.steps)) steps := make([]FunnelStep, len(tt.steps))
copy(steps, tt.steps) copy(steps, tt.steps)
result := NormalizeFunnelSteps(steps) result := NormalizeFunnelSteps(steps)
@ -429,14 +428,14 @@ func TestConstructFunnelResponse(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
funnel *tracefunnel.Funnel funnel *StorableFunnel
claims *authtypes.Claims claims *authtypes.Claims
expected tracefunnel.FunnelResponse expected GettableFunnel
}{ }{
{ {
name: "with user email from funnel", name: "with user email from funnel",
funnel: &tracefunnel.Funnel{ funnel: &StorableFunnel{
BaseMetadata: tracefunnel.BaseMetadata{ BaseMetadata: BaseMetadata{
Identifiable: types.Identifiable{ Identifiable: types.Identifiable{
ID: funnelID, ID: funnelID,
}, },
@ -455,7 +454,7 @@ func TestConstructFunnelResponse(t *testing.T) {
Identifiable: types.Identifiable{ID: valuer.MustNewUUID("user-123")}, Identifiable: types.Identifiable{ID: valuer.MustNewUUID("user-123")},
Email: "funnel@example.com", Email: "funnel@example.com",
}, },
Steps: []tracefunnel.FunnelStep{ Steps: []*FunnelStep{
{ {
ID: valuer.GenerateUUID(), ID: valuer.GenerateUUID(),
Name: "Step 1", Name: "Step 1",
@ -470,10 +469,10 @@ func TestConstructFunnelResponse(t *testing.T) {
OrgID: orgID.String(), OrgID: orgID.String(),
Email: "claims@example.com", Email: "claims@example.com",
}, },
expected: tracefunnel.FunnelResponse{ expected: GettableFunnel{
FunnelName: "test-funnel", FunnelName: "test-funnel",
FunnelID: funnelID.String(), FunnelID: funnelID.String(),
Steps: []tracefunnel.FunnelStep{ Steps: []*FunnelStep{
{ {
Name: "Step 1", Name: "Step 1",
ServiceName: "test-service", ServiceName: "test-service",
@ -491,8 +490,8 @@ func TestConstructFunnelResponse(t *testing.T) {
}, },
{ {
name: "with user email from claims", name: "with user email from claims",
funnel: &tracefunnel.Funnel{ funnel: &StorableFunnel{
BaseMetadata: tracefunnel.BaseMetadata{ BaseMetadata: BaseMetadata{
Identifiable: types.Identifiable{ Identifiable: types.Identifiable{
ID: funnelID, ID: funnelID,
}, },
@ -507,7 +506,7 @@ func TestConstructFunnelResponse(t *testing.T) {
UpdatedBy: "user-123", UpdatedBy: "user-123",
}, },
}, },
Steps: []tracefunnel.FunnelStep{ Steps: []*FunnelStep{
{ {
ID: valuer.GenerateUUID(), ID: valuer.GenerateUUID(),
Name: "Step 1", Name: "Step 1",
@ -522,10 +521,10 @@ func TestConstructFunnelResponse(t *testing.T) {
OrgID: orgID.String(), OrgID: orgID.String(),
Email: "claims@example.com", Email: "claims@example.com",
}, },
expected: tracefunnel.FunnelResponse{ expected: GettableFunnel{
FunnelName: "test-funnel", FunnelName: "test-funnel",
FunnelID: funnelID.String(), FunnelID: funnelID.String(),
Steps: []tracefunnel.FunnelStep{ Steps: []*FunnelStep{
{ {
Name: "Step 1", Name: "Step 1",
ServiceName: "test-service", ServiceName: "test-service",
@ -573,12 +572,12 @@ func TestConstructFunnelResponse(t *testing.T) {
func TestProcessFunnelSteps(t *testing.T) { func TestProcessFunnelSteps(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
steps []tracefunnel.FunnelStep steps []FunnelStep
expectError bool expectError bool
}{ }{
{ {
name: "valid steps with missing IDs", name: "valid steps with missing IDs",
steps: []tracefunnel.FunnelStep{ steps: []FunnelStep{
{ {
Name: "Step 1", Name: "Step 1",
ServiceName: "test-service", ServiceName: "test-service",
@ -596,7 +595,7 @@ func TestProcessFunnelSteps(t *testing.T) {
}, },
{ {
name: "invalid steps - missing service name", name: "invalid steps - missing service name",
steps: []tracefunnel.FunnelStep{ steps: []FunnelStep{
{ {
Name: "Step 1", Name: "Step 1",
SpanName: "test-span", SpanName: "test-span",
@ -613,7 +612,7 @@ func TestProcessFunnelSteps(t *testing.T) {
}, },
{ {
name: "invalid steps - negative order", name: "invalid steps - negative order",
steps: []tracefunnel.FunnelStep{ steps: []FunnelStep{
{ {
Name: "Step 1", Name: "Step 1",
ServiceName: "test-service", ServiceName: "test-service",