Compare commits

..

41 Commits

Author SHA1 Message Date
grandwizard28
719b1b14f9 refactor(frontend): retire GettableAlert in favor of generated type
Status and AlertHeader consume RuletypesGettableRuleDTO directly from
the generated OpenAPI client. Delete types/api/alerts/{get,getAll,patch}.ts
now that nothing imports from them.
2026-04-17 05:25:10 +05:30
grandwizard28
82d4040050 test(frontend): satisfy newly-required condition/ruleType in filterAlerts mocks
After merging the backend schema tightening, RuletypesGettableRuleDTO
requires `condition` and `ruleType`. Update the test fixtures to
include placeholder values so they type-check against the stricter
shape.
2026-04-17 05:22:06 +05:30
grandwizard28
ca01a288a5 Merge branch 'platform-pod/issues/2095' into platform-pod/issues/2095-frontend 2026-04-17 05:20:14 +05:30
grandwizard28
3c1ee390a9 refactor(ruler): tighten schema with oneOf unions and required fields
Surface the polymorphism in RuleThresholdData and EvaluationEnvelope via
JSONSchemaOneOf (the same pattern as QueryEnvelope), so the generated
TS types are discriminated unions with typed `spec` instead of unknown.
Also mark `alert`, `ruleType`, and `condition` required on PostableRule
so the generated TS types are non-optional for callers.
2026-04-17 05:18:34 +05:30
grandwizard28
bb1ca4f698 refactor(frontend): adopt generated rule types, drop as any casts
Switch reader components (ListAlertRules, Home/AlertRules, AlertDetails,
EditRules) to use RuletypesGettableRuleDTO directly from the generated
OpenAPI client. Writer callers cast once via `as unknown as
RuletypesPostableRuleDTO` at the API boundary. Also fixes a poll bug in
ListAlert.tsx that read the old refetchData.payload shape.
2026-04-17 05:04:09 +05:30
grandwizard28
18b49afd4d refactor(frontend): adopt IngestionSettings date pattern in PlannedDowntime
Replace all (x as unknown) as Date/string double casts with the
established codebase pattern: new Date() for writes to API, dayjs()
for reads. Removes String() wraps in PlannedDowntimeList in favor of
dayjs().toISOString(). Tests use new Date() instead of double casts.
2026-04-17 03:16:03 +05:30
grandwizard28
d804d9ca3c fix(frontend): mock useErrorModal in Footer test to fix provider error
Footer now calls useErrorModal which requires ErrorModalProvider. Add
mock to the test since it imports render from @testing-library/react
directly instead of the custom test-utils wrapper.
2026-04-17 03:06:18 +05:30
grandwizard28
b4e39f82f6 fix(frontend): update PlannedDowntime test mock to use generated rules hook
Replace deleted api/alerts/getAll mock with api/generated/services/rules
mock returning useListRules shape.
2026-04-17 03:01:45 +05:30
grandwizard28
ad834ff618 fix(frontend): handle null rules from API to prevent infinite spinner
The generated schema allows rules?: ...[] | null. When the API returns
null, the component treated it as still-loading. Normalize null to []
via nullish coalescing and gate loading on data presence, not rules
truthiness.
2026-04-17 03:00:00 +05:30
grandwizard28
cdd667e9ea fix(frontend): remove downtime ID number coercion, delete routing adapter
Delete DowntimeScheduleUpdatePayload and createEditDowntimeSchedule —
the form now calls createDowntimeSchedule/updateDowntimeScheduleByID
directly, keeping the string ID from the generated schema end-to-end
instead of coercing to number.
2026-04-17 02:56:51 +05:30
grandwizard28
5a4b8c4a4f refactor(frontend): integrate convertToApiError and error modal across rules/downtime
Replace all raw (error as any)?.message casts and generic "Something
went wrong" strings with proper error handling using convertToApiError
and showErrorModal. Query errors use convertToApiError for message
extraction, mutation errors show the rich error modal.
2026-04-17 02:52:34 +05:30
grandwizard28
3e19f2f308 refactor(frontend): migrate updateAlertRule to generated useUpdateRuleByID
Delete hooks/alerts/useUpdateAlertRule.ts and api/alerts/updateAlertRule.ts.
Migrate CreateAlertV2 context to use useUpdateRuleByID from generated
services. Expose ruleId on context so Footer can pass it to the mutation.
2026-04-17 02:26:40 +05:30
grandwizard28
37b5e03e24 refactor(frontend): delete dead API adapters, migrate save to generated hooks
Delete 14 unused hand-written API adapter files for alerts and planned
downtime. Migrate the 3 consumers of save.ts to call createRule and
updateRuleByID from generated services directly, removing the
create/update routing layer.
2026-04-17 02:21:26 +05:30
grandwizard28
0e8a328146 refactor(frontend): delete dead re-export wrappers for rules hooks
Remove api/alerts/getAll.ts, hooks/alerts/useCreateAlertRule.ts, and
hooks/alerts/useTestAlertRule.ts — all were single-line re-exports of
generated hooks with zero unique logic. Update the one consumer
(CreateAlertV2 context) and its test to import directly from the
generated services.
2026-04-17 02:12:03 +05:30
grandwizard28
72f0e16b77 fix(frontend): fix accidental rename, cache fragmentation, and sort mutation
- Revert setRuletypesRecurrenceDTOType back to setRecurrenceType (find-replace artifact)
- Remove duplicate isError condition in AlertDetails guard
- Replace manual useQuery(listRules) with useListRules in PlannedDowntime and hooks
- Fix showErrorNotification import path in PlannedDowntimeutils
- Use spread before sort to avoid mutating react-query cache
2026-04-17 02:07:57 +05:30
grandwizard28
2dc396effe refactor(frontend): switch to generated react-query hooks directly
Replace adapter wrappers with direct usage of generated hooks:
- useListRules() in ListAlertRules, AlertRules
- useGetRuleByID() in AlertDetails hooks, EditRules
- useCreateRule re-exported as useCreateAlertRule
- useTestRule re-exported as useTestAlertRule
- Update consumers to access data.data instead of data.payload
- Remove SuccessResponse envelope wrappers
2026-04-17 01:54:22 +05:30
grandwizard28
5d74dedfc9 refactor(frontend): extract getAll adapter to shared api/alerts/getAll.ts
Remove duplicated inline getAll adapter from ListAlertRules and
AlertRules components. Replace the old hand-written getAll.ts with a
thin adapter over the generated listRules client.
2026-04-17 01:39:32 +05:30
grandwizard28
c1b70e82d5 refactor(frontend): migrate rules APIs to generated clients
Replace hand-written getAll, get, delete, patch imports with generated
listRules, getRuleByID, deleteRuleByID, patchRuleByID from Orval
clients. Use adapter wrappers in consumers that depend on the old
SuccessResponse envelope to minimize cascading type changes.
2026-04-17 01:25:46 +05:30
grandwizard28
af692d681b refactor(frontend): migrate createAlertRule API to generated createRule client
Update useCreateAlertRule hook to use generated createRule function.
Maintain backward-compatible SuccessResponse wrapper for CreateAlertV2
context consumers.
2026-04-17 01:13:16 +05:30
grandwizard28
5f805a2ad0 refactor(frontend): migrate testAlert API to generated testRule client
Replace direct testAlertApi call in FormAlertRules with generated
testRule function. Update useTestAlertRule hook to use generated client
while maintaining backward-compatible wrapper types for CreateAlertV2
context consumers.
2026-04-17 01:10:29 +05:30
grandwizard28
dfb3c6adf5 Merge branch 'platform-pod/issues/2095' into platform-pod/issues/2095-frontend 2026-04-17 00:57:36 +05:30
grandwizard28
5d5a3b135d refactor(ruler): add GettableTestRule response type to TestRule endpoint
Define GettableTestRule struct with AlertCount and Message fields.
Use it as the Response in TestRule OpenAPIDef so the generated frontend
client has a proper response type instead of string.
2026-04-17 00:55:56 +05:30
grandwizard28
aea55afb0a refactor(frontend): migrate all downtime schedule APIs to generated clients
Replace hand-written getAllDowntimeSchedules, createDowntimeSchedule,
updateDowntimeSchedule with generated useListDowntimeSchedules,
createDowntimeSchedule, updateDowntimeScheduleByID from Orval clients.

Update all type references from DowntimeSchedules/Recurrence to
RuletypesGettablePlannedMaintenanceDTO/RuletypesRecurrenceDTO.
Update tests to mock generated hooks.
2026-04-17 00:42:54 +05:30
grandwizard28
a2d7ff166d refactor(frontend): migrate deleteDowntimeSchedule to generated client
Replace useDeleteDowntimeSchedule with useDeleteDowntimeScheduleByID
from the generated API client. Update PlannedDowntime.tsx and
PlannedDowntimeutils.ts to use pathParams pattern.
2026-04-17 00:17:58 +05:30
grandwizard28
3aefd7a549 refactor(ruler): add query params to ListDowntimeSchedules OpenAPIDef
Add ListPlannedMaintenanceParams struct with active/recurring fields.
Use binding.Query.BindQuery in the handler instead of raw URL parsing.
Add RequestQuery to the OpenAPIDef so params appear in the OpenAPI spec
and generated frontend client.
2026-04-17 00:00:16 +05:30
grandwizard28
ecbbeb9103 refactor(ruler): rename downtime_schedules tag to downtimeschedules 2026-04-16 20:44:36 +05:30
grandwizard28
27a073770b docs: regenerate OpenAPI spec and frontend API clients with ruler routes 2026-04-16 20:44:36 +05:30
grandwizard28
da1b093c7f refactor(ruler): add TODO on MaintenanceStore to not expose store directly 2026-04-16 20:44:36 +05:30
grandwizard28
61cd81b072 refactor(ruler): add TODOs for raw string params on Ruler interface
Mark CreateRule, EditRule, PatchRule, TestNotification, and DeleteRule
with TODOs to accept typed params instead of raw JSON strings. Requires
changing the storage model since the manager stores raw JSON as Data.
2026-04-16 20:44:36 +05:30
grandwizard28
c40a3dbc08 refactor(ruler): use binding.JSON.BindBody for downtime schedule decode 2026-04-16 20:44:36 +05:30
grandwizard28
f45228b605 refactor(ruler): add /api/v1/rules/test and mark /api/v1/testRule as deprecated 2026-04-16 20:44:35 +05:30
grandwizard28
20dcd11d80 refactor(ruler): remove RuleManager from APIHandlerOpts
Use Signoz.Ruler directly instead of passing it through opts.
2026-04-16 20:44:35 +05:30
grandwizard28
99ceb7149f refactor(ruler): remove unused RM() accessor from EE APIHandler 2026-04-16 20:44:35 +05:30
grandwizard28
2e6da1092c fix(ruler): make Start block on stopC per factory.Service contract
rules.Manager.Start is non-blocking (run() just closes a channel).
Add stopC to provider so Start blocks until Stop closes it, matching
the factory.Service contract used by the Registry.
2026-04-16 20:44:35 +05:30
grandwizard28
70533f91a9 refactor(ruler): use ProviderFactory pattern and register in factory.Registry
Replace the rulerCallback with rulerProviderFactories following the
standard ProviderFactory pattern (like auditorProviderFactories). The
ruler is now created via factory.NewProviderFromNamedMap and registered
in factory.Registry for lifecycle management. Start/Stop are no longer
called manually in server.go.

- Ruler interface embeds factory.Service (Start/Stop return error)
- signozruler.NewFactory accepts all deps including EE task funcs
- provider uses named field (not embedding) with explicit delegation
- cmd/community passes nil task funcs, cmd/enterprise passes EE funcs
- Remove NewRulerProviderFactories (replaced by callback from cmd/)
- Remove manual Start/Stop from both OSS and EE server.go
2026-04-16 20:44:35 +05:30
grandwizard28
92062ef0f2 refactor(ruler): remove old rules and downtime_schedules routes from http_handler
Remove 7 rules CRUD routes and 5 downtime_schedules routes plus their
handler methods from http_handler.go. These are now served by
signozapiserver/ruler.go via handler.New() with OpenAPIDef.

The 4 v1 history routes (stats, timeline, top_contributors,
overall_status) remain in http_handler.go as they depend on
interfaces.Reader and have v2 equivalents already in signozapiserver.
2026-04-16 20:44:35 +05:30
grandwizard28
ec85586fa1 refactor(ruler): wire ruler handler through signoz.New and signozapiserver
- Add Start/Stop to Ruler interface for lifecycle management
- Add rulerCallback to signoz.New() for EE customization
- Wire ruler.Handler through Handlers, signozapiserver provider
- Register 12 routes in signozapiserver/ruler.go (7 rules, 5 downtime)
- Update cmd/community and cmd/enterprise to pass rulerCallback
- Move rules.Manager creation from server.go to signoz.New via callback
- Change APIHandler.ruleManager type from *rules.Manager to ruler.Ruler
- Remove makeRulesManager from both OSS and EE server.go
2026-04-16 20:44:35 +05:30
grandwizard28
950524c0f7 refactor(ruler): add godoc comments and spacing to Ruler interface 2026-04-16 20:44:35 +05:30
grandwizard28
9305a45cd1 refactor(ruler): use time.Duration for eval_delay config
Match the convention used by all other configs in the codebase.
TextDuration is for preserving human-readable text through JSON
round-trips in user-facing rule definitions, not for internal config.
2026-04-16 20:44:34 +05:30
grandwizard28
e90e9bd9ce refactor(ruler): move eval_delay from query-service constants to ruler config
Replace constants.GetEvalDelay() with config.EvalDelay on ruler.Config,
defaulting to 2m. This removes the signozruler dependency on
pkg/query-service/constants.
2026-04-16 20:44:34 +05:30
grandwizard28
fdb8b45667 refactor(ruler): define Ruler and Handler interfaces with signozruler implementation
Expand the Ruler interface with rule management and planned maintenance
methods matching rules.Manager signatures. Add Handler interface for
HTTP endpoints. Implement handler in signozruler wrapping ruler.Ruler,
and update provider to embed *rules.Manager for interface satisfaction.
2026-04-16 20:44:34 +05:30
92 changed files with 4851 additions and 2551 deletions

View File

@@ -7,6 +7,7 @@ import (
"github.com/spf13/cobra"
"github.com/SigNoz/signoz/cmd"
"github.com/SigNoz/signoz/pkg/alertmanager"
"github.com/SigNoz/signoz/pkg/analytics"
"github.com/SigNoz/signoz/pkg/auditor"
"github.com/SigNoz/signoz/pkg/authn"
@@ -14,6 +15,7 @@ import (
"github.com/SigNoz/signoz/pkg/authz/openfgaauthz"
"github.com/SigNoz/signoz/pkg/authz/openfgaschema"
"github.com/SigNoz/signoz/pkg/authz/openfgaserver"
"github.com/SigNoz/signoz/pkg/cache"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/gateway"
@@ -26,14 +28,20 @@ import (
"github.com/SigNoz/signoz/pkg/modules/dashboard"
"github.com/SigNoz/signoz/pkg/modules/dashboard/impldashboard"
"github.com/SigNoz/signoz/pkg/modules/organization"
"github.com/SigNoz/signoz/pkg/modules/rulestatehistory"
"github.com/SigNoz/signoz/pkg/modules/serviceaccount"
"github.com/SigNoz/signoz/pkg/prometheus"
"github.com/SigNoz/signoz/pkg/querier"
"github.com/SigNoz/signoz/pkg/query-service/app"
"github.com/SigNoz/signoz/pkg/queryparser"
"github.com/SigNoz/signoz/pkg/ruler"
"github.com/SigNoz/signoz/pkg/ruler/signozruler"
"github.com/SigNoz/signoz/pkg/signoz"
"github.com/SigNoz/signoz/pkg/sqlschema"
"github.com/SigNoz/signoz/pkg/sqlstore"
"github.com/SigNoz/signoz/pkg/telemetrystore"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
"github.com/SigNoz/signoz/pkg/version"
"github.com/SigNoz/signoz/pkg/zeus"
"github.com/SigNoz/signoz/pkg/zeus/noopzeus"
@@ -107,6 +115,9 @@ func runServer(ctx context.Context, config signoz.Config, logger *slog.Logger) e
func(_ sqlstore.SQLStore, _ global.Global, _ zeus.Zeus, _ gateway.Gateway, _ licensing.Licensing, _ serviceaccount.Module, _ cloudintegration.Config) (cloudintegration.Module, error) {
return implcloudintegration.NewModule(), nil
},
func(c cache.Cache, am alertmanager.Alertmanager, ss sqlstore.SQLStore, ts telemetrystore.TelemetryStore, ms telemetrytypes.MetadataStore, p prometheus.Prometheus, og organization.Getter, rsh rulestatehistory.Module, q querier.Querier, qp queryparser.QueryParser) factory.NamedMap[factory.ProviderFactory[ruler.Ruler, ruler.Config]] {
return factory.MustNewNamedMap(signozruler.NewFactory(c, am, ss, ts, ms, p, og, rsh, q, qp, nil, nil))
},
)
if err != nil {
logger.ErrorContext(ctx, "failed to create signoz", errors.Attr(err))

View File

@@ -22,14 +22,17 @@ import (
"github.com/SigNoz/signoz/ee/modules/dashboard/impldashboard"
eequerier "github.com/SigNoz/signoz/ee/querier"
enterpriseapp "github.com/SigNoz/signoz/ee/query-service/app"
eerules "github.com/SigNoz/signoz/ee/query-service/rules"
"github.com/SigNoz/signoz/ee/sqlschema/postgressqlschema"
"github.com/SigNoz/signoz/ee/sqlstore/postgressqlstore"
enterprisezeus "github.com/SigNoz/signoz/ee/zeus"
"github.com/SigNoz/signoz/ee/zeus/httpzeus"
"github.com/SigNoz/signoz/pkg/alertmanager"
"github.com/SigNoz/signoz/pkg/analytics"
"github.com/SigNoz/signoz/pkg/auditor"
"github.com/SigNoz/signoz/pkg/authn"
"github.com/SigNoz/signoz/pkg/authz"
"github.com/SigNoz/signoz/pkg/cache"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/gateway"
@@ -40,15 +43,21 @@ import (
"github.com/SigNoz/signoz/pkg/modules/dashboard"
pkgimpldashboard "github.com/SigNoz/signoz/pkg/modules/dashboard/impldashboard"
"github.com/SigNoz/signoz/pkg/modules/organization"
"github.com/SigNoz/signoz/pkg/modules/rulestatehistory"
"github.com/SigNoz/signoz/pkg/modules/serviceaccount"
"github.com/SigNoz/signoz/pkg/prometheus"
"github.com/SigNoz/signoz/pkg/querier"
"github.com/SigNoz/signoz/pkg/queryparser"
"github.com/SigNoz/signoz/pkg/ruler"
"github.com/SigNoz/signoz/pkg/ruler/signozruler"
"github.com/SigNoz/signoz/pkg/signoz"
"github.com/SigNoz/signoz/pkg/sqlschema"
"github.com/SigNoz/signoz/pkg/sqlstore"
"github.com/SigNoz/signoz/pkg/sqlstore/sqlstorehook"
"github.com/SigNoz/signoz/pkg/telemetrystore"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/types/cloudintegrationtypes"
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
"github.com/SigNoz/signoz/pkg/version"
"github.com/SigNoz/signoz/pkg/zeus"
)
@@ -166,6 +175,9 @@ func runServer(ctx context.Context, config signoz.Config, logger *slog.Logger) e
return implcloudintegration.NewModule(pkgcloudintegration.NewStore(sqlStore), global, zeus, gateway, licensing, serviceAccount, cloudProvidersMap, config)
},
func(c cache.Cache, am alertmanager.Alertmanager, ss sqlstore.SQLStore, ts telemetrystore.TelemetryStore, ms telemetrytypes.MetadataStore, p prometheus.Prometheus, og organization.Getter, rsh rulestatehistory.Module, q querier.Querier, qp queryparser.QueryParser) factory.NamedMap[factory.ProviderFactory[ruler.Ruler, ruler.Config]] {
return factory.MustNewNamedMap(signozruler.NewFactory(c, am, ss, ts, ms, p, og, rsh, q, qp, eerules.PrepareTaskFunc, eerules.TestNotification))
},
)
if err != nil {
logger.ErrorContext(ctx, "failed to create signoz", errors.Attr(err))

File diff suppressed because it is too large Load Diff

View File

@@ -14,7 +14,6 @@ import (
"github.com/SigNoz/signoz/pkg/query-service/app/logparsingpipeline"
"github.com/SigNoz/signoz/pkg/query-service/interfaces"
basemodel "github.com/SigNoz/signoz/pkg/query-service/model"
rules "github.com/SigNoz/signoz/pkg/query-service/rules"
"github.com/SigNoz/signoz/pkg/queryparser"
"github.com/SigNoz/signoz/pkg/signoz"
"github.com/SigNoz/signoz/pkg/version"
@@ -23,7 +22,6 @@ import (
type APIHandlerOptions struct {
DataConnector interfaces.Reader
RulesManager *rules.Manager
UsageManager *usage.Manager
IntegrationsController *integrations.Controller
CloudIntegrationsController *cloudintegrations.Controller
@@ -43,7 +41,6 @@ type APIHandler struct {
func NewAPIHandler(opts APIHandlerOptions, signoz *signoz.SigNoz, config signoz.Config) (*APIHandler, error) {
baseHandler, err := baseapp.NewAPIHandler(baseapp.APIHandlerOpts{
Reader: opts.DataConnector,
RuleManager: opts.RulesManager,
IntegrationsController: opts.IntegrationsController,
CloudIntegrationsController: opts.CloudIntegrationsController,
LogsParsingPipelineController: opts.LogsParsingPipelineController,
@@ -64,10 +61,6 @@ func NewAPIHandler(opts APIHandlerOptions, signoz *signoz.SigNoz, config signoz.
return ah, nil
}
func (ah *APIHandler) RM() *rules.Manager {
return ah.opts.RulesManager
}
func (ah *APIHandler) UM() *usage.Manager {
return ah.opts.UsageManager
}

View File

@@ -12,10 +12,6 @@ import (
"github.com/SigNoz/signoz/pkg/cache/memorycache"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/queryparser"
"github.com/SigNoz/signoz/pkg/ruler/rulestore/sqlrulestore"
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
"github.com/gorilla/handlers"
@@ -23,18 +19,10 @@ import (
"github.com/soheilhy/cmux"
"github.com/SigNoz/signoz/ee/query-service/app/api"
"github.com/SigNoz/signoz/ee/query-service/rules"
"github.com/SigNoz/signoz/ee/query-service/usage"
"github.com/SigNoz/signoz/pkg/alertmanager"
"github.com/SigNoz/signoz/pkg/cache"
"github.com/SigNoz/signoz/pkg/http/middleware"
"github.com/SigNoz/signoz/pkg/modules/organization"
"github.com/SigNoz/signoz/pkg/modules/rulestatehistory"
"github.com/SigNoz/signoz/pkg/prometheus"
"github.com/SigNoz/signoz/pkg/querier"
"github.com/SigNoz/signoz/pkg/signoz"
"github.com/SigNoz/signoz/pkg/sqlstore"
"github.com/SigNoz/signoz/pkg/telemetrystore"
"github.com/SigNoz/signoz/pkg/web"
"log/slog"
@@ -49,7 +37,6 @@ import (
opAmpModel "github.com/SigNoz/signoz/pkg/query-service/app/opamp/model"
baseconst "github.com/SigNoz/signoz/pkg/query-service/constants"
"github.com/SigNoz/signoz/pkg/query-service/healthcheck"
baserules "github.com/SigNoz/signoz/pkg/query-service/rules"
"github.com/SigNoz/signoz/pkg/query-service/utils"
)
@@ -57,7 +44,6 @@ import (
type Server struct {
config signoz.Config
signoz *signoz.SigNoz
ruleManager *baserules.Manager
// public http router
httpConn net.Listener
@@ -97,24 +83,6 @@ func NewServer(config signoz.Config, signoz *signoz.SigNoz) (*Server, error) {
nil,
)
rm, err := makeRulesManager(
signoz.Cache,
signoz.Alertmanager,
signoz.SQLStore,
signoz.TelemetryStore,
signoz.TelemetryMetadataStore,
signoz.Prometheus,
signoz.Modules.OrgGetter,
signoz.Modules.RuleStateHistory,
signoz.Querier,
signoz.Instrumentation.ToProviderSettings(),
signoz.QueryParser,
)
if err != nil {
return nil, err
}
// initiate opamp
opAmpModel.Init(signoz.SQLStore, signoz.Instrumentation.Logger(), signoz.Modules.OrgGetter)
@@ -163,7 +131,6 @@ func NewServer(config signoz.Config, signoz *signoz.SigNoz) (*Server, error) {
apiOpts := api.APIHandlerOptions{
DataConnector: reader,
RulesManager: rm,
UsageManager: usageManager,
IntegrationsController: integrationsController,
CloudIntegrationsController: cloudIntegrationsController,
@@ -180,8 +147,7 @@ func NewServer(config signoz.Config, signoz *signoz.SigNoz) (*Server, error) {
s := &Server{
config: config,
signoz: signoz,
ruleManager: rm,
signoz: signoz,
httpHostPort: baseconst.HTTPHostPort,
unavailableChannel: make(chan healthcheck.Status),
usageManager: usageManager,
@@ -288,8 +254,6 @@ func (s *Server) initListeners() error {
// Start listening on http and private http port concurrently
func (s *Server) Start(ctx context.Context) error {
s.ruleManager.Start(ctx)
err := s.initListeners()
if err != nil {
return err
@@ -333,47 +297,9 @@ func (s *Server) Stop(ctx context.Context) error {
s.opampServer.Stop()
if s.ruleManager != nil {
s.ruleManager.Stop(ctx)
}
// stop usage manager
s.usageManager.Stop(ctx)
return nil
}
func makeRulesManager(cache cache.Cache, alertmanager alertmanager.Alertmanager, sqlstore sqlstore.SQLStore, telemetryStore telemetrystore.TelemetryStore, metadataStore telemetrytypes.MetadataStore, prometheus prometheus.Prometheus, orgGetter organization.Getter, ruleStateHistoryModule rulestatehistory.Module, querier querier.Querier, providerSettings factory.ProviderSettings, queryParser queryparser.QueryParser) (*baserules.Manager, error) {
ruleStore := sqlrulestore.NewRuleStore(sqlstore, queryParser, providerSettings)
maintenanceStore := sqlrulestore.NewMaintenanceStore(sqlstore)
// create manager opts
managerOpts := &baserules.ManagerOptions{
TelemetryStore: telemetryStore,
MetadataStore: metadataStore,
Prometheus: prometheus,
Context: context.Background(),
Querier: querier,
Logger: providerSettings.Logger,
Cache: cache,
EvalDelay: baseconst.GetEvalDelay(),
PrepareTaskFunc: rules.PrepareTaskFunc,
PrepareTestRuleFunc: rules.TestNotification,
Alertmanager: alertmanager,
OrgGetter: orgGetter,
RuleStore: ruleStore,
MaintenanceStore: maintenanceStore,
SQLStore: sqlstore,
QueryParser: queryParser,
RuleStateHistoryModule: ruleStateHistoryModule,
}
// create Manager
manager, err := baserules.NewManager(managerOpts)
if err != nil {
return nil, fmt.Errorf("rule manager error: %v", err)
}
slog.Info("rules manager is ready")
return manager, nil
}

View File

@@ -1,20 +0,0 @@
import axios from 'api';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { PayloadProps, Props } from 'types/api/alerts/create';
const create = async (
props: Props,
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
const response = await axios.post('/rules', {
...props.data,
});
return {
statusCode: 200,
error: null,
message: response.data.status,
payload: response.data.data,
};
};
export default create;

View File

@@ -1,28 +0,0 @@
import axios from 'api';
import { ErrorResponse, SuccessResponse } from 'types/api';
import {
AlertRuleV2,
PostableAlertRuleV2,
} from 'types/api/alerts/alertTypesV2';
export interface CreateAlertRuleResponse {
data: AlertRuleV2;
status: string;
}
const createAlertRule = async (
props: PostableAlertRuleV2,
): Promise<SuccessResponse<CreateAlertRuleResponse> | ErrorResponse> => {
const response = await axios.post(`/rules`, {
...props,
});
return {
statusCode: 200,
error: null,
message: response.data.status,
payload: response.data.data,
};
};
export default createAlertRule;

View File

@@ -1,18 +0,0 @@
import axios from 'api';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { PayloadProps, Props } from 'types/api/alerts/delete';
const deleteAlerts = async (
props: Props,
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
const response = await axios.delete(`/rules/${props.id}`);
return {
statusCode: 200,
error: null,
message: response.data.status,
payload: response.data.data.rules,
};
};
export default deleteAlerts;

View File

@@ -1,16 +0,0 @@
import axios from 'api';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { PayloadProps, Props } from 'types/api/alerts/get';
const get = async (
props: Props,
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
const response = await axios.get(`/rules/${props.id}`);
return {
statusCode: 200,
error: null,
message: response.data.status,
payload: response.data,
};
};
export default get;

View File

@@ -1,24 +0,0 @@
import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { PayloadProps } from 'types/api/alerts/getAll';
const getAll = async (): Promise<
SuccessResponse<PayloadProps> | ErrorResponse
> => {
try {
const response = await axios.get('/rules');
return {
statusCode: 200,
error: null,
message: response.data.status,
payload: response.data.data.rules,
};
} catch (error) {
return ErrorResponseHandler(error as AxiosError);
}
};
export default getAll;

View File

@@ -1,29 +0,0 @@
import { AxiosAlertManagerInstance } from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { AxiosError } from 'axios';
import convertObjectIntoParams from 'lib/query/convertObjectIntoParams';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { PayloadProps, Props } from 'types/api/alerts/getGroups';
const getGroups = async (
props: Props,
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
try {
const queryParams = convertObjectIntoParams(props);
const response = await AxiosAlertManagerInstance.get(
`/alerts/groups?${queryParams}`,
);
return {
statusCode: 200,
error: null,
message: response.data.status,
payload: response.data,
};
} catch (error) {
return ErrorResponseHandler(error as AxiosError);
}
};
export default getGroups;

View File

@@ -1,20 +0,0 @@
import axios from 'api';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { PayloadProps, Props } from 'types/api/alerts/patch';
const patch = async (
props: Props,
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
const response = await axios.patch(`/rules/${props.id}`, {
...props.data,
});
return {
statusCode: 200,
error: null,
message: response.data.status,
payload: response.data.data,
};
};
export default patch;

View File

@@ -1,20 +0,0 @@
import axios from 'api';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { PayloadProps, Props } from 'types/api/alerts/save';
const put = async (
props: Props,
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
const response = await axios.put(`/rules/${props.id}`, {
...props.data,
});
return {
statusCode: 200,
error: null,
message: response.data.status,
payload: response.data.data,
};
};
export default put;

View File

@@ -1,18 +0,0 @@
import { isEmpty } from 'lodash-es';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { PayloadProps, Props } from 'types/api/alerts/save';
import create from './create';
import put from './put';
const save = async (
props: Props,
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
if (props.id && !isEmpty(props.id)) {
return put({ ...props });
}
return create({ ...props });
};
export default save;

View File

@@ -1,26 +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/alerts/testAlert';
const testAlert = async (
props: Props,
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
try {
const response = await axios.post('/testRule', {
...props.data,
});
return {
statusCode: 200,
error: null,
message: response.data.status,
payload: response.data.data,
};
} catch (error) {
return ErrorResponseHandler(error as AxiosError);
}
};
export default testAlert;

View File

@@ -1,28 +0,0 @@
import axios from 'api';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { PostableAlertRuleV2 } from 'types/api/alerts/alertTypesV2';
export interface TestAlertRuleResponse {
data: {
alertCount: number;
message: string;
};
status: string;
}
const testAlertRule = async (
props: PostableAlertRuleV2,
): Promise<SuccessResponse<TestAlertRuleResponse> | ErrorResponse> => {
const response = await axios.post(`/testRule`, {
...props,
});
return {
statusCode: 200,
error: null,
message: response.data.status,
payload: response.data.data,
};
};
export default testAlertRule;

View File

@@ -1,26 +0,0 @@
import axios from 'api';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { PostableAlertRuleV2 } from 'types/api/alerts/alertTypesV2';
export interface UpdateAlertRuleResponse {
data: string;
status: string;
}
const updateAlertRule = async (
id: string,
postableAlertRule: PostableAlertRuleV2,
): Promise<SuccessResponse<UpdateAlertRuleResponse> | ErrorResponse> => {
const response = await axios.put(`/rules/${id}`, {
...postableAlertRule,
});
return {
statusCode: 200,
error: null,
message: response.data.status,
payload: response.data.data,
};
};
export default updateAlertRule;

View File

@@ -0,0 +1,495 @@
/**
* ! Do not edit manually
* * The file has been auto-generated using Orval for SigNoz
* * regenerate with 'yarn generate:api'
* SigNoz
*/
import type {
InvalidateOptions,
MutationFunction,
QueryClient,
QueryFunction,
QueryKey,
UseMutationOptions,
UseMutationResult,
UseQueryOptions,
UseQueryResult,
} from 'react-query';
import { useMutation, useQuery } from 'react-query';
import type { BodyType, ErrorType } from '../../../generatedAPIInstance';
import { GeneratedAPIInstance } from '../../../generatedAPIInstance';
import type {
DeleteDowntimeScheduleByIDPathParameters,
GetDowntimeScheduleByID200,
GetDowntimeScheduleByIDPathParameters,
ListDowntimeSchedules200,
ListDowntimeSchedulesParams,
RenderErrorResponseDTO,
RuletypesGettablePlannedMaintenanceDTO,
UpdateDowntimeScheduleByIDPathParameters,
} from '../sigNoz.schemas';
/**
* This endpoint lists all planned maintenance / downtime schedules
* @summary List downtime schedules
*/
export const listDowntimeSchedules = (
params?: ListDowntimeSchedulesParams,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<ListDowntimeSchedules200>({
url: `/api/v1/downtime_schedules`,
method: 'GET',
params,
signal,
});
};
export const getListDowntimeSchedulesQueryKey = (
params?: ListDowntimeSchedulesParams,
) => {
return [`/api/v1/downtime_schedules`, ...(params ? [params] : [])] as const;
};
export const getListDowntimeSchedulesQueryOptions = <
TData = Awaited<ReturnType<typeof listDowntimeSchedules>>,
TError = ErrorType<RenderErrorResponseDTO>
>(
params?: ListDowntimeSchedulesParams,
options?: {
query?: UseQueryOptions<
Awaited<ReturnType<typeof listDowntimeSchedules>>,
TError,
TData
>;
},
) => {
const { query: queryOptions } = options ?? {};
const queryKey =
queryOptions?.queryKey ?? getListDowntimeSchedulesQueryKey(params);
const queryFn: QueryFunction<
Awaited<ReturnType<typeof listDowntimeSchedules>>
> = ({ signal }) => listDowntimeSchedules(params, signal);
return { queryKey, queryFn, ...queryOptions } as UseQueryOptions<
Awaited<ReturnType<typeof listDowntimeSchedules>>,
TError,
TData
> & { queryKey: QueryKey };
};
export type ListDowntimeSchedulesQueryResult = NonNullable<
Awaited<ReturnType<typeof listDowntimeSchedules>>
>;
export type ListDowntimeSchedulesQueryError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary List downtime schedules
*/
export function useListDowntimeSchedules<
TData = Awaited<ReturnType<typeof listDowntimeSchedules>>,
TError = ErrorType<RenderErrorResponseDTO>
>(
params?: ListDowntimeSchedulesParams,
options?: {
query?: UseQueryOptions<
Awaited<ReturnType<typeof listDowntimeSchedules>>,
TError,
TData
>;
},
): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
const queryOptions = getListDowntimeSchedulesQueryOptions(params, options);
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
queryKey: QueryKey;
};
query.queryKey = queryOptions.queryKey;
return query;
}
/**
* @summary List downtime schedules
*/
export const invalidateListDowntimeSchedules = async (
queryClient: QueryClient,
params?: ListDowntimeSchedulesParams,
options?: InvalidateOptions,
): Promise<QueryClient> => {
await queryClient.invalidateQueries(
{ queryKey: getListDowntimeSchedulesQueryKey(params) },
options,
);
return queryClient;
};
/**
* This endpoint creates a new planned maintenance / downtime schedule
* @summary Create downtime schedule
*/
export const createDowntimeSchedule = (
ruletypesGettablePlannedMaintenanceDTO: BodyType<RuletypesGettablePlannedMaintenanceDTO>,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<void>({
url: `/api/v1/downtime_schedules`,
method: 'POST',
headers: { 'Content-Type': 'application/json' },
data: ruletypesGettablePlannedMaintenanceDTO,
signal,
});
};
export const getCreateDowntimeScheduleMutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof createDowntimeSchedule>>,
TError,
{ data: BodyType<RuletypesGettablePlannedMaintenanceDTO> },
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof createDowntimeSchedule>>,
TError,
{ data: BodyType<RuletypesGettablePlannedMaintenanceDTO> },
TContext
> => {
const mutationKey = ['createDowntimeSchedule'];
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 createDowntimeSchedule>>,
{ data: BodyType<RuletypesGettablePlannedMaintenanceDTO> }
> = (props) => {
const { data } = props ?? {};
return createDowntimeSchedule(data);
};
return { mutationFn, ...mutationOptions };
};
export type CreateDowntimeScheduleMutationResult = NonNullable<
Awaited<ReturnType<typeof createDowntimeSchedule>>
>;
export type CreateDowntimeScheduleMutationBody = BodyType<RuletypesGettablePlannedMaintenanceDTO>;
export type CreateDowntimeScheduleMutationError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary Create downtime schedule
*/
export const useCreateDowntimeSchedule = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof createDowntimeSchedule>>,
TError,
{ data: BodyType<RuletypesGettablePlannedMaintenanceDTO> },
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof createDowntimeSchedule>>,
TError,
{ data: BodyType<RuletypesGettablePlannedMaintenanceDTO> },
TContext
> => {
const mutationOptions = getCreateDowntimeScheduleMutationOptions(options);
return useMutation(mutationOptions);
};
/**
* This endpoint deletes a downtime schedule by ID
* @summary Delete downtime schedule
*/
export const deleteDowntimeScheduleByID = ({
id,
}: DeleteDowntimeScheduleByIDPathParameters) => {
return GeneratedAPIInstance<void>({
url: `/api/v1/downtime_schedules/${id}`,
method: 'DELETE',
});
};
export const getDeleteDowntimeScheduleByIDMutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof deleteDowntimeScheduleByID>>,
TError,
{ pathParams: DeleteDowntimeScheduleByIDPathParameters },
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof deleteDowntimeScheduleByID>>,
TError,
{ pathParams: DeleteDowntimeScheduleByIDPathParameters },
TContext
> => {
const mutationKey = ['deleteDowntimeScheduleByID'];
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 deleteDowntimeScheduleByID>>,
{ pathParams: DeleteDowntimeScheduleByIDPathParameters }
> = (props) => {
const { pathParams } = props ?? {};
return deleteDowntimeScheduleByID(pathParams);
};
return { mutationFn, ...mutationOptions };
};
export type DeleteDowntimeScheduleByIDMutationResult = NonNullable<
Awaited<ReturnType<typeof deleteDowntimeScheduleByID>>
>;
export type DeleteDowntimeScheduleByIDMutationError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary Delete downtime schedule
*/
export const useDeleteDowntimeScheduleByID = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof deleteDowntimeScheduleByID>>,
TError,
{ pathParams: DeleteDowntimeScheduleByIDPathParameters },
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof deleteDowntimeScheduleByID>>,
TError,
{ pathParams: DeleteDowntimeScheduleByIDPathParameters },
TContext
> => {
const mutationOptions = getDeleteDowntimeScheduleByIDMutationOptions(options);
return useMutation(mutationOptions);
};
/**
* This endpoint returns a downtime schedule by ID
* @summary Get downtime schedule by ID
*/
export const getDowntimeScheduleByID = (
{ id }: GetDowntimeScheduleByIDPathParameters,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<GetDowntimeScheduleByID200>({
url: `/api/v1/downtime_schedules/${id}`,
method: 'GET',
signal,
});
};
export const getGetDowntimeScheduleByIDQueryKey = ({
id,
}: GetDowntimeScheduleByIDPathParameters) => {
return [`/api/v1/downtime_schedules/${id}`] as const;
};
export const getGetDowntimeScheduleByIDQueryOptions = <
TData = Awaited<ReturnType<typeof getDowntimeScheduleByID>>,
TError = ErrorType<RenderErrorResponseDTO>
>(
{ id }: GetDowntimeScheduleByIDPathParameters,
options?: {
query?: UseQueryOptions<
Awaited<ReturnType<typeof getDowntimeScheduleByID>>,
TError,
TData
>;
},
) => {
const { query: queryOptions } = options ?? {};
const queryKey =
queryOptions?.queryKey ?? getGetDowntimeScheduleByIDQueryKey({ id });
const queryFn: QueryFunction<
Awaited<ReturnType<typeof getDowntimeScheduleByID>>
> = ({ signal }) => getDowntimeScheduleByID({ id }, signal);
return {
queryKey,
queryFn,
enabled: !!id,
...queryOptions,
} as UseQueryOptions<
Awaited<ReturnType<typeof getDowntimeScheduleByID>>,
TError,
TData
> & { queryKey: QueryKey };
};
export type GetDowntimeScheduleByIDQueryResult = NonNullable<
Awaited<ReturnType<typeof getDowntimeScheduleByID>>
>;
export type GetDowntimeScheduleByIDQueryError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary Get downtime schedule by ID
*/
export function useGetDowntimeScheduleByID<
TData = Awaited<ReturnType<typeof getDowntimeScheduleByID>>,
TError = ErrorType<RenderErrorResponseDTO>
>(
{ id }: GetDowntimeScheduleByIDPathParameters,
options?: {
query?: UseQueryOptions<
Awaited<ReturnType<typeof getDowntimeScheduleByID>>,
TError,
TData
>;
},
): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
const queryOptions = getGetDowntimeScheduleByIDQueryOptions({ id }, options);
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
queryKey: QueryKey;
};
query.queryKey = queryOptions.queryKey;
return query;
}
/**
* @summary Get downtime schedule by ID
*/
export const invalidateGetDowntimeScheduleByID = async (
queryClient: QueryClient,
{ id }: GetDowntimeScheduleByIDPathParameters,
options?: InvalidateOptions,
): Promise<QueryClient> => {
await queryClient.invalidateQueries(
{ queryKey: getGetDowntimeScheduleByIDQueryKey({ id }) },
options,
);
return queryClient;
};
/**
* This endpoint updates a downtime schedule by ID
* @summary Update downtime schedule
*/
export const updateDowntimeScheduleByID = (
{ id }: UpdateDowntimeScheduleByIDPathParameters,
ruletypesGettablePlannedMaintenanceDTO: BodyType<RuletypesGettablePlannedMaintenanceDTO>,
) => {
return GeneratedAPIInstance<void>({
url: `/api/v1/downtime_schedules/${id}`,
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
data: ruletypesGettablePlannedMaintenanceDTO,
});
};
export const getUpdateDowntimeScheduleByIDMutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof updateDowntimeScheduleByID>>,
TError,
{
pathParams: UpdateDowntimeScheduleByIDPathParameters;
data: BodyType<RuletypesGettablePlannedMaintenanceDTO>;
},
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof updateDowntimeScheduleByID>>,
TError,
{
pathParams: UpdateDowntimeScheduleByIDPathParameters;
data: BodyType<RuletypesGettablePlannedMaintenanceDTO>;
},
TContext
> => {
const mutationKey = ['updateDowntimeScheduleByID'];
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 updateDowntimeScheduleByID>>,
{
pathParams: UpdateDowntimeScheduleByIDPathParameters;
data: BodyType<RuletypesGettablePlannedMaintenanceDTO>;
}
> = (props) => {
const { pathParams, data } = props ?? {};
return updateDowntimeScheduleByID(pathParams, data);
};
return { mutationFn, ...mutationOptions };
};
export type UpdateDowntimeScheduleByIDMutationResult = NonNullable<
Awaited<ReturnType<typeof updateDowntimeScheduleByID>>
>;
export type UpdateDowntimeScheduleByIDMutationBody = BodyType<RuletypesGettablePlannedMaintenanceDTO>;
export type UpdateDowntimeScheduleByIDMutationError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary Update downtime schedule
*/
export const useUpdateDowntimeScheduleByID = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof updateDowntimeScheduleByID>>,
TError,
{
pathParams: UpdateDowntimeScheduleByIDPathParameters;
data: BodyType<RuletypesGettablePlannedMaintenanceDTO>;
},
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof updateDowntimeScheduleByID>>,
TError,
{
pathParams: UpdateDowntimeScheduleByIDPathParameters;
data: BodyType<RuletypesGettablePlannedMaintenanceDTO>;
},
TContext
> => {
const mutationOptions = getUpdateDowntimeScheduleByIDMutationOptions(options);
return useMutation(mutationOptions);
};

View File

@@ -6,17 +6,24 @@
*/
import type {
InvalidateOptions,
MutationFunction,
QueryClient,
QueryFunction,
QueryKey,
UseMutationOptions,
UseMutationResult,
UseQueryOptions,
UseQueryResult,
} from 'react-query';
import { useQuery } from 'react-query';
import { useMutation, useQuery } from 'react-query';
import type { ErrorType } from '../../../generatedAPIInstance';
import type { BodyType, ErrorType } from '../../../generatedAPIInstance';
import { GeneratedAPIInstance } from '../../../generatedAPIInstance';
import type {
CreateRule200,
DeleteRuleByIDPathParameters,
GetRuleByID200,
GetRuleByIDPathParameters,
GetRuleHistoryFilterKeys200,
GetRuleHistoryFilterKeysParams,
GetRuleHistoryFilterKeysPathParameters,
@@ -35,9 +42,717 @@ import type {
GetRuleHistoryTopContributors200,
GetRuleHistoryTopContributorsParams,
GetRuleHistoryTopContributorsPathParameters,
ListRules200,
PatchRuleByID200,
PatchRuleByIDPathParameters,
RenderErrorResponseDTO,
RuletypesPostableRuleDTO,
TestRule200,
TestRuleDeprecated200,
UpdateRuleByIDPathParameters,
} from '../sigNoz.schemas';
/**
* This endpoint lists all alert rules with their current evaluation state
* @summary List alert rules
*/
export const listRules = (signal?: AbortSignal) => {
return GeneratedAPIInstance<ListRules200>({
url: `/api/v1/rules`,
method: 'GET',
signal,
});
};
export const getListRulesQueryKey = () => {
return [`/api/v1/rules`] as const;
};
export const getListRulesQueryOptions = <
TData = Awaited<ReturnType<typeof listRules>>,
TError = ErrorType<RenderErrorResponseDTO>
>(options?: {
query?: UseQueryOptions<Awaited<ReturnType<typeof listRules>>, TError, TData>;
}) => {
const { query: queryOptions } = options ?? {};
const queryKey = queryOptions?.queryKey ?? getListRulesQueryKey();
const queryFn: QueryFunction<Awaited<ReturnType<typeof listRules>>> = ({
signal,
}) => listRules(signal);
return { queryKey, queryFn, ...queryOptions } as UseQueryOptions<
Awaited<ReturnType<typeof listRules>>,
TError,
TData
> & { queryKey: QueryKey };
};
export type ListRulesQueryResult = NonNullable<
Awaited<ReturnType<typeof listRules>>
>;
export type ListRulesQueryError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary List alert rules
*/
export function useListRules<
TData = Awaited<ReturnType<typeof listRules>>,
TError = ErrorType<RenderErrorResponseDTO>
>(options?: {
query?: UseQueryOptions<Awaited<ReturnType<typeof listRules>>, TError, TData>;
}): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
const queryOptions = getListRulesQueryOptions(options);
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
queryKey: QueryKey;
};
query.queryKey = queryOptions.queryKey;
return query;
}
/**
* @summary List alert rules
*/
export const invalidateListRules = async (
queryClient: QueryClient,
options?: InvalidateOptions,
): Promise<QueryClient> => {
await queryClient.invalidateQueries(
{ queryKey: getListRulesQueryKey() },
options,
);
return queryClient;
};
/**
* This endpoint creates a new alert rule
* @summary Create alert rule
*/
export const createRule = (
ruletypesPostableRuleDTO: BodyType<RuletypesPostableRuleDTO>,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<CreateRule200>({
url: `/api/v1/rules`,
method: 'POST',
headers: { 'Content-Type': 'application/json' },
data: ruletypesPostableRuleDTO,
signal,
});
};
export const getCreateRuleMutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof createRule>>,
TError,
{ data: BodyType<RuletypesPostableRuleDTO> },
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof createRule>>,
TError,
{ data: BodyType<RuletypesPostableRuleDTO> },
TContext
> => {
const mutationKey = ['createRule'];
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 createRule>>,
{ data: BodyType<RuletypesPostableRuleDTO> }
> = (props) => {
const { data } = props ?? {};
return createRule(data);
};
return { mutationFn, ...mutationOptions };
};
export type CreateRuleMutationResult = NonNullable<
Awaited<ReturnType<typeof createRule>>
>;
export type CreateRuleMutationBody = BodyType<RuletypesPostableRuleDTO>;
export type CreateRuleMutationError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary Create alert rule
*/
export const useCreateRule = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof createRule>>,
TError,
{ data: BodyType<RuletypesPostableRuleDTO> },
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof createRule>>,
TError,
{ data: BodyType<RuletypesPostableRuleDTO> },
TContext
> => {
const mutationOptions = getCreateRuleMutationOptions(options);
return useMutation(mutationOptions);
};
/**
* This endpoint deletes an alert rule by ID
* @summary Delete alert rule
*/
export const deleteRuleByID = ({ id }: DeleteRuleByIDPathParameters) => {
return GeneratedAPIInstance<void>({
url: `/api/v1/rules/${id}`,
method: 'DELETE',
});
};
export const getDeleteRuleByIDMutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof deleteRuleByID>>,
TError,
{ pathParams: DeleteRuleByIDPathParameters },
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof deleteRuleByID>>,
TError,
{ pathParams: DeleteRuleByIDPathParameters },
TContext
> => {
const mutationKey = ['deleteRuleByID'];
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 deleteRuleByID>>,
{ pathParams: DeleteRuleByIDPathParameters }
> = (props) => {
const { pathParams } = props ?? {};
return deleteRuleByID(pathParams);
};
return { mutationFn, ...mutationOptions };
};
export type DeleteRuleByIDMutationResult = NonNullable<
Awaited<ReturnType<typeof deleteRuleByID>>
>;
export type DeleteRuleByIDMutationError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary Delete alert rule
*/
export const useDeleteRuleByID = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof deleteRuleByID>>,
TError,
{ pathParams: DeleteRuleByIDPathParameters },
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof deleteRuleByID>>,
TError,
{ pathParams: DeleteRuleByIDPathParameters },
TContext
> => {
const mutationOptions = getDeleteRuleByIDMutationOptions(options);
return useMutation(mutationOptions);
};
/**
* This endpoint returns an alert rule by ID
* @summary Get alert rule by ID
*/
export const getRuleByID = (
{ id }: GetRuleByIDPathParameters,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<GetRuleByID200>({
url: `/api/v1/rules/${id}`,
method: 'GET',
signal,
});
};
export const getGetRuleByIDQueryKey = ({ id }: GetRuleByIDPathParameters) => {
return [`/api/v1/rules/${id}`] as const;
};
export const getGetRuleByIDQueryOptions = <
TData = Awaited<ReturnType<typeof getRuleByID>>,
TError = ErrorType<RenderErrorResponseDTO>
>(
{ id }: GetRuleByIDPathParameters,
options?: {
query?: UseQueryOptions<
Awaited<ReturnType<typeof getRuleByID>>,
TError,
TData
>;
},
) => {
const { query: queryOptions } = options ?? {};
const queryKey = queryOptions?.queryKey ?? getGetRuleByIDQueryKey({ id });
const queryFn: QueryFunction<Awaited<ReturnType<typeof getRuleByID>>> = ({
signal,
}) => getRuleByID({ id }, signal);
return {
queryKey,
queryFn,
enabled: !!id,
...queryOptions,
} as UseQueryOptions<
Awaited<ReturnType<typeof getRuleByID>>,
TError,
TData
> & { queryKey: QueryKey };
};
export type GetRuleByIDQueryResult = NonNullable<
Awaited<ReturnType<typeof getRuleByID>>
>;
export type GetRuleByIDQueryError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary Get alert rule by ID
*/
export function useGetRuleByID<
TData = Awaited<ReturnType<typeof getRuleByID>>,
TError = ErrorType<RenderErrorResponseDTO>
>(
{ id }: GetRuleByIDPathParameters,
options?: {
query?: UseQueryOptions<
Awaited<ReturnType<typeof getRuleByID>>,
TError,
TData
>;
},
): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
const queryOptions = getGetRuleByIDQueryOptions({ id }, options);
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
queryKey: QueryKey;
};
query.queryKey = queryOptions.queryKey;
return query;
}
/**
* @summary Get alert rule by ID
*/
export const invalidateGetRuleByID = async (
queryClient: QueryClient,
{ id }: GetRuleByIDPathParameters,
options?: InvalidateOptions,
): Promise<QueryClient> => {
await queryClient.invalidateQueries(
{ queryKey: getGetRuleByIDQueryKey({ id }) },
options,
);
return queryClient;
};
/**
* This endpoint applies a partial update to an alert rule by ID
* @summary Patch alert rule
*/
export const patchRuleByID = (
{ id }: PatchRuleByIDPathParameters,
ruletypesPostableRuleDTO: BodyType<RuletypesPostableRuleDTO>,
) => {
return GeneratedAPIInstance<PatchRuleByID200>({
url: `/api/v1/rules/${id}`,
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
data: ruletypesPostableRuleDTO,
});
};
export const getPatchRuleByIDMutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof patchRuleByID>>,
TError,
{
pathParams: PatchRuleByIDPathParameters;
data: BodyType<RuletypesPostableRuleDTO>;
},
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof patchRuleByID>>,
TError,
{
pathParams: PatchRuleByIDPathParameters;
data: BodyType<RuletypesPostableRuleDTO>;
},
TContext
> => {
const mutationKey = ['patchRuleByID'];
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 patchRuleByID>>,
{
pathParams: PatchRuleByIDPathParameters;
data: BodyType<RuletypesPostableRuleDTO>;
}
> = (props) => {
const { pathParams, data } = props ?? {};
return patchRuleByID(pathParams, data);
};
return { mutationFn, ...mutationOptions };
};
export type PatchRuleByIDMutationResult = NonNullable<
Awaited<ReturnType<typeof patchRuleByID>>
>;
export type PatchRuleByIDMutationBody = BodyType<RuletypesPostableRuleDTO>;
export type PatchRuleByIDMutationError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary Patch alert rule
*/
export const usePatchRuleByID = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof patchRuleByID>>,
TError,
{
pathParams: PatchRuleByIDPathParameters;
data: BodyType<RuletypesPostableRuleDTO>;
},
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof patchRuleByID>>,
TError,
{
pathParams: PatchRuleByIDPathParameters;
data: BodyType<RuletypesPostableRuleDTO>;
},
TContext
> => {
const mutationOptions = getPatchRuleByIDMutationOptions(options);
return useMutation(mutationOptions);
};
/**
* This endpoint updates an alert rule by ID
* @summary Update alert rule
*/
export const updateRuleByID = (
{ id }: UpdateRuleByIDPathParameters,
ruletypesPostableRuleDTO: BodyType<RuletypesPostableRuleDTO>,
) => {
return GeneratedAPIInstance<void>({
url: `/api/v1/rules/${id}`,
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
data: ruletypesPostableRuleDTO,
});
};
export const getUpdateRuleByIDMutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof updateRuleByID>>,
TError,
{
pathParams: UpdateRuleByIDPathParameters;
data: BodyType<RuletypesPostableRuleDTO>;
},
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof updateRuleByID>>,
TError,
{
pathParams: UpdateRuleByIDPathParameters;
data: BodyType<RuletypesPostableRuleDTO>;
},
TContext
> => {
const mutationKey = ['updateRuleByID'];
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 updateRuleByID>>,
{
pathParams: UpdateRuleByIDPathParameters;
data: BodyType<RuletypesPostableRuleDTO>;
}
> = (props) => {
const { pathParams, data } = props ?? {};
return updateRuleByID(pathParams, data);
};
return { mutationFn, ...mutationOptions };
};
export type UpdateRuleByIDMutationResult = NonNullable<
Awaited<ReturnType<typeof updateRuleByID>>
>;
export type UpdateRuleByIDMutationBody = BodyType<RuletypesPostableRuleDTO>;
export type UpdateRuleByIDMutationError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary Update alert rule
*/
export const useUpdateRuleByID = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof updateRuleByID>>,
TError,
{
pathParams: UpdateRuleByIDPathParameters;
data: BodyType<RuletypesPostableRuleDTO>;
},
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof updateRuleByID>>,
TError,
{
pathParams: UpdateRuleByIDPathParameters;
data: BodyType<RuletypesPostableRuleDTO>;
},
TContext
> => {
const mutationOptions = getUpdateRuleByIDMutationOptions(options);
return useMutation(mutationOptions);
};
/**
* This endpoint fires a test notification for the given rule definition
* @summary Test alert rule
*/
export const testRule = (
ruletypesPostableRuleDTO: BodyType<RuletypesPostableRuleDTO>,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<TestRule200>({
url: `/api/v1/rules/test`,
method: 'POST',
headers: { 'Content-Type': 'application/json' },
data: ruletypesPostableRuleDTO,
signal,
});
};
export const getTestRuleMutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof testRule>>,
TError,
{ data: BodyType<RuletypesPostableRuleDTO> },
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof testRule>>,
TError,
{ data: BodyType<RuletypesPostableRuleDTO> },
TContext
> => {
const mutationKey = ['testRule'];
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 testRule>>,
{ data: BodyType<RuletypesPostableRuleDTO> }
> = (props) => {
const { data } = props ?? {};
return testRule(data);
};
return { mutationFn, ...mutationOptions };
};
export type TestRuleMutationResult = NonNullable<
Awaited<ReturnType<typeof testRule>>
>;
export type TestRuleMutationBody = BodyType<RuletypesPostableRuleDTO>;
export type TestRuleMutationError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary Test alert rule
*/
export const useTestRule = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof testRule>>,
TError,
{ data: BodyType<RuletypesPostableRuleDTO> },
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof testRule>>,
TError,
{ data: BodyType<RuletypesPostableRuleDTO> },
TContext
> => {
const mutationOptions = getTestRuleMutationOptions(options);
return useMutation(mutationOptions);
};
/**
* Deprecated: use /api/v1/rules/test instead
* @deprecated
* @summary Test alert rule (deprecated)
*/
export const testRuleDeprecated = (
ruletypesPostableRuleDTO: BodyType<RuletypesPostableRuleDTO>,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<TestRuleDeprecated200>({
url: `/api/v1/testRule`,
method: 'POST',
headers: { 'Content-Type': 'application/json' },
data: ruletypesPostableRuleDTO,
signal,
});
};
export const getTestRuleDeprecatedMutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof testRuleDeprecated>>,
TError,
{ data: BodyType<RuletypesPostableRuleDTO> },
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof testRuleDeprecated>>,
TError,
{ data: BodyType<RuletypesPostableRuleDTO> },
TContext
> => {
const mutationKey = ['testRuleDeprecated'];
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 testRuleDeprecated>>,
{ data: BodyType<RuletypesPostableRuleDTO> }
> = (props) => {
const { data } = props ?? {};
return testRuleDeprecated(data);
};
return { mutationFn, ...mutationOptions };
};
export type TestRuleDeprecatedMutationResult = NonNullable<
Awaited<ReturnType<typeof testRuleDeprecated>>
>;
export type TestRuleDeprecatedMutationBody = BodyType<RuletypesPostableRuleDTO>;
export type TestRuleDeprecatedMutationError = ErrorType<RenderErrorResponseDTO>;
/**
* @deprecated
* @summary Test alert rule (deprecated)
*/
export const useTestRuleDeprecated = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof testRuleDeprecated>>,
TError,
{ data: BodyType<RuletypesPostableRuleDTO> },
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof testRuleDeprecated>>,
TError,
{ data: BodyType<RuletypesPostableRuleDTO> },
TContext
> => {
const mutationOptions = getTestRuleDeprecatedMutationOptions(options);
return useMutation(mutationOptions);
};
/**
* Returns distinct label keys from rule history entries for the selected range.
* @summary Get rule history filter keys

View File

@@ -4529,6 +4529,26 @@ export interface RulestatehistorytypesGettableRuleStateWindowDTO {
state: RuletypesAlertStateDTO;
}
export interface RuletypesAlertCompositeQueryDTO {
/**
* @type string
*/
panelType?: string;
/**
* @type array
* @nullable true
*/
queries?: Querybuildertypesv5QueryEnvelopeDTO[] | null;
/**
* @type string
*/
queryType?: string;
/**
* @type string
*/
unit?: string;
}
export enum RuletypesAlertStateDTO {
inactive = 'inactive',
pending = 'pending',
@@ -4537,6 +4557,495 @@ export enum RuletypesAlertStateDTO {
nodata = 'nodata',
disabled = 'disabled',
}
export interface RuletypesBasicRuleThresholdDTO {
/**
* @type array
* @nullable true
*/
channels?: string[] | null;
matchType?: RuletypesMatchTypeDTO;
/**
* @type string
*/
name?: string;
op?: RuletypesCompareOperatorDTO;
/**
* @type number
* @nullable true
*/
recoveryTarget?: number | null;
/**
* @type number
* @nullable true
*/
target?: number | null;
/**
* @type string
*/
targetUnit?: string;
}
/**
* @nullable
*/
export type RuletypesBasicRuleThresholdsDTO =
| RuletypesBasicRuleThresholdDTO[]
| null;
export enum RuletypesCompareOperatorDTO {
above = 'above',
below = 'below',
equal = 'equal',
not_equal = 'not_equal',
outside_bounds = 'outside_bounds',
}
export interface RuletypesCumulativeScheduleDTO {
/**
* @type integer
* @nullable true
*/
day?: number | null;
/**
* @type integer
* @nullable true
*/
hour?: number | null;
/**
* @type integer
* @nullable true
*/
minute?: number | null;
/**
* @type string
*/
type?: string;
/**
* @type integer
* @nullable true
*/
weekday?: number | null;
}
export interface RuletypesCumulativeWindowDTO {
/**
* @type string
*/
frequency?: string;
schedule?: RuletypesCumulativeScheduleDTO;
/**
* @type string
*/
timezone?: string;
}
export interface RuletypesEvaluationCumulativeDTO {
/**
* @type string
* @description The kind of evaluation.
*/
kind?: string;
spec?: RuletypesCumulativeWindowDTO;
}
export type RuletypesEvaluationEnvelopeDTO =
| (RuletypesEvaluationRollingDTO & {
/**
* @type string
*/
kind?: string;
spec?: unknown;
})
| (RuletypesEvaluationCumulativeDTO & {
/**
* @type string
*/
kind?: string;
spec?: unknown;
});
export interface RuletypesEvaluationRollingDTO {
/**
* @type string
* @description The kind of evaluation.
*/
kind?: string;
spec?: RuletypesRollingWindowDTO;
}
export interface RuletypesGettablePlannedMaintenanceDTO {
/**
* @type array
* @nullable true
*/
alertIds?: string[] | null;
/**
* @type string
* @format date-time
*/
createdAt?: Date;
/**
* @type string
*/
createdBy?: string;
/**
* @type string
*/
description?: string;
/**
* @type string
*/
id?: string;
/**
* @type string
*/
kind?: string;
/**
* @type string
*/
name?: string;
schedule?: RuletypesScheduleDTO;
/**
* @type string
*/
status?: string;
/**
* @type string
* @format date-time
*/
updatedAt?: Date;
/**
* @type string
*/
updatedBy?: string;
}
export type RuletypesGettableRuleDTOAnnotations = { [key: string]: string };
export type RuletypesGettableRuleDTOLabels = { [key: string]: string };
export interface RuletypesGettableRuleDTO {
/**
* @type string
*/
alert: string;
/**
* @type string
*/
alertType?: string;
/**
* @type object
*/
annotations?: RuletypesGettableRuleDTOAnnotations;
condition: RuletypesRuleConditionDTO;
/**
* @type string
* @format date-time
* @nullable true
*/
createAt?: Date | null;
/**
* @type string
* @nullable true
*/
createBy?: string | null;
/**
* @type string
*/
description?: string;
/**
* @type boolean
*/
disabled?: boolean;
/**
* @type string
*/
evalWindow?: string;
evaluation?: RuletypesEvaluationEnvelopeDTO;
/**
* @type string
*/
frequency?: string;
/**
* @type string
*/
id?: string;
/**
* @type object
*/
labels?: RuletypesGettableRuleDTOLabels;
notificationSettings?: RuletypesNotificationSettingsDTO;
/**
* @type array
*/
preferredChannels?: string[];
ruleType: RuletypesRuleTypeDTO;
/**
* @type string
*/
schemaVersion?: string;
/**
* @type string
*/
source?: string;
state?: RuletypesAlertStateDTO;
/**
* @type string
* @format date-time
* @nullable true
*/
updateAt?: Date | null;
/**
* @type string
* @nullable true
*/
updateBy?: string | null;
/**
* @type string
*/
version?: string;
}
export interface RuletypesGettableRulesDTO {
/**
* @type array
* @nullable true
*/
rules?: RuletypesGettableRuleDTO[] | null;
}
export interface RuletypesGettableTestRuleDTO {
/**
* @type integer
*/
alertCount?: number;
/**
* @type string
*/
message?: string;
}
export enum RuletypesMatchTypeDTO {
at_least_once = 'at_least_once',
all_the_times = 'all_the_times',
on_average = 'on_average',
in_total = 'in_total',
last = 'last',
}
export interface RuletypesNotificationSettingsDTO {
/**
* @type array
*/
groupBy?: string[];
/**
* @type string
*/
newGroupEvalDelay?: string;
renotify?: RuletypesRenotifyDTO;
/**
* @type boolean
*/
usePolicy?: boolean;
}
export type RuletypesPostableRuleDTOAnnotations = { [key: string]: string };
export type RuletypesPostableRuleDTOLabels = { [key: string]: string };
export interface RuletypesPostableRuleDTO {
/**
* @type string
*/
alert: string;
/**
* @type string
*/
alertType?: string;
/**
* @type object
*/
annotations?: RuletypesPostableRuleDTOAnnotations;
condition: RuletypesRuleConditionDTO;
/**
* @type string
*/
description?: string;
/**
* @type boolean
*/
disabled?: boolean;
/**
* @type string
*/
evalWindow?: string;
evaluation?: RuletypesEvaluationEnvelopeDTO;
/**
* @type string
*/
frequency?: string;
/**
* @type object
*/
labels?: RuletypesPostableRuleDTOLabels;
notificationSettings?: RuletypesNotificationSettingsDTO;
/**
* @type array
*/
preferredChannels?: string[];
ruleType: RuletypesRuleTypeDTO;
/**
* @type string
*/
schemaVersion?: string;
/**
* @type string
*/
source?: string;
/**
* @type string
*/
version?: string;
}
export interface RuletypesRecurrenceDTO {
/**
* @type string
*/
duration?: string;
/**
* @type string
* @format date-time
* @nullable true
*/
endTime?: Date | null;
/**
* @type array
* @nullable true
*/
repeatOn?: string[] | null;
/**
* @type string
*/
repeatType?: string;
/**
* @type string
* @format date-time
*/
startTime?: Date;
}
export interface RuletypesRenotifyDTO {
/**
* @type array
*/
alertStates?: RuletypesAlertStateDTO[];
/**
* @type boolean
*/
enabled?: boolean;
/**
* @type string
*/
interval?: string;
}
export interface RuletypesRollingWindowDTO {
/**
* @type string
*/
evalWindow?: string;
/**
* @type string
*/
frequency?: string;
}
export interface RuletypesRuleConditionDTO {
/**
* @type integer
* @minimum 0
*/
absentFor?: number;
/**
* @type boolean
*/
alertOnAbsent?: boolean;
/**
* @type string
*/
algorithm?: string;
compositeQuery?: RuletypesAlertCompositeQueryDTO;
matchType?: RuletypesMatchTypeDTO;
op?: RuletypesCompareOperatorDTO;
/**
* @type boolean
*/
requireMinPoints?: boolean;
/**
* @type integer
*/
requiredNumPoints?: number;
seasonality?: RuletypesSeasonalityDTO;
/**
* @type string
*/
selectedQueryName?: string;
/**
* @type number
* @nullable true
*/
target?: number | null;
/**
* @type string
*/
targetUnit?: string;
thresholds?: RuletypesRuleThresholdDataDTO;
}
export type RuletypesRuleThresholdDataDTO = RuletypesThresholdBasicDTO & {
/**
* @type string
*/
kind?: string;
spec?: unknown;
};
export enum RuletypesRuleTypeDTO {
threshold_rule = 'threshold_rule',
promql_rule = 'promql_rule',
anomaly_rule = 'anomaly_rule',
}
export interface RuletypesScheduleDTO {
/**
* @type string
* @format date-time
*/
endTime?: Date;
recurrence?: RuletypesRecurrenceDTO;
/**
* @type string
* @format date-time
*/
startTime?: Date;
/**
* @type string
*/
timezone?: string;
}
export enum RuletypesSeasonalityDTO {
hourly = 'hourly',
daily = 'daily',
weekly = 'weekly',
}
export interface RuletypesThresholdBasicDTO {
/**
* @type string
* @description The kind of threshold.
*/
kind?: string;
spec?: RuletypesBasicRuleThresholdsDTO;
}
export interface ServiceaccounttypesGettableFactorAPIKeyDTO {
/**
* @type string
@@ -4837,6 +5346,10 @@ export interface TypesChangePasswordRequestDTO {
* @type string
*/
oldPassword?: string;
/**
* @type string
*/
userId?: string;
}
export interface TypesDeprecatedUserDTO {
@@ -5200,6 +5713,9 @@ export type AuthzResources200 = {
status: string;
};
export type ChangePasswordPathParameters = {
id: string;
};
export type ListChannels200 = {
/**
* @type array
@@ -5456,6 +5972,49 @@ export type DeleteAuthDomainPathParameters = {
export type UpdateAuthDomainPathParameters = {
id: string;
};
export type ListDowntimeSchedulesParams = {
/**
* @type boolean
* @nullable true
* @description undefined
*/
active?: boolean | null;
/**
* @type boolean
* @nullable true
* @description undefined
*/
recurring?: boolean | null;
};
export type ListDowntimeSchedules200 = {
/**
* @type array
*/
data: RuletypesGettablePlannedMaintenanceDTO[];
/**
* @type string
*/
status: string;
};
export type DeleteDowntimeScheduleByIDPathParameters = {
id: string;
};
export type GetDowntimeScheduleByIDPathParameters = {
id: string;
};
export type GetDowntimeScheduleByID200 = {
data: RuletypesGettablePlannedMaintenanceDTO;
/**
* @type string
*/
status: string;
};
export type UpdateDowntimeScheduleByIDPathParameters = {
id: string;
};
export type HandleExportRawDataPOSTParams = {
/**
* @enum csv,jsonl
@@ -5597,10 +6156,10 @@ export type GetFieldsValues200 = {
status: string;
};
export type GetResetPasswordTokenDeprecatedPathParameters = {
export type GetResetPasswordTokenPathParameters = {
id: string;
};
export type GetResetPasswordTokenDeprecated200 = {
export type GetResetPasswordToken200 = {
data: TypesResetPasswordTokenDTO;
/**
* @type string
@@ -5783,6 +6342,58 @@ export type UpdateRoutePolicy200 = {
status: string;
};
export type ListRules200 = {
data: RuletypesGettableRulesDTO;
/**
* @type string
*/
status: string;
};
export type CreateRule200 = {
data: RuletypesGettableRuleDTO;
/**
* @type string
*/
status: string;
};
export type DeleteRuleByIDPathParameters = {
id: string;
};
export type GetRuleByIDPathParameters = {
id: string;
};
export type GetRuleByID200 = {
data: RuletypesGettableRuleDTO;
/**
* @type string
*/
status: string;
};
export type PatchRuleByIDPathParameters = {
id: string;
};
export type PatchRuleByID200 = {
data: RuletypesGettableRuleDTO;
/**
* @type string
*/
status: string;
};
export type UpdateRuleByIDPathParameters = {
id: string;
};
export type TestRule200 = {
data: RuletypesGettableTestRuleDTO;
/**
* @type string
*/
status: string;
};
export type ListServiceAccounts200 = {
/**
* @type array
@@ -5890,6 +6501,14 @@ export type GetMyServiceAccount200 = {
status: string;
};
export type TestRuleDeprecated200 = {
data: RuletypesGettableTestRuleDTO;
/**
* @type string
*/
status: string;
};
export type ListUsersDeprecated200 = {
/**
* @type array
@@ -6572,28 +7191,6 @@ export type GetUser200 = {
export type UpdateUserPathParameters = {
id: string;
};
export type GetResetPasswordTokenPathParameters = {
id: string;
};
export type GetResetPasswordToken200 = {
data: TypesResetPasswordTokenDTO;
/**
* @type string
*/
status: string;
};
export type CreateResetPasswordTokenPathParameters = {
id: string;
};
export type CreateResetPasswordToken201 = {
data: TypesResetPasswordTokenDTO;
/**
* @type string
*/
status: string;
};
export type GetRolesByUserIDPathParameters = {
id: string;
};

View File

@@ -20,15 +20,12 @@ import { useMutation, useQuery } from 'react-query';
import type { BodyType, ErrorType } from '../../../generatedAPIInstance';
import { GeneratedAPIInstance } from '../../../generatedAPIInstance';
import type {
ChangePasswordPathParameters,
CreateInvite201,
CreateResetPasswordToken201,
CreateResetPasswordTokenPathParameters,
DeleteUserPathParameters,
GetMyUser200,
GetMyUserDeprecated200,
GetResetPasswordToken200,
GetResetPasswordTokenDeprecated200,
GetResetPasswordTokenDeprecatedPathParameters,
GetResetPasswordTokenPathParameters,
GetRolesByUserID200,
GetRolesByUserIDPathParameters,
@@ -57,35 +54,133 @@ import type {
} from '../sigNoz.schemas';
/**
* This endpoint returns the reset password token by id
* @deprecated
* @summary Get reset password token
* This endpoint changes the password by id
* @summary Change password
*/
export const getResetPasswordTokenDeprecated = (
{ id }: GetResetPasswordTokenDeprecatedPathParameters,
export const changePassword = (
{ id }: ChangePasswordPathParameters,
typesChangePasswordRequestDTO: BodyType<TypesChangePasswordRequestDTO>,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<GetResetPasswordTokenDeprecated200>({
return GeneratedAPIInstance<void>({
url: `/api/v1/changePassword/${id}`,
method: 'POST',
headers: { 'Content-Type': 'application/json' },
data: typesChangePasswordRequestDTO,
signal,
});
};
export const getChangePasswordMutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof changePassword>>,
TError,
{
pathParams: ChangePasswordPathParameters;
data: BodyType<TypesChangePasswordRequestDTO>;
},
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof changePassword>>,
TError,
{
pathParams: ChangePasswordPathParameters;
data: BodyType<TypesChangePasswordRequestDTO>;
},
TContext
> => {
const mutationKey = ['changePassword'];
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 changePassword>>,
{
pathParams: ChangePasswordPathParameters;
data: BodyType<TypesChangePasswordRequestDTO>;
}
> = (props) => {
const { pathParams, data } = props ?? {};
return changePassword(pathParams, data);
};
return { mutationFn, ...mutationOptions };
};
export type ChangePasswordMutationResult = NonNullable<
Awaited<ReturnType<typeof changePassword>>
>;
export type ChangePasswordMutationBody = BodyType<TypesChangePasswordRequestDTO>;
export type ChangePasswordMutationError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary Change password
*/
export const useChangePassword = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof changePassword>>,
TError,
{
pathParams: ChangePasswordPathParameters;
data: BodyType<TypesChangePasswordRequestDTO>;
},
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof changePassword>>,
TError,
{
pathParams: ChangePasswordPathParameters;
data: BodyType<TypesChangePasswordRequestDTO>;
},
TContext
> => {
const mutationOptions = getChangePasswordMutationOptions(options);
return useMutation(mutationOptions);
};
/**
* This endpoint returns the reset password token by id
* @summary Get reset password token
*/
export const getResetPasswordToken = (
{ id }: GetResetPasswordTokenPathParameters,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<GetResetPasswordToken200>({
url: `/api/v1/getResetPasswordToken/${id}`,
method: 'GET',
signal,
});
};
export const getGetResetPasswordTokenDeprecatedQueryKey = ({
export const getGetResetPasswordTokenQueryKey = ({
id,
}: GetResetPasswordTokenDeprecatedPathParameters) => {
}: GetResetPasswordTokenPathParameters) => {
return [`/api/v1/getResetPasswordToken/${id}`] as const;
};
export const getGetResetPasswordTokenDeprecatedQueryOptions = <
TData = Awaited<ReturnType<typeof getResetPasswordTokenDeprecated>>,
export const getGetResetPasswordTokenQueryOptions = <
TData = Awaited<ReturnType<typeof getResetPasswordToken>>,
TError = ErrorType<RenderErrorResponseDTO>
>(
{ id }: GetResetPasswordTokenDeprecatedPathParameters,
{ id }: GetResetPasswordTokenPathParameters,
options?: {
query?: UseQueryOptions<
Awaited<ReturnType<typeof getResetPasswordTokenDeprecated>>,
Awaited<ReturnType<typeof getResetPasswordToken>>,
TError,
TData
>;
@@ -94,11 +189,11 @@ export const getGetResetPasswordTokenDeprecatedQueryOptions = <
const { query: queryOptions } = options ?? {};
const queryKey =
queryOptions?.queryKey ?? getGetResetPasswordTokenDeprecatedQueryKey({ id });
queryOptions?.queryKey ?? getGetResetPasswordTokenQueryKey({ id });
const queryFn: QueryFunction<
Awaited<ReturnType<typeof getResetPasswordTokenDeprecated>>
> = ({ signal }) => getResetPasswordTokenDeprecated({ id }, signal);
Awaited<ReturnType<typeof getResetPasswordToken>>
> = ({ signal }) => getResetPasswordToken({ id }, signal);
return {
queryKey,
@@ -106,39 +201,35 @@ export const getGetResetPasswordTokenDeprecatedQueryOptions = <
enabled: !!id,
...queryOptions,
} as UseQueryOptions<
Awaited<ReturnType<typeof getResetPasswordTokenDeprecated>>,
Awaited<ReturnType<typeof getResetPasswordToken>>,
TError,
TData
> & { queryKey: QueryKey };
};
export type GetResetPasswordTokenDeprecatedQueryResult = NonNullable<
Awaited<ReturnType<typeof getResetPasswordTokenDeprecated>>
export type GetResetPasswordTokenQueryResult = NonNullable<
Awaited<ReturnType<typeof getResetPasswordToken>>
>;
export type GetResetPasswordTokenDeprecatedQueryError = ErrorType<RenderErrorResponseDTO>;
export type GetResetPasswordTokenQueryError = ErrorType<RenderErrorResponseDTO>;
/**
* @deprecated
* @summary Get reset password token
*/
export function useGetResetPasswordTokenDeprecated<
TData = Awaited<ReturnType<typeof getResetPasswordTokenDeprecated>>,
export function useGetResetPasswordToken<
TData = Awaited<ReturnType<typeof getResetPasswordToken>>,
TError = ErrorType<RenderErrorResponseDTO>
>(
{ id }: GetResetPasswordTokenDeprecatedPathParameters,
{ id }: GetResetPasswordTokenPathParameters,
options?: {
query?: UseQueryOptions<
Awaited<ReturnType<typeof getResetPasswordTokenDeprecated>>,
Awaited<ReturnType<typeof getResetPasswordToken>>,
TError,
TData
>;
},
): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
const queryOptions = getGetResetPasswordTokenDeprecatedQueryOptions(
{ id },
options,
);
const queryOptions = getGetResetPasswordTokenQueryOptions({ id }, options);
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
queryKey: QueryKey;
@@ -150,16 +241,15 @@ export function useGetResetPasswordTokenDeprecated<
}
/**
* @deprecated
* @summary Get reset password token
*/
export const invalidateGetResetPasswordTokenDeprecated = async (
export const invalidateGetResetPasswordToken = async (
queryClient: QueryClient,
{ id }: GetResetPasswordTokenDeprecatedPathParameters,
{ id }: GetResetPasswordTokenPathParameters,
options?: InvalidateOptions,
): Promise<QueryClient> => {
await queryClient.invalidateQueries(
{ queryKey: getGetResetPasswordTokenDeprecatedQueryKey({ id }) },
{ queryKey: getGetResetPasswordTokenQueryKey({ id }) },
options,
);
@@ -1317,189 +1407,6 @@ export const useUpdateUser = <
return useMutation(mutationOptions);
};
/**
* This endpoint returns the existing reset password token for a user.
* @summary Get reset password token for a user
*/
export const getResetPasswordToken = (
{ id }: GetResetPasswordTokenPathParameters,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<GetResetPasswordToken200>({
url: `/api/v2/users/${id}/reset_password_tokens`,
method: 'GET',
signal,
});
};
export const getGetResetPasswordTokenQueryKey = ({
id,
}: GetResetPasswordTokenPathParameters) => {
return [`/api/v2/users/${id}/reset_password_tokens`] as const;
};
export const getGetResetPasswordTokenQueryOptions = <
TData = Awaited<ReturnType<typeof getResetPasswordToken>>,
TError = ErrorType<RenderErrorResponseDTO>
>(
{ id }: GetResetPasswordTokenPathParameters,
options?: {
query?: UseQueryOptions<
Awaited<ReturnType<typeof getResetPasswordToken>>,
TError,
TData
>;
},
) => {
const { query: queryOptions } = options ?? {};
const queryKey =
queryOptions?.queryKey ?? getGetResetPasswordTokenQueryKey({ id });
const queryFn: QueryFunction<
Awaited<ReturnType<typeof getResetPasswordToken>>
> = ({ signal }) => getResetPasswordToken({ id }, signal);
return {
queryKey,
queryFn,
enabled: !!id,
...queryOptions,
} as UseQueryOptions<
Awaited<ReturnType<typeof getResetPasswordToken>>,
TError,
TData
> & { queryKey: QueryKey };
};
export type GetResetPasswordTokenQueryResult = NonNullable<
Awaited<ReturnType<typeof getResetPasswordToken>>
>;
export type GetResetPasswordTokenQueryError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary Get reset password token for a user
*/
export function useGetResetPasswordToken<
TData = Awaited<ReturnType<typeof getResetPasswordToken>>,
TError = ErrorType<RenderErrorResponseDTO>
>(
{ id }: GetResetPasswordTokenPathParameters,
options?: {
query?: UseQueryOptions<
Awaited<ReturnType<typeof getResetPasswordToken>>,
TError,
TData
>;
},
): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
const queryOptions = getGetResetPasswordTokenQueryOptions({ id }, options);
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
queryKey: QueryKey;
};
query.queryKey = queryOptions.queryKey;
return query;
}
/**
* @summary Get reset password token for a user
*/
export const invalidateGetResetPasswordToken = async (
queryClient: QueryClient,
{ id }: GetResetPasswordTokenPathParameters,
options?: InvalidateOptions,
): Promise<QueryClient> => {
await queryClient.invalidateQueries(
{ queryKey: getGetResetPasswordTokenQueryKey({ id }) },
options,
);
return queryClient;
};
/**
* This endpoint creates or regenerates a reset password token for a user. If a valid token exists, it is returned. If expired, a new one is created.
* @summary Create or regenerate reset password token for a user
*/
export const createResetPasswordToken = ({
id,
}: CreateResetPasswordTokenPathParameters) => {
return GeneratedAPIInstance<CreateResetPasswordToken201>({
url: `/api/v2/users/${id}/reset_password_tokens`,
method: 'PUT',
});
};
export const getCreateResetPasswordTokenMutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof createResetPasswordToken>>,
TError,
{ pathParams: CreateResetPasswordTokenPathParameters },
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof createResetPasswordToken>>,
TError,
{ pathParams: CreateResetPasswordTokenPathParameters },
TContext
> => {
const mutationKey = ['createResetPasswordToken'];
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 createResetPasswordToken>>,
{ pathParams: CreateResetPasswordTokenPathParameters }
> = (props) => {
const { pathParams } = props ?? {};
return createResetPasswordToken(pathParams);
};
return { mutationFn, ...mutationOptions };
};
export type CreateResetPasswordTokenMutationResult = NonNullable<
Awaited<ReturnType<typeof createResetPasswordToken>>
>;
export type CreateResetPasswordTokenMutationError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary Create or regenerate reset password token for a user
*/
export const useCreateResetPasswordToken = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof createResetPasswordToken>>,
TError,
{ pathParams: CreateResetPasswordTokenPathParameters },
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof createResetPasswordToken>>,
TError,
{ pathParams: CreateResetPasswordTokenPathParameters },
TContext
> => {
const mutationOptions = getCreateResetPasswordTokenMutationOptions(options);
return useMutation(mutationOptions);
};
/**
* This endpoint returns the user roles by user id
* @summary Get user roles
@@ -1943,84 +1850,3 @@ export const useUpdateMyUserV2 = <
return useMutation(mutationOptions);
};
/**
* This endpoint updates the password of the user I belong to
* @summary Updates my password
*/
export const updateMyPassword = (
typesChangePasswordRequestDTO: BodyType<TypesChangePasswordRequestDTO>,
) => {
return GeneratedAPIInstance<void>({
url: `/api/v2/users/me/factor_password`,
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
data: typesChangePasswordRequestDTO,
});
};
export const getUpdateMyPasswordMutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof updateMyPassword>>,
TError,
{ data: BodyType<TypesChangePasswordRequestDTO> },
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof updateMyPassword>>,
TError,
{ data: BodyType<TypesChangePasswordRequestDTO> },
TContext
> => {
const mutationKey = ['updateMyPassword'];
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 updateMyPassword>>,
{ data: BodyType<TypesChangePasswordRequestDTO> }
> = (props) => {
const { data } = props ?? {};
return updateMyPassword(data);
};
return { mutationFn, ...mutationOptions };
};
export type UpdateMyPasswordMutationResult = NonNullable<
Awaited<ReturnType<typeof updateMyPassword>>
>;
export type UpdateMyPasswordMutationBody = BodyType<TypesChangePasswordRequestDTO>;
export type UpdateMyPasswordMutationError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary Updates my password
*/
export const useUpdateMyPassword = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof updateMyPassword>>,
TError,
{ data: BodyType<TypesChangePasswordRequestDTO> },
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof updateMyPassword>>,
TError,
{ data: BodyType<TypesChangePasswordRequestDTO> },
TContext
> => {
const mutationOptions = getUpdateMyPasswordMutationOptions(options);
return useMutation(mutationOptions);
};

View File

@@ -1,45 +0,0 @@
import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { AxiosError } from 'axios';
import { Dayjs } from 'dayjs';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { Recurrence } from './getAllDowntimeSchedules';
export interface DowntimeSchedulePayload {
name: string;
description?: string;
alertIds: string[];
schedule: {
timezone?: string;
startTime?: string | Dayjs;
endTime?: string | Dayjs;
recurrence?: Recurrence;
};
}
export interface PayloadProps {
status: string;
data: string;
}
const createDowntimeSchedule = async (
props: DowntimeSchedulePayload,
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
try {
const response = await axios.post('/downtime_schedules', {
...props,
});
return {
statusCode: 200,
error: null,
message: response.data.status,
payload: response.data.data,
};
} catch (error) {
return ErrorResponseHandler(error as AxiosError);
}
};
export default createDowntimeSchedule;

View File

@@ -1,19 +0,0 @@
import { useMutation, UseMutationResult } from 'react-query';
import axios from 'api';
export interface DeleteDowntimeScheduleProps {
id?: number;
}
export interface DeleteSchedulePayloadProps {
status: string;
data: string;
}
export const useDeleteDowntimeSchedule = (
props: DeleteDowntimeScheduleProps,
): UseMutationResult<DeleteSchedulePayloadProps, Error, number> =>
useMutation({
mutationKey: [props.id],
mutationFn: () => axios.delete(`/downtime_schedules/${props.id}`),
});

View File

@@ -1,51 +0,0 @@
import { useQuery, UseQueryResult } from 'react-query';
import axios from 'api';
import { AxiosError, AxiosResponse } from 'axios';
import { Option } from 'container/PlannedDowntime/PlannedDowntimeutils';
export type Recurrence = {
startTime?: string | null;
endTime?: string | null;
duration?: number | string | null;
repeatType?: string | Option | null;
repeatOn?: string[] | null;
};
type Schedule = {
timezone: string | null;
startTime: string | null;
endTime: string | null;
recurrence: Recurrence | null;
};
export interface DowntimeSchedules {
id: number;
name: string | null;
description: string | null;
schedule: Schedule | null;
alertIds: string[] | null;
createdAt: string | null;
createdBy: string | null;
updatedAt: string | null;
updatedBy: string | null;
kind: string | null;
}
export type PayloadProps = { data: DowntimeSchedules[] };
export const getAllDowntimeSchedules = async (
props?: GetAllDowntimeSchedulesPayloadProps,
): Promise<AxiosResponse<PayloadProps>> =>
axios.get('/downtime_schedules', { params: props });
export interface GetAllDowntimeSchedulesPayloadProps {
active?: boolean;
recurrence?: boolean;
}
export const useGetAllDowntimeSchedules = (
props?: GetAllDowntimeSchedulesPayloadProps,
): UseQueryResult<AxiosResponse<PayloadProps>, AxiosError> =>
useQuery<AxiosResponse<PayloadProps>, AxiosError>({
queryKey: ['getAllDowntimeSchedules', props],
queryFn: () => getAllDowntimeSchedules(props),
});

View File

@@ -1,37 +0,0 @@
import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { DowntimeSchedulePayload } from './createDowntimeSchedule';
export interface DowntimeScheduleUpdatePayload {
data: DowntimeSchedulePayload;
id?: number;
}
export interface PayloadProps {
status: string;
data: string;
}
const updateDowntimeSchedule = async (
props: DowntimeScheduleUpdatePayload,
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
try {
const response = await axios.put(`/downtime_schedules/${props.id}`, {
...props.data,
});
return {
statusCode: 200,
error: null,
message: response.data.status,
payload: response.data.data,
};
} catch (error) {
return ErrorResponseHandler(error as AxiosError);
}
};
export default updateDowntimeSchedule;

View File

@@ -0,0 +1,27 @@
import axios from 'api';
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
import { AxiosError } from 'axios';
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
import { PayloadProps, Props } from 'types/api/user/changeMyPassword';
const changeMyPassword = async (
props: Props,
): Promise<SuccessResponseV2<PayloadProps>> => {
try {
const response = await axios.post<PayloadProps>(
`/changePassword/${props.userId}`,
{
...props,
},
);
return {
httpStatusCode: response.status,
data: response.data,
};
} catch (error) {
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
}
};
export default changeMyPassword;

View File

@@ -0,0 +1,28 @@
import axios from 'api';
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
import { AxiosError } from 'axios';
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
import {
GetResetPasswordToken,
PayloadProps,
Props,
} from 'types/api/user/getResetPasswordToken';
const getResetPasswordToken = async (
props: Props,
): Promise<SuccessResponseV2<GetResetPasswordToken>> => {
try {
const response = await axios.get<PayloadProps>(
`/getResetPasswordToken/${props.userId}`,
);
return {
httpStatusCode: response.status,
data: response.data.data,
};
} catch (error) {
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
}
};
export default getResetPasswordToken;

View File

@@ -10,9 +10,8 @@ import { Skeleton, Tooltip } from 'antd';
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
import type { RenderErrorResponseDTO } from 'api/generated/services/sigNoz.schemas';
import {
useCreateResetPasswordToken,
getResetPasswordToken,
useDeleteUser,
useGetResetPasswordToken,
useGetUser,
useUpdateMyUserV2,
useUpdateUser,
@@ -56,27 +55,6 @@ function getDeleteTooltip(
return undefined;
}
function getInviteButtonLabel(
isLoading: boolean,
existingToken: { expiresAt?: Date } | undefined,
isExpired: boolean,
notFound: boolean,
): string {
if (isLoading) {
return 'Checking invite...';
}
if (existingToken && !isExpired) {
return 'Copy Invite Link';
}
if (isExpired) {
return 'Regenerate Invite Link';
}
if (notFound) {
return 'Generate Invite Link';
}
return 'Copy Invite Link';
}
function toSaveApiError(err: unknown): APIError {
return (
convertToApiError(err as AxiosError<RenderErrorResponseDTO>) ??
@@ -105,11 +83,9 @@ function EditMemberDrawer({
const [localRole, setLocalRole] = useState('');
const [isSaving, setIsSaving] = useState(false);
const [saveErrors, setSaveErrors] = useState<SaveError[]>([]);
const [isGeneratingLink, setIsGeneratingLink] = useState(false);
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
const [resetLink, setResetLink] = useState<string | null>(null);
const [resetLinkExpiresAt, setResetLinkExpiresAt] = useState<string | null>(
null,
);
const [showResetLinkDialog, setShowResetLinkDialog] = useState(false);
const [hasCopiedResetLink, setHasCopiedResetLink] = useState(false);
const [linkType, setLinkType] = useState<'invite' | 'reset' | null>(null);
@@ -145,27 +121,6 @@ function EditMemberDrawer({
applyDiff,
} = useMemberRoleManager(member?.id ?? '', open && !!member?.id);
// Token status query for invited users
const {
data: tokenQueryData,
isLoading: isLoadingTokenStatus,
isError: tokenNotFound,
} = useGetResetPasswordToken(
{ id: member?.id ?? '' },
{ query: { enabled: open && !!member?.id && isInvited } },
);
const existingToken = tokenQueryData?.data;
const isTokenExpired =
existingToken != null &&
new Date(String(existingToken.expiresAt)) < new Date();
// Create/regenerate token mutation
const {
mutateAsync: createTokenMutation,
isLoading: isGeneratingLink,
} = useCreateResetPasswordToken();
const fetchedDisplayName =
fetchedUser?.data?.displayName ?? member?.name ?? '';
const fetchedUserId = fetchedUser?.data?.id;
@@ -383,21 +338,12 @@ function EditMemberDrawer({
if (!member) {
return;
}
setIsGeneratingLink(true);
try {
const response = await createTokenMutation({
pathParams: { id: member.id },
});
const response = await getResetPasswordToken({ id: member.id });
if (response?.data?.token) {
const link = `${window.location.origin}/password-reset?token=${response.data.token}`;
setResetLink(link);
setResetLinkExpiresAt(
response.data.expiresAt
? formatTimezoneAdjustedTimestamp(
String(response.data.expiresAt),
DATE_TIME_FORMATS.DASH_DATETIME,
)
: null,
);
setHasCopiedResetLink(false);
setLinkType(isInvited ? 'invite' : 'reset');
setShowResetLinkDialog(true);
@@ -413,8 +359,10 @@ function EditMemberDrawer({
err as AxiosError<RenderErrorResponseDTO, unknown> | null,
);
showErrorModal(errMsg as APIError);
} finally {
setIsGeneratingLink(false);
}
}, [member, isInvited, onClose, showErrorModal, createTokenMutation]);
}, [member, isInvited, onClose, showErrorModal]);
const [copyState, copyToClipboard] = useCopyToClipboard();
const handleCopyResetLink = useCallback((): void => {
@@ -620,19 +568,12 @@ function EditMemberDrawer({
<Button
className="edit-member-drawer__footer-btn edit-member-drawer__footer-btn--warning"
onClick={handleGenerateResetLink}
disabled={isGeneratingLink || isRootUser || isLoadingTokenStatus}
disabled={isGeneratingLink || isRootUser}
>
<RefreshCw size={12} />
{isGeneratingLink
? 'Generating...'
: isInvited
? getInviteButtonLabel(
isLoadingTokenStatus,
existingToken,
isTokenExpired,
tokenNotFound,
)
: 'Generate Password Reset Link'}
{isGeneratingLink && 'Generating...'}
{!isGeneratingLink && isInvited && 'Copy Invite Link'}
{!isGeneratingLink && !isInvited && 'Generate Password Reset Link'}
</Button>
</span>
</Tooltip>
@@ -682,7 +623,6 @@ function EditMemberDrawer({
open={showResetLinkDialog}
linkType={linkType}
resetLink={resetLink}
expiresAt={resetLinkExpiresAt}
hasCopied={hasCopiedResetLink}
onClose={(): void => {
setShowResetLinkDialog(false);

View File

@@ -6,7 +6,6 @@ interface ResetLinkDialogProps {
open: boolean;
linkType: 'invite' | 'reset' | null;
resetLink: string | null;
expiresAt: string | null;
hasCopied: boolean;
onClose: () => void;
onCopy: () => void;
@@ -16,7 +15,6 @@ function ResetLinkDialog({
open,
linkType,
resetLink,
expiresAt,
hasCopied,
onClose,
onCopy,
@@ -55,11 +53,6 @@ function ResetLinkDialog({
{hasCopied ? 'Copied!' : 'Copy'}
</Button>
</div>
{expiresAt && (
<p className="reset-link-dialog__description">
This link expires on {expiresAt}.
</p>
)}
</div>
</DialogWrapper>
);

View File

@@ -2,9 +2,8 @@ import type { ReactNode } from 'react';
import { toast } from '@signozhq/sonner';
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
import {
useCreateResetPasswordToken,
getResetPasswordToken,
useDeleteUser,
useGetResetPasswordToken,
useGetUser,
useSetRoleByUserID,
useUpdateMyUserV2,
@@ -56,8 +55,7 @@ jest.mock('api/generated/services/users', () => ({
useUpdateUser: jest.fn(),
useUpdateMyUserV2: jest.fn(),
useSetRoleByUserID: jest.fn(),
useGetResetPasswordToken: jest.fn(),
useCreateResetPasswordToken: jest.fn(),
getResetPasswordToken: jest.fn(),
}));
jest.mock('api/ErrorResponseHandlerForGeneratedAPIs', () => ({
@@ -84,7 +82,7 @@ jest.mock('react-use', () => ({
const ROLES_ENDPOINT = '*/api/v1/roles';
const mockDeleteMutate = jest.fn();
const mockCreateTokenMutateAsync = jest.fn();
const mockGetResetPasswordToken = jest.mocked(getResetPasswordToken);
const showErrorModal = jest.fn();
jest.mock('providers/ErrorModalProvider', () => ({
@@ -186,31 +184,6 @@ describe('EditMemberDrawer', () => {
mutate: mockDeleteMutate,
isLoading: false,
});
// Token query: valid token for invited members
(useGetResetPasswordToken as jest.Mock).mockReturnValue({
data: {
data: {
token: 'invite-tok-valid',
id: 'token-1',
expiresAt: new Date(Date.now() + 86400000).toISOString(),
},
},
isLoading: false,
isError: false,
});
// Create token mutation
mockCreateTokenMutateAsync.mockResolvedValue({
status: 'success',
data: {
token: 'reset-tok-abc',
id: 'user-1',
expiresAt: new Date(Date.now() + 86400000).toISOString(),
},
});
(useCreateResetPasswordToken as jest.Mock).mockReturnValue({
mutateAsync: mockCreateTokenMutateAsync,
isLoading: false,
});
});
afterEach(() => {
@@ -384,40 +357,6 @@ describe('EditMemberDrawer', () => {
expect(screen.queryByText('Last Modified')).not.toBeInTheDocument();
});
it('shows "Regenerate Invite Link" when token is expired', () => {
(useGetResetPasswordToken as jest.Mock).mockReturnValue({
data: {
data: {
token: 'old-tok',
id: 'token-1',
expiresAt: new Date(Date.now() - 86400000).toISOString(), // expired yesterday
},
},
isLoading: false,
isError: false,
});
renderDrawer({ member: invitedMember });
expect(
screen.getByRole('button', { name: /regenerate invite link/i }),
).toBeInTheDocument();
});
it('shows "Generate Invite Link" when no token exists', () => {
(useGetResetPasswordToken as jest.Mock).mockReturnValue({
data: undefined,
isLoading: false,
isError: true,
});
renderDrawer({ member: invitedMember });
expect(
screen.getByRole('button', { name: /generate invite link/i }),
).toBeInTheDocument();
});
it('calls deleteUser after confirming revoke invite for invited members', async () => {
const onComplete = jest.fn();
const user = userEvent.setup({ pointerEventsCheck: 0 });
@@ -670,7 +609,7 @@ describe('EditMemberDrawer', () => {
).not.toBeInTheDocument();
});
it('does not call createResetPasswordToken when Reset Link is clicked while disabled (root)', async () => {
it('does not call getResetPasswordToken when Reset Link is clicked while disabled (root)', async () => {
const user = userEvent.setup({ pointerEventsCheck: 0 });
renderDrawer();
@@ -678,16 +617,20 @@ describe('EditMemberDrawer', () => {
screen.getByRole('button', { name: /generate password reset link/i }),
);
expect(mockCreateTokenMutateAsync).not.toHaveBeenCalled();
expect(mockGetResetPasswordToken).not.toHaveBeenCalled();
});
});
describe('Generate Password Reset Link', () => {
beforeEach(() => {
mockCopyToClipboard.mockClear();
mockGetResetPasswordToken.mockResolvedValue({
status: 'success',
data: { token: 'reset-tok-abc', id: 'user-1' },
});
});
it('calls POST and opens the reset link dialog with the generated link and expiry', async () => {
it('calls getResetPasswordToken and opens the reset link dialog with the generated link', async () => {
const user = userEvent.setup({ pointerEventsCheck: 0 });
renderDrawer();
@@ -699,12 +642,11 @@ describe('EditMemberDrawer', () => {
const dialog = await screen.findByRole('dialog', {
name: /password reset link/i,
});
expect(mockCreateTokenMutateAsync).toHaveBeenCalledWith({
pathParams: { id: 'user-1' },
expect(mockGetResetPasswordToken).toHaveBeenCalledWith({
id: 'user-1',
});
expect(dialog).toBeInTheDocument();
expect(dialog).toHaveTextContent('reset-tok-abc');
expect(dialog).toHaveTextContent(/this link expires on/i);
});
it('copies the link to clipboard and shows "Copied!" on the button', async () => {

View File

@@ -6,9 +6,7 @@ import { getCreateAlertLocalStateFromAlertDef } from 'container/CreateAlertV2/ut
import * as useSafeNavigateHook from 'hooks/useSafeNavigate';
import { AlertTypes } from 'types/api/alerts/alertTypes';
import * as useCreateAlertRuleHook from '../../../../hooks/alerts/useCreateAlertRule';
import * as useTestAlertRuleHook from '../../../../hooks/alerts/useTestAlertRule';
import * as useUpdateAlertRuleHook from '../../../../hooks/alerts/useUpdateAlertRule';
import * as rulesHook from '../../../../api/generated/services/rules';
import { CreateAlertProvider } from '../../context';
import CreateAlertHeader from '../CreateAlertHeader';
@@ -17,15 +15,15 @@ jest.spyOn(useSafeNavigateHook, 'useSafeNavigate').mockReturnValue({
safeNavigate: mockSafeNavigate,
});
jest.spyOn(useCreateAlertRuleHook, 'useCreateAlertRule').mockReturnValue({
jest.spyOn(rulesHook, 'useCreateRule').mockReturnValue({
mutate: jest.fn(),
isLoading: false,
} as any);
jest.spyOn(useTestAlertRuleHook, 'useTestAlertRule').mockReturnValue({
jest.spyOn(rulesHook, 'useTestRule').mockReturnValue({
mutate: jest.fn(),
isLoading: false,
} as any);
jest.spyOn(useUpdateAlertRuleHook, 'useUpdateAlertRule').mockReturnValue({
jest.spyOn(rulesHook, 'useUpdateRuleByID').mockReturnValue({
mutate: jest.fn(),
isLoading: false,
} as any);

View File

@@ -34,6 +34,7 @@ export const createMockAlertContextState = (
isUpdatingAlertRule: false,
updateAlertRule: jest.fn(),
isEditMode: false,
ruleId: '',
...overrides,
});

View File

@@ -1,9 +1,17 @@
import { useCallback, useMemo } from 'react';
import { toast } from '@signozhq/sonner';
import { Button, Tooltip, Typography } from 'antd';
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
import type {
RenderErrorResponseDTO,
RuletypesPostableRuleDTO,
} from 'api/generated/services/sigNoz.schemas';
import { AxiosError } from 'axios';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import { Check, Loader, Send, X } from 'lucide-react';
import { useErrorModal } from 'providers/ErrorModalProvider';
import APIError from 'types/api/error';
import { isModifierKeyPressed } from 'utils/app';
import { useCreateAlertState } from '../context';
@@ -30,9 +38,11 @@ function Footer(): JSX.Element {
updateAlertRule,
isUpdatingAlertRule,
isEditMode,
ruleId,
} = useCreateAlertState();
const { currentQuery } = useQueryBuilder();
const { safeNavigate } = useSafeNavigate();
const { showErrorModal } = useErrorModal();
const handleDiscard = (e: React.MouseEvent): void => {
discardAlertRule();
@@ -71,20 +81,27 @@ function Footer(): JSX.Element {
notificationSettings,
query: currentQuery,
});
testAlertRule(payload, {
onSuccess: (response) => {
if (response.payload?.data?.alertCount === 0) {
toast.error(
'No alerts found during the evaluation. This happens when rule condition is unsatisfied. You may adjust the rule threshold and retry.',
testAlertRule(
{ data: (payload as unknown) as RuletypesPostableRuleDTO },
{
onSuccess: (response) => {
if (response.data?.alertCount === 0) {
toast.error(
'No alerts found during the evaluation. This happens when rule condition is unsatisfied. You may adjust the rule threshold and retry.',
);
return;
}
toast.success('Test notification sent successfully');
},
onError: (error) => {
showErrorModal(
convertToApiError(
error as AxiosError<RenderErrorResponseDTO>,
) as APIError,
);
return;
}
toast.success('Test notification sent successfully');
},
},
onError: (error) => {
toast.error(error.message);
},
});
);
}, [
alertType,
basicAlertState,
@@ -107,25 +124,34 @@ function Footer(): JSX.Element {
query: currentQuery,
});
if (isEditMode) {
updateAlertRule(payload, {
onSuccess: () => {
toast.success('Alert rule updated successfully');
safeNavigate('/alerts');
updateAlertRule(
{
pathParams: { id: ruleId },
data: (payload as unknown) as RuletypesPostableRuleDTO,
},
onError: (error) => {
toast.error(error.message);
{
onSuccess: () => {
toast.success('Alert rule updated successfully');
safeNavigate('/alerts');
},
onError: (error) => {
toast.error(error.message);
},
},
});
);
} else {
createAlertRule(payload, {
onSuccess: () => {
toast.success('Alert rule created successfully');
safeNavigate('/alerts');
createAlertRule(
{ data: (payload as unknown) as RuletypesPostableRuleDTO },
{
onSuccess: () => {
toast.success('Alert rule created successfully');
safeNavigate('/alerts');
},
onError: (error) => {
toast.error(error.message);
},
},
onError: (error) => {
toast.error(error.message);
},
});
);
}
}, [
alertType,
@@ -136,9 +162,11 @@ function Footer(): JSX.Element {
notificationSettings,
currentQuery,
isEditMode,
ruleId,
updateAlertRule,
createAlertRule,
safeNavigate,
showErrorModal,
]);
const disableButtons =

View File

@@ -12,6 +12,11 @@ import Footer from '../Footer';
jest.mock('hooks/queryBuilder/useQueryBuilder', () => ({
useQueryBuilder: jest.fn(),
}));
jest.mock('providers/ErrorModalProvider', () => ({
useErrorModal: (): { showErrorModal: jest.Mock } => ({
showErrorModal: jest.fn(),
}),
}));
jest.mock('hooks/useSafeNavigate', () => ({
useSafeNavigate: jest.fn(),

View File

@@ -10,11 +10,13 @@ import {
useState,
} from 'react';
import { useLocation } from 'react-router-dom';
import {
useCreateRule,
useTestRule,
useUpdateRuleByID,
} from 'api/generated/services/rules';
import { QueryParams } from 'constants/query';
import { AlertDetectionTypes } from 'container/FormAlertRules';
import { useCreateAlertRule } from 'hooks/alerts/useCreateAlertRule';
import { useTestAlertRule } from 'hooks/alerts/useTestAlertRule';
import { useUpdateAlertRule } from 'hooks/alerts/useUpdateAlertRule';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { mapQueryDataFromApi } from 'lib/newQueryBuilder/queryBuilderMappers/mapQueryDataFromApi';
import { AlertTypes } from 'types/api/alerts/alertTypes';
@@ -215,17 +217,14 @@ export function CreateAlertProvider(
const {
mutate: createAlertRule,
isLoading: isCreatingAlertRule,
} = useCreateAlertRule();
} = useCreateRule();
const {
mutate: testAlertRule,
isLoading: isTestingAlertRule,
} = useTestAlertRule();
const { mutate: testAlertRule, isLoading: isTestingAlertRule } = useTestRule();
const {
mutate: updateAlertRule,
isLoading: isUpdatingAlertRule,
} = useUpdateAlertRule(ruleId || '');
} = useUpdateRuleByID();
const contextValue: ICreateAlertContextProps = useMemo(
() => ({
@@ -249,6 +248,7 @@ export function CreateAlertProvider(
updateAlertRule,
isUpdatingAlertRule,
isEditMode: isEditMode || false,
ruleId: ruleId || '',
}),
[
createAlertState,
@@ -267,6 +267,7 @@ export function CreateAlertProvider(
updateAlertRule,
isUpdatingAlertRule,
isEditMode,
ruleId,
],
);

View File

@@ -1,12 +1,15 @@
import { Dispatch } from 'react';
import { UseMutateFunction } from 'react-query';
import { CreateAlertRuleResponse } from 'api/alerts/createAlertRule';
import { TestAlertRuleResponse } from 'api/alerts/testAlertRule';
import { UpdateAlertRuleResponse } from 'api/alerts/updateAlertRule';
import type {
CreateRule200,
RenderErrorResponseDTO,
RuletypesPostableRuleDTO,
TestRule200,
UpdateRuleByIDPathParameters,
} from 'api/generated/services/sigNoz.schemas';
import type { BodyType, ErrorType } from 'api/generatedAPIInstance';
import { Dayjs } from 'dayjs';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { AlertTypes } from 'types/api/alerts/alertTypes';
import { PostableAlertRuleV2 } from 'types/api/alerts/alertTypesV2';
import { Labels } from 'types/api/alerts/def';
export interface ICreateAlertContextProps {
@@ -24,27 +27,33 @@ export interface ICreateAlertContextProps {
setNotificationSettings: Dispatch<NotificationSettingsAction>;
isCreatingAlertRule: boolean;
createAlertRule: UseMutateFunction<
SuccessResponse<CreateAlertRuleResponse, unknown> | ErrorResponse,
Error,
PostableAlertRuleV2,
CreateRule200,
ErrorType<unknown>,
{ data: BodyType<RuletypesPostableRuleDTO> },
unknown
>;
isTestingAlertRule: boolean;
testAlertRule: UseMutateFunction<
SuccessResponse<TestAlertRuleResponse, unknown> | ErrorResponse,
Error,
PostableAlertRuleV2,
TestRule200,
ErrorType<unknown>,
{ data: BodyType<RuletypesPostableRuleDTO> },
unknown
>;
discardAlertRule: () => void;
isUpdatingAlertRule: boolean;
updateAlertRule: UseMutateFunction<
SuccessResponse<UpdateAlertRuleResponse, unknown> | ErrorResponse,
Error,
PostableAlertRuleV2,
Awaited<
ReturnType<typeof import('api/generated/services/rules').updateRuleByID>
>,
ErrorType<RenderErrorResponseDTO>,
{
pathParams: UpdateRuleByIDPathParameters;
data: BodyType<RuletypesPostableRuleDTO>;
},
unknown
>;
isEditMode: boolean;
ruleId: string;
}
export interface ICreateAlertProviderProps {

View File

@@ -6,9 +6,18 @@ import { useSelector } from 'react-redux';
import { useLocation } from 'react-router-dom';
import { ExclamationCircleOutlined, SaveOutlined } from '@ant-design/icons';
import { Button, FormInstance, Modal, SelectProps, Typography } from 'antd';
import saveAlertApi from 'api/alerts/save';
import testAlertApi from 'api/alerts/testAlert';
import logEvent from 'api/common/logEvent';
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
import {
createRule,
testRule,
updateRuleByID,
} from 'api/generated/services/rules';
import type {
RenderErrorResponseDTO,
RuletypesPostableRuleDTO,
} from 'api/generated/services/sigNoz.schemas';
import { AxiosError } from 'axios';
import { getInvolvedQueriesInTraceOperator } from 'components/QueryBuilderV2/QueryV2/TraceOperator/utils/utils';
import YAxisUnitSelector from 'components/YAxisUnitSelector';
import { YAxisSource } from 'components/YAxisUnitSelector/types';
@@ -32,6 +41,7 @@ import { isEmpty, isEqual } from 'lodash-es';
import { BellDot, ExternalLink } from 'lucide-react';
import Tabs2 from 'periscope/components/Tabs2';
import { useAppContext } from 'providers/App/App';
import { useErrorModal } from 'providers/ErrorModalProvider';
import { AppState } from 'store/reducers';
import { AlertTypes } from 'types/api/alerts/alertTypes';
import {
@@ -39,6 +49,7 @@ import {
defaultEvalWindow,
defaultMatchType,
} from 'types/api/alerts/def';
import APIError from 'types/api/error';
import { IBuilderQuery, Query } from 'types/api/queryBuilder/queryBuilderData';
import { QueryFunction } from 'types/api/v5/queryRange';
import { EQueryType } from 'types/common/dashboard';
@@ -368,6 +379,7 @@ function FormAlertRules({
redirectWithQueryBuilderData(query);
};
const { notifications } = useNotifications();
const { showErrorModal } = useErrorModal();
const validatePromParams = useCallback((): boolean => {
let retval = true;
@@ -533,59 +545,47 @@ function FormAlertRules({
};
try {
const apiReq =
ruleId && !isEmpty(ruleId)
? { data: postableAlert, id: ruleId }
: { data: postableAlert };
const response = await saveAlertApi(apiReq);
if (response.statusCode === 200) {
logData = {
status: 'success',
statusMessage: isNewRule ? t('rule_created') : t('rule_edited'),
};
notifications.success({
message: 'Success',
description: logData.statusMessage,
});
// invalidate rule in cache
ruleCache.invalidateQueries([
REACT_QUERY_KEY.ALERT_RULE_DETAILS,
`${ruleId}`,
]);
// eslint-disable-next-line sonarjs/no-identical-functions
setTimeout(() => {
urlQuery.delete(QueryParams.compositeQuery);
urlQuery.delete(QueryParams.panelTypes);
urlQuery.delete(QueryParams.ruleId);
urlQuery.delete(QueryParams.relativeTime);
safeNavigate(`${ROUTES.LIST_ALL_ALERT}?${urlQuery.toString()}`);
}, 2000);
if (ruleId && !isEmpty(ruleId)) {
await updateRuleByID(
{ id: ruleId },
(postableAlert as unknown) as RuletypesPostableRuleDTO,
);
} else {
logData = {
status: 'error',
statusMessage: response.error || t('unexpected_error'),
};
notifications.error({
message: 'Error',
description: logData.statusMessage,
});
await createRule((postableAlert as unknown) as RuletypesPostableRuleDTO);
}
} catch (e) {
logData = {
status: 'error',
statusMessage: t('unexpected_error'),
status: 'success',
statusMessage: isNewRule ? t('rule_created') : t('rule_edited'),
};
notifications.error({
message: 'Error',
notifications.success({
message: 'Success',
description: logData.statusMessage,
});
// invalidate rule in cache
ruleCache.invalidateQueries([
REACT_QUERY_KEY.ALERT_RULE_DETAILS,
`${ruleId}`,
]);
// eslint-disable-next-line sonarjs/no-identical-functions
setTimeout(() => {
urlQuery.delete(QueryParams.compositeQuery);
urlQuery.delete(QueryParams.panelTypes);
urlQuery.delete(QueryParams.ruleId);
urlQuery.delete(QueryParams.relativeTime);
safeNavigate(`${ROUTES.LIST_ALL_ALERT}?${urlQuery.toString()}`);
}, 2000);
} catch (e) {
const apiError = convertToApiError(e as AxiosError<RenderErrorResponseDTO>);
logData = {
status: 'error',
statusMessage: apiError?.getErrorMessage() || t('unexpected_error'),
};
showErrorModal(apiError as APIError);
}
setLoading(false);
@@ -641,39 +641,30 @@ function FormAlertRules({
let statusResponse = { status: 'failed', message: '' };
setLoading(true);
try {
const response = await testAlertApi({ data: postableAlert });
const response = await testRule(
(postableAlert as unknown) as RuletypesPostableRuleDTO,
);
if (response.statusCode === 200) {
const { payload } = response;
if (payload?.alertCount === 0) {
notifications.error({
message: 'Error',
description: t('no_alerts_found'),
});
statusResponse = { status: 'failed', message: t('no_alerts_found') };
} else {
notifications.success({
message: 'Success',
description: t('rule_test_fired'),
});
statusResponse = { status: 'success', message: t('rule_test_fired') };
}
} else {
if (response.data?.alertCount === 0) {
notifications.error({
message: 'Error',
description: response.error || t('unexpected_error'),
description: t('no_alerts_found'),
});
statusResponse = {
status: 'failed',
message: response.error || t('unexpected_error'),
};
statusResponse = { status: 'failed', message: t('no_alerts_found') };
} else {
notifications.success({
message: 'Success',
description: t('rule_test_fired'),
});
statusResponse = { status: 'success', message: t('rule_test_fired') };
}
} catch (e) {
notifications.error({
message: 'Error',
description: t('unexpected_error'),
});
statusResponse = { status: 'failed', message: t('unexpected_error') };
const apiError = convertToApiError(e as AxiosError<RenderErrorResponseDTO>);
statusResponse = {
status: 'failed',
message: apiError?.getErrorMessage() || t('unexpected_error'),
};
showErrorModal(apiError as APIError);
}
setLoading(false);
logEvent('Alert: Test notification', {

View File

@@ -1,9 +1,9 @@
import { useEffect, useState } from 'react';
import { useQuery } from 'react-query';
import { Link, useLocation } from 'react-router-dom';
import { Button, Skeleton, Tag } from 'antd';
import getAll from 'api/alerts/getAll';
import logEvent from 'api/common/logEvent';
import { useListRules } from 'api/generated/services/rules';
import type { RuletypesGettableRuleDTO } from 'api/generated/services/sigNoz.schemas';
import { QueryParams } from 'constants/query';
import ROUTES from 'constants/routes';
import history from 'lib/history';
@@ -11,7 +11,7 @@ import { mapQueryDataFromApi } from 'lib/newQueryBuilder/queryBuilderMappers/map
import { ArrowRight, ArrowUpRight, Plus } from 'lucide-react';
import Card from 'periscope/components/Card/Card';
import { useAppContext } from 'providers/App/App';
import { GettableAlert } from 'types/api/alerts/get';
import type { ICompositeMetricQuery } from 'types/api/alerts/compositeQuery';
import { USER_ROLES } from 'types/roles';
import beaconUrl from '@/assets/Icons/beacon.svg';
@@ -28,22 +28,23 @@ export default function AlertRules({
const { user } = useAppContext();
const [rulesExist, setRulesExist] = useState(false);
const [sortedAlertRules, setSortedAlertRules] = useState<GettableAlert[]>([]);
const [sortedAlertRules, setSortedAlertRules] = useState<
RuletypesGettableRuleDTO[]
>([]);
const location = useLocation();
const params = new URLSearchParams(location.search);
// Fetch Alerts
const { data: alerts, isError, isLoading } = useQuery('allAlerts', {
queryFn: getAll,
cacheTime: 0,
const { data: alerts, isError, isLoading } = useListRules({
query: { cacheTime: 0 },
});
useEffect(() => {
const rules = alerts?.payload || [];
const rules = alerts?.data?.rules ?? [];
setRulesExist(rules.length > 0);
const sortedRules = rules.sort((a, b) => {
const sortedRules = [...rules].sort((a, b) => {
// First, prioritize firing alerts
if (a.state === 'firing' && b.state !== 'firing') {
return -1;
@@ -53,8 +54,8 @@ export default function AlertRules({
}
// Then sort by updateAt timestamp
const aUpdateAt = new Date(a.updateAt).getTime();
const bUpdateAt = new Date(b.updateAt).getTime();
const aUpdateAt = a.updateAt ? new Date(a.updateAt).getTime() : 0;
const bUpdateAt = b.updateAt ? new Date(b.updateAt).getTime() : 0;
return bUpdateAt - aUpdateAt;
});
@@ -118,22 +119,27 @@ export default function AlertRules({
</div>
);
const onEditHandler = (record: GettableAlert) => (): void => {
const onEditHandler = (record: RuletypesGettableRuleDTO) => (): void => {
logEvent('Homepage: Alert clicked', {
ruleId: record.id,
ruleName: record.alert,
ruleState: record.state,
});
const compositeQuery = mapQueryDataFromApi(record.condition.compositeQuery);
const compositeQuery = mapQueryDataFromApi(
(record.condition?.compositeQuery as unknown) as ICompositeMetricQuery,
);
params.set(
QueryParams.compositeQuery,
encodeURIComponent(JSON.stringify(compositeQuery)),
);
params.set(QueryParams.panelTypes, record.condition.compositeQuery.panelType);
const panelType = record.condition?.compositeQuery?.panelType;
if (panelType) {
params.set(QueryParams.panelTypes, panelType);
}
params.set(QueryParams.ruleId, record.id.toString());
params.set(QueryParams.ruleId, record.id ?? '');
history.push(`${ROUTES.ALERT_OVERVIEW}?${params.toString()}`);
};
@@ -156,7 +162,7 @@ export default function AlertRules({
>
<div className="alert-rule-item-name-container home-data-item-name-container">
<img
src={getItemIcon(rule.id)}
src={getItemIcon(rule.id ?? '')}
alt="alert-rules"
className="alert-rules-img"
/>

View File

@@ -1,9 +1,16 @@
import { Dispatch, SetStateAction, useState } from 'react';
import type { NotificationInstance } from 'antd/es/notification/interface';
import deleteAlerts from 'api/alerts/delete';
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
import { deleteRuleByID } from 'api/generated/services/rules';
import type {
RenderErrorResponseDTO,
RuletypesGettableRuleDTO,
} from 'api/generated/services/sigNoz.schemas';
import { AxiosError } from 'axios';
import { State } from 'hooks/useFetch';
import { useErrorModal } from 'providers/ErrorModalProvider';
import { PayloadProps as DeleteAlertPayloadProps } from 'types/api/alerts/delete';
import { GettableAlert } from 'types/api/alerts/get';
import APIError from 'types/api/error';
import { ColumnButton } from './styles';
@@ -22,48 +29,31 @@ function DeleteAlert({
payload: undefined,
});
const defaultErrorMessage = 'Something went wrong';
const { showErrorModal } = useErrorModal();
const onDeleteHandler = async (id: string): Promise<void> => {
try {
const response = await deleteAlerts({
id,
await deleteRuleByID({ id });
setData((state) => state.filter((alert) => alert.id !== id));
setDeleteAlertState((state) => ({
...state,
loading: false,
}));
notifications.success({
message: 'Success',
});
if (response.statusCode === 200) {
setData((state) => state.filter((alert) => alert.id !== id));
setDeleteAlertState((state) => ({
...state,
loading: false,
payload: response.payload,
}));
notifications.success({
message: 'Success',
});
} else {
setDeleteAlertState((state) => ({
...state,
loading: false,
error: true,
errorMessage: response.error || defaultErrorMessage,
}));
notifications.error({
message: response.error || defaultErrorMessage,
});
}
} catch (error) {
setDeleteAlertState((state) => ({
...state,
loading: false,
error: true,
errorMessage: defaultErrorMessage,
}));
notifications.error({
message: defaultErrorMessage,
});
showErrorModal(
convertToApiError(error as AxiosError<RenderErrorResponseDTO>) as APIError,
);
}
};
@@ -88,8 +78,8 @@ function DeleteAlert({
}
interface DeleteAlertProps {
id: GettableAlert['id'];
setData: Dispatch<SetStateAction<GettableAlert[]>>;
id: string;
setData: Dispatch<SetStateAction<RuletypesGettableRuleDTO[]>>;
notifications: NotificationInstance;
}

View File

@@ -4,8 +4,17 @@ import { UseQueryResult } from 'react-query';
import { PlusOutlined } from '@ant-design/icons';
import { Button, Flex, Input, Typography } from 'antd';
import type { ColumnsType } from 'antd/es/table/interface';
import saveAlertApi from 'api/alerts/save';
import logEvent from 'api/common/logEvent';
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
import { createRule } from 'api/generated/services/rules';
import type {
ListRules200,
RenderErrorResponseDTO,
RuletypesGettableRuleDTO,
RuletypesPostableRuleDTO,
} from 'api/generated/services/sigNoz.schemas';
import type { ErrorType } from 'api/generatedAPIInstance';
import { AxiosError } from 'axios';
import DropDown from 'components/DropDown/DropDown';
import {
DynamicColumnsKey,
@@ -27,9 +36,10 @@ import { useSafeNavigate } from 'hooks/useSafeNavigate';
import useUrlQuery from 'hooks/useUrlQuery';
import { mapQueryDataFromApi } from 'lib/newQueryBuilder/queryBuilderMappers/mapQueryDataFromApi';
import { useAppContext } from 'providers/App/App';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { useErrorModal } from 'providers/ErrorModalProvider';
import { AlertTypes } from 'types/api/alerts/alertTypes';
import { GettableAlert } from 'types/api/alerts/get';
import type { ICompositeMetricQuery } from 'types/api/alerts/compositeQuery';
import APIError from 'types/api/error';
import { isModifierKeyPressed } from 'utils/app';
import DeleteAlert from './DeleteAlert';
@@ -58,7 +68,7 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
const paginationParam = params.get('page');
const searchParams = params.get('search');
const [searchString, setSearchString] = useState<string>(searchParams || '');
const [data, setData] = useState<GettableAlert[]>(() => {
const [data, setData] = useState<RuletypesGettableRuleDTO[]>(() => {
const value = searchString.toLowerCase();
const filteredData = filterAlerts(allAlertRules, value);
return filteredData || [];
@@ -70,7 +80,10 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
? orderQueryParam
: null;
const { sortedInfo, handleChange } = useSortableTable<GettableAlert>(
const {
sortedInfo,
handleChange,
} = useSortableTable<RuletypesGettableRuleDTO>(
sortingOrder,
orderColumnParam || '',
searchString,
@@ -83,7 +96,7 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
const { data: refetchData, status } = await refetch();
if (status === 'success') {
const value = searchString.toLowerCase();
const filteredData = filterAlerts(refetchData.payload || [], value);
const filteredData = filterAlerts(refetchData?.data?.rules ?? [], value);
setData(filteredData || []);
}
if (status === 'error') {
@@ -94,11 +107,7 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
})();
}, 30000);
const handleError = useCallback((): void => {
notificationsApi.error({
message: t('something_went_wrong'),
});
}, [notificationsApi, t]);
const { showErrorModal } = useErrorModal();
const onClickNewAlertHandler = useCallback(
(e: React.MouseEvent): void => {
@@ -115,11 +124,13 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
);
const onEditHandler = (
record: GettableAlert,
record: RuletypesGettableRuleDTO,
options?: { newTab?: boolean },
): void => {
const compositeQuery = sanitizeDefaultAlertQuery(
mapQueryDataFromApi(record.condition.compositeQuery),
mapQueryDataFromApi(
(record.condition?.compositeQuery as unknown) as ICompositeMetricQuery,
),
record.alertType as AlertTypes,
);
params.set(
@@ -127,9 +138,12 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
encodeURIComponent(JSON.stringify(compositeQuery)),
);
params.set(QueryParams.panelTypes, record.condition.compositeQuery.panelType);
const panelType = record.condition?.compositeQuery?.panelType;
if (panelType) {
params.set(QueryParams.panelTypes, panelType);
}
params.set(QueryParams.ruleId, record.id.toString());
params.set(QueryParams.ruleId, record.id ?? '');
setEditLoader(false);
@@ -139,47 +153,41 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
};
const onCloneHandler = (
originalAlert: GettableAlert,
originalAlert: RuletypesGettableRuleDTO,
) => async (): Promise<void> => {
const copyAlert = {
const copyAlert: RuletypesGettableRuleDTO = {
...originalAlert,
alert: originalAlert.alert.concat(' - Copy'),
alert: `${originalAlert.alert ?? ''} - Copy`,
};
const apiReq = { data: copyAlert };
try {
setCloneLoader(true);
const response = await saveAlertApi(apiReq);
await createRule((copyAlert as unknown) as RuletypesPostableRuleDTO);
if (response.statusCode === 200) {
notificationsApi.success({
message: 'Success',
description: 'Alert cloned successfully',
});
notificationsApi.success({
message: 'Success',
description: 'Alert cloned successfully',
});
const { data: refetchData, status } = await refetch();
if (status === 'success' && refetchData.payload) {
setData(refetchData.payload || []);
setTimeout(() => {
const clonedAlert = refetchData.payload[refetchData.payload.length - 1];
params.set(QueryParams.ruleId, String(clonedAlert.id));
safeNavigate(`${ROUTES.EDIT_ALERTS}?${params.toString()}`);
}, 2000);
}
if (status === 'error') {
notificationsApi.error({
message: t('something_went_wrong'),
});
}
} else {
const { data: refetchData, status } = await refetch();
const rules = refetchData?.data?.rules;
if (status === 'success' && rules) {
setData(rules);
setTimeout(() => {
const clonedAlert = rules[rules.length - 1];
params.set(QueryParams.ruleId, String(clonedAlert.id));
safeNavigate(`${ROUTES.EDIT_ALERTS}?${params.toString()}`);
}, 2000);
}
if (status === 'error') {
notificationsApi.error({
message: 'Error',
description: response.error || t('something_went_wrong'),
message: t('something_went_wrong'),
});
}
} catch (error) {
handleError();
console.error(error);
showErrorModal(
convertToApiError(error as AxiosError<RenderErrorResponseDTO>) as APIError,
);
} finally {
setCloneLoader(false);
}
@@ -192,16 +200,19 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
setData(filteredData);
});
const dynamicColumns: ColumnsType<GettableAlert> = [
const dynamicColumns: ColumnsType<RuletypesGettableRuleDTO> = [
{
title: 'Created At',
dataIndex: 'createAt',
width: 80,
key: DynamicColumnsKey.CreatedAt,
align: 'center',
sorter: (a: GettableAlert, b: GettableAlert): number => {
const prev = new Date(a.createAt).getTime();
const next = new Date(b.createAt).getTime();
sorter: (
a: RuletypesGettableRuleDTO,
b: RuletypesGettableRuleDTO,
): number => {
const prev = a.createAt ? new Date(a.createAt).getTime() : 0;
const next = b.createAt ? new Date(b.createAt).getTime() : 0;
return prev - next;
},
@@ -224,9 +235,12 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
width: 80,
key: DynamicColumnsKey.UpdatedAt,
align: 'center',
sorter: (a: GettableAlert, b: GettableAlert): number => {
const prev = new Date(a.updateAt).getTime();
const next = new Date(b.updateAt).getTime();
sorter: (
a: RuletypesGettableRuleDTO,
b: RuletypesGettableRuleDTO,
): number => {
const prev = a.updateAt ? new Date(a.updateAt).getTime() : 0;
const next = b.updateAt ? new Date(b.updateAt).getTime() : 0;
return prev - next;
},
@@ -245,7 +259,7 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
},
];
const columns: ColumnsType<GettableAlert> = [
const columns: ColumnsType<RuletypesGettableRuleDTO> = [
{
title: 'Status',
dataIndex: 'state',
@@ -322,7 +336,7 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
dataIndex: 'id',
key: 'action',
width: 10,
render: (id: GettableAlert['id'], record): JSX.Element => (
render: (id: RuletypesGettableRuleDTO['id'], record): JSX.Element => (
<div data-testid="alert-actions">
<DropDown
onDropDownItemClick={(item): void =>
@@ -331,9 +345,9 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
element={[
<ToggleAlertState
key="1"
disabled={record.disabled}
disabled={record.disabled ?? false}
setData={setData}
id={id}
id={id ?? ''}
/>,
<ColumnButton
key="2"
@@ -365,7 +379,7 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
key="4"
notifications={notificationsApi}
setData={setData}
id={id}
id={id ?? ''}
/>,
]}
/>
@@ -420,9 +434,10 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
}
interface ListAlertProps {
allAlertRules: GettableAlert[];
allAlertRules: RuletypesGettableRuleDTO[];
refetch: UseQueryResult<
ErrorResponse | SuccessResponse<GettableAlert[]>
ListRules200,
ErrorType<RenderErrorResponseDTO>
>['refetch'];
}

View File

@@ -1,5 +1,5 @@
import { Tag } from 'antd';
import { GettableAlert } from 'types/api/alerts/get';
import type { RuletypesGettableRuleDTO } from 'api/generated/services/sigNoz.schemas';
function Status({ status }: StatusProps): JSX.Element {
switch (status) {
@@ -26,7 +26,7 @@ function Status({ status }: StatusProps): JSX.Element {
}
interface StatusProps {
status: GettableAlert['state'];
status: RuletypesGettableRuleDTO['state'];
}
export default Status;

View File

@@ -1,9 +1,16 @@
import { Dispatch, SetStateAction, useState } from 'react';
import patchAlert from 'api/alerts/patch';
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
import { patchRuleByID } from 'api/generated/services/rules';
import type {
RenderErrorResponseDTO,
RuletypesGettableRuleDTO,
RuletypesPostableRuleDTO,
} from 'api/generated/services/sigNoz.schemas';
import { AxiosError } from 'axios';
import { State } from 'hooks/useFetch';
import { useNotifications } from 'hooks/useNotifications';
import { GettableAlert } from 'types/api/alerts/get';
import { PayloadProps as PatchPayloadProps } from 'types/api/alerts/patch';
import { useErrorModal } from 'providers/ErrorModalProvider';
import APIError from 'types/api/error';
import { ColumnButton } from './styles';
@@ -12,7 +19,7 @@ function ToggleAlertState({
disabled,
setData,
}: ToggleAlertStateProps): JSX.Element {
const [apiStatus, setAPIStatus] = useState<State<PatchPayloadProps>>({
const [apiStatus, setAPIStatus] = useState<State<RuletypesGettableRuleDTO>>({
error: false,
errorMessage: '',
loading: false,
@@ -21,8 +28,7 @@ function ToggleAlertState({
});
const { notifications } = useNotifications();
const defaultErrorMessage = 'Something went wrong';
const { showErrorModal } = useErrorModal();
const onToggleHandler = async (
id: string,
@@ -34,58 +40,42 @@ function ToggleAlertState({
loading: true,
}));
const response = await patchAlert({
id,
data: {
disabled,
},
const response = await patchRuleByID({ id }, ({
disabled,
} as unknown) as RuletypesPostableRuleDTO);
const { data: updatedRule } = response;
setData((state) =>
state.map((alert) => {
if (alert.id === id) {
return {
...alert,
disabled: updatedRule.disabled,
state: updatedRule.state,
};
}
return alert;
}),
);
setAPIStatus((state) => ({
...state,
loading: false,
payload: updatedRule,
}));
notifications.success({
message: 'Success',
});
if (response.statusCode === 200) {
setData((state) =>
state.map((alert) => {
if (alert.id === id) {
return {
...alert,
disabled: response.payload.disabled,
state: response.payload.state,
};
}
return alert;
}),
);
setAPIStatus((state) => ({
...state,
loading: false,
payload: response.payload,
}));
notifications.success({
message: 'Success',
});
} else {
setAPIStatus((state) => ({
...state,
loading: false,
error: true,
errorMessage: response.error || defaultErrorMessage,
}));
notifications.error({
message: response.error || defaultErrorMessage,
});
}
} catch (error) {
setAPIStatus((state) => ({
...state,
loading: false,
error: true,
errorMessage: defaultErrorMessage,
}));
notifications.error({
message: defaultErrorMessage,
});
showErrorModal(
convertToApiError(error as AxiosError<RenderErrorResponseDTO>) as APIError,
);
}
};
@@ -102,9 +92,9 @@ function ToggleAlertState({
}
interface ToggleAlertStateProps {
id: GettableAlert['id'];
id: string;
disabled: boolean;
setData: Dispatch<SetStateAction<GettableAlert[]>>;
setData: Dispatch<SetStateAction<RuletypesGettableRuleDTO[]>>;
}
export default ToggleAlertState;

View File

@@ -1,19 +1,24 @@
import { GettableAlert } from 'types/api/alerts/get';
import type {
RuletypesAlertStateDTO,
RuletypesGettableRuleDTO,
} from 'api/generated/services/sigNoz.schemas';
import { filterAlerts } from '../utils';
describe('filterAlerts', () => {
const mockAlertBase: Partial<GettableAlert> = {
state: 'active',
const mockAlertBase: Partial<RuletypesGettableRuleDTO> = {
state: 'active' as RuletypesAlertStateDTO,
disabled: false,
createAt: '2024-01-01T00:00:00Z',
createAt: new Date('2024-01-01T00:00:00Z'),
createBy: 'test-user',
updateAt: '2024-01-01T00:00:00Z',
updateAt: new Date('2024-01-01T00:00:00Z'),
updateBy: 'test-user',
version: '1',
condition: {},
ruleType: 'threshold_rule' as RuletypesGettableRuleDTO['ruleType'],
};
const mockAlerts: GettableAlert[] = [
const mockAlerts: RuletypesGettableRuleDTO[] = [
{
...mockAlertBase,
id: '1',
@@ -24,7 +29,7 @@ describe('filterAlerts', () => {
status: 'ok',
environment: 'production',
},
} as GettableAlert,
} as RuletypesGettableRuleDTO,
{
...mockAlertBase,
id: '2',
@@ -35,7 +40,7 @@ describe('filterAlerts', () => {
status: 'firing',
environment: 'staging',
},
} as GettableAlert,
} as RuletypesGettableRuleDTO,
{
...mockAlertBase,
id: '3',
@@ -46,7 +51,7 @@ describe('filterAlerts', () => {
status: 'pending',
environment: 'production',
},
} as GettableAlert,
} as RuletypesGettableRuleDTO,
];
it('should return all alerts when filter is empty', () => {
@@ -97,14 +102,14 @@ describe('filterAlerts', () => {
});
it('should handle alerts with missing labels', () => {
const alertsWithMissingLabels: GettableAlert[] = [
const alertsWithMissingLabels: RuletypesGettableRuleDTO[] = [
{
...mockAlertBase,
id: '4',
alert: 'Test Alert',
alertType: 'metrics',
labels: undefined,
} as GettableAlert,
} as RuletypesGettableRuleDTO,
];
const result = filterAlerts(alertsWithMissingLabels, 'test');
expect(result).toHaveLength(1);
@@ -112,7 +117,7 @@ describe('filterAlerts', () => {
});
it('should handle alerts with missing alert name', () => {
const alertsWithMissingName: GettableAlert[] = [
const alertsWithMissingName: RuletypesGettableRuleDTO[] = [
{
...mockAlertBase,
id: '5',
@@ -121,7 +126,7 @@ describe('filterAlerts', () => {
labels: {
severity: 'warning',
},
} as GettableAlert,
} as RuletypesGettableRuleDTO,
];
const result = filterAlerts(alertsWithMissingName, 'warning');
expect(result).toHaveLength(1);

View File

@@ -1,78 +1,66 @@
import { useEffect, useRef } from 'react';
import { useEffect, useMemo, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { useQuery } from 'react-query';
import { Space } from 'antd';
import getAll from 'api/alerts/getAll';
import logEvent from 'api/common/logEvent';
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
import { useListRules } from 'api/generated/services/rules';
import { RenderErrorResponseDTO } from 'api/generated/services/sigNoz.schemas';
import { AxiosError } from 'axios';
import Spinner from 'components/Spinner';
import { useNotifications } from 'hooks/useNotifications';
import { isUndefined } from 'lodash-es';
import { AlertsEmptyState } from './AlertsEmptyState/AlertsEmptyState';
import ListAlert from './ListAlert';
function ListAlertRules(): JSX.Element {
const { t } = useTranslation('common');
const { data, isError, isLoading, refetch, status } = useQuery('allAlerts', {
queryFn: getAll,
cacheTime: 0,
const { data, isError, isLoading, refetch, error } = useListRules({
query: { cacheTime: 0 },
});
const rules = data?.data?.rules ?? [];
const hasLoaded = !isLoading && data !== undefined;
const logEventCalledRef = useRef(false);
const { notifications } = useNotifications();
const apiError = useMemo(
() => convertToApiError(error as AxiosError<RenderErrorResponseDTO> | null),
[error],
);
useEffect(() => {
if (!logEventCalledRef.current && !isUndefined(data?.payload)) {
if (!logEventCalledRef.current && hasLoaded) {
logEvent('Alert: List page visited', {
number: data?.payload?.length,
number: rules.length,
});
logEventCalledRef.current = true;
}
}, [data?.payload]);
}, [hasLoaded, rules.length]);
useEffect(() => {
if (status === 'error' || (status === 'success' && data.statusCode >= 400)) {
if (isError) {
notifications.error({
message: data?.error || t('something_went_wrong'),
message: apiError?.getErrorMessage() || t('something_went_wrong'),
});
}
}, [data?.error, data?.statusCode, status, t, notifications]);
}, [isError, apiError, t, notifications]);
// api failed to load the data
if (isError) {
return <div>{data?.error || t('something_went_wrong')}</div>;
return <div>{apiError?.getErrorMessage() || t('something_went_wrong')}</div>;
}
// api is successful but error is present
if (status === 'success' && data.statusCode >= 400) {
return (
<ListAlert
{...{
allAlertRules: [],
refetch,
}}
/>
);
}
if (status === 'success' && !data.payload?.length) {
return <AlertsEmptyState />;
}
// in case of loading
if (isLoading || !data?.payload) {
if (isLoading || !data) {
return <Spinner height="75vh" tip="Loading Rules..." />;
}
if (rules.length === 0) {
return <AlertsEmptyState />;
}
return (
<Space direction="vertical" size="large" style={{ width: '100%' }}>
<ListAlert
{...{
allAlertRules: data.payload,
refetch,
}}
/>
<ListAlert allAlertRules={rules} refetch={refetch} />
</Space>
);
}

View File

@@ -1,12 +1,12 @@
import logEvent from 'api/common/logEvent';
import type { RuletypesGettableRuleDTO } from 'api/generated/services/sigNoz.schemas';
import { ALERTS_DATA_SOURCE_MAP } from 'constants/alerts';
import { AlertTypes } from 'types/api/alerts/alertTypes';
import { GettableAlert } from 'types/api/alerts/get';
export const filterAlerts = (
allAlertRules: GettableAlert[],
allAlertRules: RuletypesGettableRuleDTO[],
filter: string,
): GettableAlert[] => {
): RuletypesGettableRuleDTO[] => {
if (!filter.trim()) {
return allAlertRules;
}
@@ -32,7 +32,7 @@ export const filterAlerts = (
export const alertActionLogEvent = (
action: string,
record: GettableAlert,
record: RuletypesGettableRuleDTO,
): void => {
let actionValue = '';
switch (action) {

View File

@@ -2,10 +2,8 @@ import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Button, Input, Modal, Typography } from 'antd';
import logEvent from 'api/common/logEvent';
import {
updateMyPassword,
useUpdateMyUserV2,
} from 'api/generated/services/users';
import { useUpdateMyUserV2 } from 'api/generated/services/users';
import changeMyPassword from 'api/v1/factor_password/changeMyPassword';
import { useNotifications } from 'hooks/useNotifications';
import { Check, FileTerminal, MailIcon, UserIcon } from 'lucide-react';
import { useAppContext } from 'providers/App/App';
@@ -55,9 +53,10 @@ function UserInfo(): JSX.Element {
try {
setIsLoading(true);
await updateMyPassword({
await changeMyPassword({
newPassword: updatePassword,
oldPassword: currentPassword,
userId: user.id,
});
notifications.success({
message: t('success', {

View File

@@ -1,21 +1,21 @@
import React, { ChangeEvent, useEffect, useState } from 'react';
import { useQuery } from 'react-query';
import { useHistory } from 'react-router-dom';
import { PlusOutlined } from '@ant-design/icons';
import { Color } from '@signozhq/design-tokens';
import { Button, Flex, Form, Input, Tooltip, Typography } from 'antd';
import getAll from 'api/alerts/getAll';
import { useDeleteDowntimeSchedule } from 'api/plannedDowntime/deleteDowntimeSchedule';
import {
DowntimeSchedules,
useGetAllDowntimeSchedules,
} from 'api/plannedDowntime/getAllDowntimeSchedules';
useDeleteDowntimeScheduleByID,
useListDowntimeSchedules,
} from 'api/generated/services/downtimeschedules';
import { useListRules } from 'api/generated/services/rules';
import type { RuletypesGettablePlannedMaintenanceDTO } from 'api/generated/services/sigNoz.schemas';
import dayjs from 'dayjs';
import useDebouncedFn from 'hooks/useDebouncedFunction';
import { useNotifications } from 'hooks/useNotifications';
import useUrlQuery from 'hooks/useUrlQuery';
import { Search } from 'lucide-react';
import { useAppContext } from 'providers/App/App';
import { useErrorModal } from 'providers/ErrorModalProvider';
import { USER_ROLES } from 'types/roles';
import 'dayjs/locale/en';
@@ -33,28 +33,28 @@ import './PlannedDowntime.styles.scss';
dayjs.locale('en');
export function PlannedDowntime(): JSX.Element {
const { data, isError, isLoading } = useQuery('allAlerts', {
queryFn: getAll,
cacheTime: 0,
const { data: alertsData, isError, isLoading } = useListRules({
query: { cacheTime: 0 },
});
const [isOpen, setIsOpen] = React.useState(false);
const [form] = Form.useForm();
const { user } = useAppContext();
const { showErrorModal } = useErrorModal();
const history = useHistory();
const urlQuery = useUrlQuery();
const [initialValues, setInitialValues] = useState<
Partial<DowntimeSchedules & { editMode: boolean }>
Partial<RuletypesGettablePlannedMaintenanceDTO & { editMode: boolean }>
>(defautlInitialValues);
const downtimeSchedules = useGetAllDowntimeSchedules();
const downtimeSchedules = useListDowntimeSchedules();
const alertOptions = React.useMemo(
() =>
data?.payload?.map((i) => ({
alertsData?.data?.rules?.map((i: any) => ({
label: i.alert,
value: i.id,
})),
[data],
[alertsData],
);
useEffect(() => {
@@ -66,7 +66,7 @@ export function PlannedDowntime(): JSX.Element {
const [searchValue, setSearchValue] = React.useState<string | number>(
urlQuery.get('search') || '',
);
const [deleteData, setDeleteData] = useState<{ id: number; name: string }>();
const [deleteData, setDeleteData] = useState<{ id: string; name: string }>();
const [isEditMode, setEditMode] = useState<boolean>(false);
const updateUrlWithSearch = useDebouncedFn((value) => {
@@ -105,12 +105,13 @@ export function PlannedDowntime(): JSX.Element {
const {
mutateAsync: deleteDowntimeScheduleAsync,
isLoading: isDeleteLoading,
} = useDeleteDowntimeSchedule({ id: deleteData?.id });
} = useDeleteDowntimeScheduleByID();
const onDeleteHandler = (): void => {
deleteDowntimeHandler({
deleteDowntimeScheduleAsync,
notifications,
showErrorModal,
refetchAllSchedules,
deleteId: deleteData?.id,
hideDeleteDowntimeScheduleModal,

View File

@@ -14,11 +14,17 @@ import {
Typography,
} from 'antd';
import type { DefaultOptionType } from 'antd/es/select';
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
import {
DowntimeSchedules,
Recurrence,
} from 'api/plannedDowntime/getAllDowntimeSchedules';
import { DowntimeScheduleUpdatePayload } from 'api/plannedDowntime/updateDowntimeSchedule';
createDowntimeSchedule,
updateDowntimeScheduleByID,
} from 'api/generated/services/downtimeschedules';
import type {
RuletypesGettablePlannedMaintenanceDTO,
RuletypesRecurrenceDTO,
} from 'api/generated/services/sigNoz.schemas';
import { RenderErrorResponseDTO } from 'api/generated/services/sigNoz.schemas';
import { AxiosError } from 'axios';
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
import {
ModalButtonWrapper,
@@ -29,15 +35,14 @@ import timezone from 'dayjs/plugin/timezone';
import utc from 'dayjs/plugin/utc';
import { useNotifications } from 'hooks/useNotifications';
import { defaultTo, isEmpty } from 'lodash-es';
import { useErrorModal } from 'providers/ErrorModalProvider';
import APIError from 'types/api/error';
import { ALL_TIME_ZONES } from 'utils/timeZoneUtil';
import 'dayjs/locale/en';
import { SOMETHING_WENT_WRONG } from '../../constants/api';
import { showErrorNotification } from '../../utils/error';
import { AlertRuleTags } from './PlannedDowntimeList';
import {
createEditDowntimeSchedule,
getAlertOptionsFromIds,
getDurationInfo,
getEndTime,
@@ -62,9 +67,9 @@ interface PlannedDowntimeFormData {
name: string;
startTime: dayjs.Dayjs | string;
endTime: dayjs.Dayjs | string;
recurrence?: Recurrence | null;
recurrence?: RuletypesRecurrenceDTO | null;
alertRules: DefaultOptionType[];
recurrenceSelect?: Recurrence;
recurrenceSelect?: RuletypesRecurrenceDTO;
timezone?: string;
}
@@ -72,7 +77,7 @@ const customFormat = DATE_TIME_FORMATS.ORDINAL_DATETIME;
interface PlannedDowntimeFormProps {
initialValues: Partial<
DowntimeSchedules & {
RuletypesGettablePlannedMaintenanceDTO & {
editMode: boolean;
}
>;
@@ -112,7 +117,7 @@ export function PlannedDowntimeForm(
);
const [formData, setFormData] = useState<PlannedDowntimeFormData>(
initialValues?.schedule as PlannedDowntimeFormData,
(initialValues?.schedule as unknown) as PlannedDowntimeFormData,
);
const [recurrenceType, setRecurrenceType] = useState<string | null>(
@@ -125,6 +130,7 @@ export function PlannedDowntimeForm(
: undefined;
const { notifications } = useNotifications();
const { showErrorModal } = useErrorModal();
const datePickerFooter = (mode: any): any =>
mode === 'time' ? (
@@ -134,57 +140,54 @@ export function PlannedDowntimeForm(
const saveHanlder = useCallback(
async (values: PlannedDowntimeFormData) => {
const shouldKeepLocalTime = !isEditMode;
const createEditProps: DowntimeScheduleUpdatePayload = {
data: {
alertIds: values.alertRules
.map((alert) => alert.value)
.filter((alert) => alert !== undefined) as string[],
name: values.name,
schedule: {
startTime: handleTimeConversion(
const data: RuletypesGettablePlannedMaintenanceDTO = {
alertIds: values.alertRules
.map((alert) => alert.value)
.filter((alert) => alert !== undefined) as string[],
name: values.name,
schedule: {
startTime: new Date(
handleTimeConversion(
values.startTime,
timezoneInitialValue,
values.timezone,
shouldKeepLocalTime,
),
timezone: values.timezone,
endTime: values.endTime
? handleTimeConversion(
),
timezone: values.timezone,
endTime: values.endTime
? new Date(
handleTimeConversion(
values.endTime,
timezoneInitialValue,
values.timezone,
shouldKeepLocalTime,
)
: undefined,
recurrence: values.recurrence as Recurrence,
},
),
)
: undefined,
recurrence: values.recurrence as RuletypesRecurrenceDTO,
},
id: isEditMode ? initialValues.id : undefined,
};
setSaveLoading(true);
try {
const response = await createEditDowntimeSchedule({ ...createEditProps });
if (response.message === 'success') {
setIsOpen(false);
notifications.success({
message: 'Success',
description: isEditMode
? 'Schedule updated successfully'
: 'Schedule created successfully',
});
refetchAllSchedules();
if (isEditMode && initialValues.id) {
await updateDowntimeScheduleByID({ id: initialValues.id }, data);
} else {
notifications.error({
message: 'Error',
description:
typeof response.error === 'string'
? response.error
: response.error?.message || SOMETHING_WENT_WRONG,
});
await createDowntimeSchedule(data);
}
setIsOpen(false);
notifications.success({
message: 'Success',
description: isEditMode
? 'Schedule updated successfully'
: 'Schedule created successfully',
});
refetchAllSchedules();
} catch (e: unknown) {
showErrorNotification(notifications, e as Error);
showErrorModal(
convertToApiError(e as AxiosError<RenderErrorResponseDTO>) as APIError,
);
}
setSaveLoading(false);
},
@@ -195,10 +198,11 @@ export function PlannedDowntimeForm(
refetchAllSchedules,
setIsOpen,
timezoneInitialValue,
showErrorModal,
],
);
const onFinish = async (values: PlannedDowntimeFormData): Promise<void> => {
const recurrenceData: Recurrence | undefined =
const recurrenceData =
values?.recurrence?.repeatType === recurrenceOptions.doesNotRepeat.value
? undefined
: {
@@ -225,7 +229,10 @@ export function PlannedDowntimeForm(
repeatType: values.recurrence?.repeatType,
};
const payloadValues = { ...values, recurrence: recurrenceData };
const payloadValues = {
...values,
recurrence: recurrenceData as RuletypesRecurrenceDTO | undefined,
};
await saveHanlder(payloadValues);
};
@@ -280,13 +287,14 @@ export function PlannedDowntimeForm(
? dayjs(initialValues.schedule?.startTime)
: '',
recurrence: {
...initialValues.schedule?.recurrence,
...(initialValues.schedule?.recurrence as Record<string, unknown>),
repeatType: !isScheduleRecurring(initialValues?.schedule)
? recurrenceOptions.doesNotRepeat.value
: (initialValues.schedule?.recurrence?.repeatType as string),
duration: getDurationInfo(
initialValues.schedule?.recurrence?.duration as string,
)?.value,
duration: String(
getDurationInfo(initialValues.schedule?.recurrence?.duration as string)
?.value ?? '',
),
},
timezone: initialValues.schedule?.timezone as string,
};
@@ -325,7 +333,12 @@ export function PlannedDowntimeForm(
const startTimeText = useMemo((): string => {
let startTime = formData?.startTime;
if (recurrenceType !== recurrenceOptions.doesNotRepeat.value) {
startTime = formData?.recurrence?.startTime || formData?.startTime || '';
startTime =
(formData?.recurrence?.startTime
? dayjs(formData.recurrence.startTime).toISOString()
: '') ||
formData?.startTime ||
'';
}
if (!startTime) {
@@ -381,7 +394,10 @@ export function PlannedDowntimeForm(
const endTimeText = useMemo((): string => {
let endTime = formData?.endTime;
if (recurrenceType !== recurrenceOptions.doesNotRepeat.value) {
endTime = formData?.recurrence?.endTime || '';
endTime =
(formData?.recurrence?.endTime
? dayjs(formData.recurrence.endTime).toISOString()
: '') || '';
if (!isEditMode && !endTime) {
endTime = formData?.endTime || '';

View File

@@ -12,13 +12,15 @@ import {
Typography,
} from 'antd';
import type { DefaultOptionType } from 'antd/es/select';
import {
DowntimeSchedules,
PayloadProps,
Recurrence,
} from 'api/plannedDowntime/getAllDowntimeSchedules';
import { AxiosError, AxiosResponse } from 'axios';
import type {
ListDowntimeSchedules200,
RenderErrorResponseDTO,
RuletypesGettablePlannedMaintenanceDTO,
RuletypesRecurrenceDTO,
} from 'api/generated/services/sigNoz.schemas';
import type { ErrorType } from 'api/generatedAPIInstance';
import cx from 'classnames';
import dayjs from 'dayjs';
import { useNotifications } from 'hooks/useNotifications';
import { defaultTo } from 'lodash-es';
import { CalendarClock, PenLine, Trash2 } from 'lucide-react';
@@ -143,7 +145,7 @@ export function CollapseListContent({
created_by_name?: string;
created_by_email?: string;
timeframe: [string | undefined | null, string | undefined | null];
repeats?: Recurrence | null;
repeats?: RuletypesRecurrenceDTO | null;
updated_at?: string;
updated_by_name?: string;
alertOptions?: DefaultOptionType[];
@@ -218,10 +220,10 @@ export function CollapseListContent({
export function CustomCollapseList(
props: DowntimeSchedulesTableData & {
setInitialValues: React.Dispatch<
React.SetStateAction<Partial<DowntimeSchedules>>
React.SetStateAction<Partial<RuletypesGettablePlannedMaintenanceDTO>>
>;
setModalOpen: React.Dispatch<React.SetStateAction<boolean>>;
handleDeleteDowntime: (id: number, name: string) => void;
handleDeleteDowntime: (id: string, name: string) => void;
setEditMode: React.Dispatch<React.SetStateAction<boolean>>;
},
): JSX.Element {
@@ -241,12 +243,19 @@ export function CustomCollapseList(
kind,
} = props;
const scheduleTime = schedule?.startTime ? schedule.startTime : createdAt;
const scheduleTime = schedule?.startTime
? dayjs(schedule.startTime).toISOString()
: createdAt
? dayjs(createdAt).toISOString()
: '';
// Combine time and date
const formattedDateAndTime = `Start time ⎯ ${formatDateTime(
defaultTo(scheduleTime, ''),
)} ${schedule?.timezone}`;
const endTime = getEndTime({ kind, schedule });
const endTime = getEndTime({
kind,
schedule,
} as Partial<RuletypesGettablePlannedMaintenanceDTO>);
return (
<>
@@ -257,7 +266,10 @@ export function CustomCollapseList(
duration={
schedule?.recurrence?.duration
? (schedule?.recurrence?.duration as string)
: getDuration(schedule?.startTime, schedule?.endTime)
: getDuration(
schedule?.startTime ? dayjs(schedule.startTime).toISOString() : '',
schedule?.endTime ? dayjs(schedule.endTime).toISOString() : '',
)
}
name={defaultTo(name, '')}
handleEdit={(): void => {
@@ -266,21 +278,23 @@ export function CustomCollapseList(
setEditMode(true);
}}
handleDelete={(): void => {
handleDeleteDowntime(id, name || '');
handleDeleteDowntime(id ?? '', name || '');
}}
/>
}
key={id}
key={id ?? ''}
>
<CollapseListContent
created_at={defaultTo(createdAt, '')}
created_at={createdAt ? dayjs(createdAt).toISOString() : ''}
created_by_name={defaultTo(createdBy, '')}
timeframe={[
schedule?.startTime?.toString(),
typeof endTime === 'string' ? endTime : endTime?.toString(),
]}
repeats={schedule?.recurrence}
updated_at={defaultTo(updatedAt, '')}
repeats={
schedule?.recurrence as RuletypesRecurrenceDTO | null | undefined
}
updated_at={updatedAt ? dayjs(updatedAt).toISOString() : ''}
updated_by_name={defaultTo(updatedBy, '')}
alertOptions={alertOptions}
timezone={defaultTo(schedule?.timezone, '')}
@@ -295,7 +309,7 @@ export function CustomCollapseList(
);
}
export type DowntimeSchedulesTableData = DowntimeSchedules & {
export type DowntimeSchedulesTableData = RuletypesGettablePlannedMaintenanceDTO & {
alertOptions: DefaultOptionType[];
};
@@ -309,15 +323,15 @@ export function PlannedDowntimeList({
searchValue,
}: {
downtimeSchedules: UseQueryResult<
AxiosResponse<PayloadProps, any>,
AxiosError<unknown, any>
ListDowntimeSchedules200,
ErrorType<RenderErrorResponseDTO>
>;
alertOptions: DefaultOptionType[];
setInitialValues: React.Dispatch<
React.SetStateAction<Partial<DowntimeSchedules>>
React.SetStateAction<Partial<RuletypesGettablePlannedMaintenanceDTO>>
>;
setModalOpen: React.Dispatch<React.SetStateAction<boolean>>;
handleDeleteDowntime: (id: number, name: string) => void;
handleDeleteDowntime: (id: string, name: string) => void;
setEditMode: React.Dispatch<React.SetStateAction<boolean>>;
searchValue: string | number;
}): JSX.Element {
@@ -337,17 +351,17 @@ export function PlannedDowntimeList({
];
const { notifications } = useNotifications();
const tableData = (downtimeSchedules.data?.data?.data || [])
const tableData = [...(downtimeSchedules.data?.data || [])]
.sort((a, b): number => {
if (a?.updatedAt && b?.updatedAt) {
return b.updatedAt.localeCompare(a.updatedAt);
return dayjs(b.updatedAt).diff(dayjs(a.updatedAt));
}
return 0;
})
?.filter(
(data) =>
data?.name?.includes(searchValue.toLocaleString()) ||
data?.id.toLocaleString() === searchValue.toLocaleString(),
String(data?.id ?? '') === searchValue.toLocaleString(),
)
.map?.((data) => {
const specificAlertOptions = getAlertOptionsFromIds(

View File

@@ -1,21 +1,19 @@
import { UseMutateAsyncFunction } from 'react-query';
import type { NotificationInstance } from 'antd/es/notification/interface';
import type { DefaultOptionType } from 'antd/es/select';
import createDowntimeSchedule from 'api/plannedDowntime/createDowntimeSchedule';
import { DeleteSchedulePayloadProps } from 'api/plannedDowntime/deleteDowntimeSchedule';
import {
DowntimeSchedules,
Recurrence,
} from 'api/plannedDowntime/getAllDowntimeSchedules';
import updateDowntimeSchedule, {
DowntimeScheduleUpdatePayload,
PayloadProps,
} from 'api/plannedDowntime/updateDowntimeSchedule';
import { showErrorNotification } from 'components/ExplorerCard/utils';
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
import type {
DeleteDowntimeScheduleByIDPathParameters,
RenderErrorResponseDTO,
RuletypesGettablePlannedMaintenanceDTO,
RuletypesRecurrenceDTO,
} from 'api/generated/services/sigNoz.schemas';
import type { ErrorType } from 'api/generatedAPIInstance';
import { AxiosError } from 'axios';
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
import dayjs from 'dayjs';
import { isEmpty, isEqual } from 'lodash-es';
import { ErrorResponse, SuccessResponse } from 'types/api';
import APIError from 'types/api/error';
type DateTimeString = string | null | undefined;
@@ -61,47 +59,54 @@ export const getAlertOptionsFromIds = (
alertIds?.includes(alert.value as string),
);
export const recurrenceInfo = (recurrence?: Recurrence | null): string => {
export const recurrenceInfo = (
recurrence?: RuletypesRecurrenceDTO | null,
): string => {
if (!recurrence) {
return 'No';
}
const { startTime, duration, repeatOn, repeatType, endTime } = recurrence;
const formattedStartTime = startTime ? formatDateTime(startTime) : '';
const formattedEndTime = endTime ? `to ${formatDateTime(endTime)}` : '';
const formattedStartTime = startTime
? formatDateTime(dayjs(startTime).toISOString())
: '';
const formattedEndTime = endTime
? `to ${formatDateTime(dayjs(endTime).toISOString())}`
: '';
const weeklyRepeatString = repeatOn ? `on ${repeatOn.join(', ')}` : '';
const durationString = duration ? `- Duration: ${duration}` : '';
return `Repeats - ${repeatType} ${weeklyRepeatString} from ${formattedStartTime} ${formattedEndTime} ${durationString}`;
};
export const defautlInitialValues: Partial<
DowntimeSchedules & { editMode: boolean }
> = {
export const defautlInitialValues = ({
name: '',
description: '',
schedule: {
timezone: '',
endTime: '',
recurrence: null,
recurrence: undefined,
startTime: '',
},
alertIds: [],
alertIds: [] as string[],
createdAt: '',
createdBy: '',
editMode: false,
};
} as unknown) as Partial<
RuletypesGettablePlannedMaintenanceDTO & { editMode: boolean }
>;
type DeleteDowntimeScheduleProps = {
deleteDowntimeScheduleAsync: UseMutateAsyncFunction<
DeleteSchedulePayloadProps,
Error,
number
void,
ErrorType<RenderErrorResponseDTO>,
{ pathParams: DeleteDowntimeScheduleByIDPathParameters }
>;
notifications: NotificationInstance;
showErrorModal: (error: APIError) => void;
refetchAllSchedules: VoidFunction;
deleteId?: number;
deleteId?: string;
hideDeleteDowntimeScheduleModal: () => void;
clearSearch: () => void;
};
@@ -113,40 +118,33 @@ export const deleteDowntimeHandler = ({
hideDeleteDowntimeScheduleModal,
clearSearch,
notifications,
showErrorModal,
}: DeleteDowntimeScheduleProps): void => {
if (!deleteId) {
const errorMsg = new Error('Something went wrong');
console.error('Unable to delete, please provide correct deleteId');
showErrorNotification(notifications, errorMsg);
notifications.error({ message: 'Something went wrong' });
} else {
deleteDowntimeScheduleAsync(deleteId, {
onSuccess: () => {
hideDeleteDowntimeScheduleModal();
clearSearch();
notifications.success({
message: 'Downtime schedule Deleted Successfully',
});
refetchAllSchedules();
deleteDowntimeScheduleAsync(
{ pathParams: { id: String(deleteId) } },
{
onSuccess: () => {
hideDeleteDowntimeScheduleModal();
clearSearch();
notifications.success({
message: 'Downtime schedule Deleted Successfully',
});
refetchAllSchedules();
},
onError: (err) => {
showErrorModal(
convertToApiError(err as AxiosError<RenderErrorResponseDTO>) as APIError,
);
},
},
onError: (err) => {
showErrorNotification(notifications, err);
},
});
);
}
};
export const createEditDowntimeSchedule = async (
props: DowntimeScheduleUpdatePayload,
): Promise<
| SuccessResponse<PayloadProps>
| ErrorResponse<{ code: string; message: string } | string>
> => {
if (props.id) {
return updateDowntimeSchedule({ ...props });
}
return createDowntimeSchedule({ ...props.data });
};
export const recurrenceOptions = {
doesNotRepeat: {
label: 'Does not repeat',
@@ -230,19 +228,21 @@ export const getEndTime = ({
kind,
schedule,
}: Partial<
DowntimeSchedules & {
RuletypesGettablePlannedMaintenanceDTO & {
editMode: boolean;
}
>): string | dayjs.Dayjs => {
if (kind === 'fixed') {
return schedule?.endTime || '';
return schedule?.endTime ? dayjs(schedule.endTime).toISOString() : '';
}
return schedule?.recurrence?.endTime || '';
return schedule?.recurrence?.endTime
? dayjs(schedule.recurrence.endTime).toISOString()
: '';
};
export const isScheduleRecurring = (
schedule?: DowntimeSchedules['schedule'],
schedule?: RuletypesGettablePlannedMaintenanceDTO['schedule'] | null,
): boolean => (schedule ? !isEmpty(schedule?.recurrence) : false);
function convertUtcOffsetToTimezoneOffset(offsetMinutes: number): string {

View File

@@ -1,7 +1,10 @@
import { UseQueryResult } from 'react-query';
import { fireEvent, screen } from '@testing-library/react';
import { PayloadProps } from 'api/plannedDowntime/getAllDowntimeSchedules';
import { AxiosError, AxiosResponse } from 'axios';
import type {
ListDowntimeSchedules200,
RenderErrorResponseDTO,
} from 'api/generated/services/sigNoz.schemas';
import type { ErrorType } from 'api/generatedAPIInstance';
import {
mockLocation,
mockQueryParams,
@@ -22,45 +25,53 @@ const MOCK_DATE_2 = '2024-01-02';
const MOCK_DATE_3 = '2024-01-03';
const MOCK_DOWNTIME_1 = createMockDowntime({
id: 1,
id: '1',
name: MOCK_DOWNTIME_1_NAME,
createdAt: MOCK_DATE_1,
updatedAt: MOCK_DATE_1,
schedule: buildSchedule({ startTime: MOCK_DATE_1, timezone: 'UTC' }),
createdAt: new Date(MOCK_DATE_1),
updatedAt: new Date(MOCK_DATE_1),
schedule: buildSchedule({
startTime: new Date(MOCK_DATE_1),
timezone: 'UTC',
}),
alertIds: [],
});
const MOCK_DOWNTIME_2 = createMockDowntime({
id: 2,
id: '2',
name: MOCK_DOWNTIME_2_NAME,
createdAt: MOCK_DATE_2,
updatedAt: MOCK_DATE_2,
schedule: buildSchedule({ startTime: MOCK_DATE_2, timezone: 'UTC' }),
createdAt: new Date(MOCK_DATE_2),
updatedAt: new Date(MOCK_DATE_2),
schedule: buildSchedule({
startTime: new Date(MOCK_DATE_2),
timezone: 'UTC',
}),
alertIds: [],
});
const MOCK_DOWNTIME_3 = createMockDowntime({
id: 3,
id: '3',
name: MOCK_DOWNTIME_3_NAME,
createdAt: MOCK_DATE_3,
updatedAt: MOCK_DATE_3,
schedule: buildSchedule({ startTime: MOCK_DATE_3, timezone: 'UTC' }),
createdAt: new Date(MOCK_DATE_3),
updatedAt: new Date(MOCK_DATE_3),
schedule: buildSchedule({
startTime: new Date(MOCK_DATE_3),
timezone: 'UTC',
}),
alertIds: [],
});
const MOCK_DOWNTIME_RESPONSE: Partial<AxiosResponse<PayloadProps>> = {
data: {
data: [MOCK_DOWNTIME_1, MOCK_DOWNTIME_2, MOCK_DOWNTIME_3],
},
const MOCK_DOWNTIME_RESPONSE: ListDowntimeSchedules200 = {
data: [MOCK_DOWNTIME_1, MOCK_DOWNTIME_2, MOCK_DOWNTIME_3],
status: 'success',
};
type DowntimeQueryResult = UseQueryResult<
AxiosResponse<PayloadProps>,
AxiosError
ListDowntimeSchedules200,
ErrorType<RenderErrorResponseDTO>
>;
const mockDowntimeQueryResult: Partial<DowntimeQueryResult> = {
data: MOCK_DOWNTIME_RESPONSE as AxiosResponse<PayloadProps>,
data: MOCK_DOWNTIME_RESPONSE,
isLoading: false,
isFetching: false,
isError: false,
@@ -89,13 +100,27 @@ jest.mock('hooks/useSafeNavigate', () => ({
}),
}));
jest.mock('api/plannedDowntime/getAllDowntimeSchedules', () => ({
useGetAllDowntimeSchedules: (): DowntimeQueryResult =>
jest.mock('api/generated/services/downtimeschedules', () => ({
useListDowntimeSchedules: (): DowntimeQueryResult =>
mockDowntimeQueryResult as DowntimeQueryResult,
useDeleteDowntimeScheduleByID: (): {
mutateAsync: jest.Mock;
isLoading: false;
} => ({
mutateAsync: jest.fn(),
isLoading: false,
}),
}));
jest.mock('api/alerts/getAll', () => ({
__esModule: true,
default: (): Promise<{ payload: [] }> => Promise.resolve({ payload: [] }),
jest.mock('api/generated/services/rules', () => ({
useListRules: (): {
data: { data: { rules: [] } };
isError: false;
isLoading: false;
} => ({
data: { data: { rules: [] } },
isError: false,
isLoading: false,
}),
}));
describe('PlannedDowntime Component', () => {

View File

@@ -1,29 +1,32 @@
import { DowntimeSchedules } from 'api/plannedDowntime/getAllDowntimeSchedules';
import type {
RuletypesGettablePlannedMaintenanceDTO,
RuletypesScheduleDTO,
} from 'api/generated/services/sigNoz.schemas';
export const buildSchedule = (
schedule: Partial<DowntimeSchedules['schedule']>,
): DowntimeSchedules['schedule'] => ({
timezone: schedule?.timezone ?? null,
startTime: schedule?.startTime ?? null,
endTime: schedule?.endTime ?? null,
recurrence: schedule?.recurrence ?? null,
schedule: Partial<RuletypesScheduleDTO>,
): RuletypesScheduleDTO => ({
timezone: schedule?.timezone ?? '',
startTime: schedule?.startTime,
endTime: schedule?.endTime,
recurrence: schedule?.recurrence,
});
export const createMockDowntime = (
overrides: Partial<DowntimeSchedules>,
): DowntimeSchedules => ({
id: overrides.id ?? 0,
name: overrides.name ?? null,
description: overrides.description ?? null,
overrides: Partial<RuletypesGettablePlannedMaintenanceDTO>,
): RuletypesGettablePlannedMaintenanceDTO => ({
id: overrides.id ?? '0',
name: overrides.name ?? '',
description: overrides.description ?? '',
schedule: buildSchedule({
timezone: 'UTC',
startTime: '2024-01-01',
startTime: new Date('2024-01-01'),
...overrides.schedule,
}),
alertIds: overrides.alertIds ?? null,
createdAt: overrides.createdAt ?? null,
createdBy: overrides.createdBy ?? null,
updatedAt: overrides.updatedAt ?? null,
updatedBy: overrides.updatedBy ?? null,
kind: overrides.kind ?? null,
alertIds: overrides.alertIds ?? [],
createdAt: overrides.createdAt,
createdBy: overrides.createdBy ?? '',
updatedAt: overrides.updatedAt,
updatedBy: overrides.updatedBy ?? '',
kind: overrides.kind ?? '',
});

View File

@@ -1,20 +0,0 @@
import { useMutation, UseMutationResult } from 'react-query';
import createAlertRule, {
CreateAlertRuleResponse,
} from 'api/alerts/createAlertRule';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { PostableAlertRuleV2 } from 'types/api/alerts/alertTypesV2';
export function useCreateAlertRule(): UseMutationResult<
SuccessResponse<CreateAlertRuleResponse> | ErrorResponse,
Error,
PostableAlertRuleV2
> {
return useMutation<
SuccessResponse<CreateAlertRuleResponse> | ErrorResponse,
Error,
PostableAlertRuleV2
>({
mutationFn: (alertData) => createAlertRule(alertData),
});
}

View File

@@ -1,18 +0,0 @@
import { useMutation, UseMutationResult } from 'react-query';
import testAlertRule, { TestAlertRuleResponse } from 'api/alerts/testAlertRule';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { PostableAlertRuleV2 } from 'types/api/alerts/alertTypesV2';
export function useTestAlertRule(): UseMutationResult<
SuccessResponse<TestAlertRuleResponse> | ErrorResponse,
Error,
PostableAlertRuleV2
> {
return useMutation<
SuccessResponse<TestAlertRuleResponse> | ErrorResponse,
Error,
PostableAlertRuleV2
>({
mutationFn: (alertData) => testAlertRule(alertData),
});
}

View File

@@ -1,22 +0,0 @@
import { useMutation, UseMutationResult } from 'react-query';
import updateAlertRule, {
UpdateAlertRuleResponse,
} from 'api/alerts/updateAlertRule';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { PostableAlertRuleV2 } from 'types/api/alerts/alertTypesV2';
export function useUpdateAlertRule(
id: string,
): UseMutationResult<
SuccessResponse<UpdateAlertRuleResponse> | ErrorResponse,
Error,
PostableAlertRuleV2
> {
return useMutation<
SuccessResponse<UpdateAlertRuleResponse> | ErrorResponse,
Error,
PostableAlertRuleV2
>({
mutationFn: (alertData) => updateAlertRule(id, alertData),
});
}

View File

@@ -191,6 +191,17 @@ export const handlers = [
}),
),
),
rest.post('http://localhost/api/v1/changePassword', (_, res, ctx) =>
res(
ctx.status(403),
ctx.json({
status: 'error',
errorType: 'forbidden',
error: 'invalid credentials',
}),
),
),
rest.get(
'http://localhost/api/v3/autocomplete/aggregate_attributes',
(req, res, ctx) =>

View File

@@ -99,7 +99,7 @@ function AlertDetails(): JSX.Element {
}, [params]);
const getDocumentTitle = useMemo(() => {
const alertTitle = alertDetailsResponse?.payload?.data?.alert;
const alertTitle = alertDetailsResponse?.data?.alert;
if (alertTitle) {
return alertTitle;
}
@@ -110,14 +110,15 @@ function AlertDetails(): JSX.Element {
return document.title;
}
return 'Alert Not Found';
}, [alertDetailsResponse?.payload?.data?.alert, isTestAlert, isLoading]);
}, [alertDetailsResponse?.data?.alert, isTestAlert, isLoading]);
useEffect(() => {
document.title = getDocumentTitle;
}, [getDocumentTitle]);
const alertRuleDetails = useMemo(
() => alertDetailsResponse?.payload?.data as PostableAlertRuleV2 | undefined,
() =>
(alertDetailsResponse?.data as unknown) as PostableAlertRuleV2 | undefined,
[alertDetailsResponse],
);
@@ -126,12 +127,7 @@ function AlertDetails(): JSX.Element {
[alertRuleDetails],
);
if (
isError ||
!isValidRuleId ||
(alertDetailsResponse && alertDetailsResponse.statusCode !== 200) ||
(!isLoading && !alertRuleDetails)
) {
if (isError || !isValidRuleId || (!isLoading && !alertRuleDetails)) {
return <AlertNotFound isTestAlert={isTestAlert} />;
}

View File

@@ -1,4 +1,5 @@
import { useMemo, useState } from 'react';
import type { RuletypesGettableRuleDTO } from 'api/generated/services/sigNoz.schemas';
import CreateAlertV2Header from 'container/CreateAlertV2/CreateAlertHeader';
import LineClampedText from 'periscope/components/LineClampedText/LineClampedText';
import { useAlertRule } from 'providers/Alert';
@@ -6,7 +7,6 @@ import {
NEW_ALERT_SCHEMA_VERSION,
PostableAlertRuleV2,
} from 'types/api/alerts/alertTypesV2';
import { GettableAlert } from 'types/api/alerts/get';
import AlertActionButtons from './ActionButtons/ActionButtons';
import AlertLabels from './AlertLabels/AlertLabels';
@@ -16,7 +16,7 @@ import AlertState from './AlertState/AlertState';
import './AlertHeader.styles.scss';
export type AlertHeaderProps = {
alertDetails: GettableAlert | PostableAlertRuleV2;
alertDetails: RuletypesGettableRuleDTO | PostableAlertRuleV2;
};
function AlertHeader({ alertDetails }: AlertHeaderProps): JSX.Element {
const { state, alert: alertName, labels } = alertDetails;

View File

@@ -3,15 +3,25 @@ import { useMutation, useQuery, useQueryClient } from 'react-query';
import { generatePath, useLocation } from 'react-router-dom';
import { TablePaginationConfig, TableProps } from 'antd';
import type { FilterValue, SorterResult } from 'antd/es/table/interface';
import deleteAlerts from 'api/alerts/delete';
import get from 'api/alerts/get';
import getAll from 'api/alerts/getAll';
import patchAlert from 'api/alerts/patch';
import ruleStats from 'api/alerts/ruleStats';
import save from 'api/alerts/save';
import timelineGraph from 'api/alerts/timelineGraph';
import timelineTable from 'api/alerts/timelineTable';
import topContributors from 'api/alerts/topContributors';
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
import {
createRule,
deleteRuleByID,
patchRuleByID,
updateRuleByID,
useGetRuleByID,
useListRules,
} from 'api/generated/services/rules';
import type {
GetRuleByID200,
RenderErrorResponseDTO,
RuletypesPostableRuleDTO,
} from 'api/generated/services/sigNoz.schemas';
import { AxiosError } from 'axios';
import { TabRoutes } from 'components/RouteTab/types';
import { QueryParams } from 'constants/query';
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
@@ -21,7 +31,6 @@ import { TIMELINE_TABLE_PAGE_SIZE } from 'container/AlertHistory/constants';
import { AlertDetailsTab, TimelineFilter } from 'container/AlertHistory/types';
import { urlKey } from 'container/AllError/utils';
import { DEFAULT_TIME_RANGE } from 'container/TopNav/DateTimeSelectionV2/constants';
import useAxiosError from 'hooks/useAxiosError';
import { useNotifications } from 'hooks/useNotifications';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import useUrlQuery from 'hooks/useUrlQuery';
@@ -34,6 +43,7 @@ import { OrderPreferenceItems } from 'pages/Logs/config';
import BetaTag from 'periscope/components/BetaTag/BetaTag';
import PaginationInfoText from 'periscope/components/PaginationInfoText/PaginationInfoText';
import { useAlertRule } from 'providers/Alert';
import { useErrorModal } from 'providers/ErrorModalProvider';
import { ErrorResponse, SuccessResponse } from 'types/api';
import {
AlertDef,
@@ -43,7 +53,7 @@ import {
AlertRuleTimelineTableResponsePayload,
AlertRuleTopContributorsPayload,
} from 'types/api/alerts/def';
import { PayloadProps } from 'types/api/alerts/get';
import APIError from 'types/api/error';
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
import { nanoToMilli } from 'utils/timeUtils';
@@ -142,10 +152,7 @@ export const useRouteTabUtils = (): { routes: TabRoutes[] } => {
type Props = {
ruleId: string | null;
isValidRuleId: boolean;
alertDetailsResponse:
| SuccessResponse<PayloadProps, unknown>
| ErrorResponse
| undefined;
alertDetailsResponse: GetRuleByID200 | undefined;
isLoading: boolean;
isRefetching: boolean;
isError: boolean;
@@ -161,14 +168,15 @@ export const useGetAlertRuleDetails = (): Props => {
data: alertDetailsResponse,
isRefetching,
isError,
} = useQuery([REACT_QUERY_KEY.ALERT_RULE_DETAILS, ruleId], {
queryFn: () =>
get({
id: ruleId || '',
}),
enabled: isValidRuleId,
refetchOnWindowFocus: false,
});
} = useGetRuleByID(
{ id: ruleId || '' },
{
query: {
enabled: isValidRuleId,
refetchOnWindowFocus: false,
},
},
);
return {
ruleId,
@@ -376,6 +384,15 @@ export const useTimelineTable = ({
return { paginationConfig, onChangeHandler };
};
const handleMutationError = (
showErrorModal: (error: APIError) => void,
error: unknown,
): void => {
showErrorModal(
convertToApiError(error as AxiosError<RenderErrorResponseDTO>) as APIError,
);
};
export const useAlertRuleStatusToggle = ({
ruleId,
}: {
@@ -387,24 +404,28 @@ export const useAlertRuleStatusToggle = ({
const { notifications } = useNotifications();
const queryClient = useQueryClient();
const handleError = useAxiosError();
const { showErrorModal } = useErrorModal();
const { mutate: toggleAlertState } = useMutation(
[REACT_QUERY_KEY.TOGGLE_ALERT_STATE, ruleId],
patchAlert,
(args: { id: string; data: Record<string, unknown> }) =>
patchRuleByID(
{ id: args.id },
(args.data as unknown) as RuletypesPostableRuleDTO,
),
{
onSuccess: (data) => {
setAlertRuleState(data?.payload?.state);
setAlertRuleState(data?.data?.state);
queryClient.refetchQueries([REACT_QUERY_KEY.ALERT_RULE_DETAILS, ruleId]);
notifications.success({
message: `Alert has been ${
data?.payload?.state === 'disabled' ? 'disabled' : 'enabled'
data?.data?.state === 'disabled' ? 'disabled' : 'enabled'
}.`,
});
},
onError: (error) => {
queryClient.refetchQueries([REACT_QUERY_KEY.ALERT_RULE_DETAILS, ruleId]);
handleError(error);
handleMutationError(showErrorModal, error);
},
},
);
@@ -431,14 +452,14 @@ export const useAlertRuleDuplicate = ({
const params = useUrlQuery();
const { refetch } = useQuery(REACT_QUERY_KEY.GET_ALL_ALLERTS, {
queryFn: getAll,
cacheTime: 0,
const { refetch } = useListRules({
query: { cacheTime: 0 },
});
const handleError = useAxiosError();
const { showErrorModal } = useErrorModal();
const { mutate: duplicateAlert } = useMutation(
[REACT_QUERY_KEY.DUPLICATE_ALERT_RULE],
save,
(args: { data: AlertDef }) =>
createRule((args.data as unknown) as RuletypesPostableRuleDTO),
{
onSuccess: async () => {
notifications.success({
@@ -447,18 +468,14 @@ export const useAlertRuleDuplicate = ({
const { data: allAlertsData } = await refetch();
if (
allAlertsData &&
allAlertsData.payload &&
allAlertsData.payload.length > 0
) {
const clonedAlert =
allAlertsData.payload[allAlertsData.payload.length - 1];
const rules = allAlertsData?.data?.rules;
if (rules && rules.length > 0) {
const clonedAlert = rules[rules.length - 1];
params.set(QueryParams.ruleId, String(clonedAlert.id));
history.push(`${ROUTES.ALERT_OVERVIEW}?${params.toString()}`);
}
},
onError: handleError,
onError: (error) => handleMutationError(showErrorModal, error),
},
);
@@ -484,18 +501,22 @@ export const useAlertRuleUpdate = ({
isLoading: boolean;
} => {
const { notifications } = useNotifications();
const handleError = useAxiosError();
const { showErrorModal } = useErrorModal();
const { mutate: updateAlertRule, isLoading } = useMutation(
[REACT_QUERY_KEY.UPDATE_ALERT_RULE, alertDetails.id],
save,
(args: { data: AlertDef; id: string }) =>
updateRuleByID(
{ id: args.id },
(args.data as unknown) as RuletypesPostableRuleDTO,
),
{
onMutate: () => setUpdatedName(intermediateName),
onSuccess: () =>
notifications.success({ message: 'Alert renamed successfully' }),
onError: (error) => {
setUpdatedName(alertDetails.alert);
handleError(error);
handleMutationError(showErrorModal, error);
},
},
);
@@ -503,7 +524,7 @@ export const useAlertRuleUpdate = ({
const handleAlertUpdate = (): void => {
updateAlertRule({
data: { ...alertDetails, alert: intermediateName },
id: alertDetails.id,
id: alertDetails.id || '',
});
};
@@ -518,11 +539,11 @@ export const useAlertRuleDelete = ({
handleAlertDelete: () => void;
} => {
const { notifications } = useNotifications();
const handleError = useAxiosError();
const { showErrorModal } = useErrorModal();
const { mutate: deleteAlert } = useMutation(
[REACT_QUERY_KEY.REMOVE_ALERT_RULE, ruleId],
deleteAlerts,
(args: { id: string }) => deleteRuleByID({ id: args.id }),
{
onSuccess: async () => {
notifications.success({
@@ -531,7 +552,7 @@ export const useAlertRuleDelete = ({
history.push(ROUTES.LIST_ALL_ALERT);
},
onError: handleError,
onError: (error) => handleMutationError(showErrorModal, error),
},
);

View File

@@ -1,11 +1,15 @@
import { useEffect } from 'react';
import { useEffect, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { useQuery } from 'react-query';
import { Button, Card } from 'antd';
import get from 'api/alerts/get';
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
import { useGetRuleByID } from 'api/generated/services/rules';
import type {
RenderErrorResponseDTO,
RuletypesGettableRuleDTO,
} from 'api/generated/services/sigNoz.schemas';
import { AxiosError } from 'axios';
import Spinner from 'components/Spinner';
import { QueryParams } from 'constants/query';
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
import ROUTES from 'constants/routes';
import EditRulesContainer from 'container/EditRules';
import { useNotifications } from 'hooks/useNotifications';
@@ -16,6 +20,7 @@ import {
NEW_ALERT_SCHEMA_VERSION,
PostableAlertRuleV2,
} from 'types/api/alerts/alertTypesV2';
import { AlertDef } from 'types/api/alerts/def';
import {
errorMessageReceivedFromBackend,
@@ -33,16 +38,14 @@ function EditRules(): JSX.Element {
const isValidRuleId = ruleId !== null && String(ruleId).length !== 0;
const { isLoading, data, isRefetching, isError } = useQuery(
[REACT_QUERY_KEY.ALERT_RULE_DETAILS, ruleId],
const { isLoading, data, isRefetching, isError, error } = useGetRuleByID(
{ id: ruleId || '' },
{
queryFn: () =>
get({
id: ruleId || '',
}),
enabled: isValidRuleId,
refetchOnMount: false,
refetchOnWindowFocus: false,
query: {
enabled: isValidRuleId,
refetchOnMount: false,
refetchOnWindowFocus: false,
},
},
);
@@ -65,18 +68,26 @@ function EditRules(): JSX.Element {
}
}, [isValidRuleId, ruleId, notifications, safeNavigate]);
const ruleData: RuletypesGettableRuleDTO | undefined = data?.data;
const apiError = useMemo(
() => convertToApiError(error as AxiosError<RenderErrorResponseDTO> | null),
[error],
);
if (
(isError && !isValidRuleId) ||
ruleId == null ||
(data?.payload?.data === undefined && !isLoading)
(ruleData === undefined && !isLoading)
) {
const errorMsg = apiError?.getErrorMessage() || '';
return (
<div className="edit-rules-container edit-rules-container--error">
<Card size="small" className="edit-rules-card">
<p className="content">
{data?.message === errorMessageReceivedFromBackend
{errorMsg === errorMessageReceivedFromBackend
? improvedErrorMessage
: data?.error || t('something_went_wrong')}
: errorMsg || t('something_went_wrong')}
</p>
<div className="btn-container">
<Button type="default" size="large" onClick={clickHandler}>
@@ -88,20 +99,20 @@ function EditRules(): JSX.Element {
);
}
if (isLoading || isRefetching || !data?.payload) {
if (isLoading || isRefetching || !ruleData) {
return <Spinner tip="Loading Rules..." />;
}
let initialV2AlertValue: PostableAlertRuleV2 | null = null;
if (data.payload.data.schemaVersion === NEW_ALERT_SCHEMA_VERSION) {
initialV2AlertValue = data.payload.data as PostableAlertRuleV2;
if (ruleData.schemaVersion === NEW_ALERT_SCHEMA_VERSION) {
initialV2AlertValue = (ruleData as unknown) as PostableAlertRuleV2;
}
return (
<div className="edit-rules-container">
<EditRulesContainer
ruleId={ruleId || ''}
initialValue={data.payload.data}
initialValue={(ruleData as unknown) as AlertDef}
initialV2AlertValue={initialV2AlertValue}
/>
</div>

View File

@@ -1,21 +0,0 @@
import { AlertDef } from './def';
export interface Props {
id: AlertDef['id'];
}
export interface GettableAlert extends AlertDef {
id: string;
alert: string;
state: string;
disabled: boolean;
createAt: string;
createBy: string;
updateAt: string;
updateBy: string;
schemaVersion: string;
}
export type PayloadProps = {
data: GettableAlert;
};

View File

@@ -1,3 +0,0 @@
import { GettableAlert } from './get';
export type PayloadProps = GettableAlert[];

View File

@@ -1,12 +0,0 @@
import { GettableAlert } from './get';
export type PayloadProps = GettableAlert;
export interface PatchProps {
disabled?: boolean;
}
export interface Props {
id?: string;
data: PatchProps;
}

View File

@@ -0,0 +1,12 @@
import { User } from 'types/reducer/app';
export interface Props {
oldPassword: string;
newPassword: string;
userId: User['userId'];
}
export interface PayloadProps {
status: string;
data: string;
}

View File

@@ -0,0 +1,15 @@
import { User } from 'types/reducer/app';
export interface Props {
userId: User['userId'];
}
export interface GetResetPasswordToken {
token: string;
userId: string;
}
export interface PayloadProps {
data: GetResetPasswordToken;
status: string;
}

View File

@@ -26,6 +26,7 @@ import (
"github.com/SigNoz/signoz/pkg/modules/session"
"github.com/SigNoz/signoz/pkg/modules/user"
"github.com/SigNoz/signoz/pkg/querier"
"github.com/SigNoz/signoz/pkg/ruler"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/zeus"
@@ -59,6 +60,7 @@ type provider struct {
cloudIntegrationHandler cloudintegration.Handler
ruleStateHistoryHandler rulestatehistory.Handler
alertmanagerHandler alertmanager.Handler
rulerHandler ruler.Handler
}
func NewFactory(
@@ -86,6 +88,7 @@ func NewFactory(
cloudIntegrationHandler cloudintegration.Handler,
ruleStateHistoryHandler rulestatehistory.Handler,
alertmanagerHandler alertmanager.Handler,
rulerHandler ruler.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(
@@ -116,6 +119,7 @@ func NewFactory(
cloudIntegrationHandler,
ruleStateHistoryHandler,
alertmanagerHandler,
rulerHandler,
)
})
}
@@ -148,6 +152,7 @@ func newProvider(
cloudIntegrationHandler cloudintegration.Handler,
ruleStateHistoryHandler rulestatehistory.Handler,
alertmanagerHandler alertmanager.Handler,
rulerHandler ruler.Handler,
) (apiserver.APIServer, error) {
settings := factory.NewScopedProviderSettings(providerSettings, "github.com/SigNoz/signoz/pkg/apiserver/signozapiserver")
router := mux.NewRouter().UseEncodedPath()
@@ -178,6 +183,7 @@ func newProvider(
cloudIntegrationHandler: cloudIntegrationHandler,
ruleStateHistoryHandler: ruleStateHistoryHandler,
alertmanagerHandler: alertmanagerHandler,
rulerHandler: rulerHandler,
}
provider.authZ = middleware.NewAuthZ(settings.Logger(), orgGetter, authz)
@@ -282,6 +288,10 @@ func (provider *provider) AddToRouter(router *mux.Router) error {
return err
}
if err := provider.addRulerRoutes(router); err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,200 @@
package signozapiserver
import (
"net/http"
"github.com/SigNoz/signoz/pkg/http/handler"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/ruletypes"
"github.com/gorilla/mux"
)
func (provider *provider) addRulerRoutes(router *mux.Router) error {
if err := router.Handle("/api/v1/rules", handler.New(provider.authZ.ViewAccess(provider.rulerHandler.ListRules), handler.OpenAPIDef{
ID: "ListRules",
Tags: []string{"rules"},
Summary: "List alert rules",
Description: "This endpoint lists all alert rules with their current evaluation state",
Response: new(ruletypes.GettableRules),
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusOK,
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
})).Methods(http.MethodGet).GetError(); err != nil {
return err
}
if err := router.Handle("/api/v1/rules/{id}", handler.New(provider.authZ.ViewAccess(provider.rulerHandler.GetRuleByID), handler.OpenAPIDef{
ID: "GetRuleByID",
Tags: []string{"rules"},
Summary: "Get alert rule by ID",
Description: "This endpoint returns an alert rule by ID",
Response: new(ruletypes.GettableRule),
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{http.StatusNotFound},
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
})).Methods(http.MethodGet).GetError(); err != nil {
return err
}
if err := router.Handle("/api/v1/rules", handler.New(provider.authZ.EditAccess(provider.rulerHandler.CreateRule), handler.OpenAPIDef{
ID: "CreateRule",
Tags: []string{"rules"},
Summary: "Create alert rule",
Description: "This endpoint creates a new alert rule",
Request: new(ruletypes.PostableRule),
RequestContentType: "application/json",
Response: new(ruletypes.GettableRule),
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{http.StatusBadRequest},
SecuritySchemes: newSecuritySchemes(types.RoleEditor),
})).Methods(http.MethodPost).GetError(); err != nil {
return err
}
if err := router.Handle("/api/v1/rules/{id}", handler.New(provider.authZ.EditAccess(provider.rulerHandler.UpdateRuleByID), handler.OpenAPIDef{
ID: "UpdateRuleByID",
Tags: []string{"rules"},
Summary: "Update alert rule",
Description: "This endpoint updates an alert rule by ID",
Request: new(ruletypes.PostableRule),
RequestContentType: "application/json",
SuccessStatusCode: http.StatusNoContent,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
SecuritySchemes: newSecuritySchemes(types.RoleEditor),
})).Methods(http.MethodPut).GetError(); err != nil {
return err
}
if err := router.Handle("/api/v1/rules/{id}", handler.New(provider.authZ.EditAccess(provider.rulerHandler.DeleteRuleByID), handler.OpenAPIDef{
ID: "DeleteRuleByID",
Tags: []string{"rules"},
Summary: "Delete alert rule",
Description: "This endpoint deletes an alert rule by ID",
SuccessStatusCode: http.StatusNoContent,
ErrorStatusCodes: []int{http.StatusNotFound},
SecuritySchemes: newSecuritySchemes(types.RoleEditor),
})).Methods(http.MethodDelete).GetError(); err != nil {
return err
}
if err := router.Handle("/api/v1/rules/{id}", handler.New(provider.authZ.EditAccess(provider.rulerHandler.PatchRuleByID), handler.OpenAPIDef{
ID: "PatchRuleByID",
Tags: []string{"rules"},
Summary: "Patch alert rule",
Description: "This endpoint applies a partial update to an alert rule by ID",
Request: new(ruletypes.PostableRule),
RequestContentType: "application/json",
Response: new(ruletypes.GettableRule),
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
SecuritySchemes: newSecuritySchemes(types.RoleEditor),
})).Methods(http.MethodPatch).GetError(); err != nil {
return err
}
if err := router.Handle("/api/v1/rules/test", handler.New(provider.authZ.EditAccess(provider.rulerHandler.TestRule), handler.OpenAPIDef{
ID: "TestRule",
Tags: []string{"rules"},
Summary: "Test alert rule",
Description: "This endpoint fires a test notification for the given rule definition",
Request: new(ruletypes.PostableRule),
RequestContentType: "application/json",
Response: new(ruletypes.GettableTestRule),
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{http.StatusBadRequest},
SecuritySchemes: newSecuritySchemes(types.RoleEditor),
})).Methods(http.MethodPost).GetError(); err != nil {
return err
}
if err := router.Handle("/api/v1/testRule", handler.New(provider.authZ.EditAccess(provider.rulerHandler.TestRule), handler.OpenAPIDef{
ID: "TestRuleDeprecated",
Tags: []string{"rules"},
Summary: "Test alert rule (deprecated)",
Description: "Deprecated: use /api/v1/rules/test instead",
Request: new(ruletypes.PostableRule),
RequestContentType: "application/json",
Response: new(ruletypes.GettableTestRule),
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{http.StatusBadRequest},
Deprecated: true,
SecuritySchemes: newSecuritySchemes(types.RoleEditor),
})).Methods(http.MethodPost).GetError(); err != nil {
return err
}
if err := router.Handle("/api/v1/downtime_schedules", handler.New(provider.authZ.ViewAccess(provider.rulerHandler.ListDowntimeSchedules), handler.OpenAPIDef{
ID: "ListDowntimeSchedules",
Tags: []string{"downtimeschedules"},
Summary: "List downtime schedules",
Description: "This endpoint lists all planned maintenance / downtime schedules",
RequestQuery: new(ruletypes.ListPlannedMaintenanceParams),
Response: make([]*ruletypes.GettablePlannedMaintenance, 0),
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusOK,
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
})).Methods(http.MethodGet).GetError(); err != nil {
return err
}
if err := router.Handle("/api/v1/downtime_schedules/{id}", handler.New(provider.authZ.ViewAccess(provider.rulerHandler.GetDowntimeScheduleByID), handler.OpenAPIDef{
ID: "GetDowntimeScheduleByID",
Tags: []string{"downtimeschedules"},
Summary: "Get downtime schedule by ID",
Description: "This endpoint returns a downtime schedule by ID",
Response: new(ruletypes.GettablePlannedMaintenance),
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{http.StatusNotFound},
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
})).Methods(http.MethodGet).GetError(); err != nil {
return err
}
if err := router.Handle("/api/v1/downtime_schedules", handler.New(provider.authZ.EditAccess(provider.rulerHandler.CreateDowntimeSchedule), handler.OpenAPIDef{
ID: "CreateDowntimeSchedule",
Tags: []string{"downtimeschedules"},
Summary: "Create downtime schedule",
Description: "This endpoint creates a new planned maintenance / downtime schedule",
Request: new(ruletypes.GettablePlannedMaintenance),
RequestContentType: "application/json",
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{http.StatusBadRequest},
SecuritySchemes: newSecuritySchemes(types.RoleEditor),
})).Methods(http.MethodPost).GetError(); err != nil {
return err
}
if err := router.Handle("/api/v1/downtime_schedules/{id}", handler.New(provider.authZ.EditAccess(provider.rulerHandler.UpdateDowntimeScheduleByID), handler.OpenAPIDef{
ID: "UpdateDowntimeScheduleByID",
Tags: []string{"downtimeschedules"},
Summary: "Update downtime schedule",
Description: "This endpoint updates a downtime schedule by ID",
Request: new(ruletypes.GettablePlannedMaintenance),
RequestContentType: "application/json",
SuccessStatusCode: http.StatusNoContent,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
SecuritySchemes: newSecuritySchemes(types.RoleEditor),
})).Methods(http.MethodPut).GetError(); err != nil {
return err
}
if err := router.Handle("/api/v1/downtime_schedules/{id}", handler.New(provider.authZ.EditAccess(provider.rulerHandler.DeleteDowntimeScheduleByID), handler.OpenAPIDef{
ID: "DeleteDowntimeScheduleByID",
Tags: []string{"downtimeschedules"},
Summary: "Delete downtime schedule",
Description: "This endpoint deletes a downtime schedule by ID",
SuccessStatusCode: http.StatusNoContent,
ErrorStatusCodes: []int{http.StatusNotFound},
SecuritySchemes: newSecuritySchemes(types.RoleEditor),
})).Methods(http.MethodDelete).GetError(); err != nil {
return err
}
return nil
}

View File

@@ -213,8 +213,8 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
return err
}
if err := router.Handle("/api/v1/getResetPasswordToken/{id}", handler.New(provider.authZ.AdminAccess(provider.userHandler.GetResetPasswordTokenDeprecated), handler.OpenAPIDef{
ID: "GetResetPasswordTokenDeprecated",
if err := router.Handle("/api/v1/getResetPasswordToken/{id}", handler.New(provider.authZ.AdminAccess(provider.userHandler.GetResetPasswordToken), handler.OpenAPIDef{
ID: "GetResetPasswordToken",
Tags: []string{"users"},
Summary: "Get reset password token",
Description: "This endpoint returns the reset password token by id",
@@ -224,46 +224,12 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
Deprecated: true,
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
})).Methods(http.MethodGet).GetError(); err != nil {
return err
}
if err := router.Handle("/api/v2/users/{id}/reset_password_tokens", handler.New(provider.authZ.AdminAccess(provider.userHandler.GetResetPasswordToken), handler.OpenAPIDef{
ID: "GetResetPasswordToken",
Tags: []string{"users"},
Summary: "Get reset password token for a user",
Description: "This endpoint returns the existing reset password token for a user.",
Request: nil,
RequestContentType: "",
Response: new(types.ResetPasswordToken),
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{http.StatusNotFound},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
})).Methods(http.MethodGet).GetError(); err != nil {
return err
}
if err := router.Handle("/api/v2/users/{id}/reset_password_tokens", handler.New(provider.authZ.AdminAccess(provider.userHandler.CreateResetPasswordToken), handler.OpenAPIDef{
ID: "CreateResetPasswordToken",
Tags: []string{"users"},
Summary: "Create or regenerate reset password token for a user",
Description: "This endpoint creates or regenerates a reset password token for a user. If a valid token exists, it is returned. If expired, a new one is created.",
Request: nil,
RequestContentType: "",
Response: new(types.ResetPasswordToken),
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusCreated,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
})).Methods(http.MethodPut).GetError(); err != nil {
return err
}
if err := router.Handle("/api/v1/resetPassword", handler.New(provider.authZ.OpenAccess(provider.userHandler.ResetPassword), handler.OpenAPIDef{
ID: "ResetPassword",
Tags: []string{"users"},
@@ -281,11 +247,11 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
return err
}
if err := router.Handle("/api/v2/users/me/factor_password", handler.New(provider.authZ.OpenAccess(provider.userHandler.ChangePassword), handler.OpenAPIDef{
ID: "UpdateMyPassword",
if err := router.Handle("/api/v1/changePassword/{id}", handler.New(provider.authZ.SelfAccess(provider.userHandler.ChangePassword), handler.OpenAPIDef{
ID: "ChangePassword",
Tags: []string{"users"},
Summary: "Updates my password",
Description: "This endpoint updates the password of the user I belong to",
Summary: "Change password",
Description: "This endpoint changes the password by id",
Request: new(types.ChangePasswordRequest),
RequestContentType: "application/json",
Response: nil,
@@ -294,7 +260,7 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
})).Methods(http.MethodPut).GetError(); err != nil {
})).Methods(http.MethodPost).GetError(); err != nil {
return err
}

View File

@@ -218,10 +218,6 @@ func (module *getter) GetRolesByUserID(ctx context.Context, userID valuer.UUID)
return userRoles, nil
}
func (module *getter) GetResetPasswordTokenByOrgIDAndUserID(ctx context.Context, orgID valuer.UUID, userID valuer.UUID) (*types.ResetPasswordToken, error) {
return module.store.GetResetPasswordTokenByOrgIDAndUserID(ctx, orgID, userID)
}
func (module *getter) GetUsersByOrgIDAndRoleID(ctx context.Context, orgID valuer.UUID, roleID valuer.UUID) ([]*types.User, error) {
return module.store.GetUsersByOrgIDAndRoleID(ctx, orgID, roleID)
}

View File

@@ -25,7 +25,7 @@ func NewHandler(setter root.Setter, getter root.Getter) root.Handler {
return &handler{setter: setter, getter: getter}
}
func (handler *handler) CreateInvite(rw http.ResponseWriter, r *http.Request) {
func (h *handler) CreateInvite(rw http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel()
@@ -41,7 +41,7 @@ func (handler *handler) CreateInvite(rw http.ResponseWriter, r *http.Request) {
return
}
invites, err := handler.setter.CreateBulkInvite(ctx, valuer.MustNewUUID(claims.OrgID), valuer.MustNewUUID(claims.IdentityID()), valuer.MustNewEmail(claims.Email), &types.PostableBulkInviteRequest{
invites, err := h.setter.CreateBulkInvite(ctx, valuer.MustNewUUID(claims.OrgID), valuer.MustNewUUID(claims.IdentityID()), valuer.MustNewEmail(claims.Email), &types.PostableBulkInviteRequest{
Invites: []types.PostableInvite{req},
})
if err != nil {
@@ -52,7 +52,7 @@ func (handler *handler) CreateInvite(rw http.ResponseWriter, r *http.Request) {
render.Success(rw, http.StatusCreated, invites[0])
}
func (handler *handler) CreateBulkInvite(rw http.ResponseWriter, r *http.Request) {
func (h *handler) CreateBulkInvite(rw http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel()
@@ -74,7 +74,7 @@ func (handler *handler) CreateBulkInvite(rw http.ResponseWriter, r *http.Request
return
}
_, err = handler.setter.CreateBulkInvite(ctx, valuer.MustNewUUID(claims.OrgID), valuer.MustNewUUID(claims.IdentityID()), valuer.MustNewEmail(claims.Email), &req)
_, err = h.setter.CreateBulkInvite(ctx, valuer.MustNewUUID(claims.OrgID), valuer.MustNewUUID(claims.IdentityID()), valuer.MustNewEmail(claims.Email), &req)
if err != nil {
render.Error(rw, err)
return
@@ -83,7 +83,7 @@ func (handler *handler) CreateBulkInvite(rw http.ResponseWriter, r *http.Request
render.Success(rw, http.StatusCreated, nil)
}
func (handler *handler) GetUserDeprecated(w http.ResponseWriter, r *http.Request) {
func (h *handler) GetUserDeprecated(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel()
@@ -95,7 +95,7 @@ func (handler *handler) GetUserDeprecated(w http.ResponseWriter, r *http.Request
return
}
user, err := handler.getter.GetDeprecatedUserByOrgIDAndID(ctx, valuer.MustNewUUID(claims.OrgID), valuer.MustNewUUID(id))
user, err := h.getter.GetDeprecatedUserByOrgIDAndID(ctx, valuer.MustNewUUID(claims.OrgID), valuer.MustNewUUID(id))
if err != nil {
render.Error(w, err)
return
@@ -104,7 +104,7 @@ func (handler *handler) GetUserDeprecated(w http.ResponseWriter, r *http.Request
render.Success(w, http.StatusOK, user)
}
func (handler *handler) GetUser(w http.ResponseWriter, r *http.Request) {
func (h *handler) GetUser(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel()
@@ -116,13 +116,13 @@ func (handler *handler) GetUser(w http.ResponseWriter, r *http.Request) {
return
}
user, err := handler.getter.GetUserByOrgIDAndID(ctx, valuer.MustNewUUID(claims.OrgID), valuer.MustNewUUID(userID))
user, err := h.getter.GetUserByOrgIDAndID(ctx, valuer.MustNewUUID(claims.OrgID), valuer.MustNewUUID(userID))
if err != nil {
render.Error(w, err)
return
}
userRoles, err := handler.getter.GetRolesByUserID(ctx, user.ID)
userRoles, err := h.getter.GetRolesByUserID(ctx, user.ID)
if err != nil {
render.Error(w, err)
return
@@ -136,7 +136,7 @@ func (handler *handler) GetUser(w http.ResponseWriter, r *http.Request) {
render.Success(w, http.StatusOK, userWithRoles)
}
func (handler *handler) GetMyUserDeprecated(w http.ResponseWriter, r *http.Request) {
func (h *handler) GetMyUserDeprecated(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel()
@@ -146,7 +146,7 @@ func (handler *handler) GetMyUserDeprecated(w http.ResponseWriter, r *http.Reque
return
}
user, err := handler.getter.GetDeprecatedUserByOrgIDAndID(ctx, valuer.MustNewUUID(claims.OrgID), valuer.MustNewUUID(claims.UserID))
user, err := h.getter.GetDeprecatedUserByOrgIDAndID(ctx, valuer.MustNewUUID(claims.OrgID), valuer.MustNewUUID(claims.UserID))
if err != nil {
render.Error(w, err)
return
@@ -155,7 +155,7 @@ func (handler *handler) GetMyUserDeprecated(w http.ResponseWriter, r *http.Reque
render.Success(w, http.StatusOK, user)
}
func (handler *handler) GetMyUser(w http.ResponseWriter, r *http.Request) {
func (h *handler) GetMyUser(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel()
@@ -165,13 +165,13 @@ func (handler *handler) GetMyUser(w http.ResponseWriter, r *http.Request) {
return
}
user, err := handler.getter.GetUserByOrgIDAndID(ctx, valuer.MustNewUUID(claims.OrgID), valuer.MustNewUUID(claims.UserID))
user, err := h.getter.GetUserByOrgIDAndID(ctx, valuer.MustNewUUID(claims.OrgID), valuer.MustNewUUID(claims.UserID))
if err != nil {
render.Error(w, err)
return
}
userRoles, err := handler.getter.GetRolesByUserID(ctx, user.ID)
userRoles, err := h.getter.GetRolesByUserID(ctx, user.ID)
if err != nil {
render.Error(w, err)
return
@@ -185,7 +185,7 @@ func (handler *handler) GetMyUser(w http.ResponseWriter, r *http.Request) {
render.Success(w, http.StatusOK, userWithRoles)
}
func (handler *handler) UpdateMyUser(w http.ResponseWriter, r *http.Request) {
func (h *handler) UpdateMyUser(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel()
@@ -201,7 +201,7 @@ func (handler *handler) UpdateMyUser(w http.ResponseWriter, r *http.Request) {
return
}
_, err = handler.setter.UpdateUser(ctx, valuer.MustNewUUID(claims.OrgID), valuer.MustNewUUID(claims.UserID), updatableUser)
_, err = h.setter.UpdateUser(ctx, valuer.MustNewUUID(claims.OrgID), valuer.MustNewUUID(claims.UserID), updatableUser)
if err != nil {
render.Error(w, err)
return
@@ -210,7 +210,7 @@ func (handler *handler) UpdateMyUser(w http.ResponseWriter, r *http.Request) {
render.Success(w, http.StatusNoContent, nil)
}
func (handler *handler) ListUsersDeprecated(w http.ResponseWriter, r *http.Request) {
func (h *handler) ListUsersDeprecated(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel()
@@ -220,7 +220,7 @@ func (handler *handler) ListUsersDeprecated(w http.ResponseWriter, r *http.Reque
return
}
users, err := handler.getter.ListDeprecatedUsersByOrgID(ctx, valuer.MustNewUUID(claims.OrgID))
users, err := h.getter.ListDeprecatedUsersByOrgID(ctx, valuer.MustNewUUID(claims.OrgID))
if err != nil {
render.Error(w, err)
return
@@ -229,7 +229,7 @@ func (handler *handler) ListUsersDeprecated(w http.ResponseWriter, r *http.Reque
render.Success(w, http.StatusOK, users)
}
func (handler *handler) ListUsers(w http.ResponseWriter, r *http.Request) {
func (h *handler) ListUsers(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel()
@@ -239,7 +239,7 @@ func (handler *handler) ListUsers(w http.ResponseWriter, r *http.Request) {
return
}
users, err := handler.getter.ListUsersByOrgID(ctx, valuer.MustNewUUID(claims.OrgID))
users, err := h.getter.ListUsersByOrgID(ctx, valuer.MustNewUUID(claims.OrgID))
if err != nil {
render.Error(w, err)
return
@@ -248,7 +248,7 @@ func (handler *handler) ListUsers(w http.ResponseWriter, r *http.Request) {
render.Success(w, http.StatusOK, users)
}
func (handler *handler) UpdateUserDeprecated(w http.ResponseWriter, r *http.Request) {
func (h *handler) UpdateUserDeprecated(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel()
@@ -266,7 +266,7 @@ func (handler *handler) UpdateUserDeprecated(w http.ResponseWriter, r *http.Requ
return
}
updatedUser, err := handler.setter.UpdateUserDeprecated(ctx, valuer.MustNewUUID(claims.OrgID), id, &user)
updatedUser, err := h.setter.UpdateUserDeprecated(ctx, valuer.MustNewUUID(claims.OrgID), id, &user)
if err != nil {
render.Error(w, err)
return
@@ -275,7 +275,7 @@ func (handler *handler) UpdateUserDeprecated(w http.ResponseWriter, r *http.Requ
render.Success(w, http.StatusOK, updatedUser)
}
func (handler *handler) UpdateUser(w http.ResponseWriter, r *http.Request) {
func (h *handler) UpdateUser(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel()
@@ -298,7 +298,7 @@ func (handler *handler) UpdateUser(w http.ResponseWriter, r *http.Request) {
return
}
_, err = handler.setter.UpdateUser(ctx, valuer.MustNewUUID(claims.OrgID), valuer.MustNewUUID(userID), updatableUser)
_, err = h.setter.UpdateUser(ctx, valuer.MustNewUUID(claims.OrgID), valuer.MustNewUUID(userID), updatableUser)
if err != nil {
render.Error(w, err)
return
@@ -307,7 +307,7 @@ func (handler *handler) UpdateUser(w http.ResponseWriter, r *http.Request) {
render.Success(w, http.StatusNoContent, nil)
}
func (handler *handler) DeleteUser(w http.ResponseWriter, r *http.Request) {
func (h *handler) DeleteUser(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel()
@@ -319,7 +319,7 @@ func (handler *handler) DeleteUser(w http.ResponseWriter, r *http.Request) {
return
}
if err := handler.setter.DeleteUser(ctx, valuer.MustNewUUID(claims.OrgID), id, claims.IdentityID()); err != nil {
if err := h.setter.DeleteUser(ctx, valuer.MustNewUUID(claims.OrgID), id, claims.IdentityID()); err != nil {
render.Error(w, err)
return
}
@@ -327,7 +327,7 @@ func (handler *handler) DeleteUser(w http.ResponseWriter, r *http.Request) {
render.Success(w, http.StatusNoContent, nil)
}
func (handler *handler) GetResetPasswordTokenDeprecated(w http.ResponseWriter, r *http.Request) {
func (handler *handler) GetResetPasswordToken(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel()
@@ -354,62 +354,6 @@ func (handler *handler) GetResetPasswordTokenDeprecated(w http.ResponseWriter, r
render.Success(w, http.StatusOK, token)
}
func (handler *handler) GetResetPasswordToken(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel()
userID, err := valuer.NewUUID(mux.Vars(r)["id"])
if err != nil {
render.Error(w, err)
return
}
claims, err := authtypes.ClaimsFromContext(ctx)
if err != nil {
render.Error(w, err)
return
}
token, err := handler.getter.GetResetPasswordTokenByOrgIDAndUserID(ctx, valuer.MustNewUUID(claims.OrgID), userID)
if err != nil {
render.Error(w, err)
return
}
render.Success(w, http.StatusOK, token)
}
func (handler *handler) CreateResetPasswordToken(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel()
userID, err := valuer.NewUUID(mux.Vars(r)["id"])
if err != nil {
render.Error(w, err)
return
}
claims, err := authtypes.ClaimsFromContext(ctx)
if err != nil {
render.Error(w, err)
return
}
user, err := handler.getter.GetUserByOrgIDAndID(ctx, valuer.MustNewUUID(claims.OrgID), userID)
if err != nil {
render.Error(w, err)
return
}
token, err := handler.setter.GetOrCreateResetPasswordToken(ctx, user.ID)
if err != nil {
render.Error(w, err)
return
}
render.Success(w, http.StatusCreated, token)
}
func (handler *handler) ResetPassword(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel()
@@ -433,19 +377,13 @@ func (handler *handler) ChangePassword(w 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(w, err)
return
}
var req types.ChangePasswordRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
render.Error(w, err)
return
}
err = handler.setter.UpdatePassword(ctx, valuer.MustNewUUID(claims.UserID), req.OldPassword, req.NewPassword)
err := handler.setter.UpdatePassword(ctx, req.UserID, req.OldPassword, req.NewPassword)
if err != nil {
render.Error(w, err)
return
@@ -454,7 +392,7 @@ func (handler *handler) ChangePassword(w http.ResponseWriter, r *http.Request) {
render.Success(w, http.StatusNoContent, nil)
}
func (handler *handler) ForgotPassword(w http.ResponseWriter, r *http.Request) {
func (h *handler) ForgotPassword(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel()
@@ -464,7 +402,7 @@ func (handler *handler) ForgotPassword(w http.ResponseWriter, r *http.Request) {
return
}
err := handler.setter.ForgotPassword(ctx, req.OrgID, req.Email, req.FrontendBaseURL)
err := h.setter.ForgotPassword(ctx, req.OrgID, req.Email, req.FrontendBaseURL)
if err != nil {
render.Error(w, err)
return
@@ -473,7 +411,7 @@ func (handler *handler) ForgotPassword(w http.ResponseWriter, r *http.Request) {
render.Success(w, http.StatusNoContent, nil)
}
func (handler *handler) GetRolesByUserID(w http.ResponseWriter, r *http.Request) {
func (h *handler) GetRolesByUserID(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel()
@@ -485,13 +423,13 @@ func (handler *handler) GetRolesByUserID(w http.ResponseWriter, r *http.Request)
return
}
user, err := handler.getter.GetUserByOrgIDAndID(ctx, valuer.MustNewUUID(claims.OrgID), valuer.MustNewUUID(userID))
user, err := h.getter.GetUserByOrgIDAndID(ctx, valuer.MustNewUUID(claims.OrgID), valuer.MustNewUUID(userID))
if err != nil {
render.Error(w, err)
return
}
userRoles, err := handler.getter.GetRolesByUserID(ctx, user.ID)
userRoles, err := h.getter.GetRolesByUserID(ctx, user.ID)
if err != nil {
render.Error(w, err)
return
@@ -505,7 +443,7 @@ func (handler *handler) GetRolesByUserID(w http.ResponseWriter, r *http.Request)
render.Success(w, http.StatusOK, roles)
}
func (handler *handler) SetRoleByUserID(w http.ResponseWriter, r *http.Request) {
func (h *handler) SetRoleByUserID(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel()
@@ -533,7 +471,7 @@ func (handler *handler) SetRoleByUserID(w http.ResponseWriter, r *http.Request)
return
}
if err := handler.setter.AddUserRole(ctx, valuer.MustNewUUID(claims.OrgID), valuer.MustNewUUID(userID), postableRole.Name); err != nil {
if err := h.setter.AddUserRole(ctx, valuer.MustNewUUID(claims.OrgID), valuer.MustNewUUID(userID), postableRole.Name); err != nil {
render.Error(w, err)
return
}
@@ -541,7 +479,7 @@ func (handler *handler) SetRoleByUserID(w http.ResponseWriter, r *http.Request)
render.Success(w, http.StatusOK, nil)
}
func (handler *handler) RemoveUserRoleByRoleID(w http.ResponseWriter, r *http.Request) {
func (h *handler) RemoveUserRoleByRoleID(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel()
@@ -559,7 +497,7 @@ func (handler *handler) RemoveUserRoleByRoleID(w http.ResponseWriter, r *http.Re
return
}
if err := handler.setter.RemoveUserRole(ctx, valuer.MustNewUUID(claims.OrgID), valuer.MustNewUUID(userID), valuer.MustNewUUID(roleID)); err != nil {
if err := h.setter.RemoveUserRole(ctx, valuer.MustNewUUID(claims.OrgID), valuer.MustNewUUID(userID), valuer.MustNewUUID(roleID)); err != nil {
render.Error(w, err)
return
}
@@ -567,7 +505,7 @@ func (handler *handler) RemoveUserRoleByRoleID(w http.ResponseWriter, r *http.Re
render.Success(w, http.StatusNoContent, nil)
}
func (handler *handler) GetUsersByRoleID(w http.ResponseWriter, r *http.Request) {
func (h *handler) GetUsersByRoleID(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel()
@@ -579,7 +517,7 @@ func (handler *handler) GetUsersByRoleID(w http.ResponseWriter, r *http.Request)
return
}
users, err := handler.getter.GetUsersByOrgIDAndRoleID(ctx, valuer.MustNewUUID(claims.OrgID), valuer.MustNewUUID(roleID))
users, err := h.getter.GetUsersByOrgIDAndRoleID(ctx, valuer.MustNewUUID(claims.OrgID), valuer.MustNewUUID(roleID))
if err != nil {
render.Error(w, err)
return

View File

@@ -359,26 +359,6 @@ func (store *store) GetResetPasswordTokenByPasswordID(ctx context.Context, passw
return resetPasswordToken, nil
}
func (store *store) GetResetPasswordTokenByOrgIDAndUserID(ctx context.Context, orgID valuer.UUID, userID valuer.UUID) (*types.ResetPasswordToken, error) {
resetPasswordToken := new(types.ResetPasswordToken)
err := store.
sqlstore.
BunDBCtx(ctx).
NewSelect().
Model(resetPasswordToken).
Join("JOIN factor_password ON factor_password.id = reset_password_token.password_id").
Join("JOIN users ON users.id = factor_password.user_id").
Where("factor_password.user_id = ?", userID).
Where("users.org_id = ?", orgID).
Scan(ctx)
if err != nil {
return nil, store.sqlstore.WrapNotFoundErrf(err, types.ErrResetPasswordTokenNotFound, "reset password token for user %s does not exist", userID)
}
return resetPasswordToken, nil
}
func (store *store) DeleteResetPasswordTokenByPasswordID(ctx context.Context, passwordID valuer.UUID) error {
_, err := store.sqlstore.BunDBCtx(ctx).NewDelete().
Model(&types.ResetPasswordToken{}).

View File

@@ -80,9 +80,6 @@ type Getter interface {
// Get factor password by user id.
GetFactorPasswordByUserID(context.Context, valuer.UUID) (*types.FactorPassword, error)
// Get reset password token by org id and user id.
GetResetPasswordTokenByOrgIDAndUserID(ctx context.Context, orgID valuer.UUID, userID valuer.UUID) (*types.ResetPasswordToken, error)
// Gets single Non-Deleted user by email and org id
GetNonDeletedUserByEmailAndOrgID(ctx context.Context, email valuer.Email, orgID valuer.UUID) (*types.User, error)
@@ -115,9 +112,7 @@ type Handler interface {
GetUsersByRoleID(http.ResponseWriter, *http.Request)
// Reset Password
GetResetPasswordTokenDeprecated(http.ResponseWriter, *http.Request)
GetResetPasswordToken(http.ResponseWriter, *http.Request)
CreateResetPasswordToken(http.ResponseWriter, *http.Request)
ResetPassword(http.ResponseWriter, *http.Request)
ChangePassword(http.ResponseWriter, *http.Request)
ForgotPassword(http.ResponseWriter, *http.Request)

View File

@@ -3,7 +3,6 @@ package app
import (
"bytes"
"context"
"database/sql"
"encoding/json"
"fmt"
@@ -12,7 +11,6 @@ import (
"github.com/SigNoz/signoz/pkg/modules/thirdpartyapi"
"github.com/SigNoz/signoz/pkg/queryparser"
"io"
"log/slog"
"math"
"net/http"
@@ -78,7 +76,7 @@ import (
"github.com/SigNoz/signoz/pkg/query-service/app/logparsingpipeline"
"github.com/SigNoz/signoz/pkg/query-service/interfaces"
"github.com/SigNoz/signoz/pkg/query-service/model"
"github.com/SigNoz/signoz/pkg/query-service/rules"
"github.com/SigNoz/signoz/pkg/ruler"
"github.com/SigNoz/signoz/pkg/version"
)
@@ -98,7 +96,7 @@ func NewRouter() *mux.Router {
type APIHandler struct {
logger *slog.Logger
reader interfaces.Reader
ruleManager *rules.Manager
ruleManager ruler.Ruler
querier interfaces.Querier
querierV2 interfaces.Querier
queryBuilder *queryBuilder.QueryBuilder
@@ -150,9 +148,6 @@ type APIHandlerOpts struct {
// business data reader e.g. clickhouse
Reader interfaces.Reader
// rule manager handles rule crud operations
RuleManager *rules.Manager
// Integrations
IntegrationsController *integrations.Controller
@@ -208,7 +203,7 @@ func NewAPIHandler(opts APIHandlerOpts, config signoz.Config) (*APIHandler, erro
logger: slog.Default(),
reader: opts.Reader,
temporalityMap: make(map[string]map[v3.Temporality]bool),
ruleManager: opts.RuleManager,
ruleManager: opts.Signoz.Ruler,
IntegrationsController: opts.IntegrationsController,
CloudIntegrationsController: opts.CloudIntegrationsController,
LogsParsingPipelineController: opts.LogsParsingPipelineController,
@@ -496,24 +491,11 @@ func (aH *APIHandler) Respond(w http.ResponseWriter, data interface{}) {
func (aH *APIHandler) RegisterRoutes(router *mux.Router, am *middleware.AuthZ) {
router.HandleFunc("/api/v1/query_range", am.ViewAccess(aH.queryRangeMetrics)).Methods(http.MethodGet)
router.HandleFunc("/api/v1/query", am.ViewAccess(aH.queryMetrics)).Methods(http.MethodGet)
router.HandleFunc("/api/v1/rules", am.ViewAccess(aH.listRules)).Methods(http.MethodGet)
router.HandleFunc("/api/v1/rules/{id}", am.ViewAccess(aH.getRule)).Methods(http.MethodGet)
router.HandleFunc("/api/v1/rules", am.EditAccess(aH.createRule)).Methods(http.MethodPost)
router.HandleFunc("/api/v1/rules/{id}", am.EditAccess(aH.editRule)).Methods(http.MethodPut)
router.HandleFunc("/api/v1/rules/{id}", am.EditAccess(aH.deleteRule)).Methods(http.MethodDelete)
router.HandleFunc("/api/v1/rules/{id}", am.EditAccess(aH.patchRule)).Methods(http.MethodPatch)
router.HandleFunc("/api/v1/testRule", am.EditAccess(aH.testRule)).Methods(http.MethodPost)
router.HandleFunc("/api/v1/rules/{id}/history/stats", am.ViewAccess(aH.getRuleStats)).Methods(http.MethodPost)
router.HandleFunc("/api/v1/rules/{id}/history/timeline", am.ViewAccess(aH.getRuleStateHistory)).Methods(http.MethodPost)
router.HandleFunc("/api/v1/rules/{id}/history/top_contributors", am.ViewAccess(aH.getRuleStateHistoryTopContributors)).Methods(http.MethodPost)
router.HandleFunc("/api/v1/rules/{id}/history/overall_status", am.ViewAccess(aH.getOverallStateTransitions)).Methods(http.MethodPost)
router.HandleFunc("/api/v1/downtime_schedules", am.ViewAccess(aH.listDowntimeSchedules)).Methods(http.MethodGet)
router.HandleFunc("/api/v1/downtime_schedules/{id}", am.ViewAccess(aH.getDowntimeSchedule)).Methods(http.MethodGet)
router.HandleFunc("/api/v1/downtime_schedules", am.EditAccess(aH.createDowntimeSchedule)).Methods(http.MethodPost)
router.HandleFunc("/api/v1/downtime_schedules/{id}", am.EditAccess(aH.editDowntimeSchedule)).Methods(http.MethodPut)
router.HandleFunc("/api/v1/downtime_schedules/{id}", am.EditAccess(aH.deleteDowntimeSchedule)).Methods(http.MethodDelete)
router.HandleFunc("/api/v1/dashboards", am.ViewAccess(aH.List)).Methods(http.MethodGet)
router.HandleFunc("/api/v1/dashboards", am.EditAccess(aH.Signoz.Handlers.Dashboard.Create)).Methods(http.MethodPost)
router.HandleFunc("/api/v1/dashboards/{id}", am.ViewAccess(aH.Get)).Methods(http.MethodGet)
@@ -603,26 +585,6 @@ func Intersection(a, b []int) (c []int) {
return
}
func (aH *APIHandler) getRule(w http.ResponseWriter, r *http.Request) {
idStr := mux.Vars(r)["id"]
id, err := valuer.NewUUID(idStr)
if err != nil {
RespondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: err}, nil)
return
}
ruleResponse, err := aH.ruleManager.GetRule(r.Context(), id)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
RespondError(w, &model.ApiError{Typ: model.ErrorNotFound, Err: fmt.Errorf("rule not found")}, nil)
return
}
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
return
}
aH.Respond(w, ruleResponse)
}
// populateTemporality adds the temporality to the query if it is not present
func (aH *APIHandler) PopulateTemporality(ctx context.Context, orgID valuer.UUID, qp *v3.QueryRangeParamsV3) error {
@@ -678,129 +640,6 @@ func (aH *APIHandler) PopulateTemporality(ctx context.Context, orgID valuer.UUID
return nil
}
func (aH *APIHandler) listDowntimeSchedules(w http.ResponseWriter, r *http.Request) {
claims, errv2 := authtypes.ClaimsFromContext(r.Context())
if errv2 != nil {
render.Error(w, errv2)
return
}
schedules, err := aH.ruleManager.MaintenanceStore().GetAllPlannedMaintenance(r.Context(), claims.OrgID)
if err != nil {
render.Error(w, err)
return
}
// The schedules are stored as JSON in the database, so we need to filter them here
// Since the number of schedules is expected to be small, this should be fine
if r.URL.Query().Get("active") != "" {
activeSchedules := make([]*ruletypes.GettablePlannedMaintenance, 0)
active, _ := strconv.ParseBool(r.URL.Query().Get("active"))
for _, schedule := range schedules {
now := time.Now().In(time.FixedZone(schedule.Schedule.Timezone, 0))
if schedule.IsActive(now) == active {
activeSchedules = append(activeSchedules, schedule)
}
}
schedules = activeSchedules
}
if r.URL.Query().Get("recurring") != "" {
recurringSchedules := make([]*ruletypes.GettablePlannedMaintenance, 0)
recurring, _ := strconv.ParseBool(r.URL.Query().Get("recurring"))
for _, schedule := range schedules {
if schedule.IsRecurring() == recurring {
recurringSchedules = append(recurringSchedules, schedule)
}
}
schedules = recurringSchedules
}
aH.Respond(w, schedules)
}
func (aH *APIHandler) getDowntimeSchedule(w http.ResponseWriter, r *http.Request) {
idStr := mux.Vars(r)["id"]
id, err := valuer.NewUUID(idStr)
if err != nil {
render.Error(w, errorsV2.New(errorsV2.TypeInvalidInput, errorsV2.CodeInvalidInput, err.Error()))
return
}
schedule, err := aH.ruleManager.MaintenanceStore().GetPlannedMaintenanceByID(r.Context(), id)
if err != nil {
render.Error(w, err)
return
}
aH.Respond(w, schedule)
}
func (aH *APIHandler) createDowntimeSchedule(w http.ResponseWriter, r *http.Request) {
var schedule ruletypes.GettablePlannedMaintenance
err := json.NewDecoder(r.Body).Decode(&schedule)
if err != nil {
RespondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: err}, nil)
return
}
if err := schedule.Validate(); err != nil {
render.Error(w, err)
return
}
_, err = aH.ruleManager.MaintenanceStore().CreatePlannedMaintenance(r.Context(), schedule)
if err != nil {
render.Error(w, err)
return
}
aH.Respond(w, nil)
}
func (aH *APIHandler) editDowntimeSchedule(w http.ResponseWriter, r *http.Request) {
idStr := mux.Vars(r)["id"]
id, err := valuer.NewUUID(idStr)
if err != nil {
render.Error(w, errorsV2.New(errorsV2.TypeInvalidInput, errorsV2.CodeInvalidInput, err.Error()))
return
}
var schedule ruletypes.GettablePlannedMaintenance
err = json.NewDecoder(r.Body).Decode(&schedule)
if err != nil {
render.Error(w, err)
return
}
if err := schedule.Validate(); err != nil {
render.Error(w, err)
return
}
err = aH.ruleManager.MaintenanceStore().EditPlannedMaintenance(r.Context(), schedule, id)
if err != nil {
render.Error(w, err)
return
}
aH.Respond(w, nil)
}
func (aH *APIHandler) deleteDowntimeSchedule(w http.ResponseWriter, r *http.Request) {
idStr := mux.Vars(r)["id"]
id, err := valuer.NewUUID(idStr)
if err != nil {
render.Error(w, errorsV2.New(errorsV2.TypeInvalidInput, errorsV2.CodeInvalidInput, err.Error()))
return
}
err = aH.ruleManager.MaintenanceStore().DeletePlannedMaintenance(r.Context(), id)
if err != nil {
render.Error(w, err)
return
}
aH.Respond(w, nil)
}
func (aH *APIHandler) getRuleStats(w http.ResponseWriter, r *http.Request) {
ruleID := mux.Vars(r)["id"]
params := model.QueryRuleStateHistory{}
@@ -1012,19 +851,6 @@ func (aH *APIHandler) getRuleStateHistoryTopContributors(w http.ResponseWriter,
aH.Respond(w, res)
}
func (aH *APIHandler) listRules(w http.ResponseWriter, r *http.Request) {
rules, err := aH.ruleManager.ListRuleStates(r.Context())
if err != nil {
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
return
}
// todo(amol): need to add sorter
aH.Respond(w, rules)
}
func prepareQuery(r *http.Request) (string, error) {
var postData *model.DashboardVars
@@ -1226,142 +1052,6 @@ func (aH *APIHandler) queryDashboardVarsV2(w http.ResponseWriter, r *http.Reques
aH.Respond(w, dashboardVars)
}
func (aH *APIHandler) testRule(w http.ResponseWriter, r *http.Request) {
claims, err := authtypes.ClaimsFromContext(r.Context())
if err != nil {
render.Error(w, err)
return
}
orgID, err := valuer.NewUUID(claims.OrgID)
if err != nil {
render.Error(w, err)
return
}
defer r.Body.Close()
body, err := io.ReadAll(r.Body)
if err != nil {
aH.logger.ErrorContext(r.Context(), "error reading request body for test rule", errors.Attr(err))
RespondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: err}, nil)
return
}
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute)
defer cancel()
alertCount, err := aH.ruleManager.TestNotification(ctx, orgID, string(body))
if err != nil {
RespondError(w, toApiError(err), nil)
return
}
response := map[string]interface{}{
"alertCount": alertCount,
"message": "notification sent",
}
aH.Respond(w, response)
}
func (aH *APIHandler) deleteRule(w http.ResponseWriter, r *http.Request) {
id := mux.Vars(r)["id"]
err := aH.ruleManager.DeleteRule(r.Context(), id)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
RespondError(w, &model.ApiError{Typ: model.ErrorNotFound, Err: fmt.Errorf("rule not found")}, nil)
return
}
RespondError(w, toApiError(err), nil)
return
}
aH.Respond(w, "rule successfully deleted")
}
// patchRule updates only requested changes in the rule
func (aH *APIHandler) patchRule(w http.ResponseWriter, r *http.Request) {
idStr := mux.Vars(r)["id"]
id, err := valuer.NewUUID(idStr)
if err != nil {
RespondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: err}, nil)
return
}
defer r.Body.Close()
body, err := io.ReadAll(r.Body)
if err != nil {
aH.logger.ErrorContext(r.Context(), "error reading request body for patch rule", errors.Attr(err))
RespondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: err}, nil)
return
}
gettableRule, err := aH.ruleManager.PatchRule(r.Context(), string(body), id)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
RespondError(w, &model.ApiError{Typ: model.ErrorNotFound, Err: fmt.Errorf("rule not found")}, nil)
return
}
RespondError(w, toApiError(err), nil)
return
}
aH.Respond(w, gettableRule)
}
func (aH *APIHandler) editRule(w http.ResponseWriter, r *http.Request) {
idStr := mux.Vars(r)["id"]
id, err := valuer.NewUUID(idStr)
if err != nil {
RespondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: err}, nil)
return
}
defer r.Body.Close()
body, err := io.ReadAll(r.Body)
if err != nil {
aH.logger.ErrorContext(r.Context(), "error reading request body for edit rule", errors.Attr(err))
RespondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: err}, nil)
return
}
err = aH.ruleManager.EditRule(r.Context(), string(body), id)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
RespondError(w, &model.ApiError{Typ: model.ErrorNotFound, Err: fmt.Errorf("rule not found")}, nil)
return
}
RespondError(w, toApiError(err), nil)
return
}
aH.Respond(w, "rule successfully edited")
}
func (aH *APIHandler) createRule(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
body, err := io.ReadAll(r.Body)
if err != nil {
aH.logger.ErrorContext(r.Context(), "error reading request body for create rule", errors.Attr(err))
RespondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: err}, nil)
return
}
rule, err := aH.ruleManager.CreateRule(r.Context(), string(body))
if err != nil {
RespondError(w, toApiError(err), nil)
return
}
aH.Respond(w, rule)
}
func (aH *APIHandler) queryRangeMetrics(w http.ResponseWriter, r *http.Request) {
query, apiErrorObj := parseQueryRangeRequest(r)

View File

@@ -9,24 +9,16 @@ import (
"github.com/SigNoz/signoz/pkg/cache/memorycache"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/queryparser"
"github.com/SigNoz/signoz/pkg/ruler/rulestore/sqlrulestore"
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
"github.com/gorilla/handlers"
"github.com/rs/cors"
"github.com/soheilhy/cmux"
"github.com/SigNoz/signoz/pkg/alertmanager"
"github.com/SigNoz/signoz/pkg/cache"
"github.com/SigNoz/signoz/pkg/http/middleware"
"github.com/SigNoz/signoz/pkg/licensing/nooplicensing"
"github.com/SigNoz/signoz/pkg/modules/organization"
"github.com/SigNoz/signoz/pkg/modules/rulestatehistory"
"github.com/SigNoz/signoz/pkg/prometheus"
"github.com/SigNoz/signoz/pkg/querier"
"github.com/SigNoz/signoz/pkg/query-service/agentConf"
"github.com/SigNoz/signoz/pkg/query-service/app/clickhouseReader"
"github.com/SigNoz/signoz/pkg/query-service/app/cloudintegrations"
@@ -34,10 +26,7 @@ import (
"github.com/SigNoz/signoz/pkg/query-service/app/logparsingpipeline"
"github.com/SigNoz/signoz/pkg/query-service/app/opamp"
opAmpModel "github.com/SigNoz/signoz/pkg/query-service/app/opamp/model"
"github.com/SigNoz/signoz/pkg/query-service/interfaces"
"github.com/SigNoz/signoz/pkg/signoz"
"github.com/SigNoz/signoz/pkg/sqlstore"
"github.com/SigNoz/signoz/pkg/telemetrystore"
"github.com/SigNoz/signoz/pkg/web"
"log/slog"
@@ -47,15 +36,13 @@ import (
"github.com/SigNoz/signoz/pkg/query-service/constants"
"github.com/SigNoz/signoz/pkg/query-service/healthcheck"
"github.com/SigNoz/signoz/pkg/query-service/rules"
"github.com/SigNoz/signoz/pkg/query-service/utils"
)
// Server runs HTTP, Mux and a grpc server
type Server struct {
config signoz.Config
signoz *signoz.SigNoz
ruleManager *rules.Manager
config signoz.Config
signoz *signoz.SigNoz
// public http router
httpConn net.Listener
@@ -102,24 +89,6 @@ func NewServer(config signoz.Config, signoz *signoz.SigNoz) (*Server, error) {
nil,
)
rm, err := makeRulesManager(
reader,
signoz.Cache,
signoz.Alertmanager,
signoz.SQLStore,
signoz.TelemetryStore,
signoz.TelemetryMetadataStore,
signoz.Prometheus,
signoz.Modules.OrgGetter,
signoz.Modules.RuleStateHistory,
signoz.Querier,
signoz.Instrumentation.ToProviderSettings(),
signoz.QueryParser,
)
if err != nil {
return nil, err
}
logParsingPipelineController, err := logparsingpipeline.NewLogParsingPipelinesController(
signoz.SQLStore,
integrationsController.GetPipelinesForInstalledIntegrations,
@@ -131,7 +100,6 @@ func NewServer(config signoz.Config, signoz *signoz.SigNoz) (*Server, error) {
apiHandler, err := NewAPIHandler(APIHandlerOpts{
Reader: reader,
RuleManager: rm,
IntegrationsController: integrationsController,
CloudIntegrationsController: cloudIntegrationsController,
LogsParsingPipelineController: logParsingPipelineController,
@@ -146,8 +114,7 @@ func NewServer(config signoz.Config, signoz *signoz.SigNoz) (*Server, error) {
s := &Server{
config: config,
signoz: signoz,
ruleManager: rm,
signoz: signoz,
httpHostPort: constants.HTTPHostPort,
unavailableChannel: make(chan healthcheck.Status),
}
@@ -270,8 +237,6 @@ func (s *Server) initListeners() error {
// Start listening on http and private http port concurrently
func (s *Server) Start(ctx context.Context) error {
s.ruleManager.Start(ctx)
err := s.initListeners()
if err != nil {
return err
@@ -315,55 +280,6 @@ func (s *Server) Stop(ctx context.Context) error {
s.opampServer.Stop()
if s.ruleManager != nil {
s.ruleManager.Stop(ctx)
}
return nil
}
func makeRulesManager(
ch interfaces.Reader,
cache cache.Cache,
alertmanager alertmanager.Alertmanager,
sqlstore sqlstore.SQLStore,
telemetryStore telemetrystore.TelemetryStore,
metadataStore telemetrytypes.MetadataStore,
prometheus prometheus.Prometheus,
orgGetter organization.Getter,
ruleStateHistoryModule rulestatehistory.Module,
querier querier.Querier,
providerSettings factory.ProviderSettings,
queryParser queryparser.QueryParser,
) (*rules.Manager, error) {
ruleStore := sqlrulestore.NewRuleStore(sqlstore, queryParser, providerSettings)
maintenanceStore := sqlrulestore.NewMaintenanceStore(sqlstore)
// create manager opts
managerOpts := &rules.ManagerOptions{
TelemetryStore: telemetryStore,
MetadataStore: metadataStore,
Prometheus: prometheus,
Context: context.Background(),
Querier: querier,
Logger: providerSettings.Logger,
Cache: cache,
EvalDelay: constants.GetEvalDelay(),
OrgGetter: orgGetter,
Alertmanager: alertmanager,
RuleStore: ruleStore,
MaintenanceStore: maintenanceStore,
SQLStore: sqlstore,
QueryParser: queryParser,
RuleStateHistoryModule: ruleStateHistoryModule,
}
// create Manager
manager, err := rules.NewManager(managerOpts)
if err != nil {
return nil, fmt.Errorf("rule manager error: %v", err)
}
slog.Info("rules manager is ready")
return manager, nil
}

View File

@@ -1,10 +1,13 @@
package ruler
import (
"time"
"github.com/SigNoz/signoz/pkg/factory"
)
type Config struct {
EvalDelay time.Duration `mapstructure:"eval_delay"`
}
func NewConfigFactory() factory.ConfigFactory {
@@ -12,7 +15,9 @@ func NewConfigFactory() factory.ConfigFactory {
}
func newConfig() factory.Config {
return Config{}
return Config{
EvalDelay: 2 * time.Minute,
}
}
func (c Config) Validate() error {

19
pkg/ruler/handler.go Normal file
View File

@@ -0,0 +1,19 @@
package ruler
import "net/http"
type Handler interface {
ListRules(http.ResponseWriter, *http.Request)
GetRuleByID(http.ResponseWriter, *http.Request)
CreateRule(http.ResponseWriter, *http.Request)
UpdateRuleByID(http.ResponseWriter, *http.Request)
DeleteRuleByID(http.ResponseWriter, *http.Request)
PatchRuleByID(http.ResponseWriter, *http.Request)
TestRule(http.ResponseWriter, *http.Request)
ListDowntimeSchedules(http.ResponseWriter, *http.Request)
GetDowntimeScheduleByID(http.ResponseWriter, *http.Request)
CreateDowntimeSchedule(http.ResponseWriter, *http.Request)
UpdateDowntimeScheduleByID(http.ResponseWriter, *http.Request)
DeleteDowntimeScheduleByID(http.ResponseWriter, *http.Request)
}

View File

@@ -1,7 +1,48 @@
package ruler
import "github.com/SigNoz/signoz/pkg/statsreporter"
import (
"context"
"github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/statsreporter"
"github.com/SigNoz/signoz/pkg/types/ruletypes"
"github.com/SigNoz/signoz/pkg/valuer"
)
type Ruler interface {
factory.Service
statsreporter.StatsCollector
// ListRuleStates returns all rules with their current evaluation state.
ListRuleStates(ctx context.Context) (*ruletypes.GettableRules, error)
// GetRule returns a single rule by ID.
GetRule(ctx context.Context, id valuer.UUID) (*ruletypes.GettableRule, error)
// CreateRule persists a new rule from a JSON string and starts its evaluator.
// TODO: accept PostableRule instead of raw string; the manager currently unmarshals
// internally because it stores the raw JSON as Data. Requires changing the storage
// model to store structured data.
CreateRule(ctx context.Context, ruleStr string) (*ruletypes.GettableRule, error)
// EditRule replaces the rule identified by id with the given JSON string.
// TODO: same as CreateRule — accept PostableRule instead of raw string.
EditRule(ctx context.Context, ruleStr string, id valuer.UUID) error
// DeleteRule removes the rule identified by the string ID.
// TODO: accept valuer.UUID instead of string for consistency with other methods.
DeleteRule(ctx context.Context, idStr string) error
// PatchRule applies a partial update to the rule identified by id.
// TODO: same as CreateRule — accept PostableRule instead of raw string.
PatchRule(ctx context.Context, ruleStr string, id valuer.UUID) (*ruletypes.GettableRule, error)
// TestNotification fires a test alert for the rule defined in ruleStr.
// TODO: same as CreateRule — accept PostableRule instead of raw string.
TestNotification(ctx context.Context, orgID valuer.UUID, ruleStr string) (int, error)
// MaintenanceStore returns the store for planned maintenance / downtime schedules.
// TODO: expose downtime CRUD as methods on Ruler directly instead of leaking the
// store interface. The handler should not call store methods directly.
MaintenanceStore() ruletypes.MaintenanceStore
}

View File

@@ -0,0 +1,314 @@
package signozruler
import (
"context"
"io"
"net/http"
"time"
"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/ruler"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/types/ruletypes"
"github.com/SigNoz/signoz/pkg/valuer"
"github.com/gorilla/mux"
)
type handler struct {
ruler ruler.Ruler
}
func NewHandler(ruler ruler.Ruler) ruler.Handler {
return &handler{ruler: ruler}
}
func (handler *handler) ListRules(rw http.ResponseWriter, req *http.Request) {
ctx, cancel := context.WithTimeout(req.Context(), 30*time.Second)
defer cancel()
rules, err := handler.ruler.ListRuleStates(ctx)
if err != nil {
render.Error(rw, err)
return
}
render.Success(rw, http.StatusOK, rules)
}
func (handler *handler) GetRuleByID(rw http.ResponseWriter, req *http.Request) {
ctx, cancel := context.WithTimeout(req.Context(), 30*time.Second)
defer cancel()
id, err := valuer.NewUUID(mux.Vars(req)["id"])
if err != nil {
render.Error(rw, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "id is not a valid uuid-v7"))
return
}
rule, err := handler.ruler.GetRule(ctx, id)
if err != nil {
render.Error(rw, err)
return
}
render.Success(rw, http.StatusOK, rule)
}
func (handler *handler) CreateRule(rw http.ResponseWriter, req *http.Request) {
ctx, cancel := context.WithTimeout(req.Context(), 30*time.Second)
defer cancel()
body, err := io.ReadAll(req.Body)
if err != nil {
render.Error(rw, err)
return
}
defer req.Body.Close() //nolint:errcheck
rule, err := handler.ruler.CreateRule(ctx, string(body))
if err != nil {
render.Error(rw, err)
return
}
render.Success(rw, http.StatusOK, rule)
}
func (handler *handler) UpdateRuleByID(rw http.ResponseWriter, req *http.Request) {
ctx, cancel := context.WithTimeout(req.Context(), 30*time.Second)
defer cancel()
id, err := valuer.NewUUID(mux.Vars(req)["id"])
if err != nil {
render.Error(rw, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "id is not a valid uuid-v7"))
return
}
body, err := io.ReadAll(req.Body)
if err != nil {
render.Error(rw, err)
return
}
defer req.Body.Close() //nolint:errcheck
err = handler.ruler.EditRule(ctx, string(body), id)
if err != nil {
render.Error(rw, err)
return
}
render.Success(rw, http.StatusNoContent, nil)
}
func (handler *handler) DeleteRuleByID(rw http.ResponseWriter, req *http.Request) {
ctx, cancel := context.WithTimeout(req.Context(), 30*time.Second)
defer cancel()
idStr := mux.Vars(req)["id"]
err := handler.ruler.DeleteRule(ctx, idStr)
if err != nil {
render.Error(rw, err)
return
}
render.Success(rw, http.StatusNoContent, nil)
}
func (handler *handler) PatchRuleByID(rw http.ResponseWriter, req *http.Request) {
ctx, cancel := context.WithTimeout(req.Context(), 30*time.Second)
defer cancel()
id, err := valuer.NewUUID(mux.Vars(req)["id"])
if err != nil {
render.Error(rw, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "id is not a valid uuid-v7"))
return
}
body, err := io.ReadAll(req.Body)
if err != nil {
render.Error(rw, err)
return
}
defer req.Body.Close() //nolint:errcheck
rule, err := handler.ruler.PatchRule(ctx, string(body), id)
if err != nil {
render.Error(rw, err)
return
}
render.Success(rw, http.StatusOK, rule)
}
func (handler *handler) TestRule(rw http.ResponseWriter, req *http.Request) {
ctx, cancel := context.WithTimeout(req.Context(), 1*time.Minute)
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, err)
return
}
body, err := io.ReadAll(req.Body)
if err != nil {
render.Error(rw, err)
return
}
defer req.Body.Close() //nolint:errcheck
alertCount, err := handler.ruler.TestNotification(ctx, orgID, string(body))
if err != nil {
render.Error(rw, err)
return
}
render.Success(rw, http.StatusOK, ruletypes.GettableTestRule{AlertCount: alertCount, Message: "notification sent"})
}
func (handler *handler) ListDowntimeSchedules(rw http.ResponseWriter, req *http.Request) {
ctx, cancel := context.WithTimeout(req.Context(), 30*time.Second)
defer cancel()
claims, err := authtypes.ClaimsFromContext(ctx)
if err != nil {
render.Error(rw, err)
return
}
var params ruletypes.ListPlannedMaintenanceParams
if err := binding.Query.BindQuery(req.URL.Query(), &params); err != nil {
render.Error(rw, err)
return
}
schedules, err := handler.ruler.MaintenanceStore().GetAllPlannedMaintenance(ctx, claims.OrgID)
if err != nil {
render.Error(rw, err)
return
}
if params.Active != nil {
activeSchedules := make([]*ruletypes.GettablePlannedMaintenance, 0)
for _, schedule := range schedules {
now := time.Now().In(time.FixedZone(schedule.Schedule.Timezone, 0))
if schedule.IsActive(now) == *params.Active {
activeSchedules = append(activeSchedules, schedule)
}
}
schedules = activeSchedules
}
if params.Recurring != nil {
recurringSchedules := make([]*ruletypes.GettablePlannedMaintenance, 0)
for _, schedule := range schedules {
if schedule.IsRecurring() == *params.Recurring {
recurringSchedules = append(recurringSchedules, schedule)
}
}
schedules = recurringSchedules
}
render.Success(rw, http.StatusOK, schedules)
}
func (handler *handler) GetDowntimeScheduleByID(rw http.ResponseWriter, req *http.Request) {
ctx, cancel := context.WithTimeout(req.Context(), 30*time.Second)
defer cancel()
id, err := valuer.NewUUID(mux.Vars(req)["id"])
if err != nil {
render.Error(rw, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "id is not a valid uuid-v7"))
return
}
schedule, err := handler.ruler.MaintenanceStore().GetPlannedMaintenanceByID(ctx, id)
if err != nil {
render.Error(rw, err)
return
}
render.Success(rw, http.StatusOK, schedule)
}
func (handler *handler) CreateDowntimeSchedule(rw http.ResponseWriter, req *http.Request) {
ctx, cancel := context.WithTimeout(req.Context(), 30*time.Second)
defer cancel()
var schedule ruletypes.GettablePlannedMaintenance
if err := binding.JSON.BindBody(req.Body, &schedule); err != nil {
render.Error(rw, err)
return
}
if err := schedule.Validate(); err != nil {
render.Error(rw, err)
return
}
_, err := handler.ruler.MaintenanceStore().CreatePlannedMaintenance(ctx, schedule)
if err != nil {
render.Error(rw, err)
return
}
render.Success(rw, http.StatusOK, nil)
}
func (handler *handler) UpdateDowntimeScheduleByID(rw http.ResponseWriter, req *http.Request) {
ctx, cancel := context.WithTimeout(req.Context(), 30*time.Second)
defer cancel()
id, err := valuer.NewUUID(mux.Vars(req)["id"])
if err != nil {
render.Error(rw, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "id is not a valid uuid-v7"))
return
}
var schedule ruletypes.GettablePlannedMaintenance
if err := binding.JSON.BindBody(req.Body, &schedule); err != nil {
render.Error(rw, err)
return
}
if err := schedule.Validate(); err != nil {
render.Error(rw, err)
return
}
err = handler.ruler.MaintenanceStore().EditPlannedMaintenance(ctx, schedule, id)
if err != nil {
render.Error(rw, err)
return
}
render.Success(rw, http.StatusNoContent, nil)
}
func (handler *handler) DeleteDowntimeScheduleByID(rw http.ResponseWriter, req *http.Request) {
ctx, cancel := context.WithTimeout(req.Context(), 30*time.Second)
defer cancel()
id, err := valuer.NewUUID(mux.Vars(req)["id"])
if err != nil {
render.Error(rw, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "id is not a valid uuid-v7"))
return
}
err = handler.ruler.MaintenanceStore().DeletePlannedMaintenance(ctx, id)
if err != nil {
render.Error(rw, err)
return
}
render.Success(rw, http.StatusNoContent, nil)
}

View File

@@ -3,27 +3,87 @@ package signozruler
import (
"context"
"github.com/SigNoz/signoz/pkg/alertmanager"
"github.com/SigNoz/signoz/pkg/cache"
"github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/modules/organization"
"github.com/SigNoz/signoz/pkg/modules/rulestatehistory"
"github.com/SigNoz/signoz/pkg/prometheus"
"github.com/SigNoz/signoz/pkg/querier"
"github.com/SigNoz/signoz/pkg/query-service/rules"
"github.com/SigNoz/signoz/pkg/queryparser"
"github.com/SigNoz/signoz/pkg/ruler"
"github.com/SigNoz/signoz/pkg/ruler/rulestore/sqlrulestore"
"github.com/SigNoz/signoz/pkg/sqlstore"
"github.com/SigNoz/signoz/pkg/telemetrystore"
"github.com/SigNoz/signoz/pkg/types/ruletypes"
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
"github.com/SigNoz/signoz/pkg/valuer"
)
type provider struct {
manager *rules.Manager
ruleStore ruletypes.RuleStore
stopC chan struct{}
}
func NewFactory(sqlstore sqlstore.SQLStore, queryParser queryparser.QueryParser) factory.ProviderFactory[ruler.Ruler, ruler.Config] {
return factory.NewProviderFactory(factory.MustNewName("signoz"), func(ctx context.Context, settings factory.ProviderSettings, config ruler.Config) (ruler.Ruler, error) {
return New(ctx, settings, config, sqlstore, queryParser)
func NewFactory(
cache cache.Cache,
alertmanager alertmanager.Alertmanager,
sqlstore sqlstore.SQLStore,
telemetryStore telemetrystore.TelemetryStore,
metadataStore telemetrytypes.MetadataStore,
prometheus prometheus.Prometheus,
orgGetter organization.Getter,
ruleStateHistoryModule rulestatehistory.Module,
querier querier.Querier,
queryParser queryparser.QueryParser,
prepareTaskFunc func(rules.PrepareTaskOptions) (rules.Task, error),
prepareTestRuleFunc func(rules.PrepareTestRuleOptions) (int, error),
) factory.ProviderFactory[ruler.Ruler, ruler.Config] {
return factory.NewProviderFactory(factory.MustNewName("signoz"), func(ctx context.Context, providerSettings factory.ProviderSettings, config ruler.Config) (ruler.Ruler, error) {
ruleStore := sqlrulestore.NewRuleStore(sqlstore, queryParser, providerSettings)
maintenanceStore := sqlrulestore.NewMaintenanceStore(sqlstore)
managerOpts := &rules.ManagerOptions{
TelemetryStore: telemetryStore,
MetadataStore: metadataStore,
Prometheus: prometheus,
Context: context.Background(),
Querier: querier,
Logger: providerSettings.Logger,
Cache: cache,
EvalDelay: valuer.MustParseTextDuration(config.EvalDelay.String()),
PrepareTaskFunc: prepareTaskFunc,
PrepareTestRuleFunc: prepareTestRuleFunc,
Alertmanager: alertmanager,
OrgGetter: orgGetter,
RuleStore: ruleStore,
MaintenanceStore: maintenanceStore,
SQLStore: sqlstore,
QueryParser: queryParser,
RuleStateHistoryModule: ruleStateHistoryModule,
}
manager, err := rules.NewManager(managerOpts)
if err != nil {
return nil, err
}
return &provider{manager: manager, ruleStore: ruleStore, stopC: make(chan struct{})}, nil
})
}
func New(ctx context.Context, settings factory.ProviderSettings, config ruler.Config, sqlstore sqlstore.SQLStore, queryParser queryparser.QueryParser) (ruler.Ruler, error) {
return &provider{ruleStore: sqlrulestore.NewRuleStore(sqlstore, queryParser, settings)}, nil
func (provider *provider) Start(ctx context.Context) error {
provider.manager.Start(ctx)
<-provider.stopC
return nil
}
func (provider *provider) Stop(ctx context.Context) error {
close(provider.stopC)
provider.manager.Stop(ctx)
return nil
}
func (provider *provider) Collect(ctx context.Context, orgID valuer.UUID) (map[string]any, error) {
@@ -34,3 +94,35 @@ func (provider *provider) Collect(ctx context.Context, orgID valuer.UUID) (map[s
return ruletypes.NewStatsFromRules(rules), nil
}
func (provider *provider) ListRuleStates(ctx context.Context) (*ruletypes.GettableRules, error) {
return provider.manager.ListRuleStates(ctx)
}
func (provider *provider) GetRule(ctx context.Context, id valuer.UUID) (*ruletypes.GettableRule, error) {
return provider.manager.GetRule(ctx, id)
}
func (provider *provider) CreateRule(ctx context.Context, ruleStr string) (*ruletypes.GettableRule, error) {
return provider.manager.CreateRule(ctx, ruleStr)
}
func (provider *provider) EditRule(ctx context.Context, ruleStr string, id valuer.UUID) error {
return provider.manager.EditRule(ctx, ruleStr, id)
}
func (provider *provider) DeleteRule(ctx context.Context, idStr string) error {
return provider.manager.DeleteRule(ctx, idStr)
}
func (provider *provider) PatchRule(ctx context.Context, ruleStr string, id valuer.UUID) (*ruletypes.GettableRule, error) {
return provider.manager.PatchRule(ctx, ruleStr, id)
}
func (provider *provider) TestNotification(ctx context.Context, orgID valuer.UUID, ruleStr string) (int, error) {
return provider.manager.TestNotification(ctx, orgID, ruleStr)
}
func (provider *provider) MaintenanceStore() ruletypes.MaintenanceStore {
return provider.manager.MaintenanceStore()
}

View File

@@ -3,6 +3,8 @@ package signoz
import (
"github.com/SigNoz/signoz/pkg/alertmanager"
"github.com/SigNoz/signoz/pkg/alertmanager/signozalertmanager"
"github.com/SigNoz/signoz/pkg/ruler"
"github.com/SigNoz/signoz/pkg/ruler/signozruler"
"github.com/SigNoz/signoz/pkg/analytics"
"github.com/SigNoz/signoz/pkg/authz"
"github.com/SigNoz/signoz/pkg/authz/signozauthzapi"
@@ -65,6 +67,7 @@ type Handlers struct {
CloudIntegrationHandler cloudintegration.Handler
RuleStateHistory rulestatehistory.Handler
AlertmanagerHandler alertmanager.Handler
RulerHandler ruler.Handler
}
func NewHandlers(
@@ -81,6 +84,7 @@ func NewHandlers(
zeusService zeus.Zeus,
registryHandler factory.Handler,
alertmanagerService alertmanager.Alertmanager,
rulerService ruler.Ruler,
) Handlers {
return Handlers{
SavedView: implsavedview.NewHandler(modules.SavedView),
@@ -104,5 +108,6 @@ func NewHandlers(
RuleStateHistory: implrulestatehistory.NewHandler(modules.RuleStateHistory),
CloudIntegrationHandler: implcloudintegration.NewHandler(modules.CloudIntegration),
AlertmanagerHandler: signozalertmanager.NewHandler(alertmanagerService),
RulerHandler: signozruler.NewHandler(rulerService),
}
}

View File

@@ -56,7 +56,7 @@ func TestNewHandlers(t *testing.T) {
querierHandler := querier.NewHandler(providerSettings, nil, nil)
registryHandler := factory.NewHandler(nil)
handlers := NewHandlers(modules, providerSettings, nil, querierHandler, nil, nil, nil, nil, nil, nil, nil, registryHandler, alertmanager)
handlers := NewHandlers(modules, providerSettings, nil, querierHandler, nil, nil, nil, nil, nil, nil, nil, registryHandler, alertmanager, nil)
reflectVal := reflect.ValueOf(handlers)
for i := 0; i < reflectVal.NumField(); i++ {
f := reflectVal.Field(i)

View File

@@ -31,6 +31,7 @@ import (
"github.com/SigNoz/signoz/pkg/modules/session"
"github.com/SigNoz/signoz/pkg/modules/user"
"github.com/SigNoz/signoz/pkg/querier"
"github.com/SigNoz/signoz/pkg/ruler"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/zeus"
"github.com/swaggest/jsonschema-go"
@@ -71,6 +72,7 @@ func NewOpenAPI(ctx context.Context, instrumentation instrumentation.Instrumenta
struct{ cloudintegration.Handler }{},
struct{ rulestatehistory.Handler }{},
struct{ alertmanager.Handler }{},
struct{ ruler.Handler }{},
).New(ctx, instrumentation.ToProviderSettings(), apiserver.Config{})
if err != nil {
return nil, err

View File

@@ -44,9 +44,6 @@ import (
"github.com/SigNoz/signoz/pkg/prometheus/clickhouseprometheus"
"github.com/SigNoz/signoz/pkg/querier"
"github.com/SigNoz/signoz/pkg/querier/signozquerier"
"github.com/SigNoz/signoz/pkg/queryparser"
"github.com/SigNoz/signoz/pkg/ruler"
"github.com/SigNoz/signoz/pkg/ruler/signozruler"
"github.com/SigNoz/signoz/pkg/sharder"
"github.com/SigNoz/signoz/pkg/sharder/noopsharder"
"github.com/SigNoz/signoz/pkg/sharder/singlesharder"
@@ -229,11 +226,7 @@ func NewAlertmanagerProviderFactories(sqlstore sqlstore.SQLStore, orgGetter orga
)
}
func NewRulerProviderFactories(sqlstore sqlstore.SQLStore, queryParser queryparser.QueryParser) factory.NamedMap[factory.ProviderFactory[ruler.Ruler, ruler.Config]] {
return factory.MustNewNamedMap(
signozruler.NewFactory(sqlstore, queryParser),
)
}
func NewEmailingProviderFactories() factory.NamedMap[factory.ProviderFactory[emailing.Emailing, emailing.Config]] {
return factory.MustNewNamedMap(
@@ -289,6 +282,7 @@ func NewAPIServerProviderFactories(orgGetter organization.Getter, authz authz.Au
handlers.CloudIntegrationHandler,
handlers.RuleStateHistory,
handlers.AlertmanagerHandler,
handlers.RulerHandler,
),
)
}

View File

@@ -11,7 +11,6 @@ import (
"github.com/SigNoz/signoz/pkg/instrumentation/instrumentationtest"
"github.com/SigNoz/signoz/pkg/modules/organization/implorganization"
"github.com/SigNoz/signoz/pkg/modules/user/impluser"
"github.com/SigNoz/signoz/pkg/queryparser"
"github.com/SigNoz/signoz/pkg/sqlschema"
"github.com/SigNoz/signoz/pkg/sqlschema/sqlschematest"
"github.com/SigNoz/signoz/pkg/sqlstore"
@@ -64,11 +63,6 @@ func TestNewProviderFactories(t *testing.T) {
NewAlertmanagerProviderFactories(sqlstoretest.New(sqlstore.Config{Provider: "sqlite"}, sqlmock.QueryMatcherEqual), orgGetter, notificationManager)
})
assert.NotPanics(t, func() {
queryParser := queryparser.New(instrumentationtest.New().ToProviderSettings())
NewRulerProviderFactories(sqlstoretest.New(sqlstore.Config{Provider: "sqlite"}, sqlmock.QueryMatcherEqual), queryParser)
})
assert.NotPanics(t, func() {
NewEmailingProviderFactories()
})

View File

@@ -26,12 +26,14 @@ import (
"github.com/SigNoz/signoz/pkg/modules/dashboard"
"github.com/SigNoz/signoz/pkg/modules/organization"
"github.com/SigNoz/signoz/pkg/modules/organization/implorganization"
"github.com/SigNoz/signoz/pkg/modules/rulestatehistory"
"github.com/SigNoz/signoz/pkg/modules/serviceaccount"
"github.com/SigNoz/signoz/pkg/modules/serviceaccount/implserviceaccount"
"github.com/SigNoz/signoz/pkg/modules/user/impluser"
"github.com/SigNoz/signoz/pkg/prometheus"
"github.com/SigNoz/signoz/pkg/querier"
"github.com/SigNoz/signoz/pkg/queryparser"
"github.com/SigNoz/signoz/pkg/ruler"
"github.com/SigNoz/signoz/pkg/sharder"
"github.com/SigNoz/signoz/pkg/sqlmigration"
"github.com/SigNoz/signoz/pkg/sqlmigrator"
@@ -75,6 +77,7 @@ type SigNoz struct {
Tokenizer pkgtokenizer.Tokenizer
IdentNResolver identn.IdentNResolver
Authz authz.AuthZ
Ruler ruler.Ruler
Modules Modules
Handlers Handlers
QueryParser queryparser.QueryParser
@@ -103,6 +106,7 @@ func New(
auditorProviderFactories func(licensing.Licensing) factory.NamedMap[factory.ProviderFactory[auditor.Auditor, auditor.Config]],
querierHandlerCallback func(factory.ProviderSettings, querier.Querier, analytics.Analytics) querier.Handler,
cloudIntegrationCallback func(sqlstore.SQLStore, global.Global, zeus.Zeus, gateway.Gateway, licensing.Licensing, serviceaccount.Module, cloudintegration.Config) (cloudintegration.Module, error),
rulerProviderFactories func(cache.Cache, alertmanager.Alertmanager, sqlstore.SQLStore, telemetrystore.TelemetryStore, telemetrytypes.MetadataStore, prometheus.Prometheus, organization.Getter, rulestatehistory.Module, querier.Querier, queryparser.QueryParser) factory.NamedMap[factory.ProviderFactory[ruler.Ruler, ruler.Config]],
) (*SigNoz, error) {
// Initialize instrumentation
instrumentation, err := instrumentation.New(ctx, config.Instrumentation, version.Info, "signoz")
@@ -361,18 +365,6 @@ func New(
return nil, err
}
// Initialize ruler from the available ruler provider factories
ruler, err := factory.NewProviderFromNamedMap(
ctx,
providerSettings,
config.Ruler,
NewRulerProviderFactories(sqlstore, queryParser),
"signoz",
)
if err != nil {
return nil, err
}
gatewayFactory := gatewayProviderFactory(licensing)
gateway, err := gatewayFactory.New(ctx, providerSettings, config.Gateway)
if err != nil {
@@ -441,6 +433,12 @@ func New(
// Initialize all modules
modules := NewModules(sqlstore, tokenizer, emailing, providerSettings, orgGetter, alertmanager, analytics, querier, telemetrystore, telemetryMetadataStore, authNs, authz, cache, queryParser, config, dashboard, userGetter, userRoleStore, serviceAccount, cloudIntegrationModule)
// Initialize ruler from the variant-specific provider factories
rulerInstance, err := factory.NewProviderFromNamedMap(ctx, providerSettings, config.Ruler, rulerProviderFactories(cache, alertmanager, sqlstore, telemetrystore, telemetryMetadataStore, prometheus, orgGetter, modules.RuleStateHistory, querier, queryParser), "signoz")
if err != nil {
return nil, err
}
// Initialize identN resolver
identNFactories := NewIdentNProviderFactories(tokenizer, serviceAccount, orgGetter, userGetter, config.User)
identNResolver, err := identn.NewIdentNResolver(ctx, providerSettings, config.IdentN, identNFactories)
@@ -456,7 +454,7 @@ func New(
// Create a list of all stats collectors
statsCollectors := []statsreporter.StatsCollector{
alertmanager,
ruler,
rulerInstance,
modules.Dashboard,
modules.SavedView,
modules.UserSetter,
@@ -493,6 +491,7 @@ func New(
factory.NewNamedService(factory.MustNewName("authz"), authz),
factory.NewNamedService(factory.MustNewName("user"), userService, factory.MustNewName("authz")),
factory.NewNamedService(factory.MustNewName("auditor"), auditor),
factory.NewNamedService(factory.MustNewName("ruler"), rulerInstance),
)
if err != nil {
return nil, err
@@ -500,7 +499,7 @@ func New(
// Initialize all handlers for the modules
registryHandler := factory.NewHandler(registry)
handlers := NewHandlers(modules, providerSettings, analytics, querierHandler, licensing, global, flagger, gateway, telemetryMetadataStore, authz, zeus, registryHandler, alertmanager)
handlers := NewHandlers(modules, providerSettings, analytics, querierHandler, licensing, global, flagger, gateway, telemetryMetadataStore, authz, zeus, registryHandler, alertmanager, rulerInstance)
// Initialize the API server (after registry so it can access service health)
apiserverInstance, err := factory.NewProviderFromNamedMap(
@@ -534,6 +533,7 @@ func New(
Tokenizer: tokenizer,
IdentNResolver: identNResolver,
Authz: authz,
Ruler: rulerInstance,
Modules: modules,
Handlers: handlers,
QueryParser: queryParser,

View File

@@ -31,8 +31,9 @@ type PostableResetPassword struct {
}
type ChangePasswordRequest struct {
OldPassword string `json:"oldPassword"`
NewPassword string `json:"newPassword"`
UserID valuer.UUID `json:"userId"`
OldPassword string `json:"oldPassword"`
NewPassword string `json:"newPassword"`
}
type PostableForgotPassword struct {

View File

@@ -38,14 +38,14 @@ const (
// PostableRule is used to create alerting rule from HTTP api.
type PostableRule struct {
AlertName string `json:"alert"`
AlertName string `json:"alert" required:"true"`
AlertType AlertType `json:"alertType,omitempty"`
Description string `json:"description,omitempty"`
RuleType RuleType `json:"ruleType,omitzero"`
RuleType RuleType `json:"ruleType,omitzero" required:"true"`
EvalWindow valuer.TextDuration `json:"evalWindow,omitzero"`
Frequency valuer.TextDuration `json:"frequency,omitzero"`
RuleCondition *RuleCondition `json:"condition,omitempty"`
RuleCondition *RuleCondition `json:"condition,omitempty" required:"true"`
Labels map[string]string `json:"labels,omitempty"`
Annotations map[string]string `json:"annotations,omitempty"`
@@ -586,6 +586,11 @@ func testTemplateParsing(rl *PostableRule) (errs []error) {
}
// GettableRules has info for all stored rules.
type GettableTestRule struct {
AlertCount int `json:"alertCount"`
Message string `json:"message"`
}
type GettableRules struct {
Rules []*GettableRule `json:"rules"`
}

View File

@@ -6,6 +6,7 @@ import (
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/valuer"
"github.com/swaggest/jsonschema-go"
)
type EvaluationKind struct {
@@ -229,6 +230,29 @@ type EvaluationEnvelope struct {
Spec any `json:"spec"`
}
// evaluationRolling is the OpenAPI schema for an EvaluationEnvelope with kind=rolling.
type evaluationRolling struct {
Kind EvaluationKind `json:"kind" description:"The kind of evaluation."`
Spec RollingWindow `json:"spec" description:"The rolling window evaluation specification."`
}
// evaluationCumulative is the OpenAPI schema for an EvaluationEnvelope with kind=cumulative.
type evaluationCumulative struct {
Kind EvaluationKind `json:"kind" description:"The kind of evaluation."`
Spec CumulativeWindow `json:"spec" description:"The cumulative window evaluation specification."`
}
var _ jsonschema.OneOfExposer = EvaluationEnvelope{}
// JSONSchemaOneOf returns the oneOf variants for the EvaluationEnvelope discriminated union.
// Each variant represents a different evaluation kind with its corresponding spec schema.
func (EvaluationEnvelope) JSONSchemaOneOf() []any {
return []any{
evaluationRolling{},
evaluationCumulative{},
}
}
func (e *EvaluationEnvelope) UnmarshalJSON(data []byte) error {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {

View File

@@ -331,6 +331,11 @@ func (m *GettablePlannedMaintenanceRule) ConvertGettableMaintenanceRuleToGettabl
}
}
type ListPlannedMaintenanceParams struct {
Active *bool `query:"active"`
Recurring *bool `query:"recurring"`
}
type MaintenanceStore interface {
CreatePlannedMaintenance(context.Context, GettablePlannedMaintenance) (valuer.UUID, error)
DeletePlannedMaintenance(context.Context, valuer.UUID) error

View File

@@ -11,6 +11,7 @@ import (
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
"github.com/SigNoz/signoz/pkg/units"
"github.com/SigNoz/signoz/pkg/valuer"
"github.com/swaggest/jsonschema-go"
)
type ThresholdKind struct {
@@ -26,6 +27,22 @@ type RuleThresholdData struct {
Spec any `json:"spec"`
}
// thresholdBasic is the OpenAPI schema for a RuleThresholdData with kind=basic.
type thresholdBasic struct {
Kind ThresholdKind `json:"kind" description:"The kind of threshold."`
Spec BasicRuleThresholds `json:"spec" description:"The basic threshold specification (array of thresholds)."`
}
var _ jsonschema.OneOfExposer = RuleThresholdData{}
// JSONSchemaOneOf returns the oneOf variants for the RuleThresholdData discriminated union.
// Each variant represents a different threshold kind with its corresponding spec schema.
func (RuleThresholdData) JSONSchemaOneOf() []any {
return []any{
thresholdBasic{},
}
}
func (r *RuleThresholdData) UnmarshalJSON(data []byte) error {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {

View File

@@ -284,7 +284,6 @@ type UserStore interface {
GetPasswordByUserID(ctx context.Context, userID valuer.UUID) (*FactorPassword, error)
GetResetPasswordToken(ctx context.Context, token string) (*ResetPasswordToken, error)
GetResetPasswordTokenByPasswordID(ctx context.Context, passwordID valuer.UUID) (*ResetPasswordToken, error)
GetResetPasswordTokenByOrgIDAndUserID(ctx context.Context, orgID valuer.UUID, userID valuer.UUID) (*ResetPasswordToken, error)
DeleteResetPasswordTokenByPasswordID(ctx context.Context, passwordID valuer.UUID) error
UpdatePassword(ctx context.Context, password *FactorPassword) error

View File

@@ -39,14 +39,20 @@ def test_change_password(
)
assert response.status_code == HTTPStatus.NO_CONTENT
# Get the user id via v2
found_user = find_user_by_email(signoz, admin_token, PASSWORD_USER_EMAIL)
# Try logging in with the password
token = get_token(PASSWORD_USER_EMAIL, PASSWORD_USER_PASSWORD)
assert token is not None
# Try changing the password with a bad old password which should fail
response = requests.put(
signoz.self.host_configs["8080"].get("/api/v2/users/me/factor_password"),
response = requests.post(
signoz.self.host_configs["8080"].get(
f"/api/v1/changePassword/{found_user['id']}"
),
json={
"userId": f"{found_user['id']}",
"oldPassword": "password",
"newPassword": PASSWORD_USER_PASSWORD,
},
@@ -57,9 +63,12 @@ def test_change_password(
assert response.status_code == HTTPStatus.BAD_REQUEST
# Try changing the password with a good old password
response = requests.put(
signoz.self.host_configs["8080"].get("/api/v2/users/me/factor_password"),
response = requests.post(
signoz.self.host_configs["8080"].get(
f"/api/v1/changePassword/{found_user['id']}"
),
json={
"userId": f"{found_user['id']}",
"oldPassword": PASSWORD_USER_PASSWORD,
"newPassword": "password123Znew$",
},
@@ -82,42 +91,17 @@ def test_reset_password(
# Get the user id via v2
found_user = find_user_by_email(signoz, admin_token, PASSWORD_USER_EMAIL)
# Create a reset password token via v2 PUT
response = requests.put(
signoz.self.host_configs["8080"].get(
f"/api/v2/users/{found_user['id']}/reset_password_tokens"
),
headers={"Authorization": f"Bearer {admin_token}"},
timeout=2,
)
assert response.status_code == HTTPStatus.CREATED, response.text
token_data = response.json()["data"]
assert "token" in token_data
assert "expiresAt" in token_data
token = token_data["token"]
# Calling PUT again should return the same token (still valid)
response = requests.put(
signoz.self.host_configs["8080"].get(
f"/api/v2/users/{found_user['id']}/reset_password_tokens"
),
headers={"Authorization": f"Bearer {admin_token}"},
timeout=2,
)
assert response.status_code == HTTPStatus.CREATED, response.text
assert response.json()["data"]["token"] == token
# GET should also return the same token
response = requests.get(
signoz.self.host_configs["8080"].get(
f"/api/v2/users/{found_user['id']}/reset_password_tokens"
f"/api/v1/getResetPasswordToken/{found_user['id']}"
),
headers={"Authorization": f"Bearer {admin_token}"},
timeout=2,
)
assert response.status_code == HTTPStatus.OK, response.text
assert response.json()["data"]["token"] == token
assert response.status_code == HTTPStatus.OK
token = response.json()["data"]["token"]
# Reset the password with a bad password which should fail
response = requests.post(
@@ -156,29 +140,18 @@ def test_reset_password_with_no_password(
)
assert result.rowcount == 1
# GET should return 404 since there's no password (and thus no token)
# Generate a new reset password token
response = requests.get(
signoz.self.host_configs["8080"].get(
f"/api/v2/users/{found_user['id']}/reset_password_tokens"
),
headers={"Authorization": f"Bearer {admin_token}"},
timeout=2,
)
assert response.status_code == HTTPStatus.NOT_FOUND, response.text
# Generate a new reset password token via v2 PUT
response = requests.put(
signoz.self.host_configs["8080"].get(
f"/api/v2/users/{found_user['id']}/reset_password_tokens"
f"/api/v1/getResetPasswordToken/{found_user['id']}"
),
headers={"Authorization": f"Bearer {admin_token}"},
timeout=2,
)
assert response.status_code == HTTPStatus.CREATED, response.text
token_data = response.json()["data"]
assert "expiresAt" in token_data
token = token_data["token"]
assert response.status_code == HTTPStatus.OK
token = response.json()["data"]["token"]
# Reset the password with a good password
response = requests.post(
@@ -289,22 +262,32 @@ def test_forgot_password_creates_reset_token(
)
assert response.status_code == HTTPStatus.NO_CONTENT
# Verify reset password token was created via the v2 GET endpoint
# Verify reset password token was created by querying the database
found_user = find_user_by_email(signoz, admin_token, forgot_email)
response = requests.get(
signoz.self.host_configs["8080"].get(
f"/api/v2/users/{found_user['id']}/reset_password_tokens"
),
headers={"Authorization": f"Bearer {admin_token}"},
timeout=2,
)
assert response.status_code == HTTPStatus.OK, response.text
token_data = response.json()["data"]
reset_token = token_data["token"]
reset_token = None
# Query the database directly to get the reset password token
# First get the password_id from factor_password, then get the token
with signoz.sqlstore.conn.connect() as conn:
result = conn.execute(
sql.text(
"""
SELECT rpt.token
FROM reset_password_token rpt
JOIN factor_password fp ON rpt.password_id = fp.id
WHERE fp.user_id = :user_id
"""
),
{"user_id": found_user["id"]},
)
row = result.fetchone()
assert (
row is not None
), "Reset password token should exist after calling forgotPassword"
reset_token = row[0]
assert reset_token is not None
assert reset_token != ""
assert "expiresAt" in token_data
# Reset password with a valid strong password
response = requests.post(