Adding different metrics sections to the start page

In the initial version of the SUSI.AI Skill CMS we simply displayed all the skills present in the system in the form of cards. Once the skill analytics was incorporated into the CMS we got a bunch of skill statistics and thus we enhanced the start page by incorporating horizontally scrollable skill cards as per skill metrics like top rated skills, most used skills, skills which have received the most feedback etc. I worked on adding the skills with most feedback section and the section for the top games. This post will majorly deal with how the metrics sections are implemented on the start page and how any new metrics can be incorporated into the system and thus displayed on the CMS.

About the API

/cms/getSkillMetricsData.json?language=${language}

Sample API call:

https://api.susi.ai/cms/getSkillMetricsData.json?language=en

 

This will return a JSON which contains the skill data for all the metrics.

{
 "accepted": true,
 "model": "general",
 "group": "All",
 "language": "en",
 "metrics": {
        "newest": [...],
     "rating": [...],
      ...
 }
 "message": "Success: Fetched skill data based on metrics",
   "session": {"identity": {
           "type": "host",
          "name": "162.158.23.7_68cefd16",
          "anonymous": true
   }}
}

 

All of the data for several metics comes from the metrics object of the response which in turn contains arrays of skill data for each metric.

CMS Implementation

Once the BrowseSkill component is mounted we make an API call to the server to fetch all the data and save it to the component state, this data is then fed to the ScrollCardList component as props and the scroll component is rendered with appropriate data for different metrics.

loadMetricsSkills = () => {
   let url;
   url =
           urls.API_URL +
           '/cms/getSkillMetricsData.json?language=' +
           this.state.languageValue;
   let self = this;
   $.ajax({
           url: url,
           dataType: 'jsonp',
           jsonp: 'callback',
           crossDomain: true,
           success: function(data) {
                   self.setState({
                           skillsLoaded: true,
                           staffPicksSkills: data.metrics.staffPicks,
                           topRatedSkills: data.metrics.rating,
                           topUsedSkills: data.metrics.usage,
                           latestUpdatedSkills: data.metrics.latest,
                           newestSkills: data.metrics.newest,
                           topFeedbackSkills: data.metrics.feedback,
                           topGames: data.metrics['Games, Trivia and Accessories'],
                   });
           },
           error: function(e) {
                   console.log('Error while fetching skills based on top metrics', e);
                   return self.loadMetricsSkills();
           },
   });
};

 

We are using a single component for skill metrics and skill listing which show up on applying any filter or visiting any category. Thus we think of a condition when the skill metrics are to be displayed and conditionally render the metrics section depending on the condition.

So the metrics section shows up only when we have not visited any category or language page, there’s no search query in the search bar, there’s no rating refine filter applied and no time filter applied.

let metricsHidden =
         this.props.routeType ||
         this.state.searchQuery.length > 0 ||
         this.state.ratingRefine ||
         this.state.timeFilter;

 

Depending on the section you want to display, pass appropriate data as props to the SkillCardScrollList component, say we want to display the section with most feedback

{this.state.topFeedbackSkills.length &&
!metricsHidden ? (
   <div style={metricsContainerStyle}>
           <div
                   style={styles.metricsHeader}
                   className="metrics-header"
           >
                   <h4>
                           {'"SUSI, what are the skills with most feedback?"'}
                   </h4>
           </div>
           {/* Scroll Id must be unique for all instances of SkillCardList*/}
           {!this.props.routeType && (
                   <SkillCardScrollList
                           scrollId="topFeedback"
                           skills={this.state.topFeedbackSkills}
                           modelValue={this.state.modelValue}
                           languageValue={this.state.languageValue}
                           skillUrl={this.state.skillUrl}
                   />
           )}
   </div>
) : null}

 

So if there are skills preset in the topFeedbackSkills array which was saved in the state from the server initially and the condition to hide metrics is false we render the component and pass appropriate props for scrollId, skills data, language and model values and skill url.

In a similar way any metrics section can be implemented in the CMS, if the data is not present in the API, modify the endpoint to enclose the data you need, fetch data data from the server and just render it.

So I hope after reading through this you have a more clearer understanding about how the metrics sections are implemented on the CMS.

Resources

Continue Reading

Performing Database Migrations using DbFlow

In Open Event Organizer Android App we decided to add database migrations for every change in database while development. Two of the reasons behind this –

  1. The users have some version of the app installed and then during development the developers had to modify the database, let’s say, add a table or modify existing ones. This makes the existing database incompatible with the new app. On adding database migration the upgradation of database takes place automatically and prevent the situation of reinstalling the app.
  2. Migrations makes it possible to rollback or upgrade to some particular database state. Thus, help in debugging certain changes in isolation.

Let’s discuss the implementation details. Consider the scenario when a user adds a new table named SpeakersCall. For creating migration for this change we need to add migration class inside OrgaDatabase class annotated with @Database. We will break it down to look closely at each step.

  1. Use @Migration annotation in DbFlow library and specify the new database version (by incrementing the existing version) and the database class.
  2. Extend BaseMigration and override migrate method.
  3. The logic used inside the migrate method can be different for different tasks. In the present case we first need to delete any existing table (if exists) with the name SpeakersCall and then recreate that table in database.
  4. Create an array of java classes which are created/modified.
  5. We wrap the SQL query inside a Database Wrapper class which prevents it from running recursively.
  6. FlowManager uses reflection to look up and construct the generated database holder class used in defining the structure for all databases used in this application. So we will getModelAdapter for the class to be recreated and use creation query returned by Model Adapter and execute it.
@Migration(version = 15, database = OrgaDatabase.class)
public static class MigrationTo15 extends BaseMigration {

@Override
public void migrate(@NonNull DatabaseWrapper databaseWrapper) {
Timber.d(“Running migration for DB version 14”);

Class<?>[] recreated = new Class[] {SpeakersCall.class};

for (Class<?> recreate: recreated) {
ModelAdapter modelAdapter = FlowManager.getModelAdapter(recreate);
databaseWrapper.execSQL(DROP_TABLE + modelAdapter.getTableName());
databaseWrapper.execSQL(modelAdapter.getCreationQuery());
}
}
}

Similarly, we can write migration for changing a column of table(s).

Continue Reading

Testing the ViewModels in Open Event Organizer App

In Open Event Organizer Android App we follow Test Driven Development Approach which means the features added in the app are tested thoroughly by unit tests. More tests would ensure better code coverage and fewer bugs. This blog explains how to write tests for Viewmodel class in MVVM architecture.

Specifications

We will use JUnit4 to write unit tests and Mockito for creating mocks. The OrdersViewModel class returns the list of Order objects to the Fragment class. The objects are requested from OrderRepository class which fetches them from Network and Database. We will create a mock of OrderRepository class since it is out of context and contain logic that doesn’t depend on Orders Respository. Below is the getOrders method that we will test.

 public LiveData<List<Order>> getOrders(long id, boolean reload) {
if (ordersLiveData.getValue() != null && !reload)
return ordersLiveData;

compositeDisposable.add(orderRepository.getOrders(id, reload)
.compose(dispose(compositeDisposable))
.doOnSubscribe(disposable -> progress.setValue(true))
.doFinally(() -> progress.setValue(false))
.toList()
.subscribe(ordersLiveData::setValue,
throwable -> error.setValue(ErrorUtils.getMessage(throwable).toString())));

return ordersLiveData;
}

We will be using InstantTaskExecutorRule() which is a JUnit Test Rule that swaps the background executor used by the Architecture Components with a different one which executes each task synchronously. We will use setUp() method to load the RxJavaPlugins, RxAndroid plugins and reset them in tearDown method which will ensure each test runs independently from the other and avoid memory leaks. After doing this initialization and basic setup for tests we can begin code the method shouldLoadOrdersSuccessfuly() to test the getOrders method present in ViewModel class. Let’s see the step by step approach.

  1. Use Mockito.when to return Observables one by one from ORDERS_LIST whenever the method getOrders of the mock orderRepository is called.
  2. We will use Mockito.InOrder and pass orders, orderRepository and progress to check if they are called in a particular order.
  3. We will use .observeForever method to observe on LiveData objects and add a ArrayList on change.
  4. Finally, we will test and verify if the methods are called in order.
@Test
public void shouldLoadOrdersSuccessfully() {
when(orderRepository.getOrders(EVENT_ID, false))
.thenReturn(Observable.fromIterable(ORDERS_LIST));

InOrder inOrder = Mockito.inOrder(orders, orderRepository, progress);

ordersViewModel.getProgress().observeForever(progress);

orders.onChanged(new ArrayList<>());

ordersViewModel.getOrders(EVENT_ID, false);

inOrder.verify(orders).onChanged(new ArrayList<>());
inOrder.verify(orderRepository).getOrders(EVENT_ID, false);
inOrder.verify(progress).onChanged(true);
inOrder.verify(progress).onChanged(false);
}

Similar approach can be followed for writing tests to check other behaviour of the ViewModel.

References

  1. Official Documentation for testing. https://developer.android.com/reference/android/arch/core/executor/testing/InstantTaskExecutorRule
  2. Official Documentation for JUnit.  https://junit.org/junit4/
  3. Official documentation for Mockito.  http://site.mockito.org/
  4. Open Event Organizer App codebase.  https://github.com/fossasia/open-event-orga-app
Continue Reading

Swipe to Check In/Out in Open Event Organizer App

Open Event Organizer App didn’t provide any option for the Event Organizer to view the list of Attendees present under an Order and check them in/out the event. Therefore, we designed a system such that the Organizer can just swipe the attendee present under an order to check them in or out. In this blog post, I will discuss how we implemented this functionality in Open Event Organizer App without using any third party libraries.

Specifications

We will create a separate class SwipeController.java which extends ItemTouchHelper.SimpleCallback and provide the swiping functionalities to our plain old recyclerview. We will call the super constructor with ItemTouchHelper.LEFT and ItemTouchHelper.RIGHT as arguments to provide left as well as right movements in each recyclerview list item. The bitmaps and paint object initialized here will be used later in onDraw.

public SwipeController(OrderDetailViewModel orderDetailViewModel, OrderAttendeesAdapter orderAttendeesAdapter, Context context) {
super(0, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT);
this.orderDetailViewModel = orderDetailViewModel;
this.orderAttendeesAdapter = orderAttendeesAdapter;

closeIcon = BitmapFactory.decodeResource(context.getResources(), R.drawable.close);
doneIcon = BitmapFactory.decodeResource(context.getResources(), R.drawable.done);

paintGreen.setColor(context.getResources().getColor(R.color.light_green_500));
paintRed.setColor(context.getResources().getColor(R.color.red_500));
}

Next, we will override getMovementFlags method. This method decides the allowed movement directions for each recyclerview item. The deciding logic is that, if an attendee is checked in then the allowed movement is left to check out and if an attendee is checked out then the allowed movement is right to check in. If neither of the above case, then both movements are allowed.

@Override
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
int dragFlags = 0;

If (orderDetailViewModel.getCheckedInStatus(

viewHolder.getAdapterPosition()) == null)
makeMovementFlags(dragFlags, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT);

if (orderDetailViewModel.getCheckedInStatus(

viewHolder.getAdapterPosition())) {
return makeMovementFlags(dragFlags, ItemTouchHelper.LEFT);
} else {
return makeMovementFlags(dragFlags, ItemTouchHelper.RIGHT);
}
}

The onChildDraw method involves the code doing actual drawing. The variables used in code are discussed below.

  1. ActionState – Checks the state of the recycler view item. We proceed with the below logic if the item is being swiped.
  2. dX – The distance by which the item is swiped. Positive for left and negative for right.
  3. Background – Background of the viewholder. Rectangular in shape and dimensions changed with change in dX.
  4. IconDest – Calculates the position where the icons (close icon or done icon) is placed in canvas
  5. Canvas – Java Canvas on which the drawing is done. We set the background and draw the bitmaps on their location in canvas.
@Override
public void onChildDraw(Canvas canvas, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder,
float dX, float dY, int actionState, boolean isCurrentlyActive) {
if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
View itemView = viewHolder.itemView;
float height = (float) itemView.getBottom() – (float) itemView.getTop();
float width = height / 3;
RectF background;
Paint paint;
Bitmap icon;
RectF iconDest;

if (dX > 0) {
background = new RectF((float) itemView.getLeft(), (float) itemView.getTop(), dX,
(float) itemView.getBottom());
paint = paintGreen;
icon = doneIcon;
iconDest = new RectF((float) itemView.getLeft() + width,
(float) itemView.getTop() + width, (float) itemView.getLeft() + 2 * width,
(float) itemView.getBottom() – width);
} else {
background = new RectF((float) itemView.getRight() + dX, (float) itemView.getTop(),
(float) itemView.getRight(), (float) itemView.getBottom());
paint = paintRed;
icon = closeIcon;
iconDest = new RectF((float) itemView.getRight() – 2 * width,
(float) itemView.getTop() + width, (float) itemView.getRight() – width,
(float) itemView.getBottom() – width);
}

canvas.drawRect(background, paint);
canvas.drawBitmap(icon, null, iconDest, paint);
}
super.onChildDraw(canvas, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
}

Now after the item is swiped out or in, we need to restore its original state again. For this we override the onSwiped method and call notifyItemChanged(). Also, the changes in UI (showing green side strip for checked in and red side strip for checked out) are done by. We call the toggleCheckin() method in ViewModel to toggle the checking status of the attendee in server and local database.

@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
int position = viewHolder.getAdapterPosition();

orderDetailViewModel.toggleCheckIn(position);
orderAttendeesAdapter.notifyItemChanged(position);
}

Last but not the least, we will override the onMove method to return false. Since we are not supporting drag and drop features therefore this method will never be called.

@Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
return false;
}

Resources

  1. Codebase for Open Event Organizer App https://github.com/fossasia/open-event-orga-app
  2. Official documentation for ItemTouchHelper.SimpleCallback https://developer.android.com/reference/android/support/v7/widget/helper/ItemTouchHelper.SimpleCallback
Continue Reading

Using Contextual Action Bar as Selection Toolbar

In Open Event Android Organizer App we were using Android Action Bar as Toolbar to allow deleting and editing of the list items but this implementation was buggy and not upto the industry standards. So we decided to implement this using Contextual Action Bar as a selection toolbar.

Specifications

We are using MVP Architecture so there will be Fragment class to interact with the UI and Presenter class to handle all the business logic and interact with network layer.

The SessionsFragment is the Fragment class for displaying List of Sessions to the user. We can long press any item to select it, entering into contextual action bar mode. Using this we will be able to select multiple items from list by a click and delete/edit them from toolbar.

To enter in Contextual Action Bar Mode use

ActionMode actionMode = getActivity().startActionMode(actionCallback);

To exit Contextual Action Bar Mode use

if (actionMode != null)
actionMode.finish();

We will implement Action.Callback interface in out fragment class. It’s contains three method declarations –

  1. onCreateActionMode – This method is executed when the contextual action bar is  created. We will inflate the toolbar menu using MenuInflator and set new status bar color.
  2. onPrepareActionMode – This method is executed after onCreateActionMode and also whenever the Contextual Action Bar is invalidated so we will set the visibility of delete button in toolbar here and return true to ignify that we have made some changes.
  3. onActionItemClicked – This method is executed whenever a menu item in toolbar is clicked. We will call the function showDeleteDialog.
  4. onDestroyActionMode – This method is executed whenever user leaves the contextual action bar mode by pressing back button on toolbar or back button in keyboard etc. Here we will make unselect all the selected list items and the status bar color.
public ActionMode.Callback actionCallback = new ActionMode.Callback() {
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
MenuInflater inflater = mode.getMenuInflater();
inflater.inflate(R.menu.menu_sessions, menu);if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
statusBarColor = getActivity().getWindow().getStatusBarColor();
getActivity().getWindow().setStatusBarColor(getResources().getColor(R.color.color_top_surface));
}
return true;
}@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
MenuItem menuItemDelete = menu.findItem(R.id.del);
MenuItem menuItemEdit = menu.findItem(R.id.edit);
menuItemEdit.setVisible(editMode);
menuItemDelete.setVisible(deletingMode);
return true;
}

@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
switch (item.getItemId()) {
case R.id.del:
showDeleteDialog();
break;
case R.id.edit:
getPresenter().updateSession();
break;
default:
return false;
}
return false;
}

@Override
public void onDestroyActionMode(ActionMode mode) {
actionMode = null;
getPresenter().resetToolbarToDefaultState();
getPresenter().unselectSessionList();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
//return to “old” color of status bar
getActivity().getWindow().setStatusBarColor(statusBarColor);
}
}
};

The showDeleteDialog method shows a AlertDialog to the user before deleting the selected items in the list. User can click on Ok to delete the items or on Cancel to close the dialog.

public void showDeleteDialog() {
if (deleteDialog == null)
deleteDialog = new AlertDialog.Builder(context)
.setTitle(R.string.delete)
.setMessage(String.format(getString(R.string.delete_confirmation_message),
getString(R.string.session)))
.setPositiveButton(R.string.ok, (dialog, which) -> {
getPresenter().deleteSelectedSessions();
})
.setNegativeButton(R.string.cancel, (dialog, which) -> {
dialog.dismiss();
})
.create();deleteDialog.show();
}

When user presses Ok then we call the method deleteSelectedSessions in presenter to delete the sessions.

Resources

  1. Offical doumentation for Contextual Action Bar by Google https://developer.android.com/guide/topics/ui/menus#CAB
  2. Official Documentation for Alert Dialog by Google https://developer.android.com/guide/topics/ui/dialogs
  3. Codebase for Open Event OrganIzer App on Github https://github.com/fossasia/open-event-orga-app/
Continue Reading

Working with Route Hooks in Badgeyay

Badgeyay is an open source project developed by FOSSASIA Community to generate badges for conferences and events through a simple user interface.

In this post I am going to explain about the route lifecycle hooks in ember and how we have utilized one lifecycle component to reset the controller state in badge generation form. In Ember every entity has some predefined set of methods, that it goes through the existence of the application. Route is not different from it. Our main goal is to restore the state of the controller every time we entered into a route, so that we receive a clean and new instance and not the previous state. The hook that fits in the situation is setupController(). This method is called after model() hook to set the controller state in the route. We will restore the variables in the controller here in this method to reset it to original state. This will help in removing the messages and progress on the previous state of the page on a new visit.

Procedure

  1. Open the route, where we want to override the hook, and create a method setupController() this will call the base hook and override its behaviour.
setupController(controller, model) {
  this._super(…arguments);
  set(controller, ‘defImages’, model.def_images);
  set(controller, ‘user’, model.user);
  this.set(‘controller.badgeGenerated’, false);
  this.set(‘controller.showProgress’, false);
}

 

As we can see in the method, it first initialises the super constructor and then we are writing the logic for the reset state. This will reset the badgeGenerated and showProgress variable in the controller to mentioned values.

  1. Getting the generated badge link from the last promise resolved to set the value of the variable in the controller action.
sendBadge(badgeData) {

        this.set(‘badgeGenerated’, true);

  },

 

This will set the value of the variable to the received object from backend.

  1. Showing the content in frontend based on the values of the variable. When we initially visit the route it is set to as false in the setupController() hook and is changed later on through some promise resolvement in an action.
{{#if badgeGenerated}}

 . . .


{{/if}}

 

This content will be only be shown in the present state and when the user revisits the route the state will be resetted.

Pull Request for the respective issue – https://github.com/fossasia/badgeyay/pull/1313

Resources

Continue Reading

Cloud Function For Sending Mail On Badge Generation in Badgeyay

The task of badgeyay is to generate badges for events and if the user has provided a large data set, then the system will take time to generate badges and we cannot hold the user on the same page for that time. So instead of showing the user the link of the generated badge on the form, what we can do is that we can send a mail to user with the link of generated badge. However listening for the completion of generated badge from the cloud function is not possible, as cloud functions are based on serverless architecture. This can be done using the listeners for the realtime database events.

Generated badge from the backend will be uploaded to the Firebase Storage, but applying a listener for storage events, will not give us the information of the sender and some other metadata. So after uploading the link on the database, we can use the public link generated and can push a dict to the realtime database with the necessary user information for sending mail.

Procedure

  1. Fetching the necessary information to be pushed on the Firebase realtime database.
def send_badge_mail(badgeId, userId, badgeLink):
  ref = firebase_db.reference(‘badgeMails’)
  print(‘Pushing badge generation mail to : ‘, badgeId)
  ref.child(userId).child(datetime.datetime.utcnow().isoformat().replace(‘-‘, ‘_’).replace(‘:’, ‘U’).replace(‘.’, ‘D’)).set({
      ‘badgeId’: badgeId,
      ‘badgeLink’: badgeLink
  })
  print(‘Pushed badge generation mail to : ‘, badgeId)

 

Payload consists of the downloadable link of the badge and the root node is the userID. So whenever any node gets created in this format them the cloud function will be called.

  1. Listening for the database changes at the state of node.
exports.sendBadgeMail = functions.database.ref(‘/badgeMails/{userId}/{date}’)
.onCreate((snapshot, context) => {
  const payload = snapshot.val();
  const uid = context.params.userId;
  return admin.auth().getUser(uid)
    .then(record => {
      return sendBadgeGenMail(uid, record.email, record.displayName, payload[‘badgeId’], payload[‘badgeLink’]);
    })
    .catch(() => { return -1 });
});

 

For the realtime database listener, it should listen to node in the form of badgeMails/<user_id>/<date> as whenever a node of such form will be created in the database the function will get triggered and not for any other data insertions in db. This will save the quota for the cloud function execution.

  1. Sending mail to the user from the payload
function sendBadgeGenMail(uid, email, displayName, badgeId, badgeLink) {
const mailOptions = {
  from: `${APP_NAME}<[email protected]>`,
  to: email,
};

mailOptions.subject = `Badge Generated ${badgeId}`;
mailOptions.html = `<p> Hello ${displayName || }! Your badge is generated successfully, please visit the <a href=${badgeLink}>link</a> to download badge</p>`;
return mailTransport.sendMail(mailOptions).then(() => {
  writeMailData(uid, “success”, 3);
  return console.log(‘Badge mail sent to: ‘, email)
}).catch((err) => {
  console.error(err.message);
  return -1;
});
}

 

This will send the mail to the user with the generated link.

Pull Request for the above feature at link : Link

Outcome:

 

Resources

Continue Reading

Showing Mail Statistics Using Firebase In Badgeyay

In this blog post I will show how we implemented mail statistics on the admin dashboard of Badgeyay. The problem is that we cannot send a request to an external API from firebase cloud functions in free plan and secondly we have to them bypass the call to realtime database and design a logic to sort out the details from the realtime database using python admin sdk. So, our approach in solving this is that we will use the realtime database with the cloud functions to store the data and using the firebase admin sdk will sort the entries and using binary search will count the statistics efficiently..

Procedure

  1. Configuring the database URL in admin SDK
firebase_admin.initialize_app(cred, {
      ‘storageBucket’: ‘badgeyay-195bf.appspot.com’,
      ‘databaseURL’: ‘https://badgeyay-195bf.firebaseio.com’
  })

 

  1. Write the mail data in realtime database using cloud function on promise resolvement
function writeMailData(uid, state, reason) {
if (state === ‘success’) {
  db.ref(‘mails’)
    .push()
    .set({

      …
    }, err => {
      …

    });
}
}

 

  1. Fetching the mail data from the realtime database in python admin sdk
def get_admin_stat():
  mail_ref = firebasedb.reference(‘mails’)
  mail_resp = mail_ref.get()

  …
  curr_date = datetime.datetime.utcnow()
  prev_month_date = curr_date – relativedelta(months=1)
  last_three_days_date = curr_date – relativedelta(days=3)
  last_seven_days_date = curr_date – relativedelta(days=7)
  last_day_date = curr_date – relativedelta(days=1)
  …

 

This will fetch the current date and them generates the relative dates in past. Then it filters out the data using a for loop and a comparison statement with the date to get the count of the mails. Those count is then passed into a dict and sent through schema.

  1. Fetching the mail stats in admin index panel
mails    : await this.get(‘store’).queryRecord(‘admin-stat-mail’, {})

 

  1. Showing mails on the index page

class=“six wide column”>
          div class=“ui fluid center aligned segment”>
              h3 class=“ui left aligned header”>
                  Messages
              h3>
              div class=“ui divider”>div>
              h3 class=“ui header”>
                  No. of Sent Mails
              h3>
              table class=“ui celled table”>
                  tbody>
                      tr>
                          td> In last 24 hourstd>
                          td class=“right aligned”>
                              {{model.mails.lastDayCount}}
                          td>
                      tr>
                      tr>
                          td>In last 3 daystd>
                          td class=“right aligned”>
                              {{model.mails.lastThreeDays}}
                          td>
                      tr>
                      tr>
                          td> In last 7 days td>
                          td class=“right aligned”>
                              {{model.mails.lastSevenDays}}
                          td>
                      tr>
                      tr>
                          td> In last 1 month td>
                          td class=“right aligned”>
                              {{model.mails.lastMonth}}
                          td>
                      tr>
                  tbody>
              table>
          div>
      

 

PR to the issue: https://github.com/fossasia/badgeyay/pull/1163

Benefits of the approach

This approach is best in the case, when we only are using a subset of services firebase offers and do not want to pay for the services that are not required by the system.

Resources

Continue Reading

Adding Marketer and Sales Admin Events Relationship with User on Open Event Server

In this blog, we will talk about how to add API for adding and displaying events in with a user acts as a Marketer and/or Sales Admin on Open Event Server. The focus is on Model Updation and Schema updation of User.

Model Updation

For the Marketer and Sales Admin events, we’ll update User model as follows

Now, let’s try to understand these relationships.

In this feature, we are providing user to act as a marketer and sales admin for a event.

  1. Both the relationships will return the events in which the user is acting as a Marketer and/or Sales Admin.
  2. There are two custom system roles in model CustomSysRole which are Marketer and Sales Admin. A user can act as these custom system roles with respect to an event.
  3. In this relationship, we will return those events from UserSystemRole model in which a user is acting as Marketer Custom System Role and Sales Admin Custom System Role.
  4. We make use of Event and join UserSystemRole and CustomSysRole where we use that user where UserSystemRole.user_id == User.id , CustomSysRole.id == UserSystemRole.role_id, CustomSysRole.name == “Sales Admin” and then we return events in which Event.id == UserSystemRole.event_id
  5. Similarly, for Marketer events we make use of Event and join UserSystemRole and CustomSysRole where we use that user where UserSystemRole.user_id == User.id , CustomSysRole.id == UserSystemRole.role_id, CustomSysRole.name == “Marketer” and then we return events in which Event.id == UserSystemRole.event_id

Schema Updation

For the Marketer and Sales Admin events, we’ll update UserSchema as follows

Now, let’s try to understand this Schema.

In this feature, we are providing user to act as a marketer and sales admin for a event.

  1. For displaying marketer_events relation self_view is displayed by API v1.user_marketer_events and collection of these events is displayed by API v1.event_list
  2. These APIs will return the Events as schema=”EventSchema”. Here, many=True tells us that this is One to many relationship with Events model.

So, we saw how an user can act as a marketer and/or sales admin for many events.

Resources

Continue Reading

Adding Custom System Roles in Open Event Server

In this blog, we will talk about how to add different custom system roles concerning a user on Open Event Server. The focus is on its model and Schema updation.

Model Updation

For the User Table, we’ll update our User Model as follows:

Now, let’s try to understand these hybrid properties.

In this feature, we are providing Admin the rights to see whether a user is acting as a Marketer and / or  Sales Admin of any of the event or not. Here, _is__system_role method is used to check whether an user plays a system role like Marketer, Sales Admin or not. This is done by querying the record from UserSystemRole model. If the record is present then the returned value is True otherwise false.

Schema Updation

For the User Model, we’ll update our Schema as follows:

Now, let’s try to understand this Schema.

Since all the properties will return either True or false so these all properties are set to Boolean in Schema.Here dump_only means, we will return this property in the Schema.

So, we saw how User Model and Schema is updated to show custom system roles concerning a user on Open Event Server.

Resources

Continue Reading
Close Menu
%d bloggers like this: