This is a step by step article on how to build a analytics tool for profiles, using . The emphasis was placed on achieving functionality quickly, therefore the end result does require further code refactoring and styling. The article is formed of two parts ( , ) and is based on the following repository. It is also hosted in this . Please note that both the repository and the website will evolve beyond these two articles. basic GitHub React Part 1 Part 2 GitHub website This articles continues from , where we built a basic page that is capable of taking a user input, using it to request data from GitHub’s API and then display the response on the web page once it has been received. Part 1 In this part we will add 3 different sections: basic information, list of most popular and starred repos and, finally, analyse most common languages of user’s own repos and generate keywords to starred repos. Lets begin by running which is necessary to adjust the date for one of the variables. Next we will need to create a new component called with the following code: npm install --save moment react-moment ProfileDetails.jsx import React from 'react';import Moment from 'react-moment'; const imgStye = {borderRadius: "50%",width: "250px",height: "250px"}; const ProfileDetails = (props) => {return (<div><div>{props.infoclean.avatar_url ?<img src={props.infoclean.avatar_url}alt="Profile"style={imgStye}/> : null }</div><div>{props.infoclean.name ? <div><p>Name:</p><p>{props.infoclean.name}</p></div> : null }</div><div>{props.infoclean.bio ? <div><p>Bio:</p><p>{props.infoclean.bio}</p></div> : null }</div><div>{props.infoclean.created_at ? <div><p>Joined:</p><p>{<Moment from={new Date()}>{props.infoclean.created_at}</Moment>}</p></div> : null }</div><div>{props.infoclean.blog ? <div><p>Blog:</p><p><a href={props.infoclean.blog.search("http") !== -1 ? props.infoclean.blog: "http://" + props.infoclean.blog } target="_blank">{props.infoclean.blog}</a></p></div> : null }</div><div>{props.infoclean.location ? <div><p>Location:</p><p>{props.infoclean.location}</p></div> : null }</div><div>{props.infoclean.company ? <div><p>Company:</p><p>{props.infoclean.company}</p></div> : null }</div><div>{props.infoclean.public_repos ? <div><p>Public Repos:</p><p>{props.infoclean.public_repos}</p></div> : null }</div><div>{props.infoclean.followers ? <div><p>Followers:</p><p>{props.infoclean.followers}</p></div> : null }</div><div>{props.infoclean.following ? <div><p>Following:</p><p>{props.infoclean.following}</p></div> : null }</div><div>{props.infoclean.html_url ? <div><p><a href={props.infoclean.html_url} target="_blank">View on GitHub</a></p></div> : null }</div><div>{props.infoclean.login ? <div>{ <img src={" "+props.infoclean.login} alt="Github chart" />}<br/><a href=" " target="_blank">Source for GitHub Chart API</a></div> : null }</div></div>) http://ghchart.rshah.org/ https://ghchart.rshah.org/ }; export default ProfileDetails; Similarly, , will need to be imported changed accordingly. App.jsx import React, { Component } from 'react';import axios from 'axios'; import Form from './components/Form.jsx';import ProfileDetails from './components/ProfileDetails.jsx'; class App extends Component {constructor() {super();this.state = {gitun: 'No username',infoclean : '',formData: {username: '',}, }this.handleUserFormSubmit = this.handleUserFormSubmit.bind(this);this.handleFormChange= this.handleFormChange.bind(this);} handleUserFormSubmit(event) {event.preventDefault();axios.get(' ).then(response => this.setState({gitun: response.data.login,infoclean: response.data,})).catch((err) => { console.log(err); });}; https://api.github.com/users/'+this.state.formData.username handleFormChange(event) {const obj = this.state.formData;obj[event.target.name] = event.target.value;this.setState(obj);}; render() {return (<div className="App"><header className="App-header"><h1 className="App-title">GitHub Analytics</h1></header><p className="App-intro">Watch this space...</p><hr></hr><FormformData={this.state.formData}handleUserFormSubmit={this.handleUserFormSubmit}handleFormChange={this.handleFormChange}/><hr></hr>Profile Details:<ProfileDetails infoclean={this.state.infoclean}/> </div>);}} export default App; These changes allow us to pull in together various data points of the profile. Such as Name, Bio, Location, number of repos, followers and some others. Overall, the section will look something like this. Now we will begin working on the list of own and starred repositories. To do this we first create a new component called : SortedList.jsx import React from 'react';import Moment from 'react-moment'; const SortedList = (props) => {if (props.repitems) {return (<ul>{props.repitems.map((repitem) =><li key={repitem.id}><div><div><a href={repitem.html_url} target="_blank">{repitem.name}</a> || Started <Moment from={new Date()}>{repitem.created_at}</Moment></div><div><i>{repitem.description}</i></div><div>Language: {repitem.language} || Watchers: {repitem.watchers_count} || Forks: {repitem.forks_count}</div></div></li>)}</ul>)} else { return null;}}; export default SortedList; We then update accordingly: App.jsx import React, { Component } from 'react';import axios from 'axios'; import Form from './components/Form.jsx';import SortedList from './components/SortedList.jsx';import ProfileDetails from './components/ProfileDetails.jsx'; class App extends Component {constructor() {super();this.state = {gitun: 'No username',infoclean : '',formData: {username: '',},repitems: null,staritems: null, }this.handleUserFormSubmit = this.handleUserFormSubmit.bind(this);this.handleFormChange= this.handleFormChange.bind(this);} handleUserFormSubmit(event) {event.preventDefault();axios.get(' ).then(response => this.setState({gitun: response.data.login,infoclean: response.data,})).catch((err) => { console.log(err); }); https://api.github.com/users/'+this.state.formData.username axios.get(' ).then(response => this.setState({repitems : response.data.filter(({fork}) => fork === false).sort((b, a) => (a.watchers_count + a.forks_count) - (b.watchers_count + b.forks_count)).slice(0,10)})).catch((err) => { console.log(err); }); https://api.github.com/users/'+this.state.formData.username+'/repos' axios.get(' ).then(response => this.setState({staritems : response.data.filter(({fork}) => fork === false).sort((b, a) => (a.watchers_count + a.forks_count) - (b.watchers_count + b.forks_count)).slice(0,10)})).catch((err) => { console.log(err); });}; https://api.github.com/users/'+this.state.formData.username+'/starred' handleFormChange(event) {const obj = this.state.formData;obj[event.target.name] = event.target.value;this.setState(obj);}; render() {return (<div className="App"><header className="App-header"><h1 className="App-title">GitHub Analytics</h1></header><p className="App-intro">Watch this space...</p><hr></hr><FormformData={this.state.formData}handleUserFormSubmit={this.handleUserFormSubmit}handleFormChange={this.handleFormChange}/><hr></hr>Profile Details:<ProfileDetails infoclean={this.state.infoclean}/><hr></hr>Own Repositories:<SortedList repitems={this.state.repitems}/><hr></hr>Starred Repositories:<SortedList repitems={this.state.staritems}/> </div>);}} export default App; The page should now be able to show items for each of the repositories lists: Finally, we can run some basic analysis on received information for repositaries. For example, for user’s own repositaries we will count the number of repositaries for each programming language. This would be an indication of the most preferred language for each user. Similarly, we will extract the description for starred repositaries and produce a list of keywords, indicating preferred topics. This topic modelling process will be done using . Latent Dirichlet Allocation Let’s begin by running , which will install the required package. In order to allow for building process to happen, we will need to move folder in to folder. As a result, 's new path becomes . This will also allow us to import into . npm install lda --save lda node_modules src lda src/lda lda App.jsx Next we will need to create a new component. LanguageList.jsx import React from 'react'; const LanguageList = (props) => {if (props.langslist) {return (<ul>{Object.entries(props.langslist).map(([key,value]) =><li key={key}>{key} - {value}</li>)}</ul>)} else { return null;}}; export default LanguageList; Finally, we can update as follows: App.jsx import React, { Component } from 'react';import axios from 'axios'; import Form from './components/Form.jsx';import SortedList from './components/SortedList.jsx';import ProfileDetails from './components/ProfileDetails.jsx';import LanguageList from './components/LanguageList.jsx'; import lda from './lda'; class App extends Component {constructor() {super();this.state = {gitun: 'No username',infoclean : '',info: '',formData: {username: '',},repitems: null,staritems: null,replanguagecount: {},keywords: null] }this.handleUserFormSubmit = this.handleUserFormSubmit.bind(this);this.handleFormChange= this.handleFormChange.bind(this);} handleUserFormSubmit(event) {event.preventDefault();axios.get(' ).then(response => this.setState({gitun: response.data.login,infoclean: response.data,info : JSON.stringify(response.data, undefined, 2)})).catch((err) => { console.log(err); }); https://api.github.com/users/'+this.state.formData.username axios.get(' ).then(response => { https://api.github.com/users/'+this.state.formData.username+'/repos' var itemsWithFalseForks = response.data.filter(item => item.fork === false) var sortedItems = itemsWithFalseForks.sort((b,a) => {if((a.watchers_count + a.forks_count) < (b.forks_count + b.watchers_count)){return -1}else if ((a.watchers_count + a.forks_count) > (b.forks_count + b.watchers_count)){return 1}else {return 0}}) let dictrlc = Object.assign({}, this.state.replanguagecount);for (var i = 0; i < itemsWithFalseForks.length; i++) {dictrlc[itemsWithFalseForks[i]['language']] = -~ dictrlc[itemsWithFalseForks[i]['language']]} this.setState({repitems: sortedItems.slice(0,10),replanguagecount: dictrlc,}) }).catch((err) => { console.log(err); }) axios.get(' ).then(response => { https://api.github.com/users/'+this.state.formData.username+'/starred' var itemsWithFalseForks = response.data.filter(item => item.fork === false) var sortedItems = itemsWithFalseForks.sort((b,a) => {if((a.watchers_count + a.forks_count) < (b.forks_count + b.watchers_count)){return -1}else if ((a.watchers_count + a.forks_count) > (b.forks_count + b.watchers_count)){return 1}else {return 0}}) var documents = []for (var i = 0; i < response.data.length; i++) {var descr = response.data[i]['description']if (descr != null) {var newtext = descr.match(/[^.!?]+[.!?]+/g)if (newtext != null) {documents = documents.concat(newtext)}}}var result = lda(documents, 3, 3);var keywords = new Set()for (var k = 0; k < 3; k++) {for (var j = 0; j < 3; j++) {keywords = keywords.add(result[k][j]['term']);}} this.setState({staritems: sortedItems.slice(0,10),keywords: Array.from(keywords).join(', ')}) }).catch((err) => { console.log(err); }) }; handleFormChange(event) {const obj = this.state.formData;obj[event.target.name] = event.target.value;this.setState(obj);}; render() {return (<div className="App"><header className="App-header"><h1 className="App-title">GitHub Analytics</h1></header><p className="App-intro">Watch this space...</p><hr></hr><FormformData={this.state.formData}handleUserFormSubmit={this.handleUserFormSubmit}handleFormChange={this.handleFormChange}/><hr></hr>Profile Details:<ProfileDetails infoclean={this.state.infoclean}/><hr></hr>Own Repositories:<SortedList repitems={this.state.repitems}/><hr></hr>Starred Repositories:<SortedList repitems={this.state.staritems}/><hr></hr>Own Repos Language Count:<LanguageList langslist={this.state.replanguagecount}/>Keywords: {this.state.keywords}</div>);}} To ensure GitHub Chart is also visible, change to the following: ProfileDetails.jsx import React from 'react';import Moment from 'react-moment'; const imgStye = {borderRadius: "50%",width: "250px",height: "250px"}; const ProfileDetails = (props) => {return (<div><div>{props.infoclean.avatar_url ?<img src={props.infoclean.avatar_url}alt="Profile"style={imgStye}/> : null }</div><div>{props.infoclean.name ? <div><p>Name:</p><p>{props.infoclean.name}</p></div> : null }</div><div>{props.infoclean.bio ? <div><p>Bio:</p><p>{props.infoclean.bio}</p></div> : null }</div><div>{props.infoclean.created_at ? <div><p>Joined:</p><p>{<Moment from={new Date()}>{props.infoclean.created_at}</Moment>}</p></div> : null }</div><div>{props.infoclean.blog ? <div><p>Blog:</p><p><a href={props.infoclean.blog.search("http") !== -1 ? props.infoclean.blog: "http://" + props.infoclean.blog } target="_blank">{props.infoclean.blog}</a></p></div> : null }</div><div>{props.infoclean.location ? <div><p>Location:</p><p>{props.infoclean.location}</p></div> : null }</div><div>{props.infoclean.company ? <div><p>Company:</p><p>{props.infoclean.company}</p></div> : null }</div><div>{props.infoclean.public_repos ? <div><p>Public Repos:</p><p>{props.infoclean.public_repos}</p></div> : null }</div><div>{props.infoclean.followers ? <div><p>Followers:</p><p>{props.infoclean.followers}</p></div> : null }</div><div>{props.infoclean.following ? <div><p>Following:</p><p>{props.infoclean.following}</p></div> : null }</div><div>{props.infoclean.html_url ? <div><p><a href={props.infoclean.html_url} target="_blank">View on GitHub</a></p></div> : null }</div><div>{props.infoclean.login ? <div>{ <img src={" "+props.infoclean.login} alt="Github chart" />}<br/><a href=" " target="_blank">Source for GitHub Chart API</a></div> : null }</div></div>) http://ghchart.rshah.org/ https://ghchart.rshah.org/ }; export default ProfileDetails; As a result, this produces the following output for my . GitHub profile Further opportunities for improvement in this project, include adding styling, code refactoring and testing. Feel free to track the progress of this project on and on its . GitHub website