mirror of
https://github.com/apache/superset.git
synced 2026-04-17 23:25:05 +00:00
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:
@@ -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,
|
||||
|
||||
@@ -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],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user