feat: add filter to collection view (#3741)

This commit is contained in:
Shashank Bairy R
2020-05-24 17:37:08 +00:00
committed by GitHub
parent d3aaf4ddb3
commit c28cc0c9e7
23 changed files with 652 additions and 63 deletions

View File

@ -4,6 +4,7 @@ import reducer, {
selectMediaFolder,
selectMediaFilePath,
selectMediaFilePublicPath,
selectEntries,
} from '../entries';
const initialState = OrderedMap({
@ -559,4 +560,118 @@ describe('entries', () => {
).toBe('/images/image.png');
});
});
describe('selectEntries', () => {
it('should return all entries', () => {
const state = fromJS({
entities: {
'posts.1': { slug: '1' },
'posts.2': { slug: '2' },
'posts.3': { slug: '3' },
'posts.4': { slug: '4' },
},
pages: { posts: { ids: ['1', '2', '3', '4'] } },
});
const collection = fromJS({
name: 'posts',
});
expect(selectEntries(state, collection)).toEqual(
fromJS([{ slug: '1' }, { slug: '2' }, { slug: '3' }, { slug: '4' }]),
);
});
});
it('should return sorted entries entries by field', () => {
const state = fromJS({
entities: {
'posts.1': { slug: '1', data: { title: '1' } },
'posts.2': { slug: '2', data: { title: '2' } },
'posts.3': { slug: '3', data: { title: '3' } },
'posts.4': { slug: '4', data: { title: '4' } },
},
pages: { posts: { ids: ['1', '2', '3', '4'] } },
sort: { posts: { title: { key: 'title', direction: 'Descending' } } },
});
const collection = fromJS({
name: 'posts',
});
expect(selectEntries(state, collection)).toEqual(
fromJS([
{ slug: '4', data: { title: '4' } },
{ slug: '3', data: { title: '3' } },
{ slug: '2', data: { title: '2' } },
{ slug: '1', data: { title: '1' } },
]),
);
});
it('should return sorted entries entries by nested field', () => {
const state = fromJS({
entities: {
'posts.1': { slug: '1', data: { title: '1', nested: { date: 4 } } },
'posts.2': { slug: '2', data: { title: '2', nested: { date: 3 } } },
'posts.3': { slug: '3', data: { title: '3', nested: { date: 2 } } },
'posts.4': { slug: '4', data: { title: '4', nested: { date: 1 } } },
},
pages: { posts: { ids: ['1', '2', '3', '4'] } },
sort: { posts: { title: { key: 'nested.date', direction: 'Ascending' } } },
});
const collection = fromJS({
name: 'posts',
});
expect(selectEntries(state, collection)).toEqual(
fromJS([
{ slug: '4', data: { title: '4', nested: { date: 1 } } },
{ slug: '3', data: { title: '3', nested: { date: 2 } } },
{ slug: '2', data: { title: '2', nested: { date: 3 } } },
{ slug: '1', data: { title: '1', nested: { date: 4 } } },
]),
);
});
it('should return filtered entries entries by field', () => {
const state = fromJS({
entities: {
'posts.1': { slug: '1', data: { title: '1' } },
'posts.2': { slug: '2', data: { title: '2' } },
'posts.3': { slug: '3', data: { title: '3' } },
'posts.4': { slug: '4', data: { title: '4' } },
},
pages: { posts: { ids: ['1', '2', '3', '4'] } },
filter: { posts: { title__1: { field: 'title', pattern: '4', active: true } } },
});
const collection = fromJS({
name: 'posts',
});
expect(selectEntries(state, collection)).toEqual(fromJS([{ slug: '4', data: { title: '4' } }]));
});
it('should return filtered entries entries by nested field', () => {
const state = fromJS({
entities: {
'posts.1': { slug: '1', data: { title: '1', nested: { draft: true } } },
'posts.2': { slug: '2', data: { title: '2', nested: { draft: true } } },
'posts.3': { slug: '3', data: { title: '3', nested: { draft: false } } },
'posts.4': { slug: '4', data: { title: '4', nested: { draft: false } } },
},
pages: { posts: { ids: ['1', '2', '3', '4'] } },
filter: {
posts: { 'nested.draft__false': { field: 'nested.draft', pattern: false, active: true } },
},
});
const collection = fromJS({
name: 'posts',
});
expect(selectEntries(state, collection)).toEqual(
fromJS([
{ slug: '3', data: { title: '3', nested: { draft: false } } },
{ slug: '4', data: { title: '4', nested: { draft: false } } },
]),
);
});
});

View File

@ -12,6 +12,7 @@ import {
EntryField,
State,
EntryMap,
ViewFilter,
} from '../types/redux';
import { selectMediaFolder } from './entries';
import { stringTemplate } from 'netlify-cms-lib-widgets';
@ -423,6 +424,11 @@ export const selectSortDataPath = (collection: Collection, key: string) => {
}
};
export const selectViewFilters = (collection: Collection) => {
const viewFilters = collection.get('view_filters').toJS() as ViewFilter[];
return viewFilters;
};
export const selectFieldsComments = (collection: Collection, entryMap: EntryMap) => {
let fields: EntryField[] = [];
if (collection.has('folder')) {

View File

@ -11,6 +11,9 @@ import {
SORT_ENTRIES_REQUEST,
SORT_ENTRIES_SUCCESS,
SORT_ENTRIES_FAILURE,
FILTER_ENTRIES_REQUEST,
FILTER_ENTRIES_SUCCESS,
FILTER_ENTRIES_FAILURE,
} from '../actions/entries';
import { SEARCH_ENTRIES_SUCCESS } from '../actions/search';
import {
@ -30,16 +33,23 @@ import {
EntryField,
CollectionFiles,
EntriesSortRequestPayload,
EntriesSortSuccessPayload,
EntriesSortFailurePayload,
SortMap,
SortObject,
Sort,
SortDirection,
Filter,
FilterMap,
EntriesFilterRequestPayload,
EntriesFilterFailurePayload,
} from '../types/redux';
import { folderFormatter } from '../lib/formatters';
import { isAbsolutePath, basename } from 'netlify-cms-lib-util';
import { trim, once, sortBy, set } from 'lodash';
import { trim, once, sortBy, set, orderBy } from 'lodash';
import { selectSortDataPath } from './collections';
import { stringTemplate } from 'netlify-cms-lib-widgets';
const { keyToPathArray } = stringTemplate;
let collection: string;
let loadedEntries: EntryObject[];
@ -203,8 +213,9 @@ const entries = (
return newState;
}
case FILTER_ENTRIES_SUCCESS:
case SORT_ENTRIES_SUCCESS: {
const payload = action.payload as EntriesSortSuccessPayload;
const payload = action.payload as { collection: string; entries: EntryObject[] };
const { collection, entries } = payload;
loadedEntries = entries;
const newState = state.withMutations(map => {
@ -238,6 +249,29 @@ const entries = (
return newState;
}
case FILTER_ENTRIES_REQUEST: {
const payload = action.payload as EntriesFilterRequestPayload;
const { collection, filter } = payload;
const newState = state.withMutations(map => {
const current: FilterMap = map.getIn(['filter', collection, filter.id], fromJS(filter));
map.setIn(
['filter', collection, current.get('id')],
current.set('active', !current.get('active')),
);
});
return newState;
}
case FILTER_ENTRIES_FAILURE: {
const payload = action.payload as EntriesFilterFailurePayload;
const { collection, filter } = payload;
const newState = state.withMutations(map => {
map.deleteIn(['filter', collection, filter.id]);
map.setIn(['pages', collection, 'isFetching'], false);
});
return newState;
}
default:
return state;
}
@ -248,6 +282,11 @@ export const selectEntriesSort = (entries: Entries, collection: string) => {
return sort?.get(collection);
};
export const selectEntriesFilter = (entries: Entries, collection: string) => {
const filter = entries.get('filter') as Filter | undefined;
return filter?.get(collection) || Map();
};
export const selectEntriesSortFields = (entries: Entries, collection: string) => {
const sort = selectEntriesSort(entries, collection);
const values =
@ -255,6 +294,17 @@ export const selectEntriesSortFields = (entries: Entries, collection: string) =>
?.valueSeq()
.filter(v => v?.get('direction') !== SortDirection.None)
.toArray() || [];
return values;
};
export const selectEntriesFilterFields = (entries: Entries, collection: string) => {
const filter = selectEntriesFilter(entries, collection);
const values =
filter
?.valueSeq()
.filter(v => v?.get('active') === true)
.toArray() || [];
return values;
};
@ -264,10 +314,39 @@ export const selectEntry = (state: Entries, collection: string, slug: string) =>
export const selectPublishedSlugs = (state: Entries, collection: string) =>
state.getIn(['pages', collection, 'ids'], List<string>());
export const selectEntries = (state: Entries, collection: string) => {
const slugs = selectPublishedSlugs(state, collection);
const entries =
slugs && (slugs.map(slug => selectEntry(state, collection, slug as string)) as List<EntryMap>);
export const selectEntries = (state: Entries, collection: Collection) => {
const collectionName = collection.get('name');
const slugs = selectPublishedSlugs(state, collectionName);
let entries =
slugs &&
(slugs.map(slug => selectEntry(state, collectionName, slug as string)) as List<EntryMap>);
const sortFields = selectEntriesSortFields(state, collectionName);
if (sortFields && sortFields.length > 0) {
const keys = sortFields.map(v => selectSortDataPath(collection, v.get('key')));
const orders = sortFields.map(v =>
v.get('direction') === SortDirection.Ascending ? 'asc' : 'desc',
);
entries = fromJS(orderBy(entries.toJS(), keys, orders));
}
const filters = selectEntriesFilterFields(state, collectionName);
if (filters && filters.length > 0) {
entries = entries
.filter(e => {
const allMatched = filters.every(f => {
const pattern = f.get('pattern');
const field = f.get('field');
const data = e!.get('data') || Map();
const toMatch = data.getIn(keyToPathArray(field));
const matched =
toMatch !== undefined && new RegExp(String(pattern)).test(String(toMatch));
return matched;
});
return allMatched;
})
.toList();
}
return entries;
};

View File

@ -12,7 +12,7 @@ import mediaLibrary from './mediaLibrary';
import deploys, * as fromDeploys from './deploys';
import globalUI from './globalUI';
import { Status } from '../constants/publishModes';
import { State } from '../types/redux';
import { State, Collection } from '../types/redux';
const reducers = {
auth,
@ -38,7 +38,7 @@ export default reducers;
export const selectEntry = (state: State, collection: string, slug: string) =>
fromEntries.selectEntry(state.entries, collection, slug);
export const selectEntries = (state: State, collection: string) =>
export const selectEntries = (state: State, collection: Collection) =>
fromEntries.selectEntries(state.entries, collection);
export const selectPublishedSlugs = (state: State, collection: string) =>