diff --git a/pkg/modules/tracefunnel/impltracefunnel/module.go b/pkg/modules/tracefunnel/impltracefunnel/module.go index a822fff953..346c6d569d 100644 --- a/pkg/modules/tracefunnel/impltracefunnel/module.go +++ b/pkg/modules/tracefunnel/impltracefunnel/module.go @@ -38,7 +38,9 @@ func (module *module) Create(ctx context.Context, timestamp int64, name string, // Set up the user relationship funnel.CreatedByUser = &types.User{ - ID: userID, + Identifiable: types.Identifiable{ + ID: valuer.MustNewUUID(userID), + }, } if err := module.store.Create(ctx, funnel); err != nil { diff --git a/pkg/modules/tracefunnel/impltracefunnel/store.go b/pkg/modules/tracefunnel/impltracefunnel/store.go index fc995f568e..01692c49ca 100644 --- a/pkg/modules/tracefunnel/impltracefunnel/store.go +++ b/pkg/modules/tracefunnel/impltracefunnel/store.go @@ -32,7 +32,7 @@ func (store *store) Create(ctx context.Context, funnel *traceFunnels.Funnel) err // Set created_by if CreatedByUser is present if funnel.CreatedByUser != nil { - funnel.CreatedBy = funnel.CreatedByUser.ID + funnel.CreatedBy = funnel.CreatedByUser.Identifiable.ID.String() } _, err := store. diff --git a/pkg/modules/tracefunnel/utils.go b/pkg/modules/tracefunnel/utils.go index df14a6b8d4..e2ab16cd79 100644 --- a/pkg/modules/tracefunnel/utils.go +++ b/pkg/modules/tracefunnel/utils.go @@ -8,7 +8,7 @@ import ( "github.com/SigNoz/signoz/pkg/errors" "github.com/SigNoz/signoz/pkg/types/authtypes" - tracefunnel "github.com/SigNoz/signoz/pkg/types/tracefunnel" + "github.com/SigNoz/signoz/pkg/types/tracefunnel" "github.com/SigNoz/signoz/pkg/valuer" ) diff --git a/pkg/modules/tracefunnel/utils_test.go b/pkg/modules/tracefunnel/utils_test.go index 17556b8a73..faff8a647e 100644 --- a/pkg/modules/tracefunnel/utils_test.go +++ b/pkg/modules/tracefunnel/utils_test.go @@ -8,7 +8,7 @@ import ( "github.com/SigNoz/signoz/pkg/types" "github.com/SigNoz/signoz/pkg/types/authtypes" - tracefunnel "github.com/SigNoz/signoz/pkg/types/tracefunnel" + "github.com/SigNoz/signoz/pkg/types/tracefunnel" "github.com/SigNoz/signoz/pkg/valuer" "github.com/stretchr/testify/assert" ) @@ -452,8 +452,8 @@ func TestConstructFunnelResponse(t *testing.T) { }, }, CreatedByUser: &types.User{ - ID: "user-123", - Email: "funnel@example.com", + Identifiable: types.Identifiable{ID: valuer.MustNewUUID("user-123")}, + Email: "funnel@example.com", }, Steps: []tracefunnel.FunnelStep{ { diff --git a/pkg/query-service/app/traces/v4/query_builder.go b/pkg/query-service/app/traces/v4/query_builder.go index 7b2befdd95..69a76de4d1 100644 --- a/pkg/query-service/app/traces/v4/query_builder.go +++ b/pkg/query-service/app/traces/v4/query_builder.go @@ -87,7 +87,7 @@ func existsSubQueryForFixedColumn(key v3.AttributeKey, op v3.FilterOperator) (st } } -func buildTracesFilterQuery(fs *v3.FilterSet) (string, error) { +func BuildTracesFilterQuery(fs *v3.FilterSet) (string, error) { var conditions []string if fs != nil && len(fs.Items) != 0 { @@ -167,7 +167,7 @@ func handleEmptyValuesInGroupBy(groupBy []v3.AttributeKey) (string, error) { Operator: "AND", Items: filterItems, } - return buildTracesFilterQuery(&filterSet) + return BuildTracesFilterQuery(&filterSet) } return "", nil } @@ -248,7 +248,7 @@ func buildTracesQuery(start, end, step int64, mq *v3.BuilderQuery, panelType v3. timeFilter := fmt.Sprintf("(timestamp >= '%d' AND timestamp <= '%d') AND (ts_bucket_start >= %d AND ts_bucket_start <= %d)", tracesStart, tracesEnd, bucketStart, bucketEnd) - filterSubQuery, err := buildTracesFilterQuery(mq.Filters) + filterSubQuery, err := BuildTracesFilterQuery(mq.Filters) if err != nil { return "", err } diff --git a/pkg/query-service/app/traces/v4/query_builder_test.go b/pkg/query-service/app/traces/v4/query_builder_test.go index eff4070b54..8943083162 100644 --- a/pkg/query-service/app/traces/v4/query_builder_test.go +++ b/pkg/query-service/app/traces/v4/query_builder_test.go @@ -211,7 +211,7 @@ func Test_buildTracesFilterQuery(t *testing.T) { want: "", }, { - name: "Test buildTracesFilterQuery in, nin", + name: "Test BuildTracesFilterQuery in, nin", args: args{ fs: &v3.FilterSet{Operator: "AND", Items: []v3.FilterItem{ {Key: v3.AttributeKey{Key: "method", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}, Value: []interface{}{"GET", "POST"}, Operator: v3.FilterOperatorIn}, @@ -226,7 +226,7 @@ func Test_buildTracesFilterQuery(t *testing.T) { wantErr: false, }, { - name: "Test buildTracesFilterQuery not eq, neq, gt, lt, gte, lte", + name: "Test BuildTracesFilterQuery not eq, neq, gt, lt, gte, lte", args: args{ fs: &v3.FilterSet{Operator: "AND", Items: []v3.FilterItem{ {Key: v3.AttributeKey{Key: "duration", DataType: v3.AttributeKeyDataTypeInt64, Type: v3.AttributeKeyTypeTag}, Value: 102, Operator: v3.FilterOperatorEqual}, @@ -274,13 +274,13 @@ func Test_buildTracesFilterQuery(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := buildTracesFilterQuery(tt.args.fs) + got, err := BuildTracesFilterQuery(tt.args.fs) if (err != nil) != tt.wantErr { - t.Errorf("buildTracesFilterQuery() error = %v, wantErr %v", err, tt.wantErr) + t.Errorf("BuildTracesFilterQuery() error = %v, wantErr %v", err, tt.wantErr) return } if got != tt.want { - t.Errorf("buildTracesFilterQuery() = %v, want %v", got, tt.want) + t.Errorf("BuildTracesFilterQuery() = %v, want %v", got, tt.want) } }) } diff --git a/pkg/signoz/handler.go b/pkg/signoz/handler.go index 2cf4c1a299..a9253ed825 100644 --- a/pkg/signoz/handler.go +++ b/pkg/signoz/handler.go @@ -13,6 +13,8 @@ import ( "github.com/SigNoz/signoz/pkg/modules/quickfilter/implquickfilter" "github.com/SigNoz/signoz/pkg/modules/savedview" "github.com/SigNoz/signoz/pkg/modules/savedview/implsavedview" + "github.com/SigNoz/signoz/pkg/modules/tracefunnel" + "github.com/SigNoz/signoz/pkg/modules/tracefunnel/impltracefunnel" "github.com/SigNoz/signoz/pkg/modules/user" "github.com/SigNoz/signoz/pkg/modules/user/impluser" "github.com/SigNoz/signoz/pkg/modules/tracefunnel" @@ -23,6 +25,7 @@ type Handlers struct { Organization organization.Handler Preference preference.Handler User user.Handler + TraceFunnel tracefunnel.Handler SavedView savedview.Handler Apdex apdex.Handler Dashboard dashboard.Handler diff --git a/pkg/signoz/module.go b/pkg/signoz/module.go index b4e6704de2..33d9be5983 100644 --- a/pkg/signoz/module.go +++ b/pkg/signoz/module.go @@ -16,10 +16,10 @@ import ( "github.com/SigNoz/signoz/pkg/modules/quickfilter/implquickfilter" "github.com/SigNoz/signoz/pkg/modules/savedview" "github.com/SigNoz/signoz/pkg/modules/savedview/implsavedview" - "github.com/SigNoz/signoz/pkg/modules/user" - "github.com/SigNoz/signoz/pkg/modules/user/impluser" "github.com/SigNoz/signoz/pkg/modules/tracefunnel" "github.com/SigNoz/signoz/pkg/modules/tracefunnel/impltracefunnel" + "github.com/SigNoz/signoz/pkg/modules/user" + "github.com/SigNoz/signoz/pkg/modules/user/impluser" "github.com/SigNoz/signoz/pkg/sqlstore" "github.com/SigNoz/signoz/pkg/types/authtypes" "github.com/SigNoz/signoz/pkg/types/preferencetypes" diff --git a/pkg/signoz/provider.go b/pkg/signoz/provider.go index 6076890333..c3b33b47ae 100644 --- a/pkg/signoz/provider.go +++ b/pkg/signoz/provider.go @@ -83,6 +83,7 @@ func NewSQLMigrationProviderFactories(sqlstore sqlstore.SQLStore) factory.NamedM sqlmigration.NewDropGroupsFactory(sqlstore), sqlmigration.NewCreateQuickFiltersFactory(sqlstore), sqlmigration.NewUpdateQuickFiltersFactory(sqlstore), + sqlmigration.NewAddTraceFunnelsFactory(sqlstore), sqlmigration.NewAuthRefactorFactory(sqlstore), sqlmigration.NewUpdateLicenseFactory(sqlstore), sqlmigration.NewMigratePATToFactorAPIKey(sqlstore), diff --git a/pkg/sqlmigration/050_add_trace_funnels.go b/pkg/sqlmigration/050_add_trace_funnels.go new file mode 100644 index 0000000000..1d8be2c34f --- /dev/null +++ b/pkg/sqlmigration/050_add_trace_funnels.go @@ -0,0 +1,114 @@ +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"). + 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 +} diff --git a/pkg/types/tracefunnel/tracefunnel.go b/pkg/types/tracefunnel/tracefunnel.go index 8ffdb6074b..91731c2b9b 100644 --- a/pkg/types/tracefunnel/tracefunnel.go +++ b/pkg/types/tracefunnel/tracefunnel.go @@ -84,8 +84,8 @@ type TimeRange struct { // StepTransitionRequest represents a request for step transition analytics type StepTransitionRequest struct { TimeRange - StepAOrder int64 `json:"step_a_order"` - StepBOrder int64 `json:"step_b_order"` + StepAOrder int64 `json:"step_start,omitempty"` + StepBOrder int64 `json:"step_end,omitempty"` } // UserInfo represents basic user information