From ef9a80deee162078dd4762df2a82a676176fa683 Mon Sep 17 00:00:00 2001 From: tozhou Date: Tue, 24 Jun 2025 22:21:29 +0100 Subject: [PATCH 1/2] NCL-9065 Fix missing legend label for charts --- src/components/Charts/DoughnutChart.tsx | 3 ++- src/components/Charts/StackedBarChart.tsx | 3 ++- src/libs/chartJsPlugins.ts | 7 +++++-- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/components/Charts/DoughnutChart.tsx b/src/components/Charts/DoughnutChart.tsx index 292ba64c..947f1016 100644 --- a/src/components/Charts/DoughnutChart.tsx +++ b/src/components/Charts/DoughnutChart.tsx @@ -48,8 +48,9 @@ export const DoughnutChart = ({ data, labels, id, description, legendHeight = 10 plugins: { legend: { position: 'bottom', + maxHeight: legendHeight * 4, labels: { - padding: 25, + padding: 15, color: 'black', font: { size: 15, diff --git a/src/components/Charts/StackedBarChart.tsx b/src/components/Charts/StackedBarChart.tsx index 7333fa43..346fcf71 100644 --- a/src/components/Charts/StackedBarChart.tsx +++ b/src/components/Charts/StackedBarChart.tsx @@ -70,8 +70,9 @@ export const StackedBarChart = ({ data, labels, id, description, legendHeight = plugins: { legend: { position: 'bottom', + maxHeight: legendHeight * 4, labels: { - padding: 25, + padding: 15, color: 'black', font: { size: 15, diff --git a/src/libs/chartJsPlugins.ts b/src/libs/chartJsPlugins.ts index ab5cb3f6..b866e139 100644 --- a/src/libs/chartJsPlugins.ts +++ b/src/libs/chartJsPlugins.ts @@ -126,7 +126,10 @@ export const dougnutCenterPlugin = { export const legendHeightPlugin = { id: 'legendHeightPlugin', beforeInit: (chart: any) => { - if (chart.config?.options?.elements?.legend) { + const pluginLegend = chart.config.options.plugins?.legend; + const elemLegend = chart.config.options.elements?.legend; + const forcedHeight = pluginLegend?.maxHeight ?? elemLegend?.height; + if (forcedHeight) { // Get reference to the original fit function const originalFit = chart.legend.fit; @@ -135,7 +138,7 @@ export const legendHeightPlugin = { // Call original function and bind scope in order to use `this` correctly inside it originalFit.bind(chart.legend)(); // Change the height as suggested in another answers - this.height = chart.config.options.elements.legend.height; + this.height = Math.min(this.height, forcedHeight); }; } }, From 3379faf210935e59dd0674cd591f094f690bf753 Mon Sep 17 00:00:00 2001 From: tozhou Date: Tue, 24 Jun 2025 22:30:18 +0100 Subject: [PATCH 2/2] NCL-9065 Introduce color map for element colors --- src/common/colorMap.ts | 56 +++++++++++++++++++ src/components/Charts/DoughnutChart.tsx | 13 ++++- src/components/Charts/StackedBarChart.tsx | 8 ++- .../ArtifactQualityLabelMapper.tsx | 44 ++------------- .../ArtifactRepositoryTypeLabelMapper.tsx | 34 +++-------- .../BuildConfigBuildTypeLabelMapper.tsx | 28 ++-------- .../DeliverableAnalysisLabelLabelMapper.tsx | 26 +++------ src/components/LabelMapper/LabelMapper.tsx | 15 +++-- .../OperationProgressStatusLabelMapper.tsx | 29 ++-------- .../OperationResultLabelMapper.tsx | 37 ++---------- .../ProductMilestoneDetailPage.tsx | 3 + .../ProductVersionDetailPage.tsx | 3 + 12 files changed, 125 insertions(+), 171 deletions(-) create mode 100644 src/common/colorMap.ts diff --git a/src/common/colorMap.ts b/src/common/colorMap.ts new file mode 100644 index 00000000..9619de1d --- /dev/null +++ b/src/common/colorMap.ts @@ -0,0 +1,56 @@ +import { LabelProps } from '@patternfly/react-core'; + +/* See PF5 color palettes: https://v5-archive.patternfly.org/design-foundations/colors#patternfly-palettes */ +export interface LabelConfig { + text: string; + color: LabelProps['color']; // PatternFly label semantic color + hexColor: string; // Exact hex code for charts/UI +} + +export const buildTypeColorMap: Record = { + MVN: { text: 'MVN', color: 'gold', hexColor: '#F9E0A2' }, // gold-100 + NPM: { text: 'NPM', color: 'purple', hexColor: '#CBC1FF' }, // purple-100 + GRADLE: { text: 'GRADLE', color: 'cyan', hexColor: '#A2D9D9' }, // cyan-100 + SBT: { text: 'SBT', color: 'grey', hexColor: '#D2D2D2' }, // black-300 +}; + +export const artifactQualityColorMap: Record = { + NEW: { text: 'NEW', color: 'grey', hexColor: '#B8BBBE' }, // black-400 + VERIFIED: { text: 'VERIFIED', color: 'blue', hexColor: '#BEE1F4' }, // blue-100 + TESTED: { text: 'TESTED', color: 'green', hexColor: '#BDE5B8' }, // green-100 + DEPRECATED: { text: 'DEPRECATED', color: 'orange', hexColor: '#F4B678' }, // orange-100 + BLACKLISTED: { text: 'BLACKLISTED', color: 'red', hexColor: '#C9190B' }, // red-100 + DELETED: { text: 'DELETED', color: 'red', hexColor: '#7D1007' }, // red-300 + TEMPORARY: { text: 'TEMPORARY', color: 'cyan', hexColor: '#A2D9D9' }, // cyan-100 + IMPORTED: { text: 'IMPORTED', color: 'grey', hexColor: '#F0F0F0' }, // black-200 +}; + +export const repositoryTypeColorMap: Record = { + MAVEN: { text: 'MAVEN', color: 'gold', hexColor: '#F9E0A2' }, // gold-100 + GENERIC_PROXY: { text: 'GENERIC_PROXY', color: 'grey', hexColor: '#D2D2D2' }, // black-300 + NPM: { text: 'NPM', color: 'purple', hexColor: '#CBC1FF' }, // purple-100 + COCOA_POD: { text: 'COCOA_POD', color: 'cyan', hexColor: '#A2D9D9' }, // cyan-100 + DISTRIBUTION_ARCHIVE: { text: 'DISTRIBUTION_ARCHIVE', color: 'red', hexColor: '#C9190B' }, // red-100 +}; + +export const operationProgressStatusColorMap: Record = { + NEW: { text: 'NEW', color: 'grey', hexColor: '#F0F0F0' }, // black-200 + PENDING: { text: 'PENDING', color: 'grey', hexColor: '#B8BBBE' }, // black-400 + IN_PROGRESS: { text: 'IN PROGRESS', color: 'blue', hexColor: '#BEE1F4' }, // blue-100 + FINISHED: { text: 'FINISHED', color: 'green', hexColor: '#BDE5B8' }, // green-100 +}; + +export const deliverableAnalysisColorMap: Record = { + DELETED: { text: 'DELETED', color: 'red', hexColor: '#C9190B' }, // red-100 + SCRATCH: { text: 'SCRATCH', color: 'grey', hexColor: '#D2D2D2' }, // black-300 + RELEASED: { text: 'RELEASED', color: 'blue', hexColor: '#BEE1F4' }, // blue-100 +}; + +export const operationResultColorMap: Record = { + SUCCESSFUL: { text: 'SUCCESSFUL', color: 'green', hexColor: '#BDE5B8' }, // green-100 + FAILED: { text: 'FAILED', color: 'orange', hexColor: '#F4B678' }, // orange-100 + REJECTED: { text: 'REJECTED', color: 'orange', hexColor: '#EF9234' }, // orange-300 + CANCELLED: { text: 'CANCELLED', color: 'grey', hexColor: '#B8BBBE' }, // black-400 + TIMEOUT: { text: 'TIMEOUT', color: 'grey', hexColor: '#D2D2D2' }, // black-300 + SYSTEM_ERROR: { text: 'SYSTEM_ERROR', color: 'red', hexColor: '#C9190B' }, // red-100 +}; diff --git a/src/components/Charts/DoughnutChart.tsx b/src/components/Charts/DoughnutChart.tsx index 947f1016..d16364d0 100644 --- a/src/components/Charts/DoughnutChart.tsx +++ b/src/components/Charts/DoughnutChart.tsx @@ -9,6 +9,7 @@ import { dougnutCenterPlugin, legendHeightPlugin } from 'libs/chartJsPlugins'; export interface IDoughnutChartProps { data: number[]; labels: string[]; + colors?: string[]; id?: string; description?: IDescription; legendHeight?: number; @@ -19,11 +20,12 @@ export interface IDoughnutChartProps { * * @param data - Chart data * @param labels - Chart data labels + * @param colors - Background colors for the chart data * @param id - ID of canvas * @param description - Description to be displayed in help icon * @param legendHeight - Legend height */ -export const DoughnutChart = ({ data, labels, id, description, legendHeight = 100 }: IDoughnutChartProps) => { +export const DoughnutChart = ({ data, labels, colors, id, description, legendHeight = 100 }: IDoughnutChartProps) => { const chart = useRef(); const chartRef = useRef(null); @@ -31,7 +33,12 @@ export const DoughnutChart = ({ data, labels, id, description, legendHeight = 10 const chartConfig: ChartConfiguration = { type: 'doughnut', data: { - datasets: [{ data }], + datasets: [ + { + data, + backgroundColor: colors?.map((color) => color ?? '#CCCCCC'), + }, + ], labels, }, options: { @@ -75,7 +82,7 @@ export const DoughnutChart = ({ data, labels, id, description, legendHeight = 10 chart.current.config.options = chartConfig.options; chart.current.update(); } - }, [data, labels, legendHeight]); + }, [data, labels, colors, legendHeight]); return ( diff --git a/src/components/Charts/StackedBarChart.tsx b/src/components/Charts/StackedBarChart.tsx index 346fcf71..74dd69ed 100644 --- a/src/components/Charts/StackedBarChart.tsx +++ b/src/components/Charts/StackedBarChart.tsx @@ -22,6 +22,7 @@ interface IStackedBarChartDataset { export interface IStackedBarChartProps { data: IStackedBarChartDataset[]; labels: string[]; + colors?: string[]; id?: string; description?: IDescription; legendHeight?: number; @@ -32,11 +33,12 @@ export interface IStackedBarChartProps { * * @param data - Chart data * @param labels - Labels of stacked rows + * @param colors - Background colors for the chart data * @param id - ID of canvas * @param description - Description to be displayed in help icon * @param legendHeight - Legend height */ -export const StackedBarChart = ({ data, labels, id, description, legendHeight = 100 }: IStackedBarChartProps) => { +export const StackedBarChart = ({ data, labels, colors, id, description, legendHeight = 100 }: IStackedBarChartProps) => { const chart = useRef(); const chartRef = useRef(null); @@ -45,7 +47,7 @@ export const StackedBarChart = ({ data, labels, id, description, legendHeight = type: 'bar', data: { datasets: data.map((dataset: IStackedBarChartDataset, index: number) => { - return { ...dataset, backgroundColor: COLORS[index % COLORS.length] }; + return { ...dataset, backgroundColor: colors ? colors[index] : COLORS[index % COLORS.length] }; }), labels: labels, }, @@ -97,7 +99,7 @@ export const StackedBarChart = ({ data, labels, id, description, legendHeight = chart.current.config.options = chartConfig.options; chart.current.update(); } - }, [data, labels, legendHeight]); + }, [data, labels, colors, legendHeight]); return ( diff --git a/src/components/LabelMapper/ArtifactQualityLabelMapper.tsx b/src/components/LabelMapper/ArtifactQualityLabelMapper.tsx index 56e43c61..235fdd7e 100644 --- a/src/components/LabelMapper/ArtifactQualityLabelMapper.tsx +++ b/src/components/LabelMapper/ArtifactQualityLabelMapper.tsx @@ -1,46 +1,14 @@ import { Artifact } from 'pnc-api-types-ts'; -import { ILabelMapper, LabelMapper } from 'components/LabelMapper/LabelMapper'; +import { artifactQualityColorMap } from 'common/colorMap'; -const ARTIFACT_QUALITIES: ILabelMapper = { - NEW: { - text: 'NEW', - color: 'grey', - }, - VERIFIED: { - text: 'VERIFIED', - color: 'blue', - }, - TESTED: { - text: 'TESTED', - color: 'green', - }, - DEPRECATED: { - text: 'DEPRECATED', - color: 'orange', - }, - BLACKLISTED: { - text: 'BLACKLISTED', - color: 'red', - }, - DELETED: { - text: 'DELETED', - color: 'red', - }, - TEMPORARY: { - text: 'TEMPORARY', - color: 'cyan', - }, - IMPORTED: { - text: 'IMPORTED', - color: 'grey', - }, -}; +import { LabelMapper } from 'components/LabelMapper/LabelMapper'; interface IArtifactQualityLabelMapperProps { quality: Artifact['artifactQuality']; } -export const ArtifactQualityLabelMapper = ({ quality }: IArtifactQualityLabelMapperProps) => ( - -); +export const ArtifactQualityLabelMapper = ({ quality }: IArtifactQualityLabelMapperProps) => { + const config = artifactQualityColorMap[quality] ?? { text: quality }; + return ; +}; diff --git a/src/components/LabelMapper/ArtifactRepositoryTypeLabelMapper.tsx b/src/components/LabelMapper/ArtifactRepositoryTypeLabelMapper.tsx index 62a4be97..5dfdcfb8 100644 --- a/src/components/LabelMapper/ArtifactRepositoryTypeLabelMapper.tsx +++ b/src/components/LabelMapper/ArtifactRepositoryTypeLabelMapper.tsx @@ -1,34 +1,16 @@ import { TargetRepository } from 'pnc-api-types-ts'; -import { ILabelMapper, LabelMapper } from 'components/LabelMapper/LabelMapper'; +import { repositoryTypeColorMap } from 'common/colorMap'; -const ARTIFACT_REPOSITORY_TYPES: ILabelMapper = { - MAVEN: { - text: 'MAVEN', - color: 'gold', - }, - GENERIC_PROXY: { - text: 'GENERIC_PROXY', - color: 'grey', - }, - NPM: { - text: 'NPM', - color: 'purple', - }, - COCOA_POD: { - text: 'COCOA_POD', - color: 'cyan', - }, - DISTRIBUTION_ARCHIVE: { - text: 'DISTRIBUTION_ARCHIVE', - color: 'red', - }, -}; +import { LabelMapper } from 'components/LabelMapper/LabelMapper'; interface IArtifactRepositoryTypeLabelMapperProps { repositoryType: TargetRepository['repositoryType']; } -export const ArtifactRepositoryTypeLabelMapper = ({ repositoryType }: IArtifactRepositoryTypeLabelMapperProps) => ( - -); +export const ArtifactRepositoryTypeLabelMapper = ({ repositoryType }: IArtifactRepositoryTypeLabelMapperProps) => { + const config = repositoryTypeColorMap[repositoryType] ?? { + text: repositoryType, + }; + return ; +}; diff --git a/src/components/LabelMapper/BuildConfigBuildTypeLabelMapper.tsx b/src/components/LabelMapper/BuildConfigBuildTypeLabelMapper.tsx index 0c23c31c..c282efb5 100644 --- a/src/components/LabelMapper/BuildConfigBuildTypeLabelMapper.tsx +++ b/src/components/LabelMapper/BuildConfigBuildTypeLabelMapper.tsx @@ -1,30 +1,14 @@ import { BuildConfiguration } from 'pnc-api-types-ts'; -import { ILabelMapper, LabelMapper } from 'components/LabelMapper/LabelMapper'; +import { buildTypeColorMap } from 'common/colorMap'; -const BUILD_CONFIG_BUILD_TYPES: ILabelMapper = { - MVN: { - text: 'MVN', - color: 'gold', - }, - NPM: { - text: 'NPM', - color: 'purple', - }, - GRADLE: { - text: 'GRADLE', - color: 'cyan', - }, - SBT: { - text: 'SBT', - color: 'grey', - }, -}; +import { LabelMapper } from 'components/LabelMapper/LabelMapper'; interface IBuildConfigBuildTypeLabelMapperProps { buildType: BuildConfiguration['buildType']; } -export const BuildConfigBuildTypeLabelMapper = ({ buildType }: IBuildConfigBuildTypeLabelMapperProps) => ( - -); +export const BuildConfigBuildTypeLabelMapper = ({ buildType }: IBuildConfigBuildTypeLabelMapperProps) => { + const config = buildTypeColorMap[buildType] ?? { text: buildType }; + return ; +}; diff --git a/src/components/LabelMapper/DeliverableAnalysisLabelLabelMapper.tsx b/src/components/LabelMapper/DeliverableAnalysisLabelLabelMapper.tsx index fb8fa566..25e4c457 100644 --- a/src/components/LabelMapper/DeliverableAnalysisLabelLabelMapper.tsx +++ b/src/components/LabelMapper/DeliverableAnalysisLabelLabelMapper.tsx @@ -1,29 +1,17 @@ import { DeliverableAnalyzerReport } from 'pnc-api-types-ts'; -import { ILabelMapper, LabelMapper } from 'components/LabelMapper/LabelMapper'; +import { deliverableAnalysisColorMap } from 'common/colorMap'; -type deliverableAnalysisLabel = Exclude[number]; +import { LabelMapper } from 'components/LabelMapper/LabelMapper'; -const DELIVERABLE_ANALYSIS_LABELS: ILabelMapper = { - DELETED: { - text: 'DELETED', - color: 'red', - }, - SCRATCH: { - text: 'SCRATCH', - color: 'grey', - }, - RELEASED: { - text: 'RELEASED', - color: 'blue', - }, -}; +type deliverableAnalysisLabel = Exclude[number]; interface IDeliverableAnalysisLabelLabelMapperProps { label: deliverableAnalysisLabel; onRemove?: () => void; } -export const DeliverableAnalysisLabelLabelMapper = ({ label, onRemove }: IDeliverableAnalysisLabelLabelMapperProps) => ( - -); +export const DeliverableAnalysisLabelLabelMapper = ({ label, onRemove }: IDeliverableAnalysisLabelLabelMapperProps) => { + const config = deliverableAnalysisColorMap[label] ?? { text: label }; + return ; +}; diff --git a/src/components/LabelMapper/LabelMapper.tsx b/src/components/LabelMapper/LabelMapper.tsx index a22088ee..23c54309 100644 --- a/src/components/LabelMapper/LabelMapper.tsx +++ b/src/components/LabelMapper/LabelMapper.tsx @@ -6,7 +6,7 @@ import { uiLogger } from 'services/uiLogger'; interface ILabelMapperItem { text: string; - color: LabelPropsPF['color']; + color?: LabelPropsPF['color']; } export type ILabelMapper = { @@ -27,12 +27,15 @@ interface ILabelMapperProps { export const LabelMapper = ({ mapperItem, onRemove }: ILabelMapperProps) => { if (!mapperItem) { uiLogger.error(`Error attempting to get mapper item: mapper item undefined`); + return ; } - return mapperItem ? ( - - {mapperItem.text} + + const { text, color } = mapperItem; + const safeColor = color ?? 'grey'; + + return ( + + {text} - ) : ( - ); }; diff --git a/src/components/LabelMapper/OperationProgressStatusLabelMapper.tsx b/src/components/LabelMapper/OperationProgressStatusLabelMapper.tsx index 6e2232e2..32a076bd 100644 --- a/src/components/LabelMapper/OperationProgressStatusLabelMapper.tsx +++ b/src/components/LabelMapper/OperationProgressStatusLabelMapper.tsx @@ -1,30 +1,13 @@ +import { operationProgressStatusColorMap } from 'common/colorMap'; import { Operation } from 'common/operationEntityAttributes'; -import { ILabelMapper, LabelMapper } from 'components/LabelMapper/LabelMapper'; - -const OPERATION_PROGRESS_STATUSES: ILabelMapper = { - NEW: { - text: 'NEW', - color: 'grey', - }, - PENDING: { - text: 'PENDING', - color: 'grey', - }, - IN_PROGRESS: { - text: 'IN PROGRESS', - color: 'blue', - }, - FINISHED: { - text: 'FINISHED', - color: 'green', - }, -}; +import { LabelMapper } from 'components/LabelMapper/LabelMapper'; interface IOperationProgressStatusLabelMapperProps { progressStatus: Exclude; } -export const OperationProgressStatusLabelMapper = ({ progressStatus }: IOperationProgressStatusLabelMapperProps) => ( - -); +export const OperationProgressStatusLabelMapper = ({ progressStatus }: IOperationProgressStatusLabelMapperProps) => { + const config = operationProgressStatusColorMap[progressStatus]!; + return ; +}; diff --git a/src/components/LabelMapper/OperationResultLabelMapper.tsx b/src/components/LabelMapper/OperationResultLabelMapper.tsx index 5e4acac7..0a9fd834 100644 --- a/src/components/LabelMapper/OperationResultLabelMapper.tsx +++ b/src/components/LabelMapper/OperationResultLabelMapper.tsx @@ -1,38 +1,13 @@ +import { operationResultColorMap } from 'common/colorMap'; import { Operation } from 'common/operationEntityAttributes'; -import { ILabelMapper, LabelMapper } from 'components/LabelMapper/LabelMapper'; - -const OPERATION_RESULTS: ILabelMapper = { - SUCCESSFUL: { - text: 'SUCCESSFUL', - color: 'green', - }, - FAILED: { - text: 'FAILED', - color: 'orange', - }, - REJECTED: { - text: 'REJECTED', - color: 'orange', - }, - CANCELLED: { - text: 'CANCELLED', - color: 'grey', - }, - TIMEOUT: { - text: 'TIMEOUT', - color: 'grey', - }, - SYSTEM_ERROR: { - text: 'SYSTEM_ERROR', - color: 'red', - }, -}; +import { LabelMapper } from 'components/LabelMapper/LabelMapper'; interface IOperationResultLabelMapperProps { result: Exclude; } -export const OperationResultLabelMapper = ({ result }: IOperationResultLabelMapperProps) => ( - -); +export const OperationResultLabelMapper = ({ result }: IOperationResultLabelMapperProps) => { + const config = operationResultColorMap[result] ?? { text: result }; + return ; +}; diff --git a/src/components/ProductMilestoneDetailPage/ProductMilestoneDetailPage.tsx b/src/components/ProductMilestoneDetailPage/ProductMilestoneDetailPage.tsx index 54f79bab..e474d407 100644 --- a/src/components/ProductMilestoneDetailPage/ProductMilestoneDetailPage.tsx +++ b/src/components/ProductMilestoneDetailPage/ProductMilestoneDetailPage.tsx @@ -1,6 +1,7 @@ import { Grid, GridItem, Text, TextContent, TextVariants } from '@patternfly/react-core'; import { useEffect, useMemo } from 'react'; +import { artifactQualityColorMap, repositoryTypeColorMap } from 'common/colorMap'; import { productMilestoneEntityAttributes } from 'common/productMilestoneEntityAttributes'; import { useParamsRequired } from 'hooks/useParamsRequired'; @@ -186,6 +187,7 @@ export const ProductMilestoneDetailPage = () => { artifactQualityColorMap[label]?.hexColor)} description={
Chart displays proportion of quality of Delivered Artifacts.
} /> @@ -205,6 +207,7 @@ export const ProductMilestoneDetailPage = () => { repositoryTypeColorMap[label]?.hexColor)} description={
Chart displays proportion of repository type of Delivered Artifacts. diff --git a/src/components/ProductVersionDetailPage/ProductVersionDetailPage.tsx b/src/components/ProductVersionDetailPage/ProductVersionDetailPage.tsx index e29420bc..678b342e 100644 --- a/src/components/ProductVersionDetailPage/ProductVersionDetailPage.tsx +++ b/src/components/ProductVersionDetailPage/ProductVersionDetailPage.tsx @@ -4,6 +4,7 @@ import { Link } from 'react-router'; import { ProductMilestoneRef, ProductReleaseRef } from 'pnc-api-types-ts'; +import { artifactQualityColorMap, repositoryTypeColorMap } from 'common/colorMap'; import { productVersionEntityAttributes } from 'common/productVersionEntityAttributes'; import { useParamsRequired } from 'hooks/useParamsRequired'; @@ -307,6 +308,7 @@ export const ProductVersionDetailPage = () => { artifactQualityColorMap[ds.label]?.hexColor)} description={
Chart displays proportion of quality of Delivered Artifacts among Product Milestones of this Version.
} @@ -333,6 +335,7 @@ export const ProductVersionDetailPage = () => { repositoryTypeColorMap[ds.label]?.hexColor)} description={
Chart displays proportion of repository type of Delivered Artifacts among Product Milestones of this Version.