mirror of
https://github.com/apache/superset.git
synced 2024-09-12 00:29:39 -04:00
feat(Filter-sets): connect to api (#17055)
* fix:fix get permission function * feat: filtersets new * feat: connect filter sets to api * lint: fix lint * doc: add comment
This commit is contained in:
parent
40b88f04f6
commit
37944e18d6
@ -32,8 +32,8 @@ export const mockDataMaskInfo: DataMaskStateWithId = {
|
|||||||
|
|
||||||
export const nativeFiltersInfo: NativeFiltersState = {
|
export const nativeFiltersInfo: NativeFiltersState = {
|
||||||
filterSets: {
|
filterSets: {
|
||||||
'set-id': {
|
'1': {
|
||||||
id: 'DefaultsID',
|
id: 1,
|
||||||
name: 'Set name',
|
name: 'Set name',
|
||||||
nativeFilters: {},
|
nativeFilters: {},
|
||||||
dataMask: mockDataMaskInfo,
|
dataMask: mockDataMaskInfo,
|
||||||
|
@ -280,7 +280,6 @@ export const hydrateDashboard = (dashboardData, chartData) => (
|
|||||||
|
|
||||||
const nativeFilters = getInitialNativeFilterState({
|
const nativeFilters = getInitialNativeFilterState({
|
||||||
filterConfig: metadata?.native_filter_configuration || [],
|
filterConfig: metadata?.native_filter_configuration || [],
|
||||||
filterSetsConfig: metadata?.filter_sets_configuration || [],
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!metadata) {
|
if (!metadata) {
|
||||||
|
@ -20,7 +20,6 @@
|
|||||||
import { makeApi } from '@superset-ui/core';
|
import { makeApi } from '@superset-ui/core';
|
||||||
import { Dispatch } from 'redux';
|
import { Dispatch } from 'redux';
|
||||||
import { FilterConfiguration } from 'src/dashboard/components/nativeFilters/types';
|
import { FilterConfiguration } from 'src/dashboard/components/nativeFilters/types';
|
||||||
import { DataMaskType, DataMaskStateWithId } from 'src/dataMask/types';
|
|
||||||
import {
|
import {
|
||||||
SET_DATA_MASK_FOR_FILTER_CONFIG_FAIL,
|
SET_DATA_MASK_FOR_FILTER_CONFIG_FAIL,
|
||||||
setDataMaskForFilterConfigComplete,
|
setDataMaskForFilterConfigComplete,
|
||||||
@ -28,17 +27,19 @@ import {
|
|||||||
import { HYDRATE_DASHBOARD } from './hydrate';
|
import { HYDRATE_DASHBOARD } from './hydrate';
|
||||||
import { dashboardInfoChanged } from './dashboardInfo';
|
import { dashboardInfoChanged } from './dashboardInfo';
|
||||||
import {
|
import {
|
||||||
DashboardInfo,
|
|
||||||
Filters,
|
Filters,
|
||||||
FilterSet,
|
FilterSet,
|
||||||
|
FilterSetFullData,
|
||||||
FilterSets,
|
FilterSets,
|
||||||
} from '../reducers/types';
|
} from '../reducers/types';
|
||||||
|
import { DashboardInfo, RootState } from '../types';
|
||||||
|
|
||||||
export const SET_FILTER_CONFIG_BEGIN = 'SET_FILTER_CONFIG_BEGIN';
|
export const SET_FILTER_CONFIG_BEGIN = 'SET_FILTER_CONFIG_BEGIN';
|
||||||
export interface SetFilterConfigBegin {
|
export interface SetFilterConfigBegin {
|
||||||
type: typeof SET_FILTER_CONFIG_BEGIN;
|
type: typeof SET_FILTER_CONFIG_BEGIN;
|
||||||
filterConfig: FilterConfiguration;
|
filterConfig: FilterConfiguration;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SET_FILTER_CONFIG_COMPLETE = 'SET_FILTER_CONFIG_COMPLETE';
|
export const SET_FILTER_CONFIG_COMPLETE = 'SET_FILTER_CONFIG_COMPLETE';
|
||||||
export interface SetFilterConfigComplete {
|
export interface SetFilterConfigComplete {
|
||||||
type: typeof SET_FILTER_CONFIG_COMPLETE;
|
type: typeof SET_FILTER_CONFIG_COMPLETE;
|
||||||
@ -54,21 +55,60 @@ export interface SetInScopeStatusOfFilters {
|
|||||||
type: typeof SET_IN_SCOPE_STATUS_OF_FILTERS;
|
type: typeof SET_IN_SCOPE_STATUS_OF_FILTERS;
|
||||||
filterConfig: FilterConfiguration;
|
filterConfig: FilterConfiguration;
|
||||||
}
|
}
|
||||||
export const SET_FILTER_SETS_CONFIG_BEGIN = 'SET_FILTER_SETS_CONFIG_BEGIN';
|
export const SET_FILTER_SETS_BEGIN = 'SET_FILTER_SETS_BEGIN';
|
||||||
export interface SetFilterSetsConfigBegin {
|
export interface SetFilterSetsBegin {
|
||||||
type: typeof SET_FILTER_SETS_CONFIG_BEGIN;
|
type: typeof SET_FILTER_SETS_BEGIN;
|
||||||
filterSetsConfig: FilterSet[];
|
|
||||||
}
|
}
|
||||||
export const SET_FILTER_SETS_CONFIG_COMPLETE =
|
export const SET_FILTER_SETS_COMPLETE = 'SET_FILTER_SETS_COMPLETE';
|
||||||
'SET_FILTER_SETS_CONFIG_COMPLETE';
|
export interface SetFilterSetsComplete {
|
||||||
export interface SetFilterSetsConfigComplete {
|
type: typeof SET_FILTER_SETS_COMPLETE;
|
||||||
type: typeof SET_FILTER_SETS_CONFIG_COMPLETE;
|
filterSets: FilterSet[];
|
||||||
filterSetsConfig: FilterSet[];
|
|
||||||
}
|
}
|
||||||
export const SET_FILTER_SETS_CONFIG_FAIL = 'SET_FILTER_SETS_CONFIG_FAIL';
|
export const SET_FILTER_SETS_FAIL = 'SET_FILTER_SETS_FAIL';
|
||||||
export interface SetFilterSetsConfigFail {
|
export interface SetFilterSetsFail {
|
||||||
type: typeof SET_FILTER_SETS_CONFIG_FAIL;
|
type: typeof SET_FILTER_SETS_FAIL;
|
||||||
filterSetsConfig: FilterSet[];
|
}
|
||||||
|
|
||||||
|
export const CREATE_FILTER_SET_BEGIN = 'CREATE_FILTER_SET_BEGIN';
|
||||||
|
export interface CreateFilterSetBegin {
|
||||||
|
type: typeof CREATE_FILTER_SET_BEGIN;
|
||||||
|
}
|
||||||
|
export const CREATE_FILTER_SET_COMPLETE = 'CREATE_FILTER_SET_COMPLETE';
|
||||||
|
export interface CreateFilterSetComplete {
|
||||||
|
type: typeof CREATE_FILTER_SET_COMPLETE;
|
||||||
|
filterSet: FilterSet;
|
||||||
|
}
|
||||||
|
export const CREATE_FILTER_SET_FAIL = 'CREATE_FILTER_SET_FAIL';
|
||||||
|
export interface CreateFilterSetFail {
|
||||||
|
type: typeof CREATE_FILTER_SET_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DELETE_FILTER_SET_BEGIN = 'DELETE_FILTER_SET_BEGIN';
|
||||||
|
export interface DeleteFilterSetBegin {
|
||||||
|
type: typeof DELETE_FILTER_SET_BEGIN;
|
||||||
|
}
|
||||||
|
export const DELETE_FILTER_SET_COMPLETE = 'DELETE_FILTER_SET_COMPLETE';
|
||||||
|
export interface DeleteFilterSetComplete {
|
||||||
|
type: typeof DELETE_FILTER_SET_COMPLETE;
|
||||||
|
filterSet: FilterSet;
|
||||||
|
}
|
||||||
|
export const DELETE_FILTER_SET_FAIL = 'DELETE_FILTER_SET_FAIL';
|
||||||
|
export interface DeleteFilterSetFail {
|
||||||
|
type: typeof DELETE_FILTER_SET_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const UPDATE_FILTER_SET_BEGIN = 'UPDATE_FILTER_SET_BEGIN';
|
||||||
|
export interface UpdateFilterSetBegin {
|
||||||
|
type: typeof UPDATE_FILTER_SET_BEGIN;
|
||||||
|
}
|
||||||
|
export const UPDATE_FILTER_SET_COMPLETE = 'UPDATE_FILTER_SET_COMPLETE';
|
||||||
|
export interface UpdateFilterSetComplete {
|
||||||
|
type: typeof UPDATE_FILTER_SET_COMPLETE;
|
||||||
|
filterSet: FilterSet;
|
||||||
|
}
|
||||||
|
export const UPDATE_FILTER_SET_FAIL = 'UPDATE_FILTER_SET_FAIL';
|
||||||
|
export interface UpdateFilterSetFail {
|
||||||
|
type: typeof UPDATE_FILTER_SET_FAIL;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const setFilterConfiguration = (
|
export const setFilterConfiguration = (
|
||||||
@ -161,66 +201,138 @@ export interface SetBootstrapData {
|
|||||||
data: BootstrapData;
|
data: BootstrapData;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const setFilterSetsConfiguration = (
|
export const getFilterSets = () => async (
|
||||||
filterSetsConfig: FilterSet[],
|
dispatch: Dispatch,
|
||||||
) => async (dispatch: Dispatch, getState: () => any) => {
|
getState: () => RootState,
|
||||||
dispatch({
|
) => {
|
||||||
type: SET_FILTER_SETS_CONFIG_BEGIN,
|
const dashboardId = getState().dashboardInfo.id;
|
||||||
filterSetsConfig,
|
const fetchFilterSets = makeApi<
|
||||||
});
|
null,
|
||||||
const { id, metadata } = getState().dashboardInfo;
|
{
|
||||||
|
count: number;
|
||||||
// TODO extract this out when makeApi supports url parameters
|
ids: number[];
|
||||||
const updateDashboard = makeApi<
|
result: FilterSetFullData[];
|
||||||
Partial<DashboardInfo>,
|
}
|
||||||
{ result: DashboardInfo }
|
|
||||||
>({
|
>({
|
||||||
method: 'PUT',
|
method: 'GET',
|
||||||
endpoint: `/api/v1/dashboard/${id}`,
|
endpoint: `/api/v1/dashboard/${dashboardId}/filtersets`,
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
dispatch({
|
||||||
const response = await updateDashboard({
|
type: SET_FILTER_SETS_BEGIN,
|
||||||
json_metadata: JSON.stringify({
|
});
|
||||||
...metadata,
|
|
||||||
filter_sets_configuration: filterSetsConfig,
|
const response = await fetchFilterSets(null);
|
||||||
}),
|
|
||||||
});
|
dispatch({
|
||||||
const newMetadata = JSON.parse(response.result.json_metadata);
|
type: SET_FILTER_SETS_COMPLETE,
|
||||||
dispatch(
|
filterSets: response.ids.map((id, i) => ({
|
||||||
dashboardInfoChanged({
|
...response.result[i].params,
|
||||||
metadata: newMetadata,
|
id,
|
||||||
}),
|
name: response.result[i].name,
|
||||||
);
|
})),
|
||||||
dispatch({
|
});
|
||||||
type: SET_FILTER_SETS_CONFIG_COMPLETE,
|
|
||||||
filterSetsConfig: newMetadata?.filter_sets_configuration,
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
dispatch({ type: SET_FILTER_SETS_CONFIG_FAIL, filterSetsConfig });
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SAVE_FILTER_SETS = 'SAVE_FILTER_SETS';
|
export const createFilterSet = (filterSet: Omit<FilterSet, 'id'>) => async (
|
||||||
export interface SaveFilterSets {
|
dispatch: Function,
|
||||||
type: typeof SAVE_FILTER_SETS;
|
getState: () => RootState,
|
||||||
name: string;
|
) => {
|
||||||
dataMask: Pick<DataMaskStateWithId, DataMaskType.NativeFilters>;
|
const dashboardId = getState().dashboardInfo.id;
|
||||||
filtersSetId: string;
|
const postFilterSets = makeApi<
|
||||||
}
|
Partial<FilterSetFullData & { json_metadata: any }>,
|
||||||
|
{
|
||||||
|
count: number;
|
||||||
|
ids: number[];
|
||||||
|
result: FilterSetFullData[];
|
||||||
|
}
|
||||||
|
>({
|
||||||
|
method: 'POST',
|
||||||
|
endpoint: `/api/v1/dashboard/${dashboardId}/filtersets`,
|
||||||
|
});
|
||||||
|
|
||||||
export function saveFilterSets(
|
dispatch({
|
||||||
name: string,
|
type: CREATE_FILTER_SET_BEGIN,
|
||||||
filtersSetId: string,
|
});
|
||||||
dataMask: Pick<DataMaskStateWithId, DataMaskType.NativeFilters>,
|
|
||||||
): SaveFilterSets {
|
const serverFilterSet: Omit<FilterSet, 'id' | 'name'> & { name?: string } = {
|
||||||
return {
|
...filterSet,
|
||||||
type: SAVE_FILTER_SETS,
|
|
||||||
name,
|
|
||||||
filtersSetId,
|
|
||||||
dataMask,
|
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
delete serverFilterSet.name;
|
||||||
|
|
||||||
|
await postFilterSets({
|
||||||
|
name: filterSet.name,
|
||||||
|
owner_type: 'Dashboard',
|
||||||
|
owner_id: dashboardId,
|
||||||
|
json_metadata: JSON.stringify(serverFilterSet),
|
||||||
|
});
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: CREATE_FILTER_SET_COMPLETE,
|
||||||
|
});
|
||||||
|
dispatch(getFilterSets());
|
||||||
|
};
|
||||||
|
|
||||||
|
export const updateFilterSet = (filterSet: FilterSet) => async (
|
||||||
|
dispatch: Function,
|
||||||
|
getState: () => RootState,
|
||||||
|
) => {
|
||||||
|
const dashboardId = getState().dashboardInfo.id;
|
||||||
|
const postFilterSets = makeApi<
|
||||||
|
Partial<FilterSetFullData & { json_metadata: any }>,
|
||||||
|
{}
|
||||||
|
>({
|
||||||
|
method: 'PUT',
|
||||||
|
endpoint: `/api/v1/dashboard/${dashboardId}/filtersets/${filterSet.id}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: UPDATE_FILTER_SET_BEGIN,
|
||||||
|
});
|
||||||
|
|
||||||
|
const serverFilterSet: Omit<FilterSet, 'id' | 'name'> & {
|
||||||
|
name?: string;
|
||||||
|
id?: number;
|
||||||
|
} = {
|
||||||
|
...filterSet,
|
||||||
|
};
|
||||||
|
|
||||||
|
delete serverFilterSet.id;
|
||||||
|
delete serverFilterSet.name;
|
||||||
|
|
||||||
|
await postFilterSets({
|
||||||
|
name: filterSet.name,
|
||||||
|
json_metadata: JSON.stringify(serverFilterSet),
|
||||||
|
});
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: UPDATE_FILTER_SET_COMPLETE,
|
||||||
|
});
|
||||||
|
dispatch(getFilterSets());
|
||||||
|
};
|
||||||
|
|
||||||
|
export const deleteFilterSet = (filterSetId: number) => async (
|
||||||
|
dispatch: Function,
|
||||||
|
getState: () => RootState,
|
||||||
|
) => {
|
||||||
|
const dashboardId = getState().dashboardInfo.id;
|
||||||
|
const deleteFilterSets = makeApi<{}, {}>({
|
||||||
|
method: 'DELETE',
|
||||||
|
endpoint: `/api/v1/dashboard/${dashboardId}/filtersets/${filterSetId}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: DELETE_FILTER_SET_BEGIN,
|
||||||
|
});
|
||||||
|
|
||||||
|
await deleteFilterSets({});
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: DELETE_FILTER_SET_COMPLETE,
|
||||||
|
});
|
||||||
|
dispatch(getFilterSets());
|
||||||
|
};
|
||||||
|
|
||||||
export const SET_FOCUSED_NATIVE_FILTER = 'SET_FOCUSED_NATIVE_FILTER';
|
export const SET_FOCUSED_NATIVE_FILTER = 'SET_FOCUSED_NATIVE_FILTER';
|
||||||
export interface SetFocusedNativeFilter {
|
export interface SetFocusedNativeFilter {
|
||||||
@ -248,11 +360,19 @@ export type AnyFilterAction =
|
|||||||
| SetFilterConfigBegin
|
| SetFilterConfigBegin
|
||||||
| SetFilterConfigComplete
|
| SetFilterConfigComplete
|
||||||
| SetFilterConfigFail
|
| SetFilterConfigFail
|
||||||
| SetFilterSetsConfigBegin
|
| SetFilterSetsBegin
|
||||||
| SetFilterSetsConfigComplete
|
| SetFilterSetsComplete
|
||||||
| SetFilterSetsConfigFail
|
| SetFilterSetsFail
|
||||||
| SetInScopeStatusOfFilters
|
| SetInScopeStatusOfFilters
|
||||||
| SaveFilterSets
|
|
||||||
| SetBootstrapData
|
| SetBootstrapData
|
||||||
| SetFocusedNativeFilter
|
| SetFocusedNativeFilter
|
||||||
| UnsetFocusedNativeFilter;
|
| UnsetFocusedNativeFilter
|
||||||
|
| CreateFilterSetBegin
|
||||||
|
| CreateFilterSetComplete
|
||||||
|
| CreateFilterSetFail
|
||||||
|
| DeleteFilterSetBegin
|
||||||
|
| DeleteFilterSetComplete
|
||||||
|
| DeleteFilterSetFail
|
||||||
|
| UpdateFilterSetBegin
|
||||||
|
| UpdateFilterSetComplete
|
||||||
|
| UpdateFilterSetFail;
|
||||||
|
@ -24,7 +24,7 @@ import { Provider } from 'react-redux';
|
|||||||
import EditSection, { EditSectionProps } from './EditSection';
|
import EditSection, { EditSectionProps } from './EditSection';
|
||||||
|
|
||||||
const createProps = () => ({
|
const createProps = () => ({
|
||||||
filterSetId: 'set-id',
|
filterSetId: 1,
|
||||||
dataMaskSelected: {
|
dataMaskSelected: {
|
||||||
DefaultsID: {
|
DefaultsID: {
|
||||||
filterState: {
|
filterState: {
|
||||||
|
@ -21,7 +21,7 @@ import { HandlerFunction, styled, t } from '@superset-ui/core';
|
|||||||
import { Typography, Tooltip } from 'src/common/components';
|
import { Typography, Tooltip } from 'src/common/components';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
import Button from 'src/components/Button';
|
import Button from 'src/components/Button';
|
||||||
import { setFilterSetsConfiguration } from 'src/dashboard/actions/nativeFilters';
|
import { updateFilterSet } from 'src/dashboard/actions/nativeFilters';
|
||||||
import { DataMaskState } from 'src/dataMask/types';
|
import { DataMaskState } from 'src/dataMask/types';
|
||||||
import { WarningOutlined } from '@ant-design/icons';
|
import { WarningOutlined } from '@ant-design/icons';
|
||||||
import { ActionButtons } from './Footer';
|
import { ActionButtons } from './Footer';
|
||||||
@ -60,7 +60,7 @@ const ActionButton = styled.div<{ disabled?: boolean }>`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
export type EditSectionProps = {
|
export type EditSectionProps = {
|
||||||
filterSetId: string;
|
filterSetId: number;
|
||||||
dataMaskSelected: DataMaskState;
|
dataMaskSelected: DataMaskState;
|
||||||
onCancel: HandlerFunction;
|
onCancel: HandlerFunction;
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
@ -89,17 +89,12 @@ const EditSection: FC<EditSectionProps> = ({
|
|||||||
|
|
||||||
const handleSave = () => {
|
const handleSave = () => {
|
||||||
dispatch(
|
dispatch(
|
||||||
setFilterSetsConfiguration(
|
updateFilterSet({
|
||||||
filterSetFilterValues.map(filterSet => {
|
id: filterSetId,
|
||||||
const newFilterSet = {
|
name: filterSetName,
|
||||||
...filterSet,
|
nativeFilters: filters,
|
||||||
name: filterSetName,
|
dataMask: { ...dataMaskApplied },
|
||||||
nativeFilters: filters,
|
}),
|
||||||
dataMask: { ...dataMaskApplied },
|
|
||||||
};
|
|
||||||
return filterSetId === filterSet.id ? newFilterSet : filterSet;
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
onCancel();
|
onCancel();
|
||||||
};
|
};
|
||||||
|
@ -21,10 +21,11 @@ import { render, screen } from 'spec/helpers/testing-library';
|
|||||||
import { mockStore } from 'spec/fixtures/mockStore';
|
import { mockStore } from 'spec/fixtures/mockStore';
|
||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
import FilterSets, { FilterSetsProps } from '.';
|
import FilterSets, { FilterSetsProps } from '.';
|
||||||
|
import { TabIds } from '../utils';
|
||||||
|
|
||||||
const createProps = () => ({
|
const createProps = () => ({
|
||||||
disabled: false,
|
disabled: false,
|
||||||
isFilterSetChanged: false,
|
tab: TabIds.FilterSets,
|
||||||
dataMaskSelected: {
|
dataMaskSelected: {
|
||||||
DefaultsID: {
|
DefaultsID: {
|
||||||
filterState: {
|
filterState: {
|
||||||
|
@ -86,9 +86,13 @@ const FiltersHeader: FC<FiltersHeaderProps> = ({ dataMask, filterSet }) => {
|
|||||||
const getFilterRow = ({ id, name }: { id: string; name: string }) => {
|
const getFilterRow = ({ id, name }: { id: string; name: string }) => {
|
||||||
const changedFilter =
|
const changedFilter =
|
||||||
filterSet &&
|
filterSet &&
|
||||||
!areObjectsEqual(filters[id], filterSet?.nativeFilters?.[id], {
|
!areObjectsEqual(
|
||||||
ignoreUndefined: true,
|
filters[id]?.controlValues,
|
||||||
});
|
filterSet?.nativeFilters?.[id]?.controlValues,
|
||||||
|
{
|
||||||
|
ignoreUndefined: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
const removedFilter = !Object.keys(filters).includes(id);
|
const removedFilter = !Object.keys(filters).includes(id);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -17,27 +17,30 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useEffect, useState, MouseEvent } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { DataMask, HandlerFunction, styled, t } from '@superset-ui/core';
|
import { DataMask, HandlerFunction, styled, t } from '@superset-ui/core';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
import { DataMaskState, DataMaskWithId } from 'src/dataMask/types';
|
import { DataMaskState, DataMaskWithId } from 'src/dataMask/types';
|
||||||
import { setFilterSetsConfiguration } from 'src/dashboard/actions/nativeFilters';
|
import {
|
||||||
|
createFilterSet,
|
||||||
|
deleteFilterSet,
|
||||||
|
updateFilterSet,
|
||||||
|
} from 'src/dashboard/actions/nativeFilters';
|
||||||
import { Filters, FilterSet } from 'src/dashboard/reducers/types';
|
import { Filters, FilterSet } from 'src/dashboard/reducers/types';
|
||||||
import { areObjectsEqual } from 'src/reduxUtils';
|
import { areObjectsEqual } from 'src/reduxUtils';
|
||||||
import { findExistingFilterSet, generateFiltersSetId } from './utils';
|
import { findExistingFilterSet } from './utils';
|
||||||
import { Filter } from '../../types';
|
import { Filter } from '../../types';
|
||||||
import { useFilters, useNativeFiltersDataMask, useFilterSets } from '../state';
|
import { useFilters, useNativeFiltersDataMask, useFilterSets } from '../state';
|
||||||
import Footer from './Footer';
|
import Footer from './Footer';
|
||||||
import FilterSetUnit from './FilterSetUnit';
|
import FilterSetUnit from './FilterSetUnit';
|
||||||
import { getFilterBarTestId } from '..';
|
import { getFilterBarTestId } from '..';
|
||||||
|
import { TabIds } from '../utils';
|
||||||
|
|
||||||
const FilterSetsWrapper = styled.div`
|
const FilterSetsWrapper = styled.div`
|
||||||
display: grid;
|
display: grid;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
padding: ${({ theme }) => theme.gridUnit * 2}px
|
|
||||||
${({ theme }) => theme.gridUnit * 4}px;
|
|
||||||
|
|
||||||
& button.superset-button {
|
& button.superset-button {
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
@ -58,7 +61,7 @@ const FilterSetUnitWrapper = styled.div<{
|
|||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
grid-gap: ${theme.gridUnit}px;
|
grid-gap: ${theme.gridUnit}px;
|
||||||
border-bottom: 1px solid ${theme.colors.grayscale.light2};
|
border-bottom: 1px solid ${theme.colors.grayscale.light2};
|
||||||
padding: ${theme.gridUnit * 2}px 0px};
|
padding: ${theme.gridUnit * 2}px ${theme.gridUnit * 4}px;
|
||||||
cursor: ${!onClick ? 'auto' : 'pointer'};
|
cursor: ${!onClick ? 'auto' : 'pointer'};
|
||||||
background: ${selected ? theme.colors.primary.light5 : 'transparent'};
|
background: ${selected ? theme.colors.primary.light5 : 'transparent'};
|
||||||
`}
|
`}
|
||||||
@ -66,9 +69,9 @@ const FilterSetUnitWrapper = styled.div<{
|
|||||||
|
|
||||||
export type FilterSetsProps = {
|
export type FilterSetsProps = {
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
isFilterSetChanged: boolean;
|
tab: TabIds;
|
||||||
dataMaskSelected: DataMaskState;
|
dataMaskSelected: DataMaskState;
|
||||||
onEditFilterSet: (id: string) => void;
|
onEditFilterSet: (id: number) => void;
|
||||||
onFilterSelectionChange: (
|
onFilterSelectionChange: (
|
||||||
filter: Pick<Filter, 'id'> & Partial<Filter>,
|
filter: Pick<Filter, 'id'> & Partial<Filter>,
|
||||||
dataMask: Partial<DataMask>,
|
dataMask: Partial<DataMask>,
|
||||||
@ -82,7 +85,7 @@ const FilterSets: React.FC<FilterSetsProps> = ({
|
|||||||
onEditFilterSet,
|
onEditFilterSet,
|
||||||
disabled,
|
disabled,
|
||||||
onFilterSelectionChange,
|
onFilterSelectionChange,
|
||||||
isFilterSetChanged,
|
tab,
|
||||||
}) => {
|
}) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const [filterSetName, setFilterSetName] = useState(DEFAULT_FILTER_SET_NAME);
|
const [filterSetName, setFilterSetName] = useState(DEFAULT_FILTER_SET_NAME);
|
||||||
@ -93,11 +96,15 @@ const FilterSets: React.FC<FilterSetsProps> = ({
|
|||||||
const filters = useFilters();
|
const filters = useFilters();
|
||||||
const filterValues = Object.values(filters) as Filter[];
|
const filterValues = Object.values(filters) as Filter[];
|
||||||
const [selectedFiltersSetId, setSelectedFiltersSetId] = useState<
|
const [selectedFiltersSetId, setSelectedFiltersSetId] = useState<
|
||||||
string | null
|
number | null
|
||||||
>(null);
|
>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isFilterSetChanged) {
|
if (tab === TabIds.AllFilters) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!filterSetFilterValues.length) {
|
||||||
|
setSelectedFiltersSetId(null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,34 +112,35 @@ const FilterSets: React.FC<FilterSetsProps> = ({
|
|||||||
dataMaskSelected,
|
dataMaskSelected,
|
||||||
filterSetFilterValues,
|
filterSetFilterValues,
|
||||||
});
|
});
|
||||||
|
|
||||||
setSelectedFiltersSetId(foundFilterSet?.id ?? null);
|
setSelectedFiltersSetId(foundFilterSet?.id ?? null);
|
||||||
}, [isFilterSetChanged, dataMaskSelected, filterSetFilterValues]);
|
}, [tab, dataMaskSelected, filterSetFilterValues]);
|
||||||
|
|
||||||
const isFilterMissingOrContainsInvalidMetadata = (
|
const isFilterMissingOrContainsInvalidMetadata = (
|
||||||
id: string,
|
id: string,
|
||||||
filterSet?: FilterSet,
|
filterSet?: FilterSet,
|
||||||
) =>
|
) =>
|
||||||
!filterValues.find(filter => filter?.id === id) ||
|
!filterValues.find(filter => filter?.id === id) ||
|
||||||
!areObjectsEqual(filters[id], filterSet?.nativeFilters?.[id], {
|
!areObjectsEqual(
|
||||||
ignoreUndefined: true,
|
filters[id]?.controlValues,
|
||||||
});
|
filterSet?.nativeFilters?.[id]?.controlValues,
|
||||||
|
{
|
||||||
|
ignoreUndefined: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
const takeFilterSet = (id: string, target?: HTMLElement) => {
|
const takeFilterSet = (id: number, event?: MouseEvent) => {
|
||||||
const ignoreSelectorHeader = 'ant-collapse-header';
|
const localTarget = event?.target as HTMLDivElement;
|
||||||
const ignoreSelectorDropdown = 'ant-dropdown-menu-item';
|
if (localTarget) {
|
||||||
if (
|
const parent = localTarget.closest(
|
||||||
target?.classList.contains(ignoreSelectorHeader) ||
|
`[data-anchor=${getFilterBarTestId('filter-set-wrapper', true)}]`,
|
||||||
target?.classList.contains(ignoreSelectorDropdown) ||
|
);
|
||||||
target?.parentElement?.classList.contains(ignoreSelectorHeader) ||
|
if (
|
||||||
target?.parentElement?.parentElement?.classList.contains(
|
parent?.querySelector('.ant-collapse-header')?.contains(localTarget) ||
|
||||||
ignoreSelectorHeader,
|
localTarget?.closest('.ant-dropdown')
|
||||||
) ||
|
) {
|
||||||
target?.parentElement?.parentElement?.parentElement?.classList.contains(
|
return;
|
||||||
ignoreSelectorHeader,
|
}
|
||||||
)
|
|
||||||
) {
|
|
||||||
// We don't want select filter set when user expand filters
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
setSelectedFiltersSetId(id);
|
setSelectedFiltersSetId(id);
|
||||||
if (!id) {
|
if (!id) {
|
||||||
@ -152,7 +160,7 @@ const FilterSets: React.FC<FilterSetsProps> = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRebuild = (id: string) => {
|
const handleRebuild = (id: number) => {
|
||||||
const filterSet = filterSets[id];
|
const filterSet = filterSets[id];
|
||||||
// We need remove invalid filters from filter set
|
// We need remove invalid filters from filter set
|
||||||
const newFilters = Object.values(filterSet?.dataMask ?? {})
|
const newFilters = Object.values(filterSet?.dataMask ?? {})
|
||||||
@ -179,29 +187,16 @@ const FilterSets: React.FC<FilterSetsProps> = ({
|
|||||||
{},
|
{},
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
dispatch(
|
dispatch(updateFilterSet(updatedFilterSet));
|
||||||
setFilterSetsConfiguration(
|
|
||||||
filterSetFilterValues.map(filterSetIt => {
|
|
||||||
const isEquals = filterSetIt.id === updatedFilterSet.id;
|
|
||||||
return isEquals ? updatedFilterSet : filterSetIt;
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleEdit = (id: string) => {
|
const handleEdit = (id: number) => {
|
||||||
takeFilterSet(id);
|
takeFilterSet(id);
|
||||||
onEditFilterSet(id);
|
onEditFilterSet(id);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDeleteFilterSet = (filterSetId: string) => {
|
const handleDeleteFilterSet = (filterSetId: number) => {
|
||||||
dispatch(
|
dispatch(deleteFilterSet(filterSetId));
|
||||||
setFilterSetsConfiguration(
|
|
||||||
filterSetFilterValues.filter(
|
|
||||||
filtersSet => filtersSet.id !== filterSetId,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
if (filterSetId === selectedFiltersSetId) {
|
if (filterSetId === selectedFiltersSetId) {
|
||||||
setSelectedFiltersSetId(null);
|
setSelectedFiltersSetId(null);
|
||||||
}
|
}
|
||||||
@ -213,9 +208,8 @@ const FilterSets: React.FC<FilterSetsProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleCreateFilterSet = () => {
|
const handleCreateFilterSet = () => {
|
||||||
const newFilterSet: FilterSet = {
|
const newFilterSet: Omit<FilterSet, 'id'> = {
|
||||||
name: filterSetName.trim(),
|
name: filterSetName.trim(),
|
||||||
id: generateFiltersSetId(),
|
|
||||||
nativeFilters: filters,
|
nativeFilters: filters,
|
||||||
dataMask: Object.keys(filters).reduce(
|
dataMask: Object.keys(filters).reduce(
|
||||||
(prev, nextFilterId) => ({
|
(prev, nextFilterId) => ({
|
||||||
@ -225,9 +219,7 @@ const FilterSets: React.FC<FilterSetsProps> = ({
|
|||||||
{},
|
{},
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
dispatch(
|
dispatch(createFilterSet(newFilterSet));
|
||||||
setFilterSetsConfiguration([newFilterSet].concat(filterSetFilterValues)),
|
|
||||||
);
|
|
||||||
setEditMode(false);
|
setEditMode(false);
|
||||||
setFilterSetName(DEFAULT_FILTER_SET_NAME);
|
setFilterSetName(DEFAULT_FILTER_SET_NAME);
|
||||||
};
|
};
|
||||||
@ -255,9 +247,11 @@ const FilterSets: React.FC<FilterSetsProps> = ({
|
|||||||
{filterSetFilterValues.map(filterSet => (
|
{filterSetFilterValues.map(filterSet => (
|
||||||
<FilterSetUnitWrapper
|
<FilterSetUnitWrapper
|
||||||
{...getFilterBarTestId('filter-set-wrapper')}
|
{...getFilterBarTestId('filter-set-wrapper')}
|
||||||
|
data-anchor={getFilterBarTestId('filter-set-wrapper', true)}
|
||||||
data-selected={filterSet.id === selectedFiltersSetId}
|
data-selected={filterSet.id === selectedFiltersSetId}
|
||||||
onClick={(e: MouseEvent<HTMLElement>) =>
|
onClick={
|
||||||
takeFilterSet(filterSet.id, e.target as HTMLElement)
|
(e =>
|
||||||
|
takeFilterSet(filterSet.id, e as MouseEvent)) as HandlerFunction
|
||||||
}
|
}
|
||||||
key={filterSet.id}
|
key={filterSet.id}
|
||||||
>
|
>
|
||||||
|
@ -28,7 +28,7 @@ test('Should find correct filter', () => {
|
|||||||
const dataMaskSelected = createDataMaskSelected();
|
const dataMaskSelected = createDataMaskSelected();
|
||||||
const filterSetFilterValues: FilterSet[] = [
|
const filterSetFilterValues: FilterSet[] = [
|
||||||
{
|
{
|
||||||
id: 'id-01',
|
id: 1,
|
||||||
name: 'name-01',
|
name: 'name-01',
|
||||||
nativeFilters: {},
|
nativeFilters: {},
|
||||||
dataMask: {
|
dataMask: {
|
||||||
@ -46,7 +46,7 @@ test('Should find correct filter', () => {
|
|||||||
filterId: { id: 'filterId', filterState: { value: 'value-1' } },
|
filterId: { id: 'filterId', filterState: { value: 'value-1' } },
|
||||||
filterId2: { id: 'filterId2', filterState: { value: 'value-2' } },
|
filterId2: { id: 'filterId2', filterState: { value: 'value-2' } },
|
||||||
},
|
},
|
||||||
id: 'id-01',
|
id: 1,
|
||||||
name: 'name-01',
|
name: 'name-01',
|
||||||
nativeFilters: {},
|
nativeFilters: {},
|
||||||
});
|
});
|
||||||
@ -56,7 +56,7 @@ test('Should return undefined when nativeFilters has less values', () => {
|
|||||||
const dataMaskSelected = createDataMaskSelected();
|
const dataMaskSelected = createDataMaskSelected();
|
||||||
const filterSetFilterValues = [
|
const filterSetFilterValues = [
|
||||||
{
|
{
|
||||||
id: 'id-01',
|
id: 1,
|
||||||
name: 'name-01',
|
name: 'name-01',
|
||||||
nativeFilters: {},
|
nativeFilters: {},
|
||||||
dataMask: {
|
dataMask: {
|
||||||
@ -75,7 +75,7 @@ test('Should return undefined when nativeFilters has different values', () => {
|
|||||||
const dataMaskSelected = createDataMaskSelected();
|
const dataMaskSelected = createDataMaskSelected();
|
||||||
const filterSetFilterValues: FilterSet[] = [
|
const filterSetFilterValues: FilterSet[] = [
|
||||||
{
|
{
|
||||||
id: 'id-01',
|
id: 1,
|
||||||
name: 'name-01',
|
name: 'name-01',
|
||||||
nativeFilters: {},
|
nativeFilters: {},
|
||||||
dataMask: {
|
dataMask: {
|
||||||
@ -95,7 +95,7 @@ test('Should return undefined when dataMask:{}', () => {
|
|||||||
const dataMaskSelected = createDataMaskSelected();
|
const dataMaskSelected = createDataMaskSelected();
|
||||||
const filterSetFilterValues = [
|
const filterSetFilterValues = [
|
||||||
{
|
{
|
||||||
id: 'id-01',
|
id: 1,
|
||||||
name: 'name-01',
|
name: 'name-01',
|
||||||
nativeFilters: {},
|
nativeFilters: {},
|
||||||
dataMask: {},
|
dataMask: {},
|
||||||
@ -112,7 +112,7 @@ test('Should return undefined when dataMask is empty}', () => {
|
|||||||
const dataMaskSelected = createDataMaskSelected();
|
const dataMaskSelected = createDataMaskSelected();
|
||||||
const filterSetFilterValues: FilterSet[] = [
|
const filterSetFilterValues: FilterSet[] = [
|
||||||
{
|
{
|
||||||
id: 'id-01',
|
id: 1,
|
||||||
name: 'name-01',
|
name: 'name-01',
|
||||||
nativeFilters: {},
|
nativeFilters: {},
|
||||||
dataMask: {},
|
dataMask: {},
|
||||||
|
@ -58,6 +58,7 @@ export const findExistingFilterSet = ({
|
|||||||
const isEqual = areObjectsEqual(
|
const isEqual = areObjectsEqual(
|
||||||
filterFromSelectedFilters.filterState,
|
filterFromSelectedFilters.filterState,
|
||||||
dataMaskFromFilterSet?.[id]?.filterState,
|
dataMaskFromFilterSet?.[id]?.filterState,
|
||||||
|
{ ignoreUndefined: true, ignoreNull: true },
|
||||||
);
|
);
|
||||||
const hasSamePropsNumber =
|
const hasSamePropsNumber =
|
||||||
dataMaskSelectedEntries.length ===
|
dataMaskSelectedEntries.length ===
|
||||||
|
@ -150,7 +150,7 @@ const FilterBar: React.FC<FiltersBarProps> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const dataMaskApplied: DataMaskStateWithId = useNativeFiltersDataMask();
|
const dataMaskApplied: DataMaskStateWithId = useNativeFiltersDataMask();
|
||||||
const [editFilterSetId, setEditFilterSetId] = useState<string | null>(null);
|
const [editFilterSetId, setEditFilterSetId] = useState<number | null>(null);
|
||||||
const [dataMaskSelected, setDataMaskSelected] = useImmer<DataMaskStateWithId>(
|
const [dataMaskSelected, setDataMaskSelected] = useImmer<DataMaskStateWithId>(
|
||||||
dataMaskApplied,
|
dataMaskApplied,
|
||||||
);
|
);
|
||||||
@ -161,13 +161,11 @@ const FilterBar: React.FC<FiltersBarProps> = ({
|
|||||||
const filters = useFilters();
|
const filters = useFilters();
|
||||||
const previousFilters = usePrevious(filters);
|
const previousFilters = usePrevious(filters);
|
||||||
const filterValues = Object.values<Filter>(filters);
|
const filterValues = Object.values<Filter>(filters);
|
||||||
const [isFilterSetChanged, setIsFilterSetChanged] = useState(false);
|
|
||||||
|
|
||||||
const handleFilterSelectionChange = (
|
const handleFilterSelectionChange = (
|
||||||
filter: Pick<Filter, 'id'> & Partial<Filter>,
|
filter: Pick<Filter, 'id'> & Partial<Filter>,
|
||||||
dataMask: Partial<DataMask>,
|
dataMask: Partial<DataMask>,
|
||||||
) => {
|
) => {
|
||||||
setIsFilterSetChanged(tab !== TabIds.AllFilters);
|
|
||||||
setDataMaskSelected(draft => {
|
setDataMaskSelected(draft => {
|
||||||
// force instant updating on initialization for filters with `requiredFirst` is true or instant filters
|
// force instant updating on initialization for filters with `requiredFirst` is true or instant filters
|
||||||
if (
|
if (
|
||||||
@ -341,7 +339,7 @@ const FilterBar: React.FC<FiltersBarProps> = ({
|
|||||||
onEditFilterSet={setEditFilterSetId}
|
onEditFilterSet={setEditFilterSetId}
|
||||||
disabled={!isApplyDisabled}
|
disabled={!isApplyDisabled}
|
||||||
dataMaskSelected={dataMaskSelected}
|
dataMaskSelected={dataMaskSelected}
|
||||||
isFilterSetChanged={isFilterSetChanged}
|
tab={tab}
|
||||||
onFilterSelectionChange={handleFilterSelectionChange}
|
onFilterSelectionChange={handleFilterSelectionChange}
|
||||||
/>
|
/>
|
||||||
</Tabs.TabPane>
|
</Tabs.TabPane>
|
||||||
|
@ -16,8 +16,8 @@
|
|||||||
* specific language governing permissions and limitations
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import React, { useEffect, useRef, FC } from 'react';
|
import React, { FC, useRef, useEffect } from 'react';
|
||||||
import { t } from '@superset-ui/core';
|
import { FeatureFlag, isFeatureEnabled, t } from '@superset-ui/core';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
import { useToasts } from 'src/components/MessageToasts/withToasts';
|
import { useToasts } from 'src/components/MessageToasts/withToasts';
|
||||||
@ -31,6 +31,7 @@ import { hydrateDashboard } from 'src/dashboard/actions/hydrate';
|
|||||||
import { setDatasources } from 'src/dashboard/actions/datasources';
|
import { setDatasources } from 'src/dashboard/actions/datasources';
|
||||||
import injectCustomCss from 'src/dashboard/util/injectCustomCss';
|
import injectCustomCss from 'src/dashboard/util/injectCustomCss';
|
||||||
import setupPlugins from 'src/setup/setupPlugins';
|
import setupPlugins from 'src/setup/setupPlugins';
|
||||||
|
import { getFilterSets } from '../actions/nativeFilters';
|
||||||
|
|
||||||
setupPlugins();
|
setupPlugins();
|
||||||
const DashboardContainer = React.lazy(
|
const DashboardContainer = React.lazy(
|
||||||
@ -66,6 +67,9 @@ const DashboardPage: FC = () => {
|
|||||||
if (readyToRender && !isDashboardHydrated.current) {
|
if (readyToRender && !isDashboardHydrated.current) {
|
||||||
isDashboardHydrated.current = true;
|
isDashboardHydrated.current = true;
|
||||||
dispatch(hydrateDashboard(dashboard, charts));
|
dispatch(hydrateDashboard(dashboard, charts));
|
||||||
|
if (isFeatureEnabled(FeatureFlag.DASHBOARD_NATIVE_FILTERS_SET)) {
|
||||||
|
dispatch(getFilterSets());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -18,10 +18,9 @@
|
|||||||
*/
|
*/
|
||||||
import {
|
import {
|
||||||
AnyFilterAction,
|
AnyFilterAction,
|
||||||
SAVE_FILTER_SETS,
|
|
||||||
SET_FILTER_CONFIG_COMPLETE,
|
SET_FILTER_CONFIG_COMPLETE,
|
||||||
SET_IN_SCOPE_STATUS_OF_FILTERS,
|
SET_IN_SCOPE_STATUS_OF_FILTERS,
|
||||||
SET_FILTER_SETS_CONFIG_COMPLETE,
|
SET_FILTER_SETS_COMPLETE,
|
||||||
SET_FOCUSED_NATIVE_FILTER,
|
SET_FOCUSED_NATIVE_FILTER,
|
||||||
UNSET_FOCUSED_NATIVE_FILTER,
|
UNSET_FOCUSED_NATIVE_FILTER,
|
||||||
} from 'src/dashboard/actions/nativeFilters';
|
} from 'src/dashboard/actions/nativeFilters';
|
||||||
@ -72,33 +71,20 @@ export default function nativeFilterReducer(
|
|||||||
},
|
},
|
||||||
action: AnyFilterAction,
|
action: AnyFilterAction,
|
||||||
) {
|
) {
|
||||||
const { filterSets } = state;
|
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case HYDRATE_DASHBOARD:
|
case HYDRATE_DASHBOARD:
|
||||||
return {
|
return {
|
||||||
filters: action.data.nativeFilters.filters,
|
filters: action.data.nativeFilters.filters,
|
||||||
filterSets: action.data.nativeFilters.filterSets,
|
filterSets: action.data.nativeFilters.filterSets,
|
||||||
};
|
};
|
||||||
case SAVE_FILTER_SETS:
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
filterSets: {
|
|
||||||
...filterSets,
|
|
||||||
[action.filtersSetId]: {
|
|
||||||
id: action.filtersSetId,
|
|
||||||
name: action.name,
|
|
||||||
dataMask: action.dataMask,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
case SET_FILTER_CONFIG_COMPLETE:
|
case SET_FILTER_CONFIG_COMPLETE:
|
||||||
case SET_IN_SCOPE_STATUS_OF_FILTERS:
|
case SET_IN_SCOPE_STATUS_OF_FILTERS:
|
||||||
return getInitialState({ filterConfig: action.filterConfig, state });
|
return getInitialState({ filterConfig: action.filterConfig, state });
|
||||||
|
|
||||||
case SET_FILTER_SETS_CONFIG_COMPLETE:
|
case SET_FILTER_SETS_COMPLETE:
|
||||||
return getInitialState({
|
return getInitialState({
|
||||||
filterSetsConfig: action.filterSetsConfig,
|
filterSetsConfig: action.filterSets,
|
||||||
state,
|
state,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
import componentTypes from 'src/dashboard/util/componentTypes';
|
import componentTypes from 'src/dashboard/util/componentTypes';
|
||||||
import { DataMaskStateWithId } from 'src/dataMask/types';
|
import { DataMaskStateWithId } from 'src/dataMask/types';
|
||||||
|
import { JsonObject } from '@superset-ui/core';
|
||||||
import { Filter, Scope } from '../components/nativeFilters/types';
|
import { Filter, Scope } from '../components/nativeFilters/types';
|
||||||
|
|
||||||
export enum Scoping {
|
export enum Scoping {
|
||||||
@ -82,12 +83,25 @@ export type LayoutItem = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type FilterSet = {
|
export type FilterSet = {
|
||||||
id: string;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
nativeFilters: Filters;
|
nativeFilters: Filters;
|
||||||
dataMask: DataMaskStateWithId;
|
dataMask: DataMaskStateWithId;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type FilterSetFullData = {
|
||||||
|
changed_by_fk: string | null;
|
||||||
|
changed_on: string | null;
|
||||||
|
created_by_fk: string | null;
|
||||||
|
created_on: string | null;
|
||||||
|
dashboard_id: number;
|
||||||
|
description: string | null;
|
||||||
|
name: string;
|
||||||
|
owner_id: number;
|
||||||
|
owner_type: string;
|
||||||
|
params: JsonObject;
|
||||||
|
};
|
||||||
|
|
||||||
export type FilterSets = {
|
export type FilterSets = {
|
||||||
[filtersSetId: string]: FilterSet;
|
[filtersSetId: string]: FilterSet;
|
||||||
};
|
};
|
||||||
|
@ -64,6 +64,7 @@ export type DashboardState = {
|
|||||||
isRefreshing: boolean;
|
isRefreshing: boolean;
|
||||||
hasUnsavedChanges: boolean;
|
hasUnsavedChanges: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type DashboardInfo = {
|
export type DashboardInfo = {
|
||||||
id: number;
|
id: number;
|
||||||
common: {
|
common: {
|
||||||
@ -72,7 +73,9 @@ export type DashboardInfo = {
|
|||||||
};
|
};
|
||||||
userId: string;
|
userId: string;
|
||||||
dash_edit_perm: boolean;
|
dash_edit_perm: boolean;
|
||||||
|
json_metadata: string;
|
||||||
metadata: {
|
metadata: {
|
||||||
|
native_filter_configuration: JsonObject;
|
||||||
show_native_filters: boolean;
|
show_native_filters: boolean;
|
||||||
chart_configuration: JsonObject;
|
chart_configuration: JsonObject;
|
||||||
};
|
};
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
import shortid from 'shortid';
|
import shortid from 'shortid';
|
||||||
import { compose } from 'redux';
|
import { compose } from 'redux';
|
||||||
import persistState, { StorageAdapter } from 'redux-localstorage';
|
import persistState, { StorageAdapter } from 'redux-localstorage';
|
||||||
import { isEqual, omitBy, isUndefined } from 'lodash';
|
import { isEqual, omitBy, isUndefined, isNull } from 'lodash';
|
||||||
|
|
||||||
export function addToObject(
|
export function addToObject(
|
||||||
state: Record<string, any>,
|
state: Record<string, any>,
|
||||||
@ -177,13 +177,20 @@ export function areArraysShallowEqual(arr1: unknown[], arr2: unknown[]) {
|
|||||||
export function areObjectsEqual(
|
export function areObjectsEqual(
|
||||||
obj1: any,
|
obj1: any,
|
||||||
obj2: any,
|
obj2: any,
|
||||||
opts = { ignoreUndefined: false },
|
opts: {
|
||||||
|
ignoreUndefined?: boolean;
|
||||||
|
ignoreNull?: boolean;
|
||||||
|
} = { ignoreUndefined: false, ignoreNull: false },
|
||||||
) {
|
) {
|
||||||
let comp1 = obj1;
|
let comp1 = obj1;
|
||||||
let comp2 = obj2;
|
let comp2 = obj2;
|
||||||
if (opts.ignoreUndefined) {
|
if (opts.ignoreUndefined) {
|
||||||
comp1 = omitBy(obj1, isUndefined);
|
comp1 = omitBy(comp1, isUndefined);
|
||||||
comp2 = omitBy(obj2, isUndefined);
|
comp2 = omitBy(comp2, isUndefined);
|
||||||
|
}
|
||||||
|
if (opts.ignoreNull) {
|
||||||
|
comp1 = omitBy(comp1, isNull);
|
||||||
|
comp2 = omitBy(comp2, isNull);
|
||||||
}
|
}
|
||||||
return isEqual(comp1, comp2);
|
return isEqual(comp1, comp2);
|
||||||
}
|
}
|
||||||
|
@ -24,17 +24,20 @@ type TestWithIdType<T> = T extends string ? string : { 'data-test': string };
|
|||||||
export const testWithId = <T extends string | JsonObject = JsonObject>(
|
export const testWithId = <T extends string | JsonObject = JsonObject>(
|
||||||
prefix?: string,
|
prefix?: string,
|
||||||
idOnly = false,
|
idOnly = false,
|
||||||
) => (id?: string): TestWithIdType<T> => {
|
) => (id?: string, localIdOnly = false): TestWithIdType<T> => {
|
||||||
|
const resultIdOnly = localIdOnly || idOnly;
|
||||||
if (!id && prefix) {
|
if (!id && prefix) {
|
||||||
return (idOnly ? prefix : { 'data-test': prefix }) as TestWithIdType<T>;
|
return (resultIdOnly
|
||||||
|
? prefix
|
||||||
|
: { 'data-test': prefix }) as TestWithIdType<T>;
|
||||||
}
|
}
|
||||||
if (id && !prefix) {
|
if (id && !prefix) {
|
||||||
return (idOnly ? id : { 'data-test': id }) as TestWithIdType<T>;
|
return (resultIdOnly ? id : { 'data-test': id }) as TestWithIdType<T>;
|
||||||
}
|
}
|
||||||
if (!id && !prefix) {
|
if (!id && !prefix) {
|
||||||
console.warn('testWithId function has missed "prefix" and "id" params');
|
console.warn('testWithId function has missed "prefix" and "id" params');
|
||||||
return (idOnly ? '' : { 'data-test': '' }) as TestWithIdType<T>;
|
return (resultIdOnly ? '' : { 'data-test': '' }) as TestWithIdType<T>;
|
||||||
}
|
}
|
||||||
const newId = `${prefix}__${id}`;
|
const newId = `${prefix}__${id}`;
|
||||||
return (idOnly ? newId : { 'data-test': newId }) as TestWithIdType<T>;
|
return (resultIdOnly ? newId : { 'data-test': newId }) as TestWithIdType<T>;
|
||||||
};
|
};
|
||||||
|
@ -381,6 +381,7 @@ DEFAULT_FEATURE_FLAGS: Dict[str, bool] = {
|
|||||||
"ESCAPE_MARKDOWN_HTML": False,
|
"ESCAPE_MARKDOWN_HTML": False,
|
||||||
"DASHBOARD_NATIVE_FILTERS": True,
|
"DASHBOARD_NATIVE_FILTERS": True,
|
||||||
"DASHBOARD_CROSS_FILTERS": False,
|
"DASHBOARD_CROSS_FILTERS": False,
|
||||||
|
# Feature is under active development and breaking changes are expected
|
||||||
"DASHBOARD_NATIVE_FILTERS_SET": False,
|
"DASHBOARD_NATIVE_FILTERS_SET": False,
|
||||||
"DASHBOARD_FILTERS_EXPERIMENTAL": False,
|
"DASHBOARD_FILTERS_EXPERIMENTAL": False,
|
||||||
"GLOBAL_ASYNC_QUERIES": False,
|
"GLOBAL_ASYNC_QUERIES": False,
|
||||||
|
Loading…
Reference in New Issue
Block a user