Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(Timeseries): Respect time grain #31765

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
AxisType,
buildCustomFormatters,
CategoricalColorNamespace,
convertKeysToCamelCase,
CurrencyFormatter,
ensureIsArray,
GenericDataType,
Expand Down Expand Up @@ -205,7 +206,11 @@ export default function transformProps(
percentageThreshold,
metrics = [],
metricsB = [],
}: EchartsMixedTimeseriesFormData = { ...DEFAULT_FORM_DATA, ...formData };
}: EchartsMixedTimeseriesFormData = {
...DEFAULT_FORM_DATA,
...formData,
...convertKeysToCamelCase(formData.extraFormData),
};

const refs: Refs = {};
const colorScale = CategoricalColorNamespace.getScale(colorScheme as string);
Expand Down Expand Up @@ -474,11 +479,11 @@ export default function transformProps(

const tooltipFormatter =
xAxisDataType === GenericDataType.Temporal
? getTooltipTimeFormatter(tooltipTimeFormat)
? getTooltipTimeFormatter(tooltipTimeFormat, timeGrainSqla)
: String;
const xAxisFormatter =
xAxisDataType === GenericDataType.Temporal
? getXAxisFormatter(xAxisTimeFormat)
? getXAxisFormatter(xAxisTimeFormat, timeGrainSqla)
: String;

const addYAxisTitleOffset = !!(yAxisTitle || yAxisTitleSecondary);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import {
t,
TimeseriesChartDataResponseResult,
NumberFormats,
convertKeysToCamelCase,
} from '@superset-ui/core';
import {
extractExtraMetrics,
Expand Down Expand Up @@ -191,7 +192,11 @@ export default function transformProps(
yAxisTitleMargin,
yAxisTitlePosition,
zoomable,
}: EchartsTimeseriesFormData = { ...DEFAULT_FORM_DATA, ...formData };
}: EchartsTimeseriesFormData = {
...DEFAULT_FORM_DATA,
...formData,
...convertKeysToCamelCase(formData.extraFormData),
};
const refs: Refs = {};
const groupBy = ensureIsArray(groupby);
const labelMap: { [key: string]: string[] } = Object.entries(
Expand Down Expand Up @@ -444,11 +449,11 @@ export default function transformProps(

const tooltipFormatter =
xAxisDataType === GenericDataType.Temporal
? getTooltipTimeFormatter(tooltipTimeFormat)
? getTooltipTimeFormatter(tooltipTimeFormat, timeGrainSqla)
: String;
const xAxisFormatter =
xAxisDataType === GenericDataType.Temporal
? getXAxisFormatter(xAxisTimeFormat)
? getXAxisFormatter(xAxisTimeFormat, timeGrainSqla)
: String;

const {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
SMART_DATE_ID,
SMART_DATE_VERBOSE_ID,
TimeFormatter,
TimeGranularity,
ValueFormatter,
} from '@superset-ui/core';

Expand Down Expand Up @@ -76,24 +77,40 @@ export const getYAxisFormatter = (

export function getTooltipTimeFormatter(
format?: string,
timeGrain?: TimeGranularity,
): TimeFormatter | StringConstructor {
if (
timeGrain === TimeGranularity.QUARTER ||
timeGrain === TimeGranularity.MONTH ||
timeGrain === TimeGranularity.YEAR
) {
return getTimeFormatter(undefined, timeGrain);
}
if (format === SMART_DATE_ID) {
return getSmartDateDetailedFormatter();
return getTimeFormatter(SMART_DATE_DETAILED_ID, timeGrain);
}
Comment on lines 89 to 91
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Incorrect Override of Smart Date Format category Functionality

Tell me more
What is the issue?

When SMART_DATE_ID format is specified, the code overrides it with SMART_DATE_DETAILED_ID, which contradicts the user's explicit format choice.

Why this matters

Users selecting SMART_DATE_ID format will unexpectedly receive a different formatting style than requested, potentially causing confusion and inconsistency in the application's behavior.

Suggested change

Respect the user's format choice by modifying the code to:

if (format === SMART_DATE_ID) {
    return getTimeFormatter(SMART_DATE_ID, timeGrain);
}
Chat with Korbit by mentioning @korbit-ai, and give a 👍 or 👎 to help Korbit improve your reviews.

if (format) {
return getTimeFormatter(format);
return getTimeFormatter(format, timeGrain);
}
return String;
}

export function getXAxisFormatter(
format?: string,
timeGrain?: TimeGranularity,
): TimeFormatter | StringConstructor | undefined {
if (
timeGrain === TimeGranularity.QUARTER ||
timeGrain === TimeGranularity.MONTH ||
timeGrain === TimeGranularity.YEAR
) {
return getTimeFormatter(undefined, timeGrain);
}
if (format === SMART_DATE_ID || !format) {
return undefined;
}
if (format) {
return getTimeFormatter(format);
return getTimeFormatter(format, timeGrain);
}
return String;
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,14 @@
* specific language governing permissions and limitations
* under the License.
*/
import { ChartProps, supersetTheme, VizType } from '@superset-ui/core';
import {
ChartProps,
GenericDataType,
supersetTheme,
TimeFormatter,
TimeGranularity,
VizType,
} from '@superset-ui/core';
import {
LegendOrientation,
LegendType,
Expand Down Expand Up @@ -159,3 +166,154 @@ it('should transform chart props for viz', () => {
}),
);
});

describe('should add the correct time formatter for the xAxis', () => {
const getXAxisFormatter = (timeGrainSqla: TimeGranularity) => {
const updatedChartPropsConfig = {
...chartPropsConfig,
formData: {
...chartPropsConfig.formData,
time_grain_sqla: timeGrainSqla,
xAxisTimeFormat: '%Y-%m-%d',
},
queriesData: [
{
data: chartPropsConfig.queriesData[0].data,
label_map: chartPropsConfig.queriesData[0].label_map,
colnames: ['Boy', 'Girl', 'ds'],
coltypes: [
GenericDataType.String,
GenericDataType.String,
GenericDataType.Temporal,
],
},
{
data: chartPropsConfig.queriesData[1].data,
label_map: chartPropsConfig.queriesData[1].label_map,
colnames: ['Boy', 'Girl', 'ds'],
coltypes: [
GenericDataType.String,
GenericDataType.String,
GenericDataType.Temporal,
],
},
],
};
const { echartOptions } = transformProps(
new ChartProps(updatedChartPropsConfig) as EchartsMixedTimeseriesProps,
);
const xAxis = echartOptions.xAxis as {
axisLabel: { formatter: TimeFormatter };
};
return xAxis.axisLabel.formatter;
};

it('should work for days', () => {
const formatter = getXAxisFormatter(TimeGranularity.DAY);
expect(formatter(1610000000000)).toEqual(
expect.stringMatching('2021-01-07'),
);
});

it('should work for weeks', () => {
const formatter = getXAxisFormatter(TimeGranularity.WEEK);
expect(formatter).toBeDefined();
expect(formatter(1610000000000)).toEqual(
expect.stringMatching('2021-01-07 — 2021-01-13'),
);
});

it('should work for months', () => {
const formatter = getXAxisFormatter(TimeGranularity.MONTH);
expect(formatter).toBeDefined();
expect(formatter(1610000000000)).toEqual(expect.stringContaining('Jan'));
expect(formatter(1610000000000)).toEqual(expect.stringContaining('2021'));
});

it('should work for quarters', () => {
const formatter = getXAxisFormatter(TimeGranularity.QUARTER);
expect(formatter).toBeDefined();
expect(formatter(1610000000000)).toEqual(expect.stringContaining('Q1'));
expect(formatter(1610000000000)).toEqual(expect.stringContaining('2021'));
});

it('should work for years', () => {
const formatter = getXAxisFormatter(TimeGranularity.QUARTER);
expect(formatter).toBeDefined();
expect(formatter(1610000000000)).toEqual(expect.stringMatching('2021'));
});
});

describe('should add the correct time formatter for the tooltip', () => {
const getTooltipFormatter = (timeGrainSqla: TimeGranularity) => {
const updatedChartPropsConfig = {
...chartPropsConfig,
formData: {
...chartPropsConfig.formData,
time_grain_sqla: timeGrainSqla,
tooltipTimeFormat: '%Y-%m-%d',
},
queriesData: [
{
data: chartPropsConfig.queriesData[0].data,
label_map: chartPropsConfig.queriesData[0].label_map,
colnames: ['Boy', 'Girl', 'ds'],
coltypes: [
GenericDataType.String,
GenericDataType.String,
GenericDataType.Temporal,
],
},
{
data: chartPropsConfig.queriesData[1].data,
label_map: chartPropsConfig.queriesData[1].label_map,
colnames: ['Boy', 'Girl', 'ds'],
coltypes: [
GenericDataType.String,
GenericDataType.String,
GenericDataType.Temporal,
],
},
],
};

return transformProps(
new ChartProps(updatedChartPropsConfig) as EchartsMixedTimeseriesProps,
).xValueFormatter;
};

it('should work for days', () => {
const formatter = getTooltipFormatter(TimeGranularity.DAY);
expect(formatter(1610000000000)).toEqual(
expect.stringMatching('2021-01-07'),
);
});

it('should work for weeks', () => {
const formatter = getTooltipFormatter(TimeGranularity.WEEK);
expect(formatter).toBeDefined();
expect(formatter(1610000000000)).toEqual(
expect.stringMatching('2021-01-07 — 2021-01-13'),
);
});

it('should work for months', () => {
const formatter = getTooltipFormatter(TimeGranularity.MONTH);
expect(formatter).toBeDefined();
expect(formatter(1610000000000)).toEqual(expect.stringContaining('Jan'));
expect(formatter(1610000000000)).toEqual(expect.stringContaining('2021'));
});

it('should work for quarters', () => {
const formatter = getTooltipFormatter(TimeGranularity.QUARTER);
expect(formatter).toBeDefined();
expect(formatter(1610000000000)).toEqual(expect.stringContaining('Q1'));
expect(formatter(1610000000000)).toEqual(expect.stringContaining('2021'));
});

it('should work for years', () => {
const formatter = getTooltipFormatter(TimeGranularity.QUARTER);
expect(formatter).toBeDefined();
expect(formatter(1610000000000)).toEqual(expect.stringMatching('2021'));
});
});
Loading
Loading