diff --git a/superset/assets/javascripts/profile/components/RecentActivity.jsx b/superset/assets/javascripts/profile/components/RecentActivity.jsx
index 476f6d6180a41..7099a08649095 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 b14d6f6865a84..1e67426eea592 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 ;
}
return (
-
-
-
- {this.state.data.map((row, i) => (
-
- {columns.map((col) => {
- if (row.hasOwnProperty('_' + col)) {
- return (
-
- {row[col]}
- | );
- }
- return {row[col]} | ;
- })}
-
- ))}
-
-
-
+
+ {this.state.data.map((row, i) => (
+
+ {columns.map((col) => {
+ if (row.hasOwnProperty('_' + col)) {
+ return (
+
+ {row[col]}
+ | );
+ }
+ return {row[col]} | ;
+ })}
+
+ ))}
+
);
}
}
diff --git a/superset/assets/javascripts/welcome/App.jsx b/superset/assets/javascripts/welcome/App.jsx
index 78674c48f0e9e..8f63719ea5128 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 3994b9908b5ec..df0f774a81bf5 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 472c0e22e7ef7..1fc2987a57275 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 c1c62796e081c..b71f73173248c 100755
--- a/superset/views/core.py
+++ b/superset/views/core.py
@@ -4,7 +4,6 @@
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.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 @@
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 @@ def welcome(self):
"""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 @@ def profile(self, username):
"""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 0000000000000..b1f3fa2db7ffe
--- /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