Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ADM-693:[backend] feat: generate rework report #1256

Merged
merged 25 commits into from
Mar 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
589d6c2
ADM-693:[backend] fix:revert to original logic in fetch done card data
yulongcai Mar 19, 2024
18a01c3
ADM-693:[backend] fix: update rework time is null to zero
yulongcai Mar 19, 2024
4f74099
ADM-693:[backend] refactor: extract methods
Liughgood Mar 19, 2024
6c0c197
ADM-693:[backend] refactor: extract generation of baseInfo and cycle …
Liughgood Mar 19, 2024
085e2a7
ADM-693:[backend] feat: generate rework field in sheet
Liughgood Mar 19, 2024
b42d47d
ADM-693:[backend] test: fix order of assert
Liughgood Mar 19, 2024
3df85f6
ADM-693:[backend] fix: add rework total time in flat map
yulongcai Mar 19, 2024
e89c08f
ADM-693: [frontend] fix: fix request param issue
neomgb Mar 20, 2024
3998b7f
ADM-693:[backend] fix: format rework fields
Liughgood Mar 20, 2024
e178eac
ADM-693:[backend] fix: use alias temporarily
Liughgood Mar 20, 2024
15bef5b
ADM-693:[backend] test: add rework fields in sheet
Liughgood Mar 20, 2024
d313f43
ADM-693:[backend] refactor: only one condition
Liughgood Mar 20, 2024
cbb69c4
ADM-693:[backend] fix: pmd check
Liughgood Mar 20, 2024
92422e7
Merge branch 'main' into ADM-693
Liughgood Mar 20, 2024
f185970
ADM-693:[backend] refactor: rename method
Liughgood Mar 20, 2024
6172738
ADM-693:[backend] refactor: sort card steps enum
Liughgood Mar 20, 2024
1988750
ADM-693:[docs] feat: return throughput in rework info
yulongcai Mar 21, 2024
ff25648
AMD-694 [frontend] test: add e2e for board metric rework both for cre…
Mar 20, 2024
2e90593
AMD-693 [frontend] fix: add some columns in board metric.csv
Mar 21, 2024
7259d54
Merge branch 'main' into ADM-693
yulongcai Mar 21, 2024
46d8acb
ADM-693:[backend] feat: report rework metric in metric csc file
yulongcai Mar 21, 2024
06b56ae
ADM-693:[frontend] fix: add rework data to e2e expect metric data csv
yulongcai Mar 21, 2024
8702858
ADM-693:[backend] fix: fix sonar issue
yulongcai Mar 21, 2024
e50214c
ADM-693:[backend] fix: update constant name
yulongcai Mar 21, 2024
1e6ae23
Merge branch 'main' into ADM-693
yulongcai Mar 21, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading