Incorrect search results in Graph Java SDK Search API for Person Entity

Pratap Meka 5 Reputation points
2023-09-13T13:31:48.52+00:00

Hi Team,

We are using the Microsoft Graph Java SDK to search for people in my organization. I was able to invoke Search API using the Graph explorer and get the results. As part of the API, we do have sorting, pagination parameters. I have observed that the search results are not consistent when we are invoking with different size and even the pagination results are not accurate. We are using the latest SDK jar (5.70) available on maven repository. Wanted to check whether there are any issues with the results with size parameter and as well with pagination parameters.

Below 3 issues observed:

  1. Sorting When we are trying to sort the results on the 'givenName' with descending set to true/false, getting same order of the results.
  2. Query results with varying size parameter (size). We had a query string within our organization which resulted into 47 records. We observed below when executed by varying the size parameter.
  1. When we didn't specify size parameter as part of the request, we got total as 25, which is default per page.
  2. When we specify size parameter with value of 60, then we are getting total as 47, which matches to exact total records.
  3. When we specify size parameter with value of 50, then we are getting total as 39, where I guess it should still be 47 as size is greater than total records.
  4. When we again specify size parameter with value of 40, then we are getting total as 36, where I guess it should still be 40 as size is less than total records. In all of the above cases, we are getting "moreResultsAvailable" as false always, even if we specify the size as 10. Here we expect it to be true as we still have 37 records for the search criteria above. We are trying to derive the pagination results existence based on this flag, but its always coming as false.
  1. Query results with pagination parameter (from). We had a query string within our organization which resulted into 47 records. We observed below when executed by varying the from parameter.
    1. when we didn't specify from parameter as part of the request with size 10, we got total as 10, which is expected as size is 10.
    2. In continuation of the above results, when we specify from parameter with value 9 and size with 10, we got total as 7, which I guess it should be 10 as we still have records.

So can you please confirm if we have any issues for the above workflows? I am attaching the sample java test programs implemented using GraphServiceClient (API based) and Java Http client (REST based).

And one more question, does the MSGraph Search API support Guest users search with the delegated user permission, if so can you please provide the details?

package com;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import com.azure.identity.AuthorizationCodeCredential;
import com.azure.identity.AuthorizationCodeCredentialBuilder;
import com.microsoft.graph.authentication.TokenCredentialAuthProvider;
import com.microsoft.graph.models.EntityType;
import com.microsoft.graph.models.Person;
import com.microsoft.graph.models.SearchEntityQueryParameterSet;
import com.microsoft.graph.models.SearchHit;
import com.microsoft.graph.models.SearchHitsContainer;
import com.microsoft.graph.models.SearchQuery;
import com.microsoft.graph.models.SearchRequest;
import com.microsoft.graph.models.SearchResponse;
import com.microsoft.graph.requests.GraphServiceClient;
import com.microsoft.graph.requests.SearchEntityQueryCollectionPage;

public class TestMSGraphServiceClient {  	
public static void main(String[] args) throws Exception { 		 		final String clientId = "xxxxxxxx"; 		
final String tenantId = "xxxxxxxx";  		
final String clientSecret = "xxxxxxxx"; 		
final String authorizationCode = "xxxxxxxx"; 		
final String redirectUrl = "http://localhost:8081/home/system/app/start/web/oauth/"; 		
final List<String> scopes = Arrays.asList("openid email offline_access user.read mail.send mail.read mail.readwrite people.read"); 

final AuthorizationCodeCredential credential = new AuthorizationCodeCredentialBuilder() 		    .clientId(clientId).tenantId(tenantId).clientSecret(clientSecret) 		    .authorizationCode(authorizationCode).redirectUrl(redirectUrl).build();

if (null == scopes || null == credential) { 
		    throw new Exception("Unexpected error"); 		
} 		
final TokenCredentialAuthProvider authProvider = new TokenCredentialAuthProvider( scopes, credential); 		 		GraphServiceClient graphClient = GraphServiceClient.builder().authenticationProvider( authProvider ).buildClient(); 

LinkedList<SearchRequest> requestsList = new LinkedList<SearchRequest>(); 		SearchRequest requests = new SearchRequest(); 		
LinkedList<EntityType> entityTypesList = new LinkedList<EntityType>(); 		entityTypesList.add(EntityType.PERSON); 		
requests.entityTypes = entityTypesList; 		
SearchQuery query = new SearchQuery(); 		
query.queryString = "lakshmi"; 		
requests.query = query; 		
requests.from = 0; 		
requests.size = 60; 
requestsList.add(requests); 		System.out.println("RequestURL:"+graphClient.search().getRequestUrl()); 		SearchEntityQueryCollectionPage entityQueryCollectionPage = graphClient.search().query(SearchEntityQueryParameterSet 				.newBuilder().withRequests(requestsList).build()).buildRequest() 			.post(); 		 		
prepareResponse(entityQueryCollectionPage); 	
}

public static void prepareResponse(final SearchEntityQueryCollectionPage usersSearchCollection) {  		
// Add users 		List<SearchResponse> users = usersSearchCollection.getCurrentPage();  		
// Add 'Count' 		int usersCount = 0; 		
if (!users.isEmpty()) { 			
usersCount = users.get(0).hitsContainers.get(0).total; 		
} 		
List <String> selectProperties = new ArrayList <> (); 		selectProperties.add("displayName"); 		selectProperties.add("userPrincipalName"); 		
List <Map <String, String>> lsUsers = new ArrayList <> (); 		
if (usersCount > 0) { 			// Iterate over the users list. 			users.forEach(searchResponse -> { 				 				List<SearchHitsContainer> searchHitsContainer = searchResponse.hitsContainers; 				searchHitsContainer.forEach(searchHitContainerObj -> { 					List<SearchHit> searchHits = searchHitContainerObj.hits; 					searchHits.forEach(searchHit -> { 						
Map <String, String> usersDetails = new HashMap <String, String> (); 						                    Person personUser = (Person) searchHit.resource; 						selectProperties.forEach(fieldName -> { 							
 String propertyValue = (String) getUserPropertyValue(personUser, fieldName);  							                                        usersDetails.put(fieldName, propertyValue); 						
                    }); 						
                    lsUsers.add(usersDetails); 					
               });  				
          }); 			
     }); 			 			
System.out.println("\n >>> Users Response:"+lsUsers);  		
    } 	
}

public static Object getUserPropertyValue(Person user, String fieldName) {         Class<? extends Person> userClass = Person.class;        
 Object value = null;        
 try {            
 Field field = userClass.getField(fieldName);     
 value = field.get(user);        
 } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException exp) {             exp.printStackTrace();         
}         
return value;    
 }      
}
package com;  import java.io.IOException; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpRequest.BodyPublisher; import java.net.http.HttpResponse; import com.fasterxml.jackson.databind.ObjectMapper;  public class TestSearchAPIWithHttpClient { 	private static String USERS_SEARCH_URL = "https://graph.microsoft.com/v1.0/search/query"; 	private static String requestJson = "{" + "    requests: [" + "        {" + "            entityTypes: ['person'" 			+ "            ]," + "            query: {queryString: 'lakshmi'" + "            }," 			+ "            sortProperties: [" + "                {" + "                    \"name\": \"givenName\"," 			+ "                    \"isDescending\": false" + "                }" + "            ]" 			+ "				,size: 10" + "				,from: 9" 			+ "			,fields: ['displayName','userPrincipalName','givenName']" + "        }" + "    ]" + "}"; 	private static String accessToken = "xxxxxxxxxx";  	public static void main(String args[]) { 		BodyPublisher reqPublisher = HttpRequest.BodyPublishers.ofString(requestJson); 		String authHeader = "Bearer " + accessToken; 		HttpRequest request = buildPostRequest(USERS_SEARCH_URL, reqPublisher, authHeader, "application/json", 				"application/json"); 		HttpClient httpClient = HttpClient.newBuilder().build(); 		try { 			HttpResponse<String> httpResponse = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); 			String responseValue = new ObjectMapper().readTree(httpResponse.body()).toPrettyString(); 			System.out.println("Response Status : \n" + responseValue); 		} catch (IOException | InterruptedException e) { 			e.printStackTrace(); 		} 	}  	public static HttpRequest buildPostRequest(final String gcpBucketURL, final BodyPublisher reqPublisher, 			final String authHeader, final String contentType, final String accepType) { 		HttpRequest.Builder builder = null; 		try { 			URI gcpURL = URI.create(gcpBucketURL); 			builder = HttpRequest.newBuilder(); 			builder.POST(reqPublisher); 			builder.uri(gcpURL); 			builder.header("Content-Type", contentType); 			if (accepType != null) { 				builder.header("Accept", accepType); 			} 			if (authHeader != null) { 				builder.header("Authorization", authHeader); 			} 		} catch (Exception exception) { 			exception.printStackTrace(); 		} 		return builder.build(); 	} }
Microsoft Graph
Microsoft Graph
A Microsoft programmability model that exposes REST APIs and client libraries to access data on Microsoft 365 services.
12,035 questions
{count} vote

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.