Skip to content

Commit

Permalink
WIP: React application
Browse files Browse the repository at this point in the history
  • Loading branch information
scriptex committed Jun 17, 2020
1 parent 0f26abe commit 21f8899
Show file tree
Hide file tree
Showing 11 changed files with 5,399 additions and 62 deletions.
4 changes: 4 additions & 0 deletions .babelrc
@@ -0,0 +1,4 @@
{
"presets": ["@babel/preset-react"],
"plugins": ["@babel/plugin-transform-runtime"]
}
18 changes: 16 additions & 2 deletions README.md
Expand Up @@ -2,21 +2,35 @@

> Get insights for your Github repositories
## How?
[Start using the live application now](https://github-repository-insights.now.sh/)

## Local usage

1. Clone this repository
2. Run `npm install` or `yarn`
3. Copy `.env.example` to `.env` and add your Github Auth Token in the new `.env` file:
```sh
TOKEN=your_github_token
```
4. Run `npm start` or `yarn start` by provding at least one of the required arguments:
4. Run `npm fetch` or `yarn fetch` by provding at least one of the required arguments:
- `--org`: a Github organisation name
- `--user`: a Github user name
- `--repository`: a Github repository in the following format `:owner/:repository` (`scriptex/github-insights`)
5. Wait for the script to fetch and transform the data from Github.
6. Check the `insights.json` file in the root of the project.

## Frontend

There is a frontend application included built with Parcel and React using Recharts.

**The application is still a work in progress.**

The deployment is taken care of by Vercel.

If you want to experience the full capabilities of this package locally then you need to create your own Vercel account, install the `now cli` and run `now dev` after you linked your clone/fork with Vercel.

More on this later.

## About

This tool uses the 3rd version of the [Github REST API](https://developer.github.com/v3/).
Expand Down
28 changes: 28 additions & 0 deletions app/bar-chart.js
@@ -0,0 +1,28 @@
import React from 'react';
import format from 'date-fns/format';
import { Bar, XAxis, YAxis, Legend, Tooltip, BarChart, CartesianGrid } from 'recharts';

import { capitalize, randomColor } from './utils';

const formatter = t => format(new Date(t), 'MMM dd');
const labelFormatter = value => capitalize(value);

export const InsightsBarChart = props => (
<div className="chart">
<h2>{props.title}</h2>

<p>Count: {props.data.count}</p>

<p>Unique: {props.data.uniques}</p>

<BarChart width={700} height={300} data={props.name ? props.data[props.name] : props.data}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey={props.dataKey} tickFormatter={props.dataKey === 'timestamp' ? formatter : () => ''} />
<YAxis />
<Tooltip labelFormatter={props.dataKey === 'timestamp' ? formatter : t => t} />
<Legend formatter={labelFormatter} />
<Bar dataKey="count" fill={randomColor()} />
<Bar dataKey="uniques" fill={randomColor()} />
</BarChart>
</div>
);
42 changes: 42 additions & 0 deletions app/charts.js
@@ -0,0 +1,42 @@
import React from 'react';

import { InsightsBarChart } from './bar-chart';
import { InsightsPieChart } from './pie-chart';

export const Charts = props => (
<div className="charts">
<InsightsBarChart
name="paths"
data={props.data.paths.reduce(
(result, path) => {
result.count = (result.count || 0) + path.count;
result.uniques = (result.uniques || 0) + path.uniques;

return result;
},
{
paths: props.data.paths
}
)}
title="Paths"
dataKey="path"
/>

<InsightsBarChart name="views" data={props.data.views} title="Views" dataKey="timestamp" />

<InsightsBarChart name="views" data={props.data.forks} title="Forks" dataKey="timestamp" />

<InsightsBarChart name="clones" data={props.data.clones} title="Clones" dataKey="timestamp" />

<InsightsPieChart name="referrer" data={props.data.referrers} title="Referrers" dataKey="timestamp" />

<InsightsPieChart
name="author"
data={props.data.contributors.map(item => ({
count: item.total,
author: item.author.login
}))}
title="Contributors"
/>
</div>
);
21 changes: 21 additions & 0 deletions app/form.js
@@ -0,0 +1,21 @@
import React from 'react';

export const Form = props => (
<div className="form">
<h1>Github Insights</h1>

<p>Get insights for your repository</p>

<form onSubmit={props.onSubmit}>
<input
type="text"
value={props.repository}
placeholder=":owner/:repository"
required
onChange={props.onChange}
/>

<input type="submit" value="Go" />
</form>
</div>
);
29 changes: 2 additions & 27 deletions app/index.html
Expand Up @@ -6,32 +6,7 @@
<title>Github Insights</title>
</head>
<body>
<h1>Github Insights</h1>

<p>Get insights for your repository</p>

<form>
<input type="text" name="repository" value="" placeholder=":owner/:repository" required />
<input type="submit" value="Go" />
</form>

<script>
const form = document.querySelector('form');

form.addEventListener('submit', event => {
event.preventDefault();

const data = new FormData(event.target);
const repository = data.get('repository');

fetch('/insights', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: 'repository=' + repository
}).then(async res => {
console.log(await res.json());
});
});
</script>
<div id="app" class="wrapper"></div>
<script src="index.js"></script>
</body>
</html>
55 changes: 55 additions & 0 deletions app/index.js
@@ -0,0 +1,55 @@
import React from 'react';
import ReactDOM from 'react-dom';

import { Form } from './form';
import { Charts } from './charts';

const App = () => {
const [data, setData] = React.useState();
const [error, setError] = React.useState('');
const [loading, setLoading] = React.useState(false);
const [repository, setRepository] = React.useState('');

const onSubmit = event => {
event.preventDefault();

setLoading(true);

fetch('/insights', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: 'repository=' + repository
})
.then(async res => {
const [response] = await res.json();
const errors = Object.values(response)
.map(value => value.status)
.filter(Boolean).length;

if (errors > 0) {
setError(`Repository ${repository} does not exist.`);
} else {
setData(response);
setError('');
}
})
.catch(_ => setError('Something went wrong. Please try again later.'))
.finally(() => {
setLoading(false);
});
};

return loading ? (
<div className="loading">... fetching data for {repository}</div>
) : (
<div className={`insights${data ? ' insights--fetched' : ''}`}>
{error && <div className="error">{error}</div>}

<Form onSubmit={onSubmit} onChange={e => setRepository(e.target.value)} repository={repository} />

{data && <Charts data={data} />}
</div>
);
};

ReactDOM.render(<App />, document.getElementById('app'));
25 changes: 25 additions & 0 deletions app/pie-chart.js
@@ -0,0 +1,25 @@
import React from 'react';
import { Pie, PieChart, Tooltip } from 'recharts';

import { randomColor } from './utils';

export const InsightsPieChart = props => (
<div className="chart">
<h2>{props.title}</h2>

<PieChart width={730} height={250}>
<Pie
cx="50%"
cy="50%"
data={props.data}
fill={randomColor()}
label
dataKey="count"
nameKey={props.name}
outerRadius={100}
/>

<Tooltip />
</PieChart>
</div>
);
3 changes: 3 additions & 0 deletions app/utils.js
@@ -0,0 +1,3 @@
export const capitalize = ([first, ...rest]) => first.toUpperCase() + rest.map(letter => letter.toLowerCase()).join('');

export const randomColor = () => '#' + Math.floor(Math.random() * 16777215).toString(16);
14 changes: 13 additions & 1 deletion package.json
Expand Up @@ -4,7 +4,8 @@
"description": "Get insights for your Github repositories",
"main": "lib/index.js",
"scripts": {
"start": "nodemon lib/index.js"
"fetch": "nodemon lib/index.js",
"start": "parcel app/index.html"
},
"keywords": [
"Github insights",
Expand All @@ -20,14 +21,25 @@
"license": "MIT",
"dependencies": {
"chalk": "4.1.0",
"date-fns": "2.14.0",
"dotenv": "8.2.0",
"express": "4.17.1",
"node-fetch": "2.6.0",
"nodemon": "2.0.4",
"ora": "4.0.4",
"react": "16.13.1",
"react-dom": "16.13.1",
"recharts": "1.8.5",
"universal-github-client": "1.0.2",
"yargs": "15.3.1"
},
"devDependencies": {
"@babel/core": "7.10.2",
"@babel/plugin-transform-runtime": "7.10.1",
"@babel/preset-env": "7.10.2",
"@babel/preset-react": "7.10.1",
"parcel-bundler": "1.12.4"
},
"nodemonConfig": {
"verbose": false,
"ignore": [
Expand Down

1 comment on commit 21f8899

@vercel
Copy link

@vercel vercel bot commented on 21f8899 Jun 17, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.