Skip to content
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

Merged
merged 3 commits into from
Dec 7, 2016

Conversation

avimehta
Copy link
Contributor

@avimehta avimehta commented Oct 14, 2016

WIP. Tests are pending a preliminary design review.

Fixes #2198, #6027

@avimehta
Copy link
Contributor Author

/cc @cramforce @dvoytenko

}

hashFilter_() {
if (this.crypto_) {
Copy link
Contributor Author

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?

Copy link
Contributor

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

Copy link
Contributor Author

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());
Copy link
Contributor

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.

Copy link
Contributor Author

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?

Copy link
Contributor

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.

Copy link
Contributor Author

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.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this still outstanding?

Copy link
Contributor Author

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..

Copy link
Contributor

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) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

jsdoc

Copy link
Contributor Author

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() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

jsdoc

Copy link
Contributor Author

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() {
Copy link
Contributor

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?

Copy link
Contributor Author

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

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also /cc @erwinmombay

Copy link
Contributor Author

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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

jsdoc

Copy link
Contributor Author

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 {
Copy link
Contributor

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.

Copy link
Contributor Author

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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

jsdoc

Copy link
Contributor Author

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]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: spacing

Copy link
Contributor Author

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_() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

jsdoc

Copy link
Contributor Author

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_) {
Copy link
Contributor

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,
Copy link
Contributor

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?

Copy link
Contributor Author

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() {
Copy link
Contributor

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?

Copy link
Contributor Author

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)
Copy link
Contributor

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.

Copy link
Contributor Author

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]) {
Copy link
Contributor

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.

Copy link
Contributor Author

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) {
Copy link
Contributor

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.

Copy link
Contributor Author

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 => {
Copy link
Contributor

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?

Copy link
Contributor Author

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;
Copy link
Contributor

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 falsesyou passed before to true).

Copy link
Contributor Author

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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#unshift

Copy link
Contributor Author

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)};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#slice

Copy link
Contributor Author

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;
Copy link
Contributor

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)".

Copy link
Contributor Author

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.

@avimehta avimehta force-pushed the filters branch 2 times, most recently from 8a63990 to 27a9182 Compare November 22, 2016 01:50
// 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
Copy link
Contributor

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?

Copy link
Contributor Author

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);
Copy link
Contributor

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.

Copy link
Contributor Author

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 =>
Copy link
Contributor

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.

Copy link
Contributor Author

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,
Copy link
Contributor

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.

Copy link
Contributor Author

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Types?

Copy link
Contributor Author

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?

Copy link
Contributor

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)};
Copy link
Contributor

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.

Copy link
Contributor Author

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(/([^(]*)(\([^)]*\))?/);
Copy link
Contributor

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?

Copy link
Contributor Author

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,
Copy link
Contributor

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.

Copy link
Contributor Author

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.

Copy link
Contributor

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. 😬

Copy link
Contributor Author

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());
Copy link
Contributor

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.

Copy link
Contributor Author

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);
Copy link
Contributor

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done. Thanks!

@dvoytenko
Copy link
Contributor

@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.

@avimehta
Copy link
Contributor Author

@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))
Copy link
Contributor

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?

Copy link
Contributor Author

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.

Copy link
Contributor Author

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.

@dvoytenko
Copy link
Contributor

@avimehta Sorry if I missed it. I looked at hashFilter_ and it continued to rely on risk-exposed crypto_ var. Is there some misconnect there?

@avimehta
Copy link
Contributor Author

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.

@avimehta avimehta self-assigned this Nov 28, 2016
@@ -41,6 +42,7 @@ AMP.registerServiceForDoc(
installActivityService(AMP.win);
installCidService(AMP.win);
installCryptoService(AMP.win);
variableServiceFor(AMP.win);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mkhatib: is this needed?

Copy link
Contributor

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.

Copy link
Contributor Author

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?

Copy link
Contributor Author

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,
Copy link
Contributor

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) {
Copy link
Contributor

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.

Copy link
Contributor Author

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));
Copy link
Contributor

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?

Copy link
Contributor Author

@avimehta avimehta Dec 1, 2016

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
Copy link
Contributor

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
Copy link
Contributor

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);
Copy link
Contributor

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?

Copy link
Contributor Author

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());
Copy link
Contributor

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

started checking for null.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool

@avimehta
Copy link
Contributor Author

avimehta commented Dec 1, 2016

ptal

.then(() => this.addParamsToUrl_(request, params))
.then(request => {
this.config_['vars']['requestCount']++;
const expansionOptions = this.expansionOptions_(event, trigger);
Copy link
Contributor

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?

Copy link
Contributor Author

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) {
Copy link
Contributor

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.

Copy link
Contributor Author

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...

Copy link
Contributor Author

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 {
Copy link
Contributor

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.

Copy link
Contributor Author

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
Copy link
Contributor

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.

Copy link
Contributor Author

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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

jsdoc

Copy link
Contributor Author

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};
Copy link
Contributor

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.

Copy link
Contributor Author

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>>}
Copy link
Contributor

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?

Copy link
Contributor Author

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?

Copy link
Contributor

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>}}

Copy link
Contributor Author

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;

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: remove empty line

Copy link
Contributor Author

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>}
Copy link
Contributor

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.

Copy link
Contributor Author

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));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/* allowNulls */ true

Copy link
Contributor Author

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)));
Copy link
Contributor

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed.

Copy link
Contributor Author

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
Copy link
Contributor

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 !.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

@dvoytenko
Copy link
Contributor

@avimehta Yep. That looks right! Thanks for adding it. I left few more (mostly cleanup) comments, but otherwise I'm close to LGTM.

@avimehta
Copy link
Contributor Author

avimehta commented Dec 2, 2016

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..

@dvoytenko
Copy link
Contributor

@avimehta Just one note from me on typedef for the return type in one of the functions. PTAL.

@avimehta
Copy link
Contributor Author

avimehta commented Dec 6, 2016

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);
Copy link
Contributor

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?

Copy link
Contributor Author

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?).

Copy link
Contributor

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.

@avimehta avimehta merged commit 65177f8 into ampproject:master Dec 7, 2016
avimehta added a commit to avimehta/amphtml that referenced this pull request Dec 8, 2016
hasOwn doesn't exist when the object is created using `map()`. Added a
test as well.
Lith pushed a commit to Lith/amphtml that referenced this pull request Dec 22, 2016
…5621)

* Added initial version of amp-anlaytics variable filters.

Fixes ampproject#2198
Lith pushed a commit to Lith/amphtml that referenced this pull request Dec 22, 2016
…5621)

* Added initial version of amp-anlaytics variable filters.

Fixes ampproject#2198
jridgewell pushed a commit to jridgewell/amphtml that referenced this pull request Jan 31, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants