Skip to content

Commit

Permalink
Fixes #95 added widget prevalidation to WidgetGroup
Browse files Browse the repository at this point in the history
  • Loading branch information
twheys committed Jan 13, 2017
1 parent 799d9ed commit 2cd173d
Show file tree
Hide file tree
Showing 4 changed files with 110 additions and 40 deletions.
54 changes: 28 additions & 26 deletions fireant/dashboards/managers.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,42 +26,44 @@ def _schema(self, dimensions=None, metric_filters=None, dimension_filters=None,
)

def render(self, dimensions=None, metric_filters=None, dimension_filters=None, references=None, operations=None):
for widget in self.widget_group.widgets:
widget.transformer.prevalidate_request(self.widget_group.slicer, widget.metrics, dimensions,
metric_filters, dimension_filters, references, operations)

schema = self._schema(dimensions, metric_filters, dimension_filters, references, operations)
dataframe = self.widget_group.slicer.manager.data(**schema)
return list(
self._transform_widgets(self.widget_group.widgets, dataframe,
schema['dimensions'], schema['references'], schema['operations'])
)

_args = [dataframe, schema['dimensions'], schema['references'], schema['operations']]
return [self._transform_widget(widget, *_args)
for widget in self.widget_group.widgets]

def query_string(self, dimensions=None, metric_filters=None, dimension_filters=None, references=None,
operations=None):
schema = self._schema(dimensions, metric_filters, dimension_filters, references, operations)
return self.widget_group.slicer.manager.query_string(**schema)

def _transform_widget(self, widget, dataframe, dimensions, references, operations):
display_schema = self.widget_group.slicer.manager.display_schema(
metrics=widget.metrics,
dimensions=dimensions,
references=references,
operations=operations,
)

def _transform_widgets(self, widgets, dataframe, dimensions, references, operations):
for widget in widgets:
display_schema = self.widget_group.slicer.manager.display_schema(
metrics=widget.metrics,
dimensions=dimensions,
references=references,
operations=operations,
)

# Temporary fix to enable operations to get output properly. Can removed when the Fireant API is refactored.
operation_columns = ['{}_{}'.format(operation.metric_key, operation.key)
for operation in operations if operation.key != Totals.key]
# Temporary fix to enable operations to get output properly. Can removed when the Fireant API is refactored.
operation_columns = ['{}_{}'.format(operation.metric_key, operation.key)
for operation in operations if operation.key != Totals.key]

columns = utils.flatten(widget.metrics) + operation_columns
columns = utils.flatten(widget.metrics) + operation_columns

if references:
# This escapes a pandas bug where a data frame subset of columns still returns the columns of the
# original data frame
reference_keys = [''] + [ref.key for ref in references]
subset_columns = pd.MultiIndex.from_product([reference_keys, columns])
subset = pd.DataFrame(dataframe[subset_columns], columns=subset_columns)
if references:
# This escapes a pandas bug where a data frame subset of columns still returns the columns of the
# original data frame
reference_keys = [''] + [ref.key for ref in references]
subset_columns = pd.MultiIndex.from_product([reference_keys, columns])
subset = pd.DataFrame(dataframe[subset_columns], columns=subset_columns)

else:
subset = dataframe[columns]
else:
subset = dataframe[columns]

yield widget.transformer.transform(subset, display_schema)
return widget.transformer.transform(subset, display_schema)
5 changes: 3 additions & 2 deletions fireant/slicer/managers.py
Original file line number Diff line number Diff line change
Expand Up @@ -444,8 +444,9 @@ def _get_and_transform_data(self, tx, metrics=(), dimensions=(),
:return:
The transformed result of the request.
"""
tx.prevalidate_request(self.manager.slicer, metrics=metrics, dimensions=[utils.slice_first(dimension)
for dimension in dimensions],
tx.prevalidate_request(self.manager.slicer, metrics=metrics,
dimensions=[utils.slice_first(dimension)
for dimension in dimensions],
metric_filters=metric_filters, dimension_filters=dimension_filters,
references=references, operations=operations)

Expand Down
44 changes: 38 additions & 6 deletions fireant/tests/dashboards/test_dashboard_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@

import pandas as pd
from mock import Mock, patch, call
from pypika import Table, functions as fn

from fireant.dashboards import *
from fireant.slicer import *
from fireant.slicer.references import WoW
from fireant.slicer.transformers import TransformationException
from fireant.tests.database.mock_database import TestDatabase
from pypika import Table, functions as fn


class DashboardTests(TestCase):
Expand Down Expand Up @@ -63,17 +64,17 @@ def assert_slicer_queried(self, metrics, dimensions=None, mfilters=None, dfilter
operations=operations or [],
)

def assert_result_transformed(self, widgets, dimensions, mock_transformer, tx_generator, references=[],
operations=[]):
def assert_result_transformed(self, widgets, dimensions, mock_transformer, tx_generator, references=(),
operations=()):
# Assert that there is a result for each widget
self.assertEqual(len(widgets), len(list(tx_generator)))

self.test_slicer.manager.display_schema.assert_has_calls(
[call(
dimensions=dimensions or [],
metrics=widget.metrics,
references=references,
operations=operations,
references=list(references),
operations=list(operations),
) for widget in widgets]
)

Expand All @@ -98,7 +99,7 @@ def test_if_the_dashboard_query_string_is_equal_to_its_slicer_query_string_given

test_render = WidgetGroup(
slicer=self.test_slicer,
widgets = [
widgets=[
LineChartWidget(metrics=metrics),
]
)
Expand Down Expand Up @@ -597,3 +598,34 @@ def test_remove_duplicated_dimension_keys_with_intervals_in_api2(self, mock_tran
dimensions=dimensions,
)
self.assert_result_transformed(test_render.widgets, dimensions, mock_transformer, result)


class PrevalidationTests(DashboardTests):
@classmethod
def setUpClass(cls):
super(PrevalidationTests, cls).setUpClass()

cls.test_wg = WidgetGroup(
slicer=cls.test_slicer,

widgets=[
LineChartWidget(metrics=['clicks']),
LineChartWidget(metrics=['conversions']),
]
)

def test_raises_exception_for_linechart_with_no_dimensions(self):
with self.assertRaises(TransformationException):
self.test_wg.manager.render(dimensions=[])

@patch('fireant.dashboards.LineChartWidget.transformer.transform')
def test_raises_exception_for_linechart_without_continuous_first_dimension(self, mock_transform):
self.test_slicer.manager.data.return_value = pd.DataFrame(columns=['clicks', 'conversions'])

self.test_wg.manager.render(dimensions=['date'])
self.test_wg.manager.render(dimensions=['clicks'])

with self.assertRaises(TransformationException):
self.test_wg.manager.render(dimensions=['locale'])
with self.assertRaises(TransformationException):
self.test_wg.manager.render(dimensions=['account'])
47 changes: 41 additions & 6 deletions fireant/tests/slicer/transformers/test_highcharts.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@
import numpy as np
import pandas as pd

from fireant.slicer import Slicer, Metric, ContinuousDimension, DatetimeDimension, CategoricalDimension, UniqueDimension
from fireant.slicer.transformers import (HighchartsLineTransformer, HighchartsColumnTransformer,
HighchartsBarTransformer)
from fireant.slicer.transformers import highcharts
from fireant.slicer.transformers import highcharts, TransformationException
from fireant.tests import mock_dataframes as mock_df
from fireant.tests.database.mock_database import TestDatabase
from pypika import Table


class HighchartsLineTransformerTests(TestCase):
Expand All @@ -18,7 +21,25 @@ class HighchartsLineTransformerTests(TestCase):
1-cont-dim, *-metric
1-cont-dim, *-dim, *-metric
"""
hc_tx = HighchartsLineTransformer()

@classmethod
def setUpClass(cls):
cls.hc_tx = HighchartsLineTransformer()

test_table = Table('test_table')
test_db = TestDatabase()
cls.test_slicer = Slicer(
table=test_table,
database=test_db,

dimensions=[
ContinuousDimension('cont', definition=test_table.clicks),
DatetimeDimension('date', definition=test_table.date),
CategoricalDimension('cat', definition=test_table.cat),
UniqueDimension('uni', definition=test_table.uni_id, display_field=test_table.uni_name),
],
metrics=[Metric('foo')],
)

def evaluate_chart_options(self, result, num_series=1, xaxis_type='linear', dash_style='Solid'):
self.assertSetEqual({'title', 'series', 'chart', 'tooltip', 'xAxis', 'yAxis'}, set(result.keys()))
Expand Down Expand Up @@ -58,6 +79,20 @@ def evaluate_tooltip_options(self, series, prefix=None, suffix=None, precision=N
else:
self.assertSetEqual({'type'}, set(series['xAxis'].keys()))

def test_require_dimensions(self):
with self.assertRaises(TransformationException):
self.hc_tx.prevalidate_request(self.test_slicer, [], [], [], [], [], [])

def test_require_continuous_first_dimension(self):
# A ContinuousDimension type is required for the first dimension
self.hc_tx.prevalidate_request(self.test_slicer, [], ['cont'], [], [], [], [])
self.hc_tx.prevalidate_request(self.test_slicer, [], ['date'], [], [], [], [])

with self.assertRaises(TransformationException):
self.hc_tx.prevalidate_request(self.test_slicer, [], ['cat'], [], [], [], [])
with self.assertRaises(TransformationException):
self.hc_tx.prevalidate_request(self.test_slicer, [], ['uni'], [], [], [], [])

def test_series_single_metric(self):
# Tests transformation of a single-metric, single-dimension result
df = mock_df.cont_dim_single_metric_df
Expand Down Expand Up @@ -240,6 +275,10 @@ class HighchartsColumnTransformerTests(TestCase):
"""
type = HighchartsColumnTransformer.chart_type

@classmethod
def setUpClass(cls):
cls.hc_tx = HighchartsColumnTransformer()

def evaluate_chart_options(self, result, num_results=1, categories=None):
self.assertSetEqual({'title', 'series', 'chart', 'tooltip', 'xAxis', 'yAxis'}, set(result.keys()))
self.assertEqual(num_results, len(result['series']))
Expand Down Expand Up @@ -273,10 +312,6 @@ def evaluate_tooltip_options(self, series, prefix=None, suffix=None, precision=N
else:
self.assertSetEqual({'type'}, set(series['xAxis'].keys()))

@classmethod
def setUpClass(cls):
cls.hc_tx = HighchartsColumnTransformer()

def evaluate_result(self, df, result):
result_data = [series['data'] for series in result['series']]

Expand Down

0 comments on commit 2cd173d

Please sign in to comment.