diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 629a9e6691..f36fa65a3b 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -11,5 +11,5 @@
/pkg/errors/ @grandwizard28
/pkg/factory/ @grandwizard28
/pkg/types/ @grandwizard28
-/pkg/sqlmigration/ @vikrantgupta25
.golangci.yml @grandwizard28
+**/(zeus|licensing|sqlmigration)/ @vikrantgupta25
\ No newline at end of file
diff --git a/README.md b/README.md
index 60f8621703..cc0925878a 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,7 @@
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 signoz.io for the full documentation, tutorials, and guide.
-
+
diff --git a/ee/licensing/httplicensing/provider.go b/ee/licensing/httplicensing/provider.go
index 1cfdad4d37..34cf90c46e 100644
--- a/ee/licensing/httplicensing/provider.go
+++ b/ee/licensing/httplicensing/provider.go
@@ -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)
- 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)
+func (provider *provider) GetFeatureFlags(ctx context.Context, organizationID valuer.UUID) ([]*licensetypes.Feature, error) {
+ license, err := provider.GetActive(ctx, organizationID)
if err != nil {
+ if errors.Ast(err, errors.TypeNotFound) {
+ return licensetypes.BasicPlan, 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
}
diff --git a/ee/licensing/licensingstore/sqllicensingstore/store.go b/ee/licensing/licensingstore/sqllicensingstore/store.go
index 9e8ea19d71..dfbb257a93 100644
--- a/ee/licensing/licensingstore/sqllicensingstore/store.go
+++ b/ee/licensing/licensingstore/sqllicensingstore/store.go
@@ -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
-}
diff --git a/ee/query-service/app/api/api.go b/ee/query-service/app/api/api.go
index 15042a4d95..bb24474e23 100644
--- a/ee/query-service/app/api/api.go
+++ b/ee/query-service/app/api/api.go
@@ -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)
diff --git a/ee/query-service/app/api/featureFlags.go b/ee/query-service/app/api/featureFlags.go
index 13572c2f5f..459b5d6508 100644
--- a/ee/query-service/app/api/featureFlags.go
+++ b/ee/query-service/app/api/featureFlags.go
@@ -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")
@@ -131,28 +139,28 @@ func fetchZeusFeatures(url, licenseKey string) ([]*featuretypes.GettableFeature,
}
type ZeusFeaturesResponse struct {
- Status string `json:"status"`
- Data []*featuretypes.GettableFeature `json:"data"`
+ Status string `json:"status"`
+ 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)
}
diff --git a/ee/query-service/app/api/featureFlags_test.go b/ee/query-service/app/api/featureFlags_test.go
index 79032a43a5..96194e41fa 100644
--- a/ee/query-service/app/api/featureFlags_test.go
+++ b/ee/query-service/app/api/featureFlags_test.go
@@ -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},
},
},
}
diff --git a/frontend/src/api/features/getFeatureFlags.ts b/frontend/src/api/features/getFeatureFlags.ts
deleted file mode 100644
index 2ce37b99e1..0000000000
--- a/frontend/src/api/features/getFeatureFlags.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-import axios from 'api';
-import { ApiResponse } from 'types/api';
-import { FeatureFlagProps } from 'types/api/features/getFeaturesFlags';
-
-const getFeaturesFlags = (): Promise =>
- axios
- .get>(`/featureFlags`)
- .then((response) => response.data.data);
-
-export default getFeaturesFlags;
diff --git a/frontend/src/api/traceFunnels/index.ts b/frontend/src/api/traceFunnels/index.ts
index c93a3c058f..d1cb0a2057 100644
--- a/frontend/src/api/traceFunnels/index.ts
+++ b/frontend/src/api/traceFunnels/index.ts
@@ -167,8 +167,8 @@ interface UpdateFunnelDescriptionPayload {
export const saveFunnelDescription = async (
payload: UpdateFunnelDescriptionPayload,
): Promise | ErrorResponse> => {
- const response: AxiosResponse = await axios.post(
- `${FUNNELS_BASE_PATH}/save`,
+ const response: AxiosResponse = await axios.put(
+ `${FUNNELS_BASE_PATH}/${payload.funnel_id}`,
payload,
);
diff --git a/frontend/src/api/v1/features/list.ts b/frontend/src/api/v1/features/list.ts
new file mode 100644
index 0000000000..43a445ead5
--- /dev/null
+++ b/frontend/src/api/v1/features/list.ts
@@ -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> => {
+ try {
+ const response = await axios.get(`/features`);
+
+ return {
+ httpStatusCode: response.status,
+ data: response.data.data,
+ };
+ } catch (error) {
+ ErrorResponseHandlerV2(error as AxiosError);
+ }
+};
+
+export default list;
diff --git a/frontend/src/constants/features.ts b/frontend/src/constants/features.ts
index f1e486ee91..2fecd0ea36 100644
--- a/frontend/src/constants/features.ts
+++ b/frontend/src/constants/features.ts
@@ -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',
}
diff --git a/frontend/src/container/AllAlertChannels/__tests__/CreateAlertChannelNormalUser.test.tsx b/frontend/src/container/AllAlertChannels/__tests__/CreateAlertChannelNormalUser.test.tsx
index e9761e8df0..b613a5a613 100644
--- a/frontend/src/container/AllAlertChannels/__tests__/CreateAlertChannelNormalUser.test.tsx
+++ b/frontend/src/container/AllAlertChannels/__tests__/CreateAlertChannelNormalUser.test.tsx
@@ -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(
diff --git a/frontend/src/container/FormAlertRules/QuerySection.tsx b/frontend/src/container/FormAlertRules/QuerySection.tsx
index 6982dfff3d..6a14f50e9e 100644
--- a/frontend/src/container/FormAlertRules/QuerySection.tsx
+++ b/frontend/src/container/FormAlertRules/QuerySection.tsx
@@ -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);
diff --git a/frontend/src/container/GridCardLayout/GridCard/index.tsx b/frontend/src/container/GridCardLayout/GridCard/index.tsx
index fe8b1a39c1..84be138d07 100644
--- a/frontend/src/container/GridCardLayout/GridCard/index.tsx
+++ b/frontend/src/container/GridCardLayout/GridCard/index.tsx
@@ -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([
diff --git a/frontend/src/container/ListAlertRules/ListAlert.tsx b/frontend/src/container/ListAlertRules/ListAlert.tsx
index 0ad805ede8..fae18d12e9 100644
--- a/frontend/src/container/ListAlertRules/ListAlert.tsx
+++ b/frontend/src/container/ListAlertRules/ListAlert.tsx
@@ -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,
diff --git a/frontend/src/container/LogsExplorerChart/index.tsx b/frontend/src/container/LogsExplorerChart/index.tsx
index d0acc3f2fd..ca15909123 100644
--- a/frontend/src/container/LogsExplorerChart/index.tsx
+++ b/frontend/src/container/LogsExplorerChart/index.tsx
@@ -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);
},
diff --git a/frontend/src/container/LogsExplorerViews/index.tsx b/frontend/src/container/LogsExplorerViews/index.tsx
index 22c321bbce..a6d7c5a1a1 100644
--- a/frontend/src/container/LogsExplorerViews/index.tsx
+++ b/frontend/src/container/LogsExplorerViews/index.tsx
@@ -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: {
diff --git a/frontend/src/container/LogsExplorerViews/tests/LogsExplorerViews.test.tsx b/frontend/src/container/LogsExplorerViews/tests/LogsExplorerViews.test.tsx
index da70cd6746..2ddaa028b5 100644
--- a/frontend/src/container/LogsExplorerViews/tests/LogsExplorerViews.test.tsx
+++ b/frontend/src/container/LogsExplorerViews/tests/LogsExplorerViews.test.tsx
@@ -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(
+
+ {}}
+ listQueryKeyRef={{ current: {} }}
+ chartQueryKeyRef={{ current: {} }}
+ />
+ ,
+ );
+
+ // 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);
+ });
});
diff --git a/frontend/src/container/LogsExplorerViews/tests/mock.ts b/frontend/src/container/LogsExplorerViews/tests/mock.ts
index 6c07004eea..fa612054c8 100644
--- a/frontend/src/container/LogsExplorerViews/tests/mock.ts
+++ b/frontend/src/container/LogsExplorerViews/tests/mock.ts
@@ -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,
+};
diff --git a/frontend/src/container/TopNav/DateTimeSelectionV2/index.tsx b/frontend/src/container/TopNav/DateTimeSelectionV2/index.tsx
index b390798bab..3ca773569f 100644
--- a/frontend/src/container/TopNav/DateTimeSelectionV2/index.tsx
+++ b/frontend/src/container/TopNav/DateTimeSelectionV2/index.tsx
@@ -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]);
diff --git a/frontend/src/hooks/TracesFunnels/useFunnels.tsx b/frontend/src/hooks/TracesFunnels/useFunnels.tsx
index 7f85619a88..fecbfdb655 100644
--- a/frontend/src/hooks/TracesFunnels/useFunnels.tsx
+++ b/frontend/src/hooks/TracesFunnels/useFunnels.tsx
@@ -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 | ErrorResponse,
+ Error,
+ SaveFunnelDescriptionPayload
+ >({
mutationFn: saveFunnelDescription,
});
diff --git a/frontend/src/hooks/useGetFeatureFlag.tsx b/frontend/src/hooks/useGetFeatureFlag.tsx
index 49cdb2379a..9459aca7d2 100644
--- a/frontend/src/hooks/useGetFeatureFlag.tsx
+++ b/frontend/src/hooks/useGetFeatureFlag.tsx
@@ -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,
+ APIError
+>;
+
+export const useGetFeatureFlag = (
onSuccessHandler: (routes: FeatureFlagProps[]) => void,
isLoggedIn: boolean,
-): UseQueryResult =>
- useQuery({
- queryFn: getFeaturesFlags,
+): UseGetFeatureFlag =>
+ useQuery, APIError>({
queryKey: [REACT_QUERY_KEY.GET_FEATURES_FLAGS],
- onSuccess: onSuccessHandler,
+ queryFn: () => list(),
+ onSuccess: (data) => {
+ onSuccessHandler(data.data);
+ },
retryOnMount: false,
enabled: !!isLoggedIn,
});
-
-export default useGetFeatureFlag;
diff --git a/frontend/src/pages/LogsExplorer/__tests__/LogsExplorer.test.tsx b/frontend/src/pages/LogsExplorer/__tests__/LogsExplorer.test.tsx
index 1b1e3d8417..778ee993c3 100644
--- a/frontend/src/pages/LogsExplorer/__tests__/LogsExplorer.test.tsx
+++ b/frontend/src/pages/LogsExplorer/__tests__/LogsExplorer.test.tsx
@@ -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 () => {
diff --git a/frontend/src/pages/TracesFunnelDetails/components/FunnelConfiguration/AddFunnelDescriptionModal.tsx b/frontend/src/pages/TracesFunnelDetails/components/FunnelConfiguration/AddFunnelDescriptionModal.tsx
index 8f0ab20df1..967e31f9ba 100644
--- a/frontend/src/pages/TracesFunnelDetails/components/FunnelConfiguration/AddFunnelDescriptionModal.tsx
+++ b/frontend/src/pages/TracesFunnelDetails/components/FunnelConfiguration/AddFunnelDescriptionModal.tsx
@@ -41,6 +41,7 @@ function AddFunnelDescriptionModal({
{
funnel_id: funnelId,
description,
+ timestamp: Date.now(),
},
{
onSuccess: () => {
diff --git a/frontend/src/providers/App/App.tsx b/frontend/src/providers/App/App.tsx
index c580815485..e769592a97 100644
--- a/frontend/src/providers/App/App.tsx
+++ b/frontend/src/providers/App/App.tsx
@@ -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 {
diff --git a/frontend/src/providers/QueryBuilder.tsx b/frontend/src/providers/QueryBuilder.tsx
index 3ce37600c0..f95f04a8c2 100644
--- a/frontend/src/providers/QueryBuilder.tsx
+++ b/frontend/src/providers/QueryBuilder.tsx
@@ -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}`
diff --git a/frontend/src/types/api/features/getFeaturesFlags.ts b/frontend/src/types/api/features/getFeaturesFlags.ts
index 3eefd1cb4b..f7e986b17e 100644
--- a/frontend/src/types/api/features/getFeaturesFlags.ts
+++ b/frontend/src/types/api/features/getFeaturesFlags.ts
@@ -8,4 +8,7 @@ export interface FeatureFlagProps {
route: string;
}
-export type PayloadProps = FeatureFlagProps[];
+export interface PayloadProps {
+ data: FeatureFlagProps[];
+ status: string;
+}
diff --git a/frontend/src/types/api/licensesV3/getActive.ts b/frontend/src/types/api/licensesV3/getActive.ts
index be0d488b5e..654a70a857 100644
--- a/frontend/src/types/api/licensesV3/getActive.ts
+++ b/frontend/src/types/api/licensesV3/getActive.ts
@@ -25,12 +25,6 @@ export enum LicensePlatform {
CLOUD = 'CLOUD',
}
-// Legacy
-export const LicensePlanKey = {
- ENTERPRISE: 'ENTERPRISE',
- BASIC: 'BASIC',
-};
-
export type LicenseEventQueueResModel = {
event: LicenseEvent;
status: string;
diff --git a/pkg/licensing/licensing.go b/pkg/licensing/licensing.go
index 0e0196650f..25385ee9af 100644
--- a/pkg/licensing/licensing.go
+++ b/pkg/licensing/licensing.go
@@ -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 {
diff --git a/pkg/licensing/nooplicensing/provider.go b/pkg/licensing/nooplicensing/provider.go
index 0e509615f2..396879fbe7 100644
--- a/pkg/licensing/nooplicensing/provider.go
+++ b/pkg/licensing/nooplicensing/provider.go
@@ -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")
-}
diff --git a/pkg/query-service/app/http_handler.go b/pkg/query-service/app/http_handler.go
index c3e6c7fb32..30cd18d274 100644
--- a/pkg/query-service/app/http_handler.go
+++ b/pkg/query-service/app/http_handler.go
@@ -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.
diff --git a/pkg/query-service/app/integrations/builtin.go b/pkg/query-service/app/integrations/builtin.go
index 603dcfcce5..db865eeb58 100644
--- a/pkg/query-service/app/integrations/builtin.go
+++ b/pkg/query-service/app/integrations/builtin.go
@@ -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
diff --git a/pkg/query-service/app/integrations/builtin_integrations/clickhouse/assets/dashboards/overview_dot.json b/pkg/query-service/app/integrations/builtin_integrations/clickhouse/assets/dashboards/overview_dot.json
index 28945e0081..44a3132e4c 100644
--- a/pkg/query-service/app/integrations/builtin_integrations/clickhouse/assets/dashboards/overview_dot.json
+++ b/pkg/query-service/app/integrations/builtin_integrations/clickhouse/assets/dashboards/overview_dot.json
@@ -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",
diff --git a/pkg/query-service/constants/constants.go b/pkg/query-service/constants/constants.go
index d6430603f0..dffdcf3a12 100644
--- a/pkg/query-service/constants/constants.go
+++ b/pkg/query-service/constants/constants.go
@@ -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)
diff --git a/pkg/signoz/provider.go b/pkg/signoz/provider.go
index 4d55471cc4..0d007b0f75 100644
--- a/pkg/signoz/provider.go
+++ b/pkg/signoz/provider.go
@@ -90,6 +90,7 @@ func NewSQLMigrationProviderFactories(sqlstore sqlstore.SQLStore) factory.NamedM
sqlmigration.NewAddKeyOrganizationFactory(sqlstore),
sqlmigration.NewAddTraceFunnelsFactory(sqlstore),
sqlmigration.NewUpdateDashboardFactory(sqlstore),
+ sqlmigration.NewDropFeatureSetFactory(),
)
}
diff --git a/pkg/sqlmigration/039_drop_feature_set.go b/pkg/sqlmigration/039_drop_feature_set.go
new file mode 100644
index 0000000000..241f08efb3
--- /dev/null
+++ b/pkg/sqlmigration/039_drop_feature_set.go
@@ -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
+}
diff --git a/pkg/types/featuretypes/feature.go b/pkg/types/featuretypes/feature.go
deleted file mode 100644
index 68d2e7f538..0000000000
--- a/pkg/types/featuretypes/feature.go
+++ /dev/null
@@ -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"
diff --git a/pkg/types/licensetypes/license.go b/pkg/types/licensetypes/license.go
index d78c45585a..55db17a88c 100644
--- a/pkg/types/licensetypes/license.go
+++ b/pkg/types/licensetypes/license.go
@@ -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
}
diff --git a/pkg/types/licensetypes/license_test.go b/pkg/types/licensetypes/license_test.go
index b2216accbb..5ddff2c4e4 100644
--- a/pkg/types/licensetypes/license_test.go
+++ b/pkg/types/licensetypes/license_test.go
@@ -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")
}
diff --git a/pkg/types/licensetypes/plan.go b/pkg/types/licensetypes/plan.go
index 4521018084..f0e697dcc6 100644
--- a/pkg/types/licensetypes/plan.go
+++ b/pkg/types/licensetypes/plan.go
@@ -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,