Errors in Microsoft Graph Integration (Part 2)

Due to the switch from EWS to MS Graph, different companies have to customize their digital products. In the first article, I have already covered some of the problems you may encounter during such a transition to Microsoft Graph REST API v1.0 with the MS Graph Java SDK. So, let’s dive straight into the following use cases.

Read the list of all available rooms and rooms of the company (It is not possible to get the list of multiple rooms)

In many companies, rooms can be booked automatically when an Outlook appointment is made. This is possible because all rooms are assigned to one email address by the administration. By adding a resource’s email address for event attendees, a booking process is automatically triggered, which checks whether a room is available. There’s also the option to create room lists, which also have their own email address. For this reason, we can initiate availability checks for multiple rooms at the same time. For example, a room list might stand for a building.
If your business app needs to get a list of all rooms or rooms for a tenant. With Microsoft Graph REST API v1.0, this can be done using the Location API. The following example shows how to make a GET request in Java to read all the rooms of a tenant (HTTP: GET /places/microsoft.graph.room,

GraphService service = getGraphService();
 
//load available rooms for one tenant 

String  extendedUrl = service.getGraphServiceClient()  
    .places() 
    .getRequestUrlWithAdditionalParameter("microsoft.graph.room");

 RoomList roomList = new RoomListRequestBuilder(extendedUrl,service.getGraphServiceClient(),null)
	.buildRequest()
	.get(); 

In this example, we first create a URL with the specific attachment “microsoft.graph.room” which is used to indicate that we want a list of all rooms in the tenant. Then we use the request builder to create the request and send the request.

According to the Microsoft docs, the room list can also be read (HTTP: GET /places/microsoft.graph.roomlist) Instead of using parameter value “microsoft.graph.room”, we should use parameter “microsoft.graph.roomlist”. Applied to the example above, the extended URL companion would look as:

String  extendedUrl = service.getGraphServiceClient()  
    .places() 
    .getRequestUrlWithAdditionalParameter("microsoft.graph.roomlist");
 

However, the Microsoft documentation clearly shows that it is not possible to read multiple cell lists through the Microsoft Graph Java SDK. If you check the SDK itself, there is no matching request builder to create the appropriate request. Although the documentation also shows for other technologies such as GO, PowerShell, or PHP, this room list option is available; The documentation insists that all implementations are still under testing and should not be used in production.

The bottom line is in the current version of Microsoft Graph REST API v1.0, all rooms should always be read, however you only need a subset of certain rooms.

Read examples of recurring events with no end date

There are different types of appointments. Basically, appointments can be divided into single and successive appointments. In Office365, series appointments have the following structure. There is a calendar schedule for the entire series. In addition, there is a calendar event for each instance. In EWS, the occurrence of the first event is also the chain event for the whole series. If you see the following example of a series event, read through the MS graph:

{
  "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users('dfb7498b-0c85-4386-bfe7-eddssbdd45390ea5')/events/$entity",
  "@odata.etag": "W/\"1+bFCkpFZUSj6sPne4wWRgAAjrcaBg==\"",
  "id": "AAMkADg4ZTk4NTQ5LTEyMWYtNDg5OSDFSSC1hZjcxLWMzZThhZGIwOTE0ZQBGAAAAAACeiB2A59PoRKo3vfjfoBEBBwDX5sUKSkVlRKPqw_d7jBZGAAAAAAENAADX5sUKSkVlRKPqw_d7jBZGAABuo5X8AAA=",
  "createdDateTime": "2022-09-22T04:35:05.4543959Z",
  "lastModifiedDateTime": "2022-09-22T04:37:06.4192553Z",
  "changeKey": "1+bFCkpFZUSj6sPne4wWRgAAjrcssfaBg==",
  "categories": [
    
  ],
  "transactionId": null,
  "originalStartTimeZone": "W. Europe Standard Time",
  "originalEndTimeZone": "W. Europe Standard Time",
  "iCalUId": "040000008200E00074C5B7101A82E0080000000020D324248C44DCED8010000000000000000100000001FA3A5B1E378134990BBA3383A6A7674",
  "reminderMinutesBeforeStart": 15,
  "isReminderOn": true,
  "hasAttachments": false,
  "subject": "title 1",
  "bodyPreview": "",
  "importance": "normal",
  "sensitivity": "normal",
  "isAllDay": false,
  "isCancelled": false,
  "isOrganizer": true,
  "responseRequested": true,
  "seriesMasterId": null,
  "showAs": "busy",
  "type": "seriesMaster",
  "webLink": "https://outlook.office365.com/owa/?itemid=AAMkADg4ZTk4ADFFSSFFNTQ5LTEyMWYtNDg5OC1hZjcxLWMzZThhZGIwOTE0ZQBGAAAAAACeiB2A59PoRKo3vfjfoBEBBwDX5sUKSkVlRKPqw%2Bd7jBZGAAAAAAENAADX5sUKSkVlRKPqw%2Bd7jBZGAABuo5X8AAA%3D&exvsurl=1&path=/calendar/item",
  "onlineMeetingUrl": null,
  "isOnlineMeeting": false,
  "onlineMeetingProvider": "unknown",
  "allowNewTimeProposals": true,
  "isDraft": false,
  "hideAttendees": false,
  "onlineMeeting": null,
  "responseStatus": {
    "response": "none",
    "time": "0001-01-01T00:00:00Z"
  },
  "body": {
    "contentType": "html",
    "content": "<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"><meta name=\"ProgId\" content=\"Word.Document\"><meta name=\"Generator\" content=\"Microsoft Word 15\"><meta name=\"Originator\" content=\"Microsoft Word 15\"><style><!--@font-face{font-family:\"Cambria Math\"}@font-face{font-family:Calibri}p.MsoNormal, li.MsoNormal, div.MsoNormal{margin:0cm;font-size:11.0pt;font-family:\"Calibri\",sans-serif}a:link, span.MsoHyperlink{color:#0563C1;text-decoration:underline}a:visited, span.MsoHyperlinkFollowed{color:#954F72;text-decoration:underline}span.EmailStyle17{font-family:\"Calibri\",sans-serif;color:windowtext}.MsoChpDefault{font-family:\"Calibri\",sans-serif}@page WordSection1{margin:70.85pt 70.85pt 2.0cm 70.85pt}div.WordSection1{}--></style></head><body lang=\"DE\" link=\"#0563C1\" vlink=\"#954F72\" style=\"word-wrap:break-word\"><div class=\"WordSection1\"><p class=\"MsoNormal\"> </p></div></body></html>"
  },
  "start": {
    "dateTime": "2022-09-23T12:30:00.0000000",
    "timeZone": "UTC"
  },
  "end": {
    "dateTime": "2022-09-23T13:00:00.0000000",
    "timeZone": "UTC"
  },
  "location": {
    "displayName": "",
    "locationType": "default",
    "uniqueIdType": "unknown",
    "address": {
      
    },
    "coordinates": {
      
    }
  },
  "locations": [
    
  ],
  "recurrence": {
    "pattern": {
      "type": "daily",
      "interval": 1,
      "month": 0,
      "dayOfMonth": 0,
      "firstDayOfWeek": "sunday",
      "index": "first"
    },
    "range": {
      "type": "noEnd",
      "startDate": "2022-09-23",
      "endDate": "0001-01-01",
      "recurrenceTimeZone": "W. Europe Standard Time",
      "numberOfOccurrences": 0
    }
  ]
  }
}

An example is the chain event for a series of events where the dates are repeated daily, and the chain has no end. For series that have no specific end date, Office365 still defines a value for the end date each time: “endDate”: “0001-01-01”. The value is the same for all series without an expiration date.

With Microsoft Graph REST API v1.0 the only way to read all instances of a series is with the Instance API. For the above example, the call would look like this:

email = "userEmail";
eventID = "ID of the series event";
startDate = "2022-09-23";
endDate = "0001-01-01";
GraphService service = getService(); 

LinkedList<Option> requestOptions = new LinkedList<>();   
requestOptions.add(new HeaderOption("Prefer", "outlook.body-content-type=\"text\"")); 
requestOptions.add(new QueryOption("startDateTime", startDate));    
requestOptions.add(new QueryOption("endDateTime", endDate));

List<Event> instances = service
	.getGraphServiceClient()
	.users(email)
    .events(eventID)
    .instances()  
    .buildRequest(requestOptions)
    .expand("attachments")     
    .get()        
    .getCurrentPage();

The example shows that a start date and an end date must be set for the request. The problem here, the end date is before the start date, so the request will not be successful. Now how can we get the instances of the series?

Assuming we have a use case, if the end date is equal to or less than the start date, the business app should read the next 30 events. Using the information from the recurrence pattern (recurrencePatternType and start date), we can define the end date that we will use for requests through the Instance API.

Let’s say we have a use case, if the end date is equal to or less than the start date, then the business app should get all the events for the next 100 days. In this case, we can easily define an update method:

String update(String numberOfDays, String startDate, String endDate) {     	
	
	if(LocalDate.parse(endDate).isBefore(LocalDate.parse(startDate)) || 
		LocalDate.parse(endDate).isEqual(LocalDate.parse(startDate))){ 
	     LocalDate newEndDate = LocalDate.parse(startDate).plusDays(Long.valueOf(numberOfDays));  
		 endDate = newEndDate.toString(); 
    }
    return endDate;
}

We just add the number of days and return the new expiration date which we can use for the request through the Instance API.

Overall, it makes sense to limit the time interval for a series with no expiration date; Otherwise, you’ll get all future events at once, and the payload will be too large to transfer. Therefore, there are two possible ways to define the end date: you define the new date in relation to the number of events you want to receive, or you define the new date in relation to a specific number of days.

Determine the ID of a user’s last generated appointment

Sometimes it is important for the user to determine the ID of the last generated event by use of an optimized query. Let’s consider the following example from MS Graph Java SDK:

final List<Option> options = new LinkedList<>();
options.add(new QueryOption("top", "1"));
String primerID = "test@mail.com";

Event event = graphClient
                .users(primerID)
                .events()
                .buildRequest(options)
                .select("id")
                .get()
                .getCurrentPage();

In this sample, graphClientis the local MS Graph client, and primerIDUser’s email address. With the client and two parameters, we build the following query:

GET https://graph.microsoft.com/user/<primerID>/events?top=1&$select=id

In this query, https://graph.microsoft.com base url is, primerID is the user’s primary email address, and top And select There are query parameters. How does this query work? Without query parameters the Events API returns a list of all events that are related to the user. query parameter top Filters the list and reduces the list to the first entry. second parameter select Causes only the property id of the event to have a value. A possible reaction might be as follows:

{
    "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users('dfb7498b-0c85-4386-bfe7-ebdd45390ea5')/events(id)",
    "@odata.nextLink": "https://graph.microsoft.com/v1.0/me/events?$select=id&top=1&$skip=1",
    "value": [
        {
            "@odata.etag": "W/\"1+bFCkpFZUSj6sPne4wWRgAAnu59UQ==\"",
            "id": "AAMkADg4ZTk4NTQ5LTEyMWYtNDg5OC1hZjcxLWMzZThhZGIwOTE0ZQBGAAAAAACeiB2A59PoRKo3vfjfoBEBBwDX5sUKSkVlRKPqw_d7jBZGAAAAAAENAADX5sUKSkVlRKPqw_d7jBZGAABuo5YAAAA="
        }
    ]
}

Leave a Comment