diff --git a/stackalytics/dashboard/decorators.py b/stackalytics/dashboard/decorators.py
index 27b027a30..0724b3ef3 100644
--- a/stackalytics/dashboard/decorators.py
+++ b/stackalytics/dashboard/decorators.py
@@ -247,6 +247,13 @@ def record_filter(ignore=None):
record_ids, _filter_records_by_days(start_date, end_date,
memory_storage_inst))
+ # filtering by non-indexed attributes goes last
+ language = params['language']
+ if language:
+ record_ids = (
+ set(memory_storage_inst.get_record_ids_by_languages(
+ record_ids, language)))
+
kwargs['record_ids'] = record_ids
kwargs['records'] = memory_storage_inst.get_records(record_ids)
@@ -454,6 +461,9 @@ def templated(template=None, return_code=200):
ctx['user_inst'] = vault.get_user_from_runtime_storage(
ctx['user_id'])
+ ctx['language'] = parameters.get_single_parameter(
+ kwargs, 'language')
+
ctx['page_title'] = helpers.make_page_title(
ctx['project_type_inst'],
ctx.get('release'), ctx.get('module_inst'),
diff --git a/stackalytics/dashboard/memory_storage.py b/stackalytics/dashboard/memory_storage.py
index ee29bbac3..da2d63ee0 100644
--- a/stackalytics/dashboard/memory_storage.py
+++ b/stackalytics/dashboard/memory_storage.py
@@ -188,6 +188,14 @@ class CachedMemoryStorage(MemoryStorage):
def get_first_record_day(self):
return min(self.day_index.keys())
+ def get_record_ids_by_languages(self, record_ids, languages):
+ # special case: filter by attribute not covered by index
+ for record in self.get_records(record_ids):
+ lng = record.value
+ if lng:
+ if lng.lower() in languages:
+ yield record.record_id
+
def get_memory_storage(memory_storage_type):
if memory_storage_type == MEMORY_STORAGE_CACHED:
diff --git a/stackalytics/dashboard/parameters.py b/stackalytics/dashboard/parameters.py
index cae6cd7f6..fff7074f2 100644
--- a/stackalytics/dashboard/parameters.py
+++ b/stackalytics/dashboard/parameters.py
@@ -58,7 +58,7 @@ METRIC_TO_RECORD_TYPE = {
FILTER_PARAMETERS = ['release', 'project_type', 'module', 'company', 'user_id',
'metric', 'start_date', 'end_date', 'blueprint_id',
- 'core_in']
+ 'core_in', 'language']
DEFAULT_RECORDS_LIMIT = 10
DEFAULT_STATIC_ACTIVITY_SIZE = 100
diff --git a/stackalytics/dashboard/templates/overview.html b/stackalytics/dashboard/templates/overview.html
index ff60b37ac..1c9e03a5c 100644
--- a/stackalytics/dashboard/templates/overview.html
+++ b/stackalytics/dashboard/templates/overview.html
@@ -127,6 +127,39 @@
{% block right_frame %}
+ {% if show_languages_breakdown %}
+
+ {% if language %}
+
+
+
+
+
+ {% else %}
+
Languages
+
+
+
+
+
+
+ | # |
+ Language |
+ Translations |
+
+
+
+
+
+ {% endif %}
+
+
+ {% endif %}
+
{% if show_module_breakdown %}
Contribution by modules
@@ -179,27 +212,6 @@
{% endif %}
- {% if show_languages_breakdown %}
-
-
Languages
-
-
-
-
-
-
- | # |
- Language |
- Translations |
-
-
-
-
-
-
-
- {% endif %}
-
{% if show_contribution_on_right %}
{{ contribution_summary.show_contribution_summary(show_all=False) }}
{{ show_report_links(module, company, user_id) }}
diff --git a/stackalytics/dashboard/web.py b/stackalytics/dashboard/web.py
index 069d31aed..a72ae6ba1 100644
--- a/stackalytics/dashboard/web.py
+++ b/stackalytics/dashboard/web.py
@@ -403,12 +403,26 @@ def get_bpd(records, **kwargs):
return result
+@app.route('/api/1.0/languages')
+@decorators.exception_handler()
+@decorators.response()
+@decorators.cached(ignore=['language'])
+@decorators.jsonify()
+@decorators.record_filter(ignore=['language'])
+def get_languages_json(record_ids, **kwargs):
+ memory_storage = vault.get_memory_storage()
+ languages = set(r.value for r in memory_storage.get_records(record_ids))
+
+ return [{'id': c.lower().replace('&', ''), 'text': c}
+ for c in sorted(languages)]
+
+
@app.route('/api/1.0/stats/languages')
@decorators.exception_handler()
@decorators.response()
@decorators.cached()
@decorators.jsonify('stats')
-@decorators.record_filter()
+@decorators.record_filter(ignore=['language'])
def get_languages(records, **kwargs):
result = []
languages = collections.defaultdict(int)