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

GitLab backend built with cursor API #1343

Merged
merged 45 commits into from
Jun 12, 2018

Conversation

Benaiah
Copy link
Contributor

@Benaiah Benaiah commented May 7, 2018

This PR contains the commits from #517, rebased onto the cursor API. This will now be the primary development PR for GitLab.

The best summary of the changes is the commit log, but a quick list of the most major aspects are here. (Once this PR has been created, I'll be doing much more detailed code review comments to request input on specific questions):

  • A new cursor API (WIP - Cursor API #1301) has been added, which allows backends to advertise support of an arbitrary set of pagination actions. It is entirely backwards-compatible, and existing backends require zero changes to maintain their current behavior. The test backend has been modified to use cursor-based pagination.
  • Integrations have been moved out of action creators and into backend.js (except for media library integrations).
  • The GitLab backend has been refactored using unsentRequest.js, and now uses cursor-based pagination.
  • An internal backend.js method has been added to retrieve all entries of a collection regardless of its pagination, for use in local search and querying.

Todo before merge:

  • Merge @tech4him1's implicit auth (WIP: Implicit OAuth for GitLab #1314) into this branch.
  • Revert changes to example/config.yml, remove remaining debug code
  • Re-enable infinite scroll when the old pagination API (based on incrementing indexes) is in use.1
  • Refactor the cursor API to be immutable.
  • Finish ensuring integration changes work correctly with Smashing's setup.2
  • Create tests for cursors and unsentRequest.1
  • Update docs (Add GitLab backend to docs #1413)

Todo after merge (some of these are not GitLab-specific, but general backend work):

  • Decouple API pagination from UI pagination, so the first page of a collection isn't the shortest one (this would also enable UI pagination of backends that don't actually paginate the API results).
  • Support editorial workflow.
  • Support pagination and cursors in the media library.
  • Move media library integrations into backend.js.
  • Finish git cache to reduce number of requests and cache cursors, entries, and completly-known trees (e.g., trees returned from listAllEntries). Currently I have a smart git cache working, but clunky - I'm doing some work to make it asynchronous and support partial knowledge of objects (for instance, truncated trees).

1 This could also be done by making integration pagination use cursors, but that may take longer than the simple fix.
2 These steps may end up being done after merge, depending on how we decide to release this.

@verythorough
Copy link
Contributor

verythorough commented May 7, 2018

Deploy preview for netlify-cms-www ready!

Built with commit d86c610

https://deploy-preview-1343--netlify-cms-www.netlify.com

@@ -0,0 +1,52 @@
import isArray from "lodash/isArray";
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Should this file be moved/renamed? Cursor is not actually a class, just a definition and validation function for a specified set of properties on any object.

Copy link
Contributor

Choose a reason for hiding this comment

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

Any reason not to provide a Cursor factory?

// Since pagination can be used for a variety of views (collections
// and searches are the most common examples), we namespace cursors by
// their type before storing them in the state.
export const collectionEntriesCursorKey = collectionName => `collectionEntries.${ collectionName }`;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This allows us to use pagination only for collections at the moment, while still using a Redux structure that will support other uses of cursors.

@@ -38,7 +38,7 @@ const entries = (state = defaultState, action) => {
map.set('isFetching', false);
map.set('page', page);
map.set('term', searchTerm);
map.set('entryIds', page === 0 ? entryIds : map.get('entryIds', List()).concat(entryIds));
map.set('entryIds', (!page || page === 0) ? entryIds : map.get('entryIds', List()).concat(entryIds));
Copy link
Contributor Author

Choose a reason for hiding this comment

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

When we're using cursor-based pagination, we do not want the entryIds to be appended to, since we only model knowledge of one set of entries and one cursor, which we retrieve from the backend.

@@ -18,3 +18,5 @@ export const resolvePromiseProperties = (obj) => {
// resolved values
Object.assign({}, obj, zipObject(promiseKeys, resolvedPromises)));
};

export const then = func => p => ((p && p.then) ? p.then(func) : func(p));
Copy link
Contributor Author

@Benaiah Benaiah May 7, 2018

Choose a reason for hiding this comment

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

This is a very simple function that lets you do something like the following (flow being from lodash):

const fn = flow([
  then(x => x + 2), // this will work whether or not x is a Promise
  then(x => x * 2),
])
fn(2) // -> 8
fn(Promise.resolve(2)) // -> Promise resolving to 8

One notable question: should this return synchronously if it doesn't receive a Promise (as it does currently), or should it be rewritten as:

export const then = func => p => Promise.resolve(p).then(func)
const fn = flow([
  then(x => x + 2), // this will work whether or not x is a Promise
  then(x => x * 2),
])
fn(2) // -> Promise resolving to 8
fn(Promise.resolve(2)) // -> Promise resolving to 8

The advantage of this would be that then would always return a Promise, regardless of what was passed in, which seems more in line with the name and analogy to the .then method it's wrapping.

// returns all the collected entries. Used to retrieve all entries
// for local searches and queries.
async listAllEntries(collection) {
if (collection.get("folder") && this.implementation.allEntriesByFolder) {
Copy link
Contributor Author

@Benaiah Benaiah May 7, 2018

Choose a reason for hiding this comment

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

I'm not very happy with how this check turned out, but it's basically necessary due to the use of two methods for listing collection (entriesByFiles and entriesByFolder). Refactoring the backend API should make this much neater by unifying the two.

Note that test-repo does not have a special implementation of allEntriesByFolder - it simply returns a cursor, and listing all entries can be done using just the cursor (as long as it supports the "next" action). GitLab has a particular implementation of this for performance reasons (it raises the files-per-page to GitLab's maximum, and doesn't list the entries in reverse).

@@ -1,10 +1,6 @@
import fuzzy from 'fuzzy';
import { currentBackend } from 'Backends/backend';
import { getIntegrationProvider } from 'Integrations';
import { selectIntegration, selectEntries } from 'Reducers';
import { selectInferedField } from 'Reducers/collections';
import { WAIT_UNTIL_ACTION } from 'Redux/middleware/waitUntilAction';
Copy link
Contributor Author

Choose a reason for hiding this comment

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

TODO: remove this import, as well as fuzzy, which are not used in the file.

@Benaiah Benaiah mentioned this pull request May 7, 2018
10 tasks
@Benaiah Benaiah force-pushed the gitlab-benaiah-rebased-onto-cursor-api branch 2 times, most recently from be2aeed to 61fef08 Compare May 8, 2018 05:34
@verythorough
Copy link
Contributor

verythorough commented May 8, 2018

Deploy preview for cms-demo ready!

Built with commit d86c610

https://deploy-preview-1343--cms-demo.netlify.com

@Benaiah Benaiah force-pushed the gitlab-benaiah-rebased-onto-cursor-api branch 2 times, most recently from cb0c93e to d7bedc9 Compare May 8, 2018 22:31
@Benaiah
Copy link
Contributor Author

Benaiah commented May 9, 2018

Update: implicit grant authentication support (#1314) has been added to this branch.

@talves talves self-requested a review May 10, 2018 20:46
@talves
Copy link
Collaborator

talves commented May 10, 2018

Built current changes and tested local, production. Media, post update, creation and delete working.

@Benaiah let me know if there is something specific we need to test. Great work!

@Benaiah Benaiah force-pushed the gitlab-benaiah-rebased-onto-cursor-api branch from ac0b0da to a35dd3d Compare May 15, 2018 22:44
Copy link
Contributor

@erquhart erquhart left a comment

Choose a reason for hiding this comment

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

Awesome work @Benaiah. My comments are all around consolidation of Cursor logic so cursor interactions are discreet across the codebase, as we discussed.

// structure.
.then(response => ((!response.cursor) || validateCursor(response.cursor)
? response
: Promise.reject(invalidCursorError(response.cursor))))
Copy link
Contributor

Choose a reason for hiding this comment

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

Nitpick: multiple negatives make this conditional a bit confusing to read, consider:

(response.cursor && !validateCursor(response.cursor)) ?  Promise.reject(..) : response

If there's no cursor or if the cursor is not valid

versus

If there's a cursor and the cursor is not valid

getCursorActionFunctions = (cursor, handler) => cursor && cursor.actions
? cursor.actions.reduce((acc, action) => ({ ...acc, [action]: handler(action) }), {})
: {};

Copy link
Contributor

Choose a reason for hiding this comment

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

Cursor interaction functions like the ones added to this file should be moved to the Cursor.js library and imported.

@@ -62,11 +78,15 @@ function mapStateToProps(state, ownProps) {
const entries = selectEntries(state, collection.get('name'));
const isFetching = state.entries.getIn(['pages', collection.get('name'), 'isFetching'], false);

return { publicFolder, collection, page, entries, isFetching, viewStyle };
const rawCursor = state.cursors.get(collectionEntriesCursorKey(collection.get('name')));
const cursor = rawCursor && pick(rawCursor, ["meta", "actions"]);
Copy link
Contributor

Choose a reason for hiding this comment

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

This logic could probably be moved to a selector in the Cursor reducer.

@Benaiah Benaiah force-pushed the gitlab-benaiah-rebased-onto-cursor-api branch 2 times, most recently from be8e092 to c125416 Compare May 18, 2018 21:29
@Benaiah Benaiah mentioned this pull request May 22, 2018
3 tasks
@Benaiah Benaiah force-pushed the gitlab-benaiah-rebased-onto-cursor-api branch from c125416 to cce0e3c Compare May 22, 2018 17:19
@verythorough verythorough mentioned this pull request May 24, 2018
@jaredmorgs
Copy link

Very excited to see this PR move forward to merged, so the BitBucket work can flow on from there. When Netlify supports all three major VCS for the CMS side, it will be a superb day for all.

Keep up the great work!

@Benaiah Benaiah force-pushed the gitlab-benaiah-rebased-onto-cursor-api branch 2 times, most recently from ed05f5f to f7fb1dc Compare May 25, 2018 20:58
@Benaiah Benaiah changed the title WIP: GitLab backend built with cursor API GitLab backend built with cursor API May 25, 2018
@Benaiah
Copy link
Contributor Author

Benaiah commented May 25, 2018

This PR is now ready for review! Notable changes for those who have already looked at the code prior:

  • The old style of pagination (which simply uses integer page numbers) is turned into cursors at the root of the entry list (EntriesCollection or EntriesSearch), so other UI elements deal only with cursors.
  • Cursors have been entirely rewritten, and now use a fluent (but immutable) class-based API.

@Benaiah Benaiah force-pushed the gitlab-benaiah-rebased-onto-cursor-api branch from 7e05ec3 to 40438e8 Compare June 6, 2018 23:18
@Benaiah
Copy link
Contributor Author

Benaiah commented Jun 6, 2018

This PR has been updated with preliminary git-gateway support for GitLab, pending netlify/git-gateway#17.

Copy link
Contributor

@verythorough verythorough left a comment

Choose a reason for hiding this comment

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

I added a checkbox to the task list to add docs before this gets merged. I'm happy to make the necessary updates and PR them against this branch, but I haven't had a chance to test the GitLab backend myself, so I need to figure out if there's anything different in implementation. (@erquhart has offered to look into this.)

I'm also checking into doc updtes for git-gateway, both at the repo level and in Netlify's docs.

if (authType === "implicit") {
this.auth = new ImplicitAuthenticator({
auth_url: this.props.config.getIn(['backend', 'auth_url'], 'https://gitlab.com/oauth/authorize'),
appid: this.props.config.getIn(['backend', 'appid']),

This comment was marked as resolved.

@Benaiah
Copy link
Contributor Author

Benaiah commented Jun 7, 2018

appid has been changed to app_id, and the fixes from #1093 have been copied over to the GitLab implementation (thanks for the catch @tech4him1).

throw new Error("The GitLab backend does not support the Editorial Workflow.")
}

if (!options.proxied && config.getIn(["backend", "repo"]) == null) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Shouldn't this be this.options.proxied?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point - options should also have a default value of {} (or L17 should do a null check before using the spread operator).

@verythorough
Copy link
Contributor

Docs PR, against this branch: #1413

@Benaiah Benaiah mentioned this pull request Jun 10, 2018
this.auth_url = config.auth_url;
const baseURL = trimEnd(config.base_url, '/');
const authEndpoint = trim(config.auth_endpoint, '/');
this.auth_url = `${ config.base_url }/${ config.auth_endpoint }`;
Copy link
Contributor

Choose a reason for hiding this comment

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

Typo here -- we want to use the trimmed values, not the ones from the config.

@Benaiah Benaiah force-pushed the gitlab-benaiah-rebased-onto-cursor-api branch from 844912f to d86c610 Compare June 12, 2018 00:43
@erquhart erquhart merged commit b65f68e into master Jun 12, 2018
erquhart pushed a commit to erquhart/netlify-cms that referenced this pull request Jun 12, 2018
@mryanb
Copy link

mryanb commented Jun 12, 2018

Through some testing today, we did run into a scenario with a protected branch. The user made a change and published, it stated that the publish was successful but nothing happened. Upon further review it was discovered the branch was protected and the user didn't have permission to push. This error should probably be surfaced to the user.

@erquhart
Copy link
Contributor

@mryanb GitLab support is merged and released now, can you open a new issue for this bug? Want to make sure it gets addressed.

Sent with GitHawk

@tech4him1 tech4him1 deleted the gitlab-benaiah-rebased-onto-cursor-api branch July 24, 2018 14:00
tech4him1 added a commit to tech4him1/netlify-cms that referenced this pull request Aug 1, 2018
tech4him1 added a commit to tech4him1/netlify-cms that referenced this pull request Aug 1, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

10 participants