feat: disable edits on external assets (#19344)

* feat: disable edits on external assets

* Update tests
This commit is contained in:
Beto Dealmeida 2022-03-28 16:32:57 -07:00 committed by GitHub
parent b689ac2d11
commit d304849b46
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 86 additions and 11 deletions

View File

@ -229,10 +229,19 @@ export default function Button(props: ButtonProps) {
id={`${kebabCase(tooltip)}-tooltip`} id={`${kebabCase(tooltip)}-tooltip`}
title={tooltip} title={tooltip}
> >
{/* this ternary wraps the button in a span so that the tooltip shows up {/* wrap the button in a span so that the tooltip shows up
when the button is disabled. */} when the button is disabled. */}
{disabled ? ( {disabled ? (
<span css={{ cursor: 'not-allowed' }}>{button}</span> <span
css={{
cursor: 'not-allowed',
'& > .superset-button': {
marginLeft: theme.gridUnit * 2,
},
}}
>
{button}
</span>
) : ( ) : (
button button
)} )}

View File

@ -226,7 +226,18 @@ const DatasourceModal: FunctionComponent<DatasourceModalProps> = ({
buttonStyle="primary" buttonStyle="primary"
data-test="datasource-modal-save" data-test="datasource-modal-save"
onClick={onClickSave} onClick={onClickSave}
disabled={isSaving || errors.length > 0} disabled={
isSaving ||
errors.length > 0 ||
currentDatasource.is_managed_externally
}
tooltip={
currentDatasource.is_managed_externally
? t(
"This dataset is managed externally, and can't be edited in Superset",
)
: ''
}
> >
{t('Save')} {t('Save')}
</Button> </Button>

View File

@ -492,7 +492,8 @@ class Header extends React.PureComponent {
} = this.props; } = this.props;
const userCanEdit = const userCanEdit =
dashboardInfo.dash_edit_perm && dashboardInfo.dash_edit_perm &&
filterboxMigrationState !== FILTER_BOX_MIGRATION_STATES.REVIEWING; filterboxMigrationState !== FILTER_BOX_MIGRATION_STATES.REVIEWING &&
!dashboardInfo.is_managed_externally;
const userCanShare = dashboardInfo.dash_share_perm; const userCanShare = dashboardInfo.dash_share_perm;
const userCanSaveAs = const userCanSaveAs =
dashboardInfo.dash_save_perm && dashboardInfo.dash_save_perm &&

View File

@ -75,6 +75,7 @@ type DashboardInfo = {
slug: string; slug: string;
certifiedBy: string; certifiedBy: string;
certificationDetails: string; certificationDetails: string;
isManagedExternally: boolean;
}; };
const PropertiesModal = ({ const PropertiesModal = ({
@ -151,6 +152,7 @@ const PropertiesModal = ({
owners, owners,
roles, roles,
metadata, metadata,
is_managed_externally,
} = dashboardData; } = dashboardData;
const dashboardInfo = { const dashboardInfo = {
id, id,
@ -158,6 +160,7 @@ const PropertiesModal = ({
slug: slug || '', slug: slug || '',
certifiedBy: certified_by || '', certifiedBy: certified_by || '',
certificationDetails: certification_details || '', certificationDetails: certification_details || '',
isManagedExternally: is_managed_externally || false,
}; };
form.setFieldsValue(dashboardInfo); form.setFieldsValue(dashboardInfo);
@ -515,6 +518,14 @@ const PropertiesModal = ({
buttonStyle="primary" buttonStyle="primary"
className="m-r-5" className="m-r-5"
cta cta
disabled={dashboardInfo?.isManagedExternally}
tooltip={
dashboardInfo?.isManagedExternally
? t(
"This dashboard is managed externally, and can't be edited in Superset",
)
: ''
}
> >
{saveLabel} {saveLabel}
</Button> </Button>

View File

@ -204,7 +204,14 @@ function PropertiesModal({
buttonSize="small" buttonSize="small"
buttonStyle="primary" buttonStyle="primary"
onClick={form.submit} onClick={form.submit}
disabled={submitting || !name} disabled={submitting || !name || slice.is_managed_externally}
tooltip={
slice.is_managed_externally
? t(
"This chart is managed externally, and can't be edited in Superset",
)
: ''
}
cta cta
> >
{t('Save')} {t('Save')}

View File

@ -79,7 +79,10 @@ class SaveModal extends React.Component<SaveModalProps, SaveModalState> {
} }
canOverwriteSlice(): boolean { canOverwriteSlice(): boolean {
return this.props.slice?.owners?.includes(this.props.userId); return (
this.props.slice?.owners?.includes(this.props.userId) &&
!this.props.slice?.is_managed_externally
);
} }
componentDidMount() { componentDidMount() {

View File

@ -43,6 +43,7 @@ export interface Chart {
form_data: { form_data: {
viz_type: string; viz_type: string;
}; };
is_managed_externally: boolean;
} }
export type Slice = { export type Slice = {
@ -55,6 +56,7 @@ export type Slice = {
certification_details?: string; certification_details?: string;
form_data?: QueryFormData; form_data?: QueryFormData;
query_context?: object; query_context?: object;
is_managed_externally: boolean;
}; };
export default Chart; export default Chart;

View File

@ -24,4 +24,5 @@ export type ChartObject = {
cache_timeout?: number; cache_timeout?: number;
datasource_id?: number; datasource_id?: number;
datasource_type?: number; datasource_type?: number;
is_managed_externally: boolean;
}; };

View File

@ -817,12 +817,24 @@ const DatabaseModal: FunctionComponent<DatabaseModalProps> = ({
return []; return [];
}; };
const renderEditModalFooter = () => ( const renderEditModalFooter = (db: Partial<DatabaseObject> | null) => (
<> <>
<StyledFooterButton key="close" onClick={onClose}> <StyledFooterButton key="close" onClick={onClose}>
{t('Close')} {t('Close')}
</StyledFooterButton> </StyledFooterButton>
<StyledFooterButton key="submit" buttonStyle="primary" onClick={onSave}> <StyledFooterButton
key="submit"
buttonStyle="primary"
onClick={onSave}
disabled={db?.is_managed_externally}
tooltip={
db?.is_managed_externally
? t(
"This database is managed externally, and can't be edited in Superset",
)
: ''
}
>
{t('Finish')} {t('Finish')}
</StyledFooterButton> </StyledFooterButton>
</> </>
@ -1033,7 +1045,7 @@ const DatabaseModal: FunctionComponent<DatabaseModalProps> = ({
title={ title={
<h4>{isEditMode ? t('Edit database') : t('Connect a database')}</h4> <h4>{isEditMode ? t('Edit database') : t('Connect a database')}</h4>
} }
footer={isEditMode ? renderEditModalFooter() : renderModalFooter()} footer={isEditMode ? renderEditModalFooter(db) : renderModalFooter()}
> >
<StyledStickyHeader> <StyledStickyHeader>
<TabHeader> <TabHeader>

View File

@ -95,6 +95,9 @@ export type DatabaseObject = {
disable_data_preview?: boolean; // in SQL Lab disable_data_preview?: boolean; // in SQL Lab
}; };
// External management
is_managed_externally: boolean;
// Temporary storage // Temporary storage
catalog?: Array<CatalogObject>; catalog?: Array<CatalogObject>;
query_input?: string; query_input?: string;

View File

@ -59,4 +59,5 @@ export type DatasetObject = {
columns: ColumnObject[]; columns: ColumnObject[];
metrics: MetricObject[]; metrics: MetricObject[];
extra?: string; extra?: string;
is_managed_externally: boolean;
}; };

View File

@ -566,6 +566,7 @@ export const useChartEditModal = (
cache_timeout: chart.cache_timeout, cache_timeout: chart.cache_timeout,
certified_by: chart.certified_by, certified_by: chart.certified_by,
certification_details: chart.certification_details, certification_details: chart.certification_details,
is_managed_externally: chart.is_managed_externally,
}); });
} }

View File

@ -125,9 +125,11 @@ class ChartRestApi(BaseSupersetModelRestApi):
"slice_name", "slice_name",
"viz_type", "viz_type",
"query_context", "query_context",
"is_managed_externally",
] ]
show_select_columns = show_columns + ["table.id"] show_select_columns = show_columns + ["table.id"]
list_columns = [ list_columns = [
"is_managed_externally",
"certified_by", "certified_by",
"certification_details", "certification_details",
"cache_timeout", "cache_timeout",

View File

@ -147,6 +147,7 @@ class DashboardRestApi(BaseSupersetModelRestApi):
"owners.email", "owners.email",
"roles.id", "roles.id",
"roles.name", "roles.name",
"is_managed_externally",
] ]
list_select_columns = list_columns + ["changed_on", "changed_by_fk"] list_select_columns = list_columns + ["changed_on", "changed_by_fk"]
order_columns = [ order_columns = [

View File

@ -166,6 +166,7 @@ class DashboardGetResponseSchema(Schema):
owners = fields.List(fields.Nested(UserSchema)) owners = fields.List(fields.Nested(UserSchema))
roles = fields.List(fields.Nested(RolesSchema)) roles = fields.List(fields.Nested(RolesSchema))
changed_on_humanized = fields.String(data_key="changed_on_delta_humanized") changed_on_humanized = fields.String(data_key="changed_on_delta_humanized")
is_managed_externally = fields.Boolean(allow_none=True, default=False)
class DatabaseSchema(Schema): class DatabaseSchema(Schema):

View File

@ -123,6 +123,7 @@ class DatabaseRestApi(BaseSupersetModelRestApi):
"parameters_schema", "parameters_schema",
"server_cert", "server_cert",
"sqlalchemy_uri", "sqlalchemy_uri",
"is_managed_externally",
] ]
list_columns = [ list_columns = [
"allow_file_upload", "allow_file_upload",

View File

@ -163,7 +163,11 @@ class DatasetRestApi(BaseSupersetModelRestApi):
"url", "url",
"extra", "extra",
] ]
show_columns = show_select_columns + ["columns.type_generic", "database.backend"] show_columns = show_select_columns + [
"columns.type_generic",
"database.backend",
"is_managed_externally",
]
add_model_schema = DatasetPostSchema() add_model_schema = DatasetPostSchema()
edit_model_schema = DatasetPutSchema() edit_model_schema = DatasetPutSchema()
add_columns = ["database", "schema", "table_name", "owners"] add_columns = ["database", "schema", "table_name", "owners"]

View File

@ -279,6 +279,7 @@ class Dashboard(Model, AuditMixinNullable, ImportExportMixin):
"slices": [slc.data for slc in self.slices], "slices": [slc.data for slc in self.slices],
"position_json": positions, "position_json": positions,
"last_modified_time": self.changed_on.replace(microsecond=0).timestamp(), "last_modified_time": self.changed_on.replace(microsecond=0).timestamp(),
"is_managed_externally": self.is_managed_externally,
} }
@cache_manager.cache.memoize( @cache_manager.cache.memoize(

View File

@ -227,6 +227,7 @@ class Slice( # pylint: disable=too-many-public-methods
"slice_url": self.slice_url, "slice_url": self.slice_url,
"certified_by": self.certified_by, "certified_by": self.certified_by,
"certification_details": self.certification_details, "certification_details": self.certification_details,
"is_managed_externally": self.is_managed_externally,
} }
@property @property

View File

@ -749,6 +749,7 @@ class TestChartApi(SupersetTestCase, ApiOwnersTestCaseMixin, InsertChartMixin):
"slice_name": "title", "slice_name": "title",
"viz_type": None, "viz_type": None,
"query_context": None, "query_context": None,
"is_managed_externally": False,
} }
data = json.loads(rv.data.decode("utf-8")) data = json.loads(rv.data.decode("utf-8"))
self.assertEqual(data["result"], expected_result) self.assertEqual(data["result"], expected_result)

View File

@ -349,6 +349,7 @@ class TestDashboardApi(SupersetTestCase, ApiOwnersTestCaseMixin, InsertChartMixi
"url": "/superset/dashboard/slug1/", "url": "/superset/dashboard/slug1/",
"slug": "slug1", "slug": "slug1",
"thumbnail_url": dashboard.thumbnail_url, "thumbnail_url": dashboard.thumbnail_url,
"is_managed_externally": False,
} }
data = json.loads(rv.data.decode("utf-8")) data = json.loads(rv.data.decode("utf-8"))
self.assertIn("changed_on", data["result"]) self.assertIn("changed_on", data["result"])