-
Notifications
You must be signed in to change notification settings - Fork 97
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
FLUID-5542: Queued DataSource #566
base: main
Are you sure you want to change the base?
Changes from 31 commits
8a8e11c
fe6a81e
a29872f
983ff2f
09b61af
1bdbf25
fed923a
53d355b
0550ea1
0613710
a124dce
6b19c80
edacdcf
80c1e6e
b1c3544
27532db
f07ec54
ac6d291
bb4fab5
05a5a22
f8865db
942976a
c62458d
a4440df
5020fb2
9a857bf
454ea1c
c5ae8ec
fc2a1d6
21803c5
b07f492
88c0e26
b395ca8
0e5fc60
550f145
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,19 +14,19 @@ 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) { | ||
lastDot = fileName.length; | ||
} | ||
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,15 +110,15 @@ 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") { | ||
fluid.fetchResources.condenseOneResource(resourceSpecs, resourceSpec, key, resourceSpec.localeExploded); | ||
} | ||
}); | ||
}; | ||
|
||
fluid.fetchResources.notifyResources = function (that, resourceSpecs, callback) { | ||
fluid.fetchResources.condenseForLocales(resourceSpecs); | ||
callback(resourceSpecs); | ||
|
@@ -431,5 +431,236 @@ var fluid_2_0 = fluid_2_0 || {}; | |
return fluid.NO_VALUE; | ||
}; | ||
|
||
/** Start dataSource **/ | ||
|
||
/* | ||
* 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 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"] | ||
// Invokers should be defined for the typical CRUD functions and return | ||
// a promise. | ||
// | ||
// The "get" and "delete" methods require the signature (directModel). | ||
// The "set" method requires the signature (directModel, model) | ||
// | ||
// directModel: An object expressing an "index" into some set of | ||
// state which can be read or written. | ||
// | ||
// 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 | ||
// "delete": {} // handles the Delete function | ||
// } | ||
}); | ||
|
||
/* | ||
* 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 = "|" + JSON.stringify(val) + "|"; | ||
} | ||
return str; | ||
}; | ||
|
||
/* | ||
* 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. | ||
* | ||
* 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", { | ||
gradeNames: ["fluid.eventedComponent", "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, | ||
afterRequestComplete: null | ||
}, | ||
listeners: { | ||
"enqueued.start": { | ||
listener: "{that}.start", | ||
args: ["{arguments}.1"] | ||
}, | ||
"afterRequestComplete.start": { | ||
listener: "{that}.start", | ||
args: ["{arguments}.1"] | ||
} | ||
}, | ||
invokers: { | ||
// 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"] | ||
} | ||
} | ||
}); | ||
|
||
fluid.queuedDataSource.start = function (that, queue) { | ||
if (!queue.isActive && queue.requests.length) { | ||
var request = queue.requests.shift(); | ||
|
||
var requestComplete = function () { | ||
queue.isActive = false; | ||
that.events.afterRequestComplete.fire(request, queue); | ||
}; | ||
|
||
queue.isActive = true; | ||
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, request.promise); | ||
} | ||
}; | ||
|
||
/* | ||
* 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, addToQueueFn, requestsQueue, request) { | ||
var promise = fluid.promise(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. From this line (500) to 516, the behaviour is generic - as is the behaviour after 519. That is, the implementation is broadly what could be expected to be generic amongst all queue variants. Although we are only providing one queue variant in this current implementation, we should make sure that the component easily allows extension. It seems like the only line which represents a concrete implementation choice is line 518 - so this line should be factored out into a dedicated function (probably an invoker) attached to the component which is easily configurable and suitably named. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See above comment - we need to ensure that the two "enqueue" functions do not have a hard binding to enqueueImpl. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've added an addToQueue invoker |
||
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 = { | ||
isActive: false, | ||
requests: [] | ||
}; | ||
requestsQueue[key] = queue; | ||
} | ||
|
||
// queue.requests[0] = request; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Stray commented line here |
||
|
||
if (typeof(addToQueueFn) === "string") { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. addToQueueFn could never be a string, since it is sourced from an invoker target - to be clearer, you can just invoke that.addToQueue(queue, request) |
||
fluid.invokeGlobalFunction(addToQueueFn, [queue, request]); | ||
} else { | ||
addToQueueFn(queue, request); | ||
} | ||
|
||
that.events.enqueued.fire(request, queue); | ||
|
||
return promise; | ||
}; | ||
|
||
fluid.queuedDataSource.enqueue = function (that, requestsQueue, method, directModel, options) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This function is mostly identical to enqueueWithModel - and this duplication also creates a hazard for people wanting to customise the queue policy since they will need to provide implementations of both of these. We should i) consolidate these into 1 function as well as ii) provide a clear means for the user to just be able to replace enqueueImpl without necessarily having to replace enqueue/enqueueWithModel. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The reason for fluid.queuedDataSource.enqueue and fluid.queuedDataSource.enqueueWithModel being separate functions is that in fluid.queuedDataSource.start we need to check for the presence of https://github.com/jobara/infusion/blob/FLUID-5542/src/framework/core/js/FluidRequests.js#L485 |
||
var request = { | ||
method: method, | ||
directModel: directModel, | ||
options: options | ||
}; | ||
|
||
return fluid.queuedDataSource.enqueueImpl(that, that.addToQueue, requestsQueue, request); | ||
}; | ||
|
||
fluid.queuedDataSource.enqueueWithModel = function (that, requestsQueue, method, directModel, model, options) { | ||
var request = { | ||
method: method, | ||
directModel: directModel, | ||
model: model, | ||
options: options | ||
}; | ||
|
||
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); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
<head> | ||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> | ||
<title>DataSource Tests</title> | ||
|
||
<!-- This is the jqUnit test css file --> | ||
<link rel="stylesheet" media="screen" href="../../../lib/qunit/css/qunit.css" /> | ||
|
||
<script type="text/javascript" src="../../../../src/lib/jquery/core/js/jquery.js"></script> | ||
<script type="text/javascript" src="../../../../src/framework/core/js/Fluid.js"></script> | ||
<script type="text/javascript" src="../../../../src/framework/core/js/FluidPromises.js"></script> | ||
<script type="text/javascript" src="../../../../src/framework/core/js/FluidIoC.js"></script> | ||
<script type="text/javascript" src="../../../../src/framework/core/js/DataBinding.js"></script> | ||
<script type="text/javascript" src="../../../../src/framework/core/js/FluidRequests.js"></script> | ||
|
||
<!-- These are the jqUnit test js files --> | ||
<script type="text/javascript" src="../../../lib/qunit/js/qunit.js"></script> | ||
<script type="text/javascript" src="../../../test-core/jqUnit/js/jqUnit.js"></script> | ||
|
||
<!-- These are tests that have been written using this page as data --> | ||
<script type="text/javascript" src="../js/DataSourceTests.js"></script> | ||
|
||
</head> | ||
<body> | ||
<h1 id="qunit-header">DataSource Test Suite</h1> | ||
<h2 id="qunit-banner"></h2> | ||
<div id="qunit-testrunner-toolbar"></div> | ||
<h2 id="qunit-userAgent"></h2> | ||
<ol id="qunit-tests"></ol> | ||
|
||
</body> | ||
</html> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The naming is still confusing here. The comment above suggests that this is some kind of generic component that permits different queue policies - but in fact this grade is already bound to an implementation that operates a particular hardcoded policy that enforces a queue size of 1 (via "fluid.queuedDataSource.enqueueImpl"). This top-level grade should be better factored into a generic (possibly abstract) grade that doesn't commit the user to anything, with functions like enqueueImpl more clearly named (bound in a different grade/namespace) to emphasize that they are part of a particular concrete realisation of the queuedDataSource.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See above comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
created a new grade called debouncedDatasource that attaches the enqueue methods.