refactor(plugins): BigNumber Time Comparison with existing time_offset API (#27718)

Co-authored-by: lilykuang <jialikuang@gmail.com>
Co-authored-by: Kamil Gabryjelski <kamil.gabryjelski@gmail.com>
This commit is contained in:
Antonio Rivero
2024-05-16 18:47:50 +02:00
committed by GitHub
parent b69958b412
commit b1f85dce71
25 changed files with 1533 additions and 195 deletions

View File

@@ -16,9 +16,18 @@
* specific language governing permissions and limitations
* under the License.
*/
import React, { useMemo } from 'react';
import { css, styled, t, useTheme } from '@superset-ui/core';
import React, { useEffect, useMemo, useState } from 'react';
import {
css,
ensureIsArray,
fetchTimeRange,
getTimeOffset,
styled,
t,
useTheme,
} from '@superset-ui/core';
import { Tooltip } from '@superset-ui/chart-controls';
import { isEmpty } from 'lodash';
import {
ColorSchemeEnum,
PopKPIComparisonSymbolStyleProps,
@@ -69,9 +78,38 @@ export default function PopKPI(props: PopKPIProps) {
comparisonColorEnabled,
comparisonColorScheme,
percentDifferenceNumber,
comparatorText,
currentTimeRangeFilter,
startDateOffset,
shift,
} = props;
const [comparisonRange, setComparisonRange] = useState<string>('');
useEffect(() => {
if (!currentTimeRangeFilter || (!shift && !startDateOffset)) {
setComparisonRange('');
} else if (!isEmpty(shift) || startDateOffset) {
const newShift = getTimeOffset(
currentTimeRangeFilter,
ensureIsArray(shift),
startDateOffset || '',
);
const promise: any = fetchTimeRange(
(currentTimeRangeFilter as any).comparator,
currentTimeRangeFilter.subject,
newShift || [],
);
Promise.resolve(promise).then((res: any) => {
const response: string[] = ensureIsArray(res.value);
const firstRange: string = response.flat()[0];
const rangeText = firstRange.split('vs\n');
setComparisonRange(
rangeText.length > 1 ? rangeText[1].trim() : rangeText[0],
);
});
}
}, [currentTimeRangeFilter, shift, startDateOffset]);
const theme = useTheme();
const flexGap = theme.gridUnit * 5;
const wrapperDivStyles = css`
@@ -150,7 +188,7 @@ export default function PopKPI(props: PopKPIProps) {
{
symbol: '#',
value: prevNumber,
tooltipText: t('Data for %s', comparatorText),
tooltipText: t('Data for %s', comparisonRange || 'previous range'),
},
{
symbol: '△',
@@ -164,7 +202,7 @@ export default function PopKPI(props: PopKPIProps) {
},
],
[
comparatorText,
comparisonRange,
prevNumber,
valueDifference,
percentDifferenceFormattedString,

View File

@@ -18,50 +18,50 @@
*/
import {
buildQueryContext,
getComparisonInfo,
ComparisonTimeRangeType,
QueryFormData,
PostProcessingRule,
ensureIsArray,
SimpleAdhocFilter,
getTimeOffset,
} from '@superset-ui/core';
import {
isTimeComparison,
timeCompareOperator,
} from '@superset-ui/chart-controls';
export default function buildQuery(formData: QueryFormData) {
const {
cols: groupby,
time_comparison: timeComparison,
extra_form_data: extraFormData,
} = formData;
const { cols: groupby } = formData;
const queryContextA = buildQueryContext(formData, baseQueryObject => [
{
...baseQueryObject,
groupby,
},
]);
const queryContextA = buildQueryContext(formData, baseQueryObject => {
const postProcessing: PostProcessingRule[] = [];
postProcessing.push(timeCompareOperator(formData, baseQueryObject));
const TimeRangeFilters =
formData.adhoc_filters?.filter(
(filter: SimpleAdhocFilter) => filter.operator === 'TEMPORAL_RANGE',
) || [];
const comparisonFormData = getComparisonInfo(
formData,
timeComparison,
extraFormData,
);
const queryContextB = buildQueryContext(
comparisonFormData,
baseQueryObject => [
const timeOffsets = ensureIsArray(
isTimeComparison(formData, baseQueryObject)
? getTimeOffset(
TimeRangeFilters[0],
formData.time_compare,
formData.start_date_offset,
)
: [],
);
return [
{
...baseQueryObject,
groupby,
extras: {
...baseQueryObject.extras,
instant_time_comparison_range:
timeComparison !== ComparisonTimeRangeType.Custom
? timeComparison
: undefined,
},
post_processing: postProcessing,
time_offsets: isTimeComparison(formData, baseQueryObject)
? ensureIsArray(timeOffsets)
: [],
},
],
);
];
});
return {
...queryContextA,
queries: [...queryContextA.queries, ...queryContextB.queries],
};
}

View File

@@ -16,20 +16,12 @@
* specific language governing permissions and limitations
* under the License.
*/
import { t } from '@superset-ui/core';
import {
AdhocFilter,
ComparisonTimeRangeType,
SimpleAdhocFilter,
t,
validateTimeComparisonRangeValues,
} from '@superset-ui/core';
import {
ColumnMeta,
ControlPanelConfig,
ControlPanelState,
ControlState,
getStandardizedControls,
sharedControls,
sections,
} from '@superset-ui/chart-controls';
import { headerFontSize, subheaderFontSize } from '../sharedControls';
import { ColorSchemeEnum } from './types';
@@ -42,70 +34,6 @@ const config: ControlPanelConfig = {
controlSetRows: [
['metric'],
['adhoc_filters'],
[
{
name: 'time_comparison',
config: {
type: 'SelectControl',
label: t('Range for Comparison'),
default: 'r',
choices: [
['r', 'Inherit range from time filters'],
['y', 'Year'],
['m', 'Month'],
['w', 'Week'],
['c', 'Custom'],
],
rerender: ['adhoc_custom'],
description: t(
'Set the time range that will be used for the comparison metrics. ' +
'For example, "Year" will compare to the same dates one year earlier. ' +
'Use "Inherit range from time filters" to shift the comparison time range' +
'by the same length as your time range and use "Custom" to set a custom comparison range.',
),
},
},
],
[
{
name: `adhoc_custom`,
config: {
...sharedControls.adhoc_filters,
label: t('Filters for Comparison'),
description:
'This only applies when selecting the Range for Comparison Type: Custom',
visibility: ({ controls }) =>
controls?.time_comparison?.value ===
ComparisonTimeRangeType.Custom,
mapStateToProps: (
state: ControlPanelState,
controlState: ControlState,
) => {
const originalMapStateToPropsRes =
sharedControls.adhoc_filters.mapStateToProps?.(
state,
controlState,
) || {};
const columns = originalMapStateToPropsRes.columns.filter(
(col: ColumnMeta) =>
col.is_dttm &&
(state.controls.adhoc_filters.value as AdhocFilter[]).some(
(val: SimpleAdhocFilter) =>
val.subject === col.column_name,
),
);
return {
...originalMapStateToPropsRes,
columns,
externalValidationErrors: validateTimeComparisonRangeValues(
state.controls?.time_comparison?.value,
controlState.value,
),
};
},
},
},
],
[
{
name: 'row_limit',
@@ -180,14 +108,16 @@ const config: ControlPanelConfig = {
],
],
},
sections.timeComparisonControls({
multi: false,
showCalculationType: false,
showFullChoices: false,
}),
],
controlOverrides: {
y_axis_format: {
label: t('Number format'),
},
adhoc_filters: {
rerender: ['adhoc_custom'],
},
},
formDataOverrides: formData => ({
...formData,

View File

@@ -22,7 +22,9 @@ import {
getMetricLabel,
getValueFormatter,
getNumberFormatter,
formatTimeRange,
SimpleAdhocFilter,
ensureIsArray,
getTimeOffset,
} from '@superset-ui/core';
import { getComparisonFontSize, getHeaderFontSize } from './utils';
@@ -87,17 +89,49 @@ export default function transformProps(chartProps: ChartProps) {
percentDifferenceFormat,
} = formData;
const { data: dataA = [] } = queriesData[0];
const {
data: dataB = [],
from_dttm: comparisonFromDatetime,
to_dttm: comparisonToDatetime,
} = queriesData[1];
const data = dataA;
const metricName = getMetricLabel(metric);
const timeComparison = ensureIsArray(chartProps.rawFormData?.time_compare)[0];
const startDateOffset = chartProps.rawFormData?.start_date_offset;
const currentTimeRangeFilter = chartProps.rawFormData?.adhoc_filters?.filter(
(adhoc_filter: SimpleAdhocFilter) =>
adhoc_filter.operator === 'TEMPORAL_RANGE',
)?.[0];
const isCustomOrInherit =
timeComparison === 'custom' || timeComparison === 'inherit';
let dataOffset: string[] = [];
if (isCustomOrInherit) {
dataOffset = getTimeOffset(
currentTimeRangeFilter,
ensureIsArray(timeComparison),
startDateOffset || '',
);
}
const { value1, value2 } = data.reduce(
(acc: { value1: number; value2: number }, curr: { [x: string]: any }) => {
Object.keys(curr).forEach(key => {
if (
key.includes(
`${metricName}__${
!isCustomOrInherit ? timeComparison : dataOffset[0]
}`,
)
) {
acc.value2 += curr[key];
} else if (key.includes(metricName)) {
acc.value1 += curr[key];
}
});
return acc;
},
{ value1: 0, value2: 0 },
);
let bigNumber: number | string =
data.length === 0 ? 0 : parseMetricValue(data[0][metricName]);
data.length === 0 ? 0 : parseMetricValue(value1);
let prevNumber: number | string =
data.length === 0 ? 0 : parseMetricValue(dataB[0][metricName]);
data.length === 0 ? 0 : parseMetricValue(value2);
const numberFormatter = getValueFormatter(
metric,
@@ -133,10 +167,6 @@ export default function transformProps(chartProps: ChartProps) {
prevNumber = numberFormatter(prevNumber);
valueDifference = numberFormatter(valueDifference);
const percentDifference: string = formatPercentChange(percentDifferenceNum);
const comparatorText = formatTimeRange('%Y-%m-%d', [
comparisonFromDatetime,
comparisonToDatetime,
]);
return {
width,
@@ -155,6 +185,8 @@ export default function transformProps(chartProps: ChartProps) {
comparisonColorEnabled,
comparisonColorScheme,
percentDifferenceNumber: percentDifferenceNum,
comparatorText,
currentTimeRangeFilter,
startDateOffset,
shift: timeComparison,
};
}

View File

@@ -21,6 +21,7 @@ import {
supersetTheme,
TimeseriesDataRecord,
Metric,
SimpleAdhocFilter,
} from '@superset-ui/core';
export interface PopKPIStylesProps {
@@ -60,8 +61,10 @@ export type PopKPIProps = PopKPIStylesProps &
percentDifferenceFormattedString: string;
compType: string;
percentDifferenceNumber: number;
comparatorText: string;
comparisonColorScheme?: string;
currentTimeRangeFilter?: SimpleAdhocFilter;
startDateOffset?: string;
shift: string;
};
export enum ColorSchemeEnum {