feat: View all fav charts and dashboards (#12060)

* Add fav filter - tentative

* Closes #11984

* Fix tests

* Delete trailing spaces

* Fix spec btns length

* Fix mock test

* Add urlDisplay option

* Redirect with history

* Catch initial fav filter
This commit is contained in:
Geido 2020-12-18 22:16:07 +01:00 committed by GitHub
parent 2a23744223
commit f4ce831a10
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 84 additions and 27 deletions

View File

@ -96,7 +96,7 @@ describe('SavedQueries', () => {
expect(wrapper.find(SavedQueries)).toExist();
});
it('fetches queries favorites and renders listviewcard cards', async () => {
it('fetches queries mine and renders listviewcard cards', async () => {
clickTab(0);
await waitForComponentToPaint(wrapper);
expect(fetchMock.calls(/saved_query\/\?q/)).toHaveLength(1);
@ -105,9 +105,9 @@ describe('SavedQueries', () => {
it('it renders a submenu with clickable tables and buttons', async () => {
expect(wrapper.find(SubMenu)).toExist();
expect(wrapper.find('li')).toHaveLength(2);
expect(wrapper.find('li')).toHaveLength(1);
expect(wrapper.find('button')).toHaveLength(2);
clickTab(1);
clickTab(0);
await waitForComponentToPaint(wrapper);
expect(fetchMock.calls(/saved_query\/\?q/)).toHaveLength(2);
});

View File

@ -55,11 +55,14 @@ type FilterOperator =
| 'all_text'
| 'chart_all_text'
| 'dataset_is_null_or_empty'
| 'between';
| 'between'
| 'dashboard_is_fav'
| 'chart_is_fav';
export interface Filter {
Header: ReactNode;
id: string;
urlDisplay?: string;
operator?: FilterOperator;
input?:
| 'text'
@ -85,6 +88,7 @@ export type ViewModeType = 'card' | 'table';
export interface FilterValue {
id: string;
urlDisplay?: string;
operator?: string;
value: string | boolean | number | null | undefined | string[] | number[];
}
@ -119,4 +123,6 @@ export enum FilterOperators {
chartAllText = 'chart_all_text',
datasetIsNullOrEmpty = 'dataset_is_null_or_empty',
between = 'between',
dashboardIsFav = 'dashboard_is_fav',
chartIsFav = 'chart_is_fav',
}

View File

@ -80,10 +80,11 @@ type QueryFilterState = {
};
function mergeCreateFilterValues(list: Filter[], updateObj: QueryFilterState) {
return list.map(({ id, operator }) => {
const update = updateObj[id];
return list.map(({ id, urlDisplay, operator }) => {
const currentFilterId = urlDisplay || id;
const update = updateObj[currentFilterId];
return { id, operator, value: update };
return { id, urlDisplay, operator, value: update };
});
}
@ -143,10 +144,12 @@ export function convertFiltersRison(
// Add operators from filter list
list.forEach(value => {
const filter = refs[value.id];
const currentFilterId = value.urlDisplay || value.id;
const filter = refs[currentFilterId];
if (filter) {
filter.operator = value.operator;
filter.id = value.id;
}
});
@ -294,7 +297,8 @@ export function useListViewState({
filter.value !== undefined &&
(typeof filter.value !== 'string' || filter.value.length > 0)
) {
filterObj[filter.id] = filter.value;
const currentFilterId = filter.urlDisplay || filter.id;
filterObj[currentFilterId] = filter.value;
}
});
@ -318,6 +322,7 @@ export function useListViewState({
: 'replace';
setQuery(queryParams, method);
fetchData({ pageIndex, pageSize, sortBy, filters });
}, [fetchData, pageIndex, pageSize, sortBy, filters, viewMode]);
@ -333,12 +338,14 @@ export function useListViewState({
if (currentInternalFilters[index].value === value) {
return currentInternalFilters;
}
const update = { ...currentInternalFilters[index], value };
const updatedFilters = updateInList(
currentInternalFilters,
index,
update,
);
setAllFilters(convertFilters(updatedFilters));
gotoPage(0); // clear pagination on filter
return updatedFilters;

View File

@ -40,6 +40,7 @@ import ListView, {
ListViewProps,
Filters,
SelectOption,
FilterOperators,
} from 'src/components/ListView';
import withToasts from 'src/messageToasts/enhancers/withToasts';
import PropertiesModal from 'src/explore/components/PropertiesModal';
@ -195,7 +196,7 @@ function ChartList(props: ChartListProps) {
/>
),
Header: '',
id: 'favorite',
id: 'id',
disableSortBy: true,
size: 'xs',
},
@ -367,7 +368,7 @@ function ChartList(props: ChartListProps) {
Header: t('Owner'),
id: 'owners',
input: 'select',
operator: 'rel_m_m',
operator: FilterOperators.relationManyMany,
unfilteredLabel: 'All',
fetchSelects: createFetchRelated(
'chart',
@ -388,7 +389,7 @@ function ChartList(props: ChartListProps) {
Header: t('Created By'),
id: 'created_by',
input: 'select',
operator: 'rel_o_m',
operator: FilterOperators.relationOneMany,
unfilteredLabel: 'All',
fetchSelects: createFetchRelated(
'chart',
@ -409,7 +410,7 @@ function ChartList(props: ChartListProps) {
Header: t('Viz Type'),
id: 'viz_type',
input: 'select',
operator: 'eq',
operator: FilterOperators.equals,
unfilteredLabel: 'All',
selects: getChartMetadataRegistry()
.keys()
@ -433,7 +434,7 @@ function ChartList(props: ChartListProps) {
Header: t('Dataset'),
id: 'datasource_id',
input: 'select',
operator: 'eq',
operator: FilterOperators.equals,
unfilteredLabel: 'All',
fetchSelects: createFetchDatasets(
createErrorHandler(errMsg =>
@ -447,11 +448,23 @@ function ChartList(props: ChartListProps) {
),
paginate: false,
},
{
Header: t('Favorite'),
id: 'id',
urlDisplay: 'favorite',
input: 'select',
operator: FilterOperators.chartIsFav,
unfilteredLabel: 'Any',
selects: [
{ label: t('Yes'), value: true },
{ label: t('No'), value: false },
],
},
{
Header: t('Search'),
id: 'slice_name',
input: 'search',
operator: 'chart_all_text',
operator: FilterOperators.chartAllText,
},
];

View File

@ -29,7 +29,11 @@ import {
import { useListViewResource, useFavoriteStatus } from 'src/views/CRUD/hooks';
import ConfirmStatusChange from 'src/components/ConfirmStatusChange';
import SubMenu, { SubMenuProps } from 'src/components/Menu/SubMenu';
import ListView, { ListViewProps, Filters } from 'src/components/ListView';
import ListView, {
ListViewProps,
Filters,
FilterOperators,
} from 'src/components/ListView';
import Owner from 'src/types/Owner';
import withToasts from 'src/messageToasts/enhancers/withToasts';
import FacePile from 'src/components/FacePile';
@ -104,6 +108,7 @@ function DashboardList(props: DashboardListProps) {
dashboardIds,
addDangerToast,
);
const [dashboardToEdit, setDashboardToEdit] = useState<Dashboard | null>(
null,
);
@ -189,7 +194,7 @@ function DashboardList(props: DashboardListProps) {
/>
),
Header: '',
id: 'favorite',
id: 'id',
disableSortBy: true,
size: 'xs',
},
@ -354,7 +359,7 @@ function DashboardList(props: DashboardListProps) {
Header: t('Owner'),
id: 'owners',
input: 'select',
operator: 'rel_m_m',
operator: FilterOperators.relationManyMany,
unfilteredLabel: 'All',
fetchSelects: createFetchRelated(
'dashboard',
@ -375,7 +380,7 @@ function DashboardList(props: DashboardListProps) {
Header: t('Created By'),
id: 'created_by',
input: 'select',
operator: 'rel_o_m',
operator: FilterOperators.relationOneMany,
unfilteredLabel: 'All',
fetchSelects: createFetchRelated(
'dashboard',
@ -396,18 +401,30 @@ function DashboardList(props: DashboardListProps) {
Header: t('Status'),
id: 'published',
input: 'select',
operator: 'eq',
operator: FilterOperators.equals,
unfilteredLabel: 'Any',
selects: [
{ label: t('Published'), value: true },
{ label: t('Unpublished'), value: false },
],
},
{
Header: t('Favorite'),
id: 'id',
urlDisplay: 'favorite',
input: 'select',
operator: FilterOperators.dashboardIsFav,
unfilteredLabel: 'Any',
selects: [
{ label: t('Yes'), value: true },
{ label: t('No'), value: false },
],
},
{
Header: t('Search'),
id: 'dashboard_title',
input: 'search',
operator: 'title_or_slug',
operator: FilterOperators.titleOrSlug,
},
];

View File

@ -115,8 +115,8 @@ export function useListViewResource<D extends object = any>(
const filterExps = (baseFilters || [])
.concat(filterValues)
.map(({ id: col, operator: opr, value }) => ({
col,
.map(({ id, operator: opr, value }) => ({
col: id,
opr,
value,
}));

View File

@ -24,6 +24,7 @@ import {
useFavoriteStatus,
} from 'src/views/CRUD/hooks';
import withToasts from 'src/messageToasts/enhancers/withToasts';
import { useHistory } from 'react-router-dom';
import PropertiesModal from 'src/explore/components/PropertiesModal';
import { User } from 'src/types/bootstrapTypes';
import Icon from 'src/components/Icon';
@ -50,6 +51,7 @@ function ChartTable({
addSuccessToast,
mine,
}: ChartTableProps) {
const history = useHistory();
const {
state: { resourceCollection: charts, bulkSelectEnabled },
setResourceCollection: setCharts,
@ -148,14 +150,18 @@ function ChartTable({
),
buttonStyle: 'tertiary',
onClick: () => {
window.location.href = '/chart/add';
history.push('/chart/add');
},
},
{
name: 'View All »',
buttonStyle: 'link',
onClick: () => {
window.location.href = '/chart/list';
const target =
chartFilter === 'Favorite'
? '/chart/list/?filters=(favorite:!t)'
: '/chart/list/';
history.push(target);
},
},
]}

View File

@ -20,6 +20,7 @@ import React, { useState, useMemo } from 'react';
import { SupersetClient, t } from '@superset-ui/core';
import { useListViewResource, useFavoriteStatus } from 'src/views/CRUD/hooks';
import { Dashboard, DashboardTableProps } from 'src/views/CRUD/types';
import { useHistory } from 'react-router-dom';
import withToasts from 'src/messageToasts/enhancers/withToasts';
import PropertiesModal from 'src/dashboard/components/PropertiesModal';
import DashboardCard from 'src/views/CRUD/dashboard/DashboardCard';
@ -42,6 +43,7 @@ function DashboardTable({
addSuccessToast,
mine,
}: DashboardTableProps) {
const history = useHistory();
const {
state: { loading, resourceCollection: dashboards },
setResourceCollection: setDashboards,
@ -155,14 +157,18 @@ function DashboardTable({
),
buttonStyle: 'tertiary',
onClick: () => {
window.location.href = '/dashboard/new';
history.push('/dashboard/new');
},
},
{
name: 'View All »',
buttonStyle: 'link',
onClick: () => {
window.location.href = '/dashboard/list/';
const target =
dashboardFilter === 'Favorite'
? '/dashboard/list/?filters=(favorite:!t)'
: '/dashboard/list/';
history.push(target);
},
},
]}

View File

@ -255,6 +255,7 @@ const SavedQueries = ({
<SubMenu
activeChild={queryFilter}
tabs={[
/* @TODO uncomment when fav functionality is implemented
{
name: 'Favorite',
label: t('Favorite'),
@ -262,6 +263,7 @@ const SavedQueries = ({
getData('Favorite').then(() => setQueryFilter('Favorite'));
},
},
*/
{
name: 'Mine',
label: t('Mine'),