From afb8bd5fe68066cfe2f3d384d659215dc4790d9d Mon Sep 17 00:00:00 2001 From: Beto Dealmeida Date: Wed, 18 Aug 2021 17:00:29 -0700 Subject: [PATCH 01/22] feat: improve embedded data table in text reports (#16335) * feat: improve HTML table in text reports * Remove unused import * Update tests * Fix test --- setup.cfg | 2 +- superset/charts/api.py | 15 +- superset/charts/post_processing.py | 39 +- superset/common/query_actions.py | 1 + superset/reports/commands/exceptions.py | 8 + superset/reports/commands/execute.py | 82 +++- superset/reports/notifications/email.py | 44 +- superset/reports/notifications/slack.py | 21 +- superset/utils/csv.py | 19 + .../reports/commands_tests.py | 63 ++- .../unit_tests/charts/test_post_processing.py | 429 ++++-------------- 11 files changed, 295 insertions(+), 428 deletions(-) diff --git a/setup.cfg b/setup.cfg index 15817a8039a38..9a108f76a480e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -30,7 +30,7 @@ combine_as_imports = true include_trailing_comma = true line_length = 88 known_first_party = superset -known_third_party =alembic,apispec,backoff,bleach,cachelib,celery,click,colorama,cron_descriptor,croniter,cryptography,dateutil,deprecation,flask,flask_appbuilder,flask_babel,flask_caching,flask_compress,flask_jwt_extended,flask_login,flask_migrate,flask_sqlalchemy,flask_talisman,flask_testing,flask_wtf,freezegun,geohash,geopy,graphlib,holidays,humanize,isodate,jinja2,jwt,markdown,markupsafe,marshmallow,marshmallow_enum,msgpack,numpy,pandas,parameterized,parsedatetime,pgsanity,pkg_resources,polyline,prison,progress,pyarrow,pyhive,pyparsing,pytest,pytest_mock,pytz,redis,requests,selenium,setuptools,simplejson,slack,sqlalchemy,sqlalchemy_utils,sqlparse,tabulate,typing_extensions,urllib3,werkzeug,wtforms,wtforms_json,yaml +known_third_party =alembic,apispec,backoff,bleach,cachelib,celery,click,colorama,cron_descriptor,croniter,cryptography,dateutil,deprecation,flask,flask_appbuilder,flask_babel,flask_caching,flask_compress,flask_jwt_extended,flask_login,flask_migrate,flask_sqlalchemy,flask_talisman,flask_testing,flask_wtf,freezegun,geohash,geopy,graphlib,holidays,humanize,isodate,jinja2,jwt,markdown,markupsafe,marshmallow,marshmallow_enum,msgpack,numpy,pandas,parameterized,parsedatetime,pgsanity,pkg_resources,polyline,prison,progress,pyarrow,pyhive,pyparsing,pytest,pytest_mock,pytz,redis,requests,selenium,setuptools,simplejson,slack,sqlalchemy,sqlalchemy_utils,sqlparse,typing_extensions,urllib3,werkzeug,wtforms,wtforms_json,yaml multi_line_output = 3 order_by_type = false diff --git a/superset/charts/api.py b/superset/charts/api.py index db86615308715..3fa48c076b177 100644 --- a/superset/charts/api.py +++ b/superset/charts/api.py @@ -499,15 +499,6 @@ def send_chart_response( result_type = result["query_context"].result_type result_format = result["query_context"].result_format - # Post-process the data so it matches the data presented in the chart. - # This is needed for sending reports based on text charts that do the - # post-processing of data, eg, the pivot table. - if ( - result_type == ChartDataResultType.POST_PROCESSED - and result_format == ChartDataResultFormat.CSV - ): - result = apply_post_process(result, form_data) - if result_format == ChartDataResultFormat.CSV: # Verify user has permission to export CSV file if not security_manager.can_access("can_csv", "Superset"): @@ -518,6 +509,12 @@ def send_chart_response( return CsvResponse(data, headers=generate_download_headers("csv")) if result_format == ChartDataResultFormat.JSON: + # Post-process the data so it matches the data presented in the chart. + # This is needed for sending reports based on text charts that do the + # post-processing of data, eg, the pivot table. + if result_type == ChartDataResultType.POST_PROCESSED: + result = apply_post_process(result, form_data) + response_data = simplejson.dumps( {"result": result["queries"]}, default=json_int_dttm_ser, diff --git a/superset/charts/post_processing.py b/superset/charts/post_processing.py index 4919907ded852..96d1e7ca0ed44 100644 --- a/superset/charts/post_processing.py +++ b/superset/charts/post_processing.py @@ -26,7 +26,6 @@ for these chart types. """ -from io import StringIO from typing import Any, Dict, List, Optional, Tuple import pandas as pd @@ -126,11 +125,13 @@ def pivot_df( # pylint: disable=too-many-locals, too-many-arguments, too-many-s total = df.sum(axis=axis["columns"]) df = df.astype(total.dtypes).div(total, axis=axis["rows"]) - if show_rows_total: - # convert to a MultiIndex to simplify logic - if not isinstance(df.columns, pd.MultiIndex): - df.columns = pd.MultiIndex.from_tuples([(str(i),) for i in df.columns]) + # convert to a MultiIndex to simplify logic + if not isinstance(df.index, pd.MultiIndex): + df.index = pd.MultiIndex.from_tuples([(str(i),) for i in df.index]) + if not isinstance(df.columns, pd.MultiIndex): + df.columns = pd.MultiIndex.from_tuples([(str(i),) for i in df.columns]) + if show_rows_total: # add subtotal for each group and overall total; we start from the # overall group, and iterate deeper into subgroups groups = df.columns @@ -146,10 +147,6 @@ def pivot_df( # pylint: disable=too-many-locals, too-many-arguments, too-many-s df.insert(int(slice_.stop), subtotal_name, subtotal) if rows and show_columns_total: - # convert to a MultiIndex to simplify logic - if not isinstance(df.index, pd.MultiIndex): - df.index = pd.MultiIndex.from_tuples([(str(i),) for i in df.index]) - # add subtotal for each group and overall total; we start from the # overall group, and iterate deeper into subgroups groups = df.index @@ -279,24 +276,28 @@ def apply_post_process( post_processor = post_processors[viz_type] for query in result["queries"]: - df = pd.read_csv(StringIO(query["data"])) + df = pd.DataFrame.from_dict(query["data"]) processed_df = post_processor(df, form_data) - # flatten column names + query["colnames"] = list(processed_df.columns) + query["indexnames"] = list(processed_df.index) + query["coltypes"] = extract_dataframe_dtypes(processed_df) + query["rowcount"] = len(processed_df.index) + + # flatten columns/index so we can encode data as JSON processed_df.columns = [ " ".join(str(name) for name in column).strip() if isinstance(column, tuple) else column for column in processed_df.columns ] + processed_df.index = [ + " ".join(str(name) for name in index).strip() + if isinstance(index, tuple) + else index + for index in processed_df.index + ] - buf = StringIO() - processed_df.to_csv(buf) - buf.seek(0) - - query["data"] = buf.getvalue() - query["colnames"] = list(processed_df.columns) - query["coltypes"] = extract_dataframe_dtypes(processed_df) - query["rowcount"] = len(processed_df.index) + query["data"] = processed_df.to_dict() return result diff --git a/superset/common/query_actions.py b/superset/common/query_actions.py index 1987872470bfa..ea8610433993d 100644 --- a/superset/common/query_actions.py +++ b/superset/common/query_actions.py @@ -101,6 +101,7 @@ def _get_full( status = payload["status"] if status != QueryStatus.FAILED: payload["colnames"] = list(df.columns) + payload["indexnames"] = list(df.index) payload["coltypes"] = extract_dataframe_dtypes(df) payload["data"] = query_context.get_data(df) del payload["df"] diff --git a/superset/reports/commands/exceptions.py b/superset/reports/commands/exceptions.py index dfe2402da0449..c59ba3e8b4c95 100644 --- a/superset/reports/commands/exceptions.py +++ b/superset/reports/commands/exceptions.py @@ -116,6 +116,10 @@ class ReportScheduleCsvFailedError(CommandException): message = _("Report Schedule execution failed when generating a csv.") +class ReportScheduleDataFrameFailedError(CommandException): + message = _("Report Schedule execution failed when generating a dataframe.") + + class ReportScheduleExecuteUnexpectedError(CommandException): message = _("Report Schedule execution got an unexpected error.") @@ -171,6 +175,10 @@ class ReportScheduleCsvTimeout(CommandException): message = _("A timeout occurred while generating a csv.") +class ReportScheduleDataFrameTimeout(CommandException): + message = _("A timeout occurred while generating a dataframe.") + + class ReportScheduleAlertGracePeriodError(CommandException): message = _("Alert fired during grace period.") diff --git a/superset/reports/commands/execute.py b/superset/reports/commands/execute.py index 065e38bc75362..c52ffb3c8fa61 100644 --- a/superset/reports/commands/execute.py +++ b/superset/reports/commands/execute.py @@ -17,7 +17,6 @@ import json import logging from datetime import datetime, timedelta -from io import BytesIO from typing import Any, List, Optional from uuid import UUID @@ -45,6 +44,8 @@ ReportScheduleAlertGracePeriodError, ReportScheduleCsvFailedError, ReportScheduleCsvTimeout, + ReportScheduleDataFrameFailedError, + ReportScheduleDataFrameTimeout, ReportScheduleExecuteUnexpectedError, ReportScheduleNotFoundError, ReportScheduleNotificationError, @@ -65,7 +66,7 @@ from superset.reports.notifications.exceptions import NotificationError from superset.utils.celery import session_scope from superset.utils.core import ChartDataResultFormat, ChartDataResultType -from superset.utils.csv import get_chart_csv_data +from superset.utils.csv import get_chart_csv_data, get_chart_dataframe from superset.utils.screenshots import ( BaseScreenshot, ChartScreenshot, @@ -137,17 +138,23 @@ def create_log( # pylint: disable=too-many-arguments self._session.commit() def _get_url( - self, user_friendly: bool = False, csv: bool = False, **kwargs: Any + self, + user_friendly: bool = False, + result_format: Optional[ChartDataResultFormat] = None, + **kwargs: Any, ) -> str: """ Get the url for this report schedule: chart or dashboard """ if self._report_schedule.chart: - if csv: + if result_format in { + ChartDataResultFormat.CSV, + ChartDataResultFormat.JSON, + }: return get_url_path( "ChartRestApi.get_data", pk=self._report_schedule.chart_id, - format=ChartDataResultFormat.CSV.value, + format=result_format.value, type=ChartDataResultType.POST_PROCESSED.value, ) return get_url_path( @@ -213,28 +220,14 @@ def _get_screenshot(self) -> bytes: return image_data def _get_csv_data(self) -> bytes: - url = self._get_url(csv=True) + url = self._get_url(result_format=ChartDataResultFormat.CSV) auth_cookies = machine_auth_provider_factory.instance.get_auth_cookies( self._get_user() ) - # To load CSV data from the endpoint the chart must have been saved - # with its query context. For charts without saved query context we - # get a screenshot to force the chart to produce and save the query - # context. if self._report_schedule.chart.query_context is None: logger.warning("No query context found, taking a screenshot to generate it") - try: - self._get_screenshot() - except ( - ReportScheduleScreenshotFailedError, - ReportScheduleScreenshotTimeout, - ) as ex: - raise ReportScheduleCsvFailedError( - "Unable to fetch CSV data because the chart has no query context " - "saved, and an error occurred when fetching it via a screenshot. " - "Please try loading the chart and saving it again." - ) from ex + self._update_query_context() try: logger.info("Getting chart from %s", url) @@ -251,11 +244,50 @@ def _get_csv_data(self) -> bytes: def _get_embedded_data(self) -> pd.DataFrame: """ - Return data as an HTML table, to embed in the email. + Return data as a Pandas dataframe, to embed in notifications as a table. + """ + url = self._get_url(result_format=ChartDataResultFormat.JSON) + auth_cookies = machine_auth_provider_factory.instance.get_auth_cookies( + self._get_user() + ) + + if self._report_schedule.chart.query_context is None: + logger.warning("No query context found, taking a screenshot to generate it") + self._update_query_context() + + try: + logger.info("Getting chart from %s", url) + dataframe = get_chart_dataframe(url, auth_cookies) + except SoftTimeLimitExceeded as ex: + raise ReportScheduleDataFrameTimeout() from ex + except Exception as ex: + raise ReportScheduleDataFrameFailedError( + f"Failed generating dataframe {str(ex)}" + ) from ex + if dataframe is None: + raise ReportScheduleCsvFailedError() + return dataframe + + def _update_query_context(self) -> None: + """ + Update chart query context. + + To load CSV data from the endpoint the chart must have been saved + with its query context. For charts without saved query context we + get a screenshot to force the chart to produce and save the query + context. """ - buf = BytesIO(self._get_csv_data()) - df = pd.read_csv(buf) - return df + try: + self._get_screenshot() + except ( + ReportScheduleScreenshotFailedError, + ReportScheduleScreenshotTimeout, + ) as ex: + raise ReportScheduleCsvFailedError( + "Unable to fetch data because the chart has no query context " + "saved, and an error occurred when fetching it via a screenshot. " + "Please try loading the chart and saving it again." + ) from ex def _get_notification_content(self) -> NotificationContent: """ diff --git a/superset/reports/notifications/email.py b/superset/reports/notifications/email.py index a55824d4f8393..c1fce9939b3fe 100644 --- a/superset/reports/notifications/email.py +++ b/superset/reports/notifications/email.py @@ -17,6 +17,7 @@ # under the License. import json import logging +import textwrap from dataclasses import dataclass from email.utils import make_msgid, parseaddr from typing import Any, Dict, Optional @@ -33,6 +34,7 @@ logger = logging.getLogger(__name__) TABLE_TAGS = ["table", "th", "tr", "td", "thead", "tbody", "tfoot"] +TABLE_ATTRIBUTES = ["colspan", "rowspan", "halign", "border", "class"] @dataclass @@ -79,24 +81,40 @@ def _get_content(self) -> EmailContent: if self._content.embedded_data is not None: df = self._content.embedded_data html_table = bleach.clean( - df.to_html(na_rep="", index=False), tags=TABLE_TAGS + df.to_html(na_rep="", index=True), + tags=TABLE_TAGS, + attributes=TABLE_ATTRIBUTES, ) else: html_table = "" - body = __( - """ -

%(description)s

- Explore in Superset

- %(html_table)s - %(img_tag)s - """, - description=description, - url=self._content.url, - html_table=html_table, - img_tag=''.format(msgid) + call_to_action = __("Explore in Superset") + img_tag = ( + f'' if self._content.screenshot - else "", + else "" + ) + body = textwrap.dedent( + f""" + + + + + +

{description}

+ {call_to_action}

+ {html_table} + {img_tag} + + + """ ) if self._content.screenshot: image = {msgid: self._content.screenshot} diff --git a/superset/reports/notifications/slack.py b/superset/reports/notifications/slack.py index 8d4078c92fb60..b5666a66f70da 100644 --- a/superset/reports/notifications/slack.py +++ b/superset/reports/notifications/slack.py @@ -24,7 +24,6 @@ from flask_babel import gettext as __ from slack import WebClient from slack.errors import SlackApiError, SlackClientError -from tabulate import tabulate from superset import app from superset.models.reports import ReportRecipientType @@ -89,6 +88,20 @@ def _get_body(self) -> str: # Embed data in the message df = self._content.embedded_data + # Flatten columns/index so they show up nicely in the table + df.columns = [ + " ".join(str(name) for name in column).strip() + if isinstance(column, tuple) + else column + for column in df.columns + ] + df.index = [ + " ".join(str(name) for name in index).strip() + if isinstance(index, tuple) + else index + for index in df.index + ] + # Slack Markdown only works on messages shorter than 4k chars, so we might # need to truncate the data for i in range(len(df) - 1): @@ -96,7 +109,7 @@ def _get_body(self) -> str: truncated_df = truncated_df.append( {k: "..." for k in df.columns}, ignore_index=True ) - tabulated = tabulate(truncated_df, headers="keys", showindex=False) + tabulated = df.to_markdown() table = f"```\n{tabulated}\n```\n\n(table was truncated)" message = self._message_template(table) if len(message) > MAXIMUM_MESSAGE_SIZE: @@ -105,7 +118,7 @@ def _get_body(self) -> str: truncated_df = truncated_df.append( {k: "..." for k in df.columns}, ignore_index=True ) - tabulated = tabulate(truncated_df, headers="keys", showindex=False) + tabulated = df.to_markdown() table = ( f"```\n{tabulated}\n```\n\n(table was truncated)" if len(truncated_df) > 0 @@ -115,7 +128,7 @@ def _get_body(self) -> str: # Send full data else: - tabulated = tabulate(df, headers="keys", showindex=False) + tabulated = df.to_markdown() table = f"```\n{tabulated}\n```" return self._message_template(table) diff --git a/superset/utils/csv.py b/superset/utils/csv.py index 3e5fe46212594..3c362f7fac432 100644 --- a/superset/utils/csv.py +++ b/superset/utils/csv.py @@ -20,6 +20,7 @@ from urllib.error import URLError import pandas as pd +import simplejson negative_number_re = re.compile(r"^-[0-9.]+$") @@ -84,3 +85,21 @@ def get_chart_csv_data( if content: return content return None + + +def get_chart_dataframe( + chart_url: str, auth_cookies: Optional[Dict[str, str]] = None +) -> Optional[pd.DataFrame]: + content = get_chart_csv_data(chart_url, auth_cookies) + if content is None: + return None + + result = simplejson.loads(content.decode("utf-8")) + df = pd.DataFrame.from_dict(result["result"][0]["data"]) + df.columns = pd.MultiIndex.from_tuples( + tuple(colname) for colname in result["result"][0]["colnames"] + ) + df.index = pd.MultiIndex.from_tuples( + tuple(indexname) for indexname in result["result"][0]["indexnames"] + ) + return df diff --git a/tests/integration_tests/reports/commands_tests.py b/tests/integration_tests/reports/commands_tests.py index fef19e88e7f01..733b75a2e52ce 100644 --- a/tests/integration_tests/reports/commands_tests.py +++ b/tests/integration_tests/reports/commands_tests.py @@ -756,19 +756,37 @@ def test_email_chart_report_schedule_with_csv_no_query_context( @patch("superset.utils.csv.urllib.request.urlopen") @patch("superset.utils.csv.urllib.request.OpenerDirector.open") @patch("superset.reports.notifications.email.send_email_smtp") -@patch("superset.utils.csv.get_chart_csv_data") +@patch("superset.utils.csv.get_chart_dataframe") def test_email_chart_report_schedule_with_text( - csv_mock, email_mock, mock_open, mock_urlopen, create_report_email_chart_with_text, + dataframe_mock, + email_mock, + mock_open, + mock_urlopen, + create_report_email_chart_with_text, ): """ ExecuteReport Command: Test chart email report schedule with text """ - # setup csv mock + # setup dataframe mock response = Mock() mock_open.return_value = response mock_urlopen.return_value = response mock_urlopen.return_value.getcode.return_value = 200 - response.read.return_value = CSV_FILE + response.read.return_value = json.dumps( + { + "result": [ + { + "data": { + "t1": {0: "c11", 1: "c21"}, + "t2": {0: "c12", 1: "c22"}, + "t3__sum": {0: "c13", 1: "c23"}, + }, + "colnames": [("t1",), ("t2",), ("t3__sum",)], + "indexnames": [(0,), (1,)], + }, + ], + } + ).encode("utf-8") with freeze_time("2020-01-01T00:00:00Z"): AsyncExecuteReportScheduleCommand( @@ -776,9 +794,10 @@ def test_email_chart_report_schedule_with_text( ).run() # assert that the data is embedded correctly - table_html = """ + table_html = """
+ @@ -786,11 +805,13 @@ def test_email_chart_report_schedule_with_text( + + @@ -908,9 +929,9 @@ def test_slack_chart_report_schedule_with_csv( @patch("superset.reports.notifications.slack.WebClient.chat_postMessage") @patch("superset.utils.csv.urllib.request.urlopen") @patch("superset.utils.csv.urllib.request.OpenerDirector.open") -@patch("superset.utils.csv.get_chart_csv_data") +@patch("superset.utils.csv.get_chart_dataframe") def test_slack_chart_report_schedule_with_text( - csv_mock, + dataframe_mock, mock_open, mock_urlopen, post_message_mock, @@ -919,24 +940,36 @@ def test_slack_chart_report_schedule_with_text( """ ExecuteReport Command: Test chart slack report schedule with text """ - # setup csv mock + # setup dataframe mock response = Mock() mock_open.return_value = response mock_urlopen.return_value = response mock_urlopen.return_value.getcode.return_value = 200 - response.read.return_value = CSV_FILE + response.read.return_value = json.dumps( + { + "result": [ + { + "data": { + "t1": {0: "c11", 1: "c21"}, + "t2": {0: "c12", 1: "c22"}, + "t3__sum": {0: "c13", 1: "c23"}, + }, + "colnames": [("t1",), ("t2",), ("t3__sum",)], + "indexnames": [(0,), (1,)], + }, + ], + } + ).encode("utf-8") with freeze_time("2020-01-01T00:00:00Z"): AsyncExecuteReportScheduleCommand( TEST_ID, create_report_slack_chart_with_text.id, datetime.utcnow() ).run() - table_markdown = """``` -t1 t2 t3__sum ----- ---- --------- -c11 c12 c13 -c21 c22 c23 -```""" + table_markdown = """| | t1 | t2 | t3__sum | +|---:|:-----|:-----|:----------| +| 0 | c11 | c12 | c13 | +| 1 | c21 | c22 | c23 |""" assert table_markdown in post_message_mock.call_args[1]["text"] # Assert logs are correct diff --git a/tests/unit_tests/charts/test_post_processing.py b/tests/unit_tests/charts/test_post_processing.py index 94635779966e8..c82ae7ab715f2 100644 --- a/tests/unit_tests/charts/test_post_processing.py +++ b/tests/unit_tests/charts/test_post_processing.py @@ -15,265 +15,9 @@ # specific language governing permissions and limitations # under the License. -import copy -from typing import Any, Dict - import pandas as pd -from superset.charts.post_processing import apply_post_process, pivot_df -from superset.utils.core import GenericDataType, QueryStatus - -RESULT: Dict[str, Any] = { - "query_context": None, - "queries": [ - { - "cache_key": "1bd3ab8c01e98a0e349fb61bc76d9b90", - "cached_dttm": None, - "cache_timeout": 86400, - "annotation_data": {}, - "error": None, - "is_cached": None, - "query": """SELECT state AS state, - gender AS gender, - sum(num) AS \"Births\" -FROM birth_names -WHERE ds >= TO_TIMESTAMP('1921-07-28 00:00:00.000000', 'YYYY-MM-DD HH24:MI:SS.US') - AND ds < TO_TIMESTAMP('2021-07-28 10:39:44.000000', 'YYYY-MM-DD HH24:MI:SS.US') -GROUP BY state, - gender -LIMIT 50000; - -""", - "status": QueryStatus.SUCCESS, - "stacktrace": None, - "rowcount": 22, - "colnames": ["state", "gender", "Births"], - "coltypes": [ - GenericDataType.STRING, - GenericDataType.STRING, - GenericDataType.NUMERIC, - ], - "data": """state,gender,Births -OH,boy,2376385 -TX,girl,2313186 -MA,boy,1285126 -MA,girl,842146 -PA,boy,2390275 -NY,boy,3543961 -FL,boy,1968060 -TX,boy,3311985 -NJ,boy,1486126 -CA,girl,3567754 -CA,boy,5430796 -IL,girl,1614427 -FL,girl,1312593 -NY,girl,2280733 -NJ,girl,992702 -MI,girl,1326229 -other,girl,15058341 -other,boy,22044909 -MI,boy,1938321 -IL,boy,2357411 -PA,girl,1615383 -OH,girl,1622814 - """, - "applied_filters": [], - "rejected_filters": [], - } - ], -} - - -def test_pivot_table(): - form_data = { - "adhoc_filters": [], - "columns": ["state"], - "datasource": "3__table", - "date_format": "smart_date", - "extra_form_data": {}, - "granularity_sqla": "ds", - "groupby": ["gender"], - "metrics": [ - { - "aggregate": "SUM", - "column": {"column_name": "num", "type": "BIGINT"}, - "expressionType": "SIMPLE", - "label": "Births", - "optionName": "metric_11", - } - ], - "number_format": "SMART_NUMBER", - "order_desc": True, - "pandas_aggfunc": "sum", - "pivot_margins": True, - "row_limit": 50000, - "slice_id": 143, - "time_grain_sqla": "P1D", - "time_range": "100 years ago : now", - "time_range_endpoints": ["inclusive", "exclusive"], - "url_params": {}, - "viz_type": "pivot_table", - } - result = copy.deepcopy(RESULT) - assert apply_post_process(result, form_data) == { - "query_context": None, - "queries": [ - { - "cache_key": "1bd3ab8c01e98a0e349fb61bc76d9b90", - "cached_dttm": None, - "cache_timeout": 86400, - "annotation_data": {}, - "error": None, - "is_cached": None, - "query": """SELECT state AS state, - gender AS gender, - sum(num) AS \"Births\" -FROM birth_names -WHERE ds >= TO_TIMESTAMP('1921-07-28 00:00:00.000000', 'YYYY-MM-DD HH24:MI:SS.US') - AND ds < TO_TIMESTAMP('2021-07-28 10:39:44.000000', 'YYYY-MM-DD HH24:MI:SS.US') -GROUP BY state, - gender -LIMIT 50000; - -""", - "status": QueryStatus.SUCCESS, - "stacktrace": None, - "rowcount": 3, - "colnames": [ - "Births CA", - "Births FL", - "Births IL", - "Births MA", - "Births MI", - "Births NJ", - "Births NY", - "Births OH", - "Births PA", - "Births TX", - "Births other", - "Births Subtotal", - "Total (Sum)", - ], - "coltypes": [ - GenericDataType.NUMERIC, - GenericDataType.NUMERIC, - GenericDataType.NUMERIC, - GenericDataType.NUMERIC, - GenericDataType.NUMERIC, - GenericDataType.NUMERIC, - GenericDataType.NUMERIC, - GenericDataType.NUMERIC, - GenericDataType.NUMERIC, - GenericDataType.NUMERIC, - GenericDataType.NUMERIC, - GenericDataType.NUMERIC, - GenericDataType.NUMERIC, - ], - "data": """,Births CA,Births FL,Births IL,Births MA,Births MI,Births NJ,Births NY,Births OH,Births PA,Births TX,Births other,Births Subtotal,Total (Sum) -boy,5430796,1968060,2357411,1285126,1938321,1486126,3543961,2376385,2390275,3311985,22044909,48133355,48133355 -girl,3567754,1312593,1614427,842146,1326229,992702,2280733,1622814,1615383,2313186,15058341,32546308,32546308 -Total (Sum),8998550,3280653,3971838,2127272,3264550,2478828,5824694,3999199,4005658,5625171,37103250,80679663,80679663 -""", - "applied_filters": [], - "rejected_filters": [], - } - ], - } - - -def test_pivot_table_v2(): - form_data = { - "adhoc_filters": [], - "aggregateFunction": "Sum as Fraction of Rows", - "colOrder": "key_a_to_z", - "colTotals": True, - "combineMetric": True, - "datasource": "3__table", - "date_format": "smart_date", - "extra_form_data": {}, - "granularity_sqla": "ds", - "groupbyColumns": ["state"], - "groupbyRows": ["gender"], - "metrics": [ - { - "aggregate": "SUM", - "column": {"column_name": "num", "type": "BIGINT"}, - "expressionType": "SIMPLE", - "label": "Births", - "optionName": "metric_11", - } - ], - "metricsLayout": "COLUMNS", - "rowOrder": "key_a_to_z", - "rowTotals": True, - "row_limit": 50000, - "slice_id": 72, - "time_grain_sqla": None, - "time_range": "100 years ago : now", - "time_range_endpoints": ["inclusive", "exclusive"], - "transposePivot": True, - "url_params": {}, - "valueFormat": "SMART_NUMBER", - "viz_type": "pivot_table_v2", - } - result = copy.deepcopy(RESULT) - assert apply_post_process(result, form_data) == { - "query_context": None, - "queries": [ - { - "cache_key": "1bd3ab8c01e98a0e349fb61bc76d9b90", - "cached_dttm": None, - "cache_timeout": 86400, - "annotation_data": {}, - "error": None, - "is_cached": None, - "query": """SELECT state AS state, - gender AS gender, - sum(num) AS \"Births\" -FROM birth_names -WHERE ds >= TO_TIMESTAMP('1921-07-28 00:00:00.000000', 'YYYY-MM-DD HH24:MI:SS.US') - AND ds < TO_TIMESTAMP('2021-07-28 10:39:44.000000', 'YYYY-MM-DD HH24:MI:SS.US') -GROUP BY state, - gender -LIMIT 50000; - -""", - "status": QueryStatus.SUCCESS, - "stacktrace": None, - "rowcount": 12, - "colnames": [ - "boy Births", - "boy Subtotal", - "girl Births", - "girl Subtotal", - "Total (Sum as Fraction of Rows)", - ], - "coltypes": [ - GenericDataType.NUMERIC, - GenericDataType.NUMERIC, - GenericDataType.NUMERIC, - GenericDataType.NUMERIC, - GenericDataType.NUMERIC, - ], - "data": """,boy Births,boy Subtotal,girl Births,girl Subtotal,Total (Sum as Fraction of Rows) -CA,0.6035190113962805,0.6035190113962805,0.3964809886037195,0.3964809886037195,1.0 -FL,0.5998988615985903,0.5998988615985903,0.4001011384014097,0.4001011384014097,1.0 -IL,0.5935315085862012,0.5935315085862012,0.40646849141379887,0.40646849141379887,1.0 -MA,0.6041192663655611,0.6041192663655611,0.3958807336344389,0.3958807336344389,1.0 -MI,0.5937482960898133,0.5937482960898133,0.4062517039101867,0.4062517039101867,1.0 -NJ,0.5995276800165239,0.5995276800165239,0.40047231998347604,0.40047231998347604,1.0 -NY,0.6084372844307357,0.6084372844307357,0.39156271556926425,0.39156271556926425,1.0 -OH,0.5942152416021308,0.5942152416021308,0.40578475839786915,0.40578475839786915,1.0 -PA,0.596724682935987,0.596724682935987,0.40327531706401293,0.40327531706401293,1.0 -TX,0.5887794344385264,0.5887794344385264,0.41122056556147357,0.41122056556147357,1.0 -other,0.5941503507105172,0.5941503507105172,0.40584964928948275,0.40584964928948275,1.0 -Total (Sum as Fraction of Rows),6.576651618170867,6.576651618170867,4.423348381829133,4.423348381829133,11.0 -""", - "applied_filters": [], - "rejected_filters": [], - } - ], - } +from superset.charts.post_processing import pivot_df def test_pivot_df_no_cols_no_rows_single_metric(): @@ -307,9 +51,9 @@ def test_pivot_df_no_cols_no_rows_single_metric(): assert ( pivoted.to_markdown() == """ -| metric | SUM(num) | -|:------------|------------:| -| Total (Sum) | 8.06797e+07 | +| | ('SUM(num)',) | +|:-----------------|----------------:| +| ('Total (Sum)',) | 8.06797e+07 | """.strip() ) @@ -329,9 +73,9 @@ def test_pivot_df_no_cols_no_rows_single_metric(): assert ( pivoted.to_markdown() == """ -| metric | SUM(num) | -|:------------|------------:| -| Total (Sum) | 8.06797e+07 | +| | ('SUM(num)',) | +|:-----------------|----------------:| +| ('Total (Sum)',) | 8.06797e+07 | """.strip() ) @@ -352,9 +96,9 @@ def test_pivot_df_no_cols_no_rows_single_metric(): assert ( pivoted.to_markdown() == """ -| | Total (Sum) | -|:---------|--------------:| -| SUM(num) | 8.06797e+07 | +| | ('Total (Sum)',) | +|:--------------|-------------------:| +| ('SUM(num)',) | 8.06797e+07 | """.strip() ) @@ -374,9 +118,9 @@ def test_pivot_df_no_cols_no_rows_single_metric(): assert ( pivoted.to_markdown() == """ -| metric | ('SUM(num)',) | ('Total (Sum)',) | -|:------------|----------------:|-------------------:| -| Total (Sum) | 8.06797e+07 | 8.06797e+07 | +| | ('SUM(num)',) | ('Total (Sum)',) | +|:-----------------|----------------:|-------------------:| +| ('Total (Sum)',) | 8.06797e+07 | 8.06797e+07 | """.strip() ) @@ -412,9 +156,9 @@ def test_pivot_df_no_cols_no_rows_two_metrics(): assert ( pivoted.to_markdown() == """ -| metric | SUM(num) | MAX(num) | -|:------------|------------:|-----------:| -| Total (Sum) | 8.06797e+07 | 37296 | +| | ('SUM(num)',) | ('MAX(num)',) | +|:-----------------|----------------:|----------------:| +| ('Total (Sum)',) | 8.06797e+07 | 37296 | """.strip() ) @@ -434,9 +178,9 @@ def test_pivot_df_no_cols_no_rows_two_metrics(): assert ( pivoted.to_markdown() == """ -| metric | SUM(num) | MAX(num) | -|:------------|------------:|-----------:| -| Total (Sum) | 8.06797e+07 | 37296 | +| | ('SUM(num)',) | ('MAX(num)',) | +|:-----------------|----------------:|----------------:| +| ('Total (Sum)',) | 8.06797e+07 | 37296 | """.strip() ) @@ -457,10 +201,10 @@ def test_pivot_df_no_cols_no_rows_two_metrics(): assert ( pivoted.to_markdown() == """ -| | Total (Sum) | -|:---------|----------------:| -| SUM(num) | 8.06797e+07 | -| MAX(num) | 37296 | +| | ('Total (Sum)',) | +|:--------------|-------------------:| +| ('SUM(num)',) | 8.06797e+07 | +| ('MAX(num)',) | 37296 | """.strip() ) @@ -481,9 +225,9 @@ def test_pivot_df_no_cols_no_rows_two_metrics(): assert ( pivoted.to_markdown() == """ -| metric | ('SUM(num)',) | ('MAX(num)',) | ('Total (Sum)',) | -|:------------|----------------:|----------------:|-------------------:| -| Total (Sum) | 8.06797e+07 | 37296 | 8.0717e+07 | +| | ('SUM(num)',) | ('MAX(num)',) | ('Total (Sum)',) | +|:-----------------|----------------:|----------------:|-------------------:| +| ('Total (Sum)',) | 8.06797e+07 | 37296 | 8.0717e+07 | """.strip() ) @@ -524,10 +268,10 @@ def test_pivot_df_single_row_two_metrics(): assert ( pivoted.to_markdown() == """ -| gender | SUM(num) | MAX(num) | -|:---------|-----------:|-----------:| -| boy | 47123 | 1280 | -| girl | 118065 | 2588 | +| | ('SUM(num)',) | ('MAX(num)',) | +|:----------|----------------:|----------------:| +| ('boy',) | 47123 | 1280 | +| ('girl',) | 118065 | 2588 | """.strip() ) @@ -547,9 +291,9 @@ def test_pivot_df_single_row_two_metrics(): assert ( pivoted.to_markdown() == """ -| metric | ('SUM(num)', 'boy') | ('SUM(num)', 'girl') | ('MAX(num)', 'boy') | ('MAX(num)', 'girl') | -|:------------|----------------------:|-----------------------:|----------------------:|-----------------------:| -| Total (Sum) | 47123 | 118065 | 1280 | 2588 | +| | ('SUM(num)', 'boy') | ('SUM(num)', 'girl') | ('MAX(num)', 'boy') | ('MAX(num)', 'girl') | +|:-----------------|----------------------:|-----------------------:|----------------------:|-----------------------:| +| ('Total (Sum)',) | 47123 | 118065 | 1280 | 2588 | """.strip() ) @@ -569,10 +313,10 @@ def test_pivot_df_single_row_two_metrics(): assert ( pivoted.to_markdown() == """ -| gender | SUM(num) | MAX(num) | -|:---------|-----------:|-----------:| -| boy | 47123 | 1280 | -| girl | 118065 | 2588 | +| | ('SUM(num)',) | ('MAX(num)',) | +|:----------|----------------:|----------------:| +| ('boy',) | 47123 | 1280 | +| ('girl',) | 118065 | 2588 | """.strip() ) @@ -616,15 +360,15 @@ def test_pivot_df_single_row_two_metrics(): assert ( pivoted.to_markdown() == """ -| | Total (Sum) | -|:-------------------------|--------------:| -| ('SUM(num)', 'boy') | 47123 | -| ('SUM(num)', 'girl') | 118065 | -| ('SUM(num)', 'Subtotal') | 165188 | -| ('MAX(num)', 'boy') | 1280 | -| ('MAX(num)', 'girl') | 2588 | -| ('MAX(num)', 'Subtotal') | 3868 | -| ('Total (Sum)', '') | 169056 | +| | ('Total (Sum)',) | +|:-------------------------|-------------------:| +| ('SUM(num)', 'boy') | 47123 | +| ('SUM(num)', 'girl') | 118065 | +| ('SUM(num)', 'Subtotal') | 165188 | +| ('MAX(num)', 'boy') | 1280 | +| ('MAX(num)', 'girl') | 2588 | +| ('MAX(num)', 'Subtotal') | 3868 | +| ('Total (Sum)', '') | 169056 | """.strip() ) @@ -644,15 +388,15 @@ def test_pivot_df_single_row_two_metrics(): assert ( pivoted.to_markdown() == """ -| | Total (Sum) | -|:---------------------|--------------:| -| ('boy', 'SUM(num)') | 47123 | -| ('boy', 'MAX(num)') | 1280 | -| ('boy', 'Subtotal') | 48403 | -| ('girl', 'SUM(num)') | 118065 | -| ('girl', 'MAX(num)') | 2588 | -| ('girl', 'Subtotal') | 120653 | -| ('Total (Sum)', '') | 169056 | +| | ('Total (Sum)',) | +|:---------------------|-------------------:| +| ('boy', 'SUM(num)') | 47123 | +| ('boy', 'MAX(num)') | 1280 | +| ('boy', 'Subtotal') | 48403 | +| ('girl', 'SUM(num)') | 118065 | +| ('girl', 'MAX(num)') | 2588 | +| ('girl', 'Subtotal') | 120653 | +| ('Total (Sum)', '') | 169056 | """.strip() ) @@ -797,10 +541,10 @@ def test_pivot_df_complex(): assert ( pivoted.to_markdown() == """ -| state | ('SUM(num)', 'boy', 'Edward') | ('SUM(num)', 'boy', 'Tony') | ('SUM(num)', 'girl', 'Amy') | ('SUM(num)', 'girl', 'Cindy') | ('SUM(num)', 'girl', 'Dawn') | ('SUM(num)', 'girl', 'Sophia') | ('MAX(num)', 'boy', 'Edward') | ('MAX(num)', 'boy', 'Tony') | ('MAX(num)', 'girl', 'Amy') | ('MAX(num)', 'girl', 'Cindy') | ('MAX(num)', 'girl', 'Dawn') | ('MAX(num)', 'girl', 'Sophia') | +| | ('SUM(num)', 'boy', 'Edward') | ('SUM(num)', 'boy', 'Tony') | ('SUM(num)', 'girl', 'Amy') | ('SUM(num)', 'girl', 'Cindy') | ('SUM(num)', 'girl', 'Dawn') | ('SUM(num)', 'girl', 'Sophia') | ('MAX(num)', 'boy', 'Edward') | ('MAX(num)', 'boy', 'Tony') | ('MAX(num)', 'girl', 'Amy') | ('MAX(num)', 'girl', 'Cindy') | ('MAX(num)', 'girl', 'Dawn') | ('MAX(num)', 'girl', 'Sophia') | |:--------|--------------------------------:|------------------------------:|------------------------------:|--------------------------------:|-------------------------------:|---------------------------------:|--------------------------------:|------------------------------:|------------------------------:|--------------------------------:|-------------------------------:|---------------------------------:| -| CA | 31290 | 3765 | 45426 | 14149 | 11403 | 18859 | 1280 | 598 | 2227 | 842 | 1157 | 2588 | -| FL | 9395 | 2673 | 14740 | 1218 | 5089 | 7181 | 389 | 247 | 854 | 217 | 461 | 1187 | +| ('CA',) | 31290 | 3765 | 45426 | 14149 | 11403 | 18859 | 1280 | 598 | 2227 | 842 | 1157 | 2588 | +| ('FL',) | 9395 | 2673 | 14740 | 1218 | 5089 | 7181 | 389 | 247 | 854 | 217 | 461 | 1187 | """.strip() ) @@ -877,20 +621,20 @@ def test_pivot_df_complex(): assert ( pivoted.to_markdown() == """ -| | CA | FL | -|:-------------------------------|------:|------:| -| ('SUM(num)', 'boy', 'Edward') | 31290 | 9395 | -| ('SUM(num)', 'boy', 'Tony') | 3765 | 2673 | -| ('SUM(num)', 'girl', 'Amy') | 45426 | 14740 | -| ('SUM(num)', 'girl', 'Cindy') | 14149 | 1218 | -| ('SUM(num)', 'girl', 'Dawn') | 11403 | 5089 | -| ('SUM(num)', 'girl', 'Sophia') | 18859 | 7181 | -| ('MAX(num)', 'boy', 'Edward') | 1280 | 389 | -| ('MAX(num)', 'boy', 'Tony') | 598 | 247 | -| ('MAX(num)', 'girl', 'Amy') | 2227 | 854 | -| ('MAX(num)', 'girl', 'Cindy') | 842 | 217 | -| ('MAX(num)', 'girl', 'Dawn') | 1157 | 461 | -| ('MAX(num)', 'girl', 'Sophia') | 2588 | 1187 | +| | ('CA',) | ('FL',) | +|:-------------------------------|----------:|----------:| +| ('SUM(num)', 'boy', 'Edward') | 31290 | 9395 | +| ('SUM(num)', 'boy', 'Tony') | 3765 | 2673 | +| ('SUM(num)', 'girl', 'Amy') | 45426 | 14740 | +| ('SUM(num)', 'girl', 'Cindy') | 14149 | 1218 | +| ('SUM(num)', 'girl', 'Dawn') | 11403 | 5089 | +| ('SUM(num)', 'girl', 'Sophia') | 18859 | 7181 | +| ('MAX(num)', 'boy', 'Edward') | 1280 | 389 | +| ('MAX(num)', 'boy', 'Tony') | 598 | 247 | +| ('MAX(num)', 'girl', 'Amy') | 2227 | 854 | +| ('MAX(num)', 'girl', 'Cindy') | 842 | 217 | +| ('MAX(num)', 'girl', 'Dawn') | 1157 | 461 | +| ('MAX(num)', 'girl', 'Sophia') | 2588 | 1187 | """.strip() ) @@ -910,20 +654,20 @@ def test_pivot_df_complex(): assert ( pivoted.to_markdown() == """ -| | CA | FL | -|:-------------------------------|------:|------:| -| ('boy', 'Edward', 'SUM(num)') | 31290 | 9395 | -| ('boy', 'Edward', 'MAX(num)') | 1280 | 389 | -| ('boy', 'Tony', 'SUM(num)') | 3765 | 2673 | -| ('boy', 'Tony', 'MAX(num)') | 598 | 247 | -| ('girl', 'Amy', 'SUM(num)') | 45426 | 14740 | -| ('girl', 'Amy', 'MAX(num)') | 2227 | 854 | -| ('girl', 'Cindy', 'SUM(num)') | 14149 | 1218 | -| ('girl', 'Cindy', 'MAX(num)') | 842 | 217 | -| ('girl', 'Dawn', 'SUM(num)') | 11403 | 5089 | -| ('girl', 'Dawn', 'MAX(num)') | 1157 | 461 | -| ('girl', 'Sophia', 'SUM(num)') | 18859 | 7181 | -| ('girl', 'Sophia', 'MAX(num)') | 2588 | 1187 | +| | ('CA',) | ('FL',) | +|:-------------------------------|----------:|----------:| +| ('boy', 'Edward', 'SUM(num)') | 31290 | 9395 | +| ('boy', 'Edward', 'MAX(num)') | 1280 | 389 | +| ('boy', 'Tony', 'SUM(num)') | 3765 | 2673 | +| ('boy', 'Tony', 'MAX(num)') | 598 | 247 | +| ('girl', 'Amy', 'SUM(num)') | 45426 | 14740 | +| ('girl', 'Amy', 'MAX(num)') | 2227 | 854 | +| ('girl', 'Cindy', 'SUM(num)') | 14149 | 1218 | +| ('girl', 'Cindy', 'MAX(num)') | 842 | 217 | +| ('girl', 'Dawn', 'SUM(num)') | 11403 | 5089 | +| ('girl', 'Dawn', 'MAX(num)') | 1157 | 461 | +| ('girl', 'Sophia', 'SUM(num)') | 18859 | 7181 | +| ('girl', 'Sophia', 'MAX(num)') | 2588 | 1187 | """.strip() ) @@ -940,6 +684,7 @@ def test_pivot_df_complex(): show_columns_total=True, apply_metrics_on_rows=True, ) + print(pivoted.to_markdown()) assert ( pivoted.to_markdown() == """ From ec8d3b03e4e14451ee5047a0a46a2a941ee13b4b Mon Sep 17 00:00:00 2001 From: Beto Dealmeida Date: Wed, 18 Aug 2021 19:36:48 -0700 Subject: [PATCH 02/22] fix: send CSV pivoted in reports (#16347) --- superset/charts/api.py | 12 ++++++------ superset/charts/post_processing.py | 24 +++++++++++++++++++++--- superset/common/query_actions.py | 1 + 3 files changed, 28 insertions(+), 9 deletions(-) diff --git a/superset/charts/api.py b/superset/charts/api.py index 3fa48c076b177..7a21fe2676d05 100644 --- a/superset/charts/api.py +++ b/superset/charts/api.py @@ -499,6 +499,12 @@ def send_chart_response( result_type = result["query_context"].result_type result_format = result["query_context"].result_format + # Post-process the data so it matches the data presented in the chart. + # This is needed for sending reports based on text charts that do the + # post-processing of data, eg, the pivot table. + if result_type == ChartDataResultType.POST_PROCESSED: + result = apply_post_process(result, form_data) + if result_format == ChartDataResultFormat.CSV: # Verify user has permission to export CSV file if not security_manager.can_access("can_csv", "Superset"): @@ -509,12 +515,6 @@ def send_chart_response( return CsvResponse(data, headers=generate_download_headers("csv")) if result_format == ChartDataResultFormat.JSON: - # Post-process the data so it matches the data presented in the chart. - # This is needed for sending reports based on text charts that do the - # post-processing of data, eg, the pivot table. - if result_type == ChartDataResultType.POST_PROCESSED: - result = apply_post_process(result, form_data) - response_data = simplejson.dumps( {"result": result["queries"]}, default=json_int_dttm_ser, diff --git a/superset/charts/post_processing.py b/superset/charts/post_processing.py index 96d1e7ca0ed44..9ef976bdec62b 100644 --- a/superset/charts/post_processing.py +++ b/superset/charts/post_processing.py @@ -26,11 +26,17 @@ for these chart types. """ +from io import StringIO from typing import Any, Dict, List, Optional, Tuple import pandas as pd -from superset.utils.core import DTTM_ALIAS, extract_dataframe_dtypes, get_metric_name +from superset.utils.core import ( + ChartDataResultFormat, + DTTM_ALIAS, + extract_dataframe_dtypes, + get_metric_name, +) def get_column_key(label: Tuple[str, ...], metrics: List[str]) -> Tuple[Any, ...]: @@ -276,7 +282,13 @@ def apply_post_process( post_processor = post_processors[viz_type] for query in result["queries"]: - df = pd.DataFrame.from_dict(query["data"]) + if query["result_format"] == ChartDataResultFormat.JSON: + df = pd.DataFrame.from_dict(query["data"]) + elif query["result_format"] == ChartDataResultFormat.CSV: + df = pd.read_csv(StringIO(query["data"])) + else: + raise Exception(f"Result format {query['result_format']} not supported") + processed_df = post_processor(df, form_data) query["colnames"] = list(processed_df.columns) @@ -298,6 +310,12 @@ def apply_post_process( for index in processed_df.index ] - query["data"] = processed_df.to_dict() + if query["result_format"] == ChartDataResultFormat.JSON: + query["data"] = processed_df.to_dict() + elif query["result_format"] == ChartDataResultFormat.CSV: + buf = StringIO() + processed_df.to_csv(buf) + buf.seek(0) + query["data"] = buf.getvalue() return result diff --git a/superset/common/query_actions.py b/superset/common/query_actions.py index ea8610433993d..349d62d38227e 100644 --- a/superset/common/query_actions.py +++ b/superset/common/query_actions.py @@ -104,6 +104,7 @@ def _get_full( payload["indexnames"] = list(df.index) payload["coltypes"] = extract_dataframe_dtypes(df) payload["data"] = query_context.get_data(df) + payload["result_format"] = query_context.result_format del payload["df"] filters = query_obj.filter From 42cd21e3833846bf737ee20c3f6931a51c708f6d Mon Sep 17 00:00:00 2001 From: Evan Rusackas Date: Thu, 19 Aug 2021 00:32:24 -0600 Subject: [PATCH 03/22] chore: bump superset-ui to v0.17.85 (#16350) --- superset-frontend/package-lock.json | 483 ++++++++++++++-------------- superset-frontend/package.json | 54 ++-- 2 files changed, 268 insertions(+), 269 deletions(-) diff --git a/superset-frontend/package-lock.json b/superset-frontend/package-lock.json index b184d0cf8df63..4930c8b04715d 100644 --- a/superset-frontend/package-lock.json +++ b/superset-frontend/package-lock.json @@ -15,35 +15,35 @@ "@emotion/babel-preset-css-prop": "^11.2.0", "@emotion/cache": "^11.1.3", "@emotion/react": "^11.1.5", - "@superset-ui/chart-controls": "^0.17.84", + "@superset-ui/chart-controls": "^0.17.85", "@superset-ui/core": "^0.17.81", - "@superset-ui/legacy-plugin-chart-calendar": "^0.17.84", - "@superset-ui/legacy-plugin-chart-chord": "^0.17.84", - "@superset-ui/legacy-plugin-chart-country-map": "^0.17.84", - "@superset-ui/legacy-plugin-chart-event-flow": "^0.17.84", - "@superset-ui/legacy-plugin-chart-force-directed": "^0.17.84", - "@superset-ui/legacy-plugin-chart-heatmap": "^0.17.84", - "@superset-ui/legacy-plugin-chart-histogram": "^0.17.84", - "@superset-ui/legacy-plugin-chart-horizon": "^0.17.84", - "@superset-ui/legacy-plugin-chart-map-box": "^0.17.84", - "@superset-ui/legacy-plugin-chart-paired-t-test": "^0.17.84", - "@superset-ui/legacy-plugin-chart-parallel-coordinates": "^0.17.84", - "@superset-ui/legacy-plugin-chart-partition": "^0.17.84", - "@superset-ui/legacy-plugin-chart-pivot-table": "^0.17.84", - "@superset-ui/legacy-plugin-chart-rose": "^0.17.84", - "@superset-ui/legacy-plugin-chart-sankey": "^0.17.84", - "@superset-ui/legacy-plugin-chart-sankey-loop": "^0.17.84", - "@superset-ui/legacy-plugin-chart-sunburst": "^0.17.84", - "@superset-ui/legacy-plugin-chart-treemap": "^0.17.84", - "@superset-ui/legacy-plugin-chart-world-map": "^0.17.84", - "@superset-ui/legacy-preset-chart-big-number": "^0.17.84", + "@superset-ui/legacy-plugin-chart-calendar": "^0.17.85", + "@superset-ui/legacy-plugin-chart-chord": "^0.17.85", + "@superset-ui/legacy-plugin-chart-country-map": "^0.17.85", + "@superset-ui/legacy-plugin-chart-event-flow": "^0.17.85", + "@superset-ui/legacy-plugin-chart-force-directed": "^0.17.85", + "@superset-ui/legacy-plugin-chart-heatmap": "^0.17.85", + "@superset-ui/legacy-plugin-chart-histogram": "^0.17.85", + "@superset-ui/legacy-plugin-chart-horizon": "^0.17.85", + "@superset-ui/legacy-plugin-chart-map-box": "^0.17.85", + "@superset-ui/legacy-plugin-chart-paired-t-test": "^0.17.85", + "@superset-ui/legacy-plugin-chart-parallel-coordinates": "^0.17.85", + "@superset-ui/legacy-plugin-chart-partition": "^0.17.85", + "@superset-ui/legacy-plugin-chart-pivot-table": "^0.17.85", + "@superset-ui/legacy-plugin-chart-rose": "^0.17.85", + "@superset-ui/legacy-plugin-chart-sankey": "^0.17.85", + "@superset-ui/legacy-plugin-chart-sankey-loop": "^0.17.85", + "@superset-ui/legacy-plugin-chart-sunburst": "^0.17.85", + "@superset-ui/legacy-plugin-chart-treemap": "^0.17.85", + "@superset-ui/legacy-plugin-chart-world-map": "^0.17.85", + "@superset-ui/legacy-preset-chart-big-number": "^0.17.85", "@superset-ui/legacy-preset-chart-deckgl": "^0.4.10", - "@superset-ui/legacy-preset-chart-nvd3": "^0.17.84", - "@superset-ui/plugin-chart-echarts": "^0.17.84", - "@superset-ui/plugin-chart-pivot-table": "^0.17.84", - "@superset-ui/plugin-chart-table": "^0.17.84", - "@superset-ui/plugin-chart-word-cloud": "^0.17.84", - "@superset-ui/preset-chart-xy": "^0.17.84", + "@superset-ui/legacy-preset-chart-nvd3": "^0.17.85", + "@superset-ui/plugin-chart-echarts": "^0.17.85", + "@superset-ui/plugin-chart-pivot-table": "^0.17.85", + "@superset-ui/plugin-chart-table": "^0.17.85", + "@superset-ui/plugin-chart-word-cloud": "^0.17.85", + "@superset-ui/preset-chart-xy": "^0.17.85", "@vx/responsive": "^0.0.195", "abortcontroller-polyfill": "^1.1.9", "antd": "^4.9.4", @@ -11959,9 +11959,9 @@ } }, "node_modules/@superset-ui/chart-controls": { - "version": "0.17.84", - "resolved": "https://registry.npmjs.org/@superset-ui/chart-controls/-/chart-controls-0.17.84.tgz", - "integrity": "sha512-tLbTdYegaez2D1N3eIHIkfpWyI3i/SoLQfY7asV1YadKcSZfff1BpSBDV2vbGom1hehsftD09sIV3uHm4wjkAg==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/chart-controls/-/chart-controls-0.17.85.tgz", + "integrity": "sha512-RNk6za6IfcnAKoDFA8XpWgK5AdwPCEprKauv29Z+8VkHrMT8DkoEwDtgl0vutEmLRPECAL+lR76g5B9Z33iCJQ==", "dependencies": { "@react-icons/all-files": "^4.1.0", "@superset-ui/core": "0.17.81", @@ -12100,11 +12100,11 @@ } }, "node_modules/@superset-ui/legacy-plugin-chart-calendar": { - "version": "0.17.84", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-calendar/-/legacy-plugin-chart-calendar-0.17.84.tgz", - "integrity": "sha512-xalLL56JnXPxlDMjLxJqwQMy4Myaq1uOww6dyOBfgxEH1eat+V0tv4Aoh9i5RCYnafXymKTX+oqS0BkUYebwPQ==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-calendar/-/legacy-plugin-chart-calendar-0.17.85.tgz", + "integrity": "sha512-bmPvflt73ncje+22YTDTkPGIbfylPcqksiCB74/dh/cjUy39lCEpBdL1iYYee6z2vmFdn6uNWuHtpENDKTG83g==", "dependencies": { - "@superset-ui/chart-controls": "0.17.84", + "@superset-ui/chart-controls": "0.17.85", "@superset-ui/core": "0.17.81", "d3-array": "^2.0.3", "d3-selection": "^1.4.0", @@ -12124,11 +12124,11 @@ } }, "node_modules/@superset-ui/legacy-plugin-chart-chord": { - "version": "0.17.84", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-chord/-/legacy-plugin-chart-chord-0.17.84.tgz", - "integrity": "sha512-7jhc+/iWrl6brVqQDKxEtNq3btPgGVtidzuz1LBLNz6Nc0EwMvBVAppnZvwvVgBqdZQ2T0C56wZRqwDgmj/Lxw==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-chord/-/legacy-plugin-chart-chord-0.17.85.tgz", + "integrity": "sha512-raQUDM3Dgcld7UIvXe9XGr4NEJV6x8J+gjVUQLlkvsttA32VN+J1tUhevxmT2eMMxifaaLnGWiBA678Q2ENVfg==", "dependencies": { - "@superset-ui/chart-controls": "0.17.84", + "@superset-ui/chart-controls": "0.17.85", "@superset-ui/core": "0.17.81", "d3": "^3.5.17", "prop-types": "^15.6.2", @@ -12136,11 +12136,11 @@ } }, "node_modules/@superset-ui/legacy-plugin-chart-country-map": { - "version": "0.17.84", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-country-map/-/legacy-plugin-chart-country-map-0.17.84.tgz", - "integrity": "sha512-POFH/ZfPKOiNKA1BbeU/5vlwfU6BALph/XsQacP4YfBD7xVFGEZWDYKWH/aZky+GLCZWIp6v7h7EVzyNmC8GJQ==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-country-map/-/legacy-plugin-chart-country-map-0.17.85.tgz", + "integrity": "sha512-g8OAwInPI9r/k/0ip9pXvQR7L6PInQhN8KfMb4j5VgC4+eUl04BQDpsclVoYOz5KqthkxPvGhdlOH0zHybv3Vg==", "dependencies": { - "@superset-ui/chart-controls": "0.17.84", + "@superset-ui/chart-controls": "0.17.85", "@superset-ui/core": "0.17.81", "d3": "^3.5.17", "d3-array": "^2.0.3", @@ -12156,12 +12156,12 @@ } }, "node_modules/@superset-ui/legacy-plugin-chart-event-flow": { - "version": "0.17.84", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-event-flow/-/legacy-plugin-chart-event-flow-0.17.84.tgz", - "integrity": "sha512-7zew1e5FRPUt06umJez3+zau/0LXtWWWDyIvy/0DiSu5i/Ds4zhru5ZmLTXhOZ6heU5BEGf/cfIV1G4bF6S00A==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-event-flow/-/legacy-plugin-chart-event-flow-0.17.85.tgz", + "integrity": "sha512-O2DiLUO0lkkpemtjwJ845veW4vFwZUkb13TkfV1ef37HBDDdEJkUa6Ls8zh7C4aybqn4HG4mr32IHg3DV2WgYg==", "dependencies": { "@data-ui/event-flow": "^0.0.84", - "@superset-ui/chart-controls": "0.17.84", + "@superset-ui/chart-controls": "0.17.85", "@superset-ui/core": "0.17.81", "prop-types": "^15.6.2" }, @@ -12170,11 +12170,11 @@ } }, "node_modules/@superset-ui/legacy-plugin-chart-force-directed": { - "version": "0.17.84", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-force-directed/-/legacy-plugin-chart-force-directed-0.17.84.tgz", - "integrity": "sha512-GBfTI+2vfr9Y4vbT0azKqmdUJ32UGTRpw1PmuHTjw/ZQjoRts8Hyyw12o6zs0Ifj+0tIt5bqxbuaZgxuzEd/Pw==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-force-directed/-/legacy-plugin-chart-force-directed-0.17.85.tgz", + "integrity": "sha512-8QB2IPNadJ5pha2PPs0AQe5zHEnpaIcTY+JGH8LFxUFtuDLjGSuGJJNbIyM/QT63zMp0WCn5VCwgeMuKnuU9Xw==", "dependencies": { - "@superset-ui/chart-controls": "0.17.84", + "@superset-ui/chart-controls": "0.17.85", "@superset-ui/core": "0.17.81", "d3": "^3.5.17", "prop-types": "^15.7.2" @@ -12184,11 +12184,11 @@ } }, "node_modules/@superset-ui/legacy-plugin-chart-heatmap": { - "version": "0.17.84", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-heatmap/-/legacy-plugin-chart-heatmap-0.17.84.tgz", - "integrity": "sha512-ZJ3BBGRL9jz8SM9UZdKKhHseZ9IZqMVfH4J/IcjeOxvNq2a0Y544WILrzmUR0DgvO++Cy7cV0VSrcSkhy/mrQw==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-heatmap/-/legacy-plugin-chart-heatmap-0.17.85.tgz", + "integrity": "sha512-zuxkDDTxxVtDkg9iu7GtcNuG9URh9GmNRM5lH2wsOdKD/UTlZrkt0Lf4tp5kKycknyHWdWFixe1CSLaLzuwwOA==", "dependencies": { - "@superset-ui/chart-controls": "0.17.84", + "@superset-ui/chart-controls": "0.17.85", "@superset-ui/core": "0.17.81", "d3": "^3.5.17", "d3-svg-legend": "^1.x", @@ -12197,13 +12197,13 @@ } }, "node_modules/@superset-ui/legacy-plugin-chart-histogram": { - "version": "0.17.84", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-histogram/-/legacy-plugin-chart-histogram-0.17.84.tgz", - "integrity": "sha512-GaELpLkP9SFbPQnkzUte+fcKl2F++KEgEBINfS9Ek9pUMD1S/WlbevIEUUUSeAtI5rI89GN+0Qzi8MM0+D2WIw==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-histogram/-/legacy-plugin-chart-histogram-0.17.85.tgz", + "integrity": "sha512-dqrTTDeMvdm4Lgb7Cav5lm86GgiQxYda1yXWoXFlqXvwZt40EJqCxYUAAHYKD5JmlqjzIn1wuvCHzJQsDxANDQ==", "dependencies": { "@data-ui/histogram": "^0.0.84", "@data-ui/theme": "^0.0.84", - "@superset-ui/chart-controls": "0.17.84", + "@superset-ui/chart-controls": "0.17.85", "@superset-ui/core": "0.17.81", "@vx/legend": "^0.0.198", "@vx/responsive": "^0.0.199", @@ -12273,11 +12273,11 @@ } }, "node_modules/@superset-ui/legacy-plugin-chart-horizon": { - "version": "0.17.84", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-horizon/-/legacy-plugin-chart-horizon-0.17.84.tgz", - "integrity": "sha512-9xm91qfW1OXOjTSvNoDXpkUqfsCU9wMttUihs2pdg0hbzpCI65+L5eysV11JzGS0uGOGKLDEOWoSpzmX/xVZvQ==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-horizon/-/legacy-plugin-chart-horizon-0.17.85.tgz", + "integrity": "sha512-lVzXqqLFgZ80I/fDbf2TT3cXCEW+xYKvna4Ldod/dUDFWjVIy3rzP7HicHsS7XCLPpeUHgZUFZhx8NEvTWhDqg==", "dependencies": { - "@superset-ui/chart-controls": "0.17.84", + "@superset-ui/chart-controls": "0.17.85", "@superset-ui/core": "0.17.81", "d3-array": "^2.0.3", "d3-scale": "^3.0.1", @@ -12308,11 +12308,11 @@ } }, "node_modules/@superset-ui/legacy-plugin-chart-map-box": { - "version": "0.17.84", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-map-box/-/legacy-plugin-chart-map-box-0.17.84.tgz", - "integrity": "sha512-vllEQe4HFTc5Rduac+klL97dcmPca/VDecos3v2nnkahNl447iuq+M08evfm2VnjXgD0QL4nLcliXwQjaZ2zsA==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-map-box/-/legacy-plugin-chart-map-box-0.17.85.tgz", + "integrity": "sha512-YJIZjcfpMDaJtve6EfEr1lUgCGnsutTxyKEOj3b6wjYMfoLhSUproDoW9TUQtBR3tFrY3PrLrZg+O41L9grDIA==", "dependencies": { - "@superset-ui/chart-controls": "0.17.84", + "@superset-ui/chart-controls": "0.17.85", "@superset-ui/core": "0.17.81", "immutable": "^3.8.2", "mapbox-gl": "^0.53.0", @@ -12334,11 +12334,11 @@ } }, "node_modules/@superset-ui/legacy-plugin-chart-paired-t-test": { - "version": "0.17.84", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-paired-t-test/-/legacy-plugin-chart-paired-t-test-0.17.84.tgz", - "integrity": "sha512-2e/UsUzRz4VUnb3q8fLNg/ZGm+6044eD3pnyCDL09Nke21BFDV6jwd3H93ryYJo89qKn3uJLPuDAvcsreN5j+w==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-paired-t-test/-/legacy-plugin-chart-paired-t-test-0.17.85.tgz", + "integrity": "sha512-JIOvvGUlVzbk2zWyQMD8WE1UN95HdOuM1I7py5LIEqJH3CwWN5SGmtDG50DAvKAhEt5InmdQkm7ihr0Uw2LoQw==", "dependencies": { - "@superset-ui/chart-controls": "0.17.84", + "@superset-ui/chart-controls": "0.17.85", "@superset-ui/core": "0.17.81", "distributions": "^1.0.0", "prop-types": "^15.6.2", @@ -12349,11 +12349,11 @@ } }, "node_modules/@superset-ui/legacy-plugin-chart-parallel-coordinates": { - "version": "0.17.84", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-parallel-coordinates/-/legacy-plugin-chart-parallel-coordinates-0.17.84.tgz", - "integrity": "sha512-p4OFm5jExsQz0uPMhUJ3K7Yp4mz4q99CMJDHNCWAsyYUmMQYSMcmUHBIBVFG0M8Y8oapnuLYvzJMl+ptbgr22A==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-parallel-coordinates/-/legacy-plugin-chart-parallel-coordinates-0.17.85.tgz", + "integrity": "sha512-ecSKIBL7806jD+sQu6ub+92tI8W1OfLZWMUW7SeupDphlDJkrQEIQ63FQOCwWEAI9xJhn+hoxIe6M2Y9Um0FwQ==", "dependencies": { - "@superset-ui/chart-controls": "0.17.84", + "@superset-ui/chart-controls": "0.17.85", "@superset-ui/core": "0.17.81", "d3": "^3.5.17", "prop-types": "^15.7.2" @@ -12363,11 +12363,11 @@ } }, "node_modules/@superset-ui/legacy-plugin-chart-partition": { - "version": "0.17.84", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-partition/-/legacy-plugin-chart-partition-0.17.84.tgz", - "integrity": "sha512-J+2cyTdIfPmjlSuNaEkdaQrmWU0LEtMoqxalDube9G+cdQrQsrWW99O141LQaaATlxBT88JRfAArN/13WfFieQ==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-partition/-/legacy-plugin-chart-partition-0.17.85.tgz", + "integrity": "sha512-seF8oZ5lRdrqGzQBDqcQIrIV9QHp6IH7e9+pyG0F4BzSY8WNKIjFVkd92HcNcm/KeqkzID1mqGv1aCzlDQNaiw==", "dependencies": { - "@superset-ui/chart-controls": "0.17.84", + "@superset-ui/chart-controls": "0.17.85", "@superset-ui/core": "0.17.81", "d3": "^3.5.17", "d3-hierarchy": "^1.1.8", @@ -12378,11 +12378,11 @@ } }, "node_modules/@superset-ui/legacy-plugin-chart-pivot-table": { - "version": "0.17.84", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-pivot-table/-/legacy-plugin-chart-pivot-table-0.17.84.tgz", - "integrity": "sha512-NXcaAzPEllzBYeQQVjexJUThgQxWRHY7mlGY2q3DDVCKq5EMakSucxv5Ma3vpBWD/nmiRTHm7F4ouVoK0b9FhA==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-pivot-table/-/legacy-plugin-chart-pivot-table-0.17.85.tgz", + "integrity": "sha512-oXBXgzi4uBKG1FsOd9ZFIhPQvaBIYanzHdqwthafsRDG9kgKL9b//+MCIZgRlqlSJK+vsf4EMxXucFKJtoLDuA==", "dependencies": { - "@superset-ui/chart-controls": "0.17.84", + "@superset-ui/chart-controls": "0.17.85", "@superset-ui/core": "0.17.81", "d3": "^3.5.17", "datatables.net-bs": "^1.10.15", @@ -12390,11 +12390,11 @@ } }, "node_modules/@superset-ui/legacy-plugin-chart-rose": { - "version": "0.17.84", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-rose/-/legacy-plugin-chart-rose-0.17.84.tgz", - "integrity": "sha512-r0Anl44JaBm4ivHErvpCcPU7RThxPuy7/tGY0jesfxghaPJ/cTMNkL6Tt4rErLQvLo7pVVO0gYpE3mLx+udNPw==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-rose/-/legacy-plugin-chart-rose-0.17.85.tgz", + "integrity": "sha512-NilJNxC49YM+j7WqGmna/+G1m0/VXb1hoj2Z2PXPy8KYGMKrLQAwNE0ce7yBnQqSljvxq5Memfjqzqjoio5aoA==", "dependencies": { - "@superset-ui/chart-controls": "0.17.84", + "@superset-ui/chart-controls": "0.17.85", "@superset-ui/core": "0.17.81", "d3": "^3.5.17", "nvd3": "1.8.6", @@ -12405,11 +12405,11 @@ } }, "node_modules/@superset-ui/legacy-plugin-chart-sankey": { - "version": "0.17.84", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-sankey/-/legacy-plugin-chart-sankey-0.17.84.tgz", - "integrity": "sha512-P9ArmxsOONfLEBQ1mlIrRHDDWaM336mZsGfsWpD7gcP7oz4hNMneyR9KwUsPanOqVaIf2rETvdXNL5d5ddFY0w==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-sankey/-/legacy-plugin-chart-sankey-0.17.85.tgz", + "integrity": "sha512-MV47pYL5QkVAT8rfYrLT9joJF8a48mLiS3suVT+u4vHv3rrsWNTa00irD1HqNoWLDCuZr+U7pCt+UbW/0w2yGw==", "dependencies": { - "@superset-ui/chart-controls": "0.17.84", + "@superset-ui/chart-controls": "0.17.85", "@superset-ui/core": "0.17.81", "d3": "^3.5.17", "d3-sankey": "^0.4.2", @@ -12420,11 +12420,11 @@ } }, "node_modules/@superset-ui/legacy-plugin-chart-sankey-loop": { - "version": "0.17.84", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-sankey-loop/-/legacy-plugin-chart-sankey-loop-0.17.84.tgz", - "integrity": "sha512-PH+FajOKURiF7h+KMCsrMATBUR2Whiw5a6eMz+SkC3LoHVcWnpiSDh6F8ELBXfc1dhK0iQdrFWdCb8+Atpjvcg==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-sankey-loop/-/legacy-plugin-chart-sankey-loop-0.17.85.tgz", + "integrity": "sha512-z3I/NCQa64LXKiel+AswpdnUuhsS/0hiziY+T+BuN2geeVrWC+LMQya1hMDg+dDR3RgpkKqgpI9lEf1KTuhHaQ==", "dependencies": { - "@superset-ui/chart-controls": "0.17.84", + "@superset-ui/chart-controls": "0.17.85", "@superset-ui/core": "0.17.81", "d3-sankey-diagram": "^0.7.3", "d3-selection": "^1.4.0", @@ -12432,22 +12432,22 @@ } }, "node_modules/@superset-ui/legacy-plugin-chart-sunburst": { - "version": "0.17.84", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-sunburst/-/legacy-plugin-chart-sunburst-0.17.84.tgz", - "integrity": "sha512-FSLKCWGzVsL+VL1DAD4Gsgt6swW9iqU2h+fW2O4UXIWlVdKCU7Xg012Lsh6QSt7lYJSQ3unnYtfMstYcpiR/HA==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-sunburst/-/legacy-plugin-chart-sunburst-0.17.85.tgz", + "integrity": "sha512-L6rRXhEasNQpFN9SQ0ZB6qfa94XIVlCxyEmy8DOR2N39nlLncCweiHtgvgS4Su9H86nidZ/6ryNZ4fKxmm2mBg==", "dependencies": { - "@superset-ui/chart-controls": "0.17.84", + "@superset-ui/chart-controls": "0.17.85", "@superset-ui/core": "0.17.81", "d3": "^3.5.17", "prop-types": "^15.6.2" } }, "node_modules/@superset-ui/legacy-plugin-chart-treemap": { - "version": "0.17.84", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-treemap/-/legacy-plugin-chart-treemap-0.17.84.tgz", - "integrity": "sha512-yxjlWn9RVBw4PI3EKisHb80sLLHAZuVqykzeFebSXW4jlFYzgfQea3k5pCymz5QPDfYjM8c4DULMiffbbrnKmA==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-treemap/-/legacy-plugin-chart-treemap-0.17.85.tgz", + "integrity": "sha512-X4HVCOuhe45qaqUzTfjrKxAr9RoSbdwmWlCWLK2djVJDAFYGqhTxcpNEXxSv+cMqpyMBLhwXqZ5SMV5u0Gu+Eg==", "dependencies": { - "@superset-ui/chart-controls": "0.17.84", + "@superset-ui/chart-controls": "0.17.85", "@superset-ui/core": "0.17.81", "d3-hierarchy": "^1.1.8", "d3-selection": "^1.4.0", @@ -12455,11 +12455,11 @@ } }, "node_modules/@superset-ui/legacy-plugin-chart-world-map": { - "version": "0.17.84", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-world-map/-/legacy-plugin-chart-world-map-0.17.84.tgz", - "integrity": "sha512-KJmt2jKXJ/1jade+bO0D36DNFl74oDJsdCr/2wuPRQgFTO8xkqoQKk/y+7on4BDSkHUfTExDuOjnnsS3ijdXjA==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-world-map/-/legacy-plugin-chart-world-map-0.17.85.tgz", + "integrity": "sha512-bxO1/tY2FI1MzfircbjwuwbLzI+jg2puIlKFek6vkbQuxA9+gCtxKD0SLOlxadNQXW4kUrU2DxhfPlr6th55bA==", "dependencies": { - "@superset-ui/chart-controls": "0.17.84", + "@superset-ui/chart-controls": "0.17.85", "@superset-ui/core": "0.17.81", "d3": "^3.5.17", "d3-array": "^2.4.0", @@ -12480,12 +12480,12 @@ } }, "node_modules/@superset-ui/legacy-preset-chart-big-number": { - "version": "0.17.84", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-preset-chart-big-number/-/legacy-preset-chart-big-number-0.17.84.tgz", - "integrity": "sha512-1SotYUwx6BXvXXTHszqpu/+GSQoS2TkQPIXVqlkd4c7bHIZCetwsmOs9F9pCyiaYo9GM3x7DdTWOezgYYORFDw==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-preset-chart-big-number/-/legacy-preset-chart-big-number-0.17.85.tgz", + "integrity": "sha512-Txuk8L/flifPESHWNpZ8vwXinHAPCXXEp2fZdB/6CRyB6TVfMs2RmKqnEyzO5xBGgqeTfdH6xeuVn+8Hfdcvrg==", "dependencies": { "@data-ui/xy-chart": "^0.0.84", - "@superset-ui/chart-controls": "0.17.84", + "@superset-ui/chart-controls": "0.17.85", "@superset-ui/core": "0.17.81", "@types/d3-color": "^1.2.2", "@types/shortid": "^0.0.29", @@ -12662,12 +12662,12 @@ } }, "node_modules/@superset-ui/legacy-preset-chart-nvd3": { - "version": "0.17.84", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-preset-chart-nvd3/-/legacy-preset-chart-nvd3-0.17.84.tgz", - "integrity": "sha512-U1LAtnwktODasC8nKxVaui3xZn71npbsfUKH/ZKFkDvf3zSuu96qcklGYJLTRiEhFMq6NDT1V4ogEmoIuRjk6w==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-preset-chart-nvd3/-/legacy-preset-chart-nvd3-0.17.85.tgz", + "integrity": "sha512-KoC8kIqP6zwtGzXBUj6yMaF3Cz2TXygAe7ZCG5XeavgrjgKsWqOXAdQzJ9oBrCPXPQBC5hF7TruBB/Xd839TDA==", "dependencies": { "@data-ui/xy-chart": "^0.0.84", - "@superset-ui/chart-controls": "0.17.84", + "@superset-ui/chart-controls": "0.17.85", "@superset-ui/core": "0.17.81", "d3": "^3.5.17", "d3-tip": "^0.9.1", @@ -12685,11 +12685,11 @@ } }, "node_modules/@superset-ui/plugin-chart-echarts": { - "version": "0.17.84", - "resolved": "https://registry.npmjs.org/@superset-ui/plugin-chart-echarts/-/plugin-chart-echarts-0.17.84.tgz", - "integrity": "sha512-KTK5dTri9mJCCWnSijPGZ4+Mf/ZdOkATMoYFQjjNrb2GL9TkI9Utc+61CBfizKk3vz3t3PKoSWWa4TliEeGCgQ==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/plugin-chart-echarts/-/plugin-chart-echarts-0.17.85.tgz", + "integrity": "sha512-dAqRzVVm7Shn2fgKoj2puX0jefe2r14dOQThJ8CG/hRlpmXsKmLnSywwLvQVxq1oUhh+uQYsemdYBCsrQAuxcg==", "dependencies": { - "@superset-ui/chart-controls": "0.17.84", + "@superset-ui/chart-controls": "0.17.85", "@superset-ui/core": "0.17.81", "@types/mathjs": "^6.0.7", "d3-array": "^1.2.0", @@ -12702,11 +12702,11 @@ } }, "node_modules/@superset-ui/plugin-chart-pivot-table": { - "version": "0.17.84", - "resolved": "https://registry.npmjs.org/@superset-ui/plugin-chart-pivot-table/-/plugin-chart-pivot-table-0.17.84.tgz", - "integrity": "sha512-JGyn9Yd5Wg5ZJSGVQ/PQwPqkwaLZeZK09u0NvCRjoqLEIz9psudLVfTVgsj6WTLJrrEdr4J8yXxlBnnMQ/N3Kw==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/plugin-chart-pivot-table/-/plugin-chart-pivot-table-0.17.85.tgz", + "integrity": "sha512-tTU79c/C7g5sdZxRNVcl6QFbwqhsv0Bi/XeYOwhTNAe7qf7LmkvwQB78fvh7WW+U2FBltkhHmOxOb/HIJscXVg==", "dependencies": { - "@superset-ui/chart-controls": "0.17.84", + "@superset-ui/chart-controls": "0.17.85", "@superset-ui/core": "0.17.81", "@superset-ui/react-pivottable": "^0.12.12" }, @@ -12716,12 +12716,12 @@ } }, "node_modules/@superset-ui/plugin-chart-table": { - "version": "0.17.84", - "resolved": "https://registry.npmjs.org/@superset-ui/plugin-chart-table/-/plugin-chart-table-0.17.84.tgz", - "integrity": "sha512-XUI3vj8m77jH5R1XTjz0UbW8SyVxFDD2xwlxkqtpstWkjmOd3VPGN2/LpQa0dKVpW8KBUpHVNRDByBYL+siVFA==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/plugin-chart-table/-/plugin-chart-table-0.17.85.tgz", + "integrity": "sha512-RGeIGAQuiiOy6JgpnQC+rtVDWR8jGZF1DOER+WkgbfTeKiRmVGyo7wOSb7dvQlsaitUg2Y30962arOGeEcgvpA==", "dependencies": { "@react-icons/all-files": "^4.1.0", - "@superset-ui/chart-controls": "0.17.84", + "@superset-ui/chart-controls": "0.17.85", "@superset-ui/core": "0.17.81", "@types/d3-array": "^2.9.0", "@types/react-table": "^7.0.29", @@ -12747,12 +12747,11 @@ } }, "node_modules/@superset-ui/plugin-chart-word-cloud": { - "version": "0.17.84", - "resolved": "https://registry.npmjs.org/@superset-ui/plugin-chart-word-cloud/-/plugin-chart-word-cloud-0.17.84.tgz", - "integrity": "sha512-ChuYtfOVlzOZpdavu65kVOoGLjwSQUU6mpJ8ziXqTr36hm8+QVzETX21+LTyJ8rWBFsehYPxOEgOlpmK2ZapUQ==", - "license": "Apache-2.0", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/plugin-chart-word-cloud/-/plugin-chart-word-cloud-0.17.85.tgz", + "integrity": "sha512-nCbmndNxUoHWkYRCARD1C4XjzxXtk/OkCUVdB3EKaPNWbi0QuQHUuAZUh3ROIewxd3KAMoSUYRIjTVYDbetnJQ==", "dependencies": { - "@superset-ui/chart-controls": "0.17.84", + "@superset-ui/chart-controls": "0.17.85", "@superset-ui/core": "0.17.81", "@types/d3-cloud": "^1.2.1", "@types/d3-scale": "^2.0.2", @@ -12786,13 +12785,13 @@ } }, "node_modules/@superset-ui/preset-chart-xy": { - "version": "0.17.84", - "resolved": "https://registry.npmjs.org/@superset-ui/preset-chart-xy/-/preset-chart-xy-0.17.84.tgz", - "integrity": "sha512-Q4+k2k9ZoQUUy1d3S3h6Taj6zMIHnU0XtVaLYIxj1oISfyaKWz1hPR8At1ReC5pOl5qoJSAQv4X61KFOwEMsUw==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/preset-chart-xy/-/preset-chart-xy-0.17.85.tgz", + "integrity": "sha512-Cd2Ji1trP6aHuOfHK0GDza8iQUVkJNbtIFSReb240ViewICH/7mtsrj8jLAvf3tyOORLD3AW2j8s68UDCrBi6g==", "dependencies": { "@data-ui/theme": "^0.0.84", "@data-ui/xy-chart": "^0.0.84", - "@superset-ui/chart-controls": "0.17.84", + "@superset-ui/chart-controls": "0.17.85", "@superset-ui/core": "0.17.81", "@vx/axis": "^0.0.198", "@vx/legend": "^0.0.198", @@ -61450,9 +61449,9 @@ } }, "@superset-ui/chart-controls": { - "version": "0.17.84", - "resolved": "https://registry.npmjs.org/@superset-ui/chart-controls/-/chart-controls-0.17.84.tgz", - "integrity": "sha512-tLbTdYegaez2D1N3eIHIkfpWyI3i/SoLQfY7asV1YadKcSZfff1BpSBDV2vbGom1hehsftD09sIV3uHm4wjkAg==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/chart-controls/-/chart-controls-0.17.85.tgz", + "integrity": "sha512-RNk6za6IfcnAKoDFA8XpWgK5AdwPCEprKauv29Z+8VkHrMT8DkoEwDtgl0vutEmLRPECAL+lR76g5B9Z33iCJQ==", "requires": { "@react-icons/all-files": "^4.1.0", "@superset-ui/core": "0.17.81", @@ -61583,11 +61582,11 @@ } }, "@superset-ui/legacy-plugin-chart-calendar": { - "version": "0.17.84", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-calendar/-/legacy-plugin-chart-calendar-0.17.84.tgz", - "integrity": "sha512-xalLL56JnXPxlDMjLxJqwQMy4Myaq1uOww6dyOBfgxEH1eat+V0tv4Aoh9i5RCYnafXymKTX+oqS0BkUYebwPQ==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-calendar/-/legacy-plugin-chart-calendar-0.17.85.tgz", + "integrity": "sha512-bmPvflt73ncje+22YTDTkPGIbfylPcqksiCB74/dh/cjUy39lCEpBdL1iYYee6z2vmFdn6uNWuHtpENDKTG83g==", "requires": { - "@superset-ui/chart-controls": "0.17.84", + "@superset-ui/chart-controls": "0.17.85", "@superset-ui/core": "0.17.81", "d3-array": "^2.0.3", "d3-selection": "^1.4.0", @@ -61606,11 +61605,11 @@ } }, "@superset-ui/legacy-plugin-chart-chord": { - "version": "0.17.84", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-chord/-/legacy-plugin-chart-chord-0.17.84.tgz", - "integrity": "sha512-7jhc+/iWrl6brVqQDKxEtNq3btPgGVtidzuz1LBLNz6Nc0EwMvBVAppnZvwvVgBqdZQ2T0C56wZRqwDgmj/Lxw==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-chord/-/legacy-plugin-chart-chord-0.17.85.tgz", + "integrity": "sha512-raQUDM3Dgcld7UIvXe9XGr4NEJV6x8J+gjVUQLlkvsttA32VN+J1tUhevxmT2eMMxifaaLnGWiBA678Q2ENVfg==", "requires": { - "@superset-ui/chart-controls": "0.17.84", + "@superset-ui/chart-controls": "0.17.85", "@superset-ui/core": "0.17.81", "d3": "^3.5.17", "prop-types": "^15.6.2", @@ -61618,11 +61617,11 @@ } }, "@superset-ui/legacy-plugin-chart-country-map": { - "version": "0.17.84", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-country-map/-/legacy-plugin-chart-country-map-0.17.84.tgz", - "integrity": "sha512-POFH/ZfPKOiNKA1BbeU/5vlwfU6BALph/XsQacP4YfBD7xVFGEZWDYKWH/aZky+GLCZWIp6v7h7EVzyNmC8GJQ==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-country-map/-/legacy-plugin-chart-country-map-0.17.85.tgz", + "integrity": "sha512-g8OAwInPI9r/k/0ip9pXvQR7L6PInQhN8KfMb4j5VgC4+eUl04BQDpsclVoYOz5KqthkxPvGhdlOH0zHybv3Vg==", "requires": { - "@superset-ui/chart-controls": "0.17.84", + "@superset-ui/chart-controls": "0.17.85", "@superset-ui/core": "0.17.81", "d3": "^3.5.17", "d3-array": "^2.0.3", @@ -61640,33 +61639,33 @@ } }, "@superset-ui/legacy-plugin-chart-event-flow": { - "version": "0.17.84", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-event-flow/-/legacy-plugin-chart-event-flow-0.17.84.tgz", - "integrity": "sha512-7zew1e5FRPUt06umJez3+zau/0LXtWWWDyIvy/0DiSu5i/Ds4zhru5ZmLTXhOZ6heU5BEGf/cfIV1G4bF6S00A==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-event-flow/-/legacy-plugin-chart-event-flow-0.17.85.tgz", + "integrity": "sha512-O2DiLUO0lkkpemtjwJ845veW4vFwZUkb13TkfV1ef37HBDDdEJkUa6Ls8zh7C4aybqn4HG4mr32IHg3DV2WgYg==", "requires": { "@data-ui/event-flow": "^0.0.84", - "@superset-ui/chart-controls": "0.17.84", + "@superset-ui/chart-controls": "0.17.85", "@superset-ui/core": "0.17.81", "prop-types": "^15.6.2" } }, "@superset-ui/legacy-plugin-chart-force-directed": { - "version": "0.17.84", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-force-directed/-/legacy-plugin-chart-force-directed-0.17.84.tgz", - "integrity": "sha512-GBfTI+2vfr9Y4vbT0azKqmdUJ32UGTRpw1PmuHTjw/ZQjoRts8Hyyw12o6zs0Ifj+0tIt5bqxbuaZgxuzEd/Pw==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-force-directed/-/legacy-plugin-chart-force-directed-0.17.85.tgz", + "integrity": "sha512-8QB2IPNadJ5pha2PPs0AQe5zHEnpaIcTY+JGH8LFxUFtuDLjGSuGJJNbIyM/QT63zMp0WCn5VCwgeMuKnuU9Xw==", "requires": { - "@superset-ui/chart-controls": "0.17.84", + "@superset-ui/chart-controls": "0.17.85", "@superset-ui/core": "0.17.81", "d3": "^3.5.17", "prop-types": "^15.7.2" } }, "@superset-ui/legacy-plugin-chart-heatmap": { - "version": "0.17.84", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-heatmap/-/legacy-plugin-chart-heatmap-0.17.84.tgz", - "integrity": "sha512-ZJ3BBGRL9jz8SM9UZdKKhHseZ9IZqMVfH4J/IcjeOxvNq2a0Y544WILrzmUR0DgvO++Cy7cV0VSrcSkhy/mrQw==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-heatmap/-/legacy-plugin-chart-heatmap-0.17.85.tgz", + "integrity": "sha512-zuxkDDTxxVtDkg9iu7GtcNuG9URh9GmNRM5lH2wsOdKD/UTlZrkt0Lf4tp5kKycknyHWdWFixe1CSLaLzuwwOA==", "requires": { - "@superset-ui/chart-controls": "0.17.84", + "@superset-ui/chart-controls": "0.17.85", "@superset-ui/core": "0.17.81", "d3": "^3.5.17", "d3-svg-legend": "^1.x", @@ -61675,13 +61674,13 @@ } }, "@superset-ui/legacy-plugin-chart-histogram": { - "version": "0.17.84", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-histogram/-/legacy-plugin-chart-histogram-0.17.84.tgz", - "integrity": "sha512-GaELpLkP9SFbPQnkzUte+fcKl2F++KEgEBINfS9Ek9pUMD1S/WlbevIEUUUSeAtI5rI89GN+0Qzi8MM0+D2WIw==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-histogram/-/legacy-plugin-chart-histogram-0.17.85.tgz", + "integrity": "sha512-dqrTTDeMvdm4Lgb7Cav5lm86GgiQxYda1yXWoXFlqXvwZt40EJqCxYUAAHYKD5JmlqjzIn1wuvCHzJQsDxANDQ==", "requires": { "@data-ui/histogram": "^0.0.84", "@data-ui/theme": "^0.0.84", - "@superset-ui/chart-controls": "0.17.84", + "@superset-ui/chart-controls": "0.17.85", "@superset-ui/core": "0.17.81", "@vx/legend": "^0.0.198", "@vx/responsive": "^0.0.199", @@ -61750,11 +61749,11 @@ } }, "@superset-ui/legacy-plugin-chart-horizon": { - "version": "0.17.84", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-horizon/-/legacy-plugin-chart-horizon-0.17.84.tgz", - "integrity": "sha512-9xm91qfW1OXOjTSvNoDXpkUqfsCU9wMttUihs2pdg0hbzpCI65+L5eysV11JzGS0uGOGKLDEOWoSpzmX/xVZvQ==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-horizon/-/legacy-plugin-chart-horizon-0.17.85.tgz", + "integrity": "sha512-lVzXqqLFgZ80I/fDbf2TT3cXCEW+xYKvna4Ldod/dUDFWjVIy3rzP7HicHsS7XCLPpeUHgZUFZhx8NEvTWhDqg==", "requires": { - "@superset-ui/chart-controls": "0.17.84", + "@superset-ui/chart-controls": "0.17.85", "@superset-ui/core": "0.17.81", "d3-array": "^2.0.3", "d3-scale": "^3.0.1", @@ -61784,11 +61783,11 @@ } }, "@superset-ui/legacy-plugin-chart-map-box": { - "version": "0.17.84", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-map-box/-/legacy-plugin-chart-map-box-0.17.84.tgz", - "integrity": "sha512-vllEQe4HFTc5Rduac+klL97dcmPca/VDecos3v2nnkahNl447iuq+M08evfm2VnjXgD0QL4nLcliXwQjaZ2zsA==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-map-box/-/legacy-plugin-chart-map-box-0.17.85.tgz", + "integrity": "sha512-YJIZjcfpMDaJtve6EfEr1lUgCGnsutTxyKEOj3b6wjYMfoLhSUproDoW9TUQtBR3tFrY3PrLrZg+O41L9grDIA==", "requires": { - "@superset-ui/chart-controls": "0.17.84", + "@superset-ui/chart-controls": "0.17.85", "@superset-ui/core": "0.17.81", "immutable": "^3.8.2", "mapbox-gl": "^0.53.0", @@ -61806,11 +61805,11 @@ } }, "@superset-ui/legacy-plugin-chart-paired-t-test": { - "version": "0.17.84", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-paired-t-test/-/legacy-plugin-chart-paired-t-test-0.17.84.tgz", - "integrity": "sha512-2e/UsUzRz4VUnb3q8fLNg/ZGm+6044eD3pnyCDL09Nke21BFDV6jwd3H93ryYJo89qKn3uJLPuDAvcsreN5j+w==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-paired-t-test/-/legacy-plugin-chart-paired-t-test-0.17.85.tgz", + "integrity": "sha512-JIOvvGUlVzbk2zWyQMD8WE1UN95HdOuM1I7py5LIEqJH3CwWN5SGmtDG50DAvKAhEt5InmdQkm7ihr0Uw2LoQw==", "requires": { - "@superset-ui/chart-controls": "0.17.84", + "@superset-ui/chart-controls": "0.17.85", "@superset-ui/core": "0.17.81", "distributions": "^1.0.0", "prop-types": "^15.6.2", @@ -61818,22 +61817,22 @@ } }, "@superset-ui/legacy-plugin-chart-parallel-coordinates": { - "version": "0.17.84", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-parallel-coordinates/-/legacy-plugin-chart-parallel-coordinates-0.17.84.tgz", - "integrity": "sha512-p4OFm5jExsQz0uPMhUJ3K7Yp4mz4q99CMJDHNCWAsyYUmMQYSMcmUHBIBVFG0M8Y8oapnuLYvzJMl+ptbgr22A==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-parallel-coordinates/-/legacy-plugin-chart-parallel-coordinates-0.17.85.tgz", + "integrity": "sha512-ecSKIBL7806jD+sQu6ub+92tI8W1OfLZWMUW7SeupDphlDJkrQEIQ63FQOCwWEAI9xJhn+hoxIe6M2Y9Um0FwQ==", "requires": { - "@superset-ui/chart-controls": "0.17.84", + "@superset-ui/chart-controls": "0.17.85", "@superset-ui/core": "0.17.81", "d3": "^3.5.17", "prop-types": "^15.7.2" } }, "@superset-ui/legacy-plugin-chart-partition": { - "version": "0.17.84", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-partition/-/legacy-plugin-chart-partition-0.17.84.tgz", - "integrity": "sha512-J+2cyTdIfPmjlSuNaEkdaQrmWU0LEtMoqxalDube9G+cdQrQsrWW99O141LQaaATlxBT88JRfAArN/13WfFieQ==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-partition/-/legacy-plugin-chart-partition-0.17.85.tgz", + "integrity": "sha512-seF8oZ5lRdrqGzQBDqcQIrIV9QHp6IH7e9+pyG0F4BzSY8WNKIjFVkd92HcNcm/KeqkzID1mqGv1aCzlDQNaiw==", "requires": { - "@superset-ui/chart-controls": "0.17.84", + "@superset-ui/chart-controls": "0.17.85", "@superset-ui/core": "0.17.81", "d3": "^3.5.17", "d3-hierarchy": "^1.1.8", @@ -61841,11 +61840,11 @@ } }, "@superset-ui/legacy-plugin-chart-pivot-table": { - "version": "0.17.84", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-pivot-table/-/legacy-plugin-chart-pivot-table-0.17.84.tgz", - "integrity": "sha512-NXcaAzPEllzBYeQQVjexJUThgQxWRHY7mlGY2q3DDVCKq5EMakSucxv5Ma3vpBWD/nmiRTHm7F4ouVoK0b9FhA==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-pivot-table/-/legacy-plugin-chart-pivot-table-0.17.85.tgz", + "integrity": "sha512-oXBXgzi4uBKG1FsOd9ZFIhPQvaBIYanzHdqwthafsRDG9kgKL9b//+MCIZgRlqlSJK+vsf4EMxXucFKJtoLDuA==", "requires": { - "@superset-ui/chart-controls": "0.17.84", + "@superset-ui/chart-controls": "0.17.85", "@superset-ui/core": "0.17.81", "d3": "^3.5.17", "datatables.net-bs": "^1.10.15", @@ -61853,11 +61852,11 @@ } }, "@superset-ui/legacy-plugin-chart-rose": { - "version": "0.17.84", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-rose/-/legacy-plugin-chart-rose-0.17.84.tgz", - "integrity": "sha512-r0Anl44JaBm4ivHErvpCcPU7RThxPuy7/tGY0jesfxghaPJ/cTMNkL6Tt4rErLQvLo7pVVO0gYpE3mLx+udNPw==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-rose/-/legacy-plugin-chart-rose-0.17.85.tgz", + "integrity": "sha512-NilJNxC49YM+j7WqGmna/+G1m0/VXb1hoj2Z2PXPy8KYGMKrLQAwNE0ce7yBnQqSljvxq5Memfjqzqjoio5aoA==", "requires": { - "@superset-ui/chart-controls": "0.17.84", + "@superset-ui/chart-controls": "0.17.85", "@superset-ui/core": "0.17.81", "d3": "^3.5.17", "nvd3": "1.8.6", @@ -61865,11 +61864,11 @@ } }, "@superset-ui/legacy-plugin-chart-sankey": { - "version": "0.17.84", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-sankey/-/legacy-plugin-chart-sankey-0.17.84.tgz", - "integrity": "sha512-P9ArmxsOONfLEBQ1mlIrRHDDWaM336mZsGfsWpD7gcP7oz4hNMneyR9KwUsPanOqVaIf2rETvdXNL5d5ddFY0w==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-sankey/-/legacy-plugin-chart-sankey-0.17.85.tgz", + "integrity": "sha512-MV47pYL5QkVAT8rfYrLT9joJF8a48mLiS3suVT+u4vHv3rrsWNTa00irD1HqNoWLDCuZr+U7pCt+UbW/0w2yGw==", "requires": { - "@superset-ui/chart-controls": "0.17.84", + "@superset-ui/chart-controls": "0.17.85", "@superset-ui/core": "0.17.81", "d3": "^3.5.17", "d3-sankey": "^0.4.2", @@ -61877,11 +61876,11 @@ } }, "@superset-ui/legacy-plugin-chart-sankey-loop": { - "version": "0.17.84", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-sankey-loop/-/legacy-plugin-chart-sankey-loop-0.17.84.tgz", - "integrity": "sha512-PH+FajOKURiF7h+KMCsrMATBUR2Whiw5a6eMz+SkC3LoHVcWnpiSDh6F8ELBXfc1dhK0iQdrFWdCb8+Atpjvcg==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-sankey-loop/-/legacy-plugin-chart-sankey-loop-0.17.85.tgz", + "integrity": "sha512-z3I/NCQa64LXKiel+AswpdnUuhsS/0hiziY+T+BuN2geeVrWC+LMQya1hMDg+dDR3RgpkKqgpI9lEf1KTuhHaQ==", "requires": { - "@superset-ui/chart-controls": "0.17.84", + "@superset-ui/chart-controls": "0.17.85", "@superset-ui/core": "0.17.81", "d3-sankey-diagram": "^0.7.3", "d3-selection": "^1.4.0", @@ -61889,22 +61888,22 @@ } }, "@superset-ui/legacy-plugin-chart-sunburst": { - "version": "0.17.84", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-sunburst/-/legacy-plugin-chart-sunburst-0.17.84.tgz", - "integrity": "sha512-FSLKCWGzVsL+VL1DAD4Gsgt6swW9iqU2h+fW2O4UXIWlVdKCU7Xg012Lsh6QSt7lYJSQ3unnYtfMstYcpiR/HA==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-sunburst/-/legacy-plugin-chart-sunburst-0.17.85.tgz", + "integrity": "sha512-L6rRXhEasNQpFN9SQ0ZB6qfa94XIVlCxyEmy8DOR2N39nlLncCweiHtgvgS4Su9H86nidZ/6ryNZ4fKxmm2mBg==", "requires": { - "@superset-ui/chart-controls": "0.17.84", + "@superset-ui/chart-controls": "0.17.85", "@superset-ui/core": "0.17.81", "d3": "^3.5.17", "prop-types": "^15.6.2" } }, "@superset-ui/legacy-plugin-chart-treemap": { - "version": "0.17.84", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-treemap/-/legacy-plugin-chart-treemap-0.17.84.tgz", - "integrity": "sha512-yxjlWn9RVBw4PI3EKisHb80sLLHAZuVqykzeFebSXW4jlFYzgfQea3k5pCymz5QPDfYjM8c4DULMiffbbrnKmA==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-treemap/-/legacy-plugin-chart-treemap-0.17.85.tgz", + "integrity": "sha512-X4HVCOuhe45qaqUzTfjrKxAr9RoSbdwmWlCWLK2djVJDAFYGqhTxcpNEXxSv+cMqpyMBLhwXqZ5SMV5u0Gu+Eg==", "requires": { - "@superset-ui/chart-controls": "0.17.84", + "@superset-ui/chart-controls": "0.17.85", "@superset-ui/core": "0.17.81", "d3-hierarchy": "^1.1.8", "d3-selection": "^1.4.0", @@ -61912,11 +61911,11 @@ } }, "@superset-ui/legacy-plugin-chart-world-map": { - "version": "0.17.84", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-world-map/-/legacy-plugin-chart-world-map-0.17.84.tgz", - "integrity": "sha512-KJmt2jKXJ/1jade+bO0D36DNFl74oDJsdCr/2wuPRQgFTO8xkqoQKk/y+7on4BDSkHUfTExDuOjnnsS3ijdXjA==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-world-map/-/legacy-plugin-chart-world-map-0.17.85.tgz", + "integrity": "sha512-bxO1/tY2FI1MzfircbjwuwbLzI+jg2puIlKFek6vkbQuxA9+gCtxKD0SLOlxadNQXW4kUrU2DxhfPlr6th55bA==", "requires": { - "@superset-ui/chart-controls": "0.17.84", + "@superset-ui/chart-controls": "0.17.85", "@superset-ui/core": "0.17.81", "d3": "^3.5.17", "d3-array": "^2.4.0", @@ -61936,12 +61935,12 @@ } }, "@superset-ui/legacy-preset-chart-big-number": { - "version": "0.17.84", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-preset-chart-big-number/-/legacy-preset-chart-big-number-0.17.84.tgz", - "integrity": "sha512-1SotYUwx6BXvXXTHszqpu/+GSQoS2TkQPIXVqlkd4c7bHIZCetwsmOs9F9pCyiaYo9GM3x7DdTWOezgYYORFDw==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-preset-chart-big-number/-/legacy-preset-chart-big-number-0.17.85.tgz", + "integrity": "sha512-Txuk8L/flifPESHWNpZ8vwXinHAPCXXEp2fZdB/6CRyB6TVfMs2RmKqnEyzO5xBGgqeTfdH6xeuVn+8Hfdcvrg==", "requires": { "@data-ui/xy-chart": "^0.0.84", - "@superset-ui/chart-controls": "0.17.84", + "@superset-ui/chart-controls": "0.17.85", "@superset-ui/core": "0.17.81", "@types/d3-color": "^1.2.2", "@types/shortid": "^0.0.29", @@ -62097,12 +62096,12 @@ } }, "@superset-ui/legacy-preset-chart-nvd3": { - "version": "0.17.84", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-preset-chart-nvd3/-/legacy-preset-chart-nvd3-0.17.84.tgz", - "integrity": "sha512-U1LAtnwktODasC8nKxVaui3xZn71npbsfUKH/ZKFkDvf3zSuu96qcklGYJLTRiEhFMq6NDT1V4ogEmoIuRjk6w==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-preset-chart-nvd3/-/legacy-preset-chart-nvd3-0.17.85.tgz", + "integrity": "sha512-KoC8kIqP6zwtGzXBUj6yMaF3Cz2TXygAe7ZCG5XeavgrjgKsWqOXAdQzJ9oBrCPXPQBC5hF7TruBB/Xd839TDA==", "requires": { "@data-ui/xy-chart": "^0.0.84", - "@superset-ui/chart-controls": "0.17.84", + "@superset-ui/chart-controls": "0.17.85", "@superset-ui/core": "0.17.81", "d3": "^3.5.17", "d3-tip": "^0.9.1", @@ -62117,11 +62116,11 @@ } }, "@superset-ui/plugin-chart-echarts": { - "version": "0.17.84", - "resolved": "https://registry.npmjs.org/@superset-ui/plugin-chart-echarts/-/plugin-chart-echarts-0.17.84.tgz", - "integrity": "sha512-KTK5dTri9mJCCWnSijPGZ4+Mf/ZdOkATMoYFQjjNrb2GL9TkI9Utc+61CBfizKk3vz3t3PKoSWWa4TliEeGCgQ==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/plugin-chart-echarts/-/plugin-chart-echarts-0.17.85.tgz", + "integrity": "sha512-dAqRzVVm7Shn2fgKoj2puX0jefe2r14dOQThJ8CG/hRlpmXsKmLnSywwLvQVxq1oUhh+uQYsemdYBCsrQAuxcg==", "requires": { - "@superset-ui/chart-controls": "0.17.84", + "@superset-ui/chart-controls": "0.17.85", "@superset-ui/core": "0.17.81", "@types/mathjs": "^6.0.7", "d3-array": "^1.2.0", @@ -62131,22 +62130,22 @@ } }, "@superset-ui/plugin-chart-pivot-table": { - "version": "0.17.84", - "resolved": "https://registry.npmjs.org/@superset-ui/plugin-chart-pivot-table/-/plugin-chart-pivot-table-0.17.84.tgz", - "integrity": "sha512-JGyn9Yd5Wg5ZJSGVQ/PQwPqkwaLZeZK09u0NvCRjoqLEIz9psudLVfTVgsj6WTLJrrEdr4J8yXxlBnnMQ/N3Kw==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/plugin-chart-pivot-table/-/plugin-chart-pivot-table-0.17.85.tgz", + "integrity": "sha512-tTU79c/C7g5sdZxRNVcl6QFbwqhsv0Bi/XeYOwhTNAe7qf7LmkvwQB78fvh7WW+U2FBltkhHmOxOb/HIJscXVg==", "requires": { - "@superset-ui/chart-controls": "0.17.84", + "@superset-ui/chart-controls": "0.17.85", "@superset-ui/core": "0.17.81", "@superset-ui/react-pivottable": "^0.12.12" } }, "@superset-ui/plugin-chart-table": { - "version": "0.17.84", - "resolved": "https://registry.npmjs.org/@superset-ui/plugin-chart-table/-/plugin-chart-table-0.17.84.tgz", - "integrity": "sha512-XUI3vj8m77jH5R1XTjz0UbW8SyVxFDD2xwlxkqtpstWkjmOd3VPGN2/LpQa0dKVpW8KBUpHVNRDByBYL+siVFA==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/plugin-chart-table/-/plugin-chart-table-0.17.85.tgz", + "integrity": "sha512-RGeIGAQuiiOy6JgpnQC+rtVDWR8jGZF1DOER+WkgbfTeKiRmVGyo7wOSb7dvQlsaitUg2Y30962arOGeEcgvpA==", "requires": { "@react-icons/all-files": "^4.1.0", - "@superset-ui/chart-controls": "0.17.84", + "@superset-ui/chart-controls": "0.17.85", "@superset-ui/core": "0.17.81", "@types/d3-array": "^2.9.0", "@types/react-table": "^7.0.29", @@ -62169,11 +62168,11 @@ } }, "@superset-ui/plugin-chart-word-cloud": { - "version": "0.17.84", - "resolved": "https://registry.npmjs.org/@superset-ui/plugin-chart-word-cloud/-/plugin-chart-word-cloud-0.17.84.tgz", - "integrity": "sha512-ChuYtfOVlzOZpdavu65kVOoGLjwSQUU6mpJ8ziXqTr36hm8+QVzETX21+LTyJ8rWBFsehYPxOEgOlpmK2ZapUQ==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/plugin-chart-word-cloud/-/plugin-chart-word-cloud-0.17.85.tgz", + "integrity": "sha512-nCbmndNxUoHWkYRCARD1C4XjzxXtk/OkCUVdB3EKaPNWbi0QuQHUuAZUh3ROIewxd3KAMoSUYRIjTVYDbetnJQ==", "requires": { - "@superset-ui/chart-controls": "0.17.84", + "@superset-ui/chart-controls": "0.17.85", "@superset-ui/core": "0.17.81", "@types/d3-cloud": "^1.2.1", "@types/d3-scale": "^2.0.2", @@ -62205,13 +62204,13 @@ } }, "@superset-ui/preset-chart-xy": { - "version": "0.17.84", - "resolved": "https://registry.npmjs.org/@superset-ui/preset-chart-xy/-/preset-chart-xy-0.17.84.tgz", - "integrity": "sha512-Q4+k2k9ZoQUUy1d3S3h6Taj6zMIHnU0XtVaLYIxj1oISfyaKWz1hPR8At1ReC5pOl5qoJSAQv4X61KFOwEMsUw==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/preset-chart-xy/-/preset-chart-xy-0.17.85.tgz", + "integrity": "sha512-Cd2Ji1trP6aHuOfHK0GDza8iQUVkJNbtIFSReb240ViewICH/7mtsrj8jLAvf3tyOORLD3AW2j8s68UDCrBi6g==", "requires": { "@data-ui/theme": "^0.0.84", "@data-ui/xy-chart": "^0.0.84", - "@superset-ui/chart-controls": "0.17.84", + "@superset-ui/chart-controls": "0.17.85", "@superset-ui/core": "0.17.81", "@vx/axis": "^0.0.198", "@vx/legend": "^0.0.198", diff --git a/superset-frontend/package.json b/superset-frontend/package.json index 746efbebd070e..4b394600d6a12 100644 --- a/superset-frontend/package.json +++ b/superset-frontend/package.json @@ -67,35 +67,35 @@ "@emotion/babel-preset-css-prop": "^11.2.0", "@emotion/cache": "^11.1.3", "@emotion/react": "^11.1.5", - "@superset-ui/chart-controls": "^0.17.84", + "@superset-ui/chart-controls": "^0.17.85", "@superset-ui/core": "^0.17.81", - "@superset-ui/legacy-plugin-chart-calendar": "^0.17.84", - "@superset-ui/legacy-plugin-chart-chord": "^0.17.84", - "@superset-ui/legacy-plugin-chart-country-map": "^0.17.84", - "@superset-ui/legacy-plugin-chart-event-flow": "^0.17.84", - "@superset-ui/legacy-plugin-chart-force-directed": "^0.17.84", - "@superset-ui/legacy-plugin-chart-heatmap": "^0.17.84", - "@superset-ui/legacy-plugin-chart-histogram": "^0.17.84", - "@superset-ui/legacy-plugin-chart-horizon": "^0.17.84", - "@superset-ui/legacy-plugin-chart-map-box": "^0.17.84", - "@superset-ui/legacy-plugin-chart-paired-t-test": "^0.17.84", - "@superset-ui/legacy-plugin-chart-parallel-coordinates": "^0.17.84", - "@superset-ui/legacy-plugin-chart-partition": "^0.17.84", - "@superset-ui/legacy-plugin-chart-pivot-table": "^0.17.84", - "@superset-ui/legacy-plugin-chart-rose": "^0.17.84", - "@superset-ui/legacy-plugin-chart-sankey": "^0.17.84", - "@superset-ui/legacy-plugin-chart-sankey-loop": "^0.17.84", - "@superset-ui/legacy-plugin-chart-sunburst": "^0.17.84", - "@superset-ui/legacy-plugin-chart-treemap": "^0.17.84", - "@superset-ui/legacy-plugin-chart-world-map": "^0.17.84", - "@superset-ui/legacy-preset-chart-big-number": "^0.17.84", + "@superset-ui/legacy-plugin-chart-calendar": "^0.17.85", + "@superset-ui/legacy-plugin-chart-chord": "^0.17.85", + "@superset-ui/legacy-plugin-chart-country-map": "^0.17.85", + "@superset-ui/legacy-plugin-chart-event-flow": "^0.17.85", + "@superset-ui/legacy-plugin-chart-force-directed": "^0.17.85", + "@superset-ui/legacy-plugin-chart-heatmap": "^0.17.85", + "@superset-ui/legacy-plugin-chart-histogram": "^0.17.85", + "@superset-ui/legacy-plugin-chart-horizon": "^0.17.85", + "@superset-ui/legacy-plugin-chart-map-box": "^0.17.85", + "@superset-ui/legacy-plugin-chart-paired-t-test": "^0.17.85", + "@superset-ui/legacy-plugin-chart-parallel-coordinates": "^0.17.85", + "@superset-ui/legacy-plugin-chart-partition": "^0.17.85", + "@superset-ui/legacy-plugin-chart-pivot-table": "^0.17.85", + "@superset-ui/legacy-plugin-chart-rose": "^0.17.85", + "@superset-ui/legacy-plugin-chart-sankey": "^0.17.85", + "@superset-ui/legacy-plugin-chart-sankey-loop": "^0.17.85", + "@superset-ui/legacy-plugin-chart-sunburst": "^0.17.85", + "@superset-ui/legacy-plugin-chart-treemap": "^0.17.85", + "@superset-ui/legacy-plugin-chart-world-map": "^0.17.85", + "@superset-ui/legacy-preset-chart-big-number": "^0.17.85", "@superset-ui/legacy-preset-chart-deckgl": "^0.4.10", - "@superset-ui/legacy-preset-chart-nvd3": "^0.17.84", - "@superset-ui/plugin-chart-echarts": "^0.17.84", - "@superset-ui/plugin-chart-pivot-table": "^0.17.84", - "@superset-ui/plugin-chart-table": "^0.17.84", - "@superset-ui/plugin-chart-word-cloud": "^0.17.84", - "@superset-ui/preset-chart-xy": "^0.17.84", + "@superset-ui/legacy-preset-chart-nvd3": "^0.17.85", + "@superset-ui/plugin-chart-echarts": "^0.17.85", + "@superset-ui/plugin-chart-pivot-table": "^0.17.85", + "@superset-ui/plugin-chart-table": "^0.17.85", + "@superset-ui/plugin-chart-word-cloud": "^0.17.85", + "@superset-ui/preset-chart-xy": "^0.17.85", "@vx/responsive": "^0.0.195", "abortcontroller-polyfill": "^1.1.9", "antd": "^4.9.4", From d75da748d5b1e7f5bcf56f0ebe2116693db2430d Mon Sep 17 00:00:00 2001 From: ETselikov Date: Thu, 19 Aug 2021 15:39:00 +0300 Subject: [PATCH 04/22] docs: add VkusVill and TechAudit to users list (#16113) * Update INTHEWILD.md * Update INTHEWILD.md --- RESOURCES/INTHEWILD.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/RESOURCES/INTHEWILD.md b/RESOURCES/INTHEWILD.md index 456f60d641910..98827c4b4b176 100644 --- a/RESOURCES/INTHEWILD.md +++ b/RESOURCES/INTHEWILD.md @@ -61,6 +61,7 @@ Join our growing community! - [Tails.com](https://tails.com) [@alanmcruickshank] - [THE ICONIC](http://theiconic.com.au/) [@ksaagariconic] - [Utair](https://www.utair.ru) [@utair-digital] +- [VkusVill](https://www.vkusvill.ru) [@ETselikov] - [Zalando](https://www.zalando.com) [@dmigo] - [Zalora](https://www.zalora.com) [@ksaagariconic] @@ -96,6 +97,7 @@ Join our growing community! - [Showmax](https://tech.showmax.com) [@bobek] - [source{d}](https://www.sourced.tech) [@marnovo] - [Steamroot](https://streamroot.io/) +- [TechAudit](https://www.techaudit.info) [@ETselikov] - [Tenable](https://www.tenable.com) [@dflionis] - [timbr.ai](https://timbr.ai/) [@semantiDan] - [Tobii](http://www.tobii.com/) [@dwa] @@ -104,7 +106,6 @@ Join our growing community! - [Windsor.ai](https://www.windsor.ai/) [@octaviancorlade] - [Zeta](https://www.zeta.tech/) [@shaikidris] - ### Entertainment - [6play](https://www.6play.fr) [@CoryChaplin] - [bilibili](https://www.bilibili.com) [@Moinheart] From c5c28618a528fca95b2ec3aca49bb9ec56c4e1a1 Mon Sep 17 00:00:00 2001 From: Geido <60598000+geido@users.noreply.github.com> Date: Thu, 19 Aug 2021 16:46:25 +0200 Subject: [PATCH 05/22] fix: Blank space in Change dataset modal without warning message (#16324) * Make scrollTable flex * Revert unclosable warning --- .../src/components/TableView/TableView.tsx | 7 +++---- .../src/datasource/ChangeDatasourceModal.tsx | 11 +++++++++-- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/superset-frontend/src/components/TableView/TableView.tsx b/superset-frontend/src/components/TableView/TableView.tsx index 6270ab2c13e12..ff3fee1cbacfe 100644 --- a/superset-frontend/src/components/TableView/TableView.tsx +++ b/superset-frontend/src/components/TableView/TableView.tsx @@ -56,15 +56,14 @@ const EmptyWrapper = styled.div` `; const TableViewStyles = styled.div<{ - hasPagination?: boolean; isPaginationSticky?: boolean; scrollTable?: boolean; small?: boolean; }>` - ${({ hasPagination, scrollTable, theme }) => + ${({ scrollTable, theme }) => scrollTable && ` - height: ${hasPagination ? '300px' : '380px'}; + flex: 1 1 auto; margin-bottom: ${theme.gridUnit * 4}px; overflow: auto; `} @@ -196,7 +195,7 @@ const TableView = ({ return ( <> - + = ({ }; return ( - = ({ )} {confirmChange && <>{CONFIRM_WARNING_MESSAGE}} - + ); }; From 86f4e691d47d8408d64b7e2c96c16c1c4c4b85c7 Mon Sep 17 00:00:00 2001 From: Ville Brofeldt <33317356+villebro@users.noreply.github.com> Date: Thu, 19 Aug 2021 19:19:48 +0300 Subject: [PATCH 06/22] chore(viz): bump deckgl plugin to 0.4.11 (#16353) --- superset-frontend/package-lock.json | 14 +++++++------- superset-frontend/package.json | 2 +- superset/examples/deck.py | 1 + 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/superset-frontend/package-lock.json b/superset-frontend/package-lock.json index 4930c8b04715d..8fa99d0229fa2 100644 --- a/superset-frontend/package-lock.json +++ b/superset-frontend/package-lock.json @@ -37,7 +37,7 @@ "@superset-ui/legacy-plugin-chart-treemap": "^0.17.85", "@superset-ui/legacy-plugin-chart-world-map": "^0.17.85", "@superset-ui/legacy-preset-chart-big-number": "^0.17.85", - "@superset-ui/legacy-preset-chart-deckgl": "^0.4.10", + "@superset-ui/legacy-preset-chart-deckgl": "^0.4.11", "@superset-ui/legacy-preset-chart-nvd3": "^0.17.85", "@superset-ui/plugin-chart-echarts": "^0.17.85", "@superset-ui/plugin-chart-pivot-table": "^0.17.85", @@ -12497,9 +12497,9 @@ } }, "node_modules/@superset-ui/legacy-preset-chart-deckgl": { - "version": "0.4.10", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-preset-chart-deckgl/-/legacy-preset-chart-deckgl-0.4.10.tgz", - "integrity": "sha512-UGgzzDjy6N+vZvHlaSbihEyblm41jS2eL/41RWAEAABrn8g27V33a1VdVR/5zLUAtjqvP/dZqqkebF0h4ebmXA==", + "version": "0.4.11", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-preset-chart-deckgl/-/legacy-preset-chart-deckgl-0.4.11.tgz", + "integrity": "sha512-N4l8zavJ3kQUyoPFqG6zXQT8EELFcXtD/GNRy3aJzSgHObRhK8+aqFaWIn3ncQrBuEiUzGDnHLNni/LRAxz7vA==", "dependencies": { "@math.gl/web-mercator": "^3.2.2", "@types/d3-array": "^2.0.0", @@ -61949,9 +61949,9 @@ } }, "@superset-ui/legacy-preset-chart-deckgl": { - "version": "0.4.10", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-preset-chart-deckgl/-/legacy-preset-chart-deckgl-0.4.10.tgz", - "integrity": "sha512-UGgzzDjy6N+vZvHlaSbihEyblm41jS2eL/41RWAEAABrn8g27V33a1VdVR/5zLUAtjqvP/dZqqkebF0h4ebmXA==", + "version": "0.4.11", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-preset-chart-deckgl/-/legacy-preset-chart-deckgl-0.4.11.tgz", + "integrity": "sha512-N4l8zavJ3kQUyoPFqG6zXQT8EELFcXtD/GNRy3aJzSgHObRhK8+aqFaWIn3ncQrBuEiUzGDnHLNni/LRAxz7vA==", "requires": { "@math.gl/web-mercator": "^3.2.2", "@types/d3-array": "^2.0.0", diff --git a/superset-frontend/package.json b/superset-frontend/package.json index 4b394600d6a12..0ffeb44658ca2 100644 --- a/superset-frontend/package.json +++ b/superset-frontend/package.json @@ -89,7 +89,7 @@ "@superset-ui/legacy-plugin-chart-treemap": "^0.17.85", "@superset-ui/legacy-plugin-chart-world-map": "^0.17.85", "@superset-ui/legacy-preset-chart-big-number": "^0.17.85", - "@superset-ui/legacy-preset-chart-deckgl": "^0.4.10", + "@superset-ui/legacy-preset-chart-deckgl": "^0.4.11", "@superset-ui/legacy-preset-chart-nvd3": "^0.17.85", "@superset-ui/plugin-chart-echarts": "^0.17.85", "@superset-ui/plugin-chart-pivot-table": "^0.17.85", diff --git a/superset/examples/deck.py b/superset/examples/deck.py index c29413e2c5051..a3d137bb06d71 100644 --- a/superset/examples/deck.py +++ b/superset/examples/deck.py @@ -287,6 +287,7 @@ def load_deck_dash() -> None: slices.append(slc) slice_data = { + "autozoom": False, "spatial": {"type": "latlong", "lonCol": "LON", "latCol": "LAT"}, "row_limit": 5000, "mapbox_style": "mapbox://styles/mapbox/satellite-streets-v9", From 37f09bd2963f248ba8a92d56a062acc96472e058 Mon Sep 17 00:00:00 2001 From: Beto Dealmeida Date: Thu, 19 Aug 2021 09:38:24 -0700 Subject: [PATCH 07/22] fix: columns/index rebuild (#16355) --- superset/charts/post_processing.py | 4 +++- superset/utils/csv.py | 8 ++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/superset/charts/post_processing.py b/superset/charts/post_processing.py index 9ef976bdec62b..3ad0a8570c6fe 100644 --- a/superset/charts/post_processing.py +++ b/superset/charts/post_processing.py @@ -296,7 +296,9 @@ def apply_post_process( query["coltypes"] = extract_dataframe_dtypes(processed_df) query["rowcount"] = len(processed_df.index) - # flatten columns/index so we can encode data as JSON + # Flatten hierarchical columns/index since they are represented as + # `Tuple[str]`. Otherwise encoding to JSON later will fail because + # maps cannot have tuples as their keys in JSON. processed_df.columns = [ " ".join(str(name) for name in column).strip() if isinstance(column, tuple) diff --git a/superset/utils/csv.py b/superset/utils/csv.py index 3c362f7fac432..42d2c557832e9 100644 --- a/superset/utils/csv.py +++ b/superset/utils/csv.py @@ -96,10 +96,14 @@ def get_chart_dataframe( result = simplejson.loads(content.decode("utf-8")) df = pd.DataFrame.from_dict(result["result"][0]["data"]) + + # rebuild hierarchical columns and index df.columns = pd.MultiIndex.from_tuples( - tuple(colname) for colname in result["result"][0]["colnames"] + tuple(colname) if isinstance(colname, list) else (colname,) + for colname in result["result"][0]["colnames"] ) df.index = pd.MultiIndex.from_tuples( - tuple(indexname) for indexname in result["result"][0]["indexnames"] + tuple(indexname) if isinstance(indexname, list) else (indexname,) + for indexname in result["result"][0]["indexnames"] ) return df From 50d896f1b74c363d944ef462dc87c6674305a69f Mon Sep 17 00:00:00 2001 From: "Hugh A. Miles II" Date: Thu, 19 Aug 2021 14:04:23 -0400 Subject: [PATCH 08/22] fix: Fix parsing onSaving reports toast when user hasn't saved chart (#16330) * don't maniuplate error message * remove extra idx reference * u * change print * update with test * add case for dashboards * fix test --- .../src/reports/actions/reports.js | 2 +- superset/reports/commands/base.py | 14 +++++ superset/reports/commands/exceptions.py | 26 +++++++++ tests/integration_tests/reports/api_tests.py | 56 +++++++++++++++++++ 4 files changed, 97 insertions(+), 1 deletion(-) diff --git a/superset-frontend/src/reports/actions/reports.js b/superset-frontend/src/reports/actions/reports.js index c7eb0c934561f..55cea9dbaa7c9 100644 --- a/superset-frontend/src/reports/actions/reports.js +++ b/superset-frontend/src/reports/actions/reports.js @@ -111,7 +111,7 @@ export const addReport = report => dispatch => { const parsedError = await getClientErrorObject(e); const errorMessage = parsedError.message; const errorArr = Object.keys(errorMessage); - const error = errorMessage[errorArr[0]][0]; + const error = errorMessage[errorArr[0]]; dispatch( addDangerToast( t('An error occurred while editing this report: %s', error), diff --git a/superset/reports/commands/base.py b/superset/reports/commands/base.py index bb4064d22cde7..3582767ef65f2 100644 --- a/superset/reports/commands/base.py +++ b/superset/reports/commands/base.py @@ -22,9 +22,12 @@ from superset.charts.dao import ChartDAO from superset.commands.base import BaseCommand from superset.dashboards.dao import DashboardDAO +from superset.models.reports import ReportCreationMethodType from superset.reports.commands.exceptions import ( ChartNotFoundValidationError, + ChartNotSavedValidationError, DashboardNotFoundValidationError, + DashboardNotSavedValidationError, ReportScheduleChartOrDashboardValidationError, ) @@ -47,6 +50,17 @@ def validate_chart_dashboard( """ Validate chart or dashboard relation """ chart_id = self._properties.get("chart") dashboard_id = self._properties.get("dashboard") + creation_method = self._properties.get("creation_method") + + if creation_method == ReportCreationMethodType.CHARTS and not chart_id: + # User has not saved chart yet in Explore view + exceptions.append(ChartNotSavedValidationError()) + return + + if creation_method == ReportCreationMethodType.DASHBOARDS and not dashboard_id: + exceptions.append(DashboardNotSavedValidationError()) + return + if chart_id and dashboard_id: exceptions.append(ReportScheduleChartOrDashboardValidationError()) if chart_id: diff --git a/superset/reports/commands/exceptions.py b/superset/reports/commands/exceptions.py index c59ba3e8b4c95..41b2e5605ca69 100644 --- a/superset/reports/commands/exceptions.py +++ b/superset/reports/commands/exceptions.py @@ -80,6 +80,32 @@ def __init__(self) -> None: super().__init__(_("Choose a chart or dashboard not both"), field_name="chart") +class ChartNotSavedValidationError(ValidationError): + """ + Marshmallow validation error for charts that haven't been saved yet + """ + + def __init__(self) -> None: + super().__init__( + _("Please save your chart first, then try creating a new email report."), + field_name="chart", + ) + + +class DashboardNotSavedValidationError(ValidationError): + """ + Marshmallow validation error for dashboards that haven't been saved yet + """ + + def __init__(self) -> None: + super().__init__( + _( + "Please save your dashboard first, then try creating a new email report." + ), + field_name="dashboard", + ) + + class ReportScheduleInvalidError(CommandInvalidError): message = _("Report Schedule parameters are invalid.") diff --git a/tests/integration_tests/reports/api_tests.py b/tests/integration_tests/reports/api_tests.py index 3c306490d1389..55fce65162ea9 100644 --- a/tests/integration_tests/reports/api_tests.py +++ b/tests/integration_tests/reports/api_tests.py @@ -734,6 +734,62 @@ def test_create_report_schedule_schema(self): assert data["result"]["timezone"] == "America/Los_Angeles" assert rv.status_code == 201 + @pytest.mark.usefixtures( + "load_birth_names_dashboard_with_slices", "create_report_schedules" + ) + def test_unsaved_report_schedule_schema(self): + """ + ReportSchedule Api: Test create report schedule with unsaved chart + """ + self.login(username="admin") + chart = db.session.query(Slice).first() + dashboard = db.session.query(Dashboard).first() + example_db = get_example_database() + + report_schedule_data = { + "type": ReportScheduleType.REPORT, + "name": "name3", + "description": "description", + "creation_method": ReportCreationMethodType.CHARTS, + "crontab": "0 9 * * *", + "chart": 0, + } + uri = "api/v1/report/" + rv = self.client.post(uri, json=report_schedule_data) + data = json.loads(rv.data.decode("utf-8")) + assert rv.status_code == 422 + assert ( + data["message"]["chart"] + == "Please save your chart first, then try creating a new email report." + ) + + @pytest.mark.usefixtures( + "load_birth_names_dashboard_with_slices", "create_report_schedules" + ) + def test_no_dashboard_report_schedule_schema(self): + """ + ReportSchedule Api: Test create report schedule with not dashboard id + """ + self.login(username="admin") + chart = db.session.query(Slice).first() + dashboard = db.session.query(Dashboard).first() + example_db = get_example_database() + report_schedule_data = { + "type": ReportScheduleType.REPORT, + "name": "name3", + "description": "description", + "creation_method": ReportCreationMethodType.DASHBOARDS, + "crontab": "0 9 * * *", + } + uri = "api/v1/report/" + rv = self.client.post(uri, json=report_schedule_data) + data = json.loads(rv.data.decode("utf-8")) + assert rv.status_code == 422 + assert ( + data["message"]["dashboard"] + == "Please save your dashboard first, then try creating a new email report." + ) + @pytest.mark.usefixtures("load_birth_names_dashboard_with_slices") def test_create_report_schedule_chart_dash_validation(self): """ From 575ee24a99011bf5b4106d3f3cc57afb2a968cb5 Mon Sep 17 00:00:00 2001 From: Ville Brofeldt <33317356+villebro@users.noreply.github.com> Date: Fri, 20 Aug 2021 11:43:57 +0300 Subject: [PATCH 09/22] fix: call external metadata endpoint with correct rison object (#16369) --- superset-frontend/src/datasource/DatasourceEditor.jsx | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/superset-frontend/src/datasource/DatasourceEditor.jsx b/superset-frontend/src/datasource/DatasourceEditor.jsx index c59b6ad70b8fa..4bf2690cfa8fd 100644 --- a/superset-frontend/src/datasource/DatasourceEditor.jsx +++ b/superset-frontend/src/datasource/DatasourceEditor.jsx @@ -493,11 +493,14 @@ class DatasourceEditor extends React.PureComponent { schema_name: datasource.schema, table_name: datasource.table_name, }; - const endpoint = `/datasource/external_metadata_by_name/?q=${rison.encode( + Object.entries(params).forEach(([key, value]) => { // rison can't encode the undefined value - Object.keys(params).map(key => - params[key] === undefined ? null : params[key], - ), + if (value === undefined) { + params[key] = null; + } + }); + const endpoint = `/datasource/external_metadata_by_name/?q=${rison.encode( + params, )}`; this.setState({ metadataLoading: true }); From ea803c3d1ce08f643b412d5e58d8cc495f9210a5 Mon Sep 17 00:00:00 2001 From: "Hugh A. Miles II" Date: Fri, 20 Aug 2021 12:31:21 -0400 Subject: [PATCH 10/22] feat: Add new dev commands to Makefile (#16327) * updating makefile * Update Makefile --- Makefile | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 85027a175f58b..ef0d3edbec13f 100644 --- a/Makefile +++ b/Makefile @@ -58,7 +58,7 @@ update-py: update-js: # Install js packages - cd superset-frontend; npm install + cd superset-frontend; npm ci venv: # Create a virtual environment and activate it (recommended) @@ -81,3 +81,9 @@ py-lint: pre-commit js-format: cd superset-frontend; npm run prettier + +flask-app: + flask run -p 8088 --with-threads --reload --debugger + +node-app: + cd superset-frontend; npm run dev-server From 13a2ee373ca338e893b87e7d73bd638e6e92617c Mon Sep 17 00:00:00 2001 From: Ke Zhu Date: Fri, 20 Aug 2021 12:54:46 -0400 Subject: [PATCH 11/22] docs: document FLASK_APP_MUTATOR (#16286) --- .../pages/docs/installation/configuring.mdx | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/docs/src/pages/docs/installation/configuring.mdx b/docs/src/pages/docs/installation/configuring.mdx index 2c141d4dd50c8..707aaeef7209e 100644 --- a/docs/src/pages/docs/installation/configuring.mdx +++ b/docs/src/pages/docs/installation/configuring.mdx @@ -201,6 +201,25 @@ CUSTOM_SECURITY_MANAGER = CustomSsoSecurityManager ] ``` +### Flask app Configuration Hook + +`FLASK_APP_MUTATOR` is a configuration function that can be provided in your environment, receives +the app object and can alter it in any way. For example, add `FLASK_APP_MUTATOR` into your +`superset_config.py` to setup session cookie expiration time to 24 hours: + +``` +def make_session_permanent(): + ''' + Enable maxAge for the cookie 'session' + ''' + session.permanent = True + +# Set up max age of session to 24 hours +PERMANENT_SESSION_LIFETIME = timedelta(hours=24) +def FLASK_APP_MUTATOR(app: Flask) -> None: + app.before_request_funcs.setdefault(None, []).append(make_session_permanent) +``` + ### Feature Flags To support a diverse set of users, Superset has some features that are not enabled by default. For From adebc0997b574f4b25a9ed9bd79543a021350956 Mon Sep 17 00:00:00 2001 From: Beto Dealmeida Date: Fri, 20 Aug 2021 10:03:31 -0700 Subject: [PATCH 12/22] fix: update table ID in query context on chart import (#16374) * fix: update table ID in query context on chart import * Fix test --- .../charts/commands/importers/v1/__init__.py | 7 +++++ .../charts/commands_tests.py | 28 +++++++++++++++++++ .../fixtures/importexport.py | 1 + 3 files changed, 36 insertions(+) diff --git a/superset/charts/commands/importers/v1/__init__.py b/superset/charts/commands/importers/v1/__init__.py index 0e2b5b3a8adf7..5c2b535a7b34a 100644 --- a/superset/charts/commands/importers/v1/__init__.py +++ b/superset/charts/commands/importers/v1/__init__.py @@ -15,6 +15,7 @@ # specific language governing permissions and limitations # under the License. +import json from typing import Any, Dict, Set from marshmallow import Schema @@ -95,4 +96,10 @@ def _import( } ) config["params"].update({"datasource": dataset.uid}) + if config["query_context"]: + # TODO (betodealmeida): export query_context as object, not string + query_context = json.loads(config["query_context"]) + query_context["datasource"] = {"id": dataset.id, "type": "table"} + config["query_context"] = json.dumps(query_context) + import_chart(session, config, overwrite=overwrite) diff --git a/tests/integration_tests/charts/commands_tests.py b/tests/integration_tests/charts/commands_tests.py index cd6e01f3dc1d4..238a54e29c7f4 100644 --- a/tests/integration_tests/charts/commands_tests.py +++ b/tests/integration_tests/charts/commands_tests.py @@ -191,6 +191,34 @@ def test_import_v1_chart(self): ) assert dataset.table_name == "imported_dataset" assert chart.table == dataset + assert json.loads(chart.query_context) == { + "datasource": {"id": dataset.id, "type": "table"}, + "force": False, + "queries": [ + { + "time_range": " : ", + "filters": [], + "extras": { + "time_grain_sqla": None, + "having": "", + "having_druid": [], + "where": "", + }, + "applied_time_extras": {}, + "columns": [], + "metrics": [], + "annotation_layers": [], + "row_limit": 5000, + "timeseries_limit": 0, + "order_desc": True, + "url_params": {}, + "custom_params": {}, + "custom_form_data": {}, + } + ], + "result_format": "json", + "result_type": "full", + } database = ( db.session.query(Database).filter_by(uuid=database_config["uuid"]).one() diff --git a/tests/integration_tests/fixtures/importexport.py b/tests/integration_tests/fixtures/importexport.py index 951ecf9bb4350..78f643c587af1 100644 --- a/tests/integration_tests/fixtures/importexport.py +++ b/tests/integration_tests/fixtures/importexport.py @@ -444,6 +444,7 @@ }, "viz_type": "deck_path", }, + "query_context": '{"datasource":{"id":12,"type":"table"},"force":false,"queries":[{"time_range":" : ","filters":[],"extras":{"time_grain_sqla":null,"having":"","having_druid":[],"where":""},"applied_time_extras":{},"columns":[],"metrics":[],"annotation_layers":[],"row_limit":5000,"timeseries_limit":0,"order_desc":true,"url_params":{},"custom_params":{},"custom_form_data":{}}],"result_format":"json","result_type":"full"}', "cache_timeout": None, "uuid": "0c23747a-6528-4629-97bf-e4b78d3b9df1", "version": "1.0.0", From 518c3c9ae088f780f1874c63eab416ee1cbb4a61 Mon Sep 17 00:00:00 2001 From: Lyndsi Kay Williams <55605634+lyndsiWilliams@users.noreply.github.com> Date: Fri, 20 Aug 2021 12:08:36 -0500 Subject: [PATCH 13/22] test: Functional RTL for email report modal II (#16148) * Email Report Modal validation testing * Starting RTL testing for email report * Calendar icon now rendering! * Create report testing in dashboard * make linter happy * Fixing weird error * Removed ExploreChartHeader_spec * Fixed dashboard header test * revert changes from merge * Fix tests Co-authored-by: Elizabeth Thompson --- .../spec/fixtures/mockReportState.js | 38 +++++ .../spec/fixtures/mockStateWithoutUser.tsx | 46 ++++++ .../spec/helpers/reducerIndex.ts | 2 + .../components/ExploreChartHeader_spec.jsx | 87 ---------- .../src/components/ReportModal/index.test.tsx | 25 ++- .../src/components/ReportModal/index.tsx | 2 +- .../components/Header/Header.test.tsx | 155 +++++++++++++++++- .../src/reports/actions/reports.js | 3 +- 8 files changed, 263 insertions(+), 95 deletions(-) create mode 100644 superset-frontend/spec/fixtures/mockReportState.js create mode 100644 superset-frontend/spec/fixtures/mockStateWithoutUser.tsx delete mode 100644 superset-frontend/spec/javascripts/explore/components/ExploreChartHeader_spec.jsx diff --git a/superset-frontend/spec/fixtures/mockReportState.js b/superset-frontend/spec/fixtures/mockReportState.js new file mode 100644 index 0000000000000..075af8bfe0962 --- /dev/null +++ b/superset-frontend/spec/fixtures/mockReportState.js @@ -0,0 +1,38 @@ +/** + * 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. + */ +import dashboardInfo from './mockDashboardInfo'; +import { user } from '../javascripts/sqllab/fixtures'; + +export default { + active: true, + creation_method: 'dashboards', + crontab: '0 12 * * 1', + dashboard: dashboardInfo.id, + name: 'Weekly Report', + owners: [user.userId], + recipients: [ + { + recipient_config_json: { + target: user.email, + }, + type: 'Email', + }, + ], + type: 'Report', +}; diff --git a/superset-frontend/spec/fixtures/mockStateWithoutUser.tsx b/superset-frontend/spec/fixtures/mockStateWithoutUser.tsx new file mode 100644 index 0000000000000..bc92df4df75d0 --- /dev/null +++ b/superset-frontend/spec/fixtures/mockStateWithoutUser.tsx @@ -0,0 +1,46 @@ +/** + * 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. + */ +import datasources from 'spec/fixtures/mockDatasource'; +import messageToasts from 'spec/javascripts/messageToasts/mockMessageToasts'; +import { + nativeFiltersInfo, + mockDataMaskInfo, +} from 'spec/javascripts/dashboard/fixtures/mockNativeFilters'; +import chartQueries from 'spec/fixtures/mockChartQueries'; +import { dashboardLayout } from 'spec/fixtures/mockDashboardLayout'; +import dashboardInfo from 'spec/fixtures/mockDashboardInfo'; +import { emptyFilters } from 'spec/fixtures/mockDashboardFilters'; +import dashboardState from 'spec/fixtures/mockDashboardState'; +import { sliceEntitiesForChart } from 'spec/fixtures/mockSliceEntities'; +import reports from 'spec/fixtures/mockReportState'; + +export default { + datasources, + sliceEntities: sliceEntitiesForChart, + charts: chartQueries, + nativeFilters: nativeFiltersInfo, + dataMask: mockDataMaskInfo, + dashboardInfo, + dashboardFilters: emptyFilters, + dashboardState, + dashboardLayout, + messageToasts, + impressionId: 'mock_impression_id', + reports, +}; diff --git a/superset-frontend/spec/helpers/reducerIndex.ts b/superset-frontend/spec/helpers/reducerIndex.ts index e84b7f6f5119a..113368389509a 100644 --- a/superset-frontend/spec/helpers/reducerIndex.ts +++ b/superset-frontend/spec/helpers/reducerIndex.ts @@ -30,6 +30,7 @@ import saveModal from 'src/explore/reducers/saveModalReducer'; import explore from 'src/explore/reducers/exploreReducer'; import sqlLab from 'src/SqlLab/reducers/sqlLab'; import localStorageUsageInKilobytes from 'src/SqlLab/reducers/localStorageUsage'; +import reports from 'src/reports/reducers/reports'; const impressionId = (state = '') => state; @@ -53,5 +54,6 @@ export default { explore, sqlLab, localStorageUsageInKilobytes, + reports, common: () => common, }; diff --git a/superset-frontend/spec/javascripts/explore/components/ExploreChartHeader_spec.jsx b/superset-frontend/spec/javascripts/explore/components/ExploreChartHeader_spec.jsx deleted file mode 100644 index c6eda704e16ec..0000000000000 --- a/superset-frontend/spec/javascripts/explore/components/ExploreChartHeader_spec.jsx +++ /dev/null @@ -1,87 +0,0 @@ -/** - * 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. - */ -import React from 'react'; -import { shallow } from 'enzyme'; - -import { ExploreChartHeader } from 'src/explore/components/ExploreChartHeader'; -import ExploreActionButtons from 'src/explore/components/ExploreActionButtons'; -import EditableTitle from 'src/components/EditableTitle'; - -const saveSliceStub = jest.fn(); -const updateChartTitleStub = jest.fn(); -const fetchUISpecificReportStub = jest.fn(); -const mockProps = { - actions: { - saveSlice: saveSliceStub, - updateChartTitle: updateChartTitleStub, - }, - can_overwrite: true, - can_download: true, - isStarred: true, - slice: { - form_data: { - viz_type: 'line', - }, - }, - table_name: 'foo', - form_data: { - viz_type: 'table', - }, - user: { - createdOn: '2021-04-27T18:12:38.952304', - email: 'admin', - firstName: 'admin', - isActive: true, - lastName: 'admin', - permissions: {}, - roles: { Admin: Array(173) }, - userId: 1, - username: 'admin', - }, - timeout: 1000, - chart: { - id: 0, - queryResponse: {}, - }, - fetchUISpecificReport: fetchUISpecificReportStub, - chartHeight: '30px', -}; - -describe('ExploreChartHeader', () => { - let wrapper; - beforeEach(() => { - wrapper = shallow(); - }); - - it('is valid', () => { - expect(React.isValidElement()).toBe( - true, - ); - }); - - it('renders', () => { - expect(wrapper.find(EditableTitle)).toExist(); - expect(wrapper.find(ExploreActionButtons)).toExist(); - }); - - it('should update title but not save', () => { - const editableTitle = wrapper.find(EditableTitle); - expect(editableTitle.props().onSaveTitle).toBe(updateChartTitleStub); - }); -}); diff --git a/superset-frontend/src/components/ReportModal/index.test.tsx b/superset-frontend/src/components/ReportModal/index.test.tsx index 99b1eadcc4970..44e3d0ef65c51 100644 --- a/superset-frontend/src/components/ReportModal/index.test.tsx +++ b/superset-frontend/src/components/ReportModal/index.test.tsx @@ -55,13 +55,17 @@ describe('Email Report Modal', () => { (featureFlag: FeatureFlag) => featureFlag === FeatureFlag.ALERT_REPORTS, ); }); + + beforeEach(() => { + render(, { useRedux: true }); + }); + afterAll(() => { // @ts-ignore isFeatureEnabledMock.restore(); }); - it('inputs respond correctly', () => { - render(, { useRedux: true }); + it('inputs respond correctly', () => { // ----- Report name textbox // Initial value const reportNameTextbox = screen.getByTestId('report-name-test'); @@ -86,4 +90,21 @@ describe('Email Report Modal', () => { const crontabInputs = screen.getAllByRole('combobox'); expect(crontabInputs).toHaveLength(5); }); + + it('does not allow user to create a report without a name', () => { + // Grab name textbox and add button + const reportNameTextbox = screen.getByTestId('report-name-test'); + const addButton = screen.getByRole('button', { name: /add/i }); + + // Add button should be enabled while name textbox has text + expect(reportNameTextbox).toHaveDisplayValue('Weekly Report'); + expect(addButton).toBeEnabled(); + + // Clear the text from the name textbox + userEvent.clear(reportNameTextbox); + + // Add button should now be disabled, blocking user from creation + expect(reportNameTextbox).toHaveDisplayValue(''); + expect(addButton).toBeDisabled(); + }); }); diff --git a/superset-frontend/src/components/ReportModal/index.tsx b/superset-frontend/src/components/ReportModal/index.tsx index 292d3b7414b2e..e24d1d756c63f 100644 --- a/superset-frontend/src/components/ReportModal/index.tsx +++ b/superset-frontend/src/components/ReportModal/index.tsx @@ -53,7 +53,7 @@ import { StyledRadioGroup, } from './styles'; -interface ReportObject { +export interface ReportObject { id?: number; active: boolean; crontab: string; diff --git a/superset-frontend/src/dashboard/components/Header/Header.test.tsx b/superset-frontend/src/dashboard/components/Header/Header.test.tsx index 68af9380e9b42..8a9ecdb5be46f 100644 --- a/superset-frontend/src/dashboard/components/Header/Header.test.tsx +++ b/superset-frontend/src/dashboard/components/Header/Header.test.tsx @@ -19,7 +19,12 @@ import React from 'react'; import { render, screen, fireEvent } from 'spec/helpers/testing-library'; import userEvent from '@testing-library/user-event'; +import sinon from 'sinon'; import fetchMock from 'fetch-mock'; +import * as actions from 'src/reports/actions/reports'; +import * as featureFlags from 'src/featureFlags'; +import { ReportObject } from 'src/components/ReportModal'; +import mockState from 'spec/fixtures/mockStateWithoutUser'; import { HeaderProps } from './types'; import Header from '.'; @@ -40,15 +45,16 @@ const createProps = () => ({ }, user: { createdOn: '2021-04-27T18:12:38.952304', - email: 'admin', + email: 'admin@test.com', firstName: 'admin', isActive: true, lastName: 'admin', permissions: {}, - roles: { Admin: Array(173) }, + roles: { Admin: [['menu_access', 'Manage']] }, userId: 1, username: 'admin', }, + reports: {}, dashboardTitle: 'Dashboard Title', charts: {}, layout: {}, @@ -107,8 +113,10 @@ const redoProps = { redoLength: 1, }; +const REPORT_ENDPOINT = 'glob:*/api/v1/report*'; + fetchMock.get('glob:*/csstemplateasyncmodelview/api/read', {}); -fetchMock.get('glob:*/api/v1/report*', {}); +fetchMock.get(REPORT_ENDPOINT, {}); function setup(props: HeaderProps) { return ( @@ -315,3 +323,144 @@ test('should refresh the charts', async () => { userEvent.click(screen.getByText('Refresh dashboard')); expect(mockedProps.onRefresh).toHaveBeenCalledTimes(1); }); + +describe('Email Report Modal', () => { + let isFeatureEnabledMock: any; + let dispatch: any; + + beforeEach(async () => { + isFeatureEnabledMock = jest + .spyOn(featureFlags, 'isFeatureEnabled') + .mockImplementation(() => true); + dispatch = sinon.spy(); + }); + + afterAll(() => { + isFeatureEnabledMock.mockRestore(); + }); + + it('creates a new email report', async () => { + // ---------- Render/value setup ---------- + const mockedProps = createProps(); + render(setup(mockedProps), { useRedux: true }); + + const reportValues = { + active: true, + creation_method: 'dashboards', + crontab: '0 12 * * 1', + dashboard: mockedProps.dashboardInfo.id, + name: 'Weekly Report', + owners: [mockedProps.user.userId], + recipients: [ + { + recipient_config_json: { + target: mockedProps.user.email, + }, + type: 'Email', + }, + ], + type: 'Report', + }; + // This is needed to structure the reportValues to match the fetchMock return + const stringyReportValues = `{"active":true,"creation_method":"dashboards","crontab":"0 12 * * 1","dashboard":${mockedProps.dashboardInfo.id},"name":"Weekly Report","owners":[${mockedProps.user.userId}],"recipients":[{"recipient_config_json":{"target":"${mockedProps.user.email}"},"type":"Email"}],"type":"Report"}`; + // Watch for report POST + fetchMock.post(REPORT_ENDPOINT, reportValues); + + screen.logTestingPlaygroundURL(); + // ---------- Begin tests ---------- + // Click calendar icon to open email report modal + const emailReportModalButton = screen.getByRole('button', { + name: /schedule email report/i, + }); + userEvent.click(emailReportModalButton); + + // Click "Add" button to create a new email report + const addButton = screen.getByRole('button', { name: /add/i }); + userEvent.click(addButton); + + // Mock addReport from Redux + const makeRequest = () => { + const request = actions.addReport(reportValues as ReportObject); + return request(dispatch); + }; + + return makeRequest().then(() => { + // 🐞 ----- There are 2 POST calls at this point ----- 🐞 + + // addReport's mocked POST return should match the mocked values + expect(fetchMock.lastOptions()?.body).toEqual(stringyReportValues); + // Dispatch should be called once for addReport + expect(dispatch.callCount).toBe(2); + const reportCalls = fetchMock.calls(REPORT_ENDPOINT); + expect(reportCalls).toHaveLength(2); + }); + }); + + it('edits an existing email report', async () => { + // TODO (lyndsiWilliams): This currently does not work, see TODOs below + // The modal does appear with the edit title, but the PUT call is not registering + + // ---------- Render/value setup ---------- + const mockedProps = createProps(); + const editedReportValues = { + active: true, + creation_method: 'dashboards', + crontab: '0 12 * * 1', + dashboard: mockedProps.dashboardInfo.id, + name: 'Weekly Report edit', + owners: [mockedProps.user.userId], + recipients: [ + { + recipient_config_json: { + target: mockedProps.user.email, + }, + type: 'Email', + }, + ], + type: 'Report', + }; + + // getMockStore({ reports: reportValues }); + render(setup(mockedProps), { + useRedux: true, + initialState: mockState, + }); + // TODO (lyndsiWilliams): currently fetchMock detects this PUT + // address as 'glob:*/api/v1/report/undefined', is not detected + // on fetchMock.calls() + fetchMock.put(`glob:*/api/v1/report*`, editedReportValues); + + // Mock fetchUISpecificReport from Redux + // const makeFetchRequest = () => { + // const request = actions.fetchUISpecificReport( + // mockedProps.user.userId, + // 'dashboard_id', + // 'dashboards', + // mockedProps.dashboardInfo.id, + // ); + // return request(dispatch); + // }; + + // makeFetchRequest(); + + dispatch(actions.setReport(editedReportValues)); + + // ---------- Begin tests ---------- + // Click calendar icon to open email report modal + const emailReportModalButton = screen.getByRole('button', { + name: /schedule email report/i, + }); + userEvent.click(emailReportModalButton); + + const nameTextbox = screen.getByTestId('report-name-test'); + userEvent.type(nameTextbox, ' edit'); + + const saveButton = screen.getByRole('button', { name: /save/i }); + userEvent.click(saveButton); + + // TODO (lyndsiWilliams): There should be a report in state at this porint, + // which would render the HeaderReportActionsDropDown under the calendar icon + // BLOCKER: I cannot get report to populate, as its data is handled through redux + expect.anything(); + }); +}); diff --git a/superset-frontend/src/reports/actions/reports.js b/superset-frontend/src/reports/actions/reports.js index 55cea9dbaa7c9..7b3bc814ca0e8 100644 --- a/superset-frontend/src/reports/actions/reports.js +++ b/superset-frontend/src/reports/actions/reports.js @@ -98,7 +98,7 @@ const structureFetchAction = (dispatch, getState) => { export const ADD_REPORT = 'ADD_REPORT'; -export const addReport = report => dispatch => { +export const addReport = report => dispatch => SupersetClient.post({ endpoint: `/api/v1/report/`, jsonPayload: report, @@ -118,7 +118,6 @@ export const addReport = report => dispatch => { ), ); }); -}; export const EDIT_REPORT = 'EDIT_REPORT'; From 649e5096073eea929760312d8fa639ac7c9bf218 Mon Sep 17 00:00:00 2001 From: Beto Dealmeida Date: Fri, 20 Aug 2021 12:03:05 -0700 Subject: [PATCH 14/22] fix: import dashboard w/o metadata (#16360) --- superset/dashboards/commands/importers/v1/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/superset/dashboards/commands/importers/v1/__init__.py b/superset/dashboards/commands/importers/v1/__init__.py index 6e17aef9e5431..1720e01ab8bcc 100644 --- a/superset/dashboards/commands/importers/v1/__init__.py +++ b/superset/dashboards/commands/importers/v1/__init__.py @@ -67,7 +67,9 @@ def _import( for file_name, config in configs.items(): if file_name.startswith("dashboards/"): chart_uuids.update(find_chart_uuids(config["position"])) - dataset_uuids.update(find_native_filter_datasets(config["metadata"])) + dataset_uuids.update( + find_native_filter_datasets(config.get("metadata", {})) + ) # discover datasets associated with charts for file_name, config in configs.items(): From c5081991fcf8b4fd747a15d6973c9d35dd5f35b1 Mon Sep 17 00:00:00 2001 From: Phillip Kelley-Dotson Date: Fri, 20 Aug 2021 15:59:53 -0700 Subject: [PATCH 15/22] initial commit (#16380) --- superset-frontend/src/datasource/DatasourceEditor.jsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/superset-frontend/src/datasource/DatasourceEditor.jsx b/superset-frontend/src/datasource/DatasourceEditor.jsx index 4bf2690cfa8fd..e11b8310bb75c 100644 --- a/superset-frontend/src/datasource/DatasourceEditor.jsx +++ b/superset-frontend/src/datasource/DatasourceEditor.jsx @@ -324,15 +324,19 @@ class DatasourceEditor extends React.PureComponent { datasource: { ...props.datasource, metrics: props.datasource.metrics?.map(metric => { + const { + certified_by: certifiedByMetric, + certification_details: certificationDetails, + } = metric; const { certification: { details, certified_by: certifiedBy } = {}, warning_markdown: warningMarkdown, } = JSON.parse(metric.extra || '{}') || {}; return { ...metric, - certification_details: details || '', + certification_details: certificationDetails || details, warning_markdown: warningMarkdown || '', - certified_by: certifiedBy, + certified_by: certifiedBy || certifiedByMetric, }; }), }, @@ -935,7 +939,6 @@ class DatasourceEditor extends React.PureComponent { const { datasource } = this.state; const { metrics } = datasource; const sortedMetrics = metrics?.length ? this.sortMetrics(metrics) : []; - return ( Date: Fri, 20 Aug 2021 16:08:54 -0700 Subject: [PATCH 16/22] fix: big number default date format (#16383) --- superset-frontend/package-lock.json | 14 +++++++------- superset-frontend/package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/superset-frontend/package-lock.json b/superset-frontend/package-lock.json index 8fa99d0229fa2..1ef93bce0e862 100644 --- a/superset-frontend/package-lock.json +++ b/superset-frontend/package-lock.json @@ -36,7 +36,7 @@ "@superset-ui/legacy-plugin-chart-sunburst": "^0.17.85", "@superset-ui/legacy-plugin-chart-treemap": "^0.17.85", "@superset-ui/legacy-plugin-chart-world-map": "^0.17.85", - "@superset-ui/legacy-preset-chart-big-number": "^0.17.85", + "@superset-ui/legacy-preset-chart-big-number": "^0.17.86", "@superset-ui/legacy-preset-chart-deckgl": "^0.4.11", "@superset-ui/legacy-preset-chart-nvd3": "^0.17.85", "@superset-ui/plugin-chart-echarts": "^0.17.85", @@ -12480,9 +12480,9 @@ } }, "node_modules/@superset-ui/legacy-preset-chart-big-number": { - "version": "0.17.85", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-preset-chart-big-number/-/legacy-preset-chart-big-number-0.17.85.tgz", - "integrity": "sha512-Txuk8L/flifPESHWNpZ8vwXinHAPCXXEp2fZdB/6CRyB6TVfMs2RmKqnEyzO5xBGgqeTfdH6xeuVn+8Hfdcvrg==", + "version": "0.17.86", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-preset-chart-big-number/-/legacy-preset-chart-big-number-0.17.86.tgz", + "integrity": "sha512-b9TZ7VJ3IK+ii6VDllfOCDujLR++rYNXcnNW/XgCZP9+ZwbHBIzGqC7APdUid7qDJM/PZwN6USXSnYf5MIYPLw==", "dependencies": { "@data-ui/xy-chart": "^0.0.84", "@superset-ui/chart-controls": "0.17.85", @@ -61935,9 +61935,9 @@ } }, "@superset-ui/legacy-preset-chart-big-number": { - "version": "0.17.85", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-preset-chart-big-number/-/legacy-preset-chart-big-number-0.17.85.tgz", - "integrity": "sha512-Txuk8L/flifPESHWNpZ8vwXinHAPCXXEp2fZdB/6CRyB6TVfMs2RmKqnEyzO5xBGgqeTfdH6xeuVn+8Hfdcvrg==", + "version": "0.17.86", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-preset-chart-big-number/-/legacy-preset-chart-big-number-0.17.86.tgz", + "integrity": "sha512-b9TZ7VJ3IK+ii6VDllfOCDujLR++rYNXcnNW/XgCZP9+ZwbHBIzGqC7APdUid7qDJM/PZwN6USXSnYf5MIYPLw==", "requires": { "@data-ui/xy-chart": "^0.0.84", "@superset-ui/chart-controls": "0.17.85", diff --git a/superset-frontend/package.json b/superset-frontend/package.json index 0ffeb44658ca2..20e1afc52bb4d 100644 --- a/superset-frontend/package.json +++ b/superset-frontend/package.json @@ -88,7 +88,7 @@ "@superset-ui/legacy-plugin-chart-sunburst": "^0.17.85", "@superset-ui/legacy-plugin-chart-treemap": "^0.17.85", "@superset-ui/legacy-plugin-chart-world-map": "^0.17.85", - "@superset-ui/legacy-preset-chart-big-number": "^0.17.85", + "@superset-ui/legacy-preset-chart-big-number": "^0.17.86", "@superset-ui/legacy-preset-chart-deckgl": "^0.4.11", "@superset-ui/legacy-preset-chart-nvd3": "^0.17.85", "@superset-ui/plugin-chart-echarts": "^0.17.85", From 3faf653e5f438b0eaf70aab94df25720e2c50afd Mon Sep 17 00:00:00 2001 From: Phillip Kelley-Dotson Date: Fri, 20 Aug 2021 16:46:55 -0700 Subject: [PATCH 17/22] initial commit (#16366) --- .../explore/components/controls/CollectionControl/index.jsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/superset-frontend/src/explore/components/controls/CollectionControl/index.jsx b/superset-frontend/src/explore/components/controls/CollectionControl/index.jsx index 8cadbf27fb09a..384d23ed03a52 100644 --- a/superset-frontend/src/explore/components/controls/CollectionControl/index.jsx +++ b/superset-frontend/src/explore/components/controls/CollectionControl/index.jsx @@ -80,8 +80,9 @@ class CollectionControl extends React.Component { } onChange(i, value) { - Object.assign(this.props.value[i], value); - this.props.onChange(this.props.value); + const newValue = [...this.props.value]; + newValue[i] = { ...this.props.value[i], ...value }; + this.props.onChange(newValue); } onAdd() { From 970d7627797fe2b96f797e413b27f09484b80c7b Mon Sep 17 00:00:00 2001 From: cccs-tom <59839056+cccs-tom@users.noreply.github.com> Date: Sat, 21 Aug 2021 20:53:13 -0400 Subject: [PATCH 18/22] feat: Add extraVolumes and extraVolumeMounts to all main containers (#16361) --- helm/superset/Chart.yaml | 2 +- helm/superset/templates/deployment-beat.yaml | 6 ++++++ helm/superset/templates/deployment-worker.yaml | 6 ++++++ helm/superset/templates/deployment.yaml | 6 ++++++ helm/superset/templates/init-job.yaml | 6 ++++++ helm/superset/values.yaml | 16 ++++++++++++++++ 6 files changed, 41 insertions(+), 1 deletion(-) diff --git a/helm/superset/Chart.yaml b/helm/superset/Chart.yaml index 8f39c30543620..30a409e083d8e 100644 --- a/helm/superset/Chart.yaml +++ b/helm/superset/Chart.yaml @@ -22,7 +22,7 @@ maintainers: - name: craig-rueda email: craig@craigrueda.com url: https://github.com/craig-rueda -version: 0.3.5 +version: 0.3.6 dependencies: - name: postgresql version: 10.2.0 diff --git a/helm/superset/templates/deployment-beat.yaml b/helm/superset/templates/deployment-beat.yaml index e5705fa08b097..2775d287498c8 100644 --- a/helm/superset/templates/deployment-beat.yaml +++ b/helm/superset/templates/deployment-beat.yaml @@ -86,6 +86,9 @@ spec: - name: superset-config mountPath: {{ .Values.configMountPath | quote }} readOnly: true + {{- with .Values.extraVolumeMounts }} + {{- tpl (toYaml .) $ | nindent 12 -}} + {{- end }} resources: {{ toYaml .Values.resources | indent 12 }} {{- with .Values.nodeSelector }} @@ -108,4 +111,7 @@ spec: - name: superset-config secret: secretName: {{ tpl .Values.configFromSecret . }} + {{- with .Values.extraVolumes }} + {{- tpl (toYaml .) $ | nindent 8 -}} + {{- end }} {{- end -}} diff --git a/helm/superset/templates/deployment-worker.yaml b/helm/superset/templates/deployment-worker.yaml index c228b35dc96d1..8bb2cc81c15d0 100644 --- a/helm/superset/templates/deployment-worker.yaml +++ b/helm/superset/templates/deployment-worker.yaml @@ -87,6 +87,9 @@ spec: - name: superset-config mountPath: {{ .Values.configMountPath | quote }} readOnly: true + {{- with .Values.extraVolumeMounts }} + {{- tpl (toYaml .) $ | nindent 12 -}} + {{- end }} resources: {{ toYaml .Values.resources | indent 12 }} {{- with .Values.nodeSelector }} @@ -109,3 +112,6 @@ spec: - name: superset-config secret: secretName: {{ tpl .Values.configFromSecret . }} + {{- with .Values.extraVolumes }} + {{- tpl (toYaml .) $ | nindent 8 -}} + {{- end }} diff --git a/helm/superset/templates/deployment.yaml b/helm/superset/templates/deployment.yaml index 919078299525f..ec6b4f453ea58 100644 --- a/helm/superset/templates/deployment.yaml +++ b/helm/superset/templates/deployment.yaml @@ -95,6 +95,9 @@ spec: mountPath: {{ .Values.extraConfigMountPath | quote }} readOnly: true {{- end }} + {{- with .Values.extraVolumeMounts }} + {{- tpl (toYaml .) $ | nindent 12 -}} + {{- end }} ports: - name: http containerPort: {{ .Values.service.port }} @@ -127,3 +130,6 @@ spec: configMap: name: {{ template "superset.fullname" . }}-extra-config {{- end }} + {{- with .Values.extraVolumes }} + {{- tpl (toYaml .) $ | nindent 8 -}} + {{- end }} diff --git a/helm/superset/templates/init-job.yaml b/helm/superset/templates/init-job.yaml index 45ab2235b7a5f..fb30abc8cef23 100644 --- a/helm/superset/templates/init-job.yaml +++ b/helm/superset/templates/init-job.yaml @@ -60,6 +60,9 @@ spec: mountPath: {{ .Values.extraConfigMountPath | quote }} readOnly: true {{- end }} + {{- with .Values.extraVolumeMounts }} + {{- tpl (toYaml .) $ | nindent 10 -}} + {{- end }} command: {{ tpl (toJson .Values.init.command) . }} resources: {{ toYaml .Values.init.resources | indent 10 }} @@ -76,5 +79,8 @@ spec: configMap: name: {{ template "superset.fullname" . }}-extra-config {{- end }} + {{- with .Values.extraVolumes }} + {{- tpl (toYaml .) $ | nindent 8 -}} + {{- end }} restartPolicy: Never {{- end }} diff --git a/helm/superset/values.yaml b/helm/superset/values.yaml index c6ad3e10ff33a..c8577774afb8e 100644 --- a/helm/superset/values.yaml +++ b/helm/superset/values.yaml @@ -82,6 +82,22 @@ extraConfigs: {} extraSecrets: {} +extraVolumes: [] + # - name: customConfig + # configMap: + # name: '{{ template "superset.fullname" . }}-custom-config' + # - name: additionalSecret + # secret: + # secretName: my-secret + # defaultMode: 0600 + +extraVolumeMounts: [] + # - name: customConfig + # mountPath: /mnt/config + # readOnly: true + # - name: additionalSecret: + # mountPath: /mnt/secret + # A dictionary of overrides to append at the end of superset_config.py - the name does not matter # WARNING: the order is not guaranteed configOverrides: {} From 7e4c940314ab6b09a89cb6938b02cf5d3028ad8a Mon Sep 17 00:00:00 2001 From: John Bodley <4567245+john-bodley@users.noreply.github.com> Date: Mon, 23 Aug 2021 08:58:41 -0700 Subject: [PATCH 19/22] chore(pylint): Enable useless-suppression check (#16388) Co-authored-by: John Bodley --- .pylintrc | 3 +- .../charts/commands/importers/dispatcher.py | 1 - superset/charts/post_processing.py | 4 +-- superset/cli.py | 4 +-- superset/commands/importers/v1/__init__.py | 1 - superset/commands/importers/v1/examples.py | 2 +- superset/common/query_object.py | 5 ++-- superset/common/tags.py | 2 +- superset/config.py | 21 +++++-------- superset/connectors/connector_registry.py | 4 +-- superset/connectors/sqla/models.py | 8 ++--- superset/connectors/sqla/views.py | 14 +++------ superset/dashboards/api.py | 6 ++-- .../commands/importers/dispatcher.py | 1 - superset/dashboards/commands/importers/v0.py | 2 +- superset/dashboards/schemas.py | 2 -- superset/databases/api.py | 14 +++------ superset/databases/commands/create.py | 2 +- .../commands/importers/dispatcher.py | 1 - .../databases/commands/test_connection.py | 2 +- superset/databases/commands/validate.py | 2 +- .../datasets/commands/importers/dispatcher.py | 1 - .../datasets/commands/importers/v1/utils.py | 2 -- superset/db_engine_specs/base.py | 6 ++-- superset/db_engine_specs/gsheets.py | 3 +- superset/db_engine_specs/presto.py | 10 +++---- superset/db_engine_specs/sqlite.py | 1 - superset/db_engine_specs/trino.py | 5 +--- superset/errors.py | 3 +- superset/examples/birth_names.py | 1 - superset/examples/country_map.py | 2 +- superset/examples/flights.py | 6 ++-- superset/examples/long_lat.py | 2 +- superset/examples/world_bank.py | 2 +- superset/extensions.py | 4 +-- superset/initialization/__init__.py | 4 +-- superset/jinja_context.py | 6 ++-- superset/models/core.py | 6 ++-- superset/models/dashboard.py | 3 -- superset/models/reports.py | 7 ++--- .../commands/importers/dispatcher.py | 1 - superset/reports/api.py | 2 +- superset/reports/commands/execute.py | 6 ++-- superset/result_set.py | 2 +- superset/security/manager.py | 7 ++--- superset/sql_parse.py | 8 ++--- superset/tasks/schedules.py | 4 +-- superset/utils/async_query_manager.py | 6 +--- superset/utils/core.py | 24 ++++++--------- superset/utils/date_parser.py | 14 ++++----- superset/utils/log.py | 2 +- superset/utils/profiler.py | 1 - superset/views/access_requests.py | 4 +-- superset/views/alerts.py | 4 +-- superset/views/annotations.py | 6 ++-- superset/views/api.py | 5 ++-- superset/views/base.py | 12 +++----- superset/views/base_api.py | 19 +++++------- superset/views/core.py | 30 +++++++------------ superset/views/css_templates.py | 4 +-- superset/views/database/views.py | 2 +- superset/views/log/views.py | 2 +- superset/views/redirects.py | 4 +-- superset/views/schedules.py | 4 +-- superset/views/sql_lab.py | 4 +-- superset/viz.py | 2 +- 66 files changed, 128 insertions(+), 226 deletions(-) diff --git a/.pylintrc b/.pylintrc index 96329041731ec..e3715334d1458 100644 --- a/.pylintrc +++ b/.pylintrc @@ -70,7 +70,8 @@ confidence= # either give multiple identifier separated by comma (,) or put this option # multiple time (only on the command line, not in the configuration file where # it should appear only once). See also the "--disable" option for examples. -#enable= +enable= + useless-suppression, # Disable the message, report, category or checker with the given id(s). You # can either give multiple identifiers separated by comma (,) or put this diff --git a/superset/charts/commands/importers/dispatcher.py b/superset/charts/commands/importers/dispatcher.py index ff348898ea49a..afeb9c2820c88 100644 --- a/superset/charts/commands/importers/dispatcher.py +++ b/superset/charts/commands/importers/dispatcher.py @@ -40,7 +40,6 @@ class ImportChartsCommand(BaseCommand): until it finds one that matches. """ - # pylint: disable=unused-argument def __init__(self, contents: Dict[str, str], *args: Any, **kwargs: Any): self.contents = contents self.args = args diff --git a/superset/charts/post_processing.py b/superset/charts/post_processing.py index 3ad0a8570c6fe..cb65c499aff39 100644 --- a/superset/charts/post_processing.py +++ b/superset/charts/post_processing.py @@ -210,9 +210,7 @@ def list_unique_values(series: pd.Series) -> str: } -def pivot_table_v2( # pylint: disable=too-many-branches - df: pd.DataFrame, form_data: Dict[str, Any] -) -> pd.DataFrame: +def pivot_table_v2(df: pd.DataFrame, form_data: Dict[str, Any]) -> pd.DataFrame: """ Pivot table v2. """ diff --git a/superset/cli.py b/superset/cli.py index 6fa478efb5e49..b6bb80dd5a09c 100755 --- a/superset/cli.py +++ b/superset/cli.py @@ -82,7 +82,7 @@ def superset() -> None: """This is a management script for the Superset application.""" @app.shell_context_processor - def make_shell_context() -> Dict[str, Any]: # pylint: disable=unused-variable + def make_shell_context() -> Dict[str, Any]: return dict(app=app, db=db) @@ -297,7 +297,6 @@ def export_dashboards(dashboard_file: Optional[str] = None) -> None: "the exception traceback in the log" ) - # pylint: disable=too-many-locals @superset.command() @with_appcontext @click.option( @@ -419,7 +418,6 @@ def export_dashboards( with open(dashboard_file, "w") as data_stream: data_stream.write(data) - # pylint: disable=too-many-locals @superset.command() @with_appcontext @click.option( diff --git a/superset/commands/importers/v1/__init__.py b/superset/commands/importers/v1/__init__.py index 0286735de6a01..5440470a16331 100644 --- a/superset/commands/importers/v1/__init__.py +++ b/superset/commands/importers/v1/__init__.py @@ -70,7 +70,6 @@ def run(self) -> None: db.session.rollback() raise self.import_error() from ex - # pylint: disable=too-many-branches def validate(self) -> None: exceptions: List[ValidationError] = [] diff --git a/superset/commands/importers/v1/examples.py b/superset/commands/importers/v1/examples.py index 7091f88419e8e..66563f856b620 100644 --- a/superset/commands/importers/v1/examples.py +++ b/superset/commands/importers/v1/examples.py @@ -85,7 +85,7 @@ def _get_uuids(cls) -> Set[str]: | ImportDashboardsCommand._get_uuids() ) - # pylint: disable=too-many-locals, arguments-differ, too-many-branches + # pylint: disable=too-many-locals, arguments-differ @staticmethod def _import( session: Session, diff --git a/superset/common/query_object.py b/superset/common/query_object.py index 4d63b118db1bf..bdf5f89e964db 100644 --- a/superset/common/query_object.py +++ b/superset/common/query_object.py @@ -14,7 +14,6 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. -# pylint: disable=R import logging from datetime import datetime, timedelta from typing import Any, Dict, List, NamedTuple, Optional @@ -66,7 +65,7 @@ class DeprecatedField(NamedTuple): ) -class QueryObject: +class QueryObject: # pylint: disable=too-many-instance-attributes """ The query object's schema matches the interfaces of DB connectors like sqla and druid. The query objects are constructed on the client. @@ -99,7 +98,7 @@ class QueryObject: is_rowcount: bool time_offsets: List[str] - def __init__( + def __init__( # pylint: disable=too-many-arguments,too-many-locals self, datasource: Optional[DatasourceDict] = None, result_type: Optional[ChartDataResultType] = None, diff --git a/superset/common/tags.py b/superset/common/tags.py index d1b4114441173..74c882cf92f69 100644 --- a/superset/common/tags.py +++ b/superset/common/tags.py @@ -79,7 +79,7 @@ def add_types(engine: Engine, metadata: Metadata) -> None: # add a tag for each object type insert = tag.insert() - for type_ in ObjectTypes.__members__: # pylint: disable=not-an-iterable + for type_ in ObjectTypes.__members__: try: engine.execute(insert, name=f"type:{type_}", type=TagTypes.type) except IntegrityError: diff --git a/superset/config.py b/superset/config.py index f18fcca8a863a..6e8c341ec789d 100644 --- a/superset/config.py +++ b/superset/config.py @@ -38,9 +38,7 @@ from flask_appbuilder.security.manager import AUTH_DB from pandas.io.parsers import STR_NA_VALUES -from superset.jinja_context import ( # pylint: disable=unused-import - BaseTemplateProcessor, -) +from superset.jinja_context import BaseTemplateProcessor from superset.stats_logger import DummyStatsLogger from superset.typing import CacheConfig from superset.utils.core import is_test, parse_boolean_string @@ -51,12 +49,9 @@ logger = logging.getLogger(__name__) if TYPE_CHECKING: - from flask_appbuilder.security.sqla import models # pylint: disable=unused-import - - from superset.connectors.sqla.models import ( # pylint: disable=unused-import - SqlaTable, - ) - from superset.models.core import Database # pylint: disable=unused-import + from flask_appbuilder.security.sqla import models + from superset.connectors.sqla.models import SqlaTable + from superset.models.core import Database # Realtime stats logger, a StatsD implementation exists STATS_LOGGER = DummyStatsLogger() @@ -96,9 +91,7 @@ def _try_json_readversion(filepath: str) -> Optional[str]: return None -def _try_json_readsha( # pylint: disable=unused-argument - filepath: str, length: int -) -> Optional[str]: +def _try_json_readsha(filepath: str, length: int) -> Optional[str]: try: with open(filepath, "r") as f: return json.load(f).get("GIT_SHA")[:length] @@ -928,7 +921,7 @@ def CSV_TO_HIVE_UPLOAD_DIRECTORY_FUNC( HIVE_POLL_INTERVAL = int(timedelta(seconds=5).total_seconds()) # Interval between consecutive polls when using Presto Engine -# See here: https://github.com/dropbox/PyHive/blob/8eb0aeab8ca300f3024655419b93dad926c1a351/pyhive/presto.py#L93 # pylint: disable=line-too-long +# See here: https://github.com/dropbox/PyHive/blob/8eb0aeab8ca300f3024655419b93dad926c1a351/pyhive/presto.py#L93 # pylint: disable=line-too-long,useless-suppression PRESTO_POLL_INTERVAL = int(timedelta(seconds=1).total_seconds()) # Allow for javascript controls components @@ -1266,7 +1259,7 @@ def CSV_TO_HIVE_UPLOAD_DIRECTORY_FUNC( elif importlib.util.find_spec("superset_config") and not is_test(): try: import superset_config # pylint: disable=import-error - from superset_config import * # type: ignore # pylint: disable=import-error,wildcard-import,unused-wildcard-import + from superset_config import * # type: ignore # pylint: disable=import-error,wildcard-import print(f"Loaded your LOCAL configuration at [{superset_config.__file__}]") except Exception: diff --git a/superset/connectors/connector_registry.py b/superset/connectors/connector_registry.py index 5f40644dca5db..06816fa53049f 100644 --- a/superset/connectors/connector_registry.py +++ b/superset/connectors/connector_registry.py @@ -75,8 +75,8 @@ def get_all_datasources(cls, session: Session) -> List["BaseDatasource"]: return datasources @classmethod - def get_datasource_by_id( # pylint: disable=too-many-arguments - cls, session: Session, datasource_id: int, + def get_datasource_by_id( + cls, session: Session, datasource_id: int ) -> "BaseDatasource": """ Find a datasource instance based on the unique id. diff --git a/superset/connectors/sqla/models.py b/superset/connectors/sqla/models.py index 163648f92ce94..14957f9364a6d 100644 --- a/superset/connectors/sqla/models.py +++ b/superset/connectors/sqla/models.py @@ -19,7 +19,7 @@ import logging import re from collections import defaultdict, OrderedDict -from dataclasses import dataclass, field # pylint: disable=wrong-import-order +from dataclasses import dataclass, field from datetime import datetime, timedelta from typing import ( Any, @@ -471,9 +471,7 @@ def data(self) -> Dict[str, Any]: ) -class SqlaTable( # pylint: disable=too-many-public-methods,too-many-instance-attributes - Model, BaseDatasource -): +class SqlaTable(Model, BaseDatasource): # pylint: disable=too-many-public-methods """An ORM object for SqlAlchemy table references""" @@ -1290,7 +1288,7 @@ def get_sqla_query( # pylint: disable=too-many-arguments,too-many-locals,too-ma qry = qry.offset(row_offset) if ( - is_timeseries # pylint: disable=too-many-boolean-expressions + is_timeseries and timeseries_limit and not time_groupby_inline and groupby_exprs_sans_timestamp diff --git a/superset/connectors/sqla/views.py b/superset/connectors/sqla/views.py index f7f6742ab53bf..08abf54a25a97 100644 --- a/superset/connectors/sqla/views.py +++ b/superset/connectors/sqla/views.py @@ -53,9 +53,7 @@ logger = logging.getLogger(__name__) -class TableColumnInlineView( # pylint: disable=too-many-ancestors - CompactCRUDMixin, SupersetModelView -): +class TableColumnInlineView(CompactCRUDMixin, SupersetModelView): datamodel = SQLAInterface(models.TableColumn) # TODO TODO, review need for this on related_views class_permission_name = "Dataset" @@ -196,9 +194,7 @@ def pre_delete(self, item: "models.SqlMetric") -> None: check_ownership(item.table) -class SqlMetricInlineView( # pylint: disable=too-many-ancestors - CompactCRUDMixin, SupersetModelView -): +class SqlMetricInlineView(CompactCRUDMixin, SupersetModelView): datamodel = SQLAInterface(models.SqlMetric) class_permission_name = "Dataset" method_permission_name = MODEL_VIEW_RW_METHOD_PERMISSION_MAP @@ -301,9 +297,7 @@ def __init__(self, **kwargs: Any): super().__init__(**kwargs) -class RowLevelSecurityFiltersModelView( # pylint: disable=too-many-ancestors - SupersetModelView, DeleteMixin -): +class RowLevelSecurityFiltersModelView(SupersetModelView, DeleteMixin): datamodel = SQLAInterface(models.RowLevelSecurityFilter) list_widget = cast(SupersetListWidget, RowLevelSecurityListWidget) @@ -561,7 +555,7 @@ def edit(self, pk: str) -> FlaskResponse: @action( "refresh", __("Refresh Metadata"), __("Refresh column metadata"), "fa-refresh" ) - def refresh( # pylint: disable=no-self-use, too-many-branches + def refresh( # pylint: disable=no-self-use, self, tables: Union["TableModelView", List["TableModelView"]] ) -> FlaskResponse: logger.warning( diff --git a/superset/dashboards/api.py b/superset/dashboards/api.py index b0357aa048845..d248afd514f80 100644 --- a/superset/dashboards/api.py +++ b/superset/dashboards/api.py @@ -222,7 +222,7 @@ def __repr__(self) -> str: ) @etag_cache( - get_last_modified=lambda _self, id_or_slug: DashboardDAO.get_dashboard_changed_on( # pylint: disable=line-too-long + get_last_modified=lambda _self, id_or_slug: DashboardDAO.get_dashboard_changed_on( # pylint: disable=line-too-long,useless-suppression id_or_slug ), max_age=0, @@ -279,7 +279,7 @@ def get(self, id_or_slug: str) -> Response: return self.response_404() @etag_cache( - get_last_modified=lambda _self, id_or_slug: DashboardDAO.get_dashboard_and_datasets_changed_on( # pylint: disable=line-too-long + get_last_modified=lambda _self, id_or_slug: DashboardDAO.get_dashboard_and_datasets_changed_on( # pylint: disable=line-too-long,useless-suppression id_or_slug ), max_age=0, @@ -340,7 +340,7 @@ def get_datasets(self, id_or_slug: str) -> Response: return self.response_404() @etag_cache( - get_last_modified=lambda _self, id_or_slug: DashboardDAO.get_dashboard_and_slices_changed_on( # pylint: disable=line-too-long + get_last_modified=lambda _self, id_or_slug: DashboardDAO.get_dashboard_and_slices_changed_on( # pylint: disable=line-too-long,useless-suppression id_or_slug ), max_age=0, diff --git a/superset/dashboards/commands/importers/dispatcher.py b/superset/dashboards/commands/importers/dispatcher.py index 59ab20488350d..dd0121f3e3500 100644 --- a/superset/dashboards/commands/importers/dispatcher.py +++ b/superset/dashboards/commands/importers/dispatcher.py @@ -43,7 +43,6 @@ class ImportDashboardsCommand(BaseCommand): until it finds one that matches. """ - # pylint: disable=unused-argument def __init__(self, contents: Dict[str, str], *args: Any, **kwargs: Any): self.contents = contents self.args = args diff --git a/superset/dashboards/commands/importers/v0.py b/superset/dashboards/commands/importers/v0.py index a37ccc1ef1ec0..f317d51086c9d 100644 --- a/superset/dashboards/commands/importers/v0.py +++ b/superset/dashboards/commands/importers/v0.py @@ -82,7 +82,7 @@ def import_chart( def import_dashboard( - # pylint: disable=too-many-locals,too-many-branches,too-many-statements + # pylint: disable=too-many-locals,too-many-statements dashboard_to_import: Dashboard, dataset_id_mapping: Optional[Dict[int, int]] = None, import_time: Optional[int] = None, diff --git a/superset/dashboards/schemas.py b/superset/dashboards/schemas.py index e577e9f19caf4..9c48d472557e6 100644 --- a/superset/dashboards/schemas.py +++ b/superset/dashboards/schemas.py @@ -217,8 +217,6 @@ def post_load(self, data: Dict[str, Any], **kwargs: Any) -> Dict[str, Any]: data["slug"] = re.sub(r"[^\w\-]+", "", data["slug"]) return data - # pylint: disable=no-self-use,unused-argument - class DashboardPostSchema(BaseDashboardSchema): dashboard_title = fields.String( diff --git a/superset/databases/api.py b/superset/databases/api.py index e604c4a4c4e6a..712540c2e29fa 100644 --- a/superset/databases/api.py +++ b/superset/databases/api.py @@ -275,9 +275,7 @@ def post(self) -> Response: action=lambda self, *args, **kwargs: f"{self.__class__.__name__}.put", log_to_statsd=False, ) - def put( # pylint: disable=too-many-return-statements, arguments-differ - self, pk: int - ) -> Response: + def put(self, pk: int) -> Response: """Changes a Database --- put: @@ -355,7 +353,7 @@ def put( # pylint: disable=too-many-return-statements, arguments-differ action=lambda self, *args, **kwargs: f"{self.__class__.__name__}" f".delete", log_to_statsd=False, ) - def delete(self, pk: int) -> Response: # pylint: disable=arguments-differ + def delete(self, pk: int) -> Response: """Deletes a Database --- delete: @@ -591,9 +589,7 @@ def select_star( f".test_connection", log_to_statsd=False, ) - def test_connection( # pylint: disable=too-many-return-statements - self, - ) -> FlaskResponse: + def test_connection(self) -> FlaskResponse: """Tests a database connection --- post: @@ -977,9 +973,7 @@ def available(self) -> Response: f".validate_parameters", log_to_statsd=False, ) - def validate_parameters( # pylint: disable=too-many-return-statements - self, - ) -> FlaskResponse: + def validate_parameters(self) -> FlaskResponse: """validates database connection parameters --- post: diff --git a/superset/databases/commands/create.py b/superset/databases/commands/create.py index 8e1bb1eed7798..6024b64469ae2 100644 --- a/superset/databases/commands/create.py +++ b/superset/databases/commands/create.py @@ -48,7 +48,7 @@ def run(self) -> Model: try: # Test connection before starting create transaction TestConnectionDatabaseCommand(self._actor, self._properties).run() - except Exception as ex: # pylint: disable=broad-except + except Exception as ex: event_logger.log_with_context( action=f"db_creation_failed.{ex.__class__.__name__}", engine=self._properties.get("sqlalchemy_uri", "").split(":")[0], diff --git a/superset/databases/commands/importers/dispatcher.py b/superset/databases/commands/importers/dispatcher.py index 0f9cd1ac5880a..88d38bf13b857 100644 --- a/superset/databases/commands/importers/dispatcher.py +++ b/superset/databases/commands/importers/dispatcher.py @@ -38,7 +38,6 @@ class ImportDatabasesCommand(BaseCommand): until it finds one that matches. """ - # pylint: disable=unused-argument def __init__(self, contents: Dict[str, str], *args: Any, **kwargs: Any): self.contents = contents self.args = args diff --git a/superset/databases/commands/test_connection.py b/superset/databases/commands/test_connection.py index 2b5b0b8d2cbf2..7b2f4da687c66 100644 --- a/superset/databases/commands/test_connection.py +++ b/superset/databases/commands/test_connection.py @@ -110,7 +110,7 @@ def run(self) -> None: engine=database.db_engine_spec.__name__, ) raise DatabaseSecurityUnsafeError(message=str(ex)) from ex - except Exception as ex: # pylint: disable=broad-except + except Exception as ex: event_logger.log_with_context( action=f"test_connection_error.{ex.__class__.__name__}", engine=database.db_engine_spec.__name__, diff --git a/superset/databases/commands/validate.py b/superset/databases/commands/validate.py index 5f6e1c7726d64..27633d2c15c5e 100644 --- a/superset/databases/commands/validate.py +++ b/superset/databases/commands/validate.py @@ -116,7 +116,7 @@ def run(self) -> None: try: with closing(engine.raw_connection()) as conn: alive = engine.dialect.do_ping(conn) - except Exception as ex: # pylint: disable=broad-except + except Exception as ex: url = make_url(sqlalchemy_uri) context = { "hostname": url.host, diff --git a/superset/datasets/commands/importers/dispatcher.py b/superset/datasets/commands/importers/dispatcher.py index f999b18dacd6f..74f1129d23bbd 100644 --- a/superset/datasets/commands/importers/dispatcher.py +++ b/superset/datasets/commands/importers/dispatcher.py @@ -43,7 +43,6 @@ class ImportDatasetsCommand(BaseCommand): until it finds one that matches. """ - # pylint: disable=unused-argument def __init__(self, contents: Dict[str, str], *args: Any, **kwargs: Any): self.contents = contents self.args = args diff --git a/superset/datasets/commands/importers/v1/utils.py b/superset/datasets/commands/importers/v1/utils.py index e6917292c5116..43df8b6ae5ec3 100644 --- a/superset/datasets/commands/importers/v1/utils.py +++ b/superset/datasets/commands/importers/v1/utils.py @@ -14,8 +14,6 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. -# pylint: disable=too-many-branches - import gzip import json import logging diff --git a/superset/db_engine_specs/base.py b/superset/db_engine_specs/base.py index 8f4ed7638c010..a249408cd05fe 100644 --- a/superset/db_engine_specs/base.py +++ b/superset/db_engine_specs/base.py @@ -75,7 +75,7 @@ logger = logging.getLogger() -class TimeGrain(NamedTuple): # pylint: disable=too-few-public-methods +class TimeGrain(NamedTuple): name: str # TODO: redundant field, remove label: str function: str @@ -108,9 +108,7 @@ class TimeGrain(NamedTuple): # pylint: disable=too-few-public-methods } -class TimestampExpression( - ColumnClause -): # pylint: disable=abstract-method,too-many-ancestors,too-few-public-methods +class TimestampExpression(ColumnClause): # pylint: disable=abstract-method def __init__(self, expr: str, col: ColumnClause, **kwargs: Any) -> None: """Sqlalchemy class that can be can be used to render native column elements respeting engine-specific quoting rules as part of a string-based expression. diff --git a/superset/db_engine_specs/gsheets.py b/superset/db_engine_specs/gsheets.py index 747262ca228ba..7c5cd1cc0523f 100644 --- a/superset/db_engine_specs/gsheets.py +++ b/superset/db_engine_specs/gsheets.py @@ -108,8 +108,7 @@ def build_sqlalchemy_uri( encrypted_extra: Optional[ # pylint: disable=unused-argument Dict[str, Any] ] = None, - ) -> str: # pylint: disable=unused-variable - + ) -> str: return "gsheets://" @classmethod diff --git a/superset/db_engine_specs/presto.py b/superset/db_engine_specs/presto.py index eb0d2df6cfe77..51d7951359de4 100644 --- a/superset/db_engine_specs/presto.py +++ b/superset/db_engine_specs/presto.py @@ -341,7 +341,7 @@ def _split_data_type(cls, data_type: str, delimiter: str) -> List[str]: ) @classmethod - def _parse_structural_column( # pylint: disable=too-many-locals,too-many-branches + def _parse_structural_column( # pylint: disable=too-many-locals cls, parent_column_name: str, parent_data_type: str, @@ -655,9 +655,7 @@ def select_star( # pylint: disable=too-many-arguments ) @classmethod - def estimate_statement_cost( # pylint: disable=too-many-locals - cls, statement: str, cursor: Any - ) -> Dict[str, Any]: + def estimate_statement_cost(cls, statement: str, cursor: Any) -> Dict[str, Any]: """ Run a SQL query that estimates the cost of a given statement. @@ -749,7 +747,7 @@ def convert_dttm(cls, target_type: str, dttm: datetime) -> Optional[str]: if tt == utils.TemporalType.DATE: return f"""from_iso8601_date('{dttm.date().isoformat()}')""" if tt == utils.TemporalType.TIMESTAMP: - return f"""from_iso8601_timestamp('{dttm.isoformat(timespec="microseconds")}')""" # pylint: disable=line-too-long + return f"""from_iso8601_timestamp('{dttm.isoformat(timespec="microseconds")}')""" # pylint: disable=line-too-long,useless-suppression return None @classmethod @@ -777,7 +775,7 @@ def get_all_datasource_names( return datasource_names @classmethod - def expand_data( # pylint: disable=too-many-locals,too-many-branches + def expand_data( # pylint: disable=too-many-locals cls, columns: List[Dict[Any, Any]], data: List[Dict[Any, Any]] ) -> Tuple[List[Dict[Any, Any]], List[Dict[Any, Any]], List[Dict[Any, Any]]]: """ diff --git a/superset/db_engine_specs/sqlite.py b/superset/db_engine_specs/sqlite.py index 9a4986b0c1025..d2b87a4889500 100644 --- a/superset/db_engine_specs/sqlite.py +++ b/superset/db_engine_specs/sqlite.py @@ -37,7 +37,6 @@ class SqliteEngineSpec(BaseEngineSpec): engine = "sqlite" engine_name = "SQLite" - # pylint: disable=line-too-long _time_grain_expressions = { None: "{col}", "PT1S": "DATETIME(STRFTIME('%Y-%m-%dT%H:%M:%S', {col}))", diff --git a/superset/db_engine_specs/trino.py b/superset/db_engine_specs/trino.py index 9218f111ae47e..7d28cfbaa6047 100644 --- a/superset/db_engine_specs/trino.py +++ b/superset/db_engine_specs/trino.py @@ -29,7 +29,6 @@ class TrinoEngineSpec(BaseEngineSpec): engine = "trino" engine_name = "Trino" - # pylint: disable=line-too-long _time_grain_expressions = { None: "{col}", "PT1S": "date_trunc('second', CAST({col} AS TIMESTAMP))", @@ -110,9 +109,7 @@ def get_allow_cost_estimate(cls, extra: Dict[str, Any]) -> bool: return True @classmethod - def estimate_statement_cost( # pylint: disable=too-many-locals - cls, statement: str, cursor: Any - ) -> Dict[str, Any]: + def estimate_statement_cost(cls, statement: str, cursor: Any) -> Dict[str, Any]: """ Run a SQL query that estimates the cost of a given statement. diff --git a/superset/errors.py b/superset/errors.py index 549474df6dcaa..abfa65352fde1 100644 --- a/superset/errors.py +++ b/superset/errors.py @@ -14,8 +14,7 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. -# pylint: disable=too-few-public-methods,invalid-name -from dataclasses import dataclass # pylint: disable=wrong-import-order +from dataclasses import dataclass from enum import Enum from typing import Any, Dict, Optional diff --git a/superset/examples/birth_names.py b/superset/examples/birth_names.py index ea5884a01bd95..2fc1fae8c037e 100644 --- a/superset/examples/birth_names.py +++ b/superset/examples/birth_names.py @@ -98,7 +98,6 @@ def load_birth_names( only_metadata: bool = False, force: bool = False, sample: bool = False ) -> None: """Loading birth name dataset from a zip file in the repo""" - # pylint: disable=too-many-locals tbl_name = "birth_names" database = get_example_database() table_exists = database.has_table_by_name(tbl_name) diff --git a/superset/examples/country_map.py b/superset/examples/country_map.py index 01c8572dbc11b..4ed5235e6d91c 100644 --- a/superset/examples/country_map.py +++ b/superset/examples/country_map.py @@ -46,7 +46,7 @@ def load_country_map_data(only_metadata: bool = False, force: bool = False) -> N ) data = pd.read_csv(csv_bytes, encoding="utf-8") data["dttm"] = datetime.datetime.now().date() - data.to_sql( # pylint: disable=no-member + data.to_sql( tbl_name, database.get_sqla_engine(), if_exists="replace", diff --git a/superset/examples/flights.py b/superset/examples/flights.py index ef993d314e0c0..cb72940f60526 100644 --- a/superset/examples/flights.py +++ b/superset/examples/flights.py @@ -38,9 +38,9 @@ def load_flights(only_metadata: bool = False, force: bool = False) -> None: airports = pd.read_csv(airports_bytes, encoding="latin-1") airports = airports.set_index("IATA_CODE") - pdf["ds"] = ( # pylint: disable=unsupported-assignment-operation - pdf.YEAR.map(str) + "-0" + pdf.MONTH.map(str) + "-0" + pdf.DAY.map(str) - ) + pdf[ # pylint: disable=unsupported-assignment-operation,useless-suppression + "ds" + ] = (pdf.YEAR.map(str) + "-0" + pdf.MONTH.map(str) + "-0" + pdf.DAY.map(str)) pdf.ds = pd.to_datetime(pdf.ds) pdf.drop(columns=["DAY", "MONTH", "YEAR"]) pdf = pdf.join(airports, on="ORIGIN_AIRPORT", rsuffix="_ORIG") diff --git a/superset/examples/long_lat.py b/superset/examples/long_lat.py index 8c7864ec210a5..7e2f2f9bdc206 100644 --- a/superset/examples/long_lat.py +++ b/superset/examples/long_lat.py @@ -54,7 +54,7 @@ def load_long_lat_data(only_metadata: bool = False, force: bool = False) -> None pdf["radius_miles"] = [random.uniform(1, 3) for _ in range(len(pdf))] pdf["geohash"] = pdf[["LAT", "LON"]].apply(lambda x: geohash.encode(*x), axis=1) pdf["delimited"] = pdf["LAT"].map(str).str.cat(pdf["LON"].map(str), sep=",") - pdf.to_sql( # pylint: disable=no-member + pdf.to_sql( tbl_name, database.get_sqla_engine(), if_exists="replace", diff --git a/superset/examples/world_bank.py b/superset/examples/world_bank.py index ccfcfca089738..83d710a2be716 100644 --- a/superset/examples/world_bank.py +++ b/superset/examples/world_bank.py @@ -41,7 +41,7 @@ ) -def load_world_bank_health_n_pop( # pylint: disable=too-many-locals, too-many-statements +def load_world_bank_health_n_pop( # pylint: disable=too-many-locals only_metadata: bool = False, force: bool = False, sample: bool = False, ) -> None: """Loads the world bank health dataset, slices and a dashboard""" diff --git a/superset/extensions.py b/superset/extensions.py index dafb4551aed18..33dc1706a6b78 100644 --- a/superset/extensions.py +++ b/superset/extensions.py @@ -65,9 +65,7 @@ def init_app(self, app: Flask) -> None: self.parse_manifest_json() @app.context_processor - def get_manifest() -> Dict[ # pylint: disable=unused-variable - str, Callable[[str], List[str]] - ]: + def get_manifest() -> Dict[str, Callable[[str], List[str]]]: loaded_chunks = set() def get_files(bundle: str, asset_type: str = "js") -> List[str]: diff --git a/superset/initialization/__init__.py b/superset/initialization/__init__.py index f2c28a38cfbc7..79c466069e762 100644 --- a/superset/initialization/__init__.py +++ b/superset/initialization/__init__.py @@ -67,7 +67,7 @@ def __init__(self, app: SupersetApp) -> None: self.config = app.config self.manifest: Dict[Any, Any] = {} - @deprecated(details="use self.superset_app instead of self.flask_app") # type: ignore # pylint: disable=line-too-long + @deprecated(details="use self.superset_app instead of self.flask_app") # type: ignore # pylint: disable=line-too-long,useless-suppression @property def flask_app(self) -> SupersetApp: return self.superset_app @@ -112,7 +112,7 @@ def init_views(self) -> None: # models which in turn try to import # the global Flask app # - # pylint: disable=import-outside-toplevel,too-many-branches,too-many-locals,too-many-statements + # pylint: disable=import-outside-toplevel,too-many-locals,too-many-statements from superset.annotation_layers.api import AnnotationLayerRestApi from superset.annotation_layers.annotations.api import AnnotationRestApi from superset.async_events.api import AsyncEventsRestApi diff --git a/superset/jinja_context.py b/superset/jinja_context.py index 907ba99a8c856..59f7d933bdb5e 100644 --- a/superset/jinja_context.py +++ b/superset/jinja_context.py @@ -378,7 +378,7 @@ def validate_template_context( return validate_context_types(context) -class BaseTemplateProcessor: # pylint: disable=too-few-public-methods +class BaseTemplateProcessor: """ Base class for database-specific jinja context """ @@ -442,9 +442,7 @@ def set_context(self, **kwargs: Any) -> None: ) -class NoOpTemplateProcessor( - BaseTemplateProcessor -): # pylint: disable=too-few-public-methods +class NoOpTemplateProcessor(BaseTemplateProcessor): def process_template(self, sql: str, **kwargs: Any) -> str: """ Makes processing a template a noop diff --git a/superset/models/core.py b/superset/models/core.py index 77cba417638c6..129923b05ccf6 100755 --- a/superset/models/core.py +++ b/superset/models/core.py @@ -14,7 +14,7 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. -# pylint: disable=line-too-long,unused-argument,ungrouped-imports +# pylint: disable=line-too-long,unused-argument """A collection of ORM sqlalchemy models for Superset""" import enum import json @@ -167,7 +167,7 @@ class Database( extra_import_fields = ["password"] export_children = ["tables"] - def __repr__(self) -> str: # pylint: disable=invalid-repr-returned + def __repr__(self) -> str: return self.name @property @@ -724,7 +724,7 @@ def has_table_by_name(self, table_name: str, schema: Optional[str] = None) -> bo @memoized def get_dialect(self) -> Dialect: sqla_url = url.make_url(self.sqlalchemy_uri_decrypted) - return sqla_url.get_dialect()() # pylint: disable=no-member + return sqla_url.get_dialect()() sqla.event.listen(Database, "after_insert", security_manager.set_perm) diff --git a/superset/models/dashboard.py b/superset/models/dashboard.py index d74efa72cde8f..11fc26d2c9af8 100644 --- a/superset/models/dashboard.py +++ b/superset/models/dashboard.py @@ -60,8 +60,6 @@ from superset.utils.hashing import md5_sha_from_str from superset.utils.urls import get_url_path -# pylint: disable=too-many-public-methods - metadata = Model.metadata # pylint: disable=no-member config = app.config logger = logging.getLogger(__name__) @@ -134,7 +132,6 @@ def copy_dashboard( class Dashboard( # pylint: disable=too-many-instance-attributes Model, AuditMixinNullable, ImportExportMixin ): - """The dashboard object!""" __tablename__ = "dashboards" diff --git a/superset/models/reports.py b/superset/models/reports.py index 7ea985c6f808e..6ed769dd49400 100644 --- a/superset/models/reports.py +++ b/superset/models/reports.py @@ -50,7 +50,7 @@ class ReportScheduleType(str, enum.Enum): class ReportScheduleValidatorType(str, enum.Enum): - """ Validator types for alerts """ + """Validator types for alerts""" NOT_NULL = "not null" OPERATOR = "operator" @@ -153,10 +153,7 @@ def crontab_humanized(self) -> str: return get_description(self.crontab) -class ReportRecipients( - Model, AuditMixinNullable -): # pylint: disable=too-few-public-methods - +class ReportRecipients(Model, AuditMixinNullable): """ Report Recipients, meant to support multiple notification types, eg: Slack, email """ diff --git a/superset/queries/saved_queries/commands/importers/dispatcher.py b/superset/queries/saved_queries/commands/importers/dispatcher.py index a53a765e790b6..828320222567f 100644 --- a/superset/queries/saved_queries/commands/importers/dispatcher.py +++ b/superset/queries/saved_queries/commands/importers/dispatcher.py @@ -40,7 +40,6 @@ class ImportSavedQueriesCommand(BaseCommand): until it finds one that matches. """ - # pylint: disable=unused-argument def __init__(self, contents: Dict[str, str], *args: Any, **kwargs: Any): self.contents = contents self.args = args diff --git a/superset/reports/api.py b/superset/reports/api.py index 86f6a461f4ed9..3a049c024594a 100644 --- a/superset/reports/api.py +++ b/superset/reports/api.py @@ -336,7 +336,7 @@ def post(self) -> Response: @safe @statsd_metrics @permission_name("put") - def put(self, pk: int) -> Response: # pylint: disable=too-many-return-statements + def put(self, pk: int) -> Response: """Updates an Report Schedule --- put: diff --git a/superset/reports/commands/execute.py b/superset/reports/commands/execute.py index c52ffb3c8fa61..d30a61fd409b3 100644 --- a/superset/reports/commands/execute.py +++ b/superset/reports/commands/execute.py @@ -117,8 +117,8 @@ def set_state(self, state: ReportState, dttm: datetime) -> None: self._session.merge(self._report_schedule) self._session.commit() - def create_log( # pylint: disable=too-many-arguments - self, state: ReportState, error_message: Optional[str] = None, + def create_log( + self, state: ReportState, error_message: Optional[str] = None ) -> None: """ Creates a Report execution log, uses the current computed last_value for Alerts @@ -578,7 +578,7 @@ def run(self) -> None: if (self._report_schedule.last_state is None and state_cls.initial) or ( self._report_schedule.last_state in state_cls.current_states ): - state_cls( # pylint: disable=not-callable + state_cls( self._session, self._report_schedule, self._scheduled_dttm, diff --git a/superset/result_set.py b/superset/result_set.py index 34d5dc909d630..1f925138d29a9 100644 --- a/superset/result_set.py +++ b/superset/result_set.py @@ -72,7 +72,7 @@ def destringify(obj: str) -> Any: class SupersetResultSet: - def __init__( # pylint: disable=too-many-locals,too-many-branches + def __init__( # pylint: disable=too-many-locals self, data: DbapiResult, cursor_description: DbapiDescription, diff --git a/superset/security/manager.py b/superset/security/manager.py index 6c99821b7b386..75a83973aa33b 100644 --- a/superset/security/manager.py +++ b/superset/security/manager.py @@ -900,7 +900,7 @@ def _is_granter_pvm( # pylint: disable=no-self-use return pvm.permission.name in {"can_override_role_permissions", "can_approve"} - def set_perm( # pylint: disable=no-self-use,unused-argument + def set_perm( # pylint: disable=unused-argument self, mapper: Mapper, connection: Connection, target: "BaseDatasource" ) -> None: """ @@ -910,7 +910,7 @@ def set_perm( # pylint: disable=no-self-use,unused-argument :param connection: The DB-API connection :param target: The mapped instance being persisted """ - link_table = target.__table__ # pylint: disable=no-member + link_table = target.__table__ if target.perm != target.get_perm(): connection.execute( link_table.update() @@ -974,8 +974,7 @@ def set_perm( # pylint: disable=no-self-use,unused-argument ) def raise_for_access( - # pylint: disable=too-many-arguments,too-many-branches, - # pylint: disable=too-many-locals + # pylint: disable=too-many-arguments,too-many-locals self, database: Optional["Database"] = None, datasource: Optional["BaseDatasource"] = None, diff --git a/superset/sql_parse.py b/superset/sql_parse.py index fdcb390998462..a5394c15c32e1 100644 --- a/superset/sql_parse.py +++ b/superset/sql_parse.py @@ -15,7 +15,7 @@ # specific language governing permissions and limitations # under the License. import logging -from dataclasses import dataclass # pylint: disable=wrong-import-order +from dataclasses import dataclass from enum import Enum from typing import List, Optional, Set from urllib import parse @@ -79,7 +79,7 @@ def strip_comments_from_sql(statement: str) -> str: @dataclass(eq=True, frozen=True) -class Table: # pylint: disable=too-few-public-methods +class Table: """ A fully qualified SQL table conforming to [[catalog.]schema.]table. """ @@ -265,9 +265,7 @@ def as_create_table( exec_sql += f"CREATE {method} {full_table_name} AS \n{sql}" return exec_sql - def _extract_from_token( # pylint: disable=too-many-branches - self, token: Token - ) -> None: + def _extract_from_token(self, token: Token) -> None: """ store a list of subtokens and store lists of subtoken list. diff --git a/superset/tasks/schedules.py b/superset/tasks/schedules.py index 78c0e8efeeae5..be5be222481e7 100644 --- a/superset/tasks/schedules.py +++ b/superset/tasks/schedules.py @@ -282,7 +282,7 @@ def deliver_dashboard( # pylint: disable=too-many-locals except WebDriverException: # Some webdrivers do not support screenshots for elements. # In such cases, take a screenshot of the entire page. - screenshot = driver.screenshot() # pylint: disable=no-member + screenshot = driver.screenshot() finally: destroy_webdriver(driver) @@ -432,7 +432,7 @@ def _get_slice_visualization( except WebDriverException: # Some webdrivers do not support screenshots for elements. # In such cases, take a screenshot of the entire page. - screenshot = driver.screenshot() # pylint: disable=no-member + screenshot = driver.screenshot() finally: destroy_webdriver(driver) diff --git a/superset/utils/async_query_manager.py b/superset/utils/async_query_manager.py index 011564e822b52..14f89a83a109b 100644 --- a/superset/utils/async_query_manager.py +++ b/superset/utils/async_query_manager.py @@ -63,8 +63,6 @@ def increment_id(redis_id: str) -> str: class AsyncQueryManager: - # pylint: disable=too-many-instance-attributes - MAX_EVENT_COUNT = 100 STATUS_PENDING = "pending" STATUS_RUNNING = "running" @@ -114,9 +112,7 @@ def init_app(self, app: Flask) -> None: self._jwt_secret = config["GLOBAL_ASYNC_QUERIES_JWT_SECRET"] @app.after_request - def validate_session( # pylint: disable=unused-variable - response: Response, - ) -> Response: + def validate_session(response: Response) -> Response: user_id = None try: diff --git a/superset/utils/core.py b/superset/utils/core.py index 270d740949636..a549a753c56a0 100644 --- a/superset/utils/core.py +++ b/superset/utils/core.py @@ -246,7 +246,7 @@ class FilterOperator(str, Enum): ILIKE = "ILIKE" IS_NULL = "IS NULL" IS_NOT_NULL = "IS NOT NULL" - IN = "IN" # pylint: disable=invalid-name + IN = "IN" NOT_IN = "NOT IN" REGEX = "REGEX" IS_TRUE = "IS TRUE" @@ -291,7 +291,7 @@ class QuerySource(Enum): SQL_LAB = 2 -class QueryStatus(str, Enum): # pylint: disable=too-few-public-methods +class QueryStatus(str, Enum): """Enum-type class for query statuses""" STOPPED: str = "stopped" @@ -545,9 +545,7 @@ def format_timedelta(time_delta: timedelta) -> str: return str(time_delta) -def base_json_conv( # pylint: disable=inconsistent-return-statements,too-many-return-statements - obj: Any, -) -> Any: +def base_json_conv(obj: Any,) -> Any: # pylint: disable=inconsistent-return-statements if isinstance(obj, memoryview): obj = obj.tobytes() if isinstance(obj, np.int64): @@ -709,7 +707,7 @@ def generic_find_constraint_name( return None -def generic_find_fk_constraint_name( # pylint: disable=invalid-name +def generic_find_fk_constraint_name( table: str, columns: Set[str], referenced: str, insp: Inspector ) -> Optional[str]: """Utility to find a foreign-key constraint name in alembic migrations""" @@ -797,7 +795,7 @@ def __enter__(self) -> None: logger.warning("timeout can't be used in the current context") logger.exception(ex) - def __exit__( # pylint: disable=redefined-outer-name,unused-variable,redefined-builtin + def __exit__( # pylint: disable=redefined-outer-name,redefined-builtin self, type: Any, value: Any, traceback: TracebackType ) -> None: try: @@ -816,7 +814,7 @@ def __init__(self, seconds: int = 1, error_message: str = "Timeout") -> None: def __enter__(self) -> None: self.timer.start() - def __exit__( # pylint: disable=redefined-outer-name,unused-variable,redefined-builtin + def __exit__( # pylint: disable=redefined-outer-name,redefined-builtin self, type: Any, value: Any, traceback: TracebackType ) -> None: self.timer.cancel() @@ -837,9 +835,7 @@ def __exit__( # pylint: disable=redefined-outer-name,unused-variable,redefined- def pessimistic_connection_handling(some_engine: Engine) -> None: @event.listens_for(some_engine, "engine_connect") - def ping_connection( # pylint: disable=unused-variable - connection: Connection, branch: bool - ) -> None: + def ping_connection(connection: Connection, branch: bool) -> None: if branch: # 'branch' refers to a sub-connection of a connection, # we don't want to bother pinging on these. @@ -1129,9 +1125,7 @@ def merge_extra_form_data(form_data: Dict[str, Any]) -> None: ) -def merge_extra_filters( # pylint: disable=too-many-branches - form_data: Dict[str, Any], -) -> None: +def merge_extra_filters(form_data: Dict[str, Any]) -> None: # extra_filters are temporary/contextual filters (using the legacy constructs) # that are external to the slice definition. We use those for dynamic # interactive filters like the ones emitted by the "Filter Box" visualization. @@ -1613,7 +1607,7 @@ def is_test() -> bool: return strtobool(os.environ.get("SUPERSET_TESTENV", "false")) -def get_time_filter_status( # pylint: disable=too-many-branches +def get_time_filter_status( datasource: "BaseDatasource", applied_time_extras: Dict[str, str], ) -> Tuple[List[Dict[str, str]], List[Dict[str, str]]]: temporal_columns = {col.column_name for col in datasource.columns if col.is_dttm} diff --git a/superset/utils/date_parser.py b/superset/utils/date_parser.py index 3e742d56def1d..2bbfc185acbc8 100644 --- a/superset/utils/date_parser.py +++ b/superset/utils/date_parser.py @@ -53,7 +53,7 @@ def parse_human_datetime(human_readable: str) -> datetime: - """ Returns ``datetime.datetime`` from human readable strings """ + """Returns ``datetime.datetime`` from human readable strings""" x_periods = r"^\s*([0-9]+)\s+(second|minute|hour|day|week|month|quarter|year)s?\s*$" if re.search(x_periods, human_readable, re.IGNORECASE): raise TimeRangeAmbiguousError(human_readable) @@ -75,7 +75,7 @@ def parse_human_datetime(human_readable: str) -> datetime: def normalize_time_delta(human_readable: str) -> Dict[str, int]: - x_unit = r"^\s*([0-9]+)\s+(second|minute|hour|day|week|month|quarter|year)s?\s+(ago|later)*$" # pylint: disable=line-too-long + x_unit = r"^\s*([0-9]+)\s+(second|minute|hour|day|week|month|quarter|year)s?\s+(ago|later)*$" # pylint: disable=line-too-long,useless-suppression matched = re.match(x_unit, human_readable, re.IGNORECASE) if not matched: raise TimeDeltaAmbiguousError(human_readable) @@ -189,19 +189,19 @@ def get_since_until( and time_range.startswith("previous calendar week") and separator not in time_range ): - time_range = "DATETRUNC(DATEADD(DATETIME('today'), -1, WEEK), WEEK) : DATETRUNC(DATETIME('today'), WEEK)" # pylint: disable=line-too-long + time_range = "DATETRUNC(DATEADD(DATETIME('today'), -1, WEEK), WEEK) : DATETRUNC(DATETIME('today'), WEEK)" # pylint: disable=line-too-long,useless-suppression if ( time_range and time_range.startswith("previous calendar month") and separator not in time_range ): - time_range = "DATETRUNC(DATEADD(DATETIME('today'), -1, MONTH), MONTH) : DATETRUNC(DATETIME('today'), MONTH)" # pylint: disable=line-too-long + time_range = "DATETRUNC(DATEADD(DATETIME('today'), -1, MONTH), MONTH) : DATETRUNC(DATETIME('today'), MONTH)" # pylint: disable=line-too-long,useless-suppression if ( time_range and time_range.startswith("previous calendar year") and separator not in time_range ): - time_range = "DATETRUNC(DATEADD(DATETIME('today'), -1, YEAR), YEAR) : DATETRUNC(DATETIME('today'), YEAR)" # pylint: disable=line-too-long + time_range = "DATETRUNC(DATEADD(DATETIME('today'), -1, YEAR), YEAR) : DATETRUNC(DATETIME('today'), YEAR)" # pylint: disable=line-too-long,useless-suppression if time_range and separator in time_range: time_range_lookup = [ @@ -211,11 +211,11 @@ def get_since_until( ), ( r"^last\s+([0-9]+)\s+(second|minute|hour|day|week|month|year)s?$", - lambda delta, unit: f"DATEADD(DATETIME('{_relative_start}'), -{int(delta)}, {unit})", # pylint: disable=line-too-long + lambda delta, unit: f"DATEADD(DATETIME('{_relative_start}'), -{int(delta)}, {unit})", # pylint: disable=line-too-long,useless-suppression ), ( r"^next\s+([0-9]+)\s+(second|minute|hour|day|week|month|year)s?$", - lambda delta, unit: f"DATEADD(DATETIME('{_relative_end}'), {int(delta)}, {unit})", # pylint: disable=line-too-long + lambda delta, unit: f"DATEADD(DATETIME('{_relative_end}'), {int(delta)}, {unit})", # pylint: disable=line-too-long,useless-suppression ), ( r"^(DATETIME.*|DATEADD.*|DATETRUNC.*|LASTDAY.*|HOLIDAY.*)$", diff --git a/superset/utils/log.py b/superset/utils/log.py index f458dccc5cced..ecee213ebef29 100644 --- a/superset/utils/log.py +++ b/superset/utils/log.py @@ -194,7 +194,7 @@ def log_with_context( # pylint: disable=too-many-locals ) @contextmanager - def log_context( # pylint: disable=too-many-locals + def log_context( self, action: str, object_ref: Optional[str] = None, log_to_statsd: bool = True, ) -> Iterator[Callable[..., None]]: """ diff --git a/superset/utils/profiler.py b/superset/utils/profiler.py index 269cf5c33b8c8..a4710bb24341e 100644 --- a/superset/utils/profiler.py +++ b/superset/utils/profiler.py @@ -21,7 +21,6 @@ from werkzeug.wrappers import Request, Response try: - # pylint: disable=import-error,import-outside-toplevel from pyinstrument import Profiler except ModuleNotFoundError: Profiler = None diff --git a/superset/views/access_requests.py b/superset/views/access_requests.py index bca2e7bfcc30e..4d671f3086c05 100644 --- a/superset/views/access_requests.py +++ b/superset/views/access_requests.py @@ -25,9 +25,7 @@ from superset.views.core import DAR -class AccessRequestsModelView( # pylint: disable=too-many-ancestors - SupersetModelView, DeleteMixin -): +class AccessRequestsModelView(SupersetModelView, DeleteMixin): datamodel = SQLAInterface(DAR) include_route_methods = RouteMethod.CRUD_SET list_columns = [ diff --git a/superset/views/alerts.py b/superset/views/alerts.py index bf3ea7ae5cbf6..e96f701c3d1b7 100644 --- a/superset/views/alerts.py +++ b/superset/views/alerts.py @@ -122,9 +122,7 @@ class ReportView(BaseAlertReportView): class_permission_name = "ReportSchedule" -class AlertModelView( - EnsureEnabledMixin, SupersetModelView -): # pylint: disable=too-many-ancestors +class AlertModelView(EnsureEnabledMixin, SupersetModelView): datamodel = SQLAInterface(Alert) route_base = "/alerts" include_route_methods = RouteMethod.CRUD_SET | {"log"} diff --git a/superset/views/annotations.py b/superset/views/annotations.py index 345fd2c15a1f0..4fa83c0ca4be4 100644 --- a/superset/views/annotations.py +++ b/superset/views/annotations.py @@ -48,9 +48,7 @@ def __call__(self, form: Dict[str, Any], field: Any) -> None: ) -class AnnotationModelView( - SupersetModelView, CompactCRUDMixin -): # pylint: disable=too-many-ancestors +class AnnotationModelView(SupersetModelView, CompactCRUDMixin): datamodel = SQLAInterface(Annotation) include_route_methods = RouteMethod.CRUD_SET | {"annotation"} @@ -108,7 +106,7 @@ def annotation(self, pk: int) -> FlaskResponse: # pylint: disable=unused-argume return super().render_app_template() -class AnnotationLayerModelView(SupersetModelView): # pylint: disable=too-many-ancestors +class AnnotationLayerModelView(SupersetModelView): datamodel = SQLAInterface(AnnotationLayer) include_route_methods = RouteMethod.CRUD_SET | {RouteMethod.API_READ} related_views = [AnnotationModelView] diff --git a/superset/views/api.py b/superset/views/api.py index 0ef973cdbee14..c205ce9b091c2 100644 --- a/superset/views/api.py +++ b/superset/views/api.py @@ -14,7 +14,6 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. -# pylint: disable=R from typing import Any import simplejson as json @@ -45,7 +44,7 @@ class Api(BaseSupersetView): @handle_api_exception @has_access_api @expose("/v1/query/", methods=["POST"]) - def query(self) -> FlaskResponse: + def query(self) -> FlaskResponse: # pylint: disable=no-self-use """ Takes a query_obj constructed in the client and returns payload data response for the given query_obj. @@ -65,7 +64,7 @@ def query(self) -> FlaskResponse: @handle_api_exception @has_access_api @expose("/v1/form_data/", methods=["GET"]) - def query_form_data(self) -> FlaskResponse: + def query_form_data(self) -> FlaskResponse: # pylint: disable=no-self-use """ Get the formdata stored in the database for existing slice. params: slice_id: integer diff --git a/superset/views/base.py b/superset/views/base.py index 2de98b5e6b91e..f85d33a10cee0 100644 --- a/superset/views/base.py +++ b/superset/views/base.py @@ -14,7 +14,7 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. -import dataclasses # pylint: disable=wrong-import-order +import dataclasses import functools import logging import traceback @@ -77,9 +77,7 @@ from .utils import bootstrap_user_data if TYPE_CHECKING: - from superset.connectors.druid.views import ( # pylint: disable=unused-import - DruidClusterModelView, - ) + from superset.connectors.druid.views import DruidClusterModelView FRONTEND_CONF_KEYS = ( "SUPERSET_WEBSERVER_TIMEOUT", @@ -180,7 +178,7 @@ def api(f: Callable[..., FlaskResponse]) -> Callable[..., FlaskResponse]: def wraps(self: "BaseSupersetView", *args: Any, **kwargs: Any) -> FlaskResponse: try: return f(self, *args, **kwargs) - except NoAuthorizationError as ex: # pylint: disable=broad-except + except NoAuthorizationError as ex: logger.warning(ex) return json_error_response(get_error_msg(), status=401) except Exception as ex: # pylint: disable=broad-except @@ -277,9 +275,7 @@ def is_user_admin() -> bool: class BaseSupersetView(BaseView): @staticmethod - def json_response( - obj: Any, status: int = 200 - ) -> FlaskResponse: # pylint: disable=no-self-use + def json_response(obj: Any, status: int = 200) -> FlaskResponse: return Response( json.dumps(obj, default=utils.json_int_dttm_ser, ignore_nan=True), status=status, diff --git a/superset/views/base_api.py b/superset/views/base_api.py index b730ce12662cb..7fb63ebb2ca0d 100644 --- a/superset/views/base_api.py +++ b/superset/views/base_api.py @@ -164,7 +164,8 @@ class BaseSupersetModelRestApi(ModelRestApi): "": ("", ""), ... } - """ # pylint: disable=pointless-string-statement + """ + related_field_filters: Dict[str, Union[RelatedFieldFilter, str]] = {} """ Declare the filters for related fields:: @@ -172,7 +173,8 @@ class BaseSupersetModelRestApi(ModelRestApi): related_fields = { "": ) } - """ # pylint: disable=pointless-string-statement + """ + filter_rel_fields: Dict[str, BaseFilter] = {} """ Declare the related field base filter:: @@ -180,11 +182,9 @@ class BaseSupersetModelRestApi(ModelRestApi): filter_rel_fields_field = { "": "") } - """ # pylint: disable=pointless-string-statement - allowed_rel_fields: Set[str] = set() """ - Declare a set of allowed related fields that the `related` endpoint supports - """ # pylint: disable=pointless-string-statement + allowed_rel_fields: Set[str] = set() + # Declare a set of allowed related fields that the `related` endpoint supports. text_field_rel_fields: Dict[str, str] = {} """ @@ -193,15 +193,12 @@ class BaseSupersetModelRestApi(ModelRestApi): text_field_rel_fields = { "": "" } - """ # pylint: disable=pointless-string-statement + """ allowed_distinct_fields: Set[str] = set() openapi_spec_component_schemas: Tuple[Type[Schema], ...] = tuple() - """ - Add extra schemas to the OpenAPI component schemas section - """ # pylint: disable=pointless-string-statement - + # Add extra schemas to the OpenAPI component schemas section. add_columns: List[str] edit_columns: List[str] list_columns: List[str] diff --git a/superset/views/core.py b/superset/views/core.py index 5faef2080cc31..dc4a8a2da1c49 100755 --- a/superset/views/core.py +++ b/superset/views/core.py @@ -713,7 +713,7 @@ def import_dashboards(self) -> FlaskResponse: @event_logger.log_this @expose("/explore///", methods=["GET", "POST"]) @expose("/explore/", methods=["GET", "POST"]) - def explore( # pylint: disable=too-many-locals,too-many-return-statements,too-many-statements + def explore( # pylint: disable=too-many-locals self, datasource_type: Optional[str] = None, datasource_id: Optional[int] = None ) -> FlaskResponse: user_id = g.user.get_id() if g.user else None @@ -918,7 +918,7 @@ def remove_extra_filters(filters: List[Dict[str, Any]]) -> List[Dict[str, Any]]: return [f for f in filters if not f.get("isExtra")] def save_or_overwrite_slice( - # pylint: disable=too-many-arguments,too-many-locals,no-self-use + # pylint: disable=too-many-arguments,too-many-locals self, slc: Optional[Slice], slice_add_perm: bool, @@ -1286,9 +1286,7 @@ def add_slices( # pylint: disable=no-self-use @has_access_api @event_logger.log_this @expose("/testconn", methods=["POST", "GET"]) - def testconn( # pylint: disable=too-many-return-statements,no-self-use - self, - ) -> FlaskResponse: + def testconn(self) -> FlaskResponse: # pylint: disable=no-self-use """Tests a sqla connection""" db_name = request.json.get("name") uri = request.json.get("uri") @@ -1547,11 +1545,7 @@ def created_dashboards( # pylint: disable=no-self-use Dash = Dashboard qry = ( db.session.query(Dash) - .filter( - or_( # pylint: disable=comparison-with-callable - Dash.created_by_fk == user_id, Dash.changed_by_fk == user_id - ) - ) + .filter(or_(Dash.created_by_fk == user_id, Dash.changed_by_fk == user_id)) .order_by(Dash.changed_on.desc()) ) payload = [ @@ -1864,8 +1858,8 @@ def publish( # pylint: disable=no-self-use utils.error_msg_from_exception(ex), status=403 ) ) - def dashboard( # pylint: disable=too-many-locals - self, # pylint: disable=no-self-use + def dashboard( + self, dashboard_id_or_slug: str, # pylint: disable=unused-argument add_extra_log_payload: Callable[..., None] = lambda **kwargs: None, dashboard: Optional[Dashboard] = None, @@ -2212,9 +2206,7 @@ def results(self, key: str) -> FlaskResponse: return self.results_exec(key) @staticmethod - def results_exec( # pylint: disable=too-many-return-statements - key: str, - ) -> FlaskResponse: + def results_exec(key: str) -> FlaskResponse: """Serves a key off of the results backend It is possible to pass the `rows` query argument to limit the number @@ -2358,7 +2350,7 @@ def stop_query(self) -> FlaskResponse: @event_logger.log_this @expose("/validate_sql_json/", methods=["POST", "GET"]) def validate_sql_json( - # pylint: disable=too-many-locals,too-many-return-statements,no-self-use + # pylint: disable=too-many-locals,no-self-use self, ) -> FlaskResponse: """Validates that arbitrary sql is acceptable for the given database. @@ -2426,7 +2418,7 @@ def validate_sql_json( return json_error_response(f"{msg}") @staticmethod - def _sql_json_async( # pylint: disable=too-many-arguments + def _sql_json_async( session: Session, rendered_query: str, query: Query, @@ -2467,7 +2459,7 @@ def _sql_json_async( # pylint: disable=too-many-arguments "Unable to forget Celery task as backend" "does not support this operation" ) - except Exception as ex: # pylint: disable=broad-except + except Exception as ex: logger.exception("Query %i: %s", query.id, str(ex)) message = __("Failed to start remote query on a worker.") @@ -2550,7 +2542,7 @@ def _sql_json_sync( except SupersetTimeoutException as ex: # re-raise exception for api exception handler raise ex - except Exception as ex: # pylint: disable=broad-except + except Exception as ex: logger.exception("Query %i failed unexpectedly", query.id) raise SupersetGenericDBErrorException( utils.error_msg_from_exception(ex) diff --git a/superset/views/css_templates.py b/superset/views/css_templates.py index c998aabc2d411..d26acea5cfac7 100644 --- a/superset/views/css_templates.py +++ b/superset/views/css_templates.py @@ -26,9 +26,7 @@ from superset.views.base import DeleteMixin, SupersetModelView -class CssTemplateModelView( # pylint: disable=too-many-ancestors - SupersetModelView, DeleteMixin -): +class CssTemplateModelView(SupersetModelView, DeleteMixin): datamodel = SQLAInterface(models.CssTemplate) include_route_methods = RouteMethod.CRUD_SET diff --git a/superset/views/database/views.py b/superset/views/database/views.py index d999853c1a1c9..0a3a274c5acfb 100644 --- a/superset/views/database/views.py +++ b/superset/views/database/views.py @@ -43,7 +43,7 @@ from .validators import schema_allows_csv_upload, sqlalchemy_uri_validator if TYPE_CHECKING: - from werkzeug.datastructures import FileStorage # pylint: disable=unused-import + from werkzeug.datastructures import FileStorage config = app.config stats_logger = config["STATS_LOGGER"] diff --git a/superset/views/log/views.py b/superset/views/log/views.py index 6cc8d2ffd6a63..383109fc08822 100644 --- a/superset/views/log/views.py +++ b/superset/views/log/views.py @@ -26,7 +26,7 @@ from . import LogMixin -class LogModelView(LogMixin, SupersetModelView): # pylint: disable=too-many-ancestors +class LogModelView(LogMixin, SupersetModelView): datamodel = SQLAInterface(models.Log) include_route_methods = {RouteMethod.LIST, RouteMethod.SHOW} class_permission_name = "Log" diff --git a/superset/views/redirects.py b/superset/views/redirects.py index 1be79b6461a2b..cb4f721b0febe 100644 --- a/superset/views/redirects.py +++ b/superset/views/redirects.py @@ -45,7 +45,7 @@ def _validate_url(url: Optional[str] = None) -> bool: @event_logger.log_this @expose("/") - def index(self, url_id: int) -> FlaskResponse: # pylint: disable=no-self-use + def index(self, url_id: int) -> FlaskResponse: url = db.session.query(models.Url).get(url_id) if url and url.url: explore_url = "//superset/explore/?" @@ -62,7 +62,7 @@ def index(self, url_id: int) -> FlaskResponse: # pylint: disable=no-self-use @event_logger.log_this @has_access_api @expose("/shortner/", methods=["POST"]) - def shortner(self) -> FlaskResponse: # pylint: disable=no-self-use + def shortner(self) -> FlaskResponse: url = request.form.get("data") if not self._validate_url(url): logger.warning("Invalid URL: %s", url) diff --git a/superset/views/schedules.py b/superset/views/schedules.py index 679973b8d1848..d1c59f413c839 100644 --- a/superset/views/schedules.py +++ b/superset/views/schedules.py @@ -50,9 +50,7 @@ from .base import DeleteMixin, SupersetModelView -class EmailScheduleView( - SupersetModelView, DeleteMixin -): # pylint: disable=too-many-ancestors +class EmailScheduleView(SupersetModelView, DeleteMixin): include_route_methods = RouteMethod.CRUD_SET _extra_data = {"test_email": False, "test_email_recipients": None} diff --git a/superset/views/sql_lab.py b/superset/views/sql_lab.py index e3a09e66a5c16..6a5ce26d38ad9 100644 --- a/superset/views/sql_lab.py +++ b/superset/views/sql_lab.py @@ -30,9 +30,7 @@ from .base import BaseSupersetView, DeleteMixin, json_success, SupersetModelView -class SavedQueryView( - SupersetModelView, DeleteMixin -): # pylint: disable=too-many-ancestors +class SavedQueryView(SupersetModelView, DeleteMixin): datamodel = SQLAInterface(SavedQuery) include_route_methods = RouteMethod.CRUD_SET diff --git a/superset/viz.py b/superset/viz.py index 8886157811d06..57eb1a8e2af4c 100644 --- a/superset/viz.py +++ b/superset/viz.py @@ -14,7 +14,7 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. -# pylint: disable=C,R,W +# pylint: disable=C,R,W,useless-suppression """This module contains the 'Viz' objects These objects represent the backend of all the visualizations that From bc4b6f0a6cfb05f0d913a5e4d2dc09844960e04a Mon Sep 17 00:00:00 2001 From: John Bodley <4567245+john-bodley@users.noreply.github.com> Date: Mon, 23 Aug 2021 10:20:52 -0700 Subject: [PATCH 20/22] fix(pylint): Fix master (#16405) Co-authored-by: John Bodley --- superset/connectors/sqla/models.py | 5 +++-- superset/models/slice.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/superset/connectors/sqla/models.py b/superset/connectors/sqla/models.py index 14957f9364a6d..c5c46628382e0 100644 --- a/superset/connectors/sqla/models.py +++ b/superset/connectors/sqla/models.py @@ -471,8 +471,9 @@ def data(self) -> Dict[str, Any]: ) -class SqlaTable(Model, BaseDatasource): # pylint: disable=too-many-public-methods - +class SqlaTable( # pylint: disable=too-many-instance-attributes,too-many-public-methods + Model, BaseDatasource +): """An ORM object for SqlAlchemy table references""" type = "table" diff --git a/superset/models/slice.py b/superset/models/slice.py index 310343a1f0a26..28473d53d14ec 100644 --- a/superset/models/slice.py +++ b/superset/models/slice.py @@ -53,7 +53,7 @@ logger = logging.getLogger(__name__) -class Slice( # pylint: disable=too-many-instance-attributes,too-many-public-methods +class Slice( # pylint: disable=too-many-public-methods Model, AuditMixinNullable, ImportExportMixin ): """A slice is essentially a report or a view on data""" From 0cdc7675b401eeb82af77ab4192ec4b90f14f794 Mon Sep 17 00:00:00 2001 From: "Michael S. Molina" <70410625+michael-s-molina@users.noreply.github.com> Date: Mon, 23 Aug 2021 15:38:53 -0300 Subject: [PATCH 21/22] chore: Displays the dataset description in a tooltip in the datasets list (#16392) --- superset-frontend/src/components/InfoTooltip/index.tsx | 10 +++++++++- .../src/views/CRUD/data/dataset/DatasetList.tsx | 5 +++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/superset-frontend/src/components/InfoTooltip/index.tsx b/superset-frontend/src/components/InfoTooltip/index.tsx index 968f5df715980..e27c3729a4e92 100644 --- a/superset-frontend/src/components/InfoTooltip/index.tsx +++ b/superset-frontend/src/components/InfoTooltip/index.tsx @@ -52,6 +52,14 @@ const StyledTooltip = styled(Tooltip)` } `; +const StyledTooltipTitle = styled.span` + display: -webkit-box; + -webkit-line-clamp: 20; + -webkit-box-orient: vertical; + overflow: hidden; + text-overflow: ellipsis; +`; + const defaultOverlayStyle = { fontSize: '12px', lineHeight: '16px', @@ -69,7 +77,7 @@ export default function InfoTooltip({ }: InfoTooltipProps) { return ( {tooltip}} placement={placement} trigger={trigger} overlayStyle={overlayStyle} diff --git a/superset-frontend/src/views/CRUD/data/dataset/DatasetList.tsx b/superset-frontend/src/views/CRUD/data/dataset/DatasetList.tsx index 15a00969961ba..496decf71e774 100644 --- a/superset-frontend/src/views/CRUD/data/dataset/DatasetList.tsx +++ b/superset-frontend/src/views/CRUD/data/dataset/DatasetList.tsx @@ -51,6 +51,7 @@ import { Tooltip } from 'src/components/Tooltip'; import Icons from 'src/components/Icons'; import FacePile from 'src/components/FacePile'; import CertifiedIcon from 'src/components/CertifiedIcon'; +import InfoTooltip from 'src/components/InfoTooltip'; import ImportModelsModal from 'src/components/ImportModal/index'; import { isFeatureEnabled, FeatureFlag } from 'src/featureFlags'; import WarningIconWithTooltip from 'src/components/WarningIconWithTooltip'; @@ -228,6 +229,7 @@ const DatasetList: FunctionComponent = ({ original: { extra, table_name: datasetTitle, + description, explore_url: exploreURL, }, }, @@ -249,6 +251,9 @@ const DatasetList: FunctionComponent = ({ /> )} {titleLink} + {description && ( + + )} ); } catch { From c768941f2f662e1a0dfa1e1731319d22ec9ca886 Mon Sep 17 00:00:00 2001 From: "Michael S. Molina" <70410625+michael-s-molina@users.noreply.github.com> Date: Mon, 23 Aug 2021 15:41:03 -0300 Subject: [PATCH 22/22] chore: Changes the DatabaseSelector to use the new Select component (#16334) --- .../sqllab/SqlEditorLeftBar_spec.jsx | 10 +- .../src/components/CertifiedIcon/index.tsx | 9 +- .../DatabaseSelector.test.tsx | 62 ++- .../src/components/DatabaseSelector/index.tsx | 310 ++++++----- .../src/components/Icons/Icon.tsx | 8 +- .../src/components/Select/Select.tsx | 76 +-- .../TableSelector/TableSelector.test.jsx | 291 ----------- .../TableSelector/TableSelector.test.tsx | 91 ++++ .../src/components/TableSelector/index.tsx | 484 +++++++++--------- .../WarningIconWithTooltip/index.tsx | 6 +- .../src/datasource/DatasourceEditor.jsx | 132 ++--- .../controls/DatasourceControl/index.jsx | 5 +- .../views/CRUD/data/dataset/DatasetList.tsx | 2 + superset/datasets/api.py | 2 +- superset/views/core.py | 21 +- tests/integration_tests/datasets/api_tests.py | 14 +- 16 files changed, 692 insertions(+), 831 deletions(-) delete mode 100644 superset-frontend/src/components/TableSelector/TableSelector.test.jsx create mode 100644 superset-frontend/src/components/TableSelector/TableSelector.test.tsx diff --git a/superset-frontend/spec/javascripts/sqllab/SqlEditorLeftBar_spec.jsx b/superset-frontend/spec/javascripts/sqllab/SqlEditorLeftBar_spec.jsx index 1ba1ac821fb1e..b153c148394ce 100644 --- a/superset-frontend/spec/javascripts/sqllab/SqlEditorLeftBar_spec.jsx +++ b/superset-frontend/spec/javascripts/sqllab/SqlEditorLeftBar_spec.jsx @@ -81,9 +81,13 @@ describe('Left Panel Expansion', () => { , ); - const dbSelect = screen.getByText(/select a database/i); - const schemaSelect = screen.getByText(/select a schema \(0\)/i); - const dropdown = screen.getByText(/Select table/i); + const dbSelect = screen.getByRole('combobox', { + name: 'Select a database', + }); + const schemaSelect = screen.getByRole('combobox', { + name: 'Select a schema', + }); + const dropdown = screen.getByText(/Select a table/i); const abUser = screen.getByText(/ab_user/i); expect(dbSelect).toBeInTheDocument(); expect(schemaSelect).toBeInTheDocument(); diff --git a/superset-frontend/src/components/CertifiedIcon/index.tsx b/superset-frontend/src/components/CertifiedIcon/index.tsx index f08e9bf6047ce..4aa0dad236b12 100644 --- a/superset-frontend/src/components/CertifiedIcon/index.tsx +++ b/superset-frontend/src/components/CertifiedIcon/index.tsx @@ -18,19 +18,19 @@ */ import React from 'react'; import { t, supersetTheme } from '@superset-ui/core'; -import Icons from 'src/components/Icons'; +import Icons, { IconType } from 'src/components/Icons'; import { Tooltip } from 'src/components/Tooltip'; export interface CertifiedIconProps { certifiedBy?: string; details?: string; - size?: number; + size?: IconType['iconSize']; } function CertifiedIcon({ certifiedBy, details, - size = 24, + size = 'l', }: CertifiedIconProps) { return ( ); diff --git a/superset-frontend/src/components/DatabaseSelector/DatabaseSelector.test.tsx b/superset-frontend/src/components/DatabaseSelector/DatabaseSelector.test.tsx index 0d812824d1cf8..6d4abb3fd9634 100644 --- a/superset-frontend/src/components/DatabaseSelector/DatabaseSelector.test.tsx +++ b/superset-frontend/src/components/DatabaseSelector/DatabaseSelector.test.tsx @@ -26,11 +26,11 @@ import DatabaseSelector from '.'; const SupersetClientGet = jest.spyOn(SupersetClient, 'get'); const createProps = () => ({ - dbId: 1, + db: { id: 1, database_name: 'test', backend: 'postgresql' }, formMode: false, isDatabaseSelectEnabled: true, readOnly: false, - schema: 'public', + schema: undefined, sqlLabMode: true, getDbList: jest.fn(), getTableList: jest.fn(), @@ -129,7 +129,7 @@ beforeEach(() => { changed_on: '2021-03-09T19:02:07.141095', changed_on_delta_humanized: 'a day ago', created_by: null, - database_name: 'examples', + database_name: 'test', explore_database_id: 1, expose_in_sqllab: true, force_ctas_schema: null, @@ -153,50 +153,62 @@ test('Refresh should work', async () => { render(); + const select = screen.getByRole('combobox', { + name: 'Select a schema', + }); + + userEvent.click(select); + await waitFor(() => { - expect(SupersetClientGet).toBeCalledTimes(2); - expect(props.getDbList).toBeCalledTimes(1); + expect(SupersetClientGet).toBeCalledTimes(1); + expect(props.getDbList).toBeCalledTimes(0); expect(props.getTableList).toBeCalledTimes(0); expect(props.handleError).toBeCalledTimes(0); expect(props.onDbChange).toBeCalledTimes(0); expect(props.onSchemaChange).toBeCalledTimes(0); - expect(props.onSchemasLoad).toBeCalledTimes(1); + expect(props.onSchemasLoad).toBeCalledTimes(0); expect(props.onUpdate).toBeCalledTimes(0); }); - userEvent.click(screen.getByRole('button')); + userEvent.click(screen.getByRole('button', { name: 'refresh' })); await waitFor(() => { - expect(SupersetClientGet).toBeCalledTimes(3); - expect(props.getDbList).toBeCalledTimes(1); + expect(SupersetClientGet).toBeCalledTimes(2); + expect(props.getDbList).toBeCalledTimes(0); expect(props.getTableList).toBeCalledTimes(0); expect(props.handleError).toBeCalledTimes(0); - expect(props.onDbChange).toBeCalledTimes(1); - expect(props.onSchemaChange).toBeCalledTimes(1); + expect(props.onDbChange).toBeCalledTimes(0); + expect(props.onSchemaChange).toBeCalledTimes(0); expect(props.onSchemasLoad).toBeCalledTimes(2); - expect(props.onUpdate).toBeCalledTimes(1); + expect(props.onUpdate).toBeCalledTimes(0); }); }); test('Should database select display options', async () => { const props = createProps(); render(); - const selector = await screen.findByText('Database:'); - expect(selector).toBeInTheDocument(); - expect(selector.parentElement).toHaveTextContent( - 'Database:postgresql examples', - ); + const select = screen.getByRole('combobox', { + name: 'Select a database', + }); + expect(select).toBeInTheDocument(); + userEvent.click(select); + expect( + await screen.findByRole('option', { name: 'postgresql: test' }), + ).toBeInTheDocument(); }); test('Should schema select display options', async () => { const props = createProps(); render(); - - const selector = await screen.findByText('Schema:'); - expect(selector).toBeInTheDocument(); - expect(selector.parentElement).toHaveTextContent('Schema: public'); - - userEvent.click(screen.getByRole('button')); - - expect(await screen.findByText('Select a schema (2)')).toBeInTheDocument(); + const select = screen.getByRole('combobox', { + name: 'Select a schema', + }); + expect(select).toBeInTheDocument(); + userEvent.click(select); + expect( + await screen.findByRole('option', { name: 'public' }), + ).toBeInTheDocument(); + expect( + await screen.findByRole('option', { name: 'information_schema' }), + ).toBeInTheDocument(); }); diff --git a/superset-frontend/src/components/DatabaseSelector/index.tsx b/superset-frontend/src/components/DatabaseSelector/index.tsx index 0282e4a4a4c08..c96fba7c81e3c 100644 --- a/superset-frontend/src/components/DatabaseSelector/index.tsx +++ b/superset-frontend/src/components/DatabaseSelector/index.tsx @@ -16,58 +16,51 @@ * specific language governing permissions and limitations * under the License. */ -import React, { ReactNode, useEffect, useState } from 'react'; +import React, { ReactNode, useState, useMemo } from 'react'; import { styled, SupersetClient, t } from '@superset-ui/core'; import rison from 'rison'; -import { Select } from 'src/components/Select'; -import Label from 'src/components/Label'; +import { Select } from 'src/components'; +import { FormLabel } from 'src/components/Form'; import RefreshLabel from 'src/components/RefreshLabel'; -import SupersetAsyncSelect from 'src/components/AsyncSelect'; - -const FieldTitle = styled.p` - color: ${({ theme }) => theme.colors.secondary.light2}; - font-size: ${({ theme }) => theme.typography.sizes.s}px; - margin: 20px 0 10px 0; - text-transform: uppercase; -`; const DatabaseSelectorWrapper = styled.div` - .fa-refresh { - padding-left: 9px; - } + ${({ theme }) => ` + .refresh { + display: flex; + align-items: center; + width: 30px; + margin-left: ${theme.gridUnit}px; + margin-top: ${theme.gridUnit * 5}px; + } - .refresh-col { - display: flex; - align-items: center; - width: 30px; - margin-left: ${({ theme }) => theme.gridUnit}px; - } + .section { + display: flex; + flex-direction: row; + align-items: center; + } - .section { - padding-bottom: 5px; - display: flex; - flex-direction: row; - } + .select { + flex: 1; + } - .select { - flex-grow: 1; - } + & > div { + margin-bottom: ${theme.gridUnit * 4}px; + } + `} `; -const DatabaseOption = styled.span` - display: inline-flex; - align-items: center; -`; +type DatabaseValue = { label: string; value: number }; + +type SchemaValue = { label: string; value: string }; interface DatabaseSelectorProps { - dbId: number; + db?: { id: number; database_name: string; backend: string }; formMode?: boolean; getDbList?: (arg0: any) => {}; - getTableList?: (dbId: number, schema: string, force: boolean) => {}; handleError: (msg: string) => void; isDatabaseSelectEnabled?: boolean; onDbChange?: (db: any) => void; - onSchemaChange?: (arg0?: any) => {}; + onSchemaChange?: (schema?: string) => void; onSchemasLoad?: (schemas: Array) => void; readOnly?: boolean; schema?: string; @@ -83,10 +76,9 @@ interface DatabaseSelectorProps { } export default function DatabaseSelector({ - dbId, + db, formMode = false, getDbList, - getTableList, handleError, isDatabaseSelectEnabled = true, onUpdate, @@ -97,193 +89,189 @@ export default function DatabaseSelector({ schema, sqlLabMode = false, }: DatabaseSelectorProps) { - const [currentDbId, setCurrentDbId] = useState(dbId); - const [currentSchema, setCurrentSchema] = useState( - schema, + const [currentDb, setCurrentDb] = useState( + db + ? { label: `${db.backend}: ${db.database_name}`, value: db.id } + : undefined, + ); + const [currentSchema, setCurrentSchema] = useState( + schema ? { label: schema, value: schema } : undefined, ); - const [schemaLoading, setSchemaLoading] = useState(false); - const [schemaOptions, setSchemaOptions] = useState([]); + const [refresh, setRefresh] = useState(0); - function fetchSchemas(databaseId: number, forceRefresh = false) { - const actualDbId = databaseId || dbId; - if (actualDbId) { - setSchemaLoading(true); - const queryParams = rison.encode({ - force: Boolean(forceRefresh), - }); - const endpoint = `/api/v1/database/${actualDbId}/schemas/?q=${queryParams}`; - return SupersetClient.get({ endpoint }) - .then(({ json }) => { + const loadSchemas = useMemo( + () => async (): Promise<{ + data: SchemaValue[]; + totalCount: number; + }> => { + if (currentDb) { + const queryParams = rison.encode({ force: refresh > 0 }); + const endpoint = `/api/v1/database/${currentDb.value}/schemas/?q=${queryParams}`; + + // TODO: Would be nice to add pagination in a follow-up. Needs endpoint changes. + return SupersetClient.get({ endpoint }).then(({ json }) => { const options = json.result.map((s: string) => ({ value: s, label: s, title: s, })); - setSchemaOptions(options); - setSchemaLoading(false); if (onSchemasLoad) { onSchemasLoad(options); } - }) - .catch(() => { - setSchemaOptions([]); - setSchemaLoading(false); - handleError(t('Error while fetching schema list')); + return { + data: options, + totalCount: options.length, + }; }); - } - return Promise.resolve(); - } - - useEffect(() => { - if (currentDbId) { - fetchSchemas(currentDbId); - } - }, [currentDbId]); + } + return { + data: [], + totalCount: 0, + }; + }, + [currentDb, refresh, onSchemasLoad], + ); - function onSelectChange({ dbId, schema }: { dbId: number; schema?: string }) { - setCurrentDbId(dbId); + function onSelectChange({ + db, + schema, + }: { + db: DatabaseValue; + schema?: SchemaValue; + }) { + setCurrentDb(db); setCurrentSchema(schema); if (onUpdate) { - onUpdate({ dbId, schema, tableName: undefined }); - } - } - - function dbMutator(data: any) { - if (getDbList) { - getDbList(data.result); - } - if (data.result.length === 0) { - handleError(t("It seems you don't have access to any database")); + onUpdate({ + dbId: db.value, + schema: schema?.value, + tableName: undefined, + }); } - return data.result.map((row: any) => ({ - ...row, - // label is used for the typeahead - label: `${row.backend} ${row.database_name}`, - })); } - function changeDataBase(db: any, force = false) { - const dbId = db ? db.id : null; - setSchemaOptions([]); + function changeDataBase(selectedValue: DatabaseValue) { + const actualDb = selectedValue || db; if (onSchemaChange) { - onSchemaChange(null); + onSchemaChange(undefined); } if (onDbChange) { onDbChange(db); } - fetchSchemas(dbId, force); - onSelectChange({ dbId, schema: undefined }); + onSelectChange({ db: actualDb, schema: undefined }); } - function changeSchema(schemaOpt: any, force = false) { - const schema = schemaOpt ? schemaOpt.value : null; + function changeSchema(schema: SchemaValue) { if (onSchemaChange) { - onSchemaChange(schema); + onSchemaChange(schema.value); } - setCurrentSchema(schema); - onSelectChange({ dbId: currentDbId, schema }); - if (getTableList) { - getTableList(currentDbId, schema, force); + if (currentDb) { + onSelectChange({ db: currentDb, schema }); } } - function renderDatabaseOption(db: any) { - return ( - - {db.database_name} - - ); - } - function renderSelectRow(select: ReactNode, refreshBtn: ReactNode) { return (
{select} - {refreshBtn} + {refreshBtn}
); } - function renderDatabaseSelect() { - const queryParams = rison.encode({ - order_columns: 'database_name', - order_direction: 'asc', - page: 0, - page_size: -1, - ...(formMode || !sqlLabMode - ? {} - : { - filters: [ - { - col: 'expose_in_sqllab', - opr: 'eq', - value: true, - }, - ], + const loadDatabases = useMemo( + () => async ( + search: string, + page: number, + pageSize: number, + ): Promise<{ + data: DatabaseValue[]; + totalCount: number; + }> => { + const queryParams = rison.encode({ + order_columns: 'database_name', + order_direction: 'asc', + page, + page_size: pageSize, + ...(formMode || !sqlLabMode + ? { filters: [{ col: 'database_name', opr: 'ct', value: search }] } + : { + filters: [ + { col: 'database_name', opr: 'ct', value: search }, + { + col: 'expose_in_sqllab', + opr: 'eq', + value: true, + }, + ], + }), + }); + const endpoint = `/api/v1/database/?q=${queryParams}`; + return SupersetClient.get({ endpoint }).then(({ json }) => { + const { result } = json; + if (getDbList) { + getDbList(result); + } + if (result.length === 0) { + handleError(t("It seems you don't have access to any database")); + } + const options = result.map( + (row: { backend: string; database_name: string; id: number }) => ({ + label: `${row.backend}: ${row.database_name}`, + value: row.id, }), - }); + ); + return { + data: options, + totalCount: options.length, + }; + }); + }, + [formMode, getDbList, handleError, sqlLabMode], + ); + function renderDatabaseSelect() { return renderSelectRow( - changeDataBase(db)} - onAsyncError={() => - handleError(t('Error while fetching database list')) - } - clearable={false} - value={currentDbId} - valueKey="id" - valueRenderer={(db: any) => ( -
- {t('Database:')} - {renderDatabaseOption(db)} -
- )} - optionRenderer={renderDatabaseOption} - mutator={dbMutator} + header={{t('Database')}} + onChange={changeDataBase} + value={currentDb} placeholder={t('Select a database')} - autoSelect - isDisabled={!isDatabaseSelectEnabled || readOnly} + disabled={!isDatabaseSelectEnabled || readOnly} + options={loadDatabases} />, null, ); } function renderSchemaSelect() { - const value = schemaOptions.filter(({ value }) => currentSchema === value); - const refresh = !formMode && !readOnly && ( + const refreshIcon = !formMode && !readOnly && ( changeDataBase({ id: dbId }, true)} + onClick={() => setRefresh(refresh + 1)} tooltipContent={t('Force refresh schema list')} /> ); return renderSelectRow( - ); - } else if (formMode) { - select = ( - - ); - } else { - // sql lab - let tableSelectPlaceholder; - let tableSelectDisabled = false; - if (database && database.allow_multi_schema_metadata_fetch) { - tableSelectPlaceholder = t('Type to search ...'); - } else { - tableSelectPlaceholder = t('Select table '); - tableSelectDisabled = true; - } - select = ( - - ); - } + const disabled = + (currentSchema && !formMode && readOnly) || + (!currentSchema && !database?.allow_multi_schema_metadata_fetch); + + const header = sqlLabMode ? ( + {t('See table schema')} + ) : ( + {t('Table')} + ); + + const select = ( +
t1 t2 t3__sum
0 c11 c12 c13
1 c21 c22 c23