Skip to content

Commit

Permalink
ADM-575&ADM-569 [backend][frontend][stub]
Browse files Browse the repository at this point in the history
ADM-575: Issue about calculating multiple columns' cycle time
  • Loading branch information
xingxy0205 authored Sep 11, 2023
2 parents 90f4ee4 + b354447 commit cb64d2c
Show file tree
Hide file tree
Showing 10 changed files with 51 additions and 15,479 deletions.
5 changes: 2 additions & 3 deletions backend/src/main/java/heartbeat/client/JiraFeignClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,15 @@
import heartbeat.client.dto.board.jira.FieldResponseDTO;
import heartbeat.client.dto.board.jira.JiraBoardConfigDTO;
import heartbeat.client.dto.board.jira.StatusSelfDTO;

import java.net.URI;

import heartbeat.decoder.JiraFeignClientDecoder;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestHeader;

import java.net.URI;

@FeignClient(value = "jiraFeignClient", url = "${jira.url}", configuration = JiraFeignClientDecoder.class)
public interface JiraFeignClient {

Expand Down
1 change: 1 addition & 0 deletions backend/src/main/java/heartbeat/config/CacheConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public class CacheConfig {
public CacheManager ehCacheManager() {
CachingProvider provider = Caching.getCachingProvider();
CacheManager cacheManager = provider.getCacheManager();
cacheManager.createCache("sprintInfo", getCacheConfiguration(String.class));
cacheManager.createCache("jiraConfig", getCacheConfiguration(JiraBoardConfigDTO.class));
cacheManager.createCache("jiraStatusCategory", getCacheConfiguration(StatusSelfDTO.class));
cacheManager.createCache("jiraActivityfeed", getCacheConfiguration(CardHistoryResponseDTO.class));
Expand Down
50 changes: 22 additions & 28 deletions backend/src/main/java/heartbeat/service/board/jira/JiraService.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
package heartbeat.service.board.jira;

import heartbeat.exception.BaseException;
import heartbeat.exception.InternalServerErrorException;

import static java.lang.Long.parseLong;
import static java.util.Objects.isNull;
import static java.util.Objects.nonNull;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
Expand All @@ -21,9 +15,9 @@
import heartbeat.client.dto.board.jira.HistoryDetail;
import heartbeat.client.dto.board.jira.IssueField;
import heartbeat.client.dto.board.jira.Issuetype;
import heartbeat.client.dto.board.jira.JiraCardWithFields;
import heartbeat.client.dto.board.jira.JiraBoardConfigDTO;
import heartbeat.client.dto.board.jira.JiraCard;
import heartbeat.client.dto.board.jira.JiraCardWithFields;
import heartbeat.client.dto.board.jira.JiraColumn;
import heartbeat.client.dto.board.jira.Sprint;
import heartbeat.client.dto.board.jira.StatusSelfDTO;
Expand All @@ -45,9 +39,15 @@
import heartbeat.controller.board.dto.response.StepsDay;
import heartbeat.controller.board.dto.response.TargetField;
import heartbeat.exception.BadRequestException;
import heartbeat.exception.BaseException;
import heartbeat.exception.InternalServerErrorException;
import heartbeat.exception.NoContentException;
import heartbeat.util.BoardUtil;
import jakarta.annotation.PreDestroy;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;

import java.net.URI;
import java.util.ArrayList;
Expand All @@ -65,10 +65,9 @@
import java.util.stream.IntStream;
import java.util.stream.Stream;

import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import static java.lang.Long.parseLong;
import static java.util.Objects.isNull;
import static java.util.Objects.nonNull;

@Service
@RequiredArgsConstructor
Expand All @@ -82,6 +81,8 @@ public class JiraService {
public static final List<String> FIELDS_IGNORE = List.of("summary", "description", "attachment", "duedate",
"issuelinks");

private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

private static final String DONE_CARD_TAG = "done";

private final ThreadPoolTaskExecutor customTaskExecutor;
Expand Down Expand Up @@ -360,9 +361,8 @@ else if (customFieldValue.equals("Flagged") && !fieldValue.isJsonNull()

private String parseJiraJql(BoardType boardType, List<String> doneColumns, BoardRequestParam boardRequestParam) {
if (boardType == BoardType.JIRA) {
return String.format(
"status in ('%s') AND statusCategoryChangedDate >= %s AND statusCategoryChangedDate <= %s",
String.join("','", doneColumns), boardRequestParam.getStartTime(), boardRequestParam.getEndTime());
return String.format("status in ('%s') AND status changed during (%s, %s)", String.join("','", doneColumns),
boardRequestParam.getStartTime(), boardRequestParam.getEndTime());
}
else {
StringBuilder subJql = new StringBuilder();
Expand Down Expand Up @@ -471,20 +471,14 @@ private List<JiraCardDTO> getRealDoneCards(StoryPointsAndCycleTimeRequest reques

private boolean isRealDoneCardByHistory(CardHistoryResponseDTO jiraCardHistory,
StoryPointsAndCycleTimeRequest request) {
HistoryDetail detail = jiraCardHistory.getItems()
List<String> upperDoneStatuses = request.getStatus().stream().map(String::toUpperCase).toList();
long validStartTime = parseLong(request.getStartTime());

return jiraCardHistory.getItems()
.stream()
.filter(historyDetail -> STATUS_FIELD_ID.equals(historyDetail.getFieldId()))
.filter((historyDetail) -> historyDetail.getTimestamp() > parseLong(request.getStartTime()))
.reduce((pre, next) -> next)
.orElse(null);
if (detail == null) {
return false;
}
else {
String displayName = detail.getTo().getDisplayName();
return request.getStatus().contains(displayName.toUpperCase())
|| CardStepsEnum.CLOSED.getValue().equalsIgnoreCase(displayName);
}
.filter(history -> STATUS_FIELD_ID.equals(history.getFieldId()))
.filter(history -> upperDoneStatuses.contains(history.getTo().getDisplayValue().toUpperCase()))
.allMatch(history -> history.getTimestamp() > validStartTime);
}

private CycleTimeInfoDTO getCycleTime(URI baseUrl, String doneCardKey, String token, Boolean treatFlagCardAsBlock,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,8 @@ public static CardHistoryResponseDTO.CardHistoryResponseDTOBuilder CARD_HISTORY_
.items(List.of(new HistoryDetail(2, "status", new Status("In Dev"), new Status("To do")),
new HistoryDetail(3, "status", new Status(REVIEW), new Status("In Dev")),
new HistoryDetail(4, "status", new Status(WAITING_FOR_TESTING), new Status(REVIEW)),
new HistoryDetail(5, "status", new Status(TESTING), new Status(WAITING_FOR_TESTING))));
new HistoryDetail(5, "status", new Status(TESTING), new Status(WAITING_FOR_TESTING)),
new HistoryDetail(1662642750003L, "status", new Status("Done"), new Status(TESTING))));
}

public static CardHistoryResponseDTO.CardHistoryResponseDTOBuilder CARD_HISTORY_RESPONSE_BUILDER_TO_DONE() {
Expand All @@ -185,9 +186,9 @@ public static CardHistoryResponseDTO.CardHistoryResponseDTOBuilder CARD_HISTORY_
new HistoryDetail(2, "assignee", new Status("In Dev"), new Status("To do")),
new HistoryDetail(3, "status", new Status(REVIEW), new Status("In Dev")),
new HistoryDetail(4, "status", new Status(WAITING_FOR_TESTING), new Status(REVIEW)),
new HistoryDetail(5, "status", new Status(TESTING), new Status(WAITING_FOR_TESTING)),
new HistoryDetail(6, "status", new Status(BLOCK), new Status(TESTING)),
new HistoryDetail(7, "status", new Status(FLAG), new Status(BLOCK)),
new HistoryDetail(1672642740000L, "status", new Status(TESTING), new Status(WAITING_FOR_TESTING)),
new HistoryDetail(1672642740001L, "status", new Status(BLOCK), new Status(TESTING)),
new HistoryDetail(1672642740002L, "status", new Status(FLAG), new Status(BLOCK)),
new HistoryDetail(1672642750001L, "customfield_10021", new Status("Impediment"), new Status(FLAG)),
new HistoryDetail(1672642750002L, "flagged", new Status("Impediment"), new Status("removeFlag")),
new HistoryDetail(1672642750003L, "status", new Status("Done"), new Status(TESTING)),
Expand Down Expand Up @@ -343,7 +344,7 @@ public static StoryPointsAndCycleTimeRequest.StoryPointsAndCycleTimeRequestBuild
.site(jiraBoardSetting.getSite())
.project(jiraBoardSetting.getProjectKey())
.boardId(jiraBoardSetting.getBoardId())
.status(jiraBoardSetting.getDoneColumn())
.status(List.of("Done", "Testing"))
.startTime("1672556350000")
.endTime("1676908799000")
.targetFields(jiraBoardSetting.getTargetFields())
Expand Down
16 changes: 6 additions & 10 deletions backend/src/test/java/heartbeat/service/jira/JiraServiceTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -442,12 +442,9 @@ void shouldGetCardsWhenCallGetStoryPointsAndCycleTime() throws JsonProcessingExc
URI baseUrl = URI.create(SITE_ATLASSIAN_NET);
String token = "token";
BoardRequestParam boardRequestParam = BOARD_REQUEST_BUILDER().build();
String jql = String.format(
"status in ('%s') AND statusCategoryChangedDate >= %s AND statusCategoryChangedDate <= %s", "DONE",
String jql = String.format("status in ('%s') AND status changed during (%s, %s)", "DONE",
boardRequestParam.getStartTime(), boardRequestParam.getEndTime());

JiraBoardSetting jiraBoardSetting = JIRA_BOARD_SETTING_BUILD().build();
StoryPointsAndCycleTimeRequest storyPointsAndCycleTimeRequest = STORY_POINTS_FORM_ALL_DONE_CARD().build();
String allDoneCards = objectMapper.writeValueAsString(ALL_DONE_CARDS_RESPONSE_FOR_STORY_POINT_BUILDER().build())
.replaceAll("\"storyPoints\":0", "\"customfield_10016\":null")
.replaceAll("storyPoints", "customfield_10016");
Expand All @@ -461,6 +458,8 @@ void shouldGetCardsWhenCallGetStoryPointsAndCycleTime() throws JsonProcessingExc
.thenReturn(CARD_HISTORY_RESPONSE_BUILDER().build());
when(jiraFeignClient.getTargetField(baseUrl, "PLL", token)).thenReturn(ALL_FIELD_RESPONSE_BUILDER().build());

StoryPointsAndCycleTimeRequest storyPointsAndCycleTimeRequest = STORY_POINTS_FORM_ALL_DONE_CARD().build();
JiraBoardSetting jiraBoardSetting = JIRA_BOARD_SETTING_BUILD().build();
CardCollection cardCollection = jiraService.getStoryPointsAndCycleTimeForDoneCards(
storyPointsAndCycleTimeRequest, jiraBoardSetting.getBoardColumns(), List.of("Zhang San"));

Expand All @@ -473,7 +472,6 @@ void shouldGetCardsWhenCallGetStoryPointsAndCycleTimeWhenBoardTypeIsClassicJira(
// given
URI baseUrl = URI.create(SITE_ATLASSIAN_NET);
String token = "token";
List<String> user = List.of("Zhang San");

JiraBoardSetting jiraBoardSetting = CLASSIC_JIRA_BOARD_SETTING_BUILD().build();
StoryPointsAndCycleTimeRequest storyPointsAndCycleTimeRequest = CLASSIC_JIRA_STORY_POINTS_FORM_ALL_DONE_CARD()
Expand All @@ -495,9 +493,8 @@ void shouldGetCardsWhenCallGetStoryPointsAndCycleTimeWhenBoardTypeIsClassicJira(
// then

CardCollection cardCollection = jiraService.getStoryPointsAndCycleTimeForDoneCards(
storyPointsAndCycleTimeRequest, jiraBoardSetting.getBoardColumns(), user);
assertThat(cardCollection.getStoryPointSum()).isEqualTo(0);
assertThat(cardCollection.getCardsNumber()).isEqualTo(0);
storyPointsAndCycleTimeRequest, jiraBoardSetting.getBoardColumns(), List.of("Zhang San"));
assertThat(cardCollection.getCardsNumber()).isEqualTo(1);
}

@Test
Expand Down Expand Up @@ -561,8 +558,7 @@ void shouldReturnIllegalArgumentExceptionWhenHaveUnknownColumn() throws JsonProc
StoryPointsAndCycleTimeRequest storyPointsAndCycleTimeRequest = STORY_POINTS_FORM_ALL_DONE_CARD().startTime("5")
.build();
BoardRequestParam boardRequestParam = BOARD_REQUEST_BUILDER().startTime("5").build();
String jql = String.format(
"status in ('%s') AND statusCategoryChangedDate >= %s AND statusCategoryChangedDate <= %s", "DONE",
String jql = String.format("status in ('%s') AND status changed during (%s, %s)", "DONE",
boardRequestParam.getStartTime(), boardRequestParam.getEndTime());
URI baseUrl = URI.create(SITE_ATLASSIAN_NET);
String allDoneCards = objectMapper.writeValueAsString(ALL_DONE_CARDS_RESPONSE_FOR_STORY_POINT_BUILDER().build())
Expand Down
15 changes: 8 additions & 7 deletions frontend/cypress/e2e/createANewProject.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,25 @@ import reportPage from '../pages/metrics/report'

const cycleTimeData = [
{ label: 'Name', value: 'Value' },
{ label: 'Average cycle time', value: '8.14(days/SP)' },
{ label: '9.30(days/card)' },
{ label: 'Total development time / Total cycle time', value: '0.64' },
{ label: 'Average cycle time', value: '8.35(days/SP)' },
{ label: '9.55(days/card)' },
{ label: 'Total development time / Total cycle time', value: '0.62' },
{ label: 'Total waiting for testing time / Total cycle time', value: '0.02' },
{ label: 'Total block time / Total cycle time', value: '0.28' },
{ label: 'Total block time / Total cycle time', value: '0.3' },
{ label: 'Total review time / Total cycle time', value: '0.04' },
{ label: 'Total testing time / Total cycle time', value: '0.02' },
{ label: 'Total testing time / Total cycle time', value: '0.01' },
{ label: 'Average development time', value: '5.18(days/SP)' },
{ label: '5.92(days/card)' },
{ label: 'Average waiting for testing time', value: '0.20(days/SP)' },
{ label: '0.23(days/card)' },
{ label: 'Average block time', value: '2.31(days/SP)' },
{ label: '2.64(days/card)' },
{ label: 'Average block time', value: '2.53(days/SP)' },
{ label: '2.89(days/card)' },
{ label: 'Average review time', value: '0.32(days/SP)' },
{ label: '0.37(days/card)' },
{ label: 'Average testing time', value: '0.12(days/SP)' },
{ label: '0.14(days/card)' },
]

const velocityData = [
{ label: 'Name', value: 'Value' },
{ label: 'Velocity(Story Point)', value: '16' },
Expand Down
Loading

0 comments on commit cb64d2c

Please sign in to comment.