From commits-return-694-archive-asf-public=cust-asf.ponee.io@superset.incubator.apache.org Tue Feb 27 02:02:45 2018 Return-Path: X-Original-To: archive-asf-public@cust-asf.ponee.io Delivered-To: archive-asf-public@cust-asf.ponee.io Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by mx-eu-01.ponee.io (Postfix) with SMTP id 1F0A318066D for ; Tue, 27 Feb 2018 02:02:44 +0100 (CET) Received: (qmail 37524 invoked by uid 500); 27 Feb 2018 01:02:44 -0000 Mailing-List: contact commits-help@superset.incubator.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@superset.incubator.apache.org Delivered-To: mailing list commits@superset.incubator.apache.org Received: (qmail 37515 invoked by uid 99); 27 Feb 2018 01:02:44 -0000 Received: from ec2-52-202-80-70.compute-1.amazonaws.com (HELO gitbox.apache.org) (52.202.80.70) by apache.org (qpsmtpd/0.29) with ESMTP; Tue, 27 Feb 2018 01:02:44 +0000 Received: by gitbox.apache.org (ASF Mail Server at gitbox.apache.org, from userid 33) id 8061D84FD9; Tue, 27 Feb 2018 01:02:43 +0000 (UTC) Date: Tue, 27 Feb 2018 01:02:43 +0000 To: "commits@superset.apache.org" Subject: [incubator-superset] branch master updated: New Landing Page v1.0 (#4463) MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit Message-ID: <151969336337.29710.5884276723096188690@gitbox.apache.org> From: maximebeauchemin@apache.org X-Git-Host: gitbox.apache.org X-Git-Repo: incubator-superset X-Git-Refname: refs/heads/master X-Git-Reftype: branch X-Git-Oldrev: 56f65158a29e93a008a3c1c4572b8f28d4c44528 X-Git-Newrev: 11ea83ecf13edb78877b89ffa73d3fc0575eb0b3 X-Git-Rev: 11ea83ecf13edb78877b89ffa73d3fc0575eb0b3 X-Git-NotificationType: ref_changed_plus_diff X-Git-Multimail-Version: 1.5.dev Auto-Submitted: auto-generated 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 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: {row.item_title}, + name: {row.item_title}, + 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}`} /> ); 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 loading; } return ( - -
- - {this.state.data.map((row, i) => ( - - {columns.map((col) => { - if (row.hasOwnProperty('_' + col)) { - return ( - ); - } - return ; - })} - - ))} -
- {row[col]} - {row[col]}
-
-
+ + {this.state.data.map((row, i) => ( + + {columns.map((col) => { + if (row.hasOwnProperty('_' + col)) { + return ( + ); + } + return ; + })} + + ))} +
+ {row[col]} + {row[col]}
); } } 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 (
- - -

Dashboards

- - - -
-
- -
+ + + + +

{t('Recently Viewed')}

+
+
+ +
+
+ + + +

{t('Favorites')}

+
+
+ +
+
+ + + +

{t('Dashboards')}

+ + + +
+
+ +
+
+
); } } + +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( - , + , 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(), ).to.equal(true); }); - it('renders 2 Col', () => { + it('renders 4 Tab, Panel, and Row components', () => { const wrapper = shallow(); - 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.