Skip to content

Commit

Permalink
Merge pull request #231 from reportportal/develop
Browse files Browse the repository at this point in the history
Release
  • Loading branch information
HardNorth committed Jan 26, 2024
2 parents 0b35235 + 725ecaf commit ab72b73
Show file tree
Hide file tree
Showing 6 changed files with 156 additions and 31 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# Changelog

## [Unreleased]
### Added
- Table size limit in `MarkdownUtils.formatDataTable(List, int)` method, by @HardNorth
- `MarkdownUtils.formatDataTable(Map<String, String>)` method, by @HardNorth

## [5.2.1]
### Added
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@
*/
package com.epam.reportportal.utils.markdown;

import org.apache.commons.lang3.tuple.Pair;

import javax.annotation.Nonnull;
import java.util.Iterator;
import java.util.List;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

Expand All @@ -36,6 +37,9 @@ public class MarkdownUtils {
public static final String TABLE_INDENT = "\u00A0\u00A0\u00A0\u00A0";
public static final String TABLE_COLUMN_SEPARATOR = "|";
public static final String TABLE_ROW_SEPARATOR = "-";
public static final String TRUNCATION_REPLACEMENT = "...";
public static final int PADDING_SPACES_NUM = 2;
public static final int MAX_TABLE_SIZE = 83;

/**
* Adds special prefix to make log message being processed as markdown
Expand All @@ -55,35 +59,68 @@ public static String asMarkdown(String message) {
* @return Message to be sent to ReportPortal
*/
public static String asCode(String language, String script) {
return asMarkdown("```" + ofNullable(language).orElse("") + NEW_LINE +
script + NEW_LINE +
"```");
return asMarkdown("```" + ofNullable(language).orElse("") + NEW_LINE + script + NEW_LINE + "```");
}

@Nonnull
private static List<Integer> calculateColSizes(@Nonnull List<Integer> colSizes, int maxTableSize) {
int colTableSize = colSizes.stream().reduce(Integer::sum).orElse(-1);
colTableSize += (PADDING_SPACES_NUM + TABLE_COLUMN_SEPARATOR.length()) * colSizes.size() - 1; // Inner columns grid
colTableSize += 2; // Outer table grid
if (maxTableSize >= colTableSize) {
return colSizes;
}
List<Pair<Integer, Integer>> colsBySize = IntStream.range(0, colSizes.size())
.mapToObj(i -> Pair.of(colSizes.get(i), i))
.sorted()
.collect(Collectors.toList());
Collections.reverse(colsBySize);
int sizeToShrink = colTableSize - maxTableSize;
for (int i = 0; i < sizeToShrink; i++) {
for (int j = 0; j < colsBySize.size(); j++) {
Pair<Integer, Integer> currentCol = colsBySize.get(j);
Pair<Integer, Integer> nextCol = colsBySize.size() > j + 1 ? colsBySize.get(j + 1) : Pair.of(0, 0);
if (currentCol.getKey() >= nextCol.getKey()) {
colsBySize.set(j, Pair.of(currentCol.getKey() - 1, currentCol.getValue()));
break;
}
}
}
List<Integer> result = new ArrayList<>(colSizes);
colsBySize.forEach(col -> result.set(col.getValue(), col.getKey()));
return result;
}

/**
* Converts a table represented as List of Lists to a formatted table string
* Converts a table represented as List of Lists to a formatted table string.
*
* @param table a table object
* @param table a table object
* @param maxTableSize maximum size in characters of result table, cells will be truncated
* @return string representation of the table
*/
@Nonnull
public static String formatDataTable(@Nonnull final List<List<String>> table) {
public static String formatDataTable(@Nonnull final List<List<String>> table, int maxTableSize) {
StringBuilder result = new StringBuilder();
int tableLength = table.stream().mapToInt(List::size).max().orElse(-1);
int tableColNum = table.stream().mapToInt(List::size).max().orElse(-1);
List<Iterator<String>> iterList = table.stream().map(List::iterator).collect(Collectors.toList());
List<Integer> colSizes = IntStream.range(0, tableLength)
List<Integer> colSizes = IntStream.range(0, tableColNum)
.mapToObj(n -> iterList.stream().filter(Iterator::hasNext).map(Iterator::next).collect(Collectors.toList()))
.map(col -> col.stream().mapToInt(String::length).max().orElse(0))
.collect(Collectors.toList());
colSizes = calculateColSizes(colSizes, maxTableSize);

boolean header = true;
for (List<String> row : table) {
result.append(TABLE_INDENT).append(TABLE_COLUMN_SEPARATOR);
for (int i = 0; i < row.size(); i++) {
String cell = row.get(i);
int maxSize = colSizes.get(i) - cell.length() + 2;
int lSpace = maxSize / 2;
int rSpace = maxSize - lSpace;
int colSize = colSizes.get(i);
if (colSize < cell.length()) {
cell = cell.substring(0, colSize - TRUNCATION_REPLACEMENT.length()) + TRUNCATION_REPLACEMENT;
}
int padSize = colSize - cell.length() + PADDING_SPACES_NUM;
int lSpace = padSize / 2;
int rSpace = padSize - lSpace;
IntStream.range(0, lSpace).forEach(j -> result.append(ONE_SPACE));
result.append(cell);
IntStream.range(0, rSpace).forEach(j -> result.append(ONE_SPACE));
Expand All @@ -103,4 +140,30 @@ public static String formatDataTable(@Nonnull final List<List<String>> table) {
}
return result.toString().trim();
}

/**
* Converts a table represented as List of Lists to a formatted table string.
*
* @param table a table object
* @return string representation of the table
*/
@Nonnull
public static String formatDataTable(@Nonnull final List<List<String>> table) {
return formatDataTable(table, MAX_TABLE_SIZE);
}

/**
* Converts a table represented as Map to a formatted table string.
*
* @param table a table object
* @return string representation of the table
*/
@Nonnull
public static String formatDataTable(@Nonnull final Map<String, String> table) {
List<List<String>> toFormat = new ArrayList<>();
List<String> keys = new ArrayList<>(table.keySet());
toFormat.add(keys);
toFormat.add(keys.stream().map(table::get).collect(Collectors.toList()));
return formatDataTable(toFormat);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public AccessibleMethod method(@Nonnull Method m) {
* @param m method to access
* @param parameterTypes an array of specific parameters to distinguish the method
* @return decorator instance
* @throws NoSuchMethodException no such method found
* @throws NoSuchMethodException no method with such name found
*/
@Nonnull
public AccessibleMethod method(@Nonnull String m, @Nullable Class<?>... parameterTypes) throws NoSuchMethodException {
Expand All @@ -78,6 +78,7 @@ public AccessibleField field(@Nonnull Field f) {
*
* @param name field to access
* @return decorator instance
* @throws NoSuchFieldException no field with such name found
*/
@Nonnull
public AccessibleField field(@Nonnull String name) throws NoSuchFieldException {
Expand All @@ -88,6 +89,7 @@ public AccessibleField field(@Nonnull String name) throws NoSuchFieldException {
* Create the decorator for given object.
*
* @param object an instance of object on which you need reflective access
* @return decorator instance
*/
@Nonnull
public static Accessible on(@Nonnull Object object) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ public void setValue(@Nullable Object value) {

/**
* Return given field value.
*
* @return field value
*/
@Nullable
public Object getValue() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ public class AccessibleMethod {
* Set given method.
*
* @param args arguments to pass to the method
* @return method call result
* @throws Throwable a throwable which was thrown during the method execution
*/
@Nullable
public Object invoke(@Nullable Object... args) throws Throwable {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,25 +17,26 @@

import org.junit.jupiter.api.Test;

import java.util.Arrays;
import java.util.List;
import java.util.*;

import static com.epam.reportportal.utils.markdown.MarkdownUtils.asCode;
import static com.epam.reportportal.utils.markdown.MarkdownUtils.formatDataTable;
import static com.epam.reportportal.utils.markdown.MarkdownUtils.*;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;

/**
* @author Andrei Varabyeu
*/
public class MarkdownUtilsTest {
//@formatter:off
public static final String ONE_ROW_EXPECTED_TABLE =
TABLE_INDENT + "|\u00A0var_a\u00A0|\u00A0var_b\u00A0|\u00A0result\u00A0|\n"
+ TABLE_INDENT + "|-------|-------|--------|\n"
+ TABLE_INDENT + "|\u00A0\u00A0\u00A02\u00A0\u00A0\u00A0|\u00A0\u00A0\u00A02\u00A0\u00A0\u00A0|\u00A0\u00A0\u00A04\u00A0\u00A0\u00A0\u00A0|";

public static final String ONE_ROW_EXPECTED_TABLE = "\u00A0\u00A0\u00A0\u00A0|\u00A0var_a\u00A0|\u00A0var_b\u00A0|\u00A0result\u00A0|\n"
+ "\u00A0\u00A0\u00A0\u00A0|-------|-------|--------|\n"
+ "\u00A0\u00A0\u00A0\u00A0|\u00A0\u00A0\u00A02\u00A0\u00A0\u00A0|\u00A0\u00A0\u00A02\u00A0\u00A0\u00A0|\u00A0\u00A0\u00A04\u00A0\u00A0\u00A0\u00A0|";

public static final String TWO_ROWS_EXPECTED_TABLE = ONE_ROW_EXPECTED_TABLE + "\n"
+ "\u00A0\u00A0\u00A0\u00A0|\u00A0\u00A0\u00A01\u00A0\u00A0\u00A0|\u00A0\u00A0\u00A02\u00A0\u00A0\u00A0|\u00A0\u00A0\u00A03\u00A0\u00A0\u00A0\u00A0|";
public static final String TWO_ROWS_EXPECTED_TABLE =
ONE_ROW_EXPECTED_TABLE
+ "\n" + TABLE_INDENT + "|\u00A0\u00A0\u00A01\u00A0\u00A0\u00A0|\u00A0\u00A0\u00A02\u00A0\u00A0\u00A0|\u00A0\u00A0\u00A03\u00A0\u00A0\u00A0\u00A0|";
//@formatter:on

@Test
public void asMarkdown() {
Expand All @@ -47,16 +48,68 @@ public void toMarkdownScript() {
assertThat("Incorrect markdown prefix", asCode("groovy", "hello"), equalTo("!!!MARKDOWN_MODE!!!```groovy\nhello\n```"));
}


@Test
public void test_format_data_table() {
List<List<String>> table =
Arrays.asList(
Arrays.asList("var_a", "var_b", "result"),
Arrays.asList("2", "2", "4"),
Arrays.asList("1", "2", "3")
);
List<List<String>> table = Arrays.asList(Arrays.asList("var_a", "var_b", "result"),
Arrays.asList("2", "2", "4"),
Arrays.asList("1", "2", "3")
);

assertThat(formatDataTable(table), equalTo(TWO_ROWS_EXPECTED_TABLE));
}

public static final String BIG_COLUMN_VALUE = "4CZtyV3qsjVX08vBKu5YpvY2ckoFxLUombHEj1yf4uaBmrSGTzcXvlfba52HtUGLm56a8Vx4fBa0onEjlXY";
public static final String TRUNCATED_COLUMN_VALUE = "4CZtyV3qsjVX08vBKu5YpvY2ckoFxLUombHEj1yf4uaBmrSGTzcXvlfba52...";

//@formatter:off
public static final String ONE_ROW_LONG_EXPECTED_TABLE =
TABLE_INDENT + "|\u00A0var_a\u00A0|\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0var_b\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0|\u00A0result\u00A0|\n"
+ TABLE_INDENT + "|-------|----------------------------------------------------------------|--------|\n"
+ TABLE_INDENT + "|\u00A0\u00A0\u00A02\u00A0\u00A0\u00A0|\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A02\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0|\u00A0\u00A0\u00A04\u00A0\u00A0\u00A0\u00A0|";

public static final String TWO_ROWS_LONG_EXPECTED_TABLE =
ONE_ROW_LONG_EXPECTED_TABLE + "\n"
+ TABLE_INDENT + "|\u00A0\u00A0\u00A01\u00A0\u00A0\u00A0|\u00A0" + TRUNCATED_COLUMN_VALUE + "\u00A0|\u00A0\u00A0\u00A03\u00A0\u00A0\u00A0\u00A0|";
//@formatter:on

@Test
public void test_format_data_table_one_big_col() {
List<List<String>> table = Arrays.asList(Arrays.asList("var_a", "var_b", "result"),
Arrays.asList("2", "2", "4"),
Arrays.asList("1", BIG_COLUMN_VALUE, "3")
);
assertThat(formatDataTable(table), equalTo(TWO_ROWS_LONG_EXPECTED_TABLE));
}

public static final String SECOND_TRUNCATED_COLUMN_VALUE = "4CZtyV3qsjVX08vBKu5YpvY2ckoFxLU...";

//@formatter:off
public static final String ONE_ROW_LONG_EXPECTED_TABLE_TWO =
TABLE_INDENT + "|\u00A0var_a\u00A0|\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0var_b\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0|\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0result\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0|\n"
+ TABLE_INDENT + "|-------|------------------------------------|------------------------------------|\n"
+ TABLE_INDENT + "|\u00A0\u00A0\u00A02\u00A0\u00A0\u00A0|\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A02\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0|\u00A0" + SECOND_TRUNCATED_COLUMN_VALUE + "\u00A0|";

public static final String TWO_ROWS_LONG_EXPECTED_TABLE_TWO =
ONE_ROW_LONG_EXPECTED_TABLE_TWO + "\n"
+ TABLE_INDENT + "|\u00A0\u00A0\u00A01\u00A0\u00A0\u00A0|\u00A0" + SECOND_TRUNCATED_COLUMN_VALUE + "\u00A0|\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A03\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0|";
//@formatter:on

@Test
public void test_format_data_table_two_big_col() {
List<List<String>> table = Arrays.asList(Arrays.asList("var_a", "var_b", "result"),
Arrays.asList("2", "2", BIG_COLUMN_VALUE),
Arrays.asList("1", BIG_COLUMN_VALUE, "3")
);
assertThat(formatDataTable(table), equalTo(TWO_ROWS_LONG_EXPECTED_TABLE_TWO));
}

@Test
public void test_format_data_table_map() {
Map<String, String> table = new LinkedHashMap<String, String>(){{
put("var_a", "2");
put("var_b", "2");
put("result", "4");
}};
assertThat(formatDataTable(table), equalTo(ONE_ROW_EXPECTED_TABLE));
}
}

0 comments on commit ab72b73

Please sign in to comment.