From d788a5477c3e24be0d04f556143ca2f38554608e Mon Sep 17 00:00:00 2001
From: Ryan Lerch <rlerch@redhat.com>
Date: Nov 24 2017 06:39:10 +0000
Subject: [PATCH 1/2] Rename Subscriptions widget to 'My Hubs'


---

diff --git a/hubs/default_config.py b/hubs/default_config.py
index 3c0ef00..cb13489 100644
--- a/hubs/default_config.py
+++ b/hubs/default_config.py
@@ -65,10 +65,10 @@ WIDGETS = [
     'hubs.widgets.irc:IRC',
     'hubs.widgets.meetings:Meetings',
     'hubs.widgets.memberships:Memberships',
+    'hubs.widgets.my_hubs:MyHubs',
     'hubs.widgets.pagure_pr:PagurePRs',
     'hubs.widgets.pagureissues:PagureIssues',
     'hubs.widgets.rules:Rules',
     'hubs.widgets.sticky:Sticky',
-    'hubs.widgets.subscriptions:Subscriptions',
     'hubs.widgets.workflow.updates2stable:Updates2Stable',
     ]
diff --git a/hubs/widgets/my_hubs/__init__.py b/hubs/widgets/my_hubs/__init__.py
new file mode 100644
index 0000000..c08f23d
--- /dev/null
+++ b/hubs/widgets/my_hubs/__init__.py
@@ -0,0 +1,79 @@
+from __future__ import unicode_literals
+
+import flask
+from fedmsg.meta import msg2usernames
+from fedmsg.meta.base import BaseConglomerator as BC
+
+import hubs.models
+from hubs.utils import get_fedmsg_config
+from hubs.widgets import validators
+from hubs.widgets.base import Widget
+from hubs.widgets.view import RootWidgetView
+from hubs.widgets.caching import CachedFunction
+
+
+fedmsg_config = get_fedmsg_config()
+
+
+class MyHubs(Widget):
+
+    name = "my_hubs"
+    position = "right"
+    label = "My Hubs"
+    hidden_if_empty = True
+    parameters = [
+        dict(
+            name="username",
+            label="Username",
+            default=None,
+            validator=validators.Username,
+            help="A FAS username.",
+        )]
+    hub_types = ['user']
+
+
+class BaseView(RootWidgetView):
+
+    def get_context(self, instance, *args, **kwargs):
+        context = {}
+        get_subs = GetSubs(instance)
+        context.update(get_subs())
+        return context
+
+
+# function hyperlinks the hubs in the subscription widget
+def manage_subscriptions(items):
+    return [
+        '<a href="{link}">{item}</a>'.format(
+            link=flask.url_for('hub', name=item),
+            item=item,
+        ) for index, item in enumerate(items[0:3])
+    ]
+
+
+class GetSubs(CachedFunction):
+
+    def execute(self):
+        username = self.instance.config["username"]
+        user = hubs.models.User.by_username(username)
+        ownerships = [u.name for u in user.ownerships]
+        memberships = [u.name for u in user.memberships]
+        subscriptions = [u.name for u in user.subscriptions]
+        subscriptions_list = manage_subscriptions(subscriptions)
+        memberships_list = manage_subscriptions(memberships)
+        return dict(
+            associations=memberships + ownerships,
+            ownerships=ownerships,
+            memberships=memberships,
+            subscriptions=subscriptions,
+            ownerships_text=BC.list_to_series(ownerships),
+            memberships_text=BC.list_to_series(memberships_list),
+            subscriptions_text=BC.list_to_series(subscriptions_list),
+        )
+
+    def should_invalidate(self, message):
+        if not message['topic'].endswith('hubs.associate'):
+            return False
+        username = self.instance.config['username']
+        users = msg2usernames(message, **fedmsg_config)
+        return username in users
diff --git a/hubs/widgets/my_hubs/templates/root.html b/hubs/widgets/my_hubs/templates/root.html
new file mode 100644
index 0000000..248d86f
--- /dev/null
+++ b/hubs/widgets/my_hubs/templates/root.html
@@ -0,0 +1,15 @@
+{% if associations %}
+
+    {% if memberships %}
+        <p><strong>Belongs to: </strong> {{memberships_text}}</p>
+    {% endif %}
+    <hr/>
+    {% if subscriptions %}
+        <p><strong>Subscribes to:</strong>
+        {% for subscription in subscriptions %}
+        <a href="{{ url_for('hub', name=subscription)}}">
+        {{subscription}}</a>
+        {% endfor %}</p>
+    {% endif %}
+
+{% endif %}
diff --git a/hubs/widgets/subscriptions/__init__.py b/hubs/widgets/subscriptions/__init__.py
deleted file mode 100644
index 6374b8a..0000000
--- a/hubs/widgets/subscriptions/__init__.py
+++ /dev/null
@@ -1,79 +0,0 @@
-from __future__ import unicode_literals
-
-import flask
-from fedmsg.meta import msg2usernames
-from fedmsg.meta.base import BaseConglomerator as BC
-
-import hubs.models
-from hubs.utils import get_fedmsg_config
-from hubs.widgets import validators
-from hubs.widgets.base import Widget
-from hubs.widgets.view import RootWidgetView
-from hubs.widgets.caching import CachedFunction
-
-
-fedmsg_config = get_fedmsg_config()
-
-
-class Subscriptions(Widget):
-
-    name = "subscriptions"
-    position = "right"
-    display_title = "Hubs"
-    hidden_if_empty = True
-    parameters = [
-        dict(
-            name="username",
-            label="Username",
-            default=None,
-            validator=validators.Username,
-            help="A FAS username.",
-        )]
-    hub_types = ['user']
-
-
-class BaseView(RootWidgetView):
-
-    def get_context(self, instance, *args, **kwargs):
-        context = {}
-        get_subs = GetSubs(instance)
-        context.update(get_subs())
-        return context
-
-
-# function hyperlinks the hubs in the subscription widget
-def manage_subscriptions(items):
-    return [
-        '<a href="{link}">{item}</a>'.format(
-            link=flask.url_for('hub', name=item),
-            item=item,
-        ) for index, item in enumerate(items[0:3])
-    ]
-
-
-class GetSubs(CachedFunction):
-
-    def execute(self):
-        username = self.instance.config["username"]
-        user = hubs.models.User.by_username(username)
-        ownerships = [u.name for u in user.ownerships]
-        memberships = [u.name for u in user.memberships]
-        subscriptions = [u.name for u in user.subscriptions]
-        subscriptions_list = manage_subscriptions(subscriptions)
-        memberships_list = manage_subscriptions(memberships)
-        return dict(
-            associations=memberships + ownerships,
-            ownerships=ownerships,
-            memberships=memberships,
-            subscriptions=subscriptions,
-            ownerships_text=BC.list_to_series(ownerships),
-            memberships_text=BC.list_to_series(memberships_list),
-            subscriptions_text=BC.list_to_series(subscriptions_list),
-        )
-
-    def should_invalidate(self, message):
-        if not message['topic'].endswith('hubs.associate'):
-            return False
-        username = self.instance.config['username']
-        users = msg2usernames(message, **fedmsg_config)
-        return username in users
diff --git a/hubs/widgets/subscriptions/templates/root.html b/hubs/widgets/subscriptions/templates/root.html
deleted file mode 100644
index 248d86f..0000000
--- a/hubs/widgets/subscriptions/templates/root.html
+++ /dev/null
@@ -1,15 +0,0 @@
-{% if associations %}
-
-    {% if memberships %}
-        <p><strong>Belongs to: </strong> {{memberships_text}}</p>
-    {% endif %}
-    <hr/>
-    {% if subscriptions %}
-        <p><strong>Subscribes to:</strong>
-        {% for subscription in subscriptions %}
-        <a href="{{ url_for('hub', name=subscription)}}">
-        {{subscription}}</a>
-        {% endfor %}</p>
-    {% endif %}
-
-{% endif %}

From e766f5329fc4cfa7a54eed55e99a84879d8a7b24 Mon Sep 17 00:00:00 2001
From: Ryan Lerch <rlerch@redhat.com>
Date: Nov 24 2017 06:39:10 +0000
Subject: [PATCH 2/2] implement my hubs widget as per the mockup


a rework of the hubs widget to match the mockup, including a
rename from "subscriptions" to "my_hubs"

fixes #436

Signed-off-by: Ryan Lerch <rlerch@redhat.com>

---

diff --git a/hubs/defaults.py b/hubs/defaults.py
index c4e3539..5f8f22d 100644
--- a/hubs/defaults.py
+++ b/hubs/defaults.py
@@ -40,7 +40,7 @@ def add_user_widgets(session, hub, username, fullname):
         }))
     hub.widgets.append(widget)
     widget = hubs.models.Widget(
-        plugin='subscriptions', index=3,
+        plugin='my_hubs', index=3,
         _config=json.dumps({
             'username': username,
         }))
diff --git a/hubs/static/css/style.css b/hubs/static/css/style.css
index f821195..90fd4df 100644
--- a/hubs/static/css/style.css
+++ b/hubs/static/css/style.css
@@ -390,3 +390,25 @@ header h5.m-b-1 {
 .small {
   font-size: small;
 }
+
+.monogram-avatar{
+  width:64px;
+  height:64px;
+  font-size:54px;
+  line-height: 64px;
+  font-weight: 700;
+  text-align: center;
+}
+
+/*Move these color defs into fedora-bootstrap*/
+.bg-fedora-blue{background-color:#3c6eb4;}
+.bg-fedora-magenta{background-color: #db3279;}
+.bg-fedora-orange{background-color: #e59728;}
+.bg-fedora-green{background-color: #79db32;}
+.bg-fedora-purple{background-color: #a07cbc;}
+.text-fedora-blue-dark{color: #294a7a;}
+.text-fedora-magenta-dark{color: #9a1b51;}
+.text-fedora-orange-dark{color: #9a6213;}
+.text-fedora-green-dark{color: #488b18;}
+.text-fedora-purple-dark{color: #70488f;}
+
diff --git a/hubs/tests/widgets/test_my_hubs.py b/hubs/tests/widgets/test_my_hubs.py
new file mode 100644
index 0000000..d5c0e44
--- /dev/null
+++ b/hubs/tests/widgets/test_my_hubs.py
@@ -0,0 +1,35 @@
+from __future__ import unicode_literals
+
+from . import WidgetTest
+import hubs.models  # noqa: E402
+
+
+class MyHubsTest(WidgetTest):
+
+    plugin = "my_hubs"
+
+    def populate(self):
+        super(MyHubsTest, self).populate()
+        self._add_widget_under_test()
+        hub = hubs.models.Hub.by_name('infra')
+        hub.subscribe(hubs.models.User.by_username('ralph'), 'owner')
+
+        hub = hubs.models.Hub.by_name('i18n')
+        hub.subscribe(hubs.models.User.by_username('ralph'), 'subscriber')
+
+        self.session.add(hubs.models.Hub(name="designteam"))
+        hub = hubs.models.Hub.by_name('designteam')
+        hub.subscribe(hubs.models.User.by_username('ralph'), 'member')
+
+    def test_myhubs_output(self):
+        self._update_widget_config({
+            "username": "ralph"
+        })
+        func = self.widget.module.get_cached_functions()['GetSubs']
+        result = func(self.widget).execute()
+        self.assertEquals(len(result['memberships']), 1)
+        self.assertEquals(len(result['ownerships']), 1)
+        self.assertEquals(len(result['subscriptions']), 1)
+        self.assertEquals(result['memberships'][0].name, "designteam")
+        self.assertEquals(result['ownerships'][0].name, "infra")
+        self.assertEquals(result['subscriptions'][0].name, "i18n")
diff --git a/hubs/widgets/my_hubs/__init__.py b/hubs/widgets/my_hubs/__init__.py
index c08f23d..eb0ef39 100644
--- a/hubs/widgets/my_hubs/__init__.py
+++ b/hubs/widgets/my_hubs/__init__.py
@@ -1,16 +1,11 @@
 from __future__ import unicode_literals
 
-import flask
-from fedmsg.meta import msg2usernames
-from fedmsg.meta.base import BaseConglomerator as BC
+import hashlib
 
 import hubs.models
 from hubs.utils import get_fedmsg_config
-from hubs.widgets import validators
 from hubs.widgets.base import Widget
 from hubs.widgets.view import RootWidgetView
-from hubs.widgets.caching import CachedFunction
-
 
 fedmsg_config = get_fedmsg_config()
 
@@ -21,59 +16,50 @@ class MyHubs(Widget):
     position = "right"
     label = "My Hubs"
     hidden_if_empty = True
-    parameters = [
-        dict(
-            name="username",
-            label="Username",
-            default=None,
-            validator=validators.Username,
-            help="A FAS username.",
-        )]
     hub_types = ['user']
 
 
 class BaseView(RootWidgetView):
 
     def get_context(self, instance, *args, **kwargs):
-        context = {}
-        get_subs = GetSubs(instance)
-        context.update(get_subs())
-        return context
-
-
-# function hyperlinks the hubs in the subscription widget
-def manage_subscriptions(items):
-    return [
-        '<a href="{link}">{item}</a>'.format(
-            link=flask.url_for('hub', name=item),
-            item=item,
-        ) for index, item in enumerate(items[0:3])
-    ]
+        # since this widget only shows on user hubs, we assume
+        # hub.name == the username
+        username = instance.hub.name
 
-
-class GetSubs(CachedFunction):
-
-    def execute(self):
-        username = self.instance.config["username"]
         user = hubs.models.User.by_username(username)
-        ownerships = [u.name for u in user.ownerships]
-        memberships = [u.name for u in user.memberships]
-        subscriptions = [u.name for u in user.subscriptions]
-        subscriptions_list = manage_subscriptions(subscriptions)
-        memberships_list = manage_subscriptions(memberships)
+        monograms = {}
+        ownerships = []
+        assoc = []
+        for hub in user.ownerships:
+            if not hub.user_hub:
+                ownerships.append(hub)
+                assoc.append(hub.name)
+                monograms[hub.name] = generate_monogram(hub.name)
+        memberships = []
+        for hub in user.memberships:
+            if not hub.user_hub:
+                if hub.name not in assoc:
+                    memberships.append(hub)
+                    assoc.append(hub.name)
+                    monograms[hub.name] = generate_monogram(hub.name)
+        subscriptions = []
+        for hub in user.subscriptions:
+            if not hub.user_hub:
+                if hub.name not in assoc:
+                    subscriptions.append(hub)
+                    monograms[hub.name] = generate_monogram(hub.name)
         return dict(
-            associations=memberships + ownerships,
             ownerships=ownerships,
             memberships=memberships,
             subscriptions=subscriptions,
-            ownerships_text=BC.list_to_series(ownerships),
-            memberships_text=BC.list_to_series(memberships_list),
-            subscriptions_text=BC.list_to_series(subscriptions_list),
+            monograms=monograms,
         )
 
-    def should_invalidate(self, message):
-        if not message['topic'].endswith('hubs.associate'):
-            return False
-        username = self.instance.config['username']
-        users = msg2usernames(message, **fedmsg_config)
-        return username in users
+
+def generate_monogram(hubname):
+    colours = ["blue", "green", "magenta", "orange", "purple"]
+    colour_index = int(hashlib.md5(hubname.encode('utf8')).hexdigest(), 16) % 5
+    return ("<div class='monogram-avatar bg-fedora-%s text-fedora-%s-dark'>"
+            "%s</div>") % (colours[colour_index],
+                           colours[colour_index],
+                           hubname[0].upper())
diff --git a/hubs/widgets/my_hubs/templates/root.html b/hubs/widgets/my_hubs/templates/root.html
index 248d86f..a1f4311 100644
--- a/hubs/widgets/my_hubs/templates/root.html
+++ b/hubs/widgets/my_hubs/templates/root.html
@@ -1,15 +1,42 @@
-{% if associations %}
-
-    {% if memberships %}
-        <p><strong>Belongs to: </strong> {{memberships_text}}</p>
-    {% endif %}
-    <hr/>
-    {% if subscriptions %}
-        <p><strong>Subscribes to:</strong>
-        {% for subscription in subscriptions %}
-        <a href="{{ url_for('hub', name=subscription)}}">
-        {{subscription}}</a>
-        {% endfor %}</p>
-    {% endif %}
-
-{% endif %}
+<div class="p-2">
+    <ul class="list-unstyled">
+    {% for ownership in ownerships %}
+        <li class="media py-2">
+            <div class="align-self-center mr-3">
+                {{monograms[ownership.name]}}
+            </div>
+            <div class="media-body">
+                <div>
+                    <strong><a href="{{url_for('hub', name=ownership.name)}}">{{ownership.name}}</a></strong>
+                </div>
+                <small>Group Administrator</small>
+            </div>
+        </li>
+    {% endfor %}
+    {% for membership in memberships %}
+    <li class="media py-2">
+            <div class="align-self-center mr-3">
+                {{monograms[membership.name]}}
+            </div>
+            <div class="media-body">
+                <div>
+                    <strong><a href="{{url_for('hub', name=membership.name)}}">{{membership.name}}</a></strong>
+                </div>
+                <small>Member</small>
+            </div>
+        </li>
+    {% endfor %}
+    {% for subscription in subscriptions %}
+    <li class="media py-2">
+            <div class="align-self-center mr-3">
+                {{monograms[subscription.name]}}
+            </div>
+            <div class="media-body">
+                <div>
+                    <strong><a href="{{url_for('hub', name=subscription.name)}}">{{subscription.name}}</a></strong>
+                </div>
+                <small>Subscribed</small>
+            </div>
+        </li>
+    {% endfor %}
+</div>

