diff --git a/app/assets/v2/css/bounty.css b/app/assets/v2/css/bounty.css index 13282147c89..1da12564e06 100644 --- a/app/assets/v2/css/bounty.css +++ b/app/assets/v2/css/bounty.css @@ -98,6 +98,30 @@ body { margin-bottom: 5px; } +.stat-card { + background: #ecf0fa; + border-radius: 4px; +} + +.stat-card h2, +.stat-card p, +.related-bounties li .bounty-org, +.popover-bounty__content .summary { + color: #717171; +} + +.related-bounties { + list-style-type: none; +} + +.related-bounties .bounty-title { + color: #3E00FF; +} + +.related-bounties .static-stars .far { + font-size: 0.8rem; +} + .box { padding: 14px; border: 1px solid #DBDBDB; @@ -153,10 +177,6 @@ body { color: #000000; } -/* #issue_description pre, code { - padding: 1em 0; -} */ - #bounty-info-row { margin-left: 1px; } @@ -490,35 +510,6 @@ a.btn { margin-bottom: 2em; } -.earned { - color: #8E2ABE; - font-size: 14px; - line-height: 13px; - font-weight: 600; -} - -.contributor-position, -.username, -.current_status, -.in-progress { - color: #0D0764; -} - -.specialty { - font-weight: 600; - font-size: 10px; - line-height: 11px; -} - -.completed-bounties { - color: #05B66A; -} - -.abandoned-bounties, -.removed-bounties { - color: #F5A623; -} - #funder_notif_info:empty { display: none; } @@ -540,7 +531,6 @@ a.btn { top: -2px; } - #bounty_details #issue_description img { max-height: 30rem; max-width: 30rem; @@ -860,7 +850,6 @@ a.btn { display: none; } - #title, #funder_notif_info { text-align: center; } diff --git a/app/assets/v2/js/user_popover.js b/app/assets/v2/js/user_popover.js index 6616e7afd71..f0328b1dacf 100644 --- a/app/assets/v2/js/user_popover.js +++ b/app/assets/v2/js/user_popover.js @@ -1,62 +1,90 @@ let popoverData = []; -const renderPopOverData = json => { - let orgs = Object.keys(json.profile.organizations).map((org, index) => { - if (index < 3) { - return `${org}`; +const renderPopOverData = data => { + const unique_contributed_to = data.contributed_to ? Array.from(new Set(data.contributed_to)) : []; + let contributed_to = unique_contributed_to && unique_contributed_to.map((_organization, index) => { + if (index < 5) { + return `${_organization}`; } - return `+${Object.keys(json.profile.organizations).length - 3}`; + return `+${data.contributed_to.length - 5}`; + }).join(' '); + + const bounties = data.related_bounties && data.related_bounties.map(bounty => { + const title = bounty.title <= 30 ? bounty.title : bounty.title.slice(0, 27) + '...'; + + let ratings = []; + + if (bounty.rating && bounty.rating[0] > 0) { + for (let i = 0; i < 5; i++) { + ratings.push(``); + } + } + + return `
  • + ${title} + by ${bounty.org} + ${ratings.length > 0 ? + ` + ${ratings.join(' ')} + ` : ''} +
  • `; }).join(' '); return `
    -
    - -

    ${json.profile.handle}

    - Specialty: ${json.profile.keywords.slice(0, 3).toString()} - ${orgs.length ? 'Contributes to: ' + orgs : ''} +
    + +

    ${data.handle}

    +
    + ${contributed_to.length ? 'Contributes to: ' + contributed_to : ''} +
    - ~ ${Number(json.profile.total_earned).toFixed(4)} ETH earned -
    -
    - #${json.profile.position} -

    Gitcoin Contributor

    + +
    +
    +

    + ${data.stats.position == 0 ? '-' : '#' + data.stats.position} +

    +

    contributor

    -
    -
    - ${json.statistics.work_completed} -

    Bounties Completed

    -
    -
    - ${json.statistics.work_in_progress} -

    Bounties In Progress

    -
    -
    - ${json.statistics.work_abandoned} -

    Bounties Abandoned

    -
    -
    - ${json.statistics.work_removed} -

    Bounties Removed

    -
    +
    +

    + ${data.stats.success_rate ? Math.round(data.stats.success_rate * 100) : 0} % +

    +

    success rate

    +
    +
    +

    + ${data.stats.earnings ? Number(data.stats.earnings).toFixed(4) : 0} ETH +

    +

    + collected from + ${data.stats.completed_bounties} bounties +

    -
    - `; }; -const openContributorPopOver = (contributor, element) => { - let contributorURL = `/api/v0.1/profile/${contributor}`; +function openContributorPopOver(contributor, element) { + const keywords = document.result.keywords || ''; + const contributorURL = `/api/v0.1/profile/${contributor}?keywords=${keywords}`; if (popoverData.filter(index => index[contributor]).length === 0) { fetch(contributorURL, { method: 'GET' }) @@ -65,8 +93,12 @@ const openContributorPopOver = (contributor, element) => { element.popover({ placement: 'auto', trigger: 'hover', - template: - '', + template: ` + `, content: renderPopOverData(response), html: true }); @@ -80,8 +112,12 @@ const openContributorPopOver = (contributor, element) => { element.popover({ placement: 'auto', trigger: 'hover', - template: - '', + template: ` + `, content: renderPopOverData( popoverData.filter(item => item[contributor])[0][contributor] ), @@ -89,35 +125,4 @@ const openContributorPopOver = (contributor, element) => { }); $(element).popover('show'); } -}; - -const currentStatus = status => { - const activity_names = { - new_bounty: gettext('New Bounty'), - start_work: gettext('Work Started'), - stop_work: gettext('Work Stopped'), - work_submitted: gettext('Work Submitted'), - work_done: gettext('Work Done'), - worker_approved: gettext('Worker Approved'), - worker_rejected: gettext('Worker Rejected'), - worker_applied: gettext('Worker Applied'), - increased_bounty: gettext('Increased Funding'), - killed_bounty: gettext('Canceled Bounty'), - new_crowdfund: gettext('New Crowdfund Contribution'), - new_tip: gettext('New Tip'), - receive_tip: gettext('Tip Received'), - bounty_abandonment_escalation_to_mods: gettext( - 'Escalated for Abandonment of Bounty' - ), - bounty_abandonment_warning: gettext('Warned for Abandonment of Bounty'), - bounty_removed_slashed_by_staff: gettext( - 'Dinged and Removed from Bounty by Staff' - ), - bounty_removed_by_staff: gettext('Removed from Bounty by Staff'), - bounty_removed_by_funder: gettext('Removed from Bounty by Funder'), - bounty_changed: gettext('Bounty Details Changed'), - extend_expiration: gettext('Extended Bounty Expiration') - }; - - return activity_names[status] || 'Unknown activity '; -}; +} diff --git a/app/dashboard/models.py b/app/dashboard/models.py index 130d72b2834..d21ad5d019b 100644 --- a/app/dashboard/models.py +++ b/app/dashboard/models.py @@ -2364,8 +2364,28 @@ def is_staff(self): """ return self.user.is_staff if self.user else False + + @property + def completed_bounties(self): + """Returns bounties completed by user + + Returns: + number: number of bounties completed + + """ + network = self.get_network() + return self.bounties.filter( + idx_status__in=['done'], network=network).count() + + @property def success_rate(self): + """Returns success rate of user on the platform + + Returns: + number: sucess rate of user + + """ network = self.get_network() num_completed_bounties = self.bounties.filter( idx_status__in=['done'], network=network).count() diff --git a/app/dashboard/templates/bounty/details.html b/app/dashboard/templates/bounty/details.html index 297ffead966..a967ba008ca 100644 --- a/app/dashboard/templates/bounty/details.html +++ b/app/dashboard/templates/bounty/details.html @@ -15,6 +15,7 @@ + @@ -488,6 +489,9 @@

    {{ noscript.keywords }}

    $("body").on("mouseover", "[data-username]", function(e) { openContributorPopOver($(this).data("username"), $(this)); }); + $("body").on("mouseout", "[data-username]", function(e) { + $(this).popover('dispose'); + }); diff --git a/app/dashboard/views.py b/app/dashboard/views.py index ba79f9d0090..e496c7a9483 100644 --- a/app/dashboard/views.py +++ b/app/dashboard/views.py @@ -34,7 +34,7 @@ from django.core import serializers from django.core.exceptions import PermissionDenied from django.core.paginator import Paginator -from django.db.models import Avg, Count, Q +from django.db.models import Avg, Count, Prefetch, Q from django.http import Http404, HttpResponse, JsonResponse from django.shortcuts import redirect from django.template import loader @@ -1804,29 +1804,67 @@ def profile_details(request, handle): """ try: profile = profile_helper(handle, True) - activity = Activity.objects.filter(profile=profile).order_by('-created_on').first() - count_work_completed = Activity.objects.filter(profile=profile, activity_type='work_done').count() - count_work_in_progress = Activity.objects.filter(profile=profile, activity_type='start_work').count() - count_work_abandoned = Activity.objects.filter(profile=profile, activity_type='stop_work').count() - count_work_removed = Activity.objects.filter(profile=profile, activity_type='bounty_removed_by_funder').count() except (ProfileNotFoundException, ProfileHiddenException): raise Http404 + if not settings.DEBUG: + network = 'mainnet' + else: + network = 'rinkeby' + + keywords = request.GET.get('keywords', '') + + bounties = Bounty.objects.current().prefetch_related( + 'fulfillments', + 'interested', + 'interested__profile', + 'feedbacks' + ).filter( + interested__profile=profile, + network=network, + ).filter( + interested__status='okay' + ).filter( + interested__pending=False + ).filter( + idx_status='done' + ).filter( + feedbacks__receiver_profile=profile + ).filter( + Q(metadata__issueKeywords__icontains=keywords) | + Q(title__icontains=keywords) | + Q(issue_description__icontains=keywords) + ).distinct('pk')[:3] + + _bounties = [] + _orgs = [] + if bounties : + for bounty in bounties: + + _bounty = { + 'title': bounty.title, + 'id': bounty.id, + 'org': bounty.org_name, + 'rating': [feedback.rating for feedback in bounty.feedbacks.all().distinct('bounty_id')], + } + _org = bounty.org_name + _orgs.append(_org) + _bounties.append(_bounty) + response = { - 'profile': ProfileSerializer(profile).data, - 'success_rate': profile.success_rate, - 'recent_activity': { - 'activity_metadata': activity.metadata, - 'activity_type': activity.activity_type, - 'created': activity.created - }, - 'statistics': { - 'work_completed': count_work_completed, - 'work_in_progress': count_work_in_progress, - 'work_abandoned': count_work_abandoned, - 'work_removed': count_work_removed + 'avatar': profile.avatar_url, + 'handle': profile.handle, + 'contributed_to': _orgs, + 'keywords': keywords, + 'related_bounties' : _bounties, + 'stats': { + 'position': profile.get_contributor_leaderboard_index(), + 'completed_bounties': profile.completed_bounties, + 'success_rate': profile.success_rate, + 'earnings': profile.get_eth_sum() } } + return JsonResponse(response, safe=False)