From b0fa28b6f7f8a02d47e7b0686eed6571cc3c7130 Mon Sep 17 00:00:00 2001 From: yulongcai <141199398+yulongcai@users.noreply.github.com> Date: Thu, 21 Mar 2024 16:44:55 +0800 Subject: [PATCH] ADM-693:[backend] feat: generate rework report (#1256) * ADM-693:[backend] fix:revert to original logic in fetch done card data * ADM-693:[backend] fix: update rework time is null to zero * ADM-693:[backend] refactor: extract methods * ADM-693:[backend] refactor: extract generation of baseInfo and cycle time to the BoardSheetGenerator * ADM-693:[backend] feat: generate rework field in sheet * ADM-693:[backend] test: fix order of assert * ADM-693:[backend] fix: add rework total time in flat map * ADM-693: [frontend] fix: fix request param issue * ADM-693:[backend] fix: format rework fields * ADM-693:[backend] fix: use alias temporarily * ADM-693:[backend] test: add rework fields in sheet * ADM-693:[backend] refactor: only one condition * ADM-693:[backend] fix: pmd check * ADM-693:[backend] refactor: rename method * ADM-693:[backend] refactor: sort card steps enum * ADM-693:[docs] feat: return throughput in rework info * AMD-694 [frontend] test: add e2e for board metric rework both for create-a-new-project.spec and import-project-from-file.spec * AMD-693 [frontend] fix: add some columns in board metric.csv * ADM-693:[backend] feat: report rework metric in metric csc file * ADM-693:[frontend] fix: add rework data to e2e expect metric data csv * ADM-693:[backend] fix: fix sonar issue * ADM-693:[backend] fix: update constant name --------- Co-authored-by: Genhao Liu Co-authored-by: GuangbinMa Co-authored-by: yp.wu --- .../board/dto/request/CardStepsEnum.java | 16 ++- .../board/dto/response/JiraCardDTO.java | 18 ++++ .../service/board/jira/JiraService.java | 2 +- .../service/report/BoardSheetGenerator.java | 69 ++++++++++++ .../service/report/CSVFileGenerator.java | 54 +++++++--- .../service/report/KanbanCsvService.java | 49 ++++++++- .../service/jira/JiraServiceTest.java | 17 +-- .../service/report/BoardCsvFixture.java | 10 ++ .../service/report/CSVFileGeneratorTest.java | 3 + .../service/report/KanbanCsvServiceTest.java | 100 +++++++++++++++--- .../service/report/MetricCsvFixture.java | 2 + frontend/e2e/fixtures/createNew/boardData.csv | 48 ++++----- .../e2e/fixtures/createNew/metricData.csv | 3 + .../e2e/fixtures/importFile/metricData.csv | 3 + 14 files changed, 326 insertions(+), 68 deletions(-) create mode 100644 backend/src/main/java/heartbeat/service/report/BoardSheetGenerator.java 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 0fc7ee3021..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,20 +5,28 @@ public enum CardStepsEnum { - TODO("To do"), ANALYSE("Analysis"), DEVELOPMENT("In Dev"), BLOCK("Block"), TESTING("Testing"), REVIEW("Review"), - DONE("Done"), CLOSED("Closed"), WAITING("Waiting for testing"), FLAG("FLAG"), REMOVEFLAG("removeFlag"), - UNKNOWN("UNKNOWN"); + TODO("To do", "To do"), ANALYSE("Analysis", "Analysis"), DEVELOPMENT("In Dev", "In dev"), BLOCK("Block", "Block"), + FLAG("FLAG", "Flag"), REMOVEFLAG("removeFlag", "Remove flag"), REVIEW("Review", "Review"), + WAITING("Waiting for testing", "Waiting for testing"), TESTING("Testing", "Testing"), DONE("Done", "Done"), + CLOSED("Closed", "Closed"), UNKNOWN("UNKNOWN", "Unknown"); private final String value; - CardStepsEnum(String value) { + private final String alias; + + CardStepsEnum(String value, String alias) { this.value = value; + this.alias = alias; } public String getValue() { return value; } + public String getAlias() { + return alias; + } + public static CardStepsEnum fromValue(String type) { for (CardStepsEnum cardStepsEnum : values()) { if (cardStepsEnum.value.equals(type)) { 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..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 @@ -8,9 +8,12 @@ import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; +import org.apache.commons.collections4.CollectionUtils; import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; @Data @Builder @@ -32,6 +35,8 @@ public class JiraCardDTO { private Object cycleTimeFlat; + private Object reworkTimesFlat; + @Nullable private String totalCycleTimeDivideStoryPoints; @@ -60,6 +65,19 @@ public Object buildCycleTimeFlatObject() { return cycleTimeFlat; } + @JsonIgnore + public Object buildReworkTimesFlatObject() { + if (CollectionUtils.isEmpty(this.getReworkTimesInfos())) { + return null; + } + Map reworkTimesMap = this.getReworkTimesInfos() + .stream() + .collect(Collectors.toMap(reworkTimesInfo -> reworkTimesInfo.getState().getValue(), + ReworkTimesInfo::getTimes)); + reworkTimesMap.put("totalReworkTimes", totalReworkTimes); + return reworkTimesMap; + } + @JsonIgnore public void calculateTotalReworkTimes() { this.totalReworkTimes = reworkTimesInfos.stream().mapToInt(ReworkTimesInfo::getTimes).sum(); 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 6a5f1e471e..72ecb67069 100644 --- a/backend/src/main/java/heartbeat/service/board/jira/JiraService.java +++ b/backend/src/main/java/heartbeat/service/board/jira/JiraService.java @@ -726,7 +726,7 @@ private void calculateTimes(CardStepsEnum reworkState, Set exclud return; } if (isRework(from, to, excludedStates)) { - reworkTimesMap.put(from, reworkTimesMap.get(from) + 1); + reworkTimesMap.computeIfPresent(from, (key, value) -> value + 1); } } 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..453713b72f --- /dev/null +++ b/backend/src/main/java/heartbeat/service/report/BoardSheetGenerator.java @@ -0,0 +1,69 @@ +package heartbeat.service.report; + +import heartbeat.controller.board.dto.response.JiraCardDTO; +import heartbeat.controller.report.dto.response.BoardCSVConfig; +import lombok.Builder; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.ArrayUtils; + +import java.util.List; + +@Builder +public class BoardSheetGenerator { + + private List 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 mergeReworkTimesSheet() { + if (CollectionUtils.isEmpty(reworkFields)) { + return this; + } + int columnCount = reworkFields.size(); + String[][] reworkTimesSheet = new String[jiraCardDTOList.size() + 1][columnCount]; + + for (int column = 0; column < columnCount; column++) { + reworkTimesSheet[0][column] = reworkFields.get(column).getLabel(); + } + for (int row = 0; row < jiraCardDTOList.size(); row++) { + JiraCardDTO cardDTO = jiraCardDTOList.get(row); + for (int column = 0; column < columnCount; column++) { + reworkTimesSheet[row + 1][column] = csvFileGenerator.getExtraDataPerRow(cardDTO.getReworkTimesFlat(), + reworkFields.get(column)); + } + } + sheet = mergeSheetHorizontally(sheet, reworkTimesSheet); + return this; + } + + private String[][] mergeSheetHorizontally(String[][] sheet, String[][] sheetToMerge) { + int rows = jiraCardDTOList.size() + 1; + String[][] combinedArray = new String[rows][]; + if (ArrayUtils.isEmpty(sheet)) { + return sheetToMerge; + } + for (int i = 0; i < rows; i++) { + combinedArray[i] = ArrayUtils.addAll(sheet[i], sheetToMerge[i]); + } + return combinedArray; + } + +} diff --git a/backend/src/main/java/heartbeat/service/report/CSVFileGenerator.java b/backend/src/main/java/heartbeat/service/report/CSVFileGenerator.java index 36f836336b..3f62856626 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; @@ -65,6 +66,8 @@ public class CSVFileGenerator { private static final String CANCELED_STATUS = "canceled"; + private static final String REWORK_FIELD = "Rework"; + private static InputStreamResource readStringFromCsvFile(String fileName) { try { InputStream inputStream = new FileInputStream(fileName); @@ -170,26 +173,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); + } + + public void writeDataToCSV(String csvTimeStamp, String[][] mergedArrays) { createCsvDirToConvertData(); String fileName = CSVFileNameEnum.BOARD.getValue() + FILENAME_SEPARATOR + csvTimeStamp + CSV_EXTENSION; if (!fileName.contains("..") && fileName.startsWith(FILE_LOCAL_PATH)) { try (CSVWriter writer = new CSVWriter(new FileWriter(fileName))) { - List 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 +195,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]; @@ -336,7 +346,7 @@ private void fixDataWithFields(JiraCardDTO cardDTO, String[] rowData) { rowData[13] = String.join(",", cardDTO.getBaseInfo().getFields().getLabels()); } - private String getExtraDataPerRow(Object object, BoardCSVConfig extraField) { + public String getExtraDataPerRow(Object object, BoardCSVConfig extraField) { Map elementMap = (Map) object; if (elementMap == null) { return null; @@ -405,6 +415,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))); @@ -463,6 +478,15 @@ private List getRowsFromCycleTime(CycleTime cycleTime) { return rows; } + private List getRowFromRework(Rework rework) { + List rows = new ArrayList<>(); + rows.add(new String[] { REWORK_FIELD, "Total rework times", String.valueOf(rework.getTotalReworkTimes()) }); + rows.add(new String[] { REWORK_FIELD, "Total rework cards", String.valueOf(rework.getTotalReworkCards()) }); + rows.add(new String[] { REWORK_FIELD, "Rework cards ratio(Total rework cards/Throughput)", + String.valueOf(rework.getReworkCardsRatio()) }); + return rows; + } + private String formatStepName(CycleTimeForSelectedStepItem cycleTimeForSelectedStepItem) { return switch (cycleTimeForSelectedStepItem.getOptionalItemName()) { case "In Dev" -> "development"; diff --git a/backend/src/main/java/heartbeat/service/report/KanbanCsvService.java b/backend/src/main/java/heartbeat/service/report/KanbanCsvService.java index 808b926d2c..83650195d0 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,12 +134,37 @@ private void generateCSVForBoard(List allDoneCards, List reworkFields = new ArrayList<>(); + if (reworkState != null) { + reworkFields.add(BoardCSVConfig.builder() + .label(reworkState.getAlias() + " total rework times") + .value("totalReworkTimes") + .build()); + reworkFields.addAll(reworkFromStates.stream() + .map(state -> BoardCSVConfig.builder() + .label("from " + state + " to " + reworkState.getAlias()) + .value("reworkTimesFlat." + state) + .build()) + .toList()); + } cardDTOList.forEach(card -> { card.setCycleTimeFlat(card.buildCycleTimeFlatObject()); card.setTotalCycleTimeDivideStoryPoints(card.getTotalCycleTimeDivideStoryPoints()); + card.setReworkTimesFlat(card.buildReworkTimesFlatObject()); }); - csvFileGenerator.convertBoardDataToCSV(cardDTOList, allBoardFields, newExtraFields, csvTimeStamp); + String[][] sheet = BoardSheetGenerator.builder() + .csvFileGenerator(csvFileGenerator) + .jiraCardDTOList(cardDTOList) + .fields(allBoardFields) + .extraFields(newExtraFields) + .reworkFields(reworkFields) + .build() + .mergeBaseInfoAndCycleTimeSheet() + .mergeReworkTimesSheet() + .generate(); + csvFileGenerator.writeDataToCSV(csvTimeStamp, sheet); } private void sortNonDoneCardsByStatusAndTime(List nonDoneCards, List jiraColumns) { 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); } } 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/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/KanbanCsvServiceTest.java b/backend/src/test/java/heartbeat/service/report/KanbanCsvServiceTest.java index 73bc189f2a..e9d6914ba1 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; @@ -86,7 +92,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 +115,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 +175,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 +237,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 +281,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 +329,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 +380,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 +429,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 +468,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 +509,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 +551,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 +600,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); @@ -613,4 +617,72 @@ void shouldAddFixedFieldsWithCorrectValueFormatWhenCustomFieldValueInstanceOfLis assertEquals("baseInfo.fields.customFields.json-array[0]", targetValue5.getValue()); } + @Test + 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()); + 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]); + } + } 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()) 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",,,,,, 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"