Merge branch 'master' into template_less

This commit is contained in:
Maxime Beauchemin
2025-04-01 00:45:31 -07:00
52 changed files with 828 additions and 185 deletions

View File

@@ -198,16 +198,19 @@ export default function PopKPI(props: PopKPIProps) {
symbol: '#',
value: prevNumber,
tooltipText: t('Data for %s', comparisonRange || 'previous range'),
columnKey: 'Previous value',
},
{
symbol: '△',
value: valueDifference,
tooltipText: t('Value difference between the time periods'),
columnKey: 'Delta',
},
{
symbol: '%',
value: percentDifferenceFormattedString,
tooltipText: t('Percentage difference between the time periods'),
columnKey: 'Percent change',
},
],
[
@@ -218,6 +221,10 @@ export default function PopKPI(props: PopKPIProps) {
],
);
const visibleSymbols = SYMBOLS_WITH_VALUES.filter(
symbol => props.columnConfig?.[symbol.columnKey]?.visible !== false,
);
const { isOverflowing, symbolContainerRef, wrapperRef } =
useOverflowDetection(flexGap);
@@ -242,51 +249,53 @@ export default function PopKPI(props: PopKPIProps) {
)}
</div>
<div
css={[
css`
display: flex;
justify-content: space-around;
gap: ${flexGap}px;
min-width: 0;
flex-shrink: 1;
`,
isOverflowing
? css`
flex-direction: column;
align-items: flex-start;
width: fit-content;
`
: css`
align-items: center;
width: 100%;
`,
]}
ref={symbolContainerRef}
>
{SYMBOLS_WITH_VALUES.map((symbol_with_value, index) => (
<ComparisonValue
key={`comparison-symbol-${symbol_with_value.symbol}`}
subheaderFontSize={subheaderFontSize}
>
<Tooltip
id="tooltip"
placement="top"
title={symbol_with_value.tooltipText}
{visibleSymbols.length > 0 && (
<div
css={[
css`
display: flex;
justify-content: space-around;
gap: ${flexGap}px;
min-width: 0;
flex-shrink: 1;
`,
isOverflowing
? css`
flex-direction: column;
align-items: flex-start;
width: fit-content;
`
: css`
align-items: center;
width: 100%;
`,
]}
ref={symbolContainerRef}
>
{visibleSymbols.map((symbol_with_value, index) => (
<ComparisonValue
key={`comparison-symbol-${symbol_with_value.symbol}`}
subheaderFontSize={subheaderFontSize}
>
<SymbolWrapper
backgroundColor={
index > 0 ? backgroundColor : defaultBackgroundColor
}
textColor={index > 0 ? textColor : defaultTextColor}
<Tooltip
id="tooltip"
placement="top"
title={symbol_with_value.tooltipText}
>
{symbol_with_value.symbol}
</SymbolWrapper>
{symbol_with_value.value}
</Tooltip>
</ComparisonValue>
))}
</div>
<SymbolWrapper
backgroundColor={
index > 0 ? backgroundColor : defaultBackgroundColor
}
textColor={index > 0 ? textColor : defaultTextColor}
>
{symbol_with_value.symbol}
</SymbolWrapper>
{symbol_with_value.value}
</Tooltip>
</ComparisonValue>
))}
</div>
)}
</NumbersContainer>
</div>
);

View File

@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
import { t } from '@superset-ui/core';
import { t, GenericDataType } from '@superset-ui/core';
import {
ControlPanelConfig,
getStandardizedControls,
@@ -106,6 +106,42 @@ const config: ControlPanelConfig = {
},
},
],
[
{
name: 'column_config',
config: {
type: 'ColumnConfigControl',
label: t('Customize columns'),
description: t('Further customize how to display each column'),
width: 400,
height: 320,
renderTrigger: true,
configFormLayout: {
[GenericDataType.Numeric]: [
{
tab: t('General'),
children: [['visible']],
},
],
},
shouldMapStateToProps() {
return true;
},
mapStateToProps(explore, _, chart) {
return {
columnsPropsObject: {
colnames: ['Previous value', 'Delta', 'Percent change'],
coltypes: [
GenericDataType.Numeric,
GenericDataType.Numeric,
GenericDataType.Numeric,
],
},
};
},
},
},
],
],
},
sections.timeComparisonControls({

View File

@@ -89,6 +89,7 @@ export default function transformProps(chartProps: ChartProps) {
comparisonColorScheme,
comparisonColorEnabled,
percentDifferenceFormat,
columnConfig,
} = formData;
const { data: dataA = [] } = queriesData[0];
const data = dataA;
@@ -193,5 +194,6 @@ export default function transformProps(chartProps: ChartProps) {
startDateOffset,
shift: timeComparison,
dashboardTimeRange: formData?.extraFormData?.time_range,
columnConfig,
};
}

View File

@@ -34,6 +34,10 @@ export interface PopKPIStylesProps {
comparisonColorEnabled: boolean;
}
export type TableColumnConfig = {
visible?: boolean;
};
interface PopKPICustomizeProps {
headerText: string;
}
@@ -67,6 +71,7 @@ export type PopKPIProps = PopKPIStylesProps &
startDateOffset?: string;
shift: string;
dashboardTimeRange?: string;
columnConfig?: Record<string, TableColumnConfig>;
};
export enum ColorSchemeEnum {

View File

@@ -24,6 +24,7 @@ import {
QueryFormData,
} from '@superset-ui/core';
import {
aggregationOperator,
flattenOperator,
pivotOperator,
resampleOperator,
@@ -47,5 +48,19 @@ export default function buildQuery(formData: QueryFormData) {
flattenOperator(formData, baseQueryObject),
],
},
{
...baseQueryObject,
columns: [
...(isXAxisSet(formData)
? ensureIsArray(getXAxisColumn(formData))
: []),
],
...(isXAxisSet(formData) ? {} : { is_timeseries: true }),
post_processing: [
pivotOperator(formData, baseQueryObject),
aggregationOperator(formData, baseQueryObject),
],
},
]);
}

View File

@@ -18,6 +18,7 @@
*/
import { SMART_DATE_ID, t } from '@superset-ui/core';
import {
aggregationControl,
ControlPanelConfig,
ControlSubSectionHeader,
D3_FORMAT_DOCS,
@@ -35,6 +36,7 @@ const config: ControlPanelConfig = {
controlSetRows: [
['x_axis'],
['time_grain_sqla'],
[aggregationControl],
['metric'],
['adhoc_filters'],
],

View File

@@ -66,6 +66,7 @@ export default function transformProps(
metric = 'value',
showTimestamp,
showTrendLine,
aggregation,
startYAxisAtZero,
subheader = '',
subheaderFontSize,
@@ -82,6 +83,15 @@ export default function transformProps(
from_dttm: fromDatetime,
to_dttm: toDatetime,
} = queriesData[0];
const aggregatedQueryData = queriesData.length > 1 ? queriesData[1] : null;
const hasAggregatedData =
aggregatedQueryData?.data &&
aggregatedQueryData.data.length > 0 &&
aggregation !== 'LAST_VALUE';
const aggregatedData = hasAggregatedData ? aggregatedQueryData.data[0] : null;
const refs: Refs = {};
const metricName = getMetricLabel(metric);
const compareLag = Number(compareLag_) || 0;
@@ -95,18 +105,39 @@ export default function transformProps(
let percentChange = 0;
let bigNumber = data.length === 0 ? null : data[0][metricName];
let timestamp = data.length === 0 ? null : data[0][xAxisLabel];
let bigNumberFallback;
const metricColtypeIndex = colnames.findIndex(name => name === metricName);
const metricColtype =
metricColtypeIndex > -1 ? coltypes[metricColtypeIndex] : null;
let bigNumberFallback = null;
let sortedData: [number | null, number | null][] = [];
if (data.length > 0) {
const sortedData = (data as BigNumberDatum[])
.map(d => [d[xAxisLabel], parseMetricValue(d[metricName])])
sortedData = (data as BigNumberDatum[])
.map(
d =>
[d[xAxisLabel], parseMetricValue(d[metricName])] as [
number | null,
number | null,
],
)
// sort in time descending order
.sort((a, b) => (a[0] !== null && b[0] !== null ? b[0] - a[0] : 0));
}
if (hasAggregatedData && aggregatedData) {
if (
aggregatedData[metricName] !== null &&
aggregatedData[metricName] !== undefined
) {
bigNumber = aggregatedData[metricName];
} else {
const metricKeys = Object.keys(aggregatedData).filter(
key =>
key !== xAxisLabel &&
aggregatedData[key] !== null &&
typeof aggregatedData[key] === 'number',
);
bigNumber = metricKeys.length > 0 ? aggregatedData[metricKeys[0]] : null;
}
timestamp = sortedData.length > 0 ? sortedData[0][0] : null;
} else if (sortedData.length > 0) {
bigNumber = sortedData[0][1];
timestamp = sortedData[0][0];
@@ -115,25 +146,28 @@ export default function transformProps(
bigNumber = bigNumberFallback ? bigNumberFallback[1] : null;
timestamp = bigNumberFallback ? bigNumberFallback[0] : null;
}
}
if (compareLag > 0) {
const compareIndex = compareLag;
if (compareIndex < sortedData.length) {
const compareValue = sortedData[compareIndex][1];
// compare values must both be non-nulls
if (bigNumber !== null && compareValue !== null) {
percentChange = compareValue
? (bigNumber - compareValue) / Math.abs(compareValue)
: 0;
formattedSubheader = `${formatPercentChange(
percentChange,
)} ${compareSuffix}`;
}
if (compareLag > 0 && sortedData.length > 0) {
const compareIndex = compareLag;
if (compareIndex < sortedData.length) {
const compareValue = sortedData[compareIndex][1];
// compare values must both be non-nulls
if (bigNumber !== null && compareValue !== null) {
percentChange = compareValue
? (Number(bigNumber) - compareValue) / Math.abs(compareValue)
: 0;
formattedSubheader = `${formatPercentChange(
percentChange,
)} ${compareSuffix}`;
}
}
sortedData.reverse();
}
if (data.length > 0) {
const reversedData = [...sortedData].reverse();
// @ts-ignore
trendLineData = showTrendLine ? sortedData : undefined;
trendLineData = showTrendLine ? reversedData : undefined;
}
let className = '';
@@ -143,6 +177,10 @@ export default function transformProps(
className = 'negative';
}
const metricColtypeIndex = colnames.findIndex(name => name === metricName);
const metricColtype =
metricColtypeIndex > -1 ? coltypes[metricColtypeIndex] : null;
let metricEntry: Metric | undefined;
if (chartProps.datasource?.metrics) {
metricEntry = chartProps.datasource.metrics.find(