utils.py 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. # Licensed to the Apache Software Foundation (ASF) under one
  2. # or more contributor license agreements. See the NOTICE file
  3. # distributed with this work for additional information
  4. # regarding copyright ownership. The ASF licenses this file
  5. # to you under the Apache License, Version 2.0 (the
  6. # "License"); you may not use this file except in compliance
  7. # with the License. You may obtain a copy of the License at
  8. #
  9. # http://www.apache.org/licenses/LICENSE-2.0
  10. #
  11. # Unless required by applicable law or agreed to in writing,
  12. # software distributed under the License is distributed on an
  13. # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  14. # KIND, either express or implied. See the License for the
  15. # specific language governing permissions and limitations
  16. # under the License.
  17. from collections import defaultdict
  18. from datetime import date
  19. from typing import Any, Dict, List, Optional, Tuple
  20. from urllib import parse
  21. import simplejson as json
  22. from flask import request
  23. import superset.models.core as models
  24. from superset import app, db, viz
  25. from superset.connectors.connector_registry import ConnectorRegistry
  26. from superset.exceptions import SupersetException
  27. from superset.legacy import update_time_range
  28. from superset.models.slice import Slice
  29. from superset.utils.core import QueryStatus, TimeRangeEndpoint
  30. FORM_DATA_KEY_BLACKLIST: List[str] = []
  31. if not app.config["ENABLE_JAVASCRIPT_CONTROLS"]:
  32. FORM_DATA_KEY_BLACKLIST = ["js_tooltip", "js_onclick_href", "js_data_mutator"]
  33. def bootstrap_user_data(user, include_perms=False):
  34. if user.is_anonymous:
  35. return {}
  36. payload = {
  37. "username": user.username,
  38. "firstName": user.first_name,
  39. "lastName": user.last_name,
  40. "userId": user.id,
  41. "isActive": user.is_active,
  42. "createdOn": user.created_on.isoformat(),
  43. "email": user.email,
  44. }
  45. if include_perms:
  46. roles, permissions = get_permissions(user)
  47. payload["roles"] = roles
  48. payload["permissions"] = permissions
  49. return payload
  50. def get_permissions(user):
  51. if not user.roles:
  52. raise AttributeError("User object does not have roles")
  53. roles = {}
  54. permissions = defaultdict(set)
  55. for role in user.roles:
  56. perms = set()
  57. for perm in role.permissions:
  58. if perm.permission and perm.view_menu:
  59. perms.add((perm.permission.name, perm.view_menu.name))
  60. if perm.permission.name in ("datasource_access", "database_access"):
  61. permissions[perm.permission.name].add(perm.view_menu.name)
  62. roles[role.name] = [
  63. [perm.permission.name, perm.view_menu.name]
  64. for perm in role.permissions
  65. if perm.permission and perm.view_menu
  66. ]
  67. return roles, permissions
  68. def get_viz(
  69. slice_id=None, form_data=None, datasource_type=None, datasource_id=None, force=False
  70. ):
  71. if slice_id:
  72. slc = db.session.query(Slice).filter_by(id=slice_id).one()
  73. return slc.get_viz()
  74. viz_type = form_data.get("viz_type", "table")
  75. datasource = ConnectorRegistry.get_datasource(
  76. datasource_type, datasource_id, db.session
  77. )
  78. viz_obj = viz.viz_types[viz_type](datasource, form_data=form_data, force=force)
  79. return viz_obj
  80. def get_form_data(slice_id=None, use_slice_data=False):
  81. form_data = {}
  82. post_data = request.form.get("form_data")
  83. request_args_data = request.args.get("form_data")
  84. # Supporting POST
  85. if post_data:
  86. form_data.update(json.loads(post_data))
  87. # request params can overwrite post body
  88. if request_args_data:
  89. form_data.update(json.loads(request_args_data))
  90. url_id = request.args.get("r")
  91. if url_id:
  92. saved_url = db.session.query(models.Url).filter_by(id=url_id).first()
  93. if saved_url:
  94. url_str = parse.unquote_plus(
  95. saved_url.url.split("?")[1][10:], encoding="utf-8", errors=None
  96. )
  97. url_form_data = json.loads(url_str)
  98. # allow form_date in request override saved url
  99. url_form_data.update(form_data)
  100. form_data = url_form_data
  101. form_data = {k: v for k, v in form_data.items() if k not in FORM_DATA_KEY_BLACKLIST}
  102. # When a slice_id is present, load from DB and override
  103. # the form_data from the DB with the other form_data provided
  104. slice_id = form_data.get("slice_id") or slice_id
  105. slc = None
  106. # Check if form data only contains slice_id, additional filters and viz type
  107. valid_keys = ["slice_id", "extra_filters", "adhoc_filters", "viz_type"]
  108. valid_slice_id = all(key in valid_keys for key in form_data)
  109. # Include the slice_form_data if request from explore or slice calls
  110. # or if form_data only contains slice_id and additional filters
  111. if slice_id and (use_slice_data or valid_slice_id):
  112. slc = db.session.query(Slice).filter_by(id=slice_id).one_or_none()
  113. if slc:
  114. slice_form_data = slc.form_data.copy()
  115. slice_form_data.update(form_data)
  116. form_data = slice_form_data
  117. update_time_range(form_data)
  118. if app.config["SIP_15_ENABLED"]:
  119. form_data["time_range_endpoints"] = get_time_range_endpoints(
  120. form_data, slc, slice_id
  121. )
  122. return form_data, slc
  123. def get_datasource_info(
  124. datasource_id: Optional[int],
  125. datasource_type: Optional[str],
  126. form_data: Dict[str, Any],
  127. ) -> Tuple[int, Optional[str]]:
  128. """
  129. Compatibility layer for handling of datasource info
  130. datasource_id & datasource_type used to be passed in the URL
  131. directory, now they should come as part of the form_data,
  132. This function allows supporting both without duplicating code
  133. :param datasource_id: The datasource ID
  134. :param datasource_type: The datasource type, i.e., 'druid' or 'table'
  135. :param form_data: The URL form data
  136. :returns: The datasource ID and type
  137. :raises SupersetException: If the datasource no longer exists
  138. """
  139. datasource = form_data.get("datasource", "")
  140. if "__" in datasource:
  141. datasource_id, datasource_type = datasource.split("__")
  142. # The case where the datasource has been deleted
  143. if datasource_id == "None":
  144. datasource_id = None
  145. if not datasource_id:
  146. raise SupersetException(
  147. "The datasource associated with this chart no longer exists"
  148. )
  149. datasource_id = int(datasource_id)
  150. return datasource_id, datasource_type
  151. def apply_display_max_row_limit(
  152. sql_results: Dict[str, Any], rows: Optional[int] = None
  153. ) -> Dict[str, Any]:
  154. """
  155. Given a `sql_results` nested structure, applies a limit to the number of rows
  156. `sql_results` here is the nested structure coming out of sql_lab.get_sql_results, it
  157. contains metadata about the query, as well as the data set returned by the query.
  158. This method limits the number of rows adds a `displayLimitReached: True` flag to the
  159. metadata.
  160. :param sql_results: The results of a sql query from sql_lab.get_sql_results
  161. :returns: The mutated sql_results structure
  162. """
  163. display_limit = rows or app.config["DISPLAY_MAX_ROW"]
  164. if (
  165. display_limit
  166. and sql_results["status"] == QueryStatus.SUCCESS
  167. and display_limit < sql_results["query"]["rows"]
  168. ):
  169. sql_results["data"] = sql_results["data"][:display_limit]
  170. sql_results["displayLimitReached"] = True
  171. return sql_results
  172. def get_time_range_endpoints(
  173. form_data: Dict[str, Any],
  174. slc: Optional[Slice] = None,
  175. slice_id: Optional[int] = None,
  176. ) -> Optional[Tuple[TimeRangeEndpoint, TimeRangeEndpoint]]:
  177. """
  178. Get the slice aware time range endpoints from the form-data falling back to the SQL
  179. database specific definition or default if not defined.
  180. Note under certain circumstances the slice object may not exist, however the slice
  181. ID may be defined which serves as a fallback.
  182. When SIP-15 is enabled all slices and will the [start, end) interval. If the grace
  183. period is defined and has ended all slices will adhere to the [start, end) interval.
  184. :param form_data: The form-data
  185. :param slc: The slice
  186. :param slice_id: The slice ID
  187. :returns: The time range endpoints tuple
  188. """
  189. if (
  190. app.config["SIP_15_GRACE_PERIOD_END"]
  191. and date.today() >= app.config["SIP_15_GRACE_PERIOD_END"]
  192. ):
  193. return (TimeRangeEndpoint.INCLUSIVE, TimeRangeEndpoint.EXCLUSIVE)
  194. endpoints = form_data.get("time_range_endpoints")
  195. if (slc or slice_id) and not endpoints:
  196. try:
  197. _, datasource_type = get_datasource_info(None, None, form_data)
  198. except SupersetException:
  199. return None
  200. if datasource_type == "table":
  201. if not slc:
  202. slc = db.session.query(Slice).filter_by(id=slice_id).one_or_none()
  203. if slc:
  204. endpoints = slc.datasource.database.get_extra().get(
  205. "time_range_endpoints"
  206. )
  207. if not endpoints:
  208. endpoints = app.config["SIP_15_DEFAULT_TIME_RANGE_ENDPOINTS"]
  209. if endpoints:
  210. start, end = endpoints
  211. return (TimeRangeEndpoint(start), TimeRangeEndpoint(end))
  212. return (TimeRangeEndpoint.INCLUSIVE, TimeRangeEndpoint.EXCLUSIVE)