Notification – Azure SignalR – Sentiment of comments given on a SharePoint page – Part 3

13 Nov

The previous two posts (part 1, part 2) explained the analysis of the sentiment of a comment given on a page and a webhook that was triggered when this sentiment score was written to the list. The last posts ended showing the value of the RawSentiment field of the item that was changed or added.

This post will show a notification to specific persons in eg a Marketing group when the sentiment score is ‘extreme’. When a very low or high score is returned from the Cognitive Service there is probably something going on about the messages on that specific page that maybe requires clarification (very low score) or something else.

To be notified on ‘extreme’ scores Azure SignalR Service is used in combination with Azure Functions 2.0 (https://azure.microsoft.com/en-us/blog/introducing-azure-functions-2-0/). Be aware that Azure Functions 2.0 runtime uses .NET Core 2.0, while version 1.x used the .NET framework 4.x.

Create a new Azure SignalR Service instance

After logging in to the Azure Portal create a new SignalR Service instance.

After the instance is created get the connection string from the Keys tab. The connection string is going to be used in the Azure Function which will be created next.

SignalR input binding

A client must retrieve the service endpoint URL to be able to connect to Azure SignalR Service created in the previous step.
Therefor a new HttpTriggered function is created where the AzureSignalRConnectionInfo object is injected using the SignalRConnectionInfo attribute. In the attribute the name of the hub (choose your own) needs to be specified. SignalR uses hubs to communicate between clients and servers.
The connection string of the Azure SignalR Service instance has to be stored in local.settings.json in the default settings name AzureSignalRConnectionString.

Azure SignalR Service is generally available. However, SignalR Service bindings for Azure Functions are currently in preview.

To get the required Azure Function bindings for the SignalR Service add a reference to the
Microsoft.Azure.WebJobs.Extensions.SignalRService package, version 1.0.0-preview1-10002. This is the only version available at the time of writing this post.


[FunctionName("negotiate")]
public static IActionResult GetSignalRInfo(
  [HttpTrigger(AuthorizationLevel.Anonymous, "post", "options")]HttpRequest req,
  [SignalRConnectionInfo(HubName = "broadcast")]SignalRConnectionInfo info)
{
  // Azure function doesn't support CORS well, workaround it by explicitly return CORS headers
  req.HttpContext.Response.Headers.Add("Access-Control-Allow-Credentials", "true");
  if (req.Headers["Origin"].Count > 0) req.HttpContext.Response.Headers.Add("Access-Control-Allow-Origin", req.Headers["Origin"][0]);
  if (req.Headers["Access-Control-Request-Headers"].Count > 0) req.HttpContext.Response.Headers.Add("Access-Control-Allow-Headers", req.Headers["access-control-request-headers"][0]);

  return info != null
    ? (ActionResult)new OkObjectResult(info)
    : new NotFoundObjectResult("Failed to get SignalR connection information");
}

ProcessMessage function

In part 2 of this series a function named ProcessMessage was created to be notified of all changes that happened on the list that’s been watched by subscribing to a webhook endpoint.
This function is going to put a message on a Service Bus Queue when an ‘extreme’ sentiment value is registered, say below 0.2 or above 0.8.

...

// put message on Service Bus Queue to trigger SignalR service
var content = $"Extreme score detected: {rawSentiment}";
var logMessage = string.Empty;
var connectionString = ConfigurationManager.AppSettings["ServiceBusConnection"];
var queueName = ConfigurationManager.AppSettings["QueueNameBroadcast"];

if (string.IsNullOrEmpty(connectionString) || string.IsNullOrEmpty(queueName))
{
logMessage = "AppSettings not found.";
log.Error(logMessage);
throw new ArgumentNullException(logMessage);
}

var client = QueueClient.CreateFromConnectionString(connectionString, queueName);
BrokeredMessage msg = new BrokeredMessage(content);
await client.SendAsync(msg);

log.Info("Message added to queue");

...

SignalR output binding

To send messages to all connected clients using Azure SignalR Service the output binding is used. To do so a new ServiceBusTriggered function is created which listens to the queue where the ProcessMessage function is putting messages to.
An IAsyncCollector object is injected in the function using the SignalR attribute. In the attribute the name of the hub needs to be specified again.
To actually broadcast a message the AddAsync method needs to be called on IAsyncCollector signalRMessages with a new SignalRMessage as parameter.
The Target is the name of the method to be invoked on each client. The arguments property is an array of zero or more objects to be passed to the client method.

[FunctionName("BroadcastMessages")]
public static void Run(
[ServiceBusTrigger("%QueueName%", Connection = "ServiceBusConnection")]string     myQueueItem,
[SignalR(HubName = "broadcast")]IAsyncCollector<SignalRMessage> signalRMessages)
{
//actually send a new message
signalRMessages.AddAsync(new SignalRMessage()
{
Target = "notify",
Arguments = new object[] { myQueueItem }
});
}

Client

At last we need a client (or more) to subscribe to the messages broadcasted by the Azure SignalR Service. Therefor an SPFx application customizer can be used.
Install the JavaScript and TypeScript clients for SignalR for ASP.NET Core using
npm install @aspnet/signalr

To connect to a hub a HubConnectionBuilder object has to be created to configure a HubConnection instance.
Once the connection is in place the client can subscribe to broadcast messages of the service by calling the ‘on’ function of the HubConnectionBuilder with the method name ‘notify’ as the first parameter. This is the method name specified in the Target of the AddAsync method.
At last the connection has to be started.


this.hubConnection = new signalR.HubConnectionBuilder()
  .withUrl(hubUrl)
  .configureLogging(signalR.LogLevel.Information)
  .build();

this.hubConnection.on('notify', (data: any) => {
  console.log(data);
});

this.hubConnection.start().catch(err => console.error(err.toString()));

As a best practice, call the start method on the HubConnection after ‘on’. Doing so ensures your handlers are registered before any messages are received.

Once the connection has been established the following message can be seen in the console of the browser:

And when a message is broadcasted it appears in the console.

At this moment the broadcasted message is written to the console, but this can be improved a lot of course!
This could be shown as an informative notification at the top of the page for example using the placeholders of the page.

Summary

There was a lot to do in this series of posts.
Part 1 collected comments of a page and determined using Cognitive Services the sentiment of them. The results were stored in a list.
Part 2 added a webhook to this list to be able to be notified of changes to this list. These changes were put on a ServiceBus Queue. Another function listened to this queue and processed the message to be able to determine (‘extreme’) sentiment scores.
Part 3, this post, created an Azure SignalR Service en Azure Functions to be able to get connection info and to broadcast messages to clients, it this case ‘extreme’ sentiment scores collected. At last clients were notified of the ‘extreme’ scores.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.