Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C++

Madcap Idea 11: Finishing the ‘View Rating’ Page

5.00/5 (3 votes)
28 Sep 2017CPOL3 min read 3.1K  
Finishing the 'view rating' page

Last Time

Last time, we looked at creating a reactive Kafka publisher that would push out a Rating from a REST call (which will eventually come from the completion of a job). The last post also used some akka streams, and how we can use a back off supervisor. In a nutshell, this post will build on the Rating stream/Kafka KTable storage we have set up to date where we will actually create the React/Play framework endpoint to wire up the “View Rating” page with the Rating data that has been pushed through the stream processing so far.

Preamble

Just as a reminder, this is part of my ongoing set of posts which I talk about here, where we will be building up to a point where we have a full app using lots of different stuff, such as these:

  • WebPack
  • React.js
  • React Router
  • TypeScript
  • Babel.js
  • Akka
  • Scala
  • Play (Scala Http Stack)
  • MySql
  • SBT
  • Kafka
  • Kafka Streams

Ok, so now that we have the introductions out of the way, let's crack on with what we want to cover in this post.

Where is the Code?

As usual, the code is on GitHub here.

What Is This Post All About?

As stated above, this post largely boils down to the following things:

  • Create an endpoint (Façade over existing Kafka stream Akka Http endpoint) to expose the previously submitted Rating data
  • Make the “View Rating” page work with the retrieved data

Play Back End Changes

This section shows the changes to the play backend API code.

Rating Endpoint Façade

As we stated 2 posts ago, we created an Akka Http REST endpoint to serve up the combined Rating(s) that have been pushed through the Kafka stream processing rating topic. However, we have this Play framework API which we use for all other REST endpoints. So I have chosen to create a façade endpoint in the Play backend that will simply call out to the existing Akka Http endpoint. Keeping all the traffic in one place is a nice thing if you ask me. So let's look at this play code to do this.

New Route

We obviously need a new route, which is as follows:

Scala
GET  /rating/byemail  controllers.RatingController.ratingByEmail()

Controller Action

To serve this new route, we need a new Action in the RatingController. This is shown below:

Scala
package controllers

import javax.inject.Inject

import Actors.Rating.RatingProducerActor
import Entities.RatingJsonFormatters._
import Entities._
import akka.actor.{ActorSystem, OneForOneStrategy, Props, SupervisorStrategy}
import akka.pattern.{Backoff, BackoffSupervisor}
import akka.stream.{ActorMaterializer, ActorMaterializerSettings, Supervision}
import play.api.libs.json._
import play.api.libs.json.Json
import play.api.libs.json.Format
import play.api.libs.json.JsSuccess
import play.api.libs.json.Writes
import play.api.libs.ws._
import play.api.mvc.{Action, Controller}
import utils.{Errors, Settings}

import scala.concurrent.{ExecutionContext, Future}
import scala.util.Random
import scala.concurrent.duration._

class RatingController @Inject()
(
  implicit actorSystem: ActorSystem,
  ec: ExecutionContext,
  ws: WSClient
) extends Controller
{

  def ratingByEmail = Action.async { request =>

    val email = request.getQueryString("email")
    email match {
      case Some(emailAddress) => {
        val url = s"http://${Settings.ratingRestApiHostName}:
                  ${Settings.ratingRestApiPort}/ratingByEmail?email=${emailAddress}"
        ws.url(url).get().map {
          response => (response.json).validate[List[Rating]]
        }.map(x => Ok(Json.toJson(x.get)))
      }
      case None => {
        Future.successful(BadRequest(
          "ratingByEmail endpoint MUST be supplied with a non empty 'email' query string value"))
      }
    }
  }
}

The main thing to note here is:

  • We use the play ws (web services) library to issues a GET request against the existing Akka Http endpoint thus creating our façade.
  • We are still using Future to make it nice an async

React Front End Changes

This section shows the changes to the React frontend code.

React “View Rating” Page

This is the final result for the “View Rating” react page. I think it's all fairly self explanatory. I guess the only bit that really of any note is that we use lodash _.sumBy(..) to do the summing up of the Ratings for this user to create an overall rating. The rest is standard jQuery/react stuff.

JavaScript
import * as React from "react";
import * as ReactDOM from "react-dom";
import * as _ from "lodash";
import { OkDialog } from "./components/OkDialog";
import 'bootstrap/dist/css/bootstrap.css';
import
{
    Well,
    Grid,
    Row,
    Col,
    Label,
    ButtonInput
} from "react-bootstrap";

import { AuthService } from "./services/AuthService";

import { hashHistory  } from 'react-router';



class Rating {
    fromEmail: string
    toEmail: string
    score: number

    constructor(fromEmail, toEmail, score) {
        this.fromEmail = fromEmail;
        this.toEmail = toEmail;
        this.score = score;
    }
}

export interface ViewRatingState {
    ratings: Array<Rating>;
    overallRating: number;
    okDialogOpen: boolean;
    okDialogKey: number;
    okDialogHeaderText: string;
    okDialogBodyText: string;
    wasSuccessful: boolean;
}

export class ViewRating extends React.Component<undefined, ViewRatingState> {

    private _authService: AuthService;

    constructor(props: any) {
        super(props);
        this._authService = props.route.authService;
        if (!this._authService.isAuthenticated()) {
            hashHistory.push('/');
        }
        this.state = {
            overallRating: 0,
            ratings: Array(),
            okDialogHeaderText: '',
            okDialogBodyText: '',
            okDialogOpen: false,
            okDialogKey: 0,
            wasSuccessful: false
        };
    }   

    loadRatingsFromServer = () => {

        var self = this;
        var currentUserEmail = this._authService.userEmail();

        $.ajax({
            type: 'GET',
            url: 'rating/byemail?email=' + currentUserEmail,
            contentType: "application/json; charset=utf-8",
            dataType: 'json'
        })
        .done(function (jdata, textStatus, jqXHR) {

            console.log("result of GET rating/byemail");
            console.log(jqXHR.responseText);
            let ratingsObtained = JSON.parse(jqXHR.responseText);
            self.setState(
                {
                    overallRating: _.sumBy(ratingsObtained, 'score'),
                    ratings: ratingsObtained
                });
        })
        .fail(function (jqXHR, textStatus, errorThrown) {
            self.setState(
                {
                    okDialogHeaderText: 'Error',
                    okDialogBodyText: 'Could not load Ratings',
                    okDialogOpen: true,
                    okDialogKey: Math.random()
                });
        });        
    }
    
    componentDidMount() {
        this.loadRatingsFromServer();
    }

    render() {

        var rowComponents = this.generateRows();

        return (
            <Well className="outer-well">
                    <Grid>
                        <Row className="show-grid">
                            <Col xs={6} md={6}>
                                <div>
                                <h4>YOUR OVERALL RATING <Label>{this.state.overallRating}</Label></h4>
                                </div>
                            </Col>
                        </Row>
                        <Row className="show-grid">
                            <Col xs={10} md={6}>
                                <h6>The finer details of your ratings are shown below</h6>
                            </Col>
                        </Row>
                        <Row className="show-grid">
                            <Col xs={10} md={6}>
                                <div className="table-responsive">
                                    <table className=
                                     "table table-striped table-bordered table-condensed factTable">
                                        <thead>
                                            <tr>
                                                <th>Rated By</th>
                                                <th>Rating Given</th>
                                            </tr>
                                        </thead>
                                        <tbody>
                                            {rowComponents} 
                                        </tbody>
                                    </table>
                                </div>
                            </Col>
                        </Row>
                        <Row className="show-grid">
                            <span>
                                <OkDialog
                                    open= {this.state.okDialogOpen}
                                    okCallBack= {this._okDialogCallBack}
                                    headerText={this.state.okDialogHeaderText}
                                    bodyText={this.state.okDialogBodyText}
                                    key={this.state.okDialogKey}/>
                            </span>
                        </Row>
                    </Grid>
            </Well>
        )
    }

    _okDialogCallBack = () => {
        this.setState(
            {
                okDialogOpen: false
            });
    }

    generateRows = () => {
        return this.state.ratings.map(function (item) {
            return  <tr key={item.fromEmail}>
                        <td>{item.fromEmail}</td>
                        <td>{item.score}</td>
                    </tr>;
        });
    } 
}

And with that, the “View Rating” page is actually done. This was a short post for a change, which is nice.

Conclusion

The previous set of posts have made this one very easy to do. It's just standard React/REST/Play stuff. So this one has been fairly easy to do.

Next Time

The more interesting stuff is still to come where we push out new jobs onto a new Kafka stream topic.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)