From 8a8e11cd252ec1301d55cf814accccccfa94e77f Mon Sep 17 00:00:00 2001 From: Justin Obara Date: Thu, 30 Oct 2014 11:57:46 -0400 Subject: [PATCH 01/29] FLUID-5542: Added requestQueue Three requestQueues have been implemented. 1) queues all requests and executes them one at a time in the order they were received 2) a debounce queue that only accepts the latests request 3) a throttled queue that only accepts new requests after a specified delay. A wrapper dataSource still needs to be implemented to make use of one or more of these queues with a supplied datasource. --- src/framework/core/js/FluidRequests.js | 170 ++++++++++++++++ .../core/html/Datasource-test.html | 32 +++ .../core/js/DatasourceTests.js | 187 ++++++++++++++++++ 3 files changed, 389 insertions(+) create mode 100644 tests/framework-tests/core/html/Datasource-test.html create mode 100644 tests/framework-tests/core/js/DatasourceTests.js diff --git a/src/framework/core/js/FluidRequests.js b/src/framework/core/js/FluidRequests.js index ca04075462..f3a9f1ac68 100644 --- a/src/framework/core/js/FluidRequests.js +++ b/src/framework/core/js/FluidRequests.js @@ -343,5 +343,175 @@ var fluid_2_0 = fluid_2_0 || {}; return fluid.NO_VALUE; }; + /** Start datasource **/ + + fluid.defaults("fluid.emptyDatasource", { + gradeNames: ["fluid.eventedComponent", "autoInit"] + // Invokers should be defined for the typical HTTP rest requests + // invokers: { + // "get": {}, + // "set": {}, // set should handle POST and PUT + // "delete": {} + // } + }); + + fluid.defaults("fluid.requestQueue", { + gradeNames: ["fluid.standardRelayComponent", "autoInit"], + events: { + queued: null, + unqueued: null + }, + model: { + isActive: false + }, + members: { + queue: [] + }, + listeners: { + "unqueued": "{that}.start", + "queued": "{that}.start" + }, + invokers: { + add: { + funcName: "fluid.requestQueue.add", + args: ["{that}", "{arguments}.0"] + }, + start: { + funcName: "fluid.requestQueue.start", + args: ["{that}"] + } + } + }); + + fluid.requestQueue.add = function (that, request) { + that.queue.push(request); + that.events.queued.fire(request); + }; + + fluid.requestQueue.start = function (that) { + if (!that.model.isActive && that.queue.length) { + var request = that.queue.shift(); + var callbackProxy = function () { + that.applier.change("isActive", false); + that.events.unqueued.fire(request); + request.callback.apply(null, arguments); + }; + + that.applier.change("isActive", true); + request.method(request.directModel, callbackProxy); + } + }; + + fluid.defaults("fluid.requestQueue.debounce", { + gradeNames: ["fluid.requestQueue", "autoInit"], + invokers: { + add: { + funcName: "fluid.requestQueue.debounce.add", + args: ["{that}", "{arguments}.0"] + } + } + }); + + fluid.requestQueue.debounce.add = function (that, request) { + that.queue[0] = request; + that.events.queued.fire(request); + }; + + fluid.defaults("fluid.requestQueue.throttle", { + gradeNames: ["fluid.requestQueue", "autoInit"], + delay: 10, + model: { + isThrottled: false + }, + invokers: { + add: { + funcName: "fluid.requestQueue.throttle.add", + args: ["{that}", "{arguments}.0"] + } + } + }); + + fluid.requestQueue.throttle.add = function (that, request) { + if (!that.model.isThrottled) { + that.applier.change("isThrottled", true); + that.queue.push(request); + that.events.queued.fire(request); + setTimeout(function () { + that.applier.change("isThrottled", false); + }, that.options.delay); + } + }; + + // fluid.defaults("fluid.queuedDataSource", { + // gradeNames: ["fluid.standardRelayComponent", "autoInit"], + // events: { + // requestQueued: null, + // requestUnqueued: null + // }, + // model: { + // isActive: false + // }, + // members: { + // queue: [] + // }, + // listeners: { + // "requestUnqueued.setIsActive": { + // changePath: "isActive", + // value: false + // }, + // "requestQueued": "{that}.start" + // }, + // modelListeners: { + // "isActive": "{that}.start" + // }, + // invokers: { + // add: { + // funcName: "fluid.queuedDataSource.add", + // args: ["{that}", "{arguments}.0"] + // }, + // start: { + // funcName: "fluid.queuedDataSource.start", + // args: ["{that}"] + // }, + // set: { + // funcName: "fluid.queuedDataSource.set", + // args: ["{that}", "{arguments}.0", "{arguments}.1"] + // }, + // get: "{wrappedDataSource}.get", + // "delete": "{wrappedDataSource}.delete" + // }, + // components: { + // wrappedDataSource: { + // // requires a dataSource that implements the standard set, get, and delete methods. + // type: "fluid.emptyDatasource" + // } + // } + // }); + + // fluid.queuedDataSource.add = function (that, args) { + // that.queue.push(args); + // that.events.requestQueued.fire(args); + // }; + // + // fluid.queuedDataSource.start = function (that) { + // if (!that.model.isActive && that.queue.length) { + // var args = that.queue[0]; + // that.applier.change("isActive", true); + // + // that.wrappedDataSource.set(args.directModel, function () { + // that.events.requestUnqueued.fire(that.queue.shift()); + // args.callback.apply(null, arguments); + // }); + // } + // }; + // + // fluid.queuedDataSource.set = function (that, directModel, callback) { + // that.add({ + // directModel: directModel, + // callback: callback + // }); + // }; + + /** End datasource **/ })(jQuery, fluid_2_0); diff --git a/tests/framework-tests/core/html/Datasource-test.html b/tests/framework-tests/core/html/Datasource-test.html new file mode 100644 index 0000000000..24207f2760 --- /dev/null +++ b/tests/framework-tests/core/html/Datasource-test.html @@ -0,0 +1,32 @@ + + + + + DataSource Tests + + + + + + + + + + + + + + + + + + + +

DataSource Test Suite

+

+
+

+
    + + + diff --git a/tests/framework-tests/core/js/DatasourceTests.js b/tests/framework-tests/core/js/DatasourceTests.js new file mode 100644 index 0000000000..7a92458e58 --- /dev/null +++ b/tests/framework-tests/core/js/DatasourceTests.js @@ -0,0 +1,187 @@ +/* +Copyright 2014 OCAD University + +Licensed under the Educational Community License (ECL), Version 2.0 or the New +BSD license. You may not use this file except in compliance with one these +Licenses. + +You may obtain a copy of the ECL 2.0 License and BSD License at +https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt +*/ + +// Declare dependencies +/* global fluid, jqUnit */ + +(function () { + "use strict"; + + fluid.registerNamespace("fluid.tests"); + + fluid.tests.asyncLoop = function (fn, delay, cycles) { + var count = 0; + cycles = Math.abs(cycles) || 1; // must have at least one cycle + + // recursive + var timeloop = function () { + count++; + fn(count); + + if (count < cycles) { + setTimeout(timeloop, delay); + } + }; + + timeloop(); + }; + + fluid.defaults("fluid.tests.requestQueue", { + gradeNames: ["fluid.requestQueue", "autoInit"], + members: { + fireRecord: { + queued: 0, + unqueued: 0, + request: 0, + isActive: { + "true": 0, + "false": 0 + } + } + }, + listeners: { + "queued": { + func: "{that}.record", + args: ["queued"] + }, + "unqueued": { + func: "{that}.record", + args: ["unqueued"] + } + }, + modelListeners: { + "isActive": { + funcName: "{that}.record", + excludeSource: ["init"], + args: ["isActive", "{change}.value"] + } + }, + invokers: { + record: { + funcName: "fluid.tests.requestQueue.record", + args: ["{that}", "{arguments}.0", "{arguments}.1"] + }, + request: { + funcName: "fluid.tests.requestQueue.request", + args: ["{that}", "{arguments}.0", "{arguments}.1"] + } + } + }); + + fluid.tests.requestQueue.record = function (that, recordName, value) { + var path = ["fireRecord", recordName]; + + if (recordName === "isActive") { + path.push(value.toString()); + } + + var count = fluid.get(that, path) + 1; + fluid.set(that, path, count); + }; + + fluid.tests.requestQueue.request = function (that, directModel, callback) { + that.record("request"); + // use a setTimeout to simulate an asynchronous request + setTimeout(callback, 100); + }; + + fluid.tests.verifyRequestQueue = function (that, numRequests, expectedRecord, requestDelay, cleanup) { + requestDelay = requestDelay || 0; + cleanup = cleanup || jqUnit.start; + var previousCallNum = 0; + + var assertRequest = function (callNum) { + return function () { + jqUnit.assertTrue("Set call " + callNum + " should be triggered in the correct order", callNum > previousCallNum); + previousCallNum = callNum; + if (callNum === numRequests) { + jqUnit.assertDeepEq("The event record should have been updated appropriately.", expectedRecord, that.fireRecord); + cleanup(); + } + }; + }; + + var triggerAdd = function (currentCycle) { + that.add({ + method: that.request, + directModel: {model: currentCycle}, + callback: assertRequest(currentCycle) + }); + }; + + fluid.tests.asyncLoop(triggerAdd, requestDelay, numRequests); + }; + + jqUnit.asyncTest("Request Queue", function () { + fluid.tests.requestQueue({ + listeners: { + "onCreate.verifyRequestQueue": { + listener: "fluid.tests.verifyRequestQueue", + args: ["{that}", 3, { + queued: 3, + unqueued: 3, + request: 3, + isActive: { + "true": 3, + "false": 3 + } + }] + } + } + }); + }); + + fluid.defaults("fluid.tests.requestQueue.debounce", { + gradeNames: ["fluid.requestQueue.debounce", "fluid.tests.requestQueue", "autoInit"] + }); + + jqUnit.asyncTest("Request Queue: Debounce", function () { + fluid.tests.requestQueue.debounce({ + listeners: { + "onCreate.verifyDebounceRequestQueue": { + listener: "fluid.tests.verifyRequestQueue", + args: ["{that}", 3, { + queued: 3, + unqueued: 2, + request: 2, + isActive: { + "true": 2, + "false": 2 + } + }] + } + } + }); + }); + + fluid.defaults("fluid.tests.requestQueue.throttle", { + gradeNames: ["fluid.requestQueue.throttle", "fluid.tests.requestQueue", "autoInit"] + }); + + jqUnit.asyncTest("Request Queue: Throttle", function () { + fluid.tests.requestQueue.throttle({ + listeners: { + "onCreate.verifyThrottleRequestQueue": { + listener: "fluid.tests.verifyRequestQueue", + args: ["{that}", 3, { + queued: 2, + unqueued: 2, + request: 2, + isActive: { + "true": 2, + "false": 2 + } + }, 6] + } + } + }); + }); +})(); From fe6a81e0075a540a824828c7e71ffe27672f6328 Mon Sep 17 00:00:00 2001 From: Justin Obara Date: Thu, 30 Oct 2014 12:13:59 -0400 Subject: [PATCH 02/29] FLUID-5542: Added more comments to requestQueues --- src/framework/core/js/FluidRequests.js | 33 +++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/src/framework/core/js/FluidRequests.js b/src/framework/core/js/FluidRequests.js index f3a9f1ac68..9ed534d060 100644 --- a/src/framework/core/js/FluidRequests.js +++ b/src/framework/core/js/FluidRequests.js @@ -345,16 +345,33 @@ var fluid_2_0 = fluid_2_0 || {}; /** Start datasource **/ + /* + * A grade definining an emptyDatasource + * The primary reason for this grade is to provide details on the + * expected structure a DataSource should have. + */ fluid.defaults("fluid.emptyDatasource", { gradeNames: ["fluid.eventedComponent", "autoInit"] - // Invokers should be defined for the typical HTTP rest requests + // Invokers should be defined for the typical HTTP rest requests. + // Each method should have the signature (directModel, callback) + // + // directmodel is a JSON object cotaining the directives and payload + // to the request. (e.g. {model: {key: value}} ) + // + // callback is a function that will be called after the request has + // returned. + // // invokers: { // "get": {}, - // "set": {}, // set should handle POST and PUT + // "set": {}, // set should handle POST and PUT requests // "delete": {} // } }); + /* + * A basic request queue that will execute each request, one-by-one + * in the order that they are received. + */ fluid.defaults("fluid.requestQueue", { gradeNames: ["fluid.standardRelayComponent", "autoInit"], events: { @@ -402,6 +419,11 @@ var fluid_2_0 = fluid_2_0 || {}; } }; + /* + * A request queue that will only store the latests request in the queue. + * Intermediate requests that are queued but not invoked, will be replaced + * by new ones. + */ fluid.defaults("fluid.requestQueue.debounce", { gradeNames: ["fluid.requestQueue", "autoInit"], invokers: { @@ -412,14 +434,19 @@ var fluid_2_0 = fluid_2_0 || {}; } }); + fluid.requestQueue.debounce.add = function (that, request) { that.queue[0] = request; that.events.queued.fire(request); }; + /* + * A request queue that will only queue requests that are received after + * a specified delay (in milliseconds). + */ fluid.defaults("fluid.requestQueue.throttle", { gradeNames: ["fluid.requestQueue", "autoInit"], - delay: 10, + delay: 10, // delay in milliseconds model: { isThrottled: false }, From a29872fb22cee19f2976f656f97252d328e8a926 Mon Sep 17 00:00:00 2001 From: Justin Obara Date: Thu, 30 Oct 2014 14:21:49 -0400 Subject: [PATCH 03/29] FLUID-5542: Added the queuedDataSource grade --- src/framework/core/js/FluidRequests.js | 145 +++++++++--------- .../core/js/DatasourceTests.js | 56 +++++++ 2 files changed, 126 insertions(+), 75 deletions(-) diff --git a/src/framework/core/js/FluidRequests.js b/src/framework/core/js/FluidRequests.js index 9ed534d060..cb51327030 100644 --- a/src/framework/core/js/FluidRequests.js +++ b/src/framework/core/js/FluidRequests.js @@ -343,14 +343,14 @@ var fluid_2_0 = fluid_2_0 || {}; return fluid.NO_VALUE; }; - /** Start datasource **/ + /** Start dataSource **/ /* - * A grade definining an emptyDatasource + * A grade definining an emptyDataSource * The primary reason for this grade is to provide details on the * expected structure a DataSource should have. */ - fluid.defaults("fluid.emptyDatasource", { + fluid.defaults("fluid.emptyDataSource", { gradeNames: ["fluid.eventedComponent", "autoInit"] // Invokers should be defined for the typical HTTP rest requests. // Each method should have the signature (directModel, callback) @@ -400,6 +400,12 @@ var fluid_2_0 = fluid_2_0 || {}; } }); + /* + * Adds reqeusts to the queue in the order they are received. + * + * The request object contains the request function and arguments. + * In the form {method: requestFn, directModel: {}, callback: callbackFn} + */ fluid.requestQueue.add = function (that, request) { that.queue.push(request); that.events.queued.fire(request); @@ -434,7 +440,12 @@ var fluid_2_0 = fluid_2_0 || {}; } }); - + /* + * Adds only one item to the queue at a time, new requests replace older ones + * + * The request object contains the request function and arguments. + * In the form {method: requestFn, directModel: {}, callback: callbackFn} + */ fluid.requestQueue.debounce.add = function (that, request) { that.queue[0] = request; that.events.queued.fire(request); @@ -458,6 +469,12 @@ var fluid_2_0 = fluid_2_0 || {}; } }); + /* + * Adds items to the queue after a specified delay. + * + * The request object contains the request function and arguments. + * In the form {method: requestFn, directModel: {}, callback: callbackFn} + */ fluid.requestQueue.throttle.add = function (that, request) { if (!that.model.isThrottled) { that.applier.change("isThrottled", true); @@ -469,76 +486,54 @@ var fluid_2_0 = fluid_2_0 || {}; } }; - // fluid.defaults("fluid.queuedDataSource", { - // gradeNames: ["fluid.standardRelayComponent", "autoInit"], - // events: { - // requestQueued: null, - // requestUnqueued: null - // }, - // model: { - // isActive: false - // }, - // members: { - // queue: [] - // }, - // listeners: { - // "requestUnqueued.setIsActive": { - // changePath: "isActive", - // value: false - // }, - // "requestQueued": "{that}.start" - // }, - // modelListeners: { - // "isActive": "{that}.start" - // }, - // invokers: { - // add: { - // funcName: "fluid.queuedDataSource.add", - // args: ["{that}", "{arguments}.0"] - // }, - // start: { - // funcName: "fluid.queuedDataSource.start", - // args: ["{that}"] - // }, - // set: { - // funcName: "fluid.queuedDataSource.set", - // args: ["{that}", "{arguments}.0", "{arguments}.1"] - // }, - // get: "{wrappedDataSource}.get", - // "delete": "{wrappedDataSource}.delete" - // }, - // components: { - // wrappedDataSource: { - // // requires a dataSource that implements the standard set, get, and delete methods. - // type: "fluid.emptyDatasource" - // } - // } - // }); - - // fluid.queuedDataSource.add = function (that, args) { - // that.queue.push(args); - // that.events.requestQueued.fire(args); - // }; - // - // fluid.queuedDataSource.start = function (that) { - // if (!that.model.isActive && that.queue.length) { - // var args = that.queue[0]; - // that.applier.change("isActive", true); - // - // that.wrappedDataSource.set(args.directModel, function () { - // that.events.requestUnqueued.fire(that.queue.shift()); - // args.callback.apply(null, arguments); - // }); - // } - // }; - // - // fluid.queuedDataSource.set = function (that, directModel, callback) { - // that.add({ - // directModel: directModel, - // callback: callback - // }); - // }; - - /** End datasource **/ + /* + * A dataSource wrapper providing a queuing mechanism for requests. + * The queue subcomponents, writeQueue (set/delete) and readQueue (get) + * can be configured to use any of the request queue grades. + * + * A fully implemented dataSource, following the structure oulined by fluid.emptyDataSource, + * must be provided in the wrappedDataSource subcomponent. The get, set, and delete methods + * found on the queuedDataSource will call thier counterparts in the wrappedDataSource, after + * filtering through the appropriate queue. + */ + fluid.defaults("fluid.queuedDataSource", { + gradeNames: ["fluid.standardRelayComponent", "autoInit"], + components: { + writeQueue: { + type: "fluid.requestQueue" + }, + readQueue: { + type: "fluid.requestQueue" + }, + wrappedDataSource: { + // requires a dataSource that implements the standard set, get, and delete methods. + type: "fluid.emptyDataSource" + } + }, + invokers: { + set: { + funcName: "fluid.queuedDataSource.set", + args: ["{writeQueue}", "{wrappedDataSource}.set", "{arguments}.0", "{arguments}.1"] + }, + get: { + funcName: "fluid.queuedDataSource.set", + args: ["{readQueue}", "{wrappedDataSource}.get", "{arguments}.0", "{arguments}.1"] + }, + "delete": { + funcName: "fluid.queuedDataSource.set", + args: ["{writeQueue}", "{wrappedDataSource}.delete", "{arguments}.0", "{arguments}.1"] + } + } + }); + + fluid.queuedDataSource.set = function (queue, requestMethod, directModel, callback) { + queue.add({ + method: requestMethod, + directModel: directModel, + callback: callback + }); + }; + + /** End dataSource **/ })(jQuery, fluid_2_0); diff --git a/tests/framework-tests/core/js/DatasourceTests.js b/tests/framework-tests/core/js/DatasourceTests.js index 7a92458e58..fee43215cc 100644 --- a/tests/framework-tests/core/js/DatasourceTests.js +++ b/tests/framework-tests/core/js/DatasourceTests.js @@ -184,4 +184,60 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt } }); }); + + + fluid.defaults("fluid.tests.queuedDataSource", { + gradeNames: ["fluid.queuedDataSource", "autoInit"], + components: { + wrappedDataSource: { + options: { + invokers: { + "get": "fluid.tests.queuedDataSource.request", + "set": "fluid.tests.queuedDataSource.request", + "delete": "fluid.tests.queuedDataSource.request" + } + } + } + } + }); + + fluid.tests.queuedDataSource.request = function (directModel, callback) { + callback(directModel); + }; + + fluid.tests.queuedDataSource.assertRequest = function (directModel) { + jqUnit.assert("The " + directModel.type + " request should have been triggerd"); + }; + + fluid.tests.queuedDataSource.assertResponseQueued = function (queueName, request) { + jqUnit.assertEquals("The request was added to the correct queue", request.directModel.queue, queueName); + }; + + jqUnit.asyncTest("Queued DataSource", function () { + jqUnit.expect(6); + fluid.tests.queuedDataSource({ + listeners: { + "{writeQueue}.events.queued": { + listener: "fluid.tests.queuedDataSource.assertResponseQueued", + args: ["writeQueue", "{arguments}.0"] + }, + "{readQueue}.events.queued": { + listener: "fluid.tests.queuedDataSource.assertResponseQueued", + args: ["readQueue", "{arguments}.0"] + }, + "onCreate": [{ + listener: "{that}.get", + args: [{queue: "readQueue", type: "get"}, fluid.tests.queuedDataSource.assertRequest] + }, { + listener: "{that}.set", + args: [{queue: "writeQueue", type: "set"}, fluid.tests.queuedDataSource.assertRequest] + }, { + listener: "{that}.delete", + args: [{queue: "writeQueue", type: "delete"}, fluid.tests.queuedDataSource.assertRequest] + }, { + listener: "jqUnit.start" + }] + } + }); + }); })(); From 983ff2f3a23eee99a25b696fe11a09df5073887c Mon Sep 17 00:00:00 2001 From: Justin Obara Date: Thu, 30 Oct 2014 14:28:44 -0400 Subject: [PATCH 04/29] FLUID-5542: Update all tests, renamed files --- tests/all-tests.html | 1 + .../core/html/{Datasource-test.html => DataSource-test.html} | 0 .../core/js/{DatasourceTests.js => DataSourceTests.js} | 0 3 files changed, 1 insertion(+) rename tests/framework-tests/core/html/{Datasource-test.html => DataSource-test.html} (100%) rename tests/framework-tests/core/js/{DatasourceTests.js => DataSourceTests.js} (100%) diff --git a/tests/all-tests.html b/tests/all-tests.html index 85e3cd9e02..fb3ae80c8f 100644 --- a/tests/all-tests.html +++ b/tests/all-tests.html @@ -27,6 +27,7 @@ "./framework-tests/core/html/FluidIoC-test.html", "./framework-tests/core/html/FluidIoCStandalone-test.html", "./framework-tests/core/html/FluidIoCView-test.html", + "./framework-tests/core/html/DataSource-test.html", "./framework-tests/enhancement/html/ProgressiveEnhancement-test.html", "./framework-tests/renderer/html/RendererUtilities-test.html", "./framework-tests/preferences/html/AuxBuilder-test.html", diff --git a/tests/framework-tests/core/html/Datasource-test.html b/tests/framework-tests/core/html/DataSource-test.html similarity index 100% rename from tests/framework-tests/core/html/Datasource-test.html rename to tests/framework-tests/core/html/DataSource-test.html diff --git a/tests/framework-tests/core/js/DatasourceTests.js b/tests/framework-tests/core/js/DataSourceTests.js similarity index 100% rename from tests/framework-tests/core/js/DatasourceTests.js rename to tests/framework-tests/core/js/DataSourceTests.js From 09b61af4a25eb569ed31d8412eeeb9a7dd1bb365 Mon Sep 17 00:00:00 2001 From: Justin Obara Date: Fri, 31 Oct 2014 08:21:31 -0400 Subject: [PATCH 05/29] FLUID-5542: Corrected typos --- src/framework/core/js/FluidRequests.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/framework/core/js/FluidRequests.js b/src/framework/core/js/FluidRequests.js index cb51327030..b4cad75fe1 100644 --- a/src/framework/core/js/FluidRequests.js +++ b/src/framework/core/js/FluidRequests.js @@ -401,7 +401,7 @@ var fluid_2_0 = fluid_2_0 || {}; }); /* - * Adds reqeusts to the queue in the order they are received. + * Adds requests to the queue in the order they are received. * * The request object contains the request function and arguments. * In the form {method: requestFn, directModel: {}, callback: callbackFn} @@ -491,9 +491,9 @@ var fluid_2_0 = fluid_2_0 || {}; * The queue subcomponents, writeQueue (set/delete) and readQueue (get) * can be configured to use any of the request queue grades. * - * A fully implemented dataSource, following the structure oulined by fluid.emptyDataSource, + * A fully implemented dataSource, following the structure outlined by fluid.emptyDataSource, * must be provided in the wrappedDataSource subcomponent. The get, set, and delete methods - * found on the queuedDataSource will call thier counterparts in the wrappedDataSource, after + * found on the queuedDataSource will call their counterparts in the wrappedDataSource, after * filtering through the appropriate queue. */ fluid.defaults("fluid.queuedDataSource", { From 1bdbf254ff0def6b3d4713736a5e77446da743dc Mon Sep 17 00:00:00 2001 From: Justin Obara Date: Fri, 31 Oct 2014 08:32:34 -0400 Subject: [PATCH 06/29] FLUID-5542: Renaming Renamed "queue" member to "requests" and "add" invoker to "queue" --- src/framework/core/js/FluidRequests.js | 32 +++++++++---------- .../core/js/DataSourceTests.js | 6 ++-- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/framework/core/js/FluidRequests.js b/src/framework/core/js/FluidRequests.js index b4cad75fe1..0e61d62e33 100644 --- a/src/framework/core/js/FluidRequests.js +++ b/src/framework/core/js/FluidRequests.js @@ -382,15 +382,15 @@ var fluid_2_0 = fluid_2_0 || {}; isActive: false }, members: { - queue: [] + requests: [] }, listeners: { "unqueued": "{that}.start", "queued": "{that}.start" }, invokers: { - add: { - funcName: "fluid.requestQueue.add", + queue: { + funcName: "fluid.requestQueue.queue", args: ["{that}", "{arguments}.0"] }, start: { @@ -406,14 +406,14 @@ var fluid_2_0 = fluid_2_0 || {}; * The request object contains the request function and arguments. * In the form {method: requestFn, directModel: {}, callback: callbackFn} */ - fluid.requestQueue.add = function (that, request) { - that.queue.push(request); + fluid.requestQueue.queue = function (that, request) { + that.requests.push(request); that.events.queued.fire(request); }; fluid.requestQueue.start = function (that) { - if (!that.model.isActive && that.queue.length) { - var request = that.queue.shift(); + if (!that.model.isActive && that.requests.length) { + var request = that.requests.shift(); var callbackProxy = function () { that.applier.change("isActive", false); that.events.unqueued.fire(request); @@ -433,8 +433,8 @@ var fluid_2_0 = fluid_2_0 || {}; fluid.defaults("fluid.requestQueue.debounce", { gradeNames: ["fluid.requestQueue", "autoInit"], invokers: { - add: { - funcName: "fluid.requestQueue.debounce.add", + queue: { + funcName: "fluid.requestQueue.debounce.queue", args: ["{that}", "{arguments}.0"] } } @@ -446,8 +446,8 @@ var fluid_2_0 = fluid_2_0 || {}; * The request object contains the request function and arguments. * In the form {method: requestFn, directModel: {}, callback: callbackFn} */ - fluid.requestQueue.debounce.add = function (that, request) { - that.queue[0] = request; + fluid.requestQueue.debounce.queue = function (that, request) { + that.requests[0] = request; that.events.queued.fire(request); }; @@ -462,8 +462,8 @@ var fluid_2_0 = fluid_2_0 || {}; isThrottled: false }, invokers: { - add: { - funcName: "fluid.requestQueue.throttle.add", + queue: { + funcName: "fluid.requestQueue.throttle.queue", args: ["{that}", "{arguments}.0"] } } @@ -475,10 +475,10 @@ var fluid_2_0 = fluid_2_0 || {}; * The request object contains the request function and arguments. * In the form {method: requestFn, directModel: {}, callback: callbackFn} */ - fluid.requestQueue.throttle.add = function (that, request) { + fluid.requestQueue.throttle.queue = function (that, request) { if (!that.model.isThrottled) { that.applier.change("isThrottled", true); - that.queue.push(request); + that.requests.push(request); that.events.queued.fire(request); setTimeout(function () { that.applier.change("isThrottled", false); @@ -527,7 +527,7 @@ var fluid_2_0 = fluid_2_0 || {}; }); fluid.queuedDataSource.set = function (queue, requestMethod, directModel, callback) { - queue.add({ + queue.queue({ method: requestMethod, directModel: directModel, callback: callback diff --git a/tests/framework-tests/core/js/DataSourceTests.js b/tests/framework-tests/core/js/DataSourceTests.js index fee43215cc..27ec17eb4b 100644 --- a/tests/framework-tests/core/js/DataSourceTests.js +++ b/tests/framework-tests/core/js/DataSourceTests.js @@ -109,15 +109,15 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt }; }; - var triggerAdd = function (currentCycle) { - that.add({ + var triggerQueue = function (currentCycle) { + that.queue({ method: that.request, directModel: {model: currentCycle}, callback: assertRequest(currentCycle) }); }; - fluid.tests.asyncLoop(triggerAdd, requestDelay, numRequests); + fluid.tests.asyncLoop(triggerQueue, requestDelay, numRequests); }; jqUnit.asyncTest("Request Queue", function () { From fed923a7388e0cb75916c5ac4f4b3722ef61587e Mon Sep 17 00:00:00 2001 From: Justin Obara Date: Fri, 31 Oct 2014 09:42:05 -0400 Subject: [PATCH 07/29] FLUID-5542: Corrected signature for the DataSource Corrected the signatures of the DataSource to take in a model for the set operation. Also updated the comments to better reflect the architecture of a DataSource. (See: http://wiki.fluidproject.org/display/fluid/Notes+on+Kettle) --- src/framework/core/js/FluidRequests.js | 64 +++++++++++-------- .../core/js/DataSourceTests.js | 6 +- 2 files changed, 42 insertions(+), 28 deletions(-) diff --git a/src/framework/core/js/FluidRequests.js b/src/framework/core/js/FluidRequests.js index 0e61d62e33..92821e6d34 100644 --- a/src/framework/core/js/FluidRequests.js +++ b/src/framework/core/js/FluidRequests.js @@ -346,25 +346,32 @@ var fluid_2_0 = fluid_2_0 || {}; /** Start dataSource **/ /* - * A grade definining an emptyDataSource - * The primary reason for this grade is to provide details on the - * expected structure a DataSource should have. + * A grade defining a DataSource. + * This grade illustrates the expected structure a DataSource, as well as + * providing a means for identifying dataSources in a component tree by type. + * + * The ultimate purpose of the "dataSource" abstraction is to abstract over + * whether particular functionality is hosted locally or remotely. */ - fluid.defaults("fluid.emptyDataSource", { + fluid.defaults("fluid.dataSource", { gradeNames: ["fluid.eventedComponent", "autoInit"] - // Invokers should be defined for the typical HTTP rest requests. - // Each method should have the signature (directModel, callback) + // Invokers should be defined for the typical CRUD functions. + // + // The "get" and "delete" methods require the signature (directModel, callback). + // The "set" method requires the signature (directModel, model, callback) // - // directmodel is a JSON object cotaining the directives and payload - // to the request. (e.g. {model: {key: value}} ) + // directModel: A JSON summary of the contents of an URL. It expresses an + // "index" into some set of state which can be read and written. // - // callback is a function that will be called after the request has - // returned. + // callback: A function that will be called after the CRUD operation has + // returned. + // + // model: The payload sent to the storage. // // invokers: { - // "get": {}, - // "set": {}, // set should handle POST and PUT requests - // "delete": {} + // "get": {}, // handles the Read function + // "set": {}, // handles the Create and Update functions + // "delete": {} // handles the Delete function // } }); @@ -404,7 +411,7 @@ var fluid_2_0 = fluid_2_0 || {}; * Adds requests to the queue in the order they are received. * * The request object contains the request function and arguments. - * In the form {method: requestFn, directModel: {}, callback: callbackFn} + * In the form {method: requestFn, directModel: {}, model: {}, callback: callbackFn} */ fluid.requestQueue.queue = function (that, request) { that.requests.push(request); @@ -421,7 +428,11 @@ var fluid_2_0 = fluid_2_0 || {}; }; that.applier.change("isActive", true); - request.method(request.directModel, callbackProxy); + if (request.model) { + request.method(request.directModel, request.model, callbackProxy); + } else { + request.method(request.directModel, callbackProxy); + } } }; @@ -444,7 +455,7 @@ var fluid_2_0 = fluid_2_0 || {}; * Adds only one item to the queue at a time, new requests replace older ones * * The request object contains the request function and arguments. - * In the form {method: requestFn, directModel: {}, callback: callbackFn} + * In the form {method: requestFn, directModel: {}, model: {}, callback: callbackFn} */ fluid.requestQueue.debounce.queue = function (that, request) { that.requests[0] = request; @@ -473,7 +484,7 @@ var fluid_2_0 = fluid_2_0 || {}; * Adds items to the queue after a specified delay. * * The request object contains the request function and arguments. - * In the form {method: requestFn, directModel: {}, callback: callbackFn} + * In the form {method: requestFn, directModel: {}, model: {}, callback: callbackFn} */ fluid.requestQueue.throttle.queue = function (that, request) { if (!that.model.isThrottled) { @@ -491,7 +502,7 @@ var fluid_2_0 = fluid_2_0 || {}; * The queue subcomponents, writeQueue (set/delete) and readQueue (get) * can be configured to use any of the request queue grades. * - * A fully implemented dataSource, following the structure outlined by fluid.emptyDataSource, + * A fully implemented dataSource, following the structure outlined by fluid.dataSource, * must be provided in the wrappedDataSource subcomponent. The get, set, and delete methods * found on the queuedDataSource will call their counterparts in the wrappedDataSource, after * filtering through the appropriate queue. @@ -507,29 +518,30 @@ var fluid_2_0 = fluid_2_0 || {}; }, wrappedDataSource: { // requires a dataSource that implements the standard set, get, and delete methods. - type: "fluid.emptyDataSource" + type: "fluid.dataSource" } }, invokers: { set: { - funcName: "fluid.queuedDataSource.set", - args: ["{writeQueue}", "{wrappedDataSource}.set", "{arguments}.0", "{arguments}.1"] + funcName: "fluid.queuedDataSource.queue", + args: ["{writeQueue}", "{wrappedDataSource}.set", "{arguments}.0", "{arguments}.1", "{arguments}.2"] }, get: { - funcName: "fluid.queuedDataSource.set", - args: ["{readQueue}", "{wrappedDataSource}.get", "{arguments}.0", "{arguments}.1"] + funcName: "fluid.queuedDataSource.queue", + args: ["{readQueue}", "{wrappedDataSource}.get", "{arguments}.0", null, "{arguments}.1"] }, "delete": { - funcName: "fluid.queuedDataSource.set", - args: ["{writeQueue}", "{wrappedDataSource}.delete", "{arguments}.0", "{arguments}.1"] + funcName: "fluid.queuedDataSource.queue", + args: ["{writeQueue}", "{wrappedDataSource}.delete", "{arguments}.0", null, "{arguments}.1"] } } }); - fluid.queuedDataSource.set = function (queue, requestMethod, directModel, callback) { + fluid.queuedDataSource.queue = function (queue, requestMethod, directModel, model, callback) { queue.queue({ method: requestMethod, directModel: directModel, + model: model, callback: callback }); }; diff --git a/tests/framework-tests/core/js/DataSourceTests.js b/tests/framework-tests/core/js/DataSourceTests.js index 27ec17eb4b..b67a6ce620 100644 --- a/tests/framework-tests/core/js/DataSourceTests.js +++ b/tests/framework-tests/core/js/DataSourceTests.js @@ -201,7 +201,9 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt } }); - fluid.tests.queuedDataSource.request = function (directModel, callback) { + fluid.tests.queuedDataSource.request = function () { + var callback = arguments[arguments.length -1]; + var directModel = arguments[0]; callback(directModel); }; @@ -230,7 +232,7 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt args: [{queue: "readQueue", type: "get"}, fluid.tests.queuedDataSource.assertRequest] }, { listener: "{that}.set", - args: [{queue: "writeQueue", type: "set"}, fluid.tests.queuedDataSource.assertRequest] + args: [{queue: "writeQueue", type: "set"}, {key: "value"}, fluid.tests.queuedDataSource.assertRequest] }, { listener: "{that}.delete", args: [{queue: "writeQueue", type: "delete"}, fluid.tests.queuedDataSource.assertRequest] From 53d355ba99eb2947ed19ebf5caa895b8c72dde77 Mon Sep 17 00:00:00 2001 From: Justin Obara Date: Fri, 31 Oct 2014 10:16:09 -0400 Subject: [PATCH 08/29] FLUID-5542: Created a new grade for fifo queue Separated the FIFO functionality into a new grade and left the base grade without a queue method implemented. --- src/framework/core/js/FluidRequests.js | 64 +++++++++++++------ .../core/js/DataSourceTests.js | 10 ++- 2 files changed, 52 insertions(+), 22 deletions(-) diff --git a/src/framework/core/js/FluidRequests.js b/src/framework/core/js/FluidRequests.js index 92821e6d34..6d27b1e3ef 100644 --- a/src/framework/core/js/FluidRequests.js +++ b/src/framework/core/js/FluidRequests.js @@ -376,8 +376,10 @@ var fluid_2_0 = fluid_2_0 || {}; }); /* - * A basic request queue that will execute each request, one-by-one - * in the order that they are received. + * A grade defining a requestQueue + * + * The basic functionality of a requestQueue is defined. However, the queue + * invoker must be supplied with a valid queuing function. */ fluid.defaults("fluid.requestQueue", { gradeNames: ["fluid.standardRelayComponent", "autoInit"], @@ -396,10 +398,20 @@ var fluid_2_0 = fluid_2_0 || {}; "queued": "{that}.start" }, invokers: { - queue: { - funcName: "fluid.requestQueue.queue", - args: ["{that}", "{arguments}.0"] - }, + // The queue method must be supplied and should take the signature + // (that, request). + // + // that: A reference to the component itself + // + // request: A JSON object containing the request method and it's arguments + // in the form: + // { + // method: function, + // directModel: object, + // model: objcet, + // callback: function + // } + // queue: {}, start: { funcName: "fluid.requestQueue.start", args: ["{that}"] @@ -407,17 +419,6 @@ var fluid_2_0 = fluid_2_0 || {}; } }); - /* - * Adds requests to the queue in the order they are received. - * - * The request object contains the request function and arguments. - * In the form {method: requestFn, directModel: {}, model: {}, callback: callbackFn} - */ - fluid.requestQueue.queue = function (that, request) { - that.requests.push(request); - that.events.queued.fire(request); - }; - fluid.requestQueue.start = function (that) { if (!that.model.isActive && that.requests.length) { var request = that.requests.shift(); @@ -436,6 +437,31 @@ var fluid_2_0 = fluid_2_0 || {}; } }; + /* + * A basic request queue that will execute each request, one-by-one + * in the order that they are received. + */ + fluid.defaults("fluid.requestQueue.fifo", { + gradeNames: ["fluid.requestQueue", "autoInit"], + invokers: { + queue: { + funcName: "fluid.requestQueue.fifo.queue", + args: ["{that}", "{arguments}.0"] + } + } + }); + + /* + * Adds requests to the queue in the order they are received. + * + * The request object contains the request function and arguments. + * In the form {method: requestFn, directModel: {}, model: {}, callback: callbackFn} + */ + fluid.requestQueue.fifo.queue = function (that, request) { + that.requests.push(request); + that.events.queued.fire(request); + }; + /* * A request queue that will only store the latests request in the queue. * Intermediate requests that are queued but not invoked, will be replaced @@ -511,10 +537,10 @@ var fluid_2_0 = fluid_2_0 || {}; gradeNames: ["fluid.standardRelayComponent", "autoInit"], components: { writeQueue: { - type: "fluid.requestQueue" + type: "fluid.requestQueue.fifo" }, readQueue: { - type: "fluid.requestQueue" + type: "fluid.requestQueue.fifo" }, wrappedDataSource: { // requires a dataSource that implements the standard set, get, and delete methods. diff --git a/tests/framework-tests/core/js/DataSourceTests.js b/tests/framework-tests/core/js/DataSourceTests.js index b67a6ce620..28254b2e50 100644 --- a/tests/framework-tests/core/js/DataSourceTests.js +++ b/tests/framework-tests/core/js/DataSourceTests.js @@ -120,10 +120,14 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt fluid.tests.asyncLoop(triggerQueue, requestDelay, numRequests); }; - jqUnit.asyncTest("Request Queue", function () { - fluid.tests.requestQueue({ + fluid.defaults("fluid.tests.requestQueue.fifo", { + gradeNames: ["fluid.requestQueue.fifo", "fluid.tests.requestQueue", "autoInit"] + }); + + jqUnit.asyncTest("Request Queue: FIFO", function () { + fluid.tests.requestQueue.fifo({ listeners: { - "onCreate.verifyRequestQueue": { + "onCreate.verifyFifoRequestQueue": { listener: "fluid.tests.verifyRequestQueue", args: ["{that}", 3, { queued: 3, From 0550ea12e0dad19e115037733ed24f1a0a6b3720 Mon Sep 17 00:00:00 2001 From: Justin Obara Date: Fri, 31 Oct 2014 10:21:00 -0400 Subject: [PATCH 09/29] FLUID-5542: Removed throttle request queue Removed the throttle request queue as there currently a use case for supporting it. If one does arise, we should implement it as necessary. --- src/framework/core/js/FluidRequests.js | 35 ------------------- .../core/js/DataSourceTests.js | 24 ------------- 2 files changed, 59 deletions(-) diff --git a/src/framework/core/js/FluidRequests.js b/src/framework/core/js/FluidRequests.js index 6d27b1e3ef..56c5677e6c 100644 --- a/src/framework/core/js/FluidRequests.js +++ b/src/framework/core/js/FluidRequests.js @@ -488,41 +488,6 @@ var fluid_2_0 = fluid_2_0 || {}; that.events.queued.fire(request); }; - /* - * A request queue that will only queue requests that are received after - * a specified delay (in milliseconds). - */ - fluid.defaults("fluid.requestQueue.throttle", { - gradeNames: ["fluid.requestQueue", "autoInit"], - delay: 10, // delay in milliseconds - model: { - isThrottled: false - }, - invokers: { - queue: { - funcName: "fluid.requestQueue.throttle.queue", - args: ["{that}", "{arguments}.0"] - } - } - }); - - /* - * Adds items to the queue after a specified delay. - * - * The request object contains the request function and arguments. - * In the form {method: requestFn, directModel: {}, model: {}, callback: callbackFn} - */ - fluid.requestQueue.throttle.queue = function (that, request) { - if (!that.model.isThrottled) { - that.applier.change("isThrottled", true); - that.requests.push(request); - that.events.queued.fire(request); - setTimeout(function () { - that.applier.change("isThrottled", false); - }, that.options.delay); - } - }; - /* * A dataSource wrapper providing a queuing mechanism for requests. * The queue subcomponents, writeQueue (set/delete) and readQueue (get) diff --git a/tests/framework-tests/core/js/DataSourceTests.js b/tests/framework-tests/core/js/DataSourceTests.js index 28254b2e50..daefcd8294 100644 --- a/tests/framework-tests/core/js/DataSourceTests.js +++ b/tests/framework-tests/core/js/DataSourceTests.js @@ -166,30 +166,6 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt }); }); - fluid.defaults("fluid.tests.requestQueue.throttle", { - gradeNames: ["fluid.requestQueue.throttle", "fluid.tests.requestQueue", "autoInit"] - }); - - jqUnit.asyncTest("Request Queue: Throttle", function () { - fluid.tests.requestQueue.throttle({ - listeners: { - "onCreate.verifyThrottleRequestQueue": { - listener: "fluid.tests.verifyRequestQueue", - args: ["{that}", 3, { - queued: 2, - unqueued: 2, - request: 2, - isActive: { - "true": 2, - "false": 2 - } - }, 6] - } - } - }); - }); - - fluid.defaults("fluid.tests.queuedDataSource", { gradeNames: ["fluid.queuedDataSource", "autoInit"], components: { From 0613710c78cb8a74dd5888e81d9bf10058837cfb Mon Sep 17 00:00:00 2001 From: Justin Obara Date: Fri, 31 Oct 2014 10:28:01 -0400 Subject: [PATCH 10/29] FLUID-5542: Removed the asyncloop from the tests This loop was only needed for the throttling tests to prevent the requests from all firing at the same time. Since the throttling request has been removed, a simple for loop could be used instead. --- .../core/js/DataSourceTests.js | 24 ++++--------------- 1 file changed, 4 insertions(+), 20 deletions(-) diff --git a/tests/framework-tests/core/js/DataSourceTests.js b/tests/framework-tests/core/js/DataSourceTests.js index daefcd8294..c7213aeaec 100644 --- a/tests/framework-tests/core/js/DataSourceTests.js +++ b/tests/framework-tests/core/js/DataSourceTests.js @@ -17,23 +17,6 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt fluid.registerNamespace("fluid.tests"); - fluid.tests.asyncLoop = function (fn, delay, cycles) { - var count = 0; - cycles = Math.abs(cycles) || 1; // must have at least one cycle - - // recursive - var timeloop = function () { - count++; - fn(count); - - if (count < cycles) { - setTimeout(timeloop, delay); - } - }; - - timeloop(); - }; - fluid.defaults("fluid.tests.requestQueue", { gradeNames: ["fluid.requestQueue", "autoInit"], members: { @@ -93,8 +76,7 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt setTimeout(callback, 100); }; - fluid.tests.verifyRequestQueue = function (that, numRequests, expectedRecord, requestDelay, cleanup) { - requestDelay = requestDelay || 0; + fluid.tests.verifyRequestQueue = function (that, numRequests, expectedRecord, cleanup) { cleanup = cleanup || jqUnit.start; var previousCallNum = 0; @@ -117,7 +99,9 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt }); }; - fluid.tests.asyncLoop(triggerQueue, requestDelay, numRequests); + for (var i = 1; i <= numRequests; i++) { + triggerQueue(i); + } }; fluid.defaults("fluid.tests.requestQueue.fifo", { From a124dce42df7214f8ba2e8d5e08029476e705afb Mon Sep 17 00:00:00 2001 From: Justin Obara Date: Wed, 5 Nov 2014 10:34:06 -0500 Subject: [PATCH 11/29] FLUID-5442: Re-implemented based on review. Queues are separated by read/write and resource (as specified by the directModel). --- src/framework/core/js/FluidRequests.js | 174 ++++++++++------- .../core/js/DataSourceTests.js | 177 +++++++++--------- 2 files changed, 198 insertions(+), 153 deletions(-) diff --git a/src/framework/core/js/FluidRequests.js b/src/framework/core/js/FluidRequests.js index 56c5677e6c..ee362a6c2e 100644 --- a/src/framework/core/js/FluidRequests.js +++ b/src/framework/core/js/FluidRequests.js @@ -347,7 +347,7 @@ var fluid_2_0 = fluid_2_0 || {}; /* * A grade defining a DataSource. - * This grade illustrates the expected structure a DataSource, as well as + * This grade illustrates the expected structure a dataSource, as well as * providing a means for identifying dataSources in a component tree by type. * * The ultimate purpose of the "dataSource" abstraction is to abstract over @@ -361,7 +361,7 @@ var fluid_2_0 = fluid_2_0 || {}; // The "set" method requires the signature (directModel, model, callback) // // directModel: A JSON summary of the contents of an URL. It expresses an - // "index" into some set of state which can be read and written. + // "index" into some set of state which can be read or written. // // callback: A function that will be called after the CRUD operation has // returned. @@ -375,43 +375,85 @@ var fluid_2_0 = fluid_2_0 || {}; // } }); + /* + * Converts an object or array to string for use as a key. + * The objects are sorted alphabetically to insure that they + * result in the same string across executions. + */ + fluid.objectToHashKey = function (obj) { + var str = fluid.isArrayable(obj) ? "array " : "object "; + var keys = fluid.keys(obj).sort(); + + fluid.each(keys, function (key) { + var val = obj[key]; + str += key + ":" + fluid.toHashKey(val); + }); + + return str; + }; + + /* + * Generates a string for use as a key. + * They typeof of the value passed in will be prepended + * to ensure that (strings vs numbers) and (arrays vs objects) + * are distinguishable. + */ + fluid.toHashKey = function (val) { + var str; + if(fluid.isPlainObject(val)){ + str = "<" + fluid.objectToHashKey(val) + ">"; + } else { + str = "|" + typeof(val) + " " + val + "|"; + } + return str; + }; + /* * A grade defining a requestQueue * * The basic functionality of a requestQueue is defined. However, the queue - * invoker must be supplied with a valid queuing function. + * invoker must be supplied with a valid queuing function. A request queue is + * used to manage concurrent requests. */ fluid.defaults("fluid.requestQueue", { gradeNames: ["fluid.standardRelayComponent", "autoInit"], events: { - queued: null, - unqueued: null + enqueued: null, + dropped: null, + onRequestStart: null, + afterRequestComplete: null }, model: { - isActive: false + request: null }, members: { - requests: [] + queue: [], }, listeners: { - "unqueued": "{that}.start", - "queued": "{that}.start" + "afterRequestComplete.start": "{that}.start", + "enqueued.start": "{that}.start" + }, + modelListeners: { + "request": { + listener: "fluid.requestQueue.fireRequestState", + args: ["{that}", "{change}.value", "{change}.oldValue"] + } }, invokers: { - // The queue method must be supplied and should take the signature + // The enqueue method must be supplied and take the signature // (that, request). // // that: A reference to the component itself // - // request: A JSON object containing the request method and it's arguments + // request: A JSON object containing the request method and its arguments // in the form: // { // method: function, // directModel: object, - // model: objcet, + // model: object, // callback: function // } - // queue: {}, + // enqueue: {}, start: { funcName: "fluid.requestQueue.start", args: ["{that}"] @@ -419,16 +461,25 @@ var fluid_2_0 = fluid_2_0 || {}; } }); + // Redirects model changes to explicit request state events, passing along + // the relavent request object. + fluid.requestQueue.fireRequestState = function (that, currentReq, previousReq) { + if (currentReq) { + that.events.onRequestStart.fire(currentReq); + } else if (previousReq) { + that.events.afterRequestComplete.fire(previousReq); + } + } + fluid.requestQueue.start = function (that) { - if (!that.model.isActive && that.requests.length) { - var request = that.requests.shift(); + if (!that.model.request && that.queue.length) { + var request = that.queue.shift(); var callbackProxy = function () { - that.applier.change("isActive", false); - that.events.unqueued.fire(request); + that.applier.change("request", null); request.callback.apply(null, arguments); }; - that.applier.change("isActive", true); + that.applier.change("request", request); if (request.model) { request.method(request.directModel, request.model, callbackProxy); } else { @@ -437,41 +488,16 @@ var fluid_2_0 = fluid_2_0 || {}; } }; - /* - * A basic request queue that will execute each request, one-by-one - * in the order that they are received. - */ - fluid.defaults("fluid.requestQueue.fifo", { - gradeNames: ["fluid.requestQueue", "autoInit"], - invokers: { - queue: { - funcName: "fluid.requestQueue.fifo.queue", - args: ["{that}", "{arguments}.0"] - } - } - }); - - /* - * Adds requests to the queue in the order they are received. - * - * The request object contains the request function and arguments. - * In the form {method: requestFn, directModel: {}, model: {}, callback: callbackFn} - */ - fluid.requestQueue.fifo.queue = function (that, request) { - that.requests.push(request); - that.events.queued.fire(request); - }; - /* * A request queue that will only store the latests request in the queue. * Intermediate requests that are queued but not invoked, will be replaced * by new ones. */ - fluid.defaults("fluid.requestQueue.debounce", { + fluid.defaults("fluid.requestQueue.concurrencyLimited", { gradeNames: ["fluid.requestQueue", "autoInit"], invokers: { - queue: { - funcName: "fluid.requestQueue.debounce.queue", + enqueue: { + funcName: "fluid.requestQueue.concurrencyLimited.enqueue", args: ["{that}", "{arguments}.0"] } } @@ -483,15 +509,18 @@ var fluid_2_0 = fluid_2_0 || {}; * The request object contains the request function and arguments. * In the form {method: requestFn, directModel: {}, model: {}, callback: callbackFn} */ - fluid.requestQueue.debounce.queue = function (that, request) { - that.requests[0] = request; - that.events.queued.fire(request); + fluid.requestQueue.concurrencyLimited.enqueue = function (that, request) { + var originalReq = that.queue[0]; + that.queue[0] = request; + if (originalReq) { + that.events.dropped.fire(originalReq); + } + that.events.enqueued.fire(request); }; /* * A dataSource wrapper providing a queuing mechanism for requests. - * The queue subcomponents, writeQueue (set/delete) and readQueue (get) - * can be configured to use any of the request queue grades. + * Requests are queued based on type (read/write) and resource (directModel). * * A fully implemented dataSource, following the structure outlined by fluid.dataSource, * must be provided in the wrappedDataSource subcomponent. The get, set, and delete methods @@ -500,36 +529,51 @@ var fluid_2_0 = fluid_2_0 || {}; */ fluid.defaults("fluid.queuedDataSource", { gradeNames: ["fluid.standardRelayComponent", "autoInit"], + members: { + requests: { + read: {}, + write: {} + } + }, components: { - writeQueue: { - type: "fluid.requestQueue.fifo" - }, - readQueue: { - type: "fluid.requestQueue.fifo" - }, wrappedDataSource: { // requires a dataSource that implements the standard set, get, and delete methods. type: "fluid.dataSource" } }, + events: { + createRequestQueue: null + }, + // The requestQueueType can take any grade conforming to the structure + // outlined by fluid.requestQueue. + // Modifying the requestQueueType will change the behaviour for all queues. + requestQueueType: "fluid.requestQueue.concurrencyLimited", invokers: { set: { - funcName: "fluid.queuedDataSource.queue", - args: ["{writeQueue}", "{wrappedDataSource}.set", "{arguments}.0", "{arguments}.1", "{arguments}.2"] + funcName: "fluid.queuedDataSource.enqueue", + args: ["{that}.options.requestQueueType", "{that}.requests.write", "{wrappedDataSource}.set", "{arguments}.0", "{arguments}.1", "{arguments}.2"] }, get: { - funcName: "fluid.queuedDataSource.queue", - args: ["{readQueue}", "{wrappedDataSource}.get", "{arguments}.0", null, "{arguments}.1"] + funcName: "fluid.queuedDataSource.enqueue", + args: ["{that}.options.requestQueueType", "{that}.requests.read", "{wrappedDataSource}.get", "{arguments}.0", null, "{arguments}.1"] }, "delete": { - funcName: "fluid.queuedDataSource.queue", - args: ["{writeQueue}", "{wrappedDataSource}.delete", "{arguments}.0", null, "{arguments}.1"] + funcName: "fluid.queuedDataSource.enqueue", + args: ["{that}.options.requestQueueType", "{that}.requests.write", "{wrappedDataSource}.delete", "{arguments}.0", null, "{arguments}.1"] } } }); - fluid.queuedDataSource.queue = function (queue, requestMethod, directModel, model, callback) { - queue.queue({ + fluid.queuedDataSource.enqueue = function (queueType, requestsQueue, requestMethod, directModel, model, callback) { + var key = fluid.toHashKey(directModel); + var queue = requestsQueue[key]; + + if (!queue) { + queue = fluid.invokeGlobalFunction(queueType); + requestsQueue[key] = queue; + } + + queue.enqueue({ method: requestMethod, directModel: directModel, model: model, diff --git a/tests/framework-tests/core/js/DataSourceTests.js b/tests/framework-tests/core/js/DataSourceTests.js index c7213aeaec..b02e1dabd1 100644 --- a/tests/framework-tests/core/js/DataSourceTests.js +++ b/tests/framework-tests/core/js/DataSourceTests.js @@ -17,61 +17,80 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt fluid.registerNamespace("fluid.tests"); + jqUnit.test("fluid.toHashKey", function () { + var singleLevel = { + a: "A", + b: "B", + g: 5, + c: ["D", "E", "F"] + }; + + var nested = { + a: "A", + h: 6, + f: ["F", "G"], + b: { + c: "C", + d: ["D", "E"] + } + }; + + var expected = { + singleLevel: "g:|number 5|>", + nested: ">f:h:|number 6|>" + } + + jqUnit.assertEquals("The single level object should be converted properly", expected.singleLevel, fluid.toHashKey(singleLevel)); + jqUnit.assertEquals("The multi-level object should be converted properly", expected.nested, fluid.toHashKey(nested)); + }); + fluid.defaults("fluid.tests.requestQueue", { gradeNames: ["fluid.requestQueue", "autoInit"], members: { + // Each record should contain a list of the request ID that was sent to the respective events. fireRecord: { - queued: 0, - unqueued: 0, - request: 0, - isActive: { - "true": 0, - "false": 0 - } + enqueued: [], + dropped: [], + onRequestStart: [], + afterRequestComplete: [] } }, listeners: { - "queued": { + "enqueued": { func: "{that}.record", - args: ["queued"] + args: ["enqueued", "{arguments}.0"] }, - "unqueued": { + "dropped": { func: "{that}.record", - args: ["unqueued"] - } - }, - modelListeners: { - "isActive": { - funcName: "{that}.record", - excludeSource: ["init"], - args: ["isActive", "{change}.value"] - } + args: ["dropped", "{arguments}.0"] + }, + "onRequestStart": { + func: "{that}.record", + args: ["onRequestStart", "{arguments}.0"] + }, + "afterRequestComplete": { + func: "{that}.record", + args: ["afterRequestComplete", "{arguments}.0"] + }, }, invokers: { record: { funcName: "fluid.tests.requestQueue.record", args: ["{that}", "{arguments}.0", "{arguments}.1"] }, - request: { - funcName: "fluid.tests.requestQueue.request", - args: ["{that}", "{arguments}.0", "{arguments}.1"] + requestFn: { + funcName: "fluid.tests.requestQueue.requestFn", + args: ["{that}", "{arguments}.0", "{arguments}.1", "{arguments}.2"] } } }); - fluid.tests.requestQueue.record = function (that, recordName, value) { - var path = ["fireRecord", recordName]; - - if (recordName === "isActive") { - path.push(value.toString()); - } - - var count = fluid.get(that, path) + 1; - fluid.set(that, path, count); + fluid.tests.requestQueue.record = function (that, recordName, request) { + var reqID = request.model.ID; + that.fireRecord[recordName].push(reqID); }; - fluid.tests.requestQueue.request = function (that, directModel, callback) { - that.record("request"); + fluid.tests.requestQueue.requestFn = function (that, directModel, model, callback) { // use a setTimeout to simulate an asynchronous request setTimeout(callback, 100); }; @@ -92,58 +111,40 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt }; var triggerQueue = function (currentCycle) { - that.queue({ - method: that.request, - directModel: {model: currentCycle}, + that.enqueue({ + method: that.requestFn, + directModel: {path: ""}, + model: {ID: currentCycle}, callback: assertRequest(currentCycle) }); }; - for (var i = 1; i <= numRequests; i++) { + for (var i = 1; i < numRequests; i++) { triggerQueue(i); } - }; - fluid.defaults("fluid.tests.requestQueue.fifo", { - gradeNames: ["fluid.requestQueue.fifo", "fluid.tests.requestQueue", "autoInit"] - }); + // add a final request that occurs after the first should have completed. + setTimeout(function () { + triggerQueue(numRequests); + }, 200); + }; - jqUnit.asyncTest("Request Queue: FIFO", function () { - fluid.tests.requestQueue.fifo({ - listeners: { - "onCreate.verifyFifoRequestQueue": { - listener: "fluid.tests.verifyRequestQueue", - args: ["{that}", 3, { - queued: 3, - unqueued: 3, - request: 3, - isActive: { - "true": 3, - "false": 3 - } - }] - } - } - }); + fluid.defaults("fluid.tests.requestQueue.concurrencyLimited", { + gradeNames: ["fluid.requestQueue.concurrencyLimited", "fluid.tests.requestQueue", "autoInit"] }); - fluid.defaults("fluid.tests.requestQueue.debounce", { - gradeNames: ["fluid.requestQueue.debounce", "fluid.tests.requestQueue", "autoInit"] - }); + jqUnit.asyncTest("Request Queue: Concurrency Limited", function () { + var request - jqUnit.asyncTest("Request Queue: Debounce", function () { - fluid.tests.requestQueue.debounce({ + fluid.tests.requestQueue.concurrencyLimited({ listeners: { "onCreate.verifyDebounceRequestQueue": { listener: "fluid.tests.verifyRequestQueue", - args: ["{that}", 3, { - queued: 3, - unqueued: 2, - request: 2, - isActive: { - "true": 2, - "false": 2 - } + args: ["{that}", 4, { + enqueued: [1, 2, 3, 4], + dropped: [2], + onRequestStart: [1, 3, 4], + afterRequestComplete: [1, 3, 4] }] } } @@ -172,36 +173,36 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt }; fluid.tests.queuedDataSource.assertRequest = function (directModel) { - jqUnit.assert("The " + directModel.type + " request should have been triggerd"); + jqUnit.assert("The " + directModel.path + " request should have been triggerd"); }; - fluid.tests.queuedDataSource.assertResponseQueued = function (queueName, request) { - jqUnit.assertEquals("The request was added to the correct queue", request.directModel.queue, queueName); + fluid.tests.queuedDataSource.assertRequestsQueue = function (that, expectedReadQueues, expectedWriteQueues) { + var actualReadQueues = fluid.keys(that.requests.read).sort(); + var actualWriteQueues = fluid.keys(that.requests.write).sort(); + + jqUnit.assertDeepEq("The read queue should be populated correctly", expectedReadQueues.sort(), actualReadQueues); + jqUnit.assertDeepEq("The write queue should be populated correctly", expectedWriteQueues.sort(), actualWriteQueues); }; - jqUnit.asyncTest("Queued DataSource", function () { - jqUnit.expect(6); + jqUnit.test("Queued DataSource", function () { + jqUnit.expect(5); + // The expected queues contain a list of the keys for which queues are bound to in the requests object. + var expectedReadQueues = [""]; + var expectedWriteQueues = ["", ""]; fluid.tests.queuedDataSource({ listeners: { - "{writeQueue}.events.queued": { - listener: "fluid.tests.queuedDataSource.assertResponseQueued", - args: ["writeQueue", "{arguments}.0"] - }, - "{readQueue}.events.queued": { - listener: "fluid.tests.queuedDataSource.assertResponseQueued", - args: ["readQueue", "{arguments}.0"] - }, "onCreate": [{ listener: "{that}.get", - args: [{queue: "readQueue", type: "get"}, fluid.tests.queuedDataSource.assertRequest] + args: [{path: "get"}, fluid.tests.queuedDataSource.assertRequest] }, { listener: "{that}.set", - args: [{queue: "writeQueue", type: "set"}, {key: "value"}, fluid.tests.queuedDataSource.assertRequest] + args: [{path: "set"}, {key: "value"}, fluid.tests.queuedDataSource.assertRequest] }, { listener: "{that}.delete", - args: [{queue: "writeQueue", type: "delete"}, fluid.tests.queuedDataSource.assertRequest] + args: [{path: "delete"}, fluid.tests.queuedDataSource.assertRequest] }, { - listener: "jqUnit.start" + listener: "fluid.tests.queuedDataSource.assertRequestsQueue", + args: ["{that}", expectedReadQueues, expectedWriteQueues] }] } }); From 6b19c80cb7ead9778f0ff882cc1e6512bdc601fa Mon Sep 17 00:00:00 2001 From: Justin Obara Date: Thu, 6 Nov 2014 08:44:56 -0500 Subject: [PATCH 12/29] FLUID-5542: Fixed typo --- src/framework/core/js/FluidRequests.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/framework/core/js/FluidRequests.js b/src/framework/core/js/FluidRequests.js index ee362a6c2e..294f8d5c9f 100644 --- a/src/framework/core/js/FluidRequests.js +++ b/src/framework/core/js/FluidRequests.js @@ -462,7 +462,7 @@ var fluid_2_0 = fluid_2_0 || {}; }); // Redirects model changes to explicit request state events, passing along - // the relavent request object. + // the relevent request object. fluid.requestQueue.fireRequestState = function (that, currentReq, previousReq) { if (currentReq) { that.events.onRequestStart.fire(currentReq); From edacdcf3fa604318407638a2bd5277f2a02bcfcf Mon Sep 17 00:00:00 2001 From: Justin Obara Date: Thu, 6 Nov 2014 08:53:55 -0500 Subject: [PATCH 13/29] FLUID-5542: Updated comments --- src/framework/core/js/FluidRequests.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/framework/core/js/FluidRequests.js b/src/framework/core/js/FluidRequests.js index 294f8d5c9f..605e003bc4 100644 --- a/src/framework/core/js/FluidRequests.js +++ b/src/framework/core/js/FluidRequests.js @@ -350,8 +350,9 @@ var fluid_2_0 = fluid_2_0 || {}; * This grade illustrates the expected structure a dataSource, as well as * providing a means for identifying dataSources in a component tree by type. * - * The ultimate purpose of the "dataSource" abstraction is to abstract over - * whether particular functionality is hosted locally or remotely. + * The purpose of the "dataSource" abstraction is to express indexed access + * to state. A REST/CRUD implementation is an example of a DataSource; however, + * it is not limited to this method of interaction. */ fluid.defaults("fluid.dataSource", { gradeNames: ["fluid.eventedComponent", "autoInit"] @@ -360,8 +361,8 @@ var fluid_2_0 = fluid_2_0 || {}; // The "get" and "delete" methods require the signature (directModel, callback). // The "set" method requires the signature (directModel, model, callback) // - // directModel: A JSON summary of the contents of an URL. It expresses an - // "index" into some set of state which can be read or written. + // directModel: A JSON summary expressing an "index" into some set of + // state which can be read or written. // // callback: A function that will be called after the CRUD operation has // returned. From 80c1e6ea891be2f3bddebed587fb12064c94dde2 Mon Sep 17 00:00:00 2001 From: Justin Obara Date: Fri, 7 Nov 2014 10:40:11 -0500 Subject: [PATCH 14/29] FLUID-5542: Interim commit before adding promises Includes the amalgamation of the requestQueue and the queuedDataSource into a single grade. Tests are also forming. --- src/framework/core/js/FluidRequests.js | 365 ++++++++++++------ .../core/js/DataSourceTests.js | 331 ++++++++++------ 2 files changed, 450 insertions(+), 246 deletions(-) diff --git a/src/framework/core/js/FluidRequests.js b/src/framework/core/js/FluidRequests.js index 605e003bc4..834c6e48bc 100644 --- a/src/framework/core/js/FluidRequests.js +++ b/src/framework/core/js/FluidRequests.js @@ -404,83 +404,257 @@ var fluid_2_0 = fluid_2_0 || {}; if(fluid.isPlainObject(val)){ str = "<" + fluid.objectToHashKey(val) + ">"; } else { - str = "|" + typeof(val) + " " + val + "|"; + str = "|" + JSON.stringify(val) + "|"; } return str; }; + // /* + // * A grade defining a requestQueue + // * + // * The basic functionality of a requestQueue is defined. However, the queue + // * invoker must be supplied with a valid queuing function. A request queue is + // * used to manage concurrent requests. + // */ + // fluid.defaults("fluid.requestQueue", { + // gradeNames: ["fluid.standardRelayComponent", "autoInit"], + // events: { + // enqueued: null, + // dropped: null, + // onRequestStart: null, + // afterRequestComplete: null + // }, + // model: { + // request: null + // }, + // members: { + // queue: [], + // }, + // listeners: { + // "afterRequestComplete.start": "{that}.start", + // "enqueued.start": "{that}.start" + // }, + // modelListeners: { + // "request": { + // listener: "fluid.requestQueue.fireRequestState", + // args: ["{that}", "{change}.value", "{change}.oldValue"] + // } + // }, + // invokers: { + // // The enqueue method must be supplied and take the signature + // // (that, request). + // // + // // that: A reference to the component itself + // // + // // request: A JSON object containing the request method and its arguments + // // in the form: + // // { + // // method: function, + // // directModel: object, + // // model: object, + // // callback: function + // // } + // // enqueue: {}, + // start: { + // funcName: "fluid.requestQueue.start", + // args: ["{that}"] + // } + // } + // }); + + // // Redirects model changes to explicit request state events, passing along + // // the relevent request object. + // fluid.requestQueue.fireRequestState = function (that, currentReq, previousReq) { + // if (currentReq) { + // that.events.onRequestStart.fire(currentReq); + // } else if (previousReq) { + // that.events.afterRequestComplete.fire(previousReq); + // } + // } + + // fluid.requestQueue.start = function (that) { + // if (!that.model.request && that.queue.length) { + // var request = that.queue.shift(); + // var callbackProxy = function () { + // that.applier.change("request", null); + // request.callback.apply(null, arguments); + // }; + // + // that.applier.change("request", request); + // if (request.model) { + // request.method(request.directModel, request.model, callbackProxy); + // } else { + // request.method(request.directModel, callbackProxy); + // } + // } + // }; + + // /* + // * A request queue that will only store the latests request in the queue. + // * Intermediate requests that are queued but not invoked, will be replaced + // * by new ones. + // */ + // fluid.defaults("fluid.requestQueue.concurrencyLimited", { + // gradeNames: ["fluid.requestQueue", "autoInit"], + // invokers: { + // enqueue: { + // funcName: "fluid.requestQueue.concurrencyLimited.enqueue", + // args: ["{that}", "{arguments}.0"] + // } + // } + // }); + + // /* + // * Adds only one item to the queue at a time, new requests replace older ones + // * + // * The request object contains the request function and arguments. + // * In the form {method: requestFn, directModel: {}, model: {}, callback: callbackFn} + // */ + // fluid.requestQueue.concurrencyLimited.enqueue = function (that, request) { + // var originalReq = that.queue[0]; + // that.queue[0] = request; + // if (originalReq) { + // that.events.dropped.fire(originalReq); + // } + // that.events.enqueued.fire(request); + // }; + + // /* + // * A dataSource wrapper providing a queuing mechanism for requests. + // * Requests are queued based on type (read/write) and resource (directModel). + // * + // * A fully implemented dataSource, following the structure outlined by fluid.dataSource, + // * must be provided in the wrappedDataSource subcomponent. The get, set, and delete methods + // * found on the queuedDataSource will call their counterparts in the wrappedDataSource, after + // * filtering through the appropriate queue. + // */ + // fluid.defaults("fluid.queuedDataSource", { + // gradeNames: ["fluid.standardRelayComponent", "autoInit"], + // members: { + // requests: { + // read: {}, + // write: {} + // } + // }, + // components: { + // wrappedDataSource: { + // // requires a dataSource that implements the standard set, get, and delete methods. + // type: "fluid.dataSource" + // } + // }, + // events: { + // createRequestQueue: null + // }, + // // The requestQueueType can take any grade conforming to the structure + // // outlined by fluid.requestQueue. + // // Modifying the requestQueueType will change the behaviour for all queues. + // requestQueueType: "fluid.requestQueue.concurrencyLimited", + // invokers: { + // set: { + // funcName: "fluid.queuedDataSource.enqueue", + // args: ["{that}.options.requestQueueType", "{that}.requests.write", "{wrappedDataSource}.set", "{arguments}.0", "{arguments}.1", "{arguments}.2"] + // }, + // get: { + // funcName: "fluid.queuedDataSource.enqueue", + // args: ["{that}.options.requestQueueType", "{that}.requests.read", "{wrappedDataSource}.get", "{arguments}.0", null, "{arguments}.1"] + // }, + // "delete": { + // funcName: "fluid.queuedDataSource.enqueue", + // args: ["{that}.options.requestQueueType", "{that}.requests.write", "{wrappedDataSource}.delete", "{arguments}.0", null, "{arguments}.1"] + // } + // } + // }); + // + // fluid.queuedDataSource.enqueue = function (queueType, requestsQueue, requestMethod, directModel, model, callback) { + // var key = fluid.toHashKey(directModel); + // var queue = requestsQueue[key]; + // + // if (!queue) { + // queue = fluid.invokeGlobalFunction(queueType); + // requestsQueue[key] = queue; + // } + // + // queue.enqueue({ + // method: requestMethod, + // directModel: directModel, + // model: model, + // callback: callback + // }); + // }; + /* - * A grade defining a requestQueue + * A dataSource wrapper providing a queuing mechanism for requests. + * Requests are queued based on type (read/write) and resource (directModel). + * + * A fully implemented dataSource, following the structure outlined by fluid.dataSource, + * must be provided in the wrappedDataSource subcomponent. The get, set, and delete methods + * found on the queuedDataSource will call their counterparts in the wrappedDataSource, after + * filtering through the appropriate queue. * - * The basic functionality of a requestQueue is defined. However, the queue - * invoker must be supplied with a valid queuing function. A request queue is - * used to manage concurrent requests. + * TODO: A fully realized implementation should provide a mechansim for working with a local + * cache. For example pending write requests could be used to service get requests directly. */ - fluid.defaults("fluid.requestQueue", { + fluid.defaults("fluid.queuedDataSource", { gradeNames: ["fluid.standardRelayComponent", "autoInit"], + members: { + requests: { + read: {}, + write: {} + } + }, + components: { + wrappedDataSource: { + // requires a dataSource that implements the standard set, get, and delete methods. + type: "fluid.dataSource" + } + }, events: { enqueued: null, dropped: null, onRequestStart: null, afterRequestComplete: null }, - model: { - request: null - }, - members: { - queue: [], - }, listeners: { - "afterRequestComplete.start": "{that}.start", - "enqueued.start": "{that}.start" - }, - modelListeners: { - "request": { - listener: "fluid.requestQueue.fireRequestState", - args: ["{that}", "{change}.value", "{change}.oldValue"] + "enqueued.start": { + listener: "{that}.start", + args: ["{arguments}.1"] + }, + "afterRequestComplete.start": { + listener: "{that}.start", + args: ["{arguments}.1"] } }, invokers: { - // The enqueue method must be supplied and take the signature - // (that, request). - // - // that: A reference to the component itself - // - // request: A JSON object containing the request method and its arguments - // in the form: - // { - // method: function, - // directModel: object, - // model: object, - // callback: function - // } - // enqueue: {}, + set: { + funcName: "fluid.queuedDataSource.enqueue", + args: ["{that}", "{that}.requests.write", "{wrappedDataSource}.set", "{arguments}.0", "{arguments}.1", "{arguments}.2"] + }, + get: { + funcName: "fluid.queuedDataSource.enqueue", + args: ["{that}", "{that}.requests.read", "{wrappedDataSource}.get", "{arguments}.0", null, "{arguments}.1"] + }, + "delete": { + funcName: "fluid.queuedDataSource.enqueue", + args: ["{that}", "{that}.requests.write", "{wrappedDataSource}.delete", "{arguments}.0", null, "{arguments}.1"] + }, start: { - funcName: "fluid.requestQueue.start", - args: ["{that}"] + funcName: "fluid.queuedDataSource.start", + args: ["{that}", "{arguments}.0"] } } }); - // Redirects model changes to explicit request state events, passing along - // the relevent request object. - fluid.requestQueue.fireRequestState = function (that, currentReq, previousReq) { - if (currentReq) { - that.events.onRequestStart.fire(currentReq); - } else if (previousReq) { - that.events.afterRequestComplete.fire(previousReq); - } - } - - fluid.requestQueue.start = function (that) { - if (!that.model.request && that.queue.length) { - var request = that.queue.shift(); + fluid.queuedDataSource.start = function (that, queue) { + if (!queue.isActive && queue.requests.length) { + var request = queue.requests.shift(); var callbackProxy = function () { - that.applier.change("request", null); + queue.isActive = false; + that.events.afterRequestComplete.fire(request, queue); request.callback.apply(null, arguments); }; - that.applier.change("request", request); + queue.isActive = true; + that.events.onRequestStart.fire(request, queue); if (request.model) { request.method(request.directModel, request.model, callbackProxy); } else { @@ -489,97 +663,40 @@ var fluid_2_0 = fluid_2_0 || {}; } }; - /* - * A request queue that will only store the latests request in the queue. - * Intermediate requests that are queued but not invoked, will be replaced - * by new ones. - */ - fluid.defaults("fluid.requestQueue.concurrencyLimited", { - gradeNames: ["fluid.requestQueue", "autoInit"], - invokers: { - enqueue: { - funcName: "fluid.requestQueue.concurrencyLimited.enqueue", - args: ["{that}", "{arguments}.0"] - } - } - }); - /* * Adds only one item to the queue at a time, new requests replace older ones * * The request object contains the request function and arguments. * In the form {method: requestFn, directModel: {}, model: {}, callback: callbackFn} */ - fluid.requestQueue.concurrencyLimited.enqueue = function (that, request) { - var originalReq = that.queue[0]; - that.queue[0] = request; + fluid.queuedDataSource.enqueueImpl = function (that, request, queue) { + var originalReq = queue.requests[0]; + queue.requests[0] = request; if (originalReq) { - that.events.dropped.fire(originalReq); + that.events.dropped.fire(originalReq, queue); } - that.events.enqueued.fire(request); + that.events.enqueued.fire(request, queue); }; - /* - * A dataSource wrapper providing a queuing mechanism for requests. - * Requests are queued based on type (read/write) and resource (directModel). - * - * A fully implemented dataSource, following the structure outlined by fluid.dataSource, - * must be provided in the wrappedDataSource subcomponent. The get, set, and delete methods - * found on the queuedDataSource will call their counterparts in the wrappedDataSource, after - * filtering through the appropriate queue. - */ - fluid.defaults("fluid.queuedDataSource", { - gradeNames: ["fluid.standardRelayComponent", "autoInit"], - members: { - requests: { - read: {}, - write: {} - } - }, - components: { - wrappedDataSource: { - // requires a dataSource that implements the standard set, get, and delete methods. - type: "fluid.dataSource" - } - }, - events: { - createRequestQueue: null - }, - // The requestQueueType can take any grade conforming to the structure - // outlined by fluid.requestQueue. - // Modifying the requestQueueType will change the behaviour for all queues. - requestQueueType: "fluid.requestQueue.concurrencyLimited", - invokers: { - set: { - funcName: "fluid.queuedDataSource.enqueue", - args: ["{that}.options.requestQueueType", "{that}.requests.write", "{wrappedDataSource}.set", "{arguments}.0", "{arguments}.1", "{arguments}.2"] - }, - get: { - funcName: "fluid.queuedDataSource.enqueue", - args: ["{that}.options.requestQueueType", "{that}.requests.read", "{wrappedDataSource}.get", "{arguments}.0", null, "{arguments}.1"] - }, - "delete": { - funcName: "fluid.queuedDataSource.enqueue", - args: ["{that}.options.requestQueueType", "{that}.requests.write", "{wrappedDataSource}.delete", "{arguments}.0", null, "{arguments}.1"] - } - } - }); - - fluid.queuedDataSource.enqueue = function (queueType, requestsQueue, requestMethod, directModel, model, callback) { + fluid.queuedDataSource.enqueue = function (that, requestsQueue, requestMethod, directModel, model, callback) { var key = fluid.toHashKey(directModel); var queue = requestsQueue[key]; + var request = { + method: requestMethod, + directModel: directModel, + model: model, + callback: callback + }; if (!queue) { - queue = fluid.invokeGlobalFunction(queueType); + queue = { + isActive: false, + requests: [] + }; requestsQueue[key] = queue; } - queue.enqueue({ - method: requestMethod, - directModel: directModel, - model: model, - callback: callback - }); + fluid.queuedDataSource.enqueueImpl(that, request, queue); }; /** End dataSource **/ diff --git a/tests/framework-tests/core/js/DataSourceTests.js b/tests/framework-tests/core/js/DataSourceTests.js index b02e1dabd1..5bb24c2eea 100644 --- a/tests/framework-tests/core/js/DataSourceTests.js +++ b/tests/framework-tests/core/js/DataSourceTests.js @@ -36,18 +36,183 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt }; var expected = { - singleLevel: "g:|number 5|>", - nested: ">f:h:|number 6|>" + singleLevel: "g:|5|>", + nested: ">f:h:|6|>" } jqUnit.assertEquals("The single level object should be converted properly", expected.singleLevel, fluid.toHashKey(singleLevel)); jqUnit.assertEquals("The multi-level object should be converted properly", expected.nested, fluid.toHashKey(nested)); }); - fluid.defaults("fluid.tests.requestQueue", { - gradeNames: ["fluid.requestQueue", "autoInit"], + // fluid.defaults("fluid.tests.requestQueue", { + // gradeNames: ["fluid.requestQueue", "autoInit"], + // members: { + // // Each record should contain a list of the request ID that was sent to the respective events. + // fireRecord: { + // enqueued: [], + // dropped: [], + // onRequestStart: [], + // afterRequestComplete: [] + // } + // }, + // listeners: { + // "enqueued": { + // func: "{that}.record", + // args: ["enqueued", "{arguments}.0"] + // }, + // "dropped": { + // func: "{that}.record", + // args: ["dropped", "{arguments}.0"] + // }, + // "onRequestStart": { + // func: "{that}.record", + // args: ["onRequestStart", "{arguments}.0"] + // }, + // "afterRequestComplete": { + // func: "{that}.record", + // args: ["afterRequestComplete", "{arguments}.0"] + // }, + // }, + // invokers: { + // record: { + // funcName: "fluid.tests.requestQueue.record", + // args: ["{that}", "{arguments}.0", "{arguments}.1"] + // }, + // requestFn: { + // funcName: "fluid.tests.requestQueue.requestFn", + // args: ["{that}", "{arguments}.0", "{arguments}.1", "{arguments}.2"] + // } + // } + // }); + // + // fluid.tests.requestQueue.record = function (that, recordName, request) { + // var reqID = request.model.ID; + // that.fireRecord[recordName].push(reqID); + // }; + // + // fluid.tests.requestQueue.requestFn = function (that, directModel, model, callback) { + // // use a setTimeout to simulate an asynchronous request + // setTimeout(callback, 100); + // }; + // + // fluid.tests.verifyRequestQueue = function (that, numRequests, expectedRecord, cleanup) { + // cleanup = cleanup || jqUnit.start; + // var previousCallNum = 0; + // + // var assertRequest = function (callNum) { + // return function () { + // jqUnit.assertTrue("Set call " + callNum + " should be triggered in the correct order", callNum > previousCallNum); + // previousCallNum = callNum; + // if (callNum === numRequests) { + // jqUnit.assertDeepEq("The event record should have been updated appropriately.", expectedRecord, that.fireRecord); + // cleanup(); + // } + // }; + // }; + // + // var triggerQueue = function (currentCycle) { + // that.enqueue({ + // method: that.requestFn, + // directModel: {path: ""}, + // model: {ID: currentCycle}, + // callback: assertRequest(currentCycle) + // }); + // }; + // + // for (var i = 1; i < numRequests; i++) { + // triggerQueue(i); + // } + // + // // add a final request that occurs after the first should have completed. + // setTimeout(function () { + // triggerQueue(numRequests); + // }, 200); + // }; + // + // fluid.defaults("fluid.tests.requestQueue.concurrencyLimited", { + // gradeNames: ["fluid.requestQueue.concurrencyLimited", "fluid.tests.requestQueue", "autoInit"] + // }); + // + // jqUnit.asyncTest("Request Queue: Concurrency Limited", function () { + // var request + // + // fluid.tests.requestQueue.concurrencyLimited({ + // listeners: { + // "onCreate.verifyDebounceRequestQueue": { + // listener: "fluid.tests.verifyRequestQueue", + // args: ["{that}", 4, { + // enqueued: [1, 2, 3, 4], + // dropped: [2], + // onRequestStart: [1, 3, 4], + // afterRequestComplete: [1, 3, 4] + // }] + // } + // } + // }); + // }); + // + // fluid.defaults("fluid.tests.queuedDataSource", { + // gradeNames: ["fluid.queuedDataSource", "autoInit"], + // components: { + // wrappedDataSource: { + // options: { + // invokers: { + // "get": "fluid.tests.queuedDataSource.request", + // "set": "fluid.tests.queuedDataSource.request", + // "delete": "fluid.tests.queuedDataSource.request" + // } + // } + // } + // } + // }); + // + // fluid.tests.queuedDataSource.request = function () { + // var callback = arguments[arguments.length -1]; + // var directModel = arguments[0]; + // callback(directModel); + // }; + // + // fluid.tests.queuedDataSource.assertRequest = function (directModel) { + // jqUnit.assert("The " + directModel.path + " request should have been triggerd"); + // }; + // + // fluid.tests.queuedDataSource.assertRequestsQueue = function (that, expectedReadQueues, expectedWriteQueues) { + // var actualReadQueues = fluid.keys(that.requests.read).sort(); + // var actualWriteQueues = fluid.keys(that.requests.write).sort(); + // + // jqUnit.assertDeepEq("The read queue should be populated correctly", expectedReadQueues.sort(), actualReadQueues); + // jqUnit.assertDeepEq("The write queue should be populated correctly", expectedWriteQueues.sort(), actualWriteQueues); + // }; + // + // jqUnit.test("Queued DataSource", function () { + // jqUnit.expect(5); + // // The expected queues contain a list of the keys for which queues are bound to in the requests object. + // var expectedReadQueues = [""]; + // var expectedWriteQueues = ["", ""]; + // fluid.tests.queuedDataSource({ + // listeners: { + // "onCreate": [{ + // listener: "{that}.get", + // args: [{path: "get"}, fluid.tests.queuedDataSource.assertRequest] + // }, { + // listener: "{that}.set", + // args: [{path: "set"}, {key: "value"}, fluid.tests.queuedDataSource.assertRequest] + // }, { + // listener: "{that}.delete", + // args: [{path: "delete"}, fluid.tests.queuedDataSource.assertRequest] + // }, { + // listener: "fluid.tests.queuedDataSource.assertRequestsQueue", + // args: ["{that}", expectedReadQueues, expectedWriteQueues] + // }] + // } + // }); + // }); + + fluid.defaults("fluid.tests.queuedDataSource", { + gradeNames: ["fluid.queuedDataSource", "autoInit"], members: { - // Each record should contain a list of the request ID that was sent to the respective events. + // Each record should contain a list of the directModels from the requests + // that were sent to the respective events. fireRecord: { enqueued: [], dropped: [], @@ -56,153 +221,75 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt } }, listeners: { - "enqueued": { - func: "{that}.record", - args: ["enqueued", "{arguments}.0"] - }, - "dropped": { - func: "{that}.record", - args: ["dropped", "{arguments}.0"] - }, - "onRequestStart": { - func: "{that}.record", - args: ["onRequestStart", "{arguments}.0"] + "enqueued.test": { + listener: "fluid.tests.queuedDataSource.record", + args: ["{that}.fireRecord.enqueued", "{arguments}.0"] }, - "afterRequestComplete": { - func: "{that}.record", - args: ["afterRequestComplete", "{arguments}.0"] + "dropped.test": { + listener: "fluid.tests.queuedDataSource.record", + args: ["{that}.fireRecord.dropped", "{arguments}.0"] }, - }, - invokers: { - record: { - funcName: "fluid.tests.requestQueue.record", - args: ["{that}", "{arguments}.0", "{arguments}.1"] + "onRequestStart.test": { + listener: "fluid.tests.queuedDataSource.record", + args: ["{that}.fireRecord.onRequestStart", "{arguments}.0"] }, - requestFn: { - funcName: "fluid.tests.requestQueue.requestFn", - args: ["{that}", "{arguments}.0", "{arguments}.1", "{arguments}.2"] - } - } - }); - - fluid.tests.requestQueue.record = function (that, recordName, request) { - var reqID = request.model.ID; - that.fireRecord[recordName].push(reqID); - }; - - fluid.tests.requestQueue.requestFn = function (that, directModel, model, callback) { - // use a setTimeout to simulate an asynchronous request - setTimeout(callback, 100); - }; - - fluid.tests.verifyRequestQueue = function (that, numRequests, expectedRecord, cleanup) { - cleanup = cleanup || jqUnit.start; - var previousCallNum = 0; - - var assertRequest = function (callNum) { - return function () { - jqUnit.assertTrue("Set call " + callNum + " should be triggered in the correct order", callNum > previousCallNum); - previousCallNum = callNum; - if (callNum === numRequests) { - jqUnit.assertDeepEq("The event record should have been updated appropriately.", expectedRecord, that.fireRecord); - cleanup(); - } - }; - }; - - var triggerQueue = function (currentCycle) { - that.enqueue({ - method: that.requestFn, - directModel: {path: ""}, - model: {ID: currentCycle}, - callback: assertRequest(currentCycle) - }); - }; - - for (var i = 1; i < numRequests; i++) { - triggerQueue(i); - } - - // add a final request that occurs after the first should have completed. - setTimeout(function () { - triggerQueue(numRequests); - }, 200); - }; - - fluid.defaults("fluid.tests.requestQueue.concurrencyLimited", { - gradeNames: ["fluid.requestQueue.concurrencyLimited", "fluid.tests.requestQueue", "autoInit"] - }); - - jqUnit.asyncTest("Request Queue: Concurrency Limited", function () { - var request - - fluid.tests.requestQueue.concurrencyLimited({ - listeners: { - "onCreate.verifyDebounceRequestQueue": { - listener: "fluid.tests.verifyRequestQueue", - args: ["{that}", 4, { - enqueued: [1, 2, 3, 4], - dropped: [2], - onRequestStart: [1, 3, 4], - afterRequestComplete: [1, 3, 4] - }] - } + "afterRequestComplete.test": { + listener: "fluid.tests.queuedDataSource.record", + args: ["{that}.fireRecord.afterRequestComplete", "{arguments}.0"] } - }); - }); - - fluid.defaults("fluid.tests.queuedDataSource", { - gradeNames: ["fluid.queuedDataSource", "autoInit"], + }, components: { wrappedDataSource: { options: { invokers: { - "get": "fluid.tests.queuedDataSource.request", - "set": "fluid.tests.queuedDataSource.request", - "delete": "fluid.tests.queuedDataSource.request" + "get": "fluid.tests.queuedDataSource.requestFn", + "set": "fluid.tests.queuedDataSource.requestSetFn", + "delete": "fluid.tests.queuedDataSource.requestFn" } } } } }); - fluid.tests.queuedDataSource.request = function () { - var callback = arguments[arguments.length -1]; - var directModel = arguments[0]; - callback(directModel); + fluid.tests.queuedDataSource.record = function (record, request) { + record.push(request.directModel); }; - fluid.tests.queuedDataSource.assertRequest = function (directModel) { - jqUnit.assert("The " + directModel.path + " request should have been triggerd"); + fluid.tests.queuedDataSource.requestFn = function (directModel, callback) { + // use a setTimeout to simulate an asynchronous request + // setTimeout(callback, 100); + callback(directModel); }; - fluid.tests.queuedDataSource.assertRequestsQueue = function (that, expectedReadQueues, expectedWriteQueues) { - var actualReadQueues = fluid.keys(that.requests.read).sort(); - var actualWriteQueues = fluid.keys(that.requests.write).sort(); + fluid.tests.queuedDataSource.requestSetFn = function (directModel, model, callback) { + // use a setTimeout to simulate an asynchronous request + // setTimeout(callback, 100); + callback(directModel, model); + }; - jqUnit.assertDeepEq("The read queue should be populated correctly", expectedReadQueues.sort(), actualReadQueues); - jqUnit.assertDeepEq("The write queue should be populated correctly", expectedWriteQueues.sort(), actualWriteQueues); + fluid.tests.queuedDataSource.assertRequests = function (that, expectedRequests) { + jqUnit.assertDeepEq("The requests should have been processed through the queue corectly.", expectedRequests, that.fireRecord); + jqUnit.start(); }; - jqUnit.test("Queued DataSource", function () { - jqUnit.expect(5); - // The expected queues contain a list of the keys for which queues are bound to in the requests object. - var expectedReadQueues = [""]; - var expectedWriteQueues = ["", ""]; + jqUnit.asyncTest("Queued DataSource", function () { fluid.tests.queuedDataSource({ listeners: { "onCreate": [{ listener: "{that}.get", - args: [{path: "get"}, fluid.tests.queuedDataSource.assertRequest] + args: [{path: "get"}, fluid.identity] }, { listener: "{that}.set", - args: [{path: "set"}, {key: "value"}, fluid.tests.queuedDataSource.assertRequest] + args: [{path: "set"}, {key: "value"}, fluid.identity] }, { listener: "{that}.delete", - args: [{path: "delete"}, fluid.tests.queuedDataSource.assertRequest] + args: [{path: "delete"}, fluid.identity] }, { - listener: "fluid.tests.queuedDataSource.assertRequestsQueue", - args: ["{that}", expectedReadQueues, expectedWriteQueues] + // listener: "fluid.tests.queuedDataSource.assertRequestsQueue", + // args: ["{that}", expectedReadQueues, expectedWriteQueues] + // }, { + listener: "fluid.tests.queuedDataSource.assertRequests", + args: ["{that}", {}] }] } }); From 27532db4e9091a5ee31157864e6dc810142a6485 Mon Sep 17 00:00:00 2001 From: Justin Obara Date: Mon, 10 Nov 2014 10:49:07 -0500 Subject: [PATCH 15/29] FLUID-5542: refactored to use promises Tests have been updated and extra comments removed. --- src/framework/core/js/FluidRequests.js | 256 +++--------- .../core/html/DataSource-test.html | 1 + .../core/js/DataSourceTests.js | 363 ++++++++---------- 3 files changed, 219 insertions(+), 401 deletions(-) diff --git a/src/framework/core/js/FluidRequests.js b/src/framework/core/js/FluidRequests.js index 476c864bad..6ee26e2c11 100644 --- a/src/framework/core/js/FluidRequests.js +++ b/src/framework/core/js/FluidRequests.js @@ -356,19 +356,22 @@ var fluid_2_0 = fluid_2_0 || {}; */ fluid.defaults("fluid.dataSource", { gradeNames: ["fluid.eventedComponent", "autoInit"] - // Invokers should be defined for the typical CRUD functions. + // Invokers should be defined for the typical CRUD functions and return + // a promise. // - // The "get" and "delete" methods require the signature (directModel, callback). - // The "set" method requires the signature (directModel, model, callback) + // The "get" and "delete" methods require the signature (directModel). + // The "set" method requires the signature (directModel, model) // - // directModel: A JSON summary expressing an "index" into some set of + // directModel: An object expressing an "index" into some set of // state which can be read or written. // - // callback: A function that will be called after the CRUD operation has - // returned. - // // model: The payload sent to the storage. // + // options: An object expressing implementation specific details + // regarding the handling of a request. Note: this does not + // include details for identifying the resource. Those should be + // placed in the directModel. + // // invokers: { // "get": {}, // handles the Read function // "set": {}, // handles the Create and Update functions @@ -409,179 +412,6 @@ var fluid_2_0 = fluid_2_0 || {}; return str; }; - // /* - // * A grade defining a requestQueue - // * - // * The basic functionality of a requestQueue is defined. However, the queue - // * invoker must be supplied with a valid queuing function. A request queue is - // * used to manage concurrent requests. - // */ - // fluid.defaults("fluid.requestQueue", { - // gradeNames: ["fluid.standardRelayComponent", "autoInit"], - // events: { - // enqueued: null, - // dropped: null, - // onRequestStart: null, - // afterRequestComplete: null - // }, - // model: { - // request: null - // }, - // members: { - // queue: [], - // }, - // listeners: { - // "afterRequestComplete.start": "{that}.start", - // "enqueued.start": "{that}.start" - // }, - // modelListeners: { - // "request": { - // listener: "fluid.requestQueue.fireRequestState", - // args: ["{that}", "{change}.value", "{change}.oldValue"] - // } - // }, - // invokers: { - // // The enqueue method must be supplied and take the signature - // // (that, request). - // // - // // that: A reference to the component itself - // // - // // request: A JSON object containing the request method and its arguments - // // in the form: - // // { - // // method: function, - // // directModel: object, - // // model: object, - // // callback: function - // // } - // // enqueue: {}, - // start: { - // funcName: "fluid.requestQueue.start", - // args: ["{that}"] - // } - // } - // }); - - // // Redirects model changes to explicit request state events, passing along - // // the relevent request object. - // fluid.requestQueue.fireRequestState = function (that, currentReq, previousReq) { - // if (currentReq) { - // that.events.onRequestStart.fire(currentReq); - // } else if (previousReq) { - // that.events.afterRequestComplete.fire(previousReq); - // } - // } - - // fluid.requestQueue.start = function (that) { - // if (!that.model.request && that.queue.length) { - // var request = that.queue.shift(); - // var callbackProxy = function () { - // that.applier.change("request", null); - // request.callback.apply(null, arguments); - // }; - // - // that.applier.change("request", request); - // if (request.model) { - // request.method(request.directModel, request.model, callbackProxy); - // } else { - // request.method(request.directModel, callbackProxy); - // } - // } - // }; - - // /* - // * A request queue that will only store the latests request in the queue. - // * Intermediate requests that are queued but not invoked, will be replaced - // * by new ones. - // */ - // fluid.defaults("fluid.requestQueue.concurrencyLimited", { - // gradeNames: ["fluid.requestQueue", "autoInit"], - // invokers: { - // enqueue: { - // funcName: "fluid.requestQueue.concurrencyLimited.enqueue", - // args: ["{that}", "{arguments}.0"] - // } - // } - // }); - - // /* - // * Adds only one item to the queue at a time, new requests replace older ones - // * - // * The request object contains the request function and arguments. - // * In the form {method: requestFn, directModel: {}, model: {}, callback: callbackFn} - // */ - // fluid.requestQueue.concurrencyLimited.enqueue = function (that, request) { - // var originalReq = that.queue[0]; - // that.queue[0] = request; - // if (originalReq) { - // that.events.dropped.fire(originalReq); - // } - // that.events.enqueued.fire(request); - // }; - - // /* - // * A dataSource wrapper providing a queuing mechanism for requests. - // * Requests are queued based on type (read/write) and resource (directModel). - // * - // * A fully implemented dataSource, following the structure outlined by fluid.dataSource, - // * must be provided in the wrappedDataSource subcomponent. The get, set, and delete methods - // * found on the queuedDataSource will call their counterparts in the wrappedDataSource, after - // * filtering through the appropriate queue. - // */ - // fluid.defaults("fluid.queuedDataSource", { - // gradeNames: ["fluid.standardRelayComponent", "autoInit"], - // members: { - // requests: { - // read: {}, - // write: {} - // } - // }, - // components: { - // wrappedDataSource: { - // // requires a dataSource that implements the standard set, get, and delete methods. - // type: "fluid.dataSource" - // } - // }, - // events: { - // createRequestQueue: null - // }, - // // The requestQueueType can take any grade conforming to the structure - // // outlined by fluid.requestQueue. - // // Modifying the requestQueueType will change the behaviour for all queues. - // requestQueueType: "fluid.requestQueue.concurrencyLimited", - // invokers: { - // set: { - // funcName: "fluid.queuedDataSource.enqueue", - // args: ["{that}.options.requestQueueType", "{that}.requests.write", "{wrappedDataSource}.set", "{arguments}.0", "{arguments}.1", "{arguments}.2"] - // }, - // get: { - // funcName: "fluid.queuedDataSource.enqueue", - // args: ["{that}.options.requestQueueType", "{that}.requests.read", "{wrappedDataSource}.get", "{arguments}.0", null, "{arguments}.1"] - // }, - // "delete": { - // funcName: "fluid.queuedDataSource.enqueue", - // args: ["{that}.options.requestQueueType", "{that}.requests.write", "{wrappedDataSource}.delete", "{arguments}.0", null, "{arguments}.1"] - // } - // } - // }); - // - // fluid.queuedDataSource.enqueue = function (queueType, requestsQueue, requestMethod, directModel, model, callback) { - // var key = fluid.toHashKey(directModel); - // var queue = requestsQueue[key]; - // - // if (!queue) { - // queue = fluid.invokeGlobalFunction(queueType); - // requestsQueue[key] = queue; - // } - // - // queue.enqueue({ - // method: requestMethod, - // directModel: directModel, - // model: model, - // callback: callback - // }); - // }; - /* * A dataSource wrapper providing a queuing mechanism for requests. * Requests are queued based on type (read/write) and resource (directModel). @@ -626,16 +456,16 @@ var fluid_2_0 = fluid_2_0 || {}; }, invokers: { set: { - funcName: "fluid.queuedDataSource.enqueue", + funcName: "fluid.queuedDataSource.enqueueWithModel", args: ["{that}", "{that}.requests.write", "{wrappedDataSource}.set", "{arguments}.0", "{arguments}.1", "{arguments}.2"] }, get: { funcName: "fluid.queuedDataSource.enqueue", - args: ["{that}", "{that}.requests.read", "{wrappedDataSource}.get", "{arguments}.0", null, "{arguments}.1"] + args: ["{that}", "{that}.requests.read", "{wrappedDataSource}.get", "{arguments}.0", "{arguments}.1"] }, "delete": { funcName: "fluid.queuedDataSource.enqueue", - args: ["{that}", "{that}.requests.write", "{wrappedDataSource}.delete", "{arguments}.0", null, "{arguments}.1"] + args: ["{that}", "{that}.requests.write", "{wrappedDataSource}.delete", "{arguments}.0", "{arguments}.1"] }, start: { funcName: "fluid.queuedDataSource.start", @@ -646,20 +476,24 @@ var fluid_2_0 = fluid_2_0 || {}; fluid.queuedDataSource.start = function (that, queue) { if (!queue.isActive && queue.requests.length) { + var promise = fluid.promise(); var request = queue.requests.shift(); - var callbackProxy = function () { + + var requestComplete = function () { queue.isActive = false; that.events.afterRequestComplete.fire(request, queue); - request.callback.apply(null, arguments); }; queue.isActive = true; that.events.onRequestStart.fire(request, queue); - if (request.model) { - request.method(request.directModel, request.model, callbackProxy); - } else { - request.method(request.directModel, callbackProxy); - } + + var args = "model" in request ? [request.directModel, request.model, request.options] : [request.directModel, request.options]; + var response = request.method.apply(null, args); + + response.then(requestComplete, requestComplete); + fluid.promise.follow(response, promise); + + return promise; } }; @@ -669,7 +503,19 @@ var fluid_2_0 = fluid_2_0 || {}; * The request object contains the request function and arguments. * In the form {method: requestFn, directModel: {}, model: {}, callback: callbackFn} */ - fluid.queuedDataSource.enqueueImpl = function (that, request, queue) { + fluid.queuedDataSource.enqueueImpl = function (that, requestsQueue, request) { + var key = fluid.toHashKey(request.directModel); + var queue = requestsQueue[key]; + + // create a queue if one doesn't already exist + if (!queue) { + queue = { + isActive: false, + requests: [] + }; + requestsQueue[key] = queue; + } + var originalReq = queue.requests[0]; queue.requests[0] = request; if (originalReq) { @@ -678,25 +524,25 @@ var fluid_2_0 = fluid_2_0 || {}; that.events.enqueued.fire(request, queue); }; - fluid.queuedDataSource.enqueue = function (that, requestsQueue, requestMethod, directModel, model, callback) { - var key = fluid.toHashKey(directModel); - var queue = requestsQueue[key]; + fluid.queuedDataSource.enqueue = function (that, requestsQueue, method, directModel, options) { var request = { - method: requestMethod, + method: method, directModel: directModel, - model: model, - callback: callback + options: options }; - if (!queue) { - queue = { - isActive: false, - requests: [] - }; - requestsQueue[key] = queue; - } + fluid.queuedDataSource.enqueueImpl(that, requestsQueue, request); + }; + + fluid.queuedDataSource.enqueueWithModel = function (that, requestsQueue, method, directModel, model, options) { + var request = { + method: method, + directModel: directModel, + model: model, + options: options + }; - fluid.queuedDataSource.enqueueImpl(that, request, queue); + fluid.queuedDataSource.enqueueImpl(that, requestsQueue, request); }; /** End dataSource **/ diff --git a/tests/framework-tests/core/html/DataSource-test.html b/tests/framework-tests/core/html/DataSource-test.html index 24207f2760..bbe9fb08e0 100644 --- a/tests/framework-tests/core/html/DataSource-test.html +++ b/tests/framework-tests/core/html/DataSource-test.html @@ -9,6 +9,7 @@ + diff --git a/tests/framework-tests/core/js/DataSourceTests.js b/tests/framework-tests/core/js/DataSourceTests.js index 5bb24c2eea..22be72d2ea 100644 --- a/tests/framework-tests/core/js/DataSourceTests.js +++ b/tests/framework-tests/core/js/DataSourceTests.js @@ -44,170 +44,6 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt jqUnit.assertEquals("The multi-level object should be converted properly", expected.nested, fluid.toHashKey(nested)); }); - // fluid.defaults("fluid.tests.requestQueue", { - // gradeNames: ["fluid.requestQueue", "autoInit"], - // members: { - // // Each record should contain a list of the request ID that was sent to the respective events. - // fireRecord: { - // enqueued: [], - // dropped: [], - // onRequestStart: [], - // afterRequestComplete: [] - // } - // }, - // listeners: { - // "enqueued": { - // func: "{that}.record", - // args: ["enqueued", "{arguments}.0"] - // }, - // "dropped": { - // func: "{that}.record", - // args: ["dropped", "{arguments}.0"] - // }, - // "onRequestStart": { - // func: "{that}.record", - // args: ["onRequestStart", "{arguments}.0"] - // }, - // "afterRequestComplete": { - // func: "{that}.record", - // args: ["afterRequestComplete", "{arguments}.0"] - // }, - // }, - // invokers: { - // record: { - // funcName: "fluid.tests.requestQueue.record", - // args: ["{that}", "{arguments}.0", "{arguments}.1"] - // }, - // requestFn: { - // funcName: "fluid.tests.requestQueue.requestFn", - // args: ["{that}", "{arguments}.0", "{arguments}.1", "{arguments}.2"] - // } - // } - // }); - // - // fluid.tests.requestQueue.record = function (that, recordName, request) { - // var reqID = request.model.ID; - // that.fireRecord[recordName].push(reqID); - // }; - // - // fluid.tests.requestQueue.requestFn = function (that, directModel, model, callback) { - // // use a setTimeout to simulate an asynchronous request - // setTimeout(callback, 100); - // }; - // - // fluid.tests.verifyRequestQueue = function (that, numRequests, expectedRecord, cleanup) { - // cleanup = cleanup || jqUnit.start; - // var previousCallNum = 0; - // - // var assertRequest = function (callNum) { - // return function () { - // jqUnit.assertTrue("Set call " + callNum + " should be triggered in the correct order", callNum > previousCallNum); - // previousCallNum = callNum; - // if (callNum === numRequests) { - // jqUnit.assertDeepEq("The event record should have been updated appropriately.", expectedRecord, that.fireRecord); - // cleanup(); - // } - // }; - // }; - // - // var triggerQueue = function (currentCycle) { - // that.enqueue({ - // method: that.requestFn, - // directModel: {path: ""}, - // model: {ID: currentCycle}, - // callback: assertRequest(currentCycle) - // }); - // }; - // - // for (var i = 1; i < numRequests; i++) { - // triggerQueue(i); - // } - // - // // add a final request that occurs after the first should have completed. - // setTimeout(function () { - // triggerQueue(numRequests); - // }, 200); - // }; - // - // fluid.defaults("fluid.tests.requestQueue.concurrencyLimited", { - // gradeNames: ["fluid.requestQueue.concurrencyLimited", "fluid.tests.requestQueue", "autoInit"] - // }); - // - // jqUnit.asyncTest("Request Queue: Concurrency Limited", function () { - // var request - // - // fluid.tests.requestQueue.concurrencyLimited({ - // listeners: { - // "onCreate.verifyDebounceRequestQueue": { - // listener: "fluid.tests.verifyRequestQueue", - // args: ["{that}", 4, { - // enqueued: [1, 2, 3, 4], - // dropped: [2], - // onRequestStart: [1, 3, 4], - // afterRequestComplete: [1, 3, 4] - // }] - // } - // } - // }); - // }); - // - // fluid.defaults("fluid.tests.queuedDataSource", { - // gradeNames: ["fluid.queuedDataSource", "autoInit"], - // components: { - // wrappedDataSource: { - // options: { - // invokers: { - // "get": "fluid.tests.queuedDataSource.request", - // "set": "fluid.tests.queuedDataSource.request", - // "delete": "fluid.tests.queuedDataSource.request" - // } - // } - // } - // } - // }); - // - // fluid.tests.queuedDataSource.request = function () { - // var callback = arguments[arguments.length -1]; - // var directModel = arguments[0]; - // callback(directModel); - // }; - // - // fluid.tests.queuedDataSource.assertRequest = function (directModel) { - // jqUnit.assert("The " + directModel.path + " request should have been triggerd"); - // }; - // - // fluid.tests.queuedDataSource.assertRequestsQueue = function (that, expectedReadQueues, expectedWriteQueues) { - // var actualReadQueues = fluid.keys(that.requests.read).sort(); - // var actualWriteQueues = fluid.keys(that.requests.write).sort(); - // - // jqUnit.assertDeepEq("The read queue should be populated correctly", expectedReadQueues.sort(), actualReadQueues); - // jqUnit.assertDeepEq("The write queue should be populated correctly", expectedWriteQueues.sort(), actualWriteQueues); - // }; - // - // jqUnit.test("Queued DataSource", function () { - // jqUnit.expect(5); - // // The expected queues contain a list of the keys for which queues are bound to in the requests object. - // var expectedReadQueues = [""]; - // var expectedWriteQueues = ["", ""]; - // fluid.tests.queuedDataSource({ - // listeners: { - // "onCreate": [{ - // listener: "{that}.get", - // args: [{path: "get"}, fluid.tests.queuedDataSource.assertRequest] - // }, { - // listener: "{that}.set", - // args: [{path: "set"}, {key: "value"}, fluid.tests.queuedDataSource.assertRequest] - // }, { - // listener: "{that}.delete", - // args: [{path: "delete"}, fluid.tests.queuedDataSource.assertRequest] - // }, { - // listener: "fluid.tests.queuedDataSource.assertRequestsQueue", - // args: ["{that}", expectedReadQueues, expectedWriteQueues] - // }] - // } - // }); - // }); - fluid.defaults("fluid.tests.queuedDataSource", { gradeNames: ["fluid.queuedDataSource", "autoInit"], members: { @@ -252,46 +88,181 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt }); fluid.tests.queuedDataSource.record = function (record, request) { - record.push(request.directModel); + record.push(request.options.ID); }; - fluid.tests.queuedDataSource.requestFn = function (directModel, callback) { - // use a setTimeout to simulate an asynchronous request - // setTimeout(callback, 100); - callback(directModel); + fluid.tests.queuedDataSource.requestImpl = function (request, duration) { + var promise = fluid.promise(); + setTimeout(function () { + promise.resolve(request); + }, duration); + return promise; }; - fluid.tests.queuedDataSource.requestSetFn = function (directModel, model, callback) { - // use a setTimeout to simulate an asynchronous request - // setTimeout(callback, 100); - callback(directModel, model); + fluid.tests.queuedDataSource.requestFn = function (directModel, options) { + var request = { + directModel: directModel, + options: options + }; + return fluid.tests.queuedDataSource.requestImpl(request, options.duration); }; - fluid.tests.queuedDataSource.assertRequests = function (that, expectedRequests) { - jqUnit.assertDeepEq("The requests should have been processed through the queue corectly.", expectedRequests, that.fireRecord); - jqUnit.start(); + fluid.tests.queuedDataSource.requestSetFn = function (directModel, model, options) { + var request = { + directModel: directModel, + model: model, + options: options + }; + return fluid.tests.queuedDataSource.requestImpl(request, options.duration); }; - jqUnit.asyncTest("Queued DataSource", function () { - fluid.tests.queuedDataSource({ - listeners: { - "onCreate": [{ - listener: "{that}.get", - args: [{path: "get"}, fluid.identity] - }, { - listener: "{that}.set", - args: [{path: "set"}, {key: "value"}, fluid.identity] - }, { - listener: "{that}.delete", - args: [{path: "delete"}, fluid.identity] - }, { - // listener: "fluid.tests.queuedDataSource.assertRequestsQueue", - // args: ["{that}", expectedReadQueues, expectedWriteQueues] - // }, { - listener: "fluid.tests.queuedDataSource.assertRequests", - args: ["{that}", {}] - }] - } + fluid.tests.invokeWithDelay = function (requests) { + requests = fluid.makeArray(requests); + + fluid.each(requests, function (request) { + setTimeout(function () { + request.method.apply(null, request.args || []); + }, request.delay || 0); }); + }; + + jqUnit.asyncTest("Queued DataSource - get", function () { + var that = fluid.tests.queuedDataSource(); + var expectedRecord = { + "enqueued": ["get1", "get2", "get3"], + "dropped": ["get2"], + "onRequestStart": ["get1", "get3"], + "afterRequestComplete": ["get1", "get3"] + }; + + var requests = [{ + method: that.get, + delay: 0, + args: [{"path": "get"}, {"ID": "get1", "duration": 100}] + }, { + method: that.get, + delay: 20, + args: [{"path": "get"}, {"ID": "get2", "duration": 100}] + }, { + method: that.get, + delay: 50, + args: [{"path": "get"}, {"ID": "get3", "duration": 100}] + }]; + + fluid.tests.invokeWithDelay(requests); + + setTimeout(function () { + jqUnit.assertDeepEq("The requests should have been processed through the read queue correctly.", expectedRecord, that.fireRecord); + jqUnit.start(); + }, 300); + }); + + jqUnit.asyncTest("Queued DataSource - set", function () { + var that = fluid.tests.queuedDataSource(); + var expectedRecord = { + "enqueued": ["set1", "set2", "set3", "set4"], + "dropped": ["set2", "set3"], + "onRequestStart": ["set1", "set4"], + "afterRequestComplete": ["set1", "set4"] + }; + + var requests = [{ + method: that.set, + delay: 0, + args: [{"path": "set"}, {"key": "value1"}, {"ID": "set1", "duration": 50}] + }, { + method: that.set, + delay: 0, + args: [{"path": "set"}, {"key": "value2"}, {"ID": "set2", "duration": 50}] + }, { + method: that.set, + delay: 0, + args: [{"path": "set"}, {"key": "value2"}, {"ID": "set3", "duration": 50}] + }, { + method: that.set, + delay: 25, + args: [{"path": "set"}, {"key": "value3"}, {"ID": "set4", "duration": 50}] + }]; + + fluid.tests.invokeWithDelay(requests); + + setTimeout(function () { + jqUnit.assertDeepEq("The requests should have been processed through the write queue correctly.", expectedRecord, that.fireRecord); + jqUnit.start(); + }, 200); + }); + + jqUnit.asyncTest("Queued DataSource - delete", function () { + var that = fluid.tests.queuedDataSource(); + var expectedRecord = { + "enqueued": ["delete1", "delete2", "delete3", "delete4"], + "dropped": ["delete3"], + "onRequestStart": ["delete1", "delete2", "delete4"], + "afterRequestComplete": ["delete1", "delete2", "delete4"] + }; + + var requests = [{ + method: that.delete, + delay: 0, + args: [{"path": "delete"}, {"ID": "delete1", "duration": 50}] + }, { + method: that.delete, + delay: 60, + args: [{"path": "delete"}, {"ID": "delete2", "duration": 50}] + }, { + method: that.delete, + delay: 75, + duration: 20, + args: [{"path": "delete"}, {"ID": "delete3", "duration": 50}] + }, { + method: that.delete, + delay: 90, + args: [{"path": "delete"}, {"ID": "delete4", "duration": 50}] + }]; + + fluid.tests.invokeWithDelay(requests); + + setTimeout(function () { + jqUnit.assertDeepEq("The requests should have been processed through the write queue correctly.", expectedRecord, that.fireRecord); + jqUnit.start(); + }, 200); + }); + + jqUnit.asyncTest("Queued DataSource - combined", function () { + var that = fluid.tests.queuedDataSource(); + var expectedRecord = { + "enqueued": ["get1", "set1", "delete1", "set2"], + "dropped": ["delete1"], + "onRequestStart": ["get1", "set1", "set2"], + "afterRequestComplete": ["get1", "set1", "set2"] + }; + + var requests = [{ + method: that.get, + delay: 0, + duration: 10, + args: [{"path": "combined"}, {"ID": "get1", "duration": 50}] + }, { + method: that.set, + delay: 0, + duration: 10, + args: [{"path": "combined"}, {"key": "value1"}, {"ID": "set1", "duration": 100}] + }, { + method: that.delete, + delay: 50, + args: [{"path": "combined"}, {"ID": "delete1", "duration": 50}] + }, { + method: that.set, + delay: 60, + duration: 10, + args: [{"path": "combined"}, {"key": "value2"}, {"ID": "set2", "duration": 100}] + }]; + + fluid.tests.invokeWithDelay(requests); + + setTimeout(function () { + jqUnit.assertDeepEq("The requests should have been processed through the write queue correctly.", expectedRecord, that.fireRecord); + jqUnit.start(); + }, 300); }); })(); From f07ec548758877676796cef4374c0797fd179f41 Mon Sep 17 00:00:00 2001 From: Justin Obara Date: Mon, 10 Nov 2014 11:56:59 -0500 Subject: [PATCH 16/29] FLUID-5542: Linting --- tests/framework-tests/core/js/DataSourceTests.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/framework-tests/core/js/DataSourceTests.js b/tests/framework-tests/core/js/DataSourceTests.js index 22be72d2ea..957991c807 100644 --- a/tests/framework-tests/core/js/DataSourceTests.js +++ b/tests/framework-tests/core/js/DataSourceTests.js @@ -38,7 +38,7 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt var expected = { singleLevel: "g:|5|>", nested: ">f:h:|6|>" - } + }; jqUnit.assertEquals("The single level object should be converted properly", expected.singleLevel, fluid.toHashKey(singleLevel)); jqUnit.assertEquals("The multi-level object should be converted properly", expected.nested, fluid.toHashKey(nested)); @@ -202,20 +202,20 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt }; var requests = [{ - method: that.delete, + method: that["delete"], delay: 0, args: [{"path": "delete"}, {"ID": "delete1", "duration": 50}] }, { - method: that.delete, + method: that["delete"], delay: 60, args: [{"path": "delete"}, {"ID": "delete2", "duration": 50}] }, { - method: that.delete, + method: that["delete"], delay: 75, duration: 20, args: [{"path": "delete"}, {"ID": "delete3", "duration": 50}] }, { - method: that.delete, + method: that["delete"], delay: 90, args: [{"path": "delete"}, {"ID": "delete4", "duration": 50}] }]; @@ -248,7 +248,7 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt duration: 10, args: [{"path": "combined"}, {"key": "value1"}, {"ID": "set1", "duration": 100}] }, { - method: that.delete, + method: that["delete"], delay: 50, args: [{"path": "combined"}, {"ID": "delete1", "duration": 50}] }, { From ac6d291899ac75bbcf22957f88c5bd42b53d6311 Mon Sep 17 00:00:00 2001 From: Justin Obara Date: Mon, 10 Nov 2014 13:28:05 -0500 Subject: [PATCH 17/29] FLUID-5542: Changed base grade Changed the base grade of fluid.queuedDataSource to "fluid.eventedComponent" --- src/framework/core/js/FluidRequests.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/framework/core/js/FluidRequests.js b/src/framework/core/js/FluidRequests.js index 6ee26e2c11..49627db30d 100644 --- a/src/framework/core/js/FluidRequests.js +++ b/src/framework/core/js/FluidRequests.js @@ -425,7 +425,7 @@ var fluid_2_0 = fluid_2_0 || {}; * cache. For example pending write requests could be used to service get requests directly. */ fluid.defaults("fluid.queuedDataSource", { - gradeNames: ["fluid.standardRelayComponent", "autoInit"], + gradeNames: ["fluid.eventedComponent", "autoInit"], members: { requests: { read: {}, From bb4fab561c710216a09df2878f6f07d401385b71 Mon Sep 17 00:00:00 2001 From: Justin Obara Date: Fri, 14 Nov 2014 10:11:07 -0500 Subject: [PATCH 18/29] FLUID-5542: Fixed returning promises + unit tests Fixed a bug where the promise wasn't being returned back by the queued dataSources request methods. Refactored the unit tests. --- src/framework/core/js/FluidRequests.js | 25 +- .../core/js/DataSourceTests.js | 283 ++++++------------ 2 files changed, 106 insertions(+), 202 deletions(-) diff --git a/src/framework/core/js/FluidRequests.js b/src/framework/core/js/FluidRequests.js index 49627db30d..fe712741dc 100644 --- a/src/framework/core/js/FluidRequests.js +++ b/src/framework/core/js/FluidRequests.js @@ -440,8 +440,6 @@ var fluid_2_0 = fluid_2_0 || {}; }, events: { enqueued: null, - dropped: null, - onRequestStart: null, afterRequestComplete: null }, listeners: { @@ -476,7 +474,6 @@ var fluid_2_0 = fluid_2_0 || {}; fluid.queuedDataSource.start = function (that, queue) { if (!queue.isActive && queue.requests.length) { - var promise = fluid.promise(); var request = queue.requests.shift(); var requestComplete = function () { @@ -485,15 +482,11 @@ var fluid_2_0 = fluid_2_0 || {}; }; queue.isActive = true; - that.events.onRequestStart.fire(request, queue); - var args = "model" in request ? [request.directModel, request.model, request.options] : [request.directModel, request.options]; var response = request.method.apply(null, args); response.then(requestComplete, requestComplete); - fluid.promise.follow(response, promise); - - return promise; + fluid.promise.follow(response, request.promise); } }; @@ -504,9 +497,15 @@ var fluid_2_0 = fluid_2_0 || {}; * In the form {method: requestFn, directModel: {}, model: {}, callback: callbackFn} */ fluid.queuedDataSource.enqueueImpl = function (that, requestsQueue, request) { + var promise = fluid.promise(); var key = fluid.toHashKey(request.directModel); var queue = requestsQueue[key]; + // add promise to the request object + // to be resolved in the start method + // when the wrapped dataSource's request returns. + request.promise = promise; + // create a queue if one doesn't already exist if (!queue) { queue = { @@ -516,12 +515,10 @@ var fluid_2_0 = fluid_2_0 || {}; requestsQueue[key] = queue; } - var originalReq = queue.requests[0]; queue.requests[0] = request; - if (originalReq) { - that.events.dropped.fire(originalReq, queue); - } that.events.enqueued.fire(request, queue); + + return promise; }; fluid.queuedDataSource.enqueue = function (that, requestsQueue, method, directModel, options) { @@ -531,7 +528,7 @@ var fluid_2_0 = fluid_2_0 || {}; options: options }; - fluid.queuedDataSource.enqueueImpl(that, requestsQueue, request); + return fluid.queuedDataSource.enqueueImpl(that, requestsQueue, request); }; fluid.queuedDataSource.enqueueWithModel = function (that, requestsQueue, method, directModel, model, options) { @@ -542,7 +539,7 @@ var fluid_2_0 = fluid_2_0 || {}; options: options }; - fluid.queuedDataSource.enqueueImpl(that, requestsQueue, request); + return fluid.queuedDataSource.enqueueImpl(that, requestsQueue, request); }; /** End dataSource **/ diff --git a/tests/framework-tests/core/js/DataSourceTests.js b/tests/framework-tests/core/js/DataSourceTests.js index 957991c807..f33f9e8838 100644 --- a/tests/framework-tests/core/js/DataSourceTests.js +++ b/tests/framework-tests/core/js/DataSourceTests.js @@ -44,225 +44,132 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt jqUnit.assertEquals("The multi-level object should be converted properly", expected.nested, fluid.toHashKey(nested)); }); + fluid.defaults("fluid.tests.dataSource", { + gradeNames: ["fluid.dataSource", "autoInit"], + invokers: { + "get": { + funcName: "fluid.tests.dataSource.request", + args: ["get", "{arguments}.0", "{arguments}.1"] + }, + "set": { + funcName: "fluid.tests.dataSource.request", + args: ["set", "{arguments}.0", "{arguments}.2", "{arguments}.1"] + }, + "delete": { + funcName: "fluid.tests.dataSource.request", + args: ["delete", "{arguments}.0", "{arguments}.1"] + } + } + }); + + fluid.tests.dataSource.request = function (type, directModel, options, model) { + var promise = fluid.promise(); + var ID = fluid.get(model, "ID") || options.ID; + setTimeout(function () { + promise.resolve({ + "requestType": type, + "ID": ID + }); + }, options.duration); + return promise; + }; + fluid.defaults("fluid.tests.queuedDataSource", { gradeNames: ["fluid.queuedDataSource", "autoInit"], members: { // Each record should contain a list of the directModels from the requests // that were sent to the respective events. fireRecord: { - enqueued: [], - dropped: [], - onRequestStart: [], - afterRequestComplete: [] - } - }, - listeners: { - "enqueued.test": { - listener: "fluid.tests.queuedDataSource.record", - args: ["{that}.fireRecord.enqueued", "{arguments}.0"] - }, - "dropped.test": { - listener: "fluid.tests.queuedDataSource.record", - args: ["{that}.fireRecord.dropped", "{arguments}.0"] - }, - "onRequestStart.test": { - listener: "fluid.tests.queuedDataSource.record", - args: ["{that}.fireRecord.onRequestStart", "{arguments}.0"] - }, - "afterRequestComplete.test": { - listener: "fluid.tests.queuedDataSource.record", - args: ["{that}.fireRecord.afterRequestComplete", "{arguments}.0"] + "get": [], + "set": [], + "delete": [] } }, components: { wrappedDataSource: { - options: { - invokers: { - "get": "fluid.tests.queuedDataSource.requestFn", - "set": "fluid.tests.queuedDataSource.requestSetFn", - "delete": "fluid.tests.queuedDataSource.requestFn" - } - } + type: "fluid.tests.dataSource" } } }); - fluid.tests.queuedDataSource.record = function (record, request) { - record.push(request.options.ID); + fluid.tests.invokeRequestWithDelay = function (that, type, delays, duration, directModel) { + var count = 0; + fluid.each(delays, function (delay) { + setTimeout(function () { + var response; + if (type === "set") { + response = that[type](directModel, {ID: ++count}, {duration: duration}); + } else { + response = that[type](directModel, { + duration: duration, + ID: ++count + }); + } + response.then(function (val) { + that.fireRecord[val.requestType].push(val.ID); + }); + }, delay); + }); }; - fluid.tests.queuedDataSource.requestImpl = function (request, duration) { - var promise = fluid.promise(); - setTimeout(function () { - promise.resolve(request); - }, duration); - return promise; + // s = short delay + // l = long delay + fluid.tests.requestRuns = { + ss: [0, 50, 55], + sl: [0, 50, 200], + ls: [0, 150, 200], + ll: [0, 150, 300] }; - fluid.tests.queuedDataSource.requestFn = function (directModel, options) { - var request = { - directModel: directModel, - options: options - }; - return fluid.tests.queuedDataSource.requestImpl(request, options.duration); + fluid.tests.expedted100ms = { + ss: [1, 3], + sl: [1, 2, 3], + ls: [1, 2, 3], + ll: [1, 2, 3] }; - fluid.tests.queuedDataSource.requestSetFn = function (directModel, model, options) { - var request = { - directModel: directModel, - model: model, - options: options - }; - return fluid.tests.queuedDataSource.requestImpl(request, options.duration); - }; + fluid.tests.assertRequest = function (requestType, setName, delays, expected) { + var promise = fluid.promise(); + var that = fluid.tests.queuedDataSource(); + var assertionDelay = delays[delays.length - 1] + 200; // time to wait to assert the requests. - fluid.tests.invokeWithDelay = function (requests) { - requests = fluid.makeArray(requests); + fluid.tests.invokeRequestWithDelay(that, requestType, delays, 100, {path: "value"}); - fluid.each(requests, function (request) { - setTimeout(function () { - request.method.apply(null, request.args || []); - }, request.delay || 0); + setTimeout(function () { + jqUnit.assertDeepEq("The " + requestType + " requests from set '" + setName + "' should have been queud correctly.", expected, that.fireRecord[requestType]); + promise.resolve(that); + }, assertionDelay); + return promise; + }; + + fluid.tests.assertDelayedRequests = function (requestType, delaySet, expectedSet) { + var count = 0; + var promise = fluid.promise(); + var numSets = fluid.keys(delaySet).length; + fluid.each(delaySet, function (delay, set) { + var response = fluid.tests.assertRequest(requestType, set, delay, expectedSet[set]); + response.then(function () { + count++; + if (count >= numSets) { + promise.resolve(); + } + }); }); + return promise; }; jqUnit.asyncTest("Queued DataSource - get", function () { - var that = fluid.tests.queuedDataSource(); - var expectedRecord = { - "enqueued": ["get1", "get2", "get3"], - "dropped": ["get2"], - "onRequestStart": ["get1", "get3"], - "afterRequestComplete": ["get1", "get3"] - }; - - var requests = [{ - method: that.get, - delay: 0, - args: [{"path": "get"}, {"ID": "get1", "duration": 100}] - }, { - method: that.get, - delay: 20, - args: [{"path": "get"}, {"ID": "get2", "duration": 100}] - }, { - method: that.get, - delay: 50, - args: [{"path": "get"}, {"ID": "get3", "duration": 100}] - }]; - - fluid.tests.invokeWithDelay(requests); - - setTimeout(function () { - jqUnit.assertDeepEq("The requests should have been processed through the read queue correctly.", expectedRecord, that.fireRecord); - jqUnit.start(); - }, 300); + var response = fluid.tests.assertDelayedRequests("get", fluid.tests.requestRuns, fluid.tests.expedted100ms); + response.then(jqUnit.start); }); jqUnit.asyncTest("Queued DataSource - set", function () { - var that = fluid.tests.queuedDataSource(); - var expectedRecord = { - "enqueued": ["set1", "set2", "set3", "set4"], - "dropped": ["set2", "set3"], - "onRequestStart": ["set1", "set4"], - "afterRequestComplete": ["set1", "set4"] - }; - - var requests = [{ - method: that.set, - delay: 0, - args: [{"path": "set"}, {"key": "value1"}, {"ID": "set1", "duration": 50}] - }, { - method: that.set, - delay: 0, - args: [{"path": "set"}, {"key": "value2"}, {"ID": "set2", "duration": 50}] - }, { - method: that.set, - delay: 0, - args: [{"path": "set"}, {"key": "value2"}, {"ID": "set3", "duration": 50}] - }, { - method: that.set, - delay: 25, - args: [{"path": "set"}, {"key": "value3"}, {"ID": "set4", "duration": 50}] - }]; - - fluid.tests.invokeWithDelay(requests); - - setTimeout(function () { - jqUnit.assertDeepEq("The requests should have been processed through the write queue correctly.", expectedRecord, that.fireRecord); - jqUnit.start(); - }, 200); + var response = fluid.tests.assertDelayedRequests("set", fluid.tests.requestRuns, fluid.tests.expedted100ms); + response.then(jqUnit.start); }); jqUnit.asyncTest("Queued DataSource - delete", function () { - var that = fluid.tests.queuedDataSource(); - var expectedRecord = { - "enqueued": ["delete1", "delete2", "delete3", "delete4"], - "dropped": ["delete3"], - "onRequestStart": ["delete1", "delete2", "delete4"], - "afterRequestComplete": ["delete1", "delete2", "delete4"] - }; - - var requests = [{ - method: that["delete"], - delay: 0, - args: [{"path": "delete"}, {"ID": "delete1", "duration": 50}] - }, { - method: that["delete"], - delay: 60, - args: [{"path": "delete"}, {"ID": "delete2", "duration": 50}] - }, { - method: that["delete"], - delay: 75, - duration: 20, - args: [{"path": "delete"}, {"ID": "delete3", "duration": 50}] - }, { - method: that["delete"], - delay: 90, - args: [{"path": "delete"}, {"ID": "delete4", "duration": 50}] - }]; - - fluid.tests.invokeWithDelay(requests); - - setTimeout(function () { - jqUnit.assertDeepEq("The requests should have been processed through the write queue correctly.", expectedRecord, that.fireRecord); - jqUnit.start(); - }, 200); - }); - - jqUnit.asyncTest("Queued DataSource - combined", function () { - var that = fluid.tests.queuedDataSource(); - var expectedRecord = { - "enqueued": ["get1", "set1", "delete1", "set2"], - "dropped": ["delete1"], - "onRequestStart": ["get1", "set1", "set2"], - "afterRequestComplete": ["get1", "set1", "set2"] - }; - - var requests = [{ - method: that.get, - delay: 0, - duration: 10, - args: [{"path": "combined"}, {"ID": "get1", "duration": 50}] - }, { - method: that.set, - delay: 0, - duration: 10, - args: [{"path": "combined"}, {"key": "value1"}, {"ID": "set1", "duration": 100}] - }, { - method: that["delete"], - delay: 50, - args: [{"path": "combined"}, {"ID": "delete1", "duration": 50}] - }, { - method: that.set, - delay: 60, - duration: 10, - args: [{"path": "combined"}, {"key": "value2"}, {"ID": "set2", "duration": 100}] - }]; - - fluid.tests.invokeWithDelay(requests); - - setTimeout(function () { - jqUnit.assertDeepEq("The requests should have been processed through the write queue correctly.", expectedRecord, that.fireRecord); - jqUnit.start(); - }, 300); + var response = fluid.tests.assertDelayedRequests("delete", fluid.tests.requestRuns, fluid.tests.expedted100ms); + response.then(jqUnit.start); }); })(); From f8865db9f0d46e862e11b3671c6b5a84f32058ed Mon Sep 17 00:00:00 2001 From: Justin Obara Date: Wed, 19 Nov 2014 08:56:00 -0500 Subject: [PATCH 19/29] FLUID-5542: Corrected typos --- tests/framework-tests/core/js/DataSourceTests.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/framework-tests/core/js/DataSourceTests.js b/tests/framework-tests/core/js/DataSourceTests.js index f33f9e8838..f564eb9797 100644 --- a/tests/framework-tests/core/js/DataSourceTests.js +++ b/tests/framework-tests/core/js/DataSourceTests.js @@ -121,7 +121,7 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt ll: [0, 150, 300] }; - fluid.tests.expedted100ms = { + fluid.tests.expected100ms = { ss: [1, 3], sl: [1, 2, 3], ls: [1, 2, 3], @@ -136,7 +136,7 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt fluid.tests.invokeRequestWithDelay(that, requestType, delays, 100, {path: "value"}); setTimeout(function () { - jqUnit.assertDeepEq("The " + requestType + " requests from set '" + setName + "' should have been queud correctly.", expected, that.fireRecord[requestType]); + jqUnit.assertDeepEq("The " + requestType + " requests from set '" + setName + "' should have been queued correctly.", expected, that.fireRecord[requestType]); promise.resolve(that); }, assertionDelay); return promise; @@ -159,17 +159,17 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt }; jqUnit.asyncTest("Queued DataSource - get", function () { - var response = fluid.tests.assertDelayedRequests("get", fluid.tests.requestRuns, fluid.tests.expedted100ms); + var response = fluid.tests.assertDelayedRequests("get", fluid.tests.requestRuns, fluid.tests.expected100ms); response.then(jqUnit.start); }); jqUnit.asyncTest("Queued DataSource - set", function () { - var response = fluid.tests.assertDelayedRequests("set", fluid.tests.requestRuns, fluid.tests.expedted100ms); + var response = fluid.tests.assertDelayedRequests("set", fluid.tests.requestRuns, fluid.tests.expected100ms); response.then(jqUnit.start); }); jqUnit.asyncTest("Queued DataSource - delete", function () { - var response = fluid.tests.assertDelayedRequests("delete", fluid.tests.requestRuns, fluid.tests.expedted100ms); + var response = fluid.tests.assertDelayedRequests("delete", fluid.tests.requestRuns, fluid.tests.expected100ms); response.then(jqUnit.start); }); })(); From 942976a7625c426ed41ee3d62d030e255bfd5a5e Mon Sep 17 00:00:00 2001 From: Justin Obara Date: Wed, 19 Nov 2014 09:00:49 -0500 Subject: [PATCH 20/29] FLUID-5542: removed quotes around some keys --- tests/framework-tests/core/js/DataSourceTests.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/framework-tests/core/js/DataSourceTests.js b/tests/framework-tests/core/js/DataSourceTests.js index f564eb9797..a885cffad1 100644 --- a/tests/framework-tests/core/js/DataSourceTests.js +++ b/tests/framework-tests/core/js/DataSourceTests.js @@ -67,8 +67,8 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt var ID = fluid.get(model, "ID") || options.ID; setTimeout(function () { promise.resolve({ - "requestType": type, - "ID": ID + requestType: type, + ID: ID }); }, options.duration); return promise; From c62458d8d80ee84d08e21674e1c0579e37a3ff7b Mon Sep 17 00:00:00 2001 From: Justin Obara Date: Wed, 19 Nov 2014 09:04:35 -0500 Subject: [PATCH 21/29] FLUID-5542: configured extra delay buffer Added an explanation for the extra 200 added to the assertionDelay and made this configurable through an argument passed to the function. --- tests/framework-tests/core/js/DataSourceTests.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/framework-tests/core/js/DataSourceTests.js b/tests/framework-tests/core/js/DataSourceTests.js index a885cffad1..ac5ac64802 100644 --- a/tests/framework-tests/core/js/DataSourceTests.js +++ b/tests/framework-tests/core/js/DataSourceTests.js @@ -128,10 +128,11 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt ll: [1, 2, 3] }; - fluid.tests.assertRequest = function (requestType, setName, delays, expected) { + fluid.tests.assertRequest = function (requestType, setName, delays, expected, delayBuffer) { + delayBuffer = delayBuffer || 200; // time in milliseconds to add to the assertion delay, to buffer against setTimeout impressions. var promise = fluid.promise(); var that = fluid.tests.queuedDataSource(); - var assertionDelay = delays[delays.length - 1] + 200; // time to wait to assert the requests. + var assertionDelay = delays[delays.length - 1] + delayBuffer; // time to wait to assert the requests. fluid.tests.invokeRequestWithDelay(that, requestType, delays, 100, {path: "value"}); @@ -142,12 +143,12 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt return promise; }; - fluid.tests.assertDelayedRequests = function (requestType, delaySet, expectedSet) { + fluid.tests.assertDelayedRequests = function (requestType, delaySet, expectedSet, delayBuffer) { var count = 0; var promise = fluid.promise(); var numSets = fluid.keys(delaySet).length; fluid.each(delaySet, function (delay, set) { - var response = fluid.tests.assertRequest(requestType, set, delay, expectedSet[set]); + var response = fluid.tests.assertRequest(requestType, set, delay, expectedSet[set], delayBuffer); response.then(function () { count++; if (count >= numSets) { From a4440df1674240e2be2c7c97a35a2cdc4dae1a79 Mon Sep 17 00:00:00 2001 From: Justin Obara Date: Wed, 19 Nov 2014 09:16:22 -0500 Subject: [PATCH 22/29] FLUID-5542: Added extra examples to test --- .../core/js/DataSourceTests.js | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/tests/framework-tests/core/js/DataSourceTests.js b/tests/framework-tests/core/js/DataSourceTests.js index ac5ac64802..c5a6d577c2 100644 --- a/tests/framework-tests/core/js/DataSourceTests.js +++ b/tests/framework-tests/core/js/DataSourceTests.js @@ -118,18 +118,33 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt ss: [0, 50, 55], sl: [0, 50, 200], ls: [0, 150, 200], - ll: [0, 150, 300] + ll: [0, 150, 300], + sss: [0, 50, 55, 75], + lss: [0, 150, 200, 220], + ssl: [0, 50, 75, 200], + ssss: [0, 50, 55, 75, 80], + lsss: [0, 150, 200, 220, 250], + sssl: [0, 50, 60, 75, 200] }; fluid.tests.expected100ms = { ss: [1, 3], sl: [1, 2, 3], ls: [1, 2, 3], - ll: [1, 2, 3] + ll: [1, 2, 3], + sss: [1, 4], + lss: [1, 2, 4], + ssl: [1, 3, 4], + ssss: [1, 5], + lsss: [1, 2, 5], + sssl: [1, 4, 5] }; fluid.tests.assertRequest = function (requestType, setName, delays, expected, delayBuffer) { - delayBuffer = delayBuffer || 200; // time in milliseconds to add to the assertion delay, to buffer against setTimeout impressions. + // Time in milliseconds to add to the assertion delay, to buffer against setTimeout impressions. + // Because multiple instances run simultaneously this number may need to be increased to take into + // account extra delays related to the thread blocking. + delayBuffer = delayBuffer || 300; var promise = fluid.promise(); var that = fluid.tests.queuedDataSource(); var assertionDelay = delays[delays.length - 1] + delayBuffer; // time to wait to assert the requests. From 454ea1c71599d09dc1bc4c05b0adf30662bf41d3 Mon Sep 17 00:00:00 2001 From: Justin Obara Date: Fri, 12 Dec 2014 13:54:11 -0500 Subject: [PATCH 23/29] FLUID-5542: expanded test namespace Increased the test namespace to prevent future collisions. --- tests/framework-tests/core/js/DataSourceTests.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/framework-tests/core/js/DataSourceTests.js b/tests/framework-tests/core/js/DataSourceTests.js index c5a6d577c2..b1a5f84991 100644 --- a/tests/framework-tests/core/js/DataSourceTests.js +++ b/tests/framework-tests/core/js/DataSourceTests.js @@ -114,7 +114,7 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt // s = short delay // l = long delay - fluid.tests.requestRuns = { + fluid.tests.queuedDataSource.requestRuns = { ss: [0, 50, 55], sl: [0, 50, 200], ls: [0, 150, 200], @@ -127,7 +127,7 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt sssl: [0, 50, 60, 75, 200] }; - fluid.tests.expected100ms = { + fluid.tests.queuedDataSource.expected100ms = { ss: [1, 3], sl: [1, 2, 3], ls: [1, 2, 3], @@ -140,7 +140,7 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt sssl: [1, 4, 5] }; - fluid.tests.assertRequest = function (requestType, setName, delays, expected, delayBuffer) { + fluid.tests.queuedDataSource.assertRequest = function (requestType, setName, delays, expected, delayBuffer) { // Time in milliseconds to add to the assertion delay, to buffer against setTimeout impressions. // Because multiple instances run simultaneously this number may need to be increased to take into // account extra delays related to the thread blocking. @@ -158,12 +158,12 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt return promise; }; - fluid.tests.assertDelayedRequests = function (requestType, delaySet, expectedSet, delayBuffer) { + fluid.tests.queuedDataSource.assertDelayedRequests = function (requestType, delaySet, expectedSet, delayBuffer) { var count = 0; var promise = fluid.promise(); var numSets = fluid.keys(delaySet).length; fluid.each(delaySet, function (delay, set) { - var response = fluid.tests.assertRequest(requestType, set, delay, expectedSet[set], delayBuffer); + var response = fluid.tests.queuedDataSource.assertRequest(requestType, set, delay, expectedSet[set], delayBuffer); response.then(function () { count++; if (count >= numSets) { @@ -175,17 +175,17 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt }; jqUnit.asyncTest("Queued DataSource - get", function () { - var response = fluid.tests.assertDelayedRequests("get", fluid.tests.requestRuns, fluid.tests.expected100ms); + var response = fluid.tests.queuedDataSource.assertDelayedRequests("get", fluid.tests.queuedDataSource.requestRuns, fluid.tests.queuedDataSource.expected100ms); response.then(jqUnit.start); }); jqUnit.asyncTest("Queued DataSource - set", function () { - var response = fluid.tests.assertDelayedRequests("set", fluid.tests.requestRuns, fluid.tests.expected100ms); + var response = fluid.tests.queuedDataSource.assertDelayedRequests("set", fluid.tests.queuedDataSource.requestRuns, fluid.tests.queuedDataSource.expected100ms); response.then(jqUnit.start); }); jqUnit.asyncTest("Queued DataSource - delete", function () { - var response = fluid.tests.assertDelayedRequests("delete", fluid.tests.requestRuns, fluid.tests.expected100ms); + var response = fluid.tests.queuedDataSource.assertDelayedRequests("delete", fluid.tests.queuedDataSource.requestRuns, fluid.tests.queuedDataSource.expected100ms); response.then(jqUnit.start); }); })(); From fc2a1d6eadd7b7586f5d5e117f5fda6dc04e7bfc Mon Sep 17 00:00:00 2001 From: Justin Obara Date: Tue, 16 Jun 2015 15:45:57 -0400 Subject: [PATCH 24/29] FLUID-5542: Reducing duplicate code in tests --- .../core/js/DataSourceTests.js | 49 +++++++++---------- 1 file changed, 22 insertions(+), 27 deletions(-) diff --git a/tests/framework-tests/core/js/DataSourceTests.js b/tests/framework-tests/core/js/DataSourceTests.js index b1a5f84991..f5f04eb4f7 100644 --- a/tests/framework-tests/core/js/DataSourceTests.js +++ b/tests/framework-tests/core/js/DataSourceTests.js @@ -140,6 +140,17 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt sssl: [1, 4, 5] }; + fluid.tests.queuedDataSource.testRequests = [{ + requestType: "get", + source: fluid.tests.queuedDataSource.expected100ms + }, { + requestType: "set", + source: fluid.tests.queuedDataSource.expected100ms + }, { + requestType: "delete", + source: fluid.tests.queuedDataSource.expected100ms + }]; + fluid.tests.queuedDataSource.assertRequest = function (requestType, setName, delays, expected, delayBuffer) { // Time in milliseconds to add to the assertion delay, to buffer against setTimeout impressions. // Because multiple instances run simultaneously this number may need to be increased to take into @@ -158,34 +169,18 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt return promise; }; - fluid.tests.queuedDataSource.assertDelayedRequests = function (requestType, delaySet, expectedSet, delayBuffer) { - var count = 0; - var promise = fluid.promise(); - var numSets = fluid.keys(delaySet).length; - fluid.each(delaySet, function (delay, set) { - var response = fluid.tests.queuedDataSource.assertRequest(requestType, set, delay, expectedSet[set], delayBuffer); - response.then(function () { - count++; - if (count >= numSets) { - promise.resolve(); - } + // Note: Due to the timeouts used to simulate actual asynchronous operations, this test + // will take a while to execute. + jqUnit.asyncTest("Queued DataSource", function () { + var sources = []; + fluid.each(fluid.tests.queuedDataSource.testRequests, function (testRequest) { + fluid.each(fluid.tests.queuedDataSource.requestRuns, function (delays, setName) { + sources.push(function () { + return fluid.tests.queuedDataSource.assertRequest(testRequest.requestType, setName, delays, testRequest.source[setName]); + }); }); }); - return promise; - }; - - jqUnit.asyncTest("Queued DataSource - get", function () { - var response = fluid.tests.queuedDataSource.assertDelayedRequests("get", fluid.tests.queuedDataSource.requestRuns, fluid.tests.queuedDataSource.expected100ms); - response.then(jqUnit.start); - }); - - jqUnit.asyncTest("Queued DataSource - set", function () { - var response = fluid.tests.queuedDataSource.assertDelayedRequests("set", fluid.tests.queuedDataSource.requestRuns, fluid.tests.queuedDataSource.expected100ms); - response.then(jqUnit.start); - }); - - jqUnit.asyncTest("Queued DataSource - delete", function () { - var response = fluid.tests.queuedDataSource.assertDelayedRequests("delete", fluid.tests.queuedDataSource.requestRuns, fluid.tests.queuedDataSource.expected100ms); - response.then(jqUnit.start); + var testSequence = fluid.promise.sequence(sources); + testSequence.then(jqUnit.start); }); })(); From 21803c5b9159948a166081ccc52743c6d941b4ce Mon Sep 17 00:00:00 2001 From: Justin Obara Date: Mon, 22 Jun 2015 10:23:52 -0400 Subject: [PATCH 25/29] FLUID-5542: Split base grade from implantation Split off the debounce queue methods from the queuedDataSource base grade and attached these in a debouncedDataSource grade. --- src/framework/core/js/FluidRequests.js | 72 ++++++++++++------- .../core/js/DataSourceTests.js | 28 ++++---- 2 files changed, 60 insertions(+), 40 deletions(-) diff --git a/src/framework/core/js/FluidRequests.js b/src/framework/core/js/FluidRequests.js index fdeca7e029..48672bec6a 100644 --- a/src/framework/core/js/FluidRequests.js +++ b/src/framework/core/js/FluidRequests.js @@ -14,9 +14,9 @@ var fluid_2_0 = fluid_2_0 || {}; (function ($, fluid) { "use strict"; - + /** NOTE: All contents of this file are DEPRECATED and no entry point should be considered a supported API **/ - + fluid.explodeLocalisedName = function (fileName, locale, defaultLocale) { var lastDot = fileName.lastIndexOf("."); if (lastDot === -1 || lastDot === 0) { @@ -24,9 +24,9 @@ var fluid_2_0 = fluid_2_0 || {}; } var baseName = fileName.substring(0, lastDot); var extension = fileName.substring(lastDot); - + var segs = locale.split("_"); - + var exploded = fluid.transform(segs, function (seg, index) { var shortSegs = segs.slice(0, index + 1); return baseName + "_" + shortSegs.join("_") + extension; @@ -77,7 +77,7 @@ var fluid_2_0 = fluid_2_0 || {}; that.operate(); return that; }; - + fluid.fetchResources.explodeForLocales = function (resourceSpecs) { fluid.each(resourceSpecs, function (resourceSpec, key) { if (resourceSpec.locale) { @@ -95,7 +95,7 @@ var fluid_2_0 = fluid_2_0 || {}; }); return resourceSpecs; }; - + fluid.fetchResources.condenseOneResource = function (resourceSpecs, resourceSpec, key, localeCount) { var localeSpecs = [resourceSpec]; for (var i = 0; i < localeCount; ++ i) { @@ -110,7 +110,7 @@ var fluid_2_0 = fluid_2_0 || {}; resourceSpecs[key] = lastNonError; } }; - + fluid.fetchResources.condenseForLocales = function (resourceSpecs) { fluid.each(resourceSpecs, function (resourceSpec, key) { if (typeof(resourceSpec.localeExploded) === "number") { @@ -118,7 +118,7 @@ var fluid_2_0 = fluid_2_0 || {}; } }); }; - + fluid.fetchResources.notifyResources = function (that, resourceSpecs, callback) { fluid.fetchResources.condenseForLocales(resourceSpecs); callback(resourceSpecs); @@ -509,7 +509,7 @@ var fluid_2_0 = fluid_2_0 || {}; * found on the queuedDataSource will call their counterparts in the wrappedDataSource, after * filtering through the appropriate queue. * - * TODO: A fully realized implementation should provide a mechansim for working with a local + * TODO: A fully realized implementation should provide a mechanism for working with a local * cache. For example pending write requests could be used to service get requests directly. */ fluid.defaults("fluid.queuedDataSource", { @@ -541,18 +541,10 @@ var fluid_2_0 = fluid_2_0 || {}; } }, invokers: { - set: { - funcName: "fluid.queuedDataSource.enqueueWithModel", - args: ["{that}", "{that}.requests.write", "{wrappedDataSource}.set", "{arguments}.0", "{arguments}.1", "{arguments}.2"] - }, - get: { - funcName: "fluid.queuedDataSource.enqueue", - args: ["{that}", "{that}.requests.read", "{wrappedDataSource}.get", "{arguments}.0", "{arguments}.1"] - }, - "delete": { - funcName: "fluid.queuedDataSource.enqueue", - args: ["{that}", "{that}.requests.write", "{wrappedDataSource}.delete", "{arguments}.0", "{arguments}.1"] - }, + // The set, get, and delete methods need to be provided by the implementor + // set: {}, + // get: {}, + // "delete": {}, start: { funcName: "fluid.queuedDataSource.start", args: ["{that}", "{arguments}.0"] @@ -578,13 +570,41 @@ var fluid_2_0 = fluid_2_0 || {}; } }; + /* + * A dataSource wrapper providing a debounce queuing mechanism for requests. + * Requests are queued based on type (read/write) and resource (directModel). + * Only 1 requested is queued at a time. New requests replace older ones. + * + * A fully implemented dataSource, following the structure outlined by fluid.dataSource, + * must be provided in the wrappedDataSource subcomponent. The get, set, and delete methods + * found on the queuedDataSource will call their counterparts in the wrappedDataSource, after + * filtering through the appropriate queue. + */ + fluid.defaults("fluid.debouncedDataSource", { + gradeNames: ["fluid.queuedDataSource", "autoInit"], + invokers: { + set: { + funcName: "fluid.debouncedDataSource.enqueueWithModel", + args: ["{that}", "{that}.requests.write", "{wrappedDataSource}.set", "{arguments}.0", "{arguments}.1", "{arguments}.2"] + }, + get: { + funcName: "fluid.debouncedDataSource.enqueue", + args: ["{that}", "{that}.requests.read", "{wrappedDataSource}.get", "{arguments}.0", "{arguments}.1"] + }, + "delete": { + funcName: "fluid.debouncedDataSource.enqueue", + args: ["{that}", "{that}.requests.write", "{wrappedDataSource}.delete", "{arguments}.0", "{arguments}.1"] + } + } + }); + /* * Adds only one item to the queue at a time, new requests replace older ones * * The request object contains the request function and arguments. * In the form {method: requestFn, directModel: {}, model: {}, callback: callbackFn} */ - fluid.queuedDataSource.enqueueImpl = function (that, requestsQueue, request) { + fluid.debouncedDataSource.enqueueImpl = function (that, requestsQueue, request) { var promise = fluid.promise(); var key = fluid.toHashKey(request.directModel); var queue = requestsQueue[key]; @@ -609,17 +629,17 @@ var fluid_2_0 = fluid_2_0 || {}; return promise; }; - fluid.queuedDataSource.enqueue = function (that, requestsQueue, method, directModel, options) { + fluid.debouncedDataSource.enqueue = function (that, requestsQueue, method, directModel, options) { var request = { method: method, directModel: directModel, options: options }; - return fluid.queuedDataSource.enqueueImpl(that, requestsQueue, request); + return fluid.debouncedDataSource.enqueueImpl(that, requestsQueue, request); }; - fluid.queuedDataSource.enqueueWithModel = function (that, requestsQueue, method, directModel, model, options) { + fluid.debouncedDataSource.enqueueWithModel = function (that, requestsQueue, method, directModel, model, options) { var request = { method: method, directModel: directModel, @@ -627,7 +647,7 @@ var fluid_2_0 = fluid_2_0 || {}; options: options }; - return fluid.queuedDataSource.enqueueImpl(that, requestsQueue, request); + return fluid.debouncedDataSource.enqueueImpl(that, requestsQueue, request); }; /** End dataSource **/ diff --git a/tests/framework-tests/core/js/DataSourceTests.js b/tests/framework-tests/core/js/DataSourceTests.js index f5f04eb4f7..1c84ffff0e 100644 --- a/tests/framework-tests/core/js/DataSourceTests.js +++ b/tests/framework-tests/core/js/DataSourceTests.js @@ -74,8 +74,8 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt return promise; }; - fluid.defaults("fluid.tests.queuedDataSource", { - gradeNames: ["fluid.queuedDataSource", "autoInit"], + fluid.defaults("fluid.tests.debouncedDataSource", { + gradeNames: ["fluid.debouncedDataSource", "autoInit"], members: { // Each record should contain a list of the directModels from the requests // that were sent to the respective events. @@ -114,7 +114,7 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt // s = short delay // l = long delay - fluid.tests.queuedDataSource.requestRuns = { + fluid.tests.debouncedDataSource.requestRuns = { ss: [0, 50, 55], sl: [0, 50, 200], ls: [0, 150, 200], @@ -127,7 +127,7 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt sssl: [0, 50, 60, 75, 200] }; - fluid.tests.queuedDataSource.expected100ms = { + fluid.tests.debouncedDataSource.expected100ms = { ss: [1, 3], sl: [1, 2, 3], ls: [1, 2, 3], @@ -140,24 +140,24 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt sssl: [1, 4, 5] }; - fluid.tests.queuedDataSource.testRequests = [{ + fluid.tests.debouncedDataSource.testRequests = [{ requestType: "get", - source: fluid.tests.queuedDataSource.expected100ms + source: fluid.tests.debouncedDataSource.expected100ms }, { requestType: "set", - source: fluid.tests.queuedDataSource.expected100ms + source: fluid.tests.debouncedDataSource.expected100ms }, { requestType: "delete", - source: fluid.tests.queuedDataSource.expected100ms + source: fluid.tests.debouncedDataSource.expected100ms }]; - fluid.tests.queuedDataSource.assertRequest = function (requestType, setName, delays, expected, delayBuffer) { + fluid.tests.debouncedDataSource.assertRequest = function (requestType, setName, delays, expected, delayBuffer) { // Time in milliseconds to add to the assertion delay, to buffer against setTimeout impressions. // Because multiple instances run simultaneously this number may need to be increased to take into // account extra delays related to the thread blocking. delayBuffer = delayBuffer || 300; var promise = fluid.promise(); - var that = fluid.tests.queuedDataSource(); + var that = fluid.tests.debouncedDataSource(); var assertionDelay = delays[delays.length - 1] + delayBuffer; // time to wait to assert the requests. fluid.tests.invokeRequestWithDelay(that, requestType, delays, 100, {path: "value"}); @@ -171,12 +171,12 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt // Note: Due to the timeouts used to simulate actual asynchronous operations, this test // will take a while to execute. - jqUnit.asyncTest("Queued DataSource", function () { + jqUnit.asyncTest("Debounce DataSource", function () { var sources = []; - fluid.each(fluid.tests.queuedDataSource.testRequests, function (testRequest) { - fluid.each(fluid.tests.queuedDataSource.requestRuns, function (delays, setName) { + fluid.each(fluid.tests.debouncedDataSource.testRequests, function (testRequest) { + fluid.each(fluid.tests.debouncedDataSource.requestRuns, function (delays, setName) { sources.push(function () { - return fluid.tests.queuedDataSource.assertRequest(testRequest.requestType, setName, delays, testRequest.source[setName]); + return fluid.tests.debouncedDataSource.assertRequest(testRequest.requestType, setName, delays, testRequest.source[setName]); }); }); }); From b07f49241b1c4b44da37b9d2c833102fe2d8e0aa Mon Sep 17 00:00:00 2001 From: Justin Obara Date: Mon, 22 Jun 2015 15:08:59 -0400 Subject: [PATCH 26/29] FLUID-5542: refactored to simplify integration Only an addToQueue method is needed for integration. The get, set, and delete methods use this to update the queue. --- src/framework/core/js/FluidRequests.js | 87 +++++++++++++++----------- 1 file changed, 49 insertions(+), 38 deletions(-) diff --git a/src/framework/core/js/FluidRequests.js b/src/framework/core/js/FluidRequests.js index 48672bec6a..cd4bc8d38e 100644 --- a/src/framework/core/js/FluidRequests.js +++ b/src/framework/core/js/FluidRequests.js @@ -541,10 +541,21 @@ var fluid_2_0 = fluid_2_0 || {}; } }, invokers: { - // The set, get, and delete methods need to be provided by the implementor - // set: {}, - // get: {}, - // "delete": {}, + // The add to queue method needs to be provided by the integrator + // addToQueue: "", + // addToQueue: {funcName: "fluid.identity"}, + set: { + funcName: "fluid.queuedDataSource.enqueueWithModel", + args: ["{that}", "{that}.requests.write", "{wrappedDataSource}.set", "{arguments}.0", "{arguments}.1", "{arguments}.2"] + }, + get: { + funcName: "fluid.queuedDataSource.enqueue", + args: ["{that}", "{that}.requests.read", "{wrappedDataSource}.get", "{arguments}.0", "{arguments}.1"] + }, + "delete": { + funcName: "fluid.queuedDataSource.enqueue", + args: ["{that}", "{that}.requests.write", "{wrappedDataSource}.delete", "{arguments}.0", "{arguments}.1"] + }, start: { funcName: "fluid.queuedDataSource.start", args: ["{that}", "{arguments}.0"] @@ -570,41 +581,13 @@ var fluid_2_0 = fluid_2_0 || {}; } }; - /* - * A dataSource wrapper providing a debounce queuing mechanism for requests. - * Requests are queued based on type (read/write) and resource (directModel). - * Only 1 requested is queued at a time. New requests replace older ones. - * - * A fully implemented dataSource, following the structure outlined by fluid.dataSource, - * must be provided in the wrappedDataSource subcomponent. The get, set, and delete methods - * found on the queuedDataSource will call their counterparts in the wrappedDataSource, after - * filtering through the appropriate queue. - */ - fluid.defaults("fluid.debouncedDataSource", { - gradeNames: ["fluid.queuedDataSource", "autoInit"], - invokers: { - set: { - funcName: "fluid.debouncedDataSource.enqueueWithModel", - args: ["{that}", "{that}.requests.write", "{wrappedDataSource}.set", "{arguments}.0", "{arguments}.1", "{arguments}.2"] - }, - get: { - funcName: "fluid.debouncedDataSource.enqueue", - args: ["{that}", "{that}.requests.read", "{wrappedDataSource}.get", "{arguments}.0", "{arguments}.1"] - }, - "delete": { - funcName: "fluid.debouncedDataSource.enqueue", - args: ["{that}", "{that}.requests.write", "{wrappedDataSource}.delete", "{arguments}.0", "{arguments}.1"] - } - } - }); - /* * Adds only one item to the queue at a time, new requests replace older ones * * The request object contains the request function and arguments. * In the form {method: requestFn, directModel: {}, model: {}, callback: callbackFn} */ - fluid.debouncedDataSource.enqueueImpl = function (that, requestsQueue, request) { + fluid.queuedDataSource.enqueueImpl = function (that, addToQueueFn, requestsQueue, request) { var promise = fluid.promise(); var key = fluid.toHashKey(request.directModel); var queue = requestsQueue[key]; @@ -623,23 +606,30 @@ var fluid_2_0 = fluid_2_0 || {}; requestsQueue[key] = queue; } - queue.requests[0] = request; + // queue.requests[0] = request; + + if (typeof(addToQueueFn) === "string") { + fluid.invokeGlobalFunction(addToQueueFn, [queue, request]); + } else { + addToQueueFn(queue, request); + } + that.events.enqueued.fire(request, queue); return promise; }; - fluid.debouncedDataSource.enqueue = function (that, requestsQueue, method, directModel, options) { + fluid.queuedDataSource.enqueue = function (that, requestsQueue, method, directModel, options) { var request = { method: method, directModel: directModel, options: options }; - return fluid.debouncedDataSource.enqueueImpl(that, requestsQueue, request); + return fluid.queuedDataSource.enqueueImpl(that, that.addToQueue, requestsQueue, request); }; - fluid.debouncedDataSource.enqueueWithModel = function (that, requestsQueue, method, directModel, model, options) { + fluid.queuedDataSource.enqueueWithModel = function (that, requestsQueue, method, directModel, model, options) { var request = { method: method, directModel: directModel, @@ -647,9 +637,30 @@ var fluid_2_0 = fluid_2_0 || {}; options: options }; - return fluid.debouncedDataSource.enqueueImpl(that, requestsQueue, request); + return fluid.queuedDataSource.enqueueImpl(that, that.addToQueue, requestsQueue, request); }; + fluid.queuedDataSource.replaceRequest = function (queue, request) { + queue.requests[0] = request; + }; + + /* + * A dataSource wrapper providing a debounce queuing mechanism for requests. + * Requests are queued based on type (read/write) and resource (directModel). + * Only 1 requested is queued at a time. New requests replace older ones. + * + * A fully implemented dataSource, following the structure outlined by fluid.dataSource, + * must be provided in the wrappedDataSource subcomponent. The get, set, and delete methods + * found on the queuedDataSource will call their counterparts in the wrappedDataSource, after + * filtering through the appropriate queue. + */ + fluid.defaults("fluid.debouncedDataSource", { + gradeNames: ["fluid.queuedDataSource", "autoInit"], + invokers: { + addToQueue: "fluid.queuedDataSource.replaceRequest" + } + }); + /** End dataSource **/ })(jQuery, fluid_2_0); From b395ca8b978768f035140ba53f5f156f6cd5a68c Mon Sep 17 00:00:00 2001 From: Justin Obara Date: Mon, 29 Jun 2015 08:49:23 -0400 Subject: [PATCH 27/29] FLUID-5542: Testing consistency of fluid.toHashKey --- tests/framework-tests/core/js/DataSourceTests.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/framework-tests/core/js/DataSourceTests.js b/tests/framework-tests/core/js/DataSourceTests.js index 1c84ffff0e..1bbdd7b2d0 100644 --- a/tests/framework-tests/core/js/DataSourceTests.js +++ b/tests/framework-tests/core/js/DataSourceTests.js @@ -44,6 +44,18 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt jqUnit.assertEquals("The multi-level object should be converted properly", expected.nested, fluid.toHashKey(nested)); }); + jqUnit.test("fluid.toHashKey - order consistency", function () { + var order1 = {}; + order1.a = "1"; + order1.b = "2"; + + var order2 = {}; + order2.b = "2"; + order2.a = "1"; + + jqUnit.assertEquals("Identical objects created in different orders should produce a consistent output", fluid.toHashKey(order1), fluid.toHashKey(order2)); + }); + fluid.defaults("fluid.tests.dataSource", { gradeNames: ["fluid.dataSource", "autoInit"], invokers: { From 0e5fc600900c05d33c4950941790cfd78d421b74 Mon Sep 17 00:00:00 2001 From: Justin Obara Date: Mon, 29 Jun 2015 08:50:49 -0400 Subject: [PATCH 28/29] FLUID-5542: removed commented out code. --- src/framework/core/js/FluidRequests.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/framework/core/js/FluidRequests.js b/src/framework/core/js/FluidRequests.js index cd4bc8d38e..9a257adc6a 100644 --- a/src/framework/core/js/FluidRequests.js +++ b/src/framework/core/js/FluidRequests.js @@ -606,8 +606,6 @@ var fluid_2_0 = fluid_2_0 || {}; requestsQueue[key] = queue; } - // queue.requests[0] = request; - if (typeof(addToQueueFn) === "string") { fluid.invokeGlobalFunction(addToQueueFn, [queue, request]); } else { From 550f145e47ea3042e2d7ea0bbabb9603f4a871d0 Mon Sep 17 00:00:00 2001 From: Justin Obara Date: Mon, 29 Jun 2015 09:00:14 -0400 Subject: [PATCH 29/29] FLUID-5542: Call that.addToQueue in enqueueImpl --- src/framework/core/js/FluidRequests.js | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/framework/core/js/FluidRequests.js b/src/framework/core/js/FluidRequests.js index 9a257adc6a..d00721233b 100644 --- a/src/framework/core/js/FluidRequests.js +++ b/src/framework/core/js/FluidRequests.js @@ -587,7 +587,7 @@ var fluid_2_0 = fluid_2_0 || {}; * The request object contains the request function and arguments. * In the form {method: requestFn, directModel: {}, model: {}, callback: callbackFn} */ - fluid.queuedDataSource.enqueueImpl = function (that, addToQueueFn, requestsQueue, request) { + fluid.queuedDataSource.enqueueImpl = function (that, requestsQueue, request) { var promise = fluid.promise(); var key = fluid.toHashKey(request.directModel); var queue = requestsQueue[key]; @@ -606,12 +606,7 @@ var fluid_2_0 = fluid_2_0 || {}; requestsQueue[key] = queue; } - if (typeof(addToQueueFn) === "string") { - fluid.invokeGlobalFunction(addToQueueFn, [queue, request]); - } else { - addToQueueFn(queue, request); - } - + that.addToQueue(queue, request); that.events.enqueued.fire(request, queue); return promise; @@ -624,7 +619,7 @@ var fluid_2_0 = fluid_2_0 || {}; options: options }; - return fluid.queuedDataSource.enqueueImpl(that, that.addToQueue, requestsQueue, request); + return fluid.queuedDataSource.enqueueImpl(that, requestsQueue, request); }; fluid.queuedDataSource.enqueueWithModel = function (that, requestsQueue, method, directModel, model, options) { @@ -635,7 +630,7 @@ var fluid_2_0 = fluid_2_0 || {}; options: options }; - return fluid.queuedDataSource.enqueueImpl(that, that.addToQueue, requestsQueue, request); + return fluid.queuedDataSource.enqueueImpl(that, requestsQueue, request); }; fluid.queuedDataSource.replaceRequest = function (queue, request) {