Skip to content

feat: add collection size limit feature #7451

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
selectEntriesGroup,
selectViewStyle,
} from '../../reducers/entries';
import { selectCanCreateNewEntry } from '../../reducers';

const CollectionContainer = styled.div`
margin: ${lengths.pageMargin};
Expand Down Expand Up @@ -53,6 +54,9 @@ export class Collection extends React.Component {
sortableFields: PropTypes.array,
sort: ImmutablePropTypes.orderedMap,
onSortClick: PropTypes.func.isRequired,
canCreate: PropTypes.bool.isRequired,
filterTerm: PropTypes.string,
viewStyle: PropTypes.string,
};

renderEntriesCollection = () => {
Expand Down Expand Up @@ -94,9 +98,10 @@ export class Collection extends React.Component {
group,
onChangeViewStyle,
viewStyle,
canCreate,
} = this.props;

let newEntryUrl = collection.get('create') ? getNewEntryUrl(collectionName) : '';
let newEntryUrl = canCreate ? getNewEntryUrl(collectionName) : '';
if (newEntryUrl && filterTerm) {
newEntryUrl = getNewEntryUrl(collectionName);
if (filterTerm) {
Expand Down Expand Up @@ -162,6 +167,7 @@ function mapStateToProps(state, ownProps) {
const filter = selectEntriesFilter(state.entries, collection.get('name'));
const group = selectEntriesGroup(state.entries, collection.get('name'));
const viewStyle = selectViewStyle(state.entries);
const canCreate = selectCanCreateNewEntry(state, name);

return {
collection,
Expand All @@ -178,6 +184,7 @@ function mapStateToProps(state, ownProps) {
filter,
group,
viewStyle,
canCreate,
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,22 +29,26 @@ describe('Collection', () => {
view_filters: [],
view_groups: [],
});
const props = {
const baseProps = {
collections: fromJS([collection]).toOrderedMap(),
collection,
collectionName: collection.get('name'),
t: jest.fn(key => key),
onSortClick: jest.fn(),
viewStyle: 'list',
};

it('should render with collection without create url', () => {
const props = { ...baseProps, canCreate: false };
const { asFragment } = render(
<Collection {...props} collection={collection.set('create', false)} />,
);

expect(asFragment()).toMatchSnapshot();
});

it('should render with collection with create url', () => {
const props = { ...baseProps, canCreate: false };
const { asFragment } = render(
<Collection {...props} collection={collection.set('create', true)} />,
);
Expand All @@ -53,17 +57,18 @@ describe('Collection', () => {
});

it('should render with collection with create url and path', () => {
const props = { ...baseProps, canCreate: false, filterTerm: 'dir1/dir2' };
const { asFragment } = render(
<Collection {...props} collection={collection.set('create', true)} filterTerm="dir1/dir2" />,
<Collection {...props} collection={collection.set('create', true)} />,
);

expect(asFragment()).toMatchSnapshot();
});

it('should render connected component', () => {
const store = mockStore({
collections: props.collections,
entries: fromJS({}),
collections: baseProps.collections,
entries: fromJS({ pages: { entries: fromJS([]) } }),
});

const { asFragment } = renderWithRedux(<ConnectedCollection match={{ params: {} }} />, {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,14 @@ exports[`Collection should render with collection with create url 1`] = `
>
<mock-collection-top
collection="Map { \\"name\\": \\"pages\\", \\"sortable_fields\\": List [], \\"view_filters\\": List [], \\"view_groups\\": List [], \\"create\\": true }"
newentryurl="/collections/pages/new"
newentryurl=""
/>
<mock-collection-controls
viewstyle="list"
/>
<mock-collection-controls />
<mock-entries-collection
collection="Map { \\"name\\": \\"pages\\", \\"sortable_fields\\": List [], \\"view_filters\\": List [], \\"view_groups\\": List [], \\"create\\": true }"
viewstyle="list"
/>
</main>
</div>
Expand Down Expand Up @@ -98,12 +101,15 @@ exports[`Collection should render with collection with create url and path 1`] =
>
<mock-collection-top
collection="Map { \\"name\\": \\"pages\\", \\"sortable_fields\\": List [], \\"view_filters\\": List [], \\"view_groups\\": List [], \\"create\\": true }"
newentryurl="/collections/pages/new?path=dir1/dir2"
newentryurl=""
/>
<mock-collection-controls
viewstyle="list"
/>
<mock-collection-controls />
<mock-entries-collection
collection="Map { \\"name\\": \\"pages\\", \\"sortable_fields\\": List [], \\"view_filters\\": List [], \\"view_groups\\": List [], \\"create\\": true }"
filterterm="dir1/dir2"
viewstyle="list"
/>
</main>
</div>
Expand Down Expand Up @@ -134,9 +140,12 @@ exports[`Collection should render with collection without create url 1`] = `
collection="Map { \\"name\\": \\"pages\\", \\"sortable_fields\\": List [], \\"view_filters\\": List [], \\"view_groups\\": List [], \\"create\\": false }"
newentryurl=""
/>
<mock-collection-controls />
<mock-collection-controls
viewstyle="list"
/>
<mock-entries-collection
collection="Map { \\"name\\": \\"pages\\", \\"sortable_fields\\": List [], \\"view_filters\\": List [], \\"view_groups\\": List [], \\"create\\": false }"
viewstyle="list"
/>
</main>
</div>
Expand Down
3 changes: 3 additions & 0 deletions packages/decap-cms-core/src/constants/configSchema.js
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,9 @@ function getConfigSchema() {
minProperties: 1,
},
i18n: i18nCollection,
limit: {
type: 'number',
},
},
required: ['name', 'label'],
oneOf: [{ required: ['files'] }, { required: ['folder', 'fields'] }],
Expand Down
32 changes: 28 additions & 4 deletions packages/decap-cms-core/src/reducers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,20 @@ import { List } from 'immutable';

import auth from './auth';
import config from './config';
import collections, * as fromCollections from './collections';
import integrations, * as fromIntegrations from './integrations';
import entries, * as fromEntries from './entries';
import cursors from './cursors';
import editorialWorkflow, * as fromEditorialWorkflow from './editorialWorkflow';
import entryDraft from './entryDraft';
import collections from './collections';
import search from './search';
import medias from './medias';
import mediaLibrary from './mediaLibrary';
import deploys, * as fromDeploys from './deploys';
import globalUI from './globalUI';
import search from './search';
import status from './status';
import notifications from './notifications';
import { FOLDER } from '../constants/collectionTypes';

import type { Status } from '../constants/publishModes';
import type { State, Collection } from '../types/redux';
Expand Down Expand Up @@ -42,8 +43,8 @@ export default reducers;
/*
* Selectors
*/
export function selectEntry(state: State, collection: string, slug: string) {
return fromEntries.selectEntry(state.entries, collection, slug);
export function selectEntry(state: State, collectionName: string, slug: string) {
return fromEntries.selectEntry(state.entries, collectionName, slug);
}

export function selectEntries(state: State, collection: Collection) {
Expand Down Expand Up @@ -77,6 +78,29 @@ export function selectUnpublishedSlugs(state: State, collection: string) {
return fromEditorialWorkflow.selectUnpublishedSlugs(state.editorialWorkflow, collection);
}

export function selectCanCreateNewEntry(state: State, collectionName: string) {
const collection = state.collections.get(collectionName);

if (!collection || !collection.get('create')) {
return false;
}

if (collection.get('type') !== FOLDER) {
return fromCollections.selectAllowNewEntries(collection);
}

const limit = collection.get('limit') as number | undefined;

if (limit === undefined || limit === null) {
return true;
}

const entries = fromEntries.selectEntries(state.entries, collection);
const entryCount = entries ? entries.size : 0;

return entryCount < limit;
}

export function selectIntegration(state: State, collection: string | null, hook: string) {
return fromIntegrations.selectIntegration(state.integrations, collection, hook);
}
2 changes: 2 additions & 0 deletions packages/decap-cms-core/src/types/redux.ts
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,7 @@ export interface CmsCollection {
view_filters?: ViewFilter[];
view_groups?: ViewGroup[];
i18n?: boolean | CmsI18nConfig;
limit?: number;

/**
* @deprecated Use sortable_fields instead
Expand Down Expand Up @@ -631,6 +632,7 @@ type CollectionObject = {
nested?: Nested;
meta?: Meta;
i18n: i18n;
limit?: number;
};

export type Collection = StaticallyTypedRecord<CollectionObject>;
Expand Down