diff --git a/community/urls.py b/community/urls.py index a171c0e8..42384cb3 100644 --- a/community/urls.py +++ b/community/urls.py @@ -11,7 +11,7 @@ from gci.feeds import LatestTasksFeed as gci_tasks_rss from ci_build.view_log import BuildLogsView from data.views import ContributorsListView -from gamification.views import index as gamification_index +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 @@ -193,7 +193,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/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..0772782c 100644 --- a/gamification/views.py +++ b/gamification/views.py @@ -1,10 +1,79 @@ -from django.shortcuts import render +import json -from gamification.models import Participant +from django.views.generic import TemplateView +from community.views import get_header_and_footer +from gamification.models import Participant, Level, Badge -def index(request): - Participant.objects.filter(username__startswith='testuser').delete() + +class GamificationResults(TemplateView): + template_name = 'gamification.html' participants = Participant.objects.all() - args = {'participants': participants} - return render(request, 'gamification.html', args) + + def get_users_username(self, users): + """ + :param users: A Queryset, with a field username + :return: A list of usernames + """ + usernames = list() + for user in users: + usernames.append(user.username) + return usernames + + def group_participants_by_score(self): + """ + Divide the participants according to their scores. For example, if + there are 10 contributors who have different scores and there are + possibly 4 ranges i.e. score_gt 80, score_between (70,80), + score_between (60,70) and score_lt 60. So, divide them and put them + in their respective lists. + :return: A Dict, with key as score_range and value a list of + contributors username + """ + scores = set() + for contrib in self.participants: + scores.add(contrib.score) + + scores = list(scores) + scores.sort() + + try: + min_score, max_score = scores[0], scores[-1] + except IndexError: + return dict() + + difference_bw_groups_score = int(max_score/5) + score_ranges = [ + min_score + i * difference_bw_groups_score for i in range(6) + ] + score_ranges[-1] += max_score % 5 + + grouped_participants = dict() + for index, score in enumerate(score_ranges[1:]): + begin_score, end_score = score_ranges[index], score + + filtered_participants = self.participants.filter( + score__range=[begin_score, end_score] + ) + + if begin_score == min_score: + grp_lvl = f'<{end_score}' + elif end_score < max_score: + grp_lvl = f'>={begin_score} and <{end_score}' + else: + grp_lvl = f'>={begin_score}' + + grouped_participants[grp_lvl] = json.dumps( + self.get_users_username(filtered_participants) + ) + return grouped_participants + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context = get_header_and_footer(context) + context['gamification_results'] = self.participants + context['levels'] = Level.objects.all() + context['badges'] = Badge.objects.all() + context['grouped_participants'] = self.group_participants_by_score() + + return context diff --git a/static/css/gamification.css b/static/css/gamification.css new file mode 100644 index 00000000..cf354e03 --- /dev/null +++ b/static/css/gamification.css @@ -0,0 +1,165 @@ +.badge-filter { + width: 150px; +} + +.bottom-center { + position: absolute; + width: 100px; + bottom: 2%; + left: 50%; + transform: translate(-50%, -50%); +} + +.clear-filters { + cursor: pointer; +} + +.gamifier-details { + max-width: 79%; + display: flex; +} + +.gamifier-details-part-1, +.gamifier-details-part-2, +.gamifier-details-part-3 { + max-width: 44%; + max-height: 80%; + padding: 10px 5px; +} + +.gamifier-card { + width: 100%; + min-height: 380px; +} + +.gamifier-image { + width: 20%; +} + +.gamifier-image img{ + width: 90%; + margin-right: 0; + margin-left: 1%; + min-width: 100px; + +} + +.github-icon { + color: white; + background-color: black; + border-radius: 100px; +} + +.gitlab-icon { + color: #e33834; + border-radius: 100px; +} + +.filter-btn { + width: 250px; + margin-top: 3%; + margin-left: 3%; + z-index: 0; +} + +.filter-btn .btn-large { + border-radius: 100px; +} + +.filter-btn .btn { + text-transform: none; + border-radius: 100px; + box-shadow: 0 0 25px 2px black; +} + +.filters-option { + margin: 3% auto auto; + display: none; + -webkit-animation-duration: 1s; + animation-duration: 1s; + -webkit-animation-fill-mode: both; + animation-fill-mode: both; +} + +.filter-select-fields { + width: 50%; + min-width: 350px; + box-shadow: 0 0 15px 2px black; + border-radius: 20px; +} + +.level-filter { + width: 145px; +} + +.no-contribs-found { + display: none; + justify-content: center; +} + +.no-contribs-found h5 { + margin: 0; +} + +.score-filter { + width: 175px; +} + +.social-icons { + font-size: 1.5em; +} + +@media only screen and (max-width: 890px){ + + .gamifier-card { + max-width: 100%; + width: auto; + margin: 10px; + } + + .gamifier-details { + max-width: 100%; + padding: 0 10px; + } + + .gamifier-image { + margin: auto; + width: 35%; + } + + .bottom-center { + bottom: 2%; + } + + @media only screen and (max-width: 526px){ + + .gamifier-details-part-3 { + display: none; + } + + .gamifier-details-part-1, + .gamifier-details-part-2 { + max-width: 50%; + } + + .gamifier-image { + width: 50%; + } + + } +} + +@-webkit-keyframes fade-in { + 0% {opacity: 0;} + 100% {opacity: 1;} +} + +@keyframes fade-in { + 0% {opacity: 0;} + 100% {opacity: 1;} +} + +.fade-in { + -webkit-animation-name: fade-in; + animation-name: fade-in; +} diff --git a/static/js/contributors.js b/static/js/contributors.js index beaa9d64..16b04828 100644 --- a/static/js/contributors.js +++ b/static/js/contributors.js @@ -35,8 +35,12 @@ $(document).ready(function(){ else { $('.search-results').css('display', 'block'); close_icon.css('display', 'block'); - var search_by_login = $('[login^=' + searched_keyword +']'); - var search_by_name = $('[name^=' + searched_keyword +']'); + var search_by_login = $( + '.contributor-card[login^=' + searched_keyword +']' + ); + var search_by_name = $( + '.contributor-card[name^=' + searched_keyword +']' + ); var results_tbody_tr = $('.search-results-tbody tr'); results_tbody_tr.remove(); if(search_by_login.length + search_by_name.length === 0 ){ diff --git a/static/js/gamification.js b/static/js/gamification.js new file mode 100644 index 00000000..9d096ab2 --- /dev/null +++ b/static/js/gamification.js @@ -0,0 +1,150 @@ +$(document).ready(function () { + + var search_input = $('#search'); + var score_range_selector = $('.score-range-selector'); + var level_selector = $('.level-selector'); + var badge_selector = $('.badge-selector'); + var filter_button = $('.filter-btn .btn-large'); + + function toggleGamificationCards(display){ + $('.gamifier-card').css('display', display); + } + + function toggleFilterButton(disabled){ + if(disabled){ + filter_button.attr('disabled', 'disabled'); + } + else { + filter_button.removeAttr('disabled'); + } + } + + search_input.on('keypress keyup', function () { + var value = search_input.val(); + if(value){ + toggleFilterButton(true); + } + else { + toggleFilterButton(false); + } + }); + + $('.fa-close').on('click', function () { + toggleFilterButton(false); + }); + + function getContributorsBasedOnFilterSelector(id_prefix, filter_option){ + var filtered_users = []; + if(filter_option.val()){ + var spans = Object.values( + $('.contributors-cards #'+id_prefix+'-'+filter_option.val()) + ); + if(spans.length > 0){ + spans.forEach(function (span) { + if(span.attributes !== undefined){ + filtered_users.push(span.attributes.login.value); + } + }); + } + } + return filtered_users; + } + + function getCommonContribs(in_range_users, at_level_users, has_badges_users){ + var all_contribs = []; + if (score_range_selector.val() !== "[]"){ + all_contribs = in_range_users; + } + if(level_selector.val() !== ""){ + if (all_contribs.length === 0){ + all_contribs = at_level_users; + } + else { + all_contribs = all_contribs.filter(function(username){ + return at_level_users.includes(username); + } + ); + } + } + if (badge_selector.val() !== ""){ + if (all_contribs.length === 0){ + all_contribs = has_badges_users; + } + else { + all_contribs = all_contribs.filter(function(username){ + return has_badges_users.includes(username); + } + ); + } + } + return all_contribs; + } + + function filterUsersAndToggleCards(){ + var in_range_users = JSON.parse(score_range_selector.val()); + var at_level_users = getContributorsBasedOnFilterSelector( + 'level', level_selector + ); + var has_badges_users = getContributorsBasedOnFilterSelector( + 'badge', badge_selector + ); + + if(score_range_selector.val() === "[]" && level_selector.val() === "" && + badge_selector.val() === ""){ + toggleGamificationCards('flex'); + } + else { + toggleGamificationCards('none'); + var contributors = getCommonContribs( + in_range_users, at_level_users, has_badges_users + ); + + if(contributors.length > 0){ + $('.no-contribs-found').css('display', 'none'); + contributors.forEach(function (username) { + $('[login='+username+']').css('display', 'flex'); + }); + } + else { + $('.no-contribs-found').css('display', 'flex'); + $('.no-contribs-found .search-message').text( + 'No contributors found for you selected filter(s). Please' + + ' try different filter options!' + ); + } + } + } + + score_range_selector.on('change', function () { + filterUsersAndToggleCards(); + }); + + level_selector.on('change', function () { + filterUsersAndToggleCards(); + }); + + badge_selector.on('change', function () { + filterUsersAndToggleCards(); + }); + + $('.filter-btn').on('click', function () { + var filters_option = $('.filters-option'); + var el_display = filters_option.css('display'); + if(el_display === 'flex'){ + filters_option.css('display', 'none'); + } + else { + filters_option.css('display', 'flex'); + } + }); + + $('.clear-filters').on('click', function () { + score_range_selector.val("[]"); + level_selector.val(""); + badge_selector.val(""); + $('select').formSelect(); + $('.no-contribs-found').css('display', 'none'); + toggleGamificationCards('flex'); + }); + +}); \ No newline at end of file diff --git a/templates/gamification.html b/templates/gamification.html index 48becb65..8ea29026 100644 --- a/templates/gamification.html +++ b/templates/gamification.html @@ -1,50 +1,185 @@ - - -
- - - - - -Note: All the datetime is in UTC
-