Skip to content
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
828c37c
Created Product Consumption Tracking Module
agzaiyenth May 12, 2026
1532794
updated request body
agzaiyenth May 12, 2026
e6390da
Update client.bal
agzaiyenth May 12, 2026
7c6fd38
Update constants.bal
agzaiyenth May 12, 2026
838f126
updated rest api endpoint
agzaiyenth May 12, 2026
5725a50
Update apps/customer-portal/backend/modules/product_consumption_track…
agzaiyenth May 14, 2026
a7a4213
Update apps/customer-portal/backend/modules/product_consumption_track…
agzaiyenth May 14, 2026
63dc846
Update apps/customer-portal/backend/modules/product_consumption_track…
agzaiyenth May 14, 2026
4cf2f26
Update apps/customer-portal/backend/service.bal
agzaiyenth May 14, 2026
29f12dc
Update apps/customer-portal/backend/modules/product_consumption_track…
agzaiyenth May 14, 2026
78d3a59
Update apps/customer-portal/backend/service.bal
agzaiyenth May 14, 2026
ca7254c
Update apps/customer-portal/backend/modules/product_consumption_track…
agzaiyenth May 14, 2026
4e7184c
Update product_consumption_tracking.bal
agzaiyenth May 14, 2026
da5ef75
Use ImportRequest payload for deployment import
agzaiyenth May 15, 2026
1e669ce
Update apps/customer-portal/backend/service.bal
agzaiyenth May 22, 2026
7b1438d
Use constants for HTTP retry config
agzaiyenth May 23, 2026
759bab8
Update constants.bal
agzaiyenth May 23, 2026
44b2701
Rename deployment-usage endpoint to deployment-usages
agzaiyenth May 24, 2026
e34c4e0
Update apps/customer-portal/backend/modules/product_consumption_track…
agzaiyenth May 25, 2026
cd129ef
Use TIMEOUT constant for client timeout
agzaiyenth May 25, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/customer-portal/backend/Dependencies.toml
Original file line number Diff line number Diff line change
Expand Up @@ -366,10 +366,10 @@ modules = [
{org = "wso2", packageName = "customer_portal", moduleName = "customer_portal.authorization"},
{org = "wso2", packageName = "customer_portal", moduleName = "customer_portal.entity"},
{org = "wso2", packageName = "customer_portal", moduleName = "customer_portal.product_consumption_subscription"},
{org = "wso2", packageName = "customer_portal", moduleName = "customer_portal.product_consumption_tracking"},
{org = "wso2", packageName = "customer_portal", moduleName = "customer_portal.registry"},
{org = "wso2", packageName = "customer_portal", moduleName = "customer_portal.scim"},
{org = "wso2", packageName = "customer_portal", moduleName = "customer_portal.types"},
{org = "wso2", packageName = "customer_portal", moduleName = "customer_portal.updates"},
{org = "wso2", packageName = "customer_portal", moduleName = "customer_portal.user_management"}
]

Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright (c) 2026 WSO2 LLC. (https://www.wso2.com).
//
// WSO2 LLC. licenses this file to you under the Apache License,
// Version 2.0 (the "License"); you may not use this file except
// in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

import ballerina/http;

configurable string productConsumptionTrackingBaseUrl = ?;
configurable ClientCredentialsOauth2Config clientCredentialsOauth2Config = ?;

@display {
label: "Product Consumption Tracking",
id: "product-consumption-tracking"
}
final http:Client productConsumptionTrackingClient = check new (productConsumptionTrackingBaseUrl, {
auth: {...clientCredentialsOauth2Config},
httpVersion: http:HTTP_1_1,
http1Settings: {keepAlive: http:KEEPALIVE_NEVER},
timeout: 300.0,
Comment thread
agzaiyenth marked this conversation as resolved.
Outdated
Comment thread
agzaiyenth marked this conversation as resolved.
Outdated
retryConfig: {
count: 3,
interval: 2.0,
Comment thread
agzaiyenth marked this conversation as resolved.
Outdated
statusCodes: [
http:STATUS_INTERNAL_SERVER_ERROR,
http:STATUS_REQUEST_TIMEOUT,
http:STATUS_BAD_GATEWAY,
http:STATUS_SERVICE_UNAVAILABLE,
http:STATUS_GATEWAY_TIMEOUT
]
}
Comment thread
agzaiyenth marked this conversation as resolved.
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright (c) 2026 WSO2 LLC. (https://www.wso2.com).
//
// WSO2 LLC. licenses this file to you under the Apache License,
// Version 2.0 (the "License"); you may not use this file except
// in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

const CONTENT_TYPE_APPLICATION_ZIP = "application/zip";
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright (c) 2026 WSO2 LLC. (https://www.wso2.com).
//
// WSO2 LLC. licenses this file to you under the Apache License,
// Version 2.0 (the "License"); you may not use this file except
// in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

# Import deployment usage data from a zip file.
#
# + email - Email address of the importing user
# + zipFile - Zip file content as bytes
# + return - Deployment usage import response or error
public isolated function importDeploymentUsage(string email, byte[] zipFile)
returns DeploymentUsageImportResponse|error {

http:Request req = new;
Comment thread
agzaiyenth marked this conversation as resolved.
Outdated
req.setBinaryPayload(zipFile);
check req.setContentType(CONTENT_TYPE_APPLICATION_ZIP);

Comment thread
agzaiyenth marked this conversation as resolved.
return productConsumptionTrackingClient->/deployment\-usage.post(req, email);
Comment thread
agzaiyenth marked this conversation as resolved.
Outdated
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Copyright (c) 2026 WSO2 LLC. (https://www.wso2.com).
//
// WSO2 LLC. licenses this file to you under the Apache License,
// Version 2.0 (the "License"); you may not use this file except
// in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

# [Configurable] Client credentials grant type oauth2 configuration.
public type ClientCredentialsOauth2Config record {|
# Token URL
string tokenUrl;
# Client ID
string clientId;
# Client Secret
string clientSecret;
Comment thread
agzaiyenth marked this conversation as resolved.
# Scopes
string[] scopes = [];
|};

# Deployment usage import response.
public type DeploymentUsageImportResponse record {|
# Response message
string message?;
# Response result
json result?;
Comment thread
agzaiyenth marked this conversation as resolved.
json...;
|};
62 changes: 61 additions & 1 deletion apps/customer-portal/backend/service.bal
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import customer_portal.ai_chat_agent;
import customer_portal.authorization;
import customer_portal.entity;
import customer_portal.product_consumption_subscription;
import customer_portal.product_consumption_tracking;
import customer_portal.registry;
import customer_portal.scim;
import customer_portal.types;
Expand Down Expand Up @@ -4001,7 +4002,7 @@ service http:InterceptableService / on new http:Listener(9090, listenerConf) {
resource function post projects/[entity:IdString id]/cases/time\-cards/search(http:RequestContext ctx,
types:TimeCardSearchPayload payload)
returns http:Ok|http:BadRequest|http:Unauthorized|http:Forbidden|http:InternalServerError {
Comment thread
agzaiyenth marked this conversation as resolved.

Comment thread
agzaiyenth marked this conversation as resolved.
Outdated
authorization:UserInfoPayload|error userInfo = ctx.getWithType(authorization:HEADER_USER_INFO);
if userInfo is error {
return <http:InternalServerError>{
Expand Down Expand Up @@ -5108,6 +5109,65 @@ service http:InterceptableService / on new http:Listener(9090, listenerConf) {
return licenseResponse;
}

# Import product consumption usage from a zip file.
#
# + req - Request containing zip file binary body
# + return - Deployment usage import response or error
isolated resource function post deployment\-usage(http:RequestContext ctx, http:Request req)
returns product_consumption_tracking:DeploymentUsageImportResponse|http:BadRequest|http:InternalServerError {

authorization:UserInfoPayload|error userInfo = ctx.getWithType(authorization:HEADER_USER_INFO);
if userInfo is error {
return <http:InternalServerError>{
body: {
message: ERR_MSG_USER_INFO_HEADER_NOT_FOUND
}
};
}

string? validationError = validateDeploymentUsageImportRequest(req);
if validationError is string {
log:printWarn(validationError);
return <http:BadRequest>{
body: {
message: validationError
Comment thread
agzaiyenth marked this conversation as resolved.
}
};
}

byte[]|http:ClientError zipFile = req.getBinaryPayload();
Comment thread
agzaiyenth marked this conversation as resolved.
if zipFile is http:ClientError {
Comment thread
agzaiyenth marked this conversation as resolved.
string customError = "Failed to read deployment usage import zip file.";
log:printError(customError, zipFile);
return <http:BadRequest>{
body: {
message: customError
}
};
}

product_consumption_tracking:DeploymentUsageImportResponse|error response =
product_consumption_tracking:importDeploymentUsage(userInfo.email, zipFile);
if response is error {
if getStatusCode(response) == http:STATUS_BAD_REQUEST {
return <http:BadRequest>{
body: {
message: "Invalid deployment usage import request."
Comment thread
agzaiyenth marked this conversation as resolved.
}
};
}

string customError = "Failed to import deployment usage data.";
log:printError(customError, response);
return <http:InternalServerError>{
body: {
message: customError
}
};
}
return response;
}

# Get usage stats for a specific project.
#
# + id - ID of the project
Expand Down
17 changes: 17 additions & 0 deletions apps/customer-portal/backend/utils.bal
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,23 @@ configurable types:FeatureFlags featureFlags = {
};
configurable string[] restrictedChangeRequestStateIds = ["-3", "-4", "-5"];

# Validate deployment usage import request.
#
# + req - Request containing zip file binary body
# + return - Error message if validation fails, nil otherwise
isolated function validateDeploymentUsageImportRequest(http:Request req) returns string? {
string contentType = req.getContentType();
if contentType.trim().length() == 0 {
return "Content-Type header is required.";
}

string baseType = re `;`.split(contentType.trim().toLowerAscii())[0].trim();
if baseType != "application/zip" && baseType != "application/x-zip-compressed" {
Comment thread
agzaiyenth marked this conversation as resolved.
Comment thread
agzaiyenth marked this conversation as resolved.
return "Request body must be a zip file.";
}
return;
}

# Search cases for a given project.
#
# + idToken - ID token for authorization
Expand Down