diff --git a/apier/v1/apier_it_test.go b/apier/v1/apier_it_test.go
index ff3570b07..c9a6b2beb 100644
--- a/apier/v1/apier_it_test.go
+++ b/apier/v1/apier_it_test.go
@@ -1061,7 +1061,7 @@ func testApierGetActionTrigger(t *testing.T) {
//set an ActionTrigger in database
var reply string
if err := rater.Call(context.Background(), utils.APIerSv1SetActionTrigger,
- AttrSetActionTrigger{
+ engine.AttrSetActionTrigger{
GroupID: "TEST_ID1",
UniqueID: "TEST_ID2",
}, &reply); err != nil {
@@ -1394,7 +1394,7 @@ func testApierSetAccountActionTriggers(t *testing.T) {
setReq := AttrSetAccountActionTriggers{
Tenant: "cgrates.org",
Account: "dan2",
- AttrSetActionTrigger: AttrSetActionTrigger{
+ AttrSetActionTrigger: engine.AttrSetActionTrigger{
UniqueID: reply[0].UniqueID,
ActionTrigger: map[string]any{
utils.ActivationDate: "2016-02-05T18:00:00Z",
diff --git a/apier/v1/replicate_it_test.go b/apier/v1/replicate_it_test.go
index 702e19e13..f6610b135 100644
--- a/apier/v1/replicate_it_test.go
+++ b/apier/v1/replicate_it_test.go
@@ -1131,7 +1131,7 @@ func testInternalReplicateITActionTrigger(t *testing.T) {
}
// set
var reply string
- attrSet := AttrSetActionTrigger{
+ attrSet := engine.AttrSetActionTrigger{
GroupID: "TestATR",
UniqueID: "UniqueID",
ActionTrigger: map[string]any{
diff --git a/apier/v1/triggers.go b/apier/v1/triggers.go
index bf9e61dcc..2783673fa 100644
--- a/apier/v1/triggers.go
+++ b/apier/v1/triggers.go
@@ -19,7 +19,6 @@ along with this program. If not, see
package v1
import (
- "errors"
"strings"
"time"
@@ -204,151 +203,7 @@ func (apierSv1 *APIerSv1) ResetAccountActionTriggers(ctx *context.Context, attr
type AttrSetAccountActionTriggers struct {
Tenant string
Account string
- AttrSetActionTrigger
-}
-type AttrSetActionTrigger struct {
- GroupID string
- UniqueID string
- ActionTrigger map[string]any
-}
-
-// UpdateActionTrigger updates the ActionTrigger if is matching
-func (attr *AttrSetActionTrigger) UpdateActionTrigger(at *engine.ActionTrigger, timezone string) (updated bool, err error) {
- if at == nil {
- return false, errors.New("Empty ActionTrigger")
- }
- if at.ID == utils.EmptyString { // New AT, update it's data
- if attr.GroupID == utils.EmptyString {
- return false, utils.NewErrMandatoryIeMissing(utils.GroupID)
- }
- if missing := utils.MissingMapFields(attr.ActionTrigger, []string{"ThresholdType", "ThresholdValue"}); len(missing) != 0 {
- return false, utils.NewErrMandatoryIeMissing(missing...)
- }
- at.ID = attr.GroupID
- if attr.UniqueID != utils.EmptyString {
- at.UniqueID = attr.UniqueID
- }
- }
- if attr.GroupID != utils.EmptyString && attr.GroupID != at.ID {
- return
- }
- if attr.UniqueID != utils.EmptyString && attr.UniqueID != at.UniqueID {
- return
- }
- // at matches
- updated = true
- if thr, has := attr.ActionTrigger[utils.ThresholdType]; has {
- at.ThresholdType = utils.IfaceAsString(thr)
- }
- if thr, has := attr.ActionTrigger[utils.ThresholdValue]; has {
- if at.ThresholdValue, err = utils.IfaceAsFloat64(thr); err != nil {
- return
- }
- }
- if rec, has := attr.ActionTrigger[utils.Recurrent]; has {
- if at.Recurrent, err = utils.IfaceAsBool(rec); err != nil {
- return
- }
- }
- if exec, has := attr.ActionTrigger[utils.Executed]; has {
- if at.Executed, err = utils.IfaceAsBool(exec); err != nil {
- return
- }
- }
- if minS, has := attr.ActionTrigger[utils.MinSleep]; has {
- if at.MinSleep, err = utils.IfaceAsDuration(minS); err != nil {
- return
- }
- }
- if exp, has := attr.ActionTrigger[utils.ExpirationDate]; has {
- if at.ExpirationDate, err = utils.IfaceAsTime(exp, timezone); err != nil {
- return
- }
- }
- if act, has := attr.ActionTrigger[utils.ActivationDate]; has {
- if at.ActivationDate, err = utils.IfaceAsTime(act, timezone); err != nil {
- return
- }
- }
- if at.Balance == nil {
- at.Balance = &engine.BalanceFilter{}
- }
- if bid, has := attr.ActionTrigger[utils.BalanceID]; has {
- at.Balance.ID = utils.StringPointer(utils.IfaceAsString(bid))
- }
- if btype, has := attr.ActionTrigger[utils.BalanceType]; has {
- at.Balance.Type = utils.StringPointer(utils.IfaceAsString(btype))
- }
- if bdest, has := attr.ActionTrigger[utils.BalanceDestinationIds]; has {
- var bdIds []string
- if bdIds, err = utils.IfaceAsSliceString(bdest); err != nil {
- return
- }
- at.Balance.DestinationIDs = utils.StringMapPointer(utils.NewStringMap(bdIds...))
- }
- if bweight, has := attr.ActionTrigger[utils.BalanceWeight]; has {
- var bw float64
- if bw, err = utils.IfaceAsFloat64(bweight); err != nil {
- return
- }
- at.Balance.Weight = utils.Float64Pointer(bw)
- }
- if exp, has := attr.ActionTrigger[utils.BalanceExpirationDate]; has {
- var balanceExpTime time.Time
- if balanceExpTime, err = utils.IfaceAsTime(exp, timezone); err != nil {
- return
- }
- at.Balance.ExpirationDate = utils.TimePointer(balanceExpTime)
- }
- if bTimeTag, has := attr.ActionTrigger[utils.BalanceTimingTags]; has {
- var timeTag []string
- if timeTag, err = utils.IfaceAsSliceString(bTimeTag); err != nil {
- return
- }
- at.Balance.TimingIDs = utils.StringMapPointer(utils.NewStringMap(timeTag...))
- }
- if brs, has := attr.ActionTrigger[utils.BalanceRatingSubject]; has {
- at.Balance.RatingSubject = utils.StringPointer(utils.IfaceAsString(brs))
- }
- if bcat, has := attr.ActionTrigger[utils.BalanceCategories]; has {
- var cat []string
- if cat, err = utils.IfaceAsSliceString(bcat); err != nil {
- return
- }
- at.Balance.Categories = utils.StringMapPointer(utils.NewStringMap(cat...))
- }
- if bsg, has := attr.ActionTrigger[utils.BalanceSharedGroups]; has {
- var shrgrps []string
- if shrgrps, err = utils.IfaceAsSliceString(bsg); err != nil {
- return
- }
- at.Balance.SharedGroups = utils.StringMapPointer(utils.NewStringMap(shrgrps...))
- }
- if bb, has := attr.ActionTrigger[utils.BalanceBlocker]; has {
- var bBlocker bool
- if bBlocker, err = utils.IfaceAsBool(bb); err != nil {
- return
- }
- at.Balance.Blocker = utils.BoolPointer(bBlocker)
- }
- if bd, has := attr.ActionTrigger[utils.BalanceDisabled]; has {
- var bDis bool
- if bDis, err = utils.IfaceAsBool(bd); err != nil {
- return
- }
- at.Balance.Disabled = utils.BoolPointer(bDis)
- }
- if minQ, has := attr.ActionTrigger[utils.MinQueuedItems]; has {
- var mQ int64
- if mQ, err = utils.IfaceAsTInt64(minQ); err != nil {
- return
- }
- at.MinQueuedItems = int(mQ)
- }
- if accID, has := attr.ActionTrigger[utils.ActionsID]; has {
- at.ActionsID = utils.IfaceAsString(accID)
- }
- return
+ engine.AttrSetActionTrigger
}
// SetAccountActionTriggers updates or creates if not present the ActionTrigger for an Account
@@ -444,7 +299,7 @@ func (apierSv1 *APIerSv1) RemoveActionTrigger(ctx *context.Context, attr *AttrRe
}
// SetActionTrigger updates a ActionTrigger
-func (apierSv1 *APIerSv1) SetActionTrigger(ctx *context.Context, attr *AttrSetActionTrigger, reply *string) (err error) {
+func (apierSv1 *APIerSv1) SetActionTrigger(ctx *context.Context, attr *engine.AttrSetActionTrigger, reply *string) (err error) {
if missing := utils.MissingStructFields(attr, []string{"GroupID"}); len(missing) != 0 {
return utils.NewErrMandatoryIeMissing(missing...)
}
diff --git a/apier/v1/triggers_test.go b/apier/v1/triggers_test.go
index 02db79102..be9dc09b1 100644
--- a/apier/v1/triggers_test.go
+++ b/apier/v1/triggers_test.go
@@ -28,7 +28,7 @@ import (
)
func TestAttrSetActionTriggerUpdateActionTrigger(t *testing.T) {
- ast := AttrSetActionTrigger{}
+ ast := engine.AttrSetActionTrigger{}
if _, err := ast.UpdateActionTrigger(nil, ""); err == nil || err.Error() != "Empty ActionTrigger" {
t.Errorf("Expected error \"Empty ActionTrigger\", received: %v", err)
}
@@ -43,7 +43,7 @@ func TestAttrSetActionTriggerUpdateActionTrigger(t *testing.T) {
t.Errorf("Expected error %s , received: %v", expErr, err)
}
tNow := time.Now()
- ast = AttrSetActionTrigger{
+ ast = engine.AttrSetActionTrigger{
GroupID: "GroupID",
UniqueID: "ID",
ActionTrigger: map[string]any{
@@ -182,7 +182,7 @@ func TestAttrSetActionTriggerUpdateActionTrigger(t *testing.T) {
ID: "GroupID2",
UniqueID: "ID2",
}
- ast = AttrSetActionTrigger{
+ ast = engine.AttrSetActionTrigger{
GroupID: "GroupID",
UniqueID: "ID",
}
@@ -195,7 +195,7 @@ func TestAttrSetActionTriggerUpdateActionTrigger(t *testing.T) {
ID: "GroupID",
UniqueID: "ID2",
}
- ast = AttrSetActionTrigger{
+ ast = engine.AttrSetActionTrigger{
GroupID: "GroupID",
UniqueID: "ID",
}
diff --git a/apier/v2/apierv2_it_test.go b/apier/v2/apierv2_it_test.go
index 90fedd320..f61c881cb 100644
--- a/apier/v2/apierv2_it_test.go
+++ b/apier/v2/apierv2_it_test.go
@@ -174,7 +174,7 @@ func testAPIerSv2itSetAccountActionTriggers(t *testing.T) {
attrs := v1.AttrSetAccountActionTriggers{
Tenant: "cgrates.org",
Account: "dan",
- AttrSetActionTrigger: v1.AttrSetActionTrigger{
+ AttrSetActionTrigger: engine.AttrSetActionTrigger{
GroupID: "MONITOR_MAX_BALANCE",
ActionTrigger: map[string]any{
utils.ThresholdType: utils.TriggerMaxBalance,
diff --git a/console/trigger_set.go b/console/trigger_set.go
index d99e7f5c0..b2dcc8c64 100644
--- a/console/trigger_set.go
+++ b/console/trigger_set.go
@@ -19,7 +19,7 @@ along with this program. If not, see
package console
import (
- v1 "github.com/cgrates/cgrates/apier/v1"
+ "github.com/cgrates/cgrates/engine"
"github.com/cgrates/cgrates/utils"
)
@@ -27,7 +27,7 @@ func init() {
c := &CmdSetTriggers{
name: "triggers_set",
rpcMethod: utils.APIerSv1SetActionTrigger,
- rpcParams: &v1.AttrSetActionTrigger{},
+ rpcParams: &engine.AttrSetActionTrigger{},
}
commands[c.Name()] = c
c.CommandExecuter = &CommandExecuter{c}
@@ -37,7 +37,7 @@ func init() {
type CmdSetTriggers struct {
name string
rpcMethod string
- rpcParams *v1.AttrSetActionTrigger
+ rpcParams *engine.AttrSetActionTrigger
*CommandExecuter
}
@@ -51,7 +51,7 @@ func (self *CmdSetTriggers) RpcMethod() string {
func (self *CmdSetTriggers) RpcParams(reset bool) any {
if reset || self.rpcParams == nil {
- self.rpcParams = &v1.AttrSetActionTrigger{}
+ self.rpcParams = &engine.AttrSetActionTrigger{}
}
return self.rpcParams
}
diff --git a/engine/action.go b/engine/action.go
index b8bd618ba..2372d0a97 100644
--- a/engine/action.go
+++ b/engine/action.go
@@ -131,7 +131,7 @@ func newActionConnCfg(source, action string, cfg *config.CGRConfig) ActionConnCf
utils.MetaDynamicActionPlanAccounts, utils.MetaDynamicAction,
utils.MetaDynamicDestination, utils.MetaDynamicFilter,
utils.MetaDynamicRoute, utils.MetaDynamicRatingProfile,
- utils.MetaDynamicResource,
+ utils.MetaDynamicResource, utils.MetaDynamicActionTrigger,
}
act := ActionConnCfg{}
switch source {
@@ -204,6 +204,7 @@ func init() {
actionFuncMap[utils.MetaDynamicRatingProfile] = dynamicRatingProfile
actionFuncMap[utils.MetaDynamicRanking] = dynamicTrend
actionFuncMap[utils.MetaDynamicResource] = dynamicResource
+ actionFuncMap[utils.MetaDynamicActionTrigger] = dynamicActionTrigger
}
func getActionFunc(typ string) (f actionTypeFunc, exists bool) {
@@ -2781,3 +2782,172 @@ func dynamicResource(_ *Account, act *Action, _ Actions, _ *FilterS, ev any,
var reply string
return connMgr.Call(context.Background(), connCfg.ConnIDs, utils.APIerSv1SetResourceProfile, rsc, &reply)
}
+
+// dynamicActionTrigger processes the `ExtraParameters` field from the action to
+// construct a ActionTrigger
+//
+// The ExtraParameters field format is expected as follows:
+//
+// 0 Tag: string
+// 1 UniqueId: string
+// 2 ThresholdType: string
+// 3 ThresholdValue: float
+// 4 Recurrent: bool
+// 5 MinSleep: duration
+// 6 ExpiryTime: time
+// 7 ActivationTime: time
+// 8 BalanceTag: string
+// 9 BalanceType: string
+// 10 BalanceCategories: strings separated by "&".
+// 11 BalanceDestinationIds: strings separated by "&".
+// 12 BalanceRatingSubject: string
+// 13 BalanceSharedGroup: strings separated by "&".
+// 14 BalanceExpiryTime: time
+// 15 BalanceTimingIds: strings separated by "&".
+// 16 BalanceWeight: float
+// 17 BalanceBlocker: bool
+// 18 BalanceDisabled: bool
+// 19 ActionsId: string
+// 20 Weight: float
+//
+// Parameters are separated by ";" and must be provided in the specified order.
+func dynamicActionTrigger(_ *Account, act *Action, _ Actions, _ *FilterS, ev any,
+ _ SharedActionsData, connCfg ActionConnCfg) (err error) {
+ cgrEv, canCast := ev.(*utils.CGREvent)
+ if !canCast {
+ return errors.New("Couldn't cast event to CGREvent")
+ }
+ dP := utils.MapStorage{ // create DataProvider from event
+ utils.MetaReq: cgrEv.Event,
+ utils.MetaTenant: cgrEv.Tenant,
+ utils.MetaNow: time.Now(),
+ utils.MetaOpts: cgrEv.APIOpts,
+ }
+ // Parse action parameters based on the predefined format.
+ params := strings.Split(act.ExtraParameters, utils.InfieldSep)
+ if len(params) != 21 {
+ return fmt.Errorf("invalid number of parameters <%d> expected 21", len(params))
+ }
+ // parse dynamic parameters
+ for i := range params {
+ if params[i], err = utils.ParseParamForDataProvider(params[i], dP, false); err != nil {
+ return err
+ }
+ }
+ // Prepare request arguments based on provided parameters.
+ at := &AttrSetActionTrigger{
+ GroupID: params[0],
+ UniqueID: utils.FirstNonEmpty(params[1], utils.GenUUID()),
+ ActionTrigger: make(map[string]any),
+ }
+ // populate ActionTrigger's ThresholdType
+ if params[2] != utils.EmptyString {
+ at.ActionTrigger[utils.ThresholdType] = params[2]
+ }
+ // populate ActionTrigger's ThresholdValue
+ if params[3] != utils.EmptyString {
+ at.ActionTrigger[utils.ThresholdValue], err = strconv.ParseFloat(params[3], 64)
+ if err != nil {
+ return err
+ }
+ }
+ // populate ActionTrigger's Recurrent
+ if params[4] != utils.EmptyString {
+ at.ActionTrigger[utils.Recurrent], err = strconv.ParseBool(params[4])
+ if err != nil {
+ return err
+ }
+ }
+ // populate ActionTrigger's MinSleep
+ if params[5] != utils.EmptyString {
+ at.ActionTrigger[utils.MinSleep], err = utils.ParseDurationWithNanosecs(params[5])
+ if err != nil {
+ return err
+ }
+ }
+ // populate ActionTrigger's ExpirationDate
+ if params[6] != utils.EmptyString {
+ at.ActionTrigger[utils.ExpirationDate], err = utils.ParseTimeDetectLayout(params[6], config.CgrConfig().GeneralCfg().DefaultTimezone)
+ if err != nil {
+ return err
+ }
+ }
+ // populate ActionTrigger's ActivationDate
+ if params[7] != utils.EmptyString {
+ at.ActionTrigger[utils.ActivationDate], err = utils.ParseTimeDetectLayout(params[7], config.CgrConfig().GeneralCfg().DefaultTimezone)
+ if err != nil {
+ return err
+ }
+ }
+ // populate ActionTrigger's BalanceID
+ if params[8] != utils.EmptyString {
+ at.ActionTrigger[utils.BalanceID] = params[8]
+ }
+ // populate ActionTrigger's BalanceType
+ if params[9] != utils.EmptyString {
+ at.ActionTrigger[utils.BalanceType] = params[9]
+ }
+ // populate ActionTrigger's BalanceCategories
+ if params[10] != utils.EmptyString {
+ at.ActionTrigger[utils.BalanceCategories] = strings.Split(params[10], utils.ANDSep)
+ }
+ // populate ActionTrigger's BalanceDestinationIds
+ if params[11] != utils.EmptyString {
+ at.ActionTrigger[utils.BalanceDestinationIds] = strings.Split(params[11], utils.ANDSep)
+ }
+ // populate ActionTrigger's BalanceRatingSubject
+ if params[12] != utils.EmptyString {
+ at.ActionTrigger[utils.BalanceRatingSubject] = params[12]
+ }
+ // populate ActionTrigger's BalanceSharedGroups
+ if params[13] != utils.EmptyString {
+ at.ActionTrigger[utils.BalanceSharedGroups] = strings.Split(params[13], utils.ANDSep)
+ }
+ // populate ActionTrigger's BalanceExpirationDate
+ if params[14] != utils.EmptyString {
+ at.ActionTrigger[utils.BalanceExpirationDate], err = utils.ParseTimeDetectLayout(params[14], config.CgrConfig().GeneralCfg().DefaultTimezone)
+ if err != nil {
+ return err
+ }
+ }
+ // populate ActionTrigger's BalanceTimingTags
+ if params[15] != utils.EmptyString {
+ at.ActionTrigger[utils.BalanceTimingTags] = strings.Split(params[15], utils.ANDSep)
+ }
+ // populate ActionTrigger's BalanceWeight
+ if params[16] != utils.EmptyString {
+ at.ActionTrigger[utils.BalanceWeight], err = strconv.ParseFloat(params[16], 64)
+ if err != nil {
+ return err
+ }
+ }
+ // populate ActionTrigger's BalanceBlocker
+ if params[17] != utils.EmptyString {
+ at.ActionTrigger[utils.BalanceBlocker], err = strconv.ParseBool(params[17])
+ if err != nil {
+ return err
+ }
+ }
+ // populate ActionTrigger's BalanceDisabled
+ if params[18] != utils.EmptyString {
+ at.ActionTrigger[utils.BalanceDisabled], err = strconv.ParseBool(params[18])
+ if err != nil {
+ return err
+ }
+ }
+ // populate ActionTrigger's ActionsID
+ if params[19] != utils.EmptyString {
+ at.ActionTrigger[utils.ActionsID] = params[19]
+ }
+ // populate ActionTrigger's Weight
+ if params[20] != utils.EmptyString {
+ at.ActionTrigger[utils.Weight], err = strconv.ParseFloat(params[20], 64)
+ if err != nil {
+ return err
+ }
+ }
+
+ // create the ActionTrigger based on the populated parameters
+ var reply string
+ return connMgr.Call(context.Background(), connCfg.ConnIDs, utils.APIerSv1SetActionTrigger, at, &reply)
+}
diff --git a/engine/action_trigger.go b/engine/action_trigger.go
index d3c09532e..17b2c7c11 100644
--- a/engine/action_trigger.go
+++ b/engine/action_trigger.go
@@ -20,6 +20,7 @@ package engine
import (
"encoding/json"
+ "errors"
"fmt"
"sort"
"time"
@@ -314,3 +315,154 @@ func (at *ActionTrigger) FieldAsString(fldPath []string) (val string, err error)
}
return utils.IfaceAsString(iface), nil
}
+
+type AttrSetActionTrigger struct {
+ GroupID string
+ UniqueID string
+ ActionTrigger map[string]any
+}
+
+// UpdateActionTrigger updates the ActionTrigger if is matching
+func (attr *AttrSetActionTrigger) UpdateActionTrigger(at *ActionTrigger, timezone string) (updated bool, err error) {
+ if at == nil {
+ return false, errors.New("Empty ActionTrigger")
+ }
+ if at.ID == utils.EmptyString { // New AT, update it's data
+ if attr.GroupID == utils.EmptyString {
+ return false, utils.NewErrMandatoryIeMissing(utils.GroupID)
+ }
+ if missing := utils.MissingMapFields(attr.ActionTrigger, []string{"ThresholdType", "ThresholdValue"}); len(missing) != 0 {
+ return false, utils.NewErrMandatoryIeMissing(missing...)
+ }
+ at.ID = attr.GroupID
+ if attr.UniqueID != utils.EmptyString {
+ at.UniqueID = attr.UniqueID
+ }
+ }
+ if attr.GroupID != utils.EmptyString && attr.GroupID != at.ID {
+ return
+ }
+ if attr.UniqueID != utils.EmptyString && attr.UniqueID != at.UniqueID {
+ return
+ }
+ // at matches
+ updated = true
+ if thr, has := attr.ActionTrigger[utils.ThresholdType]; has {
+ at.ThresholdType = utils.IfaceAsString(thr)
+ }
+ if thr, has := attr.ActionTrigger[utils.ThresholdValue]; has {
+ if at.ThresholdValue, err = utils.IfaceAsFloat64(thr); err != nil {
+ return
+ }
+ }
+ if rec, has := attr.ActionTrigger[utils.Recurrent]; has {
+ if at.Recurrent, err = utils.IfaceAsBool(rec); err != nil {
+ return
+ }
+ }
+ if exec, has := attr.ActionTrigger[utils.Executed]; has {
+ if at.Executed, err = utils.IfaceAsBool(exec); err != nil {
+ return
+ }
+ }
+ if minS, has := attr.ActionTrigger[utils.MinSleep]; has {
+ if at.MinSleep, err = utils.IfaceAsDuration(minS); err != nil {
+ return
+ }
+ }
+ if exp, has := attr.ActionTrigger[utils.ExpirationDate]; has {
+ if at.ExpirationDate, err = utils.IfaceAsTime(exp, timezone); err != nil {
+ return
+ }
+ }
+ if act, has := attr.ActionTrigger[utils.ActivationDate]; has {
+ if at.ActivationDate, err = utils.IfaceAsTime(act, timezone); err != nil {
+ return
+ }
+ }
+ if at.Balance == nil {
+ at.Balance = &BalanceFilter{}
+ }
+ if bid, has := attr.ActionTrigger[utils.BalanceID]; has {
+ at.Balance.ID = utils.StringPointer(utils.IfaceAsString(bid))
+ }
+ if btype, has := attr.ActionTrigger[utils.BalanceType]; has {
+ at.Balance.Type = utils.StringPointer(utils.IfaceAsString(btype))
+ }
+ if bdest, has := attr.ActionTrigger[utils.BalanceDestinationIds]; has {
+ var bdIds []string
+ if bdIds, err = utils.IfaceAsSliceString(bdest); err != nil {
+ return
+ }
+ at.Balance.DestinationIDs = utils.StringMapPointer(utils.NewStringMap(bdIds...))
+ }
+ if bweight, has := attr.ActionTrigger[utils.BalanceWeight]; has {
+ var bw float64
+ if bw, err = utils.IfaceAsFloat64(bweight); err != nil {
+ return
+ }
+ at.Balance.Weight = utils.Float64Pointer(bw)
+ }
+ if exp, has := attr.ActionTrigger[utils.BalanceExpirationDate]; has {
+ var balanceExpTime time.Time
+ if balanceExpTime, err = utils.IfaceAsTime(exp, timezone); err != nil {
+ return
+ }
+ at.Balance.ExpirationDate = utils.TimePointer(balanceExpTime)
+ }
+ if bTimeTag, has := attr.ActionTrigger[utils.BalanceTimingTags]; has {
+ var timeTag []string
+ if timeTag, err = utils.IfaceAsSliceString(bTimeTag); err != nil {
+ return
+ }
+ at.Balance.TimingIDs = utils.StringMapPointer(utils.NewStringMap(timeTag...))
+ }
+ if brs, has := attr.ActionTrigger[utils.BalanceRatingSubject]; has {
+ at.Balance.RatingSubject = utils.StringPointer(utils.IfaceAsString(brs))
+ }
+ if bcat, has := attr.ActionTrigger[utils.BalanceCategories]; has {
+ var cat []string
+ if cat, err = utils.IfaceAsSliceString(bcat); err != nil {
+ return
+ }
+ at.Balance.Categories = utils.StringMapPointer(utils.NewStringMap(cat...))
+ }
+ if bsg, has := attr.ActionTrigger[utils.BalanceSharedGroups]; has {
+ var shrgrps []string
+ if shrgrps, err = utils.IfaceAsSliceString(bsg); err != nil {
+ return
+ }
+ at.Balance.SharedGroups = utils.StringMapPointer(utils.NewStringMap(shrgrps...))
+ }
+ if bb, has := attr.ActionTrigger[utils.BalanceBlocker]; has {
+ var bBlocker bool
+ if bBlocker, err = utils.IfaceAsBool(bb); err != nil {
+ return
+ }
+ at.Balance.Blocker = utils.BoolPointer(bBlocker)
+ }
+ if bd, has := attr.ActionTrigger[utils.BalanceDisabled]; has {
+ var bDis bool
+ if bDis, err = utils.IfaceAsBool(bd); err != nil {
+ return
+ }
+ at.Balance.Disabled = utils.BoolPointer(bDis)
+ }
+ if minQ, has := attr.ActionTrigger[utils.MinQueuedItems]; has {
+ var mQ int64
+ if mQ, err = utils.IfaceAsTInt64(minQ); err != nil {
+ return
+ }
+ at.MinQueuedItems = int(mQ)
+ }
+ if accID, has := attr.ActionTrigger[utils.ActionsID]; has {
+ at.ActionsID = utils.IfaceAsString(accID)
+ }
+
+ if weight, has := attr.ActionTrigger[utils.Weight]; has {
+ if at.Weight, err = utils.IfaceAsFloat64(weight); err != nil {
+ return
+ }
+ }
+ return
+}
diff --git a/engine/actions_test.go b/engine/actions_test.go
index d85acad66..dbd4efba4 100644
--- a/engine/actions_test.go
+++ b/engine/actions_test.go
@@ -7334,3 +7334,264 @@ func TestDynamicResource(t *testing.T) {
})
}
}
+
+func TestDynamicActionTrigger(t *testing.T) {
+ tempConn := connMgr
+ tmpDm := dm
+ tmpCache := Cache
+ defer func() {
+ config.SetCgrConfig(config.NewDefaultCGRConfig())
+ SetConnManager(tempConn)
+ dm = tmpDm
+ Cache = tmpCache
+ }()
+ Cache.Clear(nil)
+ var at *AttrSetActionTrigger
+ ccMock := &ccMock{
+ calls: map[string]func(ctx *context.Context, args any, reply any) error{
+ utils.APIerSv1SetActionTrigger: func(ctx *context.Context, args, reply any) error {
+ var canCast bool
+ if at, canCast = args.(*AttrSetActionTrigger); !canCast {
+ return fmt.Errorf("couldnt cast")
+ }
+ return nil
+ },
+ },
+ }
+ connID := utils.ConcatenatedKey(utils.MetaInternal, utils.MetaApier)
+ clientconn := make(chan birpc.ClientConnector, 1)
+ clientconn <- ccMock
+ NewConnManager(config.NewDefaultCGRConfig(), map[string]chan birpc.ClientConnector{
+ connID: clientconn,
+ })
+ testcases := []struct {
+ name string
+ extraParams string
+ connIDs []string
+ expAt *AttrSetActionTrigger
+ expectedErr string
+ }{
+ {
+ name: "SuccessfulRequest",
+ connIDs: []string{connID},
+ expAt: &AttrSetActionTrigger{
+ GroupID: "STANDARD_TRIGGERS",
+ UniqueID: "uid",
+ ActionTrigger: map[string]any{
+ utils.ThresholdType: "*max_balance",
+ utils.ThresholdValue: float64(20),
+ utils.Recurrent: true,
+ utils.MinSleep: time.Second,
+ utils.ActivationDate: time.Date(2014, 7, 29, 15, 0, 0, 0, time.UTC),
+ utils.BalanceID: "*default",
+ utils.BalanceType: "*monetary",
+ utils.BalanceCategories: []string{utils.Call, "data"},
+ utils.BalanceDestinationIds: []string{"DST1", "DST2"},
+ utils.BalanceRatingSubject: "SPECIAL_1002",
+ utils.BalanceSharedGroups: []string{"SHRGroup1", "SHRGroup2"},
+ utils.BalanceExpirationDate: time.Date(2030, 7, 29, 15, 0, 0, 0, time.UTC),
+ utils.BalanceTimingTags: []string{"*asap"},
+ utils.BalanceWeight: float64(10),
+ utils.BalanceBlocker: true,
+ utils.BalanceDisabled: true,
+ utils.ActionsID: "ACT_1001",
+ utils.Weight: float64(20),
+ },
+ },
+ extraParams: "STANDARD_TRIGGERS;uid;*max_balance;20;true;1s;;2014-07-29T15:00:00Z;*default;*monetary;call&data;DST1&DST2;SPECIAL_1002;SHRGroup1&SHRGroup2;2030-07-29T15:00:00Z;*asap;10;true;true;ACT_1001;20",
+ },
+ {
+ name: "SuccessfulRequestWithDynamicPaths",
+ connIDs: []string{connID},
+ expAt: &AttrSetActionTrigger{
+ GroupID: "STANDARD_TRIGGERS_1001",
+ UniqueID: "uid_1001",
+ ActionTrigger: map[string]any{
+ utils.ThresholdType: "*max_balance",
+ utils.ThresholdValue: float64(20),
+ utils.Recurrent: true,
+ utils.MinSleep: time.Second,
+ utils.ExpirationDate: time.Now(),
+ utils.ActivationDate: time.Now(),
+ utils.BalanceID: "*default",
+ utils.BalanceType: "*monetary",
+ utils.BalanceCategories: []string{utils.Call, "data"},
+ utils.BalanceDestinationIds: []string{"DST_1001", "DST2"},
+ utils.BalanceRatingSubject: "SPECIAL_1001",
+ utils.BalanceSharedGroups: []string{"SHRGroup_1001", "SHRGroup2"},
+ utils.BalanceExpirationDate: time.Now(),
+ utils.BalanceTimingTags: []string{"*asap"},
+ utils.BalanceWeight: float64(10),
+ utils.BalanceBlocker: true,
+ utils.BalanceDisabled: true,
+ utils.ActionsID: "ACT_1001",
+ utils.Weight: float64(20),
+ },
+ },
+ extraParams: "STANDARD_TRIGGERS_<~*req.Account>;uid_<~*req.Account>;*max_balance;20;true;1s;*now;*now;*default;*monetary;call&data;DST_<~*req.Account>&DST2;SPECIAL_<~*req.Account>;SHRGroup_<~*req.Account>&SHRGroup2;*now;*asap;10;true;true;ACT_<~*req.Account>;20",
+ },
+ {
+ name: "SuccessfulRequestEmptyFields",
+ connIDs: []string{connID},
+ expAt: &AttrSetActionTrigger{
+ GroupID: "STANDARD_TRIGGERS",
+ UniqueID: "uid",
+ ActionTrigger: map[string]any{
+ utils.ThresholdType: "*max_balance",
+ utils.ThresholdValue: float64(20),
+ },
+ },
+ extraParams: "STANDARD_TRIGGERS;uid;*max_balance;20;;;;;;;;;;;;;;;;;",
+ },
+ {
+ name: "MissingConns",
+ extraParams: "STANDARD_TRIGGERS;uid;*max_balance;20;true;1s;;2014-07-29T15:00:00Z;*default;*monetary;call&data;DST1&DST2;SPECIAL_1002;SHRGroup1&SHRGroup2;2030-07-29T15:00:00Z;*asap;10;true;true;ACT_1001;20",
+ expectedErr: "MANDATORY_IE_MISSING: [connIDs]",
+ },
+ {
+ name: "WrongNumberOfParams",
+ extraParams: "STANDARD_TRIGGERS;uid;*max_balance;20",
+ expectedErr: "invalid number of parameters <4> expected 21",
+ },
+ {
+ name: "ThresholdValueFail",
+ extraParams: "STANDARD_TRIGGERS;uid;*max_balance;BadString;true;1s;;2014-07-29T15:00:00Z;*default;*monetary;call&data;DST1&DST2;SPECIAL_1002;SHRGroup1&SHRGroup2;2030-07-29T15:00:00Z;*asap;10;true;true;ACT_1001;20",
+ expectedErr: `strconv.ParseFloat: parsing "BadString": invalid syntax`,
+ },
+ {
+ name: "RecurrentFail",
+ extraParams: "STANDARD_TRIGGERS;uid;*max_balance;20;BadString;1s;;2014-07-29T15:00:00Z;*default;*monetary;call&data;DST1&DST2;SPECIAL_1002;SHRGroup1&SHRGroup2;2030-07-29T15:00:00Z;*asap;10;true;true;ACT_1001;20",
+ expectedErr: `strconv.ParseBool: parsing "BadString": invalid syntax`,
+ },
+ {
+ name: "MinSleepFail",
+ extraParams: "STANDARD_TRIGGERS;uid;*max_balance;20;true;BadString;;2014-07-29T15:00:00Z;*default;*monetary;call&data;DST1&DST2;SPECIAL_1002;SHRGroup1&SHRGroup2;2030-07-29T15:00:00Z;*asap;10;true;true;ACT_1001;20",
+ expectedErr: `time: invalid duration "BadString"`,
+ },
+ {
+ name: "ExpirationDateBadStringFail",
+ extraParams: "STANDARD_TRIGGERS;uid;*max_balance;20;true;1s;bad String;2014-07-29T15:00:00Z;*default;*monetary;call&data;DST1&DST2;SPECIAL_1002;SHRGroup1&SHRGroup2;2030-07-29T15:00:00Z;*asap;10;true;true;ACT_1001;20",
+ expectedErr: `Unsupported time format`,
+ },
+ {
+ name: "ActivationDateBadStringFail",
+ extraParams: "STANDARD_TRIGGERS;uid;*max_balance;20;true;1s;;bad String;*default;*monetary;call&data;DST1&DST2;SPECIAL_1002;SHRGroup1&SHRGroup2;2030-07-29T15:00:00Z;*asap;10;true;true;ACT_1001;20",
+ expectedErr: `Unsupported time format`,
+ },
+ {
+ name: "BalanceExpirationDateBadStringFail",
+ extraParams: "STANDARD_TRIGGERS;uid;*max_balance;20;true;1s;;2014-07-29T15:00:00Z;*default;*monetary;call&data;DST1&DST2;SPECIAL_1002;SHRGroup1&SHRGroup2;bad String;*asap;10;true;true;ACT_1001;20",
+ expectedErr: `Unsupported time format`,
+ },
+ {
+ name: "BalanceWeightFail",
+ extraParams: "STANDARD_TRIGGERS;uid;*max_balance;20;true;1s;;2014-07-29T15:00:00Z;*default;*monetary;call&data;DST1&DST2;SPECIAL_1002;SHRGroup1&SHRGroup2;2030-07-29T15:00:00Z;*asap;BadString;true;true;ACT_1001;20",
+ expectedErr: `strconv.ParseFloat: parsing "BadString": invalid syntax`,
+ },
+ {
+ name: "BalanceBlockerFail",
+ extraParams: "STANDARD_TRIGGERS;uid;*max_balance;20;true;1s;;2014-07-29T15:00:00Z;*default;*monetary;call&data;DST1&DST2;SPECIAL_1002;SHRGroup1&SHRGroup2;2030-07-29T15:00:00Z;*asap;10;BadString;true;ACT_1001;20",
+ expectedErr: `strconv.ParseBool: parsing "BadString": invalid syntax`,
+ },
+ {
+ name: "BalanceDisabledFail",
+ extraParams: "STANDARD_TRIGGERS;uid;*max_balance;20;true;1s;;2014-07-29T15:00:00Z;*default;*monetary;call&data;DST1&DST2;SPECIAL_1002;SHRGroup1&SHRGroup2;2030-07-29T15:00:00Z;*asap;10;true;BadString;ACT_1001;20",
+ expectedErr: `strconv.ParseBool: parsing "BadString": invalid syntax`,
+ },
+ {
+ name: "WeightFail",
+ extraParams: "STANDARD_TRIGGERS;uid;*max_balance;20;true;1s;;2014-07-29T15:00:00Z;*default;*monetary;call&data;DST1&DST2;SPECIAL_1002;SHRGroup1&SHRGroup2;2030-07-29T15:00:00Z;*asap;10;true;true;ACT_1001;BadString",
+ expectedErr: `strconv.ParseFloat: parsing "BadString": invalid syntax`,
+ },
+ }
+
+ for i, tc := range testcases {
+ t.Run(tc.name, func(t *testing.T) {
+ action := &Action{ExtraParameters: tc.extraParams}
+ ev := &utils.CGREvent{
+ Tenant: "cgrates.org",
+ ID: "evID",
+ Time: &time.Time{},
+ Event: map[string]any{
+ utils.AccountField: "1001",
+ },
+ }
+ t.Cleanup(func() {
+ at = nil
+ })
+ err := dynamicActionTrigger(nil, action, nil, nil, ev,
+ SharedActionsData{}, ActionConnCfg{
+ ConnIDs: tc.connIDs,
+ })
+ if tc.expectedErr != "" {
+ if err == nil || err.Error() != tc.expectedErr {
+ t.Errorf("expected error <%v>, received <%v>", tc.expectedErr, err)
+ }
+ } else if err != nil {
+ t.Error(err)
+ } else if !reflect.DeepEqual(at, tc.expAt) {
+ if i != 1 {
+ t.Errorf("Expected <%v>\nReceived\n<%v>", utils.ToJSON(tc.expAt), utils.ToJSON(at))
+ } else {
+ // Get the absolute difference between the times
+ rcvExpDate, err := utils.IfaceAsTime(at.ActionTrigger[utils.ExpirationDate], config.CgrConfig().GeneralCfg().DefaultTimezone)
+ if err != nil {
+ t.Fatal(err)
+ }
+ expExpDate, err := utils.IfaceAsTime(tc.expAt.ActionTrigger[utils.ExpirationDate], config.CgrConfig().GeneralCfg().DefaultTimezone)
+ if err != nil {
+ t.Fatal(err)
+ }
+ diff := rcvExpDate.Sub(expExpDate)
+ if diff < 0 {
+ diff = -diff // Make sure it's positive
+ }
+ // Check if difference is less than or equal to 1 second
+ if diff > time.Second {
+ t.Fatalf("Expected <%v>\nReceived\n<%v>", utils.ToJSON(tc.expAt), utils.ToJSON(at))
+ }
+ tc.expAt.ActionTrigger[utils.ExpirationDate] = rcvExpDate
+ // Get the absolute difference between the times
+ rcvExpDate, err = utils.IfaceAsTime(at.ActionTrigger[utils.ActivationDate], config.CgrConfig().GeneralCfg().DefaultTimezone)
+ if err != nil {
+ t.Fatal(err)
+ }
+ expExpDate, err = utils.IfaceAsTime(tc.expAt.ActionTrigger[utils.ActivationDate], config.CgrConfig().GeneralCfg().DefaultTimezone)
+ if err != nil {
+ t.Fatal(err)
+ }
+ diff = rcvExpDate.Sub(expExpDate)
+ if diff < 0 {
+ diff = -diff // Make sure it's positive
+ }
+ // Check if difference is less than or equal to 1 second
+ if diff > time.Second {
+ t.Fatalf("Expected <%v>\nReceived\n<%v>", utils.ToJSON(tc.expAt), utils.ToJSON(at))
+ }
+ tc.expAt.ActionTrigger[utils.ActivationDate] = rcvExpDate
+ // Get the absolute difference between the times
+ rcvExpDate, err = utils.IfaceAsTime(at.ActionTrigger[utils.BalanceExpirationDate], config.CgrConfig().GeneralCfg().DefaultTimezone)
+ if err != nil {
+ t.Fatal(err)
+ }
+ expExpDate, err = utils.IfaceAsTime(tc.expAt.ActionTrigger[utils.BalanceExpirationDate], config.CgrConfig().GeneralCfg().DefaultTimezone)
+ if err != nil {
+ t.Fatal(err)
+ }
+ diff = rcvExpDate.Sub(expExpDate)
+ if diff < 0 {
+ diff = -diff // Make sure it's positive
+ }
+ // Check if difference is less than or equal to 1 second
+ if diff > time.Second {
+ t.Fatalf("Expected <%v>\nReceived\n<%v>", utils.ToJSON(tc.expAt), utils.ToJSON(at))
+ }
+ tc.expAt.ActionTrigger[utils.BalanceExpirationDate] = rcvExpDate
+ if !reflect.DeepEqual(at, tc.expAt) {
+ t.Errorf("Expected <%v>\nReceived\n<%v>", utils.ToJSON(tc.expAt), utils.ToJSON(at))
+ }
+ }
+ }
+ })
+ }
+}
diff --git a/general_tests/offline_internal_it_test.go b/general_tests/offline_internal_it_test.go
index c0da2be90..bd5e0c979 100644
--- a/general_tests/offline_internal_it_test.go
+++ b/general_tests/offline_internal_it_test.go
@@ -216,7 +216,7 @@ func TestOfflineInternal(t *testing.T) { // run with sudo
t.Run("GetActionTriggers", func(t *testing.T) {
var reply string
- if err := client.Call(context.Background(), utils.APIerSv1SetActionTrigger, v1.AttrSetActionTrigger{
+ if err := client.Call(context.Background(), utils.APIerSv1SetActionTrigger, engine.AttrSetActionTrigger{
GroupID: "GroupID",
UniqueID: "ID",
ActionTrigger: map[string]any{
diff --git a/general_tests/tp_it_test.go b/general_tests/tp_it_test.go
index bf11f81ca..b1ab3bf48 100644
--- a/general_tests/tp_it_test.go
+++ b/general_tests/tp_it_test.go
@@ -162,7 +162,7 @@ func testTpActionTriggers(t *testing.T) {
t.Errorf("Calling v1.GetActionTriggers got: %v", atrs)
}
var reply string
- if err := tpRPC.Call(context.Background(), utils.APIerSv1SetActionTrigger, v1.AttrSetActionTrigger{
+ if err := tpRPC.Call(context.Background(), utils.APIerSv1SetActionTrigger, engine.AttrSetActionTrigger{
GroupID: "TestATR",
UniqueID: "Unique atr id",
ActionTrigger: map[string]any{
diff --git a/utils/consts.go b/utils/consts.go
index e30e2d9f4..aefcecba3 100644
--- a/utils/consts.go
+++ b/utils/consts.go
@@ -1241,6 +1241,7 @@ const (
MetaDynamicRatingProfile = "*dynamic_rating_profile"
MetaDynamicTrend = "*dynamic_trend"
MetaDynamicResource = "*dynamic_resource"
+ MetaDynamicActionTrigger = "*dynamic_action_trigger"
ActionID = "ActionID"
ActionType = "ActionType"
ActionValue = "ActionValue"