-
Notifications
You must be signed in to change notification settings - Fork 81
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
Moving anon user tracking from image element to form submit #433
base: main
Are you sure you want to change the base?
Conversation
@@ -27,4 +28,5 @@ | |||
{%- if header_campaign %}{{ campaign_script() }}{% endif %} | |||
{% assets "js_tinymce" %}<script src="{{ ASSET_URL }}" type="text/javascript"></script>{% endassets %} | |||
{% block footerscripts %}{% endblock %} | |||
{{ anon_user_script() }} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be in layout.html.jinja2
only. We have a problem if we’re copy pasting an entire block between templates.
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 went through the templates, and they need some reorganization. Some blocks have been redefined in several places. I can fix them, but seems a little off topic for this PR. Maybe a separate PR for 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 will change with @vidya-ram's PWA PR (#425). We should revisit this PR once that is merged.
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.
@jace alright.
hasjob/templates/macros.html.jinja2
Outdated
{%- if not g.user and not g.anon_user %} | ||
<script type="text/javascript"> | ||
$(function () { | ||
var sniffurl = "{{ url_for('sniffle') }}"; |
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.
“Sniff” is the wrong term for this. We’re looking for an interactive session.
hasjob/templates/macros.html.jinja2
Outdated
} | ||
window.onscroll = function (e) { | ||
if (!sniffed) { | ||
sniff("scolll"); |
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.
Spelling.
hasjob/views/helper.py
Outdated
@@ -28,14 +28,38 @@ | |||
gif1x1 = 'R0lGODlhAQABAJAAAP8AAAAAACH5BAUQAAAALAAAAAABAAEAAAICBAEAOw=='.decode('base64') | |||
|
|||
|
|||
@app.route('/_sniffle.gif') | |||
@app.route('/_sniffle', methods=['POST']) |
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 now be /api/1/anonsession
. This should also move into an views/api.py
.
hasjob/views/helper.py
Outdated
|
||
1. If there's g.user and session['anon_user'], it loads that anon_user and tags with user=g.user, then removes anon | ||
2. If there's no g.user and no session['anon_user'] and form submitted token matches session['au'], sets session['anon_user'] = 'test' | ||
3. If there's no g.user and there is session['au'] != form['token'], loads g.anon_user |
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.
2 and 3 seem to be reversed. Also, you don’t need a test token in session['au']
anymore. A valid form CSRF token is enough. Our entire test token mechanism can be removed.
hasjob/views/helper.py
Outdated
session['au'] = g.anon_user.id | ||
session.permanent = True | ||
|
||
return Response("OK") |
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 reply with text. Return {'status': 'ok'}
and wrap the view with render_with(json=True)
.
hasjob/views/helper.py
Outdated
if anon_user and g.user: | ||
# we have anon user id in session['au'], set anon_user.user to current user | ||
anon_user.user = g.user | ||
g.db_commit_needed = True |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ugh, what is this? We should be doing automatic database commits if there’s a dirty session and no exception was raised. Better than such hacky flags.
hasjob/views/helper.py
Outdated
session.permanent = True | ||
if 'impressions' in session: | ||
elif not g.user: | ||
session['au'] = u'test-' + unicode(uuid4()) |
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 no longer needed since we’ll use the CSRF token cookie now.
hasjob/views/helper.py
Outdated
@@ -313,7 +302,7 @@ def session_jobpost_ab(): | |||
Returns the user's B-group assignment (NA, True, False) for all jobs shown to the user | |||
in the current event session (impressions or views) as a dictionary of {id: bgroup} | |||
""" | |||
if not g.esession.persistent: | |||
if g.esession and not g.esession.persistent: |
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.
When does g.esession
not exist?
@@ -31,4 +32,5 @@ | |||
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script> | |||
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/c3/0.4.14/c3.min.js"></script> |
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.
@jace should these static files be in tablayout template? Shouldn't they be in individual templates that inherit them and needs c3/d3?
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 now use C3 enough that it should be in the top level requirements, and all these direct references should be removed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A few notes. Will need another review after the other PRs are merged.
@@ -27,4 +28,5 @@ | |||
{%- if header_campaign %}{{ campaign_script() }}{% endif %} | |||
{% assets "js_tinymce" %}<script src="{{ ASSET_URL }}" type="text/javascript"></script>{% endassets %} | |||
{% block footerscripts %}{% endblock %} | |||
{{ anon_user_script() }} |
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 change with @vidya-ram's PWA PR (#425). We should revisit this PR once that is merged.
@@ -31,4 +32,5 @@ | |||
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script> | |||
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/c3/0.4.14/c3.min.js"></script> |
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 now use C3 enough that it should be in the top level requirements, and all these direct references should be removed.
hasjob/views/api.py
Outdated
db.session.commit() | ||
|
||
if g.anon_user: | ||
session['au'] = g.anon_user.id |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are we using session['au']
for two different sorts of content? That doesn't seem clean.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm using session['au']
only to store anon user ID now. if it exists in cookie, it'll contain the anon user id.
hasjob/views/helper.py
Outdated
from ..models import (db, JobCategory, JobPost, JobType, BoardJobPost, Tag, JobPostTag, | ||
Campaign, CampaignView, CampaignAnonView, EventSessionBase, EventSession, UserEventBase, | ||
UserEvent, JobImpression, JobViewSession, AnonUser, campaign_event_session_table, | ||
JobLocation, PAY_TYPE) | ||
from ..utils import scrubemail, redactemail, cointoss | ||
|
||
|
||
gif1x1 = 'R0lGODlhAQABAJAAAP8AAAAAACH5BAUQAAAALAAAAAABAAEAAAICBAEAOw=='.decode('base64') |
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 line can go. It's not needed anymore.
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.
@jace there is a view_application_email_gif
endpoint where it's being used
hasjob/hasjob/views/listing.py
Line 397 in 41450f7
return gif1x1, 200, { |
@@ -224,3 +224,37 @@ | |||
</script> | |||
{%- endwith %} | |||
{%- endmacro -%} | |||
|
|||
{%- macro anon_user_script() -%} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this be moved into app.js
? It adds bulk to every page otherwise (to pages served to bots).
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.
But if we move it to app.js
then it'll load with all page load regardless of bots or humans? Also, will have to hardcode the api endpoint.
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 load only once because app.js
is cached.
hasjob/views/api.py
Outdated
""" | ||
Load anon user: | ||
|
||
1. If client returns valid csrf token, create and set g.anon_user |
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.
Sends, not returns.
hasjob/views/api.py
Outdated
""" | ||
csrf_form = FlaskForm() | ||
if csrf_form.validate_on_submit(): | ||
# This client sent us back valid csrf token |
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.
"sent us", not "sent us back"
hasjob/views/api.py
Outdated
2. if g.anon_user exists, set session['au'] to anon_user.id | ||
""" | ||
csrf_form = FlaskForm() | ||
if csrf_form.validate_on_submit(): |
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 not g.user and not g.anon_user and csrf_form.validate_on_submit():
hasjob/views/api.py
Outdated
|
||
if g.anon_user: | ||
session['au'] = g.anon_user.id | ||
g.esession.load_from_cache(session['au'], UserEvent) |
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.
What does this do? session['au']
contains a brand new id. How is it a cache reference?
hasjob/views/helper.py
Outdated
g.anon_user = None # Could change below | ||
g.event_data = {} # Views can add data to the current pageview event | ||
g.anon_user = None | ||
g.event_data = {} |
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.
Keep the comments.
hasjob/views/helper.py
Outdated
else: | ||
if g.user: | ||
anon_user.user = g.user | ||
session.pop('au', None) |
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.
Under if g.user:
add g.anon_user = None
, and then an else
clause before the following line. You don't want to have both g.user
and g.anon_user
hasjob/views/helper.py
Outdated
if g.esession: | ||
session['es'] = g.esession.uuid | ||
|
||
if g.anon_user and 'impressions' in session: | ||
# Run this in the foreground since we need this later in the request for A/B display consistency. | ||
# This is most likely being called from the UI-non-blocking sniffle.gif anyway. | ||
save_impressions(g.esession.id, session.pop('impressions').values(), 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.
Move this to a function that is called both here and in the the anonsession endpoint after setting g.anon_user. Comment needs to change as well.
hasjob/views/helper.py
Outdated
@@ -301,7 +257,8 @@ def record_views_and_events(response): | |||
jobpost_id=g.jobpost_viewed[0], | |||
bgroup=g.jobpost_viewed[1]) | |||
else: | |||
g.esession.save_to_cache(session['au']) | |||
if 'au' in session: | |||
g.esession.save_to_cache(session['au']) |
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.
Cache has to be saved before we have an anonymous user. It's not relevant after.
@iambibhas Here is how this is supposed to work. First, we recognise three states for a user's identity:
When the user transitions from stage 2 to stage 3, we want to preserve their browsing history. This is why we do The transition from stage 1 to 2 is trickier. At stage 1, it could be a scraping bot causing a lot of traffic. We don't want to track session history for that, but we also don't want to lose the history of a client that turns out to be an anon user. Therefore:
A bot will also create a Redis EventSession entry, but it will be auto-discarded in five minutes. |
hasjob/views/api.py
Outdated
if 'impression' in session: | ||
rq_save_impressions(g.esession.id, session.pop('impressions').values(), now, delay=False) | ||
|
||
return Response({'status': 'ok'}) |
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 needs to be jsonified.
hasjob/views/api.py
Outdated
if g.anon_user: | ||
session['au'] = g.anon_user.id | ||
if 'impression' in session: | ||
rq_save_impressions(g.esession.id, session.pop('impressions').values(), now, delay=False) |
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.
Typo. impression
or impressions
?
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.
Use the main save_impressions
function.
hasjob/views/helper.py
Outdated
def rq_save_impressions(session_id, impressions, viewed_time, delay=True): | ||
func = save_impressions.delay if delay else save_impressions | ||
print func, session_id | ||
func(session_id, impressions, viewed_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.
You don't need this function. Call save_impressions
or save_impressions.delay
directly from wherever you need it.
hasjob/views/helper.py
Outdated
if 'au' in session and session['au'] is not None: | ||
if unicode(session['au']).startswith('test'): | ||
# old test token that we no longer need | ||
session.pop('au', None) |
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.
session.pop('au')
since it's guaranteed present. If it's not, that's definitely an error worth raising for us to investigate.
hasjob/views/helper.py
Outdated
if g.anon_user and 'impressions' in session: | ||
# Run this in the foreground | ||
# since we need this later in the request for A/B display consistency. | ||
rq_save_impressions(g.esession.id, session.pop('impressions').values(), now, delay=False) |
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.
Use regular save_impressions
.
hasjob/views/helper.py
Outdated
@@ -192,7 +150,7 @@ def load_user_data(user): | |||
|
|||
@app.after_request | |||
def record_views_and_events(response): | |||
if hasattr(g, 'db_commit_needed') and g.db_commit_needed: | |||
if len(db.session.dirty) > 0 or len(db.session.new) > 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.
I'd move this into a generic function in Coaster so that this call can be commit_if_necessary(db.session)
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.
Or we could just do db.session.commit()
without an if
condition. What is the impact of committing an empty transaction?
|
Anytime the site is loaded and there is -
the anon user session is set using a form submit.
Fixes #409.