Skip to content

Commit 1f65550

Browse files
authored
ADM-1010 [frontend][backend]: fix the bug when story point sum is zero (#1612)
* ADM-1010 [backend]: modify the bug of the story point sum is zero * ADM-1010 [frontend]: update the classification story point chart when story point sum is zero * ADM-1010 [frontend]: fix test coverage * ADM-1010 [frontend]: add retry times for unstable test * ADM-1010 [frontend]: fix sonar issues * ADM-1010 [frontend]: fix unstable test
1 parent eb2617c commit 1f65550

File tree

5 files changed

+167
-11
lines changed

5 files changed

+167
-11
lines changed

backend/src/main/java/heartbeat/service/report/calculator/ClassificationCalculator.java

+4-3
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,10 @@ public List<Classification> calculate(List<TargetField> targetFields, CardCollec
6161
valueMap.remove(NONE_KEY);
6262
}
6363

64-
valueMap.forEach((displayName, count) -> classificationInfo.add(new ClassificationInfo(displayName,
65-
(double) count.getCardCount() / cards.getCardsNumber(),
66-
count.getStoryPoints() / cards.getStoryPointSum(), count.getCardCount(), count.getStoryPoints())));
64+
valueMap.forEach((displayName, count) -> classificationInfo
65+
.add(new ClassificationInfo(displayName, (double) count.getCardCount() / cards.getCardsNumber(),
66+
cards.getStoryPointSum() != 0 ? count.getStoryPoints() / cards.getStoryPointSum() : 0,
67+
count.getCardCount(), count.getStoryPoints())));
6768

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

backend/src/test/java/heartbeat/service/report/calculator/ClassificationCalculatorTest.java

+37
Original file line numberDiff line numberDiff line change
@@ -536,11 +536,48 @@ void shouldReturnClassificationWithNoneKeyWhenStoryPointIsNotZero() {
536536
assertEquals(1, classificationList.get(0).getCardCountValue());
537537
assertEquals(1, classificationList.get(0).getCardCount());
538538
assertEquals(3, classificationList.get(0).getStoryPoints(), 0.0001);
539+
assertEquals(0.5, classificationList.get(0).getStoryPointsValue(), 0.0001);
539540

540541
assertEquals("None", classificationList.get(1).getName());
541542
assertEquals(0, classificationList.get(1).getCardCountValue());
542543
assertEquals(0, classificationList.get(1).getCardCount());
543544
assertEquals(3, classificationList.get(1).getStoryPoints(), 0.0001);
545+
assertEquals(0.5, classificationList.get(0).getStoryPointsValue(), 0.0001);
546+
}
547+
548+
@Test
549+
void shouldReturnClassificationWithNoneKeyWhenStoryPointSumIsZero() {
550+
JsonPrimitive jsonPrimitive = new JsonPrimitive("test");
551+
Map<String, JsonElement> customFields = new HashMap<>();
552+
customFields.put("customfield_10000", jsonPrimitive);
553+
CardCollection cardCollectionWithJsonArray = CardCollection.builder()
554+
.cardsNumber(1)
555+
.jiraCardDTOList(List.of(JiraCardDTO.builder()
556+
.baseInfo(JiraCard.builder()
557+
.key("key1")
558+
.fields(JiraCardField.builder().customFields(customFields).build())
559+
.build())
560+
.build()))
561+
.build();
562+
List<TargetField> mockTargetFields = List.of(
563+
TargetField.builder().key("customfield_10000").name("development").flag(true).build(),
564+
TargetField.builder().key("customfield_10020").name("Sprint").flag(false).build());
565+
566+
List<Classification> classifications = classificationCalculator.calculate(mockTargetFields,
567+
cardCollectionWithJsonArray);
568+
569+
assertEquals(1, classifications.size());
570+
assertEquals("development", classifications.get(0).getFieldName());
571+
572+
List<ClassificationInfo> classificationList = classifications.get(0).getClassificationInfos();
573+
574+
assertEquals(1, classificationList.size());
575+
576+
assertEquals("test", classificationList.get(0).getName());
577+
assertEquals(1, classificationList.get(0).getCardCountValue());
578+
assertEquals(1, classificationList.get(0).getCardCount());
579+
assertEquals(0, classificationList.get(0).getStoryPoints(), 0.0001);
580+
assertEquals(0, classificationList.get(0).getStoryPointsValue(), 0.0001);
544581
}
545582

546583
}

frontend/__tests__/containers/MetricsStep/PipelineConfiguration/PipelineConfiguration.test.tsx

+11-5
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@ const mockSelectStepsParams = {
2525
],
2626
};
2727

28+
const mockInitSelectedPipelineSettings = [
29+
{ id: 0, organization: 'mockOrgName', pipelineName: '1', steps: '', branches: [] },
30+
{ id: 1, organization: '', pipelineName: '', steps: '', branches: [] },
31+
];
32+
let mockSelectedPipelineSettings = mockInitSelectedPipelineSettings;
33+
2834
jest.mock('@src/hooks', () => ({
2935
...jest.requireActual('@src/hooks'),
3036
useAppDispatch: () => jest.fn(),
@@ -35,10 +41,7 @@ jest.mock('@src/context/Metrics/metricsSlice', () => ({
3541
addAPipelineSetting: jest.fn(),
3642
deleteAPipelineSetting: jest.fn(),
3743
updatePipelineSetting: jest.fn(),
38-
selectPipelineSettings: jest.fn().mockReturnValue([
39-
{ id: 0, organization: 'mockOrgName', pipelineName: '1', steps: '', branches: [] },
40-
{ id: 1, organization: '', pipelineName: '', steps: '', branches: [] },
41-
]),
44+
selectPipelineSettings: jest.fn().mockImplementation(() => mockSelectedPipelineSettings),
4245
selectOrganizationWarningMessage: jest.fn().mockReturnValue(null),
4346
selectPipelineNameWarningMessage: jest.fn().mockReturnValue(null),
4447
selectStepWarningMessage: jest.fn().mockReturnValue(null),
@@ -112,10 +115,12 @@ describe('PipelineConfiguration', () => {
112115
mockSelectShouldGetPipelineConfig = true;
113116
mockSelectPipelineNames = [];
114117
mockGetPipelineToolInfoSpy = mockGetPipelineToolInfoOkResponse;
118+
mockSelectedPipelineSettings = mockInitSelectedPipelineSettings;
115119
});
116120

117121
it('should show crew settings when select pipelineName', async () => {
118122
mockSelectPipelineNames = ['Heartbeat'];
123+
mockSelectedPipelineSettings = [{ id: 0, organization: 'mockOrgName', pipelineName: '1', steps: '', branches: [] }];
119124
const { getAllByRole, getByRole } = await setup();
120125
await act(async () => {
121126
await userEvent.click(getAllByRole('button', { name: LIST_OPEN })[0]);
@@ -132,7 +137,8 @@ describe('PipelineConfiguration', () => {
132137
await act(async () => {
133138
await userEvent.click(listBox.getByText('Heartbeat'));
134139
});
135-
waitFor(() => {
140+
screen.debug(undefined, 30000);
141+
await waitFor(() => {
136142
expect(screen.getByText('Crew setting (optional)')).toBeInTheDocument();
137143
});
138144
});

frontend/__tests__/containers/ReportStep/ReportStep.test.tsx

+104
Original file line numberDiff line numberDiff line change
@@ -1263,6 +1263,110 @@ describe('Report Step', () => {
12631263
});
12641264
});
12651265

1266+
it('should show classification chart when click switch model button and story points sum is zero', async () => {
1267+
const mockReportData = { ...MOCK_REPORT_MOCK_PIPELINE_RESPONSE };
1268+
mockReportData.classificationList = [
1269+
{
1270+
fieldName: 'Issue Type',
1271+
totalCardCount: 3,
1272+
storyPoints: 0,
1273+
classificationInfos: [
1274+
{
1275+
name: 'Feature Work - Planned',
1276+
cardCountValue: 0.5714,
1277+
cardCount: 1,
1278+
storyPoints: 0,
1279+
storyPointsValue: 0,
1280+
},
1281+
{
1282+
name: 'Feature Work - Planned2',
1283+
cardCountValue: 0.5714,
1284+
cardCount: 2,
1285+
storyPoints: 0,
1286+
storyPointsValue: 0,
1287+
},
1288+
],
1289+
},
1290+
{
1291+
fieldName: 'Parent',
1292+
totalCardCount: 3,
1293+
storyPoints: 0,
1294+
classificationInfos: [
1295+
{
1296+
name: 'Feature Work - Planned',
1297+
cardCountValue: 0.5714,
1298+
cardCount: 3,
1299+
storyPoints: 0,
1300+
storyPointsValue: 0,
1301+
},
1302+
{
1303+
name: 'Feature Work - Planned2',
1304+
cardCountValue: 0.5714,
1305+
cardCount: 2,
1306+
storyPoints: 0,
1307+
storyPointsValue: 0,
1308+
},
1309+
],
1310+
},
1311+
];
1312+
1313+
reportHook.current.reportInfos[0].reportData = mockReportData;
1314+
1315+
setup(REQUIRED_DATA_LIST, [fullValueDateRange, emptyValueDateRange]);
1316+
1317+
const switchChartButton = screen.getByText(DISPLAY_TYPE.CHART);
1318+
await userEvent.click(switchChartButton);
1319+
1320+
const issueTypeSwitchButtonGroup = screen.queryByLabelText('classification issue type switch model button group');
1321+
const issueTypeSwitchCardCountButton = screen.queryByLabelText(
1322+
'classification issue type switch card count model button',
1323+
);
1324+
const issueTypeSwitchStoryPointsGroup = screen.queryByLabelText(
1325+
'classification issue type switch story points model button',
1326+
);
1327+
const parentSwitchButtonGroup = screen.queryByLabelText('classification parent switch model button group');
1328+
const parentSwitchCardCountButton = screen.queryByLabelText(
1329+
'classification parent switch card count model button',
1330+
);
1331+
const parentSwitchStoryPointsButton = screen.queryByLabelText(
1332+
'classification parent switch story points model button',
1333+
);
1334+
1335+
const classificationIssueTypeChart = screen.queryByLabelText('classification issue type chart');
1336+
const classificationIssueTypeSwitchIcon = screen.queryByLabelText('classification issue type switch chart');
1337+
const classificationParentChart = screen.queryByLabelText('classification parent chart');
1338+
const classificationParentSwitchIcon = screen.queryByLabelText('classification parent switch chart');
1339+
1340+
expect(issueTypeSwitchButtonGroup).toBeInTheDocument();
1341+
expect(issueTypeSwitchCardCountButton).toBeInTheDocument();
1342+
expect(issueTypeSwitchStoryPointsGroup).toBeInTheDocument();
1343+
expect(parentSwitchButtonGroup).toBeInTheDocument();
1344+
expect(parentSwitchCardCountButton).toBeInTheDocument();
1345+
expect(parentSwitchStoryPointsButton).toBeInTheDocument();
1346+
1347+
expect(classificationIssueTypeChart).toBeInTheDocument();
1348+
expect(classificationIssueTypeSwitchIcon).toBeInTheDocument();
1349+
expect(classificationParentChart).toBeInTheDocument();
1350+
expect(classificationParentSwitchIcon).toBeInTheDocument();
1351+
1352+
await userEvent.click(issueTypeSwitchCardCountButton!);
1353+
await userEvent.click(issueTypeSwitchStoryPointsGroup!);
1354+
await userEvent.click(parentSwitchCardCountButton!);
1355+
await userEvent.click(parentSwitchStoryPointsButton!);
1356+
1357+
await userEvent.click(classificationIssueTypeSwitchIcon!);
1358+
await userEvent.click(classificationParentSwitchIcon!);
1359+
1360+
const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
1361+
await wait(1500);
1362+
await waitFor(async () => {
1363+
const setOptionCalledTimes = chart.setOption.mock.calls.length;
1364+
const clearCalledTimes = chart.clear.mock.calls.length;
1365+
expect(setOptionCalledTimes).toBeGreaterThan(18);
1366+
expect(clearCalledTimes).toBeGreaterThan(18);
1367+
});
1368+
});
1369+
12661370
it('should render dora chart with empty value when exception was thrown', async () => {
12671371
reportHook.current.reportInfos = [
12681372
{

frontend/src/containers/ReportStep/BoardMetricsChart/ClassificationChart/index.tsx

+11-3
Original file line numberDiff line numberDiff line change
@@ -125,10 +125,15 @@ function checkClassificationChartType(
125125
classification: string,
126126
classificationDataByModel: (ReportDataWithThreeColumns | null | undefined)[],
127127
) {
128-
const totalCardCounts = getTotalValues(classificationDataByModel, classification);
128+
const totalValues = getTotalValues(classificationDataByModel, classification);
129+
if (totalValues === 0) {
130+
return ClassificationChartType.Bar;
131+
}
129132
const data = extractedValueList(classificationDataByModel, classification);
130-
const totalCounts = data.filter((it) => it !== undefined).reduce((res, cardInfo) => res + Number(cardInfo?.value), 0);
131-
return totalCounts === totalCardCounts ? ClassificationChartType.Pie : ClassificationChartType.Bar;
133+
const everyValueSum = data
134+
.filter((it) => it !== undefined)
135+
.reduce((res, cardInfo) => res + Number(cardInfo?.value), 0);
136+
return everyValueSum === totalValues ? ClassificationChartType.Pie : ClassificationChartType.Bar;
132137
}
133138

134139
function extractClassificationValuesPieData(
@@ -172,6 +177,9 @@ function extractClassificationCardCountsBarData(
172177
const data = extractedValueList(classificationDataByModel, classification);
173178
const allSubtitle = getAllSubtitles(classificationDataByModel, classification);
174179
const indicators = allSubtitle.map((subtitle) => {
180+
if (totalValues === 0) {
181+
return 0.0;
182+
}
175183
const value = getValueForSubtitle(data, subtitle);
176184
return Number(((value * PERCENTAGE_NUMBER) / totalValues).toFixed(2));
177185
});

0 commit comments

Comments
 (0)