Rating session in Open Event Frontend

Rating session in Open Event Frontend

This blog post will showcase an option which can be used by organizers to rate a session in Open Event Frontend. Let’s start by understanding why this feature is important for organizers.

Consider a situation where an event can have hundreds of session submissions. It’ll be hard for organizers/co-organizers to keep track of the session they have already evaluated and which session is better than other. Here, session rating comes to the rescue. After evaluating a particular session, the organizer/co-organizer can simply rate the session out of 5 stars. We have a column Average Rating and No. of ratings which can be used to pick up the top rated sessions and so the organizer/co-organizers need not worry to keep track of evaluated sessions.

We start by adding the three columns – Rating, Average Rating and No. of ratings to session controller along with actions createRating and updateRating to create and update session rating respectively.

Code snippet to add the three mentioned columns to session controller –

@computed()
 get columns() {
   return [
     {
       ...
     },
     {
       name            : 'Rating',
       valuePath       : 'id',
       extraValuePaths : ['rating', 'feedbacks'],
       cellComponent   : 'ui-table/cell/events/view/sessions/cell-rating',
       options         : {
         ratedSessions: this.ratedSessions
       },
       actions: {
         updateRating : this.updateRating.bind(this),
         addRating    : this.addRating.bind(this)
       }
     },
     {
       name            : 'Avg Rating',
       valuePath       : 'averageRating',
       isSortable      : true,
       headerComponent : 'tables/headers/sort'
     },
     {
       name            : 'No. of ratings',
       valuePath       : 'feedbacks.length',
       isSortable      : true,
       headerComponent : 'tables/headers/sort'
     },
     {
       ...
     }
   ];
 }

The code snippet to add the two actions createRating and updateRating to the session controller –

@action
 async updateRating(rating, feedback) {
   try {
     this.set('isLoading', true);
     if (rating) {
       feedback.set('rating', rating);
       await feedback.save();
     } else {
       await feedback.destroyRecord();
     }
     this.notify.success(this.l10n.t('Session feedback has been updated 
                                      successfully.'));
   } catch (error) {
     this.notify.error(this.l10n.t(error.message));
   }
   this.send('refreshRoute');
   this.set('isLoading', false);
 }

The action updateRating in the above code snippet takes rating and the feedback to be updated as parameters. If rating param is 0, the feedback is simply destroyed because it means that user removes his/her feedback from the session.

@action
 async addRating(rating, session_id) {
   try {
     let session =  this.store.peekRecord('session', session_id, { 
                                          backgroundReload: false });
     this.set('isLoading', true);
     let feedback = await this.store.createRecord('feedback', {
       rating,
       session,
       comment : '',
       user    : this.authManager.currentUser
     });
     await feedback.save();
     this.notify.success(this.l10n.t('Session feedback has been created 
                                      successfully.'));
   } catch (error) {
     this.notify.error(this.l10n.t(error.message));
   }
   this.send('refreshRoute');
   this.set('isLoading', false);
 }

And the action addRating takes rating and the id of the session being rated as an input and then create a new feedback record with the info.

Now the main challenge was to retrieve rating corresponding to a session and display it on the frontend. I tackled this by fetching all the feedback related to any session which is itself related to the event. Then I mapped the feedback to the id of the session they were related to.

Code snippet fetching the feedback and mapping them to session id –

let queryObject = {
     include : 'session',
     filter  : [
       {
         name : 'session',
         op   : 'has',
         val  : {
           name : 'event',
           op   : 'has',
           val  : {
             name : 'identifier',
             op   : 'eq',
             val  : store.id
           }
         }
       }
     ]
   };
let feedbacks = await this.authManager.currentUser.query('feedbacks',queryObject);
@mapBy('model.feedbacks', 'session.id') ratedSessions;

Once I got the mapped data as ratedSessions all I needed to do was to check if the id of the session being rendered is in the array ratedSessions or not. If the id was present in the array, it clearly depicts that the session was rated by the user and we can simply display the block of rating to the user.

Code snippet which executed the logic explained above – 

{{#if (includes props.options.ratedSessions record)}}
 {{#each extraRecords.feedbacks as |feedback|}}
   {{#if (eq feedback.user.email authManager.currentUser.email)}}
     {{ui-rating
       initialRating=feedback.rating
       rating=feedback.rating
       maxRating=5
       onRate=(pipe-action (action (mut feedback.rating)) (action 
               props.actions.updateRating feedback.rating feedback))
       clearable=true}}
   {{/if}}
 {{/each}}
{{else}}
 {{ui-rating
   initialRating=0
   rating=extraRecords.rating
   maxRating=5
   onRate=(pipe-action (action (mut extraRecords.rating)) (action 
           props.actions.addRating extraRecords.rating record))
   clearable=true}}
{{/if}}

The helper includes simply tells us if the first parameter passed contains the second parameter or not and pipe-action helps us to perform multiple action on a single click.

I used ui-rating module of semantic UI for implementing this feature. Whenever user added/updated the rating, at first the feedback.rating is mutated and then the relevant action is called. To ensure that duplicate entries of feedback doesn’t exist in the db I added unique constraint for the table feedback on user_id and session_id columns.

Resources:

Related work and code repo: