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