mirror of https://github.com/apache/superset.git
chore: remove deprecated apis and ENABLE_BROAD_ACTIVITY_ACCESS (#24400)
This commit is contained in:
parent
dc042c6c3d
commit
23bb1c48a1
|
@ -61,30 +61,21 @@
|
||||||
|can my queries on SqlLab|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|
|
|can my queries on SqlLab|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|
|
||||||
|can log on Superset|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
|
|can log on Superset|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
|
||||||
|can schemas access for csv upload on Superset|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
|
|can schemas access for csv upload on Superset|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
|
||||||
|can user slices on Superset|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
|
|
||||||
|can favstar on Superset|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
|
|
||||||
|can import dashboards on Superset|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
|
|can import dashboards on Superset|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
|
||||||
|can schemas on Superset|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
|
|can schemas on Superset|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
|
||||||
|can sqllab history on Superset|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|
|
|can sqllab history on Superset|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|
|
||||||
|can publish on Superset|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
|
|can publish on Superset|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
|
||||||
|can csv on Superset|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|
|
|can csv on Superset|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|
|
||||||
|can fave dashboards by username on Superset|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
|
|
||||||
|can slice on Superset|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
|
|can slice on Superset|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
|
||||||
|can sync druid source on Superset|:heavy_check_mark:|O|O|O|
|
|can sync druid source on Superset|:heavy_check_mark:|O|O|O|
|
||||||
|can explore on Superset|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
|
|can explore on Superset|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
|
||||||
|can fave slices on Superset|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
|
|
||||||
|can slice json on Superset|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
|
|can slice json on Superset|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
|
||||||
|can approve on Superset|:heavy_check_mark:|O|O|O|
|
|can approve on Superset|:heavy_check_mark:|O|O|O|
|
||||||
|can explore json on Superset|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
|
|can explore json on Superset|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
|
||||||
|can fetch datasource metadata on Superset|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
|
|can fetch datasource metadata on Superset|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
|
||||||
|can override role permissions on Superset|:heavy_check_mark:|O|O|O|
|
|
||||||
|can created dashboards on Superset|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
|
|
||||||
|can csrf token on Superset|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
|
|can csrf token on Superset|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
|
||||||
|can created slices on Superset|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
|
|
||||||
|can annotation json on Superset|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
|
|can annotation json on Superset|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
|
||||||
|can fave dashboards on Superset|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
|
|
||||||
|can sqllab on Superset|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|
|
|can sqllab on Superset|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|
|
||||||
|can recent activity on Superset|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
|
|
||||||
|can select star on Superset|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
|
|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 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 sqllab table viz on Superset|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|
|
||||||
|
|
|
@ -34,6 +34,7 @@ assists people when migrating to a new version.
|
||||||
|
|
||||||
### Breaking Changes
|
### Breaking Changes
|
||||||
|
|
||||||
|
- [24400](https://github.com/apache/superset/pull/24400): Removed deprecated APIs `/superset/recent_activity/...`, `/superset/fave_dashboards_by_username/...`, `/superset/fave_dashboards/...`, `/superset/created_dashboards/...`, `/superset/user_slices/`, `/superset/created_slices/...`, `/superset/fave_slices/...`, `/superset/favstar/...`,
|
||||||
- [24401](https://github.com/apache/superset/pull/24401): Removes the deprecated `metrics` column (which was blossomed in [20732](https://github.com/apache/superset/pull/20732)) from the `/api/v1/dataset/` API.
|
- [24401](https://github.com/apache/superset/pull/24401): Removes the deprecated `metrics` column (which was blossomed in [20732](https://github.com/apache/superset/pull/20732)) from the `/api/v1/dataset/` API.
|
||||||
- [24375](https://github.com/apache/superset/pull/24375): Removed deprecated API `/superset/get_or_create_table/...`, `/superset/sqllab_viz`
|
- [24375](https://github.com/apache/superset/pull/24375): Removed deprecated API `/superset/get_or_create_table/...`, `/superset/sqllab_viz`
|
||||||
- [24360](https://github.com/apache/superset/pull/24360): Removed deprecated APIs `/superset/stop_query/...`, `/superset/queries/...`, `/superset/search_queries`
|
- [24360](https://github.com/apache/superset/pull/24360): Removed deprecated APIs `/superset/stop_query/...`, `/superset/queries/...`, `/superset/search_queries`
|
||||||
|
|
|
@ -40,7 +40,6 @@ export enum FeatureFlag {
|
||||||
EMBEDDABLE_CHARTS = 'EMBEDDABLE_CHARTS',
|
EMBEDDABLE_CHARTS = 'EMBEDDABLE_CHARTS',
|
||||||
EMBEDDED_SUPERSET = 'EMBEDDED_SUPERSET',
|
EMBEDDED_SUPERSET = 'EMBEDDED_SUPERSET',
|
||||||
ENABLE_ADVANCED_DATA_TYPES = 'ENABLE_ADVANCED_DATA_TYPES',
|
ENABLE_ADVANCED_DATA_TYPES = 'ENABLE_ADVANCED_DATA_TYPES',
|
||||||
ENABLE_BROAD_ACTIVITY_ACCESS = 'ENABLE_BROAD_ACTIVITY_ACCESS',
|
|
||||||
ENABLE_EXPLORE_DRAG_AND_DROP = 'ENABLE_EXPLORE_DRAG_AND_DROP',
|
ENABLE_EXPLORE_DRAG_AND_DROP = 'ENABLE_EXPLORE_DRAG_AND_DROP',
|
||||||
ENABLE_JAVASCRIPT_CONTROLS = 'ENABLE_JAVASCRIPT_CONTROLS',
|
ENABLE_JAVASCRIPT_CONTROLS = 'ENABLE_JAVASCRIPT_CONTROLS',
|
||||||
ENABLE_TEMPLATE_PROCESSING = 'ENABLE_TEMPLATE_PROCESSING',
|
ENABLE_TEMPLATE_PROCESSING = 'ENABLE_TEMPLATE_PROCESSING',
|
||||||
|
|
|
@ -710,7 +710,6 @@ export const testQuery: ISaveableDatasource = {
|
||||||
export const mockdatasets = [...new Array(3)].map((_, i) => ({
|
export const mockdatasets = [...new Array(3)].map((_, i) => ({
|
||||||
changed_by_name: 'user',
|
changed_by_name: 'user',
|
||||||
kind: i === 0 ? 'virtual' : 'physical', // ensure there is 1 virtual
|
kind: i === 0 ? 'virtual' : 'physical', // ensure there is 1 virtual
|
||||||
changed_by_url: 'changed_by_url',
|
|
||||||
changed_by: 'user',
|
changed_by: 'user',
|
||||||
changed_on: new Date().toISOString(),
|
changed_on: new Date().toISOString(),
|
||||||
database_name: `db ${i}`,
|
database_name: `db ${i}`,
|
||||||
|
|
|
@ -99,7 +99,6 @@ const dashboardInfo = {
|
||||||
certification_details: 'Sample certification',
|
certification_details: 'Sample certification',
|
||||||
changed_by: null,
|
changed_by: null,
|
||||||
changed_by_name: '',
|
changed_by_name: '',
|
||||||
changed_by_url: '',
|
|
||||||
changed_on: '2021-03-30T19:30:14.020942',
|
changed_on: '2021-03-30T19:30:14.020942',
|
||||||
charts: [
|
charts: [
|
||||||
'Vaccine Candidates per Country & Stage',
|
'Vaccine Candidates per Country & Stage',
|
||||||
|
|
|
@ -231,9 +231,6 @@ function ChartList(props: ChartListProps) {
|
||||||
const canExport =
|
const canExport =
|
||||||
hasPerm('can_export') && isFeatureEnabled(FeatureFlag.VERSIONED_EXPORT);
|
hasPerm('can_export') && isFeatureEnabled(FeatureFlag.VERSIONED_EXPORT);
|
||||||
const initialSort = [{ id: 'changed_on_delta_humanized', desc: true }];
|
const initialSort = [{ id: 'changed_on_delta_humanized', desc: true }];
|
||||||
const enableBroadUserAccess = isFeatureEnabled(
|
|
||||||
FeatureFlag.ENABLE_BROAD_ACTIVITY_ACCESS,
|
|
||||||
);
|
|
||||||
const handleBulkChartExport = (chartsToExport: Chart[]) => {
|
const handleBulkChartExport = (chartsToExport: Chart[]) => {
|
||||||
const ids = chartsToExport.map(({ id }) => id);
|
const ids = chartsToExport.map(({ id }) => id);
|
||||||
handleResourceExport('chart', ids, () => {
|
handleResourceExport('chart', ids, () => {
|
||||||
|
@ -415,17 +412,9 @@ function ChartList(props: ChartListProps) {
|
||||||
{
|
{
|
||||||
Cell: ({
|
Cell: ({
|
||||||
row: {
|
row: {
|
||||||
original: {
|
original: { last_saved_by: lastSavedBy },
|
||||||
last_saved_by: lastSavedBy,
|
|
||||||
changed_by_url: changedByUrl,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}: any) =>
|
}: any) => <>{changedByName(lastSavedBy)}</>,
|
||||||
enableBroadUserAccess ? (
|
|
||||||
<a href={changedByUrl}>{changedByName(lastSavedBy)}</a>
|
|
||||||
) : (
|
|
||||||
<>{changedByName(lastSavedBy)}</>
|
|
||||||
),
|
|
||||||
Header: t('Modified by'),
|
Header: t('Modified by'),
|
||||||
accessor: 'last_saved_by.first_name',
|
accessor: 'last_saved_by.first_name',
|
||||||
size: 'xl',
|
size: 'xl',
|
||||||
|
|
|
@ -58,7 +58,6 @@ const mockDashboards = [...new Array(3)].map((_, i) => ({
|
||||||
url: 'url',
|
url: 'url',
|
||||||
dashboard_title: `title ${i}`,
|
dashboard_title: `title ${i}`,
|
||||||
changed_by_name: 'user',
|
changed_by_name: 'user',
|
||||||
changed_by_url: 'changed_by_url',
|
|
||||||
changed_by_fk: 1,
|
changed_by_fk: 1,
|
||||||
published: true,
|
published: true,
|
||||||
changed_on_utc: new Date().toISOString(),
|
changed_on_utc: new Date().toISOString(),
|
||||||
|
|
|
@ -83,7 +83,6 @@ interface DashboardListProps {
|
||||||
|
|
||||||
interface Dashboard {
|
interface Dashboard {
|
||||||
changed_by_name: string;
|
changed_by_name: string;
|
||||||
changed_by_url: string;
|
|
||||||
changed_on_delta_humanized: string;
|
changed_on_delta_humanized: string;
|
||||||
changed_by: string;
|
changed_by: string;
|
||||||
dashboard_title: string;
|
dashboard_title: string;
|
||||||
|
@ -140,9 +139,6 @@ function DashboardList(props: DashboardListProps) {
|
||||||
const [importingDashboard, showImportModal] = useState<boolean>(false);
|
const [importingDashboard, showImportModal] = useState<boolean>(false);
|
||||||
const [passwordFields, setPasswordFields] = useState<string[]>([]);
|
const [passwordFields, setPasswordFields] = useState<string[]>([]);
|
||||||
const [preparingExport, setPreparingExport] = useState<boolean>(false);
|
const [preparingExport, setPreparingExport] = useState<boolean>(false);
|
||||||
const enableBroadUserAccess = isFeatureEnabled(
|
|
||||||
FeatureFlag.ENABLE_BROAD_ACTIVITY_ACCESS,
|
|
||||||
);
|
|
||||||
const [sshTunnelPasswordFields, setSSHTunnelPasswordFields] = useState<
|
const [sshTunnelPasswordFields, setSSHTunnelPasswordFields] = useState<
|
||||||
string[]
|
string[]
|
||||||
>([]);
|
>([]);
|
||||||
|
@ -193,7 +189,6 @@ function DashboardList(props: DashboardListProps) {
|
||||||
if (dashboard.id === json?.result?.id) {
|
if (dashboard.id === json?.result?.id) {
|
||||||
const {
|
const {
|
||||||
changed_by_name,
|
changed_by_name,
|
||||||
changed_by_url,
|
|
||||||
changed_by,
|
changed_by,
|
||||||
dashboard_title = '',
|
dashboard_title = '',
|
||||||
slug = '',
|
slug = '',
|
||||||
|
@ -208,7 +203,6 @@ function DashboardList(props: DashboardListProps) {
|
||||||
return {
|
return {
|
||||||
...dashboard,
|
...dashboard,
|
||||||
changed_by_name,
|
changed_by_name,
|
||||||
changed_by_url,
|
|
||||||
changed_by,
|
changed_by,
|
||||||
dashboard_title,
|
dashboard_title,
|
||||||
slug,
|
slug,
|
||||||
|
@ -310,17 +304,9 @@ function DashboardList(props: DashboardListProps) {
|
||||||
{
|
{
|
||||||
Cell: ({
|
Cell: ({
|
||||||
row: {
|
row: {
|
||||||
original: {
|
original: { changed_by_name: changedByName },
|
||||||
changed_by_name: changedByName,
|
|
||||||
changed_by_url: changedByUrl,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}: any) =>
|
}: any) => <>{changedByName}</>,
|
||||||
enableBroadUserAccess ? (
|
|
||||||
<a href={changedByUrl}>{changedByName}</a>
|
|
||||||
) : (
|
|
||||||
<>{changedByName}</>
|
|
||||||
),
|
|
||||||
Header: t('Modified by'),
|
Header: t('Modified by'),
|
||||||
accessor: 'changed_by.first_name',
|
accessor: 'changed_by.first_name',
|
||||||
size: 'xl',
|
size: 'xl',
|
||||||
|
|
|
@ -50,7 +50,6 @@ const datasetsEndpoint = 'glob:*/api/v1/dataset/?*';
|
||||||
const mockdatasets = [...new Array(3)].map((_, i) => ({
|
const mockdatasets = [...new Array(3)].map((_, i) => ({
|
||||||
changed_by_name: 'user',
|
changed_by_name: 'user',
|
||||||
kind: i === 0 ? 'virtual' : 'physical', // ensure there is 1 virtual
|
kind: i === 0 ? 'virtual' : 'physical', // ensure there is 1 virtual
|
||||||
changed_by_url: 'changed_by_url',
|
|
||||||
changed_by: 'user',
|
changed_by: 'user',
|
||||||
changed_on: new Date().toISOString(),
|
changed_on: new Date().toISOString(),
|
||||||
database_name: `db ${i}`,
|
database_name: `db ${i}`,
|
||||||
|
|
|
@ -109,7 +109,6 @@ const Actions = styled.div`
|
||||||
|
|
||||||
type Dataset = {
|
type Dataset = {
|
||||||
changed_by_name: string;
|
changed_by_name: string;
|
||||||
changed_by_url: string;
|
|
||||||
changed_by: string;
|
changed_by: string;
|
||||||
changed_on_delta_humanized: string;
|
changed_on_delta_humanized: string;
|
||||||
database: {
|
database: {
|
||||||
|
|
|
@ -33,7 +33,7 @@ export default class Favorites extends React.PureComponent<FavoritesProps> {
|
||||||
const mutator = (payload: { result: Chart[] }) =>
|
const mutator = (payload: { result: Chart[] }) =>
|
||||||
payload.result.map(slice => ({
|
payload.result.map(slice => ({
|
||||||
slice: <a href={slice.slice_url}>{slice.slice_name}</a>,
|
slice: <a href={slice.slice_url}>{slice.slice_name}</a>,
|
||||||
creator: <a href={slice.created_by_url}>{slice.created_by_name}</a>,
|
creator: slice.created_by_name,
|
||||||
favorited: moment.utc(slice.changed_on_dttm).fromNow(),
|
favorited: moment.utc(slice.changed_on_dttm).fromNow(),
|
||||||
_favorited: slice.changed_on_dttm,
|
_favorited: slice.changed_on_dttm,
|
||||||
}));
|
}));
|
||||||
|
|
|
@ -31,7 +31,6 @@ export type Chart = {
|
||||||
slice_name: string;
|
slice_name: string;
|
||||||
slice_url: string;
|
slice_url: string;
|
||||||
created_by_name?: string;
|
created_by_name?: string;
|
||||||
created_by_url?: string;
|
|
||||||
changed_on_dttm: number;
|
changed_on_dttm: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,6 @@ import Owner from './Owner';
|
||||||
|
|
||||||
export default interface Dataset {
|
export default interface Dataset {
|
||||||
changed_by_name: string;
|
changed_by_name: string;
|
||||||
changed_by_url: string;
|
|
||||||
changed_by: string;
|
changed_by: string;
|
||||||
changed_on_delta_humanized: string;
|
changed_on_delta_humanized: string;
|
||||||
database: {
|
database: {
|
||||||
|
|
|
@ -55,7 +55,6 @@ export interface Dashboard {
|
||||||
certified_by?: string;
|
certified_by?: string;
|
||||||
certification_details?: string;
|
certification_details?: string;
|
||||||
changed_by_name: string;
|
changed_by_name: string;
|
||||||
changed_by_url: string;
|
|
||||||
changed_on_delta_humanized?: string;
|
changed_on_delta_humanized?: string;
|
||||||
changed_on_utc?: string;
|
changed_on_utc?: string;
|
||||||
changed_by: string;
|
changed_by: string;
|
||||||
|
|
|
@ -157,7 +157,6 @@ class ChartRestApi(BaseSupersetModelRestApi):
|
||||||
"changed_by.first_name",
|
"changed_by.first_name",
|
||||||
"changed_by.last_name",
|
"changed_by.last_name",
|
||||||
"changed_by_name",
|
"changed_by_name",
|
||||||
"changed_by_url",
|
|
||||||
"changed_on_delta_humanized",
|
"changed_on_delta_humanized",
|
||||||
"changed_on_dttm",
|
"changed_on_dttm",
|
||||||
"changed_on_utc",
|
"changed_on_utc",
|
||||||
|
@ -165,7 +164,6 @@ class ChartRestApi(BaseSupersetModelRestApi):
|
||||||
"created_by.id",
|
"created_by.id",
|
||||||
"created_by.last_name",
|
"created_by.last_name",
|
||||||
"created_by_name",
|
"created_by_name",
|
||||||
"created_by_url",
|
|
||||||
"created_on_delta_humanized",
|
"created_on_delta_humanized",
|
||||||
"datasource_id",
|
"datasource_id",
|
||||||
"datasource_name_text",
|
"datasource_name_text",
|
||||||
|
|
|
@ -476,7 +476,6 @@ DEFAULT_FEATURE_FLAGS: dict[str, bool] = {
|
||||||
"AVOID_COLORS_COLLISION": True,
|
"AVOID_COLORS_COLLISION": True,
|
||||||
# Set to False to only allow viewing own recent activity
|
# Set to False to only allow viewing own recent activity
|
||||||
# or to disallow users from viewing other users profile page
|
# or to disallow users from viewing other users profile page
|
||||||
"ENABLE_BROAD_ACTIVITY_ACCESS": False,
|
|
||||||
# Do not show user info or profile in the menu
|
# Do not show user info or profile in the menu
|
||||||
"MENU_HIDE_USER_INFO": False,
|
"MENU_HIDE_USER_INFO": False,
|
||||||
}
|
}
|
||||||
|
|
|
@ -599,14 +599,6 @@ class SqlaTable(
|
||||||
return ""
|
return ""
|
||||||
return str(self.changed_by)
|
return str(self.changed_by)
|
||||||
|
|
||||||
@property
|
|
||||||
def changed_by_url(self) -> str:
|
|
||||||
if not self.changed_by or not is_feature_enabled(
|
|
||||||
"ENABLE_BROAD_ACTIVITY_ACCESS"
|
|
||||||
):
|
|
||||||
return ""
|
|
||||||
return f"/superset/profile/{self.changed_by.username}"
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def connection(self) -> str:
|
def connection(self) -> str:
|
||||||
return str(self.database)
|
return str(self.database)
|
||||||
|
|
|
@ -174,7 +174,6 @@ class DashboardRestApi(BaseSupersetModelRestApi):
|
||||||
"changed_by.last_name",
|
"changed_by.last_name",
|
||||||
"changed_by.id",
|
"changed_by.id",
|
||||||
"changed_by_name",
|
"changed_by_name",
|
||||||
"changed_by_url",
|
|
||||||
"changed_on_utc",
|
"changed_on_utc",
|
||||||
"changed_on_delta_humanized",
|
"changed_on_delta_humanized",
|
||||||
"created_on_delta_humanized",
|
"created_on_delta_humanized",
|
||||||
|
|
|
@ -194,7 +194,6 @@ class DashboardGetResponseSchema(Schema):
|
||||||
metadata={"description": certification_details_description}
|
metadata={"description": certification_details_description}
|
||||||
)
|
)
|
||||||
changed_by_name = fields.String()
|
changed_by_name = fields.String()
|
||||||
changed_by_url = fields.String()
|
|
||||||
changed_by = fields.Nested(UserSchema(exclude=(["username"])))
|
changed_by = fields.Nested(UserSchema(exclude=(["username"])))
|
||||||
changed_on = fields.DateTime()
|
changed_on = fields.DateTime()
|
||||||
charts = fields.List(fields.String(metadata={"description": charts_description}))
|
charts = fields.List(fields.String(metadata={"description": charts_description}))
|
||||||
|
|
|
@ -101,7 +101,6 @@ class DatasetRestApi(BaseSupersetModelRestApi):
|
||||||
"database.id",
|
"database.id",
|
||||||
"database.database_name",
|
"database.database_name",
|
||||||
"changed_by_name",
|
"changed_by_name",
|
||||||
"changed_by_url",
|
|
||||||
"changed_by.first_name",
|
"changed_by.first_name",
|
||||||
"changed_by.last_name",
|
"changed_by.last_name",
|
||||||
"changed_on_utc",
|
"changed_on_utc",
|
||||||
|
|
|
@ -268,14 +268,6 @@ class Dashboard(Model, AuditMixinNullable, ImportExportMixin):
|
||||||
return ""
|
return ""
|
||||||
return str(self.changed_by)
|
return str(self.changed_by)
|
||||||
|
|
||||||
@property
|
|
||||||
def changed_by_url(self) -> str:
|
|
||||||
if not self.changed_by or not is_feature_enabled(
|
|
||||||
"ENABLE_BROAD_ACTIVITY_ACCESS"
|
|
||||||
):
|
|
||||||
return ""
|
|
||||||
return f"/superset/profile/{self.changed_by.username}"
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def data(self) -> dict[str, Any]:
|
def data(self) -> dict[str, Any]:
|
||||||
positions = self.position_json
|
positions = self.position_json
|
||||||
|
|
|
@ -25,7 +25,7 @@ from sqlalchemy import Column, ForeignKey, Integer, MetaData, String, Text
|
||||||
from sqlalchemy.orm import relationship
|
from sqlalchemy.orm import relationship
|
||||||
from sqlalchemy_utils import generic_relationship
|
from sqlalchemy_utils import generic_relationship
|
||||||
|
|
||||||
from superset import app, db, is_feature_enabled
|
from superset import app, db
|
||||||
from superset.models.helpers import AuditMixinNullable
|
from superset.models.helpers import AuditMixinNullable
|
||||||
|
|
||||||
metadata = Model.metadata # pylint: disable=no-member
|
metadata = Model.metadata # pylint: disable=no-member
|
||||||
|
@ -65,14 +65,6 @@ class FilterSet(Model, AuditMixinNullable):
|
||||||
return ""
|
return ""
|
||||||
return str(self.changed_by)
|
return str(self.changed_by)
|
||||||
|
|
||||||
@property
|
|
||||||
def changed_by_url(self) -> str:
|
|
||||||
if not self.changed_by or not is_feature_enabled(
|
|
||||||
"ENABLE_BROAD_ACTIVITY_ACCESS"
|
|
||||||
):
|
|
||||||
return ""
|
|
||||||
return f"/superset/profile/{self.changed_by.username}"
|
|
||||||
|
|
||||||
def to_dict(self) -> dict[str, Any]:
|
def to_dict(self) -> dict[str, Any]:
|
||||||
return {
|
return {
|
||||||
"id": self.id,
|
"id": self.id,
|
||||||
|
|
|
@ -331,20 +331,6 @@ class Slice( # pylint: disable=too-many-public-methods
|
||||||
name = escape(self.chart)
|
name = escape(self.chart)
|
||||||
return Markup(f'<a href="{self.url}">{name}</a>')
|
return Markup(f'<a href="{self.url}">{name}</a>')
|
||||||
|
|
||||||
@property
|
|
||||||
def created_by_url(self) -> str:
|
|
||||||
if not self.created_by:
|
|
||||||
return ""
|
|
||||||
return f"/superset/profile/{self.created_by.username}"
|
|
||||||
|
|
||||||
@property
|
|
||||||
def changed_by_url(self) -> str:
|
|
||||||
if not self.changed_by or not is_feature_enabled(
|
|
||||||
"ENABLE_BROAD_ACTIVITY_ACCESS"
|
|
||||||
):
|
|
||||||
return ""
|
|
||||||
return f"/superset/profile/{self.changed_by.username}"
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def icons(self) -> str:
|
def icons(self) -> str:
|
||||||
return f"""
|
return f"""
|
||||||
|
|
|
@ -1991,23 +1991,6 @@ class SupersetSecurityManager( # pylint: disable=too-many-public-methods
|
||||||
guest_rls = self.get_guest_rls_filters_str(datasource)
|
guest_rls = self.get_guest_rls_filters_str(datasource)
|
||||||
return guest_rls + rls_str
|
return guest_rls + rls_str
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def raise_for_user_activity_access(user_id: int) -> None:
|
|
||||||
# pylint: disable=import-outside-toplevel
|
|
||||||
from superset.extensions import feature_flag_manager
|
|
||||||
|
|
||||||
if not get_user_id() or (
|
|
||||||
not feature_flag_manager.is_feature_enabled("ENABLE_BROAD_ACTIVITY_ACCESS")
|
|
||||||
and user_id != get_user_id()
|
|
||||||
):
|
|
||||||
raise SupersetSecurityException(
|
|
||||||
SupersetError(
|
|
||||||
error_type=SupersetErrorType.USER_ACTIVITY_SECURITY_ACCESS_ERROR,
|
|
||||||
message="Access to user's activity data is restricted",
|
|
||||||
level=ErrorLevel.ERROR,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
def raise_for_dashboard_access(self, dashboard: "Dashboard") -> None:
|
def raise_for_dashboard_access(self, dashboard: "Dashboard") -> None:
|
||||||
"""
|
"""
|
||||||
Raise an exception if the user cannot access the dashboard.
|
Raise an exception if the user cannot access the dashboard.
|
||||||
|
|
|
@ -90,7 +90,6 @@ FRONTEND_CONF_KEYS = (
|
||||||
"SUPERSET_DASHBOARD_PERIODICAL_REFRESH_WARNING_MESSAGE",
|
"SUPERSET_DASHBOARD_PERIODICAL_REFRESH_WARNING_MESSAGE",
|
||||||
"DISABLE_DATASET_SOURCE_EDIT",
|
"DISABLE_DATASET_SOURCE_EDIT",
|
||||||
"ENABLE_JAVASCRIPT_CONTROLS",
|
"ENABLE_JAVASCRIPT_CONTROLS",
|
||||||
"ENABLE_BROAD_ACTIVITY_ACCESS",
|
|
||||||
"DEFAULT_SQLLAB_LIMIT",
|
"DEFAULT_SQLLAB_LIMIT",
|
||||||
"DEFAULT_VIZ_TYPE",
|
"DEFAULT_VIZ_TYPE",
|
||||||
"SQL_MAX_ROW",
|
"SQL_MAX_ROW",
|
||||||
|
@ -391,7 +390,7 @@ def menu_data(user: User) -> dict[str, Any]:
|
||||||
"user_login_url": appbuilder.get_url_for_login,
|
"user_login_url": appbuilder.get_url_for_login,
|
||||||
"user_profile_url": None
|
"user_profile_url": None
|
||||||
if user.is_anonymous or is_feature_enabled("MENU_HIDE_USER_INFO")
|
if user.is_anonymous or is_feature_enabled("MENU_HIDE_USER_INFO")
|
||||||
else f"/superset/profile/{user.username}",
|
else "/superset/profile/",
|
||||||
"locale": session.get("locale", "en"),
|
"locale": session.get("locale", "en"),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,9 +30,7 @@ from flask_appbuilder.security.decorators import (
|
||||||
has_access_api,
|
has_access_api,
|
||||||
permission_name,
|
permission_name,
|
||||||
)
|
)
|
||||||
from flask_appbuilder.security.sqla import models as ab_models
|
|
||||||
from flask_babel import gettext as __, lazy_gettext as _
|
from flask_babel import gettext as __, lazy_gettext as _
|
||||||
from sqlalchemy import and_, or_
|
|
||||||
from sqlalchemy.exc import SQLAlchemyError
|
from sqlalchemy.exc import SQLAlchemyError
|
||||||
|
|
||||||
from superset import (
|
from superset import (
|
||||||
|
@ -57,19 +55,14 @@ from superset.dashboards.permalink.exceptions import DashboardPermalinkGetFailed
|
||||||
from superset.databases.dao import DatabaseDAO
|
from superset.databases.dao import DatabaseDAO
|
||||||
from superset.datasets.commands.exceptions import DatasetNotFoundError
|
from superset.datasets.commands.exceptions import DatasetNotFoundError
|
||||||
from superset.datasource.dao import DatasourceDAO
|
from superset.datasource.dao import DatasourceDAO
|
||||||
from superset.exceptions import (
|
from superset.exceptions import CacheLoadError, DatabaseNotFound, SupersetException
|
||||||
CacheLoadError,
|
|
||||||
DatabaseNotFound,
|
|
||||||
SupersetException,
|
|
||||||
SupersetSecurityException,
|
|
||||||
)
|
|
||||||
from superset.explore.form_data.commands.create import CreateFormDataCommand
|
from superset.explore.form_data.commands.create import CreateFormDataCommand
|
||||||
from superset.explore.form_data.commands.get import GetFormDataCommand
|
from superset.explore.form_data.commands.get import GetFormDataCommand
|
||||||
from superset.explore.form_data.commands.parameters import CommandParameters
|
from superset.explore.form_data.commands.parameters import CommandParameters
|
||||||
from superset.explore.permalink.commands.get import GetExplorePermalinkCommand
|
from superset.explore.permalink.commands.get import GetExplorePermalinkCommand
|
||||||
from superset.explore.permalink.exceptions import ExplorePermalinkGetFailedError
|
from superset.explore.permalink.exceptions import ExplorePermalinkGetFailedError
|
||||||
from superset.extensions import async_query_manager, cache_manager
|
from superset.extensions import async_query_manager, cache_manager
|
||||||
from superset.models.core import Database, FavStar
|
from superset.models.core import Database
|
||||||
from superset.models.dashboard import Dashboard
|
from superset.models.dashboard import Dashboard
|
||||||
from superset.models.slice import Slice
|
from superset.models.slice import Slice
|
||||||
from superset.models.sql_lab import Query, TabState
|
from superset.models.sql_lab import Query, TabState
|
||||||
|
@ -79,7 +72,12 @@ from superset.tasks.async_queries import load_explore_json_into_cache
|
||||||
from superset.utils import core as utils
|
from superset.utils import core as utils
|
||||||
from superset.utils.async_query_manager import AsyncQueryTokenException
|
from superset.utils.async_query_manager import AsyncQueryTokenException
|
||||||
from superset.utils.cache import etag_cache
|
from superset.utils.cache import etag_cache
|
||||||
from superset.utils.core import DatasourceType, get_user_id, ReservedUrlParameters
|
from superset.utils.core import (
|
||||||
|
DatasourceType,
|
||||||
|
get_user_id,
|
||||||
|
get_username,
|
||||||
|
ReservedUrlParameters,
|
||||||
|
)
|
||||||
from superset.views.base import (
|
from superset.views.base import (
|
||||||
api,
|
api,
|
||||||
BaseSupersetView,
|
BaseSupersetView,
|
||||||
|
@ -93,7 +91,6 @@ from superset.views.base import (
|
||||||
json_error_response,
|
json_error_response,
|
||||||
json_success,
|
json_success,
|
||||||
)
|
)
|
||||||
from superset.views.log.dao import LogDAO
|
|
||||||
from superset.views.utils import (
|
from superset.views.utils import (
|
||||||
bootstrap_user_data,
|
bootstrap_user_data,
|
||||||
check_datasource_perms,
|
check_datasource_perms,
|
||||||
|
@ -829,237 +826,6 @@ class Superset(BaseSupersetView): # pylint: disable=too-many-public-methods
|
||||||
|
|
||||||
return json_success(json.dumps(response))
|
return json_success(json.dumps(response))
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_user_activity_access_error(user_id: int) -> FlaskResponse | None:
|
|
||||||
try:
|
|
||||||
security_manager.raise_for_user_activity_access(user_id)
|
|
||||||
except SupersetSecurityException as ex:
|
|
||||||
return json_error_response(
|
|
||||||
ex.message,
|
|
||||||
status=403,
|
|
||||||
)
|
|
||||||
return None
|
|
||||||
|
|
||||||
@api
|
|
||||||
@has_access_api
|
|
||||||
@event_logger.log_this
|
|
||||||
@expose("/recent_activity/<int:user_id>/", methods=("GET",))
|
|
||||||
@deprecated(new_target="/api/v1/log/recent_activity/<user_id>/")
|
|
||||||
def recent_activity(self, user_id: int) -> FlaskResponse:
|
|
||||||
"""Recent activity (actions) for a given user"""
|
|
||||||
if error_obj := self.get_user_activity_access_error(user_id):
|
|
||||||
return error_obj
|
|
||||||
|
|
||||||
limit = request.args.get("limit")
|
|
||||||
limit = int(limit) if limit and limit.isdigit() else 100
|
|
||||||
actions = request.args.get("actions", "explore,dashboard").split(",")
|
|
||||||
# whether to get distinct subjects
|
|
||||||
distinct = request.args.get("distinct") != "false"
|
|
||||||
|
|
||||||
payload = LogDAO.get_recent_activity(user_id, actions, distinct, 0, limit)
|
|
||||||
|
|
||||||
return json_success(json.dumps(payload, default=utils.json_int_dttm_ser))
|
|
||||||
|
|
||||||
@api
|
|
||||||
@has_access_api
|
|
||||||
@event_logger.log_this
|
|
||||||
@expose("/fave_dashboards_by_username/<username>/", methods=("GET",))
|
|
||||||
@deprecated(new_target="api/v1/dashboard/favorite_status/")
|
|
||||||
def fave_dashboards_by_username(self, username: str) -> FlaskResponse:
|
|
||||||
"""This lets us use a user's username to pull favourite dashboards"""
|
|
||||||
user = security_manager.find_user(username=username)
|
|
||||||
return self.fave_dashboards(user.id)
|
|
||||||
|
|
||||||
@api
|
|
||||||
@has_access_api
|
|
||||||
@event_logger.log_this
|
|
||||||
@expose("/fave_dashboards/<int:user_id>/", methods=("GET",))
|
|
||||||
@deprecated(new_target="api/v1/dashboard/favorite_status/")
|
|
||||||
def fave_dashboards(self, user_id: int) -> FlaskResponse:
|
|
||||||
if error_obj := self.get_user_activity_access_error(user_id):
|
|
||||||
return error_obj
|
|
||||||
qry = (
|
|
||||||
db.session.query(Dashboard, FavStar.dttm)
|
|
||||||
.join(
|
|
||||||
FavStar,
|
|
||||||
and_(
|
|
||||||
FavStar.user_id == int(user_id),
|
|
||||||
FavStar.class_name == "Dashboard",
|
|
||||||
Dashboard.id == FavStar.obj_id,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.order_by(FavStar.dttm.desc())
|
|
||||||
)
|
|
||||||
payload = []
|
|
||||||
for o in qry.all():
|
|
||||||
dash = {
|
|
||||||
"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
|
|
||||||
dash["creator"] = str(user)
|
|
||||||
dash["creator_url"] = f"/superset/profile/{user.username}/"
|
|
||||||
payload.append(dash)
|
|
||||||
return json_success(json.dumps(payload, default=utils.json_int_dttm_ser))
|
|
||||||
|
|
||||||
@api
|
|
||||||
@has_access_api
|
|
||||||
@event_logger.log_this
|
|
||||||
@expose("/created_dashboards/<int:user_id>/", methods=("GET",))
|
|
||||||
@deprecated(new_target="api/v1/dashboard/")
|
|
||||||
def created_dashboards(self, user_id: int) -> FlaskResponse:
|
|
||||||
if error_obj := self.get_user_activity_access_error(user_id):
|
|
||||||
return error_obj
|
|
||||||
qry = (
|
|
||||||
db.session.query(Dashboard)
|
|
||||||
.filter( # pylint: disable=comparison-with-callable
|
|
||||||
or_(
|
|
||||||
Dashboard.created_by_fk == user_id,
|
|
||||||
Dashboard.changed_by_fk == user_id,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.order_by(Dashboard.changed_on.desc())
|
|
||||||
)
|
|
||||||
payload = [
|
|
||||||
{
|
|
||||||
"id": o.id,
|
|
||||||
"dashboard": o.dashboard_link(),
|
|
||||||
"title": o.dashboard_title,
|
|
||||||
"url": o.url,
|
|
||||||
"dttm": o.changed_on,
|
|
||||||
}
|
|
||||||
for o in qry.all()
|
|
||||||
]
|
|
||||||
return json_success(json.dumps(payload, default=utils.json_int_dttm_ser))
|
|
||||||
|
|
||||||
@api
|
|
||||||
@has_access_api
|
|
||||||
@event_logger.log_this
|
|
||||||
@expose("/user_slices", methods=("GET",))
|
|
||||||
@expose("/user_slices/<int:user_id>/", methods=("GET",))
|
|
||||||
@deprecated(new_target="/api/v1/chart/")
|
|
||||||
def user_slices(self, user_id: int | None = None) -> FlaskResponse:
|
|
||||||
"""List of slices a user owns, created, modified or faved"""
|
|
||||||
if not user_id:
|
|
||||||
user_id = cast(int, get_user_id())
|
|
||||||
if error_obj := self.get_user_activity_access_error(user_id):
|
|
||||||
return error_obj
|
|
||||||
|
|
||||||
owner_ids_query = (
|
|
||||||
db.session.query(Slice.id)
|
|
||||||
.join(Slice.owners)
|
|
||||||
.filter(security_manager.user_model.id == user_id)
|
|
||||||
)
|
|
||||||
|
|
||||||
qry = (
|
|
||||||
db.session.query(Slice, FavStar.dttm)
|
|
||||||
.join(
|
|
||||||
FavStar,
|
|
||||||
and_(
|
|
||||||
FavStar.user_id == user_id,
|
|
||||||
FavStar.class_name == "slice",
|
|
||||||
Slice.id == FavStar.obj_id,
|
|
||||||
),
|
|
||||||
isouter=True,
|
|
||||||
)
|
|
||||||
.filter( # pylint: disable=comparison-with-callable
|
|
||||||
or_(
|
|
||||||
Slice.id.in_(owner_ids_query),
|
|
||||||
Slice.created_by_fk == user_id,
|
|
||||||
Slice.changed_by_fk == user_id,
|
|
||||||
FavStar.user_id == user_id,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.order_by(Slice.slice_name.asc())
|
|
||||||
)
|
|
||||||
payload = [
|
|
||||||
{
|
|
||||||
"id": o.Slice.id,
|
|
||||||
"title": o.Slice.slice_name,
|
|
||||||
"url": o.Slice.slice_url,
|
|
||||||
"data": o.Slice.form_data,
|
|
||||||
"dttm": o.dttm if o.dttm else o.Slice.changed_on,
|
|
||||||
"viz_type": o.Slice.viz_type,
|
|
||||||
}
|
|
||||||
for o in qry.all()
|
|
||||||
]
|
|
||||||
return json_success(json.dumps(payload, default=utils.json_int_dttm_ser))
|
|
||||||
|
|
||||||
@api
|
|
||||||
@has_access_api
|
|
||||||
@event_logger.log_this
|
|
||||||
@expose("/created_slices", methods=("GET",))
|
|
||||||
@expose("/created_slices/<int:user_id>/", methods=("GET",))
|
|
||||||
@deprecated(new_target="api/v1/chart/")
|
|
||||||
def created_slices(self, user_id: int | None = None) -> FlaskResponse:
|
|
||||||
"""List of slices created by this user"""
|
|
||||||
if not user_id:
|
|
||||||
user_id = cast(int, get_user_id())
|
|
||||||
if error_obj := self.get_user_activity_access_error(user_id):
|
|
||||||
return error_obj
|
|
||||||
qry = (
|
|
||||||
db.session.query(Slice)
|
|
||||||
.filter( # pylint: disable=comparison-with-callable
|
|
||||||
or_(Slice.created_by_fk == user_id, Slice.changed_by_fk == user_id)
|
|
||||||
)
|
|
||||||
.order_by(Slice.changed_on.desc())
|
|
||||||
)
|
|
||||||
payload = [
|
|
||||||
{
|
|
||||||
"id": o.id,
|
|
||||||
"title": o.slice_name,
|
|
||||||
"url": o.slice_url,
|
|
||||||
"dttm": o.changed_on,
|
|
||||||
"viz_type": o.viz_type,
|
|
||||||
}
|
|
||||||
for o in qry.all()
|
|
||||||
]
|
|
||||||
return json_success(json.dumps(payload, default=utils.json_int_dttm_ser))
|
|
||||||
|
|
||||||
@api
|
|
||||||
@has_access_api
|
|
||||||
@event_logger.log_this
|
|
||||||
@expose("/fave_slices", methods=("GET",))
|
|
||||||
@expose("/fave_slices/<int:user_id>/", methods=("GET",))
|
|
||||||
@deprecated(new_target="api/v1/chart/")
|
|
||||||
def fave_slices(self, user_id: int | None = None) -> FlaskResponse:
|
|
||||||
"""Favorite slices for a user"""
|
|
||||||
if user_id is None:
|
|
||||||
user_id = cast(int, get_user_id())
|
|
||||||
if error_obj := self.get_user_activity_access_error(user_id):
|
|
||||||
return error_obj
|
|
||||||
qry = (
|
|
||||||
db.session.query(Slice, FavStar.dttm)
|
|
||||||
.join(
|
|
||||||
FavStar,
|
|
||||||
and_(
|
|
||||||
FavStar.user_id == user_id,
|
|
||||||
FavStar.class_name == "slice",
|
|
||||||
Slice.id == FavStar.obj_id,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.order_by(FavStar.dttm.desc())
|
|
||||||
)
|
|
||||||
payload = []
|
|
||||||
for o in qry.all():
|
|
||||||
dash = {
|
|
||||||
"id": o.Slice.id,
|
|
||||||
"title": o.Slice.slice_name,
|
|
||||||
"url": o.Slice.slice_url,
|
|
||||||
"dttm": o.dttm,
|
|
||||||
"viz_type": o.Slice.viz_type,
|
|
||||||
}
|
|
||||||
if o.Slice.created_by:
|
|
||||||
user = o.Slice.created_by
|
|
||||||
dash["creator"] = str(user)
|
|
||||||
dash["creator_url"] = f"/superset/profile/{user.username}/"
|
|
||||||
payload.append(dash)
|
|
||||||
return json_success(json.dumps(payload, default=utils.json_int_dttm_ser))
|
|
||||||
|
|
||||||
@event_logger.log_this
|
@event_logger.log_this
|
||||||
@api
|
@api
|
||||||
@has_access_api
|
@has_access_api
|
||||||
|
@ -1158,42 +924,6 @@ class Superset(BaseSupersetView): # pylint: disable=too-many-public-methods
|
||||||
|
|
||||||
return json_success(json.dumps(result))
|
return json_success(json.dumps(result))
|
||||||
|
|
||||||
@has_access_api
|
|
||||||
@event_logger.log_this
|
|
||||||
@expose("/favstar/<class_name>/<int:obj_id>/<action>/")
|
|
||||||
@deprecated(new_target="api/v1/dashboard|chart/<pk>/favorites/")
|
|
||||||
def favstar( # pylint: disable=no-self-use
|
|
||||||
self, class_name: str, obj_id: int, action: str
|
|
||||||
) -> FlaskResponse:
|
|
||||||
"""Toggle favorite stars on Slices and Dashboard"""
|
|
||||||
if not get_user_id():
|
|
||||||
return json_error_response("ERROR: Favstar toggling denied", status=403)
|
|
||||||
session = db.session()
|
|
||||||
count = 0
|
|
||||||
favs = (
|
|
||||||
session.query(FavStar)
|
|
||||||
.filter_by(class_name=class_name, obj_id=obj_id, user_id=get_user_id())
|
|
||||||
.all()
|
|
||||||
)
|
|
||||||
if action == "select":
|
|
||||||
if not favs:
|
|
||||||
session.add(
|
|
||||||
FavStar(
|
|
||||||
class_name=class_name,
|
|
||||||
obj_id=obj_id,
|
|
||||||
user_id=get_user_id(),
|
|
||||||
dttm=datetime.now(),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
count = 1
|
|
||||||
elif action == "unselect":
|
|
||||||
for fav in favs:
|
|
||||||
session.delete(fav)
|
|
||||||
else:
|
|
||||||
count = len(favs)
|
|
||||||
session.commit()
|
|
||||||
return json_success(json.dumps({"count": count}))
|
|
||||||
|
|
||||||
@has_access
|
@has_access
|
||||||
@expose("/dashboard/<dashboard_id_or_slug>/")
|
@expose("/dashboard/<dashboard_id_or_slug>/")
|
||||||
@event_logger.log_this_with_extra_payload
|
@event_logger.log_this_with_extra_payload
|
||||||
|
@ -1279,23 +1009,6 @@ class Superset(BaseSupersetView): # pylint: disable=too-many-public-methods
|
||||||
def log(self) -> FlaskResponse: # pylint: disable=no-self-use
|
def log(self) -> FlaskResponse: # pylint: disable=no-self-use
|
||||||
return Response(status=200)
|
return Response(status=200)
|
||||||
|
|
||||||
@has_access
|
|
||||||
@expose("/extra_table_metadata/<int:database_id>/<table_name>/<schema>/")
|
|
||||||
@event_logger.log_this
|
|
||||||
@deprecated(
|
|
||||||
new_target="api/v1/database/<int:pk>/table_extra/<table_name>/<schema_name>/"
|
|
||||||
)
|
|
||||||
def extra_table_metadata( # pylint: disable=no-self-use
|
|
||||||
self, database_id: int, table_name: str, schema: str
|
|
||||||
) -> FlaskResponse:
|
|
||||||
parsed_schema = utils.parse_js_uri_path_item(schema, eval_undefined=True)
|
|
||||||
table_name = utils.parse_js_uri_path_item(table_name) # type: ignore
|
|
||||||
mydb = db.session.query(Database).filter_by(id=database_id).one()
|
|
||||||
payload = mydb.db_engine_spec.extra_table_metadata(
|
|
||||||
mydb, table_name, parsed_schema
|
|
||||||
)
|
|
||||||
return json_success(json.dumps(payload))
|
|
||||||
|
|
||||||
@expose("/theme/")
|
@expose("/theme/")
|
||||||
def theme(self) -> FlaskResponse:
|
def theme(self) -> FlaskResponse:
|
||||||
return self.render_template("superset/theme.html")
|
return self.render_template("superset/theme.html")
|
||||||
|
@ -1363,27 +1076,20 @@ class Superset(BaseSupersetView): # pylint: disable=too-many-public-methods
|
||||||
|
|
||||||
@has_access
|
@has_access
|
||||||
@event_logger.log_this
|
@event_logger.log_this
|
||||||
@expose("/profile/<username>/")
|
@expose("/profile/")
|
||||||
def profile(self, username: str) -> FlaskResponse:
|
def profile(self) -> FlaskResponse:
|
||||||
"""User profile page"""
|
"""User profile page"""
|
||||||
user = (
|
user = g.user if hasattr(g, "user") and g.user else None
|
||||||
db.session.query(ab_models.User).filter_by(username=username).one_or_none()
|
if not user or security_manager.is_guest_user(user) or user.is_anonymous:
|
||||||
)
|
abort(404)
|
||||||
# Prevent returning 404 when user is not found to prevent username scanning
|
|
||||||
user_id = -1 if not user else user.id
|
|
||||||
# Prevent unauthorized access to other user's profiles,
|
|
||||||
# unless configured to do so with ENABLE_BROAD_ACTIVITY_ACCESS
|
|
||||||
if error_obj := self.get_user_activity_access_error(user_id):
|
|
||||||
return error_obj
|
|
||||||
|
|
||||||
payload = {
|
payload = {
|
||||||
"user": bootstrap_user_data(user, include_perms=True),
|
"user": bootstrap_user_data(user, include_perms=True),
|
||||||
"common": common_bootstrap_payload(g.user),
|
"common": common_bootstrap_payload(user),
|
||||||
}
|
}
|
||||||
|
|
||||||
return self.render_template(
|
return self.render_template(
|
||||||
"superset/basic.html",
|
"superset/basic.html",
|
||||||
title=_("%(user)s's profile", user=username).__str__(),
|
title=_("%(user)s's profile", user=get_username()).__str__(),
|
||||||
entry="profile",
|
entry="profile",
|
||||||
bootstrap_data=json.dumps(
|
bootstrap_data=json.dumps(
|
||||||
payload, default=utils.pessimistic_json_iso_dttm_ser
|
payload, default=utils.pessimistic_json_iso_dttm_ser
|
||||||
|
|
|
@ -23,9 +23,11 @@ from flask_appbuilder.models.sqla.interface import SQLAInterface
|
||||||
|
|
||||||
import superset.models.core as models
|
import superset.models.core as models
|
||||||
from superset import event_logger, security_manager
|
from superset import event_logger, security_manager
|
||||||
|
from superset.constants import MODEL_API_RW_METHOD_PERMISSION_MAP
|
||||||
from superset.exceptions import SupersetSecurityException
|
from superset.exceptions import SupersetSecurityException
|
||||||
from superset.superset_typing import FlaskResponse
|
from superset.superset_typing import FlaskResponse
|
||||||
from superset.views.base_api import BaseSupersetModelRestApi, statsd_metrics
|
from superset.views.base_api import BaseSupersetModelRestApi, statsd_metrics
|
||||||
|
from superset.views.log import LogMixin
|
||||||
from superset.views.log.dao import LogDAO
|
from superset.views.log.dao import LogDAO
|
||||||
from superset.views.log.schemas import (
|
from superset.views.log.schemas import (
|
||||||
get_recent_activity_schema,
|
get_recent_activity_schema,
|
||||||
|
@ -33,9 +35,6 @@ from superset.views.log.schemas import (
|
||||||
RecentActivitySchema,
|
RecentActivitySchema,
|
||||||
)
|
)
|
||||||
|
|
||||||
from ...constants import MODEL_API_RW_METHOD_PERMISSION_MAP
|
|
||||||
from . import LogMixin
|
|
||||||
|
|
||||||
|
|
||||||
class LogRestApi(LogMixin, BaseSupersetModelRestApi):
|
class LogRestApi(LogMixin, BaseSupersetModelRestApi):
|
||||||
datamodel = SQLAInterface(models.Log)
|
datamodel = SQLAInterface(models.Log)
|
||||||
|
@ -82,7 +81,7 @@ class LogRestApi(LogMixin, BaseSupersetModelRestApi):
|
||||||
return self.response(403, message=ex.message)
|
return self.response(403, message=ex.message)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@expose("/recent_activity/<int:user_id>/", methods=("GET",))
|
@expose("/recent_activity/", methods=("GET",))
|
||||||
@protect()
|
@protect()
|
||||||
@safe
|
@safe
|
||||||
@statsd_metrics
|
@statsd_metrics
|
||||||
|
@ -92,7 +91,7 @@ class LogRestApi(LogMixin, BaseSupersetModelRestApi):
|
||||||
f".recent_activity",
|
f".recent_activity",
|
||||||
log_to_statsd=False,
|
log_to_statsd=False,
|
||||||
)
|
)
|
||||||
def recent_activity(self, user_id: int, **kwargs: Any) -> FlaskResponse:
|
def recent_activity(self, **kwargs: Any) -> FlaskResponse:
|
||||||
"""Get recent activity data for a user
|
"""Get recent activity data for a user
|
||||||
---
|
---
|
||||||
get:
|
get:
|
||||||
|
@ -125,16 +124,11 @@ class LogRestApi(LogMixin, BaseSupersetModelRestApi):
|
||||||
500:
|
500:
|
||||||
$ref: '#/components/responses/500'
|
$ref: '#/components/responses/500'
|
||||||
"""
|
"""
|
||||||
if error_obj := self.get_user_activity_access_error(user_id):
|
|
||||||
return error_obj
|
|
||||||
|
|
||||||
args = kwargs["rison"]
|
args = kwargs["rison"]
|
||||||
page, page_size = self._sanitize_page_args(*self._handle_page_args(args))
|
page, page_size = self._sanitize_page_args(*self._handle_page_args(args))
|
||||||
actions = args.get("actions", ["explore", "dashboard"])
|
actions = args.get("actions", ["explore", "dashboard"])
|
||||||
distinct = args.get("distinct", True)
|
distinct = args.get("distinct", True)
|
||||||
|
|
||||||
payload = LogDAO.get_recent_activity(
|
payload = LogDAO.get_recent_activity(actions, distinct, page, page_size)
|
||||||
user_id, actions, distinct, page, page_size
|
|
||||||
)
|
|
||||||
|
|
||||||
return self.response(200, result=payload)
|
return self.response(200, result=payload)
|
||||||
|
|
|
@ -26,6 +26,7 @@ from superset.dao.base import BaseDAO
|
||||||
from superset.models.core import Log
|
from superset.models.core import Log
|
||||||
from superset.models.dashboard import Dashboard
|
from superset.models.dashboard import Dashboard
|
||||||
from superset.models.slice import Slice
|
from superset.models.slice import Slice
|
||||||
|
from superset.utils.core import get_user_id
|
||||||
from superset.utils.dates import datetime_to_epoch
|
from superset.utils.dates import datetime_to_epoch
|
||||||
|
|
||||||
|
|
||||||
|
@ -34,8 +35,12 @@ class LogDAO(BaseDAO):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_recent_activity(
|
def get_recent_activity(
|
||||||
user_id: int, actions: list[str], distinct: bool, page: int, page_size: int
|
actions: list[str],
|
||||||
|
distinct: bool,
|
||||||
|
page: int,
|
||||||
|
page_size: int,
|
||||||
) -> list[dict[str, Any]]:
|
) -> list[dict[str, Any]]:
|
||||||
|
user_id = get_user_id()
|
||||||
has_subject_title = or_(
|
has_subject_title = or_(
|
||||||
and_(
|
and_(
|
||||||
Dashboard.dashboard_title is not None,
|
Dashboard.dashboard_title is not None,
|
||||||
|
|
|
@ -605,54 +605,6 @@ class TestChartApi(SupersetTestCase, ApiOwnersTestCaseMixin, InsertChartMixin):
|
||||||
db.session.delete(model)
|
db.session.delete(model)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
@pytest.mark.usefixtures("load_birth_names_dashboard_with_slices")
|
|
||||||
@with_feature_flags(ENABLE_BROAD_ACTIVITY_ACCESS=False)
|
|
||||||
def test_chart_activity_access_disabled(self):
|
|
||||||
"""
|
|
||||||
Chart API: Test ENABLE_BROAD_ACTIVITY_ACCESS = False
|
|
||||||
"""
|
|
||||||
admin = self.get_user("admin")
|
|
||||||
birth_names_table_id = SupersetTestCase.get_table(name="birth_names").id
|
|
||||||
chart_id = self.insert_chart("title", [admin.id], birth_names_table_id).id
|
|
||||||
chart_data = {
|
|
||||||
"slice_name": (new_name := "title1_changed"),
|
|
||||||
}
|
|
||||||
self.login(username="admin")
|
|
||||||
uri = f"api/v1/chart/{chart_id}"
|
|
||||||
rv = self.put_assert_metric(uri, chart_data, "put")
|
|
||||||
self.assertEqual(rv.status_code, 200)
|
|
||||||
model = db.session.query(Slice).get(chart_id)
|
|
||||||
|
|
||||||
self.assertEqual(model.slice_name, new_name)
|
|
||||||
self.assertEqual(model.changed_by_url, "")
|
|
||||||
|
|
||||||
db.session.delete(model)
|
|
||||||
db.session.commit()
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures("load_birth_names_dashboard_with_slices")
|
|
||||||
@with_feature_flags(ENABLE_BROAD_ACTIVITY_ACCESS=True)
|
|
||||||
def test_chart_activity_access_enabled(self):
|
|
||||||
"""
|
|
||||||
Chart API: Test ENABLE_BROAD_ACTIVITY_ACCESS = True
|
|
||||||
"""
|
|
||||||
admin = self.get_user("admin")
|
|
||||||
birth_names_table_id = SupersetTestCase.get_table(name="birth_names").id
|
|
||||||
chart_id = self.insert_chart("title", [admin.id], birth_names_table_id).id
|
|
||||||
chart_data = {
|
|
||||||
"slice_name": (new_name := "title1_changed"),
|
|
||||||
}
|
|
||||||
self.login(username="admin")
|
|
||||||
uri = f"api/v1/chart/{chart_id}"
|
|
||||||
rv = self.put_assert_metric(uri, chart_data, "put")
|
|
||||||
self.assertEqual(rv.status_code, 200)
|
|
||||||
model = db.session.query(Slice).get(chart_id)
|
|
||||||
|
|
||||||
self.assertEqual(model.slice_name, new_name)
|
|
||||||
self.assertEqual(model.changed_by_url, "/superset/profile/admin")
|
|
||||||
|
|
||||||
db.session.delete(model)
|
|
||||||
db.session.commit()
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures("load_birth_names_dashboard_with_slices")
|
@pytest.mark.usefixtures("load_birth_names_dashboard_with_slices")
|
||||||
def test_chart_get_list_no_username(self):
|
def test_chart_get_list_no_username(self):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -23,8 +23,10 @@ import json
|
||||||
import logging
|
import logging
|
||||||
from urllib.parse import quote
|
from urllib.parse import quote
|
||||||
|
|
||||||
|
import prison
|
||||||
import superset.utils.database
|
import superset.utils.database
|
||||||
from superset.utils.core import backend
|
from superset.utils.core import backend
|
||||||
|
from tests.integration_tests.fixtures.public_role import public_role_like_gamma
|
||||||
from tests.integration_tests.fixtures.birth_names_dashboard import (
|
from tests.integration_tests.fixtures.birth_names_dashboard import (
|
||||||
load_birth_names_dashboard_with_slices,
|
load_birth_names_dashboard_with_slices,
|
||||||
load_birth_names_data,
|
load_birth_names_data,
|
||||||
|
@ -47,6 +49,7 @@ from tests.integration_tests.fixtures.energy_dashboard import (
|
||||||
load_energy_table_with_slice,
|
load_energy_table_with_slice,
|
||||||
load_energy_table_data,
|
load_energy_table_data,
|
||||||
)
|
)
|
||||||
|
from tests.integration_tests.insert_chart_mixin import InsertChartMixin
|
||||||
from tests.integration_tests.test_app import app
|
from tests.integration_tests.test_app import app
|
||||||
import superset.views.utils
|
import superset.views.utils
|
||||||
from superset import (
|
from superset import (
|
||||||
|
@ -89,7 +92,7 @@ def cleanup():
|
||||||
yield
|
yield
|
||||||
|
|
||||||
|
|
||||||
class TestCore(SupersetTestCase):
|
class TestCore(SupersetTestCase, InsertChartMixin):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.table_ids = {
|
self.table_ids = {
|
||||||
tbl.table_name: tbl.id for tbl in (db.session.query(SqlaTable).all())
|
tbl.table_name: tbl.id for tbl in (db.session.query(SqlaTable).all())
|
||||||
|
@ -100,6 +103,50 @@ class TestCore(SupersetTestCase):
|
||||||
db.session.query(Query).delete()
|
db.session.query(Query).delete()
|
||||||
app.config["PREVENT_UNSAFE_DB_CONNECTIONS"] = self.original_unsafe_db_setting
|
app.config["PREVENT_UNSAFE_DB_CONNECTIONS"] = self.original_unsafe_db_setting
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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_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()
|
||||||
|
|
||||||
|
@pytest.fixture()
|
||||||
|
def insert_dashboard_created_by_gamma(self):
|
||||||
|
dashboard = self.insert_dashboard_created_by("gamma")
|
||||||
|
yield dashboard
|
||||||
|
db.session.delete(dashboard)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
@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()
|
||||||
|
|
||||||
def test_login(self):
|
def test_login(self):
|
||||||
resp = self.get_resp("/login/", data=dict(username="admin", password="general"))
|
resp = self.get_resp("/login/", data=dict(username="admin", password="general"))
|
||||||
self.assertNotIn("User confirmation needed", resp)
|
self.assertNotIn("User confirmation needed", resp)
|
||||||
|
@ -262,43 +309,6 @@ class TestCore(SupersetTestCase):
|
||||||
resp = self.client.get(url)
|
resp = self.client.get(url)
|
||||||
self.assertEqual(resp.status_code, 200)
|
self.assertEqual(resp.status_code, 200)
|
||||||
|
|
||||||
@pytest.mark.usefixtures("load_birth_names_dashboard_with_slices")
|
|
||||||
def test_get_user_slices_for_owners(self):
|
|
||||||
self.login(username="alpha")
|
|
||||||
user = security_manager.find_user("alpha")
|
|
||||||
slice_name = "Girls"
|
|
||||||
|
|
||||||
# ensure user is not owner of any slices
|
|
||||||
url = f"/superset/user_slices/{user.id}/"
|
|
||||||
resp = self.client.get(url)
|
|
||||||
data = json.loads(resp.data)
|
|
||||||
self.assertEqual(data, [])
|
|
||||||
|
|
||||||
# make user owner of slice and verify that endpoint returns said slice
|
|
||||||
slc = self.get_slice(
|
|
||||||
slice_name=slice_name, session=db.session, expunge_from_session=False
|
|
||||||
)
|
|
||||||
slc.owners = [user]
|
|
||||||
db.session.merge(slc)
|
|
||||||
db.session.commit()
|
|
||||||
url = f"/superset/user_slices/{user.id}/"
|
|
||||||
resp = self.client.get(url)
|
|
||||||
data = json.loads(resp.data)
|
|
||||||
self.assertEqual(len(data), 1)
|
|
||||||
self.assertEqual(data[0]["title"], slice_name)
|
|
||||||
|
|
||||||
# remove ownership and ensure user no longer gets slice
|
|
||||||
slc = self.get_slice(
|
|
||||||
slice_name=slice_name, session=db.session, expunge_from_session=False
|
|
||||||
)
|
|
||||||
slc.owners = []
|
|
||||||
db.session.merge(slc)
|
|
||||||
db.session.commit()
|
|
||||||
url = f"/superset/user_slices/{user.id}/"
|
|
||||||
resp = self.client.get(url)
|
|
||||||
data = json.loads(resp.data)
|
|
||||||
self.assertEqual(data, [])
|
|
||||||
|
|
||||||
def test_get_user_slices(self):
|
def test_get_user_slices(self):
|
||||||
self.login(username="admin")
|
self.login(username="admin")
|
||||||
userid = security_manager.find_user("admin").id
|
userid = security_manager.find_user("admin").id
|
||||||
|
@ -483,71 +493,99 @@ class TestCore(SupersetTestCase):
|
||||||
for k in keys:
|
for k in keys:
|
||||||
self.assertIn(k, resp.keys())
|
self.assertIn(k, resp.keys())
|
||||||
|
|
||||||
@staticmethod
|
@pytest.mark.usefixtures("insert_dashboard_created_by_admin")
|
||||||
def _get_user_activity_endpoints(user: str):
|
@pytest.mark.usefixtures("insert_chart_created_by_admin")
|
||||||
userid = security_manager.find_user(user).id
|
|
||||||
return (
|
|
||||||
f"/superset/recent_activity/{userid}/",
|
|
||||||
f"/superset/created_slices/{userid}/",
|
|
||||||
f"/superset/created_dashboards/{userid}/",
|
|
||||||
f"/superset/fave_slices/{userid}/",
|
|
||||||
f"/superset/fave_dashboards/{userid}/",
|
|
||||||
f"/superset/user_slices/{userid}/",
|
|
||||||
f"/superset/fave_dashboards_by_username/{user}/",
|
|
||||||
)
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures("load_birth_names_dashboard_with_slices")
|
@pytest.mark.usefixtures("load_birth_names_dashboard_with_slices")
|
||||||
def test_user_profile(self, username="admin"):
|
def test_user_profile(self, username="admin"):
|
||||||
self.login(username=username)
|
self.login(username=username)
|
||||||
slc = self.get_slice("Girls", db.session)
|
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={})
|
||||||
|
|
||||||
# Setting some faves
|
# Get favorite dashboards:
|
||||||
url = f"/superset/favstar/Slice/{slc.id}/select/"
|
request_query = {
|
||||||
resp = self.get_json_resp(url)
|
"columns": ["created_on_delta_humanized", "dashboard_title", "url"],
|
||||||
self.assertEqual(resp["count"], 1)
|
"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"
|
||||||
|
|
||||||
dash = db.session.query(Dashboard).filter_by(slug="births").first()
|
# Get Favorite Charts
|
||||||
url = f"/superset/favstar/Dashboard/{dash.id}/select/"
|
request_query = {
|
||||||
resp = self.get_json_resp(url)
|
"filters": [{"col": "id", "opr": "chart_is_favorite", "value": True}],
|
||||||
self.assertEqual(resp["count"], 1)
|
"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
|
||||||
|
|
||||||
resp = self.get_resp(f"/superset/profile/{username}/")
|
# 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"/superset/profile/")
|
||||||
self.assertIn('"app"', resp)
|
self.assertIn('"app"', resp)
|
||||||
|
|
||||||
for endpoint in self._get_user_activity_endpoints(username):
|
def test_user_profile_gamma(self):
|
||||||
data = self.get_json_resp(endpoint)
|
|
||||||
self.assertNotIn("message", data)
|
|
||||||
|
|
||||||
def test_user_profile_default_access(self):
|
|
||||||
self.login(username="gamma")
|
self.login(username="gamma")
|
||||||
resp = self.client.get(f"/superset/profile/admin/")
|
resp = self.get_resp(f"/superset/profile/")
|
||||||
self.assertEqual(resp.status_code, 403)
|
self.assertIn('"app"', resp)
|
||||||
|
|
||||||
@with_feature_flags(ENABLE_BROAD_ACTIVITY_ACCESS=True)
|
@pytest.mark.usefixtures("public_role_like_gamma")
|
||||||
def test_user_profile_broad_access(self):
|
def test_user_profile_anonymous(self):
|
||||||
self.login(username="gamma")
|
self.logout()
|
||||||
resp = self.client.get(f"/superset/profile/admin/")
|
resp = self.client.get("/superset/profile/")
|
||||||
self.assertEqual(resp.status_code, 200)
|
assert resp.status_code == 404
|
||||||
|
|
||||||
@pytest.mark.usefixtures("load_birth_names_dashboard_with_slices")
|
|
||||||
def test_user_activity_default_access(self, username="gamma"):
|
|
||||||
self.login(username=username)
|
|
||||||
|
|
||||||
for user in ("admin", "gamma"):
|
|
||||||
for endpoint in self._get_user_activity_endpoints(user):
|
|
||||||
resp = self.client.get(endpoint)
|
|
||||||
expected_status_code = 200 if user == username else 403
|
|
||||||
assert resp.status_code == expected_status_code
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures("load_birth_names_dashboard_with_slices")
|
|
||||||
@with_feature_flags(ENABLE_BROAD_ACTIVITY_ACCESS=True)
|
|
||||||
def test_user_activity_broad_access(self, username="gamma"):
|
|
||||||
self.login(username=username)
|
|
||||||
|
|
||||||
for user in ("admin", "gamma"):
|
|
||||||
for endpoint in self._get_user_activity_endpoints(user):
|
|
||||||
resp = self.client.get(endpoint)
|
|
||||||
assert resp.status_code == 200
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures("load_birth_names_dashboard_with_slices")
|
@pytest.mark.usefixtures("load_birth_names_dashboard_with_slices")
|
||||||
def test_slice_id_is_always_logged_correctly_on_web_request(self):
|
def test_slice_id_is_always_logged_correctly_on_web_request(self):
|
||||||
|
@ -1025,7 +1063,7 @@ class TestCore(SupersetTestCase):
|
||||||
"/superset/sqllab",
|
"/superset/sqllab",
|
||||||
"/superset/welcome",
|
"/superset/welcome",
|
||||||
f"/superset/dashboard/{dash_id}/",
|
f"/superset/dashboard/{dash_id}/",
|
||||||
"/superset/profile/admin/",
|
"/superset/profile/",
|
||||||
f"/explore/?datasource_type=table&datasource_id={tbl_id}",
|
f"/explore/?datasource_type=table&datasource_id={tbl_id}",
|
||||||
]
|
]
|
||||||
for url in urls:
|
for url in urls:
|
||||||
|
|
|
@ -368,7 +368,6 @@ class TestDashboardApi(SupersetTestCase, ApiOwnersTestCaseMixin, InsertChartMixi
|
||||||
"certification_details": None,
|
"certification_details": None,
|
||||||
"changed_by": None,
|
"changed_by": None,
|
||||||
"changed_by_name": "",
|
"changed_by_name": "",
|
||||||
"changed_by_url": "",
|
|
||||||
"charts": [],
|
"charts": [],
|
||||||
"created_by": {
|
"created_by": {
|
||||||
"id": 1,
|
"id": 1,
|
||||||
|
@ -1326,52 +1325,6 @@ class TestDashboardApi(SupersetTestCase, ApiOwnersTestCaseMixin, InsertChartMixi
|
||||||
db.session.delete(model)
|
db.session.delete(model)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
@with_feature_flags(ENABLE_BROAD_ACTIVITY_ACCESS=False)
|
|
||||||
def test_dashboard_activity_access_disabled(self):
|
|
||||||
"""
|
|
||||||
Dashboard API: Test ENABLE_BROAD_ACTIVITY_ACCESS = False
|
|
||||||
"""
|
|
||||||
admin = self.get_user("admin")
|
|
||||||
admin_role = self.get_role("Admin")
|
|
||||||
dashboard_id = self.insert_dashboard(
|
|
||||||
"title1", "slug1", [admin.id], roles=[admin_role.id]
|
|
||||||
).id
|
|
||||||
self.login(username="admin")
|
|
||||||
uri = f"api/v1/dashboard/{dashboard_id}"
|
|
||||||
dashboard_data = {"dashboard_title": "title2"}
|
|
||||||
rv = self.client.put(uri, json=dashboard_data)
|
|
||||||
self.assertEqual(rv.status_code, 200)
|
|
||||||
model = db.session.query(Dashboard).get(dashboard_id)
|
|
||||||
|
|
||||||
self.assertEqual(model.dashboard_title, "title2")
|
|
||||||
self.assertEqual(model.changed_by_url, "")
|
|
||||||
|
|
||||||
db.session.delete(model)
|
|
||||||
db.session.commit()
|
|
||||||
|
|
||||||
@with_feature_flags(ENABLE_BROAD_ACTIVITY_ACCESS=True)
|
|
||||||
def test_dashboard_activity_access_enabled(self):
|
|
||||||
"""
|
|
||||||
Dashboard API: Test ENABLE_BROAD_ACTIVITY_ACCESS = True
|
|
||||||
"""
|
|
||||||
admin = self.get_user("admin")
|
|
||||||
admin_role = self.get_role("Admin")
|
|
||||||
dashboard_id = self.insert_dashboard(
|
|
||||||
"title1", "slug1", [admin.id], roles=[admin_role.id]
|
|
||||||
).id
|
|
||||||
self.login(username="admin")
|
|
||||||
uri = f"api/v1/dashboard/{dashboard_id}"
|
|
||||||
dashboard_data = {"dashboard_title": "title2"}
|
|
||||||
rv = self.client.put(uri, json=dashboard_data)
|
|
||||||
self.assertEqual(rv.status_code, 200)
|
|
||||||
model = db.session.query(Dashboard).get(dashboard_id)
|
|
||||||
|
|
||||||
self.assertEqual(model.dashboard_title, "title2")
|
|
||||||
self.assertEqual(model.changed_by_url, "/superset/profile/admin")
|
|
||||||
|
|
||||||
db.session.delete(model)
|
|
||||||
db.session.commit()
|
|
||||||
|
|
||||||
def test_dashboard_get_list_no_username(self):
|
def test_dashboard_get_list_no_username(self):
|
||||||
"""
|
"""
|
||||||
Dashboard API: Tests that no username is returned
|
Dashboard API: Tests that no username is returned
|
||||||
|
|
|
@ -207,7 +207,6 @@ class TestDatasetApi(SupersetTestCase):
|
||||||
expected_columns = [
|
expected_columns = [
|
||||||
"changed_by",
|
"changed_by",
|
||||||
"changed_by_name",
|
"changed_by_name",
|
||||||
"changed_by_url",
|
|
||||||
"changed_on_delta_humanized",
|
"changed_on_delta_humanized",
|
||||||
"changed_on_utc",
|
"changed_on_utc",
|
||||||
"database",
|
"database",
|
||||||
|
@ -1358,56 +1357,6 @@ class TestDatasetApi(SupersetTestCase):
|
||||||
db.session.delete(dataset)
|
db.session.delete(dataset)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
@with_feature_flags(ENABLE_BROAD_ACTIVITY_ACCESS=True)
|
|
||||||
def test_dataset_activity_access_enabled(self):
|
|
||||||
"""
|
|
||||||
Dataset API: Test ENABLE_BROAD_ACTIVITY_ACCESS = True
|
|
||||||
"""
|
|
||||||
if backend() == "sqlite":
|
|
||||||
return
|
|
||||||
|
|
||||||
dataset = self.insert_default_dataset()
|
|
||||||
self.login(username="admin")
|
|
||||||
table_data = {"description": "changed_description"}
|
|
||||||
uri = f"api/v1/dataset/{dataset.id}"
|
|
||||||
rv = self.client.put(uri, json=table_data)
|
|
||||||
self.assertEqual(rv.status_code, 200)
|
|
||||||
|
|
||||||
response = self.get_assert_metric("api/v1/dataset/", "get_list")
|
|
||||||
res = json.loads(response.data.decode("utf-8"))["result"]
|
|
||||||
|
|
||||||
current_dataset = [d for d in res if d["id"] == dataset.id][0]
|
|
||||||
self.assertEqual(current_dataset["description"], "changed_description")
|
|
||||||
self.assertEqual(current_dataset["changed_by_url"], "/superset/profile/admin")
|
|
||||||
|
|
||||||
db.session.delete(dataset)
|
|
||||||
db.session.commit()
|
|
||||||
|
|
||||||
@with_feature_flags(ENABLE_BROAD_ACTIVITY_ACCESS=False)
|
|
||||||
def test_dataset_activity_access_disabled(self):
|
|
||||||
"""
|
|
||||||
Dataset API: Test ENABLE_BROAD_ACTIVITY_ACCESS = Fase
|
|
||||||
"""
|
|
||||||
if backend() == "sqlite":
|
|
||||||
return
|
|
||||||
|
|
||||||
dataset = self.insert_default_dataset()
|
|
||||||
self.login(username="admin")
|
|
||||||
table_data = {"description": "changed_description"}
|
|
||||||
uri = f"api/v1/dataset/{dataset.id}"
|
|
||||||
rv = self.put_assert_metric(uri, table_data, "put")
|
|
||||||
self.assertEqual(rv.status_code, 200)
|
|
||||||
|
|
||||||
response = self.get_assert_metric("api/v1/dataset/", "get_list")
|
|
||||||
res = json.loads(response.data.decode("utf-8"))["result"]
|
|
||||||
|
|
||||||
current_dataset = [d for d in res if d["id"] == dataset.id][0]
|
|
||||||
self.assertEqual(current_dataset["description"], "changed_description")
|
|
||||||
self.assertEqual(current_dataset["changed_by_url"], "")
|
|
||||||
|
|
||||||
db.session.delete(dataset)
|
|
||||||
db.session.commit()
|
|
||||||
|
|
||||||
def test_update_dataset_item_not_owned(self):
|
def test_update_dataset_item_not_owned(self):
|
||||||
"""
|
"""
|
||||||
Dataset API: Test update dataset item not owned
|
Dataset API: Test update dataset item not owned
|
||||||
|
|
|
@ -159,19 +159,6 @@ class TestLogApi(SupersetTestCase):
|
||||||
db.session.delete(log)
|
db.session.delete(log)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
@with_feature_flags(ENABLE_BROAD_ACTIVITY_ACCESS=False)
|
|
||||||
def test_get_recent_activity_no_broad_access(self):
|
|
||||||
"""
|
|
||||||
Log API: Test recent activity not visible for other users without
|
|
||||||
ENABLE_BROAD_ACTIVITY_ACCESS flag on
|
|
||||||
"""
|
|
||||||
admin_user = self.get_user("admin")
|
|
||||||
self.login(username="admin")
|
|
||||||
|
|
||||||
uri = f"api/v1/log/recent_activity/{admin_user.id + 1}/"
|
|
||||||
rv = self.client.get(uri)
|
|
||||||
self.assertEqual(rv.status_code, 403)
|
|
||||||
|
|
||||||
def test_get_recent_activity(self):
|
def test_get_recent_activity(self):
|
||||||
"""
|
"""
|
||||||
Log API: Test recent activity endpoint
|
Log API: Test recent activity endpoint
|
||||||
|
@ -182,7 +169,7 @@ class TestLogApi(SupersetTestCase):
|
||||||
log1 = self.insert_log("dashboard", admin_user, dashboard_id=dash.id)
|
log1 = self.insert_log("dashboard", admin_user, dashboard_id=dash.id)
|
||||||
log2 = self.insert_log("dashboard", admin_user, dashboard_id=dash.id)
|
log2 = self.insert_log("dashboard", admin_user, dashboard_id=dash.id)
|
||||||
|
|
||||||
uri = f"api/v1/log/recent_activity/{admin_user.id}/"
|
uri = f"api/v1/log/recent_activity/"
|
||||||
rv = self.client.get(uri)
|
rv = self.client.get(uri)
|
||||||
self.assertEqual(rv.status_code, 200)
|
self.assertEqual(rv.status_code, 200)
|
||||||
response = json.loads(rv.data.decode("utf-8"))
|
response = json.loads(rv.data.decode("utf-8"))
|
||||||
|
@ -219,7 +206,7 @@ class TestLogApi(SupersetTestCase):
|
||||||
log2 = self.insert_log("explore", admin_user, dashboard_id=dash.id)
|
log2 = self.insert_log("explore", admin_user, dashboard_id=dash.id)
|
||||||
|
|
||||||
arguments = {"actions": ["dashboard"]}
|
arguments = {"actions": ["dashboard"]}
|
||||||
uri = f"api/v1/log/recent_activity/{admin_user.id}/?q={prison.dumps(arguments)}"
|
uri = f"api/v1/log/recent_activity/?q={prison.dumps(arguments)}"
|
||||||
rv = self.client.get(uri)
|
rv = self.client.get(uri)
|
||||||
|
|
||||||
db.session.delete(log)
|
db.session.delete(log)
|
||||||
|
@ -244,7 +231,7 @@ class TestLogApi(SupersetTestCase):
|
||||||
log2 = self.insert_log("dashboard", admin_user, dashboard_id=dash.id)
|
log2 = self.insert_log("dashboard", admin_user, dashboard_id=dash.id)
|
||||||
|
|
||||||
arguments = {"distinct": False}
|
arguments = {"distinct": False}
|
||||||
uri = f"api/v1/log/recent_activity/{admin_user.id}/?q={prison.dumps(arguments)}"
|
uri = f"api/v1/log/recent_activity/?q={prison.dumps(arguments)}"
|
||||||
rv = self.client.get(uri)
|
rv = self.client.get(uri)
|
||||||
|
|
||||||
db.session.delete(log)
|
db.session.delete(log)
|
||||||
|
@ -274,7 +261,7 @@ class TestLogApi(SupersetTestCase):
|
||||||
log.dttm = now - timedelta(days=2)
|
log.dttm = now - timedelta(days=2)
|
||||||
|
|
||||||
arguments = {"page": 0, "page_size": 2}
|
arguments = {"page": 0, "page_size": 2}
|
||||||
uri = f"api/v1/log/recent_activity/{admin_user.id}/?q={prison.dumps(arguments)}"
|
uri = f"api/v1/log/recent_activity/?q={prison.dumps(arguments)}"
|
||||||
rv = self.client.get(uri)
|
rv = self.client.get(uri)
|
||||||
|
|
||||||
self.assertEqual(rv.status_code, 200)
|
self.assertEqual(rv.status_code, 200)
|
||||||
|
@ -304,7 +291,7 @@ class TestLogApi(SupersetTestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
arguments = {"page": 1, "page_size": 2}
|
arguments = {"page": 1, "page_size": 2}
|
||||||
uri = f"api/v1/log/recent_activity/{admin_user.id}/?q={prison.dumps(arguments)}"
|
uri = f"api/v1/log/recent_activity/?q={prison.dumps(arguments)}"
|
||||||
rv = self.client.get(uri)
|
rv = self.client.get(uri)
|
||||||
|
|
||||||
db.session.delete(log)
|
db.session.delete(log)
|
||||||
|
|
|
@ -1350,16 +1350,12 @@ class TestRolePermission(SupersetTestCase):
|
||||||
# make sure that user can create slices and dashboards
|
# make sure that user can create slices and dashboards
|
||||||
self.assert_can_all("Dashboard", perm_set)
|
self.assert_can_all("Dashboard", perm_set)
|
||||||
self.assert_can_all("Chart", perm_set)
|
self.assert_can_all("Chart", perm_set)
|
||||||
self.assertIn(("can_created_dashboards", "Superset"), perm_set)
|
|
||||||
self.assertIn(("can_created_slices", "Superset"), perm_set)
|
|
||||||
self.assertIn(("can_csv", "Superset"), perm_set)
|
self.assertIn(("can_csv", "Superset"), perm_set)
|
||||||
self.assertIn(("can_dashboard", "Superset"), perm_set)
|
self.assertIn(("can_dashboard", "Superset"), perm_set)
|
||||||
self.assertIn(("can_explore", "Superset"), perm_set)
|
self.assertIn(("can_explore", "Superset"), perm_set)
|
||||||
self.assertIn(("can_share_chart", "Superset"), perm_set)
|
self.assertIn(("can_share_chart", "Superset"), perm_set)
|
||||||
self.assertIn(("can_share_dashboard", "Superset"), perm_set)
|
self.assertIn(("can_share_dashboard", "Superset"), perm_set)
|
||||||
self.assertIn(("can_explore_json", "Superset"), perm_set)
|
self.assertIn(("can_explore_json", "Superset"), perm_set)
|
||||||
self.assertIn(("can_fave_dashboards", "Superset"), perm_set)
|
|
||||||
self.assertIn(("can_fave_slices", "Superset"), perm_set)
|
|
||||||
self.assertIn(("can_explore_json", "Superset"), perm_set)
|
self.assertIn(("can_explore_json", "Superset"), perm_set)
|
||||||
self.assertIn(("can_userinfo", "UserDBModelView"), perm_set)
|
self.assertIn(("can_userinfo", "UserDBModelView"), perm_set)
|
||||||
self.assert_can_menu("Databases", perm_set)
|
self.assert_can_menu("Databases", perm_set)
|
||||||
|
@ -1525,16 +1521,12 @@ class TestRolePermission(SupersetTestCase):
|
||||||
self.assert_cannot_write("UserDBModelView", gamma_perm_set)
|
self.assert_cannot_write("UserDBModelView", gamma_perm_set)
|
||||||
self.assert_cannot_write("RoleModelView", gamma_perm_set)
|
self.assert_cannot_write("RoleModelView", gamma_perm_set)
|
||||||
|
|
||||||
self.assertIn(("can_created_dashboards", "Superset"), gamma_perm_set)
|
|
||||||
self.assertIn(("can_created_slices", "Superset"), gamma_perm_set)
|
|
||||||
self.assertIn(("can_csv", "Superset"), gamma_perm_set)
|
self.assertIn(("can_csv", "Superset"), gamma_perm_set)
|
||||||
self.assertIn(("can_dashboard", "Superset"), gamma_perm_set)
|
self.assertIn(("can_dashboard", "Superset"), gamma_perm_set)
|
||||||
self.assertIn(("can_explore", "Superset"), gamma_perm_set)
|
self.assertIn(("can_explore", "Superset"), gamma_perm_set)
|
||||||
self.assertIn(("can_share_chart", "Superset"), gamma_perm_set)
|
self.assertIn(("can_share_chart", "Superset"), gamma_perm_set)
|
||||||
self.assertIn(("can_share_dashboard", "Superset"), gamma_perm_set)
|
self.assertIn(("can_share_dashboard", "Superset"), gamma_perm_set)
|
||||||
self.assertIn(("can_explore_json", "Superset"), gamma_perm_set)
|
self.assertIn(("can_explore_json", "Superset"), gamma_perm_set)
|
||||||
self.assertIn(("can_fave_dashboards", "Superset"), gamma_perm_set)
|
|
||||||
self.assertIn(("can_fave_slices", "Superset"), gamma_perm_set)
|
|
||||||
self.assertIn(("can_userinfo", "UserDBModelView"), gamma_perm_set)
|
self.assertIn(("can_userinfo", "UserDBModelView"), gamma_perm_set)
|
||||||
|
|
||||||
def test_views_are_secured(self):
|
def test_views_are_secured(self):
|
||||||
|
|
Loading…
Reference in New Issue