mirror of
https://github.com/apache/superset.git
synced 2024-09-12 08:39:45 -04:00
feat: disable edits on external assets (#19344)
* feat: disable edits on external assets * Update tests
This commit is contained in:
parent
b689ac2d11
commit
d304849b46
@ -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
|
||||||
)}
|
)}
|
||||||
|
@ -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>
|
||||||
|
@ -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 &&
|
||||||
|
@ -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>
|
||||||
|
@ -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')}
|
||||||
|
@ -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() {
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
};
|
};
|
||||||
|
@ -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>
|
||||||
|
@ -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;
|
||||||
|
@ -59,4 +59,5 @@ export type DatasetObject = {
|
|||||||
columns: ColumnObject[];
|
columns: ColumnObject[];
|
||||||
metrics: MetricObject[];
|
metrics: MetricObject[];
|
||||||
extra?: string;
|
extra?: string;
|
||||||
|
is_managed_externally: boolean;
|
||||||
};
|
};
|
||||||
|
@ -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,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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",
|
||||||
|
@ -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 = [
|
||||||
|
@ -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):
|
||||||
|
@ -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",
|
||||||
|
@ -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"]
|
||||||
|
@ -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(
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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"])
|
||||||
|
Loading…
Reference in New Issue
Block a user