Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

activity: Make activity page with graphs on issues #27

Merged
merged 3 commits into from
Dec 9, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions .coafile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[all]
files = *.py, community/**/*.py, gci/**/*.py
files = *.py, community/**/*.py, gci/**/*.py, activity/*.py
ignore = gci/client.py
max_line_length = 80
use_spaces = True
Expand Down Expand Up @@ -31,11 +31,16 @@ bears = JSHintBear
allow_unused_variables = True
javascript_strictness = False

[bash]
files = *.sh
bears = ShellCheckBear
shell = bash

[generalization]
# Do not allow the word "coala" to ensure the repository can be generalized out
# for use by other organizations.
files = **
ignore = .git/**, .coafile, .travis.yml, LICENSE
ignore = .git/**, org_name.txt, .coafile, .travis.yml, LICENSE
bears = KeywordBear
language = python 3
keywords = coala
4 changes: 4 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,16 @@ env:
before_script:
- pip install coala-bears
- npm i -g jshint
- bash orgname.sh

script:
- coala --non-interactive -V

after_success:
- mkdir _site public
- python activity/scraper.py
- python manage.py collectstatic --noinput
- python manage.py distill-local public --force
- mkdir public/activity
- cp activity/index.html public/activity/
- ./.ci/deploy.sh
41 changes: 41 additions & 0 deletions activity/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<!doctype html>
<html>

<head>
<title>Community Activity</title>
<style>
canvas {
-moz-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
}
</style>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
</head>

<body>
<h1>Community Activity</h1>
<br>
<br>
<div style="width:75%;">
<canvas id="canvas"></canvas>
</div>
<select id="chartType">
<option value="Month">Year</option>
<option value="Week">Month</option>
<option value="Day">Week</option>
</select>
<br>
<br>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.1/Chart.bundle.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src="/static/charts.js"></script>
<script>
$('#chartType').on('change', function(){
updateChart($('#chartType').val())
});
updateChart($('#chartType').val());
</script>
</body>

</html>
147 changes: 147 additions & 0 deletions activity/scraper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import requests
import json
import datetime
import os
import calendar
from dateutil import parser, relativedelta


class Scraper():
"""
This is the class responsible for scraping provided issues into a
dictionary containing just statistical information of the data.
"""

"""
Count of months/weeks/days respectively to be scraped in past.
"""
CONSTANTS = {
'month_count': 12,
'week_count': 4,
'day_count': 7,
}

def __init__(self, content, date):
"""
Constructs a new ``Scraper``

:param content: Github API Parsed JSON issues
:param date: The date to scrape data till.
"""
self.date = date
self.content = content

# Initialise data dicts
self.data = {
'year': {
'labels': [],
'closed': [0]*self.CONSTANTS['month_count'],
'opened': [0]*self.CONSTANTS['month_count'],
},
'month': {
'labels': [],
'closed': [0]*self.CONSTANTS['week_count'],
'opened': [0]*self.CONSTANTS['week_count'],
},
'week': {
'labels': [],
'closed': [0]*self.CONSTANTS['day_count'],
'opened': [0]*self.CONSTANTS['day_count'],
},
}

# Process labels for each option
for x in range(self.CONSTANTS['month_count']-1, -1, -1):
self.data['year']['labels'].append(calendar.month_name[(
self.date - relativedelta.relativedelta(months=x)).month])

for x in range(self.CONSTANTS['week_count']-1, -1, -1):
day = self.date - relativedelta.relativedelta(weeks=x)
strt = (day - datetime.timedelta(days=day.weekday()))
fin = (day + datetime.timedelta(days=6-day.weekday()))
self.data['month']['labels'].append(
calendar.month_abbr[strt.month] + ' ' + str(strt.day)
+ ' - '
+ calendar.month_abbr[fin.month] + ' ' + str(fin.day))

for x in range(self.CONSTANTS['day_count']-1, -1, -1):
day_idx = (self.date - datetime.timedelta(days=x)).weekday()
self.data['week']['labels'].append(calendar.day_name[day_idx])

def __diff_month(self, d):
"""
:param d: Date as datetime, self.date >= Date.

:return: Difference in months(int) ignoring partially complete months.
"""
return (self.date.year - d.year) * 12 + self.date.month - d.month

def __diff_week(self, d):
"""
:param d: Date as datetime, self.date >= Date.

:return: Difference in weeks(int) ignoring partially complete weeks.
"""
monday1 = (self.date - datetime.timedelta(days=self.date.weekday()))
monday2 = (d - datetime.timedelta(days=d.weekday()))
return (monday1 - monday2).days // 7

def __diff_days(self, d):
"""
:param d: Date as datetime, self.date >= Date.

:return: Difference in days(int) ignoring partially complete days.
"""
return (self.date-d).days

def get_data(self):
"""
Get data
Copy link
Member

Choose a reason for hiding this comment

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

return type


:return: Data in form of dict containing year, month, week data.
"""
for issue in self.content:
issue = issue['issue']
# Parse date, while ignoring the timestamp.
dt = parser.parse(issue['createdAt'][:10])

mon = self.__diff_month(dt)
if mon < self.CONSTANTS['month_count']:
mon = self.CONSTANTS['month_count'] - mon
self.data['year']['opened'][mon] += 1
if issue['state'] == 'closed':
self.data['year']['closed'][mon] += 1

wk = self.__diff_week(dt)
if wk < self.CONSTANTS['week_count']:
wk = self.CONSTANTS['week_count'] - wk
self.data['month']['opened'][wk] += 1
if issue['state'] == 'closed':
self.data['month']['closed'][wk] += 1

dys = self.__diff_days(dt)
if dys < self.CONSTANTS['day_count']:
dys = self.CONSTANTS['day_count'] - dys
self.data['week']['opened'][dys] += 1
if issue['state'] == 'closed':
self.data['week']['closed'][dys] += 1

return self.data


if __name__ == '__main__':

org_name = open('org_name.txt').readline()

# URL to grab all issues from
issues_url = 'http://' + org_name + '.github.io/gh-board/issues.json'

content = requests.get(issues_url)
parsed_json = content.json()

real_data = Scraper(parsed_json['issues'], datetime.datetime.today())
real_data = real_data.get_data()

print(real_data)
with open('static' + os.sep + 'activity-data.json', 'w') as fp:
json.dump(real_data, fp)
6 changes: 6 additions & 0 deletions orgname.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/bin/bash

regex="github.com/[a-z0-9A-Z]*"
git config -l | grep -o "$regex" | while read -r line ; do
echo "${line:11}" | xargs echo -n > org_name.txt
Copy link
Member

Choose a reason for hiding this comment

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

I think we prefer two indents by default for shell. Maybe see what ShellCheckBear says ;-)

Copy link
Member Author

@nalinbhardwaj nalinbhardwaj Dec 8, 2017

Choose a reason for hiding this comment

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

Seems not, .ci/deploy.sh uses 4 as well.

Copy link
Member

Choose a reason for hiding this comment

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

that was a horribly rushed script I mostly copied from other sources and fixed up; without code review; naughty me.

https://github.com/coala/coala-bears/blob/master/.ci/deps.sh
https://github.com/coala/coala/blob/master/.misc/deps.sh
(and others in those directories)

We dont run code linting on them yet. I've created coala/coala#4952 for that. (I'll publish a backlog of new tasks tomorrow)

Copy link
Member

Choose a reason for hiding this comment

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

(this script is so small it doesnt matter; you are right we dont have a convention yet, so you dont need to update this patch to follow a non-existent coding standard)

done
86 changes: 86 additions & 0 deletions static/charts.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/* globals $, Chart */
var curChart;

function setChart(labels, openedData, closedData, type) {
var ctx = document.getElementById("canvas");

curChart = new Chart(ctx, {
type: 'line',
data: {
labels: labels,
datasets: [{
label: "Issues Opened",
backgroundColor: "RGBA(33, 150, 243, 0.2)",
borderColor: "RGBA(33, 150, 243, 1)",
data: openedData,
fill: true,
}, {
label: "Issues Closed",
backgroundColor: "RGBA(244, 67, 54, 0.2)",
borderColor: "RGBA(244, 67, 54, 1)",
data: closedData,
fill: true,
}]
},
options: {
responsive: true,
title: {
display: true,
text: 'Community Activity'
},
tooltips: {
mode: 'index',
intersect: false,
},
hover: {
mode: 'nearest',
intersect: true
},
scales: {
xAxes: [{
display: true,
scaleLabel: {
display: true,
labelString: type
}
}],
yAxes: [{
display: true,
scaleLabel: {
display: true,
labelString: 'Number'
}
}]
}
}
});
}

function updateChart(type) {
if(curChart){ curChart.destroy(); }

$.getJSON("/static/activity-data.json",
function(data) {
var labels, openedData, closedData;
if(type === "Month") {
labels = data.year.labels;
openedData = data.year.opened;
closedData = data.year.closed;
}
else if(type === "Week") {
labels = data.month.labels;
openedData = data.month.opened;
closedData = data.month.closed;
}
else {
labels = data.week.labels;
openedData = data.week.opened;
closedData = data.week.closed;
}
setChart(labels, openedData, closedData, type);
})
.fail(function(data, textStatus, error) {
var err = textStatus + ", " + error;
console.error("Request Failed: " + err);
});
}