Adding report skills feature in SUSI iOS

SUSI.AI is having a various type of Skills that improving the user experience. Skills are powering the SUSI.AI personal chat assistant. SUSI skills have a nice feedback system. We have three different feedback way for SUSI skills, 5-star rating system, posting feedback, and reporting skills.

5-Star Rating – rate skills from 1 (lowest) to 5 (highest) star

Posting Feedback – user can post feedback about particular skill

Report Skill – user can report skill if he/she found it inappropriate

In this post, we will see how reporting skills feature work in SUSI iOS and how it is implemented. You can learn about 5-star rating here and posting feedback feature here.

Adding report skill button –

let reportSkillButton: UIButton = {
        let button = UIButton(type: .system)
        button.contentHorizontalAlignment = .left
        button.setTitle("Report Skill", for: .normal)
        button.setTitleColor(UIColor.iOSGray(), for: .normal)
        button.titleLabel?.font = UIFont.systemFont(ofSize: 16)
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()

In above, we have set translatesAutoresizingMaskIntoConstraints property for false. By default, the property is set to true for any view you programmatically create. If you add views in Interface Builder, the system automatically sets this property to false. If this property’s value is true, the system creates a set of constraints that duplicate the behavior specified by the view’s autoresizing mask.

Setting up report skill button –

We are setting constraints programmatically as we created button programmatically and set translatesAutoresizingMaskIntoConstraints to false. Also, setting a target to the button.

if let delegate = UIApplication.shared.delegate as? AppDelegate, let _ = delegate.currentUser {
            view.addSubview(reportSkillButton)
            reportSkillButton.widthAnchor.constraint(equalToConstant: 140).isActive = true
            reportSkillButton.heightAnchor.constraint(equalToConstant: 32).isActive = true
            reportSkillButton.leftAnchor.constraint(equalTo: contentType.leftAnchor).isActive = true
            reportSkillButton.topAnchor.constraint(equalTo: contentType.bottomAnchor, constant: 8).isActive = true

            reportSkillButton.addTarget(self, action: #selector(reportSkillAction), for: .touchUpInside)
        }

In the above method, we can see that we are only showing button if user is logged-in. Only a logged-in user can report the skill. To check if user is logged in or not, we are using the AppDelegate shared instance where we save the logged-in user globally when the user signs in.

When user clicks the Report Skill button, a popup is open up with a text field for feedback message like below:

This is how UI look like!

When user clicks the Report action after typing feedback message, we are using the following endpoint:

https://api.susi.ai/cms/reportSkill.json

With the following parameters –

  • Model
  • Group
  • Skill
  • Language
  • Access Token
  • Feedback

Here is how we are handling the API call within our app –

func reportSkill(feedbackMessage: String) {
        if let delegate = UIApplication.shared.delegate as? AppDelegate, let user = delegate.currentUser {

            let params = [
                Client.SkillListing.model: skill?.model as AnyObject,
                Client.SkillListing.group: skill?.group as AnyObject,
                Client.SkillListing.skill: skill?.skillKeyName as AnyObject,
                Client.SkillListing.language: Locale.current.languageCode as AnyObject,
                Client.SkillListing.accessToken: user.accessToken as AnyObject,
                Client.SkillListing.feedback: feedbackMessage as AnyObject
            ]

            Client.sharedInstance.reportSkill(params) { (success, error) in
                DispatchQueue.main.async {
                    if success {
                        self.view.makeToast("Skill reported successfully")
                    } else if let error = error {
                        self.view.makeToast(error)
                    }
                }
            }
        }
    }

On successfully reported skill, we show a toast with ‘Skill reported successfully’ message and if there is error reporting the skills, we present the toast with error as a message.

Resources –

  1. SUSI Skills: https://skills.susi.ai/
  2. Apple’s documentations on translatesAutoresizingMaskIntoConstraints
  3. Allowing user to submit ratings for skills in SUSI iOS
  4. Displaying Skills Feedback on SUSI iOS
Continue ReadingAdding report skills feature in SUSI iOS

UNESCO Hackathon Vietnam 2018 Wrap Up

204 participants gathered at the UNESCO Hackathon over the weekend of October 13 – 14 in Ho Chi Minh City to develop digital applications that tackle climate change and sustainable development challenges in Vietnam and the Mekong region. The event was a great success thanks to support from the Government of Malaysia, UNESCO YouthMobile Initiative and Officience.

24 project teams were formed during the Hackathon. With the dedication of team members and mentorship from facilitators, all teams managed to complete their hacks. It was challenging for the judges to make their final decision as all proposed solutions appeared to be innovative and applicable. The three winning teams were Klima Kage, Climap & Bird’s Eye View. Additionally, the judges also selected three runner-ups ThreeWolves, DBTFC & GreenELF, whose projects directly address the issues related to climate change in the country.

Winning Projects: Klima Kage, Bird’s Eye View and Climap

Klima Kage is a website application which consists of two parts: main page and community page. The main page contains data from open source database and tutorials extracted from the UNESCO handbook for journalists on climate change and sustainable development in Asia Pacific, which directly assist journalists in selecting data to monitor.

On the community side, there is a forum for discussion and a news section where relevant articles will be published. The team decided to give priority to female journalists (ration 6:4) in reviewing and releasing news articles on this page, as a way to raise public awareness on gender dimension. SUSI.AI was also integrated into the web app to record users’ behaviour and offer suggestions for their next visits.

Klima Kage (Sieben) Team

Bird’s Eye View is a mobile game built in unity with an objective to educate people about the impacts of climate change on all kind of creatures under the sea. In each level of the game, players are brought into a different coastal city in Vietnam, where they learn about the real problems which are happening to the regional aquatic animals.

The game was designed to target on younger age groups, who generally have less awareness on environmental issues, yet are expected to create big differences in the community once they grow up. In the future, the development team hopes to expand the application, to include more terrains and other kinds of animals in order to provide players with a more overall image of the situation in Vietnam and the region.

Bird’s Eye View app presented by ‘Why triple teas?’ Team

Climap is the last project selected as a winner of the hackathon. Proposed and developed by a team of young developers from Ho Chi Minh City, Climap acts as an EXIF based Image Sharing System that enables users to to collect and instantly share data/images of places before, during and after disasters happen (due to the impacts of climate change). 

By using Climap, users can view all relevant data from the uploaded photos including: its location, weather index, and the occurred events/incidents, etc. They can write tags for each picture to label what was happening at that moment so the data can be further analyzed by local authorities or responsible institutions.

Climap by BIT Team

Talks and Workshops

Alongside with the hacking competition, tech talks and workshops were hosted to support the participants and provide them crucial knowledge of the topics.

Panel Discussion: A Green Planet for All

At the beginning of the hackathon, Hong Phuc Dang (FOSSASIA Founder) led a panel discussing featuring Misako Ito (UNESCO Advisor for Information and Communication), Pham Lan Phuong (Author Khai Don) and Tran Le Thu Giang (Youtuber GiangOi) to uncover the concepts of climate change and sustainable development under the own perspectives. The conversation offered the audience specific answers on how they can practice sustainable development in every aspects of their daily lives.

Panel Discussion: A Green Planet for All

Workshops: Working with Git/Github and Doing Electronics Experiments with PSLab

PadMal M, FOSSASIA/PSLab Lead Developer delivered two workshops, when he showed participants how contributors can effectively work together on an open source project using Github and what electronics experiments can be performed using PSLab (Pocket Science Lab – a hardware device by FOSSASIA). Together with FOSSASIA robotics expert, Marco Gutiérrez, the two successfully developed and integrated a robot into the PSLab device, which amazed participants and audience alike.

Learn how to perform scientific experiments using PSLab

Robotics integration on PSLab device by Marco Gutiérrez and Padmal M

Experience the world’ first portable CO2 Laser Cutter by LionsForge

Kee Wee Teng, Founder at LionsForge is a maker at heart, he always felt that the laser cutter, being such a useful tool, should be more accessible. He believed this type of machine should be safer so that everyone can use it for their own needs. After two years working on several designs and samples, Kee Wee and his team finally introduced to the world the CraftLaser laser cutter, a portable yet powerful machine that can be safely use at home, in the schools or even in public places.

Kee Wee Teng, Founder/CEO at LionsForge explained the CraftLaser’s operation procedures,

then shared how he successfully got the project fully funded from Indiegogo in just a week.

Video Footage of UNESCO Hackathon Vietnam 2018

Links

Hackathon Photos: https://photos.app.goo.gl/tJRN2b3mayNy6FcE6

FOSSASIA on Twitter: https://twitter.com/fossasia

FOSSASIA Videos: https://www.youtube.com/fossasiaorg

FOSSASIA Calendar: https://calendar.fossasia.org

Posts on the Web

UNESCO Bangkok: UNESCO Hackathon sets its sights on climate change solutions in Asia-Pacific

LionsForge Singapore: UNESCO Hackathon in Ho Chi Minh, Vietnam

Vietnamese-German University News: VGU students won first prize at UNESCO Hackathon Vietnam  

Share your write-ups about UNESCO Hackathon by making a tweet using hashtag #UNESCOHackathon #YouthMobile @fossasia @YouthMobile_ 

Continue ReadingUNESCO Hackathon Vietnam 2018 Wrap Up

Jugaad Fest Hyderabad on 30th September 2018 – Hack, Fix, Trick It, or Simply Make Open Tech Work for You

Jugaadfest is a gathering of developers and FOSS contributors who get together to learn, share, and hack on Open Source projects for one day with the goal of making things work. 30+ mentors and successful FOSSASIA Google Summer of Code students from India and Europe will guide you during the day. At Jugaadfest you can:

  • join various hands-on activities, e.g. set up FOSSASIA apps, install conversational voice assistants, do experiments with PSLab
  • learn from developers how to find innovative fixes or simple work-arounds to get FOSSASIA prototypes working
  • learn how to build up an awesome developer profile that improves your job chances by collaborating in the FOSS community
  • improve your coding and tech skills while contributing to open source software and hardware projects
  • enjoy presentations conducted by veteran developers

Jugaadfest is a hands-on event. Please bring your laptop.

We kick-off in the morning with short introductions by developers of FOSS projects. After that participants have the chance to join teams at different tables and start learning and collaborating on Open Tech projects.

At the lunch break participants mingle and discuss what they have learned before they continue browsing  projects. At the end of the event day we ask developers to share their learnings in short lightning talks.

Certificates: Participants of Jugaadfest receive a certificate of participation. (Prerequisite: Full-day participation)

Contributors Discount Tickets

You are a contributor to FOSSASIA project or other Open Source communities? Apply for a discount ticket. There is a limited number of contributor tickets for 90, 80, and 70% discounts available. Please fill in the form here: https://jugaadfest.com/community-ticket

AGENDA

Jugaadfest Hyderabad

  • Location: BVRIT HYDERABAD College of Engineering for Women
  • Date: Sunday, September 30, 2018
  • 8:30 Registration and Breakfast Snacks
  • 9:30 Introduction and Opening Sessions
  • 9:45 How to Participate in FOSSASIA Coding Programs, Google Summer of Code and Codeheat
  • 10:00 Project Presentations: SUSI.AI, Open Event Solutions, Pocket Science Lab, Meilix, Badgeyay
  • 11:00 Developers Join Project Tables
  • 12:30 Lunch Break
  • 13:30 Developers Learn and Participate at Project Tables
  • 16:00 How to Collaborate in International Open Source Projects and Global Enterprises Using Best Practices
  • 16:15 Lightning Talks
  • 16:30 What’s Next?
  • 17:00 End of Event

Social Event (Ticket Required)

  • Date: Sunday, September 30, 2018
  • 19:30 Dinner in Hyderabad City

Jugaadfest Extended (Contributors – Invitation Only)

  • Location: Co-Working Hyderabad
  • Developer Workshops: Monday October 1 – Wednesday, October 3, 2018
  • Brunch: Thursday, October 4, 2018

LINKS

Continue ReadingJugaad Fest Hyderabad on 30th September 2018 – Hack, Fix, Trick It, or Simply Make Open Tech Work for You

UNESCO Hackathon in Ho Chi Minh City, Vietnam

Join UNESCO Hackathon in Ho Chi Minh City on Oct 13 -14, 2018 to learn about climate change and environmental challenges in Vietnam, meet regional sustainable development experts and listen to their successful startup stories by doing sustainable and green businesses.

There is no restriction of age or backgrounds of participants. Students, NGOs reps, journalists, bloggers, developers and all open source contributors are invited to join! The hackathon is open for all and awesome prizes are waiting for you!

Each winner of the three top teams will receive these prizes.

The objective of the hackathon is to propose innovative solutions that help journalists to monitor and report on climate change and sustainable development issues in Asia and the Pacific.

The participants will be introduced to UNESCO’s Guidebook for Journalists Reporting on Climate Change and Sustainable Development in Asia and the Pacific which includes information and knowledge on climate science, related international and regional treaties and policy frameworks including the 2030 Agenda for Sustainable development, and tips for journalists for finding and telling stories.

Time and Location

Time: Saturday October 13 – Sunday October 14, 2018
Location: Officience Vietnam, 16A Le Hong Phong, Ward 12, District 10, Ho Chi Minh City

Why should I participate?

  • Learn how to create a chatbot within an hour with SUSI.AI
  • Carry out experiment with electronic devices PSlab.io
  • Update yourselves with knowledge of technology and sustainable development in Vietnam
  • Meet special guest speakers from the UNESCO, Embassy of Sweden and many more.
  • Improve your language skills, presentation skills and build up your leadership abilities
  • Receive certificates from UNESCO, T-shirts, swags, and special prizes from the sponsors

How do I know if I am qualified to join?

The hackathon is open for everyone, especially for those:

  • Curious and willing to learn new things
  • Interested in technology and sustainable development
  • Like to make new friends and expand their networks
  • Able to communicate in English
  • No prior coding skill is required

How do I sign up?

  1. Get your ticket to the Event on eventyay.com
  2. Sign up on Devpost as you will need to submit your final hack there.
  3. Join the Gitter channel at https://gitter.im/fossasia/hackathon (requires login with Github).
  4. Find team members and form a team with at least 2 members and maximum 4 contributors. You are also welcome to sign up and then wait until the Presentation of Ideas on Saturday before deciding to join a team, however we’d encourage you to form/join a team in advance if you already have an idea that you’d like to work on.
  5. Join the event at the Officience Vietnam on Saturday, Oct 13 at the opening at 8.30am until 9.00pm and on Sunday, Oct 14 from 8.00am until 5.00pm.

Visit the website at unesco.sciencehack.asia and stay connected, join the event on Facebook and follow FOSSASIA on Twitter.

Prizes

All participants will receive a gift bag (Tshirt, sticker, wristband and lanyard) and a certificate from UNESCO for participating in the hacking.

Each winner of the three top teams will be awarded special gift package including:

  • A Pocket Science Lab – hardware device by FOSSASIA
  • Special Developer Helmet by FOSSASIA
  • Winner Medal
  • Team Building Buffet Dinner Voucher
  • Team Hack-Away Mekong Delta Tour (floating Market, hackerspace, hotel)
  • Tiki Techie Gift Voucher
  • 6-month coworking space membership

Links

UNESCO Hackathon: https://unesco.sciencehack.asia

Tickets: https://eventyay.com/e/dbd7567d

Project Signup: https://unesco-hackathon.devpost.com

Communication Channel: https://gitter.im/fossasia/hackathon

Facebook: https://www.facebook.com/events/1713085622073681

FOSSASIA: https://twitter.com/fossasia

Continue ReadingUNESCO Hackathon in Ho Chi Minh City, Vietnam

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 ReadingAdding different metrics sections to the start page

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 ReadingUsing Contextual Action Bar as Selection Toolbar

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 ReadingWorking with Route Hooks in Badgeyay

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}<noreply@firebase.com>`,
  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 ReadingCloud Function For Sending Mail On Badge Generation in Badgeyay

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 ReadingShowing Mail Statistics Using Firebase In Badgeyay

Create Session in Open Event Android Organizer Application

Open Event Android Organizer Application offered variety of features to Organizers from all over the world to help them host their Events globally but it didn’t had the functionality to create Sessions in the app itself and associate it to Tracks. This feature addition was crucial since it enables Organizer to create Sessions which every common person enquires about before attending and event. In this Blog Post we will see how we added this functionality in the app.

Open Event Android Organizer Application is a client for Open Event Server which provides the REST API.

Problem

There can be various Sessions associated with Tracks for an Event. Open Event API had the endpoint to implement Creating Session but the Orga app didn’t, so we worked on creating a Session in the app.

The UI for creating a Session is shown above. User can fill in the necessary details and click on the green Floating Action Button to create a Session.

How to implement functionality?

We will follow MVP Architecture and use Retrofit 2.x, RxJava, Dagger, Jackson, Data Binding and other industry standard libraries to implement this functionality.

Firstly, let’s create Session model class specifying the attributes and relationships to set up in database using RazizLabs DbFlow library. The POJO will be serialized into JSON by Jackson library to be passed on as a part of RequestBody to server.

Now we will create SessionApi class that will contain the request details to be passed to Retrofit. @POST denotes a POST request and @Body denotes the requestBody of the request which is a Session object.

public interface SessionApi {
@POST(“sessions”)
Observable<Session> postSession(@Body Session session);
}

This is the CreateSessionFragment class that contains the code binding model to the view. The Fragment class implements the CreateSessionView class overriding the method declarations present there. The @Inject annotation of Dagger is used to load singleton presenter instance lazily to improve app’s performance.

Event-Id and Track-Id’s are retrieved from Bundle from Fragment Transaction. These are then passed on to presenter when Create Session button is pressed. There are other methods to show binding progressbar, snackbar and other UI components to show progress of the background request to server and database.

public class CreateSessionFragment extends BaseFragment<CreateSessionPresenter> implements CreateSessionView {

@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup

container, @Nullable Bundle savedInstanceState) {
binding = DataBindingUtil.inflate(inflater,

R.layout.session_create_layout, container, false);
validator = new Validator(binding.form);

binding.sessionCreate.setOnClickListener(view -> {
if (validator.validate()) {
getPresenter().createSession(trackId, eventId);
}
});

return binding.getRoot();
}

@Override
public void onStart() {
super.onStart();
getPresenter().attach(this);
binding.setSession(getPresenter().getSession());
}
}

In the Presenter createSession method is called when create button is pressed in UI. The method attaches track-id and event-id to Session object. This is necessary for Relationship constraints on Session Model. Then after binding all the data to Session object, we pass it on to SessionRepository. The success response is provided to user by passing success response in getView().onSuccess() method.

public class CreateSessionPresenter extends

AbstractBasePresenter<CreateSessionView> {

public Session getSession() {
return session;
}

public void createSession(long trackId, long eventId) {

Track track = new Track();
Event event = new Event();

track.setId(trackId);
event.setId(eventId);
session.setTrack(track);
session.setEvent(event);

sessionRepository
.createSession(session)
.compose(dispose(getDisposable()))
.compose(progressiveErroneous(getView()))
.subscribe(createdSession ->

getView().onSuccess(“Session Created”), Logger::logError);
}
}

The SessionRepository uses RxJava to make asynchronous Retrofit Call to Server. We throw a Network Error to user if the device does not have Internet Connectivity.

The session object accepted as a parameter in createSession method is passed on to sessionApi. It will return Observable<Session> Response which we will process in doOnNext() method. Then the Session object along with required foreign key relationships with Track and Event is saved in database for offline use.

@Override
public Observable<Session> createSession(Session session) {
if (!repository.isConnected()) {
return Observable.error(new Throwable(Constants.NO_NETWORK));
}return sessionApi
.postSession(session)
.doOnNext(created -> {
created.setTrack(session.getTrack());
created.setEvent(session.getEvent());
repository
.save(Session.class, created)
.subscribe();
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}

The above code snippets are from Open Event Orga Application. For exploring the entire codebase please refer here. For details about the REST API used by the app please visit here.

References

  1. Official RxJava Project on Github by ReactiveX https://github.com/ReactiveX/RxJava.
  2. Official Retrofit Project on Github by Square Inc https://github.com/square/retrofit.
  3. Official Open Event Organizer App on Github by FOSSASIA https://github.com/fossasia/open-event-orga-app.
  4. Documentation for REST API of Open Event Server on Heroku by FOSSASIA https://open-event-api-dev.herokuapp.com/.
Continue ReadingCreate Session in Open Event Android Organizer Application