-
Notifications
You must be signed in to change notification settings - Fork 15
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
ADM-727:[backend][docs]feat: Merge two API into one #897
Changes from 1 commit
aceabbf
5e1ab3f
a3e9869
3e42881
00b3a7f
7cab7c3
3a66895
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package heartbeat.controller.report.dto.request; | ||
|
||
public enum ReportType { | ||
|
||
BOARD, DORA; | ||
|
||
public static ReportType fromValue(String type) { | ||
return switch (type) { | ||
case "board" -> BOARD; | ||
case "dora" -> DORA; | ||
default -> throw new IllegalArgumentException("ReportType not found!"); | ||
}; | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -71,16 +71,19 @@ | |
import java.util.Map.Entry; | ||
import java.util.Objects; | ||
import java.util.Optional; | ||
import java.util.concurrent.CompletableFuture; | ||
import java.util.function.Function; | ||
import java.util.stream.Collectors; | ||
import java.util.stream.Stream; | ||
|
||
import heartbeat.util.IdUtil; | ||
import heartbeat.util.MetricsUtil; | ||
import lombok.RequiredArgsConstructor; | ||
import lombok.extern.log4j.Log4j2; | ||
import lombok.val; | ||
import org.apache.commons.collections.CollectionUtils; | ||
import org.springframework.core.io.InputStreamResource; | ||
import org.springframework.stereotype.Service; | ||
import org.springframework.util.CollectionUtils; | ||
import org.springframework.util.ObjectUtils; | ||
import org.springframework.util.StringUtils; | ||
|
||
|
@@ -127,21 +130,6 @@ public class GenerateReporterService { | |
|
||
private final AsyncExceptionHandler asyncExceptionHandler; | ||
|
||
private final List<String> kanbanMetrics = Stream | ||
.of(RequireDataEnum.VELOCITY, RequireDataEnum.CYCLE_TIME, RequireDataEnum.CLASSIFICATION) | ||
.map(RequireDataEnum::getValue) | ||
.toList(); | ||
|
||
private final List<String> buildKiteMetrics = Stream | ||
.of(RequireDataEnum.CHANGE_FAILURE_RATE, RequireDataEnum.DEPLOYMENT_FREQUENCY, | ||
RequireDataEnum.MEAN_TIME_TO_RECOVERY) | ||
.map(RequireDataEnum::getValue) | ||
.toList(); | ||
|
||
private final List<String> codebaseMetrics = Stream.of(RequireDataEnum.LEAD_TIME_FOR_CHANGES) | ||
.map(RequireDataEnum::getValue) | ||
.toList(); | ||
|
||
private static StoryPointsAndCycleTimeRequest buildStoryPointsAndCycleTimeRequest(JiraBoardSetting jiraBoardSetting, | ||
String startTime, String endTime) { | ||
return StoryPointsAndCycleTimeRequest.builder() | ||
|
@@ -213,17 +201,81 @@ else if (jsonObject.has("key")) { | |
return result; | ||
} | ||
|
||
public void generateBoardReport(GenerateReportRequest request) { | ||
initializeMetricsDataReadyInHandler(request.getCsvTimeStamp(), request.getMetrics()); | ||
|
||
CompletableFuture.runAsync(() -> { | ||
try { | ||
saveReporterInHandler(generateReporter(request), IdUtil.getBoardReportId(request.getCsvTimeStamp())); | ||
updateMetricsDataReadyInHandler(request.getCsvTimeStamp(), request.getMetrics()); | ||
log.info( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. where is the log of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
"Successfully generate board report, _metrics: {}, _considerHoliday: {}, _startTime: {}, _endTime: {}, _boardReportId: {}", | ||
request.getMetrics(), request.getConsiderHoliday(), request.getStartTime(), | ||
request.getEndTime(), IdUtil.getBoardReportId(request.getCsvTimeStamp())); | ||
} | ||
catch (BaseException e) { | ||
asyncExceptionHandler.put(IdUtil.getBoardReportId(request.getCsvTimeStamp()), e); | ||
} | ||
}); | ||
} | ||
|
||
public void generateDoraReport(GenerateReportRequest request) { | ||
MetricsDataReady metricsDataStatus = getMetricsStatus(request.getMetrics(), Boolean.TRUE); | ||
initializeMetricsDataReadyInHandler(request.getCsvTimeStamp(), request.getMetrics()); | ||
if (Objects.nonNull(metricsDataStatus.isSourceControlMetricsReady()) | ||
&& metricsDataStatus.isSourceControlMetricsReady()) { | ||
generateCodeBaseReport(request); | ||
} | ||
if (Objects.nonNull(metricsDataStatus.isPipelineMetricsReady()) && metricsDataStatus.isPipelineMetricsReady()) { | ||
generatePipelineReport(request); | ||
} | ||
generateCsvForDora(request); | ||
} | ||
|
||
private void generatePipelineReport(GenerateReportRequest request) { | ||
CompletableFuture.runAsync(() -> { | ||
try { | ||
GenerateReportRequest pipelineRequest = request.convertToPipelineRequest(request); | ||
saveReporterInHandler(generateReporter(pipelineRequest), | ||
IdUtil.getDoraReportId(pipelineRequest.getCsvTimeStamp())); | ||
updateMetricsDataReadyInHandler(pipelineRequest.getCsvTimeStamp(), pipelineRequest.getMetrics()); | ||
log.info( | ||
"Successfully generate pipeline report, _metrics: {}, _considerHoliday: {}, _startTime: {}, _endTime: {}, _pipelineReportId: {}", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. where is the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
pipelineRequest.getMetrics(), pipelineRequest.getConsiderHoliday(), | ||
pipelineRequest.getStartTime(), pipelineRequest.getEndTime(), | ||
IdUtil.getDoraReportId(request.getCsvTimeStamp())); | ||
} | ||
catch (BaseException e) { | ||
asyncExceptionHandler.put(IdUtil.getDoraReportId(request.getCsvTimeStamp()), e); | ||
} | ||
}); | ||
} | ||
|
||
private void generateCodeBaseReport(GenerateReportRequest request) { | ||
CompletableFuture.runAsync(() -> { | ||
try { | ||
GenerateReportRequest codebaseRequest = request.convertToCodeBaseRequest(request); | ||
saveReporterInHandler(generateReporter(codebaseRequest), | ||
IdUtil.getCodeBaseReportId(codebaseRequest.getCsvTimeStamp())); | ||
updateMetricsDataReadyInHandler(codebaseRequest.getCsvTimeStamp(), codebaseRequest.getMetrics()); | ||
log.info( | ||
"Successfully generate codebase report, _metrics: {}, _considerHoliday: {}, _startTime: {}, _endTime: {}, _codeBaseReportId: {}", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. where is the start to do something,.... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
codebaseRequest.getMetrics(), codebaseRequest.getConsiderHoliday(), | ||
codebaseRequest.getStartTime(), codebaseRequest.getEndTime(), | ||
IdUtil.getCodeBaseReportId(request.getCsvTimeStamp())); | ||
} | ||
catch (BaseException e) { | ||
asyncExceptionHandler.put(IdUtil.getCodeBaseReportId(request.getCsvTimeStamp()), e); | ||
} | ||
}); | ||
} | ||
|
||
public synchronized ReportResponse generateReporter(GenerateReportRequest request) { | ||
workDay.changeConsiderHolidayMode(request.getConsiderHoliday()); | ||
// fetch data for calculate | ||
List<String> lowMetrics = request.getMetrics().stream().map(String::toLowerCase).toList(); | ||
FetchedData fetchedData = fetchOriginalData(request, lowMetrics); | ||
|
||
if (lowMetrics.stream().anyMatch(this.codebaseMetrics::contains) | ||
|| lowMetrics.stream().anyMatch(this.buildKiteMetrics::contains)) { | ||
generateCSVForPipeline(request, fetchedData.getBuildKiteData()); | ||
} | ||
|
||
ReportResponse reportResponse = new ReportResponse(EXPORT_CSV_VALIDITY_TIME); | ||
JiraBoardSetting jiraBoardSetting = request.getJiraBoardSetting(); | ||
|
||
|
@@ -258,21 +310,21 @@ public synchronized ReportResponse generateReporter(GenerateReportRequest reques | |
private FetchedData fetchOriginalData(GenerateReportRequest request, List<String> lowMetrics) { | ||
FetchedData fetchedData = new FetchedData(); | ||
|
||
if (lowMetrics.stream().anyMatch(this.kanbanMetrics::contains)) { | ||
if (CollectionUtils.isNotEmpty(MetricsUtil.getBoardMetrics(lowMetrics))) { | ||
if (request.getJiraBoardSetting() == null) | ||
throw new BadRequestException("Failed to fetch Jira info due to Jira board setting is null."); | ||
CardCollectionInfo cardCollectionInfo = fetchDataFromKanban(request); | ||
fetchedData.setCardCollectionInfo(cardCollectionInfo); | ||
} | ||
|
||
if (lowMetrics.stream().anyMatch(this.codebaseMetrics::contains)) { | ||
if (CollectionUtils.isNotEmpty(MetricsUtil.getCodeBaseMetrics(lowMetrics))) { | ||
if (request.getCodebaseSetting() == null) | ||
throw new BadRequestException("Failed to fetch Github info due to code base setting is null."); | ||
BuildKiteData buildKiteData = fetchGithubData(request); | ||
fetchedData.setBuildKiteData(buildKiteData); | ||
} | ||
|
||
if (lowMetrics.stream().anyMatch(this.buildKiteMetrics::contains)) { | ||
if (CollectionUtils.isNotEmpty(MetricsUtil.getPipelineMetrics(lowMetrics))) { | ||
if (request.getBuildKiteSetting() == null) | ||
throw new BadRequestException("Failed to fetch BuildKite info due to BuildKite setting is null."); | ||
FetchedData.BuildKiteData buildKiteData = fetchBuildKiteInfo(request); | ||
|
@@ -287,6 +339,14 @@ private FetchedData fetchOriginalData(GenerateReportRequest request, List<String | |
return fetchedData; | ||
} | ||
|
||
public void generateCsvForDora(GenerateReportRequest request) { | ||
List<String> lowMetrics = request.getMetrics().stream().map(String::toLowerCase).toList(); | ||
FetchedData fetchedData = fetchOriginalData(request, lowMetrics); | ||
|
||
generateCSVForPipeline(request, fetchedData.getBuildKiteData()); | ||
|
||
} | ||
|
||
private CardCollection fetchRealDoneCardCollection(GenerateReportRequest request) { | ||
JiraBoardSetting jiraBoardSetting = request.getJiraBoardSetting(); | ||
StoryPointsAndCycleTimeRequest storyPointsAndCycleTimeRequest = buildStoryPointsAndCycleTimeRequest( | ||
|
@@ -638,13 +698,10 @@ private Boolean checkCurrentMetricsReadyState(Boolean exist, Boolean previousVal | |
} | ||
|
||
private MetricsDataReady getMetricsStatus(List<String> metrics, Boolean flag) { | ||
boolean boardMetricsExist = metrics.stream().map(String::toLowerCase).anyMatch(this.kanbanMetrics::contains); | ||
boolean codebaseMetricsExist = metrics.stream() | ||
.map(String::toLowerCase) | ||
.anyMatch(this.codebaseMetrics::contains); | ||
boolean buildKiteMetricsExist = metrics.stream() | ||
.map(String::toLowerCase) | ||
.anyMatch(this.buildKiteMetrics::contains); | ||
List<String> lowerMetrics = metrics.stream().map(String::toLowerCase).collect(Collectors.toList()); | ||
boolean boardMetricsExist = CollectionUtils.isNotEmpty(MetricsUtil.getBoardMetrics(lowerMetrics)); | ||
boolean codebaseMetricsExist = CollectionUtils.isNotEmpty(MetricsUtil.getCodeBaseMetrics(lowerMetrics)); | ||
boolean buildKiteMetricsExist = CollectionUtils.isNotEmpty(MetricsUtil.getPipelineMetrics(lowerMetrics)); | ||
Boolean isBoardMetricsReady = boardMetricsExist ? flag : null; | ||
Boolean isCodebaseMetricsReady = codebaseMetricsExist ? flag : null; | ||
Boolean isBuildKiteMetricsReady = buildKiteMetricsExist ? flag : null; | ||
|
@@ -691,10 +748,6 @@ private List<PipelineCSVInfo> generateCSVForPipelineWithCodebase(CodebaseSetting | |
String endTime, BuildKiteData buildKiteData, List<DeploymentEnvironment> deploymentEnvironments) { | ||
List<PipelineCSVInfo> pipelineCSVInfos = new ArrayList<>(); | ||
|
||
if (codebaseSetting == null && CollectionUtils.isEmpty(deploymentEnvironments)) { | ||
return pipelineCSVInfos; | ||
} | ||
|
||
Map<String, String> repoIdMap = getRepoMap(deploymentEnvironments); | ||
for (DeploymentEnvironment deploymentEnvironment : deploymentEnvironments) { | ||
String repoId = GithubUtil.getGithubUrlFullName(repoIdMap.get(deploymentEnvironment.getId())); | ||
|
@@ -806,6 +859,7 @@ public ReportResponse getReportFromHandler(String reportId) { | |
public ReportResponse getComposedReportResponse(String reportId, boolean isReportReady) { | ||
ReportResponse boardReportResponse = getReportFromHandler(IdUtil.getBoardReportId(reportId)); | ||
ReportResponse doraReportResponse = getReportFromHandler(IdUtil.getDoraReportId(reportId)); | ||
ReportResponse codebaseReportResponse = getReportFromHandler(IdUtil.getCodeBaseReportId(reportId)); | ||
MetricsDataReady metricsDataReady = asyncReportRequestHandler.getMetricsDataReady(reportId); | ||
ReportResponse response = Optional.ofNullable(boardReportResponse).orElse(doraReportResponse); | ||
|
||
|
@@ -817,7 +871,7 @@ public ReportResponse getComposedReportResponse(String reportId, boolean isRepor | |
.deploymentFrequency(getValueOrNull(doraReportResponse, ReportResponse::getDeploymentFrequency)) | ||
.changeFailureRate(getValueOrNull(doraReportResponse, ReportResponse::getChangeFailureRate)) | ||
.meanTimeToRecovery(getValueOrNull(doraReportResponse, ReportResponse::getMeanTimeToRecovery)) | ||
.leadTimeForChanges(getValueOrNull(doraReportResponse, ReportResponse::getLeadTimeForChanges)) | ||
.leadTimeForChanges(getValueOrNull(codebaseReportResponse, ReportResponse::getLeadTimeForChanges)) | ||
.isBoardMetricsReady(getValueOrNull(metricsDataReady, MetricsDataReady::isBoardMetricsReady)) | ||
.isPipelineMetricsReady(getValueOrNull(metricsDataReady, MetricsDataReady::isPipelineMetricsReady)) | ||
.isSourceControlMetricsReady( | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
package heartbeat.util; | ||
|
||
import heartbeat.controller.report.dto.request.RequireDataEnum; | ||
|
||
import java.util.List; | ||
import java.util.stream.Collectors; | ||
import java.util.stream.Stream; | ||
|
||
public interface MetricsUtil { | ||
|
||
List<String> kanbanMetrics = Stream | ||
.of(RequireDataEnum.VELOCITY, RequireDataEnum.CYCLE_TIME, RequireDataEnum.CLASSIFICATION) | ||
.map(RequireDataEnum::getValue) | ||
.toList(); | ||
|
||
List<String> buildKiteMetrics = Stream | ||
.of(RequireDataEnum.CHANGE_FAILURE_RATE, RequireDataEnum.DEPLOYMENT_FREQUENCY, | ||
RequireDataEnum.MEAN_TIME_TO_RECOVERY) | ||
.map(RequireDataEnum::getValue) | ||
.toList(); | ||
|
||
List<String> codebaseMetrics = Stream.of(RequireDataEnum.LEAD_TIME_FOR_CHANGES) | ||
.map(RequireDataEnum::getValue) | ||
.toList(); | ||
|
||
static List<String> getPipelineMetrics(List<String> metrics) { | ||
return metrics.stream().filter(buildKiteMetrics::contains).collect(Collectors.toList()); | ||
} | ||
|
||
static List<String> getCodeBaseMetrics(List<String> metrics) { | ||
return metrics.stream().filter(codebaseMetrics::contains).collect(Collectors.toList()); | ||
|
||
} | ||
|
||
static List<String> getBoardMetrics(List<String> metrics) { | ||
return metrics.stream().filter(kanbanMetrics::contains).collect(Collectors.toList()); | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,6 +5,7 @@ | |
import heartbeat.controller.report.dto.request.ExportCSVRequest; | ||
import heartbeat.controller.report.dto.request.GenerateBoardReportRequest; | ||
import heartbeat.controller.report.dto.request.GenerateDoraReportRequest; | ||
import heartbeat.controller.report.dto.request.GenerateReportRequest; | ||
import heartbeat.controller.report.dto.response.AvgDeploymentFrequency; | ||
import heartbeat.controller.report.dto.response.DeploymentFrequency; | ||
import heartbeat.controller.report.dto.response.ReportResponse; | ||
|
@@ -28,7 +29,6 @@ | |
|
||
import java.io.ByteArrayInputStream; | ||
import java.io.File; | ||
|
||
import static org.assertj.core.api.Assertions.assertThat; | ||
import static org.junit.jupiter.api.Assertions.assertEquals; | ||
import static org.mockito.ArgumentMatchers.any; | ||
|
@@ -161,7 +161,7 @@ void shouldReturnAcceptedStatusAndCallbackUrlAndIntervalWhenCallBoardReports() t | |
doNothing().when(generateReporterService).updateMetricsDataReadyInHandler(any(), any()); | ||
|
||
MockHttpServletResponse response = mockMvc | ||
.perform(post("/reports/board").contentType(MediaType.APPLICATION_JSON) | ||
.perform(post("/reports/v1/board").contentType(MediaType.APPLICATION_JSON) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. remove is there any design for the new endpoint? i think we should follow the design There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
.content(mapper.writeValueAsString(request))) | ||
.andExpect(status().isAccepted()) | ||
.andReturn() | ||
|
@@ -189,7 +189,7 @@ void shouldGetExceptionAndPutInExceptionMapWhenCallBoardReport() throws Exceptio | |
doNothing().when(generateReporterService).updateMetricsDataReadyInHandler(any(), any()); | ||
|
||
MockHttpServletResponse response = mockMvc | ||
.perform(post("/reports/board").contentType(MediaType.APPLICATION_JSON) | ||
.perform(post("/reports/v1/board").contentType(MediaType.APPLICATION_JSON) | ||
.content(mapper.writeValueAsString(request))) | ||
.andExpect(status().isAccepted()) | ||
.andReturn() | ||
|
@@ -201,12 +201,12 @@ void shouldGetExceptionAndPutInExceptionMapWhenCallBoardReport() throws Exceptio | |
assertEquals("10", interval); | ||
|
||
Thread.sleep(2000L); | ||
verify(generateReporterService).initializeMetricsDataReadyInHandler(request.getCsvTimeStamp(), | ||
verify(generateReporterService, times(1)).initializeMetricsDataReadyInHandler(request.getCsvTimeStamp(), | ||
request.getMetrics()); | ||
verify(generateReporterService, times(0)).saveReporterInHandler(any(), any()); | ||
verify(generateReporterService, times(0)).updateMetricsDataReadyInHandler(request.getCsvTimeStamp(), | ||
request.getMetrics()); | ||
verify(asyncExceptionHandler).put(IdUtil.getBoardReportId(currentTimeStamp), requestFailedException); | ||
verify(asyncExceptionHandler, times(1)).put(IdUtil.getBoardReportId(currentTimeStamp), requestFailedException); | ||
} | ||
|
||
@Test | ||
|
@@ -225,7 +225,7 @@ void shouldGetExceptionAndPutInExceptionMapWhenCallDoraReport() throws Exception | |
doNothing().when(generateReporterService).updateMetricsDataReadyInHandler(any(), any()); | ||
|
||
MockHttpServletResponse response = mockMvc | ||
.perform(post("/reports/dora").contentType(MediaType.APPLICATION_JSON) | ||
.perform(post("/reports/v1/dora").contentType(MediaType.APPLICATION_JSON) | ||
.content(mapper.writeValueAsString(request))) | ||
.andExpect(status().isAccepted()) | ||
.andReturn() | ||
|
@@ -237,12 +237,12 @@ void shouldGetExceptionAndPutInExceptionMapWhenCallDoraReport() throws Exception | |
assertEquals("10", interval); | ||
|
||
Thread.sleep(2000L); | ||
verify(generateReporterService).initializeMetricsDataReadyInHandler(request.getCsvTimeStamp(), | ||
verify(generateReporterService, times(1)).initializeMetricsDataReadyInHandler(request.getCsvTimeStamp(), | ||
request.getMetrics()); | ||
verify(generateReporterService, times(0)).saveReporterInHandler(any(), any()); | ||
verify(generateReporterService, times(0)).updateMetricsDataReadyInHandler(request.getCsvTimeStamp(), | ||
request.getMetrics()); | ||
verify(asyncExceptionHandler).put(IdUtil.getDoraReportId(currentTimeStamp), requestFailedException); | ||
verify(asyncExceptionHandler, times(1)).put(IdUtil.getDoraReportId(currentTimeStamp), requestFailedException); | ||
} | ||
|
||
@Test | ||
|
@@ -265,13 +265,60 @@ void shouldReturnAcceptedStatusAndCallbackUrlAndIntervalWhenCallDoraReports() th | |
|
||
when(generateReporterService.generateReporter(request.convertToReportRequest())).thenReturn(expectedResponse); | ||
|
||
MockHttpServletResponse response = mockMvc | ||
.perform(post("/reports/v1/dora").contentType(MediaType.APPLICATION_JSON) | ||
.content(mapper.writeValueAsString(request))) | ||
.andExpect(status().isAccepted()) | ||
.andReturn() | ||
.getResponse(); | ||
|
||
final var callbackUrl = JsonPath.parse(response.getContentAsString()).read("$.callbackUrl").toString(); | ||
final var interval = JsonPath.parse(response.getContentAsString()).read("$.interval").toString(); | ||
assertEquals("/reports/" + currentTimeStamp, callbackUrl); | ||
assertEquals("10", interval); | ||
} | ||
|
||
@Test | ||
void shouldReturnCallBackUrlWithAcceptedStatusAndInvokeGenerateBoardReportWhenReportTypeIsBoard() throws Exception { | ||
ObjectMapper mapper = new ObjectMapper(); | ||
GenerateReportRequest request = mapper.readValue(new File(REQUEST_FILE_PATH), GenerateReportRequest.class); | ||
String currentTimeStamp = "1685010080107"; | ||
request.setCsvTimeStamp(currentTimeStamp); | ||
|
||
MockHttpServletResponse response = mockMvc | ||
.perform(post("/reports/dora").contentType(MediaType.APPLICATION_JSON) | ||
.content(mapper.writeValueAsString(request))) | ||
.andExpect(status().isAccepted()) | ||
.andReturn() | ||
.getResponse(); | ||
|
||
Thread.sleep(2000); | ||
verify(generateReporterService, times(1)).generateDoraReport(request); | ||
verify(generateReporterService, times(0)).generateBoardReport(request); | ||
|
||
final var callbackUrl = JsonPath.parse(response.getContentAsString()).read("$.callbackUrl").toString(); | ||
final var interval = JsonPath.parse(response.getContentAsString()).read("$.interval").toString(); | ||
assertEquals("/reports/" + currentTimeStamp, callbackUrl); | ||
assertEquals("10", interval); | ||
} | ||
|
||
@Test | ||
void shouldReturnCallBackUrlWithAcceptedStatusAndInvokeGenerateDoraReportWhenReportTypeIsBoard() throws Exception { | ||
ObjectMapper mapper = new ObjectMapper(); | ||
GenerateReportRequest request = mapper.readValue(new File(REQUEST_FILE_PATH), GenerateReportRequest.class); | ||
String currentTimeStamp = "1685010080107"; | ||
request.setCsvTimeStamp(currentTimeStamp); | ||
|
||
MockHttpServletResponse response = mockMvc | ||
.perform(post("/reports/board").contentType(MediaType.APPLICATION_JSON) | ||
.content(mapper.writeValueAsString(request))) | ||
.andExpect(status().isAccepted()) | ||
.andReturn() | ||
.getResponse(); | ||
|
||
verify(generateReporterService, times(0)).generateDoraReport(request); | ||
verify(generateReporterService, times(1)).generateBoardReport(request); | ||
|
||
final var callbackUrl = JsonPath.parse(response.getContentAsString()).read("$.callbackUrl").toString(); | ||
final var interval = JsonPath.parse(response.getContentAsString()).read("$.interval").toString(); | ||
assertEquals("/reports/" + currentTimeStamp, callbackUrl); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
package heartbeat.controller.report.dto.request; | ||
|
||
import org.junit.jupiter.api.Test; | ||
|
||
import static org.assertj.core.api.Assertions.assertThatThrownBy; | ||
import static org.junit.jupiter.api.Assertions.assertEquals; | ||
|
||
class ReportTypeTest { | ||
|
||
@Test | ||
public void shouldConvertValueToType() { | ||
ReportType boardType = ReportType.fromValue("board"); | ||
ReportType doraType = ReportType.fromValue("dora"); | ||
|
||
assertEquals(boardType, ReportType.BOARD); | ||
assertEquals(doraType, ReportType.DORA); | ||
} | ||
|
||
@Test | ||
public void shouldThrowExceptionWhenDateTypeNotSupported() { | ||
assertThatThrownBy(() -> ReportType.fromValue("unknown")).isInstanceOf(IllegalArgumentException.class) | ||
.hasMessageContaining("ReportType not found!"); | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -103,13 +103,16 @@ | |
import static org.mockito.ArgumentMatchers.any; | ||
import static org.mockito.ArgumentMatchers.anyLong; | ||
import static org.mockito.ArgumentMatchers.anyString; | ||
|
||
import static heartbeat.TestFixtures.BUILDKITE_TOKEN; | ||
import static org.mockito.Mockito.doAnswer; | ||
import static org.mockito.Mockito.doReturn; | ||
import static org.mockito.Mockito.doNothing; | ||
import static org.mockito.Mockito.mock; | ||
import static org.mockito.Mockito.verify; | ||
import static org.mockito.Mockito.when; | ||
|
||
import static heartbeat.TestFixtures.BUILDKITE_TOKEN; | ||
import static org.mockito.Mockito.spy; | ||
import static org.mockito.Mockito.doThrow; | ||
import static org.mockito.internal.verification.VerificationModeFactory.times; | ||
|
||
@ExtendWith(MockitoExtension.class) | ||
|
@@ -118,12 +121,19 @@ class GenerateReporterServiceTest { | |
|
||
public static final String SITE_ATLASSIAN_NET = "https://site.atlassian.net"; | ||
|
||
private static final String REQUEST_FILE_PATH = "src/test/java/heartbeat/controller/report/request.json"; | ||
|
||
private static final String RESPONSE_FILE_PATH = "src/test/java/heartbeat/controller/report/reportResponse.json"; | ||
|
||
@InjectMocks | ||
GenerateReporterService generateReporterService; | ||
|
||
@Mock | ||
JiraService jiraService; | ||
|
||
@Mock | ||
IdUtil idUtil; | ||
|
||
@Mock | ||
WorkDay workDay; | ||
|
||
|
@@ -577,7 +587,7 @@ void shouldGenerateCsvForPipelineWithPipelineMetricAndBuildInfoIsEmpty() throws | |
return null; | ||
}).when(csvFileGenerator).convertPipelineDataToCSV(any(), any()); | ||
|
||
generateReporterService.generateReporter(request); | ||
generateReporterService.generateDoraReport(request); | ||
|
||
boolean isExists = Files.exists(csvFilePath); | ||
Assertions.assertTrue(isExists); | ||
|
@@ -627,7 +637,7 @@ void shouldGenerateCsvForPipelineWithPipelineMetric() throws IOException { | |
return null; | ||
}).when(csvFileGenerator).convertPipelineDataToCSV(any(), any()); | ||
|
||
generateReporterService.generateReporter(request); | ||
generateReporterService.generateDoraReport(request); | ||
|
||
boolean isExists = Files.exists(mockPipelineCsvPath); | ||
Assertions.assertTrue(isExists); | ||
|
@@ -673,7 +683,7 @@ void shouldNotGenerateCsvForPipelineWithPipelineLeadTimeIsNull() throws IOExcept | |
when(gitHubService.fetchPipelinesLeadTime(any(), any(), any())).thenReturn(null); | ||
when(buildKiteService.getStepsBeforeEndStep(any(), any())).thenReturn(List.of("xx")); | ||
|
||
generateReporterService.generateReporter(request); | ||
generateReporterService.generateDoraReport(request); | ||
|
||
boolean isExists = Files.exists(mockPipelineCsvPath); | ||
Assertions.assertFalse(isExists); | ||
|
@@ -718,7 +728,7 @@ void shouldNotGenerateCsvForPipelineWithCommitIsNull() throws IOException { | |
when(gitHubService.fetchPipelinesLeadTime(any(), any(), any())).thenReturn(null); | ||
when(buildKiteService.getStepsBeforeEndStep(any(), any())).thenReturn(List.of("xx")); | ||
|
||
generateReporterService.generateReporter(request); | ||
generateReporterService.generateDoraReport(request); | ||
|
||
boolean isExists = Files.exists(mockPipelineCsvPath); | ||
Assertions.assertFalse(isExists); | ||
|
@@ -762,7 +772,7 @@ void shouldNotGenerateCsvForPipeline() throws IOException { | |
when(gitHubService.fetchPipelinesLeadTime(any(), any(), any())).thenReturn(null); | ||
when(buildKiteService.getStepsBeforeEndStep(any(), any())).thenReturn(List.of("xx")); | ||
|
||
generateReporterService.generateReporter(request); | ||
generateReporterService.generateDoraReport(request); | ||
|
||
boolean isExists = Files.exists(mockPipelineCsvPath); | ||
Assertions.assertFalse(isExists); | ||
|
@@ -1266,6 +1276,126 @@ void shouldPutReportInHandlerWhenCallSaveReporterInHandler() throws IOException | |
verify(asyncReportRequestHandler, times(1)).putReport(reportId, reportResponse); | ||
} | ||
|
||
@Test | ||
void shouldPutBoardReportIntoHandlerWhenCallGenerateBoardReport() throws IOException, InterruptedException { | ||
ObjectMapper mapper = new ObjectMapper(); | ||
GenerateReportRequest reportRequest = mapper.readValue(new File(REQUEST_FILE_PATH), | ||
GenerateReportRequest.class); | ||
ReportResponse reportResponse = mapper.readValue(new File(RESPONSE_FILE_PATH), ReportResponse.class); | ||
reportRequest.setCsvTimeStamp("1683734399999"); | ||
MetricsDataReady previousMetricsReady = MetricsDataReady.builder() | ||
.isBoardMetricsReady(true) | ||
.isPipelineMetricsReady(false) | ||
.isSourceControlMetricsReady(false) | ||
.build(); | ||
GenerateReporterService spyGenerateReporterService = spy(generateReporterService); | ||
|
||
doReturn(reportResponse).when(spyGenerateReporterService).generateReporter(reportRequest); | ||
when(asyncReportRequestHandler.getMetricsDataReady(reportRequest.getCsvTimeStamp())) | ||
.thenReturn(previousMetricsReady); | ||
|
||
spyGenerateReporterService.generateBoardReport(reportRequest); | ||
|
||
Thread.sleep(2000); | ||
verify(spyGenerateReporterService, times(1)).generateReporter(reportRequest); | ||
verify(spyGenerateReporterService, times(1)) | ||
.initializeMetricsDataReadyInHandler(reportRequest.getCsvTimeStamp(), reportRequest.getMetrics()); | ||
verify(spyGenerateReporterService, times(1)).saveReporterInHandler( | ||
spyGenerateReporterService.generateReporter(reportRequest), reportRequest.getCsvTimeStamp()); | ||
verify(spyGenerateReporterService, times(1)).updateMetricsDataReadyInHandler(reportRequest.getCsvTimeStamp(), | ||
reportRequest.getMetrics()); | ||
} | ||
|
||
@Test | ||
void shouldPutExceptionInHandlerWhenCallGenerateBoardReportThrowException() | ||
throws IOException, InterruptedException { | ||
ObjectMapper mapper = new ObjectMapper(); | ||
GenerateReportRequest reportRequest = mapper.readValue(new File(REQUEST_FILE_PATH), | ||
GenerateReportRequest.class); | ||
ReportResponse reportResponse = mapper.readValue(new File(RESPONSE_FILE_PATH), ReportResponse.class); | ||
reportRequest.setCsvTimeStamp("1683734399999"); | ||
String boardTimeStamp = "board-1683734399999"; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what is must be changed with this comment There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
GenerateReporterService spyGenerateReporterService = spy(generateReporterService); | ||
GenerateReportException e = new GenerateReportException( | ||
"Failed to update metrics data ready through this timestamp."); | ||
|
||
doReturn(reportResponse).when(spyGenerateReporterService).generateReporter(reportRequest); | ||
doThrow(e).when(spyGenerateReporterService).updateMetricsDataReadyInHandler(any(), any()); | ||
when(asyncReportRequestHandler.getMetricsDataReady(reportRequest.getCsvTimeStamp())).thenReturn(null); | ||
|
||
spyGenerateReporterService.generateBoardReport(reportRequest); | ||
|
||
Thread.sleep(2000L); | ||
verify(spyGenerateReporterService, times(1)) | ||
.initializeMetricsDataReadyInHandler(reportRequest.getCsvTimeStamp(), reportRequest.getMetrics()); | ||
verify(spyGenerateReporterService, times(1)) | ||
.saveReporterInHandler(spyGenerateReporterService.generateReporter(reportRequest), boardTimeStamp); | ||
verify(asyncExceptionHandler, times(1)).put(boardTimeStamp, e); | ||
} | ||
|
||
@Test | ||
void shouldGeneratePipelineReportAndUpdatePipelineMetricsReadyWhenCallGeneratePipelineReport() | ||
throws IOException, InterruptedException { | ||
ObjectMapper mapper = new ObjectMapper(); | ||
GenerateReportRequest reportRequest = mapper.readValue(new File(REQUEST_FILE_PATH), | ||
GenerateReportRequest.class); | ||
reportRequest.setMetrics(List.of("Deployment frequency")); | ||
ReportResponse reportResponse = mapper.readValue(new File(RESPONSE_FILE_PATH), ReportResponse.class); | ||
GenerateReporterService spyGenerateReporterService = spy(generateReporterService); | ||
reportRequest.setCsvTimeStamp("1683734399999"); | ||
String doraTimeStamp = "dora-1683734399999"; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same as There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
MetricsDataReady previousMetricsReady = MetricsDataReady.builder() | ||
.isBoardMetricsReady(null) | ||
.isPipelineMetricsReady(false) | ||
.isSourceControlMetricsReady(false) | ||
.build(); | ||
|
||
doReturn(reportResponse).when(spyGenerateReporterService).generateReporter(reportRequest); | ||
when(asyncReportRequestHandler.getMetricsDataReady(reportRequest.getCsvTimeStamp())) | ||
.thenReturn(previousMetricsReady); | ||
|
||
spyGenerateReporterService.generateDoraReport(reportRequest); | ||
|
||
Thread.sleep(2000); | ||
verify(spyGenerateReporterService, times(1)).generateReporter(any()); | ||
verify(spyGenerateReporterService, times(1)).initializeMetricsDataReadyInHandler(any(), any()); | ||
verify(spyGenerateReporterService, times(1)) | ||
.saveReporterInHandler(spyGenerateReporterService.generateReporter(any()), doraTimeStamp); | ||
verify(spyGenerateReporterService, times(1)).updateMetricsDataReadyInHandler(any(), any()); | ||
} | ||
|
||
@Test | ||
void shouldGenerateCodebaseReportAndUpdateCodebaseMetricsReadyWhenCallGeneratePipelineReport() | ||
throws IOException, InterruptedException { | ||
ObjectMapper mapper = new ObjectMapper(); | ||
GenerateReportRequest reportRequest = mapper.readValue(new File(REQUEST_FILE_PATH), | ||
GenerateReportRequest.class); | ||
reportRequest.setMetrics(List.of("Lead time for changes")); | ||
ReportResponse reportResponse = mapper.readValue(new File(RESPONSE_FILE_PATH), ReportResponse.class); | ||
GenerateReporterService spyGenerateReporterService = spy(generateReporterService); | ||
reportRequest.setCsvTimeStamp("1683734399999"); | ||
String codebaseTimeStamp = "github-1683734399999"; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same as board There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
MetricsDataReady previousMetricsReady = MetricsDataReady.builder() | ||
.isBoardMetricsReady(null) | ||
.isPipelineMetricsReady(false) | ||
.isSourceControlMetricsReady(false) | ||
.build(); | ||
|
||
doReturn(reportResponse).when(spyGenerateReporterService).generateReporter(reportRequest); | ||
when(asyncReportRequestHandler.getMetricsDataReady(reportRequest.getCsvTimeStamp())) | ||
.thenReturn(previousMetricsReady); | ||
|
||
spyGenerateReporterService.generateDoraReport(reportRequest); | ||
|
||
Thread.sleep(2000); | ||
verify(spyGenerateReporterService, times(1)).generateReporter(any()); | ||
verify(spyGenerateReporterService, times(1)).initializeMetricsDataReadyInHandler(any(), any()); | ||
verify(spyGenerateReporterService, times(1)) | ||
.saveReporterInHandler(spyGenerateReporterService.generateReporter(any()), codebaseTimeStamp); | ||
verify(spyGenerateReporterService, times(1)).updateMetricsDataReadyInHandler(any(), any()); | ||
|
||
} | ||
|
||
private JiraBoardSetting buildJiraBoardSetting() { | ||
return JiraBoardSetting.builder() | ||
.treatFlagCardAsBlock(true) | ||
|
Unchanged files with check annotations Beta
import { getByText, queryByText, render, waitFor } from '@testing-library/react' | ||
Check warning on line 1 in frontend/__tests__/src/components/Metrics/ReportStep/ExpiredDialog.test.tsx GitHub Actions / frontend-check
Check warning on line 1 in frontend/__tests__/src/components/Metrics/ReportStep/ExpiredDialog.test.tsx GitHub Actions / frontend-check
Check warning on line 1 in frontend/__tests__/src/components/Metrics/ReportStep/ExpiredDialog.test.tsx GitHub Actions / frontend-check
|
||
import { setupStore } from '../../../utils/setupStoreUtil' | ||
import { ExpiredDialog } from '@src/components/Metrics/ReportStep/ExpiredDialog' | ||
import { Provider } from 'react-redux' |
import React from 'react' | ||
import { useExportCsvEffect } from '@src/hooks/useExportCsvEffect' | ||
import { MESSAGE } from '@src/constants/resources' | ||
import { formatMillisecondsToHours } from '@src/utils/util' | ||
Check warning on line 31 in frontend/__tests__/src/components/Metrics/ReportStep/ReportStep.test.tsx GitHub Actions / frontend-check
|
||
jest.mock('@src/context/stepper/StepperSlice', () => ({ | ||
...jest.requireActual('@src/context/stepper/StepperSlice'), |
// <reference types="cypress" /> | ||
import fs = require('fs') | ||
module.exports = (on, config) => { | ||
Check warning on line 4 in frontend/cypress/plugins/clearDownloadFile.ts GitHub Actions / frontend-check
|
||
on('task', { | ||
clearDownloads: () => { | ||
const downloadsFolder = 'cypress/downloads' |
}) | ||
} | ||
const [_, __, ...args] = process.argv | ||
Check warning on line 141 in frontend/scripts/runE2eWithServer.ts GitHub Actions / frontend-check
Check warning on line 141 in frontend/scripts/runE2eWithServer.ts GitHub Actions / frontend-check
Check warning on line 141 in frontend/scripts/runE2eWithServer.ts GitHub Actions / frontend-check
|
||
main(args) |
optionList: string[] | ||
selectedOption: string[] | ||
// There is an any because m-ui strictly define its type, but the parameters are not that strict. Maybe because of version diff | ||
onChangeHandler: any | ||
Check warning on line 10 in frontend/src/components/Common/MultiAutoComplete/index.tsx GitHub Actions / frontend-check
|
||
isSelectAll: boolean | ||
textFieldLabel: string | ||
isError: boolean |
fontSize: '1rem', | ||
fontWeight: '250', | ||
fontFamily: theme.typography.fontFamily, | ||
backgroundColor: theme.components!.tip.color, | ||
Check warning on line 17 in frontend/src/components/Common/NotificationButton/style.tsx GitHub Actions / frontend-check
|
||
'& .MuiTooltip-arrow': { | ||
color: theme.components!.tip.color, | ||
Check warning on line 19 in frontend/src/components/Common/NotificationButton/style.tsx GitHub Actions / frontend-check
|
||
}, | ||
zIndex: Z_INDEX.TOOLTIP, | ||
} |
import Stack from '@mui/material/Stack' | ||
Check warning on line 1 in frontend/src/components/HomeGuide/index.tsx GitHub Actions / frontend-check
|
||
import { useNavigate } from 'react-router-dom' | ||
import { useAppDispatch } from '@src/hooks/useAppDispatch' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why add v1? please see the doc: http://13.214.14.43:4321/api/v1/swagger-ui/index.html#/
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done