Skip to content

Commit

Permalink
New Landing Page v1.0 (apache#4463)
Browse files Browse the repository at this point in the history
* Updated welcome landing page

* fixed test and linting

* linting

* addressing comments

* fix test

* fix test

* remove unneeded var

* add magic comments
  • Loading branch information
hughhhh authored and mistercrunch committed Feb 27, 2018
1 parent 6ca00de commit ad40c62
Show file tree
Hide file tree
Showing 7 changed files with 158 additions and 84 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}));
Expand All @@ -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>
);
Expand Down
39 changes: 18 additions & 21 deletions superset/assets/javascripts/profile/components/TableLoader.jsx
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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] !== '_');
}
Expand All @@ -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>
);
}
}
Expand Down
69 changes: 50 additions & 19 deletions superset/assets/javascripts/welcome/App.jsx
Original file line number Diff line number Diff line change
@@ -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) {
Expand All @@ -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;
7 changes: 6 additions & 1 deletion superset/assets/javascripts/welcome/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
);
10 changes: 5 additions & 5 deletions superset/assets/spec/javascripts/welcome/App_spec.jsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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);
});
});
43 changes: 8 additions & 35 deletions superset/views/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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')
Expand Down Expand Up @@ -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',
Expand All @@ -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),
)
Expand Down
67 changes: 67 additions & 0 deletions superset/views/utils.py
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit ad40c62

Please sign in to comment.