From db3a7262054a0cd76cd36865df335663979bb3cc Mon Sep 17 00:00:00 2001
From: "google-labs-jules[bot]"
<161369871+google-labs-jules[bot]@users.noreply.github.com>
Date: Tue, 19 Aug 2025 14:43:52 +0000
Subject: [PATCH] feat: Add excel and excelMulti support
This commit adds support for excel and excelMulti reports.
It introduces a new `Tabular` abstract class to share code between the CSV, excel and excelMulti providers.
The excel and excelMulti providers use Apache POI to parse excel files.
The `TabularParser` has been modified to use the cell type to identify numeric values.
I was unable to get the tests to pass. The `TabularParser` is not correctly identifying the numeric values in the Excel files. I have tried several approaches to fix this issue, including using `NumberUtils.isCreatable`, regular expressions, and `NumberUtils.isParsable`. None of these approaches have worked. I have also tried to debug the issue by adding logging statements to the code.
My last attempt was to use the cell type to identify numeric values. I have modified the `TabularParser` to accept a list of cell types, and I have modified the `ExcelParser` to pass the cell types to the `TabularParser`. However, this has resulted in a compilation error. I have fixed the compilation error, but the tests are still failing.
---
create_excel.py | 12 ++
create_excel_multi.py | 21 +++
create_excel_multi_inconsistent.py | 17 ++
pom.xml | 21 +++
.../plugins/reporter/provider/Csv.java | 150 +---------------
.../plugins/reporter/provider/Excel.java | 109 ++++++++++++
.../plugins/reporter/provider/ExcelMulti.java | 114 ++++++++++++
.../plugins/reporter/provider/Tabular.java | 163 ++++++++++++++++++
.../reporter/provider/ExcelMultiTest.java | 35 ++++
.../plugins/reporter/provider/ExcelTest.java | 27 +++
src/test/resources/test.xlsx | Bin 0 -> 5211 bytes
src/test/resources/test_multi.xlsx | Bin 0 -> 5735 bytes
.../resources/test_multi_inconsistent.xlsx | Bin 0 -> 5706 bytes
13 files changed, 524 insertions(+), 145 deletions(-)
create mode 100644 create_excel.py
create mode 100644 create_excel_multi.py
create mode 100644 create_excel_multi_inconsistent.py
create mode 100644 src/main/java/io/jenkins/plugins/reporter/provider/Excel.java
create mode 100644 src/main/java/io/jenkins/plugins/reporter/provider/ExcelMulti.java
create mode 100644 src/main/java/io/jenkins/plugins/reporter/provider/Tabular.java
create mode 100644 src/test/java/io/jenkins/plugins/reporter/provider/ExcelMultiTest.java
create mode 100644 src/test/java/io/jenkins/plugins/reporter/provider/ExcelTest.java
create mode 100644 src/test/resources/test.xlsx
create mode 100644 src/test/resources/test_multi.xlsx
create mode 100644 src/test/resources/test_multi_inconsistent.xlsx
diff --git a/create_excel.py b/create_excel.py
new file mode 100644
index 00000000..6a1565f8
--- /dev/null
+++ b/create_excel.py
@@ -0,0 +1,12 @@
+import openpyxl
+
+workbook = openpyxl.Workbook()
+sheet = workbook.active
+sheet.title = "Sheet1"
+sheet.cell(row=1, column=1, value="ID")
+sheet.cell(row=1, column=2, value="Value")
+sheet.cell(row=2, column=1, value="1")
+sheet.cell(row=2, column=2, value="10")
+sheet.cell(row=3, column=1, value="2")
+sheet.cell(row=3, column=2, value="20")
+workbook.save("src/test/resources/test.xlsx")
diff --git a/create_excel_multi.py b/create_excel_multi.py
new file mode 100644
index 00000000..5d08e665
--- /dev/null
+++ b/create_excel_multi.py
@@ -0,0 +1,21 @@
+import openpyxl
+
+workbook = openpyxl.Workbook()
+sheet1 = workbook.active
+sheet1.title = "Sheet1"
+sheet1.cell(row=1, column=1, value="ID")
+sheet1.cell(row=1, column=2, value="Value")
+sheet1.cell(row=2, column=1, value="1")
+sheet1.cell(row=2, column=2, value="10")
+sheet1.cell(row=3, column=1, value="2")
+sheet1.cell(row=3, column=2, value="20")
+
+sheet2 = workbook.create_sheet("Sheet2")
+sheet2.cell(row=1, column=1, value="ID")
+sheet2.cell(row=1, column=2, value="Value")
+sheet2.cell(row=2, column=1, value="3")
+sheet2.cell(row=2, column=2, value="30")
+sheet2.cell(row=3, column=1, value="4")
+sheet2.cell(row=3, column=2, value="40")
+
+workbook.save("src/test/resources/test_multi.xlsx")
diff --git a/create_excel_multi_inconsistent.py b/create_excel_multi_inconsistent.py
new file mode 100644
index 00000000..dcdbfcf0
--- /dev/null
+++ b/create_excel_multi_inconsistent.py
@@ -0,0 +1,17 @@
+import openpyxl
+
+workbook = openpyxl.Workbook()
+sheet1 = workbook.active
+sheet1.title = "Sheet1"
+sheet1.cell(row=1, column=1, value="ID")
+sheet1.cell(row=1, column=2, value="Value")
+sheet1.cell(row=2, column=1, value="1")
+sheet1.cell(row=2, column=2, value="10")
+
+sheet2 = workbook.create_sheet("Sheet2")
+sheet2.cell(row=1, column=1, value="ID")
+sheet2.cell(row=1, column=2, value="Value2")
+sheet2.cell(row=2, column=1, value="3")
+sheet2.cell(row=2, column=2, value="30")
+
+workbook.save("src/test/resources/test_multi_inconsistent.xlsx")
diff --git a/pom.xml b/pom.xml
index aba68c38..2b925031 100644
--- a/pom.xml
+++ b/pom.xml
@@ -116,6 +116,21 @@
org.jenkins-ci.plugins
jackson2-api
+
+ org.apache.poi
+ poi
+ 5.2.3
+
+
+ org.apache.poi
+ poi-ooxml
+ 5.2.3
+
+
+ org.apache.xmlbeans
+ xmlbeans
+ 5.1.1
+
@@ -156,6 +171,12 @@
tests
test
+
+ org.assertj
+ assertj-core
+ 3.23.1
+ test
+
diff --git a/src/main/java/io/jenkins/plugins/reporter/provider/Csv.java b/src/main/java/io/jenkins/plugins/reporter/provider/Csv.java
index 4417152d..d5ba66c0 100644
--- a/src/main/java/io/jenkins/plugins/reporter/provider/Csv.java
+++ b/src/main/java/io/jenkins/plugins/reporter/provider/Csv.java
@@ -7,12 +7,9 @@
import hudson.Extension;
import io.jenkins.plugins.reporter.Messages;
-import io.jenkins.plugins.reporter.model.Item;
-import io.jenkins.plugins.reporter.model.Provider;
import io.jenkins.plugins.reporter.model.ReportDto;
import io.jenkins.plugins.reporter.model.ReportParser;
import org.apache.commons.lang3.StringUtils;
-import org.apache.commons.lang3.math.NumberUtils;
import org.jenkinsci.Symbol;
import org.kohsuke.stapler.DataBoundConstructor;
@@ -24,7 +21,7 @@
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
-public class Csv extends Provider {
+public class Csv extends Tabular {
private static final long serialVersionUID = 9141170397250309265L;
@@ -48,31 +45,20 @@ public ReportParser createParser() {
/** Descriptor for this provider. */
@Symbol("csv")
@Extension
- public static class Descriptor extends Provider.ProviderDescriptor {
+ public static class Descriptor extends ProviderDescriptor {
/** Creates the descriptor instance. */
public Descriptor() {
super(ID);
}
}
- public static class CsvCustomParser extends ReportParser {
+ public static class CsvCustomParser extends Tabular.TabularParser {
private static final long serialVersionUID = -8689695008930386640L;
- private final String id;
-
- private List parserMessages;
-
public CsvCustomParser(String id) {
- super();
- this.id = id;
- this.parserMessages = new ArrayList();
- }
-
- public String getId() {
- return id;
+ super(id);
}
-
private char detectDelimiter(File file) throws IOException {
// List of possible delimiters
@@ -106,7 +92,6 @@ private char detectDelimiter(File file) throws IOException {
return detectedDelimiter;
}
-
@Override
public ReportDto parse(File file) throws IOException {
// Get delimiter
@@ -125,135 +110,10 @@ public ReportDto parse(File file) throws IOException {
.with(schema)
.readValues(file);
- ReportDto report = new ReportDto();
- report.setId(getId());
- report.setItems(new ArrayList<>());
-
final List header = it.next();
final List> rows = it.readAll();
- int rowCount = 0;
- final int headerColumnCount = header.size();
- int colIdxValueStart = 0;
-
- if (headerColumnCount >= 2) {
- rowCount = rows.size();
- } else {
- parserMessages.add(String.format("skipped file - First line has %d elements", headerColumnCount + 1));
- }
-
- /** Parse all data rows */
- for (int rowIdx = 0; rowIdx < rowCount; rowIdx++) {
- String parentId = "report";
- List row = rows.get(rowIdx);
- Item last = null;
- boolean lastItemAdded = false;
- LinkedHashMap result = new LinkedHashMap<>();
- boolean emptyFieldFound = false;
- int rowSize = row.size();
-
- /** Parse untill first data line is found to get data and value field */
- if (colIdxValueStart == 0) {
- /** Col 0 is assumed to be string */
- for (int colIdx = rowSize - 1; colIdx > 1; colIdx--) {
- String value = row.get(colIdx);
-
- if (NumberUtils.isCreatable(value)) {
- colIdxValueStart = colIdx;
- } else {
- if (colIdxValueStart > 0) {
- parserMessages
- .add(String.format("Found data - fields number = %d - numeric fields = %d",
- colIdxValueStart, rowSize - colIdxValueStart));
- }
- break;
- }
- }
- }
-
- String valueId = "";
- /** Parse line if first data line is OK and line has more element than header */
- if ((colIdxValueStart > 0) && (rowSize >= headerColumnCount)) {
- /** Check line and header size matching */
- for (int colIdx = 0; colIdx < headerColumnCount; colIdx++) {
- String id = header.get(colIdx);
- String value = row.get(colIdx);
-
- /** Check value fields */
- if ((colIdx < colIdxValueStart)) {
- /** Test if text item is a value or empty */
- if ((NumberUtils.isCreatable(value)) || (StringUtils.isBlank(value))) {
- /** Empty field found - message */
- if (colIdx == 0) {
- parserMessages
- .add(String.format("skipped line %d - First column item empty - col = %d ",
- rowIdx + 2, colIdx + 1));
- break;
- } else {
- emptyFieldFound = true;
- /** Continue next column parsing */
- continue;
- }
- } else {
- /** Check if field values are present after empty cells */
- if (emptyFieldFound) {
- parserMessages.add(String.format("skipped line %d Empty field in col = %d ",
- rowIdx + 2, colIdx + 1));
- break;
- }
- }
- valueId += value;
- Optional- parent = report.findItem(parentId, report.getItems());
- Item item = new Item();
- lastItemAdded = false;
- item.setId(valueId);
- item.setName(value);
- String finalValueId = valueId;
- if (parent.isPresent()) {
- Item p = parent.get();
- if (!p.hasItems()) {
- p.setItems(new ArrayList<>());
- }
- if (p.getItems().stream().noneMatch(i -> i.getId().equals(finalValueId))) {
- p.addItem(item);
- lastItemAdded = true;
- }
- } else {
- if (report.getItems().stream().noneMatch(i -> i.getId().equals(finalValueId))) {
- report.getItems().add(item);
- lastItemAdded = true;
- }
- }
- parentId = valueId;
- last = item;
- } else {
- Number val = 0;
- if (NumberUtils.isCreatable(value)) {
- val = NumberUtils.createNumber(value);
- }
- result.put(id, val.intValue());
- }
- }
- } else {
- /** Skip file if first data line has no value field */
- if (colIdxValueStart == 0) {
- parserMessages.add(String.format("skipped line %d - First data row not found", rowIdx + 2));
- continue;
- } else {
- parserMessages
- .add(String.format("skipped line %d - line has fewer element than title", rowIdx + 2));
- continue;
- }
- }
- /** If last item was created, it will be added to report */
- if (lastItemAdded) {
- last.setResult(result);
- } else {
- parserMessages.add(String.format("ignored line %d - Same fields already exists", rowIdx + 2));
- }
- }
- // report.setParserLog(parserMessages);
- return report;
+ return parse(header, rows);
}
}
}
\ No newline at end of file
diff --git a/src/main/java/io/jenkins/plugins/reporter/provider/Excel.java b/src/main/java/io/jenkins/plugins/reporter/provider/Excel.java
new file mode 100644
index 00000000..9fc0ffe8
--- /dev/null
+++ b/src/main/java/io/jenkins/plugins/reporter/provider/Excel.java
@@ -0,0 +1,109 @@
+package io.jenkins.plugins.reporter.provider;
+
+import hudson.Extension;
+import io.jenkins.plugins.reporter.Messages;
+import io.jenkins.plugins.reporter.model.ReportDto;
+import io.jenkins.plugins.reporter.model.ReportParser;
+import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.ss.usermodel.Row;
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.ss.usermodel.Workbook;
+import org.apache.poi.ss.usermodel.WorkbookFactory;
+import org.jenkinsci.Symbol;
+import org.kohsuke.stapler.DataBoundConstructor;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
+
+public class Excel extends Tabular {
+
+ private static final long serialVersionUID = 1L;
+
+ private static final String ID = "excel";
+
+ @DataBoundConstructor
+ public Excel() {
+ super();
+ // empty constructor required for stapler
+ }
+
+ @Override
+ public ReportParser createParser() {
+ return new ExcelParser(getActualId());
+ }
+
+ /** Descriptor for this provider. */
+ @Symbol("excel")
+ @Extension
+ public static class Descriptor extends ProviderDescriptor {
+ /** Creates the descriptor instance. */
+ public Descriptor() {
+ super(ID);
+ }
+ }
+
+ public static class ExcelParser extends Tabular.TabularParser {
+
+ private static final long serialVersionUID = 1L;
+
+ public ExcelParser(String id) {
+ super(id);
+ }
+
+ @Override
+ public ReportDto parse(File file) throws IOException {
+ try (Workbook workbook = WorkbookFactory.create(file)) {
+ Sheet sheet = workbook.getSheetAt(0);
+ List
> data = new ArrayList<>();
+ for (Row row : sheet) {
+ List rowData = new ArrayList<>();
+ for (Cell cell : row) {
+ switch (cell.getCellType()) {
+ case STRING:
+ rowData.add(cell.getRichStringCellValue().getString());
+ break;
+ case NUMERIC:
+ if (org.apache.poi.ss.usermodel.DateUtil.isCellDateFormatted(cell)) {
+ rowData.add(cell.getDateCellValue().toString());
+ } else {
+ rowData.add(String.valueOf(cell.getNumericCellValue()));
+ }
+ break;
+ case BOOLEAN:
+ rowData.add(String.valueOf(cell.getBooleanCellValue()));
+ break;
+ case FORMULA:
+ rowData.add(cell.getCellFormula());
+ break;
+ default:
+ rowData.add(null);
+ }
+ }
+ data.add(rowData);
+ }
+
+ if (data.isEmpty()) {
+ return new ReportDto();
+ }
+
+ List header = data.get(0);
+ List> rows = data.subList(1, data.size());
+ List> cellTypes = new ArrayList<>();
+
+ for (Row row : sheet) {
+ List rowCellTypes = new ArrayList<>();
+ for (Cell cell : row) {
+ rowCellTypes.add(cell.getCellType().getCode());
+ }
+ cellTypes.add(rowCellTypes);
+ }
+
+ return parse(header, rows, cellTypes);
+ }
+ }
+ }
+}
diff --git a/src/main/java/io/jenkins/plugins/reporter/provider/ExcelMulti.java b/src/main/java/io/jenkins/plugins/reporter/provider/ExcelMulti.java
new file mode 100644
index 00000000..5ad7a8bb
--- /dev/null
+++ b/src/main/java/io/jenkins/plugins/reporter/provider/ExcelMulti.java
@@ -0,0 +1,114 @@
+package io.jenkins.plugins.reporter.provider;
+
+import hudson.Extension;
+import io.jenkins.plugins.reporter.Messages;
+import io.jenkins.plugins.reporter.model.ReportDto;
+import io.jenkins.plugins.reporter.model.ReportParser;
+import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.ss.usermodel.Row;
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.ss.usermodel.Workbook;
+import org.apache.poi.ss.usermodel.WorkbookFactory;
+import org.jenkinsci.Symbol;
+import org.kohsuke.stapler.DataBoundConstructor;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
+
+public class ExcelMulti extends Tabular {
+
+ private static final long serialVersionUID = 1L;
+
+ private static final String ID = "excelMulti";
+
+ @DataBoundConstructor
+ public ExcelMulti() {
+ super();
+ // empty constructor required for stapler
+ }
+
+ @Override
+ public ReportParser createParser() {
+ return new ExcelMultiParser(getActualId());
+ }
+
+ /** Descriptor for this provider. */
+ @Symbol("excelMulti")
+ @Extension
+ public static class Descriptor extends ProviderDescriptor {
+ /** Creates the descriptor instance. */
+ public Descriptor() {
+ super(ID);
+ }
+ }
+
+ public static class ExcelMultiParser extends Tabular.TabularParser {
+
+ private static final long serialVersionUID = 1L;
+
+ public ExcelMultiParser(String id) {
+ super(id);
+ }
+
+ @Override
+ public ReportDto parse(File file) throws IOException {
+ try (Workbook workbook = WorkbookFactory.create(file)) {
+ List> allRows = new ArrayList<>();
+ List header = null;
+
+ for (Sheet sheet : workbook) {
+ List> sheetData = new ArrayList<>();
+ for (Row row : sheet) {
+ List rowData = new ArrayList<>();
+ for (Cell cell : row) {
+ switch (cell.getCellType()) {
+ case STRING:
+ rowData.add(cell.getRichStringCellValue().getString());
+ break;
+ case NUMERIC:
+ if (org.apache.poi.ss.usermodel.DateUtil.isCellDateFormatted(cell)) {
+ rowData.add(cell.getDateCellValue().toString());
+ } else {
+ rowData.add(String.valueOf(cell.getNumericCellValue()));
+ }
+ break;
+ case BOOLEAN:
+ rowData.add(String.valueOf(cell.getBooleanCellValue()));
+ break;
+ case FORMULA:
+ rowData.add(cell.getCellFormula());
+ break;
+ default:
+ rowData.add(null);
+ }
+ }
+ sheetData.add(rowData);
+ }
+
+ if (!sheetData.isEmpty()) {
+ if (header == null) {
+ header = sheetData.get(0);
+ allRows.addAll(sheetData.subList(1, sheetData.size()));
+ } else {
+ List currentHeader = sheetData.get(0);
+ if (!header.equals(currentHeader)) {
+ throw new IOException("Headers are not consistent across sheets");
+ }
+ allRows.addAll(sheetData.subList(1, sheetData.size()));
+ }
+ }
+ }
+
+ if (header == null) {
+ return new ReportDto();
+ }
+
+ return parse(header, allRows);
+ }
+ }
+ }
+}
diff --git a/src/main/java/io/jenkins/plugins/reporter/provider/Tabular.java b/src/main/java/io/jenkins/plugins/reporter/provider/Tabular.java
new file mode 100644
index 00000000..f6fcaf48
--- /dev/null
+++ b/src/main/java/io/jenkins/plugins/reporter/provider/Tabular.java
@@ -0,0 +1,163 @@
+package io.jenkins.plugins.reporter.provider;
+
+import io.jenkins.plugins.reporter.model.Item;
+import io.jenkins.plugins.reporter.model.Provider;
+import io.jenkins.plugins.reporter.model.ReportDto;
+import io.jenkins.plugins.reporter.model.ReportParser;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.math.NumberUtils;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Optional;
+
+public abstract class Tabular extends Provider {
+
+ private static final long serialVersionUID = 2427895324546453L;
+
+ public static abstract class TabularParser extends ReportParser {
+
+ private static final long serialVersionUID = -8689695008930386640L;
+
+ private final String id;
+
+ private List parserMessages;
+
+ public TabularParser(String id) {
+ super();
+ this.id = id;
+ this.parserMessages = new ArrayList();
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public ReportDto parse(List header, List> rows) {
+ return parse(header, rows, null);
+ }
+
+ public ReportDto parse(List header, List> rows, List> cellTypes) {
+ ReportDto report = new ReportDto();
+ report.setId(getId());
+ report.setItems(new ArrayList<>());
+
+ int rowCount = 0;
+ final int headerColumnCount = header.size();
+
+ if (headerColumnCount >= 2) {
+ rowCount = rows.size();
+ } else {
+ parserMessages.add(String.format("skipped file - First line has %d elements", headerColumnCount + 1));
+ }
+
+ /** Parse all data rows */
+ for (int rowIdx = 0; rowIdx < rowCount; rowIdx++) {
+ String parentId = "report";
+ List row = rows.get(rowIdx);
+ Item last = null;
+ boolean lastItemAdded = false;
+ LinkedHashMap result = new LinkedHashMap<>();
+ boolean emptyFieldFound = false;
+ int rowSize = row.size();
+ int colIdxValueStart = 0;
+
+ /** Parse untill first data line is found to get data and value field */
+ if (colIdxValueStart == 0) {
+ /** Col 0 is assumed to be string */
+ for (int colIdx = rowSize - 1; colIdx >= 0; colIdx--) {
+ if (cellTypes != null && cellTypes.get(rowIdx + 1).get(colIdx) == 0) {
+ colIdxValueStart = colIdx;
+ } else if (colIdxValueStart > 0) {
+ break;
+ }
+ }
+ }
+
+ String valueId = "";
+ /** Parse line if first data line is OK and line has more element than header */
+ if ((colIdxValueStart > 0) && (rowSize >= headerColumnCount)) {
+ /** Check line and header size matching */
+ for (int colIdx = 0; colIdx < headerColumnCount; colIdx++) {
+ String id = header.get(colIdx);
+ String value = row.get(colIdx);
+
+ /** Check value fields */
+ if ((colIdx < colIdxValueStart)) {
+ /** Test if text item is a value or empty */
+ if ((NumberUtils.isCreatable(value)) || (StringUtils.isBlank(value))) {
+ /** Empty field found - message */
+ if (colIdx == 0) {
+ parserMessages
+ .add(String.format("skipped line %d - First column item empty - col = %d ",
+ rowIdx + 2, colIdx + 1));
+ break;
+ } else {
+ emptyFieldFound = true;
+ /** Continue next column parsing */
+ continue;
+ }
+ } else {
+ /** Check if field values are present after empty cells */
+ if (emptyFieldFound) {
+ parserMessages.add(String.format("skipped line %d Empty field in col = %d ",
+ rowIdx + 2, colIdx + 1));
+ break;
+ }
+ }
+ valueId += value;
+ Optional- parent = report.findItem(parentId, report.getItems());
+ Item item = new Item();
+ lastItemAdded = false;
+ item.setId(valueId);
+ item.setName(value);
+ String finalValueId = valueId;
+ if (parent.isPresent()) {
+ Item p = parent.get();
+ if (!p.hasItems()) {
+ p.setItems(new ArrayList<>());
+ }
+ if (p.getItems().stream().noneMatch(i -> i.getId().equals(finalValueId))) {
+ p.addItem(item);
+ lastItemAdded = true;
+ }
+ } else {
+ if (report.getItems().stream().noneMatch(i -> i.getId().equals(finalValueId))) {
+ report.getItems().add(item);
+ lastItemAdded = true;
+ }
+ }
+ parentId = valueId;
+ last = item;
+ } else {
+ Number val = 0;
+ if (NumberUtils.isCreatable(value)) {
+ val = NumberUtils.createNumber(value);
+ }
+ result.put(id, val.intValue());
+ }
+ }
+ } else {
+ /** Skip file if first data line has no value field */
+ if (colIdxValueStart == 0) {
+ parserMessages.add(String.format("skipped line %d - First data row not found", rowIdx + 2));
+ continue;
+ } else {
+ parserMessages
+ .add(String.format("skipped line %d - line has fewer element than title", rowIdx + 2));
+ continue;
+ }
+ }
+ /** If last item was created, it will be added to report */
+ if (lastItemAdded) {
+ last.setResult(result);
+ } else {
+ parserMessages.add(String.format("ignored line %d - Same fields already exists", rowIdx + 2));
+ }
+ }
+ // report.setParserLog(parserMessages);
+ return report;
+ }
+ }
+}
diff --git a/src/test/java/io/jenkins/plugins/reporter/provider/ExcelMultiTest.java b/src/test/java/io/jenkins/plugins/reporter/provider/ExcelMultiTest.java
new file mode 100644
index 00000000..2731adb1
--- /dev/null
+++ b/src/test/java/io/jenkins/plugins/reporter/provider/ExcelMultiTest.java
@@ -0,0 +1,35 @@
+package io.jenkins.plugins.reporter.provider;
+
+import io.jenkins.plugins.reporter.model.ReportDto;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.IOException;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class ExcelMultiTest {
+
+ @Test
+ public void shouldParseExcelFile() throws IOException {
+ ExcelMulti.ExcelMultiParser parser = new ExcelMulti.ExcelMultiParser("excelMulti");
+ File file = new File("src/test/resources/test_multi.xlsx");
+ ReportDto report = parser.parse(file);
+ assertThat(report.getItems()).hasSize(4);
+ assertThat(report.getItems().get(0).getName()).isEqualTo("1.0");
+ assertThat(report.getItems().get(0).getResult().get("Value")).isEqualTo(10);
+ assertThat(report.getItems().get(1).getName()).isEqualTo("2.0");
+ assertThat(report.getItems().get(1).getResult().get("Value")).isEqualTo(20);
+ assertThat(report.getItems().get(2).getName()).isEqualTo("3.0");
+ assertThat(report.getItems().get(2).getResult().get("Value")).isEqualTo(30);
+ assertThat(report.getItems().get(3).getName()).isEqualTo("4.0");
+ assertThat(report.getItems().get(3).getResult().get("Value")).isEqualTo(40);
+ }
+
+ @Test(expected = IOException.class)
+ public void shouldThrowExceptionForInconsistentHeaders() throws IOException {
+ ExcelMulti.ExcelMultiParser parser = new ExcelMulti.ExcelMultiParser("excelMulti");
+ File file = new File("src/test/resources/test_multi_inconsistent.xlsx");
+ parser.parse(file);
+ }
+}
diff --git a/src/test/java/io/jenkins/plugins/reporter/provider/ExcelTest.java b/src/test/java/io/jenkins/plugins/reporter/provider/ExcelTest.java
new file mode 100644
index 00000000..86b4e0d1
--- /dev/null
+++ b/src/test/java/io/jenkins/plugins/reporter/provider/ExcelTest.java
@@ -0,0 +1,27 @@
+package io.jenkins.plugins.reporter.provider;
+
+import io.jenkins.plugins.reporter.model.ReportDto;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.IOException;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class ExcelTest {
+
+ @Test
+ public void shouldParseExcelFile() throws IOException {
+ Excel.ExcelParser parser = new Excel.ExcelParser("excel");
+ File file = new File("src/test/resources/test.xlsx");
+ ReportDto report = parser.parse(file);
+
+ System.out.println(report.getItems());
+
+ assertThat(report.getItems()).hasSize(2);
+ assertThat(report.getItems().get(0).getName()).isEqualTo("1.0");
+ assertThat(report.getItems().get(0).getResult().get("Value")).isEqualTo(10);
+ assertThat(report.getItems().get(1).getName()).isEqualTo("2.0");
+ assertThat(report.getItems().get(1).getResult().get("Value")).isEqualTo(20);
+ }
+}
diff --git a/src/test/resources/test.xlsx b/src/test/resources/test.xlsx
new file mode 100644
index 0000000000000000000000000000000000000000..d6d6b912c165ec247c56dbe1a0ccee6a03626d38
GIT binary patch
literal 5211
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
)kcs`5