mirror of
https://github.com/apache/superset.git
synced 2026-05-12 19:35:17 +00:00
Merge branch 'master' into template_less
This commit is contained in:
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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),
|
||||
],
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -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'],
|
||||
],
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user