From f034f2701e886d3b0e6b41203a65ad4c3fb1bea5 Mon Sep 17 00:00:00 2001 From: Maxime Beauchemin Date: Wed, 13 Jul 2016 23:45:05 -0400 Subject: [PATCH] Allowing to define a default format string per-metric (#750) --- caravel/assets/javascripts/modules/caravel.js | 7 ++++++ caravel/assets/javascripts/modules/utils.js | 13 +++++++++- caravel/assets/visualizations/table.js | 5 ++-- .../f162a1dea4c4_d3format_by_metric.py | 24 +++++++++++++++++++ caravel/models.py | 2 ++ caravel/views.py | 11 +++++++-- caravel/viz.py | 7 ++++++ 7 files changed, 63 insertions(+), 6 deletions(-) create mode 100644 caravel/migrations/versions/f162a1dea4c4_d3format_by_metric.py diff --git a/caravel/assets/javascripts/modules/caravel.js b/caravel/assets/javascripts/modules/caravel.js index 5a49e0ff1..c244032b1 100644 --- a/caravel/assets/javascripts/modules/caravel.js +++ b/caravel/assets/javascripts/modules/caravel.js @@ -2,6 +2,7 @@ var $ = require('jquery'); var jQuery = $; var d3 = require('d3'); var Mustache = require('mustache'); +var utils = require('./utils'); // vis sources var sourceMap = { @@ -240,6 +241,12 @@ var px = (function () { endpoint += "&force=" + this.force; return endpoint; }, + d3format: function (col, number) { + // uses the utils memoized d3format function and formats based on + // column level defined preferences + var format = this.data.column_formats[col]; + return utils.d3format(format, number); + }, done: function (data) { clearInterval(timer); token.find("img.loading").hide(); diff --git a/caravel/assets/javascripts/modules/utils.js b/caravel/assets/javascripts/modules/utils.js index 96992dcc3..2e3520bee 100644 --- a/caravel/assets/javascripts/modules/utils.js +++ b/caravel/assets/javascripts/modules/utils.js @@ -105,9 +105,20 @@ var fixDataTableBodyHeight = function ($tableDom, height) { .css('max-height', height - headHeight); }; +var formatters = {}; +function d3format(format, number) { + // Formats a number and memoizes formatters to be reused + format = format || '.3s'; + if (!(format in formatters)) { + formatters[format] = d3.format(format); + } + return formatters[format](number); +} + module.exports = { wrapSvgText: wrapSvgText, showModal: showModal, toggleCheckbox: toggleCheckbox, - fixDataTableBodyHeight: fixDataTableBodyHeight + fixDataTableBodyHeight: fixDataTableBodyHeight, + d3format: d3format }; diff --git a/caravel/assets/visualizations/table.js b/caravel/assets/visualizations/table.js index fc7916552..1b0d20f67 100644 --- a/caravel/assets/visualizations/table.js +++ b/caravel/assets/visualizations/table.js @@ -2,14 +2,13 @@ var $ = window.$ = require('jquery'); var jQuery = window.jQuery = $; var d3 = require('d3'); var px = window.px || require('../javascripts/modules/caravel.js'); +var utils = require('../javascripts/modules/utils.js'); require('./table.css'); require('datatables.net-bs'); require('../node_modules/datatables-bootstrap3-plugin/media/css/datatables-bootstrap3.css'); -var utils = require('../javascripts/modules/utils'); function tableVis(slice) { - var f = d3.format('.3s'); var fC = d3.format('0,000'); var timestampFormatter; @@ -120,7 +119,7 @@ function tableVis(slice) { }) .html(function (d) { if (d.isMetric) { - return f(d.val); + return slice.d3format(d.col, d.val); } else { return d.val; } diff --git a/caravel/migrations/versions/f162a1dea4c4_d3format_by_metric.py b/caravel/migrations/versions/f162a1dea4c4_d3format_by_metric.py new file mode 100644 index 000000000..9e266e23a --- /dev/null +++ b/caravel/migrations/versions/f162a1dea4c4_d3format_by_metric.py @@ -0,0 +1,24 @@ +"""d3format_by_metric + +Revision ID: f162a1dea4c4 +Revises: 960c69cb1f5b +Create Date: 2016-07-06 22:04:28.685100 + +""" + +# revision identifiers, used by Alembic. +revision = 'f162a1dea4c4' +down_revision = '960c69cb1f5b' + +from alembic import op +import sqlalchemy as sa + + +def upgrade(): + op.add_column('metrics', sa.Column('d3format', sa.String(length=128), nullable=True)) + op.add_column('sql_metrics', sa.Column('d3format', sa.String(length=128), nullable=True)) + + +def downgrade(): + op.drop_column('sql_metrics', 'd3format') + op.drop_column('metrics', 'd3format') diff --git a/caravel/models.py b/caravel/models.py index dd8670d1a..557190f32 100644 --- a/caravel/models.py +++ b/caravel/models.py @@ -870,6 +870,7 @@ class SqlMetric(Model, AuditMixinNullable): expression = Column(Text) description = Column(Text) is_restricted = Column(Boolean, default=False, nullable=True) + d3format = Column(String(128)) @property def sqla_col(self): @@ -1499,6 +1500,7 @@ class DruidMetric(Model, AuditMixinNullable): json = Column(Text) description = Column(Text) is_restricted = Column(Boolean, default=False, nullable=True) + d3format = Column(String(128)) @property def json_obj(self): diff --git a/caravel/views.py b/caravel/views.py index 7704c8529..1d0a2bcf8 100755 --- a/caravel/views.py +++ b/caravel/views.py @@ -278,7 +278,7 @@ class SqlMetricInlineView(CompactCRUDMixin, CaravelModelView): # noqa list_columns = ['metric_name', 'verbose_name', 'metric_type'] edit_columns = [ 'metric_name', 'description', 'verbose_name', 'metric_type', - 'expression', 'table', 'is_restricted'] + 'expression', 'table', 'd3format', 'is_restricted'] description_columns = { 'expression': utils.markdown( "a valid SQL expression as supported by the underlying backend. " @@ -287,6 +287,13 @@ class SqlMetricInlineView(CompactCRUDMixin, CaravelModelView): # noqa "to certain roles. Only roles with the permission " "'metric access on XXX (the name of this metric)' " "are allowed to access this metric"), + 'd3format': utils.markdown( + "d3 formatting string as defined [here]" + "(https://github.com/d3/d3-format/blob/master/README.md#format). " + "For instance, this default formatting applies in the Table " + "visualization and allow for different metric to use different " + "formats", True + ), } add_columns = edit_columns page_size = 500 @@ -313,7 +320,7 @@ class DruidMetricInlineView(CompactCRUDMixin, CaravelModelView): # noqa list_columns = ['metric_name', 'verbose_name', 'metric_type'] edit_columns = [ 'metric_name', 'description', 'verbose_name', 'metric_type', 'json', - 'datasource', 'is_restricted'] + 'datasource', 'd3format', 'is_restricted'] add_columns = edit_columns page_size = 500 validators_columns = { diff --git a/caravel/viz.py b/caravel/viz.py index cb4c2036d..246d7b200 100755 --- a/caravel/viz.py +++ b/caravel/viz.py @@ -310,6 +310,7 @@ class BaseViz(object): # cache.set call can fail if the backend is down or if # the key is too large or whatever other reasons logging.warning("Could not cache key {}".format(cache_key)) + logging.exception(e) cache.delete(cache_key) payload['is_cached'] = is_cached return self.json_dumps(payload) @@ -320,6 +321,7 @@ class BaseViz(object): @property def data(self): + """This is the data object serialized to the js layer""" content = { 'csv_endpoint': self.csv_endpoint, 'form_data': self.form_data, @@ -327,6 +329,11 @@ class BaseViz(object): 'standalone_endpoint': self.standalone_endpoint, 'token': self.token, 'viz_name': self.viz_type, + 'column_formats': { + m.metric_name: m.d3format + for m in self.datasource.metrics + if m.d3format + }, } return content