Fetch Five Star Skill Rating from getSkillList API in SUSI.AI Android

SUSI.AI had a thumbs up/down rating system till now, which has now been replaced by a five star skill rating system. Now, the user is allowed to rate the skill based on a five star rating system. The UI components include a rating bar and below the rating bar is a section that displays the skill rating statistics – total number of ratings, average rating and a graph showing the percentage of users who rated the skill with five stars, four stars and so on.

SUSI.AI Skills are rules that are defined in SUSI Skill Data repo which are basically the processed responses that SUSI returns to the user queries. When a user queries something from the SUSI Android app, a query to SUSI Server is made which in turn fetches data from SUSI Skill Data and returns a JSON response to the app. Similarly, to get skill ratings, a call to the ‘/cms/getSkillList.json’ API is made. In this API, the server checks the SUSI Skill Data repo for the skills and returns a JSON response consisting of all the required information like skill name, author name, description, ratings, etc. to the app. Then, this JSON response is parsed to extract individual fields to display the appropriate information in the skill details screen of the app.

API Information

The endpoint to fetch skills is ‘/cms/getSkillList.json’
The endpoints takes three parameters as input –

  • model – It tells the model to which the skill belongs. The default value is set to general.
  • group – It tells the group(category) to which the skill belongs. The default value is set to All.
  • language – It tells the language to which the skill belongs. The default value is set to en.

Since all skills have to be fetched, this API is called for every group individually. For instance, call “https://api.susi.ai/cms/getSkillList.json?group=Knowledge” to get all skills in group “Knowledge”. Similarly, call for other groups.

Here is a sample response of a skill named ‘Capital’ from the group Knowledge :

"capital": {
      "model": "general",
      "group": "Knowledge",
      "language": "en",
      "developer_privacy_policy": null,
      "descriptions": "A skill to tell user about capital of any country.",
      "image": "images/capital.png",
      "author": "chashmeet singh",
      "author_url": "https://github.com/chashmeetsingh",
      "skill_name": "Capital",
      "terms_of_use": null,
      "dynamic_content": true,
      "examples": ["What is the capital of India?"],
      "skill_rating": {
        "negative": "0",
        "positive": "4",
        "feedback_count" : 0,
        "stars": {
          "one_star": 0,
          "four_star": 1,
          "five_star": 0,
          "total_star": 1,
          "three_star": 0,
          "avg_star": 4,
          "two_star": 0
        }
      },
      "creationTime": "2018-03-17T17:11:59Z",
      "lastAccessTime": "2018-06-06T00:46:22Z",
      "lastModifiedTime": "2018-03-17T17:11:59Z"
    },


It consists of all details about the skill called ‘Capital’:

  1. Model (model)
  2. Group (group)
  3. Language (language)
  4. Developer Privacy Policy (developer_privacy_policy)
  5. Description (descriptions)
  6. Image (image)
  7. Author (author)
  8. Author URL (author_url)
  9. Skill name (skill_name)
  10. Terms of Use (terms_of_use)
  11. Content Type (dynamic_content)
  12. Examples (examples)
  13. Skill Rating (skill_rating)
  14. Creation Time (creationTime)
  15. Last Access Time (lastAccessTime)
  16. Last Modified Time (lastModifiedTime)

From among all this information, the information of interest for this blog is Skill Rating. This blog mainly deals with showing how to parse the JSON response to get the skill rating star values, so as to display the actual data in the skill rating graph.

A request to the getSkillList API is made for each group using the GET method.

@GET("/cms/getSkillList.json")
Call<ListSkillsResponse> fetchListSkills(@Query("group") String groups);

It returns a JSON response consisting of all the aforementioned information. Now, to parse the JSON response, do the following :

  1. Add a response for the response received as a result of API call. ListSkillsResponse contains two objects – group and skills.
    This blog is about getting the skill rating, so let us proceed with parsing the required response. The skills object contains the skill data that we need. Hence, next a SkillData class is created.

    class ListSkillsResponse {
       val group: String = "Knowledge"
       val skillMap: Map<String, SkillData> = HashMap()
    }
  2. Now, add the SkillData class. This class defines the response that we saw for ‘Capital’ skill above. It contains skill name, author, skill rating and so on.

    class SkillData : Serializable {
       var image: String = ""
       @SerializedName("author_url")
       @Expose
       var authorUrl: String = ""
       var examples: List<String> = ArrayList()
       @SerializedName("developer_privacy_policy")
       @Expose
       var developerPrivacyPolicy: String = ""
       var author: String = ""
       @SerializedName("skill_name")
       @Expose
       var skillName: String = ""
       @SerializedName("dynamic_content")
       @Expose
       var dynamicContent: Boolean? = null
       @SerializedName("terms_of_use")
       @Expose
       var termsOfUse: String = ""
       var descriptions: String = ""
       @SerializedName("skill_rating")
       @Expose
       var skillRating: SkillRating? = null
    }
    
  3. Now, add the SkillRating class. As what is required is the skill rating, narrowing down to the skill_rating object. The skill_rating object contains the actual rating for each skill i.e. the stars values. So, this files defines the response for the skill_rating object.

    class SkillRating : Serializable {
       var stars: Stars? = null
    }
    
  4. Further, add a Stars class. Ultimately, the values that are needed are the number of users who rated a skill at five stars, four stars and so on and also the total number of users and the average rating. Thus, this file contains the values inside the ‘stars’ object.

    class Stars : Serializable {
       @SerializedName("one_star")
       @Expose
       var oneStar: String? = null
       @SerializedName("two_star")
       @Expose
       var twoStar: String? = null
       @SerializedName("three_star")
       @Expose
       var threeStar: String? = null
       @SerializedName("four_star")
       @Expose
       var fourStar: String? = null
       @SerializedName("five_star")
       @Expose
       var fiveStar: String? = null
       @SerializedName("total_star")
       @Expose
       var totalStar: String? = null
       @SerializedName("avg_star")
       @Expose
       var averageStar: String? = null
    }
    

Now, the parsing is all done. It is time to use these values to plot the skill rating graph and complete the section displaying the five star skill rating.

To plot these values on the skill rating graph refer to the blog on plotting horizontal bar graph using MPAndroid Chart library. In step 5 of the linked blog, replace the second parameter to the BarEntry constructor by the actual values obtained by parsing.

Here is how we do it.

  • To get the total number of ratings
val  totalNumberofRatings: Int = skillData.skillRating?.stars?.totalStars

 

  • To get the average rating
val averageRating: Float = skillData.skillRating?.stars?.averageStars

 

  • To get number of users who rated the skill at five stars
val fiveStarUsers: Int = skillData.skillRating?.stars?.fiveStar

Similarly, get the number of users for fourStar, threeStar, twoStar and oneStar.

Note : If the totalNumberOfRatings equals to zero, then the skill is unrated. In this case, display a message informing the user that the skill is unrated instead of plotting the graph.

Now, as the graph shows the percentage of users who rated the skill at a particular number of stars, calculate the percentage of users corresponding to each rating, parse the result to Float and place it as the second parameter to the BarEntry constructor  as follows :

 entries.add(BarEntry(4f, (fiveStarUsers!!.toFloat() / totalUsers) * 100f)))

Similarly, replace the values for all five entries. Finally, add the total ratings and average rating section and display the detailed skill rating statistics for each skill, as in the following figure.

Resources

 

 

Continue Reading

Add Info on Skill Usage Distribution for all Skills by an Author in SUSI.AI

SUSI Skill CMS has a dashboard option available at the /dashboard route which displays several data for the logged in user as the skills created by the user and the ratings the user has provided to all the skills, since we have a skill usage section available on all skill pages which depicts the skill usage count for the past week in a line chart. Skill creators didn’t have a functionality to see the skill usage distribution on their skills which can provide some useful insight like how some of the skills they created are performing in comparison to the others so I developed a ‘My Analytics’ section in the dashboard page and displayed the skill usage distribution in the form of pie chart among the skills created by the logged in users.

About the API

An API is developed at the server so from the client we call this API to fetch data from the server and plug this data into the chart we wish to render.

Endpoint :

/cms/getSkillsByAuthor.json?author_email={email}

 

Parameters :

Email ID which is taken from the cookies since it is stored there once the user logs in.

Sample API call :

/cms/getSkillsByAuthor.json?author_email[email protected]

Fetching the data for the component

We first create a separate My Analytics component and require it in the dashboard and make an AJAX call to the appropriate endpoint inside a loadSkillsUsage function which is called inside the componentDidMount hook after which the server returns raw data in the form of JSON. We then pass the response into a saveUsageData function to parse the data for our use and save it to the application state.

loadSKillsUsage = () => {
 let url =
        urls.API_URL +
`/cms/getSkillsByAuthor.json?author_email=${cookies.get('emailId')}`;
 let self = this;
 $.ajax({
        url: url,
        dataType: 'jsonp',
        jsonp: 'callback',
        crossDomain: true,
        success: function(data) {
         self.saveUsageData(data.author_skills || []);
         ...
        },
        error: function(err) {
         ...
        },
 });
};

 

Set the application state with the received data which the pie chart component will use as it’s data source.

saveUsageData = data => {
 const skillUsage = data.map(skill => {
        let dataObject = {};
        dataObject.skill_name = skill.skill_name;
        dataObject.usage_count = skill.usage_count || 0;
        return dataObject;
 });
 this.setState({ skillUsage });
};

Implementing the UI

We create a separate ‘My Analytics’ component which is imported into the dashboard component to make the code cleaner and manageable. So inside the My analytics component, we fetch the data from the server as depicted above and after that, we render the pie chart component after importing from the recharts library.

Importing the pie chart components from the recharts library.

import { Legend, PieChart, Pie, Sector, Cell, ResponsiveContainer } from 'recharts';

 

Rendering the pie chart component while supplying appropriate props most important of which is the data prop which will be used in the chart and that data is available in the application state as saved earlier. We also have other styling props and a function which is triggered when hovering over cells of the pie chart to represent the data of the hovered cell. We also supply the appropriate nameKey and dataKey props as per the data format available in the state.

<ResponsiveContainer width={600} height={350}>
 <PieChart>
        <Pie
         activeIndex={this.state.activePieIndex}
         activeShape={renderActiveShape}
         data={this.state.skillUsage}
         cx={300}
         cy={175}
         innerRadius={80}
         nameKey="skill_name"
         dataKey="usage_count"
         outerRadius={120}
         fill="#8884d8"
         onMouseEnter={this.onPieEnter}
        >

       ...
         </Pie>
        <Legend wrapperStyle={{ position: 'relative' }} />
 </PieChart>
</ResponsiveContainer>

 

Configuring color for each Cell in the pie so it looks more interactive and we have distinguished colors for all devices.

{this.state.skillUsage.map((entry, index) => (
 <Cell
   key={index}
   fill={
     [
       '#0088FE',
       '#00C49F',
       '#FFBB28',
       '#FF8042',
       '#EA4335',
     ][index % 5]
   }
 />
))}

 

Rendering the Pie only when data is available in props so we don’t end up rendering a blank chart which obviously won’t look good.

{
 this.state.skillUsage !== [] ? (
   ...
 ): ''
}

Resources

Continue Reading

Implementing feature to filter skills by average customer review

SUSI Skill CMS showcases all the skills on the index page but lacks the functionality to refine skills according to average customer review which is a much-needed feature since some users may only want to try skills which have at least a minimum rating so they can know instantly which skills are performing well in comparison to others. Thus, we implement several star inputs on the sidebar to select skills which have ratings greater than or equal to the selected rating input.

Implementing the UI

Add a menu to the sidebar at the bottom of all categories and display ‘Refine by’ submenu text to denote the section.

<Menu desktop={true} disableAutoFocus={true}>
 <Subheader style={{ fontWeight: 'bold' }}>Refine by</Subheader>
 <h4 style={{ marginLeft: '12px', marginBottom: '4px' }}>
   Avg. Customer Review
 </h4>

...

 

Display rating options to the user by displaying a list of Ratings component imported from react-ratings-declarative, these are to be displayed for all ratings say four stars and above, three stars and above and so on, i.e.

<div
 style={styles.singleRating}
 onClick={() => this.handleRatingRefine(4)}
>
 <Ratings
        rating={4}
        widgetRatedColors="#ffbb28"
        widgetDimensions="20px"
        widgetSpacings="0px"
 >
        <Ratings.Widget />
        <Ratings.Widget />
        <Ratings.Widget />
        <Ratings.Widget />
        <Ratings.Widget />
 </Ratings>
 <div
        style={styles.ratingLabel}
        className={this.state.rating_refine === 4 ? 'bold' : ''}
 >
        & Up
 </div>
</div>

 

We add some styling and attach an onClick listener on each rating component which will handle the refining of skills according to the rating clicked, the idea behind this is to save the rating for the clicked option to the component state and re-render the skill cards

handleRatingRefine = rating => {
 this.setState(
        {
         rating_refine: rating,
        },
        this.loadCards(),
 );
};

 

When the component state is successfully set loadCards function as a callback is called which re-renders the cards by applying filter over the skills which match the average rating criteria which we just set.

if (self.state.rating_refine) {
 data.filteredData = data.filteredData.filter(
        skill =>
         skill.skill_rating.stars.avg_star >= self.state.rating_refine,
 );
}

Displaying a button to clear any refinements made

Once the skills are refined a button is needed to clear any refinements made. Initially when no refinements are made the rating_refine in the state is set to null which indicates that no refinements are made so whenever the value of that state is no null we render a button to clear the refinements or set the rating_refine state to null.

{this.state.rating_refine ? (
 <div
        className="clear-button"
        style={styles.clearButton}
        onClick={() => this.handleRatingRefine(null)}
 >
        Clear
 </div>
) : (
 ''
)}

Resources

Continue Reading

Implementing My Rating Section on the SUSI.AI Skills Dashboard

SUSI Skill CMS provides the functionality to rate the skills, therefore users rate skills they use but there isn’t any place where they can see all the skills they rated, thus a ‘My Ratings’ section was implemented on the dashboard page to view these statistics. So to see what ratings they have given to skills they can just login to the cms and navigate to /dashboard and a my ratings components is visible there which lists all the ratings the user has provided in a nice tabular format.

About the API

An API endpoint is implemented on the server which fetches the skill data for skills the user has rated which includes the skill name, stars given and the timestamp.

/cms/getProfileDetails.json?access_token=

 

So we pass the access token of the authenticated user and a JSON response is received which contains all the details as depicted below, this data is then parsed on the frontend and filled in a tabular form on the MyRatings section.

{
 "rated_skills": [
   {"amazon_shopping": {
     "stars": "1",
     "timestamp": "2018-06-10 13:05:32.295"
   }},
   {"aboutsusi": {
     "stars": "2",
     "timestamp": "2018-06-10 13:26:26.222"
   }},
   {"anagrams": {
     "stars": "3",
     "timestamp": "2018-06-10 13:25:31.195"
   }}
 ],
 "session": {"identity": {
   "type": "email",
   "name": "[email protected]",
   "anonymous": false
 }},
 "accepted": true,
 "message": "User ratings fetched."
}

Displaying the results on the web

Make a MyRatings component and render it on the dashboard component

Make an AJAX call to the API and save the returned data to the component state. First create a loadSkills function in componentDidMount which will be called just as the component is mounted to the DOM which will then fetch data from the server, extract the meaningful parts such as skill_name, skill_star and timestamp and push them to an array which in this case is ratingsData. While the data is being fetched we show a circular loader for better UX and once we receive the data we save it in the component state and turn loading to false which will replace the loading animation with the actual data.

loadSkills = () => {
 let url;
 url =
   urls.API_URL +
   '/cms/getProfileDetails.json?access_token=' +
   cookies.get('loggedIn');
 let self = this;
 let ratingsData = [];
 $.ajax({
   url: url,
   jsonpCallback: 'pxcd',
   dataType: 'jsonp',
   jsonp: 'callback',
   crossDomain: true,
   success: function(data) {
     if (data.rated_skills) {
       for (let i of data.rated_skills) {
         let skill_name = Object.keys(i)[0];
         ratingsData.push({
           skill_name: skill_name,
           skill_star: i[skill_name].stars,
           rating_timestamp: i[skill_name].timestamp,
         });
       }
       self.setState({
         ratingsData,
       });
     }
     self.setState({
       loading: false,
     });
   },
   error: function(err) {
     self.setState({
       loading: false,
       openSnackbar: true,
       msgSnackbar: "Error. Couldn't rating data.",
     });
   },
 });
};

 

Display a loading animation when the data is being fetched, we maintain a state in the component called loading which is initially true since we don’t have the data just as the component is rendered so after we receive the data we turn the loading state to false which will hide the circular loader and display the component with the data received.

{this.state.loading ? (
         <div className="center">
           <CircularProgress size={62} color="#4285f5" />
           <h4>Loading</h4>
         </div>
       ) : ( ... )
}

 

Add a table to structure the state data, since we’re using material-ui library throughout the project we use material table component from the library and populate the data received in it with 3 columns namely skill name, skill star, and timestamp.

<div className="table-wrap">
 <Table className="table-root" selectable={false}>
   <TableHeader displaySelectAll={false} adjustForCheckbox={false}>
     <TableRow>
       <TableHeaderColumn>Skill Name</TableHeaderColumn>
       <TableHeaderColumn>Rating</TableHeaderColumn>
       <TableHeaderColumn>Timestamp</TableHeaderColumn>
     </TableRow>
   </TableHeader>
   <TableBody displayRowCheckbox={false}>
     {ratingsData.map((skill, index) => {
       return (
         <TableRow key={index}>
           <TableRowColumn style={{ fontSize: '16px' }}>
             {(
               skill.skill_name.charAt(0).toUpperCase() +
               skill.skill_name.slice(1)
             ).replace(/[_-]/g, ' ')}
           </TableRowColumn>
           <TableRowColumn style={{ fontSize: '16px' }}>
             {skill.skill_star}
           </TableRowColumn>
           <TableRowColumn>
             {this.parseDate(skill.rating_timestamp)}
           </TableRowColumn>
         </TableRow>
       );
     })}
     <TableRow />
   </TableBody>
 </Table>
</div>

 

Display a message when there is no rating data available, let’s say the user has not rated any skills so it does not make sense to display an empty table and therefore we display a message instead for users to rate skills.

{ratingsData.length === 0 &&
!this.state.loading && (
 <div>
   <div className="center">
     <br />
     <h2>
       You have not rated any skill, go to{' '}
       <Link to="/">SUSI Skills Explorer</Link> and rate.
     </h2>
     <br />
   </div>
 </div>
)}

 

Import this component in the dashboard component and render it below My Skills

import MyRatings from './MyRatings';

 

Render the imported MyRatings component on a separate card from material-ui on the dashboard.

<Paper
 style={styles.paperStyle}
 className="botBuilder-page-card"
 zDepth={1}
>
 <h1 className="center">My Ratings</h1>
 <MyRatings />
</Paper>

Resources

Continue Reading

Plot a Horizontal Bar Graph using MPAndroidChart Library in SUSI.AI Android App

Graphs and charts provide a visual representation of the data. They provide a clearer and quicker understanding of the impact of certain statistics. Thus, SUSI.AI Android app makes use of bar charts to display statistics related to user ratings for SUSI skills. This blog guides through the steps to create a Horizontal Bar Chart, using MPAndroidChart library, that has been used in the SUSI.AI Android app skill details page to display the five star skill rating by the users.

On vertical axis : Labels of the rating shown
On horizontal axis : Percentage of total number
of users who rated the skill with the corresponding
number of stars on the vertical axis

Step – 1 : Add the required dependencies to your build.gradle.

(a) Project level build.gradle

allprojects {
    repositories {
        maven { url 'https://jitpack.io' }
    }
}

(b) App level build.gradle

dependencies {
    implementation 'com.github.PhilJay:MPAndroidChart:v3.0.3'
}

 

Step – 2 : Create an XML layout.

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
   xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
   android:layout_height="match_parent">

    <!-- Add a Horizontal Bar Chart using MPAndroidChart library -->
    <com.github.mikephil.charting.charts.HorizontalBarChart
       android:id="@+id/skill_rating_chart"
       android:layout_width="match_parent"
       android:layout_height="match_parent" />

</android.support.constraint.ConstraintLayout>

 

Step – 3 : Create an Activity and initialize the Horizontal Bar Chart.

class MainActivity : Activity {

   lateinit var skillRatingChart : HorizontalBarChart

   override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
       setContentView(R.layout.chart)

       setSkillGraph( )

   }
}

 

Step – 4 : Create a method in your MainActivity to set up the basic properties and the axes.

/**
* Set up the axes along with other necessary details for the horizontal bar chart.
*/
fun setSkillGraph(){
   skillRatingChart = skill_rating_chart              //skill_rating_chart is the id of the XML layout

   skillRatingChart.setDrawBarShadow(false)
   val description = Description()
   description.text = ""
   skillRatingChart.description = description
   skillRatingChart.legend.setEnabled(false)
   skillRatingChart.setPinchZoom(false)
   skillRatingChart.setDrawValueAboveBar(false)

   //Display the axis on the left (contains the labels 1*, 2* and so on)
   val xAxis = skillRatingChart.getXAxis()
   xAxis.setDrawGridLines(false)
   xAxis.setPosition(XAxis.XAxisPosition.BOTTOM)
   xAxis.setEnabled(true)
   xAxis.setDrawAxisLine(false)


   val yLeft = skillRatingChart.axisLeft

//Set the minimum and maximum bar lengths as per the values that they represent
   yLeft.axisMaximum = 100f
   yLeft.axisMinimum = 0f
   yLeft.isEnabled = false

   //Set label count to 5 as we are displaying 5 star rating
   xAxis.setLabelCount(5)

//Now add the labels to be added on the vertical axis
   val values = arrayOf("1 *", "2 *", "3 *", "4 *", "5 *")
   xAxis.valueFormatter = XAxisValueFormatter(values)        

   val yRight = skillRatingChart.axisRight
   yRight.setDrawAxisLine(true)
   yRight.setDrawGridLines(false)
   yRight.isEnabled = false

   //Set bar entries and add necessary formatting
   setGraphData()

   //Add animation to the graph
   skillRatingChart.animateY(2000)
}


Here is the XAxisValueFormatter class that is used to add the custom labels to the vertical axis :

public class XAxisValueFormatter implements IAxisValueFormatter {

   private String[] values;

   public XAxisValueFormatter(String[] values) {
       this.values = values;
   }

   @Override
   public String getFormattedValue(float value, AxisBase axis) {
       // "value" represents the position of the label on the axis (x or y)
       return this.values[(int) value];
   }

}

 

Step – 5 : Set the bar entries.

/**
* Set the bar entries i.e. the percentage of users who rated the skill with
* a certain number of stars.
*
* Set the colors for different bars and the bar width of the bars.
*/
private fun setGraphData() {

   //Add a list of bar entries
   val entries = ArrayList<BarEntry>()
   entries.add(BarEntry(0f, 27f))
   entries.add(BarEntry(1f, 45f))
   entries.add(BarEntry(2f, 65f))
   entries.add(BarEntry(3f, 77f))
   entries.add(BarEntry(4f, 93f))

  //Note : These entries can be replaced by real-time data, say, from an API

  ......

}

 

Step – 6 : Now create a BarDataSet.

To display the data in a bar chart, you need to initialize a
BarDataSet instance. BarDataSet is the Subclass of DataSet class. Now, initialize the BarDataSet and pass the argument as an ArrayList of BarEntry object.

val barDataSet = BarDataSet(entries, "Bar Data Set")

 

Step – 7 : Assign different colors to the bars (as required).

private fun setGraphData() {
    .....

   //Set the colors for bars with first color for 1*, second for 2* and so on
      barDataSet.setColors(
              ContextCompat.getColor(skillRatingChart.context, R.color.md_red_500),
              ContextCompat.getColor(skillRatingChart.context, R.color.md_deep_orange_400),
              ContextCompat.getColor(skillRatingChart.context, R.color.md_yellow_A700),
              ContextCompat.getColor(skillRatingChart.context, R.color.md_green_700),
              ContextCompat.getColor(skillRatingChart.context, R.color.md_indigo_700)

   .....
)


Step – 8 : Populate data into Bar Chart.

To load the data into Bar Chart, you need to initialize a
BarData object  with bardataset. This BarData object is then passed into setData() method to load Bar Chart with data.

//Set bar shadows
   skillRatingChart.setDrawBarShadow(true)
   barDataSet.barShadowColor = Color.argb(40, 150, 150, 150)
   val data = BarData(barDataSet)

   //Set the bar width
   //Note : To increase the spacing between the bars set the value of barWidth to < 1f
   data.barWidth = 0.9f

   //Finally set the data and refresh the graph
   skillRatingChart.data = data
   skillRatingChart.invalidate()
}


Your Horizontal Bar Chart is now ready.
Note: You can format the labels as per your need and requirement with the help of XAxisValueFormatter.

Resources

Continue Reading

Implementing a device wise usage section on the skill page

SUSI Skill CMS showcases all the skills on the index page as skill cards and users can visit any skill page for any skill by clicking on any of these cards, skill pages for each skill hold some interesting metrics like rating, usage data, country wise usage data etc. But since SUSI runs on different devices so we need something to distribute and showcase how a skill is performing on each device so we implemented a pie chart for visualization of device wise usage data.

About the API

An API is developed at the server so from the client we call this API to fetch data from the server and plug this data into the chart we wish to render.

Endpoint :

/cms/getDeviceWiseSkillUsage.json

 

Parameters :

  • model
  • group
  • language
  • skill

Sample API call :

/cms/getDeviceWiseSkillUsage.json?model=general&group=Knowledge&language=en&skill=ceo

 

Response

{
 "skill_usage": [
   {
     "count": 3,
     "device_type": "Others"
   },
   {
     "count": 39,
     "device_type": "Android"
   },
   {
     "count": 1,
     "device_type": "Web Client"
   }
 ],
 "session": {"identity": {
   "type": "host",
   "name": "162.158.166.37_35449f1b",
   "anonymous": true
 }},
 "skill_name": "news",
 "accepted": true,
 "message": "Device wise skill usage fetched"
}

Fetching the data for the chart

Setting the URL to fetch data from, this URL will be used to make the AJAX call.

let deviceUsageUrl = `${urls.API_URL}/cms/getSkillsByAuthor.json?author_email=${cookies.get('emailId')}`;
deviceUsageUrl = deviceUsageUrl + '?model=' + modelValue + '&group=' + this.groupValue + '&language=' + this.languageValue + '&skill=' + this.name;

 

Make an ajax call to extract data from the response and call a function which saves the data to the application state, this data will later be used to render our chart we wish to render.

$.ajax({
 url: deviceUsageUrl,
 ...
 success: function(data) {
   if (data.skill_usage) {
     self.saveDeviceUsageData(data.skill_usage);
   }
 },
 error: function(e) {
   self.saveDeviceUsageData();
 },
});

 

Set the application state with the received data which the pie chart component will use as it’s data source.

saveDeviceUsageData = (device_usage_data = []) => {
 this.setState({
   device_usage_data,
 });
};

Implementing the UI

We already have a card component for device usage section so we append our device wise usage section to this already present card. We fetch the data in the skillListing component and pass that data as props to the skill usage component so using data from the received props we render our pie chart.

Importing the needed components from recharts library.

import { Tooltip, Legend, PieChart, Pie, Sector, Cell } from 'recharts';

 

Rendering the Piechart component with appropriate props, the data props is the most important which is taken from the application state which we saved earlier.

<PieChart width={600} height={350}>
 <Pie
   data={this.props.device_usage_data}
   nameKey="device_type"
   dataKey="count"
   onMouseEnter={this.onPieEnter}
   ...
 >
   ...
 </Pie>
 <Legend wrapperStyle={{ position: 'relative' }} />
</PieChart>

 

Configuring color for each Cell in the pie so it looks more interactive and we have distinguished colors for all devices.

{this.props.device_usage_data.map((entry, index) => (
 <Cell
   key={index}
   fill={
     [
       '#0088FE',
       '#00C49F',
       '#FFBB28',
       '#FF8042',
       '#EA4335',
     ][index % 5]
   }
 />
))}

 

Rendering the Pie only when data is available in props so we don’t end up rendering a blank chart which obviously won’t look good.

{
 this.props.device_usage_data !== [] ? (
   ...
 ): ''
}

 

Resources

  • Swizec Teller, Rendering a pie chart using react and d3, URL
  • Pie chart example from recharts, URL
Continue Reading

Implementing a skill rating over time graph section in SUSI Skill CMS

In SUSI.AI skill ratings is an invaluable aspect which greatly helps the users to know which skills are performing better than the rest and are more popular than the others. A robust skill rating system for the skills was developed recently which allows the users to rate skills as per their experience and thus data like average rating, total number of ratings is available but there was no provision to see the rating history or how the skills rating has changed over time, this could be an important aspect for users or developers to know what changes to the skill made it less/more popular. An API is developed at the server to retrieve the ratings over time data, we can use these details to render attractive components for a better visual understanding of how the skill is performing and get statistics like how the skill’s rating has changed over time.

About the API

Endpoint : /cms/getRatingsOverTime.json

Parameters

  • model
  • group
  • language
  • skill

After consuming these params the API will return the number of times a skill is called along with

the date on which it is called. We use that data as an input for the line chart component that we want to render. 

Fetching data from the server and storing in the application state

Make an AJAX call to the server to fetch the data from the URL which holds the server endpoint, on successfully receiving the data we do some formatting with the timestamp that comes along the data to make it more convenient to understand and then we call a saveRatingOverTime function which saves the data received from the server to the application state.

let ratingOverTimeUrl = `${urls.API_URL}/cms/getRatingsOverTime.json`;
skillUsageUrl = skillUsageUrl + '?model=' + modelValue + '&group=' + this.groupValue + '&language=' + this.languageValue + '&skill=' + this.name;
// Fetch the skill ratings over time
$.ajax({
 url: ratingOverTimeUrl,
 dataType: 'json',
 crossDomain: true,
 success: function(data) {
        if (data.ratings_over_time) {
         const ratingData = data.ratings_over_time.map(item => {
             return {
               rating: item.rating,
               count: item.count,
               timestamp: parseDate(item.timestamp)
                 .split(' ')
                 .slice(2, 4)
                 .join(' '),
                 };
           });
         self.saveRatingOverTime(ratingData);
        }
 },
 error: function(e) {
        console.log(e);
        self.saveRatingOverTime();
 },
});

 

Save the skill usage details in the component state.

// Save ratings over time data in the component state
saveRatingOverTime = (ratings_over_time = []) => {
 this.setState({
        ratings_over_time,
 });
};

 

Send the received data as props to the Skill Rating component and render it.

<SkillUsageCard skill_usage={this.state.skill_usage} /> 

Implementing the UI

Importing the packages for rendering the chart in the Skill Ratings component.

import { XAxis, YAxis, Tooltip, LineChart, Line, Legend, ResponsiveContainer } from 'recharts';

 

Display a small heading for the section in the ratings card and Render a Responsive container component which will form a parent component for out Chart which will be rendered when the ratings over time data received in the props is not empty.

<div className="sub-title" style={{ alignSelf: 'flex-start' }}>
 Rating over time
</div>
{this.props.ratings_over_time.length ? (
 <div>
        <ResponsiveContainer
         height={300}
         width={
           window.innerWidth < 660
             ? this.state.width
             : this.state.width * 1.5
         }
         debounce={1}
        >
         ...
        </ResponsiveContainer>
 </div>
) : (
 <div>No ratings data over time is present</div>
)}

 

Render a LineChart and supply data from the data prop received from the Skill Listing component, add X-Axis and Y-Axis by supplying corresponding dataKey props depending on the data received, Add a tooltip to describe points on the line chart and a legend which describes the lines, After that we have a Line component which depicts the change in ratings over time on the chart.

<LineChart
 data={this.props.ratings_over_time}
 margin={{
        top: 5,
        right: 30,
        left: 20,
        bottom: 5,
 }}
>
 <XAxis dataKey="timestamp" padding={{ right: 20 }} />
 <YAxis dataKey="rating" />
 <Tooltip wrapperStyle={{ height: '60px' }} />
 <Legend />
 <Line
        name="Average rating"
        type="monotone"
        dataKey="rating"
        stroke="#82ca9d"
        activeDot={{ r: 8 }}
 />
</LineChart>

 

So I hope after going through this blog it is more clear how the ratings over time section is implemented in the Skill CMS.

Resources

 

Continue Reading

Appending a rating section of SUSI SKILL CMS to the skill page

Ratings is an essential component of skills which provides the developers an insight into how the SUSI Skill is functioning and how to further improve it which ultimately leads to great user experience so this was the motivation to allow users to be able to rate skills, once the rating system is implemented we need to show some statistics like average rating, total users who have rated the skills etc on the skill page for each skill, and this will also enable users to get top rated skills and thus users can get to use the best skills rated by the community. So we implemented a rating section to SUSI SKILL CMS

Implementation

Server –

  1. Two APIs were implemented by the analytics team on the server which allows the user to rate skill and fetch rating for each skill.
    1. To rate the skill (Sample)

      /cms/getSkillRating.json?model=general&group=Knowledge&language=en&skill=aboutsusi&callback=pc&_=1525446551181
      

       

    2. To get the ratings data for any skill (Sample)

      /cms/fiveStarRateSkill.json?model=general&group=Knowledge&language=en&skill=aboutsusi&stars=3&callback=p&_=1526813916145
      

       

CMS –

    1. When visiting any skill make an ajax call to the server to fetch the skill data for the visited skill. The call takes in the URL from which we have to fetch data from and of course a datatype which is jsonp since server returns data in the JSON format, when the request succeeds we save the received rating to the application state and in the case or any errors we log the error for developers to debug.

// Fetch ratings for the visited skill
           $.ajax({
               url: skillRatingUrl,
               jsonpCallback: 'pc',
               dataType: 'jsonp',
               jsonp: 'callback',
               crossDomain: true,
               success: function (data) {
                   self.saveSkillRatings(data.skill_rating)
               },
               error: function(e) {
                   console.log(e);
               }
           });
    1. Save the fetched data to the application state, this data saved in the state will be used in several components and graph present on the ratings section.

saveSkillRatings = (skill_ratings) => {
    this.setState({
        skill_ratings: data
    })
 }
  1. Plug in the data received to the Bar chart component to visualize how ratings are divide.
    1. Import the required components on the top of the file from the recharts library which provides us with several interactive charts.
    2. import {BarChart, Cell, LabelList, Bar, XAxis, YAxis, Tooltip} from 'recharts';
      
        1. Plug the data to the BarChart component through the data prop and render them to the page, this data is coming from the application state which we saved earlier. After that we define keys and styling for the X-Axis and Y-Axis and an interactive tooltip which shows up on hovering over any bar of that chart. We have 5 bars on the chart for each star rating all of different and unique colors and labels which appear on the right of each bar.

      <div className="rating-bar-chart">
         <BarChart layout='vertical' width={400} height={250}
              data={this.state.skill_ratings}>
              <XAxis type="number" padding={{right: 20}} />
              <YAxis dataKey="name" type="category"/>
              <Tooltip
      
                   wrapperStyle={{height: '60px'}} />
              <Bar name="Skill Rating" dataKey="value" fill="#8884d8">
                   <LabelList dataKey="value" position="right" />
                       {
                           this.state.skill_ratings
                            .map((entry, index) =>
                              <Cell key={index} fill={
      
                                 ['#0088FE', '#00C49F', '#FFBB28',
                                '#FF8042', '#FF2323'][index % 5]
                             }/>)
                       }
              </Bar>
          </BarChart>
      </div>
      
    3. Display the average rating of the skill along with the stars
        1. Import the stars component from the react-ratings-declarative component.

                  import Ratings from 'react-ratings-declarative';
          

           

        2. Render the average ratings and the stars component which is available in the app state as saved before.

          <div className="average">
                  Average Rating
          <div>
                     {this.state.avg_rating ? this.state.avg_rating : 2.5}
                  </div>
                  <Ratings
                     rating={this.state.avg_rating || 2.5}
                     widgetDimensions="20px"
                     widgetSpacings="5px"
                   >
                     <Ratings.Widget />
                     <Ratings.Widget />
                     <Ratings.Widget />
                     <Ratings.Widget />
                     <Ratings.Widget />
                  </Ratings>
          </div> 
          

           

    1. Display the total no of people who rated the skill, again, by using the ratings data saved in the state and calculating the total users who rated the skill by using a reduce function in ES6.

      <div className="total-rating">
              Total Ratings
              <div>
                 {this.state.skill_ratings.reduce((total, num) => {
                     return total + num.value
                 }, 0)}
              </div>
      </div>
      

      Outcome –

      I hope this post helped you in understanding how the rating system is implemented in the CMS.

      References –

Continue Reading

Implementing Five Star Rating UI in SUSI iOS

Five-star rating system introduced in SUSI to rate skills. SUSI enable the user to rate skills between 1 to 5 star. The five-star rating system is the best way to get feedback from the user. It also helps the developer for further development. Ratings help to better understand individual preferences and present a more personalized user experience. The user feedback helps products understand whether or not the content is valuable and improve offerings over time. This can benefit products with and without sophisticated personalization.

Let’s see how the five-star rating system is implemented in SUSI iOS.

Average ratings displayed near the Try It button – It shows the average rating of a particular skill.

Enable user to submit the rating of any skill between 1-star to 5-star.

The only logged-in user can submit the ratings for skills.

Rating chart that display number of rating for each star (1 to 5), the right labels of chart bars shows the number of users rated for a particular star with the percentage.

Average and total ratings for particular skills is also displayed near the bar chart.

Thumbs-up and thumbs-down ratings removed from the skill detail screen and replaced with 5-star ratings.

Implementation of Rating Chart

For the rating chart, we are using TEAChart class, which enable us to present rating data on bar charts.

Setting colors for bar chart:

We are using Google’s Material Design color for rating bars colors.

let barChartColors = [
UIColor.fiveStarRating(),
UIColor.fourStarRating(),
UIColor.threeStarRating(),
UIColor.twoStarRating(),
UIColor.oneStarRating()
]

Assigning colors to bars:

barChartView.barColors = barChartColors

Assign Data to the bars:

// Sample data
barChartView.data = [5, 1, 1, 1, 2]

Set background color and bar spacing:

barChartView.barSpacing = 3
barChartView.backgroundColor = UIColor.barBackgroundColor()

Final Output –

Resources –

  1. Material Design: https://material.io/design/
  2. SUSI iOS Link: https://github.com/fossasia/susi_iOS
Continue Reading

Implementing API to Fetch all Ratings by a User on Different Skills on SUSI.AI Web Client

SUSI Skill CMS allows the users to rate any Skill on a scale of 1 to 5. The user can also provide a feedback to any Skill. This paves the path to implementing a Dashboard, which has all the analytic data of the user. Hence, an API needed to be implemented which could return the ratings done by a particular user on all different Skills.

How are servlets implemented in SUSI.AI?

All servlets in SUSI extend AbstractAPIHandler class and implement APIHandler. All servlets have 4 methods, which we overwrite depending on what we want the servlet to do. They are as follows :

    @Override
    public String getAPIPath() {
        return null;
    }

    @Override
    public BaseUserRole getMinimalBaseUserRole() {
        return null;
    }

    @Override
    public JSONObject getDefaultPermissions(BaseUserRole baseUserRole) {
        return null;
    }

    @Override
    public ServiceResponse serviceImpl(Query post, HttpServletResponse response, Authorization rights, JsonObjectWithDefault permissions) throws APIException {
        return null;
    }

 

How these 4 methods work together?

  • First method is getAPIPath(). It returns the endpoint of the servlet.
  • The second method is getMinimalBaseUserRole(). It returns the minimum privilege level required to access the endpoint.
  • The third method is getDefaultPermissions(). It gets the Default Permissions of a UserRole in SUSI Server. Different UserRoles have different permissions defined in SUSI Server.
  • Whenever the endpoint defined in the getAPIPath() method is called properly, it responds with whatever is defined in the fourth method, which is serviceImpl().

Implementing a servlet to fetch all ratings by a user on different Skills

The task of this servlet is to fetch all the ratings done by a user on all the different Skills, so that this fetched data could be used later on for implementation of various user specific features like Dashboard page. This is the implementation of the 4 methods of this servlet:

    @Override
    public UserRole getMinimalUserRole() {
        return UserRole.USER;
    }

    @Override
    public JSONObject getDefaultPermissions(UserRole baseUserRole) {
        return null;
    }

    @Override
    public String getAPIPath() {
        return "/cms/getProfileDetails.json";
    }

    @Override
    public ServiceResp onse serviceImpl(Query query, HttpServletResponse response, Authorization authorization, final JsonObjectWithDefault permissions) throws APIException {

    JsonTray fiveStarSkillRating = DAO.fiveStarSkillRating;
    // JSONObject and JsonArray Declarations

    // Checking if access_token has been given and is valid
    if (authorization.getIdentity() == null) {
        throw new APIException(400, "Specified user data not found, ensure you are logged in");
    } 


    // Fetching email of the user from access token and storing it in a string
    String email = authorization.getIdentity().getName();


    // Iterating over the fiveStarSkillRating JsonTray by extracting keys at every level and accessing their child objects through the extracted keys
    for(String model_name : fiveStarSkillRating.keys())
    {
        // Storing the list of group names and iterating over them
        for(String group_name : groupnameKeysList)
        {
            // Storing the list of language names and iterating over them
            for(String language_name : languagenameKeysList)
            {
                // Storing the list of skill names and iterating over them
                for(String skill_name : skillnamesKeysList)
                {
                    skillnameArray = languageObject.getJSONArray(skill_name);
                    // Iterating over the different Skill JSONObjects
                    for(int i=0; i<skillnameArray.length(); i++) {
                        String jsonEmail = skillnameArray.getJSONObject(i).get("email").toString();
                        if(jsonEmail.equals(email)) {
                        // If the Skill has been rated by the required author, then put the Skill JSONObject into the result array
                        }
                    }
                }
            }
        }
    }

    if(result.length()==0) {
        // Handling case when user hasn’t rated any Skill yet
        result.put("accepted", false);
        result.put("message", "User has not rated any Skills yet.");
        return new ServiceResponse(result);
    }
    result.put("accepted", true);
    result.put("message", "User ratings fetched.");
    return new ServiceResponse(result);
    }

 

As it can be seen from the above code, the endpoint for this servlet is /cms/getProfileDetails.json and it requires 1 parameter – the access token of the user.

As the main task of this servlet is user specific, and should only be accessible to the particular user, hence we returned UserRole as USER in the getMinimalUserRole() method.

In the serviceImpl() method, we iterate over the fiveStarSkillRating.json JsonTray and keep on extracting keys and accessing the corresponding JSONObjects until we reach the lowermost layer, where all the Skill names are listed. Iterating over the JSONObjects corresponding to each Skill, we check the email in the Skill JSONObject to identify the user. If the email present in the Skill JSONObject matches with the email corresponding to the access token provided as a query parameter, we extract rating and the timestamp for the Skill and put it in a JSONArray. Then, finally we put the JSONArray into our resulting JSONObject.

This is how we can fetch all the ratings by a user on all the different Skills.

Resources

 

Continue Reading
  • 1
  • 2
Close Menu
%d bloggers like this: