mirror of https://github.com/apache/superset.git
feat(can_share): can share chart and dashboard (#14076)
* feat: share chart - can_share_chart share dashboard can_share_dashboard * fix: pre-commit * fix: userCanShare tests * fix: after hugh CR * fix: adjust after spa refactor
This commit is contained in:
parent
b394733ff3
commit
9c5264af0d
|
@ -67,7 +67,7 @@ describe('HeaderActionsDropdown', () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('readonly-user', () => {
|
describe('readonly-user', () => {
|
||||||
const overrideProps = { userCanSave: false };
|
const overrideProps = { userCanSave: false, userCanShare: false };
|
||||||
|
|
||||||
it('should render the DropdownButton', () => {
|
it('should render the DropdownButton', () => {
|
||||||
const { wrapper } = setup(overrideProps);
|
const { wrapper } = setup(overrideProps);
|
||||||
|
@ -89,9 +89,9 @@ describe('HeaderActionsDropdown', () => {
|
||||||
expect(menu.find(RefreshIntervalModal)).toExist();
|
expect(menu.find(RefreshIntervalModal)).toExist();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should render the ShareMenuItems', () => {
|
it('should not render the ShareMenuItems', () => {
|
||||||
const { menu } = setup(overrideProps);
|
const { menu } = setup(overrideProps);
|
||||||
expect(menu.find(ShareMenuItems)).toExist();
|
expect(menu.find(ShareMenuItems)).not.toExist();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not render the CssEditor', () => {
|
it('should not render the CssEditor', () => {
|
||||||
|
@ -101,7 +101,7 @@ describe('HeaderActionsDropdown', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('write-user', () => {
|
describe('write-user', () => {
|
||||||
const overrideProps = { userCanSave: true };
|
const overrideProps = { userCanSave: true, userCanShare: true };
|
||||||
|
|
||||||
it('should render the DropdownButton', () => {
|
it('should render the DropdownButton', () => {
|
||||||
const { wrapper } = setup(overrideProps);
|
const { wrapper } = setup(overrideProps);
|
||||||
|
@ -135,7 +135,11 @@ describe('HeaderActionsDropdown', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('write-user-with-edit-mode', () => {
|
describe('write-user-with-edit-mode', () => {
|
||||||
const overrideProps = { userCanSave: true, editMode: true };
|
const overrideProps = {
|
||||||
|
userCanSave: true,
|
||||||
|
editMode: true,
|
||||||
|
userCanShare: true,
|
||||||
|
};
|
||||||
|
|
||||||
it('should render the DropdownButton', () => {
|
it('should render the DropdownButton', () => {
|
||||||
const { wrapper } = setup(overrideProps);
|
const { wrapper } = setup(overrideProps);
|
||||||
|
|
|
@ -313,7 +313,17 @@ export const hydrateDashboard = (dashboardData, chartData, datasourcesData) => (
|
||||||
userId: String(user.userId), // legacy, please use state.user instead
|
userId: String(user.userId), // legacy, please use state.user instead
|
||||||
dash_edit_perm: getPermissions('can_write', 'Dashboard', roles),
|
dash_edit_perm: getPermissions('can_write', 'Dashboard', roles),
|
||||||
dash_save_perm: getPermissions('can_save_dash', 'Superset', roles),
|
dash_save_perm: getPermissions('can_save_dash', 'Superset', roles),
|
||||||
|
dash_share_perm: getPermissions(
|
||||||
|
'can_share_dashboard',
|
||||||
|
'Superset',
|
||||||
|
roles,
|
||||||
|
),
|
||||||
superset_can_explore: getPermissions('can_explore', 'Superset', roles),
|
superset_can_explore: getPermissions('can_explore', 'Superset', roles),
|
||||||
|
superset_can_share: getPermissions(
|
||||||
|
'can_share_chart',
|
||||||
|
'Superset',
|
||||||
|
roles,
|
||||||
|
),
|
||||||
superset_can_csv: getPermissions('can_csv', 'Superset', roles),
|
superset_can_csv: getPermissions('can_csv', 'Superset', roles),
|
||||||
slice_can_edit: getPermissions('can_slice', 'Superset', roles),
|
slice_can_edit: getPermissions('can_slice', 'Superset', roles),
|
||||||
common: {
|
common: {
|
||||||
|
|
|
@ -375,6 +375,7 @@ class Header extends React.PureComponent {
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const userCanEdit = dashboardInfo.dash_edit_perm;
|
const userCanEdit = dashboardInfo.dash_edit_perm;
|
||||||
|
const userCanShare = dashboardInfo.dash_share_perm;
|
||||||
const userCanSaveAs = dashboardInfo.dash_save_perm;
|
const userCanSaveAs = dashboardInfo.dash_save_perm;
|
||||||
const refreshLimit =
|
const refreshLimit =
|
||||||
dashboardInfo.common.conf.SUPERSET_DASHBOARD_PERIODICAL_REFRESH_LIMIT;
|
dashboardInfo.common.conf.SUPERSET_DASHBOARD_PERIODICAL_REFRESH_LIMIT;
|
||||||
|
@ -542,6 +543,7 @@ class Header extends React.PureComponent {
|
||||||
editMode={editMode}
|
editMode={editMode}
|
||||||
hasUnsavedChanges={hasUnsavedChanges}
|
hasUnsavedChanges={hasUnsavedChanges}
|
||||||
userCanEdit={userCanEdit}
|
userCanEdit={userCanEdit}
|
||||||
|
userCanShare={userCanShare}
|
||||||
userCanSave={userCanSaveAs}
|
userCanSave={userCanSaveAs}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
showPropertiesModal={this.showPropertiesModal}
|
showPropertiesModal={this.showPropertiesModal}
|
||||||
|
|
|
@ -54,6 +54,7 @@ const propTypes = {
|
||||||
startPeriodicRender: PropTypes.func.isRequired,
|
startPeriodicRender: PropTypes.func.isRequired,
|
||||||
editMode: PropTypes.bool.isRequired,
|
editMode: PropTypes.bool.isRequired,
|
||||||
userCanEdit: PropTypes.bool.isRequired,
|
userCanEdit: PropTypes.bool.isRequired,
|
||||||
|
userCanShare: PropTypes.bool.isRequired,
|
||||||
userCanSave: PropTypes.bool.isRequired,
|
userCanSave: PropTypes.bool.isRequired,
|
||||||
isLoading: PropTypes.bool.isRequired,
|
isLoading: PropTypes.bool.isRequired,
|
||||||
layout: PropTypes.object.isRequired,
|
layout: PropTypes.object.isRequired,
|
||||||
|
@ -192,6 +193,7 @@ class HeaderActionsDropdown extends React.PureComponent {
|
||||||
expandedSlices,
|
expandedSlices,
|
||||||
onSave,
|
onSave,
|
||||||
userCanEdit,
|
userCanEdit,
|
||||||
|
userCanShare,
|
||||||
userCanSave,
|
userCanSave,
|
||||||
isLoading,
|
isLoading,
|
||||||
refreshLimit,
|
refreshLimit,
|
||||||
|
@ -241,15 +243,17 @@ class HeaderActionsDropdown extends React.PureComponent {
|
||||||
/>
|
/>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
)}
|
)}
|
||||||
<ShareMenuItems
|
{userCanShare && (
|
||||||
url={url}
|
<ShareMenuItems
|
||||||
copyMenuItemTitle={t('Copy dashboard URL')}
|
url={url}
|
||||||
emailMenuItemTitle={t('Share dashboard by email')}
|
copyMenuItemTitle={t('Copy dashboard URL')}
|
||||||
emailSubject={emailSubject}
|
emailMenuItemTitle={t('Share dashboard by email')}
|
||||||
emailBody={emailBody}
|
emailSubject={emailSubject}
|
||||||
addSuccessToast={addSuccessToast}
|
emailBody={emailBody}
|
||||||
addDangerToast={addDangerToast}
|
addSuccessToast={addSuccessToast}
|
||||||
/>
|
addDangerToast={addDangerToast}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
key={MENU_KEYS.REFRESH_DASHBOARD}
|
key={MENU_KEYS.REFRESH_DASHBOARD}
|
||||||
data-test="refresh-dashboard-menu-item"
|
data-test="refresh-dashboard-menu-item"
|
||||||
|
|
|
@ -46,6 +46,7 @@ type SliceHeaderProps = {
|
||||||
annotationError?: object;
|
annotationError?: object;
|
||||||
sliceName?: string;
|
sliceName?: string;
|
||||||
supersetCanExplore?: boolean;
|
supersetCanExplore?: boolean;
|
||||||
|
supersetCanShare?: boolean;
|
||||||
supersetCanCSV?: boolean;
|
supersetCanCSV?: boolean;
|
||||||
sliceCanEdit?: boolean;
|
sliceCanEdit?: boolean;
|
||||||
componentId: string;
|
componentId: string;
|
||||||
|
@ -83,6 +84,7 @@ const SliceHeader: FC<SliceHeaderProps> = ({
|
||||||
isExpanded = [],
|
isExpanded = [],
|
||||||
sliceName = '',
|
sliceName = '',
|
||||||
supersetCanExplore = false,
|
supersetCanExplore = false,
|
||||||
|
supersetCanShare = false,
|
||||||
supersetCanCSV = false,
|
supersetCanCSV = false,
|
||||||
sliceCanEdit = false,
|
sliceCanEdit = false,
|
||||||
slice,
|
slice,
|
||||||
|
@ -172,6 +174,7 @@ const SliceHeader: FC<SliceHeaderProps> = ({
|
||||||
exploreChart={exploreChart}
|
exploreChart={exploreChart}
|
||||||
exportCSV={exportCSV}
|
exportCSV={exportCSV}
|
||||||
supersetCanExplore={supersetCanExplore}
|
supersetCanExplore={supersetCanExplore}
|
||||||
|
supersetCanShare={supersetCanShare}
|
||||||
supersetCanCSV={supersetCanCSV}
|
supersetCanCSV={supersetCanCSV}
|
||||||
sliceCanEdit={sliceCanEdit}
|
sliceCanEdit={sliceCanEdit}
|
||||||
componentId={componentId}
|
componentId={componentId}
|
||||||
|
|
|
@ -43,6 +43,7 @@ const propTypes = {
|
||||||
isExpanded: PropTypes.bool,
|
isExpanded: PropTypes.bool,
|
||||||
updatedDttm: PropTypes.number,
|
updatedDttm: PropTypes.number,
|
||||||
supersetCanExplore: PropTypes.bool,
|
supersetCanExplore: PropTypes.bool,
|
||||||
|
supersetCanShare: PropTypes.bool,
|
||||||
supersetCanCSV: PropTypes.bool,
|
supersetCanCSV: PropTypes.bool,
|
||||||
sliceCanEdit: PropTypes.bool,
|
sliceCanEdit: PropTypes.bool,
|
||||||
toggleExpandSlice: PropTypes.func,
|
toggleExpandSlice: PropTypes.func,
|
||||||
|
@ -61,6 +62,7 @@ const defaultProps = {
|
||||||
isCached: [],
|
isCached: [],
|
||||||
isExpanded: false,
|
isExpanded: false,
|
||||||
supersetCanExplore: false,
|
supersetCanExplore: false,
|
||||||
|
supersetCanShare: false,
|
||||||
supersetCanCSV: false,
|
supersetCanCSV: false,
|
||||||
sliceCanEdit: false,
|
sliceCanEdit: false,
|
||||||
};
|
};
|
||||||
|
@ -72,7 +74,6 @@ const MENU_KEYS = {
|
||||||
EXPLORE_CHART: 'explore_chart',
|
EXPLORE_CHART: 'explore_chart',
|
||||||
EXPORT_CSV: 'export_csv',
|
EXPORT_CSV: 'export_csv',
|
||||||
RESIZE_LABEL: 'resize_label',
|
RESIZE_LABEL: 'resize_label',
|
||||||
SHARE_CHART: 'share_chart',
|
|
||||||
DOWNLOAD_AS_IMAGE: 'download_as_image',
|
DOWNLOAD_AS_IMAGE: 'download_as_image',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -188,6 +189,7 @@ class SliceHeaderControls extends React.PureComponent {
|
||||||
addSuccessToast,
|
addSuccessToast,
|
||||||
addDangerToast,
|
addDangerToast,
|
||||||
isFullSize,
|
isFullSize,
|
||||||
|
supersetCanShare,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const crossFilterItems = getChartMetadataRegistry().items;
|
const crossFilterItems = getChartMetadataRegistry().items;
|
||||||
const isCrossFilter = Object.entries(crossFilterItems)
|
const isCrossFilter = Object.entries(crossFilterItems)
|
||||||
|
@ -253,18 +255,20 @@ class SliceHeaderControls extends React.PureComponent {
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<ShareMenuItems
|
{supersetCanShare && (
|
||||||
url={getDashboardUrl(
|
<ShareMenuItems
|
||||||
window.location.pathname,
|
url={getDashboardUrl(
|
||||||
getActiveFilters(),
|
window.location.pathname,
|
||||||
componentId,
|
getActiveFilters(),
|
||||||
)}
|
componentId,
|
||||||
copyMenuItemTitle={t('Copy chart URL')}
|
)}
|
||||||
emailMenuItemTitle={t('Share chart by email')}
|
copyMenuItemTitle={t('Copy chart URL')}
|
||||||
emailSubject={t('Superset chart')}
|
emailMenuItemTitle={t('Share chart by email')}
|
||||||
addSuccessToast={addSuccessToast}
|
emailSubject={t('Superset chart')}
|
||||||
addDangerToast={addDangerToast}
|
addSuccessToast={addSuccessToast}
|
||||||
/>
|
addDangerToast={addDangerToast}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
<Menu.Item key={MENU_KEYS.RESIZE_LABEL}>{resizeLabel}</Menu.Item>
|
<Menu.Item key={MENU_KEYS.RESIZE_LABEL}>{resizeLabel}</Menu.Item>
|
||||||
|
|
||||||
|
|
|
@ -65,6 +65,7 @@ const propTypes = {
|
||||||
isExpanded: PropTypes.bool.isRequired,
|
isExpanded: PropTypes.bool.isRequired,
|
||||||
isCached: PropTypes.bool,
|
isCached: PropTypes.bool,
|
||||||
supersetCanExplore: PropTypes.bool.isRequired,
|
supersetCanExplore: PropTypes.bool.isRequired,
|
||||||
|
supersetCanShare: PropTypes.bool.isRequired,
|
||||||
supersetCanCSV: PropTypes.bool.isRequired,
|
supersetCanCSV: PropTypes.bool.isRequired,
|
||||||
sliceCanEdit: PropTypes.bool.isRequired,
|
sliceCanEdit: PropTypes.bool.isRequired,
|
||||||
addSuccessToast: PropTypes.func.isRequired,
|
addSuccessToast: PropTypes.func.isRequired,
|
||||||
|
@ -256,6 +257,7 @@ export default class Chart extends React.Component {
|
||||||
toggleExpandSlice,
|
toggleExpandSlice,
|
||||||
timeout,
|
timeout,
|
||||||
supersetCanExplore,
|
supersetCanExplore,
|
||||||
|
supersetCanShare,
|
||||||
supersetCanCSV,
|
supersetCanCSV,
|
||||||
sliceCanEdit,
|
sliceCanEdit,
|
||||||
addSuccessToast,
|
addSuccessToast,
|
||||||
|
@ -311,6 +313,7 @@ export default class Chart extends React.Component {
|
||||||
updateSliceName={updateSliceName}
|
updateSliceName={updateSliceName}
|
||||||
sliceName={sliceName}
|
sliceName={sliceName}
|
||||||
supersetCanExplore={supersetCanExplore}
|
supersetCanExplore={supersetCanExplore}
|
||||||
|
supersetCanShare={supersetCanShare}
|
||||||
supersetCanCSV={supersetCanCSV}
|
supersetCanCSV={supersetCanCSV}
|
||||||
sliceCanEdit={sliceCanEdit}
|
sliceCanEdit={sliceCanEdit}
|
||||||
componentId={componentId}
|
componentId={componentId}
|
||||||
|
|
|
@ -84,6 +84,7 @@ function mapStateToProps(
|
||||||
editMode: dashboardState.editMode,
|
editMode: dashboardState.editMode,
|
||||||
isExpanded: !!dashboardState.expandedSlices[id],
|
isExpanded: !!dashboardState.expandedSlices[id],
|
||||||
supersetCanExplore: !!dashboardInfo.superset_can_explore,
|
supersetCanExplore: !!dashboardInfo.superset_can_explore,
|
||||||
|
supersetCanShare: !!dashboardInfo.superset_can_share,
|
||||||
supersetCanCSV: !!dashboardInfo.superset_can_csv,
|
supersetCanCSV: !!dashboardInfo.superset_can_csv,
|
||||||
sliceCanEdit: !!dashboardInfo.slice_can_edit,
|
sliceCanEdit: !!dashboardInfo.slice_can_edit,
|
||||||
ownCurrentState: dataMask.ownFilters?.[id]?.currentState,
|
ownCurrentState: dataMask.ownFilters?.[id]?.currentState,
|
||||||
|
|
|
@ -556,6 +556,8 @@ class SupersetSecurityManager( # pylint: disable=too-many-public-methods
|
||||||
self.add_permission_view_menu("all_datasource_access", "all_datasource_access")
|
self.add_permission_view_menu("all_datasource_access", "all_datasource_access")
|
||||||
self.add_permission_view_menu("all_database_access", "all_database_access")
|
self.add_permission_view_menu("all_database_access", "all_database_access")
|
||||||
self.add_permission_view_menu("all_query_access", "all_query_access")
|
self.add_permission_view_menu("all_query_access", "all_query_access")
|
||||||
|
self.add_permission_view_menu("can_share_dashboard", "Superset")
|
||||||
|
self.add_permission_view_menu("can_share_chart", "Superset")
|
||||||
|
|
||||||
def create_missing_perms(self) -> None:
|
def create_missing_perms(self) -> None:
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -670,12 +670,13 @@ class TestRolePermission(SupersetTestCase):
|
||||||
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_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_dashboards", "Superset"), perm_set)
|
||||||
self.assertIn(("can_fave_slices", "Superset"), perm_set)
|
self.assertIn(("can_fave_slices", "Superset"), perm_set)
|
||||||
self.assertIn(("can_save_dash", "Superset"), perm_set)
|
self.assertIn(("can_save_dash", "Superset"), perm_set)
|
||||||
self.assertIn(("can_slice", "Superset"), perm_set)
|
self.assertIn(("can_slice", "Superset"), perm_set)
|
||||||
self.assertIn(("can_explore", "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)
|
||||||
|
@ -868,6 +869,8 @@ class TestRolePermission(SupersetTestCase):
|
||||||
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_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_dashboards", "Superset"), gamma_perm_set)
|
||||||
self.assertIn(("can_fave_slices", "Superset"), gamma_perm_set)
|
self.assertIn(("can_fave_slices", "Superset"), gamma_perm_set)
|
||||||
|
|
Loading…
Reference in New Issue