#543 implemend badges widget as per mockup
Merged 6 years ago by abompard. Opened 6 years ago by ryanlerch.
ryanlerch/fedora-hubs badges-widget  into  develop

file modified
+41 -13
@@ -1,9 +1,9 @@ 

  from __future__ import unicode_literals

  

- import operator

  import requests

+ import arrow

+ from collections import defaultdict, OrderedDict

  

- from hubs.utils import validators

  from hubs.widgets.base import Widget

  from hubs.widgets.view import RootWidgetView

  from hubs.widgets.caching import CachedFunction
@@ -13,13 +13,13 @@ 

  

      name = "badges"

      position = "right"

-     parameters = [dict(

-         name="username",

-         label="Username",

-         default=None,

-         validator=validators.Username,

-         help="A FAS username.",

-         )]

+     hub_types = ["stream", "user"]

+ 

+     def get_template_environment(self):

+         env = super(Badges, self).get_template_environment()

+         # Add a filter

+         env.filters['humanize'] = lambda d: arrow.get(d).humanize()

+         return env

  

  

  class BaseView(RootWidgetView):
@@ -32,6 +32,9 @@ 

  

  

  class GetBadges(CachedFunction):

+     def __init__(self, instance):

+         self.instance = instance

+         self.invalidate()

  

      def execute(self):

          username = self.instance.config["username"]
@@ -39,11 +42,36 @@ 

          url = url.format(username=username)

          response = requests.get(url)

          try:

-             assertions = response.json()['assertions']

+             response = response.json()

+             assertions = response['assertions']

          except (KeyError, ValueError):

-             assertions = []

-         key = operator.itemgetter('issued')

-         return dict(assertions=sorted(assertions, key=key, reverse=True))

+             return {}

+         tag_totals = defaultdict(int)

+         latest_badge = None

+         for badge in assertions:

+             if (latest_badge is None or

+                badge["issued"] > latest_badge["issued"]):

+                     latest_badge = badge

+ 

+             tags = filter(None, badge["tags"].split(','))

+             for tag in tags:

+                 tag_totals[tag] += 1

+ 

+         tag_totals = OrderedDict(sorted(tag_totals.items(),

+                                         key=lambda t: t[1],

+                                         reverse=True))

+ 

+         tag_totals = {t: tag_totals[t] for t in list(tag_totals)[:5]}

+ 

+         return dict(

+                     username=username,

+                     total_users=response["user_count"],

+                     rank=response["rank"],

+                     tag_totals=tag_totals,

+                     latest_badge=latest_badge,

+                     total_badges=len(assertions),

+                     badge_percentage=round(response["percent_earned"], 1)

+         )

  

      def should_invalidate(self, message):

          if message['topic'].endswith('hubs.widget.update'):

@@ -1,7 +1,74 @@ 

- <div class="flex-container">

- {% for badge in assertions %}

-   <a width="60px" href="https://badges.fedoraproject.org/badge/{{badge['id']}}">

-     <span class="f-b-{{badge['id']}}"></span>

-   </a>

- {% endfor %}

+ <div class="row mb-2">

+   <div class="col-6">

+     <h6>Badges Earned</h6>

+     <div class="big_number"><a href="https://badges.fedoraproject.org/user/{{username}}">{{total_badges}}</a></div>

+     <div class="text-muted"><small>{{badge_percentage}}% of total</small></div>

+   </div>

+   <div class="col-6">

+       <h6>Badges Rank</h6>

+       <div class="big_number"><a href="https://badges.fedoraproject.org/leaderboard">#{{rank}}</a></div>

+       <div class="text-muted"><small>of {{total_users}}</small></div>

+   </div>

  </div>

+ <div class="rowmb-2">

+     <div class="col-12">

+         <div class="media py-2">

+             <div class="align-self-center mr-3">

+                 <img src='{{latest_badge["image"]}}' width=120px/> 

+             </div>

+             <div class="align-self-center media-body">

+                 <div>

+                     <h6>Most Recent Badge</h6>

+                     <div class="badgename"><strong>{{latest_badge["name"]}}</strong></div>

+                     <div><small class="text-muted">awarded {{latest_badge["issued"]|humanize}}</small></div>

+                 </div>

+             </div>

+           </div>

+     </div>

+ </div>

+ 

+ <div class="row">

+     <div class="col-12">

+     <h6 class="mb-3">Top Badge Tags</h6>

+     <ul class="list-unstyled">

+     {% for tag in tag_totals%}

+       <li class="pt-1 pb-1">

+         {{tag}} <div class="pull-right">{{tag_totals[tag]}}</div>

+       </li>

+     {% endfor %}

+     </ul>

+ </div>

+ </div>

+ 

+ <style>

+ .widget-badges h6 {

+     font-family: 'Open Sans', sans-serif;

+     text-transform: capitalize;

+     font-size: normal;

+     font-weight: 700;

+     color: #808080;

+     margin-bottom: 5px;

+ }

+ .widget-badges .big_number{

+   color:#3c6eb4;

+   font-weight: bold;

+   font-size: 2em;

+   line-height: 1em;

+ }

+ .widget-badges .text-muted{

+   line-height: 0.8em;

+ }

+ .widget-badges .list-unstyled li{

+   border-bottom: 1px solid #ddd

+ }

+ 

+ .widget-badges .list-unstyled li:first-child{

+   border-top: 1px solid #ddd;

+ }

+ 

+ .widget-badges .badgename{

+   font-size:0.8em;

+   line-height: 1em;

+ }

+ 

+ </style>

rebased onto b5a63530d22c53a3c035043e05c0d32506fe8806

6 years ago

Do I really want this widget on my stream page? Am I that obnoxious? ;-p

Hmm, this seems wrong. Was it for testing only?

In case of server error, I think the json() call fails and that's why it was put in the try-except below (it's the ValueError case).

It feels like there should be more links to badges.fp.o : the blue "badges earned" number seems like a good candidate, it makes me want to click on it :-)

It feels like there should be more links to badges.fp.o : the blue "badges earned" number seems like a good candidate, it makes me want to click on it :-)

Link added! I meant to add this one, but totally forgot previously!

rebased onto 41a954a

6 years ago

Have also removed the cache invalidation bit -- that was a vestige from testing the widget! Also removed the two commented out lines.

The .json call is also now in the try-except block.

Also, i supposed some people might want to keep up on their rating every day (i know people at the top of the list take it very seriously :) ) -- that is why i added it as an option to the stream page. :D

Beautiful, thanks! :-)

Pull-Request has been merged by abompard

6 years ago