diff --git a/lib/Application.js b/lib/Application.js index cb5999d..8ce23cb 100644 --- a/lib/Application.js +++ b/lib/Application.js @@ -101,7 +101,8 @@ class Application { this._menu = this.buildMenuFromEntities(); } return this._menu - }; + } + this._menu = menu; return this; } diff --git a/lib/Queries/ReadQueries.js b/lib/Queries/ReadQueries.js index 63fc996..9114656 100644 --- a/lib/Queries/ReadQueries.js +++ b/lib/Queries/ReadQueries.js @@ -34,7 +34,10 @@ class ReadQueries extends Queries { page = page || 1; let url = view.getUrl(); - return this.getRawValues(view.entity, view.name(), view.type, page, view.perPage(), filters, view.filters(), sortField || view.getSortFieldName(), sortDir || view.sortDir(), url) + sortField = sortField || view.getSortFieldName(); + sortDir = sortDir || view.sortDir(); + + return this.getRawValues(view.entity, view.name(), view.type, page, view.perPage(), filters, view.filters(), sortField, sortDir, url) .then((values) => { return { data: values.data, @@ -63,16 +66,19 @@ class ReadQueries extends Queries { getRawValues(entity, viewName, viewType, page, perPage, filterValues, filterFields, sortField, sortDir, url) { let params = {}; + // Compute pagination if (page !== -1) { params._page = (typeof (page) === 'undefined') ? 1 : parseInt(page, 10); params._perPage = perPage; } + // Compute sorting if (sortField && sortField.split('.')[0] === viewName) { params._sortField = sortField.split('.')[1]; params._sortDir = sortDir; } + // Compute filtering if (filterValues && Object.keys(filterValues).length !== 0) { params._filters = {}; let filterName; @@ -96,75 +102,148 @@ class ReadQueries extends Queries { /** * Returns all References for an entity with associated values [{targetEntity.identifier: targetLabel}, ...] + * by calling the API for each entries * - * @param {Object} references A hash of Reference and ReferenceMany objects + * @param {ReferenceField} references A hash of Reference and ReferenceMany objects * @param {Array} rawValues * - * @returns {promise} + * @returns {Promise} */ - getReferencedData(references, rawValues) { - let getRawValues = this.getRawValues.bind(this), - getOne = this.getOne.bind(this), - identifiers, - calls = [], - data; + getFilteredReferenceData(references, rawValues) { + if (!references || !Object.keys(references).length) { + return this._promisesResolver.empty({}); + } + + let getOne = this.getOne.bind(this), + calls = []; for (let i in references) { let reference = references[i], - targetEntity = reference.targetEntity(); - - if (!rawValues) { - calls.push(getRawValues(targetEntity, targetEntity.name() + '_ListView', 'listView', 1, reference.perPage(), reference.filters(), {}, reference.sortField(), reference.sortDir())); + targetEntity = reference.targetEntity(), + identifiers = reference.getIdentifierValues(rawValues); - continue; + for (let k in identifiers) { + calls.push(getOne(targetEntity, 'listView', identifiers[k], reference.name())); } + } - // get only for identifiers - identifiers = reference.getIdentifierValues(rawValues); + return this.fillFilteredReferencedData(calls, references, rawValues); + }; + + /** + * Returns all References for an entity with associated values [{targetEntity.identifier: targetLabel}, ...] + * by calling the API once + * + * @param {[ReferenceField]} references A hash of Reference and ReferenceMany objects + * @param {Array} rawValues + * + * @returns {Promise} + */ + getOptimizedReferencedData(references, rawValues) { + if (!references || !Object.keys(references).length) { + return this._promisesResolver.empty({}); + } + + let getRawValues = this.getRawValues.bind(this), + calls = []; + + for (let i in references) { + let reference = references[i], + targetEntity = reference.targetEntity(), + identifiers = reference.getIdentifierValues(rawValues); // Check if we should retrieve values with 1 or multiple requests - if (reference.hasSingleApiCall()) { - let singleCallFilters = reference.getSingleApiCall(identifiers); - calls.push(getRawValues(targetEntity, targetEntity.name() + '_ListView', 'listView', 1, reference.perPage(), singleCallFilters, {}, reference.sortField(), reference.sortDir())); + let singleCallFilters = reference.getSingleApiCall(identifiers); + calls.push(getRawValues(targetEntity, targetEntity.name() + '_ListView', 'listView', 1, reference.perPage(), singleCallFilters, {}, reference.sortField(), reference.sortDir())); + } - continue; - } + return this.fillOptimizedReferencedData(calls, references); + } - for (let k in identifiers) { - calls.push(getOne(targetEntity, 'listView', identifiers[k], reference.name())); - } + /** + * Returns all References for an entity with associated values [{targetEntity.identifier: targetLabel}, ...] + * without filters on an entity + * + * @param {[ReferenceField]} references A hash of Reference and ReferenceMany objects + * + * @returns {Promise} + */ + getAllReferencedData(references) { + if (!references || !Object.keys(references).length) { + return this._promisesResolver.empty({}); } - // Fill all reference entries - return this._promisesResolver.allEvenFailed(calls) + let calls = [], + getRawValues = this.getRawValues.bind(this); + + for (let i in references) { + let reference = references[i], + targetEntity = reference.targetEntity(); + + calls.push(getRawValues(targetEntity, targetEntity.name() + '_ListView', 'listView', 1, reference.perPage(), reference.filters(), {}, reference.sortField(), reference.sortDir())); + } + + return this.fillOptimizedReferencedData(calls, references); + } + + /** + * Fill all reference entries to return [{targetEntity.identifier: targetLabel}, ...] + * + * @param {[Promise]} apiCalls + * @param {[Reference]} references + * @returns {Promise} + */ + fillOptimizedReferencedData(apiCalls, references) { + return this._promisesResolver.allEvenFailed(apiCalls) .then((responses) => { if (responses.length === 0) { return {}; } let referencedData = {}, - response, i = 0; for (let j in references) { let reference = references[j], - singleCallFilters = reference.getSingleApiCall(identifiers); - - // Retrieve entries depending on 1 or many request was done - if (singleCallFilters || !rawValues) { response = responses[i++]; - if (response.status == 'error') { - // the response failed - continue; - } - - referencedData[reference.name()] = response.result.data; + // Retrieve entries depending on 1 or many request was done + if (response.status == 'error') { + // the response failed continue; } - data = []; - identifiers = reference.getIdentifierValues(rawValues); + referencedData[reference.name()] = response.result.data; + } + + return referencedData; + }); + } + + /** + * Fill all reference entries to return [{targetEntity.identifier: targetLabel}, ...] + * + * @param {[Promise]} apiCalls + * @param {[Reference]} references + * @param {[Object]} rawValues + * @returns {Promise} + */ + fillFilteredReferencedData(apiCalls, references, rawValues) { + return this._promisesResolver.allEvenFailed(apiCalls) + .then((responses) => { + if (responses.length === 0) { + return {}; + } + + let referencedData = {}, + response, + i = 0; + + for (let j in references) { + let data = [], + reference = references[j], + identifiers = reference.getIdentifierValues(rawValues); + for (let k in identifiers) { response = responses[i++]; if (response.status == 'error') { @@ -183,7 +262,7 @@ class ReadQueries extends Queries { return referencedData; }); - }; + } /** * Returns all ReferencedList for an entity for associated values [{targetEntity.identifier: [targetFields, ...]}} @@ -217,7 +296,7 @@ class ReadQueries extends Queries { for (let i in referencedLists) { let response = responses[j++]; if (response.status == 'error') { - // one of the responses failed + // If a response fail, skip it continue; } diff --git a/lib/Utils/PromisesResolver.js b/lib/Utils/PromisesResolver.js index c427e9d..6edc915 100644 --- a/lib/Utils/PromisesResolver.js +++ b/lib/Utils/PromisesResolver.js @@ -1,5 +1,11 @@ class PromisesResolver { + static empty(value) { + return new Promise((resolve) => { + resolve(value); + }); + } + static allEvenFailed(promises) { if (!Array.isArray(promises)) { throw Error('allEvenFailed can only handle an array of promises'); diff --git a/lib/View/DeleteView.js b/lib/View/DeleteView.js index 07782c8..4a3f7a2 100644 --- a/lib/View/DeleteView.js +++ b/lib/View/DeleteView.js @@ -4,6 +4,7 @@ class DeleteView extends View { constructor(name) { super(name); this._type = 'DeleteView'; + this._enabled = true; } } diff --git a/lib/View/MenuView.js b/lib/View/MenuView.js index 217a6b6..ae88e11 100644 --- a/lib/View/MenuView.js +++ b/lib/View/MenuView.js @@ -7,6 +7,10 @@ class MenuView extends View { this._icon = null; } + get enabled() { + return this._enabled || this.entity.views['ListView'].enabled; + } + icon() { if (arguments.length) { console.warn('entity.menuView() is deprecated. Please use the Menu class instead'); diff --git a/lib/View/View.js b/lib/View/View.js index b3dbff9..b5eda3c 100644 --- a/lib/View/View.js +++ b/lib/View/View.js @@ -8,7 +8,7 @@ class View { this._description = ''; this._template = null; - this._enabled = true; + this._enabled = false; this._fields = []; this._type = null; this._name = name; @@ -18,7 +18,7 @@ class View { } get enabled() { - return this._enabled; + return this._enabled || !!this._fields.length; } title(title) { @@ -47,17 +47,21 @@ class View { disable() { this._enabled = false; + + return this; } enable() { this._enabled = true; + + return this; } /** * @deprecated Use getter "enabled" instead */ isEnabled() { - return this._enabled; + return this.enabled; } /** @@ -136,6 +140,14 @@ class View { return result; } + getNonOptimizedReferences() { + return this._getReferencesByOptimizationType(false); + } + + getOptimizedReferences() { + return this._getReferencesByOptimizationType(true); + } + getReferencedLists() { let result = {}; let lists = this._fields.filter(f => f.type() === 'referenced_list'); @@ -231,6 +243,27 @@ class View { } }); } + + /** + * + * @param {Boolean} optimized + * @returns {[Reference]} + * @private + */ + _getReferencesByOptimizationType(optimized=true) { + let result = {}, + references = this.getReferences(); + + for (let i in references) { + let reference = references[i]; + + if (!!reference.getSingleApiCall() === optimized) { + result[i] = reference; + } + } + + return result; + } } export default View; diff --git a/tests/lib/ApplicationTest.js b/tests/lib/ApplicationTest.js index 0a85dcc..75158f9 100644 --- a/tests/lib/ApplicationTest.js +++ b/tests/lib/ApplicationTest.js @@ -102,10 +102,16 @@ describe('Application', function() { }); it('should return only views of type', () => { - let application = new Application(); + let application = new Application(), + post = new Entity('post'), + comment = new Entity('comment'); + + post.views["DashboardView"].enable(); + comment.views["DashboardView"].enable(); + application - .addEntity(new Entity('post')) - .addEntity(new Entity('comment')); + .addEntity(post) + .addEntity(comment); let views = application.getViewsOfType('DashboardView'); @@ -121,9 +127,13 @@ describe('Application', function() { it('should return only enabled views of type', () => { let application = new Application(); let comment = new Entity('comment'); + let post = new Entity('post'); + comment.views.DashboardView.disable(); + post.views.DashboardView.enable(); + application - .addEntity(new Entity('post')) + .addEntity(post) .addEntity(comment); let views = application.getViewsOfType('DashboardView'); @@ -135,6 +145,11 @@ describe('Application', function() { it('should return ordered views of type', function() { let application = new Application(); let [post, comment, tag] = [new Entity('post'), new Entity('comment'), new Entity('tag')]; + + post.views["DashboardView"].enable(); + comment.views["DashboardView"].enable(); + tag.views["DashboardView"].enable(); + post.views.DashboardView.order(2); comment.views.DashboardView.order(1); tag.views.DashboardView.order(3); @@ -165,10 +180,17 @@ describe('Application', function() { describe('buildMenuFromEntities', () => { it('should create a menu based on the entity list', () => { - let application = new Application(); + let application = new Application(), + comment = new Entity('comment'), + post = new Entity('post'); + + comment.views.ListView.enable(); + post.views.ListView.enable(); + application - .addEntity(new Entity('post')) - .addEntity(new Entity('comment')); + .addEntity(post) + .addEntity(comment); + let menu = application.buildMenuFromEntities(); assert.equal(2, menu.children().length); let [menu1, menu2] = menu.children(); @@ -178,9 +200,15 @@ describe('Application', function() { it('should use the menuView order when provided', () => { let application = new Application(); let [e1, e2, e3] = [new Entity('e1'), new Entity('e2'), new Entity('e3')]; + e1.menuView().order(2); e2.menuView().order(1); e3.menuView().order(3); + + e1.views.ListView.enable(); + e2.views.ListView.enable(); + e3.views.ListView.enable(); + application .addEntity(e1) .addEntity(e2) diff --git a/tests/lib/Entity/EntityTest.js b/tests/lib/Entity/EntityTest.js index 623d7e9..83f189c 100644 --- a/tests/lib/Entity/EntityTest.js +++ b/tests/lib/Entity/EntityTest.js @@ -46,10 +46,13 @@ describe('Entity', function() { it('should set read-only attribute', function() { entity.readOnly(); assert.equal(true, entity.isReadOnly); - }) ; + }); it('should disable all edition views', function() { entity.readOnly(); + entity.views.ListView.enable(); + entity.views.DashboardView.enable(); + assert.equal(true, entity.menuView().enabled); assert.equal(true, entity.dashboardView().enabled); assert.equal(true, entity.listView().enabled); diff --git a/tests/lib/Queries/ReadQueriesTest.js b/tests/lib/Queries/ReadQueriesTest.js index 6993b0a..9ee4d83 100644 --- a/tests/lib/Queries/ReadQueriesTest.js +++ b/tests/lib/Queries/ReadQueriesTest.js @@ -131,7 +131,7 @@ describe('ReadQueries', () => { { status: 'success', result: rawAuthors[1] } ])); - readQueries.getReferencedData(post.views["ListView"].getReferences(), rawPosts) + readQueries.getFilteredReferenceData(post.views["ListView"].getReferences(), rawPosts) .then((referencedData) => { assert.equal(referencedData.author.length, 2); assert.equal(referencedData.author[0].id, 'abc'); @@ -170,7 +170,7 @@ describe('ReadQueries', () => { {status: 'success', result: { data: rawAuthors }} ])); - readQueries.getReferencedData(post.views["ListView"].getReferences(), rawPosts) + readQueries.getOptimizedReferencedData(post.views["ListView"].getReferences(), rawPosts) .then((referencedData) => { assert.equal(referencedData['author'].length, 2); assert.equal(referencedData['author'][0].id, 'abc');