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 2023/02/02 16:04:39 UTC

[superset] 01/01: Initial implementation

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

diegopucci pushed a commit to branch feat/cross-filters-sidebar
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 6f8418b9922f32fecff039e146e81c0e83ba8265
Author: geido <di...@gmail.com>
AuthorDate: Thu Feb 2 17:04:17 2023 +0100

    Initial implementation
---
 .../DetailsPanel/DetailsPanel.test.tsx             |   2 +-
 .../components/FiltersBadge/DetailsPanel/index.tsx |   2 +-
 .../FilterIndicator/FilterIndicator.test.tsx       |   2 +-
 .../FiltersBadge/FilterIndicator/index.tsx         |   2 +-
 .../dashboard/components/FiltersBadge/index.tsx    |   2 +-
 .../FilterBar/FilterBarCrossFilters/Vertical.tsx   | 155 +++++++++++++++++
 .../FilterBar/FilterBarCrossFilters/index.tsx      |  72 ++++++++
 .../nativeFilters/FilterBar/Vertical.tsx           | 188 ++++++++++++---------
 .../{FiltersBadge => nativeFilters}/selectors.ts   | 146 +++++++++-------
 9 files changed, 426 insertions(+), 145 deletions(-)

diff --git a/superset-frontend/src/dashboard/components/FiltersBadge/DetailsPanel/DetailsPanel.test.tsx b/superset-frontend/src/dashboard/components/FiltersBadge/DetailsPanel/DetailsPanel.test.tsx
index 8d2b121ff6..3c3e25ee56 100644
--- a/superset-frontend/src/dashboard/components/FiltersBadge/DetailsPanel/DetailsPanel.test.tsx
+++ b/superset-frontend/src/dashboard/components/FiltersBadge/DetailsPanel/DetailsPanel.test.tsx
@@ -19,7 +19,7 @@
 import userEvent from '@testing-library/user-event';
 import React from 'react';
 import { render, screen } from 'spec/helpers/testing-library';
-import { Indicator } from 'src/dashboard/components/FiltersBadge/selectors';
+import { Indicator } from 'src/dashboard/components/nativeFilters/selectors';
 import DetailsPanel from '.';
 
 const createProps = () => ({
diff --git a/superset-frontend/src/dashboard/components/FiltersBadge/DetailsPanel/index.tsx b/superset-frontend/src/dashboard/components/FiltersBadge/DetailsPanel/index.tsx
index 3531ab1be9..022dab7968 100644
--- a/superset-frontend/src/dashboard/components/FiltersBadge/DetailsPanel/index.tsx
+++ b/superset-frontend/src/dashboard/components/FiltersBadge/DetailsPanel/index.tsx
@@ -29,7 +29,7 @@ import {
   Reset,
   Title,
 } from 'src/dashboard/components/FiltersBadge/Styles';
-import { Indicator } from 'src/dashboard/components/FiltersBadge/selectors';
+import { Indicator } from 'src/dashboard/components/nativeFilters/selectors';
 import FilterIndicator from 'src/dashboard/components/FiltersBadge/FilterIndicator';
 import { RootState } from 'src/dashboard/types';
 
diff --git a/superset-frontend/src/dashboard/components/FiltersBadge/FilterIndicator/FilterIndicator.test.tsx b/superset-frontend/src/dashboard/components/FiltersBadge/FilterIndicator/FilterIndicator.test.tsx
index fdded804b8..de49516a64 100644
--- a/superset-frontend/src/dashboard/components/FiltersBadge/FilterIndicator/FilterIndicator.test.tsx
+++ b/superset-frontend/src/dashboard/components/FiltersBadge/FilterIndicator/FilterIndicator.test.tsx
@@ -19,7 +19,7 @@
 import userEvent from '@testing-library/user-event';
 import React from 'react';
 import { render, screen } from 'spec/helpers/testing-library';
-import { Indicator } from 'src/dashboard/components/FiltersBadge/selectors';
+import { Indicator } from 'src/dashboard/components/nativeFilters/selectors';
 import FilterIndicator from '.';
 
 const createProps = () => ({
diff --git a/superset-frontend/src/dashboard/components/FiltersBadge/FilterIndicator/index.tsx b/superset-frontend/src/dashboard/components/FiltersBadge/FilterIndicator/index.tsx
index e7675a3ef0..2954474e64 100644
--- a/superset-frontend/src/dashboard/components/FiltersBadge/FilterIndicator/index.tsx
+++ b/superset-frontend/src/dashboard/components/FiltersBadge/FilterIndicator/index.tsx
@@ -28,7 +28,7 @@ import {
   ItemIcon,
   Title,
 } from 'src/dashboard/components/FiltersBadge/Styles';
-import { Indicator } from 'src/dashboard/components/FiltersBadge/selectors';
+import { Indicator } from 'src/dashboard/components/nativeFilters/selectors';
 
 export interface IndicatorProps {
   indicator: Indicator;
diff --git a/superset-frontend/src/dashboard/components/FiltersBadge/index.tsx b/superset-frontend/src/dashboard/components/FiltersBadge/index.tsx
index fd21d07fc2..0532822530 100644
--- a/superset-frontend/src/dashboard/components/FiltersBadge/index.tsx
+++ b/superset-frontend/src/dashboard/components/FiltersBadge/index.tsx
@@ -31,7 +31,7 @@ import {
   IndicatorStatus,
   selectIndicatorsForChart,
   selectNativeIndicatorsForChart,
-} from './selectors';
+} from '../nativeFilters/selectors';
 import {
   ChartsState,
   DashboardInfo,
diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBarCrossFilters/Vertical.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBarCrossFilters/Vertical.tsx
new file mode 100644
index 0000000000..484404580a
--- /dev/null
+++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBarCrossFilters/Vertical.tsx
@@ -0,0 +1,155 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import React, { useCallback } from 'react';
+import Collapse from 'src/components/Collapse';
+import { styled, t, css } from '@superset-ui/core';
+import { Indicator } from 'src/dashboard/components/nativeFilters/selectors';
+import Icons from 'src/components/Icons';
+import { useDispatch } from 'react-redux';
+import { setFocusedNativeFilter } from 'src/dashboard/actions/nativeFilters';
+import { Tag } from 'src/components';
+
+const StyledCollapse = styled(Collapse)`
+  ${({ theme }) => `
+    .ant-collapse-item > .ant-collapse-header {
+      padding-bottom: 0;
+    }
+    .ant-collapse-item > .ant-collapse-header > .ant-collapse-arrow {
+      font-size: ${theme.typography.sizes.xs}px;
+      padding-top: ${theme.gridUnit * 3.5}px;
+    }
+    .ant-collapse-item > .ant-collapse-content > .ant-collapse-content-box {
+      padding-top: 0;
+    }
+  `}
+`;
+
+const StyledCrossFilter = styled.div`
+  ${({ theme }) => `
+    margin-top: ${theme.gridUnit * 4}px;
+  `}
+`;
+
+const StyledCrossFiltersTitle = styled.span`
+  font-size: ${({ theme }) => `${theme.typography.sizes.s}px;`};
+`;
+
+const StyledCrossFilterTitle = styled.div`
+  ${({ theme }) => `
+    display: flex;
+    font-size: ${theme.typography.sizes.s}px;
+    color: ${theme.colors.grayscale.base};
+    vertical-align: middle;
+    cursor: pointer;
+    align-items: center;
+  `}
+`;
+const StyledIconSearch = styled(Icons.SearchOutlined)`
+  ${({ theme }) => `
+    color: ${theme.colors.grayscale.light1};
+    margin-left: ${theme.gridUnit * 2}px;
+    &:hover {
+      color: ${theme.colors.grayscale.base};
+    }
+  `}
+`;
+
+const StyledCrossFilterTag = styled(Tag)`
+  ${({ theme }) => `
+    margin-top: ${theme.gridUnit * 2}px;
+  `}
+`;
+
+const ellipsisCss = css`
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  display: inline-block;
+  vertical-align: middle;
+`;
+
+const StyledCrossFilterValue = styled('b')`
+  ${({ theme }) => `
+    max-width: ${theme.gridUnit * 30}px;
+    ${ellipsisCss}
+  `}
+`;
+
+const StyledCrossFilterColumn = styled('span')`
+  ${({ theme }) => `
+    max-width: ${theme.gridUnit * 20}px;
+    ${ellipsisCss}
+  `}
+`;
+
+const FilterBarCrossFiltersVertical = (props: {
+  crossFilters: Indicator[];
+}) => {
+  const { crossFilters } = props;
+  const dispatch = useDispatch();
+  const onHighlightFilterSource = useCallback(
+    (path?: string[]) => {
+      if (path) {
+        dispatch(setFocusedNativeFilter(path[0]));
+      }
+    },
+    [dispatch],
+  );
+
+  const crossFiltersIndicators = crossFilters.map(filter => (
+    <StyledCrossFilter>
+      <StyledCrossFilterTitle
+        key={filter.name}
+        onClick={() => onHighlightFilterSource(filter.path)}
+        role="button"
+        tabIndex={0}
+      >
+        {filter.name} <StyledIconSearch iconSize="xs" />
+      </StyledCrossFilterTitle>
+      {(filter.column || filter.value) && (
+        <StyledCrossFilterTag closable>
+          <StyledCrossFilterColumn>{filter.column} </StyledCrossFilterColumn>
+          <StyledCrossFilterValue>{filter.value}</StyledCrossFilterValue>
+        </StyledCrossFilterTag>
+      )}
+    </StyledCrossFilter>
+  ));
+
+  return (
+    <StyledCollapse
+      ghost
+      defaultActiveKey="crossFilters"
+      expandIconPosition="right"
+    >
+      <Collapse.Panel
+        key="crossFilters"
+        header={
+          <StyledCrossFiltersTitle>
+            {t('Cross-filters')}
+          </StyledCrossFiltersTitle>
+        }
+      >
+        {crossFiltersIndicators}
+      </Collapse.Panel>
+    </StyledCollapse>
+  );
+};
+
+export default FilterBarCrossFiltersVertical;
diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBarCrossFilters/index.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBarCrossFilters/index.tsx
new file mode 100644
index 0000000000..8fc639aad8
--- /dev/null
+++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBarCrossFilters/index.tsx
@@ -0,0 +1,72 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import React from 'react';
+import {
+  FilterBarOrientation,
+  DashboardInfo,
+  DashboardLayout,
+  RootState,
+} from 'src/dashboard/types';
+import { DataMaskStateWithId } from '@superset-ui/core';
+import {
+  selectChartCrossFilters,
+  Indicator,
+} from 'src/dashboard/components/nativeFilters/selectors';
+import { useSelector } from 'react-redux';
+import FilterBarCrossFiltersVertical from './Vertical';
+
+const FilterBarCrossFilters = (props: {
+  orientation: FilterBarOrientation;
+}) => {
+  const { orientation } = props;
+  const dataMask = useSelector<RootState, DataMaskStateWithId>(
+    state => state.dataMask,
+  );
+  const dashboardInfo = useSelector<RootState, DashboardInfo>(
+    state => state.dashboardInfo,
+  );
+  const dashboardLayout = useSelector<RootState, DashboardLayout>(
+    state => state.dashboardLayout.present,
+  );
+  const chartConfiguration = dashboardInfo.metadata?.chart_configuration;
+  const chartsIds = Object.keys(chartConfiguration);
+  let selectedCrossFilters: Indicator[] = [];
+
+  chartsIds.forEach(id => {
+    const chartId = Number(id);
+    const crossFilters = selectChartCrossFilters(
+      dataMask,
+      chartId,
+      dashboardLayout,
+      chartConfiguration,
+    );
+    selectedCrossFilters = [...selectedCrossFilters, ...crossFilters];
+  });
+
+  if (!selectedCrossFilters.length) return null;
+
+  return orientation === FilterBarOrientation.VERTICAL ? (
+    <FilterBarCrossFiltersVertical crossFilters={selectedCrossFilters} />
+  ) : (
+    <span>Horizontal</span>
+  );
+};
+
+export default FilterBarCrossFilters;
diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/Vertical.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/Vertical.tsx
index 2a6b717836..168994db0c 100644
--- a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/Vertical.tsx
+++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/Vertical.tsx
@@ -34,6 +34,7 @@ import { AntdTabs } from 'src/components';
 import { FeatureFlag, isFeatureEnabled } from 'src/featureFlags';
 import Loading from 'src/components/Loading';
 import { EmptyStateSmall } from 'src/components/EmptyState';
+import { FilterBarOrientation } from 'src/dashboard/types';
 import { getFilterBarTestId } from './utils';
 import { TabIds, VerticalBarProps } from './types';
 import FilterSets from './FilterSets';
@@ -41,6 +42,7 @@ import { useFilterSets } from './state';
 import EditSection from './FilterSets/EditSection';
 import Header from './Header';
 import FilterControls from './FilterControls/FilterControls';
+import FilterBarCrossFilters from './FilterBarCrossFilters';
 
 const BarWrapper = styled.div<{ width: number }>`
   width: ${({ theme }) => theme.gridUnit * 8}px;
@@ -191,6 +193,101 @@ const VerticalFilterBar: React.FC<VerticalBarProps> = ({
 
   const numberOfFilters = nativeFilterValues.length;
 
+  const filterControls = useMemo(
+    () =>
+      filterValues.length === 0 ? (
+        <FilterBarEmptyStateContainer>
+          <EmptyStateSmall
+            title={t('No filters are currently added')}
+            image="filter.svg"
+            description={
+              canEdit &&
+              t('Click the button above to add a filter to the dashboard')
+            }
+          />
+        </FilterBarEmptyStateContainer>
+      ) : (
+        <FilterControlsWrapper>
+          <FilterControls
+            dataMaskSelected={dataMaskSelected}
+            focusedFilterId={focusedFilterId}
+            onFilterSelectionChange={onSelectionChange}
+          />
+        </FilterControlsWrapper>
+      ),
+    [
+      canEdit,
+      dataMaskSelected,
+      filterValues.length,
+      focusedFilterId,
+      onSelectionChange,
+    ],
+  );
+
+  const filterSetsTabs = useMemo(
+    () => (
+      <StyledTabs
+        centered
+        onChange={setTab as HandlerFunction}
+        defaultActiveKey={TabIds.AllFilters}
+        activeKey={editFilterSetId ? TabIds.AllFilters : undefined}
+      >
+        <AntdTabs.TabPane
+          tab={t('All filters (%(filterCount)d)', {
+            filterCount: numberOfFilters,
+          })}
+          key={TabIds.AllFilters}
+          css={tabPaneStyle}
+        >
+          {editFilterSetId && (
+            <EditSection
+              dataMaskSelected={dataMaskSelected}
+              disabled={!isDisabled}
+              onCancel={() => setEditFilterSetId(null)}
+              filterSetId={editFilterSetId}
+            />
+          )}
+          {filterControls}
+        </AntdTabs.TabPane>
+        <AntdTabs.TabPane
+          disabled={!!editFilterSetId}
+          tab={t('Filter sets (%(filterSetCount)d)', {
+            filterSetCount: filterSetFilterValues.length,
+          })}
+          key={TabIds.FilterSets}
+          css={tabPaneStyle}
+        >
+          <FilterSets
+            onEditFilterSet={setEditFilterSetId}
+            disabled={!isDisabled}
+            dataMaskSelected={dataMaskSelected}
+            tab={tab}
+            onFilterSelectionChange={onSelectionChange}
+          />
+        </AntdTabs.TabPane>
+      </StyledTabs>
+    ),
+    [
+      dataMaskSelected,
+      editFilterSetId,
+      filterControls,
+      filterSetFilterValues.length,
+      isDisabled,
+      numberOfFilters,
+      onSelectionChange,
+      tab,
+      tabPaneStyle,
+    ],
+  );
+
+  const crossFilters = useMemo(
+    () =>
+      isFeatureEnabled(FeatureFlag.DASHBOARD_CROSS_FILTERS) ? (
+        <FilterBarCrossFilters orientation={FilterBarOrientation.VERTICAL} />
+      ) : null,
+    [],
+  );
+
   return (
     <FilterBarScrollContext.Provider value={isScrolling}>
       <BarWrapper
@@ -220,91 +317,16 @@ const VerticalFilterBar: React.FC<VerticalBarProps> = ({
               <Loading />
             </div>
           ) : isFeatureEnabled(FeatureFlag.DASHBOARD_NATIVE_FILTERS_SET) ? (
-            <StyledTabs
-              centered
-              onChange={setTab as HandlerFunction}
-              defaultActiveKey={TabIds.AllFilters}
-              activeKey={editFilterSetId ? TabIds.AllFilters : undefined}
-            >
-              <AntdTabs.TabPane
-                tab={t('All filters (%(filterCount)d)', {
-                  filterCount: numberOfFilters,
-                })}
-                key={TabIds.AllFilters}
-                css={tabPaneStyle}
-              >
-                {editFilterSetId && (
-                  <EditSection
-                    dataMaskSelected={dataMaskSelected}
-                    disabled={!isDisabled}
-                    onCancel={() => setEditFilterSetId(null)}
-                    filterSetId={editFilterSetId}
-                  />
-                )}
-                {filterValues.length === 0 ? (
-                  <FilterBarEmptyStateContainer>
-                    <EmptyStateSmall
-                      title={t('No filters are currently added')}
-                      image="filter.svg"
-                      description={
-                        canEdit &&
-                        t(
-                          'Click the button above to add a filter to the dashboard',
-                        )
-                      }
-                    />
-                  </FilterBarEmptyStateContainer>
-                ) : (
-                  <FilterControlsWrapper>
-                    <FilterControls
-                      dataMaskSelected={dataMaskSelected}
-                      focusedFilterId={focusedFilterId}
-                      onFilterSelectionChange={onSelectionChange}
-                    />
-                  </FilterControlsWrapper>
-                )}
-              </AntdTabs.TabPane>
-              <AntdTabs.TabPane
-                disabled={!!editFilterSetId}
-                tab={t('Filter sets (%(filterSetCount)d)', {
-                  filterSetCount: filterSetFilterValues.length,
-                })}
-                key={TabIds.FilterSets}
-                css={tabPaneStyle}
-              >
-                <FilterSets
-                  onEditFilterSet={setEditFilterSetId}
-                  disabled={!isDisabled}
-                  dataMaskSelected={dataMaskSelected}
-                  tab={tab}
-                  onFilterSelectionChange={onSelectionChange}
-                />
-              </AntdTabs.TabPane>
-            </StyledTabs>
+            <>
+              {crossFilters}
+              {filterSetsTabs}
+            </>
           ) : (
             <div css={tabPaneStyle} onScroll={onScroll}>
-              {filterValues.length === 0 ? (
-                <FilterBarEmptyStateContainer>
-                  <EmptyStateSmall
-                    title={t('No filters are currently added')}
-                    image="filter.svg"
-                    description={
-                      canEdit &&
-                      t(
-                        'Click the button above to add a filter to the dashboard',
-                      )
-                    }
-                  />
-                </FilterBarEmptyStateContainer>
-              ) : (
-                <FilterControlsWrapper>
-                  <FilterControls
-                    dataMaskSelected={dataMaskSelected}
-                    focusedFilterId={focusedFilterId}
-                    onFilterSelectionChange={onSelectionChange}
-                  />
-                </FilterControlsWrapper>
-              )}
+              <>
+                {crossFilters}
+                {filterControls}
+              </>
             </div>
           )}
           {actions}
diff --git a/superset-frontend/src/dashboard/components/FiltersBadge/selectors.ts b/superset-frontend/src/dashboard/components/nativeFilters/selectors.ts
similarity index 85%
rename from superset-frontend/src/dashboard/components/FiltersBadge/selectors.ts
rename to superset-frontend/src/dashboard/components/nativeFilters/selectors.ts
index c0916b9960..d84b755a11 100644
--- a/superset-frontend/src/dashboard/components/FiltersBadge/selectors.ts
+++ b/superset-frontend/src/dashboard/components/nativeFilters/selectors.ts
@@ -60,7 +60,7 @@ type Filter = {
 };
 
 const extractLabel = (filter?: FilterState): string | null => {
-  if (filter?.label) {
+  if (filter?.label && !filter?.label?.includes(undefined)) {
     return filter.label;
   }
   if (filter?.value) {
@@ -214,9 +214,84 @@ export const selectIndicatorsForChart = (
   return indicators;
 };
 
+const getStatus = ({
+  label,
+  column,
+  type = DataMaskType.NativeFilters,
+  rejectedColumns,
+  appliedColumns,
+}: {
+  label: string | null;
+  column?: string;
+  type?: DataMaskType;
+  rejectedColumns?: Set<string>;
+  appliedColumns?: Set<string>;
+}): IndicatorStatus => {
+  // a filter is only considered unset if it's value is null
+  const hasValue = label !== null;
+  if (type === DataMaskType.CrossFilters && hasValue) {
+    return IndicatorStatus.CrossFilterApplied;
+  }
+  if (!column && hasValue) {
+    // Filter without datasource
+    return IndicatorStatus.Applied;
+  }
+  if (column && rejectedColumns?.has(column))
+    return IndicatorStatus.Incompatible;
+  if (column && appliedColumns?.has(column) && hasValue) {
+    return IndicatorStatus.Applied;
+  }
+  return IndicatorStatus.Unset;
+};
+
+const defaultChartConfig = {};
+export const selectChartCrossFilters = (
+  dataMask: DataMaskStateWithId,
+  chartId: number,
+  dashboardLayout: Layout,
+  chartConfiguration: ChartConfiguration = defaultChartConfig,
+): Indicator[] => {
+  let crossFilterIndicators: any = [];
+  if (isFeatureEnabled(FeatureFlag.DASHBOARD_CROSS_FILTERS)) {
+    const dashboardLayoutValues = Object.values(dashboardLayout);
+    crossFilterIndicators = Object.values(chartConfiguration)
+      .filter(chartConfig =>
+        chartConfig.crossFilters?.chartsInScope?.includes(chartId),
+      )
+      .map(chartConfig => {
+        const filterState = dataMask[chartConfig.id]?.filterState;
+        const extraFormData = dataMask[chartConfig.id]?.extraFormData;
+        const label = extractLabel(filterState);
+        const filtersState = filterState?.filters;
+        const column =
+          extraFormData?.filters?.[0]?.col ||
+          (filtersState && Object.keys(filtersState)[0]);
+
+        const dashboardLayoutItem = dashboardLayoutValues.find(
+          layoutItem => layoutItem?.meta?.chartId === chartConfig.id,
+        );
+        return {
+          column,
+          name: dashboardLayoutItem?.meta?.sliceName as string,
+          path: [
+            ...(dashboardLayoutItem?.parents ?? []),
+            dashboardLayoutItem?.id,
+          ],
+          status: getStatus({
+            label,
+            type: DataMaskType.CrossFilters,
+          }),
+          value: label,
+        };
+      })
+      .filter(filter => filter.status === IndicatorStatus.CrossFilterApplied);
+  }
+
+  return crossFilterIndicators;
+};
+
 const cachedNativeIndicatorsForChart = {};
 const cachedNativeFilterDataForChart: any = {};
-const defaultChartConfig = {};
 export const selectNativeIndicatorsForChart = (
   nativeFilters: Filters,
   dataMask: DataMaskStateWithId,
@@ -240,31 +315,6 @@ export const selectNativeIndicatorsForChart = (
   ) {
     return cachedNativeIndicatorsForChart[chartId];
   }
-  const getStatus = ({
-    label,
-    column,
-    type = DataMaskType.NativeFilters,
-  }: {
-    label: string | null;
-    column?: string;
-    type?: DataMaskType;
-  }): IndicatorStatus => {
-    // a filter is only considered unset if it's value is null
-    const hasValue = label !== null;
-    if (type === DataMaskType.CrossFilters && hasValue) {
-      return IndicatorStatus.CrossFilterApplied;
-    }
-    if (!column && hasValue) {
-      // Filter without datasource
-      return IndicatorStatus.Applied;
-    }
-    if (column && rejectedColumns.has(column))
-      return IndicatorStatus.Incompatible;
-    if (column && appliedColumns.has(column) && hasValue) {
-      return IndicatorStatus.Applied;
-    }
-    return IndicatorStatus.Unset;
-  };
 
   let nativeFilterIndicators: any = [];
   if (isFeatureEnabled(FeatureFlag.DASHBOARD_NATIVE_FILTERS)) {
@@ -284,7 +334,12 @@ export const selectNativeIndicatorsForChart = (
             column,
             name: nativeFilter.name,
             path: [nativeFilter.id],
-            status: getStatus({ label, column }),
+            status: getStatus({
+              label,
+              column,
+              rejectedColumns,
+              appliedColumns,
+            }),
             value: label,
           };
         });
@@ -292,35 +347,12 @@ export const selectNativeIndicatorsForChart = (
 
   let crossFilterIndicators: any = [];
   if (isFeatureEnabled(FeatureFlag.DASHBOARD_CROSS_FILTERS)) {
-    const dashboardLayoutValues = Object.values(dashboardLayout);
-    crossFilterIndicators = Object.values(chartConfiguration)
-      .filter(chartConfig =>
-        chartConfig.crossFilters?.chartsInScope?.includes(chartId),
-      )
-      .map(chartConfig => {
-        const filterState = dataMask[chartConfig.id]?.filterState;
-        const label = extractLabel(filterState);
-        const filtersState = filterState?.filters;
-        const column = filtersState && Object.keys(filtersState)[0];
-
-        const dashboardLayoutItem = dashboardLayoutValues.find(
-          layoutItem => layoutItem?.meta?.chartId === chartConfig.id,
-        );
-        return {
-          column,
-          name: dashboardLayoutItem?.meta?.sliceName as string,
-          path: [
-            ...(dashboardLayoutItem?.parents ?? []),
-            dashboardLayoutItem?.id,
-          ],
-          status: getStatus({
-            label,
-            type: DataMaskType.CrossFilters,
-          }),
-          value: label,
-        };
-      })
-      .filter(filter => filter.status === IndicatorStatus.CrossFilterApplied);
+    crossFilterIndicators = selectChartCrossFilters(
+      dataMask,
+      chartId,
+      dashboardLayout,
+      chartConfiguration,
+    );
   }
   const indicators = crossFilterIndicators.concat(nativeFilterIndicators);
   cachedNativeIndicatorsForChart[chartId] = indicators;