diff --git a/UPDATING.md b/UPDATING.md
index d844ef4ed01a..1769772d71ca 100644
--- a/UPDATING.md
+++ b/UPDATING.md
@@ -26,6 +26,7 @@ assists people when migrating to a new version.
### Breaking Changes
+- [15254](https://github.com/apache/superset/pull/15254): Previously `QUERY_COST_FORMATTERS_BY_ENGINE`, `SQL_VALIDATORS_BY_ENGINE` and `SCHEDULED_QUERIES` were expected to be defined in the feature flag dictionary in the `config.py` file. These should now be defined as a top-level config, with the feature flag dictionary being reserved for boolean only values.
- [17290](https://github.com/apache/superset/pull/17290): Bumps pandas to `1.3.4` and pyarrow to `5.0.0`
- [16660](https://github.com/apache/incubator-superset/pull/16660): The `columns` Jinja parameter has been renamed `table_columns` to make the `columns` query object parameter available in the Jinja context.
- [16711](https://github.com/apache/incubator-superset/pull/16711): The `url_param` Jinja function will now by default escape the result. For instance, the value `O'Brien` will now be changed to `O''Brien`. To disable this behavior, call `url_param` with `escape_result` set to `False`: `url_param("my_key", "my default", escape_result=False)`.
diff --git a/superset-frontend/src/SqlLab/components/ScheduleQueryButton/index.tsx b/superset-frontend/src/SqlLab/components/ScheduleQueryButton/index.tsx
index 0e08f7827da4..6f9bf9b7d412 100644
--- a/superset-frontend/src/SqlLab/components/ScheduleQueryButton/index.tsx
+++ b/superset-frontend/src/SqlLab/components/ScheduleQueryButton/index.tsx
@@ -26,6 +26,12 @@ import { Form, FormItem } from 'src/components/Form';
import './ScheduleQueryButton.less';
import Button from 'src/components/Button';
+const appContainer = document.getElementById('app');
+const bootstrapData = JSON.parse(
+ appContainer?.getAttribute('data-bootstrap') || '{}',
+);
+const scheduledQueriesConf = bootstrapData?.common?.conf?.SCHEDULED_QUERIES;
+
const validators = {
greater: (a: number, b: number) => a > b,
greater_equal: (a: number, b: number) => a >= b,
@@ -34,7 +40,7 @@ const validators = {
};
const getJSONSchema = () => {
- const jsonSchema = window.featureFlags.SCHEDULED_QUERIES?.JSONSCHEMA;
+ const jsonSchema = scheduledQueriesConf?.JSONSCHEMA;
// parse date-time into usable value (eg, 'today' => `new Date()`)
if (jsonSchema) {
Object.entries(jsonSchema.properties).forEach(
@@ -52,10 +58,9 @@ const getJSONSchema = () => {
return {};
};
-const getUISchema = () => window.featureFlags.SCHEDULED_QUERIES?.UISCHEMA;
+const getUISchema = () => scheduledQueriesConf?.UISCHEMA;
-const getValidationRules = () =>
- window.featureFlags.SCHEDULED_QUERIES?.VALIDATION || [];
+const getValidationRules = () => scheduledQueriesConf?.VALIDATION || [];
const getValidator = () => {
const rules: any = getValidationRules();
diff --git a/superset-frontend/src/SqlLab/components/SqlEditor/index.jsx b/superset-frontend/src/SqlLab/components/SqlEditor/index.jsx
index 9263832502d9..619725666636 100644
--- a/superset-frontend/src/SqlLab/components/SqlEditor/index.jsx
+++ b/superset-frontend/src/SqlLab/components/SqlEditor/index.jsx
@@ -82,6 +82,14 @@ const SET_QUERY_EDITOR_SQL_DEBOUNCE_MS = 2000;
const VALIDATION_DEBOUNCE_MS = 600;
const WINDOW_RESIZE_THROTTLE_MS = 100;
+const appContainer = document.getElementById('app');
+const bootstrapData = JSON.parse(
+ appContainer.getAttribute('data-bootstrap') || '{}',
+);
+const validatorMap =
+ bootstrapData?.common?.conf?.SQL_VALIDATORS_BY_ENGINE || {};
+const scheduledQueriesConf = bootstrapData?.common?.conf?.SCHEDULED_QUERIES;
+
const LimitSelectStyled = styled.span`
.ant-dropdown-trigger {
align-items: center;
@@ -391,8 +399,7 @@ class SqlEditor extends React.PureComponent {
canValidateQuery() {
// Check whether or not we can validate the current query based on whether
// or not the backend has a validator configured for it.
- const validatorMap = window.featureFlags.SQL_VALIDATORS_BY_ENGINE;
- if (this.props.database && validatorMap != null) {
+ if (this.props.database) {
return validatorMap.hasOwnProperty(this.props.database.backend);
}
return false;
@@ -531,7 +538,7 @@ class SqlEditor extends React.PureComponent {
/>
)}
- {isFeatureEnabled(FeatureFlag.SCHEDULED_QUERIES) && (
+ {scheduledQueriesConf && (
0,
diff --git a/superset-frontend/src/showSavedQuery/index.jsx b/superset-frontend/src/showSavedQuery/index.jsx
index 93a65b08ae59..12259cb0d4d4 100644
--- a/superset-frontend/src/showSavedQuery/index.jsx
+++ b/superset-frontend/src/showSavedQuery/index.jsx
@@ -26,7 +26,7 @@ const scheduleInfoContainer = document.getElementById('schedule-info');
const bootstrapData = JSON.parse(
scheduleInfoContainer.getAttribute('data-bootstrap'),
);
-const config = bootstrapData.common.feature_flags.SCHEDULED_QUERIES;
+const config = bootstrapData.common.conf.SCHEDULED_QUERIES;
const { query } = bootstrapData.common;
const scheduleInfo = query.extra_json.schedule_info;
const linkback = config.linkback ? interpolate(config.linkback, query) : null;
diff --git a/superset/config.py b/superset/config.py
index 05d7b8ecd706..07b3bd73c799 100644
--- a/superset/config.py
+++ b/superset/config.py
@@ -798,10 +798,12 @@ class CeleryConfig: # pylint: disable=too-few-public-methods
#
# return out
#
-# FEATURE_FLAGS = {
-# "ESTIMATE_QUERY_COST": True,
-# "QUERY_COST_FORMATTERS_BY_ENGINE": {"postgresql": postgres_query_cost_formatter},
-# }
+# Then on define the formatter on the config:
+#
+# "QUERY_COST_FORMATTERS_BY_ENGINE": {"postgresql": postgres_query_cost_formatter},
+QUERY_COST_FORMATTERS_BY_ENGINE: Dict[
+ str, Callable[[List[Dict[str, Any]]], List[Dict[str, Any]]]
+] = {}
# Flag that controls if limit should be enforced on the CTA (create table as queries).
SQLLAB_CTAS_NO_LIMIT = False
diff --git a/superset/utils/feature_flag_manager.py b/superset/utils/feature_flag_manager.py
index 86d2487f8cc0..9874656722e6 100644
--- a/superset/utils/feature_flag_manager.py
+++ b/superset/utils/feature_flag_manager.py
@@ -15,7 +15,7 @@
# specific language governing permissions and limitations
# under the License.
from copy import deepcopy
-from typing import Any, Dict
+from typing import Dict
from flask import Flask
@@ -25,7 +25,7 @@ def __init__(self) -> None:
super().__init__()
self._get_feature_flags_func = None
self._is_feature_enabled_func = None
- self._feature_flags: Dict[str, Any] = {}
+ self._feature_flags: Dict[str, bool] = {}
def init_app(self, app: Flask) -> None:
self._get_feature_flags_func = app.config["GET_FEATURE_FLAGS_FUNC"]
@@ -33,7 +33,7 @@ def init_app(self, app: Flask) -> None:
self._feature_flags = app.config["DEFAULT_FEATURE_FLAGS"]
self._feature_flags.update(app.config["FEATURE_FLAGS"])
- def get_feature_flags(self) -> Dict[str, Any]:
+ def get_feature_flags(self) -> Dict[str, bool]:
if self._get_feature_flags_func:
return self._get_feature_flags_func(deepcopy(self._feature_flags))
if callable(self._is_feature_enabled_func):
diff --git a/superset/views/base.py b/superset/views/base.py
index 05c2a2994aaf..72dc8053d461 100644
--- a/superset/views/base.py
+++ b/superset/views/base.py
@@ -96,6 +96,7 @@
"DISPLAY_MAX_ROW",
"GLOBAL_ASYNC_QUERIES_TRANSPORT",
"GLOBAL_ASYNC_QUERIES_POLLING_DELAY",
+ "SQL_VALIDATORS_BY_ENGINE",
"SQLALCHEMY_DOCS_URL",
"SQLALCHEMY_DISPLAY_TEXT",
"GLOBAL_ASYNC_QUERIES_WEBSOCKET_URL",
diff --git a/superset/views/core.py b/superset/views/core.py
index 38a791319bf3..9afcbb8e3def 100755
--- a/superset/views/core.py
+++ b/superset/views/core.py
@@ -51,7 +51,6 @@
conf,
db,
event_logger,
- get_feature_flags,
is_feature_enabled,
results_backend,
results_backend_use_msgpack,
@@ -2221,9 +2220,9 @@ def estimate_query_cost( # pylint: disable=no-self-use
return json_error_response(utils.error_msg_from_exception(ex))
spec = mydb.db_engine_spec
- query_cost_formatters: Dict[str, Any] = get_feature_flags().get(
- "QUERY_COST_FORMATTERS_BY_ENGINE", {}
- )
+ query_cost_formatters: Dict[str, Any] = app.config[
+ "QUERY_COST_FORMATTERS_BY_ENGINE"
+ ]
query_cost_formatter = query_cost_formatters.get(
spec.engine, spec.query_cost_formatter
)
@@ -2414,7 +2413,7 @@ def validate_sql_json(
)
spec = mydb.db_engine_spec
- validators_by_engine = get_feature_flags().get("SQL_VALIDATORS_BY_ENGINE")
+ validators_by_engine = app.config["SQL_VALIDATORS_BY_ENGINE"]
if not validators_by_engine or spec.engine not in validators_by_engine:
return json_error_response(
"no SQL validator is configured for {}".format(spec.engine), status=400
diff --git a/tests/integration_tests/sql_validator_tests.py b/tests/integration_tests/sql_validator_tests.py
index e26b8416b7fa..6c7658336b81 100644
--- a/tests/integration_tests/sql_validator_tests.py
+++ b/tests/integration_tests/sql_validator_tests.py
@@ -34,13 +34,11 @@
from .base_tests import SupersetTestCase
-PRESTO_TEST_FEATURE_FLAGS = {
- "SQL_VALIDATORS_BY_ENGINE": {
- "presto": "PrestoDBSQLValidator",
- "sqlite": "PrestoDBSQLValidator",
- "postgresql": "PrestoDBSQLValidator",
- "mysql": "PrestoDBSQLValidator",
- }
+PRESTO_SQL_VALIDATORS_BY_ENGINE = {
+ "presto": "PrestoDBSQLValidator",
+ "sqlite": "PrestoDBSQLValidator",
+ "postgresql": "PrestoDBSQLValidator",
+ "mysql": "PrestoDBSQLValidator",
}
@@ -65,8 +63,8 @@ def test_validate_sql_endpoint_noconfig(self):
@patch("superset.views.core.get_validator_by_name")
@patch.dict(
- "superset.extensions.feature_flag_manager._feature_flags",
- PRESTO_TEST_FEATURE_FLAGS,
+ "superset.config.SQL_VALIDATORS_BY_ENGINE",
+ PRESTO_SQL_VALIDATORS_BY_ENGINE,
clear=True,
)
def test_validate_sql_endpoint_mocked(self, get_validator_by_name):
@@ -98,8 +96,8 @@ def test_validate_sql_endpoint_mocked(self, get_validator_by_name):
@patch("superset.views.core.get_validator_by_name")
@patch.dict(
- "superset.extensions.feature_flag_manager._feature_flags",
- PRESTO_TEST_FEATURE_FLAGS,
+ "superset.config.SQL_VALIDATORS_BY_ENGINE",
+ PRESTO_SQL_VALIDATORS_BY_ENGINE,
clear=True,
)
def test_validate_sql_endpoint_failure(self, get_validator_by_name):