pkp / pkp-lib

The library used by PKP's applications OJS, OMP and OPS, open source software for scholarly publishing.
https://pkp.sfu.ca
GNU General Public License v3.0
306 stars 445 forks source link

Routing events between handlers in the JS framework #2163

Closed NateWr closed 7 years ago

NateWr commented 7 years ago

This issue is a placeholder to track some work/experimentation to improve the way that our JS handlers can communicate between themselves. The suggested approach is to have a kind of global event router which is based on the observer pattern common in several JS frameworks.

I'd like to use this to document some of the lessons learned about how events in the JS Handlers (eg - UI "widgets) work.

Overview

As a general rule, events that are executed on a Handler do not bubble up like normal JS events. They're captured in an effort to ensure that Handlers remain encapsulated.

However, in some cases we have needed Handlers to talk to each other. This is done by white-listing certain events to be "published", using the publishEvent, trigger and triggerPublicEvent_ methods. These published events are triggered on the Handler's parent element in the DOM, from which they can bubble up.

This event is captured by a parent DOM element which has a handler attached, typically a complex Handler like a grid or the SiteHandler.

Because modals exist outside the normal DOM hierarchy, bubbling doesn't really get the job done. To accomodate this, there's a eventBridge option which can be passed to Handlers. This is used by modals to redirect events to the LinkAction which opened the modal.

The notifyUser event also gets it's own routing. Several handlers deliberately bubble the event up to the SiteHandler, which then makes a request to the server to decide what notifications to display.

So we have two ways for Handlers which are separate in the DOM (ie - not nested) to communicate with each other: an event that bubbles to SiteHandler and is then re-routed by SiteHandler, or an event bridge that reaches out to another element with a jQuery selector.

The maintenance problem

This architecture has worked to keep the Handlers highly re-usable. But it means that in cases where we do need Handlers to interact, including in cases where bubbling may not be sufficient, we have written a lot of code to reach out from one Handler to another.

The result has been that the connections between coupled Handlers tend to be ad-hoc, written into whatever method of the Handler happens to be doing that job right then. As a result, it's hard to have a clear sense of the dependencies between Handlers where they do exist, and to guard against breaking these when refactoring.

The (first) proposed solution

I looked into addressing this with the observer pattern, something I was familiar with from the Backbone.js framework. It would also give us an opportunity to start piecemeal refactoring towards a more common approach to JS-heavy applications.

The blocking problem with implementing the observer pattern

The observer pattern can’t be implemented because we lack a central registry of handlers. A handler which updates its content will remove large chunks of the DOM without tracking which handlers were caught up in the destruction. We will have references to listeners that are no longer present in the DOM (the lapsed listener problem).

The usual way to resolve this is with a central registry of handlers which deregister their events when removed (the dispose pattern). But because a handler doesn’t know what handlers are caught up in it’s refresh operations, it can’t trigger deregistration on embedded handlers (eg - a LinkActionHandler that's part of a GridHandler).

A new proposal

Without a significant refactor, we can’t have truly decoupled handlers. Instead, I’d suggest using a modified observer pattern in which the relationships between events and listeners are stored explicitly in the observer’s code. So handlers would not subscribe to events on the observer. Instead, the observer would have explicit instructions for passing specific events to specific handlers. The typical observer pattern looks like this:

Sender.init() {
    Observer.trigger(‘changeEvent’);
}
Listener.init() {
    Observer.listenTo(‘changeEvent’, Listener.respondToChangeEvent);
}

The Observer can be completely ignorant of the events that are passing through it. The modified observer pattern I’m proposing would nest lookup logic in the Observer.

Sender.init() {
    Observer.trigger(‘changeEvent’, eventData);
}
Listener.init() {
    this.bind(‘changeEvent’, Listener.respondToChangeEvent);
}
Observer.trigger(event, eventData) {
    if (event === ‘changeEvent’) {
        $(‘.listener’).trigger(‘changeEvent’, eventData);
    }
}

The Observer would not store references to handlers. Instead, it would look up any specific handlers by their associated DOM elements, performing a fresh lookup each time an event was triggered. This would provide decoupling of a sort, though the coupling would still have to be specified in a central Observer. It’s only marginally better than the current practice of triggering an event from one handler on another handler’s DOM element directly.

But is it really an improvement?

Relying on DOM lookups is not something I feel good about, to be honest. All this pattern really does is restructure the hacky DOM-based lookups away from the Handler into a central location (example). I think it will help (a little) with the maintenance issue by exposing these relationships more clearly. But the same could be done in theory with the existing eventBridge technique.

I don't see a way around these DOM lookup approaches without a pretty significant refactor of our Handlers. I've been wracking my brain but I haven't come up with anything.

One very simple refactor that I have made which I think is genuinely useful is that, on the PHP side, the JSONMessage object now supports multiple events. So you can register more than one event to return in a JSON response. Example: when publishing an issue you can return the dataChanged event that the grid is programmed to respond to, as well as a issuePublished event that can be broadcast separately.

The global event router, combined with multiple events in a JSONMessage, could be a good pattern to adopt more broadly (triggering notifications, tasks, etc from the PHP side). But I'm not sure it gets us very far down the road I originally had in mind of an event-driven UI with stronger decoupling of Handlers.

Examples

pkp-lib: https://github.com/NateWr/pkp-lib/commits/i2163_event_router ojs: https://github.com/NateWr/ojs/tree/i2163_event_router

NateWr commented 7 years ago

Adding another PR to this to update the help guide for the new list panels: https://github.com/pkp/ojs-user-guide/pull/13

asmecher commented 7 years ago

@NateWr, here are some JS linting/build fixes to pull into your branches:

https://github.com/asmecher/pkp-lib/commit/48c86e48c275755283bac6d1d76b9e7aa9fdebaa https://github.com/asmecher/ojs/commit/8e90e9456e409da0aa12e47f0c8a9b63f2ca88f6

These will get it past the current build stage where it's failing, to a new and exciting stage where the failures are different. Does this help? I didn't want to put too much time into this in case you were struggling with something different.

(Those commits are on a throwaway branch, but they'll be around for at least a few days.)

NateWr commented 7 years ago

@asmecher, you're actually on an old abandoned branch. Sorry, it's my fault for leaving the branch around and not cleaning it up properly.

All the latest work is in i2163_js_with_api. I've got all those linting issues you identified cleaned up. I only have a few remaining which I can't work out:

  1. Some undefined globals: https://travis-ci.org/pkp/ojs/jobs/243265753#L731-L739

  2. Some type notation issues: https://travis-ci.org/pkp/ojs/jobs/243265753#L839-L849

(To avoid further confusion, there's also a branch called i2163_tests_prep. Ignore that one altogether, it's just me doing some stuff until the FCGI testing changes gets merged.)

asmecher commented 7 years ago

Try this, @NateWr. It's not beautiful but it can be shown the door as soon as we do away with our current closure compiler/jslint toolset, which won't be a moment too soon. https://github.com/asmecher/pkp-lib/commit/b4f7c7e5662e7ed4a0593546de3c0727537d2319

(edit: does it show that I've been wrestling with Crockford on a Friday evening after a long week?)

NateWr commented 7 years ago

:tada: That got the JS tests passing!

asmecher commented 7 years ago

Woo! There is no emoticon that adequately expresses both festiveness and grave injury.

NateWr commented 7 years ago

Tests have passed for OJS!

https://github.com/pkp/ojs/pull/1337 https://github.com/pkp/pkp-lib/pull/2399

@asmecher, these tests include the commit you recommended we cherry-pick in to fix the FCGI issues, and I've also disabled the MySQL tests in travis. Can you do code review and merge if/when you see fit?

I'll work on getting the OMP tests going now.

asmecher commented 7 years ago

Huge thanks, @NateWr and @kaschioudi. Still some things to work on -- in particular removing OJS-specific stuff from the pkp-lib part, and some yak-shaving. @NateWr, before you leave, could you spend a few minutes with @kaschioudi to hash over the remaining work so that this doesn't stall more than necessary while you're gone?

NateWr commented 7 years ago

I've already got family in town so won't be able to dip into code much. Next week I may not have internet access at all.

I'll try to comment on a couple of the big ones right now. Otherwise, tag me explicitly if you need input on something to move forward.

NateWr commented 7 years ago

@asmecher, @kaschioudi Tests are passing for OJS and OMP. Here are the PRs for final review/merge:

https://github.com/pkp/pkp-lib/pull/2399 https://github.com/pkp/ojs/pull/1337 https://github.com/pkp/omp/pull/412

asmecher commented 7 years ago

All right, it's merged! @kaschioudi, there are some warnings I'm seeing:

PHP Notice:  Undefined index: collation in /home/asmecher/git/ojs/lib/pkp/lib/vendor/illuminate/database/Illuminate/Database/Connectors/MySqlConnector.php on line 22
[26-Jul-2017 22:31:22 UTC] PHP Stack trace:
[26-Jul-2017 22:31:22 UTC] PHP   1. {main}() /home/asmecher/git/ojs/index.php:0
[26-Jul-2017 22:31:22 UTC] PHP   2. PKPApplication->execute() /home/asmecher/git/ojs/index.php:68
[26-Jul-2017 22:31:22 UTC] PHP   3. Dispatcher->dispatch() /home/asmecher/git/ojs/lib/pkp/classes/core/PKPApplication.inc.php:239
[26-Jul-2017 22:31:22 UTC] PHP   4. APIRouter->route() /home/asmecher/git/ojs/lib/pkp/classes/core/Dispatcher.inc.php:134
[26-Jul-2017 22:31:22 UTC] PHP   5. Slim\App->run() /home/asmecher/git/ojs/lib/pkp/classes/core/APIRouter.inc.php:110
[26-Jul-2017 22:31:22 UTC] PHP   6. Slim\App->process() /home/asmecher/git/ojs/lib/pkp/lib/vendor/slim/slim/Slim/App.php:295
[26-Jul-2017 22:31:22 UTC] PHP   7. Slim\App->callMiddlewareStack() /home/asmecher/git/ojs/lib/pkp/lib/vendor/slim/slim/Slim/App.php:370
[26-Jul-2017 22:31:22 UTC] PHP   8. Slim\App->Slim\{closure}() /home/asmecher/git/ojs/lib/pkp/lib/vendor/slim/slim/Slim/MiddlewareAwareTrait.php:122
[26-Jul-2017 22:31:22 UTC] PHP   9. call_user_func:{/home/asmecher/git/ojs/lib/pkp/lib/vendor/slim/slim/Slim/MiddlewareAwareTrait.php:73}() /home/asmecher/git/ojs/lib/pkp/lib/vendor/slim/slim/Slim/MiddlewareAwareTrait.php:73
[26-Jul-2017 22:31:22 UTC] PHP  10. Slim\DeferredCallable->__invoke() /home/asmecher/git/ojs/lib/pkp/lib/vendor/slim/slim/Slim/MiddlewareAwareTrait.php:73
[26-Jul-2017 22:31:22 UTC] PHP  11. call_user_func_array:{/home/asmecher/git/ojs/lib/pkp/lib/vendor/slim/slim/Slim/DeferredCallable.php:43}() /home/asmecher/git/ojs/lib/pkp/lib/vendor/slim/slim/Slim/DeferredCallable.php:43
[26-Jul-2017 22:31:22 UTC] PHP  12. APIHandler->{closure:/home/asmecher/git/ojs/lib/pkp/classes/handler/APIHandler.inc.php:68-87}() /home/asmecher/git/ojs/lib/pkp/lib/vendor/slim/slim/Slim/DeferredCallable.php:43
[26-Jul-2017 22:31:22 UTC] PHP  13. Slim\App->Slim\{closure}() /home/asmecher/git/ojs/lib/pkp/classes/handler/APIHandler.inc.php:86
[26-Jul-2017 22:31:22 UTC] PHP  14. call_user_func:{/home/asmecher/git/ojs/lib/pkp/lib/vendor/slim/slim/Slim/MiddlewareAwareTrait.php:73}() /home/asmecher/git/ojs/lib/pkp/lib/vendor/slim/slim/Slim/MiddlewareAwareTrait.php:73
[26-Jul-2017 22:31:22 UTC] PHP  15. Slim\DeferredCallable->__invoke() /home/asmecher/git/ojs/lib/pkp/lib/vendor/slim/slim/Slim/MiddlewareAwareTrait.php:73
[26-Jul-2017 22:31:22 UTC] PHP  16. call_user_func_array:{/home/asmecher/git/ojs/lib/pkp/lib/vendor/slim/slim/Slim/DeferredCallable.php:43}() /home/asmecher/git/ojs/lib/pkp/lib/vendor/slim/slim/Slim/DeferredCallable.php:43
[26-Jul-2017 22:31:22 UTC] PHP  17. APIHandler->{closure:/home/asmecher/git/ojs/lib/pkp/classes/handler/APIHandler.inc.php:51-65}() /home/asmecher/git/ojs/lib/pkp/lib/vendor/slim/slim/Slim/DeferredCallable.php:43
[26-Jul-2017 22:31:22 UTC] PHP  18. Slim\App->Slim\{closure}() /home/asmecher/git/ojs/lib/pkp/classes/handler/APIHandler.inc.php:64
[26-Jul-2017 22:31:22 UTC] PHP  19. call_user_func:{/home/asmecher/git/ojs/lib/pkp/lib/vendor/slim/slim/Slim/MiddlewareAwareTrait.php:73}() /home/asmecher/git/ojs/lib/pkp/lib/vendor/slim/slim/Slim/MiddlewareAwareTrait.php:73
[26-Jul-2017 22:31:22 UTC] PHP  20. Slim\DeferredCallable->__invoke() /home/asmecher/git/ojs/lib/pkp/lib/vendor/slim/slim/Slim/MiddlewareAwareTrait.php:73
[26-Jul-2017 22:31:22 UTC] PHP  21. call_user_func_array:{/home/asmecher/git/ojs/lib/pkp/lib/vendor/slim/slim/Slim/DeferredCallable.php:43}() /home/asmecher/git/ojs/lib/pkp/lib/vendor/slim/slim/Slim/DeferredCallable.php:43
[26-Jul-2017 22:31:22 UTC] PHP  22. ApiAuthorizationMiddleware->__invoke() /home/asmecher/git/ojs/lib/pkp/lib/vendor/slim/slim/Slim/DeferredCallable.php:43
[26-Jul-2017 22:31:22 UTC] PHP  23. Slim\App->__invoke() /home/asmecher/git/ojs/lib/pkp/classes/security/authorization/internal/ApiAuthorizationMiddleware.inc.php:74
[26-Jul-2017 22:31:22 UTC] PHP  24. Slim\Route->run() /home/asmecher/git/ojs/lib/pkp/lib/vendor/slim/slim/Slim/App.php:476
[26-Jul-2017 22:31:22 UTC] PHP  25. Slim\Route->callMiddlewareStack() /home/asmecher/git/ojs/lib/pkp/lib/vendor/slim/slim/Slim/Route.php:316
[26-Jul-2017 22:31:22 UTC] PHP  26. Slim\Route->__invoke() /home/asmecher/git/ojs/lib/pkp/lib/vendor/slim/slim/Slim/MiddlewareAwareTrait.php:122
[26-Jul-2017 22:31:22 UTC] PHP  27. Slim\Handlers\Strategies\RequestResponse->__invoke() /home/asmecher/git/ojs/lib/pkp/lib/vendor/slim/slim/Slim/Route.php:344
[26-Jul-2017 22:31:22 UTC] PHP  28. call_user_func:{/home/asmecher/git/ojs/lib/pkp/lib/vendor/slim/slim/Slim/Handlers/Strategies/RequestResponse.php:41}() /home/asmecher/git/ojs/lib/pkp/lib/vendor/slim/slim/Slim/Handlers/Strategies/RequestResponse.php:41
[26-Jul-2017 22:31:22 UTC] PHP  29. PKPBackendSubmissionsHandler->getSubmissions() /home/asmecher/git/ojs/lib/pkp/lib/vendor/slim/slim/Slim/Handlers/Strategies/RequestResponse.php:41
[26-Jul-2017 22:31:22 UTC] PHP  30. PKP\Services\PKPSubmissionService->getSubmissionList() /home/asmecher/git/ojs/lib/pkp/api/v1/_submissions/PKPBackendSubmissionsHandler.inc.php:137
[26-Jul-2017 22:31:22 UTC] PHP  31. PKP\Services\QueryBuilders\PKPSubmissionListQueryBuilder->get() /home/asmecher/git/ojs/lib/pkp/classes/services/PKPSubmissionService.inc.php:71
[26-Jul-2017 22:31:22 UTC] PHP  32. Illuminate\Database\Capsule\Manager::table() /home/asmecher/git/ojs/lib/pkp/classes/services/queryBuilders/PKPSubmissionListQueryBuilder.inc.php:134
[26-Jul-2017 22:31:22 UTC] PHP  33. Illuminate\Database\Capsule\Manager::connection() /home/asmecher/git/ojs/lib/pkp/lib/vendor/illuminate/database/Illuminate/Database/Capsule/Manager.php:110
[26-Jul-2017 22:31:22 UTC] PHP  34. Illuminate\Database\Capsule\Manager->getConnection() /home/asmecher/git/ojs/lib/pkp/lib/vendor/illuminate/database/Illuminate/Database/Capsule/Manager.php:98
[26-Jul-2017 22:31:22 UTC] PHP  35. Illuminate\Database\DatabaseManager->connection() /home/asmecher/git/ojs/lib/pkp/lib/vendor/illuminate/database/Illuminate/Database/Capsule/Manager.php:132
[26-Jul-2017 22:31:22 UTC] PHP  36. Illuminate\Database\DatabaseManager->makeConnection() /home/asmecher/git/ojs/lib/pkp/lib/vendor/illuminate/database/Illuminate/Database/DatabaseManager.php:63
[26-Jul-2017 22:31:22 UTC] PHP  37. Illuminate\Database\Connectors\ConnectionFactory->make() /home/asmecher/git/ojs/lib/pkp/lib/vendor/illuminate/database/Illuminate/Database/DatabaseManager.php:127
[26-Jul-2017 22:31:22 UTC] PHP  38. Illuminate\Database\Connectors\ConnectionFactory->createSingleConnection() /home/asmecher/git/ojs/lib/pkp/lib/vendor/illuminate/database/Illuminate/Database/Connectors/ConnectionFactory.php:47
[26-Jul-2017 22:31:22 UTC] PHP  39. Illuminate\Database\Connectors\MySqlConnector->connect() /home/asmecher/git/ojs/lib/pkp/lib/vendor/illuminate/database/Illuminate/Database/Connectors/ConnectionFactory.php:59
asmecher commented 7 years ago

Also:

[26-Jul-2017 23:00:53 UTC] PHP Notice:  Undefined index: section_title in /home/asmecher/git/ojs/classes/article/ArticleDAO.inc.php on line 81
[26-Jul-2017 23:00:53 UTC] PHP Stack trace:
[26-Jul-2017 23:00:53 UTC] PHP   1. {main}() /home/asmecher/git/ojs/index.php:0
[26-Jul-2017 23:00:53 UTC] PHP   2. PKPApplication->execute() /home/asmecher/git/ojs/index.php:68
[26-Jul-2017 23:00:53 UTC] PHP   3. Dispatcher->dispatch() /home/asmecher/git/ojs/lib/pkp/classes/core/PKPApplication.inc.php:239
[26-Jul-2017 23:00:53 UTC] PHP   4. PKPPageRouter->route() /home/asmecher/git/ojs/lib/pkp/classes/core/Dispatcher.inc.php:134
[26-Jul-2017 23:00:53 UTC] PHP   5. PKPRouter->_authorizeInitializeAndCallRequest() /home/asmecher/git/ojs/lib/pkp/classes/core/PKPPageRouter.inc.php:233
[26-Jul-2017 23:00:53 UTC] PHP   6. call_user_func:{/home/asmecher/git/ojs/lib/pkp/classes/core/PKPRouter.inc.php:372}() /home/asmecher/git/ojs/lib/pkp/classes/core/PKPRouter.inc.php:372
[26-Jul-2017 23:00:53 UTC] PHP   7. DashboardHandler->index() /home/asmecher/git/ojs/lib/pkp/classes/core/PKPRouter.inc.php:372
[26-Jul-2017 23:00:53 UTC] PHP   8. SubmissionsListHandler->getConfig() /home/asmecher/git/ojs/lib/pkp/pages/dashboard/DashboardHandler.inc.php:62
[26-Jul-2017 23:00:53 UTC] PHP   9. SubmissionsListHandler->getItems() /home/asmecher/git/ojs/lib/pkp/controllers/list/submissions/SubmissionsListHandler.inc.php:70
[26-Jul-2017 23:00:53 UTC] PHP  10. PKP\Services\PKPSubmissionService->getSubmissionList() /home/asmecher/git/ojs/lib/pkp/controllers/list/submissions/SubmissionsListHandler.inc.php:159
[26-Jul-2017 23:00:53 UTC] PHP  11. DAOResultFactory->toArray() /home/asmecher/git/ojs/lib/pkp/classes/services/PKPSubmissionService.inc.php:81
[26-Jul-2017 23:00:53 UTC] PHP  12. DAOResultFactory->next() /home/asmecher/git/ojs/lib/pkp/classes/db/DAOResultFactory.inc.php:218
[26-Jul-2017 23:00:53 UTC] PHP  13. ArticleDAO->_fromRow() /home/asmecher/git/ojs/lib/pkp/classes/db/DAOResultFactory.inc.php:104
[26-Jul-2017 23:00:53 UTC] PHP Notice:  Undefined index: section_abbrev in /home/asmecher/git/ojs/classes/article/ArticleDAO.inc.php on line 82
[26-Jul-2017 23:00:53 UTC] PHP Stack trace:
[26-Jul-2017 23:00:53 UTC] PHP   1. {main}() /home/asmecher/git/ojs/index.php:0
[26-Jul-2017 23:00:53 UTC] PHP   2. PKPApplication->execute() /home/asmecher/git/ojs/index.php:68
[26-Jul-2017 23:00:53 UTC] PHP   3. Dispatcher->dispatch() /home/asmecher/git/ojs/lib/pkp/classes/core/PKPApplication.inc.php:239
[26-Jul-2017 23:00:53 UTC] PHP   4. PKPPageRouter->route() /home/asmecher/git/ojs/lib/pkp/classes/core/Dispatcher.inc.php:134
[26-Jul-2017 23:00:53 UTC] PHP   5. PKPRouter->_authorizeInitializeAndCallRequest() /home/asmecher/git/ojs/lib/pkp/classes/core/PKPPageRouter.inc.php:233
[26-Jul-2017 23:00:53 UTC] PHP   6. call_user_func:{/home/asmecher/git/ojs/lib/pkp/classes/core/PKPRouter.inc.php:372}() /home/asmecher/git/ojs/lib/pkp/classes/core/PKPRouter.inc.php:372
[26-Jul-2017 23:00:53 UTC] PHP   7. DashboardHandler->index() /home/asmecher/git/ojs/lib/pkp/classes/core/PKPRouter.inc.php:372
[26-Jul-2017 23:00:53 UTC] PHP   8. SubmissionsListHandler->getConfig() /home/asmecher/git/ojs/lib/pkp/pages/dashboard/DashboardHandler.inc.php:62
[26-Jul-2017 23:00:53 UTC] PHP   9. SubmissionsListHandler->getItems() /home/asmecher/git/ojs/lib/pkp/controllers/list/submissions/SubmissionsListHandler.inc.php:70
[26-Jul-2017 23:00:53 UTC] PHP  10. PKP\Services\PKPSubmissionService->getSubmissionList() /home/asmecher/git/ojs/lib/pkp/controllers/list/submissions/SubmissionsListHandler.inc.php:159
[26-Jul-2017 23:00:53 UTC] PHP  11. DAOResultFactory->toArray() /home/asmecher/git/ojs/lib/pkp/classes/services/PKPSubmissionService.inc.php:81
[26-Jul-2017 23:00:53 UTC] PHP  12. DAOResultFactory->next() /home/asmecher/git/ojs/lib/pkp/classes/db/DAOResultFactory.inc.php:218
[26-Jul-2017 23:00:53 UTC] PHP  13. ArticleDAO->_fromRow() /home/asmecher/git/ojs/lib/pkp/classes/db/DAOResultFactory.inc.php:104
kaschioudi commented 7 years ago

@asmecher : the first warning is caused by the absence of collation index in config array. We could add a default collation. Would 'collation' => 'utf8_general_ci' be a good default value?

asmecher commented 7 years ago

@kaschioudi, yes, I think so.

kaschioudi commented 7 years ago

https://github.com/pkp/pkp-lib/pull/2674

NateWr commented 7 years ago

@asmecher and the second notice addressed here: https://github.com/pkp/ojs/pull/1471

NateWr commented 7 years ago

@kaschioudi can you take a look at the updated docs in this PR and merge if it looks alright?

https://github.com/pkp/ojs/pull/1472

NateWr commented 7 years ago

@kaschioudi I've pushed up the changes I've been working on the past couple days. I just cherry-picked your commit into my branch, but I've also rebased on latest master so you might want to just work off my branch as a base.

https://github.com/NateWr/pkp-lib/tree/i2163_api_prop_list https://github.com/NateWr/ojs/tree/i2163_api_prop_list https://github.com/NateWr/omp/tree/i2163_api_prop_list https://github.com/NateWr/ui-library/tree/i2163_api_prop_list

The entity property pattern looks really nice. I've brought the API in line with the documentation (at least what's promised for 3.1), making a few changes to the documentation in places and adding some of the missing stuff. Here are some of the changes to notice:

Here are my TODO notes. Maybe we can split this work up between us a bit:

(edit - I've updated with a link to the ui-library changes for OJS. Going to check on OMP now.)

(edit 2 - I've added an OMP branch and a few more commits that should get everything separate but still working.)

kaschioudi commented 7 years ago

@NateWr

Do we need PKPAuthorService and AuthorService? I'm not sure if we'll use it but don't care if we leave it in for now.

Since authorString and shortAuthorString values are pulled directly from Submission data object, I don't think there's a need for PKPAuthorService and AuthorService. We could remove them.

In /submissions, passing orderBy=title&orderDirection=ASC doesn't seem to work.

I can't reproduce this. I just tested and it seems to be working fine. Could you confirm? If no please describe your use case.

It'd be good to find a way to document the required args that we're passing into getSummaryProperties and getFullProperties. I think request is always required. And I've made it so it works without slimRequest in all cases. But I think there are a couple examples where other things are required (like the galley expects a parent).

We could document the expected fields for each service getSummaryProperties/getFullProperties function implementation phpdoc. Something like below:

/**
 * function description
 *
 * @param $args array extra arguments
 *    $args['request'] PKPRequest
 *    $args['slimRequest'] SlimRequest 
 */

What do you think?

kaschioudi commented 7 years ago

@NateWr : am I missing something or it's not longer possible to download files through the api?

kaschioudi commented 7 years ago

@asmecher @NateWr

I have looked into an approach to modify the Authorization framework so that error codes are also associated with corresponding error messages when access is denied. This way the api will be capable of returning correct error codes (400, 403 or 404) instead of always returning 404.

My initial approach was to add a helper method, say mapErrorMessageToErrorCode(), to ApiAuthorizationMiddleware so that whenever authorization fails it's possible to figure out an error code based on the error message. But when I look closer to the policies, let's say SubmissionRequiredPolicy, the error message user.authorization.invalidSubmission could be returned for multiples reasons. For instance

The other approach is to modify policies base classe by introducing a new $_errorCode property and modify all the constructors to support an additional $errorCode parameter along with $errorMessage. After more digging I realized there is no single base class which policies extend. Some policies extend AuthorizationPolicy whilst others extend PolicySet. I thought about using traits (for code reuse) but at least PHP 5.4 is required (http://php.net/manual/en/language.oop5.traits.php).

Even if we don't mind the code duplication that is required by the latter approach's implementation, we still have to browse through most effect() methods to set appropriate error codes (just like in the initial approach) when multiple rules lead to AUTHORIZATION_DENY.

(I hope these last paragraphs are clear enough. these things are not simple to explain in text)

Based on the above, I suggest we delay this task until we refresh the authorization framework.

NateWr commented 7 years ago

Based on the above, I suggest we delay this task until we refresh the authorization framework.

I agree. Thanks for the overview. That makes sense. We'll live with the error codes as they are in 3.1 and flag it as an area for improvement.

We could document the expected fields for each service getSummaryProperties/getFullProperties function implementation phpdoc. Something like below:

I like it. Have you made any commits yet or am I safe to continue pushing to the branches above?

NateWr commented 7 years ago

am I missing something or it's not longer possible to download files through the api?

We do provide the public URL for galleys. But we'll need a proper mechanism for files in the API. I just thought it'd be a post-3.1 thing.

NateWr commented 7 years ago

In /submissions, passing orderBy=title&orderDirection=ASC doesn't seem to work.

I can't reproduce this. I just tested and it seems to be working fine. Could you confirm? If no please describe your use case.

You're right. I couldn't reproduce this.

NateWr commented 7 years ago

PRs: https://github.com/pkp/pkp-lib/pull/2876 https://github.com/pkp/ui-library/pull/2 https://github.com/pkp/ojs/pull/1592 https://github.com/pkp/omp/pull/449

kaschioudi commented 7 years ago

@NateWr I added this fix. Please cherry-pick and add it to your branch. https://github.com/kaschioudi/pkp-lib/commit/92bddcb0614f480a12fb78a644b211137460568b thanks.

NateWr commented 7 years ago

Cheers! I've got it in now.

NateWr commented 7 years ago

@kaschioudi I think I've worked out the test issues. Do you have time to code review the PRs in this comment? https://github.com/pkp/pkp-lib/issues/2163#issuecomment-337226135

kaschioudi commented 7 years ago

@NateWr Everything looks fine to me :thumbsup:

NateWr commented 7 years ago

:tada: Really happy to have this merged in. Ready to start build more API stuff. :)

asmecher commented 7 years ago

@NateWr and @kaschioudi, is this one ready to be closed now that the OMP warning fix has been merged?

NateWr commented 7 years ago

:boom: