superset-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From maximebeauche...@apache.org
Subject [incubator-superset] branch master updated: New Landing Page v1.0 (#4463)
Date Tue, 27 Feb 2018 01:02:43 GMT
This is an automated email from the ASF dual-hosted git repository.

maximebeauchemin pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-superset.git


The following commit(s) were added to refs/heads/master by this push:
     new 11ea83e  New Landing Page v1.0 (#4463)
11ea83e is described below

commit 11ea83ecf13edb78877b89ffa73d3fc0575eb0b3
Author: Hugh A. Miles II <hughmil3s@gmail.com>
AuthorDate: Mon Feb 26 17:02:41 2018 -0800

    New Landing Page v1.0 (#4463)
    
    * Updated welcome landing page
    
    * fixed test and linting
    
    * linting
    
    * addressing comments
    
    * fix test
    
    * fix test
    
    * remove unneeded var
    
    * add magic comments
---
 .../profile/components/RecentActivity.jsx          |  7 ++-
 .../javascripts/profile/components/TableLoader.jsx | 39 ++++++------
 superset/assets/javascripts/welcome/App.jsx        | 69 ++++++++++++++++------
 superset/assets/javascripts/welcome/index.jsx      |  7 ++-
 .../assets/spec/javascripts/welcome/App_spec.jsx   | 10 ++--
 superset/views/core.py                             | 43 +++-----------
 superset/views/utils.py                            | 67 +++++++++++++++++++++
 7 files changed, 158 insertions(+), 84 deletions(-)

diff --git a/superset/assets/javascripts/profile/components/RecentActivity.jsx b/superset/assets/javascripts/profile/components/RecentActivity.jsx
index 476f6d6..7099a08 100644
--- a/superset/assets/javascripts/profile/components/RecentActivity.jsx
+++ b/superset/assets/javascripts/profile/components/RecentActivity.jsx
@@ -10,10 +10,11 @@ const propTypes = {
 
 export default class RecentActivity extends React.PureComponent {
   render() {
+    const rowLimit = 50;
     const mutator = function (data) {
       return data.map(row => ({
-        action: row.action,
-        item: <a href={row.item_url}>{row.item_title}</a>,
+        name: <a href={row.item_url}>{row.item_title}</a>,
+        type: row.action,
         time: moment.utc(row.time).fromNow(),
         _time: row.time,
       }));
@@ -24,7 +25,7 @@ export default class RecentActivity extends React.PureComponent {
           className="table table-condensed"
           mutator={mutator}
           sortable
-          dataEndpoint={`/superset/recent_activity/${this.props.user.userId}/`}
+          dataEndpoint={`/superset/recent_activity/${this.props.user.userId}/?limit=${rowLimit}`}
         />
       </div>
     );
diff --git a/superset/assets/javascripts/profile/components/TableLoader.jsx b/superset/assets/javascripts/profile/components/TableLoader.jsx
index b14d6f6..1e67426 100644
--- a/superset/assets/javascripts/profile/components/TableLoader.jsx
+++ b/superset/assets/javascripts/profile/components/TableLoader.jsx
@@ -1,9 +1,10 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 import { Table, Tr, Td } from 'reactable';
-import { Collapse } from 'react-bootstrap';
 import $ from 'jquery';
 
+import '../../../stylesheets/reactable-pagination.css';
+
 const propTypes = {
   dataEndpoint: PropTypes.string.isRequired,
   mutator: PropTypes.func,
@@ -29,7 +30,7 @@ export default class TableLoader extends React.PureComponent {
   }
   render() {
     const tableProps = Object.assign({}, this.props);
-    let columns = this.props.columns;
+    let { columns } = this.props;
     if (!columns && this.state.data.length > 0) {
       columns = Object.keys(this.state.data[0]).filter(col => col[0] !== '_');
     }
@@ -40,25 +41,21 @@ export default class TableLoader extends React.PureComponent {
       return <img alt="loading" width="25" src="/static/assets/images/loading.gif" />;
     }
     return (
-      <Collapse in transitionAppear >
-        <div>
-          <Table {...tableProps}>
-            {this.state.data.map((row, i) => (
-              <Tr key={i}>
-                {columns.map((col) => {
-                  if (row.hasOwnProperty('_' + col)) {
-                    return (
-                      <Td key={col} column={col} value={row['_' + col]}>
-                        {row[col]}
-                      </Td>);
-                  }
-                  return <Td key={col} column={col}>{row[col]}</Td>;
-                })}
-              </Tr>
-            ))}
-          </Table>
-        </div>
-      </Collapse>
+      <Table {...tableProps} className="table" itemsPerPage={50} style={{ textTransform:
'capitalize' }}>
+        {this.state.data.map((row, i) => (
+          <Tr key={i}>
+            {columns.map((col) => {
+              if (row.hasOwnProperty('_' + col)) {
+                return (
+                  <Td key={col} column={col} value={row['_' + col]}>
+                    {row[col]}
+                  </Td>);
+              }
+              return <Td key={col} column={col}>{row[col]}</Td>;
+            })}
+          </Tr>
+        ))}
+      </Table>
     );
   }
 }
diff --git a/superset/assets/javascripts/welcome/App.jsx b/superset/assets/javascripts/welcome/App.jsx
index 78674c4..8f63719 100644
--- a/superset/assets/javascripts/welcome/App.jsx
+++ b/superset/assets/javascripts/welcome/App.jsx
@@ -1,7 +1,14 @@
 import React from 'react';
-import { Panel, Row, Col, FormControl } from 'react-bootstrap';
-
+import PropTypes from 'prop-types';
+import { Panel, Row, Col, Tabs, Tab, FormControl } from 'react-bootstrap';
+import RecentActivity from '../profile/components/RecentActivity';
+import Favorites from '../profile/components/Favorites';
 import DashboardTable from './DashboardTable';
+import { t } from '../locales';
+
+const propTypes = {
+  user: PropTypes.object.isRequired,
+};
 
 export default class App extends React.PureComponent {
   constructor(props) {
@@ -17,24 +24,48 @@ export default class App extends React.PureComponent {
   render() {
     return (
       <div className="container welcome">
-        <Panel>
-          <Row>
-            <Col md={8}><h2>Dashboards</h2></Col>
-            <Col md={4}>
-              <FormControl
-                type="text"
-                bsSize="sm"
-                style={{ marginTop: '25px' }}
-                placeholder="Search"
-                value={this.state.search}
-                onChange={this.onSearchChange}
-              />
-            </Col>
-          </Row>
-          <hr />
-          <DashboardTable search={this.state.search} />
-        </Panel>
+        <Tabs defaultActiveKey={1} id="uncontrolled-tab-example">
+          <Tab eventKey={1} title={t('Recently Viewed')}>
+            <Panel>
+              <Row>
+                <Col md={8}><h2>{t('Recently Viewed')}</h2></Col>
+              </Row>
+              <hr />
+              <RecentActivity user={this.props.user} />
+            </Panel>
+          </Tab>
+          <Tab eventKey={2} title={t('Favorites')}>
+            <Panel>
+              <Row>
+                <Col md={8}><h2>{t('Favorites')}</h2></Col>
+              </Row>
+              <hr />
+              <Favorites user={this.props.user} />
+            </Panel>
+          </Tab>
+          <Tab eventKey={3} title={t('Dashboards')}>
+            <Panel>
+              <Row>
+                <Col md={8}><h2>{t('Dashboards')}</h2></Col>
+                <Col md={4}>
+                  <FormControl
+                    type="text"
+                    bsSize="sm"
+                    style={{ marginTop: '25px' }}
+                    placeholder="Search"
+                    value={this.state.search}
+                    onChange={this.onSearchChange}
+                  />
+                </Col>
+              </Row>
+              <hr />
+              <DashboardTable search={this.state.search} />
+            </Panel>
+          </Tab>
+        </Tabs>
       </div>
     );
   }
 }
+
+App.propTypes = propTypes;
diff --git a/superset/assets/javascripts/welcome/index.jsx b/superset/assets/javascripts/welcome/index.jsx
index 3994b99..df0f774 100644
--- a/superset/assets/javascripts/welcome/index.jsx
+++ b/superset/assets/javascripts/welcome/index.jsx
@@ -10,8 +10,13 @@ appSetup();
 
 const container = document.getElementById('app');
 const bootstrap = JSON.parse(container.getAttribute('data-bootstrap'));
+const user = {
+  ...bootstrap.user,
+};
 
 ReactDOM.render(
-  <App />,
+  <App
+    user={user}
+  />,
   container,
 );
diff --git a/superset/assets/spec/javascripts/welcome/App_spec.jsx b/superset/assets/spec/javascripts/welcome/App_spec.jsx
index 472c0e2..1fc2987 100644
--- a/superset/assets/spec/javascripts/welcome/App_spec.jsx
+++ b/superset/assets/spec/javascripts/welcome/App_spec.jsx
@@ -1,5 +1,5 @@
 import React from 'react';
-import { Panel, Col, Row } from 'react-bootstrap';
+import { Panel, Row, Tab } from 'react-bootstrap';
 import { shallow } from 'enzyme';
 import { describe, it } from 'mocha';
 import { expect } from 'chai';
@@ -13,10 +13,10 @@ describe('App', () => {
       React.isValidElement(<App {...mockedProps} />),
     ).to.equal(true);
   });
-  it('renders 2 Col', () => {
+  it('renders 4 Tab, Panel, and Row components', () => {
     const wrapper = shallow(<App {...mockedProps} />);
-    expect(wrapper.find(Panel)).to.have.length(1);
-    expect(wrapper.find(Row)).to.have.length(1);
-    expect(wrapper.find(Col)).to.have.length(2);
+    expect(wrapper.find(Tab)).to.have.length(3);
+    expect(wrapper.find(Panel)).to.have.length(3);
+    expect(wrapper.find(Row)).to.have.length(3);
   });
 });
diff --git a/superset/views/core.py b/superset/views/core.py
index c1c6279..b71f731 100755
--- a/superset/views/core.py
+++ b/superset/views/core.py
@@ -4,7 +4,6 @@ from __future__ import division
 from __future__ import print_function
 from __future__ import unicode_literals
 
-from collections import defaultdict
 from datetime import datetime, timedelta
 import json
 import logging
@@ -21,7 +20,6 @@ from flask_appbuilder import expose, SimpleFormView
 from flask_appbuilder.actions import action
 from flask_appbuilder.models.sqla.interface import SQLAInterface
 from flask_appbuilder.security.decorators import has_access_api
-from flask_appbuilder.security.sqla import models as ab_models
 from flask_babel import gettext as __
 from flask_babel import lazy_gettext as _
 import pandas as pd
@@ -51,6 +49,7 @@ from .base import (
     generate_download_headers, get_error_msg, get_user_roles,
     json_error_response, SupersetFilter, SupersetModelView, YamlExportMixin,
 )
+from .utils import bootstrap_user_data
 
 config = app.config
 stats_logger = config.get('STATS_LOGGER')
@@ -2555,9 +2554,12 @@ class Superset(BaseSupersetView):
         """Personalized welcome page"""
         if not g.user or not g.user.get_id():
             return redirect(appbuilder.get_url_for_login)
+
         payload = {
+            'user': bootstrap_user_data(),
             'common': self.common_bootsrap_payload(),
         }
+
         return self.render_template(
             'superset/basic.html',
             entry='welcome',
@@ -2571,44 +2573,15 @@ class Superset(BaseSupersetView):
         """User profile page"""
         if not username and g.user:
             username = g.user.username
-        user = (
-            db.session.query(ab_models.User)
-            .filter_by(username=username)
-            .one()
-        )
-        roles = {}
-        permissions = defaultdict(set)
-        for role in user.roles:
-            perms = set()
-            for perm in role.permissions:
-                if perm.permission and perm.view_menu:
-                    perms.add(
-                        (perm.permission.name, perm.view_menu.name),
-                    )
-                    if perm.permission.name in ('datasource_access', 'database_access'):
-                        permissions[perm.permission.name].add(perm.view_menu.name)
-            roles[role.name] = [
-                [perm.permission.name, perm.view_menu.name]
-                for perm in role.permissions
-                if perm.permission and perm.view_menu
-            ]
+
         payload = {
-            'user': {
-                'username': user.username,
-                'firstName': user.first_name,
-                'lastName': user.last_name,
-                'userId': user.id,
-                'isActive': user.is_active(),
-                'createdOn': user.created_on.isoformat(),
-                'email': user.email,
-                'roles': roles,
-                'permissions': permissions,
-            },
+            'user': bootstrap_user_data(username, include_perms=True),
             'common': self.common_bootsrap_payload(),
         }
+
         return self.render_template(
             'superset/basic.html',
-            title=user.username + "'s profile",
+            title=username + "'s profile",
             entry='profile',
             bootstrap_data=json.dumps(payload, default=utils.json_iso_dttm_ser),
         )
diff --git a/superset/views/utils.py b/superset/views/utils.py
new file mode 100644
index 0000000..b1f3fa2
--- /dev/null
+++ b/superset/views/utils.py
@@ -0,0 +1,67 @@
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+from __future__ import unicode_literals
+
+from collections import defaultdict
+
+from flask import g
+from flask_appbuilder.security.sqla import models as ab_models
+
+from superset import db
+
+
+def bootstrap_user_data(username=None, include_perms=False):
+    if username:
+        username = username
+    else:
+        username = g.user.username
+
+    user = (
+        db.session.query(ab_models.User)
+        .filter_by(username=username)
+        .one()
+    )
+
+    payload = {
+        'username': user.username,
+        'firstName': user.first_name,
+        'lastName': user.last_name,
+        'userId': user.id,
+        'isActive': user.is_active(),
+        'createdOn': user.created_on.isoformat(),
+        'email': user.email,
+    }
+
+    if include_perms:
+        roles, permissions = get_permissions(user)
+        payload['roles'] = roles
+        payload['permissions'] = permissions
+
+    return payload
+
+
+def get_permissions(user):
+    if not user.roles:
+        raise AttributeError('User object does not have roles')
+
+    roles = {}
+    permissions = defaultdict(set)
+    for role in user.roles:
+        perms = set()
+        for perm in role.permissions:
+            if perm.permission and perm.view_menu:
+                perms.add(
+                    (perm.permission.name, perm.view_menu.name),
+                )
+                if perm.permission.name in ('datasource_access',
+                                            'database_access'):
+                    permissions[perm.permission.name].add(perm.view_menu.name)
+        roles[role.name] = [
+            [perm.permission.name, perm.view_menu.name]
+            for perm in role.permissions
+            if perm.permission and perm.view_menu
+        ]
+
+    return roles, permissions

-- 
To stop receiving notification emails like this one, please contact
maximebeauchemin@apache.org.

Mime
View raw message