Skip to content

Commit

Permalink
Merge branch 'main' into ADM-709
Browse files Browse the repository at this point in the history
  • Loading branch information
Andrea2000728 authored Jan 12, 2024
2 parents 7007521 + b33b7ad commit b1fc0da
Show file tree
Hide file tree
Showing 28 changed files with 797 additions and 481 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,6 @@ ResponseEntity<List<BuildKiteBuildInfo>> getPipelineSteps(@RequestHeader("Author
@RequestParam("per_page") String perPage, @RequestParam("created_from") String createdFrom,
@RequestParam("created_to") String createdTo, @RequestParam("branch[]") List<String> branch);

@Cacheable(cacheNames = "pipelineStepsInfo",
key = "#organizationId+'-'+#pipelineId+'-'+#page+'-'+#perPage+'-'"
+ "+#createdFrom+'-'+#createdTo+'-'+(#branch!=null ? branch.toString() : '')")
@GetMapping(path = "v2/organizations/{organizationId}/pipelines/{pipelineId}/builds",
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
@ResponseStatus(HttpStatus.OK)
Expand Down
1 change: 0 additions & 1 deletion backend/src/main/java/heartbeat/config/CacheConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ public CacheManager ehCacheManager() {
cacheManager.createCache("tokenInfo", getCacheConfiguration(BuildKiteTokenInfo.class));
cacheManager.createCache("buildKiteOrganizationInfo", getCacheConfiguration(List.class));
cacheManager.createCache("pipelineInfo", getCacheConfiguration(List.class));
cacheManager.createCache("pipelineStepsInfo", getCacheConfiguration(List.class));
cacheManager.createCache("githubOrganizationInfo", getCacheConfiguration(List.class));
cacheManager.createCache("githubAllRepos", getCacheConfiguration(List.class));
cacheManager.createCache("githubRepos", getCacheConfiguration(List.class));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package heartbeat.controller.report.dto.response;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class ErrorInfo {

private int status;

private String errorMessage;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package heartbeat.controller.report.dto.response;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class ReportError {

private ErrorInfo boardError;

private ErrorInfo pipelineError;

private ErrorInfo sourceControlError;

}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ public class ReportResponse {

private LeadTimeForChanges leadTimeForChanges;

private ReportError reportError;

private Long exportValidityTime;

private Boolean isBoardMetricsReady;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,12 @@ public GitHubResponse getRepos(@RequestBody @Valid SourceControlDTO sourceContro
public ResponseEntity<Void> verifyToken(@PathVariable @NotBlank String sourceType,
@RequestBody @Valid SourceControlDTO sourceControlDTO) {
log.info("Start to verify source type: {} token.", sourceType);
switch (SourceType.matchSourceType(sourceType)) {
switch (SourceType.fromValue(sourceType)) {
case GITHUB -> {
gitHubService.verifyTokenV2(sourceControlDTO.getToken());
log.info("Successfully verify source type: {} token.", sourceType);
}
default -> {
log.error("Failed to verify source type: {} token.", sourceType);
throw new BadRequestException("Source type is incorrect.");
}
}
return ResponseEntity.status(HttpStatus.NO_CONTENT).build();
Expand All @@ -65,14 +63,12 @@ public ResponseEntity<Void> verifyToken(@PathVariable @NotBlank String sourceTyp
public ResponseEntity<Void> verifyBranch(@PathVariable @NotBlank String sourceType,
@PathVariable @NotBlank String branch, @RequestBody @Valid VerifyBranchRequest request) {
log.info("Start to verify source type: {} branch: {}.", sourceType, branch);
switch (SourceType.matchSourceType(sourceType)) {
switch (SourceType.fromValue(sourceType)) {
case GITHUB -> {
gitHubService.verifyCanReadTargetBranch(request.getRepository(), branch, request.getToken());
log.info("Successfully verify source type: {} branch: {}.", sourceType, branch);
}
default -> {
log.error("Failed to verify source type: {} branch: {}.", sourceType, branch);
throw new BadRequestException("Source type is incorrect.");
}
}
return ResponseEntity.status(HttpStatus.NO_CONTENT).build();
Expand Down
19 changes: 5 additions & 14 deletions backend/src/main/java/heartbeat/controller/source/SourceType.java
Original file line number Diff line number Diff line change
@@ -1,24 +1,15 @@
package heartbeat.controller.source;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import heartbeat.exception.BadRequestException;

@Getter
@NoArgsConstructor
@AllArgsConstructor
public enum SourceType {

GITHUB("github"),
GITHUB;

ANOTHERSOURCETYPE("anotherSourceType");

private String value;

public static SourceType matchSourceType(String value) {
return switch (value) {
public static SourceType fromValue(String sourceType) {
return switch (sourceType) {
case "github" -> GITHUB;
default -> ANOTHERSOURCETYPE;
default -> throw new BadRequestException("Source type is incorrect.");
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,25 +26,25 @@ protected ResponseEntity<Object> handleDecryptProcessException(DecryptDataOrPass
@ExceptionHandler(value = EncryptDecryptProcessException.class)
protected ResponseEntity<Object> handleEncryptProcessException(EncryptDecryptProcessException ex) {
return ResponseEntity.status(ex.getStatus())
.body(new RestApiErrorResponse(ex.getStatus(), ex.getMessage(), "Encrypt or decrypt process failed"));
.body(new RestApiErrorResponse(ex.getStatus(), ex.getMessage(), "Failed to encrypt or decrypt process"));
}

@ExceptionHandler(value = GenerateReportException.class)
protected ResponseEntity<Object> handleGenerateReportException(GenerateReportException ex) {
return ResponseEntity.status(ex.getStatus())
.body(new RestApiErrorResponse(ex.getStatus(), ex.getMessage(), "Generate report failed"));
.body(new RestApiErrorResponse(ex.getStatus(), ex.getMessage(), "Failed to generate report"));
}

@ExceptionHandler(value = NotFoundException.class)
protected ResponseEntity<Object> handleNotFoundException(NotFoundException ex) {
return ResponseEntity.status(ex.getStatus())
.body(new RestApiErrorResponse(ex.getStatus(), ex.getMessage(), "404 Not Found"));
.body(new RestApiErrorResponse(ex.getStatus(), ex.getMessage(), "Not found"));
}

@ExceptionHandler(value = ServiceUnavailableException.class)
protected ResponseEntity<Object> handleTimeoutException(ServiceUnavailableException ex) {
return ResponseEntity.status(ex.getStatus())
.body(new RestApiErrorResponse(ex.getStatus(), ex.getMessage(), "Service Unavailable"));
.body(new RestApiErrorResponse(ex.getStatus(), ex.getMessage(), "Service unavailable"));
}

@ExceptionHandler(value = RequestFailedException.class)
Expand Down Expand Up @@ -106,7 +106,7 @@ public ResponseEntity<Object> handleConstraintViolation(ConstraintViolationExcep
@ExceptionHandler(FileIOException.class)
public ResponseEntity<Object> handleFileIOException(FileIOException ex) {
return ResponseEntity.status(ex.getStatus())
.body(new RestApiErrorResponse(ex.getStatus(), ex.getMessage(), "File read failed"));
.body(new RestApiErrorResponse(ex.getStatus(), ex.getMessage(), "Failed to read file"));
}

@ExceptionHandler(InternalServerErrorException.class)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public void put(String reportId, BaseException e) {
}

public BaseException get(String reportId) {
return exceptionMap.remove(reportId);
return exceptionMap.get(reportId);
}

public void deleteExpireException(long currentTimeStamp) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -470,13 +470,13 @@ private List<String[]> getRowsFromDeploymentFrequency(DeploymentFrequency deploy
.getDeploymentFrequencyOfPipelines();
deploymentFrequencyOfPipelines.forEach(pipeline -> rows.add(new String[] { "Deployment frequency",
pipeline.getName() + " / " + pipeline.getStep().replaceAll(":\\w+: ", "")
+ " / Deployment frequency(deployments/day)",
+ " / Deployment frequency(Deployments/Day)",
DecimalUtil.formatDecimalTwo(pipeline.getDeploymentFrequency()) }));

AvgDeploymentFrequency avgDeploymentFrequency = deploymentFrequency.getAvgDeploymentFrequency();
if (deploymentFrequencyOfPipelines.size() > 1)
rows.add(new String[] { "Deployment frequency",
avgDeploymentFrequency.getName() + " / Deployment frequency(deployments/day)",
avgDeploymentFrequency.getName() + " / Deployment frequency(Deployments/Day)",
DecimalUtil.formatDecimalTwo(avgDeploymentFrequency.getDeploymentFrequency()) });

return rows;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,18 +30,18 @@
import heartbeat.controller.report.dto.request.JiraBoardSetting;
import heartbeat.controller.report.dto.response.BoardCSVConfig;
import heartbeat.controller.report.dto.response.BoardCSVConfigEnum;
import heartbeat.controller.report.dto.response.ErrorInfo;
import heartbeat.controller.report.dto.response.LeadTimeInfo;
import heartbeat.controller.report.dto.response.MetricsDataReady;
import heartbeat.controller.report.dto.response.PipelineCSVInfo;
import heartbeat.controller.report.dto.response.ReportError;
import heartbeat.controller.report.dto.response.ReportResponse;
import heartbeat.exception.BadRequestException;
import heartbeat.exception.BaseException;
import heartbeat.exception.GenerateReportException;
import heartbeat.exception.NotFoundException;
import heartbeat.exception.PermissionDenyException;
import heartbeat.exception.RequestFailedException;
import heartbeat.exception.ServiceUnavailableException;
import heartbeat.exception.UnauthorizedException;
import heartbeat.handler.AsyncExceptionHandler;
import heartbeat.handler.AsyncReportRequestHandler;
import heartbeat.service.board.jira.JiraColumnResult;
Expand Down Expand Up @@ -805,24 +805,23 @@ public boolean checkGenerateReportIsDone(String reportTimeStamp) {
if (validateExpire(System.currentTimeMillis(), Long.parseLong(reportTimeStamp))) {
throw new GenerateReportException("Failed to get report due to report time expires");
}
BaseException boardException = asyncExceptionHandler.get(IdUtil.getBoardReportId(reportTimeStamp));
BaseException doraException = asyncExceptionHandler.get(IdUtil.getPipelineReportId(reportTimeStamp));
handleAsyncException(boardException);
handleAsyncException(doraException);
return asyncReportRequestHandler.isReportReady(reportTimeStamp);
}

private static void handleAsyncException(BaseException exception) {
private ErrorInfo handleAsyncExceptionAndGetErrorInfo(BaseException exception) {
if (Objects.nonNull(exception)) {
switch (exception.getStatus()) {
case 401 -> throw new UnauthorizedException(exception.getMessage());
case 403 -> throw new PermissionDenyException(exception.getMessage());
case 404 -> throw new NotFoundException(exception.getMessage());
case 500 -> throw new GenerateReportException(exception.getMessage());
case 503 -> throw new ServiceUnavailableException(exception.getMessage());
default -> throw new RequestFailedException(exception.getStatus(), exception.getMessage());
int status = exception.getStatus();
final String errorMessage = exception.getMessage();
switch (status) {
case 401, 403, 404 -> {
return ErrorInfo.builder().status(status).errorMessage(errorMessage).build();
}
case 500 -> throw new GenerateReportException(errorMessage);
case 503 -> throw new ServiceUnavailableException(errorMessage);
default -> throw new RequestFailedException(status, errorMessage);
}
}
return null;
}

private void validateExpire(long csvTimeStamp) {
Expand Down Expand Up @@ -873,6 +872,7 @@ public ReportResponse getComposedReportResponse(String reportId, boolean isRepor
ReportResponse codebaseReportResponse = getReportFromHandler(IdUtil.getSourceControlReportId(reportId));
MetricsDataReady metricsDataReady = asyncReportRequestHandler.getMetricsDataReady(reportId);
ReportResponse response = Optional.ofNullable(boardReportResponse).orElse(doraReportResponse);
ReportError reportError = getReportErrorAndHandleAsyncException(reportId);

return ReportResponse.builder()
.velocity(getValueOrNull(boardReportResponse, ReportResponse::getVelocity))
Expand All @@ -888,6 +888,21 @@ public ReportResponse getComposedReportResponse(String reportId, boolean isRepor
.isSourceControlMetricsReady(
getValueOrNull(metricsDataReady, MetricsDataReady::isSourceControlMetricsReady))
.isAllMetricsReady(isReportReady)
.reportError(reportError)
.build();
}

public ReportError getReportErrorAndHandleAsyncException(String reportId) {
BaseException boardException = asyncExceptionHandler.get(IdUtil.getBoardReportId(reportId));
BaseException pipelineException = asyncExceptionHandler.get(IdUtil.getPipelineReportId(reportId));
BaseException sourceControlException = asyncExceptionHandler.get(IdUtil.getSourceControlReportId(reportId));
ErrorInfo boardError = handleAsyncExceptionAndGetErrorInfo(boardException);
ErrorInfo pipelineError = handleAsyncExceptionAndGetErrorInfo(pipelineException);
ErrorInfo sourceControlError = handleAsyncExceptionAndGetErrorInfo(sourceControlException);
return ReportError.builder()
.boardError(boardError)
.pipelineError(pipelineError)
.sourceControlError(sourceControlError)
.build();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ void shouldReturn500StatusWhenServiceThrowException() throws Exception {
final var message = JsonPath.parse(content).read("$.message").toString();
final var hintInfo = JsonPath.parse(content).read("$.hintInfo").toString();
assertThat(message).isEqualTo(FAKE_EXCEPTION_MESSAGE);
assertThat(hintInfo).isEqualTo("Encrypt or decrypt process failed");
assertThat(hintInfo).isEqualTo("Failed to encrypt or decrypt process");
}

@Test
Expand Down Expand Up @@ -224,7 +224,7 @@ void shouldReturn5xxOr4xxWhenDecryptServiceThrowException() throws Exception {
final var internalServerMessage = JsonPath.parse(internalServerContent).read("$.message").toString();
final var internalServerHintInfo = JsonPath.parse(internalServerContent).read("$.hintInfo").toString();
assertThat(internalServerMessage).isEqualTo(FAKE_EXCEPTION_MESSAGE);
assertThat(internalServerHintInfo).isEqualTo("Encrypt or decrypt process failed");
assertThat(internalServerHintInfo).isEqualTo("Failed to encrypt or decrypt process");

var badRequestResponse = mockMvc
.perform(post("/decrypt").content(new ObjectMapper().writeValueAsString(request))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ void shouldReturnInternalServerErrorStatusWhenCheckGenerateReportThrowException(
final var hintInfo = JsonPath.parse(content).read("$.hintInfo").toString();

assertEquals("Report time expires", errorMessage);
assertEquals("Generate report failed", hintInfo);
assertEquals("Failed to generate report", hintInfo);
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -346,9 +346,9 @@ void shouldHasContentWhenGetDataFromCsvGivenDataTypeIsMetric() throws IOExceptio
"Cycle time","Average time(days/card)","0.02"
"Classifications","Issue Type / Bug","33.33"
"Classifications","Issue Type / Story","66.67"
"Deployment frequency","Heartbeat / Deploy prod / Deployment frequency(deployments/day)","0.78"
"Deployment frequency","Heartbeat / Check Frontend License / Deployment frequency(deployments/day)","0.56"
"Deployment frequency","Average / Deployment frequency(deployments/day)","0.67"
"Deployment frequency","Heartbeat / Deploy prod / Deployment frequency(Deployments/Day)","0.78"
"Deployment frequency","Heartbeat / Check Frontend License / Deployment frequency(Deployments/Day)","0.56"
"Deployment frequency","Average / Deployment frequency(Deployments/Day)","0.67"
"Lead time for changes","Heartbeat / Deploy prod / PR Lead Time","0"
"Lead time for changes","Heartbeat / Deploy prod / Pipeline Lead Time","0.02"
"Lead time for changes","Heartbeat / Deploy prod / Total Lead Time","0.02"
Expand Down Expand Up @@ -400,7 +400,7 @@ void shouldHasNoContentForAveragesWhenGetDataFromCsvGivenDataTypeIsMetricAndTheQ

Assertions.assertEquals(metricCsvData, """
"Group","Metrics","Value"
"Deployment frequency","Heartbeat / Deploy prod / Deployment frequency(deployments/day)","0.78"
"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"
"Lead time for changes","Heartbeat / Deploy prod / Total Lead Time","0.02"
Expand Down
Loading

0 comments on commit b1fc0da

Please sign in to comment.