DEV Community

Cover image for Serverless Prediction of a Product Feedback
Jayendran Arumugam
Jayendran Arumugam

Posted on • Edited on

Serverless Prediction of a Product Feedback

This article is part of #ServerlessSeptember. You'll find other helpful articles, detailed tutorials, and videos in this all-things-Serverless content collection. New articles from community members and cloud advocates are published every week from Monday to Thursday through September.

Find out more about how Microsoft Azure enables your Serverless functions at https://docs.microsoft.com/azure/azure-functions/.

Story behind my use-case

If you are developing any product, feedback is much more important. You have to trace each and every feedback like feature request or Bugs/Issues. Even Microsoft has a dedicated website https://feedback.azure.com/ for tracking all its feedback related to their Azure Product. For issues they have to use GitHub issues https://github.com/MicrosoftDocs/azure-docs/issues for all their open Source Projects. This would be a great way to track all the feedback on the open-source project. But how we can track Internal Project feedback efficiently!

Because ...

In this blog, we are going to use a better way to solve this problem of getting feedback about our internal projects with the help of azure server-less computing and AI šŸ˜Ž

Let's jump into our Project

Architecture

Let's assume that we are going to develop a new internal product called myproduct (wow what a unique name šŸ¤£ )

I'm going to use Yammer to get all the feedback's from our internal users.

Yammer is your social layer across Microsoft 365, integrating with the apps and services you already use to stay productive. You can create and edit documents, take notes, and share resources as a group. And get back to your conversations from anywhere in Microsoft 365.

If you notice the architecture, most of the main components are server-less like azure functions and logic app. There are many advantages of using server-less is our architecture. some of them are:

  • Cost Efficient (Consumption model)
  • Less Development time (logic-app : drag/drop model)
  • Quick Deployments etc.,

Components used

The components in this architecture are more flexible. i.e, you can easily replace one component with the other similar one.

For e.g

  1. You can use MS Teams or any other internal chat-based tool instead of yammer for getting the feedback.
  2. Azure DevOps WorkItem Tracking can be replaced by github/JIRA kind of tools easily.
  3. Cosmos DB can be replaced by the Azure Table Storage.

See how flexible this architecture is when we start using more and more server-less components šŸ˜‰

Workflow

Here I'm focusing on a product that was developed for internal purposes. So our end-users are nothing but the employees in our organization. So I used Yammer as my main platform to gather all the feedback and bugs about myproduct from users.

Logic App

As soon as a user posted in yammer our logic app will be triggered. Let's see how our logic app now.

Yammer

LUIS

I'm using LUIS to predict what are the intents in the yammer post(utterances)

Intents: BugsšŸž /Features
Utterances: Yammer post which users posted

An Utterances are input from the user that your app needs to interpret.

An Intent represents a task or action the user wants to perform. It is a purpose or goal expressed in a user's utterance.

I trained by LUIS with some user-defined Intents. You can also find my exported LUIS model in the Github

AzureDevOps WorkItems

If the predicted intent is Bug then I'll create a bug work-item in AzureDevOps else if it is Feedback then I'll create Feature work-item as simple as that.

CosmosDB

All are fine, but what happens if the intent is neither bug nor feedback?
In such a case, None Intent will be our predicted Intent. This None Intent is more important for Re-training our LUIS model. So I'm going to save these intents safely in CosmosDB

Azure Functions

OK, I've saved the None Intents in cosmos DB, now I'm going to build a static website where developers can view all the information about the none intents in near real-time without doing any refresh.

This is quite an interesting part, where I've learned how powerful the Azure-Functions are! Thanks to Anthony Chu for his wonderful blog

Image Credits: Anthony Chu

I've used Signal-R for a Broadcasting real-time updates from cosmos DB using CosmosDB Change-Feed in Azure Function.

If you see the below image I've four azure functions totally.

Let's start with OnDocumentsChanged class, which will be triggered automatically for every insert/update on the target Cosmos DB.

For every new/updated item in the cosmos DB, I'll simply adding those items as messages in the SignalRHub(SignalRHubItems)

public static class OnDocumentsChanged
    {
        [FunctionName("OnDocumentsChanged")]
        public static async Task Run([CosmosDBTrigger(
            databaseName: "MyProductDB",
            collectionName: "Items",
            ConnectionStringSetting = "AzureWebJobsCosmosDBConnectionString",
            LeaseCollectionName = "leases",
            CreateLeaseCollectionIfNotExists = true)]
             IEnumerable<object> updatedItems,
            [SignalR(HubName = "SignalRHubItems")] IAsyncCollector<SignalRMessage> signalRMessages,
            ILogger log)
        {
            foreach(var item in updatedItems)
            {
                await signalRMessages.AddAsync(new SignalRMessage
                {
                    Target = "SignalRHubUpdatedItems",
                    Arguments = new[] { item }
                });
            }
        }
Enter fullscreen mode Exit fullscreen mode

The SignalRInfo class will be called from the static web page to make connections to SignalRhub to read the message from that.

public static class SignalRInfo
    {
        [FunctionName("SignalRInfo")]
        public static IActionResult Run(
            [HttpTrigger(AuthorizationLevel.Anonymous,"get","post")] HttpRequest req,
            [SignalRConnectionInfo(HubName = "SignalRHubItems")] SignalRConnectionInfo connectionInfo,
            ILogger log)
        {
            return new OkObjectResult(connectionInfo);
        }
    }
Enter fullscreen mode Exit fullscreen mode

The GetItems class will also be called from the static web page to make connections to CosmosDB to get/read all the Items.

public static class GetItems
    {
        [FunctionName("GetItems")]
        public static IActionResult Run(
            [HttpTrigger(AuthorizationLevel.Anonymous,"get","post")] HttpRequest req,
            [CosmosDB("MyProductDB", "Items", ConnectionStringSetting = "AzureWebJobsCosmosDBConnectionString")]
                IEnumerable<object> updatedItems,
            ILogger log)
        {
            try{
                return new OkObjectResult(updatedItems);
            }
            catch(Exception e){
                log.LogError(e.Message.ToString());
            }
             return new OkObjectResult(updatedItems);

        }
    }
Enter fullscreen mode Exit fullscreen mode

Finally StaticFileFunction class is simple Az-function to show our statics web page (index.html), inside www folder

public static class StaticFileFunction
    {
        const string staticFilesFolder = "www";
        static string defaultPage = String.IsNullOrEmpty(GetEnvironmentVariable("DEFAULT_PAGE")) ?
    "index.html" : GetEnvironmentVariable("DEFAULT_PAGE");

        [FunctionName("StaticFileFunction")]
        public static HttpResponseMessage Run(
            [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,
            ILogger log)
        {
            try
            {
                var filePath = GetFilePath(req, log);

                var response = new HttpResponseMessage(HttpStatusCode.OK);
                var stream = new FileStream(filePath, FileMode.Open);
                response.Content = new StreamContent(stream);
                response.Content.Headers.ContentType =new MediaTypeHeaderValue(GetMimeType(filePath));
                return response;
            }
            catch (Exception e)
            {
                string name = e.Message.ToString();
                return new HttpResponseMessage(HttpStatusCode.NotFound);
            }
        }

        private static string GetScriptPath()
            => Path.Combine(GetEnvironmentVariable("HOME"), @"site\wwwroot");

        private static string GetEnvironmentVariable(string name)
            => System.Environment.GetEnvironmentVariable(name, EnvironmentVariableTarget.Process);
        private static string GetFilePath(HttpRequest req, ILogger log)
        {
            var path = req.Query["file"];

            var staticFilesPath = Path.GetFullPath(Path.Combine(GetScriptPath(), staticFilesFolder));
            var fullPath = Path.GetFullPath(Path.Combine(staticFilesPath, path));

            if (!IsInDirectory(staticFilesPath, fullPath))
            {
                throw new ArgumentException("Invalid path");
            }

            var isDirectory = Directory.Exists(fullPath);
            if (isDirectory)
            {
                fullPath = Path.Combine(fullPath, defaultPage);
            }

            return fullPath;
        }

        private static bool IsInDirectory(string parentPath, string childPath)
        {
            var parent = new DirectoryInfo(parentPath);
            var child = new DirectoryInfo(childPath);

            var dir = child;
            do
            {
                if (dir.FullName == parent.FullName)
                {
                    return true;
                }
                dir = dir.Parent;
            } while (dir != null);

            return false;
        }

        private static string GetMimeType(string filePath)
        {
            var provider = new FileExtensionContentTypeProvider();
            string contentType;
            provider.TryGetContentType(filePath, out contentType);
            return contentType;
        }
    }

Enter fullscreen mode Exit fullscreen mode

That's it! Let's test it out

Testing

Posted some posts in Yammer.

Checking AzureDevOps

It's created bugs and features correctly. However one of the posts got missed meaning it's predicted as None Intent.

Checking CosmosDB

So our post got inserted in cosmos DB correctly.

Static Web Page

Here is the live-stream

And it's Working Finally!

Source Code

This is an Open-Source Project, view the full source code in the below link and feel free to provide feedback/issues to me šŸ˜Ž

Serverless Prediction of a Product Feedback (#AzureDevStories)

This Project is created as a part of Azure Dev Stories Challenge and won the First Prize šŸ†

I've published a detailed article about this project in the Dev Community

Architecture Diagram

Components Used

  • Yammer
  • LUIS
  • Logic Apps
  • AzureDevOps WorkItems
  • Cosmos DB
  • Signal R
  • Azure Functions

Yammer

Users provide their feedback about the product. It could be many, for the demo purpose I just choose 2 topics (Bug, Feature)



LUIS

Creating Intents for Bugs and Feedbacks in the LUIS.

Logic Apps

Predicting the Intents i.e, Bug or Feedback based on the Yammer Post by the user and take necessary actions

AzureDevOps WorkItems

Create Bug/Feature if the top intent of the Post matched with LUIS

Cosmos DB

Insert the document in Cosmos DB if the top intent of the Post is None

Signal R

Serverless Signal R used to autorefresh the WebPage for theā€¦




Reference

Top comments (1)

Collapse
 
madebygps profile image
Gwyneth PeƱa-Siguenza

This is fantastic! I've got to try this with logic apps, thanks for sharing.