Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
9 changes: 9 additions & 0 deletions apps/customer-portal/backend/modules/entity/entity.bal
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,15 @@ public isolated function getProjectTimeCardStats(string idToken, string projectI
return csEntityClient->/projects/[projectId]/time\-cards/stats.get(generateHeaders(idToken));
}

# Get change request by ID.
#
# + idToken - ID token for authorization
# + changeRequestId - Unique ID of the change request to be retrieved
# + return - Change request response containing details of the retrieved change request or error
public isolated function getChangeRequestDetails(string idToken, string changeRequestId) returns ChangeRequestResponse|error {
return csEntityClient->/change\-requests/[changeRequestId].get(generateHeaders(idToken));
}

# Search change requests of a project.
#
# + idToken - ID token for authorization
Expand Down
36 changes: 33 additions & 3 deletions apps/customer-portal/backend/modules/entity/types.bal
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ public type Case record {|
# Choice list item information.
public type ChoiceListItem record {|
# Choice list item value
int id;
int|string id;
Comment thread
Rashmika998 marked this conversation as resolved.
# Choice list item label
string label;
# Count
Expand Down Expand Up @@ -688,6 +688,8 @@ public type DeployedProductUpdatePayload record {|
int? cores?;
# TPS allocated for the product
decimal? tps?;
# Active status (can only be set to false to deactivate deployed product)
boolean active?;
|};

# Response from updating a deployed product.
Expand Down Expand Up @@ -1411,10 +1413,12 @@ public type ChangeRequest record {|
ReferenceTableItem? deployment;
# Deployed product information
ReferenceTableItem? deployedProduct;
# Product information
ReferenceTableItem? product;
# Planned start date and time
string? startDate;
Date? plannedStartOn;
# Planned end date and time
string? endDate;
Date? plannedEndOn;
# Duration
string? duration;
# Indicates if the change request has a service outage
Expand Down Expand Up @@ -1487,3 +1491,29 @@ public type CatalogItemVariablesResponse record {|
# List of catalog item variables
CatalogItemVariable[] variables;
|};

# Change request details information.
public type ChangeRequestResponse record {|
*ChangeRequest;
# Change request description
string? description;
# User who created the change request
string createdBy;
# Justification for the change request
string? justification;
# Impact description
string? impactDescription;
# Service outage details
string? serviceOutage;
# Communication plan
string? communicationPlan;
# Rollback plan
string? rollbackPlan;
# Test plan
string? testPlan;
# Indicates if the customer has approved
boolean hasCustomerApproved;
# Indicates if the customer has reviewed
boolean hasCustomerReviewed;
json...;
|};
21 changes: 21 additions & 0 deletions apps/customer-portal/backend/modules/entity/utils.bal
Original file line number Diff line number Diff line change
Expand Up @@ -225,3 +225,24 @@ public isolated function validateCaseCreatePayload(CaseCreatePayload payload) re
}
return;
}

# Validate deployed product update payload.
#
# + payload - Deployed product update payload
# + return - Error message if validation fails, () otherwise
public isolated function validateDeployedProductUpdatePayload(DeployedProductUpdatePayload payload) returns string? {
boolean? active = payload.active;
int? cores = payload?.cores;
decimal? tps = payload?.tps;
if active is boolean {
if active {
return "Invalid value for active field. When updating cores or tps, active field should be set to false.";
}
if cores !is () || tps !is () {
return "When deactivating, cores and tps fields should not be provided.";
}
} else if cores is () && tps is () {
return "At least one of cores or tps should be provided when updating deployed product details.";
}
return;
}
33 changes: 31 additions & 2 deletions apps/customer-portal/backend/modules/types/types.bal
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ public type CaseSearchFilters record {|
int severityId?;
# Deployment ID
string deploymentId?;
# Case created by the logged in user
boolean createdByMe?;
|};

# Payload for case search.
Expand Down Expand Up @@ -1052,10 +1054,12 @@ public type ChangeRequest record {|
ReferenceItem? deployment;
# Deployed product information
ReferenceItem? deployedProduct;
# Product information
ReferenceItem? product;
# Planned start date and time
string? startDate;
entity:Date? startDate;
# Planned end date and time
string? endDate;
entity:Date? endDate;
# Duration
string? duration;
# Indicates if the change request has a service outage
Expand Down Expand Up @@ -1122,3 +1126,28 @@ public type CatalogSearchPayload record {|
# Pagination details
entity:Pagination pagination?;
|};

# Change request details information.
public type ChangeRequestResponse record {|
*ChangeRequest;
# Change request description
string? description;
# User who created the change request
string createdBy;
# Justification for the change request
string? justification;
# Impact description
string? impactDescription;
# Service outage details
string? serviceOutage;
# Communication plan
string? communicationPlan;
# Rollback plan
string? rollbackPlan;
# Test plan
string? testPlan;
# Indicates if the customer has approved
boolean hasCustomerApproved;
# Indicates if the customer has reviewed
boolean hasCustomerReviewed;
|};
75 changes: 69 additions & 6 deletions apps/customer-portal/backend/service.bal
Original file line number Diff line number Diff line change
Expand Up @@ -718,10 +718,10 @@ service http:InterceptableService / on new http:Listener(9090, listenerConf) {
};
}

types:OverallConversationStats { openCount, resolvedCount, activeCount, abandonedCount} =
types:OverallConversationStats {openCount, resolvedCount, activeCount, abandonedCount} =
getConversationStats(conversationStats);

return { openCount, resolvedCount, activeCount, abandonedCount };
return {openCount, resolvedCount, activeCount, abandonedCount};
}

# Get project support statistics by ID.
Expand Down Expand Up @@ -1824,6 +1824,15 @@ service http:InterceptableService / on new http:Listener(9090, listenerConf) {
};
}

string? validateDeployedProductUpdatePayload = entity:validateDeployedProductUpdatePayload(payload);
if validateDeployedProductUpdatePayload is string {
return <http:BadRequest>{
body: {
message: validateDeployedProductUpdatePayload
}
};
}

entity:DeployedProductUpdateResponse|error response =
entity:updateDeployedProduct(userInfo.idToken, productId, payload);
if response is error {
Expand Down Expand Up @@ -1869,7 +1878,7 @@ service http:InterceptableService / on new http:Listener(9090, listenerConf) {
}

# Search catalogs for a specific deployed product with filters and pagination.
#
#
# + id - ID of the deployed product
# + payload - Catalog search request body
# + return - Paginated catalogs or error
Expand All @@ -1894,7 +1903,7 @@ service http:InterceptableService / on new http:Listener(9090, listenerConf) {
if searchResponse is error {
if getStatusCode(searchResponse) == http:STATUS_FORBIDDEN {
log:printWarn(string `User: ${
userInfo.userId} is forbidden to search catalogs for deployed product with ID: ${id}!`);
userInfo.userId} is forbidden to search catalogs for deployed product with ID: ${id}!`);
return <http:Forbidden>{
body: {
message: "You're not authorized to search catalogs for the deployed product. " +
Expand Down Expand Up @@ -1922,11 +1931,11 @@ service http:InterceptableService / on new http:Listener(9090, listenerConf) {
}

# Get catalog item variables by catalog ID and item ID.
#
#
# + catalogId - ID of the catalog
# + itemId - ID of the catalog item
# + return - Catalog item details or error
resource function get catalogs/[entity:IdString catalogId]/items/[entity:IdString itemId](http:RequestContext ctx)
resource function get catalogs/[entity:IdString catalogId]/items/[entity:IdString itemId](http:RequestContext ctx)
returns http:Ok|http:Unauthorized|http:Forbidden|http:InternalServerError {

authorization:UserInfoPayload|error userInfo = ctx.getWithType(authorization:HEADER_USER_INFO);
Expand Down Expand Up @@ -3074,4 +3083,58 @@ service http:InterceptableService / on new http:Listener(9090, listenerConf) {
body: mapChangeRequestSearchResponse(response)
};
}

# Get change request details by change request ID.
#
# + id - ID of the change request
# + return - Change request details or an error
resource function get change\-requests/[entity:IdString id](http:RequestContext ctx)
returns types:ChangeRequestResponse|http:Unauthorized|http:Forbidden|http:NotFound|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
}
};
}

entity:ChangeRequestResponse|error response = entity:getChangeRequestDetails(userInfo.idToken, id);
if response is error {
if getStatusCode(response) == http:STATUS_UNAUTHORIZED {
log:printWarn(string `User: ${
userInfo.userId} is not authorized to access change request information!`);
return <http:Unauthorized>{
body: {
message: ERR_MSG_UNAUTHORIZED_ACCESS
}
};
}
if getStatusCode(response) == http:STATUS_FORBIDDEN {
log:printWarn(string `Access to change request information is forbidden for user: ${userInfo.userId}`);
return <http:Forbidden>{
body: {
message: "Access to change request information is forbidden for the user!"
}
};
}
if getStatusCode(response) == http:STATUS_NOT_FOUND {
return <http:NotFound>{
body: {
message: "The requested change request is not found!"
}
};
}

string customError = "Failed to retrieve change request details.";
log:printError(customError, response);
return <http:InternalServerError>{
body: {
message: customError
}
};
}
return mapChangeRequestResponse(response);
}
}
61 changes: 57 additions & 4 deletions apps/customer-portal/backend/utils.bal
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ public isolated function searchCases(string idToken, string projectId, types:Cas
severityKey: payload.filters?.severityId,
caseTypes: payload.filters?.caseTypes,
stateKeys: payload.filters?.statusIds,
deploymentId: payload.filters?.deploymentId
deploymentId: payload.filters?.deploymentId,
createdByMe: payload.filters?.createdByMe
},
pagination: payload.pagination,
sortBy: payload.sortBy
Expand Down Expand Up @@ -585,7 +586,8 @@ public isolated function mapConversationResponse(entity:ConversationResponse res
public isolated function getOngoingCasesCount(entity:ProjectCaseStatsResponse|error response) returns int? {
if response is entity:ProjectCaseStatsResponse {
types:ReferenceItem[] stateCount = from entity:ChoiceListItem item in response.stateCount
where item.id != entity:caseStateIds.closed && item.id != entity:caseStateIds.solutionProposed
where item.id.toString() != entity:caseStateIds.closed.toString()
&& item.id.toString() != entity:caseStateIds.solutionProposed.toString()
select {id: item.id.toString(), label: item.label, count: item.count};
if stateCount.length() > 0 {
int ongoingCasesCount = 0;
Expand Down Expand Up @@ -653,15 +655,16 @@ public isolated function mapChangeRequestSearchResponse(entity:ChangeRequestSear
let entity:ReferenceTableItem? case = changeRequest.case
let entity:ReferenceTableItem? deployment = changeRequest.deployment
let entity:ReferenceTableItem? deployedProduct = changeRequest.deployedProduct
let entity:ReferenceTableItem? product = changeRequest.product
let entity:ChoiceListItem? state = changeRequest.state
let entity:ChoiceListItem? impact = changeRequest.impact
let entity:ChoiceListItem? 'type = changeRequest.'type
select {
id: changeRequest.id,
number: changeRequest.number,
title: changeRequest.title,
startDate: changeRequest.startDate,
endDate: changeRequest.endDate,
startDate: changeRequest.plannedStartOn,
endDate: changeRequest.plannedEndOn,
duration: changeRequest.duration,
hasServiceOutage: changeRequest.hasServiceOutage,
createdOn: changeRequest.createdOn,
Expand All @@ -671,6 +674,8 @@ public isolated function mapChangeRequestSearchResponse(entity:ChangeRequestSear
deployment: deployment != () ? {id: deployment.id, label: deployment.name, number: deployment?.number} : (),
deployedProduct: deployedProduct != () ?
{id: deployedProduct.id, label: deployedProduct.name, number: deployedProduct?.number} : (),
product: product != () ?
{id: product.id, label: product.name, number: product?.number} : (),
state: state != () ? {id: state.id.toString(), label: state.label} : (),
impact: impact != () ? {id: impact.id.toString(), label: impact.label} : (),
'type: 'type != () ? {id: 'type.id.toString(), label: 'type.label} : ()
Expand Down Expand Up @@ -702,3 +707,51 @@ public isolated function mapCatalogSearchResponse(entity:CatalogSearchResponse r

return {catalogs, totalRecords: response.totalRecords, 'limit: response.'limit, offset: response.offset};
}

# Map change request response to the desired structure.
#
# + response - Change request response from the entity service
# + return - Mapped change request response
public isolated function mapChangeRequestResponse(entity:ChangeRequestResponse response)
returns types:ChangeRequestResponse {

entity:ReferenceTableItem? project = response.project;
entity:ReferenceTableItem? case = response.case;
entity:ReferenceTableItem? deployment = response.deployment;
entity:ReferenceTableItem? deployedProduct = response.deployedProduct;
entity:ReferenceTableItem? product = response.product;
entity:ChoiceListItem? state = response.state;
entity:ChoiceListItem? impact = response.impact;
entity:ChoiceListItem? 'type = response.'type;
return {
id: response.id,
number: response.number,
title: response.title,
startDate: response.plannedStartOn,
endDate: response.plannedEndOn,
duration: response.duration,
hasServiceOutage: response.hasServiceOutage,
createdOn: response.createdOn,
updatedOn: response.updatedOn,
project: project != () ? {id: project.id, label: project.name, number: project?.number} : (),
case: case != () ? {id: case.id, label: case.name, number: case?.number} : (),
deployment: deployment != () ? {id: deployment.id, label: deployment.name, number: deployment?.number} : (),
deployedProduct: deployedProduct != () ?
{id: deployedProduct.id, label: deployedProduct.name, number: deployedProduct?.number} : (),
product: product != () ?
{id: product.id, label: product.name, number: product?.number} : (),
state: state != () ? {id: state.id.toString(), label: state.label} : (),
impact: impact != () ? {id: impact.id.toString(), label: impact.label} : (),
'type: 'type != () ? {id: 'type.id.toString(), label: 'type.label} : (),
description: response.description,
createdBy: response.createdBy,
justification: response.justification,
impactDescription: response.impactDescription,
serviceOutage: response.serviceOutage,
communicationPlan: response.communicationPlan,
rollbackPlan: response.rollbackPlan,
testPlan: response.testPlan,
hasCustomerApproved: response.hasCustomerApproved,
hasCustomerReviewed: response.hasCustomerReviewed
};
}