Skip to content
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-984 [frontend][backend]: add reverse chart for classification #1548

Merged
merged 21 commits into from
Jul 26, 2024
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
83e8464
ADM-984 [backend]: finish the backend
zhou-yinyuan Jul 23, 2024
d97aabd
Merge branch 'main' into ADM-984
zhou-yinyuan Jul 23, 2024
e77eccf
ADM-984 [frontend]: add pie for classification
zhou-yinyuan Jul 24, 2024
e678525
ADM-984 [frontend][backend]: add totalCardCounts
zhou-yinyuan Jul 24, 2024
a217b21
ADM-984 [frontend]: add classification reverse chart and fix test
zhou-yinyuan Jul 24, 2024
1dd0fb7
ADM-984 [frontend]: hide the switch icon when only one legend
zhou-yinyuan Jul 24, 2024
218fe8c
ADM-984 [frontend]: add animation for classification charts when clic…
zhou-yinyuan Jul 25, 2024
8a5eca0
ADM-984 [frontend]: fix the bug that board chart button is not clicke…
zhou-yinyuan Jul 25, 2024
e1a2dae
ADM-984 [backend]: add story points
zhou-yinyuan Jul 25, 2024
a62e8a8
Merge branch 'main' into ADM-984
zhou-yinyuan Jul 25, 2024
d9ebaee
ADM-984 [frontend]: fix sonar
zhou-yinyuan Jul 25, 2024
4001748
ADM-984 [frontend]: change readme
zhou-yinyuan Jul 25, 2024
b7db538
ADM-984 [frontend]: adjust the position of pie chart
zhou-yinyuan Jul 25, 2024
4dd7650
ADM-984 [frontend]: adjust the tech from setTimeout to requestAnimati…
zhou-yinyuan Jul 25, 2024
055b202
ADM-984 [frontend]: adjust the click
zhou-yinyuan Jul 25, 2024
34651ac
ADM-984 [frontend][backend]: fix comments
zhou-yinyuan Jul 26, 2024
85df2ee
ADM-984 [frontend]: fix the Accuracy of the rotate deg
zhou-yinyuan Jul 26, 2024
f32cb97
ADM-984 [frontend]: fix comments
zhou-yinyuan Jul 26, 2024
77aca45
ADM-984 [frontend]: change the pie chart
zhou-yinyuan Jul 26, 2024
bb8c46e
ADM-984 [frontend]: fix comment
zhou-yinyuan Jul 26, 2024
3ef7f14
ADM-984 [frontend]: fix comments
zhou-yinyuan Jul 26, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,9 @@ In report `chart` page, heartbeat provide a better visualization on delivery and

- Board chart
![Image 3-22-1](https://cdn.jsdelivr.net/gh/au-heartbeat/data-hosting@main/readme/3-22-1-2.png)
In the classification chart of the board chart, if the legend is more than 1, you can click the switch button to reverse the chart. the reversed chart will show the sum of each legend in all time periods.
![Image 3-22-1](https://cdn.jsdelivr.net/gh/au-heartbeat/data-hosting@main/readme/reverse-classification-chart.png)

- Dora chart
![Image 3-22-2](https://cdn.jsdelivr.net/gh/au-heartbeat/data-hosting@main/readme/3-22-2-2.png)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ public class Classification {

private String fieldName;

private List<ClassificationNameValuePair> pairList;
private int totalCardCount;

private double storyPoints;

private List<ClassificationInfo> classificationInfos;

}
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,14 @@
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class ClassificationNameValuePair {
public class ClassificationInfo {

private String name;

private Double value;

private int cardCount;

private double storyPoints;

}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
import heartbeat.controller.report.dto.response.BoardCSVConfig;
import heartbeat.controller.report.dto.response.BoardCSVConfigEnum;
import heartbeat.controller.report.dto.response.Classification;
import heartbeat.controller.report.dto.response.ClassificationNameValuePair;
import heartbeat.controller.report.dto.response.ClassificationInfo;
import heartbeat.controller.report.dto.response.CycleTime;
import heartbeat.controller.report.dto.response.CycleTimeForSelectedStepItem;
import heartbeat.controller.report.dto.response.DeploymentFrequency;
Expand Down Expand Up @@ -446,7 +446,7 @@ private String formatStepName(CycleTimeForSelectedStepItem cycleTimeForSelectedS
private List<String[]> getRowsFormClassification(Classification classificationList) {
List<String[]> rows = new ArrayList<>();
String fieldName = String.valueOf((classificationList.getFieldName()));
List<ClassificationNameValuePair> pairList = classificationList.getPairList();
List<ClassificationInfo> pairList = classificationList.getClassificationInfos();
pairList.forEach(
nameValuePair -> rows.add(new String[] { "Classifications", fieldName + " / " + nameValuePair.getName(),
DecimalUtil.formatDecimalTwo(nameValuePair.getValue() * 100) }));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@
import heartbeat.controller.board.dto.response.JiraCardDTO;
import heartbeat.controller.board.dto.response.TargetField;
import heartbeat.controller.report.dto.response.Classification;
import heartbeat.controller.report.dto.response.ClassificationNameValuePair;
import heartbeat.controller.report.dto.response.ClassificationInfo;
import heartbeat.service.report.ICardFieldDisplayName;
import heartbeat.service.report.calculator.model.CardCountAndStoryPointsPairInClassification;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;

Expand All @@ -32,12 +33,16 @@ public class ClassificationCalculator {

public List<Classification> calculate(List<TargetField> targetFields, CardCollection cards) {
List<Classification> classificationFields = new ArrayList<>();
Map<String, Map<String, Integer>> resultMap = new HashMap<>();
Map<String, Map<String, CardCountAndStoryPointsPairInClassification>> resultMap = new HashMap<>();
Map<String, String> nameMap = new HashMap<>();

targetFields.stream().filter(TargetField::isFlag).forEach(targetField -> {
Map<String, Integer> innerMap = new HashMap<>();
innerMap.put(NONE_KEY, cards.getCardsNumber());
Map<String, CardCountAndStoryPointsPairInClassification> innerMap = new HashMap<>();
innerMap.put(NONE_KEY,
CardCountAndStoryPointsPairInClassification.builder()
.cardCount(cards.getCardsNumber())
.storyPoints(cards.getStoryPointSum())
.build());
resultMap.put(targetField.getKey(), innerMap);
nameMap.put(targetField.getKey(), targetField.getName());
});
Expand All @@ -46,26 +51,29 @@ public List<Classification> calculate(List<TargetField> targetFields, CardCollec
JiraCardField jiraCardFields = jiraCardResponse.getBaseInfo().getFields();
Map<String, Object> tempFields = extractFields(jiraCardFields);

mapFields(tempFields, resultMap);
mapFields(jiraCardFields.getStoryPoints(), tempFields, resultMap);
}

resultMap.forEach((fieldName, valueMap) -> {
List<ClassificationNameValuePair> classificationNameValuePair = new ArrayList<>();
List<ClassificationInfo> classificationInfo = new ArrayList<>();

if (valueMap.get(NONE_KEY) == 0) {
if (valueMap.get(NONE_KEY).getCardCount() == 0 && valueMap.get(NONE_KEY).getStoryPoints() == 0) {
valueMap.remove(NONE_KEY);
}

valueMap.forEach((displayName, count) -> classificationNameValuePair
.add(new ClassificationNameValuePair(displayName, (double) count / cards.getCardsNumber())));
valueMap.forEach((displayName, count) -> classificationInfo
.add(new ClassificationInfo(displayName, (double) count.getCardCount() / cards.getCardsNumber(),
count.getCardCount(), count.getStoryPoints())));

classificationFields.add(new Classification(nameMap.get(fieldName), classificationNameValuePair));
classificationFields.add(new Classification(nameMap.get(fieldName), cards.getCardsNumber(),
cards.getStoryPointSum(), classificationInfo));
});

return classificationFields;
}

private void mapFields(Map<String, Object> tempFields, Map<String, Map<String, Integer>> resultMap) {
private void mapFields(double storyPoints, Map<String, Object> tempFields,
Map<String, Map<String, CardCountAndStoryPointsPairInClassification>> resultMap) {
tempFields.forEach((tempFieldsKey, object) -> {
if (object instanceof JsonArray objectArray) {
List<JsonObject> objectList = new ArrayList<>();
Expand All @@ -75,36 +83,50 @@ private void mapFields(Map<String, Object> tempFields, Map<String, Map<String, I
objectList.add(jsonObject);
}
});
mapArrayField(resultMap, tempFieldsKey, (List.of(objectList)));
mapArrayField(storyPoints, resultMap, tempFieldsKey, List.of(objectList));
}
else if (object instanceof List) {
mapArrayField(resultMap, tempFieldsKey, (List.of(object)));
mapArrayField(storyPoints, resultMap, tempFieldsKey, List.of(object));
}
else if (object != null) {
Map<String, Integer> countMap = resultMap.get(tempFieldsKey);
Map<String, CardCountAndStoryPointsPairInClassification> countMap = resultMap.get(tempFieldsKey);
if (countMap != null) {
String displayName = pickDisplayNameFromObj(object);
Integer count = countMap.getOrDefault(displayName, 0);
countMap.put(displayName, count > 0 ? count + 1 : 1);
countMap.put(NONE_KEY, countMap.get(NONE_KEY) - 1);
CardCountAndStoryPointsPairInClassification count = countMap.getOrDefault(displayName,
CardCountAndStoryPointsPairInClassification.of());
count.addCardCount();
count.addStoryPoints(storyPoints);
countMap.put(displayName, count);
CardCountAndStoryPointsPairInClassification noneCount = countMap.get(NONE_KEY);
noneCount.reduceCardCount();
noneCount.reduceStoryPoints(storyPoints);
countMap.put(NONE_KEY, noneCount);
}
}
});
}

private void mapArrayField(Map<String, Map<String, Integer>> resultMap, String fieldsKey, List<Object> objects) {
Map<String, Integer> countMap = resultMap.get(fieldsKey);
private void mapArrayField(double storyPoints,
Map<String, Map<String, CardCountAndStoryPointsPairInClassification>> resultMap, String fieldsKey,
List<Object> objects) {
Map<String, CardCountAndStoryPointsPairInClassification> countMap = resultMap.get(fieldsKey);
if (countMap != null) {
for (Object object : (List) objects.get(0)) {
String displayName = pickDisplayNameFromObj(object);
Integer count = countMap.getOrDefault(displayName, 0);
countMap.put(displayName, count > 0 ? count + 1 : 1);
CardCountAndStoryPointsPairInClassification count = countMap.getOrDefault(displayName,
CardCountAndStoryPointsPairInClassification.of());
count.addCardCount();
count.addStoryPoints(storyPoints);
countMap.put(displayName, count);
}
if (!objects.isEmpty() && objects.get(0) instanceof List && ((List<?>) objects.get(0)).isEmpty()) {
if (((List<?>) objects.get(0)).isEmpty()) {
countMap.put(NONE_KEY, countMap.get(NONE_KEY));
}
else {
countMap.put(NONE_KEY, countMap.get(NONE_KEY) - 1);
CardCountAndStoryPointsPairInClassification noneCount = countMap.get(NONE_KEY);
noneCount.reduceCardCount();
noneCount.reduceStoryPoints(storyPoints);
countMap.put(NONE_KEY, noneCount);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package heartbeat.service.report.calculator.model;

import lombok.Builder;
import lombok.Data;

@Data
@Builder
public class CardCountAndStoryPointsPairInClassification {

private int cardCount;

private double storyPoints;

public static CardCountAndStoryPointsPairInClassification of() {
return new CardCountAndStoryPointsPairInClassification(0, 0.0);
}

public void addCardCount() {
this.cardCount = Math.max(this.cardCount, 0);
this.cardCount += 1;
}

public void addStoryPoints(double storyPoints) {
this.storyPoints = Math.max(this.storyPoints, 0);
this.storyPoints += storyPoints;
}

public void reduceCardCount() {
this.cardCount -= 1;
this.cardCount = Math.max(this.cardCount, 0);
}

public void reduceStoryPoints(double storyPoints) {
this.storyPoints -= storyPoints;
this.storyPoints = Math.max(this.storyPoints, 0);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
import heartbeat.controller.report.dto.response.AvgLeadTimeForChanges;
import heartbeat.controller.report.dto.response.BoardCSVConfig;
import heartbeat.controller.report.dto.response.Classification;
import heartbeat.controller.report.dto.response.ClassificationNameValuePair;
import heartbeat.controller.report.dto.response.ClassificationInfo;
import heartbeat.controller.report.dto.response.CycleTime;
import heartbeat.controller.report.dto.response.CycleTimeForSelectedStepItem;
import heartbeat.controller.report.dto.response.DailyDeploymentCount;
Expand Down Expand Up @@ -341,8 +341,8 @@ void shouldHasContentWhenGetDataFromCsvGivenDataTypeIsMetric() {
.velocity(Velocity.builder().velocityForCards(2).velocityForSP(7).build())
.classificationList(List.of(Classification.builder()
.fieldName("Issue Type")
.pairList(List.of(ClassificationNameValuePair.builder().name("Bug").value(0.3333333333333333).build(),
ClassificationNameValuePair.builder().name("Story").value(0.6666666666666666).build()))
.classificationInfos(List.of(ClassificationInfo.builder().name("Bug").value(0.3333333333333333).build(),
ClassificationInfo.builder().name("Story").value(0.6666666666666666).build()))
.build()))
.cycleTime(CycleTime.builder()
.totalTimeForCards(29.26)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package heartbeat.service.report;

import heartbeat.controller.report.dto.response.Classification;
import heartbeat.controller.report.dto.response.ClassificationNameValuePair;
import heartbeat.controller.report.dto.response.ClassificationInfo;
import heartbeat.controller.report.dto.response.ReportResponse;
import heartbeat.controller.report.dto.response.Rework;
import heartbeat.controller.report.dto.response.Velocity;
Expand Down Expand Up @@ -32,8 +32,8 @@ public static ReportResponse MOCK_METRIC_CSV_DATA() {
.velocity(Velocity.builder().velocityForCards(2).velocityForSP(7).build())
.classificationList(List.of(Classification.builder()
.fieldName("Issue Type")
.pairList(List.of(ClassificationNameValuePair.builder().name("Bug").value(0.3333333333333333).build(),
ClassificationNameValuePair.builder().name("Story").value(0.6666666666666666).build()))
.classificationInfos(List.of(ClassificationInfo.builder().name("Bug").value(0.3333333333333333).build(),
ClassificationInfo.builder().name("Story").value(0.6666666666666666).build()))
.build()))
.cycleTime(CycleTime.builder()
.totalTimeForCards(29.26)
Expand Down
Loading
Loading