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

17 add timezone map #77

Merged
merged 1 commit into from
Aug 29, 2017
Merged

17 add timezone map #77

merged 1 commit into from
Aug 29, 2017

Conversation

seanson
Copy link

@seanson seanson commented Aug 26, 2017

Relevant Issue:

Implements the changes requested in #17

Description of Changes

This commit adds two features:
- An enhancement to the existing celery task for gathering user information. We save a large chunk of the most common time zones for display purposes. We store a JSON record in the database so we can do historical tracking of timezone membership.
- A new partial template for the Slack module. This includes some Jinja2 html templating for the Leaflet map and a JS script that incldues timezone -> coordinate mapping and the Leaflet map builder.
This mapping is mostly generated from the public domain tzdata's "zone1970.tab" file, converted from iso6709 format and and dropped as a static mapping inside the javascript.

Screenshots, if applicable

screen shot 2017-08-26 at 11 17 53 pm

Requirements

  • Tests
  • Documentation

@seanson
Copy link
Author

seanson commented Aug 26, 2017

If anyone would like to test this locally, the user capture script can be invoked manually with ./manage.py shell -c 'from pyslackers_website.slack.tasks import capture_snapshot_of_user_count; capture_snapshot_of_user_count.apply()'

# image:
# context: .
# command: celery -A config beat -l debug -S django
scheduler:
Copy link
Contributor

Choose a reason for hiding this comment

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

This shouldn't be necessary when using the devserver command, that command auto starts celery beat+worker when running locally

Copy link
Author

Choose a reason for hiding this comment

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

Ooh, nice. I'll drop this from the compose.

# image:
# context: .
# command: celery -A config worker -l debug
workers:
Copy link
Contributor

Choose a reason for hiding this comment

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

Same, this shouldn't be necessary here. The intend of the default compose file is just for run-time services locally

@@ -0,0 +1,401 @@
const timezone_mapping = {
Copy link
Contributor

Choose a reason for hiding this comment

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

Since this is just vanilla JS+jquery, it's best to wrap this in an IIFE, or jquery is available put it on the document on ready handler

Copy link
Contributor

Choose a reason for hiding this comment

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

I need to do this for the small other bits of js...

@@ -7,3 +7,4 @@ class Membership(models.Model):
deleted_count = models.IntegerField()
bot_count = models.IntegerField()
timestamp = models.DateTimeField(auto_now_add=True)
tz_count_json = models.TextField(default="")
Copy link
Contributor

Choose a reason for hiding this comment

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

If the rationale of grabbing all this is some tracking and such, should we use the postgres json type so we can query data without loading it all into application memory ?

Copy link
Contributor

Choose a reason for hiding this comment

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

I haven't used the postgres JSON type in django before, but I see the edit to the migration but not this model - I believe that is required for it to act as we want

},
"type": "Feature",
"properties": {
"popupContent": `<h3>${timezone}: ${member_count} members</h3>`,
Copy link
Contributor

Choose a reason for hiding this comment

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

The key naming is inconsistent, is this required for the map or can we use snake_case?

Copy link
Author

Choose a reason for hiding this comment

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

Camelcase is required for the map, I'll switch the other counts to camel case for consistency.

time_zones.append(tz)

counter = Counter(time_zones)
tz_count_json = dumps(dict(counter))
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice usage of counter, however I would prefer to not manually dump the json and let the orm handle it. https://docs.djangoproject.com/en/1.11/ref/contrib/postgres/fields/#jsonfield

Secondly, from what I recall with the slack API and the Django cache - it should work with the object you're caching since it's a non-complex dict

Copy link
Contributor

Choose a reason for hiding this comment

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

(continued, since reviews on mobile are : 💩)

For item 2, any reason to manually dump vs allowing the cache abstraction to deal with it? One favorable reason is json vs pickle, but the context and cache accept Python types so I favor that

@@ -0,0 +1,14 @@
{% load static %}
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.2.0/leaflet.js"></script>
Copy link
Contributor

Choose a reason for hiding this comment

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

There should be extension points in the base template to put these in the right spot (on mobile, so double check, but should be extra_script I think).

{% load static %}
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.2.0/leaflet.js"></script>
<!-- leaflet requires CSS for map applied before init -->
<link rel="stylesheet" href="{% static 'css/member_map.css' %}" />
Copy link
Contributor

Choose a reason for hiding this comment

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

Also they should be an extra_links template block

<link rel="stylesheet" href="{% static 'css/member_map.css' %}" />

<div class="ui container">
<div id='leaflet_member_map' />
Copy link
Contributor

Choose a reason for hiding this comment

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

Quoting is inconsistent with the file, please make these double quotes like the rest

@@ -0,0 +1,6 @@
#leaflet_member_map {
Copy link
Contributor

Choose a reason for hiding this comment

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

This won't be very responsive, can the available classes from semantic work for this so it responds and collapses?

@mattrasband
Copy link
Contributor

I'm out of town until tomorrow but will test it out when I'm back home.

@seanson
Copy link
Author

seanson commented Aug 28, 2017

Changes for all requests pushed!


};

console.log(slack_member_tz_count);
Copy link
Contributor

Choose a reason for hiding this comment

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

Debug statement left around

@@ -52,6 +52,10 @@ Take note of the `client_id` and `client_secret` for each, and register them wit
(.venv) $ ./manage.py createoauth2app --client-id $CLIENT_ID --client-secret $CLIENT_SECRET --provider {google,twitter}
```

### Docker Environment Backend Services

The default backend services can be run with `docker-compose up`, which will both expose ports locally as well as be available for inter-dependant links through the default docker networking (Celery -> Redis, Celery -> Postgres, etc.).
Copy link
Contributor

Choose a reason for hiding this comment

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

I believe this became less-relevant since the docker-compose revert. Line#28 should cover it, though maybe this is indicating we need more detail there. In any case, I think this can be reverted

console.log(slack_member_tz_count);


$(window).on('orientationchange pageshow resize', function () {
Copy link
Contributor

Choose a reason for hiding this comment

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

Interesting, I'm guessing this is a leaflet nuance?

Copy link
Author

Choose a reason for hiding this comment

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

Yup, this is required for us to support the responsive UI containers.



$(window).on('orientationchange pageshow resize', function () {
if (!map) buildMap();
Copy link
Contributor

Choose a reason for hiding this comment

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

Note to self, I'll add a js check to the build later to enforce some standards. You don't need to change this, but I intend to make braces always required

Copy link
Author

Choose a reason for hiding this comment

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

Added JS linting and an .eslintrc to support failing this behaviour. I've grabbed airbnb as a default nice style guide.

@@ -7,3 +7,4 @@ class Membership(models.Model):
deleted_count = models.IntegerField()
bot_count = models.IntegerField()
timestamp = models.DateTimeField(auto_now_add=True)
tz_count_json = models.TextField(default="")
Copy link
Contributor

Choose a reason for hiding this comment

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

I haven't used the postgres JSON type in django before, but I see the edit to the migration but not this model - I believe that is required for it to act as we want

},
"type": "Feature",
"properties": {
"popupContent": `<h3>${timezone}: ${memberCount} members</h3>`,
Copy link
Contributor

Choose a reason for hiding this comment

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

Using this does mean not supporting IE at all (which is fine in my mind), I think it's a simple enough template literal that all modern major browsers support it at least.

Copy link
Author

Choose a reason for hiding this comment

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

Switched it out for a string concat because it's the only usage.


var features = slack_member_tz_count.map(([timezone, memberCount]) => (
{
"geometry": {
Copy link
Contributor

Choose a reason for hiding this comment

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

You don't have to fix this, but I would after merge if you don't: you're mixing quotes back and forth throughout this file - generally I like to see them remain consistent.

Copy link
Author

Choose a reason for hiding this comment

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

Sorted and now enforceable through linting.

onEachFeature: (feature, layer) => (layer.bindPopup(feature.properties.popupContent)),

pointToLayer: function (feature, latlng) {
console.log(feature.member_count);
Copy link
Contributor

Choose a reason for hiding this comment

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

debug statement left around

</div>
</div>
<script type="text/javascript">
const slack_member_tz_count = {{ slack_member_tz_count | safe }};
Copy link
Contributor

Choose a reason for hiding this comment

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

I get a bit nervous depending on a global variable to exist for a script to be able to run properly - but I don't have any great ideas other than making it an arg to an IIFE (As commented above).

I do need to validate what happens when a partial does these tags and the main template this is included into defines its own, I didn't think about that prior to recommending it. In jinja you have the super() call, django I don't think does.

Copy link
Author

Choose a reason for hiding this comment

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

The code itself won't get included if we don't have this variable defined, does that fit the required constraint?

@@ -20,8 +21,10 @@ class SlackInvite(FormView):

def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
slack_member_tz_count = dumps(cache.get('slack_member_tz_count', []))
Copy link
Contributor

Choose a reason for hiding this comment

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

I believe that the controller/view should be dumb and not know how this data intends to be used (I think that is a display concern strictly). I think it would be best to make a new template tag that converts a python object to JSON for the view.

Along those lines, new additions require tests - if you can add something about context I'd like to see tests that operate like the blog/tests.py for just this new context item (I can back fill the other context I already have here).

Copy link
Author

Choose a reason for hiding this comment

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

Cool, I was hesitant to add a new template tag for this but it's doable.

I'll check out the tests as well.

This commit adds the following features:
- An enhancement to the existing celery task for gathering user information. We save a large chunk of the most common time zones for display purposes. We store a JSON record in the database so we can do historical tracking of timezone membership.
- A new partial template for the Slack module. This includes some Jinja2 html templating for the Leaflet map and a JS script that incldues timezone -> coordinate mapping and the Leaflet map builder.
- A new tojson tag for converting objects to json string formatting.
- A new package.json and associated eslint / jsbeautifyrc for managing future javascript implementation linting.

This mapping is mostly generated from the public domain tzdata's "zone1970.tab" file, converted from iso6709 format and and dropped as a static mapping inside the javascript.

Fixes:
Specify addon version for postgresql to 9.6 to support JSON and match our Ansible playbook version
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants