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

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

 

Integrate prettier with lint-staged and ESLint for consistent code style throughout the project

SUSI Skill CMS presently use ESLint to check for code linting errors, the ESLint rules are written in a separate .eslintrc file which lives at the project root. The project didn’t follow any best practices for react apps and the rules were weak therefore a lot of bad/unindented code was present in the project which takes a lot of time to fix manually, not to mention there was no mechanism to auto-format the code while committing. Also, code reviews take a lot of time to discuss code styles and fixing them.

Prettier comes to the rescue as it’s a code formatter which provides a ton of options to achieve the desired well-formatted code. Prettier enforces a consistent code style across your entire codebase because it disregards the original styling by parsing it away and re-printing the parsed code with its own rules

Add prettier as a development dependency

npm install prettier --save-dev --save-exact

 

Similar to how we write ESLint rules in a separate .eslintrc file, we have a .prettierrc file which contains rules for prettier but since we already have ESLint so we run prettier on top of ESLint to leverage functionalities of both packages, this is achieved by using eslint-plugin-prettier and eslint-config-prettier which exist for ESLint. These packages are saved as devDependencies in the project and “prettier” as a plugin is added to .eslintrc file and recommended prettier rules are extended by adding “prettier” in .eslintrc file.

Install the config and plugin packages

npm i eslint-plugin-prettier eslint-config-prettier --save-dev

 

To run prettier using ESLint, add the prettier to ESLint plugins and add prettier errors to ESLint rules.

// .eslintrc
{
 "plugins": ["prettier"],
 "rules": {
   "prettier/prettier": "error"
 }
}

 

Extending Prettier rules in ESLint

// .eslintrc
{
 ...
 "extends": ["prettier"]
 ...
}

 

5856 linting errors found which were undetected initially (SUSI Skill CMS).

image

Add a .prettierrc file with some basic formatting rules for now like enabling single quotes wherever applicable and to have trailing comma at the end of JSON objects.

// .prettierrc
{
   "singleQuote": true,
   "trailingComma": "all",
   "parser": "flow"
}

 

Add a format script in package.json so that user can format the code whenever required manually too.

// package.json
"scripts": {
        ...
        "format": "prettier --write \"src/**/*.js\"",
        ...
},

 

Since we want to prevent contributors from committing unindented code we used lint-staged, a package which runs tasks on a set of specified files. We achieve this by adding a set of tasks in the lint-staged object and then run lint-staged as a pre-commit hook using husky.

Install lint-staged as a development dependency which will be visible in package.json file.

npm i lint-staged --save-dev 

 

Add tasks for lint-staged as an object in package.json, we add a lint-staged object in the pacage.json file and grab all .js files in the project and run eslint, prettier over them and then we finally run git add to stage the changes done by prettier.

// package.json

"lint-staged": {
   "src/**/*.js": [
     "eslint src --ext .js",
     "prettier --write",
     "git add"
   ]
 }

 

Call lint-staged in pre-commit git-hook to run the specified tasks in the package.json.

// package.json

"scripts": {
   ...
   "precommit": "lint-staged",
   ...
},

 

Running lint-staged tasks before committing

image

As a result, we save a lot of time in reviewing the code since we don’t have to be worried about the code styles anymore as pre-commit hook already takes care of that also this ensures consistent code style throughout the project which improves the overall quality.

Resources

Prettier library website
Use ESLint to run Prettier
Link to my Pull Request
eslint-plugin-prettier
eslint-config-prettier

Display Skill Usage of the Past Week in a Line Chart

Skill usage statistics in SUSI.AI is an important aspect which greatly helps the skill developers know what is more engaging for the users and the users know which skills are more popular than the others and are being used widely. So data like this should be interactively available on the clients. An API is developed at the server to retrieve the skill usage details, we can use these details to render attractive components for a better visual understanding of how the skill is performing and get statistics like on which days the skill has been active in particular.

 

About the API

Endpoint : /cms/getSkillUsage.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.

Creating a Skill Usage card component

Import the required packages from corresponding libraries. We are using recharts as the library for the charts support and thus we import several components required for the line chart at the top of the Skill Usage component.

// Packages
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { LineChart, Line, XAxis, YAxis, Tooltip, Legend } from 'recharts';
import { Paper } from 'material-ui';

 

Create a SkillUsageCard component which is enclosed in a Paper component from material ui library to give it a card like look and then we render a Line chart inside it using appropriate data props which are received from the skill page component, we set a height and a width for the chart, Add an X-Axis, Y-Axis, Tooltip which shows up on hovering over points on the graph, legends to describe the line on the chart and then finally a line with several styling and other props.

class SkillUsageCard extends Component {
 render() {
   return(
     <Paper className="margin-b-md margin-t-md">
       <h1 className='title'>
           Skill Usage
       </h1>
       <div className="skill-usage-graph">
         <LineChart width={600} height={300} data={this.props.skill_usage}
               margin={{top: 5, right: 30, left: 20, bottom: 5}}>
           <XAxis dataKey="date" padding={{right: 20}} />
           <YAxis/>
           <Tooltip wrapperStyle={{height: '60px'}}/>
           <Legend />
           <Line name='Skill usage count' type="monotone" dataKey="count" stroke="#82ca9d" activeDot={{r: 8}}/>
         </LineChart>
       </div>
     </Paper>
   )
 }
}

 

Add prop validation at the end of the file to validate the coming props from the skill page component to validate that correct props are being received.

SkillUsageCard.propTypes = {
   skill_usage: PropTypes.array
}

 

Export the component so it can be used in other components.

export default SkillUsageCard;

 

Fetch the data for the component from the API in the skill page component where the skill usage component will be rendered. First set the API url and then make an AJAX call to that URL and once the data is received from the server pass that received data to a saveSkillUsage function which does the simple task of saving the data to the state and passing the saved data as a prop to the skill usage component. In case the call fails we log the error to the console.

let skillRatingUrl = `${urls.API_URL}/cms/getSkillRating.json`;
skillUsageUrl = skillUsageUrl + '?model=' + modelValue + '&group=' + this.groupValue + '&language=' + this.languageValue + '&skill=' + this.name;
// Fetch skill usage of the visited skill
$.ajax({
  url: skillUsageUrl,
  dataType: 'json',
  crossDomain: true,
  success: function (data) {
      self.saveSkillUsage(data.skill_usage)
  },
  error: function(e) {
       console.log(e)
  }
});

 

Save the skill usage details in the component state to render the skill usage component.

saveSkillUsage = (skill_usage = []) => {
  this.setState({
     skill_usage
 })
}

 

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

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

 

So I hope after reading this blog you have a more clearer insight into how the skill usage details are implemented in the CMS.

Resources –

  • Jerry J. Muzsik, Creating and deploying a react app using recharts URL.
  • Recharts, Documentation, URL.

Rendering a Uniform StaticAppBar Component across all SUSI Web Clients on all Routes.

The Problem –
We have three SUSI Web Clients namely

Webchat
Skills CMS
Accounts

And it’s important to keep the design guidelines in sync across all the clients, StaticAppBar is a component which forms the header of all the pages and thus it is important to keep it uniform in all clients which was clearly missing before. There is also a lot of code duplication of the AppBar component (in accounts app) since it is used on all the pages so our approach will be to prepare a single component and render it on all routes.

Tackling the problem – Since the StaticAppBar component is present on all the clients we simply make the menu items uniform across all the clients and apply a check on those menu items on which are a premium feature or should appear only once the user is logged in.

Building blocks of the StaticAppBar component

  • AppBar
  • SUSI logo on the left end
  • Drop down hamburger menu on the right

Here’s how the JSX for the StaticAppBar component in CMS looks like, it uses an AppBar component from the material-ui library and has several props and styling as per the requirement.

<AppBar
   className='topAppBar'
   id='appBar'
   title={<div id='rightIconButton' ><Link to='/' style={{ float: 'left', marginTop: '-10px',height:'25px',width:'122px' }}>
       <img src={susiWhite} alt='susi-logo' className='siteTitle' /></Link></div>}
   style={{
       backgroundColor: colors.header,
       height: '46px',
       boxShadow: 'none',
       margin: '0 auto',
   }}
   iconStyleRight={{ marginTop: '-2px' }}
   iconElementRight={<TopRightMenu />}
/>

 

TopRightMenu is a function that returns JSX for the hamburger dropdown and is rendered in the AppBar as depicted below. It is a conditional menubar meaning some menu items are only rendered when the user is logged in and thus this helps cover those features which should only be available to logged in users. After that we use a popover component which shows up when the 3 dots or the expander on the top right is clicked. Almost all of the components in material ui has a style prop so styling is easy for the components moreover the workflow for the popover click goes like once the expander is clicked a boolean state variable named showOptions is toggled which in turn toggles the opening or closing state of the Popover as per the open prop.

let TopRightMenu = (props) => (
           <div onScroll={this.handleScroll}>
               <div>
                   {cookies.get('loggedIn') ?
                       (<label
                           style={{color: 'white', fontSize: '16px', verticalAlign:'super'}}>
                           {cookies.get('emailId')}
                           </label>) :
                       (<label>
                           </label>)
                   }
                   <IconMenu
                       {...props}
                       iconButtonElement={
                           <IconButton
                               iconStyle={{ fill: 'white' }}><MoreVertIcon /></IconButton>
                       }
                       targetOrigin={{ horizontal: 'right', vertical: 'top' }}
                       anchorOrigin={{ horizontal: 'right', vertical: 'top' }}
                       onTouchTap={this.showOptions}
                   >
                   </IconMenu>
                   <Popover
                       {...props}
                       animated={false}
                       style={{ float: 'right', position: 'relative', marginTop: '46px', marginLeft: leftGap }}
                       open={this.state.showOptions}
                       anchorEl={this.state.anchorEl}
                       anchorOrigin={{ horizontal: 'right', vertical: 'bottom' }}
                       targetOrigin={{ horizontal: 'right', vertical: 'top' }}
                       onRequestClose={this.closeOptions}
                   >
                       <TopRightMenuItems />
                       {cookies.get('loggedIn') ?
                           (<MenuItem primaryText='Botbuilder'
                               containerElement={<Link to='/botbuilder' />}
                               rightIcon={<Extension />} />) :
                           null
                       }
                       <MenuItem primaryText='Settings'
                           containerElement={<Link to='/settings' />}
                           rightIcon={<Settings />} />
                       {cookies.get('loggedIn') ?
                           (<MenuItem primaryText='Logout'
                               containerElement={<Link to='/logout' />}
                               rightIcon={<Exit />} />) :
                           (<MenuItem primaryText='Login'
                               onTouchTap={this.handleLogin}
                               rightIcon={<LoginIcon />} />)
                       }
                   </Popover>
               </div>
           </div>
       );
Handling the conditional display of menu items based on the user session

Some features are to be offered to only those who are logged in and thus we need to display them depending on the user session i.e they should be visible when the user is logged in and hidden when the user is logged out. User state is stored in the browser’s cookies and using that we can achieve the desired result.

{
     cookies.get('loggedIn') ?
       (<MenuItem primaryText="Botbuilder"
         href="https://skills.susi.ai/botbuilder"
         rightIcon={<Extension/>}/>):
         null
}

 

So I hope after going through this blog you have a much more clearer insight to how the StaticAppBar is implemented.

 

References

  1. Check out AppBar component from material-ui library here.
  2. Check blog post introducing the usage of material-ui in react here

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 –

Displaying skill rating for each skill on skill page of SUSI SKILL CMS

SUSI exhibits several skills which are managed by the SUSI Skill CMS, it essentially is a client which allows users to create/update skills conveniently since for each skill it is important to have the functionality of rating system so developers can get to know which skills are performing better than the rest and consequently improve them, thus a skill rating system which allows the users to give positive or negative feedback for each skill is implemented on the server.

Fetching skill_rating from the server

  1. Fetch skill data for which ratings are to be displayed through ajax calls
    API Endpoint –

    /cms/getSkillMetadata.json?
    

  2. Parse the received metadata object to get positive and negative ratings for that skill
  3. if(skillData.skill_rating) {
           	let positive_rating = skillData.skill_rating.positive;
            	let negative_rating = skillData.skill_rating.negative;
    }
    

    Sample API response

    {
      "skill_metadata": {
        "model": "general",
        "group": "Knowledge",
        "language": "en",
        "developer_privacy_policy": null,
        "descriptions": "Want to know about fossasia, just ask susi to tell that, Susi tells about the SUSI.AI creators",
        "image": "images/creator_info.png",
        "author": "madhav rathi",
        "author_url": "https://github.com/madhavrathi",
        "author_email": null,
        "skill_name": "Creator Info",
        "terms_of_use": null,
        "dynamic_content": false,
        "examples": [
          "Who created you?",
          "what is fossasia?"
        ],
        "skill_rating": {
          "negative": "0",
          "positive": "0",
          "stars": {
            "one_star": 0,
            "four_star": 0,
            "five_star": 0,
            "total_star": 0,
            "three_star": 0,
            "avg_star": 0,
            "two_star": 0
          },
          "feedback_count": 0
        },
        "creationTime": "2018-03-17T16:38:29Z",
        "lastAccessTime": "2018-06-15T15:51:50Z",
        "lastModifiedTime": "2018-03-17T16:38:29Z"
      },
      "accepted": true,
      "message": "Success: Fetched Skill's Metadata",
      "session": {"identity": {
        "type": "host",
        "name": "162.158.166.37_d80fb5c9",
        "anonymous": true
      }}
    }
    

  4. Set the react state of the component to store positive and negative rating.
  5. this.setState({
      positive_rating,
      negative_rating
    })
    

  6. Use react-icons to fetch like and dislike icon components from font-awesome.
  7. npm i -S react-icons
    

  8. Import the corresponding icons in the SkillPage component
  9. import { FaThumbsOUp, FaThumbsODown } from 'react-icons/lib/fa/'
    

  10. Display the rating count along with their icons
  11. <div className="rating">
        <div className="positive">
             <FaThumbsOUp />
             {this.state.positive_rating}
         </div>
           <div className="negative">
                 <FaThumbsODown />
                 {this.state.negative_rating}
             </div>
    </div>
    

Example

References

Using a Git repo as a Storage & Managing skills through susi_skill_cms

In this post, I’ll be talking about SUSI’s skill management and the workflow of creating new skills

The SUSI skills are maintained in a separate github repository susi_skill_data which provides the features of version controlling and the ability to rollback to a previous version implemented in SUSI Server.

The workflow is as explained in the featured image of this blog, SUSI CMS provides the user with a GUI through which user can talk to the SUSI Server and using it’s api calls, it can manipulate the susi skills present/stored on the susi_skill_data repository.

When the user opts to create a new skill, a new createSkill component is loaded with an editor to define rules of the skill. Once the form is submitted, an AJAX POST request is made to the server which actually commits the skill data to the repository and thus it is visible in the CMS from that point on.

Grab the skill details within the editor and put them in a form which is to be sent via the POST request.

let form = new FormData();
form.append('model', 'general');
form.append('group', this.state.groupValue);
form.append('language', this.state.languageValue);
form.append('skill', this.state.expertValue.trim().replace(/\s/g,'_'));
form.append('image', this.state.file);
form.append('content', this.state.code);
form.append('image_name', this.state.imageUrl.replace('images/',''));
form.append('access_token', cookies.get('loggedIn'));


Configure POST request settings object

let settings = {
   'async': true,
   'crossDomain': true,
   'url': urls.API_URL + '/cms/createSkill.json',
   'method': 'POST',
   'processData': false,
   'contentType': false,
   'mimeType': 'multipart/form-data',
   'data': form
};


Make an AJAX request using the settings above to upload the skill to the server and send a notification when the request is successful.

$.ajax(settings)
   .done(function (response) {
   self.setState({
          loading:false
   });
notification.open({
    message: 'Accepted',
    description: 'Your Skill has been uploaded to the server',
    icon: <Icon type='check-circle' style={{ color: '#00C853' }}       />,
});


Parse the received response as JSON and if the accept key in the response is true, we push the new skill data to the history API and set relevant states.

let data = JSON.parse(response);
if(data.accepted===true){
  self.props.history.push({
	pathname: '/' + self.state.groupValue  +
  	'/' + self.state.expertValue.trim().replace(/\s/g,'_') +
  	'/' + self.state.languageValue,
	state: {
  	from_upload: true,
  	expertValue:  self.state.expertValue,
  	groupValue: self.state.groupValue ,
  	languageValue: self.state.languageValue,
}});


If the accepted key of the server response is not true, display a notification.

else{
	self.setState({
  		loading:false
	});
	notification.open({
	  	message: 'Error Processing your Request',
	  	description: String(data.message),
	  	icon: <Icon type='close-circle' style={{ color: '#f44336' }} />,
	});
}})


Handle cases when AJAX request fails and send a corresponding notification

.fail(function (jqXHR, textStatus) {
 ...
  notification.open({
    message: 'Error Processing your Request',
    description: String(textStatus),
    icon: <Icon type='close-circle' style={{ color: '#f44336' }} />,
  });
});


I hope after reading this post, the objectives of susi_skill_data are more clear and you understood how CMS handles the creation of skills.

Resources

1.AJAX Jquery – AJAX request using Jquery
2. React State – Read about React states and lifecycle hooks.

Setting up SUSI Desktop Locally for Development and Using Webview Tag and Adding Event Listeners

SUSI Desktop is a cross platform desktop application based on electron which presently uses chat.susi.ai as a submodule and allows the users to interact with susi right from their desktop.

Any electron app essentially comprises of the following components

    • Main Process (Managing windows and other interactions with the operating system)
    • Renderer Process (Manage the view inside the BrowserWindow)

Steps to setup development environment

      • Clone the repo locally.
$ git clone https://github.com/fossasia/susi_desktop.git
$ cd susi_desktop
      • Install the dependencies listed in package.json file.
$ npm install
      • Start the app using the start script.
$ npm start

Structure of the project

The project was restructured to ensure that the working environment of the Main and Renderer processes are separate which makes the codebase easier to read and debug, this is how the current project is structured.

The root directory of the project contains another directory ‘app’ which contains our electron application. Then we have a package.json which contains the information about the project and the modules required for building the project and then there are other github helper files.

Inside the app directory-

  • Main – Files for managing the main process of the app
  • Renderer – Files for managing the renderer process of the app
  • Resources – Icons for the app and the tray/media files
  • Webview Tag

    Display external web content in an isolated frame and process, this is used to load chat.susi.ai in a BrowserWindow as

    <webview src="https://chat.susi.ai/"></webview>
    

    Adding event listeners to the app

    Various electron APIs were used to give a native feel to the application.

  • Send focus to the window WebContents on focussing the app window.
  • win.on('focus', () => {
    	win.webContents.send('focus');
    });
    
  • Display the window only once the DOM has completely loaded.
  • const page = mainWindow.webContents;
    ...
    page.on('dom-ready', () => {
    	mainWindow.show();
    });
    
  • Display the window on ‘ready-to-show’ event
  • win.once('ready-to-show', () => {
    	win.show();
    });
    

    Resources

    1. A quick article to understand electron’s main and renderer process by Cameron Nokes at Medium link
    2. Official documentation about the webview tag at https://electron.atom.io/docs/api/webview-tag/
    3. Read more about electron processes at https://electronjs.org/docs/glossary#process
    4. SUSI Desktop repository at https://github.com/fossasia/susi_desktop.

    Enhancing SUSI Desktop to Display a Loading Animation and Auto-Hide Menu Bar by Default

    SUSI Desktop is a cross platform desktop application based on electron which presently uses chat.susi.ai as a submodule and allows the users to interact with susi right from their desktop. The benefits of using chat.susi.ai as a submodule is that it inherits all the features that the webapp offers and thus serves them in a nicely build native application.

    Display a loading animation during DOM load.

    Electron apps should give a native feel, rather than feeling like they are just rendering some DOM, it would be great if we display a loading animation while the web content is actually loading, as depicted in the gif below is how I implemented that.
    Electron provides a nice, easy to use API for handling BrowserWindow, WebContent events. I read through the official docs and came up with a simple solution for this, as depicted in the below snippet.

    onload = function () {
    	const webview = document.querySelector('webview');
    	const loading = document.querySelector('#loading');
    
    	function onStopLoad() {
    		loading.classList.add('hide');
    	}
    
    	function onStartLoad() {
    		loading.classList.remove('hide');
    	}
    
    	webview.addEventListener('did-stop-loading', onStopLoad);
    	webview.addEventListener('did-start-loading', onStartLoad);
    };
    

    Hiding menu bar as default

    Menu bars are useful, but are annoying since they take up space in main window, so I hid them by default and users can toggle their display on pressing the Alt key at any point of time, I used the autoHideMenuBar property of BrowserWindow class while creating an object to achieve this.

    const win = new BrowserWindow({
    	
    	show: false,
    	autoHideMenuBar: true
    });
    

    Resources

    1. More information about BrowserWindow class in the official documentation at electron.atom.io.
    2. Follow a quick tutorial to kickstart creating apps with electron at https://www.youtube.com/watch?v=jKzBJAowmGg.
    3. SUSI Desktop repository at https://github.com/fossasia/susi_desktop.