blob: 2bcc0c7c99b1ecd1f7f1cfc579fc41bf56f9f023 [file] [log] [blame]
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
from __future__ import absolute_import, division, print_function, unicode_literals
from typing import Any, Dict, List
import simplejson as json
from flask import request, Response
from flask_appbuilder import expose
from flask_appbuilder.security.decorators import has_access_api
from jinja2.sandbox import SandboxedEnvironment
from sqlalchemy import and_, func
from superset import db, utils
from superset.jinja_context import ExtraCache
from superset.models.dashboard import Dashboard
from superset.models.slice import Slice
from superset.models.sql_lab import SavedQuery
from superset.models.tags import ObjectTypes, Tag, TaggedObject, TagTypes
from superset.typing import FlaskResponse
from .base import BaseSupersetView, json_success
def process_template(content: str) -> str:
env = SandboxedEnvironment()
template = env.from_string(content)
context = {
"current_user_id": ExtraCache.current_user_id,
"current_username": ExtraCache.current_username,
}
return template.render(context)
class TagView(BaseSupersetView):
@has_access_api
@expose("/tags/suggestions/", methods=["GET"])
def suggestions(self) -> FlaskResponse: # pylint: disable=no-self-use
query = (
db.session.query(TaggedObject)
.join(Tag)
.with_entities(TaggedObject.tag_id, Tag.name)
.group_by(TaggedObject.tag_id, Tag.name)
.order_by(func.count().desc())
.all()
)
tags = [{"id": id, "name": name} for id, name in query]
return json_success(json.dumps(tags))
@has_access_api
@expose("/tags/<object_type:object_type>/<int:object_id>/", methods=["GET"])
def get( # pylint: disable=no-self-use
self, object_type: ObjectTypes, object_id: int
) -> FlaskResponse:
"""List all tags a given object has."""
if object_id == 0:
return json_success(json.dumps([]))
query = db.session.query(TaggedObject).filter(
and_(
TaggedObject.object_type == object_type,
TaggedObject.object_id == object_id,
)
)
tags = [{"id": obj.tag.id, "name": obj.tag.name} for obj in query]
return json_success(json.dumps(tags))
@has_access_api
@expose("/tags/<object_type:object_type>/<int:object_id>/", methods=["POST"])
def post( # pylint: disable=no-self-use
self, object_type: ObjectTypes, object_id: int
) -> FlaskResponse:
"""Add new tags to an object."""
if object_id == 0:
return Response(status=404)
tagged_objects = []
for name in request.get_json(force=True):
if ":" in name:
type_name = name.split(":", 1)[0]
type_ = TagTypes[type_name]
else:
type_ = TagTypes.custom
tag = db.session.query(Tag).filter_by(name=name, type=type_).first()
if not tag:
tag = Tag(name=name, type=type_)
tagged_objects.append(
TaggedObject(object_id=object_id, object_type=object_type, tag=tag)
)
db.session.add_all(tagged_objects)
db.session.commit()
return Response(status=201) # 201 CREATED
@has_access_api
@expose("/tags/<object_type:object_type>/<int:object_id>/", methods=["DELETE"])
def delete( # pylint: disable=no-self-use
self, object_type: ObjectTypes, object_id: int
) -> FlaskResponse:
"""Remove tags from an object."""
tag_names = request.get_json(force=True)
if not tag_names:
return Response(status=403)
db.session.query(TaggedObject).filter(
and_(
TaggedObject.object_type == object_type,
TaggedObject.object_id == object_id,
),
TaggedObject.tag.has(Tag.name.in_(tag_names)),
).delete(synchronize_session=False)
db.session.commit()
return Response(status=204) # 204 NO CONTENT
@has_access_api
@expose("/tagged_objects/", methods=["GET", "POST"])
def tagged_objects(self) -> FlaskResponse: # pylint: disable=no-self-use
tags = [
process_template(tag)
for tag in request.args.get("tags", "").split(",")
if tag
]
if not tags:
return json_success(json.dumps([]))
# filter types
types = [type_ for type_ in request.args.get("types", "").split(",") if type_]
results: List[Dict[str, Any]] = []
# dashboards
if not types or "dashboard" in types:
dashboards = (
db.session.query(Dashboard)
.join(
TaggedObject,
and_(
TaggedObject.object_id == Dashboard.id,
TaggedObject.object_type == ObjectTypes.dashboard,
),
)
.join(Tag, TaggedObject.tag_id == Tag.id)
.filter(Tag.name.in_(tags))
)
results.extend(
{
"id": obj.id,
"type": ObjectTypes.dashboard.name,
"name": obj.dashboard_title,
"url": obj.url,
"changed_on": obj.changed_on,
"created_by": obj.created_by_fk,
"creator": obj.creator(),
}
for obj in dashboards
)
# charts
if not types or "chart" in types:
charts = (
db.session.query(Slice)
.join(
TaggedObject,
and_(
TaggedObject.object_id == Slice.id,
TaggedObject.object_type == ObjectTypes.chart,
),
)
.join(Tag, TaggedObject.tag_id == Tag.id)
.filter(Tag.name.in_(tags))
)
results.extend(
{
"id": obj.id,
"type": ObjectTypes.chart.name,
"name": obj.slice_name,
"url": obj.url,
"changed_on": obj.changed_on,
"created_by": obj.created_by_fk,
"creator": obj.creator(),
}
for obj in charts
)
# saved queries
if not types or "query" in types:
saved_queries = (
db.session.query(SavedQuery)
.join(
TaggedObject,
and_(
TaggedObject.object_id == SavedQuery.id,
TaggedObject.object_type == ObjectTypes.query,
),
)
.join(Tag, TaggedObject.tag_id == Tag.id)
.filter(Tag.name.in_(tags))
)
results.extend(
{
"id": obj.id,
"type": ObjectTypes.query.name,
"name": obj.label,
"url": obj.url(),
"changed_on": obj.changed_on,
"created_by": obj.created_by_fk,
"creator": obj.creator(),
}
for obj in saved_queries
)
return json_success(json.dumps(results, default=utils.core.json_int_dttm_ser))