-
Notifications
You must be signed in to change notification settings - Fork 3.9k
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
Added initial version of amp-anlaytics variable filters. #5621
Conversation
/cc @cramforce @dvoytenko |
} | ||
|
||
hashFilter_() { | ||
if (this.crypto_) { |
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.
Hash seems to be the only filter for now that is async. Should I make everything async? Otherwise, what are the options?
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.
My guess is that, yes, it should be all async. Unless we decide to initially exclude SHA. But sooner or later something else will come up with async requirement.
/cc @cramforce
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.
made everything async.
this.register_('default', defaultFilter); | ||
this.register_('hash', this.hashFilter_.bind(this)); | ||
this.register_('substr', substrFilter); | ||
this.register_('trim', value => value.trim()); |
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.
Value can be null
, right? Here and everywhere.
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.
should I make a lightweight wrapper or something? Or will assertString work?
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.
I almost want to say that the callback should not be called for null
or undefined
. But we have default
filter so that's not possible. We need to allow null
and undefined
though. So I see only two options: (a) always process null
directly as in value => value != null ? filter(value) : null
; (b) have two register ways registerNotNull_
and registerNull_
: one will callback on nulls and the other won't.
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.
i am not sure I understand option (a). How will default
filter work in this case? option (b) sounds fine.
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.
Is this still outstanding?
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.
yes, this is outstanding. I don't understand option (a) and didn't want to implement it without knowing what it means..
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.
Option (a) basically says, all functions should expect (and test) any value: be it an empty string or null or undefined or 0, etc. Option (b) means: if value is null
it won't even call the filter (except in some cases such as default
). The main question here is whether the value can ever be null
or undefined
.
|
||
export class VariableService { | ||
|
||
constructor(window) { |
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.
jsdoc
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.
done.
*/ | ||
let VariableFilterDef; | ||
|
||
function substrFilter() { |
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.
jsdoc
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.
added.
return arguments[0].substr(start, length); | ||
} | ||
|
||
function defaultFilter() { |
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.
jsdoc. why use arguments
vs positioned args?
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.
Any ideas on how to handle check-types warnings like following:
extensions/amp-analytics/0.1/variables.js:69: ERROR - actual parameter 2 of VariableService$$module$extensions$amp_analytics$0_1$variables.prototype.register_ does not match formal parameter
found : function (string, string, string): Promise<VariableFilterIODef$$module$extensions$amp_analytics$0_1$variables>
required: function (...(Array<string>|string)): (Array<string>|string)
this.register_('substr', substrFilter);
/cc @jridgewell
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.
also /cc @erwinmombay
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.
okay.. using positioned args now. Still need to figure out if there is a better way to do type checking.
constructor(window) { | ||
this.win_ = window; | ||
|
||
this.filters_ = Object.create(null); |
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.
jsdoc
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.
done.
return arguments[0] || arguments[1]; | ||
} | ||
|
||
export class VariableService { |
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.
I guess a bigger question is whether this should be exclusively an analytics feature or url-replacements
. Let's keep it here for now.
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.
ack. One issue with url-replacements is that the format used there is plain strings for variables. Passing arguments and nesting becomes difficult.
this.win_ = window; | ||
|
||
this.filters_ = Object.create(null); | ||
this.crypto_ = null; |
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.
jsdoc
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.
done.
|
||
/** | ||
* [register_ description] | ||
* @param {string} name [description] |
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.
nit: spacing
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.
forgot to fix in this revision. Will fix with next commit.
return encodeURIComponent(name) + argList; | ||
} | ||
|
||
hashFilter_() { |
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.
jsdoc
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.
added.
} | ||
|
||
hashFilter_() { | ||
if (this.crypto_) { |
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.
My guess is that, yes, it should be all async. Unless we decide to initially exclude SHA. But sooner or later something else will come up with async requirement.
/cc @cramforce
/* arg*/ false); | ||
trigger['selector'] = this.variableService_.expandTemplate( | ||
trigger['selector'], trigger, this.config_, | ||
/* opt_event */ undefined, /* opt_iterations */ undefined, |
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.
Are these opt parameters required?
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.
nope. I need to pass in the last argument. Any better way to do this?
*/ | ||
let VariableFilterDef; | ||
|
||
function substrFilter() { |
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.
Why not declare string
, start
, and length
as parameters?
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.
changed.
this.register_('toUpperCase', value => value.toUpperCase()); | ||
this.register_('not', value => String(!value)); | ||
this.register_('base64', value => base64Encode(String(value))); | ||
this.register_('if', (value, thenValue, elseValue) => Boolean(value) |
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.
Boolean cast isn't necessary.
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.
forgot in current commit. Will do it in next commit.
* @param {VariableFilterDef} handler [description] | ||
*/ | ||
register_(name, handler) { | ||
if (this.filters_[name]) { |
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.
Make this an assert, so it'll get DCEd.
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.
ack
return {name: '', argList: ''}; | ||
} | ||
const match = key.match(/([^(]*)(\([^)]*\))?/); | ||
if (!match) { |
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.
This should be an assert, since the next line match[1]
will throw an error anyways.
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.
done.
// Replace placeholders with URI encoded values. | ||
// Precedence is opt_event.vars > trigger.vars > config.vars. | ||
// Nested expansion not supported. | ||
return expandTemplate(template, key => { |
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.
Can we rename the imported expandTemplate
to something else?
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.
removed the import.
expandTemplate(template, trigger, config, opt_event, opt_iterations, | ||
opt_encode) { | ||
opt_iterations = opt_iterations === undefined ? 2 : opt_iterations; | ||
opt_encode = opt_encode === undefined ? true : opt_encode; |
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.
Calling this opt_encode
make me think the default is to not encode. Can we call this opt_no_encode
, which its default value being false
(you'll have to swap any falses
you passed before to true
).
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.
done.
for (let i = 0; i < filters.length; i++) { | ||
const parsedFilter = this.parseFilter_(filters[i].trim()); | ||
if (parsedFilter.fn) { | ||
parsedFilter.args.splice(0, 0, value); |
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.
#unshift
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.
done.
user().error(TAG, 'Invalid filter name: ' + tokens[0]); | ||
return {}; | ||
} | ||
return {fn, args: tokens.splice(1)}; |
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.
#slice
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.
done.
} | ||
// Separate out names and arguments from the value and encode the value. | ||
const {name, argList} = this.getNameArgs_(String(raw)); | ||
return encodeURIComponent(name) + argList; |
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.
I never understood why we turn ${default(a, b)}
into encodeURIComponent('default') + "(a, b)"
.
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.
still not addressed. I am aware of this and have not had the time to go through all the possible scenarios here.
8a63990
to
27a9182
Compare
// Add any given extraUrlParams as query string param | ||
if (this.config_['extraUrlParams'] || trigger['extraUrlParams']) { | ||
const params = Object.create(null); | ||
Object.assign(params, this.config_['extraUrlParams'], | ||
trigger['extraUrlParams']); | ||
for (const k in params) { | ||
if (typeof params[k] == 'string') { | ||
params[k] = this.expandTemplate_(params[k], trigger, event); | ||
requestPromise = requestPromise |
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.
Can this be done in parallel?
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.
yes! done.
} | ||
} | ||
request = this.addParamsToUrl_(request, params); | ||
requestPromise.then(() => { | ||
request = this.addParamsToUrl_(request, params); |
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.
We can return this instead, and consume the promises value.
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.
done.
this.config_['vars']['requestCount']++; | ||
return this.variableService_.expandTemplate( | ||
request, trigger, this.config_, event) | ||
.then(request => |
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.
These can be hoisted out of this then block.
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.
thanks. This is much cleaner!
function substrFilter(str, s, l) { | ||
const start = Number(s); | ||
let length = str.length; | ||
user().assertNumber(start, |
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.
Note that this will allow NaN
, which is a number. We have an isFinite
that'll probably be better.
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.
changed. Was thinking of putting range checks as well but since substr accepts arbitrary values, didn't add them here.
} | ||
|
||
/** | ||
* @param {*} value |
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.
Types?
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.
Not sure what to put here. Suggestions?
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.
No idea. 😬
user().error(TAG, 'Invalid filter name: ' + tokens[0]); | ||
return {}; | ||
} | ||
return {fn, args: tokens.slice(1)}; |
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.
A stretch goal might be to validate the number of arguments matches what we expect.
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.
This is not addressed. Not sure if this should be done at this level or inside the functions.
if (!key) { | ||
return {name: '', argList: ''}; | ||
} | ||
const match = key.match(/([^(]*)(\([^)]*\))?/); |
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.
Can we fix #6027 while we're here?
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.
Still not fixed. I have a rudimentary solution but looking more to find a better solution. Suggestions welcome.
|
||
return this.variableService_.expandTemplate( | ||
trigger['selector'], trigger, this.config_, | ||
/* opt_event */ undefined, /* opt_iterations */ undefined, |
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.
This is becoming complex enough to warrant an options object. Not to mention it'll take care of the special opt_iterations
must equal undefined
to get the default behavior.
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.
technically, I was not changing any of the code here. just moving it from one file to another. If you think options object will be better, I'll add it. Let me know.
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.
3 boolean flags is pushing my code smell button. 😬
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.
opt_event
is an object, opt_iterations
is a number. opt_no_encode
is the only boolean :). Making the change :)
return Promise.resolve(''); | ||
} | ||
|
||
const {name, argList} = this.getNameArgs_(tokens[0].trim()); |
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.
We can do another #shift
to avoid the #slice
below.
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.
done.
|
||
// Queue current replacement promise after the last replacement. | ||
if (replacementPromise) { | ||
replacementPromise = replacementPromise.then(() => p); |
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.
We could avoid a lot of promise allocations if we use an array and Promise.all
instead.
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.
done. Thanks!
@avimehta Still looking for an answer re: sync vs async. It seems that at some point we'll run into an issue if we continue down the sync path. Indeed, we might already have with crypto. |
@dvoytenko I already made everything async. Does anything need changing? |
request, trigger, this.config_, event) | ||
.then(request => | ||
// For consistency with amp-pixel we also expand any url replacements. | ||
urlReplacementsForDoc(this.win.document).expandAsync(request)) |
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.
I'm getting some concerns over the refactoring that @mkhatib is currently doing. You've seen that, right?
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.
yes. Will work with him to resolve the conflicts.
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.
Resolved conflicts. Rebased on top of his changes.
@avimehta Sorry if I missed it. I looked at |
ptal. @mkhatib I was planning to wait for your PR to merge before merging this since your PR is much more complex and more urgent. Let me know if you think otherwise. |
@@ -41,6 +42,7 @@ AMP.registerServiceForDoc( | |||
installActivityService(AMP.win); | |||
installCidService(AMP.win); | |||
installCryptoService(AMP.win); | |||
variableServiceFor(AMP.win); |
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.
@mkhatib: is this needed?
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.
Ideally we wouldn't expose this as a service, but instead just create it as an instance var of Instrumentation
class.
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.
Instrumentation
class is not really involved in this PR (or variable expansion). Why move it there?
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.
@mkhatib Can you look at this PR wrt your decent changes to amp-analytics?
|
||
return this.variableService_.expandTemplate( | ||
trigger['selector'], trigger, this.config_, | ||
/* opt_event */ undefined, /* opt_iterations */ undefined, |
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.
3 boolean flags is pushing my code smell button. 😬
let result = Promise.resolve(value); | ||
for (let i = 0; i < filters.length; i++) { | ||
const parsedFilter = this.parseFilter_(filters[i].trim()); | ||
if (parsedFilter) { |
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.
parsedFilter
will always be truthy.
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.
Fixed.
user().assertString(value).toLowerCase()); | ||
this.register_('toUpperCase', value => | ||
user().assertString(value).toUpperCase()); | ||
this.register_('not', value => String(!value)); |
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.
Is it useful to propagate the boolean value instead of the string?
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.
Yes, I think it results in consistency. Otherwise falsey values give value: true
whereas truthy values give a value:
(empty string)
} | ||
|
||
/** | ||
* @param {*} value |
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.
No idea. 😬
let VariableFilterDef; | ||
|
||
/** | ||
* @param {!string} str |
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.
So, in truth, these are always strings, right? If yes - let's declare them as strings; type conversions are already explicit in the code.
'Length ' + length + ' in substr filter should be a number'); | ||
} | ||
|
||
return str.substr(start, length); |
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.
Let's confirm on this example. Is it ever possible that str
passed here is a null
, aside the fact that it's declared non-nullable?
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.
added allowNull
property. I need to add tests but want you guys to go through the code once..
|
||
this.register_('default', defaultFilter); | ||
this.register_('substr', substrFilter); | ||
this.register_('trim', value => user().assertString(value).trim()); |
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.
Ditto: is this really possible that a value is null
? If yes, is it really a user error? Is it an error at all? If we did not have a filter here, what value would be sent to the server? My strong suspicion is that it'd be an empty string. If that's the case, we should treat this value here similarly.
This applies to all of the filters below.
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.
started checking for null.
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.
Cool
ptal |
.then(() => this.addParamsToUrl_(request, params)) | ||
.then(request => { | ||
this.config_['vars']['requestCount']++; | ||
const expansionOptions = this.expansionOptions_(event, trigger); |
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.
Can we reuse the one above?
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.
done.
const {filter, args} = this.parseFilter_(filters[i].trim()); | ||
if (filter) { | ||
result = result.then(value => { | ||
if (value != null || filter.allowNull) { |
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.
We should just call the #filter
, with the value and args, let it figure out if it should allow, then it'll call the filtering callback.
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.
That results in most of the 1-line registrations into full fledged functions. Will try it out but thought you should know...
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.
Didn't do this. The change adds bunch of verbosity for not many benefits.
* @struct | ||
* @const | ||
*/ | ||
class FilterDef { |
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.
No need in Def
- that only for typedefs, not classes.
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.
fixed.
|
||
/** | ||
* @param {!string} str | ||
* @param {Number} s |
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.
Commented before: since the real args are all strings and we are doing type conversion below - let's just go ahead and type them as strings.
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.
made them all strings.
} | ||
|
||
|
||
export class VariableService { |
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.
jsdoc
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.
added.
user().assert(fnName, 'Filter ' + name + ' is invalid.'); | ||
const filter = user().assert(this.filters_[fnName], | ||
'Invalid filter name: ' + fnName); | ||
return {filter, args: tokens}; |
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.
Let's clarify: this is either a typedef or a class: it can't be either or, otherwise we may have problems with obfuscation.
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.
Class.
|
||
/** | ||
* @param {string} filterStr | ||
* @return {Object<string, FilterDef|Array<string>>} |
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 return type seems wrong?
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.
seems right to me. which part do you think is wrong?
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.
It looks like you return a typedef that looks like this:
@return {!{filter: !Filter, args: !Array<string>}}
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.
Had to use @return {?{filter: !Filter, args: !Array<string>}}
as I return null when the filter is not found.
|
||
// Since the replacement will happen later, return the original template. | ||
return match; | ||
|
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.
nit: remove empty line
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.
removed.
* Returns an array containing two values: name and args parsed from the key. | ||
* | ||
* @param {string} key The key to be parsed. | ||
* @return {!Object<string>} |
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.
This looks like a typedef and not a map.
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.
changed.
/** @private {!Object<string, FilterDef>} */ | ||
this.filters_ = map(); | ||
|
||
this.register_('default', new FilterDef(defaultFilter, true)); |
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.
/* allowNulls */ true
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.
changed.
*/ | ||
hashFilter_(value) { | ||
return cryptoFor(this.win_).then(crypto => | ||
crypto.sha384Base64(user().assertString(value))); |
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.
This filter is declared with allowNulls=false
, thus value
can no longer be null
here, right? So we don't need assertString
?
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.
removed.
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.
removed from here and above.
} | ||
|
||
/** | ||
* @param {!string} template The template to expand |
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.
string
is always !string
already, so let's dump !
everywhere. The same is true for number
and boolean
. All other types need explicit !
.
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.
done.
@avimehta Yep. That looks right! Thanks for adding it. I left few more (mostly cleanup) comments, but otherwise I'm close to LGTM. |
I added all the filter work behind a flag. I want to work on this a bit more before releasing it. Hope that is okay.. |
@avimehta Just one note from me on typedef for the return type in one of the functions. PTAL. |
Made some related changes and addressed the comment. ptal. |
.then(() => { | ||
request = this.addParamsToUrl_(request, params); | ||
this.config_['vars']['requestCount']++; | ||
const expansionOptions = this.expansionOptions_(event, trigger); |
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.
Can we reuse the expansionOptions
from above?
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.
It is not clear here but the requestCount
variable is incremented for each request that is sent. If I reuse, the object, the requestCount
doesn't change. Even if I increment it, each request increments the value from 0 to 1(do they have a separate copy in their closure?).
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.
Perfect. Didn't realize the options depended on the request count.
hasOwn doesn't exist when the object is created using `map()`. Added a test as well.
…5621) * Added initial version of amp-anlaytics variable filters. Fixes ampproject#2198
…5621) * Added initial version of amp-anlaytics variable filters. Fixes ampproject#2198
WIP. Tests are pending a preliminary design review.
Fixes #2198, #6027