diff --git a/superset/assets/javascripts/profile/components/Favorites.jsx b/superset/assets/javascripts/profile/components/Favorites.jsx index 7aa31f19fc67a..147d1b210f4ff 100644 --- a/superset/assets/javascripts/profile/components/Favorites.jsx +++ b/superset/assets/javascripts/profile/components/Favorites.jsx @@ -19,6 +19,7 @@ export default class Favorites extends React.PureComponent { renderSliceTable() { const mutator = (data) => data.map(slice => ({ slice: {slice.title}, + creator: {slice.creator}, favorited: moment.utc(slice.dttm).fromNow(), _favorited: slice.dttm, })); @@ -26,7 +27,7 @@ export default class Favorites extends React.PureComponent { data.map(dash => ({ dashboard: {dash.title}, + creator: {dash.creator}, favorited: moment.utc(dash.dttm).fromNow(), })); return ( @@ -44,7 +46,7 @@ export default class Favorites extends React.PureComponent { mutator={mutator} dataEndpoint={`/superset/fave_dashboards/${this.props.user.userId}/`} noDataText="No favorite dashboards yet, go click on stars!" - columns={['dashboard', 'favorited']} + columns={['dashboard', 'creator', 'favorited']} sortable /> ); diff --git a/superset/assets/javascripts/profile/index.jsx b/superset/assets/javascripts/profile/index.jsx index f32932b182b01..913dfca705051 100644 --- a/superset/assets/javascripts/profile/index.jsx +++ b/superset/assets/javascripts/profile/index.jsx @@ -15,7 +15,6 @@ const profileViewContainer = document.getElementById('app'); const bootstrap = JSON.parse(profileViewContainer.getAttribute('data-bootstrap')); const user = bootstrap.user; - ReactDOM.render( , profileViewContainer diff --git a/superset/models.py b/superset/models.py index a5e3d3a572693..882634680a27b 100644 --- a/superset/models.py +++ b/superset/models.py @@ -149,13 +149,19 @@ def changed_by_fk(cls): # noqa Integer, ForeignKey('ab_user.id'), default=cls.get_user_id, onupdate=cls.get_user_id, nullable=True) - @renders('created_on') + def _user_link(self, user): + if not user: + return '' + url = '/superset/profile/{}/'.format(user.username) + return '{}'.format(url, escape(user) or '') + + @renders('created_by') def creator(self): # noqa - return '{}'.format(self.created_by or '') + return self._user_link(self.created_by) @property def changed_by_(self): - return '{}'.format(self.changed_by or '') + return self._user_link(self.changed_by) @renders('changed_on') def changed_on_(self): diff --git a/superset/views.py b/superset/views.py index a375056a39277..0dcee47908a77 100755 --- a/superset/views.py +++ b/superset/views.py @@ -1796,7 +1796,7 @@ def recent_activity(self, user_id): ) .filter( sqla.and_( - M.Log.action != 'queries', + ~M.Log.action.in_(('queries', 'shortner', 'sql_json')), M.Log.user_id == user_id, ) ) @@ -1845,13 +1845,21 @@ def fave_dashboards(self, user_id): models.FavStar.dttm.desc() ) ) - payload = [{ - 'id': o.Dashboard.id, - 'dashboard': o.Dashboard.dashboard_link(), - 'title': o.Dashboard.dashboard_title, - 'url': o.Dashboard.url, - 'dttm': o.dttm, - } for o in qry.all()] + payload = [] + for o in qry.all(): + d = { + 'id': o.Dashboard.id, + 'dashboard': o.Dashboard.dashboard_link(), + 'title': o.Dashboard.dashboard_title, + 'url': o.Dashboard.url, + 'dttm': o.dttm, + } + if o.Dashboard.created_by: + user = o.Dashboard.created_by + d['creator'] = str(user) + d['creator_url'] = '/superset/profile/{}/'.format( + user.username) + payload.append(d) return Response( json.dumps(payload, default=utils.json_int_dttm_ser), mimetype="application/json") @@ -1934,12 +1942,20 @@ def fave_slices(self, user_id): models.FavStar.dttm.desc() ) ) - payload = [{ - 'id': o.Slice.id, - 'title': o.Slice.slice_name, - 'url': o.Slice.slice_url, - 'dttm': o.dttm, - } for o in qry.all()] + payload = [] + for o in qry.all(): + d = { + 'id': o.Slice.id, + 'title': o.Slice.slice_name, + 'url': o.Slice.slice_url, + 'dttm': o.dttm, + } + if o.Slice.created_by: + user = o.Slice.created_by + d['creator'] = str(user) + d['creator_url'] = '/superset/profile/{}/'.format( + user.username) + payload.append(d) return Response( json.dumps(payload, default=utils.json_int_dttm_ser), mimetype="application/json") @@ -2613,15 +2629,15 @@ def profile(self, username): ) roles = {} from collections import defaultdict - permissions = defaultdict(list) + permissions = defaultdict(set) for role in user.roles: - perms = [] + perms = set() for perm in role.permissions: - perms.append( + perms.add( (perm.permission.name, perm.view_menu.name) ) if perm.permission.name in ('datasource_access', 'database_access'): - permissions[perm.permission.name].append(perm.view_menu.name) + permissions[perm.permission.name].add(perm.view_menu.name) roles[role.name] = [ [perm.permission.name, perm.view_menu.name] for perm in role.permissions @@ -2643,7 +2659,8 @@ def profile(self, username): 'superset/profile.html', title=user.username + "'s profile", navbar_container=True, - bootstrap_data=json.dumps(payload)) + bootstrap_data=json.dumps(payload, default=utils.json_iso_dttm_ser) + ) @has_access @expose("/sqllab") diff --git a/tests/base_tests.py b/tests/base_tests.py index 3a66c756de48d..36f3d8f3a9049 100644 --- a/tests/base_tests.py +++ b/tests/base_tests.py @@ -140,19 +140,23 @@ def get_druid_ds_by_name(self, name): return db.session.query(models.DruidDatasource).filter_by( datasource_name=name).first() - def get_resp(self, url, data=None, follow_redirects=True): + def get_resp( + self, url, data=None, follow_redirects=True, raise_on_error=True): """Shortcut to get the parsed results while following redirects""" if data: resp = self.client.post( url, data=data, follow_redirects=follow_redirects) - return resp.data.decode('utf-8') else: resp = self.client.get(url, follow_redirects=follow_redirects) - return resp.data.decode('utf-8') + if raise_on_error and resp.status_code > 400: + raise Exception( + "http request failed with code {}".format(resp.status_code)) + return resp.data.decode('utf-8') - def get_json_resp(self, url, data=None): + def get_json_resp( + self, url, data=None, follow_redirects=True, raise_on_error=True): """Shortcut to get the parsed results while following redirects""" - resp = self.get_resp(url, data=data) + resp = self.get_resp(url, data, follow_redirects, raise_on_error) return json.loads(resp) def get_main_database(self, session): @@ -200,6 +204,7 @@ def run_sql(self, sql, client_id, user_name=None): dbid = self.get_main_database(db.session).id resp = self.get_json_resp( '/superset/sql_json/', + raise_on_error=False, data=dict(database_id=dbid, sql=sql, select_as_create_as=False, client_id=client_id), ) diff --git a/tests/core_tests.py b/tests/core_tests.py index d79dd67baaab6..2c2267a3bdb7a 100644 --- a/tests/core_tests.py +++ b/tests/core_tests.py @@ -478,6 +478,23 @@ def test_fetch_all_tables(self): def test_user_profile(self): self.login(username='admin') + slc = self.get_slice("Girls", db.session) + + # Setting some faves + url = '/superset/favstar/Slice/{}/select/'.format(slc.id) + resp = self.get_json_resp(url) + self.assertEqual(resp['count'], 1) + + dash = ( + db.session + .query(models.Dashboard) + .filter_by(slug="births") + .first() + ) + url = '/superset/favstar/Dashboard/{}/select/'.format(dash.id) + resp = self.get_json_resp(url) + self.assertEqual(resp['count'], 1) + userid = appbuilder.sm.find_user('admin').id resp = self.get_resp('/superset/profile/admin/') self.assertIn('"app"', resp) diff --git a/tests/sqllab_tests.py b/tests/sqllab_tests.py index cd8a93d806bc2..00c574dc741d4 100644 --- a/tests/sqllab_tests.py +++ b/tests/sqllab_tests.py @@ -118,8 +118,7 @@ def test_search_query_on_db_id(self): self.run_some_queries() self.login('admin') # Test search queries on database Id - resp = self.get_resp('/superset/search_queries?database_id=1') - data = json.loads(resp) + data = self.get_json_resp('/superset/search_queries?database_id=1') self.assertEquals(3, len(data)) db_ids = [data[k]['dbId'] for k in data] self.assertEquals([1, 1, 1], db_ids) @@ -164,8 +163,8 @@ def test_search_query_on_status(self): def test_search_query_on_text(self): self.run_some_queries() self.login('admin') - resp = self.get_resp('/superset/search_queries?search_text=permission') - data = json.loads(resp) + url = '/superset/search_queries?search_text=permission' + data = self.get_json_resp(url) self.assertEquals(1, len(data)) self.assertIn('permission', list(data.values())[0]['sql'])