You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by di...@apache.org on 2024/10/15 13:36:50 UTC

(superset) branch geido/refactor/native-filters-form created (now 4134228d51)

This is an automated email from the ASF dual-hosted git repository.

diegopucci pushed a change to branch geido/refactor/native-filters-form
in repository https://gitbox.apache.org/repos/asf/superset.git


      at 4134228d51 Merge branch 'master' of https://github.com/apache/superset into geido/refactor/native-filters-form

This branch includes the following new commits:

     new f9d9c99ee9 refactor(Dashboard): Improve native filters form performance
     new 4134228d51 Merge branch 'master' of https://github.com/apache/superset into geido/refactor/native-filters-form

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



(superset) 02/02: Merge branch 'master' of https://github.com/apache/superset into geido/refactor/native-filters-form

Posted by di...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

diegopucci pushed a commit to branch geido/refactor/native-filters-form
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 4134228d515a14cc7601fd1d32de2b1379917133
Merge: f9d9c99ee9 6ede3271ff
Author: Diego Pucci <di...@gmail.com>
AuthorDate: Tue Oct 15 16:36:19 2024 +0300

    Merge branch 'master' of https://github.com/apache/superset into geido/refactor/native-filters-form

 docs/static/resources/openapi.json                 |   3 +-
 superset-frontend/jest.config.js                   |   2 +-
 superset-frontend/package-lock.json                | 455 ++++++++++++---------
 superset-frontend/package.json                     |   8 +-
 .../superset-ui-chart-controls/package.json        |   6 +-
 .../src/utils/D3Formatting.ts                      |   2 +
 .../packages/superset-ui-core/package.json         |  38 +-
 .../factories/createMemoryFormatter.ts             |  55 +++
 .../superset-ui-core/src/number-format/index.ts    |   1 +
 .../superset-ui-core/src/query/types/Query.ts      |   1 +
 .../test/chart/clients/ChartClient.test.ts         |   2 +-
 .../test/connection/SupersetClient.test.ts         |   8 +-
 .../test/connection/SupersetClientClass.test.ts    |  37 +-
 .../test/connection/callApi/callApi.test.ts        |  78 ++--
 .../callApi/callApiAndParseWithTimeout.test.ts     |  13 +-
 .../test/connection/callApi/parseResponse.test.ts  |  14 +-
 .../factories/createMemoryFormatter.test.ts        |  94 +++++
 .../test/number-format/index.test.ts               |   2 +
 .../query/api/legacy/getDatasourceMetadata.test.ts |   4 +-
 .../test/query/api/legacy/getFormData.test.ts      |   4 +-
 .../test/query/api/setupClientForTest.ts           |   1 +
 .../test/query/api/v1/getChartData.test.ts         |   4 +-
 .../test/query/api/v1/makeApi.test.ts              |   4 +-
 .../test/time-comparison/fetchTimeRange.test.ts    |  20 +-
 .../legacy-plugin-chart-partition/package.json     |   2 +-
 .../plugins/plugin-chart-table/package.json        |   6 +-
 superset-frontend/spec/helpers/testing-library.tsx |   2 +-
 .../ShareSqlLabQuery/ShareSqlLabQuery.test.tsx     |   2 +-
 .../SqlLab/components/SouthPane/SouthPane.test.tsx |   2 +-
 .../AlteredSliceTag/AlteredSliceTag.test.jsx       |   2 +-
 .../ErrorMessage/InvalidSQLErrorMessage.test.tsx   |   2 +-
 .../ErrorMessage/MarshmallowErrorMessage.test.tsx  |   2 +-
 .../FiltersConfigModal/FiltersConfigModal.test.tsx |   4 +-
 .../tests/CurrentCalendarFrame.test.tsx            |   2 +-
 .../ColumnSelectPopover.test.tsx                   |   2 +-
 .../OAuth2ClientField.test.tsx                     |   2 +-
 .../databases/DatabaseModal/index.test.tsx         |   2 +
 .../UploadDataModel/UploadDataModal.test.tsx       |   6 +-
 superset-frontend/src/setup/setupErrorMessages.ts  |   4 +
 superset-frontend/src/setup/setupFormatters.ts     |   5 +-
 superset/config.py                                 |   3 +
 superset/errors.py                                 |   3 +
 superset/sql/parse.py                              | 108 ++++-
 superset/sql_lab.py                                |  36 ++
 tests/integration_tests/sql_lab/api_tests.py       |   2 +-
 tests/unit_tests/db_engine_specs/test_base.py      |  16 +-
 tests/unit_tests/sql/parse_tests.py                |  34 ++
 tests/unit_tests/sql_lab_test.py                   | 115 +++++-
 48 files changed, 847 insertions(+), 373 deletions(-)

diff --cc tests/integration_tests/sql_lab/api_tests.py
index e6c1f686b1,cf1e190bbb..e1ea4cd395
--- a/tests/integration_tests/sql_lab/api_tests.py
+++ b/tests/integration_tests/sql_lab/api_tests.py
@@@ -281,10 -281,10 +281,10 @@@ class TestSqlLabApi(SupersetTestCase)
              "/api/v1/sqllab/format_sql/",
              json=data,
          )
-         success_resp = {"result": "SELECT\n  1\nFROM my_table"}
+         success_resp = {"result": "SELECT 1\nFROM my_table"}
          resp_data = json.loads(rv.data.decode("utf-8"))
 -        self.assertDictEqual(resp_data, success_resp)  # noqa: PT009
 -        assert rv.status_code == 200
 +        self.assertDictEqual(resp_data, success_resp)
 +        self.assertEqual(rv.status_code, 200)
  
      @mock.patch("superset.commands.sql_lab.results.results_backend_use_msgpack", False)
      def test_execute_required_params(self):


(superset) 01/02: refactor(Dashboard): Improve native filters form performance

Posted by di...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

diegopucci pushed a commit to branch geido/refactor/native-filters-form
in repository https://gitbox.apache.org/repos/asf/superset.git

commit f9d9c99ee9d76b270ec664c56554b754b54d9604
Author: Diego Pucci <di...@gmail.com>
AuthorDate: Tue Oct 15 16:36:09 2024 +0300

    refactor(Dashboard): Improve native filters form performance
---
 pyproject.toml                                     |    1 -
 .../src/controlPanel.ts                            |    6 -
 .../src/SqlLab/components/ResultSet/index.tsx      |    2 +-
 .../src/SqlLab/components/SqlEditor/index.tsx      |    2 +-
 superset-frontend/src/components/Chart/Chart.jsx   |  361 ++++++
 .../src/components/Chart/ChartErrorMessage.tsx     |   12 +-
 .../src/dashboard/actions/dashboardInfo.ts         |    6 +
 .../src/dashboard/actions/dashboardState.js        |    4 +-
 .../src/dashboard/actions/nativeFilters.ts         |  108 +-
 .../FilterBar/FilterConfigurationLink/index.tsx    |    8 +-
 .../FilterBar/FilterControls/FilterValue.tsx       |    1 -
 .../FilterScope/FilterScope.test.tsx               |    1 +
 .../FiltersConfigForm/FiltersConfigForm.tsx        |   61 +-
 .../FiltersConfigForm/getControlItemsMap.test.tsx  |    1 +
 .../FiltersConfigForm/getControlItemsMap.tsx       |    4 +
 .../FiltersConfigModal/FiltersConfigModal.test.tsx |  196 ++-
 .../FiltersConfigModal/FiltersConfigModal.tsx      |  147 ++-
 .../nativeFilters/FiltersConfigModal/types.ts      |   11 +
 .../nativeFilters/FiltersConfigModal/utils.ts      |  115 +-
 .../src/dashboard/reducers/dashboardInfo.js        |   37 +
 .../src/dashboard/reducers/nativeFilters.ts        |   45 +-
 superset-frontend/src/dataMask/actions.ts          |   35 +-
 superset-frontend/src/dataMask/reducer.ts          |   41 +-
 superset/commands/dashboard/exceptions.py          |    4 +
 superset/commands/dashboard/export.py              |   34 +-
 superset/commands/dashboard/update.py              |   17 +-
 superset/config.py                                 |    5 -
 superset/connectors/sqla/models.py                 |    2 -
 superset/constants.py                              |    1 +
 superset/daos/dashboard.py                         |   64 +
 superset/dashboards/api.py                         |   97 +-
 superset/dashboards/schemas.py                     |    6 +
 superset/initialization/__init__.py                |    5 -
 superset/jinja_context.py                          |   59 +-
 superset/models/helpers.py                         |   21 +-
 superset/models/sql_lab.py                         |    1 -
 superset/sql_parse.py                              |  103 +-
 superset/translations/sl/LC_MESSAGES/messages.po   | 1273 ++++++++++----------
 tests/integration_tests/async_events/api_tests.py  |    6 +-
 tests/integration_tests/base_api_tests.py          |   35 +-
 tests/integration_tests/cache_tests.py             |   24 +-
 tests/integration_tests/charts/api_tests.py        |  400 +++---
 tests/integration_tests/charts/commands_tests.py   |   16 +-
 tests/integration_tests/charts/data/api_tests.py   |   69 +-
 tests/integration_tests/charts/schema_tests.py     |    4 +-
 tests/integration_tests/core_tests.py              |  168 +--
 tests/integration_tests/dashboard_tests.py         |   20 +-
 tests/integration_tests/dashboards/api_tests.py    |  644 ++++++----
 tests/integration_tests/dashboards/base_case.py    |   10 +-
 .../integration_tests/dashboards/commands_tests.py |   45 -
 tests/integration_tests/dashboards/dao_tests.py    |   30 +-
 .../dashboards/security/security_dataset_tests.py  |   16 +-
 .../dashboards/security/security_rbac_tests.py     |    6 +-
 tests/integration_tests/databases/api_tests.py     |  381 +++---
 .../integration_tests/databases/commands_tests.py  |    6 +-
 tests/integration_tests/datasets/api_tests.py      |   75 +-
 tests/integration_tests/datasets/commands_tests.py |    6 +-
 tests/integration_tests/datasource/api_tests.py    |   43 +-
 tests/integration_tests/datasource_tests.py        |   95 +-
 .../db_engine_specs/ascend_tests.py                |   10 +-
 .../db_engine_specs/base_engine_spec_tests.py      |   65 +-
 .../db_engine_specs/base_tests.py                  |    2 +-
 .../db_engine_specs/bigquery_tests.py              |   12 +-
 .../db_engine_specs/elasticsearch_tests.py         |    2 +-
 .../db_engine_specs/mysql_tests.py                 |    6 +-
 .../db_engine_specs/pinot_tests.py                 |   30 +-
 .../db_engine_specs/postgres_tests.py              |   42 +-
 .../db_engine_specs/presto_tests.py                |   46 +-
 .../integration_tests/dict_import_export_tests.py  |   68 +-
 tests/integration_tests/dynamic_plugins_tests.py   |    4 +-
 tests/integration_tests/email_tests.py             |    4 +-
 tests/integration_tests/embedded/dao_tests.py      |   10 +-
 tests/integration_tests/event_logger_tests.py      |  108 +-
 tests/integration_tests/form_tests.py              |   12 +-
 tests/integration_tests/import_export_tests.py     |  198 +--
 tests/integration_tests/log_api_tests.py           |  137 ++-
 .../logging_configurator_tests.py                  |    2 +-
 tests/integration_tests/model_tests.py             |   67 +-
 tests/integration_tests/queries/api_tests.py       |   28 +-
 .../queries/saved_queries/api_tests.py             |   18 +-
 tests/integration_tests/query_context_tests.py     |  112 +-
 tests/integration_tests/reports/api_tests.py       |    6 +-
 tests/integration_tests/result_set_tests.py        |  333 ++---
 tests/integration_tests/security/api_tests.py      |    6 +-
 .../security/guest_token_security_tests.py         |   30 +-
 .../security/row_level_security_tests.py           |   78 +-
 tests/integration_tests/security_tests.py          |  475 ++++----
 tests/integration_tests/sql_lab/api_tests.py       |   48 +-
 tests/integration_tests/sql_validator_tests.py     |    4 +-
 tests/integration_tests/sqla_models_tests.py       |  230 ++--
 tests/integration_tests/sqllab_tests.py            |   80 +-
 tests/integration_tests/strategy_tests.py          |   12 +-
 tests/integration_tests/tagging_tests.py           |   68 +-
 tests/integration_tests/tags/api_tests.py          |   66 +-
 tests/integration_tests/thumbnails_tests.py        |   36 +-
 tests/integration_tests/users/api_tests.py         |   18 +-
 tests/integration_tests/utils/encrypt_tests.py     |   12 +-
 .../integration_tests/utils/machine_auth_tests.py  |    2 +-
 tests/integration_tests/utils_tests.py             |  238 ++--
 tests/integration_tests/viz_tests.py               |  240 ++--
 tests/unit_tests/jinja_context_test.py             |  393 +++---
 tests/unit_tests/sql/parse_tests.py                |   13 -
 tests/unit_tests/sql_parse_tests.py                |   50 +-
 103 files changed, 4727 insertions(+), 3690 deletions(-)

diff --git a/pyproject.toml b/pyproject.toml
index ada303a4e9..e39b9da690 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -446,7 +446,6 @@ select = [
     "E7",
     "E9",
     "F",
-    "PT009",
     "TRY201",
 ]
 ignore = []
diff --git a/superset-frontend/plugins/legacy-plugin-chart-horizon/src/controlPanel.ts b/superset-frontend/plugins/legacy-plugin-chart-horizon/src/controlPanel.ts
index 51a43a450e..c96b2f0ed7 100644
--- a/superset-frontend/plugins/legacy-plugin-chart-horizon/src/controlPanel.ts
+++ b/superset-frontend/plugins/legacy-plugin-chart-horizon/src/controlPanel.ts
@@ -24,12 +24,6 @@ import {
 
 const config: ControlPanelConfig = {
   controlPanelSections: [
-    {
-      label: t('Time'),
-      expanded: true,
-      description: t('Time related form attributes'),
-      controlSetRows: [['granularity_sqla'], ['time_range']],
-    },
     {
       label: t('Query'),
       expanded: true,
diff --git a/superset-frontend/src/SqlLab/components/ResultSet/index.tsx b/superset-frontend/src/SqlLab/components/ResultSet/index.tsx
index 7d0df189a2..3e9c9db5c6 100644
--- a/superset-frontend/src/SqlLab/components/ResultSet/index.tsx
+++ b/superset-frontend/src/SqlLab/components/ResultSet/index.tsx
@@ -646,7 +646,7 @@ const ResultSet = ({
         : [];
       const allowHTML = getItem(
         LocalStorageKeys.SqllabIsRenderHtmlEnabled,
-        true,
+        false,
       );
       return (
         <ResultContainer>
diff --git a/superset-frontend/src/SqlLab/components/SqlEditor/index.tsx b/superset-frontend/src/SqlLab/components/SqlEditor/index.tsx
index 731053ac0b..e5b24b72ad 100644
--- a/superset-frontend/src/SqlLab/components/SqlEditor/index.tsx
+++ b/superset-frontend/src/SqlLab/components/SqlEditor/index.tsx
@@ -308,7 +308,7 @@ const SqlEditor: FC<Props> = ({
     getItem(LocalStorageKeys.SqllabIsAutocompleteEnabled, true),
   );
   const [renderHTMLEnabled, setRenderHTMLEnabled] = useState(
-    getItem(LocalStorageKeys.SqllabIsRenderHtmlEnabled, true),
+    getItem(LocalStorageKeys.SqllabIsRenderHtmlEnabled, false),
   );
   const [showCreateAsModal, setShowCreateAsModal] = useState(false);
   const [createAs, setCreateAs] = useState('');
diff --git a/superset-frontend/src/components/Chart/Chart.jsx b/superset-frontend/src/components/Chart/Chart.jsx
new file mode 100644
index 0000000000..ea1a2d968c
--- /dev/null
+++ b/superset-frontend/src/components/Chart/Chart.jsx
@@ -0,0 +1,361 @@
+/**
+ * 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 PropTypes from 'prop-types';
+import { PureComponent } from 'react';
+import {
+  ensureIsArray,
+  FeatureFlag,
+  isFeatureEnabled,
+  logging,
+  styled,
+  t,
+} from '@superset-ui/core';
+import { PLACEHOLDER_DATASOURCE } from 'src/dashboard/constants';
+import Loading from 'src/components/Loading';
+import { EmptyStateBig } from 'src/components/EmptyState';
+import ErrorBoundary from 'src/components/ErrorBoundary';
+import { Logger, LOG_ACTIONS_RENDER_CHART } from 'src/logger/LogUtils';
+import { URL_PARAMS } from 'src/constants';
+import { getUrlParam } from 'src/utils/urlUtils';
+import { isCurrentUserBot } from 'src/utils/isBot';
+import { ChartSource } from 'src/types/ChartSource';
+import { ResourceStatus } from 'src/hooks/apiResources/apiResources';
+import ChartRenderer from './ChartRenderer';
+import { ChartErrorMessage } from './ChartErrorMessage';
+import { getChartRequiredFieldsMissingMessage } from '../../utils/getChartRequiredFieldsMissingMessage';
+
+const propTypes = {
+  annotationData: PropTypes.object,
+  actions: PropTypes.object,
+  chartId: PropTypes.number.isRequired,
+  datasource: PropTypes.object,
+  // current chart is included by dashboard
+  dashboardId: PropTypes.number,
+  // original selected values for FilterBox viz
+  // so that FilterBox can pre-populate selected values
+  // only affect UI control
+  initialValues: PropTypes.object,
+  // formData contains chart's own filter parameter
+  // and merged with extra filter that current dashboard applying
+  formData: PropTypes.object.isRequired,
+  labelsColor: PropTypes.object,
+  labelsColorMap: PropTypes.object,
+  width: PropTypes.number,
+  height: PropTypes.number,
+  setControlValue: PropTypes.func,
+  timeout: PropTypes.number,
+  vizType: PropTypes.string.isRequired,
+  triggerRender: PropTypes.bool,
+  force: PropTypes.bool,
+  isFiltersInitialized: PropTypes.bool,
+  // state
+  chartAlert: PropTypes.string,
+  chartStatus: PropTypes.string,
+  chartStackTrace: PropTypes.string,
+  queriesResponse: PropTypes.arrayOf(PropTypes.object),
+  triggerQuery: PropTypes.bool,
+  chartIsStale: PropTypes.bool,
+  errorMessage: PropTypes.node,
+  // dashboard callbacks
+  addFilter: PropTypes.func,
+  onQuery: PropTypes.func,
+  onFilterMenuOpen: PropTypes.func,
+  onFilterMenuClose: PropTypes.func,
+  ownState: PropTypes.object,
+  postTransformProps: PropTypes.func,
+  datasetsStatus: PropTypes.oneOf(['loading', 'error', 'complete']),
+  isInView: PropTypes.bool,
+  emitCrossFilters: PropTypes.bool,
+};
+
+const BLANK = {};
+const NONEXISTENT_DATASET = t(
+  'The dataset associated with this chart no longer exists',
+);
+
+const defaultProps = {
+  addFilter: () => BLANK,
+  onFilterMenuOpen: () => BLANK,
+  onFilterMenuClose: () => BLANK,
+  initialValues: BLANK,
+  setControlValue() {},
+  triggerRender: false,
+  dashboardId: null,
+  chartStackTrace: null,
+  force: false,
+  isInView: true,
+};
+
+const Styles = styled.div`
+  min-height: ${p => p.height}px;
+  position: relative;
+  text-align: center;
+
+  .chart-tooltip {
+    opacity: 0.75;
+    font-size: ${({ theme }) => theme.typography.sizes.s}px;
+  }
+
+  .slice_container {
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+
+    height: ${p => p.height}px;
+
+    .pivot_table tbody tr {
+      font-feature-settings: 'tnum' 1;
+    }
+
+    .alert {
+      margin: ${({ theme }) => theme.gridUnit * 2}px;
+    }
+  }
+`;
+
+const LoadingDiv = styled.div`
+  position: absolute;
+  left: 50%;
+  top: 50%;
+  width: 80%;
+  transform: translate(-50%, -50%);
+`;
+
+const MessageSpan = styled.span`
+  display: block;
+  margin: ${({ theme }) => theme.gridUnit * 4}px auto;
+  width: fit-content;
+  color: ${({ theme }) => theme.colors.grayscale.base};
+`;
+
+const MonospaceDiv = styled.div`
+  font-family: ${({ theme }) => theme.typography.families.monospace};
+  word-break: break-word;
+  overflow-x: auto;
+  white-space: pre-wrap;
+`;
+
+class Chart extends PureComponent {
+  constructor(props) {
+    super(props);
+    this.handleRenderContainerFailure =
+      this.handleRenderContainerFailure.bind(this);
+  }
+
+  componentDidMount() {
+    if (this.props.triggerQuery) {
+      this.runQuery();
+    }
+  }
+
+  componentDidUpdate() {
+    if (this.props.triggerQuery) {
+      this.runQuery();
+    }
+  }
+
+  runQuery() {
+    // Create chart with POST request
+    this.props.actions.postChartFormData(
+      this.props.formData,
+      Boolean(this.props.force || getUrlParam(URL_PARAMS.force)), // allow override via url params force=true
+      this.props.timeout,
+      this.props.chartId,
+      this.props.dashboardId,
+      this.props.ownState,
+    );
+  }
+
+  handleRenderContainerFailure(error, info) {
+    const { actions, chartId } = this.props;
+    logging.warn(error);
+    actions.chartRenderingFailed(
+      error.toString(),
+      chartId,
+      info ? info.componentStack : null,
+    );
+
+    actions.logEvent(LOG_ACTIONS_RENDER_CHART, {
+      slice_id: chartId,
+      has_err: true,
+      error_details: error.toString(),
+      start_offset: this.renderStartTime,
+      ts: new Date().getTime(),
+      duration: Logger.getTimestamp() - this.renderStartTime,
+    });
+  }
+
+  renderErrorMessage(queryResponse) {
+    const {
+      chartId,
+      chartAlert,
+      chartStackTrace,
+      datasource,
+      dashboardId,
+      height,
+      datasetsStatus,
+    } = this.props;
+    const error = queryResponse?.errors?.[0];
+    const message = chartAlert || queryResponse?.message;
+
+    // if datasource is still loading, don't render JS errors
+    if (
+      chartAlert !== undefined &&
+      chartAlert !== NONEXISTENT_DATASET &&
+      datasource === PLACEHOLDER_DATASOURCE &&
+      datasetsStatus !== ResourceStatus.Error
+    ) {
+      return (
+        <Styles
+          key={chartId}
+          data-ui-anchor="chart"
+          className="chart-container"
+          data-test="chart-container"
+          height={height}
+        >
+          <Loading />
+        </Styles>
+      );
+    }
+
+    return (
+      <ChartErrorMessage
+        key={chartId}
+        chartId={chartId}
+        error={error}
+        subtitle={<MonospaceDiv>{message}</MonospaceDiv>}
+        copyText={message}
+        link={queryResponse ? queryResponse.link : null}
+        source={dashboardId ? ChartSource.Dashboard : ChartSource.Explore}
+        stackTrace={chartStackTrace}
+      />
+    );
+  }
+
+  renderSpinner(databaseName) {
+    const message = databaseName
+      ? t('Waiting on %s', databaseName)
+      : t('Waiting on database...');
+
+    return (
+      <LoadingDiv>
+        <Loading position="inline-centered" />
+        <MessageSpan>{message}</MessageSpan>
+      </LoadingDiv>
+    );
+  }
+
+  renderChartContainer() {
+    return (
+      <div className="slice_container" data-test="slice-container">
+        {this.props.isInView ||
+        !isFeatureEnabled(FeatureFlag.DashboardVirtualization) ||
+        isCurrentUserBot() ? (
+          <ChartRenderer
+            {...this.props}
+            source={this.props.dashboardId ? 'dashboard' : 'explore'}
+            data-test={this.props.vizType}
+          />
+        ) : (
+          <Loading />
+        )}
+      </div>
+    );
+  }
+
+  render() {
+    const {
+      height,
+      chartAlert,
+      chartStatus,
+      datasource,
+      errorMessage,
+      chartIsStale,
+      queriesResponse = [],
+      width,
+    } = this.props;
+    const databaseName = datasource?.database?.name;
+
+    const isLoading = chartStatus === 'loading';
+    this.renderContainerStartTime = Logger.getTimestamp();
+    if (chartStatus === 'failed') {
+      return queriesResponse.map(item => this.renderErrorMessage(item));
+    }
+
+    if (errorMessage && ensureIsArray(queriesResponse).length === 0) {
+      return (
+        <EmptyStateBig
+          title={t('Add required control values to preview chart')}
+          description={getChartRequiredFieldsMissingMessage(true)}
+          image="chart.svg"
+        />
+      );
+    }
+
+    if (
+      !isLoading &&
+      !chartAlert &&
+      !errorMessage &&
+      chartIsStale &&
+      ensureIsArray(queriesResponse).length === 0
+    ) {
+      return (
+        <EmptyStateBig
+          title={t('Your chart is ready to go!')}
+          description={
+            <span>
+              {t(
+                'Click on "Create chart" button in the control panel on the left to preview a visualization or',
+              )}{' '}
+              <span role="button" tabIndex={0} onClick={this.props.onQuery}>
+                {t('click here')}
+              </span>
+              .
+            </span>
+          }
+          image="chart.svg"
+        />
+      );
+    }
+
+    return (
+      <ErrorBoundary
+        onError={this.handleRenderContainerFailure}
+        showMessage={false}
+      >
+        <Styles
+          data-ui-anchor="chart"
+          className="chart-container"
+          data-test="chart-container"
+          height={height}
+          width={width}
+        >
+          {isLoading
+            ? this.renderSpinner(databaseName)
+            : this.renderChartContainer()}
+        </Styles>
+      </ErrorBoundary>
+    );
+  }
+}
+
+Chart.propTypes = propTypes;
+Chart.defaultProps = defaultProps;
+
+export default Chart;
diff --git a/superset-frontend/src/components/Chart/ChartErrorMessage.tsx b/superset-frontend/src/components/Chart/ChartErrorMessage.tsx
index 142c057000..25f2f683c2 100644
--- a/superset-frontend/src/components/Chart/ChartErrorMessage.tsx
+++ b/superset-frontend/src/components/Chart/ChartErrorMessage.tsx
@@ -17,21 +17,15 @@
  * under the License.
  */
 
-import { ClientErrorObject, SupersetError } from '@superset-ui/core';
 import { FC } from 'react';
+import { SupersetError } from '@superset-ui/core';
 import { useChartOwnerNames } from 'src/hooks/apiResources';
 import ErrorMessageWithStackTrace from 'src/components/ErrorMessage/ErrorMessageWithStackTrace';
-import { ChartSource } from 'src/types/ChartSource';
 
-export type Props = {
+interface Props {
   chartId: string;
   error?: SupersetError;
-  subtitle: JSX.Element;
-  copyText: string | undefined;
-  link?: string;
-  source: ChartSource;
-  stackTrace?: string;
-} & Omit<ClientErrorObject, 'error'>;
+}
 
 /**
  * fetches the chart owners and adds them to the extra data of the error message
diff --git a/superset-frontend/src/dashboard/actions/dashboardInfo.ts b/superset-frontend/src/dashboard/actions/dashboardInfo.ts
index 14659b9fc3..ea25e8c715 100644
--- a/superset-frontend/src/dashboard/actions/dashboardInfo.ts
+++ b/superset-frontend/src/dashboard/actions/dashboardInfo.ts
@@ -29,11 +29,17 @@ import {
 import { onSave } from './dashboardState';
 
 export const DASHBOARD_INFO_UPDATED = 'DASHBOARD_INFO_UPDATED';
+export const DASHBOARD_INFO_FILTERS_CHANGED = 'DASHBOARD_INFO_FILTERS_CHANGED';
 
 // updates partially changed dashboard info
 export function dashboardInfoChanged(newInfo: { metadata: any }) {
   return { type: DASHBOARD_INFO_UPDATED, newInfo };
 }
+
+export function nativeFiltersConfigChanged(newInfo: Record<string, any>) {
+  return { type: DASHBOARD_INFO_FILTERS_CHANGED, newInfo };
+}
+
 export const SAVE_CHART_CONFIG_BEGIN = 'SAVE_CHART_CONFIG_BEGIN';
 export const SAVE_CHART_CONFIG_COMPLETE = 'SAVE_CHART_CONFIG_COMPLETE';
 export const SAVE_CHART_CONFIG_FAIL = 'SAVE_CHART_CONFIG_FAIL';
diff --git a/superset-frontend/src/dashboard/actions/dashboardState.js b/superset-frontend/src/dashboard/actions/dashboardState.js
index 9b0c181821..443db787e0 100644
--- a/superset-frontend/src/dashboard/actions/dashboardState.js
+++ b/superset-frontend/src/dashboard/actions/dashboardState.js
@@ -63,7 +63,7 @@ import {
 } from './dashboardInfo';
 import { fetchDatasourceMetadata } from './datasources';
 import { updateDirectPathToFilter } from './dashboardFilters';
-import { SET_FILTER_CONFIG_COMPLETE } from './nativeFilters';
+import { SET_IN_SCOPE_STATUS_OF_FILTERS } from './nativeFilters';
 import getOverwriteItems from '../util/getOverwriteItems';
 import {
   applyColors,
@@ -337,7 +337,7 @@ export function saveDashboardRequest(data, id, saveType) {
         }
         if (metadata.native_filter_configuration) {
           dispatch({
-            type: SET_FILTER_CONFIG_COMPLETE,
+            type: SET_IN_SCOPE_STATUS_OF_FILTERS,
             filterConfig: metadata.native_filter_configuration,
           });
         }
diff --git a/superset-frontend/src/dashboard/actions/nativeFilters.ts b/superset-frontend/src/dashboard/actions/nativeFilters.ts
index 968805dbb7..f594080b70 100644
--- a/superset-frontend/src/dashboard/actions/nativeFilters.ts
+++ b/superset-frontend/src/dashboard/actions/nativeFilters.ts
@@ -19,28 +19,31 @@
 import { FilterConfiguration, Filters, makeApi } from '@superset-ui/core';
 import { Dispatch } from 'redux';
 import { cloneDeep } from 'lodash';
-import {
-  SET_DATA_MASK_FOR_FILTER_CONFIG_FAIL,
-  setDataMaskForFilterConfigComplete,
-} from 'src/dataMask/actions';
+import { setDataMaskForFilterChangesComplete } from 'src/dataMask/actions';
 import { HYDRATE_DASHBOARD } from './hydrate';
-import { dashboardInfoChanged } from './dashboardInfo';
-import { DashboardInfo } from '../types';
+import {
+  dashboardInfoChanged,
+  nativeFiltersConfigChanged,
+} from './dashboardInfo';
+import { SaveFilterChangesType } from '../components/nativeFilters/FiltersConfigModal/types';
 
-export const SET_FILTER_CONFIG_BEGIN = 'SET_FILTER_CONFIG_BEGIN';
-export interface SetFilterConfigBegin {
-  type: typeof SET_FILTER_CONFIG_BEGIN;
+export const SET_NATIVE_FILTERS_CONFIG_BEGIN =
+  'SET_NATIVE_FILTERS_CONFIG_BEGIN';
+export interface SetNativeFiltersConfigBegin {
+  type: typeof SET_NATIVE_FILTERS_CONFIG_BEGIN;
   filterConfig: FilterConfiguration;
 }
 
-export const SET_FILTER_CONFIG_COMPLETE = 'SET_FILTER_CONFIG_COMPLETE';
-export interface SetFilterConfigComplete {
-  type: typeof SET_FILTER_CONFIG_COMPLETE;
-  filterConfig: FilterConfiguration;
+export const SET_NATIVE_FILTERS_CONFIG_COMPLETE =
+  'SET_NATIVE_FILTERS_CONFIG_COMPLETE';
+export interface SetNativeFiltersConfigComplete {
+  type: typeof SET_NATIVE_FILTERS_CONFIG_COMPLETE;
+  filterChanges: SaveFilterChangesType;
 }
-export const SET_FILTER_CONFIG_FAIL = 'SET_FILTER_CONFIG_FAIL';
-export interface SetFilterConfigFail {
-  type: typeof SET_FILTER_CONFIG_FAIL;
+
+export const SET_NATIVE_FILTERS_CONFIG_FAIL = 'SET_NATIVE_FILTERS_CONFIG_FAIL';
+export interface SetNativeFiltersConfigFail {
+  type: typeof SET_NATIVE_FILTERS_CONFIG_FAIL;
   filterConfig: FilterConfiguration;
 }
 export const SET_IN_SCOPE_STATUS_OF_FILTERS = 'SET_IN_SCOPE_STATUS_OF_FILTERS';
@@ -49,60 +52,45 @@ export interface SetInScopeStatusOfFilters {
   filterConfig: FilterConfiguration;
 }
 
+const isFilterChangesEmpty = (filterChanges: SaveFilterChangesType) =>
+  Object.values(filterChanges).every(
+    array => Array.isArray(array) && !array.length,
+  );
+
 export const setFilterConfiguration =
-  (filterConfig: FilterConfiguration) =>
+  (filterChanges: SaveFilterChangesType) =>
   async (dispatch: Dispatch, getState: () => any) => {
+    if (isFilterChangesEmpty(filterChanges)) {
+      return;
+    }
+
+    const { id } = getState().dashboardInfo;
+    const oldFilters = getState().nativeFilters?.filters;
+
     dispatch({
-      type: SET_FILTER_CONFIG_BEGIN,
-      filterConfig,
+      type: SET_NATIVE_FILTERS_CONFIG_BEGIN,
+      filterChanges,
     });
-    const { id, metadata } = getState().dashboardInfo;
-    const oldFilters = getState().nativeFilters?.filters;
 
-    // TODO extract this out when makeApi supports url parameters
-    const updateDashboard = makeApi<
-      Partial<DashboardInfo>,
-      { result: DashboardInfo }
+    const updateFilters = makeApi<
+      SaveFilterChangesType,
+      { result: SaveFilterChangesType }
     >({
       method: 'PUT',
-      endpoint: `/api/v1/dashboard/${id}`,
+      endpoint: `/api/v1/dashboard/${id}/filters`,
     });
-
-    const mergedFilterConfig = filterConfig.map(filter => {
-      const oldFilter = oldFilters[filter.id];
-      if (!oldFilter) {
-        return filter;
-      }
-      return { ...oldFilter, ...filter };
-    });
-
     try {
-      const response = await updateDashboard({
-        json_metadata: JSON.stringify({
-          ...metadata,
-          native_filter_configuration: mergedFilterConfig,
-        }),
-      });
-      dispatch(
-        dashboardInfoChanged({
-          metadata: JSON.parse(response.result.json_metadata),
-        }),
-      );
+      const response = await updateFilters(filterChanges);
+      dispatch(nativeFiltersConfigChanged(response.result));
       dispatch({
-        type: SET_FILTER_CONFIG_COMPLETE,
-        filterConfig: mergedFilterConfig,
+        type: SET_NATIVE_FILTERS_CONFIG_COMPLETE,
+        filterChanges,
       });
-      dispatch(
-        setDataMaskForFilterConfigComplete(mergedFilterConfig, oldFilters),
-      );
+      dispatch(setDataMaskForFilterChangesComplete(filterChanges, oldFilters));
     } catch (err) {
       dispatch({
-        type: SET_FILTER_CONFIG_FAIL,
-        filterConfig: mergedFilterConfig,
-      });
-      dispatch({
-        type: SET_DATA_MASK_FOR_FILTER_CONFIG_FAIL,
-        filterConfig: mergedFilterConfig,
+        type: SET_NATIVE_FILTERS_CONFIG_FAIL,
+        filterConfig: filterChanges,
       });
     }
   };
@@ -221,9 +209,9 @@ export function updateCascadeParentIds(
 }
 
 export type AnyFilterAction =
-  | SetFilterConfigBegin
-  | SetFilterConfigComplete
-  | SetFilterConfigFail
+  | SetNativeFiltersConfigBegin
+  | SetNativeFiltersConfigComplete
+  | SetNativeFiltersConfigFail
   | SetInScopeStatusOfFilters
   | SetBootstrapData
   | SetFocusedNativeFilter
diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterConfigurationLink/index.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterConfigurationLink/index.tsx
index d7e8564eb5..7be1e7814c 100644
--- a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterConfigurationLink/index.tsx
+++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterConfigurationLink/index.tsx
@@ -21,9 +21,10 @@ import { ReactNode, FC, useCallback, useState, memo } from 'react';
 import { useDispatch } from 'react-redux';
 import { setFilterConfiguration } from 'src/dashboard/actions/nativeFilters';
 import Button from 'src/components/Button';
-import { FilterConfiguration, styled } from '@superset-ui/core';
+import { styled } from '@superset-ui/core';
 import FiltersConfigModal from 'src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigModal';
 import { getFilterBarTestId } from '../utils';
+import { SaveFilterChangesType } from '../../FiltersConfigModal/types';
 
 export interface FCBProps {
   createNewOnOpen?: boolean;
@@ -46,14 +47,13 @@ export const FilterConfigurationLink: FC<FCBProps> = ({
 }) => {
   const dispatch = useDispatch();
   const [isOpen, setOpen] = useState(false);
-
   const close = useCallback(() => {
     setOpen(false);
   }, [setOpen]);
 
   const submit = useCallback(
-    async (filterConfig: FilterConfiguration) => {
-      dispatch(await setFilterConfiguration(filterConfig));
+    async (filterChanges: SaveFilterChangesType) => {
+      dispatch(await setFilterConfiguration(filterChanges));
       close();
     },
     [dispatch, close],
diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterValue.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterValue.tsx
index 2d85f413c3..45ccd4dd41 100644
--- a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterValue.tsx
+++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterValue.tsx
@@ -182,7 +182,6 @@ const FilterValue: FC<FilterControlProps> = ({
           if (isFeatureEnabled(FeatureFlag.GlobalAsyncQueries)) {
             // deal with getChartDataRequest transforming the response data
             const result = 'result' in json ? json.result[0] : json;
-
             if (response.status === 200) {
               setState([result]);
               handleFilterLoadFinish();
diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/FilterScope/FilterScope.test.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/FilterScope/FilterScope.test.tsx
index 93f3dd6ec1..aece899a16 100644
--- a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/FilterScope/FilterScope.test.tsx
+++ b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/FilterScope/FilterScope.test.tsx
@@ -47,6 +47,7 @@ describe('FilterScope', () => {
     activeFilterPanelKeys: `DefaultFilterId-${FilterPanels.configuration.key}`,
     isActive: true,
     validateDependencies: jest.fn(),
+    onModifyFilter: jest.fn(),
   };
 
   const MockModal = ({ scope }: { scope?: object }) => {
diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/FiltersConfigForm.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/FiltersConfigForm.tsx
index 77a423df76..b7c66412fa 100644
--- a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/FiltersConfigForm.tsx
+++ b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/FiltersConfigForm.tsx
@@ -39,8 +39,9 @@ import {
   t,
   ClientErrorObject,
   getClientErrorObject,
+  SLOW_DEBOUNCE,
 } from '@superset-ui/core';
-import { isEqual } from 'lodash';
+import { debounce, isEqual } from 'lodash';
 import {
   forwardRef,
   useCallback,
@@ -306,6 +307,7 @@ export interface FiltersConfigFormProps {
   filterToEdit?: Filter;
   removedFilters: Record<string, FilterRemoval>;
   restoreFilter: (filterId: string) => void;
+  onModifyFilter: (filterId: string) => void;
   form: FormInstance<NativeFiltersForm>;
   getAvailableFilters: (
     filterId: string,
@@ -346,6 +348,7 @@ const FiltersConfigForm = (
     restoreFilter,
     handleActiveFilterPanelChange,
     setErroredFilters,
+    onModifyFilter,
     validateDependencies,
     getDependencySuggestion,
     isActive,
@@ -372,6 +375,12 @@ const FiltersConfigForm = (
   const formValues = filters?.[filterId];
   const formFilter = formValues || undoFormValues || defaultFormFilter;
 
+  const handleModifyFilter = useCallback(() => {
+    if (onModifyFilter) {
+      onModifyFilter(filterId);
+    }
+  }, [onModifyFilter, filterId]);
+
   const dependencies: string[] =
     formFilter?.dependencies || filterToEdit?.cascadeParentIds || [];
 
@@ -412,12 +421,28 @@ const FiltersConfigForm = (
     filterToEdit?.targets[0]?.datasetId ??
     mostUsedDataset(loadedDatasets, charts);
 
+  const formChanged = useCallback(() => {
+    form.setFields([
+      {
+        name: 'changed',
+        value: true,
+      },
+    ]);
+    handleModifyFilter();
+  }, [form, handleModifyFilter]);
+
+  const debouncedFormChanged = useCallback(
+    debounce(formChanged, SLOW_DEBOUNCE),
+    [],
+  );
+
   const { controlItems = {}, mainControlItems = {} } = formFilter
     ? getControlItemsMap({
         expanded,
         datasetId,
         disabled: false,
         forceUpdate,
+        formChanged,
         form,
         filterId,
         filterType: formFilter?.filterType,
@@ -488,7 +513,6 @@ const FiltersConfigForm = (
         groupby: formFilter?.column,
         ...formFilter,
       });
-
       formData.extra_form_data = dependenciesDefaultValues;
 
       setNativeFilterFieldValuesWrapper({
@@ -549,6 +573,7 @@ const FiltersConfigForm = (
     groupby: hasColumn ? formFilter?.column : undefined,
     ...formFilter,
   });
+
   newFormData.extra_form_data = dependenciesDefaultValues;
 
   const [hasDefaultValue, isRequired, defaultValueTooltip, setHasDefaultValue] =
@@ -557,15 +582,6 @@ const FiltersConfigForm = (
   const showDataset =
     !datasetId || datasetDetails || formFilter?.dataset?.label;
 
-  const formChanged = useCallback(() => {
-    form.setFields([
-      {
-        name: 'changed',
-        value: true,
-      },
-    ]);
-  }, [form]);
-
   const updateFormValues = useCallback(
     (values: any) => {
       setNativeFilterFieldValues(form, filterId, values);
@@ -794,6 +810,7 @@ const FiltersConfigForm = (
             granularity_sqla: column,
           });
           forceUpdate();
+          formChanged();
         }}
       />
     </StyledRowFormItem>
@@ -817,7 +834,7 @@ const FiltersConfigForm = (
             hidden
             initialValue={NativeFilterType.NativeFilter}
           >
-            <Input />
+            <Input onChange={formChanged} />
           </StyledFormItem>
           <StyledFormItem
             expanded={expanded}
@@ -826,7 +843,10 @@ const FiltersConfigForm = (
             initialValue={filterToEdit?.name}
             rules={[{ required: !isRemoved, message: t('Name is required') }]}
           >
-            <Input {...getFiltersConfigModalTestId('name-input')} />
+            <Input
+              {...getFiltersConfigModalTestId('name-input')}
+              onChange={debouncedFormChanged}
+            />
           </StyledFormItem>
           <StyledFormItem
             expanded={expanded}
@@ -870,6 +890,7 @@ const FiltersConfigForm = (
                   column: null,
                 });
                 forceUpdate();
+                formChanged();
               }}
             />
           </StyledFormItem>
@@ -920,6 +941,7 @@ const FiltersConfigForm = (
                       });
                     }
                     forceUpdate();
+                    formChanged();
                   }}
                 />
               </StyledFormItem>
@@ -1018,6 +1040,7 @@ const FiltersConfigForm = (
                             adhoc_filters: filters,
                           });
                           forceUpdate();
+                          formChanged();
                           validatePreFilter();
                         }}
                         label={
@@ -1050,6 +1073,7 @@ const FiltersConfigForm = (
                               time_range: timeRange,
                             });
                             forceUpdate();
+                            formChanged();
                             validatePreFilter();
                           }}
                         />
@@ -1085,6 +1109,7 @@ const FiltersConfigForm = (
                       <Radio.Group
                         onChange={value => {
                           onSortChanged(value.target.value);
+                          formChanged();
                         }}
                       >
                         <Radio value>{t('Sort ascending')}</Radio>
@@ -1124,6 +1149,7 @@ const FiltersConfigForm = (
                               });
                               forceUpdate();
                             }
+                            formChanged();
                           }}
                         />
                       </StyledRowSubFormItem>
@@ -1156,9 +1182,10 @@ const FiltersConfigForm = (
                       }
                     >
                       <Radio.Group
-                        onChange={value =>
-                          onEnableSingleValueChanged(value.target.value)
-                        }
+                        onChange={value => {
+                          onEnableSingleValueChanged(value.target.value);
+                          formChanged();
+                        }}
                       >
                         <Radio value={SingleValueType.Minimum}>
                           {t('Minimum')}
@@ -1187,7 +1214,7 @@ const FiltersConfigForm = (
               initialValue={filterToEdit?.description}
               label={<StyledLabel>{t('Description')}</StyledLabel>}
             >
-              <TextArea />
+              <TextArea onChange={debouncedFormChanged} />
             </StyledFormItem>
             <CleanFormItem
               name={['filters', filterId, 'defaultValueQueriesData']}
diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/getControlItemsMap.test.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/getControlItemsMap.test.tsx
index 93d31990ad..9d2fc12e0c 100644
--- a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/getControlItemsMap.test.tsx
+++ b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/getControlItemsMap.test.tsx
@@ -72,6 +72,7 @@ const createProps: () => ControlItemsProps = () => ({
   filterId: 'filterId',
   filterToEdit: filterMock,
   filterType: 'filterType',
+  formChanged: jest.fn(),
 });
 
 const createControlItems = () => [
diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/getControlItemsMap.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/getControlItemsMap.tsx
index 62fd4917ec..ed92809988 100644
--- a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/getControlItemsMap.tsx
+++ b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/getControlItemsMap.tsx
@@ -48,6 +48,7 @@ export interface ControlItemsProps {
   datasetId: number;
   disabled: boolean;
   forceUpdate: Function;
+  formChanged: Function;
   form: FormInstance<NativeFiltersForm>;
   filterId: string;
   filterType: string;
@@ -65,6 +66,7 @@ export default function getControlItemsMap({
   datasetId,
   disabled,
   forceUpdate,
+  formChanged,
   form,
   filterId,
   filterType,
@@ -137,6 +139,7 @@ export default function getControlItemsMap({
                   defaultDataMask: null,
                 });
                 forceUpdate();
+                formChanged();
               }}
             />
           </StyledFormItem>
@@ -200,6 +203,7 @@ export default function getControlItemsMap({
                       defaultDataMask: null,
                     });
                   }
+                  formChanged();
                   forceUpdate();
                 }}
               >
diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigModal.test.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigModal.test.tsx
index dbb1c2fc47..353b429bd3 100644
--- a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigModal.test.tsx
+++ b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigModal.test.tsx
@@ -23,7 +23,12 @@ import chartQueries from 'spec/fixtures/mockChartQueries';
 import { dashboardLayout } from 'spec/fixtures/mockDashboardLayout';
 import mockDatasource, { datasourceId, id } from 'spec/fixtures/mockDatasource';
 import { buildNativeFilter } from 'spec/fixtures/mockNativeFilters';
-import { render, screen, waitFor } from 'spec/helpers/testing-library';
+import {
+  fireEvent,
+  render,
+  screen,
+  waitFor,
+} from 'spec/helpers/testing-library';
 import {
   RangeFilterPlugin,
   SelectFilterPlugin,
@@ -147,6 +152,10 @@ beforeAll(() => {
   new MainPreset().register();
 });
 
+afterEach(() => {
+  jest.restoreAllMocks();
+});
+
 function defaultRender(initialState: any = defaultState(), modalProps = props) {
   return render(<FiltersConfigModal {...modalProps} />, {
     initialState,
@@ -373,29 +382,24 @@ test('deletes a filter', async () => {
     dashboardLayout,
   };
   const onSave = jest.fn();
+
   defaultRender(state, {
     ...props,
     createNewOnOpen: false,
     onSave,
   });
   const removeButtons = screen.getAllByRole('img', { name: 'trash' });
-  // remove NATIVE_FILTER-3 which isn't a dependency of any other filter
   userEvent.click(removeButtons[2]);
+
   userEvent.click(screen.getByRole('button', { name: SAVE_REGEX }));
+
   await waitFor(() =>
     expect(onSave).toHaveBeenCalledWith(
-      expect.arrayContaining([
-        expect.objectContaining({
-          type: 'NATIVE_FILTER',
-          id: 'NATIVE_FILTER-1',
-          cascadeParentIds: ['NATIVE_FILTER-2'],
-        }),
-        expect.objectContaining({
-          type: 'NATIVE_FILTER',
-          id: 'NATIVE_FILTER-2',
-          cascadeParentIds: [],
-        }),
-      ]),
+      expect.objectContaining({
+        deleted: expect.arrayContaining(['NATIVE_FILTER-3']),
+        modified: expect.arrayContaining([]),
+        reordered: expect.arrayContaining([]),
+      }),
     ),
   );
 });
@@ -420,23 +424,161 @@ test('deletes a filter including dependencies', async () => {
     onSave,
   });
   const removeButtons = screen.getAllByRole('img', { name: 'trash' });
-  // remove NATIVE_FILTER-2 which is a dependency of NATIVE_FILTER-1
   userEvent.click(removeButtons[1]);
   userEvent.click(screen.getByRole('button', { name: SAVE_REGEX }));
   await waitFor(() =>
     expect(onSave).toHaveBeenCalledWith(
-      expect.arrayContaining([
-        expect.objectContaining({
-          type: 'NATIVE_FILTER',
-          id: 'NATIVE_FILTER-1',
-          cascadeParentIds: [],
-        }),
-        expect.objectContaining({
-          type: 'NATIVE_FILTER',
-          id: 'NATIVE_FILTER-3',
-          cascadeParentIds: [],
-        }),
-      ]),
+      expect.objectContaining({
+        deleted: ['NATIVE_FILTER-2'],
+        modified: expect.arrayContaining([
+          expect.objectContaining({
+            id: 'NATIVE_FILTER-1',
+          }),
+        ]),
+        reordered: [],
+      }),
+    ),
+  );
+});
+
+test('switches the order between two filters', async () => {
+  const nativeFilterState = [
+    buildNativeFilter('NATIVE_FILTER-1', 'state', []),
+    buildNativeFilter('NATIVE_FILTER-2', 'country', []),
+  ];
+
+  const state = {
+    ...defaultState(),
+    dashboardInfo: {
+      metadata: { native_filter_configuration: nativeFilterState },
+    },
+    dashboardLayout,
+  };
+
+  const onSave = jest.fn();
+
+  defaultRender(state, {
+    ...props,
+    createNewOnOpen: false,
+    onSave,
+  });
+
+  const draggableFilters = screen.getAllByRole('tab');
+
+  fireEvent.dragStart(draggableFilters[0]);
+
+  fireEvent.dragOver(draggableFilters[1]);
+
+  fireEvent.drop(draggableFilters[1]);
+
+  fireEvent.dragEnd(draggableFilters[0]);
+
+  userEvent.click(screen.getByRole('button', { name: SAVE_REGEX }));
+
+  await waitFor(() =>
+    expect(onSave).toHaveBeenCalledWith(
+      expect.objectContaining({
+        deleted: [],
+        modified: [],
+        reordered: expect.arrayContaining([
+          'NATIVE_FILTER-2',
+          'NATIVE_FILTER-1',
+        ]),
+      }),
+    ),
+  );
+});
+
+test('rearranges three filters and deletes one of them', async () => {
+  const nativeFilterState = [
+    buildNativeFilter('NATIVE_FILTER-1', 'state', []),
+    buildNativeFilter('NATIVE_FILTER-2', 'country', []),
+    buildNativeFilter('NATIVE_FILTER-3', 'product', []),
+  ];
+
+  const state = {
+    ...defaultState(),
+    dashboardInfo: {
+      metadata: { native_filter_configuration: nativeFilterState },
+    },
+    dashboardLayout,
+  };
+
+  const onSave = jest.fn();
+
+  defaultRender(state, {
+    ...props,
+    createNewOnOpen: false,
+    onSave,
+  });
+
+  const draggableFilters = screen.getAllByRole('tab');
+  const deleteButtons = screen.getAllByRole('img', { name: 'trash' });
+  userEvent.click(deleteButtons[1]);
+
+  fireEvent.dragStart(draggableFilters[0]);
+  fireEvent.dragOver(draggableFilters[2]);
+  fireEvent.drop(draggableFilters[2]);
+  fireEvent.dragEnd(draggableFilters[0]);
+
+  userEvent.click(screen.getByRole('button', { name: SAVE_REGEX }));
+
+  await waitFor(() =>
+    expect(onSave).toHaveBeenCalledWith(
+      expect.objectContaining({
+        modified: [],
+        deleted: ['NATIVE_FILTER-2'],
+        reordered: expect.arrayContaining([
+          'NATIVE_FILTER-2',
+          'NATIVE_FILTER-3',
+          'NATIVE_FILTER-1',
+        ]),
+      }),
+    ),
+  );
+});
+
+test('modifies the name of a filter', async () => {
+  jest.useFakeTimers();
+  const nativeFilterState = [
+    buildNativeFilter('NATIVE_FILTER-1', 'state', []),
+    buildNativeFilter('NATIVE_FILTER-2', 'country', []),
+  ];
+
+  const state = {
+    ...defaultState(),
+    dashboardInfo: {
+      metadata: { native_filter_configuration: nativeFilterState },
+    },
+    dashboardLayout,
+  };
+
+  const onSave = jest.fn();
+
+  defaultRender(state, {
+    ...props,
+    createNewOnOpen: false,
+    onSave,
+  });
+
+  const filterNameInput = screen.getByRole('textbox', {
+    name: FILTER_NAME_REGEX,
+  });
+
+  userEvent.clear(filterNameInput);
+  userEvent.type(filterNameInput, 'New Filter Name');
+
+  jest.runAllTimers();
+
+  userEvent.click(screen.getByRole('button', { name: SAVE_REGEX }));
+
+  await waitFor(() =>
+    expect(onSave).toHaveBeenCalledWith(
+      expect.objectContaining({
+        modified: expect.arrayContaining([
+          expect.objectContaining({ name: 'New Filter Name' }),
+        ]),
+      }),
     ),
   );
 });
diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigModal.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigModal.tsx
index 7ad2838d26..9432075689 100644
--- a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigModal.tsx
+++ b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigModal.tsx
@@ -20,7 +20,6 @@ import { memo, useEffect, useCallback, useMemo, useState, useRef } from 'react';
 import { uniq, isEqual, sortBy, debounce, isEmpty } from 'lodash';
 import {
   Filter,
-  FilterConfiguration,
   NativeFilterType,
   Divider,
   styled,
@@ -44,7 +43,12 @@ import FiltersConfigForm, {
 } from './FiltersConfigForm/FiltersConfigForm';
 import Footer from './Footer/Footer';
 import { useOpenModal, useRemoveCurrentFilter } from './state';
-import { FilterRemoval, NativeFiltersForm } from './types';
+import {
+  FilterChangesType,
+  FilterRemoval,
+  NativeFiltersForm,
+  SaveFilterChangesType,
+} from './types';
 import {
   createHandleSave,
   createHandleRemoveItem,
@@ -114,7 +118,7 @@ export interface FiltersConfigModalProps {
   isOpen: boolean;
   initialFilterId?: string;
   createNewOnOpen?: boolean;
-  onSave: (filterConfig: FilterConfiguration) => Promise<void>;
+  onSave: (filterChanges: SaveFilterChangesType) => Promise<void>;
   onCancel: () => void;
 }
 export const ALLOW_DEPENDENCIES = [
@@ -128,6 +132,11 @@ const DEFAULT_REMOVED_FILTERS: Record<string, FilterRemoval> = {};
 const DEFAULT_FORM_VALUES: NativeFiltersForm = {
   filters: {},
 };
+const DEFAULT_FILTER_CHANGES: FilterChangesType = {
+  modified: [],
+  deleted: [],
+  reordered: [],
+};
 
 /**
  * This is the modal to configure all the dashboard-native filters.
@@ -155,6 +164,27 @@ function FiltersConfigModal({
   const filterConfig = useFilterConfiguration();
   const filterConfigMap = useFilterConfigMap();
 
+  // this state contains the changes that we'll be sent through the PATCH endpoint
+  const [filterChanges, setFilterChanges] = useState<FilterChangesType>(
+    DEFAULT_FILTER_CHANGES,
+  );
+
+  const resetFilterChanges = () => {
+    setFilterChanges(DEFAULT_FILTER_CHANGES);
+  };
+
+  const handleModifyFilter = useCallback(
+    (filterId: string) => {
+      if (!filterChanges.modified.includes(filterId)) {
+        setFilterChanges(prev => ({
+          ...prev,
+          modified: [...prev.modified, filterId],
+        }));
+      }
+    },
+    [filterChanges.modified],
+  );
+
   // new filter ids belong to filters have been added during
   // this configuration session, and only exist in the form state until we submit.
   const [newFilterIds, setNewFilterIds] = useState<string[]>(
@@ -200,11 +230,17 @@ function FiltersConfigModal({
   const restoreFilter = useCallback(
     (id: string) => {
       const removal = removedFilters[id];
-      // gotta clear the removal timeout to prevent the filter from getting deleted
+      // Clear the removal timeout if the filter is pending deletion
       if (removal?.isPending) clearTimeout(removal.timerId);
+
       setRemovedFilters(current => ({ ...current, [id]: null }));
+
+      setFilterChanges(prev => ({
+        ...prev,
+        deleted: prev.deleted.filter(deletedId => deletedId !== id),
+      }));
     },
-    [removedFilters],
+    [removedFilters, setRemovedFilters],
   );
   const initialFilterOrder = useMemo(
     () => Object.keys(filterConfigMap),
@@ -239,18 +275,13 @@ function FiltersConfigModal({
     (type: NativeFilterType) => {
       const newFilterId = generateFilterId(type);
       setNewFilterIds([...newFilterIds, newFilterId]);
+      handleModifyFilter(newFilterId);
       setCurrentFilterId(newFilterId);
       setSaveAlertVisible(false);
       setOrderedFilters([...orderedFilters, newFilterId]);
       setActiveFilterPanelKey(getActiveFilterPanelKey(newFilterId));
     },
-    [
-      newFilterIds,
-      orderedFilters,
-      setCurrentFilterId,
-      setOrderedFilters,
-      setNewFilterIds,
-    ],
+    [newFilterIds, handleModifyFilter, orderedFilters],
   );
 
   useOpenModal(isOpen, addFilter, createNewOnOpen);
@@ -266,6 +297,13 @@ function FiltersConfigModal({
     setRemovedFilters,
     setOrderedFilters,
     setSaveAlertVisible,
+    filterChanges,
+    filterId => {
+      setFilterChanges(prev => ({
+        ...prev,
+        deleted: [...prev.deleted, filterId],
+      }));
+    },
   );
 
   // After this, it should be as if the modal was just opened fresh.
@@ -276,6 +314,7 @@ function FiltersConfigModal({
     setRemovedFilters(DEFAULT_REMOVED_FILTERS);
     setSaveAlertVisible(false);
     setFormValues(DEFAULT_FORM_VALUES);
+    resetFilterChanges();
     setErroredFilters(DEFAULT_EMPTY_FILTERS);
     if (filterIds.length > 0) {
       setActiveFilterPanelKey(getActiveFilterPanelKey(filterIds[0]));
@@ -329,19 +368,32 @@ function FiltersConfigModal({
           value: id,
           type: filterConfigMap[id]?.filterType,
         })),
-    [canBeUsedAsDependency, filterIds, getFilterTitle],
+    [canBeUsedAsDependency, filterConfigMap, filterIds, getFilterTitle],
   );
 
+  /**
+   * Manages dependencies of filters associated with a deleted filter.
+   *
+   * @param values the native filters form
+   * @returns the updated filterConfigMap
+   */
   const cleanDeletedParents = (values: NativeFiltersForm | null) => {
+    const modifiedParentFilters = new Set<string>();
     const updatedFilterConfigMap = Object.keys(filterConfigMap).reduce(
       (acc, key) => {
         const filter = filterConfigMap[key];
         const cascadeParentIds = filter.cascadeParentIds?.filter(id =>
           canBeUsedAsDependency(id),
         );
-        if (cascadeParentIds) {
+
+        if (
+          cascadeParentIds &&
+          !isEqual(cascadeParentIds, filter.cascadeParentIds)
+        ) {
           dispatch(updateCascadeParentIds(key, cascadeParentIds));
+          modifiedParentFilters.add(key);
         }
+
         return {
           ...acc,
           [key]: {
@@ -357,18 +409,24 @@ function FiltersConfigModal({
     if (filters) {
       Object.keys(filters).forEach(key => {
         const filter = filters[key];
+
         if (!('dependencies' in filter)) {
           return;
         }
-        const { dependencies } = filter;
-        if (dependencies) {
-          filter.dependencies = dependencies.filter(id =>
-            canBeUsedAsDependency(id),
-          );
+
+        const originalDependencies = filter.dependencies || [];
+        const cleanedDependencies = originalDependencies.filter(id =>
+          canBeUsedAsDependency(id),
+        );
+
+        if (!isEqual(cleanedDependencies, originalDependencies)) {
+          filter.dependencies = cleanedDependencies;
+          modifiedParentFilters.add(key);
         }
       });
     }
-    return updatedFilterConfigMap;
+
+    return [updatedFilterConfigMap, modifiedParentFilters];
   };
 
   const handleErroredFilters = useCallback(() => {
@@ -407,18 +465,35 @@ function FiltersConfigModal({
     handleErroredFilters();
 
     if (values) {
-      const updatedFilterConfigMap = cleanDeletedParents(values);
-      createHandleSave(
-        updatedFilterConfigMap,
-        orderedFilters,
-        removedFilters,
-        onSave,
-        values,
-      )();
+      const [updatedFilterConfigMap, modifiedParentFilters] =
+        cleanDeletedParents(values);
+
+      const allModified = [
+        ...new Set([
+          ...(modifiedParentFilters as Set<string>),
+          ...filterChanges.modified,
+        ]),
+      ];
+
+      const actualChanges = {
+        ...filterChanges,
+        modified:
+          allModified.length && filterChanges.deleted.length
+            ? allModified.filter(id => !filterChanges.deleted.includes(id))
+            : allModified,
+        reordered:
+          filterChanges.reordered.length &&
+          !isEqual(filterChanges.reordered, initialFilterOrder)
+            ? filterChanges.reordered
+            : [],
+      };
+
+      createHandleSave(onSave, actualChanges, values, updatedFilterConfigMap)();
       resetForm(true);
     } else {
       configFormRef.current?.changeTab?.('configuration');
     }
+    resetFilterChanges();
   };
 
   const handleConfirmCancel = () => {
@@ -447,6 +522,10 @@ function FiltersConfigModal({
     const removed = newOrderedFilter.splice(dragIndex, 1)[0];
     newOrderedFilter.splice(targetIndex, 0, removed);
     setOrderedFilters(newOrderedFilter);
+    setFilterChanges(prev => ({
+      ...prev,
+      reordered: newOrderedFilter,
+    }));
   };
 
   const buildDependencyMap = useCallback(() => {
@@ -596,28 +675,34 @@ function FiltersConfigModal({
                 setErroredFilters={setErroredFilters}
                 validateDependencies={validateDependencies}
                 getDependencySuggestion={getDependencySuggestion}
+                onModifyFilter={handleModifyFilter}
               />
             )}
           </div>
         );
       }),
     [
-      renderedFilters,
       orderedFilters,
+      renderedFilters,
       currentFilterId,
       filterConfigMap,
+      expanded,
       form,
       removedFilters,
       restoreFilter,
       getAvailableFilters,
       activeFilterPanelKey,
+      handleActiveFilterPanelChange,
       validateDependencies,
       getDependencySuggestion,
-      handleActiveFilterPanelChange,
-      expanded,
+      handleModifyFilter,
     ],
   );
 
+  useEffect(() => {
+    resetFilterChanges();
+  }, []);
+
   return (
     <StyledModalWrapper
       visible={isOpen}
diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/types.ts b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/types.ts
index 6f40ea6542..41d53791f6 100644
--- a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/types.ts
+++ b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/types.ts
@@ -21,6 +21,7 @@ import {
   DataMask,
   NativeFilterType,
   NativeFilterScope,
+  Filter,
 } from '@superset-ui/core';
 
 export interface NativeFiltersFormItem {
@@ -60,6 +61,16 @@ export interface NativeFiltersForm {
   changed?: boolean;
 }
 
+export type FilterChangesType = {
+  modified: string[];
+  deleted: string[];
+  reordered: string[];
+};
+
+export type SaveFilterChangesType = {
+  modified: Filter[];
+} & Omit<FilterChangesType, 'modified'>;
+
 export type FilterRemoval =
   | null
   | {
diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/utils.ts b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/utils.ts
index ffe53378ab..f0d09d9aa3 100644
--- a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/utils.ts
+++ b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/utils.ts
@@ -20,15 +20,15 @@ import { FormInstance } from 'src/components';
 import { nanoid } from 'nanoid';
 import { getInitialDataMask } from 'src/dataMask/reducer';
 import {
-  Filter,
   FilterConfiguration,
   NativeFilterType,
-  Divider,
   NativeFilterTarget,
   logging,
+  Filter,
+  Divider,
 } from '@superset-ui/core';
 import { DASHBOARD_ROOT_ID } from 'src/dashboard/util/constants';
-import { FilterRemoval, NativeFiltersForm } from './types';
+import { FilterChangesType, FilterRemoval, NativeFiltersForm } from './types';
 
 export const REMOVAL_DELAY_SECS = 5;
 
@@ -92,62 +92,69 @@ export const validateForm = async (
 
 export const createHandleSave =
   (
-    filterConfigMap: Record<string, Filter | Divider>,
-    filterIds: string[],
-    removedFilters: Record<string, FilterRemoval>,
     saveForm: Function,
+    filterChanges: FilterChangesType,
     values: NativeFiltersForm,
+    filterConfigMap: Record<string, Filter | Divider>,
   ) =>
   async () => {
-    const newFilterConfig: FilterConfiguration = filterIds
-      .filter(id => !removedFilters[id])
-      .map(id => {
-        // create a filter config object from the form inputs
-        const formInputs = values.filters?.[id];
-        // if user didn't open a filter, return the original config
-        if (!formInputs) return filterConfigMap[id];
-        if (formInputs.type === NativeFilterType.Divider) {
-          return {
-            id,
-            type: NativeFilterType.Divider,
-            scope: {
-              rootPath: [DASHBOARD_ROOT_ID],
-              excluded: [],
-            },
-            title: formInputs.title,
-            description: formInputs.description,
-          };
-        }
-        const target: Partial<NativeFilterTarget> = {};
-        if (formInputs.dataset) {
-          target.datasetId = formInputs.dataset.value;
-        }
-        if (formInputs.dataset && formInputs.column) {
-          target.column = { name: formInputs.column };
-        }
+    const transformFilter = (id: string) => {
+      const formInputs = values.filters?.[id] || filterConfigMap[id];
+      if (!formInputs) {
+        return undefined;
+      }
+      if (formInputs.type === NativeFilterType.Divider) {
         return {
           id,
-          adhoc_filters: formInputs.adhoc_filters,
-          time_range: formInputs.time_range,
-          controlValues: formInputs.controlValues ?? {},
-          granularity_sqla: formInputs.granularity_sqla,
-          requiredFirst: Object.values(formInputs.requiredFirst ?? {}).find(
-            rf => rf,
-          ),
-          name: formInputs.name,
-          filterType: formInputs.filterType,
-          // for now there will only ever be one target
-          targets: [target],
-          defaultDataMask: formInputs.defaultDataMask ?? getInitialDataMask(),
-          cascadeParentIds: formInputs.dependencies || [],
-          scope: formInputs.scope,
-          sortMetric: formInputs.sortMetric,
-          type: formInputs.type,
-          description: (formInputs.description || '').trim(),
+          type: NativeFilterType.Divider,
+          scope: {
+            rootPath: [DASHBOARD_ROOT_ID],
+            excluded: [],
+          },
+          title: formInputs.title,
+          description: formInputs.description,
         };
-      });
+      }
 
-    await saveForm(newFilterConfig);
+      const target: Partial<NativeFilterTarget> = {};
+      if (formInputs.dataset) {
+        target.datasetId = formInputs.dataset.value;
+      }
+      if (formInputs.dataset && formInputs.column) {
+        target.column = { name: formInputs.column };
+      }
+
+      return {
+        id,
+        adhoc_filters: formInputs.adhoc_filters,
+        time_range: formInputs.time_range,
+        controlValues: formInputs.controlValues ?? {},
+        granularity_sqla: formInputs.granularity_sqla,
+        requiredFirst: Object.values(formInputs.requiredFirst ?? {}).find(
+          rf => rf,
+        ),
+        name: formInputs.name,
+        filterType: formInputs.filterType,
+        targets: [target],
+        defaultDataMask: formInputs.defaultDataMask ?? getInitialDataMask(),
+        cascadeParentIds: formInputs.dependencies || [],
+        scope: formInputs.scope,
+        sortMetric: formInputs.sortMetric,
+        type: formInputs.type,
+        description: (formInputs.description || '').trim(),
+      };
+      return undefined;
+    };
+
+    const transformedModified = filterChanges.modified
+      .map(transformFilter)
+      .filter(Boolean);
+
+    const newFilterChanges = {
+      ...filterChanges,
+      modified: transformedModified,
+    };
+    await saveForm(newFilterChanges);
   };
 
 export const createHandleRemoveItem =
@@ -163,6 +170,8 @@ export const createHandleRemoveItem =
       val: string[] | ((prevState: string[]) => string[]),
     ) => void,
     setSaveAlertVisible: Function,
+    filterChanges: FilterChangesType,
+    removeFilter: (filterId: string) => void,
   ) =>
   (filterId: string) => {
     const completeFilterRemoval = (filterId: string) => {
@@ -187,12 +196,14 @@ export const createHandleRemoveItem =
       ...removedFilters,
       [filterId]: { isPending: true, timerId },
     }));
+    removeFilter(filterId);
+    // eslint-disable-next-line no-param-reassign
     setSaveAlertVisible(false);
   };
 
 export const NATIVE_FILTER_PREFIX = 'NATIVE_FILTER-';
 export const NATIVE_FILTER_DIVIDER_PREFIX = 'NATIVE_FILTER_DIVIDER-';
-export const generateFilterId = (type: NativeFilterType) => {
+export const generateFilterId = (type: NativeFilterType): string => {
   const prefix =
     type === NativeFilterType.NativeFilter
       ? NATIVE_FILTER_PREFIX
diff --git a/superset-frontend/src/dashboard/reducers/dashboardInfo.js b/superset-frontend/src/dashboard/reducers/dashboardInfo.js
index 3503c441b6..29f4dd2d3b 100644
--- a/superset-frontend/src/dashboard/reducers/dashboardInfo.js
+++ b/superset-frontend/src/dashboard/reducers/dashboardInfo.js
@@ -21,6 +21,7 @@ import {
   DASHBOARD_INFO_UPDATED,
   SET_FILTER_BAR_ORIENTATION,
   SET_CROSS_FILTERS_ENABLED,
+  DASHBOARD_INFO_FILTERS_CHANGED,
 } from '../actions/dashboardInfo';
 import { HYDRATE_DASHBOARD } from '../actions/hydrate';
 
@@ -33,6 +34,42 @@ export default function dashboardStateReducer(state = {}, action) {
         // server-side compare last_modified_time in second level
         last_modified_time: Math.round(new Date().getTime() / 1000),
       };
+    case DASHBOARD_INFO_FILTERS_CHANGED: {
+      const { modified = [], deleted = [], reordered = [] } = action.newInfo;
+      let updatedFilters = state.metadata.native_filter_configuration || [];
+      if (deleted.length > 0) {
+        updatedFilters = updatedFilters.filter(
+          filter => !deleted.includes(filter.id),
+        );
+      }
+      if (modified.length > 0) {
+        modified.forEach(modifiedFilter => {
+          const existingFilterIndex = updatedFilters.findIndex(
+            filter => filter.id === modifiedFilter.id,
+          );
+          if (existingFilterIndex > -1) {
+            updatedFilters[existingFilterIndex] = modifiedFilter;
+          } else {
+            updatedFilters = [...updatedFilters, modifiedFilter];
+          }
+        });
+      }
+      if (reordered.length > 0) {
+        updatedFilters = reordered
+          .map(reorderedId =>
+            updatedFilters.find(filter => filter.id === reorderedId),
+          )
+          .filter(Boolean);
+      }
+      return {
+        ...state,
+        metadata: {
+          ...state.metadata,
+          native_filter_configuration: updatedFilters,
+        },
+        last_modified_time: Math.round(new Date().getTime() / 1000),
+      };
+    }
     case HYDRATE_DASHBOARD:
       return {
         ...state,
diff --git a/superset-frontend/src/dashboard/reducers/nativeFilters.ts b/superset-frontend/src/dashboard/reducers/nativeFilters.ts
index c68ff3c19b..8ca3f0eea6 100644
--- a/superset-frontend/src/dashboard/reducers/nativeFilters.ts
+++ b/superset-frontend/src/dashboard/reducers/nativeFilters.ts
@@ -18,7 +18,7 @@
  */
 import {
   AnyFilterAction,
-  SET_FILTER_CONFIG_COMPLETE,
+  SET_NATIVE_FILTERS_CONFIG_COMPLETE,
   SET_IN_SCOPE_STATUS_OF_FILTERS,
   SET_FOCUSED_NATIVE_FILTER,
   UNSET_FOCUSED_NATIVE_FILTER,
@@ -28,6 +28,7 @@ import {
 } from 'src/dashboard/actions/nativeFilters';
 import { FilterConfiguration, NativeFiltersState } from '@superset-ui/core';
 import { HYDRATE_DASHBOARD } from '../actions/hydrate';
+import { SaveFilterChangesType } from '../components/nativeFilters/FiltersConfigModal/types';
 
 export function getInitialState({
   filterConfig,
@@ -37,7 +38,6 @@ export function getInitialState({
   state?: NativeFiltersState;
 }): NativeFiltersState {
   const state: Partial<NativeFiltersState> = {};
-
   const filters = {};
   if (filterConfig) {
     filterConfig.forEach(filter => {
@@ -52,6 +52,43 @@ export function getInitialState({
   return state as NativeFiltersState;
 }
 
+function handleFilterChangesComplete(
+  state: NativeFiltersState,
+  changes: SaveFilterChangesType,
+) {
+  const { modified = [], deleted = [], reordered = [] } = changes;
+
+  let updatedFilters = { ...state.filters };
+
+  if (deleted.length > 0) {
+    deleted.forEach(id => {
+      if (updatedFilters[id]) {
+        delete updatedFilters[id];
+      }
+    });
+  }
+
+  if (modified.length > 0) {
+    const modifiedFilters = Object.fromEntries(
+      modified.map(filter => [filter.id, filter]),
+    );
+    updatedFilters = { ...updatedFilters, ...modifiedFilters };
+  }
+
+  if (reordered.length > 0) {
+    updatedFilters = Object.fromEntries(
+      reordered
+        .map(id => [id, updatedFilters[id]])
+        .filter(([, filter]) => filter),
+    );
+  }
+
+  return {
+    ...state,
+    filters: updatedFilters,
+  } as NativeFiltersState;
+}
+
 export default function nativeFilterReducer(
   state: NativeFiltersState = {
     filters: {},
@@ -64,10 +101,12 @@ export default function nativeFilterReducer(
         filters: action.data.nativeFilters.filters,
       };
 
-    case SET_FILTER_CONFIG_COMPLETE:
     case SET_IN_SCOPE_STATUS_OF_FILTERS:
       return getInitialState({ filterConfig: action.filterConfig, state });
 
+    case SET_NATIVE_FILTERS_CONFIG_COMPLETE:
+      return handleFilterChangesComplete(state, action.filterChanges);
+
     case SET_FOCUSED_NATIVE_FILTER:
       return {
         ...state,
diff --git a/superset-frontend/src/dataMask/actions.ts b/superset-frontend/src/dataMask/actions.ts
index cb7ed393d7..e8c7d6c4f3 100644
--- a/superset-frontend/src/dataMask/actions.ts
+++ b/superset-frontend/src/dataMask/actions.ts
@@ -16,7 +16,8 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-import { DataMask, FilterConfiguration, Filters } from '@superset-ui/core';
+import { DataMask, Filters } from '@superset-ui/core';
+import { SaveFilterChangesType } from 'src/dashboard/components/nativeFilters/FiltersConfigModal/types';
 import { getInitialDataMask } from './reducer';
 
 export const CLEAR_DATA_MASK_STATE = 'CLEAR_DATA_MASK_STATE';
@@ -37,32 +38,25 @@ export interface INITDATAMASK {
   dataMask: DataMask;
 }
 
-export const SET_DATA_MASK_FOR_FILTER_CONFIG_COMPLETE =
-  'SET_DATA_MASK_FOR_FILTER_CONFIG_COMPLETE';
-
-export interface SetDataMaskForFilterConfigComplete {
-  type: typeof SET_DATA_MASK_FOR_FILTER_CONFIG_COMPLETE;
-  filterConfig: FilterConfiguration;
+export const SET_DATA_MASK_FOR_FILTER_CHANGES_COMPLETE =
+  'SET_DATA_MASK_FOR_FILTER_CHANGES_COMPLETE';
+export interface SetDataMaskForFilterChangesComplete {
+  type: typeof SET_DATA_MASK_FOR_FILTER_CHANGES_COMPLETE;
+  filterChanges: SaveFilterChangesType;
   filters?: Filters;
 }
 
-export const SET_DATA_MASK_FOR_FILTER_CONFIG_FAIL =
-  'SET_DATA_MASK_FOR_FILTER_CONFIG_FAIL';
-
-export interface SetDataMaskForFilterConfigFail {
-  type: typeof SET_DATA_MASK_FOR_FILTER_CONFIG_FAIL;
-  filterConfig: FilterConfiguration;
-}
-export function setDataMaskForFilterConfigComplete(
-  filterConfig: FilterConfiguration,
+export function setDataMaskForFilterChangesComplete(
+  filterChanges: SaveFilterChangesType,
   filters?: Filters,
-): SetDataMaskForFilterConfigComplete {
+): SetDataMaskForFilterChangesComplete {
   return {
-    type: SET_DATA_MASK_FOR_FILTER_CONFIG_COMPLETE,
-    filterConfig,
+    type: SET_DATA_MASK_FOR_FILTER_CHANGES_COMPLETE,
+    filterChanges,
     filters,
   };
 }
+
 export function updateDataMask(
   filterId: string | number,
   dataMask: DataMask,
@@ -87,5 +81,4 @@ export function clearDataMaskState(): ClearDataMaskState {
 export type AnyDataMaskAction =
   | ClearDataMaskState
   | UpdateDataMask
-  | SetDataMaskForFilterConfigFail
-  | SetDataMaskForFilterConfigComplete;
+  | SetDataMaskForFilterChangesComplete;
diff --git a/superset-frontend/src/dataMask/reducer.ts b/superset-frontend/src/dataMask/reducer.ts
index 28158630ad..b79f17fb1d 100644
--- a/superset-frontend/src/dataMask/reducer.ts
+++ b/superset-frontend/src/dataMask/reducer.ts
@@ -32,10 +32,11 @@ import {
 } from '@superset-ui/core';
 import { NATIVE_FILTER_PREFIX } from 'src/dashboard/components/nativeFilters/FiltersConfigModal/utils';
 import { HYDRATE_DASHBOARD } from 'src/dashboard/actions/hydrate';
+import { SaveFilterChangesType } from 'src/dashboard/components/nativeFilters/FiltersConfigModal/types';
 import {
   AnyDataMaskAction,
   CLEAR_DATA_MASK_STATE,
-  SET_DATA_MASK_FOR_FILTER_CONFIG_COMPLETE,
+  SET_DATA_MASK_FOR_FILTER_CHANGES_COMPLETE,
   UPDATE_DATA_MASK,
 } from './actions';
 import { areObjectsEqual } from '../reduxUtils';
@@ -100,6 +101,37 @@ function fillNativeFilters(
   });
 }
 
+function updateDataMaskForFilterChanges(
+  filterChanges: SaveFilterChangesType,
+  mergedDataMask: DataMaskStateWithId,
+  draftDataMask: DataMaskStateWithId,
+  initialDataMask?: Filters,
+) {
+  const dataMask = initialDataMask || {};
+
+  Object.entries(dataMask).forEach(([key, value]) => {
+    mergedDataMask[key] = { ...value, ...value.defaultDataMask };
+  });
+
+  filterChanges.deleted.forEach((filterId: string) => {
+    delete mergedDataMask[filterId];
+  });
+
+  filterChanges.modified.forEach((filter: Filter) => {
+    mergedDataMask[filter.id] = {
+      ...getInitialDataMask(filter.id),
+      ...filter.defaultDataMask,
+      ...filter,
+    };
+  });
+
+  Object.values(draftDataMask).forEach(filter => {
+    if (!String(filter?.id).startsWith(NATIVE_FILTER_PREFIX)) {
+      mergedDataMask[filter?.id] = filter;
+    }
+  });
+}
+
 const dataMaskReducer = produce(
   (draft: DataMaskStateWithId, action: AnyDataMaskAction) => {
     const cleanState = {};
@@ -136,15 +168,14 @@ const dataMaskReducer = produce(
           action.data.dataMask,
         );
         return cleanState;
-      case SET_DATA_MASK_FOR_FILTER_CONFIG_COMPLETE:
-        fillNativeFilters(
-          action.filterConfig ?? [],
+      case SET_DATA_MASK_FOR_FILTER_CHANGES_COMPLETE:
+        updateDataMaskForFilterChanges(
+          action.filterChanges,
           cleanState,
           draft,
           action.filters,
         );
         return cleanState;
-
       default:
         return draft;
     }
diff --git a/superset/commands/dashboard/exceptions.py b/superset/commands/dashboard/exceptions.py
index 9281119b32..a4b7be1261 100644
--- a/superset/commands/dashboard/exceptions.py
+++ b/superset/commands/dashboard/exceptions.py
@@ -58,6 +58,10 @@ class DashboardUpdateFailedError(UpdateFailedError):
     message = _("Dashboard could not be updated.")
 
 
+class DashboardNativeFiltersUpdateFailedError(UpdateFailedError):
+    message = _("Dashboard native filters could not be patched.")
+
+
 class DashboardDeleteFailedError(DeleteFailedError):
     message = _("Dashboard could not be deleted.")
 
diff --git a/superset/commands/dashboard/export.py b/superset/commands/dashboard/export.py
index 93cc490ad7..fe3a6b8eab 100644
--- a/superset/commands/dashboard/export.py
+++ b/superset/commands/dashboard/export.py
@@ -130,18 +130,6 @@ class ExportDashboardsCommand(ExportModelsCommand):
                     logger.info("Unable to decode `%s` field: %s", key, value)
                     payload[new_name] = {}
 
-        # Extract all native filter datasets and replace native
-        # filter dataset references with uuid
-        for native_filter in payload.get("metadata", {}).get(
-            "native_filter_configuration", []
-        ):
-            for target in native_filter.get("targets", []):
-                dataset_id = target.pop("datasetId", None)
-                if dataset_id is not None:
-                    dataset = DatasetDAO.find_by_id(dataset_id)
-                    if dataset:
-                        target["datasetUuid"] = str(dataset.uuid)
-
         # the mapping between dashboard -> charts is inferred from the position
         # attribute, so if it's not present we need to add a default config
         if not payload.get("position"):
@@ -192,14 +180,16 @@ class ExportDashboardsCommand(ExportModelsCommand):
                     logger.info("Unable to decode `%s` field: %s", key, value)
                     payload[new_name] = {}
 
-        if export_related:
-            # Extract all native filter datasets and export referenced datasets
-            for native_filter in payload.get("metadata", {}).get(
-                "native_filter_configuration", []
-            ):
-                for target in native_filter.get("targets", []):
-                    dataset_id = target.pop("datasetId", None)
-                    if dataset_id is not None:
-                        dataset = DatasetDAO.find_by_id(dataset_id)
-                        if dataset:
+        # Extract all native filter datasets and replace native
+        # filter dataset references with uuid
+        for native_filter in payload.get("metadata", {}).get(
+            "native_filter_configuration", []
+        ):
+            for target in native_filter.get("targets", []):
+                dataset_id = target.pop("datasetId", None)
+                if dataset_id is not None:
+                    dataset = DatasetDAO.find_by_id(dataset_id)
+                    if dataset:
+                        target["datasetUuid"] = str(dataset.uuid)
+                        if export_related:
                             yield from ExportDatasetsCommand([dataset_id]).run()
diff --git a/superset/commands/dashboard/update.py b/superset/commands/dashboard/update.py
index 031db1af31..a87ac899d1 100644
--- a/superset/commands/dashboard/update.py
+++ b/superset/commands/dashboard/update.py
@@ -27,6 +27,7 @@ from superset.commands.base import BaseCommand, UpdateMixin
 from superset.commands.dashboard.exceptions import (
     DashboardForbiddenError,
     DashboardInvalidError,
+    DashboardNativeFiltersUpdateFailedError,
     DashboardNotFoundError,
     DashboardSlugExistsValidationError,
     DashboardUpdateFailedError,
@@ -67,7 +68,6 @@ class UpdateDashboardCommand(UpdateMixin, BaseCommand):
                 dashboard,
                 data=json.loads(self._properties.get("json_metadata", "{}")),
             )
-
         return dashboard
 
     def validate(self) -> None:
@@ -187,3 +187,18 @@ class UpdateDashboardCommand(UpdateMixin, BaseCommand):
         deleted_tabs = find_deleted_tabs()
         reports = find_reports_containing_tabs(deleted_tabs)
         deactivate_reports(reports)
+
+
+class UpdateDashboardNativeFiltersCommand(UpdateDashboardCommand):
+    @transaction(
+        on_error=partial(on_error, reraise=DashboardNativeFiltersUpdateFailedError)
+    )
+    def run(self) -> Model:
+        super().validate()
+        assert self._model
+
+        dashboard = DashboardDAO.update_native_filters_config(
+            self._model, self._properties
+        )
+
+        return dashboard
diff --git a/superset/config.py b/superset/config.py
index 7ae46bba76..0234c0deb2 100644
--- a/superset/config.py
+++ b/superset/config.py
@@ -68,7 +68,6 @@ logger = logging.getLogger(__name__)
 
 if TYPE_CHECKING:
     from flask_appbuilder.security.sqla import models
-    from sqlglot import Dialect, Dialects
 
     from superset.connectors.sqla.models import SqlaTable
     from superset.models.core import Database
@@ -250,10 +249,6 @@ SQLALCHEMY_CUSTOM_PASSWORD_STORE = None
 SQLALCHEMY_ENCRYPTED_FIELD_TYPE_ADAPTER = (  # pylint: disable=invalid-name
     SQLAlchemyUtilsAdapter
 )
-
-# Extends the default SQLGlot dialects with additional dialects
-SQLGLOT_DIALECTS_EXTENSIONS: dict[str, Dialects | type[Dialect]] = {}
-
 # The limit of queries fetched for query search
 QUERY_SEARCH_LIMIT = 1000
 
diff --git a/superset/connectors/sqla/models.py b/superset/connectors/sqla/models.py
index 82980cef16..9215e1545b 100644
--- a/superset/connectors/sqla/models.py
+++ b/superset/connectors/sqla/models.py
@@ -1533,7 +1533,6 @@ class SqlaTable(
                 expression = self._process_sql_expression(
                     expression=metric["sqlExpression"],
                     database_id=self.database_id,
-                    engine=self.database.backend,
                     schema=self.schema,
                     template_processor=template_processor,
                 )
@@ -1567,7 +1566,6 @@ class SqlaTable(
             expression = self._process_sql_expression(
                 expression=col["sqlExpression"],
                 database_id=self.database_id,
-                engine=self.database.backend,
                 schema=self.schema,
                 template_processor=template_processor,
             )
diff --git a/superset/constants.py b/superset/constants.py
index d233f271c6..f6cf2a1157 100644
--- a/superset/constants.py
+++ b/superset/constants.py
@@ -173,6 +173,7 @@ MODEL_API_RW_METHOD_PERMISSION_MAP = {
     "columnar_metadata": "columnar_upload",
     "csv_metadata": "csv_upload",
     "slack_channels": "write",
+    "put_filters": "write",
 }
 
 EXTRA_FORM_DATA_APPEND_KEYS = {
diff --git a/superset/daos/dashboard.py b/superset/daos/dashboard.py
index 8196c197b2..1f1f0fc015 100644
--- a/superset/daos/dashboard.py
+++ b/superset/daos/dashboard.py
@@ -28,6 +28,7 @@ from superset.commands.dashboard.exceptions import (
     DashboardAccessDeniedError,
     DashboardForbiddenError,
     DashboardNotFoundError,
+    DashboardUpdateFailedError,
 )
 from superset.daos.base import BaseDAO
 from superset.dashboards.filters import DashboardAccessFilter, is_uuid
@@ -318,6 +319,69 @@ class DashboardDAO(BaseDAO[Dashboard]):
         db.session.add(dash)
         return dash
 
+    @classmethod
+    def update_native_filters_config(
+        cls,
+        dashboard: Dashboard | None = None,
+        attributes: dict[str, Any] | None = None,
+    ) -> Dashboard:
+        if not dashboard:
+            raise DashboardUpdateFailedError("Dashboard not found")
+
+        if attributes:
+            metadata = json.loads(dashboard.json_metadata or "{}")
+            native_filter_configuration = metadata.get(
+                "native_filter_configuration", []
+            )
+            updated_configuration = []
+
+            # Modify / Delete existing filters
+            for conf in native_filter_configuration:
+                deleted_filter = next(
+                    (f for f in attributes.get("deleted", []) if f == conf.get("id")),
+                    None,
+                )
+                if deleted_filter:
+                    continue
+
+                modified_filter = next(
+                    (
+                        f
+                        for f in attributes.get("modified", [])
+                        if f.get("id") == conf.get("id")
+                    ),
+                    None,
+                )
+                if modified_filter:
+                    updated_configuration.append(modified_filter)
+                else:
+                    updated_configuration.append(conf)
+
+            # Append new filters
+            for new_filter in attributes.get("modified", []):
+                if new_filter.get("id") not in [
+                    f.get("id") for f in updated_configuration
+                ]:
+                    updated_configuration.append(new_filter)
+
+            # Reorder filters
+            reordered_filter_ids = attributes.get("reordered", [])
+            if reordered_filter_ids:
+                filter_map = {
+                    filter_config["id"]: filter_config
+                    for filter_config in updated_configuration
+                }
+                updated_configuration = [
+                    filter_map[filter_id]
+                    for filter_id in reordered_filter_ids
+                    if filter_id in filter_map
+                ]
+
+            metadata["native_filter_configuration"] = updated_configuration
+            dashboard.json_metadata = json.dumps(metadata)
+
+        return dashboard
+
     @staticmethod
     def add_favorite(dashboard: Dashboard) -> None:
         ids = DashboardDAO.favorited_ids([dashboard])
diff --git a/superset/dashboards/api.py b/superset/dashboards/api.py
index cb7e30ef02..2f510f0380 100644
--- a/superset/dashboards/api.py
+++ b/superset/dashboards/api.py
@@ -47,6 +47,7 @@ from superset.commands.dashboard.exceptions import (
     DashboardDeleteFailedError,
     DashboardForbiddenError,
     DashboardInvalidError,
+    DashboardNativeFiltersUpdateFailedError,
     DashboardNotFoundError,
     DashboardUpdateFailedError,
 )
@@ -55,7 +56,10 @@ from superset.commands.dashboard.fave import AddFavoriteDashboardCommand
 from superset.commands.dashboard.importers.dispatcher import ImportDashboardsCommand
 from superset.commands.dashboard.permalink.create import CreateDashboardPermalinkCommand
 from superset.commands.dashboard.unfave import DelFavoriteDashboardCommand
-from superset.commands.dashboard.update import UpdateDashboardCommand
+from superset.commands.dashboard.update import (
+    UpdateDashboardCommand,
+    UpdateDashboardNativeFiltersCommand,
+)
 from superset.commands.database.exceptions import DatasetValidationError
 from superset.commands.exceptions import TagForbiddenError
 from superset.commands.importers.exceptions import NoValidFilesFoundError
@@ -80,6 +84,7 @@ from superset.dashboards.schemas import (
     DashboardCopySchema,
     DashboardDatasetSchema,
     DashboardGetResponseSchema,
+    DashboardNativeFiltersConfigUpdateSchema,
     DashboardPostSchema,
     DashboardPutSchema,
     EmbeddedDashboardConfigSchema,
@@ -175,6 +180,7 @@ class DashboardRestApi(BaseSupersetModelRestApi):
         "copy_dash",
         "cache_dashboard_screenshot",
         "screenshot",
+        "put_filters",
     }
     resource_name = "dashboard"
     allow_browser_login = True
@@ -262,6 +268,7 @@ class DashboardRestApi(BaseSupersetModelRestApi):
 
     add_model_schema = DashboardPostSchema()
     edit_model_schema = DashboardPutSchema()
+    update_filters_model_schema = DashboardNativeFiltersConfigUpdateSchema()
     chart_entity_response_schema = ChartEntityResponseSchema()
     dashboard_get_response_schema = DashboardGetResponseSchema()
     dashboard_dataset_schema = DashboardDatasetSchema()
@@ -675,6 +682,94 @@ class DashboardRestApi(BaseSupersetModelRestApi):
             response = self.response_422(message=str(ex))
         return response
 
+    @expose("/<pk>/filters", methods=("PUT",))
+    @protect()
+    @safe
+    @statsd_metrics
+    @event_logger.log_this_with_context(
+        action=lambda self, *args, **kwargs: f"{self.__class__.__name__}.put",
+        log_to_statsd=False,
+    )
+    @requires_json
+    def put_filters(self, pk: int) -> Response:
+        """
+        Modify native filters configuration for a dashboard.
+        ---
+        put:
+          summary: Update native filters configuration for a dashboard.
+          parameters:
+          - in: path
+            schema:
+              type: integer
+            name: pk
+          requestBody:
+            description: Native filters configuration
+            required: true
+            content:
+              application/json:
+                schema:
+                  $ref: '#/components/schemas/{{self.__class__.__name__}}.put'
+          responses:
+            200:
+              description: Dashboard native filters updated
+              content:
+                application/json:
+                  schema:
+                    type: object
+                    properties:
+                      id:
+                        type: number
+                      result:
+                        $ref: '#/components/schemas/{{self.__class__.__name__}}.put'
+                      last_modified_time:
+                        type: number
+            400:
+              $ref: '#/components/responses/400'
+            401:
+              $ref: '#/components/responses/401'
+            403:
+              $ref: '#/components/responses/403'
+            404:
+              $ref: '#/components/responses/404'
+            422:
+              $ref: '#/components/responses/422'
+            500:
+              $ref: '#/components/responses/500'
+        """
+        try:
+            item = self.update_filters_model_schema.load(request.json, partial=True)
+        except ValidationError as error:
+            return self.response_400(message=error.messages)
+
+        try:
+            changed_model = UpdateDashboardNativeFiltersCommand(pk, item).run()
+            last_modified_time = changed_model.changed_on.replace(
+                microsecond=0
+            ).timestamp()
+            response = self.response(
+                200,
+                id=changed_model.id,
+                result=item,
+                last_modified_time=last_modified_time,
+            )
+        except DashboardNotFoundError:
+            response = self.response_404()
+        except DashboardForbiddenError:
+            response = self.response_403()
+        except TagForbiddenError as ex:
+            response = self.response(403, message=str(ex))
+        except DashboardInvalidError as ex:
+            return self.response_422(message=ex.normalized_messages())
+        except DashboardNativeFiltersUpdateFailedError as ex:
+            logger.error(
+                "Error changing native filters for dashboard %s: %s",
+                self.__class__.__name__,
+                str(ex),
+                exc_info=True,
+            )
+            response = self.response_422(message=str(ex))
+        return response
+
     @expose("/<pk>", methods=("DELETE",))
     @protect()
     @safe
diff --git a/superset/dashboards/schemas.py b/superset/dashboards/schemas.py
index b0a47aba41..8f6c56738c 100644
--- a/superset/dashboards/schemas.py
+++ b/superset/dashboards/schemas.py
@@ -398,6 +398,12 @@ class DashboardPutSchema(BaseDashboardSchema):
     )
 
 
+class DashboardNativeFiltersConfigUpdateSchema(BaseDashboardSchema):
+    deleted = fields.List(fields.String(), allow_none=False)
+    modified = fields.List(fields.Raw(), allow_none=False)
+    reordered = fields.List(fields.String(), allow_none=False)
+
+
 class DashboardScreenshotPostSchema(Schema):
     dataMask = fields.Dict(
         keys=fields.Str(),
diff --git a/superset/initialization/__init__.py b/superset/initialization/__init__.py
index 1720c87af4..10686b872f 100644
--- a/superset/initialization/__init__.py
+++ b/superset/initialization/__init__.py
@@ -54,7 +54,6 @@ from superset.extensions import (
     talisman,
 )
 from superset.security import SupersetSecurityManager
-from superset.sql.parse import SQLGLOT_DIALECTS
 from superset.superset_typing import FlaskResponse
 from superset.tags.core import register_sqla_event_listeners
 from superset.utils.core import is_test, pessimistic_connection_handling
@@ -485,7 +484,6 @@ class SupersetAppInitializer:  # pylint: disable=too-many-public-methods
         self.configure_middlewares()
         self.configure_cache()
         self.set_db_default_isolation()
-        self.configure_sqlglot_dialects()
 
         with self.superset_app.app_context():
             self.init_app_in_ctx()
@@ -546,9 +544,6 @@ class SupersetAppInitializer:  # pylint: disable=too-many-public-methods
     def configure_feature_flags(self) -> None:
         feature_flag_manager.init_app(self.superset_app)
 
-    def configure_sqlglot_dialects(self) -> None:
-        SQLGLOT_DIALECTS.update(self.config["SQLGLOT_DIALECTS_EXTENSIONS"])
-
     @transaction()
     def configure_fab(self) -> None:
         if self.config["SILENCE_FAB"]:
diff --git a/superset/jinja_context.py b/superset/jinja_context.py
index 604e26b1db..d7ae892301 100644
--- a/superset/jinja_context.py
+++ b/superset/jinja_context.py
@@ -25,7 +25,7 @@ from functools import lru_cache, partial
 from typing import Any, Callable, cast, Optional, TYPE_CHECKING, TypedDict, Union
 
 import dateutil
-from flask import current_app, g, has_request_context, request
+from flask import current_app, has_request_context, request
 from flask_babel import gettext as _
 from jinja2 import DebugUndefined, Environment
 from jinja2.sandbox import SandboxedEnvironment
@@ -104,14 +104,13 @@ class ExtraCache:
     # Regular expression for detecting the presence of templated methods which could
     # be added to the cache key.
     regex = re.compile(
-        r"(\{\{|\{%)[^{}]*?("
-        r"current_user_id\([^()]*\)|"
-        r"current_username\([^()]*\)|"
-        r"current_user_email\([^()]*\)|"
-        r"cache_key_wrapper\([^()]*\)|"
-        r"url_param\([^()]*\)"
-        r")"
-        r"[^{}]*?(\}\}|\%\})"
+        r"\{\{.*("
+        r"current_user_id\(.*\)|"
+        r"current_username\(.*\)|"
+        r"current_user_email\(.*\)|"
+        r"cache_key_wrapper\(.*\)|"
+        r"url_param\(.*\)"
+        r").*\}\}"
     )
 
     def __init__(  # pylint: disable=too-many-arguments
@@ -847,45 +846,35 @@ def dataset_macro(
 
 def get_dataset_id_from_context(metric_key: str) -> int:
     """
-    Retrieves the Dataset ID from the request context.
+    Retrives the Dataset ID from the request context.
 
     :param metric_key: the metric key.
     :returns: the dataset ID.
     """
     # pylint: disable=import-outside-toplevel
     from superset.daos.chart import ChartDAO
-    from superset.views.utils import loads_request_json
+    from superset.views.utils import get_form_data
 
-    form_data: dict[str, Any] = {}
     exc_message = _(
         "Please specify the Dataset ID for the ``%(name)s`` metric in the Jinja macro.",
         name=metric_key,
     )
 
-    if has_request_context():
-        if payload := request.get_json(cache=True) if request.is_json else None:
-            if dataset_id := payload.get("datasource", {}).get("id"):
-                return dataset_id
-            form_data.update(payload.get("form_data", {}))
-        request_form = loads_request_json(request.form.get("form_data"))
-        form_data.update(request_form)
-        request_args = loads_request_json(request.args.get("form_data"))
-        form_data.update(request_args)
-
-    if form_data := (form_data or getattr(g, "form_data", {})):
-        if datasource_info := form_data.get("datasource"):
-            if isinstance(datasource_info, dict):
-                return datasource_info["id"]
-            return datasource_info.split("__")[0]
-        url_params = form_data.get("queries", [{}])[0].get("url_params", {})
-        if dataset_id := url_params.get("datasource_id"):
-            return dataset_id
-        if chart_id := (form_data.get("slice_id") or url_params.get("slice_id")):
-            chart_data = ChartDAO.find_by_id(chart_id)
-            if not chart_data:
-                raise SupersetTemplateException(exc_message)
-            return chart_data.datasource_id
+    form_data, chart = get_form_data()
+    if not (form_data or chart):
+        raise SupersetTemplateException(exc_message)
 
+    if chart and chart.datasource_id:
+        return chart.datasource_id
+    if dataset_id := form_data.get("url_params", {}).get("datasource_id"):
+        return dataset_id
+    if chart_id := (
+        form_data.get("slice_id") or form_data.get("url_params", {}).get("slice_id")
+    ):
+        chart_data = ChartDAO.find_by_id(chart_id)
+        if not chart_data:
+            raise SupersetTemplateException(exc_message)
+        return chart_data.datasource_id
     raise SupersetTemplateException(exc_message)
 
 
diff --git a/superset/models/helpers.py b/superset/models/helpers.py
index 51808f9a46..4085d3a0aa 100644
--- a/superset/models/helpers.py
+++ b/superset/models/helpers.py
@@ -63,7 +63,6 @@ from superset.exceptions import (
     ColumnNotFoundException,
     QueryClauseValidationException,
     QueryObjectValidationError,
-    SupersetParseError,
     SupersetSecurityException,
 )
 from superset.extensions import feature_flag_manager
@@ -113,7 +112,6 @@ ADVANCED_DATA_TYPES = config["ADVANCED_DATA_TYPES"]
 def validate_adhoc_subquery(
     sql: str,
     database_id: int,
-    engine: str,
     default_schema: str,
 ) -> str:
     """
@@ -128,12 +126,7 @@ def validate_adhoc_subquery(
     """
     statements = []
     for statement in sqlparse.parse(sql):
-        try:
-            has_table = has_table_query(str(statement), engine)
-        except SupersetParseError:
-            has_table = True
-
-        if has_table:
+        if has_table_query(statement):
             if not is_feature_enabled("ALLOW_ADHOC_SUBQUERY"):
                 raise SupersetSecurityException(
                     SupersetError(
@@ -142,9 +135,7 @@ def validate_adhoc_subquery(
                         level=ErrorLevel.ERROR,
                     )
                 )
-            # TODO (betodealmeida): reimplement with sqlglot
             statement = insert_rls_in_predicate(statement, database_id, default_schema)
-
         statements.append(statement)
 
     return ";\n".join(str(statement) for statement in statements)
@@ -819,11 +810,10 @@ class ExploreMixin:  # pylint: disable=too-many-public-methods
         # for datasources of type query
         return []
 
-    def _process_sql_expression(  # pylint: disable=too-many-arguments
+    def _process_sql_expression(
         self,
         expression: Optional[str],
         database_id: int,
-        engine: str,
         schema: str,
         template_processor: Optional[BaseTemplateProcessor],
     ) -> Optional[str]:
@@ -833,7 +823,6 @@ class ExploreMixin:  # pylint: disable=too-many-public-methods
             expression = validate_adhoc_subquery(
                 expression,
                 database_id,
-                engine,
                 schema,
             )
             try:
@@ -1119,7 +1108,6 @@ class ExploreMixin:  # pylint: disable=too-many-public-methods
             expression = self._process_sql_expression(
                 expression=metric["sqlExpression"],
                 database_id=self.database_id,
-                engine=self.database.backend,
                 schema=self.schema,
                 template_processor=template_processor,
             )
@@ -1563,7 +1551,6 @@ class ExploreMixin:  # pylint: disable=too-many-public-methods
                     col["sqlExpression"] = self._process_sql_expression(
                         expression=col["sqlExpression"],
                         database_id=self.database_id,
-                        engine=self.database.backend,
                         schema=self.schema,
                         template_processor=template_processor,
                     )
@@ -1626,7 +1613,6 @@ class ExploreMixin:  # pylint: disable=too-many-public-methods
                         selected = validate_adhoc_subquery(
                             selected,
                             self.database_id,
-                            self.database.backend,
                             self.schema,
                         )
                         outer = literal_column(f"({selected})")
@@ -1653,7 +1639,6 @@ class ExploreMixin:  # pylint: disable=too-many-public-methods
                 selected = validate_adhoc_subquery(
                     _sql,
                     self.database_id,
-                    self.database.backend,
                     self.schema,
                 )
 
@@ -1930,7 +1915,6 @@ class ExploreMixin:  # pylint: disable=too-many-public-methods
                 where = self._process_sql_expression(
                     expression=where,
                     database_id=self.database_id,
-                    engine=self.database.backend,
                     schema=self.schema,
                     template_processor=template_processor,
                 )
@@ -1949,7 +1933,6 @@ class ExploreMixin:  # pylint: disable=too-many-public-methods
                 having = self._process_sql_expression(
                     expression=having,
                     database_id=self.database_id,
-                    engine=self.database.backend,
                     schema=self.schema,
                     template_processor=template_processor,
                 )
diff --git a/superset/models/sql_lab.py b/superset/models/sql_lab.py
index 1702601d0f..6f25a5a660 100644
--- a/superset/models/sql_lab.py
+++ b/superset/models/sql_lab.py
@@ -374,7 +374,6 @@ class Query(
         expression = self._process_sql_expression(
             expression=col["sqlExpression"],
             database_id=self.database_id,
-            engine=self.database.backend,
             schema=self.schema,
             template_processor=template_processor,
         )
diff --git a/superset/sql_parse.py b/superset/sql_parse.py
index cb457cd4f5..91b6812635 100644
--- a/superset/sql_parse.py
+++ b/superset/sql_parse.py
@@ -28,6 +28,7 @@ import sqlparse
 from flask_babel import gettext as __
 from jinja2 import nodes
 from sqlalchemy import and_
+from sqlglot.dialects.dialect import Dialects
 from sqlparse import keywords
 from sqlparse.lexer import Lexer
 from sqlparse.sql import (
@@ -60,13 +61,7 @@ from superset.exceptions import (
     SupersetParseError,
     SupersetSecurityException,
 )
-from superset.sql.parse import (
-    extract_tables_from_statement,
-    SQLGLOT_DIALECTS,
-    SQLScript,
-    SQLStatement,
-    Table,
-)
+from superset.sql.parse import extract_tables_from_statement, SQLScript, Table
 from superset.utils.backports import StrEnum
 
 try:
@@ -93,6 +88,61 @@ sqlparser_sql_regex.insert(25, (r"'(''|\\\\|\\|[^'])*'", sqlparse.tokens.String.
 lex.set_SQL_REGEX(sqlparser_sql_regex)
 
 
+# mapping between DB engine specs and sqlglot dialects
+SQLGLOT_DIALECTS = {
+    "ascend": Dialects.HIVE,
+    "awsathena": Dialects.PRESTO,
+    "bigquery": Dialects.BIGQUERY,
+    "clickhouse": Dialects.CLICKHOUSE,
+    "clickhousedb": Dialects.CLICKHOUSE,
+    "cockroachdb": Dialects.POSTGRES,
+    "couchbase": Dialects.MYSQL,
+    # "crate": ???
+    # "databend": ???
+    "databricks": Dialects.DATABRICKS,
+    # "db2": ???
+    # "dremio": ???
+    "drill": Dialects.DRILL,
+    # "druid": ???
+    "duckdb": Dialects.DUCKDB,
+    # "dynamodb": ???
+    # "elasticsearch": ???
+    # "exa": ???
+    # "firebird": ???
+    # "firebolt": ???
+    "gsheets": Dialects.SQLITE,
+    "hana": Dialects.POSTGRES,
+    "hive": Dialects.HIVE,
+    # "ibmi": ???
+    # "impala": ???
+    # "kustokql": ???
+    # "kylin": ???
+    "mssql": Dialects.TSQL,
+    "mysql": Dialects.MYSQL,
+    "netezza": Dialects.POSTGRES,
+    # "ocient": ???
+    # "odelasticsearch": ???
+    "oracle": Dialects.ORACLE,
+    # "pinot": ???
+    "postgresql": Dialects.POSTGRES,
+    "presto": Dialects.PRESTO,
+    "pydoris": Dialects.DORIS,
+    "redshift": Dialects.REDSHIFT,
+    # "risingwave": ???
+    # "rockset": ???
+    "shillelagh": Dialects.SQLITE,
+    "snowflake": Dialects.SNOWFLAKE,
+    # "solr": ???
+    "spark": Dialects.SPARK,
+    "sqlite": Dialects.SQLITE,
+    "starrocks": Dialects.STARROCKS,
+    "superset": Dialects.SQLITE,
+    "teradatasql": Dialects.TERADATA,
+    "trino": Dialects.TRINO,
+    "vertica": Dialects.POSTGRES,
+}
+
+
 class CtasMethod(StrEnum):
     TABLE = "TABLE"
     VIEW = "VIEW"
@@ -571,31 +621,46 @@ class InsertRLSState(StrEnum):
     FOUND_TABLE = "FOUND_TABLE"
 
 
-def has_table_query(expression: str, engine: str) -> bool:
+def has_table_query(token_list: TokenList) -> bool:
     """
     Return if a statement has a query reading from a table.
 
-        >>> has_table_query("COUNT(*)", "postgresql")
+        >>> has_table_query(sqlparse.parse("COUNT(*)")[0])
         False
-        >>> has_table_query("SELECT * FROM table", "postgresql")
+        >>> has_table_query(sqlparse.parse("SELECT * FROM table")[0])
         True
 
     Note that queries reading from constant values return false:
 
-        >>> has_table_query("SELECT * FROM (SELECT 1)", "postgresql")
+        >>> has_table_query(sqlparse.parse("SELECT * FROM (SELECT 1)")[0])
         False
 
     """
-    # Remove trailing semicolon.
-    expression = expression.strip().rstrip(";")
+    state = InsertRLSState.SCANNING
+    for token in token_list.tokens:
+        # Ignore comments
+        if isinstance(token, sqlparse.sql.Comment):
+            continue
+
+        # Recurse into child token list
+        if isinstance(token, TokenList) and has_table_query(token):
+            return True
+
+        # Found a source keyword (FROM/JOIN)
+        if imt(token, m=[(Keyword, "FROM"), (Keyword, "JOIN")]):
+            state = InsertRLSState.SEEN_SOURCE
+
+        # Found identifier/keyword after FROM/JOIN
+        elif state == InsertRLSState.SEEN_SOURCE and (
+            isinstance(token, sqlparse.sql.Identifier) or token.ttype == Keyword
+        ):
+            return True
 
-    # Wrap the expression in parentheses if it's not already.
-    if not expression.startswith("("):
-        expression = f"({expression})"
+        # Found nothing, leaving source
+        elif state == InsertRLSState.SEEN_SOURCE and token.ttype != Whitespace:
+            state = InsertRLSState.SCANNING
 
-    sql = f"SELECT {expression}"
-    statement = SQLStatement(sql, engine)
-    return any(statement.tables)
+    return False
 
 
 def add_table_name(rls: TokenList, table: str) -> None:
diff --git a/superset/translations/sl/LC_MESSAGES/messages.po b/superset/translations/sl/LC_MESSAGES/messages.po
index 4ed7730508..904070712d 100644
--- a/superset/translations/sl/LC_MESSAGES/messages.po
+++ b/superset/translations/sl/LC_MESSAGES/messages.po
@@ -17,8 +17,8 @@ msgid ""
 msgstr ""
 "Project-Id-Version:  Superset\n"
 "Report-Msgid-Bugs-To: dkrat7 @github.com\n"
-"POT-Creation-Date: 2024-10-05 23:48+0200\n"
-"PO-Revision-Date: 2024-10-05 23:46+0200\n"
+"POT-Creation-Date: 2024-07-30 17:32-0600\n"
+"PO-Revision-Date: 2024-01-01 23:56+0100\n"
 "Last-Translator: dkrat7 <dkrat7 @github.com>\n"
 "Language: sl_SI\n"
 "Language-Team: \n"
@@ -42,16 +42,6 @@ msgid ""
 "                original data, it just changes the way the histogram is "
 "displayed."
 msgstr ""
-"\n"
-"                Možnost kumulative omogoča vpogled v to, kako se podatki "
-"kumulirajo po različnih\n"
-"                 vrednostih. Ko je omogočeno, stolpci histograma "
-"predstavljajo kumulativo frekvenc\n"
-"                do vsakega razdelka. To pomaga razumeti, kako pogoste so "
-"vrednosti pod določeno mejo.\n"
-"                Omogočenje kumulative ne spremeni izvornih podatkov, le "
-"spremeni način, na katerega\n"
-"                je prikazan histogram."
 
 msgid ""
 "\n"
@@ -66,16 +56,6 @@ msgid ""
 "                clearer understanding of the proportion of data points "
 "within each bin."
 msgstr ""
-"\n"
-"                Možnost normiranja spremeni vrednosti histograma v deleže"
-" ali verjetnosti,\n"
-"                tako da se število točk v vsakem razdelku deli s številom"
-" vseh točk. Normiranje\n"
-"                zagotovi, da je vsota dobljenih vrednosti enaka 1, kar "
-"omogoča medsebojno\n"
-"                primerjavo porazdelitev podatkov in lažje razumevanje "
-"deleža podatkovnih\n"
-"                točk v vsakem razdelku."
 
 msgid ""
 "\n"
@@ -112,17 +92,9 @@ msgstr " nadzorno ploščo ALI "
 msgid " a new one"
 msgstr " novo"
 
-#, python-format
-msgid " at line %(line)d"
-msgstr " v vrstici %(line)d"
-
 msgid " expression which needs to adhere to the "
 msgstr " , ki mora upoštevati "
 
-#, python-format
-msgid " near '%(highlight)s'"
-msgstr " blizu '%(highlight)s'"
-
 msgid " source code of Superset's sandboxed parser"
 msgstr " izvorno kodo za Supersetov \"sandboxed parser\""
 
@@ -183,13 +155,13 @@ msgstr "!= (ni enako)"
 msgid "% calculation"
 msgstr "% cizračun"
 
-#, python-format
+#, fuzzy, python-format
 msgid "% of parent"
-msgstr "% nadrejenega"
+msgstr "Nadrejeni"
 
-#, python-format
+#, fuzzy, python-format
 msgid "% of total"
-msgstr "% celote"
+msgstr "Prikaži vsoto"
 
 #, python-format
 msgid "%(dialect)s cannot be used as a data source for security reasons."
@@ -211,9 +183,9 @@ msgstr ""
 msgid "%(name)s.csv"
 msgstr "%(name)s.csv"
 
-#, python-format
+#, fuzzy, python-format
 msgid "%(name)s.pdf"
-msgstr "%(name)s.pdf"
+msgstr "%(name)s.csv"
 
 #, python-format
 msgid "%(object)s does not exist in this database."
@@ -245,8 +217,6 @@ msgid ""
 "schedule with a minimum interval of %(minimum_interval)d minutes per "
 "execution."
 msgstr ""
-"Frekvenca urnika za %(report_type)s presega omejitev. Nastavite urnik z "
-"minimalnim intervalom %(minimum_interval)d minut na zagon."
 
 #, python-format
 msgid "%(rows)d rows returned"
@@ -262,25 +232,19 @@ msgstr ""
 "To je lahko sproženo z/s: \n"
 " %(issue)s"
 
-#, python-format
+#, fuzzy, python-format
 msgid "%(suggestion)s instead of \"%(undefinedParameter)s?\""
 msgid_plural ""
 "%(firstSuggestions)s or %(lastSuggestion)s instead of "
 "\"%(undefinedParameter)s\"?"
 msgstr[0] "%(suggestion)s namesto \"%(undefinedParameter)s?\""
 msgstr[1] ""
-"%(firstSuggestions)s ali %(lastSuggestion)s namesto "
-"\"%(undefinedParameter)s\"?"
 msgstr[2] ""
-"%(firstSuggestions)s ali %(lastSuggestion)s namesto "
-"\"%(undefinedParameter)s\"?"
 msgstr[3] ""
-"%(firstSuggestions)s ali %(lastSuggestion)s namesto "
-"\"%(undefinedParameter)s\"?"
 
-#, python-format
+#, fuzzy, python-format
 msgid "%(type)s File"
-msgstr "%(type)s datoteka"
+msgstr "%(prefix)s %(title)s"
 
 #, python-format
 msgid ""
@@ -338,7 +302,7 @@ msgstr "Stolpci: %s"
 
 #, python-format
 msgid "%s ineligible item(s) are hidden"
-msgstr "%s neustreznih elementov je skritih"
+msgstr ""
 
 #, python-format
 msgid ""
@@ -352,29 +316,29 @@ msgstr ""
 msgid "%s operator(s)"
 msgstr "Operatorji: %s"
 
-#, python-format
+#, fuzzy, python-format
 msgid "%s option"
 msgid_plural "%s options"
 msgstr[0] "%s možnost"
-msgstr[1] "%s možnosti"
-msgstr[2] "%s možnosti"
-msgstr[3] "%s možnosti"
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
 
 #, python-format
 msgid "%s option(s)"
 msgstr "Možnosti: %s"
 
-#, python-format
+#, fuzzy, python-format
 msgid "%s recipients"
-msgstr "%s prejemnikov"
+msgstr "nedavne"
 
-#, python-format
+#, fuzzy, python-format
 msgid "%s row"
 msgid_plural "%s rows"
 msgstr[0] "%s vrstica"
-msgstr[1] "%s vrstici"
-msgstr[2] "%s vrstice"
-msgstr[3] "%s vrstic"
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
 
 #, python-format
 msgid "%s saved metric(s)"
@@ -404,7 +368,7 @@ msgstr "(ni opisa, kliknite za ogled zapisov)"
 msgid "), and they become available in your SQL (example:"
 msgstr "), s čimer bodo na razpolago v sklopu SQL-poizvedbe (primer:"
 
-#, python-format
+#, fuzzy, python-format
 msgid ""
 "*%(name)s*\n"
 "\n"
@@ -415,10 +379,9 @@ msgid ""
 msgstr ""
 "*%(name)s*\n"
 "\n"
-"    %(description)s\n"
+"%(description)s\n"
 "\n"
-"    Napaka: %(text)s\n"
-"    "
+"napaka: %(text)s\n"
 
 #, python-format
 msgid ""
@@ -451,10 +414,6 @@ msgstr ""
 "boste počistili piškote ali zamenjali brskalnik.\n"
 "\n"
 
-#, python-format
-msgid "... and %s others"
-msgstr "... in %s drugih"
-
 msgid "0 Selected"
 msgstr "0 izbranih"
 
@@ -479,8 +438,9 @@ msgstr "1 minuta"
 msgid "1 minutely frequency"
 msgstr "frekvenca: 1 minuta"
 
+#, fuzzy
 msgid "1 month ago"
-msgstr "1 month ago"
+msgstr "mesec"
 
 msgid "1 month end frequency"
 msgstr "frekvenca: 1 mesec - konec"
@@ -644,10 +604,6 @@ msgstr "90 days"
 msgid ":"
 msgstr ":"
 
-#, python-format
-msgid ":%(column)d"
-msgstr ":%(column)d"
-
 msgid "< (Smaller than)"
 msgstr "< (manjše kot)"
 
@@ -688,19 +644,19 @@ msgid "A comma-separated list of schemas that files are allowed to upload to."
 msgstr "Z vejicami ločen seznam shem, kjer je dovoljeno nalaganje datotek."
 
 msgid "A database port is required when connecting via SSH Tunnel."
-msgstr "Pri povezavi preko SSH-tunela so potrebna vrata podatkovne baze."
+msgstr ""
 
 msgid "A database with the same name already exists."
 msgstr "Podatkovna baza z enakim imenom že obstaja."
 
+#, fuzzy
 msgid ""
 "A dictionary with column names and their data types if you need to change"
 " the defaults. Example: {\"user_id\":\"int\"}. Check Python's Pandas "
 "library for supported data types."
 msgstr ""
 "Slovar z imeni in podatkovnimi tipi stolpcev, s pomočjo katerega "
-"spremenite privzete nastavitve. Primer: {\"user_id\":\"int\"}. Preverite "
-"Pythonovo knjižnico Pandas za podprte podatkovne tipe."
+"spremenite privzete nastavitve. Primer: {\"user_id\":\"integer\"}"
 
 msgid ""
 "A full URL pointing to the location of the built plugin (could be hosted "
@@ -811,9 +767,9 @@ msgid ""
 "          These intermediate values can either be time based or category "
 "based."
 msgstr ""
-"Grafikon slapov je način prikaza, ki pomaga razumeti tkumulativni učinek "
-"zaporedja negativnih ali pozitivnih vrednosti. Vmesne vrednosti so bodisi"
-" kategorične bodisi časovne."
+"Grafikon slapov je način prikaza, ki pomaga razumeti\n"
+"\tkumulativni učinek zaporedja negativnih ali pozitivnih vrednosti.\n"
+"\tVmesne vrednosti so bodisi kategorične bodisi časovne."
 
 msgid "APPLY"
 msgstr "UPORABI"
@@ -857,8 +813,9 @@ msgstr "Aktiven"
 msgid "Actual Values"
 msgstr "Dejanske vrednosti"
 
+#, fuzzy
 msgid "Actual range for comparison"
-msgstr "Dejanski obseg za primerjavo"
+msgstr "Časovna primerjava"
 
 msgid "Actual time range"
 msgstr "Dejansko časovno obdobje"
@@ -878,11 +835,13 @@ msgstr "Dodaj"
 msgid "Add Alert"
 msgstr "Dodaj opozorilo"
 
+#, fuzzy
 msgid "Add BCC Recipients"
-msgstr "Dodaj skrite (BCC) prejemnike"
+msgstr "nedavne"
 
+#, fuzzy
 msgid "Add CC Recipients"
-msgstr "Dodaj prejemnike v vednost (CC)"
+msgstr "nedavne"
 
 msgid "Add CSS template"
 msgstr "Dodaj CSS predlogo"
@@ -944,8 +903,9 @@ msgstr "Dodaj oznako"
 msgid "Add annotation layer"
 msgstr "Dodaj sloj z oznakami"
 
+#, fuzzy
 msgid "Add another notification method"
-msgstr "Dodaj način obveščanja"
+msgstr "Dodajte način obveščanja"
 
 msgid "Add calculated columns to dataset in \"Edit datasource\" modal"
 msgstr "Dodaj izračunan stolpec v podatkovni set v oknu \"Uredi podatkovni vir\""
@@ -956,7 +916,7 @@ msgstr ""
 "podatkovni vir\""
 
 msgid "Add color for positive/negative change"
-msgstr "Dodaj barvo za pozitivno/negativno spremembo"
+msgstr ""
 
 msgid "Add cross-filter"
 msgstr "Dodaj medsebojni filter"
@@ -974,7 +934,7 @@ msgid "Add description of your tag"
 msgstr "Dodajte opis vaše oznake"
 
 msgid "Add extra connection information."
-msgstr "Dodatne informacije o povezavi."
+msgstr "Dodaj informacije o povezavi."
 
 msgid "Add filter"
 msgstr "Dodaj filter"
@@ -1032,7 +992,7 @@ msgid "Add the name of the chart"
 msgstr "Dodajte naslov grafikona"
 
 msgid "Add the name of the dashboard"
-msgstr "Dodajte ime nadzorne plošče"
+msgstr "Dodajte naziv nadzorne plošče"
 
 msgid "Add to dashboard"
 msgstr "Dodaj na nadzorno ploščo"
@@ -1043,13 +1003,13 @@ msgstr "Dodaj/uredi filter"
 msgid "Added"
 msgstr "Dodano"
 
-#, python-format
+#, fuzzy, python-format
 msgid "Added to 1 dashboard"
 msgid_plural "Added to %s dashboards"
 msgstr[0] "Dodano na 1 nadzorno ploščo"
-msgstr[1] "Dodano na %s nadzorni plošči"
-msgstr[2] "Dodano na %s nadzorne plošče"
-msgstr[3] "Dodano na %s nadzornih plošč"
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
 
 msgid "Additional Parameters"
 msgstr "Dodatni parametri"
@@ -1082,25 +1042,19 @@ msgid ""
 "Adds color to the chart symbols based on the positive or negative change "
 "from the comparison value."
 msgstr ""
-"Doda barvo za simbole na grafikonu, glede na to ali je pozitivna ali "
-"negativna sprememba."
 
 msgid ""
 "Adjust column settings such as specifying the columns to read, how "
 "duplicates are handled, column data types, and more."
 msgstr ""
-"Prilagodi nastavitve stolpcev, kot so kateri stolpci se berejo, obravnava"
-" duplikatov, podatkovni tipi in druge."
 
 msgid ""
 "Adjust how spaces, blank lines, null values are handled and other file "
 "wide settings."
 msgstr ""
-"Prilagodi, kako se obravnavajo presledki, prazne vrstice, NULL-vrednosti "
-"in ostale nastavitve glede datoteke."
 
 msgid "Adjust how this database will interact with SQL Lab."
-msgstr "Nastavite kako bo ta podatkovna baza delovala z SQL laboratorijem."
+msgstr "Nastavite kako bo ta podatkovna baza delovala z SQL-laboratorijem."
 
 msgid "Adjust performance settings of this database."
 msgstr "Prilagodite nastavitve zmogljivosti te podatkovne baze."
@@ -1123,8 +1077,9 @@ msgstr "Napredna analitika za poizvedbo A"
 msgid "Advanced analytics Query B"
 msgstr "Napredna analitika za poizvedba B"
 
+#, fuzzy
 msgid "Advanced analytics post processing"
-msgstr "Napredna analitika - poprocesiranje"
+msgstr "Napredna analitika"
 
 msgid "Advanced data type"
 msgstr "Napredni podatkovni tip"
@@ -1132,12 +1087,6 @@ msgstr "Napredni podatkovni tip"
 msgid "Advanced-Analytics"
 msgstr "Napredna analitika"
 
-msgid "Affected Charts"
-msgstr "Zadevani grafikoni"
-
-msgid "Affected Dashboards"
-msgstr "Zadevane nadzorne plošče"
-
 msgid "After"
 msgstr "Potem"
 
@@ -1184,8 +1133,9 @@ msgstr "Opozorilo sproženo, v obdobju mirovanja"
 msgid "Alert condition"
 msgstr "Status opozorila"
 
+#, fuzzy
 msgid "Alert contents"
-msgstr "Vsebina opozorila"
+msgstr "Označi vsebino"
 
 msgid "Alert ended grace period."
 msgstr "Opozorilo je končalo obdobje mirovanja."
@@ -1199,8 +1149,9 @@ msgstr "Opozorilo sproženo med obdobjem mirovanja."
 msgid "Alert found an error while executing a query."
 msgstr "Opozorilo je našlo napako pri izvajanju poizvedbe."
 
+#, fuzzy
 msgid "Alert is active"
-msgstr "Opozorilo je aktivno"
+msgstr "E-poštna poročila aktivna"
 
 msgid "Alert name"
 msgstr "Naslov opozorila"
@@ -1214,20 +1165,16 @@ msgstr "Poizvedba za opozorilo je vrnila neštevilsko vrednost."
 msgid "Alert query returned more than one column."
 msgstr "Poizvedba za opozorilo je vrnila več kot en stolpec."
 
-#, python-format
+#, fuzzy, python-format
 msgid "Alert query returned more than one column. %(num_cols)s columns returned"
-msgstr ""
-"Poizvedba za opozorilo je vrnila več kot en stolpec. %(num_cols)s "
-"vrnjenih stopcev"
+msgstr "Poizvedba za opozorilo je vrnila več kot en stolpec. "
 
 msgid "Alert query returned more than one row."
 msgstr "Poizvedba za opozorilo je vrnila več kot eno vrstico."
 
-#, python-format
+#, fuzzy, python-format
 msgid "Alert query returned more than one row. %(num_rows)s rows returned"
-msgstr ""
-"Poizvedba za opozorilo je vrnila več kot eno vrstico. %(num_rows)s "
-"vrnjenih vrstic"
+msgstr "Poizvedba za opozorilo je vrnila več kot eno vrstico."
 
 msgid "Alert running"
 msgstr "Opozorilo aktivno"
@@ -1286,14 +1233,11 @@ msgstr "Dovoli opcijo CREATE VIEW AS v SQL laboratoriju"
 msgid "Allow Csv Upload"
 msgstr "Dovoli nalaganje CSV"
 
-msgid "Allow DDL and DML"
-msgstr "Dovoli DDL in DML"
-
-msgid "Allow DDL/DML"
-msgstr "Dovoli DDL/DML"
+msgid "Allow DML"
+msgstr "Dovoli DML"
 
 msgid "Allow changing catalogs"
-msgstr "Omogoči spreminjanje katalogov"
+msgstr ""
 
 msgid ""
 "Allow column names to be changed to case insensitive format, if supported"
@@ -1324,20 +1268,19 @@ msgstr ""
 msgid "Allow file uploads to database"
 msgstr "Dovolite nalaganje datotek v podatkovno bazo"
 
+msgid ""
+"Allow manipulation of the database using non-SELECT statements such as "
+"UPDATE, DELETE, CREATE, etc."
+msgstr ""
+"Dovoli manipulacije podatkovne baze z uporabo ne-SELECT stavkov, kot so "
+"UPDATE, DELETE, CREATE, itd."
+
 msgid "Allow node selections"
 msgstr "Dovoli izbiro vozlišča"
 
 msgid "Allow sending multiple polygons as a filter event"
 msgstr "Dovoli pošiljanje več poligonov kot dogodek filtra"
 
-msgid ""
-"Allow the execution of DDL (Data Definition Language: CREATE, DROP, "
-"TRUNCATE, etc.) and DML (Data Modification Language: INSERT, UPDATE, "
-"DELETE, etc)"
-msgstr ""
-"Dovoli izvajanje DDL (Data Definition Language: CREATE, DROP, TRUNCATE, "
-"itd.) in DML (Data Modification Language: INSERT, UPDATE, DELETE, itd.)"
-
 msgid "Allow this database to be explored"
 msgstr "Dovoli raziskovanje te podatkovne baze"
 
@@ -1404,8 +1347,9 @@ msgstr "Prišlo je do napake"
 msgid "An error occurred saving dataset"
 msgstr "Pri shranjevanju podatkovnega seta je prišlo do napake"
 
+#, fuzzy
 msgid "An error occurred when running alert query"
-msgstr "Pri zaganjanju poizvedbe za opozorilo je prišlo do napake"
+msgstr "Pri krajšanju dnevnikov je prišlo do napake "
 
 msgid "An error occurred while accessing the value."
 msgstr "Pri dostopanju do vednosti je prišlo do težave."
@@ -1702,15 +1646,15 @@ msgstr ""
 
 msgid "Any databases that allow connections via SQL Alchemy URIs can be added. "
 msgstr ""
-"Dodate lahko katerokoli podatkovno bazo, ki podpira konekcije z "
-"SQLAlchemy URI-ji. "
+"Dodate lahko katerokoli podatkovno bazo, ki podpira konekcije z SQL "
+"Alchemy URI-ji. "
 
 msgid ""
 "Any databases that allow connections via SQL Alchemy URIs can be added. "
 "Learn about how to connect a database driver "
 msgstr ""
-"Dodate lahko katerokoli podatkovno bazo, ki podpira konekcije z "
-"SQLAlchemy URI-ji. Naučite se, kako povezati gonilnik podatkovne baze "
+"Dodate lahko katerokoli podatkovno bazo, ki podpira konekcije z SQL "
+"Alchemy URI-ji. Naučite se kako povezati gonilnik podatkovne baze "
 
 #, python-format
 msgid "Applied cross-filters (%d)"
@@ -1720,9 +1664,9 @@ msgstr "Uporabljeni medsebojni filtri (%d)"
 msgid "Applied filters (%d)"
 msgstr "Uporabljeni filtri (%d)"
 
-#, python-format
+#, fuzzy, python-format
 msgid "Applied filters (%s)"
-msgstr "Uporabljeni filtri (%s)"
+msgstr "Uporabljeni filtri (%d)"
 
 #, python-format
 msgid "Applied filters: %s"
@@ -1818,8 +1762,9 @@ msgstr "Ali želite nadaljevati?"
 msgid "Are you sure you want to save and apply changes?"
 msgstr "Ali resnično želite shraniti in uporabiti spremembe?"
 
+#, fuzzy
 msgid "Area"
-msgstr "Ploščina"
+msgstr "področje besedila"
 
 msgid "Area Chart"
 msgstr "Ploščinski grafikon"
@@ -1861,7 +1806,7 @@ msgid "August"
 msgstr "Avgust"
 
 msgid "Authorization needed"
-msgstr "Potrebna je avtorizacija"
+msgstr ""
 
 msgid "Auto"
 msgstr "Samodejno"
@@ -1908,8 +1853,9 @@ msgstr "Naraščajoča os"
 msgid "Axis descending"
 msgstr "Padajoča os"
 
+#, fuzzy
 msgid "BCC recipients"
-msgstr "Skriti (BCC) prejemniki"
+msgstr "nedavne"
 
 msgid "BOOLEAN"
 msgstr "BOOLEAN"
@@ -1951,7 +1897,7 @@ msgid "Bar orientation"
 msgstr "Orientacija stolpcev"
 
 msgid "Base"
-msgstr "Osnoven"
+msgstr "Osnova"
 
 #, python-format
 msgid "Base layer map style. See Mapbox documentation: %s"
@@ -1992,13 +1938,14 @@ msgid "Big Number Font Size"
 msgstr "Velikost pisave Velike številke"
 
 msgid "Big Number with Time Period Comparison"
-msgstr "Velika številka s časovno primerjavo"
+msgstr ""
 
 msgid "Big Number with Trendline"
 msgstr "Velika številka s trendno krivuljo"
 
+#, fuzzy
 msgid "Bins"
-msgstr "Razdelki"
+msgstr "v"
 
 msgid "Bottom"
 msgstr "Spodaj"
@@ -2149,8 +2096,9 @@ msgstr "Po vrednosti: za razvrščanje uporabite vrednosti mere"
 msgid "CANCEL"
 msgstr "PREKINI"
 
+#, fuzzy
 msgid "CC recipients"
-msgstr "V vednost (CC)"
+msgstr "nedavne"
 
 msgid "CREATE DATASET"
 msgstr "USTVARI PODATKOVNI SET"
@@ -2299,8 +2247,9 @@ msgstr "Filtra ni mogoče naložiti"
 msgid "Cannot parse time string [%(human_readable)s]"
 msgstr "Ni mogoče razčleniti časovnega izraza [%(human_readable)s]"
 
+#, fuzzy
 msgid "Catalog"
-msgstr "Katalog"
+msgstr "oznaka"
 
 msgid "Categorical"
 msgstr "Kategorični"
@@ -2448,13 +2397,13 @@ msgstr "Možnosti grafikona"
 msgid "Chart Orientation"
 msgstr "Orientacija grafikona"
 
-#, python-format
+#, fuzzy, python-format
 msgid "Chart Owner: %s"
 msgid_plural "Chart Owners: %s"
 msgstr[0] "Lastnik grafikona: %s"
-msgstr[1] "Lastnika grafikona: %s"
-msgstr[2] "Lastniki grafikona: %s"
-msgstr[3] "Lastniki grafikona: %s"
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
 
 msgid "Chart Source"
 msgstr "Podatkovni vir grafikona"
@@ -2553,8 +2502,8 @@ msgid ""
 "Check if the Rose Chart should use segment area instead of segment radius"
 " for proportioning"
 msgstr ""
-"Če želite, da polarni tortni grafikon uporablja površino segmenta namesto"
-" radija za proporcioniranje"
+"Če želite, da grafikon \"Rose\" uporablja površino segmenta namesto "
+"radija za proporcioniranje"
 
 msgid "Check out this chart in dashboard:"
 msgstr "Preizkusite ta grafikon v nadzorni plošči:"
@@ -2589,8 +2538,9 @@ msgstr "Izberite podatkovno bazo..."
 msgid "Choose a dataset"
 msgstr "Izberite podatkovni set"
 
+#, fuzzy
 msgid "Choose a delimiter"
-msgstr "Izberite ločilnik"
+msgstr "Izberite podatkovni set"
 
 msgid "Choose a metric for right axis"
 msgstr "Izberite mero za desno os"
@@ -2607,32 +2557,39 @@ msgstr "Izberite izhodišče in cilj"
 msgid "Choose a target"
 msgstr "Izberite cilj"
 
+#, fuzzy
 msgid "Choose already exists"
-msgstr "Izbira že obstaja"
+msgstr "Oznaka že obstaja"
 
 msgid "Choose chart type"
 msgstr "Izberite tip grafikona"
 
+#, fuzzy
 msgid "Choose columns to be parsed as dates"
-msgstr "Izberite stolpce, ki bodo prepoznani kot datumi"
+msgstr "Stolpci, ki bodo prepoznani kot datumi"
 
+#, fuzzy
 msgid "Choose columns to read"
-msgstr "Izberite stolpce za branje"
+msgstr "Stolpci za branje"
 
+#, fuzzy
 msgid "Choose index column"
-msgstr "Izberite Indeksni stolpec"
+msgstr "Indeksni stolpec"
 
+#, fuzzy
 msgid "Choose notification method and recipients."
-msgstr "Dodajte način obveščanja in prejemnike."
+msgstr "Dodajte način obveščanja"
 
 msgid "Choose one of the available databases from the panel on the left."
 msgstr "Izberite eno od razpoložljivih podatkovnih baz v panelu na levi."
 
+#, fuzzy
 msgid "Choose one of the available databases on the left panel."
 msgstr "Izberite eno od razpoložljivih podatkovnih baz v panelu na levi."
 
+#, fuzzy
 msgid "Choose sheet name"
-msgstr "Izberite ime zvezka"
+msgstr "Ime zvezka"
 
 msgid "Choose the annotation layer type"
 msgstr "Izberite tip sloja z oznakami"
@@ -2646,12 +2603,14 @@ msgstr "Izberite položaj legende"
 msgid "Choose the source of your annotations"
 msgstr "Izberite vir svojih oznak"
 
+#, fuzzy
 msgid ""
 "Choose values that should be treated as null. Warning: Hive database "
 "supports only a single value"
 msgstr ""
-"Izberite vrednosti, ki bodo obravnavane kot prazne (Null). Opozorilo: "
-"podatkovna baza Hive podpira le eno vrednost"
+"JSON seznam vrednosti, ki bodo obravnavane kot prazne (Null). Primeri: "
+"[\"\"] za prazne nize, [\"None\", \"N/A\"], [\"nan\", \"null\"]. "
+"Opozorilo: Podatkovna baza Hive podpira le eno vrednost"
 
 msgid ""
 "Choose whether a country should be shaded by the metric, or assigned a "
@@ -2690,7 +2649,7 @@ msgid ""
 msgstr "Standardna razpredelnica za prikaz podatkovnega seta."
 
 msgid "Clause"
-msgstr "Dodatni WHERE pogoj"
+msgstr "Stavek"
 
 msgid "Clear"
 msgstr "Počisti"
@@ -2877,6 +2836,7 @@ msgstr "Stolpec s podatki zemljepisne širine"
 msgid "Column containing longitude data"
 msgstr "Stolpec s podatki zemljepisne dolžine"
 
+#, fuzzy
 msgid "Column data types"
 msgstr "Podatkovni tipi stolpcev"
 
@@ -2903,15 +2863,17 @@ msgstr "Stolpec referenciran z agregacijo ni definiran: %(column)s"
 msgid "Column select"
 msgstr "Izbira stolpca"
 
+#, fuzzy
 msgid ""
 "Column to use as the index of the dataframe. If None is given, Index "
 "label is used."
 msgstr ""
-"Stolpec, ki se uporabi kot indeks v dataframe-u. Če je prazno, se uporabi"
-" oznaka Index."
+"Stolpec, ki se uporabi za naslove vrstic v dataframe-u. Pustite prazno, "
+"če ni indeksnega stolpca."
 
+#, fuzzy
 msgid "Columnar Upload"
-msgstr "Nalaganje stolpčne datoteke"
+msgstr "Stolpčna datoteka"
 
 msgid "Columns"
 msgstr "Stolpci"
@@ -2978,7 +2940,7 @@ msgstr ""
 "povezanih mer."
 
 msgid "Compare results with other time periods."
-msgstr "Primerjava rezultatov z med časovnimi obdobji."
+msgstr ""
 
 msgid "Compare the same summarized metric across multiple groups."
 msgstr "Primerja isto mero med različnimi skupinami."
@@ -3011,8 +2973,9 @@ msgstr "Primerjava"
 msgid "Comparison Period Lag"
 msgstr "Preteklo obdobje za primerjavo"
 
+#, fuzzy
 msgid "Comparison font size"
-msgstr "Velikost pisave za primerjavo"
+msgstr "Pripona za procent"
 
 msgid "Comparison suffix"
 msgstr "Pripona za procent"
@@ -3044,8 +3007,9 @@ msgstr "Nastavitve"
 msgid "Configure Advanced Time Range "
 msgstr "Nastavi napredno časovno obdobje "
 
+#, fuzzy
 msgid "Configure Time Range: Current..."
-msgstr "Nastavi časovno obdobje: Tekoči ..."
+msgstr "Nastavi časovno obdobje: Zadnji ..."
 
 msgid "Configure Time Range: Last..."
 msgstr "Nastavi časovno obdobje: Zadnji ..."
@@ -3066,7 +3030,7 @@ msgid "Configure this dashboard to embed it into an external web application."
 msgstr "Nastavite nadzorno ploščo za vgradnjo v zunanjo spletno aplikacijo."
 
 msgid "Configure your how you overlay is displayed here."
-msgstr "Nastavite kako prikazuje vrhnja plast."
+msgstr "Nastavite kako se tukaj prikazuje vrhnja plast."
 
 msgid "Confirm overwrite"
 msgstr "Potrdite prepis"
@@ -3084,10 +3048,10 @@ msgid "Connect Google Sheets as tables to this database"
 msgstr "Googlove preglednice poveži s to podatkovno bazo kot tabele"
 
 msgid "Connect a database"
-msgstr "Povezovanje s podatkovno bazo"
+msgstr "Poveži se s podatkovno bazo"
 
 msgid "Connect database"
-msgstr "Povezovanje s podatkovno bazo"
+msgstr "Poveži se s podatkovno bazo"
 
 msgid "Connect this database using the dynamic form instead"
 msgstr "S podatkovno bazo se povežite z dinamičnim obrazcem"
@@ -3101,17 +3065,20 @@ msgstr "Povezava"
 msgid "Connection failed, please check your connection settings"
 msgstr "Povezava neuspešna. Preverite nastavitve povezave"
 
+#, fuzzy
 msgid "Connection failed, please check your connection settings."
-msgstr "Povezava neuspešna. Preverite nastavitve povezave."
+msgstr "Povezava neuspešna. Preverite nastavitve povezave"
 
 msgid "Connection looks good!"
 msgstr "Povezava izgleda v redu!"
 
+#, fuzzy
 msgid "Content format"
-msgstr "Oblika vsebine"
+msgstr "Oblika zapisa datuma"
 
+#, fuzzy
 msgid "Content type"
-msgstr "Tip vsebine"
+msgstr "Stopnični tip"
 
 msgid "Continue"
 msgstr "Nadaljuj"
@@ -3263,7 +3230,7 @@ msgid ""
 msgstr ""
 "Ustvarite podatkovni set, da začnete vizualizacijo podatkov z grafikonom "
 "ali\n"
-"          pojdite v SQL laboratorij za poizvedovanje nad podatki."
+"          pojdite v SQL-laboratorij za poizvedovanje nad podatki."
 
 msgid "Create a new chart"
 msgstr "Ustvarite nov grafikon"
@@ -3274,8 +3241,9 @@ msgstr "Ustvarite grafikon"
 msgid "Create chart with dataset"
 msgstr "Ustvarite grafikon s podatkovnim setom"
 
+#, fuzzy
 msgid "Create dataframe index"
-msgstr "Ustvarite indeks dataframe-a"
+msgstr "Indeks dataframe-a"
 
 msgid "Create dataset"
 msgstr "Ustvarite podatkovni set"
@@ -3290,7 +3258,7 @@ msgid "Create or select schema..."
 msgstr "Ustvarite ali izberite shemo..."
 
 msgid "Created"
-msgstr "Ustvarjeno"
+msgstr "Ustvarjene"
 
 msgid "Created by"
 msgstr "Ustvaril"
@@ -3345,23 +3313,29 @@ msgstr "Predpona ali pripona valute"
 msgid "Currency symbol"
 msgstr "Simbol valute"
 
+#, fuzzy
 msgid "Current"
-msgstr "Tekoči"
+msgstr "Valuta"
 
+#, fuzzy
 msgid "Current day"
-msgstr "Tekoči dan"
+msgstr "Valuta"
 
+#, fuzzy
 msgid "Current month"
-msgstr "Tekoči mesec"
+msgstr "Simbol valute"
 
+#, fuzzy
 msgid "Current quarter"
-msgstr "Tekoče četrtletje"
+msgstr "Zaženi trenutno poizvedbo"
 
+#, fuzzy
 msgid "Current week"
-msgstr "Tekoči teden"
+msgstr "Zaženi trenutno poizvedbo"
 
+#, fuzzy
 msgid "Current year"
-msgstr "Tekoče leto"
+msgstr "Valuta"
 
 #, python-format
 msgid "Currently rendered: %s"
@@ -3370,8 +3344,9 @@ msgstr "Trenutno izrisano: %s"
 msgid "Custom"
 msgstr "Prilagojen"
 
+#, fuzzy
 msgid "Custom Conditional Formatting"
-msgstr "Prilagojeno pogojno oblikovanje"
+msgstr "Pogojno oblikovanje"
 
 msgid "Custom Plugin"
 msgstr "Prilagojeni vtičnik"
@@ -3383,19 +3358,22 @@ msgid "Custom SQL"
 msgstr "Prilagojen SQL"
 
 msgid "Custom SQL ad-hoc metrics are not enabled for this dataset"
-msgstr "Prilagodljive ad-hoc SQL-mere za ta podatkovni set niso omogočene"
+msgstr "Ad-hoc SQL mere po meri za ta podatkovni set niso omogočene"
 
 msgid "Custom SQL fields cannot contain sub-queries."
 msgstr "Prilagojena SQL-polja ne smejo vsebovati podpoizvedb."
 
+#, fuzzy
 msgid "Custom color palettes"
-msgstr "Prilagojene barvne palete"
+msgstr "Samodokončaj"
 
+#, fuzzy
 msgid "Custom date"
-msgstr "Prilagojen datum"
+msgstr "Prilagojen"
 
+#, fuzzy
 msgid "Custom interval"
-msgstr "Prilagojen interval"
+msgstr "Interval"
 
 msgid "Custom time filter plugin"
 msgstr "Prilagojeni vtičnik za časovni filter"
@@ -3420,7 +3398,7 @@ msgid "Customize columns"
 msgstr "Prilagodi stolpce"
 
 msgid "Customize data source, filters, and layout."
-msgstr "Prilagodite podatkovni vir, filtre in izgled."
+msgstr ""
 
 msgid "Cyclic dependency detected"
 msgstr "Zaznana krožna odvisnost"
@@ -3488,15 +3466,6 @@ msgstr "Nadzorna plošča [%s] je bila ravno ustvarjena in grafikon [%s] dodan n
 msgid "Dashboard [{}] just got created and chart [{}] was added to it"
 msgstr "Nadzorna plošča [{}] je bila ravno ustvarjena in grafikon [{}] dodan nanjo"
 
-msgid "Dashboard cannot be copied due to invalid parameters."
-msgstr "Nadzorne plošče ni mogoče kopirati zaradi neveljavnih parametrov."
-
-msgid "Dashboard cannot be favorited."
-msgstr "Nadzorne plošče ni mogoče dodati med priljubljene."
-
-msgid "Dashboard cannot be unfavorited."
-msgstr "Nadzorne plošče ni mogoče odstraniti iz priljubljenih."
-
 msgid "Dashboard could not be deleted."
 msgstr "Nadzorne plošče ni mogoče izbrisati."
 
@@ -3527,12 +3496,13 @@ msgid ""
 "chart\n"
 "          filters to have this dashboard filter impact those charts."
 msgstr ""
-"Filtri časovnega obdobja vplivajo na časovne stolpce, definirane v "
-"filtrski sekciji vsakega grafikona. Filtrom grafikonov dodajte časovne "
-"stolpce, da bodo filtri nadzorne plošče imeli učinek nanje."
+"Filtri časovnega obdobja vplivajo na časovne stolpce, definirane v\n"
+"\tfiltrski sekciji vsakega grafikona. Filtrom grafikonov dodajte časovne "
+"stolpce,\n"
+"\t da bodo filtri nadzorne plošče imeli učinek nanje."
 
 msgid "Dashboard title"
-msgstr "Ime nadzorne plošče"
+msgstr "Naziv nadzorne plošče"
 
 msgid "Dashboard usage"
 msgstr "Uporaba nadzorne plošče"
@@ -3552,8 +3522,9 @@ msgstr "Črtkano"
 msgid "Data"
 msgstr "Podatki"
 
+#, fuzzy
 msgid "Data Imported"
-msgstr "Podatki uvoženi"
+msgstr "Podatkovni set uvožen"
 
 msgid "Data Table"
 msgstr "Tabela podatkov"
@@ -3580,9 +3551,9 @@ msgstr ""
 "Podatkov ni bilo mogoče pridobiti iz zalednega sistema rezultatov. "
 "Ponovno morate zagnati izvorno poizvedbo."
 
-#, python-format
+#, fuzzy, python-format
 msgid "Data for %s"
-msgstr "Podatki za %s"
+msgstr "Dodatni podatki za JS"
 
 msgid "Data preview"
 msgstr "Ogled podatkov"
@@ -3663,22 +3634,23 @@ msgstr "Gesla podatkovne baze"
 msgid "Database port"
 msgstr "Vrata podatkovne baze"
 
+#, fuzzy
 msgid "Database schema is not allowed for csv uploads."
-msgstr "Shema podatkovne baze ne dovoljuje nalaganje csv-datotek."
+msgstr "Dovoljene sheme za nalaganje datotek"
 
 msgid "Database settings updated"
 msgstr "Nastavitve podatkovne baze posodobljene"
 
+#, fuzzy
 msgid "Database type does not support file uploads."
-msgstr "Tip podatkovne baze ne podpira nalaganje datotek."
+msgstr "Podatkovna baza ne podpira podpoizvedb"
 
+#, fuzzy
 msgid "Database upload file failed"
-msgstr "Nalaganje datoteke v podatkovno bazo ni uspelo"
+msgstr "Izberite podatkovno bazo za nalaganje datoteke"
 
 msgid "Database upload file failed, while saving metadata"
 msgstr ""
-"Nalaganje datoteke v podatkovno bazo ni uspelo pri shranjevanju "
-"metapodatkov"
 
 msgid "Databases"
 msgstr "Podatkovne baze"
@@ -3686,9 +3658,9 @@ msgstr "Podatkovne baze"
 msgid "Dataset"
 msgstr "Podatkovni set"
 
-#, python-format
+#, fuzzy, python-format
 msgid "Dataset %(table)s already exists"
-msgstr "Podatkovni set %(table)s že obstaja"
+msgstr "Podatkovni set %(name)s že obstaja"
 
 msgid "Dataset Name"
 msgstr "Ime podatkovnega seta"
@@ -3863,14 +3835,16 @@ msgstr "Deck.gl - mreža"
 msgid "Decrease"
 msgstr "Zmanjšaj"
 
+#, fuzzy
 msgid "Default Catalog"
-msgstr "Privzeti katalog"
+msgstr "Privzeta vrednost"
 
 msgid "Default Endpoint"
 msgstr "Privzeta končna točka"
 
+#, fuzzy
 msgid "Default Schema"
-msgstr "Privzeta shema"
+msgstr "Izberite shemo"
 
 msgid "Default URL"
 msgstr "Privzeti URL"
@@ -3948,12 +3922,10 @@ msgstr ""
 "poligonov, ki zapolnjujejo področje v določenem obsegu vrednosti."
 
 msgid "Define delivery schedule, timezone, and frequency settings."
-msgstr "Definirajte nastavitve urnika dostave, časovnega pasu in frekvence."
+msgstr ""
 
 msgid "Define the database, SQL query, and triggering conditions for alert."
 msgstr ""
-"Definirajte podatkovno bazo, SQL-poizvedbo in pogoje proženja za "
-"opozorilo."
 
 msgid ""
 "Defines a rolling window function to apply, works along with the "
@@ -4133,11 +4105,11 @@ msgid "Deleted: %s"
 msgstr "Izbrisano: %s"
 
 msgid ""
-"Deleting a tab will remove all content within it and will deactivate any "
-"related alerts or reports. You may still reverse this action with the"
+"Deleting a tab will remove all content within it. You may still reverse "
+"this action with the"
 msgstr ""
-"Izbris zavihka bo odstranil vso vsebino v njem in deaktiviral povezana "
-"opozorila in poročila. Še vedno boste lahko razveljavili dejanje z"
+"Izbris zavihka bo odstranil vso vsebino v njem. Še vedno boste lahko "
+"razveljavili dejanje z"
 
 msgid "Delimited long & lat single column"
 msgstr "En stolpec z ločenima zemljepisno dolžino in širino"
@@ -4241,8 +4213,9 @@ msgstr ""
 "laboratoriju. S tem se zmanjša obremenitev brskalnika pri podatkovnih "
 "bazah z zelo širokimi tabelami."
 
+#, fuzzy
 msgid "Disable drill to detail"
-msgstr "Onemogočite vrtanje v podrobnosti"
+msgstr "Vrtanje v podrobnosti"
 
 msgid "Disable embedding?"
 msgstr "Onemogočite vgrajevanje?"
@@ -4250,8 +4223,9 @@ msgstr "Onemogočite vgrajevanje?"
 msgid "Disabled"
 msgstr "Onemogočeno"
 
+#, fuzzy
 msgid "Disables the drill to detail feature for this database."
-msgstr "Za to podatkovno bazo onemogočite vrtanje v podrobnosti."
+msgstr "Za podatkovni set ni vrnjenih vzorcev"
 
 msgid "Discard"
 msgstr "Zavrzi"
@@ -4262,8 +4236,9 @@ msgstr "Prikaz"
 msgid "Display Name"
 msgstr "Ime za prikaz"
 
+#, fuzzy
 msgid "Display all"
-msgstr "Prikaži vse"
+msgstr "Ime za prikaz"
 
 msgid "Display column level subtotal"
 msgstr "Prikaži delno vsoto na nivoju stolpca"
@@ -4405,26 +4380,26 @@ msgid ""
 "Drill to detail is disabled for this database. Change the database "
 "settings to enable it."
 msgstr ""
-"Vrtanje v podrobnosti je onemogočeno za to podatkovno bazo. Spremenite "
-"nastavitve podatkovne baze, če ga želite omogočiti."
 
 #, python-format
 msgid "Drill to detail: %s"
 msgstr "Vrtanje v podrobnosti: %s"
 
+#, fuzzy
 msgid "Drop a column here or click"
 msgid_plural "Drop columns here or click"
 msgstr[0] "Spustite stolpec sem ali kliknite"
-msgstr[1] "Spustite stolpce sem ali kliknite"
-msgstr[2] "Spustite stolpce sem ali kliknite"
-msgstr[3] "Spustite stolpce sem ali kliknite"
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
 
+#, fuzzy
 msgid "Drop a column/metric here or click"
 msgid_plural "Drop columns/metrics here or click"
 msgstr[0] "Spustite stolpec/mero sem ali kliknite"
-msgstr[1] "Spustite stolpce/mere sem ali kliknite"
-msgstr[2] "Spustite stolpce/mere sem ali kliknite"
-msgstr[3] "Spustite stolpce/mere sem ali kliknite"
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
 
 msgid "Drop a temporal column here or click"
 msgstr "Spustite stolpec sem ali kliknite"
@@ -4686,7 +4661,7 @@ msgid "Email reports active"
 msgstr "E-poštna poročila aktivna"
 
 msgid "Email subject name (optional)"
-msgstr "Naslov sporočila (opcijsko)"
+msgstr ""
 
 msgid "Embed"
 msgstr "Vgradi"
@@ -4697,9 +4672,6 @@ msgstr "Koda za vgradnjo"
 msgid "Embed dashboard"
 msgstr "Vgradi nadzorno ploščo"
 
-msgid "Embedded dashboard could not be deleted."
-msgstr "Vdelane nadzorne plošče ni mogoče izbrisati."
-
 msgid "Embedding deactivated."
 msgstr "Vgrajevanje deaktivirano."
 
@@ -4765,8 +4737,8 @@ msgstr "Omogoči razširitev vrstic v shemah"
 
 msgid "Enable server side pagination of results (experimental feature)"
 msgstr ""
-"Omogoči številčenje strani rezultatov na strani strežnika (funkcija v "
-"razvoju)"
+"Omogoči številčenje strani rezultatov na strani strežnika (preizkusna "
+"funkcija)"
 
 msgid ""
 "Encountered invalid NULL spatial entry,"
@@ -4823,8 +4795,9 @@ msgstr "Vnesite ime te preglednice"
 msgid "Enter a new title for the tab"
 msgstr "Vnesite novo naslov zavihka"
 
+#, fuzzy
 msgid "Enter alert name"
-msgstr "Vnesite naslov opozorila"
+msgstr "Naslov opozorila"
 
 msgid "Enter duration in seconds"
 msgstr "Vnesite trajanje v sekundah"
@@ -4832,8 +4805,9 @@ msgstr "Vnesite trajanje v sekundah"
 msgid "Enter fullscreen"
 msgstr "Vklopi celozaslonski način"
 
+#, fuzzy
 msgid "Enter report name"
-msgstr "Vnesite naslov poročila"
+msgstr "Naslov poročila"
 
 #, python-format
 msgid "Enter the required %(dbModelName)s credentials"
@@ -4857,9 +4831,6 @@ msgstr "Napaka"
 msgid "Error Fetching Tagged Objects"
 msgstr "Pri pridobivanju označenih elementov je prišlo do napake"
 
-msgid "Error faving chart"
-msgstr "Napaka pri dodajanju grafikona med priljubljene"
-
 #, python-format
 msgid "Error in jinja expression in HAVING clause: %(msg)s"
 msgstr "Napaka v jinja izrazu HAVING stavka: %(msg)s"
@@ -4884,24 +4855,21 @@ msgstr ""
 msgid "Error message"
 msgstr "Sporočilo napake"
 
-msgid "Error parsing"
-msgstr "Napaka obdelave"
-
+#, fuzzy
 msgid "Error reading CSV file"
-msgstr "Napaka pri branju CSV-datoteke"
+msgstr "Pri shranjevanju podatkovnega seta je prišlo do napake"
 
+#, fuzzy
 msgid "Error reading Columnar file"
-msgstr "Napaka pri branju stolpčne datoteke"
+msgstr "Naloži stolpčno datoteko"
 
+#, fuzzy
 msgid "Error reading Excel file"
-msgstr "Napaka pri branju Excel-ove datoteke"
+msgstr "Naloži Excel-ovo datoteko"
 
 msgid "Error saving dataset"
 msgstr "Pri shranjevanju podatkovnega seta je prišlo do napake"
 
-msgid "Error unfaving chart"
-msgstr "Napaka pri odstranjevanju grafikona iz priljubljenih"
-
 msgid "Error while fetching charts"
 msgstr "Napaka pri pridobivanju grafikonov"
 
@@ -4966,11 +4934,12 @@ msgstr "Primer"
 msgid "Examples"
 msgstr "Vzorci"
 
+#, fuzzy
 msgid "Excel Upload"
-msgstr "Nalaganje Excel-a"
+msgstr "Nalaganje CSV"
 
 msgid "Excel file format cannot be determined"
-msgstr "Ni mogoče določiti formata Excel-ove datoteke"
+msgstr ""
 
 msgid "Exclude selected values"
 msgstr "Izloči izbrane vrednosti"
@@ -5059,6 +5028,7 @@ msgstr "Izvozi v Excel"
 msgid "Export to PDF"
 msgstr "Izvozi v PDF"
 
+#, fuzzy
 msgid "Export to Pivoted .CSV"
 msgstr "Izvozi v vrtilni .CSV"
 
@@ -5081,7 +5051,7 @@ msgid "Export to pivoted .CSV"
 msgstr "Izvozi v vrtilni .CSV"
 
 msgid "Expose database in SQL Lab"
-msgstr "Prikaži podatkovno bazo v SQL laboratoriju"
+msgstr "Razkrij podatkovno bazo v SQL laboratoriju"
 
 msgid "Expose in SQL Lab"
 msgstr "Uporabi v SQL laboratoriju"
@@ -5196,13 +5166,14 @@ msgid "Failed to verify select options: %s"
 msgstr "Preverjanje možnosti izbire ni uspelo: %s"
 
 msgid "Favorite"
-msgstr "Priljubljeno"
+msgstr "Priljubljene"
 
+#, fuzzy
 msgid "Featured"
 msgstr "Ustvarjene"
 
 msgid "Featured color palettes"
-msgstr "Ustvarjene barvne palete"
+msgstr ""
 
 msgid "February"
 msgstr "Februar"
@@ -5234,17 +5205,20 @@ msgstr "Polje je obvezno"
 msgid "File"
 msgstr "Datoteka"
 
+#, fuzzy
 msgid "File Settings"
-msgstr "Nastavitve datoteke"
+msgstr "Nastavitve filtra"
 
+#, fuzzy
 msgid "File extension is not allowed."
-msgstr "Končnica datoteke ni dovoljena."
+msgstr "URI za podatke ni dovoljen."
 
 msgid "File size exceeds the maximum allowed size."
-msgstr "Velikost datoteke presega največjo dovoljeno."
+msgstr ""
 
+#, fuzzy
 msgid "File upload"
-msgstr "Nalaganje datoteke"
+msgstr "Naloži"
 
 msgid "Fill Color"
 msgstr "Barva polnila"
@@ -5319,7 +5293,7 @@ msgid "Filters by metrics"
 msgstr "Filtrira po merah"
 
 msgid "Filters for comparison must have a value"
-msgstr "Filtri za primerjavo morajo imeti vrednost"
+msgstr ""
 
 #, python-format
 msgid "Filters out of scope (%d)"
@@ -5417,7 +5391,7 @@ msgid ""
 "For base filters, these are the roles that the filter DOES NOT apply to, "
 "e.g. Admin if admin should see all data."
 msgstr ""
-"Za navadne filtre so te vloge tiste, ki bodo filtrirane. Za osnovne "
+"Za regularne filtre so te vloge tiste, ki bodo filtrirane. Za osnovne "
 "filtre, so te vloge tiste, ki NE bodo filtrirane, npr. Admin, če naj "
 "administrator vidi vse podatke."
 
@@ -5431,8 +5405,9 @@ msgstr ""
 "Vsilite, da bodo vse tabele in pogledi ustvarjeni s to shemo, ko kliknete"
 " CTAS ali CVAS v SQL laboratoriju."
 
+#, fuzzy
 msgid "Force categorical"
-msgstr "Vsili kategorično"
+msgstr "Kategorija izvora"
 
 msgid "Force date format"
 msgstr "Vsili obliko zapisa datuma"
@@ -5440,8 +5415,9 @@ msgstr "Vsili obliko zapisa datuma"
 msgid "Force refresh"
 msgstr "Osveži"
 
+#, fuzzy
 msgid "Force refresh catalog list"
-msgstr "Vsili osvežitev seznama katalogov"
+msgstr "Osveži seznam tabel"
 
 msgid "Force refresh schema list"
 msgstr "Osveži seznam shem"
@@ -5476,9 +5452,6 @@ msgid ""
 "represents a new line. ECharts compatibility:\n"
 "{a} (series), {b} (name), {c} (value), {d} (percentage)"
 msgstr ""
-"Oblikujte oznake podatkov. Uporabite spremenljivke: {name}, {value}, "
-"{percent}. \\n predstavlja novo vrstico. Kompatibilnost z ECharts:\n"
-"{a} (serija), {b} (naziv), {c} (vrednost), {d} (odstotek)"
 
 msgid "Formatted CSV attached in email"
 msgstr "Oblikovan CSV pripet e-pošti"
@@ -5540,8 +5513,9 @@ msgstr "Števčni grafikon"
 msgid "General"
 msgstr "Splošno"
 
+#, fuzzy
 msgid "General information"
-msgstr "Splošne informacije"
+msgstr "Dodatne informacije"
 
 msgid "Generating link, please wait.."
 msgstr "Ustvarjam povezavo, prosim počakajte..."
@@ -5568,7 +5542,7 @@ msgid "Get the specify date for the holiday"
 msgstr "Določi datum praznika"
 
 msgid "Give access to multiple catalogs in a single database connection."
-msgstr "Dodeli dostop do več katalogov z eno povezavo na podatkovno bazo."
+msgstr ""
 
 msgid "Go to the edit mode to configure the dashboard and add charts"
 msgstr ""
@@ -5600,7 +5574,7 @@ msgid "Greater than (>)"
 msgstr "Večje kot (>)"
 
 msgid "Green for increase, red for decrease"
-msgstr "Zelena za povečanje, rdeča za zmanjšanje"
+msgstr ""
 
 msgid "Grid"
 msgstr "Mreža"
@@ -5624,10 +5598,11 @@ msgid "Groupable"
 msgstr "Združevanje"
 
 msgid "Guest user cannot modify chart payload"
-msgstr "Gost ne more spreminjati atributov grafikona"
+msgstr ""
 
+#, fuzzy
 msgid "HOUR"
-msgstr "URA"
+msgstr "ura"
 
 msgid "Handlebars"
 msgstr "Handlebars"
@@ -5636,7 +5611,7 @@ msgid "Handlebars Template"
 msgstr "Predloga za Handlebars"
 
 msgid "Hard value bounds applied for color coding."
-msgstr "Točno definiran obseg vrednosti za barvno kodiranje."
+msgstr ""
 
 msgid ""
 "Hard value bounds applied for color coding. Is only relevant and applied "
@@ -5654,17 +5629,19 @@ msgstr "Glava"
 msgid "Header Row"
 msgstr "Naslovna vrstica"
 
+#, fuzzy
 msgid "Header row"
-msgstr "Vrstica z glavo"
+msgstr "Naslovna vrstica"
 
 msgid "Heatmap"
-msgstr "Toplotna karta"
+msgstr "Toplotni prikaz"
 
+#, fuzzy
 msgid "Heatmap (legacy)"
-msgstr "Toplotna karta (zastarelo)"
+msgstr "Tortni grafikon (zastarelo)"
 
 msgid "Heatmap Options"
-msgstr "Možnosti toplotne karte"
+msgstr "Možnosti toplotnega prikaza"
 
 msgid "Height"
 msgstr "Višina"
@@ -5696,8 +5673,9 @@ msgstr "Hierarhija"
 msgid "Histogram"
 msgstr "Histogram"
 
+#, fuzzy
 msgid "Histogram (legacy)"
-msgstr "Histogram (zastarelo)"
+msgstr "Tortni grafikon (zastarelo)"
 
 msgid "Home"
 msgstr "Domov"
@@ -5891,9 +5869,9 @@ msgstr "Vključi serijo"
 msgid "Include a description that will be sent with your report"
 msgstr "Vključite opis, ki bo vključen v poročilo"
 
-#, python-format
+#, fuzzy, python-format
 msgid "Include description to be sent with %s"
-msgstr "Vključite opis, ki bo poslan z %s"
+msgstr "Vključite opis, ki bo vključen v poročilo"
 
 msgid "Include series name as an axis"
 msgstr "Vključi ime podatkovne serije v naslov osi"
@@ -5910,17 +5888,19 @@ msgstr "Indeks"
 msgid "Index Column"
 msgstr "Indeksni stolpec"
 
+#, fuzzy
 msgid "Index Label"
-msgstr "Oznaka indeksa"
+msgstr "Oznake razponov"
 
+#, fuzzy
 msgid "Index label"
-msgstr "Oznaka indeksa"
+msgstr "Oznake razponov"
 
 msgid "Info"
 msgstr "Informacije"
 
 msgid "Inherit range from time filter"
-msgstr "Prevzemi obdobje iz časovnega filtra"
+msgstr ""
 
 msgid "Inner Radius"
 msgstr "Notranji polmer"
@@ -5964,6 +5944,9 @@ msgstr "Stolpec začetka intervala"
 msgid "Intervals"
 msgstr "Intervali"
 
+msgid "Intesity"
+msgstr "Intenzivnost"
+
 msgid ""
 "Invalid Connection String: Expecting String of the form "
 "'ocient://user:pass@host:port/database'."
@@ -6059,7 +6042,7 @@ msgstr "Neveljaven rolling_type: %(type)s"
 
 #, python-format
 msgid "Invalid spatial point encountered: %(latlong)s"
-msgstr "Neveljavna prostorska točka: %(latlong)s"
+msgstr ""
 
 msgid "Invalid state."
 msgstr "Neveljavno stanje."
@@ -6077,8 +6060,9 @@ msgstr "Invertiraj trenutno stran"
 msgid "Is certified"
 msgstr "Certificiran"
 
+#, fuzzy
 msgid "Is custom tag"
-msgstr "Prilagojena oznaka"
+msgstr "Nastavi prilagojeno časovno obdobje"
 
 msgid "Is dimension"
 msgstr "Dimenzija"
@@ -6208,8 +6192,9 @@ msgstr "Označi vsebino"
 msgid "Label Line"
 msgstr "Črta oznake"
 
+#, fuzzy
 msgid "Label Template"
-msgstr "Predloga oznake"
+msgstr "Izbriši predlogo"
 
 msgid "Label Type"
 msgstr "Tip oznake"
@@ -6218,7 +6203,7 @@ msgid "Label already exists"
 msgstr "Oznaka že obstaja"
 
 msgid "Label for the index column. Don't use an existing column name."
-msgstr "Oznaka za indeksni stolpec. Ne smete uporabiti obstoječega imena stolpca."
+msgstr ""
 
 msgid "Label for your query"
 msgstr "Ime vaše poizvedbe"
@@ -6268,26 +6253,31 @@ msgstr "Zadnja posodobitev %s, %s"
 msgid "Last available value seen on %s"
 msgstr "Zadnja razpoložljiva vrednost na %s"
 
+#, fuzzy
 msgid "Last day"
-msgstr "Zadnji dan"
+msgstr "zadnji dan"
 
 msgid "Last modified"
 msgstr "Zadnja sprememba"
 
+#, fuzzy
 msgid "Last month"
-msgstr "Zadnji mesec"
+msgstr "zadnji mesec"
 
+#, fuzzy
 msgid "Last quarter"
-msgstr "Zadnje četrletje"
+msgstr "zadnje četrletje"
 
 msgid "Last run"
 msgstr "Zadnji zagon"
 
+#, fuzzy
 msgid "Last week"
-msgstr "Zadnji teden"
+msgstr "zadnji teden"
 
+#, fuzzy
 msgid "Last year"
-msgstr "Zadnje leto"
+msgstr "zadnje leto"
 
 msgid "Latitude"
 msgstr "Širina"
@@ -6350,6 +6340,7 @@ msgstr "Orientacija legende"
 msgid "Legend Position"
 msgstr "Položaj legende"
 
+#, fuzzy
 msgid "Legend Type"
 msgstr "Tip legende"
 
@@ -6409,8 +6400,8 @@ msgid ""
 "Limits the number of the rows that are computed in the query that is the "
 "source of the data used for this chart."
 msgstr ""
-"Omeji število vrnjenih vrstic poizvedbe, ki je vir podatkov za ta "
-"grafikon."
+"Omeji število vrstic, ki se izračunajo v poizvedbi, ki je vir podatkov za"
+" ta grafikon."
 
 msgid "Line"
 msgstr "Črta"
@@ -6427,8 +6418,8 @@ msgid ""
 "data points connected by straight line segments. It is a basic type of "
 "chart common in many fields."
 msgstr ""
-"Črtni grafikon se uporablja za vizualizacijo meritev zajetih skozi čas. "
-"Posamezne točke so med seboj povezane z ravnimi črtami."
+"Črtni grafikon se uporablja se za vizualizacijo meritev zajetih skozi "
+"čas. Posamezne točke so med seboj povezane z ravnimi črtami."
 
 msgid "Line interpolation as defined by d3.js"
 msgstr "Interpolacija krivulje na osnovi d3.js"
@@ -6466,8 +6457,9 @@ msgstr "Seznam dodatnih stolpcev, ki bodo na razpolago v JavaScript funkcijah"
 msgid "List of n+1 values for bucketing metric into n buckets."
 msgstr "Seznam n+1 vrednosti za mero razvrščanja v n razdelkov."
 
+#, fuzzy
 msgid "List of the column names that should be read"
-msgstr "Seznam imen stolpcev, ki bodo prebrani"
+msgstr "Json seznam imen stolpcev, ki bodo prebrani"
 
 msgid "List of values to mark with lines"
 msgstr "Seznam vrednosti, ki bodo markirane s črticami"
@@ -6565,14 +6557,16 @@ msgstr "MAR"
 msgid "MAY"
 msgstr "MAJ"
 
+#, fuzzy
 msgid "MINUTE"
-msgstr "MINUTA"
+msgstr "minuta"
 
 msgid "MON"
 msgstr "PON"
 
+#, fuzzy
 msgid "Main"
-msgstr "Glavni"
+msgstr "min"
 
 msgid "Main Datetime Column"
 msgstr "Glavni stolpec Datum-Čas"
@@ -6585,7 +6579,7 @@ msgstr ""
 "vsebuje podatke za izbrano časovno obdobje"
 
 msgid "Make the x-axis categorical"
-msgstr "Ustvarite kategorično x-os"
+msgstr ""
 
 msgid ""
 "Malformed request. slice_id or table_name and db_name arguments are "
@@ -6595,7 +6589,7 @@ msgstr ""
 "db_name"
 
 msgid "Manage"
-msgstr "Upravljanje"
+msgstr "Upravljaj"
 
 msgid "Manage email report"
 msgstr "Upravljaj e-poštno poročilo"
@@ -6654,9 +6648,6 @@ msgstr "Markerji"
 msgid "Markup type"
 msgstr "Tip označevanja"
 
-msgid "Match time shift color with original series"
-msgstr "Uskladi barvo časovnega premika z izvorno serijo"
-
 msgid "Max"
 msgstr "Max"
 
@@ -6750,7 +6741,7 @@ msgstr "Ključ mere"
 
 #, python-format
 msgid "Metric ``%(metric_name)s`` not found in %(dataset_name)s."
-msgstr "Mera ``%(metric_name)s`` ni najdena v %(dataset_name)s."
+msgstr ""
 
 msgid "Metric ascending"
 msgstr "Naraščajoča mera"
@@ -6900,8 +6891,9 @@ msgstr "Minuta"
 msgid "Minutes %s"
 msgstr "Minute %s"
 
+#, fuzzy
 msgid "Minutes value"
-msgstr "Vrednost minut"
+msgstr "Minimalna vrednost"
 
 msgid "Missing URL parameters"
 msgstr "Manjkajo parametri URL-ja"
@@ -6947,7 +6939,7 @@ msgid "More filters"
 msgstr "Več filtrov"
 
 msgid "MotherDuck token"
-msgstr "Žeton za MotherDuck"
+msgstr ""
 
 msgid "Move only"
 msgstr "Samo premikanje"
@@ -7034,14 +7026,15 @@ msgstr "Zahtevano je ime"
 msgid "Name must be unique"
 msgstr "Ime mora biti unikatno"
 
+#, fuzzy
 msgid "Name of table to be created"
-msgstr "Ime tabele, ki bo ustvarjena"
+msgstr "Ime tabele, ki bo ustvarjena iz CSV podatkov"
 
 msgid "Name of the column containing the id of the parent node"
 msgstr "Ime stolpca, ki vsebuje id nadrejenega vozlišča"
 
 msgid "Name of the id column"
-msgstr "Ime id-stolpca"
+msgstr "Naziv id-stolpca"
 
 msgid "Name of the source nodes"
 msgstr "Imena izvornih vozlišč"
@@ -7098,11 +7091,12 @@ msgstr "Nov zavihek (Ctrl + t)"
 msgid "Next"
 msgstr "Naslednji"
 
+#, fuzzy
 msgid "Nightingale"
-msgstr "Nightingale"
+msgstr "Nightingale Rose grafikon"
 
 msgid "Nightingale Rose Chart"
-msgstr "Polarni tortni grafikon"
+msgstr "Nightingale Rose grafikon"
 
 msgid "No"
 msgstr "Ne"
@@ -7147,8 +7141,9 @@ msgstr "Ni še grafikonov"
 msgid "No columns found"
 msgstr "Ni najdenih stolpcev"
 
+#, fuzzy
 msgid "No compatible catalog found"
-msgstr "Ni najdenega kompatibilnega kataloga"
+msgstr "Ni najdenih skladnih podatkovnih setov"
 
 msgid "No compatible columns found"
 msgstr "Ni najdenih skladnih stolpcev"
@@ -7253,8 +7248,8 @@ msgstr "Rezultatov še ni shranjenih, ponovno morate zagnati poizvedbo"
 
 msgid "No such column found. To filter on a metric, try the Custom SQL tab."
 msgstr ""
-"Tak stolpec ni najden. Za filtriranje po meri uporabite zavihek za SQL-"
-"izraz."
+"Tak stolpec ni najden. Za filtriranje po meri uporabite prilagojen SQL "
+"zavihek."
 
 msgid "No table columns"
 msgstr "Ni stolpcev tabel"
@@ -7268,13 +7263,11 @@ msgstr "Ni časovnih stolpcev"
 msgid "No validator found (configured for the engine)"
 msgstr "Potrjevalnik ni najden (nastavljen za podatkovno bazo)"
 
-#, python-format
+#, fuzzy, python-format
 msgid ""
 "No validator named %(validator_name)s found (configured for the "
 "%(engine_spec)s engine)"
-msgstr ""
-"Potrjevalnik z nazivom %(validator_name)s ni najden (nastavljen za "
-"podatkovno bazo %(engine_spec)s)"
+msgstr "Potrjevalnik ni najden (nastavljen za podatkovno bazo)"
 
 msgid "Node label position"
 msgstr "Položaj oznake vozlišča"
@@ -7297,8 +7290,9 @@ msgstr "Brez -> Brez"
 msgid "Normal"
 msgstr "Normalno"
 
+#, fuzzy
 msgid "Normalize"
-msgstr "Normiraj"
+msgstr "Normiran"
 
 msgid "Normalize Across"
 msgstr "Normiraj glede na"
@@ -7312,14 +7306,15 @@ msgstr "Normiran"
 msgid "Not Time Series"
 msgstr "Ni časovna vrsta"
 
+#, fuzzy
 msgid "Not a valid ZIP file"
-msgstr "Ni veljavna ZIP-datoteka"
+msgstr "Ni uporabljenih filtrov"
 
 msgid "Not added to any dashboard"
 msgstr "Ni dodano na nobeno nadzorno ploščo"
 
 msgid "Not all required fields are complete. Please provide the following:"
-msgstr "Vsa zahtevana polja niso izpolnjena. Dopolnite naslednje:"
+msgstr ""
 
 msgid "Not available"
 msgstr "Ni razpoložljivo"
@@ -7345,6 +7340,7 @@ msgstr "Ni posodobljeno"
 msgid "Nothing triggered"
 msgstr "Ni ni sproženo"
 
+#, fuzzy
 msgid "Notification Method"
 msgstr "Način obveščanja"
 
@@ -7375,9 +7371,10 @@ msgid ""
 "blue,\n"
 "               you can enter either only min or max."
 msgstr ""
-"Številske meje za kodiranje barv od rdeče do modre. Zamenjajte števili za"
-" barve od modre do rdeče. Če želite čisto rdečo ali modro, vnesite samo "
-"min ali max."
+"Številske meje za kodiranje barv od rdeče do modre.\n"
+"\tZamenjajte števili za barve od modre do rdeče. Če želite čisto rdečo "
+"ali modro,\n"
+"\tvnesite samo min ali max."
 
 msgid "Number format"
 msgstr "Oblika zapisa števila"
@@ -7404,16 +7401,13 @@ msgid ""
 "Number of periods to compare against. You can use negative numbers to "
 "compare from the beginning of the time range."
 msgstr ""
-"Število period za primerjavo. Uporabite lahko negativna števila za "
-"primerjavo od začetka časovnega obsega."
 
 msgid "Number of periods to ratio against"
 msgstr "Število časovnih obdobij za izračun deleža"
 
+#, fuzzy
 msgid "Number of rows of file to read. Leave empty (default) to read all rows"
-msgstr ""
-"Število vrstic v datoteki za branje. Pustite prazno (privzeto) za branje "
-"vseh vrstic"
+msgstr "Število vrstic v datoteki za branje."
 
 msgid "Number of rows to skip at start of file."
 msgstr "Število vrstic, ki se izpustijo na začetku datoteke."
@@ -7427,8 +7421,9 @@ msgstr "Število korakov med oznakami pri prikazu X-osi"
 msgid "Number of steps to take between ticks when displaying the Y scale"
 msgstr "Število korakov med oznakami pri prikazu Y-osi"
 
+#, fuzzy
 msgid "Numeric column used to calculate the histogram."
-msgstr "Numerični stolpec za izračun histograma."
+msgstr "Izberite numerične stolpce za izris histograma"
 
 msgid "Numerical range"
 msgstr "Številski obseg"
@@ -7454,8 +7449,9 @@ msgstr "Odmik"
 msgid "On Grace"
 msgstr "V mirovanju"
 
+#, fuzzy
 msgid "On dashboards"
-msgstr "Na nadzornih ploščah"
+msgstr "nadzorne plošče"
 
 msgid ""
 "One or many columns to group by. High cardinality groupings should "
@@ -7479,9 +7475,6 @@ msgstr "En ali več kontrolnikov za stolpčno vrtenje"
 msgid "One or many metrics to display"
 msgstr "Ena ali več mer za prikaz"
 
-msgid "One or more annotation layers failed loading."
-msgstr "En ali več slojev z oznakami se ni naložil."
-
 msgid "One or more columns already exist"
 msgstr "En ali več stolpcev že obstaja"
 
@@ -7509,6 +7502,12 @@ msgstr "En ali več parametrov v SQL-poizvedbi ima napačno obliko."
 msgid "One or more parameters specified in the query are missing."
 msgstr "En ali več parametrov v SQL-poizvedbi manjka."
 
+msgid "One ore more annotation layers failed loading."
+msgstr "Eden ali več slojev z oznakami se ni naložil."
+
+msgid "Only SELECT statements are allowed against this database."
+msgstr "Za to podatkovno bazo so dovoljeni le `SELECT` stavki."
+
 msgid "Only Total"
 msgstr "Samo vsota"
 
@@ -7586,8 +7585,8 @@ msgid ""
 "Optional CA_BUNDLE contents to validate HTTPS requests. Only available on"
 " certain database engines."
 msgstr ""
-"Izbirna CA_BUNDLE vsebina, za potrjevanje HTTPS zahtev. Uporablja se samo"
-" pri določenih podatkovnih bazah."
+"Opcijska CA_BUNDLE vsebina, za potrjevanje HTTPS zahtev. Razpoložljivo le"
+" na določenih sistemih podatkovnih baz."
 
 msgid "Optional d3 date format string"
 msgstr "Opcijski niz za d3-oblikovanje datuma"
@@ -7616,16 +7615,6 @@ msgstr "Razvrsti rezultate glede na izbrani stolpec"
 msgid "Ordering"
 msgstr "Razvrščanje"
 
-msgid ""
-"Orders the query result that generates the source data for this chart. If"
-" a series or row limit is reached, this determines what data are "
-"truncated. If undefined, defaults to the first metric (where "
-"appropriate)."
-msgstr ""
-"Razvrsti rezultat poizvedbe, ki je vir podatkov za ta grafikon. Če je "
-"dosežena omejitev vrstic, bo vplivali na to, kateri podatki so zavrženi. "
-"Če ni definirano, je privzeta prva mera (kjer je na razpolago)."
-
 msgid "Orientation"
 msgstr "Orientacija"
 
@@ -7653,8 +7642,9 @@ msgstr "Pravokotna"
 msgid "Other"
 msgstr "Ostalo"
 
+#, fuzzy
 msgid "Other color palettes"
-msgstr "Druge barvne palete"
+msgstr "Število barvnih korakov"
 
 msgid "Outdoors"
 msgstr "Outdoors"
@@ -7663,7 +7653,7 @@ msgid "Outer Radius"
 msgstr "Zunanji polmer"
 
 msgid "Outer edge of Pie chart"
-msgstr "Zunanji polmer tortnega grafikona"
+msgstr "Veljavno samo"
 
 msgid "Overlap"
 msgstr "Prekrivanje"
@@ -7686,6 +7676,7 @@ msgstr ""
 "se relativne časovne razlike v naravnem (angleškem) jeziku (npr. 24 "
 "hours, 7 days, 52 weeks, 365 days). Prosto besedilo je podprto."
 
+#, fuzzy
 msgid ""
 "Overlay results from a relative time period. Expects relative time deltas"
 " in natural language (example:  24 hours, 7 days, 52 weeks, 365 days). "
@@ -7693,12 +7684,9 @@ msgid ""
 "the comparison time range by the same length as your time range and use "
 "\"Custom\" to set a custom comparison range."
 msgstr ""
-"Zamaknite rezultate za relativno časovno obdobje. Vnaša se relativne "
-"časovne razlike v naravnem (angleškem) jeziku (npr. 24 hours, 7 days, 52 "
-"weeks, 365 days). Prosto besedilo je podprto. Uporabite \"Prevzemi "
-"obdobje iz časovnega filtra\", da časovno obdobje za primerjavo "
-"premaknete za enako dolžino kot vaše časovno obdobje in uporabite "
-"\"Prilagojeno\" za poljubno obdobje za primerjavo."
+"Zamaknite eno ali več časovnih vrst za relativno časovno obdobje. Vnaša "
+"se relativne časovne razlike v naravnem (angleškem) jeziku (npr. 24 "
+"hours, 7 days, 52 weeks, 365 days). Prosto besedilo je podprto."
 
 msgid ""
 "Overlays a hexagonal grid on a map, and aggregates data within the "
@@ -7781,9 +7769,9 @@ msgstr "Parametri povezani s pogledom in perspektivo zemljevida"
 msgid "Parent"
 msgstr "Nadrejeni"
 
-#, python-format
+#, fuzzy, python-format
 msgid "Parsing error: %(error)s"
-msgstr "Napaka obdelave: %(error)s"
+msgstr "Napaka: %(error)s"
 
 msgid "Part of a Whole"
 msgstr "Del celote"
@@ -7819,8 +7807,9 @@ msgstr "Sem prilepite vsebino json-datoteke servisnega računa"
 msgid "Paste the shareable Google Sheet URL here"
 msgstr "Prilepite deljeni URL Googlove preglednice sem"
 
+#, fuzzy
 msgid "Paste your access token here"
-msgstr "Sem prilepite žeton za dostop"
+msgstr "Vstavite svojo kodo sem"
 
 msgid "Pattern"
 msgstr "Vzorec"
@@ -7828,8 +7817,9 @@ msgstr "Vzorec"
 msgid "Percent Change"
 msgstr "Procentualna sprememba"
 
+#, fuzzy
 msgid "Percent Difference format"
-msgstr "Oblika za procentualno razliko"
+msgstr "Vsili obliko zapisa datuma"
 
 msgid "Percent of total"
 msgstr "Procent celote"
@@ -7841,7 +7831,7 @@ msgid "Percentage change"
 msgstr "Procentualna sprememba"
 
 msgid "Percentage difference between the time periods"
-msgstr "Procentualna razlika med časovnimi obdobji"
+msgstr ""
 
 msgid "Percentage metrics"
 msgstr "Procentualne mere"
@@ -7932,7 +7922,7 @@ msgid "Pie shape"
 msgstr "Oblika torte"
 
 msgid "Piecewise"
-msgstr "Odsekovno"
+msgstr ""
 
 msgid "Pin"
 msgstr "Žebljiček"
@@ -7990,7 +7980,7 @@ msgid ""
 "they match across your SQL query and Set Parameters. Then, try running "
 "your query again."
 msgstr ""
-"Preverite, če imajo Jinja parametri sintaktične napake in poskrbite, da "
+"Preverite, če imajo jinja parametri sintaktične napake in poskrbite, da "
 "se ujemajo znotraj SQL-poizvedbe. Potem ponovno poženite poizvedbo."
 
 msgid "Please choose at least one groupby"
@@ -8006,7 +7996,7 @@ msgid "Please enter a SQLAlchemy URI to test"
 msgstr "Vnesite SQLAlchemy URI za test"
 
 msgid "Please enter valid text. Spaces alone are not permitted."
-msgstr "Vnesite veljaven zapis. Samo presledki niso dovoljeni."
+msgstr ""
 
 msgid "Please re-enter the password."
 msgstr "Ponovno vpišite geslo."
@@ -8014,12 +8004,13 @@ msgstr "Ponovno vpišite geslo."
 msgid "Please re-export your file and try importing again"
 msgstr "Ponovno izvozite datoteko in jo nato uvozite"
 
+#, fuzzy
 msgid "Please reach out to the Chart Owner for assistance."
 msgid_plural "Please reach out to the Chart Owners for assistance."
 msgstr[0] "Za pomoč se obrnite na lastnika grafikona."
-msgstr[1] "Za pomoč se obrnite na lastnika grafikona."
-msgstr[2] "Za pomoč se obrnite na lastnike grafikona."
-msgstr[3] "Za pomoč se obrnite na lastnike grafikona."
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
 
 msgid "Please save the query to enable sharing"
 msgstr "Shranite poizvedbo za deljenje"
@@ -8041,7 +8032,7 @@ msgstr "Za nadaljevanje izberite podatkovni set in tip grafikona"
 msgid ""
 "Please specify the Dataset ID for the ``%(name)s`` metric in the Jinja "
 "macro."
-msgstr "Podajte ID podatkovnega seta za mero ``%(name)s`` v Jinja kodi."
+msgstr ""
 
 msgid "Please use 3 different metric labels"
 msgstr "Uporabite 3 različne nazive mer"
@@ -8167,8 +8158,9 @@ msgstr "Predpona ali pripona"
 msgid "Preview"
 msgstr "Predogled"
 
+#, fuzzy
 msgid "Preview uploaded file"
-msgstr "Predogled naložene datoteke"
+msgstr "Naloži Excel-ovo datoteko"
 
 #, python-format
 msgid "Preview: `%s`"
@@ -8199,7 +8191,7 @@ msgid "Primary y-axis format"
 msgstr "Oblika primarne y-osi"
 
 msgid "Private Channels (Bot in channel)"
-msgstr "Zasebni kanali (Bot na kanalu)"
+msgstr ""
 
 msgid "Private Key"
 msgstr "Privatni ključ"
@@ -8241,10 +8233,11 @@ msgid "Put labels outside"
 msgstr "Postavi oznake zunaj"
 
 msgid "Put positive values and valid minute and second value less than 60"
-msgstr "Podajte pozitivne vrednosti in veljavne minute ter sekunde manjše od 60"
+msgstr ""
 
+#, fuzzy
 msgid "Put some positive value greater than 0"
-msgstr "Podajte pozitivno vrednost večjo od 0"
+msgstr "Vrednost mora biti večja od 0"
 
 msgid "Put the labels outside of the pie?"
 msgstr "Postavim oznake zunaj torte?"
@@ -8259,7 +8252,7 @@ msgid "Python datetime string pattern"
 msgstr "Pythonov format zapisa datum-časa"
 
 msgid "QUERY DATA IN SQL LAB"
-msgstr "POIZVEDBA V SQL LABORATORIJU"
+msgstr "POIZVEDBA V SQL-LABORATORIJU"
 
 msgid "Quarter"
 msgstr "Četrtletje"
@@ -8341,8 +8334,9 @@ msgstr "Prikaz radarja okrogle oblike."
 msgid "Radial"
 msgstr "Radialna"
 
+#, fuzzy
 msgid "Radius"
-msgstr "Radij"
+msgstr "Zaobljenost celice"
 
 msgid "Radius in kilometers"
 msgstr "Polmer v kilometrih"
@@ -8415,10 +8409,10 @@ msgid "Rectangle"
 msgstr "Pravokotnik"
 
 msgid "Recurring (every)"
-msgstr "Ponavljajoče (vsak)"
+msgstr ""
 
 msgid "Red for increase, green for decrease"
-msgstr "Rdeča za povečanje, zelena za zmanjšanje"
+msgstr ""
 
 msgid "Redirects to this endpoint when clicking on the table from the table list"
 msgstr "Preusmeri v to končno točko, ko kliknete na tabelo v seznamu tabel"
@@ -8463,9 +8457,6 @@ msgstr "Interval osveževanja"
 msgid "Refresh interval saved"
 msgstr "Interval osveževanja shranjen"
 
-msgid "Refresh table schema"
-msgstr "Osveži shemo tabele"
-
 msgid "Refresh the default values"
 msgstr "Osveži privzete vrednosti"
 
@@ -8531,13 +8522,13 @@ msgid "Rename tab"
 msgstr "Preimenuj zavihek"
 
 msgid "Render HTML"
-msgstr "Izvedi HTML"
+msgstr ""
 
 msgid "Render columns in HTML format"
-msgstr "Izvede HTML v stolpcih"
+msgstr ""
 
 msgid "Render data in HTML format if applicable."
-msgstr "Izvede HTML v podatkih, če je to izvedljivo."
+msgstr ""
 
 msgid "Rendering"
 msgstr "Izris"
@@ -8568,8 +8559,9 @@ msgstr ""
 "Izvajanje urnika poročanja je bilo neuspešno pri ustvarjanju podatkovnega"
 " okvira."
 
+#, fuzzy
 msgid "Report Schedule execution failed when generating a pdf."
-msgstr "Izvajanje urnika poročanja je bilo neuspešno pri ustvarjanju pdf."
+msgstr "Izvajanje urnika poročanja je bilo neuspešno pri ustvarjanju csv."
 
 msgid "Report Schedule execution failed when generating a screenshot."
 msgstr ""
@@ -8600,14 +8592,16 @@ msgstr "Stanje urnika poročanj ni najdeno"
 msgid "Report a bug"
 msgstr "Sporočite napako"
 
+#, fuzzy
 msgid "Report contents"
-msgstr "Vsebina poročila"
+msgstr "Poročilo poslano"
 
 msgid "Report failed"
 msgstr "Poročilo ni uspelo"
 
+#, fuzzy
 msgid "Report is active"
-msgstr "Poročilo je aktivno"
+msgstr "E-poštna poročila aktivna"
 
 msgid "Report name"
 msgstr "Naslov poročila"
@@ -8661,8 +8655,9 @@ msgstr "Zahtevane kontrolne vrednosti so bile odstranjene"
 msgid "Resample"
 msgstr "Prevzorči"
 
+#, fuzzy
 msgid "Resample method should be in "
-msgstr "Metoda za prevzorčenje v mora biti v "
+msgstr "Metoda za prevzorčenje v Pandas mora "
 
 msgid "Resample operation requires DatetimeIndex"
 msgstr "Prevzorčevalna operacija zahteva indeks tipa datumčas"
@@ -8697,9 +8692,6 @@ msgstr ""
 "Zaledni sistem za rezultate, potreben za asinhrone poizvedbe, ni "
 "konfiguriran."
 
-msgid "Retry fetching results"
-msgstr "Ponovno pridobi rezultate"
-
 msgid "Return to specific datetime."
 msgstr "Vrne datum-čas."
 
@@ -8780,8 +8772,9 @@ msgstr "Korenski certifikat"
 msgid "Root node id"
 msgstr "Id korenskega vozlišča"
 
+#, fuzzy
 msgid "Rose Type"
-msgstr "Tip tortnega grafikona"
+msgstr "Stopnični tip"
 
 msgid "Rotate x axis label"
 msgstr "Zavrti oznako x-osi"
@@ -8801,12 +8794,13 @@ msgstr "Vrstica"
 msgid "Row Level Security"
 msgstr "Varnost na nivoju vrstic"
 
+#, fuzzy
 msgid ""
 "Row containing the headers to use as column names (0 is first line of "
 "data)."
 msgstr ""
-"Vrstica z glavo, ki se uporabi za imena stolpcev (0 je prva vrstica "
-"podatkov)."
+"Vrstica z naslovi, ki se uporabi za imena stolpcev (0 je prva vrstica "
+"podatkov). Pustite prazno, če ni naslovne vrstice"
 
 msgid "Row limit"
 msgstr "Omejitev števila vrstic"
@@ -8823,6 +8817,7 @@ msgstr "Položaj delnih vsot vrstic"
 msgid "Rows to Read"
 msgstr "Vrstice za branje"
 
+#, fuzzy
 msgid "Rows to read"
 msgstr "Vrstice za branje"
 
@@ -8872,8 +8867,9 @@ msgstr "Poganjanje izraza %(statement_num)s od %(statement_count)s"
 msgid "SAT"
 msgstr "SOB"
 
+#, fuzzy
 msgid "SECOND"
-msgstr "SEKUNDA"
+msgstr "Sekunda"
 
 msgid "SEP"
 msgstr "SEP"
@@ -8888,7 +8884,7 @@ msgid "SQL Copied!"
 msgstr "SQL kopiran!"
 
 msgid "SQL Expression"
-msgstr "SQL-izraz"
+msgstr "SQL izraz"
 
 msgid "SQL Lab"
 msgstr "SQL laboratorij"
@@ -8909,8 +8905,7 @@ msgstr ""
 "SQL laboratorij uporablja lokalno shrambo brskalnika za shranjevanje "
 "poizvedb in rezultatov.\n"
 "Trenutno uporabljate %(currentUsage)s KB od %(maxStorage)d KB prostora.\n"
-"Da preprečite sesutje SQL laboratorija, izbrišite nekaj zavihkov s "
-"poizvedbami.\n"
+"Da preprečite sesutje SQL laba, izbrišite nekaj zavihkov s poizvedbami.\n"
 "Poizvedbe lahko ponovno pridobite, če pred brisanjem uporabite funkcijo "
 "Shrani.\n"
 "Pred tem morate zapreti druga okna SQL laboratorija."
@@ -8958,7 +8953,7 @@ msgid "SSH Tunneling is not enabled"
 msgstr "SSH-tunel ni omogočen"
 
 msgid "SSL Mode \"require\" will be used."
-msgstr "Uporabljen bo SSL-način \"REQUIRED\"."
+msgstr "Uporabljen bo SSL-način tipa \"require\"."
 
 msgid "START (INCLUSIVE)"
 msgstr "ZAČETEK (VKLJUČEN)"
@@ -8991,11 +8986,13 @@ msgstr "Vzorcev za podatkovni vir ni bilo mogoče pridobiti."
 msgid "Sankey"
 msgstr "Sankey"
 
+#, fuzzy
 msgid "Sankey Chart"
-msgstr "Sankey grafikon"
+msgstr "Shrani grafikon"
 
+#, fuzzy
 msgid "Sankey Diagram (legacy)"
-msgstr "Sankey grafikon (zastarelo)"
+msgstr "Sankey grafikon"
 
 msgid "Sankey Diagram with Loops"
 msgstr "Sankey grafikon z zankami"
@@ -9126,8 +9123,9 @@ msgstr "Urnik poizvedb"
 msgid "Schedule the query periodically"
 msgstr "Periodično zaganjaj poizvedbo"
 
+#, fuzzy
 msgid "Schedule type"
-msgstr "Vrsta urnika"
+msgstr "Urnik poizvedb"
 
 msgid "Scheduled"
 msgstr "V urniku"
@@ -9174,9 +9172,9 @@ msgstr "Pomaknite se do dna, da omogočite prepis sprememb. "
 msgid "Search"
 msgstr "Iskanje"
 
-#, python-format
+#, fuzzy, python-format
 msgid "Search %s records"
-msgstr "Išči %s zapisov"
+msgstr "Surovi podatki"
 
 msgid "Search / Filter"
 msgstr "Iskanje / Filter"
@@ -9191,7 +9189,7 @@ msgid "Search box"
 msgstr "Iskalno polje"
 
 msgid "Search by query text"
-msgstr "Išči po vsebini poizvedbe"
+msgstr "Išči z besedilom poizvedbe"
 
 msgid "Search columns"
 msgstr "Iskanje stolpcev"
@@ -9227,8 +9225,9 @@ msgstr "Naslov sekundarne y-osi"
 msgid "Seconds %s"
 msgstr "Sekunde %s"
 
+#, fuzzy
 msgid "Seconds value"
-msgstr "Število sekund"
+msgstr "sekunde"
 
 msgid "Secure Extra"
 msgstr "Dodatna varnost"
@@ -9276,8 +9275,9 @@ msgstr "Izberite stolpec"
 msgid "Select a dashboard"
 msgstr "Izberite nadzorno ploščo"
 
+#, fuzzy
 msgid "Select a database"
-msgstr "Izberite podatkovno bazo"
+msgstr "Izbriši podatkovno bazo"
 
 msgid "Select a database table and create dataset"
 msgstr "Izberite tabelo podatkovne baze in ustvarite podatkovni set"
@@ -9297,6 +9297,7 @@ msgstr "Izberite podatkovno bazo za poizvedbo"
 msgid "Select a dataset"
 msgstr "Izberite podatkovni set"
 
+#, fuzzy
 msgid "Select a delimiter for this data"
 msgstr "Vnesite ločilnik za te podatke"
 
@@ -9313,17 +9314,16 @@ msgstr ""
 "Izberite mero za prikaz. Uporabite lahko agregacijsko funkcijo na stolpcu"
 " ali napišete poljuben SQL-izraz za mero."
 
+#, fuzzy
 msgid "Select a schema"
 msgstr "Izberite shemo"
 
 msgid "Select a schema if the database supports this"
 msgstr "Izberite shemo (če vrsta podatkovne baze to podpira)"
 
+#, fuzzy
 msgid "Select a sheet name from the uploaded file"
-msgstr "Izberite ime zvezka iz naložene datoteke"
-
-msgid "Select a tab"
-msgstr "Izberite zavihek"
+msgstr "Izberite podatkovno bazo za nalaganje datoteke"
 
 msgid ""
 "Select a time grain for the visualization. The grain is the time interval"
@@ -9347,20 +9347,23 @@ msgstr "Izberite vse elemente"
 msgid "Select any columns for metadata inspection"
 msgstr "Izberite poljubne stolpce za pregled metapodatkov"
 
+#, fuzzy
 msgid "Select catalog or type to search catalogs"
-msgstr "Izberite katalog ali poiščite kataloge z vnosom"
+msgstr "Izberite ali vnesite ime tabele"
 
+#, fuzzy
 msgid "Select channels"
-msgstr "Izberitw kanale"
+msgstr "Izberi grafikone"
 
 msgid "Select chart"
-msgstr "Izberite grafikon"
+msgstr "Izberi grafikon"
 
+#, fuzzy
 msgid "Select chart to use"
-msgstr "Izberite grafikon za uporabo"
+msgstr "Izberi grafikone"
 
 msgid "Select charts"
-msgstr "Izberite grafikone"
+msgstr "Izberi grafikone"
 
 msgid "Select color scheme"
 msgstr "Izberite barvno shemo"
@@ -9372,26 +9375,28 @@ msgid ""
 "Select columns that will be displayed in the table. You can multiselect "
 "columns."
 msgstr ""
-"Izberite stolpce, ki bodo prikazani v tabeli. Izberete jih lahko več "
-"hkrati."
 
+#, fuzzy
 msgid "Select content type"
-msgstr "Izberite vrsto vsebine"
+msgstr "Izberite trenutno stran"
 
 msgid "Select current page"
 msgstr "Izberite trenutno stran"
 
+#, fuzzy
 msgid "Select dashboard"
-msgstr "Izberite nadzorno ploščo"
+msgstr "Izberite nadzorne plošče"
 
+#, fuzzy
 msgid "Select dashboard to use"
-msgstr "Izberite nadzorno ploščo za uporabo"
+msgstr "Izberite nadzorne plošče"
 
 msgid "Select dashboards"
 msgstr "Izberite nadzorne plošče"
 
+#, fuzzy
 msgid "Select database"
-msgstr "Izberite podatkovno bazo"
+msgstr "Izbriši podatkovno bazo"
 
 msgid "Select database or type to search databases"
 msgstr "Izberite ali vnesite ime podatkovne baze"
@@ -9401,9 +9406,8 @@ msgid ""
 "Advanced tab to successfully connect the database. Learn what "
 "requirements your databases has "
 msgstr ""
-"Nekatere podatkovne baze za uspešno povezavo zahtevajo izpolnitev "
-"dodatnih polj v zavihku Napredno. O zahtevah za vašo podatkovno bazo se "
-"podučite "
+"Izbira podatkovnih baz za uspešno povezavo zahteva izpolnitev dodatnih "
+"polj v zavihku Napredno. Kaj zahteva vaša podatkovna baza se naučite "
 
 msgid "Select dataset source"
 msgstr "Izberite podatkovni vir"
@@ -9420,8 +9424,9 @@ msgstr "Izberite Vtičnik za filter z uporabo AntD"
 msgid "Select first filter value by default"
 msgstr "Izberi prvo vrednost kot privzeto"
 
+#, fuzzy
 msgid "Select format"
-msgstr "Izberite obliko"
+msgstr "Oblika zapisa vrednosti"
 
 msgid ""
 "Select one or many metrics to display, that will be displayed in the "
@@ -9444,8 +9449,9 @@ msgstr ""
 msgid "Select operator"
 msgstr "Izberite operator"
 
+#, fuzzy
 msgid "Select or type a custom value..."
-msgstr "Izberite ali vnesite poljubno vrednost..."
+msgstr "Izberite ali vnesite vrednost"
 
 msgid "Select or type a value"
 msgstr "Izberite ali vnesite vrednost"
@@ -9454,7 +9460,7 @@ msgid "Select or type currency symbol"
 msgstr "Izberite ali vnesite simbol valute"
 
 msgid "Select or type dataset name"
-msgstr "Izberite ali vnesite ime podatkovnega seta"
+msgstr "Izberite ali vnesite naziv podatkovnega seta"
 
 msgid "Select owners"
 msgstr "Izberite lastnike"
@@ -9474,9 +9480,6 @@ msgstr "Izberite shemo"
 msgid "Select subject"
 msgstr "Izberite zadevo"
 
-msgid "Select tab"
-msgstr "Izberite zavihek"
-
 msgid "Select table or type to search tables"
 msgstr "Izberite ali vnesite ime tabele"
 
@@ -9524,14 +9527,16 @@ msgstr ""
 "Izberite vrednosti v osvetljenih poljih na levi strani kontrolnika in "
 "zaženite poizvedbo z gumbom %s."
 
+#, fuzzy
 msgid "Selecting a database is required"
-msgstr "Izbira podatkovne baze je obvezna"
+msgstr "Izberite podatkovno bazo za poizvedbo"
 
 msgid "Send as CSV"
 msgstr "Pošlji kot CSV"
 
+#, fuzzy
 msgid "Send as PDF"
-msgstr "Pošlji kot PDF"
+msgstr "Pošlji kot PNG"
 
 msgid "Send as PNG"
 msgstr "Pošlji kot PNG"
@@ -9595,22 +9600,21 @@ msgstr "Nastavi shemo filtrov"
 
 msgid "Set header rows and the number of rows to read or skip."
 msgstr ""
-"Izberite vrstice z glavo in število vrstic, ki bodo prebrane ali "
-"izpuščene."
 
 msgid "Set up an email report"
 msgstr "Nastavite e-poštno poročilo"
 
 msgid "Set up basic details, such as name and description."
-msgstr "Nastavite bistvene atribute, kot sta ime in opis."
+msgstr ""
 
 msgid ""
 "Sets the hierarchy levels of the chart. Each level is\n"
 "        represented by one ring with the innermost circle as the top of "
 "the hierarchy."
 msgstr ""
-"Nastavi hierarhične nivoje grafikona. Vsak nivo je predstavljen z enim "
-"obročem, pri čemer je notranji krog na vrhu hierarhije."
+"Nastavi hierarhične nivoje grafikona. Vsak nivo je\n"
+"\tpredstavljen z enim obročem, pri čemer je notranji krog na vrhu "
+"hierarhije."
 
 msgid "Settings"
 msgstr "Nastavitve"
@@ -9633,14 +9637,16 @@ msgstr "Deljene poizvedbe"
 msgid "Shared query fields"
 msgstr "Polja deljenih poizvedb"
 
+#, fuzzy
 msgid "Sheet name"
 msgstr "Ime zvezka"
 
 msgid "Shift + Click to sort by multiple columns"
 msgstr "Shift + klik za razvrščanje po več stolpcih"
 
+#, fuzzy
 msgid "Shift start date"
-msgstr "Zamaknite začetni datum"
+msgstr "Začetni datum"
 
 msgid "Short description must be unique for this layer"
 msgstr "Kratek opis mora biti za ta sloj unikaten"
@@ -9669,9 +9675,9 @@ msgstr ""
 msgid "Show"
 msgstr "Prikaži"
 
-#, python-format
+#, fuzzy, python-format
 msgid "Show %s entries"
-msgstr "Prikaži %s vnosov"
+msgstr "Prikaži mero"
 
 msgid "Show Bubbles"
 msgstr "Prikaži mehurčke"
@@ -9679,6 +9685,7 @@ msgstr "Prikaži mehurčke"
 msgid "Show CREATE VIEW statement"
 msgstr "Prikaži CREATE VIEW stavek"
 
+#, fuzzy
 msgid "Show Cell bars"
 msgstr "Prikaži grafe v celicah"
 
@@ -9828,8 +9835,9 @@ msgstr "Na grafikonu prikaži vrednosti serij"
 msgid "Show split lines"
 msgstr "Prikaži razdelitvene črte"
 
+#, fuzzy
 msgid "Show summary"
-msgstr "Prikaži povzetek"
+msgstr "Prikaži markerje"
 
 msgid "Show the value on top of the bar"
 msgstr "Prikaži vrednosti na vrhu stolpcev"
@@ -9884,7 +9892,7 @@ msgid "Showing %s of %s"
 msgstr "Prikazanih %s od %s"
 
 msgid "Shows a list of all series available at that point in time"
-msgstr "Prikaže vrednosti vseh serij za posamezno časovno točko"
+msgstr "Prikaže seznam vseh razpoložljivih podatkovnih serij za istočasno točko"
 
 msgid "Shows or hides markers for the time series"
 msgstr "Prikaže ali skrije markerje časovne serije"
@@ -9928,6 +9936,7 @@ msgstr "Izpusti vrstice"
 msgid "Skip blank lines rather than interpreting them as Not A Number values"
 msgstr "Raje izpusti prazne vrstice, kot pa da so prepoznane kot NaN vrednosti"
 
+#, fuzzy
 msgid "Skip rows"
 msgstr "Izpusti vrstice"
 
@@ -9963,8 +9972,6 @@ msgid ""
 "Something went wrong with embedded authentication. Check the dev console "
 "for details."
 msgstr ""
-"Nekaj je šlo narobe pri vdelani avtentikaciji. Preverite podrobnosti v "
-"razvojni konzoli."
 
 msgid "Something went wrong."
 msgstr "Nekaj je šlo narobe."
@@ -9991,8 +9998,9 @@ msgstr "Prišlo je do neznane napake."
 msgid "Sorry, something went wrong. Embedding could not be deactivated."
 msgstr "Nekaj je šlo narobe. Vgrajevanja ni mogoče deaktivirati."
 
+#, fuzzy
 msgid "Sorry, something went wrong. Please try again."
-msgstr "Nekaj je šlo narobe. Poskusite ponovno."
+msgstr "Nekaj je šlo narobe. Poskusite ponovno kasneje."
 
 msgid "Sorry, something went wrong. Try again later."
 msgstr "Nekaj je šlo narobe. Poskusite ponovno kasneje."
@@ -10069,9 +10077,6 @@ msgstr "Razvrsti vrednosti filtra"
 msgid "Sort metric"
 msgstr "Mera za razvrščanje"
 
-msgid "Sort query by"
-msgstr "Razvrščanje poizvedbe po"
-
 msgid "Sort rows by"
 msgstr "Razvrsti vrstice"
 
@@ -10103,17 +10108,17 @@ msgid "Specific Date/Time"
 msgstr "Fiksen Datum/Čas"
 
 msgid "Specify name to CREATE TABLE AS schema in: public"
-msgstr "Podajte ime sheme za CREATE TABLE AS: public"
+msgstr "Podajte naziv sheme za CREATE TABLE AS: public"
 
 msgid "Specify name to CREATE VIEW AS schema in: public"
-msgstr "Podajte ime sheme za CREATE VIEW AS: public"
+msgstr "Podajte naziv sheme za CREATE VIEW AS: public"
 
 msgid ""
 "Specify the database version. This is used with Presto for query cost "
 "estimation, and Dremio for syntax changes, among others."
 msgstr ""
 "Podajte verzijo podatkovne baze. Uporablja se s Presto za potrebe "
-"ocenjevanja potratnosti poizvedbe in z Dremio za spremembe sintakse."
+"ocenjevanja potratnosti poizvedbe in z Dremio za sprememba sintakse."
 
 msgid "Split number"
 msgstr "Število razdelitev"
@@ -10316,8 +10321,9 @@ msgstr "Vsota vrednosti v dani periodi"
 msgid "Sum values"
 msgstr "Vsote"
 
+#, fuzzy
 msgid "Summary"
-msgstr "Povzetek"
+msgstr "Nedelja"
 
 msgid "Sunburst Chart"
 msgstr "Večnivojski tortni grafikon"
@@ -10364,12 +10370,6 @@ msgstr ""
 "stopničastimi, črtnimi, raztresenimi in stolpčnimi grafikoni. Grafikon "
 "ima širok nabor prilagoditev."
 
-msgid "Switch to the next tab"
-msgstr "Preklopite na naslednji zavihek"
-
-msgid "Switch to the previous tab"
-msgstr "Preklopite na prejšnji zavihek"
-
 msgid "Symbol"
 msgstr "Simbol"
 
@@ -10394,6 +10394,9 @@ msgstr ""
 msgid "TABLES"
 msgstr "TABELE"
 
+msgid "TEMPORAL X-AXIS"
+msgstr "ČASOVNA X-OS"
+
 msgid "TEMPORAL_RANGE"
 msgstr "ČASOVNI_OBSEG"
 
@@ -10406,9 +10409,9 @@ msgstr "TOR"
 msgid "Tab name"
 msgstr "Naslov zavihka"
 
-#, python-format
+#, fuzzy, python-format
 msgid "Tab schema is invalid, caused by: %(error)s"
-msgstr "Shema zavihkov ni veljavna, zaradi napake: %(error)s"
+msgstr "Shema podatkovnega seta ni veljavna, zaradi napake: %(error)s"
 
 msgid "Tab title"
 msgstr "Naslov zavihka"
@@ -10423,20 +10426,18 @@ msgstr "Tabela %(table)s ni bila najdena v podatkovni bazi %(db)s"
 msgid "Table Name"
 msgstr "Ime tabele"
 
-#, python-format
+#, fuzzy, python-format
 msgid ""
 "Table [%(table)s] could not be found, please double check your database "
 "connection, schema, and table name"
 msgstr ""
-"Tabele [%(table)s] ni mogoče najti. Preverite povezavo, shemo in ime "
-"podatkovne baze"
+"Tabele [%(table_name)s] ni mogoče najti. Preverite povezavo, shemo in ime"
+" podatkovne baze"
 
 msgid ""
 "Table already exists. You can change your 'if table already exists' "
 "strategy to append or replace or provide a different Table Name to use."
 msgstr ""
-"Tabela že obstaja. Spremenite lahko način obravnave primerov, ko tabela "
-"že obstaja."
 
 msgid "Table cache timeout"
 msgstr "Trajanje predpomnilnika tabele"
@@ -10526,8 +10527,9 @@ msgstr "Kategorija cilja"
 msgid "Target value"
 msgstr "Ciljna vrednost"
 
+#, fuzzy
 msgid "Template"
-msgstr "Predloga"
+msgstr "css_template"
 
 msgid "Template parameters"
 msgstr "Parametri predlog"
@@ -10539,17 +10541,14 @@ msgstr ""
 "Vzorčna povezava, vključiti je mogoče {{ metric }} ali drugo vrednost iz "
 "kontrolnikov."
 
-msgid "Temporal X-Axis"
-msgstr "Časovna X-os"
-
 msgid ""
 "Terminate running queries when browser window closed or navigated to "
 "another page. Available for Presto, Hive, MySQL, Postgres and Snowflake "
 "databases."
 msgstr ""
-"Ustavi zagnane poizvedbe, ko se zapre okno brskalnika ali uporabnik "
-"zapusti stran. Na razpolago za Presto, Hive, MySQL, Postgres in Snowflake"
-" podatkovne baze."
+"Ustavi zagnane poizvedbe, ko se zapre okno brskalnika ali uporabnik gre "
+"na drugo stran. Na razpolago za Presto, Hive, MySQL, Postgres in "
+"Snowflake podatkovne baze."
 
 msgid "Test Connection"
 msgstr "Preizkus povezave"
@@ -10561,7 +10560,7 @@ msgid "Text"
 msgstr "Besedilo"
 
 msgid "Text / Markdown"
-msgstr "Besedilo / Markdown"
+msgstr ""
 
 msgid "Text align"
 msgstr "Poravnava besedila"
@@ -10595,6 +10594,9 @@ msgstr ""
 "GeoJsonLayer uporablja podatke v formatu GeoJSON in jih izriše kot "
 "interaktivne poligone, črte in točke (krogi, ikone in/ali besedila)."
 
+msgid "The SQL is invalid and cannot be parsed."
+msgstr ""
+
 msgid ""
 "The Sankey chart visually tracks the movement and transformation of "
 "values across\n"
@@ -10604,13 +10606,6 @@ msgid ""
 "representation of\n"
 "          value distribution and transformation."
 msgstr ""
-"Sankey grafikon vizualizira gibanje in transformacijo vrednosti po "
-"nivojih sistema.\n"
-"          Vozlišča predstavljajo nivoje, povezave med njimi pa "
-"predstavljajo gibanje\n"
-"          vrednosti. Višina vozlišča ustreza predstavljeni meri, in "
-"ponazori porazdelitev\n"
-"          vrednosti in transformacijo."
 
 msgid "The URL is missing the dataset_id or slice_id parameters."
 msgstr "V URL-ju manjkata parametra dataset_id ali slice_id."
@@ -10624,9 +10619,9 @@ msgid ""
 "            time range filters in dashboards. Would you like to add it to"
 " the filters list?"
 msgstr ""
-"X-osi ni na seznamu filtrov, kar preprečuje njeno uporabo v filtrih "
-"časovnega obdobja v nadzorni plošči. Jo želite najprej dodati na seznam "
-"filtrov?"
+"X-osi ni na seznamu filtrov, kar preprečuje njeno uporabo v\n"
+"\tfiltrih časovnega obdobja v nadzorni plošči. Jo želite najprej dodati "
+"na seznam filtrov?"
 
 msgid "The annotation has been saved"
 msgstr "Označba je bila shranjena"
@@ -10685,10 +10680,10 @@ msgid "The column header label"
 msgstr "Naslov stolpca"
 
 msgid "The column to be used as the source of the edge."
-msgstr "Stolpec, ki bo uporabljen kot izvor povezave."
+msgstr ""
 
 msgid "The column to be used as the target of the edge."
-msgstr "Stolpec, ki bo uporabljen kot cilj povezave."
+msgstr ""
 
 msgid "The column was deleted or renamed in the database."
 msgstr "Stolpec je bil izbrisan ali preimenovan v podatkovni bazi."
@@ -10713,8 +10708,16 @@ msgstr ""
 "potrebno ročno vnesti tip za stolpce, ki temeljijo na izrazih. V večini "
 "primerov uporabniku tega ni potrebno spreminjati."
 
-msgid "The database"
-msgstr "Podatkovna baza"
+#, python-format
+msgid ""
+"The database %s is linked to %s charts that appear on %s dashboards and "
+"users have %s SQL Lab tabs using this database open. Are you sure you "
+"want to continue? Deleting the database will break those objects."
+msgstr ""
+"Podatkovna baza %s je povezana z %s grafikoni, ki so prisotni na %s "
+"nadzornih ploščah. Uporabniki imajo odprtih %s zavihkov SQL-laboratorija "
+"s to podatkovno bazo. Ali želite nadaljevati? Izbris podatkovne baze bo "
+"pokvaril te objekte."
 
 msgid "The database columns that contains lines information"
 msgstr "Stolpec v podatkovni bazi, ki vsebuje podatke črt"
@@ -10738,10 +10741,9 @@ msgstr ""
 msgid "The database returned an unexpected error."
 msgstr "Podatkovna baza je vrnila nepričakovano napako."
 
+#, fuzzy
 msgid "The database that was used to generate this query could not be found"
-msgstr ""
-"Podatkovna baza, ki je bila uporabljena za kreiranje te poizvedbe, ni "
-"bila najdena"
+msgstr "Podatkovna baza ni bila najdena"
 
 msgid "The database was deleted."
 msgstr "Podatkovna baza je bila izbrisana."
@@ -10749,8 +10751,15 @@ msgstr "Podatkovna baza je bila izbrisana."
 msgid "The database was not found."
 msgstr "Podatkovna baza ni bila najdena."
 
-msgid "The dataset"
-msgstr "Podatkovni set"
+#, python-format
+msgid ""
+"The dataset %s is linked to %s charts that appear on %s dashboards. Are "
+"you sure you want to continue? Deleting the dataset will break those "
+"objects."
+msgstr ""
+"Podatkovni set %s je povezan z grafikoni %s, ki so prisotni na nadzorni "
+"plošči %s. Ali želite nadaljevati? Izbris podatkovnega seta bo pokvaril "
+"te objekte."
 
 msgid "The dataset associated with this chart no longer exists"
 msgstr "Podatkovni set, povezan s tem grafikonom, ne obstaja več"
@@ -10787,10 +10796,10 @@ msgid "The datasource is too large to query."
 msgstr "Podatkovni vir je prevelik za poizvedbo."
 
 msgid "The default catalog that should be used for the connection."
-msgstr "Privzeti katalog, ki bo uporabljen za to povezavo."
+msgstr ""
 
 msgid "The default schema that should be used for the connection."
-msgstr "Privzeta shema, ki bo uporabljena za to povezavo."
+msgstr ""
 
 msgid ""
 "The description can be displayed as widget headers in the dashboard view."
@@ -10830,10 +10839,6 @@ msgid ""
 "preventing the dashboard\n"
 "                    from rendering: %s"
 msgstr ""
-"Naslednji filtri imajo vklopljeno funkcijo 'Izberi prvo vrednost kot "
-"privzeto'\n"
-"                    in jih ni mogoče naložiti, kar preprečuje izris "
-"nadzorne plošče: %s"
 
 msgid "The function to use when aggregating points into groups"
 msgstr "Funkcija za agregacijo točk v skupine"
@@ -10846,10 +10851,6 @@ msgid ""
 " and provides\n"
 "          insights into its shape, central tendency, and spread."
 msgstr ""
-"Histogram ponazoti porazdelitev podatkov, tako da prikaže\n"
-"          frekvenco oz. število vrednosti znotraj posameznih\n"
-"          obsegov oz. razdelkov. Pomaga pri vizualizaciji, vzorcev,\n"
-"          gruč in osamelcev."
 
 #, python-format
 msgid "The host \"%(hostname)s\" might be down and can't be reached."
@@ -10938,21 +10939,16 @@ msgstr ""
 " period\" na 7. Tako bodo vse prikazane točke skupaj obsegale 7 obdobij. "
 "To bo prikrilo rampo, ki bi trajala prvih 7 obdobij"
 
-msgid ""
-"The minimum value of metrics. It is an optional configuration. If not "
-"set, it will be the minimum value of the data"
-msgstr ""
-"Najmanjša vrednost mer. To je opcijska nastavitev. Če ni nastavljeno, bo "
-"uporabljena najmanjša vrednost med podatki"
-
+#, fuzzy
 msgid "The name of the rule must be unique"
-msgstr "Ime pravila mora biti unikatno"
+msgstr "Ime mora biti unikatno"
 
 msgid "The number color \"steps\""
 msgstr "Število barvnih korakov"
 
+#, fuzzy
 msgid "The number of bins for the histogram"
-msgstr "Število razdelkov za histogram"
+msgstr "Izberite število razdelkov za histogram"
 
 msgid ""
 "The number of hours, negative or positive, to shift the time column. This"
@@ -11177,16 +11173,15 @@ msgstr ""
 msgid "The report has been created"
 msgstr "Poročilo je bilo ustvarjeno"
 
+#, fuzzy
 msgid "The report will be sent to your email at"
-msgstr "Poročilo bo poslano na vaš e-naslov ob"
+msgstr "Zaslonska slika nadzorne plošče bo poslana na vaš e-naslov ob"
 
 msgid ""
 "The result of this query must be a value capable of numeric "
 "interpretation e.g. 1, 1.0, or \"1\" (compatible with Python's float() "
 "function)."
 msgstr ""
-"Rezultat te poizvedbe mora biti številska vrednost, kot je 1, 1.0 ali "
-"\"1\" (kompatibilno s Pythonovo float() funkcijo)."
 
 msgid "The results backend no longer has the data from the query."
 msgstr "Zaledni sistem rezultatov nima več podatkov iz poizvedbe."
@@ -11207,8 +11202,6 @@ msgid ""
 "The row limit set for the chart was reached. The chart may show partial "
 "data."
 msgstr ""
-"Omejitev števila vrstic za grafikon je bila dosežena. Možno je, da "
-"grafikon prikazuje delne podatke."
 
 #, python-format
 msgid ""
@@ -11233,13 +11226,13 @@ msgid "The schema was deleted or renamed in the database."
 msgstr "Shema je bila izbrisana ali preimenovana v podatkovni bazi."
 
 msgid "The screenshot could not be downloaded. Please, try again later."
-msgstr "Zaslonske slike ni mogoče prenesti. Poskusite ponovno kasneje."
+msgstr ""
 
 msgid "The screenshot is being generated. Please, do not leave the page."
-msgstr "Ustvarja se zaslonska slika. Ne zapuščajte strani."
+msgstr ""
 
 msgid "The screenshot is now being downloaded."
-msgstr "Zaslonska slika se prenaša."
+msgstr ""
 
 msgid "The size of each cell in meters"
 msgstr "Velikost vsake celice v metrih"
@@ -11376,9 +11369,9 @@ msgstr "Debelina črt"
 msgid "There are associated alerts or reports"
 msgstr "Prisotna so povezana opozorila in poročila"
 
-#, python-format
+#, fuzzy, python-format
 msgid "There are associated alerts or reports: %(report_names)s"
-msgstr "Prisotna so povezana opozorila in poročila: %(report_names)s"
+msgstr "Prisotna so povezana opozorila in poročila"
 
 msgid "There are no charts added to this dashboard"
 msgstr "V nadzorni plošči ni grafikonov"
@@ -11424,11 +11417,13 @@ msgstr "Pri pridobivanju elementov podatkovnega seta je prišlo do napake"
 msgid "There was an error fetching the favorite status: %s"
 msgstr "Napaka pri pridobivanju statusa \"Priljubljeno\": %s"
 
+#, fuzzy
 msgid "There was an error fetching the filtered charts and dashboards:"
-msgstr "Napaka pri pridobivanju filtriranih grafikonov in nadzornih plošč:"
+msgstr "Napaka pri pridobivanju statusa \"Priljubljeno\": %s"
 
+#, fuzzy
 msgid "There was an error loading the catalogs"
-msgstr "Napaka pri nalaganju katalogov"
+msgstr "Napaka pri nalaganju tabel"
 
 msgid "There was an error loading the chart data"
 msgstr "Napaka pri nalaganju podatkov grafikona"
@@ -11442,9 +11437,6 @@ msgstr "Napaka pri nalaganju shem"
 msgid "There was an error loading the tables"
 msgstr "Napaka pri nalaganju tabel"
 
-msgid "There was an error retrieving dashboard tabs."
-msgstr "Prišlo je do napake pri pridobivanju zavihkov nadzornih plošč."
-
 #, python-format
 msgid "There was an error saving the favorite status: %s"
 msgstr "Napaka pri shranjevanju statusa \"Priljubljeno\": %s"
@@ -11641,8 +11633,8 @@ msgid ""
 "                  view should be passed down to the chart containing the "
 "annotation data."
 msgstr ""
-"Upravlja ali je polje \"time_range\" iz trenutnega pogleda lahko "
-"posredovano grafikonu, ki vsebuje podatke oznak slojev."
+"Upravlja ali je polje \"time_range\" iz trenutnega pogleda lahko\n"
+"\tposredovano grafikonu, ki vsebuje podatke oznak slojev."
 
 msgid ""
 "This controls whether the time grain field from the current\n"
@@ -11700,15 +11692,6 @@ msgstr ""
 msgid "This dashboard was saved successfully."
 msgstr "Nadzorna plošča je bila uspešno shranjena."
 
-msgid ""
-"This database does not allow for DDL/DML, and the query could not be "
-"parsed to confirm it is a read-only query. Please contact your "
-"administrator for more assistance."
-msgstr ""
-"Podatkovna baza ne dovoljuje DDL/DML in poizvedbe ni mogoče prebrati, da "
-"bi potrdili, da je poizvedba samo za branje. Kontaktirajte "
-"administratorja za nadaljnjo podporo."
-
 msgid "This database is managed externally, and can't be edited in Superset"
 msgstr ""
 "Podatkovna baza se upravlja eksterno in je ni mogoče urediti znotraj "
@@ -11765,9 +11748,9 @@ msgid ""
 "the clause `1 = 0` (always false)."
 msgstr ""
 "To je pogoj, ki bo dodan WHERE stavku. Npr., če želite dobiti vrstice za "
-"določeno stranko, lahko definirate navadni filter z izrazom 'id_stranke ="
-" 9'. Če ne želimo prikazati vrstic, razen če uporabnik pripada RLS vlogi,"
-" lahko filter ustvarimo z izrazom `1 = 0` (vedno FALSE)."
+"določeno stranko, lahko definirate regularni filter z izrazom 'id_stranke"
+" = 9'. Če ne želimo prikazati vrstic, razen če uporabnik pripada RLS "
+"vlogi, lahko filter ustvarimo z izrazom `1 = 0` (vedno FALSE)."
 
 msgid ""
 "This json object describes the positioning of the widgets in the "
@@ -11787,16 +11770,24 @@ msgstr "Markdown komponenta ima napako. Povrnite nedavne spremembe."
 msgid "This may be triggered by:"
 msgstr "To je lahko sproženo z/s:"
 
+msgid ""
+"This metric is used to define row selection criteria (how the rows are "
+"sorted) if a series or row limit is present. If not defined, it reverts "
+"to the first metric (where appropriate)."
+msgstr ""
+"Mera, ki določa kako so razvrščene vrstice, če je določena omejitev serij"
+" ali vrstic. Če ni določena, se uporabi prva mera (kjer je ustrezno)."
+
 msgid "This metric might be incompatible with current dataset"
 msgstr "Ta mera je lahko nekompatibilna s trenutnim podatkovnim setom"
 
 msgid "This option has been disabled by the administrator."
-msgstr "To opcijo je onemogočil administrator."
+msgstr ""
 
 msgid ""
 "This page is intended to be embedded in an iframe, but it looks like that"
 " is not the case."
-msgstr "Ta stran naj bi bila vdelana kot iframe, vendar izgleda, da temu ni tako."
+msgstr ""
 
 msgid ""
 "This section allows you to configure how to use the slice\n"
@@ -11846,21 +11837,19 @@ msgstr "Ta tip vizualizacije ni podpira medsebojnih filtrov."
 msgid "This visualization type is not supported."
 msgstr "Ta tip vizualizacije ni podprt."
 
+#, fuzzy
 msgid "This was triggered by:"
 msgid_plural "This may be triggered by:"
-msgstr[0] "To je bilo sproženo z:"
-msgstr[1] "To je bilo sproženo z:"
-msgstr[2] "To je bilo sproženo z:"
-msgstr[3] "To je bilo sproženo z:"
+msgstr[0] "To je bilo sproženo z/s:"
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
 
 msgid ""
 "This will be applied to the whole table. Arrows (↑ and ↓) will be added "
 "to main columns for increase and decrease. Basic conditional formatting "
 "can be overwritten by conditional formatting below."
 msgstr ""
-"To bo uporabljeno za celotno tabelo. Puščici (↑ in ↓) bosta dodani "
-"glavnim stolpcem za povečevanje in zmanjševanje. Osnovno pogojno "
-"oblikovanje, lahko spremenite s pogojnim oblikovanjem spodaj."
 
 msgid "This will remove your current embed configuration."
 msgstr "To bo odstranilo trenutno konfiguracijo za vgrajevanje."
@@ -11921,7 +11910,7 @@ msgid "Time Series - Line Chart"
 msgstr "Časovna vrsta - Črtni grafikon"
 
 msgid "Time Series - Nightingale Rose Chart"
-msgstr "Časovna vrsta - Polarni tortni grafikon"
+msgstr "Časovna vrsta - Nightingale Rose grafikon"
 
 msgid "Time Series - Paired t-test"
 msgstr "Časovna vrsta - t-test za odvisne vzorce"
@@ -12077,7 +12066,7 @@ msgid "Title or Slug"
 msgstr "Naslov ali `Slug`"
 
 msgid "To filter on a metric, use Custom SQL tab."
-msgstr "Za filtriranje po meri uporabite zavihek za SQL-izraz."
+msgstr "Za filtriranje po meri uporabite prilagojen SQL zavihek."
 
 msgid "To get a readable URL for your dashboard"
 msgstr "Za pridobitev berljivega URL-ja za nadzorno ploščo"
@@ -12137,7 +12126,7 @@ msgid "Transpose pivot"
 msgstr "Transponirano vrtenje"
 
 msgid "Treat values as categorical."
-msgstr "Obravnavaj vrednosti kot kategorične."
+msgstr ""
 
 msgid "Tree Chart"
 msgstr "Drevesni grafikon"
@@ -12214,8 +12203,9 @@ msgstr "Tip"
 msgid "Type \"%s\" to confirm"
 msgstr "Vnesite \"%s\" za potrditev"
 
+#, fuzzy
 msgid "Type a number"
-msgstr "Vnesite število"
+msgstr "Vnesite vrednost"
 
 msgid "Type a value"
 msgstr "Vnesite vrednost"
@@ -12248,7 +12238,7 @@ msgid "URL slug"
 msgstr "URL slug"
 
 msgid "Unable to calculate such a date delta"
-msgstr "Časovne razlike ni mogoče izračunati"
+msgstr ""
 
 #, python-format
 msgid "Unable to connect to catalog named \"%(catalog_name)s\"."
@@ -12287,8 +12277,9 @@ msgid ""
 "table."
 msgstr "Stolpcev za izbrano tabelo ni bilo mogoče naložiti. Izberite drugo tabelo."
 
+#, fuzzy
 msgid "Unable to load dashboard"
-msgstr "Nadzorne plošče ni mogoče naložiti"
+msgstr "Dodano na 1 nadzorno ploščo"
 
 msgid ""
 "Unable to migrate query editor state to backend. Superset will retry "
@@ -12336,12 +12327,13 @@ msgstr "Zgodila se je nepričakovana napaka. Podrobnosti preverite v dnevnikih"
 msgid "Unexpected error: "
 msgstr "Nepričakovana napaka: "
 
+#, fuzzy
 msgid "Unexpected no file extension found"
-msgstr "Nepričakovana napaka končnice datoteke"
+msgstr "Shranjeni izrazi niso najdeni"
 
-#, python-format
+#, fuzzy, python-format
 msgid "Unexpected time range: %(error)s"
-msgstr "Nepričakovano časovno obdobje: %(error)s"
+msgstr "Nepodprta časovna granulacija: %(time_grain)s"
 
 msgid "Unknown"
 msgstr "Neznano"
@@ -12354,9 +12346,9 @@ msgstr "Neznan Doris strežnik \"%(hostname)s\"."
 msgid "Unknown MySQL server host \"%(hostname)s\"."
 msgstr "Neznan MySQL strežnik \"%(hostname)s\"."
 
-#, python-format
+#, fuzzy, python-format
 msgid "Unknown OceanBase server host \"%(hostname)s\"."
-msgstr "Neznan OceanBase gostiteljski strežnik \"%(hostname)s\"."
+msgstr "Neznan Doris strežnik \"%(hostname)s\"."
 
 msgid "Unknown Presto Error"
 msgstr "Neznana Presto napaka"
@@ -12435,9 +12427,11 @@ msgstr "Naloži CSV"
 msgid "Upload CSV to database"
 msgstr "Naloži CSV v podatkovno bazo"
 
+#, fuzzy
 msgid "Upload Columnar"
 msgstr "Naloži stolpčno datoteko"
 
+#, fuzzy
 msgid "Upload Columnar file to database"
 msgstr "Naloži stolpčno datoteko v podatkovno bazo"
 
@@ -12447,30 +12441,35 @@ msgstr "Naloži prijavne podatke"
 msgid "Upload Enabled"
 msgstr "Nalaganje omogočeno"
 
+#, fuzzy
 msgid "Upload Excel"
-msgstr "Naloži Excel"
+msgstr "Naloži Excel-ovo datoteko"
 
+#, fuzzy
 msgid "Upload Excel to database"
-msgstr "Naloži Excel v podatkovno bazo"
+msgstr "Naloži Excel-ovo datoteko v podatkovno bazo"
 
 msgid "Upload JSON file"
 msgstr "Naloži JSON datoteko"
 
+#, fuzzy
 msgid "Upload a file to a database."
-msgstr "Naloži datoteko v podatkovno bazo."
+msgstr "Naloži datoteko v podatkovno bazo"
 
 #, python-format
 msgid "Upload a file with a valid extension. Valid: [%s]"
-msgstr "Naložite datoteko z veljavno končnico. Veljavne so: [%s]"
+msgstr ""
 
 msgid "Upload file to database"
 msgstr "Naloži datoteko v podatkovno bazo"
 
+#, fuzzy
 msgid "Upload file to preview columns"
-msgstr "Naloži datoteko za predogled stolpcev"
+msgstr "Naloži datoteko v podatkovno bazo"
 
+#, fuzzy
 msgid "Uploading a file is required"
-msgstr "Zahtevano je nalaganje datoteke"
+msgstr "Zahtevano je ime"
 
 msgid "Upper Threshold"
 msgstr "Zgornji prag"
@@ -12621,8 +12620,9 @@ msgstr "Domena vrednosti"
 msgid "Value Format"
 msgstr "Oblika zapisa vrednosti"
 
+#, fuzzy
 msgid "Value and Percentage"
-msgstr "Vrednost in procent"
+msgstr "Kategorija in procent"
 
 msgid "Value bounds"
 msgstr "Meje vrednosti"
@@ -12632,7 +12632,7 @@ msgid "Value cannot exceed %s"
 msgstr "Vrednost ne sme presegati %s"
 
 msgid "Value difference between the time periods"
-msgstr "Razlika vrednosti med časovnimi obdobji"
+msgstr ""
 
 msgid "Value format"
 msgstr "Oblika zapisa vrednosti"
@@ -12640,8 +12640,9 @@ msgstr "Oblika zapisa vrednosti"
 msgid "Value is required"
 msgstr "Zahtevana je vrednost"
 
+#, fuzzy
 msgid "Value must be 0 or greater"
-msgstr "Vrednost mora biti 0 ali večja"
+msgstr "Vrednost mora biti večja od 0"
 
 msgid "Value must be greater than 0"
 msgstr "Vrednost mora biti večja od 0"
@@ -12704,7 +12705,7 @@ msgid "View query"
 msgstr "Ogled poizvedbe"
 
 msgid "Viewed"
-msgstr "Ogledano"
+msgstr "Ogledane"
 
 #, python-format
 msgid "Viewed %s"
@@ -12736,8 +12737,9 @@ msgstr "Poizvedba na virtualnem podatkovnem setu mora biti samo za branje"
 msgid "Visual Tweaks"
 msgstr "Nastavitve izgleda"
 
+#, fuzzy
 msgid "Visual formatting"
-msgstr "Oblikovanje videza"
+msgstr "Oblikovanje"
 
 msgid "Visualization Type"
 msgstr "Tip vizualizacije"
@@ -12855,12 +12857,13 @@ msgstr "Tip vizualizacije"
 msgid "WED"
 msgstr "SRE"
 
-#, python-format
+#, fuzzy, python-format
 msgid "Waiting on %s"
-msgstr "Čakanje na %s"
+msgstr "Prikazanih %s od %s"
 
+#, fuzzy
 msgid "Waiting on database..."
-msgstr "Čakanje na podatkovno bazo..."
+msgstr "Upravljajte podatkovne baze"
 
 msgid "Want to add a new database?"
 msgstr "Želite dodati novo podatkovno bazo?"
@@ -12970,7 +12973,7 @@ msgstr "Tedni %s"
 msgid "Weight"
 msgstr "Utež"
 
-#, python-format
+#, fuzzy, python-format
 msgid ""
 "We’re having trouble loading these results. Queries are set to timeout "
 "after %s second."
@@ -12981,16 +12984,10 @@ msgstr[0] ""
 "Težava pri nalaganju rezultatov. Časovni iztek poizvedb je nastavljen na "
 "%s sekundo."
 msgstr[1] ""
-"Težava pri nalaganju rezultatov. Časovni iztek poizvedb je nastavljen na "
-"%s sekundi."
 msgstr[2] ""
-"Težava pri nalaganju rezultatov. Časovni iztek poizvedb je nastavljen na "
-"%s sekunde."
 msgstr[3] ""
-"Težava pri nalaganju rezultatov. Časovni iztek poizvedb je nastavljen na "
-"%s sekund."
 
-#, python-format
+#, fuzzy, python-format
 msgid ""
 "We’re having trouble loading this visualization. Queries are set to "
 "timeout after %s second."
@@ -13001,14 +12998,8 @@ msgstr[0] ""
 "Težava pri nalaganju vizualizacije. Časovni iztek poizvedb je nastavljen "
 "na %s sekundo."
 msgstr[1] ""
-"Težava pri nalaganju vizualizacije. Časovni iztek poizvedb je nastavljen "
-"na %s sekundi."
 msgstr[2] ""
-"Težava pri nalaganju vizualizacije. Časovni iztek poizvedb je nastavljen "
-"na %s sekunde."
 msgstr[3] ""
-"Težava pri nalaganju vizualizacije. Časovni iztek poizvedb je nastavljen "
-"na %s sekund."
 
 msgid "What should be shown as the label"
 msgstr "Kaj bo prikazano na oznaki"
@@ -13057,9 +13048,9 @@ msgid ""
 "this statement as a subquery while grouping and filtering on the "
 "generated parent queries."
 msgstr ""
-"Ko uporabite SQL-poizvedbo, se podatkovni vir obnaša kot pogled (view). "
-"Superset bo ta zapis uporabil kot podpoizvedbo, pri čemer bo združeval in"
-" filtriral v ustvarjeni nadrejeni poizvedbi."
+"Ko podajate SQL, se podatkovni vir obnaša kot pogled (view). Superset bo "
+"ta zapis uporabil kot podpoizvedbo, pri čemer bo združeval in filtriral "
+"na podlagi ustvarjenih starševskih poizvedb."
 
 msgid ""
 "When the secondary temporal columns are filtered, apply the same filter "
@@ -13068,13 +13059,6 @@ msgstr ""
 "Če so sekundarni časovni stolpci filtrirani, uporabi enak filter tudi za "
 "glavni časovni stolpec."
 
-msgid ""
-"When unchecked, colors from the selected color scheme will be used for "
-"time shifted series"
-msgstr ""
-"Ko ni izbrano, bodo barve iz izbrane barvne sheme uporabljene za časovno "
-"premaknjene serije"
-
 msgid ""
 "When using \"Autocomplete filters\", this can be used to improve "
 "performance of the query fetching the values. Use this option to apply a "
@@ -13251,12 +13235,13 @@ msgstr ""
 "Če želite napolniti spustni seznam filtra v raziskovalnem pogledu "
 "filtrske sekcije z različnimi vrednostmi, pridobljenimi sproti v ozadju"
 
+#, fuzzy
 msgid "Whether to show as Nightingale chart."
-msgstr "Če želite prikaz kot Nightingale grafikon."
+msgstr "Prikaži merilno območje števčnega grafikona"
 
 msgid ""
 "Whether to show extra controls or not. Extra controls include things like"
-" making multiBar charts stacked or side by side."
+" making mulitBar charts stacked or side by side."
 msgstr ""
 "Če želite prikaz dodatnih kontrolnikov. Dodatni kontrolniki vključujejo "
 "možnost izdelave večstolpčnih grafikonov, naloženih ali drug ob drugem."
@@ -13419,6 +13404,7 @@ msgstr "Logaritemska Y-os"
 msgid "Y bounds"
 msgstr "Meje Y-osi"
 
+#, fuzzy
 msgid "Y-Axis"
 msgstr "Y-os"
 
@@ -13567,8 +13553,9 @@ msgstr "Nimate dovoljenja za branje oznak"
 msgid "You do not have permissions to edit this dashboard."
 msgstr "Nimate dovoljenj za urejanje te nadzorne plošče."
 
+#, fuzzy
 msgid "You do not have sufficient permissions to edit the chart"
-msgstr "Nimate zadostnih dovoljenj za urejanje grafikona"
+msgstr "Nimate dovoljenja za urejanje tega grafikona"
 
 msgid "You don't have access to this chart."
 msgstr "Nimate dostopa do tega grafikona."
@@ -13627,7 +13614,7 @@ msgstr ""
 " shranite stanje."
 
 msgid "You may have an error in your SQL statement. {message}"
-msgstr "Lahko, da je napaka v SQL-izrazu. {message}"
+msgstr ""
 
 msgid ""
 "You must be a dataset owner in order to edit. Please reach out to a "
@@ -13699,7 +13686,7 @@ msgid "Your report could not be deleted"
 msgstr "Vašega poročila ni mogoče izbrisati"
 
 msgid "ZIP file contains multiple file types"
-msgstr "ZIP-datoteka vsebuje več tipov datotek"
+msgstr ""
 
 msgid "Zero imputation"
 msgstr "Nadomeščanje ničel"
@@ -13772,12 +13759,11 @@ msgstr "Lastnost  `operation` poprocesirnega objekta ni definirana"
 msgid "`prophet` package not installed"
 msgstr "Knjižnica `prophet` ni nameščena"
 
+#, fuzzy
 msgid ""
 "`rename_columns` must have the same length as `columns` + "
 "`time_shift_columns`."
-msgstr ""
-"`rename_columns` morajo imeti enako dolžino kot `columns` + "
-"`time_shift_columns`."
+msgstr "`rename_columns` morajo imeti enako dolžino kot `columns`."
 
 msgid "`row_limit` must be greater than or equal to 0"
 msgstr "`row_limit` mora biti večja ali enaka 0"
@@ -13789,7 +13775,7 @@ msgid "`width` must be greater or equal to 0"
 msgstr "`width` mora biti večja ali enaka 0"
 
 msgid "add colors to cell bars for +/-"
-msgstr "dodajte barvo za graf v celici za +/-"
+msgstr ""
 
 msgid "aggregate"
 msgstr "agregacija"
@@ -13797,8 +13783,9 @@ msgstr "agregacija"
 msgid "alert"
 msgstr "opozorilo"
 
+#, fuzzy
 msgid "alert condition"
-msgstr "pogoj za opozorilo"
+msgstr "Status opozorila"
 
 msgid "alert dark"
 msgstr "opozorilo (temno)"
@@ -13836,14 +13823,15 @@ msgstr "samodejno (glajenje)"
 msgid "background"
 msgstr "ozadje"
 
+#, fuzzy
 msgid "basic conditional formatting"
-msgstr "osnovno pogojno oblikovanje"
+msgstr "Pogojno oblikovanje"
 
 msgid "basis"
 msgstr "basis"
 
 msgid "below (example:"
-msgstr "v polju spodaj (primer:"
+msgstr "v polje spodaj (primer:"
 
 msgid "between {down} and {up} {name}"
 msgstr "med {down} in {up} {name}"
@@ -13899,21 +13887,24 @@ msgstr "koda ISO 3166-1 alpha-3 (cca3)"
 msgid "code International Olympic Committee (cioc)"
 msgstr "koda Mednarodnega olimpijskega komiteja (cioc)"
 
+#, fuzzy
 msgid "color scheme for comparison"
-msgstr "barvna shema za primerjavo"
+msgstr "Časovna primerjava"
 
+#, fuzzy
 msgid "color type"
-msgstr "tip barve"
+msgstr "Barva glede na"
 
 msgid "column"
 msgstr "stolpec"
 
-#, python-format
+#, fuzzy, python-format
 msgid "connecting to %(dbModelName)s"
-msgstr "povezovanju z/s %(dbModelName)s"
+msgstr "povezovanje z %(dbModelName)s."
 
+#, fuzzy
 msgid "content type"
-msgstr "vrsta vsebine"
+msgstr "Stopnični tip"
 
 msgid "count"
 msgstr "število"
@@ -13927,8 +13918,9 @@ msgstr "ustvarite nov grafikon"
 msgid "create dataset from SQL query"
 msgstr "ustvari podatkovni set iz SQL-poizvedbe"
 
+#, fuzzy
 msgid "crontab"
-msgstr "crontab"
+msgstr "število"
 
 msgid "css"
 msgstr "css"
@@ -13955,7 +13947,7 @@ msgid "dataset"
 msgstr "podatkovni set"
 
 msgid "dataset name"
-msgstr "ime podatkovnega seta"
+msgstr "naziv podatkovnega seta"
 
 msgid "date"
 msgstr "datum"
@@ -13970,11 +13962,12 @@ msgid "day of the week"
 msgstr "dan v tednu"
 
 msgid "deck.gl 3D Hexagon"
-msgstr "deck.gl - 3D šestkotniki"
+msgstr "deck.gl - 3D HEX"
 
 msgid "deck.gl Arc"
 msgstr "deck.gl - lok"
 
+#, fuzzy
 msgid "deck.gl Contour"
 msgstr "deck.gl - plastnice"
 
@@ -14011,6 +14004,9 @@ msgstr "deckGL"
 msgid "default"
 msgstr "privzeto"
 
+msgid "delete"
+msgstr "izbriši"
+
 msgid "descendant"
 msgstr "podrejeni"
 
@@ -14047,11 +14043,12 @@ msgstr "npr. Analitika"
 msgid "e.g. compute_wh"
 msgstr "npr. compute_wh"
 
+#, fuzzy
 msgid "e.g. default"
-msgstr "npr. privzeto"
+msgstr "privzeto"
 
 msgid "e.g. hive_metastore"
-msgstr "npr. hive_metastore"
+msgstr ""
 
 msgid "e.g. param1=value1&param2=value2"
 msgstr "npr. param1=value1&param2=value2"
@@ -14071,8 +14068,9 @@ msgstr "npr. stolpec \"id_uporabnika\""
 msgid "edit mode"
 msgstr "načinu urejanja"
 
+#, fuzzy
 msgid "email subject"
-msgstr "zadeva sporočila"
+msgstr "Izberite zadevo"
 
 msgid "entries"
 msgstr "vnosi"
@@ -14156,8 +14154,9 @@ msgstr "v"
 msgid "in modal"
 msgstr "v modalnem oknu"
 
+#, fuzzy
 msgid "invalid email"
-msgstr "neveljaven email"
+msgstr "Neveljaven ključ povezave"
 
 msgid "is expected to be a Mapbox URL"
 msgstr "mora biti URL za Mapbox"
@@ -14168,25 +14167,6 @@ msgstr "pričakovano je število"
 msgid "is expected to be an integer"
 msgstr "pričakovano je celo število"
 
-#, python-format
-msgid ""
-"is linked to %s charts that appear on %s dashboards and users have %s SQL"
-" Lab tabs using this database open. Are you sure you want to continue? "
-"Deleting the database will break those objects."
-msgstr ""
-"je povezana z %s grafikoni, ki so prisotni na %s nadzornih ploščah, in "
-"uporabniki imajo %s odprtih zavihkov SQL laboratorija s to podatkovno "
-"bazo. Ali želite nadaljevati? Izbris podatkovne baze bo pokvaril te "
-"objekte."
-
-#, python-format
-msgid ""
-"is linked to %s charts that appear on %s dashboards. Are you sure you "
-"want to continue? Deleting the dataset will break those objects."
-msgstr ""
-"je povezan z grafikoni %s, ki so prisotni na %s nadzornih ploščah. Ali "
-"želite nadaljevati? Izbris podatkovnega seta bo pokvaril te objekte."
-
 msgid "key a-z"
 msgstr "a - ž"
 
@@ -14260,9 +14240,9 @@ msgstr "ime"
 msgid "no SQL validator is configured"
 msgstr "potrjevalnik SQL ni nastavljen"
 
-#, python-format
+#, fuzzy, python-format
 msgid "no SQL validator is configured for %(engine_spec)s"
-msgstr "potrjevalnik SQL ni nastavljen za %(engine_spec)s"
+msgstr "Potrjevalnik ni najden (nastavljen za podatkovno bazo)"
 
 msgid "numeric type icon"
 msgstr "ikona numeričnega tipa"
@@ -14288,8 +14268,9 @@ msgstr "stolpec za razvrščanje (orderby) mora biti izpolnjen"
 msgid "overall"
 msgstr "skupaj"
 
+#, fuzzy
 msgid "owners"
-msgstr "lastniki"
+msgstr "Lastniki"
 
 msgid "p-value precision"
 msgstr "točnost p-vrednosti"
@@ -14370,8 +14351,9 @@ msgstr "nedavno"
 msgid "recents"
 msgstr "nedavne"
 
+#, fuzzy
 msgid "recipients"
-msgstr "prejemniki"
+msgstr "nedavne"
 
 msgid "report"
 msgstr "poročilo"
@@ -14405,15 +14387,16 @@ msgid ""
 " scale; change: Show changes compared to the first data point in each "
 "series"
 msgstr ""
-"serije: Obravnavaj vsako podatkovno serijo neodvisno; skupaj: Vse vrste "
-"uporabljajo enako skalo; sprememba: Prikaži razlike glede na prvo točko "
+"serije: Obravnavaj vsako podatkovno serijo neodvisno; skupno: Vse vrste "
+"uporabljajo enako skalo; razlika: Prikaži razlike glede na prvo točko "
 "vsake serije"
 
+#, fuzzy
 msgid "shift start date"
-msgstr "premakni začetni datum"
+msgstr "Začetni datum"
 
 msgid "sql"
-msgstr "sql"
+msgstr ""
 
 msgid "square"
 msgstr "pravokotno"
@@ -14503,6 +14486,9 @@ msgstr "varianca"
 msgid "view instructions"
 msgstr "ogled navodil"
 
+msgid "virtual"
+msgstr "virtualen"
+
 msgid "viz type"
 msgstr "tip vizualizacije"
 
@@ -14518,8 +14504,9 @@ msgstr "teden s koncem v soboto"
 msgid "week starting Sunday"
 msgstr "teden z začetkom v nedeljo"
 
+#, fuzzy
 msgid "working timeout"
-msgstr "pretek delovanja"
+msgstr "Pretek delovanja"
 
 msgid "x"
 msgstr "x"
diff --git a/tests/integration_tests/async_events/api_tests.py b/tests/integration_tests/async_events/api_tests.py
index 8397b8cf97..5a8189f9a7 100644
--- a/tests/integration_tests/async_events/api_tests.py
+++ b/tests/integration_tests/async_events/api_tests.py
@@ -59,7 +59,7 @@ class TestAsyncEventApi(SupersetTestCase):
         assert rv.status_code == 200
         channel_id = app.config["GLOBAL_ASYNC_QUERIES_REDIS_STREAM_PREFIX"] + self.UUID
         mock_xrange.assert_called_with(channel_id, "-", "+", 100)
-        assert response == {"result": []}
+        self.assertEqual(response, {"result": []})
 
     def _test_events_last_id_logic(self, mock_cache):
         with mock.patch.object(mock_cache, "xrange") as mock_xrange:
@@ -69,7 +69,7 @@ class TestAsyncEventApi(SupersetTestCase):
         assert rv.status_code == 200
         channel_id = app.config["GLOBAL_ASYNC_QUERIES_REDIS_STREAM_PREFIX"] + self.UUID
         mock_xrange.assert_called_with(channel_id, "1607471525180-1", "+", 100)
-        assert response == {"result": []}
+        self.assertEqual(response, {"result": []})
 
     def _test_events_results_logic(self, mock_cache):
         with mock.patch.object(mock_cache, "xrange") as mock_xrange:
@@ -115,7 +115,7 @@ class TestAsyncEventApi(SupersetTestCase):
                 },
             ]
         }
-        assert response == expected
+        self.assertEqual(response, expected)
 
     @mock.patch("uuid.uuid4", return_value=UUID)
     def test_events_redis_cache_backend(self, mock_uuid4):
diff --git a/tests/integration_tests/base_api_tests.py b/tests/integration_tests/base_api_tests.py
index 6c10b7cf26..de003ff945 100644
--- a/tests/integration_tests/base_api_tests.py
+++ b/tests/integration_tests/base_api_tests.py
@@ -69,7 +69,7 @@ class TestOpenApiSpec(SupersetTestCase):
         self.login(ADMIN_USERNAME)
         uri = "api/v1/_openapi"
         rv = self.client.get(uri)
-        assert rv.status_code == 200
+        self.assertEqual(rv.status_code, 200)
         response = json.loads(rv.data.decode("utf-8"))
         validate_spec(response)
 
@@ -87,20 +87,20 @@ class TestBaseModelRestApi(SupersetTestCase):
         self.login(ADMIN_USERNAME)
         uri = "api/v1/model1api/"
         rv = self.client.get(uri)
-        assert rv.status_code == 200
+        self.assertEqual(rv.status_code, 200)
         response = json.loads(rv.data.decode("utf-8"))
-        assert response["list_columns"] == ["id"]
+        self.assertEqual(response["list_columns"], ["id"])
         for result in response["result"]:
-            assert list(result.keys()) == ["id"]
+            self.assertEqual(list(result.keys()), ["id"])
 
         # Check get response
         dashboard = db.session.query(Dashboard).first()
         uri = f"api/v1/model1api/{dashboard.id}"
         rv = self.client.get(uri)
-        assert rv.status_code == 200
+        self.assertEqual(rv.status_code, 200)
         response = json.loads(rv.data.decode("utf-8"))
-        assert response["show_columns"] == ["id"]
-        assert list(response["result"].keys()) == ["id"]
+        self.assertEqual(response["show_columns"], ["id"])
+        self.assertEqual(list(response["result"].keys()), ["id"])
 
     def test_default_missing_declaration_put_spec(self):
         """
@@ -113,18 +113,17 @@ class TestBaseModelRestApi(SupersetTestCase):
         uri = "api/v1/_openapi"
         rv = self.client.get(uri)
         # dashboard model accepts all fields are null
-        assert rv.status_code == 200
+        self.assertEqual(rv.status_code, 200)
         response = json.loads(rv.data.decode("utf-8"))
         expected_mutation_spec = {
             "properties": {"id": {"type": "integer"}},
             "type": "object",
         }
-        assert (
-            response["components"]["schemas"]["Model1Api.post"]
-            == expected_mutation_spec
+        self.assertEqual(
+            response["components"]["schemas"]["Model1Api.post"], expected_mutation_spec
         )
-        assert (
-            response["components"]["schemas"]["Model1Api.put"] == expected_mutation_spec
+        self.assertEqual(
+            response["components"]["schemas"]["Model1Api.put"], expected_mutation_spec
         )
 
     def test_default_missing_declaration_post(self):
@@ -146,7 +145,7 @@ class TestBaseModelRestApi(SupersetTestCase):
         uri = "api/v1/model1api/"
         rv = self.client.post(uri, json=dashboard_data)
         response = json.loads(rv.data.decode("utf-8"))
-        assert rv.status_code == 422
+        self.assertEqual(rv.status_code, 422)
         expected_response = {
             "message": {
                 "css": ["Unknown field."],
@@ -157,7 +156,7 @@ class TestBaseModelRestApi(SupersetTestCase):
                 "slug": ["Unknown field."],
             }
         }
-        assert response == expected_response
+        self.assertEqual(response, expected_response)
 
     def test_refuse_invalid_format_request(self):
         """
@@ -170,7 +169,7 @@ class TestBaseModelRestApi(SupersetTestCase):
         rv = self.client.post(
             uri, data="a: value\nb: 1\n", content_type="application/yaml"
         )
-        assert rv.status_code == 400
+        self.assertEqual(rv.status_code, 400)
 
     @pytest.mark.usefixtures("load_world_bank_dashboard_with_slices")
     def test_default_missing_declaration_put(self):
@@ -186,14 +185,14 @@ class TestBaseModelRestApi(SupersetTestCase):
         uri = f"api/v1/model1api/{dashboard.id}"
         rv = self.client.put(uri, json=dashboard_data)
         response = json.loads(rv.data.decode("utf-8"))
-        assert rv.status_code == 422
+        self.assertEqual(rv.status_code, 422)
         expected_response = {
             "message": {
                 "dashboard_title": ["Unknown field."],
                 "slug": ["Unknown field."],
             }
         }
-        assert response == expected_response
+        self.assertEqual(response, expected_response)
 
 
 class ApiOwnersTestCaseMixin:
diff --git a/tests/integration_tests/cache_tests.py b/tests/integration_tests/cache_tests.py
index 1356e32cd8..88b20282f4 100644
--- a/tests/integration_tests/cache_tests.py
+++ b/tests/integration_tests/cache_tests.py
@@ -59,8 +59,8 @@ class TestCache(SupersetTestCase):
         )
         # restore DATA_CACHE_CONFIG
         app.config["DATA_CACHE_CONFIG"] = data_cache_config
-        assert not resp["is_cached"]
-        assert not resp_from_cache["is_cached"]
+        self.assertFalse(resp["is_cached"])
+        self.assertFalse(resp_from_cache["is_cached"])
 
     @pytest.mark.usefixtures("load_birth_names_dashboard_with_slices")
     def test_slice_data_cache(self):
@@ -84,20 +84,20 @@ class TestCache(SupersetTestCase):
         resp_from_cache = self.get_json_resp(
             json_endpoint, {"form_data": json.dumps(slc.viz.form_data)}
         )
-        assert not resp["is_cached"]
-        assert resp_from_cache["is_cached"]
+        self.assertFalse(resp["is_cached"])
+        self.assertTrue(resp_from_cache["is_cached"])
         # should fallback to default cache timeout
-        assert resp_from_cache["cache_timeout"] == 10
-        assert resp_from_cache["status"] == QueryStatus.SUCCESS
-        assert resp["data"] == resp_from_cache["data"]
-        assert resp["query"] == resp_from_cache["query"]
+        self.assertEqual(resp_from_cache["cache_timeout"], 10)
+        self.assertEqual(resp_from_cache["status"], QueryStatus.SUCCESS)
+        self.assertEqual(resp["data"], resp_from_cache["data"])
+        self.assertEqual(resp["query"], resp_from_cache["query"])
         # should exists in `data_cache`
-        assert (
-            cache_manager.data_cache.get(resp_from_cache["cache_key"])["query"]
-            == resp_from_cache["query"]
+        self.assertEqual(
+            cache_manager.data_cache.get(resp_from_cache["cache_key"])["query"],
+            resp_from_cache["query"],
         )
         # should not exists in `cache`
-        assert cache_manager.cache.get(resp_from_cache["cache_key"]) is None
+        self.assertIsNone(cache_manager.cache.get(resp_from_cache["cache_key"]))
 
         # reset cache config
         app.config["DATA_CACHE_CONFIG"] = data_cache_config
diff --git a/tests/integration_tests/charts/api_tests.py b/tests/integration_tests/charts/api_tests.py
index a99ba04f78..a8bae9b64d 100644
--- a/tests/integration_tests/charts/api_tests.py
+++ b/tests/integration_tests/charts/api_tests.py
@@ -336,9 +336,9 @@ class TestChartApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCase):
         self.login(ADMIN_USERNAME)
         uri = f"api/v1/chart/{chart_id}"
         rv = self.delete_assert_metric(uri, "delete")
-        assert rv.status_code == 200
+        self.assertEqual(rv.status_code, 200)
         model = db.session.query(Slice).get(chart_id)
-        assert model is None
+        self.assertEqual(model, None)
 
     def test_delete_bulk_charts(self):
         """
@@ -355,13 +355,13 @@ class TestChartApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCase):
         argument = chart_ids
         uri = f"api/v1/chart/?q={prison.dumps(argument)}"
         rv = self.delete_assert_metric(uri, "bulk_delete")
-        assert rv.status_code == 200
+        self.assertEqual(rv.status_code, 200)
         response = json.loads(rv.data.decode("utf-8"))
         expected_response = {"message": f"Deleted {chart_count} charts"}
-        assert response == expected_response
+        self.assertEqual(response, expected_response)
         for chart_id in chart_ids:
             model = db.session.query(Slice).get(chart_id)
-            assert model is None
+            self.assertEqual(model, None)
 
     def test_delete_bulk_chart_bad_request(self):
         """
@@ -372,7 +372,7 @@ class TestChartApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCase):
         argument = chart_ids
         uri = f"api/v1/chart/?q={prison.dumps(argument)}"
         rv = self.delete_assert_metric(uri, "bulk_delete")
-        assert rv.status_code == 400
+        self.assertEqual(rv.status_code, 400)
 
     def test_delete_not_found_chart(self):
         """
@@ -382,7 +382,7 @@ class TestChartApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCase):
         chart_id = 1000
         uri = f"api/v1/chart/{chart_id}"
         rv = self.delete_assert_metric(uri, "delete")
-        assert rv.status_code == 404
+        self.assertEqual(rv.status_code, 404)
 
     @pytest.mark.usefixtures("create_chart_with_report")
     def test_delete_chart_with_report(self):
@@ -398,11 +398,11 @@ class TestChartApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCase):
         uri = f"api/v1/chart/{chart.id}"
         rv = self.client.delete(uri)
         response = json.loads(rv.data.decode("utf-8"))
-        assert rv.status_code == 422
+        self.assertEqual(rv.status_code, 422)
         expected_response = {
             "message": "There are associated alerts or reports: report_with_chart"
         }
-        assert response == expected_response
+        self.assertEqual(response, expected_response)
 
     def test_delete_bulk_charts_not_found(self):
         """
@@ -413,7 +413,7 @@ class TestChartApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCase):
         self.login(ADMIN_USERNAME)
         uri = f"api/v1/chart/?q={prison.dumps(chart_ids)}"
         rv = self.delete_assert_metric(uri, "bulk_delete")
-        assert rv.status_code == 404
+        self.assertEqual(rv.status_code, 404)
 
     @pytest.mark.usefixtures("create_chart_with_report", "create_charts")
     def test_bulk_delete_chart_with_report(self):
@@ -434,11 +434,11 @@ class TestChartApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCase):
         uri = f"api/v1/chart/?q={prison.dumps(chart_ids)}"
         rv = self.client.delete(uri)
         response = json.loads(rv.data.decode("utf-8"))
-        assert rv.status_code == 422
+        self.assertEqual(rv.status_code, 422)
         expected_response = {
             "message": "There are associated alerts or reports: report_with_chart"
         }
-        assert response == expected_response
+        self.assertEqual(response, expected_response)
 
     def test_delete_chart_admin_not_owned(self):
         """
@@ -450,9 +450,9 @@ class TestChartApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCase):
         self.login(ADMIN_USERNAME)
         uri = f"api/v1/chart/{chart_id}"
         rv = self.delete_assert_metric(uri, "delete")
-        assert rv.status_code == 200
+        self.assertEqual(rv.status_code, 200)
         model = db.session.query(Slice).get(chart_id)
-        assert model is None
+        self.assertEqual(model, None)
 
     def test_delete_bulk_chart_admin_not_owned(self):
         """
@@ -471,13 +471,13 @@ class TestChartApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCase):
         uri = f"api/v1/chart/?q={prison.dumps(argument)}"
         rv = self.delete_assert_metric(uri, "bulk_delete")
         response = json.loads(rv.data.decode("utf-8"))
-        assert rv.status_code == 200
+        self.assertEqual(rv.status_code, 200)
         expected_response = {"message": f"Deleted {chart_count} charts"}
-        assert response == expected_response
+        self.assertEqual(response, expected_response)
 
         for chart_id in chart_ids:
             model = db.session.query(Slice).get(chart_id)
-            assert model is None
+            self.assertEqual(model, None)
 
     def test_delete_chart_not_owned(self):
         """
@@ -493,7 +493,7 @@ class TestChartApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCase):
         self.login(username="alpha2", password="password")
         uri = f"api/v1/chart/{chart.id}"
         rv = self.delete_assert_metric(uri, "delete")
-        assert rv.status_code == 403
+        self.assertEqual(rv.status_code, 403)
         db.session.delete(chart)
         db.session.delete(user_alpha1)
         db.session.delete(user_alpha2)
@@ -525,19 +525,19 @@ class TestChartApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCase):
         arguments = [chart.id for chart in charts]
         uri = f"api/v1/chart/?q={prison.dumps(arguments)}"
         rv = self.delete_assert_metric(uri, "bulk_delete")
-        assert rv.status_code == 403
+        self.assertEqual(rv.status_code, 403)
         response = json.loads(rv.data.decode("utf-8"))
         expected_response = {"message": "Forbidden"}
-        assert response == expected_response
+        self.assertEqual(response, expected_response)
 
         # # nothing is deleted in bulk with a list of owned and not owned charts
         arguments = [chart.id for chart in charts] + [owned_chart.id]
         uri = f"api/v1/chart/?q={prison.dumps(arguments)}"
         rv = self.delete_assert_metric(uri, "bulk_delete")
-        assert rv.status_code == 403
+        self.assertEqual(rv.status_code, 403)
         response = json.loads(rv.data.decode("utf-8"))
         expected_response = {"message": "Forbidden"}
-        assert response == expected_response
+        self.assertEqual(response, expected_response)
 
         for chart in charts:
             db.session.delete(chart)
@@ -572,7 +572,7 @@ class TestChartApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCase):
         self.login(ADMIN_USERNAME)
         uri = "api/v1/chart/"
         rv = self.post_assert_metric(uri, chart_data, "post")
-        assert rv.status_code == 201
+        self.assertEqual(rv.status_code, 201)
         data = json.loads(rv.data.decode("utf-8"))
         model = db.session.query(Slice).get(data.get("id"))
         db.session.delete(model)
@@ -590,7 +590,7 @@ class TestChartApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCase):
         self.login(ADMIN_USERNAME)
         uri = "api/v1/chart/"
         rv = self.post_assert_metric(uri, chart_data, "post")
-        assert rv.status_code == 201
+        self.assertEqual(rv.status_code, 201)
         data = json.loads(rv.data.decode("utf-8"))
         model = db.session.query(Slice).get(data.get("id"))
         db.session.delete(model)
@@ -609,10 +609,10 @@ class TestChartApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCase):
         self.login(ADMIN_USERNAME)
         uri = "api/v1/chart/"
         rv = self.post_assert_metric(uri, chart_data, "post")
-        assert rv.status_code == 422
+        self.assertEqual(rv.status_code, 422)
         response = json.loads(rv.data.decode("utf-8"))
         expected_response = {"message": {"owners": ["Owners are invalid"]}}
-        assert response == expected_response
+        self.assertEqual(response, expected_response)
 
     def test_create_chart_validate_params(self):
         """
@@ -627,7 +627,7 @@ class TestChartApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCase):
         self.login(ADMIN_USERNAME)
         uri = "api/v1/chart/"
         rv = self.post_assert_metric(uri, chart_data, "post")
-        assert rv.status_code == 400
+        self.assertEqual(rv.status_code, 400)
 
     def test_create_chart_validate_datasource(self):
         """
@@ -640,24 +640,29 @@ class TestChartApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCase):
             "datasource_type": "unknown",
         }
         rv = self.post_assert_metric("/api/v1/chart/", chart_data, "post")
-        assert rv.status_code == 400
+        self.assertEqual(rv.status_code, 400)
         response = json.loads(rv.data.decode("utf-8"))
-        assert response == {
-            "message": {
-                "datasource_type": [
-                    "Must be one of: table, dataset, query, saved_query, view."
-                ]
-            }
-        }
+        self.assertEqual(
+            response,
+            {
+                "message": {
+                    "datasource_type": [
+                        "Must be one of: table, dataset, query, saved_query, view."
+                    ]
+                }
+            },
+        )
         chart_data = {
             "slice_name": "title1",
             "datasource_id": 0,
             "datasource_type": "table",
         }
         rv = self.post_assert_metric("/api/v1/chart/", chart_data, "post")
-        assert rv.status_code == 422
+        self.assertEqual(rv.status_code, 422)
         response = json.loads(rv.data.decode("utf-8"))
-        assert response == {"message": {"datasource_id": ["Datasource does not exist"]}}
+        self.assertEqual(
+            response, {"message": {"datasource_id": ["Datasource does not exist"]}}
+        )
 
     @pytest.mark.usefixtures("load_world_bank_dashboard_with_slices")
     def test_create_chart_validate_user_is_dashboard_owner(self):
@@ -677,11 +682,12 @@ class TestChartApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCase):
         self.login(ALPHA_USERNAME)
         uri = "api/v1/chart/"
         rv = self.post_assert_metric(uri, chart_data, "post")
-        assert rv.status_code == 403
+        self.assertEqual(rv.status_code, 403)
         response = json.loads(rv.data.decode("utf-8"))
-        assert response == {
-            "message": "Changing one or more of these dashboards is forbidden"
-        }
+        self.assertEqual(
+            response,
+            {"message": "Changing one or more of these dashboards is forbidden"},
+        )
 
     @pytest.mark.usefixtures("load_birth_names_dashboard_with_slices")
     def test_update_chart(self):
@@ -714,23 +720,23 @@ class TestChartApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCase):
         self.login(ADMIN_USERNAME)
         uri = f"api/v1/chart/{chart_id}"
         rv = self.put_assert_metric(uri, chart_data, "put")
-        assert rv.status_code == 200
+        self.assertEqual(rv.status_code, 200)
         model = db.session.query(Slice).get(chart_id)
         related_dashboard = db.session.query(Dashboard).filter_by(slug="births").first()
-        assert model.created_by == admin
-        assert model.slice_name == "title1_changed"
-        assert model.description == "description1"
-        assert admin not in model.owners
-        assert gamma in model.owners
-        assert model.viz_type == "viz_type1"
-        assert model.params == '{"a": 1}'
-        assert model.cache_timeout == 1000
-        assert model.datasource_id == birth_names_table_id
-        assert model.datasource_type == "table"
-        assert model.datasource_name == full_table_name
-        assert model.certified_by == "Mario Rossi"
-        assert model.certification_details == "Edited certification"
-        assert model.id in [slice.id for slice in related_dashboard.slices]
+        self.assertEqual(model.created_by, admin)
+        self.assertEqual(model.slice_name, "title1_changed")
+        self.assertEqual(model.description, "description1")
+        self.assertNotIn(admin, model.owners)
+        self.assertIn(gamma, model.owners)
+        self.assertEqual(model.viz_type, "viz_type1")
+        self.assertEqual(model.params, """{"a": 1}""")
+        self.assertEqual(model.cache_timeout, 1000)
+        self.assertEqual(model.datasource_id, birth_names_table_id)
+        self.assertEqual(model.datasource_type, "table")
+        self.assertEqual(model.datasource_name, full_table_name)
+        self.assertEqual(model.certified_by, "Mario Rossi")
+        self.assertEqual(model.certification_details, "Edited certification")
+        self.assertIn(model.id, [slice.id for slice in related_dashboard.slices])
         db.session.delete(model)
         db.session.commit()
 
@@ -749,16 +755,16 @@ class TestChartApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCase):
         self.login(ADMIN_USERNAME)
         uri = f"api/v1/chart/{chart_id}"
         rv = self.put_assert_metric(uri, chart_data, "put")
-        assert rv.status_code == 200
+        self.assertEqual(rv.status_code, 200)
         model = db.session.query(Slice).get(chart_id)
 
         response = self.get_assert_metric("api/v1/chart/", "get_list")
         res = json.loads(response.data.decode("utf-8"))["result"]
 
         current_chart = [d for d in res if d["id"] == chart_id][0]
-        assert current_chart["slice_name"] == new_name
-        assert "username" not in current_chart["changed_by"].keys()
-        assert "username" not in current_chart["owners"][0].keys()
+        self.assertEqual(current_chart["slice_name"], new_name)
+        self.assertNotIn("username", current_chart["changed_by"].keys())
+        self.assertNotIn("username", current_chart["owners"][0].keys())
 
         db.session.delete(model)
         db.session.commit()
@@ -778,14 +784,14 @@ class TestChartApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCase):
         self.login(ADMIN_USERNAME)
         uri = f"api/v1/chart/{chart_id}"
         rv = self.put_assert_metric(uri, chart_data, "put")
-        assert rv.status_code == 200
+        self.assertEqual(rv.status_code, 200)
         model = db.session.query(Slice).get(chart_id)
 
         response = self.get_assert_metric(uri, "get")
         res = json.loads(response.data.decode("utf-8"))["result"]
 
-        assert res["slice_name"] == new_name
-        assert "username" not in res["owners"][0].keys()
+        self.assertEqual(res["slice_name"], new_name)
+        self.assertNotIn("username", res["owners"][0].keys())
 
         db.session.delete(model)
         db.session.commit()
@@ -823,10 +829,10 @@ class TestChartApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCase):
         self.login(ADMIN_USERNAME)
         uri = f"api/v1/chart/{chart_id}"
         rv = self.put_assert_metric(uri, chart_data, "put")
-        assert rv.status_code == 200
+        self.assertEqual(rv.status_code, 200)
         model = db.session.query(Slice).get(chart_id)
-        assert admin not in model.owners
-        assert gamma in model.owners
+        self.assertNotIn(admin, model.owners)
+        self.assertIn(gamma, model.owners)
         db.session.delete(model)
         db.session.commit()
 
@@ -842,8 +848,8 @@ class TestChartApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCase):
         self.login(username="admin")
         uri = f"api/v1/chart/{self.chart.id}"
         rv = self.put_assert_metric(uri, chart_data, "put")
-        assert rv.status_code == 200
-        assert [admin] == self.chart.owners
+        self.assertEqual(rv.status_code, 200)
+        self.assertEqual([admin], self.chart.owners)
 
     @pytest.mark.usefixtures("add_dashboard_to_chart")
     def test_update_chart_clear_owner_list(self):
@@ -855,8 +861,8 @@ class TestChartApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCase):
         self.login(username="admin")
         uri = f"api/v1/chart/{self.chart.id}"
         rv = self.put_assert_metric(uri, chart_data, "put")
-        assert rv.status_code == 200
-        assert [] == self.chart.owners
+        self.assertEqual(rv.status_code, 200)
+        self.assertEqual([], self.chart.owners)
 
     def test_update_chart_populate_owner(self):
         """
@@ -867,15 +873,15 @@ class TestChartApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCase):
         admin = self.get_user("admin")
         chart_id = self.insert_chart("title", [], 1).id
         model = db.session.query(Slice).get(chart_id)
-        assert model.owners == []
+        self.assertEqual(model.owners, [])
         chart_data = {"owners": [gamma.id]}
         self.login(username="admin")
         uri = f"api/v1/chart/{chart_id}"
         rv = self.put_assert_metric(uri, chart_data, "put")
-        assert rv.status_code == 200
+        self.assertEqual(rv.status_code, 200)
         model_updated = db.session.query(Slice).get(chart_id)
-        assert admin not in model_updated.owners
-        assert gamma in model_updated.owners
+        self.assertNotIn(admin, model_updated.owners)
+        self.assertIn(gamma, model_updated.owners)
         db.session.delete(model_updated)
         db.session.commit()
 
@@ -891,9 +897,9 @@ class TestChartApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCase):
         self.login(ADMIN_USERNAME)
         uri = f"api/v1/chart/{self.chart.id}"
         rv = self.put_assert_metric(uri, chart_data, "put")
-        assert rv.status_code == 200
-        assert self.new_dashboard in self.chart.dashboards
-        assert self.original_dashboard not in self.chart.dashboards
+        self.assertEqual(rv.status_code, 200)
+        self.assertIn(self.new_dashboard, self.chart.dashboards)
+        self.assertNotIn(self.original_dashboard, self.chart.dashboards)
 
     @pytest.mark.usefixtures("add_dashboard_to_chart")
     def test_not_update_chart_none_dashboards(self):
@@ -904,9 +910,9 @@ class TestChartApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCase):
         self.login(ADMIN_USERNAME)
         uri = f"api/v1/chart/{self.chart.id}"
         rv = self.put_assert_metric(uri, chart_data, "put")
-        assert rv.status_code == 200
-        assert self.original_dashboard in self.chart.dashboards
-        assert len(self.chart.dashboards) == 1
+        self.assertEqual(rv.status_code, 200)
+        self.assertIn(self.original_dashboard, self.chart.dashboards)
+        self.assertEqual(len(self.chart.dashboards), 1)
 
     def test_update_chart_not_owned(self):
         """
@@ -924,7 +930,7 @@ class TestChartApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCase):
         chart_data = {"slice_name": "title1_changed"}
         uri = f"api/v1/chart/{chart.id}"
         rv = self.put_assert_metric(uri, chart_data, "put")
-        assert rv.status_code == 403
+        self.assertEqual(rv.status_code, 403)
         db.session.delete(chart)
         db.session.delete(user_alpha1)
         db.session.delete(user_alpha2)
@@ -970,13 +976,13 @@ class TestChartApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCase):
         uri = f"api/v1/chart/{chart.id}"
 
         rv = self.put_assert_metric(uri, chart_data_with_invalid_dashboard, "put")
-        assert rv.status_code == 422
+        self.assertEqual(rv.status_code, 422)
         response = json.loads(rv.data.decode("utf-8"))
         expected_response = {"message": {"dashboards": ["Dashboards do not exist"]}}
-        assert response == expected_response
+        self.assertEqual(response, expected_response)
 
         rv = self.put_assert_metric(uri, chart_data, "put")
-        assert rv.status_code == 200
+        self.assertEqual(rv.status_code, 200)
 
         db.session.delete(chart)
         db.session.delete(original_dashboard)
@@ -995,21 +1001,26 @@ class TestChartApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCase):
 
         chart_data = {"datasource_id": 1, "datasource_type": "unknown"}
         rv = self.put_assert_metric(f"/api/v1/chart/{chart.id}", chart_data, "put")
-        assert rv.status_code == 400
+        self.assertEqual(rv.status_code, 400)
         response = json.loads(rv.data.decode("utf-8"))
-        assert response == {
-            "message": {
-                "datasource_type": [
-                    "Must be one of: table, dataset, query, saved_query, view."
-                ]
-            }
-        }
+        self.assertEqual(
+            response,
+            {
+                "message": {
+                    "datasource_type": [
+                        "Must be one of: table, dataset, query, saved_query, view."
+                    ]
+                }
+            },
+        )
 
         chart_data = {"datasource_id": 0, "datasource_type": "table"}
         rv = self.put_assert_metric(f"/api/v1/chart/{chart.id}", chart_data, "put")
-        assert rv.status_code == 422
+        self.assertEqual(rv.status_code, 422)
         response = json.loads(rv.data.decode("utf-8"))
-        assert response == {"message": {"datasource_id": ["Datasource does not exist"]}}
+        self.assertEqual(
+            response, {"message": {"datasource_id": ["Datasource does not exist"]}}
+        )
 
         db.session.delete(chart)
         db.session.commit()
@@ -1027,10 +1038,10 @@ class TestChartApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCase):
         self.login(ADMIN_USERNAME)
         uri = "api/v1/chart/"  # noqa: F541
         rv = self.client.post(uri, json=chart_data)
-        assert rv.status_code == 422
+        self.assertEqual(rv.status_code, 422)
         response = json.loads(rv.data.decode("utf-8"))
         expected_response = {"message": {"owners": ["Owners are invalid"]}}
-        assert response == expected_response
+        self.assertEqual(response, expected_response)
 
     @pytest.mark.usefixtures("load_world_bank_dashboard_with_slices")
     def test_get_chart(self):
@@ -1042,7 +1053,7 @@ class TestChartApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCase):
         self.login(ADMIN_USERNAME)
         uri = f"api/v1/chart/{chart.id}"
         rv = self.get_assert_metric(uri, "get")
-        assert rv.status_code == 200
+        self.assertEqual(rv.status_code, 200)
         expected_result = {
             "cache_timeout": None,
             "certified_by": None,
@@ -1064,10 +1075,10 @@ class TestChartApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCase):
             "is_managed_externally": False,
         }
         data = json.loads(rv.data.decode("utf-8"))
-        assert "changed_on_delta_humanized" in data["result"]
-        assert "id" in data["result"]
-        assert "thumbnail_url" in data["result"]
-        assert "url" in data["result"]
+        self.assertIn("changed_on_delta_humanized", data["result"])
+        self.assertIn("id", data["result"])
+        self.assertIn("thumbnail_url", data["result"])
+        self.assertIn("url", data["result"])
         for key, value in data["result"].items():
             # We can't assert timestamp values or id/urls
             if key not in (
@@ -1076,7 +1087,7 @@ class TestChartApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCase):
                 "thumbnail_url",
                 "url",
             ):
-                assert value == expected_result[key]
+                self.assertEqual(value, expected_result[key])
         db.session.delete(chart)
         db.session.commit()
 
@@ -1088,7 +1099,7 @@ class TestChartApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCase):
         self.login(ADMIN_USERNAME)
         uri = f"api/v1/chart/{chart_id}"
         rv = self.get_assert_metric(uri, "get")
-        assert rv.status_code == 404
+        self.assertEqual(rv.status_code, 404)
 
     @pytest.mark.usefixtures("load_birth_names_dashboard_with_slices")
     def test_get_chart_no_data_access(self):
@@ -1103,7 +1114,7 @@ class TestChartApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCase):
         )
         uri = f"api/v1/chart/{chart_no_access.id}"
         rv = self.client.get(uri)
-        assert rv.status_code == 404
+        self.assertEqual(rv.status_code, 404)
 
     @pytest.mark.usefixtures(
         "load_energy_table_with_slice",
@@ -1118,9 +1129,9 @@ class TestChartApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCase):
         self.login(ADMIN_USERNAME)
         uri = "api/v1/chart/"  # noqa: F541
         rv = self.get_assert_metric(uri, "get_list")
-        assert rv.status_code == 200
+        self.assertEqual(rv.status_code, 200)
         data = json.loads(rv.data.decode("utf-8"))
-        assert data["count"] == 33
+        self.assertEqual(data["count"], 33)
 
     @pytest.mark.usefixtures("load_energy_table_with_slice", "add_dashboard_to_chart")
     def test_get_charts_dashboards(self):
@@ -1135,7 +1146,7 @@ class TestChartApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCase):
         }
         uri = f"api/v1/chart/?q={prison.dumps(arguments)}"
         rv = self.get_assert_metric(uri, "get_list")
-        assert rv.status_code == 200
+        self.assertEqual(rv.status_code, 200)
         data = json.loads(rv.data.decode("utf-8"))
         assert data["result"][0]["dashboards"] == [
             {
@@ -1161,7 +1172,7 @@ class TestChartApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCase):
         }
         uri = f"api/v1/chart/?q={prison.dumps(arguments)}"
         rv = self.get_assert_metric(uri, "get_list")
-        assert rv.status_code == 200
+        self.assertEqual(rv.status_code, 200)
         data = json.loads(rv.data.decode("utf-8"))
         result = data["result"]
         assert len(result) == 1
@@ -1195,20 +1206,26 @@ class TestChartApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCase):
             # Filter by tag ID
             filter_params = get_filter_params("chart_tag_id", tag.id)
             response_by_id = self.get_list("chart", filter_params)
-            assert response_by_id.status_code == 200
+            self.assertEqual(response_by_id.status_code, 200)
             data_by_id = json.loads(response_by_id.data.decode("utf-8"))
 
             # Filter by tag name
             filter_params = get_filter_params("chart_tags", tag.name)
             response_by_name = self.get_list("chart", filter_params)
-            assert response_by_name.status_code == 200
+            self.assertEqual(response_by_name.status_code, 200)
             data_by_name = json.loads(response_by_name.data.decode("utf-8"))
 
             # Compare results
-            assert data_by_id["count"] == data_by_name["count"], len(expected_charts)
-            assert set(chart["id"] for chart in data_by_id["result"]) == set(
-                chart["id"] for chart in data_by_name["result"]
-            ), set(chart.id for chart in expected_charts)
+            self.assertEqual(
+                data_by_id["count"],
+                data_by_name["count"],
+                len(expected_charts),
+            )
+            self.assertEqual(
+                set(chart["id"] for chart in data_by_id["result"]),
+                set(chart["id"] for chart in data_by_name["result"]),
+                set(chart.id for chart in expected_charts),
+            )
 
     def test_get_charts_changed_on(self):
         """
@@ -1226,7 +1243,7 @@ class TestChartApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCase):
         uri = f"api/v1/chart/?q={prison.dumps(arguments)}"
 
         rv = self.get_assert_metric(uri, "get_list")
-        assert rv.status_code == 200
+        self.assertEqual(rv.status_code, 200)
         data = json.loads(rv.data.decode("utf-8"))
         assert data["result"][0]["changed_on_delta_humanized"] in (
             "now",
@@ -1249,9 +1266,9 @@ class TestChartApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCase):
         arguments = {"filters": [{"col": "slice_name", "opr": "sw", "value": "G"}]}
         uri = f"api/v1/chart/?q={prison.dumps(arguments)}"
         rv = self.get_assert_metric(uri, "get_list")
-        assert rv.status_code == 200
+        self.assertEqual(rv.status_code, 200)
         data = json.loads(rv.data.decode("utf-8"))
-        assert data["count"] == 5
+        self.assertEqual(data["count"], 5)
 
     @pytest.fixture()
     def load_energy_charts(self):
@@ -1306,9 +1323,9 @@ class TestChartApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCase):
         self.login(ADMIN_USERNAME)
         uri = f"api/v1/chart/?q={prison.dumps(arguments)}"
         rv = self.get_assert_metric(uri, "get_list")
-        assert rv.status_code == 200
+        self.assertEqual(rv.status_code, 200)
         data = json.loads(rv.data.decode("utf-8"))
-        assert data["count"] == 4
+        self.assertEqual(data["count"], 4)
 
         expected_response = [
             {"description": "ZY_bar", "slice_name": "foo_a", "viz_type": None},
@@ -1317,9 +1334,11 @@ class TestChartApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCase):
             {"description": "desc1", "slice_name": "zy_foo", "viz_type": None},
         ]
         for index, item in enumerate(data["result"]):
-            assert item["description"] == expected_response[index]["description"]
-            assert item["slice_name"] == expected_response[index]["slice_name"]
-            assert item["viz_type"] == expected_response[index]["viz_type"]
+            self.assertEqual(
+                item["description"], expected_response[index]["description"]
+            )
+            self.assertEqual(item["slice_name"], expected_response[index]["slice_name"])
+            self.assertEqual(item["viz_type"], expected_response[index]["viz_type"])
 
     @pytest.mark.usefixtures("load_energy_table_with_slice", "load_energy_charts")
     def test_admin_gets_filtered_energy_slices(self):
@@ -1371,9 +1390,9 @@ class TestChartApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCase):
 
         uri = f"api/v1/chart/?q={prison.dumps(arguments)}"
         rv = self.get_assert_metric(uri, "get_list")
-        assert rv.status_code == 200
+        self.assertEqual(rv.status_code, 200)
         data = json.loads(rv.data.decode("utf-8"))
-        assert data["count"] == CHARTS_FIXTURE_COUNT
+        self.assertEqual(data["count"], CHARTS_FIXTURE_COUNT)
 
     @pytest.mark.usefixtures("create_charts")
     def test_gets_not_certified_charts_filter(self):
@@ -1392,9 +1411,9 @@ class TestChartApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCase):
 
         uri = f"api/v1/chart/?q={prison.dumps(arguments)}"
         rv = self.get_assert_metric(uri, "get_list")
-        assert rv.status_code == 200
+        self.assertEqual(rv.status_code, 200)
         data = json.loads(rv.data.decode("utf-8"))
-        assert data["count"] == 17
+        self.assertEqual(data["count"], 17)
 
     @pytest.mark.usefixtures("load_energy_charts")
     def test_user_gets_none_filtered_energy_slices(self):
@@ -1414,9 +1433,9 @@ class TestChartApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCase):
         self.login(GAMMA_USERNAME)
         uri = f"api/v1/chart/?q={prison.dumps(arguments)}"
         rv = self.get_assert_metric(uri, "get_list")
-        assert rv.status_code == 200
+        self.assertEqual(rv.status_code, 200)
         data = json.loads(rv.data.decode("utf-8"))
-        assert data["count"] == 0
+        self.assertEqual(data["count"], 0)
 
     @pytest.mark.usefixtures("load_energy_charts")
     def test_user_gets_all_charts(self):
@@ -1426,12 +1445,12 @@ class TestChartApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCase):
         def count_charts():
             uri = "api/v1/chart/"
             rv = self.client.get(uri, "get_list")
-            assert rv.status_code == 200
+            self.assertEqual(rv.status_code, 200)
             data = rv.get_json()
             return data["count"]
 
         with self.temporary_user(gamma_user, login=True):
-            assert count_charts() == 0
+            self.assertEqual(count_charts(), 0)
 
         perm = ("all_database_access", "all_database_access")
         with self.temporary_user(gamma_user, extra_pvms=[perm], login=True):
@@ -1443,7 +1462,7 @@ class TestChartApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCase):
 
         # Back to normal
         with self.temporary_user(gamma_user, login=True):
-            assert count_charts() == 0
+            self.assertEqual(count_charts(), 0)
 
     @pytest.mark.usefixtures("create_charts")
     def test_get_charts_favorite_filter(self):
@@ -1626,7 +1645,7 @@ class TestChartApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCase):
         uri = f"api/v1/time_range/?q={prison.dumps(humanize_time_range)}"
         rv = self.client.get(uri)
         data = json.loads(rv.data.decode("utf-8"))
-        assert rv.status_code == 200
+        self.assertEqual(rv.status_code, 200)
         assert "since" in data["result"][0]
         assert "until" in data["result"][0]
         assert "timeRange" in data["result"][0]
@@ -1667,10 +1686,10 @@ class TestChartApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCase):
         uri = f"api/v1/form_data/?slice_id={slice.id if slice else None}"
         rv = self.client.get(uri)
         data = json.loads(rv.data.decode("utf-8"))
-        assert rv.status_code == 200
-        assert rv.content_type == "application/json"
+        self.assertEqual(rv.status_code, 200)
+        self.assertEqual(rv.content_type, "application/json")
         if slice:
-            assert data["slice_id"] == slice.id
+            self.assertEqual(data["slice_id"], slice.id)
 
     @pytest.mark.usefixtures(
         "load_unicode_dashboard_with_slice",
@@ -1687,16 +1706,16 @@ class TestChartApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCase):
         arguments = {"page_size": 10, "page": 0}
         uri = f"api/v1/chart/?q={prison.dumps(arguments)}"
         rv = self.client.get(uri)
-        assert rv.status_code == 200
+        self.assertEqual(rv.status_code, 200)
         data = json.loads(rv.data.decode("utf-8"))
-        assert len(data["result"]) == 10
+        self.assertEqual(len(data["result"]), 10)
 
         arguments = {"page_size": 10, "page": 3}
         uri = f"api/v1/chart/?q={prison.dumps(arguments)}"
         rv = self.get_assert_metric(uri, "get_list")
-        assert rv.status_code == 200
+        self.assertEqual(rv.status_code, 200)
         data = json.loads(rv.data.decode("utf-8"))
-        assert len(data["result"]) == 3
+        self.assertEqual(len(data["result"]), 3)
 
     def test_get_charts_no_data_access(self):
         """
@@ -1705,9 +1724,9 @@ class TestChartApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCase):
         self.login(GAMMA_USERNAME)
         uri = "api/v1/chart/"
         rv = self.get_assert_metric(uri, "get_list")
-        assert rv.status_code == 200
+        self.assertEqual(rv.status_code, 200)
         data = json.loads(rv.data.decode("utf-8"))
-        assert data["count"] == 0
+        self.assertEqual(data["count"], 0)
 
     def test_export_chart(self):
         """
@@ -1921,9 +1940,9 @@ class TestChartApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCase):
 
         uri = f"api/v1/chart/?q={prison.dumps(arguments)}"
         rv = self.get_assert_metric(uri, "get_list")
-        assert rv.status_code == 200
+        self.assertEqual(rv.status_code, 200)
         data = json.loads(rv.data.decode("utf-8"))
-        assert data["count"] == 8
+        self.assertEqual(data["count"], 8)
 
     def test_gets_not_created_by_user_charts_filter(self):
         arguments = {
@@ -1935,9 +1954,9 @@ class TestChartApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCase):
 
         uri = f"api/v1/chart/?q={prison.dumps(arguments)}"
         rv = self.get_assert_metric(uri, "get_list")
-        assert rv.status_code == 200
+        self.assertEqual(rv.status_code, 200)
         data = json.loads(rv.data.decode("utf-8"))
-        assert data["count"] == 8
+        self.assertEqual(data["count"], 8)
 
     @pytest.mark.usefixtures("create_charts")
     def test_gets_owned_created_favorited_by_me_filter(self):
@@ -1959,7 +1978,7 @@ class TestChartApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCase):
             "page_size": 25,
         }
         rv = self.client.get(f"api/v1/chart/?q={prison.dumps(arguments)}")
-        assert rv.status_code == 200
+        self.assertEqual(rv.status_code, 200)
         data = json.loads(rv.data.decode("utf-8"))
 
         assert data["result"][0]["slice_name"] == "name0"
@@ -1976,12 +1995,13 @@ class TestChartApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCase):
         self.login(ADMIN_USERNAME)
         slc = self.get_slice(slice_name)
         rv = self.client.put("/api/v1/chart/warm_up_cache", json={"chart_id": slc.id})
-        assert rv.status_code == 200
+        self.assertEqual(rv.status_code, 200)
         data = json.loads(rv.data.decode("utf-8"))
 
-        assert data["result"] == [
-            {"chart_id": slc.id, "viz_error": None, "viz_status": "success"}
-        ]
+        self.assertEqual(
+            data["result"],
+            [{"chart_id": slc.id, "viz_error": None, "viz_status": "success"}],
+        )
 
         dashboard = self.get_dash_by_slug("births")
 
@@ -1989,11 +2009,12 @@ class TestChartApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCase):
             "/api/v1/chart/warm_up_cache",
             json={"chart_id": slc.id, "dashboard_id": dashboard.id},
         )
-        assert rv.status_code == 200
+        self.assertEqual(rv.status_code, 200)
         data = json.loads(rv.data.decode("utf-8"))
-        assert data["result"] == [
-            {"chart_id": slc.id, "viz_error": None, "viz_status": "success"}
-        ]
+        self.assertEqual(
+            data["result"],
+            [{"chart_id": slc.id, "viz_error": None, "viz_status": "success"}],
+        )
 
         rv = self.client.put(
             "/api/v1/chart/warm_up_cache",
@@ -2005,25 +2026,29 @@ class TestChartApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCase):
                 ),
             },
         )
-        assert rv.status_code == 200
+        self.assertEqual(rv.status_code, 200)
         data = json.loads(rv.data.decode("utf-8"))
-        assert data["result"] == [
-            {"chart_id": slc.id, "viz_error": None, "viz_status": "success"}
-        ]
+        self.assertEqual(
+            data["result"],
+            [{"chart_id": slc.id, "viz_error": None, "viz_status": "success"}],
+        )
 
     def test_warm_up_cache_chart_id_required(self):
         self.login(ADMIN_USERNAME)
         rv = self.client.put("/api/v1/chart/warm_up_cache", json={"dashboard_id": 1})
-        assert rv.status_code == 400
+        self.assertEqual(rv.status_code, 400)
         data = json.loads(rv.data.decode("utf-8"))
-        assert data == {"message": {"chart_id": ["Missing data for required field."]}}
+        self.assertEqual(
+            data,
+            {"message": {"chart_id": ["Missing data for required field."]}},
+        )
 
     def test_warm_up_cache_chart_not_found(self):
         self.login(ADMIN_USERNAME)
         rv = self.client.put("/api/v1/chart/warm_up_cache", json={"chart_id": 99999})
-        assert rv.status_code == 404
+        self.assertEqual(rv.status_code, 404)
         data = json.loads(rv.data.decode("utf-8"))
-        assert data == {"message": "Chart not found"}
+        self.assertEqual(data, {"message": "Chart not found"})
 
     def test_warm_up_cache_payload_validation(self):
         self.login(ADMIN_USERNAME)
@@ -2031,15 +2056,18 @@ class TestChartApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCase):
             "/api/v1/chart/warm_up_cache",
             json={"chart_id": "id", "dashboard_id": "id", "extra_filters": 4},
         )
-        assert rv.status_code == 400
+        self.assertEqual(rv.status_code, 400)
         data = json.loads(rv.data.decode("utf-8"))
-        assert data == {
-            "message": {
-                "chart_id": ["Not a valid integer."],
-                "dashboard_id": ["Not a valid integer."],
-                "extra_filters": ["Not a valid string."],
-            }
-        }
+        self.assertEqual(
+            data,
+            {
+                "message": {
+                    "chart_id": ["Not a valid integer."],
+                    "dashboard_id": ["Not a valid integer."],
+                    "extra_filters": ["Not a valid string."],
+                }
+            },
+        )
 
     @pytest.mark.usefixtures("load_birth_names_dashboard_with_slices")
     def test_warm_up_cache_error(self) -> None:
@@ -2139,12 +2167,12 @@ class TestChartApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCase):
 
         uri = f"api/v1/chart/{chart.id}"
         rv = self.put_assert_metric(uri, update_payload, "put")
-        assert rv.status_code == 200
+        self.assertEqual(rv.status_code, 200)
         model = db.session.query(Slice).get(chart.id)
 
         # Clean up system tags
         tag_list = [tag.id for tag in model.tags if tag.type == TagType.custom]
-        assert tag_list == new_tags
+        self.assertEqual(tag_list, new_tags)
 
     @pytest.mark.usefixtures("create_chart_with_tag")
     def test_update_chart_remove_tags_can_write_on_tag(self):
@@ -2166,12 +2194,12 @@ class TestChartApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCase):
 
         uri = f"api/v1/chart/{chart.id}"
         rv = self.put_assert_metric(uri, update_payload, "put")
-        assert rv.status_code == 200
+        self.assertEqual(rv.status_code, 200)
         model = db.session.query(Slice).get(chart.id)
 
         # Clean up system tags
         tag_list = [tag.id for tag in model.tags if tag.type == TagType.custom]
-        assert tag_list == new_tags
+        self.assertEqual(tag_list, new_tags)
 
     @pytest.mark.usefixtures("create_chart_with_tag")
     def test_update_chart_add_tags_can_tag_on_chart(self):
@@ -2198,12 +2226,12 @@ class TestChartApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCase):
 
         uri = f"api/v1/chart/{chart.id}"
         rv = self.put_assert_metric(uri, update_payload, "put")
-        assert rv.status_code == 200
+        self.assertEqual(rv.status_code, 200)
         model = db.session.query(Slice).get(chart.id)
 
         # Clean up system tags
         tag_list = [tag.id for tag in model.tags if tag.type == TagType.custom]
-        assert tag_list == new_tags
+        self.assertEqual(tag_list, new_tags)
 
         security_manager.add_permission_role(alpha_role, write_tags_perm)
 
@@ -2228,12 +2256,12 @@ class TestChartApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCase):
 
         uri = f"api/v1/chart/{chart.id}"
         rv = self.put_assert_metric(uri, update_payload, "put")
-        assert rv.status_code == 200
+        self.assertEqual(rv.status_code, 200)
         model = db.session.query(Slice).get(chart.id)
 
         # Clean up system tags
         tag_list = [tag.id for tag in model.tags if tag.type == TagType.custom]
-        assert tag_list == []
+        self.assertEqual(tag_list, [])
 
         security_manager.add_permission_role(alpha_role, write_tags_perm)
 
@@ -2263,9 +2291,10 @@ class TestChartApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCase):
 
         uri = f"api/v1/chart/{chart.id}"
         rv = self.put_assert_metric(uri, update_payload, "put")
-        assert rv.status_code == 403
-        assert (
-            rv.json["message"] == "You do not have permission to manage tags on charts"
+        self.assertEqual(rv.status_code, 403)
+        self.assertEqual(
+            rv.json["message"],
+            "You do not have permission to manage tags on charts",
         )
 
         security_manager.add_permission_role(alpha_role, write_tags_perm)
@@ -2293,9 +2322,10 @@ class TestChartApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCase):
 
         uri = f"api/v1/chart/{chart.id}"
         rv = self.put_assert_metric(uri, update_payload, "put")
-        assert rv.status_code == 403
-        assert (
-            rv.json["message"] == "You do not have permission to manage tags on charts"
+        self.assertEqual(rv.status_code, 403)
+        self.assertEqual(
+            rv.json["message"],
+            "You do not have permission to manage tags on charts",
         )
 
         security_manager.add_permission_role(alpha_role, write_tags_perm)
@@ -2323,7 +2353,7 @@ class TestChartApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCase):
 
         uri = f"api/v1/chart/{chart.id}"
         rv = self.put_assert_metric(uri, update_payload, "put")
-        assert rv.status_code == 200
+        self.assertEqual(rv.status_code, 200)
 
         security_manager.add_permission_role(alpha_role, write_tags_perm)
         security_manager.add_permission_role(alpha_role, tag_charts_perm)
diff --git a/tests/integration_tests/charts/commands_tests.py b/tests/integration_tests/charts/commands_tests.py
index d66980585e..950c7cbc88 100644
--- a/tests/integration_tests/charts/commands_tests.py
+++ b/tests/integration_tests/charts/commands_tests.py
@@ -424,19 +424,15 @@ class TestChartWarmUpCacheCommand(SupersetTestCase):
     def test_warm_up_cache(self):
         slc = self.get_slice("Top 10 Girl Name Share")
         result = ChartWarmUpCacheCommand(slc.id, None, None).run()
-        assert result == {
-            "chart_id": slc.id,
-            "viz_error": None,
-            "viz_status": "success",
-        }
+        self.assertEqual(
+            result, {"chart_id": slc.id, "viz_error": None, "viz_status": "success"}
+        )
 
         # can just pass in chart as well
         result = ChartWarmUpCacheCommand(slc, None, None).run()
-        assert result == {
-            "chart_id": slc.id,
-            "viz_error": None,
-            "viz_status": "success",
-        }
+        self.assertEqual(
+            result, {"chart_id": slc.id, "viz_error": None, "viz_status": "success"}
+        )
 
 
 class TestFavoriteChartCommand(SupersetTestCase):
diff --git a/tests/integration_tests/charts/data/api_tests.py b/tests/integration_tests/charts/data/api_tests.py
index b922f16cb2..9aeac84cc5 100644
--- a/tests/integration_tests/charts/data/api_tests.py
+++ b/tests/integration_tests/charts/data/api_tests.py
@@ -471,16 +471,19 @@ class TestPostChartDataApi(BaseTestChartDataApi):
             "__time_origin": "now",
         }
         rv = self.post_assert_metric(CHART_DATA_URI, self.query_context_payload, "data")
-        assert rv.status_code == 200
+        self.assertEqual(rv.status_code, 200)
         data = json.loads(rv.data.decode("utf-8"))
-        assert data["result"][0]["applied_filters"] == [
-            {"column": "gender"},
-            {"column": "num"},
-            {"column": "name"},
-            {"column": "__time_range"},
-        ]
+        self.assertEqual(
+            data["result"][0]["applied_filters"],
+            [
+                {"column": "gender"},
+                {"column": "num"},
+                {"column": "name"},
+                {"column": "__time_range"},
+            ],
+        )
         expected_row_count = self.get_expected_row_count("client_id_2")
-        assert data["result"][0]["rowcount"] == expected_row_count
+        self.assertEqual(data["result"][0]["rowcount"], expected_row_count)
 
     @pytest.mark.usefixtures("load_birth_names_dashboard_with_slices")
     def test_with_in_op_filter__data_is_returned(self):
@@ -530,7 +533,7 @@ class TestPostChartDataApi(BaseTestChartDataApi):
                 dttm_col.type,
                 dttm,
             )
-            assert dttm_expression in result["query"]
+            self.assertIn(dttm_expression, result["query"])
         else:
             raise Exception("ds column not found")
 
@@ -560,16 +563,16 @@ class TestPostChartDataApi(BaseTestChartDataApi):
             }
         ]
         rv = self.post_assert_metric(CHART_DATA_URI, self.query_context_payload, "data")
-        assert rv.status_code == 200
+        self.assertEqual(rv.status_code, 200)
         response_payload = json.loads(rv.data.decode("utf-8"))
         result = response_payload["result"][0]
         row = result["data"][0]
-        assert "__timestamp" in row
-        assert "sum__num" in row
-        assert "sum__num__yhat" in row
-        assert "sum__num__yhat_upper" in row
-        assert "sum__num__yhat_lower" in row
-        assert result["rowcount"] == 103
+        self.assertIn("__timestamp", row)
+        self.assertIn("sum__num", row)
+        self.assertIn("sum__num__yhat", row)
+        self.assertIn("sum__num__yhat_upper", row)
+        self.assertIn("sum__num__yhat_lower", row)
+        self.assertEqual(result["rowcount"], 103)
 
     @pytest.mark.usefixtures("load_birth_names_dashboard_with_slices")
     def test_chart_data_invalid_post_processing(self):
@@ -727,11 +730,11 @@ class TestPostChartDataApi(BaseTestChartDataApi):
         time.sleep(1)
         rv = self.post_assert_metric(CHART_DATA_URI, self.query_context_payload, "data")
         time.sleep(1)
-        assert rv.status_code == 202
+        self.assertEqual(rv.status_code, 202)
         time.sleep(1)
         data = json.loads(rv.data.decode("utf-8"))
         keys = list(data.keys())
-        self.assertCountEqual(  # noqa: PT009
+        self.assertCountEqual(
             keys, ["channel_id", "job_id", "user_id", "status", "errors", "result_url"]
         )
 
@@ -761,10 +764,10 @@ class TestPostChartDataApi(BaseTestChartDataApi):
             rv = self.post_assert_metric(
                 CHART_DATA_URI, self.query_context_payload, "data"
             )
-            assert rv.status_code == 200
+            self.assertEqual(rv.status_code, 200)
             data = json.loads(rv.data.decode("utf-8"))
             patched_run.assert_called_once_with(force_cached=True)
-            assert data == {"result": [{"query": "select * from foo"}]}
+            self.assertEqual(data, {"result": [{"query": "select * from foo"}]})
 
     @with_feature_flags(GLOBAL_ASYNC_QUERIES=True)
     @pytest.mark.usefixtures("load_birth_names_dashboard_with_slices")
@@ -776,7 +779,7 @@ class TestPostChartDataApi(BaseTestChartDataApi):
         async_query_manager_factory.init_app(app)
         self.query_context_payload["result_type"] = "results"
         rv = self.post_assert_metric(CHART_DATA_URI, self.query_context_payload, "data")
-        assert rv.status_code == 200
+        self.assertEqual(rv.status_code, 200)
 
     @with_feature_flags(GLOBAL_ASYNC_QUERIES=True)
     @pytest.mark.usefixtures("load_birth_names_dashboard_with_slices")
@@ -790,7 +793,7 @@ class TestPostChartDataApi(BaseTestChartDataApi):
             app.config["GLOBAL_ASYNC_QUERIES_JWT_COOKIE_NAME"], "foo"
         )
         rv = test_client.post(CHART_DATA_URI, json=self.query_context_payload)
-        assert rv.status_code == 401
+        self.assertEqual(rv.status_code, 401)
 
     @pytest.mark.usefixtures("load_birth_names_dashboard_with_slices")
     def test_chart_data_rowcount(self):
@@ -843,8 +846,10 @@ class TestPostChartDataApi(BaseTestChartDataApi):
 
         unique_names = {row["name"] for row in data}
         self.maxDiff = None
-        assert len(unique_names) == SERIES_LIMIT
-        assert {column for column in data[0].keys()} == {"state", "name", "sum__num"}
+        self.assertEqual(len(unique_names), SERIES_LIMIT)
+        self.assertEqual(
+            {column for column in data[0].keys()}, {"state", "name", "sum__num"}
+        )
 
     @pytest.mark.usefixtures(
         "create_annotation_layers", "load_birth_names_dashboard_with_slices"
@@ -883,10 +888,10 @@ class TestPostChartDataApi(BaseTestChartDataApi):
         annotation_layers.append(event)
 
         rv = self.post_assert_metric(CHART_DATA_URI, self.query_context_payload, "data")
-        assert rv.status_code == 200
+        self.assertEqual(rv.status_code, 200)
         data = json.loads(rv.data.decode("utf-8"))
         # response should only contain interval and event data, not formula
-        assert len(data["result"][0]["annotation_data"]) == 2
+        self.assertEqual(len(data["result"][0]["annotation_data"]), 2)
 
     @pytest.mark.usefixtures("load_birth_names_dashboard_with_slices")
     def test_with_virtual_table_with_colons_as_datasource(self):
@@ -1179,8 +1184,8 @@ class TestGetChartDataApi(BaseTestChartDataApi):
             data = json.loads(rv.data.decode("utf-8"))
 
         expected_row_count = self.get_expected_row_count("client_id_3")
-        assert rv.status_code == 200
-        assert data["result"][0]["rowcount"] == expected_row_count
+        self.assertEqual(rv.status_code, 200)
+        self.assertEqual(data["result"][0]["rowcount"], expected_row_count)
 
     @with_feature_flags(GLOBAL_ASYNC_QUERIES=True)
     @mock.patch("superset.charts.data.api.QueryContextCacheLoader")
@@ -1197,8 +1202,8 @@ class TestGetChartDataApi(BaseTestChartDataApi):
         )
         data = json.loads(rv.data.decode("utf-8"))
 
-        assert rv.status_code == 422
-        assert data["message"] == "Error loading data from cache"
+        self.assertEqual(rv.status_code, 422)
+        self.assertEqual(data["message"], "Error loading data from cache")
 
     @with_feature_flags(GLOBAL_ASYNC_QUERIES=True)
     @mock.patch("superset.charts.data.api.QueryContextCacheLoader")
@@ -1226,7 +1231,7 @@ class TestGetChartDataApi(BaseTestChartDataApi):
                 f"{CHART_DATA_URI}/test-cache-key",
             )
 
-        assert rv.status_code == 401
+        self.assertEqual(rv.status_code, 401)
 
     @with_feature_flags(GLOBAL_ASYNC_QUERIES=True)
     def test_chart_data_cache_key_error(self):
@@ -1239,7 +1244,7 @@ class TestGetChartDataApi(BaseTestChartDataApi):
             f"{CHART_DATA_URI}/test-cache-key", "data_from_cache"
         )
 
-        assert rv.status_code == 404
+        self.assertEqual(rv.status_code, 404)
 
     @pytest.mark.usefixtures("load_birth_names_dashboard_with_slices")
     def test_chart_data_with_adhoc_column(self):
diff --git a/tests/integration_tests/charts/schema_tests.py b/tests/integration_tests/charts/schema_tests.py
index 46ba792e52..8f74d9de29 100644
--- a/tests/integration_tests/charts/schema_tests.py
+++ b/tests/integration_tests/charts/schema_tests.py
@@ -46,8 +46,8 @@ class TestSchema(SupersetTestCase):
         payload["queries"][0]["row_offset"] = -1
         with self.assertRaises(ValidationError) as context:
             _ = ChartDataQueryContextSchema().load(payload)
-        assert "row_limit" in context.exception.messages["queries"][0]
-        assert "row_offset" in context.exception.messages["queries"][0]
+        self.assertIn("row_limit", context.exception.messages["queries"][0])
+        self.assertIn("row_offset", context.exception.messages["queries"][0])
 
     @pytest.mark.usefixtures("load_birth_names_dashboard_with_slices")
     def test_query_context_null_timegrain(self):
diff --git a/tests/integration_tests/core_tests.py b/tests/integration_tests/core_tests.py
index 4f989611b2..44b7ef26e6 100644
--- a/tests/integration_tests/core_tests.py
+++ b/tests/integration_tests/core_tests.py
@@ -114,15 +114,15 @@ class TestCore(SupersetTestCase):
 
     def test_login(self):
         resp = self.get_resp("/login/", data=dict(username="admin", password="general"))
-        assert "User confirmation needed" not in resp
+        self.assertNotIn("User confirmation needed", resp)
 
         resp = self.get_resp("/logout/", follow_redirects=True)
-        assert "User confirmation needed" in resp
+        self.assertIn("User confirmation needed", resp)
 
         resp = self.get_resp(
             "/login/", data=dict(username="admin", password="wrongPassword")
         )
-        assert "User confirmation needed" in resp
+        self.assertIn("User confirmation needed", resp)
 
     def test_dashboard_endpoint(self):
         self.login(ADMIN_USERNAME)
@@ -146,17 +146,20 @@ class TestCore(SupersetTestCase):
 
         qobj["groupby"] = []
         cache_key_with_groupby = viz.cache_key(qobj)
-        assert cache_key != cache_key_with_groupby
+        self.assertNotEqual(cache_key, cache_key_with_groupby)
 
-        assert viz.cache_key(qobj) != viz.cache_key(qobj, time_compare="12 weeks")
+        self.assertNotEqual(
+            viz.cache_key(qobj), viz.cache_key(qobj, time_compare="12 weeks")
+        )
 
-        assert viz.cache_key(qobj, time_compare="28 days") != viz.cache_key(
-            qobj, time_compare="12 weeks"
+        self.assertNotEqual(
+            viz.cache_key(qobj, time_compare="28 days"),
+            viz.cache_key(qobj, time_compare="12 weeks"),
         )
 
         qobj["inner_from_dttm"] = datetime.datetime(1901, 1, 1)
 
-        assert cache_key_with_groupby == viz.cache_key(qobj)
+        self.assertEqual(cache_key_with_groupby, viz.cache_key(qobj))
 
     def test_admin_only_menu_views(self):
         def assert_admin_view_menus_in(role_name, assert_func):
@@ -202,9 +205,9 @@ class TestCore(SupersetTestCase):
         new_slice_id = resp.json["form_data"]["slice_id"]
         slc = db.session.query(Slice).filter_by(id=new_slice_id).one()
 
-        assert slc.slice_name == copy_name
+        self.assertEqual(slc.slice_name, copy_name)
         form_data.pop("slice_id")  # We don't save the slice id when saving as
-        assert slc.viz.form_data == form_data
+        self.assertEqual(slc.viz.form_data, form_data)
 
         form_data = {
             "adhoc_filters": [],
@@ -221,8 +224,8 @@ class TestCore(SupersetTestCase):
             data={"form_data": json.dumps(form_data)},
         )
         slc = db.session.query(Slice).filter_by(id=new_slice_id).one()
-        assert slc.slice_name == new_slice_name
-        assert slc.viz.form_data == form_data
+        self.assertEqual(slc.slice_name, new_slice_name)
+        self.assertEqual(slc.viz.form_data, form_data)
 
         # Cleanup
         slices = (
@@ -258,21 +261,21 @@ class TestCore(SupersetTestCase):
             logger.info(f"[{name}]/[{method}]: {url}")
             print(f"[{name}]/[{method}]: {url}")
             resp = self.client.get(url)
-            assert resp.status_code == 200
+            self.assertEqual(resp.status_code, 200)
 
     def test_add_slice(self):
         self.login(ADMIN_USERNAME)
         # assert that /chart/add responds with 200
         url = "/chart/add"
         resp = self.client.get(url)
-        assert resp.status_code == 200
+        self.assertEqual(resp.status_code, 200)
 
     def test_get_user_slices(self):
         self.login(ADMIN_USERNAME)
         userid = security_manager.find_user("admin").id
         url = f"/sliceasync/api/read?_flt_0_created_by={userid}"
         resp = self.client.get(url)
-        assert resp.status_code == 200
+        self.assertEqual(resp.status_code, 200)
 
     @pytest.mark.usefixtures("load_energy_table_with_slice")
     def test_slices_V2(self):
@@ -336,7 +339,7 @@ class TestCore(SupersetTestCase):
         data["sqlalchemy_uri"] = database.safe_sqlalchemy_uri()
         self.client.post(url, data=data)
         database = superset.utils.database.get_example_database()
-        assert sqlalchemy_uri_decrypted == database.sqlalchemy_uri_decrypted
+        self.assertEqual(sqlalchemy_uri_decrypted, database.sqlalchemy_uri_decrypted)
 
         # Need to clean up after ourselves
         database.impersonate_user = False
@@ -352,9 +355,9 @@ class TestCore(SupersetTestCase):
         self.login(ADMIN_USERNAME)
         slc = self.get_slice("Top 10 Girl Name Share")
         data = self.get_json_resp(f"/superset/warm_up_cache?slice_id={slc.id}")
-        assert data == [
-            {"slice_id": slc.id, "viz_error": None, "viz_status": "success"}
-        ]
+        self.assertEqual(
+            data, [{"slice_id": slc.id, "viz_error": None, "viz_status": "success"}]
+        )
 
         data = self.get_json_resp(
             "/superset/warm_up_cache?table_name=energy_usage&db_name=main"
@@ -412,29 +415,29 @@ class TestCore(SupersetTestCase):
         self.login(ADMIN_USERNAME)
 
         resp = self.client.get("/kv/10001/")
-        assert 404 == resp.status_code
+        self.assertEqual(404, resp.status_code)
 
         value = json.dumps({"data": "this is a test"})
         resp = self.client.post("/kv/store/", data=dict(data=value))
-        assert resp.status_code == 404
+        self.assertEqual(resp.status_code, 404)
 
     @with_feature_flags(KV_STORE=True)
     def test_kv_enabled(self):
         self.login(ADMIN_USERNAME)
 
         resp = self.client.get("/kv/10001/")
-        assert 404 == resp.status_code
+        self.assertEqual(404, resp.status_code)
 
         value = json.dumps({"data": "this is a test"})
         resp = self.client.post("/kv/store/", data=dict(data=value))
-        assert resp.status_code == 200
+        self.assertEqual(resp.status_code, 200)
         kv = db.session.query(models.KeyValue).first()
         kv_value = kv.value
-        assert json.loads(value) == json.loads(kv_value)
+        self.assertEqual(json.loads(value), json.loads(kv_value))
 
         resp = self.client.get(f"/kv/{kv.id}/")
-        assert resp.status_code == 200
-        assert json.loads(value) == json.loads(resp.data.decode("utf-8"))
+        self.assertEqual(resp.status_code, 200)
+        self.assertEqual(json.loads(value), json.loads(resp.data.decode("utf-8")))
 
     def test_gamma(self):
         self.login(GAMMA_USERNAME)
@@ -448,7 +451,7 @@ class TestCore(SupersetTestCase):
         self.login(ADMIN_USERNAME)
         sql = "SELECT '{{ 1+1 }}' as test"
         data = self.run_sql(sql, "fdaklj3ws")
-        assert data["data"][0]["test"] == "2"
+        self.assertEqual(data["data"][0]["test"], "2")
 
     def test_fetch_datasource_metadata(self):
         self.login(ADMIN_USERNAME)
@@ -463,7 +466,7 @@ class TestCore(SupersetTestCase):
             "id",
         ]
         for k in keys:
-            assert k in resp.keys()
+            self.assertIn(k, resp.keys())
 
     @pytest.mark.usefixtures("load_birth_names_dashboard_with_slices")
     def test_slice_id_is_always_logged_correctly_on_web_request(self):
@@ -472,7 +475,7 @@ class TestCore(SupersetTestCase):
         slc = db.session.query(Slice).filter_by(slice_name="Girls").one()
         qry = db.session.query(models.Log).filter_by(slice_id=slc.id)
         self.get_resp(slc.slice_url)
-        assert 1 == qry.count()
+        self.assertEqual(1, qry.count())
 
     def create_sample_csvfile(self, filename: str, content: list[str]) -> None:
         with open(filename, "w+") as test_file:
@@ -487,7 +490,7 @@ class TestCore(SupersetTestCase):
         database.allow_file_upload = True
         db.session.commit()
         add_datasource_page = self.get_resp("/databaseview/list/")
-        assert "Upload a CSV" in add_datasource_page
+        self.assertIn("Upload a CSV", add_datasource_page)
 
     def test_dataframe_timezone(self):
         tz = pytz.FixedOffset(60)
@@ -499,15 +502,15 @@ class TestCore(SupersetTestCase):
         df = results.to_pandas_df()
         data = dataframe.df_to_records(df)
         json_str = json.dumps(data, default=json.pessimistic_json_iso_dttm_ser)
-        self.assertDictEqual(  # noqa: PT009
+        self.assertDictEqual(
             data[0], {"data": pd.Timestamp("2017-11-18 21:53:00.219225+0100", tz=tz)}
         )
-        self.assertDictEqual(  # noqa: PT009
+        self.assertDictEqual(
             data[1], {"data": pd.Timestamp("2017-11-18 22:06:30+0100", tz=tz)}
         )
-        assert (
-            json_str
-            == '[{"data": "2017-11-18T21:53:00.219225+01:00"}, {"data": "2017-11-18T22:06:30+01:00"}]'
+        self.assertEqual(
+            json_str,
+            '[{"data": "2017-11-18T21:53:00.219225+01:00"}, {"data": "2017-11-18T22:06:30+01:00"}]',
         )
 
     def test_mssql_engine_spec_pymssql(self):
@@ -521,12 +524,11 @@ class TestCore(SupersetTestCase):
         )
         df = results.to_pandas_df()
         data = dataframe.df_to_records(df)
-        assert len(data) == 2
-        assert data[0] == {
-            "col1": 1,
-            "col2": 1,
-            "col3": pd.Timestamp("2017-10-19 23:39:16.660000"),
-        }
+        self.assertEqual(len(data), 2)
+        self.assertEqual(
+            data[0],
+            {"col1": 1, "col2": 1, "col3": pd.Timestamp("2017-10-19 23:39:16.660000")},
+        )
 
     def test_comments_in_sqlatable_query(self):
         clean_query = "SELECT\n  '/* val 1 */' AS c1,\n  '-- val 2' AS c2\nFROM tbl"
@@ -552,9 +554,9 @@ class TestCore(SupersetTestCase):
         )
         data = json.loads(rv.data.decode("utf-8"))
 
-        assert (
-            data["errors"][0]["message"]
-            == "The dataset associated with this chart no longer exists"
+        self.assertEqual(
+            data["errors"][0]["message"],
+            "The dataset associated with this chart no longer exists",
         )
 
     @pytest.mark.usefixtures("load_birth_names_dashboard_with_slices")
@@ -577,8 +579,8 @@ class TestCore(SupersetTestCase):
         )
         data = json.loads(rv.data.decode("utf-8"))
 
-        assert rv.status_code == 200
-        assert data["rowcount"] == 2
+        self.assertEqual(rv.status_code, 200)
+        self.assertEqual(data["rowcount"], 2)
 
     @pytest.mark.usefixtures("load_birth_names_dashboard_with_slices")
     def test_explore_json_dist_bar_order(self):
@@ -739,7 +741,7 @@ class TestCore(SupersetTestCase):
             "/superset/explore_json/?results=true",
             data={"form_data": json.dumps(form_data)},
         )
-        assert rv.status_code == 200
+        self.assertEqual(rv.status_code, 200)
 
     @pytest.mark.usefixtures("load_birth_names_dashboard_with_slices")
     @mock.patch(
@@ -778,8 +780,8 @@ class TestCore(SupersetTestCase):
         rv = self.client.get("/superset/explore_json/data/valid-cache-key")
         data = json.loads(rv.data.decode("utf-8"))
 
-        assert rv.status_code == 200
-        assert data["rowcount"] == 2
+        self.assertEqual(rv.status_code, 200)
+        self.assertEqual(data["rowcount"], 2)
 
     @mock.patch(
         "superset.utils.cache_manager.CacheManager.cache",
@@ -812,7 +814,7 @@ class TestCore(SupersetTestCase):
         mock_cache.return_value = MockCache()
 
         rv = self.client.get("/superset/explore_json/data/valid-cache-key")
-        assert rv.status_code == 403
+        self.assertEqual(rv.status_code, 403)
 
     def test_explore_json_data_invalid_cache_key(self):
         self.login(ADMIN_USERNAME)
@@ -820,8 +822,8 @@ class TestCore(SupersetTestCase):
         rv = self.client.get(f"/superset/explore_json/data/{cache_key}")
         data = json.loads(rv.data.decode("utf-8"))
 
-        assert rv.status_code == 404
-        assert data["error"] == "Cached data not found"
+        self.assertEqual(rv.status_code, 404)
+        self.assertEqual(data["error"], "Cached data not found")
 
     def test_results_default_deserialization(self):
         use_new_deserialization = False
@@ -861,14 +863,14 @@ class TestCore(SupersetTestCase):
         serialized_payload = sql_lab._serialize_payload(
             payload, use_new_deserialization
         )
-        assert isinstance(serialized_payload, str)
+        self.assertIsInstance(serialized_payload, str)
 
         query_mock = mock.Mock()
         deserialized_payload = superset.views.utils._deserialize_results_payload(
             serialized_payload, query_mock, use_new_deserialization
         )
 
-        self.assertDictEqual(deserialized_payload, payload)  # noqa: PT009
+        self.assertDictEqual(deserialized_payload, payload)
         query_mock.assert_not_called()
 
     def test_results_msgpack_deserialization(self):
@@ -909,7 +911,7 @@ class TestCore(SupersetTestCase):
         serialized_payload = sql_lab._serialize_payload(
             payload, use_new_deserialization
         )
-        assert isinstance(serialized_payload, bytes)
+        self.assertIsInstance(serialized_payload, bytes)
 
         with mock.patch.object(
             db_engine_spec, "expand_data", wraps=db_engine_spec.expand_data
@@ -923,7 +925,7 @@ class TestCore(SupersetTestCase):
             df = results.to_pandas_df()
             payload["data"] = dataframe.df_to_records(df)
 
-            self.assertDictEqual(deserialized_payload, payload)  # noqa: PT009
+            self.assertDictEqual(deserialized_payload, payload)
             expand_data.assert_called_once()
 
     @mock.patch.dict(
@@ -958,7 +960,7 @@ class TestCore(SupersetTestCase):
         ]
         for url in urls:
             data = self.get_resp(url)
-            assert html_string in data
+            self.assertTrue(html_string in data)
 
     @mock.patch.dict(
         "superset.extensions.feature_flag_manager._feature_flags",
@@ -989,7 +991,7 @@ class TestCore(SupersetTestCase):
         tab_state_id = resp["id"]
         payload = self.get_json_resp(f"/tabstateview/{tab_state_id}")
 
-        assert payload["label"] == "Untitled Query foo"
+        self.assertEqual(payload["label"], "Untitled Query foo")
 
     def test_tabstate_update(self):
         self.login(ADMIN_USERNAME)
@@ -1012,87 +1014,87 @@ class TestCore(SupersetTestCase):
         client_id = "asdfasdf"
         data = {"sql": json.dumps("select 1"), "latest_query_id": json.dumps(client_id)}
         response = self.client.put(f"/tabstateview/{tab_state_id}", data=data)
-        assert response.status_code == 400
-        assert response.json["error"] == "Bad request"
+        self.assertEqual(response.status_code, 400)
+        self.assertEqual(response.json["error"], "Bad request")
         # generate query
         db.session.add(Query(client_id=client_id, database_id=1))
         db.session.commit()
         # update tab state with a valid client_id
         response = self.client.put(f"/tabstateview/{tab_state_id}", data=data)
-        assert response.status_code == 200
+        self.assertEqual(response.status_code, 200)
         # nulls should be ok too
         data["latest_query_id"] = "null"
         response = self.client.put(f"/tabstateview/{tab_state_id}", data=data)
-        assert response.status_code == 200
+        self.assertEqual(response.status_code, 200)
 
     def test_virtual_table_explore_visibility(self):
         # test that default visibility it set to True
         database = superset.utils.database.get_example_database()
-        assert database.allows_virtual_table_explore is True
+        self.assertEqual(database.allows_virtual_table_explore, True)
 
         # test that visibility is disabled when extra is set to False
         extra = database.get_extra()
         extra["allows_virtual_table_explore"] = False
         database.extra = json.dumps(extra)
-        assert database.allows_virtual_table_explore is False
+        self.assertEqual(database.allows_virtual_table_explore, False)
 
         # test that visibility is enabled when extra is set to True
         extra = database.get_extra()
         extra["allows_virtual_table_explore"] = True
         database.extra = json.dumps(extra)
-        assert database.allows_virtual_table_explore is True
+        self.assertEqual(database.allows_virtual_table_explore, True)
 
         # test that visibility is not broken with bad values
         extra = database.get_extra()
         extra["allows_virtual_table_explore"] = "trash value"
         database.extra = json.dumps(extra)
-        assert database.allows_virtual_table_explore is True
+        self.assertEqual(database.allows_virtual_table_explore, True)
 
     def test_data_preview_visibility(self):
         # test that default visibility is allowed
         database = utils.get_example_database()
-        assert database.disable_data_preview is False
+        self.assertEqual(database.disable_data_preview, False)
 
         # test that visibility is disabled when extra is set to true
         extra = database.get_extra()
         extra["disable_data_preview"] = True
         database.extra = json.dumps(extra)
-        assert database.disable_data_preview is True
+        self.assertEqual(database.disable_data_preview, True)
 
         # test that visibility is enabled when extra is set to false
         extra = database.get_extra()
         extra["disable_data_preview"] = False
         database.extra = json.dumps(extra)
-        assert database.disable_data_preview is False
+        self.assertEqual(database.disable_data_preview, False)
 
         # test that visibility is not broken with bad values
         extra = database.get_extra()
         extra["disable_data_preview"] = "trash value"
         database.extra = json.dumps(extra)
-        assert database.disable_data_preview is False
+        self.assertEqual(database.disable_data_preview, False)
 
     def test_disable_drill_to_detail(self):
         # test that disable_drill_to_detail is False by default
         database = utils.get_example_database()
-        assert database.disable_drill_to_detail is False
+        self.assertEqual(database.disable_drill_to_detail, False)
 
         # test that disable_drill_to_detail can be set to True
         extra = database.get_extra()
         extra["disable_drill_to_detail"] = True
         database.extra = json.dumps(extra)
-        assert database.disable_drill_to_detail is True
+        self.assertEqual(database.disable_drill_to_detail, True)
 
         # test that disable_drill_to_detail can be set to False
         extra = database.get_extra()
         extra["disable_drill_to_detail"] = False
         database.extra = json.dumps(extra)
-        assert database.disable_drill_to_detail is False
+        self.assertEqual(database.disable_drill_to_detail, False)
 
         # test that disable_drill_to_detail is not broken with bad values
         extra = database.get_extra()
         extra["disable_drill_to_detail"] = "trash value"
         database.extra = json.dumps(extra)
-        assert database.disable_drill_to_detail is False
+        self.assertEqual(database.disable_drill_to_detail, False)
 
     def test_explore_database_id(self):
         database = superset.utils.database.get_example_database()
@@ -1100,13 +1102,13 @@ class TestCore(SupersetTestCase):
 
         # test that explore_database_id is the regular database
         # id if none is set in the extra
-        assert database.explore_database_id == database.id
+        self.assertEqual(database.explore_database_id, database.id)
 
         # test that explore_database_id is correct if the extra is set
         extra = database.get_extra()
         extra["explore_database_id"] = explore_database.id
         database.extra = json.dumps(extra)
-        assert database.explore_database_id == explore_database.id
+        self.assertEqual(database.explore_database_id, explore_database.id)
 
     def test_get_column_names_from_metric(self):
         simple_metric = {
@@ -1144,7 +1146,7 @@ class TestCore(SupersetTestCase):
 
         self.login(ADMIN_USERNAME)
         data = self.get_resp(url)
-        assert "Error message" in data
+        self.assertIn("Error message", data)
 
         # Assert we can handle a driver exception at the mutator level
         exception = SQLAlchemyError("Error message")
@@ -1154,7 +1156,7 @@ class TestCore(SupersetTestCase):
 
         self.login(ADMIN_USERNAME)
         data = self.get_resp(url)
-        assert "Error message" in data
+        self.assertIn("Error message", data)
 
     @pytest.mark.skip(
         "TODO This test was wrong - 'Error message' was in the language pack"
@@ -1174,7 +1176,7 @@ class TestCore(SupersetTestCase):
 
         self.login(ADMIN_USERNAME)
         data = self.get_resp(url)
-        assert "Error message" in data
+        self.assertIn("Error message", data)
 
         # Assert we can handle a driver exception at the mutator level
         exception = SQLAlchemyError("Error message")
@@ -1184,7 +1186,7 @@ class TestCore(SupersetTestCase):
 
         self.login(ADMIN_USERNAME)
         data = self.get_resp(url)
-        assert "Error message" in data
+        self.assertIn("Error message", data)
 
     @pytest.mark.usefixtures("load_energy_table_with_slice")
     @mock.patch("superset.commands.explore.form_data.create.CreateFormDataCommand.run")
@@ -1198,7 +1200,9 @@ class TestCore(SupersetTestCase):
         rv = self.client.get(
             f"/superset/explore/?form_data={quote(json.dumps(form_data))}"
         )
-        assert rv.headers["Location"] == f"/explore/?form_data_key={random_key}"
+        self.assertEqual(
+            rv.headers["Location"], f"/explore/?form_data_key={random_key}"
+        )
 
     @pytest.mark.usefixtures("load_birth_names_dashboard_with_slices")
     def test_has_table(self):
@@ -1219,7 +1223,7 @@ class TestCore(SupersetTestCase):
 
         expected_url = "/superset/dashboard/1?permalink_key=123&standalone=3"
 
-        assert resp.headers["Location"] == expected_url
+        self.assertEqual(resp.headers["Location"], expected_url)
         assert resp.status_code == 302
 
 
diff --git a/tests/integration_tests/dashboard_tests.py b/tests/integration_tests/dashboard_tests.py
index 021fa0e1b5..bee8de7a5e 100644
--- a/tests/integration_tests/dashboard_tests.py
+++ b/tests/integration_tests/dashboard_tests.py
@@ -117,7 +117,7 @@ class TestDashboard(SupersetTestCase):
         url = "/dashboard/new/"
         response = self.client.get(url, follow_redirects=False)
         dash_count_after = db.session.query(func.count(Dashboard.id)).first()[0]
-        assert dash_count_before + 1 == dash_count_after
+        self.assertEqual(dash_count_before + 1, dash_count_after)
         group = re.match(
             r"\/superset\/dashboard\/([0-9]*)\/\?edit=true",
             response.headers["Location"],
@@ -145,25 +145,25 @@ class TestDashboard(SupersetTestCase):
         self.logout()
 
         resp = self.get_resp("/api/v1/chart/")
-        assert "birth_names" not in resp
+        self.assertNotIn("birth_names", resp)
 
         resp = self.get_resp("/api/v1/dashboard/")
-        assert "/superset/dashboard/births/" not in resp
+        self.assertNotIn("/superset/dashboard/births/", resp)
 
         self.grant_public_access_to_table(table)
 
         # Try access after adding appropriate permissions.
-        assert "birth_names" in self.get_resp("/api/v1/chart/")
+        self.assertIn("birth_names", self.get_resp("/api/v1/chart/"))
 
         resp = self.get_resp("/api/v1/dashboard/")
-        assert "/superset/dashboard/births/" in resp
+        self.assertIn("/superset/dashboard/births/", resp)
 
         # Confirm that public doesn't have access to other datasets.
         resp = self.get_resp("/api/v1/chart/")
-        assert "wb_health_population" not in resp
+        self.assertNotIn("wb_health_population", resp)
 
         resp = self.get_resp("/api/v1/dashboard/")
-        assert "/superset/dashboard/world_health/" not in resp
+        self.assertNotIn("/superset/dashboard/world_health/", resp)
 
         # Cleanup
         self.revoke_public_access_to_table(table)
@@ -224,8 +224,8 @@ class TestDashboard(SupersetTestCase):
         db.session.delete(hidden_dash)
         db.session.commit()
 
-        assert f"/superset/dashboard/{my_dash_slug}/" in resp
-        assert f"/superset/dashboard/{not_my_dash_slug}/" not in resp
+        self.assertIn(f"/superset/dashboard/{my_dash_slug}/", resp)
+        self.assertNotIn(f"/superset/dashboard/{not_my_dash_slug}/", resp)
 
     def test_user_can_not_view_unpublished_dash(self):
         admin_user = security_manager.find_user("admin")
@@ -247,7 +247,7 @@ class TestDashboard(SupersetTestCase):
         db.session.delete(dash)
         db.session.commit()
 
-        assert f"/superset/dashboard/{slug}/" not in resp
+        self.assertNotIn(f"/superset/dashboard/{slug}/", resp)
 
 
 if __name__ == "__main__":
diff --git a/tests/integration_tests/dashboards/api_tests.py b/tests/integration_tests/dashboards/api_tests.py
index f88bdccc0e..05b51b3569 100644
--- a/tests/integration_tests/dashboards/api_tests.py
+++ b/tests/integration_tests/dashboards/api_tests.py
@@ -85,6 +85,15 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
         "published": False,
     }
 
+    dashboard_put_filters_data = {
+        "modified": [
+            {"id": "native_filter_1", "name": "Filter 1"},
+            {"id": "native_filter_2", "name": "Filter 2"},
+        ],
+        "deleted": [],
+        "reordered": [],
+    }
+
     @pytest.fixture()
     def create_dashboards(self):
         with self.create_app().app_context():
@@ -275,15 +284,15 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
         self.login(ADMIN_USERNAME)
         uri = "api/v1/dashboard/world_health/datasets"
         response = self.get_assert_metric(uri, "get_datasets")
-        assert response.status_code == 200
+        self.assertEqual(response.status_code, 200)
         data = json.loads(response.data.decode("utf-8"))
         dashboard = Dashboard.get("world_health")
         expected_dataset_ids = {s.datasource_id for s in dashboard.slices}
         result = data["result"]
         actual_dataset_ids = {dataset["id"] for dataset in result}
-        assert actual_dataset_ids == expected_dataset_ids
+        self.assertEqual(actual_dataset_ids, expected_dataset_ids)
         expected_values = [0, 1] if backend() == "presto" else [0, 1, 2]
-        assert result[0]["column_types"] == expected_values
+        self.assertEqual(result[0]["column_types"], expected_values)
         logger_mock.warning.assert_not_called()
 
     @pytest.mark.usefixtures("load_world_bank_dashboard_with_slices")
@@ -293,13 +302,13 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
         self.login(ADMIN_USERNAME)
         uri = "api/v1/dashboard/world_health/datasets"
         response = self.get_assert_metric(uri, "get_datasets")
-        assert response.status_code == 200
+        self.assertEqual(response.status_code, 200)
         data = json.loads(response.data.decode("utf-8"))
         dashboard = Dashboard.get("world_health")
         expected_dataset_ids = {s.datasource_id for s in dashboard.slices}
         result = data["result"]
         actual_dataset_ids = {dataset["id"] for dataset in result}
-        assert actual_dataset_ids == expected_dataset_ids
+        self.assertEqual(actual_dataset_ids, expected_dataset_ids)
         for dataset in result:
             for excluded_key in ["database", "owners"]:
                 assert excluded_key not in dataset
@@ -310,7 +319,7 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
         self.login(ALPHA_USERNAME)
         uri = "api/v1/dashboard/not_found/datasets"
         response = self.get_assert_metric(uri, "get_datasets")
-        assert response.status_code == 404
+        self.assertEqual(response.status_code, 404)
         logger_mock.warning.assert_called_once_with(
             "Dashboard not found.", exc_info=True
         )
@@ -325,7 +334,7 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
         self.login(ADMIN_USERNAME)
         uri = "api/v1/dashboard/world_health/datasets"
         response = self.get_assert_metric(uri, "get_datasets")
-        assert response.status_code == 422
+        self.assertEqual(response.status_code, 422)
         logger_mock.warning.assert_called_once_with(
             "Dataset schema is invalid, caused by: Invalid schema", exc_info=True
         )
@@ -367,9 +376,9 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
         dashboard = self.dashboards[0]
         uri = f"api/v1/dashboard/{dashboard.slug}"
         response = self.get_assert_metric(uri, "get")
-        assert response.status_code == 200
+        self.assertEqual(response.status_code, 200)
         data = json.loads(response.data.decode("utf-8"))
-        assert data["id"] == dashboard.id
+        self.assertEqual(data["id"], dashboard.id)
 
     @pytest.mark.usefixtures("create_dashboards")
     def get_dashboard_by_bad_slug(self):
@@ -377,7 +386,7 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
         dashboard = self.dashboards[0]
         uri = f"api/v1/dashboard/{dashboard.slug}-bad-slug"
         response = self.get_assert_metric(uri, "get")
-        assert response.status_code == 404
+        self.assertEqual(response.status_code, 404)
 
     @pytest.mark.usefixtures("create_dashboards")
     def get_draft_dashboard_by_slug(self):
@@ -388,7 +397,7 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
         dashboard = self.dashboards[0]
         uri = f"api/v1/dashboard/{dashboard.slug}"
         response = self.get_assert_metric(uri, "get")
-        assert response.status_code == 200
+        self.assertEqual(response.status_code, 200)
 
     @pytest.mark.usefixtures("create_dashboards")
     def test_get_dashboard_charts(self):
@@ -399,7 +408,7 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
         dashboard = self.dashboards[0]
         uri = f"api/v1/dashboard/{dashboard.id}/charts"
         response = self.get_assert_metric(uri, "get_charts")
-        assert response.status_code == 200
+        self.assertEqual(response.status_code, 200)
         data = json.loads(response.data.decode("utf-8"))
         assert len(data["result"]) == 1
         result = data["result"][0]
@@ -427,10 +436,12 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
         dashboard = self.dashboards[0]
         uri = f"api/v1/dashboard/{dashboard.slug}/charts"
         response = self.get_assert_metric(uri, "get_charts")
-        assert response.status_code == 200
+        self.assertEqual(response.status_code, 200)
         data = json.loads(response.data.decode("utf-8"))
-        assert len(data["result"]) == 1
-        assert data["result"][0]["slice_name"] == dashboard.slices[0].slice_name
+        self.assertEqual(len(data["result"]), 1)
+        self.assertEqual(
+            data["result"][0]["slice_name"], dashboard.slices[0].slice_name
+        )
 
     @pytest.mark.usefixtures("create_dashboards")
     def test_get_dashboard_charts_not_found(self):
@@ -441,14 +452,14 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
         bad_id = self.get_nonexistent_numeric_id(Dashboard)
         uri = f"api/v1/dashboard/{bad_id}/charts"
         response = self.get_assert_metric(uri, "get_charts")
-        assert response.status_code == 404
+        self.assertEqual(response.status_code, 404)
 
     @pytest.mark.usefixtures("load_world_bank_dashboard_with_slices")
     def test_get_dashboard_datasets_not_allowed(self):
         self.login(GAMMA_USERNAME)
         uri = "api/v1/dashboard/world_health/datasets"
         response = self.get_assert_metric(uri, "get_datasets")
-        assert response.status_code == 404
+        self.assertEqual(response.status_code, 404)
 
     @pytest.mark.usefixtures("create_dashboards")
     def test_get_gamma_dashboard_charts(self):
@@ -491,9 +502,9 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
         # the fixture setup assigns no charts to the second half of dashboards
         uri = f"api/v1/dashboard/{self.dashboards[-1].id}/charts"
         response = self.get_assert_metric(uri, "get_charts")
-        assert response.status_code == 200
+        self.assertEqual(response.status_code, 200)
         data = json.loads(response.data.decode("utf-8"))
-        assert data["result"] == []
+        self.assertEqual(data["result"], [])
 
     def test_get_dashboard(self):
         """
@@ -506,7 +517,7 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
         self.login(ADMIN_USERNAME)
         uri = f"api/v1/dashboard/{dashboard.id}"
         rv = self.get_assert_metric(uri, "get")
-        assert rv.status_code == 200
+        self.assertEqual(rv.status_code, 200)
         with override_user(admin):
             expected_result = {
                 "certified_by": None,
@@ -541,9 +552,9 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
                 "is_managed_externally": False,
             }
         data = json.loads(rv.data.decode("utf-8"))
-        assert "changed_on" in data["result"]
-        assert "changed_on_delta_humanized" in data["result"]
-        assert "created_on_delta_humanized" in data["result"]
+        self.assertIn("changed_on", data["result"])
+        self.assertIn("changed_on_delta_humanized", data["result"])
+        self.assertIn("created_on_delta_humanized", data["result"])
         for key, value in data["result"].items():
             # We can't assert timestamp values
             if key not in (
@@ -551,7 +562,7 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
                 "changed_on_delta_humanized",
                 "created_on_delta_humanized",
             ):
-                assert value == expected_result[key]
+                self.assertEqual(value, expected_result[key])
         # rollback changes
         db.session.delete(dashboard)
         db.session.commit()
@@ -571,7 +582,7 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
         self.login(ADMIN_USERNAME)
         uri = f"api/v1/dashboard/{dashboard.id}"
         rv = self.get_assert_metric(uri, "get")
-        assert rv.status_code == 200
+        self.assertEqual(rv.status_code, 200)
         data = json.loads(rv.data.decode("utf-8"))
         for excluded_key in ["changed_by", "changed_by_name", "owners"]:
             assert excluded_key not in data["result"]
@@ -586,7 +597,7 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
         self.login(ADMIN_USERNAME)
         uri = "api/v1/dashboard/_info"
         rv = self.get_assert_metric(uri, "info")
-        assert rv.status_code == 200
+        self.assertEqual(rv.status_code, 200)
 
     def test_info_security_dashboard(self):
         """
@@ -617,7 +628,7 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
         self.login(ADMIN_USERNAME)
         uri = f"api/v1/dashboard/{bad_id}"
         rv = self.get_assert_metric(uri, "get")
-        assert rv.status_code == 404
+        self.assertEqual(rv.status_code, 404)
 
     def test_get_dashboard_no_data_access(self):
         """
@@ -654,11 +665,12 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
             uri = f"api/v1/dashboard/?q={prison.dumps(arguments)}"
 
             rv = self.get_assert_metric(uri, "get_list")
-            assert rv.status_code == 200
+            self.assertEqual(rv.status_code, 200)
             data = json.loads(rv.data.decode("utf-8"))
-            assert data["result"][0][
-                "changed_on_delta_humanized"
-            ] == humanize.naturaltime(datetime.now())
+            self.assertEqual(
+                data["result"][0]["changed_on_delta_humanized"],
+                humanize.naturaltime(datetime.now()),
+            )
 
             # rollback changes
             db.session.delete(dashboard)
@@ -680,9 +692,9 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
         uri = f"api/v1/dashboard/?q={prison.dumps(arguments)}"
 
         rv = self.get_assert_metric(uri, "get_list")
-        assert rv.status_code == 200
+        self.assertEqual(rv.status_code, 200)
         data = json.loads(rv.data.decode("utf-8"))
-        assert data["count"] == 1
+        self.assertEqual(data["count"], 1)
 
         arguments = {
             "filters": [
@@ -691,9 +703,9 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
         }
         uri = f"api/v1/dashboard/?q={prison.dumps(arguments)}"
         rv = self.client.get(uri)
-        assert rv.status_code == 200
+        self.assertEqual(rv.status_code, 200)
         data = json.loads(rv.data.decode("utf-8"))
-        assert data["count"] == 1
+        self.assertEqual(data["count"], 1)
 
         # rollback changes
         db.session.delete(dashboard)
@@ -717,9 +729,9 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
         self.login(ADMIN_USERNAME)
         uri = f"api/v1/dashboard/?q={prison.dumps(arguments)}"
         rv = self.client.get(uri)
-        assert rv.status_code == 200
+        self.assertEqual(rv.status_code, 200)
         data = json.loads(rv.data.decode("utf-8"))
-        assert data["count"] == 1
+        self.assertEqual(data["count"], 1)
 
         expected_response = [
             {"slug": "slug1", "dashboard_title": "title1"},
@@ -730,9 +742,9 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
         arguments["filters"][0]["value"] = "slug2"
         uri = f"api/v1/dashboard/?q={prison.dumps(arguments)}"
         rv = self.client.get(uri)
-        assert rv.status_code == 200
+        self.assertEqual(rv.status_code, 200)
         data = json.loads(rv.data.decode("utf-8"))
-        assert data["count"] == 1
+        self.assertEqual(data["count"], 1)
 
         expected_response = [
             {"slug": "slug2", "dashboard_title": "title2"},
@@ -743,9 +755,9 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
         self.login(GAMMA_USERNAME)
         uri = f"api/v1/dashboard/?q={prison.dumps(arguments)}"
         rv = self.client.get(uri)
-        assert rv.status_code == 200
+        self.assertEqual(rv.status_code, 200)
         data = json.loads(rv.data.decode("utf-8"))
-        assert data["count"] == 0
+        self.assertEqual(data["count"], 0)
 
     @pytest.mark.usefixtures("create_dashboards")
     def test_get_dashboards_favorite_filter(self):
@@ -810,22 +822,26 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
             # Filter by tag ID
             filter_params = get_filter_params("dashboard_tag_id", tag.id)
             response_by_id = self.get_list("dashboard", filter_params)
-            assert response_by_id.status_code == 200
+            self.assertEqual(response_by_id.status_code, 200)
             data_by_id = json.loads(response_by_id.data.decode("utf-8"))
 
             # Filter by tag name
             filter_params = get_filter_params("dashboard_tags", tag.name)
             response_by_name = self.get_list("dashboard", filter_params)
-            assert response_by_name.status_code == 200
+            self.assertEqual(response_by_name.status_code, 200)
             data_by_name = json.loads(response_by_name.data.decode("utf-8"))
 
             # Compare results
-            assert data_by_id["count"] == data_by_name["count"], len(
-                expected_dashboards
+            self.assertEqual(
+                data_by_id["count"],
+                data_by_name["count"],
+                len(expected_dashboards),
+            )
+            self.assertEqual(
+                set(chart["id"] for chart in data_by_id["result"]),
+                set(chart["id"] for chart in data_by_name["result"]),
+                set(chart.id for chart in expected_dashboards),
             )
-            assert set(chart["id"] for chart in data_by_id["result"]) == set(
-                chart["id"] for chart in data_by_name["result"]
-            ), set(chart.id for chart in expected_dashboards)
 
     @pytest.mark.usefixtures("create_dashboards")
     def test_get_current_user_favorite_status(self):
@@ -975,9 +991,9 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
 
         uri = f"api/v1/dashboard/?q={prison.dumps(arguments)}"
         rv = self.get_assert_metric(uri, "get_list")
-        assert rv.status_code == 200
+        self.assertEqual(rv.status_code, 200)
         data = json.loads(rv.data.decode("utf-8"))
-        assert data["count"] == DASHBOARDS_FIXTURE_COUNT
+        self.assertEqual(data["count"], DASHBOARDS_FIXTURE_COUNT)
 
     @pytest.mark.usefixtures("create_dashboards")
     def test_gets_not_certified_dashboards_filter(self):
@@ -996,9 +1012,9 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
 
         uri = f"api/v1/dashboard/?q={prison.dumps(arguments)}"
         rv = self.get_assert_metric(uri, "get_list")
-        assert rv.status_code == 200
+        self.assertEqual(rv.status_code, 200)
         data = json.loads(rv.data.decode("utf-8"))
-        assert data["count"] == 0
+        self.assertEqual(data["count"], 0)
 
     @pytest.mark.usefixtures("create_created_by_gamma_dashboards")
     def test_get_dashboards_created_by_me(self):
@@ -1199,8 +1215,8 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
                 ],
             }
         }
-        assert rv.status_code == 200
-        assert response == expected_response
+        self.assertEqual(rv.status_code, 200)
+        self.assertEqual(response, expected_response)
         db.session.delete(dashboard)
         db.session.commit()
 
@@ -1213,7 +1229,7 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
         self.login(ADMIN_USERNAME)
         uri = f"api/v1/dashboard/{bad_id}/tabs"
         rv = self.get_assert_metric(uri, "get_tabs")
-        assert rv.status_code == 404
+        self.assertEqual(rv.status_code, 404)
 
     def create_dashboard_import(self):
         buf = BytesIO()
@@ -1254,9 +1270,9 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
         self.login(ADMIN_USERNAME)
         uri = f"api/v1/dashboard/{dashboard_id}"
         rv = self.delete_assert_metric(uri, "delete")
-        assert rv.status_code == 200
+        self.assertEqual(rv.status_code, 200)
         model = db.session.query(Dashboard).get(dashboard_id)
-        assert model is None
+        self.assertEqual(model, None)
 
     def test_delete_bulk_dashboards(self):
         """
@@ -1277,13 +1293,13 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
         argument = dashboard_ids
         uri = f"api/v1/dashboard/?q={prison.dumps(argument)}"
         rv = self.delete_assert_metric(uri, "bulk_delete")
-        assert rv.status_code == 200
+        self.assertEqual(rv.status_code, 200)
         response = json.loads(rv.data.decode("utf-8"))
         expected_response = {"message": f"Deleted {dashboard_count} dashboards"}
-        assert response == expected_response
+        self.assertEqual(response, expected_response)
         for dashboard_id in dashboard_ids:
             model = db.session.query(Dashboard).get(dashboard_id)
-            assert model is None
+            self.assertEqual(model, None)
 
     def test_delete_bulk_embedded_dashboards(self):
         """
@@ -1309,21 +1325,21 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
                 {"allowed_domains": allowed_domains},
                 "set_embedded",
             )
-            assert resp.status_code == 200
+            self.assertEqual(resp.status_code, 200)
             result = json.loads(resp.data.decode("utf-8"))["result"]
-            assert result["uuid"] is not None
-            assert result["uuid"] != ""
-            assert result["allowed_domains"] == allowed_domains
+            self.assertIsNotNone(result["uuid"])
+            self.assertNotEqual(result["uuid"], "")
+            self.assertEqual(result["allowed_domains"], allowed_domains)
         argument = dashboard_ids
         uri = f"api/v1/dashboard/?q={prison.dumps(argument)}"
         rv = self.delete_assert_metric(uri, "bulk_delete")
-        assert rv.status_code == 200
+        self.assertEqual(rv.status_code, 200)
         response = json.loads(rv.data.decode("utf-8"))
         expected_response = {"message": f"Deleted {dashboard_count} dashboards"}
-        assert response == expected_response
+        self.assertEqual(response, expected_response)
         for dashboard_id in dashboard_ids:
             model = db.session.query(Dashboard).get(dashboard_id)
-            assert model is None
+            self.assertEqual(model, None)
 
     def test_delete_bulk_dashboards_bad_request(self):
         """
@@ -1334,7 +1350,7 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
         argument = dashboard_ids
         uri = f"api/v1/dashboard/?q={prison.dumps(argument)}"
         rv = self.client.delete(uri)
-        assert rv.status_code == 400
+        self.assertEqual(rv.status_code, 400)
 
     def test_delete_not_found_dashboard(self):
         """
@@ -1344,7 +1360,7 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
         dashboard_id = 1000
         uri = f"api/v1/dashboard/{dashboard_id}"
         rv = self.client.delete(uri)
-        assert rv.status_code == 404
+        self.assertEqual(rv.status_code, 404)
 
     @pytest.mark.usefixtures("create_dashboard_with_report")
     def test_delete_dashboard_with_report(self):
@@ -1360,11 +1376,11 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
         uri = f"api/v1/dashboard/{dashboard.id}"
         rv = self.client.delete(uri)
         response = json.loads(rv.data.decode("utf-8"))
-        assert rv.status_code == 422
+        self.assertEqual(rv.status_code, 422)
         expected_response = {
             "message": "There are associated alerts or reports: report_with_dashboard"
         }
-        assert response == expected_response
+        self.assertEqual(response, expected_response)
 
     def test_delete_bulk_dashboards_not_found(self):
         """
@@ -1375,7 +1391,7 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
         argument = dashboard_ids
         uri = f"api/v1/dashboard/?q={prison.dumps(argument)}"
         rv = self.client.delete(uri)
-        assert rv.status_code == 404
+        self.assertEqual(rv.status_code, 404)
 
     @pytest.mark.usefixtures("create_dashboard_with_report", "create_dashboards")
     def test_delete_bulk_dashboard_with_report(self):
@@ -1399,11 +1415,11 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
         uri = f"api/v1/dashboard/?q={prison.dumps(dashboard_ids)}"
         rv = self.client.delete(uri)
         response = json.loads(rv.data.decode("utf-8"))
-        assert rv.status_code == 422
+        self.assertEqual(rv.status_code, 422)
         expected_response = {
             "message": "There are associated alerts or reports: report_with_dashboard"
         }
-        assert response == expected_response
+        self.assertEqual(response, expected_response)
 
     def test_delete_dashboard_admin_not_owned(self):
         """
@@ -1415,9 +1431,9 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
         self.login(ADMIN_USERNAME)
         uri = f"api/v1/dashboard/{dashboard_id}"
         rv = self.client.delete(uri)
-        assert rv.status_code == 200
+        self.assertEqual(rv.status_code, 200)
         model = db.session.query(Dashboard).get(dashboard_id)
-        assert model is None
+        self.assertEqual(model, None)
 
     def test_delete_bulk_dashboard_admin_not_owned(self):
         """
@@ -1440,13 +1456,13 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
         uri = f"api/v1/dashboard/?q={prison.dumps(argument)}"
         rv = self.client.delete(uri)
         response = json.loads(rv.data.decode("utf-8"))
-        assert rv.status_code == 200
+        self.assertEqual(rv.status_code, 200)
         expected_response = {"message": f"Deleted {dashboard_count} dashboards"}
-        assert response == expected_response
+        self.assertEqual(response, expected_response)
 
         for dashboard_id in dashboard_ids:
             model = db.session.query(Dashboard).get(dashboard_id)
-            assert model is None
+            self.assertEqual(model, None)
 
     @pytest.mark.usefixtures("load_birth_names_dashboard_with_slices")
     def test_delete_dashboard_not_owned(self):
@@ -1468,7 +1484,7 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
         self.login(username="alpha2", password="password")
         uri = f"api/v1/dashboard/{dashboard.id}"
         rv = self.client.delete(uri)
-        assert rv.status_code == 403
+        self.assertEqual(rv.status_code, 403)
         db.session.delete(dashboard)
         db.session.delete(user_alpha1)
         db.session.delete(user_alpha2)
@@ -1516,19 +1532,19 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
         arguments = [dashboard.id for dashboard in dashboards]
         uri = f"api/v1/dashboard/?q={prison.dumps(arguments)}"
         rv = self.client.delete(uri)
-        assert rv.status_code == 403
+        self.assertEqual(rv.status_code, 403)
         response = json.loads(rv.data.decode("utf-8"))
         expected_response = {"message": "Forbidden"}
-        assert response == expected_response
+        self.assertEqual(response, expected_response)
 
         # nothing is deleted in bulk with a list of owned and not owned dashboards
         arguments = [dashboard.id for dashboard in dashboards] + [owned_dashboard.id]
         uri = f"api/v1/dashboard/?q={prison.dumps(arguments)}"
         rv = self.client.delete(uri)
-        assert rv.status_code == 403
+        self.assertEqual(rv.status_code, 403)
         response = json.loads(rv.data.decode("utf-8"))
         expected_response = {"message": "Forbidden"}
-        assert response == expected_response
+        self.assertEqual(response, expected_response)
 
         for dashboard in dashboards:
             db.session.delete(dashboard)
@@ -1554,7 +1570,7 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
         self.login(ADMIN_USERNAME)
         uri = "api/v1/dashboard/"
         rv = self.post_assert_metric(uri, dashboard_data, "post")
-        assert rv.status_code == 201
+        self.assertEqual(rv.status_code, 201)
         data = json.loads(rv.data.decode("utf-8"))
         model = db.session.query(Dashboard).get(data.get("id"))
         db.session.delete(model)
@@ -1568,7 +1584,7 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
         self.login(ADMIN_USERNAME)
         uri = "api/v1/dashboard/"
         rv = self.client.post(uri, json=dashboard_data)
-        assert rv.status_code == 201
+        self.assertEqual(rv.status_code, 201)
         data = json.loads(rv.data.decode("utf-8"))
         model = db.session.query(Dashboard).get(data.get("id"))
         db.session.delete(model)
@@ -1582,7 +1598,7 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
         self.login(ADMIN_USERNAME)
         uri = "api/v1/dashboard/"
         rv = self.client.post(uri, json=dashboard_data)
-        assert rv.status_code == 201
+        self.assertEqual(rv.status_code, 201)
         data = json.loads(rv.data.decode("utf-8"))
         model = db.session.query(Dashboard).get(data.get("id"))
         db.session.delete(model)
@@ -1592,7 +1608,7 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
         self.login(ADMIN_USERNAME)
         uri = "api/v1/dashboard/"
         rv = self.client.post(uri, json=dashboard_data)
-        assert rv.status_code == 201
+        self.assertEqual(rv.status_code, 201)
         data = json.loads(rv.data.decode("utf-8"))
         model = db.session.query(Dashboard).get(data.get("id"))
         db.session.delete(model)
@@ -1606,12 +1622,12 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
         self.login(ADMIN_USERNAME)
         uri = "api/v1/dashboard/"
         rv = self.post_assert_metric(uri, dashboard_data, "post")
-        assert rv.status_code == 400
+        self.assertEqual(rv.status_code, 400)
         response = json.loads(rv.data.decode("utf-8"))
         expected_response = {
             "message": {"dashboard_title": ["Length must be between 0 and 500."]}
         }
-        assert response == expected_response
+        self.assertEqual(response, expected_response)
 
     def test_create_dashboard_validate_slug(self):
         """
@@ -1625,19 +1641,19 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
         dashboard_data = {"dashboard_title": "title2", "slug": "slug1"}
         uri = "api/v1/dashboard/"
         rv = self.client.post(uri, json=dashboard_data)
-        assert rv.status_code == 422
+        self.assertEqual(rv.status_code, 422)
         response = json.loads(rv.data.decode("utf-8"))
         expected_response = {"message": {"slug": ["Must be unique"]}}
-        assert response == expected_response
+        self.assertEqual(response, expected_response)
 
         # Check for slug max size
         dashboard_data = {"dashboard_title": "title2", "slug": "a" * 256}
         uri = "api/v1/dashboard/"
         rv = self.client.post(uri, json=dashboard_data)
-        assert rv.status_code == 400
+        self.assertEqual(rv.status_code, 400)
         response = json.loads(rv.data.decode("utf-8"))
         expected_response = {"message": {"slug": ["Length must be between 1 and 255."]}}
-        assert response == expected_response
+        self.assertEqual(response, expected_response)
 
         db.session.delete(dashboard)
         db.session.commit()
@@ -1650,10 +1666,10 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
         self.login(ADMIN_USERNAME)
         uri = "api/v1/dashboard/"
         rv = self.client.post(uri, json=dashboard_data)
-        assert rv.status_code == 422
+        self.assertEqual(rv.status_code, 422)
         response = json.loads(rv.data.decode("utf-8"))
         expected_response = {"message": {"owners": ["Owners are invalid"]}}
-        assert response == expected_response
+        self.assertEqual(response, expected_response)
 
     def test_create_dashboard_validate_roles(self):
         """
@@ -1663,10 +1679,10 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
         self.login(ADMIN_USERNAME)
         uri = "api/v1/dashboard/"
         rv = self.client.post(uri, json=dashboard_data)
-        assert rv.status_code == 422
+        self.assertEqual(rv.status_code, 422)
         response = json.loads(rv.data.decode("utf-8"))
         expected_response = {"message": {"roles": ["Some roles do not exist"]}}
-        assert response == expected_response
+        self.assertEqual(response, expected_response)
 
     def test_create_dashboard_validate_json(self):
         """
@@ -1676,13 +1692,13 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
         self.login(ADMIN_USERNAME)
         uri = "api/v1/dashboard/"
         rv = self.client.post(uri, json=dashboard_data)
-        assert rv.status_code == 400
+        self.assertEqual(rv.status_code, 400)
 
         dashboard_data = {"dashboard_title": "title1", "json_metadata": '{"A:"a"}'}
         self.login(ADMIN_USERNAME)
         uri = "api/v1/dashboard/"
         rv = self.client.post(uri, json=dashboard_data)
-        assert rv.status_code == 400
+        self.assertEqual(rv.status_code, 400)
 
         dashboard_data = {
             "dashboard_title": "title1",
@@ -1691,7 +1707,7 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
         self.login(ADMIN_USERNAME)
         uri = "api/v1/dashboard/"
         rv = self.client.post(uri, json=dashboard_data)
-        assert rv.status_code == 400
+        self.assertEqual(rv.status_code, 400)
 
     def test_update_dashboard(self):
         """
@@ -1705,20 +1721,186 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
         self.login(ADMIN_USERNAME)
         uri = f"api/v1/dashboard/{dashboard_id}"
         rv = self.put_assert_metric(uri, self.dashboard_data, "put")
-        assert rv.status_code == 200
+        self.assertEqual(rv.status_code, 200)
         model = db.session.query(Dashboard).get(dashboard_id)
-        assert model.dashboard_title == self.dashboard_data["dashboard_title"]
-        assert model.slug == self.dashboard_data["slug"]
-        assert model.position_json == self.dashboard_data["position_json"]
-        assert model.css == self.dashboard_data["css"]
-        assert model.json_metadata == self.dashboard_data["json_metadata"]
-        assert model.published == self.dashboard_data["published"]
-        assert model.owners == [admin]
-        assert model.roles == [admin_role]
+        self.assertEqual(model.dashboard_title, self.dashboard_data["dashboard_title"])
+        self.assertEqual(model.slug, self.dashboard_data["slug"])
+        self.assertEqual(model.position_json, self.dashboard_data["position_json"])
+        self.assertEqual(model.css, self.dashboard_data["css"])
+        self.assertEqual(model.json_metadata, self.dashboard_data["json_metadata"])
+        self.assertEqual(model.published, self.dashboard_data["published"])
+        self.assertEqual(model.owners, [admin])
+        self.assertEqual(model.roles, [admin_role])
 
         db.session.delete(model)
         db.session.commit()
 
+    def test_add_dashboard_filters(self):
+        """
+        Dashboard API: Test that a filter was added
+        """
+        admin = self.get_user("admin")
+        admin_role = self.get_role("Admin")
+        dashboard_id = self.insert_dashboard(
+            "title1", "slug1", [admin.id], roles=[admin_role.id]
+        ).id
+        self.login(ADMIN_USERNAME)
+        uri = f"api/v1/dashboard/{dashboard_id}/filters"
+        rv = self.put_assert_metric(uri, self.dashboard_put_filters_data, "put_filters")
+        self.assertEqual(rv.status_code, 200)
+        model = db.session.query(Dashboard).get(dashboard_id)
+        json_metadata = model.json_metadata
+        native_filter_config = json.loads(json_metadata)["native_filter_configuration"]
+
+        self.assertEqual(native_filter_config[0]["name"], "Filter 1")
+        db.session.delete(model)
+        db.session.commit()
+
+    def test_modify_dashboard_filters_values(self):
+        """
+        Dashboard API: Test that a filter was added
+        """
+        admin = self.get_user("admin")
+        admin_role = self.get_role("Admin")
+        json_metadata = {
+            "native_filter_configuration": [
+                {
+                    "id": "native_filter_1",
+                    "name": "Filter X",
+                    "filterType": "filter_select",
+                    "cascadeParentIds": [],
+                }
+            ]
+        }
+        dashboard_id = self.insert_dashboard(
+            "title1",
+            "slug1",
+            [admin.id],
+            roles=[admin_role.id],
+            json_metadata=json.dumps(json_metadata),
+        ).id
+        self.login(ADMIN_USERNAME)
+        uri = f"api/v1/dashboard/{dashboard_id}/filters"
+        rv = self.put_assert_metric(uri, self.dashboard_put_filters_data, "put_filters")
+
+        self.assertEqual(rv.status_code, 200)
+        model = db.session.query(Dashboard).get(dashboard_id)
+        json_metadata = model.json_metadata
+        native_filter_config = json.loads(json_metadata)["native_filter_configuration"]
+
+        self.assertEqual(native_filter_config[0]["name"], "Filter 1")
+        db.session.delete(model)
+        db.session.commit()
+
+    def test_modfify_dashboard_filters_order(self):
+        """
+        Dashboard API: Test filters reordered
+        """
+        admin = self.get_user("admin")
+        admin_role = self.get_role("Admin")
+        json_metadata = {
+            "native_filter_configuration": [
+                {
+                    "id": "native_filter_1",
+                    "name": "Filter 1",
+                    "filterType": "filter_select",
+                    "cascadeParentIds": [],
+                },
+                {
+                    "id": "native_filter_2",
+                    "name": "Filter 2",
+                    "filterType": "filter_select",
+                    "cascadeParentIds": [],
+                },
+            ]
+        }
+        dashboard_id = self.insert_dashboard(
+            "title1",
+            "slug1",
+            [admin.id],
+            roles=[admin_role.id],
+            json_metadata=json.dumps(json_metadata),
+        ).id
+        self.login(ADMIN_USERNAME)
+        uri = f"api/v1/dashboard/{dashboard_id}/filters"
+        put_data = {
+            **self.dashboard_put_filters_data,
+            "reordered": ["native_filter_2", "native_filter_1"],
+        }
+        rv = self.put_assert_metric(uri, put_data, "put_filters")
+        self.assertEqual(rv.status_code, 200)
+        model = db.session.query(Dashboard).get(dashboard_id)
+        json_metadata = model.json_metadata
+        native_filter_config = json.loads(json_metadata)["native_filter_configuration"]
+
+        self.assertEqual(native_filter_config[0]["name"], "Filter 2")
+        db.session.delete(model)
+        db.session.commit()
+
+    def test_dashboard_filters_deleted(self):
+        """
+        Dashboard API: Test filters deleted
+        """
+        admin = self.get_user("admin")
+        admin_role = self.get_role("Admin")
+        json_metadata = {
+            "native_filter_configuration": [
+                {
+                    "id": "native_filter_1",
+                    "name": "Filter 1",
+                    "filterType": "filter_select",
+                    "cascadeParentIds": [],
+                },
+                {
+                    "id": "native_filter_2",
+                    "name": "Filter 2",
+                    "filterType": "filter_select",
+                    "cascadeParentIds": [],
+                },
+            ]
+        }
+        dashboard_id = self.insert_dashboard(
+            "title1",
+            "slug1",
+            [admin.id],
+            roles=[admin_role.id],
+            json_metadata=json.dumps(json_metadata),
+        ).id
+        self.login(ADMIN_USERNAME)
+        uri = f"api/v1/dashboard/{dashboard_id}/filters"
+        put_data = {
+            **self.dashboard_put_filters_data,
+            "deleted": ["native_filter_1"],
+        }
+        rv = self.put_assert_metric(uri, put_data, "put_filters")
+        self.assertEqual(rv.status_code, 200)
+        model = db.session.query(Dashboard).get(dashboard_id)
+        json_metadata = model.json_metadata
+        native_filter_config = json.loads(json_metadata)["native_filter_configuration"]
+
+        self.assertEqual(native_filter_config[0]["name"], "Filter 2")
+        db.session.delete(model)
+        db.session.commit()
+
+    def test_modify_dashboard_filters_invalid_data(self):
+        """
+        Dashboard API: Test modify filters with invalid data
+        """
+        admin = self.get_user("admin")
+        admin_role = self.get_role("Admin")
+        dashboard_id = self.insert_dashboard(
+            "title1", "slug1", [admin.id], roles=[admin_role.id]
+        ).id
+        self.login(ADMIN_USERNAME)
+        uri = f"api/v1/dashboard/{dashboard_id}/filters"
+        put_data = {"invalid_key": "invalid_value"}
+        rv = self.put_assert_metric(uri, put_data, "put_filters")
+        self.assertEqual(rv.status_code, 400)
+
+        model = db.session.query(Dashboard).get(dashboard_id)
+        db.session.delete(model)
+        db.session.commit()
+
     def test_dashboard_get_list_no_username(self):
         """
         Dashboard API: Tests that no username is returned
@@ -1733,15 +1915,15 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
         uri = f"api/v1/dashboard/{dashboard_id}"
         dashboard_data = {"dashboard_title": "title2"}
         rv = self.client.put(uri, json=dashboard_data)
-        assert rv.status_code == 200
+        self.assertEqual(rv.status_code, 200)
 
         response = self.get_assert_metric("api/v1/dashboard/", "get_list")
         res = json.loads(response.data.decode("utf-8"))["result"]
 
         current_dash = [d for d in res if d["id"] == dashboard_id][0]
-        assert current_dash["dashboard_title"] == "title2"
-        assert "username" not in current_dash["changed_by"].keys()
-        assert "username" not in current_dash["owners"][0].keys()
+        self.assertEqual(current_dash["dashboard_title"], "title2")
+        self.assertNotIn("username", current_dash["changed_by"].keys())
+        self.assertNotIn("username", current_dash["owners"][0].keys())
 
         db.session.delete(model)
         db.session.commit()
@@ -1760,14 +1942,14 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
         uri = f"api/v1/dashboard/{dashboard_id}"
         dashboard_data = {"dashboard_title": "title2"}
         rv = self.client.put(uri, json=dashboard_data)
-        assert rv.status_code == 200
+        self.assertEqual(rv.status_code, 200)
 
         response = self.get_assert_metric(uri, "get")
         res = json.loads(response.data.decode("utf-8"))["result"]
 
-        assert res["dashboard_title"] == "title2"
-        assert "username" not in res["changed_by"].keys()
-        assert "username" not in res["owners"][0].keys()
+        self.assertEqual(res["dashboard_title"], "title2")
+        self.assertNotIn("username", res["changed_by"].keys())
+        self.assertNotIn("username", res["owners"][0].keys())
 
         db.session.delete(model)
         db.session.commit()
@@ -1815,11 +1997,11 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
         self.login(ADMIN_USERNAME)
         uri = f"api/v1/dashboard/{dashboard.id}"
         rv = self.client.put(uri, json=dashboard_data)
-        assert rv.status_code == 200
+        self.assertEqual(rv.status_code, 200)
 
         # Check that chart named Boys does not contain alpha 1 in its owners
         boys = db.session.query(Slice).filter_by(slice_name="Boys").one()
-        assert user_alpha1 not in boys.owners
+        self.assertNotIn(user_alpha1, boys.owners)
 
         # Revert owners on slice
         for slice in slices:
@@ -1842,20 +2024,20 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
         rv = self.client.put(
             uri, json={"json_metadata": self.dashboard_data["json_metadata"]}
         )
-        assert rv.status_code == 200
+        self.assertEqual(rv.status_code, 200)
 
         rv = self.client.put(
             uri, json={"dashboard_title": self.dashboard_data["dashboard_title"]}
         )
-        assert rv.status_code == 200
+        self.assertEqual(rv.status_code, 200)
 
         rv = self.client.put(uri, json={"slug": self.dashboard_data["slug"]})
-        assert rv.status_code == 200
+        self.assertEqual(rv.status_code, 200)
 
         model = db.session.query(Dashboard).get(dashboard_id)
-        assert model.json_metadata == self.dashboard_data["json_metadata"]
-        assert model.dashboard_title == self.dashboard_data["dashboard_title"]
-        assert model.slug == self.dashboard_data["slug"]
+        self.assertEqual(model.json_metadata, self.dashboard_data["json_metadata"])
+        self.assertEqual(model.dashboard_title, self.dashboard_data["dashboard_title"])
+        self.assertEqual(model.slug, self.dashboard_data["slug"])
 
         db.session.delete(model)
         db.session.commit()
@@ -1871,13 +2053,13 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
         self.login(ALPHA_USERNAME)
         uri = f"api/v1/dashboard/{dashboard_id}"
         rv = self.client.put(uri, json=dashboard_data)
-        assert rv.status_code == 200
+        self.assertEqual(rv.status_code, 200)
         model = db.session.query(Dashboard).get(dashboard_id)
-        assert gamma in model.owners
-        assert alpha in model.owners
+        self.assertIn(gamma, model.owners)
+        self.assertIn(alpha, model.owners)
         for slc in model.slices:
-            assert gamma in slc.owners
-            assert alpha in slc.owners
+            self.assertIn(gamma, slc.owners)
+            self.assertIn(alpha, slc.owners)
         db.session.delete(model)
         db.session.commit()
 
@@ -1892,13 +2074,13 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
         self.login(ADMIN_USERNAME)
         uri = f"api/v1/dashboard/{dashboard_id}"
         rv = self.client.put(uri, json=dashboard_data)
-        assert rv.status_code == 200
+        self.assertEqual(rv.status_code, 200)
         model = db.session.query(Dashboard).get(dashboard_id)
-        assert gamma in model.owners
-        assert admin not in model.owners
+        self.assertIn(gamma, model.owners)
+        self.assertNotIn(admin, model.owners)
         for slc in model.slices:
-            assert gamma in slc.owners
-            assert admin not in slc.owners
+            self.assertIn(gamma, slc.owners)
+            self.assertNotIn(admin, slc.owners)
         db.session.delete(model)
         db.session.commit()
 
@@ -1912,9 +2094,9 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
         uri = f"api/v1/dashboard/{dashboard_id}"
         dashboard_data = {"owners": []}
         rv = self.client.put(uri, json=dashboard_data)
-        assert rv.status_code == 200
+        self.assertEqual(rv.status_code, 200)
         model = db.session.query(Dashboard).get(dashboard_id)
-        assert [] == model.owners
+        self.assertEqual([], model.owners)
         db.session.delete(model)
         db.session.commit()
 
@@ -1933,9 +2115,9 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
         uri = f"api/v1/dashboard/{dashboard.id}"
         dashboard_data = {"owners": [gamma.id]}
         rv = self.client.put(uri, json=dashboard_data)
-        assert rv.status_code == 200
+        self.assertEqual(rv.status_code, 200)
         model = db.session.query(Dashboard).get(dashboard.id)
-        assert [gamma] == model.owners
+        self.assertEqual([gamma], model.owners)
         db.session.delete(model)
         db.session.commit()
 
@@ -1949,10 +2131,10 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
         self.login(ADMIN_USERNAME)
         uri = f"api/v1/dashboard/{dashboard_id}"
         rv = self.client.put(uri, json=dashboard_data)
-        assert rv.status_code == 200
+        self.assertEqual(rv.status_code, 200)
         model = db.session.query(Dashboard).get(dashboard_id)
-        assert model.dashboard_title == "title1_changed"
-        assert model.slug == "slug1-changed"
+        self.assertEqual(model.dashboard_title, "title1_changed")
+        self.assertEqual(model.slug, "slug1-changed")
         db.session.delete(model)
         db.session.commit()
 
@@ -1969,10 +2151,10 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
         dashboard_data = {"dashboard_title": "title2", "slug": "slug 1"}
         uri = f"api/v1/dashboard/{dashboard2.id}"
         rv = self.client.put(uri, json=dashboard_data)
-        assert rv.status_code == 422
+        self.assertEqual(rv.status_code, 422)
         response = json.loads(rv.data.decode("utf-8"))
         expected_response = {"message": {"slug": ["Must be unique"]}}
-        assert response == expected_response
+        self.assertEqual(response, expected_response)
 
         db.session.delete(dashboard1)
         db.session.delete(dashboard2)
@@ -1985,7 +2167,7 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
         dashboard_data = {"dashboard_title": "title2_changed", "slug": ""}
         uri = f"api/v1/dashboard/{dashboard2.id}"
         rv = self.client.put(uri, json=dashboard_data)
-        assert rv.status_code == 200
+        self.assertEqual(rv.status_code, 200)
 
         db.session.delete(dashboard1)
         db.session.delete(dashboard2)
@@ -2003,13 +2185,13 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
         self.login(ADMIN_USERNAME)
         uri = f"api/v1/dashboard/{dashboard.id}"
         rv = self.client.put(uri, json=dashboard_data)
-        assert rv.status_code == 200
+        self.assertEqual(rv.status_code, 200)
 
         model = db.session.query(Dashboard).get(dashboard.id)
-        assert model.published is True
-        assert model.slug == "slug1"
-        assert admin in model.owners
-        assert gamma in model.owners
+        self.assertEqual(model.published, True)
+        self.assertEqual(model.slug, "slug1")
+        self.assertIn(admin, model.owners)
+        self.assertIn(gamma, model.owners)
         db.session.delete(model)
         db.session.commit()
 
@@ -2034,7 +2216,7 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
         dashboard_data = {"dashboard_title": "title1_changed", "slug": "slug1 changed"}
         uri = f"api/v1/dashboard/{dashboard.id}"
         rv = self.put_assert_metric(uri, dashboard_data, "put")
-        assert rv.status_code == 403
+        self.assertEqual(rv.status_code, 403)
         db.session.delete(dashboard)
         db.session.delete(user_alpha1)
         db.session.delete(user_alpha2)
@@ -2067,7 +2249,7 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
         argument = [1000]
         uri = f"api/v1/dashboard/export/?q={prison.dumps(argument)}"
         rv = self.client.get(uri)
-        assert rv.status_code == 404
+        self.assertEqual(rv.status_code, 404)
 
     def test_export_not_allowed(self):
         """
@@ -2080,7 +2262,7 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
         argument = [dashboard.id]
         uri = f"api/v1/dashboard/export/?q={prison.dumps(argument)}"
         rv = self.client.get(uri)
-        assert rv.status_code == 404
+        self.assertEqual(rv.status_code, 404)
         db.session.delete(dashboard)
         db.session.commit()
 
@@ -2422,7 +2604,7 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
 
         # initial get should return 404
         resp = self.get_assert_metric(uri, "get_embedded")
-        assert resp.status_code == 404
+        self.assertEqual(resp.status_code, 404)
 
         # post succeeds and returns value
         allowed_domains = ["test.example", "embedded.example"]
@@ -2431,46 +2613,46 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
             {"allowed_domains": allowed_domains},
             "set_embedded",
         )
-        assert resp.status_code == 200
+        self.assertEqual(resp.status_code, 200)
         result = json.loads(resp.data.decode("utf-8"))["result"]
-        assert result["uuid"] is not None
-        assert result["uuid"] != ""
-        assert result["allowed_domains"] == allowed_domains
+        self.assertIsNotNone(result["uuid"])
+        self.assertNotEqual(result["uuid"], "")
+        self.assertEqual(result["allowed_domains"], allowed_domains)
 
         # get returns value
         resp = self.get_assert_metric(uri, "get_embedded")
-        assert resp.status_code == 200
+        self.assertEqual(resp.status_code, 200)
         result = json.loads(resp.data.decode("utf-8"))["result"]
-        assert result["uuid"] is not None
-        assert result["uuid"] != ""
-        assert result["allowed_domains"] == allowed_domains
+        self.assertIsNotNone(result["uuid"])
+        self.assertNotEqual(result["uuid"], "")
+        self.assertEqual(result["allowed_domains"], allowed_domains)
 
         # save uuid for later
         original_uuid = result["uuid"]
 
         # put succeeds and returns value
         resp = self.post_assert_metric(uri, {"allowed_domains": []}, "set_embedded")
-        assert resp.status_code == 200
+        self.assertEqual(resp.status_code, 200)
         result = json.loads(resp.data.decode("utf-8"))["result"]
-        assert resp.status_code == 200
-        assert result["uuid"] is not None
-        assert result["uuid"] != ""
-        assert result["allowed_domains"] == []
+        self.assertEqual(resp.status_code, 200)
+        self.assertIsNotNone(result["uuid"])
+        self.assertNotEqual(result["uuid"], "")
+        self.assertEqual(result["allowed_domains"], [])
 
         # get returns changed value
         resp = self.get_assert_metric(uri, "get_embedded")
-        assert resp.status_code == 200
+        self.assertEqual(resp.status_code, 200)
         result = json.loads(resp.data.decode("utf-8"))["result"]
-        assert result["uuid"] == original_uuid
-        assert result["allowed_domains"] == []
+        self.assertEqual(result["uuid"], original_uuid)
+        self.assertEqual(result["allowed_domains"], [])
 
         # delete succeeds
         resp = self.delete_assert_metric(uri, "delete_embedded")
-        assert resp.status_code == 200
+        self.assertEqual(resp.status_code, 200)
 
         # get returns 404
         resp = self.get_assert_metric(uri, "get_embedded")
-        assert resp.status_code == 404
+        self.assertEqual(resp.status_code, 404)
 
     @pytest.mark.usefixtures("create_created_by_gamma_dashboards")
     def test_gets_created_by_user_dashboards_filter(self):
@@ -2491,9 +2673,9 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
 
         uri = f"api/v1/dashboard/?q={prison.dumps(arguments)}"
         rv = self.get_assert_metric(uri, "get_list")
-        assert rv.status_code == 200
+        self.assertEqual(rv.status_code, 200)
         data = json.loads(rv.data.decode("utf-8"))
-        assert data["count"] == len(expected_models)
+        self.assertEqual(data["count"], len(expected_models))
 
     def test_gets_not_created_by_user_dashboards_filter(self):
         dashboard = self.insert_dashboard("title", "slug", [])  # noqa: F541
@@ -2512,9 +2694,9 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
 
         uri = f"api/v1/dashboard/?q={prison.dumps(arguments)}"
         rv = self.get_assert_metric(uri, "get_list")
-        assert rv.status_code == 200
+        self.assertEqual(rv.status_code, 200)
         data = json.loads(rv.data.decode("utf-8"))
-        assert data["count"] == len(expected_models)
+        self.assertEqual(data["count"], len(expected_models))
         db.session.delete(dashboard)
         db.session.commit()
 
@@ -2540,23 +2722,23 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
         pk = original_dash.id
         uri = f"api/v1/dashboard/{pk}/copy/"
         rv = self.client.post(uri, json=data)
-        assert rv.status_code == 200
+        self.assertEqual(rv.status_code, 200)
         response = json.loads(rv.data.decode("utf-8"))
-        assert response == {"result": {"id": ANY, "last_modified_time": ANY}}
+        self.assertEqual(response, {"result": {"id": ANY, "last_modified_time": ANY}})
 
         dash = (
             db.session.query(Dashboard)
             .filter(Dashboard.id == response["result"]["id"])
             .one()
         )
-        assert dash.id != original_dash.id
-        assert len(dash.position) == len(original_dash.position)
-        assert dash.dashboard_title == "copied dash"
-        assert dash.css == "<css>"
-        assert dash.owners == [security_manager.find_user("admin")]
-        self.assertCountEqual(dash.slices, original_dash.slices)  # noqa: PT009
-        assert dash.params_dict["color_namespace"] == "Color Namespace Test"
-        assert dash.params_dict["color_scheme"] == "Color Scheme Test"
+        self.assertNotEqual(dash.id, original_dash.id)
+        self.assertEqual(len(dash.position), len(original_dash.position))
+        self.assertEqual(dash.dashboard_title, "copied dash")
+        self.assertEqual(dash.css, "<css>")
+        self.assertEqual(dash.owners, [security_manager.find_user("admin")])
+        self.assertCountEqual(dash.slices, original_dash.slices)
+        self.assertEqual(dash.params_dict["color_namespace"], "Color Namespace Test")
+        self.assertEqual(dash.params_dict["color_scheme"], "Color Scheme Test")
 
         db.session.delete(dash)
         db.session.commit()
@@ -2583,26 +2765,26 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
         pk = original_dash.id
         uri = f"api/v1/dashboard/{pk}/copy/"
         rv = self.client.post(uri, json=data)
-        assert rv.status_code == 200
+        self.assertEqual(rv.status_code, 200)
         response = json.loads(rv.data.decode("utf-8"))
-        assert response == {"result": {"id": ANY, "last_modified_time": ANY}}
+        self.assertEqual(response, {"result": {"id": ANY, "last_modified_time": ANY}})
 
         dash = (
             db.session.query(Dashboard)
             .filter(Dashboard.id == response["result"]["id"])
             .one()
         )
-        assert dash.id != original_dash.id
-        assert len(dash.position) == len(original_dash.position)
-        assert dash.dashboard_title == "copied dash"
-        assert dash.css == "<css>"
-        assert dash.owners == [security_manager.find_user("admin")]
-        assert dash.params_dict["color_namespace"] == "Color Namespace Test"
-        assert dash.params_dict["color_scheme"] == "Color Scheme Test"
-        assert len(dash.slices) == len(original_dash.slices)
+        self.assertNotEqual(dash.id, original_dash.id)
+        self.assertEqual(len(dash.position), len(original_dash.position))
+        self.assertEqual(dash.dashboard_title, "copied dash")
+        self.assertEqual(dash.css, "<css>")
+        self.assertEqual(dash.owners, [security_manager.find_user("admin")])
+        self.assertEqual(dash.params_dict["color_namespace"], "Color Namespace Test")
+        self.assertEqual(dash.params_dict["color_scheme"], "Color Scheme Test")
+        self.assertEqual(len(dash.slices), len(original_dash.slices))
         for original_slc in original_dash.slices:
             for slc in dash.slices:
-                assert slc.id != original_slc.id
+                self.assertNotEqual(slc.id, original_slc.id)
 
         for slc in dash.slices:
             db.session.delete(slc)
@@ -2632,12 +2814,12 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
 
         uri = f"api/v1/dashboard/{dashboard.id}"
         rv = self.put_assert_metric(uri, update_payload, "put")
-        assert rv.status_code == 200
+        self.assertEqual(rv.status_code, 200)
         model = db.session.query(Dashboard).get(dashboard.id)
 
         # Clean up system tags
         tag_list = [tag.id for tag in model.tags if tag.type == TagType.custom]
-        assert sorted(tag_list) == sorted(new_tags)
+        self.assertEqual(sorted(tag_list), sorted(new_tags))
 
     @pytest.mark.usefixtures("create_dashboard_with_tag")
     def test_update_dashboard_remove_tags_can_write_on_tag(self):
@@ -2661,12 +2843,12 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
 
         uri = f"api/v1/dashboard/{dashboard.id}"
         rv = self.put_assert_metric(uri, update_payload, "put")
-        assert rv.status_code == 200
+        self.assertEqual(rv.status_code, 200)
         model = db.session.query(Dashboard).get(dashboard.id)
 
         # Clean up system tags
         tag_list = [tag.id for tag in model.tags if tag.type == TagType.custom]
-        assert tag_list == new_tags
+        self.assertEqual(tag_list, new_tags)
 
     @pytest.mark.usefixtures("create_dashboard_with_tag")
     def test_update_dashboard_add_tags_can_tag_on_dashboard(self):
@@ -2694,12 +2876,12 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
 
         uri = f"api/v1/dashboard/{dashboard.id}"
         rv = self.put_assert_metric(uri, update_payload, "put")
-        assert rv.status_code == 200
+        self.assertEqual(rv.status_code, 200)
         model = db.session.query(Dashboard).get(dashboard.id)
 
         # Clean up system tags
         tag_list = [tag.id for tag in model.tags if tag.type == TagType.custom]
-        assert sorted(tag_list) == sorted(new_tags)
+        self.assertEqual(sorted(tag_list), sorted(new_tags))
 
         security_manager.add_permission_role(gamma_role, write_tags_perm)
 
@@ -2725,12 +2907,12 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
 
         uri = f"api/v1/dashboard/{dashboard.id}"
         rv = self.put_assert_metric(uri, update_payload, "put")
-        assert rv.status_code == 200
+        self.assertEqual(rv.status_code, 200)
         model = db.session.query(Dashboard).get(dashboard.id)
 
         # Clean up system tags
         tag_list = [tag.id for tag in model.tags if tag.type == TagType.custom]
-        assert tag_list == []
+        self.assertEqual(tag_list, [])
 
         security_manager.add_permission_role(gamma_role, write_tags_perm)
 
@@ -2763,10 +2945,10 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
 
         uri = f"api/v1/dashboard/{dashboard.id}"
         rv = self.put_assert_metric(uri, update_payload, "put")
-        assert rv.status_code == 403
-        assert (
-            rv.json["message"]
-            == "You do not have permission to manage tags on dashboards"
+        self.assertEqual(rv.status_code, 403)
+        self.assertEqual(
+            rv.json["message"],
+            "You do not have permission to manage tags on dashboards",
         )
 
         security_manager.add_permission_role(gamma_role, write_tags_perm)
@@ -2797,10 +2979,10 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
 
         uri = f"api/v1/dashboard/{dashboard.id}"
         rv = self.put_assert_metric(uri, update_payload, "put")
-        assert rv.status_code == 403
-        assert (
-            rv.json["message"]
-            == "You do not have permission to manage tags on dashboards"
+        self.assertEqual(rv.status_code, 403)
+        self.assertEqual(
+            rv.json["message"],
+            "You do not have permission to manage tags on dashboards",
         )
 
         security_manager.add_permission_role(gamma_role, write_tags_perm)
@@ -2831,7 +3013,7 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
 
         uri = f"api/v1/dashboard/{dashboard.id}"
         rv = self.put_assert_metric(uri, update_payload, "put")
-        assert rv.status_code == 200
+        self.assertEqual(rv.status_code, 200)
 
         security_manager.add_permission_role(gamma_role, write_tags_perm)
         security_manager.add_permission_role(gamma_role, tag_dashboards_perm)
@@ -2855,7 +3037,7 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
             .first()
         )
         response = self._cache_screenshot(dashboard.id)
-        assert response.status_code == 202
+        self.assertEqual(response.status_code, 202)
 
     @pytest.mark.usefixtures("create_dashboard_with_tag")
     def test_cache_dashboard_screenshot_dashboard_validation(self):
@@ -2872,13 +3054,13 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
             "urlParams": "should be a list",
         }
         response = self._cache_screenshot(dashboard.id, invalid_payload)
-        assert response.status_code == 400
+        self.assertEqual(response.status_code, 400)
 
     def test_cache_dashboard_screenshot_dashboard_not_found(self):
         self.login(ADMIN_USERNAME)
         non_existent_id = 999
         response = self._cache_screenshot(non_existent_id)
-        assert response.status_code == 404
+        self.assertEqual(response.status_code, 404)
 
     @pytest.mark.usefixtures("create_dashboard_with_tag")
     @patch("superset.dashboards.api.cache_dashboard_screenshot")
@@ -2897,13 +3079,13 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
             .first()
         )
         cache_resp = self._cache_screenshot(dashboard.id)
-        assert cache_resp.status_code == 202
+        self.assertEqual(cache_resp.status_code, 202)
         cache_key = json.loads(cache_resp.data.decode("utf-8"))["cache_key"]
 
         response = self._get_screenshot(dashboard.id, cache_key, "png")
-        assert response.status_code == 200
-        assert response.mimetype == "image/png"
-        assert response.data == b"fake image data"
+        self.assertEqual(response.status_code, 200)
+        self.assertEqual(response.mimetype, "image/png")
+        self.assertEqual(response.data, b"fake image data")
 
     @pytest.mark.usefixtures("create_dashboard_with_tag")
     @patch("superset.dashboards.api.cache_dashboard_screenshot")
@@ -2926,13 +3108,13 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
             .first()
         )
         cache_resp = self._cache_screenshot(dashboard.id)
-        assert cache_resp.status_code == 202
+        self.assertEqual(cache_resp.status_code, 202)
         cache_key = json.loads(cache_resp.data.decode("utf-8"))["cache_key"]
 
         response = self._get_screenshot(dashboard.id, cache_key, "pdf")
-        assert response.status_code == 200
-        assert response.mimetype == "application/pdf"
-        assert response.data == b"fake pdf data"
+        self.assertEqual(response.status_code, 200)
+        self.assertEqual(response.mimetype, "application/pdf")
+        self.assertEqual(response.data, b"fake pdf data")
 
     @pytest.mark.usefixtures("create_dashboard_with_tag")
... 9044 lines suppressed ...