mirror of
https://github.com/SigNoz/signoz.git
synced 2026-09-05 19:10:40 +01:00
Compare commits
13 Commits
feat/quick
...
qf-values-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d97e579f5e | ||
|
|
f8821b73aa | ||
|
|
7b7fd139d1 | ||
|
|
ba0ffd36d1 | ||
|
|
a75442f31e | ||
|
|
52588c4582 | ||
|
|
e485f50221 | ||
|
|
b3b547f34a | ||
|
|
be9a045455 | ||
|
|
297d3dd44b | ||
|
|
e84a61d43f | ||
|
|
afb1eb4a41 | ||
|
|
4f6414ef61 |
1
.github/workflows/integrationci.yaml
vendored
1
.github/workflows/integrationci.yaml
vendored
@@ -50,6 +50,7 @@ jobs:
|
||||
- logspipelines
|
||||
- passwordauthn
|
||||
- preference
|
||||
- quickfilter
|
||||
- querierlogs
|
||||
- queriertraces
|
||||
- queriermetrics
|
||||
|
||||
@@ -96,6 +96,7 @@ func runGenerateAuthz(_ context.Context) error {
|
||||
coretypes.NewResourceRef(coretypes.ResourceServiceAccount).String(): true,
|
||||
coretypes.NewResourceRef(coretypes.ResourceRole).String(): true,
|
||||
coretypes.NewResourceRef(coretypes.ResourceMetaResourceFactorAPIKey).String(): true,
|
||||
coretypes.NewResourceRef(coretypes.ResourceMetaResourceLicense).String(): true,
|
||||
coretypes.NewResourceRef(coretypes.ResourceTelemetryResourceLogs).String(): true,
|
||||
coretypes.NewResourceRef(coretypes.ResourceTelemetryResourceTraces).String(): true,
|
||||
coretypes.NewResourceRef(coretypes.ResourceTelemetryResourceMetrics).String(): true,
|
||||
|
||||
1246
docs/api/openapi.yml
1246
docs/api/openapi.yml
File diff suppressed because it is too large
Load Diff
@@ -108,6 +108,20 @@ func (provider *Provider) SearchIngestionKeysByName(ctx context.Context, orgID v
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (provider *Provider) GetIngestionKey(ctx context.Context, orgID valuer.UUID, keyID string) (*gatewaytypes.IngestionKey, error) {
|
||||
responseBody, err := provider.do(ctx, orgID, http.MethodGet, "/v1/workspaces/me/keys/"+keyID, nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var ingestionKey gatewaytypes.IngestionKey
|
||||
if err := json.Unmarshal([]byte(gjson.GetBytes(responseBody, "data").String()), &ingestionKey); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &ingestionKey, nil
|
||||
}
|
||||
|
||||
func (provider *Provider) CreateIngestionKey(ctx context.Context, orgID valuer.UUID, name string, tags []string, expiresAt time.Time) (*gatewaytypes.GettableCreatedIngestionKey, error) {
|
||||
requestBody := gatewaytypes.PostableIngestionKey{
|
||||
Name: name,
|
||||
@@ -161,7 +175,7 @@ func (provider *Provider) DeleteIngestionKey(ctx context.Context, orgID valuer.U
|
||||
}
|
||||
|
||||
func (provider *Provider) CreateIngestionKeyLimit(ctx context.Context, orgID valuer.UUID, keyID string, signal string, limitConfig gatewaytypes.LimitConfig, tags []string) (*gatewaytypes.GettableCreatedIngestionKeyLimit, error) {
|
||||
requestBody := gatewaytypes.PostableIngestionKeyLimit{
|
||||
requestBody := gatewaytypes.DeprecatedPostableIngestionKeyLimit{
|
||||
Signal: signal,
|
||||
Config: limitConfig,
|
||||
Tags: tags,
|
||||
@@ -184,6 +198,34 @@ func (provider *Provider) CreateIngestionKeyLimit(ctx context.Context, orgID val
|
||||
return &createdIngestionKeyLimitResponse, nil
|
||||
}
|
||||
|
||||
func (provider *Provider) GetIngestionKeyLimit(ctx context.Context, orgID valuer.UUID, limitID string) (*gatewaytypes.Limit, error) {
|
||||
responseBody, err := provider.do(ctx, orgID, http.MethodGet, "/v1/workspaces/me/limits/"+limitID, nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var limit gatewaytypes.Limit
|
||||
if err := json.Unmarshal([]byte(gjson.GetBytes(responseBody, "data").String()), &limit); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &limit, nil
|
||||
}
|
||||
|
||||
func (provider *Provider) GetIngestionKeyLimits(ctx context.Context, orgID valuer.UUID, keyID string) ([]gatewaytypes.Limit, error) {
|
||||
responseBody, err := provider.do(ctx, orgID, http.MethodGet, "/v1/workspaces/me/keys/"+keyID+"/limits", nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var limits []gatewaytypes.Limit
|
||||
if err := json.Unmarshal([]byte(gjson.GetBytes(responseBody, "data").String()), &limits); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return limits, nil
|
||||
}
|
||||
|
||||
func (provider *Provider) UpdateIngestionKeyLimit(ctx context.Context, orgID valuer.UUID, limitID string, limitConfig gatewaytypes.LimitConfig, tags []string) error {
|
||||
requestBody := gatewaytypes.UpdatableIngestionKeyLimit{
|
||||
Config: limitConfig,
|
||||
|
||||
@@ -22,89 +22,6 @@ func NewLicensingAPI(licensing licensing.Licensing) licensing.API {
|
||||
return &licensingAPI{licensing: licensing}
|
||||
}
|
||||
|
||||
func (api *licensingAPI) Activate(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
claims, err := authtypes.ClaimsFromContext(ctx)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
orgID, err := valuer.NewUUID(claims.OrgID)
|
||||
if err != nil {
|
||||
render.Error(rw, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "orgId is invalid"))
|
||||
return
|
||||
}
|
||||
|
||||
req := new(licensetypes.PostableLicense)
|
||||
err = json.NewDecoder(r.Body).Decode(&req)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
err = api.licensing.Activate(r.Context(), orgID, req.Key)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
render.Success(rw, http.StatusAccepted, nil)
|
||||
}
|
||||
|
||||
func (api *licensingAPI) GetActive(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
claims, err := authtypes.ClaimsFromContext(ctx)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
orgID, err := valuer.NewUUID(claims.OrgID)
|
||||
if err != nil {
|
||||
render.Error(rw, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "orgId is invalid"))
|
||||
return
|
||||
}
|
||||
|
||||
license, err := api.licensing.GetActive(r.Context(), orgID)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
gettableLicense := licensetypes.NewGettableLicense(license.Data, license.Key)
|
||||
render.Success(rw, http.StatusOK, gettableLicense)
|
||||
}
|
||||
|
||||
func (api *licensingAPI) Refresh(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
claims, err := authtypes.ClaimsFromContext(ctx)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
orgID, err := valuer.NewUUID(claims.OrgID)
|
||||
if err != nil {
|
||||
render.Error(rw, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "orgId is invalid"))
|
||||
return
|
||||
}
|
||||
|
||||
err = api.licensing.Refresh(r.Context(), orgID)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
render.Success(rw, http.StatusNoContent, nil)
|
||||
}
|
||||
|
||||
func (api *licensingAPI) Checkout(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
@@ -95,24 +95,65 @@ func (provider *provider) Validate(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (provider *provider) Activate(ctx context.Context, organizationID valuer.UUID, key string) error {
|
||||
data, err := provider.zeus.GetLicense(ctx, key)
|
||||
func (provider *provider) Activate(ctx context.Context, organizationID valuer.UUID, key string) (*licensetypes.License, error) {
|
||||
zeusLicense, err := provider.zeus.GetLicense(ctx, key)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "unable to fetch license data with upstream server")
|
||||
return nil, errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "unable to fetch license data with upstream server")
|
||||
}
|
||||
|
||||
license, err := licensetypes.NewLicense(data, organizationID)
|
||||
license, err := licensetypes.NewLicense(zeusLicense, organizationID)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "failed to create license entity")
|
||||
return nil, errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "failed to create license entity")
|
||||
}
|
||||
|
||||
storableLicense := licensetypes.NewStorableLicenseFromLicense(license)
|
||||
err = provider.store.Create(ctx, storableLicense)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return license, nil
|
||||
}
|
||||
|
||||
func (provider *provider) Get(ctx context.Context, organizationID valuer.UUID, licenseID valuer.UUID) (*licensetypes.License, error) {
|
||||
storableLicense, err := provider.store.Get(ctx, organizationID, licenseID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return licensetypes.NewLicenseFromStorableLicense(storableLicense)
|
||||
}
|
||||
|
||||
func (provider *provider) List(ctx context.Context, organizationID valuer.UUID) ([]*licensetypes.License, error) {
|
||||
storableLicenses, err := provider.store.GetAll(ctx, organizationID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
licenses := make([]*licensetypes.License, 0, len(storableLicenses))
|
||||
for _, storableLicense := range storableLicenses {
|
||||
license, err := licensetypes.NewLicenseFromStorableLicense(storableLicense)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
licenses = append(licenses, license)
|
||||
}
|
||||
|
||||
return licenses, nil
|
||||
}
|
||||
|
||||
func (provider *provider) Delete(ctx context.Context, organizationID valuer.UUID, licenseID valuer.UUID) error {
|
||||
license, err := provider.Get(ctx, organizationID, licenseID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
if err := license.ErrIfCloud(); err != nil {
|
||||
return errors.WithAdditionalf(err, "license %s cannot be deleted", licenseID.StringValue())
|
||||
}
|
||||
|
||||
return provider.store.Delete(ctx, organizationID, licenseID)
|
||||
}
|
||||
|
||||
func (provider *provider) GetActive(ctx context.Context, organizationID valuer.UUID) (*licensetypes.License, error) {
|
||||
@@ -139,7 +180,7 @@ func (provider *provider) Refresh(ctx context.Context, organizationID valuer.UUI
|
||||
return err
|
||||
}
|
||||
|
||||
data, err := provider.zeus.GetLicense(ctx, activeLicense.Key)
|
||||
zeusLicense, err := provider.zeus.GetLicense(ctx, activeLicense.Key)
|
||||
if err != nil {
|
||||
if time.Since(activeLicense.LastValidatedAt) > time.Duration(provider.config.FailureThreshold)*provider.config.PollInterval {
|
||||
activeLicense.UpdateFeatures(licensetypes.BasicPlan)
|
||||
@@ -154,7 +195,7 @@ func (provider *provider) Refresh(ctx context.Context, organizationID valuer.UUI
|
||||
return err
|
||||
}
|
||||
|
||||
err = activeLicense.Update(data)
|
||||
err = activeLicense.Update(zeusLicense)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "failed to create license entity from license data")
|
||||
}
|
||||
|
||||
@@ -64,6 +64,22 @@ func (store *store) GetAll(ctx context.Context, organizationID valuer.UUID) ([]*
|
||||
return storableLicenses, nil
|
||||
}
|
||||
|
||||
func (store *store) Delete(ctx context.Context, organizationID valuer.UUID, licenseID valuer.UUID) error {
|
||||
_, err := store.
|
||||
sqlstore.
|
||||
BunDB().
|
||||
NewDelete().
|
||||
Model(new(licensetypes.StorableLicense)).
|
||||
Where("org_id = ?", organizationID).
|
||||
Where("id = ?", licenseID).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "unable to delete license with ID: %s", licenseID)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (store *store) Update(ctx context.Context, organizationID valuer.UUID, storableLicense *licensetypes.StorableLicense) error {
|
||||
_, err := store.
|
||||
sqlstore.
|
||||
|
||||
@@ -76,11 +76,6 @@ func (ah *APIHandler) RegisterRoutes(router *mux.Router, am *middleware.AuthZ) {
|
||||
router.HandleFunc("/api/v1/billing", am.AdminAccess(ah.getBilling)).Methods(http.MethodGet)
|
||||
router.HandleFunc("/api/v1/portal", am.AdminAccess(ah.LicensingAPI.Portal)).Methods(http.MethodPost)
|
||||
|
||||
// v3
|
||||
router.HandleFunc("/api/v3/licenses", am.AdminAccess(ah.LicensingAPI.Activate)).Methods(http.MethodPost)
|
||||
router.HandleFunc("/api/v3/licenses", am.AdminAccess(ah.LicensingAPI.Refresh)).Methods(http.MethodPut)
|
||||
router.HandleFunc("/api/v3/licenses/active", am.ViewAccess(ah.LicensingAPI.GetActive)).Methods(http.MethodGet)
|
||||
|
||||
// v4
|
||||
router.HandleFunc("/api/v4/query_range", am.ViewAccess(ah.queryRangeV4)).Methods(http.MethodPost)
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ func New(ctx context.Context, providerSettings factory.ProviderSettings, config
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (provider *Provider) GetLicense(ctx context.Context, key string) ([]byte, error) {
|
||||
func (provider *Provider) GetLicense(ctx context.Context, key string) (*zeustypes.License, error) {
|
||||
response, err := provider.do(
|
||||
ctx,
|
||||
provider.config.URL.JoinPath("/v2/licenses/me"),
|
||||
@@ -63,7 +63,12 @@ func (provider *Provider) GetLicense(ctx context.Context, key string) ([]byte, e
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return []byte(gjson.GetBytes(response, "data").String()), nil
|
||||
license := new(zeustypes.License)
|
||||
if err := json.Unmarshal([]byte(gjson.GetBytes(response, "data").String()), license); err != nil {
|
||||
return nil, errors.Wrapf(err, errors.TypeInternal, zeus.ErrCodeResponseMalformed, "failed to unmarshal license data")
|
||||
}
|
||||
|
||||
return license, nil
|
||||
}
|
||||
|
||||
func (provider *Provider) GetCheckoutURL(ctx context.Context, key string, body []byte) ([]byte, error) {
|
||||
|
||||
@@ -75,6 +75,23 @@
|
||||
"field_jsmops_tags": "Tags",
|
||||
"placeholder_jsmops_tags": "Type a tag and press Enter",
|
||||
"help_jsmops_tags": "Tags added to every alert.",
|
||||
"incidentio_tip": "Create an HTTP alert source in incident.io (On-call \u2192 Alert routing \u2192 Sources) and paste its URL and token below.",
|
||||
"incidentio_tip_link": "Learn how",
|
||||
"field_incidentio_url": "Alert source URL",
|
||||
"help_incidentio_url": "The alert events URL from the source's setup page, e.g. https://api.incident.io/v2/alert_events/http/<source_config_id>.",
|
||||
"field_incidentio_token": "Token",
|
||||
"help_incidentio_token": "The alert source's secret token, from the same setup page.",
|
||||
"field_incidentio_title": "Title",
|
||||
"help_incidentio_title": "Template for the alert title. Kept stable while the alert fires \u2014 incident.io ignores content updates on repeat events.",
|
||||
"field_incidentio_description": "Description",
|
||||
"help_incidentio_description": "Template for the alert description. Markdown, rendered natively by incident.io.",
|
||||
"incidentio_required_fields": "Alert source URL and token are required",
|
||||
"incidentio_url_invalid": "URL must be an incident.io alert events URL (https://api.incident.io/v2/alert_events/http/<source_config_id>)",
|
||||
"field_incidentio_metadata": "Additional metadata",
|
||||
"help_incidentio_metadata": "Key-value pairs added to every alert's metadata, on top of the alert's labels (these win on a key clash). Values may use templates, e.g. {{ .CommonLabels.severity }}.",
|
||||
"placeholder_incidentio_metadata_key": "Key",
|
||||
"placeholder_incidentio_metadata_value": "Value",
|
||||
"button_incidentio_add_metadata": "Add metadata",
|
||||
|
||||
"field_slack_recipient": "Recipient",
|
||||
"field_slack_title": "Title",
|
||||
|
||||
@@ -75,6 +75,23 @@
|
||||
"field_jsmops_tags": "Tags",
|
||||
"placeholder_jsmops_tags": "Type a tag and press Enter",
|
||||
"help_jsmops_tags": "Tags added to every alert.",
|
||||
"incidentio_tip": "Create an HTTP alert source in incident.io (On-call \u2192 Alert routing \u2192 Sources) and paste its URL and token below.",
|
||||
"incidentio_tip_link": "Learn how",
|
||||
"field_incidentio_url": "Alert source URL",
|
||||
"help_incidentio_url": "The alert events URL from the source's setup page, e.g. https://api.incident.io/v2/alert_events/http/<source_config_id>.",
|
||||
"field_incidentio_token": "Token",
|
||||
"help_incidentio_token": "The alert source's secret token, from the same setup page.",
|
||||
"field_incidentio_title": "Title",
|
||||
"help_incidentio_title": "Template for the alert title. Kept stable while the alert fires \u2014 incident.io ignores content updates on repeat events.",
|
||||
"field_incidentio_description": "Description",
|
||||
"help_incidentio_description": "Template for the alert description. Markdown, rendered natively by incident.io.",
|
||||
"incidentio_required_fields": "Alert source URL and token are required",
|
||||
"incidentio_url_invalid": "URL must be an incident.io alert events URL (https://api.incident.io/v2/alert_events/http/<source_config_id>)",
|
||||
"field_incidentio_metadata": "Additional metadata",
|
||||
"help_incidentio_metadata": "Key-value pairs added to every alert's metadata, on top of the alert's labels (these win on a key clash). Values may use templates, e.g. {{ .CommonLabels.severity }}.",
|
||||
"placeholder_incidentio_metadata_key": "Key",
|
||||
"placeholder_incidentio_metadata_value": "Value",
|
||||
"button_incidentio_add_metadata": "Add metadata",
|
||||
"field_slack_recipient": "Recipient",
|
||||
"field_slack_title": "Title",
|
||||
"field_slack_description": "Description",
|
||||
|
||||
@@ -103,30 +103,30 @@ function createMockLicense(
|
||||
overrides: Partial<LicenseResModel> = {},
|
||||
): LicenseResModel {
|
||||
return {
|
||||
key: 'test-key',
|
||||
event_queue: {
|
||||
created_at: '0',
|
||||
id: 'test-license-id',
|
||||
eventQueue: {
|
||||
createdAt: '0',
|
||||
event: LicenseEvent.NO_EVENT,
|
||||
scheduled_at: '0',
|
||||
scheduledAt: '0',
|
||||
status: '',
|
||||
updated_at: '0',
|
||||
updatedAt: '0',
|
||||
},
|
||||
state: LicenseState.ACTIVATED,
|
||||
status: LicenseStatus.VALID,
|
||||
platform: LicensePlatform.CLOUD,
|
||||
created_at: '0',
|
||||
createdAt: '0',
|
||||
plan: {
|
||||
created_at: '0',
|
||||
id: '0',
|
||||
createdAt: '0',
|
||||
description: '',
|
||||
is_active: true,
|
||||
isActive: true,
|
||||
name: '',
|
||||
updated_at: '0',
|
||||
updatedAt: '0',
|
||||
},
|
||||
plan_id: '0',
|
||||
free_until: '0',
|
||||
updated_at: '0',
|
||||
valid_from: 0,
|
||||
valid_until: 0,
|
||||
freeUntil: '0',
|
||||
updatedAt: '0',
|
||||
validFrom: 0,
|
||||
validUntil: 0,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
@@ -850,6 +850,22 @@ describe('PrivateRoute', () => {
|
||||
assertStaysOnRoute(ROUTES.WORKSPACE_LOCKED);
|
||||
});
|
||||
|
||||
it('should keep a custom role (ANONYMOUS) on workspace locked instead of bouncing to unauthorized', () => {
|
||||
renderPrivateRoute({
|
||||
initialRoute: ROUTES.WORKSPACE_LOCKED,
|
||||
appContext: {
|
||||
isLoggedIn: true,
|
||||
isFetchingActiveLicense: false,
|
||||
activeLicense: createMockLicense({ platform: LicensePlatform.CLOUD }),
|
||||
trialInfo: createMockTrialInfo({ workSpaceBlock: true }),
|
||||
user: createMockUser({ role: USER_ROLES.ANONYMOUS as ROLES }),
|
||||
},
|
||||
isCloudUser: true,
|
||||
});
|
||||
|
||||
assertStaysOnRoute(ROUTES.WORKSPACE_LOCKED);
|
||||
});
|
||||
|
||||
it('should not redirect self-hosted users to workspace locked even when workSpaceBlock is true', () => {
|
||||
renderPrivateRoute({
|
||||
initialRoute: ROUTES.HOME,
|
||||
@@ -1024,6 +1040,24 @@ describe('PrivateRoute', () => {
|
||||
assertStaysOnRoute(ROUTES.WORKSPACE_SUSPENDED);
|
||||
});
|
||||
|
||||
it('should keep a custom role (ANONYMOUS) on workspace suspended instead of bouncing to unauthorized', () => {
|
||||
renderPrivateRoute({
|
||||
initialRoute: ROUTES.WORKSPACE_SUSPENDED,
|
||||
appContext: {
|
||||
isLoggedIn: true,
|
||||
isFetchingActiveLicense: false,
|
||||
activeLicense: createMockLicense({
|
||||
platform: LicensePlatform.CLOUD,
|
||||
state: LicenseState.DEFAULTED,
|
||||
}),
|
||||
user: createMockUser({ role: USER_ROLES.ANONYMOUS as ROLES }),
|
||||
},
|
||||
isCloudUser: true,
|
||||
});
|
||||
|
||||
assertStaysOnRoute(ROUTES.WORKSPACE_SUSPENDED);
|
||||
});
|
||||
|
||||
it('should not redirect self-hosted users to workspace suspended when license is defaulted', () => {
|
||||
renderPrivateRoute({
|
||||
initialRoute: ROUTES.HOME,
|
||||
@@ -1580,6 +1614,18 @@ describe('PrivateRoute', () => {
|
||||
path: ROUTES.SUPPORT,
|
||||
deniedRoles: [USER_ROLES.AUTHOR as ROLES],
|
||||
},
|
||||
WORKSPACE_LOCKED: {
|
||||
path: ROUTES.WORKSPACE_LOCKED,
|
||||
deniedRoles: DENIED_ROLES,
|
||||
},
|
||||
WORKSPACE_SUSPENDED: {
|
||||
path: ROUTES.WORKSPACE_SUSPENDED,
|
||||
deniedRoles: DENIED_ROLES,
|
||||
},
|
||||
WORKSPACE_ACCESS_RESTRICTED: {
|
||||
path: ROUTES.WORKSPACE_ACCESS_RESTRICTED,
|
||||
deniedRoles: DENIED_ROLES,
|
||||
},
|
||||
};
|
||||
|
||||
const authzRouteRolePairs: [string, string, ROLES][] = Object.entries(
|
||||
|
||||
@@ -21,18 +21,28 @@ import type {
|
||||
CreateIngestionKey201,
|
||||
CreateIngestionKeyLimit201,
|
||||
CreateIngestionKeyLimitPathParameters,
|
||||
CreateIngestionLimit201,
|
||||
DeleteIngestionKeyLimitPathParameters,
|
||||
DeleteIngestionKeyPathParameters,
|
||||
DeleteIngestionLimitPathParameters,
|
||||
GatewaytypesDeprecatedPostableIngestionKeyLimitDTO,
|
||||
GatewaytypesPostableIngestionKeyDTO,
|
||||
GatewaytypesPostableIngestionKeyLimitDTO,
|
||||
GatewaytypesUpdatableIngestionKeyLimitDTO,
|
||||
GetIngestionKey200,
|
||||
GetIngestionKeyLimits200,
|
||||
GetIngestionKeyLimitsPathParameters,
|
||||
GetIngestionKeyPathParameters,
|
||||
GetIngestionKeys200,
|
||||
GetIngestionKeysParams,
|
||||
GetIngestionLimit200,
|
||||
GetIngestionLimitPathParameters,
|
||||
RenderErrorResponseDTO,
|
||||
SearchIngestionKeys200,
|
||||
SearchIngestionKeysParams,
|
||||
UpdateIngestionKeyLimitPathParameters,
|
||||
UpdateIngestionKeyPathParameters,
|
||||
UpdateIngestionLimitPathParameters,
|
||||
} from '../sigNoz.schemas';
|
||||
|
||||
import { GeneratedAPIInstance } from '../../../generatedAPIInstance';
|
||||
@@ -300,6 +310,108 @@ export const useDeleteIngestionKey = <
|
||||
> => {
|
||||
return useMutation(getDeleteIngestionKeyMutationOptions(options));
|
||||
};
|
||||
/**
|
||||
* This endpoint returns an ingestion key for the workspace
|
||||
* @summary Get ingestion key for workspace
|
||||
*/
|
||||
export const getIngestionKey = (
|
||||
{ keyId }: GetIngestionKeyPathParameters,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<GetIngestionKey200>({
|
||||
url: `/api/v2/gateway/ingestion_keys/${keyId}`,
|
||||
method: 'GET',
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getGetIngestionKeyQueryKey = ({
|
||||
keyId,
|
||||
}: GetIngestionKeyPathParameters) => {
|
||||
return [`/api/v2/gateway/ingestion_keys/${keyId}`] as const;
|
||||
};
|
||||
|
||||
export const getGetIngestionKeyQueryOptions = <
|
||||
TData = Awaited<ReturnType<typeof getIngestionKey>>,
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
>(
|
||||
{ keyId }: GetIngestionKeyPathParameters,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getIngestionKey>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
},
|
||||
) => {
|
||||
const { query: queryOptions } = options ?? {};
|
||||
|
||||
const queryKey =
|
||||
queryOptions?.queryKey ?? getGetIngestionKeyQueryKey({ keyId });
|
||||
|
||||
const queryFn: QueryFunction<Awaited<ReturnType<typeof getIngestionKey>>> = ({
|
||||
signal,
|
||||
}) => getIngestionKey({ keyId }, signal);
|
||||
|
||||
return {
|
||||
queryKey,
|
||||
queryFn,
|
||||
enabled: !!keyId,
|
||||
...queryOptions,
|
||||
} as UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getIngestionKey>>,
|
||||
TError,
|
||||
TData
|
||||
> & { queryKey: QueryKey };
|
||||
};
|
||||
|
||||
export type GetIngestionKeyQueryResult = NonNullable<
|
||||
Awaited<ReturnType<typeof getIngestionKey>>
|
||||
>;
|
||||
export type GetIngestionKeyQueryError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary Get ingestion key for workspace
|
||||
*/
|
||||
|
||||
export function useGetIngestionKey<
|
||||
TData = Awaited<ReturnType<typeof getIngestionKey>>,
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
>(
|
||||
{ keyId }: GetIngestionKeyPathParameters,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getIngestionKey>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
},
|
||||
): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
|
||||
const queryOptions = getGetIngestionKeyQueryOptions({ keyId }, options);
|
||||
|
||||
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
|
||||
queryKey: QueryKey;
|
||||
};
|
||||
|
||||
return { ...query, queryKey: queryOptions.queryKey };
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Get ingestion key for workspace
|
||||
*/
|
||||
export const invalidateGetIngestionKey = async (
|
||||
queryClient: QueryClient,
|
||||
{ keyId }: GetIngestionKeyPathParameters,
|
||||
options?: InvalidateOptions,
|
||||
): Promise<QueryClient> => {
|
||||
await queryClient.invalidateQueries(
|
||||
{ queryKey: getGetIngestionKeyQueryKey({ keyId }) },
|
||||
options,
|
||||
);
|
||||
|
||||
return queryClient;
|
||||
};
|
||||
|
||||
/**
|
||||
* This endpoint updates an ingestion key for the workspace
|
||||
* @summary Update ingestion key for workspace
|
||||
@@ -399,20 +511,123 @@ export const useUpdateIngestionKey = <
|
||||
> => {
|
||||
return useMutation(getUpdateIngestionKeyMutationOptions(options));
|
||||
};
|
||||
/**
|
||||
* This endpoint returns the ingestion limits for an ingestion key
|
||||
* @summary Get limits for the ingestion key
|
||||
*/
|
||||
export const getIngestionKeyLimits = (
|
||||
{ keyId }: GetIngestionKeyLimitsPathParameters,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<GetIngestionKeyLimits200>({
|
||||
url: `/api/v2/gateway/ingestion_keys/${keyId}/limits`,
|
||||
method: 'GET',
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getGetIngestionKeyLimitsQueryKey = ({
|
||||
keyId,
|
||||
}: GetIngestionKeyLimitsPathParameters) => {
|
||||
return [`/api/v2/gateway/ingestion_keys/${keyId}/limits`] as const;
|
||||
};
|
||||
|
||||
export const getGetIngestionKeyLimitsQueryOptions = <
|
||||
TData = Awaited<ReturnType<typeof getIngestionKeyLimits>>,
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
>(
|
||||
{ keyId }: GetIngestionKeyLimitsPathParameters,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getIngestionKeyLimits>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
},
|
||||
) => {
|
||||
const { query: queryOptions } = options ?? {};
|
||||
|
||||
const queryKey =
|
||||
queryOptions?.queryKey ?? getGetIngestionKeyLimitsQueryKey({ keyId });
|
||||
|
||||
const queryFn: QueryFunction<
|
||||
Awaited<ReturnType<typeof getIngestionKeyLimits>>
|
||||
> = ({ signal }) => getIngestionKeyLimits({ keyId }, signal);
|
||||
|
||||
return {
|
||||
queryKey,
|
||||
queryFn,
|
||||
enabled: !!keyId,
|
||||
...queryOptions,
|
||||
} as UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getIngestionKeyLimits>>,
|
||||
TError,
|
||||
TData
|
||||
> & { queryKey: QueryKey };
|
||||
};
|
||||
|
||||
export type GetIngestionKeyLimitsQueryResult = NonNullable<
|
||||
Awaited<ReturnType<typeof getIngestionKeyLimits>>
|
||||
>;
|
||||
export type GetIngestionKeyLimitsQueryError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary Get limits for the ingestion key
|
||||
*/
|
||||
|
||||
export function useGetIngestionKeyLimits<
|
||||
TData = Awaited<ReturnType<typeof getIngestionKeyLimits>>,
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
>(
|
||||
{ keyId }: GetIngestionKeyLimitsPathParameters,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getIngestionKeyLimits>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
},
|
||||
): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
|
||||
const queryOptions = getGetIngestionKeyLimitsQueryOptions({ keyId }, options);
|
||||
|
||||
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
|
||||
queryKey: QueryKey;
|
||||
};
|
||||
|
||||
return { ...query, queryKey: queryOptions.queryKey };
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Get limits for the ingestion key
|
||||
*/
|
||||
export const invalidateGetIngestionKeyLimits = async (
|
||||
queryClient: QueryClient,
|
||||
{ keyId }: GetIngestionKeyLimitsPathParameters,
|
||||
options?: InvalidateOptions,
|
||||
): Promise<QueryClient> => {
|
||||
await queryClient.invalidateQueries(
|
||||
{ queryKey: getGetIngestionKeyLimitsQueryKey({ keyId }) },
|
||||
options,
|
||||
);
|
||||
|
||||
return queryClient;
|
||||
};
|
||||
|
||||
/**
|
||||
* This endpoint creates an ingestion key limit
|
||||
* @deprecated
|
||||
* @summary Create limit for the ingestion key
|
||||
*/
|
||||
export const createIngestionKeyLimit = (
|
||||
{ keyId }: CreateIngestionKeyLimitPathParameters,
|
||||
gatewaytypesPostableIngestionKeyLimitDTO?: BodyType<GatewaytypesPostableIngestionKeyLimitDTO>,
|
||||
gatewaytypesDeprecatedPostableIngestionKeyLimitDTO?: BodyType<GatewaytypesDeprecatedPostableIngestionKeyLimitDTO>,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<CreateIngestionKeyLimit201>({
|
||||
url: `/api/v2/gateway/ingestion_keys/${keyId}/limits`,
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
data: gatewaytypesPostableIngestionKeyLimitDTO,
|
||||
data: gatewaytypesDeprecatedPostableIngestionKeyLimitDTO,
|
||||
signal,
|
||||
});
|
||||
};
|
||||
@@ -426,7 +641,7 @@ export const getCreateIngestionKeyLimitMutationOptions = <
|
||||
TError,
|
||||
{
|
||||
pathParams: CreateIngestionKeyLimitPathParameters;
|
||||
data?: BodyType<GatewaytypesPostableIngestionKeyLimitDTO>;
|
||||
data?: BodyType<GatewaytypesDeprecatedPostableIngestionKeyLimitDTO>;
|
||||
},
|
||||
TContext
|
||||
>;
|
||||
@@ -435,7 +650,7 @@ export const getCreateIngestionKeyLimitMutationOptions = <
|
||||
TError,
|
||||
{
|
||||
pathParams: CreateIngestionKeyLimitPathParameters;
|
||||
data?: BodyType<GatewaytypesPostableIngestionKeyLimitDTO>;
|
||||
data?: BodyType<GatewaytypesDeprecatedPostableIngestionKeyLimitDTO>;
|
||||
},
|
||||
TContext
|
||||
> => {
|
||||
@@ -452,7 +667,7 @@ export const getCreateIngestionKeyLimitMutationOptions = <
|
||||
Awaited<ReturnType<typeof createIngestionKeyLimit>>,
|
||||
{
|
||||
pathParams: CreateIngestionKeyLimitPathParameters;
|
||||
data?: BodyType<GatewaytypesPostableIngestionKeyLimitDTO>;
|
||||
data?: BodyType<GatewaytypesDeprecatedPostableIngestionKeyLimitDTO>;
|
||||
}
|
||||
> = (props) => {
|
||||
const { pathParams, data } = props ?? {};
|
||||
@@ -467,12 +682,13 @@ export type CreateIngestionKeyLimitMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof createIngestionKeyLimit>>
|
||||
>;
|
||||
export type CreateIngestionKeyLimitMutationBody =
|
||||
| BodyType<GatewaytypesPostableIngestionKeyLimitDTO>
|
||||
| BodyType<GatewaytypesDeprecatedPostableIngestionKeyLimitDTO>
|
||||
| undefined;
|
||||
export type CreateIngestionKeyLimitMutationError =
|
||||
ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @summary Create limit for the ingestion key
|
||||
*/
|
||||
export const useCreateIngestionKeyLimit = <
|
||||
@@ -484,7 +700,7 @@ export const useCreateIngestionKeyLimit = <
|
||||
TError,
|
||||
{
|
||||
pathParams: CreateIngestionKeyLimitPathParameters;
|
||||
data?: BodyType<GatewaytypesPostableIngestionKeyLimitDTO>;
|
||||
data?: BodyType<GatewaytypesDeprecatedPostableIngestionKeyLimitDTO>;
|
||||
},
|
||||
TContext
|
||||
>;
|
||||
@@ -493,7 +709,7 @@ export const useCreateIngestionKeyLimit = <
|
||||
TError,
|
||||
{
|
||||
pathParams: CreateIngestionKeyLimitPathParameters;
|
||||
data?: BodyType<GatewaytypesPostableIngestionKeyLimitDTO>;
|
||||
data?: BodyType<GatewaytypesDeprecatedPostableIngestionKeyLimitDTO>;
|
||||
},
|
||||
TContext
|
||||
> => {
|
||||
@@ -501,6 +717,7 @@ export const useCreateIngestionKeyLimit = <
|
||||
};
|
||||
/**
|
||||
* This endpoint deletes an ingestion key limit
|
||||
* @deprecated
|
||||
* @summary Delete limit for the ingestion key
|
||||
*/
|
||||
export const deleteIngestionKeyLimit = (
|
||||
@@ -559,6 +776,7 @@ export type DeleteIngestionKeyLimitMutationError =
|
||||
ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @summary Delete limit for the ingestion key
|
||||
*/
|
||||
export const useDeleteIngestionKeyLimit = <
|
||||
@@ -581,6 +799,7 @@ export const useDeleteIngestionKeyLimit = <
|
||||
};
|
||||
/**
|
||||
* This endpoint updates an ingestion key limit
|
||||
* @deprecated
|
||||
* @summary Update limit for the ingestion key
|
||||
*/
|
||||
export const updateIngestionKeyLimit = (
|
||||
@@ -653,6 +872,7 @@ export type UpdateIngestionKeyLimitMutationError =
|
||||
ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @summary Update limit for the ingestion key
|
||||
*/
|
||||
export const useUpdateIngestionKeyLimit = <
|
||||
@@ -779,3 +999,370 @@ export const invalidateSearchIngestionKeys = async (
|
||||
|
||||
return queryClient;
|
||||
};
|
||||
|
||||
/**
|
||||
* This endpoint creates an ingestion limit for the ingestion key referenced by keyId
|
||||
* @summary Create ingestion limit
|
||||
*/
|
||||
export const createIngestionLimit = (
|
||||
gatewaytypesPostableIngestionKeyLimitDTO?: BodyType<GatewaytypesPostableIngestionKeyLimitDTO>,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<CreateIngestionLimit201>({
|
||||
url: `/api/v2/gateway/ingestion_limits`,
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
data: gatewaytypesPostableIngestionKeyLimitDTO,
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getCreateIngestionLimitMutationOptions = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof createIngestionLimit>>,
|
||||
TError,
|
||||
{ data?: BodyType<GatewaytypesPostableIngestionKeyLimitDTO> },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof createIngestionLimit>>,
|
||||
TError,
|
||||
{ data?: BodyType<GatewaytypesPostableIngestionKeyLimitDTO> },
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ['createIngestionLimit'];
|
||||
const { mutation: mutationOptions } = options
|
||||
? options.mutation &&
|
||||
'mutationKey' in options.mutation &&
|
||||
options.mutation.mutationKey
|
||||
? options
|
||||
: { ...options, mutation: { ...options.mutation, mutationKey } }
|
||||
: { mutation: { mutationKey } };
|
||||
|
||||
const mutationFn: MutationFunction<
|
||||
Awaited<ReturnType<typeof createIngestionLimit>>,
|
||||
{ data?: BodyType<GatewaytypesPostableIngestionKeyLimitDTO> }
|
||||
> = (props) => {
|
||||
const { data } = props ?? {};
|
||||
|
||||
return createIngestionLimit(data);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type CreateIngestionLimitMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof createIngestionLimit>>
|
||||
>;
|
||||
export type CreateIngestionLimitMutationBody =
|
||||
| BodyType<GatewaytypesPostableIngestionKeyLimitDTO>
|
||||
| undefined;
|
||||
export type CreateIngestionLimitMutationError =
|
||||
ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary Create ingestion limit
|
||||
*/
|
||||
export const useCreateIngestionLimit = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof createIngestionLimit>>,
|
||||
TError,
|
||||
{ data?: BodyType<GatewaytypesPostableIngestionKeyLimitDTO> },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof createIngestionLimit>>,
|
||||
TError,
|
||||
{ data?: BodyType<GatewaytypesPostableIngestionKeyLimitDTO> },
|
||||
TContext
|
||||
> => {
|
||||
return useMutation(getCreateIngestionLimitMutationOptions(options));
|
||||
};
|
||||
/**
|
||||
* This endpoint deletes an ingestion limit
|
||||
* @summary Delete ingestion limit
|
||||
*/
|
||||
export const deleteIngestionLimit = (
|
||||
{ limitId }: DeleteIngestionLimitPathParameters,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<void>({
|
||||
url: `/api/v2/gateway/ingestion_limits/${limitId}`,
|
||||
method: 'DELETE',
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getDeleteIngestionLimitMutationOptions = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof deleteIngestionLimit>>,
|
||||
TError,
|
||||
{ pathParams: DeleteIngestionLimitPathParameters },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof deleteIngestionLimit>>,
|
||||
TError,
|
||||
{ pathParams: DeleteIngestionLimitPathParameters },
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ['deleteIngestionLimit'];
|
||||
const { mutation: mutationOptions } = options
|
||||
? options.mutation &&
|
||||
'mutationKey' in options.mutation &&
|
||||
options.mutation.mutationKey
|
||||
? options
|
||||
: { ...options, mutation: { ...options.mutation, mutationKey } }
|
||||
: { mutation: { mutationKey } };
|
||||
|
||||
const mutationFn: MutationFunction<
|
||||
Awaited<ReturnType<typeof deleteIngestionLimit>>,
|
||||
{ pathParams: DeleteIngestionLimitPathParameters }
|
||||
> = (props) => {
|
||||
const { pathParams } = props ?? {};
|
||||
|
||||
return deleteIngestionLimit(pathParams);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type DeleteIngestionLimitMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof deleteIngestionLimit>>
|
||||
>;
|
||||
|
||||
export type DeleteIngestionLimitMutationError =
|
||||
ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary Delete ingestion limit
|
||||
*/
|
||||
export const useDeleteIngestionLimit = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof deleteIngestionLimit>>,
|
||||
TError,
|
||||
{ pathParams: DeleteIngestionLimitPathParameters },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof deleteIngestionLimit>>,
|
||||
TError,
|
||||
{ pathParams: DeleteIngestionLimitPathParameters },
|
||||
TContext
|
||||
> => {
|
||||
return useMutation(getDeleteIngestionLimitMutationOptions(options));
|
||||
};
|
||||
/**
|
||||
* This endpoint returns an ingestion limit
|
||||
* @summary Get ingestion limit
|
||||
*/
|
||||
export const getIngestionLimit = (
|
||||
{ limitId }: GetIngestionLimitPathParameters,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<GetIngestionLimit200>({
|
||||
url: `/api/v2/gateway/ingestion_limits/${limitId}`,
|
||||
method: 'GET',
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getGetIngestionLimitQueryKey = ({
|
||||
limitId,
|
||||
}: GetIngestionLimitPathParameters) => {
|
||||
return [`/api/v2/gateway/ingestion_limits/${limitId}`] as const;
|
||||
};
|
||||
|
||||
export const getGetIngestionLimitQueryOptions = <
|
||||
TData = Awaited<ReturnType<typeof getIngestionLimit>>,
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
>(
|
||||
{ limitId }: GetIngestionLimitPathParameters,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getIngestionLimit>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
},
|
||||
) => {
|
||||
const { query: queryOptions } = options ?? {};
|
||||
|
||||
const queryKey =
|
||||
queryOptions?.queryKey ?? getGetIngestionLimitQueryKey({ limitId });
|
||||
|
||||
const queryFn: QueryFunction<
|
||||
Awaited<ReturnType<typeof getIngestionLimit>>
|
||||
> = ({ signal }) => getIngestionLimit({ limitId }, signal);
|
||||
|
||||
return {
|
||||
queryKey,
|
||||
queryFn,
|
||||
enabled: !!limitId,
|
||||
...queryOptions,
|
||||
} as UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getIngestionLimit>>,
|
||||
TError,
|
||||
TData
|
||||
> & { queryKey: QueryKey };
|
||||
};
|
||||
|
||||
export type GetIngestionLimitQueryResult = NonNullable<
|
||||
Awaited<ReturnType<typeof getIngestionLimit>>
|
||||
>;
|
||||
export type GetIngestionLimitQueryError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary Get ingestion limit
|
||||
*/
|
||||
|
||||
export function useGetIngestionLimit<
|
||||
TData = Awaited<ReturnType<typeof getIngestionLimit>>,
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
>(
|
||||
{ limitId }: GetIngestionLimitPathParameters,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getIngestionLimit>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
},
|
||||
): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
|
||||
const queryOptions = getGetIngestionLimitQueryOptions({ limitId }, options);
|
||||
|
||||
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
|
||||
queryKey: QueryKey;
|
||||
};
|
||||
|
||||
return { ...query, queryKey: queryOptions.queryKey };
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Get ingestion limit
|
||||
*/
|
||||
export const invalidateGetIngestionLimit = async (
|
||||
queryClient: QueryClient,
|
||||
{ limitId }: GetIngestionLimitPathParameters,
|
||||
options?: InvalidateOptions,
|
||||
): Promise<QueryClient> => {
|
||||
await queryClient.invalidateQueries(
|
||||
{ queryKey: getGetIngestionLimitQueryKey({ limitId }) },
|
||||
options,
|
||||
);
|
||||
|
||||
return queryClient;
|
||||
};
|
||||
|
||||
/**
|
||||
* This endpoint updates an ingestion limit
|
||||
* @summary Update ingestion limit
|
||||
*/
|
||||
export const updateIngestionLimit = (
|
||||
{ limitId }: UpdateIngestionLimitPathParameters,
|
||||
gatewaytypesUpdatableIngestionKeyLimitDTO?: BodyType<GatewaytypesUpdatableIngestionKeyLimitDTO>,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<void>({
|
||||
url: `/api/v2/gateway/ingestion_limits/${limitId}`,
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
data: gatewaytypesUpdatableIngestionKeyLimitDTO,
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getUpdateIngestionLimitMutationOptions = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof updateIngestionLimit>>,
|
||||
TError,
|
||||
{
|
||||
pathParams: UpdateIngestionLimitPathParameters;
|
||||
data?: BodyType<GatewaytypesUpdatableIngestionKeyLimitDTO>;
|
||||
},
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof updateIngestionLimit>>,
|
||||
TError,
|
||||
{
|
||||
pathParams: UpdateIngestionLimitPathParameters;
|
||||
data?: BodyType<GatewaytypesUpdatableIngestionKeyLimitDTO>;
|
||||
},
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ['updateIngestionLimit'];
|
||||
const { mutation: mutationOptions } = options
|
||||
? options.mutation &&
|
||||
'mutationKey' in options.mutation &&
|
||||
options.mutation.mutationKey
|
||||
? options
|
||||
: { ...options, mutation: { ...options.mutation, mutationKey } }
|
||||
: { mutation: { mutationKey } };
|
||||
|
||||
const mutationFn: MutationFunction<
|
||||
Awaited<ReturnType<typeof updateIngestionLimit>>,
|
||||
{
|
||||
pathParams: UpdateIngestionLimitPathParameters;
|
||||
data?: BodyType<GatewaytypesUpdatableIngestionKeyLimitDTO>;
|
||||
}
|
||||
> = (props) => {
|
||||
const { pathParams, data } = props ?? {};
|
||||
|
||||
return updateIngestionLimit(pathParams, data);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type UpdateIngestionLimitMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof updateIngestionLimit>>
|
||||
>;
|
||||
export type UpdateIngestionLimitMutationBody =
|
||||
| BodyType<GatewaytypesUpdatableIngestionKeyLimitDTO>
|
||||
| undefined;
|
||||
export type UpdateIngestionLimitMutationError =
|
||||
ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary Update ingestion limit
|
||||
*/
|
||||
export const useUpdateIngestionLimit = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof updateIngestionLimit>>,
|
||||
TError,
|
||||
{
|
||||
pathParams: UpdateIngestionLimitPathParameters;
|
||||
data?: BodyType<GatewaytypesUpdatableIngestionKeyLimitDTO>;
|
||||
},
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof updateIngestionLimit>>,
|
||||
TError,
|
||||
{
|
||||
pathParams: UpdateIngestionLimitPathParameters;
|
||||
data?: BodyType<GatewaytypesUpdatableIngestionKeyLimitDTO>;
|
||||
},
|
||||
TContext
|
||||
> => {
|
||||
return useMutation(getUpdateIngestionLimitMutationOptions(options));
|
||||
};
|
||||
|
||||
702
frontend/src/api/generated/services/licenses/index.ts
Normal file
702
frontend/src/api/generated/services/licenses/index.ts
Normal file
@@ -0,0 +1,702 @@
|
||||
/**
|
||||
* ! Do not edit manually
|
||||
* * The file has been auto-generated using Orval for SigNoz
|
||||
* * regenerate with 'pnpm generate:api'
|
||||
* SigNoz
|
||||
*/
|
||||
import { useMutation, useQuery } from 'react-query';
|
||||
import type {
|
||||
InvalidateOptions,
|
||||
MutationFunction,
|
||||
QueryClient,
|
||||
QueryFunction,
|
||||
QueryKey,
|
||||
UseMutationOptions,
|
||||
UseMutationResult,
|
||||
UseQueryOptions,
|
||||
UseQueryResult,
|
||||
} from 'react-query';
|
||||
|
||||
import type {
|
||||
ActivateLicense201,
|
||||
DeleteLicensePathParameters,
|
||||
GetActiveLicense200,
|
||||
GetLicense200,
|
||||
GetLicensePathParameters,
|
||||
LicensetypesPostableLicenseDTO,
|
||||
ListLicenses200,
|
||||
RefreshLicensePathParameters,
|
||||
RenderErrorResponseDTO,
|
||||
} from '../sigNoz.schemas';
|
||||
|
||||
import { GeneratedAPIInstance } from '../../../generatedAPIInstance';
|
||||
import type { ErrorType, BodyType } from '../../../generatedAPIInstance';
|
||||
|
||||
/**
|
||||
* This endpoint validates the license key with the upstream server and activates the license for the organization.
|
||||
* @deprecated
|
||||
* @summary Activate a license.
|
||||
*/
|
||||
export const activateLicenseDeprecated = (
|
||||
licensetypesPostableLicenseDTO?: BodyType<LicensetypesPostableLicenseDTO>,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<void>({
|
||||
url: `/api/v3/licenses`,
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
data: licensetypesPostableLicenseDTO,
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getActivateLicenseDeprecatedMutationOptions = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof activateLicenseDeprecated>>,
|
||||
TError,
|
||||
{ data?: BodyType<LicensetypesPostableLicenseDTO> },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof activateLicenseDeprecated>>,
|
||||
TError,
|
||||
{ data?: BodyType<LicensetypesPostableLicenseDTO> },
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ['activateLicenseDeprecated'];
|
||||
const { mutation: mutationOptions } = options
|
||||
? options.mutation &&
|
||||
'mutationKey' in options.mutation &&
|
||||
options.mutation.mutationKey
|
||||
? options
|
||||
: { ...options, mutation: { ...options.mutation, mutationKey } }
|
||||
: { mutation: { mutationKey } };
|
||||
|
||||
const mutationFn: MutationFunction<
|
||||
Awaited<ReturnType<typeof activateLicenseDeprecated>>,
|
||||
{ data?: BodyType<LicensetypesPostableLicenseDTO> }
|
||||
> = (props) => {
|
||||
const { data } = props ?? {};
|
||||
|
||||
return activateLicenseDeprecated(data);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type ActivateLicenseDeprecatedMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof activateLicenseDeprecated>>
|
||||
>;
|
||||
export type ActivateLicenseDeprecatedMutationBody =
|
||||
| BodyType<LicensetypesPostableLicenseDTO>
|
||||
| undefined;
|
||||
export type ActivateLicenseDeprecatedMutationError =
|
||||
ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @summary Activate a license.
|
||||
*/
|
||||
export const useActivateLicenseDeprecated = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof activateLicenseDeprecated>>,
|
||||
TError,
|
||||
{ data?: BodyType<LicensetypesPostableLicenseDTO> },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof activateLicenseDeprecated>>,
|
||||
TError,
|
||||
{ data?: BodyType<LicensetypesPostableLicenseDTO> },
|
||||
TContext
|
||||
> => {
|
||||
return useMutation(getActivateLicenseDeprecatedMutationOptions(options));
|
||||
};
|
||||
/**
|
||||
* This endpoint refreshes the active license of the organization from the upstream server.
|
||||
* @deprecated
|
||||
* @summary Refresh a license.
|
||||
*/
|
||||
export const refreshLicenseDeprecated = (signal?: AbortSignal) => {
|
||||
return GeneratedAPIInstance<void>({
|
||||
url: `/api/v3/licenses`,
|
||||
method: 'PUT',
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getRefreshLicenseDeprecatedMutationOptions = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof refreshLicenseDeprecated>>,
|
||||
TError,
|
||||
void,
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof refreshLicenseDeprecated>>,
|
||||
TError,
|
||||
void,
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ['refreshLicenseDeprecated'];
|
||||
const { mutation: mutationOptions } = options
|
||||
? options.mutation &&
|
||||
'mutationKey' in options.mutation &&
|
||||
options.mutation.mutationKey
|
||||
? options
|
||||
: { ...options, mutation: { ...options.mutation, mutationKey } }
|
||||
: { mutation: { mutationKey } };
|
||||
|
||||
const mutationFn: MutationFunction<
|
||||
Awaited<ReturnType<typeof refreshLicenseDeprecated>>,
|
||||
void
|
||||
> = () => {
|
||||
return refreshLicenseDeprecated();
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type RefreshLicenseDeprecatedMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof refreshLicenseDeprecated>>
|
||||
>;
|
||||
|
||||
export type RefreshLicenseDeprecatedMutationError =
|
||||
ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @summary Refresh a license.
|
||||
*/
|
||||
export const useRefreshLicenseDeprecated = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof refreshLicenseDeprecated>>,
|
||||
TError,
|
||||
void,
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof refreshLicenseDeprecated>>,
|
||||
TError,
|
||||
void,
|
||||
TContext
|
||||
> => {
|
||||
return useMutation(getRefreshLicenseDeprecatedMutationOptions(options));
|
||||
};
|
||||
/**
|
||||
* This endpoint lists all the licenses of the organization.
|
||||
* @summary List licenses.
|
||||
*/
|
||||
export const listLicenses = (signal?: AbortSignal) => {
|
||||
return GeneratedAPIInstance<ListLicenses200>({
|
||||
url: `/api/v4/licenses`,
|
||||
method: 'GET',
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getListLicensesQueryKey = () => {
|
||||
return [`/api/v4/licenses`] as const;
|
||||
};
|
||||
|
||||
export const getListLicensesQueryOptions = <
|
||||
TData = Awaited<ReturnType<typeof listLicenses>>,
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
>(options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof listLicenses>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
}) => {
|
||||
const { query: queryOptions } = options ?? {};
|
||||
|
||||
const queryKey = queryOptions?.queryKey ?? getListLicensesQueryKey();
|
||||
|
||||
const queryFn: QueryFunction<Awaited<ReturnType<typeof listLicenses>>> = ({
|
||||
signal,
|
||||
}) => listLicenses(signal);
|
||||
|
||||
return { queryKey, queryFn, ...queryOptions } as UseQueryOptions<
|
||||
Awaited<ReturnType<typeof listLicenses>>,
|
||||
TError,
|
||||
TData
|
||||
> & { queryKey: QueryKey };
|
||||
};
|
||||
|
||||
export type ListLicensesQueryResult = NonNullable<
|
||||
Awaited<ReturnType<typeof listLicenses>>
|
||||
>;
|
||||
export type ListLicensesQueryError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary List licenses.
|
||||
*/
|
||||
|
||||
export function useListLicenses<
|
||||
TData = Awaited<ReturnType<typeof listLicenses>>,
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
>(options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof listLicenses>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
}): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
|
||||
const queryOptions = getListLicensesQueryOptions(options);
|
||||
|
||||
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
|
||||
queryKey: QueryKey;
|
||||
};
|
||||
|
||||
return { ...query, queryKey: queryOptions.queryKey };
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary List licenses.
|
||||
*/
|
||||
export const invalidateListLicenses = async (
|
||||
queryClient: QueryClient,
|
||||
options?: InvalidateOptions,
|
||||
): Promise<QueryClient> => {
|
||||
await queryClient.invalidateQueries(
|
||||
{ queryKey: getListLicensesQueryKey() },
|
||||
options,
|
||||
);
|
||||
|
||||
return queryClient;
|
||||
};
|
||||
|
||||
/**
|
||||
* This endpoint validates the license key with the upstream server and activates the license for the organization.
|
||||
* @summary Activate a license.
|
||||
*/
|
||||
export const activateLicense = (
|
||||
licensetypesPostableLicenseDTO?: BodyType<LicensetypesPostableLicenseDTO>,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<ActivateLicense201>({
|
||||
url: `/api/v4/licenses`,
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
data: licensetypesPostableLicenseDTO,
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getActivateLicenseMutationOptions = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof activateLicense>>,
|
||||
TError,
|
||||
{ data?: BodyType<LicensetypesPostableLicenseDTO> },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof activateLicense>>,
|
||||
TError,
|
||||
{ data?: BodyType<LicensetypesPostableLicenseDTO> },
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ['activateLicense'];
|
||||
const { mutation: mutationOptions } = options
|
||||
? options.mutation &&
|
||||
'mutationKey' in options.mutation &&
|
||||
options.mutation.mutationKey
|
||||
? options
|
||||
: { ...options, mutation: { ...options.mutation, mutationKey } }
|
||||
: { mutation: { mutationKey } };
|
||||
|
||||
const mutationFn: MutationFunction<
|
||||
Awaited<ReturnType<typeof activateLicense>>,
|
||||
{ data?: BodyType<LicensetypesPostableLicenseDTO> }
|
||||
> = (props) => {
|
||||
const { data } = props ?? {};
|
||||
|
||||
return activateLicense(data);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type ActivateLicenseMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof activateLicense>>
|
||||
>;
|
||||
export type ActivateLicenseMutationBody =
|
||||
| BodyType<LicensetypesPostableLicenseDTO>
|
||||
| undefined;
|
||||
export type ActivateLicenseMutationError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary Activate a license.
|
||||
*/
|
||||
export const useActivateLicense = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof activateLicense>>,
|
||||
TError,
|
||||
{ data?: BodyType<LicensetypesPostableLicenseDTO> },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof activateLicense>>,
|
||||
TError,
|
||||
{ data?: BodyType<LicensetypesPostableLicenseDTO> },
|
||||
TContext
|
||||
> => {
|
||||
return useMutation(getActivateLicenseMutationOptions(options));
|
||||
};
|
||||
/**
|
||||
* This endpoint deletes the license by id. Licenses managed by SigNoz Cloud cannot be deleted.
|
||||
* @summary Delete a license.
|
||||
*/
|
||||
export const deleteLicense = (
|
||||
{ id }: DeleteLicensePathParameters,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<void>({
|
||||
url: `/api/v4/licenses/${id}`,
|
||||
method: 'DELETE',
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getDeleteLicenseMutationOptions = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof deleteLicense>>,
|
||||
TError,
|
||||
{ pathParams: DeleteLicensePathParameters },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof deleteLicense>>,
|
||||
TError,
|
||||
{ pathParams: DeleteLicensePathParameters },
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ['deleteLicense'];
|
||||
const { mutation: mutationOptions } = options
|
||||
? options.mutation &&
|
||||
'mutationKey' in options.mutation &&
|
||||
options.mutation.mutationKey
|
||||
? options
|
||||
: { ...options, mutation: { ...options.mutation, mutationKey } }
|
||||
: { mutation: { mutationKey } };
|
||||
|
||||
const mutationFn: MutationFunction<
|
||||
Awaited<ReturnType<typeof deleteLicense>>,
|
||||
{ pathParams: DeleteLicensePathParameters }
|
||||
> = (props) => {
|
||||
const { pathParams } = props ?? {};
|
||||
|
||||
return deleteLicense(pathParams);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type DeleteLicenseMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof deleteLicense>>
|
||||
>;
|
||||
|
||||
export type DeleteLicenseMutationError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary Delete a license.
|
||||
*/
|
||||
export const useDeleteLicense = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof deleteLicense>>,
|
||||
TError,
|
||||
{ pathParams: DeleteLicensePathParameters },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof deleteLicense>>,
|
||||
TError,
|
||||
{ pathParams: DeleteLicensePathParameters },
|
||||
TContext
|
||||
> => {
|
||||
return useMutation(getDeleteLicenseMutationOptions(options));
|
||||
};
|
||||
/**
|
||||
* This endpoint gets the license by id.
|
||||
* @summary Get a license.
|
||||
*/
|
||||
export const getLicense = (
|
||||
{ id }: GetLicensePathParameters,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<GetLicense200>({
|
||||
url: `/api/v4/licenses/${id}`,
|
||||
method: 'GET',
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getGetLicenseQueryKey = ({ id }: GetLicensePathParameters) => {
|
||||
return [`/api/v4/licenses/${id}`] as const;
|
||||
};
|
||||
|
||||
export const getGetLicenseQueryOptions = <
|
||||
TData = Awaited<ReturnType<typeof getLicense>>,
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
>(
|
||||
{ id }: GetLicensePathParameters,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getLicense>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
},
|
||||
) => {
|
||||
const { query: queryOptions } = options ?? {};
|
||||
|
||||
const queryKey = queryOptions?.queryKey ?? getGetLicenseQueryKey({ id });
|
||||
|
||||
const queryFn: QueryFunction<Awaited<ReturnType<typeof getLicense>>> = ({
|
||||
signal,
|
||||
}) => getLicense({ id }, signal);
|
||||
|
||||
return {
|
||||
queryKey,
|
||||
queryFn,
|
||||
enabled: !!id,
|
||||
...queryOptions,
|
||||
} as UseQueryOptions<Awaited<ReturnType<typeof getLicense>>, TError, TData> & {
|
||||
queryKey: QueryKey;
|
||||
};
|
||||
};
|
||||
|
||||
export type GetLicenseQueryResult = NonNullable<
|
||||
Awaited<ReturnType<typeof getLicense>>
|
||||
>;
|
||||
export type GetLicenseQueryError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary Get a license.
|
||||
*/
|
||||
|
||||
export function useGetLicense<
|
||||
TData = Awaited<ReturnType<typeof getLicense>>,
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
>(
|
||||
{ id }: GetLicensePathParameters,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getLicense>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
},
|
||||
): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
|
||||
const queryOptions = getGetLicenseQueryOptions({ id }, options);
|
||||
|
||||
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
|
||||
queryKey: QueryKey;
|
||||
};
|
||||
|
||||
return { ...query, queryKey: queryOptions.queryKey };
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Get a license.
|
||||
*/
|
||||
export const invalidateGetLicense = async (
|
||||
queryClient: QueryClient,
|
||||
{ id }: GetLicensePathParameters,
|
||||
options?: InvalidateOptions,
|
||||
): Promise<QueryClient> => {
|
||||
await queryClient.invalidateQueries(
|
||||
{ queryKey: getGetLicenseQueryKey({ id }) },
|
||||
options,
|
||||
);
|
||||
|
||||
return queryClient;
|
||||
};
|
||||
|
||||
/**
|
||||
* This endpoint refreshes the active license of the organization from the upstream server.
|
||||
* @summary Refresh a license.
|
||||
*/
|
||||
export const refreshLicense = (
|
||||
{ id }: RefreshLicensePathParameters,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<void>({
|
||||
url: `/api/v4/licenses/${id}`,
|
||||
method: 'PUT',
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getRefreshLicenseMutationOptions = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof refreshLicense>>,
|
||||
TError,
|
||||
{ pathParams: RefreshLicensePathParameters },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof refreshLicense>>,
|
||||
TError,
|
||||
{ pathParams: RefreshLicensePathParameters },
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ['refreshLicense'];
|
||||
const { mutation: mutationOptions } = options
|
||||
? options.mutation &&
|
||||
'mutationKey' in options.mutation &&
|
||||
options.mutation.mutationKey
|
||||
? options
|
||||
: { ...options, mutation: { ...options.mutation, mutationKey } }
|
||||
: { mutation: { mutationKey } };
|
||||
|
||||
const mutationFn: MutationFunction<
|
||||
Awaited<ReturnType<typeof refreshLicense>>,
|
||||
{ pathParams: RefreshLicensePathParameters }
|
||||
> = (props) => {
|
||||
const { pathParams } = props ?? {};
|
||||
|
||||
return refreshLicense(pathParams);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type RefreshLicenseMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof refreshLicense>>
|
||||
>;
|
||||
|
||||
export type RefreshLicenseMutationError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary Refresh a license.
|
||||
*/
|
||||
export const useRefreshLicense = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof refreshLicense>>,
|
||||
TError,
|
||||
{ pathParams: RefreshLicensePathParameters },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof refreshLicense>>,
|
||||
TError,
|
||||
{ pathParams: RefreshLicensePathParameters },
|
||||
TContext
|
||||
> => {
|
||||
return useMutation(getRefreshLicenseMutationOptions(options));
|
||||
};
|
||||
/**
|
||||
* This endpoint gets the active license of the organization.
|
||||
* @summary Get the active license.
|
||||
*/
|
||||
export const getActiveLicense = (signal?: AbortSignal) => {
|
||||
return GeneratedAPIInstance<GetActiveLicense200>({
|
||||
url: `/api/v4/licenses/active`,
|
||||
method: 'GET',
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getGetActiveLicenseQueryKey = () => {
|
||||
return [`/api/v4/licenses/active`] as const;
|
||||
};
|
||||
|
||||
export const getGetActiveLicenseQueryOptions = <
|
||||
TData = Awaited<ReturnType<typeof getActiveLicense>>,
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
>(options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getActiveLicense>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
}) => {
|
||||
const { query: queryOptions } = options ?? {};
|
||||
|
||||
const queryKey = queryOptions?.queryKey ?? getGetActiveLicenseQueryKey();
|
||||
|
||||
const queryFn: QueryFunction<Awaited<ReturnType<typeof getActiveLicense>>> = ({
|
||||
signal,
|
||||
}) => getActiveLicense(signal);
|
||||
|
||||
return { queryKey, queryFn, ...queryOptions } as UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getActiveLicense>>,
|
||||
TError,
|
||||
TData
|
||||
> & { queryKey: QueryKey };
|
||||
};
|
||||
|
||||
export type GetActiveLicenseQueryResult = NonNullable<
|
||||
Awaited<ReturnType<typeof getActiveLicense>>
|
||||
>;
|
||||
export type GetActiveLicenseQueryError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary Get the active license.
|
||||
*/
|
||||
|
||||
export function useGetActiveLicense<
|
||||
TData = Awaited<ReturnType<typeof getActiveLicense>>,
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
>(options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getActiveLicense>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
}): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
|
||||
const queryOptions = getGetActiveLicenseQueryOptions(options);
|
||||
|
||||
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
|
||||
queryKey: QueryKey;
|
||||
};
|
||||
|
||||
return { ...query, queryKey: queryOptions.queryKey };
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Get the active license.
|
||||
*/
|
||||
export const invalidateGetActiveLicense = async (
|
||||
queryClient: QueryClient,
|
||||
options?: InvalidateOptions,
|
||||
): Promise<QueryClient> => {
|
||||
await queryClient.invalidateQueries(
|
||||
{ queryKey: getGetActiveLicenseQueryKey() },
|
||||
options,
|
||||
);
|
||||
|
||||
return queryClient;
|
||||
};
|
||||
316
frontend/src/api/generated/services/quick-filter/index.ts
Normal file
316
frontend/src/api/generated/services/quick-filter/index.ts
Normal file
@@ -0,0 +1,316 @@
|
||||
/**
|
||||
* ! Do not edit manually
|
||||
* * The file has been auto-generated using Orval for SigNoz
|
||||
* * regenerate with 'pnpm generate:api'
|
||||
* SigNoz
|
||||
*/
|
||||
import { useMutation, useQuery } from 'react-query';
|
||||
import type {
|
||||
InvalidateOptions,
|
||||
MutationFunction,
|
||||
QueryClient,
|
||||
QueryFunction,
|
||||
QueryKey,
|
||||
UseMutationOptions,
|
||||
UseMutationResult,
|
||||
UseQueryOptions,
|
||||
UseQueryResult,
|
||||
} from 'react-query';
|
||||
|
||||
import type {
|
||||
GetQuickFilters200,
|
||||
GetQuickFiltersPathParameters,
|
||||
ListQuickFilters200,
|
||||
QuickfiltertypesUpdatableQuickFiltersDTO,
|
||||
RenderErrorResponseDTO,
|
||||
UpdateQuickFiltersPathParameters,
|
||||
} from '../sigNoz.schemas';
|
||||
|
||||
import { GeneratedAPIInstance } from '../../../generatedAPIInstance';
|
||||
import type { ErrorType, BodyType } from '../../../generatedAPIInstance';
|
||||
|
||||
/**
|
||||
* Returns the org's quick filters for every source, each filter as a telemetry field key.
|
||||
* @summary List quick filters
|
||||
*/
|
||||
export const listQuickFilters = (signal?: AbortSignal) => {
|
||||
return GeneratedAPIInstance<ListQuickFilters200>({
|
||||
url: `/api/v2/quick_filters`,
|
||||
method: 'GET',
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getListQuickFiltersQueryKey = () => {
|
||||
return [`/api/v2/quick_filters`] as const;
|
||||
};
|
||||
|
||||
export const getListQuickFiltersQueryOptions = <
|
||||
TData = Awaited<ReturnType<typeof listQuickFilters>>,
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
>(options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof listQuickFilters>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
}) => {
|
||||
const { query: queryOptions } = options ?? {};
|
||||
|
||||
const queryKey = queryOptions?.queryKey ?? getListQuickFiltersQueryKey();
|
||||
|
||||
const queryFn: QueryFunction<Awaited<ReturnType<typeof listQuickFilters>>> = ({
|
||||
signal,
|
||||
}) => listQuickFilters(signal);
|
||||
|
||||
return { queryKey, queryFn, ...queryOptions } as UseQueryOptions<
|
||||
Awaited<ReturnType<typeof listQuickFilters>>,
|
||||
TError,
|
||||
TData
|
||||
> & { queryKey: QueryKey };
|
||||
};
|
||||
|
||||
export type ListQuickFiltersQueryResult = NonNullable<
|
||||
Awaited<ReturnType<typeof listQuickFilters>>
|
||||
>;
|
||||
export type ListQuickFiltersQueryError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary List quick filters
|
||||
*/
|
||||
|
||||
export function useListQuickFilters<
|
||||
TData = Awaited<ReturnType<typeof listQuickFilters>>,
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
>(options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof listQuickFilters>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
}): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
|
||||
const queryOptions = getListQuickFiltersQueryOptions(options);
|
||||
|
||||
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
|
||||
queryKey: QueryKey;
|
||||
};
|
||||
|
||||
return { ...query, queryKey: queryOptions.queryKey };
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary List quick filters
|
||||
*/
|
||||
export const invalidateListQuickFilters = async (
|
||||
queryClient: QueryClient,
|
||||
options?: InvalidateOptions,
|
||||
): Promise<QueryClient> => {
|
||||
await queryClient.invalidateQueries(
|
||||
{ queryKey: getListQuickFiltersQueryKey() },
|
||||
options,
|
||||
);
|
||||
|
||||
return queryClient;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the org's quick filters for one source, each filter as a telemetry field key.
|
||||
* @summary Get a source's quick filters
|
||||
*/
|
||||
export const getQuickFilters = (
|
||||
{ source }: GetQuickFiltersPathParameters,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<GetQuickFilters200>({
|
||||
url: `/api/v2/quick_filters/${source}`,
|
||||
method: 'GET',
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getGetQuickFiltersQueryKey = ({
|
||||
source,
|
||||
}: GetQuickFiltersPathParameters) => {
|
||||
return [`/api/v2/quick_filters/${source}`] as const;
|
||||
};
|
||||
|
||||
export const getGetQuickFiltersQueryOptions = <
|
||||
TData = Awaited<ReturnType<typeof getQuickFilters>>,
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
>(
|
||||
{ source }: GetQuickFiltersPathParameters,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getQuickFilters>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
},
|
||||
) => {
|
||||
const { query: queryOptions } = options ?? {};
|
||||
|
||||
const queryKey =
|
||||
queryOptions?.queryKey ?? getGetQuickFiltersQueryKey({ source });
|
||||
|
||||
const queryFn: QueryFunction<Awaited<ReturnType<typeof getQuickFilters>>> = ({
|
||||
signal,
|
||||
}) => getQuickFilters({ source }, signal);
|
||||
|
||||
return {
|
||||
queryKey,
|
||||
queryFn,
|
||||
enabled: !!source,
|
||||
...queryOptions,
|
||||
} as UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getQuickFilters>>,
|
||||
TError,
|
||||
TData
|
||||
> & { queryKey: QueryKey };
|
||||
};
|
||||
|
||||
export type GetQuickFiltersQueryResult = NonNullable<
|
||||
Awaited<ReturnType<typeof getQuickFilters>>
|
||||
>;
|
||||
export type GetQuickFiltersQueryError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary Get a source's quick filters
|
||||
*/
|
||||
|
||||
export function useGetQuickFilters<
|
||||
TData = Awaited<ReturnType<typeof getQuickFilters>>,
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
>(
|
||||
{ source }: GetQuickFiltersPathParameters,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getQuickFilters>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
},
|
||||
): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
|
||||
const queryOptions = getGetQuickFiltersQueryOptions({ source }, options);
|
||||
|
||||
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
|
||||
queryKey: QueryKey;
|
||||
};
|
||||
|
||||
return { ...query, queryKey: queryOptions.queryKey };
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Get a source's quick filters
|
||||
*/
|
||||
export const invalidateGetQuickFilters = async (
|
||||
queryClient: QueryClient,
|
||||
{ source }: GetQuickFiltersPathParameters,
|
||||
options?: InvalidateOptions,
|
||||
): Promise<QueryClient> => {
|
||||
await queryClient.invalidateQueries(
|
||||
{ queryKey: getGetQuickFiltersQueryKey({ source }) },
|
||||
options,
|
||||
);
|
||||
|
||||
return queryClient;
|
||||
};
|
||||
|
||||
/**
|
||||
* Replaces the org's quick filters for the source named in the path.
|
||||
* @summary Update quick filters
|
||||
*/
|
||||
export const updateQuickFilters = (
|
||||
{ source }: UpdateQuickFiltersPathParameters,
|
||||
quickfiltertypesUpdatableQuickFiltersDTO?: BodyType<QuickfiltertypesUpdatableQuickFiltersDTO>,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<void>({
|
||||
url: `/api/v2/quick_filters/${source}`,
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
data: quickfiltertypesUpdatableQuickFiltersDTO,
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getUpdateQuickFiltersMutationOptions = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof updateQuickFilters>>,
|
||||
TError,
|
||||
{
|
||||
pathParams: UpdateQuickFiltersPathParameters;
|
||||
data?: BodyType<QuickfiltertypesUpdatableQuickFiltersDTO>;
|
||||
},
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof updateQuickFilters>>,
|
||||
TError,
|
||||
{
|
||||
pathParams: UpdateQuickFiltersPathParameters;
|
||||
data?: BodyType<QuickfiltertypesUpdatableQuickFiltersDTO>;
|
||||
},
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ['updateQuickFilters'];
|
||||
const { mutation: mutationOptions } = options
|
||||
? options.mutation &&
|
||||
'mutationKey' in options.mutation &&
|
||||
options.mutation.mutationKey
|
||||
? options
|
||||
: { ...options, mutation: { ...options.mutation, mutationKey } }
|
||||
: { mutation: { mutationKey } };
|
||||
|
||||
const mutationFn: MutationFunction<
|
||||
Awaited<ReturnType<typeof updateQuickFilters>>,
|
||||
{
|
||||
pathParams: UpdateQuickFiltersPathParameters;
|
||||
data?: BodyType<QuickfiltertypesUpdatableQuickFiltersDTO>;
|
||||
}
|
||||
> = (props) => {
|
||||
const { pathParams, data } = props ?? {};
|
||||
|
||||
return updateQuickFilters(pathParams, data);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type UpdateQuickFiltersMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof updateQuickFilters>>
|
||||
>;
|
||||
export type UpdateQuickFiltersMutationBody =
|
||||
| BodyType<QuickfiltertypesUpdatableQuickFiltersDTO>
|
||||
| undefined;
|
||||
export type UpdateQuickFiltersMutationError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary Update quick filters
|
||||
*/
|
||||
export const useUpdateQuickFilters = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof updateQuickFilters>>,
|
||||
TError,
|
||||
{
|
||||
pathParams: UpdateQuickFiltersPathParameters;
|
||||
data?: BodyType<QuickfiltertypesUpdatableQuickFiltersDTO>;
|
||||
},
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof updateQuickFilters>>,
|
||||
TError,
|
||||
{
|
||||
pathParams: UpdateQuickFiltersPathParameters;
|
||||
data?: BodyType<QuickfiltertypesUpdatableQuickFiltersDTO>;
|
||||
},
|
||||
TContext
|
||||
> => {
|
||||
return useMutation(getUpdateQuickFiltersMutationOptions(options));
|
||||
};
|
||||
@@ -385,6 +385,38 @@ export interface AlertmanagertypesGoogleChatReceiverConfigDTO {
|
||||
webhook_url?: ConfigSecretURLDTO;
|
||||
}
|
||||
|
||||
export type AlertmanagertypesIncidentIOReceiverConfigDTOMetadata = {
|
||||
[key: string]: string;
|
||||
};
|
||||
|
||||
export interface AlertmanagertypesIncidentIOReceiverConfigDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
description?: string;
|
||||
http_config?: ConfigHTTPClientConfigDTO;
|
||||
/**
|
||||
* @type object
|
||||
*/
|
||||
metadata?: AlertmanagertypesIncidentIOReceiverConfigDTOMetadata;
|
||||
/**
|
||||
* @type boolean
|
||||
*/
|
||||
send_resolved?: boolean;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
title?: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
token?: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
url?: string;
|
||||
}
|
||||
|
||||
export interface AlertmanagertypesJSMOpsReceiverConfigDTO {
|
||||
/**
|
||||
* @type string
|
||||
@@ -685,39 +717,6 @@ export interface ConfigEmailConfigDTO {
|
||||
to?: string;
|
||||
}
|
||||
|
||||
export type TimeDurationDTO = number;
|
||||
|
||||
export interface ConfigURLType2DTO {
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export interface ConfigIncidentioConfigDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
alert_source_token?: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
alert_source_token_file?: string;
|
||||
http_config?: ConfigHTTPClientConfigDTO;
|
||||
/**
|
||||
* @type integer
|
||||
* @minimum 0
|
||||
*/
|
||||
max_alerts?: number;
|
||||
/**
|
||||
* @type boolean
|
||||
*/
|
||||
send_resolved?: boolean;
|
||||
timeout?: TimeDurationDTO;
|
||||
url?: ConfigURLType2DTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
url_file?: string;
|
||||
}
|
||||
|
||||
export interface ConfigMattermostFieldDTO {
|
||||
/**
|
||||
* @type boolean,null
|
||||
@@ -903,6 +902,10 @@ export interface ConfigMSTeamsV2ConfigDTO {
|
||||
webhook_url_file?: string;
|
||||
}
|
||||
|
||||
export interface ConfigURLType2DTO {
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export interface ConfigOpsGenieConfigResponderDTO {
|
||||
/**
|
||||
* @type string
|
||||
@@ -1011,6 +1014,8 @@ export interface ConfigPagerdutyLinkDTO {
|
||||
text?: string;
|
||||
}
|
||||
|
||||
export type TimeDurationDTO = number;
|
||||
|
||||
export type ConfigPagerdutyConfigDTODetails = { [key: string]: unknown };
|
||||
|
||||
export interface ConfigPagerdutyConfigDTO {
|
||||
@@ -1672,7 +1677,7 @@ export type AlertmanagertypesPostableChannelDTO = unknown & {
|
||||
/**
|
||||
* @type array
|
||||
*/
|
||||
incidentio_configs?: ConfigIncidentioConfigDTO[];
|
||||
incidentio_configs?: AlertmanagertypesIncidentIOReceiverConfigDTO[];
|
||||
/**
|
||||
* @type array
|
||||
*/
|
||||
@@ -1803,7 +1808,7 @@ export interface AlertmanagertypesReceiverDTO {
|
||||
/**
|
||||
* @type array
|
||||
*/
|
||||
incidentio_configs?: ConfigIncidentioConfigDTO[];
|
||||
incidentio_configs?: AlertmanagertypesIncidentIOReceiverConfigDTO[];
|
||||
/**
|
||||
* @type array
|
||||
*/
|
||||
@@ -3300,6 +3305,33 @@ export interface CommonJSONRefDTO {
|
||||
$ref?: string;
|
||||
}
|
||||
|
||||
export interface ConfigIncidentioConfigDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
alert_source_token?: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
alert_source_token_file?: string;
|
||||
http_config?: ConfigHTTPClientConfigDTO;
|
||||
/**
|
||||
* @type integer
|
||||
* @minimum 0
|
||||
*/
|
||||
max_alerts?: number;
|
||||
/**
|
||||
* @type boolean
|
||||
*/
|
||||
send_resolved?: boolean;
|
||||
timeout?: TimeDurationDTO;
|
||||
url?: ConfigURLType2DTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
url_file?: string;
|
||||
}
|
||||
|
||||
export type ConfigJiraConfigDTOCustomFields = { [key: string]: unknown };
|
||||
|
||||
export interface ConfigJiraFieldConfigDTO {
|
||||
@@ -5461,6 +5493,34 @@ export interface FeaturetypesGettableFeatureDTO {
|
||||
variants?: FeaturetypesGettableFeatureDTOVariants;
|
||||
}
|
||||
|
||||
export interface GatewaytypesLimitValueDTO {
|
||||
/**
|
||||
* @type integer,null
|
||||
*/
|
||||
count?: number | null;
|
||||
/**
|
||||
* @type integer,null
|
||||
*/
|
||||
size?: number | null;
|
||||
}
|
||||
|
||||
export interface GatewaytypesLimitConfigDTO {
|
||||
day?: GatewaytypesLimitValueDTO;
|
||||
second?: GatewaytypesLimitValueDTO;
|
||||
}
|
||||
|
||||
export interface GatewaytypesDeprecatedPostableIngestionKeyLimitDTO {
|
||||
config?: GatewaytypesLimitConfigDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
signal?: string;
|
||||
/**
|
||||
* @type array,null
|
||||
*/
|
||||
tags?: string[] | null;
|
||||
}
|
||||
|
||||
export interface GatewaytypesGettableCreatedIngestionKeyDTO {
|
||||
/**
|
||||
* @type string
|
||||
@@ -5498,22 +5558,6 @@ export interface GatewaytypesPaginationDTO {
|
||||
total?: number;
|
||||
}
|
||||
|
||||
export interface GatewaytypesLimitValueDTO {
|
||||
/**
|
||||
* @type integer,null
|
||||
*/
|
||||
count?: number | null;
|
||||
/**
|
||||
* @type integer,null
|
||||
*/
|
||||
size?: number | null;
|
||||
}
|
||||
|
||||
export interface GatewaytypesLimitConfigDTO {
|
||||
day?: GatewaytypesLimitValueDTO;
|
||||
second?: GatewaytypesLimitValueDTO;
|
||||
}
|
||||
|
||||
export interface GatewaytypesLimitMetricValueDTO {
|
||||
/**
|
||||
* @type integer
|
||||
@@ -5631,6 +5675,10 @@ export interface GatewaytypesPostableIngestionKeyDTO {
|
||||
|
||||
export interface GatewaytypesPostableIngestionKeyLimitDTO {
|
||||
config?: GatewaytypesLimitConfigDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
keyId: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
@@ -7313,6 +7361,249 @@ export interface InframonitoringtypesVolumesDTO {
|
||||
warning?: Querybuildertypesv5QueryWarnDataDTO;
|
||||
}
|
||||
|
||||
export interface LicensetypesFeatureDTO {
|
||||
/**
|
||||
* @type boolean
|
||||
*/
|
||||
active?: boolean;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
name?: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
route?: string;
|
||||
/**
|
||||
* @type integer
|
||||
* @format int64
|
||||
*/
|
||||
usage?: number;
|
||||
/**
|
||||
* @type integer
|
||||
* @format int64
|
||||
*/
|
||||
usage_limit?: number;
|
||||
}
|
||||
|
||||
export interface LicensetypesLicenseEventQueueDTO {
|
||||
/**
|
||||
* @type string
|
||||
* @format date-time
|
||||
*/
|
||||
createdAt: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
event: string;
|
||||
/**
|
||||
* @type string
|
||||
* @format date-time
|
||||
*/
|
||||
scheduledAt: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
status: string;
|
||||
/**
|
||||
* @type string
|
||||
* @format date-time
|
||||
*/
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export interface LicensetypesLicensePlanDTO {
|
||||
/**
|
||||
* @type string
|
||||
* @format date-time
|
||||
*/
|
||||
createdAt: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
description: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* @type boolean
|
||||
*/
|
||||
isActive: boolean;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* @type string
|
||||
* @format date-time
|
||||
*/
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export interface LicensetypesGettableActiveLicenseDTO {
|
||||
/**
|
||||
* @type string
|
||||
* @format date-time
|
||||
*/
|
||||
createdAt: string;
|
||||
eventQueue: LicensetypesLicenseEventQueueDTO;
|
||||
/**
|
||||
* @type array
|
||||
*/
|
||||
features: LicensetypesFeatureDTO[];
|
||||
/**
|
||||
* @type string
|
||||
* @format date-time
|
||||
*/
|
||||
freeUntil: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
id: string;
|
||||
plan: LicensetypesLicensePlanDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
platform: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
state: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
status: string;
|
||||
/**
|
||||
* @type string
|
||||
* @format date-time
|
||||
*/
|
||||
updatedAt: string;
|
||||
/**
|
||||
* @type integer
|
||||
* @format int64
|
||||
*/
|
||||
validFrom: number;
|
||||
/**
|
||||
* @type integer
|
||||
* @format int64
|
||||
*/
|
||||
validUntil: number;
|
||||
}
|
||||
|
||||
export interface LicensetypesGettableLicenseDTO {
|
||||
/**
|
||||
* @type string
|
||||
* @format date-time
|
||||
*/
|
||||
createdAt: string;
|
||||
eventQueue: LicensetypesLicenseEventQueueDTO;
|
||||
/**
|
||||
* @type array
|
||||
*/
|
||||
features: LicensetypesFeatureDTO[];
|
||||
/**
|
||||
* @type string
|
||||
* @format date-time
|
||||
*/
|
||||
freeUntil: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
id: string;
|
||||
plan: LicensetypesLicensePlanDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
platform: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
state: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
status: string;
|
||||
/**
|
||||
* @type string
|
||||
* @format date-time
|
||||
*/
|
||||
updatedAt: string;
|
||||
/**
|
||||
* @type integer
|
||||
* @format int64
|
||||
*/
|
||||
validFrom: number;
|
||||
/**
|
||||
* @type integer
|
||||
* @format int64
|
||||
*/
|
||||
validUntil: number;
|
||||
}
|
||||
|
||||
export interface LicensetypesGettableLicenseWithKeyDTO {
|
||||
/**
|
||||
* @type string
|
||||
* @format date-time
|
||||
*/
|
||||
createdAt: string;
|
||||
eventQueue: LicensetypesLicenseEventQueueDTO;
|
||||
/**
|
||||
* @type array
|
||||
*/
|
||||
features: LicensetypesFeatureDTO[];
|
||||
/**
|
||||
* @type string
|
||||
* @format date-time
|
||||
*/
|
||||
freeUntil: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* @type string
|
||||
* @format password
|
||||
*/
|
||||
key: string;
|
||||
plan: LicensetypesLicensePlanDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
platform: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
state: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
status: string;
|
||||
/**
|
||||
* @type string
|
||||
* @format date-time
|
||||
*/
|
||||
updatedAt: string;
|
||||
/**
|
||||
* @type integer
|
||||
* @format int64
|
||||
*/
|
||||
validFrom: number;
|
||||
/**
|
||||
* @type integer
|
||||
* @format int64
|
||||
*/
|
||||
validUntil: number;
|
||||
}
|
||||
|
||||
export interface LicensetypesPostableLicenseDTO {
|
||||
/**
|
||||
* @type string
|
||||
* @format password
|
||||
*/
|
||||
key?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @nullable
|
||||
*/
|
||||
@@ -8735,6 +9026,47 @@ export enum Querybuildertypesv5QueryTypeDTO {
|
||||
clickhouse_sql = 'clickhouse_sql',
|
||||
promql = 'promql',
|
||||
}
|
||||
export enum QuickfiltertypesSourceDTO {
|
||||
traces = 'traces',
|
||||
logs = 'logs',
|
||||
api_monitoring = 'api_monitoring',
|
||||
exceptions = 'exceptions',
|
||||
meter = 'meter',
|
||||
ai_observability = 'ai_observability',
|
||||
}
|
||||
export interface QuickfiltertypesSourceFiltersDTO {
|
||||
/**
|
||||
* @type string
|
||||
* @format date-time
|
||||
*/
|
||||
createdAt?: string;
|
||||
/**
|
||||
* @type array
|
||||
*/
|
||||
filters: TelemetrytypesTelemetryFieldKeyDTO[];
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
orgId: string;
|
||||
source: QuickfiltertypesSourceDTO;
|
||||
/**
|
||||
* @type string
|
||||
* @format date-time
|
||||
*/
|
||||
updatedAt?: string;
|
||||
}
|
||||
|
||||
export interface QuickfiltertypesUpdatableQuickFiltersDTO {
|
||||
/**
|
||||
* @type array
|
||||
*/
|
||||
filters: TelemetrytypesTelemetryFieldKeyDTO[];
|
||||
}
|
||||
|
||||
export interface RenderErrorResponseDTO {
|
||||
error: ErrorsJSONDTO;
|
||||
/**
|
||||
@@ -10547,6 +10879,11 @@ export type GetAIObservabilityFieldsValuesParams = {
|
||||
* @description undefined
|
||||
*/
|
||||
name?: string;
|
||||
/**
|
||||
* @type string
|
||||
* @description undefined
|
||||
*/
|
||||
existingQuery?: string;
|
||||
};
|
||||
|
||||
export type GetAIObservabilityFieldsValues200 = {
|
||||
@@ -11665,9 +12002,34 @@ export type CreateIngestionKey201 = {
|
||||
export type DeleteIngestionKeyPathParameters = {
|
||||
keyId: string;
|
||||
};
|
||||
export type GetIngestionKeyPathParameters = {
|
||||
keyId: string;
|
||||
};
|
||||
export type GetIngestionKey200 = {
|
||||
data: GatewaytypesIngestionKeyDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type UpdateIngestionKeyPathParameters = {
|
||||
keyId: string;
|
||||
};
|
||||
export type GetIngestionKeyLimitsPathParameters = {
|
||||
keyId: string;
|
||||
};
|
||||
export type GetIngestionKeyLimits200 = {
|
||||
/**
|
||||
* @type array,null
|
||||
*/
|
||||
data: GatewaytypesLimitDTO[] | null;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type CreateIngestionKeyLimitPathParameters = {
|
||||
keyId: string;
|
||||
};
|
||||
@@ -11711,6 +12073,31 @@ export type SearchIngestionKeys200 = {
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type CreateIngestionLimit201 = {
|
||||
data: TypesIdentifiableDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type DeleteIngestionLimitPathParameters = {
|
||||
limitId: string;
|
||||
};
|
||||
export type GetIngestionLimitPathParameters = {
|
||||
limitId: string;
|
||||
};
|
||||
export type GetIngestionLimit200 = {
|
||||
data: GatewaytypesLimitDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type UpdateIngestionLimitPathParameters = {
|
||||
limitId: string;
|
||||
};
|
||||
export type Healthz200 = {
|
||||
data: FactoryResponseDTO;
|
||||
/**
|
||||
@@ -12136,6 +12523,31 @@ export type GetPublicDashboardPanelQueryRangeV2200 = {
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type ListQuickFilters200 = {
|
||||
/**
|
||||
* @type array
|
||||
*/
|
||||
data: QuickfiltertypesSourceFiltersDTO[];
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type GetQuickFiltersPathParameters = {
|
||||
source: string;
|
||||
};
|
||||
export type GetQuickFilters200 = {
|
||||
data: QuickfiltertypesSourceFiltersDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type UpdateQuickFiltersPathParameters = {
|
||||
source: string;
|
||||
};
|
||||
export type Readyz200 = {
|
||||
data: FactoryResponseDTO;
|
||||
/**
|
||||
@@ -12738,6 +13150,50 @@ export type GetFlamegraph200 = {
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type ListLicenses200 = {
|
||||
/**
|
||||
* @type array
|
||||
*/
|
||||
data: LicensetypesGettableLicenseDTO[];
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type ActivateLicense201 = {
|
||||
data: TypesIdentifiableDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type DeleteLicensePathParameters = {
|
||||
id: string;
|
||||
};
|
||||
export type GetLicensePathParameters = {
|
||||
id: string;
|
||||
};
|
||||
export type GetLicense200 = {
|
||||
data: LicensetypesGettableLicenseWithKeyDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type RefreshLicensePathParameters = {
|
||||
id: string;
|
||||
};
|
||||
export type GetActiveLicense200 = {
|
||||
data: LicensetypesGettableActiveLicenseDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type GetWaterfallV4PathParameters = {
|
||||
traceID: string;
|
||||
};
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/quickFilters/getCustomFilters';
|
||||
|
||||
const getCustomFilters = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
const { signal } = props;
|
||||
try {
|
||||
const response = await axios.get(`/orgs/me/filters/${signal}`);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: 'Success',
|
||||
payload: response.data.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default getCustomFilters;
|
||||
@@ -1,13 +0,0 @@
|
||||
import axios from 'api';
|
||||
import { AxiosError } from 'axios';
|
||||
import { SuccessResponse } from 'types/api';
|
||||
import { UpdateCustomFiltersProps } from 'types/api/quickFilters/updateCustomFilters';
|
||||
|
||||
const updateCustomFiltersAPI = async (
|
||||
props: UpdateCustomFiltersProps,
|
||||
): Promise<SuccessResponse<void> | AxiosError> =>
|
||||
axios.put(`/orgs/me/filters`, {
|
||||
...props.data,
|
||||
});
|
||||
|
||||
export default updateCustomFiltersAPI;
|
||||
@@ -1,25 +0,0 @@
|
||||
import { ApiV3Instance as axios } from 'api';
|
||||
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
||||
import {
|
||||
LicenseEventQueueResModel,
|
||||
PayloadProps,
|
||||
} from 'types/api/licensesV3/getActive';
|
||||
|
||||
const getActive = async (): Promise<
|
||||
SuccessResponseV2<LicenseEventQueueResModel>
|
||||
> => {
|
||||
try {
|
||||
const response = await axios.get<PayloadProps>('/licenses/active');
|
||||
|
||||
return {
|
||||
httpStatusCode: response.status,
|
||||
data: response.data.data,
|
||||
};
|
||||
} catch (error) {
|
||||
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
|
||||
}
|
||||
};
|
||||
|
||||
export default getActive;
|
||||
@@ -1,24 +0,0 @@
|
||||
import { ApiV3Instance as axios } from 'api';
|
||||
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/licenses/apply';
|
||||
|
||||
const apply = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponseV2<PayloadProps>> => {
|
||||
try {
|
||||
const response = await axios.post<PayloadProps>('/licenses', {
|
||||
key: props.key,
|
||||
});
|
||||
|
||||
return {
|
||||
httpStatusCode: response.status,
|
||||
data: response.data,
|
||||
};
|
||||
} catch (error) {
|
||||
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
|
||||
}
|
||||
};
|
||||
|
||||
export default apply;
|
||||
@@ -1,20 +0,0 @@
|
||||
import { ApiV3Instance as axios } from 'api';
|
||||
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
||||
import { PayloadProps } from 'types/api/licenses/apply';
|
||||
|
||||
const apply = async (): Promise<SuccessResponseV2<PayloadProps>> => {
|
||||
try {
|
||||
const response = await axios.put<PayloadProps>('/licenses');
|
||||
|
||||
return {
|
||||
httpStatusCode: response.status,
|
||||
data: response.data,
|
||||
};
|
||||
} catch (error) {
|
||||
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
|
||||
}
|
||||
};
|
||||
|
||||
export default apply;
|
||||
@@ -57,8 +57,8 @@ function MenuItemGenerator({
|
||||
|
||||
handleExplorerTabChange(currentPanelType, {
|
||||
query,
|
||||
name,
|
||||
id,
|
||||
viewName: name,
|
||||
viewKey: id,
|
||||
});
|
||||
},
|
||||
[viewData, handleExplorerTabChange],
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Input } from '@signozhq/ui/input';
|
||||
import { Skeleton } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import { LoaderCircle } from '@signozhq/icons';
|
||||
import { TelemetrytypesSourceDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import {
|
||||
IQuickFiltersConfig,
|
||||
QuickFilterChangeEventData,
|
||||
@@ -74,6 +75,10 @@ export default function CheckboxFilterV2(
|
||||
searchText,
|
||||
existingQuery,
|
||||
metricNamespace: useFieldApis.metricNamespace,
|
||||
source:
|
||||
source === QuickFiltersSource.METER_EXPLORER
|
||||
? TelemetrytypesSourceDTO.meter
|
||||
: undefined,
|
||||
startUnixMilli: useFieldApis.startUnixMilli,
|
||||
endUnixMilli: useFieldApis.endUnixMilli,
|
||||
enabled: isOpen,
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useGetFieldsValues } from 'api/generated/services/fields';
|
||||
import { TelemetrytypesSignalDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import {
|
||||
TelemetrytypesSignalDTO,
|
||||
TelemetrytypesSourceDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import { IQuickFiltersConfig } from 'components/QuickFilters/types';
|
||||
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
import { FIELD_API_CACHE_TIME } from 'constants/queryCacheTime';
|
||||
|
||||
@@ -10,6 +14,7 @@ interface UseFieldValuesProps {
|
||||
searchText: string;
|
||||
existingQuery?: string;
|
||||
metricNamespace?: string;
|
||||
source?: TelemetrytypesSourceDTO;
|
||||
startUnixMilli?: number;
|
||||
endUnixMilli?: number;
|
||||
enabled: boolean;
|
||||
@@ -33,6 +38,7 @@ export function useFieldValues({
|
||||
searchText,
|
||||
existingQuery,
|
||||
metricNamespace,
|
||||
source,
|
||||
startUnixMilli,
|
||||
endUnixMilli,
|
||||
enabled,
|
||||
@@ -46,6 +52,7 @@ export function useFieldValues({
|
||||
searchText,
|
||||
existingQuery,
|
||||
metricNamespace,
|
||||
source,
|
||||
startUnixMilli,
|
||||
// This field does not affect the backend but I wanted to keep it here
|
||||
// in case we add the support in the future
|
||||
@@ -75,6 +82,12 @@ export function useFieldValues({
|
||||
}, [data]);
|
||||
|
||||
const allValues: string[] = useMemo(() => {
|
||||
// Bool fields should always offer true/false.
|
||||
// The values api returns nothing for them.
|
||||
if (filter.attributeKey.dataType === DataTypes.bool) {
|
||||
return ['true', 'false'];
|
||||
}
|
||||
|
||||
const values = data?.data?.values;
|
||||
if (!values) {
|
||||
return [];
|
||||
@@ -91,7 +104,7 @@ export function useFieldValues({
|
||||
.map((value) => value.toString()) || [];
|
||||
|
||||
return [...stringValues, ...numberValues];
|
||||
}, [data]);
|
||||
}, [data, filter.attributeKey.dataType]);
|
||||
|
||||
return { relatedValues, allValues, isLoading, isFetching };
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ import { CSS } from '@dnd-kit/utilities';
|
||||
import { Button } from 'antd';
|
||||
import OverlayScrollbar from 'components/OverlayScrollbar/OverlayScrollbar';
|
||||
import { GripVertical } from '@signozhq/icons';
|
||||
import { Filter as FilterType } from 'types/api/quickFilters/getCustomFilters';
|
||||
import { TelemetryFieldKey } from 'types/api/v5/queryRange';
|
||||
|
||||
function SortableFilter({
|
||||
filter,
|
||||
@@ -25,13 +25,13 @@ function SortableFilter({
|
||||
allowDrag,
|
||||
allowRemove,
|
||||
}: {
|
||||
filter: FilterType;
|
||||
onRemove: (filter: FilterType) => void;
|
||||
filter: TelemetryFieldKey;
|
||||
onRemove: (filter: TelemetryFieldKey) => void;
|
||||
allowDrag: boolean;
|
||||
allowRemove: boolean;
|
||||
}): JSX.Element {
|
||||
const { attributes, listeners, setNodeRef, transform, transition } =
|
||||
useSortable({ id: filter.key });
|
||||
useSortable({ id: filter.key as string });
|
||||
|
||||
const style = {
|
||||
transform: CSS.Transform.toString(transform),
|
||||
@@ -46,14 +46,14 @@ function SortableFilter({
|
||||
>
|
||||
<div {...attributes} {...listeners} className="drag-handle">
|
||||
{allowDrag && <GripVertical size={16} />}
|
||||
{filter.key}
|
||||
{filter.name}
|
||||
</div>
|
||||
{allowRemove && (
|
||||
<Button
|
||||
className="remove-filter-btn periscope-btn"
|
||||
size="small"
|
||||
onClick={(): void => {
|
||||
onRemove(filter as FilterType);
|
||||
onRemove(filter);
|
||||
}}
|
||||
>
|
||||
Remove
|
||||
@@ -69,8 +69,8 @@ function AddedFilters({
|
||||
setAddedFilters,
|
||||
}: {
|
||||
inputValue: string;
|
||||
addedFilters: FilterType[];
|
||||
setAddedFilters: React.Dispatch<React.SetStateAction<FilterType[]>>;
|
||||
addedFilters: TelemetryFieldKey[];
|
||||
setAddedFilters: React.Dispatch<React.SetStateAction<TelemetryFieldKey[]>>;
|
||||
}): JSX.Element {
|
||||
const sensors = useSensors(useSensor(PointerSensor));
|
||||
|
||||
@@ -90,12 +90,12 @@ function AddedFilters({
|
||||
const filteredAddedFilters = useMemo(
|
||||
() =>
|
||||
addedFilters.filter((filter) =>
|
||||
filter.key.toLowerCase().includes(inputValue.toLowerCase()),
|
||||
filter.name.toLowerCase().includes(inputValue.toLowerCase()),
|
||||
),
|
||||
[addedFilters, inputValue],
|
||||
);
|
||||
|
||||
const handleRemoveFilter = (filter: FilterType): void => {
|
||||
const handleRemoveFilter = (filter: TelemetryFieldKey): void => {
|
||||
setAddedFilters((prev) => prev.filter((f) => f.key !== filter.key));
|
||||
};
|
||||
|
||||
@@ -116,7 +116,7 @@ function AddedFilters({
|
||||
<div className="no-values-found">No values found</div>
|
||||
) : (
|
||||
<SortableContext
|
||||
items={addedFilters.map((f) => f.key)}
|
||||
items={addedFilters.map((f) => f.key as string)}
|
||||
strategy={verticalListSortingStrategy}
|
||||
disabled={!allowDrag}
|
||||
>
|
||||
|
||||
@@ -4,14 +4,9 @@ import OverlayScrollbar from 'components/OverlayScrollbar/OverlayScrollbar';
|
||||
import { SIGNAL_DATA_SOURCE_MAP } from 'components/QuickFilters/QuickFiltersSettings/constants';
|
||||
import { SignalType } from 'components/QuickFilters/types';
|
||||
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
||||
import { useGetAggregateKeys } from 'hooks/queryBuilder/useGetAggregateKeys';
|
||||
import { useGetAttributeSuggestions } from 'hooks/queryBuilder/useGetAttributeSuggestions';
|
||||
import { buildCompositeKey } from 'container/OptionsMenu/utils';
|
||||
import { useGetQueryKeySuggestions } from 'hooks/querySuggestions/useGetQueryKeySuggestions';
|
||||
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { QueryKeyDataSuggestionsProps } from 'types/api/querySuggestions/types';
|
||||
import { Filter as FilterType } from 'types/api/quickFilters/getCustomFilters';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
import { FieldContext, TelemetryFieldKey } from 'types/api/v5/queryRange';
|
||||
|
||||
function OtherFiltersSkeleton(): JSX.Element {
|
||||
return (
|
||||
@@ -37,106 +32,49 @@ function OtherFilters({
|
||||
}: {
|
||||
signal: SignalType | undefined;
|
||||
inputValue: string;
|
||||
addedFilters: FilterType[];
|
||||
setAddedFilters: React.Dispatch<React.SetStateAction<FilterType[]>>;
|
||||
addedFilters: TelemetryFieldKey[];
|
||||
setAddedFilters: React.Dispatch<React.SetStateAction<TelemetryFieldKey[]>>;
|
||||
}): JSX.Element {
|
||||
const isLogDataSource = useMemo(
|
||||
() => SIGNAL_DATA_SOURCE_MAP[signal as SignalType] === DataSource.LOGS,
|
||||
[signal],
|
||||
);
|
||||
const isMeterDataSource = useMemo(
|
||||
() => signal && signal === SignalType.METER_EXPLORER,
|
||||
[signal],
|
||||
const isMeterDataSource = signal === SignalType.METER_EXPLORER;
|
||||
|
||||
const { data, isFetching } = useGetQueryKeySuggestions(
|
||||
{
|
||||
searchText: inputValue,
|
||||
signal: SIGNAL_DATA_SOURCE_MAP[signal as SignalType],
|
||||
signalSource: isMeterDataSource ? 'meter' : '',
|
||||
},
|
||||
{
|
||||
queryKey: [REACT_QUERY_KEY.GET_OTHER_FILTERS, signal, inputValue],
|
||||
enabled: !!signal,
|
||||
},
|
||||
);
|
||||
|
||||
const { data: suggestionsData, isFetching: isFetchingSuggestions } =
|
||||
useGetAttributeSuggestions(
|
||||
{
|
||||
searchText: inputValue,
|
||||
dataSource: SIGNAL_DATA_SOURCE_MAP[signal as SignalType],
|
||||
filters: {} as TagFilter,
|
||||
},
|
||||
{
|
||||
queryKey: [REACT_QUERY_KEY.GET_OTHER_FILTERS, inputValue],
|
||||
enabled: !!signal && isLogDataSource,
|
||||
},
|
||||
);
|
||||
const otherFilters = useMemo<TelemetryFieldKey[]>(() => {
|
||||
const rawSuggestions = Object.values(data?.data?.data?.keys || {}).flat();
|
||||
// Normalize: synthesize the composite `key` once so downstream reads (dedupe,
|
||||
// add, render) can trust it.
|
||||
const suggestions: TelemetryFieldKey[] = rawSuggestions.map((attr) => ({
|
||||
name: attr.name,
|
||||
signal: attr.signal,
|
||||
fieldContext: attr.fieldContext as FieldContext,
|
||||
fieldDataType: attr.fieldDataType,
|
||||
key: buildCompositeKey(attr.name, attr.fieldContext, attr.fieldDataType),
|
||||
}));
|
||||
|
||||
const { data: aggregateKeysData, isFetching: isFetchingAggregateKeys } =
|
||||
useGetAggregateKeys(
|
||||
{
|
||||
searchText: inputValue,
|
||||
dataSource: SIGNAL_DATA_SOURCE_MAP[signal as SignalType],
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: '',
|
||||
tagType: '',
|
||||
},
|
||||
{
|
||||
queryKey: [REACT_QUERY_KEY.GET_OTHER_FILTERS, inputValue],
|
||||
enabled: !!signal && !isLogDataSource && !isMeterDataSource,
|
||||
},
|
||||
const addedKeys = new Set(
|
||||
addedFilters.map((filter) =>
|
||||
buildCompositeKey(filter.name, filter.fieldContext, filter.fieldDataType),
|
||||
),
|
||||
);
|
||||
return suggestions.filter((attr) => !addedKeys.has(attr.key as string));
|
||||
}, [data, addedFilters]);
|
||||
|
||||
const { data: fieldKeysData, isLoading: isLoadingFieldKeys } =
|
||||
useGetQueryKeySuggestions(
|
||||
{
|
||||
searchText: inputValue,
|
||||
signal: SIGNAL_DATA_SOURCE_MAP[signal as SignalType],
|
||||
signalSource: 'meter',
|
||||
},
|
||||
{
|
||||
queryKey: [REACT_QUERY_KEY.GET_OTHER_FILTERS, inputValue],
|
||||
enabled: !!signal && isMeterDataSource,
|
||||
},
|
||||
);
|
||||
|
||||
const otherFilters = useMemo(() => {
|
||||
let filterAttributes;
|
||||
if (isLogDataSource) {
|
||||
filterAttributes = suggestionsData?.payload?.attributes || [];
|
||||
} else if (isMeterDataSource) {
|
||||
const fieldKeys: QueryKeyDataSuggestionsProps[] = Object.values(
|
||||
fieldKeysData?.data?.data?.keys || {},
|
||||
)?.flat();
|
||||
filterAttributes = fieldKeys.map(
|
||||
(attr) =>
|
||||
({
|
||||
key: attr.name,
|
||||
dataType: attr.fieldDataType,
|
||||
type: attr.fieldContext,
|
||||
signal: attr.signal,
|
||||
}) as BaseAutocompleteData,
|
||||
);
|
||||
} else {
|
||||
filterAttributes = aggregateKeysData?.payload?.attributeKeys || [];
|
||||
}
|
||||
return filterAttributes?.filter(
|
||||
(attr) => !addedFilters.some((filter) => filter.key === attr.key),
|
||||
);
|
||||
}, [
|
||||
suggestionsData,
|
||||
aggregateKeysData,
|
||||
addedFilters,
|
||||
isLogDataSource,
|
||||
fieldKeysData,
|
||||
isMeterDataSource,
|
||||
]);
|
||||
|
||||
const handleAddFilter = (filter: FilterType): void => {
|
||||
setAddedFilters((prev) => [
|
||||
...prev,
|
||||
{
|
||||
key: filter.key,
|
||||
dataType: filter.dataType,
|
||||
type: filter.type,
|
||||
},
|
||||
]);
|
||||
const handleAddFilter = (filter: TelemetryFieldKey): void => {
|
||||
setAddedFilters((prev) => [...prev, filter]);
|
||||
};
|
||||
|
||||
const renderFilters = (): React.ReactNode => {
|
||||
const isLoading =
|
||||
isFetchingSuggestions || isFetchingAggregateKeys || isLoadingFieldKeys;
|
||||
if (isLoading) {
|
||||
if (isFetching) {
|
||||
return <OtherFiltersSkeleton />;
|
||||
}
|
||||
if (!otherFilters?.length) {
|
||||
@@ -145,11 +83,11 @@ function OtherFilters({
|
||||
|
||||
return otherFilters.map((filter) => (
|
||||
<div key={filter.key} className="qf-filter-item other-filters-item">
|
||||
<div className="qf-filter-key">{filter.key}</div>
|
||||
<div className="qf-filter-key">{filter.name}</div>
|
||||
<Button
|
||||
className="add-filter-btn periscope-btn"
|
||||
size="small"
|
||||
onClick={(): void => handleAddFilter(filter as FilterType)}
|
||||
onClick={(): void => handleAddFilter(filter)}
|
||||
>
|
||||
Add
|
||||
</Button>
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { useMemo } from 'react';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Button } from 'antd';
|
||||
import { Check, TableColumnsSplit, X } from '@signozhq/icons';
|
||||
import { Filter as FilterType } from 'types/api/quickFilters/getCustomFilters';
|
||||
import { TelemetryFieldKey } from 'types/api/v5/queryRange';
|
||||
|
||||
import { SignalType } from '../types';
|
||||
import AddedFilters from './AddedFilters';
|
||||
@@ -19,7 +18,7 @@ function QuickFiltersSettings({
|
||||
}: {
|
||||
signal: SignalType | undefined;
|
||||
setIsSettingsOpen: (isSettingsOpen: boolean) => void;
|
||||
customFilters: FilterType[];
|
||||
customFilters: TelemetryFieldKey[];
|
||||
refetchCustomFilters: () => void;
|
||||
}): JSX.Element {
|
||||
const {
|
||||
@@ -28,6 +27,7 @@ function QuickFiltersSettings({
|
||||
addedFilters,
|
||||
setAddedFilters,
|
||||
handleSaveChanges,
|
||||
hasUnsavedChanges,
|
||||
isUpdatingCustomFilters,
|
||||
inputValue,
|
||||
handleInputChange,
|
||||
@@ -39,18 +39,6 @@ function QuickFiltersSettings({
|
||||
signal,
|
||||
});
|
||||
|
||||
const hasUnsavedChanges = useMemo(
|
||||
() =>
|
||||
// check if both arrays have the same length and same order of elements
|
||||
!(
|
||||
addedFilters.length === customFilters.length &&
|
||||
addedFilters.every(
|
||||
(filter, index) => filter.key === customFilters[index].key,
|
||||
)
|
||||
),
|
||||
[addedFilters, customFilters],
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="qf-header">
|
||||
|
||||
@@ -1,27 +1,31 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useMutation } from 'react-query';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useUpdateQuickFilters } from 'api/generated/services/quick-filter';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import updateCustomFiltersAPI from 'api/quickFilters/updateCustomFilters';
|
||||
import axios, { AxiosError } from 'axios';
|
||||
import {
|
||||
TelemetrytypesFieldContextDTO,
|
||||
TelemetrytypesFieldDataTypeDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import { SignalType } from 'components/QuickFilters/types';
|
||||
import { SOMETHING_WENT_WRONG } from 'constants/api';
|
||||
import { buildCompositeKey } from 'container/OptionsMenu/utils';
|
||||
import useDebouncedFn from 'hooks/useDebouncedFunction';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import { Filter as FilterType } from 'types/api/quickFilters/getCustomFilters';
|
||||
import { TelemetryFieldKey } from 'types/api/v5/queryRange';
|
||||
|
||||
interface UseQuickFilterSettingsProps {
|
||||
setIsSettingsOpen: (isSettingsOpen: boolean) => void;
|
||||
customFilters: FilterType[];
|
||||
customFilters: TelemetryFieldKey[];
|
||||
refetchCustomFilters: () => void;
|
||||
signal?: SignalType;
|
||||
}
|
||||
|
||||
interface UseQuickFilterSettingsReturn {
|
||||
addedFilters: FilterType[];
|
||||
setAddedFilters: React.Dispatch<React.SetStateAction<FilterType[]>>;
|
||||
addedFilters: TelemetryFieldKey[];
|
||||
setAddedFilters: React.Dispatch<React.SetStateAction<TelemetryFieldKey[]>>;
|
||||
handleSettingsClose: () => void;
|
||||
handleDiscardChanges: () => void;
|
||||
handleSaveChanges: () => void;
|
||||
hasUnsavedChanges: boolean;
|
||||
isUpdatingCustomFilters: boolean;
|
||||
inputValue: string;
|
||||
setInputValue: React.Dispatch<React.SetStateAction<string>>;
|
||||
@@ -37,27 +41,43 @@ const useQuickFilterSettings = ({
|
||||
}: UseQuickFilterSettingsProps): UseQuickFilterSettingsReturn => {
|
||||
const [inputValue, setInputValue] = useState<string>('');
|
||||
const [debouncedInputValue, setDebouncedInputValue] = useState<string>('');
|
||||
const [addedFilters, setAddedFilters] = useState<FilterType[]>(customFilters);
|
||||
const normalizedCustomFilters = useMemo<TelemetryFieldKey[]>(
|
||||
() =>
|
||||
customFilters.map((filter) => ({
|
||||
...filter,
|
||||
key: buildCompositeKey(
|
||||
filter.name,
|
||||
filter.fieldContext,
|
||||
filter.fieldDataType,
|
||||
),
|
||||
})),
|
||||
[customFilters],
|
||||
);
|
||||
const [addedFilters, setAddedFilters] = useState<TelemetryFieldKey[]>(
|
||||
normalizedCustomFilters,
|
||||
);
|
||||
const { notifications } = useNotifications();
|
||||
|
||||
const { mutate: updateCustomFilters, isLoading: isUpdatingCustomFilters } =
|
||||
useMutation(updateCustomFiltersAPI, {
|
||||
onSuccess: () => {
|
||||
setIsSettingsOpen(false);
|
||||
refetchCustomFilters();
|
||||
logEvent('Quick Filters Settings: changes saved', {
|
||||
addedFilters,
|
||||
});
|
||||
notifications.success({
|
||||
message: 'Quick filters updated successfully',
|
||||
placement: 'bottomRight',
|
||||
});
|
||||
},
|
||||
onError: (error: AxiosError) => {
|
||||
notifications.error({
|
||||
message: axios.isAxiosError(error) ? error.message : SOMETHING_WENT_WRONG,
|
||||
placement: 'bottomRight',
|
||||
});
|
||||
useUpdateQuickFilters({
|
||||
mutation: {
|
||||
onSuccess: () => {
|
||||
setIsSettingsOpen(false);
|
||||
refetchCustomFilters();
|
||||
void logEvent('Quick Filters Settings: changes saved', {
|
||||
addedFilters,
|
||||
});
|
||||
notifications.success({
|
||||
message: 'Quick filters updated successfully',
|
||||
placement: 'bottomRight',
|
||||
});
|
||||
},
|
||||
onError: (error) => {
|
||||
notifications.error({
|
||||
message: error.message || SOMETHING_WENT_WRONG,
|
||||
placement: 'bottomRight',
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
const debouncedUpdate = useDebouncedFn((value) => {
|
||||
@@ -78,17 +98,30 @@ const useQuickFilterSettings = ({
|
||||
}, [setIsSettingsOpen]);
|
||||
|
||||
const handleDiscardChanges = useCallback((): void => {
|
||||
setAddedFilters(customFilters);
|
||||
}, [customFilters, setAddedFilters]);
|
||||
setAddedFilters(normalizedCustomFilters);
|
||||
}, [normalizedCustomFilters, setAddedFilters]);
|
||||
|
||||
const hasUnsavedChanges = useMemo(
|
||||
() =>
|
||||
!(
|
||||
addedFilters.length === normalizedCustomFilters.length &&
|
||||
addedFilters.every(
|
||||
(filter, index) => filter.key === normalizedCustomFilters[index].key,
|
||||
)
|
||||
),
|
||||
[addedFilters, normalizedCustomFilters],
|
||||
);
|
||||
|
||||
const handleSaveChanges = useCallback((): void => {
|
||||
if (signal) {
|
||||
updateCustomFilters({
|
||||
data: {
|
||||
// Send only the stored TelemetryFieldKey fields; the composite `key`
|
||||
// is UI-only.
|
||||
filters: addedFilters.map((filter) => ({
|
||||
key: filter.key,
|
||||
datatype: filter.dataType,
|
||||
type: filter.type,
|
||||
name: filter.name,
|
||||
fieldContext: filter.fieldContext as TelemetrytypesFieldContextDTO,
|
||||
fieldDataType: filter.fieldDataType as TelemetrytypesFieldDataTypeDTO,
|
||||
})),
|
||||
signal,
|
||||
},
|
||||
@@ -102,6 +135,7 @@ const useQuickFilterSettings = ({
|
||||
addedFilters,
|
||||
setAddedFilters,
|
||||
handleSaveChanges,
|
||||
hasUnsavedChanges,
|
||||
isUpdatingCustomFilters,
|
||||
inputValue,
|
||||
setInputValue,
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useQuery } from 'react-query';
|
||||
import getCustomFilters from 'api/quickFilters/getCustomFilters';
|
||||
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
||||
import { Filter as FilterType } from 'types/api/quickFilters/getCustomFilters';
|
||||
import { useGetQuickFilters } from 'api/generated/services/quick-filter';
|
||||
import { TelemetryFieldKey } from 'types/api/v5/queryRange';
|
||||
|
||||
import { IQuickFiltersConfig, SignalType } from '../types';
|
||||
import { getFilterConfig } from '../utils';
|
||||
@@ -13,7 +11,7 @@ interface UseFilterConfigProps {
|
||||
}
|
||||
interface UseFilterConfigReturn {
|
||||
filterConfig: IQuickFiltersConfig[];
|
||||
customFilters: FilterType[];
|
||||
customFilters: TelemetryFieldKey[];
|
||||
isCustomFiltersLoading: boolean;
|
||||
isDynamicFilters: boolean;
|
||||
refetchCustomFilters: () => void;
|
||||
@@ -25,17 +23,16 @@ const useFilterConfig = ({
|
||||
}: UseFilterConfigProps): UseFilterConfigReturn => {
|
||||
const {
|
||||
isFetching: isCustomFiltersLoading,
|
||||
data: customFilters = [],
|
||||
data,
|
||||
refetch,
|
||||
} = useQuery<FilterType[], Error>(
|
||||
[REACT_QUERY_KEY.GET_CUSTOM_FILTERS, signal],
|
||||
async () => {
|
||||
const res = await getCustomFilters({ signal: signal || '' });
|
||||
return 'payload' in res && res.payload?.filters ? res.payload.filters : [];
|
||||
},
|
||||
{
|
||||
enabled: !!signal,
|
||||
},
|
||||
} = useGetQuickFilters(
|
||||
{ signalName: signal ?? '' },
|
||||
{ query: { enabled: !!signal } },
|
||||
);
|
||||
|
||||
const customFilters = useMemo<TelemetryFieldKey[]>(
|
||||
() => (data?.data?.filters ?? []) as TelemetryFieldKey[],
|
||||
[data],
|
||||
);
|
||||
|
||||
const isDynamicFilters = useMemo(
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import { useMemo } from 'react';
|
||||
import {
|
||||
NANO_SECOND_MULTIPLIER,
|
||||
useLastComputedMinMax,
|
||||
} from 'store/globalTime';
|
||||
|
||||
import { QuickFilterCheckboxUseFieldApis } from '../types';
|
||||
|
||||
/**
|
||||
* Builds the `useFieldApis` config for a signal quick-filter page.
|
||||
* if existingQuery is sent null, related values are not fetched
|
||||
*/
|
||||
export function useSignalFieldApis(): QuickFilterCheckboxUseFieldApis {
|
||||
const { minTime, maxTime } = useLastComputedMinMax();
|
||||
|
||||
return useMemo(
|
||||
() => ({
|
||||
startUnixMilli: Math.floor(minTime / NANO_SECOND_MULTIPLIER),
|
||||
endUnixMilli: Math.floor(maxTime / NANO_SECOND_MULTIPLIER),
|
||||
existingQuery: null,
|
||||
}),
|
||||
[minTime, maxTime],
|
||||
);
|
||||
}
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
} from 'mocks-server/__mockdata__/customQuickFilters';
|
||||
import { server } from 'mocks-server/server';
|
||||
import { rest } from 'msw';
|
||||
import { render, screen, userEvent, waitFor } from 'tests/test-utils';
|
||||
import { render, screen, userEvent, waitFor, within } from 'tests/test-utils';
|
||||
|
||||
import '@testing-library/jest-dom';
|
||||
|
||||
@@ -34,9 +34,9 @@ const mockUseApiMonitoringParams = jest.mocked(useApiMonitoringParams);
|
||||
|
||||
const BASE_URL = ENVIRONMENT.baseURL;
|
||||
const SIGNAL = SignalType.LOGS;
|
||||
const quickFiltersListURL = `${BASE_URL}/api/v1/orgs/me/filters/${SIGNAL}`;
|
||||
const saveQuickFiltersURL = `${BASE_URL}/api/v1/orgs/me/filters`;
|
||||
const quickFiltersSuggestionsURL = `${BASE_URL}/api/v3/filter_suggestions`;
|
||||
const quickFiltersListURL = `${BASE_URL}/api/v2/quick_filters/${SIGNAL}`;
|
||||
const saveQuickFiltersURL = `${BASE_URL}/api/v2/quick_filters`;
|
||||
const quickFiltersSuggestionsURL = `${BASE_URL}/api/v1/fields/keys`;
|
||||
const quickFiltersAttributeValuesURL = `${BASE_URL}/api/v3/autocomplete/attribute_values`;
|
||||
const fieldsValuesURL = `${BASE_URL}/api/v1/fields/values`;
|
||||
|
||||
@@ -338,6 +338,63 @@ describe('Quick Filters with custom filters', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('keeps same-name fields with different context as distinct entries', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
server.use(
|
||||
rest.get(quickFiltersSuggestionsURL, (_req, res, ctx) =>
|
||||
res(
|
||||
ctx.status(200),
|
||||
ctx.json({
|
||||
status: 'success',
|
||||
data: {
|
||||
complete: true,
|
||||
keys: {
|
||||
level: [
|
||||
{
|
||||
name: 'level',
|
||||
fieldContext: 'attribute',
|
||||
fieldDataType: 'string',
|
||||
signal: 'logs',
|
||||
},
|
||||
{
|
||||
name: 'level',
|
||||
fieldContext: 'span',
|
||||
fieldDataType: 'string',
|
||||
signal: 'logs',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
render(<TestQuickFilters signal={SIGNAL} />);
|
||||
await screen.findByText(FILTER_SERVICE_NAME);
|
||||
|
||||
const icon = await screen.findByTestId(SETTINGS_ICON_TEST_ID);
|
||||
const settingsButton = icon.closest('button') ?? icon;
|
||||
await user.click(settingsButton);
|
||||
|
||||
const otherSection = screen.getByText(OTHER_FILTERS_LABEL).parentElement!;
|
||||
// Both `level` variants are shown despite sharing a name.
|
||||
await waitFor(() =>
|
||||
expect(within(otherSection).getAllByText('level')).toHaveLength(2),
|
||||
);
|
||||
|
||||
// Adding one variant removes only that one; the other stays.
|
||||
const firstLevel = within(otherSection).getAllByText('level')[0];
|
||||
const addButton = firstLevel.parentElement?.querySelector('button');
|
||||
await user.click(addButton as HTMLButtonElement);
|
||||
|
||||
const addedSection = screen.getByText(ADDED_FILTERS_LABEL).parentElement!;
|
||||
await waitFor(() => {
|
||||
expect(within(addedSection).getAllByText('level')).toHaveLength(1);
|
||||
expect(within(otherSection).getAllByText('level')).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
it('adds a filter from OTHER FILTERS to ADDED FILTERS when clicked', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
|
||||
@@ -458,7 +515,7 @@ describe('Quick Filters with custom filters', () => {
|
||||
const requestBody = putHandler.mock.calls[0][0];
|
||||
expect(requestBody.filters).toStrictEqual(
|
||||
expect.arrayContaining([
|
||||
expect.not.objectContaining({ key: FILTER_OS_DESCRIPTION }),
|
||||
expect.not.objectContaining({ name: FILTER_OS_DESCRIPTION }),
|
||||
]),
|
||||
);
|
||||
expect(requestBody.signal).toBe(SIGNAL);
|
||||
@@ -612,9 +669,9 @@ describe('Quick Filters refetch behavior', () => {
|
||||
filters: [
|
||||
...(quickFiltersListResponse.data.filters ?? []),
|
||||
{
|
||||
key: 'new.custom.filter',
|
||||
dataType: 'string',
|
||||
type: 'resource',
|
||||
name: 'new.custom.filter',
|
||||
fieldDataType: 'string',
|
||||
fieldContext: 'resource',
|
||||
} as const,
|
||||
],
|
||||
},
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import {
|
||||
TelemetrytypesFieldContextDTO,
|
||||
TelemetrytypesFieldDataTypeDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import { SIGNAL_DATA_SOURCE_MAP } from 'components/QuickFilters/QuickFiltersSettings/constants';
|
||||
import { Filter as FilterType } from 'types/api/quickFilters/getCustomFilters';
|
||||
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { TelemetryFieldKey } from 'types/api/v5/queryRange';
|
||||
|
||||
import { FiltersType, IQuickFiltersConfig, SignalType } from './types';
|
||||
|
||||
@@ -12,6 +17,39 @@ const FILTER_TYPE_MAP: Record<string, FiltersType> = {
|
||||
duration_nano: FiltersType.DURATION,
|
||||
};
|
||||
|
||||
// Both maps below (and mapFieldDataType/mapFieldContext) exist only for the old
|
||||
// v3 attribute-values fetch in useCheckboxFilterValues, which is the sole reader
|
||||
// of attributeKey.dataType/type. Query/list endpoints don't consume them (v5 and
|
||||
// the API-monitoring/exceptions/infra paths all send a name-based expression).
|
||||
// Once the values fetch moves to fields/values (by name) in Phase A, this whole
|
||||
// mapping can be removed and attributeKey reduced to { id, key }.
|
||||
|
||||
// The new field data types are rendered down to the v3 spellings the
|
||||
// attribute-values call expects, matching the backend's legacy conversion
|
||||
// (number -> float64).
|
||||
const FIELD_DATA_TYPE_TO_DATA_TYPE: Record<string, DataTypes> = {
|
||||
[TelemetrytypesFieldDataTypeDTO.string]: DataTypes.String,
|
||||
[TelemetrytypesFieldDataTypeDTO.bool]: DataTypes.bool,
|
||||
[TelemetrytypesFieldDataTypeDTO.float64]: DataTypes.Float64,
|
||||
[TelemetrytypesFieldDataTypeDTO.int64]: DataTypes.Int64,
|
||||
[TelemetrytypesFieldDataTypeDTO.number]: DataTypes.Float64,
|
||||
};
|
||||
|
||||
// Only tag and resource exist in the v3 attribute-type enum; other contexts
|
||||
// render as empty so the still-live v3 values path never sees a spelling it
|
||||
// can't use, matching the backend's legacy conversion.
|
||||
const FIELD_CONTEXT_TO_ATTRIBUTE_TYPE: Record<string, string> = {
|
||||
[TelemetrytypesFieldContextDTO.attribute]: 'tag',
|
||||
[TelemetrytypesFieldContextDTO.resource]: 'resource',
|
||||
};
|
||||
|
||||
const mapFieldDataType = (fieldDataType?: string): DataTypes =>
|
||||
(fieldDataType && FIELD_DATA_TYPE_TO_DATA_TYPE[fieldDataType]) ||
|
||||
DataTypes.EMPTY;
|
||||
|
||||
const mapFieldContext = (fieldContext?: string): string =>
|
||||
(fieldContext && FIELD_CONTEXT_TO_ATTRIBUTE_TYPE[fieldContext]) || '';
|
||||
|
||||
const getFilterName = (str: string): string => {
|
||||
if (FILTER_TITLE_MAP[str]) {
|
||||
return FILTER_TITLE_MAP[str];
|
||||
@@ -26,16 +64,16 @@ const getFilterName = (str: string): string => {
|
||||
.join(' ');
|
||||
};
|
||||
|
||||
const getFilterType = (att: FilterType): FiltersType => {
|
||||
if (FILTER_TYPE_MAP[att.key]) {
|
||||
return FILTER_TYPE_MAP[att.key];
|
||||
const getFilterType = (att: TelemetryFieldKey): FiltersType => {
|
||||
if (FILTER_TYPE_MAP[att.name]) {
|
||||
return FILTER_TYPE_MAP[att.name];
|
||||
}
|
||||
return FiltersType.CHECKBOX;
|
||||
};
|
||||
|
||||
export const getFilterConfig = (
|
||||
signal?: SignalType,
|
||||
customFilters?: FilterType[],
|
||||
customFilters?: TelemetryFieldKey[],
|
||||
config?: IQuickFiltersConfig[],
|
||||
): IQuickFiltersConfig[] => {
|
||||
if (!customFilters?.length || !signal) {
|
||||
@@ -46,13 +84,13 @@ export const getFilterConfig = (
|
||||
(att, index) =>
|
||||
({
|
||||
type: getFilterType(att),
|
||||
title: getFilterName(att.key),
|
||||
title: getFilterName(att.name),
|
||||
dataSource: SIGNAL_DATA_SOURCE_MAP[signal],
|
||||
attributeKey: {
|
||||
id: att.key,
|
||||
key: att.key,
|
||||
dataType: att.dataType,
|
||||
type: att.type,
|
||||
id: att.name,
|
||||
key: att.name,
|
||||
dataType: mapFieldDataType(att.fieldDataType),
|
||||
type: mapFieldContext(att.fieldContext),
|
||||
},
|
||||
defaultOpen: index < 2,
|
||||
}) as IQuickFiltersConfig,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import refreshPaymentStatus from 'api/v3/licenses/put';
|
||||
import { refreshLicense } from 'api/generated/services/licenses';
|
||||
import { Button } from '@signozhq/ui/button';
|
||||
import { TooltipSimple } from '@signozhq/ui/tooltip';
|
||||
import { RefreshCcw } from '@signozhq/icons';
|
||||
@@ -14,17 +14,21 @@ function RefreshPaymentStatus({
|
||||
className?: string;
|
||||
}): JSX.Element {
|
||||
const { t } = useTranslation(['failedPayment']);
|
||||
const { activeLicenseRefetch } = useAppContext();
|
||||
const { activeLicense, activeLicenseRefetch } = useAppContext();
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const handleRefreshPaymentStatus = async (): Promise<void> => {
|
||||
if (!activeLicense) {
|
||||
return;
|
||||
}
|
||||
|
||||
setIsLoading(true);
|
||||
|
||||
try {
|
||||
await refreshPaymentStatus();
|
||||
await refreshLicense({ id: activeLicense.id });
|
||||
|
||||
await Promise.all([activeLicenseRefetch()]);
|
||||
activeLicenseRefetch();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
|
||||
@@ -28,7 +28,6 @@ export const REACT_QUERY_KEY = {
|
||||
DUPLICATE_ALERT_RULE: 'DUPLICATE_ALERT_RULE',
|
||||
GET_HOST_LIST: 'GET_HOST_LIST',
|
||||
UPDATE_ALERT_RULE: 'UPDATE_ALERT_RULE',
|
||||
GET_ACTIVE_LICENSE_V3: 'GET_ACTIVE_LICENSE_V3',
|
||||
GET_TRACE_V2_WATERFALL: 'GET_TRACE_V2_WATERFALL',
|
||||
GET_TRACE_V4_WATERFALL: 'GET_TRACE_V4_WATERFALL',
|
||||
GET_TRACE_AGGREGATIONS: 'GET_TRACE_AGGREGATIONS',
|
||||
|
||||
@@ -2,6 +2,7 @@ import CreateAlertChannels from 'container/CreateAlertChannels';
|
||||
import { ChannelType } from 'container/CreateAlertChannels/config';
|
||||
import {
|
||||
GoogleChatInitialConfig,
|
||||
IncidentIOInitialConfig,
|
||||
JiraInitialConfig,
|
||||
JsmOpsInitialConfig,
|
||||
} from 'container/CreateAlertChannels/defaults';
|
||||
@@ -585,7 +586,7 @@ describe('Create Alert Channel', () => {
|
||||
description: 'jira_site_invalid',
|
||||
}),
|
||||
);
|
||||
});
|
||||
}, 15000);
|
||||
|
||||
it('Should send a jira_configs payload with basic auth', async () => {
|
||||
let requestBody: unknown;
|
||||
@@ -737,6 +738,122 @@ describe('Create Alert Channel', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('incident.io', () => {
|
||||
const incidentIOURL =
|
||||
'https://api.incident.io/v2/alert_events/http/01M0D1JNVBGBGVTWX053EM12XV';
|
||||
|
||||
beforeEach(() => {
|
||||
render(<CreateAlertChannels preType={ChannelType.IncidentIO} />);
|
||||
});
|
||||
|
||||
it('Should display the URL and token fields with the docs tip', () => {
|
||||
testLabelInputAndHelpValue({
|
||||
labelText: 'field_incidentio_url',
|
||||
testId: 'incidentio-url-textbox',
|
||||
});
|
||||
testLabelInputAndHelpValue({
|
||||
labelText: 'field_incidentio_token',
|
||||
testId: 'incidentio-token-textbox',
|
||||
});
|
||||
expect(screen.getByTestId('incidentio-tip')).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByRole('link', { name: 'incidentio_tip_link' }),
|
||||
).toHaveAttribute(
|
||||
'href',
|
||||
'https://signoz.io/docs/alerts-management/notification-channel/incidentio/',
|
||||
);
|
||||
});
|
||||
|
||||
it('Should block save when the URL or token is missing', async () => {
|
||||
const user = userEvent.setup();
|
||||
await user.type(
|
||||
screen.getByTestId('channel-name-textbox'),
|
||||
'incidentio-channel',
|
||||
);
|
||||
|
||||
await user.click(screen.getByTestId('save-channel-button'));
|
||||
|
||||
await waitFor(() =>
|
||||
expect(errorNotification).toHaveBeenCalledWith({
|
||||
message: 'Error',
|
||||
description: 'incidentio_required_fields',
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('Should display an error when the URL is not an alert events URL', async () => {
|
||||
const user = userEvent.setup();
|
||||
await user.type(
|
||||
screen.getByTestId('channel-name-textbox'),
|
||||
'incidentio-channel',
|
||||
);
|
||||
await user.type(
|
||||
screen.getByTestId('incidentio-url-textbox'),
|
||||
'https://api.incident.io/v2/incidents',
|
||||
);
|
||||
await user.type(screen.getByTestId('incidentio-token-textbox'), 'tok-abc');
|
||||
|
||||
await user.click(screen.getByTestId('save-channel-button'));
|
||||
|
||||
await waitFor(() =>
|
||||
expect(errorNotification).toHaveBeenCalledWith({
|
||||
message: 'Error',
|
||||
description: 'incidentio_url_invalid',
|
||||
}),
|
||||
);
|
||||
}, 15000);
|
||||
|
||||
it('Should send an incidentio_configs payload with prefilled defaults', async () => {
|
||||
let requestBody: unknown;
|
||||
server.use(
|
||||
rest.post('http://localhost/api/v1/channels', async (req, res, ctx) => {
|
||||
requestBody = await req.json();
|
||||
return res(
|
||||
ctx.status(201),
|
||||
ctx.json({ status: 'success', data: 'channel created' }),
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
const user = userEvent.setup();
|
||||
await user.type(
|
||||
screen.getByTestId('channel-name-textbox'),
|
||||
'incidentio-channel',
|
||||
);
|
||||
await user.type(
|
||||
screen.getByTestId('incidentio-url-textbox'),
|
||||
incidentIOURL,
|
||||
);
|
||||
await user.type(screen.getByTestId('incidentio-token-textbox'), 'tok-abc');
|
||||
|
||||
await user.click(screen.getByTestId('incidentio-metadata-add'));
|
||||
await user.type(screen.getByTestId('incidentio-metadata-key-0'), 'team');
|
||||
await user.type(screen.getByTestId('incidentio-metadata-value-0'), 'core');
|
||||
|
||||
await user.click(screen.getByTestId('save-channel-button'));
|
||||
|
||||
await waitFor(() =>
|
||||
expect(successNotification).toHaveBeenCalledWith({
|
||||
message: 'Success',
|
||||
description: 'channel_creation_done',
|
||||
}),
|
||||
);
|
||||
|
||||
expect(requestBody).toStrictEqual({
|
||||
name: 'incidentio-channel',
|
||||
incidentio_configs: [
|
||||
{
|
||||
url: incidentIOURL,
|
||||
token: 'tok-abc',
|
||||
send_resolved: true,
|
||||
title: IncidentIOInitialConfig.title,
|
||||
description: IncidentIOInitialConfig.description,
|
||||
metadata: { team: 'core' },
|
||||
},
|
||||
],
|
||||
});
|
||||
}, 15000);
|
||||
});
|
||||
describe('Changing the channel type', () => {
|
||||
async function selectType(
|
||||
user: ReturnType<typeof userEvent.setup>,
|
||||
|
||||
@@ -90,6 +90,40 @@ describe('EditAlertChannels save', () => {
|
||||
await waitFor(() => expect(edit.calls).toHaveLength(1));
|
||||
});
|
||||
|
||||
it('sends an incidentio_configs payload when editing an incident.io channel', async () => {
|
||||
const edit = mockEditChannel();
|
||||
render(
|
||||
<EditAlertChannels
|
||||
channelId="4"
|
||||
initialValue={{
|
||||
type: 'incidentio',
|
||||
name: 'incidentio-channel',
|
||||
url: 'https://api.incident.io/v2/alert_events/http/01M0D1JNVBGBGVTWX053EM12XV',
|
||||
token: 'tok-abc',
|
||||
send_resolved: true,
|
||||
metadata: { env: 'prod' },
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
|
||||
const user = userEvent.setup();
|
||||
await user.click(screen.getByTestId('save-channel-button'));
|
||||
|
||||
await waitFor(() => expect(edit.calls).toHaveLength(1));
|
||||
expect(edit.calls[0].id).toBe('4');
|
||||
expect(edit.calls[0].body).toStrictEqual({
|
||||
name: 'incidentio-channel',
|
||||
incidentio_configs: [
|
||||
{
|
||||
url: 'https://api.incident.io/v2/alert_events/http/01M0D1JNVBGBGVTWX053EM12XV',
|
||||
token: 'tok-abc',
|
||||
send_resolved: true,
|
||||
metadata: { env: 'prod' },
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('persists send_resolved toggle in the edit request', async () => {
|
||||
const edit = mockEditChannel();
|
||||
render(
|
||||
|
||||
@@ -3,6 +3,7 @@ import * as Sentry from '@sentry/react';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import cx from 'classnames';
|
||||
import QuickFilters from 'components/QuickFilters/QuickFilters';
|
||||
import { useSignalFieldApis } from 'components/QuickFilters/hooks/useSignalFieldApis';
|
||||
import { QuickFiltersSource, SignalType } from 'components/QuickFilters/types';
|
||||
import ErrorBoundaryFallback from 'pages/ErrorBoundaryFallback/ErrorBoundaryFallback';
|
||||
|
||||
@@ -11,6 +12,8 @@ import DomainList from './Domains/DomainList';
|
||||
import './Explorer.styles.scss';
|
||||
|
||||
function Explorer(): JSX.Element {
|
||||
const quickFilterFieldApis = useSignalFieldApis();
|
||||
|
||||
useEffect(() => {
|
||||
logEvent('API Monitoring: Landing page visited', {});
|
||||
}, []);
|
||||
@@ -26,6 +29,7 @@ function Explorer(): JSX.Element {
|
||||
showFilterCollapse={false}
|
||||
showQueryName={false}
|
||||
handleFilterVisibilityChange={(): void => {}}
|
||||
useFieldApis={quickFilterFieldApis}
|
||||
/>
|
||||
</section>
|
||||
<DomainList />
|
||||
|
||||
@@ -453,7 +453,7 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
|
||||
if (
|
||||
!isFetchingActiveLicense &&
|
||||
!isNull(activeLicense) &&
|
||||
activeLicense?.event_queue?.event === LicenseEvent.DEFAULT
|
||||
activeLicense?.eventQueue?.event === LicenseEvent.DEFAULT
|
||||
) {
|
||||
setShowPaymentFailedWarning(true);
|
||||
}
|
||||
@@ -820,7 +820,9 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
|
||||
Your bill payment has failed. Your workspace will get suspended on{' '}
|
||||
<span>
|
||||
{getFormattedDateWithMinutes(
|
||||
dayjs(activeLicense?.event_queue?.scheduled_at).unix() || Date.now(),
|
||||
activeLicense?.eventQueue?.scheduledAt
|
||||
? dayjs(activeLicense.eventQueue.scheduledAt).unix()
|
||||
: dayjs().unix(),
|
||||
)}
|
||||
.
|
||||
</span>
|
||||
|
||||
@@ -15,6 +15,11 @@ import { getFormattedDate } from 'utils/timeUtils';
|
||||
|
||||
import BillingContainer from './BillingContainer';
|
||||
|
||||
jest.mock('hooks/useActiveLicenseKey/useActiveLicenseKey', () => ({
|
||||
__esModule: true,
|
||||
default: jest.fn(() => ({ licenseKey: 'test-key', isLoading: false })),
|
||||
}));
|
||||
|
||||
window.ResizeObserver =
|
||||
window.ResizeObserver ||
|
||||
jest.fn().mockImplementation(() => ({
|
||||
|
||||
@@ -30,6 +30,7 @@ import useAxiosError from 'hooks/useAxiosError';
|
||||
import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import { isEmpty, pick } from 'lodash-es';
|
||||
import useActiveLicenseKey from 'hooks/useActiveLicenseKey/useActiveLicenseKey';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
import { ErrorResponse, SuccessResponse, SuccessResponseV2 } from 'types/api';
|
||||
import { CheckoutSuccessPayloadProps } from 'types/api/billing/checkout';
|
||||
@@ -145,6 +146,7 @@ export default function BillingContainer(): JSX.Element {
|
||||
activeLicense,
|
||||
activeLicenseFetchError,
|
||||
} = useAppContext();
|
||||
const { licenseKey } = useActiveLicenseKey();
|
||||
const { notifications } = useNotifications();
|
||||
|
||||
const handleError = useAxiosError();
|
||||
@@ -207,9 +209,9 @@ export default function BillingContainer(): JSX.Element {
|
||||
isFetching: isFetchingBillingData,
|
||||
data: billingData,
|
||||
} = useQuery([REACT_QUERY_KEY.GET_BILLING_USAGE, user?.id], {
|
||||
queryFn: () => getUsage(activeLicense?.key || ''),
|
||||
queryFn: () => getUsage(licenseKey || ''),
|
||||
onError: handleError,
|
||||
enabled: activeLicense !== null,
|
||||
enabled: !!licenseKey,
|
||||
onSuccess: processUsageData,
|
||||
});
|
||||
|
||||
|
||||
@@ -107,6 +107,7 @@ export enum ChannelType {
|
||||
GoogleChat = 'googlechat',
|
||||
Jira = 'jira',
|
||||
JsmOps = 'jsmops',
|
||||
IncidentIO = 'incidentio',
|
||||
}
|
||||
|
||||
// LabelFilterStatement will be used for preparing filter conditions / matchers
|
||||
@@ -159,6 +160,22 @@ export interface JiraChannel extends Channel {
|
||||
reopen_duration?: string;
|
||||
}
|
||||
|
||||
// IncidentIOChannel configures the incident.io alert channel, backed by an
|
||||
// incident.io HTTP alert source (Alert Events V2 API).
|
||||
export interface IncidentIOChannel extends Channel {
|
||||
// per-source alert events URL, e.g.
|
||||
// https://api.incident.io/v2/alert_events/http/<source_config_id>
|
||||
url: string;
|
||||
// the alert source's secret token
|
||||
token: string;
|
||||
// alert title template
|
||||
title?: string;
|
||||
// alert body template (markdown, rendered natively by incident.io)
|
||||
description?: string;
|
||||
// extra metadata pairs merged over the alert's labels (channel wins on clash)
|
||||
metadata?: Record<string, string>;
|
||||
}
|
||||
|
||||
// JsmOpsChannel configures the Jira Service Management Ops alert channel
|
||||
// (ex-Opsgenie alert API). Auth is the JSM integration API key.
|
||||
export interface JsmOpsChannel extends Channel {
|
||||
|
||||
@@ -2,6 +2,7 @@ import {
|
||||
ChannelType,
|
||||
EmailChannel,
|
||||
GoogleChatChannel,
|
||||
IncidentIOChannel,
|
||||
JiraChannel,
|
||||
JsmOpsChannel,
|
||||
MsTeamsChannel,
|
||||
@@ -144,6 +145,29 @@ export const JsmOpsInitialConfig: Partial<JsmOpsChannel> = {
|
||||
tags: ['signoz-alert'],
|
||||
};
|
||||
|
||||
// mirrors DefaultIncidentIOTitleTemplate / DefaultIncidentIODescriptionTemplate
|
||||
// in pkg/types/alertmanagertypes/incidentio.go, applied by the backend when
|
||||
// title / description are left empty. send_resolved is seeded on so incident.io
|
||||
// alerts resolve with the rule (the backend cannot default it).
|
||||
export const IncidentIOInitialConfig: Partial<IncidentIOChannel> = {
|
||||
send_resolved: true,
|
||||
title: `[{{ .Status | toUpper }}{{ if eq .Status "firing" }}:{{ .Alerts.Firing | len }}{{ end }}] {{ .CommonLabels.alertname }}`,
|
||||
description: `{{ range .Alerts -}}
|
||||
**Alert:** {{ .Labels.alertname }}{{ if .Labels.severity }} ({{ .Labels.severity }}){{ end }}
|
||||
|
||||
{{ if .Annotations.summary }}**Summary:** {{ .Annotations.summary }}
|
||||
|
||||
{{ end }}{{ if .Annotations.description }}**Description:** {{ .Annotations.description }}
|
||||
|
||||
{{ end }}{{ if .GeneratorURL }}[View in SigNoz]({{ .GeneratorURL }})
|
||||
|
||||
{{ end }}{{ if .Annotations.related_logs }}[View related logs]({{ .Annotations.related_logs }})
|
||||
|
||||
{{ end }}{{ if .Annotations.related_traces }}[View related traces]({{ .Annotations.related_traces }})
|
||||
|
||||
{{ end }}{{ end }}`,
|
||||
};
|
||||
|
||||
export const EmailInitialConfig: Partial<EmailChannel> = {
|
||||
send_resolved: true,
|
||||
html: `<!--
|
||||
@@ -553,7 +577,8 @@ export const ChannelInitialConfig: Record<
|
||||
EmailChannel &
|
||||
GoogleChatChannel &
|
||||
JiraChannel &
|
||||
JsmOpsChannel
|
||||
JsmOpsChannel &
|
||||
IncidentIOChannel
|
||||
>
|
||||
> = {
|
||||
[ChannelType.Slack]: SlackInitialConfig,
|
||||
@@ -561,6 +586,7 @@ export const ChannelInitialConfig: Record<
|
||||
[ChannelType.GoogleChat]: GoogleChatInitialConfig,
|
||||
[ChannelType.Jira]: JiraInitialConfig,
|
||||
[ChannelType.JsmOps]: JsmOpsInitialConfig,
|
||||
[ChannelType.IncidentIO]: IncidentIOInitialConfig,
|
||||
[ChannelType.Pagerduty]: PagerInitialConfig,
|
||||
[ChannelType.Opsgenie]: OpsgenieInitialConfig,
|
||||
[ChannelType.Email]: EmailInitialConfig,
|
||||
|
||||
@@ -32,6 +32,7 @@ import {
|
||||
ChannelType,
|
||||
EmailChannel,
|
||||
GoogleChatChannel,
|
||||
IncidentIOChannel,
|
||||
JiraChannel,
|
||||
JsmOpsChannel,
|
||||
MsTeamsChannel,
|
||||
@@ -45,9 +46,11 @@ import { ChannelInitialConfig } from './defaults';
|
||||
import {
|
||||
isChannelType,
|
||||
isValidGoogleChatWebhookURL,
|
||||
isValidIncidentIOURL,
|
||||
isValidJiraReopenDuration,
|
||||
isValidJiraSiteURL,
|
||||
prepareGoogleChatRequest,
|
||||
prepareIncidentIORequest,
|
||||
prepareJiraRequest,
|
||||
prepareJsmOpsRequest,
|
||||
} from './utils';
|
||||
@@ -77,7 +80,8 @@ function CreateAlertChannels({
|
||||
EmailChannel &
|
||||
GoogleChatChannel &
|
||||
JiraChannel &
|
||||
JsmOpsChannel
|
||||
JsmOpsChannel &
|
||||
IncidentIOChannel
|
||||
>
|
||||
>(() => ({
|
||||
send_resolved: true,
|
||||
@@ -550,6 +554,56 @@ function CreateAlertChannels({
|
||||
showErrorModal,
|
||||
]);
|
||||
|
||||
const validateIncidentIOConfig = useCallback((): boolean => {
|
||||
if (!selectedConfig.url || !selectedConfig.token) {
|
||||
notifications.error({
|
||||
message: 'Error',
|
||||
description: t('incidentio_required_fields'),
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isValidIncidentIOURL(selectedConfig.url)) {
|
||||
notifications.error({
|
||||
message: 'Error',
|
||||
description: t('incidentio_url_invalid'),
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}, [selectedConfig.url, selectedConfig.token, notifications, t]);
|
||||
|
||||
const onIncidentIOHandler = useCallback(async () => {
|
||||
if (!validateIncidentIOConfig()) {
|
||||
return { status: 'failed', statusMessage: t('channel_creation_failed') };
|
||||
}
|
||||
|
||||
setSavingState(true);
|
||||
|
||||
try {
|
||||
await createChannel({ data: prepareIncidentIORequest(selectedConfig) });
|
||||
notifications.success({
|
||||
message: 'Success',
|
||||
description: t('channel_creation_done'),
|
||||
});
|
||||
history.replace(ROUTES.ALL_CHANNELS);
|
||||
return { status: 'success', statusMessage: t('channel_creation_done') };
|
||||
} catch (error) {
|
||||
showErrorModal(toAPIError(error as ErrorType<RenderErrorResponseDTO>));
|
||||
return { status: 'failed', statusMessage: t('channel_creation_failed') };
|
||||
} finally {
|
||||
setSavingState(false);
|
||||
}
|
||||
}, [
|
||||
validateIncidentIOConfig,
|
||||
createChannel,
|
||||
selectedConfig,
|
||||
notifications,
|
||||
t,
|
||||
showErrorModal,
|
||||
]);
|
||||
|
||||
const onSaveHandler = useCallback(
|
||||
async (value: ChannelType) => {
|
||||
if (!selectedConfig.name) {
|
||||
@@ -570,6 +624,7 @@ function CreateAlertChannels({
|
||||
[ChannelType.GoogleChat]: onGoogleChatHandler,
|
||||
[ChannelType.Jira]: onJiraHandler,
|
||||
[ChannelType.JsmOps]: onJsmOpsHandler,
|
||||
[ChannelType.IncidentIO]: onIncidentIOHandler,
|
||||
};
|
||||
|
||||
if (isChannelType(value)) {
|
||||
@@ -604,6 +659,7 @@ function CreateAlertChannels({
|
||||
onGoogleChatHandler,
|
||||
onJiraHandler,
|
||||
onJsmOpsHandler,
|
||||
onIncidentIOHandler,
|
||||
notifications,
|
||||
t,
|
||||
],
|
||||
@@ -662,6 +718,13 @@ function CreateAlertChannels({
|
||||
}
|
||||
await testChannel({ data: prepareJsmOpsRequest(selectedConfig) });
|
||||
break;
|
||||
case ChannelType.IncidentIO:
|
||||
if (!validateIncidentIOConfig()) {
|
||||
setTestingState(false);
|
||||
return;
|
||||
}
|
||||
await testChannel({ data: prepareIncidentIORequest(selectedConfig) });
|
||||
break;
|
||||
default:
|
||||
notifications.error({
|
||||
message: 'Error',
|
||||
@@ -712,6 +775,7 @@ function CreateAlertChannels({
|
||||
validateGoogleChatConfig,
|
||||
validateJiraConfig,
|
||||
validateJsmOpsConfig,
|
||||
validateIncidentIOConfig,
|
||||
testChannel,
|
||||
notifications,
|
||||
],
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
AlertmanagertypesIncidentIOReceiverConfigDTO,
|
||||
AlertmanagertypesJiraReceiverConfigDTO,
|
||||
AlertmanagertypesJSMOpsReceiverConfigDTO,
|
||||
AlertmanagertypesPostableChannelDTO,
|
||||
@@ -9,6 +10,7 @@ import {
|
||||
import {
|
||||
ChannelType,
|
||||
GoogleChatChannel,
|
||||
IncidentIOChannel,
|
||||
JiraChannel,
|
||||
JsmOpsChannel,
|
||||
} from './config';
|
||||
@@ -168,3 +170,50 @@ export const prepareJsmOpsRequest = (
|
||||
jsmops_configs: [jsmops],
|
||||
};
|
||||
};
|
||||
|
||||
const INCIDENTIO_EVENTS_PATH_PREFIX = '/v2/alert_events/http/';
|
||||
|
||||
// the backend enforces the same rule, this is only for a nicer error experience
|
||||
export const isValidIncidentIOURL = (url: string): boolean => {
|
||||
try {
|
||||
const { protocol, pathname } = new URL(url);
|
||||
const idx = pathname.indexOf(INCIDENTIO_EVENTS_PATH_PREFIX);
|
||||
return (
|
||||
protocol === 'https:' &&
|
||||
idx !== -1 &&
|
||||
pathname.length > idx + INCIDENTIO_EVENTS_PATH_PREFIX.length
|
||||
);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// create, update and test all send the same body shape. Optional fields are
|
||||
// omitted when empty so the backend applies its defaults.
|
||||
export const prepareIncidentIORequest = (
|
||||
config: Partial<IncidentIOChannel>,
|
||||
): AlertmanagertypesPostableChannelDTO => {
|
||||
const incidentio: AlertmanagertypesIncidentIOReceiverConfigDTO = {
|
||||
url: config.url || '',
|
||||
token: config.token || '',
|
||||
send_resolved: config.send_resolved || false,
|
||||
};
|
||||
|
||||
if (config.title) {
|
||||
incidentio.title = config.title;
|
||||
}
|
||||
if (config.description) {
|
||||
incidentio.description = config.description;
|
||||
}
|
||||
const metadata = Object.fromEntries(
|
||||
Object.entries(config.metadata || {}).filter(([key]) => key.trim() !== ''),
|
||||
);
|
||||
if (Object.keys(metadata).length > 0) {
|
||||
incidentio.metadata = metadata;
|
||||
}
|
||||
|
||||
return {
|
||||
name: config.name || '',
|
||||
incidentio_configs: [incidentio],
|
||||
};
|
||||
};
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
ChannelType,
|
||||
EmailChannel,
|
||||
GoogleChatChannel,
|
||||
IncidentIOChannel,
|
||||
JiraChannel,
|
||||
JsmOpsChannel,
|
||||
MsTeamsChannel,
|
||||
@@ -36,9 +37,11 @@ import {
|
||||
} from 'container/CreateAlertChannels/config';
|
||||
import {
|
||||
isValidGoogleChatWebhookURL,
|
||||
isValidIncidentIOURL,
|
||||
isValidJiraReopenDuration,
|
||||
isValidJiraSiteURL,
|
||||
prepareGoogleChatRequest,
|
||||
prepareIncidentIORequest,
|
||||
prepareJiraRequest,
|
||||
prepareJsmOpsRequest,
|
||||
} from 'container/CreateAlertChannels/utils';
|
||||
@@ -66,7 +69,8 @@ function EditAlertChannels({
|
||||
EmailChannel &
|
||||
GoogleChatChannel &
|
||||
JiraChannel &
|
||||
JsmOpsChannel
|
||||
JsmOpsChannel &
|
||||
IncidentIOChannel
|
||||
>
|
||||
>({
|
||||
...initialValue,
|
||||
@@ -578,6 +582,61 @@ function EditAlertChannels({
|
||||
t,
|
||||
]);
|
||||
|
||||
const validateIncidentIOConfig = useCallback((): string => {
|
||||
if (!selectedConfig.url || !selectedConfig.token) {
|
||||
return t('incidentio_required_fields');
|
||||
}
|
||||
|
||||
if (!isValidIncidentIOURL(selectedConfig.url)) {
|
||||
return t('incidentio_url_invalid');
|
||||
}
|
||||
|
||||
return '';
|
||||
}, [selectedConfig, t]);
|
||||
|
||||
const onIncidentIOEditHandler = useCallback(async () => {
|
||||
const validationError = validateIncidentIOConfig();
|
||||
|
||||
if (validationError !== '') {
|
||||
notifications.error({
|
||||
message: 'Error',
|
||||
description: validationError,
|
||||
});
|
||||
return { status: 'failed', statusMessage: validationError };
|
||||
}
|
||||
|
||||
setSavingState(true);
|
||||
|
||||
try {
|
||||
await updateChannel({
|
||||
pathParams: { id },
|
||||
data: prepareIncidentIORequest(selectedConfig),
|
||||
});
|
||||
notifications.success({
|
||||
message: 'Success',
|
||||
description: t('channel_edit_done'),
|
||||
});
|
||||
history.replace(ROUTES.ALL_CHANNELS);
|
||||
return { status: 'success', statusMessage: t('channel_edit_done') };
|
||||
} catch (error) {
|
||||
const apiError = notifyError(error);
|
||||
return {
|
||||
status: 'failed',
|
||||
statusMessage: apiError.getErrorMessage() || t('channel_edit_failed'),
|
||||
};
|
||||
} finally {
|
||||
setSavingState(false);
|
||||
}
|
||||
}, [
|
||||
validateIncidentIOConfig,
|
||||
updateChannel,
|
||||
id,
|
||||
selectedConfig,
|
||||
notifications,
|
||||
notifyError,
|
||||
t,
|
||||
]);
|
||||
|
||||
const onSaveHandler = useCallback(
|
||||
async (value: ChannelType) => {
|
||||
let result;
|
||||
@@ -599,6 +658,8 @@ function EditAlertChannels({
|
||||
result = await onJiraEditHandler();
|
||||
} else if (value === ChannelType.JsmOps) {
|
||||
result = await onJsmOpsEditHandler();
|
||||
} else if (value === ChannelType.IncidentIO) {
|
||||
result = await onIncidentIOEditHandler();
|
||||
}
|
||||
logEvent('Alert Channel: Save channel', {
|
||||
type: value,
|
||||
@@ -620,6 +681,7 @@ function EditAlertChannels({
|
||||
onGoogleChatEditHandler,
|
||||
onJiraEditHandler,
|
||||
onJsmOpsEditHandler,
|
||||
onIncidentIOEditHandler,
|
||||
],
|
||||
);
|
||||
|
||||
@@ -701,6 +763,19 @@ function EditAlertChannels({
|
||||
await testChannel({ data: prepareJsmOpsRequest(selectedConfig) });
|
||||
break;
|
||||
}
|
||||
case ChannelType.IncidentIO: {
|
||||
const validationError = validateIncidentIOConfig();
|
||||
if (validationError !== '') {
|
||||
notifications.error({
|
||||
message: 'Error',
|
||||
description: validationError,
|
||||
});
|
||||
setTestingState(false);
|
||||
return;
|
||||
}
|
||||
await testChannel({ data: prepareIncidentIORequest(selectedConfig) });
|
||||
break;
|
||||
}
|
||||
default:
|
||||
notifications.error({
|
||||
message: 'Error',
|
||||
@@ -740,6 +815,7 @@ function EditAlertChannels({
|
||||
validateGoogleChatConfig,
|
||||
validateJiraConfig,
|
||||
validateJsmOpsConfig,
|
||||
validateIncidentIOConfig,
|
||||
testChannel,
|
||||
prepareWebhookRequest,
|
||||
preparePagerRequest,
|
||||
|
||||
@@ -452,15 +452,15 @@ function ExplorerOptions({
|
||||
if (handleChangeSelectedView) {
|
||||
handleChangeSelectedView(panelTypeToExplorerView[currentPanelType], {
|
||||
query,
|
||||
name,
|
||||
id,
|
||||
viewName: name,
|
||||
viewKey: id,
|
||||
});
|
||||
} else {
|
||||
// to remove this after traces cleanup
|
||||
handleExplorerTabChange(currentPanelType, {
|
||||
query,
|
||||
name,
|
||||
id,
|
||||
viewName: name,
|
||||
viewKey: id,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
172
frontend/src/container/FormAlertChannels/Settings/IncidentIo.tsx
Normal file
172
frontend/src/container/FormAlertChannels/Settings/IncidentIo.tsx
Normal file
@@ -0,0 +1,172 @@
|
||||
import { Dispatch, SetStateAction, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Minus, Plus } from '@signozhq/icons';
|
||||
import { Button, Form, Input } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
|
||||
import { IncidentIOChannel } from '../../CreateAlertChannels/config';
|
||||
|
||||
interface MetadataRow {
|
||||
key: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
function IncidentIOSettings({
|
||||
setSelectedConfig,
|
||||
initialMetadata,
|
||||
}: IncidentIOProps): JSX.Element {
|
||||
const { t } = useTranslation('channels');
|
||||
const [metadataRows, setMetadataRows] = useState<MetadataRow[]>(() =>
|
||||
Object.entries(initialMetadata || {}).map(([key, value]) => ({
|
||||
key,
|
||||
value,
|
||||
})),
|
||||
);
|
||||
|
||||
const update = (patch: Partial<IncidentIOChannel>): void =>
|
||||
setSelectedConfig((value) => ({ ...value, ...patch }));
|
||||
|
||||
const syncMetadata = (rows: MetadataRow[]): void => {
|
||||
setMetadataRows(rows);
|
||||
update({
|
||||
metadata: Object.fromEntries(
|
||||
rows
|
||||
.filter((row) => row.key.trim() !== '')
|
||||
.map((row) => [row.key, row.value]),
|
||||
),
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Typography.Text
|
||||
color="muted"
|
||||
size="sm"
|
||||
testId="incidentio-tip"
|
||||
style={{ display: 'block', marginBottom: 16 }}
|
||||
>
|
||||
{t('incidentio_tip')}{' '}
|
||||
<Typography.Link
|
||||
href="https://signoz.io/docs/alerts-management/notification-channel/incidentio/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{t('incidentio_tip_link')}
|
||||
</Typography.Link>
|
||||
</Typography.Text>
|
||||
|
||||
<Form.Item
|
||||
name="url"
|
||||
label={t('field_incidentio_url')}
|
||||
help={t('help_incidentio_url')}
|
||||
required
|
||||
>
|
||||
<Input
|
||||
onChange={(event): void => update({ url: event.target.value })}
|
||||
data-testid="incidentio-url-textbox"
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="token"
|
||||
label={t('field_incidentio_token')}
|
||||
help={t('help_incidentio_token')}
|
||||
required
|
||||
>
|
||||
<Input
|
||||
type="password"
|
||||
onChange={(event): void => update({ token: event.target.value })}
|
||||
data-testid="incidentio-token-textbox"
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="title"
|
||||
label={t('field_incidentio_title')}
|
||||
help={t('help_incidentio_title')}
|
||||
>
|
||||
<Input.TextArea
|
||||
rows={2}
|
||||
onChange={(event): void => update({ title: event.target.value })}
|
||||
data-testid="incidentio-title-textarea"
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="description"
|
||||
label={t('field_incidentio_description')}
|
||||
help={t('help_incidentio_description')}
|
||||
>
|
||||
<Input.TextArea
|
||||
rows={6}
|
||||
onChange={(event): void => update({ description: event.target.value })}
|
||||
data-testid="incidentio-description-textarea"
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label={t('field_incidentio_metadata')}
|
||||
help={t('help_incidentio_metadata')}
|
||||
>
|
||||
{metadataRows.map((row, index) => (
|
||||
// rows have no stable identity beyond their position
|
||||
// eslint-disable-next-line react/no-array-index-key
|
||||
<div key={index} style={{ display: 'flex', gap: 8, marginBottom: 8 }}>
|
||||
<Input
|
||||
placeholder={t('placeholder_incidentio_metadata_key')}
|
||||
value={row.key}
|
||||
onChange={(event): void =>
|
||||
syncMetadata(
|
||||
metadataRows.map((r, i) =>
|
||||
i === index ? { ...r, key: event.target.value } : r,
|
||||
),
|
||||
)
|
||||
}
|
||||
data-testid={`incidentio-metadata-key-${index}`}
|
||||
/>
|
||||
<Input
|
||||
placeholder={t('placeholder_incidentio_metadata_value')}
|
||||
value={row.value}
|
||||
onChange={(event): void =>
|
||||
syncMetadata(
|
||||
metadataRows.map((r, i) =>
|
||||
i === index ? { ...r, value: event.target.value } : r,
|
||||
),
|
||||
)
|
||||
}
|
||||
data-testid={`incidentio-metadata-value-${index}`}
|
||||
/>
|
||||
<Button
|
||||
icon={<Minus size={14} />}
|
||||
onClick={(): void =>
|
||||
syncMetadata(metadataRows.filter((_, i) => i !== index))
|
||||
}
|
||||
data-testid={`incidentio-metadata-remove-${index}`}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
<Button
|
||||
type="dashed"
|
||||
icon={<Plus size={14} />}
|
||||
onClick={(): void =>
|
||||
syncMetadata([...metadataRows, { key: '', value: '' }])
|
||||
}
|
||||
data-testid="incidentio-metadata-add"
|
||||
>
|
||||
{t('button_incidentio_add_metadata')}
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
interface IncidentIOProps {
|
||||
setSelectedConfig: Dispatch<SetStateAction<Partial<IncidentIOChannel>>>;
|
||||
initialMetadata?: Record<string, string>;
|
||||
}
|
||||
|
||||
IncidentIOSettings.defaultProps = {
|
||||
initialMetadata: undefined,
|
||||
};
|
||||
|
||||
export default IncidentIOSettings;
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
ChannelType,
|
||||
EmailChannel,
|
||||
GoogleChatChannel,
|
||||
IncidentIOChannel,
|
||||
JiraChannel,
|
||||
JsmOpsChannel,
|
||||
OpsgenieChannel,
|
||||
@@ -21,6 +22,7 @@ import history from 'lib/history';
|
||||
|
||||
import EmailSettings from './Settings/Email';
|
||||
import GoogleChatSettings from './Settings/GoogleChat';
|
||||
import IncidentIOSettings from './Settings/IncidentIo';
|
||||
import JiraSettings from './Settings/Jira';
|
||||
import JsmOpsSettings from './Settings/JsmOps';
|
||||
import MsTeamsSettings from './Settings/MsTeams';
|
||||
@@ -61,6 +63,13 @@ function FormAlertChannels({
|
||||
return <JiraSettings setSelectedConfig={setSelectedConfig} />;
|
||||
case ChannelType.JsmOps:
|
||||
return <JsmOpsSettings setSelectedConfig={setSelectedConfig} />;
|
||||
case ChannelType.IncidentIO:
|
||||
return (
|
||||
<IncidentIOSettings
|
||||
setSelectedConfig={setSelectedConfig}
|
||||
initialMetadata={initialValue?.metadata as Record<string, string>}
|
||||
/>
|
||||
);
|
||||
case ChannelType.Opsgenie:
|
||||
return <OpsgenieSettings setSelectedConfig={setSelectedConfig} />;
|
||||
case ChannelType.Email:
|
||||
@@ -157,6 +166,14 @@ function FormAlertChannels({
|
||||
<Select.Option value="jsmops" key="jsmops" data-testid="select-option">
|
||||
Jira Service Management Ops
|
||||
</Select.Option>
|
||||
|
||||
<Select.Option
|
||||
value="incidentio"
|
||||
key="incidentio"
|
||||
data-testid="select-option"
|
||||
>
|
||||
incident.io
|
||||
</Select.Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
@@ -207,7 +224,8 @@ interface FormAlertChannelsProps {
|
||||
EmailChannel &
|
||||
GoogleChatChannel &
|
||||
JiraChannel &
|
||||
JsmOpsChannel
|
||||
JsmOpsChannel &
|
||||
IncidentIOChannel
|
||||
>
|
||||
>
|
||||
>;
|
||||
|
||||
@@ -16,6 +16,8 @@ import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import { StatusCodes } from 'http-status-codes';
|
||||
import find from 'lodash-es/find';
|
||||
import { AuthZGuardContent } from 'lib/authz/components/AuthZGuard/AuthZGuardContent';
|
||||
import { buildLicenseReadPermission } from 'lib/authz/hooks/useAuthZ/permissions/license.permissions';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
import {
|
||||
ErrorResponse,
|
||||
@@ -673,17 +675,21 @@ function GeneralSettings({
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{(showCustomDomainSettings || activeLicense?.key) && (
|
||||
{(showCustomDomainSettings || activeLicense) && (
|
||||
<div className="custom-domain-card">
|
||||
{showCustomDomainSettings && <CustomDomainSettings />}
|
||||
{showCustomDomainSettings && activeLicense?.key && (
|
||||
{showCustomDomainSettings && activeLicense && (
|
||||
<div className="custom-domain-card-divider" />
|
||||
)}
|
||||
{activeLicense?.key && (
|
||||
<>
|
||||
<LicenseKeyRow />
|
||||
<LicenseRowDismissibleCallout />
|
||||
</>
|
||||
{activeLicense && (
|
||||
<AuthZGuardContent
|
||||
checks={[buildLicenseReadPermission(activeLicense.id)]}
|
||||
>
|
||||
<>
|
||||
<LicenseKeyRow />
|
||||
<LicenseRowDismissibleCallout />
|
||||
</>
|
||||
</AuthZGuardContent>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -2,16 +2,16 @@ import { useCopyToClipboard } from 'react-use';
|
||||
import { Copy, KeyRound } from '@signozhq/icons';
|
||||
import { Button } from '@signozhq/ui/button';
|
||||
import { toast } from '@signozhq/ui/sonner';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
import useActiveLicenseKey from 'hooks/useActiveLicenseKey/useActiveLicenseKey';
|
||||
import { getMaskedKey } from 'utils/maskedKey';
|
||||
|
||||
import './LicenseKeyRow.styles.scss';
|
||||
|
||||
function LicenseKeyRow(): JSX.Element | null {
|
||||
const { activeLicense } = useAppContext();
|
||||
const { licenseKey } = useActiveLicenseKey();
|
||||
const [, copyToClipboard] = useCopyToClipboard();
|
||||
|
||||
if (!activeLicense?.key) {
|
||||
if (!licenseKey) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -27,16 +27,14 @@ function LicenseKeyRow(): JSX.Element | null {
|
||||
<span className="license-key-row__label">SigNoz License Key</span>
|
||||
</span>
|
||||
<span className="license-key-row__value">
|
||||
<code className="license-key-row__code">
|
||||
{getMaskedKey(activeLicense.key)}
|
||||
</code>
|
||||
<code className="license-key-row__code">{getMaskedKey(licenseKey)}</code>
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
aria-label="Copy license key"
|
||||
data-testid="license-key-row-copy-btn"
|
||||
className="license-key-row__copy-btn"
|
||||
onClick={(): void => handleCopyLicenseKey(activeLicense.key)}
|
||||
onClick={(): void => handleCopyLicenseKey(licenseKey)}
|
||||
>
|
||||
<Copy size={12} />
|
||||
</Button>
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
import { render, screen, userEvent, waitFor } from 'tests/test-utils';
|
||||
import useActiveLicenseKey from 'hooks/useActiveLicenseKey/useActiveLicenseKey';
|
||||
|
||||
import LicenseKeyRow from '../LicenseKeyRow';
|
||||
|
||||
jest.mock('hooks/useActiveLicenseKey/useActiveLicenseKey');
|
||||
const mockUseActiveLicenseKey = useActiveLicenseKey as jest.MockedFunction<
|
||||
typeof useActiveLicenseKey
|
||||
>;
|
||||
|
||||
const mockCopyToClipboard = jest.fn();
|
||||
|
||||
jest.mock('react-use', () => ({
|
||||
@@ -23,20 +29,22 @@ describe('LicenseKeyRow', () => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('renders nothing when activeLicense key is absent', () => {
|
||||
const { container } = render(<LicenseKeyRow />, undefined, {
|
||||
appContextOverrides: { activeLicense: null },
|
||||
it('renders nothing when the license key is absent', () => {
|
||||
mockUseActiveLicenseKey.mockReturnValue({
|
||||
licenseKey: undefined,
|
||||
isLoading: false,
|
||||
});
|
||||
const { container } = render(<LicenseKeyRow />);
|
||||
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
it('renders label and masked key when activeLicense key exists', () => {
|
||||
render(<LicenseKeyRow />, undefined, {
|
||||
appContextOverrides: {
|
||||
activeLicense: { key: 'abcdefghij' } as any,
|
||||
},
|
||||
it('renders label and masked key when the license key exists', () => {
|
||||
mockUseActiveLicenseKey.mockReturnValue({
|
||||
licenseKey: 'abcdefghij',
|
||||
isLoading: false,
|
||||
});
|
||||
render(<LicenseKeyRow />);
|
||||
|
||||
expect(screen.getByText('SigNoz License Key')).toBeInTheDocument();
|
||||
expect(screen.getByText('ab·······ij')).toBeInTheDocument();
|
||||
@@ -45,6 +53,10 @@ describe('LicenseKeyRow', () => {
|
||||
it('calls copyToClipboard and shows success toast when clipboard is available', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
|
||||
mockUseActiveLicenseKey.mockReturnValue({
|
||||
licenseKey: 'test-key',
|
||||
isLoading: false,
|
||||
});
|
||||
render(<LicenseKeyRow />);
|
||||
|
||||
await user.click(screen.getByRole('button', { name: /copy license key/i }));
|
||||
|
||||
@@ -115,8 +115,8 @@ export default function SavedViews({
|
||||
currentPanelType,
|
||||
{
|
||||
query,
|
||||
name,
|
||||
id,
|
||||
viewName: name,
|
||||
viewKey: id,
|
||||
},
|
||||
SOURCEPAGE_VS_ROUTES[selectedEntity],
|
||||
);
|
||||
|
||||
@@ -2,9 +2,9 @@ import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Button, Form } from 'antd';
|
||||
import apply from 'api/v3/licenses/post';
|
||||
import { activateLicense } from 'api/generated/services/licenses';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import APIError from 'types/api/error';
|
||||
import { toAPIError } from 'utils/errorUtils';
|
||||
import { requireErrorMessage } from 'utils/form/requireErrorMessage';
|
||||
|
||||
import {
|
||||
@@ -26,7 +26,7 @@ function ApplyLicenseForm({
|
||||
|
||||
const isDisabled = isLoading || !key;
|
||||
|
||||
const onFinish = async (values: unknown | { key: string }): Promise<void> => {
|
||||
const onFinish = async (values: unknown): Promise<void> => {
|
||||
const params = values as { key: string };
|
||||
if (params.key === '' || !params.key) {
|
||||
notifications.error({
|
||||
@@ -38,18 +38,19 @@ function ApplyLicenseForm({
|
||||
|
||||
setIsLoading(true);
|
||||
try {
|
||||
await apply({
|
||||
await activateLicense({
|
||||
key: params.key,
|
||||
});
|
||||
await Promise.all([licenseRefetch()]);
|
||||
licenseRefetch();
|
||||
notifications.success({
|
||||
message: 'Success',
|
||||
description: t('license_applied'),
|
||||
});
|
||||
} catch (e) {
|
||||
const apiError = toAPIError(e as Parameters<typeof toAPIError>[0]);
|
||||
notifications.error({
|
||||
message: (e as APIError).getErrorCode(),
|
||||
description: (e as APIError).getErrorMessage(),
|
||||
message: apiError.getErrorCode(),
|
||||
description: apiError.getErrorMessage(),
|
||||
});
|
||||
}
|
||||
setIsLoading(false);
|
||||
|
||||
@@ -16,7 +16,6 @@ import {
|
||||
} from 'constants/antlrQueryConstants';
|
||||
import { FeatureKeys } from 'constants/features';
|
||||
import { useActiveLog } from 'hooks/logs/useActiveLog';
|
||||
import { useGetSavedViewParams } from 'hooks/saveViews/useGetSavedViewParams';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { ICurrentQueryData } from 'hooks/useHandleExplorerTabChange';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
@@ -49,7 +48,6 @@ function BodyTitleRenderer({
|
||||
const { featureFlags } = useAppContext();
|
||||
const [, setCopy] = useCopyToClipboard();
|
||||
const { notifications } = useNotifications();
|
||||
const { viewName } = useGetSavedViewParams();
|
||||
|
||||
const cleanedNodeKey = removeObjectFromString(nodeKey);
|
||||
const isBodyJsonQueryEnabled =
|
||||
@@ -123,8 +121,6 @@ function BodyTitleRenderer({
|
||||
);
|
||||
|
||||
const queryData: ICurrentQueryData = {
|
||||
name: viewName,
|
||||
id: updatedQuery.id,
|
||||
query: updatedQuery,
|
||||
};
|
||||
|
||||
@@ -137,7 +133,6 @@ function BodyTitleRenderer({
|
||||
stagedQuery,
|
||||
updateQueriesData,
|
||||
value,
|
||||
viewName,
|
||||
]);
|
||||
|
||||
const onClickHandler = (key: string): void => {
|
||||
|
||||
@@ -12,7 +12,6 @@ import ROUTES from 'constants/routes';
|
||||
import { ChangeViewFunctionType } from 'container/ExplorerOptions/types';
|
||||
import { RESTRICTED_SELECTED_FIELDS } from 'container/LogsFilters/config';
|
||||
import { MetricsType } from 'container/MetricsApplication/constant';
|
||||
import { useGetSavedViewParams } from 'hooks/saveViews/useGetSavedViewParams';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { ICurrentQueryData } from 'hooks/useHandleExplorerTabChange';
|
||||
import {
|
||||
@@ -140,7 +139,6 @@ export default function TableViewActions(
|
||||
|
||||
const { pathname } = useLocation();
|
||||
const { stagedQuery, updateQueriesData } = useQueryBuilder();
|
||||
const { viewName } = useGetSavedViewParams();
|
||||
const { dataType, logType: fieldType } = getFieldAttributes(record.field);
|
||||
|
||||
// there is no option for where clause in old logs explorer and live logs page or infra monitoring
|
||||
@@ -201,8 +199,6 @@ export default function TableViewActions(
|
||||
);
|
||||
|
||||
const queryData: ICurrentQueryData = {
|
||||
name: viewName,
|
||||
id: updatedQuery.id,
|
||||
query: updatedQuery,
|
||||
};
|
||||
|
||||
@@ -214,7 +210,6 @@ export default function TableViewActions(
|
||||
fieldType,
|
||||
dataType,
|
||||
handleChangeSelectedView,
|
||||
viewName,
|
||||
]);
|
||||
|
||||
const handleReplaceFilter = useCallback((): void => {
|
||||
@@ -264,8 +259,6 @@ export default function TableViewActions(
|
||||
);
|
||||
|
||||
const queryData: ICurrentQueryData = {
|
||||
name: viewName,
|
||||
id: updatedQuery.id,
|
||||
query: updatedQuery,
|
||||
};
|
||||
|
||||
@@ -278,7 +271,6 @@ export default function TableViewActions(
|
||||
dataType,
|
||||
fieldData,
|
||||
handleChangeSelectedView,
|
||||
viewName,
|
||||
]);
|
||||
|
||||
// Memoize textToCopy computation
|
||||
|
||||
@@ -272,8 +272,6 @@ describe('TableViewActions', () => {
|
||||
expect(defaultProps.handleChangeSelectedView).toHaveBeenCalledWith(
|
||||
ExplorerViews.TIMESERIES,
|
||||
expect.objectContaining({
|
||||
name: '',
|
||||
id: 'test-query-id',
|
||||
query: expect.objectContaining({
|
||||
builder: expect.objectContaining({
|
||||
queryData: expect.arrayContaining([
|
||||
|
||||
@@ -5,7 +5,6 @@ import { convertFiltersToExpression } from 'components/QueryBuilderV2/utils';
|
||||
import { FeatureKeys } from 'constants/features';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { ChangeViewFunctionType } from 'container/ExplorerOptions/types';
|
||||
import { useGetSavedViewParams } from 'hooks/saveViews/useGetSavedViewParams';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { ICurrentQueryData } from 'hooks/useHandleExplorerTabChange';
|
||||
import { ExplorerViews } from 'pages/LogsExplorer/utils';
|
||||
@@ -57,7 +56,6 @@ export function useLogAttributeActions({
|
||||
const { pathname } = useLocation();
|
||||
const { stagedQuery, updateQueriesData } = useQueryBuilder();
|
||||
const { featureFlags } = useAppContext();
|
||||
const { viewName } = useGetSavedViewParams();
|
||||
|
||||
const isBodyJsonQueryEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.USE_JSON_BODY)
|
||||
@@ -110,8 +108,6 @@ export function useLogAttributeActions({
|
||||
);
|
||||
|
||||
const queryData: ICurrentQueryData = {
|
||||
name: viewName,
|
||||
id: updatedQuery.id,
|
||||
query: updatedQuery,
|
||||
};
|
||||
handleChangeSelectedView?.(ExplorerViews.LIST, queryData);
|
||||
@@ -120,7 +116,6 @@ export function useLogAttributeActions({
|
||||
stagedQuery,
|
||||
isBodyJsonQueryEnabled,
|
||||
updateQueriesData,
|
||||
viewName,
|
||||
handleChangeSelectedView,
|
||||
onApplyLogFilter,
|
||||
],
|
||||
@@ -147,8 +142,6 @@ export function useLogAttributeActions({
|
||||
);
|
||||
|
||||
const queryData: ICurrentQueryData = {
|
||||
name: viewName,
|
||||
id: updatedQuery.id,
|
||||
query: updatedQuery,
|
||||
};
|
||||
handleChangeSelectedView?.(ExplorerViews.TIMESERIES, queryData);
|
||||
@@ -157,7 +150,6 @@ export function useLogAttributeActions({
|
||||
stagedQuery,
|
||||
isBodyJsonQueryEnabled,
|
||||
updateQueriesData,
|
||||
viewName,
|
||||
handleChangeSelectedView,
|
||||
],
|
||||
);
|
||||
@@ -183,8 +175,6 @@ export function useLogAttributeActions({
|
||||
);
|
||||
|
||||
const queryData: ICurrentQueryData = {
|
||||
name: viewName,
|
||||
id: updatedQuery.id,
|
||||
query: updatedQuery,
|
||||
};
|
||||
handleChangeSelectedView?.(ExplorerViews.LIST, queryData);
|
||||
@@ -193,7 +183,6 @@ export function useLogAttributeActions({
|
||||
stagedQuery,
|
||||
isBodyJsonQueryEnabled,
|
||||
updateQueriesData,
|
||||
viewName,
|
||||
handleChangeSelectedView,
|
||||
],
|
||||
);
|
||||
|
||||
@@ -6,6 +6,7 @@ import logEvent from 'api/common/logEvent';
|
||||
import cx from 'classnames';
|
||||
import { QueryBuilderV2 } from 'components/QueryBuilderV2/QueryBuilderV2';
|
||||
import QuickFilters from 'components/QuickFilters/QuickFilters';
|
||||
import { useSignalFieldApis } from 'components/QuickFilters/hooks/useSignalFieldApis';
|
||||
import { QuickFiltersSource, SignalType } from 'components/QuickFilters/types';
|
||||
import { initialQueryMeterWithType, PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
||||
@@ -31,6 +32,7 @@ import { splitQueryIntoOneChartPerQuery } from './utils';
|
||||
import './Explorer.styles.scss';
|
||||
|
||||
function Explorer(): JSX.Element {
|
||||
const quickFilterFieldApis = useSignalFieldApis();
|
||||
const {
|
||||
handleRunQuery,
|
||||
stagedQuery,
|
||||
@@ -141,6 +143,7 @@ function Explorer(): JSX.Element {
|
||||
handleFilterVisibilityChange={(): void => {
|
||||
setShowQuickFilters(!showQuickFilters);
|
||||
}}
|
||||
useFieldApis={quickFilterFieldApis}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -78,8 +78,6 @@ function AllAttributes({
|
||||
PANEL_TYPES.TIME_SERIES,
|
||||
{
|
||||
query: compositeQuery,
|
||||
name: metricName,
|
||||
id: metricName,
|
||||
},
|
||||
ROUTES.METRICS_EXPLORER_EXPLORER,
|
||||
true,
|
||||
@@ -109,8 +107,6 @@ function AllAttributes({
|
||||
PANEL_TYPES.TIME_SERIES,
|
||||
{
|
||||
query: compositeQuery,
|
||||
name: metricName,
|
||||
id: metricName,
|
||||
},
|
||||
ROUTES.METRICS_EXPLORER_EXPLORER,
|
||||
true,
|
||||
|
||||
@@ -92,8 +92,6 @@ function MetricDetails({
|
||||
PANEL_TYPES.TIME_SERIES,
|
||||
{
|
||||
query: compositeQuery,
|
||||
name: metricName,
|
||||
id: metricName,
|
||||
},
|
||||
ROUTES.METRICS_EXPLORER_EXPLORER,
|
||||
true,
|
||||
|
||||
@@ -3,13 +3,16 @@ import { Button } from '@signozhq/ui/button';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import { Copy } from '@signozhq/icons';
|
||||
import useActiveLicenseKey from 'hooks/useActiveLicenseKey/useActiveLicenseKey';
|
||||
import { AuthZGuardContent } from 'lib/authz/components/AuthZGuard/AuthZGuardContent';
|
||||
import { buildLicenseReadPermission } from 'lib/authz/hooks/useAuthZ/permissions/license.permissions';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
import { getMaskedKey } from 'utils/maskedKey';
|
||||
|
||||
import './LicenseSection.styles.scss';
|
||||
|
||||
function LicenseSection(): JSX.Element | null {
|
||||
const { activeLicense } = useAppContext();
|
||||
function LicenseSectionContent(): JSX.Element | null {
|
||||
const { licenseKey } = useActiveLicenseKey();
|
||||
const { notifications } = useNotifications();
|
||||
const [, handleCopyToClipboard] = useCopyToClipboard();
|
||||
|
||||
@@ -20,7 +23,41 @@ function LicenseSection(): JSX.Element | null {
|
||||
});
|
||||
};
|
||||
|
||||
if (!activeLicense?.key) {
|
||||
if (!licenseKey) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="license-section-content">
|
||||
<div className="license-section-content-item">
|
||||
<div className="license-section-content-item-title-action">
|
||||
<span>License key</span>
|
||||
<span style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
||||
<Typography.Text code>{getMaskedKey(licenseKey)}</Typography.Text>
|
||||
<Button
|
||||
variant="link"
|
||||
color="none"
|
||||
aria-label="Copy license key"
|
||||
data-testid="license-key-copy-btn"
|
||||
onClick={(): void => handleCopyKey(licenseKey)}
|
||||
>
|
||||
<Copy size={14} />
|
||||
</Button>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="license-section-content-item-description">
|
||||
Your SigNoz license key.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function LicenseSection(): JSX.Element | null {
|
||||
const { activeLicense } = useAppContext();
|
||||
|
||||
if (!activeLicense) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
@@ -30,29 +67,9 @@ function LicenseSection(): JSX.Element | null {
|
||||
<div className="license-section-title">License</div>
|
||||
</div>
|
||||
|
||||
<div className="license-section-content">
|
||||
<div className="license-section-content-item">
|
||||
<div className="license-section-content-item-title-action">
|
||||
<span>License key</span>
|
||||
<span style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
||||
<Typography.Text code>{getMaskedKey(activeLicense.key)}</Typography.Text>
|
||||
<Button
|
||||
variant="link"
|
||||
color="none"
|
||||
aria-label="Copy license key"
|
||||
data-testid="license-key-copy-btn"
|
||||
onClick={(): void => handleCopyKey(activeLicense.key)}
|
||||
>
|
||||
<Copy size={14} />
|
||||
</Button>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="license-section-content-item-description">
|
||||
Your SigNoz license key.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<AuthZGuardContent checks={[buildLicenseReadPermission(activeLicense.id)]}>
|
||||
<LicenseSectionContent />
|
||||
</AuthZGuardContent>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import MySettingsContainer from 'container/MySettings';
|
||||
import useActiveLicenseKey from 'hooks/useActiveLicenseKey/useActiveLicenseKey';
|
||||
import {
|
||||
setupAuthzAdmin,
|
||||
setupAuthzDenyAll,
|
||||
} from 'lib/authz/utils/authz-test-utils';
|
||||
import { server } from 'mocks-server/server';
|
||||
import { logEventMock } from '__tests__/logEventMock';
|
||||
import {
|
||||
act,
|
||||
@@ -12,6 +18,11 @@ import {
|
||||
import APIError from 'types/api/error';
|
||||
import { toast } from '@signozhq/ui/sonner';
|
||||
|
||||
jest.mock('hooks/useActiveLicenseKey/useActiveLicenseKey');
|
||||
const mockUseActiveLicenseKey = useActiveLicenseKey as jest.MockedFunction<
|
||||
typeof useActiveLicenseKey
|
||||
>;
|
||||
|
||||
const toggleThemeFunction = jest.fn();
|
||||
const copyToClipboardFn = jest.fn();
|
||||
const editUserFn = jest.fn();
|
||||
@@ -87,6 +98,10 @@ describe('MySettings Flows', () => {
|
||||
jest.clearAllMocks();
|
||||
editUserFn.mockResolvedValue({});
|
||||
updateMyPasswordFn.mockResolvedValue({});
|
||||
mockUseActiveLicenseKey.mockReturnValue({
|
||||
licenseKey: 'test-key',
|
||||
isLoading: false,
|
||||
});
|
||||
render(<MySettingsContainer />);
|
||||
});
|
||||
|
||||
@@ -361,17 +376,27 @@ describe('MySettings Flows', () => {
|
||||
});
|
||||
|
||||
describe('License section', () => {
|
||||
it('Should render license section content when license key exists', () => {
|
||||
beforeEach(() => {
|
||||
server.use(setupAuthzAdmin());
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
server.resetHandlers();
|
||||
});
|
||||
|
||||
it('Should render license section content when license key exists', async () => {
|
||||
expect(screen.getByText('License')).toBeInTheDocument();
|
||||
expect(screen.getByText('License key')).toBeInTheDocument();
|
||||
await expect(screen.findByText('License key')).resolves.toBeInTheDocument();
|
||||
expect(screen.getByText('Your SigNoz license key.')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Should not render license section when license key is missing', () => {
|
||||
it('Should not render license section when there is no active license', () => {
|
||||
mockUseActiveLicenseKey.mockReturnValue({
|
||||
licenseKey: undefined,
|
||||
isLoading: false,
|
||||
});
|
||||
const { container } = render(<MySettingsContainer />, undefined, {
|
||||
appContextOverrides: {
|
||||
activeLicense: null,
|
||||
},
|
||||
appContextOverrides: { activeLicense: null },
|
||||
});
|
||||
|
||||
const scoped = within(container);
|
||||
@@ -382,41 +407,53 @@ describe('MySettings Flows', () => {
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Should mask license key in the UI', () => {
|
||||
const { container } = render(<MySettingsContainer />, undefined, {
|
||||
appContextOverrides: {
|
||||
activeLicense: {
|
||||
key: 'abcd',
|
||||
} as any,
|
||||
},
|
||||
});
|
||||
it('Should show permission denied instead of the license key when read is denied', async () => {
|
||||
server.use(setupAuthzDenyAll());
|
||||
const { container } = render(<MySettingsContainer />);
|
||||
|
||||
expect(within(container).getByText('ab·······cd')).toBeInTheDocument();
|
||||
const scoped = within(container);
|
||||
await expect(
|
||||
scoped.findByText(/not authorized/i),
|
||||
).resolves.toBeInTheDocument();
|
||||
expect(scoped.getByText('License')).toBeInTheDocument();
|
||||
expect(scoped.queryByText('License key')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Should not mask license key if it is too short', () => {
|
||||
const { container } = render(<MySettingsContainer />, undefined, {
|
||||
appContextOverrides: {
|
||||
activeLicense: {
|
||||
key: 'abc',
|
||||
} as any,
|
||||
},
|
||||
it('Should mask license key in the UI', async () => {
|
||||
mockUseActiveLicenseKey.mockReturnValue({
|
||||
licenseKey: 'abcd',
|
||||
isLoading: false,
|
||||
});
|
||||
const { container } = render(<MySettingsContainer />);
|
||||
|
||||
expect(within(container).getByText('abc')).toBeInTheDocument();
|
||||
await expect(
|
||||
within(container).findByText('ab·······cd'),
|
||||
).resolves.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Should not mask license key if it is too short', async () => {
|
||||
mockUseActiveLicenseKey.mockReturnValue({
|
||||
licenseKey: 'abc',
|
||||
isLoading: false,
|
||||
});
|
||||
const { container } = render(<MySettingsContainer />);
|
||||
|
||||
await expect(
|
||||
within(container).findByText('abc'),
|
||||
).resolves.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Should copy license key and show success toast', async () => {
|
||||
const user = userEvent.setup();
|
||||
const { container } = render(<MySettingsContainer />, undefined, {
|
||||
appContextOverrides: {
|
||||
activeLicense: {
|
||||
key: 'test-license-key-12345',
|
||||
} as any,
|
||||
},
|
||||
mockUseActiveLicenseKey.mockReturnValue({
|
||||
licenseKey: 'test-license-key-12345',
|
||||
isLoading: false,
|
||||
});
|
||||
const { container } = render(<MySettingsContainer />);
|
||||
|
||||
await user.click(within(container).getByTestId('license-key-copy-btn'));
|
||||
await user.click(
|
||||
await within(container).findByTestId('license-key-copy-btn'),
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(copyToClipboardFn).toHaveBeenCalledWith('test-license-key-12345');
|
||||
|
||||
@@ -329,11 +329,12 @@ describe('transformTransactionGroupsToResourcePermissions', () => {
|
||||
it('returns all resources from RESOURCE_ORDER even with empty transaction groups', () => {
|
||||
const result = transformTransactionGroupsToResourcePermissions([]);
|
||||
|
||||
expect(result).toHaveLength(7);
|
||||
expect(result).toHaveLength(8);
|
||||
expect(result.map((r) => r.resourceKind)).toStrictEqual([
|
||||
'factor-api-key',
|
||||
'role',
|
||||
'serviceaccount',
|
||||
'license',
|
||||
'logs',
|
||||
'traces',
|
||||
'metrics',
|
||||
@@ -418,11 +419,12 @@ describe('createEmptyRolePermissions', () => {
|
||||
it('creates permissions for all resources in RESOURCE_ORDER', () => {
|
||||
const result = createEmptyRolePermissions();
|
||||
|
||||
expect(result).toHaveLength(7);
|
||||
expect(result).toHaveLength(8);
|
||||
expect(result.map((r) => r.resourceKind)).toStrictEqual([
|
||||
'factor-api-key',
|
||||
'role',
|
||||
'serviceaccount',
|
||||
'license',
|
||||
'logs',
|
||||
'traces',
|
||||
'metrics',
|
||||
|
||||
@@ -2,6 +2,7 @@ import {
|
||||
Bot,
|
||||
ChartLine,
|
||||
DraftingCompass,
|
||||
FileKey,
|
||||
Gauge,
|
||||
Key,
|
||||
Logs,
|
||||
@@ -61,6 +62,13 @@ export const RESOURCE_PANELS: Record<AuthZResource, ResourcePanelConfig> = {
|
||||
'Type service account ID, separate multiple with comma or space',
|
||||
docsAnchor: 'service-account',
|
||||
},
|
||||
license: {
|
||||
label: 'Licenses',
|
||||
description: 'Licenses of the workspace, including the license key.',
|
||||
icon: FileKey,
|
||||
selectorPlaceholder: 'Type license ID, separate multiple with comma or space',
|
||||
docsAnchor: 'license',
|
||||
},
|
||||
logs: {
|
||||
label: 'Logs',
|
||||
description: 'Log data collected across the workspace.',
|
||||
|
||||
29
frontend/src/hooks/useActiveLicense/useActiveLicense.tsx
Normal file
29
frontend/src/hooks/useActiveLicense/useActiveLicense.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import { useQuery, UseQueryResult } from 'react-query';
|
||||
import {
|
||||
getActiveLicense,
|
||||
getGetActiveLicenseQueryKey,
|
||||
} from 'api/generated/services/licenses';
|
||||
import APIError from 'types/api/error';
|
||||
import { LicenseResModel } from 'types/api/licensesV3/getActive';
|
||||
import { toAPIError } from 'utils/errorUtils';
|
||||
|
||||
import { toLicenseResModel } from './utils';
|
||||
|
||||
const useActiveLicense = (
|
||||
isLoggedIn: boolean,
|
||||
): UseQueryResult<LicenseResModel, APIError> =>
|
||||
useQuery({
|
||||
queryFn: async (): Promise<LicenseResModel> => {
|
||||
try {
|
||||
const response = await getActiveLicense();
|
||||
return toLicenseResModel(response.data);
|
||||
} catch (error) {
|
||||
throw toAPIError(error as Parameters<typeof toAPIError>[0]);
|
||||
}
|
||||
},
|
||||
queryKey: getGetActiveLicenseQueryKey(),
|
||||
enabled: !!isLoggedIn,
|
||||
retry: false,
|
||||
});
|
||||
|
||||
export default useActiveLicense;
|
||||
37
frontend/src/hooks/useActiveLicense/utils.ts
Normal file
37
frontend/src/hooks/useActiveLicense/utils.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { LicensetypesGettableActiveLicenseDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import {
|
||||
LicenseEvent,
|
||||
LicensePlatform,
|
||||
LicenseResModel,
|
||||
LicenseState,
|
||||
LicenseStatus,
|
||||
} from 'types/api/licensesV3/getActive';
|
||||
|
||||
export const toLicenseResModel = (
|
||||
dto: LicensetypesGettableActiveLicenseDTO,
|
||||
): LicenseResModel => ({
|
||||
id: dto.id,
|
||||
status: dto.status as LicenseStatus,
|
||||
state: dto.state as LicenseState,
|
||||
platform: dto.platform as LicensePlatform,
|
||||
plan: {
|
||||
id: dto.plan.id,
|
||||
name: dto.plan.name,
|
||||
description: dto.plan.description,
|
||||
isActive: dto.plan.isActive,
|
||||
createdAt: dto.plan.createdAt,
|
||||
updatedAt: dto.plan.updatedAt,
|
||||
},
|
||||
eventQueue: {
|
||||
event: dto.eventQueue.event as LicenseEvent,
|
||||
status: dto.eventQueue.status,
|
||||
scheduledAt: dto.eventQueue.scheduledAt,
|
||||
createdAt: dto.eventQueue.createdAt,
|
||||
updatedAt: dto.eventQueue.updatedAt,
|
||||
},
|
||||
freeUntil: dto.freeUntil,
|
||||
createdAt: dto.createdAt,
|
||||
updatedAt: dto.updatedAt,
|
||||
validFrom: dto.validFrom,
|
||||
validUntil: dto.validUntil,
|
||||
});
|
||||
@@ -0,0 +1,35 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useGetLicense } from 'api/generated/services/licenses';
|
||||
import { buildLicenseReadPermission } from 'lib/authz/hooks/useAuthZ/permissions/license.permissions';
|
||||
import { useAuthZ } from 'lib/authz/hooks/useAuthZ/useAuthZ';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
|
||||
interface UseActiveLicenseKey {
|
||||
licenseKey: string | undefined;
|
||||
isLoading: boolean;
|
||||
}
|
||||
|
||||
const useActiveLicenseKey = (): UseActiveLicenseKey => {
|
||||
const { activeLicense } = useAppContext();
|
||||
|
||||
const permissions = useMemo(
|
||||
() => (activeLicense ? [buildLicenseReadPermission(activeLicense.id)] : []),
|
||||
[activeLicense],
|
||||
);
|
||||
const { allowed, isLoading: isAuthZLoading } = useAuthZ(permissions, {
|
||||
enabled: !!activeLicense,
|
||||
});
|
||||
|
||||
const { data, isLoading: isLicenseLoading } = useGetLicense(
|
||||
{ id: activeLicense?.id ?? '' },
|
||||
{ query: { enabled: !!activeLicense && allowed } },
|
||||
);
|
||||
|
||||
return {
|
||||
licenseKey: data?.data.key,
|
||||
isLoading:
|
||||
!!activeLicense && (isAuthZLoading || (allowed && isLicenseLoading)),
|
||||
};
|
||||
};
|
||||
|
||||
export default useActiveLicenseKey;
|
||||
@@ -1,18 +0,0 @@
|
||||
import { useQuery, UseQueryResult } from 'react-query';
|
||||
import getActive from 'api/v3/licenses/active/get';
|
||||
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
||||
import { SuccessResponseV2 } from 'types/api';
|
||||
import APIError from 'types/api/error';
|
||||
import { LicenseResModel } from 'types/api/licensesV3/getActive';
|
||||
|
||||
const useActiveLicenseV3 = (isLoggedIn: boolean): UseLicense =>
|
||||
useQuery({
|
||||
queryFn: getActive,
|
||||
queryKey: [REACT_QUERY_KEY.GET_ACTIVE_LICENSE_V3],
|
||||
enabled: !!isLoggedIn,
|
||||
retry: false,
|
||||
});
|
||||
|
||||
type UseLicense = UseQueryResult<SuccessResponseV2<LicenseResModel>, APIError>;
|
||||
|
||||
export default useActiveLicenseV3;
|
||||
@@ -10,8 +10,8 @@ import { useGetSavedViewParams } from './saveViews/useGetSavedViewParams';
|
||||
import { useQueryBuilder } from './queryBuilder/useQueryBuilder';
|
||||
|
||||
export interface ICurrentQueryData {
|
||||
name: string;
|
||||
id: string;
|
||||
viewName?: string;
|
||||
viewKey?: string;
|
||||
query: Query;
|
||||
}
|
||||
|
||||
@@ -57,6 +57,8 @@ export const useHandleExplorerTabChange = (): {
|
||||
[currentQuery, updateAllQueriesOperators, updateQueriesData],
|
||||
);
|
||||
|
||||
//TODO: this util is used not just to change explorer tab but also
|
||||
// for changing just the query or saved view. consider renaming this.
|
||||
const handleExplorerTabChange = useCallback(
|
||||
(
|
||||
type: string,
|
||||
@@ -77,8 +79,8 @@ export const useHandleExplorerTabChange = (): {
|
||||
query,
|
||||
{
|
||||
[QueryParams.panelTypes]: newPanelType,
|
||||
[QueryParams.viewName]: currentQueryData?.name || viewName,
|
||||
[QueryParams.viewKey]: currentQueryData?.id || viewKey,
|
||||
[QueryParams.viewName]: currentQueryData?.viewName || viewName,
|
||||
[QueryParams.viewKey]: currentQueryData?.viewKey || viewKey,
|
||||
},
|
||||
redirectToUrl,
|
||||
undefined,
|
||||
@@ -89,8 +91,8 @@ export const useHandleExplorerTabChange = (): {
|
||||
query,
|
||||
{
|
||||
[QueryParams.panelTypes]: newPanelType,
|
||||
[QueryParams.viewName]: currentQueryData?.name || viewName,
|
||||
[QueryParams.viewKey]: currentQueryData?.id || viewKey,
|
||||
[QueryParams.viewName]: currentQueryData?.viewName || viewName,
|
||||
[QueryParams.viewKey]: currentQueryData?.viewKey || viewKey,
|
||||
},
|
||||
undefined,
|
||||
undefined,
|
||||
|
||||
@@ -8,6 +8,11 @@ export default {
|
||||
type: 'metaresource',
|
||||
allowedVerbs: ['create', 'delete', 'list', 'read', 'update'],
|
||||
},
|
||||
{
|
||||
kind: 'license',
|
||||
type: 'metaresource',
|
||||
allowedVerbs: ['create', 'delete', 'list', 'read', 'update'],
|
||||
},
|
||||
{
|
||||
kind: 'role',
|
||||
type: 'role',
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import { buildPermission } from '../utils';
|
||||
import type { BrandedPermission } from '../types';
|
||||
|
||||
// Resource-level — require a specific license id
|
||||
export const buildLicenseReadPermission = (id: string): BrandedPermission =>
|
||||
buildPermission('read', `license:${id}`);
|
||||
@@ -127,30 +127,30 @@ export function buildLicense(
|
||||
overrides?: Partial<LicenseResModel>,
|
||||
): LicenseResModel {
|
||||
return {
|
||||
key: 'test-key',
|
||||
id: 'test-license-id',
|
||||
status: LicenseStatus.VALID,
|
||||
state: LicenseState.ACTIVATED,
|
||||
platform: LicensePlatform.CLOUD,
|
||||
event_queue: {
|
||||
created_at: '0',
|
||||
eventQueue: {
|
||||
createdAt: '0',
|
||||
event: LicenseEvent.NO_EVENT,
|
||||
scheduled_at: '0',
|
||||
scheduledAt: '0',
|
||||
status: '',
|
||||
updated_at: '0',
|
||||
updatedAt: '0',
|
||||
},
|
||||
plan: {
|
||||
created_at: '0',
|
||||
id: '0',
|
||||
createdAt: '0',
|
||||
description: '',
|
||||
is_active: true,
|
||||
isActive: true,
|
||||
name: '',
|
||||
updated_at: '0',
|
||||
updatedAt: '0',
|
||||
},
|
||||
plan_id: '0',
|
||||
free_until: '0',
|
||||
updated_at: '0',
|
||||
valid_from: 0,
|
||||
valid_until: 0,
|
||||
created_at: '0',
|
||||
freeUntil: '0',
|
||||
updatedAt: '0',
|
||||
validFrom: 0,
|
||||
validUntil: 0,
|
||||
createdAt: '0',
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -4,114 +4,85 @@ export const quickFiltersListResponse = {
|
||||
signal: 'logs',
|
||||
filters: [
|
||||
{
|
||||
key: 'os.description',
|
||||
dataType: 'string',
|
||||
type: 'resource',
|
||||
name: 'os.description',
|
||||
fieldDataType: 'string',
|
||||
fieldContext: 'resource',
|
||||
},
|
||||
{
|
||||
key: 'service.name',
|
||||
dataType: 'string',
|
||||
type: 'resource',
|
||||
name: 'service.name',
|
||||
fieldDataType: 'string',
|
||||
fieldContext: 'resource',
|
||||
},
|
||||
{
|
||||
key: 'duration_nano',
|
||||
dataType: 'float64',
|
||||
type: 'tag',
|
||||
name: 'duration_nano',
|
||||
fieldDataType: 'float64',
|
||||
fieldContext: 'attribute',
|
||||
},
|
||||
{
|
||||
key: 'quantity',
|
||||
dataType: 'float64',
|
||||
type: 'tag',
|
||||
name: 'quantity',
|
||||
fieldDataType: 'float64',
|
||||
fieldContext: 'attribute',
|
||||
},
|
||||
{
|
||||
key: 'body',
|
||||
dataType: 'string',
|
||||
type: '',
|
||||
name: 'body',
|
||||
fieldDataType: 'string',
|
||||
fieldContext: '',
|
||||
},
|
||||
{
|
||||
key: 'deployment.environment',
|
||||
dataType: 'string',
|
||||
type: 'resource',
|
||||
name: 'deployment.environment',
|
||||
fieldDataType: 'string',
|
||||
fieldContext: 'resource',
|
||||
},
|
||||
{
|
||||
key: 'service.namespace',
|
||||
dataType: 'string',
|
||||
type: 'resource',
|
||||
name: 'service.namespace',
|
||||
fieldDataType: 'string',
|
||||
fieldContext: 'resource',
|
||||
},
|
||||
{
|
||||
key: 'k8s.namespace.name',
|
||||
dataType: 'string',
|
||||
type: 'resource',
|
||||
name: 'k8s.namespace.name',
|
||||
fieldDataType: 'string',
|
||||
fieldContext: 'resource',
|
||||
},
|
||||
{
|
||||
key: 'service.instance.id',
|
||||
dataType: 'string',
|
||||
type: 'resource',
|
||||
name: 'service.instance.id',
|
||||
fieldDataType: 'string',
|
||||
fieldContext: 'resource',
|
||||
},
|
||||
{
|
||||
key: 'k8s.pod.name',
|
||||
dataType: 'string',
|
||||
type: 'resource',
|
||||
name: 'k8s.pod.name',
|
||||
fieldDataType: 'string',
|
||||
fieldContext: 'resource',
|
||||
},
|
||||
{
|
||||
key: 'process.owner',
|
||||
dataType: 'string',
|
||||
type: 'resource',
|
||||
name: 'process.owner',
|
||||
fieldDataType: 'string',
|
||||
fieldContext: 'resource',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const otherFilterName = (name: string): { [k: string]: unknown[] } => ({
|
||||
[name]: [
|
||||
{ name, fieldContext: 'resource', fieldDataType: 'string', signal: 'logs' },
|
||||
],
|
||||
});
|
||||
|
||||
export const otherFiltersResponse = {
|
||||
status: 'success',
|
||||
data: {
|
||||
attributes: [
|
||||
{
|
||||
key: 'service.name',
|
||||
dataType: 'string',
|
||||
type: 'resource',
|
||||
},
|
||||
{
|
||||
key: 'k8s.deployment.name',
|
||||
dataType: 'string',
|
||||
type: 'resource',
|
||||
},
|
||||
{
|
||||
key: 'deployment.environment',
|
||||
dataType: 'string',
|
||||
type: 'resource',
|
||||
},
|
||||
{
|
||||
key: 'service.namespace',
|
||||
dataType: 'string',
|
||||
type: 'resource',
|
||||
},
|
||||
{
|
||||
key: 'k8s.namespace.name',
|
||||
dataType: 'string',
|
||||
type: 'resource',
|
||||
},
|
||||
{
|
||||
key: 'service.instance.id',
|
||||
dataType: 'string',
|
||||
type: 'resource',
|
||||
},
|
||||
{
|
||||
key: 'k8s.pod.name',
|
||||
dataType: 'string',
|
||||
type: 'resource',
|
||||
},
|
||||
{
|
||||
key: 'k8s.pod.uid',
|
||||
dataType: 'string',
|
||||
type: 'resource',
|
||||
},
|
||||
{
|
||||
key: 'os.description',
|
||||
dataType: 'string',
|
||||
type: 'resource',
|
||||
},
|
||||
],
|
||||
complete: true,
|
||||
keys: {
|
||||
...otherFilterName('service.name'),
|
||||
...otherFilterName('k8s.deployment.name'),
|
||||
...otherFilterName('deployment.environment'),
|
||||
...otherFilterName('service.namespace'),
|
||||
...otherFilterName('k8s.namespace.name'),
|
||||
...otherFilterName('service.instance.id'),
|
||||
...otherFilterName('k8s.pod.name'),
|
||||
...otherFilterName('k8s.pod.uid'),
|
||||
...otherFilterName('os.description'),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import setLocalStorageApi from 'api/browser/localstorage/set';
|
||||
import cx from 'classnames';
|
||||
import HeaderRightSection from 'components/HeaderRightSection/HeaderRightSection';
|
||||
import QuickFilters from 'components/QuickFilters/QuickFilters';
|
||||
import { useSignalFieldApis } from 'components/QuickFilters/hooks/useSignalFieldApis';
|
||||
import { QuickFiltersSource, SignalType } from 'components/QuickFilters/types';
|
||||
import RouteTab from 'components/RouteTab';
|
||||
import TypicalOverlayScrollbar from 'components/TypicalOverlayScrollbar/TypicalOverlayScrollbar';
|
||||
@@ -55,6 +56,8 @@ function AllErrors(): JSX.Element {
|
||||
setShowFilters((prev) => !prev);
|
||||
};
|
||||
|
||||
const quickFilterFieldApis = useSignalFieldApis();
|
||||
|
||||
return (
|
||||
<div className={cx('all-errors-page', showFilters ? 'filter-visible' : '')}>
|
||||
{showFilters && (
|
||||
@@ -64,6 +67,7 @@ function AllErrors(): JSX.Element {
|
||||
source={QuickFiltersSource.EXCEPTIONS}
|
||||
signal={SignalType.EXCEPTIONS}
|
||||
handleFilterVisibilityChange={handleFilterVisibilityChange}
|
||||
useFieldApis={quickFilterFieldApis}
|
||||
/>
|
||||
</section>
|
||||
)}
|
||||
|
||||
@@ -11,6 +11,7 @@ import ROUTES from 'constants/routes';
|
||||
import {
|
||||
ChannelType,
|
||||
GoogleChatChannel,
|
||||
IncidentIOChannel,
|
||||
JiraChannel,
|
||||
JsmOpsChannel,
|
||||
MsTeamsChannel,
|
||||
@@ -69,7 +70,8 @@ function ChannelsEdit(): JSX.Element {
|
||||
MsTeamsChannel &
|
||||
GoogleChatChannel &
|
||||
JiraChannel &
|
||||
JsmOpsChannel
|
||||
JsmOpsChannel &
|
||||
IncidentIOChannel
|
||||
>;
|
||||
} => {
|
||||
let channel: Partial<
|
||||
@@ -79,7 +81,8 @@ function ChannelsEdit(): JSX.Element {
|
||||
MsTeamsChannel &
|
||||
GoogleChatChannel &
|
||||
JiraChannel &
|
||||
JsmOpsChannel
|
||||
JsmOpsChannel &
|
||||
IncidentIOChannel
|
||||
> = {
|
||||
name: '',
|
||||
};
|
||||
@@ -135,6 +138,15 @@ function ChannelsEdit(): JSX.Element {
|
||||
};
|
||||
}
|
||||
|
||||
if (value && 'incidentio_configs' in value) {
|
||||
const [incidentIOConfig] = value.incidentio_configs;
|
||||
channel = incidentIOConfig;
|
||||
return {
|
||||
type: ChannelType.IncidentIO,
|
||||
channel,
|
||||
};
|
||||
}
|
||||
|
||||
if (value && 'jsmops_configs' in value) {
|
||||
const [jsmopsConfig] = value.jsmops_configs;
|
||||
channel = jsmopsConfig;
|
||||
|
||||
@@ -7,6 +7,7 @@ import cx from 'classnames';
|
||||
import ExplorerCard from 'components/ExplorerCard/ExplorerCard';
|
||||
import QueryCancelledPlaceholder from 'components/QueryCancelledPlaceholder';
|
||||
import QuickFilters from 'components/QuickFilters/QuickFilters';
|
||||
import { useSignalFieldApis } from 'components/QuickFilters/hooks/useSignalFieldApis';
|
||||
import { QuickFiltersSource, SignalType } from 'components/QuickFilters/types';
|
||||
import WarningPopover from 'components/WarningPopover/WarningPopover';
|
||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||
@@ -74,6 +75,8 @@ function LogsExplorer(): JSX.Element {
|
||||
|
||||
const { handleExplorerTabChange } = useHandleExplorerTabChange();
|
||||
|
||||
const quickFilterFieldApis = useSignalFieldApis();
|
||||
|
||||
const isAIAssistantEnabled = useIsAIAssistantEnabled();
|
||||
|
||||
const listQueryKeyRef = useRef<any>();
|
||||
@@ -232,6 +235,7 @@ function LogsExplorer(): JSX.Element {
|
||||
signal={SignalType.LOGS}
|
||||
source={QuickFiltersSource.LOGS_EXPLORER}
|
||||
handleFilterVisibilityChange={handleFilterVisibilityChange}
|
||||
useFieldApis={quickFilterFieldApis}
|
||||
/>
|
||||
</section>
|
||||
)}
|
||||
|
||||
@@ -209,8 +209,8 @@ function SaveView(): JSX.Element {
|
||||
currentPanelType,
|
||||
{
|
||||
query,
|
||||
name,
|
||||
id,
|
||||
viewName: name,
|
||||
viewKey: id,
|
||||
},
|
||||
SOURCEPAGE_VS_ROUTES[sourcepage],
|
||||
);
|
||||
|
||||
@@ -504,7 +504,7 @@ jest.mock('hooks/useHandleExplorerTabChange', () => ({
|
||||
let capturedPayload: QueryRangePayloadV5;
|
||||
|
||||
describe('TracesExplorer -', () => {
|
||||
const quickFiltersListURL = `${BASE_URL}/api/v1/orgs/me/filters/traces`;
|
||||
const quickFiltersListURL = `${BASE_URL}/api/v2/quick_filters/traces`;
|
||||
|
||||
const setupServer = (): void => {
|
||||
server.use(
|
||||
|
||||
@@ -8,6 +8,7 @@ import cx from 'classnames';
|
||||
import ExplorerCard from 'components/ExplorerCard/ExplorerCard';
|
||||
import QueryCancelledPlaceholder from 'components/QueryCancelledPlaceholder';
|
||||
import QuickFilters from 'components/QuickFilters/QuickFilters';
|
||||
import { useSignalFieldApis } from 'components/QuickFilters/hooks/useSignalFieldApis';
|
||||
import { QuickFiltersSource, SignalType } from 'components/QuickFilters/types';
|
||||
import WarningPopover from 'components/WarningPopover/WarningPopover';
|
||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||
@@ -128,6 +129,8 @@ function TracesExplorer(): JSX.Element {
|
||||
);
|
||||
|
||||
const { handleExplorerTabChange } = useHandleExplorerTabChange();
|
||||
|
||||
const quickFilterFieldApis = useSignalFieldApis();
|
||||
const { safeNavigate } = useSafeNavigate();
|
||||
const getExportToDashboardLink = useGetExportToDashboardLink();
|
||||
|
||||
@@ -267,6 +270,7 @@ function TracesExplorer(): JSX.Element {
|
||||
handleFilterVisibilityChange={(): void => {
|
||||
setOpen(!isOpen);
|
||||
}}
|
||||
useFieldApis={quickFilterFieldApis}
|
||||
/>
|
||||
</Card>
|
||||
<div
|
||||
|
||||
@@ -22,7 +22,7 @@ import listUserPreferences from 'api/v1/user/preferences/list';
|
||||
import getUserVersion from 'api/v1/version/get';
|
||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||
import dayjs from 'dayjs';
|
||||
import useActiveLicenseV3 from 'hooks/useActiveLicenseV3/useActiveLicenseV3';
|
||||
import useActiveLicense from 'hooks/useActiveLicense/useActiveLicense';
|
||||
import {
|
||||
IsAdminPermission,
|
||||
IsEditorPermission,
|
||||
@@ -210,35 +210,34 @@ export function AppProvider({ children }: PropsWithChildren): JSX.Element {
|
||||
}
|
||||
}, [userData, isFetchingUserData]);
|
||||
|
||||
// fetcher for licenses v3
|
||||
// fetcher for the active license
|
||||
const {
|
||||
data: activeLicenseData,
|
||||
isFetching: isFetchingActiveLicense,
|
||||
error: activeLicenseFetchError,
|
||||
refetch: activeLicenseRefetch,
|
||||
} = useActiveLicenseV3(isLoggedIn);
|
||||
} = useActiveLicense(isLoggedIn);
|
||||
useEffect(() => {
|
||||
if (!isFetchingActiveLicense && activeLicenseData && activeLicenseData.data) {
|
||||
setActiveLicense(activeLicenseData.data);
|
||||
if (!isFetchingActiveLicense && activeLicenseData) {
|
||||
setActiveLicense(activeLicenseData);
|
||||
|
||||
const isOnTrial = dayjs(
|
||||
activeLicenseData.data.free_until || Date.now(),
|
||||
).isAfter(dayjs());
|
||||
const freeUntilUnix = dayjs(activeLicenseData.freeUntil).unix();
|
||||
const scheduledAtUnix = dayjs(
|
||||
activeLicenseData.eventQueue.scheduledAt,
|
||||
).unix();
|
||||
|
||||
const trialInfo: TrialInfo = {
|
||||
trialStart: activeLicenseData.data.valid_from,
|
||||
trialEnd: dayjs(activeLicenseData.data.free_until || Date.now()).unix(),
|
||||
onTrial: isOnTrial,
|
||||
trialStart: activeLicenseData.validFrom,
|
||||
trialEnd: freeUntilUnix > 0 ? freeUntilUnix : dayjs().unix(),
|
||||
onTrial: dayjs(activeLicenseData.freeUntil).isAfter(dayjs()),
|
||||
workSpaceBlock:
|
||||
activeLicenseData.data.state === LicenseState.EVALUATION_EXPIRED &&
|
||||
activeLicenseData.data.platform === LicensePlatform.CLOUD,
|
||||
activeLicenseData.state === LicenseState.EVALUATION_EXPIRED &&
|
||||
activeLicenseData.platform === LicensePlatform.CLOUD,
|
||||
trialConvertedToSubscription:
|
||||
activeLicenseData.data.state !== LicenseState.ISSUED &&
|
||||
activeLicenseData.data.state !== LicenseState.EVALUATING &&
|
||||
activeLicenseData.data.state !== LicenseState.EVALUATION_EXPIRED,
|
||||
gracePeriodEnd: dayjs(
|
||||
activeLicenseData.data.event_queue.scheduled_at || Date.now(),
|
||||
).unix(),
|
||||
activeLicenseData.state !== LicenseState.ISSUED &&
|
||||
activeLicenseData.state !== LicenseState.EVALUATING &&
|
||||
activeLicenseData.state !== LicenseState.EVALUATION_EXPIRED,
|
||||
gracePeriodEnd: scheduledAtUnix > 0 ? scheduledAtUnix : dayjs().unix(),
|
||||
};
|
||||
|
||||
setTrialInfo(trialInfo);
|
||||
|
||||
@@ -158,30 +158,30 @@ export function getAppContextMock(
|
||||
): IAppContext {
|
||||
return {
|
||||
activeLicense: {
|
||||
key: 'test-key',
|
||||
event_queue: {
|
||||
created_at: '0',
|
||||
id: 'test-license-id',
|
||||
eventQueue: {
|
||||
createdAt: '0',
|
||||
event: LicenseEvent.NO_EVENT,
|
||||
scheduled_at: '0',
|
||||
scheduledAt: '0',
|
||||
status: '',
|
||||
updated_at: '0',
|
||||
updatedAt: '0',
|
||||
},
|
||||
state: LicenseState.ACTIVATED,
|
||||
status: LicenseStatus.VALID,
|
||||
platform: LicensePlatform.CLOUD,
|
||||
created_at: '0',
|
||||
createdAt: '0',
|
||||
plan: {
|
||||
created_at: '0',
|
||||
id: '0',
|
||||
createdAt: '0',
|
||||
description: '',
|
||||
is_active: true,
|
||||
isActive: true,
|
||||
name: '',
|
||||
updated_at: '0',
|
||||
updatedAt: '0',
|
||||
},
|
||||
plan_id: '0',
|
||||
free_until: '0',
|
||||
updated_at: '0',
|
||||
valid_from: 0,
|
||||
valid_until: 0,
|
||||
freeUntil: '0',
|
||||
updatedAt: '0',
|
||||
validFrom: 0,
|
||||
validUntil: 0,
|
||||
},
|
||||
trialInfo: {
|
||||
trialStart: -1,
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
import { License } from './def';
|
||||
|
||||
export interface Props {
|
||||
key: string;
|
||||
}
|
||||
|
||||
export interface PayloadProps {
|
||||
status: string;
|
||||
data: License;
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
export interface License {
|
||||
key: string;
|
||||
ValidFrom: Date;
|
||||
ValidUntil: Date;
|
||||
planKey: string;
|
||||
status: string;
|
||||
isCurrent: boolean;
|
||||
}
|
||||
@@ -1,57 +1,59 @@
|
||||
export enum LicenseEvent {
|
||||
NO_EVENT = '',
|
||||
DEFAULT = 'DEFAULT',
|
||||
DEFAULT = 'default',
|
||||
}
|
||||
|
||||
export enum LicenseStatus {
|
||||
SUSPENDED = 'SUSPENDED',
|
||||
VALID = 'VALID',
|
||||
INVALID = 'INVALID',
|
||||
SUSPENDED = 'suspended',
|
||||
VALID = 'valid',
|
||||
INVALID = 'invalid',
|
||||
}
|
||||
|
||||
export enum LicenseState {
|
||||
DEFAULTED = 'DEFAULTED',
|
||||
ACTIVATED = 'ACTIVATED',
|
||||
EXPIRED = 'EXPIRED',
|
||||
ISSUED = 'ISSUED',
|
||||
EVALUATING = 'EVALUATING',
|
||||
EVALUATION_EXPIRED = 'EVALUATION_EXPIRED',
|
||||
TERMINATED = 'TERMINATED',
|
||||
CANCELLED = 'CANCELLED',
|
||||
DEFAULTED = 'defaulted',
|
||||
ACTIVATED = 'activated',
|
||||
EXPIRED = 'expired',
|
||||
ISSUED = 'issued',
|
||||
EVALUATING = 'evaluating',
|
||||
EVALUATION_EXPIRED = 'evaluation_expired',
|
||||
TERMINATED = 'terminated',
|
||||
CANCELLED = 'cancelled',
|
||||
}
|
||||
|
||||
export enum LicensePlatform {
|
||||
SELF_HOSTED = 'SELF_HOSTED',
|
||||
CLOUD = 'CLOUD',
|
||||
SELF_HOSTED = 'self_hosted',
|
||||
CLOUD = 'cloud',
|
||||
}
|
||||
|
||||
export type LicensePlanResModel = {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
isActive: boolean;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
};
|
||||
|
||||
export type LicenseEventQueueResModel = {
|
||||
event: LicenseEvent;
|
||||
status: string;
|
||||
scheduled_at: string;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
scheduledAt: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
};
|
||||
|
||||
export type LicenseResModel = {
|
||||
key: string;
|
||||
id: string;
|
||||
status: LicenseStatus;
|
||||
state: LicenseState;
|
||||
event_queue: LicenseEventQueueResModel;
|
||||
platform: LicensePlatform;
|
||||
created_at: string;
|
||||
plan: {
|
||||
created_at: string;
|
||||
description: string;
|
||||
is_active: boolean;
|
||||
name: string;
|
||||
updated_at: string;
|
||||
};
|
||||
plan_id: string;
|
||||
free_until: string;
|
||||
updated_at: string;
|
||||
valid_from: number;
|
||||
valid_until: number;
|
||||
plan: LicensePlanResModel;
|
||||
eventQueue: LicenseEventQueueResModel;
|
||||
freeUntil: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
validFrom: number;
|
||||
validUntil: number;
|
||||
};
|
||||
|
||||
// Duplicate of old licenses API response, need to improve this later
|
||||
@@ -63,8 +65,3 @@ export type TrialInfo = {
|
||||
trialConvertedToSubscription: boolean;
|
||||
gracePeriodEnd: number;
|
||||
};
|
||||
|
||||
export interface PayloadProps {
|
||||
data: LicenseEventQueueResModel;
|
||||
status: string;
|
||||
}
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
export interface Filter {
|
||||
key: string;
|
||||
dataType: string;
|
||||
type: string;
|
||||
}
|
||||
|
||||
export interface Props {
|
||||
signal: string;
|
||||
}
|
||||
|
||||
export type PayloadProps = {
|
||||
filters: Filter[];
|
||||
signal: string;
|
||||
};
|
||||
@@ -1,14 +0,0 @@
|
||||
import { SignalType } from 'components/QuickFilters/types';
|
||||
|
||||
interface FilterType {
|
||||
key: string;
|
||||
datatype: string;
|
||||
type: string;
|
||||
}
|
||||
|
||||
export interface UpdateCustomFiltersProps {
|
||||
data: {
|
||||
filters: FilterType[];
|
||||
signal: SignalType;
|
||||
};
|
||||
}
|
||||
@@ -184,4 +184,7 @@ export const routeWithInitialAuthZSupport = {
|
||||
METRICS_EXPLORER_VOLUME_CONTROL: true,
|
||||
METER_EXPLORER: true,
|
||||
METER: true,
|
||||
WORKSPACE_LOCKED: true,
|
||||
WORKSPACE_SUSPENDED: true,
|
||||
WORKSPACE_ACCESS_RESTRICTED: true,
|
||||
} as const satisfies Partial<Record<keyof typeof ROUTES, true>>;
|
||||
|
||||
188
pkg/alertmanager/alertmanagernotify/incidentio/incidentio.go
Normal file
188
pkg/alertmanager/alertmanagernotify/incidentio/incidentio.go
Normal file
@@ -0,0 +1,188 @@
|
||||
package incidentio
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"log/slog"
|
||||
"maps"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/alertmanager/alertmanagertemplate"
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/types/alertmanagertypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/ruletypes"
|
||||
"github.com/prometheus/alertmanager/notify"
|
||||
"github.com/prometheus/alertmanager/template"
|
||||
"github.com/prometheus/alertmanager/types"
|
||||
)
|
||||
|
||||
const (
|
||||
Integration = "incidentio"
|
||||
|
||||
// incident.io rejects payloads over 512 KB with a 413. Runes cap the
|
||||
// description at 4 bytes each worst case (~400 KB), leaving headroom for
|
||||
// the other fields.
|
||||
maxDescriptionLenRunes = 100000
|
||||
|
||||
statusFiring = "firing"
|
||||
statusResolved = "resolved"
|
||||
)
|
||||
|
||||
// alertEvent is the body of incident.io's HTTP alert source endpoint
|
||||
// (Alert Events V2 API). Title, status and deduplication_key are required;
|
||||
// metadata values must be flat scalars. Repeat events for a firing key are
|
||||
// dropped server-side and resolves for unknown keys are safe no-ops, so
|
||||
// events are sent unconditionally.
|
||||
type alertEvent struct {
|
||||
Title string `json:"title"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Status string `json:"status"`
|
||||
DeduplicationKey string `json:"deduplication_key"`
|
||||
SourceURL string `json:"source_url,omitempty"`
|
||||
Metadata map[string]string `json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
type Notifier struct {
|
||||
conf *alertmanagertypes.IncidentIOReceiverConfig
|
||||
tmpl *template.Template
|
||||
logger *slog.Logger
|
||||
client *http.Client
|
||||
retrier *notify.Retrier
|
||||
templater alertmanagertypes.Templater
|
||||
}
|
||||
|
||||
func New(conf *alertmanagertypes.IncidentIOReceiverConfig, t *template.Template, l *slog.Logger, templater alertmanagertypes.Templater) (*Notifier, error) {
|
||||
if conf.HTTPConfig == nil {
|
||||
return nil, errors.New(errors.TypeInvalidInput, errors.CodeInvalidInput, "incidentio http_config is nil")
|
||||
}
|
||||
client, err := notify.NewClientWithTracing(*conf.HTTPConfig, Integration)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Notifier{
|
||||
conf: conf,
|
||||
tmpl: t,
|
||||
logger: l,
|
||||
client: client,
|
||||
retrier: ¬ify.Retrier{RetryCodes: []int{http.StatusTooManyRequests}},
|
||||
templater: templater,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (n *Notifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error) {
|
||||
key, err := notify.ExtractGroupKey(ctx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
firing := types.Alerts(as...).HasFiring()
|
||||
n.logger.DebugContext(ctx, "sending incidentio notification", slog.String("group_key", key.String()), slog.Bool("firing", firing))
|
||||
|
||||
customTitle, customBody := alertmanagertemplate.ExtractTemplatesFromAnnotations(as)
|
||||
result, err := n.templater.Expand(ctx, alertmanagertypes.ExpandRequest{
|
||||
TitleTemplate: customTitle,
|
||||
BodyTemplate: customBody,
|
||||
DefaultTitleTemplate: n.conf.Title,
|
||||
DefaultBodyTemplate: n.conf.Description,
|
||||
}, as)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// title is required by the API; a channel title template can render empty,
|
||||
// so fall back to the rule name, then to a static last resort.
|
||||
title := result.Title
|
||||
if strings.TrimSpace(title) == "" && len(as) > 0 {
|
||||
title = string(as[0].Labels[ruletypes.LabelAlertName])
|
||||
}
|
||||
if strings.TrimSpace(title) == "" {
|
||||
title = "SigNoz alert"
|
||||
}
|
||||
|
||||
var parts []string
|
||||
for _, body := range result.Body {
|
||||
if body != "" {
|
||||
parts = append(parts, body)
|
||||
}
|
||||
}
|
||||
description := truncateRunes(strings.Join(parts, "\n\n---\n\n"), maxDescriptionLenRunes)
|
||||
|
||||
status := statusFiring
|
||||
if !firing {
|
||||
status = statusResolved
|
||||
}
|
||||
|
||||
event := alertEvent{
|
||||
Title: title,
|
||||
Description: description,
|
||||
Status: status,
|
||||
DeduplicationKey: key.Hash(),
|
||||
SourceURL: sourceURL(as),
|
||||
Metadata: n.metadata(ctx, as),
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
if err := json.NewEncoder(&buf).Encode(event); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, n.conf.URL, &buf)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Authorization", "Bearer "+string(n.conf.Token))
|
||||
|
||||
resp, err := n.client.Do(req) //nolint:bodyclose // notify.Drain closes the body
|
||||
if err != nil {
|
||||
return true, notify.RedactURL(err)
|
||||
}
|
||||
defer notify.Drain(resp)
|
||||
|
||||
shouldRetry, err := n.retrier.Check(resp.StatusCode, resp.Body)
|
||||
if err != nil {
|
||||
return shouldRetry, notify.NewErrorWithReason(notify.GetFailureReasonFromStatusCode(resp.StatusCode), err)
|
||||
}
|
||||
return shouldRetry, nil
|
||||
}
|
||||
|
||||
// metadata copies the group's common labels wholesale (the Opsgenie details
|
||||
// precedent), so severity, ruleId and any user-defined rule labels arrive as
|
||||
// flat strings ready for incident.io attribute mapping. Channel-configured
|
||||
// pairs are template-expanded and laid on top (channel wins on key clash);
|
||||
// a value that fails to expand is sent raw so delivery never breaks on it.
|
||||
func (n *Notifier) metadata(ctx context.Context, as []*types.Alert) map[string]string {
|
||||
data := notify.GetTemplateData(ctx, n.tmpl, as, n.logger)
|
||||
out := make(map[string]string, len(data.CommonLabels)+len(n.conf.Metadata))
|
||||
maps.Copy(out, data.CommonLabels)
|
||||
for k, v := range n.conf.Metadata {
|
||||
expanded, err := n.tmpl.ExecuteTextString(v, data)
|
||||
if err != nil {
|
||||
n.logger.WarnContext(ctx, "failed to expand incidentio metadata value, sending it raw", slog.String("metadata_key", k))
|
||||
expanded = v
|
||||
}
|
||||
out[k] = expanded
|
||||
}
|
||||
if len(out) == 0 {
|
||||
return nil
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// sourceURL returns the per-rule SigNoz link from the ruleSource label, which
|
||||
// is identical for every alert in the group.
|
||||
func sourceURL(as []*types.Alert) string {
|
||||
if len(as) == 0 {
|
||||
return ""
|
||||
}
|
||||
return string(as[0].Labels[ruletypes.LabelRuleSource])
|
||||
}
|
||||
|
||||
func truncateRunes(s string, max int) string {
|
||||
runes := []rune(s)
|
||||
if len(runes) <= max {
|
||||
return s
|
||||
}
|
||||
return string(runes[:max-1]) + "…"
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
package incidentio
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/alertmanager/alertmanagertemplate"
|
||||
"github.com/SigNoz/signoz/pkg/types/alertmanagertypes"
|
||||
"github.com/prometheus/alertmanager/notify"
|
||||
"github.com/prometheus/alertmanager/notify/test"
|
||||
"github.com/prometheus/alertmanager/types"
|
||||
commoncfg "github.com/prometheus/common/config"
|
||||
"github.com/prometheus/common/model"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type mockIncidentIO struct {
|
||||
srv *httptest.Server
|
||||
mu sync.Mutex
|
||||
events []alertEvent
|
||||
auths []string
|
||||
status int
|
||||
}
|
||||
|
||||
func newMockIncidentIO(t *testing.T) *mockIncidentIO {
|
||||
t.Helper()
|
||||
m := &mockIncidentIO{status: http.StatusAccepted}
|
||||
m.srv = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
var ev alertEvent
|
||||
_ = json.NewDecoder(r.Body).Decode(&ev)
|
||||
m.mu.Lock()
|
||||
m.events = append(m.events, ev)
|
||||
m.auths = append(m.auths, r.Header.Get("Authorization"))
|
||||
status := m.status
|
||||
m.mu.Unlock()
|
||||
w.WriteHeader(status)
|
||||
if status == http.StatusAccepted {
|
||||
_, _ = w.Write([]byte(`{"status":"accepted","message":"Event accepted for processing","deduplication_key":"` + ev.DeduplicationKey + `"}`))
|
||||
} else {
|
||||
_, _ = w.Write([]byte(`{"type":"validation_error","status":422,"errors":[{"code":"is_required","message":"Deduplication key is required"}]}`))
|
||||
}
|
||||
}))
|
||||
t.Cleanup(m.srv.Close)
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *mockIncidentIO) lastEvent(t *testing.T) alertEvent {
|
||||
t.Helper()
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
require.NotEmpty(t, m.events)
|
||||
return m.events[len(m.events)-1]
|
||||
}
|
||||
|
||||
func newNotifier(t *testing.T, m *mockIncidentIO) *Notifier {
|
||||
t.Helper()
|
||||
tmpl := test.CreateTmpl(t)
|
||||
n, err := New(&alertmanagertypes.IncidentIOReceiverConfig{
|
||||
URL: m.srv.URL + "/v2/alert_events/http/src-1",
|
||||
Token: "tok-1",
|
||||
Title: alertmanagertypes.DefaultIncidentIOTitleTemplate,
|
||||
Description: alertmanagertypes.DefaultIncidentIODescriptionTemplate,
|
||||
HTTPConfig: &commoncfg.HTTPClientConfig{},
|
||||
}, tmpl, slog.New(slog.DiscardHandler), alertmanagertemplate.New(tmpl, slog.New(slog.DiscardHandler)))
|
||||
require.NoError(t, err)
|
||||
return n
|
||||
}
|
||||
|
||||
func alert(firing bool) *types.Alert {
|
||||
a := &types.Alert{Alert: model.Alert{
|
||||
Labels: model.LabelSet{
|
||||
"alertname": "HighCPU",
|
||||
"severity": "critical",
|
||||
"ruleSource": "https://signoz.example/alerts/edit?ruleId=1",
|
||||
},
|
||||
Annotations: model.LabelSet{"summary": "cpu high", "related_logs": "https://signoz.example/logs?q=1"},
|
||||
GeneratorURL: "https://signoz.example/alerts/edit?ruleId=1",
|
||||
StartsAt: time.Now().Add(-time.Minute),
|
||||
}}
|
||||
if firing {
|
||||
a.EndsAt = time.Now().Add(time.Hour)
|
||||
} else {
|
||||
a.EndsAt = time.Now().Add(-time.Minute)
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
func ctx() context.Context {
|
||||
return notify.WithGroupKey(context.Background(), "test-incidentio")
|
||||
}
|
||||
|
||||
func TestNotifyFiringEvent(t *testing.T) {
|
||||
m := newMockIncidentIO(t)
|
||||
retry, err := newNotifier(t, m).Notify(ctx(), alert(true))
|
||||
require.NoError(t, err)
|
||||
assert.False(t, retry)
|
||||
|
||||
ev := m.lastEvent(t)
|
||||
assert.Equal(t, "[FIRING:1] HighCPU", ev.Title)
|
||||
assert.Equal(t, "firing", ev.Status)
|
||||
assert.NotEmpty(t, ev.DeduplicationKey)
|
||||
assert.Equal(t, "https://signoz.example/alerts/edit?ruleId=1", ev.SourceURL)
|
||||
assert.Contains(t, ev.Description, "**Alert:** HighCPU (critical)")
|
||||
assert.Contains(t, ev.Description, "**Summary:** cpu high")
|
||||
assert.Contains(t, ev.Description, "[View in SigNoz](https://signoz.example/alerts/edit?ruleId=1)")
|
||||
assert.Contains(t, ev.Description, "[View related logs](https://signoz.example/logs?q=1)")
|
||||
assert.Equal(t, map[string]string{
|
||||
"alertname": "HighCPU",
|
||||
"severity": "critical",
|
||||
"ruleSource": "https://signoz.example/alerts/edit?ruleId=1",
|
||||
}, ev.Metadata)
|
||||
assert.Equal(t, "Bearer tok-1", m.auths[0])
|
||||
}
|
||||
|
||||
func TestNotifyResolvedEventReusesDedupKey(t *testing.T) {
|
||||
m := newMockIncidentIO(t)
|
||||
n := newNotifier(t, m)
|
||||
|
||||
_, err := n.Notify(ctx(), alert(true))
|
||||
require.NoError(t, err)
|
||||
firingKey := m.lastEvent(t).DeduplicationKey
|
||||
|
||||
_, err = n.Notify(ctx(), alert(false))
|
||||
require.NoError(t, err)
|
||||
|
||||
ev := m.lastEvent(t)
|
||||
assert.Equal(t, "resolved", ev.Status)
|
||||
assert.Equal(t, firingKey, ev.DeduplicationKey)
|
||||
}
|
||||
|
||||
func TestNotifyPermanentFailureDoesNotRetry(t *testing.T) {
|
||||
m := newMockIncidentIO(t)
|
||||
m.status = http.StatusUnprocessableEntity
|
||||
|
||||
retry, err := newNotifier(t, m).Notify(ctx(), alert(true))
|
||||
require.Error(t, err)
|
||||
assert.False(t, retry)
|
||||
assert.Contains(t, err.Error(), "Deduplication key is required") // response body surfaces to the user
|
||||
}
|
||||
|
||||
func TestNotifyRateLimitRetries(t *testing.T) {
|
||||
m := newMockIncidentIO(t)
|
||||
m.status = http.StatusTooManyRequests
|
||||
|
||||
retry, err := newNotifier(t, m).Notify(ctx(), alert(true))
|
||||
require.Error(t, err)
|
||||
assert.True(t, retry)
|
||||
}
|
||||
|
||||
func TestNotifyMergesChannelMetadata(t *testing.T) {
|
||||
m := newMockIncidentIO(t)
|
||||
tmpl := test.CreateTmpl(t)
|
||||
n, err := New(&alertmanagertypes.IncidentIOReceiverConfig{
|
||||
URL: m.srv.URL + "/v2/alert_events/http/src-1",
|
||||
Token: "tok-1",
|
||||
Title: alertmanagertypes.DefaultIncidentIOTitleTemplate,
|
||||
Description: alertmanagertypes.DefaultIncidentIODescriptionTemplate,
|
||||
HTTPConfig: &commoncfg.HTTPClientConfig{},
|
||||
Metadata: map[string]string{
|
||||
"env": "prod",
|
||||
"sev": "{{ .CommonLabels.severity }}",
|
||||
"alertname": "channel-wins",
|
||||
"broken": "{{ .Nope",
|
||||
},
|
||||
}, tmpl, slog.New(slog.DiscardHandler), alertmanagertemplate.New(tmpl, slog.New(slog.DiscardHandler)))
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = n.Notify(ctx(), alert(true))
|
||||
require.NoError(t, err) // a broken metadata template must not fail delivery
|
||||
|
||||
md := m.lastEvent(t).Metadata
|
||||
assert.Equal(t, "prod", md["env"])
|
||||
assert.Equal(t, "critical", md["sev"]) // values are template-expanded
|
||||
assert.Equal(t, "channel-wins", md["alertname"]) // channel overrides the rule label
|
||||
assert.Equal(t, "{{ .Nope", md["broken"]) // unexpandable value sent raw
|
||||
assert.Equal(t, "critical", md["severity"]) // rule labels still present
|
||||
}
|
||||
|
||||
func TestNotifyEmptyTitleFallsBackToRuleName(t *testing.T) {
|
||||
m := newMockIncidentIO(t)
|
||||
tmpl := test.CreateTmpl(t)
|
||||
n, err := New(&alertmanagertypes.IncidentIOReceiverConfig{
|
||||
URL: m.srv.URL + "/v2/alert_events/http/src-1",
|
||||
Token: "tok-1",
|
||||
Title: `{{ .CommonLabels.nonexistent }}`,
|
||||
Description: alertmanagertypes.DefaultIncidentIODescriptionTemplate,
|
||||
HTTPConfig: &commoncfg.HTTPClientConfig{},
|
||||
}, tmpl, slog.New(slog.DiscardHandler), alertmanagertemplate.New(tmpl, slog.New(slog.DiscardHandler)))
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = n.Notify(ctx(), alert(true))
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "HighCPU", m.lastEvent(t).Title)
|
||||
}
|
||||
|
||||
func TestNotifyTruncatesLongDescription(t *testing.T) {
|
||||
m := newMockIncidentIO(t)
|
||||
a := alert(true)
|
||||
a.Annotations["description"] = model.LabelValue(strings.Repeat("x", maxDescriptionLenRunes+1000))
|
||||
|
||||
_, err := newNotifier(t, m).Notify(ctx(), a)
|
||||
require.NoError(t, err)
|
||||
|
||||
desc := []rune(m.lastEvent(t).Description)
|
||||
assert.LessOrEqual(t, len(desc), maxDescriptionLenRunes)
|
||||
assert.Equal(t, '…', desc[len(desc)-1])
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/alertmanager/alertmanagernotify/email"
|
||||
"github.com/SigNoz/signoz/pkg/alertmanager/alertmanagernotify/googlechat"
|
||||
"github.com/SigNoz/signoz/pkg/alertmanager/alertmanagernotify/incidentio"
|
||||
"github.com/SigNoz/signoz/pkg/alertmanager/alertmanagernotify/jira"
|
||||
"github.com/SigNoz/signoz/pkg/alertmanager/alertmanagernotify/jsmops"
|
||||
"github.com/SigNoz/signoz/pkg/alertmanager/alertmanagernotify/msteamsv2"
|
||||
@@ -30,6 +31,7 @@ var customNotifierIntegrations = []string{
|
||||
googlechat.Integration,
|
||||
jira.Integration,
|
||||
jsmops.Integration,
|
||||
incidentio.Integration,
|
||||
}
|
||||
|
||||
func NewReceiverIntegrations(nc *alertmanagertypes.Receiver, tmpl *template.Template, logger *slog.Logger, templater alertmanagertypes.Templater) ([]notify.Integration, error) {
|
||||
@@ -95,6 +97,11 @@ func NewReceiverIntegrations(nc *alertmanagertypes.Receiver, tmpl *template.Temp
|
||||
return jsmops.New(c, tmpl, l, templater, true)
|
||||
})
|
||||
}
|
||||
for i, c := range nc.IncidentIOConfigs {
|
||||
add(incidentio.Integration, i, c, func(l *slog.Logger) (notify.Notifier, error) {
|
||||
return incidentio.New(c, tmpl, l, templater)
|
||||
})
|
||||
}
|
||||
|
||||
if errs.Len() > 0 {
|
||||
return nil, &errs
|
||||
|
||||
@@ -75,7 +75,7 @@ func (store *config) CreateChannel(ctx context.Context, channel *alertmanagertyp
|
||||
NewInsert().
|
||||
Model(channel).
|
||||
Exec(ctx); err != nil {
|
||||
return err
|
||||
return store.sqlstore.WrapAlreadyExistsErrf(err, alertmanagertypes.ErrCodeAlertmanagerChannelAlreadyExists, "channel with name %q already exists", channel.Name)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
package sqlalertmanagerstore
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/alertmanagertypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
|
||||
func TestCreateChannelRejectsDuplicateNameInSameOrg(t *testing.T) {
|
||||
sqlstore := newTestStore(t)
|
||||
|
||||
_, err := sqlstore.BunDB().NewCreateTable().
|
||||
Model((*alertmanagertypes.Channel)(nil)).
|
||||
IfNotExists().
|
||||
Exec(t.Context())
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = sqlstore.BunDB().NewCreateIndex().
|
||||
Model((*alertmanagertypes.Channel)(nil)).
|
||||
Index("notification_channel_org_id_name_idx").
|
||||
Column("org_id", "name").
|
||||
Unique().
|
||||
Exec(t.Context())
|
||||
require.NoError(t, err)
|
||||
|
||||
store := NewConfigStore(sqlstore)
|
||||
orgID := valuer.GenerateUUID().StringValue()
|
||||
now := time.Now().UTC()
|
||||
|
||||
firstChannel := &alertmanagertypes.Channel{
|
||||
Identifiable: types.Identifiable{ID: valuer.GenerateUUID()},
|
||||
TimeAuditable: types.TimeAuditable{CreatedAt: now, UpdatedAt: now},
|
||||
Name: "shared-name",
|
||||
DisplayName: "First Channel",
|
||||
Type: "slack",
|
||||
Data: `{"name":"First Channel","slack_configs":[{"api_url":"https://hooks.slack.com/services/first"}]}`,
|
||||
OrgID: orgID,
|
||||
}
|
||||
require.NoError(t, store.CreateChannel(t.Context(), firstChannel))
|
||||
|
||||
duplicateChannel := &alertmanagertypes.Channel{
|
||||
Identifiable: types.Identifiable{ID: valuer.GenerateUUID()},
|
||||
TimeAuditable: types.TimeAuditable{CreatedAt: now, UpdatedAt: now},
|
||||
Name: "shared-name",
|
||||
DisplayName: "Second Channel",
|
||||
Type: "slack",
|
||||
Data: `{"name":"Second Channel","slack_configs":[{"api_url":"https://hooks.slack.com/services/second"}]}`,
|
||||
OrgID: orgID,
|
||||
}
|
||||
err = store.CreateChannel(t.Context(), duplicateChannel)
|
||||
require.Error(t, err)
|
||||
assert.True(t, errors.Ast(err, errors.TypeAlreadyExists))
|
||||
|
||||
otherOrgChannel := &alertmanagertypes.Channel{
|
||||
Identifiable: types.Identifiable{ID: valuer.GenerateUUID()},
|
||||
TimeAuditable: types.TimeAuditable{CreatedAt: now, UpdatedAt: now},
|
||||
Name: "shared-name",
|
||||
DisplayName: "Second Channel",
|
||||
Type: "slack",
|
||||
Data: `{"name":"Second Channel","slack_configs":[{"api_url":"https://hooks.slack.com/services/second"}]}`,
|
||||
OrgID: valuer.GenerateUUID().StringValue(),
|
||||
}
|
||||
assert.NoError(t, store.CreateChannel(t.Context(), otherOrgChannel))
|
||||
}
|
||||
@@ -187,7 +187,7 @@ func (provider *provider) DeleteChannelByID(ctx context.Context, orgID string, c
|
||||
}
|
||||
|
||||
// Check if channel is referenced by any route policy (rule-based or policy-based)
|
||||
policies, err := provider.notificationManager.GetRoutePoliciesByChannel(ctx, orgID, channel.Name)
|
||||
policies, err := provider.notificationManager.GetRoutePoliciesByChannel(ctx, orgID, channel.DisplayName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -198,7 +198,7 @@ func (provider *provider) DeleteChannelByID(ctx context.Context, orgID string, c
|
||||
}
|
||||
return errors.NewInvalidInputf(errors.CodeInvalidInput,
|
||||
"channel %q cannot be deleted because it is used by the following routing policies: %v",
|
||||
channel.Name, names)
|
||||
channel.DisplayName, names)
|
||||
}
|
||||
|
||||
config, err := provider.configStore.Get(ctx, orgID)
|
||||
@@ -206,7 +206,7 @@ func (provider *provider) DeleteChannelByID(ctx context.Context, orgID string, c
|
||||
return err
|
||||
}
|
||||
|
||||
if err := config.DeleteReceiver(channel.Name); err != nil {
|
||||
if err := config.DeleteReceiver(channel.DisplayName); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -97,23 +97,57 @@ func (provider *provider) addGatewayRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/gateway/ingestion_keys/{keyId}/limits", handler.New(provider.authzMiddleware.EditAccess(provider.gatewayHandler.CreateIngestionKeyLimit), handler.OpenAPIDef{
|
||||
if err := router.Handle("/api/v2/gateway/ingestion_keys/{keyId}", handler.New(provider.authzMiddleware.EditAccess(provider.gatewayHandler.GetIngestionKey), handler.OpenAPIDef{
|
||||
ID: "GetIngestionKey",
|
||||
Tags: []string{"gateway"},
|
||||
Summary: "Get ingestion key for workspace",
|
||||
Description: "This endpoint returns an ingestion key for the workspace",
|
||||
Request: nil,
|
||||
RequestContentType: "",
|
||||
Response: new(gatewaytypes.IngestionKey),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{http.StatusNotFound},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleEditor),
|
||||
})).Methods(http.MethodGet).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/gateway/ingestion_keys/{keyId}/limits", handler.New(provider.authzMiddleware.EditAccess(provider.gatewayHandler.DeprecatedCreateIngestionKeyLimit), handler.OpenAPIDef{
|
||||
ID: "CreateIngestionKeyLimit",
|
||||
Tags: []string{"gateway"},
|
||||
Summary: "Create limit for the ingestion key",
|
||||
Description: "This endpoint creates an ingestion key limit",
|
||||
Request: new(gatewaytypes.PostableIngestionKeyLimit),
|
||||
Request: new(gatewaytypes.DeprecatedPostableIngestionKeyLimit),
|
||||
RequestContentType: "application/json",
|
||||
Response: new(gatewaytypes.GettableCreatedIngestionKeyLimit),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusCreated,
|
||||
ErrorStatusCodes: []int{},
|
||||
Deprecated: false,
|
||||
Deprecated: true,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleEditor),
|
||||
})).Methods(http.MethodPost).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/gateway/ingestion_keys/{keyId}/limits", handler.New(provider.authzMiddleware.EditAccess(provider.gatewayHandler.GetIngestionKeyLimits), handler.OpenAPIDef{
|
||||
ID: "GetIngestionKeyLimits",
|
||||
Tags: []string{"gateway"},
|
||||
Summary: "Get limits for the ingestion key",
|
||||
Description: "This endpoint returns the ingestion limits for an ingestion key",
|
||||
Request: nil,
|
||||
RequestContentType: "",
|
||||
Response: new([]gatewaytypes.Limit),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{http.StatusNotFound},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleEditor),
|
||||
})).Methods(http.MethodGet).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/gateway/ingestion_keys/limits/{limitId}", handler.New(provider.authzMiddleware.EditAccess(provider.gatewayHandler.UpdateIngestionKeyLimit), handler.OpenAPIDef{
|
||||
ID: "UpdateIngestionKeyLimit",
|
||||
Tags: []string{"gateway"},
|
||||
@@ -125,7 +159,7 @@ func (provider *provider) addGatewayRoutes(router *mux.Router) error {
|
||||
ResponseContentType: "",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{},
|
||||
Deprecated: false,
|
||||
Deprecated: true,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleEditor),
|
||||
})).Methods(http.MethodPatch).GetError(); err != nil {
|
||||
return err
|
||||
@@ -142,6 +176,74 @@ func (provider *provider) addGatewayRoutes(router *mux.Router) error {
|
||||
ResponseContentType: "",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{},
|
||||
Deprecated: true,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleEditor),
|
||||
})).Methods(http.MethodDelete).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/gateway/ingestion_limits", handler.New(provider.authzMiddleware.EditAccess(provider.gatewayHandler.CreateIngestionKeyLimit), handler.OpenAPIDef{
|
||||
ID: "CreateIngestionLimit",
|
||||
Tags: []string{"gateway"},
|
||||
Summary: "Create ingestion limit",
|
||||
Description: "This endpoint creates an ingestion limit for the ingestion key referenced by keyId",
|
||||
Request: new(gatewaytypes.PostableIngestionKeyLimit),
|
||||
RequestContentType: "application/json",
|
||||
Response: new(types.Identifiable),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusCreated,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleEditor),
|
||||
})).Methods(http.MethodPost).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/gateway/ingestion_limits/{limitId}", handler.New(provider.authzMiddleware.EditAccess(provider.gatewayHandler.GetIngestionKeyLimit), handler.OpenAPIDef{
|
||||
ID: "GetIngestionLimit",
|
||||
Tags: []string{"gateway"},
|
||||
Summary: "Get ingestion limit",
|
||||
Description: "This endpoint returns an ingestion limit",
|
||||
Request: nil,
|
||||
RequestContentType: "",
|
||||
Response: new(gatewaytypes.Limit),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{http.StatusNotFound},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleEditor),
|
||||
})).Methods(http.MethodGet).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/gateway/ingestion_limits/{limitId}", handler.New(provider.authzMiddleware.EditAccess(provider.gatewayHandler.UpdateIngestionKeyLimit), handler.OpenAPIDef{
|
||||
ID: "UpdateIngestionLimit",
|
||||
Tags: []string{"gateway"},
|
||||
Summary: "Update ingestion limit",
|
||||
Description: "This endpoint updates an ingestion limit",
|
||||
Request: new(gatewaytypes.UpdatableIngestionKeyLimit),
|
||||
RequestContentType: "application/json",
|
||||
Response: nil,
|
||||
ResponseContentType: "",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleEditor),
|
||||
})).Methods(http.MethodPatch).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/gateway/ingestion_limits/{limitId}", handler.New(provider.authzMiddleware.EditAccess(provider.gatewayHandler.DeleteIngestionKeyLimit), handler.OpenAPIDef{
|
||||
ID: "DeleteIngestionLimit",
|
||||
Tags: []string{"gateway"},
|
||||
Summary: "Delete ingestion limit",
|
||||
Description: "This endpoint deletes an ingestion limit",
|
||||
Request: nil,
|
||||
RequestContentType: "",
|
||||
Response: nil,
|
||||
ResponseContentType: "",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleEditor),
|
||||
})).Methods(http.MethodDelete).GetError(); err != nil {
|
||||
|
||||
219
pkg/apiserver/signozapiserver/licensing.go
Normal file
219
pkg/apiserver/signozapiserver/licensing.go
Normal file
@@ -0,0 +1,219 @@
|
||||
package signozapiserver
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/http/handler"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/coretypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/licensetypes"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
func (provider *provider) addLicensingRoutes(router *mux.Router) error {
|
||||
if err := router.Handle("/api/v4/licenses", handler.New(
|
||||
provider.authzMiddleware.CheckResources(provider.licensingHandler.Create, authtypes.SigNozAdminRoleName),
|
||||
handler.OpenAPIDef{
|
||||
ID: "ActivateLicense",
|
||||
Tags: []string{"licenses"},
|
||||
Summary: "Activate a license.",
|
||||
Description: "This endpoint validates the license key with the upstream server and activates the license for the organization.",
|
||||
Request: new(licensetypes.PostableLicense),
|
||||
RequestContentType: "application/json",
|
||||
Response: new(types.Identifiable),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusCreated,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound, http.StatusConflict},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newScopedSecuritySchemes([]string{coretypes.ResourceMetaResourceLicense.Scope(coretypes.VerbCreate)}),
|
||||
},
|
||||
handler.WithResourceDefs(handler.BasicResourceDef{
|
||||
Resource: coretypes.ResourceMetaResourceLicense,
|
||||
Verb: coretypes.VerbCreate,
|
||||
Category: coretypes.ActionCategoryConfigurationChange,
|
||||
ID: coretypes.ResponseJSONPath("data.id"),
|
||||
Selector: coretypes.WildcardSelector,
|
||||
}),
|
||||
)).Methods(http.MethodPost).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v3/licenses", handler.New(
|
||||
provider.authzMiddleware.CheckResources(provider.licensingHandler.ActivateDeprecated, authtypes.SigNozAdminRoleName),
|
||||
handler.OpenAPIDef{
|
||||
ID: "ActivateLicenseDeprecated",
|
||||
Tags: []string{"licenses"},
|
||||
Summary: "Activate a license.",
|
||||
Description: "This endpoint validates the license key with the upstream server and activates the license for the organization.",
|
||||
Request: new(licensetypes.PostableLicense),
|
||||
RequestContentType: "application/json",
|
||||
Response: nil,
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusAccepted,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound, http.StatusConflict},
|
||||
Deprecated: true,
|
||||
SecuritySchemes: newScopedSecuritySchemes([]string{coretypes.ResourceMetaResourceLicense.Scope(coretypes.VerbCreate)}),
|
||||
},
|
||||
handler.WithResourceDefs(handler.BasicResourceDef{
|
||||
Resource: coretypes.ResourceMetaResourceLicense,
|
||||
Verb: coretypes.VerbCreate,
|
||||
Category: coretypes.ActionCategoryConfigurationChange,
|
||||
Selector: coretypes.WildcardSelector,
|
||||
}),
|
||||
)).Methods(http.MethodPost).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v3/licenses", handler.New(
|
||||
provider.authzMiddleware.CheckResources(provider.licensingHandler.RefreshDeprecated, authtypes.SigNozAdminRoleName),
|
||||
handler.OpenAPIDef{
|
||||
ID: "RefreshLicenseDeprecated",
|
||||
Tags: []string{"licenses"},
|
||||
Summary: "Refresh a license.",
|
||||
Description: "This endpoint refreshes the active license of the organization from the upstream server.",
|
||||
Request: nil,
|
||||
RequestContentType: "",
|
||||
Response: nil,
|
||||
ResponseContentType: "",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
|
||||
Deprecated: true,
|
||||
SecuritySchemes: newScopedSecuritySchemes([]string{coretypes.ResourceMetaResourceLicense.Scope(coretypes.VerbUpdate)}),
|
||||
},
|
||||
handler.WithResourceDefs(handler.BasicResourceDef{
|
||||
Resource: coretypes.ResourceMetaResourceLicense,
|
||||
Verb: coretypes.VerbUpdate,
|
||||
Category: coretypes.ActionCategoryConfigurationChange,
|
||||
Selector: coretypes.WildcardSelector,
|
||||
}),
|
||||
)).Methods(http.MethodPut).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v4/licenses", handler.New(
|
||||
provider.authzMiddleware.CheckResources(provider.licensingHandler.List, authtypes.SigNozAdminRoleName),
|
||||
handler.OpenAPIDef{
|
||||
ID: "ListLicenses",
|
||||
Tags: []string{"licenses"},
|
||||
Summary: "List licenses.",
|
||||
Description: "This endpoint lists all the licenses of the organization.",
|
||||
Request: nil,
|
||||
RequestContentType: "",
|
||||
Response: make([]*licensetypes.GettableLicense, 0),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newScopedSecuritySchemes([]string{coretypes.ResourceMetaResourceLicense.Scope(coretypes.VerbList)}),
|
||||
},
|
||||
handler.WithResourceDefs(handler.BasicResourceDef{
|
||||
Resource: coretypes.ResourceMetaResourceLicense,
|
||||
Verb: coretypes.VerbList,
|
||||
Category: coretypes.ActionCategoryDataAccess,
|
||||
Selector: coretypes.WildcardSelector,
|
||||
}),
|
||||
)).Methods(http.MethodGet).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v4/licenses/active", handler.New(provider.authzMiddleware.OpenAccess(provider.licensingHandler.GetActive), handler.OpenAPIDef{
|
||||
ID: "GetActiveLicense",
|
||||
Tags: []string{"licenses"},
|
||||
Summary: "Get the active license.",
|
||||
Description: "This endpoint gets the active license of the organization.",
|
||||
Request: nil,
|
||||
RequestContentType: "",
|
||||
Response: new(licensetypes.GettableActiveLicense),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound, http.StatusNotImplemented},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newScopedSecuritySchemes(nil),
|
||||
})).Methods(http.MethodGet).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v4/licenses/{id}", handler.New(
|
||||
provider.authzMiddleware.CheckResources(provider.licensingHandler.Get, authtypes.SigNozAdminRoleName),
|
||||
handler.OpenAPIDef{
|
||||
ID: "GetLicense",
|
||||
Tags: []string{"licenses"},
|
||||
Summary: "Get a license.",
|
||||
Description: "This endpoint gets the license by id.",
|
||||
Request: nil,
|
||||
RequestContentType: "",
|
||||
Response: new(licensetypes.GettableLicenseWithKey),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newScopedSecuritySchemes([]string{coretypes.ResourceMetaResourceLicense.Scope(coretypes.VerbRead)}),
|
||||
},
|
||||
handler.WithResourceDefs(handler.BasicResourceDef{
|
||||
Resource: coretypes.ResourceMetaResourceLicense,
|
||||
Verb: coretypes.VerbRead,
|
||||
Category: coretypes.ActionCategoryDataAccess,
|
||||
ID: coretypes.PathParam("id"),
|
||||
Selector: coretypes.IDSelector,
|
||||
}),
|
||||
)).Methods(http.MethodGet).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v4/licenses/{id}", handler.New(
|
||||
provider.authzMiddleware.CheckResources(provider.licensingHandler.Refresh, authtypes.SigNozAdminRoleName),
|
||||
handler.OpenAPIDef{
|
||||
ID: "RefreshLicense",
|
||||
Tags: []string{"licenses"},
|
||||
Summary: "Refresh a license.",
|
||||
Description: "This endpoint refreshes the active license of the organization from the upstream server.",
|
||||
Request: nil,
|
||||
RequestContentType: "",
|
||||
Response: nil,
|
||||
ResponseContentType: "",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newScopedSecuritySchemes([]string{coretypes.ResourceMetaResourceLicense.Scope(coretypes.VerbUpdate)}),
|
||||
},
|
||||
handler.WithResourceDefs(handler.BasicResourceDef{
|
||||
Resource: coretypes.ResourceMetaResourceLicense,
|
||||
Verb: coretypes.VerbUpdate,
|
||||
Category: coretypes.ActionCategoryConfigurationChange,
|
||||
ID: coretypes.PathParam("id"),
|
||||
Selector: coretypes.IDSelector,
|
||||
}),
|
||||
)).Methods(http.MethodPut).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v4/licenses/{id}", handler.New(
|
||||
provider.authzMiddleware.CheckResources(provider.licensingHandler.Delete, authtypes.SigNozAdminRoleName),
|
||||
handler.OpenAPIDef{
|
||||
ID: "DeleteLicense",
|
||||
Tags: []string{"licenses"},
|
||||
Summary: "Delete a license.",
|
||||
Description: "This endpoint deletes the license by id. Licenses managed by SigNoz Cloud cannot be deleted.",
|
||||
Request: nil,
|
||||
RequestContentType: "",
|
||||
Response: nil,
|
||||
ResponseContentType: "",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newScopedSecuritySchemes([]string{coretypes.ResourceMetaResourceLicense.Scope(coretypes.VerbDelete)}),
|
||||
},
|
||||
handler.WithResourceDefs(handler.BasicResourceDef{
|
||||
Resource: coretypes.ResourceMetaResourceLicense,
|
||||
Verb: coretypes.VerbDelete,
|
||||
Category: coretypes.ActionCategoryConfigurationChange,
|
||||
ID: coretypes.PathParam("id"),
|
||||
Selector: coretypes.IDSelector,
|
||||
}),
|
||||
)).Methods(http.MethodDelete).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/global"
|
||||
"github.com/SigNoz/signoz/pkg/http/handler"
|
||||
"github.com/SigNoz/signoz/pkg/http/middleware"
|
||||
"github.com/SigNoz/signoz/pkg/licensing"
|
||||
"github.com/SigNoz/signoz/pkg/modules/aiobservability"
|
||||
"github.com/SigNoz/signoz/pkg/modules/authdomain"
|
||||
"github.com/SigNoz/signoz/pkg/modules/cloudintegration"
|
||||
@@ -24,6 +25,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/modules/organization"
|
||||
"github.com/SigNoz/signoz/pkg/modules/preference"
|
||||
"github.com/SigNoz/signoz/pkg/modules/promote"
|
||||
"github.com/SigNoz/signoz/pkg/modules/quickfilter"
|
||||
"github.com/SigNoz/signoz/pkg/modules/rawdataexport"
|
||||
"github.com/SigNoz/signoz/pkg/modules/rulestatehistory"
|
||||
"github.com/SigNoz/signoz/pkg/modules/savedview"
|
||||
@@ -68,6 +70,7 @@ type provider struct {
|
||||
authzHandler authz.Handler
|
||||
rawDataExportHandler rawdataexport.Handler
|
||||
zeusHandler zeus.Handler
|
||||
licensingHandler licensing.Handler
|
||||
querierHandler querier.Handler
|
||||
serviceAccountHandler serviceaccount.Handler
|
||||
serviceAccountGetter serviceaccount.Getter
|
||||
@@ -82,6 +85,8 @@ type provider struct {
|
||||
llmPricingRuleHandler llmpricingrule.Handler
|
||||
statsHandler statsreporter.Handler
|
||||
savedViewHandler savedview.Handler
|
||||
quickFilterModule quickfilter.Module
|
||||
quickFilterHandler quickfilter.Handler
|
||||
}
|
||||
|
||||
func NewFactory(
|
||||
@@ -107,6 +112,7 @@ func NewFactory(
|
||||
authzHandler authz.Handler,
|
||||
rawDataExportHandler rawdataexport.Handler,
|
||||
zeusHandler zeus.Handler,
|
||||
licensingHandler licensing.Handler,
|
||||
querierHandler querier.Handler,
|
||||
serviceAccountHandler serviceaccount.Handler,
|
||||
serviceAccountGetter serviceaccount.Getter,
|
||||
@@ -121,6 +127,8 @@ func NewFactory(
|
||||
rulerHandler ruler.Handler,
|
||||
statsHandler statsreporter.Handler,
|
||||
savedViewHandler savedview.Handler,
|
||||
quickFilterModule quickfilter.Module,
|
||||
quickFilterHandler quickfilter.Handler,
|
||||
) factory.ProviderFactory[apiserver.APIServer, apiserver.Config] {
|
||||
return factory.NewProviderFactory(factory.MustNewName("signoz"), func(ctx context.Context, providerSettings factory.ProviderSettings, config apiserver.Config) (apiserver.APIServer, error) {
|
||||
return newProvider(
|
||||
@@ -149,6 +157,7 @@ func NewFactory(
|
||||
authzHandler,
|
||||
rawDataExportHandler,
|
||||
zeusHandler,
|
||||
licensingHandler,
|
||||
querierHandler,
|
||||
serviceAccountHandler,
|
||||
serviceAccountGetter,
|
||||
@@ -163,6 +172,8 @@ func NewFactory(
|
||||
rulerHandler,
|
||||
statsHandler,
|
||||
savedViewHandler,
|
||||
quickFilterModule,
|
||||
quickFilterHandler,
|
||||
)
|
||||
})
|
||||
}
|
||||
@@ -193,6 +204,7 @@ func newProvider(
|
||||
authzHandler authz.Handler,
|
||||
rawDataExportHandler rawdataexport.Handler,
|
||||
zeusHandler zeus.Handler,
|
||||
licensingHandler licensing.Handler,
|
||||
querierHandler querier.Handler,
|
||||
serviceAccountHandler serviceaccount.Handler,
|
||||
serviceAccountGetter serviceaccount.Getter,
|
||||
@@ -207,6 +219,8 @@ func newProvider(
|
||||
rulerHandler ruler.Handler,
|
||||
statsHandler statsreporter.Handler,
|
||||
savedViewHandler savedview.Handler,
|
||||
quickFilterModule quickfilter.Module,
|
||||
quickFilterHandler quickfilter.Handler,
|
||||
) (apiserver.APIServer, error) {
|
||||
settings := factory.NewScopedProviderSettings(providerSettings, "github.com/SigNoz/signoz/pkg/apiserver/signozapiserver")
|
||||
router := mux.NewRouter().UseEncodedPath()
|
||||
@@ -236,6 +250,7 @@ func newProvider(
|
||||
authzHandler: authzHandler,
|
||||
rawDataExportHandler: rawDataExportHandler,
|
||||
zeusHandler: zeusHandler,
|
||||
licensingHandler: licensingHandler,
|
||||
querierHandler: querierHandler,
|
||||
serviceAccountHandler: serviceAccountHandler,
|
||||
serviceAccountGetter: serviceAccountGetter,
|
||||
@@ -250,6 +265,8 @@ func newProvider(
|
||||
llmPricingRuleHandler: llmPricingRuleHandler,
|
||||
statsHandler: statsHandler,
|
||||
savedViewHandler: savedViewHandler,
|
||||
quickFilterModule: quickFilterModule,
|
||||
quickFilterHandler: quickFilterHandler,
|
||||
}
|
||||
|
||||
provider.authzMiddleware = middleware.NewAuthZ(settings.Logger(), orgGetter, authzService)
|
||||
@@ -338,6 +355,10 @@ func (provider *provider) AddToRouter(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := provider.addLicensingRoutes(router); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := provider.addZeusRoutes(router); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -394,6 +415,10 @@ func (provider *provider) AddToRouter(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := provider.addQuickFilterRoutes(router); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
120
pkg/apiserver/signozapiserver/quickfilter.go
Normal file
120
pkg/apiserver/signozapiserver/quickfilter.go
Normal file
@@ -0,0 +1,120 @@
|
||||
package signozapiserver
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/http/handler"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/coretypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/quickfiltertypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
func (provider *provider) addQuickFilterRoutes(router *mux.Router) error {
|
||||
if err := router.Handle("/api/v2/quick_filters", handler.New(
|
||||
provider.authzMiddleware.CheckResources(provider.quickFilterHandler.ListQuickFiltersV2, authtypes.SigNozAdminRoleName, authtypes.SigNozEditorRoleName, authtypes.SigNozViewerRoleName),
|
||||
handler.OpenAPIDef{
|
||||
ID: "ListQuickFilters",
|
||||
Tags: []string{"quick_filter"},
|
||||
Summary: "List quick filters",
|
||||
Description: "Returns the org's quick filters for every source, each filter as a telemetry field key.",
|
||||
Request: nil,
|
||||
RequestContentType: "",
|
||||
Response: make([]*quickfiltertypes.SourceFilters, 0),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newScopedSecuritySchemes([]string{coretypes.ResourceMetaResourceQuickFilter.Scope(coretypes.VerbList)}),
|
||||
},
|
||||
handler.WithResourceDefs(handler.BasicResourceDef{
|
||||
Resource: coretypes.ResourceMetaResourceQuickFilter,
|
||||
Verb: coretypes.VerbList,
|
||||
Category: coretypes.ActionCategoryDataAccess,
|
||||
Selector: coretypes.WildcardSelector,
|
||||
}),
|
||||
)).Methods(http.MethodGet).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/quick_filters/{source}", handler.New(
|
||||
provider.authzMiddleware.CheckResources(provider.quickFilterHandler.GetQuickFiltersV2, authtypes.SigNozAdminRoleName, authtypes.SigNozEditorRoleName, authtypes.SigNozViewerRoleName),
|
||||
handler.OpenAPIDef{
|
||||
ID: "GetQuickFilters",
|
||||
Tags: []string{"quick_filter"},
|
||||
Summary: "Get a source's quick filters",
|
||||
Description: "Returns the org's quick filters for one source, each filter as a telemetry field key.",
|
||||
Request: nil,
|
||||
RequestContentType: "",
|
||||
Response: new(quickfiltertypes.SourceFilters),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newScopedSecuritySchemes([]string{coretypes.ResourceMetaResourceQuickFilter.Scope(coretypes.VerbRead)}),
|
||||
},
|
||||
handler.WithResourceDefs(handler.BasicResourceDef{
|
||||
Resource: coretypes.ResourceMetaResourceQuickFilter,
|
||||
Verb: coretypes.VerbRead,
|
||||
Category: coretypes.ActionCategoryDataAccess,
|
||||
ID: coretypes.PathParam("source"),
|
||||
Selector: provider.quickFilterSelector,
|
||||
}),
|
||||
)).Methods(http.MethodGet).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/quick_filters/{source}", handler.New(
|
||||
provider.authzMiddleware.CheckResources(provider.quickFilterHandler.UpdateQuickFiltersV2, authtypes.SigNozAdminRoleName),
|
||||
handler.OpenAPIDef{
|
||||
ID: "UpdateQuickFilters",
|
||||
Tags: []string{"quick_filter"},
|
||||
Summary: "Update quick filters",
|
||||
Description: "Replaces the org's quick filters for the source named in the path.",
|
||||
Request: new(quickfiltertypes.UpdatableQuickFilters),
|
||||
RequestContentType: "application/json",
|
||||
Response: nil,
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newScopedSecuritySchemes([]string{coretypes.ResourceMetaResourceQuickFilter.Scope(coretypes.VerbUpdate)}),
|
||||
},
|
||||
handler.WithResourceDefs(handler.BasicResourceDef{
|
||||
Resource: coretypes.ResourceMetaResourceQuickFilter,
|
||||
Verb: coretypes.VerbUpdate,
|
||||
Category: coretypes.ActionCategoryConfigurationChange,
|
||||
ID: coretypes.PathParam("source"),
|
||||
Selector: provider.quickFilterSelector,
|
||||
}),
|
||||
)).Methods(http.MethodPut).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (provider *provider) quickFilterSelector(ctx context.Context, resource coretypes.Resource, source string, orgID valuer.UUID) ([]coretypes.Selector, error) {
|
||||
validatedSource, err := quickfiltertypes.NewSource(source)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// A source can have no stored row yet: GET serves it as empty and PUT
|
||||
// creates it, so only the wildcard grant applies until the row exists.
|
||||
quickFilter, err := provider.quickFilterModule.Get(ctx, orgID, validatedSource)
|
||||
if err != nil {
|
||||
if errors.Ast(err, errors.TypeNotFound) {
|
||||
return []coretypes.Selector{resource.Type().MustSelector(coretypes.WildCardSelectorString)}, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return []coretypes.Selector{
|
||||
resource.Type().MustSelector(quickFilter.ID.StringValue()),
|
||||
resource.Type().MustSelector(coretypes.WildCardSelectorString),
|
||||
}, nil
|
||||
}
|
||||
@@ -22,6 +22,9 @@ type Gateway interface {
|
||||
// Search Ingestion Keys by Name (this is supposed to be for the current user but for now in gateway code this is ignoring the consumer user)
|
||||
SearchIngestionKeysByName(ctx context.Context, orgID valuer.UUID, name string, page, perPage int) (*gatewaytypes.GettableIngestionKeys, error)
|
||||
|
||||
// Get Ingestion Key
|
||||
GetIngestionKey(ctx context.Context, orgID valuer.UUID, keyID string) (*gatewaytypes.IngestionKey, error)
|
||||
|
||||
// Create Ingestion Key
|
||||
CreateIngestionKey(ctx context.Context, orgID valuer.UUID, name string, tags []string, expiresAt time.Time) (*gatewaytypes.GettableCreatedIngestionKey, error)
|
||||
|
||||
@@ -34,6 +37,12 @@ type Gateway interface {
|
||||
// Create Ingestion Key Limit
|
||||
CreateIngestionKeyLimit(ctx context.Context, orgID valuer.UUID, keyID string, signal string, limitConfig gatewaytypes.LimitConfig, tags []string) (*gatewaytypes.GettableCreatedIngestionKeyLimit, error)
|
||||
|
||||
// Get Ingestion Key Limit
|
||||
GetIngestionKeyLimit(ctx context.Context, orgID valuer.UUID, limitID string) (*gatewaytypes.Limit, error)
|
||||
|
||||
// Get Ingestion Key Limits
|
||||
GetIngestionKeyLimits(ctx context.Context, orgID valuer.UUID, keyID string) ([]gatewaytypes.Limit, error)
|
||||
|
||||
// Update Ingestion Key Limit
|
||||
UpdateIngestionKeyLimit(ctx context.Context, orgID valuer.UUID, limitID string, limitConfig gatewaytypes.LimitConfig, tags []string) error
|
||||
|
||||
@@ -46,6 +55,8 @@ type Handler interface {
|
||||
|
||||
SearchIngestionKeys(http.ResponseWriter, *http.Request)
|
||||
|
||||
GetIngestionKey(http.ResponseWriter, *http.Request)
|
||||
|
||||
CreateIngestionKey(http.ResponseWriter, *http.Request)
|
||||
|
||||
UpdateIngestionKey(http.ResponseWriter, *http.Request)
|
||||
@@ -54,7 +65,13 @@ type Handler interface {
|
||||
|
||||
CreateIngestionKeyLimit(http.ResponseWriter, *http.Request)
|
||||
|
||||
GetIngestionKeyLimit(http.ResponseWriter, *http.Request)
|
||||
|
||||
GetIngestionKeyLimits(http.ResponseWriter, *http.Request)
|
||||
|
||||
UpdateIngestionKeyLimit(http.ResponseWriter, *http.Request)
|
||||
|
||||
DeleteIngestionKeyLimit(http.ResponseWriter, *http.Request)
|
||||
|
||||
DeprecatedCreateIngestionKeyLimit(http.ResponseWriter, *http.Request)
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package gateway
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/http/binding"
|
||||
"github.com/SigNoz/signoz/pkg/http/render"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/gatewaytypes"
|
||||
@@ -111,8 +111,8 @@ func (handler *handler) CreateIngestionKey(rw http.ResponseWriter, r *http.Reque
|
||||
orgID := valuer.MustNewUUID(claims.OrgID)
|
||||
|
||||
var req gatewaytypes.PostableIngestionKey
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
render.Error(rw, errors.New(errors.TypeInvalidInput, errors.CodeInvalidInput, "invalid request body"))
|
||||
if err := binding.JSON.BindBody(r.Body, &req); err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -143,8 +143,8 @@ func (handler *handler) UpdateIngestionKey(rw http.ResponseWriter, r *http.Reque
|
||||
}
|
||||
|
||||
var req gatewaytypes.PostableIngestionKey
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
render.Error(rw, errors.New(errors.TypeInvalidInput, errors.CodeInvalidInput, "invalid request body"))
|
||||
if err := binding.JSON.BindBody(r.Body, &req); err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -183,7 +183,7 @@ func (handler *handler) DeleteIngestionKey(rw http.ResponseWriter, r *http.Reque
|
||||
render.Success(rw, http.StatusNoContent, nil)
|
||||
}
|
||||
|
||||
func (handler *handler) CreateIngestionKeyLimit(rw http.ResponseWriter, r *http.Request) {
|
||||
func (handler *handler) DeprecatedCreateIngestionKeyLimit(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
claims, err := authtypes.ClaimsFromContext(ctx)
|
||||
@@ -200,9 +200,9 @@ func (handler *handler) CreateIngestionKeyLimit(rw http.ResponseWriter, r *http.
|
||||
return
|
||||
}
|
||||
|
||||
var req gatewaytypes.PostableIngestionKeyLimit
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
render.Error(rw, errors.New(errors.TypeInvalidInput, errors.CodeInvalidInput, "invalid request body"))
|
||||
var req gatewaytypes.DeprecatedPostableIngestionKeyLimit
|
||||
if err := binding.JSON.BindBody(r.Body, &req); err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -233,8 +233,8 @@ func (handler *handler) UpdateIngestionKeyLimit(rw http.ResponseWriter, r *http.
|
||||
}
|
||||
|
||||
var req gatewaytypes.UpdatableIngestionKeyLimit
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
render.Error(rw, errors.New(errors.TypeInvalidInput, errors.CodeInvalidInput, "invalid request body"))
|
||||
if err := binding.JSON.BindBody(r.Body, &req); err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -273,6 +273,115 @@ func (handler *handler) DeleteIngestionKeyLimit(rw http.ResponseWriter, r *http.
|
||||
render.Success(rw, http.StatusNoContent, nil)
|
||||
}
|
||||
|
||||
func (handler *handler) CreateIngestionKeyLimit(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
claims, err := authtypes.ClaimsFromContext(ctx)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
orgID := valuer.MustNewUUID(claims.OrgID)
|
||||
|
||||
var req gatewaytypes.PostableIngestionKeyLimit
|
||||
if err := binding.JSON.BindBody(r.Body, &req); err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
if req.KeyID == "" {
|
||||
render.Error(rw, errors.New(errors.TypeInvalidInput, errors.CodeInvalidInput, "keyId is required"))
|
||||
return
|
||||
}
|
||||
|
||||
response, err := handler.gateway.CreateIngestionKeyLimit(ctx, orgID, req.KeyID, req.Signal, req.Config, req.Tags)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
render.Success(rw, http.StatusCreated, response)
|
||||
}
|
||||
|
||||
func (handler *handler) GetIngestionKeyLimit(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
claims, err := authtypes.ClaimsFromContext(ctx)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
orgID := valuer.MustNewUUID(claims.OrgID)
|
||||
|
||||
limitID := mux.Vars(r)["limitId"]
|
||||
if limitID == "" {
|
||||
render.Error(rw, errors.New(errors.TypeInvalidInput, errors.CodeInvalidInput, "limitId is required"))
|
||||
return
|
||||
}
|
||||
|
||||
response, err := handler.gateway.GetIngestionKeyLimit(ctx, orgID, limitID)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
render.Success(rw, http.StatusOK, response)
|
||||
}
|
||||
|
||||
func (handler *handler) GetIngestionKey(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
claims, err := authtypes.ClaimsFromContext(ctx)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
orgID := valuer.MustNewUUID(claims.OrgID)
|
||||
|
||||
keyID := mux.Vars(r)["keyId"]
|
||||
if keyID == "" {
|
||||
render.Error(rw, errors.New(errors.TypeInvalidInput, errors.CodeInvalidInput, "keyId is required"))
|
||||
return
|
||||
}
|
||||
|
||||
response, err := handler.gateway.GetIngestionKey(ctx, orgID, keyID)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
render.Success(rw, http.StatusOK, response)
|
||||
}
|
||||
|
||||
func (handler *handler) GetIngestionKeyLimits(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
claims, err := authtypes.ClaimsFromContext(ctx)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
orgID := valuer.MustNewUUID(claims.OrgID)
|
||||
|
||||
keyID := mux.Vars(r)["keyId"]
|
||||
if keyID == "" {
|
||||
render.Error(rw, errors.New(errors.TypeInvalidInput, errors.CodeInvalidInput, "keyId is required"))
|
||||
return
|
||||
}
|
||||
|
||||
response, err := handler.gateway.GetIngestionKeyLimits(ctx, orgID, keyID)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
render.Success(rw, http.StatusOK, response)
|
||||
}
|
||||
|
||||
func parseIntWithDefaultValue(value string, defaultValue int) (int, error) {
|
||||
if value == "" {
|
||||
return defaultValue, nil
|
||||
|
||||
@@ -31,6 +31,10 @@ func (p *provider) SearchIngestionKeysByName(_ context.Context, _ valuer.UUID, _
|
||||
return nil, errors.New(errors.TypeUnsupported, gateway.ErrCodeGatewayUnsupported, "unsupported call")
|
||||
}
|
||||
|
||||
func (p *provider) GetIngestionKey(_ context.Context, _ valuer.UUID, _ string) (*gatewaytypes.IngestionKey, error) {
|
||||
return nil, errors.New(errors.TypeUnsupported, gateway.ErrCodeGatewayUnsupported, "unsupported call")
|
||||
}
|
||||
|
||||
func (p *provider) CreateIngestionKey(_ context.Context, _ valuer.UUID, _ string, _ []string, _ time.Time) (*gatewaytypes.GettableCreatedIngestionKey, error) {
|
||||
return nil, errors.New(errors.TypeUnsupported, gateway.ErrCodeGatewayUnsupported, "unsupported call")
|
||||
}
|
||||
@@ -47,6 +51,14 @@ func (p *provider) CreateIngestionKeyLimit(_ context.Context, _ valuer.UUID, _ s
|
||||
return nil, errors.New(errors.TypeUnsupported, gateway.ErrCodeGatewayUnsupported, "unsupported call")
|
||||
}
|
||||
|
||||
func (p *provider) GetIngestionKeyLimit(_ context.Context, _ valuer.UUID, _ string) (*gatewaytypes.Limit, error) {
|
||||
return nil, errors.New(errors.TypeUnsupported, gateway.ErrCodeGatewayUnsupported, "unsupported call")
|
||||
}
|
||||
|
||||
func (p *provider) GetIngestionKeyLimits(_ context.Context, _ valuer.UUID, _ string) ([]gatewaytypes.Limit, error) {
|
||||
return nil, errors.New(errors.TypeUnsupported, gateway.ErrCodeGatewayUnsupported, "unsupported call")
|
||||
}
|
||||
|
||||
func (p *provider) UpdateIngestionKeyLimit(_ context.Context, _ valuer.UUID, _ string, _ gatewaytypes.LimitConfig, _ []string) error {
|
||||
return errors.New(errors.TypeUnsupported, gateway.ErrCodeGatewayUnsupported, "unsupported call")
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user