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}  …

Continue ReadingAdding different metrics sections to the start page

Showing skills based on different metrics in SUSI Android App using Nested RecyclerViews

SUSI.AI Android app had an existing skills listing page, which displayed skills under different categories. As a result, there were a number of API calls at almost the same time, which led to slowing down of the app. Thus, the UI of the Skill Listing page has been changed so as to reduce the number of API calls and also to make this page more useful to the user. API Information For getting a list of SUSI skills based on various metrics, the endpoint used is /cms/getSkillMetricsData.json This will give you top ten skills for each metric. Some of the metrics include skill ratings, feedback count, etc. Sample response for top skills based on rating : "rating": [ { "model": "general", "group": "Knowledge", "language": "en", "developer_privacy_policy": null, "descriptions": "A skill to tell atomic mass and elements of periodic table", "image": "images/atomic.png", "author": "Chetan Kaushik", "author_url": "https://github.com/dynamitechetan", "author_email": null, "skill_name": "Atomic", "protected": false, "reviewed": false, "editable": true, "staffPick": false, "terms_of_use": null, "dynamic_content": true, "examples": ["search for atomic mass of radium"], "skill_rating": { "bookmark_count": 0, "stars": { "one_star": 0, "four_star": 3, "five_star": 8, "total_star": 11, "three_star": 0, "avg_star": 4.73, "two_star": 0 }, "feedback_count": 3 }, "usage_count": 0, "skill_tag": "atomic", "supported_languages": [{ "name": "atomic", "language": "en" }], "creationTime": "2018-07-25T15:12:25Z", "lastAccessTime": "2018-07-30T18:50:41Z", "lastModifiedTime": "2018-07-25T15:12:25Z" }, . . ]   Note : The above response shows only one of the ten objects. There will be ten such skill metadata objects inside the “rating” array. It contains all the details about skills. Implementation in SUSI.AI Android App Skill Listing UI of SUSI SKill CMS Skill Listing UI of SUSI Android App The UI of skills listing in SUSI Android app displays skills for each metric in a horizontal recyclerview, nested in a vertical recyclerview. Thus, for implementing horizontal recyclerview inside vertical recyclerview, you need two viewholders and two adapters (one each for a recyclerview). Let us go through the implementation. Make a query object consisting of the model and language query parameters that shall be passed in the request to the server. val queryObject = SkillMetricsDataQuery("general", PrefManager.getString(Constant.LANGUAGE,Constant.DEFAULT))   Fetch the skills based on metrics, by calling fetch in SkillListModel which then makes an API call to fetch groups. skillListingModel.fetchSkillsMetrics(queryObject, this)   When the API call is successful, the below mentioned method is called which in turn parses the received response and updates the adapter to display the skills based on different metrics. override fun onSkillMetricsFetchSuccess(response: Response<ListSkillMetricsResponse>) { skillListingView?.visibilityProgressBar(false) if (response.isSuccessful && response.body() != null) { Timber.d("METRICS FETCHED") metricsData = response.body().metrics if (metricsData != null) { metrics.metricsList.clear() metrics.metricsGroupTitles.clear() if (metricsData?.rating != null) { if (metricsData?.rating?.size as Int > 0) { metrics.metricsGroupTitles.add(utilModel.getString(R.string.metric_rating)) metrics.metricsList.add(metricsData?.rating) skillListingView?.updateAdapter(metrics) } } if (metricsData?.usage != null) { if (metricsData?.usage?.size as Int > 0) { metrics.metricsGroupTitles.add(utilModel.getString(R.string.metric_usage)) metrics.metricsList.add(metricsData?.usage) skillListingView?.updateAdapter(metrics) } } if (metricsData?.newest != null) { val size = metricsData?.newest?.size if (size is Int) { if (size > 0) { metrics.metricsGroupTitles.add(utilModel.getString(R.string.metric_newest)) metrics.metricsList.add(metricsData?.newest) skillListingView?.updateAdapter(metrics) } } } if (metricsData?.latest != null) { if (metricsData?.latest?.size as Int > 0) { metrics.metricsGroupTitles.add(utilModel.getString(R.string.metric_latest)) metrics.metricsList.add(metricsData?.latest) skillListingView?.updateAdapter(metrics) } } if (metricsData?.feedback !=…

Continue ReadingShowing skills based on different metrics in SUSI Android App using Nested RecyclerViews

Showing top metrics from skill groups

SUSI.AI shows top metrics on the home page. They include highest rated skills, most used skills, latest skills and skills with most feedbacks etc. Now the idea is to include top skills from a particular category also. For example “SUSI, what are your top games”? So how to fetch the required metrics in a generalized way. Updating the skill metrics data API Add an API parameter in SkillMetricsDataService.java to specify the names of groups to fetch the required metrics. It accepts a semicolon (;) separated list of group names. If no group is passed then by default it shows the top games. String metrics_list = call.get("metrics", "Games, Trivia and Accessories"); String[] metrics_names = metrics_list.split(";"); Split the metrics parameter by semicolon and store in an array. This array contains all the groups of which top skills are to be displayed on the CMS home page. Loop over the array, group by group and filter out the skills that don’t belong to the current metrics group. Sort the filtered skills in decreasing order of the overall rating. But this sorting does not simply arrange the skills in decreasing order of their overall rating. Actually, it divides the skills into 2 halves. The first half contains the skills that have been rated by at least 10 users in decreasing order of the overall ratings. And the second half contains the rest of the skills in decreasing order of their overall rating. for (String metric_name : metrics_names) { try { metric_name = metric_name.trim(); List<JSONObject> groupJsonValues = new ArrayList<JSONObject>(); for (int i = 0; i < jsonArray.length(); i++) { if (jsonArray.getJSONObject(i).get("group").toString().equals(metric_name)) { groupJsonValues.add(jsonArray.getJSONObject(i)); } } // Get skills based on ratings of a particular group SusiSkill.sortByAvgStar(groupJsonValues, false); JSONArray topGroup = new JSONArray(); topGroup = getSlicedArray(groupJsonValues, count); skillMetrics.put(metric_name, topGroup); } catch (Exception e) { e.printStackTrace(); } } So if top games and news skills are to be shown on the CMS then the API endpoint looks like : https://api.susi.ai/cms/getSkillMetricsData.json?metrics=Games, Trivia and Accessories; News And the response is like : { accepted: true, model: "general", group: "All", language: "en", metrics: { +newest: [...], +latest: [...], +rating: [...], +usage: [...], +feedback: [...], +staffPicks: [...], +Games, Trivia and Accessories: [...], +News: [...] }, message: "Success: Fetched skill data based on metrics" } Resources NA

Continue ReadingShowing top metrics from skill groups

Adding a Horizontally scrollable component to display Skills based on metrics

In this blog post, I will discuss about the implementation of a horizontally scrollable component to display skill based on metrics. The purpose of the implementation is to show top skills based on metrics related to usage, ratings, etc in SUSI.AI Skills CMS. Implementational details We call this component SkillCardScrollList which takes in a list of cards to be displayed along with some other properties and returns an UI, as shown in the above GIF. The parameters that the component takes are: scrollId: It is a required field of the type String. It is the id name of the horizontally scrollable div. skills: It contains an array of cards that are to displayed inside the container. languageValue: It represents the language of the skills that are shown. skillUrl: It contains the URL that the app would be taken to, on clicking individual Skill Card. modelValue: It contains the model that the skill belongs to. Here is a sample of how it is used in the BrowseSkill component, for showing the Top Rated Skills in a SkillCardsScrollList - <SkillCardScrollList scrollId="topRated" skills={this.state.topRatedSkills} modalValue={this.state.modalValue} languageValue={this.state.languageValue} skillUrl={this.state.skillUrl} />   The reason behind passing an unique scrollId as a prop to the component is that, there was a need to trigger the scroll event of the scrollable div n the click of left and right Floating Action Buttons (FABs) as shown in the UI. And, on multiple imports of this component, there would have been inconsistent scroll behaviour seen, had it not been unique. Following in the code block of the component, which will be explained in details, that deals with the main implementation - . . . . scrollLeft = () => { let parentEle = document.getElementById(this.props.scrollId); let scrollValue = $(parentEle).scrollLeft() - 200; $(parentEle) .stop() .animate({ scrollLeft: scrollValue }, 100); }; scrollRight = () => { // Similar function of scrollLeft }; loadSkillCards = () => { let cards = []; Object.keys(this.state.skills).forEach(el => { . /* Each skill object is passed and then pushed to the cards array*/ . ); }); // Set the cards array in the state this.setState({ cards, }); }; render() { return ( <div style={{ marginTop: '20px', marginBottom: '40px', textAlign: 'justify', fontSize: '0.1px', width: '100%', }} > <div> <div id={this.props.scrollId} className="scrolling-wrapper" style={styles.gridList} > <FloatingActionButton mini={true} backgroundColor={'#4285f4'} style={styles.leftFab} onClick={this.scrollLeft} > <NavigationChevronLeft /> </FloatingActionButton> {this.state.cards} <FloatingActionButton mini={true} backgroundColor={'#4285f4'} style={styles.rightFab} onClick={this.scrollRight} > <NavigationChevronRight /> </FloatingActionButton> </div> </div> </div> ); } }   The div with class scrolling-wrapper is actually scrolled on the click of the left and right FAB. For choosing the correct div to be scrolled, there was a necessary condition of an unique id as explained earlier, which has been set to the div. For making the component horizontally scrollable, specific CSS rules are added to the div. They are - gridList: { margin: '10px', textAlign: 'center', overflowX: 'scroll', overflowY: 'hidden', whiteSpace: 'nowrap', }, leftFab: { position: 'absolute', left: 260, marginTop: 75, }, rightFab: { position: 'absolute', right: 0, marginTop: 75, marginRight: 10, },   The CSS rules for the FABs…

Continue ReadingAdding a Horizontally scrollable component to display Skills based on metrics

Displaying skill rating for each skill on skill page of SUSI SKILL CMS

SUSI exhibits several skills which are managed by the SUSI Skill CMS, it essentially is a client which allows users to create/update skills conveniently since for each skill it is important to have the functionality of rating system so developers can get to know which skills are performing better than the rest and consequently improve them, thus a skill rating system which allows the users to give positive or negative feedback for each skill is implemented on the server. Fetching skill_rating from the server Fetch skill data for which ratings are to be displayed through ajax calls API Endpoint - /cms/getSkillMetadata.json?… Parse the received metadata object to get positive and negative ratings for that skill if(skillData.skill_rating) { let positive_rating = skillData.skill_rating.positive; let negative_rating = skillData.skill_rating.negative; } Sample API response { "skill_metadata": { "model": "general", "group": "Knowledge", "language": "en", "developer_privacy_policy": null, "descriptions": "Want to know about fossasia, just ask susi to tell that, Susi tells about the SUSI.AI creators", "image": "images/creator_info.png", "author": "madhav rathi", "author_url": "https://github.com/madhavrathi", "author_email": null, "skill_name": "Creator Info", "terms_of_use": null, "dynamic_content": false, "examples": [ "Who created you?", "what is fossasia?" ], "skill_rating": { "negative": "0", "positive": "0", "stars": { "one_star": 0, "four_star": 0, "five_star": 0, "total_star": 0, "three_star": 0, "avg_star": 0, "two_star": 0 }, "feedback_count": 0 }, "creationTime": "2018-03-17T16:38:29Z", "lastAccessTime": "2018-06-15T15:51:50Z", "lastModifiedTime": "2018-03-17T16:38:29Z" }, "accepted": true, "message": "Success: Fetched Skill's Metadata", "session": {"identity": { "type": "host", "name": "162.158.166.37_d80fb5c9", "anonymous": true }} } Set the react state of the component to store positive and negative rating. this.setState({ positive_rating, negative_rating }) Use react-icons to fetch like and dislike icon components from font-awesome. npm i -S react-icons Import the corresponding icons in the SkillPage component import { FaThumbsOUp, FaThumbsODown } from 'react-icons/lib/fa/' Display the rating count along with their icons <div className="rating"> <div className="positive"> <FaThumbsOUp /> {this.state.positive_rating} </div> <div className="negative"> <FaThumbsODown /> {this.state.negative_rating} </div> </div> Example References react-icons npm package Blog on icons as react components - https://medium.com/@david.gilbertson/icons-as-react-components-de3e33cb8792

Continue ReadingDisplaying skill rating for each skill on skill page of SUSI SKILL CMS