mirror of https://github.com/apache/superset.git
refactor: Removes the Profile feature (#26462)
This commit is contained in:
parent
b245e66198
commit
8f8c435d7c
|
@ -77,7 +77,6 @@
|
|||
| can select star on Superset | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | O |
|
||||
| can warm up cache on Superset | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | O |
|
||||
| can sqllab table viz on Superset | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| can profile on Superset | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | O |
|
||||
| can available domains on Superset | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | O |
|
||||
| can request access on Superset | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | O |
|
||||
| can dashboard on Superset | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | O |
|
||||
|
|
|
@ -39,6 +39,7 @@ assists people when migrating to a new version.
|
|||
- [26331](https://github.com/apache/superset/issues/26331): Removes the deprecated `DISABLE_DATASET_SOURCE_EDIT` feature flag. The previous value of the feature flag was `False` and now the feature is permanently removed.
|
||||
- [26636](https://github.com/apache/superset/issues/26636): Sets the `DASHBOARD_VIRTUALIZATION` feature flag to `True` by default. This feature was introduced by [21438](https://github.com/apache/superset/pull/21438) and will enable virtualization when rendering a dashboard's charts in an attempt to reduce the number of elements (DOM nodes) rendered at once. This is especially useful for large dashboards.
|
||||
- [26637](https://github.com/apache/superset/issues/26637): Sets the `DRILL_BY` feature flag to `True` by default given that the feature has been tested for a while and reached a stable state.
|
||||
- [26462](https://github.com/apache/superset/issues/26462): Removes the Profile feature given that it's not actively maintained and not widely used.
|
||||
|
||||
### Potential Downtime
|
||||
|
||||
|
|
|
@ -112,7 +112,6 @@
|
|||
"react-dnd-html5-backend": "^11.1.3",
|
||||
"react-dom": "^16.13.0",
|
||||
"react-draggable": "^4.4.6",
|
||||
"react-gravatar": "^2.6.1",
|
||||
"react-hot-loader": "^4.13.1",
|
||||
"react-intersection-observer": "^9.4.1",
|
||||
"react-js-cron": "^1.2.0",
|
||||
|
@ -25235,14 +25234,6 @@
|
|||
"integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==",
|
||||
"devOptional": true
|
||||
},
|
||||
"node_modules/charenc": {
|
||||
"version": "0.0.2",
|
||||
"resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz",
|
||||
"integrity": "sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc=",
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/check-more-types": {
|
||||
"version": "2.24.0",
|
||||
"resolved": "https://registry.npmjs.org/check-more-types/-/check-more-types-2.24.0.tgz",
|
||||
|
@ -27066,14 +27057,6 @@
|
|||
"node": ">=4.8"
|
||||
}
|
||||
},
|
||||
"node_modules/crypt": {
|
||||
"version": "0.0.2",
|
||||
"resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz",
|
||||
"integrity": "sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs=",
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/crypto-browserify": {
|
||||
"version": "3.12.0",
|
||||
"resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz",
|
||||
|
@ -36753,11 +36736,6 @@
|
|||
"integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/is-retina": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/is-retina/-/is-retina-1.0.3.tgz",
|
||||
"integrity": "sha1-10AbKGvqKuN/Ykd1iN5QTQuGR+M="
|
||||
},
|
||||
"node_modules/is-scoped": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-scoped/-/is-scoped-2.1.0.tgz",
|
||||
|
@ -44884,16 +44862,6 @@
|
|||
"integrity": "sha512-QBcepxkFxuGk12q4G0KuNbuU3UCXhDROxWZllaNZSpBivkHl2z8qNvi7UGE/WLJt+c7GTC4jigYtur+JDL+40A==",
|
||||
"deprecated": "Use cephes instead, for a more complete and well-tested module"
|
||||
},
|
||||
"node_modules/md5": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/md5/-/md5-2.2.1.tgz",
|
||||
"integrity": "sha1-U6s41f48iJG6RlMp6iP6wFQBJvk=",
|
||||
"dependencies": {
|
||||
"charenc": "~0.0.1",
|
||||
"crypt": "~0.0.1",
|
||||
"is-buffer": "~1.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/md5.js": {
|
||||
"version": "1.3.5",
|
||||
"resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
|
||||
|
@ -51757,39 +51725,6 @@
|
|||
"resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.0.tgz",
|
||||
"integrity": "sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA=="
|
||||
},
|
||||
"node_modules/react-gravatar": {
|
||||
"version": "2.6.3",
|
||||
"resolved": "https://registry.npmjs.org/react-gravatar/-/react-gravatar-2.6.3.tgz",
|
||||
"integrity": "sha1-VAfrash+gw4qNN63YNKkxATrHaw=",
|
||||
"dependencies": {
|
||||
"is-retina": "^1.0.3",
|
||||
"md5": "^2.1.0",
|
||||
"query-string": "^4.2.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/react-gravatar/node_modules/query-string": {
|
||||
"version": "4.3.4",
|
||||
"resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz",
|
||||
"integrity": "sha1-u7aTucqRXCMlFbIosaArYJBD2+s=",
|
||||
"dependencies": {
|
||||
"object-assign": "^4.1.0",
|
||||
"strict-uri-encode": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-gravatar/node_modules/strict-uri-encode": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz",
|
||||
"integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-helmet-async": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/react-helmet-async/-/react-helmet-async-1.3.0.tgz",
|
||||
|
@ -84150,11 +84085,6 @@
|
|||
"integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==",
|
||||
"devOptional": true
|
||||
},
|
||||
"charenc": {
|
||||
"version": "0.0.2",
|
||||
"resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz",
|
||||
"integrity": "sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc="
|
||||
},
|
||||
"check-more-types": {
|
||||
"version": "2.24.0",
|
||||
"resolved": "https://registry.npmjs.org/check-more-types/-/check-more-types-2.24.0.tgz",
|
||||
|
@ -85609,11 +85539,6 @@
|
|||
"which": "^1.2.9"
|
||||
}
|
||||
},
|
||||
"crypt": {
|
||||
"version": "0.0.2",
|
||||
"resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz",
|
||||
"integrity": "sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs="
|
||||
},
|
||||
"crypto-browserify": {
|
||||
"version": "3.12.0",
|
||||
"resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz",
|
||||
|
@ -92968,11 +92893,6 @@
|
|||
"integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==",
|
||||
"dev": true
|
||||
},
|
||||
"is-retina": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/is-retina/-/is-retina-1.0.3.tgz",
|
||||
"integrity": "sha1-10AbKGvqKuN/Ykd1iN5QTQuGR+M="
|
||||
},
|
||||
"is-scoped": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-scoped/-/is-scoped-2.1.0.tgz",
|
||||
|
@ -99270,16 +99190,6 @@
|
|||
"resolved": "https://registry.npmjs.org/mathfn/-/mathfn-1.2.0.tgz",
|
||||
"integrity": "sha512-QBcepxkFxuGk12q4G0KuNbuU3UCXhDROxWZllaNZSpBivkHl2z8qNvi7UGE/WLJt+c7GTC4jigYtur+JDL+40A=="
|
||||
},
|
||||
"md5": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/md5/-/md5-2.2.1.tgz",
|
||||
"integrity": "sha1-U6s41f48iJG6RlMp6iP6wFQBJvk=",
|
||||
"requires": {
|
||||
"charenc": "~0.0.1",
|
||||
"crypt": "~0.0.1",
|
||||
"is-buffer": "~1.1.1"
|
||||
}
|
||||
},
|
||||
"md5.js": {
|
||||
"version": "1.3.5",
|
||||
"resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
|
||||
|
@ -104452,32 +104362,6 @@
|
|||
"resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.0.tgz",
|
||||
"integrity": "sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA=="
|
||||
},
|
||||
"react-gravatar": {
|
||||
"version": "2.6.3",
|
||||
"resolved": "https://registry.npmjs.org/react-gravatar/-/react-gravatar-2.6.3.tgz",
|
||||
"integrity": "sha1-VAfrash+gw4qNN63YNKkxATrHaw=",
|
||||
"requires": {
|
||||
"is-retina": "^1.0.3",
|
||||
"md5": "^2.1.0",
|
||||
"query-string": "^4.2.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"query-string": {
|
||||
"version": "4.3.4",
|
||||
"resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz",
|
||||
"integrity": "sha1-u7aTucqRXCMlFbIosaArYJBD2+s=",
|
||||
"requires": {
|
||||
"object-assign": "^4.1.0",
|
||||
"strict-uri-encode": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"strict-uri-encode": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz",
|
||||
"integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM="
|
||||
}
|
||||
}
|
||||
},
|
||||
"react-helmet-async": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/react-helmet-async/-/react-helmet-async-1.3.0.tgz",
|
||||
|
|
|
@ -178,7 +178,6 @@
|
|||
"react-dnd-html5-backend": "^11.1.3",
|
||||
"react-dom": "^16.13.0",
|
||||
"react-draggable": "^4.4.6",
|
||||
"react-gravatar": "^2.6.1",
|
||||
"react-hot-loader": "^4.13.1",
|
||||
"react-intersection-observer": "^9.4.1",
|
||||
"react-js-cron": "^1.2.0",
|
||||
|
|
|
@ -1,122 +0,0 @@
|
|||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { render, screen } from 'spec/helpers/testing-library';
|
||||
import { Provider } from 'react-redux';
|
||||
import fetchMock from 'fetch-mock';
|
||||
import { storeWithState } from 'spec/fixtures/mockStore';
|
||||
import ToastContainer from 'src/components/MessageToasts/ToastContainer';
|
||||
import TableLoader, { TableLoaderProps } from '.';
|
||||
|
||||
const NO_DATA_TEXT = 'No data available';
|
||||
const MOCK_GLOB = 'glob:*/api/v1/mock';
|
||||
|
||||
fetchMock.get(MOCK_GLOB, [
|
||||
{ id: 1, name: 'John Doe' },
|
||||
{ id: 2, name: 'Jane Doe' },
|
||||
]);
|
||||
|
||||
const defaultProps: TableLoaderProps = {
|
||||
dataEndpoint: '/api/v1/mock',
|
||||
addDangerToast: jest.fn(),
|
||||
noDataText: NO_DATA_TEXT,
|
||||
};
|
||||
|
||||
function renderWithProps(props: TableLoaderProps = defaultProps) {
|
||||
return render(
|
||||
<Provider store={storeWithState({})}>
|
||||
<TableLoader {...props} />
|
||||
<ToastContainer />
|
||||
</Provider>,
|
||||
);
|
||||
}
|
||||
|
||||
test('renders loading and table', async () => {
|
||||
renderWithProps();
|
||||
|
||||
expect(screen.getByRole('status')).toBeInTheDocument();
|
||||
expect(await screen.findByRole('table')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('renders with column names', async () => {
|
||||
renderWithProps({
|
||||
...defaultProps,
|
||||
columns: ['id_modified', 'name_modified'],
|
||||
});
|
||||
|
||||
const columnHeaders = await screen.findAllByRole('columnheader');
|
||||
|
||||
expect(columnHeaders[0]).toHaveTextContent('id_modified');
|
||||
expect(columnHeaders[1]).toHaveTextContent('name_modified');
|
||||
});
|
||||
|
||||
test('renders without mutator', async () => {
|
||||
renderWithProps();
|
||||
|
||||
expect(await screen.findAllByRole('row')).toHaveLength(3);
|
||||
expect(await screen.findAllByRole('columnheader')).toHaveLength(2);
|
||||
expect(await screen.findAllByRole('cell')).toHaveLength(4);
|
||||
});
|
||||
|
||||
test('renders with mutator', async () => {
|
||||
const mutator = function (data: { id: number; name: string }[]) {
|
||||
return data.map(row => ({
|
||||
id: row.id,
|
||||
name: <h4>{row.name}</h4>,
|
||||
}));
|
||||
};
|
||||
|
||||
renderWithProps({ ...defaultProps, mutator });
|
||||
|
||||
expect(await screen.findAllByRole('heading', { level: 4 })).toHaveLength(2);
|
||||
});
|
||||
|
||||
test('renders empty message', async () => {
|
||||
fetchMock.mock(MOCK_GLOB, [], {
|
||||
overwriteRoutes: true,
|
||||
});
|
||||
|
||||
renderWithProps();
|
||||
|
||||
expect(await screen.findByText('No data available')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('renders blocked message', async () => {
|
||||
fetchMock.mock(MOCK_GLOB, 403, {
|
||||
overwriteRoutes: true,
|
||||
});
|
||||
|
||||
renderWithProps();
|
||||
|
||||
expect(
|
||||
await screen.findByText('Access to user activity data is restricted'),
|
||||
).toBeInTheDocument();
|
||||
expect(screen.queryByRole('alert')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('renders error message', async () => {
|
||||
fetchMock.mock(MOCK_GLOB, 500, {
|
||||
overwriteRoutes: true,
|
||||
});
|
||||
|
||||
renderWithProps();
|
||||
|
||||
expect(await screen.findByText(NO_DATA_TEXT)).toBeInTheDocument();
|
||||
expect(await screen.findByRole('alert')).toBeInTheDocument();
|
||||
});
|
|
@ -1,98 +0,0 @@
|
|||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import React, { useState, useEffect, useMemo } from 'react';
|
||||
import { t, SupersetClient, JsonObject } from '@superset-ui/core';
|
||||
import TableView, { EmptyWrapperType } from 'src/components/TableView';
|
||||
import withToasts from 'src/components/MessageToasts/withToasts';
|
||||
import Loading from 'src/components/Loading';
|
||||
import '../../assets/stylesheets/reactable-pagination.less';
|
||||
|
||||
export interface TableLoaderProps {
|
||||
dataEndpoint?: string;
|
||||
mutator?: (data: JsonObject) => any[];
|
||||
columns?: string[];
|
||||
noDataText?: string;
|
||||
addDangerToast(text: string): any;
|
||||
}
|
||||
|
||||
const TableLoader = (props: TableLoaderProps) => {
|
||||
const [data, setData] = useState<Array<any>>([]);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [isBlocked, setIsBlocked] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const { dataEndpoint, mutator } = props;
|
||||
if (dataEndpoint) {
|
||||
SupersetClient.get({ endpoint: dataEndpoint })
|
||||
.then(({ json }) => {
|
||||
const data = (mutator ? mutator(json) : json) as Array<any>;
|
||||
setData(data);
|
||||
setIsBlocked(false);
|
||||
setIsLoading(false);
|
||||
})
|
||||
.catch(response => {
|
||||
setIsLoading(false);
|
||||
if (response.status === 403) {
|
||||
setIsBlocked(true);
|
||||
} else {
|
||||
setIsBlocked(false);
|
||||
props.addDangerToast(t('An error occurred'));
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [props]);
|
||||
|
||||
const { columns, noDataText, ...tableProps } = props;
|
||||
|
||||
const memoizedColumns = useMemo(() => {
|
||||
let tableColumns = columns;
|
||||
if (!columns && data.length > 0) {
|
||||
tableColumns = Object.keys(data[0]).filter(col => col[0] !== '_');
|
||||
}
|
||||
return tableColumns
|
||||
? tableColumns.map((column: string) => ({
|
||||
accessor: column,
|
||||
Header: column,
|
||||
}))
|
||||
: [];
|
||||
}, [columns, data]);
|
||||
|
||||
delete tableProps.dataEndpoint;
|
||||
delete tableProps.mutator;
|
||||
|
||||
if (isLoading) {
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
return (
|
||||
<TableView
|
||||
columns={memoizedColumns}
|
||||
data={data}
|
||||
pageSize={50}
|
||||
loading={isLoading}
|
||||
emptyWrapperType={EmptyWrapperType.Small}
|
||||
noDataText={
|
||||
isBlocked ? t('Access to user activity data is restricted') : noDataText
|
||||
}
|
||||
{...tableProps}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default withToasts(TableLoader);
|
|
@ -175,7 +175,6 @@ export const DEFAULT_COMMON_BOOTSTRAP_DATA: CommonBootstrapData = {
|
|||
user_info_url: '',
|
||||
user_login_url: '',
|
||||
user_logout_url: '',
|
||||
user_profile_url: '',
|
||||
locale: '',
|
||||
},
|
||||
settings: [],
|
||||
|
|
|
@ -171,7 +171,7 @@ const mockedProps = {
|
|||
},
|
||||
],
|
||||
brand: {
|
||||
path: '/superset/profile/admin/',
|
||||
path: '/superset/welcome/',
|
||||
icon: '/static/assets/images/superset-logo-horiz.png',
|
||||
alt: 'Superset',
|
||||
width: '126',
|
||||
|
@ -203,7 +203,6 @@ const mockedProps = {
|
|||
user_info_url: '/users/userinfo/',
|
||||
user_logout_url: '/logout/',
|
||||
user_login_url: '/login/',
|
||||
user_profile_url: '/profile/',
|
||||
locale: 'en',
|
||||
version_string: '1.0.0',
|
||||
version_sha: 'randomSHA',
|
||||
|
@ -464,25 +463,6 @@ test('should NOT render the user actions when user is anonymous', async () => {
|
|||
expect(screen.queryByText('User')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should render the Profile link when available', async () => {
|
||||
useSelectorMock.mockReturnValue({ roles: user.roles });
|
||||
const {
|
||||
data: {
|
||||
navbar_right: { user_profile_url },
|
||||
},
|
||||
} = mockedProps;
|
||||
|
||||
render(<Menu {...notanonProps} />, {
|
||||
useRedux: true,
|
||||
useQueryParams: true,
|
||||
useRouter: true,
|
||||
});
|
||||
|
||||
userEvent.hover(screen.getByText('Settings'));
|
||||
const profile = await screen.findByText('Profile');
|
||||
expect(profile).toHaveAttribute('href', user_profile_url);
|
||||
});
|
||||
|
||||
test('should render the About section and version_string, sha or build_number when available', async () => {
|
||||
useSelectorMock.mockReturnValue({ roles: user.roles });
|
||||
const {
|
||||
|
|
|
@ -117,7 +117,6 @@ const createProps = (): RightMenuProps => ({
|
|||
user_info_url: '/users/userinfo/',
|
||||
user_logout_url: '/logout/',
|
||||
user_login_url: '/login/',
|
||||
user_profile_url: '/profile/',
|
||||
locale: 'en',
|
||||
version_string: '1.0.0',
|
||||
version_sha: 'randomSHA',
|
||||
|
|
|
@ -473,11 +473,6 @@ const RightMenu = ({
|
|||
{!navbarRight.user_is_anonymous && [
|
||||
<Menu.Divider key="user-divider" />,
|
||||
<Menu.ItemGroup key="user-section" title={t('User')}>
|
||||
{navbarRight.user_profile_url && (
|
||||
<Menu.Item key="profile">
|
||||
<Link to={navbarRight.user_profile_url}>{t('Profile')}</Link>
|
||||
</Menu.Item>
|
||||
)}
|
||||
{navbarRight.user_info_url && (
|
||||
<Menu.Item key="info">
|
||||
<a href={navbarRight.user_info_url}>{t('Info')}</a>
|
||||
|
|
|
@ -1,50 +0,0 @@
|
|||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import thunk from 'redux-thunk';
|
||||
import configureStore from 'redux-mock-store';
|
||||
import TableLoader from 'src/components/TableLoader';
|
||||
import CreatedContent from './CreatedContent';
|
||||
|
||||
import { user } from './fixtures';
|
||||
|
||||
// store needed for withToasts(TableLoader)
|
||||
const mockStore = configureStore([thunk]);
|
||||
const store = mockStore({});
|
||||
|
||||
describe('CreatedContent', () => {
|
||||
const mockedProps = {
|
||||
user,
|
||||
};
|
||||
|
||||
it('renders 2 TableLoader', () => {
|
||||
const wrapper = shallow(<CreatedContent {...mockedProps} />, {
|
||||
context: { store },
|
||||
});
|
||||
expect(wrapper.find(TableLoader)).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('renders 2 titles', () => {
|
||||
const wrapper = shallow(<CreatedContent {...mockedProps} />, {
|
||||
context: { store },
|
||||
});
|
||||
expect(wrapper.find('h3')).toHaveLength(2);
|
||||
});
|
||||
});
|
|
@ -1,111 +0,0 @@
|
|||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import rison from 'rison';
|
||||
import React from 'react';
|
||||
import { t } from '@superset-ui/core';
|
||||
|
||||
import TableLoader from 'src/components/TableLoader';
|
||||
import {
|
||||
BootstrapUser,
|
||||
ChartResponse,
|
||||
DashboardResponse,
|
||||
} from 'src/types/bootstrapTypes';
|
||||
|
||||
interface CreatedContentProps {
|
||||
user: BootstrapUser;
|
||||
}
|
||||
|
||||
class CreatedContent extends React.PureComponent<CreatedContentProps> {
|
||||
renderSliceTable() {
|
||||
const search = [
|
||||
{ col: 'created_by', opr: 'chart_created_by_me', value: 'me' },
|
||||
];
|
||||
const query = rison.encode({
|
||||
keys: ['none'],
|
||||
columns: ['created_on_delta_humanized', 'slice_name', 'url'],
|
||||
filters: search,
|
||||
order_column: 'changed_on_delta_humanized',
|
||||
order_direction: 'desc',
|
||||
page: 0,
|
||||
page_size: 100,
|
||||
});
|
||||
|
||||
const mutator = (data: ChartResponse) =>
|
||||
data.result.map(chart => ({
|
||||
chart: <a href={chart.url}>{chart.slice_name}</a>,
|
||||
created: chart.created_on_delta_humanized,
|
||||
_created: chart.created_on_delta_humanized,
|
||||
}));
|
||||
return (
|
||||
<TableLoader
|
||||
dataEndpoint={`/api/v1/chart/?q=${query}`}
|
||||
className="table-condensed"
|
||||
columns={['chart', 'created']}
|
||||
mutator={mutator}
|
||||
noDataText={t('No charts')}
|
||||
sortable
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
renderDashboardTable() {
|
||||
const search = [
|
||||
{ col: 'created_by', opr: 'dashboard_created_by_me', value: 'me' },
|
||||
];
|
||||
const query = rison.encode({
|
||||
keys: ['none'],
|
||||
columns: ['created_on_delta_humanized', 'dashboard_title', 'url'],
|
||||
filters: search,
|
||||
order_column: 'changed_on',
|
||||
order_direction: 'desc',
|
||||
page: 0,
|
||||
page_size: 100,
|
||||
});
|
||||
const mutator = (data: DashboardResponse) =>
|
||||
data.result.map(dash => ({
|
||||
dashboard: <a href={dash.url}>{dash.dashboard_title}</a>,
|
||||
created: dash.created_on_delta_humanized,
|
||||
_created: dash.created_on_delta_humanized,
|
||||
}));
|
||||
return (
|
||||
<TableLoader
|
||||
className="table-condensed"
|
||||
mutator={mutator}
|
||||
dataEndpoint={`/api/v1/dashboard/?q=${query}`}
|
||||
noDataText={t('No dashboards')}
|
||||
columns={['dashboard', 'created']}
|
||||
sortable
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<h3>{t('Dashboards')}</h3>
|
||||
{this.renderDashboardTable()}
|
||||
<hr />
|
||||
<h3>{t('Charts')}</h3>
|
||||
{this.renderSliceTable()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default CreatedContent;
|
|
@ -1,50 +0,0 @@
|
|||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import thunk from 'redux-thunk';
|
||||
import configureStore from 'redux-mock-store';
|
||||
import TableLoader from 'src/components/TableLoader';
|
||||
import Favorites from './Favorites';
|
||||
|
||||
import { user } from './fixtures';
|
||||
|
||||
// store needed for withToasts(TableLoader)
|
||||
const mockStore = configureStore([thunk]);
|
||||
const store = mockStore({});
|
||||
|
||||
describe('Favorites', () => {
|
||||
const mockedProps = {
|
||||
user,
|
||||
};
|
||||
|
||||
it('renders 2 TableLoader', () => {
|
||||
const wrapper = shallow(<Favorites {...mockedProps} />, {
|
||||
context: { store },
|
||||
});
|
||||
expect(wrapper.find(TableLoader)).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('renders 2 titles', () => {
|
||||
const wrapper = shallow(<Favorites {...mockedProps} />, {
|
||||
context: { store },
|
||||
});
|
||||
expect(wrapper.find('h3')).toHaveLength(2);
|
||||
});
|
||||
});
|
|
@ -1,107 +0,0 @@
|
|||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import rison from 'rison';
|
||||
import moment from 'moment';
|
||||
import { t } from '@superset-ui/core';
|
||||
import { DashboardResponse, BootstrapUser } from 'src/types/bootstrapTypes';
|
||||
import TableLoader from '../../components/TableLoader';
|
||||
import { Chart } from './types';
|
||||
|
||||
interface FavoritesProps {
|
||||
user: BootstrapUser;
|
||||
}
|
||||
|
||||
export default class Favorites extends React.PureComponent<FavoritesProps> {
|
||||
renderSliceTable() {
|
||||
const mutator = (payload: { result: Chart[] }) =>
|
||||
payload.result.map(slice => ({
|
||||
slice: <a href={slice.slice_url}>{slice.slice_name}</a>,
|
||||
creator: slice.created_by_name,
|
||||
favorited: moment.utc(slice.changed_on_dttm).fromNow(),
|
||||
_favorited: slice.changed_on_dttm,
|
||||
}));
|
||||
|
||||
const query = rison.encode({
|
||||
filters: [
|
||||
{
|
||||
col: 'id',
|
||||
opr: 'chart_is_favorite',
|
||||
value: true,
|
||||
},
|
||||
],
|
||||
order_column: 'slice_name',
|
||||
order_direction: 'asc',
|
||||
page: 0,
|
||||
page_size: 25,
|
||||
});
|
||||
|
||||
return (
|
||||
<TableLoader
|
||||
dataEndpoint={`/api/v1/chart/?q=${query}`}
|
||||
className="table-condensed"
|
||||
columns={['slice', 'creator', 'favorited']}
|
||||
mutator={mutator}
|
||||
noDataText={t('No favorite charts yet, go click on stars!')}
|
||||
sortable
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
renderDashboardTable() {
|
||||
const search = [{ col: 'id', opr: 'dashboard_is_favorite', value: true }];
|
||||
const query = rison.encode({
|
||||
keys: ['none'],
|
||||
columns: ['created_on_delta_humanized', 'dashboard_title', 'url'],
|
||||
filters: search,
|
||||
order_column: 'changed_on',
|
||||
order_direction: 'desc',
|
||||
page: 0,
|
||||
page_size: 100,
|
||||
});
|
||||
const mutator = (data: DashboardResponse) =>
|
||||
data.result.map(dash => ({
|
||||
dashboard: <a href={dash.url}>{dash.dashboard_title}</a>,
|
||||
created: dash.created_on_delta_humanized,
|
||||
_created: dash.created_on_delta_humanized,
|
||||
}));
|
||||
return (
|
||||
<TableLoader
|
||||
className="table-condensed"
|
||||
mutator={mutator}
|
||||
dataEndpoint={`/api/v1/dashboard/?q=${query}`}
|
||||
noDataText={t('No favorite dashboards yet, go click on stars!')}
|
||||
columns={['dashboard', 'creator', 'created']}
|
||||
sortable
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<h3>{t('Dashboards')}</h3>
|
||||
{this.renderDashboardTable()}
|
||||
<hr />
|
||||
<h3>{t('Charts')}</h3>
|
||||
{this.renderSliceTable()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import TableLoader from 'src/components/TableLoader';
|
||||
import RecentActivity from './RecentActivity';
|
||||
|
||||
import { user } from './fixtures';
|
||||
|
||||
describe('RecentActivity', () => {
|
||||
const mockedProps = {
|
||||
user,
|
||||
};
|
||||
it('is valid', () => {
|
||||
expect(React.isValidElement(<RecentActivity {...mockedProps} />)).toBe(
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
it('renders a TableLoader', () => {
|
||||
const wrapper = shallow(<RecentActivity {...mockedProps} />);
|
||||
expect(wrapper.find(TableLoader)).toExist();
|
||||
});
|
||||
});
|
|
@ -1,55 +0,0 @@
|
|||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import moment from 'moment';
|
||||
import { t } from '@superset-ui/core';
|
||||
import rison from 'rison';
|
||||
import TableLoader from 'src/components/TableLoader';
|
||||
import { BootstrapUser } from 'src/types/bootstrapTypes';
|
||||
import { ActivityResult } from './types';
|
||||
|
||||
interface RecentActivityProps {
|
||||
user: BootstrapUser;
|
||||
}
|
||||
|
||||
export default function RecentActivity({ user }: RecentActivityProps) {
|
||||
const rowLimit = 50;
|
||||
const mutator = function (data: ActivityResult) {
|
||||
return data.result
|
||||
.filter(row => row.action === 'dashboard' || row.action === 'explore')
|
||||
.map(row => ({
|
||||
name: <a href={row.item_url}>{row.item_title}</a>,
|
||||
type: row.action,
|
||||
time: moment.utc(row.time).fromNow(),
|
||||
_time: row.time,
|
||||
}));
|
||||
};
|
||||
const params = rison.encode({ page_size: rowLimit });
|
||||
return (
|
||||
<div>
|
||||
<TableLoader
|
||||
className="table-condensed"
|
||||
mutator={mutator}
|
||||
sortable
|
||||
dataEndpoint={`/api/v1/log/recent_activity/?q=${params}`}
|
||||
noDataText={t('No Data')}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -1,49 +0,0 @@
|
|||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { styledMount as mount } from 'spec/helpers/theming';
|
||||
import Label from 'src/components/Label';
|
||||
import { user, userNoPerms } from './fixtures';
|
||||
import Security from './Security';
|
||||
|
||||
describe('Security', () => {
|
||||
const mockedProps = {
|
||||
user,
|
||||
};
|
||||
it('is valid', () => {
|
||||
expect(React.isValidElement(<Security {...mockedProps} />)).toBe(true);
|
||||
});
|
||||
it('renders 2 role labels', () => {
|
||||
const wrapper = mount(<Security {...mockedProps} />);
|
||||
expect(wrapper.find('.roles').find(Label)).toHaveLength(2);
|
||||
});
|
||||
it('renders 2 datasource labels', () => {
|
||||
const wrapper = mount(<Security {...mockedProps} />);
|
||||
expect(wrapper.find('.datasources').find(Label)).toHaveLength(2);
|
||||
});
|
||||
it('renders 3 database labels', () => {
|
||||
const wrapper = mount(<Security {...mockedProps} />);
|
||||
expect(wrapper.find('.databases').find(Label)).toHaveLength(3);
|
||||
});
|
||||
it('renders no permission label when empty', () => {
|
||||
const wrapper = mount(<Security user={userNoPerms} />);
|
||||
expect(wrapper.find('.datasources').find(Label)).not.toExist();
|
||||
expect(wrapper.find('.databases').find(Label)).not.toExist();
|
||||
});
|
||||
});
|
|
@ -1,75 +0,0 @@
|
|||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import Badge from 'src/components/Badge';
|
||||
import { t } from '@superset-ui/core';
|
||||
|
||||
import Label from 'src/components/Label';
|
||||
import { BootstrapUser } from 'src/types/bootstrapTypes';
|
||||
|
||||
interface SecurityProps {
|
||||
user: BootstrapUser;
|
||||
}
|
||||
|
||||
export default function Security({ user }: SecurityProps) {
|
||||
return (
|
||||
<div>
|
||||
<div className="roles">
|
||||
<h4>
|
||||
{t('Roles')}{' '}
|
||||
<Badge count={Object.keys(user?.roles || {}).length} showZero />
|
||||
</h4>
|
||||
{Object.keys(user?.roles || {}).map(role => (
|
||||
<Label key={role}>{role}</Label>
|
||||
))}
|
||||
<hr />
|
||||
</div>
|
||||
<div className="databases">
|
||||
{user?.permissions.database_access && (
|
||||
<div>
|
||||
<h4>
|
||||
{t('Databases')}{' '}
|
||||
<Badge count={user.permissions.database_access.length} showZero />
|
||||
</h4>
|
||||
{user.permissions.database_access.map(role => (
|
||||
<Label key={role}>{role}</Label>
|
||||
))}
|
||||
<hr />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="datasources">
|
||||
{user?.permissions.datasource_access && (
|
||||
<div>
|
||||
<h4>
|
||||
{t('Datasets')}{' '}
|
||||
<Badge
|
||||
count={user.permissions.datasource_access.length}
|
||||
showZero
|
||||
/>
|
||||
</h4>
|
||||
{user.permissions.datasource_access.map(role => (
|
||||
<Label key={role}>{role}</Label>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -1,53 +0,0 @@
|
|||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import Gravatar from 'react-gravatar';
|
||||
import { mount } from 'enzyme';
|
||||
import UserInfo from './UserInfo';
|
||||
|
||||
import { user } from './fixtures';
|
||||
|
||||
describe('UserInfo', () => {
|
||||
const mockedProps = {
|
||||
user,
|
||||
};
|
||||
it('is valid', () => {
|
||||
expect(React.isValidElement(<UserInfo {...mockedProps} />)).toBe(true);
|
||||
});
|
||||
it('renders a Gravatar', () => {
|
||||
const wrapper = mount(<UserInfo {...mockedProps} />);
|
||||
expect(wrapper.find(Gravatar)).toExist();
|
||||
});
|
||||
it('renders a Panel', () => {
|
||||
const wrapper = mount(<UserInfo {...mockedProps} />);
|
||||
expect(wrapper.find('.panel')).toExist();
|
||||
});
|
||||
it('renders 5 icons', () => {
|
||||
const wrapper = mount(<UserInfo {...mockedProps} />);
|
||||
expect(wrapper.find('i')).toHaveLength(5);
|
||||
});
|
||||
it('renders roles information', () => {
|
||||
const wrapper = mount(<UserInfo {...mockedProps} />);
|
||||
expect(wrapper.find('.roles').text()).toBe(' Alpha, sql_lab');
|
||||
});
|
||||
it('shows the right user-id', () => {
|
||||
const wrapper = mount(<UserInfo {...mockedProps} />);
|
||||
expect(wrapper.find('.user-id').text()).toBe('5');
|
||||
});
|
||||
});
|
|
@ -1,82 +0,0 @@
|
|||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import Gravatar from 'react-gravatar';
|
||||
import moment from 'moment';
|
||||
import { t, styled } from '@superset-ui/core';
|
||||
import { BootstrapUser } from 'src/types/bootstrapTypes';
|
||||
|
||||
interface UserInfoProps {
|
||||
user: BootstrapUser;
|
||||
}
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
.panel {
|
||||
padding: ${({ theme }) => theme.gridUnit * 6}px;
|
||||
}
|
||||
`;
|
||||
|
||||
export default function UserInfo({ user }: UserInfoProps) {
|
||||
return (
|
||||
<StyledContainer>
|
||||
<a href="https://en.gravatar.com/">
|
||||
<Gravatar
|
||||
email={user?.email}
|
||||
width="100%"
|
||||
height=""
|
||||
size={220}
|
||||
alt={t('Profile picture provided by Gravatar')}
|
||||
className="img-rounded"
|
||||
style={{ borderRadius: 15 }}
|
||||
/>
|
||||
</a>
|
||||
<hr />
|
||||
<div className="panel">
|
||||
<div className="header">
|
||||
<h3>
|
||||
<strong>
|
||||
{user?.firstName} {user?.lastName}
|
||||
</strong>
|
||||
</h3>
|
||||
<h4 className="username">
|
||||
<i className="fa fa-user-o" /> {user?.username}
|
||||
</h4>
|
||||
</div>
|
||||
<hr />
|
||||
<p>
|
||||
<i className="fa fa-clock-o" data-test="clock-icon-test" />{' '}
|
||||
{t('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">{t('id')}:</span>
|
||||
<span className="user-id">{user?.userId}</span>
|
||||
</p>
|
||||
</div>
|
||||
</StyledContainer>
|
||||
);
|
||||
}
|
|
@ -1,48 +0,0 @@
|
|||
import { UserWithPermissionsAndRoles } from 'src/types/bootstrapTypes';
|
||||
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
export const user: UserWithPermissionsAndRoles = {
|
||||
username: 'alpha',
|
||||
roles: {
|
||||
Alpha: [
|
||||
['can_this_form_post', 'ResetMyPasswordView'],
|
||||
['can_this_form_get', 'ResetMyPasswordView'],
|
||||
['can_this_form_post', 'UserInfoEditView'],
|
||||
['can_this_form_get', 'UserInfoEditView'],
|
||||
],
|
||||
sql_lab: [
|
||||
['menu_access', 'SQL Lab'],
|
||||
['can_search_queries', 'Superset'],
|
||||
['can_csv', 'Superset'],
|
||||
],
|
||||
},
|
||||
firstName: 'alpha',
|
||||
lastName: 'alpha',
|
||||
createdOn: '2016-11-11T12:34:17',
|
||||
userId: 5,
|
||||
email: 'alpha@alpha.com',
|
||||
isActive: true,
|
||||
isAnonymous: false,
|
||||
permissions: {
|
||||
datasource_access: ['table1', 'table2'],
|
||||
database_access: ['db1', 'db2', 'db3'],
|
||||
},
|
||||
};
|
||||
export const userNoPerms = { ...user, permissions: {} };
|
|
@ -1,46 +0,0 @@
|
|||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
export type Slice = {
|
||||
dttm: number;
|
||||
id: number;
|
||||
url: string;
|
||||
title: string;
|
||||
creator?: string;
|
||||
creator_url?: string;
|
||||
viz_type: string;
|
||||
};
|
||||
|
||||
export type Chart = {
|
||||
id: number;
|
||||
slice_name: string;
|
||||
slice_url: string;
|
||||
created_by_name?: string;
|
||||
changed_on_dttm: number;
|
||||
};
|
||||
|
||||
export type Activity = {
|
||||
action: string;
|
||||
item_title: string;
|
||||
item_url: string;
|
||||
time: number;
|
||||
};
|
||||
|
||||
export type ActivityResult = {
|
||||
result: Activity[];
|
||||
};
|
|
@ -1,43 +0,0 @@
|
|||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { Row, Col } from 'src/components';
|
||||
import { shallow } from 'enzyme';
|
||||
import Profile from 'src/pages/Profile';
|
||||
import { user } from 'src/features/profile/fixtures';
|
||||
|
||||
describe('Profile', () => {
|
||||
const mockedProps = {
|
||||
user,
|
||||
};
|
||||
it('is valid', () => {
|
||||
expect(React.isValidElement(<Profile {...mockedProps} />)).toBe(true);
|
||||
});
|
||||
|
||||
it('renders 2 Col', () => {
|
||||
const wrapper = shallow(<Profile {...mockedProps} />);
|
||||
expect(wrapper.find(Row)).toExist();
|
||||
expect(wrapper.find(Col)).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('renders 4 Tabs', () => {
|
||||
const wrapper = shallow(<Profile {...mockedProps} />);
|
||||
expect(wrapper.find('[tab]')).toHaveLength(4);
|
||||
});
|
||||
});
|
|
@ -1,93 +0,0 @@
|
|||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { t, styled } from '@superset-ui/core';
|
||||
import { Row, Col } from 'src/components';
|
||||
import Tabs from 'src/components/Tabs';
|
||||
import { BootstrapUser } from 'src/types/bootstrapTypes';
|
||||
import Favorites from 'src/features/profile/Favorites';
|
||||
import UserInfo from 'src/features/profile/UserInfo';
|
||||
import Security from 'src/features/profile/Security';
|
||||
import RecentActivity from 'src/features/profile/RecentActivity';
|
||||
import CreatedContent from 'src/features/profile/CreatedContent';
|
||||
|
||||
interface AppProps {
|
||||
user: BootstrapUser;
|
||||
}
|
||||
|
||||
const StyledTabPane = styled(Tabs.TabPane)`
|
||||
background-color: ${({ theme }) => theme.colors.grayscale.light5};
|
||||
padding: ${({ theme }) => theme.gridUnit * 4}px;
|
||||
`;
|
||||
|
||||
export default function App({ user }: AppProps) {
|
||||
return (
|
||||
<div className="container app">
|
||||
<Row gutter={16}>
|
||||
<Col xs={24} md={6}>
|
||||
<UserInfo user={user} />
|
||||
</Col>
|
||||
<Col xs={24} md={18}>
|
||||
<Tabs centered>
|
||||
<StyledTabPane
|
||||
key="1"
|
||||
tab={
|
||||
<div>
|
||||
<i className="fa fa-star" /> {t('Favorites')}
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Favorites user={user} />
|
||||
</StyledTabPane>
|
||||
<StyledTabPane
|
||||
key="2"
|
||||
tab={
|
||||
<div>
|
||||
<i className="fa fa-paint-brush" /> {t('Created content')}
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<CreatedContent user={user} />
|
||||
</StyledTabPane>
|
||||
<StyledTabPane
|
||||
key="3"
|
||||
tab={
|
||||
<div>
|
||||
<i className="fa fa-list" /> {t('Recent activity')}
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<RecentActivity user={user} />
|
||||
</StyledTabPane>
|
||||
<StyledTabPane
|
||||
key="4"
|
||||
tab={
|
||||
<div>
|
||||
<i className="fa fa-lock" /> {t('Security & Access')}
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Security user={user} />
|
||||
</StyledTabPane>
|
||||
</Tabs>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -109,7 +109,6 @@ export interface NavBarProps {
|
|||
user_info_url: string;
|
||||
user_login_url: string;
|
||||
user_logout_url: string;
|
||||
user_profile_url: string | null;
|
||||
locale: string;
|
||||
}
|
||||
|
||||
|
|
|
@ -123,10 +123,6 @@ const RowLevelSecurityList = lazy(
|
|||
),
|
||||
);
|
||||
|
||||
const Profile = lazy(
|
||||
() => import(/* webpackChunkName: "Profile" */ 'src/pages/Profile'),
|
||||
);
|
||||
|
||||
type Routes = {
|
||||
path: string;
|
||||
Component: React.ComponentType;
|
||||
|
@ -225,10 +221,6 @@ export const routes: Routes = [
|
|||
path: '/rowlevelsecurity/list',
|
||||
Component: RowLevelSecurityList,
|
||||
},
|
||||
{
|
||||
path: '/profile',
|
||||
Component: Profile,
|
||||
},
|
||||
{
|
||||
path: '/sqllab/',
|
||||
Component: SqlLab,
|
||||
|
|
|
@ -475,9 +475,7 @@ DEFAULT_FEATURE_FLAGS: dict[str, bool] = {
|
|||
# otherwise enabling this flag won't have any effect on the DB.
|
||||
"SSH_TUNNELING": False,
|
||||
"AVOID_COLORS_COLLISION": True,
|
||||
# Set to False to only allow viewing own recent activity
|
||||
# or to disallow users from viewing other users profile page
|
||||
# Do not show user info or profile in the menu
|
||||
# Do not show user info in the menu
|
||||
"MENU_HIDE_USER_INFO": False,
|
||||
# Allows users to add a ``superset://`` DB that can query across databases. This is
|
||||
# an experimental feature with potential security and performance risks, so use with
|
||||
|
|
|
@ -183,7 +183,6 @@ class SupersetAppInitializer: # pylint: disable=too-many-public-methods
|
|||
from superset.views.key_value import KV
|
||||
from superset.views.log.api import LogRestApi
|
||||
from superset.views.log.views import LogModelView
|
||||
from superset.views.profile import ProfileView
|
||||
from superset.views.redirects import R
|
||||
from superset.views.sql_lab.views import (
|
||||
SavedQueryView,
|
||||
|
@ -311,7 +310,6 @@ class SupersetAppInitializer: # pylint: disable=too-many-public-methods
|
|||
appbuilder.add_view_no_menu(ExplorePermalinkView)
|
||||
appbuilder.add_view_no_menu(KV)
|
||||
appbuilder.add_view_no_menu(R)
|
||||
appbuilder.add_view_no_menu(ProfileView)
|
||||
appbuilder.add_view_no_menu(SavedQueryView)
|
||||
appbuilder.add_view_no_menu(SavedQueryViewApi)
|
||||
appbuilder.add_view_no_menu(SliceAsync)
|
||||
|
|
|
@ -461,11 +461,10 @@ class ImportExportMixin:
|
|||
return json_to_dict(self.template_params) # type: ignore
|
||||
|
||||
|
||||
def _user_link(user: User) -> Union[Markup, str]:
|
||||
def _user(user: User) -> str:
|
||||
if not user:
|
||||
return ""
|
||||
url = f"/superset/profile/{user.username}/"
|
||||
return Markup(f"<a href=\"{url}\">{escape(user) or ''}</a>")
|
||||
return escape(user)
|
||||
|
||||
|
||||
class AuditMixinNullable(AuditMixin):
|
||||
|
@ -512,11 +511,11 @@ class AuditMixinNullable(AuditMixin):
|
|||
|
||||
@renders("created_by")
|
||||
def creator(self) -> Union[Markup, str]:
|
||||
return _user_link(self.created_by)
|
||||
return _user(self.created_by)
|
||||
|
||||
@property
|
||||
def changed_by_(self) -> Union[Markup, str]:
|
||||
return _user_link(self.changed_by)
|
||||
return _user(self.changed_by)
|
||||
|
||||
@renders("changed_on")
|
||||
def changed_on_(self) -> Markup:
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
#}
|
||||
{% set menu = appbuilder.menu %}
|
||||
{% set app_icon_width = appbuilder.app.config['APP_ICON_WIDTH'] %}
|
||||
{% set logo_target_path = appbuilder.app.config['LOGO_TARGET_PATH'] or '/profile/{}/'.format(current_user.username) %}
|
||||
{% set logo_target_path = appbuilder.app.config['LOGO_TARGET_PATH'] or '/' %}
|
||||
{% set root_path = logo_target_path if not logo_target_path.startswith('/') else '/superset' + logo_target_path if current_user.username is defined else '#' %}
|
||||
|
||||
{% block navbar %}
|
||||
|
|
|
@ -370,9 +370,6 @@ def menu_data(user: User) -> dict[str, Any]:
|
|||
else appbuilder.get_url_for_userinfo,
|
||||
"user_logout_url": appbuilder.get_url_for_logout,
|
||||
"user_login_url": appbuilder.get_url_for_login,
|
||||
"user_profile_url": None
|
||||
if user.is_anonymous or is_feature_enabled("MENU_HIDE_USER_INFO")
|
||||
else "/profile/",
|
||||
"locale": session.get("locale", "en"),
|
||||
},
|
||||
}
|
||||
|
|
|
@ -968,13 +968,6 @@ class Superset(BaseSupersetView): # pylint: disable=too-many-public-methods
|
|||
),
|
||||
)
|
||||
|
||||
@has_access
|
||||
@event_logger.log_this
|
||||
@expose("/profile/")
|
||||
@deprecated(new_target="/profile")
|
||||
def profile(self) -> FlaskResponse:
|
||||
return redirect("/profile/")
|
||||
|
||||
@has_access
|
||||
@event_logger.log_this
|
||||
@expose(
|
||||
|
|
|
@ -1,40 +0,0 @@
|
|||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
from flask import abort, g
|
||||
from flask_appbuilder import permission_name
|
||||
from flask_appbuilder.api import expose
|
||||
from flask_appbuilder.security.decorators import has_access
|
||||
|
||||
from superset import event_logger, security_manager
|
||||
from superset.superset_typing import FlaskResponse
|
||||
|
||||
from .base import BaseSupersetView
|
||||
|
||||
|
||||
class ProfileView(BaseSupersetView):
|
||||
route_base = "/profile"
|
||||
class_permission_name = "Profile"
|
||||
|
||||
@expose("/")
|
||||
@has_access
|
||||
@permission_name("read")
|
||||
@event_logger.log_this
|
||||
def root(self) -> FlaskResponse:
|
||||
user = g.user if hasattr(g, "user") and g.user else None
|
||||
if not user or security_manager.is_guest_user(user) or user.is_anonymous:
|
||||
abort(404)
|
||||
return super().render_app_template()
|
|
@ -971,7 +971,6 @@ class TestCore(SupersetTestCase):
|
|||
urls = [
|
||||
"/superset/welcome",
|
||||
f"/superset/dashboard/{dash_id}/",
|
||||
"/superset/profile/",
|
||||
f"/explore/?datasource_type=table&datasource_id={tbl_id}",
|
||||
]
|
||||
for url in urls:
|
||||
|
@ -1203,11 +1202,6 @@ class TestCore(SupersetTestCase):
|
|||
is True
|
||||
)
|
||||
|
||||
def test_redirect_new_profile(self):
|
||||
self.login(username="admin")
|
||||
resp = self.client.get("/superset/profile/")
|
||||
assert resp.status_code == 302
|
||||
|
||||
def test_redirect_new_sqllab(self):
|
||||
self.login(username="admin")
|
||||
resp = self.client.get(
|
||||
|
|
|
@ -1,164 +0,0 @@
|
|||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
import prison
|
||||
import pytest
|
||||
|
||||
from superset import db
|
||||
from superset.connectors.sqla.models import SqlaTable
|
||||
from superset.models.dashboard import Dashboard
|
||||
from superset.models.slice import Slice
|
||||
from tests.integration_tests.fixtures.birth_names_dashboard import (
|
||||
load_birth_names_dashboard_with_slices,
|
||||
load_birth_names_data,
|
||||
)
|
||||
from tests.integration_tests.fixtures.public_role import public_role_like_gamma
|
||||
from tests.integration_tests.insert_chart_mixin import InsertChartMixin
|
||||
|
||||
from .base_tests import SupersetTestCase
|
||||
|
||||
|
||||
class TestProfile(InsertChartMixin, SupersetTestCase):
|
||||
def insert_dashboard_created_by(self, username: str) -> Dashboard:
|
||||
user = self.get_user(username)
|
||||
dashboard = self.insert_dashboard(
|
||||
f"create_title_test",
|
||||
f"create_slug_test",
|
||||
[user.id],
|
||||
created_by=user,
|
||||
)
|
||||
return dashboard
|
||||
|
||||
@pytest.fixture()
|
||||
def insert_dashboard_created_by_admin(self):
|
||||
with self.create_app().app_context():
|
||||
dashboard = self.insert_dashboard_created_by("admin")
|
||||
yield dashboard
|
||||
db.session.delete(dashboard)
|
||||
db.session.commit()
|
||||
|
||||
def insert_chart_created_by(self, username: str) -> Slice:
|
||||
user = self.get_user(username)
|
||||
dataset = db.session.query(SqlaTable).first()
|
||||
chart = self.insert_chart(
|
||||
f"create_title_test",
|
||||
[user.id],
|
||||
dataset.id,
|
||||
created_by=user,
|
||||
)
|
||||
return chart
|
||||
|
||||
@pytest.fixture()
|
||||
def insert_chart_created_by_admin(self):
|
||||
with self.create_app().app_context():
|
||||
chart = self.insert_chart_created_by("admin")
|
||||
yield chart
|
||||
db.session.delete(chart)
|
||||
db.session.commit()
|
||||
|
||||
@pytest.mark.usefixtures("insert_dashboard_created_by_admin")
|
||||
@pytest.mark.usefixtures("insert_chart_created_by_admin")
|
||||
@pytest.mark.usefixtures("load_birth_names_dashboard_with_slices")
|
||||
def test_user_profile(self, username="admin"):
|
||||
self.login(username=username)
|
||||
slc = self.get_slice("Girls", db.session)
|
||||
dashboard = db.session.query(Dashboard).filter_by(slug="births").first()
|
||||
# Set a favorite dashboard
|
||||
self.client.post(f"/api/v1/dashboard/{dashboard.id}/favorites/", json={})
|
||||
# Set a favorite chart
|
||||
self.client.post(f"/api/v1/chart/{slc.id}/favorites/", json={})
|
||||
|
||||
# Get favorite dashboards:
|
||||
request_query = {
|
||||
"columns": ["created_on_delta_humanized", "dashboard_title", "url"],
|
||||
"filters": [{"col": "id", "opr": "dashboard_is_favorite", "value": True}],
|
||||
"keys": ["none"],
|
||||
"order_column": "changed_on",
|
||||
"order_direction": "desc",
|
||||
"page": 0,
|
||||
"page_size": 100,
|
||||
}
|
||||
url = f"/api/v1/dashboard/?q={prison.dumps(request_query)}"
|
||||
resp = self.client.get(url)
|
||||
assert resp.json["count"] == 1
|
||||
assert resp.json["result"][0]["dashboard_title"] == "USA Births Names"
|
||||
|
||||
# Get Favorite Charts
|
||||
request_query = {
|
||||
"filters": [{"col": "id", "opr": "chart_is_favorite", "value": True}],
|
||||
"order_column": "slice_name",
|
||||
"order_direction": "asc",
|
||||
"page": 0,
|
||||
"page_size": 25,
|
||||
}
|
||||
url = f"api/v1/chart/?q={prison.dumps(request_query)}"
|
||||
resp = self.client.get(url)
|
||||
assert resp.json["count"] == 1
|
||||
assert resp.json["result"][0]["id"] == slc.id
|
||||
|
||||
# Get recent activity
|
||||
url = "/api/v1/log/recent_activity/?q=(page_size:50)"
|
||||
resp = self.client.get(url)
|
||||
# TODO data for recent activity varies for sqlite, we should be able to assert
|
||||
# the returned data
|
||||
assert resp.status_code == 200
|
||||
|
||||
# Get dashboards created by the user
|
||||
request_query = {
|
||||
"columns": ["created_on_delta_humanized", "dashboard_title", "url"],
|
||||
"filters": [
|
||||
{"col": "created_by", "opr": "dashboard_created_by_me", "value": "me"}
|
||||
],
|
||||
"keys": ["none"],
|
||||
"order_column": "changed_on",
|
||||
"order_direction": "desc",
|
||||
"page": 0,
|
||||
"page_size": 100,
|
||||
}
|
||||
url = f"/api/v1/dashboard/?q={prison.dumps(request_query)}"
|
||||
resp = self.client.get(url)
|
||||
assert resp.json["result"][0]["dashboard_title"] == "create_title_test"
|
||||
|
||||
# Get charts created by the user
|
||||
request_query = {
|
||||
"columns": ["created_on_delta_humanized", "slice_name", "url"],
|
||||
"filters": [
|
||||
{"col": "created_by", "opr": "chart_created_by_me", "value": "me"}
|
||||
],
|
||||
"keys": ["none"],
|
||||
"order_column": "changed_on_delta_humanized",
|
||||
"order_direction": "desc",
|
||||
"page": 0,
|
||||
"page_size": 100,
|
||||
}
|
||||
url = f"/api/v1/chart/?q={prison.dumps(request_query)}"
|
||||
resp = self.client.get(url)
|
||||
assert resp.json["count"] == 1
|
||||
assert resp.json["result"][0]["slice_name"] == "create_title_test"
|
||||
|
||||
resp = self.get_resp(f"/profile/")
|
||||
self.assertIn('"app"', resp)
|
||||
|
||||
def test_user_profile_gamma(self):
|
||||
self.login(username="gamma")
|
||||
resp = self.get_resp(f"/profile/")
|
||||
self.assertIn('"app"', resp)
|
||||
|
||||
@pytest.mark.usefixtures("public_role_like_gamma")
|
||||
def test_user_profile_anonymous(self):
|
||||
self.logout()
|
||||
resp = self.client.get("/profile/")
|
||||
assert resp.status_code == 404
|
Loading…
Reference in New Issue