Skip to content

Commit

Permalink
Make Welcome page into a simple React app (apache#4147)
Browse files Browse the repository at this point in the history
* Make Welcome page into a simple React app

This removes a dependency on datatables, we should be able to get rid
of it as we re-write the Table and PivotTable viz

* tests/lint

* Bump node version to latest
  • Loading branch information
mistercrunch authored Jan 8, 2018
1 parent b9af019 commit c49fb0a
Show file tree
Hide file tree
Showing 12 changed files with 221 additions and 93 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ cache:
env:
global:
- TRAVIS_CACHE=$HOME/.travis_cache/
- TRAVIS_NODE_VERSION="7.10.0"
- TRAVIS_NODE_VERSION="8.8.1"
matrix:
- TOX_ENV=flake8
- TOX_ENV=javascript
Expand Down
57 changes: 0 additions & 57 deletions superset/assets/javascripts/welcome.js

This file was deleted.

40 changes: 40 additions & 0 deletions superset/assets/javascripts/welcome/App.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import React from 'react';
import { Panel, Row, Col, FormControl } from 'react-bootstrap';

import DashboardTable from './DashboardTable';

export default class App extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
search: '',
};
this.onSearchChange = this.onSearchChange.bind(this);
}
onSearchChange(event) {
this.setState({ search: event.target.value });
}
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>
</div>
);
}
}
71 changes: 71 additions & 0 deletions superset/assets/javascripts/welcome/DashboardTable.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/* eslint no-unused-vars: 0 */
import React from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
import { Table, Tr, Td, Thead, Th, unsafe } from 'reactable';

import '../../stylesheets/reactable-pagination.css';

const $ = window.$ = require('jquery');

const propTypes = {
search: PropTypes.string,
};

export default class DashboardTable extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
dashboards: false,
};
}
componentDidMount() {
const url = (
'/dashboardmodelviewasync/api/read' +
'?_oc_DashboardModelViewAsync=changed_on' +
'&_od_DashboardModelViewAsync=desc');
$.getJSON(url, (data) => {
this.setState({ dashboards: data.result });
});
}
render() {
if (this.state.dashboards) {
return (
<Table
className="table"
sortable={['dashboard', 'creator', 'modified']}
filterBy={this.props.search}
filterable={['dashboard', 'creator']}
itemsPerPage={50}
hideFilterInput
columns={[
{ key: 'dashboard', label: 'Dashboard' },
{ key: 'creator', label: 'Creator' },
{ key: 'modified', label: 'Modified' },
]}
defaultSort={{ column: 'modified', direction: 'desc' }}
>
{this.state.dashboards.map(o => (
<Tr key={o.id}>
<Td column="dashboard" value={o.dashboard_title}>
<a href={o.url}>{o.dashboard_title}</a>
</Td>
<Td column="creator" value={o.changed_by_name}>
{unsafe(o.creator)}
</Td>
<Td column="modified" value={o.changed_on} className="text-muted">
{unsafe(o.modified)}
</Td>
</Tr>))}
</Table>
);
}
return (
<img
className="loading"
alt="Loading..."
src="/static/assets/images/loading.gif"
/>);
}
}
DashboardTable.propTypes = propTypes;
17 changes: 17 additions & 0 deletions superset/assets/javascripts/welcome/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/* eslint no-unused-vars: 0 */
import React from 'react';
import ReactDOM from 'react-dom';
import { Panel, Row, Col, FormControl } from 'react-bootstrap';

import { appSetup } from '../common';
import App from './App';

appSetup();

const container = document.getElementById('app');
const bootstrap = JSON.parse(container.getAttribute('data-bootstrap'));

ReactDOM.render(
<App />,
container,
);
22 changes: 22 additions & 0 deletions superset/assets/spec/javascripts/welcome/App_spec.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import React from 'react';
import { Panel, Col, Row } from 'react-bootstrap';
import { shallow } from 'enzyme';
import { describe, it } from 'mocha';
import { expect } from 'chai';

import App from '../../../javascripts/welcome/App';

describe('App', () => {
const mockedProps = {};
it('is valid', () => {
expect(
React.isValidElement(<App {...mockedProps} />),
).to.equal(true);
});
it('renders 2 Col', () => {
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);
});
});
31 changes: 31 additions & 0 deletions superset/assets/spec/javascripts/welcome/DashboardTable_spec.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import React from 'react';
import { mount } from 'enzyme';
import { describe, it } from 'mocha';
import { expect } from 'chai';

import DashboardTable from '../../../javascripts/welcome/DashboardTable';

const $ = window.$ = require('jquery');


describe('DashboardTable', () => {
const mockedProps = {};
let stub;
beforeEach(() => {
stub = sinon.stub($, 'getJSON');
});
afterEach(() => {
stub.restore();
});

it('is valid', () => {
expect(
React.isValidElement(<DashboardTable {...mockedProps} />),
).to.equal(true);
});
it('renders', () => {
const wrapper = mount(<DashboardTable {...mockedProps} />);
expect(stub.callCount).to.equal(1);
expect(wrapper.find('img')).to.have.length(1);
});
});
21 changes: 21 additions & 0 deletions superset/assets/stylesheets/superset.less
Original file line number Diff line number Diff line change
Expand Up @@ -402,3 +402,24 @@ g.annotation-container {
.stroke-primary {
stroke: @brand-primary;
}
.reactable-header-sortable{
position:relative;
padding-right: 40px;
}

.reactable-header-sortable::before{
font: normal normal normal 14px/1 FontAwesome;
content: "\f0dc";
position: absolute;
top: 17px;
right: 15px;
color: @brand-primary;
}
.reactable-header-sort-asc::before{
content: "\f0de";
color: @brand-primary;
}
.reactable-header-sort-desc::before{
content: "\f0dd";
color: @brand-primary;
}
2 changes: 1 addition & 1 deletion superset/assets/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const config = {
explore: ['babel-polyfill', APP_DIR + '/javascripts/explore/index.jsx'],
dashboard: ['babel-polyfill', APP_DIR + '/javascripts/dashboard/index.jsx'],
sqllab: ['babel-polyfill', APP_DIR + '/javascripts/SqlLab/index.jsx'],
welcome: ['babel-polyfill', APP_DIR + '/javascripts/welcome.js'],
welcome: ['babel-polyfill', APP_DIR + '/javascripts/welcome/index.jsx'],
profile: ['babel-polyfill', APP_DIR + '/javascripts/profile/index.jsx'],
},
output: {
Expand Down
5 changes: 5 additions & 0 deletions superset/models/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,11 @@ def _user_link(self, user):
url = '/superset/profile/{}/'.format(user.username)
return Markup('<a href="{}">{}</a>'.format(url, escape(user) or ''))

def changed_by_name(self):
if self.created_by:
return escape('{}'.format(self.created_by))
return ''

@renders('created_by')
def creator(self): # noqa
return self._user_link(self.created_by)
Expand Down
31 changes: 0 additions & 31 deletions superset/templates/superset/welcome.html

This file was deleted.

15 changes: 12 additions & 3 deletions superset/views/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -619,7 +619,10 @@ def download_dashboards(self):


class DashboardModelViewAsync(DashboardModelView): # noqa
list_columns = ['dashboard_link', 'creator', 'modified', 'dashboard_title']
list_columns = [
'id', 'dashboard_link', 'creator', 'modified', 'dashboard_title',
'changed_on', 'url', 'changed_by_name',
]
label_columns = {
'dashboard_link': _('Dashboard'),
'dashboard_title': _('Title'),
Expand Down Expand Up @@ -2463,8 +2466,15 @@ def welcome(self):
"""Personalized welcome page"""
if not g.user or not g.user.get_id():
return redirect(appbuilder.get_url_for_login)
payload = {
'common': self.common_bootsrap_payload(),
}
return self.render_template(
'superset/welcome.html', entry='welcome', utils=utils)
'superset/basic.html',
entry='welcome',
title='Superset',
bootstrap_data=json.dumps(payload, default=utils.json_iso_dttm_ser),
)

@has_access
@expose('/profile/<username>/')
Expand Down Expand Up @@ -2510,7 +2520,6 @@ def profile(self, username):
return self.render_template(
'superset/basic.html',
title=user.username + "'s profile",
navbar_container=True,
entry='profile',
bootstrap_data=json.dumps(payload, default=utils.json_iso_dttm_ser),
)
Expand Down

0 comments on commit c49fb0a

Please sign in to comment.