mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-15 00:36:08 +08:00
Merge branch 'main' into chore/trace-funnels-bugfixes/improvements
This commit is contained in:
commit
aad892eee4
2
.github/CODEOWNERS
vendored
2
.github/CODEOWNERS
vendored
@ -11,5 +11,5 @@
|
||||
/pkg/errors/ @grandwizard28
|
||||
/pkg/factory/ @grandwizard28
|
||||
/pkg/types/ @grandwizard28
|
||||
/pkg/sqlmigration/ @vikrantgupta25
|
||||
.golangci.yml @grandwizard28
|
||||
**/(zeus|licensing|sqlmigration)/ @vikrantgupta25
|
@ -8,7 +8,7 @@
|
||||
<p align="center">All your logs, metrics, and traces in one place. Monitor your application, spot issues before they occur and troubleshoot downtime quickly with rich context. SigNoz is a cost-effective open-source alternative to Datadog and New Relic. Visit <a href="https://signoz.io" target="_blank">signoz.io</a> for the full documentation, tutorials, and guide.</p>
|
||||
|
||||
<p align="center">
|
||||
<img alt="Downloads" src="https://img.shields.io/docker/pulls/signoz/query-service?label=Docker Downloads"> </a>
|
||||
<img alt="Downloads" src="https://img.shields.io/docker/pulls/signoz/signoz.svg?label=Docker%20Downloads"> </a>
|
||||
<img alt="GitHub issues" src="https://img.shields.io/github/issues/signoz/signoz"> </a>
|
||||
<a href="https://twitter.com/intent/tweet?text=Monitor%20your%20applications%20and%20troubleshoot%20problems%20with%20SigNoz,%20an%20open-source%20alternative%20to%20DataDog,%20NewRelic.&url=https://signoz.io/&via=SigNozHQ&hashtags=opensource,signoz,observability">
|
||||
<img alt="tweet" src="https://img.shields.io/twitter/url/http/shields.io.svg?style=social"> </a>
|
||||
|
@ -5,15 +5,12 @@ import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/SigNoz/signoz/ee/query-service/constants"
|
||||
|
||||
"github.com/SigNoz/signoz/ee/licensing/licensingstore/sqllicensingstore"
|
||||
"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/featuretypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/licensetypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"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
|
||||
}
|
||||
|
||||
@ -116,11 +106,6 @@ func (provider *provider) Activate(ctx context.Context, organizationID valuer.UU
|
||||
return err
|
||||
}
|
||||
|
||||
err = provider.InitFeatures(ctx, license.Features)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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 {
|
||||
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())
|
||||
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)
|
||||
if err != nil {
|
||||
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())
|
||||
err = provider.InitFeatures(ctx, licensetypes.BasicPlan)
|
||||
activeLicense.UpdateFeatures(licensetypes.BasicPlan)
|
||||
updatedStorableLicense := licensetypes.NewStorableLicenseFromLicense(activeLicense)
|
||||
err = provider.store.Update(ctx, organizationID, updatedStorableLicense)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
@ -178,11 +159,6 @@ func (provider *provider) Refresh(ctx context.Context, organizationID valuer.UUI
|
||||
return err
|
||||
}
|
||||
|
||||
err = provider.InitFeatures(ctx, activeLicense.Features)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// feature surrogate
|
||||
func (provider *provider) CheckFeature(ctx context.Context, key string) error {
|
||||
feature, err := provider.store.GetFeature(ctx, key)
|
||||
func (provider *provider) GetFeatureFlags(ctx context.Context, organizationID valuer.UUID) ([]*licensetypes.Feature, error) {
|
||||
license, err := provider.GetActive(ctx, organizationID)
|
||||
if err != nil {
|
||||
return err
|
||||
if errors.Ast(err, errors.TypeNotFound) {
|
||||
return licensetypes.BasicPlan, nil
|
||||
}
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
gettableFeatures := make([]*featuretypes.GettableFeature, len(storableFeatures))
|
||||
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,
|
||||
})
|
||||
return license.Features, nil
|
||||
}
|
||||
|
@ -5,7 +5,6 @@ import (
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"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/valuer"
|
||||
)
|
||||
@ -80,81 +79,3 @@ func (store *store) Update(ctx context.Context, organizationID valuer.UUID, stor
|
||||
|
||||
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
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"time"
|
||||
@ -86,17 +85,12 @@ func (ah *APIHandler) Gateway() *httputil.ReverseProxy {
|
||||
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
|
||||
func (ah *APIHandler) RegisterRoutes(router *mux.Router, am *middleware.AuthZ) {
|
||||
// note: add ee override methods first
|
||||
|
||||
// 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
|
||||
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"
|
||||
"github.com/SigNoz/signoz/pkg/http/render"
|
||||
"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"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
@ -31,7 +31,7 @@ func (ah *APIHandler) getFeatureFlags(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
featureSet, err := ah.Signoz.Licensing.GetFeatureFlags(r.Context())
|
||||
featureSet, err := ah.Signoz.Licensing.GetFeatureFlags(r.Context(), orgID)
|
||||
if err != nil {
|
||||
ah.HandleError(w, err, http.StatusInternalServerError)
|
||||
return
|
||||
@ -61,7 +61,15 @@ func (ah *APIHandler) getFeatureFlags(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
if ah.opts.PreferSpanMetrics {
|
||||
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
|
||||
}
|
||||
}
|
||||
@ -72,7 +80,7 @@ func (ah *APIHandler) getFeatureFlags(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// fetchZeusFeatures makes an HTTP GET request to the /zeusFeatures endpoint
|
||||
// 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
|
||||
if url == "" {
|
||||
return nil, fmt.Errorf("url is empty")
|
||||
@ -132,27 +140,27 @@ func fetchZeusFeatures(url, licenseKey string) ([]*featuretypes.GettableFeature,
|
||||
|
||||
type ZeusFeaturesResponse struct {
|
||||
Status string `json:"status"`
|
||||
Data []*featuretypes.GettableFeature `json:"data"`
|
||||
Data []*licensetypes.Feature `json:"data"`
|
||||
}
|
||||
|
||||
// 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
|
||||
featureMap := make(map[string]*featuretypes.GettableFeature)
|
||||
featureMap := make(map[string]*licensetypes.Feature)
|
||||
|
||||
// Add all features from the otherFeatures set to the map
|
||||
for _, feature := range internalFeatures {
|
||||
featureMap[feature.Name] = feature
|
||||
featureMap[feature.Name.StringValue()] = feature
|
||||
}
|
||||
|
||||
// Add all features from the zeusFeatures set to the map
|
||||
// If a feature already exists (i.e., same name), the zeusFeature will overwrite it
|
||||
for _, feature := range zeusFeatures {
|
||||
featureMap[feature.Name] = feature
|
||||
featureMap[feature.Name.StringValue()] = feature
|
||||
}
|
||||
|
||||
// Convert the map back to a FeatureSet slice
|
||||
var mergedFeatures []*featuretypes.GettableFeature
|
||||
var mergedFeatures []*licensetypes.Feature
|
||||
for _, feature := range featureMap {
|
||||
mergedFeatures = append(mergedFeatures, feature)
|
||||
}
|
||||
|
@ -3,78 +3,79 @@ package api
|
||||
import (
|
||||
"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"
|
||||
)
|
||||
|
||||
func TestMergeFeatureSets(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
zeusFeatures []*featuretypes.GettableFeature
|
||||
internalFeatures []*featuretypes.GettableFeature
|
||||
expected []*featuretypes.GettableFeature
|
||||
zeusFeatures []*licensetypes.Feature
|
||||
internalFeatures []*licensetypes.Feature
|
||||
expected []*licensetypes.Feature
|
||||
}{
|
||||
{
|
||||
name: "empty zeusFeatures and internalFeatures",
|
||||
zeusFeatures: []*featuretypes.GettableFeature{},
|
||||
internalFeatures: []*featuretypes.GettableFeature{},
|
||||
expected: []*featuretypes.GettableFeature{},
|
||||
zeusFeatures: []*licensetypes.Feature{},
|
||||
internalFeatures: []*licensetypes.Feature{},
|
||||
expected: []*licensetypes.Feature{},
|
||||
},
|
||||
{
|
||||
name: "non-empty zeusFeatures and empty internalFeatures",
|
||||
zeusFeatures: []*featuretypes.GettableFeature{
|
||||
{Name: "Feature1", Active: true},
|
||||
{Name: "Feature2", Active: false},
|
||||
zeusFeatures: []*licensetypes.Feature{
|
||||
{Name: valuer.NewString("Feature1"), Active: true},
|
||||
{Name: valuer.NewString("Feature2"), Active: false},
|
||||
},
|
||||
internalFeatures: []*featuretypes.GettableFeature{},
|
||||
expected: []*featuretypes.GettableFeature{
|
||||
{Name: "Feature1", Active: true},
|
||||
{Name: "Feature2", Active: false},
|
||||
internalFeatures: []*licensetypes.Feature{},
|
||||
expected: []*licensetypes.Feature{
|
||||
{Name: valuer.NewString("Feature1"), Active: true},
|
||||
{Name: valuer.NewString("Feature2"), Active: false},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "empty zeusFeatures and non-empty internalFeatures",
|
||||
zeusFeatures: []*featuretypes.GettableFeature{},
|
||||
internalFeatures: []*featuretypes.GettableFeature{
|
||||
{Name: "Feature1", Active: true},
|
||||
{Name: "Feature2", Active: false},
|
||||
zeusFeatures: []*licensetypes.Feature{},
|
||||
internalFeatures: []*licensetypes.Feature{
|
||||
{Name: valuer.NewString("Feature1"), Active: true},
|
||||
{Name: valuer.NewString("Feature2"), Active: false},
|
||||
},
|
||||
expected: []*featuretypes.GettableFeature{
|
||||
{Name: "Feature1", Active: true},
|
||||
{Name: "Feature2", Active: false},
|
||||
expected: []*licensetypes.Feature{
|
||||
{Name: valuer.NewString("Feature1"), Active: true},
|
||||
{Name: valuer.NewString("Feature2"), Active: false},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "non-empty zeusFeatures and non-empty internalFeatures with no conflicts",
|
||||
zeusFeatures: []*featuretypes.GettableFeature{
|
||||
{Name: "Feature1", Active: true},
|
||||
{Name: "Feature3", Active: false},
|
||||
zeusFeatures: []*licensetypes.Feature{
|
||||
{Name: valuer.NewString("Feature1"), Active: true},
|
||||
{Name: valuer.NewString("Feature3"), Active: false},
|
||||
},
|
||||
internalFeatures: []*featuretypes.GettableFeature{
|
||||
{Name: "Feature2", Active: true},
|
||||
{Name: "Feature4", Active: false},
|
||||
internalFeatures: []*licensetypes.Feature{
|
||||
{Name: valuer.NewString("Feature2"), Active: true},
|
||||
{Name: valuer.NewString("Feature4"), Active: false},
|
||||
},
|
||||
expected: []*featuretypes.GettableFeature{
|
||||
{Name: "Feature1", Active: true},
|
||||
{Name: "Feature2", Active: true},
|
||||
{Name: "Feature3", Active: false},
|
||||
{Name: "Feature4", Active: false},
|
||||
expected: []*licensetypes.Feature{
|
||||
{Name: valuer.NewString("Feature1"), Active: true},
|
||||
{Name: valuer.NewString("Feature2"), Active: true},
|
||||
{Name: valuer.NewString("Feature3"), Active: false},
|
||||
{Name: valuer.NewString("Feature4"), Active: false},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "non-empty zeusFeatures and non-empty internalFeatures with conflicts",
|
||||
zeusFeatures: []*featuretypes.GettableFeature{
|
||||
{Name: "Feature1", Active: true},
|
||||
{Name: "Feature2", Active: false},
|
||||
zeusFeatures: []*licensetypes.Feature{
|
||||
{Name: valuer.NewString("Feature1"), Active: true},
|
||||
{Name: valuer.NewString("Feature2"), Active: false},
|
||||
},
|
||||
internalFeatures: []*featuretypes.GettableFeature{
|
||||
{Name: "Feature1", Active: false},
|
||||
{Name: "Feature3", Active: true},
|
||||
internalFeatures: []*licensetypes.Feature{
|
||||
{Name: valuer.NewString("Feature1"), Active: false},
|
||||
{Name: valuer.NewString("Feature3"), Active: true},
|
||||
},
|
||||
expected: []*featuretypes.GettableFeature{
|
||||
{Name: "Feature1", Active: true},
|
||||
{Name: "Feature2", Active: false},
|
||||
{Name: "Feature3", Active: true},
|
||||
expected: []*licensetypes.Feature{
|
||||
{Name: valuer.NewString("Feature1"), Active: true},
|
||||
{Name: valuer.NewString("Feature2"), Active: false},
|
||||
{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;
|
@ -167,8 +167,8 @@ interface UpdateFunnelDescriptionPayload {
|
||||
export const saveFunnelDescription = async (
|
||||
payload: UpdateFunnelDescriptionPayload,
|
||||
): Promise<SuccessResponse<FunnelData> | ErrorResponse> => {
|
||||
const response: AxiosResponse = await axios.post(
|
||||
`${FUNNELS_BASE_PATH}/save`,
|
||||
const response: AxiosResponse = await axios.put(
|
||||
`${FUNNELS_BASE_PATH}/${payload.funnel_id}`,
|
||||
payload,
|
||||
);
|
||||
|
||||
|
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 {
|
||||
SSO = 'SSO',
|
||||
USE_SPAN_METRICS = 'USE_SPAN_METRICS',
|
||||
ONBOARDING = 'ONBOARDING',
|
||||
CHAT_SUPPORT = 'CHAT_SUPPORT',
|
||||
GATEWAY = 'GATEWAY',
|
||||
PREMIUM_SUPPORT = 'PREMIUM_SUPPORT',
|
||||
ANOMALY_DETECTION = 'ANOMALY_DETECTION',
|
||||
ONBOARDING_V3 = 'ONBOARDING_V3',
|
||||
THIRD_PARTY_API = 'THIRD_PARTY_API',
|
||||
TRACE_FUNNELS = 'TRACE_FUNNELS',
|
||||
DOT_METRICS_ENABLED = 'DOT_METRICS_ENABLED',
|
||||
SSO = 'sso',
|
||||
USE_SPAN_METRICS = 'use_span_metrics',
|
||||
ONBOARDING = 'onboarding',
|
||||
CHAT_SUPPORT = 'chat_support',
|
||||
GATEWAY = 'gateway',
|
||||
PREMIUM_SUPPORT = 'premium_support',
|
||||
ANOMALY_DETECTION = 'anomaly_detection',
|
||||
ONBOARDING_V3 = 'onboarding_v3',
|
||||
DOT_METRICS_ENABLED = 'dot_metrics_enabled',
|
||||
}
|
||||
|
@ -316,7 +316,6 @@ describe('Create Alert Channel (Normal User)', () => {
|
||||
expect(screen.getByText('Microsoft Teams')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// TODO[vikrantgupta25]: check with Shaheer
|
||||
it.skip('Should check if the upgrade plan message is shown', () => {
|
||||
expect(screen.getByText('Upgrade to a Paid Plan')).toBeInTheDocument();
|
||||
expect(
|
||||
|
@ -36,7 +36,6 @@ function QuerySection({
|
||||
const { t } = useTranslation('alerts');
|
||||
const [currentTab, setCurrentTab] = useState(queryCategory);
|
||||
|
||||
// TODO[vikrantgupta25] : check if this is still required ??
|
||||
const handleQueryCategoryChange = (queryType: string): void => {
|
||||
setQueryCategory(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(() => {
|
||||
if (variablesToGetUpdated.length > 0) {
|
||||
queryClient.cancelQueries([
|
||||
|
@ -41,7 +41,6 @@ const { Search } = Input;
|
||||
function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
|
||||
const { t } = useTranslation('common');
|
||||
const { user } = useAppContext();
|
||||
// TODO[vikrantgupta25]: check with sagar on cleanup
|
||||
const [addNewAlert, action] = useComponentPermission(
|
||||
['add_new_alert', 'action'],
|
||||
user.role,
|
||||
|
@ -62,6 +62,8 @@ function LogsExplorerChart({
|
||||
urlQuery.set(QueryParams.startTime, minTime.toString());
|
||||
urlQuery.set(QueryParams.endTime, maxTime.toString());
|
||||
urlQuery.delete(QueryParams.relativeTime);
|
||||
// Remove Hidden Filters from URL query parameters on time change
|
||||
urlQuery.delete(QueryParams.activeLogId);
|
||||
const generatedUrl = `${location.pathname}?${urlQuery.toString()}`;
|
||||
safeNavigate(generatedUrl);
|
||||
},
|
||||
|
@ -188,6 +188,26 @@ function LogsExplorerViews({
|
||||
},
|
||||
],
|
||||
legend: '{{severity_text}}',
|
||||
...(activeLogId && {
|
||||
filters: {
|
||||
...listQuery?.filters,
|
||||
items: [
|
||||
...(listQuery?.filters?.items || []),
|
||||
{
|
||||
id: v4(),
|
||||
key: {
|
||||
key: 'id',
|
||||
type: '',
|
||||
dataType: DataTypes.String,
|
||||
isColumn: true,
|
||||
},
|
||||
op: OPERATORS['<='],
|
||||
value: activeLogId,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
const modifiedQuery: Query = {
|
||||
@ -202,7 +222,7 @@ function LogsExplorerViews({
|
||||
};
|
||||
|
||||
return modifiedQuery;
|
||||
}, [stagedQuery, listQuery]);
|
||||
}, [stagedQuery, listQuery, activeLogId]);
|
||||
|
||||
const exportDefaultQuery = useMemo(
|
||||
() =>
|
||||
@ -287,12 +307,12 @@ function LogsExplorerViews({
|
||||
});
|
||||
|
||||
// Add filter for activeLogId if present
|
||||
let updatedFilters = paginateData.filters;
|
||||
let updatedFilters = params.filters;
|
||||
if (activeLogId) {
|
||||
updatedFilters = {
|
||||
...paginateData.filters,
|
||||
...params.filters,
|
||||
items: [
|
||||
...(paginateData.filters?.items || []),
|
||||
...(params.filters?.items || []),
|
||||
{
|
||||
id: v4(),
|
||||
key: {
|
||||
|
@ -1,17 +1,23 @@
|
||||
import ROUTES from 'constants/routes';
|
||||
import { useCopyLogLink } from 'hooks/logs/useCopyLogLink';
|
||||
import { useGetExplorerQueryRange } from 'hooks/queryBuilder/useGetExplorerQueryRange';
|
||||
import { logsQueryRangeSuccessResponse } from 'mocks-server/__mockdata__/logs_query_range';
|
||||
import { server } from 'mocks-server/server';
|
||||
import { rest } from 'msw';
|
||||
import { SELECTED_VIEWS } from 'pages/LogsExplorer/utils';
|
||||
import { QueryBuilderContext } from 'providers/QueryBuilder';
|
||||
import { VirtuosoMockContext } from 'react-virtuoso';
|
||||
import { fireEvent, render, RenderResult } from 'tests/test-utils';
|
||||
import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
import LogsExplorerViews from '..';
|
||||
import { logsQueryRangeSuccessNewFormatResponse } from './mock';
|
||||
import {
|
||||
logsQueryRangeSuccessNewFormatResponse,
|
||||
mockQueryBuilderContextValue,
|
||||
} from './mock';
|
||||
|
||||
const queryRangeURL = 'http://localhost/api/v3/query_range';
|
||||
|
||||
const ACTIVE_LOG_ID = 'test-log-id';
|
||||
jest.mock('react-router-dom', () => ({
|
||||
...jest.requireActual('react-router-dom'),
|
||||
useLocation: (): { pathname: string } => ({
|
||||
@ -81,6 +87,12 @@ jest.mock('hooks/useSafeNavigate', () => ({
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock('hooks/logs/useCopyLogLink', () => ({
|
||||
useCopyLogLink: jest.fn().mockReturnValue({
|
||||
activeLogId: ACTIVE_LOG_ID,
|
||||
}),
|
||||
}));
|
||||
|
||||
// Set up the specific behavior for useGetExplorerQueryRange in individual test cases
|
||||
beforeEach(() => {
|
||||
(useGetExplorerQueryRange as jest.Mock).mockReturnValue({
|
||||
@ -162,4 +174,47 @@ describe('LogsExplorerViews -', () => {
|
||||
queryByText('Something went wrong. Please try again or contact support.'),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should add activeLogId filter when present in URL', () => {
|
||||
// Mock useCopyLogLink to return an activeLogId
|
||||
(useCopyLogLink as jest.Mock).mockReturnValue({
|
||||
activeLogId: ACTIVE_LOG_ID,
|
||||
});
|
||||
|
||||
lodsQueryServerRequest();
|
||||
render(
|
||||
<QueryBuilderContext.Provider value={mockQueryBuilderContextValue}>
|
||||
<LogsExplorerViews
|
||||
selectedView={SELECTED_VIEWS.SEARCH}
|
||||
showFrequencyChart
|
||||
setIsLoadingQueries={(): void => {}}
|
||||
listQueryKeyRef={{ current: {} }}
|
||||
chartQueryKeyRef={{ current: {} }}
|
||||
/>
|
||||
</QueryBuilderContext.Provider>,
|
||||
);
|
||||
|
||||
// Get the query data from the first call to useGetExplorerQueryRange
|
||||
const {
|
||||
queryData,
|
||||
} = (useGetExplorerQueryRange as jest.Mock).mock.calls[0][0].builder;
|
||||
const firstQuery = queryData[0];
|
||||
|
||||
// Get the original number of filters from mock data
|
||||
const originalFiltersLength =
|
||||
mockQueryBuilderContextValue.currentQuery.builder.queryData[0].filters?.items
|
||||
.length || 0;
|
||||
const expectedFiltersLength = originalFiltersLength + 1; // +1 for activeLogId filter
|
||||
|
||||
// Verify that the activeLogId filter is present
|
||||
expect(
|
||||
firstQuery.filters?.items.some(
|
||||
(item: TagFilterItem) =>
|
||||
item.key?.key === 'id' && item.op === '<=' && item.value === ACTIVE_LOG_ID,
|
||||
),
|
||||
).toBe(true);
|
||||
|
||||
// Verify the total number of filters (original + 1 new activeLogId filter)
|
||||
expect(firstQuery.filters?.items.length).toBe(expectedFiltersLength);
|
||||
});
|
||||
});
|
||||
|
@ -1,3 +1,13 @@
|
||||
import {
|
||||
initialQueriesMap,
|
||||
initialQueryBuilderFormValues,
|
||||
OPERATORS,
|
||||
PANEL_TYPES,
|
||||
} from 'constants/queryBuilder';
|
||||
import { noop } from 'lodash-es';
|
||||
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
export const logsQueryRangeSuccessNewFormatResponse = {
|
||||
data: {
|
||||
result: [],
|
||||
@ -49,3 +59,148 @@ export const logsQueryRangeSuccessNewFormatResponse = {
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const mockQueryBuilderContextValue = {
|
||||
isDefaultQuery: (): boolean => false,
|
||||
currentQuery: {
|
||||
...initialQueriesMap.logs,
|
||||
builder: {
|
||||
...initialQueriesMap.logs.builder,
|
||||
queryData: [
|
||||
{
|
||||
...initialQueryBuilderFormValues,
|
||||
filters: {
|
||||
items: [
|
||||
{
|
||||
id: '1',
|
||||
key: {
|
||||
key: 'service',
|
||||
type: '',
|
||||
dataType: DataTypes.String,
|
||||
isColumn: true,
|
||||
},
|
||||
op: OPERATORS['='],
|
||||
value: 'frontend',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
key: {
|
||||
key: 'log_level',
|
||||
type: '',
|
||||
dataType: DataTypes.String,
|
||||
isColumn: true,
|
||||
},
|
||||
op: OPERATORS['='],
|
||||
value: 'INFO',
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
},
|
||||
},
|
||||
initialQueryBuilderFormValues,
|
||||
],
|
||||
},
|
||||
},
|
||||
setSupersetQuery: jest.fn(),
|
||||
supersetQuery: {
|
||||
...initialQueriesMap.logs,
|
||||
builder: {
|
||||
...initialQueriesMap.logs.builder,
|
||||
queryData: [
|
||||
{
|
||||
...initialQueryBuilderFormValues,
|
||||
filters: {
|
||||
items: [
|
||||
{
|
||||
id: '1',
|
||||
key: {
|
||||
key: 'service',
|
||||
type: '',
|
||||
dataType: DataTypes.String,
|
||||
isColumn: true,
|
||||
},
|
||||
op: OPERATORS['='],
|
||||
value: 'frontend',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
key: {
|
||||
key: 'log_level',
|
||||
type: '',
|
||||
dataType: DataTypes.String,
|
||||
isColumn: true,
|
||||
},
|
||||
op: OPERATORS['='],
|
||||
value: 'INFO',
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
},
|
||||
},
|
||||
initialQueryBuilderFormValues,
|
||||
],
|
||||
},
|
||||
},
|
||||
stagedQuery: {
|
||||
...initialQueriesMap.logs,
|
||||
builder: {
|
||||
...initialQueriesMap.logs.builder,
|
||||
queryData: [
|
||||
{
|
||||
...initialQueryBuilderFormValues,
|
||||
filters: {
|
||||
items: [
|
||||
{
|
||||
id: '1',
|
||||
key: {
|
||||
key: 'service',
|
||||
type: '',
|
||||
dataType: DataTypes.String,
|
||||
isColumn: true,
|
||||
},
|
||||
op: OPERATORS['='],
|
||||
value: 'frontend',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
key: {
|
||||
key: 'log_level',
|
||||
type: '',
|
||||
dataType: DataTypes.String,
|
||||
isColumn: true,
|
||||
},
|
||||
op: OPERATORS['='],
|
||||
value: 'INFO',
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
},
|
||||
},
|
||||
initialQueryBuilderFormValues,
|
||||
],
|
||||
},
|
||||
},
|
||||
initialDataSource: null,
|
||||
panelType: PANEL_TYPES.TIME_SERIES,
|
||||
isEnabledQuery: false,
|
||||
lastUsedQuery: 0,
|
||||
setLastUsedQuery: noop,
|
||||
handleSetQueryData: noop,
|
||||
handleSetFormulaData: noop,
|
||||
handleSetQueryItemData: noop,
|
||||
handleSetConfig: noop,
|
||||
removeQueryBuilderEntityByIndex: noop,
|
||||
removeQueryTypeItemByIndex: noop,
|
||||
addNewBuilderQuery: noop,
|
||||
cloneQuery: noop,
|
||||
addNewFormula: noop,
|
||||
addNewQueryItem: noop,
|
||||
redirectWithQueryBuilderData: noop,
|
||||
handleRunQuery: noop,
|
||||
resetQuery: noop,
|
||||
updateAllQueriesOperators: (): Query => initialQueriesMap.logs,
|
||||
updateQueriesData: (): Query => initialQueriesMap.logs,
|
||||
initQueryBuilderData: noop,
|
||||
handleOnUnitsChange: noop,
|
||||
isStagedQueryUpdated: (): boolean => false,
|
||||
};
|
||||
|
@ -377,6 +377,8 @@ function DateTimeSelection({
|
||||
urlQuery.delete('endTime');
|
||||
|
||||
urlQuery.set(QueryParams.relativeTime, value);
|
||||
// Remove Hidden Filters from URL query parameters on time change
|
||||
urlQuery.delete(QueryParams.activeLogId);
|
||||
|
||||
const generatedUrl = `${location.pathname}?${urlQuery.toString()}`;
|
||||
safeNavigate(generatedUrl);
|
||||
@ -669,9 +671,7 @@ function DateTimeSelection({
|
||||
urlQuery.set(QueryParams.endTime, endTime);
|
||||
urlQuery.delete(QueryParams.relativeTime);
|
||||
}
|
||||
|
||||
const generatedUrl = `${location.pathname}?${urlQuery.toString()}`;
|
||||
|
||||
safeNavigate(generatedUrl);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [location.pathname, updateTimeInterval, globalTimeLoading]);
|
||||
|
@ -142,6 +142,7 @@ export const useValidateFunnelSteps = ({
|
||||
interface SaveFunnelDescriptionPayload {
|
||||
funnel_id: string;
|
||||
description: string;
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
export const useSaveFunnelDescription = (): UseMutationResult<
|
||||
@ -149,7 +150,11 @@ export const useSaveFunnelDescription = (): UseMutationResult<
|
||||
Error,
|
||||
SaveFunnelDescriptionPayload
|
||||
> =>
|
||||
useMutation({
|
||||
useMutation<
|
||||
SuccessResponse<FunnelData> | ErrorResponse,
|
||||
Error,
|
||||
SaveFunnelDescriptionPayload
|
||||
>({
|
||||
mutationFn: saveFunnelDescription,
|
||||
});
|
||||
|
||||
|
@ -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 { useQuery, UseQueryResult } from 'react-query';
|
||||
import { SuccessResponseV2 } from 'types/api';
|
||||
import APIError from 'types/api/error';
|
||||
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,
|
||||
isLoggedIn: boolean,
|
||||
): UseQueryResult<FeatureFlagProps[], unknown> =>
|
||||
useQuery<FeatureFlagProps[]>({
|
||||
queryFn: getFeaturesFlags,
|
||||
): UseGetFeatureFlag =>
|
||||
useQuery<SuccessResponseV2<FeatureFlagProps[]>, APIError>({
|
||||
queryKey: [REACT_QUERY_KEY.GET_FEATURES_FLAGS],
|
||||
onSuccess: onSuccessHandler,
|
||||
queryFn: () => list(),
|
||||
onSuccess: (data) => {
|
||||
onSuccessHandler(data.data);
|
||||
},
|
||||
retryOnMount: false,
|
||||
enabled: !!isLoggedIn,
|
||||
});
|
||||
|
||||
export default useGetFeatureFlag;
|
||||
|
@ -145,14 +145,6 @@ describe('Logs Explorer Tests', () => {
|
||||
await waitFor(() =>
|
||||
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 () => {
|
||||
|
@ -41,6 +41,7 @@ function AddFunnelDescriptionModal({
|
||||
{
|
||||
funnel_id: funnelId,
|
||||
description,
|
||||
timestamp: Date.now(),
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
|
@ -5,7 +5,7 @@ import getUserVersion from 'api/v1/version/getVersion';
|
||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||
import dayjs from 'dayjs';
|
||||
import useActiveLicenseV3 from 'hooks/useActiveLicenseV3/useActiveLicenseV3';
|
||||
import useGetFeatureFlag from 'hooks/useGetFeatureFlag';
|
||||
import { useGetFeatureFlag } from 'hooks/useGetFeatureFlag';
|
||||
import { useGlobalEventListener } from 'hooks/useGlobalEventListener';
|
||||
import useGetUser from 'hooks/user/useGetUser';
|
||||
import {
|
||||
|
@ -832,6 +832,8 @@ export function QueryBuilderProvider({
|
||||
),
|
||||
);
|
||||
}
|
||||
// Remove Hidden Filters from URL query parameters on query change
|
||||
urlQuery.delete(QueryParams.activeLogId);
|
||||
|
||||
const generatedUrl = redirectingUrl
|
||||
? `${redirectingUrl}?${urlQuery}`
|
||||
|
@ -8,4 +8,7 @@ export interface FeatureFlagProps {
|
||||
route: string;
|
||||
}
|
||||
|
||||
export type PayloadProps = FeatureFlagProps[];
|
||||
export interface PayloadProps {
|
||||
data: FeatureFlagProps[];
|
||||
status: string;
|
||||
}
|
||||
|
@ -25,12 +25,6 @@ export enum LicensePlatform {
|
||||
CLOUD = 'CLOUD',
|
||||
}
|
||||
|
||||
// Legacy
|
||||
export const LicensePlanKey = {
|
||||
ENTERPRISE: 'ENTERPRISE',
|
||||
BASIC: 'BASIC',
|
||||
};
|
||||
|
||||
export type LicenseEventQueueResModel = {
|
||||
event: LicenseEvent;
|
||||
status: string;
|
||||
|
@ -6,7 +6,6 @@ import (
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"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/valuer"
|
||||
)
|
||||
@ -31,18 +30,8 @@ type Licensing interface {
|
||||
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(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
|
||||
GetFeatureFlag(ctx context.Context, key string) (*featuretypes.GettableFeature, 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
|
||||
GetFeatureFlags(ctx context.Context, organizationID valuer.UUID) ([]*licensetypes.Feature, error)
|
||||
}
|
||||
|
||||
type API interface {
|
||||
|
@ -6,7 +6,6 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"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/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")
|
||||
}
|
||||
|
||||
func (provider *noopLicensing) CheckFeature(ctx context.Context, key string) 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) {
|
||||
func (provider *noopLicensing) GetFeatureFlags(_ context.Context, _ valuer.UUID) ([]*licensetypes.Feature, error) {
|
||||
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/authtypes"
|
||||
"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"
|
||||
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/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/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) {
|
||||
featureSet, err := aH.Signoz.Licensing.GetFeatureFlags(r.Context())
|
||||
featureSet, err := aH.Signoz.Licensing.GetFeatureFlags(r.Context(), valuer.GenerateUUID())
|
||||
if err != nil {
|
||||
aH.HandleError(w, err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if aH.preferSpanMetrics {
|
||||
for idx, feature := range featureSet {
|
||||
if feature.Name == featuretypes.UseSpanMetrics {
|
||||
if feature.Name == licensetypes.UseSpanMetrics {
|
||||
featureSet[idx].Active = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if constants.IsDotMetricsEnabled {
|
||||
featureSet = append(featureSet, &featuretypes.GettableFeature{
|
||||
Name: featuretypes.DotMetricsEnabled,
|
||||
Active: true,
|
||||
})
|
||||
for idx, feature := range featureSet {
|
||||
if feature.Name == licensetypes.DotMetricsEnabled {
|
||||
featureSet[idx].Active = true
|
||||
}
|
||||
}
|
||||
}
|
||||
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.
|
||||
// 'live' query param can be used to check liveliness of
|
||||
// the service by checking the database connection.
|
||||
|
@ -186,6 +186,14 @@ func HydrateFileUris(spec interface{}, fs embed.FS, basedir string) (interface{}
|
||||
if strings.HasPrefix(dashboardUri, "file://") {
|
||||
dashboards[i] = strings.Replace(dashboardUri, ".json", "_dot.json", 1)
|
||||
}
|
||||
} else if dashBoardMap, ok := dashboard.(map[string]interface{}); ok {
|
||||
if dashboardUri, ok := dashBoardMap["definition"].(string); ok {
|
||||
if strings.HasPrefix(dashboardUri, "file://") {
|
||||
dashboardUri = strings.Replace(dashboardUri, ".json", "_dot.json", 1)
|
||||
}
|
||||
dashBoardMap["definition"] = dashboardUri
|
||||
}
|
||||
dashboards[i] = dashBoardMap
|
||||
}
|
||||
}
|
||||
v = dashboards
|
||||
|
@ -124,7 +124,7 @@
|
||||
"multiSelect": false,
|
||||
"name": "host.name",
|
||||
"order": 0,
|
||||
"queryValue": "SELECT JSONExtractString(labels, 'host.name') AS host.name\nFROM signoz_metrics.distributed_time_series_v4_1day\nWHERE metric_name = 'ClickHouseMetrics_VersionInteger' and __normalized=false \nGROUP BY host.name",
|
||||
"queryValue": "SELECT JSONExtractString(labels, 'host.name') AS `host.name`\nFROM signoz_metrics.distributed_time_series_v4_1day\nWHERE metric_name = 'ClickHouseMetrics_VersionInteger' and __normalized=false \nGROUP BY `host.name`",
|
||||
"selectedValue": "",
|
||||
"showALLOption": false,
|
||||
"sort": "DISABLED",
|
||||
|
@ -9,7 +9,6 @@ import (
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/query-service/model"
|
||||
v3 "github.com/SigNoz/signoz/pkg/query-service/model/v3"
|
||||
"github.com/SigNoz/signoz/pkg/types/featuretypes"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -66,16 +65,6 @@ func UseMetricsPreAggregation() bool {
|
||||
|
||||
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 {
|
||||
evalDelayStr := GetOrDefaultEnv("RULES_EVAL_DELAY", "2m")
|
||||
evalDelayDuration, err := time.ParseDuration(evalDelayStr)
|
||||
|
@ -90,6 +90,7 @@ func NewSQLMigrationProviderFactories(sqlstore sqlstore.SQLStore) factory.NamedM
|
||||
sqlmigration.NewAddKeyOrganizationFactory(sqlstore),
|
||||
sqlmigration.NewAddTraceFunnelsFactory(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/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/featuretypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
@ -30,9 +29,9 @@ type License struct {
|
||||
ID valuer.UUID
|
||||
Key string
|
||||
Data map[string]interface{}
|
||||
PlanName string
|
||||
Features []*featuretypes.GettableFeature
|
||||
Status string
|
||||
PlanName valuer.String
|
||||
Features []*Feature
|
||||
Status valuer.String
|
||||
ValidFrom int64
|
||||
ValidUntil int64
|
||||
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")
|
||||
}
|
||||
|
||||
var features []*featuretypes.GettableFeature
|
||||
var features []*Feature
|
||||
|
||||
// extract id from data
|
||||
licenseIDStr, err := extractKeyFromMapStringInterface[string](licenseData, "id")
|
||||
@ -145,26 +144,28 @@ func NewLicense(data []byte, organizationID valuer.UUID) (*License, error) {
|
||||
delete(licenseData, "key")
|
||||
|
||||
// extract status from data
|
||||
status, err := extractKeyFromMapStringInterface[string](licenseData, "status")
|
||||
statusStr, err := extractKeyFromMapStringInterface[string](licenseData, "status")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
status := valuer.NewString(statusStr)
|
||||
|
||||
planMap, err := extractKeyFromMapStringInterface[map[string]any](licenseData, "plan")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
planName, err := extractKeyFromMapStringInterface[string](planMap, "name")
|
||||
planNameStr, err := extractKeyFromMapStringInterface[string](planMap, "name")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
planName := valuer.NewString(planNameStr)
|
||||
// if license status is invalid then default it to basic
|
||||
if status == LicenseStatusInvalid {
|
||||
planName = PlanNameBasic
|
||||
}
|
||||
|
||||
featuresFromZeus := make([]*featuretypes.GettableFeature, 0)
|
||||
featuresFromZeus := make([]*Feature, 0)
|
||||
if _features, ok := licenseData["features"]; ok {
|
||||
featuresData, err := json.Marshal(_features)
|
||||
if err != nil {
|
||||
@ -232,28 +233,30 @@ func NewLicense(data []byte, organizationID valuer.UUID) (*License, error) {
|
||||
}
|
||||
|
||||
func NewLicenseFromStorableLicense(storableLicense *StorableLicense) (*License, error) {
|
||||
var features []*featuretypes.GettableFeature
|
||||
var features []*Feature
|
||||
// extract status from data
|
||||
status, err := extractKeyFromMapStringInterface[string](storableLicense.Data, "status")
|
||||
statusStr, err := extractKeyFromMapStringInterface[string](storableLicense.Data, "status")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
status := valuer.NewString(statusStr)
|
||||
|
||||
planMap, err := extractKeyFromMapStringInterface[map[string]any](storableLicense.Data, "plan")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
planName, err := extractKeyFromMapStringInterface[string](planMap, "name")
|
||||
planNameStr, err := extractKeyFromMapStringInterface[string](planMap, "name")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
planName := valuer.NewString(planNameStr)
|
||||
// if license status is invalid then default it to basic
|
||||
if status == LicenseStatusInvalid {
|
||||
planName = PlanNameBasic
|
||||
}
|
||||
|
||||
featuresFromZeus := make([]*featuretypes.GettableFeature, 0)
|
||||
featuresFromZeus := make([]*Feature, 0)
|
||||
if _features, ok := storableLicense.Data["features"]; ok {
|
||||
featuresData, err := json.Marshal(_features)
|
||||
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 {
|
||||
updatedLicense, err := NewLicense(data, license.OrganizationID)
|
||||
if err != nil {
|
||||
@ -373,11 +380,4 @@ type Store interface {
|
||||
Get(context.Context, valuer.UUID, valuer.UUID) (*StorableLicense, error)
|
||||
GetAll(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"
|
||||
"time"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/types/featuretypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@ -92,8 +91,8 @@ func TestNewLicenseV3(t *testing.T) {
|
||||
PlanName: PlanNameEnterprise,
|
||||
ValidFrom: 1730899309,
|
||||
ValidUntil: -1,
|
||||
Status: "ACTIVE",
|
||||
Features: make([]*featuretypes.GettableFeature, 0),
|
||||
Status: valuer.NewString("ACTIVE"),
|
||||
Features: make([]*Feature, 0),
|
||||
OrganizationID: valuer.MustNewUUID("0196f794-ff30-7bee-a5f4-ef5ad315715e"),
|
||||
},
|
||||
},
|
||||
@ -116,8 +115,8 @@ func TestNewLicenseV3(t *testing.T) {
|
||||
PlanName: PlanNameBasic,
|
||||
ValidFrom: 1730899309,
|
||||
ValidUntil: -1,
|
||||
Status: "INVALID",
|
||||
Features: make([]*featuretypes.GettableFeature, 0),
|
||||
Status: valuer.NewString("INVALID"),
|
||||
Features: make([]*Feature, 0),
|
||||
OrganizationID: valuer.MustNewUUID("0196f794-ff30-7bee-a5f4-ef5ad315715e"),
|
||||
},
|
||||
},
|
||||
@ -140,8 +139,8 @@ func TestNewLicenseV3(t *testing.T) {
|
||||
PlanName: PlanNameEnterprise,
|
||||
ValidFrom: 1234,
|
||||
ValidUntil: 5678,
|
||||
Status: "ACTIVE",
|
||||
Features: make([]*featuretypes.GettableFeature, 0),
|
||||
Status: valuer.NewString("ACTIVE"),
|
||||
Features: make([]*Feature, 0),
|
||||
CreatedAt: time.Time{},
|
||||
UpdatedAt: time.Time{},
|
||||
LastValidatedAt: time.Time{},
|
||||
@ -153,7 +152,7 @@ func TestNewLicenseV3(t *testing.T) {
|
||||
for _, tc := range testCases {
|
||||
license, err := NewLicense(tc.data, valuer.MustNewUUID("0196f794-ff30-7bee-a5f4-ef5ad315715e"))
|
||||
if license != nil {
|
||||
license.Features = make([]*featuretypes.GettableFeature, 0)
|
||||
license.Features = make([]*Feature, 0)
|
||||
delete(license.Data, "features")
|
||||
}
|
||||
|
||||
|
@ -1,67 +1,72 @@
|
||||
package licensetypes
|
||||
|
||||
import "github.com/SigNoz/signoz/pkg/types/featuretypes"
|
||||
|
||||
const SSO = "SSO"
|
||||
const Basic = "BASIC_PLAN"
|
||||
const Enterprise = "ENTERPRISE_PLAN"
|
||||
import "github.com/SigNoz/signoz/pkg/valuer"
|
||||
|
||||
var (
|
||||
PlanNameEnterprise = "ENTERPRISE"
|
||||
PlanNameBasic = "BASIC"
|
||||
// Feature Key
|
||||
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 (
|
||||
MapOldPlanKeyToNewPlanName map[string]string = map[string]string{PlanNameBasic: Basic, PlanNameEnterprise: Enterprise}
|
||||
)
|
||||
type Feature struct {
|
||||
Name valuer.String `json:"name"`
|
||||
Active bool `json:"active"`
|
||||
Usage int64 `json:"usage"`
|
||||
UsageLimit int64 `json:"usage_limit"`
|
||||
Route string `json:"route"`
|
||||
}
|
||||
|
||||
var (
|
||||
LicenseStatusInvalid = "INVALID"
|
||||
)
|
||||
|
||||
const Onboarding = "ONBOARDING"
|
||||
const ChatSupport = "CHAT_SUPPORT"
|
||||
const Gateway = "GATEWAY"
|
||||
const PremiumSupport = "PREMIUM_SUPPORT"
|
||||
|
||||
var BasicPlan = featuretypes.FeatureSet{
|
||||
&featuretypes.GettableFeature{
|
||||
var BasicPlan = []*Feature{
|
||||
{
|
||||
Name: SSO,
|
||||
Active: false,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
&featuretypes.GettableFeature{
|
||||
Name: featuretypes.UseSpanMetrics,
|
||||
{
|
||||
Name: UseSpanMetrics,
|
||||
Active: false,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
&featuretypes.GettableFeature{
|
||||
{
|
||||
Name: Gateway,
|
||||
Active: false,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
&featuretypes.GettableFeature{
|
||||
{
|
||||
Name: PremiumSupport,
|
||||
Active: false,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
&featuretypes.GettableFeature{
|
||||
Name: featuretypes.AnomalyDetection,
|
||||
{
|
||||
Name: AnomalyDetection,
|
||||
Active: false,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
&featuretypes.GettableFeature{
|
||||
Name: featuretypes.TraceFunnels,
|
||||
{
|
||||
Name: DotMetricsEnabled,
|
||||
Active: false,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
@ -69,58 +74,58 @@ var BasicPlan = featuretypes.FeatureSet{
|
||||
},
|
||||
}
|
||||
|
||||
var EnterprisePlan = featuretypes.FeatureSet{
|
||||
&featuretypes.GettableFeature{
|
||||
var EnterprisePlan = []*Feature{
|
||||
{
|
||||
Name: SSO,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
&featuretypes.GettableFeature{
|
||||
Name: featuretypes.UseSpanMetrics,
|
||||
{
|
||||
Name: UseSpanMetrics,
|
||||
Active: false,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
&featuretypes.GettableFeature{
|
||||
{
|
||||
Name: Onboarding,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
&featuretypes.GettableFeature{
|
||||
{
|
||||
Name: ChatSupport,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
&featuretypes.GettableFeature{
|
||||
{
|
||||
Name: Gateway,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
&featuretypes.GettableFeature{
|
||||
{
|
||||
Name: PremiumSupport,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
&featuretypes.GettableFeature{
|
||||
Name: featuretypes.AnomalyDetection,
|
||||
{
|
||||
Name: AnomalyDetection,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
&featuretypes.GettableFeature{
|
||||
Name: featuretypes.TraceFunnels,
|
||||
{
|
||||
Name: DotMetricsEnabled,
|
||||
Active: false,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
@ -128,9 +133,16 @@ var EnterprisePlan = featuretypes.FeatureSet{
|
||||
},
|
||||
}
|
||||
|
||||
var DefaultFeatureSet = featuretypes.FeatureSet{
|
||||
&featuretypes.GettableFeature{
|
||||
Name: featuretypes.UseSpanMetrics,
|
||||
var DefaultFeatureSet = []*Feature{
|
||||
{
|
||||
Name: UseSpanMetrics,
|
||||
Active: false,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
{
|
||||
Name: DotMetricsEnabled,
|
||||
Active: false,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
|
Loading…
x
Reference in New Issue
Block a user