Skip to content

Commit

Permalink
ADM-693:[backend] feat: generate rework report (#1256)
Browse files Browse the repository at this point in the history
* ADM-693:[backend] fix:revert to original logic in fetch done card data

* ADM-693:[backend] fix: update rework time is null to zero

* ADM-693:[backend] refactor: extract methods

* ADM-693:[backend] refactor: extract generation of baseInfo and cycle time to the BoardSheetGenerator

* ADM-693:[backend] feat: generate rework field in sheet

* ADM-693:[backend] test: fix order of assert

* ADM-693:[backend] fix: add rework total time in flat map

* ADM-693: [frontend] fix: fix request param issue

* ADM-693:[backend] fix: format rework fields

* ADM-693:[backend] fix: use alias temporarily

* ADM-693:[backend] test: add rework fields in sheet

* ADM-693:[backend] refactor: only one condition

* ADM-693:[backend] fix: pmd check

* ADM-693:[backend] refactor: rename method

* ADM-693:[backend] refactor: sort card steps enum

* ADM-693:[docs] feat: return throughput in rework info

* AMD-694 [frontend] test: add e2e for board metric rework both for create-a-new-project.spec and import-project-from-file.spec

* AMD-693 [frontend] fix: add some columns in board metric.csv

* ADM-693:[backend] feat: report rework metric in metric csc file

* ADM-693:[frontend] fix: add rework data to e2e expect metric data csv

* ADM-693:[backend] fix: fix sonar issue

* ADM-693:[backend] fix: update constant name

---------

Co-authored-by: Genhao Liu <[email protected]>
Co-authored-by: GuangbinMa <[email protected]>
Co-authored-by: yp.wu <[email protected]>
  • Loading branch information
4 people authored and TingyuDong committed Mar 25, 2024
1 parent c827401 commit b0fa28b
Show file tree
Hide file tree
Showing 14 changed files with 326 additions and 68 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,28 @@

public enum CardStepsEnum {

TODO("To do"), ANALYSE("Analysis"), DEVELOPMENT("In Dev"), BLOCK("Block"), TESTING("Testing"), REVIEW("Review"),
DONE("Done"), CLOSED("Closed"), WAITING("Waiting for testing"), FLAG("FLAG"), REMOVEFLAG("removeFlag"),
UNKNOWN("UNKNOWN");
TODO("To do", "To do"), ANALYSE("Analysis", "Analysis"), DEVELOPMENT("In Dev", "In dev"), BLOCK("Block", "Block"),
FLAG("FLAG", "Flag"), REMOVEFLAG("removeFlag", "Remove flag"), REVIEW("Review", "Review"),
WAITING("Waiting for testing", "Waiting for testing"), TESTING("Testing", "Testing"), DONE("Done", "Done"),
CLOSED("Closed", "Closed"), UNKNOWN("UNKNOWN", "Unknown");

private final String value;

CardStepsEnum(String value) {
private final String alias;

CardStepsEnum(String value, String alias) {
this.value = value;
this.alias = alias;
}

public String getValue() {
return value;
}

public String getAlias() {
return alias;
}

public static CardStepsEnum fromValue(String type) {
for (CardStepsEnum cardStepsEnum : values()) {
if (cardStepsEnum.value.equals(type)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.commons.collections4.CollectionUtils;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Data
@Builder
Expand All @@ -32,6 +35,8 @@ public class JiraCardDTO {

private Object cycleTimeFlat;

private Object reworkTimesFlat;

@Nullable
private String totalCycleTimeDivideStoryPoints;

Expand Down Expand Up @@ -60,6 +65,19 @@ public Object buildCycleTimeFlatObject() {
return cycleTimeFlat;
}

@JsonIgnore
public Object buildReworkTimesFlatObject() {
if (CollectionUtils.isEmpty(this.getReworkTimesInfos())) {
return null;
}
Map<String, Integer> reworkTimesMap = this.getReworkTimesInfos()
.stream()
.collect(Collectors.toMap(reworkTimesInfo -> reworkTimesInfo.getState().getValue(),
ReworkTimesInfo::getTimes));
reworkTimesMap.put("totalReworkTimes", totalReworkTimes);
return reworkTimesMap;
}

@JsonIgnore
public void calculateTotalReworkTimes() {
this.totalReworkTimes = reworkTimesInfos.stream().mapToInt(ReworkTimesInfo::getTimes).sum();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -726,7 +726,7 @@ private void calculateTimes(CardStepsEnum reworkState, Set<CardStepsEnum> exclud
return;
}
if (isRework(from, to, excludedStates)) {
reworkTimesMap.put(from, reworkTimesMap.get(from) + 1);
reworkTimesMap.computeIfPresent(from, (key, value) -> value + 1);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package heartbeat.service.report;

import heartbeat.controller.board.dto.response.JiraCardDTO;
import heartbeat.controller.report.dto.response.BoardCSVConfig;
import lombok.Builder;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.ArrayUtils;

import java.util.List;

@Builder
public class BoardSheetGenerator {

private List<JiraCardDTO> jiraCardDTOList;

private List<BoardCSVConfig> fields;

private List<BoardCSVConfig> extraFields;

private List<BoardCSVConfig> reworkFields;

private CSVFileGenerator csvFileGenerator;

private String[][] sheet;

String[][] generate() {
return sheet;
}

BoardSheetGenerator mergeBaseInfoAndCycleTimeSheet() {
String[][] baseInfoAndCycleTimeSheet = csvFileGenerator.assembleBoardData(jiraCardDTOList, fields, extraFields);
sheet = mergeSheetHorizontally(sheet, baseInfoAndCycleTimeSheet);
return this;
}

BoardSheetGenerator mergeReworkTimesSheet() {
if (CollectionUtils.isEmpty(reworkFields)) {
return this;
}
int columnCount = reworkFields.size();
String[][] reworkTimesSheet = new String[jiraCardDTOList.size() + 1][columnCount];

for (int column = 0; column < columnCount; column++) {
reworkTimesSheet[0][column] = reworkFields.get(column).getLabel();
}
for (int row = 0; row < jiraCardDTOList.size(); row++) {
JiraCardDTO cardDTO = jiraCardDTOList.get(row);
for (int column = 0; column < columnCount; column++) {
reworkTimesSheet[row + 1][column] = csvFileGenerator.getExtraDataPerRow(cardDTO.getReworkTimesFlat(),
reworkFields.get(column));
}
}
sheet = mergeSheetHorizontally(sheet, reworkTimesSheet);
return this;
}

private String[][] mergeSheetHorizontally(String[][] sheet, String[][] sheetToMerge) {
int rows = jiraCardDTOList.size() + 1;
String[][] combinedArray = new String[rows][];
if (ArrayUtils.isEmpty(sheet)) {
return sheetToMerge;
}
for (int i = 0; i < rows; i++) {
combinedArray[i] = ArrayUtils.addAll(sheet[i], sheetToMerge[i]);
}
return combinedArray;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import heartbeat.controller.report.dto.response.DevMeanTimeToRecoveryOfPipeline;
import heartbeat.controller.report.dto.response.PipelineCSVInfo;
import heartbeat.controller.report.dto.response.ReportResponse;
import heartbeat.controller.report.dto.response.Rework;
import heartbeat.controller.report.dto.response.Velocity;
import heartbeat.exception.FileIOException;
import heartbeat.exception.GenerateReportException;
Expand Down Expand Up @@ -65,6 +66,8 @@ public class CSVFileGenerator {

private static final String CANCELED_STATUS = "canceled";

private static final String REWORK_FIELD = "Rework";

private static InputStreamResource readStringFromCsvFile(String fileName) {
try {
InputStream inputStream = new FileInputStream(fileName);
Expand Down Expand Up @@ -170,26 +173,17 @@ private void createCsvDirToConvertData() {
public void convertBoardDataToCSV(List<JiraCardDTO> cardDTOList, List<BoardCSVConfig> fields,
List<BoardCSVConfig> extraFields, String csvTimeStamp) {
log.info("Start to create board csv directory");
String[][] mergedArrays = assembleBoardData(cardDTOList, fields, extraFields);
writeDataToCSV(csvTimeStamp, mergedArrays);
}

public void writeDataToCSV(String csvTimeStamp, String[][] mergedArrays) {
createCsvDirToConvertData();

String fileName = CSVFileNameEnum.BOARD.getValue() + FILENAME_SEPARATOR + csvTimeStamp + CSV_EXTENSION;
if (!fileName.contains("..") && fileName.startsWith(FILE_LOCAL_PATH)) {
try (CSVWriter writer = new CSVWriter(new FileWriter(fileName))) {
List<BoardCSVConfig> fixedFields = new ArrayList<>(fields);
fixedFields.removeAll(extraFields);

String[][] fixedFieldsData = getFixedFieldsData(cardDTOList, fixedFields);
String[][] extraFieldsData = getExtraFieldsData(cardDTOList, extraFields);

String[] fixedFieldsRow = fixedFieldsData[0];
String targetElement = "Cycle Time";
List<String> fixedFieldsRowList = Arrays.asList(fixedFieldsRow);
int targetIndex = fixedFieldsRowList.indexOf(targetElement) + 1;

String[][] mergedArrays = mergeArrays(fixedFieldsData, extraFieldsData, targetIndex);

writer.writeAll(Arrays.asList(mergedArrays));

}
catch (IOException e) {
log.error("Failed to write board file", e);
Expand All @@ -201,6 +195,22 @@ public void convertBoardDataToCSV(List<JiraCardDTO> cardDTOList, List<BoardCSVCo
}
}

public String[][] assembleBoardData(List<JiraCardDTO> cardDTOList, List<BoardCSVConfig> fields,
List<BoardCSVConfig> extraFields) {
List<BoardCSVConfig> fixedFields = new ArrayList<>(fields);
fixedFields.removeAll(extraFields);

String[][] fixedFieldsData = getFixedFieldsData(cardDTOList, fixedFields);
String[][] extraFieldsData = getExtraFieldsData(cardDTOList, extraFields);

String[] fixedFieldsRow = fixedFieldsData[0];
String targetElement = "Cycle Time";
List<String> fixedFieldsRowList = Arrays.asList(fixedFieldsRow);
int targetIndex = fixedFieldsRowList.indexOf(targetElement) + 1;

return mergeArrays(fixedFieldsData, extraFieldsData, targetIndex);
}

public String[][] mergeArrays(String[][] fixedFieldsData, String[][] extraFieldsData, int fixedColumnCount) {
int mergedColumnLength = fixedFieldsData[0].length + extraFieldsData[0].length;
String[][] mergedArray = new String[fixedFieldsData.length][mergedColumnLength];
Expand Down Expand Up @@ -336,7 +346,7 @@ private void fixDataWithFields(JiraCardDTO cardDTO, String[] rowData) {
rowData[13] = String.join(",", cardDTO.getBaseInfo().getFields().getLabels());
}

private String getExtraDataPerRow(Object object, BoardCSVConfig extraField) {
public String getExtraDataPerRow(Object object, BoardCSVConfig extraField) {
Map<String, JsonElement> elementMap = (Map<String, JsonElement>) object;
if (elementMap == null) {
return null;
Expand Down Expand Up @@ -405,6 +415,11 @@ private List<String[]> convertReportResponseToCSVRows(ReportResponse reportRespo
if (cycleTime != null)
rows.addAll(getRowsFromCycleTime(cycleTime));

Rework rework = reportResponse.getRework();
if (rework != null) {
rows.addAll(getRowFromRework(rework));
}

List<Classification> classificationList = reportResponse.getClassificationList();
if (classificationList != null)
classificationList.forEach(classification -> rows.addAll(getRowsFormClassification(classification)));
Expand Down Expand Up @@ -463,6 +478,15 @@ private List<String[]> getRowsFromCycleTime(CycleTime cycleTime) {
return rows;
}

private List<String[]> getRowFromRework(Rework rework) {
List<String[]> rows = new ArrayList<>();
rows.add(new String[] { REWORK_FIELD, "Total rework times", String.valueOf(rework.getTotalReworkTimes()) });
rows.add(new String[] { REWORK_FIELD, "Total rework cards", String.valueOf(rework.getTotalReworkCards()) });
rows.add(new String[] { REWORK_FIELD, "Rework cards ratio(Total rework cards/Throughput)",
String.valueOf(rework.getReworkCardsRatio()) });
return rows;
}

private String formatStepName(CycleTimeForSelectedStepItem cycleTimeForSelectedStepItem) {
return switch (cycleTimeForSelectedStepItem.getOptionalItemName()) {
case "In Dev" -> "development";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import heartbeat.service.board.jira.JiraService;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.stereotype.Service;

import java.net.URI;
Expand All @@ -35,6 +36,8 @@
import java.util.Objects;
import java.util.stream.Stream;

import static heartbeat.controller.board.dto.request.CardStepsEnum.reworkJudgmentMap;

@Service
@Log4j2
@RequiredArgsConstructor
Expand Down Expand Up @@ -68,13 +71,28 @@ public void generateCsvInfo(GenerateReportRequest request, CardCollection realDo
boardRequestParam.getToken());
JiraColumnResult jiraColumns = jiraService.getJiraColumns(boardRequestParam, baseUrl, jiraBoardConfigDTO);

List<String> reworkFromStates = null;
CardStepsEnum reworkState = null;
if (request.getJiraBoardSetting().getReworkTimesSetting() != null) {
reworkState = request.getJiraBoardSetting().getReworkTimesSetting().getEnumReworkState();
List<CardStepsEnum> reworkExcludeStates = request.getJiraBoardSetting()
.getReworkTimesSetting()
.getEnumExcludeStates();
reworkFromStates = reworkJudgmentMap.get(reworkState)
.stream()
.sorted()
.filter(state -> !reworkExcludeStates.contains(state))
.map(CardStepsEnum::getValue)
.toList();
}
this.generateCSVForBoard(realDoneCardCollection.getJiraCardDTOList(),
nonDoneCardCollection.getJiraCardDTOList(), jiraColumns.getJiraColumnResponse(),
jiraBoardSetting.getTargetFields(), request.getCsvTimeStamp());
jiraBoardSetting.getTargetFields(), request.getCsvTimeStamp(), reworkState, reworkFromStates);
}

private void generateCSVForBoard(List<JiraCardDTO> allDoneCards, List<JiraCardDTO> nonDoneCards,
List<JiraColumnDTO> jiraColumns, List<TargetField> targetFields, String csvTimeStamp) {
List<JiraColumnDTO> jiraColumns, List<TargetField> targetFields, String csvTimeStamp,
CardStepsEnum reworkState, List<String> reworkFromStates) {
List<JiraCardDTO> cardDTOList = new ArrayList<>();
List<JiraCardDTO> emptyJiraCard = List.of(JiraCardDTO.builder().build());

Expand Down Expand Up @@ -116,12 +134,37 @@ private void generateCSVForBoard(List<JiraCardDTO> allDoneCards, List<JiraCardDT
.label("OriginCycleTime: " + column)
.value("cycleTimeFlat." + column)
.build()));
// rework times fields
List<BoardCSVConfig> reworkFields = new ArrayList<>();
if (reworkState != null) {
reworkFields.add(BoardCSVConfig.builder()
.label(reworkState.getAlias() + " total rework times")
.value("totalReworkTimes")
.build());
reworkFields.addAll(reworkFromStates.stream()
.map(state -> BoardCSVConfig.builder()
.label("from " + state + " to " + reworkState.getAlias())
.value("reworkTimesFlat." + state)
.build())
.toList());
}

cardDTOList.forEach(card -> {
card.setCycleTimeFlat(card.buildCycleTimeFlatObject());
card.setTotalCycleTimeDivideStoryPoints(card.getTotalCycleTimeDivideStoryPoints());
card.setReworkTimesFlat(card.buildReworkTimesFlatObject());
});
csvFileGenerator.convertBoardDataToCSV(cardDTOList, allBoardFields, newExtraFields, csvTimeStamp);
String[][] sheet = BoardSheetGenerator.builder()
.csvFileGenerator(csvFileGenerator)
.jiraCardDTOList(cardDTOList)
.fields(allBoardFields)
.extraFields(newExtraFields)
.reworkFields(reworkFields)
.build()
.mergeBaseInfoAndCycleTimeSheet()
.mergeReworkTimesSheet()
.generate();
csvFileGenerator.writeDataToCSV(csvTimeStamp, sheet);
}

private void sortNonDoneCardsByStatusAndTime(List<JiraCardDTO> nonDoneCards, List<JiraColumnDTO> jiraColumns) {
Expand Down
17 changes: 10 additions & 7 deletions backend/src/test/java/heartbeat/service/jira/JiraServiceTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -1608,11 +1608,11 @@ void shouldGetRealDoneCardsReworkToInDevTimesGivenNotConsiderFlagIsBlockAndNoExc
.isEqualTo(CardStepsEnum.BLOCK);
assertThat(cardCollection.getJiraCardDTOList().get(0).getReworkTimesInfos().get(0).getTimes()).isEqualTo(1);
assertThat(cardCollection.getJiraCardDTOList().get(0).getReworkTimesInfos().get(1).getState())
.isEqualTo(CardStepsEnum.TESTING);
assertThat(cardCollection.getJiraCardDTOList().get(0).getReworkTimesInfos().get(1).getTimes()).isEqualTo(1);
assertThat(cardCollection.getJiraCardDTOList().get(0).getReworkTimesInfos().get(2).getState())
.isEqualTo(CardStepsEnum.REVIEW);
assertThat(cardCollection.getJiraCardDTOList().get(0).getReworkTimesInfos().get(2).getTimes()).isEqualTo(1);
assertThat(cardCollection.getJiraCardDTOList().get(0).getReworkTimesInfos().get(1).getTimes()).isEqualTo(1);
assertThat(cardCollection.getJiraCardDTOList().get(0).getReworkTimesInfos().get(3).getState())
.isEqualTo(CardStepsEnum.TESTING);
assertThat(cardCollection.getJiraCardDTOList().get(0).getReworkTimesInfos().get(3).getTimes()).isEqualTo(1);
}

@Test
Expand Down Expand Up @@ -1698,11 +1698,14 @@ void shouldGetRealDoneCardsReworkToInDevTimesGivenNotConsiderFlagIsBlockAndExclu
assertThat(cardCollection.getReworkRatio()).isEqualTo(0.5);
assertThat(cardCollection.getJiraCardDTOList().get(0).getTotalReworkTimes()).isEqualTo(2);
assertThat(cardCollection.getJiraCardDTOList().get(0).getReworkTimesInfos().get(0).getState())
.isEqualTo(CardStepsEnum.TESTING);
.isEqualTo(CardStepsEnum.REVIEW);
assertThat(cardCollection.getJiraCardDTOList().get(0).getReworkTimesInfos().get(0).getTimes()).isEqualTo(1);
assertThat(cardCollection.getJiraCardDTOList().get(0).getReworkTimesInfos().get(1).getState())
.isEqualTo(CardStepsEnum.REVIEW);
assertThat(cardCollection.getJiraCardDTOList().get(0).getReworkTimesInfos().get(1).getTimes()).isEqualTo(1);
.isEqualTo(CardStepsEnum.WAITING);
assertThat(cardCollection.getJiraCardDTOList().get(0).getReworkTimesInfos().get(1).getTimes()).isZero();
assertThat(cardCollection.getJiraCardDTOList().get(0).getReworkTimesInfos().get(2).getState())
.isEqualTo(CardStepsEnum.TESTING);
assertThat(cardCollection.getJiraCardDTOList().get(0).getReworkTimesInfos().get(2).getTimes()).isEqualTo(1);
}

}
Loading

0 comments on commit b0fa28b

Please sign in to comment.