diff --git a/plugin/pom.xml b/plugin/pom.xml index c531f3da0..349e9e726 100644 --- a/plugin/pom.xml +++ b/plugin/pom.xml @@ -23,9 +23,6 @@ -SNAPSHOT jenkinsci/code-coverage-api-plugin - 2.346 - 2.346.3 - 3.0.3 11.4 @@ -37,6 +34,7 @@ 1.11 1.17.4 1.81 + 5.4.0-1 @@ -63,7 +61,7 @@ io.jenkins.plugins ionicons-api - 28.vccb_448cc14b_d + 28.va_f3a_84439e5f net.sf.trove4j @@ -112,6 +110,7 @@ io.jenkins.plugins echarts-api + ${echarts-api.version} io.jenkins.plugins diff --git a/plugin/src/main/java/io/jenkins/plugins/coverage/model/ChangeCoverageTableModel.java b/plugin/src/main/java/io/jenkins/plugins/coverage/model/ChangeCoverageTableModel.java index 975408ae4..27e00b267 100644 --- a/plugin/src/main/java/io/jenkins/plugins/coverage/model/ChangeCoverageTableModel.java +++ b/plugin/src/main/java/io/jenkins/plugins/coverage/model/ChangeCoverageTableModel.java @@ -9,6 +9,7 @@ import hudson.Functions; +import io.jenkins.plugins.coverage.model.visualization.colorization.ColorProvider; import io.jenkins.plugins.datatables.DetailedCell; import io.jenkins.plugins.datatables.TableConfiguration; import io.jenkins.plugins.datatables.TableConfiguration.SelectStyle; @@ -32,10 +33,12 @@ class ChangeCoverageTableModel extends CoverageTableModel { * The root of the change coverage tree * @param renderer * the renderer to use for the file names + * @param colorProvider + * The {@link ColorProvider} which provides the used colors */ ChangeCoverageTableModel(final String id, final CoverageNode root, final CoverageNode changeRoot, - final RowRenderer renderer) { - super(id, root, renderer); + final RowRenderer renderer, final ColorProvider colorProvider) { + super(id, root, renderer, colorProvider); this.changeRoot = changeRoot; } @@ -50,7 +53,7 @@ public List getRows() { Locale browserLocale = Functions.getCurrentLocale(); return changeRoot.getAllFileCoverageNodes().stream() .map(file -> new ChangeCoverageRow( - getOriginalNode(file), file, browserLocale, getRenderer())) + getOriginalNode(file), file, browserLocale, getRenderer(), getColorProvider())) .collect(Collectors.toList()); } @@ -71,8 +74,8 @@ private static class ChangeCoverageRow extends CoverageRow { private final FileCoverageNode changedFileNode; ChangeCoverageRow(final FileCoverageNode root, final FileCoverageNode changedFileNode, - final Locale browserLocale, final RowRenderer renderer) { - super(root, browserLocale, renderer); + final Locale browserLocale, final RowRenderer renderer, final ColorProvider colorProvider) { + super(root, browserLocale, renderer, colorProvider); this.changedFileNode = changedFileNode; } diff --git a/plugin/src/main/java/io/jenkins/plugins/coverage/model/CoverageTableModel.java b/plugin/src/main/java/io/jenkins/plugins/coverage/model/CoverageTableModel.java index dd4234482..0da98b330 100644 --- a/plugin/src/main/java/io/jenkins/plugins/coverage/model/CoverageTableModel.java +++ b/plugin/src/main/java/io/jenkins/plugins/coverage/model/CoverageTableModel.java @@ -10,7 +10,6 @@ import io.jenkins.plugins.coverage.model.visualization.colorization.ColorProvider; import io.jenkins.plugins.coverage.model.visualization.colorization.ColorProvider.DisplayColors; -import io.jenkins.plugins.coverage.model.visualization.colorization.ColorProviderFactory; import io.jenkins.plugins.coverage.model.visualization.colorization.CoverageChangeTendency; import io.jenkins.plugins.coverage.model.visualization.colorization.CoverageLevel; import io.jenkins.plugins.datatables.DetailedCell; @@ -28,10 +27,17 @@ * UI table model for the coverage details table. */ class CoverageTableModel extends TableModel { - private static final ColorProvider COLOR_PROVIDER = ColorProviderFactory.createColorProvider(); private static final int NO_COVERAGE_SORT = -1_000; + + /** + * The alpha value for colors to be used to highlight the coverage within the table view. + */ + private static final int TABLE_COVERAGE_COLOR_ALPHA = 80; + static final DetailedCell NO_COVERAGE = new DetailedCell<>(Messages.Coverage_Not_Available(), NO_COVERAGE_SORT); + + private final ColorProvider colorProvider; private final CoverageNode root; private final RowRenderer renderer; private final String id; @@ -45,13 +51,16 @@ class CoverageTableModel extends TableModel { * The root of the coverage tree * @param renderer * the renderer to use for the file names + * @param colors + * The {@link ColorProvider} which provides the used colors */ - CoverageTableModel(final String id, final CoverageNode root, final RowRenderer renderer) { + CoverageTableModel(final String id, final CoverageNode root, final RowRenderer renderer, final ColorProvider colors) { super(); this.id = id; this.root = root; this.renderer = renderer; + colorProvider = colors; } RowRenderer getRenderer() { @@ -132,7 +141,7 @@ public List getColumns() { public List getRows() { Locale browserLocale = Functions.getCurrentLocale(); return root.getAll(CoverageMetric.FILE).stream() - .map(file -> new CoverageRow(file, browserLocale, renderer)) + .map(file -> new CoverageRow(file, browserLocale, renderer, colorProvider)) .collect(Collectors.toList()); } @@ -140,6 +149,10 @@ protected CoverageNode getRoot() { return root; } + protected ColorProvider getColorProvider() { + return colorProvider; + } + /** * UI row model for the coverage details table. */ @@ -149,11 +162,13 @@ static class CoverageRow { private final CoverageNode root; private final Locale browserLocale; private final RowRenderer renderer; + private final ColorProvider colorProvider; - CoverageRow(final CoverageNode root, final Locale browserLocale, final RowRenderer renderer) { + CoverageRow(final CoverageNode root, final Locale browserLocale, final RowRenderer renderer, final ColorProvider colors) { this.root = root; this.browserLocale = browserLocale; this.renderer = renderer; + this.colorProvider = colors; } public String getFileHash() { @@ -206,12 +221,12 @@ public int getLoc() { protected DetailedCell createColoredCoverageColumn(final Coverage coverage, final String tooltip) { if (coverage.isSet()) { double percentage = coverage.getCoveredPercentage().getDoubleValue(); - DisplayColors colors = CoverageLevel.getDisplayColorsOfCoverageLevel(percentage, COLOR_PROVIDER); + DisplayColors colors = CoverageLevel.getDisplayColorsOfCoverageLevel(percentage, colorProvider); String cell = div().withClasses(COVERAGE_COLUMN_OUTER).with( div().withClasses(COVERAGE_COLUMN_INNER) .withStyle(String.format( "background-image: linear-gradient(90deg, %s %f%%, transparent %f%%);", - colors.getFillColorAsHex(), + colors.getFillColorAsRGBAHex(TABLE_COVERAGE_COLOR_ALPHA), percentage, percentage)) .withTitle(tooltip) .withText(coverage.formatCoveredPercentage(browserLocale))) @@ -234,10 +249,11 @@ protected DetailedCell createColoredCoverageColumn(final Coverage coverage, f protected DetailedCell createColoredCoverageDeltaColumn( final CoveragePercentage coveragePercentage, final String tooltip) { double coverageValue = coveragePercentage.getDoubleValue(); - DisplayColors colors = CoverageChangeTendency.getDisplayColorsForTendency(coverageValue, COLOR_PROVIDER); + DisplayColors colors = CoverageChangeTendency.getDisplayColorsForTendency(coverageValue, colorProvider); String cell = div().withClasses(COVERAGE_COLUMN_OUTER).with( div().withClasses(COVERAGE_COLUMN_INNER) - .withStyle(String.format("background-color:%s;", colors.getFillColorAsHex())) + .withStyle(String.format("background-color:%s;", colors.getFillColorAsRGBAHex( + TABLE_COVERAGE_COLOR_ALPHA))) .withText(coveragePercentage.formatDeltaPercentage(browserLocale)) .withTitle(tooltip)) .render(); diff --git a/plugin/src/main/java/io/jenkins/plugins/coverage/model/CoverageViewModel.java b/plugin/src/main/java/io/jenkins/plugins/coverage/model/CoverageViewModel.java index ae2eafbe0..0a11230be 100644 --- a/plugin/src/main/java/io/jenkins/plugins/coverage/model/CoverageViewModel.java +++ b/plugin/src/main/java/io/jenkins/plugins/coverage/model/CoverageViewModel.java @@ -2,9 +2,12 @@ import java.io.File; import java.io.IOException; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.NoSuchElementException; import java.util.Optional; +import java.util.Set; import java.util.SortedMap; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -13,6 +16,10 @@ import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.commons.lang3.math.Fraction; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; + import edu.hm.hafner.echarts.JacksonFacade; import edu.hm.hafner.echarts.LinesChartModel; import edu.hm.hafner.echarts.TreeMapNode; @@ -30,6 +37,7 @@ import io.jenkins.plugins.coverage.model.visualization.code.SourceCodeFacade; import io.jenkins.plugins.coverage.model.visualization.colorization.ColorProvider; import io.jenkins.plugins.coverage.model.visualization.colorization.ColorProviderFactory; +import io.jenkins.plugins.coverage.model.visualization.colorization.CoverageColorJenkinsId; import io.jenkins.plugins.coverage.model.visualization.tree.TreeMapNodeConverter; import io.jenkins.plugins.datatables.DefaultAsyncTableContentProvider; import io.jenkins.plugins.datatables.TableModel; @@ -45,8 +53,7 @@ @SuppressWarnings({"PMD.GodClass", "PMD.ExcessivePublicCount", "checkstyle:ClassDataAbstractionCoupling", "checkstyle:ClassFanOutComplexity"}) public class CoverageViewModel extends DefaultAsyncTableContentProvider implements ModelObject { private static final JacksonFacade JACKSON_FACADE = new JacksonFacade(); - private static final ColorProvider COLOR_PROVIDER = ColorProviderFactory.createColorProvider(); - private static final TreeMapNodeConverter TREE_MAP_NODE_CONVERTER = new TreeMapNodeConverter(COLOR_PROVIDER); + private static final TreeMapNodeConverter TREE_MAP_NODE_CONVERTER = new TreeMapNodeConverter(); private static final BuildResultNavigator NAVIGATOR = new BuildResultNavigator(); private static final SourceCodeFacade SOURCE_CODE_FACADE = new SourceCodeFacade(); @@ -62,6 +69,8 @@ public class CoverageViewModel extends DefaultAsyncTableContentProvider implemen private final CoverageNode changeCoverageTreeRoot; private final CoverageNode indirectCoverageChangesTreeRoot; + private ColorProvider colorProvider = ColorProviderFactory.createDefaultColorProvider(); + /** * Creates a new view model instance. * @@ -99,6 +108,48 @@ public String getDisplayName() { return Messages.Coverage_Title(node.getName()); } + /** + * Gets a set of color IDs which can be used to dynamically load the defined Jenkins colors. + * + * @return the available color IDs + */ + @JavaScriptMethod + @SuppressWarnings("unused") + public Set getJenkinsColorIDs() { + return CoverageColorJenkinsId.getAll(); + } + + /** + * Creates a new {@link ColorProvider} based on the passed color json string which contains the set Jenkins colors. + * + * @param colors + * The dynamically loaded Jenkins colors to be used for highlighting the coverage tree as json string + */ + @JavaScriptMethod + @SuppressWarnings("unused") + public void setJenkinsColors(final String colors) { + colorProvider = createColorProvider(colors); + } + + /** + * Parses the passed color json string to a {@link ColorProvider}. + * + * @param json + * The color json + * + * @return the created color provider + */ + private ColorProvider createColorProvider(final String json) { + try { + ObjectMapper mapper = new ObjectMapper(); + Map colorMapping = mapper.readValue(json, new ColorMappingType()); + return ColorProviderFactory.createColorProvider(colorMapping); + } + catch (JsonProcessingException e) { + return ColorProviderFactory.createDefaultColorProvider(); + } + } + @JavaScriptMethod public CoverageOverview getOverview() { return new CoverageOverview(getNode().filterPackageStructure()); @@ -149,7 +200,7 @@ private Optional getLatestAction() { @SuppressWarnings("unused") public TreeMapNode getCoverageTree(final String coverageMetric) { CoverageMetric metric = getCoverageMetricFromText(coverageMetric); - return TREE_MAP_NODE_CONVERTER.toTeeChartModel(getNode(), metric); + return TREE_MAP_NODE_CONVERTER.toTeeChartModel(getNode(), metric, colorProvider); } /** @@ -165,7 +216,7 @@ public TreeMapNode getCoverageTree(final String coverageMetric) { @SuppressWarnings("unused") public TreeMapNode getChangeCoverageTree(final String coverageMetric) { CoverageMetric metric = getCoverageMetricFromText(coverageMetric); - return TREE_MAP_NODE_CONVERTER.toTeeChartModel(changeCoverageTreeRoot, metric); + return TREE_MAP_NODE_CONVERTER.toTeeChartModel(changeCoverageTreeRoot, metric, colorProvider); } /** @@ -181,7 +232,7 @@ public TreeMapNode getChangeCoverageTree(final String coverageMetric) { @SuppressWarnings("unused") public TreeMapNode getCoverageChangesTree(final String coverageMetric) { CoverageMetric metric = getCoverageMetricFromText(coverageMetric); - return TREE_MAP_NODE_CONVERTER.toTeeChartModel(indirectCoverageChangesTreeRoot, metric); + return TREE_MAP_NODE_CONVERTER.toTeeChartModel(indirectCoverageChangesTreeRoot, metric, colorProvider); } /** @@ -223,11 +274,13 @@ public TableModel getTableModel(final String tableId) { switch (actualId) { case ABSOLUTE_COVERAGE_TABLE_ID: - return new CoverageTableModel(tableId, getNode(), renderer); + return new CoverageTableModel(tableId, getNode(), renderer, colorProvider); case CHANGE_COVERAGE_TABLE_ID: - return new ChangeCoverageTableModel(tableId, getNode(), changeCoverageTreeRoot, renderer); + return new ChangeCoverageTableModel(tableId, getNode(), changeCoverageTreeRoot, renderer, + colorProvider); case INDIRECT_COVERAGE_TABLE_ID: - return new IndirectCoverageChangesTable(tableId, getNode(), indirectCoverageChangesTreeRoot, renderer); + return new IndirectCoverageChangesTable(tableId, getNode(), indirectCoverageChangesTreeRoot, renderer, + colorProvider); default: throw new NoSuchElementException("No such table with id " + actualId); } @@ -488,4 +541,10 @@ private SortedMap getMetricsDistribution() { return coverage.getMetricsDistribution(); } } + + /** + * Used for parsing a Jenkins color mapping JSON string to a color map. + */ + private static final class ColorMappingType extends TypeReference> { + } } diff --git a/plugin/src/main/java/io/jenkins/plugins/coverage/model/IndirectCoverageChangesTable.java b/plugin/src/main/java/io/jenkins/plugins/coverage/model/IndirectCoverageChangesTable.java index 55b3978c9..ce6a3d99b 100644 --- a/plugin/src/main/java/io/jenkins/plugins/coverage/model/IndirectCoverageChangesTable.java +++ b/plugin/src/main/java/io/jenkins/plugins/coverage/model/IndirectCoverageChangesTable.java @@ -9,6 +9,7 @@ import hudson.Functions; +import io.jenkins.plugins.coverage.model.visualization.colorization.ColorProvider; import io.jenkins.plugins.datatables.DetailedCell; /** @@ -29,11 +30,13 @@ class IndirectCoverageChangesTable extends CoverageTableModel { * @param changeRoot * The root of the indirect coverage changes tree * @param renderer - * the renderer to use for the file names + * The renderer to use for the file names + * @param colorProvider + * The {@link ColorProvider} which provides the used colors */ IndirectCoverageChangesTable(final String id, final CoverageNode root, final CoverageNode changeRoot, - final RowRenderer renderer) { - super(id, root, renderer); + final RowRenderer renderer, final ColorProvider colorProvider) { + super(id, root, renderer, colorProvider); this.changeRoot = changeRoot; } @@ -43,7 +46,7 @@ public List getRows() { Locale browserLocale = Functions.getCurrentLocale(); return changeRoot.getAllFileCoverageNodes().stream() .map(file -> new IndirectCoverageChangesRow( - getOriginalNode(file), file, browserLocale, getRenderer())) + getOriginalNode(file), file, browserLocale, getRenderer(), getColorProvider())) .collect(Collectors.toList()); } @@ -64,8 +67,8 @@ private static class IndirectCoverageChangesRow extends CoverageRow { private final FileCoverageNode changedFileNode; IndirectCoverageChangesRow(final FileCoverageNode root, final FileCoverageNode changedFileNode, - final Locale browserLocale, final RowRenderer renderer) { - super(root, browserLocale, renderer); + final Locale browserLocale, final RowRenderer renderer, final ColorProvider colorProvider) { + super(root, browserLocale, renderer, colorProvider); this.changedFileNode = changedFileNode; } diff --git a/plugin/src/main/java/io/jenkins/plugins/coverage/model/visualization/colorization/ColorId.java b/plugin/src/main/java/io/jenkins/plugins/coverage/model/visualization/colorization/ColorId.java index 86fcb935c..a45978a81 100644 --- a/plugin/src/main/java/io/jenkins/plugins/coverage/model/visualization/colorization/ColorId.java +++ b/plugin/src/main/java/io/jenkins/plugins/coverage/model/visualization/colorization/ColorId.java @@ -10,13 +10,10 @@ public enum ColorId { VERY_BAD, BAD, INADEQUATE, - BELOW_AVERAGE, AVERAGE, - ABOVE_AVERAGE, GOOD, VERY_GOOD, EXCELLENT, - OUTSTANDING, BLACK, WHITE diff --git a/plugin/src/main/java/io/jenkins/plugins/coverage/model/visualization/colorization/ColorProvider.java b/plugin/src/main/java/io/jenkins/plugins/coverage/model/visualization/colorization/ColorProvider.java index 06147023a..a721534e6 100644 --- a/plugin/src/main/java/io/jenkins/plugins/coverage/model/visualization/colorization/ColorProvider.java +++ b/plugin/src/main/java/io/jenkins/plugins/coverage/model/visualization/colorization/ColorProvider.java @@ -1,11 +1,9 @@ package io.jenkins.plugins.coverage.model.visualization.colorization; import java.awt.*; -import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.Objects; -import java.util.stream.Collectors; import edu.umd.cs.findbugs.annotations.NonNull; @@ -27,23 +25,17 @@ public class ColorProvider { /** * The available {@link DisplayColors display colors} are mapped by the {@link ColorId id} of the fill color. */ - private final Map availableColors = new HashMap<>(); + private final Map availableColors; /** - * Creates a color provider which uses the {@link CoverageColorPalette internal color palette}. - */ - public ColorProvider() { - loadDefaultColors(); - } - - /** - * Creates a color provider for the passed {@link ColorScheme scheme}. + * Creates a color provider which uses the passed colors. Each color entry contains a background and a fitting text + * color. * - * @param colorScheme - * The color scheme to be used + * @param colorMapping + * The color mapping to be used */ - public ColorProvider(final ColorScheme colorScheme) { - loadColors(colorScheme); + ColorProvider(final Map colorMapping) { + availableColors = new HashMap<>(colorMapping); } /** @@ -92,25 +84,25 @@ public static Color blendWeightedColors(@NonNull final Color color1, @NonNull fi * * @param color * The {@link Color} + * @param alpha + * The alpha value within the range [0;255] * * @return the color as a hex string */ - public static String colorAsHex(final Color color) { - return String.format("#%02X%02X%02X%02X", color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha()); + public static String colorAsRGBAHex(final Color color, final int alpha) { + return String.format("#%02X%02X%02X%02X", color.getRed(), color.getGreen(), color.getBlue(), alpha); } /** - * Loads a color palette matching with the passed scheme. + * Provides the RGB hex string of the passed color. + * + * @param color + * The {@link Color} * - * @param colorScheme - * The color scheme + * @return the color as a hex string */ - @SuppressWarnings("PMD.UnusedFormalParameter") - private void loadColors(final ColorScheme colorScheme) { - // TODO: insert code here in order to load colors dependent on selected scheme - - // Internal fallback if no other schemes exist - loadDefaultColors(); + public static String colorAsRGBHex(final Color color) { + return String.format("#%02X%02X%02X", color.getRed(), color.getGreen(), color.getBlue()); } /** @@ -173,17 +165,6 @@ public DisplayColors getBlendedDisplayColors(final double weightFirst, final dou return DEFAULT_COLOR; } - /** - * Loads the internally usable {@link CoverageColorPalette color palette}. - */ - private void loadDefaultColors() { - availableColors.clear(); - availableColors.putAll( - Arrays.stream(CoverageColorPalette.values()) - .collect(Collectors.toMap(CoverageColorPalette::getColorId, - color -> new DisplayColors(color.getLineColor(), color.getFillColor())))); - } - /** * Wraps the fill color and the line color that should be used in order to visualize coverage values. * @@ -215,12 +196,25 @@ public Color getFillColor() { return fillColor; } - public String getLineColorAsHex() { - return colorAsHex(lineColor); + /** + * Gets the fill color with the passed alpha value. Using a low alpha value might require using another line + * color than the provided {@link #lineColor}. + * + * @param alpha + * The color alpha + * + * @return the hex code which contains the alpha value + */ + public String getFillColorAsRGBAHex(final int alpha) { + return colorAsRGBAHex(fillColor, alpha); + } + + public String getLineColorAsRGBHex() { + return colorAsRGBHex(lineColor); } - public String getFillColorAsHex() { - return colorAsHex(fillColor); + public String getFillColorAsRGBHex() { + return colorAsRGBHex(fillColor); } @Override diff --git a/plugin/src/main/java/io/jenkins/plugins/coverage/model/visualization/colorization/ColorProviderFactory.java b/plugin/src/main/java/io/jenkins/plugins/coverage/model/visualization/colorization/ColorProviderFactory.java index bbbd6b9ef..4fec5e125 100644 --- a/plugin/src/main/java/io/jenkins/plugins/coverage/model/visualization/colorization/ColorProviderFactory.java +++ b/plugin/src/main/java/io/jenkins/plugins/coverage/model/visualization/colorization/ColorProviderFactory.java @@ -1,5 +1,18 @@ package io.jenkins.plugins.coverage.model.visualization.colorization; +import java.awt.*; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import io.jenkins.plugins.coverage.model.visualization.colorization.ColorProvider.DisplayColors; + +import static io.jenkins.plugins.coverage.model.visualization.colorization.ColorId.*; +import static io.jenkins.plugins.coverage.model.visualization.colorization.CoverageColorJenkinsId.*; + /** * Provides factory methods for creating different {@link ColorProvider color providers}. * @@ -12,22 +25,106 @@ private ColorProviderFactory() { } /** - * Creates a {@link ColorProvider color provider} which uses the internal {@link CoverageColorPalette color - * palette}. + * Creates a {@link ColorProvider color provider} which uses the internal + * {@link CoverageColorPalette color palette}. * * @return the created color provider */ public static ColorProvider createDefaultColorProvider() { - return new ColorProvider(); + return new ColorProvider(getDefaultColors()); } /** - * Creates a {@link ColorProvider color provider} which uses a suitable {@link ColorScheme color scheme}. + * Creates a {@link ColorProvider color provider} which uses the set Jenkins colors. Required color keys are: + * '--green', '--light-green', '--yellow', '--light-yellow', '--orange', '--light-orange', '--red', '--light-red' - + * see {@link CoverageColorJenkinsId}. If colors are missing, the internal default colors are used - see + * {@link CoverageColorPalette}. + * + * @param colors + * Maps {@link CoverageColorJenkinsId jenkins color IDs} * * @return the created color provider */ - public static ColorProvider createColorProvider() { - // TODO: create provider dependent on selected color scheme - return new ColorProvider(ColorScheme.DEFAULT); + public static ColorProvider createColorProvider(final Map colors) { + if (!colors.keySet().equals(getAll()) || !verifyHexCodes(colors.values())) { + return createDefaultColorProvider(); + } + Map colorMap = new HashMap<>(); + // TODO: use dynamic text color (not provided yet) + colorMap.put(INSUFFICIENT, + createDisplayColor(colors.get(RED.getJenkinsColorId()), "#ffffff")); + colorMap.put(VERY_BAD, + createDisplayColor(colors.get(LIGHT_RED.getJenkinsColorId()), "#ffffff")); + colorMap.put(BAD, + createDisplayColor(colors.get(ORANGE.getJenkinsColorId()), "#000000")); + colorMap.put(INADEQUATE, + createDisplayColor(colors.get(LIGHT_ORANGE.getJenkinsColorId()), "#000000")); + colorMap.put(AVERAGE, + createDisplayColor(colors.get(YELLOW.getJenkinsColorId()), "#000000")); + colorMap.put(GOOD, + createDisplayColor(colors.get(LIGHT_YELLOW.getJenkinsColorId()), "#000000")); + colorMap.put(VERY_GOOD, + createDisplayColor(colors.get(LIGHT_GREEN.getJenkinsColorId()), "#000000")); + colorMap.put(EXCELLENT, + createDisplayColor(colors.get(GREEN.getJenkinsColorId()), "#ffffff")); + colorMap.put(BLACK, createDisplayColor(CoverageColorPalette.BLACK)); + colorMap.put(WHITE, createDisplayColor(CoverageColorPalette.WHITE)); + return new ColorProvider(colorMap); + } + + /** + * Loads the internally usable {@link CoverageColorPalette color palette}. This can be also used as a fallback. + * + * @return the default color mapping + */ + private static Map getDefaultColors() { + return Arrays.stream(CoverageColorPalette.values()) + .collect(Collectors.toMap(CoverageColorPalette::getColorId, ColorProviderFactory::createDisplayColor)); + } + + /** + * Verifies that all passed strings are color hex codes. + * + * @param hexCodes + * The strings to be investigated + * + * @return {@code true} if all strings are hex codes + */ + private static boolean verifyHexCodes(final Collection hexCodes) { + Pattern hexPattern = Pattern.compile("^#[A-Fa-f0-9]{6}$"); + for (String hex : hexCodes) { + if (!hexPattern.matcher(hex).matches()) { + return false; + } + } + return true; + } + + /** + * Creates a pair of {@link DisplayColors display colors} from the {@link CoverageColorPalette}. + * + * @param color + * The passed palette color + * + * @return the created display color + */ + private static DisplayColors createDisplayColor(final CoverageColorPalette color) { + return new DisplayColors(color.getLineColor(), color.getFillColor()); + } + + /** + * Creates a pair of {@link DisplayColors display colors} from the passed hex colors. + * + * @param backgroundColorHex + * The hex code of the background color + * @param textColorHex + * The hex code of the text color + * + * @return the created display color + */ + private static DisplayColors createDisplayColor(final String backgroundColorHex, final String textColorHex) { + Color backgroundColor = Color.decode(backgroundColorHex); + Color textColor = Color.decode(textColorHex); + return new DisplayColors(textColor, backgroundColor); } } diff --git a/plugin/src/main/java/io/jenkins/plugins/coverage/model/visualization/colorization/CoverageChangeLevel.java b/plugin/src/main/java/io/jenkins/plugins/coverage/model/visualization/colorization/CoverageChangeLevel.java index 4727195f0..ecc01b28f 100644 --- a/plugin/src/main/java/io/jenkins/plugins/coverage/model/visualization/colorization/CoverageChangeLevel.java +++ b/plugin/src/main/java/io/jenkins/plugins/coverage/model/visualization/colorization/CoverageChangeLevel.java @@ -11,8 +11,8 @@ */ public enum CoverageChangeLevel { - INCREASE_5(5.0, ColorId.OUTSTANDING), - INCREASE_2(2.0, ColorId.EXCELLENT), + INCREASE_5(5.0, ColorId.EXCELLENT), + INCREASE_2(2.0, ColorId.VERY_GOOD), EQUALS(0.0, ColorId.AVERAGE), DECREASE_2(-2.0, ColorId.INADEQUATE), DECREASE_5(-5.0, ColorId.BAD), diff --git a/plugin/src/main/java/io/jenkins/plugins/coverage/model/visualization/colorization/CoverageColorJenkinsId.java b/plugin/src/main/java/io/jenkins/plugins/coverage/model/visualization/colorization/CoverageColorJenkinsId.java new file mode 100644 index 000000000..64971c27a --- /dev/null +++ b/plugin/src/main/java/io/jenkins/plugins/coverage/model/visualization/colorization/CoverageColorJenkinsId.java @@ -0,0 +1,39 @@ +package io.jenkins.plugins.coverage.model.visualization.colorization; + +import java.util.Arrays; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * Contains color IDs which represent the keys of a JSON object that is dynamically filled with the currently set + * Jenkins colors. + * + * @author Florian Orendi + */ +public enum CoverageColorJenkinsId { + + GREEN("--green"), + LIGHT_GREEN("--light-green"), + YELLOW("--yellow"), + LIGHT_YELLOW("--light-yellow"), + ORANGE("--orange"), + LIGHT_ORANGE("--light-orange"), + RED("--red"), + LIGHT_RED("--light-red"); + + private final String jenkinsColorId; + + CoverageColorJenkinsId(final String colorId) { + this.jenkinsColorId = colorId; + } + + public String getJenkinsColorId() { + return jenkinsColorId; + } + + public static Set getAll() { + return Arrays.stream(values()) + .map(id -> id.jenkinsColorId) + .collect(Collectors.toSet()); + } +} diff --git a/plugin/src/main/java/io/jenkins/plugins/coverage/model/visualization/colorization/CoverageColorPalette.java b/plugin/src/main/java/io/jenkins/plugins/coverage/model/visualization/colorization/CoverageColorPalette.java index 2e3b809ea..8ed5ef22a 100644 --- a/plugin/src/main/java/io/jenkins/plugins/coverage/model/visualization/colorization/CoverageColorPalette.java +++ b/plugin/src/main/java/io/jenkins/plugins/coverage/model/visualization/colorization/CoverageColorPalette.java @@ -3,7 +3,9 @@ import java.awt.*; /** - * Provides a color palette which can be used as an plugin internal fallback if no other color schemes have been defined. + * Provides a color palette which can be used as a plugin internal fallback if no other color schemes have been defined. + * The defined colors correspond to the Jenkins Design + * Library. * * @author Florian Orendi */ @@ -12,20 +14,17 @@ public enum CoverageColorPalette { WHITE(ColorId.WHITE, new Color(255, 255, 255), new Color(0, 0, 0)), BLACK(ColorId.BLACK, new Color(0, 0, 0), new Color(255, 255, 255)), - DARK_RED(ColorId.INSUFFICIENT, new Color(200, 0, 0, 80), new Color(0, 0, 0)), - LIGHT_RED(ColorId.VERY_BAD, new Color(255, 50, 50, 80), new Color(0, 0, 0)), + RED(ColorId.INSUFFICIENT, new Color(230, 0, 31), new Color(255, 255, 255)), + LIGHT_RED(ColorId.VERY_BAD, new Color(255, 77, 101), new Color(255, 255, 255)), - DARK_ORANGE(ColorId.BAD, new Color(255, 120, 50, 80), new Color(0, 0, 0)), - ORANGE(ColorId.INADEQUATE, new Color(255, 170, 50, 80), new Color(0, 0, 0)), - LIGHT_ORANGE(ColorId.BELOW_AVERAGE, new Color(255, 200, 50, 80), new Color(0, 0, 0)), + ORANGE(ColorId.BAD, new Color(254, 130, 10), new Color(0, 0, 0)), + LIGHT_ORANGE(ColorId.INADEQUATE, new Color(254, 182, 112), new Color(0, 0, 0)), - DARK_YELLOW(ColorId.AVERAGE, new Color(240, 240, 120, 80), new Color(0, 0, 0)), - YELLOW(ColorId.ABOVE_AVERAGE, new Color(220, 250, 110, 80), new Color(0, 0, 0)), - LIGHT_YELLOW(ColorId.GOOD, new Color(200, 255, 100, 80), new Color(0, 0, 0)), + YELLOW(ColorId.AVERAGE, new Color(255, 204, 0), new Color(0, 0, 0)), + LIGHT_YELLOW(ColorId.GOOD, new Color(255, 224, 102), new Color(0, 0, 0)), - LIGHT_GREEN(ColorId.VERY_GOOD, new Color(150, 255, 100, 80), new Color(0, 0, 0)), - GREEN(ColorId.EXCELLENT, new Color(0, 200, 0, 80), new Color(0, 0, 0)), - DARK_GREEN(ColorId.OUTSTANDING, new Color(0, 130, 0, 80), new Color(0, 0, 0)); + LIGHT_GREEN(ColorId.VERY_GOOD, new Color(75, 223, 124), new Color(0, 0, 0)), + GREEN(ColorId.EXCELLENT, new Color(30, 166, 75), new Color(255, 255, 255)); private final ColorId colorId; private final Color fillColor; diff --git a/plugin/src/main/java/io/jenkins/plugins/coverage/model/visualization/colorization/CoverageLevel.java b/plugin/src/main/java/io/jenkins/plugins/coverage/model/visualization/colorization/CoverageLevel.java index c489a66de..a09024a65 100644 --- a/plugin/src/main/java/io/jenkins/plugins/coverage/model/visualization/colorization/CoverageLevel.java +++ b/plugin/src/main/java/io/jenkins/plugins/coverage/model/visualization/colorization/CoverageLevel.java @@ -11,13 +11,11 @@ */ public enum CoverageLevel { - LVL_95(95.0, ColorId.OUTSTANDING), - LVL_90(90.0, ColorId.EXCELLENT), - LVL_85(85.0, ColorId.VERY_GOOD), - LVL_80(80.0, ColorId.GOOD), - LVL_75(75.0, ColorId.ABOVE_AVERAGE), - LVL_70(70.0, ColorId.AVERAGE), - LVL_65(65.0, ColorId.BELOW_AVERAGE), + LVL_95(95.0, ColorId.EXCELLENT), + LVL_90(90.0, ColorId.VERY_GOOD), + LVL_80(85.0, ColorId.GOOD), + LVL_75(80.0, ColorId.AVERAGE), + LVL_70(70.0, ColorId.INADEQUATE), LVL_60(60.0, ColorId.BAD), LVL_50(50.0, ColorId.VERY_BAD), LVL_0(0.0, ColorId.INSUFFICIENT), diff --git a/plugin/src/main/java/io/jenkins/plugins/coverage/model/visualization/dashboard/CoverageColumnType.java b/plugin/src/main/java/io/jenkins/plugins/coverage/model/visualization/dashboard/CoverageColumnType.java index 4e34a16bf..ffc4f1095 100644 --- a/plugin/src/main/java/io/jenkins/plugins/coverage/model/visualization/dashboard/CoverageColumnType.java +++ b/plugin/src/main/java/io/jenkins/plugins/coverage/model/visualization/dashboard/CoverageColumnType.java @@ -43,7 +43,7 @@ public CoverageColumnType(final Localizable displayName) { * @return the color provider */ protected ColorProvider getColorProvider() { - return ColorProviderFactory.createColorProvider(); + return ColorProviderFactory.createDefaultColorProvider(); } /** diff --git a/plugin/src/main/java/io/jenkins/plugins/coverage/model/visualization/tree/TreeMapNodeConverter.java b/plugin/src/main/java/io/jenkins/plugins/coverage/model/visualization/tree/TreeMapNodeConverter.java index 33053fd23..d265dfe03 100644 --- a/plugin/src/main/java/io/jenkins/plugins/coverage/model/visualization/tree/TreeMapNodeConverter.java +++ b/plugin/src/main/java/io/jenkins/plugins/coverage/model/visualization/tree/TreeMapNodeConverter.java @@ -1,33 +1,25 @@ package io.jenkins.plugins.coverage.model.visualization.tree; +import edu.hm.hafner.echarts.ItemStyle; +import edu.hm.hafner.echarts.Label; import edu.hm.hafner.echarts.TreeMapNode; import io.jenkins.plugins.coverage.model.Coverage; import io.jenkins.plugins.coverage.model.CoverageMetric; import io.jenkins.plugins.coverage.model.CoverageNode; +import io.jenkins.plugins.coverage.model.FileCoverageNode; import io.jenkins.plugins.coverage.model.visualization.colorization.ColorProvider; +import io.jenkins.plugins.coverage.model.visualization.colorization.ColorProvider.DisplayColors; import io.jenkins.plugins.coverage.model.visualization.colorization.CoverageLevel; /** - * Converts a tree of {@link CoverageNode coverage nodes} to a corresponding tree of {@link TreeMapNode ECharts tree map - * nodes}. + * Converts a tree of {@link CoverageNode coverage nodes} to a corresponding tree of + * {@link TreeMapNode ECharts tree map nodes}. * * @author Ullrich Hafner */ public class TreeMapNodeConverter { - private final ColorProvider colorProvider; - - /** - * Creates a converter which converts a {@link CoverageNode} to a tree map of {@link TreeMapNode}. - * - * @param colorProvider - * The {@link ColorProvider provider} which provides the colors used by the tree map - */ - public TreeMapNodeConverter(final ColorProvider colorProvider) { - this.colorProvider = colorProvider; - } - /** * Converts a coverage tree of {@link CoverageNode} to a ECharts tree map of {@link TreeMapNode}. * @@ -35,11 +27,14 @@ public TreeMapNodeConverter(final ColorProvider colorProvider) { * The root node of the tree to be converted * @param metric * The coverage metric that should be represented (line and branch coverage are available) + * @param colorProvider + * Provides the colors to be used for highlighting the tree nodes * * @return the converted tree map representation */ - public TreeMapNode toTeeChartModel(final CoverageNode node, final CoverageMetric metric) { - TreeMapNode root = toTreeMapNode(node, metric); + public TreeMapNode toTeeChartModel(final CoverageNode node, final CoverageMetric metric, + final ColorProvider colorProvider) { + TreeMapNode root = toTreeMapNode(node, metric, colorProvider); for (TreeMapNode child : root.getChildren()) { child.collapseEmptyPackages(); } @@ -47,22 +42,32 @@ public TreeMapNode toTeeChartModel(final CoverageNode node, final CoverageMetric return root; } - private TreeMapNode toTreeMapNode(final CoverageNode node, final CoverageMetric metric) { + private TreeMapNode toTreeMapNode(final CoverageNode node, final CoverageMetric metric, + final ColorProvider colorProvider) { Coverage coverage = node.getCoverage(metric); double coveragePercentage = coverage.getCoveredPercentage().getDoubleValue(); - String color = CoverageLevel - .getDisplayColorsOfCoverageLevel(coveragePercentage, colorProvider) - .getFillColorAsHex(); + DisplayColors colors = CoverageLevel.getDisplayColorsOfCoverageLevel(coveragePercentage, colorProvider); + String lineColor = colors.getLineColorAsRGBHex(); + String fillColor = colors.getFillColorAsRGBHex(); - TreeMapNode treeNode = new TreeMapNode(node.getName(), color, coverage.getTotal(), coverage.getCovered()); - if (node.getMetric().equals(CoverageMetric.FILE)) { - return treeNode; + Label label = new Label(true, lineColor); + Label upperLabel = new Label(true, lineColor); + + if (node instanceof FileCoverageNode) { + ItemStyle style = new ItemStyle(fillColor); + return new TreeMapNode(node.getName(), style, label, upperLabel, coverage.getTotal(), + coverage.getCovered()); } + ItemStyle packageStyle = new ItemStyle(fillColor, fillColor, 4); + TreeMapNode treeNode = + new TreeMapNode(node.getName(), packageStyle, label, upperLabel, coverage.getTotal(), + coverage.getCovered()); + node.getChildren().stream() - .map(n -> toTreeMapNode(n, metric)) + .map(n -> toTreeMapNode(n, metric, colorProvider)) .forEach(treeNode::insertNode); return treeNode; } diff --git a/plugin/src/main/resources/io/jenkins/plugins/coverage/model/CoverageViewModel/index.jelly b/plugin/src/main/resources/io/jenkins/plugins/coverage/model/CoverageViewModel/index.jelly index 5cfbb1f18..5de9cfb4c 100644 --- a/plugin/src/main/resources/io/jenkins/plugins/coverage/model/CoverageViewModel/index.jelly +++ b/plugin/src/main/resources/io/jenkins/plugins/coverage/model/CoverageViewModel/index.jelly @@ -13,6 +13,7 @@ diff --git a/plugin/src/main/resources/io/jenkins/plugins/coverage/model/visualization/dashboard/CoverageColumn/column.jelly b/plugin/src/main/resources/io/jenkins/plugins/coverage/model/visualization/dashboard/CoverageColumn/column.jelly index 1c593f5c1..54dfbf370 100644 --- a/plugin/src/main/resources/io/jenkins/plugins/coverage/model/visualization/dashboard/CoverageColumn/column.jelly +++ b/plugin/src/main/resources/io/jenkins/plugins/coverage/model/visualization/dashboard/CoverageColumn/column.jelly @@ -9,7 +9,7 @@ - + diff --git a/plugin/src/main/webapp/js/charts.js b/plugin/src/main/webapp/js/charts.js index 0d609c49a..b07330b96 100644 --- a/plugin/src/main/webapp/js/charts.js +++ b/plugin/src/main/webapp/js/charts.js @@ -17,9 +17,29 @@ const CoverageChartGenerator = function ($) { return getComputedStyle(document.body).getPropertyValue('--text-color') || '#333'; } - function createOverview(overview, id) { - const missedColor = '#EF9A9A'; - const coveredColor = '#A5D6A7'; + /** + * Searches for a Jenkins color by a color id. + * + * @param jenkinsColors The available Jenkins colors + * @param id The color id + * @param defaultValue The default value if the id does not exist + * @param alpha The alpha value between [0;255] + * @returns {string} the hex code of the Jenkins color or, if not existent, the default value + */ + function getJenkinsColorById(jenkinsColors, id, defaultValue, alpha) { + const alphaHex = alpha.toString(16); + if (jenkinsColors.has(id)) { + const color = jenkinsColors.get(id); + if (color.match(/^#[a-fA-F0-9]{6}$/) !== null) { + return color + alphaHex; + } + } + return defaultValue + alphaHex; + } + + function createOverview(overview, id, jenkinsColors) { + const missedColor = getJenkinsColorById(jenkinsColors, "--light-red", "#ff4d65", 120); + const coveredColor = getJenkinsColorById(jenkinsColors, "--light-green", "#4bdf7c", 120); const summaryChartDiv = $('#' + id); summaryChartDiv.height(overview.metrics.length * 31 + 150 + 'px'); @@ -121,6 +141,7 @@ const CoverageChartGenerator = function ($) { label: { show: true, position: 'insideLeft', + color: 'black', formatter: function (obj) { return overview.covered[obj.dataIndex]; } @@ -139,6 +160,7 @@ const CoverageChartGenerator = function ($) { label: { show: true, position: 'insideRight', + color: 'black', formatter: function (obj) { return overview.missed[obj.dataIndex]; } @@ -156,9 +178,8 @@ const CoverageChartGenerator = function ($) { return [ { itemStyle: { - borderColor: 'black', borderWidth: 0, - gapWidth: 1 + gapWidth: 5, }, upperLabel: { show: false @@ -166,65 +187,47 @@ const CoverageChartGenerator = function ($) { }, { itemStyle: { - borderColor: '#ddd', - borderWidth: 2, - gapWidth: 2 + gapWidth: 3, } }, { itemStyle: { - borderWidth: 4, - gapWidth: 2, - borderColorSaturation: 0.6 + gapWidth: 1, } }, { itemStyle: { - borderWidth: 4, - gapWidth: 2, - borderColorSaturation: 0.7 + gapWidth: 1, } }, { itemStyle: { - borderWidth: 4, - gapWidth: 2, - borderColorSaturation: 0.6 + gapWidth: 1, } }, { itemStyle: { - borderWidth: 4, - gapWidth: 2, - borderColorSaturation: 0.7 + gapWidth: 1, } }, { itemStyle: { - borderWidth: 4, - gapWidth: 2, - borderColorSaturation: 0.6 + gapWidth: 1, } }, { itemStyle: { - borderWidth: 4, - gapWidth: 2, - borderColorSaturation: 0.7 + gapWidth: 1, } }, { itemStyle: { - borderWidth: 4, - gapWidth: 2, - borderColorSaturation: 0.6 + gapWidth: 1, } }, { itemStyle: { - borderWidth: 4, - gapWidth: 2, - borderColorSaturation: 0.7 + gapWidth: 1, } }, ]; @@ -234,7 +237,6 @@ const CoverageChartGenerator = function ($) { const treeChart = echarts.init(treeChartDiv[0]); treeChartDiv[0].echart = treeChart; - const textColor = getTextColor(); const formatUtil = echarts.format; const option = { @@ -281,17 +283,14 @@ const CoverageChartGenerator = function ($) { label: { show: true, formatter: '{b}', - color: textColor }, upperLabel: { show: true, height: 30, - color: 'black', - borderColorSaturation: 0.6, - colorSaturation: 0.6, }, itemStyle: { - borderColor: '#fff', + shadowColor: '#000', + shadowBlur: 3, }, levels: getLevelOption(), data: [coverageTree] @@ -302,7 +301,7 @@ const CoverageChartGenerator = function ($) { treeChart.resize(); } - this.populateDetailsCharts = function () { + this.populateDetailsCharts = function (jenkinsColors) { /** * Activate the tab that has been visited the last time. If there is no such tab, highlight the first one. * If the user selects the tab using an #anchor prefer this tab. @@ -362,7 +361,7 @@ const CoverageChartGenerator = function ($) { renderTrendChart(); viewProxy.getOverview(function (t) { - createOverview(t.responseObject(), 'coverage-overview'); + createOverview(t.responseObject(), 'coverage-overview', jenkinsColors); }); viewProxy.getCoverageTree('Line', function (t) { diff --git a/plugin/src/main/webapp/js/colors.js b/plugin/src/main/webapp/js/colors.js new file mode 100644 index 000000000..a64659245 --- /dev/null +++ b/plugin/src/main/webapp/js/colors.js @@ -0,0 +1,12 @@ +getJenkinsColors = function (colors) { + // TODO: also handle HSL colors and parse them to hex in order to use dark mode colors + const colorHexMapping = new Map; + colors.forEach(function (jenkinsId) { + const colorHex = getComputedStyle(document.body).getPropertyValue(jenkinsId); + if (colorHex.match(/^#[a-fA-F0-9]{6}$/) !== null) { + colorHexMapping.set(jenkinsId, colorHex); + } + }) + return colorHexMapping; +}; + diff --git a/plugin/src/test/java/io/jenkins/plugins/coverage/model/visualization/colorization/ColorProviderFactoryTest.java b/plugin/src/test/java/io/jenkins/plugins/coverage/model/visualization/colorization/ColorProviderFactoryTest.java index 7ff8f43d9..37bd62094 100644 --- a/plugin/src/test/java/io/jenkins/plugins/coverage/model/visualization/colorization/ColorProviderFactoryTest.java +++ b/plugin/src/test/java/io/jenkins/plugins/coverage/model/visualization/colorization/ColorProviderFactoryTest.java @@ -1,7 +1,12 @@ package io.jenkins.plugins.coverage.model.visualization.colorization; +import java.awt.*; +import java.util.HashMap; +import java.util.Map; + import org.junit.jupiter.api.Test; +import static io.jenkins.plugins.coverage.model.visualization.colorization.CoverageColorJenkinsId.*; import static org.assertj.core.api.Assertions.*; /** @@ -11,6 +16,9 @@ */ class ColorProviderFactoryTest { + private static final String TEST_COLOR_HEX = "#ffffff"; + private static final Color TEST_COLOR = Color.decode(TEST_COLOR_HEX); + @Test void shouldCreateDefaultColorProvider() { ColorProvider colorProvider = ColorProviderFactory.createDefaultColorProvider(); @@ -18,4 +26,51 @@ void shouldCreateDefaultColorProvider() { assertThat(colorProvider.containsColorId(color.getColorId())).isTrue(); } } + + @Test + void shouldCreateColorProviderWithJenkinsColors() { + Map colorMapping = createColorMapping(); + ColorProvider colorProvider = ColorProviderFactory.createColorProvider(colorMapping); + + for (CoverageColorPalette color : CoverageColorPalette.values()) { + assertThat(colorProvider.containsColorId(color.getColorId())).isTrue(); + if (!color.getColorId().equals(ColorId.BLACK) && !color.getColorId() + .equals(ColorId.WHITE)) { // skip set default color + assertThat(colorProvider.getDisplayColorsOf(color.getColorId())) + .satisfies(displayColor -> assertThat(displayColor.getFillColor()).isEqualTo(TEST_COLOR)); + } + } + } + + @Test + void shouldCreateDefaultColorProviderWithMissingJenkinsColorIds() { + Map colorMapping = createColorMapping(); + colorMapping.remove("--green"); + ColorProvider colorProvider = ColorProviderFactory.createColorProvider(colorMapping); + for (CoverageColorPalette color : CoverageColorPalette.values()) { + assertThat(colorProvider.containsColorId(color.getColorId())).isTrue(); + } + } + + @Test + void shouldCreateDefaultColorProviderWithoutHexColors() { + Map colorMapping = createColorMapping(); + colorMapping.replace("--green", "hsl(135deg, 75%, 55%)"); + ColorProvider colorProvider = ColorProviderFactory.createColorProvider(colorMapping); + for (CoverageColorPalette color : CoverageColorPalette.values()) { + assertThat(colorProvider.containsColorId(color.getColorId())).isTrue(); + } + } + + /** + * Creates a color mapping between the {@link CoverageColorJenkinsId jenkins color id} and the corresponding color + * hex code. + * + * @return the created mapping + */ + private Map createColorMapping() { + Map colorMapping = new HashMap<>(); + getAll().forEach(id -> colorMapping.put(id, TEST_COLOR_HEX)); + return colorMapping; + } } diff --git a/plugin/src/test/java/io/jenkins/plugins/coverage/model/visualization/colorization/ColorProviderTest.java b/plugin/src/test/java/io/jenkins/plugins/coverage/model/visualization/colorization/ColorProviderTest.java index 315152f49..278fe2ee4 100644 --- a/plugin/src/test/java/io/jenkins/plugins/coverage/model/visualization/colorization/ColorProviderTest.java +++ b/plugin/src/test/java/io/jenkins/plugins/coverage/model/visualization/colorization/ColorProviderTest.java @@ -16,13 +16,6 @@ */ class ColorProviderTest { - @Test - void shouldLoadColors() { - // TODO: this test case is trivial at the moment and needs to get expanded with further color schemes - ColorProvider colorProvider = new ColorProvider(ColorScheme.DEFAULT); - assertThat(colorProvider.containsColorId(ColorId.EXCELLENT)).isTrue(); - } - @Test void shouldGetDisplayColorsOfId() { ColorProvider colorProvider = createDefaultColorProvider(); @@ -80,14 +73,17 @@ void shouldBlendWeightedColors() { @Test void shouldProvideColorAsHex() { - assertThat(colorAsHex(Color.black)).isEqualTo("#000000FF"); + assertThat(colorAsRGBHex(Color.black)).isEqualTo("#000000"); + assertThat(colorAsRGBAHex(Color.black, 255)).isEqualTo("#000000FF"); } @Test void shouldProvideColorAsHexForDisplayColors() { DisplayColors displayColors = new DisplayColors(Color.black, Color.white); - assertThat(displayColors.getLineColorAsHex()).isEqualTo("#000000FF"); - assertThat(displayColors.getFillColorAsHex()).isEqualTo("#FFFFFFFF"); + + assertThat(displayColors.getFillColorAsRGBAHex(255)).isEqualTo("#FFFFFFFF"); + assertThat(displayColors.getLineColorAsRGBHex()).isEqualTo("#000000"); + assertThat(displayColors.getFillColorAsRGBHex()).isEqualTo("#FFFFFF"); } @Test diff --git a/plugin/src/test/java/io/jenkins/plugins/coverage/model/visualization/colorization/CoverageChangeLevelTest.java b/plugin/src/test/java/io/jenkins/plugins/coverage/model/visualization/colorization/CoverageChangeLevelTest.java index 079f7614e..5cb5c846e 100644 --- a/plugin/src/test/java/io/jenkins/plugins/coverage/model/visualization/colorization/CoverageChangeLevelTest.java +++ b/plugin/src/test/java/io/jenkins/plugins/coverage/model/visualization/colorization/CoverageChangeLevelTest.java @@ -15,13 +15,13 @@ */ class CoverageChangeLevelTest { - private static final ColorProvider COLOR_PROVIDER = ColorProviderFactory.createColorProvider(); + private static final ColorProvider COLOR_PROVIDER = ColorProviderFactory.createDefaultColorProvider(); @Test void shouldHaveWorkingGetters() { CoverageChangeLevel coverageChangeLevel = CoverageChangeLevel.INCREASE_2; assertThat(coverageChangeLevel.getChange()).isEqualTo(2.0); - assertThat(coverageChangeLevel.getColorizationId()).isEqualTo(ColorId.EXCELLENT); + assertThat(coverageChangeLevel.getColorizationId()).isEqualTo(ColorId.VERY_GOOD); } @Test @@ -39,7 +39,7 @@ void shouldGetDisplayColorsOfCoveragePercentage() { assertThat(CoverageChangeLevel.getDisplayColorsOfCoverageChange(-1.0, COLOR_PROVIDER)) .isEqualTo(new DisplayColors(blendedLineColor, blendedColorDecreased)); assertThat(CoverageChangeLevel.getDisplayColorsOfCoverageChange(7.0, COLOR_PROVIDER)) - .isEqualTo(COLOR_PROVIDER.getDisplayColorsOf(ColorId.OUTSTANDING)); + .isEqualTo(COLOR_PROVIDER.getDisplayColorsOf(ColorId.EXCELLENT)); assertThat(CoverageChangeLevel.getDisplayColorsOfCoverageChange(-2.0, COLOR_PROVIDER)) .isEqualTo(COLOR_PROVIDER.getDisplayColorsOf(ColorId.INADEQUATE)); assertThat(CoverageChangeLevel.getDisplayColorsOfCoverageChange(-110.0, COLOR_PROVIDER)) diff --git a/plugin/src/test/java/io/jenkins/plugins/coverage/model/visualization/colorization/CoverageChangeTendencyTest.java b/plugin/src/test/java/io/jenkins/plugins/coverage/model/visualization/colorization/CoverageChangeTendencyTest.java index 29e2030ab..c73acf5ba 100644 --- a/plugin/src/test/java/io/jenkins/plugins/coverage/model/visualization/colorization/CoverageChangeTendencyTest.java +++ b/plugin/src/test/java/io/jenkins/plugins/coverage/model/visualization/colorization/CoverageChangeTendencyTest.java @@ -11,7 +11,7 @@ */ class CoverageChangeTendencyTest { - private static final ColorProvider COLOR_PROVIDER = ColorProviderFactory.createColorProvider(); + private static final ColorProvider COLOR_PROVIDER = ColorProviderFactory.createDefaultColorProvider(); @Test void shouldGetDisplayColorsForTendency() { diff --git a/plugin/src/test/java/io/jenkins/plugins/coverage/model/visualization/colorization/CoverageColorJenkinsIdTest.java b/plugin/src/test/java/io/jenkins/plugins/coverage/model/visualization/colorization/CoverageColorJenkinsIdTest.java new file mode 100644 index 000000000..8f94e7929 --- /dev/null +++ b/plugin/src/test/java/io/jenkins/plugins/coverage/model/visualization/colorization/CoverageColorJenkinsIdTest.java @@ -0,0 +1,24 @@ +package io.jenkins.plugins.coverage.model.visualization.colorization; + +import org.junit.jupiter.api.Test; + +import static io.jenkins.plugins.coverage.model.visualization.colorization.CoverageColorJenkinsId.*; +import static org.assertj.core.api.Assertions.*; + +/** + * Test class for {@link CoverageColorJenkinsId}. + * + * @author Florian Orendi + */ +class CoverageColorJenkinsIdTest { + + @Test + void shouldGetAllIds() { + assertThat(getAll().size()).isEqualTo(values().length); + } + + @Test + void shouldGetColorId() { + assertThat(GREEN.getJenkinsColorId()).isEqualTo("--green"); + } +} diff --git a/plugin/src/test/java/io/jenkins/plugins/coverage/model/visualization/colorization/CoverageLevelTest.java b/plugin/src/test/java/io/jenkins/plugins/coverage/model/visualization/colorization/CoverageLevelTest.java index 23277bdae..010df7a62 100644 --- a/plugin/src/test/java/io/jenkins/plugins/coverage/model/visualization/colorization/CoverageLevelTest.java +++ b/plugin/src/test/java/io/jenkins/plugins/coverage/model/visualization/colorization/CoverageLevelTest.java @@ -15,7 +15,7 @@ */ class CoverageLevelTest { - private static final ColorProvider COLOR_PROVIDER = ColorProviderFactory.createColorProvider(); + private static final ColorProvider COLOR_PROVIDER = ColorProviderFactory.createDefaultColorProvider(); @Test void shouldHaveWorkingGetters() { @@ -27,14 +27,14 @@ void shouldHaveWorkingGetters() { @Test void shouldGetDisplayColorsOfCoveragePercentage() { Color blendedColor = ColorProvider.blendColors( - COLOR_PROVIDER.getDisplayColorsOf(CoverageLevel.LVL_65.getColorizationId()).getFillColor(), + COLOR_PROVIDER.getDisplayColorsOf(CoverageLevel.LVL_60.getColorizationId()).getFillColor(), COLOR_PROVIDER.getDisplayColorsOf(CoverageLevel.LVL_70.getColorizationId()).getFillColor()); - assertThat(CoverageLevel.getDisplayColorsOfCoverageLevel(67.5, COLOR_PROVIDER)) + assertThat(CoverageLevel.getDisplayColorsOfCoverageLevel(65.0, COLOR_PROVIDER)) .isEqualTo(new DisplayColors(COLOR_PROVIDER.getDisplayColorsOf(ColorId.BLACK).getFillColor(), blendedColor)); assertThat(CoverageLevel.getDisplayColorsOfCoverageLevel(96.0, COLOR_PROVIDER)) - .isEqualTo(COLOR_PROVIDER.getDisplayColorsOf(ColorId.OUTSTANDING)); + .isEqualTo(COLOR_PROVIDER.getDisplayColorsOf(ColorId.EXCELLENT)); assertThat(CoverageLevel.getDisplayColorsOfCoverageLevel(50.0, COLOR_PROVIDER)) .isEqualTo(COLOR_PROVIDER.getDisplayColorsOf(ColorId.VERY_BAD)); assertThat(CoverageLevel.getDisplayColorsOfCoverageLevel(-2.0, COLOR_PROVIDER)) diff --git a/plugin/src/test/java/io/jenkins/plugins/coverage/model/visualization/dashboard/CoverageColumnTest.java b/plugin/src/test/java/io/jenkins/plugins/coverage/model/visualization/dashboard/CoverageColumnTest.java index 97713eb78..b6edd2dde 100644 --- a/plugin/src/test/java/io/jenkins/plugins/coverage/model/visualization/dashboard/CoverageColumnTest.java +++ b/plugin/src/test/java/io/jenkins/plugins/coverage/model/visualization/dashboard/CoverageColumnTest.java @@ -34,7 +34,7 @@ class CoverageColumnTest { private static final ProjectCoverageDelta PROJECT_COVERAGE_DELTA = new ProjectCoverageDelta(); private static final CoverageMetric COVERAGE_METRIC = CoverageMetric.BRANCH; - private static final ColorProvider COLOR_PROVIDER = ColorProviderFactory.createColorProvider(); + private static final ColorProvider COLOR_PROVIDER = ColorProviderFactory.createDefaultColorProvider(); @Test void shouldHaveWorkingDataGetters() { diff --git a/plugin/src/test/java/io/jenkins/plugins/coverage/model/visualization/dashboard/CoverageColumnTypeTest.java b/plugin/src/test/java/io/jenkins/plugins/coverage/model/visualization/dashboard/CoverageColumnTypeTest.java index c5fa136a8..6dc269d33 100644 --- a/plugin/src/test/java/io/jenkins/plugins/coverage/model/visualization/dashboard/CoverageColumnTypeTest.java +++ b/plugin/src/test/java/io/jenkins/plugins/coverage/model/visualization/dashboard/CoverageColumnTypeTest.java @@ -41,7 +41,7 @@ class CoverageColumnTypeTest { protected static final CoverageMetric COVERAGE_METRIC = CoverageMetric.BRANCH; protected static final Locale LOCALE = Locale.GERMAN; - protected static final ColorProvider COLOR_PROVIDER = ColorProviderFactory.createColorProvider(); + protected static final ColorProvider COLOR_PROVIDER = ColorProviderFactory.createDefaultColorProvider(); @Test void shouldGetDisplayName() { diff --git a/plugin/src/test/java/io/jenkins/plugins/coverage/model/visualization/tree/TreeMapNodeConverterTest.java b/plugin/src/test/java/io/jenkins/plugins/coverage/model/visualization/tree/TreeMapNodeConverterTest.java index bc4d2331d..d582b55ed 100644 --- a/plugin/src/test/java/io/jenkins/plugins/coverage/model/visualization/tree/TreeMapNodeConverterTest.java +++ b/plugin/src/test/java/io/jenkins/plugins/coverage/model/visualization/tree/TreeMapNodeConverterTest.java @@ -10,6 +10,7 @@ import io.jenkins.plugins.coverage.model.CoverageMetric; import io.jenkins.plugins.coverage.model.CoverageNode; import io.jenkins.plugins.coverage.model.visualization.colorization.ColorProvider; +import io.jenkins.plugins.coverage.model.visualization.colorization.ColorProviderFactory; import io.jenkins.plugins.coverage.model.visualization.colorization.CoverageLevel; import static org.assertj.core.api.Assertions.*; @@ -21,7 +22,7 @@ */ class TreeMapNodeConverterTest extends AbstractCoverageTest { - private static final ColorProvider COLOR_PROVIDER = new ColorProvider(); + private static final ColorProvider COLOR_PROVIDER = ColorProviderFactory.createDefaultColorProvider(); @Test void shouldConvertCodingStyleToTree() { @@ -32,16 +33,16 @@ void shouldConvertCodingStyleToTree() { final double coveredLines = 294.0; final double coveredPercentage = coveredLines / totalLines * 100.0; - TreeMapNode root = new TreeMapNodeConverter(COLOR_PROVIDER).toTeeChartModel(tree, CoverageMetric.LINE); + TreeMapNode root = new TreeMapNodeConverter().toTeeChartModel(tree, CoverageMetric.LINE, COLOR_PROVIDER); assertThat(root.getName()).isEqualTo("Java coding style: jacoco-codingstyle.xml"); assertThat(root.getValue()).containsExactly(totalLines, coveredLines); - assertThat(root.getItemStyle().getColor()).isEqualTo(getNodeColorAsHex(coveredPercentage)); + assertThat(root.getItemStyle().getColor()).isEqualTo(getNodeColorAsRGBHex(coveredPercentage)); assertThat(root.getChildren()).hasSize(1).element(0).satisfies( node -> { assertThat(node.getName()).isEqualTo("edu.hm.hafner.util"); assertThat(node.getValue()).containsExactly(totalLines, coveredLines); - assertThat(root.getItemStyle().getColor()).isEqualTo(getNodeColorAsHex(coveredPercentage)); + assertThat(root.getItemStyle().getColor()).isEqualTo(getNodeColorAsRGBHex(coveredPercentage)); } ); } @@ -51,7 +52,7 @@ void shouldConvertAnalysisModelToTree() { CoverageNode tree = readNode(Paths.get("..", "..", "jacoco-analysis-model.xml").toString()); tree.splitPackages(); - TreeMapNode root = new TreeMapNodeConverter(COLOR_PROVIDER).toTeeChartModel(tree, CoverageMetric.LINE); + TreeMapNode root = new TreeMapNodeConverter().toTeeChartModel(tree, CoverageMetric.LINE, COLOR_PROVIDER); final double totalLines = 6368.0; final double coveredLines = 6083.0; @@ -59,12 +60,12 @@ void shouldConvertAnalysisModelToTree() { assertThat(root.getName()).isEqualTo("Static Analysis Model and Parsers: jacoco-analysis-model.xml"); assertThat(root.getValue()).containsExactly(totalLines, coveredLines); - assertThat(root.getItemStyle().getColor()).isEqualTo(getNodeColorAsHex(coveredPercentage)); + assertThat(root.getItemStyle().getColor()).isEqualTo(getNodeColorAsRGBHex(coveredPercentage)); assertThat(root.getChildren()).hasSize(1).element(0).satisfies( node -> { assertThat(node.getName()).isEqualTo("edu.hm.hafner"); assertThat(node.getValue()).containsExactly(totalLines, coveredLines); - assertThat(node.getItemStyle().getColor()).isEqualTo(getNodeColorAsHex(coveredPercentage)); + assertThat(node.getItemStyle().getColor()).isEqualTo(getNodeColorAsRGBHex(coveredPercentage)); } ); } @@ -77,9 +78,9 @@ void shouldConvertAnalysisModelToTree() { * * @return the fill color as a hex string */ - private String getNodeColorAsHex(final Double coveredPercentage) { + private String getNodeColorAsRGBHex(final Double coveredPercentage) { return CoverageLevel .getDisplayColorsOfCoverageLevel(coveredPercentage, COLOR_PROVIDER) - .getFillColorAsHex(); + .getFillColorAsRGBHex(); } }