diff --git a/superset/common/query_object.py b/superset/common/query_object.py index 47abbf2a0..553c0b9dd 100644 --- a/superset/common/query_object.py +++ b/superset/common/query_object.py @@ -51,9 +51,16 @@ class QueryObject: is_prequery: bool = False, columns: List[str] = None, orderby: List[List] = None, + relative_start: str = app.config.get('DEFAULT_RELATIVE_START_TIME', 'today'), + relative_end: str = app.config.get('DEFAULT_RELATIVE_END_TIME', 'today'), ): self.granularity = granularity - self.from_dttm, self.to_dttm = utils.get_since_until(time_range, time_shift) + self.from_dttm, self.to_dttm = utils.get_since_until( + relative_start=relative_start, + relative_end=relative_end, + time_range=time_range, + time_shift=time_shift, + ) self.is_timeseries = is_timeseries self.time_range = time_range self.time_shift = utils.parse_human_timedelta(time_shift) diff --git a/superset/config.py b/superset/config.py index fc287b7f2..7d6ca33e5 100644 --- a/superset/config.py +++ b/superset/config.py @@ -599,8 +599,13 @@ BUG_REPORT_URL = None DOCUMENTATION_URL = None # What is the Last N days relative in the time selector to: -# 'today' means it is midnight (00:00:00) of today in the local timezone +# 'today' means it is midnight (00:00:00) in the local timezone # 'now' means it is relative to the query issue time +# If both start and end time is set to now, this will make the time +# filter a moving window. By only setting the end time to now, +# start time will be set to midnight, while end will be relative to +# the query issue time. +DEFAULT_RELATIVE_START_TIME = 'today' DEFAULT_RELATIVE_END_TIME = 'today' # Is epoch_s/epoch_ms datetime format supposed to be considered since UTC ? diff --git a/superset/utils/core.py b/superset/utils/core.py index 2defa70dd..df2550040 100644 --- a/superset/utils/core.py +++ b/superset/utils/core.py @@ -237,14 +237,14 @@ def parse_human_datetime(s): # when time is not extracted, we 'reset to midnight' if parsed_flags & 2 == 0: parsed_dttm = parsed_dttm.replace(hour=0, minute=0, second=0) - dttm = dttm_from_timtuple(parsed_dttm.utctimetuple()) + dttm = dttm_from_timetuple(parsed_dttm.utctimetuple()) except Exception as e: logging.exception(e) raise ValueError("Couldn't parse date string [{}]".format(s)) return dttm -def dttm_from_timtuple(d: struct_time) -> datetime: +def dttm_from_timetuple(d: struct_time) -> datetime: return datetime( d.tm_year, d.tm_mon, d.tm_mday, d.tm_hour, d.tm_min, d.tm_sec) @@ -306,7 +306,7 @@ def parse_human_timedelta(s: str): True """ cal = parsedatetime.Calendar() - dttm = dttm_from_timtuple(datetime.now().timetuple()) + dttm = dttm_from_timetuple(datetime.now().timetuple()) d = cal.parse(s or '', dttm)[0] d = datetime(d.tm_year, d.tm_mon, d.tm_mday, d.tm_hour, d.tm_min, d.tm_sec) return d - dttm @@ -939,6 +939,7 @@ def get_since_until(time_range: Optional[str] = None, since: Optional[str] = None, until: Optional[str] = None, time_shift: Optional[str] = None, + relative_start: Optional[str] = None, relative_end: Optional[str] = None) -> Tuple[datetime, datetime]: """Return `since` and `until` date time tuple from string representations of time_range, since, until and time_shift. @@ -965,13 +966,14 @@ def get_since_until(time_range: Optional[str] = None, """ separator = ' : ' + relative_start = parse_human_datetime(relative_start if relative_start else 'today') relative_end = parse_human_datetime(relative_end if relative_end else 'today') common_time_frames = { - 'Last day': (relative_end - relativedelta(days=1), relative_end), # noqa: T400 - 'Last week': (relative_end - relativedelta(weeks=1), relative_end), # noqa: T400 - 'Last month': (relative_end - relativedelta(months=1), relative_end), # noqa: E501, T400 - 'Last quarter': (relative_end - relativedelta(months=3), relative_end), # noqa: E501, T400 - 'Last year': (relative_end - relativedelta(years=1), relative_end), # noqa: T400 + 'Last day': (relative_start - relativedelta(days=1), relative_end), # noqa: T400 + 'Last week': (relative_start - relativedelta(weeks=1), relative_end), # noqa: E501, T400 + 'Last month': (relative_start - relativedelta(months=1), relative_end), # noqa: E501, T400 + 'Last quarter': (relative_start - relativedelta(months=3), relative_end), # noqa: E501, T400 + 'Last year': (relative_start - relativedelta(years=1), relative_end), # noqa: E501, T400 } if time_range: @@ -988,10 +990,10 @@ def get_since_until(time_range: Optional[str] = None, else: rel, num, grain = time_range.split() if rel == 'Last': - since = relative_end - relativedelta(**{grain: int(num)}) # noqa: T400 + since = relative_start - relativedelta(**{grain: int(num)}) # noqa: T400 until = relative_end else: # rel == 'Next' - since = relative_end + since = relative_start until = relative_end + relativedelta(**{grain: int(num)}) # noqa: T400 else: since = since or '' diff --git a/superset/viz.py b/superset/viz.py index 8eb062048..a6864d323 100644 --- a/superset/viz.py +++ b/superset/viz.py @@ -59,6 +59,7 @@ from superset.utils.core import ( config = app.config stats_logger = config.get('STATS_LOGGER') +relative_start = config.get('DEFAULT_RELATIVE_START_TIME', 'today') relative_end = config.get('DEFAULT_RELATIVE_END_TIME', 'today') METRIC_KEYS = [ @@ -274,7 +275,8 @@ class BaseViz(object): # default order direction order_desc = form_data.get('order_desc', True) - since, until = utils.get_since_until(relative_end=relative_end, + since, until = utils.get_since_until(relative_start=relative_start, + relative_end=relative_end, time_range=form_data.get('time_range'), since=form_data.get('since'), until=form_data.get('until')) @@ -800,7 +802,8 @@ class CalHeatmapViz(BaseViz): values[str(v / 10**9)] = obj.get(metric) data[metric] = values - start, end = utils.get_since_until(relative_end=relative_end, + start, end = utils.get_since_until(relative_start=relative_start, + relative_end=relative_end, time_range=form_data.get('time_range'), since=form_data.get('since'), until=form_data.get('until')) diff --git a/tests/utils_tests.py b/tests/utils_tests.py index 40dedafae..a39631b83 100644 --- a/tests/utils_tests.py +++ b/tests/utils_tests.py @@ -43,7 +43,9 @@ from superset.utils.core import ( def mock_parse_human_datetime(s): - if s in ['now', 'today']: + if s == 'now': + return datetime(2016, 11, 7, 9, 30, 10) + elif s == 'today': return datetime(2016, 11, 7) elif s == 'yesterday': return datetime(2016, 11, 6) @@ -51,6 +53,8 @@ def mock_parse_human_datetime(s): return datetime(2016, 11, 8) elif s == 'Last year': return datetime(2015, 11, 7) + elif s == 'Last week': + return datetime(2015, 10, 31) elif s == 'Last 5 months': return datetime(2016, 6, 7) elif s == 'Next 5 months': @@ -600,7 +604,7 @@ class UtilsTestCase(unittest.TestCase): self.assertEqual(result, expected) result = get_since_until(' : now') - expected = None, datetime(2016, 11, 7) + expected = None, datetime(2016, 11, 7, 9, 30, 10) self.assertEqual(result, expected) result = get_since_until('yesterday : tomorrow') @@ -636,7 +640,19 @@ class UtilsTestCase(unittest.TestCase): self.assertEqual(result, expected) result = get_since_until(time_range='5 days : now') - expected = datetime(2016, 11, 2), datetime(2016, 11, 7) + expected = datetime(2016, 11, 2), datetime(2016, 11, 7, 9, 30, 10) + self.assertEqual(result, expected) + + result = get_since_until('Last week', relative_end='now') + expected = datetime(2016, 10, 31), datetime(2016, 11, 7, 9, 30, 10) + self.assertEqual(result, expected) + + result = get_since_until('Last week', relative_start='now') + expected = datetime(2016, 10, 31, 9, 30, 10), datetime(2016, 11, 7) + self.assertEqual(result, expected) + + result = get_since_until('Last week', relative_start='now', relative_end='now') + expected = datetime(2016, 10, 31, 9, 30, 10), datetime(2016, 11, 7, 9, 30, 10) self.assertEqual(result, expected) with self.assertRaises(ValueError):