diff --git a/frontend/__tests__/constants/fileConfig/fileConfig.test.ts b/frontend/__tests__/constants/fileConfig/fileConfig.test.ts
index 4d275e394d..4cc0dc54c3 100644
--- a/frontend/__tests__/constants/fileConfig/fileConfig.test.ts
+++ b/frontend/__tests__/constants/fileConfig/fileConfig.test.ts
@@ -76,4 +76,34 @@ describe('#fileConfig', () => {
}),
).toEqual(expected);
});
+
+ it('should get default rework value when rework2State and excludeStates are all invalid', () => {
+ const expected = {
+ ...BASIC_NEW_CONFIG,
+ calendarType: CHINA_CALENDAR,
+ reworkTimesSettings: { rework2State: null, excludeStates: [] },
+ };
+ expect(
+ convertToNewFileConfig({
+ ...BASIC_IMPORTED_OLD_CONFIG_FIXTURE,
+ considerHoliday: true,
+ reworkTimesSettings: { rework2State: 'test', excludeStates: ['In Dev'] },
+ }),
+ ).toEqual(expected);
+ });
+
+ it('should filter invalid rework value when excludeStates field is invalid', () => {
+ const expected = {
+ ...BASIC_NEW_CONFIG,
+ calendarType: CHINA_CALENDAR,
+ reworkTimesSettings: { rework2State: 'In Dev', excludeStates: ['Review'] },
+ };
+ expect(
+ convertToNewFileConfig({
+ ...BASIC_IMPORTED_OLD_CONFIG_FIXTURE,
+ considerHoliday: true,
+ reworkTimesSettings: { rework2State: 'In Dev', excludeStates: ['Review', 'test'] },
+ }),
+ ).toEqual(expected);
+ });
});
diff --git a/frontend/__tests__/containers/MetricsStep/CycleTime.test.tsx b/frontend/__tests__/containers/MetricsStep/CycleTime.test.tsx
index f46a9d10ec..52f70410c1 100644
--- a/frontend/__tests__/containers/MetricsStep/CycleTime.test.tsx
+++ b/frontend/__tests__/containers/MetricsStep/CycleTime.test.tsx
@@ -269,7 +269,7 @@ describe('CycleTime', () => {
const inputElements = screen.getAllByRole('combobox');
const selectedInputValue = inputElements.map((option) => option.getAttribute('value'))[0];
- expect(selectedInputValue).toBe('Review');
+ expect(selectedInputValue).toBe('Testing');
await waitFor(() => expect(mockedUseAppDispatch).toHaveBeenCalledWith(saveDoneColumn([])));
});
});
@@ -402,7 +402,7 @@ describe('CycleTime', () => {
const inputElements = screen.getAllByRole('combobox');
const selectedInputValue = inputElements.map((option) => option.getAttribute('value'))[0];
- expect(selectedInputValue).toBe('Review');
+ expect(selectedInputValue).toBe('Testing');
expect(mockedUseAppDispatch).not.toHaveBeenCalledWith(saveDoneColumn([]));
});
});
diff --git a/frontend/__tests__/containers/MetricsStep/reworkSettings.test.tsx b/frontend/__tests__/containers/MetricsStep/reworkSettings.test.tsx
index 320383f7dd..83e836fdfc 100644
--- a/frontend/__tests__/containers/MetricsStep/reworkSettings.test.tsx
+++ b/frontend/__tests__/containers/MetricsStep/reworkSettings.test.tsx
@@ -7,6 +7,7 @@ import {
} from '../../fixtures';
import { METRICS_CONSTANTS, REWORK_TIME_LIST } from '@src/constants/resources';
import { act, render, screen, waitFor, within } from '@testing-library/react';
+import { updateCycleTimeSettings } from '@src/context/Metrics/metricsSlice';
import ReworkSettings from '@src/containers/MetricsStep/ReworkSettings';
import { setupStore } from '../../utils/setupStoreUtil';
import userEvent from '@testing-library/user-event';
@@ -19,6 +20,29 @@ jest.mock('@src/hooks/useAppDispatch', () => ({
}));
const store = setupStore();
+const mockCycleTimeSettings1 = [
+ {
+ column: 'TODO',
+ status: 'TODO',
+ value: 'To do',
+ },
+ {
+ column: 'Doing',
+ status: 'DOING',
+ value: 'In Dev',
+ },
+ {
+ column: 'Blocked',
+ status: 'BLOCKED',
+ value: 'Block',
+ },
+ {
+ column: 'Done',
+ status: 'DONE',
+ value: 'Done',
+ },
+];
+const mockCycleTimeSettings2 = mockCycleTimeSettings1.filter((item) => item.value !== 'Done');
describe('reworkSetting', () => {
const setup = () =>
@@ -27,6 +51,11 @@ describe('reworkSetting', () => {
,
);
+
+ beforeEach(() => {
+ store.dispatch(updateCycleTimeSettings(mockCycleTimeSettings1));
+ });
+
afterEach(() => {
jest.clearAllMocks();
});
@@ -100,7 +129,7 @@ describe('reworkSetting', () => {
await userEvent.click(stepsListBox2.getByText(ALL));
});
await waitFor(async () => {
- REWORK_TIME_LIST.slice(1).forEach((value) => {
+ [...new Set(mockCycleTimeSettings1.map((item) => item.value))].slice(1).forEach((value) => {
expect(getByRole('button', { name: value })).toBeInTheDocument();
});
});
@@ -109,16 +138,41 @@ describe('reworkSetting', () => {
await userEvent.click(stepsListBox2.getByText(ALL));
});
await waitFor(() => {
- REWORK_TIME_LIST.forEach((value) => {
+ [...new Set(mockCycleTimeSettings1.map((item) => item.value))].slice(1).forEach((value) => {
expect(queryByRole('button', { name: value })).not.toBeInTheDocument();
});
});
await act(async () => {
- await userEvent.click(stepsListBox2.getByText(REWORK_TIME_LIST[1]));
+ await userEvent.click(stepsListBox2.getByText(REWORK_TIME_LIST[3]));
});
await waitFor(async () => {
- expect(getByRole('button', { name: REWORK_TIME_LIST[1] })).toBeInTheDocument();
+ expect(getByRole('button', { name: REWORK_TIME_LIST[3] })).toBeInTheDocument();
+ });
+ });
+
+ it('should get correct value when board mappings have not done value', async () => {
+ store.dispatch(updateCycleTimeSettings(mockCycleTimeSettings2));
+ const { getByRole, getAllByRole } = setup();
+ await act(async () => {
+ await userEvent.click(getAllByRole('button', { name: LIST_OPEN })[0]);
+ });
+ const stepsListBox1 = within(getByRole('listbox'));
+ await act(async () => {
+ await userEvent.click(stepsListBox1.getByText(METRICS_CONSTANTS.todoValue));
+ });
+ await act(async () => {
+ await userEvent.click(getAllByRole('button', { name: LIST_OPEN })[1]);
+ });
+ const stepsListBox2 = within(getByRole('listbox'));
+
+ await act(async () => {
+ await userEvent.click(stepsListBox2.getByText(ALL));
+ });
+ await waitFor(async () => {
+ [...new Set(mockCycleTimeSettings2.map((item) => item.value))].slice(1).forEach((value) => {
+ expect(getByRole('button', { name: value })).toBeInTheDocument();
+ });
});
});
});
diff --git a/frontend/__tests__/hooks/useVerifyPipelineToolEffect.test.tsx b/frontend/__tests__/hooks/useVerifyPipelineToolEffect.test.tsx
index c75bdab78f..f8e8bd4da1 100644
--- a/frontend/__tests__/hooks/useVerifyPipelineToolEffect.test.tsx
+++ b/frontend/__tests__/hooks/useVerifyPipelineToolEffect.test.tsx
@@ -17,6 +17,10 @@ jest.mock('react-redux', () => ({
describe('use verify pipelineTool state', () => {
it('should return empty error message when call verify feature given client returns 204', async () => {
+ pipelineToolClient.verify = jest.fn().mockResolvedValue({
+ code: HttpStatusCode.NoContent,
+ });
+
const { result } = renderHook(() => useVerifyPipelineToolEffect());
act(() => {
diff --git a/frontend/src/constants/fileConfig.ts b/frontend/src/constants/fileConfig.ts
index 33da65d358..12c440e192 100644
--- a/frontend/src/constants/fileConfig.ts
+++ b/frontend/src/constants/fileConfig.ts
@@ -1,5 +1,5 @@
+import { CALENDAR, REWORK_TIME_LIST } from '@src/constants/resources';
import { IReworkConfig } from '@src/context/Metrics/metricsSlice';
-import { CALENDAR } from '@src/constants/resources';
export interface OldFileConfig {
projectName: string;
@@ -36,7 +36,7 @@ export interface OldFileConfig {
deployment?: OldConfigSetting[];
leadTime?: OldConfigSetting[];
pipelineCrews?: string[];
- reworkTimesSettings: IReworkConfig;
+ reworkTimesSettings?: IReworkConfig;
}
interface OldConfigSetting {
@@ -89,9 +89,23 @@ export interface NewFileConfig {
deployment?: NewConfigSetting[];
leadTime?: NewConfigSetting[];
pipelineCrews?: string[];
- reworkTimesSettings: IReworkConfig;
+ reworkTimesSettings?: IReworkConfig;
}
+const filterExcludeReworkStatus = (reworkTimesSettings: IReworkConfig | undefined) => {
+ if (!reworkTimesSettings) return;
+ const rework2State = REWORK_TIME_LIST.includes(reworkTimesSettings?.rework2State as string)
+ ? reworkTimesSettings.rework2State
+ : null;
+ const excludeStates = reworkTimesSettings?.excludeStates.filter((value) => {
+ return REWORK_TIME_LIST.includes(value);
+ });
+ return {
+ rework2State,
+ excludeStates: rework2State ? excludeStates : [],
+ };
+};
+
export const convertToNewFileConfig = (fileConfig: OldFileConfig | NewFileConfig): NewFileConfig => {
if ('considerHoliday' in fileConfig) {
const {
@@ -138,7 +152,7 @@ export const convertToNewFileConfig = (fileConfig: OldFileConfig | NewFileConfig
pipelineCrews,
cycleTime,
doneStatus,
- reworkTimesSettings,
+ reworkTimesSettings: filterExcludeReworkStatus(reworkTimesSettings),
classification: classifications,
deployment: deployment?.map((item, index) => ({
id: index,
@@ -149,5 +163,5 @@ export const convertToNewFileConfig = (fileConfig: OldFileConfig | NewFileConfig
})),
};
}
- return fileConfig;
+ return { ...fileConfig, reworkTimesSettings: filterExcludeReworkStatus(fileConfig.reworkTimesSettings) };
};
diff --git a/frontend/src/constants/resources.ts b/frontend/src/constants/resources.ts
index 2d5c3bf098..4b902de48e 100644
--- a/frontend/src/constants/resources.ts
+++ b/frontend/src/constants/resources.ts
@@ -161,9 +161,9 @@ export const CYCLE_TIME_LIST = [
METRICS_CONSTANTS.analysisValue,
METRICS_CONSTANTS.inDevValue,
METRICS_CONSTANTS.blockValue,
+ METRICS_CONSTANTS.reviewValue,
METRICS_CONSTANTS.waitingValue,
METRICS_CONSTANTS.testingValue,
- METRICS_CONSTANTS.reviewValue,
METRICS_CONSTANTS.doneValue,
];
diff --git a/frontend/src/containers/MetricsStep/Advance/Advance.tsx b/frontend/src/containers/MetricsStep/Advance/Advance.tsx
index 14681bf1b1..aef862eeda 100644
--- a/frontend/src/containers/MetricsStep/Advance/Advance.tsx
+++ b/frontend/src/containers/MetricsStep/Advance/Advance.tsx
@@ -1,6 +1,6 @@
import { selectAdvancedSettings, updateAdvancedSettings } from '@src/context/Metrics/metricsSlice';
+import { AdvancedContainer, AdvancedForm, AdvancedTitleContainer, AdvancedWrapper } from './style';
import { ItemCheckbox, TitleAndTooltipContainer, TooltipContainer } from '../CycleTime/style';
-import { AdvancedContainer, AdvancedForm, AdvancedTitleContainer } from './style';
import HelpOutlineOutlinedIcon from '@mui/icons-material/HelpOutlineOutlined';
import { StyledLink } from '@src/containers/MetricsStep/style';
import { useAppDispatch } from '@src/hooks/useAppDispatch';
@@ -82,21 +82,23 @@ export const Advance = () => {
{open && (
<>
-
- {fields.map(({ key, col, value }, index) => (
- updateField(key, e.target.value)}
- placeholder={'Customized filed key'}
- />
- ))}
-
+
+
+ {fields.map(({ key, col, value }, index) => (
+ updateField(key, e.target.value)}
+ placeholder={'Customized filed key'}
+ />
+ ))}
+
+
>
)}
>
diff --git a/frontend/src/containers/MetricsStep/Advance/style.tsx b/frontend/src/containers/MetricsStep/Advance/style.tsx
index 7f6c383fcd..5a834de518 100644
--- a/frontend/src/containers/MetricsStep/Advance/style.tsx
+++ b/frontend/src/containers/MetricsStep/Advance/style.tsx
@@ -10,17 +10,31 @@ export const AdvancedTitleContainer = styled.div({
export const AdvancedContainer = styled('div')({
display: 'flex',
- margin: '1rem 0 0.375rem',
+ margin: '1rem 0 1.25rem',
});
export const AdvancedForm = styled('form')({
display: 'grid',
gridTemplateColumns: 'repeat(2, 1fr)',
gap: '1rem',
- marginTop: '1rem',
marginBottom: '1rem',
width: '100%',
[theme.breakpoints.down('md')]: {
gridTemplateColumns: '1fr',
},
});
+
+export const AdvancedWrapper = styled('div')`
+ position: relative;
+ width: 100%;
+ border: ${theme.main.cardBorder};
+ box-shadow: none;
+ margin-bottom: 1rem;
+ line-height: 2rem;
+ boarder-radius: 0.25rem;
+ padding: 2.5%;
+ box-sizing: border-box;
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+`;
diff --git a/frontend/src/containers/MetricsStep/CycleTime/Table/index.tsx b/frontend/src/containers/MetricsStep/CycleTime/Table/index.tsx
index 912b314fe8..bc91ed6375 100644
--- a/frontend/src/containers/MetricsStep/CycleTime/Table/index.tsx
+++ b/frontend/src/containers/MetricsStep/CycleTime/Table/index.tsx
@@ -10,6 +10,7 @@ import {
saveDoneColumn,
selectMetricsContent,
setCycleTimeSettingsType,
+ updateReworkTimesSettings,
} from '@src/context/Metrics/metricsSlice';
import {
StyledRadioGroup,
@@ -56,6 +57,7 @@ const CycleTimeTable = () => {
);
isColumnAsKey && resetRealDoneColumn(name, value);
dispatch(updateCycleTimeSettings(newCycleTimeSettings));
+ dispatch(updateReworkTimesSettings({ excludeStates: [], rework2State: null }));
},
[cycleTimeSettings, dispatch, isColumnAsKey, resetRealDoneColumn],
);
diff --git a/frontend/src/containers/MetricsStep/ReworkSettings/index.tsx b/frontend/src/containers/MetricsStep/ReworkSettings/index.tsx
index 6708b10316..78f8f37883 100644
--- a/frontend/src/containers/MetricsStep/ReworkSettings/index.tsx
+++ b/frontend/src/containers/MetricsStep/ReworkSettings/index.tsx
@@ -1,4 +1,8 @@
-import { selectReworkTimesSettings, updateReworkTimesSettings } from '@src/context/Metrics/metricsSlice';
+import {
+ selectReworkTimesSettings,
+ updateReworkTimesSettings,
+ selectCycleTimeSettings,
+} from '@src/context/Metrics/metricsSlice';
import { ReworkDialog } from '@src/containers/MetricsStep/ReworkSettings/ReworkDialog';
import { MetricsSettingTitle } from '@src/components/Common/MetricsSettingTitle';
import { METRICS_CONSTANTS, REWORK_TIME_LIST } from '@src/constants/resources';
@@ -8,26 +12,39 @@ import { ReworkHeaderWrapper, ReworkSettingsWrapper } from './style';
import { StyledLink } from '@src/containers/MetricsStep/style';
import { useAppDispatch, useAppSelector } from '@src/hooks';
import { SingleSelection } from './SingleSelection';
-import React, { useState } from 'react';
+import React, { useState, useMemo } from 'react';
function ReworkSettings() {
const [isShowDialog, setIsShowDialog] = useState(false);
const reworkTimesSettings = useAppSelector(selectReworkTimesSettings);
+ const cycleTimeSettings = useAppSelector(selectCycleTimeSettings);
const dispatch = useAppDispatch();
+ const boardingMappingStatus = useMemo(() => {
+ return [...new Set(cycleTimeSettings.map((item) => item.value))];
+ }, [cycleTimeSettings]);
- const MultiOptions = reworkTimesSettings.rework2State
+ const boardingMappingHasDoneStatus = boardingMappingStatus.includes(METRICS_CONSTANTS.doneValue);
+ const allStateIsEmpty = boardingMappingStatus.every((value) => value === METRICS_CONSTANTS.cycleTimeEmptyStr);
+ const onlyDoneStateSelected = boardingMappingHasDoneStatus && boardingMappingStatus.length === 2;
+ const singleOptions = boardingMappingStatus
+ .filter((item) => item !== METRICS_CONSTANTS.doneValue && item !== METRICS_CONSTANTS.cycleTimeEmptyStr)
+ .sort((a, b) => {
+ return REWORK_TIME_LIST.indexOf(a) - REWORK_TIME_LIST.indexOf(b);
+ });
+
+ const multiOptions = reworkTimesSettings.rework2State
? [
- ...REWORK_TIME_LIST.slice(REWORK_TIME_LIST.indexOf(reworkTimesSettings.rework2State) + 1),
- METRICS_CONSTANTS.doneValue,
+ ...singleOptions.slice(singleOptions.indexOf(reworkTimesSettings.rework2State as string) + 1),
+ ...(boardingMappingHasDoneStatus ? [METRICS_CONSTANTS.doneValue] : []),
]
: [];
- const isAllSelected = MultiOptions.length > 0 && reworkTimesSettings.excludeStates.length === MultiOptions.length;
+ const isAllSelected = multiOptions.length > 0 && reworkTimesSettings.excludeStates.length === multiOptions.length;
const handleReworkSettingsChange = (_: React.SyntheticEvent, value: string[]) => {
let selectValue = value;
if (value[value.length - 1] === 'All') {
- selectValue = reworkTimesSettings.excludeStates.length === MultiOptions.length ? [] : MultiOptions;
+ selectValue = reworkTimesSettings.excludeStates.length === multiOptions.length ? [] : multiOptions;
}
dispatch(updateReworkTimesSettings({ ...reworkTimesSettings, excludeStates: selectValue }));
};
@@ -50,28 +67,32 @@ function ReworkSettings() {
How to setup
-
-
- dispatch(updateReworkTimesSettings({ excludeStates: [], rework2State: newValue }))
- }
- />
-
-
+ {allStateIsEmpty || onlyDoneStateSelected ? (
+ <>>
+ ) : (
+
+
+ dispatch(updateReworkTimesSettings({ excludeStates: [], rework2State: newValue }))
+ }
+ />
+
+
+ )}
>
);
}
diff --git a/frontend/src/containers/MetricsStepper/index.tsx b/frontend/src/containers/MetricsStepper/index.tsx
index b6e4397786..8ba975f132 100644
--- a/frontend/src/containers/MetricsStepper/index.tsx
+++ b/frontend/src/containers/MetricsStepper/index.tsx
@@ -78,6 +78,8 @@ const MetricsStepper = () => {
requiredData.includes(REQUIRED_DATA.CLASSIFICATION) ||
requiredData.includes(REQUIRED_DATA.VELOCITY);
const isCycleTimeSettingsVerified = cycleTimeSettings.some((e) => e.value === DONE);
+ const boardingMappingStatus = [...new Set(cycleTimeSettings.map((item) => item.value))];
+ const onlyDoneStateSelected = isCycleTimeSettingsVerified && boardingMappingStatus.length === 2;
const isShowClassificationSetting = requiredData.includes(REQUIRED_DATA.CLASSIFICATION);
const isShowReworkSettings = requiredData.includes(REQUIRED_DATA.REWORK_TIMES);
const isClassificationSettingVerified = metricsConfig.targetFields.some((item) => item.flag);
@@ -135,7 +137,7 @@ const MetricsStepper = () => {
{ isShow: isShowDeploymentFrequency, isValid: isDeploymentFrequencyValid },
{ isShow: isShowCycleTimeSettings, isValid: isCycleTimeSettingsVerified },
{ isShow: isShowClassificationSetting, isValid: isClassificationSettingVerified },
- { isShow: isShowReworkSettings, isValid: isRework2StateSelected },
+ { isShow: isShowReworkSettings, isValid: isRework2StateSelected || onlyDoneStateSelected },
];
const activeNextButtonValidityOptions = nextButtonValidityOptions.filter(({ isShow }) => isShow);
activeNextButtonValidityOptions.every(({ isValid }) => isValid)
@@ -165,6 +167,7 @@ const MetricsStepper = () => {
isClassificationSettingVerified,
isRework2StateSelected,
isShowReworkSettings,
+ onlyDoneStateSelected,
]);
const filterMetricsConfig = (metricsConfig: savedMetricsSettingState) => {