Saturday, 27 April, 2019 UTC


Summary

A handful of products rely on reviews and star-ratings for product feedback. When customers are impressed or dissatisfied about our product, they come back to where it was purchased looking for a way to leave feedback. Apps and e-commerce products are examples of the scenarios where we would love to hear what the customer thinks.
Here's what we'll build. Notice how the stars generate based on our text!
TLDR: How does it work!?
We'll create a serverless function that accepts text. We'll analyze the text using Cognitive Services by Azure. We'll display the stars using React.
Review Flow (1-5 Stars)
The common flow for requesting feedback for our products might start with asking for a rating "1-5 stars" for example, and followed by a request for more personal observations to give context to the rating that’s been requested. This additional context gives future consumers more information about whether they might also like the product being reviewed based on common context.
The other strategy is to present the customer with both the form to write their review and the star rating control at the same time.
What if we take the user experience up one level and help them suggest stars based on the sentiments in their review texts?
This would look like the customer typing and we automatically rate the product in realtime.
  • For each keystroke or set of keystrokes, we will analyze what the customer is typing, generate a score based on the sentiment of the customer’s input, then star the product.
  • The customer can also click on the star to rate directly before writing the feedback or if they don’t agree on the suggestion
In this article, I will walk you through all the steps to make this possible. You can use it as an idea for a related project or read to get more excited about Artificial Intelligence.
The project is divided into 2 sections:
  1. A serverless function that handles the requests and analysis
  2. A React app for the UI
Serverless function for our server
One of the reasons why serverless functions are shining these days is because it takes only 5 mins to set up a scalable backend for your products at all complexity levels. I use Azure Functions a lot because it integrates nicely with VS Code, so I end up not having to go to a dashboard in the browser or even the terminal to test and deploy servers.
If you have not set up a function before or need a refresher, take a look at my detailed yet quick 5 minute post on how to set up a serverless function using VS Code.
Create a new function called autostar in your function project/folder using HTTPTrigger. You should get the default function’s template in autostar/index.js.
Getting started with Cognitive Services (CogS)
Cognitive Services is a subset of Artificial Intelligence that gives you trained models. These models help you with things like text analysis, sentiment analysis, facial recognition, speech analysis, and a lot more.
What this means is that you will NOT need to manually use data to train a system before it can be smart enough to answer some questions like:
  1. Which celebrity is in this picture?
  2. How sad is the text I sent?
  3. What is the license number of the cars that visited my hotel today?
  4. How happy is my grandmother this morning?
  5. Is this customer old enough to order a beer?
  6. Who said that?
With CogS, you just send an audio, video, image or text file to an endpoint, then get some analysis and answers. Answers enough to solve the questions we listed above.
Get a CogS endpoint and key
You need an API endpoint to send your requests to and a key to authenticate that it’s really you.
  1. Go to Try Cognitive Service.
  2. Choose the Language APIs tab
  3. Click Get API Key.
  4. Pick one of the signup options that works for you. You can pick something very temporary just for learning and fun sake.
  5. Copy your Endpoint and Keys. You can only use one of the keys at a given request.
Requesting for text (sentiment) analysis
Sentiment analysis is basically analyzing what someone said and weighing how positive or negative their content is. Is it sad? Is it happy? Is it hateful? This is what we need to determine how many stars a feedback is worth.
This is what the lifecycle of our App will look like when trying to analyze feedback:
The React app will send the feedback content to the serverless endpoint. The serverless endpoint validates the request and sends a payload to the CogS endpoint for analysis. After analyzing, the CogS endpoint will send the sentiment payload back to the serverless function and the serverless function forwards this response to the React app for the stars.
Delete everything in autostar/index.js and add the following:
const fetch = require('node-fetch');

module.exports = async function(context, req) {
  const accessKey = '<KEY>';
  const baseUri = 'https://<center>.api.cognitive.microsoft.com';
  const sentimentPath = '/text/analytics/v2.0/sentiment';

  const feedback = req.query.feedback || (req.body && req.body.feedback);

  if (feedback) {
    const documents = {
      documents: [{ id: 1, language: 'en', text: feedback }]
    };
    const params = {
      method: 'post',
      body: JSON.stringify(documents),
      headers: {
        'Content-Type': 'application/json',
        'Ocp-Apim-Subscription-Key': accessKey
      }
    };
    const res = await fetch(baseUri + sentimentPath, params);
    const json = await res.json();
    context.res = { body: json };
  } else {
    context.res = {
      status: 400,
      body: {
        error:
          'Please pass a feedback text on the query string or in the request body'
      }
    };
  }
};
When a request hits this function, it validates if feedback can be found anywhere in the body or query string. After which it assembles the feedback into an array then sends a request to CogS. Notice how we are passing the key using the Ocp-Apim-Subscription-Key.
Rating and submitting feedback with React
We have the endpoint for our UI to push feedback to, now let’s start sending those requests. Create a new React app in CodeSandbox or with the CLI tool:
npx create-react-app auto-star-ui
We need an external library for rating and Lodash’s throttle. The throttle function will help us delay spamming our server with too much requests during keystrokes from the customer:
npm install --save react-rater lodash.throttle
Create a FeedbackForm.js file in the src folder. This will be the component that holds and handles the feedback form. Import the following:
import React from 'react';
import Rater from 'react-rater';
import throttle from 'lodash.throttle'; 
import 'react-rater/lib/react-rater.css';
import './App.css';
I am also importing the App.css file because we will update it with styles that are related to this component.
Create the actual functional component skeleton:
export const FeedbackForm = ({
  handleFormSubmit,
  handleFeedbackChange,
  setRating,
  curRating = 0
}) => {
  // ...
}
The component will receive
  1. handleFormSubmit to be called when the form is submitted.
  2. handleFeedbackChange This is where the real thing happens. It’s called at keystrokes (customer is typing) and it also sends the request to our serverless function.
  3. setRating is used to update the stars.
  4. curRating is the current rating at any given time.
We need an internal state to hold the feedback text that the user is typing:
export const FeedbackForm = ({
 //...
}) => {
 const [feedback, setFeedback] = React.useState('');
}
If we send a request at every keystroke, we would flood our endpoint with meaningless or wasteful content. A quick strategy to fix this is to throttle the requests. What this means is that we only send a request every x period of time; in our case, every 1.5 seconds. Lodash’s throttle will help us with this.
const [feedback, setFeedback] = React.useState('');

const throttledChangeHandler = React.useRef(
  throttle(feedback => handleFeedbackChange(feedback), 1500)
);

React.useEffect(() => {
  if (!!feedback) {
    return throttledChangeHandler.current(feedback);
  }
}, [feedback]);
We are wrapping the throttle in a useRef because for every render; we get a new throttle function which resets everything, so the function does not remember what the last inputs of the user were.I explained this in more detail in an article I wrote previously.
Now we can render the elements, pass in some states and props to them, and handle events:
export const FeedbackForm = ({
 // ...
}) => {
  //...
  return (
    <form>
      <div className="label-stars">
        <label htmlFor="feedback">What do you think?</label>
        <Rater
          total={5}
          rating={curRating}
          onRate={({ rating }) => {
            setRating(rating);
          }}
        />
      </div>
      <textarea
        value={feedback}
        onKeyPress={e => {
          if (e.which === 13 && !e.shiftKey) {
            e.preventDefault();
            handleFormSubmit(feedback, curRating);
            setFeedback('');
          }
        }}
        onChange={e => {
          setFeedback(e.target.value);
        }}
        placeholder="Love or hate? No hard feelings!"
      />
    </form>
  );
};
When we type in the textarea, both onChang``e() and onKeyPres``s() is called:
  1. onChange calls setFeedback which updates the state. When the state is updated, the component re-renders and calls useEffect which we saw previously. When useEffect is called, it runs throttledChangeHandler and then handleFeedbackChange is called.
  2. onKeyPress just checks if we hit enter without holding shift. If we held shit, it would move to a new line. If we didn’t, it would submit the feedback.
I am also updating the rater through the onRate property and also setting the current rating with rating property.
Sending requests from React
We are just passing down handleFeedbackChange, handleFormSubmit, and setRating but we don’t know what they look like. We are now going to render FeedbackForm in App and see how those props get passed down and what those props look like.
Delete the content of ./src/App.js and update with the following:
import React, { Component } from 'react';
import { FeedbackForm } from './FeedbackForm';
import './App.css';

class App extends Component {
  state = {
    curRating: 0,
  };

  // ...
  render() {
    return (
      <div className="App">
        <FeedbackForm
          curRating={this.state.curRating}
          handleFeedbackChange={this.handleFeedbackChange}
          handleFormSubmit={this.handleFormSubmit}
          setRating={this.setRating}
        />
      </div>
    );
  }
}
export default App;
Next, add the setRating and handleFeedbackChange instance methods to the class:
setRating = (rating) => {
  this.setState({ curRating: rating });
}
handleFeedbackChange = feedback => {
  fetch('http://localhost:7071/api/autoStar', {
    method: 'post',
    body: JSON.stringify({ feedback }), 
    headers: {
      'Content-Type': 'application/json'
    }
  })
    .then(res => res.json())
    .then(json => {
      const sentimentScore = json.documents && json.documents[0] ? json.documents[0].score : 0;
      this.setState({
        curRating: Math.round((sentimentScore * 10) / 2)
      });
    });
};
setRating simply updates the curRating state item which is also passed as a prop to FeedbackForm.
handleFeedbackChange sends a request to our serverless function with the user’s feedback attached to the body as payload. When a response is returned, we expect to have a sentiment score if no error occurred. Lastly, in the response callback, we are updating the curRating state with a value between 1-5. The reason for multiplying with 10 is because CogS gives its rating between 0 and 1.
What’s Next?
You might have noticed that I did not implement what happens when you submit. I left this one up to you. It’s less difficult than what we just learned. You need to set up a database of choice (maybe Cosmos DB since it integrates well with Functions) and create one more function that the create request will be sent to.
If you get stuck, you can take a look at the GitHub repo which has the completed project. You can also have a look at the final demo to play with and see how things are expected to works.
I will strongly suggest you take a look at the Mojifier project. It’s a project that uses CogS to suggest emojis based on facial expressions. More like what you learned here but with images and faces.