From 59ff7ed1e17cda99775b62f9a6808399d67570ee Mon Sep 17 00:00:00 2001 From: Vibhu Pandey Date: Mon, 16 Jun 2025 01:09:41 +0530 Subject: [PATCH] feat(licensing): add analytics (#8252) --- ee/licensing/httplicensing/provider.go | 29 ++++++++++++++++-- ee/query-service/main.go | 5 +-- pkg/query-service/main.go | 3 +- pkg/signoz/signoz.go | 5 +-- pkg/types/licensetypes/license.go | 42 ++++++++++++++++++++++++-- pkg/types/licensetypes/license_test.go | 6 +++- 6 files changed, 79 insertions(+), 11 deletions(-) diff --git a/ee/licensing/httplicensing/provider.go b/ee/licensing/httplicensing/provider.go index 69dd05c7601d..4bd2be39224d 100644 --- a/ee/licensing/httplicensing/provider.go +++ b/ee/licensing/httplicensing/provider.go @@ -6,11 +6,13 @@ import ( "time" "github.com/SigNoz/signoz/ee/licensing/licensingstore/sqllicensingstore" + "github.com/SigNoz/signoz/pkg/analytics" "github.com/SigNoz/signoz/pkg/errors" "github.com/SigNoz/signoz/pkg/factory" "github.com/SigNoz/signoz/pkg/licensing" "github.com/SigNoz/signoz/pkg/modules/organization" "github.com/SigNoz/signoz/pkg/sqlstore" + "github.com/SigNoz/signoz/pkg/types/analyticstypes" "github.com/SigNoz/signoz/pkg/types/licensetypes" "github.com/SigNoz/signoz/pkg/valuer" "github.com/SigNoz/signoz/pkg/zeus" @@ -23,16 +25,17 @@ type provider struct { config licensing.Config settings factory.ScopedProviderSettings orgGetter organization.Getter + analytics analytics.Analytics stopChan chan struct{} } -func NewProviderFactory(store sqlstore.SQLStore, zeus zeus.Zeus, orgGetter organization.Getter) factory.ProviderFactory[licensing.Licensing, licensing.Config] { +func NewProviderFactory(store sqlstore.SQLStore, zeus zeus.Zeus, orgGetter organization.Getter, analytics analytics.Analytics) factory.ProviderFactory[licensing.Licensing, licensing.Config] { return factory.NewProviderFactory(factory.MustNewName("http"), func(ctx context.Context, providerSettings factory.ProviderSettings, config licensing.Config) (licensing.Licensing, error) { - return New(ctx, providerSettings, config, store, zeus, orgGetter) + return New(ctx, providerSettings, config, store, zeus, orgGetter, analytics) }) } -func New(ctx context.Context, ps factory.ProviderSettings, config licensing.Config, sqlstore sqlstore.SQLStore, zeus zeus.Zeus, orgGetter organization.Getter) (licensing.Licensing, error) { +func New(ctx context.Context, ps factory.ProviderSettings, config licensing.Config, sqlstore sqlstore.SQLStore, zeus zeus.Zeus, orgGetter organization.Getter, analytics analytics.Analytics) (licensing.Licensing, error) { settings := factory.NewScopedProviderSettings(ps, "github.com/SigNoz/signoz/ee/licensing/httplicensing") licensestore := sqllicensingstore.New(sqlstore) return &provider{ @@ -42,6 +45,7 @@ func New(ctx context.Context, ps factory.ProviderSettings, config licensing.Conf settings: settings, orgGetter: orgGetter, stopChan: make(chan struct{}), + analytics: analytics, }, nil } @@ -159,6 +163,25 @@ func (provider *provider) Refresh(ctx context.Context, organizationID valuer.UUI return err } + stats := licensetypes.NewStatsFromLicense(activeLicense) + provider.analytics.Send(ctx, + analyticstypes.Track{ + UserId: "stats_" + organizationID.String(), + Event: "License Updated", + Properties: analyticstypes.NewPropertiesFromMap(stats), + Context: &analyticstypes.Context{ + Extra: map[string]interface{}{ + analyticstypes.KeyGroupID: organizationID.String(), + }, + }, + }, + analyticstypes.Group{ + UserId: "stats_" + organizationID.String(), + GroupId: organizationID.String(), + Traits: analyticstypes.NewTraitsFromMap(stats), + }, + ) + return nil } diff --git a/ee/query-service/main.go b/ee/query-service/main.go index 6d9c3eeaf4ac..53a2102e6911 100644 --- a/ee/query-service/main.go +++ b/ee/query-service/main.go @@ -12,6 +12,7 @@ import ( "github.com/SigNoz/signoz/ee/sqlstore/postgressqlstore" "github.com/SigNoz/signoz/ee/zeus" "github.com/SigNoz/signoz/ee/zeus/httpzeus" + "github.com/SigNoz/signoz/pkg/analytics" "github.com/SigNoz/signoz/pkg/config" "github.com/SigNoz/signoz/pkg/config/envprovider" "github.com/SigNoz/signoz/pkg/config/fileprovider" @@ -134,8 +135,8 @@ func main() { zeus.Config(), httpzeus.NewProviderFactory(), licensing.Config(24*time.Hour, 3), - func(sqlstore sqlstore.SQLStore, zeus pkgzeus.Zeus, orgGetter organization.Getter) factory.ProviderFactory[pkglicensing.Licensing, pkglicensing.Config] { - return httplicensing.NewProviderFactory(sqlstore, zeus, orgGetter) + func(sqlstore sqlstore.SQLStore, zeus pkgzeus.Zeus, orgGetter organization.Getter, analytics analytics.Analytics) factory.ProviderFactory[pkglicensing.Licensing, pkglicensing.Config] { + return httplicensing.NewProviderFactory(sqlstore, zeus, orgGetter, analytics) }, signoz.NewEmailingProviderFactories(), signoz.NewCacheProviderFactories(), diff --git a/pkg/query-service/main.go b/pkg/query-service/main.go index ddfb28002bee..6d9505f9e43f 100644 --- a/pkg/query-service/main.go +++ b/pkg/query-service/main.go @@ -6,6 +6,7 @@ import ( "os" "time" + "github.com/SigNoz/signoz/pkg/analytics" "github.com/SigNoz/signoz/pkg/config" "github.com/SigNoz/signoz/pkg/config/envprovider" "github.com/SigNoz/signoz/pkg/config/fileprovider" @@ -122,7 +123,7 @@ func main() { zeus.Config{}, noopzeus.NewProviderFactory(), licensing.Config{}, - func(_ sqlstore.SQLStore, _ zeus.Zeus, _ organization.Getter) factory.ProviderFactory[licensing.Licensing, licensing.Config] { + func(_ sqlstore.SQLStore, _ zeus.Zeus, _ organization.Getter, _ analytics.Analytics) factory.ProviderFactory[licensing.Licensing, licensing.Config] { return nooplicensing.NewFactory() }, signoz.NewEmailingProviderFactories(), diff --git a/pkg/signoz/signoz.go b/pkg/signoz/signoz.go index 6b78785f063c..adb33f49b014 100644 --- a/pkg/signoz/signoz.go +++ b/pkg/signoz/signoz.go @@ -4,6 +4,7 @@ import ( "context" "github.com/SigNoz/signoz/pkg/alertmanager" + "github.com/SigNoz/signoz/pkg/analytics" "github.com/SigNoz/signoz/pkg/cache" "github.com/SigNoz/signoz/pkg/emailing" "github.com/SigNoz/signoz/pkg/factory" @@ -54,7 +55,7 @@ func New( zeusConfig zeus.Config, zeusProviderFactory factory.ProviderFactory[zeus.Zeus, zeus.Config], licenseConfig licensing.Config, - licenseProviderFactory func(sqlstore.SQLStore, zeus.Zeus, organization.Getter) factory.ProviderFactory[licensing.Licensing, licensing.Config], + licenseProviderFactory func(sqlstore.SQLStore, zeus.Zeus, organization.Getter, analytics.Analytics) factory.ProviderFactory[licensing.Licensing, licensing.Config], emailingProviderFactories factory.NamedMap[factory.ProviderFactory[emailing.Emailing, emailing.Config]], cacheProviderFactories factory.NamedMap[factory.ProviderFactory[cache.Cache, cache.Config]], webProviderFactories factory.NamedMap[factory.ProviderFactory[web.Web, web.Config]], @@ -235,7 +236,7 @@ func New( return nil, err } - licensingProviderFactory := licenseProviderFactory(sqlstore, zeus, orgGetter) + licensingProviderFactory := licenseProviderFactory(sqlstore, zeus, orgGetter, analytics) licensing, err := licensingProviderFactory.New( ctx, providerSettings, diff --git a/pkg/types/licensetypes/license.go b/pkg/types/licensetypes/license.go index 74028447a5db..0f624f1c0b70 100644 --- a/pkg/types/licensetypes/license.go +++ b/pkg/types/licensetypes/license.go @@ -32,6 +32,8 @@ type License struct { PlanName valuer.String Features []*Feature Status valuer.String + State string + FreeUntil time.Time ValidFrom int64 ValidUntil int64 CreatedAt time.Time @@ -165,6 +167,21 @@ func NewLicense(data []byte, organizationID valuer.UUID) (*License, error) { planName = PlanNameBasic } + state, err := extractKeyFromMapStringInterface[string](licenseData, "state") + if err != nil { + state = "" + } + + freeUntilStr, err := extractKeyFromMapStringInterface[string](licenseData, "free_until") + if err != nil { + freeUntilStr = "" + } + + freeUntil, err := time.Parse(time.RFC3339, freeUntilStr) + if err != nil { + freeUntil = time.Time{} + } + featuresFromZeus := make([]*Feature, 0) if _features, ok := licenseData["features"]; ok { featuresData, err := json.Marshal(_features) @@ -224,6 +241,8 @@ func NewLicense(data []byte, organizationID valuer.UUID) (*License, error) { ValidFrom: validFrom, ValidUntil: validUntil, Status: status, + State: state, + FreeUntil: freeUntil, CreatedAt: time.Now(), UpdatedAt: time.Now(), LastValidatedAt: time.Now(), @@ -306,6 +325,21 @@ func NewLicenseFromStorableLicense(storableLicense *StorableLicense) (*License, } validUntil := int64(_validUntil) + state, err := extractKeyFromMapStringInterface[string](storableLicense.Data, "state") + if err != nil { + state = "" + } + + freeUntilStr, err := extractKeyFromMapStringInterface[string](storableLicense.Data, "free_until") + if err != nil { + freeUntilStr = "" + } + + freeUntil, err := time.Parse(time.RFC3339, freeUntilStr) + if err != nil { + freeUntil = time.Time{} + } + return &License{ ID: storableLicense.ID, Key: storableLicense.Key, @@ -315,6 +349,8 @@ func NewLicenseFromStorableLicense(storableLicense *StorableLicense) (*License, ValidFrom: validFrom, ValidUntil: validUntil, Status: status, + State: state, + FreeUntil: freeUntil, CreatedAt: storableLicense.CreatedAt, UpdatedAt: storableLicense.UpdatedAt, LastValidatedAt: storableLicense.LastValidatedAt, @@ -325,8 +361,10 @@ func NewLicenseFromStorableLicense(storableLicense *StorableLicense) (*License, func NewStatsFromLicense(license *License) map[string]any { return map[string]any{ - "license.plan": license.PlanName.StringValue(), - "license.id": license.ID.StringValue(), + "license.id": license.ID.StringValue(), + "license.plan.name": license.PlanName.StringValue(), + "license.state.name": license.State, + "license.free_until.time": license.FreeUntil.UTC(), } } diff --git a/pkg/types/licensetypes/license_test.go b/pkg/types/licensetypes/license_test.go index 5ddff2c4e4b9..e6589643f47b 100644 --- a/pkg/types/licensetypes/license_test.go +++ b/pkg/types/licensetypes/license_test.go @@ -74,7 +74,7 @@ func TestNewLicenseV3(t *testing.T) { }, { name: "Parse the entire license properly", - data: []byte(`{"id":"0196f794-ff30-7bee-a5f4-ef5ad315715e","key":"does-not-matter-key","category":"FREE","status":"ACTIVE","plan":{"name":"ENTERPRISE"},"valid_from": 1730899309,"valid_until": -1}`), + data: []byte(`{"id":"0196f794-ff30-7bee-a5f4-ef5ad315715e","key":"does-not-matter-key","category":"FREE","status":"ACTIVE","plan":{"name":"ENTERPRISE"},"valid_from": 1730899309,"valid_until": -1,"state":"test","free_until":"2025-05-16T11:17:48.124202Z"}`), pass: true, expected: &License{ ID: valuer.MustNewUUID("0196f794-ff30-7bee-a5f4-ef5ad315715e"), @@ -87,11 +87,15 @@ func TestNewLicenseV3(t *testing.T) { "status": "ACTIVE", "valid_from": float64(1730899309), "valid_until": float64(-1), + "state": "test", + "free_until": "2025-05-16T11:17:48.124202Z", }, PlanName: PlanNameEnterprise, ValidFrom: 1730899309, ValidUntil: -1, Status: valuer.NewString("ACTIVE"), + State: "test", + FreeUntil: time.Date(2025, 5, 16, 11, 17, 48, 124202000, time.UTC), Features: make([]*Feature, 0), OrganizationID: valuer.MustNewUUID("0196f794-ff30-7bee-a5f4-ef5ad315715e"), },