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

View File

@ -22,24 +22,24 @@ type MockModule struct {
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)
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)
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)
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)
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 {
@ -47,7 +47,7 @@ func (m *MockModule) Delete(ctx context.Context, funnelID string) error {
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)
return args.Error(0)
}
@ -61,7 +61,7 @@ func TestHandler_New(t *testing.T) {
mockModule := new(MockModule)
handler := NewHandler(mockModule)
reqBody := traceFunnels.FunnelRequest{
reqBody := traceFunnels.PostableFunnel{
Name: "test-funnel",
Timestamp: time.Now().UnixMilli(),
}
@ -81,7 +81,7 @@ func TestHandler_New(t *testing.T) {
rr := httptest.NewRecorder()
funnelID := valuer.GenerateUUID()
expectedFunnel := &traceFunnels.Funnel{
expectedFunnel := &traceFunnels.StorableFunnel{
BaseMetadata: traceFunnels.BaseMetadata{
Identifiable: types.Identifiable{
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)
handler.New(rr, req)
@ -100,7 +100,7 @@ func TestHandler_New(t *testing.T) {
var response struct {
Status string `json:"status"`
Data traceFunnels.FunnelResponse `json:"data"`
Data traceFunnels.GettableFunnel `json:"data"`
}
err := json.Unmarshal(rr.Body.Bytes(), &response)
assert.NoError(t, err)
@ -120,10 +120,10 @@ func TestHandler_Update(t *testing.T) {
funnelID := valuer.GenerateUUID()
orgID := valuer.GenerateUUID().String()
reqBody := traceFunnels.FunnelRequest{
reqBody := traceFunnels.PostableFunnel{
FunnelID: funnelID,
Name: "updated-funnel",
Steps: []traceFunnels.FunnelStep{
Steps: []*traceFunnels.FunnelStep{
{
ID: valuer.GenerateUUID(),
Name: "Step 1",
@ -161,7 +161,7 @@ func TestHandler_Update(t *testing.T) {
rr := httptest.NewRecorder()
// Set up mock expectations
existingFunnel := &traceFunnels.Funnel{
existingFunnel := &traceFunnels.StorableFunnel{
BaseMetadata: traceFunnels.BaseMetadata{
Identifiable: types.Identifiable{
ID: funnelID,
@ -183,7 +183,7 @@ func TestHandler_Update(t *testing.T) {
},
}
updatedFunnel := &traceFunnels.Funnel{
updatedFunnel := &traceFunnels.StorableFunnel{
BaseMetadata: traceFunnels.BaseMetadata{
Identifiable: types.Identifiable{
ID: funnelID,
@ -209,9 +209,9 @@ func TestHandler_Update(t *testing.T) {
// First Get call to validate the funnel exists
mockModule.On("Get", req.Context(), funnelID.String()).Return(existingFunnel, nil).Once()
// 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
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 &&
f.ID.String() == funnelID.String() &&
len(f.Steps) == len(reqBody.Steps) &&
@ -233,7 +233,7 @@ func TestHandler_Update(t *testing.T) {
var response struct {
Status string `json:"status"`
Data traceFunnels.FunnelResponse `json:"data"`
Data traceFunnels.GettableFunnel `json:"data"`
}
err = json.Unmarshal(rr.Body.Bytes(), &response)
assert.NoError(t, err)
@ -261,7 +261,7 @@ func TestHandler_List(t *testing.T) {
funnel1ID := valuer.GenerateUUID()
funnel2ID := valuer.GenerateUUID()
expectedFunnels := []*traceFunnels.Funnel{
expectedFunnels := []*traceFunnels.StorableFunnel{
{
BaseMetadata: traceFunnels.BaseMetadata{
Identifiable: types.Identifiable{
@ -290,7 +290,7 @@ func TestHandler_List(t *testing.T) {
var response struct {
Status string `json:"status"`
Data []traceFunnels.FunnelResponse `json:"data"`
Data []traceFunnels.GettableFunnel `json:"data"`
}
err := json.Unmarshal(rr.Body.Bytes(), &response)
assert.NoError(t, err)
@ -312,7 +312,7 @@ func TestHandler_Get(t *testing.T) {
rr := httptest.NewRecorder()
expectedFunnel := &traceFunnels.Funnel{
expectedFunnel := &traceFunnels.StorableFunnel{
BaseMetadata: traceFunnels.BaseMetadata{
Identifiable: types.Identifiable{
ID: funnelID,
@ -330,7 +330,7 @@ func TestHandler_Get(t *testing.T) {
var response struct {
Status string `json:"status"`
Data traceFunnels.FunnelResponse `json:"data"`
Data traceFunnels.GettableFunnel `json:"data"`
}
err := json.Unmarshal(rr.Body.Bytes(), &response)
assert.NoError(t, err)
@ -364,7 +364,7 @@ func TestHandler_Save(t *testing.T) {
mockModule := new(MockModule)
handler := NewHandler(mockModule)
reqBody := traceFunnels.FunnelRequest{
reqBody := traceFunnels.PostableFunnel{
FunnelID: valuer.GenerateUUID(),
Description: "updated description",
Timestamp: time.Now().UnixMilli(),
@ -384,7 +384,7 @@ func TestHandler_Save(t *testing.T) {
rr := httptest.NewRecorder()
existingFunnel := &traceFunnels.Funnel{
existingFunnel := &traceFunnels.StorableFunnel{
BaseMetadata: traceFunnels.BaseMetadata{
Identifiable: types.Identifiable{
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("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() &&
f.Name == existingFunnel.Name &&
f.Description == reqBody.Description &&
@ -410,7 +410,7 @@ func TestHandler_Save(t *testing.T) {
var response struct {
Status string `json:"status"`
Data traceFunnels.FunnelResponse `json:"data"`
Data traceFunnels.GettableFunnel `json:"data"`
}
err := json.Unmarshal(rr.Body.Bytes(), &response)
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)
if err != nil {
return nil, fmt.Errorf("invalid org ID: %v", err)
}
funnel := &traceFunnels.Funnel{
funnel := &traceFunnels.StorableFunnel{
BaseMetadata: traceFunnels.BaseMetadata{
Name: name,
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 {
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
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)
if err != nil {
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
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
return module.store.Update(ctx, funnel)
}
// 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)
if err != nil {
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
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)
if err != nil {
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}
}
func (store *store) Create(ctx context.Context, funnel *traceFunnels.Funnel) 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()
}
func (store *store) Create(ctx context.Context, funnel *traceFunnels.StorableFunnel) error {
_, err := store.
sqlstore.
BunDB().
@ -49,8 +33,8 @@ func (store *store) Create(ctx context.Context, funnel *traceFunnels.Funnel) err
}
// Get retrieves a funnel by ID
func (store *store) Get(ctx context.Context, uuid valuer.UUID) (*traceFunnels.Funnel, error) {
funnel := &traceFunnels.Funnel{}
func (store *store) Get(ctx context.Context, uuid valuer.UUID) (*traceFunnels.StorableFunnel, error) {
funnel := &traceFunnels.StorableFunnel{}
err := store.
sqlstore.
BunDB().
@ -66,7 +50,7 @@ func (store *store) Get(ctx context.Context, uuid valuer.UUID) (*traceFunnels.Fu
}
// 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()
_, 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
func (store *store) List(ctx context.Context, orgID valuer.UUID) ([]*traceFunnels.Funnel, error) {
var funnels []*traceFunnels.Funnel
func (store *store) List(ctx context.Context, orgID valuer.UUID) ([]*traceFunnels.StorableFunnel, error) {
var funnels []*traceFunnels.StorableFunnel
err := store.
sqlstore.
BunDB().
@ -105,7 +89,7 @@ func (store *store) Delete(ctx context.Context, uuid valuer.UUID) error {
sqlstore.
BunDB().
NewDelete().
Model((*traceFunnels.Funnel)(nil)).
Model((*traceFunnels.StorableFunnel)(nil)).
Where("id = ?", uuid).Exec(ctx)
if err != nil {
return fmt.Errorf("failed to delete funnel: %v", err)

View File

@ -9,17 +9,17 @@ import (
// Module defines the interface for trace funnel operations
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
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)
}

View File

@ -17,22 +17,22 @@ type MockStore struct {
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)
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)
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)
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)
return args.Error(0)
}
@ -52,7 +52,7 @@ func TestModule_Create(t *testing.T) {
userID := valuer.GenerateUUID()
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 &&
f.CreatedBy == userID.String() &&
f.OrgID.String() == orgID &&
@ -79,7 +79,7 @@ func TestModule_Get(t *testing.T) {
ctx := context.Background()
funnelID := valuer.GenerateUUID().String()
expectedFunnel := &traceFunnels.Funnel{
expectedFunnel := &traceFunnels.StorableFunnel{
BaseMetadata: traceFunnels.BaseMetadata{
Name: "test-funnel",
},
@ -100,7 +100,7 @@ func TestModule_Update(t *testing.T) {
ctx := context.Background()
userID := "user-123"
funnel := &traceFunnels.Funnel{
funnel := &traceFunnels.StorableFunnel{
BaseMetadata: traceFunnels.BaseMetadata{
Name: "test-funnel",
},
@ -122,7 +122,7 @@ func TestModule_List(t *testing.T) {
ctx := context.Background()
orgID := valuer.GenerateUUID().String()
orgUUID := valuer.MustNewUUID(orgID)
expectedFunnels := []*traceFunnels.Funnel{
expectedFunnels := []*traceFunnels.StorableFunnel{
{
BaseMetadata: traceFunnels.BaseMetadata{
Name: "funnel-1",
@ -169,7 +169,7 @@ func TestModule_Save(t *testing.T) {
ctx := context.Background()
userID := "user-123"
orgID := valuer.GenerateUUID().String()
funnel := &traceFunnels.Funnel{
funnel := &traceFunnels.StorableFunnel{
BaseMetadata: traceFunnels.BaseMetadata{
Name: "test-funnel",
},
@ -192,7 +192,7 @@ func TestModule_GetFunnelMetadata(t *testing.T) {
ctx := context.Background()
funnelID := valuer.GenerateUUID().String()
now := time.Now()
expectedFunnel := &traceFunnels.Funnel{
expectedFunnel := &traceFunnels.StorableFunnel{
BaseMetadata: traceFunnels.BaseMetadata{
Description: "test description",
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 (
"context"
"fmt"
"github.com/SigNoz/signoz/pkg/factory"
v3 "github.com/SigNoz/signoz/pkg/query-service/model/v3"
"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/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 {
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)
})
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) {
@ -29,8 +58,7 @@ func newAddTraceFunnels(_ context.Context, _ factory.ProviderSettings, _ Config,
}
func (migration *addTraceFunnels) Register(migrations *migrate.Migrations) error {
if err := migrations.
Register(migration.Up, migration.Down); err != nil {
if err := migrations.Register(migration.Up, migration.Down); err != nil {
return err
}
return nil
@ -43,9 +71,8 @@ func (migration *addTraceFunnels) Up(ctx context.Context, db *bun.DB) error {
}
defer tx.Rollback()
// Create trace_funnel table with foreign key constraint inline
_, err = tx.NewCreateTable().
Model((*traceFunnels.Funnel)(nil)).
Model((*Funnel)(nil)).
ForeignKey(`("org_id") REFERENCES "organizations" ("id") ON DELETE CASCADE`).
IfNotExists().
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)
}
// 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
}
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

@ -7,9 +7,9 @@ import (
)
type FunnelStore interface {
Create(context.Context, *Funnel) error
Get(context.Context, valuer.UUID) (*Funnel, error)
List(context.Context, valuer.UUID) ([]*Funnel, error)
Update(context.Context, *Funnel) error
Create(context.Context, *StorableFunnel) error
Get(context.Context, valuer.UUID) (*StorableFunnel, error)
List(context.Context, valuer.UUID) ([]*StorableFunnel, error)
Update(context.Context, *StorableFunnel) 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"`
}
// Funnel Core Data Structure (Funnel and FunnelStep)
type Funnel struct {
// StorableFunnel Core Data Structure (StorableFunnel and FunnelStep)
type StorableFunnel 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"`
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 {
@ -44,14 +44,14 @@ type FunnelStep struct {
HasErrors bool `json:"has_errors"`
}
// FunnelRequest represents all possible funnel-related requests
type FunnelRequest struct {
FunnelID valuer.UUID `json:"funnel_id,omitempty"`
Name string `json:"funnel_name,omitempty"`
Timestamp int64 `json:"timestamp,omitempty"`
Description string `json:"description,omitempty"`
Steps []FunnelStep `json:"steps,omitempty"`
UserID string `json:"user_id,omitempty"`
// PostableFunnel represents all possible funnel-related requests
type PostableFunnel struct {
FunnelID valuer.UUID `json:"funnel_id,omitempty"`
Name string `json:"funnel_name,omitempty"`
Timestamp int64 `json:"timestamp,omitempty"`
Description string `json:"description,omitempty"`
Steps []*FunnelStep `json:"steps,omitempty"`
UserID string `json:"user_id,omitempty"`
// Analytics specific fields
StartTime int64 `json:"start_time,omitempty"`
@ -60,19 +60,19 @@ type FunnelRequest struct {
StepBOrder int64 `json:"step_b_order,omitempty"`
}
// FunnelResponse represents all possible funnel-related responses
type FunnelResponse struct {
FunnelID string `json:"funnel_id,omitempty"`
FunnelName string `json:"funnel_name,omitempty"`
Description string `json:"description,omitempty"`
CreatedAt int64 `json:"created_at,omitempty"`
CreatedBy string `json:"created_by,omitempty"`
UpdatedAt int64 `json:"updated_at,omitempty"`
UpdatedBy string `json:"updated_by,omitempty"`
OrgID string `json:"org_id,omitempty"`
UserEmail string `json:"user_email,omitempty"`
Funnel *Funnel `json:"funnel,omitempty"`
Steps []FunnelStep `json:"steps,omitempty"`
// GettableFunnel represents all possible funnel-related responses
type GettableFunnel struct {
FunnelID string `json:"funnel_id,omitempty"`
FunnelName string `json:"funnel_name,omitempty"`
Description string `json:"description,omitempty"`
CreatedAt int64 `json:"created_at,omitempty"`
CreatedBy string `json:"created_by,omitempty"`
UpdatedAt int64 `json:"updated_at,omitempty"`
UpdatedBy string `json:"updated_by,omitempty"`
OrgID string `json:"org_id,omitempty"`
UserEmail string `json:"user_email,omitempty"`
Funnel *StorableFunnel `json:"funnel,omitempty"`
Steps []*FunnelStep `json:"steps,omitempty"`
}
// 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/types/authtypes"
"github.com/SigNoz/signoz/pkg/types/tracefunnel"
"github.com/SigNoz/signoz/pkg/valuer"
)
@ -28,7 +27,7 @@ func ValidateTimestampIsMilliseconds(timestamp int64) bool {
return timestamp >= 1000000000000 && timestamp <= 9999999999999
}
func ValidateFunnelSteps(steps []tracefunnel.FunnelStep) error {
func ValidateFunnelSteps(steps []FunnelStep) error {
if len(steps) < 2 {
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.
// 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 {
return []tracefunnel.FunnelStep{}
return []FunnelStep{}
}
newSteps := make([]tracefunnel.FunnelStep, len(steps))
newSteps := make([]FunnelStep, len(steps))
copy(newSteps, steps)
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
}
func ConstructFunnelResponse(funnel *tracefunnel.Funnel, claims *authtypes.Claims) tracefunnel.FunnelResponse {
resp := tracefunnel.FunnelResponse{
func ConstructFunnelResponse(funnel *StorableFunnel, claims *authtypes.Claims) GettableFunnel {
resp := GettableFunnel{
FunnelName: funnel.Name,
FunnelID: funnel.ID.String(),
Steps: funnel.Steps,
@ -110,7 +109,7 @@ func ConstructFunnelResponse(funnel *tracefunnel.Funnel, claims *authtypes.Claim
return resp
}
func ProcessFunnelSteps(steps []tracefunnel.FunnelStep) ([]tracefunnel.FunnelStep, error) {
func ProcessFunnelSteps(steps []FunnelStep) ([]FunnelStep, error) {
// First validate the steps
if err := ValidateFunnelSteps(steps); err != nil {
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/authtypes"
"github.com/SigNoz/signoz/pkg/types/tracefunnel"
"github.com/SigNoz/signoz/pkg/valuer"
"github.com/stretchr/testify/assert"
)
@ -91,12 +90,12 @@ func TestValidateTimestampIsMilliseconds(t *testing.T) {
func TestValidateFunnelSteps(t *testing.T) {
tests := []struct {
name string
steps []tracefunnel.FunnelStep
steps []FunnelStep
expectError bool
}{
{
name: "valid steps",
steps: []tracefunnel.FunnelStep{
steps: []FunnelStep{
{
ID: valuer.GenerateUUID(),
Name: "Step 1",
@ -116,7 +115,7 @@ func TestValidateFunnelSteps(t *testing.T) {
},
{
name: "too few steps",
steps: []tracefunnel.FunnelStep{
steps: []FunnelStep{
{
ID: valuer.GenerateUUID(),
Name: "Step 1",
@ -129,7 +128,7 @@ func TestValidateFunnelSteps(t *testing.T) {
},
{
name: "missing service name",
steps: []tracefunnel.FunnelStep{
steps: []FunnelStep{
{
ID: valuer.GenerateUUID(),
Name: "Step 1",
@ -148,7 +147,7 @@ func TestValidateFunnelSteps(t *testing.T) {
},
{
name: "missing span name",
steps: []tracefunnel.FunnelStep{
steps: []FunnelStep{
{
ID: valuer.GenerateUUID(),
Name: "Step 1",
@ -167,7 +166,7 @@ func TestValidateFunnelSteps(t *testing.T) {
},
{
name: "negative order",
steps: []tracefunnel.FunnelStep{
steps: []FunnelStep{
{
ID: valuer.GenerateUUID(),
Name: "Step 1",
@ -202,12 +201,12 @@ func TestValidateFunnelSteps(t *testing.T) {
func TestNormalizeFunnelSteps(t *testing.T) {
tests := []struct {
name string
steps []tracefunnel.FunnelStep
expected []tracefunnel.FunnelStep
steps []FunnelStep
expected []FunnelStep
}{
{
name: "already normalized steps",
steps: []tracefunnel.FunnelStep{
steps: []FunnelStep{
{
ID: valuer.GenerateUUID(),
Name: "Step 1",
@ -223,7 +222,7 @@ func TestNormalizeFunnelSteps(t *testing.T) {
Order: 2,
},
},
expected: []tracefunnel.FunnelStep{
expected: []FunnelStep{
{
Name: "Step 1",
ServiceName: "test-service",
@ -240,7 +239,7 @@ func TestNormalizeFunnelSteps(t *testing.T) {
},
{
name: "unordered steps",
steps: []tracefunnel.FunnelStep{
steps: []FunnelStep{
{
ID: valuer.GenerateUUID(),
Name: "Step 2",
@ -256,7 +255,7 @@ func TestNormalizeFunnelSteps(t *testing.T) {
Order: 1,
},
},
expected: []tracefunnel.FunnelStep{
expected: []FunnelStep{
{
Name: "Step 1",
ServiceName: "test-service",
@ -273,7 +272,7 @@ func TestNormalizeFunnelSteps(t *testing.T) {
},
{
name: "steps with gaps in order",
steps: []tracefunnel.FunnelStep{
steps: []FunnelStep{
{
ID: valuer.GenerateUUID(),
Name: "Step 1",
@ -296,7 +295,7 @@ func TestNormalizeFunnelSteps(t *testing.T) {
Order: 2,
},
},
expected: []tracefunnel.FunnelStep{
expected: []FunnelStep{
{
Name: "Step 1",
ServiceName: "test-service",
@ -322,7 +321,7 @@ func TestNormalizeFunnelSteps(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// 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)
result := NormalizeFunnelSteps(steps)
@ -429,14 +428,14 @@ func TestConstructFunnelResponse(t *testing.T) {
tests := []struct {
name string
funnel *tracefunnel.Funnel
funnel *StorableFunnel
claims *authtypes.Claims
expected tracefunnel.FunnelResponse
expected GettableFunnel
}{
{
name: "with user email from funnel",
funnel: &tracefunnel.Funnel{
BaseMetadata: tracefunnel.BaseMetadata{
funnel: &StorableFunnel{
BaseMetadata: BaseMetadata{
Identifiable: types.Identifiable{
ID: funnelID,
},
@ -455,7 +454,7 @@ func TestConstructFunnelResponse(t *testing.T) {
Identifiable: types.Identifiable{ID: valuer.MustNewUUID("user-123")},
Email: "funnel@example.com",
},
Steps: []tracefunnel.FunnelStep{
Steps: []*FunnelStep{
{
ID: valuer.GenerateUUID(),
Name: "Step 1",
@ -470,10 +469,10 @@ func TestConstructFunnelResponse(t *testing.T) {
OrgID: orgID.String(),
Email: "claims@example.com",
},
expected: tracefunnel.FunnelResponse{
expected: GettableFunnel{
FunnelName: "test-funnel",
FunnelID: funnelID.String(),
Steps: []tracefunnel.FunnelStep{
Steps: []*FunnelStep{
{
Name: "Step 1",
ServiceName: "test-service",
@ -491,8 +490,8 @@ func TestConstructFunnelResponse(t *testing.T) {
},
{
name: "with user email from claims",
funnel: &tracefunnel.Funnel{
BaseMetadata: tracefunnel.BaseMetadata{
funnel: &StorableFunnel{
BaseMetadata: BaseMetadata{
Identifiable: types.Identifiable{
ID: funnelID,
},
@ -507,7 +506,7 @@ func TestConstructFunnelResponse(t *testing.T) {
UpdatedBy: "user-123",
},
},
Steps: []tracefunnel.FunnelStep{
Steps: []*FunnelStep{
{
ID: valuer.GenerateUUID(),
Name: "Step 1",
@ -522,10 +521,10 @@ func TestConstructFunnelResponse(t *testing.T) {
OrgID: orgID.String(),
Email: "claims@example.com",
},
expected: tracefunnel.FunnelResponse{
expected: GettableFunnel{
FunnelName: "test-funnel",
FunnelID: funnelID.String(),
Steps: []tracefunnel.FunnelStep{
Steps: []*FunnelStep{
{
Name: "Step 1",
ServiceName: "test-service",
@ -573,12 +572,12 @@ func TestConstructFunnelResponse(t *testing.T) {
func TestProcessFunnelSteps(t *testing.T) {
tests := []struct {
name string
steps []tracefunnel.FunnelStep
steps []FunnelStep
expectError bool
}{
{
name: "valid steps with missing IDs",
steps: []tracefunnel.FunnelStep{
steps: []FunnelStep{
{
Name: "Step 1",
ServiceName: "test-service",
@ -596,7 +595,7 @@ func TestProcessFunnelSteps(t *testing.T) {
},
{
name: "invalid steps - missing service name",
steps: []tracefunnel.FunnelStep{
steps: []FunnelStep{
{
Name: "Step 1",
SpanName: "test-span",
@ -613,7 +612,7 @@ func TestProcessFunnelSteps(t *testing.T) {
},
{
name: "invalid steps - negative order",
steps: []tracefunnel.FunnelStep{
steps: []FunnelStep{
{
Name: "Step 1",
ServiceName: "test-service",