#207 Feed widget to external SSE and JSX style rules
Merged 7 years ago by atelic. Opened 7 years ago by atelic.
atelic/fedora-hubs fix/jsx-style  into  develop

file modified
+2
@@ -7,3 +7,5 @@ 

  scratch/

  

  npm-debug.log

+ client_secrets.json

+ hubs.js

file modified
+7 -2
@@ -205,12 +205,17 @@ 

      [2015-07-01 14:33:21][    fedmsg    INFO] copr has 6 entries

      [2015-07-01 14:33:21][    fedmsg    INFO] askbot has 2 entries

  

- Build JavaScript assets. Add ``--w`` for live reloading::

+ Change the ``SSE_URL`` location in ``default_config.py`` to point to the

+ streaming server then build JavaScript assets. Add ``--w`` for live reloading::

  

      $ cd hubs/static/client && node_modules/.bin/webpack

  

  **Lastly**, (fingers crossed) start up the fedora-hubs webapp and load your

- profile page.  Once there are some messages that get into your local database

+ profile page. Change back to the project root and run::

+ 

+   $ python runserver.py -c config

+ 

+ Once there are some messages that get into your local database

  that *should* show up on your feed.. they should appear there.  (At very least,

  you shouldn't get an error message about that widget being unable to be

  displayed).

@@ -1,134 +0,0 @@ 

- # encoding=utf8

- '''

- This file is for development purposes only.

- 

- The end goal is for the streaming server to be implemented on the FMN side

- '''

- import json

- import logging

- import sys

- import trollius

- import trollius_redis

- import urllib2

- from concurrent.futures import TimeoutError

- from trollius import From

- 

- reload(sys)

- sys.setdefaultencoding('utf8')

- 

- log = logging.getLogger(__name__)

- SERVER = None

- REDIS_HOST = '0.0.0.0'

- REDIS_PORT = 6379

- REDIS_DB = 0

- EVENTSOURCE_PORT = 9090

- '''

- you need to

- 

- pip install trollius trollius_redis

- 

- dnf install redis httpie

- systemctl start redis

- 

- 

- usage: http get 0.0.0.0:9090/

- 

- '''

- 

- 

- def get_recent_posts():

-     delta = "delta=86400"  # one day worth of data

-     rows_per_page = "rows_per_page=50"

-     url = "https://apps.fedoraproject.org/datagrepper/raw" + "?" + delta + "&" + rows_per_page

-     request = urllib2.Request(url)

-     contents = urllib2.urlopen(request).read()

-     json_response = json.loads(contents)

-     return json_response['raw_messages']

- 

- 

- @trollius.coroutine

- def handle_client(client_reader, client_writer):

-     origin = '*'

-     if origin.endswith('/'):

-         origin = origin[:-1]

- 

-     client_writer.write(("HTTP/1.0 200 OK\n"

-                          "Content-Type: text/event-stream\n"

-                          "Cache: nocache\n"

-                          "Connection: keep-alive\n"

-                          "Access-Control-Allow-Origin: %s\n\n" % origin

-                          ).encode())

- 

-     connection = yield trollius.From(trollius_redis.Connection.create(

-         host=REDIS_HOST, port=REDIS_PORT,

-         db=REDIS_DB))

- 

-     try:

-         posts = get_recent_posts()

- 

-         # send 50 latest posts

-         num_post_sent = 0

-         while num_post_sent < 50:

-             reply = posts[num_post_sent]

-             reply = json.dumps(reply)

-             log.info(reply)

-             log.info("Sending post %s %s", str(num_post_sent), reply)

-             client_writer.write(('data: %s\n\n' % reply).encode())

-             yield trollius.From(client_writer.drain())

-             num_post_sent += 1

-             yield From(trollius.sleep(2))

- 

-     except trollius.ConnectionResetError:

-         log.exception("ERROR: ConnectionResetError in handle_client")

-     except Exception:

-         log.exception("ERROR: Exception in handle_client")

-     finally:

-         # Wathever happens, close the connection.

-         connection.close()

-         client_writer.close()

- 

- 

- def main():

-     global SERVER

- 

-     try:

-         loop = trollius.get_event_loop()

-         coro = trollius.start_server(handle_client,

-                                      host=None,

-                                      port=EVENTSOURCE_PORT,

-                                      loop=loop)

-         SERVER = loop.run_until_complete(coro)

-         log.info('Serving server at {}'.format(SERVER.sockets[0].getsockname(

-         )))

-         loop.run_forever()

-     except KeyboardInterrupt:

-         pass

-     except trollius.ConnectionResetError as err:

-         log.exception("ERROR: ConnectionResetError in main")

-     except Exception as err:

-         log.exception("ERROR: Exception in main")

-     finally:

-         # Close the server

-         SERVER.close()

-         log.info("End Connection")

-         loop.run_until_complete(SERVER.wait_closed())

-         loop.close()

-         log.info("End")

- 

- 

- if __name__ == '__main__':

-     log = logging.getLogger("")

-     formatter = logging.Formatter(

-         "%(asctime)s %(levelname)s [%(module)s:%(lineno)d] %(message)s")

- 

-     # setup console logging

-     log.setLevel(logging.DEBUG)

-     ch = logging.StreamHandler()

-     ch.setLevel(logging.DEBUG)

- 

-     aslog = logging.getLogger("asyncio")

-     aslog.setLevel(logging.DEBUG)

- 

-     ch.setFormatter(formatter)

-     log.addHandler(ch)

-     main()

file modified
-73
@@ -639,76 +639,3 @@ 

          return flask.abort(400)

      session.commit()

      return flask.redirect(flask.url_for('hub', name=hub.name))

- 

- 

- PATHS = fmn.lib.load_rules(root='fmn.rules')

- 

- 

- @app.route('/api/fedmsg/markup', methods=['GET'])

- def markup_fedmsg():

-     '''

-     This is a temporary endpoint to create a human-readable form of a message.

-     For now it serves as a development tool

- 

-     This route will be removed once its functionality is integrated into FMN

-     '''

-     from hubs.widgets.feed import (

-         apply_markup, rehydrate_preference,

-         get_remote_preference

-     )

-     try:

-         data = flask.request.args['message']

-         plugin = flask.request.args['plugin']

-     except KeyError:

-         return flask.abort(400)

- 

-     widget = hubs.models.Widget.by_plugin(session, plugin)

- 

-     if not widget:

-         return flask.abort(400)

- 

-     context = widget.config.get('fmn_context')

- 

-     messages = []

-     message = json.loads(data)

- 

-     if 'topic' not in message:

-         return flask.abort(400)

- 

-     try:

-         nickname = flask.g.auth.nickname

-     except AttributeError:      # Not logged in

-         return flask.abort(403)

- 

-     preference = get_remote_preference(nickname, context)

- 

-     if preference:

-         try:

-             preference = rehydrate_preference(preference)

-         except ImportError:

-             pass

-     recipients = fmn.lib.recipients(

-         [preference], message, PATHS, fedmsg_config)

-     if recipients:

-         messages.append(message)

-     matches = fedmsg.meta.conglomerate(messages, lexers=True, **fedmsg_config)

- 

-     for match in matches:

-         match['markup'] = apply_markup(match)

-         for _, constituent in match['msg_ids'].items():

-             constituent['markup'] = apply_markup(constituent)

-             if constituent['long_form'] != constituent['subtitle']:

-                 if constituent.get('lexer'):

-                     constituent['long_form'] = pygments.highlight(

-                         constituent['long_form'],

-                         constituent['lexer'],

-                         pygments.formatters.HtmlFormatter(style='monokai'),

-                     )

-                 else:

-                     markup = u"<h5>{long_form}</h5>".format(**constituent)

-                     constituent['long_form'] = markup

- 

-         # And tack on a unique identifier for each top level entry.

-         match['dom_id'] = six.text_type(uuid.uuid4())

- 

-     return flask.jsonify(matches)

file modified
+1
@@ -9,6 +9,7 @@ 

  

  HUB_OF_THE_MONTH = 'commops'

  

+ SSE_URL = 'http://localhost:8080/user/'

  

  OIDC_CLIENT_SECRETS = os.path.join(os.path.dirname(

      os.path.abspath(__file__)), '..', 'client_secrets.json')

file modified
+1 -1
@@ -11,7 +11,7 @@ 

          plugin='feed', index=0, left=True,

          _config=json.dumps({

              'username': username,

-             'fmn_context': 'irc',  # TODO -- make this 'hubs'

+             'message_limit': 20

          }))

      hub.widgets.append(widget)

  

@@ -0,0 +1,8 @@ 

+ {

+   "extends": "airbnb",

+   "rules": {

+     "react/prop-types": "off",

+     "arrow-body-style": "off",

+     "func-names": "off"

+   }

+ }

@@ -1,52 +0,0 @@ 

- import React from 'react';

- 

- export default class Constituent extends React.Component {

-     render() {

-         const match = this.props.match;

-         const hasConstituent = (match['msg_ids'].length === 1 && Object.keys(match['msg_ids'])[0]['long_form'] !== Object.keys(match['msg_ids'])[0]['subtitle']);

-         if ( hasConstituent ) {

-             const constituentKey = Object.keys(match['msg_ids'])[0];

-             const constituent = match['msg_ids'][constituentKey];

-             const differentIcon = constituent['icon'] != constituent['__icon__'];

-             const longLink = ( constituent['link'] && constituent['long_form'].indexOf(constituent['link']) === -1 );

- 

-             let constituentIcon;

-             if (differentIcon)

-                 constituentIcon = (

-                     <div className="media-left media-top">

-                         <a href={constituent['link']}>

-                             <img className="media-object" src={constituent['icon']} alt={constituent['subtitle']}/>

-                         </a>

-                     </div>

- 

-                 )

- 

-             let readMore;

-             if(longLink)

-                 readMore = (

-                     <div className="media-body">

-                         {constituent['long_form']}

-                         <a href={constituent['link']} target="_blank">

-                             Read more <span class="glyphicon glyphicon-new-window"></span>

-                         </a>

-                     </div>

-                 )

- 

-             return (

-                 <div id="content-{{match['dom_id']}}" className="media">

-                     <ExpandCollapse match={this.props.match} />

-                     {constituentIcon}

-                     {readMore}

-                 </div>

-             )

-         } else if (match['msg_ids'].length > 1) {

-             <div id="content-{{match['dom_id']}}" className="media">

-                 <ExpandCollapse match={this.props.match} />

-                 <MessageList msgs={this.match['msg_ids']} />

-             </div>

-         }

-         else {

-             return null

-         }

-     }

- }

@@ -1,18 +0,0 @@ 

- import React from 'react';

- 

- export default class ExpandCollapse extends React.Component {

-     render() {

-         return (

-             <div className="pull-right">

-                 <a id="expand-{this.props.match['dom_id']}" href="javascript:expand_feed_entries({this.props.match['dom_id']});" className="hidden">

-                     <span className="glyphicon glyphicon-chevron-down"></span>

-                     expand

-                 </a>

-                 <a id="collapse-{this.props.match['dom_id']}" href="javascript:collapse_feed_entries('{this.props.match['dom_id']}');">

-                     <span className="glyphicon glyphicon-chevron-up"></span>

-                     collapse

-                 </a>

-             </div>

-         )

-     }

- }

@@ -1,67 +1,44 @@ 

  import React from 'react';

- import {render} from 'react-dom';

+ import { render } from 'react-dom';

  

- 

- import Constituent from './Constituent.jsx';

- import ExpandCollapse from './ExpandCollapse.jsx';

- import Icon from './Icon.jsx';

- import Markup from './Markup.jsx';

- import {Message, MessageList} from './Messages.jsx';

- 

- 

- class Panel extends React.Component {

-     render() {

-         return (

-             <div className="panel panel-default">

-                 <div className="panel-body">

-                     <Icon match={this.props.match} />

-                     <Markup match={this.props.match} />

-                     <Constituent match={this.props.match} />

-                 </div>

-             </div>

-         );

-     }

- }

+ import Panel from './Panel.jsx';

  

  export default class Feed extends React.Component {

-     constructor (props) {

-         super(props);

-         this.state = {

-             matches: this.props.matches,

-             sse: true,

-         }

-         this.source = (!!window.EventSource) ? new EventSource('http://localhost:9090') : {};

-         this.source.addEventListener('error', () => {

-             this.state.sse = false;

-         }, false);

-         window.onbeforeunload = () => {

-             this.source.close();

-         }

-         this.source.addEventListener('message', resp => {

-             $.get('/api/fedmsg/markup', {

-                 message: resp.data,

-                 plugin: 'feed',

-             }).done(data => {

-                 if(!$.isEmptyObject(data)) {

-                     this.state.matches.push.apply(this.state.matches, data);

-                     this.setState({matches: this.state.matches});

-                 }

-             });

-         }, false);

-     }

-     render() {

-         const feedNodes = this.state.matches.map(match => {

-             return <Panel match={match}/>

-         });

-         return (

-             <div>

-                 {feedNodes}

-             </div>

-         )

-     }

+   constructor(props) {

+     super(props);

+     this.state = {

+       matches: this.props.matches,

+       messageLimit: this.props.messageLimit,

+       sse: true,

+     };

+     this.source = (!!window.EventSource) ? new EventSource(this.props.url) : {};

+     this.source.addEventListener('error', () => {

+       this.state.sse = false;

+     }, false);

+     window.onbeforeunload = () => {

+       this.source.close();

+     };

+     this.source.onmessage = resp => {

+       const data = JSON.parse(resp.data);

+       if (this.state.matches.length >= this.state.messageLimit) {

+         this.state.matches.pop();

+       }

+       this.state.matches.unshift(data);

+       this.setState({ matches: this.state.matches });

+     };

+   }

+   render() {

+     const feedNodes = this.state.matches.map((match, idx) => {

+       return <Panel match={match} key={idx} />;

+     });

+     return (

+       <div>

+       {feedNodes}

+       </div>

+     );

+   }

  }

  

- 

  window.Feed = Feed;

  window.React = React;

  window.reactRender = render;

@@ -1,15 +1,19 @@ 

  import React from 'react';

  

- export default class Icon extends React.Component {

-     render() {

-         return (

-             <div>

-                 <div className="media-left">

-                     <a href={this.props.match['link'] ? this.props.match['link'] : '#'} target="_blank">

-                         <img className="media-object square-32 img-circle" src={this.props.match['secondary_icon']} />

-                     </a>

-                 </div>

-             </div>

-         )

-     }

- }

+ const Icon = function (props) {

+   return (

+     <div>

+       <div className="media-left">

+         <a href={props.match.link ? props.match.link : '#'} target="_blank">

+           <img

+             alt="User avatar"

+             className="media-object square-32 img-circle"

+             src={props.match.secondary_icon}

+           />

+         </a>

+       </div>

+     </div>

+   );

+ };

+ 

+ export default Icon;

@@ -1,17 +1,20 @@ 

  import React from 'react';

+ import TimeAgo from 'react-timeago';

  

  export default class Markup extends React.Component {

-     createMarkup() {

-         return {__html: this.props.match['markup']};

-     }

-     render() {

-         return (

-             <div className="media-body">

-                 <h4 className="media-heading"

-                     dangerouslySetInnerHTML={this.createMarkup()}

-                 ></h4>

-                 {this.props.match['human_time']}

-             </div>

-         )

-     }

+   createMarkup() {

+     return { __html: this.props.match.markup };

+   }

+   render() {

+     return (

+       <div className="media-body">

+         <h4

+           className="media-heading"

+           dangerouslySetInnerHTML={this.createMarkup()}

+         >

+         </h4>

+         <TimeAgo date={this.props.match.date_time} />

+       </div>

+     );

+   }

  }

@@ -1,36 +0,0 @@ 

- import React from 'react';

- 

- class Message extends React.Component {

-     render() {

-         if(this.props.constituent['link']) {

-             return (

-                 <li>

-                     {this.props.constituent['markup']}

-                     <a href={this.props.constituent['link']} target="_blank">

-                         <span className="glyphicon glyphicon-new-window"></span>

-                     </a>

-                 </li>

-             )

-         } else {

-             return (<li>{ this.props.constituent['markup'] }</li>)

-         }

-     }

- }

- 

- class MessageList extends React.Component {

-     render() {

-         const messageNodes =  this.props.items.map(function(item) {

-             return <Message constituent={item}/>;

-         });

-         return (

-             <ul id="content-{this.props.match['dom_id']}" className="hidden list-unstyled">

-                 {messageNodes}

-             </ul>

-         )

-     }

- }

- 

- module.exports = {

-     Message: Message,

-     MessageList: MessageList

- }

@@ -0,0 +1,17 @@ 

+ import React from 'react';

+ 

+ import Icon from './Icon.jsx';

+ import Markup from './Markup.jsx';

+ 

+ const Panel = function (props) {

+   return (

+     <div className="panel panel-default panel-visible">

+       <div className="panel-body">

+         <Icon match={props.match} />

+         <Markup match={props.match} />

+       </div>

+     </div>

+   );

+ };

+ 

+ export default Panel;

@@ -1,5 +1,5 @@ 

- import React from 'react';

- import { render } from 'react-dom';

- 

- import Feed from './components/Feed.jsx'

- 

+ /* This import is needed even though it's not used

+  * since the feed component places itself on window.

+  * The feed will not work without it.

+  */

+ import Feed from './components/Feed.jsx'; // eslint-disable-line

@@ -7,27 +7,24 @@ 

      "doc": "docs"

    },

    "dependencies": {

-     "babel-core": "~6.9.1",

      "babel-loader": "~6.2.4",

+     "babel-plugin-transform-class-properties": "^6.10.2",

      "babel-preset-es2015": "~6.9.0",

      "babel-preset-react": "~6.5.0",

      "react": "~15.1.0",

      "react-dom": "~15.1.0",

-     "reactify": "~1.1.1",

+     "react-timeago": "^3.1.1",

      "webpack": "~1.13.1"

    },

    "devDependencies": {

-     "eslint-config-airbnb": "~9.0.1",

-     "eslint-plugin-react": "~5.2.1",

-     "eslint-plugin-jsx-a11y": "~1.5.3",

-     "eslint-plugin-import": "~1.8.1",

-     "eslint": "~2.12.0"

+     "eslint": "^2.13.1",

+     "depcheck": "~0.6.3"

    },

    "scripts": {

      "dev": "webpack --watch",

      "build": "webpack -p",

-     "test": "echo \"Error: no test specified\" && exit 1"

+     "test": "eslint app/* && depcheck ."

    },

-   "author": "",

-   "license": "BSD"

+   "author": "Eric Barbour <ebarbour@redhat.com>",

+   "license": "AGPL-3.0"

  }

@@ -1,80 +0,0 @@ 

- import json

- import os

- 

- import hubs.tests

- import hubs.models

- from hubs.app import app

- from hubs.tests import json_path

- 

- 

- class TestFeed(hubs.tests.APPTest):

- 

-     with open(json_path + 'message_involved.json', 'r') as fp:

-         message_involved = json.loads(fp.read())

- 

-     with open(json_path + 'message_not_involved.json', 'r') as fp:

-         message_not_involved = json.loads(fp.read())

- 

-     # TODO this test relies on a specific filter for the atelic user

-     # It would be better to fake preferences on FAS

-     user = hubs.tests.FakeAuthorization('atelic')

- 

-     plugin = 'feed'

- 

-     def test_returns_400_when_no_message(self):

-         payload = {'plugin': 'feed'}

-         response = self.app.get('/api/fedmsg/markup', query_string=payload)

-         self.assertEqual(response.status_code, 400)

- 

-     def test_returns_400_when_no_plugin(self):

-         payload = {'message': json.dumps(self.message_involved)}

-         response = self.app.get('/api/fedmsg/markup', query_string=payload)

-         self.assertEqual(response.status_code, 400)

- 

-     def test_returns_400_when_bad_plugin_name(self):

-         payload = {

-             'message': json.dumps(self.message_involved),

-             'plugin': 'notarealplugin'

-         }

-         response = self.app.get('/api/fedmsg/markup', query_string=payload)

-         self.assertEqual(response.status_code, 400)

- 

-     def test_returns_403_when_not_logged_in(self):

-         payload = {

-             'message': json.dumps(self.message_involved),

-             'plugin': 'feed'

-         }

- 

-         response = self.app.get('/api/fedmsg/markup', query_string=payload)

-         self.assertEqual(response.status_code, 403)

- 

-     def test_returns_match_if_involved(self):

-         payload = {

-             'message': json.dumps(self.message_involved),

-             'plugin': 'feed'

-         }

-         with self.app.session_transaction() as sess:

-             sess['nickname'] = 'atelic'

- 

-         with hubs.tests.auth_set(app, self.user):

-             response = self.app.get('/api/fedmsg/markup', query_string=payload)

- 

-             self.assertEqual(response.status_code, 200)

-             data = json.loads(response.data)

-             self.assertTrue(data)

-             self.assertTrue(isinstance(data[0], dict))

- 

-     def test_returns_no_match_if_not_involved(self):

-         payload = {

-             'message': json.dumps(self.message_not_involved),

-             'plugin': 'feed'

-         }

-         with self.app.session_transaction() as sess:

-             sess['nickname'] = 'atelic'

- 

-         with hubs.tests.auth_set(app, self.user):

-             response = self.app.get('/api/fedmsg/markup', query_string=payload)

- 

-             self.assertEqual(response.status_code, 200)

-             data = json.loads(response.data)

-             self.assertFalse(data)

@@ -268,7 +268,7 @@ 

          with tests.auth_set(app, user):

              result = self.app.get('/ralph/31/edit', follow_redirects=True)

              self.assertEqual(result.status_code, 200)

-             expected_str = '<input id="fmn_context" class="form-control" type="text"'

+             expected_str = '<input id="message_limit" class="form-control" type="text"'

              self.assertTrue(expected_str in result.data)

  

      @unittest.skip("Authorization layer not present yet")

file modified
+18 -12
@@ -34,9 +34,8 @@ 

  bazillion = 1000

  paths = fmn.lib.load_rules(root='fmn.rules')

  

- # No chrome around the feed.

- # from hubs.widgets.chrome import panel

- # chrome = panel()

+ from hubs.widgets.chrome import panel

+ chrome = panel('Live Feed')

  template = templating.environment.get_template('templates/feed.html')

  position = 'left'

  
@@ -82,14 +81,17 @@ 

            default=None,

            validator=validators.username,

            help="A FAS username.")

- @argument(name="fmn_context",

-           default="irc",  # TODO - Make this 'hubs', or...

-           validator=validators.fmn_context,

-           help="A FMN context.")

- def data(session, widget, username, fmn_context):

+ @argument(name="message_limit",

+           default=20,

+           validator=validators.integer,

+           help="Max number of feed messages to display")

+ def data(session, widget, username, message_limit):

      messages = []

      matches = []

-     preference = get_remote_preference(username, fmn_context)

+     # Avoid circular import

+     from hubs.app import app

+     feed_url = app.config['SSE_URL'] + username

+     preference = get_remote_preference(username, 'sse')

      if preference:

          try:

              preference = rehydrate_preference(preference)
@@ -111,7 +113,7 @@ 

              total, pages, rows = datanommer.models.Message.grep(

                  start=end - delta,

                  end=end,

-                 rows_per_page=bazillion,

+                 rows_per_page=message_limit,

                  page=page,

                  order='desc',

                  **fmn_hinting
@@ -151,13 +153,17 @@ 

          # And tack on a unique identifier for each top level entry.

          match['dom_id'] = six.text_type(uuid.uuid4())

      matches = json.dumps(matches, cls=PythonObjectEncoder)

-     return dict(matches=matches)

+     return dict(

+         matches=matches,

+         message_limit=message_limit,

+         feed_url=feed_url

+     )

  

  

  @hint(ubiquitous=True)

  def should_invalidate(message, session, widget):

      username = widget.config['username']

-     fmn_context = widget.config['fmn_context']

+     fmn_context = 'sse'

      preference = get_remote_preference(username, fmn_context)

      if not preference:

          return False

@@ -5,11 +5,11 @@ 

          src ="{{url_for('static', filename='js/hubs.js')}}"></script>

  <script>

   (function() {

-      const FeedElement = React.createElement(Feed, {

-          matches: {{ matches }}

-      });

- 

-      reactRender(FeedElement, document.getElementById('feed'));

+    const FeedElement = React.createElement(Feed, {

+      matches: {{ matches }},

+      url: '{{ feed_url }}',

+      messageLimit: {{ message_limit }}

+    });

+    reactRender(FeedElement, document.getElementById('feed'));

   })();

- 

  </script>

file modified
+1 -1
@@ -4,7 +4,7 @@ 

  fedmsg

  fedmsg_meta_fedora_infrastructure

  flask

- flask-oidc

+ flask-oidc>=1.0.3

  fmn.lib

  fmn.rules

  gunicorn

  • In order to comply with JavaScript style standards, change 4 spaces to 2.
  • Adds eslint rules using Airbnb style guide
  • Moves Feed widget to use external SSE

is the rest of the html/js files 4 spaces or 2 spaces?

rebased

7 years ago

Yes, the consistent style for hubs and other fedora applications is 2 spaces for html/js

Yes, 2 spaces is what we maintain. So :thumbsup: for me

:thumbsup: for me as well

Cool, can you confirm #206 fixes the issue you were having earlier with the feed?

3 new commits added

  • Change feed widget to card format
  • Add class properties req and new data to top
  • Move Feed to external SSE
7 years ago

2 new commits added

  • Make feed limit configurable
  • JSX style rules from Airbnb style guide
7 years ago

Should this come from the configuration ?

Yes, it would be nice to have a config variable in the template environment for this. I also didn't want to commit skrzepto's IP.

we can user 'localhost' as a placeholder for now if that helps?

We can just access the flask config via {{ config }} in the templates

rebased

7 years ago

rebased

7 years ago

8 new commits added

  • Use application config to set the SSE url
  • Fix human readable time, clean up JS/JSX
  • Make feed limit configurable
  • JSX style rules from Airbnb style guide
  • Change feed widget to card format
  • Add class properties req and new data to top
  • Move Feed to external SSE
  • Move 4 spaces to 2 in JSX files
7 years ago

@pingou @sayanchowdhury This is ready for another round of review.

Just to summarize the changes here:

  • The majority of the changed files are just code indentation for the JSX files to use a consistent 2 spaces
  • Remove of a lot of the temporary things implemented before regarding the Feed widget (e.g. the api route)
  • Move to getting streamed messages from an external server. This value is changed in default_config.py and you will need to change it in order to test.
  • Add ability to configure limit for messages in Feed widget
  • Pin minimum requirement for flask-oidc

This line is too long I can't read it in the PR page w/o scrolling on the side

Ah, right. I added this before the -c arg. Changing

rebased

7 years ago

8 new commits added

  • Use application config to set the SSE url
  • Fix human readable time, clean up JS/JSX
  • Make feed limit configurable
  • JSX style rules from Airbnb style guide
  • Change feed widget to card format
  • Add class properties req and new data to top
  • Move Feed to external SSE
  • Move 4 spaces to 2 in JSX files
7 years ago

Bringing up review on this again since this is now a blocker for #158

is this fmn_context still used?

Couple of comments but nothing else stands out for me, but I am definitely not the best person to review w/ my lack of understanding of the JS code :(

Maybe @sayanchowdhury could do another pass at it?

Hm, I hadn't thought about this before. After testing it a bit this morning, it looks like it isn't needed. Removed

rebased

7 years ago

1 new commit added

  • Fix tests fmn_context change + fedmsg api removal
7 years ago

1 new commit added

  • Explain seemingly unused import in index.jsx
7 years ago

Looked through the code :thumbsup: for me.

@atelic should we merge this now?

Pull-Request has been merged by atelic

7 years ago