feat(licensing): add analytics (#8252)

This commit is contained in:
Vibhu Pandey 2025-06-16 01:09:41 +05:30 committed by GitHub
parent d236b6ce1e
commit 59ff7ed1e1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 79 additions and 11 deletions

View File

@ -6,11 +6,13 @@ import (
"time" "time"
"github.com/SigNoz/signoz/ee/licensing/licensingstore/sqllicensingstore" "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/errors"
"github.com/SigNoz/signoz/pkg/factory" "github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/licensing" "github.com/SigNoz/signoz/pkg/licensing"
"github.com/SigNoz/signoz/pkg/modules/organization" "github.com/SigNoz/signoz/pkg/modules/organization"
"github.com/SigNoz/signoz/pkg/sqlstore" "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/types/licensetypes"
"github.com/SigNoz/signoz/pkg/valuer" "github.com/SigNoz/signoz/pkg/valuer"
"github.com/SigNoz/signoz/pkg/zeus" "github.com/SigNoz/signoz/pkg/zeus"
@ -23,16 +25,17 @@ type provider struct {
config licensing.Config config licensing.Config
settings factory.ScopedProviderSettings settings factory.ScopedProviderSettings
orgGetter organization.Getter orgGetter organization.Getter
analytics analytics.Analytics
stopChan chan struct{} 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 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") settings := factory.NewScopedProviderSettings(ps, "github.com/SigNoz/signoz/ee/licensing/httplicensing")
licensestore := sqllicensingstore.New(sqlstore) licensestore := sqllicensingstore.New(sqlstore)
return &provider{ return &provider{
@ -42,6 +45,7 @@ func New(ctx context.Context, ps factory.ProviderSettings, config licensing.Conf
settings: settings, settings: settings,
orgGetter: orgGetter, orgGetter: orgGetter,
stopChan: make(chan struct{}), stopChan: make(chan struct{}),
analytics: analytics,
}, nil }, nil
} }
@ -159,6 +163,25 @@ func (provider *provider) Refresh(ctx context.Context, organizationID valuer.UUI
return err 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 return nil
} }

View File

@ -12,6 +12,7 @@ import (
"github.com/SigNoz/signoz/ee/sqlstore/postgressqlstore" "github.com/SigNoz/signoz/ee/sqlstore/postgressqlstore"
"github.com/SigNoz/signoz/ee/zeus" "github.com/SigNoz/signoz/ee/zeus"
"github.com/SigNoz/signoz/ee/zeus/httpzeus" "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"
"github.com/SigNoz/signoz/pkg/config/envprovider" "github.com/SigNoz/signoz/pkg/config/envprovider"
"github.com/SigNoz/signoz/pkg/config/fileprovider" "github.com/SigNoz/signoz/pkg/config/fileprovider"
@ -134,8 +135,8 @@ func main() {
zeus.Config(), zeus.Config(),
httpzeus.NewProviderFactory(), httpzeus.NewProviderFactory(),
licensing.Config(24*time.Hour, 3), licensing.Config(24*time.Hour, 3),
func(sqlstore sqlstore.SQLStore, zeus pkgzeus.Zeus, orgGetter organization.Getter) factory.ProviderFactory[pkglicensing.Licensing, pkglicensing.Config] { 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) return httplicensing.NewProviderFactory(sqlstore, zeus, orgGetter, analytics)
}, },
signoz.NewEmailingProviderFactories(), signoz.NewEmailingProviderFactories(),
signoz.NewCacheProviderFactories(), signoz.NewCacheProviderFactories(),

View File

@ -6,6 +6,7 @@ import (
"os" "os"
"time" "time"
"github.com/SigNoz/signoz/pkg/analytics"
"github.com/SigNoz/signoz/pkg/config" "github.com/SigNoz/signoz/pkg/config"
"github.com/SigNoz/signoz/pkg/config/envprovider" "github.com/SigNoz/signoz/pkg/config/envprovider"
"github.com/SigNoz/signoz/pkg/config/fileprovider" "github.com/SigNoz/signoz/pkg/config/fileprovider"
@ -122,7 +123,7 @@ func main() {
zeus.Config{}, zeus.Config{},
noopzeus.NewProviderFactory(), noopzeus.NewProviderFactory(),
licensing.Config{}, 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() return nooplicensing.NewFactory()
}, },
signoz.NewEmailingProviderFactories(), signoz.NewEmailingProviderFactories(),

View File

@ -4,6 +4,7 @@ import (
"context" "context"
"github.com/SigNoz/signoz/pkg/alertmanager" "github.com/SigNoz/signoz/pkg/alertmanager"
"github.com/SigNoz/signoz/pkg/analytics"
"github.com/SigNoz/signoz/pkg/cache" "github.com/SigNoz/signoz/pkg/cache"
"github.com/SigNoz/signoz/pkg/emailing" "github.com/SigNoz/signoz/pkg/emailing"
"github.com/SigNoz/signoz/pkg/factory" "github.com/SigNoz/signoz/pkg/factory"
@ -54,7 +55,7 @@ func New(
zeusConfig zeus.Config, zeusConfig zeus.Config,
zeusProviderFactory factory.ProviderFactory[zeus.Zeus, zeus.Config], zeusProviderFactory factory.ProviderFactory[zeus.Zeus, zeus.Config],
licenseConfig licensing.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]], emailingProviderFactories factory.NamedMap[factory.ProviderFactory[emailing.Emailing, emailing.Config]],
cacheProviderFactories factory.NamedMap[factory.ProviderFactory[cache.Cache, cache.Config]], cacheProviderFactories factory.NamedMap[factory.ProviderFactory[cache.Cache, cache.Config]],
webProviderFactories factory.NamedMap[factory.ProviderFactory[web.Web, web.Config]], webProviderFactories factory.NamedMap[factory.ProviderFactory[web.Web, web.Config]],
@ -235,7 +236,7 @@ func New(
return nil, err return nil, err
} }
licensingProviderFactory := licenseProviderFactory(sqlstore, zeus, orgGetter) licensingProviderFactory := licenseProviderFactory(sqlstore, zeus, orgGetter, analytics)
licensing, err := licensingProviderFactory.New( licensing, err := licensingProviderFactory.New(
ctx, ctx,
providerSettings, providerSettings,

View File

@ -32,6 +32,8 @@ type License struct {
PlanName valuer.String PlanName valuer.String
Features []*Feature Features []*Feature
Status valuer.String Status valuer.String
State string
FreeUntil time.Time
ValidFrom int64 ValidFrom int64
ValidUntil int64 ValidUntil int64
CreatedAt time.Time CreatedAt time.Time
@ -165,6 +167,21 @@ func NewLicense(data []byte, organizationID valuer.UUID) (*License, error) {
planName = PlanNameBasic 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) featuresFromZeus := make([]*Feature, 0)
if _features, ok := licenseData["features"]; ok { if _features, ok := licenseData["features"]; ok {
featuresData, err := json.Marshal(_features) featuresData, err := json.Marshal(_features)
@ -224,6 +241,8 @@ func NewLicense(data []byte, organizationID valuer.UUID) (*License, error) {
ValidFrom: validFrom, ValidFrom: validFrom,
ValidUntil: validUntil, ValidUntil: validUntil,
Status: status, Status: status,
State: state,
FreeUntil: freeUntil,
CreatedAt: time.Now(), CreatedAt: time.Now(),
UpdatedAt: time.Now(), UpdatedAt: time.Now(),
LastValidatedAt: time.Now(), LastValidatedAt: time.Now(),
@ -306,6 +325,21 @@ func NewLicenseFromStorableLicense(storableLicense *StorableLicense) (*License,
} }
validUntil := int64(_validUntil) 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{ return &License{
ID: storableLicense.ID, ID: storableLicense.ID,
Key: storableLicense.Key, Key: storableLicense.Key,
@ -315,6 +349,8 @@ func NewLicenseFromStorableLicense(storableLicense *StorableLicense) (*License,
ValidFrom: validFrom, ValidFrom: validFrom,
ValidUntil: validUntil, ValidUntil: validUntil,
Status: status, Status: status,
State: state,
FreeUntil: freeUntil,
CreatedAt: storableLicense.CreatedAt, CreatedAt: storableLicense.CreatedAt,
UpdatedAt: storableLicense.UpdatedAt, UpdatedAt: storableLicense.UpdatedAt,
LastValidatedAt: storableLicense.LastValidatedAt, LastValidatedAt: storableLicense.LastValidatedAt,
@ -325,8 +361,10 @@ func NewLicenseFromStorableLicense(storableLicense *StorableLicense) (*License,
func NewStatsFromLicense(license *License) map[string]any { func NewStatsFromLicense(license *License) map[string]any {
return 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(),
} }
} }

View File

@ -74,7 +74,7 @@ func TestNewLicenseV3(t *testing.T) {
}, },
{ {
name: "Parse the entire license properly", 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, pass: true,
expected: &License{ expected: &License{
ID: valuer.MustNewUUID("0196f794-ff30-7bee-a5f4-ef5ad315715e"), ID: valuer.MustNewUUID("0196f794-ff30-7bee-a5f4-ef5ad315715e"),
@ -87,11 +87,15 @@ func TestNewLicenseV3(t *testing.T) {
"status": "ACTIVE", "status": "ACTIVE",
"valid_from": float64(1730899309), "valid_from": float64(1730899309),
"valid_until": float64(-1), "valid_until": float64(-1),
"state": "test",
"free_until": "2025-05-16T11:17:48.124202Z",
}, },
PlanName: PlanNameEnterprise, PlanName: PlanNameEnterprise,
ValidFrom: 1730899309, ValidFrom: 1730899309,
ValidUntil: -1, ValidUntil: -1,
Status: valuer.NewString("ACTIVE"), Status: valuer.NewString("ACTIVE"),
State: "test",
FreeUntil: time.Date(2025, 5, 16, 11, 17, 48, 124202000, time.UTC),
Features: make([]*Feature, 0), Features: make([]*Feature, 0),
OrganizationID: valuer.MustNewUUID("0196f794-ff30-7bee-a5f4-ef5ad315715e"), OrganizationID: valuer.MustNewUUID("0196f794-ff30-7bee-a5f4-ef5ad315715e"),
}, },