Skip to content
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

Refactor ducks #572

Merged
merged 21 commits into from
Nov 12, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Next Next commit
Refactor ducks
  • Loading branch information
ashmaroli committed Dec 27, 2019
commit c8cbbe6750f3ddc4416d8c0ca3c9936790bbb750
2 changes: 1 addition & 1 deletion src/containers/views/DraftEdit.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export class DraftEdit extends Component {
if (fieldChanged) {
const [directory, ...rest] = params.splat;
const filename = rest.join('.');
putDraft('edit', directory, filename);
putDraft(directory, filename);
}
};

Expand Down
2 changes: 1 addition & 1 deletion src/containers/views/DraftNew.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export class DraftNew extends Component {
handleClickSave = e => {
preventDefault(e);
const { fieldChanged, putDraft, params } = this.props;
fieldChanged && putDraft('create', params.splat);
fieldChanged && putDraft(params.splat);
};

render() {
Expand Down
12 changes: 6 additions & 6 deletions src/ducks/action_tests/drafts.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ describe('Actions::Drafts', () => {
const store = mockStore({ metadata: { metadata: draft } });

return store
.dispatch(draftsDuck.putDraft('edit', null, 'draft-post.md'))
.dispatch(draftsDuck.putDraft(null, 'draft-post.md'))
.then(() => {
expect(store.getActions()).toEqual(expectedActions);
});
Expand All @@ -118,14 +118,14 @@ describe('Actions::Drafts', () => {

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

return store.dispatch(draftsDuck.putDraft('create')).then(() => {
return store.dispatch(draftsDuck.createDraft('')).then(() => {
expect(store.getActions()).toEqual(expectedActions);
});
});

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

const expectedActions = [
Expand All @@ -137,7 +137,7 @@ describe('Actions::Drafts', () => {
metadata: { metadata: { ...new_draft, path: '' } },
});

return store.dispatch(draftsDuck.putDraft('create')).then(() => {
return store.dispatch(draftsDuck.createDraft('')).then(() => {
expect(store.getActions()).toEqual(expectedActions);
});
});
Expand All @@ -154,7 +154,7 @@ describe('Actions::Drafts', () => {

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

return store.dispatch(draftsDuck.putDraft('edit')).then(() => {
return store.dispatch(draftsDuck.putDraft(draft.relative_path)).then(() => {
expect(store.getActions()[1].type).toEqual(expectedActions[1].type);
});
});
Expand All @@ -171,7 +171,7 @@ describe('Actions::Drafts', () => {
metadata: { metadata: { path: '', title: '' } },
});

store.dispatch(draftsDuck.putDraft('create'));
store.dispatch(draftsDuck.putDraft(''));
expect(store.getActions()).toEqual(expectedActions);
});
});
94 changes: 46 additions & 48 deletions src/ducks/collections.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ import moment from 'moment';
import { CLEAR_ERRORS, validationError } from './utils';
import { get, put } from '../utils/fetch';
import { validator } from '../utils/validation';
import { slugify, trimObject } from '../utils/helpers';
import {
slugify,
preparePayload,
getFrontMatterFromMetdata,
} from '../utils/helpers';
import {
collectionsAPIUrl,
collectionAPIUrl,
Expand Down Expand Up @@ -78,23 +82,17 @@ export const createDocument = (collection, directory) => (
) => {
// get edited fields from metadata state
const metadata = getState().metadata.metadata;
let { path, raw_content, title } = metadata;
// if path is not given or equals to directory, generate filename from the title
if ((!path || `${path}/` == directory) && title) {
path = generateFilenameFromTitle(metadata, collection); // override empty path
} else {
// validate otherwise
const errors = validateDocument(metadata, collection);
if (errors.length) {
return dispatch(validationError(errors));
}
}

// get path or return if metadata doesn't validate
const { path, errors } = validateMetadata(metadata, collection, directory);
if (errors.length) return dispatch(validationError(errors));

// clear errors
dispatch({ type: CLEAR_ERRORS });
// omit raw_content, path and empty-value keys in metadata state from front_matter
const front_matter = _.omit(metadata, (value, key, object) => {
return key == 'raw_content' || key == 'path' || value == '';
});

const front_matter = getFrontMatterFromMetdata(metadata);
const raw_content = metadata.raw_content;

// send the put request
return put(
// create or update document according to filename existence
Expand All @@ -112,27 +110,22 @@ export const putDocument = (collection, directory, filename) => (
) => {
// get edited fields from metadata state
const metadata = getState().metadata.metadata;
let { path, raw_content, title } = metadata;
// if path is not given or equals to directory, generate filename from the title
if ((!path || `${path}/` == directory) && title) {
path = generateFilenameFromTitle(metadata, collection); // override empty path
} else {
// validate otherwise
const errors = validateDocument(metadata, collection);
if (errors.length) {
return dispatch(validationError(errors));
}
}

// get path or abort if metadata doesn't validate
const { path, errors } = validateMetadata(metadata, collection, directory);
if (errors.length) return dispatch(validationError(errors));

// clear errors
dispatch({ type: CLEAR_ERRORS });
// omit raw_content, path and empty-value keys in metadata state from front_matter
const front_matter = _.omit(metadata, (value, key, object) => {
return key == 'raw_content' || key == 'path' || value == '';
});

const raw_content = metadata.raw_content;
const front_matter = getFrontMatterFromMetdata(metadata);

// add collection type prefix to relative path
const relative_path = directory
? `_${collection}/${directory}/${path}`
: `_${collection}/${path}`;

// send the put request
return put(
// create or update document according to filename existence
Expand Down Expand Up @@ -161,18 +154,28 @@ export const deleteDocument = (collection, directory, filename) => dispatch => {
);
};

const generateFilenameFromTitle = (metadata, collection) => {
const getFilenameFromTitle = (title, collection, date) => {
const slugifiedTitle = slugify(title);
if (collection == 'posts') {
// if date is provided, use it, otherwise generate it with today's date
let date;
if (metadata.date) {
date = metadata.date.split(' ')[0];
} else {
date = moment().format('YYYY-MM-DD');
}
return `${date}-${slugify(metadata.title)}.md`;
const docDate = date ? date.split(' ')[0] : moment().format('YYYY-MM-DD');
return `${docDate}-${slugifiedTitle}.md`;
}
return `${slugify(metadata.title)}.md`;
return `${slugifiedTitle}.md`;
};

const validateMetadata = (metadata, collection, directory) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not just validating metadata but also mutating path. I don't think we should mix things here. In order to understand what this function does to path, we need to look into it. It seems not write to me.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, what would be the best way to proceed yet avoid repeating the following logic in every duck (that handles metadata)?

const validateMetadata = (metadata, directory) => {
  let { path, title } = metadata;
  let errors = [];

  if ((!path || `${path}/` === directory) && title) {
    path = getFilenameFromTitle(title);
  } else {
    errors = validatePage(metadata);
  }
  return { path, errors };
};

let { path, title, date } = metadata;
let errors = [];

// if path is not given or equals to directory, generate filename from the title
if ((!path || `${path}/` == directory) && title) {
path = getFilenameFromTitle(title, collection, date); // override empty path
} else {
// validate otherwise
errors = validateDocument(metadata, collection);
}
return { path, errors };
};

const validateDocument = (metadata, collection) => {
Expand All @@ -193,8 +196,6 @@ const validateDocument = (metadata, collection) => {
return validator(metadata, validations, messages);
};

const preparePayload = obj => JSON.stringify(trimObject(obj));

// Reducer
export default function collections(
state = {
Expand Down Expand Up @@ -266,17 +267,14 @@ export default function collections(

// Selectors
export const filterBySearchInput = (list, input) => {
if (!list) {
return [];
}
if (input) {
return _.filter(list, item => {
if (item.type) {
return list.filter(item => {
if (item.name) {
return item.name.toLowerCase().includes(input.toLowerCase());
} else {
return item.title.toLowerCase().includes(input.toLowerCase());
}
});
}
return list;
return list || [];
};
102 changes: 52 additions & 50 deletions src/ducks/drafts.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,29 +6,28 @@ import {
PUT_DOCUMENT_FAILURE,
} from './collections';
import { get, put } from '../utils/fetch';
import { validator } from '../utils/validation';
import { slugify, trimObject } from '../utils/helpers';
import { validateMetadata } from '../utils/validation';
import { preparePayload, getFrontMatterFromMetdata } from '../utils/helpers';
import { draftsAPIUrl, draftAPIUrl, documentAPIUrl } from '../constants/api';

import translations from '../translations';
const { getTitleRequiredMessage, getFilenameNotValidMessage } = translations;

// Action Types
export const FETCH_DRAFTS_REQUEST = 'FETCH_DRAFTS_REQUEST';
export const FETCH_DRAFTS_SUCCESS = 'FETCH_DRAFTS_SUCCESS';
export const FETCH_DRAFTS_FAILURE = 'FETCH_DRAFTS_FAILURE';

export const FETCH_DRAFT_REQUEST = 'FETCH_DRAFT_REQUEST';
export const FETCH_DRAFT_SUCCESS = 'FETCH_DRAFT_SUCCESS';
export const FETCH_DRAFT_FAILURE = 'FETCH_DRAFT_FAILURE';

export const PUT_DRAFT_REQUEST = 'PUT_DRAFT_REQUEST';
export const PUT_DRAFT_SUCCESS = 'PUT_DRAFT_SUCCESS';
export const PUT_DRAFT_FAILURE = 'PUT_DRAFT_FAILURE';

export const DELETE_DRAFT_REQUEST = 'DELETE_DRAFT_REQUEST';
export const DELETE_DRAFT_SUCCESS = 'DELETE_DRAFT_SUCCESS';
export const DELETE_DRAFT_FAILURE = 'DELETE_DRAFT_FAILURE';

// Actions
export const fetchDrafts = (directory = '') => dispatch => {
dispatch({ type: FETCH_DRAFTS_REQUEST });
return get(
Expand All @@ -49,44 +48,54 @@ export const fetchDraft = (directory, filename) => dispatch => {
);
};

export const putDraft = (mode, directory, filename = '') => (
dispatch,
getState
) => {
export const createDraft = directory => (dispatch, getState) => {
// get edited fields from metadata state
const metadata = getState().metadata.metadata;
let { path, raw_content, title } = metadata;

// if path is not given or equals to directory, generate filename from the title
if (!path && title) {
path = `${slugify(title)}.md`;
} else if (!path && !title) {
return dispatch(validationError(validateDraft(metadata)));
}
// get path or return if metadata doesn't validate
const { path, errors } = validateMetadata(metadata, directory);
if (errors.length) return dispatch(validationError(errors));

// clear errors
dispatch({ type: CLEAR_ERRORS });

// omit raw_content, path and empty-value keys in metadata state from front_matter
const front_matter = _.omit(metadata, (value, key, object) => {
return key == 'raw_content' || key == 'path' || value === '';
});

let payload;
if (mode == 'create') {
// strip '_drafts/' from path when provided
filename = path.replace('_drafts/', '');
payload = { front_matter, raw_content };
} else {
let writePath = directory
? `_drafts/${directory}/${path}`
: `_drafts/${path}`;
payload = { path: writePath, front_matter, raw_content };
}
const raw_content = metadata.raw_content;
const front_matter = getFrontMatterFromMetdata(metadata);

// strip '_drafts/' from path when provided
const filename = path.replace('_drafts/', '');

//send the put request
return put(
draftAPIUrl(directory, filename),
preparePayload(payload),
preparePayload({ front_matter, raw_content }),
{ type: PUT_DRAFT_SUCCESS, name: 'draft' },
{ type: PUT_DRAFT_FAILURE, name: 'error' },
dispatch
);
};

export const putDraft = (directory, filename) => (dispatch, getState) => {
// get edited fields from metadata state
const metadata = getState().metadata.metadata;

// get path or return if metadata doesn't validate
const { path, errors } = validateMetadata(metadata, directory);
if (errors.length) return dispatch(validationError(errors));

// clear errors
dispatch({ type: CLEAR_ERRORS });

const raw_content = metadata.raw_content;
const front_matter = getFrontMatterFromMetdata(metadata);
const relative_path = directory
? `_drafts/${directory}/${path}`
: `_drafts/${path}`;

//send the put request
return put(
draftAPIUrl(directory, filename),
preparePayload({ path: relative_path, front_matter, raw_content }),
{ type: PUT_DRAFT_SUCCESS, name: 'draft' },
{ type: PUT_DRAFT_FAILURE, name: 'error' },
dispatch
Expand All @@ -112,12 +121,16 @@ export const deleteDraft = (directory, filename) => dispatch => {

export const publishDraft = (directory, filename) => (dispatch, getState) => {
const metadata = getState().metadata.metadata;
const { raw_content } = metadata;

// omit raw_content, path and empty-value keys in metadata state from front_matter
const front_matter = _.omit(metadata, (value, key, object) => {
return key == 'raw_content' || key == 'path' || value == '';
});
// return if metadata doesn't validate
const { errors } = validateMetadata(metadata, directory);
if (errors.length) return dispatch(validationError(errors));

// clear errors
dispatch({ type: CLEAR_ERRORS });

const raw_content = metadata.raw_content;
const front_matter = getFrontMatterFromMetdata(metadata);

return put(
documentAPIUrl('posts', directory, filename),
Expand All @@ -128,18 +141,7 @@ export const publishDraft = (directory, filename) => (dispatch, getState) => {
);
};

const validateDraft = metadata =>
validator(
metadata,
{ path: 'required|filename' },
{
'path.required': getTitleRequiredMessage(),
'path.filename': getFilenameNotValidMessage(),
}
);

const preparePayload = obj => JSON.stringify(trimObject(obj));

// Reducer
export default function drafts(
state = {
drafts: [],
Expand Down
Loading