You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by su...@apache.org on 2020/09/17 15:58:48 UTC

[incubator-superset] 01/03: Initial commit of new filters badge.

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

suddjian pushed a commit to branch feature/filter-p0
in repository https://gitbox.apache.org/repos/asf/incubator-superset.git

commit 7da1628d13891b7e916055e0f6b3958d98a7b7fe
Author: Natalie Ruhe <na...@preset.io>
AuthorDate: Fri Aug 21 14:06:35 2020 -0700

    Initial commit of new filters badge.
---
 superset-frontend/images/icons/filter.svg          |   3 +
 superset-frontend/package-lock.json                |  20 ++--
 superset-frontend/package.json                     |   2 +-
 superset-frontend/src/chart/chartAction.js         |   2 +
 superset-frontend/src/common/components/index.ts   |   2 +
 superset-frontend/src/components/Icon/index.tsx    |   5 +-
 .../components/FiltersBadge/DetailsPanel.tsx       |  99 +++++++++++++++++
 .../dashboard/components/FiltersBadge/Styles.tsx   |  89 +++++++++++++++
 .../dashboard/components/FiltersBadge/index.tsx    |  77 +++++++++++++
 .../dashboard/components/FiltersBadge/selectors.js | 120 +++++++++++++++++++++
 .../src/dashboard/components/SliceHeader.jsx       | 106 +++++++++---------
 .../src/dashboard/stylesheets/dashboard.less       |  20 ++++
 superset/connectors/base/models.py                 |   3 +-
 superset/views/core.py                             |   2 +
 superset/views/utils.py                            |   1 +
 superset/viz.py                                    |  12 +++
 16 files changed, 499 insertions(+), 64 deletions(-)

diff --git a/superset-frontend/images/icons/filter.svg b/superset-frontend/images/icons/filter.svg
new file mode 100644
index 0000000..c2f2c66
--- /dev/null
+++ b/superset-frontend/images/icons/filter.svg
@@ -0,0 +1,3 @@
+<svg width="1em" height="1em" viewBox="0 0 12 12" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M4.74998 9.33333C4.74998 9.79357 5.12308 10.1667 5.58331 10.1667H6.41665C6.87688 10.1667 7.24998 9.79357 7.24998 9.33333V9.33333C7.24998 8.87309 6.87688 8.5 6.41665 8.5H5.58331C5.12308 8.5 4.74998 8.87309 4.74998 9.33333V9.33333ZM1.41665 1.83333C0.956409 1.83333 0.583313 2.20642 0.583313 2.66666V2.66666C0.583313 3.1269 0.956409 3.5 1.41665 3.5H10.5833C11.0435 3.5 11.4166 3.1269 11.4166 2.66666V2.66666C11.4166 2.20642 11.0435 1.83333 10.583 [...]
+</svg>
diff --git a/superset-frontend/package-lock.json b/superset-frontend/package-lock.json
index 0a8b674..a2b0553 100644
--- a/superset-frontend/package-lock.json
+++ b/superset-frontend/package-lock.json
@@ -18,22 +18,22 @@
       "integrity": "sha512-LrX0OGZtW+W6iLnTAqnTaoIsRelYeuLZWsrmBJFUXDALQphPsN8cE5DCsmoSlL0QYb94BQxINiuS70Ar/8BNgA=="
     },
     "@ant-design/icons": {
-      "version": "4.2.1",
-      "resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-4.2.1.tgz",
-      "integrity": "sha512-245ZI40MOr5GGws+sNSiJIRRoEf/J2xvPSMgwRYf3bv8mVGQZ6XTQI/OMeV16KtiSZ3D+mBKXVYSBz2fhigOXQ==",
+      "version": "4.2.2",
+      "resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-4.2.2.tgz",
+      "integrity": "sha512-DrVV+wcupnHS7PehJ6KiTcJtAR5c25UMgjGECCc6pUT9rsvw0AuYG+a4HDjfxEQuDqKTHwW+oX/nIvCymyLE8Q==",
       "requires": {
         "@ant-design/colors": "^3.1.0",
         "@ant-design/icons-svg": "^4.0.0",
-        "@babel/runtime": "^7.10.1",
+        "@babel/runtime": "^7.10.4",
         "classnames": "^2.2.6",
         "insert-css": "^2.0.0",
         "rc-util": "^5.0.1"
       },
       "dependencies": {
         "@babel/runtime": {
-          "version": "7.11.0",
-          "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.0.tgz",
-          "integrity": "sha512-qArkXsjJq7H+T86WrIFV0Fnu/tNOkZ4cgXmjkzAu3b/58D5mFIO8JH/y77t7C9q0OdDRdh9s7Ue5GasYssxtXw==",
+          "version": "7.11.2",
+          "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz",
+          "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==",
           "requires": {
             "regenerator-runtime": "^0.13.4"
           }
@@ -19722,9 +19722,9 @@
       }
     },
     "babel-plugin-emotion": {
-      "version": "10.0.29",
-      "resolved": "https://registry.npmjs.org/babel-plugin-emotion/-/babel-plugin-emotion-10.0.29.tgz",
-      "integrity": "sha512-7Jpi1OCxjyz0k163lKtqP+LHMg5z3S6A7vMBfHnF06l2unmtsOmFDzZBpGf0CWo1G4m8UACfVcDJiSiRuu/cSw==",
+      "version": "10.0.33",
+      "resolved": "https://registry.npmjs.org/babel-plugin-emotion/-/babel-plugin-emotion-10.0.33.tgz",
+      "integrity": "sha512-bxZbTTGz0AJQDHm8k6Rf3RQJ8tX2scsfsRyKVgAbiUPUNIRtlK+7JxP+TAd1kRLABFxe0CFm2VdK4ePkoA9FxQ==",
       "requires": {
         "@babel/helper-module-imports": "^7.0.0",
         "@emotion/hash": "0.8.0",
diff --git a/superset-frontend/package.json b/superset-frontend/package.json
index f8f0980..2d95c3f 100644
--- a/superset-frontend/package.json
+++ b/superset-frontend/package.json
@@ -60,6 +60,7 @@
   },
   "homepage": "https://superset.apache.org/",
   "dependencies": {
+    "@ant-design/icons": "^4.2.2",
     "@babel/runtime-corejs3": "^7.8.4",
     "@data-ui/sparkline": "^0.0.54",
     "@emotion/core": "^10.0.28",
@@ -223,7 +224,6 @@
     "babel-jest": "^26.1.0",
     "babel-loader": "^8.0.6",
     "babel-plugin-dynamic-import-node": "^2.3.0",
-    "babel-plugin-emotion": "^10.0.29",
     "babel-plugin-lodash": "^3.3.4",
     "cache-loader": "^1.2.2",
     "clean-webpack-plugin": "^3.0.0",
diff --git a/superset-frontend/src/chart/chartAction.js b/superset-frontend/src/chart/chartAction.js
index fb19186..b8fb15e 100644
--- a/superset-frontend/src/chart/chartAction.js
+++ b/superset-frontend/src/chart/chartAction.js
@@ -356,9 +356,11 @@ export function exploreJSON(
         // How to make the entire app compatible with multiple results?
         // For now just use the first result.
         const result = response.result[0];
+
         dispatch(
           logEvent(LOG_ACTIONS_LOAD_CHART, {
             slice_id: key,
+            applied_filters: result.applied_filters,
             is_cached: result.is_cached,
             force_refresh: force,
             row_count: result.rowcount,
diff --git a/superset-frontend/src/common/components/index.ts b/superset-frontend/src/common/components/index.ts
index 4f72e4c..39f9c2d 100644
--- a/superset-frontend/src/common/components/index.ts
+++ b/superset-frontend/src/common/components/index.ts
@@ -37,3 +37,5 @@ export const ThinSkeleton = styled(Skeleton)`
     margin-bottom: 0;
   }
 `;
+
+export { default as Icon } from '@ant-design/icons';
diff --git a/superset-frontend/src/components/Icon/index.tsx b/superset-frontend/src/components/Icon/index.tsx
index f033fd8..7c4924e 100644
--- a/superset-frontend/src/components/Icon/index.tsx
+++ b/superset-frontend/src/components/Icon/index.tsx
@@ -45,6 +45,7 @@ import { ReactComponent as SortDescIcon } from 'images/icons/sort-desc.svg';
 import { ReactComponent as SortIcon } from 'images/icons/sort.svg';
 import { ReactComponent as TrashIcon } from 'images/icons/trash.svg';
 import { ReactComponent as WarningIcon } from 'images/icons/warning.svg';
+import { ReactComponent as FilterIcon } from 'images/icons/filter.svg';
 
 type IconName =
   | 'cancel-x'
@@ -74,7 +75,8 @@ type IconName =
   | 'sort-desc'
   | 'sort'
   | 'trash'
-  | 'warning';
+  | 'warning'
+  | 'filter';
 
 export const iconsRegistry: Record<
   IconName,
@@ -108,6 +110,7 @@ export const iconsRegistry: Record<
   sort: SortIcon,
   trash: TrashIcon,
   warning: WarningIcon,
+  filter: FilterIcon,
 };
 
 interface IconProps extends SVGProps<SVGSVGElement> {
diff --git a/superset-frontend/src/dashboard/components/FiltersBadge/DetailsPanel.tsx b/superset-frontend/src/dashboard/components/FiltersBadge/DetailsPanel.tsx
new file mode 100644
index 0000000..723f6c0
--- /dev/null
+++ b/superset-frontend/src/dashboard/components/FiltersBadge/DetailsPanel.tsx
@@ -0,0 +1,99 @@
+import React from 'react';
+import { Collapse } from '../../../common/components/index';
+import { SearchOutlined, MinusCircleFilled, CheckCircleFilled, ExclamationCircleFilled } from '@ant-design/icons';
+import S from './Styles';
+
+const Indicator = ({ indicator: { name, value = [], path }, onClick }) => (
+  <S.Item onClick={() => onClick(path)}>
+    <S.ItemIcon>
+      <SearchOutlined />
+    </S.ItemIcon>
+    <S.Title bold>{name.toUpperCase()}</S.Title>
+    {value.length ? `: ${[].concat(value).join(', ')}` : ''}
+  </S.Item>
+);
+
+const DetailsPanel = ({
+  appliedIndicators = [],
+  incompatibleIndicators = [],
+  unsetIndicators = [],
+  onHighlightFilterSource,
+}) => {
+  const total = appliedIndicators.length + incompatibleIndicators.length + unsetIndicators.length;
+  return (
+    <S.Panel>
+      <div>{`${total} Scoped Filters`}</div>
+      <S.Reset>
+        <Collapse
+          ghost
+          defaultActiveKey={['applied', 'incompatible']}
+        >
+          {appliedIndicators.length ? (
+            <Collapse.Panel
+              key="applied"
+              header={
+                <S.Title color="#59C189">
+                  <CheckCircleFilled />
+                  {` Applied (${appliedIndicators.length})`}
+                </S.Title>
+              }
+            >
+              <S.Indent>
+                {appliedIndicators.map(indicator => (
+                  <Indicator
+                    key={indicator.id}
+                    indicator={indicator}
+                    onClick={onHighlightFilterSource}
+                  />
+                ))}
+              </S.Indent>
+            </Collapse.Panel>
+          ) : null}
+          {incompatibleIndicators.length ? (
+            <Collapse.Panel
+              key="incompatible"
+              header={
+                <S.Title color="#FBC700">
+                  <ExclamationCircleFilled />
+                  {` Incompatible (${incompatibleIndicators.length})`}
+                </S.Title>
+              }
+            >
+              <S.Indent>
+                {incompatibleIndicators.map(indicator => (
+                  <Indicator
+                    key={indicator.id}
+                    indicator={indicator}
+                    onClick={onHighlightFilterSource}
+                  />
+                ))}
+              </S.Indent>
+            </Collapse.Panel>
+          ) : null}
+          <Collapse.Panel
+            key="unset"
+            header={
+              <S.Title color="#B2B2B2">
+                <MinusCircleFilled />
+                {` Unset (${unsetIndicators.length})`}
+              </S.Title>
+            }
+            disabled={!unsetIndicators.length}
+          >
+            <S.Indent>
+              {unsetIndicators.map(indicator => (
+                <Indicator
+                  key={indicator.id}
+                  indicator={indicator}
+                  onClick={onHighlightFilterSource}
+                />
+              ))}
+            </S.Indent>
+          </Collapse.Panel>
+        </Collapse>
+      </S.Reset>
+    </S.Panel>
+  );
+};
+
+export default DetailsPanel;
\ No newline at end of file
diff --git a/superset-frontend/src/dashboard/components/FiltersBadge/Styles.tsx b/superset-frontend/src/dashboard/components/FiltersBadge/Styles.tsx
new file mode 100644
index 0000000..7106bae
--- /dev/null
+++ b/superset-frontend/src/dashboard/components/FiltersBadge/Styles.tsx
@@ -0,0 +1,89 @@
+import styled from '@superset-ui/style';
+
+const Pill = styled.div`
+  display: inline-block;
+  background: ${({ color }) => color || '#000'};
+  color: #fff;
+  border-radius: 1em;
+  vertical-align: text-top;
+  padding: 0 8px;
+  font-size: 14px;
+  font-weight: normal;
+  
+  &:hover {
+    cursor: pointer;
+    filter: brightness(3);
+  }
+  
+  svg {
+    vertical-align: text-top;
+  }
+`;
+
+const Title = styled.span`
+  color: ${({ color }) => color || 'auto'};
+  font-weight: ${({ bold }) => (bold ? '600' : 'auto')};
+  
+  & > .anticon * {
+    color: ${({ color }) => color || 'auto'};
+  }
+`;
+
+const ItemIcon = styled.i`
+  display: none;
+  position: absolute;
+  top: 50%;
+  transform: translateY(-50%);
+  left: -20px;
+`;
+
+const Item = styled.button`
+  cursor: pointer;
+  display: block;
+  padding: 0;
+  border: none;
+  background: none;
+  white-space: nowrap;
+  position: relative;
+  outline: none;
+  
+  &::-moz-focus-inner {
+    border: 0;
+  }
+
+  &:hover > i {
+    display: block;
+  }
+`;
+
+const Reset = styled.div`
+  margin: 0 -16px;
+`;
+
+const Indent = styled.div`
+  padding-left: 24px;
+  margin: -12px 0;
+`;
+
+const Panel = styled.div`
+  color: #fff;
+  min-width: 200px;
+  max-width: 400px;
+  overflow-x: hidden;
+    
+  * {
+    color: #fff;
+  }
+`;
+
+const S = {
+  Pill,
+  Title,
+  Reset,
+  Indent,
+  Panel,
+  Item,
+  ItemIcon,
+};
+
+export default S;
\ No newline at end of file
diff --git a/superset-frontend/src/dashboard/components/FiltersBadge/index.tsx b/superset-frontend/src/dashboard/components/FiltersBadge/index.tsx
new file mode 100644
index 0000000..debf0e9
--- /dev/null
+++ b/superset-frontend/src/dashboard/components/FiltersBadge/index.tsx
@@ -0,0 +1,77 @@
+import React from 'react';
+import { connect } from 'react-redux';
+import { bindActionCreators } from 'redux';
+import { WarningFilled } from '@ant-design/icons';
+import {Popover, Icon, Collapse} from '../../../common/components';
+import { ReactComponent as FilterIcon } from 'images/icons/filter.svg';
+import DetailsPanel from './DetailsPanel';
+import S from './Styles';
+import { setDirectPathToChild } from '../../actions/dashboardState';
+import { selectIndicatorsForChart, INCOMPATIBLE, APPLIED, UNSET } from './selectors';
+
+const mapDispatchToProps = (dispatch) => {
+  return bindActionCreators(
+    {
+      onHighlightFilterSource: setDirectPathToChild,
+    },
+    dispatch,
+  );
+};
+
+const mapStateToProps = (
+  { datasources, dashboardFilters, charts },
+  { chartId },
+) => {
+  const indicators = selectIndicatorsForChart(chartId, dashboardFilters, datasources, charts);
+
+  return {
+    chartId,
+    indicators,
+  };
+};
+
+const Index = ({
+  indicators,
+  onHighlightFilterSource
+}) => {
+  const appliedIndicators = indicators.filter((indicator) => indicator.status === APPLIED);
+  const unsetIndicators = indicators.filter((indicator) => indicator.status === UNSET);
+  const incompatibleIndicators = indicators.filter((indicator) => indicator.status === INCOMPATIBLE);
+
+  if (!appliedIndicators.length && !incompatibleIndicators.length) {
+    return null;
+  }
+
+  return (
+    <span>
+      <Popover
+        content={
+          <DetailsPanel
+            appliedIndicators={appliedIndicators}
+            unsetIndicators={unsetIndicators}
+            incompatibleIndicators={incompatibleIndicators}
+            onHighlightFilterSource={onHighlightFilterSource}
+          />
+        }
+        placement="bottomRight"
+        trigger="click"
+        color="rgba(0, 0, 0, 0.8)"
+      >
+        <S.Pill>
+          <Icon component={FilterIcon} /> {appliedIndicators.length + incompatibleIndicators.length}
+          {incompatibleIndicators.length ? (
+            <span>
+              {' '}
+              <WarningFilled style={{ color: '#FBC700' }} />
+            </span>
+          ) : null}
+        </S.Pill>
+      </Popover>
+    </span>
+  );
+};
+
+export default connect(
+  mapStateToProps,
+  mapDispatchToProps,
+)(Index);
\ No newline at end of file
diff --git a/superset-frontend/src/dashboard/components/FiltersBadge/selectors.js b/superset-frontend/src/dashboard/components/FiltersBadge/selectors.js
new file mode 100644
index 0000000..6aedfe8
--- /dev/null
+++ b/superset-frontend/src/dashboard/components/FiltersBadge/selectors.js
@@ -0,0 +1,120 @@
+import { getChartIdsInFilterScope } from "../../util/activeDashboardFilters";
+import { isNil, get } from "lodash";
+import {TIME_FILTER_MAP} from "../../../visualizations/FilterBox/FilterBox";
+
+export const UNSET = 'UNSET';
+export const APPLIED = 'APPLIED';
+export const INCOMPATIBLE = 'INCOMPATIBLE';
+
+const TIME_GRANULARITY_FIELDS = new Set([
+  TIME_FILTER_MAP.granularity,
+  TIME_FILTER_MAP.time_grain_sqla,
+]);
+
+/*
+if (isDateFilter && TIME_GRANULARITY_FIELDS.includes(name)) {
+    const timeGranularityConfig =
+      (name === TIME_FILTER_MAP.time_grain_sqla
+        ? datasource.time_grain_sqla
+        : datasource.granularity) || [];
+    const timeGranularityDisplayMapping = timeGranularityConfig.reduce(
+      (map, [key, value]) => ({
+        ...map,
+        [key]: value,
+      }),
+      {},
+    );
+
+    indicator.values = indicator.values.map(
+      value => timeGranularityDisplayMapping[value] || value,
+    );
+  }
+
+  if (isEmpty(indicator.values)) {
+    indicators[1].push(indicator);
+  } else {
+    indicators[0].push(indicator);
+  }
+});
+ */
+
+const selectIndicatorValue = (columnKey, filter, datasource) => {
+  if (
+    isNil(filter.columns[columnKey]) ||
+    (filter.isDateFilter && filter.columns[columnKey] === 'No filter') ||
+    (Array.isArray(filter.columns[columnKey]) && filter.columns[columnKey].length === 0)
+  ) {
+    return [];
+  }
+
+  if (filter.isDateFilter && TIME_GRANULARITY_FIELDS.has(columnKey)) {
+    const timeGranularityMap = ((
+      columnKey === TIME_FILTER_MAP.time_grain_sqla
+        ? datasource.time_grain_sqla
+        : datasource.granularity
+    ) || [])
+      .reduce(
+        (map, [key, value]) => ({
+          ...map,
+          [key]: value,
+        }),
+        {},
+      )
+    ;
+
+    return []
+      .concat(filter.columns[columnKey])
+      .map(value => timeGranularityMap[value] || value)
+    ;
+  }
+
+  return [].concat(filter.columns[columnKey]);
+};
+
+const selectIndicatorStatus = (columnKey, filter, chart) => {
+  if (
+    isNil(filter.columns[columnKey]) ||
+    (filter.isDateFilter && filter.columns[columnKey] === 'No filter') ||
+    (Array.isArray(filter.columns[columnKey]) && filter.columns[columnKey].length === 0)
+  ) {
+    return UNSET;
+  }
+
+  if (!get(chart,'queryResponse.applied_filters',[]).includes(columnKey)) {
+    return INCOMPATIBLE;
+  }
+
+  return APPLIED;
+};
+
+const selectIndicatorsForChartFromFilter = (chartId, filter, filterDataSource, chart) => {
+  return Object
+    .keys(filter.columns)
+    .filter((key) => getChartIdsInFilterScope({ filterScope: filter.scopes[key] }).includes(chartId))
+    .map((key) => ({
+      id: key,
+      name: filter.labels[key] || key,
+      value: selectIndicatorValue(key, filter, filterDataSource),
+      status: selectIndicatorStatus(key, filter, chart),
+      path: filter.directPathToFilter,
+    }))
+  ;
+};
+
+export const selectIndicatorsForChart = (chartId, filters, datasources, charts) => {
+  console.log(charts);
+  return Object
+    .values(filters)
+    .filter((filter) => filter.chartId !== chartId)
+    .reduce((acc, filter) => (
+      acc.concat(
+        selectIndicatorsForChartFromFilter(
+          chartId,
+          filter,
+          datasources[filter.datasourceId] || {},
+          charts[chartId],
+        )
+      )
+    ), [])
+  ;
+};
\ No newline at end of file
diff --git a/superset-frontend/src/dashboard/components/SliceHeader.jsx b/superset-frontend/src/dashboard/components/SliceHeader.jsx
index 941c0ad..69a9dc1 100644
--- a/superset-frontend/src/dashboard/components/SliceHeader.jsx
+++ b/superset-frontend/src/dashboard/components/SliceHeader.jsx
@@ -23,6 +23,7 @@ import { t } from '@superset-ui/core';
 import EditableTitle from '../../components/EditableTitle';
 import TooltipWrapper from '../../components/TooltipWrapper';
 import SliceHeaderControls from './SliceHeaderControls';
+import FiltersBadge from './FiltersBadge';
 
 const propTypes = {
   innerRef: PropTypes.func,
@@ -104,59 +105,62 @@ class SliceHeader extends React.PureComponent {
 
     return (
       <div className="chart-header" ref={innerRef}>
-        <div className="header">
-          <EditableTitle
-            title={
-              sliceName ||
-              (editMode
-                ? '---' // this makes an empty title clickable
-                : '')
-            }
-            canEdit={editMode}
-            emptyText=""
-            onSaveTitle={updateSliceName}
-            showTooltip={false}
-          />
-          {!!Object.values(annotationQuery).length && (
-            <TooltipWrapper
-              label="annotations-loading"
-              placement="top"
-              tooltip={annoationsLoading}
-            >
-              <i className="fa fa-refresh warning" />
-            </TooltipWrapper>
-          )}
-          {!!Object.values(annotationError).length && (
-            <TooltipWrapper
-              label="annoation-errors"
-              placement="top"
-              tooltip={annoationsError}
-            >
-              <i className="fa fa-exclamation-circle danger" />
-            </TooltipWrapper>
-          )}
-          {!editMode && (
-            <SliceHeaderControls
-              slice={slice}
-              isCached={isCached}
-              isExpanded={isExpanded}
-              cachedDttm={cachedDttm}
-              updatedDttm={updatedDttm}
-              toggleExpandSlice={toggleExpandSlice}
-              forceRefresh={forceRefresh}
-              exploreChart={exploreChart}
-              exportCSV={exportCSV}
-              supersetCanExplore={supersetCanExplore}
-              supersetCanCSV={supersetCanCSV}
-              sliceCanEdit={sliceCanEdit}
-              componentId={componentId}
-              dashboardId={dashboardId}
-              addDangerToast={addDangerToast}
-              handleToggleFullSize={handleToggleFullSize}
-              isFullSize={isFullSize}
+        <div className="chart-header-container">
+          <div className="header">
+            <EditableTitle
+              title={
+                sliceName ||
+                (editMode
+                  ? '---' // this makes an empty title clickable
+                  : '')
+              }
+              canEdit={editMode}
+              emptyText=""
+              onSaveTitle={updateSliceName}
+              showTooltip={false}
             />
-          )}
+            {!!Object.values(annotationQuery).length && (
+              <TooltipWrapper
+                label="annotations-loading"
+                placement="top"
+                tooltip={annoationsLoading}
+              >
+                <i className="fa fa-refresh warning" />
+              </TooltipWrapper>
+            )}
+            {!!Object.values(annotationError).length && (
+              <TooltipWrapper
+                label="annoation-errors"
+                placement="top"
+                tooltip={annoationsError}
+              >
+                <i className="fa fa-exclamation-circle danger" />
+              </TooltipWrapper>
+            )}
+          </div>
+          <FiltersBadge chartId={slice.slice_id} />
         </div>
+        {!editMode && (
+          <SliceHeaderControls
+            slice={slice}
+            isCached={isCached}
+            isExpanded={isExpanded}
+            cachedDttm={cachedDttm}
+            updatedDttm={updatedDttm}
+            toggleExpandSlice={toggleExpandSlice}
+            forceRefresh={forceRefresh}
+            exploreChart={exploreChart}
+            exportCSV={exportCSV}
+            supersetCanExplore={supersetCanExplore}
+            supersetCanCSV={supersetCanCSV}
+            sliceCanEdit={sliceCanEdit}
+            componentId={componentId}
+            dashboardId={dashboardId}
+            addDangerToast={addDangerToast}
+            handleToggleFullSize={handleToggleFullSize}
+            isFullSize={isFullSize}
+          />
+        )}
       </div>
     );
   }
diff --git a/superset-frontend/src/dashboard/stylesheets/dashboard.less b/superset-frontend/src/dashboard/stylesheets/dashboard.less
index 97f2f26..7cfc74c 100644
--- a/superset-frontend/src/dashboard/stylesheets/dashboard.less
+++ b/superset-frontend/src/dashboard/stylesheets/dashboard.less
@@ -62,9 +62,29 @@ body {
   font-weight: @font-weight-bold;
   margin-bottom: 4px;
 
+  & > .chart-header-container {
+    display: flex;
+    flex-wrap: nowrap;
+    padding-right: 20px;
+
+    & > * {
+      flex: none;
+    }
+
+    & > .header {
+      flex: 1;
+    }
+  }
+
   .dropdown.btn-group {
     position: absolute;
     right: 0;
+    top: 2px;
+    pointer-events: none;
+
+    & > * {
+      pointer-events: auto;
+    }
   }
 
   .dropdown-toggle.btn.btn-default {
diff --git a/superset/connectors/base/models.py b/superset/connectors/base/models.py
index 1f6f032..724a9ce 100644
--- a/superset/connectors/base/models.py
+++ b/superset/connectors/base/models.py
@@ -14,6 +14,7 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
+import logging
 import json
 from enum import Enum
 from typing import Any, Dict, Hashable, List, Optional, Type, Union
@@ -29,7 +30,7 @@ from superset.models.helpers import AuditMixinNullable, ImportMixin, QueryResult
 from superset.models.slice import Slice
 from superset.typing import FilterValue, FilterValues, QueryObjectDict
 from superset.utils import core as utils
-
+logger = logging.getLogger(__name__)
 METRIC_FORM_DATA_PARAMS = [
     "metric",
     "metrics",
diff --git a/superset/views/core.py b/superset/views/core.py
index 3f0c477..8899efb 100755
--- a/superset/views/core.py
+++ b/superset/views/core.py
@@ -510,6 +510,8 @@ class Superset(BaseSupersetView):  # pylint: disable=too-many-public-methods
         payloads based on the request args in the first block
 
         TODO: break into one endpoint for each return shape"""
+
+        logger.info('test123')
         response_type = utils.ChartDataResultFormat.JSON.value
         responses: List[
             Union[utils.ChartDataResultFormat, utils.ChartDataResultType]
diff --git a/superset/views/utils.py b/superset/views/utils.py
index eaecc5f..f941834 100644
--- a/superset/views/utils.py
+++ b/superset/views/utils.py
@@ -105,6 +105,7 @@ def get_viz(
     form_data: FormData, datasource_type: str, datasource_id: int, force: bool = False
 ) -> BaseViz:
     viz_type = form_data.get("viz_type", "table")
+    logger.info('testaa3')
     datasource = ConnectorRegistry.get_datasource(
         datasource_type, datasource_id, db.session
     )
diff --git a/superset/viz.py b/superset/viz.py
index 57531ea..6068605 100644
--- a/superset/viz.py
+++ b/superset/viz.py
@@ -468,14 +468,26 @@ class BaseViz:
 
     def get_payload(self, query_obj: Optional[QueryObjectDict] = None) -> VizPayload:
         """Returns a payload of metadata and data"""
+
         self.run_extra_queries()
         payload = self.get_df_payload(query_obj)
 
         df = payload.get("df")
+
+        # Check incompatible filters. Probably a better spot for this.
+        filters = self.form_data.get("filters")
+        labels = set()
+        for flt in filters:
+            labels.add(flt.get("col"))
+        # This doesn't seem to work for the date-time columns. Probably due to date time fields getting prefixed
+        # with underscore like __ds. To fix, we need to try comparing with ds instead.
+        payload["applied_filters"] = labels.intersection(set(self.datasource.column_names))
+
         if self.status != utils.QueryStatus.FAILED:
             payload["data"] = self.get_data(df)
         if "df" in payload:
             del payload["df"]
+
         return payload
 
     def get_df_payload(