Implemeting Permissions for Speakers API in Open Event API Server

In my previous blogpost I talked about what the permissions enlisted in developer handbook means and which part of the codebase defines what part of the permissions clauses. The permission manager provides the permissions framework to implement the permissions and proper access controls based on the dev handbook.

In this blogpost, the actual implementation of the permissions is described. (Speakers API is under consideration here). The following table is the permissions in the developer handbook.

List

View

Create

Update

Delete

Superadmin/admin

Event organizer

✓ [1]

✓ [1]

✓ [1]

✓ [1]

✓ [1]

Registered User

✓ [3]

✓ [3]

✓ [4]

✓ [3]

✓ [3]

Everyone else

✓ [2][4]

✓ [2][4]

  1. Only self-owned events
  2. Only of sessions with state approved or accepted
  3. Only of self-submitted sessions
  4. Only to events with state published.

Super admin and admin should be able to access all the methods – list, view, create, update and delete. All the permissions are implemented through functions derived from permissions manager.Since all the functions have first check for super admin and admin, these are automatically taken care of.

Only of self-submitted sessions
This means that a registered user can list, view, edit or delete speakers of a session which he himself submitted. This requires adding a ‘creator’ attribute to session object which will help us determine if the session was created by the user. So before making a post for sessions, the current user identity is included as part of the payload.

def before_post(self, args, kwargs, data):
   data['creator_id'] = current_identity.id


Now that we have added creator id to a session, a method is used to check if session was created by the same user.

def is_session_self_submitted(view, view_args, view_kwargs, *args, **kwargs):
    user = current_identity


Firstly the current identity is set as user which will later be used to check id. Sequentially, admin, superadmin, organizer and co-organizers are checked. After this a session is fetched using 
kwargs[session_id]. Then if the current user id is same as the creator id of the session fetched, access is granted, else Forbidden Error is returned.

if session.creator_id == user.id:
   return view(*view_args, **view_kwargs)


In the before_post method of speakers class, the session ids received in the data are passed to this function in 
kwargs as session_id. The permissions are then checked there using current user. If the session id are not those of self submitted sessions, ‘Session Not Found’ is returned.

 if not has_access('is_session_self_submitted', session_id=session_id):
                    raise ObjectNotFound({'parameter': 'session_id'},
                                         "Session: {} not found".format(session_id))


Only of sessions with state approved or accepted
This check is required for user who has not submitted the session himself, so he can only see speaker profiles of accepted sessions. First, if the user is not authenticated, permissions are not checked. If co-organizer access is available, then the user can see all the speakers, so for this case filtering is not done. If not, then ‘is_session_self_submitted’ is checked. If yes, then then again no filtering, but if not then the following query filters accepted sessions.

if not has_access('is_session_self_submitted', session_id=session.id):
    query_ = query_.filter(Session.state == "approved" or Session.state == "accepted")

Similarly all the permissions first generate a list of all objects and then filtering is done based on the access level, instead of getting the list based on permissions.

Only to events with state published
It is necessary that users except the organizers and co-organizers can not see the events which are in draft state. The same thing follows for speaker profiles – a user cannot submit or view a speaker profile to an unpublished event. Hence, this constraint. So before POST of speakers, if event is not published, an event not found error is returned.

if event.state == "draft":
    raise ObjectNotFound({'parameter': 'event_id'},
                        "Event: {} not found".format(data['event_id'])


For GET, the  implementation of this is similar to the previous permission. A basic query is generated as such:

query_ = query_.join(Event).filter(Event.id == event.id)


Now if the user does not have at least 
co-organizer access, draft events must be filtered out.

if not has_access('is_coorganizer', event_id=event.id):
    query_ = query_.filter(Event.state == "published")


Some of the finer details have been skipped here, which can be found in the 
code.

Resources

Continue ReadingImplemeting Permissions for Speakers API in Open Event API Server

Understanding Permissions for Various APIs in Open Event API Server

Since the Open Event Server has various elements, a proper permissions system is essential. This huge list of permissions is well compiled in the developer handbook which can be found here. In this blogpost, permissions listed in the developer handbook are discussed. Let’s start with what we wish to achieve, that is, how to make sense of these permissions and where does each clause fit in the API Server’s codebase.

For example, Sponsors API has the following permissions.

List

View

Create

Update

Delete

Superadmin/admin

Event organizer

✓ [1]

✓ [1]

✓ [1]

✓ [1]

✓ [1]

Registered User

✓ [3]

✓ [3]

✓ [4]

✓ [3]

✓ [3]

Everyone else

✓ [2][4]

✓ [2][4]

  1. Only self-owned events
  2. Only sessions with state approved or accepted
  3. Only self-submitted sessions
  4. Only to events with state published.

Based on flask-rest-jsonapi resource manager, we get list create under ResourceList through ResourceList’s GET and POST methods, whereas View, Update, Delete work on single objects and hence are provided by ResourceDetail’s GET, PATCH and DELETE respectively. Each function of the permission manager has a jwt_required decorator.

@jwt_required
def is_super_admin(view, view_args, view_kwargs, *args, **kwargs):

@jwt_required
def is_session_self_submitted(view, view_args, view_kwargs, *args, **kwargs):


This
 ensures that whenever a check for access control is made to the permission manager, the user is signed in to Open Event. Additionally, the permissions are written in a hierarchical way such that for every permission, first the useris checked for admin or super admin, then for other accesses. Similar hierarchy is kept for organizer accesses like track organizer, registrar, staff or organizer and coorganizer.

Some APIs resources require no authentication for List. To do this we need to add a check for Authentication token in the headers. Since each of the functions of permission manager have jwt_required as decorator, it is important to checkfor the presence of JWT token in request headers, because we can proceed to check for specific permissions in that case only.

if 'Authorizationin request.headers:
 _jwt_required(current_app.config['JWT_DEFAULT_REALM'])


Since the resources are created by endpoints of the form : 
‘/v1/<resource>/` , this is derived from the separate ResourceListPost class. This class is POST only and has a before_create object method where the required relationships and permissions are checked before inserting the data in the tables. In the before_create method, let’s say that event is a required relationship, which will be defined by the ResourceRelationRequired , then we use our custom method

def require_relationship(resource_list, data):
    for resource in resource_list:
        if resource not in data:
            raise UnprocessableEntity({'pointer': '/data/relationships/{}'.format(resource)},
                                      "A valid relationship with {} resource is required".format(resource))


to check if the required relationships are present in the data. The event_id here can also be used to check for organizer or co-organizer access in the permissions manager for a particular event.

Here’s another permissions structure for a different API – Settings.

List

View

Create

Update

Delete

Superadmin/admin

Everyone else

✓ [1]

  1. Only app_nametaglineanalytics_keystripe_publishable_keygoogle_urlgithub_urltwitter_urlsupport_urlfacebook_urlyoutube_urlandroid_app_urlweb_app_url fields .

This API does not allow access to the complete object, but to only some fields which are listed above. The complete details can be checked here.

Resources

Continue ReadingUnderstanding Permissions for Various APIs in Open Event API Server

Using Custom Forms In Open Event API Server

One feature of the  Open Event management system is the ability to add a custom form for an event. The nextgen API Server exposes endpoints to view, edit and delete forms and form-fields. This blogpost describes how to use a custom-form in Open Event API Server.

Custom forms allow the event organizer to make a personalized forms for his/her event. The form object includes an identifier set by the user, and the form itself in the form of a string. The user can also set the type for the form which can be either of text or checkbox depending on the user needs. There are other fields as well, which are abstracted. These fields include:

  • id : auto generated unique identifier for the form
  • event_id : id of the event with which the form is associated
  • is_required : If the form is required
  • is_included : if the form is to be included
  • is_fixed : if the form is fixedThe last three of these fields are boolean fields and provide the user with better control over forms use-cases in the event management.

Only the event organizer has permissions to edit or delete these forms, while any user who is logged in to eventyay.com can see the fields available for a custom form for an event.

To create a custom-form for event with id=1, the following request is to be made:
POST  https://api.eventyay.com/v1/events/1/custom-forms?sort=type&filter=[]

with all the above described fields to be included in the request body.  For example:

{
 "data": {
   "type": "custom_form",
   "attributes": {
     "form": "form",
     "type": "text",
     "field-identifier": "abc123",
     "is-required": "true",
     "is-included": "false",
     "is-fixed": "false"
   }
 }
}

The API returns the custom form object along with the event relationships and other self and related links. To see what the response looks like exactly, please check the sample here.

Now that we have created a form, any user can get the fields for the same. But let’s say that the event organiser wants to update some field or some other attribute for the form, he can make the following request along with the custom-form id.

PATCH https://api.eventyay.com/v1/custom-forms/1

(Note: custom-form id must be included in both the URL as well as request body)

Similarly, to delete the form,
DELETE https://api.eventyay.com/v1/custom-forms/1     can be used.

Resources

Continue ReadingUsing Custom Forms In Open Event API Server

Implementing Admin Statistics Mail and Session API on Open Event Frontend

This blog article will illustrate how the admin-statistics-mail and admin-statistics-session API  are implemented on the admin dashboard page in Open Event Frontend.Our discussion primarily will involve the admin/index route to illustrate the process.The primary end points of Open Event API with which we are concerned with for fetching the admin statistics  for the dashboard are

GET /v1/admin/statistics/mails
GET /v1/admin/statistics/sessions

First we need to create the corresponding models according to the type of the response returned by the server , which in this case will be admin-statistics-event and admin-statistics-sessions, so we proceed with the ember CLI commands:

ember g model admin-statistics-mail
ember g model admin-statistics-session

Next we define the model according to the requirements. The model needs to extend the base model class, and all the fields will be number since the all the data obtained via these models from the API will be numerical statistics

import attr from 'ember-data/attr';
import ModelBase from 'open-event-frontend/models/base';

export default ModelBase.extend({
 oneDay     : attr('number'),
 threeDays  : attr('number'),
 sevenDays  : attr('number'),
 thirtyDays : attr('number')
});

And the model for sessions will be the following. It too will consist all the attributes of type number since it represents statistics

import attr from 'ember-data/attr';
import ModelBase from 'open-event-frontend/models/base';

export default ModelBase.extend({
 confirmed : attr('number'),
 accepted  : attr('number'),
 submitted : attr('number'),
 draft     : attr('number'),
 rejected  : attr('number'),
 pending   : attr('number')
});

Now we need to load the data from the api using the models, so will send a get request to the api to fetch the current permissions. This can be easily achieved via a store query in the model hook of the admin/index route.However this cannot be a normal get request. Because the the urls for the end point are /v1/admin/statistics/mails & /v1/admin/statistics/sessions but there are no relationships between statistics and various sub routes, which is what ember’s default behaviour would expect.

Hence we need to override the generated default request url using custom adapters and use buildUrl method to customize the request urls.

import ApplicationAdapter from './application';

export default ApplicationAdapter.extend({
 buildURL(modelName, id, snapshot, requestType, query) {
   let url = this._super(modelName, id, snapshot, requestType, query);
   url = url.replace('admin-statistics-session', 'admin/statistics/session');
   return url;
 }
});

The buildURL method replaces the the default  URL for admin-statistics-session  with admin/statistics/session otherwise the the default request would have been

GET v1/admin-statistics-session

Similarly it must be done for the mail statistics too. These will ensure that the correct request is sent to the server. Now all that remains is making the requests in the model hooks and adjusting the template slightly for the new model.

model() {
   return RSVP.hash({
         mails: this.get('store').queryRecord('admin-statistics-mail', {
       filter: {
         name : 'id',
         op   : 'eq',
         val  : 1
       }
     }),
     sessions: this.get('store').queryRecord('admin-statistics-session', {
       filter: {
         name : 'id',
         op   : 'eq',
         val  : 1
       }
     })
   });
 }


queryRecord is used instead of query because only a single record is expected to be returned by the API.

Resources

Tags :

Open event, Open event frontend, ember JS, ember service, semantic UI, ember-data, ember adapters,  tickets, Open Event API, Ember models

Continue ReadingImplementing Admin Statistics Mail and Session API on Open Event Frontend

Skill Editor in SUSI Skill CMS

SUSI Skill CMS is a web application built on ReactJS framework for creating and editing SUSI skills easily. It follows an API centric approach where the SUSI Server acts as an API server. In this blogpost we will see how to add a component which can be used to create a new skill SUSI Skill CMS.

For creating any skill in SUSI we need four parameters i.e model, group, language, skill name. So we need to ask these 4 parameters from the user. For input purposes we have a common card component which has dropdowns for selecting models, groups and languages, and a text field for skill name input.

<SelectField
    floatingLabelText="Model"
    value={this.state.modelValue}
    onChange={this.handleModelChange}
>
    {models}
</SelectField>
<SelectField
    floatingLabelText="Group"
    value={this.state.groupValue}
    onChange={this.handleGroupChange}
>
    {groups}
</SelectField>
<SelectField
    floatingLabelText="Language"
    value={this.state.languageValue}
    onChange={this.handleLanguageChange}
>
    {languages}
</SelectField>
<TextField
    floatingLabelText="Enter Skill name"
    floatingLabelFixed={true}
    hintText="My SUSI Skill"
    onChange={this.handleExpertChange}
/>
<RaisedButton label="Save" backgroundColor="#4285f4" labelColor="#fff" style={{marginLeft:10}} onTouchTap={this.saveClick} />

This is the card component where we get the user input. We have API endpoints on SUSI Server for getting the list of models, groups and languages. Using those APIs we inflate the dropdowns.
Then the user needs to edit the skill. For editing of skills we have used Ace Editor. Ace is an code
editor written in pure javascript. It matches the features native editors like Sublime and TextMate.

To use Ace we need to install the component.

npm install react-ace --save                        

This command will install the dependency and update the package.json file in our project with this dependency.

To use this editor we need to import AceEditor and place it in the render function of our react class.

<AceEditor
    mode=" markup"
    theme={this.state.editorTheme}
    width="100%"
    fontSize={this.state.fontSizeCode}
    height= "400px"
    value={this.state.code}
    name="skill_code_editor"
    onChange={this.onChange}
    editorProps={{$blockScrolling: true}}
/>

Now we have a page that looks something like this

Now we need to handle the click event when a user clicks on the save button.

First we check if the user is logged in or not. For this we check if we have the required cookies and the access token of the user.

 if(!cookies.get('loggedIn')) {
            notification.open({
                message: 'Not logged In',
                description: 'Please login and then try to create/edit a skill',
                icon: <Icon type="close-circle" style={{ color: '#f44336' }} />,
            });
            return 0;
        }

If the user is not logged in then we show him a error notification and asks him to login.

Then we check if he has filled all the required fields like name of the skill etc. and after that we call an API Endpoint on SUSI Server that will finally store the skill in the skill_data_repo.

let url= “http://api.susi.ai/cms/modifySkill.json”
$.ajax({
    url:url,
    dataType: 'jsonp',
    jsonp: 'callback',
    crossDomain: true,
    success: function (data) {
        console.log(data);
        if(data.accepted===true){
            notification.open({
                message: 'Accepted',
                description: 'Your Skill has been uploaded to the server',
                icon: <Icon type="check-circle" style={{ color: '#00C853' }} />,
            });
           }
    }
});

In the success function of ajax call we check if accepted parameter is true from the server or not. If accepted is true then we show user a notification with a message that “Your Skill has been uploaded to the server”.

To see this component running please visit http://skills.susi.ai/skillEditor.

Resources

Material-UI: http://www.material-ui.com/

Ace Editor: https://github.com/securingsincity/react-ace

Ajax: http://api.jquery.com/jquery.ajax/

Universal Cookies: https://www.npmjs.com/package/universal-cookie

Continue ReadingSkill Editor in SUSI Skill CMS

Adding TextDrawable as a PlaceHolder in Open Event Android App

The Open Event Android project has a fragment for showing speakers of the event. Each Speaker model has image-url which is used to fetch the image from server and load in the ImageView. In some cases it is possible that image-url is null or client is not able to fetch the image from the server because of the network problem. So in these cases showing Drawable which contains First letters of the first name and the last name along with a color background gives great UI and UX. In this post I explain how to add TextDrawable as a placeholder in the ImageView using TextDrawable library.

1. Add dependency

In order to use TextDrawable in your app add following dependencies in your app module’s build.gradle file.

dependencies {
	compile 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1'
}

2. Create static TextDrawable builder

Create static method in the Application class which returns the builder object for creating TextDrawables. We are creating static method so that the method can be used all over the App.

private static TextDrawable.IShapeBuilder textDrawableBuilder;

public static TextDrawable.IShapeBuilder getTextDrawableBuilder()
 {
        if (textDrawableBuilder == null) {
            textDrawableBuilder = TextDrawable.builder();
        }
        return textDrawableBuilder;
}

This method first checks if the builder object is null or not and then initialize it if null. Then it returns the builder object.

3.  Create and initialize TextDrawable object

Now create a TextDrawable object and initialize it using the builder object. The Builder has methods like buildRound(), buildRect() and buildRoundRect() for making drawable round, rectangle and rectangle with rounded corner respectively. Here we are using buildRect() to make the drawable rectangle.

TextDrawable drawable = OpenEventApp.getTextDrawableBuilder()
                    .buildRect(Utils.getNameLetters(name), ColorGenerator.MATERIAL.getColor(name));

The buildRect() method takes two arguments one is String text which will be used as a text in the drawable and second is int color which will be used as a background color of the drawable. Here ColorGenerator.MATERIAL returns material color for given string.

4.  Create getNameLetters()  method

The getNameLetters(String name) method should return the first letters of the first name and last name as String.

Example, if the name is “Shailesh Baldaniya” then it will return “SB”.

public static String getNameLetters(String name) {
        if (isEmpty(name))
            return "#";

        String[] strings = name.split(" ");
        StringBuilder nameLetters = new StringBuilder();
        for (String s : strings) {
            if (nameLetters.length() >= 2)
                return nameLetters.toString().toUpperCase();
            if (!isEmpty(s)) {
                nameLetters.append(s.trim().charAt(0));
            }
        }
        return nameLetters.toString().toUpperCase();
}

Here we are using split method to get the first name and last name from the name. The charAt(0) gives the first character of the string. If the name string is null then it will return “#”.   

5.  Use Drawable

Now after creating the TextDrawable object we need to load it as a placeholder in the ImageView for this we are using Picasso library.

Picasso.with(context)
        .load(image-url)
        .placeholder(drawable)
        .error(drawable)
        .into(speakerImage);

Here the placeholder() method displays drawable while the image is being loaded. The error() method displays drawable when the requested image could not be loaded when the device is offline. SpeakerImage is an ImageView in which we want to load the image.

Conclusion

TextDrawable is a great library for generating Drawable with text. It has also support for animations, font and shapes. To know more about TextDrawable follow the links given below.

Continue ReadingAdding TextDrawable as a PlaceHolder in Open Event Android App

Implementing Logging Functionality in Open Event Webapp

  • INFO: Info statements give information about the task currently being performed by the webapp
  • SUCCESS: Success statements give the information of a task being successfully completed
  • ERROR: Error statements give information about a task failing to complete. These statements also contain a detailed error log

Along with the type of the statement, the object also contains information about the task. For all types of statements, there is a field called smallMessage containing short information about the task. For the ERROR statements where more information is required to see what went wrong, the message object has an additional field called largeMessage which holds detailed information about the event.

We also create a new file called buildlogger.js and define a function for creating log statements about the tasks being performed by generator and export it. The function creates a message object from the arguments received and then return it to the client under the buildLog event via the socket IO.

exports.addLog = function(type, smallMessage, socket, largeMessage) {
 var obj = {'type' : type, 'smallMessage' : smallMessage, 'largeMessage': largeMessage};
 var emit = false;
 if (socket.constructor.name === 'Socket') {
   emit = true;
 }
 if (emit) {
   socket.emit('buildLog', obj);
 }
};

Most of the steps of the generation process are defined in the generator.js file. So, we include the logging file there and call the addLog function for sending logs messages to the client. All the different steps like cleaning temporary folders, copying assets, fetching JSONs, creating the website directory, resizing images etc have multiple log statements for their inception and successful/erroneous completion. Below is an excerpt from the cleaning step.

var logger = require('./buildlogger.js');
 async.series([
   (done) => {
     console.log('CLEANING TEMPORARY FOLDERS\n');
     logger.addLog('Info', 'Cleaning up the previously existing temporary folders', socket);
     fs.remove(distHelper.distPath + '/' + appFolder, (err) => {
       if(err !== null) {
         // Sending Error Message when the remove process failed
         logger.addLog('Error', 'Failed to clean up the previously existing temporary folders', socket, err);
       }
       // Success message denoting the completion of the step
       logger.addLog('Success', 'Successfully cleaned up the temporary folders', socket);
       done(null, 'clean');
     });
   }
 )]

But we have only done the server side work now. We also have to handle the message on the client side. We send the message object to the client under the event buildLog and set up a listener for that event to catch the sent message. After the message object is received on the client side, we extract the information out of that object and then display it on the website. We have a div having an id of buildLog for displaying the log information. The content of the message is dynamically added to it as soon as it is received from the server. All the client side logic is handled in the form.js file.

socket.on('buildLog', function(data) {
   var spanElem = $('<span></span>'); // will contain the info about type of statement
   var spanMess = $('<span></span>'); // will contain the actual message
   var aElem = $('<button></button>'); // Button to view the detailed error log
   var paragraph = $('<p></p>'); // Contain the whole statement
   var divElem; // Contain the detailed error log
   spanMess.text(data.smallMessage);
   spanElem.text(data.type.toUpperCase() + ':');
   paragraph.append(spanElem);
   paragraph.append(spanMess);
   divElem.text(data.largeMessage);
   paragraph.append(aElem);
   paragraph.append(divElem);
   $('#buildLog').append(paragraph); // Div containing all the log messages
};

This is how the logs look on the client side. They are loaded on the go in real time as and when the events occur.

image (1).jpg

Resources:

Continue ReadingImplementing Logging Functionality in Open Event Webapp

Implementing Tracks Filter in Open Event Webapp using the side track name list

4f451d29-c6c2-44be-9ef7-d91a45fc1eb0.png

On Clicking the Design, Art, Community Track

ff85d907-512b-4a41-be49-888bbd17bf83.png

But, it was not an elegant solution. We already had a track names list present on the side of the page which remained unused. A better idea was to use this side track names list to filter the sessions. Other event management sites like http://sched.org follow the same idea. The relevant issue for it is here and the major work can be seen in this Pull Request. Below is the screenshot of the unused side track names list.

5b15a297-fd5e-4c23-bc1b-dbed193db0f4.png

The end behavior should be something like this, the user clicks on a track and only sessions belonging to the track should be visible and the rest be hidden. There should also be a button for clearing the applied filter and reverting the page back to its default view. Let’s jump to the implementation part.

First, we make the side track name list and make the individual tracks clickable.

<div class="track-names col-md-3 col-sm-3"> 
  {{#tracknames}}
    <div class="track-info">
      <span style="background-color: {{color}};" 
      class="titlecolor"></span>
      <span class="track-name" style="cursor: pointer">{{title}}
      </span>
    </div>
  {{/tracknames}}
</div>

f45f1591-937d-4e2f-9245-237cfaf3af0d.png

Now we need to write a function for handling the user click event on the track name. Before writing the function, we need to see the basic structure of the tracks page. The divs with the class date-filter contain all the sessions scheduled on a given day. Inside that div, we have another div with class tracks-filter which contains the name of the track and all the sessions of that track are inside the div with class room-filter.

Below is a relevant block of code from the tracks.hbs file

<div class="date-filter">
  // Contains all the sessions present in a single day
  <div class="track-filter row">
    // Contains all the sessions of a single track
    <div class="row">
      // Contains the name of the track
      <h5 class="text">{{caption}}</h4>
    </div>
    <div class="room-filter" id="{{session_id}}">
      // Contain the information about the session
    </div>
  </div>
</div>

We iterate over all the date-filter divs and check all the track-filter divs inside it. We extract the name of the track and compare it to the name of the track which the user selected. If both of them are same, then we show that track div and all the sessions inside it. If the names don’t match, then we hide that track div and all the content inside it. We also keep a variable named flag and set it to 0 initially. If the user selected track is present on a given day, we set the flag to 1. Based on it, we decide whether to display that particular day or not. If the flag is set, we display the date-filter div of that day and the matched track inside it. Otherwise, we hide the div and all tracks inside it.

$('.track-name').click(function() {
  // Get the name of the track which the user clicked
  trackName = $(this).text();
  // Show the button for clearing the filter applied and reverting to the default view
  $('#filterInfo').show();
  $('#curFilter').text(trackName);
  // Iterate through the divs and show sessions of user selected track
  $('.date-filter').each(function() {
    var flag = 0;
    $(this).find('.track-filter').each(function() {
      var name = $(this).find('.text').text();
      if(name != trackName) {
        $(this).hide();
        return;
      }
      flag = 1;
      $(this).show();
    });
    if (flag) {
     $(this).show();
    } else {
      $(this).hide();
    }
  });
});

On Selecting the Android Track of FOSSASIA Summit, we see something like this

935f208b-c17c-4d41-abb6-7197c003d962.png

Now the user may want to remove the filter. He/she can just click on the Clear Filter button shown in the above screenshot to remove the filter and revert back to the default view of the page.

$('#clearFilter').click(function() {                                                                                                   
  trackFilterMode = 0;                                                                                                                 
  display();                                                                                                                           
  $('#filterInfo').hide();                                                                                                             
});

Back to the default view of the page

2ff61ccf-17cf-4595-9c2f-e6825de549f7.png

References:

Continue ReadingImplementing Tracks Filter in Open Event Webapp using the side track name list

Writing Dredd Test for Event Topic-Event Endpoint in Open Event API Server

The API Server exposes a large set of endpoints which are well documented using apiary’s API Blueprint. Ton ensure that these documentations describe exactly what the API does, as in the response made to a request, testing them is crucial. This testing is done through Dredd Documentation testing with the help of FactoryBoy for faking objects.

In this blogpost I describe how to use FactoryBoy to write Dredd tests for the Event Topic- Event endpoint of Open Event API Server.

The endpoint for which tests are described here is this: For testing this endpoint, we need to simulate the API GET request by making a call to our database and then compare the response received to the expected response written in the api_blueprint.apib file. For GET to return some data we need to insert an event with some event topic in the database.

The documentation for this endpoint is the following:

To add the event topic and event objects for GET events-topics/1/events, we use a hook. This hook is written in hook_main.py file and is run before the request is made.

We add this decorator on the function which will add objects to the database. This decorator basically traverses the APIB docs following level with number of ‘#’ in the documentation to ‘>’ in the decorator. So for
 we have,

Now let’s write the method itself. In the method here, we first add the event topic object using EventTopic Factory defined in the factories/event-topic.py file, the code for which can be found here.

Since the endpoint also requires some event to be created in order to fetch events related to an event topic, we add an event object too based on the EventFactoryBasic class in factories/event.py  file. [Code]

To fetch the event related to a topic, the event must be referenced in that particular event topic. This is achieved by passing event_topic_id=1 when creating the event object, so that for the event that is created by the constructor, event topic is set as id = 1.
event = EventFactoryBasic(event_topic_id=1)
In the EventFactoryBasic class, the event_topic_id is set as ‘None’, so that we don’t have to create event topic for creating events in other endpoints testing also. This also lets us to not add event-topic as a related factory. To add event_topic_id=1 as the event’s attribute, an event topic with id = 1 must be already present, hence event_topic object is added first.
After adding the event object also, we commit both of these into the database. Now that we have an event topic object with id = 1, an event object with id = 1 , and the event is related to that event topic, we can make a call to GET event-topics/1/events and get the correct response.

Related:

Continue ReadingWriting Dredd Test for Event Topic-Event Endpoint in Open Event API Server

Adding Event Type – Event Endpoint Docs in Open Event API Server

As part of the extensive documentation written for Open Event API Server, event list endpoint with regards to event type had to be added to API Blueprint docs. The endpoint under consideration in this blogpost is:

GET https://api.eventyay.com/v1/event-types?sort=identifier&filter=[]

This endpoint returns a list of events for a specific event type. These event types are used for categorising similar types of events in the API Server. One example for an event type is : Camp, Treat & Retreat

With "slug": "camp-treat-retreat"
API Blueprint docs: 

To add the documentation in api_bluprint.apib file, we need to define the collection list and different levels using different numbers of ‘#’ . Since this endpoint is classified under the collection group Events, we first write it under # Events Collection
Now, since this is a separate and standalone endpoint, we describe the URL format for this in the following manner:

GET https://api.eventyay.com/v1/event-types?sort=identifier&filter=[]

Defining the parameters for the url includes

  • Page size: 10
  • Page number: 2
  • Sort: ‘identifier’
  • Filter_by : [none]

On the third level we define the type of request to be made along with the endpoint description, which in this case is :
### List All Events of an Event Type [GET]
Get a list of events.
This defines the GET request being made to the URL

GET https://api.eventyay.com/v1/event-types?sort=identifier&filter=[]

 The next step is adding the request headers to the docs. In the API server each request will contain the JWT Authentication token and one or more of Content-type or Accept attribute depending on the request method: GET, PATCH, DELETE or POST.
In any case the value for both of these will be:

Content-Typeapplication/vnd.api+json
Acceptapplication/vnd.api+json, (mime type for JSONAPI 0.1 specs)
Authentication token included in the following format:
AuthorizationJWT <Auth Key>
 

The response obtained from making a call to this endpoint is added next.API Blueprint describes adding the Response along with the mime type.
+ Response 200 (application/vnd.api+json)

To parse this document correctly, apiary requires the response data to be added starting at column 9, which means 8 spaces have to be left before it. Indenting with TAB is currently not supported by apiary for api blueprint docs and will give rise to compilation error if tab indentation is found. If not properly indented, semantic issues will arise on compilation, but the tests proceed with a valid document. To ensure that this document is syntactically correct, we need to check once which can be done on apiary’s site. The compilation tool there raises proper issues, exceptions and errors. If the  document is valid, it is rendered side-by-side using api blueprint’s default theme. It is a helpful tool for on the fly editing for apib documentation. The tool and my copy of documentation can be found at: 
https://app.apiary.io/eopenevent/editor

The response data as received from making the GET request is added below. This data in this case includes

"meta": {
    "count": 1 
     },
    "data": [
         {}
     ]

Meta data contains the count of number of events for the given event-type, here : 1.
The details of each event is contained in the list defined by data. 
This is excluded from here as it can be simply found on  API Servers’ Documentation.

Related:
API Blueprint | Write the Docs – APIB Docs
Apiary.io
API Blueprint tutorial | Writing REST APIs – I’d rather be writing, blog

 

Continue ReadingAdding Event Type – Event Endpoint Docs in Open Event API Server