diff --git a/packages/ui/src/design-system/pie-chart/pie-chart.component.tsx b/packages/ui/src/design-system/pie-chart/pie-chart.component.tsx index f2559dc58d..0692b46497 100644 --- a/packages/ui/src/design-system/pie-chart/pie-chart.component.tsx +++ b/packages/ui/src/design-system/pie-chart/pie-chart.component.tsx @@ -9,6 +9,8 @@ import { Tooltip, } from 'recharts'; +import { createLogger } from '../../logger'; + import { PIE_CHART_DEFAULT_COLOR_SET, PieChartGradientColor, @@ -45,6 +47,8 @@ export type PieChartProps = ? PieChartDefaultKeyProps : PieChartCustomKeyProps; +const logger = createLogger('PieChart'); + const formatPieColor = (color: PieChartColor): string => Boolean(PieChartGradientColor[color as PieChartGradientColor]) ? `url(#${color})` @@ -74,6 +78,19 @@ export const PieChart = ({ }: PieChartProps): JSX.Element => { const data = inputData.slice(0, colors.length); + if (data.length < inputData.length) { + logger.error({ + message: [ + '`colors` array length is lower than the `data` array length.', + 'The `data` items without corresponding `colors` are not rendered.', + ], + data: { + colors, + dataCount: inputData.length, + }, + }); + } + return ( diff --git a/packages/ui/src/logger.ts b/packages/ui/src/logger.ts new file mode 100644 index 0000000000..c7fb7968f8 --- /dev/null +++ b/packages/ui/src/logger.ts @@ -0,0 +1,64 @@ +type LoggableComponent = 'PieChart'; +type LogLevel = 'error' | 'warn'; + +interface LogConfig { + componentName: string; + data?: unknown; + message: string[] | string; +} +type LogFunction = (config: Readonly>) => void; +type Logger = Record; + +const namespace = 'UI-Toolkit'; +const namespaceSeparator = '::'; +const componentSeparator = ' '; + +// https://github.com/microsoft/TypeScript/issues/17002 +// eslint-disable-next-line functional/prefer-immutable-types +const formatMessage = (message: string[] | string): string => + Array.isArray(message) ? `\n${message.join('\n')}` : message; + +const buildMessageArguments = ({ + componentName, + message, + data, +}: Readonly): unknown[] => + [ + `${namespace}${namespaceSeparator}${componentName}${componentSeparator}${formatMessage( + message, + )}`, + data, + ].filter(Boolean); + +const getLogFunctionByLevel = ( + logLevel: LogLevel, + componentName: LoggableComponent, +): LogFunction => { + const logFunction = console[logLevel]; + return config => { + logFunction(...buildMessageArguments({ ...config, componentName })); + }; +}; + +// eslint-disable-next-line @typescript-eslint/no-empty-function +const noop = (): void => {}; + +/** + * Creates scoped logger based on component name. + * + * Please ensure that log messages are clear and concise. + * Include corresponding data either in the `data` property or as `${interpolation}` in the message. + * If message is a `string[]` it will be rendered multiline. + */ +const getLogger = (componentName: LoggableComponent): Logger => ({ + warn: getLogFunctionByLevel('warn', componentName), + error: getLogFunctionByLevel('error', componentName), +}); + +const getMockLogger: typeof getLogger = () => ({ + warn: noop, + error: noop, +}); + +export const createLogger = + process.env.NODE_ENV === 'development' ? getLogger : getMockLogger;