diff --git a/.ci/build.sh b/.ci/build.sh
index 1f01164d..56c2be06 100755
--- a/.ci/build.sh
+++ b/.ci/build.sh
@@ -27,6 +27,7 @@ python manage.py fetch_deployed_data _site $ISSUES_JSON \
python manage.py migrate
python manage.py import_contributors_data
+python manage.py create_org_cluster_map_and_activity_graph org_map
python manage.py import_issues_data
python manage.py import_merge_requests_data
python manage.py create_config_data
diff --git a/.coafile b/.coafile
index edfbf0ec..d2942cee 100644
--- a/.coafile
+++ b/.coafile
@@ -1,6 +1,6 @@
[all]
files = **.py, **.js, **.sh
-ignore = .git/**, **/__pycache__/**, gci/client.py, */migrations/**, private/*
+ignore = .git/**, **/__pycache__/**, gci/client.py, */migrations/**, private/*, openhub/**, **/leaflet_dist/**
max_line_length = 80
use_spaces = True
preferred_quotation = '
@@ -42,6 +42,7 @@ files = static/**/*.js
bears = JSHintBear
allow_unused_variables = True
javascript_strictness = False
+environment_jquery = True
[all.yml]
bears = YAMLLintBear
@@ -58,7 +59,7 @@ shell = bash
# for use by other organizations.
files = **
# .coverage crashes AnnotationBear
-ignore += .coafile, *requirements.txt, .travis.yml, LICENSE, .nocover.yaml, .moban.yaml, .moban.dt/community-*.jj2, public/**, _site/**, .ci/check_moban.sh, .coverage
+ignore += .coafile, *requirements.txt, .travis.yml, LICENSE, .nocover.yaml, .moban.yaml, .moban.dt/community-*.jj2, public/**, _site/**, .ci/check_moban.sh, .coverage, static/js/main.js
bears = KeywordBear
language = python 3
keywords = coala
diff --git a/.gitignore b/.gitignore
index 6ca4d51a..2592fe2b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -63,6 +63,7 @@ coverage.xml
*.log
local_settings.py
db.sqlite3
+db.sqlite3-journal
# Flask stuff:
instance/
@@ -272,6 +273,7 @@ flycheck_*.el
# Session
Session.vim
+Sessionx.vim
# Temporary
.netrwhist
@@ -429,11 +431,7 @@ DerivedData/
*.perspectivev3
!default.perspectivev3
-## Xcode Patch
-*.xcodeproj/*
-!*.xcodeproj/project.pbxproj
-!*.xcodeproj/xcshareddata/
-!*.xcworkspace/contents.xcworkspacedata
+## Gcc Patch
/*.gcno
# Eclipse rules
diff --git a/.moban.yaml b/.moban.yaml
index 574c2d69..f9db2b3c 100644
--- a/.moban.yaml
+++ b/.moban.yaml
@@ -9,13 +9,13 @@ packages:
- gci
- gsoc
- gamification
- - log
+ - ci_build
- meta_review
- model
- - twitter
- unassigned_issues
dependencies:
+ - getorg~=0.3.1
- git+https://gitlab.com/coala/coala-utils.git
- git-url-parse
- django>2.1,<2.2
diff --git a/.nocover.yaml b/.nocover.yaml
index 987773ce..4757eb64 100644
--- a/.nocover.yaml
+++ b/.nocover.yaml
@@ -8,11 +8,10 @@ nocover_file_globs:
- community/git.py
- gci/*.py
- gsoc/*.py
- - log/*.py
+ - ci_build/*.py
- meta_review/handler.py
- model/*.py
- openhub/*.py
- - twitter/*.py
# Optional coverage. Once off scripts.
- inactive_issues/inactive_issues_scraper.py
- unassigned_issues/unassigned_issues_scraper.py
diff --git a/.travis.yml b/.travis.yml
index 61015858..255cb955 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,5 +1,5 @@
language: python
-python: 3.6
+python: 3.6.3
cache:
pip: true
diff --git a/activity/scraper.py b/activity/scraper.py
index 069bc668..9d8fc794 100644
--- a/activity/scraper.py
+++ b/activity/scraper.py
@@ -136,7 +136,7 @@ def get_data(self):
return self.data
-def activity_json(request):
+def activity_json(filename):
org_name = get_org_name()
@@ -152,4 +152,5 @@ def activity_json(request):
real_data = Scraper(parsed_json['issues'], datetime.datetime.today())
real_data = real_data.get_data()
- return HttpResponse(json.dumps(real_data))
+ with open(filename, 'w+') as f:
+ json.dump(real_data, f, indent=4)
diff --git a/ci_build/view_log.py b/ci_build/view_log.py
new file mode 100644
index 00000000..8c476ae3
--- /dev/null
+++ b/ci_build/view_log.py
@@ -0,0 +1,131 @@
+import re
+import json
+import os
+import sys
+
+from django.views.generic import TemplateView
+
+from community.views import get_header_and_footer
+from community.git import (
+ get_org_name,
+ get_owner,
+ get_deploy_url,
+ get_upstream_deploy_url
+)
+
+
+class BuildLogsView(TemplateView):
+ template_name = 'build_logs.html'
+
+ def copy_build_logs_json(self, ci_build_jsons):
+ """
+ :param ci_build_jsons: A dict of directories path
+ :return: A boolean, whether the build file is copied
+ """
+ if os.path.isfile(ci_build_jsons['public_path']):
+ if sys.platform == 'linux':
+ os.popen('cp {} {}'.format(
+ ci_build_jsons['site_path'],
+ ci_build_jsons['public_path']))
+ os.popen('cp {} {}'.format(
+ ci_build_jsons['site_path'],
+ ci_build_jsons['static_path']))
+ else:
+ os.popen('copy {} {}'.format(
+ ci_build_jsons['site_path'],
+ ci_build_jsons['public_path']))
+ os.popen('copy {} {}'.format(
+ ci_build_jsons['site_path'],
+ ci_build_jsons['static_path']))
+ return True
+ return False
+
+ def create_and_copy_build_logs_json(self, logs, level_specific_logs):
+ """
+ Create a build logs detailed json file in ./_site directory and copy
+ that file in the ./static and ./public/static directories
+ :param logs: A list of all lines in build log file
+ :param level_specific_logs: A dict containing logs divided in their
+ respective categories
+ :return: A boolean, whether the files were copied or not
+ """
+ ci_build_jsons = {
+ 'site_path': './_site/ci-build-detailed-logs.json',
+ 'public_path': './public/static/ci-build-detailed-logs.json',
+ 'static_path': './static/ci-build-detailed-logs.json'
+ }
+ with open(ci_build_jsons['site_path'], 'w+') as build_logs_file:
+ data = {
+ 'logs': logs,
+ 'logs_level_Specific': level_specific_logs
+ }
+ json.dump(data, build_logs_file, indent=4)
+ return self.copy_build_logs_json(ci_build_jsons)
+
+ def get_build_logs(self, log_file_path):
+ """
+ :param log_file_path: build logs file path
+ :return: a tuple of two where the first element in tuple refers to
+ a list of build logs in the file, and the second element is a dict
+ which categorizes the build logs into 5 categories - INFO, DEBUG,
+ WARNING, ERROR nad CRITICAL
+ """
+ log_lines = []
+ log_level_specific_lines = {
+ 'INFO': [],
+ 'DEBUG': [],
+ 'WARNING': [],
+ 'ERROR': [],
+ 'CRITICAL': []
+ }
+ with open(log_file_path) as log_file:
+ previous_found_level = None
+ for line in log_file:
+ log_lines.append(line)
+ levels = re.findall(r'\[[A-Z]+]', line)
+ if levels:
+ level = levels[0]
+ level = previous_found_level = level[1:-1]
+ log_level_specific_lines[level].append(line)
+ elif previous_found_level:
+ log_level_specific_lines[previous_found_level].append(
+ line)
+ return log_lines, log_level_specific_lines
+
+ def check_build_logs_stored(self):
+ """
+ Check whether the build logs json file is copied to _site and public
+ directories or not
+ :return: A Boolean
+ """
+ log_file_path = './_site/community.log'
+ log_file_exists = os.path.isfile(log_file_path)
+ if log_file_exists:
+ logs, level_specific_logs = self.get_build_logs(log_file_path)
+ return self.create_and_copy_build_logs_json(logs,
+ level_specific_logs)
+ return False
+
+ def get_build_info(self):
+ """
+ Get the information about build, like who deployed the website i.e.
+ owner, name of the organization or user etc.
+ :return: A dict having information about build related details
+ """
+ data = {
+ 'Org name': get_org_name(),
+ 'Owner': get_owner(),
+ 'Deploy URL': get_deploy_url(),
+ }
+ try:
+ data['Upstream deploy URL'] = get_upstream_deploy_url()
+ except RuntimeError:
+ data['Upstream deploy URL'] = 'Not found'
+ return data
+
+ def get_context_data(self, **kwargs):
+ context = super().get_context_data(**kwargs)
+ context = get_header_and_footer(context)
+ context['build_info'] = self.get_build_info()
+ context['logs_stored'] = self.check_build_logs_stored()
+ return context
diff --git a/community/forms.py b/community/forms.py
new file mode 100644
index 00000000..729abded
--- /dev/null
+++ b/community/forms.py
@@ -0,0 +1,295 @@
+from datetime import datetime
+
+from django import forms
+
+from community.git import get_org_name
+
+TODAY = datetime.now().today()
+ORG_NAME = get_org_name()
+
+
+class JoinCommunityForm(forms.Form):
+
+ github_username = forms.CharField(
+ max_length=50, label='GitHub Username',
+ widget=forms.TextInput(
+ attrs={
+ 'placeholder': 'Make sure to NOT enter your github profile url',
+ 'autocomplete': 'off'
+ }
+ )
+ )
+ gh_first_repo = forms.URLField(
+ required=False, label='GitHub Personal Repository',
+ widget=forms.URLInput(
+ attrs={
+ 'placeholder': 'A valid github url of your personal repository',
+ 'autocomplete': 'off'
+ }
+ )
+ )
+ gh_git_training_exercise = forms.URLField(
+ required=False, label='From which GitHub repository you have done git'
+ ' training?',
+ widget=forms.URLInput(
+ attrs={
+ 'placeholder': 'A valid github url of git training repository',
+ 'autocomplete': 'off'
+ }
+ )
+ )
+ gh_most_contributed_repo = forms.URLField(
+ required=False,
+ label="GitHub Repository to which you've contributed most!",
+ widget=forms.URLInput(
+ attrs={
+ 'placeholder': 'A valid github public repository url',
+ 'autocomplete': 'off'
+ }
+ )
+ )
+
+ gitlab_user_id = forms.IntegerField(
+ label='GitLab User ID',
+ widget=forms.NumberInput(
+ attrs={
+ 'placeholder': 'Make sure to NOT enter your gitlab profile url',
+ 'autocomplete': 'off'
+ }
+ )
+ )
+ gl_first_repo_id = forms.IntegerField(
+ required=False, label='GitLab Personal Project ID',
+ widget=forms.NumberInput(
+ attrs={
+ 'placeholder': 'Your personal gitlab project ID',
+ 'autocomplete': 'off'
+ }
+ )
+ )
+ gl_git_training_exercise = forms.IntegerField(
+ required=False, label='From which GitLab project you have done git'
+ ' training?',
+ widget=forms.NumberInput(
+ attrs={
+ 'placeholder': 'A valid project ID of Git training project',
+ 'autocomplete': 'off'
+ }
+ )
+ )
+ gl_most_contributed_repo_id = forms.IntegerField(
+ required=False,
+ label="GitLab Project to which you've contributed most!",
+ widget=forms.NumberInput(
+ attrs={
+ 'placeholder': 'A valid ID of gitlab public project',
+ 'autocomplete': 'off',
+ }
+ )
+ )
+
+
+class CommunityGoogleForm(forms.Form):
+ user = forms.CharField(
+ max_length=50, label='GitHub Username',
+ widget=forms.TextInput(attrs={'autocomplete': 'off'})
+ )
+ title = forms.CharField(
+ max_length=100, label='Title',
+ widget=forms.TextInput(attrs={'autocomplete': 'off'})
+ )
+ description = forms.CharField(
+ max_length=1000, label='Form Description', required=False,
+ widget=forms.Textarea(attrs={'autocomplete': 'off'})
+ )
+ url = forms.URLField(
+ label='Google Form URL',
+ widget=forms.TextInput(attrs={'autocomplete': 'off'})
+ )
+ expiry_date = forms.DateTimeField(
+ label='Expiry date and time', required=False,
+ help_text='DateTime Format should be YYYY-MM-DD HH:MM:SS',
+ widget=forms.TextInput(attrs={'autocomplete': 'off'})
+ )
+
+
+class CommunityEvent(forms.Form):
+ user = forms.CharField(
+ max_length=50, label='GitHub Username',
+ widget=forms.TextInput(attrs={'autocomplete': 'off'})
+ )
+ title = forms.CharField(
+ max_length=300, label='Event Title',
+ widget=forms.TextInput(attrs={'autocomplete': 'off'})
+ )
+ description = forms.CharField(
+ max_length=1000, label='Event Description', required=False,
+ widget=forms.Textarea(attrs={'autocomplete': 'off'})
+ )
+ start_date_time = forms.DateTimeField(
+ label='Event occurrence date and time(in UTC)',
+ help_text='DateTime Format should be YYYY-MM-DD HH:MM:SS',
+ widget=forms.TextInput(attrs={'autocomplete': 'off'})
+ )
+ end_date_time = forms.DateTimeField(
+ label='Event end date and time(in UTC)', required=False,
+ help_text='DateTime Format should be YYYY-MM-DD HH:MM:SS',
+ widget=forms.TextInput(attrs={'autocomplete': 'off'})
+ )
+
+
+class OrganizationMentor(forms.Form):
+ user = forms.CharField(
+ max_length=50, label='GitHub Username',
+ widget=forms.TextInput(attrs={'autocomplete': 'off'})
+ )
+ year = forms.ChoiceField(
+ choices=[(TODAY.year, TODAY.year), (TODAY.year + 1, TODAY.year + 1)],
+ label='Mentoring Year', widget=forms.Select()
+ )
+ program = forms.ChoiceField(
+ choices=[('GSoC', 'Google Summer of Code'), ('GCI', 'Google Code-In')],
+ label='Mentoring Program'
+ )
+
+
+class GSOCStudent(forms.Form):
+ user = forms.CharField(
+ max_length=50, label='GitHub Username',
+ widget=forms.TextInput(attrs={'autocomplete': 'off'})
+ )
+ year = forms.IntegerField(
+ label='Participation year',
+ widget=forms.NumberInput(attrs={'autocomplete': 'off'})
+ )
+ project_topic = forms.CharField(
+ max_length=300, label='GSoC Project Topic',
+ help_text='Should be same as on GSoC Website!',
+ widget=forms.TextInput(attrs={'autocomplete': 'off'})
+ )
+ project_desc = forms.CharField(
+ max_length=2000, label='Project Description',
+ widget=forms.Textarea(attrs={'autocomplete': 'off'})
+ )
+ accepted_proposal = forms.URLField(
+ label='Accepted Proposal URL',
+ help_text='The proposal you submitted during GSoC Program!',
+ widget=forms.URLInput(attrs={'autocomplete': 'off'})
+ )
+ cEP = forms.URLField(
+ label='Org Enhancement Proposal Merge Request', required=False,
+ help_text='For example, in {org} we have cEP({org} Enhancement '
+ 'Proposal)'.format(org='coala'), # Ignore KeywordBear
+ widget=forms.URLInput(attrs={'autocomplete': 'off'})
+ )
+ project_url = forms.URLField(
+ label='GSoC Project URL',
+ widget=forms.URLInput(attrs={'autocomplete': 'off'})
+ )
+ mentors = forms.CharField(
+ max_length=200, label='Project Mentors',
+ help_text='Separate name of mentor by comma(,)',
+ widget=forms.TextInput(attrs={'autocomplete': 'off'})
+ )
+ image = forms.URLField(
+ label='Personal Image URL', required=False,
+ widget=forms.URLInput(attrs={'autocomplete': 'off'})
+ )
+
+
+class AssignIssue(forms.Form):
+ user = forms.CharField(
+ max_length=50, label='GitHub Username',
+ widget=forms.TextInput(attrs={'autocomplete': 'off'})
+ )
+ hoster = forms.ChoiceField(
+ choices=[('github', 'GitHub'), ('gitlab', 'GitLab')], label='Hoster'
+ )
+ repository_url = forms.URLField(
+ label='Repository URL',
+ help_text=f'For example, https://github.com/{ORG_NAME}/community/',
+ widget=forms.URLInput(attrs={'autocomplete': 'off'})
+ )
+ issue_number = forms.IntegerField(
+ label='Issue Number',
+ widget=forms.NumberInput(attrs={'autocomplete': 'off'})
+ )
+ requested_user = forms.CharField(
+ max_length=50, label='GitHub Username',
+ widget=forms.TextInput(attrs={'autocomplete': 'off', 'hidden': True})
+ )
+
+
+class NewcomerPromotion(forms.Form):
+ github_username = forms.CharField(
+ max_length=50, label='GitHub Username',
+ widget=forms.TextInput(attrs={'autocomplete': 'off'})
+ )
+ gitlab_user_id = forms.IntegerField(
+ label='GitLab User ID',
+ widget=forms.NumberInput(attrs={'autocomplete': 'off'})
+ )
+ project_web_url = forms.URLField(
+ label='Personal Project URL',
+ help_text="A project in which you've added .coafile and build it!",
+ widget=forms.URLInput(attrs={'autocomplete': 'off'})
+ )
+ build_file_relative_path = forms.CharField(
+ label='Build File',
+ help_text='For example, if integrated Travis CI, provide '
+ '/relative/path/to/travis.yml',
+ widget=forms.TextInput(attrs={'autocomplete': 'off'})
+ )
+ coafile_relative_path = forms.CharField(
+ label='.coafile File',
+ help_text="Add 5 or more sections in '.coafile'! Provide "
+ '/relative/path/to/.coafile',
+ widget=forms.TextInput(attrs={'autocomplete': 'off'})
+ )
+ gist_or_snippet_id = forms.IntegerField(
+ label='Gist or Snippet ID',
+ help_text='Paste your local build output to gist or snippet! Choose '
+ 'Gist, if personal project on GitHub else choose '
+ 'GitLab Snippet.',
+ widget=forms.NumberInput(attrs={'autocomplete': 'off'})
+ )
+ newcomer_solved_issue_web_url = forms.URLField(
+ label='Issue URL',
+ help_text=f'For example, https://github.com/{ORG_NAME}/community/'
+ 'issues/1',
+ widget=forms.URLInput(attrs={'autocomplete': 'off'})
+ )
+ newcomer_issue_related_pr = forms.URLField(
+ label='Merge Request URL',
+ help_text=f'For example, https://github.com/{ORG_NAME}/community'
+ '/pulls/1',
+ widget=forms.URLInput(attrs={'autocomplete': 'off'})
+ )
+ newcomer_issue_pr_reviewed_url = forms.URLField(
+ label='Reviewed PR URL',
+ help_text=f'For example, https://github.com/{ORG_NAME}/community/'
+ 'pulls/2',
+ widget=forms.URLInput(attrs={'autocomplete': 'off'})
+ )
+ low_level_solved_issue_web_url = forms.URLField(
+ label='Issue URL',
+ help_text=f'For example, https://github.com/{ORG_NAME}/community/'
+ 'issues/1',
+ widget=forms.URLInput(attrs={'autocomplete': 'off'})
+ )
+ low_level_issue_related_pr = forms.URLField(
+ label='Merge Request URL',
+ help_text=f'For example, https://github.com/{ORG_NAME}/community'
+ '/pulls/1',
+ widget=forms.URLInput(attrs={'autocomplete': 'off'})
+ )
+ low_level_issue_pr_reviewed_url = forms.URLField(
+ label='Reviewed PR URL',
+ help_text=f'For example, https://github.com/{ORG_NAME}/community/'
+ 'pulls/2',
+ widget=forms.URLInput(attrs={'autocomplete': 'off'})
+ )
+ request_created_by_user = forms.CharField(
+ max_length=50, label='GitHub Username',
+ widget=forms.TextInput(attrs={'autocomplete': 'off', 'hidden': True})
+ )
diff --git a/community/git.py b/community/git.py
index fabe06d8..7d5541fc 100644
--- a/community/git.py
+++ b/community/git.py
@@ -49,7 +49,7 @@ def get_config_remote(name='origin'):
raise KeyError('No git remotes found')
-def get_remote_url():
+def get_remote_url(name='origin'):
"""Obtain a parsed remote URL.
Uses CI environment variables or git remotes.
@@ -58,7 +58,7 @@ def get_remote_url():
# It only sets the REPOSITORY_URL
url = os.environ.get('REPOSITORY_URL')
if not url:
- remote = get_config_remote()
+ remote = get_config_remote(name)
assert remote[0][0] == 'url'
url = remote[0][1]
@@ -146,7 +146,7 @@ def get_upstream_repo():
"""Obtain the parent slug of the repository.
"""
try:
- remote = get_config_remote(name='upstream')
+ remote = get_remote_url(name='origin')
except KeyError:
remote = None
diff --git a/community/urls.py b/community/urls.py
index ed936b9d..a42078b6 100644
--- a/community/urls.py
+++ b/community/urls.py
@@ -5,17 +5,14 @@
from django_distill import distill_url
from django.conf.urls.static import static
from django.conf import settings
-from django.views.generic import TemplateView
-from community.views import HomePageView, info
-from gci.views import index as gci_index
+from community.views import HomePageView, JoinCommunityView
+from gci.views import GCIStudentsList
from gci.feeds import LatestTasksFeed as gci_tasks_rss
-from activity.scraper import activity_json
-from twitter.view_twitter import index as twitter_index
-from log.view_log import index as log_index
-from data.views import index as contributors_index
-from gamification.views import index as gamification_index
-from meta_review.views import index as meta_review_index
+from ci_build.view_log import BuildLogsView
+from data.views import ContributorsListView
+from gamification.views import GamificationResults
+from meta_review.views import ContributorsMetaReview
from inactive_issues.inactive_issues_scraper import inactive_issues_json
from openhub.views import index as openhub_index
from model.views import index as model_index
@@ -82,22 +79,10 @@ def get_organization():
distill_file='index.html',
),
distill_url(
- 'info.txt', info,
- name='index',
- distill_func=get_index,
- distill_file='info.txt',
- ),
- distill_url(
- r'static/activity-data.json', activity_json,
- name='activity_json',
- distill_func=get_index,
- distill_file='static/activity-data.json',
- ),
- distill_url(
- r'activity/', TemplateView.as_view(template_name='activity.html'),
- name='activity',
+ r'^join/', JoinCommunityView.as_view(),
+ name='join-community',
distill_func=get_index,
- distill_file='activity/index.html',
+ distill_file='join/index.html',
),
distill_url(
r'gci/tasks/rss.xml', gci_tasks_rss(),
@@ -106,31 +91,25 @@ def get_organization():
distill_file='gci/tasks/rss.xml',
),
distill_url(
- r'gci/', gci_index,
+ r'gci/', GCIStudentsList.as_view(),
name='community-gci',
distill_func=get_index,
distill_file='gci/index.html',
),
distill_url(
- r'twitter/', twitter_index,
- name='twitter',
- distill_func=get_index,
- distill_file='twitter/index.html',
- ),
- distill_url(
- r'log/', log_index,
- name='log',
+ r'ci/build/', BuildLogsView.as_view(),
+ name='ci_build',
distill_func=get_index,
- distill_file='log/index.html',
+ distill_file='ci/build/index.html',
),
distill_url(
- r'contributors/$', contributors_index,
+ r'contributors/$', ContributorsListView.as_view(),
name='community-data',
distill_func=get_index,
distill_file='contributors/index.html',
),
distill_url(
- r'meta-review/$', meta_review_index,
+ r'meta-review/$', ContributorsMetaReview.as_view(),
name='meta_review_data',
distill_func=get_index,
distill_file='meta-review/index.html',
@@ -220,7 +199,7 @@ def get_organization():
distill_file='static/unassigned-issues.json',
),
distill_url(
- r'gamification/$', gamification_index,
+ r'gamification/$', GamificationResults.as_view(),
name='community-gamification',
distill_func=get_index,
distill_file='gamification/index.html',
diff --git a/community/views.py b/community/views.py
index 595c02ed..8d456db6 100644
--- a/community/views.py
+++ b/community/views.py
@@ -1,43 +1,201 @@
-from django.http import HttpResponse
-from django.views.generic.base import TemplateView
+import os
+
+import logging
+
+import requests
from trav import Travis
+from django.views.generic.base import TemplateView
+
from .git import (
- get_deploy_url,
get_org_name,
- get_owner,
- get_upstream_deploy_url,
+ get_remote_url
)
+from .forms import (
+ JoinCommunityForm,
+ CommunityGoogleForm,
+ CommunityEvent,
+ OrganizationMentor,
+ GSOCStudent,
+ AssignIssue,
+ NewcomerPromotion
+)
+from data.models import Team
+from gamification.models import Participant as GamificationParticipant
+from meta_review.models import Participant as MetaReviewer
+
+GL_NEWCOMERS_GRP = 'https://gitlab.com/{}/roles/newcomers'.format(
+ get_org_name()
+)
+
+
+def initialize_org_context_details():
+ org_name = get_org_name()
+ org_details = {
+ 'name': org_name,
+ 'blog_url': f'https://blog.{org_name}.io/',
+ 'twitter_url': f'https://twitter.com/{org_name}_io/',
+ 'facebook_url': f'https://www.facebook.com/{org_name}Analyzer',
+ 'repo_url': get_remote_url().href,
+ 'docs': f'https://{org_name}.io/docs',
+ 'newcomer_docs': f'https://{org_name}.io/newcomer',
+ 'coc': f'https://{org_name}.io/coc',
+ 'logo_url': (f'https://api.{org_name}.io/en/latest/_static/images/'
+ f'{org_name}_logo.svg'),
+ 'gitter_chat': f'https://gitter.im/{org_name}/{org_name}/',
+ 'github_core_repo': f'https://github.com/{org_name}/{org_name}/',
+ 'licence_type': 'GNU AGPL v3.0'
+ }
+ return org_details
+
+
+def get_newcomer_promotion_form_variables(context):
+ context['newcomer_promotion_form'] = NewcomerPromotion()
+ context['newcomer_promotion_form_name'] = os.environ.get(
+ 'NEWCOMER_PROMOTION_REQUEST_FORM_NAME', None
+ )
+ return context
+
+
+def get_assign_issue_form_variables(context):
+ context['assign_issue_form'] = AssignIssue()
+ context['assign_issue_form_name'] = os.environ.get(
+ 'ISSUES_ASSIGN_REQUEST_FORM_NAME', None
+ )
+ return context
+
+
+def get_gsoc_student_form_variables(context):
+ context['gsoc_student_form'] = GSOCStudent()
+ context['gsoc_student_form_name'] = os.environ.get(
+ 'GSOC_STUDENT_FORM_NAME', None
+ )
+ return context
+
+
+def get_community_mentor_form_variables(context):
+ context['organization_mentor_form'] = OrganizationMentor()
+ context['organization_mentor_form_name'] = os.environ.get(
+ 'MENTOR_FORM_NAME', None
+ )
+ return context
+
+
+def get_community_event_form_variables(context):
+ context['community_event_form'] = CommunityEvent()
+ context['community_event_form_name'] = os.environ.get(
+ 'CALENDAR_NETLIFY_FORM_NAME', None
+ )
+ return context
+
+
+def get_community_google_form_variables(context):
+ context['community_google_form'] = CommunityGoogleForm()
+ context['community_google_form_name'] = os.environ.get(
+ 'OSFORMS_NETLIFY_FORM_NAME', None
+ )
+ return context
+
+
+def get_all_community_forms(context):
+ context = get_community_google_form_variables(context)
+ context = get_community_event_form_variables(context)
+ context = get_community_mentor_form_variables(context)
+ context = get_gsoc_student_form_variables(context)
+ context = get_assign_issue_form_variables(context)
+ context = get_newcomer_promotion_form_variables(context)
+ return context
+
+
+def get_header_and_footer(context):
+ context['isTravis'] = Travis.TRAVIS
+ context['travisLink'] = Travis.TRAVIS_BUILD_WEB_URL
+ context['org'] = initialize_org_context_details()
+ context = get_all_community_forms(context)
+ print('Running on Travis: {}, build link: {}'.format(context['isTravis'],
+ context['travisLink']
+ ))
+ return context
class HomePageView(TemplateView):
template_name = 'index.html'
+ def get_team_details(self, org_name):
+ teams = [
+ f'{org_name} newcomers',
+ f'{org_name} developers',
+ f'{org_name} admins'
+ ]
+ team_details = {}
+ for team_name in teams:
+ team = Team.objects.get(name=team_name)
+ contributors_count = team.contributors.count()
+ team_details[
+ team_name.replace(org_name, '').strip().capitalize()
+ ] = contributors_count
+ return team_details
+
+ def get_quote_of_the_day(self):
+
+ try:
+ qod = requests.get('http://quotes.rest/qod?category=inspire')
+ qod.raise_for_status()
+ except requests.HTTPError as err:
+ error_info = f'HTTPError while fetching Quote of the day! {err}'
+ logging.error(error_info)
+ return
+
+ qod_data = qod.json()
+ return {
+ 'quote': qod_data['contents']['quotes'][0]['quote'],
+ 'author': qod_data['contents']['quotes'][0]['author'],
+ }
+
+ def get_top_meta_review_users(self, count):
+ participants = MetaReviewer.objects.all()[:count]
+ return participants
+
+ def get_top_gamification_users(self, count):
+ return enumerate(GamificationParticipant.objects.all()[:count])
+
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
- context['isTravis'] = Travis.TRAVIS
- context['travisLink'] = Travis.TRAVIS_BUILD_WEB_URL
+ context = get_header_and_footer(context)
+ org_name = context['org']['name']
+ context['org']['team_details'] = dict(self.get_team_details(org_name))
+ about_org = (f'{org_name} (always spelled with a lowercase c!) is one'
+ ' of the welcoming open-source organizations for'
+ f' newcomers. {org_name} stands for “COde AnaLysis'
+ ' Application” as it works well with animals and thus is'
+ ' well visualizable which makes it easy to memorize.'
+ f' {org_name} provides a unified interface for linting'
+ ' and fixing the code with a single configuration file,'
+ ' regardless of the programming languages used. You can'
+ f' use {org_name} from within your favorite editor,'
+ ' integrate it with your CI and, get the results as JSON'
+ ', or customize it to your needs with its flexible'
+ ' configuration syntax.')
+ context['org']['about'] = about_org
+ context['quote_details'] = self.get_quote_of_the_day()
+ context['top_meta_review_users'] = self.get_top_meta_review_users(
+ count=5)
+ context['top_gamification_users'] = self.get_top_gamification_users(
+ count=5)
+ return context
- print('Running on Travis: {}, build link: {}'.format(
- context['isTravis'],
- context['travisLink']))
- return context
+class JoinCommunityView(TemplateView):
+ template_name = 'join_community.html'
-def info(request):
- data = {
- 'Org name': get_org_name(),
- 'Owner': get_owner(),
- 'Deploy URL': get_deploy_url(),
- }
- try:
- upstream_deploy_url = get_upstream_deploy_url()
- data['Upstream deploy URL'] = upstream_deploy_url
- except RuntimeError:
- data['Upstream deploy URL'] = 'Not found'
-
- s = '\n'.join(name + ': ' + value
- for name, value in data.items())
- return HttpResponse(s)
+ def get_context_data(self, **kwargs):
+ context = super().get_context_data(**kwargs)
+ context = get_header_and_footer(context)
+ context['join_community_form'] = JoinCommunityForm()
+ context['gitlab_newcomers_group_url'] = GL_NEWCOMERS_GRP
+ context['join_community_form_name'] = os.environ.get(
+ 'JOIN_COMMUNITY_FORM_NAME', None
+ )
+ return context
diff --git a/data/contrib_data.py b/data/contrib_data.py
index aacbab89..4d696b31 100644
--- a/data/contrib_data.py
+++ b/data/contrib_data.py
@@ -28,11 +28,10 @@ def get_contrib_data():
def import_data(contributor):
logger = logging.getLogger(__name__)
login = contributor.get('login', None)
- teams = contributor.get('teams')
+ teams = contributor.pop('teams')
try:
contributor['issues_opened'] = contributor.pop('issues')
contributor['num_commits'] = contributor.pop('contributions')
- contributor.pop('teams')
c, create = Contributor.objects.get_or_create(
**contributor
)
diff --git a/data/management/commands/create_org_cluster_map_and_activity_graph.py b/data/management/commands/create_org_cluster_map_and_activity_graph.py
new file mode 100644
index 00000000..c71647b1
--- /dev/null
+++ b/data/management/commands/create_org_cluster_map_and_activity_graph.py
@@ -0,0 +1,20 @@
+from django.core.management.base import BaseCommand
+
+from data.org_cluster_map_handler import handle as org_cluster_map_handler
+from activity.scraper import activity_json
+
+
+class Command(BaseCommand):
+ help = 'Create a cluster map using contributors geolocation'
+
+ def add_arguments(self, parser):
+ parser.add_argument('output_dir', nargs='?', type=str)
+
+ def handle(self, *args, **options):
+ output_dir = options.get('output_dir')
+ if not output_dir:
+ org_cluster_map_handler()
+ else:
+ org_cluster_map_handler(output_dir)
+ # Fetch & Store data for activity graph to be displayed on home-page
+ activity_json('static/activity-data.js')
diff --git a/data/migrations/0005_auto_20190801_1442.py b/data/migrations/0005_auto_20190801_1442.py
new file mode 100644
index 00000000..82fba40d
--- /dev/null
+++ b/data/migrations/0005_auto_20190801_1442.py
@@ -0,0 +1,33 @@
+# Generated by Django 2.1.7 on 2019-08-01 14:42
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('data', '0004_auto_20180809_2229'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='contributor',
+ name='followers',
+ field=models.IntegerField(default=None, null=True),
+ ),
+ migrations.AddField(
+ model_name='contributor',
+ name='location',
+ field=models.TextField(default=None, null=True),
+ ),
+ migrations.AddField(
+ model_name='contributor',
+ name='public_gists',
+ field=models.IntegerField(default=None, null=True),
+ ),
+ migrations.AddField(
+ model_name='contributor',
+ name='public_repos',
+ field=models.IntegerField(default=None, null=True),
+ ),
+ ]
diff --git a/data/migrations/0006_auto_20190801_1752.py b/data/migrations/0006_auto_20190801_1752.py
new file mode 100644
index 00000000..aa2d0ef6
--- /dev/null
+++ b/data/migrations/0006_auto_20190801_1752.py
@@ -0,0 +1,18 @@
+# Generated by Django 2.1.7 on 2019-08-01 17:52
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('data', '0005_auto_20190801_1442'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='contributor',
+ name='teams',
+ field=models.ManyToManyField(related_name='contributors', to='data.Team'),
+ ),
+ ]
diff --git a/data/migrations/0007_auto_20190802_2015.py b/data/migrations/0007_auto_20190802_2015.py
new file mode 100644
index 00000000..b0cd314b
--- /dev/null
+++ b/data/migrations/0007_auto_20190802_2015.py
@@ -0,0 +1,43 @@
+# Generated by Django 2.1.7 on 2019-08-02 20:15
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('data', '0006_auto_20190801_1752'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='contributor',
+ name='is_gci_participant',
+ field=models.BooleanField(default=False),
+ ),
+ migrations.AddField(
+ model_name='contributor',
+ name='oauth_completed',
+ field=models.BooleanField(default=False),
+ ),
+ migrations.AddField(
+ model_name='contributor',
+ name='statistics',
+ field=models.TextField(default=None, null=True),
+ ),
+ migrations.AddField(
+ model_name='contributor',
+ name='type_of_issues_worked_on',
+ field=models.TextField(default=None, null=True),
+ ),
+ migrations.AddField(
+ model_name='contributor',
+ name='updated_at',
+ field=models.TextField(default=None, null=True),
+ ),
+ migrations.AddField(
+ model_name='contributor',
+ name='working_on_issues_count',
+ field=models.TextField(default=None, null=True),
+ ),
+ ]
diff --git a/data/models.py b/data/models.py
index b6ba1a0e..c794f00e 100644
--- a/data/models.py
+++ b/data/models.py
@@ -13,9 +13,19 @@ class Contributor(models.Model):
name = models.TextField(default=None, null=True)
bio = models.TextField(default=None, null=True)
num_commits = models.IntegerField(default=None, null=True)
+ public_repos = models.IntegerField(default=None, null=True)
+ public_gists = models.IntegerField(default=None, null=True)
+ followers = models.IntegerField(default=None, null=True)
reviews = models.IntegerField(default=None, null=True)
issues_opened = models.IntegerField(default=None, null=True)
- teams = models.ManyToManyField(Team)
+ location = models.TextField(default=None, null=True)
+ teams = models.ManyToManyField(Team, related_name='contributors')
+ statistics = models.TextField(default=None, null=True)
+ type_of_issues_worked_on = models.TextField(default=None, null=True)
+ is_gci_participant = models.BooleanField(default=False)
+ working_on_issues_count = models.TextField(default=None, null=True)
+ updated_at = models.TextField(default=None, null=True)
+ oauth_completed = models.BooleanField(default=False)
def __str__(self):
return self.login
diff --git a/data/org_cluster_map_handler.py b/data/org_cluster_map_handler.py
new file mode 100644
index 00000000..baf0969b
--- /dev/null
+++ b/data/org_cluster_map_handler.py
@@ -0,0 +1,82 @@
+import os
+import json
+
+import logging
+
+import getorg
+
+from data.models import Contributor
+
+
+def handle(output_dir='cluster_map'):
+ """
+ Creates a organization cluster map using the contributors location
+ stored in the database
+ :param output_dir: Directory where all the required CSS and JS files
+ are copied by 'getorg' package
+ """
+ logger = logging.getLogger(__name__)
+ logger.info("'cluster_map/' is the default directory for storing"
+ " organization map related files. If arg 'output_dir'"
+ ' not provided it will be used as a default directory by'
+ " 'getorg' package.")
+
+ # For creating the organization map, the 'getorg' uses a 'Nominatim' named
+ # package which geocodes the contributor location and then uses that class
+ # to create the map. Since, we're not dealing with that function which use
+ # that 'Nominatim' package because we're fetching a JSON data and storing
+ # it in our db. Therefore, defining our own simple class that can aid us
+ # to create a cluster map.
+ class Location:
+
+ def __init__(self, longitude, latitude):
+ self.longitude = longitude
+ self.latitude = latitude
+
+ org_location_dict = {}
+
+ for contrib in Contributor.objects.filter(location__isnull=False):
+ user_location = json.loads(contrib.location)
+ location = Location(user_location['longitude'],
+ user_location['latitude'])
+ org_location_dict[contrib.login] = location
+ logger.debug(f'{contrib.login} location {user_location} added on map')
+ getorg.orgmap.output_html_cluster_map(org_location_dict,
+ folder_name=output_dir)
+
+ move_and_make_changes_in_files(output_dir)
+
+
+def move_and_make_changes_in_files(output_dir):
+ """
+ Move static files from 'output_dir' to django static folder which
+ is being required by the map.html which is being auto-generated
+ by getorg.
+ :param output_dir: Directory from where the files have to be moved
+ """
+
+ move_leaflet_dist_folder(output_dir)
+
+ os.rename(
+ src=get_file_path(os.getcwd(), output_dir, 'org-locations.js'),
+ dst=get_file_path(os.getcwd(), 'static', 'org-locations.js')
+ )
+
+ os.remove(get_file_path(os.getcwd(), output_dir, 'map.html'))
+
+
+def move_leaflet_dist_folder(output_dir):
+ source_path = get_file_path(os.getcwd(), output_dir, 'leaflet_dist')
+ destination_path = get_file_path(os.getcwd(), 'static', 'leaflet_dist')
+
+ # Remove existing leaflet_dir if exists
+ for root, dirs, files in os.walk(destination_path):
+ for file in files:
+ os.remove(os.path.join(destination_path, file))
+ os.rmdir(root)
+
+ os.renames(source_path, destination_path)
+
+
+def get_file_path(*args):
+ return '/'.join(args)
diff --git a/data/tests/test_contrib_data.py b/data/tests/test_contrib_data.py
index e820e413..88a20ac8 100644
--- a/data/tests/test_contrib_data.py
+++ b/data/tests/test_contrib_data.py
@@ -2,7 +2,9 @@
from django.test import TestCase
-from data.contrib_data import get_contrib_data
+from data.contrib_data import get_contrib_data, import_data
+from gamification.tests.test_management_commands import (
+ get_false_contributors_data)
class GetContribDataTest(TestCase):
@@ -10,3 +12,7 @@ class GetContribDataTest(TestCase):
def test_get_contrib_data(self):
with requests_mock.Mocker():
get_contrib_data()
+
+ def test_false_contributor_data(self):
+ for contrib in get_false_contributors_data():
+ import_data(contrib)
diff --git a/data/tests/test_issues.py b/data/tests/test_issues.py
index f94e23e3..b75bc041 100644
--- a/data/tests/test_issues.py
+++ b/data/tests/test_issues.py
@@ -2,7 +2,9 @@
from django.test import TestCase
-from data.issues import fetch_issues
+from data.issues import fetch_issues, import_issue
+from gamification.tests.test_management_commands import (
+ get_false_issues_data)
class FetchIssueTest(TestCase):
@@ -10,3 +12,7 @@ class FetchIssueTest(TestCase):
def test_fetch_issues(self):
with requests_mock.Mocker():
fetch_issues('GitHub')
+
+ def test_false_issue_data(self):
+ for issue in get_false_issues_data():
+ import_issue('github', issue)
diff --git a/data/tests/test_management_commands.py b/data/tests/test_management_commands.py
index f1309700..866616eb 100644
--- a/data/tests/test_management_commands.py
+++ b/data/tests/test_management_commands.py
@@ -32,8 +32,7 @@ def test_command_import_issues_data(self):
if not issues:
raise unittest.SkipTest(
'No record of issues from webservices')
- self.assertIn('testuser',
- [issue.author.login for issue in issues])
+ self.assertGreater(issues.count(), 0)
class ImportMergeRequestDataTest(TestCase):
@@ -47,5 +46,4 @@ def test_command_import_issues_data(self):
if not mrs:
raise unittest.SkipTest(
'No record of mrs from webservices')
- self.assertIn('testuser',
- [mr.author.login for mr in mrs])
+ self.assertGreater(mrs.count(), 0)
diff --git a/data/tests/test_merge_requests.py b/data/tests/test_merge_requests.py
index 3d4350a8..f0efdead 100644
--- a/data/tests/test_merge_requests.py
+++ b/data/tests/test_merge_requests.py
@@ -2,7 +2,8 @@
from django.test import TestCase
-from data.merge_requests import fetch_mrs
+from data.merge_requests import fetch_mrs, import_mr
+from gamification.tests.test_management_commands import (get_false_mrs_data)
class FetchMergeRequestTest(TestCase):
@@ -10,3 +11,7 @@ class FetchMergeRequestTest(TestCase):
def test_fetch_mrs(self):
with requests_mock.Mocker():
fetch_mrs('GitHub')
+
+ def test_false_mr_data(self):
+ for mr in get_false_mrs_data():
+ import_mr('github', mr)
diff --git a/data/tests/test_org_cluster_map_handler.py b/data/tests/test_org_cluster_map_handler.py
new file mode 100644
index 00000000..8199dcc0
--- /dev/null
+++ b/data/tests/test_org_cluster_map_handler.py
@@ -0,0 +1,22 @@
+from django.test import TestCase
+
+from data.models import Contributor
+from data.org_cluster_map_handler import handle as org_cluster_map_handler
+
+
+class CreateOrgClusterMapAndActivityGraphTest(TestCase):
+
+ @classmethod
+ def setUpTestData(cls):
+ Contributor.objects.create(login='test',
+ name='Test User',
+ location='{"latitude": 12.9,'
+ '"longitude": 77.8}')
+ Contributor.objects.create(login='testuser',
+ name='Test User 2')
+
+ def test_with_output_dir(self):
+ org_cluster_map_handler()
+
+ def test_without_output_dir(self):
+ org_cluster_map_handler(output_dir='org_map')
diff --git a/data/urls.py b/data/urls.py
index a3780aa2..6eb2a98a 100644
--- a/data/urls.py
+++ b/data/urls.py
@@ -1,7 +1,7 @@
from django.conf.urls import url
-from . import views
+from .views import ContributorsListView
urlpatterns = [
- url(r'^$', views.index, name='index'),
+ url(r'^$', ContributorsListView.as_view(), name='index'),
]
diff --git a/data/views.py b/data/views.py
index 40436c72..53cd8a3e 100644
--- a/data/views.py
+++ b/data/views.py
@@ -1,8 +1,16 @@
+from django.views.generic import TemplateView
+
+from community.views import get_header_and_footer
from data.models import Contributor
-from django.shortcuts import render
-def index(request):
- contributors = Contributor.objects.all()
- args = {'contributors': contributors}
- return render(request, 'contributors.html', args)
+class ContributorsListView(TemplateView):
+ template_name = 'contributors.html'
+
+ def get_context_data(self, **kwargs):
+ context = super().get_context_data(**kwargs)
+ context = get_header_and_footer(context)
+ contrib_objects = Contributor.objects.all()
+ context['contributors'] = contrib_objects.order_by('-num_commits',
+ 'name')
+ return context
diff --git a/gamification/tests/test_management_commands.py b/gamification/tests/test_management_commands.py
index 7519d3ad..ba795dc8 100644
--- a/gamification/tests/test_management_commands.py
+++ b/gamification/tests/test_management_commands.py
@@ -1,14 +1,20 @@
from django.core.management import call_command
from django.test import TestCase
+from data.issues import import_issue
+from community.git import get_org_name
+from data.merge_requests import import_mr
from gamification.models import (
Level,
Badge,
Participant,
BadgeActivity,
)
+from data.contrib_data import import_data
from data.newcomers import active_newcomers
+ORG_NAME = get_org_name()
+
class CreateConfigDataTest(TestCase):
@@ -79,6 +85,18 @@ class UpdateParticipantsTest(TestCase):
@classmethod
def setUpTestData(cls):
+ for contrib in get_false_contributors_data():
+ import_data(contrib)
+
+ for issue in get_false_issues_data():
+ import_issue('github', issue)
+
+ for mr in get_false_mrs_data():
+ import_mr('github', mr)
+
+ for contrib in get_false_active_newcomers():
+ Participant.objects.create(username=contrib['username'])
+
call_command('import_issues_data')
call_command('import_merge_requests_data')
call_command('create_config_data')
@@ -98,3 +116,204 @@ def test_command_update_particiapants_data(self):
number_of_badges = participant.badges.all().count()
self.assertEquals(number_of_badges, 2)
+
+
+def get_false_contributors_data():
+ return [
+ {
+ 'bio': '',
+ 'teams': [
+ f'{ORG_NAME} newcomers'
+ ],
+ 'reviews': 0,
+ 'issues': 0,
+ 'name': '',
+ 'login': 'testuser',
+ 'contributions': 1
+ },
+ {
+ 'bio': '',
+ 'teams': [
+ f'{ORG_NAME} newcomers'
+ ],
+ 'reviews': 0,
+ 'issues': 0,
+ 'name': '',
+ 'login': 'testuser',
+ 'contributions': 1
+ },
+ {
+ 'bio': '',
+ 'teams': [
+ ],
+ 'reviews': 0,
+ 'name': '',
+ 'login': 'testuser1',
+ 'contributions': 1
+ }
+ ]
+
+
+def get_false_issues_data():
+ return [
+ {
+ 'created_at': '2016-11-21T00:46:14',
+ 'hoster': 'github',
+ 'updated_at': '2017-12-21T00:00:48',
+ 'labels': [
+ 'status/duplicate'
+ ],
+ 'number': 1,
+ 'assignees': [],
+ 'repo_id': 254525111,
+ 'title': 'Test issue',
+ 'state': 'closed',
+ 'repo': f'{ORG_NAME}/{ORG_NAME}',
+ 'url': f'https://github.com/{ORG_NAME}/corobo/issues/585',
+ 'author': 'testuser'
+ },
+ {
+ 'created_at': '2016-11-21T00:46:14',
+ 'hoster': 'github',
+ 'updated_at': '2017-12-21T00:00:48',
+ 'labels': [
+ 'difficulty/newcomer',
+ 'type/bug'
+ ],
+ 'number': 3,
+ 'assignees': [],
+ 'repo_id': 254525111,
+ 'title': 'Test issue',
+ 'state': 'closed',
+ 'repo': f'{ORG_NAME}/{ORG_NAME}',
+ 'url': f'https://github.com/{ORG_NAME}/{ORG_NAME}/issues/1',
+ 'author': 'testuser1'
+ },
+ {
+ 'created_at': '2016-11-21T00:46:14',
+ 'hoster': 'github',
+ 'updated_at': '2017-12-21T00:00:48',
+ 'labels': [
+ 'difficulty/newcomer',
+ 'type/bug'
+ ],
+ 'number': 2,
+ 'assignees': [],
+ 'repo_id': 254525111,
+ 'title': 'Test issue',
+ 'state': 'closed',
+ 'repo': f'{ORG_NAME}/{ORG_NAME}',
+ 'url': f'https://github.com/{ORG_NAME}/{ORG_NAME}/issues/2',
+ 'author': 'testuser'
+ },
+ {
+ 'created_at': '2016-11-21T00:46:14',
+ 'hoster': 'github',
+ 'updated_at': '2017-12-21T00:00:48',
+ 'labels': [
+ 'difficulty/newcomer',
+ 'type/bug'
+ ],
+ 'number': 2,
+ 'assignees': [],
+ 'title': 'Test issue',
+ 'state': 'closed',
+ 'repo': 'test/test',
+ 'url': f'https://github.com/{ORG_NAME}/{ORG_NAME}/issues/3',
+ 'author': 'testuser1'
+ }
+ ]
+
+
+def get_false_mrs_data():
+ return [
+ {
+ 'created_at': '2016-02-21T05:04:25',
+ 'hoster': 'github',
+ 'ci_status': True,
+ 'labels': [
+ 'difficulty/newcomer',
+ 'type/bug'
+ ],
+ 'title': 'Test merge request-I',
+ 'number': 1625,
+ 'updated_at': '2016-04-21T12:06:19',
+ 'assignees': [],
+ 'repo_id': 254525111,
+ 'closes_issues': [
+ 2,
+ 3
+ ],
+ 'repo': f'{ORG_NAME}/{ORG_NAME}',
+ 'url': f'https://github.com/{ORG_NAME}/{ORG_NAME}/pull/1625',
+ 'state': 'merged',
+ 'author': 'testuser'
+ },
+ {
+ 'created_at': '2016-02-21T05:04:25',
+ 'hoster': 'github',
+ 'ci_status': True,
+ 'labels': [
+ 'status/STALE'
+ ],
+ 'title': 'Test merge request-II',
+ 'number': 1626,
+ 'updated_at': '2016-02-21T12:06:19',
+ 'assignees': [],
+ 'repo_id': 25452511,
+ 'closes_issues': [
+ ],
+ 'repo': f'{ORG_NAME}/{ORG_NAME}',
+ 'state': 'merged',
+ 'url': f'https://github.com/{ORG_NAME}/{ORG_NAME}/pull/1626',
+ 'author': 'testuser'
+ },
+ {
+ 'created_at': '2016-02-21T05:04:25',
+ 'hoster': 'github',
+ 'ci_status': True,
+ 'labels': [
+ 'difficulty/low',
+ 'type/bug'
+ ],
+ 'title': 'Test merge request-III',
+ 'number': 1626,
+ 'updated_at': '2016-02-21T12:06:19',
+ 'assignees': [
+ 'testuser',
+ 'testuser1'
+ ],
+ 'repo_id': 25452511,
+ 'closes_issues': [
+ ],
+ 'repo': f'{ORG_NAME}/{ORG_NAME}',
+ 'state': 'merged',
+ 'url': f'https://github.com/{ORG_NAME}/{ORG_NAME}/pull/1625',
+ 'author': 'testuser'
+ },
+ {
+ 'created_at': '2016-02-21T05:04:25',
+ 'hoster': 'github',
+ 'labels': [
+ 'difficulty/low',
+ 'type/bug'
+ ],
+ 'title': 'Test merge request-III',
+ 'number': 1626,
+ 'updated_at': '2016-02-21T12:06:19',
+ 'assignees': [],
+ 'repo_id': 25452511,
+ 'repo': f'{ORG_NAME}/{ORG_NAME}',
+ 'url': f'https://github.com/{ORG_NAME}/{ORG_NAME}/pull/1625',
+ 'closes_issues': [
+ ],
+ 'author': 'testuser1'
+ }
+ ]
+
+
+def get_false_active_newcomers():
+ return [
+ {'username': 'testuser'},
+ {'username': 'testuser1'}
+ ]
diff --git a/gamification/tests/test_views.py b/gamification/tests/test_views.py
index 64e270c7..fa7a1a22 100644
--- a/gamification/tests/test_views.py
+++ b/gamification/tests/test_views.py
@@ -25,4 +25,4 @@ def test_view_uses_correct_template(self):
def test_all_contributors_on_template(self):
resp = self.client.get(reverse('community-gamification'))
self.assertEqual(resp.status_code, 200)
- self.assertTrue(len(resp.context['participants']) == 10)
+ self.assertTrue(len(resp.context['gamification_results']) == 10)
diff --git a/gamification/views.py b/gamification/views.py
index 25b14243..d006b231 100644
--- a/gamification/views.py
+++ b/gamification/views.py
@@ -1,10 +1,14 @@
-from django.shortcuts import render
+from django.views.generic import TemplateView
+from community.views import get_header_and_footer
from gamification.models import Participant
-def index(request):
- Participant.objects.filter(username__startswith='testuser').delete()
- participants = Participant.objects.all()
- args = {'participants': participants}
- return render(request, 'gamification.html', args)
+class GamificationResults(TemplateView):
+ template_name = 'gamification.html'
+
+ def get_context_data(self, **kwargs):
+ context = super().get_context_data(**kwargs)
+ context = get_header_and_footer(context)
+ context['gamification_results'] = Participant.objects.all()
+ return context
diff --git a/gci/urls.py b/gci/urls.py
index a3780aa2..10e10974 100644
--- a/gci/urls.py
+++ b/gci/urls.py
@@ -3,5 +3,5 @@
from . import views
urlpatterns = [
- url(r'^$', views.index, name='index'),
+ url(r'^$', views.GCIStudentsList.as_view(), name='index'),
]
diff --git a/gci/views.py b/gci/views.py
index e9c97589..ceed5c12 100644
--- a/gci/views.py
+++ b/gci/views.py
@@ -1,11 +1,13 @@
-from django.http import HttpResponse
from datetime import datetime
from calendar import timegm
+
import logging
-import requests
+from django.views.generic import TemplateView
+
+from community.views import get_header_and_footer
+from data.models import Contributor
from .students import get_linked_students
-from .gitorg import get_logo
from .task import get_tasks
STUDENT_URL = (
@@ -15,75 +17,78 @@
)
-def index(request):
- logger = logging.getLogger(__name__ + '.index')
- try:
- get_tasks()
- except FileNotFoundError:
- logger.info('GCI data not available')
- s = ['GCI data not available']
- else:
- s = gci_overview()
-
- return HttpResponse('\n'.join(s))
-
-
-def gci_overview():
- logger = logging.getLogger(__name__ + '.gci_overview')
- linked_students = list(get_linked_students())
- if not linked_students:
- logger.info('No GCI students are linked')
- return ['No GCI students are linked']
-
- org_id = linked_students[0]['organization_id']
- org_name = linked_students[0]['organization_name']
- s = []
- s.append('')
-
- favicon = get_logo(org_name, 16)
- with open('_site/favicon.png', 'wb') as favicon_file:
- favicon_file.write(favicon)
-
- org_logo = get_logo(org_name)
- with open('_site/org_logo.png', 'wb') as org_logo_file:
- org_logo_file.write(org_logo)
-
- s.append('')
- s.append('')
- s.append('
').text( + 'GitHub: ' + gh_date.toUTCString()); + var last_fetched_gl_data_el = $('
').text( + 'GitLab: ' + gl_date.toUTCString()); + user_updated_el.append(last_fetched_gh_data_el); + user_updated_el.append(last_fetched_gl_data_el); + user_updated_el.css('justify-content', 'space-evenly'); + } + + function displayWorkingOnIssuesCount(data) { + var on_hoster_counts = data.working_on_issues_count; + var count_list_el = $('.count-list'); + count_list_el.empty(); + var github_issue_count_el = $('').text('GitHub: ' + + on_hoster_counts.github); + var gitlab_issue_count_el = $('').text('GitLab: ' + + on_hoster_counts.gitlab); + count_list_el.append(github_issue_count_el); + count_list_el.append(gitlab_issue_count_el); + } + + function setLabelCSSProperties(label_element, bg_color){ + label_element.css('background-color', bg_color); + label_element.css('color', 'white'); + label_element.css('border-radius', '10px'); + label_element.css('margin', '0 3px'); + label_element.css('padding', '5px'); + return label_element; + } + + function displayWorkedOnIssueLabels(data) { + var issue_labels = data.type_of_issues_worked_on; + var gh_issue_labels_el = $('.github-issue-labels'); + var gl_issue_labels_el = $('.gitlab-issue-labels'); + gh_issue_labels_el.empty(); + gl_issue_labels_el.empty(); + jQuery.each(issue_labels.github, function (label_name, color) { + var label_el = $('').text(label_name); + label_el = setLabelCSSProperties(label_el, '#'+color); + gh_issue_labels_el.append(label_el); + }); + jQuery.each(issue_labels.gitlab, function (label_name, color) { + var label_el = $('').text(label_name); + label_el = setLabelCSSProperties(label_el, color); + gl_issue_labels_el.append(label_el); + }); + } + + function get_options(title) { + return { + responsive: true, + maintainAspectRatio: false, + fill: true, + borderWidth: 3, + title: { + display: true, + text: title + }, + tooltips: { + mode: 'index', + intersect: false, + }, + hover: { + mode: 'nearest', + intersect: true + }, + scales: { + xAxes: [{ + stacked: true, + display: true, + scaleLabel: { + display: true, + } + }], + yAxes: [{ + stacked: true, + display: true, + scaleLabel: { + display: true, + labelString: 'Number' + } + }] + } + }; + } + + function get_dataset_properties(label, backgroundColor, data){ + return { + label: label, + backgroundColor: backgroundColor, + data: data, + }; + } + + function setRepositoryCanvasChart(stats) { + new Chart(repository_stats_canvas, { + type: 'bar', + data: { + labels: stats.repositories, + datasets: [ + get_dataset_properties("Commits", "RGBA(236,255,52,0.7)", + stats.commits), + get_dataset_properties("Reviews", "RGBA(236,151,52,0.7)", + stats.reviews), + get_dataset_properties("Issues Opened", "RGBA(178, 191, 0, 0.7)", + stats.issues_opened), + get_dataset_properties("Issues Assigned", "RGBA(178, 52, 237, 0.7)", + stats.assigned_issues), + get_dataset_properties("Issues Closed", "RGBA(255, 52, 61, 0.7)", + stats.issues_closed), + get_dataset_properties("Merge Requests Opened", "RGBA(255, 190," + + " 217, 0.7)", stats.merge_requests_opened), + get_dataset_properties("Unmerged Merge Requests", "RGBA(87, 190," + + " 138, 0.7)", stats.unmerged_merge_requests), + ] + }, + options: get_options('Repository-Wise Statistics') + }); + } + + function setCommitsAndReviewsChart(commitLabels, commitData, + reviewLabels, reviewData) { + + commitsChart = new Chart(commits_canvas, { + type: 'bar', + data: { + labels: commitLabels, + datasets: [ + get_dataset_properties("Commits Activity", "RGBA(87, 190, 217," + + " 0.7)", commitData) + ] + }, + options: get_options('Commits Activity') + }); + + reviewsChart = new Chart(reviews_canvas, { + type: 'bar', + data: { + labels: reviewLabels, + datasets: [ + get_dataset_properties("Reviews Activity", "RGBA(87, 190, 138," + + " 0.7)", reviewData) + ] + }, + options: get_options('Review Activity'), + }); + } + + function setIssuesCanvasChart(data){ + var data_labels = new Set(); + jQuery.each(data, function (subtype, display_filters){ + jQuery.each(display_filters, function (filter, value) { + data_labels.add(filter); + }); + }); + data_labels.forEach(function (data_label) { + jQuery.each(data, function (subtype){ + if(isNaN(data[subtype][data_label])){ + data[subtype][data_label] = 0; + } + }); + }); + data_labels = Array.from(data_labels); + issuesChart = new Chart(issues_canvas, { + type: 'bar', + data: { + labels: data_labels, + datasets: [ + get_dataset_properties("Closed Issues Activity","RGBA(87, 190," + + " 138, 0.7)", Object.values(data.closed)), + get_dataset_properties("Assigned Issues Activity","RGBA(255, 224," + + " 217, 0.8)", Object.values(data.assigned)), + get_dataset_properties("Opened Issues Activity", "RGBA(236, 151," + + " 52, 0.6)", data.open !== undefined?Object.values(data.open): + Object.values(data.opened)) + ] + }, + options: get_options('Issues Activity') + }); + } + + function setMergeRequestsCanvasChart(data){ + var data_labels = new Set(); + jQuery.each(data, function (subtype, display_filters){ + jQuery.each(display_filters, function (filter, value) { + data_labels.add(filter); + }); + }); + data_labels.forEach(function (data_label) { + jQuery.each(data, function (subtype){ + if(isNaN(data[subtype][data_label])){ + data[subtype][data_label] = 0; + } + }); + }); + data_labels = Array.from(data_labels); + mergeRequestsChart = new Chart(merge_requests_canvas, { + type: 'line', + data: { + labels: data_labels, + datasets: [ + get_dataset_properties("Merged PRs Activity","RGBA(255, 224, 217," + + " 0.5)",data.merged !==undefined?Object.values(data.merged): + new Array(data_labels.size)), + get_dataset_properties("Unmerged PRs Activity","RGBA(178, 52, 237," + + " 0.7)", data.unmerged !== undefined?Object.values(data.unmerged): + new Array(data_labels.size)), + get_dataset_properties("Opened PRs Activity", "RGBA(236, 151, 52," + + " 0.9)", data.open !== undefined?Object.values(data.open): + (data.opened !== undefined?Object.values(data.opened): + new Array(data_labels.size))) + ] + }, + options: get_options('Merge Requests Activity') + }); + } + + function setCanvasCharts(data){ + setCommitsAndReviewsChart( + Object.keys(data.commits), Object.values(data.commits), + Object.keys(data.reviews), Object.values(data.reviews) + ); + setIssuesCanvasChart(data.issues); + setMergeRequestsCanvasChart(data.merge_requests); + } + + function toggleCanvasDisplays(data){ + if (Object.values(data.reviews).length === 0) { + $('.bar-reviews-canvas').css('display', 'none'); + } + else { + $('.bar-reviews-canvas').css('display', 'block'); + } + if (Object.values(data.issues).length === 0) { + $('.bar-issues-canvas').css('display', 'none'); + } + else { + $('.bar-issues-canvas').css('display', 'block'); + } + if (Object.values(data.commits).length === 0) { + $('.bar-commits-canvas').css('display', 'none'); + } + else { + $('.bar-commits-canvas').css('display', 'block'); + } + if (Object.values(data.merge_requests).length === 0) { + $('.line-merge-requests-canvas').css('display', 'none'); + } + else { + $('.line-merge-requests-canvas').css('display', 'block'); + } + } + + function getWeekNumber(date) { + var d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), + date.getDate())); + var dayNum = d.getUTCDay() || 7; + d.setUTCDate(d.getUTCDate() + 4 - dayNum); + var yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1)); + return Math.ceil((((d - yearStart) / 86400000) + 1) / 7); + } + + function getMonthNameFromWeekNumber(year, week_number) { + var total_ms_count = ((week_number * 7) - 1) * 86400000; + var current_date = new Date(); + var d = new Date(Date.UTC(year, current_date.getMonth(), + current_date.getDate())); + var dayNum = d.getUTCDay() || 7; + d.setUTCDate(d.getUTCDate() + 4 - dayNum); + var yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1)); + var date = new Date(total_ms_count + yearStart.getTime()); + return month_names[date.getMonth()]; + } + + function get_last_twelve_months_begin_end_weeks() { + var current_date = new Date(); + var last_year_date = new Date( + current_date.getFullYear() - 1, current_date.getMonth(), + current_date.getDate() + ); + return [current_date.getFullYear(), getWeekNumber(current_date), + getWeekNumber(last_year_date)]; + } + + function get_last_twelve_weeks_begin_end() { + var current_date = new Date(); + var current_week = getWeekNumber(current_date); + var last_twelfth_week = 1; + if (current_week > 12) { + last_twelfth_week = current_week - 12; + } else { + var week_difference = 12 - current_week; + var month = Math.trunc((11 - week_difference) / 4); + var last_year_date = new Date( + current_date.getFullYear() - 1, month, + current_date.getDate()); + last_twelfth_week = getWeekNumber(last_year_date); + } + return [current_date.getFullYear(), current_week, last_twelfth_week]; + } + + function updateCharts(data, hoster_type, display_type) { + if(commitsChart){ + commitsChart.destroy(); + reviewsChart.destroy(); + issuesChart.destroy(); + mergeRequestsChart.destroy(); + } + var hoster_stats = data.statistics[hoster_type]; + var charts_data = { + issues: new Map(), + commits: new Map(), + merge_requests: new Map(), + reviews: new Map(), + }; + var issue_stats, commits_stats, prs_stats, reviews_stats, current_year, + current_week; + if (display_type === "yearly") { + + jQuery.each(hoster_stats, function (repo_name, repo_stats) { + issue_stats = repo_stats.issues; + jQuery.each(issue_stats, function (issue_type, years) { + if (charts_data.issues[issue_type] === undefined) { + charts_data.issues[issue_type] = new Map(); + } + jQuery.each(years, function (year, week_numbers) { + jQuery.each(week_numbers, function ( + week_number, weekdays) { + jQuery.each(weekdays, function ( + weekday, issues) { + if (isNaN(charts_data.issues[issue_type][year])) { + charts_data.issues[issue_type][year] = 0; + } + charts_data.issues[issue_type][year] += + Object.keys(issues).length; + }); + + }); + }); + }); + + prs_stats = repo_stats.prs || repo_stats.merge_requests; + jQuery.each(prs_stats, function (pr_type, years) { + if (charts_data.merge_requests[pr_type] === undefined) { + charts_data.merge_requests[pr_type] = new Map(); + } + jQuery.each(years, function (year, week_numbers) { + jQuery.each(week_numbers, function ( + week_number, weekdays) { + jQuery.each(weekdays, function (weekday, mrs) { + if (isNaN(charts_data.merge_requests[pr_type][year])) { + charts_data.merge_requests[pr_type][year] = 0; + } + charts_data.merge_requests[pr_type][year] += + Object.keys(mrs).length; + }); + + }); + }); + }); + + commits_stats = repo_stats.commits; + jQuery.each(commits_stats, function (year, week_numbers) { + jQuery.each(week_numbers, function (week_number, weekdays){ + jQuery.each(weekdays, function (weekday, commits_done){ + if (isNaN(charts_data.commits[year])) { + charts_data.commits[year] = 0; + } + charts_data.commits[year] += + Object.keys(commits_done).length; + }); + }); + }); + + reviews_stats = repo_stats.reviews; + jQuery.each(reviews_stats, function (year, week_numbers) { + jQuery.each(week_numbers, function (week_number, weekdays){ + jQuery.each(weekdays, function (weekday, reviews_done){ + if (isNaN(charts_data.reviews[year])) { + charts_data.reviews[year] = 0; + } + charts_data.reviews[year] += + Object.keys(reviews_done).length; + }); + }); + }); + }); + } + + else if (display_type === "monthly") { + var last_twelve_months_weeks = get_last_twelve_months_begin_end_weeks(); + current_year = last_twelve_months_weeks[0]; + current_week = last_twelve_months_weeks[1]; + var last_year_week = last_twelve_months_weeks[2]; + + jQuery.each(hoster_stats, function (repo_name, repo_stats) { + + issue_stats = repo_stats.issues; + jQuery.each(issue_stats, function (issue_type, years) { + if (charts_data.issues[issue_type] === undefined) { + charts_data.issues[issue_type] = new Map(); + } + jQuery.each(years, function (year, week_numbers) { + jQuery.each(week_numbers, function (week_number, weekdays) { + year = parseInt(year); + week_number = parseInt(week_number); + if ( + (current_year === year && + week_number <= current_week) || + (year === (current_year - 1) && + week_number >= last_year_week)) { + jQuery.each(weekdays, function (weekday, + issues) { + var month_name + = getMonthNameFromWeekNumber(year, week_number); + var key = month_name + '\'' + year%100; + if (isNaN(charts_data.issues[issue_type][key])) { + charts_data.issues[issue_type][key] = 0; + } + charts_data.issues[issue_type][key] += + Object.keys(issues).length; + }); + } + }); + }); + }); + + prs_stats = repo_stats.prs || repo_stats.merge_requests; + jQuery.each(prs_stats, function (pr_type, years) { + if (charts_data.merge_requests[pr_type] === undefined) { + charts_data.merge_requests[pr_type] = new Map(); + } + jQuery.each(years, function (year, week_numbers) { + jQuery.each(week_numbers, function (week_number, weekdays) { + year = parseInt(year); + week_number = parseInt(week_number); + if ((current_year === year && week_number <= current_week) || + (year === (current_year - 1) && + week_number >= last_year_week)) { + jQuery.each(weekdays, function (weekday, mrs) { + var month_name + = getMonthNameFromWeekNumber(year, week_number); + var key = month_name + '\'' + year%100; + if (isNaN(charts_data.merge_requests[pr_type][key])) { + charts_data.merge_requests[pr_type][key] = 0; + } + charts_data.merge_requests[pr_type][key] += + Object.keys(mrs).length; + }); + } + }); + }); + }); + + commits_stats = repo_stats.commits; + jQuery.each(commits_stats, function (year, week_numbers) { + jQuery.each(week_numbers, function (week_number, weekdays) { + year = parseInt(year); + week_number = parseInt(week_number); + if ((current_year === year && week_number <= current_week) || + (year === (current_year - 1) && week_number >= last_year_week)) { + jQuery.each(weekdays, function (weekday, commits_done) { + var month_name + = getMonthNameFromWeekNumber(year, week_number); + var key = month_name + '\'' + year%100; + if (isNaN(charts_data.commits[key])) { + charts_data.commits[key] = 0; + } + charts_data.commits[key] += Object.keys(commits_done).length; + }); + } + }); + }); + + reviews_stats = repo_stats.reviews; + jQuery.each(reviews_stats, function (year, week_numbers) { + jQuery.each(week_numbers, function (week_number, weekdays) { + year = parseInt(year); + week_number = parseInt(week_number); + if ((current_year === year && week_number <= current_week) || + (year === (current_year - 1) && week_number >= last_year_week)) { + jQuery.each(weekdays, function (weekday, reviews_done) { + var month_name = getMonthNameFromWeekNumber(year, week_number); + var key = month_name + '\'' + year%100; + if (isNaN(charts_data.reviews[key])) { + charts_data.reviews[key] = 0; + } + charts_data.reviews[key] += Object.keys(reviews_done).length; + }); + } + }); + }); + }); + } + + else { + + var last_twelve_weeks = get_last_twelve_weeks_begin_end(); + current_year = last_twelve_weeks[0]; + current_week = last_twelve_weeks[1]; + var last_twelfth_week = last_twelve_weeks[2]; + + jQuery.each(hoster_stats, function (repo_name, repo_stats) { + + issue_stats = repo_stats.issues; + jQuery.each(issue_stats, function (issue_type, years) { + if (charts_data.issues[issue_type] === undefined) { + charts_data.issues[issue_type] = new Map(); + } + jQuery.each(years, function (year, week_numbers) { + jQuery.each(week_numbers, function (week_number, weekdays) { + if ((current_year === parseInt(year) && + last_twelfth_week <= parseInt(week_number) && + parseInt(week_number) <= current_week && + current_week >= 12) || + (parseInt(year) === (current_year - 1) && + parseInt(week_number) >= last_twelfth_week && + current_week < 12)) { + jQuery.each(weekdays, function (weekday, issues) { + var key = 'Week-' + week_number + ',' + year; + if (isNaN(charts_data.issues[issue_type][key])) { + charts_data.issues[issue_type][key] = 0; + } + charts_data.issues[issue_type][key] += + Object.keys(issues).length; + }); + } + }); + }); + }); + + prs_stats = repo_stats.prs || repo_stats.merge_requests; + jQuery.each(prs_stats, function (pr_type, years) { + if (charts_data.merge_requests[pr_type] === undefined) { + charts_data.merge_requests[pr_type] = new Map(); + } + jQuery.each(years, function (year, week_numbers) { + jQuery.each(week_numbers, function (week_number, weekdays) { + if ((current_year === parseInt(year) && + last_twelfth_week <= parseInt(week_number) && + parseInt(week_number) <= current_week && + current_week >= 12) || + (parseInt(year) === (current_year - 1) && + parseInt(week_number) >= last_twelfth_week && + current_week < 12)) { + jQuery.each(weekdays, function (weekday, mrs) { + var key = 'Week-' + week_number + ',' + year; + if (isNaN(charts_data.merge_requests[pr_type][key])) { + charts_data.merge_requests[pr_type][key] = 0; + } + charts_data.merge_requests[pr_type][key] += + Object.keys(mrs).length; + }); + } + }); + }); + }); + + commits_stats = repo_stats.commits; + jQuery.each(commits_stats, function (year, week_numbers) { + jQuery.each(week_numbers, function (week_number, weekdays) { + if ((current_year === parseInt(year) && + last_twelfth_week <= parseInt(week_number) && + parseInt(week_number) <= current_week && + current_week >= 12) || + (parseInt(year) === (current_year - 1) && + parseInt(week_number) >= last_twelfth_week && + current_week < 12)) { + jQuery.each(weekdays, function (weekday, commits_done) { + var key = 'Week-' + week_number + ',' + year; + if (isNaN(charts_data.commits[key])) { + charts_data.commits[key] = 0; + } + charts_data.commits[key] += Object.keys(commits_done).length; + }); + } + }); + }); + + reviews_stats = repo_stats.reviews; + jQuery.each(reviews_stats, function (year, week_numbers) { + jQuery.each(week_numbers, function (week_number, weekdays) { + if ((current_year === parseInt(year) && + last_twelfth_week <= parseInt(week_number) && + parseInt(week_number) <= current_week && + current_week >= 12) || + (parseInt(year) === (current_year - 1) && + parseInt(week_number) >= last_twelfth_week && + current_week < 12)) { + jQuery.each(weekdays, function (weekday, reviews_done) { + var key = 'Week-' + week_number + ',' + year; + if (isNaN(charts_data.reviews[key])) { + charts_data.reviews[key] = 0; + } + charts_data.reviews[key] += Object.keys(reviews_done).length; + }); + } + }); + }); + }); + } + + toggleCanvasDisplays(charts_data); + setCanvasCharts(charts_data); + } + + function addEventListenerToStatisticsSelector(contrib_data){ + hoster_selector.on('change', function () { + updateCharts(contrib_data, hoster_selector.val(), stats_divider.val()); + }); + stats_divider.on('change', function () { + updateCharts(contrib_data, hoster_selector.val(), stats_divider.val()); + }); + updateCharts(contrib_data, hoster_selector.val(), stats_divider.val()); + } + + function getContributionsCount(data){ + var contributions_count = 0; + jQuery.each(data, function (years, contributions) { + jQuery.each(contributions, function (weeknumbers, weekdays) { + contributions_count += Object.keys(weekdays).length; + }); + }); + return contributions_count; + } + + function createRepositoryCanvasChart(data){ + var repositories_stats = { + repositories: [], commits: [], reviews: [], issues_opened: [], + assigned_issues: [], issues_closed: [], merge_requests_opened: [], + unmerged_merge_requests: [] + }; + var github_data = data.statistics.github, + gitlab_data = data.statistics.gitlab; + jQuery.each(github_data, function (repository, stats) { + repositories_stats.repositories.push(repository); + repositories_stats.commits.push(getContributionsCount(stats.commits)); + repositories_stats.reviews.push(getContributionsCount(stats.reviews)); + repositories_stats.issues_opened.push(getContributionsCount( + stats.issues === undefined? new Map(): stats.issues.open + )); + repositories_stats.assigned_issues.push(getContributionsCount( + stats.issues === undefined? new Map(): stats.issues.assigned + )); + repositories_stats.issues_closed.push(getContributionsCount( + stats.issues === undefined? new Map(): stats.issues.closed + )); + repositories_stats.merge_requests_opened.push(getContributionsCount( + stats.prs === undefined? new Map(): stats.prs.open + )); + repositories_stats.unmerged_merge_requests.push(getContributionsCount( + stats.prs === undefined? new Map(): stats.prs.unmerged + )); + }); + + jQuery.each(gitlab_data, function (repository, stats) { + repositories_stats.repositories.push(repository); + repositories_stats.commits.push(getContributionsCount(stats.commits)); + repositories_stats.reviews.push(getContributionsCount(stats.reviews)); + repositories_stats.issues_opened.push(getContributionsCount( + stats.issues === undefined? new Map(): stats.issues.opened + )); + repositories_stats.assigned_issues.push(getContributionsCount( + stats.issues === undefined? new Map(): stats.issues.assigned + )); + repositories_stats.issues_closed.push(getContributionsCount( + stats.issues === undefined? new Map(): stats.issues.closed + )); + repositories_stats.merge_requests_opened.push(getContributionsCount( + stats.merge_requests === undefined? new Map(): + stats.merge_requests.opened + )); + repositories_stats.unmerged_merge_requests.push(getContributionsCount( + stats.merge_requests === undefined? new Map(): + stats.merge_requests.unmerged + )); + }); + setRepositoryCanvasChart(repositories_stats); + } + + $('select').formSelect(); + $('.user-statistics-option').on('click', function () { + var username = $(this).attr('username'); + user_statistics_display.css('display', 'block'); + $.getJSON("/static/contributors-data.json", function (data) { + var contrib_data = data[username]; + contrib_data.statistics = $.parseJSON(contrib_data.statistics); + contrib_data.type_of_issues_worked_on = $.parseJSON( + contrib_data.type_of_issues_worked_on + ); + contrib_data.working_on_issues_count = $.parseJSON( + contrib_data.working_on_issues_count + ); + contrib_data.updated_at = $.parseJSON(contrib_data.updated_at); + createRepositoryCanvasChart(contrib_data); + addEventListenerToStatisticsSelector(contrib_data); + displayWorkedOnIssueLabels(contrib_data); + displayWorkingOnIssuesCount(contrib_data); + displayDataUpdatedDates(contrib_data); + }).fail(function (data, textStatus, error) { + console.error("Request Failed: " + textStatus + ", " + error); + }); + $('.close-statistics').on('click', function () { + user_statistics_display.css('display', 'none'); + }); + }); +}); diff --git a/static/js/contributors.js b/static/js/contributors.js new file mode 100644 index 00000000..beaa9d64 --- /dev/null +++ b/static/js/contributors.js @@ -0,0 +1,77 @@ +$(document).ready(function(){ + var search_input = $('#search'); + var close_icon = $('.contributors-section .fa-close'); + var results_body = $('.search-results-tbody'); + var searched_keyword = null; + + function appendChildren(element, username, el_result_value, + hide_all_contributors){ + var result_td = $('
{{ key }}: {{ value }}
+ {% endfor %}{# for key, value in build_info.items #} +Great! Wait for build logs to get displayed.
+ {% else %} +No logs found! Please run '.ci/build.sh' on the project.
+ {% endif %}{# if logs_stored #} +login: {{ contributor.login }}
-name: {{ contributor.name }}
-bio: {{ contributor.bio }}
-num_commits: {{ contributor.num_commits }}
-reviews: {{ contributor.reviews }}
-issues_opened: {{ contributor.issues_opened }}
-teams: - {% for team in contributor.teams.all %} - {{ team.name }} - {% endfor %}{# for team in contributor.teams.all #} -
-+ Contributor's who've been putting their hard-work to make {{ org.name }} best of its + own. Thanks to all contributors to make {{ org.name }} what is it today. +
+Search Results | +
---|
+ No results found! + | +
{{ contributor.num_commits }}
+Commits
+{{ contributor.reviews }}
+Reviews
+{{ contributor.issues_opened }}
+Issues
+Note: All the datetime is in UTC
-Username: {{ participant.username }}
- - - -Score: {{ participant.score }}
-Level: {{ participant.level.name }}
-Activities Performed: - {% for activity in participant.activities.all %} -
{{ forloop.counter }}. {{ activity.name }}, performed_at: - {{ activity.performed_at }} updated_at: {{ activity.updated_at }} -
- {% endfor %}{# for activity in participant.activities.all #} -Badges Earned: - {% for badge in participant.badges.all %} -
{{ forloop.counter }}.{{ badge.name }}
- {% endfor %}{# for badge in participant.badges.all #} - +{% extends 'base.html' %} +{% load staticfiles %} +{% block title %} + Community | Gamification Leaderboard +{% endblock %} + +{% block add_css_files %} + + + +{% endblock %} + +{% block add_js_files %} + +{% endblock %} + +{% block main-content %} + ++ The leaderboard is based upon the gamification system which automates the + recognition of activities performed by community contributors. Based on activities + performed, various parameters are calculated. +
+Search Results | +
---|
+ No results found! + | +
+ Hello, World! {{ org.name }} has been participating in GCI (Google Code-In) from last few years and will + be participating in coming years too. Following are the GCI students who participated in GCI with {{ org.name }} + organization. +
+{{ student.bio }}
+ {% endif %}{# if student.bio #} +ID:
+ {{ student.id }} +Participation year: {{ student.program_year }}
+Repos:
+{{ student.public_repos }}
+Gists:
+{{ student.public_gists }}
+Followers:
+{{ student.followers }}
+{{ org.about }}
+{{ quote_details.quote }}
+Rank | +Username | +Gamification Score | +
---|---|---|
{{ index|add:"1" }} | +{{ contrib.username }} | +{{ contrib.score }} | +
login: {{ participant.login }}
-name: {{ participant.name }}
-score: {{ participant.score|floatformat:2 }}
-rank: {{ participant.rank }}
-trend: {{ participant.trend }}
-received:
-number of positive reactions: {{ participant.pos_in }}
-- weighted positive reactions: - {{ participant.weighted_pos_in|floatformat:2 }} -
-number of negative reactions: {{ participant.neg_in }}
-- weighted negative reactions: - {{ participant.weighted_neg_in|floatformat:2 }} -
-give away:
-number of positive reactions: {{ participant.pos_out }}
-number of negative reactions: {{ participant.neg_out }}
-- negative point offset: - {{ participant.offset|floatformat:2 }} -
-weight_factor: {{ participant.weight_factor|floatformat:2 }}
-+ Contributor meta-review related details are calculated based upon the + GitHub reactions made by that contributor on any issue or merge request + within an organization, for example - {{ org.name }}. +
+Search Results | +
---|
+ No results found! + | +