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
Conversation
'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', |
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.
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', |
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.
LOL :)
request(options, (error, response, body) => { | ||
if (!error && response.statusCode == 200) { | ||
res.send(body); | ||
} |
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.
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 |
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.
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'); |
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 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. |
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 desired. Just delete the branch.
let gclid = response['gclid']; | ||
if (!gclid) { | ||
if (adLocation.indexOf('gclid=') > 0) { | ||
gclid = adLocation.substr(adLocation.indexOf('gclid=') + 6); |
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 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) { |
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.
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.
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.
+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'); |
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.
Above use the location for the image URL if no tracking_url was provided.
Can you also add some tests to ensure that the variables from |
} | ||
} | ||
if (gclid) { | ||
win.location.hash = '#gclid=' + gclid; |
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.
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. |
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.
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) { |
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 will never be null
* Send the url to ad server and wait for its response | ||
* @param {!Window} win | ||
* @param {string} clickUrl | ||
* @return {!Promise} |
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.
{@Promise<!JSONType>}
const adLocation = response['location']; | ||
|
||
// If there's a tracking_url, need to redirect to that url. | ||
const adTrackingUrl = response['tracking_url']; |
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.
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) { |
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'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) { |
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.
+1
it can be achieved by pushing the gclid as a service:
getService('gclid', gclid);
0dc02e7
to
366a535
Compare
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', () => { |
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.
Please provide sync alternatives to these that do not wait for the promise
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 added. I didn't change the previous this.set_
https://github.com/zhouyx/amphtml/blob/366a535bf7a4c0c0e33125637c1052593a5bd199/src/service/url-replacements-impl.js#L201
it's still 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.
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) { |
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 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) { |
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.
Honestly I think we can skip this path entirely. The Location
should include a gclid
param
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.
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) { |
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.
Check if the url looks like a proxyUrl and don't make the call in that case.
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
'#gclid=' + gclid); | ||
return; | ||
} | ||
win.history.replaceState(null, '', adLocation); |
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.
@dvoytenko Should this use HistoryService
?
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 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); |
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 will not work. The location should be
currentHref
+ queryParamsOfAdLocation
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 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']); |
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 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.
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.
Don't think you can. You'd need to hardcode one :)
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 promise that resolve after getting info from ad server |
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.
Comment should be on the next 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.
fixed
|
||
/** A promise that resolve after getting info from ad server | ||
* {Promise=} |
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.
?Promise
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. changed to !Promise
promise = Promise.resolve(); | ||
} | ||
return promise.then(() => { | ||
const url = parseUrl(this.ampdoc.win.location.href); |
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 exactly the same as the sync version. Let's add a method for this, and call it from both the sync and async code.
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 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); |
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 duplication.
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.
Sorry, I don't quite understand. ❓
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.
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 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'; |
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 export a function for this? Exporting a modified module value always feels very dirty, unless it's specifically for testing.
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 to getTrackImpressionPromise
and add the dev().assert
inside this function
My tests coverage: |
@@ -163,6 +164,14 @@ export class UrlReplacements { | |||
return removeFragment(info.sourceUrl); | |||
})); | |||
|
|||
this.setAsync_('SOURCE_URL', () => { | |||
return getTrackImpressionPromise().then( | |||
this.getDocInfoValue_.bind(this, info => { |
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 think there is a problem here:
Doc info eagerly reads location.href
and never reads it again after.
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.
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?
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.
You need to create a way that
- Reads the current value at time of evaluation
- Works on shadow mode (even though that is not relevant for your case.
Maybe add an update method to the document-info
service?
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 questions are:
- Not only
SOURCE_URL
needs theinfo.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? (cuzSOURCE_HOST
SOURCE_HOSTNAME
may still needs the old values?) - 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 todocument-info
. why don't we just callthis.ampdoc.win.location.href
since no one will ever use that again. - It's interesting that we never to
document-info
provided url anywhere inurl-replaces-impl
, we always get fromwin.location.href
directly. Why is that?
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 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?
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.
add a getter on source_url
. I also removed url
since it's not used anywhere.
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.
PTAL
user().assert(param, | ||
'The first argument to QUERY_PARAM, the query string ' + | ||
'param is required'); | ||
const url = parseUrl(this.ampdoc.win.location.href); |
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 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); |
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.
@dvoytenko Should this look at the ampdoc?
const prevHref = window.location.href; | ||
maybeTrackImpression(window); | ||
return Promise.resolve().then(() => { | ||
return Promise.resolve().then(() => { |
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 is this ?
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 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); |
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.
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
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 is probably OK I think.
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 don't we just make it the servers responsibility to come up the right URL, and we just blindly replace it?
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 100% sure what you are suggesting, but we want to minimize server changes.
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 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.
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.
@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?
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.
@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?
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 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); |
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.
You can return this 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.
👍
b67900b
to
b521f9a
Compare
PTAL |
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.
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']); |
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.
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', |
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.
Add schema and a few URL 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.
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 => { |
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.
Maybe add a ForTesting
function that sets it back to 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.
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? |
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, please. 8s?
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 remembered you said no. 😄 😈
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 trackUrl = adTracking || adLocation; | ||
|
||
if (trackUrl && !isProxyOrigin(trackUrl)) { | ||
new Image().src = trackUrl; |
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.
Add comment as to why and what this does.
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 url = parseUrl(adLocation); | ||
const params = parseQueryString(url.search); | ||
const newHref = addParamsToUrl(currentHref, params); | ||
win.history.replaceState(null, '', newHref); |
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 think replaceState
is optional for us. Check if it exists and just don't call it if it does not.
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.
hmmm, add the check for if(!win.history.replaceState)
. But when would it be 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.
This is not supported by some browsers that otherwise work with AMP.
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 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] : |
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: Indent +2
I also prefer the ?
and :
on the newline
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 also like to write it in this format:
const x = isConditionMatched(state)
? calculateA(value)
: calculateB(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.
I also prefer the ? and : on the newline
Ick.
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. @lannka since everything can fit in the new line, i follow the format of
const x = isCondiftionMatched(state)
? A : B
LOL :) On Tue, Oct 25, 2016 at 4:47 PM, Yuxuan Zhou notifications@github.com
|
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', |
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.
Please add an actual querystring, not a fragment
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.
Those should be concrete values, not stuff like RANDOM
, 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.
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?
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 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'); |
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 assertEqual
test to make sure the URL looks good.
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
'location': 'test_location?gclid=123456', | ||
}); | ||
}; | ||
const prevHref = window.location.href; |
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 you spice this one up a bit with a few query 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.
done
|
||
xhr.fetchJson = () => { | ||
return Promise.resolve({ | ||
'location': 'test_location?gclid=123456', |
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.
Add a few more query 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.
done
win.location = parseUrl('https://wrong.com'); | ||
sandbox.stub(trackPromise, 'getTrackImpressionPromise', () => { | ||
return new Promise(resolve => { | ||
win.location = parseUrl('https://example.com#gclid=123456'); |
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 the #
part?
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 bad, changed to ?
Just something like ?gclid=1234&foo=bar&example=123 On Wed, Oct 26, 2016 at 5:17 PM, Yuxuan Zhou notifications@github.com
|
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
|
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.
LGTM
const url = parseUrl(adLocation); | ||
const params = parseQueryString(url.search); | ||
const newHref = addParamsToUrl(currentHref, params); | ||
win.history.replaceState(null, '', newHref); |
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 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', { |
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 really 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.
It's necessary, because of this comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
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.
// Timeout invoke promise after 8s and resolve trackImpressionPromise. | ||
timerFor(win).timeoutPromise(TIMEOUT_VALUE, promise, | ||
'timeout waiting for ad server response').catch(() => { | ||
}).then(() => { |
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 can be done with resolveImpression(timerFor()...)
, which will skip the deferment from the #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.
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.
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.
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); |
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't you just parseQueryString(adLocation)
?
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.
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 => { |
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 #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); |
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.
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.
same here
* Return the QUERY_PARAM from the current location href | ||
* @param {*} param | ||
* @param {string} defaultValue | ||
* @param {boolean=} opt_sync |
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.
Unused param.
@lannka PTAL |
@jridgewell ptal |
get: () => getSourceUrl(ampdoc.getUrl()), | ||
}); | ||
const res = { | ||
get sourceUrl() { |
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 still a getter property. Remove the get
part, can it turns into a getter method. Maybe call it getSourceUrl
.
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.
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.
I think we need to go with the getter property for backward compat. On Thu, Oct 27, 2016 at 5:15 PM, Yuxuan Zhou notifications@github.com
|
…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) ...
* 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
* 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
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.