mistercrunch closed pull request #4147: Make Welcome page into a simple React 
app
URL: https://github.com/apache/incubator-superset/pull/4147
 
 
   

This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:

As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):

diff --git a/.travis.yml b/.travis.yml
index d32057c8d7..89ca5b6e5e 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -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
diff --git a/superset/assets/javascripts/welcome.js 
b/superset/assets/javascripts/welcome.js
deleted file mode 100644
index cfece7e682..0000000000
--- a/superset/assets/javascripts/welcome.js
+++ /dev/null
@@ -1,57 +0,0 @@
-/* eslint no-unused-vars: 0 */
-import d3 from 'd3';
-import dt from 'datatables.net-bs';
-import 'datatables.net-bs/css/dataTables.bootstrap.css';
-
-import '../stylesheets/welcome.css';
-import { appSetup } from './common';
-
-appSetup();
-
-dt(window, $);
-
-function modelViewTable(selector, modelView, orderCol, order) {
-  // Builds a dataTable from a flask appbuilder api endpoint
-  let url = '/' + modelView.toLowerCase() + '/api/read';
-  url += '?_oc_' + modelView + '=' + orderCol;
-  url += '&_od_' + modelView + '=' + order;
-  $.getJSON(url, function (data) {
-    const columns = ['dashboard_link', 'creator', 'modified'];
-    const tableData = $.map(data.result, function (el) {
-      const row = $.map(columns, function (col) {
-        return el[col];
-      });
-      return [row];
-    });
-    const cols = $.map(columns, function (col) {
-      return { sTitle: data.label_columns[col] };
-    });
-    const panel = $(selector).parents('.panel');
-    panel.find('img.loading').remove();
-    $(selector).DataTable({
-      aaData: tableData,
-      aoColumns: cols,
-      bPaginate: true,
-      pageLength: 10,
-      bLengthChange: false,
-      aaSorting: [],
-      searching: true,
-      bInfo: false,
-    });
-    // Hack to move the searchbox in the right spot
-    const search = panel.find('.dataTables_filter input');
-    search.addClass('form-control').detach();
-    search.appendTo(panel.find('.search'));
-    panel.find('.dataTables_filter').remove();
-    // Hack to display the page navigator properly
-    panel.find('.col-sm-5').remove();
-    const nav = panel.find('.col-sm-7');
-    nav.removeClass('col-sm-7');
-    nav.addClass('col-sm-12');
-    $(selector).slideDown();
-    $('[data-toggle="tooltip"]').tooltip({ container: 'body' });
-  });
-}
-$(document).ready(function () {
-  modelViewTable('#dash_table', 'DashboardModelViewAsync', 'changed_on', 
'desc');
-});
diff --git a/superset/assets/javascripts/welcome/App.jsx 
b/superset/assets/javascripts/welcome/App.jsx
new file mode 100644
index 0000000000..78674c48f0
--- /dev/null
+++ b/superset/assets/javascripts/welcome/App.jsx
@@ -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>
+    );
+  }
+}
diff --git a/superset/assets/javascripts/welcome/DashboardTable.jsx 
b/superset/assets/javascripts/welcome/DashboardTable.jsx
new file mode 100644
index 0000000000..78d4bdd57b
--- /dev/null
+++ b/superset/assets/javascripts/welcome/DashboardTable.jsx
@@ -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;
diff --git a/superset/assets/javascripts/welcome/index.jsx 
b/superset/assets/javascripts/welcome/index.jsx
new file mode 100644
index 0000000000..3994b9908b
--- /dev/null
+++ b/superset/assets/javascripts/welcome/index.jsx
@@ -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,
+);
diff --git a/superset/assets/spec/javascripts/welcome/App_spec.jsx 
b/superset/assets/spec/javascripts/welcome/App_spec.jsx
new file mode 100644
index 0000000000..472c0e22e7
--- /dev/null
+++ b/superset/assets/spec/javascripts/welcome/App_spec.jsx
@@ -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);
+  });
+});
diff --git a/superset/assets/spec/javascripts/welcome/DashboardTable_spec.jsx 
b/superset/assets/spec/javascripts/welcome/DashboardTable_spec.jsx
new file mode 100644
index 0000000000..2a9727942d
--- /dev/null
+++ b/superset/assets/spec/javascripts/welcome/DashboardTable_spec.jsx
@@ -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);
+  });
+});
diff --git a/superset/assets/stylesheets/superset.less 
b/superset/assets/stylesheets/superset.less
index 14c7519bca..c5a8ea7d45 100644
--- a/superset/assets/stylesheets/superset.less
+++ b/superset/assets/stylesheets/superset.less
@@ -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;
+}
diff --git a/superset/assets/webpack.config.js 
b/superset/assets/webpack.config.js
index ca1465e703..1dce5245f1 100644
--- a/superset/assets/webpack.config.js
+++ b/superset/assets/webpack.config.js
@@ -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: {
diff --git a/superset/models/helpers.py b/superset/models/helpers.py
index 02b2cf225c..948cf0d49e 100644
--- a/superset/models/helpers.py
+++ b/superset/models/helpers.py
@@ -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)
diff --git a/superset/templates/superset/welcome.html 
b/superset/templates/superset/welcome.html
deleted file mode 100644
index 4db2cd3ab5..0000000000
--- a/superset/templates/superset/welcome.html
+++ /dev/null
@@ -1,31 +0,0 @@
-{% extends "superset/basic.html" %}
-
-{% block title %}{{ _("Welcome!") }}{% endblock %}
-
-{% block body %}
-<div class="container welcome">
-  {% include 'superset/flash_wrapper.html' %}
-  <div class="panel panel-default">
-    <div class="panel-heading">
-      <div class="panel-title">
-        <div class="row">
-          <div class="col-md-6">
-            <h2>{{ _("Dashboards") }}</h2>
-          </div>
-          <div class="col-md-6">
-            <div class="search-container pull-right">
-              <i class="fa fa-search"></i>
-              <span class="search"></span>
-            </div>
-          </div>
-        </div>
-      </div>
-    </div>
-    <div class="panel-body">
-      <img class="loading" src="/static/assets/images/loading.gif"/>
-      <table id="dash_table" class="table" width="100%"></table>
-    </div>
-  </div>
-</div>
-{% endblock %}
-
diff --git a/superset/views/core.py b/superset/views/core.py
index 2f0c0e5c24..5a02fbcf09 100755
--- a/superset/views/core.py
+++ b/superset/views/core.py
@@ -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'),
@@ -2457,8 +2460,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>/')
@@ -2504,7 +2514,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),
         )


 

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
[email protected]


With regards,
Apache Git Services

Reply via email to