mirror of https://github.com/apache/superset.git
[filtering] define combo of slice/fields unafected by filtering (#1179)
* [FilterBox] dashboard date range filtering * [filtering] define combo of slice/fields unafected by filtering * adding an entry to the docs * Addressed comments
This commit is contained in:
parent
7115c5458d
commit
aed473d0d2
|
@ -82,6 +82,32 @@ function dashboardContainer(dashboardData) {
|
||||||
setFilter(sliceId, col, vals, refresh) {
|
setFilter(sliceId, col, vals, refresh) {
|
||||||
this.addFilter(sliceId, col, vals, false, refresh);
|
this.addFilter(sliceId, col, vals, false, refresh);
|
||||||
},
|
},
|
||||||
|
effectiveExtraFilters(sliceId) {
|
||||||
|
// Summarized filter, not defined by sliceId
|
||||||
|
// returns k=field, v=array of values
|
||||||
|
const f = {};
|
||||||
|
if (sliceId && this.metadata.filter_immune_slices.includes(sliceId)) {
|
||||||
|
// The slice is immune to dashboard fiterls
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Building a list of fields the slice is immune to filters on
|
||||||
|
let immuneToFields = [];
|
||||||
|
if (
|
||||||
|
sliceId &&
|
||||||
|
this.metadata.filter_immune_slice_fields &&
|
||||||
|
this.metadata.filter_immune_slice_fields[sliceId]) {
|
||||||
|
immuneToFields = this.metadata.filter_immune_slice_fields[sliceId];
|
||||||
|
}
|
||||||
|
for (const filteringSliceId in this.filters) {
|
||||||
|
for (const field in this.filters[filteringSliceId]) {
|
||||||
|
if (!immuneToFields.includes(field)) {
|
||||||
|
f[field] = this.filters[filteringSliceId][field];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return f;
|
||||||
|
},
|
||||||
addFilter(sliceId, col, vals, merge = true, refresh = true) {
|
addFilter(sliceId, col, vals, merge = true, refresh = true) {
|
||||||
if (!(sliceId in this.filters)) {
|
if (!(sliceId in this.filters)) {
|
||||||
this.filters[sliceId] = {};
|
this.filters[sliceId] = {};
|
||||||
|
|
|
@ -81,9 +81,11 @@ const px = function () {
|
||||||
const parser = document.createElement('a');
|
const parser = document.createElement('a');
|
||||||
parser.href = data.json_endpoint;
|
parser.href = data.json_endpoint;
|
||||||
if (dashboard !== undefined) {
|
if (dashboard !== undefined) {
|
||||||
const flts =
|
let flts = '';
|
||||||
newParams.extraFilters === false ? '' :
|
if (newParams.extraFilters !== false) {
|
||||||
encodeURIComponent(JSON.stringify(dashboard.filters));
|
flts = dashboard.effectiveExtraFilters(sliceId);
|
||||||
|
flts = encodeURIComponent(JSON.stringify(flts));
|
||||||
|
}
|
||||||
qrystr = parser.search + '&extra_filters=' + flts;
|
qrystr = parser.search + '&extra_filters=' + flts;
|
||||||
} else if ($('#query').length === 0) {
|
} else if ($('#query').length === 0) {
|
||||||
qrystr = parser.search;
|
qrystr = parser.search;
|
||||||
|
|
|
@ -12,16 +12,15 @@ import './filter_box.css';
|
||||||
import { TIME_CHOICES } from './constants.js';
|
import { TIME_CHOICES } from './constants.js';
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
|
origSelectedValues: React.PropTypes.object,
|
||||||
filtersChoices: React.PropTypes.object,
|
filtersChoices: React.PropTypes.object,
|
||||||
onChange: React.PropTypes.func,
|
onChange: React.PropTypes.func,
|
||||||
origSelectedValues: React.PropTypes.object,
|
|
||||||
showDateFilter: React.PropTypes.bool,
|
showDateFilter: React.PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaultProps = {
|
const defaultProps = {
|
||||||
filtersChoices: {},
|
|
||||||
onChange: () => {},
|
|
||||||
origSelectedValues: {},
|
origSelectedValues: {},
|
||||||
|
onChange: () => {},
|
||||||
showDateFilter: false,
|
showDateFilter: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -445,6 +445,14 @@ def generic_find_constraint_name(table, columns, referenced, db):
|
||||||
return fk.name
|
return fk.name
|
||||||
|
|
||||||
|
|
||||||
|
def validate_json(obj):
|
||||||
|
if obj:
|
||||||
|
try:
|
||||||
|
json.loads(obj)
|
||||||
|
except Exception:
|
||||||
|
raise CaravelException("JSON is not valid")
|
||||||
|
|
||||||
|
|
||||||
class timeout(object):
|
class timeout(object):
|
||||||
"""
|
"""
|
||||||
To be used in a ``with`` block and timeout its content.
|
To be used in a ``with`` block and timeout its content.
|
||||||
|
|
|
@ -850,6 +850,8 @@ class DashboardModelView(CaravelModelView, DeleteMixin): # noqa
|
||||||
obj.slug = re.sub(r'\W+', '', obj.slug)
|
obj.slug = re.sub(r'\W+', '', obj.slug)
|
||||||
if g.user not in obj.owners:
|
if g.user not in obj.owners:
|
||||||
obj.owners.append(g.user)
|
obj.owners.append(g.user)
|
||||||
|
utils.validate_json(obj.json_metadata)
|
||||||
|
utils.validate_json(obj.position_json)
|
||||||
|
|
||||||
def pre_update(self, obj):
|
def pre_update(self, obj):
|
||||||
check_ownership(obj)
|
check_ownership(obj)
|
||||||
|
@ -1369,6 +1371,8 @@ class Caravel(BaseCaravelView):
|
||||||
md = dash.metadata_dejson
|
md = dash.metadata_dejson
|
||||||
if 'filter_immune_slices' not in md:
|
if 'filter_immune_slices' not in md:
|
||||||
md['filter_immune_slices'] = []
|
md['filter_immune_slices'] = []
|
||||||
|
if 'filter_immune_slice_fields' not in md:
|
||||||
|
md['filter_immune_slice_fields'] = {}
|
||||||
md['expanded_slices'] = data['expanded_slices']
|
md['expanded_slices'] = data['expanded_slices']
|
||||||
dash.json_metadata = json.dumps(md, indent=4)
|
dash.json_metadata = json.dumps(md, indent=4)
|
||||||
dash.css = data['css']
|
dash.css = data['css']
|
||||||
|
|
|
@ -196,12 +196,7 @@ class BaseViz(object):
|
||||||
extra_filters = self.form_data.get('extra_filters')
|
extra_filters = self.form_data.get('extra_filters')
|
||||||
if not extra_filters:
|
if not extra_filters:
|
||||||
return {}
|
return {}
|
||||||
extra_filters = json.loads(extra_filters)
|
return json.loads(extra_filters)
|
||||||
# removing per-slice details
|
|
||||||
summary = {}
|
|
||||||
for flt in extra_filters.values():
|
|
||||||
summary.update(flt)
|
|
||||||
return summary
|
|
||||||
|
|
||||||
def query_filters(self, is_having_filter=False):
|
def query_filters(self, is_having_filter=False):
|
||||||
"""Processes the filters for the query"""
|
"""Processes the filters for the query"""
|
||||||
|
|
48
docs/faq.rst
48
docs/faq.rst
|
@ -61,3 +61,51 @@ Why is the map not visible in the mapbox visualization?
|
||||||
|
|
||||||
You need to register to mapbox.com, get an API key and configure it as
|
You need to register to mapbox.com, get an API key and configure it as
|
||||||
``MAPBOX_API_KEY`` in ``caravel_config.py``.
|
``MAPBOX_API_KEY`` in ``caravel_config.py``.
|
||||||
|
|
||||||
|
|
||||||
|
How to add dynamic filters to a dashboard?
|
||||||
|
------------------------------------------
|
||||||
|
|
||||||
|
It's easy: use the ``Filter Box`` widget, build a slice, and add it to your
|
||||||
|
dashboard.
|
||||||
|
|
||||||
|
The ``Filter Box`` widget allows you to define a query to populate dropdowns
|
||||||
|
that can be use for filtering. To build the list of distinct values, we
|
||||||
|
run a query, and sort the result by the metric you provide, sorting
|
||||||
|
descending.
|
||||||
|
|
||||||
|
The widget also has a checkbox ``Date Filter``, which enables time filtering
|
||||||
|
capabilities to your dashboard. After checking the box and refreshing, you'll
|
||||||
|
see a ``from`` and a ``to`` dropdown show up.
|
||||||
|
|
||||||
|
But what about if you don't want certain widgets to get filtered on your
|
||||||
|
dashboard? You can do that by editing your dashboard, and in the form,
|
||||||
|
edit the ``JSON Metadata`` field, more specifically the
|
||||||
|
``filter_immune_slices`` key, that receives an array of sliceIds that should
|
||||||
|
never be affected by any dashboard level filtering.
|
||||||
|
|
||||||
|
|
||||||
|
..code::
|
||||||
|
|
||||||
|
{
|
||||||
|
"filter_immune_slices": [324, 65, 92],
|
||||||
|
"expanded_slices": {},
|
||||||
|
"filter_immune_slice_fields": {
|
||||||
|
"177": ["country_name", "__from", "__to"],
|
||||||
|
"32": ["__from", "__to"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
In the json blob above, slices 324, 65 and 92 won't be affected by any
|
||||||
|
dashboard level filtering.
|
||||||
|
|
||||||
|
Now note the ``filter_immune_slice_fields`` key. This one allows you to
|
||||||
|
be more specific and define for a specific slice_id, which filter fields
|
||||||
|
should be disregarded.
|
||||||
|
|
||||||
|
Note the use of the ``__from`` and ``__to`` keywords, those are reserved
|
||||||
|
for dealing with the time boundary filtering mentioned above.
|
||||||
|
|
||||||
|
But what happens with filtering when dealing with slices coming from
|
||||||
|
different tables or databases? If the column name is shared, the filter will
|
||||||
|
be applied, it's as simple as that.
|
||||||
|
|
Loading…
Reference in New Issue