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

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.