adds FAB style filter types (#9086)

This commit is contained in:
ʈᵃᵢ 2020-02-24 10:16:11 -08:00 committed by GitHub
parent 72518e20ee
commit 1748728c45
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 467 additions and 253 deletions

View File

@ -4912,6 +4912,7 @@
"version": "0.6.11",
"resolved": "https://registry.npmjs.org/@types/react-json-tree/-/react-json-tree-0.6.11.tgz",
"integrity": "sha512-HP0Sf0ZHjCi1FHLJxh/pLaxaevEW6ILlV2C5Dn3EZFTkLjWkv+EVf/l/zvtmoU9ZwuO/3TKVeWK/700UDxunTw==",
"dev": true,
"requires": {
"@types/react": "*"
}

View File

@ -163,6 +163,7 @@
"@types/jest": "^23.3.5",
"@types/react": "^16.4.18",
"@types/react-dom": "^16.0.9",
"@types/react-json-tree": "^0.6.11",
"@types/react-table": "^7.0.2",
"@typescript-eslint/eslint-plugin": "^2.20.0",
"@typescript-eslint/parser": "^2.20.0",

View File

@ -38,6 +38,13 @@ describe('ListView', () => {
filterable: true,
},
],
filters: [
{
Header: 'Name',
id: 'name',
operators: [{ label: 'Starts With', value: 'sw' }],
},
],
data: [
{ id: 1, name: 'data 1' },
{ id: 2, name: 'data 2' },
@ -64,15 +71,15 @@ describe('ListView', () => {
it('calls fetchData on mount', () => {
expect(wrapper.find(ListView)).toHaveLength(1);
expect(mockedProps.fetchData.mock.calls[0]).toMatchInlineSnapshot(`
Array [
Object {
"filters": Array [],
"pageIndex": 0,
"pageSize": 1,
"sortBy": Array [],
},
]
`);
Array [
Object {
"filters": Array [],
"pageIndex": 0,
"pageSize": 1,
"sortBy": Array [],
},
]
`);
});
it('calls fetchData on sort', () => {
@ -83,20 +90,20 @@ describe('ListView', () => {
expect(mockedProps.fetchData).toHaveBeenCalled();
expect(mockedProps.fetchData.mock.calls[0]).toMatchInlineSnapshot(`
Array [
Object {
"filters": Array [],
"pageIndex": 0,
"pageSize": 1,
"sortBy": Array [
Object {
"desc": false,
"id": "id",
},
],
},
]
`);
Array [
Object {
"filters": Array [],
"pageIndex": 0,
"pageSize": 1,
"sortBy": Array [
Object {
"desc": false,
"id": "id",
},
],
},
]
`);
});
it('calls fetchData on filter', () => {
@ -118,6 +125,7 @@ describe('ListView', () => {
act(() => {
wrapper.find('.filter-inputs input[type="text"]').prop('onChange')({
persist() {},
currentTarget: { value: 'foo' },
});
});
@ -132,27 +140,27 @@ describe('ListView', () => {
wrapper.update();
expect(mockedProps.fetchData.mock.calls[0]).toMatchInlineSnapshot(`
Array [
Object {
"filters": Array [
Object {
"Header": "name",
"filterId": "sw",
"id": "name",
"value": "foo",
},
],
"pageIndex": 0,
"pageSize": 1,
"sortBy": Array [
Object {
"desc": false,
"id": "id",
},
],
},
]
`);
Array [
Object {
"filters": Array [
Object {
"Header": "name",
"id": "name",
"operator": "sw",
"value": "foo",
},
],
"pageIndex": 0,
"pageSize": 1,
"sortBy": Array [
Object {
"desc": false,
"id": "id",
},
],
},
]
`);
});
it('calls fetchData on page change', () => {
@ -162,27 +170,27 @@ describe('ListView', () => {
wrapper.update();
expect(mockedProps.fetchData.mock.calls[0]).toMatchInlineSnapshot(`
Array [
Object {
"filters": Array [
Object {
"Header": "name",
"filterId": "sw",
"id": "name",
"value": "foo",
},
],
"pageIndex": 1,
"pageSize": 1,
"sortBy": Array [
Object {
"desc": false,
"id": "id",
},
],
},
]
`);
Array [
Object {
"filters": Array [
Object {
"Header": "name",
"id": "name",
"operator": "sw",
"value": "foo",
},
],
"pageIndex": 1,
"pageSize": 1,
"sortBy": Array [
Object {
"desc": false,
"id": "id",
},
],
},
]
`);
});
it('handles bulk actions on 1 row', () => {
act(() => {
@ -207,15 +215,15 @@ describe('ListView', () => {
bulkActionsProps.onSelect(bulkActionsProps.eventKey);
expect(mockedProps.bulkActions[0].onSelect.mock.calls[0])
.toMatchInlineSnapshot(`
Array [
Array [
Object {
"id": 1,
"name": "data 1",
},
],
]
`);
Array [
Array [
Object {
"id": 1,
"name": "data 1",
},
],
]
`);
});
it('handles bulk actions on all rows', () => {
act(() => {
@ -240,18 +248,18 @@ describe('ListView', () => {
bulkActionsProps.onSelect(bulkActionsProps.eventKey);
expect(mockedProps.bulkActions[0].onSelect.mock.calls[0])
.toMatchInlineSnapshot(`
Array [
Array [
Object {
"id": 1,
"name": "data 1",
},
Object {
"id": 2,
"name": "data 2",
},
],
]
`);
Array [
Array [
Object {
"id": 1,
"name": "data 1",
},
Object {
"id": 2,
"name": "data 2",
},
],
]
`);
});
});

View File

@ -17,7 +17,7 @@
* under the License.
*/
import { t } from '@superset-ui/translation';
import React, { FunctionComponent, useMemo } from 'react';
import React, { FunctionComponent } from 'react';
import {
Button,
Col,
@ -28,17 +28,26 @@ import {
Row,
// @ts-ignore
} from 'react-bootstrap';
// @ts-ignore
import SelectComponent from 'react-select';
// @ts-ignore
import VirtualizedSelect from 'react-virtualized-select';
import IndeterminateCheckbox from '../IndeterminateCheckbox';
import './ListViewStyles.less';
import TableCollection from './TableCollection';
import {
FetchDataConfig,
FilterToggle,
FilterType,
FilterTypeMap,
Filters,
InternalFilter,
Select,
SortColumn,
} from './types';
import { convertFilters, removeFromList, useListViewState } from './utils';
import {
convertFilters,
extractInputValue,
removeFromList,
useListViewState,
} from './utils';
interface Props {
columns: any[];
@ -50,7 +59,7 @@ interface Props {
className?: string;
title?: string;
initialSort?: SortColumn[];
filterTypes?: FilterTypeMap;
filters?: Filters;
bulkActions?: Array<{
key?: string;
name: React.ReactNode;
@ -82,7 +91,7 @@ const ListView: FunctionComponent<Props> = ({
initialSort = [],
className = '',
title = '',
filterTypes = {},
filters = [],
bulkActions = [],
}) => {
const {
@ -96,12 +105,12 @@ const ListView: FunctionComponent<Props> = ({
pageCount = 1,
gotoPage,
setAllFilters,
setFilterToggles,
updateFilterToggle,
setInternalFilters,
updateInternalFilter,
applyFilters,
filtersApplied,
selectedFlatRows,
state: { pageIndex, pageSize, filterToggles },
state: { pageIndex, pageSize, internalFilters },
} = useListViewState({
bulkSelectColumnConfig,
bulkSelectMode: Boolean(bulkActions.length),
@ -112,14 +121,11 @@ const ListView: FunctionComponent<Props> = ({
initialPageSize,
initialSort,
});
const filterableColumns = useMemo(() => columns.filter(c => c.filterable), [
columns,
]);
const filterable = Boolean(columns.length);
const filterable = Boolean(filters.length);
const removeFilterAndApply = (index: number) => {
const updated = removeFromList(filterToggles, index);
setFilterToggles(updated);
const updated = removeFromList(internalFilters, index);
setInternalFilters(updated);
setAllFilters(convertFilters(updated));
};
@ -147,17 +153,17 @@ const ListView: FunctionComponent<Props> = ({
</>
}
>
{filterableColumns
.map(({ id, accessor, Header }) => ({
{filters
.map(({ id, Header }) => ({
Header,
id: id || accessor,
id,
}))
.map((ft: FilterToggle) => (
.map((ft: InternalFilter) => (
<MenuItem
key={ft.id}
eventKey={ft}
onSelect={(fltr: FilterToggle) =>
setFilterToggles([...filterToggles, fltr])
onSelect={(fltr: InternalFilter) =>
setInternalFilters([...internalFilters, fltr])
}
>
{ft.Header}
@ -169,62 +175,89 @@ const ListView: FunctionComponent<Props> = ({
)}
</Row>
<hr />
{filterToggles.map((ft, i) => (
<div key={`${ft.Header}-${i}`} className="filter-inputs">
<Row>
<Col className="text-center filter-column" md={2}>
<span>{ft.Header}</span>
</Col>
<Col md={2}>
<FormControl
componentClass="select"
bsSize="small"
value={ft.filterId}
placeholder={
filterTypes[ft.id] ? filterTypes[ft.id][0].name : ''
}
onChange={(e: React.MouseEvent<HTMLInputElement>) =>
updateFilterToggle(i, { filterId: e.currentTarget.value })
}
>
{filterTypes[ft.id] &&
filterTypes[ft.id].map(
({ name, operator }: FilterType) => (
<option key={name} value={operator}>
{name}
</option>
),
)}
</FormControl>
</Col>
<Col md={1} />
<Col md={4}>
<FormControl
type="text"
bsSize="small"
value={ft.value || ''}
onChange={(e: React.KeyboardEvent<HTMLInputElement>) =>
updateFilterToggle(i, {
value: e.currentTarget.value,
})
}
/>
</Col>
<Col md={1}>
<div
className="filter-close"
role="button"
tabIndex={0}
onClick={() => removeFilterAndApply(i)}
>
<i className="fa fa-close text-primary" />
</div>
</Col>
</Row>
<br />
</div>
))}
{filterToggles.length > 0 && (
{internalFilters.map((ft, i) => {
const filter = filters.find(f => f.id === ft.id);
if (!filter) {
console.error(`could not find filter for ${ft.id}`);
return null;
}
return (
<div key={`${ft.Header}-${i}`} className="filter-inputs">
<Row>
<Col className="text-center filter-column" md={2}>
<span>{ft.Header}</span>
</Col>
<Col md={2}>
<FormControl
componentClass="select"
bsSize="small"
value={ft.operator}
placeholder={filter ? filter.operators[0] : ''}
onChange={(e: React.MouseEvent<HTMLInputElement>) => {
updateInternalFilter(i, {
operator: e.currentTarget.value,
});
}}
>
{filter.operators.map(({ label, value }: Select) => (
<option key={label} value={value}>
{label}
</option>
))}
</FormControl>
</Col>
<Col md={1} />
<Col md={4}>
{filter.input === 'select' && (
<VirtualizedSelect
autoFocus
multi
searchable
name={`filter-${filter.id}-select`}
options={filter.selects}
placeholder="Select Value"
value={ft.value}
selectComponent={SelectComponent}
onChange={(e: Select[] | null) => {
updateInternalFilter(i, {
operator: ft.operator || filter.operators[0].value,
value: e ? e.map(s => s.value) : e,
});
}}
/>
)}
{filter.input !== 'select' && (
<FormControl
type={filter.input ? filter.input : 'text'}
bsSize="small"
value={ft.value || ''}
checked={Boolean(ft.value)}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
e.persist();
updateInternalFilter(i, {
operator: ft.operator || filter.operators[0].value,
value: extractInputValue(filter.input, e),
});
}}
/>
)}
</Col>
<Col md={1}>
<div
className="filter-close"
role="button"
tabIndex={0}
onClick={() => removeFilterAndApply(i)}
>
<i className="fa fa-close text-primary" />
</div>
</Col>
</Row>
<br />
</div>
);
})}
{internalFilters.length > 0 && (
<>
<Row>
<Col md={10} />

View File

@ -23,31 +23,50 @@ export interface SortColumn {
export type SortColumns = SortColumn[];
export interface Select {
label: string;
value: any;
}
export interface Filter {
Header: string;
id: string;
filterId?: string;
value: string;
operators: Select[];
input?: 'text' | 'textarea' | 'select' | 'checkbox';
selects?: Select[];
}
export interface FilterType {
name: string;
operator: any;
}
export type Filters = Filter[];
export interface FilterTypeMap {
[columnId: string]: FilterType[];
export interface FilterValue {
id: string;
operator?: string;
value: string | boolean | number;
}
export interface FetchDataConfig {
pageIndex: number;
pageSize: number;
sortBy: SortColumns;
filters: Filter[];
filters: FilterValue[];
}
export interface FilterToggle {
id: string;
export interface InternalFilter extends FilterValue {
Header: string;
filterId?: number;
value?: string;
}
export interface FilterOperatorMap {
[columnId: string]: Array<{
name: string;
operator:
| 'sw'
| 'ew'
| 'ct'
| 'eq'
| 'nsw'
| 'new'
| 'nct'
| 'neq'
| 'rel_m_m';
}>;
}

View File

@ -33,7 +33,7 @@ import {
useQueryParams,
} from 'use-query-params';
import { FetchDataConfig, FilterToggle, SortColumn } from './types';
import { FetchDataConfig, InternalFilter, SortColumn } from './types';
// removes element from a list, returns new list
export function removeFromList(list: any[], index: number): any[] {
@ -52,10 +52,24 @@ function updateInList(list: any[], index: number, update: any): any[] {
}
// convert filters from UI objects to data objects
export function convertFilters(fts: FilterToggle[]) {
export function convertFilters(fts: InternalFilter[]) {
return fts
.filter((ft: FilterToggle) => ft.value)
.map(ft => ({ value: null, filterId: ft.filterId || 'sw', ...ft }));
.filter((ft: InternalFilter) => ft.value)
.map(ft => ({ operator: ft.operator, ...ft }));
}
export function extractInputValue(
inputType: 'text' | 'textarea' | 'checkbox' | 'select' | undefined,
event: any,
) {
if (!inputType || inputType === 'text') {
return event.currentTarget.value;
}
if (inputType === 'checkbox') {
return event.currentTarget.checked;
}
return null;
}
interface UseListViewConfig {
@ -128,6 +142,7 @@ export function useListViewState({
columns: columnsWithSelect,
count,
data,
disableFilters: true,
disableSortRemove: true,
initialState,
manualFilters: true,
@ -142,13 +157,13 @@ export function useListViewState({
useRowSelect,
);
const [filterToggles, setFilterToggles] = useState<FilterToggle[]>(
const [internalFilters, setInternalFilters] = useState<InternalFilter[]>(
query.filters || [],
);
useEffect(() => {
const queryParams: any = {
filters: filterToggles,
filters: internalFilters,
pageIndex,
};
if (sortBy[0]) {
@ -160,18 +175,18 @@ export function useListViewState({
fetchData({ pageIndex, pageSize, sortBy, filters });
}, [fetchData, pageIndex, pageSize, sortBy, filters]);
const filtersApplied = filterToggles.every(
({ id, value, filterId }, index) =>
const filtersApplied = internalFilters.every(
({ id, value, operator }, index) =>
id &&
filters[index] &&
filters[index].id === id &&
filters[index].value === value &&
// @ts-ignore
filters[index].filterId === filterId,
filters[index].operator === operator,
);
return {
applyFilters: () => setAllFilters(convertFilters(filterToggles)),
applyFilters: () => setAllFilters(convertFilters(internalFilters)),
canNextPage,
canPreviousPage,
filtersApplied,
@ -184,9 +199,9 @@ export function useListViewState({
rows,
selectedFlatRows,
setAllFilters,
setFilterToggles,
state: { pageIndex, pageSize, sortBy, filters, filterToggles },
updateFilterToggle: (index: number, update: object) =>
setFilterToggles(updateInList(filterToggles, index, update)),
setInternalFilters,
state: { pageIndex, pageSize, sortBy, filters, internalFilters },
updateInternalFilter: (index: number, update: object) =>
setInternalFilters(updateInList(internalFilters, index, update)),
};
}

View File

@ -25,7 +25,11 @@ import React from 'react';
import { Panel } from 'react-bootstrap';
import ConfirmStatusChange from 'src/components/ConfirmStatusChange';
import ListView from 'src/components/ListView/ListView';
import { FetchDataConfig, FilterTypeMap } from 'src/components/ListView/types';
import {
FetchDataConfig,
FilterOperatorMap,
Filters,
} from 'src/components/ListView/types';
import withToasts from 'src/messageToasts/enhancers/withToasts';
const PAGE_SIZE = 25;
@ -36,12 +40,13 @@ interface Props {
}
interface State {
chartCount: number;
charts: any[];
filterTypes: FilterTypeMap;
labelColumns: { [key: string]: string };
lastFetchDataConfig: FetchDataConfig | null;
chartCount: number;
loading: boolean;
filterOperators: FilterOperatorMap;
filters: Filters;
owners: Array<{ text: string; value: number }>;
lastFetchDataConfig: FetchDataConfig | null;
permissions: string[];
}
@ -62,22 +67,43 @@ class ChartList extends React.PureComponent<Props, State> {
state: State = {
chartCount: 0,
charts: [],
filterTypes: {},
labelColumns: {},
filterOperators: {},
filters: [],
lastFetchDataConfig: null,
loading: false,
owners: [],
permissions: [],
};
componentDidMount() {
SupersetClient.get({
endpoint: `/api/v1/chart/_info`,
}).then(({ json = {} }) => {
this.setState({
filterTypes: json.filters,
permissions: json.permissions,
});
});
Promise.all([
SupersetClient.get({
endpoint: `/api/v1/chart/_info`,
}),
SupersetClient.get({
endpoint: `/api/v1/chart/related/owners`,
}),
]).then(
([{ json: infoJson = {} }, { json: ownersJson = {} }]) => {
this.setState(
{
filterOperators: infoJson.filters,
owners: ownersJson.result,
permissions: infoJson.permissions,
},
this.updateFilters,
);
},
([e1, e2]) => {
this.props.addDangerToast(t('An error occurred while fetching Charts'));
if (e1) {
console.error(e1);
}
if (e2) {
console.error(e2);
}
},
);
}
get canEdit() {
@ -99,7 +125,6 @@ class ChartList extends React.PureComponent<Props, State> {
}: any) => <a href={url}>{sliceName}</a>,
Header: t('Chart'),
accessor: 'slice_name',
filterable: true,
sortable: true,
},
{
@ -274,11 +299,7 @@ class ChartList extends React.PureComponent<Props, State> {
endpoint: `/api/v1/chart/?q=${queryParams}`,
})
.then(({ json = {} }) => {
this.setState({
charts: json.result,
chartCount: json.count,
labelColumns: json.label_columns,
});
this.setState({ charts: json.result, chartCount: json.count });
})
.catch(() => {
this.props.addDangerToast(t('An error occurred while fetching Charts'));
@ -288,8 +309,52 @@ class ChartList extends React.PureComponent<Props, State> {
});
};
updateFilters = () => {
const { filterOperators, owners } = this.state;
const convertFilter = ({
name: label,
operator,
}: {
name: string;
operator: string;
}) => ({ label, value: operator });
this.setState({
filters: [
{
Header: 'Chart',
id: 'slice_name',
operators: filterOperators.slice_name.map(convertFilter),
},
{
Header: 'Description',
id: 'description',
input: 'textarea',
operators: filterOperators.slice_name.map(convertFilter),
},
{
Header: 'Visualization Type',
id: 'viz_type',
operators: filterOperators.viz_type.map(convertFilter),
},
{
Header: 'Datasource Name',
id: 'datasource_name',
operators: filterOperators.datasource_name.map(convertFilter),
},
{
Header: 'Owners',
id: 'owners',
input: 'select',
operators: filterOperators.owners.map(convertFilter),
selects: owners.map(({ text: label, value }) => ({ label, value })),
},
],
});
};
render() {
const { charts, chartCount, loading, filterTypes } = this.state;
const { charts, chartCount, loading, filters } = this.state;
return (
<div className="container welcome">
<Panel>
@ -324,7 +389,7 @@ class ChartList extends React.PureComponent<Props, State> {
fetchData={this.fetchData}
loading={loading}
initialSort={this.initialSort}
filterTypes={filterTypes}
filters={filters}
bulkActions={bulkActions}
/>
);

View File

@ -25,7 +25,11 @@ import React from 'react';
import { Panel } from 'react-bootstrap';
import ConfirmStatusChange from 'src/components/ConfirmStatusChange';
import ListView from 'src/components/ListView/ListView';
import { FetchDataConfig, FilterTypeMap } from 'src/components/ListView/types';
import {
FetchDataConfig,
FilterOperatorMap,
Filters,
} from 'src/components/ListView/types';
import withToasts from 'src/messageToasts/enhancers/withToasts';
const PAGE_SIZE = 25;
@ -39,9 +43,10 @@ interface State {
dashboards: any[];
dashboardCount: number;
loading: boolean;
filterTypes: FilterTypeMap;
filterOperators: FilterOperatorMap;
filters: Filters;
owners: Array<{ text: string; value: number }>;
permissions: string[];
labelColumns: { [key: string]: string };
lastFetchDataConfig: FetchDataConfig | null;
}
@ -64,22 +69,45 @@ class DashboardList extends React.PureComponent<Props, State> {
state: State = {
dashboardCount: 0,
dashboards: [],
filterTypes: {},
labelColumns: {},
filterOperators: {},
filters: [],
lastFetchDataConfig: null,
loading: false,
owners: [],
permissions: [],
};
componentDidMount() {
SupersetClient.get({
endpoint: `/api/v1/dashboard/_info`,
}).then(({ json = {} }) => {
this.setState({
filterTypes: json.filters,
permissions: json.permissions,
});
});
Promise.all([
SupersetClient.get({
endpoint: `/api/v1/dashboard/_info`,
}),
SupersetClient.get({
endpoint: `/api/v1/dashboard/related/owners`,
}),
]).then(
([{ json: infoJson = {} }, { json: ownersJson = {} }]) => {
this.setState(
{
filterOperators: infoJson.filters,
owners: ownersJson.result,
permissions: infoJson.permissions,
},
this.updateFilters,
);
},
([e1, e2]) => {
this.props.addDangerToast(
t('An error occurred while fetching Dashboards'),
);
if (e1) {
console.error(e1);
}
if (e2) {
console.error(e2);
}
},
);
}
get canEdit() {
@ -105,7 +133,6 @@ class DashboardList extends React.PureComponent<Props, State> {
}: any) => <a href={url}>{dashboardTitle}</a>,
Header: t('Title'),
accessor: 'dashboard_title',
filterable: true,
sortable: true,
},
{
@ -117,7 +144,7 @@ class DashboardList extends React.PureComponent<Props, State> {
},
},
}: any) => <a href={changedByUrl}>{changedByName}</a>,
Header: t('Changed By Name'),
Header: t('Creator'),
accessor: 'changed_by_fk',
sortable: true,
},
@ -141,10 +168,16 @@ class DashboardList extends React.PureComponent<Props, State> {
original: { changed_on: changedOn },
},
}: any) => <span className="no-wrap">{moment(changedOn).fromNow()}</span>,
Header: t('Changed On'),
Header: t('Modified'),
accessor: 'changed_on',
sortable: true,
},
{
accessor: 'slug',
},
{
accessor: 'owners',
},
{
Cell: ({ row: { state, original } }: any) => {
const handleDelete = () => this.handleDashboardDelete(original);
@ -265,9 +298,11 @@ class DashboardList extends React.PureComponent<Props, State> {
};
handleBulkDashboardExport = (dashboards: Dashboard[]) => {
window.location.href = `/api/v1/dashboard/export/?q=!(${dashboards
.map(({ id }) => id)
.join(',')})`;
return window.location.assign(
`/api/v1/dashboard/export/?q=!(${dashboards
.map(({ id }) => id)
.join(',')})`,
);
};
fetchData = ({ pageIndex, pageSize, sortBy, filters }: FetchDataConfig) => {
@ -281,9 +316,9 @@ class DashboardList extends React.PureComponent<Props, State> {
},
loading: true,
});
const filterExps = filters.map(({ id, filterId, value }) => ({
col: id,
opr: filterId,
const filterExps = filters.map(({ id: col, operator: opr, value }) => ({
col,
opr,
value,
}));
@ -299,11 +334,7 @@ class DashboardList extends React.PureComponent<Props, State> {
endpoint: `/api/v1/dashboard/?q=${queryParams}`,
})
.then(({ json = {} }) => {
this.setState({
dashboards: json.result,
dashboardCount: json.count,
labelColumns: json.label_columns,
});
this.setState({ dashboards: json.result, dashboardCount: json.count });
})
.catch(() => {
this.props.addDangerToast(
@ -315,8 +346,48 @@ class DashboardList extends React.PureComponent<Props, State> {
});
};
updateFilters = () => {
const { filterOperators, owners } = this.state;
const convertFilter = ({
name: label,
operator,
}: {
name: string;
operator: string;
}) => ({ label, value: operator });
this.setState({
filters: [
{
Header: 'Dashboard',
id: 'dashboard_title',
operators: filterOperators.dashboard_title.map(convertFilter),
},
{
Header: 'Slug',
id: 'slug',
operators: filterOperators.slug.map(convertFilter),
},
{
Header: 'Owners',
id: 'owners',
input: 'select',
operators: filterOperators.owners.map(convertFilter),
selects: owners.map(({ text: label, value }) => ({ label, value })),
},
{
Header: 'Published',
id: 'published',
input: 'checkbox',
operators: filterOperators.published.map(convertFilter),
},
],
});
};
render() {
const { dashboards, dashboardCount, loading, filterTypes } = this.state;
const { dashboards, dashboardCount, loading, filters } = this.state;
return (
<div className="container welcome">
<Panel>
@ -362,7 +433,7 @@ class DashboardList extends React.PureComponent<Props, State> {
fetchData={this.fetchData}
loading={loading}
initialSort={this.initialSort}
filterTypes={filterTypes}
filters={filters}
bulkActions={bulkActions}
/>
);

View File

@ -142,27 +142,28 @@ class DashboardRestApi(DashboardMixin, BaseOwnedModelRestApi):
class_permission_name = "DashboardModelView"
show_columns = [
"charts",
"css",
"dashboard_title",
"slug",
"json_metadata",
"owners.id",
"owners.username",
"position_json",
"css",
"json_metadata",
"published",
"slug",
"table_names",
"charts",
]
order_columns = ["dashboard_title", "changed_on", "published", "changed_by_fk"]
list_columns = [
"id",
"dashboard_title",
"url",
"published",
"changed_by.username",
"changed_by_name",
"changed_by_url",
"changed_by.username",
"changed_on",
"dashboard_title",
"id",
"published",
"slug",
"url",
]
add_model_schema = DashboardPostSchema()