feat(table): Table with Time Comparison (#28057)

Co-authored-by: Lily Kuang <lily@preset.io>
Co-authored-by: lilykuang <jialikuang@gmail.com>
Co-authored-by: Kamil Gabryjelski <kamil.gabryjelski@gmail.com>
This commit is contained in:
Antonio Rivero
2024-06-14 20:21:43 +02:00
committed by GitHub
parent 37753cbdc2
commit 7ddea62331
36 changed files with 3722 additions and 543 deletions

View File

@@ -21,13 +21,21 @@ import {
buildQueryContext,
ensureIsArray,
getMetricLabel,
getTimeOffset,
isPhysicalColumn,
parseDttmToDate,
QueryMode,
QueryObject,
removeDuplicates,
SimpleAdhocFilter,
} from '@superset-ui/core';
import { PostProcessingRule } from '@superset-ui/core/src/query/types/PostProcessing';
import { BuildQuery } from '@superset-ui/core/src/chart/registries/ChartBuildQueryRegistrySingleton';
import {
isTimeComparison,
timeCompareOperator,
} from '@superset-ui/chart-controls';
import { isEmpty } from 'lodash';
import { TableChartFormData } from './types';
import { updateExternalFormData } from './DataTable/utils/externalAPIs';
@@ -69,9 +77,51 @@ const buildQuery: BuildQuery<TableChartFormData> = (
};
}
const addComparisonPercentMetrics = (metrics: string[], suffixes: string[]) =>
metrics.reduce<string[]>((acc, metric) => {
const newMetrics = suffixes.map(suffix => `${metric}__${suffix}`);
return acc.concat([metric, ...newMetrics]);
}, []);
return buildQueryContext(formDataCopy, baseQueryObject => {
let { metrics, orderby = [], columns = [] } = baseQueryObject;
const { extras = {} } = baseQueryObject;
let postProcessing: PostProcessingRule[] = [];
const TimeRangeFilters =
formData.adhoc_filters?.filter(
(filter: SimpleAdhocFilter) => filter.operator === 'TEMPORAL_RANGE',
) || [];
// In case the viz is using all version of controls, we try to load them
const previousCustomTimeRangeFilters: any =
formData.adhoc_custom?.filter(
(filter: SimpleAdhocFilter) => filter.operator === 'TEMPORAL_RANGE',
) || [];
let previousCustomStartDate = '';
if (
!isEmpty(previousCustomTimeRangeFilters) &&
previousCustomTimeRangeFilters[0]?.comparator !== 'No Filter'
) {
previousCustomStartDate =
previousCustomTimeRangeFilters[0]?.comparator.split(' : ')[0];
}
const timeOffsets = ensureIsArray(
isTimeComparison(formData, baseQueryObject)
? getTimeOffset({
timeRangeFilter: TimeRangeFilters[0],
shifts: formData.time_compare,
startDate:
previousCustomStartDate && !formData.start_date_offset
? parseDttmToDate(previousCustomStartDate)?.toUTCString()
: formData.start_date_offset,
})
: [],
);
let temporalColumAdded = false;
let temporalColum = null;
if (queryMode === QueryMode.Aggregate) {
metrics = metrics || [];
@@ -85,8 +135,17 @@ const buildQuery: BuildQuery<TableChartFormData> = (
}
// add postprocessing for percent metrics only when in aggregation mode
if (percentMetrics && percentMetrics.length > 0) {
const percentMetricsLabelsWithTimeComparison = isTimeComparison(
formData,
baseQueryObject,
)
? addComparisonPercentMetrics(
percentMetrics.map(getMetricLabel),
timeOffsets,
)
: percentMetrics.map(getMetricLabel);
const percentMetricLabels = removeDuplicates(
percentMetrics.map(getMetricLabel),
percentMetricsLabelsWithTimeComparison,
);
metrics = removeDuplicates(
metrics.concat(percentMetrics),
@@ -102,23 +161,38 @@ const buildQuery: BuildQuery<TableChartFormData> = (
},
];
}
// Add the operator for the time comparison if some is selected
if (!isEmpty(timeOffsets)) {
postProcessing.push(timeCompareOperator(formData, baseQueryObject));
}
columns = columns.map(col => {
if (
const temporalColumnsLookup = formData?.temporal_columns_lookup;
// Filter out the column if needed and prepare the temporal column object
columns = columns.filter(col => {
const shouldBeAdded =
isPhysicalColumn(col) &&
time_grain_sqla &&
formData?.temporal_columns_lookup?.[col]
) {
return {
temporalColumnsLookup?.[col];
if (shouldBeAdded && !temporalColumAdded) {
temporalColum = {
timeGrain: time_grain_sqla,
columnType: 'BASE_AXIS',
sqlExpression: col,
label: col,
expressionType: 'SQL',
} as AdhocColumn;
temporalColumAdded = true;
return false; // Do not include this in the output; it's added separately
}
return col;
return true;
});
// So we ensure the temporal column is added first
if (temporalColum) {
columns = [temporalColum, ...columns];
}
}
const moreProps: Partial<QueryObject> = {};
@@ -133,9 +207,11 @@ const buildQuery: BuildQuery<TableChartFormData> = (
let queryObject = {
...baseQueryObject,
columns,
extras: !isEmpty(timeOffsets) && !temporalColum ? {} : extras,
orderby,
metrics,
post_processing: postProcessing,
time_offsets: timeOffsets,
...moreProps,
};
@@ -169,6 +245,7 @@ const buildQuery: BuildQuery<TableChartFormData> = (
row_limit: 0,
row_offset: 0,
post_processing: [],
extras: undefined, // we don't need time grain here
order_desc: undefined, // we don't need orderby stuff here,
orderby: undefined, // because this query will be used for get total aggregation.
});
@@ -186,6 +263,7 @@ const buildQuery: BuildQuery<TableChartFormData> = (
{ ...queryObject },
{
...queryObject,
time_offsets: [],
row_limit: 0,
row_offset: 0,
post_processing: [],