From 1ee5fa0720090b6f314a9e5879ffaacd3d689a28 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Wed, 5 Feb 2020 15:09:43 +0200 Subject: [PATCH] Added EventCost to DataProvider for filters.Fixes #1910 --- config/navigablemap.go | 20 ++++- engine/cdr.go | 20 ++++- engine/cdre.go | 5 +- engine/eventcost.go | 2 +- engine/filters.go | 3 +- engine/filters_test.go | 164 +++++++++++++++++++++++++++++++++++++++++ utils/consts.go | 2 + 7 files changed, 207 insertions(+), 9 deletions(-) diff --git a/config/navigablemap.go b/config/navigablemap.go index 687608f33..57b0607f3 100644 --- a/config/navigablemap.go +++ b/config/navigablemap.go @@ -159,9 +159,19 @@ func (nM *NavigableMap) FieldAsInterface(fldPath []string) (fldVal interface{}, if i == lenPath-1 { // lastElement return nM.getLastItem(lastMp, spath) } - if lastMp, err = nM.getNextMap(lastMp, spath); err != nil { + var dp interface{} + if dp, err = nM.getNextMap(lastMp, spath); err != nil { return } + switch mv := dp.(type) { // used for cdr when populating eventCost whitin + case map[string]interface{}: + lastMp = mv + case DataProvider: + return mv.FieldAsInterface(fldPath[i+1:]) + default: + return nil, fmt.Errorf("cannot cast field: <%+v> type: %T with path: <%s> to map[string]interface{}", + dp, dp, spath) + } } err = errors.New("end of function") return @@ -213,7 +223,7 @@ func (nM *NavigableMap) getLastItem(mp map[string]interface{}, spath string) (va // getNextMap returns the next map from the given map // used only for path parsing -func (nM *NavigableMap) getNextMap(mp map[string]interface{}, spath string) (map[string]interface{}, error) { +func (nM *NavigableMap) getNextMap(mp map[string]interface{}, spath string) (interface{}, error) { var idx *int spath, idx = nM.getIndex(spath) mi, has := mp[spath] @@ -230,6 +240,8 @@ func (nM *NavigableMap) getNextMap(mp map[string]interface{}, spath string) (map return mv.data, nil case *NavigableMap: return mv.data, nil + case DataProvider: // used for cdr when populating eventCost whitin + return mv, nil default: } } else { @@ -264,6 +276,10 @@ func (nM *NavigableMap) getNextMap(mp map[string]interface{}, spath string) (map if *idx < len(mv) { return mv[*idx].data, nil } + case []DataProvider: // used for cdr when populating eventCost whitin + if *idx < len(mv) { + return mv[*idx], nil + } default: } return nil, utils.ErrNotFound // xml compatible diff --git a/engine/cdr.go b/engine/cdr.go index 4796c2896..ed944710a 100644 --- a/engine/cdr.go +++ b/engine/cdr.go @@ -153,7 +153,10 @@ func (cdr *CDR) FormatCost(shiftDecimals, roundDecimals int) string { // FieldAsString is used to retrieve fields as string, primary fields are const labeled func (cdr *CDR) FieldAsString(rsrPrs *config.RSRParser) (parsed string, err error) { parsed, err = rsrPrs.ParseDataProviderWithInterfaces( - config.NewNavigableMap(map[string]interface{}{utils.MetaReq: cdr.AsMapStringIface()}), utils.NestingSep) + config.NewNavigableMap(map[string]interface{}{ + utils.MetaReq: cdr.AsMapStringIface(), + utils.MetaEC: cdr.CostDetails, + }), utils.NestingSep) if err != nil { return } @@ -163,7 +166,10 @@ func (cdr *CDR) FieldAsString(rsrPrs *config.RSRParser) (parsed string, err erro // FieldsAsString concatenates values of multiple fields defined in template, used eg in CDR templates func (cdr *CDR) FieldsAsString(rsrFlds config.RSRParsers) string { outVal, err := rsrFlds.ParseDataProviderWithInterfaces( - config.NewNavigableMap(map[string]interface{}{utils.MetaReq: cdr.AsMapStringIface()}), utils.NestingSep) + config.NewNavigableMap(map[string]interface{}{ + utils.MetaReq: cdr.AsMapStringIface(), + utils.MetaEC: cdr.CostDetails, + }), utils.NestingSep) if err != nil { return "" } @@ -403,7 +409,10 @@ func (cdr *CDR) formatField(cfgFld *config.FCTemplate, httpSkipTLSCheck bool, // ExportRecord is a []string to keep it compatible with encoding/csv Writer func (cdr *CDR) AsExportRecord(exportFields []*config.FCTemplate, httpSkipTLSCheck bool, groupedCDRs []*CDR, filterS *FilterS) (expRecord []string, err error) { - nM := config.NewNavigableMap(map[string]interface{}{utils.MetaReq: cdr.AsMapStringIface()}) + nM := config.NewNavigableMap(map[string]interface{}{ + utils.MetaReq: cdr.AsMapStringIface(), + utils.MetaEC: cdr.CostDetails, + }) for _, cfgFld := range exportFields { if !strings.HasPrefix(cfgFld.Path, utils.MetaExp) { continue @@ -430,7 +439,10 @@ func (cdr *CDR) AsExportRecord(exportFields []*config.FCTemplate, func (cdr *CDR) AsExportMap(exportFields []*config.FCTemplate, httpSkipTLSCheck bool, groupedCDRs []*CDR, filterS *FilterS) (expMap map[string]string, err error) { expMap = make(map[string]string) - nM := config.NewNavigableMap(map[string]interface{}{utils.MetaReq: cdr.AsMapStringIface()}) + nM := config.NewNavigableMap(map[string]interface{}{ + utils.MetaReq: cdr.AsMapStringIface(), + utils.MetaEC: cdr.CostDetails, + }) for _, cfgFld := range exportFields { if !strings.HasPrefix(cfgFld.Path, utils.MetaExp+utils.NestingSep) { continue diff --git a/engine/cdre.go b/engine/cdre.go index 0ced37348..800c606a2 100644 --- a/engine/cdre.go +++ b/engine/cdre.go @@ -390,7 +390,10 @@ func (cdre *CDRExporter) processCDRs() (err error) { continue } if len(cdre.exportTemplate.Filters) != 0 { - cgrDp := config.NewNavigableMap(map[string]interface{}{utils.MetaReq: cdr.AsMapStringIface()}) + cgrDp := config.NewNavigableMap(map[string]interface{}{ + utils.MetaReq: cdr.AsMapStringIface(), + utils.MetaEC: cdr.CostDetails, + }) if pass, err := cdre.filterS.Pass(cdre.exportTemplate.Tenant, cdre.exportTemplate.Filters, cgrDp); err != nil || !pass { continue // Not passes filters, ignore this CDR diff --git a/engine/eventcost.go b/engine/eventcost.go index f63029980..116090a31 100644 --- a/engine/eventcost.go +++ b/engine/eventcost.go @@ -934,7 +934,7 @@ func (ec *EventCost) getChargesForPath(fldPath []string, chr *ChargingInterval) return incr, nil } if fldPath[1] == utils.Accounting { - return ec.getAcountingForPath(fldPath[3:], ec.Accounting[incr.AccountingID]) + return ec.getAcountingForPath(fldPath[2:], ec.Accounting[incr.AccountingID]) } return incr.FieldAsInterface(fldPath) } diff --git a/engine/filters.go b/engine/filters.go index efc25db6f..5467e51be 100644 --- a/engine/filters.go +++ b/engine/filters.go @@ -484,7 +484,8 @@ func (fS *FilterS) getFieldNameDataProvider(initialDP config.DataProvider, strings.HasPrefix(fieldName, utils.DynamicDataPrefix+utils.MetaCgrep), strings.HasPrefix(fieldName, utils.DynamicDataPrefix+utils.MetaRep), strings.HasPrefix(fieldName, utils.DynamicDataPrefix+utils.MetaCGRAReq), - strings.HasPrefix(fieldName, utils.DynamicDataPrefix+utils.MetaAct): + strings.HasPrefix(fieldName, utils.DynamicDataPrefix+utils.MetaAct), + strings.HasPrefix(fieldName, utils.DynamicDataPrefix+utils.MetaEC): dp = initialDP // don't need to take out the prefix because the navigable map have ~*req prefix case fieldName == utils.EmptyString: diff --git a/engine/filters_test.go b/engine/filters_test.go index 806a30393..e53f2adf1 100644 --- a/engine/filters_test.go +++ b/engine/filters_test.go @@ -1017,3 +1017,167 @@ func TestPassFilterMissingField(t *testing.T) { t.Errorf("Expecting: false , received: %+v", pass) } } + +func TestEventCostFilter(t *testing.T) { + + cfg, _ := config.NewDefaultCGRConfig() + data := NewInternalDB(nil, nil, true, cfg.DataDbCfg().Items) + dmFilterPass := NewDataManager(data, config.CgrConfig().CacheCfg(), nil) + filterS := FilterS{ + cfg: cfg, + dm: dmFilterPass, + } + cd := &EventCost{ + Cost: utils.Float64Pointer(0.264933), + CGRID: "d8534def2b7067f4f5ad4f7ec7bbcc94bb46111a", + Rates: ChargedRates{ + "3db483c": RateGroups{ + { + Value: 0.1574, + RateUnit: 60000000000, + RateIncrement: 30000000000, + GroupIntervalStart: 0, + }, + { + Value: 0.1574, + RateUnit: 60000000000, + RateIncrement: 1000000000, + GroupIntervalStart: 30000000000, + }, + }, + }, + RunID: "*default", + Usage: utils.DurationPointer(101 * time.Second), + Rating: Rating{ + "7f3d423": &RatingUnit{ + MaxCost: 40, + RatesID: "3db483c", + TimingID: "128e970", + ConnectFee: 0, + RoundingMethod: "*up", + MaxCostStrategy: "*disconnect", + RatingFiltersID: "f8e95f2", + RoundingDecimals: 4, + }, + }, + Charges: []*ChargingInterval{ + { + RatingID: "7f3d423", + Increments: []*ChargingIncrement{ + { + Cost: 0.0787, + Usage: 30000000000, + AccountingID: "fee8a3a", + CompressFactor: 1, + }, + }, + CompressFactor: 1, + }, + { + RatingID: "7f3d423", + Increments: []*ChargingIncrement{ + { + Cost: 0.002623, + Usage: 1000000000, + AccountingID: "3463957", + CompressFactor: 71, + }, + }, + CompressFactor: 1, + }, + }, + Timings: ChargedTimings{ + "128e970": &ChargedTiming{ + StartTime: "00:00:00", + }, + }, + StartTime: time.Date(2019, 12, 06, 11, 57, 32, 0, time.UTC), + Accounting: Accounting{ + "3463957": &BalanceCharge{ + Units: 0.002623, + RatingID: "", + AccountID: "cgrates.org:1001", + BalanceUUID: "154419f2-45e0-4629-a203-06034ccb493f", + ExtraChargeID: "", + }, + "fee8a3a": &BalanceCharge{ + Units: 0.0787, + RatingID: "", + AccountID: "cgrates.org:1001", + BalanceUUID: "154419f2-45e0-4629-a203-06034ccb493f", + ExtraChargeID: "", + }, + }, + RatingFilters: RatingFilters{ + "f8e95f2": RatingMatchedFilters{ + "Subject": "*out:cgrates.org:mo_call_UK_Mobile_O2_GBRCN:*any", + "RatingPlanID": "RP_MO_CALL_44800", + "DestinationID": "DST_44800", + "DestinationPrefix": "44800", + }, + }, + AccountSummary: &AccountSummary{ + ID: "234189200129930", + Tenant: "cgrates.org", + Disabled: false, + AllowNegative: false, + BalanceSummaries: BalanceSummaries{ + &BalanceSummary{ + ID: "MOBILE_DATA", + Type: "*data", + UUID: "08a05723-5849-41b9-b6a9-8ee362539280", + Value: 3221225472, + Disabled: false, + }, + &BalanceSummary{ + ID: "MOBILE_SMS", + Type: "*sms", + UUID: "06a87f20-3774-4eeb-826e-a79c5f175fd3", + Value: 247, + Disabled: false, + }, + &BalanceSummary{ + ID: "MOBILE_VOICE", + Type: "*voice", + UUID: "4ad16621-6e22-4e35-958e-5e1ff93ad7b7", + Value: 14270000000000, + Disabled: false, + }, + &BalanceSummary{ + ID: "MONETARY_POSTPAID", + Type: "*monetary", + UUID: "154419f2-45e0-4629-a203-06034ccb493f", + Value: 50, + Disabled: false, + }, + }, + }, + } + + cgrDp := config.NewNavigableMap(map[string]interface{}{utils.MetaEC: cd}) + + if pass, err := filterS.Pass("cgrates.org", + []string{"*string:~*ec.Charges[0].Increments[0].Accounting.Balance.Value:50"}, cgrDp); err != nil { + t.Errorf(err.Error()) + } else if !pass { + t.Errorf("Expecting: true , received: %+v", pass) + } + if pass, err := filterS.Pass("cgrates.org", + []string{"*string:~*ec.Charges[0].Increments[0].Accounting.AccountID:cgrates.org:1001"}, cgrDp); err != nil { + t.Errorf(err.Error()) + } else if !pass { + t.Errorf("Expecting: true , received: %+v", pass) + } + if pass, err := filterS.Pass("cgrates.org", + []string{"*string:~*ec.Charges[0].Rating.Rates[0].Value:0.1574"}, cgrDp); err != nil { + t.Errorf(err.Error()) + } else if !pass { + t.Errorf("Expecting: true , received: %+v", pass) + } + if pass, err := filterS.Pass("cgrates.org", + []string{"*string:~*ec.Charges[0].Increments[0].Accounting.Balance.ID:MONETARY_POSTPAID"}, cgrDp); err != nil { + t.Errorf(err.Error()) + } else if !pass { + t.Errorf("Expecting: true , received: %+v", pass) + } +} diff --git a/utils/consts.go b/utils/consts.go index 45bf0bddf..5d1290f26 100755 --- a/utils/consts.go +++ b/utils/consts.go @@ -882,6 +882,8 @@ const ( MetaNotDestinations = "*notdestinations" MetaNotResources = "*notresources" MetaNotEqual = "*noteq" + + MetaEC = "*ec" ) // ReplicatorSv1 APIs