Showing Query Description in Infobox

Knowledge Graph is used to give a brief introduction about the search query instantly. But the information of knowledge graph becomes more useful when it is defined by one or two line description. Google currently shows a description of search query in its knowledge graph which makes the search results more meaningful. In this blog I will describe how I have description for query using Wikidata API implemented a similar functionality in Susper.

Steps:

1.Adding a function to fetch data from Wikidata in Service:

getQueryDescription(searchquery) {
   const params = new URLSearchParams();
   params.set('origin', '*');
   params.set('action', 'wbsearchentities');
   params.set('search', searchquery);
   params.set('language', 'en');
   params.set('format', 'json');
   const headers = new Headers({ 'Accept': 'application/json' });
   const options = new RequestOptions({ headers: headers, search: params });
   return this.http.get(this.descriptionURL, options).map(res =>
     res.json()
     ).catch(this.handleError);
 }

 

2.Creating an effect for query description:

To implement the logic of getting query description from the results returned by the service is made in knowledge.ts file which creates an effect for knowledge description.

Here is the effect for query description

this.knowledgeservice.getQueryDescription(querypay.query)
       .takeUntil(nextSearch$)
       .subscribe((response) => {
         if (response['search']) {
           this.store.dispatch(new knowledge.DescriptionAction(response));
           return empty();
            } else {    this.store.dispatch(new knowledge.DescriptionAction([]));
                         return empty();
                 }
});

 

The response is then dispatched to the corresponding knowledge action.

3.Creating an Action for Query description:

After dispatching the query description logic to the action, we must have an action which will correspond to get the query description. Here is the implementation of Query Description Action in knowledge.ts action file.

export const ActionTypes = {
 CONTENT_CHANGE: type('[Knowledge] Content Change'),
 IMAGE_CHANGE: type('[Knowledge] Image Change'),
 DESCRIPTION_CHANGE: type('[Knowledge] Description Change')
};
export class SearchContentAction implements Action {
 type = ActionTypes.CONTENT_CHANGE;
 constructor(public payload: object) {}
}
export class SearchImageAction implements Action {
 type = ActionTypes.IMAGE_CHANGE;
 constructor(public payload: object) {}
}
export class DescriptionAction implements Action {
 type = ActionTypes.DESCRIPTION_CHANGE;
 constructor(public payload: object) {}
}
export type Actions
 = SearchContentAction | SearchImageAction | DescriptionAction;

 

DescriptionAction is the action for Query Description and is then exported for the reducer.

4.Creating a case for query description in Knowledge reducer:

We must have a case to deal in reducer function to deal with query description action and here is the code for the case:

case knowledge.ActionTypes.DESCRIPTION_CHANGE: {
     const description_response = action.payload;
     return Object.assign({}, state, {
       content_response: state.content_response,
       image_response: state.image_response,
       description_response: description_response
     });
   }

 

The new state is then exported to the store:

export const getDescriptionResponse = (state: State) => state.description_response;

 

5.Updating the contents in store:

To update the store we just retrieve the getKnowledgeState and read its description_response and export it.

export const getDescriptionResponse = (state: State) => state.description_response;

 

6.Fetching Query Description from store in infobox component:

Now fetching results is very simple, we just need to select the getDescription from the store at store it in description_response$ and then we extract the query description and store in a local variable querydescription

this.description_response$ = store.select(fromRoot.getDescription);
   this.description_response$.subscribe(res => {
     if (res['search']) {
       this.querydescription = res['search'][0]['description'];
     }
   }
   );

 

7.Displaying query description on the result page:

Now displaying the query description on our page is very simple we just need to use our querydescription variable.

<p class="query_description">{{this.querydescription}}</p><hr width="50%" align="left" *ngIf="this.image">

Resources

  1. Wikidata API: https://www.wikidata.org/w/api.php
  2. Angular Services:  https://angular.io/tutorial/toh-pt4
  3. Corresponding Pull Request: https://github.com/fossasia/susper.com/pull/1115

 

 

 

Continue ReadingShowing Query Description in Infobox

Countrywise Usage Analytics of a Skill in SUSI.AI

The statistics regarding which country the skills are being used is quite important. They help in updating the skill to support the native language of those countries. SUSI.AI must be able to understand as well as reply in its user’s language. So mainly the server side and some client side (web client) implementation of country wise skill usage statistics is explained in this blog.

Fetching the user’s location on the web client

  1. Add a function in chat.susi.ai/src/actions/API.actions.js to fetch the users location. The function makes a call to freegeoip.net API which returns the client’s location based on its IP address. So country name and code are required for country wise usage analytics.

export function getLocation(){
  $.ajax({
    url: 'https://cors-anywhere.herokuapp.com/http://freegeoip.net/json/',
    success: function (response) {
      _Location = {
        lat: response.latitude,
        lng: response.longitude,
        countryCode: response.country_code,
        countryName: response.country_name
      };
    },
  });
}
  1. Send the location parameters along with the query while fetching reply from SUSI server in chat.json API. The parameters are country_name and country_code.

if(_Location){
	url += '&latitude='+_Location.lat+'&longitude='+_Location.lng+'&country_code='+_Location.countryCode+'&country_name='+_Location.countryName;
}

Storage of Country Wise Skill Usage Data on SUSI Server

  1. Create a countryWiseSkillUsage.json file to store the country wise skill usage stats and make a JSONTray object for that in src/ai/susi/DAO.java file. The JSON file contains the country name, country code and the usage count in that country.
  1. Modify the src/ai/susi/server/api/susi/SusiService.java file to fetch country_name and country_code from the query parameters and pass them SusiCognition constructor.

String countryName = post.get("country_name", "");
String countryCode = post.get("country_code", "");
...

SusiCognition cognition = new SusiCognition(q, timezoneOffset, latitude, longitude, countryCode, countryName, language, count, user.getIdentity(), minds.toArray(new SusiMind[minds.size()]));
  1. Modify the src/ai/susi/mind/SusiCognition.java file to accept the countryCode and countryName in the constructor parameters. Check which skill is being currently used for the response and update the skill usage stats for that country in countryWiseSkillUsage.json. Call the function updateCountryWiseUsageData() to update the skill usage data.

if (!countryCode.equals("") && !countryName.equals("")) {
    List<String> skills = dispute.get(0).getSkills();
    for (String skill : skills) {
        try {
            updateCountryWiseUsageData(skill, countryCode, countryName);
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }
}

The updateCountryWiseUsageData() function accepts the skill path , country name and country code. It parses the skill path to get the skill metadata like its model name, group name, language etc. The function then checks if the country already exists in the JSON file or not. If it exists then it increments the usage count by 1 else it creates an entry for the skill in the JSON file and initializes it with the current country name and usage count 1.

for (int i = 0; i < countryWiseUsageData.length(); i++) {
  countryUsage = countryWiseUsageData.getJSONObject(i);
  if (countryUsage.get("country_code").equals(countryCode)) {
    countryUsage.put("count", countryUsage.getInt("count")+1);
    countryWiseUsageData.put(i,countryUsage);
  }
}

API to access the Country Wise Skill Usage Data

  1. Create GetCountryWiseSkillUsageService.java file to return the usage stats stored in countryWiseSkillUsage.json

public ServiceResponse serviceImpl(Query call, HttpServletResponse response, Authorization rights, final JsonObjectWithDefault permissions) {
        
  ...  // Fetch the query parameters
  JSONArray countryWiseSkillUsage = languageName.getJSONArray(skill_name);
  return new ServiceResponse(result);
}
  1. Add the API file to src/ai/susi/server/api/susi/SusiServer.java

services = new Class[]{
	...
	//Skill usage data
	GetCountryWiseSkillUsageService.class
	...
}

 

Endpoint : /cms/getCountryWiseSkillUsage.json

Parameters

  • model
  • group
  • language
  • Skill

Sample query: /cms/getCountryWiseSkillUsage.json?model=general&group=Knowledge&language=en&skill=aboutsusi

Sample response:

{  
   "skill_usage":[  
      {  
         "country_code":"MYS",
         "country_name":"Malaysia",
         "count":1
      },
      {  
         "country_code":"MYS",
         "country_name":"Malaysia",
         "count":1
      }
   ],
   "session":{  
      "identity":{  
         "type":"host",
         "name":"162.158.154.147_81c88a10",
         "anonymous":true
      }
   },
   "skill_name":"ceo",
   "accepted":true,
   "message":"Country wise skill usage fetched"
}

Resources

Continue ReadingCountrywise Usage Analytics of a Skill in SUSI.AI

Converting Drupal into WordPress

Converting Drupal into WordPress

WordPress has the ability to update automatically, while our existing Drupal systems require time to take care manually. This is the main reason for our switch.

So the first thing I did was to google for a plugin, I found about the fg-drupal2wordpress plugin however it only passes the posts, and doesn’t import users/media/tags unless you pay about 40€, so naturally I stayed off from it, my second intuition was to look it up on Github, and what I found it was also a plugin and it was open source, I used it, and it worked for almost anything except the images.

original website, even some images are missing now because the website is not on /

The gallery

Challenges

The thing with the images is that the Drupal that I was given used a very old version of the drupal “Images” add-on, so the plugin converts these images as “posts”, but they’re not actually converted as images.

as you can see, some posts are blank

In drupal, every blogpost and image are stored as a Node

sql query result of “SELECT nid,file_usage.fid,node.title,files.filepath FROM perspektive.node INNER JOIN perspektive.file_usage ON perspektive.node.nid = perspektive.file_usage.id INNER JOIN perspektive.files ON perspektive.file_usage.fid = perspektive.files.fid”, as you can see we can see where the images are stored, what node id do they have, and what file id they have according to Drupal.

In order to pass these images to wordpress you need a script that reads every image location, copies it into the correspondent wp-content/uploads/YYYY/MM/ directory, makes a thumbnail of every image, and you have to add around 5 columns to the wp_postmeta table, if you do that, you’ll effectively import all the images to the media file in wordpress, and that is without counting the tags, and the author of every image! It is not an easy task!

Proposals

One solution to this problem was to add an `img` tag for every of these posts so that every posts has their own image, and this works in principle, you might even add an “image” tag to every image post, however they’re not actually appended on the wordpress media library, so if you want a page to act as a gallery, it won’t work in that sense. What would you need to do is to add every image as a media gallery on wordpress, you can do this, but you have to convert a lot of images, and it’s not easy because you also have to create thumbnails, you could kind of automate this process using WP-CLI, and add a lot of images with it, the problem would be that the images keep their post id or node id on drupal, and copying the information that is contained on the drupal database, it’s not hard but it takes time to do.

Another one was to actually update the open source plugin myself which is what I started to do, so I started digging into its code to find out how it worked, I probably found some errors while investigating, and I also searched for forks of the project since apparently the original author was not replying to PRs or Issues, the project had been abandoned, so if I needed help, I could only help myself.

Thoughts

I always knew about CMS, and how they worked, but never got into them because I never felt I was fully of control or that I understood the website, however now that I investigated about this issue, I feel I have a more intrinsic understanding about Drupal and WordPress, and I might be more prompt to use them myself in the case that I need to.

I find it surprising that it is really hard to find converters or conversors for SQL to other formats (like JSON), there were some web converters but they were web only and couldn’t handle the data size. At the end I used PHPMyAdmin, which was probably what I was looking for, and I did learn a ton about SQL.

Resources

Author: Mario Behling, Website: https://web.archive.org/web/20060615070835/http://www.perspektive89.com:80, Title: Perspektive89

Author: Dries Buytaert, https://api.drupal.org/api/drupal, Title: Drupal API reference

Author: WordPress, Website: https://developer.wordpress.org/reference/, Title: WordPress Reference

Continue ReadingConverting Drupal into WordPress

Open Event Frontend – Settings Service

This blog illustrates how the settings of a particular user are obtained in the Open Event Frontend web app. To access the settings of the user a service has been created which fetches the settings from the endpoint provided by Open Event Server.

Let’s see why a special service was created for this.

Problem

In the first step of the event creation wizard, the user has the option to link Paypal or Stripe to accept payments. The option to accept payment through Paypal or Stripe was shown to the user without checking if it was enabled by the admin in his settings. To solve this problem, we needed to access the settings of the admin and check for the required conditions. But since queryRecord() returned a promise we had to re-render the page for the effect to show which resulted in this code:

canAcceptPayPal: computed('data.event.paymentCurrency', function() {     this.get('store').queryRecord('setting', {}) .then(setting => { this.set('canAcceptPayPal', (setting.paypalSandboxUsername || setting.paypalLiveUsername) && find(paymentCurrencies, ['code', this.get('data.event.paymentCurrency')]).paypal); this.rerender(); });

This code was setting a computed property inside it and then re-rendering which is bad programming and can result in weird bugs.

Solution

The above problem was solved by creating a service for settings. This made sense as settings would be required at other places as well. The file was called settings.js and was placed in the services folder. Let me walk you through its code.

  • Extend the default Service provided by Ember.js and initialize store, session, authManager and _lastPromise.
import Service, { inject as service } from '@ember/service';
import { observer } from '@ember/object';

export default Service.extend({

 store       : service(),
 session     : service(),
 authManager : service(),

_lastPromise: Promise.resolve(),
  • The main method which fetches results from the server is called _loadSettings(). It is an async method. It queries setting from the server and then iterates through every attribute of the setting model and stores the corresponding value from the fetched result.
/**
* Load the settings from the API and set the attributes as properties on the service
*
* @return {Promise<void>}
* @private
*/
async _loadSettings() {
 const settingsModel = await this.get('store').queryRecord('setting', {});
 this.get('store').modelFor('setting').eachAttribute(attributeName => {
   this.set(attributeName, settingsModel.get(attributeName));
 });
},
  • The initialization of the settings service is handled by initialize(). This method returns a promise.
/**
* Initialize the settings service
* @return {*|Promise<void>}
*/
initialize() {
 const promise = this._loadSettings();
 this.set('_lastPromise', promise);
 return promise;
}
  • _authenticationObserver observes for changes in authentication changes and reloads the settings as required.
/**
* Reload settings when the authentication state changes.
*/
_authenticationObserver: observer('session.isAuthenticated', function() {
 this.get('_lastPromise')
   .then(() => this.set('_lastPromise', this._loadSettings()))
   .catch(() => this.set('_lastPromise', this._loadSettings()));
}),

The service we created can be directly used in the app to fetch the settings for the user. To solve the Paypal and Stripe payment problem described above, we use it as follows:

canAcceptPayPal: computed('data.event.paymentCurrency', 'settings.paypalSandboxUsername', 'settings.paypalLiveUsername', function() {
 return (this.get('settings.paypalSandboxUsername') || this.get('settings.paypalLiveUsername')) && find(paymentCurrencies, ['code', this.get('data.event.paymentCurrency')]).paypal;
}),

canAcceptStripe: computed('data.event.paymentCurrency', 'settings.stripeClientId', function() {
 return this.get('settings.stripeClientId') && find(paymentCurrencies, ['code', this.get('data.event.paymentCurrency')]).stripe;
}),

Thus, there is no need to re-render the page and dangerously set the property inside its computed method.

References

Continue ReadingOpen Event Frontend – Settings Service

Open Event Server – Export Sessions as CSV File

FOSSASIA‘s Open Event Server is the REST API backend for the event management platform, Open Event. Here, the event organizers can create their events, add tickets for it and manage all aspects from the schedule to the speakers. Also, once he/she makes his event public, others can view it and buy tickets if interested.

The organizer can see all the sessions in a very detailed view in the event management dashboard. He can see the statuses of all the sessions. The possible statuses are pending, accepted, confirmed and rejected. He/she can take actions such as accepting/rejecting the sessions.

If the organizer wants to download the list of all the sessions as a CSV file, he or she can do it very easily by simply clicking on the Export As CSV button in the top right-hand corner.

Let us see how this is done on the server.

Server side – generating the Sessions CSV file

Here we will be using the csv package provided by python for writing the csv file.

import csv
  • We define a method export_sessions_csv which takes the sessions to be exported as a CSV file as the argument.
  • Next, we define the headers of the CSV file. It is the first row of the CSV file.
def export_sessions_csv(sessions):
   headers = ['Session Title', 'Session Speakers',
              'Session Track', 'Session Abstract', 'Created At', 'Email Sent']
  • A list is defined called rows. This contains the rows of the CSV file. As mentioned earlier, headers is the first row.
rows = [headers]
  • We iterate over each session in sessions and form a row for that session by separating the values of each of the columns by a comma. Here, every row is one session.
  • As a session can contain multiple speakers we iterate over each speaker for that particular session and append each speaker to a string. ‘;’ is used as a delimiter. This string is then added to the row.
  • The newly formed row is added to the rows list.
for session in sessions:
   if not session.deleted_at:
       column = [session.title + ' (' + session.state + ')' if session.title else '']
       if session.speakers:
           in_session = ''
           for speaker in session.speakers:
               if speaker.name:
                   in_session += (speaker.name + '; ')
           column.append(in_session[:-2])
       else:
           column.append('')
       column.append(session.track.name if session.track and session.track.name else '')
       column.append(strip_tags(session.short_abstract) if session.short_abstract else '')
       column.append(session.created_at if session.created_at else '')
       column.append('Yes' if session.is_mail_sent else 'No')
       rows.append(column)
  • rows contains the contents of the CSV file and hence it is returned.
return rows
  • We iterate over each item of rows and write it to the CSV file using the methods provided by the csv package.
writer = csv.writer(temp_file)
from app.api.helpers.csv_jobs_util import export_sessions_csv
content = export_sessions_csv(sessions)
for row in content:
   writer.writerow(row)

Obtaining the Sessions CSV file:

Firstly, we have an API endpoint which starts the task on the server.

GET - /v1/events/{event_identifier}/export/sessions/csv

Here, event_identifier is the unique ID of the event. This endpoint starts a celery task on the server to export the sessions of the event as a CSV file. It returns the URL of the task to get the status of the export task. A sample response is as follows:

{
  "task_url": "/v1/tasks/b7ca7088-876e-4c29-a0ee-b8029a64849a"
}

The user can go to the above-returned URL and check the status of his/her Celery task. If the task completed successfully he/she will get the download URL. The endpoint to check the status of the task is:

and the corresponding response from the server –

{
  "result": {
    "download_url": "/v1/events/1/exports/http://localhost/static/media/exports/1/zip/OGpMM0w2RH/event1.zip"
  },
  "state": "SUCCESS"
}

The file can be downloaded from the above-mentioned URL.

Resources

Continue ReadingOpen Event Server – Export Sessions as CSV File

Disable editing for non-editable skills for non-admin users

As the Skills in SUSI Skill CMS are publicly editable, any user has the access to edit them. Hence, there needed to be a better control over who can edit the Skills in CMS. We needed to implement a feature to allow Admins and higher user roles to change the status of a Skill to non-editable. The subsequent implementation on CMS would require disabling editing for non-editable Skills for non-admin users. This blog post explains how this feature has been implemented in SUSI.AI.

Adding a boolean parameter ‘editable’ to the Skill metadata

We needed to add a boolean parameter in the Skill metadata for each Skill. The boolean parameter is ‘editable’. If its value is true, then it implies that editing should be allowed for that Skill. If it is set to false, then the Skill should not be editable for non-admin users. By default, its value has been set to true for all Skills. This is implemented as follows in the SusiSkill.java file:

    // in the getSkillMetadata() method
  skillMetadata.put("editable", getSkillEditStatus(model, group, language, skillname));

    // declaration of the getSkillEditStatus() method
    public static boolean getSkillEditStatus(String model, String group, String language, String skillname) {
        // skill status
        JsonTray skillStatus = DAO.skillStatus;
        if (skillStatus.has(model)) {
            JSONObject modelName = skillStatus.getJSONObject(model);
            if (modelName.has(group)) {
                JSONObject groupName = modelName.getJSONObject(group);
                if (groupName.has(language)) {
                    JSONObject languageName = groupName.getJSONObject(language);
                    if (languageName.has(skillname)) {
                        JSONObject skillName = languageName.getJSONObject(skillname);

                        if (skillName.has("editable")) {
                            return false;
                        }
                    }
                }
            }
        }
        return true;
    }

 

Allowing Admin and higher user roles to change edit status of any Skill

This is facilitated by the endpoint ‘/cms/changeSkillStatus.json’. Its minimum base user role is set to Admin so that only Admins and higher user roles are able to change status of any Skill. A sample API call to this endpoint to change the edit status of any Skill to ‘false’ is as follows:

http://127.0.0.1:4000/cms/changeSkillStatus.json?model=general&group=Knowledge&language=en&skill=aboutsusi&editable=false&access_token=zdasIagg71NF9S2Wu060ZxrRdHeFAx

 

If we want to change the edit status of any Skill to ‘false’, then we need to add the Skill to the ‘skillStatus.json’ file. For this, we need to traverse inside the JSONObject in the ‘skillStatus.json’ file. We need to traverse inside the model, group and language as specified in the query parameters. This is done as follows:

   if(editable.equals("false")) {
       skill_status.put("editable", false);
   }

   JsonTray skillStatus = DAO.skillStatus;

   if (skillStatus.has(model_name)) {
        modelName = skillStatus.getJSONObject(model_name);
        if (modelName.has(group_name)) {
            groupName = modelName.getJSONObject(group_name);
            if (groupName.has(language_name)) {
                languageName = groupName.getJSONObject(language_name);

                if (languageName.has(skill_name)) {
                    skillName = languageName.getJSONObject(skill_name);

                    if(editable != null && editable.equals("false")) {
                        skillName.put("editable", false);
                    }
                    else if(editable != null && editable.equals("true")) {
                        skillName.remove("editable");
                    }

                    skillStatus.commit();
                    result.put("accepted", true);
                    result.put("message", "Skill status changed successfully.");
                    return new ServiceResponse(result);
                }
            }
        }
    }

 

If we want to change the edit status of any Skill to ‘true’, then we need to remove the Skill from the ‘skillStatus.json’ file. We also need to remove all the empty JSONObjects inside the ‘skillStatus.json’ file, if they are created in the process of removing Skills from it. This is done as follows:

   if (skillStatus.has(model_name)) {
        modelName = skillStatus.getJSONObject(model_name);
        if (modelName.has(group_name)) {
            groupName = modelName.getJSONObject(group_name);
            if (groupName.has(language_name)) {
                languageName = groupName.getJSONObject(language_name);
                if (languageName.has(skill_name)) {
                    skillName = languageName.getJSONObject(skill_name);
                    if(editable != null && editable.equals("true")) {
                        skillName.remove("editable");
                    }
                    if(skillName.length() == 0) {
                        languageName.remove(skill_name);
                        if(languageName.length() == 0) {
                            groupName.remove(language_name);
                            if(groupName.length() == 0) {
                                modelName.remove(group_name);
                                if(modelName.length() == 0) {
                                    skillStatus.remove(model_name);
                                }
                            }
                        }
                    }
                    skillStatus.commit();
                }
            }
        }
    }

 

Disabling editing for non-editable Skills for non-admin users on Skill CMS

For the Skills whose edit status has been set to ‘false’ by the Admins, we need to allow the non-admin users to only be able to view the code of the Skill, and not permit them to change the code and save the changes to the Skill. We need to display a message to the users about the possible reasons. All the code for displaying the message is put in an if() condition as follows:

   if (
      cookies.get('loggedIn') &&
      !this.state.editable &&
      !this.state.showAdmin
    )

 

This is how the Skill edit page for a non-editable Skill would look like for a non-admin user:

For an Admin user, this would look exactly same like an editable Skill page. Admin user would be able to edit and make changes to the Skill code and save the changes.

This is how editing of non-editable Skills have been disabled for non-admin users.

Resources

Continue ReadingDisable editing for non-editable skills for non-admin users

Hiding Multiple Attendee Recycler for Single Tickets in Open Event Android

Multiple attendee recycler allows the user to easily write in attendee details for all tickets he/she is buying. The UI for attendee details fragment can be divided into two segments, one contains the details of the person creating the whole order and followed by multiple details section for the attendee of every ticket. This is a good way to take details when multiple ticket or tickets with greater than 1 quantity is involved. However, if there is only one attendee detail to be taken instead of asking the user for order creator’s details and then first attendee details it is better to add some views in the creator segment and not ask for the same details again from the user. This blog post will help you in understanding how it’s done in Open Event Android.

Keeping a track of orders with single attendee

We keep a boolean to track whether or not the sum of quantities of tickets selected by a user is one since one attendee is to be generated per ticket quantity. And incase it is boolean variable singleTicket is set to True which is false by default initially. Following is how it can be done

singleTicket = ticketIdAndQty?.map { it.second }?.sum() == 1

The above statements basically iterate over the list of pairs of tickets and their quantity and then sums over the second element of all pairs that is the quantities, if the summation leads to 1 single ticket is set to True else false.

Adding elements to recycler adapter

The next step is to update the logic as to when blank attendees are added to recycler adapter. The idea is to check the boolean singleTicket and if it is false dont insert any attendee.

attendeeFragmentViewModel.tickets.observe(this, Observer {
               it?.let {

                   if (!singleTicket)
                       it.forEach {
                           val pos = ticketIdAndQty?.map { it.first }?.indexOf(it.id)
                           val iterations = pos?.let { it1 -> ticketIdAndQty?.get(it1)?.second } ?: 0
                           for (i in 0 until iterations)
                               attendeeRecyclerAdapter.add(Attendee(attendeeFragmentViewModel.getId()), it)
                           attendeeRecyclerAdapter.notifyDataSetChanged()
                       }
               }
           })

If a single ticket is false regular procedure to add blank attendee is carried out. The quantity of ticket is found and the same quantity of attendees are added to recycler adapter’s attendee’s list similary ticket information is also added to the recycler adapter. The id for blank attendees is taken from the attendeeViewModel’s getId function which in turn simply returns id using the authHolder’s getId function.

Modify the register method to check for the single attendee

So far what we have discussed helps us display the right no of details section on the UI. We will also have to modify the register method to take into account the above changes. Following is the implementation of what happens when the user selects register.

rootView.register.setOnClickListener {
               if (selectedPaymentOption == “Stripe”)
                   sendToken()

               val attendees = ArrayList<Attendee>()
               if (singleTicket) {
                   val pos = ticketIdAndQty?.map { it.second }?.indexOf(1)
                   val ticket = pos?.let { it1 -> ticketIdAndQty?.get(it1)?.first?.toLong() } ?: –1
                   val attendee = Attendee(id = attendeeFragmentViewModel.getId(),
                           firstname = firstName.text.toString(),
                           lastname = lastName.text.toString(),
                           city = getAttendeeField(“city”),
                           address = getAttendeeField(“address”),
                           state = getAttendeeField(“state”),
                           email = email.text.toString(),
                           ticket = TicketId(ticket),
                           event = eventId)
                   attendees.add(attendee)
               } else {
                   attendees.addAll(attendeeRecyclerAdapter.attendeeList)
               }
               val country = if (country.text.isEmpty()) country.text.toString() else null
               attendeeFragmentViewModel.createAttendees(attendees, country, selectedPaymentOption)
           }

For create attendee object and then add it to the attendees ArrayList first we need to find the ticket it is to be created for as the attendee object accepts Ticket Id as one of the parameters. The logic to get the ticket is fairly straightforward if singleTicket is true first we find out the ticket for which quantity is 1 and then we get the ticket object for the same. Finally, we take the id field of the ticket and add it to our attendee constructor. Other fields for attendee such as first name, last name, email, and country are taken from the EditText View’s rendered on the screen. Else if the single ticket is false which means there are multiple attendee details section on the fragment we simply add the recycler adapter’s attendee list into attendees ArrayList which is later provided to the order function for creating an order of it.

References

Continue ReadingHiding Multiple Attendee Recycler for Single Tickets in Open Event Android

Giving users option to switch between All and Reviewed Only Skills on SUSI Skill CMS

There are a lot of Skills on SUSI Skill CMS. Any registered user has the access to creating his/her own Skills. Hence, we need to give the users an option on SUSI Skill CMS whether they want to see all the Skills, or only those Skills that have been tested thoroughly and have been approved by the Admin and higher user roles. This blog post explains how this feature has been implemented on the SUSI Skill CMS.

How is review status of any Skill changed on the server?

The API endpoint which allows Admin and higher user roles to change the review status of any Skill on the server is ‘/cms/changeSkillStatus.json’. It takes the following parameters:

  • model: Model of the Skill
  • group: Group of the Skill
  • language: Language of the Skill
  • skill: Skill name
  • reviewed: A boolean parameter which if true, signifies that the Skill has been approved.

Sample API call:

https://api.susi.ai/cms/changeSkillStatus.json?model=general&group=Knowledge&language=en&skill=aboutsusi&reviewed=true&access_token=yourAccessToken

 

Fetching reviewed only Skills from the server

The ‘/cms/getSkillList.json’ endpoint has been modified to facilitate returning only the Skills whose review status is true. This is done by the following API call:

https://api.susi.ai/cms/getSkillList.json?reviewed=true

 

Creating checkbox to switch between All and Reviewed Only Skills

Checkbox is one of the many Material-UI components. Hence, we need to first import it before we can use it directly in our BrowseSkill component.

import Checkbox from 'material-ui/Checkbox';

 

In the constructor of the BrowseSkill class, we set states for two variables as follows:

constructor(props) {
  super(props);
    this.state = {
      // other state variables
      showSkills: '',
      showReviewedSkills: false,
    };
}

 

In the above code for the constructor, we have set two state variables. ‘showSkills’ is a string which can either be an empty string, or ‘&reviewed=true’. We want to append this string to the ‘/cms/getSkillList.json’ API call because it would determine whether we want to fetch All Skills or reviewed only Skills. The second variable ‘showReviewedSkills’ is a boolean used to keep record of the current state of the page. If it is true, then it means that currently, only the reviewed Skills are being displayed on the CMS site.

Implementation of the checkbox

This is how the Checkbox has been implemented for the purpose of switching between All and Reviewed Only Skills:

 <Checkbox
    label="Show Only Reviewed Skills"
    labelPosition="right"
    className="select"
    checked={this.state.showReviewedSkills}
    labelStyle={{ fontSize: '14px' }}
    iconStyle={{ left: '4px' }}
    style={{
      width: '256px',
      paddingLeft: '8px',
      top: '3px',
    }}
    onCheck={this.handleShowSkills}
  />

 

As can be seen from the above code, the initial state of the checkbox is unchecked as initially, the value of the state variable ‘showReviewedSkills’ is set to false in the constructor. This means that initially all Skills will be shown to the user. On clicking on the checkbox, handleShowSkills() function is called. Its implementation is as follows:

  handleShowSkills = () => {
    let value = !this.state.showReviewedSkills;
    let showSkills = value ? '&reviewed=true' : '';
    this.setState(
      {
        showReviewedSkills: value,
        showSkills: showSkills,
      },
      function() {
        this.loadCards();
      },
    );
  };

 

In the handleShowSkills() function, firstly we store the current value of the state variable ‘showReviewedSkills’. The value of ‘showSkills’ string is determined according to the value of ‘showReviewedSkills’. Then the states of both these variables are updated in the setState() function. Lastly, loadCards() function is called.

In the loadCards() function, we append the value of the state variable ‘showSkills’ to the AJAX call to the ‘/cms/getSkillList.json’ endpoint. The URL used for the API call is as follows:

https://api.susi.ai/cms/getSkillList.json?group=' +
  this.props.routeValue +
  '&language=' +
  this.state.languageValue +
  this.state.filter +
  this.state.showSkills;

 

This is how the implementation of the feature to give users an option to switch between All and Reviewed Only Skills has been done.

Resources

Continue ReadingGiving users option to switch between All and Reviewed Only Skills on SUSI Skill CMS

Nomodeset in Meilix

Meilix contains configuration file which is required to config the ways of Meilix booting. There are several parameter out of which is nomodeset is one of them.

Problem:

Meilix result in blank screen in a machine which boots splash screen with a driver.

Solution:

When the OS boots, nomodeset takes care that the video like splash screen happen in kernel rather than on driver. Sometimes the driver is unable to run the video which results in blank screen issue. nomodeset confirms that no video get loaded and OS boots in BIOS mode until the driver get loaded.

Way 1:

menuentry "Try Live Meilix" {
	linux	/casper/vmlinuz  file=/cdrom/preseed/lubuntu.seed boot=casper iso-scan/filename=${iso_path} nomodeset quiet splash --
	initrd	/casper/initrd.lz
}
menuentry "Install Meilix" {
	linux	/casper/vmlinuz  file=/cdrom/preseed/lubuntu.seed boot=casper only-ubiquity iso-scan/filename=${iso_path} quiet splash --
	initrd	/casper/initrd.lz
}
menuentry "Check disc for defects" {
	linux	/casper/vmlinuz  boot=casper integrity-check iso-scan/filename=${iso_path} quiet splash --
	initrd	/casper/initrd.lz
}
menuentry "Test memory" {
	linux16	/install/mt86plus
}

We put nomodeset in Try Live Meilix option for testing purpose. This file can be found https://github.com/fossasia/meilix/edit/master/image/boot/grub/loopback.cfg.

Parameter quiet splash — is responsible for showing the splash screen and at the same time, it takes care to not to display other messages while the splash screen is loaded.

We can tweak parameter with the present options during the boot screen.

This is the one way through which we can use nomodeset while editing the configuration file.

Way 2:

During running of the OS, we can use this feature by editing this file /etc/default/grub and including nomodeset in it.

GRUB_DEFAULT=0
GRUB_HIDDEN_TIMEOUT=0
GRUB_HIDDEN_TIMEOUT_QUIET=true
GRUB_TIMEOUT=5
GRUB_DISTRIBUTOR=`lsb_release -i -s 2> /dev/null || echo Debi`
GRUB_CMDLINE_LINUX_DEFAULT="quiet splash nomodeset"
GRUB_CMDLINE_LINUX=""

And then save and exiting the file. Then we need to update grub using

sudo update-grub

Reference:

Configuring nomodeset

nomodeset, quiet and splash

 

Continue ReadingNomodeset in Meilix

apt-get update in building Meilix through Travis

Meilix uses Travis to build and then make a github release of the ISO. There are packages that get built in the script executed by Travis.

What problem it is solving? It’s the need of apt-get update which are:

  • Update of the system to support for the newest builds.
  • Adding of the repo after adding a Personal Package Archives (‘PPAs’)

By default, Travis disabled apt-get update for every build. So if we want to run it automatically for each build, we can do it in two different ways.

Way 1:Running apt-get update in before_install step.

Implementation in .travis.yml of Meilix

print 'hello world!'
sudo: required

before_install:
  - apt-get update

 

We already used sudo so there is no need to use sudo in the apt-get update.

This is the most simplistic approach and it update the system packages and source list before installing any packages.

Way 2: executing apt-get update in Travis through APT addon.

The addon comes under the include step and the lint follows this order:

include:
  - os: linux
    addons:
      apt:
        update: true
        sources:
          - ubuntu-toolchain-r-test
        packages:
          - debootstrap
          - genisoimage
          - p7zip-full
          - squashfs-tools
          - ubuntu-dev-tools
          - dpkg-dev
          - debhelper
          - fakeroot
          - devscripts

 

This is helpful in case when we don’t have the before_install step. Additionally, it allows us to specify the packages we need to install.

Choosing between the two options

One can adopt any of the above process to implement apt-update in Travis.

Meilix uses the second approach as because we don’t have before_install parameter.

Sometimes we need some commands to get executed before updating the system, so in that case we prefer before_install to prioritize the tasks.

Resources:

 

 

Continue Readingapt-get update in building Meilix through Travis