diff --git a/backend/src/main/java/heartbeat/controller/report/dto/response/ClassificationInfo.java b/backend/src/main/java/heartbeat/controller/report/dto/response/ClassificationInfo.java index 796a239bb7..ed88b2917e 100644 --- a/backend/src/main/java/heartbeat/controller/report/dto/response/ClassificationInfo.java +++ b/backend/src/main/java/heartbeat/controller/report/dto/response/ClassificationInfo.java @@ -13,7 +13,9 @@ public class ClassificationInfo { private String name; - private Double value; + private Double cardCountValue; + + private Double storyPointsValue; private int cardCount; diff --git a/backend/src/main/java/heartbeat/service/report/CSVFileGenerator.java b/backend/src/main/java/heartbeat/service/report/CSVFileGenerator.java index fc83fe0c5c..85ea2dc46a 100644 --- a/backend/src/main/java/heartbeat/service/report/CSVFileGenerator.java +++ b/backend/src/main/java/heartbeat/service/report/CSVFileGenerator.java @@ -443,10 +443,15 @@ private String formatStepName(CycleTimeForSelectedStepItem cycleTimeForSelectedS private List getRowsFormClassification(Classification classificationList) { List rows = new ArrayList<>(); String fieldName = String.valueOf((classificationList.getFieldName())); + final String CLASSIFICATION = "Classifications"; List pairList = classificationList.getClassificationInfos(); - pairList.forEach(nameValuePair -> rows - .add(new String[] { "Classifications", fieldName + " / " + nameValuePair.getName() + "(%)", - DecimalUtil.formatDecimalTwo(nameValuePair.getValue() * 100) })); + pairList.forEach(nameValuePair -> { + String classificationName = nameValuePair.getName(); + rows.add(new String[] { CLASSIFICATION, fieldName + " / " + classificationName + "(Value/Cards count%)", + DecimalUtil.formatDecimalTwo(nameValuePair.getCardCountValue() * 100) }); + rows.add(new String[] { CLASSIFICATION, fieldName + " / " + classificationName + "(Value/Story point%)", + DecimalUtil.formatDecimalTwo(nameValuePair.getStoryPointsValue() * 100) }); + }); return rows; } diff --git a/backend/src/main/java/heartbeat/service/report/calculator/ClassificationCalculator.java b/backend/src/main/java/heartbeat/service/report/calculator/ClassificationCalculator.java index afec01c922..db3c3a1ce7 100644 --- a/backend/src/main/java/heartbeat/service/report/calculator/ClassificationCalculator.java +++ b/backend/src/main/java/heartbeat/service/report/calculator/ClassificationCalculator.java @@ -61,9 +61,9 @@ public List calculate(List targetFields, CardCollec valueMap.remove(NONE_KEY); } - valueMap.forEach((displayName, count) -> classificationInfo - .add(new ClassificationInfo(displayName, (double) count.getCardCount() / cards.getCardsNumber(), - count.getCardCount(), count.getStoryPoints()))); + valueMap.forEach((displayName, count) -> classificationInfo.add(new ClassificationInfo(displayName, + (double) count.getCardCount() / cards.getCardsNumber(), + count.getStoryPoints() / cards.getStoryPointSum(), count.getCardCount(), count.getStoryPoints()))); classificationFields.add(new Classification(nameMap.get(fieldName), cards.getCardsNumber(), cards.getStoryPointSum(), classificationInfo)); diff --git a/backend/src/test/java/heartbeat/service/report/CSVFileGeneratorTest.java b/backend/src/test/java/heartbeat/service/report/CSVFileGeneratorTest.java index 335c1af97f..182c031487 100644 --- a/backend/src/test/java/heartbeat/service/report/CSVFileGeneratorTest.java +++ b/backend/src/test/java/heartbeat/service/report/CSVFileGeneratorTest.java @@ -567,8 +567,10 @@ void shouldConvertMetricDataToCsv() { { "Cycle time", "Average testing time(days/card)", "0.02" }, { "Cycle time", "Average time(days/storyPoint)", "0.01" }, { "Cycle time", "Average time(days/card)", "0.02" }, - { "Classifications", "Issue Type / Bug(%)", "33.33" }, - { "Classifications", "Issue Type / Story(%)", "66.67" }, + { "Classifications", "Issue Type / Bug(Value/Cards count%)", "33.33" }, + { "Classifications", "Issue Type / Bug(Value/Story point%)", "50.00" }, + { "Classifications", "Issue Type / Story(Value/Cards count%)", "66.67" }, + { "Classifications", "Issue Type / Story(Value/Story point%)", "50.00" }, { "Deployment frequency", "Heartbeat / Deploy prod / Deployment frequency(Deployments/Day)", "0.78" }, { "Deployment frequency", "Heartbeat / Deploy prod / Deployment frequency(Deployment times)", @@ -622,13 +624,20 @@ void shouldHasContentWhenGetDataFromCsvGivenDataTypeIsMetric() { try (MockedStatic cardStepsEnumMockedStatic = mockStatic(CardStepsEnum.class)) { ReportResponse reportResponse = ReportResponse.builder() .velocity(Velocity.builder().velocityForCards(2).velocityForSP(7).build()) - .classificationList( - List.of(Classification.builder() - .fieldName("Issue Type") - .classificationInfos(List.of( - ClassificationInfo.builder().name("Bug").value(0.3333333333333333).build(), - ClassificationInfo.builder().name("Story").value(0.6666666666666666).build())) - .build())) + .classificationList(List.of(Classification.builder() + .fieldName("Issue Type") + .classificationInfos(List.of( + ClassificationInfo.builder() + .name("Bug") + .cardCountValue(0.3333333333333333) + .storyPointsValue(0.6) + .build(), + ClassificationInfo.builder() + .name("Story") + .cardCountValue(0.6666666666666666) + .storyPointsValue(0.4) + .build())) + .build())) .cycleTime(CycleTime.builder() .totalTimeForCards(29.26) .averageCycleTimePerCard(9.75) @@ -789,8 +798,10 @@ void shouldHasContentWhenGetDataFromCsvGivenDataTypeIsMetric() { { "Cycle time", "Average waiting for deployment time(days/card)", "6.06" }, { "Cycle time", "Average time(days/storyPoint)", "0.01" }, { "Cycle time", "Average time(days/card)", "0.02" }, - { "Classifications", "Issue Type / Bug(%)", "33.33" }, - { "Classifications", "Issue Type / Story(%)", "66.67" }, + { "Classifications", "Issue Type / Bug(Value/Cards count%)", "33.33" }, + { "Classifications", "Issue Type / Bug(Value/Story point%)", "60.00" }, + { "Classifications", "Issue Type / Story(Value/Cards count%)", "66.67" }, + { "Classifications", "Issue Type / Story(Value/Story point%)", "40.00" }, { "Deployment frequency", "Heartbeat / Deploy prod / Deployment frequency(Deployments/Day)", "0.78" }, { "Deployment frequency", "Heartbeat / Deploy prod / Deployment frequency(Deployment times)", diff --git a/backend/src/test/java/heartbeat/service/report/MetricCsvFixture.java b/backend/src/test/java/heartbeat/service/report/MetricCsvFixture.java index 17e2e8ba03..c2d1b3fdc3 100644 --- a/backend/src/test/java/heartbeat/service/report/MetricCsvFixture.java +++ b/backend/src/test/java/heartbeat/service/report/MetricCsvFixture.java @@ -32,8 +32,17 @@ public static ReportResponse MOCK_METRIC_CSV_DATA() { .velocity(Velocity.builder().velocityForCards(2).velocityForSP(7).build()) .classificationList(List.of(Classification.builder() .fieldName("Issue Type") - .classificationInfos(List.of(ClassificationInfo.builder().name("Bug").value(0.3333333333333333).build(), - ClassificationInfo.builder().name("Story").value(0.6666666666666666).build())) + .classificationInfos(List.of( + ClassificationInfo.builder() + .name("Bug") + .cardCountValue(0.3333333333333333) + .storyPointsValue(0.5) + .build(), + ClassificationInfo.builder() + .name("Story") + .cardCountValue(0.6666666666666666) + .storyPointsValue(0.5) + .build())) .build())) .cycleTime(CycleTime.builder() .totalTimeForCards(29.26) diff --git a/backend/src/test/java/heartbeat/service/report/calculator/ClassificationCalculatorTest.java b/backend/src/test/java/heartbeat/service/report/calculator/ClassificationCalculatorTest.java index 7b46380aa2..37b6624526 100644 --- a/backend/src/test/java/heartbeat/service/report/calculator/ClassificationCalculatorTest.java +++ b/backend/src/test/java/heartbeat/service/report/calculator/ClassificationCalculatorTest.java @@ -69,19 +69,22 @@ void shouldReturnClassificationWithMapArrayFields() { assertEquals(3, classificationList.size()); assertEquals("sprint1", classificationList.get(0).getName()); - assertEquals(0.5, classificationList.get(0).getValue()); + assertEquals(0.5, classificationList.get(0).getCardCountValue()); assertEquals(2, classificationList.get(0).getCardCount()); assertEquals(6, classificationList.get(0).getStoryPoints(), 0.0001); + assertEquals(2, classificationList.get(0).getStoryPointsValue(), 0.0001); assertEquals("sprint2", classificationList.get(1).getName()); - assertEquals(0.5, classificationList.get(1).getValue()); + assertEquals(0.5, classificationList.get(1).getCardCountValue()); assertEquals(2, classificationList.get(1).getCardCount()); assertEquals(6, classificationList.get(1).getStoryPoints(), 0.0001); + assertEquals(2, classificationList.get(1).getStoryPointsValue(), 0.0001); assertEquals("None", classificationList.get(2).getName()); - assertEquals(0.5, classificationList.get(2).getValue()); + assertEquals(0.5, classificationList.get(2).getCardCountValue()); assertEquals(2, classificationList.get(2).getCardCount()); assertEquals(0, classificationList.get(2).getStoryPoints(), 0.0001); + assertEquals(0, classificationList.get(2).getStoryPointsValue(), 0.0001); } @Test @@ -118,12 +121,12 @@ void shouldReturnClassificationWithJsonArrayMapArrayFieldsWithPickName() { assertEquals(2, classificationList.size()); assertEquals("None", classificationList.get(0).getName()); - assertEquals(0.5, classificationList.get(0).getValue()); + assertEquals(0.5, classificationList.get(0).getCardCountValue()); assertEquals(1, classificationList.get(0).getCardCount()); assertEquals(3, classificationList.get(0).getStoryPoints(), 0.0001); assertEquals("Tool Sprint 6", classificationList.get(1).getName()); - assertEquals(0.5, classificationList.get(1).getValue()); + assertEquals(0.5, classificationList.get(1).getCardCountValue()); assertEquals(1, classificationList.get(1).getCardCount()); assertEquals(0, classificationList.get(1).getStoryPoints(), 0.0001); } @@ -158,10 +161,10 @@ void shouldReturnClassificationWithJsonArrayMapArrayFieldsWithPickDisplayName() assertEquals(1, classifications.size()); assertEquals("Partner", classifications.get(0).getFieldName()); assertEquals("Shawn", classificationList.get(0).getName()); - assertEquals(0.5, classificationList.get(0).getValue()); + assertEquals(0.5, classificationList.get(0).getCardCountValue()); assertEquals(1, classificationList.get(0).getCardCount()); assertEquals("None", classificationList.get(1).getName()); - assertEquals(0.5, classificationList.get(1).getValue()); + assertEquals(0.5, classificationList.get(1).getCardCountValue()); assertEquals(1, classificationList.get(1).getCardCount()); } @@ -195,10 +198,10 @@ void shouldReturnClassificationWithJsonArrayMapArrayFieldsWithPickValueName() { assertEquals(1, classifications.size()); assertEquals("Rank", classifications.get(0).getFieldName()); assertEquals("bug", classificationList.get(0).getName()); - assertEquals(0.5, classificationList.get(0).getValue()); + assertEquals(0.5, classificationList.get(0).getCardCountValue()); assertEquals(1, classificationList.get(0).getCardCount()); assertEquals("None", classificationList.get(1).getName()); - assertEquals(0.5, classificationList.get(1).getValue()); + assertEquals(0.5, classificationList.get(1).getCardCountValue()); assertEquals(1, classificationList.get(1).getCardCount()); } @@ -231,7 +234,7 @@ void shouldReturnClassificationWithJsonArrayMapArrayFieldsWithNoneKey() { assertEquals(1, classifications.size()); assertEquals("Issue Color", classifications.get(0).getFieldName()); assertEquals("None", classificationList.get(0).getName()); - assertEquals(1, classificationList.get(0).getValue()); + assertEquals(1, classificationList.get(0).getCardCountValue()); assertEquals(2, classificationList.get(0).getCardCount()); } @@ -261,7 +264,7 @@ void shouldReturnClassificationWithJsonNull() { assertEquals(1, classifications.size()); assertEquals("Start date", classifications.get(0).getFieldName()); assertEquals("None", classificationList.get(0).getName()); - assertEquals(1, classificationList.get(0).getValue()); + assertEquals(1, classificationList.get(0).getCardCountValue()); assertEquals(2, classificationList.get(0).getCardCount()); } @@ -290,7 +293,7 @@ void shouldReturnClassificationWithJsonPrimitive() { assertEquals(1, classifications.size()); assertEquals("development", classifications.get(0).getFieldName()); assertEquals("test", classificationList.get(0).getName()); - assertEquals(0.5, classificationList.get(0).getValue()); + assertEquals(0.5, classificationList.get(0).getCardCountValue()); assertEquals(1, classificationList.get(0).getCardCount()); } @@ -311,14 +314,14 @@ void shouldReturnClassificationWithPickRightDisplayName() { assertEquals(2, classifications.size()); assertEquals("Assignee", classifications.get(1).getFieldName()); assertEquals("Shawn", classificationListForAssignee.get(0).getName()); - assertEquals(0.25, classificationListForAssignee.get(0).getValue()); + assertEquals(0.25, classificationListForAssignee.get(0).getCardCountValue()); assertEquals(1, classificationListForAssignee.get(0).getCardCount()); assertEquals("Tom", classificationListForAssignee.get(1).getName()); - assertEquals(0.25, classificationListForAssignee.get(1).getValue()); + assertEquals(0.25, classificationListForAssignee.get(1).getCardCountValue()); assertEquals(1, classificationListForAssignee.get(1).getCardCount()); assertEquals("Reporter", classifications.get(0).getFieldName()); assertEquals("Jack", classificationListForReporter.get(0).getName()); - assertEquals(0.5, classificationListForReporter.get(0).getValue()); + assertEquals(0.5, classificationListForReporter.get(0).getCardCountValue()); assertEquals(2, classificationListForReporter.get(0).getCardCount()); } @@ -340,15 +343,15 @@ void shouldReturnClassificationWithPickRightName() { assertEquals(3, classifications.size()); assertEquals("IssueType", classifications.get(0).getFieldName()); assertEquals("Task", classificationListForIssueType.get(0).getName()); - assertEquals(0.5, classificationListForIssueType.get(0).getValue()); + assertEquals(0.5, classificationListForIssueType.get(0).getCardCountValue()); assertEquals(2, classificationListForIssueType.get(0).getCardCount()); assertEquals("Card Parent", classifications.get(1).getFieldName()); assertEquals("test", classificationListForCardParent.get(0).getName()); - assertEquals(0.25, classificationListForCardParent.get(0).getValue()); + assertEquals(0.25, classificationListForCardParent.get(0).getCardCountValue()); assertEquals(1, classificationListForCardParent.get(0).getCardCount()); assertEquals("Project", classifications.get(2).getFieldName()); assertEquals("heartBeat", classificationListForProject.get(0).getName()); - assertEquals(0.5, classificationListForProject.get(0).getValue()); + assertEquals(0.5, classificationListForProject.get(0).getCardCountValue()); assertEquals(2, classificationListForProject.get(0).getCardCount()); } @@ -381,10 +384,10 @@ void shouldReturnClassificationWithString() { assertEquals("Labels", classifications.get(0).getFieldName()); assertEquals(4, classifications.get(0).getTotalCardCount()); assertEquals("backend", classifications.get(0).getClassificationInfos().get(0).getName()); - assertEquals(0.5, classificationListForLabels.get(0).getValue()); + assertEquals(0.5, classificationListForLabels.get(0).getCardCountValue()); assertEquals(2, classificationListForLabels.get(0).getCardCount()); assertEquals("frontend", classificationListForLabels.get(2).getName()); - assertEquals(0.5, classificationListForLabels.get(2).getValue()); + assertEquals(0.5, classificationListForLabels.get(2).getCardCountValue()); assertEquals(2, classificationListForLabels.get(2).getCardCount()); } @@ -440,10 +443,10 @@ void shouldReturnClassificationWithMapArrayFieldSameContent() { assertEquals("Fix Versions", classifications.get(0).getFieldName()); assertEquals(4, classifications.get(0).getTotalCardCount()); assertEquals("sprint1", classificationListForFixVersions.get(0).getName()); - assertEquals(0.5, classificationListForFixVersions.get(0).getValue()); + assertEquals(0.5, classificationListForFixVersions.get(0).getCardCountValue()); assertEquals(2, classificationListForFixVersions.get(0).getCardCount()); assertEquals("sprint2", classificationListForFixVersions.get(1).getName()); - assertEquals(0.5, classificationListForFixVersions.get(1).getValue()); + assertEquals(0.5, classificationListForFixVersions.get(1).getCardCountValue()); assertEquals(2, classificationListForFixVersions.get(1).getCardCount()); } @@ -461,7 +464,7 @@ void shouldReturnClassificationWithMapArrayFieldEmptyContent() { assertEquals("Fix Versions", classifications.get(0).getFieldName()); assertEquals(2, classifications.get(0).getTotalCardCount()); assertEquals("None", classificationListForFixVersions.get(0).getName()); - assertEquals(1, classificationListForFixVersions.get(0).getValue()); + assertEquals(1, classificationListForFixVersions.get(0).getCardCountValue()); assertEquals(2, classificationListForFixVersions.get(0).getCardCount()); } @@ -495,7 +498,7 @@ void shouldReturnClassificationWithoutNoneKeyWhenCardCountsAndStoryPointAreZero( assertEquals(1, classificationList.size()); assertEquals("test", classificationList.get(0).getName()); - assertEquals(1, classificationList.get(0).getValue()); + assertEquals(1, classificationList.get(0).getCardCountValue()); assertEquals(1, classificationList.get(0).getCardCount()); assertEquals(3, classificationList.get(0).getStoryPoints(), 0.0001); } @@ -530,12 +533,12 @@ void shouldReturnClassificationWithNoneKeyWhenStoryPointIsNotZero() { assertEquals(2, classificationList.size()); assertEquals("test", classificationList.get(0).getName()); - assertEquals(1, classificationList.get(0).getValue()); + assertEquals(1, classificationList.get(0).getCardCountValue()); assertEquals(1, classificationList.get(0).getCardCount()); assertEquals(3, classificationList.get(0).getStoryPoints(), 0.0001); assertEquals("None", classificationList.get(1).getName()); - assertEquals(0, classificationList.get(1).getValue()); + assertEquals(0, classificationList.get(1).getCardCountValue()); assertEquals(0, classificationList.get(1).getCardCount()); assertEquals(3, classificationList.get(1).getStoryPoints(), 0.0001); } diff --git a/frontend/__tests__/components/Common/ReportForThreeColumns.test.tsx b/frontend/__tests__/components/Common/ReportForThreeColumns.test.tsx index 999feea9ce..cbdb745753 100644 --- a/frontend/__tests__/components/Common/ReportForThreeColumns.test.tsx +++ b/frontend/__tests__/components/Common/ReportForThreeColumns.test.tsx @@ -1,10 +1,18 @@ -import ReportForThreeColumns from '@src/components/Common/ReportForThreeColumns'; +import ReportDetailTableContainsSubtitle from '@src/components/Common/ReportDetailTableContainsSubtitle'; import { LEAD_TIME_FOR_CHANGES, LOADING, VELOCITY } from '../../fixtures'; import { render, screen } from '@testing-library/react'; -describe('Report for three columns', () => { +describe('Report detail table contains subtitle', () => { it('should show loading when data is empty', () => { - render(); + render( + , + ); expect(screen.getByTestId(LOADING)).toBeInTheDocument(); expect(screen.getByText(VELOCITY)).toBeInTheDocument(); @@ -12,35 +20,57 @@ describe('Report for three columns', () => { it('should show table when data is not empty', () => { const mockData = [ - { id: 0, name: 'name1', valueList: [{ name: 'test1', value: '1' }] }, - { id: 1, name: 'name2', valueList: [{ name: 'test2', value: '2' }] }, - { id: 2, name: 'name3', valueList: [{ name: 'test3', value: '3' }] }, + { id: 0, name: 'name1', valueList: [{ name: 'test1', values: ['1'] }] }, + { id: 1, name: 'name2', valueList: [{ name: 'test2', values: ['2'] }] }, + { id: 2, name: 'name3', valueList: [{ name: 'test3', values: ['3'] }] }, ]; render( - , + , ); - expect(screen.getByTestId(LEAD_TIME_FOR_CHANGES)).toBeInTheDocument(); + expect(screen.getByLabelText(LEAD_TIME_FOR_CHANGES)).toBeInTheDocument(); }); it('should show table when data name contains emoji', () => { const mockData = [ - { id: 0, name: 'name1/:rocket: Deploy prod', valueList: [{ name: 'test1', value: '1' }] }, - { id: 1, name: 'name2/:rocket: Deploy prod', valueList: [{ name: 'test2', value: '2' }] }, - { id: 2, name: 'name3/:rocket: Deploy prod', valueList: [{ name: 'test3', value: '3' }] }, + { id: 0, name: 'name1/:rocket: Deploy prod', valueList: [{ name: 'test1', values: ['1'] }] }, + { id: 1, name: 'name2/:rocket: Deploy prod', valueList: [{ name: 'test2', values: ['2'] }] }, + { id: 2, name: 'name3/:rocket: Deploy prod', valueList: [{ name: 'test3', values: ['3'] }] }, ]; - render(); + render( + , + ); - expect(screen.getByTestId(VELOCITY)).toBeInTheDocument(); + expect(screen.getByLabelText(VELOCITY)).toBeInTheDocument(); }); it('should show default value when valueList is empty', () => { const mockData = [{ id: 0, name: 'name1', valueList: [] }]; - render(); + render( + , + ); - expect(screen.getAllByText('--')).toHaveLength(2); + expect(screen.getAllByText('--')).toHaveLength(3); }); }); diff --git a/frontend/__tests__/containers/ReportStep/ChartAndTitleWrapper.test.tsx b/frontend/__tests__/containers/ReportStep/ChartAndTitleWrapper.test.tsx index 1a1f10ac0b..2496bea65d 100644 --- a/frontend/__tests__/containers/ReportStep/ChartAndTitleWrapper.test.tsx +++ b/frontend/__tests__/containers/ReportStep/ChartAndTitleWrapper.test.tsx @@ -1,6 +1,7 @@ import ChartAndTitleWrapper from '@src/containers/ReportStep/ChartAndTitleWrapper'; import { ChartType, TrendIcon, TrendType } from '@src/constants/resources'; import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import { theme } from '@src/theme'; describe('ChartAndTitleWrapper', () => { @@ -43,4 +44,32 @@ describe('ChartAndTitleWrapper', () => { expect(screen.getByLabelText('trend number')).toHaveTextContent('83.72%'); }); + + it('should show the switch button group when chart type is classification', async () => { + const testedTrendInfo = { + type: ChartType.Classification, + }; + const clickSwitchClassificationModel = jest.fn(); + render( + , + ); + + expect(screen.getByLabelText('classification test switch model button group')).toBeInTheDocument(); + + const cardCountSwitchButton = screen.getByLabelText('classification test switch card count model button'); + const storyPointsSwitchButton = screen.getByLabelText('classification test switch story points model button'); + + expect(cardCountSwitchButton).toBeInTheDocument(); + expect(storyPointsSwitchButton).toBeInTheDocument(); + + await userEvent.click(cardCountSwitchButton); + await userEvent.click(storyPointsSwitchButton); + + expect(clickSwitchClassificationModel).toHaveBeenCalledTimes(2); + }); }); diff --git a/frontend/__tests__/containers/ReportStep/ReportDetail/board.test.tsx b/frontend/__tests__/containers/ReportStep/ReportDetail/board.test.tsx index 78d64b2e7a..f5911cdba3 100644 --- a/frontend/__tests__/containers/ReportStep/ReportDetail/board.test.tsx +++ b/frontend/__tests__/containers/ReportStep/ReportDetail/board.test.tsx @@ -112,10 +112,10 @@ describe('board', () => { it('should show classifications when classifications data is existing', () => { (reportMapper as jest.Mock).mockReturnValue({ classification: [ - { id: 0, name: 'name1', valuesList: [{ name: 'test1', value: 1 }] }, - { id: 1, name: 'name2', valuesList: [{ name: 'test2', value: 2 }] }, - { id: 2, name: 'name3', valuesList: [{ name: 'test3', value: 3 }] }, - { id: 3, name: 'name4', valuesList: [{ name: 'test4', value: 4 }] }, + { id: 0, name: 'name1', valuesList: [{ name: 'test1', values: [1, 1] }] }, + { id: 1, name: 'name2', valuesList: [{ name: 'test2', values: [2, 2] }] }, + { id: 2, name: 'name3', valuesList: [{ name: 'test3', values: [3, 3] }] }, + { id: 3, name: 'name4', valuesList: [{ name: 'test4', values: [4, 4] }] }, ], }); const store = setupStore(); @@ -133,10 +133,10 @@ describe('board', () => { , ); - const classificationTable = screen.getByTestId('Classification'); + const classificationTable = screen.getByLabelText('Classification'); expect(screen.getByText('Classification')).toBeInTheDocument(); expect(classificationTable).toBeInTheDocument(); - expect(within(classificationTable).queryAllByTestId('tr').length).toBe(8); + expect(within(classificationTable).queryAllByLabelText('tr').length).toBe(8); }); it('should not show classifications when classifications data is not existing', () => { @@ -186,9 +186,9 @@ describe('board', () => { { id: 1, name: 'name2', valueList: [{ value: 2 }] }, ], classification: [ - { id: 0, name: 'name1', valuesList: [{ name: 'test1', value: 1 }] }, - { id: 1, name: 'name2', valuesList: [{ name: 'test2', value: 2 }] }, - { id: 2, name: 'name3', valuesList: [{ name: 'test3', value: 3 }] }, + { id: 0, name: 'name1', valuesList: [{ name: 'test1', values: [1, 1] }] }, + { id: 1, name: 'name2', valuesList: [{ name: 'test2', values: [2, 2] }] }, + { id: 2, name: 'name3', valuesList: [{ name: 'test3', values: [3, 3] }] }, ], }); @@ -206,7 +206,7 @@ describe('board', () => { const velocityTable = screen.getByTestId('Velocity'); const cycleTimeTable = screen.getByTestId('Cycle Time'); - const classificationTable = screen.getByTestId('Classification'); + const classificationTable = screen.getByLabelText('Classification'); expect(screen.getByText('Velocity')).toBeInTheDocument(); expect(velocityTable).toBeInTheDocument(); expect(screen.getByText('Cycle Time')).toBeInTheDocument(); @@ -216,6 +216,6 @@ describe('board', () => { expect(within(velocityTable).queryAllByTestId('tr').length).toBe(1); expect(within(cycleTimeTable).queryAllByTestId('tr').length).toBe(2); - expect(within(classificationTable).queryAllByTestId('tr').length).toBe(6); + expect(within(classificationTable).queryAllByLabelText('tr').length).toBe(6); }); }); diff --git a/frontend/__tests__/containers/ReportStep/ReportDetail/dora.test.tsx b/frontend/__tests__/containers/ReportStep/ReportDetail/dora.test.tsx index dddca10da4..b7fc0d44c5 100644 --- a/frontend/__tests__/containers/ReportStep/ReportDetail/dora.test.tsx +++ b/frontend/__tests__/containers/ReportStep/ReportDetail/dora.test.tsx @@ -43,13 +43,13 @@ describe('DoraDetail', () => { describe('Lead Time For Changes', () => { it('should show leadTimeForChangesList when leadTimeForChangesList data is existing', () => { (reportMapper as jest.Mock).mockReturnValue({ - leadTimeForChangesList: [{ id: 0, name: 'name1', valuesList: [{ name: 'test1', value: 1 }] }], + leadTimeForChangesList: [{ id: 0, name: 'name1', valuesList: [{ name: 'test1', values: [1] }] }], }); render(); - const leadTimeForChangesTable = screen.getByTestId('Lead Time For Changes'); + const leadTimeForChangesTable = screen.getByLabelText('Lead Time For Changes'); expect(screen.getByText('Lead Time For Changes')).toBeInTheDocument(); expect(leadTimeForChangesTable).toBeInTheDocument(); - expect(within(leadTimeForChangesTable).queryAllByTestId('tr').length).toBe(2); + expect(within(leadTimeForChangesTable).queryAllByLabelText('tr').length).toBe(2); }); it('should not show leadTimeForChangesList when leadTimeForChangesList data is not existing', () => { diff --git a/frontend/__tests__/containers/ReportStep/ReportStep.test.tsx b/frontend/__tests__/containers/ReportStep/ReportStep.test.tsx index bd5c80b845..c683457e67 100644 --- a/frontend/__tests__/containers/ReportStep/ReportStep.test.tsx +++ b/frontend/__tests__/containers/ReportStep/ReportStep.test.tsx @@ -857,7 +857,7 @@ describe('Report Step', () => { }); }); - describe('Dora chart test', () => { + describe('chart test', () => { const chart = { setOption: jest.fn(), resize: jest.fn(), @@ -917,27 +917,35 @@ describe('Report Step', () => { { fieldName: 'Issue Type', totalCardCount: 3, + storyPoints: 1, classificationInfos: [ { name: 'Feature Work - Planned', - value: 0.5714, + cardCountValue: 0.5714, cardCount: 3, + storyPoints: 1, + storyPointsValue: 0.1, }, ], }, { fieldName: 'Issue Type', totalCardCount: 3, + storyPoints: 1, classificationInfos: [ { name: 'Feature Work - Planned', - value: 0.5714, + cardCountValue: 0.5714, cardCount: 1, + storyPoints: 1, + storyPointsValue: 0.1, }, { name: 'Feature Work - Planned2', - value: 0.5714, + cardCountValue: 0.5714, cardCount: 2, + storyPoints: 2, + storyPointsValue: 0.2, }, ], }, @@ -988,38 +996,49 @@ describe('Report Step', () => { { fieldName: 'name1-1', totalCardCount: 3, + storyPoints: 1, classificationInfos: [ { name: 'name1-1 - Planned', - value: 0.5714, + cardCountValue: 0.5714, cardCount: 3, + storyPoints: 1, + storyPointsValue: 0.1, }, ], }, { fieldName: 'name1-2', totalCardCount: 3, + storyPoints: 1, classificationInfos: [ { name: 'name1-2 - Planned', - value: 0.5714, + cardCountValue: 0.5714, cardCount: 3, + storyPoints: 1, + storyPointsValue: 0.1, }, ], }, { fieldName: 'name1-3', totalCardCount: 3, + storyPoints: 1, classificationInfos: [ { name: 'name1-3 - Planned', - value: 0.5714, + cardCountValue: 0.5714, cardCount: 3, + storyPoints: 1, + storyPointsValue: 0.1, }, { name: 'name1-3 - Planned2', - value: 0.5714, + cardCountValue: 0.5714, cardCount: 3, + storyPoints: 1, + storyPointsValue: 0.1, }, ], }, @@ -1056,32 +1075,42 @@ describe('Report Step', () => { { fieldName: 'Issue Type', totalCardCount: 3, + storyPoints: 1, classificationInfos: [ { name: 'Feature Work - Planned', - value: 0.5714, + cardCountValue: 0.5714, cardCount: 1, + storyPoints: 1, + storyPointsValue: 0.1, }, { name: 'Feature Work - Planned2', - value: 0.5714, + cardCountValue: 0.5714, cardCount: 2, + storyPoints: 1, + storyPointsValue: 0.1, }, ], }, { fieldName: 'Parent', totalCardCount: 3, + storyPoints: 1, classificationInfos: [ { name: 'Feature Work - Planned', - value: 0.5714, + cardCountValue: 0.5714, cardCount: 3, + storyPoints: 1, + storyPointsValue: 0.1, }, { name: 'Feature Work - Planned2', - value: 0.5714, + cardCountValue: 0.5714, cardCount: 2, + storyPoints: 1, + storyPointsValue: 0.1, }, ], }, @@ -1117,6 +1146,110 @@ describe('Report Step', () => { }, ); + test.each(Array.from({ length: 10 }))('should show story points chart when click switch model button', async () => { + const mockReportData = { ...MOCK_REPORT_MOCK_PIPELINE_RESPONSE }; + mockReportData.classificationList = [ + { + fieldName: 'Issue Type', + totalCardCount: 3, + storyPoints: 1, + classificationInfos: [ + { + name: 'Feature Work - Planned', + cardCountValue: 0.5714, + cardCount: 1, + storyPoints: 1, + storyPointsValue: 0.1, + }, + { + name: 'Feature Work - Planned2', + cardCountValue: 0.5714, + cardCount: 2, + storyPoints: 1, + storyPointsValue: 0.1, + }, + ], + }, + { + fieldName: 'Parent', + totalCardCount: 3, + storyPoints: 1, + classificationInfos: [ + { + name: 'Feature Work - Planned', + cardCountValue: 0.5714, + cardCount: 3, + storyPoints: 1, + storyPointsValue: 0.1, + }, + { + name: 'Feature Work - Planned2', + cardCountValue: 0.5714, + cardCount: 2, + storyPoints: 1, + storyPointsValue: 0.1, + }, + ], + }, + ]; + + reportHook.current.reportInfos[0].reportData = mockReportData; + + setup(REQUIRED_DATA_LIST, [fullValueDateRange, emptyValueDateRange]); + + const switchChartButton = screen.getByText(DISPLAY_TYPE.CHART); + await userEvent.click(switchChartButton); + + const issueTypeSwitchButtonGroup = screen.queryByLabelText('classification issue type switch model button group'); + const issueTypeSwitchCardCountButton = screen.queryByLabelText( + 'classification issue type switch card count model button', + ); + const issueTypeSwitchStoryPointsGroup = screen.queryByLabelText( + 'classification issue type switch story points model button', + ); + const parentSwitchButtonGroup = screen.queryByLabelText('classification parent switch model button group'); + const parentSwitchCardCountButton = screen.queryByLabelText( + 'classification parent switch card count model button', + ); + const parentSwitchStoryPointsButton = screen.queryByLabelText( + 'classification parent switch story points model button', + ); + + const classificationIssueTypeChart = screen.queryByLabelText('classification issue type chart'); + const classificationIssueTypeSwitchIcon = screen.queryByLabelText('classification issue type switch chart'); + const classificationParentChart = screen.queryByLabelText('classification parent chart'); + const classificationParentSwitchIcon = screen.queryByLabelText('classification parent switch chart'); + + expect(issueTypeSwitchButtonGroup).toBeInTheDocument(); + expect(issueTypeSwitchCardCountButton).toBeInTheDocument(); + expect(issueTypeSwitchStoryPointsGroup).toBeInTheDocument(); + expect(parentSwitchButtonGroup).toBeInTheDocument(); + expect(parentSwitchCardCountButton).toBeInTheDocument(); + expect(parentSwitchStoryPointsButton).toBeInTheDocument(); + + expect(classificationIssueTypeChart).toBeInTheDocument(); + expect(classificationIssueTypeSwitchIcon).toBeInTheDocument(); + expect(classificationParentChart).toBeInTheDocument(); + expect(classificationParentSwitchIcon).toBeInTheDocument(); + + await userEvent.click(issueTypeSwitchCardCountButton!); + await userEvent.click(issueTypeSwitchStoryPointsGroup!); + await userEvent.click(parentSwitchCardCountButton!); + await userEvent.click(parentSwitchStoryPointsButton!); + + await userEvent.click(classificationIssueTypeSwitchIcon!); + await userEvent.click(classificationParentSwitchIcon!); + + const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); + await wait(1500); + await waitFor(async () => { + const setOptionCalledTimes = chart.setOption.mock.calls.length; + const clearCalledTimes = chart.clear.mock.calls.length; + expect(setOptionCalledTimes).toBeGreaterThan(18); + expect(clearCalledTimes).toBeGreaterThan(18); + }); + }); + it('should render dora chart with empty value when exception was thrown', async () => { reportHook.current.reportInfos = [ { diff --git a/frontend/__tests__/fixtures.ts b/frontend/__tests__/fixtures.ts index dfabcfaed6..9640b5d62c 100644 --- a/frontend/__tests__/fixtures.ts +++ b/frontend/__tests__/fixtures.ts @@ -551,11 +551,14 @@ export const MOCK_REPORT_RESPONSE_WITH_AVERAGE_EXCEPTION: ReportResponseDTO = { { fieldName: 'FS Work Type', totalCardCount: 3, + storyPoints: 1, classificationInfos: [ { name: 'Feature Work - Planned', - value: 0.5714, + cardCountValue: 0.5714, cardCount: 3, + storyPoints: 1, + storyPointsValue: 0.1, }, ], }, @@ -691,11 +694,14 @@ export const MOCK_REPORT_RESPONSE: ReportResponseDTO = { { fieldName: 'FS Work Type', totalCardCount: 3, + storyPoints: 1, classificationInfos: [ { name: 'Feature Work - Planned', - value: 0.5714, + cardCountValue: 0.5714, cardCount: 3, + storyPoints: 1, + storyPointsValue: 0.1, }, ], }, @@ -821,10 +827,13 @@ export const MOCK_REPORT_MOCK_PIPELINE_RESPONSE: ReportResponseDTO = { { fieldName: 'Issue Type', totalCardCount: 3, + storyPoints: 1, classificationInfos: [ { name: 'Feature Work - Planned', - value: 0.5714, + cardCountValue: 0.5714, + storyPoints: 1, + storyPointsValue: 0.1, cardCount: 3, }, ], diff --git a/frontend/__tests__/hooks/reportMapper/classfication.test.tsx b/frontend/__tests__/hooks/reportMapper/classfication.test.tsx index 9850f1132a..9de92483c9 100644 --- a/frontend/__tests__/hooks/reportMapper/classfication.test.tsx +++ b/frontend/__tests__/hooks/reportMapper/classfication.test.tsx @@ -9,21 +9,24 @@ describe('classification data mapper', () => { classificationInfos: [ { name: 'Feature Work - Planned', - value: 0.5714, + cardCountValue: 0.5714, cardCount: 1, storyPoints: 1, + storyPointsValue: 0.1, }, { name: 'Operational Work - Planned', - value: 0.3571, + cardCountValue: 0.3571, cardCount: 1, storyPoints: 1, + storyPointsValue: 0.1, }, { name: 'Feature Work - Unplanned', - value: 0.0714, + cardCountValue: 0.0714, cardCount: 1, storyPoints: 1, + storyPointsValue: 0.1, }, ], }, @@ -34,9 +37,9 @@ describe('classification data mapper', () => { id: 0, name: 'FS Work Type', valueList: [ - { name: 'Feature Work - Planned', value: '57.14%' }, - { name: 'Operational Work - Planned', value: '35.71%' }, - { name: 'Feature Work - Unplanned', value: '7.14%' }, + { name: 'Feature Work - Planned', values: ['57.14%', '10.00%'] }, + { name: 'Operational Work - Planned', values: ['35.71%', '10.00%'] }, + { name: 'Feature Work - Unplanned', values: ['7.14%', '10.00%'] }, ], }, ]; diff --git a/frontend/__tests__/hooks/reportMapper/leadTimeForChanges.test.tsx b/frontend/__tests__/hooks/reportMapper/leadTimeForChanges.test.tsx index d497efa24e..4d62f356d7 100644 --- a/frontend/__tests__/hooks/reportMapper/leadTimeForChanges.test.tsx +++ b/frontend/__tests__/hooks/reportMapper/leadTimeForChanges.test.tsx @@ -27,15 +27,15 @@ describe('lead time for changes data mapper', () => { valueList: [ { name: 'PR Lead Time', - value: '374.69', + values: ['374.69'], }, { name: 'Pipeline Lead Time', - value: '69.47', + values: ['69.47'], }, { name: 'Total Lead Time', - value: '305.23', + values: ['305.23'], }, ], }, @@ -45,15 +45,15 @@ describe('lead time for changes data mapper', () => { valueList: [ { name: 'PR Lead Time', - value: '374.69', + values: ['374.69'], }, { name: 'Pipeline Lead Time', - value: '69.47', + values: ['69.47'], }, { name: 'Total Lead Time', - value: '305.23', + values: ['305.23'], }, ], }, @@ -87,18 +87,18 @@ describe('lead time for changes data mapper', () => { id: 0, name: 'fs-platform-payment-selector/RECORD RELEASE TO PROD', valueList: [ - { name: PR_LEAD_TIME, value: '0.00' }, - { name: PIPELINE_LEAD_TIME, value: '0.00' }, - { name: TOTAL_DELAY_TIME, value: '0.00' }, + { name: PR_LEAD_TIME, values: ['0.00'] }, + { name: PIPELINE_LEAD_TIME, values: ['0.00'] }, + { name: TOTAL_DELAY_TIME, values: ['0.00'] }, ], }, { id: 1, name: 'Average', valueList: [ - { name: PR_LEAD_TIME, value: '0.00' }, - { name: PIPELINE_LEAD_TIME, value: '0.00' }, - { name: TOTAL_DELAY_TIME, value: '0.00' }, + { name: PR_LEAD_TIME, values: ['0.00'] }, + { name: PIPELINE_LEAD_TIME, values: ['0.00'] }, + { name: TOTAL_DELAY_TIME, values: ['0.00'] }, ], }, ]; diff --git a/frontend/__tests__/hooks/reportMapper/report.test.tsx b/frontend/__tests__/hooks/reportMapper/report.test.tsx index 0e8b5da059..383fe93439 100644 --- a/frontend/__tests__/hooks/reportMapper/report.test.tsx +++ b/frontend/__tests__/hooks/reportMapper/report.test.tsx @@ -83,7 +83,7 @@ export const EXPECTED_REPORT_VALUES = { { id: 0, name: 'FS Work Type', - valueList: [{ name: 'Feature Work - Planned', value: '57.14%' }], + valueList: [{ name: 'Feature Work - Planned', values: ['57.14%', '10.00%'] }], }, ], deploymentFrequencyList: [ @@ -155,18 +155,18 @@ export const EXPECTED_REPORT_VALUES = { id: 0, name: 'fs-platform-payment-selector/RECORD RELEASE TO PROD', valueList: [ - { name: PR_LEAD_TIME, value: '45.04' }, - { name: PIPELINE_LEAD_TIME, value: '43.12' }, - { name: TOTAL_DELAY_TIME, value: '88.17' }, + { name: PR_LEAD_TIME, values: ['45.04'] }, + { name: PIPELINE_LEAD_TIME, values: ['43.12'] }, + { name: TOTAL_DELAY_TIME, values: ['88.17'] }, ], }, { id: 1, name: 'Average', valueList: [ - { name: PR_LEAD_TIME, value: '60.79' }, - { name: PIPELINE_LEAD_TIME, value: '39.03' }, - { name: TOTAL_DELAY_TIME, value: '99.82' }, + { name: PR_LEAD_TIME, values: ['60.79'] }, + { name: PIPELINE_LEAD_TIME, values: ['39.03'] }, + { name: TOTAL_DELAY_TIME, values: ['99.82'] }, ], }, ], @@ -306,6 +306,19 @@ export const EXPECTED_REPORT_VALUES = { ], }, ], + classificationStoryPoints: [ + { + id: 0, + name: 'FS Work Type', + totalCount: 1, + valueList: [ + { + name: 'Feature Work - Planned', + value: '1', + }, + ], + }, + ], }; describe('report response data mapper', () => { it('maps response velocity values to ui display value', () => { diff --git a/frontend/e2e/fixtures/create-new/metric-20240603-20240604.csv b/frontend/e2e/fixtures/create-new/metric-20240603-20240604.csv index 288bdafabb..4d188e34bb 100644 --- a/frontend/e2e/fixtures/create-new/metric-20240603-20240604.csv +++ b/frontend/e2e/fixtures/create-new/metric-20240603-20240604.csv @@ -18,34 +18,62 @@ "Cycle time","Average waiting for testing time(days/card)","0.33" "Cycle time","Average testing time(days/storyPoint)","0.18" "Cycle time","Average testing time(days/card)","0.33" -"Classifications","Issue Type / Task(%)","66.67" -"Classifications","Issue Type / Story(%)","33.33" -"Classifications","Parent / ADM-868(%)","100.00" -"Classifications","Story testing-2 / None(%)","100.00" -"Classifications","Story testing-1 / 1.0(%)","66.67" -"Classifications","Story testing-1 / None(%)","33.33" -"Classifications","Design / None(%)","100.00" -"Classifications","Vulnerability / None(%)","100.00" -"Classifications","Sprint / Sprint37(%)","100.00" -"Classifications","Sprint / Sprint 35(%)","33.33" -"Classifications","Sprint / Sprint 36(%)","66.67" -"Classifications","Sprint / Sprint34(%)","33.33" -"Classifications","Project / Auto Dora Metrics(%)","100.00" -"Classifications","Flagged / None(%)","100.00" -"Classifications","Fix versions / None(%)","100.00" -"Classifications","Priority / Medium(%)","100.00" -"Classifications","Partner / YinYuan Zhou(%)","33.33" -"Classifications","Partner / None(%)","66.67" -"Classifications","Labels / 1.1.7(%)","100.00" -"Classifications","Time tracking / None(%)","100.00" -"Classifications","Story point estimate / 2.0(%)","33.33" -"Classifications","Story point estimate / 3.0(%)","33.33" -"Classifications","Story point estimate / 0.5(%)","33.33" -"Classifications","QA / None(%)","100.00" -"Classifications","Feature/Operation / None(%)","100.00" -"Classifications","Assignee / Chao Wang(%)","33.33" -"Classifications","Assignee / YinYuan Zhou(%)","33.33" -"Classifications","Assignee / Qiuhong Lei(%)","33.33" +"Classifications","Issue Type / Task(Value/Cards count%)","66.67" +"Classifications","Issue Type / Task(Value/Story point%)","63.64" +"Classifications","Issue Type / Story(Value/Cards count%)","33.33" +"Classifications","Issue Type / Story(Value/Story point%)","36.36" +"Classifications","Parent / ADM-868(Value/Cards count%)","100.00" +"Classifications","Parent / ADM-868(Value/Story point%)","100.00" +"Classifications","Story testing-2 / None(Value/Cards count%)","100.00" +"Classifications","Story testing-2 / None(Value/Story point%)","100.00" +"Classifications","Story testing-1 / 1.0(Value/Cards count%)","66.67" +"Classifications","Story testing-1 / 1.0(Value/Story point%)","63.64" +"Classifications","Story testing-1 / None(Value/Cards count%)","33.33" +"Classifications","Story testing-1 / None(Value/Story point%)","36.36" +"Classifications","Design / None(Value/Cards count%)","100.00" +"Classifications","Design / None(Value/Story point%)","100.00" +"Classifications","Vulnerability / None(Value/Cards count%)","100.00" +"Classifications","Vulnerability / None(Value/Story point%)","100.00" +"Classifications","Sprint / Sprint37(Value/Cards count%)","100.00" +"Classifications","Sprint / Sprint37(Value/Story point%)","100.00" +"Classifications","Sprint / Sprint 35(Value/Cards count%)","33.33" +"Classifications","Sprint / Sprint 35(Value/Story point%)","54.55" +"Classifications","Sprint / Sprint 36(Value/Cards count%)","66.67" +"Classifications","Sprint / Sprint 36(Value/Story point%)","90.91" +"Classifications","Sprint / Sprint34(Value/Cards count%)","33.33" +"Classifications","Sprint / Sprint34(Value/Story point%)","54.55" +"Classifications","Project / Auto Dora Metrics(Value/Cards count%)","100.00" +"Classifications","Project / Auto Dora Metrics(Value/Story point%)","100.00" +"Classifications","Flagged / None(Value/Cards count%)","100.00" +"Classifications","Flagged / None(Value/Story point%)","100.00" +"Classifications","Fix versions / None(Value/Cards count%)","100.00" +"Classifications","Fix versions / None(Value/Story point%)","100.00" +"Classifications","Priority / Medium(Value/Cards count%)","100.00" +"Classifications","Priority / Medium(Value/Story point%)","100.00" +"Classifications","Partner / YinYuan Zhou(Value/Cards count%)","33.33" +"Classifications","Partner / YinYuan Zhou(Value/Story point%)","54.55" +"Classifications","Partner / None(Value/Cards count%)","66.67" +"Classifications","Partner / None(Value/Story point%)","45.45" +"Classifications","Labels / 1.1.7(Value/Cards count%)","100.00" +"Classifications","Labels / 1.1.7(Value/Story point%)","100.00" +"Classifications","Time tracking / None(Value/Cards count%)","100.00" +"Classifications","Time tracking / None(Value/Story point%)","100.00" +"Classifications","Story point estimate / 2.0(Value/Cards count%)","33.33" +"Classifications","Story point estimate / 2.0(Value/Story point%)","36.36" +"Classifications","Story point estimate / 3.0(Value/Cards count%)","33.33" +"Classifications","Story point estimate / 3.0(Value/Story point%)","54.55" +"Classifications","Story point estimate / 0.5(Value/Cards count%)","33.33" +"Classifications","Story point estimate / 0.5(Value/Story point%)","9.09" +"Classifications","QA / None(Value/Cards count%)","100.00" +"Classifications","QA / None(Value/Story point%)","100.00" +"Classifications","Feature/Operation / None(Value/Cards count%)","100.00" +"Classifications","Feature/Operation / None(Value/Story point%)","100.00" +"Classifications","Assignee / Chao Wang(Value/Cards count%)","33.33" +"Classifications","Assignee / Chao Wang(Value/Story point%)","54.55" +"Classifications","Assignee / YinYuan Zhou(Value/Cards count%)","33.33" +"Classifications","Assignee / YinYuan Zhou(Value/Story point%)","9.09" +"Classifications","Assignee / Qiuhong Lei(Value/Cards count%)","33.33" +"Classifications","Assignee / Qiuhong Lei(Value/Story point%)","36.36" "Rework","Total rework times","1" "Rework","Total rework cards","1" "Rework","Rework cards ratio(Total rework cards/Throughput%)","33.33" diff --git a/frontend/e2e/fixtures/create-new/metric-20240605-20240606.csv b/frontend/e2e/fixtures/create-new/metric-20240605-20240606.csv index b899c01d5d..4f56c6dd48 100644 --- a/frontend/e2e/fixtures/create-new/metric-20240605-20240606.csv +++ b/frontend/e2e/fixtures/create-new/metric-20240605-20240606.csv @@ -18,24 +18,42 @@ "Cycle time","Average waiting for testing time(days/card)","0.14" "Cycle time","Average testing time(days/storyPoint)","0.74" "Cycle time","Average testing time(days/card)","0.74" -"Classifications","Issue Type / Bug(%)","100.00" -"Classifications","Parent / ADM-868(%)","100.00" -"Classifications","Story testing-2 / None(%)","100.00" -"Classifications","Story testing-1 / None(%)","100.00" -"Classifications","Design / None(%)","100.00" -"Classifications","Vulnerability / None(%)","100.00" -"Classifications","Sprint / Sprint37(%)","100.00" -"Classifications","Project / Auto Dora Metrics(%)","100.00" -"Classifications","Flagged / None(%)","100.00" -"Classifications","Fix versions / None(%)","100.00" -"Classifications","Priority / High(%)","100.00" -"Classifications","Partner / None(%)","100.00" -"Classifications","Labels / 1.1.7(%)","100.00" -"Classifications","Time tracking / None(%)","100.00" -"Classifications","Story point estimate / 1.0(%)","100.00" -"Classifications","QA / None(%)","100.00" -"Classifications","Feature/Operation / None(%)","100.00" -"Classifications","Assignee / Man Tang(%)","100.00" +"Classifications","Issue Type / Bug(Value/Cards count%)","100.00" +"Classifications","Issue Type / Bug(Value/Story point%)","100.00" +"Classifications","Parent / ADM-868(Value/Cards count%)","100.00" +"Classifications","Parent / ADM-868(Value/Story point%)","100.00" +"Classifications","Story testing-2 / None(Value/Cards count%)","100.00" +"Classifications","Story testing-2 / None(Value/Story point%)","100.00" +"Classifications","Story testing-1 / None(Value/Cards count%)","100.00" +"Classifications","Story testing-1 / None(Value/Story point%)","100.00" +"Classifications","Design / None(Value/Cards count%)","100.00" +"Classifications","Design / None(Value/Story point%)","100.00" +"Classifications","Vulnerability / None(Value/Cards count%)","100.00" +"Classifications","Vulnerability / None(Value/Story point%)","100.00" +"Classifications","Sprint / Sprint37(Value/Cards count%)","100.00" +"Classifications","Sprint / Sprint37(Value/Story point%)","100.00" +"Classifications","Project / Auto Dora Metrics(Value/Cards count%)","100.00" +"Classifications","Project / Auto Dora Metrics(Value/Story point%)","100.00" +"Classifications","Flagged / None(Value/Cards count%)","100.00" +"Classifications","Flagged / None(Value/Story point%)","100.00" +"Classifications","Fix versions / None(Value/Cards count%)","100.00" +"Classifications","Fix versions / None(Value/Story point%)","100.00" +"Classifications","Priority / High(Value/Cards count%)","100.00" +"Classifications","Priority / High(Value/Story point%)","100.00" +"Classifications","Partner / None(Value/Cards count%)","100.00" +"Classifications","Partner / None(Value/Story point%)","100.00" +"Classifications","Labels / 1.1.7(Value/Cards count%)","100.00" +"Classifications","Labels / 1.1.7(Value/Story point%)","100.00" +"Classifications","Time tracking / None(Value/Cards count%)","100.00" +"Classifications","Time tracking / None(Value/Story point%)","100.00" +"Classifications","Story point estimate / 1.0(Value/Cards count%)","100.00" +"Classifications","Story point estimate / 1.0(Value/Story point%)","100.00" +"Classifications","QA / None(Value/Cards count%)","100.00" +"Classifications","QA / None(Value/Story point%)","100.00" +"Classifications","Feature/Operation / None(Value/Cards count%)","100.00" +"Classifications","Feature/Operation / None(Value/Story point%)","100.00" +"Classifications","Assignee / Man Tang(Value/Cards count%)","100.00" +"Classifications","Assignee / Man Tang(Value/Story point%)","100.00" "Rework","Total rework times","1" "Rework","Total rework cards","1" "Rework","Rework cards ratio(Total rework cards/Throughput%)","100.00" diff --git a/frontend/e2e/fixtures/create-new/metric-20240607-20240607.csv b/frontend/e2e/fixtures/create-new/metric-20240607-20240607.csv index e0ac5f584e..7197173725 100644 --- a/frontend/e2e/fixtures/create-new/metric-20240607-20240607.csv +++ b/frontend/e2e/fixtures/create-new/metric-20240607-20240607.csv @@ -15,24 +15,42 @@ "Cycle time","Average waiting for testing time(days/card)","0.84" "Cycle time","Average testing time(days/storyPoint)","0" "Cycle time","Average testing time(days/card)","0" -"Classifications","Issue Type / Bug(%)","100.00" -"Classifications","Parent / ADM-319(%)","100.00" -"Classifications","Story testing-2 / None(%)","100.00" -"Classifications","Story testing-1 / None(%)","100.00" -"Classifications","Design / None(%)","100.00" -"Classifications","Vulnerability / None(%)","100.00" -"Classifications","Sprint / Sprint37(%)","100.00" -"Classifications","Project / Auto Dora Metrics(%)","100.00" -"Classifications","Flagged / None(%)","100.00" -"Classifications","Fix versions / None(%)","100.00" -"Classifications","Priority / Medium(%)","100.00" -"Classifications","Partner / None(%)","100.00" -"Classifications","Labels / None(%)","100.00" -"Classifications","Time tracking / None(%)","100.00" -"Classifications","Story point estimate / 1.0(%)","100.00" -"Classifications","QA / None(%)","100.00" -"Classifications","Feature/Operation / None(%)","100.00" -"Classifications","Assignee / YinYuan Zhou(%)","100.00" +"Classifications","Issue Type / Bug(Value/Cards count%)","100.00" +"Classifications","Issue Type / Bug(Value/Story point%)","100.00" +"Classifications","Parent / ADM-319(Value/Cards count%)","100.00" +"Classifications","Parent / ADM-319(Value/Story point%)","100.00" +"Classifications","Story testing-2 / None(Value/Cards count%)","100.00" +"Classifications","Story testing-2 / None(Value/Story point%)","100.00" +"Classifications","Story testing-1 / None(Value/Cards count%)","100.00" +"Classifications","Story testing-1 / None(Value/Story point%)","100.00" +"Classifications","Design / None(Value/Cards count%)","100.00" +"Classifications","Design / None(Value/Story point%)","100.00" +"Classifications","Vulnerability / None(Value/Cards count%)","100.00" +"Classifications","Vulnerability / None(Value/Story point%)","100.00" +"Classifications","Sprint / Sprint37(Value/Cards count%)","100.00" +"Classifications","Sprint / Sprint37(Value/Story point%)","100.00" +"Classifications","Project / Auto Dora Metrics(Value/Cards count%)","100.00" +"Classifications","Project / Auto Dora Metrics(Value/Story point%)","100.00" +"Classifications","Flagged / None(Value/Cards count%)","100.00" +"Classifications","Flagged / None(Value/Story point%)","100.00" +"Classifications","Fix versions / None(Value/Cards count%)","100.00" +"Classifications","Fix versions / None(Value/Story point%)","100.00" +"Classifications","Priority / Medium(Value/Cards count%)","100.00" +"Classifications","Priority / Medium(Value/Story point%)","100.00" +"Classifications","Partner / None(Value/Cards count%)","100.00" +"Classifications","Partner / None(Value/Story point%)","100.00" +"Classifications","Labels / None(Value/Cards count%)","100.00" +"Classifications","Labels / None(Value/Story point%)","100.00" +"Classifications","Time tracking / None(Value/Cards count%)","100.00" +"Classifications","Time tracking / None(Value/Story point%)","100.00" +"Classifications","Story point estimate / 1.0(Value/Cards count%)","100.00" +"Classifications","Story point estimate / 1.0(Value/Story point%)","100.00" +"Classifications","QA / None(Value/Cards count%)","100.00" +"Classifications","QA / None(Value/Story point%)","100.00" +"Classifications","Feature/Operation / None(Value/Cards count%)","100.00" +"Classifications","Feature/Operation / None(Value/Story point%)","100.00" +"Classifications","Assignee / YinYuan Zhou(Value/Cards count%)","100.00" +"Classifications","Assignee / YinYuan Zhou(Value/Story point%)","100.00" "Rework","Total rework times","0" "Rework","Total rework cards","0" "Rework","Rework cards ratio(Total rework cards/Throughput%)","0" diff --git a/frontend/e2e/fixtures/create-new/metric-data.csv b/frontend/e2e/fixtures/create-new/metric-data.csv deleted file mode 100644 index ec1429c1f1..0000000000 --- a/frontend/e2e/fixtures/create-new/metric-data.csv +++ /dev/null @@ -1,60 +0,0 @@ -"Group","Metrics","Value" -"Velocity","Velocity(Story Point)","17.0" -"Velocity","Throughput(Cards Count)","9" -"Cycle time","Average cycle time(days/storyPoint)","4.86" -"Cycle time","Average cycle time(days/card)","9.18" -"Cycle time","Total time / Total cycle time","10.92" -"Cycle time","Total development time / Total cycle time","37.55" -"Cycle time","Total block time / Total cycle time","19.96" -"Cycle time","Total review time / Total cycle time","22.47" -"Cycle time","Total testing time / Total cycle time","9.10" -"Cycle time","Average time(days/storyPoint)","0.53" -"Cycle time","Average time(days/card)","1.00" -"Cycle time","Average development time(days/storyPoint)","1.83" -"Cycle time","Average development time(days/card)","3.45" -"Cycle time","Average block time(days/storyPoint)","0.97" -"Cycle time","Average block time(days/card)","1.83" -"Cycle time","Average review time(days/storyPoint)","1.09" -"Cycle time","Average review time(days/card)","2.06" -"Cycle time","Average testing time(days/storyPoint)","0.44" -"Cycle time","Average testing time(days/card)","0.84" -"Classifications","Issue Type / Spike","11.11" -"Classifications","Issue Type / Task","88.89" -"Classifications","Parent / ADM-322","66.67" -"Classifications","Parent / ADM-279","22.22" -"Classifications","Parent / ADM-319","11.11" -"Classifications","Story testing-2 / None","100.00" -"Classifications","Story testing-1 / 1.0","88.89" -"Classifications","Story testing-1 / None","11.11" -"Classifications","Project / Auto Dora Metrics","100.00" -"Classifications","Sprint / Sprint 26","11.11" -"Classifications","Sprint / Sprint 27","100.00" -"Classifications","Sprint / Sprint 28","88.89" -"Classifications","Flagged / None","100.00" -"Classifications","Fix versions / None","100.00" -"Classifications","Priority / Medium","100.00" -"Classifications","Partner / None","100.00" -"Classifications","Time tracking / None","100.00" -"Classifications","Labels / Stream1","44.44" -"Classifications","Labels / Stream2","55.56" -"Classifications","Story point estimate / 1.0","44.44" -"Classifications","Story point estimate / 2.0","22.22" -"Classifications","Story point estimate / 3.0","33.33" -"Classifications","QA / Weiran Sun","11.11" -"Classifications","QA / None","88.89" -"Classifications","Feature/Operation / None","100.00" -"Classifications","Assignee / heartbeat user","44.44" -"Classifications","Assignee / Junbo Dai","11.11" -"Classifications","Assignee / Xinyi Wang","11.11" -"Classifications","Assignee / Weiran Sun","11.11" -"Classifications","Assignee / Xuebing Li","11.11" -"Classifications","Assignee / Yunsong Yang","11.11" -"Rework","Total rework times","11" -"Rework","Total rework cards","6" -"Rework","Rework cards ratio(Total rework cards/Throughput)","0.6667" -"Deployment frequency","Heartbeat / Deploy prod / Deployment frequency(Deployments/Day)","6.60" -"Lead time for changes","Heartbeat / Deploy prod / PR Lead Time","3.21" -"Lead time for changes","Heartbeat / Deploy prod / Pipeline Lead Time","0.50" -"Lead time for changes","Heartbeat / Deploy prod / Total Lead Time","3.71" -"Dev change failure rate","Heartbeat / Deploy prod / Dev change failure rate","0.1750" -"Dev mean time to recovery","Heartbeat / Deploy prod / Dev mean time to recovery","1.90" diff --git a/frontend/e2e/fixtures/create-new/report-result.ts b/frontend/e2e/fixtures/create-new/report-result.ts index 34ed21b308..0eb650891b 100644 --- a/frontend/e2e/fixtures/create-new/report-result.ts +++ b/frontend/e2e/fixtures/create-new/report-result.ts @@ -30,7 +30,7 @@ export interface IBoardCycletimeDetailItem { export interface IBoardClassificationDetailItem { name: string; - lines: [string, string][]; + lines: [string, string, string][]; } export interface ICsvComparedLines extends Record {} @@ -440,244 +440,244 @@ export const BOARD_METRICS_CLASSIFICATION_MULTIPLE_RANGES: IBoardClassificationD [ { name: 'Issue Type', - lines: [['Bug', '100.00%']], + lines: [['Bug', '100.00%', '100.00%']], }, { name: 'Parent', - lines: [['ADM-319', '100.00%']], + lines: [['ADM-319', '100.00%', '100.00%']], }, { name: 'Story testing-2', - lines: [['None', '100.00%']], + lines: [['None', '100.00%', '100.00%']], }, { name: 'Story testing-1', - lines: [['None', '100.00%']], + lines: [['None', '100.00%', '100.00%']], }, { name: 'Design', - lines: [['None', '100.00%']], + lines: [['None', '100.00%', '100.00%']], }, { name: 'Vulnerability', - lines: [['None', '100.00%']], + lines: [['None', '100.00%', '100.00%']], }, { name: 'Sprint', - lines: [['Sprint37', '100.00%']], + lines: [['Sprint37', '100.00%', '100.00%']], }, { name: 'Project', - lines: [['Auto Dora Metrics', '100.00%']], + lines: [['Auto Dora Metrics', '100.00%', '100.00%']], }, { name: 'Flagged', - lines: [['None', '100.00%']], + lines: [['None', '100.00%', '100.00%']], }, { name: 'Fix versions', - lines: [['None', '100.00%']], + lines: [['None', '100.00%', '100.00%']], }, { name: 'Priority', - lines: [['Medium', '100.00%']], + lines: [['Medium', '100.00%', '100.00%']], }, { name: 'Partner', - lines: [['None', '100.00%']], + lines: [['None', '100.00%', '100.00%']], }, { name: 'Labels', - lines: [['None', '100.00%']], + lines: [['None', '100.00%', '100.00%']], }, { name: 'Time tracking', - lines: [['None', '100.00%']], + lines: [['None', '100.00%', '100.00%']], }, { name: 'Story point estimate', - lines: [['1.0', '100.00%']], + lines: [['1.0', '100.00%', '100.00%']], }, { name: 'QA', - lines: [['None', '100.00%']], + lines: [['None', '100.00%', '100.00%']], }, { name: 'Feature/Operation', - lines: [['None', '100.00%']], + lines: [['None', '100.00%', '100.00%']], }, { name: 'Assignee', - lines: [['YinYuan Zhou', '100.00%']], + lines: [['YinYuan Zhou', '100.00%', '100.00%']], }, ], [ { name: 'Issue Type', - lines: [['Bug', '100.00%']], + lines: [['Bug', '100.00%', '100.00%']], }, { name: 'Parent', - lines: [['ADM-868', '100.00%']], + lines: [['ADM-868', '100.00%', '100.00%']], }, { name: 'Story testing-2', - lines: [['None', '100.00%']], + lines: [['None', '100.00%', '100.00%']], }, { name: 'Story testing-1', - lines: [['None', '100.00%']], + lines: [['None', '100.00%', '100.00%']], }, { name: 'Design', - lines: [['None', '100.00%']], + lines: [['None', '100.00%', '100.00%']], }, { name: 'Vulnerability', - lines: [['None', '100.00%']], + lines: [['None', '100.00%', '100.00%']], }, { name: 'Sprint', - lines: [['Sprint37', '100.00%']], + lines: [['Sprint37', '100.00%', '100.00%']], }, { name: 'Project', - lines: [['Auto Dora Metrics', '100.00%']], + lines: [['Auto Dora Metrics', '100.00%', '100.00%']], }, { name: 'Flagged', - lines: [['None', '100.00%']], + lines: [['None', '100.00%', '100.00%']], }, { name: 'Fix versions', - lines: [['None', '100.00%']], + lines: [['None', '100.00%', '100.00%']], }, { name: 'Priority', - lines: [['High', '100.00%']], + lines: [['High', '100.00%', '100.00%']], }, { name: 'Partner', - lines: [['None', '100.00%']], + lines: [['None', '100.00%', '100.00%']], }, { name: 'Labels', - lines: [['1.1.7', '100.00%']], + lines: [['1.1.7', '100.00%', '100.00%']], }, { name: 'Time tracking', - lines: [['None', '100.00%']], + lines: [['None', '100.00%', '100.00%']], }, { name: 'Story point estimate', - lines: [['1.0', '100.00%']], + lines: [['1.0', '100.00%', '100.00%']], }, { name: 'QA', - lines: [['None', '100.00%']], + lines: [['None', '100.00%', '100.00%']], }, { name: 'Feature/Operation', - lines: [['None', '100.00%']], + lines: [['None', '100.00%', '100.00%']], }, { name: 'Assignee', - lines: [['Man Tang', '100.00%']], + lines: [['Man Tang', '100.00%', '100.00%']], }, ], [ { name: 'Issue Type', lines: [ - ['Task', '66.67%'], - ['Story', '33.33%'], + ['Task', '66.67%', '63.64%'], + ['Story', '33.33%', '36.36%'], ], }, { name: 'Parent', - lines: [['ADM-868', '100.00%']], + lines: [['ADM-868', '100.00%', '100.00%']], }, { name: 'Story testing-2', - lines: [['None', '100.00%']], + lines: [['None', '100.00%', '100.00%']], }, { name: 'Story testing-1', lines: [ - ['1.0', '66.67%'], - ['None', '33.33%'], + ['1.0', '66.67%', '63.64%'], + ['None', '33.33%', '36.36%'], ], }, { name: 'Design', - lines: [['None', '100.00%']], + lines: [['None', '100.00%', '100.00%']], }, { name: 'Vulnerability', - lines: [['None', '100.00%']], + lines: [['None', '100.00%', '100.00%']], }, { name: 'Sprint', lines: [ - ['Sprint37', '100.00%'], - ['Sprint 35', '33.33%'], - ['Sprint 36', '66.67%'], - ['Sprint34', '33.33%'], + ['Sprint37', '100.00%', '100.00%'], + ['Sprint 35', '33.33%', '54.55%'], + ['Sprint 36', '66.67%', '90.91%'], + ['Sprint34', '33.33%', '54.55%'], ], }, { name: 'Project', - lines: [['Auto Dora Metrics', '100.00%']], + lines: [['Auto Dora Metrics', '100.00%', '100.00%']], }, { name: 'Flagged', - lines: [['None', '100.00%']], + lines: [['None', '100.00%', '100.00%']], }, { name: 'Fix versions', - lines: [['None', '100.00%']], + lines: [['None', '100.00%', '100.00%']], }, { name: 'Priority', - lines: [['Medium', '100.00%']], + lines: [['Medium', '100.00%', '100.00%']], }, { name: 'Partner', lines: [ - ['YinYuan Zhou', '33.33%'], - ['None', '66.67%'], + ['YinYuan Zhou', '33.33%', '54.55%'], + ['None', '66.67%', '45.45%'], ], }, { name: 'Labels', - lines: [['1.1.7', '100.00%']], + lines: [['1.1.7', '100.00%', '100.00%']], }, { name: 'Time tracking', - lines: [['None', '100.00%']], + lines: [['None', '100.00%', '100.00%']], }, { name: 'Story point estimate', lines: [ - ['2.0', '33.33%'], - ['3.0', '33.33%'], - ['0.5', '33.33%'], + ['2.0', '33.33%', '36.36%'], + ['3.0', '33.33%', '54.55%'], + ['0.5', '33.33%', '9.09%'], ], }, { name: 'QA', - lines: [['None', '100.00%']], + lines: [['None', '100.00%', '100.00%']], }, { name: 'Feature/Operation', - lines: [['None', '100.00%']], + lines: [['None', '100.00%', '100.00%']], }, { name: 'Assignee', lines: [ - ['Chao Wang', '33.33%'], - ['YinYuan Zhou', '33.33%'], - ['Qiuhong Lei', '33.33%'], + ['Chao Wang', '33.33%', '54.55%'], + ['YinYuan Zhou', '33.33%', '9.09%'], + ['Qiuhong Lei', '33.33%', '36.36%'], ], }, ], diff --git a/frontend/e2e/fixtures/create-new/with-design-and-wait-for-deployment-metric-20240603-20240604.csv b/frontend/e2e/fixtures/create-new/with-design-and-wait-for-deployment-metric-20240603-20240604.csv index b9d6475c7c..c53efc9338 100644 --- a/frontend/e2e/fixtures/create-new/with-design-and-wait-for-deployment-metric-20240603-20240604.csv +++ b/frontend/e2e/fixtures/create-new/with-design-and-wait-for-deployment-metric-20240603-20240604.csv @@ -21,34 +21,62 @@ "Cycle time","Average testing time(days/card)","0.33" "Cycle time","Average waiting for deployment time(days/storyPoint)","0.18" "Cycle time","Average waiting for deployment time(days/card)","0.33" -"Classifications","Issue Type / Task(%)","66.67" -"Classifications","Issue Type / Story(%)","33.33" -"Classifications","Parent / ADM-868(%)","100.00" -"Classifications","Story testing-2 / None(%)","100.00" -"Classifications","Story testing-1 / 1.0(%)","66.67" -"Classifications","Story testing-1 / None(%)","33.33" -"Classifications","Design / None(%)","100.00" -"Classifications","Vulnerability / None(%)","100.00" -"Classifications","Sprint / Sprint37(%)","100.00" -"Classifications","Sprint / Sprint 35(%)","33.33" -"Classifications","Sprint / Sprint 36(%)","66.67" -"Classifications","Sprint / Sprint34(%)","33.33" -"Classifications","Project / Auto Dora Metrics(%)","100.00" -"Classifications","Flagged / None(%)","100.00" -"Classifications","Fix versions / None(%)","100.00" -"Classifications","Priority / Medium(%)","100.00" -"Classifications","Partner / YinYuan Zhou(%)","33.33" -"Classifications","Partner / None(%)","66.67" -"Classifications","Labels / 1.1.7(%)","100.00" -"Classifications","Time tracking / None(%)","100.00" -"Classifications","Story point estimate / 2.0(%)","33.33" -"Classifications","Story point estimate / 3.0(%)","33.33" -"Classifications","Story point estimate / 0.5(%)","33.33" -"Classifications","QA / None(%)","100.00" -"Classifications","Feature/Operation / None(%)","100.00" -"Classifications","Assignee / Chao Wang(%)","33.33" -"Classifications","Assignee / YinYuan Zhou(%)","33.33" -"Classifications","Assignee / Qiuhong Lei(%)","33.33" +"Classifications","Issue Type / Task(Value/Cards count%)","66.67" +"Classifications","Issue Type / Task(Value/Story point%)","63.64" +"Classifications","Issue Type / Story(Value/Cards count%)","33.33" +"Classifications","Issue Type / Story(Value/Story point%)","36.36" +"Classifications","Parent / ADM-868(Value/Cards count%)","100.00" +"Classifications","Parent / ADM-868(Value/Story point%)","100.00" +"Classifications","Story testing-2 / None(Value/Cards count%)","100.00" +"Classifications","Story testing-2 / None(Value/Story point%)","100.00" +"Classifications","Story testing-1 / 1.0(Value/Cards count%)","66.67" +"Classifications","Story testing-1 / 1.0(Value/Story point%)","63.64" +"Classifications","Story testing-1 / None(Value/Cards count%)","33.33" +"Classifications","Story testing-1 / None(Value/Story point%)","36.36" +"Classifications","Design / None(Value/Cards count%)","100.00" +"Classifications","Design / None(Value/Story point%)","100.00" +"Classifications","Vulnerability / None(Value/Cards count%)","100.00" +"Classifications","Vulnerability / None(Value/Story point%)","100.00" +"Classifications","Sprint / Sprint37(Value/Cards count%)","100.00" +"Classifications","Sprint / Sprint37(Value/Story point%)","100.00" +"Classifications","Sprint / Sprint 35(Value/Cards count%)","33.33" +"Classifications","Sprint / Sprint 35(Value/Story point%)","54.55" +"Classifications","Sprint / Sprint 36(Value/Cards count%)","66.67" +"Classifications","Sprint / Sprint 36(Value/Story point%)","90.91" +"Classifications","Sprint / Sprint34(Value/Cards count%)","33.33" +"Classifications","Sprint / Sprint34(Value/Story point%)","54.55" +"Classifications","Project / Auto Dora Metrics(Value/Cards count%)","100.00" +"Classifications","Project / Auto Dora Metrics(Value/Story point%)","100.00" +"Classifications","Flagged / None(Value/Cards count%)","100.00" +"Classifications","Flagged / None(Value/Story point%)","100.00" +"Classifications","Fix versions / None(Value/Cards count%)","100.00" +"Classifications","Fix versions / None(Value/Story point%)","100.00" +"Classifications","Priority / Medium(Value/Cards count%)","100.00" +"Classifications","Priority / Medium(Value/Story point%)","100.00" +"Classifications","Partner / YinYuan Zhou(Value/Cards count%)","33.33" +"Classifications","Partner / YinYuan Zhou(Value/Story point%)","54.55" +"Classifications","Partner / None(Value/Cards count%)","66.67" +"Classifications","Partner / None(Value/Story point%)","45.45" +"Classifications","Labels / 1.1.7(Value/Cards count%)","100.00" +"Classifications","Labels / 1.1.7(Value/Story point%)","100.00" +"Classifications","Time tracking / None(Value/Cards count%)","100.00" +"Classifications","Time tracking / None(Value/Story point%)","100.00" +"Classifications","Story point estimate / 2.0(Value/Cards count%)","33.33" +"Classifications","Story point estimate / 2.0(Value/Story point%)","36.36" +"Classifications","Story point estimate / 3.0(Value/Cards count%)","33.33" +"Classifications","Story point estimate / 3.0(Value/Story point%)","54.55" +"Classifications","Story point estimate / 0.5(Value/Cards count%)","33.33" +"Classifications","Story point estimate / 0.5(Value/Story point%)","9.09" +"Classifications","QA / None(Value/Cards count%)","100.00" +"Classifications","QA / None(Value/Story point%)","100.00" +"Classifications","Feature/Operation / None(Value/Cards count%)","100.00" +"Classifications","Feature/Operation / None(Value/Story point%)","100.00" +"Classifications","Assignee / Chao Wang(Value/Cards count%)","33.33" +"Classifications","Assignee / Chao Wang(Value/Story point%)","54.55" +"Classifications","Assignee / YinYuan Zhou(Value/Cards count%)","33.33" +"Classifications","Assignee / YinYuan Zhou(Value/Story point%)","9.09" +"Classifications","Assignee / Qiuhong Lei(Value/Cards count%)","33.33" +"Classifications","Assignee / Qiuhong Lei(Value/Story point%)","36.36" "Rework","Total rework times","1" "Rework","Total rework cards","1" "Rework","Rework cards ratio(Total rework cards/Throughput%)","33.33" diff --git a/frontend/e2e/fixtures/create-new/with-design-and-wait-for-deployment-metric-20240605-20240606.csv b/frontend/e2e/fixtures/create-new/with-design-and-wait-for-deployment-metric-20240605-20240606.csv index e8917b2376..ff4e6b946c 100644 --- a/frontend/e2e/fixtures/create-new/with-design-and-wait-for-deployment-metric-20240605-20240606.csv +++ b/frontend/e2e/fixtures/create-new/with-design-and-wait-for-deployment-metric-20240605-20240606.csv @@ -21,24 +21,42 @@ "Cycle time","Average testing time(days/card)","0.74" "Cycle time","Average waiting for deployment time(days/storyPoint)","0.14" "Cycle time","Average waiting for deployment time(days/card)","0.14" -"Classifications","Issue Type / Bug(%)","100.00" -"Classifications","Parent / ADM-868(%)","100.00" -"Classifications","Story testing-2 / None(%)","100.00" -"Classifications","Story testing-1 / None(%)","100.00" -"Classifications","Design / None(%)","100.00" -"Classifications","Vulnerability / None(%)","100.00" -"Classifications","Sprint / Sprint37(%)","100.00" -"Classifications","Project / Auto Dora Metrics(%)","100.00" -"Classifications","Flagged / None(%)","100.00" -"Classifications","Fix versions / None(%)","100.00" -"Classifications","Priority / High(%)","100.00" -"Classifications","Partner / None(%)","100.00" -"Classifications","Labels / 1.1.7(%)","100.00" -"Classifications","Time tracking / None(%)","100.00" -"Classifications","Story point estimate / 1.0(%)","100.00" -"Classifications","QA / None(%)","100.00" -"Classifications","Feature/Operation / None(%)","100.00" -"Classifications","Assignee / Man Tang(%)","100.00" +"Classifications","Issue Type / Bug(Value/Cards count%)","100.00" +"Classifications","Issue Type / Bug(Value/Story point%)","100.00" +"Classifications","Parent / ADM-868(Value/Cards count%)","100.00" +"Classifications","Parent / ADM-868(Value/Story point%)","100.00" +"Classifications","Story testing-2 / None(Value/Cards count%)","100.00" +"Classifications","Story testing-2 / None(Value/Story point%)","100.00" +"Classifications","Story testing-1 / None(Value/Cards count%)","100.00" +"Classifications","Story testing-1 / None(Value/Story point%)","100.00" +"Classifications","Design / None(Value/Cards count%)","100.00" +"Classifications","Design / None(Value/Story point%)","100.00" +"Classifications","Vulnerability / None(Value/Cards count%)","100.00" +"Classifications","Vulnerability / None(Value/Story point%)","100.00" +"Classifications","Sprint / Sprint37(Value/Cards count%)","100.00" +"Classifications","Sprint / Sprint37(Value/Story point%)","100.00" +"Classifications","Project / Auto Dora Metrics(Value/Cards count%)","100.00" +"Classifications","Project / Auto Dora Metrics(Value/Story point%)","100.00" +"Classifications","Flagged / None(Value/Cards count%)","100.00" +"Classifications","Flagged / None(Value/Story point%)","100.00" +"Classifications","Fix versions / None(Value/Cards count%)","100.00" +"Classifications","Fix versions / None(Value/Story point%)","100.00" +"Classifications","Priority / High(Value/Cards count%)","100.00" +"Classifications","Priority / High(Value/Story point%)","100.00" +"Classifications","Partner / None(Value/Cards count%)","100.00" +"Classifications","Partner / None(Value/Story point%)","100.00" +"Classifications","Labels / 1.1.7(Value/Cards count%)","100.00" +"Classifications","Labels / 1.1.7(Value/Story point%)","100.00" +"Classifications","Time tracking / None(Value/Cards count%)","100.00" +"Classifications","Time tracking / None(Value/Story point%)","100.00" +"Classifications","Story point estimate / 1.0(Value/Cards count%)","100.00" +"Classifications","Story point estimate / 1.0(Value/Story point%)","100.00" +"Classifications","QA / None(Value/Cards count%)","100.00" +"Classifications","QA / None(Value/Story point%)","100.00" +"Classifications","Feature/Operation / None(Value/Cards count%)","100.00" +"Classifications","Feature/Operation / None(Value/Story point%)","100.00" +"Classifications","Assignee / Man Tang(Value/Cards count%)","100.00" +"Classifications","Assignee / Man Tang(Value/Story point%)","100.00" "Rework","Total rework times","1" "Rework","Total rework cards","1" "Rework","Rework cards ratio(Total rework cards/Throughput%)","100.00" diff --git a/frontend/e2e/fixtures/create-new/with-design-and-wait-for-deployment-metric-20240607-20240607.csv b/frontend/e2e/fixtures/create-new/with-design-and-wait-for-deployment-metric-20240607-20240607.csv index dbf56bdb39..09480b1c14 100644 --- a/frontend/e2e/fixtures/create-new/with-design-and-wait-for-deployment-metric-20240607-20240607.csv +++ b/frontend/e2e/fixtures/create-new/with-design-and-wait-for-deployment-metric-20240607-20240607.csv @@ -18,24 +18,42 @@ "Cycle time","Average testing time(days/card)","0" "Cycle time","Average waiting for deployment time(days/storyPoint)","0.84" "Cycle time","Average waiting for deployment time(days/card)","0.84" -"Classifications","Issue Type / Bug(%)","100.00" -"Classifications","Parent / ADM-319(%)","100.00" -"Classifications","Story testing-2 / None(%)","100.00" -"Classifications","Story testing-1 / None(%)","100.00" -"Classifications","Design / None(%)","100.00" -"Classifications","Vulnerability / None(%)","100.00" -"Classifications","Sprint / Sprint37(%)","100.00" -"Classifications","Project / Auto Dora Metrics(%)","100.00" -"Classifications","Flagged / None(%)","100.00" -"Classifications","Fix versions / None(%)","100.00" -"Classifications","Priority / Medium(%)","100.00" -"Classifications","Partner / None(%)","100.00" -"Classifications","Labels / None(%)","100.00" -"Classifications","Time tracking / None(%)","100.00" -"Classifications","Story point estimate / 1.0(%)","100.00" -"Classifications","QA / None(%)","100.00" -"Classifications","Feature/Operation / None(%)","100.00" -"Classifications","Assignee / YinYuan Zhou(%)","100.00" +"Classifications","Issue Type / Bug(Value/Cards count%)","100.00" +"Classifications","Issue Type / Bug(Value/Story point%)","100.00" +"Classifications","Parent / ADM-319(Value/Cards count%)","100.00" +"Classifications","Parent / ADM-319(Value/Story point%)","100.00" +"Classifications","Story testing-2 / None(Value/Cards count%)","100.00" +"Classifications","Story testing-2 / None(Value/Story point%)","100.00" +"Classifications","Story testing-1 / None(Value/Cards count%)","100.00" +"Classifications","Story testing-1 / None(Value/Story point%)","100.00" +"Classifications","Design / None(Value/Cards count%)","100.00" +"Classifications","Design / None(Value/Story point%)","100.00" +"Classifications","Vulnerability / None(Value/Cards count%)","100.00" +"Classifications","Vulnerability / None(Value/Story point%)","100.00" +"Classifications","Sprint / Sprint37(Value/Cards count%)","100.00" +"Classifications","Sprint / Sprint37(Value/Story point%)","100.00" +"Classifications","Project / Auto Dora Metrics(Value/Cards count%)","100.00" +"Classifications","Project / Auto Dora Metrics(Value/Story point%)","100.00" +"Classifications","Flagged / None(Value/Cards count%)","100.00" +"Classifications","Flagged / None(Value/Story point%)","100.00" +"Classifications","Fix versions / None(Value/Cards count%)","100.00" +"Classifications","Fix versions / None(Value/Story point%)","100.00" +"Classifications","Priority / Medium(Value/Cards count%)","100.00" +"Classifications","Priority / Medium(Value/Story point%)","100.00" +"Classifications","Partner / None(Value/Cards count%)","100.00" +"Classifications","Partner / None(Value/Story point%)","100.00" +"Classifications","Labels / None(Value/Cards count%)","100.00" +"Classifications","Labels / None(Value/Story point%)","100.00" +"Classifications","Time tracking / None(Value/Cards count%)","100.00" +"Classifications","Time tracking / None(Value/Story point%)","100.00" +"Classifications","Story point estimate / 1.0(Value/Cards count%)","100.00" +"Classifications","Story point estimate / 1.0(Value/Story point%)","100.00" +"Classifications","QA / None(Value/Cards count%)","100.00" +"Classifications","QA / None(Value/Story point%)","100.00" +"Classifications","Feature/Operation / None(Value/Cards count%)","100.00" +"Classifications","Feature/Operation / None(Value/Story point%)","100.00" +"Classifications","Assignee / YinYuan Zhou(Value/Cards count%)","100.00" +"Classifications","Assignee / YinYuan Zhou(Value/Story point%)","100.00" "Rework","Total rework times","0" "Rework","Total rework cards","0" "Rework","Rework cards ratio(Total rework cards/Throughput%)","0" diff --git a/frontend/e2e/fixtures/cycle-time-by-status/metric-data-by-status.csv b/frontend/e2e/fixtures/cycle-time-by-status/metric-data-by-status.csv index acae944a45..0d5e7c9b2e 100644 --- a/frontend/e2e/fixtures/cycle-time-by-status/metric-data-by-status.csv +++ b/frontend/e2e/fixtures/cycle-time-by-status/metric-data-by-status.csv @@ -12,16 +12,29 @@ "Cycle time","Average review time(days/card)","0.02" "Cycle time","Average waiting for testing time(days/storyPoint)","0.01" "Cycle time","Average waiting for testing time(days/card)","0.02" -"Classifications","Story point estimate / 2.0(%)","66.67" -"Classifications","Story point estimate / 3.0(%)","33.33" -"Classifications","Issue Type / Task(%)","100.00" -"Classifications","Parent / None(%)","100.00" -"Classifications","Design / None(%)","100.00" -"Classifications","story point / None(%)","100.00" -"Classifications","Project / status-test(%)","100.00" -"Classifications","Flagged / None(%)","100.00" -"Classifications","Reporter / heartbeat user(%)","100.00" -"Classifications","Assignee / heartbeat user(%)","33.33" -"Classifications","Assignee / Chao Wang(%)","33.33" -"Classifications","Assignee / Junbo Dai(%)","33.33" -"Classifications","Labels / None(%)","100.00" +"Classifications","Story point estimate / 2.0(Value/Cards count%)","66.67" +"Classifications","Story point estimate / 2.0(Value/Story point%)","57.14" +"Classifications","Story point estimate / 3.0(Value/Cards count%)","33.33" +"Classifications","Story point estimate / 3.0(Value/Story point%)","42.86" +"Classifications","Issue Type / Task(Value/Cards count%)","100.00" +"Classifications","Issue Type / Task(Value/Story point%)","100.00" +"Classifications","Parent / None(Value/Cards count%)","100.00" +"Classifications","Parent / None(Value/Story point%)","100.00" +"Classifications","Design / None(Value/Cards count%)","100.00" +"Classifications","Design / None(Value/Story point%)","100.00" +"Classifications","story point / None(Value/Cards count%)","100.00" +"Classifications","story point / None(Value/Story point%)","100.00" +"Classifications","Project / status-test(Value/Cards count%)","100.00" +"Classifications","Project / status-test(Value/Story point%)","100.00" +"Classifications","Flagged / None(Value/Cards count%)","100.00" +"Classifications","Flagged / None(Value/Story point%)","100.00" +"Classifications","Reporter / heartbeat user(Value/Cards count%)","100.00" +"Classifications","Reporter / heartbeat user(Value/Story point%)","100.00" +"Classifications","Assignee / heartbeat user(Value/Cards count%)","33.33" +"Classifications","Assignee / heartbeat user(Value/Story point%)","28.57" +"Classifications","Assignee / Chao Wang(Value/Cards count%)","33.33" +"Classifications","Assignee / Chao Wang(Value/Story point%)","28.57" +"Classifications","Assignee / Junbo Dai(Value/Cards count%)","33.33" +"Classifications","Assignee / Junbo Dai(Value/Story point%)","42.86" +"Classifications","Labels / None(Value/Cards count%)","100.00" +"Classifications","Labels / None(Value/Story point%)","100.00" diff --git a/frontend/e2e/fixtures/import-file/metric-with-holiday-data.csv b/frontend/e2e/fixtures/import-file/metric-with-holiday-data.csv index ce88dac4f8..65fc95fdf8 100644 --- a/frontend/e2e/fixtures/import-file/metric-with-holiday-data.csv +++ b/frontend/e2e/fixtures/import-file/metric-with-holiday-data.csv @@ -15,24 +15,42 @@ "Cycle time","Average waiting for testing time(days/card)","0.84" "Cycle time","Average testing time(days/storyPoint)","0" "Cycle time","Average testing time(days/card)","0" -"Classifications","Issue Type / Bug(%)","100.00" -"Classifications","Parent / ADM-319(%)","100.00" -"Classifications","Story testing-2 / None(%)","100.00" -"Classifications","Story testing-1 / None(%)","100.00" -"Classifications","Design / None(%)","100.00" -"Classifications","Vulnerability / None(%)","100.00" -"Classifications","Sprint / Sprint37(%)","100.00" -"Classifications","Project / Auto Dora Metrics(%)","100.00" -"Classifications","Flagged / None(%)","100.00" -"Classifications","Fix versions / None(%)","100.00" -"Classifications","Priority / Medium(%)","100.00" -"Classifications","Partner / None(%)","100.00" -"Classifications","Labels / None(%)","100.00" -"Classifications","Time tracking / None(%)","100.00" -"Classifications","Story point estimate / 1.0(%)","100.00" -"Classifications","QA / None(%)","100.00" -"Classifications","Feature/Operation / None(%)","100.00" -"Classifications","Assignee / YinYuan Zhou(%)","100.00" +"Classifications","Issue Type / Bug(Value/Cards count%)","100.00" +"Classifications","Issue Type / Bug(Value/Story point%)","100.00" +"Classifications","Parent / ADM-319(Value/Cards count%)","100.00" +"Classifications","Parent / ADM-319(Value/Story point%)","100.00" +"Classifications","Story testing-2 / None(Value/Cards count%)","100.00" +"Classifications","Story testing-2 / None(Value/Story point%)","100.00" +"Classifications","Story testing-1 / None(Value/Cards count%)","100.00" +"Classifications","Story testing-1 / None(Value/Story point%)","100.00" +"Classifications","Design / None(Value/Cards count%)","100.00" +"Classifications","Design / None(Value/Story point%)","100.00" +"Classifications","Vulnerability / None(Value/Cards count%)","100.00" +"Classifications","Vulnerability / None(Value/Story point%)","100.00" +"Classifications","Sprint / Sprint37(Value/Cards count%)","100.00" +"Classifications","Sprint / Sprint37(Value/Story point%)","100.00" +"Classifications","Project / Auto Dora Metrics(Value/Cards count%)","100.00" +"Classifications","Project / Auto Dora Metrics(Value/Story point%)","100.00" +"Classifications","Flagged / None(Value/Cards count%)","100.00" +"Classifications","Flagged / None(Value/Story point%)","100.00" +"Classifications","Fix versions / None(Value/Cards count%)","100.00" +"Classifications","Fix versions / None(Value/Story point%)","100.00" +"Classifications","Priority / Medium(Value/Cards count%)","100.00" +"Classifications","Priority / Medium(Value/Story point%)","100.00" +"Classifications","Partner / None(Value/Cards count%)","100.00" +"Classifications","Partner / None(Value/Story point%)","100.00" +"Classifications","Labels / None(Value/Cards count%)","100.00" +"Classifications","Labels / None(Value/Story point%)","100.00" +"Classifications","Time tracking / None(Value/Cards count%)","100.00" +"Classifications","Time tracking / None(Value/Story point%)","100.00" +"Classifications","Story point estimate / 1.0(Value/Cards count%)","100.00" +"Classifications","Story point estimate / 1.0(Value/Story point%)","100.00" +"Classifications","QA / None(Value/Cards count%)","100.00" +"Classifications","QA / None(Value/Story point%)","100.00" +"Classifications","Feature/Operation / None(Value/Cards count%)","100.00" +"Classifications","Feature/Operation / None(Value/Story point%)","100.00" +"Classifications","Assignee / YinYuan Zhou(Value/Cards count%)","100.00" +"Classifications","Assignee / YinYuan Zhou(Value/Story point%)","100.00" "Rework","Total rework times","0" "Rework","Total rework cards","0" "Rework","Rework cards ratio(Total rework cards/Throughput%)","0" diff --git a/frontend/e2e/pages/metrics/report-step.ts b/frontend/e2e/pages/metrics/report-step.ts index 788b70c489..865fa71a99 100644 --- a/frontend/e2e/pages/metrics/report-step.ts +++ b/frontend/e2e/pages/metrics/report-step.ts @@ -110,7 +110,11 @@ export class ReportStep { readonly deploymentFrequencyTrendIcon: Locator; readonly leadTimeForChangesTrendIcon: Locator; readonly classificationIssueTypeChartSwitchIcon: Locator; + readonly classificationIssueTypeChartSwitchCardCountModel: Locator; + readonly classificationIssueTypeChartSwitchStoryPointsModel: Locator; readonly classificationAssigneeChartSwitchIcon: Locator; + readonly classificationAssigneeChartSwitchCardCountModel: Locator; + readonly classificationAssigneeChartSwitchStoryPointsModel: Locator; readonly leadTimeForChangesExplanationIcon: Locator; readonly deploymentFrequencyExplanationIcon: Locator; @@ -137,7 +141,7 @@ export class ReportStep { this.boardMetricRework = this.page.locator('[data-test-id="Rework"] [data-test-id="report-section"]'); this.boardMetricsDetailVelocityPart = this.page.locator('[data-test-id="Velocity"]'); this.boardMetricsDetailCycleTimePart = this.page.locator('[data-test-id="Cycle Time"]'); - this.boardMetricsDetailClassificationPart = this.page.locator('[data-test-id="Classification"]'); + this.boardMetricsDetailClassificationPart = this.page.getByLabel('classification'); this.boardMetricsDetailReworkTimesPart = this.page.locator('[data-test-id="Rework"]'); this.prLeadTime = this.page.locator('[data-test-id="Lead Time For Changes"] [data-test-id="report-section"]'); @@ -166,8 +170,8 @@ export class ReportStep { this.velocityRows = this.page.getByTestId('Velocity').locator('tbody').getByRole('row'); this.cycleTimeRows = this.page.getByTestId('Cycle Time').locator('tbody').getByRole('row'); this.deploymentFrequencyRows = this.page.getByLabel('Deployment Frequency').locator('tbody').getByRole('row'); - this.classificationRows = this.page.getByTestId('Classification').locator('tbody').getByRole('row'); - this.leadTimeForChangesRows = this.page.getByTestId('Lead Time For Changes').getByRole('row'); + this.classificationRows = this.page.getByLabel('Classification').locator('tbody').getByRole('row'); + this.leadTimeForChangesRows = this.page.getByLabel('Lead Time For Changes').getByRole('row'); this.pipelineChangeFailureRateRows = this.page .getByTestId('Pipeline Change Failure Rate') .locator('tbody') @@ -212,7 +216,19 @@ export class ReportStep { this.reworkTrendIcon = this.reworkTrendContainer.getByLabel('trend down'); this.classificationIssueTypeChartSwitchIcon = this.page.getByLabel('classification issue type switch chart'); + this.classificationIssueTypeChartSwitchCardCountModel = this.page.getByLabel( + 'classification issue type switch card count model button', + ); + this.classificationIssueTypeChartSwitchStoryPointsModel = this.page.getByLabel( + 'classification issue type switch story points model button', + ); this.classificationAssigneeChartSwitchIcon = this.page.getByLabel('classification assignee switch chart'); + this.classificationAssigneeChartSwitchCardCountModel = this.page.getByLabel( + 'classification assignee switch card count model button', + ); + this.classificationAssigneeChartSwitchStoryPointsModel = this.page.getByLabel( + 'classification assignee switch story points model button', + ); this.doraPipelineSelector = this.page.getByLabel('Pipeline Selector').first(); this.leadTimeForChangesTrendContainer = this.page.getByLabel('lead time for changes trend container'); @@ -446,9 +462,10 @@ export class ReportStep { if (restLines.length < currentMetric.lines.length) { currentDataRow = currentDataRow.locator('+tr'); } - const [subtitle, value] = restLines.shift()!; + const [subtitle, value1, value2] = restLines.shift()!; expect(await currentDataRow.getByRole('cell').first().innerHTML()).toEqual(subtitle); - expect(await currentDataRow.getByRole('cell').nth(1).innerHTML()).toEqual(value); + expect(await currentDataRow.getByRole('cell').nth(1).innerHTML()).toEqual(value1); + expect(await currentDataRow.getByRole('cell').nth(2).innerHTML()).toEqual(value2); } } } @@ -696,16 +713,6 @@ export class ReportStep { } } - async checkMetricDownloadData() { - await downloadFileAndCheck(this.page, this.exportMetricData, 'metricData.csv', async (fileDataString) => { - const localCsvFile = fs.readFileSync(path.resolve(__dirname, '../../fixtures/create-new/metric-data.csv')); - const localCsv = parse(localCsvFile); - const downloadCsv = parse(fileDataString); - - expect(localCsv).toStrictEqual(downloadCsv); - }); - } - async checkMetricDownloadDataForMultipleRanges(rangeCount: number, fileNamePrefix?: string) { await this.downloadFileAndCheckForMultipleRanges({ trigger: this.exportMetricData, @@ -857,19 +864,34 @@ export class ReportStep { await expect(this.reworkTrendContainer).not.toBeVisible(); await expect(this.reworkTrendIcon).not.toBeVisible(); } + if (showClassificationIssueTypeChart) { await expect(this.classificationIssueTypeChart).toBeVisible(); await expect(this.classificationIssueTypeChartSwitchIcon).toBeVisible(); + await expect(this.classificationIssueTypeChartSwitchCardCountModel).toBeVisible(); + await expect(this.classificationIssueTypeChartSwitchStoryPointsModel).toBeVisible(); + + await this.classificationIssueTypeChartSwitchCardCountModel.click(); + await this.classificationIssueTypeChartSwitchStoryPointsModel.click(); } else { await expect(this.classificationIssueTypeChart).not.toBeVisible(); await expect(this.classificationIssueTypeChartSwitchIcon).not.toBeVisible(); + await expect(this.classificationIssueTypeChartSwitchCardCountModel).not.toBeVisible(); + await expect(this.classificationIssueTypeChartSwitchStoryPointsModel).not.toBeVisible(); } if (showClassificationAssigneeChart) { await expect(this.classificationAssigneeChart).toBeVisible(); await expect(this.classificationAssigneeChartSwitchIcon).toBeVisible(); + await expect(this.classificationAssigneeChartSwitchCardCountModel).toBeVisible(); + await expect(this.classificationAssigneeChartSwitchStoryPointsModel).toBeVisible(); + + await this.classificationAssigneeChartSwitchCardCountModel.click(); + await this.classificationAssigneeChartSwitchStoryPointsModel.click(); } else { await expect(this.classificationAssigneeChart).not.toBeVisible(); await expect(this.classificationAssigneeChartSwitchIcon).not.toBeVisible(); + await expect(this.classificationAssigneeChartSwitchCardCountModel).not.toBeVisible(); + await expect(this.classificationAssigneeChartSwitchStoryPointsModel).not.toBeVisible(); } } @@ -1095,7 +1117,7 @@ export class ReportStep { const newPage = await this.page.waitForEvent('popup'); const newPageUrl = newPage.url(); - expect(newPageUrl).toContain('https://github.com/au-heartbeat/Heartbeat?tab=readme-ov-file'); // Check if the new page URL contains a specific string + expect(newPageUrl).toContain('https://github.com/au-heartbeat/Heartbeat?tab=readme-ov-file'); await newPage.close(); diff --git a/frontend/src/clients/report/ReportClient.ts b/frontend/src/clients/report/ReportClient.ts index 481c76200c..80a7d48e3f 100644 --- a/frontend/src/clients/report/ReportClient.ts +++ b/frontend/src/clients/report/ReportClient.ts @@ -54,6 +54,7 @@ export class ReportClient extends HttpClient { { fieldName: '', totalCardCount: 1, + storyPoints: 1, classificationInfos: [], }, ], diff --git a/frontend/src/clients/report/dto/response.ts b/frontend/src/clients/report/dto/response.ts index c14254e14f..6f29af4507 100644 --- a/frontend/src/clients/report/dto/response.ts +++ b/frontend/src/clients/report/dto/response.ts @@ -1,4 +1,8 @@ -import { ReportDataWithThreeColumns, ReportDataWithTwoColumns } from '@src/hooks/reportMapper/reportUIDataStructure'; +import { + ReportDataForMultipleValueColumns, + ReportDataWithThreeColumns, + ReportDataWithTwoColumns, +} from '@src/hooks/reportMapper/reportUIDataStructure'; import { Nullable } from '@src/utils/types'; export interface ReportResponseDTO { @@ -63,6 +67,7 @@ export interface ReworkTimeResponse { export interface ClassificationResponse { fieldName: string; totalCardCount: number; + storyPoints: number; classificationInfos: Array; } @@ -158,8 +163,10 @@ export interface PipelineMeanTimeToRecoveryResponse { export interface ClassificationInfoList { name: string; - value: number; + cardCountValue: number; cardCount: number; + storyPointsValue: number; + storyPoints: number; } export interface ReportCallbackResponse { @@ -173,13 +180,14 @@ export interface ReportResponse { rework?: Nullable; cycleTime?: Nullable; reworkList?: ReportDataWithTwoColumns[] | null; - classification?: ReportDataWithThreeColumns[] | null; + classification?: ReportDataForMultipleValueColumns[] | null; deploymentFrequencyList?: ReportDataWithTwoColumns[] | null; pipelineMeanTimeToRecoveryList?: ReportDataWithTwoColumns[] | null; - leadTimeForChangesList?: ReportDataWithThreeColumns[] | null; + leadTimeForChangesList?: ReportDataForMultipleValueColumns[] | null; pipelineChangeFailureRateList?: ReportDataWithTwoColumns[] | null; exportValidityTimeMin?: number | null; classificationCardCount?: ReportDataWithThreeColumns[] | null; + classificationStoryPoints?: ReportDataWithThreeColumns[] | null; } export interface ReportURLsResponse { diff --git a/frontend/src/components/Common/ReportForThreeColumns/index.tsx b/frontend/src/components/Common/ReportDetailTableContainsSubtitle/index.tsx similarity index 57% rename from frontend/src/components/Common/ReportForThreeColumns/index.tsx rename to frontend/src/components/Common/ReportDetailTableContainsSubtitle/index.tsx index b9f62c91b1..e94ca35018 100644 --- a/frontend/src/components/Common/ReportForThreeColumns/index.tsx +++ b/frontend/src/components/Common/ReportDetailTableContainsSubtitle/index.tsx @@ -5,24 +5,25 @@ import { Row, StyledTableCell, } from '@src/components/Common/ReportForTwoColumns/style'; -import { ReportDataWithThreeColumns } from '@src/hooks/reportMapper/reportUIDataStructure'; -import { AVERAGE_FIELD, MetricsTitle, ReportSuffixUnits } from '@src/constants/resources'; +import { ReportDataForMultipleValueColumns } from '@src/hooks/reportMapper/reportUIDataStructure'; import { EmojiWrap, StyledAvatar, StyledTypography } from '@src/constants/emojis/style'; import { getEmojiUrls, removeExtraEmojiName } from '@src/constants/emojis/emoji'; import { ReportSelectionTitle } from '@src/containers/MetricsStep/style'; import { ErrorMessagePrompt } from '@src/components/ErrorMessagePrompt'; import { Table, TableBody, TableHead, TableRow } from '@mui/material'; +import { AVERAGE_FIELD } from '@src/constants/resources'; import { Loading } from '@src/components/Loading'; import { styled } from '@mui/material/styles'; import { Optional } from '@src/utils/types'; import React, { Fragment } from 'react'; import { isEmpty } from 'lodash'; -interface ReportForThreeColumnsProps { +interface ReportDetailTableContainsSubtitleProps { title: string; + units: string[]; fieldName: string; listName: string; - data: Optional; + data: Optional; errorMessage?: string; } @@ -32,14 +33,15 @@ export const StyledLoadingWrapper = styled('div')({ width: '100%', }); -export const ReportForThreeColumns = ({ +export const ReportDetailTableContainsSubtitle = ({ title, + units, fieldName, listName, data, errorMessage, -}: ReportForThreeColumnsProps) => { - const emojiRow = (row: ReportDataWithThreeColumns) => { +}: ReportDetailTableContainsSubtitleProps) => { + const emojiRow = (row: ReportDataForMultipleValueColumns) => { const { name } = row; const emojiUrls: string[] = getEmojiUrls(name); if (name.includes(':') && emojiUrls.length > 0) { @@ -57,35 +59,57 @@ export const ReportForThreeColumns = ({ return {name}; }; + const renderRowValueColumn = (values: string[]) => { + return values.map((it, index) => ( + + {it} + + )); + }; + const renderRows = () => data?.slice(0, data?.length === 2 && data[1]?.name === AVERAGE_FIELD ? 1 : data?.length).map((row) => { if (isEmpty(row.valueList)) { row.valueList = [ { name: '--', - value: '--', + values: ['--', '--'], }, ]; } return ( - - {emojiRow(row)} + + + {emojiRow(row)} + {row.valueList.map((valuesList) => ( - - {valuesList.name} - {valuesList.value} + + + {valuesList.name} + + {renderRowValueColumn(valuesList.values)} ))} ); }); - const getTitleUnit = (title: string) => { - return title === MetricsTitle.LeadTimeForChanges ? ReportSuffixUnits.Hours : ''; - }; - const renderLoading = () => ( <> {!errorMessage && !data && ( @@ -99,12 +123,19 @@ export const ReportForThreeColumns = ({ const renderData = () => ( <> {!errorMessage && data && ( - +
{fieldName} {listName} - {`Value${getTitleUnit(title)}`} + {units.map((it, index) => ( + {`Value${it}`} + ))} {renderRows()} @@ -114,15 +145,13 @@ export const ReportForThreeColumns = ({ ); return ( - <> - - {title} - {errorMessage && } - {renderLoading()} - {renderData()} - - + + {title} + {errorMessage && } + {renderLoading()} + {renderData()} + ); }; -export default ReportForThreeColumns; +export default ReportDetailTableContainsSubtitle; diff --git a/frontend/src/constants/resources.ts b/frontend/src/constants/resources.ts index 01f3fdc5ee..492faa6725 100644 --- a/frontend/src/constants/resources.ts +++ b/frontend/src/constants/resources.ts @@ -1,3 +1,4 @@ +import { ReportResponse } from '@src/clients/report/dto/response'; import { AxiosError } from 'axios'; export enum Calendar { @@ -391,6 +392,8 @@ export enum ReportSuffixUnits { Hours = '(Hours)', DeploymentsPerDay = '(Deployments/Day)', DeploymentsTimes = '(Deployment times)', + ClassificationCardCounts = '/Cards count', + ClassificationStoryPoint = '/Story point', } export const MESSAGE = { @@ -582,7 +585,7 @@ export enum SortingDateRangeText { export const DISABLED_DATE_RANGE_MESSAGE = 'Report generated failed during this period.'; -export const emptyDataMapperDoraChart = (allPipelines: string[], value: string) => { +export const emptyDataMapperDoraChart = (allPipelines: string[], value: string): ReportResponse => { const deploymentFrequencyList = allPipelines.map((it, index) => { return { id: index, @@ -597,7 +600,7 @@ export const emptyDataMapperDoraChart = (allPipelines: string[], value: string) ], }; }); - const devMeanTimeToRecoveryList = allPipelines.map((it, index) => { + const pipelineMeanTimeToRecoveryList = allPipelines.map((it, index) => { return { id: index, name: it, @@ -615,20 +618,20 @@ export const emptyDataMapperDoraChart = (allPipelines: string[], value: string) valueList: [ { name: LEAD_TIME_FOR_CHANGES.PR_LEAD_TIME, - value: value, + values: [value], }, { name: LEAD_TIME_FOR_CHANGES.PIPELINE_LEAD_TIME, - value: value, + values: [value], }, { name: LEAD_TIME_FOR_CHANGES.TOTAL_LEAD_TIME, - value: value, + values: [value], }, ], }; }); - const devChangeFailureRateList = allPipelines.map((it, index) => { + const pipelineChangeFailureRateList = allPipelines.map((it, index) => { return { id: index, name: it, @@ -641,9 +644,9 @@ export const emptyDataMapperDoraChart = (allPipelines: string[], value: string) }); return { deploymentFrequencyList, - devMeanTimeToRecoveryList, + pipelineMeanTimeToRecoveryList, leadTimeForChangesList, - devChangeFailureRateList, + pipelineChangeFailureRateList, exportValidityTimeMin: 0.0005, }; }; diff --git a/frontend/src/containers/ReportStep/BoardMetricsChart/ClassificationChart/index.tsx b/frontend/src/containers/ReportStep/BoardMetricsChart/ClassificationChart/index.tsx index 52bd931cb9..7382379e2f 100644 --- a/frontend/src/containers/ReportStep/BoardMetricsChart/ClassificationChart/index.tsx +++ b/frontend/src/containers/ReportStep/BoardMetricsChart/ClassificationChart/index.tsx @@ -4,6 +4,7 @@ import { stackedBarOptionMapper, } from '@src/containers/ReportStep/ChartOption'; import { ANIMATION_SECONDS, EVERY_FRAME_MILLISECOND, MILLISECONDS_PER_SECOND } from '@src/constants/commons'; +import { ReportDataWithThreeColumns } from '@src/hooks/reportMapper/reportUIDataStructure'; import ChartAndTitleWrapper from '@src/containers/ReportStep/ChartAndTitleWrapper'; import { LABEL_PERCENT } from '@src/containers/ReportStep/BoardMetricsChart'; import { ReportResponse } from '@src/clients/report/dto/response'; @@ -12,6 +13,11 @@ import { showChart } from '@src/containers/ReportStep'; import { ChartType } from '@src/constants/resources'; import { theme } from '@src/theme'; +export enum ClassificationChartModelType { + CardCount = 'cound count', + StoryPoints = 'story points', +} + enum ClassificationChartType { Pie = 'pie', Bar = 'bar', @@ -19,7 +25,12 @@ enum ClassificationChartType { const PERCENTAGE_NUMBER = 100; -function extractClassificationData(classification: string, dateRanges: string[], mappedData: ReportResponse[]) { +function extractClassificationData( + classification: string, + classificationChartModelType: ClassificationChartModelType, + dateRanges: string[], + mappedData: ReportResponse[], +) { const data = mappedData.flatMap((item) => item.classification?.filter((it) => it.name === classification)); const allSubtitle = [ ...new Set( @@ -30,33 +41,32 @@ function extractClassificationData(classification: string, dateRanges: string[], ), ]; const indicators: { data: number[]; name: string; type: string }[] = []; - data .filter((it) => it !== undefined) .map((it) => it!.valueList) .forEach((it) => { allSubtitle.map((subtitle) => { if (it.every((item) => item.name !== subtitle)) { - it.push({ name: subtitle, value: '0.00%' }); + it.push({ name: subtitle, values: ['0.00%', '0.00%'] }); } }); }); - allSubtitle.forEach((item) => { const classificationValue: number[] = data .filter((it) => it !== undefined) .flatMap((it) => it!.valueList) .filter((it) => it.name === item) - .map((it) => parseFloat(it.value)); + .map((it) => + classificationChartModelType === ClassificationChartModelType.CardCount ? it.values[0] : it.values[1], + ) + .map((it) => parseFloat(it)); indicators.push({ data: classificationValue, name: item, type: 'bar' }); }); - const trendInfo = { type: ChartType.Classification }; - return { xAxis: dateRanges, yAxis: { - name: 'Value/Total cards', + name: '', alignTick: false, axisLabel: LABEL_PERCENT, }, @@ -77,27 +87,32 @@ function extractClassificationData(classification: string, dateRanges: string[], }; } -function getTotalCardCounts(mappedData: ReportResponse[], classification: string) { - return mappedData - .flatMap((it) => it.classificationCardCount) +function getTotalValues( + classificationDataByModel: (ReportDataWithThreeColumns | null | undefined)[], + classification: string, +) { + return classificationDataByModel .filter((it) => it?.name === classification) .map((it) => it!.totalCount!) .reduce((res, it) => res + it, 0); } -function extractedValueList(mappedData: ReportResponse[], classification: string) { - return mappedData - .flatMap((it) => it.classificationCardCount) - .filter((it) => it?.name === classification) - .flatMap((it) => it?.valueList); +function extractedValueList( + classificationDataByModel: (ReportDataWithThreeColumns | null | undefined)[], + classification: string, +) { + return classificationDataByModel.filter((it) => it?.name === classification).flatMap((it) => it?.valueList); } -function getAllSubtitles(mappedData: ReportResponse[], classification: string) { - const data = extractedValueList(mappedData, classification); +function getAllSubtitles( + classificationDataByModel: (ReportDataWithThreeColumns | null | undefined)[], + classification: string, +) { + const data = extractedValueList(classificationDataByModel, classification); return [...new Set(data.filter((it) => it !== undefined).map((it) => it!.name))]; } -function getCardCountForSubtitle(data: ({ name: string; value: string } | undefined)[], subtitle: string) { +function getValueForSubtitle(data: ({ name: string; value: string } | undefined)[], subtitle: string) { return data .filter((it) => it !== undefined) .filter((it) => it!.name === subtitle) @@ -106,31 +121,31 @@ function getCardCountForSubtitle(data: ({ name: string; value: string } | undefi }, 0); } -function checkClassificationChartType(classification: string, mappedData: ReportResponse[]) { - const totalCardCounts = getTotalCardCounts(mappedData, classification); - - const data = extractedValueList(mappedData, classification); - +function checkClassificationChartType( + classification: string, + classificationDataByModel: (ReportDataWithThreeColumns | null | undefined)[], +) { + const totalCardCounts = getTotalValues(classificationDataByModel, classification); + const data = extractedValueList(classificationDataByModel, classification); const totalCounts = data.filter((it) => it !== undefined).reduce((res, cardInfo) => res + Number(cardInfo?.value), 0); return totalCounts === totalCardCounts ? ClassificationChartType.Pie : ClassificationChartType.Bar; } -function extractClassificationCardCountsPieData(classification: string, mappedData: ReportResponse[]) { - const totalCardCounts = getTotalCardCounts(mappedData, classification); - - const data = extractedValueList(mappedData, classification); - - const allSubtitle = getAllSubtitles(mappedData, classification); +function extractClassificationValuesPieData( + classification: string, + classificationDataByModel: (ReportDataWithThreeColumns | null | undefined)[], +) { + const totalValues = getTotalValues(classificationDataByModel, classification); + const data = extractedValueList(classificationDataByModel, classification); + const allSubtitle = getAllSubtitles(classificationDataByModel, classification); const indicators: { value: string; name: string }[] = allSubtitle.map((subtitle) => { - const cardCount = getCardCountForSubtitle(data, subtitle); + const value = getValueForSubtitle(data, subtitle); return { - name: `${subtitle}: ${cardCount}`, - value: `${((cardCount * PERCENTAGE_NUMBER) / totalCardCounts).toFixed(2)}`, + name: `${subtitle}: ${value}`, + value: `${((value * PERCENTAGE_NUMBER) / totalValues).toFixed(2)}`, }; }); - const trendInfo = { type: ChartType.Classification }; - return { series: indicators, color: [ @@ -149,19 +164,18 @@ function extractClassificationCardCountsPieData(classification: string, mappedDa }; } -function extractClassificationCardCountsBarData(classification: string, mappedData: ReportResponse[]) { - const totalCardCounts = getTotalCardCounts(mappedData, classification); - - const data = extractedValueList(mappedData, classification); - - const allSubtitle = getAllSubtitles(mappedData, classification); +function extractClassificationCardCountsBarData( + classification: string, + classificationDataByModel: (ReportDataWithThreeColumns | null | undefined)[], +) { + const totalValues = getTotalValues(classificationDataByModel, classification); + const data = extractedValueList(classificationDataByModel, classification); + const allSubtitle = getAllSubtitles(classificationDataByModel, classification); const indicators = allSubtitle.map((subtitle) => { - const cardCount = getCardCountForSubtitle(data, subtitle); - return Number(((cardCount * PERCENTAGE_NUMBER) / totalCardCounts).toFixed(2)); + const value = getValueForSubtitle(data, subtitle); + return Number(((value * PERCENTAGE_NUMBER) / totalValues).toFixed(2)); }); - const trendInfo = { type: ChartType.Classification }; - return { xAxis: { data: allSubtitle, @@ -174,7 +188,7 @@ function extractClassificationCardCountsBarData(classification: string, mappedDa }, yAxis: [ { - name: 'Value/Total cards', + name: '', alignTick: false, axisLabel: LABEL_PERCENT, }, @@ -216,26 +230,34 @@ export const ClassificationChart = ({ dateRanges: string[]; allDateRangeLoadingFinished: boolean; }) => { - const [isFirstIntoClassification, setIsFirstIntoClassification] = useState(true); + const [isShowAnimation, setIsShowAnimation] = useState(true); const [isShowTimePeriodChart, setIsShowTimePeriodChart] = useState(true); const [canSwitchChart, setCanSwitchChart] = useState(true); const [rotate, setRotate] = useState(0); const classificationRef = useRef(null); + const [classificationChartModel, setClassificationChartModel] = useState( + ClassificationChartModelType.CardCount, + ); + let classificationData; let classificationDataOption; if (isShowTimePeriodChart) { - classificationData = extractClassificationData(classification, dateRanges, mappedData); - classificationDataOption = - classificationData && stackedBarOptionMapper(classificationData, true, isFirstIntoClassification); + classificationData = extractClassificationData(classification, classificationChartModel, dateRanges, mappedData); + classificationDataOption = classificationData && stackedBarOptionMapper(classificationData, true, isShowAnimation); } else { - const chartType = checkClassificationChartType(classification, mappedData); + const classificationDataByModel: (ReportDataWithThreeColumns | null | undefined)[] = mappedData.flatMap((it) => + classificationChartModel === ClassificationChartModelType.CardCount + ? it.classificationCardCount + : it.classificationStoryPoints, + ); + const chartType = checkClassificationChartType(classification, classificationDataByModel); if (chartType === ClassificationChartType.Pie) { - classificationData = extractClassificationCardCountsPieData(classification, mappedData); - classificationDataOption = classificationData && pieOptionMapper(classificationData); + classificationData = extractClassificationValuesPieData(classification, classificationDataByModel); + classificationDataOption = classificationData && pieOptionMapper(classificationData, isShowAnimation); } else { - classificationData = extractClassificationCardCountsBarData(classification, mappedData); + classificationData = extractClassificationCardCountsBarData(classification, classificationDataByModel); classificationDataOption = - classificationData && stackedAreaOptionMapper(classificationData, true, isFirstIntoClassification); + classificationData && stackedAreaOptionMapper(classificationData, true, isShowAnimation); } } const isClassificationFinished = @@ -284,12 +306,17 @@ export const ClassificationChart = ({ } const switchChart = () => { if (canSwitchChart) { - setIsFirstIntoClassification(false); + setIsShowAnimation(false); setCanSwitchChart(false); id = window.requestAnimationFrame(animationStep); } }; + const switchModel = (newModel: ClassificationChartModelType) => { + setClassificationChartModel(newModel); + setIsShowAnimation(true); + }; + useEffect(() => { showChart(classificationRef.current, classificationDataOption); }, [classificationRef, classificationDataOption]); @@ -304,6 +331,8 @@ export const ClassificationChart = ({ animationStyle={transition} disabledClickRepeatButton={!canSwitchChart} isShowSwitch + classificationChartModel={classificationChartModel} + clickSwitchClassificationModel={switchModel} /> ); }; diff --git a/frontend/src/containers/ReportStep/ChartAndTitleWrapper/index.tsx b/frontend/src/containers/ReportStep/ChartAndTitleWrapper/index.tsx index 75d3af965a..809f4dda69 100644 --- a/frontend/src/containers/ReportStep/ChartAndTitleWrapper/index.tsx +++ b/frontend/src/containers/ReportStep/ChartAndTitleWrapper/index.tsx @@ -2,11 +2,14 @@ import { ChartTitle, StyledChartAndTitleWrapper, StyledTooltipContent, + SwitchButtonGroup, SwitchIconWrapper, + SwitchModelButton, TrendContainer, TrendIconSpan, TrendTypeIcon, } from '@src/containers/ReportStep/ChartAndTitleWrapper/style'; +import { ClassificationChartModelType } from '@src/containers/ReportStep/BoardMetricsChart/ClassificationChart'; import { CHART_TREND_TIP, ChartType, TrendIcon, TrendType, UP_TREND_IS_BETTER } from '@src/constants/resources'; import TrendingDownSharpIcon from '@mui/icons-material/TrendingDownSharp'; import TrendingUpSharpIcon from '@mui/icons-material/TrendingUpSharp'; @@ -50,6 +53,8 @@ const ChartAndTitleWrapper = forwardRef( clickSwitch, animationStyle = {}, disabledClickRepeatButton = false, + classificationChartModel = ClassificationChartModelType.CardCount, + clickSwitchClassificationModel, }: { trendInfo: ITrendInfo; isLoading: boolean; @@ -58,6 +63,8 @@ const ChartAndTitleWrapper = forwardRef( clickSwitch?: () => void; animationStyle?: object; disabledClickRepeatButton?: boolean; + classificationChartModel?: ClassificationChartModelType; + clickSwitchClassificationModel?: (newModel: ClassificationChartModelType) => void; }, ref: ForwardedRef, ) => { @@ -83,6 +90,14 @@ const ChartAndTitleWrapper = forwardRef( ); + const clickStoryPointsButton = () => { + clickSwitchClassificationModel && clickSwitchClassificationModel(ClassificationChartModelType.StoryPoints); + }; + + const clickCardCountButton = () => { + clickSwitchClassificationModel && clickSwitchClassificationModel(ClassificationChartModelType.CardCount); + }; + return ( )} + {trendInfo.type === ChartType.Classification && ( + + + Value/Cards count + + + Value/Story point + + + )} { + return { + appearance: 'none', + border: 'none', + outline: 'none', + background: 'none', + cursor: 'pointer', + padding: '0.3rem', + backgroundColor: selected ? theme.main.boardChart.classificationModelColor : 'white', + borderRadius: selected ? '0.5rem' : 0, + }; +}); + export const TrendIconSpan = styled('span')({ position: 'relative', fontSize: '1rem', diff --git a/frontend/src/containers/ReportStep/ChartOption.ts b/frontend/src/containers/ReportStep/ChartOption.ts index 1c1158c144..c78738b346 100644 --- a/frontend/src/containers/ReportStep/ChartOption.ts +++ b/frontend/src/containers/ReportStep/ChartOption.ts @@ -305,7 +305,7 @@ export const stackedBarOptionMapper = ( }; }; -export const pieOptionMapper = (props: PieOptionProps, showPercentage: boolean = true) => { +export const pieOptionMapper = (props: PieOptionProps, animation: boolean) => { const series = props.series; return { legend: { @@ -315,11 +315,11 @@ export const pieOptionMapper = (props: PieOptionProps, showPercentage: boolean = orient: 'vertical', }, tooltip: { - valueFormatter: percentageFormatter(showPercentage), + valueFormatter: percentageFormatter(true), trigger: 'item', }, color: props.color, - animation: false, + animation, series: [ { type: 'pie', diff --git a/frontend/src/containers/ReportStep/DoraMetricsChart/index.tsx b/frontend/src/containers/ReportStep/DoraMetricsChart/index.tsx index bd80e921c8..9cc1a0b0a7 100644 --- a/frontend/src/containers/ReportStep/DoraMetricsChart/index.tsx +++ b/frontend/src/containers/ReportStep/DoraMetricsChart/index.tsx @@ -66,7 +66,7 @@ function extractedStackedBarData( ); if (!averageItem) return []; - return averageItem.valueList.map((item) => Number(item.value)); + return averageItem.valueList.map((item) => Number(item.values[0])); }); const leadTimeValues = extractedValues?.map((value) => value![2]); @@ -236,16 +236,7 @@ function isDoraMetricsChartFinish({ type, }: { dateRangeLength: number; - mappedData: ( - | ReportResponse - | { - deploymentFrequencyList: ChartValueSource[]; - pipelineChangeFailureRateList: ChartValueSource[]; - pipelineMeanTimeToRecoveryList: ChartValueSource[]; - exportValidityTimeMin: number; - leadTimeForChangesList: ChartValueSource[]; - } - )[]; + mappedData: ReportResponse[]; type: DORAMetricsChartType; }): boolean { const valueList = mappedData diff --git a/frontend/src/containers/ReportStep/ReportDetail/board.tsx b/frontend/src/containers/ReportStep/ReportDetail/board.tsx index 84e2685a33..4bce008aea 100644 --- a/frontend/src/containers/ReportStep/ReportDetail/board.tsx +++ b/frontend/src/containers/ReportStep/ReportDetail/board.tsx @@ -1,7 +1,7 @@ +import ReportDetailTableContainsSubtitle from '@src/components/Common/ReportDetailTableContainsSubtitle'; +import { MESSAGE, MetricsTitle, ReportSuffixUnits, RequiredData } from '@src/constants/resources'; import { ReportDataWithTwoColumns } from '@src/hooks/reportMapper/reportUIDataStructure'; -import ReportForThreeColumns from '@src/components/Common/ReportForThreeColumns'; import { DetailContainer } from '@src/containers/ReportStep/ReportDetail/style'; -import { MESSAGE, MetricsTitle, RequiredData } from '@src/constants/resources'; import { addNotification } from '@src/context/notification/NotificationSlice'; import ReportForTwoColumns from '@src/components/Common/ReportForTwoColumns'; import { ReportResponseDTO } from '@src/clients/report/dto/response'; @@ -43,9 +43,10 @@ export const BoardDetail = withGoBack(({ data, errorMessage, metrics }: Property {showSectionWith2Columns(MetricsTitle.Velocity, mappedData?.velocityList)} {showSectionWith2Columns(MetricsTitle.CycleTime, mappedData?.cycleTimeList)} {metrics.includes(RequiredData.Classification) && ( - ) => value && ; -const showThreeColumnSection = (title: string, value: Optional) => - value && ; +const showThreeColumnSection = (title: string, value: Optional) => + value && ( + + ); export const DoraDetail = withGoBack(({ data }: Property) => { const mappedData = reportMapper(data); diff --git a/frontend/src/hooks/reportMapper/classification.ts b/frontend/src/hooks/reportMapper/classification.ts index 6ce183bfc4..270bea9603 100644 --- a/frontend/src/hooks/reportMapper/classification.ts +++ b/frontend/src/hooks/reportMapper/classification.ts @@ -1,17 +1,20 @@ -import { ReportDataWithThreeColumns } from '@src/hooks/reportMapper/reportUIDataStructure'; +import { ReportDataForMultipleValueColumns } from '@src/hooks/reportMapper/reportUIDataStructure'; import { ClassificationResponse } from '@src/clients/report/dto/response'; export const classificationMapper = (classification: ClassificationResponse[]) => { - const mappedClassificationValue: ReportDataWithThreeColumns[] = []; + const mappedClassificationValue: ReportDataForMultipleValueColumns[] = []; classification.map((item, index) => { - const pairsValues: { name: string; value: string }[] = []; + const pairsValues: { name: string; values: string[] }[] = []; item.classificationInfos.map((pairItem) => { - pairsValues.push({ name: pairItem.name, value: `${(pairItem.value * 100).toFixed(2)}%` }); + pairsValues.push({ + name: pairItem.name, + values: [`${(pairItem.cardCountValue * 100).toFixed(2)}%`, `${(pairItem.storyPointsValue * 100).toFixed(2)}%`], + }); }); - const classificationValue: ReportDataWithThreeColumns = { + const classificationValue: ReportDataForMultipleValueColumns = { id: index, name: item.fieldName, valueList: pairsValues, diff --git a/frontend/src/hooks/reportMapper/classificationStoryPoints.ts b/frontend/src/hooks/reportMapper/classificationStoryPoints.ts new file mode 100644 index 0000000000..bb3e0e502e --- /dev/null +++ b/frontend/src/hooks/reportMapper/classificationStoryPoints.ts @@ -0,0 +1,21 @@ +import { ReportDataWithThreeColumns } from '@src/hooks/reportMapper/reportUIDataStructure'; +import { ClassificationResponse } from '@src/clients/report/dto/response'; + +export const classificationStoryPointsMapper = ( + classification: ClassificationResponse[], +): ReportDataWithThreeColumns[] => { + return classification.map((it, index) => { + const pairsValues = it.classificationInfos.map((classificationInfo) => { + return { + name: classificationInfo.name, + value: `${classificationInfo.storyPoints}`, + }; + }); + return { + id: index, + name: it.fieldName, + totalCount: it.storyPoints, + valueList: pairsValues, + }; + }); +}; diff --git a/frontend/src/hooks/reportMapper/leadTimeForChanges.ts b/frontend/src/hooks/reportMapper/leadTimeForChanges.ts index 31d9fa153a..4b11cef931 100644 --- a/frontend/src/hooks/reportMapper/leadTimeForChanges.ts +++ b/frontend/src/hooks/reportMapper/leadTimeForChanges.ts @@ -1,3 +1,4 @@ +import { ReportDataForMultipleValueColumns } from '@src/hooks/reportMapper/reportUIDataStructure'; import { LeadTimeForChangesResponse } from '@src/clients/report/dto/response'; export const leadTimeForChangesMapper = ({ @@ -14,18 +15,20 @@ export const leadTimeForChangesMapper = ({ if (name == 'totalDelayTime') return 'Total Lead Time'; }; - const mappedLeadTimeForChangesValue = leadTimeForChangesOfPipelines.map((item, index) => { - return { - id: index, - name: `${item.name}/${item.step}`, - valueList: Object.entries(item) - .slice(-3) - .map(([name, value]) => ({ - name: formatNameDisplay(name) as string, - value: formatDuration(value), - })), - }; - }); + const mappedLeadTimeForChangesValue: ReportDataForMultipleValueColumns[] = leadTimeForChangesOfPipelines.map( + (item, index) => { + return { + id: index, + name: `${item.name}/${item.step}`, + valueList: Object.entries(item) + .slice(-3) + .map(([name, value]) => ({ + name: formatNameDisplay(name) as string, + values: [formatDuration(value)], + })), + }; + }, + ); mappedLeadTimeForChangesValue.push({ id: mappedLeadTimeForChangesValue.length, @@ -34,7 +37,7 @@ export const leadTimeForChangesMapper = ({ .slice(-3) .map(([name, value]) => ({ name: formatNameDisplay(name) as string, - value: formatDuration(value), + values: [formatDuration(value)], })), }); diff --git a/frontend/src/hooks/reportMapper/report.ts b/frontend/src/hooks/reportMapper/report.ts index bbefeb42b8..e686ba5111 100644 --- a/frontend/src/hooks/reportMapper/report.ts +++ b/frontend/src/hooks/reportMapper/report.ts @@ -1,3 +1,4 @@ +import { classificationStoryPointsMapper } from '@src/hooks/reportMapper/classificationStoryPoints'; import { pipelineMeanTimeToRecoveryMapper } from '@src/hooks/reportMapper/devMeanTimeToRecovery'; import { classificationCardCountMapper } from '@src/hooks/reportMapper/classificationCardCount'; import { pipelineChangeFailureRateMapper } from '@src/hooks/reportMapper/devChangeFailureRate'; @@ -43,6 +44,8 @@ export const reportMapper = ({ const classificationCardCount = classificationList && classificationCardCountMapper(classificationList); + const classificationStoryPoints = classificationList && classificationStoryPointsMapper(classificationList); + return { velocityList, cycleTimeList, @@ -56,5 +59,6 @@ export const reportMapper = ({ pipelineChangeFailureRateList, exportValidityTimeMin, classificationCardCount, + classificationStoryPoints, }; }; diff --git a/frontend/src/hooks/reportMapper/reportUIDataStructure.ts b/frontend/src/hooks/reportMapper/reportUIDataStructure.ts index d543218931..85c1f703d1 100644 --- a/frontend/src/hooks/reportMapper/reportUIDataStructure.ts +++ b/frontend/src/hooks/reportMapper/reportUIDataStructure.ts @@ -20,3 +20,13 @@ export interface ReportDataWithThreeColumns { value: string; }[]; } + +export interface ReportDataForMultipleValueColumns { + id: number; + name: string; + totalCount?: number; + valueList: { + name: string; + values: string[]; + }[]; +} diff --git a/frontend/src/theme.ts b/frontend/src/theme.ts index da8ce7febb..f89c680cfc 100644 --- a/frontend/src/theme.ts +++ b/frontend/src/theme.ts @@ -37,6 +37,7 @@ declare module '@mui/material/styles' { lineColorA: string; lineColorB: string; gridColor: string; + classificationModelColor: string; }; backgroundColor: string; color: string; @@ -154,6 +155,7 @@ export const theme = createTheme({ lineColorA: '#163C4D', lineColorB: '#E16A7C', gridColor: '#D9D9D9', + classificationModelColor: '#E8EAF6', }, backgroundColor: indigo[FIVE_HUNDRED], color: '#fff',