-
-
Notifications
You must be signed in to change notification settings - Fork 3.1k
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
feat: commit media with post #2851
Conversation
parentSha: sha, | ||
})); | ||
}); | ||
async updateTree(sha, files) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Used nested file paths to update the tree instead of recursion (@erquhart figured it out).
This is also the only way we got file deletion to work.
Should have some performance boost.
@@ -322,7 +323,9 @@ export default class GitHub { | |||
|
|||
async persistMedia(mediaFile, options = {}) { | |||
try { | |||
await this.api.persistFiles(null, [mediaFile], options); | |||
if (!options.draft) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Skip the update and just return the expected object structure
@@ -656,7 +630,7 @@ export default class API { | |||
} | |||
|
|||
if (pr) { | |||
return this.rebasePullRequest(pr.number, branchName, contentKey, metadata, commit); | |||
return this.rebasePullRequest(pr.number, branchName, contentKey, updatedMetadata, commit); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Had to fix this to make sure metadata.object.files
are synced
@@ -681,7 +655,7 @@ export default class API { | |||
*/ | |||
const [baseBranch, commits] = await Promise.all([ | |||
this.getBranch(), | |||
this.getPullRequestCommits(prNumber, head), | |||
this.getPullRequestCommits(prNumber), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
getPullRequestCommits
accepts only one argument
@@ -343,6 +347,29 @@ export default class GitHub { | |||
return this.api.deleteFile(path, commitMessage, options); | |||
} | |||
|
|||
async getMediaFiles(data) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Loads the draft entry media files
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice!
resolve({ | ||
slug, | ||
file: { path }, | ||
file: { path: data.metaData.objects.entry.path }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just to be consistent with unpublishedEntry
const assetProxies = await Promise.all( | ||
mediaFiles.map(({ file }) => createAssetProxy(file.name, file)), | ||
); | ||
dispatch(addAssets(assetProxies)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sync assets, draft media files and media library on entry load
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doing this all in one place is really good for visibility, love it
const assetProxies = await Promise.all( | ||
assets.map(asset => createAssetProxy(asset.value, asset.fileObj)), | ||
); | ||
dispatch(addAssets(assetProxies)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sync assets - createAssetProxy
creates an AssetProxy
class (can't used the serialized object directly since we need the class methods).
|
||
const entry = state.entryDraft.get('entry'); | ||
const useWorkflow = state.config.getIn(['publish_mode']) === EDITORIAL_WORKFLOW; | ||
const draft = entry && !entry.isEmpty() && useWorkflow; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure this is the best way to check that we are editing an entry
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would expect the entry in state.entryDraft
to always be an open entry, but maybe there are gaps - did you experience this not being the case?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm note sure what "open entry" means.
What I have experienced is that when you exit the editor the draft is discarded here: https://github.com/netlify/netlify-cms/blob/7554134f999970e8f8198f38200c47ff5880c0a4/packages/netlify-cms-core/src/reducers/entryDraft.js#L72
if (!integration) { | ||
const asset = await backend.persistMedia(state.config, assetProxy); | ||
const asset = await backend.persistMedia(state.config, assetProxy, draft); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We still need the backend to generate the asset object so we call persistMedia
dispatch(removeDraftEntryMediaFile({ id: file.id })); | ||
|
||
if (!file.draft) { | ||
await backend.deleteMedia(state.config, file.path); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't need the result so we can skip the backend operation completely
const updatedFiles = map.get('files').filter(file => file.key !== key); | ||
const updatedFiles = map | ||
.get('files') | ||
.filter(file => (key ? file.key !== key : file.id !== id)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
support remove by id
payload: { mediaFiles }, | ||
}; | ||
// add media files to library only after the library finished loading | ||
if (state.mediaLibrary.get('isLoading') === false) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can't believe I had to do this, but when loading a backup for a new unsaved entry directly from the new entry url (e.g. /collections/posts/new
) the backup is loaded before the media library is mounted (hence loaded). Once the library did finish loading the entry media files were overwritten in the media library reducer.
Another solution was not to overwrite at all on media library load success, but it does makes sense to wait for the library to finish initial load before using it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah this is fine for now at least. It's a code smell, but we have stuff like isLoading
and isFetching
for a reason.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When you start syncing actions order inside componentDidUpdate
based on props you know something is not right.
A better way to wait for actions is to use redux-saga
https://redux-saga.js.org/docs/advanced/FutureActions.html as it uses a pull approach, but it might be an overkill for now.
let updatedFiles = differenceBy(state.get('files'), mediaFiles, 'path'); | ||
mediaFiles.forEach(file => { | ||
const fileWithKey = { ...file, key: uuid() }; | ||
updatedFiles = [fileWithKey, ...updatedFiles]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Won't something like so:
[...mediaFiles.map(file => ({ ...file, key: uuid() })), ...updatedFiles]
be sufficient here ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch, I'll fix it.
}); | ||
jest.mock('ValueObjects/AssetProxy'); | ||
jest.mock('netlify-cms-lib-util'); | ||
jest.mock('ValueObjects/AssetProxy'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Duplicate?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I'll remove it
04f231e
to
62dc650
Compare
This does break expectations, so from a semver standpoint we should wait for 3.0 to make it default behavior. That said, the current default is just not great at all, and I doubt anyone will have an issue with this change. We could just introduce it as default and have a backup plan to move it under a config flag if folks cry foul. Sent with GitHawk |
19a63dc
to
af2cecd
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Blown away by all of these tests, fantastic work Erez!! This is a big PR but I'd be comfortable merging and releasing it into the beta channel. Regarding default behavior, I'm inclined to just do it. If people are thrown off we can put it behind a flag, but I don't think that will happen.
@@ -1,6 +1,7 @@ | |||
import { Base64 } from 'js-base64'; | |||
import semaphore from 'semaphore'; | |||
import { find, flow, get, hasIn, initial, last, partial, result, uniq } from 'lodash'; | |||
import { find, flow, get, hasIn, initial, last, partial, result, differenceBy } from 'lodash'; | |||
import trimStart from 'lodash/trimStart'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we include this in the line above and let treeshaking resolve the rest? Might need the lodash webpack plugin for that to actually happen.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right. I probably copy pasted it from somewhere and didn't notice the existing import. Going forward we can use https://github.com/wix/eslint-plugin-lodash/blob/master/docs/rules/import-scope.md to enforce a way to import lodash methods (probably Destructured Members
) and use a Webpack/babel plugin to get tree shaking like you said.
Update: there is lodash-es
, but we'll also need to check what happens if one of our dependencies imports lodash
the "wrong way".
sha, | ||
})); | ||
return this.editorialWorkflowGit(files, entry, mediaFilesList, options); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Love all of this code removal 🔥🔥🔥
@@ -343,6 +347,29 @@ export default class GitHub { | |||
return this.api.deleteFile(path, commitMessage, options); | |||
} | |||
|
|||
async getMediaFiles(data) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice!
const assetProxies = await Promise.all( | ||
mediaFiles.map(({ file }) => createAssetProxy(file.name, file)), | ||
); | ||
dispatch(addAssets(assetProxies)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doing this all in one place is really good for visibility, love it
|
||
const entry = state.entryDraft.get('entry'); | ||
const useWorkflow = state.config.getIn(['publish_mode']) === EDITORIAL_WORKFLOW; | ||
const draft = entry && !entry.isEmpty() && useWorkflow; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would expect the entry in state.entryDraft
to always be an open entry, but maybe there are gaps - did you experience this not being the case?
payload: { mediaFiles }, | ||
}; | ||
// add media files to library only after the library finished loading | ||
if (state.mediaLibrary.get('isLoading') === false) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah this is fine for now at least. It's a code smell, but we have stuff like isLoading
and isFetching
for a reason.
7554134
to
4dfd87f
Compare
* feat: commit media with post - initial commit * feat: add draft media indication * feat: sync UI media files with GitHub on entry load * feat: bug fixes * feat: delete media files from github when removed from library * test: add GitHub backend tests * test: add unit tests * fix: meta data object files are not updated * feat: used nested paths when update a tree instead of recursion * feat(test-backend): update test backend to persist media file with entry * test(e2e): re-record fixtures data * chore: code cleanup * chore: code cleanup * fix: wait for library to load before adding entry media files * chore: code cleanup * fix: don't add media files on entry when not a draft * fix: sync media library after draft entry was published * feat: update media library card draft style, add tests * test: add Editor unit tests * chore: test code cleanup * fix: publishing an entry from workflow tab throws an error * fix: duplicate media files when using test backend * refactor: fix lodash import * chore: update translations and yarn file after rebase * test(cypress): update recorded data * fix(test-backend): fix mapping of media files on publish
* feat: commit media with post - initial commit * feat: add draft media indication * feat: sync UI media files with GitHub on entry load * feat: bug fixes * feat: delete media files from github when removed from library * test: add GitHub backend tests * test: add unit tests * fix: meta data object files are not updated * feat: used nested paths when update a tree instead of recursion * feat(test-backend): update test backend to persist media file with entry * test(e2e): re-record fixtures data * chore: code cleanup * chore: code cleanup * fix: wait for library to load before adding entry media files * chore: code cleanup * fix: don't add media files on entry when not a draft * fix: sync media library after draft entry was published * feat: update media library card draft style, add tests * test: add Editor unit tests * chore: test code cleanup * fix: publishing an entry from workflow tab throws an error * fix: duplicate media files when using test backend * refactor: fix lodash import * chore: update translations and yarn file after rebase * test(cypress): update recorded data * fix(test-backend): fix mapping of media files on publish
* feat: commit media with post - initial commit * feat: add draft media indication * feat: sync UI media files with GitHub on entry load * feat: bug fixes * feat: delete media files from github when removed from library * test: add GitHub backend tests * test: add unit tests * fix: meta data object files are not updated * feat: used nested paths when update a tree instead of recursion * feat(test-backend): update test backend to persist media file with entry * test(e2e): re-record fixtures data * chore: code cleanup * chore: code cleanup * fix: wait for library to load before adding entry media files * chore: code cleanup * fix: don't add media files on entry when not a draft * fix: sync media library after draft entry was published * feat: update media library card draft style, add tests * test: add Editor unit tests * chore: test code cleanup * fix: publishing an entry from workflow tab throws an error * fix: duplicate media files when using test backend * refactor: fix lodash import * chore: update translations and yarn file after rebase * test(cypress): update recorded data * fix(test-backend): fix mapping of media files on publish
Fixes #1344
Opening a new one since I can't re-open #2823 due to a forced commit (rebased branch).
This is a new (breaking) behaviour for the editorial workflow (not sure if we should make it the default).
When creating a new draft entry while in editorial workflow all media files will be stored on the client side until the first save (instead of being committed directly to the default branch). On first save the media files and entry will be pushed together as a single commit.
Any further changes will be synched between the browser and the corresponding branch.
Main changes:
Items left:
Known issue:
Stuff I found and fixed during my tests: