Warning
This help isn’t complete. It may even look terrible. If you want to work on it, see How to Contribute. You can also ask for help in the Juice Slack #documentation channel.
Recipes are the heart of data services for slices.
Ingredients are the raw material for making reusable SQLAlchemy queries. We
define ingredients in the data service base class in the dimension_shelf
and
metric_shelf
. These ingredients can then be used across all your data
services.
def MyBaseService(RecipeServiceBaseV3):
dimension_shelf = {
# A dictionary of reusable dimensions. The key is the "id"
# of the dimension and the value is a Dimension object
}
metric_shelf = {
# A dictionary of reusable metrics. The key is the "id"
# of the dimension and the value is a Metric object
}
automatic_filter_keys = (...)
# A tuple or list of values from the dimension_shelf
# these will automatically be used for filters and show up in global filters
LookupDimension(Census.state, {
"AL": "Alabama",
"AK": "Alaska",
...
}, default="State not found")
summing_expression ``if the
``conditional_expression
is true. For instance, to count up the total
amount of sales with status=’complete’, you could use:SumIfMetric(MyTable.status == 'complete', MyTable.sales
counting_expression
where
conditional_expression
is true.You will use your ingredients to build recipes to supply data for a slice.
self.dimensions = ('age', 'city')
self.metrics = ('sales_dollars', 'sales_count')
self.recipe().metrics(*self.metrics).dimensions(*self.dimensions)\
.filters(Filter(MyTable.state == 'Georgia'))
metric_shelf
.dimension_shelf
.filter_shelf
or filter objects you create just for the recipe using
Filter(expression)dimensions
in the comparison recipe. The metrics
in the comparison recipe will be added to each row with a suffix..blend()
but only rows that are in the base recipe will be
returned. If there is no match in the blend recipe, the blend recipe values
will be None.Once you have a recipe you can either look at the values or use it to generate the slice response.
{{dimension_key}}_id
which is the id for for each dimension. If
formatters are used there will be a {{key}}_raw
, the unformatted value
of that ingredient.In juicebox3 apps, a frontend debugging view is available. This view is visible to superusers or if the recipe renders with parameter show_debug=True. Client implementations like HealthStream can override FruitionUser.can_see_dataservice_debug to customize who sees the debug views.
This debugging view is not available in HIPAA environments (where
ALLOW_QUERY_CACHING=False
in settings).
def build_response(self):
self.metrics = ('dollars', 'avg', 'gteed', 'per_gteed', 'age', 'yrs')
self.dimensions = ('name', 'pos', 'team', 'start_year', 'end_year', 'free_agent', 'signed')
recipe = self.recipe().metrics(*self.metrics).dimensions(*self.dimensions)
self.response['responses'].append(recipe.render(show_debug=True))
The debug view is a modal dialog that shows automatic filters, custom filters, ingredients and the generated sql.
Slices can support pagination. To do this they need to do three things.
.order_by()
parameter.stack.yaml
configuration.
For more details see pagination.Here’s a sample recipe that would work
def build_response(self):
self.metrics = ('dollars', 'avg', 'gteed', 'per_gteed', 'age', 'yrs')
self.dimensions = ('name', 'pos', 'team', 'start_year', 'end_year', 'free_agent', 'signed')
recipe = self.recipe().metrics(*self.metrics)\
.dimensions(*self.dimensions).order_by('name')
self.response['responses'].append(recipe.render())
When the data service returns the response for the paginated slice, it needs
to tell the front-end which page of data it is returning and how many total
items exist. This information is injected by the data service into the
response’s config.pagination
when the data service runs:
{
'config': {
'pagination':
{
page: 1,
pageSize: 20,
totalItems: 78
},
'data': [{ 'name': 'items', 'values': [] }],
'metadata': {},
'name': 'Untitled',
'templateContext': {},
'version': '3'
}
This data service might look like this when displayed in the front end. Here we are on the first page of a total of four.
Pagination may also support searching and sorting. For instance a details table slice allows users to search for a term and to sort each column.
When you use pagination, this searching and sorting must happen in the recipe in the data service. Data services that support pagination will have a paginator property on the data services. You can access the user’s requested search term and sort order as follows. This recipe support custom sorting and searching on all dimensions.
def build_response(self):
# get the optional search query and sorts from the paginator
search_term = self.paginator.q
sorts = self.paginator.sort
# If no sorts are provided sort by nane
if not sorts:
sorts = ('name',)
self.metrics = ('dollars', 'avg', 'gteed', 'per_gteed', 'age', 'yrs')
self.dimensions = ('name', 'pos', 'team', 'start_year', 'end_year', 'free_agent', 'signed')
recipe = self.recipe().metrics(*self.metrics)\
.dimensions(*self.dimensions).order_by(*sorts)
self.response['responses'].append(recipe.render())
Search will automatically search all dimensions used in the recipe. If you
want to limit the search to fewer values change the paginator’s
search_keys
. Here’s an example that limits searching to only the name
and team
:
def build_response(self):
# Limit search to name and team. Don't support custom sorting.
self.paginator.search_keys = ('name', 'team')
self.metrics = ('dollars', 'avg', 'gteed', 'per_gteed', 'age', 'yrs')
self.dimensions = ('name', 'pos', 'team', 'start_year', 'end_year', 'free_agent', 'signed')
recipe = self.recipe().metrics(*self.metrics)\
.dimensions(*self.dimensions).order_by('name')
self.response['responses'].append(recipe.render())