From 38e7c7eb39d99c5820d8e4c3450bd6e64761571a Mon Sep 17 00:00:00 2001 From: arberkatellari Date: Wed, 16 Jul 2025 12:20:46 +0200 Subject: [PATCH] ActionProfile modifications --- actions/accounts.go | 5 +- actions/dynamic.go | 28 +++--- actions/export.go | 4 +- admins/actions.go | 8 ++ apis/actions.go | 11 +++ config/config_defaults.go | 9 +- engine/model_helpers.go | 109 ++++++++++++++++++---- engine/models.go | 39 ++++---- tpes/tpe_actions.go | 7 +- utils/actions.go | 187 +++++++++++++++++++++++++++++++------- utils/apitpdata.go | 9 +- utils/consts.go | 14 ++- 12 files changed, 339 insertions(+), 91 deletions(-) diff --git a/actions/accounts.go b/actions/accounts.go index 8b18ef99f..68911b359 100644 --- a/actions/accounts.go +++ b/actions/accounts.go @@ -66,9 +66,8 @@ func (aL *actSetBalance) execute(ctx *context.Context, data utils.MapStorage, tr if val, err = rsr.ParseDataProvider(data); err != nil { return } - args.Diktats[i] = &utils.BalDiktat{ - Path: actD.Path, + Path: utils.IfaceAsString(actD.Opts[utils.MetaBalancePath]), Value: val, } } @@ -106,7 +105,7 @@ func (aL *actRemBalance) execute(ctx *context.Context, data utils.MapStorage, tr APIOpts: aL.cfg().Opts, } for i, actD := range aL.cfg().Diktats { - args.BalanceIDs[i] = actD.Path + args.BalanceIDs[i] = utils.IfaceAsString(actD.Opts[utils.MetaBalancePath]) } var rply string return aL.connMgr.Call(ctx, aL.config.ActionSCfg().AccountSConns, diff --git a/actions/dynamic.go b/actions/dynamic.go index fb9df7750..8f6b01168 100644 --- a/actions/dynamic.go +++ b/actions/dynamic.go @@ -45,9 +45,9 @@ func parseParamStringToMap(paramStr string, targetMap map[string]any) error { return nil } -// actDynamicThreshold processes the `ActionValue` field from the action to construct a Threshold profile +// actDynamicThreshold processes the `ActionDiktatsOpts` field from the action to construct a Threshold profile // -// The ActionValue field format is expected as follows: +// The ActionDiktatsOpts field format is expected as follows: // // 0 Tenant: string // 1 ID: string @@ -91,7 +91,8 @@ func (aL *actDynamicThreshold) execute(ctx *context.Context, data utils.MapStora if len(aL.aCfg.Diktats) == 0 { return fmt.Errorf("No diktats were speified for action <%v>", aL.aCfg.ID) } - params := strings.Split(aL.aCfg.Diktats[0].Value, utils.InfieldSep) + params := strings.Split(utils.IfaceAsString(aL.aCfg.Diktats[0].Opts[utils.MetaTemplate]), + utils.InfieldSep) if len(params) != 11 { return fmt.Errorf("invalid number of parameters <%d> expected 11", len(params)) } @@ -181,9 +182,9 @@ func (aL *actDynamicThreshold) execute(ctx *context.Context, data utils.MapStora utils.AdminSv1SetThresholdProfile, args, &rply) } -// actDynamicStats processes the `ActionValue` field from the action to construct a StatQueueProfile +// actDynamicStats processes the `ActionDiktatsOpts` field from the action to construct a StatQueueProfile // -// The ActionValue field format is expected as follows: +// The ActionDiktatsOpts field format is expected as follows: // // 0 Tenant: string // 1 ID: string @@ -229,7 +230,8 @@ func (aL *actDynamicStats) execute(ctx *context.Context, data utils.MapStorage, if len(aL.aCfg.Diktats) == 0 { return fmt.Errorf("No diktats were speified for action <%v>", aL.aCfg.ID) } - params := strings.Split(aL.aCfg.Diktats[0].Value, utils.InfieldSep) + params := strings.Split(utils.IfaceAsString(aL.aCfg.Diktats[0].Opts[utils.MetaTemplate]), + utils.InfieldSep) if len(params) != 14 { return fmt.Errorf("invalid number of parameters <%d> expected 14", len(params)) } @@ -364,9 +366,9 @@ func (aL *actDynamicStats) execute(ctx *context.Context, data utils.MapStorage, utils.AdminSv1SetStatQueueProfile, args, &rply) } -// actDynamicAttribute processes the `ActionValue` field from the action to construct a AttributeProfile +// actDynamicAttribute processes the `ActionDiktatsOpts` field from the action to construct a AttributeProfile // -// The ActionValue field format is expected as follows: +// The ActionDiktatsOpts field format is expected as follows: // // 0 Tenant: string // 1 ID: string @@ -409,7 +411,8 @@ func (aL *actDynamicAttribute) execute(ctx *context.Context, data utils.MapStora if len(aL.aCfg.Diktats) == 0 { return fmt.Errorf("No diktats were speified for action <%v>", aL.aCfg.ID) } - params := strings.Split(aL.aCfg.Diktats[0].Value, utils.InfieldSep) + params := strings.Split(utils.IfaceAsString(aL.aCfg.Diktats[0].Opts[utils.MetaTemplate]), + utils.InfieldSep) if len(params) != 11 { return fmt.Errorf("invalid number of parameters <%d> expected 11", len(params)) } @@ -509,9 +512,9 @@ func (aL *actDynamicAttribute) execute(ctx *context.Context, data utils.MapStora utils.AdminSv1SetAttributeProfile, args, &rply) } -// actDynamicResource processes the `ActionValue` field from the action to construct a ResourceProfile +// actDynamicResource processes the `ActionDiktatsOpts` field from the action to construct a ResourceProfile // -// The ActionValue field format is expected as follows: +// The ActionDiktatsOpts field format is expected as follows: // // 0 Tenant: string // 1 ID: string @@ -554,7 +557,8 @@ func (aL *actDynamicResource) execute(ctx *context.Context, data utils.MapStorag if len(aL.aCfg.Diktats) == 0 { return fmt.Errorf("No diktats were speified for action <%v>", aL.aCfg.ID) } - params := strings.Split(aL.aCfg.Diktats[0].Value, utils.InfieldSep) + params := strings.Split(utils.IfaceAsString(aL.aCfg.Diktats[0].Opts[utils.MetaTemplate]), + utils.InfieldSep) if len(params) != 11 { return fmt.Errorf("invalid number of parameters <%d> expected 11", len(params)) } diff --git a/actions/export.go b/actions/export.go index df060d6e5..8ebb4fe3e 100644 --- a/actions/export.go +++ b/actions/export.go @@ -43,7 +43,9 @@ func newActHTTPPost(ctx *context.Context, tnt string, cgrEv *utils.CGREvent, if err != nil { return nil, err } - eeCfg := config.NewEventExporterCfg(aL.id(), "", actD.Path, cfg.EEsCfg().ExporterCfg(utils.MetaDefault).FailedPostsDir, + eeCfg := config.NewEventExporterCfg(aL.id(), utils.EmptyString, + utils.IfaceAsString(actD.Opts[utils.MetaURL]), + cfg.EEsCfg().ExporterCfg(utils.MetaDefault).FailedPostsDir, attempts, nil) aL.pstrs[i], _ = ees.NewHTTPjsonMapEE(eeCfg, cfg, nil, nil) } diff --git a/admins/actions.go b/admins/actions.go index 0fb40f38b..3fdd6ded6 100644 --- a/admins/actions.go +++ b/admins/actions.go @@ -120,6 +120,14 @@ func (admS *AdminS) V1SetActionProfile(ctx *context.Context, ap *utils.ActionPro if missing := utils.MissingStructFields(ap.ActionProfile, []string{utils.ID, utils.Actions}); len(missing) != 0 { return utils.NewErrMandatoryIeMissing(missing...) } + for i := range ap.ActionProfile.Actions { + for j := range ap.ActionProfile.Actions[i].Diktats { // if there are diktats, make sure their ID exists + if missing := utils.MissingStructFields(ap.ActionProfile.Actions[i]. + Diktats[j], []string{utils.ID}); len(missing) != 0 { + return utils.NewErrMandatoryIeMissing(missing...) + } + } + } if ap.Tenant == utils.EmptyString { ap.Tenant = admS.cfg.GeneralCfg().DefaultTenant } diff --git a/apis/actions.go b/apis/actions.go index c4f285a39..65a864538 100644 --- a/apis/actions.go +++ b/apis/actions.go @@ -121,6 +121,17 @@ func (admS *AdminSv1) SetActionProfile(ctx *context.Context, ap *utils.ActionPro if missing := utils.MissingStructFields(ap.ActionProfile, []string{utils.ID, utils.Actions}); len(missing) != 0 { return utils.NewErrMandatoryIeMissing(missing...) } + for i := range ap.ActionProfile.Actions { + if ap.ActionProfile.Actions[i] == nil { + continue + } + for j := range ap.ActionProfile.Actions[i].Diktats { // if there are diktats, make sure their ID exists + if missing := utils.MissingStructFields(ap.ActionProfile.Actions[i]. + Diktats[j], []string{utils.ID}); len(missing) != 0 { + return utils.NewErrMandatoryIeMissing(missing...) + } + } + } if ap.Tenant == utils.EmptyString { ap.Tenant = admS.cfg.GeneralCfg().DefaultTenant } diff --git a/config/config_defaults.go b/config/config_defaults.go index e41286f25..05370228a 100644 --- a/config/config_defaults.go +++ b/config/config_defaults.go @@ -1646,8 +1646,13 @@ const CGRATES_CFG_JSON = ` {"tag": "ActionTTL", "path": "Actions[<~*req.8>].TTL", "type": "*variable", "value": "~*req.10", "filters": ["*notempty:~*req.8:"]}, {"tag": "ActionType", "path": "Actions[<~*req.8>].Type", "type": "*variable", "value": "~*req.11", "filters": ["*notempty:~*req.8:"]}, {"tag": "ActionOpts", "path": "Actions[<~*req.8>].Opts", "type": "*variable", "value": "~*req.12", "filters": ["*notempty:~*req.8:"]}, - {"tag": "ActionPath", "path": "Actions[<~*req.8>].Diktats.Path", "type": "*variable", "value": "~*req.13","new_branch":true, "filters": ["*notempty:~*req.8:"]}, - {"tag": "ActionValue", "path": "Actions[<~*req.8>].Diktats.Value", "type": "*variable", "value": "~*req.14", "filters": ["*notempty:~*req.8:"]} + {"tag": "ActionWeights", "path": "Actions[<~*req.8>].Weights", "type": "*variable", "value": "~*req.13", "filters": ["*notempty:~*req.8:"]}, + {"tag": "ActionBlockers", "path": "Actions[<~*req.8>].Blockers", "type": "*variable", "value": "~*req.14", "filters": ["*notempty:~*req.8:"]}, + {"tag": "ActionDiktatsID", "path": "Actions[<~*req.8>].Diktats.ID", "type": "*variable", "value": "~*req.15","new_branch":true, "filters": ["*notempty:~*req.8:"]}, + {"tag": "ActionDiktatsFilterIDs", "path": "Actions[<~*req.8>].Diktats.FilterIDs", "type": "*variable", "value": "~*req.16", "filters": ["*notempty:~*req.8:"]}, + {"tag": "ActionDiktatsOpts", "path": "Actions[<~*req.8>].Diktats.Opts", "type": "*variable", "value": "~*req.17", "filters": ["*notempty:~*req.8:"]}, + {"tag": "ActionDiktatsWeights", "path": "Actions[<~*req.8>].Diktats.Weights", "type": "*variable", "value": "~*req.18", "filters": ["*notempty:~*req.8:"]}, + {"tag": "ActionDiktatsBlockers", "path": "Actions[<~*req.8>].Diktats.Blockers", "type": "*variable", "value": "~*req.19", "filters": ["*notempty:~*req.8:"]}, ] }, { diff --git a/engine/model_helpers.go b/engine/model_helpers.go index 53aba1a5d..a68a8659a 100644 --- a/engine/model_helpers.go +++ b/engine/model_helpers.go @@ -2024,7 +2024,9 @@ func (apm ActionProfileMdls) CSVHeader() (result []string) { return []string{"#" + utils.Tenant, utils.ID, utils.FilterIDs, utils.Weights, utils.Blockers, utils.Schedule, utils.TargetType, utils.TargetIDs, utils.ActionID, utils.ActionFilterIDs, utils.ActionTTL, - utils.ActionType, utils.ActionOpts, utils.ActionPath, utils.ActionValue, + utils.ActionType, utils.ActionOpts, utils.ActionWeights, utils.ActionBlockers, + utils.ActionDiktatsID, utils.ActionDiktatsFilterIDs, utils.ActionDiktatsOpts, + utils.ActionDiktatsWeights, utils.ActionDiktatsBlockers, } } @@ -2074,19 +2076,45 @@ func (apm ActionProfileMdls) AsTPActionProfile() (result []*utils.TPActionProfil Type: tp.ActionType, Opts: tp.ActionOpts, Diktats: []*utils.TPAPDiktat{{ - Path: tp.ActionPath, - Value: tp.ActionValue, + ID: tp.ActionDiktatsID, + Opts: tp.ActionDiktatsOpts, }}, } if tp.ActionFilterIDs != utils.EmptyString { tpAAction.FilterIDs = utils.NewStringSet(strings.Split(tp.ActionFilterIDs, utils.InfieldSep)).AsSlice() } + if tp.ActionWeights != utils.EmptyString { + tpAAction.Weights = tp.ActionWeights + } + if tp.ActionBlockers != utils.EmptyString { + tpAAction.Blockers = tp.ActionBlockers + } + if tp.ActionDiktatsFilterIDs != utils.EmptyString { + tpAAction.Diktats[0].FilterIDs = utils.NewStringSet(strings.Split(tp.ActionDiktatsFilterIDs, utils.InfieldSep)).AsSlice() + } + if tp.ActionDiktatsWeights != utils.EmptyString { + tpAAction.Diktats[0].Weights = tp.ActionDiktatsWeights + } + if tp.ActionDiktatsBlockers != utils.EmptyString { + tpAAction.Diktats[0].Blockers = tp.ActionDiktatsBlockers + } aPrf.Actions = append(aPrf.Actions, tpAAction) } else { - aPrf.Actions[lacts-1].Diktats = append(aPrf.Actions[lacts-1].Diktats, &utils.TPAPDiktat{ - Path: tp.ActionPath, - Value: tp.ActionValue, - }) + diktat := &utils.TPAPDiktat{ + ID: tp.ActionDiktatsID, + Opts: tp.ActionDiktatsOpts, + } + if tp.ActionDiktatsFilterIDs != utils.EmptyString { + diktat.FilterIDs = utils.NewStringSet(strings.Split(tp.ActionDiktatsFilterIDs, utils.InfieldSep)).AsSlice() + } + if tp.ActionDiktatsWeights != utils.EmptyString { + diktat.Weights = tp.ActionDiktatsWeights + } + if tp.ActionDiktatsBlockers != utils.EmptyString { + diktat.Blockers = tp.ActionDiktatsBlockers + } + aPrf.Actions[lacts-1].Diktats = append(aPrf.Actions[lacts-1].Diktats, + diktat) } } actPrfMap[tenID] = aPrf @@ -2129,6 +2157,8 @@ func APItoModelTPActionProfile(tPrf *utils.TPActionProfile) (mdls ActionProfileM mdl.ActionTTL = action.TTL mdl.ActionType = action.Type mdl.ActionOpts = action.Opts + mdl.ActionWeights = action.Weights + mdl.ActionBlockers = action.Blockers for j, actD := range action.Diktats { if j != 0 { mdl = &ActionProfileMdl{ @@ -2139,8 +2169,11 @@ func APItoModelTPActionProfile(tPrf *utils.TPActionProfile) (mdls ActionProfileM ActionType: mdl.ActionType, } } - mdl.ActionPath = actD.Path - mdl.ActionValue = actD.Value + mdl.ActionDiktatsID = actD.ID + mdl.ActionDiktatsFilterIDs = strings.Join(actD.FilterIDs, utils.InfieldSep) + mdl.ActionDiktatsOpts = actD.Opts + mdl.ActionDiktatsWeights = actD.Weights + mdl.ActionDiktatsBlockers = actD.Blockers } mdls = append(mdls, mdl) } @@ -2173,9 +2206,33 @@ func APItoActionProfile(tpAp *utils.TPActionProfile, timezone string) (ap *utils for i, act := range tpAp.Actions { actDs := make([]*utils.APDiktat, len(act.Diktats)) for j, actD := range act.Diktats { + if actD.ID == utils.EmptyString { + return nil, fmt.Errorf("missing ID from Diktats of ActionProfile <%s> Action <%s>", ap.TenantID(), actD.ID) + } actDs[j] = &utils.APDiktat{ - Path: actD.Path, - Value: actD.Value, + ID: actD.ID, + FilterIDs: actD.FilterIDs, + } + if actD.Opts != utils.EmptyString { + actDs[j].Opts = make(map[string]any) + for opt := range strings.SplitSeq(actD.Opts, utils.InfieldSep) { // example of opts: key1:val1;key2:val2;key3:val3 + keyValSls := utils.SplitPath(opt, utils.InInFieldSep[0], 2) + if len(keyValSls) != 2 { + return nil, fmt.Errorf("malformed option for ActionProfile <%s> for action <%s> for diktat <%s>", ap.TenantID(), actD.ID, actD.ID) + + } + actDs[j].Opts[keyValSls[0]] = keyValSls[1] + } + } + if actD.Weights != utils.EmptyString { + if actDs[j].Weights, err = utils.NewDynamicWeightsFromString(actD.Weights, utils.InfieldSep, utils.ANDSep); err != nil { + return + } + } + if actD.Blockers != utils.EmptyString { + if actDs[j].Blockers, err = utils.NewDynamicBlockersFromString(actD.Blockers, utils.InfieldSep, utils.ANDSep); err != nil { + return + } } } ap.Actions[i] = &utils.APAction{ @@ -2189,8 +2246,8 @@ func APItoActionProfile(tpAp *utils.TPActionProfile, timezone string) (ap *utils } if act.Opts != utils.EmptyString { ap.Actions[i].Opts = make(map[string]any) - for _, opt := range strings.Split(act.Opts, utils.InfieldSep) { // example of opts: key1:val1;key2:val2;key3:val3 - keyValSls := utils.SplitConcatenatedKey(opt) + for opt := range strings.SplitSeq(act.Opts, utils.InfieldSep) { // example of opts: key1:val1;key2:val2;key3:val3 + keyValSls := utils.SplitPath(opt, utils.InInFieldSep[0], 2) if len(keyValSls) != 2 { err = fmt.Errorf("malformed option for ActionProfile <%s> for action <%s>", ap.TenantID(), act.ID) return @@ -2198,7 +2255,16 @@ func APItoActionProfile(tpAp *utils.TPActionProfile, timezone string) (ap *utils ap.Actions[i].Opts[keyValSls[0]] = keyValSls[1] } } - + if act.Weights != utils.EmptyString { + if ap.Actions[i].Weights, err = utils.NewDynamicWeightsFromString(act.Weights, utils.InfieldSep, utils.ANDSep); err != nil { + return + } + } + if act.Blockers != utils.EmptyString { + if ap.Actions[i].Blockers, err = utils.NewDynamicBlockersFromString(act.Blockers, utils.InfieldSep, utils.ANDSep); err != nil { + return + } + } } return } @@ -2221,9 +2287,16 @@ func ActionProfileToAPI(ap *utils.ActionProfile) (tpAp *utils.TPActionProfile) { for i, act := range ap.Actions { actDs := make([]*utils.TPAPDiktat, len(act.Diktats)) for j, actD := range act.Diktats { + elems := make([]string, 0, len(actD.Opts)) + for k, v := range actD.Opts { + elems = append(elems, utils.ConcatenatedKey(k, utils.IfaceAsString(v))) + } actDs[j] = &utils.TPAPDiktat{ - Path: actD.Path, - Value: actD.Value, + ID: actD.ID, + FilterIDs: actD.FilterIDs, + Opts: strings.Join(elems, utils.InfieldSep), + Weights: actD.Weights.String(utils.InfieldSep, utils.ANDSep), + Blockers: actD.Blockers.String(utils.InfieldSep, utils.ANDSep), } } @@ -2236,8 +2309,10 @@ func ActionProfileToAPI(ap *utils.ActionProfile) (tpAp *utils.TPActionProfile) { FilterIDs: act.FilterIDs, TTL: act.TTL.String(), Type: act.Type, - Diktats: actDs, Opts: strings.Join(elems, utils.InfieldSep), + Weights: act.Weights.String(utils.InfieldSep, utils.ANDSep), + Blockers: act.Blockers.String(utils.InfieldSep, utils.ANDSep), + Diktats: actDs, } } return diff --git a/engine/models.go b/engine/models.go index 2983864df..2dacec62d 100644 --- a/engine/models.go +++ b/engine/models.go @@ -325,23 +325,28 @@ func (RateProfileMdl) TableName() string { } type ActionProfileMdl struct { - PK uint `gorm:"primary_key"` - Tpid string - Tenant string `index:"0" re:".*"` - ID string `index:"1" re:".*"` - FilterIDs string `index:"2" re:".*"` - Weights string `index:"3" re:".*"` - Blockers string `index:"4" re:".*"` - Schedule string `index:"5" re:".*"` - TargetType string `index:"6" re:".*"` - TargetIDs string `index:"7" re:".*"` - ActionID string `index:"8" re:".*"` - ActionFilterIDs string `index:"9" re:".*"` - ActionTTL string `index:"10" re:".*"` - ActionType string `index:"11" re:".*"` - ActionOpts string `index:"12" re:".*"` - ActionPath string `index:"13" re:".*"` - ActionValue string `index:"14" re:".*"` + PK uint `gorm:"primary_key"` + Tpid string + Tenant string `index:"0" re:".*"` + ID string `index:"1" re:".*"` + FilterIDs string `index:"2" re:".*"` + Weights string `index:"3" re:".*"` + Blockers string `index:"4" re:".*"` + Schedule string `index:"5" re:".*"` + TargetType string `index:"6" re:".*"` + TargetIDs string `index:"7" re:".*"` + ActionID string `index:"8" re:".*"` + ActionFilterIDs string `index:"9" re:".*"` + ActionTTL string `index:"10" re:".*"` + ActionType string `index:"11" re:".*"` + ActionOpts string `index:"12" re:".*"` + ActionWeights string `index:"13" re:".*"` + ActionBlockers string `index:"14" re:".*"` + ActionDiktatsID string `index:"15" re:".*"` + ActionDiktatsFilterIDs string `index:"16" re:".*"` + ActionDiktatsOpts string `index:"17" re:".*"` + ActionDiktatsWeights string `index:"18" re:".*"` + ActionDiktatsBlockers string `index:"19" re:".*"` CreatedAt time.Time } diff --git a/tpes/tpe_actions.go b/tpes/tpe_actions.go index d072c67e2..2d92e3843 100644 --- a/tpes/tpe_actions.go +++ b/tpes/tpe_actions.go @@ -45,7 +45,12 @@ func (tpActs TPActions) exportItems(ctx *context.Context, wrtr io.Writer, tnt st csvWriter := csv.NewWriter(wrtr) csvWriter.Comma = utils.CSVSep // before writing the profiles, we must write the headers - if err = csvWriter.Write([]string{"#Tenant", "ID", "FilterIDs", "Weights", "Blockers", "Schedule", "TargetType", "TargetIDs", "ActionID", "ActionFilterIDs", "ActionTTL", "ActionType", "ActionOpts", "ActionPath", "ActionValue"}); err != nil { + if err = csvWriter.Write([]string{"#" + utils.Tenant, utils.ID, utils.FilterIDs, + utils.Weights, utils.Blockers, utils.Schedule, utils.TargetType, utils.TargetIDs, + utils.ActionID, utils.ActionFilterIDs, utils.ActionTTL, + utils.ActionType, utils.ActionOpts, utils.ActionWeights, utils.ActionBlockers, + utils.ActionDiktatsID, utils.ActionDiktatsFilterIDs, utils.ActionDiktatsOpts, + utils.ActionDiktatsWeights, utils.ActionDiktatsBlockers}); err != nil { return } for _, actsID := range itmIDs { diff --git a/utils/actions.go b/utils/actions.go index f2c0d9d86..0e7e91b17 100644 --- a/utils/actions.go +++ b/utils/actions.go @@ -20,6 +20,7 @@ package utils import ( "maps" + "slices" "strconv" "strings" "time" @@ -297,6 +298,8 @@ type APAction struct { TTL time.Duration // Cancel Action if not executed within TTL Type string // Type of Action Opts map[string]any // Extra options to pass depending on action type + Weights DynamicWeights + Blockers DynamicBlockers Diktats []*APDiktat } @@ -318,6 +321,12 @@ func (a *APAction) Clone() *APAction { cloned.Opts = make(map[string]any, len(a.Opts)) maps.Copy(cloned.Opts, a.Opts) } + if a.Weights != nil { + cloned.Weights = a.Weights.Clone() + } + if a.Blockers != nil { + cloned.Blockers = a.Blockers.Clone() + } if a.Diktats != nil { cloned.Diktats = make([]*APDiktat, len(a.Diktats)) for i, diktat := range a.Diktats { @@ -336,6 +345,12 @@ func (a *APAction) Set(path []string, val any, newBranch bool) (err error) { if path[0] == Opts { return MapStorage(a.Opts).Set(path[1:], val) } + if path[0] == Diktats && path[1] == Opts { + if len(a.Diktats) == 0 || newBranch { + a.Diktats = append(a.Diktats, new(APDiktat)) + } + return MapStorage(a.Diktats[len(a.Diktats)-1].Opts).Set(path[2:], val) + } return ErrWrongPath case 0: return ErrWrongPath @@ -360,6 +375,14 @@ func (a *APAction) Set(path []string, val any, newBranch bool) (err error) { a.TTL, err = IfaceAsDuration(val) case Opts: a.Opts, err = NewMapFromCSV(IfaceAsString(val)) + case Weights: + if val != EmptyString { + a.Weights, err = NewDynamicWeightsFromString(IfaceAsString(val), InfieldSep, ANDSep) + } + case Blockers: + if val != EmptyString { + a.Blockers, err = NewDynamicBlockersFromString(IfaceAsString(val), InfieldSep, ANDSep) + } } case 2: switch path[0] { @@ -372,10 +395,34 @@ func (a *APAction) Set(path []string, val any, newBranch bool) (err error) { a.Diktats = append(a.Diktats, new(APDiktat)) } switch path[1] { - case Path: - a.Diktats[len(a.Diktats)-1].Path = IfaceAsString(val) - case Value: - a.Diktats[len(a.Diktats)-1].Value = IfaceAsString(val) + default: + if strings.HasPrefix(path[1], Opts) && + path[1][4] == '[' && path[1][len(path[1])-1] == ']' { + a.Opts[path[1][5:len(path[1])-1]] = val + return + } + return ErrWrongPath + case ID: + if dID := IfaceAsString(val); dID == EmptyString { + return ErrWrongPath + } else { + a.Diktats[len(a.Diktats)-1].ID = dID + } + case FilterIDs: + var valA []string + valA, err = IfaceAsStringSlice(val) + a.Diktats[len(a.Diktats)-1].FilterIDs = append(a.Diktats[len(a.Diktats)-1]. + FilterIDs, valA...) + case Opts: + a.Diktats[len(a.Diktats)-1].Opts, err = NewMapFromCSV(IfaceAsString(val)) + case Weights: + if val != EmptyString { + a.Diktats[len(a.Diktats)-1].Weights, err = NewDynamicWeightsFromString(IfaceAsString(val), InfieldSep, ANDSep) + } + case Blockers: + if val != EmptyString { + a.Diktats[len(a.Diktats)-1].Blockers, err = NewDynamicBlockersFromString(IfaceAsString(val), InfieldSep, ANDSep) + } } } } @@ -387,23 +434,31 @@ func (a *APAction) Merge(v2 *APAction) { if len(v2.ID) != 0 { a.ID = v2.ID } + a.FilterIDs = append(a.FilterIDs, v2.FilterIDs...) if v2.TTL != 0 { a.TTL = v2.TTL } if len(v2.Type) != 0 { a.Type = v2.Type } - for key, value := range v2.Opts { - a.Opts[key] = value + maps.Copy(a.Opts, v2.Opts) + if v2.Blockers != nil { + a.Blockers = append(a.Blockers, v2.Blockers...) } - a.FilterIDs = append(a.FilterIDs, v2.FilterIDs...) - if len(a.Diktats) == 1 && a.Diktats[0].Path == EmptyString { + if v2.Weights != nil { + a.Weights = append(a.Weights, v2.Weights...) + } + if len(a.Diktats) == 1 && len(a.Diktats[0].Opts) == 0 { a.Diktats = a.Diktats[:0] } - for _, diktat := range v2.Diktats { - if diktat.Path != EmptyString { - a.Diktats = append(a.Diktats, diktat) + for _, diktatV2 := range v2.Diktats { + if idx := slices.IndexFunc(a.Diktats, func(a *APDiktat) bool { + return a.ID == diktatV2.ID + }); idx != -1 { + a.Diktats[idx].Merge(diktatV2) + continue } + a.Diktats = append(a.Diktats, diktatV2) } } @@ -472,19 +527,20 @@ func (a *APAction) FieldAsInterface(fldPath []string) (_ any, err error) { return a.Type, nil case Opts: return a.Opts, nil + case Weights: + return a.Weights, nil + case Blockers: + return a.Blockers, nil } case 2: - fld, idxStr := GetPathIndexString(fldPath[0]) - switch fld { - default: + if fld, _ := GetPathIndexString(fldPath[0]); fld != Opts { return nil, ErrNotFound - case Opts: - path := fldPath[1:] - if idxStr != nil { - path = append([]string{*idxStr}, path...) - } - return MapStorage(a.Opts).FieldAsInterface(path) - case Diktats: + } + return MapStorage(a.Opts).FieldAsInterface(fldPath[1:]) + case 3: + if fld, idxStr := GetPathIndexString(fldPath[0]); fld != Diktats { + return nil, ErrNotFound + } else { if idxStr == nil { return nil, ErrNotFound } @@ -502,8 +558,11 @@ func (a *APAction) FieldAsInterface(fldPath []string) (_ any, err error) { // APDiktat defines a path and value operation to be executed by an action. type APDiktat struct { - Path string // Path to execute - Value string // Value to execute on Path + ID string // Diktat ID + FilterIDs []string // Diktat FilterIDs + Opts map[string]any // Diktat options to pass + Weights DynamicWeights + Blockers DynamicBlockers valRSR RSRParsers } @@ -514,8 +573,21 @@ func (d *APDiktat) Clone() *APDiktat { return nil } cloned := &APDiktat{ - Path: d.Path, - Value: d.Value, + ID: d.ID, + } + if d.FilterIDs != nil { + cloned.FilterIDs = make([]string, len(d.FilterIDs)) + copy(cloned.FilterIDs, d.FilterIDs) + } + if d.Opts != nil { + cloned.Opts = make(map[string]any, len(d.Opts)) + maps.Copy(cloned.Opts, d.Opts) + } + if d.Weights != nil { + cloned.Weights = d.Weights.Clone() + } + if d.Blockers != nil { + cloned.Blockers = d.Blockers.Clone() } if d.valRSR != nil { cloned.valRSR = d.valRSR.Clone() @@ -523,10 +595,20 @@ func (d *APDiktat) Clone() *APDiktat { return cloned } +// Merge combines the values from another APDiktat into this one. +func (a *APDiktat) Merge(v2 *APDiktat) { + a.FilterIDs = append(a.FilterIDs, v2.FilterIDs...) + maps.Copy(a.Opts, v2.Opts) + a.Blockers = append(a.Blockers, v2.Blockers...) + a.Weights = append(a.Weights, v2.Weights...) +} + // RSRValues returns the Value as RSRParsers or creates new ones if not initialized. func (dk *APDiktat) RSRValues() (RSRParsers, error) { if dk.valRSR == nil { - return NewRSRParsers(dk.Value, RSRSep) + if _, has := dk.Opts[MetaBalanceValue]; has { + return NewRSRParsers(IfaceAsString(dk.Opts[MetaBalanceValue]), RSRSep) + } } return dk.valRSR, nil } @@ -545,15 +627,52 @@ func (dk *APDiktat) FieldAsString(fldPath []string) (_ string, err error) { // FieldAsInterface implements the DataProvider interface, retrieving field value as interface. func (dk *APDiktat) FieldAsInterface(fldPath []string) (_ any, err error) { - if len(fldPath) != 1 { - return nil, ErrNotFound - } - switch fldPath[0] { + switch len(fldPath) { default: + if fld, idxStr := GetPathIndexString(fldPath[0]); fld == Opts { + path := fldPath[1:] + if idxStr != nil { + path = append([]string{*idxStr}, path...) + } + return MapStorage(dk.Opts).FieldAsInterface(path) + } + fallthrough + case 0: return nil, ErrNotFound - case Path: - return dk.Path, nil - case Value: - return dk.Value, nil + case 1: + switch fldPath[0] { + default: + fld, idxStr := GetPathIndexString(fldPath[0]) + if idxStr != nil { + switch fld { + case FilterIDs: + var idx int + if idx, err = strconv.Atoi(*idxStr); err != nil { + return + } + if idx < len(dk.FilterIDs) { + return dk.FilterIDs[idx], nil + } + case Opts: + return MapStorage(dk.Opts).FieldAsInterface([]string{*idxStr}) + } + } + return nil, ErrNotFound + case ID: + return dk.ID, nil + case FilterIDs: + return dk.FilterIDs, nil + case Opts: + return dk.Opts, nil + case Weights: + return dk.Weights, nil + case Blockers: + return dk.Blockers, nil + } + case 2: + if fld, _ := GetPathIndexString(fldPath[0]); fld != Opts { + return nil, ErrNotFound + } + return MapStorage(dk.Opts).FieldAsInterface(fldPath[1:]) } } diff --git a/utils/apitpdata.go b/utils/apitpdata.go index 87a5d4d23..c43e1e213 100644 --- a/utils/apitpdata.go +++ b/utils/apitpdata.go @@ -687,12 +687,17 @@ type TPAPAction struct { TTL string Type string Opts string + Weights string + Blockers string Diktats []*TPAPDiktat } type TPAPDiktat struct { - Path string - Value string + ID string + FilterIDs []string + Opts string + Weights string + Blockers string } type TPAccount struct { diff --git a/utils/consts.go b/utils/consts.go index 4e99e913b..0caa74f79 100644 --- a/utils/consts.go +++ b/utils/consts.go @@ -834,7 +834,13 @@ const ( ActionFilterIDs = "ActionFilterIDs" ActionTTL = "ActionTTL" ActionOpts = "ActionOpts" - ActionPath = "ActionPath" + ActionWeights = "ActionWeights" + ActionBlockers = "ActionBlockers" + ActionDiktatsID = "ActionDiktatsID" + ActionDiktatsFilterIDs = "ActionDiktatsFilterIDs" + ActionDiktatsOpts = "ActionDiktatsOpts" + ActionDiktatsWeights = "ActionDiktatsWeights" + ActionDiktatsBlockers = "ActionDiktatsBlockers" TPid = "TPid" LoadId = "LoadId" ActionPlanId = "ActionPlanId" @@ -1131,7 +1137,6 @@ const ( MetaRemoteSetAccount = "*remote_set_account" ActionID = "ActionID" ActionType = "ActionType" - ActionValue = "ActionValue" BalanceValue = "BalanceValue" BalanceUnits = "BalanceUnits" BalanceUnitFactors = "BalanceUnitFactors" @@ -1145,6 +1150,11 @@ const ( MetaDynamicStats = "*dynamic_stats" MetaDynamicAttribute = "*dynamic_attribute" MetaDynamicResource = "*dynamic_resource" + + // Diktats Opts Fields + MetaBalancePath = "*balancePath" + MetaBalanceValue = "*balanceValue" + MetaURL = "*url" ) // Migrator Metas