Implementing Scheduler Actions on Open Event Frontend

After the functionality to display scheduled sessions was added to Open Event Frontend, the read-only implementation of the scheduler had been completed. What was remaining now in the scheduler were the write actions, i.e., the sessions’ scheduling which event organizers do by deciding its timings, duration and venue. First of all, these actions required the editable flag to be true for the fullcalendar plugin. This allowed the sessions displayed to be dragged and dropped. Once this was enabled, the next task was to embed data in each of the unscheduled sessions so that when they get dropped on the fullcalendar space, they get recognized by the calendar, which can place it at the appropriate location. For this functionality, they had to be jQuery UI draggables and contain an “event” data within them. This was accomplished by the following code: this.$().draggable({ zIndex : 999, revert : true, // will cause the event to go back to its revertDuration : 0 // original position after the drag }); this.$().data('event', { title : this.$().text().replace(/\s\s+/g, ' '), // use the element's text as the event title id : this.$().attr('id'), serverId : this.get('session.id'), stick : true, // maintain when user navigates (see docs on the renderEvent method) color : this.get('session.track.color') }); Here, “this” refers to each unscheduled session. Note that the session color is fetched via the corresponding session track. Once the unscheduled sessions contain enough relevant data and are of the right type (i.e, jQuery UI draggable type), they’re ready to be dropped on the fullcalendar space. Now, when an unscheduled session is dropped on the fullcalendar space, fullcalendar’s eventReceive callback is triggered after its drop callback. In this callback, the code removes the session data from the unscheduled sessions’ list, so it disappears from there and gets stuck to the fullcalendar space. Then the code in the drop callback makes a PATCH request to Open Event Server with the relevant data, i.e, start and end times as well as microlocation. This updates the corresponding session on the server. Similarly, another callback is generated when an event is resized, which means when its duration is changed. This again sends a corresponding session PATCH request to the server. Furthermore, the functionality to pop a scheduled event out of the calendar and add it back to the unscheduled sessions’ list is also implemented, just like in Eventyay version 1. For this, a cross button is implemented, which is embedded in each scheduled session. Clicking this pops the session out of the calendar and adds it back to the unscheduled sessions list. Again, a corresponding PATCH request is sent to the server. After getting the response of such requests, a notification is displayed on the screen, which informs the users whether the action was successful or not. The main PATCH functionality is in a separate function which is called by different callbacks accordingly, so code reusability is increased: updateSession(start, end, microlocationId, sessionId) { let payload = { data: { attributes: { 'starts-at' : start ? start.toISOString() :…

Continue ReadingImplementing Scheduler Actions on Open Event Frontend

Open Event Server – Export Speakers as PDF 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 speakers in a very detailed view in the event management dashboard. He can see the statuses of all the speakers. The possible statuses are pending, accepted, and rejected. He/she can take actions such as editing the speakers. If the organizer wants to download the list of all the speakers as a PDF file, he or she can do it very easily by simply clicking on the Export As PDF button in the top right-hand corner. Let us see how this is done on the server. Server side - generating the Speakers PDF file Here we will be using the pisa package which is used to convert from HTML to PDF. It is a html2pdf converter which uses ReportLab Toolkit, the HTML5lib and pyPdf. It supports HTML5 and CSS 2.1 (and some of CSS 3). It is completely written in pure Python so it is platform independent. from xhtml2pdf import pisa< We have a utility method create_save_pdf which creates and saves PDFs from HTML. It takes the following arguments: pdf_data - This contains the HTML template which has to be converted to PDF. key - This contains the file name dir_path - This contains the directory It returns the newly formed PDF file. The code is as follows: def create_save_pdf(pdf_data, key, dir_path='/static/uploads/pdf/temp/'):   filedir = current_app.config.get('BASE_DIR') + dir_path   if not os.path.isdir(filedir):       os.makedirs(filedir)   filename = get_file_name() + '.pdf'   dest = filedir + filename   file = open(dest, "wb")   pisa.CreatePDF(io.BytesIO(pdf_data.encode('utf-8')), file)   file.close()   uploaded_file = UploadedFile(dest, filename)   upload_path = key.format(identifier=get_file_name())   new_file = upload(uploaded_file, upload_path)   # Removing old file created   os.remove(dest)   return new_file The HTML file is formed using the render_template method of flask. This method takes the HTML template and its required variables as the arguments. In our case, we pass in 'pdf/speakers_pdf.html'(template) and speakers. Here, speakers is the list of speakers to be included in the PDF file. In the template, we loop through each item of speakers. We print his name, email, list of its sessions, mobile, a short biography, organization, and position. All these fields form a row in the table. Hence, each speaker is a row in our PDF file. The various columns are as follows: <thead> <tr>   <th>       {{ ("Name") }}   </th>   <th>       {{ ("Email") }}   </th>   <th>       {{ ("Sessions") }}   </th>   <th>       {{ ("Mobile") }}   </th>   <th>       {{ ("Short Biography") }}   </th>   <th>       {{ ("Organisation") }}   </th>   <th>       {{ ("Position") }}   </th> </tr> </thead> A snippet of the code which handles iterating over the speakers' list and forming a row is as follows: {% for speaker in speakers %}   <tr class="padded" style="text-align:center; margin-top: 5px">       <td>           {% if speaker.name %}               {{ speaker.name }}           {% else %}               {{ "-" }}           {% endif %}…

Continue ReadingOpen Event Server – Export Speakers as PDF File

Implementing Scheduled Sessions in Open Event Scheduler

Until recently, the Open Event Frontend version 2 didn’t have the functionality to display the already scheduled sessions of an event on the sessions scheduler. Displaying the already scheduled sessions is important so that the event organizer can always use the sessions scheduler as a draft and not worry about losing progress or data about scheduled sessions’ timings. Therefore, just like a list of unscheduled sessions was implemented for the scheduler, the provision for displaying scheduled sessions also had to be implemented. The first step towards implementing this was to fetch the scheduled sessions’ details from Open Event Server. To perform this fetch, an appropriate filter was required. This filter should ideally ask the server to send only those sessions that are “scheduled”. Thus, scheduled sessions need to be defined as sessions which have a non-null value of its starts-at and ends-at fields. Also, few more details are required to be fetched for a clean display of scheduled sessions. First, the sessions’ speaker details should be included so that the speakers’ names can be displayed alongside the sessions. Also, the microlocations’ details need to be included so that each session is displayed according to its microlocation. For example, if a session is to be delivered in a place named ‘Lecture Hall A’, it should appear under the ‘Lecture Hall A’ microlocation column. Therefore, the filter goes as follows: let scheduledFilterOptions = [ { and: [ { name : 'starts-at', op : 'ne', val : null }, { name : 'ends-at', op : 'ne', val : null } ] } ];   After fetching the scheduled sessions’ details, they need to be delivered to the fulllcalendar code for displaying on the session scheduler. For that, the sessions need to be converted in a format which can be parsed by the fullcalendar add-on of emberJS. For example, fullcalendar calls microlocations as ‘resources’. Here is the format which fullcalendar understands: { title : `${session.title} | ${speakerNames.join(', ')}`, start : session.startsAt.format('YYYY-MM-DDTHH:mm:SS'), end : session.endsAt.format('YYYY-MM-DDTHH:mm:SS'), resourceId : session.microlocation.get('id'), color : session.track.get('color'), serverId : session.get('id') // id of the session on BE }   Once the sessions are in the appropriate format, their data is sent to the fullcalendar template, which renders them on the screen: This completes the implementation of displaying the scheduled sessions of an event on the Open Event Scheduler. Resources Fullcalendar Events and Scheduling Tutorial by Drifting Ruby Blog - How to Implement jQuery Fullcalendar in Laravel - Krissanawat​ Kaewsanmuang

Continue ReadingImplementing Scheduled Sessions in Open Event Scheduler

Ticket Details in the Open Event Android App

After entering all the attendee details and buying a ticket for an event the user expects to see the ticket so that he can use it later. This is why ticket details are shown in a separate fragment in the Open Event Android App. Let’s see how the tickets fragment was made in the Open Event Android App. Two things that we require from the previous fragment are the event id and the order identifier so that we show the information related to the event as well as the order. if (bundle != null) { id = bundle.getLong(EVENT_ID, -1) orderId = bundle.getString(ORDERS) }   We are requesting data from the following two endpoints. In the first GET request we are passing the order identifier in the URL and we get the list of attendees from the server. In the second endpoint we simply pass the event identifier and get the event details from the server.   @GET("/v1/orders/{orderIdentifier}/attendees") fun attendeesUnderOrder(@Path("orderIdentifier") orderIdentifier: String): Single<List<Attendee>> @GET("/v1/events/{eventIdentifier}") fun getEventFromApi(@Path("eventIdentifier") eventIdentifier: Long): Single<Event>   Here we are observing the attendees live data and adding the list of attendees returned from the server to the recyclerview so that we can show the user all the details of the attendees like the first name, last name etc. We then notify the adapter that the list of attendees have been added. In the end we log the number of attendees so that it is easier to debug in case there are any bugs. orderDetailsViewModel.attendees.observe(this, Observer { it?.let { ordersRecyclerAdapter.addAll(it) ordersRecyclerAdapter.notifyDataSetChanged() } Timber.d("Fetched attendees of size %s", ordersRecyclerAdapter.itemCount) })   As mentioned earlier we need the event id and order identifier to show event and attendee related information to the user so here we are using the event id and appending it to the url. We are sending a GET request in a background thread and storing the list of events returned from the server in a mutable live data. In case of any errors we log it and show the error message to the user. Similarly we will use the order identifier to get the list of orders from the server and show it to the user. compositeDisposable.add(eventService.getEventFromApi(id) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe({ event.value = it }, { Timber.e(it, "Error fetching event %d", id) message.value = "Error fetching event" }))   After fetching the list of attendees and event details, the only thing that we need to do is extract the important information and show it to the user so we pass the order and event objects to the ViewHolder. This can be done simply by using the attendee and event objects and accessing the fields required. itemView.name.text = "${attendee.firstname} ${attendee.lastname}" itemView.eventName.text = event?.name itemView.date.text = "$formattedDate\n$formattedTime $timezone"   Resources ReactiveX official documentation: http://reactivex.io/ Retrofit Android: http://square.github.io/retrofit/ Google Android Developers Recycler View: https://developer.android.com/guide/topics/ui/layout/recyclerview

Continue ReadingTicket Details in the Open Event Android App

Implementing Unscheduled Sessions List for Event Scheduler

Until recently, Open Event Server didn’t allow the storage of unscheduled sessions. However, having the provision of unscheduled sessions was necessary so that event organizers can easily schedule the unscheduled sessions and keep track of them. Also, it allows them to remove scheduled sessions from the scheduler and place them in the unscheduled sessions list, so that they can be scheduled later. Also, since the unscheduled sessions list was also present in Eventyay version 1, it was decided to have the same in version 2. The first step was to enable the storage of unscheduled sessions on the server. For this, the starts-at and ends-at fields of the session model were modified to be non-required (earlier they were mandatory). Once this change was done, the next step was to fetch the list of unscheduled sessions on the frontend, from the server. Unscheduled sessions were the ones which had the starts-at and ends-at fields as null. Also, the speakers’ details needed to be fetched so that their names can be mentioned along with sessions’ titles, in accordance with Eventyay version 1. Thus, the following were the filter options for the unscheduled sessions’ fetching: let unscheduledFilterOptions = [ { and: [ { name : 'starts-at', op : 'eq', val : null }, { name : 'ends-at', op : 'eq', val : null } ] } ]; let unscheduledSessions = await eventDetails.query('sessions', { include : 'speakers,track', filter : unscheduledFilterOptions });   This gave us the list of unscheduled sessions on the frontend appropriately. After this, the next step was to display this list to the event organizer. For this, the scheduler’s Handlebars template file was modified appropriately. The colors and sizes were chosen so that the list looks similar to the one in Eventyay version 1. Also, the Ember add-on named ember-drag-drop was used to make these unscheduled session components draggable, so that they can be ultimately scheduled on the scheduler. After installing this package and making the necessary changes to the project’s package.json file, the component file for unscheduled sessions was modified accordingly to adapt for the draggable components’ UI. This was the final step and completed the implementation of listing unscheduled sessions. Resources Ember Drag-Drop Addon Documentation Stackoverflow answer on Dragging external events to FullCalendar from slick slider, by Freeman Lambda

Continue ReadingImplementing Unscheduled Sessions List for Event Scheduler

Open Event Frontend – Events Explore Page

This blog illustrates how the events explore page is implemented in the Open Event Frontend web app. The user can land on the events explore page by clicking on Browse Events button in the top panel on the home page, shown by the mouse tip in the following picture. Here, the user can use the various filter options provided to search for the events as per his requirements, He/she can filter according to categories, sub-categories for each category, event type, and date range. A unique feature here is that the user can pick from the start date range options such as today, tomorrow, this week, this weekend, next week and many more. If neither of these fits his needs he can use custom dates as well. The user can also filter events using event location which is autocompleted using Google Maps API. Thus, searching for events is fast, easy and fun. Let us see how this has been implemented. Implementation The explore routes has a method _loadEvents(params). Here, params is the various query parameters for filtering the events. This method forms the query, sends it to the server and returns the list of events returned by the server. The server uses Flask-REST-JSONAPI. It has a very flexible filtering system. It is completely related to the data layer used by the ResourceList manager. More information about this can be found here. So, the filters are formed using syntax specified in the link mentioned above. We form an array filterOptions which stores the various filters. The default filter is that the event should be published: let filterOptions = [ {   name : 'state',   op  : 'eq',   val  : 'published' } ]; Then we check for each filter option and check if it is present or not. If yes then we add it to filterOptions. An example as follows: if (params.category) { filterOptions.push({   name : 'event-topic',   op  : 'has',   val  : {     name : 'name',     op : 'eq',     val : params.category   } }); } This is repeated for sub_category, event_type, location and start_date and end_date. An event is considered to fulfill the date filter if it satisfies any one of the given conditions: If both start_date and end_date are mentioned: Event start_date is after filter start date and before filter end date. Or, event end date if after filter start date and before filter end date. Or, event start date is before filter start date and event end date date is after filter end date. If only start_date is mentioned, then if the event start date is after filter start date or event end date is after filter start date. The code to this can be found here. For the date ranges mentioned above(today, tomorrow etc) the start dates and end dates are calculated using the moment.js library and then passed on as params. The filteredEvents are passed in the route model. async model(params) { return {   eventTypes     : await this.store.findAll('event-type'),   eventTopics    : await this.store.findAll('event-topic', { include: 'event-sub-topics' }),   filteredEvents : await this._loadEvents(params)…

Continue ReadingOpen Event Frontend – Events Explore Page

Open Event Server – Export Sessions as PDF 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 PDF file, he or she can do it very easily by simply clicking on the Export As PDF button in the top right-hand corner. Let us see how this is done on the server. Server side - generating the Sessions PDF file Here we will be using the pisa package which is used to convert from HTML to PDF. It is a html2pdf converter which uses ReportLab Toolkit, the HTML5lib and pyPdf. It supports HTML5 and CSS 2.1 (and some of CSS 3). It is completely written in pure Python so it is platform independent. from xhtml2pdf import pisa We have a utility method create_save_pdf which creates and saves PDFs from HTML. It takes the following arguments: pdf_data - This contains the HTML template which has to be converted to PDF. key - This contains the file name dir_path - This contains the directory It returns the newly formed PDF file. The code is as follows: def create_save_pdf(pdf_data, key, dir_path='/static/uploads/pdf/temp/'):   filedir = current_app.config.get('BASE_DIR') + dir_path   if not os.path.isdir(filedir):       os.makedirs(filedir)   filename = get_file_name() + '.pdf'   dest = filedir + filename   file = open(dest, "wb")   pisa.CreatePDF(io.BytesIO(pdf_data.encode('utf-8')), file)   file.close()   uploaded_file = UploadedFile(dest, filename)   upload_path = key.format(identifier=get_file_name())   new_file = upload(uploaded_file, upload_path)   # Removing old file created   os.remove(dest)   return new_file The HTML file is formed using the render_template method of flask. This method takes the HTML template and its required variables as the arguments. In our case, we pass in 'pdf/sessions_pdf.html'(template) and sessions. Here, sessions is the list of sessions to be included in the PDF file. In the template, we loop through each item of sessions and check if it is deleted or not. If it not deleted then we print its title, state, list of its speakers, track, created at and has an email been sent or not. All these fields form a row in the table. Hence, each session is a row in our PDF file. The various columns are as follows: <thead> <tr>   <th>       {{ ("Title") }}   </th>   <th>       {{ ("State") }}   </th>   <th>       {{ ("Speakers") }}   </th>   <th>       {{ ("Track") }}   </th>   <th>       {{ ("Created At") }}   </th>   <th>       {{ ("Email Sent") }}   </th> </tr> </thead> A snippet of the code which handles iterating over the sessions list and forming a row is as follows: {% for session in sessions %}   {% if not session.deleted_at %}       <tr class="padded"…

Continue ReadingOpen Event Server – Export Sessions as PDF File

Implementing User Email Verification in Open Event Frontend

Open Event Server provides the functionality of user email verification after a user registers, but it was not implemented on Open Event Frontend until recently. For users, this meant they were still not able to verify themselves, even after receiving confirmation links in their inboxes, which were sent by the server. Thus, implementing it on frontend was crucial for a complete user registration workflow. Since the server had already exposed an endpoint to perform the registration, all that was required on the frontend to be done was to make a call to this endpoint with the necessary data. The entire process can be summarized as follows: The recently registered user clicks on the verification link she receives on her email The above step opens the link, which is of the format http://fossasia.github.io/open-event-frontend/verify?token= As soon as the frontend server receives this request, it extracts the token from the URL query parameter The token is now sent to the backend server as a patch request The response of the above request confirms whether the user verification is successful or not, and an appropriate message is displayed In the frontend code, the above algorithm is spread across 3 files: the router, verify route and verify controller. A new route named /verify was implemented for the user verification, and was registered in the project’s main router.js file. After that, in the verify route, the beforeModel() method is used to trigger the above algorithm before the page is loaded: // in app/routes/verify.js beforeModel(transition) { this.controllerFor('verify').verify(transition.queryParams.token); } The main algorithm above is implemented in the verify controller: // in app/controllers/verify.js ... queryParams : ['token'], token : null, success : false, error : null, verify(tokenVal) { let payload = { data: { token: tokenVal } }; return this.get('loader') .post('auth/verify-email', payload) .then(() => { this.set('success', true); }) .catch(reason => { this.set('error', reason); this.set('success', false); }); } });   A template for displaying the success or failure messages to the user was also created. It uses the value of the success boolean set above to decide the message to be displayed to the user. The user registration workflow is now complete and the user sees the following message after clicking on the verification link she receives: Resources Blog - Setting Up Email Verification in FeatherJS, by Imre Gelens EmberJS PATCH Request Discussion

Continue ReadingImplementing User Email Verification in Open Event Frontend

Adding Filters for Lists of Skills on the SUSI.AI Server

In this blog post, we will learn how to add filters to the API responsible for fetching the list of skills i.e. the endpoint - https://api.susi.ai/cms/getSkillList.json. The purpose of adding filters is to return a list of skills based on some parameters associated with the skill, that would be required to allow the user to get the desired response that s/he may be using to display it on the UI. Overview of the API API to fetch the list of skills - URL -  https://api.susi.ai/cms/getSkillList.json It takes 5 optional parameters - model - It is the name of the model that user is requesting group - It is the name of the group that user is requesting language - It is the name of the language that user is requesting skill - It is the name of the skill that user is requesting applyFilter - It has true/false values, depending whether filtering is required If the request URL contains the parameter applyFilter as true, in that case the other 2 compulsory parameters are - filter_name - ascending/descending, depending upon the type of sorting the user wants filter_type - lexicographical, rating, etc based on what basis the filtering is going to happen So, we will now look into adding a new filter_type to the API. Detailed explanation of the implementation We can add filters based on the key values of the Metadata object of individual skills. The Metadata object for each skill is similar to the following object - { "model": "general", "group": "Knowledge", "language": "en", "developer_privacy_policy": null, "descriptions": "A skill that returns the anagrams for a word", "image": "images/anagrams.jpg", "author": "vivek iyer", "author_url": "https://github.com/Remorax", "author_email": null, "skill_name": "Anagrams", "protected": false, "terms_of_use": null, "dynamic_content": true, "examples": ["Anagram for best"], "skill_rating": { "negative": "0", "positive": "0", "stars": { "one_star": 0, "four_star": 0, "five_star": 0, "total_star": 0, "three_star": 0, "avg_star": 0, "two_star": 0 }, "feedback_count": 0 }, "creationTime": "2017-12-17T14:32:15Z", "lastAccessTime": "2018-06-19T17:50:01Z", "lastModifiedTime": "2017-12-17T14:32:15Z" }   We will now add provision for URL parameter, filter_type=feedback in the API, which will filter the results based on the feedback_count key, which tells the number of feedback/comments a skill has received. In the serviceImpl method of the ListSkillService class, we can see a code snippet that handles the filtering part, It checks the filter_type parameter received in the URL on if-else block. The code snippet looks like this - if (filter_type.equals("date")) { . . } else if (filter_type.equals("lexicographical")) { . . } else if (filter_type.equals("rating")) { . . }   Similarly, we will need to add an else if condition with feedback_type=feedback and write the code block inside it. Here is the code for it, which is explained in detail. . . else if (filter_type.equals("feedback")) { if (filter_name.equals("ascending")) { Collections.sort(jsonValues, new Comparator<JSONObject>() { @Override public int compare(JSONObject a, JSONObject b) { Integer valA; Integer valB; int result=0; try { valA = a.getJSONObject("skill_rating").getInt("feedback_count"); valB = b.getJSONObject("skill_rating").getInt("feedback_count"); result = Integer.compare(valA, valB); } catch (JSONException e) { e.printStackTrace(); } return result; } }); } else { Collections.sort(jsonValues, new Comparator<JSONObject>() {…

Continue ReadingAdding Filters for Lists of Skills on the SUSI.AI Server