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

Track impression on amp landing page #5606

Merged
merged 13 commits into from Oct 28, 2016
Merged

Conversation

zhouyx
Copy link
Contributor

@zhouyx zhouyx commented Oct 14, 2016

ALP related. Not fully tested as I only get one ad server url that I can ping to.
I added some tests to test our logic after receiving response from ad server, the coverage should be enough for init impl I think.
PTAL to see I am on the right track.

'Cookie': 'Cookie:id=4903ef7b59596f28||t=1451370221|et=730|cs=002213fd48e4405e4579c95dfa',
};
const options = {
url: 'https://googleads.g.doubleclick.net/pcs/click?alp=1&xai=AKAOjstvASJmd6-NeoO3ner8FBNJW2w8n-sXMo0Nj5YC_LIY2NQjbNs0CoXQtM9tPi8by4H4bHRpMdB14qgRLctKkBKkh3vpR3m8fvPCzcFZ6HrxvxXUqzP17YJsihcINtRniOfmGFkzIolJ3ccPSPq6oYdJpg5lPeufOrLhtWsNspOsRgMSBFP7zH0l8tgtAb665jHEFmdAMH1vl69BxpqU2Q0ZoGDO_SVBMArlL--2nLOVgQt8om6IdzkcodppT9c&sig=Cg0ArKJSzEJGKs3-NmXNEAE&urlfix=1&adurl=https://cdn.ampproject.org/c/www.nbcnews.com/news/us-news/amp/milwaukee-cop-cars-smashed-torched-after-police-kill-suspect-n630236',
Copy link
Contributor Author

Choose a reason for hiding this comment

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

need to extract this from real url.

const fakeHeaders = {
'Host': 'adclick.g.doubleclick.net',
'Origin': 'https://cdn.ampproject.org',
'Cookie': 'Cookie:id=4903ef7b59596f28||t=1451370221|et=730|cs=002213fd48e4405e4579c95dfa',
Copy link
Member

Choose a reason for hiding this comment

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

LOL :)

request(options, (error, response, body) => {
if (!error && response.statusCode == 200) {
res.send(body);
}
Copy link
Member

Choose a reason for hiding this comment

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

You probably want to do something in the else here.

I'd consider to kill the if, though. Should be fine to always return the status code and always return the body.

const clickUrl = viewer.getParam('click');
let clickUrl = viewer.getParam('click');

// One test clickUrl
Copy link
Member

Choose a reason for hiding this comment

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

Please delete. Or add a comment here with the properly encoded click URL. Can be produced by using any example AMP doc and then appending #click={encodeURIComponent('YOUR_URL')}

if (adTrackingUrl) {
// TODO(@zhouyx) Confirm we assume the tracking_url is not
// a cdn.ampproject page, and we don't do viewer redirect
openWindowDialog(win, adTrackingUrl, '_top');
Copy link
Member

Choose a reason for hiding this comment

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

Not sure where this came frame. Just request the URL with new Image().src = adTrackingUrl

return;
}

// If adLocation is not an amp page, need to redirect to it.
Copy link
Member

Choose a reason for hiding this comment

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

This is not desired. Just delete the branch.

let gclid = response['gclid'];
if (!gclid) {
if (adLocation.indexOf('gclid=') > 0) {
gclid = adLocation.substr(adLocation.indexOf('gclid=') + 6);
Copy link
Member

Choose a reason for hiding this comment

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

The code will go away, but please never parse a URL like that :) Use the utilities in url.js

gclid = adLocation.substr(adLocation.indexOf('gclid=') + 6);
}
}
if (gclid) {
Copy link
Member

Choose a reason for hiding this comment

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

Instead this replacement
https://github.com/ampproject/amphtml/blob/master/src/service/url-replacements-impl.js#L187
should wait for this code to run and then magically discover that there is a second URL (The location) and also provide params from that.

Copy link
Contributor

Choose a reason for hiding this comment

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

+1
it can be achieved by pushing the gclid as a service:

getService('gclid', gclid);

if (adLocation && adLocation.indexOf('https://cdn.ampproject.org') != 0) {
// TODO(@zhouyx) Confirm we assume the tracking_url is not
// a cdn.ampproject page, and we don't do viewer redirect
openWindowDialog(win, adLocation, '_top');
Copy link
Member

Choose a reason for hiding this comment

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

Above use the location for the image URL if no tracking_url was provided.

@avimehta
Copy link
Contributor

Can you also add some tests to ensure that the variables from url-replacements are getting the gclid value. For example, does source url contain the gclid once this code is checked in? What about ampdoc_url etc.

}
}
if (gclid) {
win.location.hash = '#gclid=' + gclid;
Copy link
Member

Choose a reason for hiding this comment

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

Thinking about it more, I think we should create a virtualHref in document-info, use that for SOURCE_URL and QUERY_PARAM substitutions and override it here to be the original location.href with the query string coming from the location

if (getMode().localDev && !getMode().test) {
clickUrl = 'http://localhost:8000/impression-proxy?url=' + clickUrl;
}
return xhrFor(win).fetchJson(clickUrl, {
credentials: 'include',
requireAmpResponseSourceOrigin: true,
});
// TODO(@cramforce): Do something with the result.
Copy link
Contributor

Choose a reason for hiding this comment

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

Remove the TODO? Since we are doing it outside this function.

// TODO(@zhouyx) We need a timeout here?
// TODO(@zhouyx) need test with a real response.
invoke(win, clickUrl).then(response => {
if (!response) {
Copy link
Contributor

Choose a reason for hiding this comment

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

it will never be null

* Send the url to ad server and wait for its response
* @param {!Window} win
* @param {string} clickUrl
* @return {!Promise}
Copy link
Contributor

Choose a reason for hiding this comment

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

{@Promise<!JSONType>}

const adLocation = response['location'];

// If there's a tracking_url, need to redirect to that url.
const adTrackingUrl = response['tracking_url'];
Copy link
Contributor

Choose a reason for hiding this comment

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

in the design doc, the string literal is 'tracking'

// If provided use it, if not try to find in adLocation from response
let gclid = response['gclid'];
if (!gclid) {
if (adLocation.indexOf('gclid=') > 0) {
Copy link
Contributor

Choose a reason for hiding this comment

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

we'd better assume gclid is already in a separate field if there is one (that's the protocol). we should not try to parse it from URL.

gclid = adLocation.substr(adLocation.indexOf('gclid=') + 6);
}
}
if (gclid) {
Copy link
Contributor

Choose a reason for hiding this comment

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

+1
it can be achieved by pushing the gclid as a service:

getService('gclid', gclid);

@zhouyx
Copy link
Contributor Author

zhouyx commented Oct 18, 2016

As discussed with @cramforce offline, I tried another approach, PTAL

@@ -163,6 +164,18 @@ export class UrlReplacements {
return removeFragment(info.sourceUrl);
}));

this.setAsync_('SOURCE_URL', () => {
Copy link
Member

Choose a reason for hiding this comment

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

Please provide sync alternatives to these that do not wait for the promise

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Member

@cramforce cramforce left a comment

Choose a reason for hiding this comment

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

This looks great overall. Some comment and please add more tests.

@@ -163,6 +164,18 @@ export class UrlReplacements {
return removeFragment(info.sourceUrl);
}));

this.setAsync_('SOURCE_URL', () => {
let promise = trackImpressionPromise;
if (!promise) {
Copy link
Member

Choose a reason for hiding this comment

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

Can we dev().assert that this is defined instead?

// If have we have gclid replace the location href with new location we get.
// TODO(@zhouyx) should we replaceState directly w/o checking gclid?
const gclid = response['gclid'];
if (gclid && adLocation) {
Copy link
Member

Choose a reason for hiding this comment

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

Honestly I think we can skip this path entirely. The Location should include a gclid param

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ok, only replace use location. So i should be promised if there's a gclid, it is inside location

// If there is a tracking_url, need to track it
// Otherwise track the location
const trackUrl = adTracking || adLocation;
if (trackUrl) {
Copy link
Member

Choose a reason for hiding this comment

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

Check if the url looks like a proxyUrl and don't make the call in that case.

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

'#gclid=' + gclid);
return;
}
win.history.replaceState(null, '', adLocation);
Copy link
Member

Choose a reason for hiding this comment

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

@dvoytenko Should this use HistoryService?

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 see a lot of usage of win.history.replaceState. @dvoytenko if I need to use HistoryService how should I use that?

'#gclid=' + gclid);
return;
}
win.history.replaceState(null, '', adLocation);
Copy link
Member

Choose a reason for hiding this comment

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

This will not work. The location should be
currentHref + queryParamsOfAdLocation

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 logic, PTAL

@@ -404,6 +404,18 @@ app.use('/examples/amp-fresh.amp.(min.|max.)?html', function(req, res, next) {
next();
});


app.use('/impression-proxy/', function(req, res) {
assertCors(req, res, ['GET']);
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 thought about this. I don't want to put a fake cookie here to get the ad server response. Is there a way to get the correct cookie here in proxy server ?(I don't think there should be due to security reason). If not I think I will stick with the fake ad response for now.

Copy link
Member

Choose a reason for hiding this comment

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

Don't think you can. You'd need to hardcode one :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

😢


/** A promise that resolve after getting info from ad server
Copy link
Contributor

Choose a reason for hiding this comment

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

Comment should be on the next 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.

fixed


/** A promise that resolve after getting info from ad server
* {Promise=}
Copy link
Contributor

Choose a reason for hiding this comment

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

?Promise

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. changed to !Promise

promise = Promise.resolve();
}
return promise.then(() => {
const url = parseUrl(this.ampdoc.win.location.href);
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 exactly the same as the sync version. Let's add a method for this, and call it from both the sync and async code.

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 added a getQueryParamData_ function. I think you have your point, it definitely reduce repeated code. but adding an extra method else seems unclear to me actually.

}
return promise.then(
this.getDocInfoValue_.bind(this, info => {
return removeFragment(info.sourceUrl);
Copy link
Contributor

Choose a reason for hiding this comment

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

Ditto duplication.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sorry, I don't quite understand. ❓

Copy link
Contributor

Choose a reason for hiding this comment

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

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 see! I don't want to change this though. it's a one line code, no need to add an extra method for it.

@@ -29,6 +29,7 @@ import {viewportForDoc} from '../viewport';
import {userNotificationManagerFor} from '../user-notification';
import {activityFor} from '../activity';
import {isExperimentOn} from '../experiments';
import {trackImpressionPromise} from '../impression.js';
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 export a function for this? Exporting a modified module value always feels very dirty, unless it's specifically for testing.

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 to getTrackImpressionPromise and add the dev().assert inside this function

@zhouyx
Copy link
Contributor Author

zhouyx commented Oct 18, 2016

My tests coverage:
for impression.js 1. should change history with a property location back. 2. should do nothing if never receive response back. 3. should do nothing if response is empty.
for url-replacement 1. QUERY_PARAM and SOURCE_URL expandAsync should wait for track impression promise to resolved. 2. For expandSync QUERY_PARAM should not wait for track impression promise, and should always get the current value.
Let me know if you find any other things that I fail to include @avimehta @cramforce

@@ -163,6 +164,14 @@ export class UrlReplacements {
return removeFragment(info.sourceUrl);
}));

this.setAsync_('SOURCE_URL', () => {
return getTrackImpressionPromise().then(
this.getDocInfoValue_.bind(this, info => {
Copy link
Member

Choose a reason for hiding this comment

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

I think there is a problem here:

Doc info eagerly reads location.href and never reads it again after.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If that's the case I think I should read getSourceUrl(this.ampdoc.win.location.href) directly.
However I see that what you are suggesting here only happens in AmpDocShadow, but not with AmpDocSingle.
So what would be the case that we are in a shadow-doc mode, and will it happen here?

Copy link
Member

Choose a reason for hiding this comment

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

You need to create a way that

  1. Reads the current value at time of evaluation
  2. Works on shadow mode (even though that is not relevant for your case.

Maybe add an update method to the document-info service?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

my questions are:

  1. Not only SOURCE_URL needs the info.sourceUrl, SOURCE_HOST SOURCE_HOSTNAME...all needs that info. When I update the source url, should I make a copy and keep the old value? (cuz SOURCE_HOST SOURCE_HOSTNAME may still needs the old values?)
  2. If we only update source url only for the case SOURCE_URL I don't see why it's necessary to add this update method to document-info. why don't we just call this.ampdoc.win.location.href since no one will ever use that again.
  3. It's interesting that we never to document-info provided url anywhere in url-replaces-impl, we always get from win.location.href directly. Why is that?

Copy link
Member

Choose a reason for hiding this comment

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

I don't have a good answer to the why. It is fine to read this directly inside of UrlReplacements, but you need to handle the shadow case.

SOURCE_URL and QUERY_PARAM should be the only affected, since the change cannot affect other parts of the URL, 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.

add a getter on source_url. I also removed url since it's not used anywhere.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

PTAL

user().assert(param,
'The first argument to QUERY_PARAM, the query string ' +
'param is required');
const url = parseUrl(this.ampdoc.win.location.href);
Copy link
Member

Choose a reason for hiding this comment

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

This doesn't have the same problem as above, because it reads the .href every time.

user().assert(param,
'The first argument to QUERY_PARAM, the query string ' +
'param is required');
const url = parseUrl(this.ampdoc.win.location.href);
Copy link
Member

Choose a reason for hiding this comment

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

@dvoytenko Should this look at the ampdoc?

const prevHref = window.location.href;
maybeTrackImpression(window);
return Promise.resolve().then(() => {
return Promise.resolve().then(() => {
Copy link
Contributor

Choose a reason for hiding this comment

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

why is this ?

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 to intentionally create some delay here before I run the test. Basicly to wait until xhr.fetchJson and viewer.whenFirstVisible to resolve.

const url = parseUrl(adLocation);
const params = parseQueryString(url.search);
const newHref = addParamsToUrl(currentHref, params);
win.history.replaceState(null, '', newHref);
Copy link
Contributor

Choose a reason for hiding this comment

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

Imagine the URLs are:
location.href = http://a.com/?a=1&b=2
adLocation = http://a.com/?a=1&b=2

The above logic gives:
newHref = http://a.com/?a=1&b=2&a=1&b=2

Copy link
Member

Choose a reason for hiding this comment

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

That is probably OK I think.

Copy link
Contributor

Choose a reason for hiding this comment

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

Why don't we just make it the servers responsibility to come up the right URL, and we just blindly replace it?

Copy link
Member

Choose a reason for hiding this comment

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

Not 100% sure what you are suggesting, but we want to minimize server changes.

Copy link
Contributor

Choose a reason for hiding this comment

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

I meant to make the protocol simple, just completely replace the URL to the location provided in the response if the host matches. That gives server the ability to remove any existing query params if it wants.

Overall, I think client is not the right place to concatenate the URL, because client does not have the full picture as the server does. It will potentially limit future changes at server side.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@lannka would it be the case that location provided in the response and the original href has different query params? If so what should we do?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@lannka persuaded me that blindly replacing URL is the right way to do here on my bus home yesterday 😄 . It will help to keep behaviors same with what we have now w/o ALP. Server most likely will keep all query params and append gclid to the end. If server really want to delete any query params that we have, we should still enable them to do that, instead of keeping it.
@cramforce What's your idea about this?

Copy link
Member

Choose a reason for hiding this comment

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

I think this is fine. I'd avoid the call to replaceState if old and new are the same.

* @return {!Promise}
*/
export function getTrackImpressionPromise() {
dev().assert(trackImpressionPromise);
Copy link
Contributor

Choose a reason for hiding this comment

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

You can return this 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.

👍

@zhouyx
Copy link
Contributor Author

zhouyx commented Oct 25, 2016

PTAL

Copy link
Member

@cramforce cramforce left a comment

Choose a reason for hiding this comment

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

Getting very close. A few more comments.

@@ -404,6 +404,18 @@ app.use('/examples/amp-fresh.amp.(min.|max.)?html', function(req, res, next) {
next();
});


app.use('/impression-proxy/', function(req, res) {
assertCors(req, res, ['GET']);
Copy link
Member

Choose a reason for hiding this comment

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

Don't think you can. You'd need to hardcode one :)

assertCors(req, res, ['GET']);
// TODO(@zhouyx): How to request real ad server response with cookie set
const body = {
'location': 'localhost:8000/examples/#gclid=1234',
Copy link
Member

Choose a reason for hiding this comment

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

Add schema and a few URL params.

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. but not sure this is what you want? Please let me know


/**
* Emit a HTTP request to a destination defined on the incoming URL.
* Protected by experiment.
* @param {!Window} win
*/
export function maybeTrackImpression(win) {
let resolveImpression;

trackImpressionPromise = new Promise(resolve => {
Copy link
Member

Choose a reason for hiding this comment

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

Maybe add a ForTesting function that sets it back to null?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ForTesting function added. But why can't we just export resolveImpression for testing?

viewer.whenFirstVisible().then(() => {
invoke(win, clickUrl);
// TODO(@zhouyx) We need a timeout here?
Copy link
Member

Choose a reason for hiding this comment

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

yes, please. 8s?

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 remembered you said no. 😄 😈

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 trackUrl = adTracking || adLocation;

if (trackUrl && !isProxyOrigin(trackUrl)) {
new Image().src = trackUrl;
Copy link
Member

Choose a reason for hiding this comment

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

Add comment as to why and what this does.

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 url = parseUrl(adLocation);
const params = parseQueryString(url.search);
const newHref = addParamsToUrl(currentHref, params);
win.history.replaceState(null, '', newHref);
Copy link
Member

Choose a reason for hiding this comment

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

I think replaceState is optional for us. Check if it exists and just don't call it if it does not.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

hmmm, add the check for if(!win.history.replaceState). But when would it be null?

Copy link
Member

Choose a reason for hiding this comment

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

This is not supported by some browsers that otherwise work with AMP.

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 see. we just do nothing at all for this case? I mean we can probably use other way to attach the params to location.hash

const url = parseUrl(this.ampdoc.win.location.href);
const params = parseQueryString(url.search);
return (typeof params[param] !== 'undefined') ?
params[param] :
Copy link
Member

Choose a reason for hiding this comment

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

Nit: Indent +2
I also prefer the ? and : on the newline

Copy link
Contributor

Choose a reason for hiding this comment

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

+! I also like to write it in this format:

  const x = isConditionMatched(state)
      ? calculateA(value)
      : calculateB(value);

Copy link
Contributor

Choose a reason for hiding this comment

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

I also prefer the ? and : on the newline

Ick.

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. @lannka since everything can fit in the new line, i follow the format of

const x = isCondiftionMatched(state)
       ? A : B

@cramforce
Copy link
Member

LOL :)

On Tue, Oct 25, 2016 at 4:47 PM, Yuxuan Zhou notifications@github.com
wrote:

@zhouyx commented on this pull request.

In src/impression.js #5606:

viewer.whenFirstVisible().then(() => {

  • invoke(win, clickUrl);
  • // TODO(@zhouyx) We need a timeout here?

i remembered you said no. 😄 😈


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
#5606, or mute the thread
https://github.com/notifications/unsubscribe-auth/AAFeTyKh6TrNoxj7LEnPVJxBwcqz9GBOks5q3pT7gaJpZM4KWj_w
.

@zhouyx
Copy link
Contributor Author

zhouyx commented Oct 26, 2016

Addressed comments.

// tracking_url: URL that should be requested to track click
// gclid: The conversion tracking value
const body = {
'location': 'localhost:8000/examples/#gclid=1234?gclid=RANDOM&r=1&url=SOURCE_URL',
Copy link
Member

Choose a reason for hiding this comment

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

Please add an actual querystring, not a fragment

Copy link
Member

Choose a reason for hiding this comment

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

Those should be concrete values, not stuff like RANDOM, right?

Copy link
Contributor Author

@zhouyx zhouyx Oct 27, 2016

Choose a reason for hiding this comment

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

I honestly don't know how to write a real querystring. 😢 Can you give me an example or lead me to a certain doc i can take a look at?

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 always thought querystring is part of the location.hash. big mistake : (

maybeTrackImpression(window);
return Promise.resolve().then(() => {
return Promise.resolve().then(() => {
expect(window.location.href).to.contain('gclid=123456');
Copy link
Member

Choose a reason for hiding this comment

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

Make this an assertEqual test to make sure the URL looks good.

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

'location': 'test_location?gclid=123456',
});
};
const prevHref = window.location.href;
Copy link
Member

Choose a reason for hiding this comment

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

Can you spice this one up a bit with a few query params.

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


xhr.fetchJson = () => {
return Promise.resolve({
'location': 'test_location?gclid=123456',
Copy link
Member

Choose a reason for hiding this comment

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

Add a few more query params.

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

win.location = parseUrl('https://wrong.com');
sandbox.stub(trackPromise, 'getTrackImpressionPromise', () => {
return new Promise(resolve => {
win.location = parseUrl('https://example.com#gclid=123456');
Copy link
Member

Choose a reason for hiding this comment

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

Why the # part?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

my bad, changed to ?

@cramforce
Copy link
Member

Just something like ?gclid=1234&foo=bar&example=123
and similar in the test suite.

On Wed, Oct 26, 2016 at 5:17 PM, Yuxuan Zhou notifications@github.com
wrote:

@zhouyx commented on this pull request.

In build-system/server.js
#5606:

@@ -404,6 +404,21 @@ app.use('/examples/amp-fresh.amp.(min.|max.)?html', function(req, res, next) {
next();
});

+app.use('/impression-proxy/', function(req, res) {

  • assertCors(req, res, ['GET']);
  • // Fake response with the following optional fields:
  • // location: The Url the that server would have sent redirect to w/o ALP
  • // tracking_url: URL that should be requested to track click
  • // gclid: The conversion tracking value
  • const body = {
  • 'location': 'localhost:8000/examples/#gclid=1234?gclid=RANDOM&r=1&url=SOURCE_URL',

I honestly don't know how to write a real querystring. Can you give me an
example or lead to a certain doc i can take a look at?


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
#5606, or mute the thread
https://github.com/notifications/unsubscribe-auth/AAFeT9ejq9lWq_9kU1ix0K1BA8_SyTpVks5q3-2LgaJpZM4KWj_w
.

@cramforce
Copy link
Member

The Google JS style guide is on your side, but it is wrong :)

On Wed, Oct 26, 2016 at 10:38 AM, Justin Ridgewell <notifications@github.com

wrote:

@jridgewell commented on this pull request.

In src/service/url-replacements-impl.js
#5606:

@@ -541,6 +548,25 @@ export class UrlReplacements {
}

/**

  • * Return the QUERY_PARAM from the current location href
  • * @param {*} param
  • * @param {string} defaultValue
  • * @param {boolean=} opt_sync
  • * @return {string}
  • */
  • getQueryParamData_(param, defaultValue, opt_sync) {
  • user().assert(param,
  •    'The first argument to QUERY_PARAM, the query string ' +
    
  •    'param is required');
    
  • user().assert(typeof param == 'string', 'param should be a string');
  • const url = parseUrl(this.ampdoc.win.location.href);
  • const params = parseQueryString(url.search);
  • return (typeof params[param] !== 'undefined') ?
  •  params[param] :
    

I also prefer the ? and : on the newline

Ick.


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
#5606, or mute the thread
https://github.com/notifications/unsubscribe-auth/AAFeT-NrpUcbg-BI339lwKPLkpdsc50lks5q35AXgaJpZM4KWj_w
.

Copy link
Member

@cramforce cramforce left a comment

Choose a reason for hiding this comment

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

LGTM

const url = parseUrl(adLocation);
const params = parseQueryString(url.search);
const newHref = addParamsToUrl(currentHref, params);
win.history.replaceState(null, '', newHref);
Copy link
Member

Choose a reason for hiding this comment

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

I think this is fine. I'd avoid the call to replaceState if old and new are the same.

@@ -56,11 +56,16 @@ export function documentInfoForDoc(nodeOrDoc) {
canonicalUrl = parseUrl(canonicalTag.href).href;
}
const pageViewId = getPageViewId(ampdoc.win);
return {url, sourceUrl, canonicalUrl, pageViewId};
const res = {sourceUrl, canonicalUrl, pageViewId};
Object.defineProperty(res, 'sourceUrl', {
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 really 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.

It's necessary, because of this comment

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 add a getter method instead (of a getter property).

I think the best solution would be to have our History class update this field when the URL changes. But that can wait for another time.

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.

// Timeout invoke promise after 8s and resolve trackImpressionPromise.
timerFor(win).timeoutPromise(TIMEOUT_VALUE, promise,
'timeout waiting for ad server response').catch(() => {
}).then(() => {
Copy link
Contributor

Choose a reason for hiding this comment

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

This can be done with resolveImpression(timerFor()...), which will skip the deferment from the #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.

eh I don't quite get, resolveImpression() is a simple resolve function that don't receive any param? you mean make the trackImpressionPromise resolve after 8s

trackImpressionPromise = new Promise(resolve => {
   const id = timerFor(win).delay(resolve, 8000);
   resolveImpression = () => {
      timerFor(win).cancel(id);
      resolve();
   }
});

I don't see this one is much better than the current approach.

Copy link
Contributor

Choose a reason for hiding this comment

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

resolveImpression is the resolve (I hate that that's what everyone calls it, it should be fulfill) given to you by the Promise constructor. It takes a single param, and if that param is a Promise itself, it will adopt the param's state (will resolve when the param resolves, with whatever the param resolves to).

}
const currentHref = win.location.href;
const url = parseUrl(adLocation);
const params = parseQueryString(url.search);
Copy link
Contributor

Choose a reason for hiding this comment

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

Can't you just parseQueryString(adLocation)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

parseQueryString expects the input to be a query string either ?a=1&b=1 or a=1&b=1

@@ -163,6 +164,14 @@ export class UrlReplacements {
return removeFragment(info.sourceUrl);
}));

this.setAsync_('SOURCE_URL', () => {
return getTrackImpressionPromise().then(
this.getDocInfoValue_.bind(this, info => {
Copy link
Contributor

Choose a reason for hiding this comment

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

This #bind is totally unnecessary.

'param is required');
user().assert(typeof param == 'string', 'param should be a string');
const url = parseUrl(this.ampdoc.win.location.href);
const params = parseQueryString(url.search);
Copy link
Contributor

Choose a reason for hiding this comment

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

ditto.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

same here

* Return the QUERY_PARAM from the current location href
* @param {*} param
* @param {string} defaultValue
* @param {boolean=} opt_sync
Copy link
Contributor

Choose a reason for hiding this comment

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

Unused param.

@zhouyx
Copy link
Contributor Author

zhouyx commented Oct 27, 2016

@lannka PTAL

@zhouyx
Copy link
Contributor Author

zhouyx commented Oct 27, 2016

@jridgewell ptal

get: () => getSourceUrl(ampdoc.getUrl()),
});
const res = {
get sourceUrl() {
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 still a getter property. Remove the get part, can it turns into a getter method. Maybe call it getSourceUrl.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

you want user to call data.getSourceUrl(), instead of data.sourceUrl? I think this get makes sure they can access sourceUrl just like before and that's what we want.

@cramforce
Copy link
Member

I think we need to go with the getter property for backward compat.
Switching to a method would be better in the long term.

On Thu, Oct 27, 2016 at 5:15 PM, Yuxuan Zhou notifications@github.com
wrote:

@zhouyx commented on this pull request.

In src/document-info.js #5606:

@@ -56,10 +54,13 @@ export function documentInfoForDoc(nodeOrDoc) {
canonicalUrl = parseUrl(canonicalTag.href).href;
}
const pageViewId = getPageViewId(ampdoc.win);

  •    const res = {sourceUrl, canonicalUrl, pageViewId};
    
  •    Object.defineProperty(res, 'sourceUrl', {
    
  •      get: () => getSourceUrl(ampdoc.getUrl()),
    
  •    });
    
  •    const res = {
    
  •      get sourceUrl() {
    

you want user to call data.getSourceUrl(), instead of data.sourceUrl? I
think this get makes sure they can access sourceUrl just like before and
that's what we want.


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
#5606, or mute the thread
https://github.com/notifications/unsubscribe-auth/AAFeT7pcB4eY18DeYK5I0Iu3X3UVj-P0ks5q4T6jgaJpZM4KWj_w
.

@zhouyx zhouyx merged commit aa205dc into ampproject:master Oct 28, 2016
@zhouyx zhouyx deleted the alp-impression branch October 28, 2016 19:44
samiamorwas pushed a commit to samiamorwas/amphtml that referenced this pull request Oct 31, 2016
…mp_reddit_extension

* 'master' of https://github.com/ampproject/amphtml: (121 commits)
  `#setOwner` should rewrite `resource.owner_` value if it exist (ampproject#5898)
  Defer full upgrade until the element is connected (ampproject#5908)
  Skeleton for an amp-animation component (ampproject#5891)
  Use upgrade callback to pick the correct carousel class. (ampproject#5899)
  Add alert role to user-notification by default (ampproject#5896)
  Fix test-iframe-createIframeWithMessageStub failures on older browsers (ampproject#5895)
  Make screen readers announce changes in the slides carousel. (ampproject#5892)
  Separate out implementaton of document-info and remove dependency of … (ampproject#5864)
  Fixed assertion in key fetch function. (ampproject#5854)
  Track impression on amp landing page (ampproject#5606)
  Backward compatible way of stringifying Uint8Array (ampproject#5881)
  Ensure that a friendly-iframe embed cancels any boilerplate when ready (ampproject#5863)
  Fix amp-forms broken and flakey tests. (ampproject#5835)
  Make SW test properly skip when Request == undefined (ampproject#5876)
  Add Preamble section to AMP Cache Guidelines (ampproject#5873)
  Adding Adverline to amp-ad (ampproject#5829)
  Clarify cache guidelines (ampproject#5874)
  Consider it as non-viewer mode if there is no "origin" in hash param. (ampproject#5867)
  [amp-youtube] autoplay and test suite to run across all video players that implement the video API (ampproject#5765)
  Update Forms Docs to reflect availability. (ampproject#5815)
  ...
Lith pushed a commit to Lith/amphtml that referenced this pull request Dec 22, 2016
* wip

* get response back from proxy server

* first impl

* another approach

* fix

* fix type check

* wip

* add getter

* add getter to sourceUrl

* address comments, add timeout test

* test fix

* small fix

* use getter method
Lith pushed a commit to Lith/amphtml that referenced this pull request Dec 22, 2016
* wip

* get response back from proxy server

* first impl

* another approach

* fix

* fix type check

* wip

* add getter

* add getter to sourceUrl

* address comments, add timeout test

* test fix

* small fix

* use getter method
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

5 participants