diff --git a/service/account/api/api.go b/service/account/api/api.go index 2613e36ac34..f91c3bddcc3 100644 --- a/service/account/api/api.go +++ b/service/account/api/api.go @@ -108,7 +108,7 @@ func GetConsumptionAmount(c *gin.Context) { c.JSON(http.StatusUnauthorized, gin.H{"error": fmt.Sprintf("authenticate error : %v", err)}) return } - amount, err := dao.DBClient.GetConsumptionAmount(req.Owner, req.Namespace, req.AppType, req.TimeRange.StartTime, req.TimeRange.EndTime) + amount, err := dao.DBClient.GetConsumptionAmount(*req) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("failed to get consumption amount : %v", err)}) return diff --git a/service/account/dao/interface.go b/service/account/dao/interface.go index 335f196fe96..d69ed3e03cf 100644 --- a/service/account/dao/interface.go +++ b/service/account/dao/interface.go @@ -34,7 +34,7 @@ type Interface interface { GetBasicCostDistribution(req helper.GetCostAppListReq) (map[string]int64, error) GetCostAppList(req helper.GetCostAppListReq) (helper.CostAppListResp, error) Disconnect(ctx context.Context) error - GetConsumptionAmount(user, namespace, appType string, startTime, endTime time.Time) (int64, error) + GetConsumptionAmount(req helper.ConsumptionRecordReq) (int64, error) GetRechargeAmount(ops types.UserQueryOpts, startTime, endTime time.Time) (int64, error) GetPropertiesUsedAmount(user string, startTime, endTime time.Time) (map[string]int64, error) GetAccount(ops types.UserQueryOpts) (*types.Account, error) @@ -462,10 +462,6 @@ func (m *MongoDB) getAppStoreCosts(matchConditions bson.D, skip, limit int) (*co return &results, nil } -func (m *MongoDB) GetConsumptionAmount(user, namespace, appType string, startTime, endTime time.Time) (int64, error) { - return m.getAmountWithType(0, user, namespace, appType, startTime, endTime) -} - func (m *MongoDB) GetCostOverview(req helper.GetCostAppListReq) (resp helper.CostOverviewResp, rErr error) { appResp, err := m.GetCostAppList(req) if err != nil { @@ -474,7 +470,7 @@ func (m *MongoDB) GetCostOverview(req helper.GetCostAppListReq) (resp helper.Cos } resp.LimitResp = appResp.LimitResp for _, app := range appResp.Apps { - totalAmount, err := m.GetTotalAppCost(req.Owner, app.Namespace, app.AppName, app.AppType) + totalAmount, err := m.getTotalAppCost(req, app) if err != nil { rErr = fmt.Errorf("failed to get total app cost: %w", err) return @@ -489,18 +485,41 @@ func (m *MongoDB) GetCostOverview(req helper.GetCostAppListReq) (resp helper.Cos return } -func (m *MongoDB) GetTotalAppCost(owner string, namespace string, appName string, appType uint8) (int64, error) { +func (m *MongoDB) getTotalAppCost(req helper.GetCostAppListReq, app helper.CostApp) (int64, error) { + owner := req.Owner + namespace := app.Namespace + appName := app.AppName + appType := app.AppType + if req.StartTime.IsZero() { + req.StartTime = time.Now().UTC().Add(-time.Hour * 24 * 30) + req.EndTime = time.Now().UTC() + } + match := bson.M{ + "owner": owner, + "namespace": namespace, + "app_costs.name": appName, + "app_type": appType, + "time": bson.M{ + "$gte": req.StartTime, + "$lte": req.EndTime, + }, + } + appStoreMatch := bson.M{ + "owner": owner, + "namespace": namespace, + "app_name": appName, + "app_type": appType, + "time": bson.M{ + "$gte": req.StartTime, + "$lte": req.EndTime, + }, + } var pipeline mongo.Pipeline if appType == resources.AppType[resources.AppStore] { // If appType is 8, match app_name and app_type directly pipeline = mongo.Pipeline{ - {{Key: "$match", Value: bson.D{ - {Key: "owner", Value: owner}, - {Key: "namespace", Value: namespace}, - {Key: "app_name", Value: appName}, - {Key: "app_type", Value: appType}, - }}}, + {{Key: "$match", Value: appStoreMatch}}, {{Key: "$group", Value: bson.D{ {Key: "_id", Value: nil}, {Key: "totalAmount", Value: bson.D{{Key: "$sum", Value: "$amount"}}}, @@ -509,12 +528,7 @@ func (m *MongoDB) GetTotalAppCost(owner string, namespace string, appName string } else { // Otherwise, match inside app_costs pipeline = mongo.Pipeline{ - {{Key: "$match", Value: bson.D{ - {Key: "owner", Value: owner}, - {Key: "namespace", Value: namespace}, - {Key: "app_costs.name", Value: appName}, - {Key: "app_type", Value: appType}, - }}}, + {{Key: "$match", Value: match}}, {{Key: "$unwind", Value: "$app_costs"}}, {{Key: "$match", Value: bson.D{ {Key: "app_costs.name", Value: appName}, @@ -987,29 +1001,36 @@ func (m *MongoDB) Disconnect(ctx context.Context) error { return m.Client.Disconnect(ctx) } -func (m *MongoDB) getAmountWithType(_type int64, user, namespace, _appType string, startTime, endTime time.Time) (int64, error) { +func (m *MongoDB) GetConsumptionAmount(req helper.ConsumptionRecordReq) (int64, error) { + owner, namespace, appType, appName, startTime, endTime := req.Owner, req.Namespace, req.AppType, req.AppName, req.TimeRange.StartTime, req.TimeRange.EndTime timeMatchValue := bson.D{primitive.E{Key: "$gte", Value: startTime}, primitive.E{Key: "$lte", Value: endTime}} matchValue := bson.D{ primitive.E{Key: "time", Value: timeMatchValue}, - primitive.E{Key: "owner", Value: user}, - primitive.E{Key: "type", Value: _type}, + primitive.E{Key: "owner", Value: owner}, + } + if appType != "" { + matchValue = append(matchValue, primitive.E{Key: "app_type", Value: resources.AppType[strings.ToUpper(appType)]}) } if namespace != "" { matchValue = append(matchValue, primitive.E{Key: "namespace", Value: namespace}) } - if _appType != "" { - matchValue = append(matchValue, primitive.E{Key: "app_type", Value: resources.AppType[strings.ToUpper(_appType)]}) + unwindMatchValue := bson.D{ + primitive.E{Key: "time", Value: timeMatchValue}, } - matchStage := bson.D{ - primitive.E{ - Key: "$match", Value: matchValue, - }, + if appType != "" && appName != "" { + if appType != resources.AppStore { + unwindMatchValue = append(unwindMatchValue, primitive.E{Key: "app_costs.name", Value: appName}) + } else { + unwindMatchValue = append(unwindMatchValue, primitive.E{Key: "app_name", Value: appName}) + } } pipeline := bson.A{ - matchStage, + bson.D{{Key: "$match", Value: matchValue}}, + bson.D{{Key: "$unwind", Value: "$app_costs"}}, + bson.D{{Key: "$match", Value: unwindMatchValue}}, bson.D{{Key: "$group", Value: bson.M{ "_id": nil, - "total": bson.M{"$sum": "$amount"}, + "total": bson.M{"$sum": "$app_costs.amount"}, }}}, } diff --git a/service/account/dao/interface_test.go b/service/account/dao/interface_test.go index 2c193d75432..07dd14d92d0 100644 --- a/service/account/dao/interface_test.go +++ b/service/account/dao/interface_test.go @@ -381,6 +381,62 @@ func TestMongoDB_GetAppCostTimeRange(t *testing.T) { t.Logf("costAppList: %v", timeRange) } +func TestMongoDB_GetConsumptionAmount(t *testing.T) { + dbCTX := context.Background() + m, err := newAccountForTest(os.Getenv("MONGO_URI"), "", "") + if err != nil { + t.Fatalf("NewAccountInterface() error = %v", err) + return + } + defer func() { + if err = m.Disconnect(dbCTX); err != nil { + t.Errorf("failed to disconnect mongo: error = %v", err) + } + }() + req := helper.GetCostAppListReq{ + Auth: &helper.Auth{ + Owner: "uy771xun", + }, + Namespace: "ns-uy771xun", + AppType: "APP-STORE", + AppName: "wordpress-nljzdohs", + LimitReq: helper.LimitReq{ + TimeRange: helper.TimeRange{ + StartTime: time.Now().Add(-24 * time.Hour * 300), + EndTime: time.Now(), + }, + PageSize: 20, + Page: 1, + }, + } + costs, err := m.GetCostOverview(req) + if err != nil { + t.Fatalf("failed to get cost app list: %v", err) + } + t.Logf("GetCostOverview: %v", costs) + amountAll := int64(0) + for _, cost := range costs.Overviews { + amountAll += cost.Amount + } + t.Logf("amountAll: %v", amountAll) + amount2, err := m.GetConsumptionAmount(helper.ConsumptionRecordReq{ + Auth: &helper.Auth{ + Owner: req.Owner, + }, + TimeRange: helper.TimeRange{ + StartTime: req.StartTime, + EndTime: req.EndTime, + }, + Namespace: req.Namespace, + AppType: req.AppType, + AppName: req.AppName, + }) + if err != nil { + t.Fatalf("failed to get cost app list: %v", err) + } + t.Logf("GetConsumptionAmount: %v", amount2) +} + func TestMongoDB_GetAppCost1(t *testing.T) { dbCTX := context.Background() m, err := newAccountForTest(os.Getenv("MONGO_URI"), "", "") diff --git a/service/account/helper/request.go b/service/account/helper/request.go index 11ce0cc4f08..99f2acd923f 100644 --- a/service/account/helper/request.go +++ b/service/account/helper/request.go @@ -81,6 +81,10 @@ type ConsumptionRecordReq struct { // @Summary App type // @Description App type AppType string `json:"appType,omitempty" bson:"appType" example:"app"` + + // @Summary App Name + // @Description App Name + AppName string `json:"appName,omitempty" bson:"appName" example:"app"` } type NamespaceBillingHistoryResp struct { diff --git a/service/go.work.sum b/service/go.work.sum index f354d364be4..84dc085c5c9 100644 --- a/service/go.work.sum +++ b/service/go.work.sum @@ -634,6 +634,7 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:ma github.com/creack/pty v1.1.9 h1:uDmaGzcdjhF4i/plgjmEsriH11Y0o7RKapEf/LDaM3w= github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= @@ -782,6 +783,7 @@ github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJ github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jackc/pgx/v5 v5.5.4 h1:Xp2aQS8uXButQdnCMWNmvx6UysWQQC+u1EoizjguY+8= github.com/jackc/pgx/v5 v5.5.4/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A= github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= @@ -1104,6 +1106,7 @@ golang.org/x/oauth2 v0.9.0/go.mod h1:qYgFZaFiu6Wg24azG8bdV52QJXJGbZzIIsRCdVKzbLw golang.org/x/oauth2 v0.13.0/go.mod h1:/JMhi4ZRXAf4HG9LiNmxvk+45+96RUlVThiH8FzNBn0= golang.org/x/oauth2 v0.14.0/go.mod h1:lAtNWgaWfL4cm7j2OV8TxGi9Qb7ECORx8DktCY74OwM= golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o= +golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI= golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1132,6 +1135,7 @@ golang.org/x/telemetry v0.0.0-20240208230135-b75ee8823808/go.mod h1:KG1lNk5ZFNss golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -1179,6 +1183,7 @@ google.golang.org/genproto v0.0.0-20230803162519-f966b187b2e5/go.mod h1:oH/ZOT02 google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:CgAqfJo+Xmu0GwA0411Ht3OU3OntXwsGmrmjI8ioGXI= google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:J7XzRzVy1+IPwWHZUzoD0IccYZIrXILAQpc+Qy9CMhY= google.golang.org/genproto v0.0.0-20231212172506-995d672761c0/go.mod h1:l/k7rMz0vFTBPy+tFSGvXEd3z+BcoG1k7EHbqm+YBsY= +google.golang.org/genproto v0.0.0-20240116215550-a9fa1716bcac h1:ZL/Teoy/ZGnzyrqK/Optxxp2pmVh+fmJ97slxSRyzUg= google.golang.org/genproto v0.0.0-20240116215550-a9fa1716bcac/go.mod h1:+Rvu7ElI+aLzyDQhpHMFMMltsD6m7nqpuWDd2CwJw3k= google.golang.org/genproto/googleapis/api v0.0.0-20230525234035-dd9d682886f9 h1:m8v1xLLLzMe1m5P+gCTF8nJB9epwZQUBERm20Oy1poQ= google.golang.org/genproto/googleapis/api v0.0.0-20230525234035-dd9d682886f9/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= @@ -1203,6 +1208,7 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b/go. google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:oQ5rr10WTTMvP4A36n8JpR1OrO1BEiV4f78CneXZxkA= google.golang.org/genproto/googleapis/rpc v0.0.0-20231212172506-995d672761c0/go.mod h1:FUoWkonphQm3RhTS+kOEhF8h0iDpm4tdXolVCeZ9KKA= google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917/go.mod h1:xtjpI3tXFPP051KaWnhvxkiubL/6dJ18vLVf7q2pTOU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240125205218-1f4bbc51befe h1:bQnxqljG/wqi4NTXu2+DJ3n7APcEA882QZ1JvhQAq9o= google.golang.org/genproto/googleapis/rpc v0.0.0-20240125205218-1f4bbc51befe/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= @@ -1220,6 +1226,7 @@ google.golang.org/grpc v1.56.2/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpX google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo= google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= google.golang.org/grpc v1.60.1/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM= +google.golang.org/grpc v1.61.0 h1:TOvOcuXn30kRao+gfcvsebNEa5iZIiLkisYEkf7R7o0= google.golang.org/grpc v1.61.0/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0 h1:M1YKkFIboKNieVO5DLUEVzQfGwJD30Nv2jfUgzb5UcE= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= @@ -1286,6 +1293,7 @@ k8s.io/kms v0.29.0/go.mod h1:mB0f9HLxRXeXUfHfn1A7rpwOlzXI1gIWu86z6buNoYA= k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f/go.mod h1:byini6yhqGC14c3ebc/QwanvYwhuMWF6yz2F8uwW8eg= k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20230209194617-a36077c30491/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/utils v0.0.0-20231127182322-b307cd553661 h1:FepOBzJ0GXm8t0su67ln2wAZjbQ6RxQGZDnzuLcrUTI= k8s.io/utils v0.0.0-20231127182322-b307cd553661/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= rsc.io/pdf v0.1.1 h1:k1MczvYDUvJBe93bYd7wrZLLUEcLZAuF824/I4e5Xr4= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.1.2 h1:trsWhjU5jZrx6UvFu4WzQDrN7Pga4a7Qg+zcfcj64PA=