Background
I need help with making use of the response from the Microsoft Graph API's GET /callRecords endpoint. I included the expand=segments parameter to inspect the segments within each session. The JSON response I received is ~3100 lines long. It contains 15 sessions, and each session contains 1 segment. I have spent a while studying it, and I cannot figure out how to determine the flow of the call from it. One thing I noticed was that when I tried sorting the sessions by StartDateTime, the time-sorted results were significantly different in order than the sessions on the original Call Record object.
My test call
In my test call, I used my test Teams User Diego to call my Teams Resource queue. My Teams User Adele answered the queue call. Then, Adele transferred the call to a different Teams User Demo User.
What I'm hoping to deduce
I'd like to be able to figure out what information shows me that Adele transfered the call to Demo User.
I put the serialized json into a file sample.json
and put it into my code sandbox so I could share it.
Here's the start of my C# code:
var config = new ConfigurationBuilder()
.SetBasePath(projectDirectory)
.AddJsonFile("appsettings.json", false, true)
.Build();
var authenticationProvider = CreateAuthorizationProvider(config);
var graphServiceClient = new GraphServiceClient(authenticationProvider);
var config = LoadAppSettings();
var graphClient = GetAuthenticatedGraphClient(config);
var testCallId = config["testCallId"];
var options = new List<QueryOption> { new QueryOption("$top", "1") };
CallRecord graphResult = await graphClient.Communications.CallRecords[testCallId]
.Request()
.Expand("sessions($expand=segments)")
.GetAsync();
var jsonResult = JsonSerializer.Serialize(graphResult, new JsonSerializerOptions { WriteIndented = true });
Background
I need help with making use of the response from the Microsoft Graph API's GET /callRecords endpoint. I included the expand=segments parameter to inspect the segments within each session. The JSON response I received is ~3100 lines long. It contains 15 sessions, and each session contains 1 segment. I have spent a while studying it, and I cannot figure out how to determine the flow of the call from it. One thing I noticed was that when I tried sorting the sessions by StartDateTime, the time-sorted results were significantly different in order than the sessions on the original Call Record object.
My test call
In my test call, I used my test Teams User Diego to call my Teams Resource queue. My Teams User Adele answered the queue call. Then, Adele transferred the call to a different Teams User Demo User.
What I'm hoping to deduce
I'd like to be able to figure out what information shows me that Adele transfered the call to Demo User.
I put the serialized json into a file sample.json
and put it into my code sandbox so I could share it. https://codesandbox.io/p/sandbox/sls33t
Here's the start of my C# code:
var config = new ConfigurationBuilder()
.SetBasePath(projectDirectory)
.AddJsonFile("appsettings.json", false, true)
.Build();
var authenticationProvider = CreateAuthorizationProvider(config);
var graphServiceClient = new GraphServiceClient(authenticationProvider);
var config = LoadAppSettings();
var graphClient = GetAuthenticatedGraphClient(config);
var testCallId = config["testCallId"];
var options = new List<QueryOption> { new QueryOption("$top", "1") };
CallRecord graphResult = await graphClient.Communications.CallRecords[testCallId]
.Request()
.Expand("sessions($expand=segments)")
.GetAsync();
var jsonResult = JsonSerializer.Serialize(graphResult, new JsonSerializerOptions { WriteIndented = true });
Share
Improve this question
edited Mar 24 at 21:01
Nate W
asked Mar 24 at 18:36
Nate WNate W
3203 silver badges13 bronze badges
1 Answer
Reset to default 0I think I have this figured out. I'll post my answer here for posterity.
I'm learning a lot about the kinds of objects that are returned from this API. A CallRecord
returns with some basic information about the call, if it was able to find it. Retrieving a call record from graph requires submitting a guid that matches any of the id
values from the sessions on the call record.
I realized that I do need the CallRecord.Sessions
property to be populated, but I don't need the Segments
that are on each session. The sessions have enough information for me to figure out what I wanted.
.Expand("sessions") // this is correct
.Expand("sessions($expand=segments)") // this is unnecessary
I needed to sort the sessions by StartDateTime, and then inspect the Caller
and Callee
properties on them. These properties are both endpoints that are extending the base type into either participant endpoint or service endpoint. All of the sessions have at least one of the two, Caller
or Callee
as a participant endpoint. The sessions that I'm interested in have the Callee
as the participant endpoint, meaning that the call is being connected to a participant.
If the id that I am requesting from graph matches with my queue call, where the Teams user connected to my queue and an agent answered, the condition of whether there was a transfer is if there's another session that has Participant Endpoint as the Callee
.
When trying to find the name of the Callee, I used the ODataType property on the endpoint's associated identity. If the type is userIdentity, then the identity is a Microsoft Teams identity. If the type contains the string "communicationsPhoneIdentity", then the identity is a PSTN identity.
Here's my final code.
private static void InspectSessions(CallRecord graphResult, string testCallId)
{
var sessions = graphResult.Sessions.CurrentPage.OrderBy(s => s.StartDateTime).ToList();
var session = sessions.First(s => s.Id == testCallId);
var startTime = session.StartDateTime;
var transferSession = sessions.FirstOrDefault(s => s.Callee is ParticipantEndpoint && s.Id != testCallId);
if (transferSession != null)
{
var transferAnalysis = new MyCallRecordDto
{
SessionId = transferSession.Id,
StartDateTime = transferSession.StartDateTime,
Direction = transferSession.StartDateTime > session.StartDateTime ?
Direction.AwayFromRequestedScenarioId :
Direction.IntoRequestedScenarioId
};
SetDisplayNameAndTransferType(transferSession.Callee, transferAnalysis);
Console.WriteLine();
Console.WriteLine("Transfer analysis:");
Console.WriteLine(transferAnalysis);
}
else
{
Console.WriteLine();
Console.WriteLine($"No transfers for call {testCallId}");
}
}
private static void SetDisplayNameAndTransferType(Microsoft.Graph.CallRecords.Endpoint transferSession, MyCallRecordDto dto)
{
string displayName;
dto.TransferType = TransferType.Unknown;
var calleeEndpoint = transferSession as ParticipantEndpoint;
if (IsTeamsEndpoint(calleeEndpoint))
{
TryGetTeamsUserDisplayName(calleeEndpoint, out displayName);
dto.DisplayName = displayName;
dto.TransferType = TransferType.Teams;
}
else if (IsPSTNEndpoint(calleeEndpoint))
{
TryGetPSTNPhoneNumber(calleeEndpoint, out string phone);
dto.DisplayName = phone;
dto.TransferType = TransferType.PSTN;
}
if (string.IsNullOrWhiteSpace(dto.DisplayName))
{
SetDisplayNameFromHeader(dto, calleeEndpoint);
}
}
private static bool IsTeamsEndpoint(ParticipantEndpoint calleeEndpoint)
{
try
{
calleeEndpoint.AdditionalData.TryGetValue("associatedIdentity", out var associatedIdentity);
var userIdentity = (associatedIdentity as JsonElement?)?.Deserialize<UserIdentity>();
return userIdentity.ODataType.Contains("userIdentity");
}
catch
{
return false;
}
}
private static bool IsPSTNEndpoint(ParticipantEndpoint calleeEndpoint)
{
try
{
calleeEndpoint.AdditionalData.TryGetValue("associatedIdentity", out var associatedIdentity);
var userIdentity = (associatedIdentity as JsonElement?)?.Deserialize<UserIdentity>();
return userIdentity.ODataType.Contains("communicationsPhoneIdentity");
}
catch
{
return false;
}
}
private static void SetDisplayNameFromHeader(MyCallRecordDto dto, ParticipantEndpoint calleeEndpoint)
{
dto.DisplayName = string.IsNullOrWhiteSpace(calleeEndpoint.UserAgent.HeaderValue) ?
"ServiceEndpoint" :
Regex.Replace(calleeEndpoint.UserAgent.HeaderValue, @"appid:[\w-]+|\/[\d.]+|-\d+(\.\d+)+", "").Trim();
dto.DisplayName = dto.DisplayName.Replace(" ()", "");
}
private static bool TryGetPSTNPhoneNumber(ParticipantEndpoint calleeEndpoint, out string phone)
{
if (calleeEndpoint?.Identity.AdditionalData != null &&
calleeEndpoint.Identity.AdditionalData.TryGetValue("phone", out var phoneData) &&
(phoneData as JsonElement?)?.Deserialize<IDictionary<string, object>>() is IDictionary<string, object> info &&
info.TryGetValue("id", out var phoneValue))
{
phone = phoneValue.ToString();
return true;
}
else
{
phone = null;
return false;
}
}
private static bool TryGetTeamsUserDisplayName(ParticipantEndpoint calleeEndpoint, out string displayName)
{
try
{
calleeEndpoint.AdditionalData.TryGetValue("associatedIdentity", out var associatedIdentity);
var userIdentity = (associatedIdentity as JsonElement?)?.Deserialize<UserIdentity>();
displayName = userIdentity.DisplayName;
return true;
}
catch
{
displayName = null;
return false;
}
}
private enum Direction
{
Unknown,
IntoRequestedScenarioId,
AwayFromRequestedScenarioId
}
private enum TransferType
{
Unknown,
Teams,
PSTN
}
private class MyCallRecordDto
{
public string SessionId { get; set; }
public DateTimeOffset? StartDateTime { get; set; }
public string DisplayName { get; set; }
public Direction Direction { get; set; }
public TransferType TransferType { get; set; }
public override string ToString()
{
return $"SessionId: {SessionId}\nStartDateTime: {StartDateTime}\n" +
$"DisplayName: {DisplayName}\nDirection: {Direction}\n" +
$"TransferType: {TransferType}";
}
}