From 589d6c29fb08114b4c0f3bc82967a6bc56de368c Mon Sep 17 00:00:00 2001 From: yulongcai Date: Tue, 19 Mar 2024 11:26:02 +0800 Subject: [PATCH 01/22] ADM-693:[backend] fix:revert to original logic in fetch done card data --- .../StoryPointsAndCycleTimeRequest.java | 2 - .../service/board/jira/JiraService.java | 58 ++++++++----------- .../service/report/KanbanService.java | 9 +-- .../jira/JiraBoardConfigDTOFixture.java | 5 -- .../service/jira/JiraServiceTest.java | 40 ------------- .../service/report/KanbanFixture.java | 1 - 6 files changed, 28 insertions(+), 87 deletions(-) diff --git a/backend/src/main/java/heartbeat/controller/board/dto/request/StoryPointsAndCycleTimeRequest.java b/backend/src/main/java/heartbeat/controller/board/dto/request/StoryPointsAndCycleTimeRequest.java index 1c13bcd11c..51226a9d14 100644 --- a/backend/src/main/java/heartbeat/controller/board/dto/request/StoryPointsAndCycleTimeRequest.java +++ b/backend/src/main/java/heartbeat/controller/board/dto/request/StoryPointsAndCycleTimeRequest.java @@ -38,6 +38,4 @@ public class StoryPointsAndCycleTimeRequest { private ReworkTimesSetting reworkTimesSetting; - private List boardMetrics; - } diff --git a/backend/src/main/java/heartbeat/service/board/jira/JiraService.java b/backend/src/main/java/heartbeat/service/board/jira/JiraService.java index e40cbcca57..05a79d63fb 100644 --- a/backend/src/main/java/heartbeat/service/board/jira/JiraService.java +++ b/backend/src/main/java/heartbeat/service/board/jira/JiraService.java @@ -43,7 +43,6 @@ import heartbeat.controller.board.dto.response.StatusChangedItem; import heartbeat.controller.board.dto.response.StepsDay; import heartbeat.controller.board.dto.response.TargetField; -import heartbeat.controller.report.dto.request.MetricEnum; import heartbeat.exception.BadRequestException; import heartbeat.exception.BaseException; import heartbeat.exception.InternalServerErrorException; @@ -259,30 +258,27 @@ public CardCollection getStoryPointsAndCycleTimeAndReworkInfoForDoneCards(StoryP } List realDoneCards = getRealDoneCards(request, boardColumns, users, baseUrl, allDoneCards, jiraCardWithFields.getTargetFields(), assigneeFilter); + double storyPointSum = realDoneCards.stream() .mapToDouble(card -> card.getBaseInfo().getFields().getStoryPoints()) .sum(); - CardCollection cardCollection = CardCollection.builder() + int reworkCardNumber = realDoneCards.stream() + .filter(realDoneCard -> !realDoneCard.getReworkTimesInfos().isEmpty()) + .toList() + .size(); + double reworkRatio = realDoneCards.isEmpty() ? 0 + : BigDecimal.valueOf(reworkCardNumber) + .divide(BigDecimal.valueOf(realDoneCards.size()), 2, RoundingMode.HALF_UP) + .doubleValue(); + + return CardCollection.builder() .storyPointSum(storyPointSum) .cardsNumber(realDoneCards.size()) + .reworkCardNumber(reworkCardNumber) + .reworkRatio(reworkRatio) .jiraCardDTOList(realDoneCards) .build(); - - if (request.getBoardMetrics().contains(MetricEnum.REWORK_TIMES.getValue())) { - int reworkCardNumber = realDoneCards.stream() - .filter(realDoneCard -> !realDoneCard.getReworkTimesInfos().isEmpty()) - .toList() - .size(); - double reworkRatio = realDoneCards.isEmpty() ? 0 - : BigDecimal.valueOf(reworkCardNumber) - .divide(BigDecimal.valueOf(realDoneCards.size()), 2, RoundingMode.HALF_UP) - .doubleValue(); - cardCollection.setReworkCardNumber(reworkCardNumber); - cardCollection.setReworkRatio(reworkRatio); - } - - return cardCollection; } private CompletableFuture getJiraColumnsAsync(BoardRequestParam boardRequestParam, URI baseUrl, @@ -602,24 +598,20 @@ private List getRealDoneCards(StoryPointsAndCycleTimeRequest reques jiraCards.forEach(doneCard -> { CardHistoryResponseDTO cardHistoryResponseDTO = getJiraCardHistory(baseUrl, doneCard.getKey(), 0, request.getToken()); - List assigneeSet = getAssigneeSet(cardHistoryResponseDTO, filterMethod, doneCard); + CycleTimeInfoDTO cycleTimeInfoDTO = getCycleTime(cardHistoryResponseDTO, request.isTreatFlagCardAsBlock(), + keyFlagged, request.getStatus()); if (users.stream().anyMatch(assigneeSet::contains)) { - CycleTimeInfoDTO cycleTimeInfoDTO; - JiraCardDTO jiraCardDTO = JiraCardDTO.builder().baseInfo(doneCard).build(); - if (request.getBoardMetrics().contains(MetricEnum.CYCLE_TIME.getValue())) { - cycleTimeInfoDTO = getCycleTime(cardHistoryResponseDTO, request.isTreatFlagCardAsBlock(), - keyFlagged, request.getStatus()); - jiraCardDTO.setCycleTime(cycleTimeInfoDTO.getCycleTimeInfos()); - jiraCardDTO.setOriginCycleTime(cycleTimeInfoDTO.getOriginCycleTimeInfos()); - jiraCardDTO.setCardCycleTime(calculateCardCycleTime(doneCard.getKey(), - cycleTimeInfoDTO.getCycleTimeInfos(), boardColumns)); - } - if (request.getBoardMetrics().contains(MetricEnum.REWORK_TIMES.getValue())) { - jiraCardDTO.setReworkTimesInfos(getReworkTimesInfo(cardHistoryResponseDTO, - request.getReworkTimesSetting(), request.isTreatFlagCardAsBlock(), boardColumns)); - jiraCardDTO.calculateTotalReworkTimes(); - } + JiraCardDTO jiraCardDTO = JiraCardDTO.builder() + .baseInfo(doneCard) + .cycleTime(cycleTimeInfoDTO.getCycleTimeInfos()) + .originCycleTime(cycleTimeInfoDTO.getOriginCycleTimeInfos()) + .cardCycleTime(calculateCardCycleTime(doneCard.getKey(), cycleTimeInfoDTO.getCycleTimeInfos(), + boardColumns)) + .reworkTimesInfos(getReworkTimesInfo(cardHistoryResponseDTO, request.getReworkTimesSetting(), + request.isTreatFlagCardAsBlock(), boardColumns)) + .build(); + jiraCardDTO.calculateTotalReworkTimes(); realDoneCards.add(jiraCardDTO); } }); diff --git a/backend/src/main/java/heartbeat/service/report/KanbanService.java b/backend/src/main/java/heartbeat/service/report/KanbanService.java index 5d36eb9602..4a93b81f06 100644 --- a/backend/src/main/java/heartbeat/service/report/KanbanService.java +++ b/backend/src/main/java/heartbeat/service/report/KanbanService.java @@ -10,8 +10,6 @@ import lombok.extern.log4j.Log4j2; import org.springframework.stereotype.Service; -import java.util.List; - @Service @Log4j2 @RequiredArgsConstructor @@ -35,7 +33,7 @@ public FetchedData.CardCollectionInfo fetchDataFromKanban(GenerateReportRequest private CardCollection fetchRealDoneCardCollection(GenerateReportRequest request) { JiraBoardSetting jiraBoardSetting = request.getJiraBoardSetting(); StoryPointsAndCycleTimeRequest storyPointsAndCycleTimeRequest = buildStoryPointsAndCycleTimeRequest( - jiraBoardSetting, request.getStartTime(), request.getEndTime(), request.getBoardMetrics()); + jiraBoardSetting, request.getStartTime(), request.getEndTime()); return jiraService.getStoryPointsAndCycleTimeAndReworkInfoForDoneCards(storyPointsAndCycleTimeRequest, jiraBoardSetting.getBoardColumns(), jiraBoardSetting.getUsers(), jiraBoardSetting.getAssigneeFilter()); } @@ -43,13 +41,13 @@ private CardCollection fetchRealDoneCardCollection(GenerateReportRequest request private CardCollection fetchNonDoneCardCollection(GenerateReportRequest request) { JiraBoardSetting jiraBoardSetting = request.getJiraBoardSetting(); StoryPointsAndCycleTimeRequest storyPointsAndCycleTimeRequest = buildStoryPointsAndCycleTimeRequest( - jiraBoardSetting, request.getStartTime(), request.getEndTime(), request.getBoardMetrics()); + jiraBoardSetting, request.getStartTime(), request.getEndTime()); return jiraService.getStoryPointsAndCycleTimeForNonDoneCards(storyPointsAndCycleTimeRequest, jiraBoardSetting.getBoardColumns(), jiraBoardSetting.getUsers()); } private static StoryPointsAndCycleTimeRequest buildStoryPointsAndCycleTimeRequest(JiraBoardSetting jiraBoardSetting, - String startTime, String endTime, List metrics) { + String startTime, String endTime) { return StoryPointsAndCycleTimeRequest.builder() .token(jiraBoardSetting.getToken()) .type(jiraBoardSetting.getType()) @@ -63,7 +61,6 @@ private static StoryPointsAndCycleTimeRequest buildStoryPointsAndCycleTimeReques .overrideFields(jiraBoardSetting.getOverrideFields()) .treatFlagCardAsBlock(jiraBoardSetting.getTreatFlagCardAsBlock()) .reworkTimesSetting(jiraBoardSetting.getReworkTimesSetting()) - .boardMetrics(metrics) .build(); } diff --git a/backend/src/test/java/heartbeat/service/jira/JiraBoardConfigDTOFixture.java b/backend/src/test/java/heartbeat/service/jira/JiraBoardConfigDTOFixture.java index 4fa1308f55..fe82094db0 100644 --- a/backend/src/test/java/heartbeat/service/jira/JiraBoardConfigDTOFixture.java +++ b/backend/src/test/java/heartbeat/service/jira/JiraBoardConfigDTOFixture.java @@ -535,7 +535,6 @@ public static StoryPointsAndCycleTimeRequest.StoryPointsAndCycleTimeRequestBuild .startTime(START_TIME) .endTime(END_TIME) .targetFields(jiraBoardSetting.getTargetFields()) - .boardMetrics(List.of("cycle time", "rework times")) .treatFlagCardAsBlock(jiraBoardSetting.getTreatFlagCardAsBlock()); } @@ -551,7 +550,6 @@ public static StoryPointsAndCycleTimeRequest.StoryPointsAndCycleTimeRequestBuild .startTime(START_TIME) .endTime(END_TIME) .targetFields(jiraBoardSetting.getTargetFields()) - .boardMetrics(List.of("cycle time", "rework times")) .treatFlagCardAsBlock(jiraBoardSetting.getTreatFlagCardAsBlock()); } @@ -567,7 +565,6 @@ public static StoryPointsAndCycleTimeRequest.StoryPointsAndCycleTimeRequestBuild .startTime(START_TIME) .endTime(END_TIME) .targetFields(jiraBoardSetting.getTargetFields()) - .boardMetrics(List.of("cycle time", "rework times")) .treatFlagCardAsBlock(jiraBoardSetting.getTreatFlagCardAsBlock()); } @@ -650,7 +647,6 @@ public static StoryPointsAndCycleTimeRequest.StoryPointsAndCycleTimeRequestBuild .startTime(START_TIME) .endTime(END_TIME) .targetFields(jiraBoardSetting.getTargetFields()) - .boardMetrics(List.of("cycle time", "rework times")) .treatFlagCardAsBlock(jiraBoardSetting.getTreatFlagCardAsBlock()); } @@ -666,7 +662,6 @@ public static StoryPointsAndCycleTimeRequest.StoryPointsAndCycleTimeRequestBuild .startTime(START_TIME) .endTime(END_TIME) .targetFields(jiraBoardSetting.getTargetFields()) - .boardMetrics(List.of("cycle time", "rework times")) .treatFlagCardAsBlock(jiraBoardSetting.getTreatFlagCardAsBlock()); } diff --git a/backend/src/test/java/heartbeat/service/jira/JiraServiceTest.java b/backend/src/test/java/heartbeat/service/jira/JiraServiceTest.java index e779bc8132..4493aa9211 100644 --- a/backend/src/test/java/heartbeat/service/jira/JiraServiceTest.java +++ b/backend/src/test/java/heartbeat/service/jira/JiraServiceTest.java @@ -1658,46 +1658,6 @@ void shouldGetRealDoneCardsReworkTimesToInDevGivenConsiderFlagAsBlock() throws J .isEqualTo(CardStepsEnum.BLOCK); } - @Test - void shouldGetRealDoneCardsGivenNoMetrics() throws JsonProcessingException { - - URI baseUrl = URI.create(SITE_ATLASSIAN_NET); - String token = "token"; - String assigneeFilter = "lastAssignee"; - - // request param - JiraBoardSetting jiraBoardSetting = JIRA_BOARD_SETTING_WITH_HISTORICAL_ASSIGNEE_FILTER_METHOD().build(); - StoryPointsAndCycleTimeRequest request = STORY_POINTS_REQUEST_WITH_MULTIPLE_REAL_DONE_STATUSES() - .treatFlagCardAsBlock(false) - .reworkTimesSetting(ReworkTimesSetting.builder().reworkState("In Dev").excludedStates(List.of()).build()) - .boardMetrics(List.of()) - .build(); - - // return value - String allDoneCards = objectMapper.writeValueAsString(ALL_DONE_CARDS_RESPONSE_FOR_MULTIPLE_STATUS().build()) - .replaceAll("sprint", "customfield_10020") - .replaceAll("partner", "customfield_10037") - .replaceAll("flagged", "customfield_10021") - .replaceAll("development", "customfield_10000"); - - when(urlGenerator.getUri(any())).thenReturn(baseUrl); - when(jiraFeignClient.getJiraCards(any(), any(), anyInt(), anyInt(), any(), any())).thenReturn(allDoneCards); - when(jiraFeignClient.getJiraCardHistoryByCount(baseUrl, "ADM-475", 0, 100, token)) - .thenReturn(CARD1_HISTORY_FOR_MULTIPLE_STATUSES().build()); - when(jiraFeignClient.getJiraCardHistoryByCount(baseUrl, "ADM-524", 0, 100, token)) - .thenReturn(CARD3_HISTORY_FOR_MULTIPLE_STATUSES().build()); - when(jiraFeignClient.getTargetField(baseUrl, "PLL", token)).thenReturn(ALL_FIELD_RESPONSE_BUILDER().build()); - - CardCollection cardCollection = jiraService.getStoryPointsAndCycleTimeAndReworkInfoForDoneCards(request, - jiraBoardSetting.getBoardColumns(), - List.of(JiraBoardConfigDTOFixture.DISPLAY_NAME_ONE, JiraBoardConfigDTOFixture.DISPLAY_NAME_TWO), - assigneeFilter); - - assertThat(cardCollection.getJiraCardDTOList().get(0).getReworkTimesInfos()).isNull(); - assertThat(cardCollection.getJiraCardDTOList().get(0).getCardCycleTime()).isNull(); - - } - @Test void shouldGetRealDoneCardsReworkToInDevTimesGivenNotConsiderFlagIsBlockAndExcludeState() throws JsonProcessingException { diff --git a/backend/src/test/java/heartbeat/service/report/KanbanFixture.java b/backend/src/test/java/heartbeat/service/report/KanbanFixture.java index 116061e74f..f508a58c67 100644 --- a/backend/src/test/java/heartbeat/service/report/KanbanFixture.java +++ b/backend/src/test/java/heartbeat/service/report/KanbanFixture.java @@ -35,7 +35,6 @@ public static StoryPointsAndCycleTimeRequest MOCK_EXPECT_STORY_POINT_AND_CYCLE_T .treatFlagCardAsBlock(true) .startTime("startTime") .endTime("endTime") - .boardMetrics(List.of("cycle time", "rework times")) .build(); } From 18a01c3861a939618cdf969a14b61c517b33f98c Mon Sep 17 00:00:00 2001 From: yulongcai Date: Tue, 19 Mar 2024 14:53:42 +0800 Subject: [PATCH 02/22] ADM-693:[backend] fix: update rework time is null to zero --- .../service/board/jira/JiraService.java | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/backend/src/main/java/heartbeat/service/board/jira/JiraService.java b/backend/src/main/java/heartbeat/service/board/jira/JiraService.java index 05a79d63fb..9f3581205b 100644 --- a/backend/src/main/java/heartbeat/service/board/jira/JiraService.java +++ b/backend/src/main/java/heartbeat/service/board/jira/JiraService.java @@ -264,7 +264,9 @@ public CardCollection getStoryPointsAndCycleTimeAndReworkInfoForDoneCards(StoryP .sum(); int reworkCardNumber = realDoneCards.stream() - .filter(realDoneCard -> !realDoneCard.getReworkTimesInfos().isEmpty()) + .filter(realDoneCard -> realDoneCard.getReworkTimesInfos() + .stream() + .anyMatch(reworkTimesInfo -> reworkTimesInfo.getTimes() != 0)) .toList() .size(); double reworkRatio = realDoneCards.isEmpty() ? 0 @@ -638,7 +640,7 @@ private List getReworkTimesInfo(CardHistoryResponseDTO jiraCard private List getReworkTimesInfoWhenConsiderFlagAsBlock(CardHistoryResponseDTO jiraCardHistory, CardStepsEnum reworkState, Set excludedStates, Map stateMap) { - Map reworkTimesMap = new EnumMap<>(CardStepsEnum.class); + Map reworkTimesMap = initialReworkTimesMap(reworkState, excludedStates); AtomicReference currentState = new AtomicReference<>(); AtomicBoolean hasFlag = new AtomicBoolean(false); jiraCardHistory.getItems() @@ -673,6 +675,15 @@ private List getReworkTimesInfoWhenConsiderFlagAsBlock(CardHist .toList(); } + private static Map initialReworkTimesMap(CardStepsEnum reworkState, + Set excludedStates) { + Map reworkTimesMap = new EnumMap<>(CardStepsEnum.class); + Set stateReworkEnums = new HashSet<>(reworkJudgmentMap.get(reworkState)); + stateReworkEnums.removeAll(excludedStates); + stateReworkEnums.forEach(state -> reworkTimesMap.put(state, 0)); + return reworkTimesMap; + } + private Map buildBoardStateMap(List boardColumns) { return boardColumns.stream() .collect(Collectors.toMap(boardColumn -> boardColumn.getName().toUpperCase(), @@ -689,7 +700,7 @@ private CardStepsEnum convertBoardStateToEnumState(String value, Map getReworkTimesInfoWhenNotConsiderFlagAsBlock(CardHistoryResponseDTO jiraCardHistory, CardStepsEnum reworkState, Set excludedStates, Map stateMap) { - Map reworkTimesMap = new EnumMap<>(CardStepsEnum.class); + Map reworkTimesMap = initialReworkTimesMap(reworkState, excludedStates); jiraCardHistory.getItems() .stream() .filter(jiraCardHistoryItem -> STATUS_FIELD_ID.equalsIgnoreCase(jiraCardHistoryItem.getFieldId())) @@ -718,9 +729,6 @@ private void calculateTimes(CardStepsEnum reworkState, Set exclud if (reworkTimesMap.containsKey(from)) { reworkTimesMap.put(from, reworkTimesMap.get(from) + 1); } - else { - reworkTimesMap.put(from, 1); - } } } From 4f7409941369d5769c2f2d7a3e62fbedaa114a67 Mon Sep 17 00:00:00 2001 From: Genhao Liu Date: Tue, 19 Mar 2024 11:04:38 +0800 Subject: [PATCH 03/22] ADM-693:[backend] refactor: extract methods --- .../service/report/CSVFileGenerator.java | 35 +++++++++++-------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/backend/src/main/java/heartbeat/service/report/CSVFileGenerator.java b/backend/src/main/java/heartbeat/service/report/CSVFileGenerator.java index 36f836336b..3468066dcb 100644 --- a/backend/src/main/java/heartbeat/service/report/CSVFileGenerator.java +++ b/backend/src/main/java/heartbeat/service/report/CSVFileGenerator.java @@ -170,26 +170,17 @@ private void createCsvDirToConvertData() { public void convertBoardDataToCSV(List cardDTOList, List fields, List extraFields, String csvTimeStamp) { log.info("Start to create board csv directory"); + String[][] mergedArrays = assembleBoardData(cardDTOList, fields, extraFields); + writeDataToCSV(csvTimeStamp, mergedArrays); + } + + private 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 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 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); @@ -201,6 +192,22 @@ public void convertBoardDataToCSV(List cardDTOList, List cardDTOList, List fields, + List extraFields) { + List 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 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]; From 6c0c19733f639d6656302b1105575581c57f7006 Mon Sep 17 00:00:00 2001 From: Genhao Liu Date: Tue, 19 Mar 2024 16:05:43 +0800 Subject: [PATCH 04/22] ADM-693:[backend] refactor: extract generation of baseInfo and cycle time to the BoardSheetGenerator --- .../service/report/BoardSheetGenerator.java | 65 +++++++++++++++++++ .../service/report/CSVFileGenerator.java | 6 +- .../service/report/KanbanCsvService.java | 10 ++- .../service/report/KanbanCsvServiceTest.java | 26 ++++---- 4 files changed, 89 insertions(+), 18 deletions(-) create mode 100644 backend/src/main/java/heartbeat/service/report/BoardSheetGenerator.java diff --git a/backend/src/main/java/heartbeat/service/report/BoardSheetGenerator.java b/backend/src/main/java/heartbeat/service/report/BoardSheetGenerator.java new file mode 100644 index 0000000000..f7068a2999 --- /dev/null +++ b/backend/src/main/java/heartbeat/service/report/BoardSheetGenerator.java @@ -0,0 +1,65 @@ +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.lang3.ArrayUtils; + +import java.util.List; + +@Builder +public class BoardSheetGenerator { + + private List jiraCardDTOList; + + private List fields; + + private List extraFields; + + private List 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 generateReworkTimes() { + 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.getCycleTimeFlat(), + reworkFields.get(column)); + } + } + sheet = mergeSheetHorizontally(sheet, reworkTimesSheet); + return this; + } + + private String[][] mergeSheetHorizontally(String[][] sheet, String[][] sheetToMerge) { + int rows = jiraCardDTOList.size(); + 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; + } + +} diff --git a/backend/src/main/java/heartbeat/service/report/CSVFileGenerator.java b/backend/src/main/java/heartbeat/service/report/CSVFileGenerator.java index 3468066dcb..a8bc585599 100644 --- a/backend/src/main/java/heartbeat/service/report/CSVFileGenerator.java +++ b/backend/src/main/java/heartbeat/service/report/CSVFileGenerator.java @@ -174,7 +174,7 @@ public void convertBoardDataToCSV(List cardDTOList, List cardDTOList, List fields, + public String[][] assembleBoardData(List cardDTOList, List fields, List extraFields) { List fixedFields = new ArrayList<>(fields); fixedFields.removeAll(extraFields); @@ -343,7 +343,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 elementMap = (Map) object; if (elementMap == null) { return null; diff --git a/backend/src/main/java/heartbeat/service/report/KanbanCsvService.java b/backend/src/main/java/heartbeat/service/report/KanbanCsvService.java index 808b926d2c..73cc355623 100644 --- a/backend/src/main/java/heartbeat/service/report/KanbanCsvService.java +++ b/backend/src/main/java/heartbeat/service/report/KanbanCsvService.java @@ -121,7 +121,15 @@ private void generateCSVForBoard(List allDoneCards, List nonDoneCards, List jiraColumns) { diff --git a/backend/src/test/java/heartbeat/service/report/KanbanCsvServiceTest.java b/backend/src/test/java/heartbeat/service/report/KanbanCsvServiceTest.java index 73bc189f2a..3e22f4c071 100644 --- a/backend/src/test/java/heartbeat/service/report/KanbanCsvServiceTest.java +++ b/backend/src/test/java/heartbeat/service/report/KanbanCsvServiceTest.java @@ -86,7 +86,7 @@ void shouldSaveCsvWithoutNonDoneCardsWhenNonDoneCardIsNull() throws URISyntaxExc CardCollection.builder().jiraCardDTOList(List.of(jiraCardDTO)).build(), CardCollection.builder().build()); - verify(csvFileGenerator).convertBoardDataToCSV(jiraCardDTOCaptor.capture(), anyList(), any(), any()); + verify(csvFileGenerator).assembleBoardData(jiraCardDTOCaptor.capture(), anyList(), any()); assertEquals(2, jiraCardDTOCaptor.getValue().size()); assertTrue(jiraCardDTOCaptor.getValue().contains(jiraCardDTO)); } @@ -109,7 +109,7 @@ void shouldSaveCsvWithoutNonDoneCardsWhenNonDoneCardIsEmpty() throws URISyntaxEx CardCollection.builder().jiraCardDTOList(List.of(jiraCardDTO)).build(), CardCollection.builder().jiraCardDTOList(Lists.list()).build()); - verify(csvFileGenerator).convertBoardDataToCSV(jiraCardDTOCaptor.capture(), anyList(), any(), any()); + verify(csvFileGenerator).assembleBoardData(jiraCardDTOCaptor.capture(), anyList(), any()); assertEquals(2, jiraCardDTOCaptor.getValue().size()); assertTrue(jiraCardDTOCaptor.getValue().contains(jiraCardDTO)); } @@ -169,7 +169,7 @@ void shouldSaveCsvWithOrderedNonDoneCardsByJiraColumnDescendingWhenNonDoneCardIs CardCollection.builder().jiraCardDTOList(List.of(jiraCardDTO)).build(), CardCollection.builder().jiraCardDTOList(NonDoneJiraCardDTOList).build()); - verify(csvFileGenerator).convertBoardDataToCSV(jiraCardDTOCaptor.capture(), anyList(), any(), any()); + verify(csvFileGenerator).assembleBoardData(jiraCardDTOCaptor.capture(), anyList(), any()); assertEquals(5, jiraCardDTOCaptor.getValue().size()); assertEquals(testingJiraCard, jiraCardDTOCaptor.getValue().get(2)); assertEquals(blockedJiraCard, jiraCardDTOCaptor.getValue().get(3)); @@ -231,7 +231,7 @@ void shouldSaveCsvWithOrderedNonDoneCardsByJiraColumnDescendingWhenNonDoneCardIs CardCollection.builder().jiraCardDTOList(List.of(jiraCardDTO)).build(), CardCollection.builder().jiraCardDTOList(NonDoneJiraCardDTOList).build()); - verify(csvFileGenerator).convertBoardDataToCSV(jiraCardDTOCaptor.capture(), anyList(), any(), any()); + verify(csvFileGenerator).assembleBoardData(jiraCardDTOCaptor.capture(), anyList(), any()); assertEquals(5, jiraCardDTOCaptor.getValue().size()); assertEquals(preDoingJiraCard, jiraCardDTOCaptor.getValue().get(3)); assertEquals(nextDoingJiraCard, jiraCardDTOCaptor.getValue().get(4)); @@ -275,7 +275,7 @@ void shouldSaveCsvWithOrderedDoneCardsByJiraColumnDescendingWhenDoneCardIsNotEmp CardCollection.builder().jiraCardDTOList(doneJiraCardDTOList).build(), CardCollection.builder().jiraCardDTOList(List.of(jiraCardDTO)).build()); - verify(csvFileGenerator).convertBoardDataToCSV(jiraCardDTOCaptor.capture(), anyList(), any(), any()); + verify(csvFileGenerator).assembleBoardData(jiraCardDTOCaptor.capture(), anyList(), any()); assertEquals(4, jiraCardDTOCaptor.getValue().size()); assertEquals(preDoneJiraCard, jiraCardDTOCaptor.getValue().get(0)); assertEquals(nextDoneJiraCard, jiraCardDTOCaptor.getValue().get(1)); @@ -323,7 +323,7 @@ void shouldSaveCsvWithOrderedDoneCardsByJiraColumnDescendingWhenNonDoneCardIsNot CardCollection.builder().jiraCardDTOList(doneJiraCardDTOList).build(), CardCollection.builder().jiraCardDTOList(List.of(jiraCardDTO)).build()); - verify(csvFileGenerator).convertBoardDataToCSV(jiraCardDTOCaptor.capture(), anyList(), any(), any()); + verify(csvFileGenerator).assembleBoardData(jiraCardDTOCaptor.capture(), anyList(), any()); assertEquals(4, jiraCardDTOCaptor.getValue().size()); assertEquals(preDoneJiraCard, jiraCardDTOCaptor.getValue().get(1)); assertEquals(nextDoneJiraCard, jiraCardDTOCaptor.getValue().get(0)); @@ -374,7 +374,7 @@ void shouldSaveCsvWithoutOrderedNonDoneCardsByJiraColumnWhenNonDoneCardIsNotEmpt CardCollection.builder().jiraCardDTOList(List.of(jiraCardDTO)).build(), CardCollection.builder().jiraCardDTOList(NonDoneJiraCardDTOList).build()); - verify(csvFileGenerator).convertBoardDataToCSV(jiraCardDTOCaptor.capture(), anyList(), any(), any()); + verify(csvFileGenerator).assembleBoardData(jiraCardDTOCaptor.capture(), anyList(), any()); assertEquals(5, jiraCardDTOCaptor.getValue().size()); assertEquals(blockedJiraCard, jiraCardDTOCaptor.getValue().get(2)); assertEquals(doingJiraCard, jiraCardDTOCaptor.getValue().get(3)); @@ -423,7 +423,7 @@ void shouldSaveCsvWithOrderedNonDoneCardsByJiraColumnDescendingWhenNonDoneCardIs CardCollection.builder().jiraCardDTOList(List.of(jiraCardDTO)).build(), CardCollection.builder().jiraCardDTOList(NonDoneJiraCardDTOList).build()); - verify(csvFileGenerator).convertBoardDataToCSV(jiraCardDTOCaptor.capture(), anyList(), any(), any()); + verify(csvFileGenerator).assembleBoardData(jiraCardDTOCaptor.capture(), anyList(), any()); assertEquals(5, jiraCardDTOCaptor.getValue().size()); assertEquals(doingJiraCard, jiraCardDTOCaptor.getValue().get(2)); assertEquals(blockedJiraCard, jiraCardDTOCaptor.getValue().get(3)); @@ -462,8 +462,7 @@ void shouldAddFixedFieldsWhenItIsNotInSettingsFields() throws URISyntaxException CardCollection.builder().jiraCardDTOList(List.of(jiraCardDTO)).build(), CardCollection.builder().jiraCardDTOList(NonDoneJiraCardDTOList).build()); - verify(csvFileGenerator).convertBoardDataToCSV(anyList(), csvFieldsCaptor.capture(), - csvNewFieldsCaptor.capture(), any()); + verify(csvFileGenerator).assembleBoardData(anyList(), csvFieldsCaptor.capture(), csvNewFieldsCaptor.capture()); assertEquals(23, csvFieldsCaptor.getValue().size()); BoardCSVConfig targetValue = csvNewFieldsCaptor.getValue().get(0); assertEquals("baseInfo.fields.customFields.key-target1", targetValue.getValue()); @@ -504,7 +503,7 @@ void shouldAddFixedFieldsWhenItIsNotInSettingsFieldsAndCardHasOriginCycleTime() CardCollection.builder().jiraCardDTOList(List.of(jiraCardDTO)).build(), CardCollection.builder().jiraCardDTOList(NonDoneJiraCardDTOList).build()); - verify(csvFileGenerator).convertBoardDataToCSV(anyList(), csvFieldsCaptor.capture(), anyList(), any()); + verify(csvFileGenerator).assembleBoardData(anyList(), csvFieldsCaptor.capture(), anyList()); assertEquals(24, csvFieldsCaptor.getValue().size()); BoardCSVConfig targetValue = csvFieldsCaptor.getValue().get(22); assertEquals("cardCycleTime.steps.review", targetValue.getValue()); @@ -546,7 +545,7 @@ void shouldAddFixedFieldsWhenItIsNotInSettingsFieldsAndCardHasOriginCycleTimeAnd CardCollection.builder().jiraCardDTOList(List.of(jiraCardDTO)).build(), CardCollection.builder().jiraCardDTOList(NonDoneJiraCardDTOList).build()); - verify(csvFileGenerator).convertBoardDataToCSV(anyList(), csvFieldsCaptor.capture(), anyList(), any()); + verify(csvFileGenerator).assembleBoardData(anyList(), csvFieldsCaptor.capture(), anyList()); assertEquals(23, csvFieldsCaptor.getValue().size()); BoardCSVConfig targetValue = csvFieldsCaptor.getValue().get(22); assertEquals("cardCycleTime.steps.review", targetValue.getValue()); @@ -595,8 +594,7 @@ void shouldAddFixedFieldsWithCorrectValueFormatWhenCustomFieldValueInstanceOfLis CardCollection.builder().jiraCardDTOList(List.of(doneJiraCardDTO)).build(), CardCollection.builder().jiraCardDTOList(NonDoneJiraCardDTOList).build()); - verify(csvFileGenerator).convertBoardDataToCSV(anyList(), csvFieldsCaptor.capture(), - csvNewFieldsCaptor.capture(), any()); + verify(csvFileGenerator).assembleBoardData(anyList(), csvFieldsCaptor.capture(), csvNewFieldsCaptor.capture()); assertEquals(28, csvFieldsCaptor.getValue().size()); BoardCSVConfig targetValue1 = csvNewFieldsCaptor.getValue().get(0); From 085e2a7d87c378fcf5f0c3e80c07dc881a5219a6 Mon Sep 17 00:00:00 2001 From: Genhao Liu Date: Tue, 19 Mar 2024 16:32:22 +0800 Subject: [PATCH 05/22] ADM-693:[backend] feat: generate rework field in sheet --- .../board/dto/request/CardStepsEnum.java | 4 +- .../board/dto/response/JiraCardDTO.java | 15 +++++++ .../service/report/BoardSheetGenerator.java | 6 ++- .../service/report/KanbanCsvService.java | 39 ++++++++++++++++++- 4 files changed, 59 insertions(+), 5 deletions(-) diff --git a/backend/src/main/java/heartbeat/controller/board/dto/request/CardStepsEnum.java b/backend/src/main/java/heartbeat/controller/board/dto/request/CardStepsEnum.java index e3d1adc5bc..552887b35b 100644 --- a/backend/src/main/java/heartbeat/controller/board/dto/request/CardStepsEnum.java +++ b/backend/src/main/java/heartbeat/controller/board/dto/request/CardStepsEnum.java @@ -5,8 +5,8 @@ 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"), + ANALYSE("Analysis"), TODO("To do"), DEVELOPMENT("In Dev"), BLOCK("Block"), FLAG("FLAG"), REMOVEFLAG("removeFlag"), + REVIEW("Review"), WAITING("Waiting for testing"), TESTING("Testing"), DONE("Done"), CLOSED("Closed"), UNKNOWN("UNKNOWN"); private final String value; diff --git a/backend/src/main/java/heartbeat/controller/board/dto/response/JiraCardDTO.java b/backend/src/main/java/heartbeat/controller/board/dto/response/JiraCardDTO.java index af06779da7..0b47ca84af 100644 --- a/backend/src/main/java/heartbeat/controller/board/dto/response/JiraCardDTO.java +++ b/backend/src/main/java/heartbeat/controller/board/dto/response/JiraCardDTO.java @@ -8,9 +8,11 @@ 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.stream.Collectors; @Data @Builder @@ -32,6 +34,8 @@ public class JiraCardDTO { private Object cycleTimeFlat; + private Object reworkTimesFlat; + @Nullable private String totalCycleTimeDivideStoryPoints; @@ -60,6 +64,17 @@ public Object buildCycleTimeFlatObject() { return cycleTimeFlat; } + @JsonIgnore + public Object buildReworkTimesFlatObject() { + if (CollectionUtils.isEmpty(this.getReworkTimesInfos())) { + return null; + } + return this.getReworkTimesInfos() + .stream() + .collect(Collectors.toMap(reworkTimesInfo -> reworkTimesInfo.getState().getValue(), + ReworkTimesInfo::getTimes)); + } + @JsonIgnore public void calculateTotalReworkTimes() { this.totalReworkTimes = reworkTimesInfos.stream().mapToInt(ReworkTimesInfo::getTimes).sum(); diff --git a/backend/src/main/java/heartbeat/service/report/BoardSheetGenerator.java b/backend/src/main/java/heartbeat/service/report/BoardSheetGenerator.java index f7068a2999..9edb87bb68 100644 --- a/backend/src/main/java/heartbeat/service/report/BoardSheetGenerator.java +++ b/backend/src/main/java/heartbeat/service/report/BoardSheetGenerator.java @@ -3,6 +3,7 @@ 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; @@ -32,7 +33,10 @@ BoardSheetGenerator mergeBaseInfoAndCycleTimeSheet() { return this; } - BoardSheetGenerator generateReworkTimes() { + BoardSheetGenerator mergeReworkTimesSheet() { + if (CollectionUtils.isEmpty(reworkFields)) { + return this; + } int columnCount = reworkFields.size(); String[][] reworkTimesSheet = new String[jiraCardDTOList.size() + 1][columnCount]; diff --git a/backend/src/main/java/heartbeat/service/report/KanbanCsvService.java b/backend/src/main/java/heartbeat/service/report/KanbanCsvService.java index 73cc355623..9959a575b9 100644 --- a/backend/src/main/java/heartbeat/service/report/KanbanCsvService.java +++ b/backend/src/main/java/heartbeat/service/report/KanbanCsvService.java @@ -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; @@ -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 @@ -68,13 +71,28 @@ public void generateCsvInfo(GenerateReportRequest request, CardCollection realDo boardRequestParam.getToken()); JiraColumnResult jiraColumns = jiraService.getJiraColumns(boardRequestParam, baseUrl, jiraBoardConfigDTO); + List reworkFromStates = null; + CardStepsEnum reworkState = null; + if (request.getJiraBoardSetting().getReworkTimesSetting() != null) { + reworkState = request.getJiraBoardSetting().getReworkTimesSetting().getEnumReworkState(); + List 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 allDoneCards, List nonDoneCards, - List jiraColumns, List targetFields, String csvTimeStamp) { + List jiraColumns, List targetFields, String csvTimeStamp, + CardStepsEnum reworkState, List reworkFromStates) { List cardDTOList = new ArrayList<>(); List emptyJiraCard = List.of(JiraCardDTO.builder().build()); @@ -116,18 +134,35 @@ private void generateCSVForBoard(List allDoneCards, List reworkFields = new ArrayList<>(); + if (CollectionUtils.isNotEmpty(reworkFromStates) && reworkState != null) { + reworkFields.add(BoardCSVConfig.builder() + .label(reworkState.getValue() + "total rework times") + .value("totalReworkTimes") + .build()); + reworkFields.addAll(reworkFromStates.stream() + .map(state -> BoardCSVConfig.builder() + .label("from " + state + " to " + reworkState.getValue()) + .value("reworkTimesFlat." + state) + .build()) + .toList()); + } cardDTOList.forEach(card -> { card.setCycleTimeFlat(card.buildCycleTimeFlatObject()); card.setTotalCycleTimeDivideStoryPoints(card.getTotalCycleTimeDivideStoryPoints()); + card.setReworkTimesFlat(card.buildReworkTimesFlatObject()); }); String[][] sheet = BoardSheetGenerator.builder() .csvFileGenerator(csvFileGenerator) .jiraCardDTOList(cardDTOList) .fields(allBoardFields) .extraFields(newExtraFields) + .reworkFields(reworkFields) .build() .mergeBaseInfoAndCycleTimeSheet() + .mergeReworkTimesSheet() .generate(); csvFileGenerator.writeDataToCSV(csvTimeStamp, sheet); } From b42d47dea1063bc5dbbae69950ac153c4b93ba8c Mon Sep 17 00:00:00 2001 From: Genhao Liu Date: Tue, 19 Mar 2024 16:32:53 +0800 Subject: [PATCH 06/22] ADM-693:[backend] test: fix order of assert --- .../heartbeat/service/jira/JiraServiceTest.java | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/backend/src/test/java/heartbeat/service/jira/JiraServiceTest.java b/backend/src/test/java/heartbeat/service/jira/JiraServiceTest.java index 4493aa9211..c6c4e1e82e 100644 --- a/backend/src/test/java/heartbeat/service/jira/JiraServiceTest.java +++ b/backend/src/test/java/heartbeat/service/jira/JiraServiceTest.java @@ -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 @@ -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); } } From 3df85f67f2bbc85eda36d83566740bda70ce3090 Mon Sep 17 00:00:00 2001 From: yulongcai Date: Tue, 19 Mar 2024 17:48:43 +0800 Subject: [PATCH 07/22] ADM-693:[backend] fix: add rework total time in flat map --- .../heartbeat/controller/board/dto/response/JiraCardDTO.java | 5 ++++- .../java/heartbeat/service/report/BoardSheetGenerator.java | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/backend/src/main/java/heartbeat/controller/board/dto/response/JiraCardDTO.java b/backend/src/main/java/heartbeat/controller/board/dto/response/JiraCardDTO.java index 0b47ca84af..c4bf50d447 100644 --- a/backend/src/main/java/heartbeat/controller/board/dto/response/JiraCardDTO.java +++ b/backend/src/main/java/heartbeat/controller/board/dto/response/JiraCardDTO.java @@ -12,6 +12,7 @@ import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; @Data @@ -69,10 +70,12 @@ public Object buildReworkTimesFlatObject() { if (CollectionUtils.isEmpty(this.getReworkTimesInfos())) { return null; } - return this.getReworkTimesInfos() + Map reworkTimesMap = this.getReworkTimesInfos() .stream() .collect(Collectors.toMap(reworkTimesInfo -> reworkTimesInfo.getState().getValue(), ReworkTimesInfo::getTimes)); + reworkTimesMap.put("totalReworkTimes", totalReworkTimes); + return reworkTimesMap; } @JsonIgnore diff --git a/backend/src/main/java/heartbeat/service/report/BoardSheetGenerator.java b/backend/src/main/java/heartbeat/service/report/BoardSheetGenerator.java index 9edb87bb68..d23a5836ce 100644 --- a/backend/src/main/java/heartbeat/service/report/BoardSheetGenerator.java +++ b/backend/src/main/java/heartbeat/service/report/BoardSheetGenerator.java @@ -46,7 +46,7 @@ BoardSheetGenerator mergeReworkTimesSheet() { 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.getCycleTimeFlat(), + reworkTimesSheet[row + 1][column] = csvFileGenerator.getExtraDataPerRow(cardDTO.getReworkTimesFlat(), reworkFields.get(column)); } } From e89c08fc03ab58c8ec9beca606e587c1c59852e9 Mon Sep 17 00:00:00 2001 From: GuangbinMa Date: Wed, 20 Mar 2024 10:33:49 +0800 Subject: [PATCH 08/22] ADM-693: [frontend] fix: fix request param issue --- .../src/containers/ReportStep/BoardMetrics/index.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/frontend/src/containers/ReportStep/BoardMetrics/index.tsx b/frontend/src/containers/ReportStep/BoardMetrics/index.tsx index 2ddcfb03e2..a94e68fe19 100644 --- a/frontend/src/containers/ReportStep/BoardMetrics/index.tsx +++ b/frontend/src/containers/ReportStep/BoardMetrics/index.tsx @@ -94,10 +94,12 @@ const BoardMetrics = ({ assigneeFilter, targetFields: formatDuplicatedNameWithSuffix(targetFields), doneColumn: getRealDoneStatus(cycleTimeSettings, cycleTimeSettingsType, doneColumn), - reworkTimesSetting: { - reworkState: reworkTimesSettings.rework2State, - excludedStates: reworkTimesSettings.excludeStates, - }, + reworkTimesSetting: boardMetrics.includes(REQUIRED_DATA.REWORK_TIMES) + ? { + reworkState: reworkTimesSettings.rework2State, + excludedStates: reworkTimesSettings.excludeStates, + } + : {}, overrideFields: [ { name: 'Story Points', From 3998b7fe46fc6720ebc1d2188e8951aaf28b8fc2 Mon Sep 17 00:00:00 2001 From: Genhao Liu Date: Wed, 20 Mar 2024 14:07:42 +0800 Subject: [PATCH 09/22] ADM-693:[backend] fix: format rework fields --- .../main/java/heartbeat/service/report/KanbanCsvService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/main/java/heartbeat/service/report/KanbanCsvService.java b/backend/src/main/java/heartbeat/service/report/KanbanCsvService.java index 9959a575b9..948cfc9828 100644 --- a/backend/src/main/java/heartbeat/service/report/KanbanCsvService.java +++ b/backend/src/main/java/heartbeat/service/report/KanbanCsvService.java @@ -138,7 +138,7 @@ private void generateCSVForBoard(List allDoneCards, List reworkFields = new ArrayList<>(); if (CollectionUtils.isNotEmpty(reworkFromStates) && reworkState != null) { reworkFields.add(BoardCSVConfig.builder() - .label(reworkState.getValue() + "total rework times") + .label(reworkState.getValue() + " total rework times") .value("totalReworkTimes") .build()); reworkFields.addAll(reworkFromStates.stream() From e178eac38a4b8a2a46d70d920305e05c41bb842b Mon Sep 17 00:00:00 2001 From: Genhao Liu Date: Wed, 20 Mar 2024 14:18:32 +0800 Subject: [PATCH 10/22] ADM-693:[backend] fix: use alias temporarily --- .../board/dto/request/CardStepsEnum.java | 16 ++++++++++++---- .../service/report/KanbanCsvService.java | 4 ++-- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/backend/src/main/java/heartbeat/controller/board/dto/request/CardStepsEnum.java b/backend/src/main/java/heartbeat/controller/board/dto/request/CardStepsEnum.java index 552887b35b..d8a5f53fd1 100644 --- a/backend/src/main/java/heartbeat/controller/board/dto/request/CardStepsEnum.java +++ b/backend/src/main/java/heartbeat/controller/board/dto/request/CardStepsEnum.java @@ -5,20 +5,28 @@ public enum CardStepsEnum { - ANALYSE("Analysis"), TODO("To do"), DEVELOPMENT("In Dev"), BLOCK("Block"), FLAG("FLAG"), REMOVEFLAG("removeFlag"), - REVIEW("Review"), WAITING("Waiting for testing"), TESTING("Testing"), DONE("Done"), CLOSED("Closed"), - UNKNOWN("UNKNOWN"); + ANALYSE("Analysis", "Analysis"), TODO("To do", "To do"), 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)) { diff --git a/backend/src/main/java/heartbeat/service/report/KanbanCsvService.java b/backend/src/main/java/heartbeat/service/report/KanbanCsvService.java index 948cfc9828..105f08ca51 100644 --- a/backend/src/main/java/heartbeat/service/report/KanbanCsvService.java +++ b/backend/src/main/java/heartbeat/service/report/KanbanCsvService.java @@ -138,12 +138,12 @@ private void generateCSVForBoard(List allDoneCards, List reworkFields = new ArrayList<>(); if (CollectionUtils.isNotEmpty(reworkFromStates) && reworkState != null) { reworkFields.add(BoardCSVConfig.builder() - .label(reworkState.getValue() + " total rework times") + .label(reworkState.getAlias() + " total rework times") .value("totalReworkTimes") .build()); reworkFields.addAll(reworkFromStates.stream() .map(state -> BoardCSVConfig.builder() - .label("from " + state + " to " + reworkState.getValue()) + .label("from " + state + " to " + reworkState.getAlias()) .value("reworkTimesFlat." + state) .build()) .toList()); From 15bef5bfdf48c78316da1858ee20533bcb92f499 Mon Sep 17 00:00:00 2001 From: Genhao Liu Date: Wed, 20 Mar 2024 15:52:07 +0800 Subject: [PATCH 11/22] ADM-693:[backend] test: add rework fields in sheet --- .../service/report/BoardSheetGenerator.java | 2 +- .../service/report/BoardCsvFixture.java | 10 +++ .../service/report/KanbanCsvServiceTest.java | 74 +++++++++++++++++++ 3 files changed, 85 insertions(+), 1 deletion(-) diff --git a/backend/src/main/java/heartbeat/service/report/BoardSheetGenerator.java b/backend/src/main/java/heartbeat/service/report/BoardSheetGenerator.java index d23a5836ce..453713b72f 100644 --- a/backend/src/main/java/heartbeat/service/report/BoardSheetGenerator.java +++ b/backend/src/main/java/heartbeat/service/report/BoardSheetGenerator.java @@ -55,7 +55,7 @@ BoardSheetGenerator mergeReworkTimesSheet() { } private String[][] mergeSheetHorizontally(String[][] sheet, String[][] sheetToMerge) { - int rows = jiraCardDTOList.size(); + int rows = jiraCardDTOList.size() + 1; String[][] combinedArray = new String[rows][]; if (ArrayUtils.isEmpty(sheet)) { return sheetToMerge; diff --git a/backend/src/test/java/heartbeat/service/report/BoardCsvFixture.java b/backend/src/test/java/heartbeat/service/report/BoardCsvFixture.java index d82283b2d3..8acfed95d3 100644 --- a/backend/src/test/java/heartbeat/service/report/BoardCsvFixture.java +++ b/backend/src/test/java/heartbeat/service/report/BoardCsvFixture.java @@ -9,6 +9,7 @@ import heartbeat.client.dto.board.jira.JiraCardField; import heartbeat.client.dto.board.jira.Sprint; import heartbeat.client.dto.board.jira.Status; +import heartbeat.controller.board.dto.request.CardStepsEnum; import heartbeat.controller.board.dto.response.CardCycleTime; import heartbeat.controller.board.dto.response.CardParent; import heartbeat.controller.board.dto.response.ColumnValue; @@ -21,6 +22,7 @@ import heartbeat.controller.board.dto.response.JiraProject; import heartbeat.controller.board.dto.response.Priority; import heartbeat.controller.board.dto.response.Reporter; +import heartbeat.controller.board.dto.response.ReworkTimesInfo; import heartbeat.controller.board.dto.response.StepsDay; import heartbeat.controller.board.dto.response.TargetField; import heartbeat.controller.report.dto.response.BoardCSVConfig; @@ -510,4 +512,12 @@ public static List MOCK_JIRA_COLUMN_LIST() { .build()); } + public static List MOCK_REWORK_TIMES_INFO_LIST() { + return List.of(ReworkTimesInfo.builder().state(CardStepsEnum.BLOCK).times(2).build(), + ReworkTimesInfo.builder().state(CardStepsEnum.REVIEW).times(0).build(), + ReworkTimesInfo.builder().state(CardStepsEnum.WAITING).times(1).build(), + ReworkTimesInfo.builder().state(CardStepsEnum.TESTING).times(0).build(), + ReworkTimesInfo.builder().state(CardStepsEnum.DONE).times(0).build()); + } + } diff --git a/backend/src/test/java/heartbeat/service/report/KanbanCsvServiceTest.java b/backend/src/test/java/heartbeat/service/report/KanbanCsvServiceTest.java index 3e22f4c071..9e637a0de8 100644 --- a/backend/src/test/java/heartbeat/service/report/KanbanCsvServiceTest.java +++ b/backend/src/test/java/heartbeat/service/report/KanbanCsvServiceTest.java @@ -7,6 +7,7 @@ import heartbeat.client.dto.board.jira.JiraCard; import heartbeat.client.dto.board.jira.JiraCardField; import heartbeat.client.dto.board.jira.Status; +import heartbeat.controller.board.dto.request.ReworkTimesSetting; import heartbeat.controller.board.dto.response.CardCollection; import heartbeat.controller.board.dto.response.ColumnValue; import heartbeat.controller.board.dto.response.CycleTimeInfo; @@ -35,11 +36,13 @@ import java.util.List; import static heartbeat.service.report.BoardCsvFixture.MOCK_JIRA_CARD; +import static heartbeat.service.report.BoardCsvFixture.MOCK_REWORK_TIMES_INFO_LIST; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -65,6 +68,9 @@ class KanbanCsvServiceTest { @Captor private ArgumentCaptor> csvFieldsCaptor; + @Captor + private ArgumentCaptor csvSheetCaptor; + @Captor private ArgumentCaptor> csvNewFieldsCaptor; @@ -611,4 +617,72 @@ void shouldAddFixedFieldsWithCorrectValueFormatWhenCustomFieldValueInstanceOfLis assertEquals("baseInfo.fields.customFields.json-array[0]", targetValue5.getValue()); } + @Test + void shouldAddReworkFieldsWhenGenerateSheetGivenReworkStateAndExcludeState() throws URISyntaxException { + URI uri = new URI("site-uri"); + when(urlGenerator.getUri(any())).thenReturn(uri); + when(jiraService.getJiraBoardConfig(any(), any(), any())).thenReturn(JiraBoardConfigDTO.builder().build()); + when(jiraService.getJiraColumns(any(), any(), any())).thenReturn(JiraColumnResult.builder() + .jiraColumnResponse(List + .of(JiraColumnDTO.builder().value(ColumnValue.builder().statuses(List.of("BLOCKED")).build()).build())) + .build()); + JiraCard jiraCard = JiraCard.builder().fields(MOCK_JIRA_CARD()).build(); + JiraCard jiraCard2 = JiraCard.builder().fields(MOCK_JIRA_CARD()).build(); + jiraCard2.getFields().setLastStatusChangeDate(1701251323000L); + List jiraCardDTOS = new ArrayList<>(List.of( + JiraCardDTO.builder() + .baseInfo(jiraCard) + .reworkTimesInfos(MOCK_REWORK_TIMES_INFO_LIST()) + .totalReworkTimes(3) + .build(), + JiraCardDTO.builder() + .baseInfo(jiraCard2) + .reworkTimesInfos(MOCK_REWORK_TIMES_INFO_LIST()) + .totalReworkTimes(3) + .build())); + JiraCardDTO blockedJiraCard = JiraCardDTO.builder() + .baseInfo(JiraCard.builder().fields(MOCK_JIRA_CARD()).build()) + .build(); + List NonDoneJiraCardDTOList = new ArrayList<>() { + { + add(blockedJiraCard); + } + }; + String[][] fakeSringArray = new String[][] { { "cycle time" }, { "1" }, { "2" }, { "3" }, { "4" } }; + when(csvFileGenerator.assembleBoardData(anyList(), anyList(), anyList())).thenReturn(fakeSringArray); + kanbanCsvService.generateCsvInfo( + GenerateReportRequest.builder() + .jiraBoardSetting(JiraBoardSetting.builder() + .targetFields(List.of( + TargetField.builder().name("assignee").flag(true).key("key-assignee").build(), + TargetField.builder().name("fake-target1").flag(true).key("key-target1").build(), + TargetField.builder().name("fake-target2").flag(false).key("key-target2").build())) + .reworkTimesSetting(ReworkTimesSetting.builder() + .reworkState("In Dev") + .excludedStates(List.of("Review")) + .build()) + .build()) + .csvTimeStamp("2022-01-01 00:00:00") + .build(), + CardCollection.builder().jiraCardDTOList(jiraCardDTOS).build(), + CardCollection.builder().jiraCardDTOList(NonDoneJiraCardDTOList).build()); + + verify(csvFileGenerator).assembleBoardData(anyList(), csvFieldsCaptor.capture(), anyList()); + verify(csvFileGenerator).writeDataToCSV(anyString(), csvSheetCaptor.capture()); + + assertEquals(23, csvFieldsCaptor.getValue().size()); + BoardCSVConfig targetValue = csvFieldsCaptor.getValue().get(22); + assertEquals("cardCycleTime.steps.review", targetValue.getValue()); + assertEquals("Review Days", targetValue.getLabel()); + assertNull(targetValue.getOriginKey()); + + assertEquals(5, csvSheetCaptor.getValue().length); + assertEquals("cycle time", csvSheetCaptor.getValue()[0][0]); + assertEquals("In dev total rework times", csvSheetCaptor.getValue()[0][1]); + assertEquals("from Block to In dev", csvSheetCaptor.getValue()[0][2]); + assertEquals("from Waiting for testing to In dev", csvSheetCaptor.getValue()[0][3]); + assertEquals("from Testing to In dev", csvSheetCaptor.getValue()[0][4]); + assertEquals("from Done to In dev", csvSheetCaptor.getValue()[0][5]); + } + } From d313f43ede2e536e67506bd3f911a080fd2087bb Mon Sep 17 00:00:00 2001 From: Genhao Liu Date: Wed, 20 Mar 2024 16:23:32 +0800 Subject: [PATCH 12/22] ADM-693:[backend] refactor: only one condition --- .../main/java/heartbeat/service/report/KanbanCsvService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/main/java/heartbeat/service/report/KanbanCsvService.java b/backend/src/main/java/heartbeat/service/report/KanbanCsvService.java index 105f08ca51..83650195d0 100644 --- a/backend/src/main/java/heartbeat/service/report/KanbanCsvService.java +++ b/backend/src/main/java/heartbeat/service/report/KanbanCsvService.java @@ -136,7 +136,7 @@ private void generateCSVForBoard(List allDoneCards, List reworkFields = new ArrayList<>(); - if (CollectionUtils.isNotEmpty(reworkFromStates) && reworkState != null) { + if (reworkState != null) { reworkFields.add(BoardCSVConfig.builder() .label(reworkState.getAlias() + " total rework times") .value("totalReworkTimes") From cbb69c484b791ed30105beacc19a16445ca4e149 Mon Sep 17 00:00:00 2001 From: Genhao Liu Date: Wed, 20 Mar 2024 16:34:47 +0800 Subject: [PATCH 13/22] ADM-693:[backend] fix: pmd check --- .../main/java/heartbeat/service/board/jira/JiraService.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/backend/src/main/java/heartbeat/service/board/jira/JiraService.java b/backend/src/main/java/heartbeat/service/board/jira/JiraService.java index 9f3581205b..113c876f34 100644 --- a/backend/src/main/java/heartbeat/service/board/jira/JiraService.java +++ b/backend/src/main/java/heartbeat/service/board/jira/JiraService.java @@ -726,9 +726,7 @@ private void calculateTimes(CardStepsEnum reworkState, Set exclud return; } if (isRework(from, to, excludedStates)) { - if (reworkTimesMap.containsKey(from)) { - reworkTimesMap.put(from, reworkTimesMap.get(from) + 1); - } + reworkTimesMap.computeIfPresent(from, (key, value) -> value + 1); } } From f185970af5ff0bf08eba754b7f20b27e6b22632d Mon Sep 17 00:00:00 2001 From: Genhao Liu Date: Wed, 20 Mar 2024 17:31:32 +0800 Subject: [PATCH 14/22] ADM-693:[backend] refactor: rename method --- .../java/heartbeat/service/report/KanbanCsvServiceTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/test/java/heartbeat/service/report/KanbanCsvServiceTest.java b/backend/src/test/java/heartbeat/service/report/KanbanCsvServiceTest.java index 9e637a0de8..e9d6914ba1 100644 --- a/backend/src/test/java/heartbeat/service/report/KanbanCsvServiceTest.java +++ b/backend/src/test/java/heartbeat/service/report/KanbanCsvServiceTest.java @@ -618,7 +618,7 @@ void shouldAddFixedFieldsWithCorrectValueFormatWhenCustomFieldValueInstanceOfLis } @Test - void shouldAddReworkFieldsWhenGenerateSheetGivenReworkStateAndExcludeState() throws URISyntaxException { + void shouldAddReworkFieldsWhenGenerateSheetGivenReworkStateAndExcludedStates() throws URISyntaxException { URI uri = new URI("site-uri"); when(urlGenerator.getUri(any())).thenReturn(uri); when(jiraService.getJiraBoardConfig(any(), any(), any())).thenReturn(JiraBoardConfigDTO.builder().build()); From 6172738bc0fe47ff31eadd0a72b2829016ffd521 Mon Sep 17 00:00:00 2001 From: Genhao Liu Date: Wed, 20 Mar 2024 17:38:59 +0800 Subject: [PATCH 15/22] ADM-693:[backend] refactor: sort card steps enum --- .../heartbeat/controller/board/dto/request/CardStepsEnum.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/main/java/heartbeat/controller/board/dto/request/CardStepsEnum.java b/backend/src/main/java/heartbeat/controller/board/dto/request/CardStepsEnum.java index 5ec34399fb..22ce0102b5 100644 --- a/backend/src/main/java/heartbeat/controller/board/dto/request/CardStepsEnum.java +++ b/backend/src/main/java/heartbeat/controller/board/dto/request/CardStepsEnum.java @@ -5,7 +5,7 @@ public enum CardStepsEnum { - ANALYSE("Analysis", "Analysis"), TODO("To do", "To do"), DEVELOPMENT("In Dev", "In dev"), BLOCK("Block", "Block"), + 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"); From 19887501a1af4106f174e70934feeb833aeb2ab3 Mon Sep 17 00:00:00 2001 From: yulongcai Date: Thu, 21 Mar 2024 10:17:58 +0800 Subject: [PATCH 16/22] ADM-693:[docs] feat: return throughput in rework info --- .../heartbeat/controller/report/dto/response/Rework.java | 2 ++ .../service/report/calculator/ReworkCalculator.java | 1 + .../heartbeat/service/report/ReworkCalculatorTest.java | 1 + .../test/java/heartbeat/service/report/ReworkFixture.java | 7 ++++++- 4 files changed, 10 insertions(+), 1 deletion(-) diff --git a/backend/src/main/java/heartbeat/controller/report/dto/response/Rework.java b/backend/src/main/java/heartbeat/controller/report/dto/response/Rework.java index d450fad4d0..c3f462d278 100644 --- a/backend/src/main/java/heartbeat/controller/report/dto/response/Rework.java +++ b/backend/src/main/java/heartbeat/controller/report/dto/response/Rework.java @@ -29,6 +29,8 @@ public class Rework { private Integer totalReworkCards; + private Integer throughput; + private Double reworkCardsRatio; } diff --git a/backend/src/main/java/heartbeat/service/report/calculator/ReworkCalculator.java b/backend/src/main/java/heartbeat/service/report/calculator/ReworkCalculator.java index 42b506e298..b921b6e532 100644 --- a/backend/src/main/java/heartbeat/service/report/calculator/ReworkCalculator.java +++ b/backend/src/main/java/heartbeat/service/report/calculator/ReworkCalculator.java @@ -17,6 +17,7 @@ public Rework calculateRework(CardCollection realDoneCardCollection, CardStepsEn .reworkState(reworkState.getValue()) .reworkCardsRatio(realDoneCardCollection.getReworkRatio()) .totalReworkCards(realDoneCardCollection.getReworkCardNumber()) + .throughput(realDoneCardCollection.getCardsNumber()) .totalReworkTimes(0) .build(); realDoneCardCollection.getJiraCardDTOList() diff --git a/backend/src/test/java/heartbeat/service/report/ReworkCalculatorTest.java b/backend/src/test/java/heartbeat/service/report/ReworkCalculatorTest.java index 648a5e936e..c6784cfcd3 100644 --- a/backend/src/test/java/heartbeat/service/report/ReworkCalculatorTest.java +++ b/backend/src/test/java/heartbeat/service/report/ReworkCalculatorTest.java @@ -37,6 +37,7 @@ void shouldReturnAllDoneCardsReworkTimesWhenCallCalculateRework() { assertEquals(2, rework.getFromTesting()); assertEquals(2, rework.getFromReview()); assertEquals(2, rework.getFromDone()); + assertEquals(2, rework.getThroughput()); } @Test diff --git a/backend/src/test/java/heartbeat/service/report/ReworkFixture.java b/backend/src/test/java/heartbeat/service/report/ReworkFixture.java index 00180b22ba..0b3f2de3bf 100644 --- a/backend/src/test/java/heartbeat/service/report/ReworkFixture.java +++ b/backend/src/test/java/heartbeat/service/report/ReworkFixture.java @@ -27,7 +27,12 @@ public static CardCollection MOCK_CARD_COLLECTION() { ReworkTimesInfo.builder().state(DONE).times(1).build()); List jiraCardList = List.of(JiraCardDTO.builder().reworkTimesInfos(reworkTimesInfos).build(), JiraCardDTO.builder().reworkTimesInfos(reworkTimesInfos).build()); - return CardCollection.builder().reworkCardNumber(2).reworkRatio(1).jiraCardDTOList(jiraCardList).build(); + return CardCollection.builder() + .reworkCardNumber(2) + .cardsNumber(2) + .reworkRatio(1) + .jiraCardDTOList(jiraCardList) + .build(); } public static CardCollection MOCK_CARD_COLLECTION_WITH_TODO() { From ff256482f06e529909846504e12f48e9df1830f2 Mon Sep 17 00:00:00 2001 From: "yp.wu" Date: Wed, 20 Mar 2024 15:29:40 +0800 Subject: [PATCH 17/22] AMD-694 [frontend] test: add e2e for board metric rework both for create-a-new-project.spec and import-project-from-file.spec --- .../e2e/fixtures/createNew/reportResult.ts | 6 +++++ .../add-flag-as-block-config-file.ts | 6 ++++- .../importFile/multiple-done-config-file.ts | 5 ++++ frontend/e2e/pages/metrics/ReportStep.ts | 23 +++++++++++++++++++ .../e2e/specs/create-a-new-project.spec.ts | 3 +++ .../specs/import-project-from-file.spec.ts | 6 +++++ 6 files changed, 48 insertions(+), 1 deletion(-) diff --git a/frontend/e2e/fixtures/createNew/reportResult.ts b/frontend/e2e/fixtures/createNew/reportResult.ts index c4b60efdff..5bd00f46cd 100644 --- a/frontend/e2e/fixtures/createNew/reportResult.ts +++ b/frontend/e2e/fixtures/createNew/reportResult.ts @@ -3,6 +3,9 @@ export const BOARD_METRICS_RESULT = { Throughput: '9', AverageCycleTime4SP: '4.86', AverageCycleTime4Card: '9.18', + totalReworkTimes: '11.00', + totalReworkCards: '6.00', + reworkCardsRatio: '0.67', }; export const FLAG_AS_BLOCK_PROJECT_BOARD_METRICS_RESULT = { @@ -10,6 +13,9 @@ export const FLAG_AS_BLOCK_PROJECT_BOARD_METRICS_RESULT = { Throughput: '5', AverageCycleTime4SP: '0.19', AverageCycleTime4Card: '0.28', + totalReworkTimes: '3.00', + totalReworkCards: '3.00', + reworkCardsRatio: '0.60', }; export const DORA_METRICS_RESULT = { diff --git a/frontend/e2e/fixtures/importFile/add-flag-as-block-config-file.ts b/frontend/e2e/fixtures/importFile/add-flag-as-block-config-file.ts index 0b8f728e20..c18cf8c2b9 100644 --- a/frontend/e2e/fixtures/importFile/add-flag-as-block-config-file.ts +++ b/frontend/e2e/fixtures/importFile/add-flag-as-block-config-file.ts @@ -5,7 +5,7 @@ export const importFlagAsBlockFile = { endDate: '2024-02-01T23:59:59.999+08:00', }, calendarType: 'Calendar with Chinese Holiday', - metrics: ['Velocity', 'Cycle time', 'Classification'], + metrics: ['Velocity', 'Cycle time', 'Classification', 'Rework times'], board: { type: 'Jira', boardId: '1', @@ -47,4 +47,8 @@ export const importFlagAsBlockFile = { 'assignee', 'customfield_10030', ], + reworkTimesSettings: { + rework2State: 'In Dev', + excludeStates: [], + }, }; diff --git a/frontend/e2e/fixtures/importFile/multiple-done-config-file.ts b/frontend/e2e/fixtures/importFile/multiple-done-config-file.ts index 6a294cbf27..d2dedaf658 100644 --- a/frontend/e2e/fixtures/importFile/multiple-done-config-file.ts +++ b/frontend/e2e/fixtures/importFile/multiple-done-config-file.ts @@ -10,6 +10,7 @@ export const importMultipleDoneProjectFromFile = { 'Velocity', 'Cycle time', 'Classification', + 'Rework times', 'Lead time for changes', 'Deployment frequency', 'Dev change failure rate', @@ -97,4 +98,8 @@ export const importMultipleDoneProjectFromFile = { branches: ['main'], }, ], + reworkTimesSettings: { + rework2State: 'In Dev', + excludeStates: [], + }, }; diff --git a/frontend/e2e/pages/metrics/ReportStep.ts b/frontend/e2e/pages/metrics/ReportStep.ts index 2310b75518..2f1c5275b4 100644 --- a/frontend/e2e/pages/metrics/ReportStep.ts +++ b/frontend/e2e/pages/metrics/ReportStep.ts @@ -15,6 +15,7 @@ export class ReportStep { readonly velocityPart: Locator; readonly averageCycleTimeForSP: Locator; readonly averageCycleTimeForCard: Locator; + readonly boardMetricRework: Locator; readonly prLeadTime: Locator; readonly pipelineLeadTime: Locator; readonly totalLeadTime: Locator; @@ -34,6 +35,7 @@ export class ReportStep { readonly leadTimeForChangesRows: Locator; readonly devChangeFailureRateRows: Locator; readonly devMeanTimeToRecoveryRows: Locator; + readonly reworkRows: Locator; constructor(page: Page) { this.page = page; @@ -41,6 +43,8 @@ export class ReportStep { this.velocityPart = this.page.locator('[data-test-id="Velocity"] [data-test-id="report-section"]'); this.averageCycleTimeForSP = this.page.locator('[data-test-id="Cycle Time"] [data-test-id="report-section"]'); this.averageCycleTimeForCard = this.page.locator('[data-test-id="Cycle Time"] [data-test-id="report-section"]'); + this.boardMetricRework = this.page.locator('[data-test-id="Rework"] [data-test-id="report-section"]'); + this.prLeadTime = this.page.locator('[data-test-id="Lead Time For Changes"] [data-test-id="report-section"]'); this.pipelineLeadTime = this.page.locator('[data-test-id="Lead Time For Changes"] [data-test-id="report-section"]'); this.totalLeadTime = this.page.locator('[data-test-id="Lead Time For Changes"] [data-test-id="report-section"]'); @@ -64,6 +68,7 @@ export class ReportStep { this.leadTimeForChangesRows = this.page.getByTestId('Lead Time For Changes').getByRole('row'); this.devChangeFailureRateRows = this.page.getByTestId('Dev Change Failure Rate').getByRole('row'); this.devMeanTimeToRecoveryRows = this.page.getByTestId('Dev Mean Time To Recovery').getByRole('row'); + this.reworkRows = this.page.getByTestId('Rework').getByRole('row'); } combineStrings(arr: string[]): string { return arr.join(''); @@ -125,11 +130,17 @@ export class ReportStep { throughPut: string, averageCycleTimeForSP: string, averageCycleTimeForCard: string, + totalReworkTimes: string, + totalReworkCards: string, + reworkCardsRatio: string, ) { await expect(this.velocityPart).toContainText(`${velocity}Velocity(Story Point)`); await expect(this.velocityPart).toContainText(`${throughPut}Throughput(Cards Count)`); await expect(this.averageCycleTimeForSP).toContainText(`${averageCycleTimeForSP}Average Cycle Time(Days/SP)`); await expect(this.averageCycleTimeForCard).toContainText(`${averageCycleTimeForCard}Average Cycle Time(Days/Card)`); + await expect(this.boardMetricRework).toContainText(`${totalReworkTimes}Total rework times`); + await expect(this.boardMetricRework).toContainText(`${totalReworkCards}Total rework cards`); + await expect(this.boardMetricRework).toContainText(`${reworkCardsRatio}Rework cards ratio`); } async checkBoardMetricsReportReportDetail() { @@ -215,6 +226,18 @@ export class ReportStep { await expect(this.classificationRows.nth(44)).toContainText(this.combineStrings(['Weiran Sun', '11.11%'])); await expect(this.classificationRows.nth(45)).toContainText(this.combineStrings(['Xuebing Li', '11.11%'])); await expect(this.classificationRows.nth(46)).toContainText(this.combineStrings(['Yunsong Yang', '11.11%'])); + await expect(this.reworkRows.filter({ hasText: 'Total rework' }).getByRole('cell').nth(1)).toContainText( + '11 (times)', + ); + await expect(this.reworkRows.filter({ hasText: 'From block to In Dev' }).getByRole('cell').nth(1)).toContainText( + '11 (times)', + ); + await expect(this.reworkRows.filter({ hasText: 'Total rework cards' }).getByRole('cell').nth(1)).toContainText( + '6 (cards)', + ); + await expect(this.reworkRows.filter({ hasText: 'Rework cards ratio' }).getByRole('cell').nth(1)).toContainText( + '0.67 (rework card/throughput)', + ); } async checkBoardMetricsDetails(boardDetailType: ProjectCreationType, csvCompareLines: number) { diff --git a/frontend/e2e/specs/create-a-new-project.spec.ts b/frontend/e2e/specs/create-a-new-project.spec.ts index 6840ca6cd3..b4c3a6a47c 100644 --- a/frontend/e2e/specs/create-a-new-project.spec.ts +++ b/frontend/e2e/specs/create-a-new-project.spec.ts @@ -65,6 +65,9 @@ test('Create a new project', async ({ homePage, configStep, metricsStep, reportS BOARD_METRICS_RESULT.Throughput, BOARD_METRICS_RESULT.AverageCycleTime4SP, BOARD_METRICS_RESULT.AverageCycleTime4Card, + BOARD_METRICS_RESULT.totalReworkTimes, + BOARD_METRICS_RESULT.totalReworkCards, + BOARD_METRICS_RESULT.reworkCardsRatio, ); await reportStep.checkBoardMetricsDetails(ProjectCreationType.CREATE_A_NEW_PROJECT, 9); await reportStep.checkDoraMetrics( diff --git a/frontend/e2e/specs/import-project-from-file.spec.ts b/frontend/e2e/specs/import-project-from-file.spec.ts index 571559ae29..9d572adb5c 100644 --- a/frontend/e2e/specs/import-project-from-file.spec.ts +++ b/frontend/e2e/specs/import-project-from-file.spec.ts @@ -36,6 +36,9 @@ test('Import project from file', async ({ homePage, configStep, metricsStep, rep BOARD_METRICS_RESULT.Throughput, BOARD_METRICS_RESULT.AverageCycleTime4SP, BOARD_METRICS_RESULT.AverageCycleTime4Card, + BOARD_METRICS_RESULT.totalReworkTimes, + BOARD_METRICS_RESULT.totalReworkCards, + BOARD_METRICS_RESULT.reworkCardsRatio, ); await reportStep.checkBoardMetricsDetails(ProjectCreationType.IMPORT_PROJECT_FROM_FILE, 9); await reportStep.checkDoraMetricsDetails(ProjectCreationType.IMPORT_PROJECT_FROM_FILE); @@ -56,5 +59,8 @@ test('Import project from flag as block', async ({ homePage, configStep, metrics FLAG_AS_BLOCK_PROJECT_BOARD_METRICS_RESULT.Throughput, FLAG_AS_BLOCK_PROJECT_BOARD_METRICS_RESULT.AverageCycleTime4SP, FLAG_AS_BLOCK_PROJECT_BOARD_METRICS_RESULT.AverageCycleTime4Card, + FLAG_AS_BLOCK_PROJECT_BOARD_METRICS_RESULT.totalReworkTimes, + FLAG_AS_BLOCK_PROJECT_BOARD_METRICS_RESULT.totalReworkCards, + FLAG_AS_BLOCK_PROJECT_BOARD_METRICS_RESULT.reworkCardsRatio, ); }); From 2e9059357ec8b904301ba22a2329070749858c04 Mon Sep 17 00:00:00 2001 From: "yp.wu" Date: Thu, 21 Mar 2024 11:21:06 +0800 Subject: [PATCH 18/22] AMD-693 [frontend] fix: add some columns in board metric.csv --- frontend/e2e/fixtures/createNew/boardData.csv | 48 +++++++++---------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/frontend/e2e/fixtures/createNew/boardData.csv b/frontend/e2e/fixtures/createNew/boardData.csv index 3b1b74f835..5f0a5f192d 100644 --- a/frontend/e2e/fixtures/createNew/boardData.csv +++ b/frontend/e2e/fixtures/createNew/boardData.csv @@ -1,24 +1,24 @@ -"Issue key","Summary","Issue Type","Status","Status Date","Story Points","assignee","Reporter","Project Key","Project Name","Priority","Parent Summary","Sprint","Labels","Cycle Time","Story testing-1","Flagged","Fix versions","Partner","Time tracking","Story point estimate","QA","Feature/Operation","Story testing-2","Cycle Time / Story Points","Analysis Days","In Dev Days","Waiting Days","Testing Days","Block Days","Review Days","OriginCycleTime: TODO","OriginCycleTime: TESTING","OriginCycleTime: WAIT FOR TEST","OriginCycleTime: DOING","OriginCycleTime: REVIEW","OriginCycleTime: FLAG","OriginCycleTime: BLOCKED" -"ADM-735","[backend]identify the source of the error when generate reports encounter exception","Task","Done","2024-01-19","1.0","Yunsong Yang","Yunsong Yang","ADM","Auto Dora Metrics","Medium","Precise on Metrics","Sprint 28","Stream2","7.70","1.0","","","","None","1.0","","","","7.70","0","2.02","1.81","0","0","3.87","3.03","0","1.81","2.02","3.87","0","0" -"ADM-708","[Backend] Verify board and obtain board data with new API","Task","Done","2024-01-19","3.0","Weiran Sun","heartbeat user","ADM","Auto Dora Metrics","Medium","easy to use","Sprint 28","Stream1","9.95","1.0","","","","None","3.0","","","","3.32","0","4.00","0.93","1.04","0.98","3.00","7.10","1.04","0.93","4.00","3.00","0","0.98" -"ADM-699","[Frontend] Optimize the 4xx&504 error display of report overview","Task","Done","2024-01-18","2.0","heartbeat user","heartbeat user","ADM","Auto Dora Metrics","Medium","Performance Improvement","Sprint 28","Stream2","10.93","1.0","","","","None","2.0","","","","5.46","0","5.14","0.04","0.78","2.01","2.96","10.75","0.78","0.04","5.14","2.96","0","2.01" -"ADM-717","[Backend] Verify github and obtain github data with new API","Task","Done","2024-01-17","2.0","Junbo Dai","Yufan Wang","ADM","Auto Dora Metrics","Medium","easy to use","Sprint 28","Stream1","8.09","1.0","","","","None","2.0","Weiran Sun","","","4.04","0","2.83","2.72","0.05","2.14","0.35","6.00","0.05","2.72","2.83","0.35","0","2.14" -"ADM-724","[Spike] redesign board verify API to meet business requirements","Spike","Done","2024-01-17","1.0","heartbeat user","Yufan Wang","ADM","Auto Dora Metrics","Medium","easy to use","Sprint 28","Stream1","12.94","","","","","None","1.0","","","","12.94","0","1.08","1.99","0","7.65","2.22","0.27","0","1.99","1.08","2.22","0","7.65" -"ADM-652","[Frontend]Generate the separate modules detail report","Task","Done","2024-01-17","3.0","Xuebing Li","heartbeat user","ADM","Auto Dora Metrics","Medium","Performance Improvement","Sprint 28","Stream2","10.15","1.0","","","","None","3.0","","","","3.38","0","5.94","1.35","1.87","0.72","0.27","22.87","1.87","1.35","5.94","0.27","0","0.72" -"ADM-683","[Frontend] UI refine for the date picker in report page","Task","Done","2024-01-17","1.0","heartbeat user","heartbeat user","ADM","Auto Dora Metrics","Medium","easy to use","Sprint 28","Stream2","8.92","1.0","","","","None","1.0","","","","8.92","0","3.00","0.10","1.84","3.00","0.98","15.05","1.84","0.10","3.00","0.98","0","3.00" -"ADM-669","[Frontend] UI refine for notification pop up change in report page","Task","Done","2024-01-17","1.0","heartbeat user","heartbeat user","ADM","Auto Dora Metrics","Medium","easy to use","Sprint 28","Stream2","7.13","1.0","","","","None","1.0","","","","7.13","0","4.22","0.02","1.16","0","1.73","17.80","1.16","0.02","4.22","1.73","0","0" -"ADM-709","[Backend] Verify buildkite and obtain buildkite data with new API","Task","Done","2024-01-15","3.0","Xinyi Wang","heartbeat user","ADM","Auto Dora Metrics","Medium","easy to use","Sprint 27","Stream1","6.85","1.0","","","","None","3.0","","","","2.28","0","2.81","0.07","0.78","0","3.19","8.03","0.78","0.07","2.81","3.19","0","0" -,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -"ADM-806","[BE]no need to obtain pipeline data twice in backend","Bug","Review","2024-02-26","2.0","heartbeat user","Yufan Wang","ADM","Auto Dora Metrics","Medium",,"Sprint 30","Stream2,v1.1.5","0","","","","","None","2.0","","","","0","0","2.89","0","0","0","0.04","8.10","0","0","2.89","0.04","0","0" -"ADM-813","[FE]add new field 'Advance' in metrics page","Task","Review","2024-02-26","2.0","heartbeat user","Yufan Wang","ADM","Auto Dora Metrics","Medium",,"Sprint 30","Stream2,v1.1.5","0","1.0","","","","None","2.0","","","","0","0","4.78","0","0","4.68","0.53","0.19","0","0","4.78","0.53","0","4.68" -"ADM-677","[Spike]Investigate Github graphQL API about replacing existing REST API","Spike","Blocked","2024-02-21","2.0","Junbo Dai","Yichen Wang","ADM","Auto Dora Metrics","Medium","Performance Improvement","Sprint 30","Stream1","0","","","","","None","2.0","","","","0","0","1.05","0","0","10.17","0","38.43","0","0","1.05","0","0","10.17" -"ADM-819","[BE]cache doesn't work in one case","Bug","Doing","2024-02-26","2.0","Shiqi Yuan","Yufan Wang","ADM","Auto Dora Metrics","Medium",,"Sprint 30","Stream2,v1.1.5","0","","","","","None","2.0","","","","0","0","3.14","0","0","1.05","0","0.84","0","0","3.14","0","0","1.05" -"ADM-797","[BE]The add flag as block logic is not working","Bug","Doing","2024-02-26","2.0","heartbeat user","Wenting Yan","ADM","Auto Dora Metrics","High",,"Sprint 30","Stream2,v1.1.5","0","","","","","None","2.0","","","","0","0","7.13","0","0","5.00","0","2.03","0","0","7.38","0","1.05","5.80" -"ADM-829","jump home page when user click next button in config page","Bug","Doing","2024-02-23","2.0","Junbo Dai","Yufan Wang","ADM","Auto Dora Metrics","High",,"Sprint 30","Stream1,v1.1.5","0","","","","","None","2.0","","","","0","0","1.03","0","0","0","0","1.17","0","0","1.03","0","0","0" -"ADM-812","[FE]metrics page needs to retain the modified data","Bug","Doing","2024-02-23","2.0","heartbeat user","Yufan Wang","ADM","Auto Dora Metrics","Medium",,"Sprint 30","Stream1","0","","","","","None","2.0","","","","0","0","3.04","0","0","1.03","0","6.67","0","0","3.04","0","0","1.03" -"ADM-809","[E2E] build ""import a new project"" scenario","Task","Doing","2024-02-22","2.0","heartbeat user","Xingmeng Tao","ADM","Auto Dora Metrics","High",,"Sprint 30","Stream1,v1.1.5","0","1.0","","","","None","2.0","","","","0","0","2.24","0","0","0","0","8.00","0","0","2.24","0","0","0" -"ADM-808","[E2E] build ""Create a new Project"" scenario","Task","Doing","2024-02-19","3.5","heartbeat user","Xingmeng Tao","ADM","Auto Dora Metrics","High",,"Sprint 30","Stream1,v1.1.5","0","1.0","","","","None","3.5","","","","0","0","8.04","0","0","1.99","0","0.95","0","0","8.04","0","0","1.99" -"ADM-825","[E2E] build ""page jumps"" scenario","Task","TODO",,"2.0",,"Yufan Wang","ADM","Auto Dora Metrics","High",,"Sprint 30","Stream1,v1.1.5","0","1.0","","","","None","2.0","","","","0","0","0","0","0","0","0","0","0","0","0","0","0","0" -"ADM-820","user was misguided to home page when they want to enter metrics page","Bug","TODO",,"0.0","heartbeat user","Yufan Wang","ADM","Auto Dora Metrics","Medium",,"Sprint 30","Stream2","0","","","","","None","","","","","","0","0","0","0","0","0","0","0","0","0","0","0","0" -"ADM-833","[E2E] build ""unhappy path"" scenario","Task","TODO",,"0.0",,"heartbeat user","ADM","Auto Dora Metrics","Medium",,"Sprint 30","Stream1","0","1.0","","","","None","","","","","","0","0","0","0","0","0","0","0","0","0","0","0","0" -"ADM-789","refactor E2E-step2","Task","TODO",,"1.0",,"Yufan Wang","ADM","Auto Dora Metrics","High",,"Sprint 30","Stream2","0","1.0","","","","None","1.0","","","","0","0","0","0","0","0","0","0","0","0","0","0","0","0" +"Issue key","Summary","Issue Type","Status","Status Date","Story Points","assignee","Reporter","Project Key","Project Name","Priority","Parent Summary","Sprint","Labels","Cycle Time","Story testing-1","Flagged","Fix versions","Partner","Time tracking","Story point estimate","QA","Feature/Operation","Story testing-2","Cycle Time / Story Points","Analysis Days","In Dev Days","Waiting Days","Testing Days","Block Days","Review Days","OriginCycleTime: TODO","OriginCycleTime: TESTING","OriginCycleTime: WAIT FOR TEST","OriginCycleTime: DOING","OriginCycleTime: REVIEW","OriginCycleTime: FLAG","OriginCycleTime: BLOCKED",In dev total rework times,from Block to In dev,from Review to In dev,from Waiting for testing to In dev,from Testing to In dev,from Done to In dev +"ADM-735","[backend]identify the source of the error when generate reports encounter exception","Task","Done","2024-01-19","1.0","Yunsong Yang","Yunsong Yang","ADM","Auto Dora Metrics","Medium","Precise on Metrics","Sprint 28","Stream2","7.70","1.0","","","","None","1.0","","","","7.70","0","2.02","1.81","0","0","3.87","3.03","0","1.81","2.02","3.87","0","0","0","0","0","0","0","0" +"ADM-708","[Backend] Verify board and obtain board data with new API","Task","Done","2024-01-19","3.0","Weiran Sun","heartbeat user","ADM","Auto Dora Metrics","Medium","easy to use","Sprint 28","Stream1","9.95","1.0","","","","None","3.0","","","","3.32","0","4.00","0.93","1.04","0.98","3.00","7.10","1.04","0.93","4.00","3.00","0","0.98","2","2","0","0","0","0" +"ADM-699","[Frontend] Optimize the 4xx&504 error display of report overview","Task","Done","2024-01-18","2.0","heartbeat user","heartbeat user","ADM","Auto Dora Metrics","Medium","Performance Improvement","Sprint 28","Stream2","10.93","1.0","","","","None","2.0","","","","5.46","0","5.14","0.04","0.78","2.01","2.96","10.75","0.78","0.04","5.14","2.96","0","2.01","2","2","0","0","0","0" +"ADM-717","[Backend] Verify github and obtain github data with new API","Task","Done","2024-01-17","2.0","Junbo Dai","Yufan Wang","ADM","Auto Dora Metrics","Medium","easy to use","Sprint 28","Stream1","8.09","1.0","","","","None","2.0","Weiran Sun","","","4.04","0","2.83","2.72","0.05","2.14","0.35","6.00","0.05","2.72","2.83","0.35","0","2.14","3","3","0","0","0","0" +"ADM-724","[Spike] redesign board verify API to meet business requirements","Spike","Done","2024-01-17","1.0","heartbeat user","Yufan Wang","ADM","Auto Dora Metrics","Medium","easy to use","Sprint 28","Stream1","12.94","","","","","None","1.0","","","","12.94","0","1.08","1.99","0","7.65","2.22","0.27","0","1.99","1.08","2.22","0","7.65","2","2","0","0","0","0" +"ADM-652","[Frontend]Generate the separate modules detail report","Task","Done","2024-01-17","3.0","Xuebing Li","heartbeat user","ADM","Auto Dora Metrics","Medium","Performance Improvement","Sprint 28","Stream2","10.15","1.0","","","","None","3.0","","","","3.38","0","5.94","1.35","1.87","0.72","0.27","22.87","1.87","1.35","5.94","0.27","0","0.72","1","1","0","0","0","0" +"ADM-683","[Frontend] UI refine for the date picker in report page","Task","Done","2024-01-17","1.0","heartbeat user","heartbeat user","ADM","Auto Dora Metrics","Medium","easy to use","Sprint 28","Stream2","8.92","1.0","","","","None","1.0","","","","8.92","0","3.00","0.10","1.84","3.00","0.98","15.05","1.84","0.10","3.00","0.98","0","3.00","1","1","0","0","0","0" +"ADM-669","[Frontend] UI refine for notification pop up change in report page","Task","Done","2024-01-17","1.0","heartbeat user","heartbeat user","ADM","Auto Dora Metrics","Medium","easy to use","Sprint 28","Stream2","7.13","1.0","","","","None","1.0","","","","7.13","0","4.22","0.02","1.16","0","1.73","17.80","1.16","0.02","4.22","1.73","0","0","0","0","0","0","0","0" +"ADM-709","[Backend] Verify buildkite and obtain buildkite data with new API","Task","Done","2024-01-15","3.0","Xinyi Wang","heartbeat user","ADM","Auto Dora Metrics","Medium","easy to use","Sprint 27","Stream1","6.85","1.0","","","","None","3.0","","","","2.28","0","2.81","0.07","0.78","0","3.19","8.03","0.78","0.07","2.81","3.19","0","0",,,,,, +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +"ADM-806","[BE]no need to obtain pipeline data twice in backend","Bug","Review","2024-02-26","2.0","heartbeat user","Yufan Wang","ADM","Auto Dora Metrics","Medium",,"Sprint 30","Stream2,v1.1.5","0","","","","","None","2.0","","","","0","0","2.89","0","0","0","0.04","8.10","0","0","2.89","0.04","0","0",,,,,, +"ADM-813","[FE]add new field 'Advance' in metrics page","Task","Review","2024-02-26","2.0","heartbeat user","Yufan Wang","ADM","Auto Dora Metrics","Medium",,"Sprint 30","Stream2,v1.1.5","0","1.0","","","","None","2.0","","","","0","0","4.78","0","0","4.68","0.53","0.19","0","0","4.78","0.53","0","4.68",,,,,, +"ADM-677","[Spike]Investigate Github graphQL API about replacing existing REST API","Spike","Blocked","2024-02-21","2.0","Junbo Dai","Yichen Wang","ADM","Auto Dora Metrics","Medium","Performance Improvement","Sprint 30","Stream1","0","","","","","None","2.0","","","","0","0","1.05","0","0","10.17","0","38.43","0","0","1.05","0","0","10.17",,,,,, +"ADM-819","[BE]cache doesn't work in one case","Bug","Doing","2024-02-26","2.0","Shiqi Yuan","Yufan Wang","ADM","Auto Dora Metrics","Medium",,"Sprint 30","Stream2,v1.1.5","0","","","","","None","2.0","","","","0","0","3.14","0","0","1.05","0","0.84","0","0","3.14","0","0","1.05",,,,,, +"ADM-797","[BE]The add flag as block logic is not working","Bug","Doing","2024-02-26","2.0","heartbeat user","Wenting Yan","ADM","Auto Dora Metrics","High",,"Sprint 30","Stream2,v1.1.5","0","","","","","None","2.0","","","","0","0","7.13","0","0","5.00","0","2.03","0","0","7.38","0","1.05","5.80",,,,,, +"ADM-829","jump home page when user click next button in config page","Bug","Doing","2024-02-23","2.0","Junbo Dai","Yufan Wang","ADM","Auto Dora Metrics","High",,"Sprint 30","Stream1,v1.1.5","0","","","","","None","2.0","","","","0","0","1.03","0","0","0","0","1.17","0","0","1.03","0","0","0",,,,,, +"ADM-812","[FE]metrics page needs to retain the modified data","Bug","Doing","2024-02-23","2.0","heartbeat user","Yufan Wang","ADM","Auto Dora Metrics","Medium",,"Sprint 30","Stream1","0","","","","","None","2.0","","","","0","0","3.04","0","0","1.03","0","6.67","0","0","3.04","0","0","1.03",,,,,, +"ADM-809","[E2E] build ""import a new project"" scenario","Task","Doing","2024-02-22","2.0","heartbeat user","Xingmeng Tao","ADM","Auto Dora Metrics","High",,"Sprint 30","Stream1,v1.1.5","0","1.0","","","","None","2.0","","","","0","0","2.24","0","0","0","0","8.00","0","0","2.24","0","0","0",,,,,, +"ADM-808","[E2E] build ""Create a new Project"" scenario","Task","Doing","2024-02-19","3.5","heartbeat user","Xingmeng Tao","ADM","Auto Dora Metrics","High",,"Sprint 30","Stream1,v1.1.5","0","1.0","","","","None","3.5","","","","0","0","8.04","0","0","1.99","0","0.95","0","0","8.04","0","0","1.99",,,,,, +"ADM-825","[E2E] build ""page jumps"" scenario","Task","TODO",,"2.0",,"Yufan Wang","ADM","Auto Dora Metrics","High",,"Sprint 30","Stream1,v1.1.5","0","1.0","","","","None","2.0","","","","0","0","0","0","0","0","0","0","0","0","0","0","0","0",,,,,, +"ADM-820","user was misguided to home page when they want to enter metrics page","Bug","TODO",,"0.0","heartbeat user","Yufan Wang","ADM","Auto Dora Metrics","Medium",,"Sprint 30","Stream2","0","","","","","None","","","","","","0","0","0","0","0","0","0","0","0","0","0","0","0",,,,,, +"ADM-833","[E2E] build ""unhappy path"" scenario","Task","TODO",,"0.0",,"heartbeat user","ADM","Auto Dora Metrics","Medium",,"Sprint 30","Stream1","0","1.0","","","","None","","","","","","0","0","0","0","0","0","0","0","0","0","0","0","0",,,,,, +"ADM-789","refactor E2E-step2","Task","TODO",,"1.0",,"Yufan Wang","ADM","Auto Dora Metrics","High",,"Sprint 30","Stream2","0","1.0","","","","None","1.0","","","","0","0","0","0","0","0","0","0","0","0","0","0","0","0",,,,,, From 46d8acbdc4ff2ad24323f89faa860f6e09bde011 Mon Sep 17 00:00:00 2001 From: yulongcai Date: Thu, 21 Mar 2024 14:57:20 +0800 Subject: [PATCH 19/22] ADM-693:[backend] feat: report rework metric in metric csc file --- .../service/report/CSVFileGenerator.java | 15 +++++++++++++++ .../service/report/CSVFileGeneratorTest.java | 3 +++ .../service/report/MetricCsvFixture.java | 2 ++ 3 files changed, 20 insertions(+) diff --git a/backend/src/main/java/heartbeat/service/report/CSVFileGenerator.java b/backend/src/main/java/heartbeat/service/report/CSVFileGenerator.java index a8bc585599..81f76dee86 100644 --- a/backend/src/main/java/heartbeat/service/report/CSVFileGenerator.java +++ b/backend/src/main/java/heartbeat/service/report/CSVFileGenerator.java @@ -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; @@ -412,6 +413,11 @@ private List convertReportResponseToCSVRows(ReportResponse reportRespo if (cycleTime != null) rows.addAll(getRowsFromCycleTime(cycleTime)); + Rework rework = reportResponse.getRework(); + if (rework != null) { + rows.addAll(getRowFromRework(rework)); + } + List classificationList = reportResponse.getClassificationList(); if (classificationList != null) classificationList.forEach(classification -> rows.addAll(getRowsFormClassification(classification))); @@ -470,6 +476,15 @@ private List getRowsFromCycleTime(CycleTime cycleTime) { return rows; } + private List getRowFromRework(Rework rework) { + List rows = new ArrayList<>(); + rows.add(new String[] { "Rework", "Total rework times", String.valueOf(rework.getTotalReworkTimes()) }); + rows.add(new String[] { "Rework", "Total rework cards", String.valueOf(rework.getTotalReworkCards()) }); + rows.add(new String[] { "Rework", "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"; diff --git a/backend/src/test/java/heartbeat/service/report/CSVFileGeneratorTest.java b/backend/src/test/java/heartbeat/service/report/CSVFileGeneratorTest.java index f9770dbcf8..861cf23e31 100644 --- a/backend/src/test/java/heartbeat/service/report/CSVFileGeneratorTest.java +++ b/backend/src/test/java/heartbeat/service/report/CSVFileGeneratorTest.java @@ -527,6 +527,9 @@ void shouldHasNoContentForAveragesWhenGetDataFromCsvGivenDataTypeIsMetricAndTheQ Assertions.assertEquals(metricCsvData, """ "Group","Metrics","Value" + "Rework","Total rework times","3" + "Rework","Total rework cards","3" + "Rework","Rework cards ratio(Total rework cards/Throughput)","0.99" "Deployment frequency","Heartbeat / Deploy prod / Deployment frequency(Deployments/Day)","0.78" "Lead time for changes","Heartbeat / Deploy prod / PR Lead Time","0" "Lead time for changes","Heartbeat / Deploy prod / Pipeline Lead Time","0.02" diff --git a/backend/src/test/java/heartbeat/service/report/MetricCsvFixture.java b/backend/src/test/java/heartbeat/service/report/MetricCsvFixture.java index a47bc81491..e957f60666 100644 --- a/backend/src/test/java/heartbeat/service/report/MetricCsvFixture.java +++ b/backend/src/test/java/heartbeat/service/report/MetricCsvFixture.java @@ -3,6 +3,7 @@ import heartbeat.controller.report.dto.response.Classification; import heartbeat.controller.report.dto.response.ClassificationNameValuePair; import heartbeat.controller.report.dto.response.ReportResponse; +import heartbeat.controller.report.dto.response.Rework; import heartbeat.controller.report.dto.response.Velocity; import heartbeat.controller.report.dto.response.CycleTime; import heartbeat.controller.report.dto.response.CycleTimeForSelectedStepItem; @@ -158,6 +159,7 @@ public static ReportResponse MOCK_EMPTY_METRIC_CSV_DATA() { public static ReportResponse MOCK_METRIC_CSV_DATA_WITH_ONE_PIPELINE() { return ReportResponse.builder() + .rework(Rework.builder().totalReworkTimes(3).totalReworkCards(3).reworkCardsRatio(0.99).build()) .deploymentFrequency(DeploymentFrequency.builder() .avgDeploymentFrequency( AvgDeploymentFrequency.builder().name("Average").deploymentFrequency(0.67F).build()) From 06b56ae79d3bc4dabbc95b688d64abefcf4ccf8c Mon Sep 17 00:00:00 2001 From: yulongcai Date: Thu, 21 Mar 2024 15:19:05 +0800 Subject: [PATCH 20/22] ADM-693:[frontend] fix: add rework data to e2e expect metric data csv --- frontend/e2e/fixtures/createNew/metricData.csv | 3 +++ frontend/e2e/fixtures/importFile/metricData.csv | 3 +++ 2 files changed, 6 insertions(+) diff --git a/frontend/e2e/fixtures/createNew/metricData.csv b/frontend/e2e/fixtures/createNew/metricData.csv index fc92c4a91d..ad13c4eeef 100644 --- a/frontend/e2e/fixtures/createNew/metricData.csv +++ b/frontend/e2e/fixtures/createNew/metricData.csv @@ -18,6 +18,9 @@ "Cycle time","Average review time(days/card)","2.06" "Cycle time","Average testing time(days/storyPoint)","0.44" "Cycle time","Average testing time(days/card)","0.84" +"Rework","Total rework times","11" +"Rework","Total rework cards","6" +"Rework","Rework cards ratio(Total rework cards/Throughput)","0.67" "Classifications","Issue Type / Spike","11.11" "Classifications","Issue Type / Task","88.89" "Classifications","Parent / ADM-322","66.67" diff --git a/frontend/e2e/fixtures/importFile/metricData.csv b/frontend/e2e/fixtures/importFile/metricData.csv index fc92c4a91d..ad13c4eeef 100644 --- a/frontend/e2e/fixtures/importFile/metricData.csv +++ b/frontend/e2e/fixtures/importFile/metricData.csv @@ -18,6 +18,9 @@ "Cycle time","Average review time(days/card)","2.06" "Cycle time","Average testing time(days/storyPoint)","0.44" "Cycle time","Average testing time(days/card)","0.84" +"Rework","Total rework times","11" +"Rework","Total rework cards","6" +"Rework","Rework cards ratio(Total rework cards/Throughput)","0.67" "Classifications","Issue Type / Spike","11.11" "Classifications","Issue Type / Task","88.89" "Classifications","Parent / ADM-322","66.67" From 870285811828bb9aac7f5ab5a19731b8b1bec3f5 Mon Sep 17 00:00:00 2001 From: yulongcai Date: Thu, 21 Mar 2024 15:28:31 +0800 Subject: [PATCH 21/22] ADM-693:[backend] fix: fix sonar issue --- .../java/heartbeat/service/report/CSVFileGenerator.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/backend/src/main/java/heartbeat/service/report/CSVFileGenerator.java b/backend/src/main/java/heartbeat/service/report/CSVFileGenerator.java index 81f76dee86..16fc24c04c 100644 --- a/backend/src/main/java/heartbeat/service/report/CSVFileGenerator.java +++ b/backend/src/main/java/heartbeat/service/report/CSVFileGenerator.java @@ -66,6 +66,8 @@ public class CSVFileGenerator { private static final String CANCELED_STATUS = "canceled"; + private static final String reworkField = "Rework"; + private static InputStreamResource readStringFromCsvFile(String fileName) { try { InputStream inputStream = new FileInputStream(fileName); @@ -478,9 +480,9 @@ private List getRowsFromCycleTime(CycleTime cycleTime) { private List getRowFromRework(Rework rework) { List rows = new ArrayList<>(); - rows.add(new String[] { "Rework", "Total rework times", String.valueOf(rework.getTotalReworkTimes()) }); - rows.add(new String[] { "Rework", "Total rework cards", String.valueOf(rework.getTotalReworkCards()) }); - rows.add(new String[] { "Rework", "Rework cards ratio(Total rework cards/Throughput)", + rows.add(new String[] { reworkField, "Total rework times", String.valueOf(rework.getTotalReworkTimes()) }); + rows.add(new String[] { reworkField, "Total rework cards", String.valueOf(rework.getTotalReworkCards()) }); + rows.add(new String[] { reworkField, "Rework cards ratio(Total rework cards/Throughput)", String.valueOf(rework.getReworkCardsRatio()) }); return rows; } From e50214c2bdcfa8d819498541c687ecbd7447dc8c Mon Sep 17 00:00:00 2001 From: yulongcai Date: Thu, 21 Mar 2024 15:33:04 +0800 Subject: [PATCH 22/22] ADM-693:[backend] fix: update constant name --- .../java/heartbeat/service/report/CSVFileGenerator.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/backend/src/main/java/heartbeat/service/report/CSVFileGenerator.java b/backend/src/main/java/heartbeat/service/report/CSVFileGenerator.java index 16fc24c04c..3f62856626 100644 --- a/backend/src/main/java/heartbeat/service/report/CSVFileGenerator.java +++ b/backend/src/main/java/heartbeat/service/report/CSVFileGenerator.java @@ -66,7 +66,7 @@ public class CSVFileGenerator { private static final String CANCELED_STATUS = "canceled"; - private static final String reworkField = "Rework"; + private static final String REWORK_FIELD = "Rework"; private static InputStreamResource readStringFromCsvFile(String fileName) { try { @@ -480,9 +480,9 @@ private List getRowsFromCycleTime(CycleTime cycleTime) { private List getRowFromRework(Rework rework) { List rows = new ArrayList<>(); - rows.add(new String[] { reworkField, "Total rework times", String.valueOf(rework.getTotalReworkTimes()) }); - rows.add(new String[] { reworkField, "Total rework cards", String.valueOf(rework.getTotalReworkCards()) }); - rows.add(new String[] { reworkField, "Rework cards ratio(Total rework cards/Throughput)", + 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; }