Different Text Color On Each Line In Badgeyay

In this blog post I am going to explain about how to create different text color for each line in badges generation in Badgeyay. As the system now has option for different badge size and paper size, currently the system sets same color for each line by mutating the fill parameter in the SVG. The main challenge in mutating the SVG parameter for each badge is the Id. The ID identifies the element, in our case text, and gives access to iterate the SVG through libraries like lxml. So for implementing this feature we first need to manipulate the SVG and assign id’s to the text tag so that it can be easily manipulated through the algorithm.

Procedure

  1. Manipulating the text tag in SVG and assigning a proper ID according to the logic for iteration in the function.
<text

     id=“Person_color_1_1”

     ….>

Person_1_1

</text>

The id of the person in first badge and first line is represented as Person_color_1_1, where the first number denotes the number of badge and second number denotes the line number.

  1. Creating a class for the dimensions of the badges
class Dimen(object):
  def __init__(self, badges, badgeSize, paperSize):
      self.badges = badges
      self.badgeSize = badgeSize
      self.paperSize = paperSize
  1. Creating an initialiser function that stores the dimension objects
badge_config = {}


def init_dimen():
  paper_sizes = [‘A2’, ‘A3’, ‘A4’]
  for paper in paper_sizes:
      if paper == ‘A2’:
          badge_config.__setitem__(paper, {‘4×3’: Dimen(18, ‘4×3’, paper)})
          badge_config[paper][‘4.5×4’] = Dimen(15, ‘4.5×4’, paper)
      elif paper == ‘A3’:
          badge_config.__setitem__(paper, {‘4×3’: Dimen(8, ‘4×3’, paper)})
          badge_config[paper][‘4.5×4’] = Dimen(6, ‘4.5×4’, paper)
      elif paper == ‘A4’:
          badge_config.__setitem__(paper, {‘4×3’: Dimen(6, ‘4×3’, paper)})
          badge_config[paper][‘4.5×4’] = Dimen(2, ‘4.5×4’, paper) 
  1. Selecting the dimension config based on the parameters passed in the function.
dimensions = badge_config[paper_size][badge_size]
  1. Looping criteria is to loop through the number of badges mentioned in the dimension config and through the number of lines which will be five.
for idx in range(1, dimensions.badges + 1):

          for row in range(1, 6):
  1. Selecting the text element with the ID as provided above.
_id = ‘Person_color_{}_{}’.format(idx, row)
              path = element.xpath((“//*[@id='{}’]”).format(_id))[0]
  1. Fill the text color argument of the selected object by changing the value of fill.
style_detail[6] = “fill:” + str(fill[row])

That’s it and now when the loop runs each line will have its individual color as passed in the function. The choice of color is passed as the list named fill.

Resources

Continue Reading

Loading Default System Image of Event Topic on Open Event Server

In this blog, we will talk about how to add feature of loading system image of event topic from server to display it on Open Event Server. The focus is on adding a helper function to create system image and loading that local image onto server.

Helper function

In this feature, we are providing feature of addition of loading default system image if user doesn’t provides that.

  1. First we get a suitable filename for a image file using get_file_name() function.
  2. After getting filename, we check if the url provided by user is a valid url or not.
  3. If the url is invalid then we use the default system image as the image of that particular event topic.
  4. After getting the local image then we read that image, if the given image file or the default image is not readable or gives IOError then we send a message to the user that Image url is invalid.
  5. After successful reading of image we upload the image to event_topic directory in static directory of the project.
  6. After uploading of this image we get a local URL which shows where is the image is stored. This path is stored into database and finally we can display this image.

Resources

Continue Reading

Adding Custom System Roles API on Open Event Server

In this blog, we will talk about how to add API for accessing the Custom System Roles on Open Event Server. The focus is on Schema creation and it’s API creation.

Schema Creation

For the CustomSystemRoleSchema, we’ll make our Schema as follows

Now, let’s try to understand this Schema.

In this feature, we are providing Admin the rights to get and create more system roles.

  1. First of all, we are provide the two fields in this Schema, which are id and name.
  2. The very first attribute id should be of type string as it would have the identity which will auto increment when a new system role is created. Here dump_only means that this value can’t be changed after the record is created.
  3. Next attribute name should be of string type and it will contain the name of new custom system role. This attribute is required in a custom_system_roles table.

API Creation

For the Custom System Roles, we’ll make our API as follows

Now, let’s try to understand this Schema.

In this API, we are providing Admin the rights to set Custom System roles.

  1. CustomSystemRoleList inherits ResourceList which will give us list of all the custom system roles in the whole system.
  2. CustomSystemRoleList has a decorators attribute which gives the permission of POST request to only admins of the system.
  3. CustomSystemRoleDetail inherits ResourceDetail which will give the details of a CustomSystemRole object by id.
  4. CustomSystemRoleDetail has a decorators attribute which gives the permission of PATCH and DELETE requests to only admins of the system.

So, we saw how Custom System Role Schema and API is created to allow users to get it’s values and Admin users to update and delete it’s record.

Resources

Continue Reading

Adding Panel Permissions API in Open Event Server

In this blog, we will talk about how to add API for accessing the Panel Permissions on Open Event Server. The focus is on Schema creation and it’s API creation.

Schema Creation

For the PanelPermissionSchema, we’ll make our Schema as follows

Now, let’s try to understand this Schema.

In this feature, we are providing Admin the rights to create and assign panel permission to any of the custom system role.

  1. First of all, we are provide the four fields in this Schema, which are id, panel_name, role_id and can_access.
  2. The very first attribute id should be of type string as it would have the identity which will auto increment when a new system role is created. Here dump_only means that this value can’t be changed after the record is created.
  3. Next attribute panel_name should be of string type and it will contain the name of panel. This attribute is required in a panel_permissions table so set as allow_none=False.
  4. Next attribute role_id should be of integer type as it will tell us that to which role current panel is concerning.
  5. Next attribute can_access should be of boolean type as it will tell us whether a role of id=role_id has access to this panel or not.
  6. There is also a relationship named role which will give us the details of the custom system role with id=role_id.

API Creation

For the Panel Permissions, we’ll make our API as follows

Now, let’s try to understand this Schema.

In this API, we are providing Admin the rights to set panel permissions for a custom system role.

  1. PanelPermissionList inherits ResourceList which will give us list of all the custom system roles in the whole system.
  2. PanelPermissionList has a decorators attribute which gives the permission of both GET and POST requests to only admins of the system.
  3. The POST request of PanelPermissionList API requires the relationship of role.
  4. PanelPermissionDetail inherits ResourceDetail which will give the details of a Panel Permission object by id.
  5. PanelPermissionDetail has a decorators attribute which gives the permission of GET, PATCH and DELETE requests to only admins of the system.

So, we saw how Panel Permissions Schema and API is created to allow Admin users to get, update and delete it’s record.

Resources

 

Continue Reading

Change Role of User in SUSI.AI Admin section

In this blog post, we are going to implement the functionality to change role of an user from the Admin section of Skills CMS Web-app. The SUSI Server has multiple user role levels with different access levels and functions. We will see how to facilitate the change in roles.

The UI interacts with the back-end server via the following API –

  • Endpoint URL –  https://api.susi.ai/cms/getSkillFeedback.json
  • The minimal user role for hitting the API is ADMIN
  • It takes 4 parameters –
    • user – The email of the user.
    • role – The new role of the user. It can take only selected values that are accepted by the server and whose roles have been defined by the server. They are – USER, REVIEWER, OPERATOR, ADMIN, SUPERADMIN.
    • access_token –  The access token of the user who is making the request

Implementation on the CMS Admin

  • Firstly, a dialog box containing a drop-down was added in the Admin section which contains a list of possible User roles. The dialog box is shown when Edit is clicked, present in each row of the User table.
  • The UI of the dialog box is as follows –

  • The implementation of the UI is done as follows –

….
<Dialog
  title="Change User Role"
  actions={actions} // Contains 2 buttons for Change and Cancel
  modal={true}
  open={this.state.showEditDialog}
>
  <div>
    Select new User Role for
    <span style={{ fontWeight: 'bold', marginLeft: '5px' }}>
      {this.state.userEmail}
    </span>
  </div>
  <div>
    <DropDownMenu
      selectedMenuItemStyle={blueThemeColor}
      onChange={this.handleUserRoleChange}
      value={this.state.userRole}
      autoWidth={false}
    >
      <MenuItem
        primaryText="USER"
        value="user"
        className="setting-item"
      />
      /*
        Similarly for REVIEWER, OPERATOR, ADMIN, SUPERADMIN
        Add Menu Items
     */ 
    </DropDownMenu>
  </div>
</Dialog>
….
.
.
.

 

  • In the above UI immplementation the Material-UI compoenents namely Dialog, DropDownMenu, MenuItems, FlatButton is used.
  • When the Drop down value is changed the handleUserRoleChange function is executed. The function changes the value of the state variables and the definition is as follows –
handleUserRoleChange = (event, index, value) => {
  this.setState({
      userRole: value,
  });
};

 

  • Once, the correct user role to be changed has been selected, the on click handlers for the action button comes into picture.
  • The handler for clicking the Cancel button simply closes the dialog box, whereas the handler for the Change button, makes an API call that changes the user role on the Server.
  • The click handlers for both the buttons is as follows –

// Handler for ‘Change’ button
onChange = () => {
  let url =
   `${urls.API_URL}/aaa/changeRoles.json?user=${this.state.userEmail}&
   role=${this.state.userRole}&access_token=${cookies.get('loggedIn')}`;
  let self = this;
  $.ajax({
    url: url,
    dataType: 'jsonp',
    crossDomain: true,
    timeout: 3000,
    async: false,
    success: function(response) {
      self.setState({ changeRoleDialog: true });
    },
    error: function(errorThrown) {
      console.log(errorThrown);
    },
  });
  this.handleClose();
};

// Handler for ‘Cancel’ button
handleClose = () => {
  this.setState({
    showEditDialog: false,
  });
};
  • In the first function above, the URL endpoint is hit, and on success the Success Dialog is shown and the previous dialog is hidden.
  • In the second function above, only the Dialog box is hidden.
  • The cross domain in the AJAX call is kept as true to enable API usage from multiple domain names and the data type set as jsonp also deals with the same issue.

Resources

Continue Reading

Scan and detect available networks in SUSI.AI Android app

We all have experienced Apple HomPad, Google Home, Alexa etc or read about smart speakers which offer interactive actions over voice commands. Smart speaker uses hotword for activation. They utilize WiFi, bluetooth and other wireless protocols.

SUSI.AI is also coming with an Open Source smart speaker using  as simple as a RaspberryPi for the speaker which can perform various actions like playing music etc over voice commands. To use SUSI Smart Speaker, you have to connect it to the SUSI iOS or Android App. You can manage your connected devices in SUSI Android,SUSI iOS and Web clients. Here we will see initial setups for connecting SUSI Smart Speaker with an Android device.

Unlike iOS, Android allows any app to change the WiFi settings of the phone if the specific permissions are added in the app and on the run time while using the app. To connect to a particular WiFi in android given that permissions are enabled a few lines of code is sufficient enough to connect to the new WiFi network, but in our case before connecting to the WiFi we must know that the SUSI.AI hotspot is available around or not.

To know if the SUSI hotspot is available or not we have to scan for the available WiFi networks present and then choose the SUSI WiFi hotspot.

The app simply detects the available WiFi networks and displays a set of them that have the name SUSI.AI. To detect the available WiFi networks we have used a BroadCastReceiver that listens for the action :

WifiManager.SCAN_RESULTS_AVAILABLE_ACTION

The broadcast receiver listens to this action and fetches the list of networks available. The broadcast receiver is registered after the objects of WifiReceiver class and WifiManager class are declared. Once the broadcast receiver is registered the object of WifiManager is used to start the scanning process of the available Wifi networks.

Here is the object of the WifiManager and WifiReceiver declared in the onCreate() method of the DeviceActivity.kt file :

mainWifi = application.getSystemService(Context.WIFI_SERVICE) as WifiManager
receiverWifi = WifiReceiver()

The broadcast receiver class that scans the available networks is as follows :

inner class WifiReceiver : BroadcastReceiver() {
  override fun onReceive(p0: Context?, p1: Intent?) {
      Timber.d(“Inside the app”)
      if (p1 != null) {
          if (p1.action.equals(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) {
              var wifiList: List<ScanResult> = ArrayList<ScanResult>()
              wifiList = mainWifi.getScanResults()
              devicePresenter.inflateList(wifiList)
          }

In the above code all the available networks on scanning are sent to the presenter for further processing.

In the presenter class in the method inflateList() the available WiFi networks list is scanned on the basis of their SSID and all the networks with name are “SUSI.AI” are selected and then displayed as a list to the user.

The inflateList() function is as follows :

override fun inflateList(list: List<ScanResult>) {
  Timber.d(“size “ + list.size)
  connections = ArrayList<String>()
  for (i in list.indices) {
      if (list[i].SSID.equals(utilModel.getString(R.string.device_name)))
          connections.add(list[i].SSID)
  }

  if (connections.size > 0) {
      deviceView?.setupAdapter(connections)
  } else {
      deviceView?.onDeviceConnectionError(utilModel.getString(R.string.no_device_found), utilModel.getString(R.string.setup_tut))
  }
}

Now after the wifi list is scanned the sorted and selected list is sent to the Recycler adapter that will display the list to available SUSI networks to the user.

CONCLUSION :

REFERENCES :

  1. Android Manipulation of wifi using WifiManager : https://medium.com/@josiassena/android-manipulating-wifi-using-the-wifimanager-9af77cb04c6a
  2. Scan and list all Wifi in Android : https://www.nplix.com/scan-list-wifi-network-android/
  3. Kotlin android broadcast receivers : https://www.techotopia.com/index.php/Kotlin_Android_Broadcast_Intents_and_Broadcast_Receivers

 

Continue Reading

Individual skill usage subsections in SUSI Skill CMS

In SUSI.AI Skills CMS several interactive skill related statistics are displayed on the skill page for each skill which includes user ratings, ratings over time, user feedback and skill usage data displayed interactively. The skill usage section is further subdivided to get more insight into how the skill has been used and from where. Therefore we have three subsections which display Time wise skill usage, device wise usage, and country wise usage. All this data can help evaluate which devices are mostly using the skill or data like in which country the skill is more popular than others. So in this post, we mainly discuss the UI of how these sections are implemented.

Implementation

Adding a Card component to the skill page component at the bottom of the skill page component.

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

 

In the render function of the newly made component, we import the Paper component from material-ui and render it at the top to contain the subsections to give it a card-like UI.

<div>
   <Paper className="margin-b-md margin-t-md">
           ...
   </Paper>
</div>

 

Create div for the time wise skill usage. Calculate total skill usage for displaying the total skill usage count and also it helps to decide whether we need to render the section or not. So if the total skill usage by time count is greater than zero then render the line chart for visual analysis and display the total skill usage count too.

let totalSkillUsage = 0;
if (this.props.skill_usage) {
 // eslint-disable-next-line
 totalSkillUsage = this.props.skill_usage.reduce((totalCount, day) => {
        if (day) {
         return totalCount + day.count;
        }
        return totalCount;
 }, 0);
}

<div className="time-chart">
 <div>
        <ResponsiveContainer width={this.state.width} height={300}>
         <LineChart
           ...
         >
           <XAxis dataKey="date" padding={{ right: 20 }} />
           <YAxis allowDecimals={false} />
           <Tooltip wrapperStyle={{ height: '60px' }} />
           <Legend />
           <Line
             ...
           />
         </LineChart>
        </ResponsiveContainer>
 </div>
</div>
<div className="total-hits">
 <div className="large-text">{totalSkillUsage}</div>
 Hits this week
</div>

 

Create div for the Device wise usage. Conditionally render it in case the device wise data is available in the props.

<div className="device-usage">
 <div className="sub-title">Device wise Usage</div>
 {this.props.device_usage_data &&
 this.props.device_usage_data.length ? (
        <div className="pie-chart">
         <ResponsiveContainer width={600} height={350}>
           <PieChart>
             <Pie
               ...
             >
               {this.props.device_usage_data.map((entry, index) => (
                 <Cell key={index} fill={entry.color} />
               ))}
             </Pie>
             <Legend wrapperStyle={{ position: 'relative' }} />
           </PieChart>
         </ResponsiveContainer>
        </div>
</div>

 

Create a div for the country wise usage. We get the country wise usage data from the props and then we plug in the data in the geo chart component and also display the data as a table on the side. In case no data comes in or is unavailable we do not render the component at all.

<div>
 {countryWiseSkillUsage && countryWiseSkillUsage.length ? (
        <div className="country-usage-container">
         <div className="country-usage-graph">
           <GeoChart data={countryWiseSkillUsage} />
         </div>
         <div className="country-usage-list">
           <Table>
             ...
         </div>
        </div>
 ) : (
        <div className="unavailable-message">
         Country wise usage distribution is not available.
        </div>
 )}
</div>

 

This is how the three subsection in the skill usage component are implemented

Resources

Continue Reading

Adding a feature to delete skills from skill page for admins

SUSI Skill CMS has evolved drastically over the past few months with not only the introduction of skill metrics, skill analytics and powerful sorting features and interactive skill view types we needed the SUSI admins to be able to delete skills directly from the skills page and hence the skill can be deleted without visiting the admin service and then locating the skill and deleting it. This feature can be useful when a skill live on the system needs to be removed instantaneously for any reason like the API used by the skill going down or if it is a redundant skill or anything else. This feature was much needed for the admins and thus it was implemented.

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/deleteSkill.json?access_token={access_token}&model={model}&group={group}&language={language}&skill={skill}

 

Parameters :

  • Model
  • Group
  • Skill
  • Language
  • Feedback
  • Access token (taken from the session of the logged in user)

Sample API call :

/cms/deleteSkill.json?access_token=ANecfA1GjP4Bkgv4PwjL0OAW4kODzW&model=general&group=Knowledge&language=en&skill=whois

Displaying a button with delete icon on skill page

The option to delete skill should be available at the skill page for each skill so we add a button with a delete icon for this in the group of edit skills and skill version buttons, clicking over this button will open up a confirmation dialog with two actions notable the delete/confirm button which deletes the skills and the cancel button which can be useful in case the user changes their mind. On clicking the delete button the request to delete the skill is sent to the server and thus the skill is deleted.

Import some required components

import Dialog from 'material-ui/Dialog';
import FlatButton from 'material-ui/FlatButton';
import Cookies from 'universal-cookie';
import $ from 'jquery';

 

Adding some variables to the component state which will help us decide when the delete skill dialog is to be shown.

this.state = {
   ...
   showDeleteDialog: false,
   ...
}

 

Display the delete skill button only when the user is logged in user has admin rights.

{
   cookies.get(showAdmin) ? (
           ...
   ): ''
}

 

Adding some JSX to the component’s render function which includes a div in the skill page section and the Dialog component for the delete skill and some actions which in our case is the the confirmation to delete skill and to cancel the skill deletion in case the user changes their mind. Also a tooltip is shown which appears on hovering over the delete skill button.

<div className="skillDeleteBtn">
 <FloatingActionButton
        onClick={this.handleDeleteToggle}
        data-tip="Delete Skill"
        backgroundColor={colors.header}
 >
        <DeleteBtn />
 </FloatingActionButton>
 <ReactTooltip effect="solid" place="bottom" />
 <Dialog
        title="Delete Skill"
        actions={deleteDialogActions}
        modal={false}
        open={this.state.showDeleteDialog}
        onRequestClose={this.handleDeleteToggle}
 >
        <div>
         Are you sure about deleting{' '}
         <span style={{ fontWeight: 'bold' }}>
           {this.state.skill_name}
         </span>?
        </div>
 </Dialog>
</div>

 

Clicking the delete skill button will change the state variable which decides whether the dialog is to be shown or not.

handleDeleteToggle = () => {
 this.setState({
        showDeleteDialog: !this.state.showDeleteDialog,
 });
};

 

Adding submit and cancel actions for the dialog menu and send them to the dialog as a prop.

const deleteDialogActions = [
 <FlatButton
        label="Delete"
        key="delete"
        style={{ color: 'rgb(66, 133, 244)' }}
        onClick={this.deleteSkill}
 />,
 <FlatButton
        label="Cancel"
        key="cancel"
        style={{ color: 'rgb(66, 133, 244)' }}
        onClick={this.handleDeleteToggle}
 />,
];

Hitting the endpoint for skill deletion.

Adding onClick event handlers for dialog actions, for the cancel button we simply toggle the view of the delete skill dialog and for the submit button hits the endpoint for skill deletion and then we display a snack bar message about the status of request we submit if it succeeded or failed. Once the submit button is clicked we hit the delete skill endpoint by supplying appropriate params and post the request for skill deletion by calling the API through AJAX with appropriate params and thus this concludes the skill deletion workflow.

Build the URL for the AJAX request.

let deleteUrl =    `${urls.API_URL}/cms/deleteSkill.json?model=${this.state.skillModel}&group=${this.state.skillGroup}&language=${this.state.skillLanguagelanguage}&skill=${this.state.skill_name}&access_token=${cookies.get('loggedIn')}`

 

Make an AJAX request to the built API URL and in case the request is successful we set the conditional for displaying the snackbar to true and set the snackbar message depending on whether the skill was deleted successfully or it failed.

$.ajax({
 url: deleteUrl,
 dataType: 'jsonp',
 jsonp: 'callback',
 crossDomain: true,
 success: function(data) {
        // redirect to the index page since the skill page won't be accessible
        this.handleDeleteToggle();
        this.setState({
         dataReceived: true,
        });
        this.props.history.push('/');
 }.bind(this),
 error: function(err) {
        console.log(err);
        this.handleReportToggle();
        this.setState({
         openSnack: true,
         snackMessage: 'Failed to delete the skill.',
        });
 }.bind(this),
});

 

This concludes the workflow of how the skill deletion feature was implemented on the CMS skill page for admins. I hope you found this useful.

Resources

Continue Reading

Adding a feature to report skills in the CMS

A lot of interesting features were introduced in the SUSI.AI Skills CMS over the past few months but it lacked the functionality for users to be able to report skills which they find inappropriate or for any other reason. So an API was developed at the server to flag the skills as inappropriate and thus using this API endpoint an option was added at the skill page for each skill to mark the skill as inappropriate or report it. This data could be useful by admins to re-review the skills and see if something is wrong with it and it things seem out of place the skill can be removed or can be disabled.

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/reportSkill.json?model={model}&group={group}&skill={skill}&feedback={feedback message}&access_token={access_token}

 

Parameters :

  • Model
  • Group
  • Skill
  • Language
  • Feedback
  • Access token (taken from the session of the logged in user)

Sample API call :

/cms/reportSkill.json?model=general&group=Knowledge&skill=Anime Suggestions&feedback=Not good&access_token=6O7cqoMbzlClxPwg1is31Tz5pjVwo3

Displaying option to report on skill page

The option to report skill should be available at the skill page for each skill so we add a field in the skill details section to the skill page component which will only be visible to the logged in users and on clicking over this field we display a dialog with a text field, the user can enter the message or the reason for reporting the skill and then clicking on the submit button when the user is done writing or click on the cancel button in case the user changes their mind to report the skill. Once the message is submitted we run a function by passing in the feedback message which in turn hits the corresponding endpoint and posts the data on the server.

Import some required components

import Dialog from 'material-ui/Dialog';
import FlatButton from 'material-ui/FlatButton';
import TextField from 'material-ui/TextField';

 

Adding some variables to the component state which will help us decide when the report dialog is to be shown and the feedback message as the user types and some other relevant data.

this.state = {
   ...
   skillTag: '',
   showReportDialog: false,
   feedbackMessage: ''
   ...
}

 

Display the report feature only when the user is logged in.

{
   cookies.get('loggedIn') ? (
           ...
   ): ''
}

 

Adding some jsx to the component’s render function which includes a div in the skill details section and the Dialog component for the report message and confirmation and the dialog contains a text field to take report message and some actions which in our case is the send report action and the cancel report action.

<tr>
 <td>Report: </td>
 <td>
        <div
         style={{ color: '#108ee9', cursor: 'pointer' }}
         onClick={this.handleReportToggle}
        >
         Flag as inappropriate
        </div>
 </td>
 <Dialog
        title="Flag as inappropriate"
        actions={reportDialogActions}
        modal={false}
        open={this.state.showReportDialog}
        onRequestClose={this.handleReportToggle}
 >
        <TextField
         hintText="Leave a feedback message"
         floatingLabelText="Feedback message"
         multiLine
         floatingLabelFocusStyle={{
           color: 'rgb(66, 133, 244)',
         }}
         underlineFocusStyle={{
           borderColor: 'rgb(66, 133, 244)',
         }}
         fullWidth
         onChange={(event, val) =>
           this.saveReportFeedback(val)
         }
        />
 </Dialog>
</tr>

 

Clicking the report as inappropriate will change the state variable which decides whether the dialog is to be shown or not.

handleReportToggle = () => {
        this.setState({
             showReportDialog: !this.state.showReportDialog,
        });
};

 

Adding submit and cancel actions for the dialog menu and send them to the dialog as a prop.

const reportDialogActions = [
 <FlatButton
        label="Cancel"
        key="cancel"
        style={{ color: 'rgb(66, 133, 244)' }}
        onClick={this.handleReportToggle}
 />,
 <FlatButton
        label="Submit"
        key="submit"
        style={{ color: 'rgb(66, 133, 244)' }}
        onClick={this.handleReportSubmit}
 />,
];

Hitting the endpoint for report submit.

Adding onClick event handlers for dialog actions, for the cancel button we simply toggle the view of the report dialog and for the submit button we take the feedback message entered by the user in the text field and hit the endpoint for skill reporting and then we display a snackbar message about the status of report submit if it succeeded or failed.
Once the skill is submitted button is clicked we hit the report endpoint and post the feedback data and call the API through AJAX with appropriate params and thus this concludes the skill reporting workflow.

Build the URL for the AJAX request.

let reportUrl = `${urls.API_URL}/cms/reportSkill.json?model=${
        this.state.skillModel
 }&group=${this.state.skillGroup}&language=${
        this.state.skillLanguage
 }&skill=${this.state.skillTag}&feedback=${
        this.state.feedbackMessage
 }&access_token=${cookies.get('loggedIn')}`;

 

Make an AJAX request to the built API URL and in case the request is successful we set the conditional for displaying the snackbar to true and set the snackbar message depending on whether the skill was reported successfully or it failed.

$.ajax({
 url: reportUrl,
 dataType: 'jsonp',
 jsonp: 'callback',
 crossDomain: true,
 success: function(data) {
        self.handleReportToggle();
        self.setState({
           openSnack: true,
           snackMessage: 'Skill has been reported successfully.',
        });
 },
 error: function(e) {
        self.handleReportToggle();
        self.setState({
           openSnack: true,
           snackMessage: 'Failed to report the skill.',
        });
 },
});

 

This concludes the workflow of how the skill reporting feature was implemented on the CMS. I hope you found this useful.

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
Close Menu
%d bloggers like this: