-
Notifications
You must be signed in to change notification settings - Fork 14.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
User profile pages (favorites, created content, recent activity, secu…
…rity & access) (#1615) * Super * User profile page * Fixing python style * Python unit tests * Touchups and js tests * Addressing comments
- Loading branch information
1 parent
5ae98bc
commit 7e1852e
Showing
28 changed files
with
903 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import React from 'react'; | ||
import { Col, Row, Tabs, Tab, Panel } from 'react-bootstrap'; | ||
import Favorites from './Favorites'; | ||
import UserInfo from './UserInfo'; | ||
import Security from './Security'; | ||
import RecentActivity from './RecentActivity'; | ||
import CreatedContent from './CreatedContent'; | ||
|
||
const propTypes = { | ||
user: React.PropTypes.object.isRequired, | ||
}; | ||
|
||
export default function App(props) { | ||
return ( | ||
<div className="container app"> | ||
<Row> | ||
<Col md={3}> | ||
<UserInfo user={props.user} /> | ||
</Col> | ||
<Col md={9}> | ||
<Tabs id="options"> | ||
<Tab eventKey={1} title={<div><i className="fa fa-star" /> Favorites</div>}> | ||
<Panel><Favorites user={props.user} /></Panel> | ||
</Tab> | ||
<Tab | ||
eventKey={2} | ||
title={ | ||
<div><i className="fa fa-paint-brush" /> Created Content</div> | ||
} | ||
> | ||
<Panel> | ||
<CreatedContent user={props.user} /> | ||
</Panel> | ||
</Tab> | ||
<Tab eventKey={3} title={<div><i className="fa fa-list" /> Recent Activity</div>}> | ||
<Panel> | ||
<RecentActivity user={props.user} /> | ||
</Panel> | ||
</Tab> | ||
<Tab eventKey={4} title={<div><i className="fa fa-lock" /> Security & Access</div>}> | ||
<Panel> | ||
<Security user={props.user} /> | ||
</Panel> | ||
</Tab> | ||
</Tabs> | ||
</Col> | ||
</Row> | ||
</div> | ||
); | ||
} | ||
App.propTypes = propTypes; |
67 changes: 67 additions & 0 deletions
67
superset/assets/javascripts/profile/components/CreatedContent.jsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
import React from 'react'; | ||
import moment from 'moment'; | ||
import TableLoader from './TableLoader'; | ||
|
||
const propTypes = { | ||
user: React.PropTypes.object.isRequired, | ||
}; | ||
|
||
class CreatedContent extends React.PureComponent { | ||
constructor(props) { | ||
super(props); | ||
this.state = { | ||
dashboardsLoading: true, | ||
slicesLoading: true, | ||
dashboards: [], | ||
slices: [], | ||
}; | ||
} | ||
renderSliceTable() { | ||
const mutator = (data) => data.map(slice => ({ | ||
slice: <a href={slice.url}>{slice.title}</a>, | ||
favorited: moment.utc(slice.dttm).fromNow(), | ||
_favorited: slice.dttm, | ||
})); | ||
return ( | ||
<TableLoader | ||
dataEndpoint={`/superset/created_slices/${this.props.user.userId}/`} | ||
className="table table-condensed" | ||
columns={['slice', 'favorited']} | ||
mutator={mutator} | ||
noDataText="No slices" | ||
sortable | ||
/> | ||
); | ||
} | ||
renderDashboardTable() { | ||
const mutator = (data) => data.map(dash => ({ | ||
dashboard: <a href={dash.url}>{dash.title}</a>, | ||
favorited: moment.utc(dash.dttm).fromNow(), | ||
_favorited: dash.dttm, | ||
})); | ||
return ( | ||
<TableLoader | ||
className="table table-condensed" | ||
mutator={mutator} | ||
dataEndpoint={`/superset/created_dashboards/${this.props.user.userId}/`} | ||
noDataText="No dashboards" | ||
columns={['dashboard', 'favorited']} | ||
sortable | ||
/> | ||
); | ||
} | ||
render() { | ||
return ( | ||
<div> | ||
<h3>Dashboards</h3> | ||
{this.renderDashboardTable()} | ||
<hr /> | ||
<h3>Slices</h3> | ||
{this.renderSliceTable()} | ||
</div> | ||
); | ||
} | ||
} | ||
CreatedContent.propTypes = propTypes; | ||
|
||
export default CreatedContent; |
64 changes: 64 additions & 0 deletions
64
superset/assets/javascripts/profile/components/Favorites.jsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
import React from 'react'; | ||
import moment from 'moment'; | ||
import TableLoader from './TableLoader'; | ||
|
||
const propTypes = { | ||
user: React.PropTypes.object.isRequired, | ||
}; | ||
|
||
export default class Favorites extends React.PureComponent { | ||
constructor(props) { | ||
super(props); | ||
this.state = { | ||
dashboardsLoading: true, | ||
slicesLoading: true, | ||
dashboards: [], | ||
slices: [], | ||
}; | ||
} | ||
renderSliceTable() { | ||
const mutator = (data) => data.map(slice => ({ | ||
slice: <a href={slice.url}>{slice.title}</a>, | ||
favorited: moment.utc(slice.dttm).fromNow(), | ||
_favorited: slice.dttm, | ||
})); | ||
return ( | ||
<TableLoader | ||
dataEndpoint={`/superset/fave_slices/${this.props.user.userId}/`} | ||
className="table table-condensed" | ||
columns={['slice', 'favorited']} | ||
mutator={mutator} | ||
noDataText="No favorite slices yet, go click on stars!" | ||
sortable | ||
/> | ||
); | ||
} | ||
renderDashboardTable() { | ||
const mutator = (data) => data.map(dash => ({ | ||
dashboard: <a href={dash.url}>{dash.title}</a>, | ||
favorited: moment.utc(dash.dttm).fromNow(), | ||
})); | ||
return ( | ||
<TableLoader | ||
className="table table-condensed" | ||
mutator={mutator} | ||
dataEndpoint={`/superset/fave_dashboards/${this.props.user.userId}/`} | ||
noDataText="No favorite dashboards yet, go click on stars!" | ||
columns={['dashboard', 'favorited']} | ||
sortable | ||
/> | ||
); | ||
} | ||
render() { | ||
return ( | ||
<div> | ||
<h3>Dashboards</h3> | ||
{this.renderDashboardTable()} | ||
<hr /> | ||
<h3>Slices</h3> | ||
{this.renderSliceTable()} | ||
</div> | ||
); | ||
} | ||
} | ||
Favorites.propTypes = propTypes; |
44 changes: 44 additions & 0 deletions
44
superset/assets/javascripts/profile/components/RecentActivity.jsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
import React from 'react'; | ||
import TableLoader from './TableLoader'; | ||
import moment from 'moment'; | ||
import $ from 'jquery'; | ||
|
||
const propTypes = { | ||
user: React.PropTypes.object, | ||
}; | ||
|
||
export default class RecentActivity extends React.PureComponent { | ||
constructor(props) { | ||
super(props); | ||
this.state = { | ||
recentActions: [], | ||
}; | ||
} | ||
|
||
componentWillMount() { | ||
$.get(`/superset/recent_activity/${this.props.user.userId}/`, (data) => { | ||
this.setState({ recentActions: data }); | ||
}); | ||
} | ||
render() { | ||
const mutator = function (data) { | ||
return data.map(row => ({ | ||
action: row.action, | ||
item: <a href={row.item_url}>{row.item_title}</a>, | ||
time: moment.utc(row.time).fromNow(), | ||
_time: row.time, | ||
})); | ||
}; | ||
return ( | ||
<div> | ||
<TableLoader | ||
className="table table-condensed" | ||
mutator={mutator} | ||
sortable | ||
dataEndpoint={`/superset/recent_activity/${this.props.user.userId}/`} | ||
/> | ||
</div> | ||
); | ||
} | ||
} | ||
RecentActivity.propTypes = propTypes; |
41 changes: 41 additions & 0 deletions
41
superset/assets/javascripts/profile/components/Security.jsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import React from 'react'; | ||
import { Badge, Label } from 'react-bootstrap'; | ||
|
||
const propTypes = { | ||
user: React.PropTypes.object.isRequired, | ||
}; | ||
export default function Security({ user }) { | ||
return ( | ||
<div> | ||
<div className="roles"> | ||
<h4> | ||
Roles <Badge>{Object.keys(user.roles).length}</Badge> | ||
</h4> | ||
{Object.keys(user.roles).map(role => <Label key={role}>{role}</Label>)} | ||
<hr /> | ||
</div> | ||
<div className="databases"> | ||
{user.permissions.database_access && | ||
<div> | ||
<h4> | ||
Databases <Badge>{user.permissions.database_access.length}</Badge> | ||
</h4> | ||
{user.permissions.database_access.map(role => <Label key={role}>{role}</Label>)} | ||
<hr /> | ||
</div> | ||
} | ||
</div> | ||
<div className="datasources"> | ||
{user.permissions.datasource_access && | ||
<div> | ||
<h4> | ||
Datasources <Badge>{user.permissions.datasource_access.length}</Badge> | ||
</h4> | ||
{user.permissions.datasource_access.map(role => <Label key={role}>{role}</Label>)} | ||
</div> | ||
} | ||
</div> | ||
</div> | ||
); | ||
} | ||
Security.propTypes = propTypes; |
64 changes: 64 additions & 0 deletions
64
superset/assets/javascripts/profile/components/TableLoader.jsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
import React from 'react'; | ||
import { Table, Tr, Td } from 'reactable'; | ||
import { Collapse } from 'react-bootstrap'; | ||
import $ from 'jquery'; | ||
|
||
const propTypes = { | ||
dataEndpoint: React.PropTypes.string.isRequired, | ||
mutator: React.PropTypes.func, | ||
columns: React.PropTypes.arrayOf(React.PropTypes.string), | ||
}; | ||
|
||
export default class TableLoader extends React.PureComponent { | ||
constructor(props) { | ||
super(props); | ||
this.state = { | ||
isLoading: true, | ||
data: [], | ||
}; | ||
} | ||
componentWillMount() { | ||
$.get(this.props.dataEndpoint, (data) => { | ||
let actualData = data; | ||
if (this.props.mutator) { | ||
actualData = this.props.mutator(data); | ||
} | ||
this.setState({ data: actualData, isLoading: false }); | ||
}); | ||
} | ||
render() { | ||
const tableProps = Object.assign({}, this.props); | ||
let columns = this.props.columns; | ||
if (!columns && this.state.data.length > 0) { | ||
columns = Object.keys(this.state.data[0]).filter(col => col[0] !== '_'); | ||
} | ||
delete tableProps.dataEndpoint; | ||
delete tableProps.mutator; | ||
delete tableProps.columns; | ||
if (this.state.isLoading) { | ||
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> | ||
); | ||
} | ||
} | ||
TableLoader.propTypes = propTypes; |
48 changes: 48 additions & 0 deletions
48
superset/assets/javascripts/profile/components/UserInfo.jsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
import React from 'react'; | ||
import Gravatar from 'react-gravatar'; | ||
import moment from 'moment'; | ||
import { Panel } from 'react-bootstrap'; | ||
|
||
const propTypes = { | ||
user: React.PropTypes.object.isRequired, | ||
}; | ||
const UserInfo = ({ user }) => ( | ||
<div> | ||
<a href="https://en.gravatar.com/"> | ||
<Gravatar | ||
email={user.email} | ||
width="100%" | ||
height="" | ||
alt="Profile picture provided by Gravatar" | ||
className="img-rounded" | ||
style={{ borderRadius: 15 }} | ||
/> | ||
</a> | ||
<hr /> | ||
<Panel> | ||
<h3> | ||
<strong>{user.firstName} {user.lastName}</strong> | ||
</h3> | ||
<h4 className="username"> | ||
<i className="fa fa-user-o" /> {user.username} | ||
</h4> | ||
<hr /> | ||
<p> | ||
<i className="fa fa-clock-o" /> joined {moment(user.createdOn, 'YYYYMMDD').fromNow()} | ||
</p> | ||
<p className="email"> | ||
<i className="fa fa-envelope-o" /> {user.email} | ||
</p> | ||
<p className="roles"> | ||
<i className="fa fa-lock" /> {Object.keys(user.roles).join(', ')} | ||
</p> | ||
<p> | ||
<i className="fa fa-key" /> | ||
<span className="text-muted">id:</span> | ||
<span className="user-id">{user.userId}</span> | ||
</p> | ||
</Panel> | ||
</div> | ||
); | ||
UserInfo.propTypes = propTypes; | ||
export default UserInfo; |
Oops, something went wrong.