mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-15 09:35:53 +08:00
chore(feature): drop the feature status table (#8124)
* chore(feature): drop the feature set table * chore(feature): cleanup the types and remove unused flags * chore(feature): some more cleanup * chore(feature): add codeowners file * chore(feature): init to basic plan for failed validations * chore(feature): cleanup * chore(feature): pkg handler cleanup * chore(feature): pkg handler cleanup * chore(feature): address review comments * chore(feature): address review comments * chore(feature): address review comments --------- Co-authored-by: Vibhu Pandey <vibhupandey28@gmail.com>
This commit is contained in:
parent
c58cf67eb0
commit
c32dd9f17e
2
.github/CODEOWNERS
vendored
2
.github/CODEOWNERS
vendored
@ -11,5 +11,5 @@
|
|||||||
/pkg/errors/ @grandwizard28
|
/pkg/errors/ @grandwizard28
|
||||||
/pkg/factory/ @grandwizard28
|
/pkg/factory/ @grandwizard28
|
||||||
/pkg/types/ @grandwizard28
|
/pkg/types/ @grandwizard28
|
||||||
/pkg/sqlmigration/ @vikrantgupta25
|
|
||||||
.golangci.yml @grandwizard28
|
.golangci.yml @grandwizard28
|
||||||
|
**/(zeus|licensing|sqlmigration)/ @vikrantgupta25
|
@ -5,15 +5,12 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/SigNoz/signoz/ee/query-service/constants"
|
|
||||||
|
|
||||||
"github.com/SigNoz/signoz/ee/licensing/licensingstore/sqllicensingstore"
|
"github.com/SigNoz/signoz/ee/licensing/licensingstore/sqllicensingstore"
|
||||||
"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/featuretypes"
|
|
||||||
"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"
|
||||||
@ -89,13 +86,6 @@ func (provider *provider) Validate(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(organizations) == 0 {
|
|
||||||
err = provider.InitFeatures(ctx, licensetypes.BasicPlan)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,11 +106,6 @@ func (provider *provider) Activate(ctx context.Context, organizationID valuer.UU
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = provider.InitFeatures(ctx, license.Features)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,28 +125,24 @@ func (provider *provider) GetActive(ctx context.Context, organizationID valuer.U
|
|||||||
|
|
||||||
func (provider *provider) Refresh(ctx context.Context, organizationID valuer.UUID) error {
|
func (provider *provider) Refresh(ctx context.Context, organizationID valuer.UUID) error {
|
||||||
activeLicense, err := provider.GetActive(ctx, organizationID)
|
activeLicense, err := provider.GetActive(ctx, organizationID)
|
||||||
if err != nil && !errors.Ast(err, errors.TypeNotFound) {
|
if err != nil {
|
||||||
|
if errors.Ast(err, errors.TypeNotFound) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
provider.settings.Logger().ErrorContext(ctx, "license validation failed", "org_id", organizationID.StringValue())
|
provider.settings.Logger().ErrorContext(ctx, "license validation failed", "org_id", organizationID.StringValue())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil && errors.Ast(err, errors.TypeNotFound) {
|
|
||||||
provider.settings.Logger().DebugContext(ctx, "no active license found, defaulting to basic plan", "org_id", organizationID.StringValue())
|
|
||||||
err = provider.InitFeatures(ctx, licensetypes.BasicPlan)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err := provider.zeus.GetLicense(ctx, activeLicense.Key)
|
data, err := provider.zeus.GetLicense(ctx, activeLicense.Key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if time.Since(activeLicense.LastValidatedAt) > time.Duration(provider.config.FailureThreshold)*provider.config.PollInterval {
|
if time.Since(activeLicense.LastValidatedAt) > time.Duration(provider.config.FailureThreshold)*provider.config.PollInterval {
|
||||||
provider.settings.Logger().ErrorContext(ctx, "license validation failed for consecutive poll intervals, defaulting to basic plan", "failure_threshold", provider.config.FailureThreshold, "license_id", activeLicense.ID.StringValue(), "org_id", organizationID.StringValue())
|
activeLicense.UpdateFeatures(licensetypes.BasicPlan)
|
||||||
err = provider.InitFeatures(ctx, licensetypes.BasicPlan)
|
updatedStorableLicense := licensetypes.NewStorableLicenseFromLicense(activeLicense)
|
||||||
|
err = provider.store.Update(ctx, organizationID, updatedStorableLicense)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
@ -178,11 +159,6 @@ func (provider *provider) Refresh(ctx context.Context, organizationID valuer.UUI
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = provider.InitFeatures(ctx, activeLicense.Features)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -224,80 +200,14 @@ func (provider *provider) Portal(ctx context.Context, organizationID valuer.UUID
|
|||||||
return &licensetypes.GettableSubscription{RedirectURL: gjson.GetBytes(response, "url").String()}, nil
|
return &licensetypes.GettableSubscription{RedirectURL: gjson.GetBytes(response, "url").String()}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// feature surrogate
|
func (provider *provider) GetFeatureFlags(ctx context.Context, organizationID valuer.UUID) ([]*licensetypes.Feature, error) {
|
||||||
func (provider *provider) CheckFeature(ctx context.Context, key string) error {
|
license, err := provider.GetActive(ctx, organizationID)
|
||||||
feature, err := provider.store.GetFeature(ctx, key)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if feature.Active {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return errors.Newf(errors.TypeUnsupported, licensing.ErrCodeFeatureUnavailable, "feature unavailable: %s", key)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (provider *provider) GetFeatureFlag(ctx context.Context, key string) (*featuretypes.GettableFeature, error) {
|
|
||||||
featureStatus, err := provider.store.GetFeature(ctx, key)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &featuretypes.GettableFeature{
|
|
||||||
Name: featureStatus.Name,
|
|
||||||
Active: featureStatus.Active,
|
|
||||||
Usage: int64(featureStatus.Usage),
|
|
||||||
UsageLimit: int64(featureStatus.UsageLimit),
|
|
||||||
Route: featureStatus.Route,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (provider *provider) GetFeatureFlags(ctx context.Context) ([]*featuretypes.GettableFeature, error) {
|
|
||||||
storableFeatures, err := provider.store.GetAllFeatures(ctx)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if errors.Ast(err, errors.TypeNotFound) {
|
||||||
|
return licensetypes.BasicPlan, nil
|
||||||
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
gettableFeatures := make([]*featuretypes.GettableFeature, len(storableFeatures))
|
return license.Features, nil
|
||||||
for idx, gettableFeature := range storableFeatures {
|
|
||||||
gettableFeatures[idx] = &featuretypes.GettableFeature{
|
|
||||||
Name: gettableFeature.Name,
|
|
||||||
Active: gettableFeature.Active,
|
|
||||||
Usage: int64(gettableFeature.Usage),
|
|
||||||
UsageLimit: int64(gettableFeature.UsageLimit),
|
|
||||||
Route: gettableFeature.Route,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if constants.IsDotMetricsEnabled {
|
|
||||||
gettableFeatures = append(gettableFeatures, &featuretypes.GettableFeature{
|
|
||||||
Name: featuretypes.DotMetricsEnabled,
|
|
||||||
Active: true,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return gettableFeatures, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (provider *provider) InitFeatures(ctx context.Context, features []*featuretypes.GettableFeature) error {
|
|
||||||
featureStatus := make([]*featuretypes.StorableFeature, len(features))
|
|
||||||
for i, f := range features {
|
|
||||||
featureStatus[i] = &featuretypes.StorableFeature{
|
|
||||||
Name: f.Name,
|
|
||||||
Active: f.Active,
|
|
||||||
Usage: int(f.Usage),
|
|
||||||
UsageLimit: int(f.UsageLimit),
|
|
||||||
Route: f.Route,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return provider.store.InitFeatures(ctx, featureStatus)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (provider *provider) UpdateFeatureFlag(ctx context.Context, feature *featuretypes.GettableFeature) error {
|
|
||||||
return provider.store.UpdateFeature(ctx, &featuretypes.StorableFeature{
|
|
||||||
Name: feature.Name,
|
|
||||||
Active: feature.Active,
|
|
||||||
Usage: int(feature.Usage),
|
|
||||||
UsageLimit: int(feature.UsageLimit),
|
|
||||||
Route: feature.Route,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,6 @@ import (
|
|||||||
|
|
||||||
"github.com/SigNoz/signoz/pkg/errors"
|
"github.com/SigNoz/signoz/pkg/errors"
|
||||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||||
"github.com/SigNoz/signoz/pkg/types/featuretypes"
|
|
||||||
"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"
|
||||||
)
|
)
|
||||||
@ -80,81 +79,3 @@ func (store *store) Update(ctx context.Context, organizationID valuer.UUID, stor
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (store *store) CreateFeature(ctx context.Context, storableFeature *featuretypes.StorableFeature) error {
|
|
||||||
_, err := store.
|
|
||||||
sqlstore.
|
|
||||||
BunDB().
|
|
||||||
NewInsert().
|
|
||||||
Model(storableFeature).
|
|
||||||
Exec(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return store.sqlstore.WrapAlreadyExistsErrf(err, errors.CodeAlreadyExists, "feature with name:%s already exists", storableFeature.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (store *store) GetFeature(ctx context.Context, key string) (*featuretypes.StorableFeature, error) {
|
|
||||||
storableFeature := new(featuretypes.StorableFeature)
|
|
||||||
err := store.
|
|
||||||
sqlstore.
|
|
||||||
BunDB().
|
|
||||||
NewSelect().
|
|
||||||
Model(storableFeature).
|
|
||||||
Where("name = ?", key).
|
|
||||||
Scan(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, store.sqlstore.WrapNotFoundErrf(err, errors.CodeNotFound, "feature with name:%s does not exist", key)
|
|
||||||
}
|
|
||||||
|
|
||||||
return storableFeature, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (store *store) GetAllFeatures(ctx context.Context) ([]*featuretypes.StorableFeature, error) {
|
|
||||||
storableFeatures := make([]*featuretypes.StorableFeature, 0)
|
|
||||||
err := store.
|
|
||||||
sqlstore.
|
|
||||||
BunDB().
|
|
||||||
NewSelect().
|
|
||||||
Model(&storableFeatures).
|
|
||||||
Scan(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, store.sqlstore.WrapNotFoundErrf(err, errors.CodeNotFound, "features do not exist")
|
|
||||||
}
|
|
||||||
|
|
||||||
return storableFeatures, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (store *store) InitFeatures(ctx context.Context, storableFeatures []*featuretypes.StorableFeature) error {
|
|
||||||
_, err := store.
|
|
||||||
sqlstore.
|
|
||||||
BunDB().
|
|
||||||
NewInsert().
|
|
||||||
Model(&storableFeatures).
|
|
||||||
On("CONFLICT (name) DO UPDATE").
|
|
||||||
Set("active = EXCLUDED.active").
|
|
||||||
Set("usage = EXCLUDED.usage").
|
|
||||||
Set("usage_limit = EXCLUDED.usage_limit").
|
|
||||||
Set("route = EXCLUDED.route").
|
|
||||||
Exec(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "unable to initialise features")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (store *store) UpdateFeature(ctx context.Context, storableFeature *featuretypes.StorableFeature) error {
|
|
||||||
_, err := store.
|
|
||||||
sqlstore.
|
|
||||||
BunDB().
|
|
||||||
NewUpdate().
|
|
||||||
Model(storableFeature).
|
|
||||||
Exec(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "unable to update feature with key: %s", storableFeature.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httputil"
|
"net/http/httputil"
|
||||||
"time"
|
"time"
|
||||||
@ -86,17 +85,12 @@ func (ah *APIHandler) Gateway() *httputil.ReverseProxy {
|
|||||||
return ah.opts.Gateway
|
return ah.opts.Gateway
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ah *APIHandler) CheckFeature(ctx context.Context, key string) bool {
|
|
||||||
err := ah.Signoz.Licensing.CheckFeature(ctx, key)
|
|
||||||
return err == nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RegisterRoutes registers routes for this handler on the given router
|
// RegisterRoutes registers routes for this handler on the given router
|
||||||
func (ah *APIHandler) RegisterRoutes(router *mux.Router, am *middleware.AuthZ) {
|
func (ah *APIHandler) RegisterRoutes(router *mux.Router, am *middleware.AuthZ) {
|
||||||
// note: add ee override methods first
|
// note: add ee override methods first
|
||||||
|
|
||||||
// routes available only in ee version
|
// routes available only in ee version
|
||||||
router.HandleFunc("/api/v1/featureFlags", am.OpenAccess(ah.getFeatureFlags)).Methods(http.MethodGet)
|
router.HandleFunc("/api/v1/features", am.ViewAccess(ah.getFeatureFlags)).Methods(http.MethodGet)
|
||||||
|
|
||||||
// paid plans specific routes
|
// paid plans specific routes
|
||||||
router.HandleFunc("/api/v1/complete/saml", am.OpenAccess(ah.receiveSAML)).Methods(http.MethodPost)
|
router.HandleFunc("/api/v1/complete/saml", am.OpenAccess(ah.receiveSAML)).Methods(http.MethodPost)
|
||||||
|
@ -12,7 +12,7 @@ import (
|
|||||||
pkgError "github.com/SigNoz/signoz/pkg/errors"
|
pkgError "github.com/SigNoz/signoz/pkg/errors"
|
||||||
"github.com/SigNoz/signoz/pkg/http/render"
|
"github.com/SigNoz/signoz/pkg/http/render"
|
||||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||||
"github.com/SigNoz/signoz/pkg/types/featuretypes"
|
"github.com/SigNoz/signoz/pkg/types/licensetypes"
|
||||||
"github.com/SigNoz/signoz/pkg/valuer"
|
"github.com/SigNoz/signoz/pkg/valuer"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
@ -31,7 +31,7 @@ func (ah *APIHandler) getFeatureFlags(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
featureSet, err := ah.Signoz.Licensing.GetFeatureFlags(r.Context())
|
featureSet, err := ah.Signoz.Licensing.GetFeatureFlags(r.Context(), orgID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ah.HandleError(w, err, http.StatusInternalServerError)
|
ah.HandleError(w, err, http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
@ -61,7 +61,15 @@ func (ah *APIHandler) getFeatureFlags(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
if ah.opts.PreferSpanMetrics {
|
if ah.opts.PreferSpanMetrics {
|
||||||
for idx, feature := range featureSet {
|
for idx, feature := range featureSet {
|
||||||
if feature.Name == featuretypes.UseSpanMetrics {
|
if feature.Name == licensetypes.UseSpanMetrics {
|
||||||
|
featureSet[idx].Active = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if constants.IsDotMetricsEnabled {
|
||||||
|
for idx, feature := range featureSet {
|
||||||
|
if feature.Name == licensetypes.DotMetricsEnabled {
|
||||||
featureSet[idx].Active = true
|
featureSet[idx].Active = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -72,7 +80,7 @@ func (ah *APIHandler) getFeatureFlags(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
// fetchZeusFeatures makes an HTTP GET request to the /zeusFeatures endpoint
|
// fetchZeusFeatures makes an HTTP GET request to the /zeusFeatures endpoint
|
||||||
// and returns the FeatureSet.
|
// and returns the FeatureSet.
|
||||||
func fetchZeusFeatures(url, licenseKey string) ([]*featuretypes.GettableFeature, error) {
|
func fetchZeusFeatures(url, licenseKey string) ([]*licensetypes.Feature, error) {
|
||||||
// Check if the URL is empty
|
// Check if the URL is empty
|
||||||
if url == "" {
|
if url == "" {
|
||||||
return nil, fmt.Errorf("url is empty")
|
return nil, fmt.Errorf("url is empty")
|
||||||
@ -131,28 +139,28 @@ func fetchZeusFeatures(url, licenseKey string) ([]*featuretypes.GettableFeature,
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ZeusFeaturesResponse struct {
|
type ZeusFeaturesResponse struct {
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
Data []*featuretypes.GettableFeature `json:"data"`
|
Data []*licensetypes.Feature `json:"data"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// MergeFeatureSets merges two FeatureSet arrays with precedence to zeusFeatures.
|
// MergeFeatureSets merges two FeatureSet arrays with precedence to zeusFeatures.
|
||||||
func MergeFeatureSets(zeusFeatures, internalFeatures []*featuretypes.GettableFeature) []*featuretypes.GettableFeature {
|
func MergeFeatureSets(zeusFeatures, internalFeatures []*licensetypes.Feature) []*licensetypes.Feature {
|
||||||
// Create a map to store the merged features
|
// Create a map to store the merged features
|
||||||
featureMap := make(map[string]*featuretypes.GettableFeature)
|
featureMap := make(map[string]*licensetypes.Feature)
|
||||||
|
|
||||||
// Add all features from the otherFeatures set to the map
|
// Add all features from the otherFeatures set to the map
|
||||||
for _, feature := range internalFeatures {
|
for _, feature := range internalFeatures {
|
||||||
featureMap[feature.Name] = feature
|
featureMap[feature.Name.StringValue()] = feature
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add all features from the zeusFeatures set to the map
|
// Add all features from the zeusFeatures set to the map
|
||||||
// If a feature already exists (i.e., same name), the zeusFeature will overwrite it
|
// If a feature already exists (i.e., same name), the zeusFeature will overwrite it
|
||||||
for _, feature := range zeusFeatures {
|
for _, feature := range zeusFeatures {
|
||||||
featureMap[feature.Name] = feature
|
featureMap[feature.Name.StringValue()] = feature
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert the map back to a FeatureSet slice
|
// Convert the map back to a FeatureSet slice
|
||||||
var mergedFeatures []*featuretypes.GettableFeature
|
var mergedFeatures []*licensetypes.Feature
|
||||||
for _, feature := range featureMap {
|
for _, feature := range featureMap {
|
||||||
mergedFeatures = append(mergedFeatures, feature)
|
mergedFeatures = append(mergedFeatures, feature)
|
||||||
}
|
}
|
||||||
|
@ -3,78 +3,79 @@ package api
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/SigNoz/signoz/pkg/types/featuretypes"
|
"github.com/SigNoz/signoz/pkg/types/licensetypes"
|
||||||
|
"github.com/SigNoz/signoz/pkg/valuer"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMergeFeatureSets(t *testing.T) {
|
func TestMergeFeatureSets(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
zeusFeatures []*featuretypes.GettableFeature
|
zeusFeatures []*licensetypes.Feature
|
||||||
internalFeatures []*featuretypes.GettableFeature
|
internalFeatures []*licensetypes.Feature
|
||||||
expected []*featuretypes.GettableFeature
|
expected []*licensetypes.Feature
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "empty zeusFeatures and internalFeatures",
|
name: "empty zeusFeatures and internalFeatures",
|
||||||
zeusFeatures: []*featuretypes.GettableFeature{},
|
zeusFeatures: []*licensetypes.Feature{},
|
||||||
internalFeatures: []*featuretypes.GettableFeature{},
|
internalFeatures: []*licensetypes.Feature{},
|
||||||
expected: []*featuretypes.GettableFeature{},
|
expected: []*licensetypes.Feature{},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "non-empty zeusFeatures and empty internalFeatures",
|
name: "non-empty zeusFeatures and empty internalFeatures",
|
||||||
zeusFeatures: []*featuretypes.GettableFeature{
|
zeusFeatures: []*licensetypes.Feature{
|
||||||
{Name: "Feature1", Active: true},
|
{Name: valuer.NewString("Feature1"), Active: true},
|
||||||
{Name: "Feature2", Active: false},
|
{Name: valuer.NewString("Feature2"), Active: false},
|
||||||
},
|
},
|
||||||
internalFeatures: []*featuretypes.GettableFeature{},
|
internalFeatures: []*licensetypes.Feature{},
|
||||||
expected: []*featuretypes.GettableFeature{
|
expected: []*licensetypes.Feature{
|
||||||
{Name: "Feature1", Active: true},
|
{Name: valuer.NewString("Feature1"), Active: true},
|
||||||
{Name: "Feature2", Active: false},
|
{Name: valuer.NewString("Feature2"), Active: false},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "empty zeusFeatures and non-empty internalFeatures",
|
name: "empty zeusFeatures and non-empty internalFeatures",
|
||||||
zeusFeatures: []*featuretypes.GettableFeature{},
|
zeusFeatures: []*licensetypes.Feature{},
|
||||||
internalFeatures: []*featuretypes.GettableFeature{
|
internalFeatures: []*licensetypes.Feature{
|
||||||
{Name: "Feature1", Active: true},
|
{Name: valuer.NewString("Feature1"), Active: true},
|
||||||
{Name: "Feature2", Active: false},
|
{Name: valuer.NewString("Feature2"), Active: false},
|
||||||
},
|
},
|
||||||
expected: []*featuretypes.GettableFeature{
|
expected: []*licensetypes.Feature{
|
||||||
{Name: "Feature1", Active: true},
|
{Name: valuer.NewString("Feature1"), Active: true},
|
||||||
{Name: "Feature2", Active: false},
|
{Name: valuer.NewString("Feature2"), Active: false},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "non-empty zeusFeatures and non-empty internalFeatures with no conflicts",
|
name: "non-empty zeusFeatures and non-empty internalFeatures with no conflicts",
|
||||||
zeusFeatures: []*featuretypes.GettableFeature{
|
zeusFeatures: []*licensetypes.Feature{
|
||||||
{Name: "Feature1", Active: true},
|
{Name: valuer.NewString("Feature1"), Active: true},
|
||||||
{Name: "Feature3", Active: false},
|
{Name: valuer.NewString("Feature3"), Active: false},
|
||||||
},
|
},
|
||||||
internalFeatures: []*featuretypes.GettableFeature{
|
internalFeatures: []*licensetypes.Feature{
|
||||||
{Name: "Feature2", Active: true},
|
{Name: valuer.NewString("Feature2"), Active: true},
|
||||||
{Name: "Feature4", Active: false},
|
{Name: valuer.NewString("Feature4"), Active: false},
|
||||||
},
|
},
|
||||||
expected: []*featuretypes.GettableFeature{
|
expected: []*licensetypes.Feature{
|
||||||
{Name: "Feature1", Active: true},
|
{Name: valuer.NewString("Feature1"), Active: true},
|
||||||
{Name: "Feature2", Active: true},
|
{Name: valuer.NewString("Feature2"), Active: true},
|
||||||
{Name: "Feature3", Active: false},
|
{Name: valuer.NewString("Feature3"), Active: false},
|
||||||
{Name: "Feature4", Active: false},
|
{Name: valuer.NewString("Feature4"), Active: false},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "non-empty zeusFeatures and non-empty internalFeatures with conflicts",
|
name: "non-empty zeusFeatures and non-empty internalFeatures with conflicts",
|
||||||
zeusFeatures: []*featuretypes.GettableFeature{
|
zeusFeatures: []*licensetypes.Feature{
|
||||||
{Name: "Feature1", Active: true},
|
{Name: valuer.NewString("Feature1"), Active: true},
|
||||||
{Name: "Feature2", Active: false},
|
{Name: valuer.NewString("Feature2"), Active: false},
|
||||||
},
|
},
|
||||||
internalFeatures: []*featuretypes.GettableFeature{
|
internalFeatures: []*licensetypes.Feature{
|
||||||
{Name: "Feature1", Active: false},
|
{Name: valuer.NewString("Feature1"), Active: false},
|
||||||
{Name: "Feature3", Active: true},
|
{Name: valuer.NewString("Feature3"), Active: true},
|
||||||
},
|
},
|
||||||
expected: []*featuretypes.GettableFeature{
|
expected: []*licensetypes.Feature{
|
||||||
{Name: "Feature1", Active: true},
|
{Name: valuer.NewString("Feature1"), Active: true},
|
||||||
{Name: "Feature2", Active: false},
|
{Name: valuer.NewString("Feature2"), Active: false},
|
||||||
{Name: "Feature3", Active: true},
|
{Name: valuer.NewString("Feature3"), Active: true},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -1,10 +0,0 @@
|
|||||||
import axios from 'api';
|
|
||||||
import { ApiResponse } from 'types/api';
|
|
||||||
import { FeatureFlagProps } from 'types/api/features/getFeaturesFlags';
|
|
||||||
|
|
||||||
const getFeaturesFlags = (): Promise<FeatureFlagProps[]> =>
|
|
||||||
axios
|
|
||||||
.get<ApiResponse<FeatureFlagProps[]>>(`/featureFlags`)
|
|
||||||
.then((response) => response.data.data);
|
|
||||||
|
|
||||||
export default getFeaturesFlags;
|
|
23
frontend/src/api/v1/features/list.ts
Normal file
23
frontend/src/api/v1/features/list.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import axios from 'api';
|
||||||
|
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
|
||||||
|
import { AxiosError } from 'axios';
|
||||||
|
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
||||||
|
import {
|
||||||
|
FeatureFlagProps,
|
||||||
|
PayloadProps,
|
||||||
|
} from 'types/api/features/getFeaturesFlags';
|
||||||
|
|
||||||
|
const list = async (): Promise<SuccessResponseV2<FeatureFlagProps[]>> => {
|
||||||
|
try {
|
||||||
|
const response = await axios.get<PayloadProps>(`/features`);
|
||||||
|
|
||||||
|
return {
|
||||||
|
httpStatusCode: response.status,
|
||||||
|
data: response.data.data,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default list;
|
@ -1,14 +1,12 @@
|
|||||||
// keep this consistent with backend constants.go
|
// keep this consistent with backend plan.go
|
||||||
export enum FeatureKeys {
|
export enum FeatureKeys {
|
||||||
SSO = 'SSO',
|
SSO = 'sso',
|
||||||
USE_SPAN_METRICS = 'USE_SPAN_METRICS',
|
USE_SPAN_METRICS = 'use_span_metrics',
|
||||||
ONBOARDING = 'ONBOARDING',
|
ONBOARDING = 'onboarding',
|
||||||
CHAT_SUPPORT = 'CHAT_SUPPORT',
|
CHAT_SUPPORT = 'chat_support',
|
||||||
GATEWAY = 'GATEWAY',
|
GATEWAY = 'gateway',
|
||||||
PREMIUM_SUPPORT = 'PREMIUM_SUPPORT',
|
PREMIUM_SUPPORT = 'premium_support',
|
||||||
ANOMALY_DETECTION = 'ANOMALY_DETECTION',
|
ANOMALY_DETECTION = 'anomaly_detection',
|
||||||
ONBOARDING_V3 = 'ONBOARDING_V3',
|
ONBOARDING_V3 = 'onboarding_v3',
|
||||||
THIRD_PARTY_API = 'THIRD_PARTY_API',
|
DOT_METRICS_ENABLED = 'dot_metrics_enabled',
|
||||||
TRACE_FUNNELS = 'TRACE_FUNNELS',
|
|
||||||
DOT_METRICS_ENABLED = 'DOT_METRICS_ENABLED',
|
|
||||||
}
|
}
|
||||||
|
@ -316,7 +316,6 @@ describe('Create Alert Channel (Normal User)', () => {
|
|||||||
expect(screen.getByText('Microsoft Teams')).toBeInTheDocument();
|
expect(screen.getByText('Microsoft Teams')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO[vikrantgupta25]: check with Shaheer
|
|
||||||
it.skip('Should check if the upgrade plan message is shown', () => {
|
it.skip('Should check if the upgrade plan message is shown', () => {
|
||||||
expect(screen.getByText('Upgrade to a Paid Plan')).toBeInTheDocument();
|
expect(screen.getByText('Upgrade to a Paid Plan')).toBeInTheDocument();
|
||||||
expect(
|
expect(
|
||||||
|
@ -36,7 +36,6 @@ function QuerySection({
|
|||||||
const { t } = useTranslation('alerts');
|
const { t } = useTranslation('alerts');
|
||||||
const [currentTab, setCurrentTab] = useState(queryCategory);
|
const [currentTab, setCurrentTab] = useState(queryCategory);
|
||||||
|
|
||||||
// TODO[vikrantgupta25] : check if this is still required ??
|
|
||||||
const handleQueryCategoryChange = (queryType: string): void => {
|
const handleQueryCategoryChange = (queryType: string): void => {
|
||||||
setQueryCategory(queryType as EQueryType);
|
setQueryCategory(queryType as EQueryType);
|
||||||
setCurrentTab(queryType as EQueryType);
|
setCurrentTab(queryType as EQueryType);
|
||||||
|
@ -160,8 +160,6 @@ function GridCardGraph({
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO [vikrantgupta25] remove this useEffect with refactor as this is prone to race condition
|
|
||||||
// this is added to tackle the case of async communication between VariableItem.tsx and GridCard.tsx
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (variablesToGetUpdated.length > 0) {
|
if (variablesToGetUpdated.length > 0) {
|
||||||
queryClient.cancelQueries([
|
queryClient.cancelQueries([
|
||||||
|
@ -41,7 +41,6 @@ const { Search } = Input;
|
|||||||
function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
|
function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
|
||||||
const { t } = useTranslation('common');
|
const { t } = useTranslation('common');
|
||||||
const { user } = useAppContext();
|
const { user } = useAppContext();
|
||||||
// TODO[vikrantgupta25]: check with sagar on cleanup
|
|
||||||
const [addNewAlert, action] = useComponentPermission(
|
const [addNewAlert, action] = useComponentPermission(
|
||||||
['add_new_alert', 'action'],
|
['add_new_alert', 'action'],
|
||||||
user.role,
|
user.role,
|
||||||
|
@ -1,18 +1,29 @@
|
|||||||
import getFeaturesFlags from 'api/features/getFeatureFlags';
|
import list from 'api/v1/features/list';
|
||||||
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
||||||
import { useQuery, UseQueryResult } from 'react-query';
|
import { useQuery, UseQueryResult } from 'react-query';
|
||||||
|
import { SuccessResponseV2 } from 'types/api';
|
||||||
|
import APIError from 'types/api/error';
|
||||||
import { FeatureFlagProps } from 'types/api/features/getFeaturesFlags';
|
import { FeatureFlagProps } from 'types/api/features/getFeaturesFlags';
|
||||||
|
|
||||||
const useGetFeatureFlag = (
|
export interface Props {
|
||||||
|
onSuccessHandler: (routes: FeatureFlagProps[]) => void;
|
||||||
|
isLoggedIn: boolean;
|
||||||
|
}
|
||||||
|
type UseGetFeatureFlag = UseQueryResult<
|
||||||
|
SuccessResponseV2<FeatureFlagProps[]>,
|
||||||
|
APIError
|
||||||
|
>;
|
||||||
|
|
||||||
|
export const useGetFeatureFlag = (
|
||||||
onSuccessHandler: (routes: FeatureFlagProps[]) => void,
|
onSuccessHandler: (routes: FeatureFlagProps[]) => void,
|
||||||
isLoggedIn: boolean,
|
isLoggedIn: boolean,
|
||||||
): UseQueryResult<FeatureFlagProps[], unknown> =>
|
): UseGetFeatureFlag =>
|
||||||
useQuery<FeatureFlagProps[]>({
|
useQuery<SuccessResponseV2<FeatureFlagProps[]>, APIError>({
|
||||||
queryFn: getFeaturesFlags,
|
|
||||||
queryKey: [REACT_QUERY_KEY.GET_FEATURES_FLAGS],
|
queryKey: [REACT_QUERY_KEY.GET_FEATURES_FLAGS],
|
||||||
onSuccess: onSuccessHandler,
|
queryFn: () => list(),
|
||||||
|
onSuccess: (data) => {
|
||||||
|
onSuccessHandler(data.data);
|
||||||
|
},
|
||||||
retryOnMount: false,
|
retryOnMount: false,
|
||||||
enabled: !!isLoggedIn,
|
enabled: !!isLoggedIn,
|
||||||
});
|
});
|
||||||
|
|
||||||
export default useGetFeatureFlag;
|
|
||||||
|
@ -145,14 +145,6 @@ describe('Logs Explorer Tests', () => {
|
|||||||
await waitFor(() =>
|
await waitFor(() =>
|
||||||
expect(queryByTestId('logs-list-virtuoso')).toBeInTheDocument(),
|
expect(queryByTestId('logs-list-virtuoso')).toBeInTheDocument(),
|
||||||
);
|
);
|
||||||
|
|
||||||
// check for data being present in the UI
|
|
||||||
// todo[@vikrantgupta25]: skipping this for now as the formatting matching is not picking up in the CI will debug later.
|
|
||||||
// expect(
|
|
||||||
// queryByText(
|
|
||||||
// `2024-02-16 02:50:22.000 | 2024-02-15T21:20:22.035Z INFO frontend Dispatch successful {"service": "frontend", "trace_id": "span_id", "span_id": "span_id", "driver": "driver", "eta": "2m0s"}`,
|
|
||||||
// ),
|
|
||||||
// ).toBeInTheDocument();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Multiple Current Queries', async () => {
|
test('Multiple Current Queries', async () => {
|
||||||
|
@ -5,7 +5,7 @@ import getUserVersion from 'api/v1/version/getVersion';
|
|||||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import useActiveLicenseV3 from 'hooks/useActiveLicenseV3/useActiveLicenseV3';
|
import useActiveLicenseV3 from 'hooks/useActiveLicenseV3/useActiveLicenseV3';
|
||||||
import useGetFeatureFlag from 'hooks/useGetFeatureFlag';
|
import { useGetFeatureFlag } from 'hooks/useGetFeatureFlag';
|
||||||
import { useGlobalEventListener } from 'hooks/useGlobalEventListener';
|
import { useGlobalEventListener } from 'hooks/useGlobalEventListener';
|
||||||
import useGetUser from 'hooks/user/useGetUser';
|
import useGetUser from 'hooks/user/useGetUser';
|
||||||
import {
|
import {
|
||||||
|
@ -8,4 +8,7 @@ export interface FeatureFlagProps {
|
|||||||
route: string;
|
route: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PayloadProps = FeatureFlagProps[];
|
export interface PayloadProps {
|
||||||
|
data: FeatureFlagProps[];
|
||||||
|
status: string;
|
||||||
|
}
|
||||||
|
@ -25,12 +25,6 @@ export enum LicensePlatform {
|
|||||||
CLOUD = 'CLOUD',
|
CLOUD = 'CLOUD',
|
||||||
}
|
}
|
||||||
|
|
||||||
// Legacy
|
|
||||||
export const LicensePlanKey = {
|
|
||||||
ENTERPRISE: 'ENTERPRISE',
|
|
||||||
BASIC: 'BASIC',
|
|
||||||
};
|
|
||||||
|
|
||||||
export type LicenseEventQueueResModel = {
|
export type LicenseEventQueueResModel = {
|
||||||
event: LicenseEvent;
|
event: LicenseEvent;
|
||||||
status: string;
|
status: string;
|
||||||
|
@ -6,7 +6,6 @@ import (
|
|||||||
|
|
||||||
"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/types/featuretypes"
|
|
||||||
"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"
|
||||||
)
|
)
|
||||||
@ -31,18 +30,8 @@ type Licensing interface {
|
|||||||
Checkout(ctx context.Context, organizationID valuer.UUID, postableSubscription *licensetypes.PostableSubscription) (*licensetypes.GettableSubscription, error)
|
Checkout(ctx context.Context, organizationID valuer.UUID, postableSubscription *licensetypes.PostableSubscription) (*licensetypes.GettableSubscription, error)
|
||||||
// Portal creates a portal session via upstream server and return the redirection link
|
// Portal creates a portal session via upstream server and return the redirection link
|
||||||
Portal(ctx context.Context, organizationID valuer.UUID, postableSubscription *licensetypes.PostableSubscription) (*licensetypes.GettableSubscription, error)
|
Portal(ctx context.Context, organizationID valuer.UUID, postableSubscription *licensetypes.PostableSubscription) (*licensetypes.GettableSubscription, error)
|
||||||
|
|
||||||
// feature surrogate
|
|
||||||
// CheckFeature checks if the feature is active or not
|
|
||||||
CheckFeature(ctx context.Context, key string) error
|
|
||||||
// GetFeatureFlags fetches all the defined feature flags
|
// GetFeatureFlags fetches all the defined feature flags
|
||||||
GetFeatureFlag(ctx context.Context, key string) (*featuretypes.GettableFeature, error)
|
GetFeatureFlags(ctx context.Context, organizationID valuer.UUID) ([]*licensetypes.Feature, error)
|
||||||
// GetFeatureFlags fetches all the defined feature flags
|
|
||||||
GetFeatureFlags(ctx context.Context) ([]*featuretypes.GettableFeature, error)
|
|
||||||
// InitFeatures initialises the feature flags
|
|
||||||
InitFeatures(ctx context.Context, features []*featuretypes.GettableFeature) error
|
|
||||||
// UpdateFeatureFlag updates the feature flag
|
|
||||||
UpdateFeatureFlag(ctx context.Context, feature *featuretypes.GettableFeature) error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type API interface {
|
type API interface {
|
||||||
|
@ -6,7 +6,6 @@ import (
|
|||||||
"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/types/featuretypes"
|
|
||||||
"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"
|
||||||
)
|
)
|
||||||
@ -60,40 +59,6 @@ func (provider *noopLicensing) GetActive(ctx context.Context, organizationID val
|
|||||||
return nil, errors.New(errors.TypeUnsupported, licensing.ErrCodeUnsupported, "fetching active license is not supported")
|
return nil, errors.New(errors.TypeUnsupported, licensing.ErrCodeUnsupported, "fetching active license is not supported")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (provider *noopLicensing) CheckFeature(ctx context.Context, key string) error {
|
func (provider *noopLicensing) GetFeatureFlags(_ context.Context, _ valuer.UUID) ([]*licensetypes.Feature, error) {
|
||||||
feature, err := provider.GetFeatureFlag(ctx, key)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if feature.Active {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return errors.Newf(errors.TypeNotFound, licensing.ErrCodeFeatureUnavailable, "feature unavailable: %s", key)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (provider *noopLicensing) GetFeatureFlag(ctx context.Context, key string) (*featuretypes.GettableFeature, error) {
|
|
||||||
features, err := provider.GetFeatureFlags(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for _, feature := range features {
|
|
||||||
if feature.Name == key {
|
|
||||||
return feature, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, errors.Newf(errors.TypeNotFound, errors.CodeNotFound, "no feature available with given key: %s", key)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (provider *noopLicensing) GetFeatureFlags(ctx context.Context) ([]*featuretypes.GettableFeature, error) {
|
|
||||||
return licensetypes.DefaultFeatureSet, nil
|
return licensetypes.DefaultFeatureSet, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (provider *noopLicensing) InitFeatures(ctx context.Context, features []*featuretypes.GettableFeature) error {
|
|
||||||
return errors.New(errors.TypeUnsupported, licensing.ErrCodeUnsupported, "init features is not supported")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (provider *noopLicensing) UpdateFeatureFlag(ctx context.Context, feature *featuretypes.GettableFeature) error {
|
|
||||||
return errors.New(errors.TypeUnsupported, licensing.ErrCodeUnsupported, "updating feature flag is not supported")
|
|
||||||
}
|
|
||||||
|
@ -62,7 +62,7 @@ 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/dashboardtypes"
|
"github.com/SigNoz/signoz/pkg/types/dashboardtypes"
|
||||||
"github.com/SigNoz/signoz/pkg/types/featuretypes"
|
"github.com/SigNoz/signoz/pkg/types/licensetypes"
|
||||||
"github.com/SigNoz/signoz/pkg/types/pipelinetypes"
|
"github.com/SigNoz/signoz/pkg/types/pipelinetypes"
|
||||||
ruletypes "github.com/SigNoz/signoz/pkg/types/ruletypes"
|
ruletypes "github.com/SigNoz/signoz/pkg/types/ruletypes"
|
||||||
|
|
||||||
@ -550,7 +550,7 @@ func (aH *APIHandler) RegisterRoutes(router *mux.Router, am *middleware.AuthZ) {
|
|||||||
router.HandleFunc("/api/v2/traces/waterfall/{traceId}", am.ViewAccess(aH.GetWaterfallSpansForTraceWithMetadata)).Methods(http.MethodPost)
|
router.HandleFunc("/api/v2/traces/waterfall/{traceId}", am.ViewAccess(aH.GetWaterfallSpansForTraceWithMetadata)).Methods(http.MethodPost)
|
||||||
|
|
||||||
router.HandleFunc("/api/v1/version", am.OpenAccess(aH.getVersion)).Methods(http.MethodGet)
|
router.HandleFunc("/api/v1/version", am.OpenAccess(aH.getVersion)).Methods(http.MethodGet)
|
||||||
router.HandleFunc("/api/v1/featureFlags", am.OpenAccess(aH.getFeatureFlags)).Methods(http.MethodGet)
|
router.HandleFunc("/api/v1/features", am.ViewAccess(aH.getFeatureFlags)).Methods(http.MethodGet)
|
||||||
router.HandleFunc("/api/v1/health", am.OpenAccess(aH.getHealth)).Methods(http.MethodGet)
|
router.HandleFunc("/api/v1/health", am.OpenAccess(aH.getHealth)).Methods(http.MethodGet)
|
||||||
|
|
||||||
router.HandleFunc("/api/v1/listErrors", am.ViewAccess(aH.listErrors)).Methods(http.MethodPost)
|
router.HandleFunc("/api/v1/listErrors", am.ViewAccess(aH.listErrors)).Methods(http.MethodPost)
|
||||||
@ -1931,32 +1931,29 @@ func (aH *APIHandler) getVersion(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (aH *APIHandler) getFeatureFlags(w http.ResponseWriter, r *http.Request) {
|
func (aH *APIHandler) getFeatureFlags(w http.ResponseWriter, r *http.Request) {
|
||||||
featureSet, err := aH.Signoz.Licensing.GetFeatureFlags(r.Context())
|
featureSet, err := aH.Signoz.Licensing.GetFeatureFlags(r.Context(), valuer.GenerateUUID())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
aH.HandleError(w, err, http.StatusInternalServerError)
|
aH.HandleError(w, err, http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if aH.preferSpanMetrics {
|
if aH.preferSpanMetrics {
|
||||||
for idx, feature := range featureSet {
|
for idx, feature := range featureSet {
|
||||||
if feature.Name == featuretypes.UseSpanMetrics {
|
if feature.Name == licensetypes.UseSpanMetrics {
|
||||||
featureSet[idx].Active = true
|
featureSet[idx].Active = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if constants.IsDotMetricsEnabled {
|
if constants.IsDotMetricsEnabled {
|
||||||
featureSet = append(featureSet, &featuretypes.GettableFeature{
|
for idx, feature := range featureSet {
|
||||||
Name: featuretypes.DotMetricsEnabled,
|
if feature.Name == licensetypes.DotMetricsEnabled {
|
||||||
Active: true,
|
featureSet[idx].Active = true
|
||||||
})
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
aH.Respond(w, featureSet)
|
aH.Respond(w, featureSet)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (aH *APIHandler) CheckFeature(ctx context.Context, key string) bool {
|
|
||||||
err := aH.Signoz.Licensing.CheckFeature(ctx, key)
|
|
||||||
return err == nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// getHealth is used to check the health of the service.
|
// getHealth is used to check the health of the service.
|
||||||
// 'live' query param can be used to check liveliness of
|
// 'live' query param can be used to check liveliness of
|
||||||
// the service by checking the database connection.
|
// the service by checking the database connection.
|
||||||
|
@ -9,7 +9,6 @@ import (
|
|||||||
|
|
||||||
"github.com/SigNoz/signoz/pkg/query-service/model"
|
"github.com/SigNoz/signoz/pkg/query-service/model"
|
||||||
v3 "github.com/SigNoz/signoz/pkg/query-service/model/v3"
|
v3 "github.com/SigNoz/signoz/pkg/query-service/model/v3"
|
||||||
"github.com/SigNoz/signoz/pkg/types/featuretypes"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -66,16 +65,6 @@ func UseMetricsPreAggregation() bool {
|
|||||||
|
|
||||||
var KafkaSpanEval = GetOrDefaultEnv("KAFKA_SPAN_EVAL", "false")
|
var KafkaSpanEval = GetOrDefaultEnv("KAFKA_SPAN_EVAL", "false")
|
||||||
|
|
||||||
var DEFAULT_FEATURE_SET = []*featuretypes.GettableFeature{
|
|
||||||
&featuretypes.GettableFeature{
|
|
||||||
Name: featuretypes.UseSpanMetrics,
|
|
||||||
Active: false,
|
|
||||||
Usage: 0,
|
|
||||||
UsageLimit: -1,
|
|
||||||
Route: "",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetEvalDelay() time.Duration {
|
func GetEvalDelay() time.Duration {
|
||||||
evalDelayStr := GetOrDefaultEnv("RULES_EVAL_DELAY", "2m")
|
evalDelayStr := GetOrDefaultEnv("RULES_EVAL_DELAY", "2m")
|
||||||
evalDelayDuration, err := time.ParseDuration(evalDelayStr)
|
evalDelayDuration, err := time.ParseDuration(evalDelayStr)
|
||||||
|
@ -90,6 +90,7 @@ func NewSQLMigrationProviderFactories(sqlstore sqlstore.SQLStore) factory.NamedM
|
|||||||
sqlmigration.NewAddKeyOrganizationFactory(sqlstore),
|
sqlmigration.NewAddKeyOrganizationFactory(sqlstore),
|
||||||
sqlmigration.NewAddTraceFunnelsFactory(sqlstore),
|
sqlmigration.NewAddTraceFunnelsFactory(sqlstore),
|
||||||
sqlmigration.NewUpdateDashboardFactory(sqlstore),
|
sqlmigration.NewUpdateDashboardFactory(sqlstore),
|
||||||
|
sqlmigration.NewDropFeatureSetFactory(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
58
pkg/sqlmigration/039_drop_feature_set.go
Normal file
58
pkg/sqlmigration/039_drop_feature_set.go
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
package sqlmigration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/SigNoz/signoz/pkg/factory"
|
||||||
|
"github.com/uptrace/bun"
|
||||||
|
"github.com/uptrace/bun/migrate"
|
||||||
|
)
|
||||||
|
|
||||||
|
type dropFeatureSet struct{}
|
||||||
|
|
||||||
|
func NewDropFeatureSetFactory() factory.ProviderFactory[SQLMigration, Config] {
|
||||||
|
return factory.NewProviderFactory(factory.MustNewName("drop_feature_set"), func(ctx context.Context, ps factory.ProviderSettings, c Config) (SQLMigration, error) {
|
||||||
|
return newDropFeatureSet(ctx, ps, c)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func newDropFeatureSet(_ context.Context, _ factory.ProviderSettings, _ Config) (SQLMigration, error) {
|
||||||
|
return &dropFeatureSet{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (migration *dropFeatureSet) Register(migrations *migrate.Migrations) error {
|
||||||
|
if err := migrations.Register(migration.Up, migration.Down); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (migration *dropFeatureSet) Up(ctx context.Context, db *bun.DB) error {
|
||||||
|
tx, err := db.BeginTx(ctx, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
_ = tx.Rollback()
|
||||||
|
}()
|
||||||
|
|
||||||
|
if _, err := tx.
|
||||||
|
NewDropTable().
|
||||||
|
IfExists().
|
||||||
|
Table("feature_status").
|
||||||
|
Exec(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tx.Commit(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (migration *dropFeatureSet) Down(context.Context, *bun.DB) error {
|
||||||
|
return nil
|
||||||
|
}
|
@ -1,29 +0,0 @@
|
|||||||
package featuretypes
|
|
||||||
|
|
||||||
import "github.com/uptrace/bun"
|
|
||||||
|
|
||||||
type FeatureSet []*GettableFeature
|
|
||||||
type GettableFeature struct {
|
|
||||||
Name string `db:"name" json:"name"`
|
|
||||||
Active bool `db:"active" json:"active"`
|
|
||||||
Usage int64 `db:"usage" json:"usage"`
|
|
||||||
UsageLimit int64 `db:"usage_limit" json:"usage_limit"`
|
|
||||||
Route string `db:"route" json:"route"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type StorableFeature struct {
|
|
||||||
bun.BaseModel `bun:"table:feature_status"`
|
|
||||||
|
|
||||||
Name string `bun:"name,pk,type:text" json:"name"`
|
|
||||||
Active bool `bun:"active" json:"active"`
|
|
||||||
Usage int `bun:"usage,default:0" json:"usage"`
|
|
||||||
UsageLimit int `bun:"usage_limit,default:0" json:"usage_limit"`
|
|
||||||
Route string `bun:"route,type:text" json:"route"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewStorableFeature() {}
|
|
||||||
|
|
||||||
const UseSpanMetrics = "USE_SPAN_METRICS"
|
|
||||||
const AnomalyDetection = "ANOMALY_DETECTION"
|
|
||||||
const TraceFunnels = "TRACE_FUNNELS"
|
|
||||||
const DotMetricsEnabled = "DOT_METRICS_ENABLED"
|
|
@ -9,7 +9,6 @@ import (
|
|||||||
|
|
||||||
"github.com/SigNoz/signoz/pkg/errors"
|
"github.com/SigNoz/signoz/pkg/errors"
|
||||||
"github.com/SigNoz/signoz/pkg/types"
|
"github.com/SigNoz/signoz/pkg/types"
|
||||||
"github.com/SigNoz/signoz/pkg/types/featuretypes"
|
|
||||||
"github.com/SigNoz/signoz/pkg/valuer"
|
"github.com/SigNoz/signoz/pkg/valuer"
|
||||||
"github.com/uptrace/bun"
|
"github.com/uptrace/bun"
|
||||||
)
|
)
|
||||||
@ -30,9 +29,9 @@ type License struct {
|
|||||||
ID valuer.UUID
|
ID valuer.UUID
|
||||||
Key string
|
Key string
|
||||||
Data map[string]interface{}
|
Data map[string]interface{}
|
||||||
PlanName string
|
PlanName valuer.String
|
||||||
Features []*featuretypes.GettableFeature
|
Features []*Feature
|
||||||
Status string
|
Status valuer.String
|
||||||
ValidFrom int64
|
ValidFrom int64
|
||||||
ValidUntil int64
|
ValidUntil int64
|
||||||
CreatedAt time.Time
|
CreatedAt time.Time
|
||||||
@ -124,7 +123,7 @@ func NewLicense(data []byte, organizationID valuer.UUID) (*License, error) {
|
|||||||
return nil, errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "failed to unmarshal license data")
|
return nil, errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "failed to unmarshal license data")
|
||||||
}
|
}
|
||||||
|
|
||||||
var features []*featuretypes.GettableFeature
|
var features []*Feature
|
||||||
|
|
||||||
// extract id from data
|
// extract id from data
|
||||||
licenseIDStr, err := extractKeyFromMapStringInterface[string](licenseData, "id")
|
licenseIDStr, err := extractKeyFromMapStringInterface[string](licenseData, "id")
|
||||||
@ -145,26 +144,28 @@ func NewLicense(data []byte, organizationID valuer.UUID) (*License, error) {
|
|||||||
delete(licenseData, "key")
|
delete(licenseData, "key")
|
||||||
|
|
||||||
// extract status from data
|
// extract status from data
|
||||||
status, err := extractKeyFromMapStringInterface[string](licenseData, "status")
|
statusStr, err := extractKeyFromMapStringInterface[string](licenseData, "status")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
status := valuer.NewString(statusStr)
|
||||||
|
|
||||||
planMap, err := extractKeyFromMapStringInterface[map[string]any](licenseData, "plan")
|
planMap, err := extractKeyFromMapStringInterface[map[string]any](licenseData, "plan")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
planName, err := extractKeyFromMapStringInterface[string](planMap, "name")
|
planNameStr, err := extractKeyFromMapStringInterface[string](planMap, "name")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
planName := valuer.NewString(planNameStr)
|
||||||
// if license status is invalid then default it to basic
|
// if license status is invalid then default it to basic
|
||||||
if status == LicenseStatusInvalid {
|
if status == LicenseStatusInvalid {
|
||||||
planName = PlanNameBasic
|
planName = PlanNameBasic
|
||||||
}
|
}
|
||||||
|
|
||||||
featuresFromZeus := make([]*featuretypes.GettableFeature, 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)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -232,28 +233,30 @@ func NewLicense(data []byte, organizationID valuer.UUID) (*License, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewLicenseFromStorableLicense(storableLicense *StorableLicense) (*License, error) {
|
func NewLicenseFromStorableLicense(storableLicense *StorableLicense) (*License, error) {
|
||||||
var features []*featuretypes.GettableFeature
|
var features []*Feature
|
||||||
// extract status from data
|
// extract status from data
|
||||||
status, err := extractKeyFromMapStringInterface[string](storableLicense.Data, "status")
|
statusStr, err := extractKeyFromMapStringInterface[string](storableLicense.Data, "status")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
status := valuer.NewString(statusStr)
|
||||||
|
|
||||||
planMap, err := extractKeyFromMapStringInterface[map[string]any](storableLicense.Data, "plan")
|
planMap, err := extractKeyFromMapStringInterface[map[string]any](storableLicense.Data, "plan")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
planName, err := extractKeyFromMapStringInterface[string](planMap, "name")
|
planNameStr, err := extractKeyFromMapStringInterface[string](planMap, "name")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
planName := valuer.NewString(planNameStr)
|
||||||
// if license status is invalid then default it to basic
|
// if license status is invalid then default it to basic
|
||||||
if status == LicenseStatusInvalid {
|
if status == LicenseStatusInvalid {
|
||||||
planName = PlanNameBasic
|
planName = PlanNameBasic
|
||||||
}
|
}
|
||||||
|
|
||||||
featuresFromZeus := make([]*featuretypes.GettableFeature, 0)
|
featuresFromZeus := make([]*Feature, 0)
|
||||||
if _features, ok := storableLicense.Data["features"]; ok {
|
if _features, ok := storableLicense.Data["features"]; ok {
|
||||||
featuresData, err := json.Marshal(_features)
|
featuresData, err := json.Marshal(_features)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -320,6 +323,10 @@ func NewLicenseFromStorableLicense(storableLicense *StorableLicense) (*License,
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (license *License) UpdateFeatures(features []*Feature) {
|
||||||
|
license.Features = features
|
||||||
|
}
|
||||||
|
|
||||||
func (license *License) Update(data []byte) error {
|
func (license *License) Update(data []byte) error {
|
||||||
updatedLicense, err := NewLicense(data, license.OrganizationID)
|
updatedLicense, err := NewLicense(data, license.OrganizationID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -373,11 +380,4 @@ type Store interface {
|
|||||||
Get(context.Context, valuer.UUID, valuer.UUID) (*StorableLicense, error)
|
Get(context.Context, valuer.UUID, valuer.UUID) (*StorableLicense, error)
|
||||||
GetAll(context.Context, valuer.UUID) ([]*StorableLicense, error)
|
GetAll(context.Context, valuer.UUID) ([]*StorableLicense, error)
|
||||||
Update(context.Context, valuer.UUID, *StorableLicense) error
|
Update(context.Context, valuer.UUID, *StorableLicense) error
|
||||||
|
|
||||||
// feature surrogate
|
|
||||||
InitFeatures(context.Context, []*featuretypes.StorableFeature) error
|
|
||||||
CreateFeature(context.Context, *featuretypes.StorableFeature) error
|
|
||||||
GetFeature(context.Context, string) (*featuretypes.StorableFeature, error)
|
|
||||||
GetAllFeatures(context.Context) ([]*featuretypes.StorableFeature, error)
|
|
||||||
UpdateFeature(context.Context, *featuretypes.StorableFeature) error
|
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,6 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/SigNoz/signoz/pkg/types/featuretypes"
|
|
||||||
"github.com/SigNoz/signoz/pkg/valuer"
|
"github.com/SigNoz/signoz/pkg/valuer"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@ -92,8 +91,8 @@ func TestNewLicenseV3(t *testing.T) {
|
|||||||
PlanName: PlanNameEnterprise,
|
PlanName: PlanNameEnterprise,
|
||||||
ValidFrom: 1730899309,
|
ValidFrom: 1730899309,
|
||||||
ValidUntil: -1,
|
ValidUntil: -1,
|
||||||
Status: "ACTIVE",
|
Status: valuer.NewString("ACTIVE"),
|
||||||
Features: make([]*featuretypes.GettableFeature, 0),
|
Features: make([]*Feature, 0),
|
||||||
OrganizationID: valuer.MustNewUUID("0196f794-ff30-7bee-a5f4-ef5ad315715e"),
|
OrganizationID: valuer.MustNewUUID("0196f794-ff30-7bee-a5f4-ef5ad315715e"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -116,8 +115,8 @@ func TestNewLicenseV3(t *testing.T) {
|
|||||||
PlanName: PlanNameBasic,
|
PlanName: PlanNameBasic,
|
||||||
ValidFrom: 1730899309,
|
ValidFrom: 1730899309,
|
||||||
ValidUntil: -1,
|
ValidUntil: -1,
|
||||||
Status: "INVALID",
|
Status: valuer.NewString("INVALID"),
|
||||||
Features: make([]*featuretypes.GettableFeature, 0),
|
Features: make([]*Feature, 0),
|
||||||
OrganizationID: valuer.MustNewUUID("0196f794-ff30-7bee-a5f4-ef5ad315715e"),
|
OrganizationID: valuer.MustNewUUID("0196f794-ff30-7bee-a5f4-ef5ad315715e"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -140,8 +139,8 @@ func TestNewLicenseV3(t *testing.T) {
|
|||||||
PlanName: PlanNameEnterprise,
|
PlanName: PlanNameEnterprise,
|
||||||
ValidFrom: 1234,
|
ValidFrom: 1234,
|
||||||
ValidUntil: 5678,
|
ValidUntil: 5678,
|
||||||
Status: "ACTIVE",
|
Status: valuer.NewString("ACTIVE"),
|
||||||
Features: make([]*featuretypes.GettableFeature, 0),
|
Features: make([]*Feature, 0),
|
||||||
CreatedAt: time.Time{},
|
CreatedAt: time.Time{},
|
||||||
UpdatedAt: time.Time{},
|
UpdatedAt: time.Time{},
|
||||||
LastValidatedAt: time.Time{},
|
LastValidatedAt: time.Time{},
|
||||||
@ -153,7 +152,7 @@ func TestNewLicenseV3(t *testing.T) {
|
|||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
license, err := NewLicense(tc.data, valuer.MustNewUUID("0196f794-ff30-7bee-a5f4-ef5ad315715e"))
|
license, err := NewLicense(tc.data, valuer.MustNewUUID("0196f794-ff30-7bee-a5f4-ef5ad315715e"))
|
||||||
if license != nil {
|
if license != nil {
|
||||||
license.Features = make([]*featuretypes.GettableFeature, 0)
|
license.Features = make([]*Feature, 0)
|
||||||
delete(license.Data, "features")
|
delete(license.Data, "features")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,67 +1,72 @@
|
|||||||
package licensetypes
|
package licensetypes
|
||||||
|
|
||||||
import "github.com/SigNoz/signoz/pkg/types/featuretypes"
|
import "github.com/SigNoz/signoz/pkg/valuer"
|
||||||
|
|
||||||
const SSO = "SSO"
|
|
||||||
const Basic = "BASIC_PLAN"
|
|
||||||
const Enterprise = "ENTERPRISE_PLAN"
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
PlanNameEnterprise = "ENTERPRISE"
|
// Feature Key
|
||||||
PlanNameBasic = "BASIC"
|
SSO = valuer.NewString("sso")
|
||||||
|
Onboarding = valuer.NewString("onboarding")
|
||||||
|
ChatSupport = valuer.NewString("chat_support")
|
||||||
|
Gateway = valuer.NewString("gateway")
|
||||||
|
PremiumSupport = valuer.NewString("premium_support")
|
||||||
|
UseSpanMetrics = valuer.NewString("use_span_metrics")
|
||||||
|
AnomalyDetection = valuer.NewString("anomaly_detection")
|
||||||
|
DotMetricsEnabled = valuer.NewString("dot_metrics_enabled")
|
||||||
|
|
||||||
|
// License State
|
||||||
|
LicenseStatusInvalid = valuer.NewString("invalid")
|
||||||
|
|
||||||
|
// Plan
|
||||||
|
PlanNameEnterprise = valuer.NewString("enterprise")
|
||||||
|
PlanNameBasic = valuer.NewString("basic")
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
type Feature struct {
|
||||||
MapOldPlanKeyToNewPlanName map[string]string = map[string]string{PlanNameBasic: Basic, PlanNameEnterprise: Enterprise}
|
Name valuer.String `json:"name"`
|
||||||
)
|
Active bool `json:"active"`
|
||||||
|
Usage int64 `json:"usage"`
|
||||||
|
UsageLimit int64 `json:"usage_limit"`
|
||||||
|
Route string `json:"route"`
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var BasicPlan = []*Feature{
|
||||||
LicenseStatusInvalid = "INVALID"
|
{
|
||||||
)
|
|
||||||
|
|
||||||
const Onboarding = "ONBOARDING"
|
|
||||||
const ChatSupport = "CHAT_SUPPORT"
|
|
||||||
const Gateway = "GATEWAY"
|
|
||||||
const PremiumSupport = "PREMIUM_SUPPORT"
|
|
||||||
|
|
||||||
var BasicPlan = featuretypes.FeatureSet{
|
|
||||||
&featuretypes.GettableFeature{
|
|
||||||
Name: SSO,
|
Name: SSO,
|
||||||
Active: false,
|
Active: false,
|
||||||
Usage: 0,
|
Usage: 0,
|
||||||
UsageLimit: -1,
|
UsageLimit: -1,
|
||||||
Route: "",
|
Route: "",
|
||||||
},
|
},
|
||||||
&featuretypes.GettableFeature{
|
{
|
||||||
Name: featuretypes.UseSpanMetrics,
|
Name: UseSpanMetrics,
|
||||||
Active: false,
|
Active: false,
|
||||||
Usage: 0,
|
Usage: 0,
|
||||||
UsageLimit: -1,
|
UsageLimit: -1,
|
||||||
Route: "",
|
Route: "",
|
||||||
},
|
},
|
||||||
&featuretypes.GettableFeature{
|
{
|
||||||
Name: Gateway,
|
Name: Gateway,
|
||||||
Active: false,
|
Active: false,
|
||||||
Usage: 0,
|
Usage: 0,
|
||||||
UsageLimit: -1,
|
UsageLimit: -1,
|
||||||
Route: "",
|
Route: "",
|
||||||
},
|
},
|
||||||
&featuretypes.GettableFeature{
|
{
|
||||||
Name: PremiumSupport,
|
Name: PremiumSupport,
|
||||||
Active: false,
|
Active: false,
|
||||||
Usage: 0,
|
Usage: 0,
|
||||||
UsageLimit: -1,
|
UsageLimit: -1,
|
||||||
Route: "",
|
Route: "",
|
||||||
},
|
},
|
||||||
&featuretypes.GettableFeature{
|
{
|
||||||
Name: featuretypes.AnomalyDetection,
|
Name: AnomalyDetection,
|
||||||
Active: false,
|
Active: false,
|
||||||
Usage: 0,
|
Usage: 0,
|
||||||
UsageLimit: -1,
|
UsageLimit: -1,
|
||||||
Route: "",
|
Route: "",
|
||||||
},
|
},
|
||||||
&featuretypes.GettableFeature{
|
{
|
||||||
Name: featuretypes.TraceFunnels,
|
Name: DotMetricsEnabled,
|
||||||
Active: false,
|
Active: false,
|
||||||
Usage: 0,
|
Usage: 0,
|
||||||
UsageLimit: -1,
|
UsageLimit: -1,
|
||||||
@ -69,58 +74,58 @@ var BasicPlan = featuretypes.FeatureSet{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var EnterprisePlan = featuretypes.FeatureSet{
|
var EnterprisePlan = []*Feature{
|
||||||
&featuretypes.GettableFeature{
|
{
|
||||||
Name: SSO,
|
Name: SSO,
|
||||||
Active: true,
|
Active: true,
|
||||||
Usage: 0,
|
Usage: 0,
|
||||||
UsageLimit: -1,
|
UsageLimit: -1,
|
||||||
Route: "",
|
Route: "",
|
||||||
},
|
},
|
||||||
&featuretypes.GettableFeature{
|
{
|
||||||
Name: featuretypes.UseSpanMetrics,
|
Name: UseSpanMetrics,
|
||||||
Active: false,
|
Active: false,
|
||||||
Usage: 0,
|
Usage: 0,
|
||||||
UsageLimit: -1,
|
UsageLimit: -1,
|
||||||
Route: "",
|
Route: "",
|
||||||
},
|
},
|
||||||
&featuretypes.GettableFeature{
|
{
|
||||||
Name: Onboarding,
|
Name: Onboarding,
|
||||||
Active: true,
|
Active: true,
|
||||||
Usage: 0,
|
Usage: 0,
|
||||||
UsageLimit: -1,
|
UsageLimit: -1,
|
||||||
Route: "",
|
Route: "",
|
||||||
},
|
},
|
||||||
&featuretypes.GettableFeature{
|
{
|
||||||
Name: ChatSupport,
|
Name: ChatSupport,
|
||||||
Active: true,
|
Active: true,
|
||||||
Usage: 0,
|
Usage: 0,
|
||||||
UsageLimit: -1,
|
UsageLimit: -1,
|
||||||
Route: "",
|
Route: "",
|
||||||
},
|
},
|
||||||
&featuretypes.GettableFeature{
|
{
|
||||||
Name: Gateway,
|
Name: Gateway,
|
||||||
Active: true,
|
Active: true,
|
||||||
Usage: 0,
|
Usage: 0,
|
||||||
UsageLimit: -1,
|
UsageLimit: -1,
|
||||||
Route: "",
|
Route: "",
|
||||||
},
|
},
|
||||||
&featuretypes.GettableFeature{
|
{
|
||||||
Name: PremiumSupport,
|
Name: PremiumSupport,
|
||||||
Active: true,
|
Active: true,
|
||||||
Usage: 0,
|
Usage: 0,
|
||||||
UsageLimit: -1,
|
UsageLimit: -1,
|
||||||
Route: "",
|
Route: "",
|
||||||
},
|
},
|
||||||
&featuretypes.GettableFeature{
|
{
|
||||||
Name: featuretypes.AnomalyDetection,
|
Name: AnomalyDetection,
|
||||||
Active: true,
|
Active: true,
|
||||||
Usage: 0,
|
Usage: 0,
|
||||||
UsageLimit: -1,
|
UsageLimit: -1,
|
||||||
Route: "",
|
Route: "",
|
||||||
},
|
},
|
||||||
&featuretypes.GettableFeature{
|
{
|
||||||
Name: featuretypes.TraceFunnels,
|
Name: DotMetricsEnabled,
|
||||||
Active: false,
|
Active: false,
|
||||||
Usage: 0,
|
Usage: 0,
|
||||||
UsageLimit: -1,
|
UsageLimit: -1,
|
||||||
@ -128,9 +133,16 @@ var EnterprisePlan = featuretypes.FeatureSet{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var DefaultFeatureSet = featuretypes.FeatureSet{
|
var DefaultFeatureSet = []*Feature{
|
||||||
&featuretypes.GettableFeature{
|
{
|
||||||
Name: featuretypes.UseSpanMetrics,
|
Name: UseSpanMetrics,
|
||||||
|
Active: false,
|
||||||
|
Usage: 0,
|
||||||
|
UsageLimit: -1,
|
||||||
|
Route: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: DotMetricsEnabled,
|
||||||
Active: false,
|
Active: false,
|
||||||
Usage: 0,
|
Usage: 0,
|
||||||
UsageLimit: -1,
|
UsageLimit: -1,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user