Commonly Occurring Errors in Microsoft Graph Integrations and How to Troubleshoot Them (Part 2)
This second article documents common integration errors that may be seen for Microsoft Graph integrations into business apps.
Join the DZone community and get the full member experience.
Join For FreeDue to the switch from EWS to MS Graph, various companies have to adapt their digital products. In the first article, I have already covered a few issues you may face during such a transition to the Microsoft Graph REST API v1.0 with the MS Graph Java SDK. So, let's dive right into the following use cases.
Read All Available Rooms and Room Lists of the Company (Getting Multiple Room Lists Is Not Possible)
In many companies, rooms can be booked automatically when an Outlook appointment is created. This is possible because all rooms are assigned to an email address by the administration. By adding the email address of the resource to the attendees of the event, a booking process is automatically triggered, which checks if the room is available or not. There is also the option to create room lists, which also have their own email address. For this reason, we can start the availability check for multiple rooms at the same time. For instance, a room list can stand for a building.
In case your business app has to get all rooms or room lists for a tenant. With the Microsoft Graph REST API v1.0, this can be done by using the Places API. The following example shows how to create a GET request in Java to read all 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 create a URL with the specific attachment "microsoft.graph.room" in the first place that is used to tell the API we want a list of all rooms of the tenant. Then we use the request builder to build and send the request.
According to the Microsoft documentation, room lists can also be read (HTTP: GET /places/microsoft.graph.roomlist
). Instead of using the parameter value "microsoft.graph.room", we must use the parameter "microsoft.graph.roomlist". Applied to the example above, the extended URL would look as fellow:
String extendedUrl = service.getGraphServiceClient()
.places()
.getRequestUrlWithAdditionalParameter("microsoft.graph.roomlist");
However, the Microsoft documentation shows clearly that reading out multiple room lists is not possible via 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 like GO, PowerShell, or PHP, this room list option is available; the documentation emphasizes all implementations are still under test and should not be used in production.
The bottom line is in the current version of Microsoft Graph REST API v1.0, all rooms must always be read, although you only need a subset of certain rooms.
Read Instances of Recurring Events Without an End Date
There are different types of appointments. Basically, appointments can be divided into single and serial appointments. In Office365, a series appointment has the following structure. There is one calendar event for the whole series. In addition to that, there is a calendar event for each instance. In EWS, the event of the first instance is also the series event for the whole series. If you look at the following example of a series event, read via 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 series event for a series of events where the dates repeat daily, and the series has no end. For series that do not have a 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 end date.
The only way to read all instances of a series with Microsoft Graph REST API v1.0 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 defined for the request. The problem here, the end date is before the start date, so the request will not be successful. How can we get the instances of the series now?
Assuming we have the use case, if the end date is equal to or lower than the start date, then 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 we will use for the request via the Instance API.
Assuming we have the use case, if the end date is equal to or lower than the start date, then the business app should get all 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 end date we may use for the request via the Instance API.
Overall, it makes sense to restrict the time interval for series without an end date; otherwise, you would get all events from the future at once, and the payload would be too big to transfer. So, there are two possible ways to define the end date: you define the new date with regard to the number of events you want to get, or you define the new date with regard to a specific number of days.
Determine the ID of the Last Generated Appointment of a User
Sometimes it is important to determine the ID of the last generated event for a user by the use of a customized query. Let's consider the following instance of the 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, graphClient
is the local MS Graph client, and primerID
is the user's email address. With the client and two parameters, we create the following query:
GET https://graph.microsoft.com/user/<primerID>/events?top=1&$select=id
In this query, https://graph.microsoft.com
is the base URL, primerID is
the user's primary email address, and top
and select
are query parameters. How does this query work? The Events API without query parameter returns a list of all events which belong to the user. The query parameter top
filters the list and reduces the list to the first entry. The second parameter select
causes only the property id of the event to have a value. A possible response 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="
}
]
}
Opinions expressed by DZone contributors are their own.
Comments