Automatically deploy SUSI Web Chat on surge after Travis passes

We are using surge from the very beginning of this SUSI web chat and SUSI skill cms projects development. We used surge for provide preview links for Pull requests. Surge is really easy tool to use. We can deploy our static web pages really easily and quickly.  But If user had to change something in pull request user has to deploy again in surge and update the link. If we can connect this operation with travis ci we can minimise re-works. We can embed the deploying commands inside the travis.yml.

We can tell travis to make a preview link (surge deployment) if test cases are passed by embedding the surge deployment commands inside the travis.yml like below.

This is travis.yml file

sudo: required
dist: trusty
language: node_js
node_js:
 - 6
script:
 - npm test
after_success:
 - bash ./surge_deploy.sh
 - bash ./deploy.sh
cache:
 directories:
   - node_modules
branches:
 only:
   - master

Surge deployment commands are inside the “surge_deploy.sh” file.
In that we have to check the status of the pull request whether it is passing test cases or not. We can do it like below.

if [ "$TRAVIS_PULL_REQUEST" == "false" ]; then
   echo "Not a PR. Skipping surge deployment"
   exit 0
fi

Then we have to install surge in the environment. Then after install all npm packages and run build.

npm i -g surge
npm install
npm run build

Since there is a issue with displaying moving to child routes we have to take a copy of index.html file and name it as a 404.html.

cp ./build/index.html ./build/404.html

Then make two environment variables for your surge email address and surge token

export [email protected]
# surge Token (run ‘surge token’ to get token)
export SURGE_TOKEN=d1c28a7a75967cc2b4c852cca0d12206

Now we have to make the surge deployment URL (Domain). It should be unique so we made a URL that contains pull request number.

export DEPLOY_DOMAIN=https://pr-${TRAVIS_PULL_REQUEST}-susi-web-chat.surge.sh
surge --project ./build/ --domain $DEPLOY_DOMAIN;

Since all our static contents which made after the build process are in “build” folder we have to tell surge to get static html files from that.
Now make a pull request. you would find the deployment link in travis ci report after travis passed.

Expand the output of the surge_deploy.sh

You will find the deployment link as we defined in the surge_deploy.sh file

References:

  • Integrating with travis ci – https://surge.sh/help/integrating-with-travis-ci
  • React Routes to Deploy 404 page on gh-pages and surge – https://blog.fossasia.org/react-routes-to-deploy-404-page-on-gh-pages-and-surge/

Recognise new SUSI users and Welcome them

SUSI web chat application is up and running now. It gives better answers for most of the questions that users ask. But for new users application does not display a welcome message or introduction about the application. It is a distraction for new users. So a new requirement arrived that is to show a welcome message for new users or give them a introduction about the application.

To give a introduction or to show a welcome message we need to identify new users. For that I used cookies.
I added a new dialog to show welcome message and introductory video. Then placed below code in the DialogSection.js file which contains codes about every dialog-box of the application.

 <Dialog
          contentStyle={{ width: '610px' }}
          title="Welcome to SUSI Web Chat"
          open={this.props.tour}
        >
          
            width="560"
            height="315"
            src="https://www.youtube.com/embed/9T3iMoAUKYA"
            gesture="media"
            allow="encrypted-media"
            >
          
          <Close style={closingStyle} onTouchTap={this.props.onRequestCloseTour()} />
        </Dialog>

We already have installed ‘universal-cookie’ npm module in our application so we can use this module to identify cookies.
I used this way to check whether user is new or not.

           <DialogSection
             {...this.props}
             openLogin={this.state.showLogin}
              .
              .
              .
              onRequestCloseTour={()=>this.handleCloseTour}
              tour={!cookies.get('visited')}

           	/>

Now it shows dialog-box for each and every user we don’t need to display the welcome message to old users so we need to store a cookie in users computer.
I stored a cookie in users computer when user clicks on the close button of the welcome dialog-box.
Below function makes new cookie in users computer.

  handleCloseTour = ()=>{
   this.setState({
     showLogin: false,
     showSignUp: false,
     showThemeChanger: false,
     openForgotPassword: false,
     tour:false
    });
    cookies.set('visited', true, { path: '/' });
 }

 

Below line sets a cookie and { path : ’/’ } makes cookie accessible on all pages.

References:

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.

    How to Store Mobile Settings in the Server from SUSI Web Chat Settings Page

    While we are adding new features and capabilities to SUSI Web Chat application, we wanted to provide settings changing capability to SUSI users. SUSI team decided to maintain a settings page to give that capability to users.

    This is how it’s interface looks like now.

    In this blog post I’m going to add another setting category to our setting page. This one is for  saving mobile phone number and dial code in the server.

    UI Development:

    First we need to  add new category to settings page and it should be invisible when user is not logged in. Anonymous users should not get mobile phone category in settings page.

         let menuItems = cookies.get('loggedIn') ?
                <div>
                    <div className="settings-list">
                        <Menu
                            onItemTouchTap={this.loadSettings}
                            selectedMenuItemStyle={blueThemeColor}
                            style={{ width: '100%' }}
                            value={this.state.selectedSetting}
                        >
                           <MenuItem value='Mobile' className="setting-item" leftIcon={<MobileIcon />}>Mobile<ChevronRight className="right-chevron" /></MenuItem>
                            <hr className="break-line" />
                        </Menu>
                    </div>
                </div>
    

     

    Next we have to show settings UI when user clicks on the category name.

     if (this.state.selectedSetting === 'Mobile' && cookies.get('loggedIn')) {}
                    currentSetting = (
      <Translate text="Country/region : " />
                                <DropDownMenu maxHeight={300}
                   value={this.state.countryCode?this.state.countryCode:'US'}
    

     

    Show US if the state does not deines the country code

                                    onChange={this.handleCountryChange}>
                                    {countries}
                                </DropDownMenu>
    <Translate text="Phone number : " />
                                <TextField name="selectedCountry"
                                disabled={true}
                                value={countryData.countries[this.state.countryCode?this.state.countryCode:'US'].countryCallingCodes[0] }
                             	/>
                                <TextField name="serverUrl"
                                    onChange={this.handleTelephoneNoChange}
                                    value={this.state.phoneNo }
     />
    )}
    

     

    Then we need to get list of country names and country dial codes to show in the above drop down. We used country-data node module for that.

    To install country-data module use this  command.

    npm install --save country-data
    

     

    We have used it in the settings page as below.

    import countryData from 'country-data';
        	countryData.countries.all.sort(function(a, b) {
                if(a.name < b.name){ return -1};
                if(a.name > b.name){ return 1};
                return 0;
            });
            let countries = countryData.countries.all.map((country, i) => {
             	return (<MenuItem value={countryData.countries.all[i].alpha2} key={i} primaryText={ countryData.countries.all[i].name+' '+ countryData.countries.all[i].countryCallingCodes[0] } />);
            });
    

     

    First we sort the country data list from it’s name. After that we made a list of “”s from this list of data.
    Then we have to check whether the user changed or added the phone number and region (dial code).
    It handles by this function mentioned above. ( onChange={this.handleCountryChange}> and
    onChange={this.handleTelephoneNoChange} )

        handleCountryChange = (event, index, value) => {
            this.setState({'countryCode': value });
        }
    

     

    Then we have to get the phone number using below function.

        handleTelephoneNoChange = (event, value) => {
            this.setState({'phoneNo': value});
        }
    

     

    Next we have to update the function that triggers when user clicks the save button.

        handleSubmit = () => {
            let newCountryCode = !this.state.countryCode?
            this.intialSettings.countryCode:this.state.countryCode;
            let newCountryDialCode = !this.state.countryDialCode?
            this.intialSettings.countryDialCode:this.state.countryDialCode;
            let newPhoneNo = this.state.phoneNo;
            let vals = {
                countryCode: newCountryCode,
                countryDialCode: newCountryDialCode,
                phoneNo: newPhoneNo
    }
    let settings = Object.assign({}, vals);
    cookies.set('settings', settings);
     this.implementSettings(vals);
     }
    

     

    This code snippet stores Country Code, Country Dial code and phone no in the server.
    Now we have to update the Store. Here we are going to change UserPreferencesStore “UserPreferencesStore” .
    First we have to setup default values for things we are going to store.

    let _defaults = {
    	  CountryCode: 'US',
       	  CountryDialCode: '+1',
       	  PhoneNo: ''
    }
    

     

    Finally we have to update the dispatchToken to change and get these new data

    UserPreferencesStore.dispatchToken = ChatAppDispatcher.register(action => {
       switch (action.type) {
           case ActionTypes.SETTINGS_CHANGED: {
               let settings = action.settings;
               if(settings.hasOwnProperty('theme')){
                       _defaults.Theme = settings.theme;
               }
               if(settings.hasOwnProperty('countryDialCode')){
                   _defaults.countryDialCode = settings.countryDialCode;
               }
               if(settings.hasOwnProperty('phoneNo')){
                   _defaults.phoneNo = settings.phoneNo;
               }
               if(settings.hasOwnProperty('countryCode')){
                   _defaults.countryCode = settings.countryCode;
               }
               UserPreferencesStore.emitChange();
               break;
    }
    }
    

     

    Finally application is ready to store and update Mobile phone number and region code in the server.

    Resources:

    SUSI.AI Chrome Bot and Web Speech: Integrating Speech Synthesis and Recognition

    Susi Chrome Bot is a Chrome extension which is used to communicate with Susi AI. The advantage of having chrome extensions is that they are very accessible for the user to perform certain tasks which sometimes needs the user to move to another tab/site.

    In this blog post, we will be going through the process of integrating the web speech API to SUSI Chromebot.

    Web Speech API

    Web Speech API enables web apps to be able to use voice data. The Web Speech API has two components:

    Speech Recognition:  Speech recognition gives web apps the ability to recognize voice data from an audio source. Speech recognition provides the speech-to-text service.

    Speech Synthesis: Speech synthesis provides the text-to-speech services for the web apps.

    Integrating speech synthesis and speech recognition in SUSI Chromebot

    Chrome provides the webkitSpeechRecognition() interface which we will use for our speech recognition tasks.

    var recognition = new webkitSpeechRecognition();
    

     

    Now, we have a speech recognition instance recognition. Let us define necessary checks for error detection and resetting the recognizer.

    var recognizing;
    
    function reset() {
    recognizing = false;
    }
    
    recognition.onerror = function(e){
    console.log(e.error);
    };
    
    recognition.onend = function(){
    reset();
    };
    

     

    We now define the toggleStartStop() function that will check if recognition is already being performed in which case it will stop recognition and reset the recognizer, otherwise, it will start recognition.

    function toggleStartStop() {
        if (recognizing) {
          recognition.stop();
          reset();
        } else {
          recognition.start();
          recognizing = true;
        }
    }
    

     

    We can then attach an event listener to a mic button which calls the toggleStartStop() function to start or stop our speech recognition.

    mic.addEventListener("click", function () {
        toggleStartStop();
    });
    

     

    Finally, when the speech recognizer has some results it calls the onresult event handler. We’ll use this event handler to catch the results returned.

    recognition.onresult = function (event) {
        for (var i = event.resultIndex; i < event.results.length; ++i) {
          if (event.results[i].isFinal) {
            textarea.value = event.results[i][0].transcript;
            submitForm();
          }
        }
    };
    

     

    The above code snipped tests for the results produced by the speech recognizer and if it’s the final result then it sets textarea value with the result of speech recognition and then we submit that to the backend.

    One problem that we might face is the extension not being able to access the microphone. This can be resolved by asking for microphone access from an external tab/window/iframe. For SUSI Chromebot this is being done using an external tab. Pressing on the settings icon makes a new tab which then asks for microphone access from the user. This needs to be done only once, so that does not cause a lot of trouble.

    setting.addEventListener("click", function () {
    chrome.tabs.create({
    url: chrome.runtime.getURL("options.html")
    });
    });navigator.webkitGetUserMedia({
    audio: true
    }, function(stream) {
    stream.stop();
    }, function () {
    console.log('no access');
    });
    

     

    In contrast to speech recognition, speech synthesis is very easy to implement.

    function speakOutput(msg){
        var voiceMsg = new SpeechSynthesisUtterance(msg);
        window.speechSynthesis.speak(voiceMsg);
    }
    

     

    This function takes a message as input, declares a new SpeechSynthesisUtterance instance and then calls the speak method to convert the text message to voice.

    There are many properties and attributes that come with this speech recognition and synthesis interface. This blog post only introduces the very basics.

    Resources

     

    Store User’s Personal Information with SUSI

    In this blog, I discuss how SUSI.AI stores personal information of it’s users. This personal information is mostly about usernames/links to different websites like LinkedIn, GitHub, Facebook, Google/Gmail etc. To store such details, we have a dedicated API. Endpoint is :

    https://api.susi.ai/aaa/storePersonalInfo.json
    

    In this API/Servlet, storing the details and getting the details, both the aspects are covered. At the time of making the API call, user has an option either to ask the server for a list of available store names along with their values or request the server to store the value for a particular store name. If a store name already exists and a client makes a call with new/updated value, The servlet will update the value for that particular store name.

    The reason you are looking at minimal user role as USER is quite obvious, i.e. these details correspond to a particular user. Hence neither we want someone writing such information anonymously nor we want this information to be visible to anonymous user until allowed by the user.

    In the next steps, we start evaluating the API call made by the client. We look at the combination of the parameters present in the request. If the request is to fetch list of available stores, server first checks if Accounting object even has a JSONObject for “stores” or not. If not found, it sends an error message “No personal information is added yet.” and error code 420. Prior to all these steps, server first generates an accounting object for the user. If found, details are encoded as JSONObject’s parameter. Look at the code below to understand things fairly.

    Accounting accounting = DAO.getAccounting(authorization.getIdentity());
            if(post.get("fetchDetails", false)) {
                if(accounting.getJSON().has("stores")){
                    JSONObject jsonObject = accounting.getJSON().getJSONObject("stores");
                    json.put("stores", jsonObject);
                    json.put("accepted", true);
                    json.put("message", "details fetched successfully.");
                    return new ServiceResponse(json);
                } else {
                    throw new APIException(420, "No personal information is added yet.");
                }
            }
    

    If the request was not to fetch the list of available stores, It means client wants server to save a new field or update a previous value for that of a store name. A combination of If-else evaluates whether the call even contains required parameters.

    if (post.get(“storeName”, null) == null) {
    throw new APIException(422, “Bad store name encountered!”);
    }

    String storeName = post.get(“storeName”, null);
    if (post.get(“value”, null) == null) {
    throw new APIException(422, “Bad store name value encountered!”);
    }

    If request contains all the required data, then store name & value are extracted as key-value pair from the request.

    In the next steps, since the server is expected to store list of the store names for a particular user, First the identity is gathered from the already present authorization object in “serviceImpl” method. If the server finds a “null” identity, It throws an error with error code 400 and error message “Specified User Setting not found, ensure you are logged in”.

    Else, server first checks if a JSONObject with key “stores” exists or not. If not, It will create an object and will put the key value pair in the new JSONObject. Otherwise it would anyways do so.

    Since these details are for a particular account (i.e. for a particular user), these are placed in the Accounting.json file. For better knowledge, Look at the code snippet below.

    if (accounting.getJSON().has("stores")) {
                    accounting.getJSON().getJSONObject("stores").put(storeName, value);
                } else {
                    JSONObject jsonObject = new JSONObject(true);
                    jsonObject.put(storeName, value);
                    accounting.getJSON().put("stores", jsonObject);
                }
    
                json.put("accepted", true);
                json.put("message", "You successfully updated your account information!");
                return new ServiceResponse(json);
    

    Additional Resources :

    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.

    Enhancing Settings Menu in SUSI Webchat using Material-UI Menu React Component

    Material-UI is a great library for react developers since you can directly use the material components in your app. The SUSI.AI Webchat uses Material-UI (https://github.com/callemall/material-ui). In this blog we’ll see how a Menu component is implemented in settings page of susi’s web chat app.

    Menu & MenuItem Component

    Menu component allows you execute an action on selecting from a list. In the settings menu we want to change the state variable to which setting details is binded (ie. on change of state variable the setting details corresponding to selected menu changes).

    A simple menu component is defined like this –

    <Menu>
            <MenuItem primaryText="Item 1" />
            <MenuItem primaryText="Item 2" />
            <MenuItem primaryText="Item 3" />
            <MenuItem primaryText="Item 4" />
    </Menu>
    
    

    Menu component and MenuItem accepts some properties too. Like if you add disable={true} then the MenuItem it will disable onClick action of MenuItem.

    Adding Icon

    MenuItem can contain icons through defining leftIcon and rightIcon properties.

    Example snippet:

    <MenuItem primaryText="Download" leftIcon={<Download />} />
    

    Output:

    You can also add style to the leftIcon or rightIcon in style property of MenuItem.

    Active State of MenuItem

    You must be interested in assigning a different style to the active MenuItem in the Menu. This can be achieved through selectedMenuItemStyle property. It allows overriding the inline-style property of selected MenuItem.

    To implement this we need to use the concept of ‘controlled component’. Each MenuItem has to be assigned a value. Also, assign the Menu with a state variable.

    <Menu 
     selectedMenuItemStyle={{color: '#FFFFFF'} }   
     value={this.state.selectedItem} > 
        <MenuItem primaryText="Item 1" value='1'/> 
        <MenuItem primaryText="Item 2" value='2' /> 
    </Menu>
    

    This way the state variable will control Menu’s value and the selectedMenuItemStyle property will override the inline-style of the corresponding MenuItem.

    Implement onClick function for MenuItem and change the state value.

    This way you can add style to active MenuItem.

    You can see the demo of how it was implemented in chat settings at https://chat.susi.ai/settings

    Also, you can check out the github repo https://github.com/fossasia/chat.susi.ai

    Note – Make sure that you define the state change function outside render else it will get a warning like this.

    Warning: setState(…): Cannot update during an existing state transition (such as within `render` or another component’s constructor). Render methods should be a pure function of props and state; constructor side-effects are an anti-pattern, but can be moved to `componentWillMount`.

    This will result in abnormal behaviour in runtime. So keep that mind in while creating the function to change the state.

    Resources

     

    List all the Users Registered on SUSI.AI

    In this blog, I’ll be telling on how SUSI admins can access list of all the registered users from SUSI-server. Following this, they may modify/edit user role of any registered user.

    What is User Role?

    A UserRole defines the servlet access right. Not all users are allowed to access all the data and services. For  example, To list all the users, minimal user role expected is ADMIN. This classification of users are inspired by the wikipedia User Access Levels, see https://en.wikipedia.org/wiki/Wikipedia:User_access_levels.While querying SUSI, Users are classified into 7 different categories, namely :

    • BOT
    • ANONYMOUS
    • USER  
    • REVIEWER
    • ACCOUNTCREATOR
    • ADMIN
    • BUREAUCRAT

    * Please see that these are as of the date of publish of this blog. These are subject to change, which is very unlikely.

    All the users who are not logged in but interacting with SUSI are anonymous users. These are only subject to chat with SUSI, login, signup or may use forgot password service. Once a user login to the server, a token is generated and sent back to client to maintain the identity, hence acknowledging them. Privileged users are those who have special rights with them. These are more like moderators with much special rights than any other user. At the top level of the hierarchy are the admins. These users have more rights than anyone. They can change role of any other user, override decision of any privileged user as well.

    Let us now look at the control flow of this.

    First things first, make a component of User List in the project. Let us name it ListUsers and since it has to be accessible by those users who possess ADMIN rights, you will find it enclosed in Admin package in components folder. Open up

    index.js file, import Listusers component  and add route to it in the following way :

    ...//other import statements
    import ListUser from "./components/Admin/ListUser/ListUser";
    ...//class definition and other methods
    <Route path="/listUser" component={ListUser}/>
    //other routes defined
    

    Find a suitable image for “List Users” option and add the option for List Users in static appbar component along with the image. We have used Material UI’s List image in our project.

    ...// other imports
    
    import List from 'material-ui/svg-icons/action/list';
    
    Class and method definition
    
    <MenuItem primaryText="List Users"
              onTouchTap={this.handleClose}
              containerElement={<Link to="/listUser" />}
                    rightIcon={<List/>}
          />
    
    ...//other options in top right corner menu
    

    Above code snippet will add an option to redirect admins to ‘/listUsers’ route. Let us now have a closer look at functionality of both client and server. By now you must have known what ComponentDidMount does. {If not, I’ll tell you. This is a method which is given first execution after the page is rendered. For more information, visit this link}. As mentioned earlier as well that this list will be available only for admins and may be even extended for privileged users but not for anonymous or any other user, an AJAX call is made to server in ComponentDidMount of ‘listuser’ route which returns the base user role of current user. If user is an Admin, another method, fetchUsers() is called.

    let url;
            url = "http://api.susi.ai/aaa/account-permissions.json";
            $.ajax({
                url: url,
                dataType: 'jsonp',
                jsonpCallback: 'py',
                jsonp: 'callback',
                crossDomain: true,
                success: function (response) {
                    console.log(response.userRole)
                    if (response.userRole !== "admin") {
                        console.log("Not an admin")
                    } else {
                        this.fetchUsers();
                        console.log("Admin")
                    }
                }.bind(this),
    });
    

    In fetchUsers method, an AJAX call is made to server which returns username in JSONArray. The response looks something likes this :

    {
    	"users" : {
    		"email:""[email protected]",
    ...
    },
    "Username":["[email protected]", "[email protected]"...]
    }
    

    Now, only rendering this data in a systematic form is left. To give it a proper look, we have used material-ui’s table. Import Table, TableBody, TableHeader,

       TableHeaderColumn, TableRow, TableRowColumn from material-ui/table.

    In fetchUsers method, response is catched in data Oblect. Now the keys are extracted from the JSON response and mapped with an array. Iterating through array received as username array, we get list of all the registered users. Now, popuulate the data in the table you generated.

    return (
                            <TableRow key={i}>
                                <TableRowColumn>{++i}>
                                <TableRowColumn>{name}</TableRowColumn>
                                <TableRowColumn> </TableRowColumn>
                                <TableRowColumn> </TableRowColumn>
                                <TableRowColumn> </TableRowColumn>
                                <TableRowColumn> </TableRowColumn>
                            </TableRow>
                        )
    

    Above piece of code may help you while populating the table. These details are returned from susi server which gets a list of all the registered in the following manner. First, it checks if base url of this user is something apart from admin. If not, it returns error which may look like this :

    Failed to load resource: the server responded with a status of 401 (Base user role not sufficient. Your base user role is 'ANONYMOUS', your user role is 'anonymous')
    

    Otherwise, it will generate a client identity, use to to get an authorization object which will loop through authorization.json file and return all the users encoded as JSONArray.

    Additional Resources

    1. Official Material UI Documentation on Tables from marterial-ui
    2. Answer by Marco Bonelli on Stackoverflow on How to map JSON response in JavaScript?
    3. Answer by janpieter_z on Stackoverflow – on Render JSON data in ReactJS table

    SUSI.AI User Roles and How to Modify Them

    In this blog, I discuss what is ‘user-role’ in SUSI.AI, what are the various roles and how SUSI admins can modify/update a user’s roles.

    What is User Role?

    A UserRole defines the servlet access right. Not all users are allowed to access all the data and services. For  example, To list all the users, minimal user role expected is ADMIN. This classification of users are inspired by the wikipedia User Access Levels, see https://en.wikipedia.org/wiki/Wikipedia:User_access_levels.While querying SUSI, Users are classified into 7 different categories, namely :

    • BOT
    • ANONYMOUS
    • USER  
    • REVIEWER
    • ACCOUNTCREATOR
    • ADMIN
    • BUREAUCRAT

    * Please see that these are as of the date of publish of this blog. These are subject to change, which is very unlikely.

    If SUSI is active as a bot on some bot integrated platform (like line or kik), the user role assigned to it will be that of BOT. This user role just has technical access to the server.

    All the users who are not logged in but interacting with SUSI are ANONYMOUS users. These are only subject to chat, login and signup. They may use forgot password service and reset password services as well.

    Once a user login to the server, a token is generated and sent back to client to maintain the identity, hence acknowledging them as USER(s).

    Users with role assigned as “REVIEWERS” are expected to manage the Skill CMS. There might be some dispute or conflict in a skill. REVIEWERS then take the access of skill data and finalise the conflict there itself for smooth functionality.

    ADMIN users are those who have special rights with them. These are more like moderators with much special rights than any other user.

    At the top level of the hierarchy are the BUREAUCRATS. These users have more rights than anyone. They can change role of any other user, override decision of any ADMIN user as well. Both admins and bureaucrats have the access to all the settings file on the server. They not only can look at the list, but also download and upload them. Now these users also have right to upgrade or downgrade any other user as well.

    All these user roles are defined in UserRole.java file.

    In each request received by the server, the user role of user making the request is compared with the minimal user role in getMinimalUserRole() method. This method is defined in AbstractAPIHandler which validates if a user is allowed to access a particular servlet or not.

    private void process(HttpServletRequest request, HttpServletResponse response, Query query) throws ServletException, IOException {
    	// object initialisation and comparsions
    // user authorization: we use the identification of the user to get the assigned authorization
            Authorization authorization = DAO.getAuthorization(identity);
    
            if (authorization.getUserRole().ordinal() < minimalUserRole.ordinal()) {
            	response.sendError(401, "Base user role not sufficient. Your base user role is '" + authorization.getUserRole().name() + "', your user role is '" + authorization.getUserRole().getName() + "'");
    			return;
            }
    // evaluations based on other request parameters.
    }
    

    Now that we know about what User Roles actually are, let us look at how the servlet which allows the users {with at least ADMIN login} to change user role of some other user works.

    In the request, 2 parameters are expected. These are :

    • user : email id of the user whose role has to be changed.
    • role : new role which will be assigned to this user.

    Using a switch case, we identify the user role which is requested. If role is found to be null or any other value apart from “bot”, “anonymous”, “user”, “reviewer”, “accountcreator”, “admin” or “bureaucrat”, an error with error code 400 and error message “Bad User role” is thrown.

    In the next steps, server generates client identity in order to get the corresponding Authorization object. If the user is not found in the database, again an error is thrown with error code 400 and error message “role not found

    ClientCredential credential = new ClientCredential(ClientCredential.Type.passwd_login, userTobeUpgraded);
                ClientIdentity identity = new ClientIdentity(ClientIdentity.Type.email, credential.getName());
                if (!DAO.hasAuthorization(identity)) {
                    throw new APIException(400, "Username not found");
                }
    

    By now, server is clear with the user identity and new role to be assigned. Since the user role is defined in authorization.json file, we overwrite the existing user role and finally server sends back the new user role of the use

    Authorization auth = DAO.getAuthorization(identity);
                try {
                    auth.setUserRole(userRole);
                } catch (IllegalArgumentException e) {
                    throw new APIException(400, "role not found");
                }
    
                // Print Response
                result.put("newDetails", auth.getJSON());
                result.put("accepted", true);
                result.put("message", "User role changed successfully!!");
                return new ServiceResponse(result);