Skip to content

Commit

Permalink
KAR-548 - NEW V2 API for getUserProgress with Pagination (#544)
Browse files Browse the repository at this point in the history
* NEW V2 API for getUserProgress with Pagination

* changes after testing

* review chnages
  • Loading branch information
sreeragksgh authored May 1, 2024
1 parent 36d95f0 commit b868f91
Show file tree
Hide file tree
Showing 7 changed files with 302 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -115,4 +115,9 @@ public List<Map<String, Object>> getKarmaPointsRecordsByPropertiesWithPagination
Map<String, Object> propertyMap, List<String> fields, int limit, Date updatedOn, String key,Date limitDate);

public Long getRecordCountWithUserId(String keyspace, String table, String userId,Date limitDate);
}

public Map<String,Object> getRecordByIdentifierWithPage(String keyspaceName, String tableName, Map<String,Object> key, List<String> fields, String pageString, int limit);

public Long getCountOfRecordByIdentifier(String keyspaceName, String tableName, Map<String,Object> key, String field);

}
Original file line number Diff line number Diff line change
Expand Up @@ -449,5 +449,103 @@ public Long getRecordCountWithUserId(String keyspace, String tableName, String u
}
}

@Override
public Map<String,Object> getRecordByIdentifierWithPage(String keyspaceName, String tableName,
Map<String,Object> key, List<String> fields, String pageString, int limit) {
long startTime = System.currentTimeMillis();
Map<String,Object> response = new HashMap<>();
try {
Session session = connectionManager.getSession(keyspaceName);
Builder selectBuilder;
if (CollectionUtils.isNotEmpty(fields)) {
selectBuilder = QueryBuilder.select(fields.toArray(new String[fields.size()]));
} else {
selectBuilder = QueryBuilder.select().all();
}
Select selectQuery = selectBuilder.from(keyspaceName, tableName);
if (MapUtils.isNotEmpty(key)) {
Where selectWhere = selectQuery.where();
for (Entry<String, Object> entry : key.entrySet()) {
if (entry.getValue() instanceof List) {
List<Object> list = (List) entry.getValue();
if (null != list) {
Object[] propertyValues = list.toArray(new Object[list.size()]);
Clause clause = QueryBuilder.in(entry.getKey(), propertyValues);
selectWhere.and(clause);
}
} else {
Clause clause = QueryBuilder.eq(entry.getKey(), entry.getValue());
selectWhere.and(clause);
}
}
}
if (StringUtils.isNotBlank(pageString)) {
selectQuery.setPagingState(PagingState.fromString(pageString));
}
selectQuery.setFetchSize(limit);
ResultSet results = session.execute(selectQuery);
List<Map<String, Object>> responseList = new ArrayList<>();
Map<String, String> columnsMapping = CassandraUtil.fetchColumnsMapping(results);
int remaining = results.getAvailableWithoutFetching();
Iterator<Row> rowIterator = results.iterator();
while(rowIterator.hasNext()) {
Row row = rowIterator.next();
Map<String, Object> rowMap = new HashMap<>();
columnsMapping.entrySet().stream()
.forEach(entry -> rowMap.put(entry.getKey(), row.getObject(entry.getValue())));
responseList.add(rowMap);
remaining--;
if (remaining == 0 || responseList.size() >= limit) {
break;
}
}
response.put(Constants.RESPONSE, responseList);
if (results.getExecutionInfo().getPagingState() != null) {
response.put(Constants.PAGE_ID, results.getExecutionInfo().getPagingState().toString());
}
} catch (Exception e) {
logger.error(Constants.EXCEPTION_MSG_FETCH + tableName + " : " + e.getMessage(), e);
}
return response;
}

@Override
public Long getCountOfRecordByIdentifier(String keyspaceName, String tableName, Map<String,Object> key, String field) {
long startTime = System.currentTimeMillis();
List<Map<String,Object>> response = new ArrayList<>();
Long count = 0L;
try {
if (MapUtils.isEmpty(key)) {
throw new IllegalArgumentException("Key parameter cannot be null");
}
Session session = connectionManager.getSession(keyspaceName);
Builder selectBuilder;
selectBuilder = QueryBuilder.select().count(field);
Select selectQuery = selectBuilder.from(keyspaceName, tableName);
if (MapUtils.isNotEmpty(key)) {
Where selectWhere = selectQuery.where();
for (Entry<String, Object> entry : key.entrySet()) {
if (entry.getValue() instanceof List) {
List<Object> list = (List) entry.getValue();
if (null != list) {
Object[] propertyValues = list.toArray(new Object[list.size()]);
Clause clause = QueryBuilder.in(entry.getKey(), propertyValues);
selectWhere.and(clause);
}
} else {
Clause clause = QueryBuilder.eq(entry.getKey(), entry.getValue());
selectWhere.and(clause);
}
}
}
ResultSet results = session.execute(selectQuery);
response = CassandraUtil.createResponse(results);
count = ((Long)((Map<String,Object>)response.get(0)).get("system.count(" + field.toLowerCase() + ")"));
} catch (Exception e) {
logger.error(Constants.EXCEPTION_MSG_FETCH + tableName + " : " + e.getMessage(), e);

}
return count;
}
}

5 changes: 4 additions & 1 deletion src/main/java/org/sunbird/common/util/Constants.java
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ public class Constants {
public static final String API_GET_EXPLORE_COURSE_DETAIL = "api.explore.course";
public static final String API_REFRESH_EXPLORE_COURSE_DETAIL = "api.refresh.explore.course.list";
public static final String API_GET_MASTER_DATA = "api.get.master.data";
public static final String API_GET_USER_PROGRESS = "api.get.user.progress";

public static final String ORG_PROFILE_UPDATE = "org.profile.update";
public static final String ID = "id";
Expand Down Expand Up @@ -1024,7 +1025,9 @@ public class Constants {
public static final String PARENTS = "parents";
public static final String SUB_SECTORS = "subsectors";
public static final String COURSE_LINK = "courseLink";

public static final String PAGE_ID = "pageId";
public static final String USERS_LIST = "userList";
public static final String TOTAL_COUNT = "totalCount";
private Constants() {
throw new IllegalStateException("Utility class");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RestController;
import org.sunbird.common.model.SBApiResponse;
import org.sunbird.common.model.SunbirdApiRequest;
import org.sunbird.common.util.Constants;
import org.sunbird.progress.model.MandatoryContentResponse;
Expand Down Expand Up @@ -43,4 +44,12 @@ public ResponseEntity<Map<String, Object>> getUserProgress(@RequestBody SunbirdA
return new ResponseEntity<>(service.getUserProgress(requestBody, authUserToken, rootOrgId ,userChannel), HttpStatus.OK);
}

@PostMapping("/v2/progress/getUserProgress")
public ResponseEntity<SBApiResponse> getUserProgressV2(@RequestBody Map<String,Object> requestBody,
@RequestHeader(Constants.USER_TOKEN) String authUserToken,
@RequestHeader(Constants.X_AUTH_USER_ORG_ID) String rootOrgId,
@RequestHeader(Constants.X_AUTH_USER_CHANNEL) String userChannel) {
SBApiResponse response =service.getUserProgressV2(requestBody, authUserToken, rootOrgId, userChannel);
return new ResponseEntity<>(response, response.getResponseCode());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import java.util.Map;

import org.sunbird.common.model.SBApiResponse;
import org.sunbird.common.model.SunbirdApiRequest;
import org.sunbird.progress.model.MandatoryContentResponse;

Expand All @@ -11,4 +12,6 @@ public MandatoryContentResponse getMandatoryContentStatusForUser(String authUser
String userId);

public Map<String, Object> getUserProgress(SunbirdApiRequest requestBody, String authUserToken, String rootOrgId, String userChannel);

public SBApiResponse getUserProgressV2(Map<String, Object> requestBody, String authUserToken, String rootOrgId, String userChannel);
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,14 @@
import java.util.Optional;
import java.util.stream.Collectors;

import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.sunbird.cassandra.utils.CassandraOperation;
import org.sunbird.common.model.SearchUserApiContent;
import org.sunbird.common.model.SunbirdApiRequest;
import org.sunbird.common.model.SunbirdApiResp;
import org.sunbird.common.model.SunbirdUserProfileDetail;
import org.sunbird.common.model.*;
import org.sunbird.common.service.ContentServiceImpl;
import org.sunbird.common.service.OutboundRequestHandlerServiceImpl;
import org.sunbird.common.util.CbExtServerProperties;
Expand Down Expand Up @@ -218,6 +217,81 @@ public Map<String, Object> getUserProgress(SunbirdApiRequest requestBody, String
return result;
}

public SBApiResponse getUserProgressV2(Map<String, Object> requestBody, String authUserToken, String rootOrgId, String userChannel) {
SBApiResponse response = new SBApiResponse(Constants.API_GET_USER_PROGRESS);
Map<String,Object> reqMap = new HashMap<>();
Map<String,Object>participantsDetails = new HashMap<>();
String errMsg = "";
try {
errMsg = validateRequest(requestBody);
if (StringUtils.isNotBlank(errMsg)) {
response.setResponseCode(HttpStatus.BAD_REQUEST);
response.getParams().setStatus(Constants.FAILED);
response.getParams().setErrmsg(errMsg);
return response;
}
Map<String, Object> request = (Map<String, Object>) requestBody.get(Constants.REQUEST);
int courseLeafCount = getLeafCountForTheCourse(rootOrgId, (String) request.get(Constants.COURSE_ID), userChannel);
// get all enrolled details
List<String> enrollmentIdList = null;
List<Map<String, Object>> userEnrolmentList = new ArrayList<>();
Map<String, Object> propertyMap = new HashMap<>();
propertyMap.put(Constants.BATCH_ID, request.get(Constants.BATCH_ID));
reqMap.put(Constants.BATCH_ID,request.get(Constants.BATCH_ID));
reqMap.put(Constants.COURSE_ID,request.get(Constants.COURSE_ID));
reqMap.put(Constants.LIMIT,request.get(Constants.LIMIT));
reqMap.put(Constants.OFFSET,request.get(Constants.OFFSET));
participantsDetails = getBatchParticipantsByPage(reqMap);
enrollmentIdList = (List<String>) participantsDetails.get(Constants.USERS_LIST);
propertyMap.put(Constants.USER_ID, enrollmentIdList);
propertyMap.put(Constants.COURSE_ID, request.get(Constants.COURSE_ID));
userEnrolmentList.addAll(cassandraOperation.getRecordsByPropertiesWithoutFiltering(Constants.KEYSPACE_SUNBIRD_COURSES,
Constants.TABLE_USER_ENROLMENT, propertyMap,
Arrays.asList(Constants.USER_ID_CONSTANT, Constants.COURSE_ID,
Constants.BATCH_ID, Constants.COMPLETION_PERCENTAGE, Constants.PROGRESS,
Constants.STATUS, Constants.ISSUED_CERTIFICATES), cbExtServerProperties.getBatchEnrolmentReturnSize()));
// restricting with only 100 items in the response
if ( (Integer) request.get(Constants.LIMIT) > cbExtServerProperties.getBatchEnrolmentReturnSize()) {
response.setResponseCode(HttpStatus.BAD_REQUEST);
response.getParams().setStatus(Constants.FAILED);
response.getParams().setErrmsg(" Given Limit is greater than expected value, Limit should be less than "+cbExtServerProperties.getBatchEnrolmentReturnSize());
return response;
}

//get id list from userEnrollmentList, in case request has more than one batch
List<String> enrolledUserIdList = userEnrolmentList.stream()
.map(obj -> (String) obj.get(Constants.USER_ID_CONSTANT)).collect(Collectors.toList());

//inside loop iterating batch list
List<String> userFields = Arrays.asList(Constants.USER_ID_CONSTANT, Constants.FIRSTNAME, Constants.PROFILE_DETAILS_PRIMARY_EMAIL, Constants.CHANNEL,
Constants.PROFILE_DETAILS_DESIGNATION, Constants.PROFILE_DETAILS_DESIGNATION_OTHER);
Map<String, Object> userMap = userUtilService.getUsersDataFromUserIds(enrolledUserIdList, userFields,
authUserToken);


for (Map<String, Object> responseObj : userEnrolmentList) {
// set user details
if (userMap.containsKey(responseObj.get(Constants.USER_ID_CONSTANT))) {
SearchUserApiContent userObj = mapper.convertValue(
userMap.get(responseObj.get(Constants.USER_ID_CONSTANT)), SearchUserApiContent.class);
appendUserDetails(responseObj, userObj);
}
// set completion percentage & status
setCourseCompletiondetails(responseObj, courseLeafCount);
}
response.getParams().setStatus(Constants.SUCCESS);
response.setResponseCode(HttpStatus.OK);
response.getResult().put(Constants.TOTAL_COUNT,participantsDetails.get(Constants.COUNT));
response.getResult().put(Constants.PROGRESS, userEnrolmentList);
} catch (Exception ex) {
response.setResponseCode(HttpStatus.BAD_REQUEST);
response.getParams().setStatus(Constants.FAILED);
logger.error(ex);
}
return response;
}


private UserProgressRequest validateGetBatchEnrolment(SunbirdApiRequest requestBody) {
try {
UserProgressRequest userProgressRequest = new UserProgressRequest();
Expand Down Expand Up @@ -312,4 +386,105 @@ private int getLeafCountForTheCourse(String rootOrgId, String courseId, String u
}
return leafCountForTheCOurse;
}

private String validateRequest(Map<String, Object> request) {
StringBuilder strBuilder = new StringBuilder();
Map<String, Object> requestBody = (Map<String, Object>) request.get(Constants.REQUEST);
if (ObjectUtils.isEmpty(requestBody)) {
strBuilder.append("Invalid Request Body.");
return strBuilder.toString();
}

List<String> missingAttrib = new ArrayList<String>();
if (!requestBody.containsKey(Constants.COURSE_ID)) {
missingAttrib.add(Constants.COURSE_ID);
}

if (!requestBody.containsKey(Constants.BATCH_ID)) {
missingAttrib.add(Constants.BATCH_ID);
}

if (!requestBody.containsKey(Constants.LIMIT)) {
missingAttrib.add(Constants.LIMIT);
}

if (!requestBody.containsKey(Constants.OFFSET)) {
missingAttrib.add(Constants.OFFSET);
}

if (missingAttrib.size() > 0) {
strBuilder.append("The following parameter(s) are missing. Missing params - [")
.append(missingAttrib.toString()).append("]");
}

return strBuilder.toString();
}

public Map<String,Object> getBatchParticipantsByPage(Map<String, Object> request) {
Map<String,Object> responseMap = new HashMap<>();
Map<String, Object> queryMap = new HashMap<>();
queryMap.put(Constants.BATCH_ID, (String) request.get(Constants.BATCH_ID));
Map<String, Object> result = new HashMap<String, Object>();
List<String> userList = new ArrayList<String>();
Boolean active = (Boolean) request.get(Constants.ACTIVE);
if (null == active) {
active = true;
}
Integer limit = (Integer) request.get(Constants.LIMIT);
Integer currentOffSetFromRequest = (Integer) request.get(Constants.OFFSET);
String pageId = (String) request.get(Constants.PAGE_ID);
String previousPageId = null;
int currentOffSet = 0;
String currentPagingState = null;
Long count = cassandraOperation.getCountOfRecordByIdentifier(Constants.KEYSPACE_SUNBIRD_COURSES,
Constants.TABLE_ENROLLMENT_BATCH_LOOKUP, queryMap, Constants.USER_ID);
do {
Map<String,Object> response = cassandraOperation.getRecordByIdentifierWithPage(Constants.KEYSPACE_SUNBIRD_COURSES,
Constants.TABLE_ENROLLMENT_BATCH_LOOKUP, queryMap,
null, pageId, (Integer) request.get(Constants.LIMIT));
currentPagingState = (String) response.get(Constants.PAGE_ID);
if (org.codehaus.plexus.util.StringUtils.isBlank(previousPageId) && org.codehaus.plexus.util.StringUtils.isNotBlank(currentPagingState)) {
previousPageId = currentPagingState;
}
pageId = currentPagingState;
List<Map<String, Object>> userCoursesList = (List<Map<String, Object>>) response.get(Constants.RESPONSE);
if (org.apache.commons.collections.CollectionUtils.isEmpty(userCoursesList)) {
//Set null so that client knows there are no data to read further.
previousPageId = null;
break;
}
for (Map<String, Object> userCourse : userCoursesList) {
//From this page, we have already read some records, so skip the records
if (currentOffSetFromRequest > 0) {
currentOffSetFromRequest--;
continue;
}
if (userCourse.get(Constants.ACTIVE) != null
&& (active == (boolean) userCourse.get(Constants.ACTIVE))) {
userList.add((String) userCourse.get(Constants.USER_ID));
if (userList.size() == limit) {
//We have read the data... if pageId available send back in response.
previousPageId = pageId != null ? pageId : previousPageId;
break;
}
} else {
logger.info("No active enrolment for user : "+userCourse.get(Constants.USER_ID).toString()+" course : "+request.get(Constants.COURSE_ID).toString()+" batch : "+request.get(Constants.BATCH_ID).toString());
}
currentOffSet++;
}
//We may have read the given limit... if so, break from while loop
if (userList.size() == limit) {
break;
}
} while (org.codehaus.plexus.util.StringUtils.isNotBlank(currentPagingState));
if (org.codehaus.plexus.util.StringUtils.isNotBlank(previousPageId)) {
result.put(Constants.PAGE_ID, previousPageId);
if (currentOffSet >= limit) {
currentOffSet = currentOffSet - limit;
}
}
responseMap.put(Constants.COUNT,count);
responseMap.put(Constants.USERS_LIST,userList);
return responseMap;
}
}
Loading

0 comments on commit b868f91

Please sign in to comment.