Antonio-RiveroMartnez commented on issue #27432:
URL: https://github.com/apache/superset/issues/27432#issuecomment-2099458313

   ### Extending current `/time_range` API endpoint
   
   We are going to leverage recent changes in the API that let us use arrays as 
input of this API and extend the schema of it. We are adding a new `shift` 
property so the endpoint can return the adequate `since` and `until` with any 
passed `shift` applied to it using the existing `get_since_until`method.
   
   `superset/views/api.py`
   ```
   get_time_range_schema = {
       "type": ["string", "array"],
       "items": {
           "type": "object",
           "properties": {
               "timeRange": {"type": "string"},
               "shift": {"type": "string"},
           },
       },
   }
   ...
   since, until = get_since_until(
       time_range=time_range["timeRange"],
       time_shift=time_range.get("shift"),
   )
   ...
   
   ```
   
   ### Extending how `time_offset` is handled in the backend
   
   Superset doesn't support having only textual columns (dimension) when a 
`time_offset` comes into play. Instead we are breaking the hard requirement for 
a temporal column as part of the `queyObject` and a `xaxis_label`. Also the 
`time_grain` is mandatory as long a temporal dimensions are used.
   
   ```
   def get_since_until_from_query_object(
       ...
       # The condition that checked for get_xaxis_label is removed
       if flt.get("op") == FilterOperator.TEMPORAL_RANGE.value and isinstance(
               flt.get("val"), str
           ):
       ...
   )
   ```
   Since now our `time_offset` API supports textual columns we might end up 
running a query that has no temporal column in it, in that case we cannot apply 
the offset to any column using the same filter values, for that particular case 
where no temporal column is passed we must run the query altering the filter 
itself so we can perform the joins later on.
   
   `superset/common/query_context_processor.py`
   ```
   def processing_time_offsets(
   ...
   if not dataframe_utils.is_datetime_series(df.get(index)):
       # Lets find the first temporal filter in the filters array and change
       # its val to be the result of get_since_until with the offset
       for flt in query_object_clone.filter:
           if flt.get(
               "op"
           ) == FilterOperator.TEMPORAL_RANGE.value and isinstance(
               flt.get("val"), str
           ):
               time_range = cast(str, flt.get("val"))
               (
                   new_outer_from_dttm,
                   new_outer_to_dttm,
               ) = get_since_until_from_time_range(
                   time_range=time_range,
                   time_shift=offset,
               )
               flt["val"] = f"{new_outer_from_dttm} : {new_outer_to_dttm}"
   ```
   
   Additionally, we are proposing the use of `Full Outer Joins` as a way to 
solve the missing datapoints when the query uses a temporal dimension, since 
SQLAlchemy cannot perform a `Left Join` as it is right now using ON clauses 
like `ON queryA.temporal_column = queryB.temporal_column + time_delta`.
   
   The usage of the `Full Outer Joins` would produce more datapoints in our 
visualizations but generate a more correct result set.
   
   `superset/common/query_context_processor.py`
   ```
   def processing_time_offsets(
   ...
   
   if join_keys and not time_grain:
       # only do a left join if textual dimensions are the only
       # thing we care about
       df = dataframe_utils.left_join_df(
           left_df=df,
           right_df=offset_df,
           join_keys=join_keys,
           rsuffix=R_SUFFIX,
       )
       else:
           # If a temporal dimension is used, we must use full outer join to
           # ensure that data is not lost if not present in both dataframes
           df = dataframe_utils.full_outer_join_df(
               left_df=df,
               right_df=offset_df,
               join_keys=join_keys,
               rsuffix=R_SUFFIX,
           )
   ```
   
   ### Adding a new and reusable control section to charts
   
   `Advanced Analytics` controls are meant to be our source of truth for 
visualizations that want to use time comparison, however, our main goal is to 
extract some of the inner controls in it to a more compact and flexible set of 
controls which are called `Time Comparison`.
   
   In order to not create friction for visualizations unintentionally and 
collect better feedback from users, we are going to use this new section in a 
limited set of visualizations, meaning, until the full transition occurs to 
this new one, some degree of duplication is expected.
   
   
`superset-frontend/packages/superset-ui-chart-controls/src/sections/timeComparison.tsx`
   
   ```
   export const timeComparisonControls: (
     multi?: boolean, // Multiple is not always true as in Advanced Analytics
     showCalculationType?: boolean, // Some visualizations might not want to 
make use of the calculation type
     showFullChoices?: boolean, // We can show a limited set of choices instead 
of the very populated one
   ) => 
   ...
   ) => ({
     label: t('Time Comparison'),
     tabOverride: 'data',
     description: t('Compare results with other time periods.'),
     controlSetRows: [
       [
         {
           name: 'time_compare',
           config: {
             type: 'SelectControl',
             multi,
             freeForm: true,
             placeholder: t('Select or type a custom value...'),
             label: t('Time shift'),
             choices: showFullChoices ? fullChoices : reducedChoices,
             // We would support the new Custom Start date and Inherit form 
time filter
             description: ...
           },
         },
       ],
       [
         {
           name: 'start_date_offset',
           config: {
             type: 'TimeOffsetControl',
             label: t('shift start date'),
             visibility: ({ controls }) =>
               controls?.time_compare.value === 'custom',
           },
         },
       ],
       [
         {
           name: 'comparison_type',
             ...
             visibility: () => Boolean(showCalculationType),
           },
         },
       ],
       [
         {
           name: 'comparison_range_label',
           config: {
             type: 'ComparisonRangeLabel',
             multi,
             visibility: ({ controls }) => 
Boolean(controls?.time_compare.value),
           },
         },
       ],
     ],
   });
   ```
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscr...@superset.apache.org

For queries about this service, please contact Infrastructure at:
us...@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscr...@superset.apache.org
For additional commands, e-mail: notifications-h...@superset.apache.org

Reply via email to