Skip to content

Commit 95493f9

Browse files
authored
Clean up implementation of ducks (#572)
1 parent 10a2934 commit 95493f9

File tree

16 files changed

+245
-175
lines changed

16 files changed

+245
-175
lines changed

src/containers/views/DraftEdit.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ export class DraftEdit extends Component {
7070
if (fieldChanged) {
7171
const [directory, ...rest] = params.splat;
7272
const filename = rest.join('.');
73-
putDraft('edit', directory, filename);
73+
putDraft(directory, filename);
7474
}
7575
};
7676

src/containers/views/DraftNew.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { browserHistory, withRouter } from 'react-router';
66
import DocumentTitle from 'react-document-title';
77
import CreateMarkdownPage from '../../components/CreateMarkdownPage';
88
import { updateTitle, updateBody, updatePath } from '../../ducks/metadata';
9-
import { putDraft } from '../../ducks/drafts';
9+
import { createDraft } from '../../ducks/drafts';
1010
import { clearErrors } from '../../ducks/utils';
1111
import { preventDefault, getDocumentTitle } from '../../utils/helpers';
1212
import { ADMIN_PREFIX } from '../../constants';
@@ -41,8 +41,8 @@ export class DraftNew extends Component {
4141

4242
handleClickSave = e => {
4343
preventDefault(e);
44-
const { fieldChanged, putDraft, params } = this.props;
45-
fieldChanged && putDraft('create', params.splat);
44+
const { fieldChanged, createDraft, params } = this.props;
45+
fieldChanged && createDraft(params.splat);
4646
};
4747

4848
render() {
@@ -79,7 +79,7 @@ export class DraftNew extends Component {
7979
}
8080

8181
DraftNew.propTypes = {
82-
putDraft: PropTypes.func.isRequired,
82+
createDraft: PropTypes.func.isRequired,
8383
updateTitle: PropTypes.func.isRequired,
8484
updateBody: PropTypes.func.isRequired,
8585
updatePath: PropTypes.func.isRequired,
@@ -108,7 +108,7 @@ const mapDispatchToProps = dispatch =>
108108
updateTitle,
109109
updateBody,
110110
updatePath,
111-
putDraft,
111+
createDraft,
112112
clearErrors,
113113
},
114114
dispatch

src/ducks/action_tests/drafts.spec.js

Lines changed: 43 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import configureMockStore from 'redux-mock-store';
22
import thunk from 'redux-thunk';
3+
import moment from 'moment';
34
import * as draftsDuck from '../drafts';
5+
import * as collectionsDuck from '../collections';
46
import * as utilsDuck from '../utils';
57
import { API } from '../../constants/api';
68
import nock from 'nock';
79

8-
import { draft, new_draft } from './fixtures';
10+
import { draft, new_draft, publishedDraft } from './fixtures';
911

1012
const middlewares = [thunk];
1113
const mockStore = configureMockStore(middlewares);
@@ -109,7 +111,7 @@ describe('Actions::Drafts', () => {
109111
const store = mockStore({ metadata: { metadata: draft } });
110112

111113
return store
112-
.dispatch(draftsDuck.putDraft('edit', null, 'draft-post.md'))
114+
.dispatch(draftsDuck.putDraft(null, 'draft-post.md'))
113115
.then(() => {
114116
expect(store.getActions()).toEqual(expectedActions);
115117
});
@@ -127,14 +129,14 @@ describe('Actions::Drafts', () => {
127129

128130
const store = mockStore({ metadata: { metadata: new_draft } });
129131

130-
return store.dispatch(draftsDuck.putDraft('create')).then(() => {
132+
return store.dispatch(draftsDuck.createDraft('')).then(() => {
131133
expect(store.getActions()).toEqual(expectedActions);
132134
});
133135
});
134136

135137
it('creates the draft with autogenerated filename', () => {
136138
nock(API)
137-
.put('/drafts/draft-post.md')
139+
.put(`/drafts/${new_draft.relative_path}`)
138140
.reply(200, draft);
139141

140142
const expectedActions = [
@@ -146,7 +148,7 @@ describe('Actions::Drafts', () => {
146148
metadata: { metadata: { ...new_draft, path: '' } },
147149
});
148150

149-
return store.dispatch(draftsDuck.putDraft('create')).then(() => {
151+
return store.dispatch(draftsDuck.createDraft('')).then(() => {
150152
expect(store.getActions()).toEqual(expectedActions);
151153
});
152154
});
@@ -163,11 +165,37 @@ describe('Actions::Drafts', () => {
163165

164166
const store = mockStore({ metadata: { metadata: draft } });
165167

166-
return store.dispatch(draftsDuck.putDraft('edit')).then(() => {
168+
return store.dispatch(draftsDuck.putDraft(draft.relative_path)).then(() => {
167169
expect(store.getActions()[1].type).toEqual(expectedActions[1].type);
168170
});
169171
});
170172

173+
it('publishes the draft as a post successfully', () => {
174+
const today = moment().format('YYYY-MM-DD');
175+
const datedfilename = `${today}-draft-post.md`;
176+
const doc = {
177+
...publishedDraft,
178+
date: `${today} 00:00:00 +0200`,
179+
};
180+
181+
nock(API)
182+
.put(`/collections/posts/${datedfilename}`)
183+
.reply(200, doc);
184+
185+
const expectedActions = [
186+
{ type: utilsDuck.CLEAR_ERRORS },
187+
{ type: collectionsDuck.PUT_DOCUMENT_SUCCESS, doc },
188+
];
189+
190+
const store = mockStore({ metadata: { metadata: publishedDraft } });
191+
192+
return store
193+
.dispatch(draftsDuck.publishDraft(null, datedfilename))
194+
.then(() => {
195+
expect(store.getActions()).toEqual(expectedActions);
196+
});
197+
});
198+
171199
it('creates VALIDATION_ERROR if required fields are not provided.', () => {
172200
const expectedActions = [
173201
{
@@ -180,7 +208,15 @@ describe('Actions::Drafts', () => {
180208
metadata: { metadata: { path: '', title: '' } },
181209
});
182210

183-
store.dispatch(draftsDuck.putDraft('create'));
211+
store.dispatch(draftsDuck.createDraft(''));
184212
expect(store.getActions()).toEqual(expectedActions);
213+
214+
store.dispatch(draftsDuck.putDraft(null, 'draft-post.md'));
215+
expect(store.getActions()).toEqual(expectedActions.concat(expectedActions));
216+
217+
store.dispatch(draftsDuck.publishDraft(null, 'draft-post.md'));
218+
expect(store.getActions()).toEqual(
219+
expectedActions.concat(expectedActions).concat(expectedActions)
220+
);
185221
});
186222
});

src/ducks/action_tests/fixtures/index.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,19 @@ export const draft = {
126126
}
127127
};
128128

129+
export const publishedDraft = {
130+
raw_content: "# Test Draft\n",
131+
name: "draft-post.md",
132+
relative_path: "draft-post.md",
133+
title: "Draft Post",
134+
slug: "draft-post",
135+
collection: "posts",
136+
draft: true,
137+
front_matter: {
138+
title: "Draft Post"
139+
}
140+
};
141+
129142
export const new_draft = {
130143
raw_content: "# Test Draft\n",
131144
name: "draft-post.md",

src/ducks/collections.js

Lines changed: 38 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
1-
import _ from 'underscore';
21
import moment from 'moment';
3-
import { CLEAR_ERRORS, validationError, filterDeleted } from './utils';
2+
import { clearErrors, validationError, filterDeleted } from './utils';
43
import { get, put, del } from '../utils/fetch';
54
import { validator } from '../utils/validation';
6-
import { slugify, trimObject, computeRelativePath } from '../utils/helpers';
5+
import {
6+
slugify,
7+
preparePayload,
8+
sanitizeFrontMatter,
9+
computeRelativePath,
10+
} from '../utils/helpers';
711
import {
812
collectionsAPIUrl,
913
collectionAPIUrl,
@@ -45,27 +49,20 @@ export const fetchCollections = () => dispatch => {
4549
);
4650
};
4751

48-
export const fetchCollection = (
49-
collection_name,
50-
directory = ''
51-
) => dispatch => {
52+
export const fetchCollection = (collection, directory = '') => dispatch => {
5253
dispatch({ type: FETCH_COLLECTION_REQUEST });
5354
return get(
54-
collectionAPIUrl(collection_name, directory),
55+
collectionAPIUrl(collection, directory),
5556
{ type: FETCH_COLLECTION_SUCCESS, name: 'entries' },
5657
{ type: FETCH_COLLECTION_FAILURE, name: 'error' },
5758
dispatch
5859
);
5960
};
6061

61-
export const fetchDocument = (
62-
collection_name,
63-
directory,
64-
filename
65-
) => dispatch => {
62+
export const fetchDocument = (collection, directory, filename) => dispatch => {
6663
dispatch({ type: FETCH_DOCUMENT_REQUEST });
6764
return get(
68-
documentAPIUrl(collection_name, directory, filename),
65+
documentAPIUrl(collection, directory, filename),
6966
{ type: FETCH_DOCUMENT_SUCCESS, name: 'doc' },
7067
{ type: FETCH_DOCUMENT_FAILURE, name: 'error' },
7168
dispatch
@@ -78,10 +75,11 @@ export const createDocument = (collection, directory) => (
7875
) => {
7976
// get edited fields from metadata state
8077
const metadata = getState().metadata.metadata;
81-
let { path, raw_content, title } = metadata;
82-
// if path is not given or equals to directory, generate filename from the title
78+
let { path, raw_content, title, date } = metadata;
79+
// if `path` is a falsy value or if appending a slash to it equals to
80+
// `directory`, generate filename from `title`.
8381
if ((!path || `${path}/` === directory) && title) {
84-
path = generateFilenameFromTitle(metadata, collection); // override empty path
82+
path = generateFilenameFromTitle(title, collection, date);
8583
} else {
8684
// validate otherwise
8785
const errors = validateDocument(metadata, collection);
@@ -90,14 +88,12 @@ export const createDocument = (collection, directory) => (
9088
}
9189
}
9290
// clear errors
93-
dispatch({ type: CLEAR_ERRORS });
94-
// omit raw_content, path and empty-value keys in metadata state from front_matter
95-
const front_matter = _.omit(metadata, (value, key, object) => {
96-
return key === 'raw_content' || key === 'path' || value === '';
97-
});
91+
dispatch(clearErrors());
92+
93+
const front_matter = sanitizeFrontMatter(metadata);
94+
9895
// send the put request
9996
return put(
100-
// create or update document according to filename existence
10197
documentAPIUrl(collection, directory, path),
10298
preparePayload({ raw_content, front_matter }),
10399
{ type: PUT_DOCUMENT_SUCCESS, name: 'doc' },
@@ -112,10 +108,11 @@ export const putDocument = (collection, directory, filename) => (
112108
) => {
113109
// get edited fields from metadata state
114110
const metadata = getState().metadata.metadata;
115-
let { path, raw_content, title } = metadata;
116-
// if path is not given or equals to directory, generate filename from the title
111+
let { path, raw_content, title, date } = metadata;
112+
// if `path` is a falsy value or if appending a slash to it equals to
113+
// `directory`, generate filename from `title`.
117114
if ((!path || `${path}/` === directory) && title) {
118-
path = generateFilenameFromTitle(metadata, collection); // override empty path
115+
path = generateFilenameFromTitle(title, collection, date);
119116
} else {
120117
// validate otherwise
121118
const errors = validateDocument(metadata, collection);
@@ -124,18 +121,17 @@ export const putDocument = (collection, directory, filename) => (
124121
}
125122
}
126123
// clear errors
127-
dispatch({ type: CLEAR_ERRORS });
128-
// omit raw_content, path and empty-value keys in metadata state from front_matter
129-
const front_matter = _.omit(metadata, (value, key, object) => {
130-
return key === 'raw_content' || key === 'path' || value === '';
131-
});
124+
dispatch(clearErrors());
125+
126+
const front_matter = sanitizeFrontMatter(metadata);
127+
132128
// add collection type prefix to relative path
133129
const relative_path = directory
134130
? `_${collection}/${directory}/${path}`
135131
: `_${collection}/${path}`;
132+
136133
// send the put request
137134
return put(
138-
// create or update document according to filename existence
139135
documentAPIUrl(collection, directory, filename),
140136
preparePayload({ path: relative_path, raw_content, front_matter }),
141137
{ type: PUT_DOCUMENT_SUCCESS, name: 'doc' },
@@ -154,18 +150,14 @@ export const deleteDocument = (collection, directory, filename) => dispatch => {
154150
);
155151
};
156152

157-
const generateFilenameFromTitle = (metadata, collection) => {
153+
const generateFilenameFromTitle = (title, collection, date) => {
154+
const slugifiedTitle = slugify(title);
158155
if (collection === 'posts') {
159156
// if date is provided, use it, otherwise generate it with today's date
160-
let date;
161-
if (metadata.date) {
162-
date = metadata.date.split(' ')[0];
163-
} else {
164-
date = moment().format('YYYY-MM-DD');
165-
}
166-
return `${date}-${slugify(metadata.title)}.md`;
157+
const docDate = date ? date.split(' ')[0] : moment().format('YYYY-MM-DD');
158+
return `${docDate}-${slugifiedTitle}.md`;
167159
}
168-
return `${slugify(metadata.title)}.md`;
160+
return `${slugifiedTitle}.md`;
169161
};
170162

171163
const validateDocument = (metadata, collection) => {
@@ -186,8 +178,6 @@ const validateDocument = (metadata, collection) => {
186178
return validator(metadata, validations, messages);
187179
};
188180

189-
const preparePayload = obj => JSON.stringify(trimObject(obj));
190-
191181
// Reducer
192182
export default function collections(
193183
state = {
@@ -264,17 +254,10 @@ export default function collections(
264254

265255
// Selectors
266256
export const filterBySearchInput = (list, input) => {
267-
if (!list) {
268-
return [];
257+
if (!input) {
258+
return list;
269259
}
270-
if (input) {
271-
return _.filter(list, item => {
272-
if (item.type) {
273-
return item.name.toLowerCase().includes(input.toLowerCase());
274-
} else {
275-
return item.title.toLowerCase().includes(input.toLowerCase());
276-
}
277-
});
278-
}
279-
return list;
260+
return list.filter(f =>
261+
(f.title || f.name).toLowerCase().includes(input.toLowerCase())
262+
);
280263
};

src/ducks/config.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { getConfigurationUrl, putConfigurationUrl } from '../constants/api';
22
import { get, put } from '../utils/fetch';
33
import { validator } from '../utils/validation';
4-
import { CLEAR_ERRORS, validationError } from './utils';
4+
import { clearErrors, validationError } from './utils';
55
import { toYAML } from '../utils/helpers';
66

77
import translations from '../translations';
@@ -45,7 +45,7 @@ export const putConfig = (config, source = 'editor') => (
4545
if (errors.length) {
4646
return dispatch(validationError(errors));
4747
}
48-
dispatch({ type: CLEAR_ERRORS });
48+
dispatch(clearErrors());
4949

5050
return put(
5151
putConfigurationUrl(),

src/ducks/datafiles.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { CLEAR_ERRORS, validationError, filterDeleted } from './utils';
1+
import { clearErrors, validationError, filterDeleted } from './utils';
22
import { get, put, del } from '../utils/fetch';
33
import { datafilesAPIUrl, datafileAPIUrl } from '../constants/api';
44
import {
@@ -82,7 +82,7 @@ export const putDataFile = (
8282
if (errors.length) {
8383
return dispatch(validationError(errors));
8484
}
85-
dispatch({ type: CLEAR_ERRORS });
85+
dispatch(clearErrors());
8686

8787
return put(
8888
datafileAPIUrl(directory, filename),

0 commit comments

Comments
 (0)