chore: remove annotation layer FAB CRUD model view (#22178)
This commit is contained in:
parent
d1567ba06d
commit
a77b2d6ebf
|
|
@ -115,7 +115,7 @@ const NotFoundContent = () => (
|
|||
<span>
|
||||
{t('Add an annotation layer')}{' '}
|
||||
<a
|
||||
href="/annotationlayermodelview/list"
|
||||
href="/annotationlayer/list"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
|
|
@ -300,7 +300,7 @@ class AnnotationLayer extends React.PureComponent {
|
|||
if (isLoadingOptions) {
|
||||
if (sourceType === ANNOTATION_SOURCE_TYPES.NATIVE) {
|
||||
SupersetClient.get({
|
||||
endpoint: '/annotationlayermodelview/api/read?',
|
||||
endpoint: '/api/v1/annotation_layer/',
|
||||
}).then(({ json }) => {
|
||||
const layers = json
|
||||
? json.result.map(layer => ({
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ beforeAll(() => {
|
|||
value => value.value,
|
||||
);
|
||||
|
||||
fetchMock.get('glob:*/annotationlayermodelview/api/read?*', {
|
||||
fetchMock.get('glob:*/api/v1/annotation_layer/*', {
|
||||
result: [{ label: 'Chart A', value: 'a' }],
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -259,9 +259,9 @@ function AnnotationList({
|
|||
<span>{t('Annotation Layer %s', annotationLayerName)}</span>
|
||||
<span>
|
||||
{hasHistory ? (
|
||||
<Link to="/annotationlayermodelview/list/">Back to all</Link>
|
||||
<Link to="/annotationlayer/list/">Back to all</Link>
|
||||
) : (
|
||||
<a href="/annotationlayermodelview/list/">Back to all</a>
|
||||
<a href="/annotationlayer/list/">Back to all</a>
|
||||
)}
|
||||
</span>
|
||||
</StyledHeader>
|
||||
|
|
|
|||
|
|
@ -143,12 +143,10 @@ function AnnotationLayersList({
|
|||
}
|
||||
|
||||
if (hasHistory) {
|
||||
return (
|
||||
<Link to={`/annotationmodelview/${id}/annotation`}>{name}</Link>
|
||||
);
|
||||
return <Link to={`/annotationlayer/${id}/annotation`}>{name}</Link>;
|
||||
}
|
||||
|
||||
return <a href={`/annotationmodelview/${id}/annotation`}>{name}</a>;
|
||||
return <a href={`/annotationlayer/${id}/annotation`}>{name}</a>;
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -324,7 +322,7 @@ function AnnotationLayersList({
|
|||
};
|
||||
|
||||
const onLayerAdd = (id?: number) => {
|
||||
window.location.href = `/annotationmodelview/${id}/annotation`;
|
||||
window.location.href = `/annotationlayer/${id}/annotation`;
|
||||
};
|
||||
|
||||
const onModalHide = () => {
|
||||
|
|
|
|||
|
|
@ -156,11 +156,11 @@ export const routes: Routes = [
|
|||
Component: CssTemplatesList,
|
||||
},
|
||||
{
|
||||
path: '/annotationlayermodelview/list/',
|
||||
path: '/annotationlayer/list/',
|
||||
Component: AnnotationLayersList,
|
||||
},
|
||||
{
|
||||
path: '/annotationmodelview/:annotationLayerId/annotation/',
|
||||
path: '/annotationlayer/:annotationLayerId/annotation/',
|
||||
Component: AnnotationList,
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ class CreateAnnotationCommand(BaseCommand):
|
|||
|
||||
# validate date time sanity
|
||||
if start_dttm and end_dttm and end_dttm < start_dttm:
|
||||
exceptions.append(AnnotationDatesValidationError)
|
||||
exceptions.append(AnnotationDatesValidationError())
|
||||
|
||||
if exceptions:
|
||||
exception = AnnotationInvalidError()
|
||||
|
|
|
|||
|
|
@ -149,10 +149,7 @@ class SupersetAppInitializer: # pylint: disable=too-many-public-methods
|
|||
from superset.security.api import SecurityRestApi
|
||||
from superset.views.access_requests import AccessRequestsModelView
|
||||
from superset.views.alerts import AlertView, ReportView
|
||||
from superset.views.annotations import (
|
||||
AnnotationLayerModelView,
|
||||
AnnotationModelView,
|
||||
)
|
||||
from superset.views.annotations import AnnotationLayerView
|
||||
from superset.views.api import Api
|
||||
from superset.views.chart.views import SliceAsync, SliceModelView
|
||||
from superset.views.core import Superset
|
||||
|
|
@ -236,16 +233,6 @@ class SupersetAppInitializer: # pylint: disable=too-many-public-methods
|
|||
category="Data",
|
||||
category_label=__("Data"),
|
||||
)
|
||||
|
||||
appbuilder.add_view(
|
||||
AnnotationLayerModelView,
|
||||
"Annotation Layers",
|
||||
label=__("Annotation Layers"),
|
||||
icon="fa-comment",
|
||||
category="Manage",
|
||||
category_label=__("Manage"),
|
||||
category_icon="",
|
||||
)
|
||||
appbuilder.add_view(
|
||||
DashboardModelView,
|
||||
"Dashboards",
|
||||
|
|
@ -323,7 +310,6 @@ class SupersetAppInitializer: # pylint: disable=too-many-public-methods
|
|||
appbuilder.add_view_no_menu(SliceAsync)
|
||||
appbuilder.add_view_no_menu(SqlLab)
|
||||
appbuilder.add_view_no_menu(SqlMetricInlineView)
|
||||
appbuilder.add_view_no_menu(AnnotationModelView)
|
||||
appbuilder.add_view_no_menu(Superset)
|
||||
appbuilder.add_view_no_menu(TableColumnInlineView)
|
||||
appbuilder.add_view_no_menu(TableModelView)
|
||||
|
|
@ -401,6 +387,17 @@ class SupersetAppInitializer: # pylint: disable=too-many-public-methods
|
|||
menu_cond=lambda: feature_flag_manager.is_feature_enabled("ALERT_REPORTS"),
|
||||
)
|
||||
|
||||
appbuilder.add_view(
|
||||
AnnotationLayerView,
|
||||
"Annotation Layers",
|
||||
label=_("Annotation Layers"),
|
||||
href="/annotationlayer/list/",
|
||||
icon="fa-comment",
|
||||
category_icon="",
|
||||
category="Manage",
|
||||
category_label=__("Manage"),
|
||||
)
|
||||
|
||||
appbuilder.add_view(
|
||||
AccessRequestsModelView,
|
||||
"Access requests",
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@
|
|||
from . import (
|
||||
access_requests,
|
||||
alerts,
|
||||
annotations,
|
||||
api,
|
||||
base,
|
||||
core,
|
||||
|
|
|
|||
|
|
@ -14,117 +14,27 @@
|
|||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
from typing import Any, Dict
|
||||
|
||||
from flask_appbuilder import CompactCRUDMixin
|
||||
from flask_appbuilder import permission_name
|
||||
from flask_appbuilder.api import expose
|
||||
from flask_appbuilder.models.sqla.interface import SQLAInterface
|
||||
from flask_appbuilder.security.decorators import has_access
|
||||
from flask_babel import lazy_gettext as _
|
||||
from wtforms.validators import StopValidation
|
||||
|
||||
from superset.constants import MODEL_VIEW_RW_METHOD_PERMISSION_MAP, RouteMethod
|
||||
from superset.models.annotations import Annotation, AnnotationLayer
|
||||
from superset.superset_typing import FlaskResponse
|
||||
from superset.views.base import SupersetModelView
|
||||
|
||||
from .base import BaseSupersetView
|
||||
|
||||
|
||||
class StartEndDttmValidator: # pylint: disable=too-few-public-methods
|
||||
"""
|
||||
Validates dttm fields.
|
||||
"""
|
||||
|
||||
def __call__(self, form: Dict[str, Any], field: Any) -> None:
|
||||
if not form["start_dttm"].data and not form["end_dttm"].data:
|
||||
raise StopValidation(_("annotation start time or end time is required."))
|
||||
if (
|
||||
form["end_dttm"].data
|
||||
and form["start_dttm"].data
|
||||
and form["end_dttm"].data < form["start_dttm"].data
|
||||
):
|
||||
raise StopValidation(
|
||||
_("Annotation end time must be no earlier than start time.")
|
||||
)
|
||||
|
||||
|
||||
class AnnotationModelView( # pylint: disable=too-many-ancestors
|
||||
SupersetModelView,
|
||||
CompactCRUDMixin,
|
||||
):
|
||||
datamodel = SQLAInterface(Annotation)
|
||||
include_route_methods = RouteMethod.CRUD_SET | {"annotation"}
|
||||
|
||||
class AnnotationLayerView(BaseSupersetView):
|
||||
route_base = "/annotationlayer"
|
||||
class_permission_name = "Annotation"
|
||||
method_permission_name = MODEL_VIEW_RW_METHOD_PERMISSION_MAP
|
||||
|
||||
list_title = _("Annotations")
|
||||
show_title = _("Show Annotation")
|
||||
add_title = _("Add Annotation")
|
||||
edit_title = _("Edit Annotation")
|
||||
|
||||
list_columns = ["short_descr", "start_dttm", "end_dttm"]
|
||||
edit_columns = [
|
||||
"layer",
|
||||
"short_descr",
|
||||
"long_descr",
|
||||
"start_dttm",
|
||||
"end_dttm",
|
||||
"json_metadata",
|
||||
]
|
||||
|
||||
add_columns = edit_columns
|
||||
|
||||
label_columns = {
|
||||
"layer": _("Layer"),
|
||||
"short_descr": _("Label"),
|
||||
"long_descr": _("Description"),
|
||||
"start_dttm": _("Start"),
|
||||
"end_dttm": _("End"),
|
||||
"json_metadata": _("JSON Metadata"),
|
||||
}
|
||||
|
||||
description_columns = {
|
||||
"json_metadata": "This JSON represents any additional metadata this \
|
||||
annotation needs to add more context."
|
||||
}
|
||||
|
||||
validators_columns = {"start_dttm": [StartEndDttmValidator()]}
|
||||
|
||||
def pre_add(self, item: "AnnotationModelView") -> None:
|
||||
if not item.start_dttm:
|
||||
item.start_dttm = item.end_dttm
|
||||
elif not item.end_dttm:
|
||||
item.end_dttm = item.start_dttm
|
||||
|
||||
def pre_update(self, item: "AnnotationModelView") -> None:
|
||||
self.pre_add(item)
|
||||
|
||||
@expose("/<pk>/annotation/", methods=["GET"])
|
||||
@has_access
|
||||
def annotation(self, pk: int) -> FlaskResponse: # pylint: disable=unused-argument
|
||||
return super().render_app_template()
|
||||
|
||||
|
||||
class AnnotationLayerModelView(SupersetModelView):
|
||||
datamodel = SQLAInterface(AnnotationLayer)
|
||||
include_route_methods = RouteMethod.CRUD_SET | {RouteMethod.API_READ}
|
||||
related_views = [AnnotationModelView]
|
||||
|
||||
class_permission_name = "Annotation"
|
||||
method_permission_name = MODEL_VIEW_RW_METHOD_PERMISSION_MAP
|
||||
|
||||
list_title = _("Annotation Layers")
|
||||
show_title = _("Show Annotation Layer")
|
||||
add_title = _("Add Annotation Layer")
|
||||
edit_title = _("Edit Annotation Layer")
|
||||
|
||||
list_columns = ["id", "name", "descr"]
|
||||
edit_columns = ["name", "descr"]
|
||||
add_columns = edit_columns
|
||||
|
||||
label_columns = {"name": _("Name"), "descr": _("Description")}
|
||||
|
||||
@expose("/list/")
|
||||
@has_access
|
||||
@permission_name("read")
|
||||
def list(self) -> FlaskResponse:
|
||||
return super().render_app_template()
|
||||
|
||||
@expose("/<int:pk>/annotation")
|
||||
@has_access
|
||||
@permission_name("read")
|
||||
def get(self, pk: int) -> FlaskResponse: # pylint: disable=unused-argument
|
||||
return super().render_app_template()
|
||||
|
|
|
|||
|
|
@ -231,41 +231,6 @@ class TestCore(SupersetTestCase):
|
|||
rv = self.client.get(uri)
|
||||
self.assertEqual(rv.status_code, 422)
|
||||
|
||||
def test_annotation_json_endpoint(self):
|
||||
# Set up an annotation layer and annotation
|
||||
layer = AnnotationLayer(name="foo", descr="bar")
|
||||
db.session.add(layer)
|
||||
db.session.commit()
|
||||
|
||||
annotation = Annotation(
|
||||
layer_id=layer.id,
|
||||
short_descr="my_annotation",
|
||||
start_dttm=datetime.datetime(2020, 5, 20, 18, 21, 51),
|
||||
end_dttm=datetime.datetime(2020, 5, 20, 18, 31, 51),
|
||||
)
|
||||
|
||||
db.session.add(annotation)
|
||||
db.session.commit()
|
||||
|
||||
self.login()
|
||||
resp_annotations = json.loads(
|
||||
self.get_resp("annotationlayermodelview/api/read")
|
||||
)
|
||||
# the UI needs id and name to function
|
||||
self.assertIn("id", resp_annotations["result"][0])
|
||||
self.assertIn("name", resp_annotations["result"][0])
|
||||
|
||||
response = self.get_resp(
|
||||
f"/superset/annotation_json/{layer.id}?form_data="
|
||||
+ quote(json.dumps({"time_range": "100 years ago : now"}))
|
||||
)
|
||||
assert "my_annotation" in response
|
||||
|
||||
# Rollback changes
|
||||
db.session.delete(annotation)
|
||||
db.session.delete(layer)
|
||||
db.session.commit()
|
||||
|
||||
def test_admin_only_permissions(self):
|
||||
def assert_admin_permission_in(role_name, assert_func):
|
||||
role = security_manager.find_role(role_name)
|
||||
|
|
|
|||
Loading…
Reference in New Issue