Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ADM-927:[frontend]feat: add metrics board and pipeline failed status handle logic #1410

Merged
merged 28 commits into from
Apr 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
3109db5
ADM-927:[frontend]feat: add retry button
PengxiWPix Apr 17, 2024
b242616
ADM-927:[frontend]feat: http client handle 5xx error with special route
weiraneve Apr 17, 2024
f28d0b3
ADM-927:[frontend]feat: add failed status for retry
PengxiWPix Apr 17, 2024
5b5e2ef
ADM-927:[frontend]feat: add notification pop up when partial failed
PengxiWPix Apr 17, 2024
8f316a2
ADM-927:[frontend]feat: implemented all no cards and all 4xx error cases
PengxiWPix Apr 17, 2024
98f077f
ADM-927:[frontend]feat: map correct api result to board
PengxiWPix Apr 17, 2024
d8eba66
ADM-927:[frontend]refactor: restore the http client handle 500 logic
weiraneve Apr 18, 2024
a968268
ADM-927:[frontend]fix: refactor from cr comments
PengxiWPix Apr 18, 2024
b46ccb9
ADM-927:[frontend]refactor: adapt code style
weiraneve Apr 18, 2024
c75b29a
ADM-927:[frontend]refactor: remove retry for board info
weiraneve Apr 18, 2024
4aa0d77
ADM-927:[frontend]feat: add board info failed status ability
weiraneve Apr 19, 2024
a533f53
ADM-927:[frontend]refactor: adapt all no cards
weiraneve Apr 22, 2024
2c847b6
ADM-927:[frontend]refactor: move board info response filter and map t…
weiraneve Apr 22, 2024
d13e741
ADM-927:[frontend]test: fix test
weiraneve Apr 22, 2024
c82dc3a
ADM-927:[frontend]refactor: rename status and remove unnecessary vari…
weiraneve Apr 22, 2024
d9f6314
ADM-927:[frontend]feat: fixed test coverage
PengxiWPix Apr 23, 2024
edf648e
ADM-927:[frontend]fix: fix coverage to 100 by delete useless code
PengxiWPix Apr 23, 2024
0701d12
ADM-927:[frontend]feat: add partial error popup for metrics step
weiraneve Apr 25, 2024
eb6bbc7
ADM-927:[frontend]fix: combined two tests using mockImplementation
PengxiWPix Apr 25, 2024
cb3dc79
ADM-927:[frontend]fix: use and logic for error code mapping
weiraneve Apr 25, 2024
57ccc62
ADM-927:[frontend]fix: fix code space
weiraneve Apr 25, 2024
69d699f
ADM-927:[frontend]refactor: refactor pipeline info request error title
weiraneve Apr 25, 2024
6c74104
ADM-927:[frontend]test: add pipeline failed status test
weiraneve Apr 25, 2024
fae697f
ADM-927:[frontend]test: rename test
weiraneve Apr 25, 2024
b41eda3
Merge branch 'main' into ADM-927
guzhongren Apr 25, 2024
eb200b1
ADM-927:[frontend]test: rename test
weiraneve Apr 26, 2024
6cfd081
Merge branch 'main' into ADM-927
weiraneve Apr 26, 2024
0584890
Merge branch 'main' into ADM-927
weiraneve Apr 26, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions frontend/__tests__/client/PipelineToolClient.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,22 +79,22 @@ describe('PipelineToolClient', () => {
},
{
code: HttpStatusCode.BadRequest,
errorTitle: 'Invalid input!',
errorTitle: 'Failed to get Pipeline configuration!',
weiraneve marked this conversation as resolved.
Show resolved Hide resolved
errorMessage,
},
{
code: HttpStatusCode.Unauthorized,
errorTitle: 'Unauthorized request!',
errorTitle: 'Failed to get Pipeline configuration!',
errorMessage,
},
{
code: HttpStatusCode.Forbidden,
errorTitle: 'Forbidden request!',
errorTitle: 'Failed to get Pipeline configuration!',
errorMessage,
},
{
code: HttpStatusCode.NotFound,
errorTitle: 'Not found!',
errorTitle: 'Failed to get Pipeline configuration!',
errorMessage,
},
];
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { render, waitFor } from '@testing-library/react';
import { setupStore } from '@test/utils/setupStoreUtil';
import { Provider } from 'react-redux';

import { PipelineMetricSelection } from '@src/containers/MetricsStep/DeploymentFrequencySettings/PipelineMetricSelection';
import { IPipelineConfig, updateShouldGetPipelineConfig } from '@src/context/Metrics/metricsSlice';
import { addNotification } from '@src/context/notification/NotificationSlice';
import { METRICS_DATA_FAIL_STATUS } from '@src/constants/commons';
import { PIPELINE_SETTING_TYPES } from '@test/fixtures';

const store = setupStore();
let stepFailStatus = METRICS_DATA_FAIL_STATUS.NOT_FAILED;

jest.mock('@src/context/notification/NotificationSlice', () => ({
...jest.requireActual('@src/context/notification/NotificationSlice'),
addNotification: jest.fn().mockReturnValue({ type: 'ADD_NEW_NOTIFICATION' }),
PengxiWPix marked this conversation as resolved.
Show resolved Hide resolved
}));

jest.mock('@src/hooks/useGetMetricsStepsEffect', () => ({
...jest.requireActual('@src/hooks/useGetMetricsStepsEffect'),

useGetMetricsStepsEffect: jest.fn().mockImplementation(() => {
return {
stepFailedStatus: stepFailStatus,
};
}),
}));

describe('PipelineMetricSelection', () => {
const deploymentFrequencySetting = {
id: 0,
organization: '',
pipelineName: '',
step: '',
branches: [],
};
const mockHandleClickRemoveButton = jest.fn();
const mockUpdatePipeline = jest.fn();
const mockSetLoadingCompletedNumber = jest.fn();

const setup = async (
deploymentFrequencySetting: IPipelineConfig,
isShowRemoveButton: boolean,
isDuplicated: boolean,
) => {
store.dispatch(updateShouldGetPipelineConfig(true));
return render(
<Provider store={store}>
<PipelineMetricSelection
type={PIPELINE_SETTING_TYPES.DEPLOYMENT_FREQUENCY_SETTINGS_TYPE}
pipelineSetting={deploymentFrequencySetting}
isShowRemoveButton={isShowRemoveButton}
onRemovePipeline={mockHandleClickRemoveButton}
onUpdatePipeline={mockUpdatePipeline}
isDuplicated={isDuplicated}
isInfoLoading={false}
totalPipelineNumber={2}
setLoadingCompletedNumber={mockSetLoadingCompletedNumber}
/>
</Provider>,
);
};

beforeEach(() => {
jest.clearAllMocks();
});

it('should show 4xx popup when call pipeline step to get partial 4xx error', async () => {
stepFailStatus = METRICS_DATA_FAIL_STATUS.PARTIAL_FAILED_4XX;
await setup(deploymentFrequencySetting, true, false);

await waitFor(() => {
expect(addNotification).toHaveBeenCalled();
});
});

it('should show timeout popup when call pipeline step to get partial timeout error', async () => {
stepFailStatus = METRICS_DATA_FAIL_STATUS.PARTIAL_FAILED_TIMEOUT;
await setup(deploymentFrequencySetting, true, false);

await waitFor(() => {
expect(addNotification).toHaveBeenCalled();
});
});
});
31 changes: 30 additions & 1 deletion frontend/__tests__/containers/MetricsStep/MetricsStep.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,6 @@ describe('MetricsStep', () => {
{ key: 'done', value: { name: 'Done', statuses: ['PRE-DONE,', 'DONE', 'CANCEL'] } },
];

store.dispatch(updateShouldGetBoardConfig(true));
store.dispatch(updateMetrics(REQUIRED_DATA_LIST));
store.dispatch(updateCycleTimeSettings(cycleTimeSettingsWithTwoDoneValue));
store.dispatch(saveDoneColumn(doneColumn));
Expand Down Expand Up @@ -286,6 +285,7 @@ describe('MetricsStep', () => {
});

it('should be render no card container when get board card when no data', async () => {
store.dispatch(updateShouldGetBoardConfig(true));
server.use(
rest.post(MOCK_BOARD_INFO_URL, (_, res, ctx) => {
return res(ctx.status(HttpStatusCode.Ok));
Expand All @@ -304,7 +304,35 @@ describe('MetricsStep', () => {
).toBeInTheDocument();
});

it('should be render failed message container when get 4xx error', async () => {
store.dispatch(updateShouldGetBoardConfig(true));
server.use(
rest.post(MOCK_BOARD_INFO_URL, (_, res, ctx) => {
return res(ctx.status(HttpStatusCode.BadRequest));
}),
);

setup();

await waitFor(() => {
expect(screen.getByText('Failed to get Board configuration!')).toBeInTheDocument();
weiraneve marked this conversation as resolved.
Show resolved Hide resolved
});
expect(screen.getByText('Please go back to the previous page and check your board info!')).toBeInTheDocument();
});

it('should be render popup when get partial 4xx error', async () => {
store.dispatch(updateShouldGetBoardConfig(true));

setup();

await waitFor(() => {
expect(screen.getByText('Failed to get Board configuration!')).toBeInTheDocument();
weiraneve marked this conversation as resolved.
Show resolved Hide resolved
});
expect(screen.getByText('Please go back to the previous page and check your board info!')).toBeInTheDocument();
});

it('should be render form container when got board card success', async () => {
store.dispatch(updateShouldGetBoardConfig(true));
server.use(
rest.post(MOCK_BOARD_INFO_URL, (_, res, ctx) => {
return res(
Expand Down Expand Up @@ -374,6 +402,7 @@ describe('MetricsStep', () => {
});

it('should show retry button when call get info timeout', async () => {
store.dispatch(updateShouldGetBoardConfig(true));
server.use(
rest.post(MOCK_BOARD_INFO_URL, (_, res) => {
return res.networkError('NETWORK_TIMEOUT');
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { render, waitFor } from '@testing-library/react';
import { setupStore } from '../../utils/setupStoreUtil';
import MetricsStep from '@src/containers/MetricsStep';
import { Provider } from 'react-redux';

import { addNotification } from '@src/context/notification/NotificationSlice';
import { METRICS_DATA_FAIL_STATUS } from '@src/constants/commons';

let store = setupStore();
const setup = () =>
render(
<Provider store={store}>
<MetricsStep />
</Provider>,
);

jest.mock('@src/context/notification/NotificationSlice', () => ({
...jest.requireActual('@src/context/notification/NotificationSlice'),
addNotification: jest.fn().mockReturnValue({ type: 'ADD_NEW_NOTIFICATION' }),
}));

let boardInfoFailStatus = METRICS_DATA_FAIL_STATUS.NOT_FAILED;

jest.mock('@src/hooks/useGetBoardInfo', () => ({
...jest.requireActual('@src/hooks/useGetBoardInfo'),

useGetBoardInfoEffect: jest.fn().mockImplementation(() => {
return {
boardInfoFailedStatus: boardInfoFailStatus,
};
}),
}));

describe('MetricsStep', () => {
beforeEach(() => {
store = setupStore();
});

afterEach(() => {
jest.clearAllMocks();
});

it('should show 4xx popup when call get partial 4xx error', async () => {
boardInfoFailStatus = METRICS_DATA_FAIL_STATUS.PARTIAL_FAILED_4XX;
setup();

await waitFor(() => {
expect(addNotification).toHaveBeenCalled();
});
});

it('should show no cards popup when call get partial no cards error', async () => {
boardInfoFailStatus = METRICS_DATA_FAIL_STATUS.PARTIAL_FAILED_NO_CARDS;
setup();

await waitFor(() => {
expect(addNotification).toHaveBeenCalled();
});
});
});
128 changes: 120 additions & 8 deletions frontend/__tests__/hooks/useGetBoardInfo.test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { MOCK_BOARD_INFO_URL, FAKE_TOKEN, FAKE_DATE_EARLIER, FAKE_DATE_LATER } from '@test/fixtures';
import { AXIOS_REQUEST_ERROR_CODE } from '@src/constants/resources';
import { useGetBoardInfoEffect } from '@src/hooks/useGetBoardInfo';
import { renderHook, act, waitFor } from '@testing-library/react';
import { setupServer } from 'msw/node';
Expand Down Expand Up @@ -46,23 +47,23 @@ describe('use get board info', () => {
],
[
HttpStatusCode.BadRequest,
'Failed to get the board configuration!',
'Please go back to the previous page to check your board info, or change your time range!',
'Failed to get Board configuration!',
weiraneve marked this conversation as resolved.
Show resolved Hide resolved
'Please go back to the previous page and check your board info!',
],
[
HttpStatusCode.Unauthorized,
'Failed to get the board configuration!',
'Please go back to the previous page to check your board info, or change your time range!',
'Failed to get Board configuration!',
'Please go back to the previous page and check your board info!',
],
[
HttpStatusCode.Forbidden,
'Failed to get the board configuration!',
'Please go back to the previous page to check your board info, or change your time range!',
'Failed to get Board configuration!',
'Please go back to the previous page and check your board info!',
],
[
HttpStatusCode.NotFound,
'Failed to get the board configuration!',
'Please go back to the previous page to check your board info, or change your time range!',
'Failed to get Board configuration!',
'Please go back to the previous page and check your board info!',
],
])('should got error message when got code is %s', async (code, title, message) => {
server.use(
Expand All @@ -81,4 +82,115 @@ describe('use get board info', () => {
});
expect(result.current.errorMessage.message).toEqual(message);
});

it('should get data when mock 4xx error', async () => {
server.use(
rest.post(MOCK_BOARD_INFO_URL, (_, res, ctx) => {
return res(
ctx.status(HttpStatusCode.BadRequest),
ctx.json({
ignoredTargetFields: [
{
key: 'description',
name: 'Description',
flag: false,
},
],
jiraColumns: [
{
key: 'To Do',
value: {
name: 'TODO',
statuses: ['TODO'],
},
},
],
targetFields: [
{
key: 'issuetype',
name: 'Issue Type',
flag: false,
},
],
users: ['heartbeat user'],
}),
);
}),
);
const { result } = renderHook(() => useGetBoardInfoEffect());
await act(() => {
result.current.getBoardInfo(mockBoardConfig);
});

await waitFor(() => {
expect(result.current.errorMessage.title).toEqual('Failed to get Board configuration!');
});
expect(result.current.errorMessage.message).toEqual(
'Please go back to the previous page and check your board info!',
);
});

it('should get data when mock 3xx error', async () => {
server.use(
rest.post(MOCK_BOARD_INFO_URL, (_, res, ctx) => {
return res(
ctx.status(HttpStatusCode.Unused),
ctx.json({
code: AXIOS_REQUEST_ERROR_CODE.TIMEOUT,
}),
);
}),
);
const { result } = renderHook(() => useGetBoardInfoEffect());
await act(() => {
result.current.getBoardInfo(mockBoardConfig);
});

await waitFor(() => {
expect(result.current.errorMessage.title).toEqual('Failed to get Board configuration!');
weiraneve marked this conversation as resolved.
Show resolved Hide resolved
});
expect(result.current.errorMessage.message).toEqual(
'Please go back to the previous page and check your board info!',
);
});

it('should get data when status is OK', async () => {
server.use(
rest.post(MOCK_BOARD_INFO_URL, (_, res, ctx) => {
return res.once(
ctx.status(HttpStatusCode.Ok),
ctx.json({
ignoredTargetFields: [
{
key: 'description',
name: 'Description',
flag: false,
},
],
jiraColumns: [
{
key: 'To Do',
value: {
name: 'TODO',
statuses: ['TODO'],
},
},
],
targetFields: [
{
key: 'issuetype',
name: 'Issue Type',
flag: false,
},
],
users: ['heartbeat user'],
}),
);
}),
);
const { result } = renderHook(() => useGetBoardInfoEffect());
await act(() => {
result.current.getBoardInfo(mockBoardConfig);
});
});
});
Loading
Loading