Adding a list view for the Sessions Public Page in Open Event Frontend
This blog article will describe how the sessions are listed in the public pages of an event in Open Event Frontend, which allows the user to view all the sessions of an event. The sessions are filtered as per date. The primary end point of Open Event API with which we are concerned with for fetching the the users details is GET /v1/events/{event_identifier}/sessions
The route of the public page fetches all the sessions of a particular events and filters them as per the criteria selected by the user. The user can view the sessions of a particular day, week or month. The user can also view the list of all the sessions. The query written in the route is:
async model(params) { const eventDetails = this.modelFor('public'); let sessions = null; if (params.session_status === 'today') { sessions = await this.get('store').query('session', { filter: [ { and: [ { name : 'event', op : 'has', val : { name : 'identifier', op : 'eq', val : eventDetails.id } }, { name : 'starts-at', op : 'ge', val : moment().startOf('day').toISOString() }, { name : 'starts-at', op : 'lt', val : moment().endOf('day').toISOString() } ] } ] }); } else { sessions = await this.get('store').query('session', { filter: [ { name : 'event', op : 'has', val : { name : 'identifier', op : 'eq', val : eventDetails.id } } ] }); } return { event : eventDetails, session : sessions }; }
The view route is located at app/e/{event_identifier}/sessions/all. This route will show all the sessions of the selected event. Similarly /week will show the sessions of a week and /month will show the sessions of a month.Four joint buttons are used in the UI of the public page to redirect to these routes.
To list the sessions ember component of session cards is used to include a session in a card with the details of the session like the time, abstract etc and also the session’s track and the details of the speakers like the name, information and social media accounts. In the template of the route this component is called and used in the UI within an ember component. In case there are no sessions that exist between a given time period, a helper text is displayed stating “No sessions exist for the given period”.
class="ui buttons"> {{#link-to 'public.sessions.list' model.event.id 'all' class="ui button"}}{{t 'All'}}{{/link-to}} {{#link-to 'public.sessions.list' model.event.id 'today' class="ui button"}}{{t 'Today'}}{{/link-to}} {{#link-to 'public.sessions.list' model.event.id 'week' class="ui button"}}{{t 'Week'}}{{/link-to}} {{#link-to 'public.sessions.list' model.event.id 'month' class="ui button"}}{{t 'Month'}}{{/link-to}}class="ui raised very padded text container segment"> {{#each model.session as |session|}} {{public/session-item session=session}} {{else}}class="ui disabled header">{{t 'No Sessions exist for this time period'}}{{/each}} </div>Resources
- Official Ember Model docs: https://github.com/ebryn/ember-model
- Ember JS- route: https://guides.emberjs.com/release/routing/defining-your-routes
- Open Event API Docs: https://open-event-api.herokuapp.com/
- ember JS- Conditionally rendering Templates: https://guides.emberjs.com/v1.12.0/templates/rendering-with-helpers
Add Routes to Add and Edit multiple Sessions in the CFS section of Open Event Frontend
This blog article will describe how the users can add multiple session proposals and edit them through the Call for Speakers modal in Open Event Frontend. The logged in user first adds himself as the speaker through the modal then he can add multiple sessions. The user will be added as the speaker for all the sessions he adds through the CFS modal.
To submit the sessions the user first has to add himself as a speaker of that event in the route:
e/{event_identifier}/cfs
After the user registers himself as the speaker of that event whose Call for Speakers is open he can add multiple sessions and also edit those sessions.
When Add Session Details button is clicked the user gets redirected to a form with the route
e/{event_identifier}/cfs/new-session.
async model() { const eventDetails = this.modelFor('public'); return { event : eventDetails, forms : await eventDetails.query('customForms', { sort : 'id', 'page[size]' : 50 }), session: await this.get('store').createRecord('session', { event : eventDetails, creator : this.get('authManager.currentUser') }), tracks : await eventDetails.query('tracks', {}), sessionTypes : await eventDetails.query('sessionTypes', {}) }
On this route there is a session form where the user can add details like title, short abstract, comments, track etc. Once he clicks on the save button after entering the details post request is sent to the server and that session is added to the list of sessions of that event and the user is added as the speaker of that session.
class="ui container">
{{#if speaker.id}}
{{forms/session-speaker-form fields=model.forms data=model isLoading=isLoading
save=(action 'save' speaker) isSession=true includeSession=true}}
{{/if}}
The user can add another session or edit the sessions previously entered by him. When Edit session is clicked the user gets redirected to the route e/{event_identifier}/cfs/edit/{session_id}
async model(params) { const eventDetails = this.modelFor('public'); return { event : eventDetails, forms : await eventDetails.query('customForms', { sort : 'id', 'page[size]' : 50 }), session: await this.get('store').findRecord('session', params.session_id, { include: 'session-type,track' }) }; }
On this route the user can change the details of the session he had entered before. On clicking save a patch request is sent to the server and the new details are saved.
Resources
- Ember JS- route: https://guides.emberjs.com/release/routing/defining-your-routes
- Open Event API Docs: https://open-event-api.herokuapp.com/
- ember JS- Conditionally rendering Templates: https://guides.emberjs.com/v1.12.0/templates/rendering-with-helpers
Displaying name of users in Users tab in Admin Panel
In the Users tab in the Admin Panel, we have a lot of user information displayed in a tabular form. This information is fetched from the accounting objects of each user. As the users are now able to also store their name in their respective accounting object, hence we needed to implement a feature to display the name of the users in the Users table in a separate column. This blog post explains how the user names are fetched from the respective accounting objects and are then displayed in the Users table in the Admin Panel.
How is name of user stored on the server?
The name of any user is stored in the user’s accounting object. All the settings of a user are stored in a JSONObject with the key name as ‘settings’. The name of a user is also stored in ‘settings’ JSONObject. This is shown as follows:
Modifying GetUsers.java to return name of users
The endpoint /aaa/getUsers.json is used to return the accounting info of all users. This includes their signup time, last login time, last login IP, etc. We needed to modify it to return the name of users also along with the already returned data. This is implemented as follows:
if(accounting.getJSON().has("settings")) { JSONObject settings = accounting.getJSON().getJSONObject("settings"); if(settings.has("userName")) { json.put("userName", settings.get("userName")); } else { json.put("userName", ""); } } else { json.put("userName", ""); } accounting.commit();
Fetching names of all users from the server
We need to make an AJAX call to ‘/aaa/getUsers.json’ as soon as we switch to the Users tab in the Admin Panel. We need to extract all the required data from the JSON response object and put them in state variables so that they can further be used as data indexes for different columns of the table. The implementation of the AJAX call is as follows:
let url = `${urls.API_URL}/aaa/getUsers.json?access_token=` + cookies.get('loggedIn') + '&page=' + page; $.ajax({ url: url, dataType: 'jsonp', jsonp: 'callback', crossDomain: true, success: function(response) { let userList = response.users; let users = []; userList.map((data, i) => { let user = { userName: data.userName, }; users.push(user); return 1; }); this.setState({ data: users, }); }.bind(this) });
Displaying name of users in Users tab in Admin Panel
We needed to add another column titled ‘User Name’ in the Users table in the Admin Panel. The ‘dataIndex’ attribute of the Ant Design table component specifies the data value which is to be used for that particular column. For our purpose, our data value which needs to be displayed in the ‘User Name’ column is ‘userName’. We also specify a width of the column as another attribute. The implementation is as follows:
this.columns = [ // other columns { title: 'User Name', dataIndex: 'userName', width: '12%', } // other columns ];
This is how the names of users are fetched from their accounting object and are then being displayed in the Users tab in Admin Panel.
Resources
Implementing Search User Feature for Admins
The users tab of admin panel of SUSI.AI provides a list of all users registered on SUSI. This helps admins to get an overview of users and also provides the admins option like change user roles and delete accounts. The list of users is displayed in a table which also uses pagination to handle larger number of users. But to find a particular user can be a difficult task if the user base is large. Therefore a feature to search for users by their email has been implement which searches for users on SUSI server and then sends the searched users to the client. In this post we will discuss both about the server and client side implementation of this feature.
Enhancing pagination in Badgeyay
A Badge generator like Badgeyay must be able to generate, store and export the user data as and when needed. This blog post covers the enhancement of pagination in the frontend of badgeyay project. There are small “next” and “previous” links to toggle between pages..
Enhancing the current way of links
The problem with the pagination links was that in case of no more badges/users etc, the links would always appear on the bottom right of the table. The previous link must not appear when no previous page is there and vice versa for the next link.
Step 1 : Adding the package to package.json
Image link is the link to the user’s uploaded image on remote firebase server.
{{#if allow}} |
Step 2 : Initializing the variables in setupController
Once we have added the if construct to the badgeyay frontend then we need to add the variable initialization in the setupController method in EmberJS.
setupController(controller, model) { |
Step 3 : Implementing state changed in the controllers
Now we need to handle the situation when a user clicks the links and there are more or less links to display. This is done by checking the length of the model in the controller.
if (this.page === 1) { |
Same needs to be done for all the controllers that have pagination available.
And finally we need to pass these variables in the component template. One such example is given below.
<div class=“ui grid user-grid”> |
Finally, we have the pagination links working as desired..
Screenshot of changes
Resources
- The Pull Request for the same : https://github.com/fossasia/badgeyay/pull/1415
- The Issue for the same : https://github.com/fossasia/badgeyay/issues/1402
- Read about Ember data : https://www.emberjs.com/api/data/classes/DS.Model.html
- Read about Handlebars : https://guides.emberjs.com/release/templates/handlebars-basics/
Implementing User Stats for SUSI.AI Admin Panel
SUSI.AI has an admin panel where users with roles operator or above get an overview of various stats and manage other users and skills. The admin panel allows the system admins to get a list of all users registered on susi and also provide an option to change their user roles as well. The admin tab provides us with the statistics of its users. In this post we will discuss how this is implemented on susi server.
Delete User Account Service using LoginService.java API in SUSI.AI
SUSI.AI has an api to handle all account related services called accounts.susi.ai. This API provides services like change password and delete account. the main goal of accounts.susi.ai is to centralise the accounts’ related settings from web, android and iOS clients into a single place like other accounting services. In this post we will discuss one of these features which is the delete user account from SUSI.AI.
The api accounts.susi.ai has a react file DeleteAccount.react.js. This file handle the delete account service and interacts with the LoginServce.java api, which is the core file for authentication. I will discuss in details how these two interact with each other and with other files.
How User preferences data is stored in Chat.susi.ai using Accounting Model
Like any other accounting services SUSI.AI also provides a lot of account preferences. Users can select their language, timezone, themes, speech settings etc. This data helps users to customize their experience when using SUSI.AI.
In the web client these user preferences are fetch from the server by UserPreferencesStore.js and the user identity is fetched by UserIdentityStore.js. These settings are then exported to the settings.react.js file. This file is responsible for the settings page and takes care of all user settings. Whenever a user changes a setting, it identifies the changes and show an option to save these changes. These changes are then updated on the server using the accounting model of SUSI.AI. Let’s take a look at each file discussed above in detail.
Fetching Info of All Users and their connected devices for the SUSI.AI Admin Panel
Fetching the data of all users is required for displaying the list of users on the SUSI.AI Admin panel. It was also required to fetch the information of connected devices of the user along with the other user data. The right to fetch the data of all users should only be permitted to user roles “OPERATOR” and above. This blog post explains how the data of connected devices of all users is fetched, which can then be used in the Admin panel.
How is user data stored on the server?
All the personal accounting information of any user is stored in the user’s accounting object. This is stored in the “accounting.json” file. The structure of this file is as follows:
{ "email:akjn11@gmail.com": { "devices": { "8C-39-45-23-D8-95": { "name": "Device 2", "room": "Room 2", "geolocation": { "latitude": "54.34567", "longitude": "64.34567" } } }, "lastLoginIP": "127.0.0.1" }, "email:akjn22@gmail.com": { "devices": { "1C-29-46-24-D3-55": { "name": "Device 2", "room": "Room 2", "geolocation": { "latitude": "54.34567", "longitude": "64.34567" } } }, "lastLoginIP": "127.0.0.1" } }
As can be seen from the above sample content of the “accounting.json” file, we need to fetch this data so that it can then be used to display the list of users along with their connected devices on the Admin panel.
Implementing API to fetch user data and their connected devices
The endpoint of the servlet is “/aaa/getUsers.json” and the minimum user role for this servlet is “OPERATOR”. This is implemented as follows:
@Override public String getAPIPath() { return "/aaa/getUsers.json"; } @Override public UserRole getMinimalUserRole() { return UserRole.OPERATOR; }
Let us go over the main method serviceImpl() of the servlet:
- We need to traverse through the user data of all authorized users. This is done by getting the data using DAO.getAuthorizedClients() and storing them in a Collection. Then we extract all the keys from this collection, which is then used to traverse into the Collection and fetch the user data. The implementation is as follows:
Collection<ClientIdentity> authorized = DAO.getAuthorizedClients(); List<String> keysList = new ArrayList<String>(); authorized.forEach(client -> keysList.add(client.toString())); for (Client client : authorized) { // code }
- Then we traverse through each client and generate a client identity to get the user role of the client. This is done using the DAO.getAuthorization() method. The user role of the client is also put in the final object which we want to return. This is implemented as follows:
JSONObject json = client.toJSON(); ClientIdentity identity = new ClientIdentity(ClientIdentity.Type.email, client.getName()); Authorization authorization = DAO.getAuthorization(identity); UserRole userRole = authorization.getUserRole(); json.put("userRole", userRole.toString().toLowerCase());
- Then the client credentials are generated and it is checked whether the user is verified or not. If the user is verified, then in the final object, “confirmed” is set to true, else it is set to false.
ClientCredential clientCredential = new ClientCredential (ClientCredential.Type.passwd_login, identity.getName()); Authentication authentication = DAO.getAuthentication(clientCredential); json.put("confirmed", authentication.getBoolean("activated", false));
- Then we fetch the accounting object of the user using DAO.getAccounting(), and extract all the user data and put them in separate key value pairs in the final object which we want to return. As the information of all connected devices of a user is also stored in the user’s accounting object, that info is also extracted the same way and put into the final object.
Accounting accounting = DAO.getAccounting(authorization.getIdentity()); if (accounting.getJSON().has("lastLoginIP")) { json.put("lastLoginIP", accounting.getJSON().getString("lastLoginIP")); } else { json.put("lastLoginIP", ""); } if(accounting.getJSON().has("signupTime")) { json.put("signupTime", accounting.getJSON().getString("signupTime")); } else { json.put("signupTime", ""); } if(accounting.getJSON().has("lastLoginTime")) { json.put("lastLoginTime", accounting.getJSON().getString("lastLoginTime")); } else { json.put("lastLoginTime", ""); } if(accounting.getJSON().has("devices")) { json.put("devices", accounting.getJSON().getJSONObject("devices")); } else { json.put("devices", ""); } accounting.commit();
This is how the data of all users is fetched by any Admin or higher user role, and is then used to display the user list on the Admin panel.
Resources
Implementation of admin dashboard on Accounts
The admin dashboard for SUSI.AI is implemented in the susi accounts to access various admin features like list all users registered on SUSI and change user roles of other users. In this post we will discuss about the client side implementation of listing registered users and changing their user roles.
You must be logged in to post a comment.