From 07ec473ffcd587927e84cd60ced5707e64ef5979 Mon Sep 17 00:00:00 2001
From: Ryan Lerch <rlerch@redhat.com>
Date: Dec 13 2017 11:41:12 +0000
Subject: get basic allgroups page working


---

diff --git a/hubs/static/client/app/components/AllGroupsPage.js b/hubs/static/client/app/components/AllGroupsPage.js
new file mode 100644
index 0000000..9ccdd32
--- /dev/null
+++ b/hubs/static/client/app/components/AllGroupsPage.js
@@ -0,0 +1,81 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { connect } from 'react-redux';
+import {
+  defineMessages,
+  FormattedMessage,
+  } from 'react-intl';
+import PageStructure from './PageStructure';
+import { monogramColour } from '../core/utils';
+
+
+class AllGroupsPage extends React.Component {
+
+  render() {
+    return (
+      <PageStructure
+        cssClass="AllGroupsPage"
+        header={
+            <div className="row">
+              <div className="col-md-12">
+                <h1>Groups</h1>
+                <p>Summary of available team hubs</p>
+              </div>
+            </div>
+          }
+          content={
+            <div className="row">
+              <div className="col-md-12">
+                <div className="list-group">
+                {this.props.hubslist.hubs.map(function(hub, i) {
+                    return (
+                    <a className="list-group-item list-group-item-action d-flex justify-content-between align-items-center"
+                       key={i}
+                       href={hub.hub_url}
+                       >
+                        <div className="d-flex">
+                        { !hub.config.avatar ?
+                            <div className={`monogram-avatar avatar bg-fedora-${monogramColour(hub.name)} text-fedora-${monogramColour(hub.name)}-dark`}>
+                                {hub.name.charAt(0).toUpperCase()}
+                            </div>
+                            :
+                            <img
+                                className="avatar"
+                                src={hub.config.avatar}
+                                width='60px'
+                                />
+                        }
+                            <div className="text-dark align-self-center ml-3">
+                                <h3>{hub.name}</h3>
+                                <div>{hub.config.summary}</div>
+                            </div>
+                        </div>
+                        <div className="d-flex">
+                            <div className="mx-2 align-self-center">
+                                <div className="text-uppercase text-secondary"><small>Members</small></div>
+                                <div className="text-primary text-center">{hub.member_count}</div>
+                            </div>
+                            <div className="mx-2 align-self-center">
+                                <div className="text-uppercase text-secondary"><small>Subscribers</small></div>
+                                <div className="text-primary text-center">{hub.subscriber_count}</div>
+                            </div>
+                        </div>
+                    </a>
+                    );
+                })}
+                </div>
+              </div>
+            </div>
+          }
+        />
+    );
+  }
+}
+
+const mapStateToProps = (state) => {
+    return {
+      hubslist: state.hubslist,
+    }
+  };
+
+  export default connect(mapStateToProps)(AllGroupsPage);
diff --git a/hubs/static/client/app/core/Pages.js b/hubs/static/client/app/core/Pages.js
index 67aee23..bfe19dc 100644
--- a/hubs/static/client/app/core/Pages.js
+++ b/hubs/static/client/app/core/Pages.js
@@ -14,6 +14,10 @@ const Streams = makeLoadable(
   "Loading...",
   "Sorry, there was a problem loading the page."
 );
+const Groups = makeLoadable(
+  () => import(/* webpackChunkName: "page-allgroups" */ '../components/AllGroupsPage'),
+  "Loading...",
+  "Sorry, there was a problem loading the page."
+);
 
-
-export {Hub, Streams};
+export {Hub, Streams, Groups};
diff --git a/hubs/static/client/app/core/reducers/index.js b/hubs/static/client/app/core/reducers/index.js
index 61bb90e..acb6644 100644
--- a/hubs/static/client/app/core/reducers/index.js
+++ b/hubs/static/client/app/core/reducers/index.js
@@ -40,6 +40,7 @@ const rootReducer = combineReducers({
   globalConfig: noop,
   urls: noop,
   currentUser: userReducer,
+  hubslist: noop,
   ui,
   entities,
 });
diff --git a/hubs/static/css/style.css b/hubs/static/css/style.css
index dae5273..defa6b5 100644
--- a/hubs/static/css/style.css
+++ b/hubs/static/css/style.css
@@ -401,16 +401,16 @@ header h5.m-b-1 {
 }
 
 /*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;}
+.bg-fedora-blue{background-color:#3c6eb4!important;}
+.bg-fedora-magenta{background-color: #db3279!important;}
+.bg-fedora-orange{background-color: #e59728!important;}
+.bg-fedora-green{background-color: #79db32!important;}
+.bg-fedora-purple{background-color: #a07cbc!important;}
+.text-fedora-blue-dark{color: #294a7a!important;}
+.text-fedora-magenta-dark{color: #9a1b51!important;}
+.text-fedora-orange-dark{color: #9a6213!important;}
+.text-fedora-green-dark{color: #488b18!important;}
+.text-fedora-purple-dark{color: #70488f!important;}
 
 .typeahead, .twitter-typeahead {
   width: 100%!important;
diff --git a/hubs/templates/groups.html b/hubs/templates/groups.html
deleted file mode 100644
index 50b5710..0000000
--- a/hubs/templates/groups.html
+++ /dev/null
@@ -1,60 +0,0 @@
-{% extends "master.html" %}
-
-{% block title %}Groups{% endblock %}
-
-
-{% block header %}
-  <h1 class="team">Groups</h2>
-  <p>Summary of available team hubs</p>
-{% endblock header %}
-
-
-{% block content %}
-
-  <div class="row">
-    <div class="col-md-12">
-      {% for group in promoted %}
-        <a href="{{ url_for('hub', name=group.name)}}">{{group.name}}</a>
-        is a promoted group, and it has {{
-            group.subscribers | length }} subscribers and {{
-            group.members | length }} members, and {{
-            group.owners | length }} owners
-      {% endfor %}
-    </div>
-  </div>
-
-  <hr/>
-
-  <div class="row">
-    <div class="col-md-12">
-      ZOMG - {{ hub_of_the_month.name }} is the Hub Of The Month! <br/>
-      It has {{ hub_of_the_month.subscribers | length }} subscribers and {{
-        hub_of_the_month.members | length }} members, and {{
-        hub_of_the_month.owners | length }} owners
-      <h6>group owners</h6>
-      {% for owner in hub_of_the_month.owners %}
-          <div class="col-sm-6">
-              <img class="img-circle" src="{{owner.username | avatar}}"/>
-              <a href="{{ url_for('hub', name=owner.username)}}">{{owner.username}}</a>
-          </div>
-      {% endfor %}
-    </div>
-  </div>
-
-  <hr/>
-
-  <div class="row">
-    <div class="col-md-12">
-      {% for group in secondary %}
-        <a href="{{ url_for('hub', name=group.name)}}">{{group.name}}</a>
-        is not a promoted group
-        but it has {{ group.subscribers | length }} subscribers and {{
-            group.members | length }} members, and {{
-            group.owners | length }} owners
-        <br/>
-      {% endfor %}
-    </div>
-  </div>
-</div>
-
-{% endblock content %}
diff --git a/hubs/tests/views/test_root.py b/hubs/tests/views/test_root.py
index 1288c6b..8394783 100644
--- a/hubs/tests/views/test_root.py
+++ b/hubs/tests/views/test_root.py
@@ -1,7 +1,10 @@
 from __future__ import unicode_literals
 
 from six.moves.urllib.parse import urlparse
+import mock
+import json
 
+import hubs
 from hubs.app import app
 from hubs.tests import APPTest, FakeAuthorization, auth_set
 
@@ -41,11 +44,71 @@ class RootTestCase(APPTest):
         # this will redirect to fedora.login
         self.assertEqual(urlparse(result.location).path, "/")
 
-    def test_groups_logged_in(self):
+
+class RootGroupsTestCase(APPTest):
+    def _parse_setuppage_json(self, result):
+        result = result.get_data(as_text=True)
+        result = result.partition('function() {\n  Hubs.setupPage("page", ')[2]
+        result = result.partition(');\n})();\n</script>')[0]
+        return json.loads(result)
+
+    def _add_assoc(self, hubname, username, role):
+        hub = hubs.models.Hub.query.get(hubname)
+        user = hubs.models.User.query.get(username)
+        self.session.add(hubs.models.Association(
+            hub=hub, user=user, role=role))
+
+    @mock.patch('hubs.views.root.log.warning')
+    def test_groups_HOTM_not_found(self, warning):
+        """
+        Test we log a warning if the HUB_OF_THE_MONTH value
+        set in the config does not name a hub that exists.
+        The config in the base tests sets commops, as the
+        HOTM, but this hub doesnt exist in the base testcase
+        """
         user = FakeAuthorization('ralph')
         with auth_set(app, user):
             result = self.app.get('/groups/', follow_redirects=True)
-            # assert the status code of the response
+            data = self._parse_setuppage_json(result)
+
+            self.assertEqual(result.status_code, 200)
+            warning.assert_called_once_with("The hub (commops) set as the "
+                                            "HUB_OF_THE_MONTH in the config "
+                                            "does not exist")
+            self.assertEquals(data["hubslist"]["hub_of_the_month"], None)
+
+    @mock.patch('hubs.views.root.log.warning')
+    @mock.patch('hubs.app.app.config.get', return_value="infra")
+    def test_groups_HOTM_found(self, config, warning):
+        """
+        Test that we return the HOTM in the JSON we give to
+        the react component
+        """
+        user = FakeAuthorization('ralph')
+        with auth_set(app, user):
+            result = self.app.get('/groups/', follow_redirects=True)
+            data = self._parse_setuppage_json(result)
+
+            self.assertEqual(result.status_code, 200)
+            warning.assert_not_called()
+            self.assertEquals(data["hubslist"]
+                                  ["hub_of_the_month"]
+                                  ["name"], "infra")
+
+    def test_groups(self):
+        """
+        Test that we return the correct groups list
+        in the JSON we give to the react component
+        """
+        user = FakeAuthorization('ralph')
+        self._add_assoc("infra", "ralph", "stargazer")
+        self._add_assoc("infra", "ralph", "member")
+        self._add_assoc("infra", "ralph", "subscriber")
+        with auth_set(app, user):
+            result = self.app.get('/groups/', follow_redirects=True)
+            data = self._parse_setuppage_json(result)
             self.assertEqual(result.status_code, 200)
-            self.assertIn("ZOMG -  is the Hub Of The Month!",
-                          result.get_data(as_text=True))
+            self.assertEqual(len(data["hubslist"]["hubs"]), 3)
+            self.assertEqual(data["hubslist"]["hubs"][0]["name"], "i18n")
+            self.assertEqual(data["hubslist"]["hubs"][1]["name"], "infra")
+            self.assertEqual(data["hubslist"]["hubs"][2]["name"], "old")
diff --git a/hubs/views/root.py b/hubs/views/root.py
index cae9996..c4c525d 100644
--- a/hubs/views/root.py
+++ b/hubs/views/root.py
@@ -1,10 +1,16 @@
 from __future__ import unicode_literals, absolute_import
 
+import logging
 import flask
 import hubs.models
 
 from hubs.app import app, OIDC
-from hubs.utils.views import authenticated, is_safe_url, login_required
+from hubs.utils import hubname2monogramcolour
+from hubs.utils.views import (authenticated, is_safe_url,
+                              login_required, get_user_details)
+
+
+log = logging.getLogger("hubs")
 
 
 @app.route('/')
@@ -21,21 +27,53 @@ def index():
 @app.route('/groups/')
 @login_required
 def groups():
-    # Get the list of promoted and non-promoted group hubs from the DB
-    promoted_names = app.config.get('PROMOTED_GROUPS')
+    current_user = get_user_details()
+    urls = {
+        "allGroups": flask.url_for("groups"),
+    }
+    flash_messages = [
+        {"msg": msg[1], "type": msg[0]} for msg in
+        flask.get_flashed_messages(with_categories=True)
+        ]
+
     groups = hubs.models.Hub.all_group_hubs()
-    promoted = [g for g in groups if g.name in promoted_names]
-    secondary = [g for g in groups if g.name not in promoted_names]
+    hubslist = []
+    for group in groups:
+        hub = group.get_props()
+        hub["monogram_colour"] = hubname2monogramcolour(hub["name"])
+        hub["hub_url"] = flask.url_for('hub', name=hub["name"])
+
+        member_count = len(hub["users"]["member"])
+        owner_count = len(hub["users"]["owner"])
+        hub["member_count"] = member_count + owner_count
+
+        subscriber_count = len(hub["users"]["subscriber"])
+        hub["subscriber_count"] = subscriber_count
+
+        hubslist.append(hub)
 
     name_of_the_month = app.config.get('HUB_OF_THE_MONTH')
     hub_of_the_month = hubs.models.Hub.by_name(name_of_the_month)
+    if hub_of_the_month:
+        hub_of_the_month = hub_of_the_month.get_props()
+    else:
+        log.warning("The hub (%s) set as the HUB_OF_THE_MONTH "
+                    "in the config does not exist" % name_of_the_month)
 
     return flask.render_template(
-        'groups.html',
-        promoted=promoted,
-        secondary=secondary,
-        hub_of_the_month=hub_of_the_month,
-    )
+        'react.html',
+        page_title="All Groups",
+        initial_state=dict(
+            ui=dict(
+                page="Groups",
+                flashMessages=flash_messages,
+            ),
+            urls=urls,
+            currentUser=current_user,
+            hubslist={"hubs": hubslist,
+                      "hub_of_the_month": hub_of_the_month}
+            ),
+        )
 
 
 @app.route('/login/', methods=('GET', 'POST'))

