This is an automated email from the ASF dual-hosted git repository.

arivero pushed a commit to branch time_comparison_zeros
in repository https://gitbox.apache.org/repos/asf/superset.git

commit d0f1c6a49eb3e98e8ec671bfa9983140524a4a14
Author: Antonio Rivero <[email protected]>
AuthorDate: Mon Aug 19 13:25:05 2024 +0200

    Time Comparison:
    
    - Support Human readable dates in the label and data call
    - Handling inherit and dates as time_offsets in the backend
---
 .../src/time-comparison/getTimeOffset.ts           |  5 ++-
 .../BigNumberPeriodOverPeriod/buildQuery.ts        |  9 ++++-
 .../BigNumberPeriodOverPeriod/transformProps.ts    |  7 ++++
 .../plugins/plugin-chart-table/src/buildQuery.ts   | 10 +++++-
 .../plugin-chart-table/src/transformProps.ts       |  9 ++++-
 .../components/controls/ComparisonRangeLabel.tsx   | 20 ++++++++++-
 .../components/controls/TimeOffsetControl.tsx      | 16 ++++++++-
 superset/common/query_context_processor.py         | 42 +++++++++++++++++++++-
 8 files changed, 111 insertions(+), 7 deletions(-)

diff --git 
a/superset-frontend/packages/superset-ui-core/src/time-comparison/getTimeOffset.ts
 
b/superset-frontend/packages/superset-ui-core/src/time-comparison/getTimeOffset.ts
index 4d09d509a2..84047841e4 100644
--- 
a/superset-frontend/packages/superset-ui-core/src/time-comparison/getTimeOffset.ts
+++ 
b/superset-frontend/packages/superset-ui-core/src/time-comparison/getTimeOffset.ts
@@ -280,9 +280,12 @@ export const getTimeOffset = ({
 
   const customShift =
     customStartDateTime &&
+    filterStartDateTime &&
     Math.round((filterStartDateTime - customStartDateTime) / DAY_IN_MS);
   const inInheritShift =
     isInherit &&
+    filterEndDateTime &&
+    filterStartDateTime &&
     Math.round((filterEndDateTime - filterStartDateTime) / DAY_IN_MS);
 
   const newShifts = ensureIsArray(shifts)
@@ -292,7 +295,7 @@ export const getTimeOffset = ({
           if (includeFutureOffsets && customShift < 0) {
             return `${customShift * -1} days after`;
           }
-          if (customShift >= 0) {
+          if (customShift >= 0 && filterStartDateTime) {
             return `${customShift} days ago`;
           }
         }
diff --git 
a/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/buildQuery.ts
 
b/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/buildQuery.ts
index ce75ebd13b..2c680b8310 100644
--- 
a/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/buildQuery.ts
+++ 
b/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/buildQuery.ts
@@ -57,7 +57,7 @@ export default function buildQuery(formData: QueryFormData) {
         previousCustomTimeRangeFilters[0]?.comparator.split(' : ')[0];
     }
 
-    const timeOffsets = ensureIsArray(
+    let timeOffsets = ensureIsArray(
       isTimeComparison(formData, baseQueryObject)
         ? getTimeOffset({
             timeRangeFilter: {
@@ -74,6 +74,13 @@ export default function buildQuery(formData: QueryFormData) {
           })
         : [],
     );
+    if (isEmpty(timeOffsets)) {
+      if (formData.time_compare && formData.time_compare === 'custom') {
+        timeOffsets = [formData.start_date_offset];
+      } else {
+        timeOffsets = ensureIsArray(formData.time_compare) || [];
+      }
+    }
     return [
       {
         ...baseQueryObject,
diff --git 
a/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/transformProps.ts
 
b/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/transformProps.ts
index fbac5f4303..3381e26a8a 100644
--- 
a/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/transformProps.ts
+++ 
b/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/transformProps.ts
@@ -130,6 +130,13 @@ export default function transformProps(chartProps: 
ChartProps) {
           ? parseDttmToDate(previousCustomStartDate)?.toUTCString()
           : startDateOffset,
     });
+    if (isEmpty(dataOffset)) {
+      if (timeComparison && timeComparison === 'custom') {
+        dataOffset = [startDateOffset];
+      } else {
+        dataOffset = ensureIsArray(timeComparison) || [];
+      }
+    }
   }
 
   const { value1, value2 } = data.reduce(
diff --git a/superset-frontend/plugins/plugin-chart-table/src/buildQuery.ts 
b/superset-frontend/plugins/plugin-chart-table/src/buildQuery.ts
index 554914053c..a3c7c37937 100644
--- a/superset-frontend/plugins/plugin-chart-table/src/buildQuery.ts
+++ b/superset-frontend/plugins/plugin-chart-table/src/buildQuery.ts
@@ -107,7 +107,7 @@ const buildQuery: BuildQuery<TableChartFormData> = (
         previousCustomTimeRangeFilters[0]?.comparator.split(' : ')[0];
     }
 
-    const timeOffsets = ensureIsArray(
+    let timeOffsets = ensureIsArray(
       isTimeComparison(formData, baseQueryObject)
         ? getTimeOffset({
             timeRangeFilter: {
@@ -125,6 +125,14 @@ const buildQuery: BuildQuery<TableChartFormData> = (
         : [],
     );
 
+    if (isEmpty(timeOffsets)) {
+      if (formData.time_compare && formData.time_compare === 'custom') {
+        timeOffsets = [formData.start_date_offset];
+      } else {
+        timeOffsets = ensureIsArray(formData.time_compare) || [];
+      }
+    }
+
     let temporalColumAdded = false;
     let temporalColum = null;
 
diff --git a/superset-frontend/plugins/plugin-chart-table/src/transformProps.ts 
b/superset-frontend/plugins/plugin-chart-table/src/transformProps.ts
index 8c02d8293b..d11d7ed972 100644
--- a/superset-frontend/plugins/plugin-chart-table/src/transformProps.ts
+++ b/superset-frontend/plugins/plugin-chart-table/src/transformProps.ts
@@ -615,7 +615,7 @@ const transformProps = (
       previousCustomTimeRangeFilters[0]?.comparator.split(' : ')[0];
   }
 
-  const timeOffsets = getTimeOffset({
+  let timeOffsets = getTimeOffset({
     timeRangeFilter: {
       ...TimeRangeFilters[0],
       comparator:
@@ -628,6 +628,13 @@ const transformProps = (
         ? parseDttmToDate(previousCustomStartDate)?.toUTCString()
         : formData.start_date_offset,
   });
+  if (isEmpty(timeOffsets)) {
+    if (formData.time_compare && formData.time_compare === 'custom') {
+      timeOffsets = [formData.start_date_offset];
+    } else {
+      timeOffsets = ensureIsArray(formData.time_compare) || [];
+    }
+  }
   const comparisonSuffix = isUsingTimeComparison
     ? ensureIsArray(timeOffsets)[0]
     : '';
diff --git 
a/superset-frontend/src/explore/components/controls/ComparisonRangeLabel.tsx 
b/superset-frontend/src/explore/components/controls/ComparisonRangeLabel.tsx
index 5df43256ad..41f08654f1 100644
--- a/superset-frontend/src/explore/components/controls/ComparisonRangeLabel.tsx
+++ b/superset-frontend/src/explore/components/controls/ComparisonRangeLabel.tsx
@@ -122,7 +122,25 @@ export const ComparisonRangeLabel = ({
             ensureIsArray(newShifts),
           );
         }
-        return Promise.resolve({ value: '' });
+        return fetchTimeRange(filter.comparator, filter.subject).then(res => {
+          const datePattern = /\d{4}-\d{2}-\d{2}/g;
+          const dates = res?.value?.match(datePattern);
+          const [startDate, endDate] = dates ?? [];
+          const postProcessedShifts = getTimeOffset({
+            timeRangeFilter: {
+              ...filter,
+              comparator: `${startDate} : ${endDate}`,
+            },
+            shifts: shiftsArray,
+            startDate: useStartDate,
+            includeFutureOffsets: false, // So we don't trigger requests for 
future dates
+          });
+          return fetchTimeRange(
+            filter.comparator,
+            filter.subject,
+            ensureIsArray(postProcessedShifts),
+          );
+        });
       });
       Promise.all(promises).then(res => {
         // access the value property inside the res and set the labels with it 
in the state
diff --git 
a/superset-frontend/src/explore/components/controls/TimeOffsetControl.tsx 
b/superset-frontend/src/explore/components/controls/TimeOffsetControl.tsx
index 528e9dfac1..2cbf500e69 100644
--- a/superset-frontend/src/explore/components/controls/TimeOffsetControl.tsx
+++ b/superset-frontend/src/explore/components/controls/TimeOffsetControl.tsx
@@ -26,6 +26,7 @@ import {
   css,
   customTimeRangeDecode,
   computeCustomDateTime,
+  fetchTimeRange,
 } from '@superset-ui/core';
 import { DatePicker } from 'antd';
 import { RangePickerProps } from 'antd/lib/date-picker';
@@ -132,7 +133,20 @@ export default function TimeOffsetControls({
     if (!isEmpty(currentTimeRangeFilters)) {
       customTimeRange(currentTimeRangeFilters[0]?.comparator ?? '');
       const date = currentTimeRangeFilters[0]?.comparator.split(' : ')[0];
-      setFormatedFilterDate(moment(parseDttmToDate(date)));
+      const parsedDate = parseDttmToDate(date);
+      if (parsedDate) {
+        setFormatedFilterDate(moment(parseDttmToDate(date)));
+      } else {
+        fetchTimeRange(
+          currentTimeRangeFilters[0]?.comparator,
+          currentTimeRangeFilters[0]?.subject,
+        ).then(res => {
+          const datePattern = /\d{4}-\d{2}-\d{2}/g;
+          const dates = res?.value?.match(datePattern);
+          const [startDate, _] = dates ?? [];
+          setFormatedFilterDate(moment(parseDttmToDate(startDate)));
+        });
+      }
     } else {
       setCustomStartDateInFilter(undefined);
       setFormatedFilterDate(moment(parseDttmToDate('')));
diff --git a/superset/common/query_context_processor.py 
b/superset/common/query_context_processor.py
index 26935a4d96..ece73c348a 100644
--- a/superset/common/query_context_processor.py
+++ b/superset/common/query_context_processor.py
@@ -17,6 +17,7 @@
 from __future__ import annotations
 
 import copy
+from datetime import datetime
 import logging
 import re
 from typing import Any, cast, ClassVar, TYPE_CHECKING, TypedDict
@@ -368,6 +369,38 @@ class QueryContextProcessor:
                 axis=1,
             )
 
+    def is_valid_date(self, date_string: str):
+        try:
+            # Attempt to parse the string as a date in the format YYYY-MM-DD
+            datetime.strptime(date_string, "%Y-%m-%d")
+            return True
+        except ValueError:
+            # If parsing fails, it's not a valid date in the format YYYY-MM-DD
+            return False
+
+    def get_time_offset_for_custom_or_inherit(
+        self,
+        offset: str,
+        outer_from_dttm: datetime,
+        outer_to_dttm: datetime,
+    ) -> str:
+        """
+        Get the time offset for custom or inherit.
+
+        :param offset: The offset string.
+        :param outer_from_dttm: The outer from datetime.
+        :param outer_to_dttm: The outer to datetime.
+        :returns: The time offset.
+        """
+        if offset == "inherit":
+            # return the difference in days between the from and the to dttm 
formatted as a string with the " days ago" suffix
+            return f"{(outer_to_dttm - outer_from_dttm).days} days ago"
+        if self.is_valid_date(offset):
+            # return the offset as the difference in days between the outer 
from dttm and the offset date (which is a YYYY-MM-DD string) formatted as a 
string with the " days ago" suffix
+            offset_date = datetime.strptime(offset, "%Y-%m-%d")
+            return f"{(outer_from_dttm - offset_date).days} days ago"
+        return ""
+
     def processing_time_offsets(  # pylint: 
disable=too-many-locals,too-many-statements
         self,
         df: pd.DataFrame,
@@ -408,6 +441,13 @@ class QueryContextProcessor:
                 #      time_offsets: ['1 year ago'],
                 #      filters: [{col: 'dttm_col', op: 'TEMPORAL_RANGE', val: 
'2020 : 2021'}],
                 #    }
+                original_offset = offset
+                if self.is_valid_date(offset) or offset == "inherit":
+                    offset = self.get_time_offset_for_custom_or_inherit(
+                        offset,
+                        outer_from_dttm,
+                        outer_to_dttm,
+                    )
                 query_object_clone.from_dttm = get_past_or_future(
                     offset,
                     outer_from_dttm,
@@ -470,7 +510,7 @@ class QueryContextProcessor:
             query_object_clone_dct = query_object_clone.to_dict()
             # rename metrics: SUM(value) => SUM(value) 1 year ago
             metrics_mapping = {
-                metric: TIME_COMPARISON.join([metric, offset])
+                metric: TIME_COMPARISON.join([metric, original_offset])
                 for metric in metric_names
             }
 

Reply via email to