From b8174e1e7937b876b41a5b7aa10c2b8eb1a40f64 Mon Sep 17 00:00:00 2001 From: GuangbinMa Date: Thu, 25 Apr 2024 14:25:46 +0800 Subject: [PATCH 01/31] ADM-879: [frontend] feat: set date range when select related date range --- .../Common/DateRangeViewer/index.tsx | 18 ++++++++++++++---- .../Common/DateRangeViewer/style.tsx | 4 ---- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/frontend/src/components/Common/DateRangeViewer/index.tsx b/frontend/src/components/Common/DateRangeViewer/index.tsx index d90e08e488..7b467af097 100644 --- a/frontend/src/components/Common/DateRangeViewer/index.tsx +++ b/frontend/src/components/Common/DateRangeViewer/index.tsx @@ -24,7 +24,7 @@ const DateRangeViewer = ({ expandBackgroundColor = theme.palette.secondary.dark, }: Props) => { const [showMoreDateRange, setShowMoreDateRange] = useState(false); - const datePick = dateRanges[0]; + const [selectedDateRange, setSelectedDateRange] = useState(dateRanges[0]); const DateRangeExpandRef = useRef(null); const handleClickOutside = useCallback((event: MouseEvent) => { @@ -33,6 +33,11 @@ const DateRangeViewer = ({ } }, []); + const handleClick = (index: number) => { + setSelectedDateRange(dateRanges[index]) + setShowMoreDateRange(false); + } + useEffect(() => { document.addEventListener('mousedown', handleClickOutside); return () => { @@ -45,7 +50,12 @@ const DateRangeViewer = ({ {dateRanges.map((dateRange, index) => { return ( - + handleClick(index)} + key={index} + color={expandColor} + backgroundColor={expandBackgroundColor} + > {formatDate(dateRange.startDate as string)} {formatDate(dateRange.endDate as string)} @@ -58,9 +68,9 @@ const DateRangeViewer = ({ return ( - {formatDate(datePick.startDate as string)} + {formatDate(selectedDateRange.startDate as string)} - {formatDate(datePick.endDate as string)} + {formatDate(selectedDateRange.endDate as string)} setShowMoreDateRange(true)} /> diff --git a/frontend/src/components/Common/DateRangeViewer/style.tsx b/frontend/src/components/Common/DateRangeViewer/style.tsx index 531eaad9a0..96bd03a880 100644 --- a/frontend/src/components/Common/DateRangeViewer/style.tsx +++ b/frontend/src/components/Common/DateRangeViewer/style.tsx @@ -9,13 +9,11 @@ export const DateRangeContainer = styled.div({ display: 'flex', justifyContent: 'flex-start', alignItems: 'center', - backgroundColor: theme.palette.secondary.dark, borderRadius: '0.5rem', border: '0.07rem solid', borderColor: theme.palette.grey[400], width: 'fit-content', padding: '.75rem', - color: theme.palette.text.disabled, fontSize: '.875rem', }); @@ -61,13 +59,11 @@ export const SingleDateRange = styled.div((props) => ({ padding: '0.5rem', })); export const StyledArrowForward = styled(ArrowForward)({ - color: theme.palette.text.disabled, margin: '0 .5rem', fontSize: '0.875rem', }); export const StyledCalendarToday = styled(CalendarToday)({ - color: theme.palette.text.disabled, marginLeft: '1rem', fontSize: '.875rem', }); From d53936529d45425bf4fbcdd3e84546cefa2225bd Mon Sep 17 00:00:00 2001 From: Tingyu Dong Date: Thu, 25 Apr 2024 16:44:59 +0800 Subject: [PATCH 02/31] ADM-924:[frontend]feat: add disabled logic for DateRangeViewer --- .../Common/DateRangeViewer/index.tsx | 21 +++++---------- .../Common/DateRangeViewer/style.tsx | 27 +++++++++++-------- frontend/src/containers/MetricsStep/index.tsx | 6 +---- frontend/src/containers/ReportStep/index.tsx | 2 +- frontend/src/context/config/configSlice.ts | 1 + 5 files changed, 26 insertions(+), 31 deletions(-) diff --git a/frontend/src/components/Common/DateRangeViewer/index.tsx b/frontend/src/components/Common/DateRangeViewer/index.tsx index 7b467af097..88f5a5f632 100644 --- a/frontend/src/components/Common/DateRangeViewer/index.tsx +++ b/frontend/src/components/Common/DateRangeViewer/index.tsx @@ -16,13 +16,10 @@ type Props = { dateRanges: DateRange; expandColor?: string; expandBackgroundColor?: string; + disabledAll: boolean; }; -const DateRangeViewer = ({ - dateRanges, - expandColor = theme.palette.text.disabled, - expandBackgroundColor = theme.palette.secondary.dark, -}: Props) => { +const DateRangeViewer = ({ dateRanges, disabledAll = true }: Props) => { const [showMoreDateRange, setShowMoreDateRange] = useState(false); const [selectedDateRange, setSelectedDateRange] = useState(dateRanges[0]); const DateRangeExpandRef = useRef(null); @@ -34,9 +31,9 @@ const DateRangeViewer = ({ }, []); const handleClick = (index: number) => { - setSelectedDateRange(dateRanges[index]) + setSelectedDateRange(dateRanges[index]); setShowMoreDateRange(false); - } + }; useEffect(() => { document.addEventListener('mousedown', handleClickOutside); @@ -49,13 +46,9 @@ const DateRangeViewer = ({ return ( {dateRanges.map((dateRange, index) => { + const disabled = disabledAll || dateRange.disabled; return ( - handleClick(index)} - key={index} - color={expandColor} - backgroundColor={expandBackgroundColor} - > + handleClick(index)} key={index}> {formatDate(dateRange.startDate as string)} {formatDate(dateRange.endDate as string)} @@ -67,7 +60,7 @@ const DateRangeViewer = ({ }); return ( - + {formatDate(selectedDateRange.startDate as string)} {formatDate(selectedDateRange.endDate as string)} diff --git a/frontend/src/components/Common/DateRangeViewer/style.tsx b/frontend/src/components/Common/DateRangeViewer/style.tsx index 96bd03a880..61fee89973 100644 --- a/frontend/src/components/Common/DateRangeViewer/style.tsx +++ b/frontend/src/components/Common/DateRangeViewer/style.tsx @@ -1,10 +1,10 @@ import { ArrowForward, CalendarToday, ExpandMore } from '@mui/icons-material'; +import { Divider, MenuItem } from '@mui/material'; import { Z_INDEX } from '@src/constants/commons'; -import { Divider } from '@mui/material'; import styled from '@emotion/styled'; import { theme } from '@src/theme'; -export const DateRangeContainer = styled.div({ +export const DateRangeContainer = styled('div')(({ disabled }) => ({ position: 'relative', display: 'flex', justifyContent: 'flex-start', @@ -15,7 +15,11 @@ export const DateRangeContainer = styled.div({ width: 'fit-content', padding: '.75rem', fontSize: '.875rem', -}); + + ...(disabled && { + color: theme.palette.text.disabled, + }), +})); export const DateRangeExpandContainer = styled.div({ position: 'absolute', @@ -44,20 +48,21 @@ export const DateRangeExpandContainer = styled.div({ }, }); -interface SingleDateRangeProps { - backgroundColor: string; - color: string; -} - -export const SingleDateRange = styled.div((props) => ({ +export const SingleDateRange = styled('div')(({ disabled }) => ({ display: 'flex', justifyContent: 'center', alignItems: 'center', - backgroundColor: props.backgroundColor, - color: props.color, + color: theme.palette.text.primary, fontSize: '.875rem', padding: '0.5rem', + cursor: 'pointer', + + ...(disabled && { + color: theme.palette.text.disabled, + cursor: 'default', + }), })); + export const StyledArrowForward = styled(ArrowForward)({ margin: '0 .5rem', fontSize: '0.875rem', diff --git a/frontend/src/containers/MetricsStep/index.tsx b/frontend/src/containers/MetricsStep/index.tsx index dbddd57045..895acef331 100644 --- a/frontend/src/containers/MetricsStep/index.tsx +++ b/frontend/src/containers/MetricsStep/index.tsx @@ -99,11 +99,7 @@ const MetricsStep = () => { <> {startDate && endDate && ( - + )} diff --git a/frontend/src/containers/ReportStep/index.tsx b/frontend/src/containers/ReportStep/index.tsx index 2b98f4c5e6..a7e95eadc6 100644 --- a/frontend/src/containers/ReportStep/index.tsx +++ b/frontend/src/containers/ReportStep/index.tsx @@ -404,7 +404,7 @@ const ReportStep = ({ handleSave }: ReportStepProps) => { <> {startDate && endDate && ( - + )} {isSummaryPage diff --git a/frontend/src/context/config/configSlice.ts b/frontend/src/context/config/configSlice.ts index 3d1af27439..7bdd17b9c6 100644 --- a/frontend/src/context/config/configSlice.ts +++ b/frontend/src/context/config/configSlice.ts @@ -22,6 +22,7 @@ import dayjs from 'dayjs'; export type DateRange = { startDate: string | null; endDate: string | null; + disabled?: boolean; }[]; export interface BasicConfigState { From 058201a7e9a72812b8b7522c9bd5f236992746b7 Mon Sep 17 00:00:00 2001 From: Leiqiuhong Date: Thu, 25 Apr 2024 15:01:17 +0800 Subject: [PATCH 03/31] ADM-879-new feat: send multi fetch in report page --- frontend/src/clients/report/ReportClient.ts | 11 +- frontend/src/containers/ReportStep/index.tsx | 100 ++++--- frontend/src/hooks/useGenerateReportEffect.ts | 254 +++++++++++++----- 3 files changed, 250 insertions(+), 115 deletions(-) diff --git a/frontend/src/clients/report/ReportClient.ts b/frontend/src/clients/report/ReportClient.ts index 811352c415..a7dfd7579c 100644 --- a/frontend/src/clients/report/ReportClient.ts +++ b/frontend/src/clients/report/ReportClient.ts @@ -2,6 +2,11 @@ import { ReportCallbackResponse, ReportResponseDTO } from '@src/clients/report/d import { ReportRequestDTO } from '@src/clients/report/dto/request'; import { HttpClient } from '@src/clients/HttpClient'; +export interface IPollingRes { + status: number; + response: ReportResponseDTO; +} + export class ReportClient extends HttpClient { status = 0; reportCallbackResponse: ReportCallbackResponse = { @@ -94,12 +99,10 @@ export class ReportClient extends HttpClient { .catch((e) => { throw e; }); - return { - response: this.reportCallbackResponse, - }; + return this.reportCallbackResponse; }; - polling = async (url: string) => { + polling = async (url: string): Promise => { await this.axiosInstance .get(url) .then((res) => { diff --git a/frontend/src/containers/ReportStep/index.tsx b/frontend/src/containers/ReportStep/index.tsx index a7e95eadc6..49067cecf1 100644 --- a/frontend/src/containers/ReportStep/index.tsx +++ b/frontend/src/containers/ReportStep/index.tsx @@ -8,6 +8,7 @@ import { sortDateRanges, } from '@src/utils/util'; import { + DateRange, isOnlySelectClassification, isSelectBoardMetrics, isSelectDoraMetrics, @@ -22,9 +23,9 @@ import { REQUIRED_DATA, } from '@src/constants/resources'; import { addNotification, closeAllNotifications, Notification } from '@src/context/notification/NotificationSlice'; +import { initReportInfo, IReportInfo, useGenerateReportEffect } from '@src/hooks/useGenerateReportEffect'; import { IPipelineConfig, selectMetricsContent } from '@src/context/Metrics/metricsSlice'; import { backStep, selectTimeStamp } from '@src/context/stepper/StepperSlice'; -import { useGenerateReportEffect } from '@src/hooks/useGenerateReportEffect'; import React, { useEffect, useLayoutEffect, useMemo, useState } from 'react'; import { StyledCalendarWrapper } from '@src/containers/ReportStep/style'; import { ReportButtonGroup } from '@src/containers/ReportButtonGroup'; @@ -36,6 +37,7 @@ import { useAppDispatch } from '@src/hooks/useAppDispatch'; import { BoardDetail, DoraDetail } from './ReportDetail'; import { METRIC_TYPES } from '@src/constants/commons'; import { useAppSelector } from '@src/hooks'; +import get from 'lodash/get'; export interface ReportStepProps { handleSave: () => void; @@ -43,24 +45,23 @@ export interface ReportStepProps { const ReportStep = ({ handleSave }: ReportStepProps) => { const dispatch = useAppDispatch(); - const { - startToRequestData, - reportData, - stopPollingReports, - timeout4Board, - timeout4Dora, - timeout4Report, - generalError4Board, - generalError4Dora, - generalError4Report, - } = useGenerateReportEffect(); + const configData = useAppSelector(selectConfig); + const dateRanges: DateRange = get(configData, 'basic.dateRange', []); + const currentDateRange = dateRanges[0]; + const [currentDataInfo, setCurrentDataInfo] = useState(initReportInfo); + + const { startToRequestData, result, stopPollingReports } = useGenerateReportEffect(); + + useEffect(() => { + console.log(result, 123456, JSON.stringify(currentDateRange), currentDateRange); + setCurrentDataInfo(result.find((singleResult) => singleResult.id === currentDateRange.startDate)!); + }, [result, currentDateRange]); const [exportValidityTimeMin, setExportValidityTimeMin] = useState(undefined); const [pageType, setPageType] = useState(REPORT_PAGE_TYPE.SUMMARY); const [isCsvFileGeneratedAtEnd, setIsCsvFileGeneratedAtEnd] = useState(false); const [notifications4SummaryPage, setNotifications4SummaryPage] = useState[]>([]); - const configData = useAppSelector(selectConfig); const csvTimeStamp = useAppSelector(selectTimeStamp); const { cycleTimeSettingsType, @@ -89,10 +90,15 @@ const ReportStep = ({ handleSave }: ReportStepProps) => { const isSummaryPage = useMemo(() => pageType === REPORT_PAGE_TYPE.SUMMARY, [pageType]); const getErrorMessage4Board = () => { - if (reportData?.reportMetricsError.boardMetricsError) { - return `Failed to get Jira info, status: ${reportData.reportMetricsError.boardMetricsError.status}`; + if (currentDataInfo.reportData?.reportMetricsError.boardMetricsError) { + return `Failed to get Jira info, status: ${currentDataInfo.reportData.reportMetricsError.boardMetricsError.status}`; } - return timeout4Board || timeout4Report || generalError4Board || generalError4Report; + return ( + currentDataInfo.timeout4Board || + currentDataInfo.timeout4Report || + currentDataInfo.generalError4Board || + currentDataInfo.generalError4Report + ); }; const getJiraBoardSetting = () => { @@ -248,9 +254,12 @@ const ReportStep = ({ handleSave }: ReportStepProps) => { }, [dispatch, pageType]); useEffect(() => { - setExportValidityTimeMin(reportData?.exportValidityTime); - reportData && setIsCsvFileGeneratedAtEnd(reportData.allMetricsCompleted && reportData.isSuccessfulCreateCsvFile); - }, [dispatch, reportData]); + setExportValidityTimeMin(currentDataInfo.reportData?.exportValidityTime); + currentDataInfo.reportData && + setIsCsvFileGeneratedAtEnd( + currentDataInfo.reportData.allMetricsCompleted && currentDataInfo.reportData.isSuccessfulCreateCsvFile, + ); + }, [dispatch, currentDataInfo.reportData]); useEffect(() => { if (isSummaryPage && notifications4SummaryPage.length > 0) { @@ -261,7 +270,7 @@ const ReportStep = ({ handleSave }: ReportStepProps) => { }, [dispatch, notifications4SummaryPage, isSummaryPage]); useEffect(() => { - if (reportData?.reportMetricsError.boardMetricsError) { + if (currentDataInfo.reportData?.reportMetricsError.boardMetricsError) { setNotifications4SummaryPage((prevState) => [ ...prevState, { @@ -270,10 +279,10 @@ const ReportStep = ({ handleSave }: ReportStepProps) => { }, ]); } - }, [reportData?.reportMetricsError.boardMetricsError]); + }, [currentDataInfo.reportData?.reportMetricsError.boardMetricsError]); useEffect(() => { - if (reportData?.reportMetricsError.pipelineMetricsError) { + if (currentDataInfo.reportData?.reportMetricsError.pipelineMetricsError) { setNotifications4SummaryPage((prevState) => [ ...prevState, { @@ -282,10 +291,10 @@ const ReportStep = ({ handleSave }: ReportStepProps) => { }, ]); } - }, [reportData?.reportMetricsError.pipelineMetricsError]); + }, [currentDataInfo.reportData?.reportMetricsError.pipelineMetricsError]); useEffect(() => { - if (reportData?.reportMetricsError.sourceControlMetricsError) { + if (currentDataInfo.reportData?.reportMetricsError.sourceControlMetricsError) { setNotifications4SummaryPage((prevState) => [ ...prevState, { @@ -294,10 +303,10 @@ const ReportStep = ({ handleSave }: ReportStepProps) => { }, ]); } - }, [reportData?.reportMetricsError.sourceControlMetricsError]); + }, [currentDataInfo.reportData?.reportMetricsError.sourceControlMetricsError]); useEffect(() => { - timeout4Report && + currentDataInfo.timeout4Report && setNotifications4SummaryPage((prevState) => [ ...prevState, { @@ -305,10 +314,10 @@ const ReportStep = ({ handleSave }: ReportStepProps) => { type: 'error', }, ]); - }, [timeout4Report]); + }, [currentDataInfo.timeout4Report]); useEffect(() => { - timeout4Board && + currentDataInfo.timeout4Board && setNotifications4SummaryPage((prevState) => [ ...prevState, { @@ -316,10 +325,10 @@ const ReportStep = ({ handleSave }: ReportStepProps) => { type: 'error', }, ]); - }, [timeout4Board]); + }, [currentDataInfo.timeout4Board]); useEffect(() => { - timeout4Dora && + currentDataInfo.timeout4Dora && setNotifications4SummaryPage((prevState) => [ ...prevState, { @@ -327,10 +336,10 @@ const ReportStep = ({ handleSave }: ReportStepProps) => { type: 'error', }, ]); - }, [timeout4Dora]); + }, [currentDataInfo.timeout4Dora]); useEffect(() => { - generalError4Board && + currentDataInfo.generalError4Board && setNotifications4SummaryPage((prevState) => [ ...prevState, { @@ -338,10 +347,10 @@ const ReportStep = ({ handleSave }: ReportStepProps) => { type: 'error', }, ]); - }, [generalError4Board]); + }, [currentDataInfo.generalError4Board]); useEffect(() => { - generalError4Dora && + currentDataInfo.generalError4Dora && setNotifications4SummaryPage((prevState) => [ ...prevState, { @@ -349,10 +358,10 @@ const ReportStep = ({ handleSave }: ReportStepProps) => { type: 'error', }, ]); - }, [generalError4Dora]); + }, [currentDataInfo.generalError4Dora]); useEffect(() => { - generalError4Report && + currentDataInfo.generalError4Report && setNotifications4SummaryPage((prevState) => [ ...prevState, { @@ -360,7 +369,7 @@ const ReportStep = ({ handleSave }: ReportStepProps) => { type: 'error', }, ]); - }, [generalError4Report]); + }, [currentDataInfo.generalError4Report]); useEffect(() => { startToRequestData(basicReportRequestBody); @@ -373,7 +382,7 @@ const ReportStep = ({ handleSave }: ReportStepProps) => { startToRequestData(boardReportRequestBody)} onShowDetail={() => setPageType(REPORT_PAGE_TYPE.BOARD)} - boardReport={reportData} + boardReport={currentDataInfo.reportData} errorMessage={getErrorMessage4Board()} /> )} @@ -381,8 +390,13 @@ const ReportStep = ({ handleSave }: ReportStepProps) => { startToRequestData(doraReportRequestBody)} onShowDetail={() => setPageType(REPORT_PAGE_TYPE.DORA)} - doraReport={reportData} - errorMessage={timeout4Dora || timeout4Report || generalError4Dora || generalError4Report} + doraReport={currentDataInfo.reportData} + errorMessage={ + currentDataInfo.timeout4Dora || + currentDataInfo.timeout4Report || + currentDataInfo.generalError4Dora || + currentDataInfo.generalError4Report + } /> )} @@ -410,8 +424,8 @@ const ReportStep = ({ handleSave }: ReportStepProps) => { {isSummaryPage ? showSummary() : pageType === REPORT_PAGE_TYPE.BOARD - ? showBoardDetail(reportData) - : !!reportData && showDoraDetail(reportData)} + ? showBoardDetail(currentDataInfo.reportData) + : !!currentDataInfo.reportData && showDoraDetail(currentDataInfo.reportData)} { isShowExportPipelineButton={isSummaryPage ? shouldShowDoraMetrics : pageType === REPORT_PAGE_TYPE.DORA} handleBack={() => handleBack()} handleSave={() => handleSave()} - reportData={reportData} + reportData={currentDataInfo.reportData} startDate={startDate} endDate={endDate} csvTimeStamp={csvTimeStamp} diff --git a/frontend/src/hooks/useGenerateReportEffect.ts b/frontend/src/hooks/useGenerateReportEffect.ts index 009da1163c..71c34486fc 100644 --- a/frontend/src/hooks/useGenerateReportEffect.ts +++ b/frontend/src/hooks/useGenerateReportEffect.ts @@ -1,97 +1,177 @@ +import { ReportCallbackResponse, ReportResponseDTO } from '@src/clients/report/dto/response'; import { exportValidityTimeMapper } from '@src/hooks/reportMapper/exportValidityTime'; import { DATA_LOADING_FAILED, DEFAULT_MESSAGE } from '@src/constants/resources'; -import { ReportResponseDTO } from '@src/clients/report/dto/response'; +import { IPollingRes, reportClient } from '@src/clients/report/ReportClient'; +import { DateRange, selectConfig } from '@src/context/config/configSlice'; import { ReportRequestDTO } from '@src/clients/report/dto/request'; -import { reportClient } from '@src/clients/report/ReportClient'; +import { formatDateToTimestampString } from '@src/utils/util'; import { TimeoutError } from '@src/errors/TimeoutError'; import { METRIC_TYPES } from '@src/constants/commons'; +import { useAppSelector } from '@src/hooks/index'; import { useRef, useState } from 'react'; +import get from 'lodash/get'; + +export type MyPromiseSettledResult = PromiseSettledResult & { + id: string; +}; export interface useGenerateReportEffectInterface { startToRequestData: (params: ReportRequestDTO) => void; stopPollingReports: () => void; + result: IReportInfo[]; +} + +interface IReportError { timeout4Board: string; timeout4Dora: string; timeout4Report: string; generalError4Board: string; generalError4Dora: string; generalError4Report: string; +} + +export interface IReportInfo extends IReportError { + id: string; reportData: ReportResponseDTO | undefined; } +export const initReportInfo: IReportInfo = { + id: '', + timeout4Board: '', + timeout4Dora: '', + timeout4Report: '', + generalError4Board: '', + generalError4Dora: '', + generalError4Report: '', + reportData: undefined, +}; + +const timeoutErrorKey = { + [METRIC_TYPES.BOARD]: 'timeout4Board', + [METRIC_TYPES.DORA]: 'timeout4Dora', + [METRIC_TYPES.ALL]: 'timeout4Report', +}; + +const generalErrorKey = { + [METRIC_TYPES.BOARD]: 'generalError4Board', + [METRIC_TYPES.DORA]: 'generalError4Dora', + [METRIC_TYPES.ALL]: 'generalError4Report', +}; + +const getErrorKey = (error: Error, source: METRIC_TYPES): string => { + return error instanceof TimeoutError ? timeoutErrorKey[source] : generalErrorKey[source]; +}; + export const useGenerateReportEffect = (): useGenerateReportEffectInterface => { const reportPath = '/reports'; - const [timeout4Board, setTimeout4Board] = useState(DEFAULT_MESSAGE); - const [timeout4Dora, setTimeout4Dora] = useState(DEFAULT_MESSAGE); - const [timeout4Report, setTimeout4Report] = useState(DEFAULT_MESSAGE); - const [generalError4Board, setGeneralError4Board] = useState(DEFAULT_MESSAGE); - const [generalError4Dora, setGeneralError4Dora] = useState(DEFAULT_MESSAGE); - const [generalError4Report, setGeneralError4Report] = useState(DEFAULT_MESSAGE); - const [reportData, setReportData] = useState(); + const configData = useAppSelector(selectConfig); const timerIdRef = useRef(); + const dateRanges: DateRange = get(configData, 'basic.dateRange', []); + const [result, setResult] = useState( + dateRanges.map((dateRange) => ({ ...initReportInfo, id: dateRange?.startDate || '' })), + ); let hasPollingStarted = false; - const startToRequestData = (params: ReportRequestDTO) => { + function assemblePollingParams(res: PromiseSettledResult[]) { + const resWithIds: MyPromiseSettledResult[] = res.map((item, index) => ({ + ...item, + id: result[index].id, + })); + + const fulfilledResponses: MyPromiseSettledResult[] = resWithIds.filter( + ({ status }) => status === 'fulfilled', + ); + + const pollingInfos: Record[] = fulfilledResponses.map((v) => { + return { callbackUrl: (v as PromiseFulfilledResult).value.callbackUrl, id: v.id }; + }); + + const pollingInterval = (fulfilledResponses[0] as PromiseFulfilledResult).value.interval; + return { pollingInfos, pollingInterval }; + } + + const startToRequestData = async (params: ReportRequestDTO) => { const { metricTypes } = params; resetTimeoutMessage(metricTypes); - reportClient - .retrieveByUrl(params, reportPath) - .then((res) => { - if (hasPollingStarted) return; - hasPollingStarted = true; - pollingReport(res.response.callbackUrl, res.response.interval); - }) - .catch((e) => { - const source: METRIC_TYPES = metricTypes.length === 2 ? METRIC_TYPES.ALL : metricTypes[0]; - handleError(e, source); - }); - }; + const res: PromiseSettledResult[] = await Promise.allSettled( + dateRanges.map(({ startDate, endDate }) => + reportClient.retrieveByUrl( + { + ...params, + startTime: formatDateToTimestampString(startDate!), + endTime: formatDateToTimestampString(endDate!), + }, + reportPath, + ), + ), + ); - const resetTimeoutMessage = (metricTypes: string[]) => { - if (metricTypes.length === 2) { - setTimeout4Report(DEFAULT_MESSAGE); - } else if (metricTypes.includes(METRIC_TYPES.BOARD)) { - setTimeout4Board(DEFAULT_MESSAGE); - } else { - setTimeout4Dora(DEFAULT_MESSAGE); - } - }; + updateResultAfterFetchReport(res, metricTypes); - const handleTimeoutError = { - [METRIC_TYPES.BOARD]: setTimeout4Board, - [METRIC_TYPES.DORA]: setTimeout4Dora, - [METRIC_TYPES.ALL]: setTimeout4Report, - }; + if (hasPollingStarted) return; + hasPollingStarted = true; - const handleGeneralError = { - [METRIC_TYPES.BOARD]: setGeneralError4Board, - [METRIC_TYPES.DORA]: setGeneralError4Dora, - [METRIC_TYPES.ALL]: setGeneralError4Report, - }; + const { pollingInfos, pollingInterval } = assemblePollingParams(res); - const handleError = (error: Error, source: METRIC_TYPES) => { - return error instanceof TimeoutError - ? handleTimeoutError[source](DATA_LOADING_FAILED) - : handleGeneralError[source](DATA_LOADING_FAILED); + await pollingReport({ pollingInfos, interval: pollingInterval }); }; - const pollingReport = (url: string, interval: number) => { - setTimeout4Report(DEFAULT_MESSAGE); - reportClient - .polling(url) - .then((res: { status: number; response: ReportResponseDTO }) => { - const response = res.response; - handleAndUpdateData(response); - if (response.allMetricsCompleted || !hasPollingStarted) { - stopPollingReports(); + const pollingReport = async ({ + pollingInfos, + interval, + }: { + pollingInfos: Record[]; + interval: number; + }) => { + if (pollingInfos.length === 0) { + stopPollingReports(); + return; + } + const pollingIds: string[] = pollingInfos.map((pollingInfo) => pollingInfo.id); + setResult((preInfos) => { + return preInfos.map((info) => { + if (pollingIds.includes(info.id)) { + info.timeout4Report = DEFAULT_MESSAGE; + } + return info; + }); + }); + + const pollingQueue: Promise[] = pollingInfos.map((pollingInfo) => + reportClient.polling(pollingInfo.callbackUrl), + ); + const pollingResponses = await Promise.allSettled(pollingQueue); + const pollingResponsesWithId: MyPromiseSettledResult[] = pollingResponses.map((singleRes, index) => ({ + ...singleRes, + id: pollingInfos[index].id, + })); + const nextPollingInfos: Record[] = []; + setResult((preResult) => { + return preResult.map((singleResult) => { + const matchedRes = pollingResponsesWithId.find( + (singleRes) => singleRes.id === singleResult.id, + ) as MyPromiseSettledResult; + + if (matchedRes.status === 'fulfilled') { + const { response } = matchedRes.value; + singleResult.reportData = assembleReportData(response); + if (response.allMetricsCompleted || !hasPollingStarted) { + // todo 这一条不再polling + } else { + // todo 继续polling + nextPollingInfos.push(pollingInfos.find((pollingInfo) => pollingInfo.id === matchedRes.id)!); + } } else { - timerIdRef.current = window.setTimeout(() => pollingReport(url, interval), interval * 1000); + const errorKey = getErrorKey(matchedRes.reason, METRIC_TYPES.ALL) as keyof IReportError; + singleResult[errorKey] = DATA_LOADING_FAILED; + // todo 这一条不再polling } - }) - .catch((e) => { - handleError(e, METRIC_TYPES.ALL); - stopPollingReports(); + return singleResult; }); + }); + timerIdRef.current = window.setTimeout(() => { + pollingReport({ pollingInfos: nextPollingInfos, interval }); + }, interval * 1000); }; const stopPollingReports = () => { @@ -99,20 +179,58 @@ export const useGenerateReportEffect = (): useGenerateReportEffectInterface => { hasPollingStarted = false; }; - const handleAndUpdateData = (response: ReportResponseDTO) => { + const assembleReportData = (response: ReportResponseDTO) => { const exportValidityTime = exportValidityTimeMapper(response.exportValidityTime); - setReportData({ ...response, exportValidityTime: exportValidityTime }); + return { ...response, exportValidityTime: exportValidityTime }; + }; + + const resetTimeoutMessage = (metricTypes: string[]) => { + if (metricTypes.length === 2) { + setResult((preResult) => { + return preResult.map((singleResult) => { + singleResult.timeout4Report = DEFAULT_MESSAGE; + return singleResult; + }); + }); + } else if (metricTypes.includes(METRIC_TYPES.BOARD)) { + setResult((preResult) => { + return preResult.map((singleResult) => { + singleResult.timeout4Board = DEFAULT_MESSAGE; + return singleResult; + }); + }); + } else { + setResult((preResult) => { + return preResult.map((singleResult) => { + singleResult.timeout4Dora = DEFAULT_MESSAGE; + return singleResult; + }); + }); + } + }; + + const updateResultAfterFetchReport = ( + res: PromiseSettledResult[], + metricTypes: METRIC_TYPES[], + ) => { + if (res.filter(({ status }) => status === 'rejected').length) { + setResult((preResult: IReportInfo[]) => { + return preResult.map((resInfo, index) => { + const currentRes = res[index]; + if (currentRes.status === 'rejected') { + const source: METRIC_TYPES = metricTypes.length === 2 ? METRIC_TYPES.ALL : metricTypes[0]; + const errorKey = getErrorKey(currentRes.reason, source) as keyof IReportError; + resInfo[errorKey] = METRIC_TYPES.ALL; + } + return resInfo; + }); + }); + } }; return { startToRequestData, stopPollingReports, - reportData, - timeout4Board, - timeout4Dora, - timeout4Report, - generalError4Board, - generalError4Dora, - generalError4Report, + result, }; }; From 7b9856ccd30a2315dc5b36841ef901c8b1f6ba97 Mon Sep 17 00:00:00 2001 From: Leiqiuhong Date: Thu, 25 Apr 2024 15:04:16 +0800 Subject: [PATCH 04/31] ADM-879-new refactor: rename --- frontend/src/hooks/useGenerateReportEffect.ts | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/frontend/src/hooks/useGenerateReportEffect.ts b/frontend/src/hooks/useGenerateReportEffect.ts index 71c34486fc..8ef052af7a 100644 --- a/frontend/src/hooks/useGenerateReportEffect.ts +++ b/frontend/src/hooks/useGenerateReportEffect.ts @@ -67,7 +67,7 @@ export const useGenerateReportEffect = (): useGenerateReportEffectInterface => { const configData = useAppSelector(selectConfig); const timerIdRef = useRef(); const dateRanges: DateRange = get(configData, 'basic.dateRange', []); - const [result, setResult] = useState( + const [reportInfos, setReportInfos] = useState( dateRanges.map((dateRange) => ({ ...initReportInfo, id: dateRange?.startDate || '' })), ); let hasPollingStarted = false; @@ -75,7 +75,7 @@ export const useGenerateReportEffect = (): useGenerateReportEffectInterface => { function assemblePollingParams(res: PromiseSettledResult[]) { const resWithIds: MyPromiseSettledResult[] = res.map((item, index) => ({ ...item, - id: result[index].id, + id: reportInfos[index].id, })); const fulfilledResponses: MyPromiseSettledResult[] = resWithIds.filter( @@ -128,7 +128,7 @@ export const useGenerateReportEffect = (): useGenerateReportEffectInterface => { return; } const pollingIds: string[] = pollingInfos.map((pollingInfo) => pollingInfo.id); - setResult((preInfos) => { + setReportInfos((preInfos) => { return preInfos.map((info) => { if (pollingIds.includes(info.id)) { info.timeout4Report = DEFAULT_MESSAGE; @@ -146,8 +146,8 @@ export const useGenerateReportEffect = (): useGenerateReportEffectInterface => { id: pollingInfos[index].id, })); const nextPollingInfos: Record[] = []; - setResult((preResult) => { - return preResult.map((singleResult) => { + setReportInfos((preReportInfos) => { + return preReportInfos.map((singleResult) => { const matchedRes = pollingResponsesWithId.find( (singleRes) => singleRes.id === singleResult.id, ) as MyPromiseSettledResult; @@ -186,22 +186,22 @@ export const useGenerateReportEffect = (): useGenerateReportEffectInterface => { const resetTimeoutMessage = (metricTypes: string[]) => { if (metricTypes.length === 2) { - setResult((preResult) => { - return preResult.map((singleResult) => { + setReportInfos((preReportInfos) => { + return preReportInfos.map((singleResult) => { singleResult.timeout4Report = DEFAULT_MESSAGE; return singleResult; }); }); } else if (metricTypes.includes(METRIC_TYPES.BOARD)) { - setResult((preResult) => { - return preResult.map((singleResult) => { + setReportInfos((preReportInfos) => { + return preReportInfos.map((singleResult) => { singleResult.timeout4Board = DEFAULT_MESSAGE; return singleResult; }); }); } else { - setResult((preResult) => { - return preResult.map((singleResult) => { + setReportInfos((preReportInfos) => { + return preReportInfos.map((singleResult) => { singleResult.timeout4Dora = DEFAULT_MESSAGE; return singleResult; }); @@ -214,8 +214,8 @@ export const useGenerateReportEffect = (): useGenerateReportEffectInterface => { metricTypes: METRIC_TYPES[], ) => { if (res.filter(({ status }) => status === 'rejected').length) { - setResult((preResult: IReportInfo[]) => { - return preResult.map((resInfo, index) => { + setReportInfos((preReportInfos: IReportInfo[]) => { + return preReportInfos.map((resInfo, index) => { const currentRes = res[index]; if (currentRes.status === 'rejected') { const source: METRIC_TYPES = metricTypes.length === 2 ? METRIC_TYPES.ALL : metricTypes[0]; @@ -231,6 +231,6 @@ export const useGenerateReportEffect = (): useGenerateReportEffectInterface => { return { startToRequestData, stopPollingReports, - result, + result: reportInfos, }; }; From c4c5e46d0a56e3e4455949f4331f2dbbd6a4cbee Mon Sep 17 00:00:00 2001 From: Leiqiuhong Date: Thu, 25 Apr 2024 15:18:49 +0800 Subject: [PATCH 05/31] ADM-879-new refactor: rename and move function --- frontend/src/hooks/useGenerateReportEffect.ts | 74 ++++++++++--------- 1 file changed, 38 insertions(+), 36 deletions(-) diff --git a/frontend/src/hooks/useGenerateReportEffect.ts b/frontend/src/hooks/useGenerateReportEffect.ts index 8ef052af7a..8b8534e710 100644 --- a/frontend/src/hooks/useGenerateReportEffect.ts +++ b/frontend/src/hooks/useGenerateReportEffect.ts @@ -11,7 +11,7 @@ import { useAppSelector } from '@src/hooks/index'; import { useRef, useState } from 'react'; import get from 'lodash/get'; -export type MyPromiseSettledResult = PromiseSettledResult & { +export type PromiseSettledResultWithId = PromiseSettledResult & { id: string; }; @@ -72,24 +72,6 @@ export const useGenerateReportEffect = (): useGenerateReportEffectInterface => { ); let hasPollingStarted = false; - function assemblePollingParams(res: PromiseSettledResult[]) { - const resWithIds: MyPromiseSettledResult[] = res.map((item, index) => ({ - ...item, - id: reportInfos[index].id, - })); - - const fulfilledResponses: MyPromiseSettledResult[] = resWithIds.filter( - ({ status }) => status === 'fulfilled', - ); - - const pollingInfos: Record[] = fulfilledResponses.map((v) => { - return { callbackUrl: (v as PromiseFulfilledResult).value.callbackUrl, id: v.id }; - }); - - const pollingInterval = (fulfilledResponses[0] as PromiseFulfilledResult).value.interval; - return { pollingInfos, pollingInterval }; - } - const startToRequestData = async (params: ReportRequestDTO) => { const { metricTypes } = params; resetTimeoutMessage(metricTypes); @@ -141,16 +123,18 @@ export const useGenerateReportEffect = (): useGenerateReportEffectInterface => { reportClient.polling(pollingInfo.callbackUrl), ); const pollingResponses = await Promise.allSettled(pollingQueue); - const pollingResponsesWithId: MyPromiseSettledResult[] = pollingResponses.map((singleRes, index) => ({ - ...singleRes, - id: pollingInfos[index].id, - })); + const pollingResponsesWithId: PromiseSettledResultWithId[] = pollingResponses.map( + (singleRes, index) => ({ + ...singleRes, + id: pollingInfos[index].id, + }), + ); const nextPollingInfos: Record[] = []; setReportInfos((preReportInfos) => { return preReportInfos.map((singleResult) => { const matchedRes = pollingResponsesWithId.find( (singleRes) => singleRes.id === singleResult.id, - ) as MyPromiseSettledResult; + ) as PromiseSettledResultWithId; if (matchedRes.status === 'fulfilled') { const { response } = matchedRes.value; @@ -213,21 +197,39 @@ export const useGenerateReportEffect = (): useGenerateReportEffectInterface => { res: PromiseSettledResult[], metricTypes: METRIC_TYPES[], ) => { - if (res.filter(({ status }) => status === 'rejected').length) { - setReportInfos((preReportInfos: IReportInfo[]) => { - return preReportInfos.map((resInfo, index) => { - const currentRes = res[index]; - if (currentRes.status === 'rejected') { - const source: METRIC_TYPES = metricTypes.length === 2 ? METRIC_TYPES.ALL : metricTypes[0]; - const errorKey = getErrorKey(currentRes.reason, source) as keyof IReportError; - resInfo[errorKey] = METRIC_TYPES.ALL; - } - return resInfo; - }); + if (res.filter(({ status }) => status === 'rejected').length === 0) return; + + setReportInfos((preReportInfos: IReportInfo[]) => { + return preReportInfos.map((resInfo, index) => { + const currentRes = res[index]; + if (currentRes.status === 'rejected') { + const source: METRIC_TYPES = metricTypes.length === 2 ? METRIC_TYPES.ALL : metricTypes[0]; + const errorKey = getErrorKey(currentRes.reason, source) as keyof IReportError; + resInfo[errorKey] = DATA_LOADING_FAILED; + } + return resInfo; }); - } + }); }; + function assemblePollingParams(res: PromiseSettledResult[]) { + const resWithIds: PromiseSettledResultWithId[] = res.map((item, index) => ({ + ...item, + id: reportInfos[index].id, + })); + + const fulfilledResponses: PromiseSettledResultWithId[] = resWithIds.filter( + ({ status }) => status === 'fulfilled', + ); + + const pollingInfos: Record[] = fulfilledResponses.map((v) => { + return { callbackUrl: (v as PromiseFulfilledResult).value.callbackUrl, id: v.id }; + }); + + const pollingInterval = (fulfilledResponses[0] as PromiseFulfilledResult).value.interval; + return { pollingInfos, pollingInterval }; + } + return { startToRequestData, stopPollingReports, From 9b37a01989869e627320c997aa4dbb5c03a7859e Mon Sep 17 00:00:00 2001 From: Leiqiuhong Date: Thu, 25 Apr 2024 15:46:00 +0800 Subject: [PATCH 06/31] ADM-879-new refactor: extract function --- frontend/src/hooks/useGenerateReportEffect.ts | 108 +++++++++++------- 1 file changed, 67 insertions(+), 41 deletions(-) diff --git a/frontend/src/hooks/useGenerateReportEffect.ts b/frontend/src/hooks/useGenerateReportEffect.ts index 8b8534e710..b996f8dded 100644 --- a/frontend/src/hooks/useGenerateReportEffect.ts +++ b/frontend/src/hooks/useGenerateReportEffect.ts @@ -88,7 +88,7 @@ export const useGenerateReportEffect = (): useGenerateReportEffectInterface => { ), ); - updateResultAfterFetchReport(res, metricTypes); + updateErrorAfterFetchReport(res, metricTypes); if (hasPollingStarted) return; hasPollingStarted = true; @@ -110,49 +110,17 @@ export const useGenerateReportEffect = (): useGenerateReportEffectInterface => { return; } const pollingIds: string[] = pollingInfos.map((pollingInfo) => pollingInfo.id); - setReportInfos((preInfos) => { - return preInfos.map((info) => { - if (pollingIds.includes(info.id)) { - info.timeout4Report = DEFAULT_MESSAGE; - } - return info; - }); - }); + initReportInfosTimeout4Report(pollingIds); const pollingQueue: Promise[] = pollingInfos.map((pollingInfo) => reportClient.polling(pollingInfo.callbackUrl), ); const pollingResponses = await Promise.allSettled(pollingQueue); - const pollingResponsesWithId: PromiseSettledResultWithId[] = pollingResponses.map( - (singleRes, index) => ({ - ...singleRes, - id: pollingInfos[index].id, - }), - ); - const nextPollingInfos: Record[] = []; - setReportInfos((preReportInfos) => { - return preReportInfos.map((singleResult) => { - const matchedRes = pollingResponsesWithId.find( - (singleRes) => singleRes.id === singleResult.id, - ) as PromiseSettledResultWithId; + const pollingResponsesWithId = assemblePollingResWithId(pollingResponses, pollingInfos); - if (matchedRes.status === 'fulfilled') { - const { response } = matchedRes.value; - singleResult.reportData = assembleReportData(response); - if (response.allMetricsCompleted || !hasPollingStarted) { - // todo 这一条不再polling - } else { - // todo 继续polling - nextPollingInfos.push(pollingInfos.find((pollingInfo) => pollingInfo.id === matchedRes.id)!); - } - } else { - const errorKey = getErrorKey(matchedRes.reason, METRIC_TYPES.ALL) as keyof IReportError; - singleResult[errorKey] = DATA_LOADING_FAILED; - // todo 这一条不再polling - } - return singleResult; - }); - }); + updateReportInfosAfterPolling(pollingResponsesWithId); + + const nextPollingInfos = getNextPollingInfos(pollingResponsesWithId, pollingInfos); timerIdRef.current = window.setTimeout(() => { pollingReport({ pollingInfos: nextPollingInfos, interval }); }, interval * 1000); @@ -193,7 +161,7 @@ export const useGenerateReportEffect = (): useGenerateReportEffectInterface => { } }; - const updateResultAfterFetchReport = ( + const updateErrorAfterFetchReport = ( res: PromiseSettledResult[], metricTypes: METRIC_TYPES[], ) => { @@ -212,7 +180,7 @@ export const useGenerateReportEffect = (): useGenerateReportEffectInterface => { }); }; - function assemblePollingParams(res: PromiseSettledResult[]) { + const assemblePollingParams = (res: PromiseSettledResult[]) => { const resWithIds: PromiseSettledResultWithId[] = res.map((item, index) => ({ ...item, id: reportInfos[index].id, @@ -228,7 +196,65 @@ export const useGenerateReportEffect = (): useGenerateReportEffectInterface => { const pollingInterval = (fulfilledResponses[0] as PromiseFulfilledResult).value.interval; return { pollingInfos, pollingInterval }; - } + }; + + const assemblePollingResWithId = ( + pollingResponses: Array>>>, + pollingInfos: Record[], + ) => { + const pollingResponsesWithId: PromiseSettledResultWithId[] = pollingResponses.map( + (singleRes, index) => ({ + ...singleRes, + id: pollingInfos[index].id, + }), + ); + return pollingResponsesWithId; + }; + + const getNextPollingInfos = ( + pollingResponsesWithId: PromiseSettledResultWithId[], + pollingInfos: Record[], + ) => { + const nextPollingInfos: Record[] = pollingResponsesWithId + .filter( + (pollingResponseWithId) => + pollingResponseWithId.status === 'fulfilled' && + !pollingResponseWithId.value.response.allMetricsCompleted && + hasPollingStarted, + ) + .map((pollingResponseWithId) => pollingInfos.find((pollingInfo) => pollingInfo.id === pollingResponseWithId.id)!); + return nextPollingInfos; + }; + + const updateReportInfosAfterPolling = (pollingResponsesWithId: PromiseSettledResultWithId[]) => { + setReportInfos((preReportInfos) => { + return preReportInfos.map((singleResult) => { + const matchedRes = pollingResponsesWithId.find( + (singleRes) => singleRes.id === singleResult.id, + ) as PromiseSettledResultWithId; + + if (matchedRes.status === 'fulfilled') { + const { response } = matchedRes.value; + singleResult.reportData = assembleReportData(response); + } else { + const errorKey = getErrorKey(matchedRes.reason, METRIC_TYPES.ALL) as keyof IReportError; + singleResult[errorKey] = DATA_LOADING_FAILED; + } + return singleResult; + }); + }); + }; + + const initReportInfosTimeout4Report = (pollingIds: string[]) => { + setReportInfos((preInfos) => { + return preInfos.map((info) => { + if (pollingIds.includes(info.id)) { + info.timeout4Report = DEFAULT_MESSAGE; + } + return info; + }); + }); + }; return { startToRequestData, From 24b687dd19d32ccc56214781bd46fbb42b99749c Mon Sep 17 00:00:00 2001 From: Leiqiuhong Date: Thu, 25 Apr 2024 16:52:44 +0800 Subject: [PATCH 07/31] ADM-879-new feat: should change data when select other date range --- .../components/Common/DateRangeViewer/index.tsx | 12 ++++++------ frontend/src/containers/ReportStep/index.tsx | 16 +++++++++++----- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/frontend/src/components/Common/DateRangeViewer/index.tsx b/frontend/src/components/Common/DateRangeViewer/index.tsx index 88f5a5f632..d4efc18994 100644 --- a/frontend/src/components/Common/DateRangeViewer/index.tsx +++ b/frontend/src/components/Common/DateRangeViewer/index.tsx @@ -10,18 +10,18 @@ import { import React, { useRef, useState, forwardRef, useEffect, useCallback } from 'react'; import { DateRange } from '@src/context/config/configSlice'; import { formatDate } from '@src/utils/util'; -import { theme } from '@src/theme'; type Props = { dateRanges: DateRange; + selectedDateRange?: Record; + changeDateRange?: (dateRange: Record) => void; expandColor?: string; expandBackgroundColor?: string; disabledAll: boolean; }; -const DateRangeViewer = ({ dateRanges, disabledAll = true }: Props) => { +const DateRangeViewer = ({ dateRanges, changeDateRange, selectedDateRange, disabledAll = true }: Props) => { const [showMoreDateRange, setShowMoreDateRange] = useState(false); - const [selectedDateRange, setSelectedDateRange] = useState(dateRanges[0]); const DateRangeExpandRef = useRef(null); const handleClickOutside = useCallback((event: MouseEvent) => { @@ -31,7 +31,7 @@ const DateRangeViewer = ({ dateRanges, disabledAll = true }: Props) => { }, []); const handleClick = (index: number) => { - setSelectedDateRange(dateRanges[index]); + changeDateRange && changeDateRange(dateRanges[index]); setShowMoreDateRange(false); }; @@ -61,9 +61,9 @@ const DateRangeViewer = ({ dateRanges, disabledAll = true }: Props) => { return ( - {formatDate(selectedDateRange.startDate as string)} + {formatDate((selectedDateRange || dateRanges[0]).startDate as string)} - {formatDate(selectedDateRange.endDate as string)} + {formatDate((selectedDateRange || dateRanges[0]).endDate as string)} setShowMoreDateRange(true)} /> diff --git a/frontend/src/containers/ReportStep/index.tsx b/frontend/src/containers/ReportStep/index.tsx index 49067cecf1..3b33a166f0 100644 --- a/frontend/src/containers/ReportStep/index.tsx +++ b/frontend/src/containers/ReportStep/index.tsx @@ -47,15 +47,16 @@ const ReportStep = ({ handleSave }: ReportStepProps) => { const dispatch = useAppDispatch(); const configData = useAppSelector(selectConfig); const dateRanges: DateRange = get(configData, 'basic.dateRange', []); - const currentDateRange = dateRanges[0]; + const [selectedDateRange, setSelectedDateRange] = useState>( + dateRanges[0], + ); const [currentDataInfo, setCurrentDataInfo] = useState(initReportInfo); const { startToRequestData, result, stopPollingReports } = useGenerateReportEffect(); useEffect(() => { - console.log(result, 123456, JSON.stringify(currentDateRange), currentDateRange); - setCurrentDataInfo(result.find((singleResult) => singleResult.id === currentDateRange.startDate)!); - }, [result, currentDateRange]); + setCurrentDataInfo(result.find((singleResult) => singleResult.id === selectedDateRange.startDate)!); + }, [result, selectedDateRange]); const [exportValidityTimeMin, setExportValidityTimeMin] = useState(undefined); const [pageType, setPageType] = useState(REPORT_PAGE_TYPE.SUMMARY); @@ -418,7 +419,12 @@ const ReportStep = ({ handleSave }: ReportStepProps) => { <> {startDate && endDate && ( - + setSelectedDateRange(dateRange)} + disabledAll={false} + /> )} {isSummaryPage From fbb7c51ced29691ea84a1ebe80eefb0a76cb5ece Mon Sep 17 00:00:00 2001 From: Leiqiuhong Date: Thu, 25 Apr 2024 16:58:27 +0800 Subject: [PATCH 08/31] ADM-879-new fix: fix update logic --- frontend/src/hooks/useGenerateReportEffect.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/frontend/src/hooks/useGenerateReportEffect.ts b/frontend/src/hooks/useGenerateReportEffect.ts index b996f8dded..e78ea402d5 100644 --- a/frontend/src/hooks/useGenerateReportEffect.ts +++ b/frontend/src/hooks/useGenerateReportEffect.ts @@ -229,9 +229,8 @@ export const useGenerateReportEffect = (): useGenerateReportEffectInterface => { const updateReportInfosAfterPolling = (pollingResponsesWithId: PromiseSettledResultWithId[]) => { setReportInfos((preReportInfos) => { return preReportInfos.map((singleResult) => { - const matchedRes = pollingResponsesWithId.find( - (singleRes) => singleRes.id === singleResult.id, - ) as PromiseSettledResultWithId; + const matchedRes = pollingResponsesWithId.find((singleRes) => singleRes.id === singleResult.id); + if (!matchedRes) return singleResult; if (matchedRes.status === 'fulfilled') { const { response } = matchedRes.value; From a3feec1c9bd1db905eba5e4c57e112c7fd1810d1 Mon Sep 17 00:00:00 2001 From: Leiqiuhong Date: Fri, 26 Apr 2024 10:27:54 +0800 Subject: [PATCH 09/31] ADM-879-new refactor: rename --- frontend/src/containers/ReportStep/index.tsx | 6 +++--- frontend/src/router.tsx | 20 ++++++++++---------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/frontend/src/containers/ReportStep/index.tsx b/frontend/src/containers/ReportStep/index.tsx index 3b33a166f0..f67434f891 100644 --- a/frontend/src/containers/ReportStep/index.tsx +++ b/frontend/src/containers/ReportStep/index.tsx @@ -52,11 +52,11 @@ const ReportStep = ({ handleSave }: ReportStepProps) => { ); const [currentDataInfo, setCurrentDataInfo] = useState(initReportInfo); - const { startToRequestData, result, stopPollingReports } = useGenerateReportEffect(); + const { startToRequestData, reportInfos, stopPollingReports } = useGenerateReportEffect(); useEffect(() => { - setCurrentDataInfo(result.find((singleResult) => singleResult.id === selectedDateRange.startDate)!); - }, [result, selectedDateRange]); + setCurrentDataInfo(reportInfos.find((singleResult) => singleResult.id === selectedDateRange.startDate)!); + }, [reportInfos, selectedDateRange]); const [exportValidityTimeMin, setExportValidityTimeMin] = useState(undefined); const [pageType, setPageType] = useState(REPORT_PAGE_TYPE.SUMMARY); diff --git a/frontend/src/router.tsx b/frontend/src/router.tsx index 68b242bcdd..9c57f832df 100644 --- a/frontend/src/router.tsx +++ b/frontend/src/router.tsx @@ -10,16 +10,16 @@ const Router = () => { return } />; }); return ( - { - window.location.href = '/'; - }} - > - }> - {appRoutes} - - + // { + // window.location.href = '/'; + // }} + // > + }> + {appRoutes} + + // ); }; From df961c8272c7ec3f03405cab107a550c8e1bf8b1 Mon Sep 17 00:00:00 2001 From: Leiqiuhong Date: Fri, 26 Apr 2024 10:44:08 +0800 Subject: [PATCH 10/31] ADM-879-new refactor: rename --- frontend/src/hooks/useGenerateReportEffect.ts | 46 +++++++++---------- frontend/src/router.tsx | 20 ++++---- 2 files changed, 33 insertions(+), 33 deletions(-) diff --git a/frontend/src/hooks/useGenerateReportEffect.ts b/frontend/src/hooks/useGenerateReportEffect.ts index e78ea402d5..0277b2b9f3 100644 --- a/frontend/src/hooks/useGenerateReportEffect.ts +++ b/frontend/src/hooks/useGenerateReportEffect.ts @@ -18,7 +18,7 @@ export type PromiseSettledResultWithId = PromiseSettledResult & { export interface useGenerateReportEffectInterface { startToRequestData: (params: ReportRequestDTO) => void; stopPollingReports: () => void; - result: IReportInfo[]; + reportInfos: IReportInfo[]; } interface IReportError { @@ -37,12 +37,12 @@ export interface IReportInfo extends IReportError { export const initReportInfo: IReportInfo = { id: '', - timeout4Board: '', - timeout4Dora: '', - timeout4Report: '', - generalError4Board: '', - generalError4Dora: '', - generalError4Report: '', + timeout4Board: DEFAULT_MESSAGE, + timeout4Dora: DEFAULT_MESSAGE, + timeout4Report: DEFAULT_MESSAGE, + generalError4Board: DEFAULT_MESSAGE, + generalError4Dora: DEFAULT_MESSAGE, + generalError4Report: DEFAULT_MESSAGE, reportData: undefined, }; @@ -139,23 +139,23 @@ export const useGenerateReportEffect = (): useGenerateReportEffectInterface => { const resetTimeoutMessage = (metricTypes: string[]) => { if (metricTypes.length === 2) { setReportInfos((preReportInfos) => { - return preReportInfos.map((singleResult) => { - singleResult.timeout4Report = DEFAULT_MESSAGE; - return singleResult; + return preReportInfos.map((reportInfo) => { + reportInfo.timeout4Report = DEFAULT_MESSAGE; + return reportInfo; }); }); } else if (metricTypes.includes(METRIC_TYPES.BOARD)) { setReportInfos((preReportInfos) => { - return preReportInfos.map((singleResult) => { - singleResult.timeout4Board = DEFAULT_MESSAGE; - return singleResult; + return preReportInfos.map((reportInfo) => { + reportInfo.timeout4Board = DEFAULT_MESSAGE; + return reportInfo; }); }); } else { setReportInfos((preReportInfos) => { - return preReportInfos.map((singleResult) => { - singleResult.timeout4Dora = DEFAULT_MESSAGE; - return singleResult; + return preReportInfos.map((reportInfo) => { + reportInfo.timeout4Dora = DEFAULT_MESSAGE; + return reportInfo; }); }); } @@ -228,18 +228,18 @@ export const useGenerateReportEffect = (): useGenerateReportEffectInterface => { const updateReportInfosAfterPolling = (pollingResponsesWithId: PromiseSettledResultWithId[]) => { setReportInfos((preReportInfos) => { - return preReportInfos.map((singleResult) => { - const matchedRes = pollingResponsesWithId.find((singleRes) => singleRes.id === singleResult.id); - if (!matchedRes) return singleResult; + return preReportInfos.map((reportInfo) => { + const matchedRes = pollingResponsesWithId.find((singleRes) => singleRes.id === reportInfo.id); + if (!matchedRes) return reportInfo; if (matchedRes.status === 'fulfilled') { const { response } = matchedRes.value; - singleResult.reportData = assembleReportData(response); + reportInfo.reportData = assembleReportData(response); } else { const errorKey = getErrorKey(matchedRes.reason, METRIC_TYPES.ALL) as keyof IReportError; - singleResult[errorKey] = DATA_LOADING_FAILED; + reportInfo[errorKey] = DATA_LOADING_FAILED; } - return singleResult; + return reportInfo; }); }); }; @@ -258,6 +258,6 @@ export const useGenerateReportEffect = (): useGenerateReportEffectInterface => { return { startToRequestData, stopPollingReports, - result: reportInfos, + reportInfos, }; }; diff --git a/frontend/src/router.tsx b/frontend/src/router.tsx index 9c57f832df..68b242bcdd 100644 --- a/frontend/src/router.tsx +++ b/frontend/src/router.tsx @@ -10,16 +10,16 @@ const Router = () => { return } />; }); return ( - // { - // window.location.href = '/'; - // }} - // > - }> - {appRoutes} - - // + { + window.location.href = '/'; + }} + > + }> + {appRoutes} + + ); }; From d85d7336f8e6d34d9fa079598abbcca3590199da Mon Sep 17 00:00:00 2001 From: Tingyu Dong Date: Fri, 26 Apr 2024 11:23:45 +0800 Subject: [PATCH 11/31] ADM-924:[frontend]fix: pass disabled prop to SingleDateRange and DateRangeContainer --- .../components/Common/DateRangeViewer/index.tsx | 4 ++-- .../components/Common/DateRangeViewer/style.tsx | 14 +++++++++++--- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/frontend/src/components/Common/DateRangeViewer/index.tsx b/frontend/src/components/Common/DateRangeViewer/index.tsx index d4efc18994..0d8df39a10 100644 --- a/frontend/src/components/Common/DateRangeViewer/index.tsx +++ b/frontend/src/components/Common/DateRangeViewer/index.tsx @@ -46,7 +46,7 @@ const DateRangeViewer = ({ dateRanges, changeDateRange, selectedDateRange, disab return ( {dateRanges.map((dateRange, index) => { - const disabled = disabledAll || dateRange.disabled; + const disabled = dateRange.disabled || disabledAll; return ( handleClick(index)} key={index}> {formatDate(dateRange.startDate as string)} @@ -60,7 +60,7 @@ const DateRangeViewer = ({ dateRanges, changeDateRange, selectedDateRange, disab }); return ( - + {formatDate((selectedDateRange || dateRanges[0]).startDate as string)} {formatDate((selectedDateRange || dateRanges[0]).endDate as string)} diff --git a/frontend/src/components/Common/DateRangeViewer/style.tsx b/frontend/src/components/Common/DateRangeViewer/style.tsx index 61fee89973..e1235b1fd1 100644 --- a/frontend/src/components/Common/DateRangeViewer/style.tsx +++ b/frontend/src/components/Common/DateRangeViewer/style.tsx @@ -1,10 +1,14 @@ import { ArrowForward, CalendarToday, ExpandMore } from '@mui/icons-material'; -import { Divider, MenuItem } from '@mui/material'; import { Z_INDEX } from '@src/constants/commons'; +import { Divider } from '@mui/material'; import styled from '@emotion/styled'; import { theme } from '@src/theme'; -export const DateRangeContainer = styled('div')(({ disabled }) => ({ +interface DateRangeContainerProps { + disabled: boolean; +} + +export const DateRangeContainer = styled('div')(({ disabled }: DateRangeContainerProps) => ({ position: 'relative', display: 'flex', justifyContent: 'flex-start', @@ -48,7 +52,11 @@ export const DateRangeExpandContainer = styled.div({ }, }); -export const SingleDateRange = styled('div')(({ disabled }) => ({ +interface SingleDateRangeProps { + disabled: boolean; +} + +export const SingleDateRange = styled('div')(({ disabled }: SingleDateRangeProps) => ({ display: 'flex', justifyContent: 'center', alignItems: 'center', From d615d7d1a981706385284771da74da798a6c3841 Mon Sep 17 00:00:00 2001 From: Leiqiuhong Date: Fri, 26 Apr 2024 14:27:47 +0800 Subject: [PATCH 12/31] ADM-879-new feat: add status for alert --- frontend/src/containers/ReportStep/index.tsx | 52 +++++++-- frontend/src/hooks/useGenerateReportEffect.ts | 104 ++++++++++++++---- 2 files changed, 125 insertions(+), 31 deletions(-) diff --git a/frontend/src/containers/ReportStep/index.tsx b/frontend/src/containers/ReportStep/index.tsx index f67434f891..2542faffe8 100644 --- a/frontend/src/containers/ReportStep/index.tsx +++ b/frontend/src/containers/ReportStep/index.tsx @@ -14,6 +14,13 @@ import { isSelectDoraMetrics, selectConfig, } from '@src/context/config/configSlice'; +import { + generalErrorKey, + initReportInfo, + IReportInfo, + timeoutErrorKey, + useGenerateReportEffect, +} from '@src/hooks/useGenerateReportEffect'; import { BOARD_METRICS, CALENDAR, @@ -23,7 +30,6 @@ import { REQUIRED_DATA, } from '@src/constants/resources'; import { addNotification, closeAllNotifications, Notification } from '@src/context/notification/NotificationSlice'; -import { initReportInfo, IReportInfo, useGenerateReportEffect } from '@src/hooks/useGenerateReportEffect'; import { IPipelineConfig, selectMetricsContent } from '@src/context/Metrics/metricsSlice'; import { backStep, selectTimeStamp } from '@src/context/stepper/StepperSlice'; import React, { useEffect, useLayoutEffect, useMemo, useState } from 'react'; @@ -52,7 +58,15 @@ const ReportStep = ({ handleSave }: ReportStepProps) => { ); const [currentDataInfo, setCurrentDataInfo] = useState(initReportInfo); - const { startToRequestData, reportInfos, stopPollingReports } = useGenerateReportEffect(); + const { + startToRequestData, + reportInfos, + stopPollingReports, + shutReportInfosErrorStatus, + shutBoardMetricsError, + shutPipelineMetricsError, + shutSourceControlMetricsError, + } = useGenerateReportEffect(); useEffect(() => { setCurrentDataInfo(reportInfos.find((singleResult) => singleResult.id === selectedDateRange.startDate)!); @@ -95,10 +109,10 @@ const ReportStep = ({ handleSave }: ReportStepProps) => { return `Failed to get Jira info, status: ${currentDataInfo.reportData.reportMetricsError.boardMetricsError.status}`; } return ( - currentDataInfo.timeout4Board || - currentDataInfo.timeout4Report || - currentDataInfo.generalError4Board || - currentDataInfo.generalError4Report + currentDataInfo.timeout4Board.message || + currentDataInfo.timeout4Report.message || + currentDataInfo.generalError4Board.message || + currentDataInfo.generalError4Report.message ); }; @@ -271,6 +285,7 @@ const ReportStep = ({ handleSave }: ReportStepProps) => { }, [dispatch, notifications4SummaryPage, isSummaryPage]); useEffect(() => { + if (!currentDataInfo.shouldShowBoardMetricsError) return; if (currentDataInfo.reportData?.reportMetricsError.boardMetricsError) { setNotifications4SummaryPage((prevState) => [ ...prevState, @@ -280,9 +295,11 @@ const ReportStep = ({ handleSave }: ReportStepProps) => { }, ]); } + shutBoardMetricsError(selectedDateRange.startDate as string); }, [currentDataInfo.reportData?.reportMetricsError.boardMetricsError]); useEffect(() => { + if (!currentDataInfo.shouldShowPipelineMetricsError) return; if (currentDataInfo.reportData?.reportMetricsError.pipelineMetricsError) { setNotifications4SummaryPage((prevState) => [ ...prevState, @@ -292,9 +309,11 @@ const ReportStep = ({ handleSave }: ReportStepProps) => { }, ]); } + shutPipelineMetricsError(selectedDateRange.startDate as string); }, [currentDataInfo.reportData?.reportMetricsError.pipelineMetricsError]); useEffect(() => { + if (!currentDataInfo.shouldShowSourceControlMetricsError) return; if (currentDataInfo.reportData?.reportMetricsError.sourceControlMetricsError) { setNotifications4SummaryPage((prevState) => [ ...prevState, @@ -304,9 +323,11 @@ const ReportStep = ({ handleSave }: ReportStepProps) => { }, ]); } + shutSourceControlMetricsError(selectedDateRange.startDate as string); }, [currentDataInfo.reportData?.reportMetricsError.sourceControlMetricsError]); useEffect(() => { + if (!currentDataInfo.timeout4Report.shouldShow) return; currentDataInfo.timeout4Report && setNotifications4SummaryPage((prevState) => [ ...prevState, @@ -315,9 +336,11 @@ const ReportStep = ({ handleSave }: ReportStepProps) => { type: 'error', }, ]); + shutReportInfosErrorStatus(selectedDateRange.startDate as string, timeoutErrorKey[METRIC_TYPES.ALL]); }, [currentDataInfo.timeout4Report]); useEffect(() => { + if (!currentDataInfo.timeout4Board.shouldShow) return; currentDataInfo.timeout4Board && setNotifications4SummaryPage((prevState) => [ ...prevState, @@ -326,9 +349,11 @@ const ReportStep = ({ handleSave }: ReportStepProps) => { type: 'error', }, ]); + shutReportInfosErrorStatus(selectedDateRange.startDate as string, timeoutErrorKey[METRIC_TYPES.BOARD]); }, [currentDataInfo.timeout4Board]); useEffect(() => { + if (!currentDataInfo.timeout4Dora.shouldShow) return; currentDataInfo.timeout4Dora && setNotifications4SummaryPage((prevState) => [ ...prevState, @@ -337,9 +362,11 @@ const ReportStep = ({ handleSave }: ReportStepProps) => { type: 'error', }, ]); + shutReportInfosErrorStatus(selectedDateRange.startDate as string, timeoutErrorKey[METRIC_TYPES.DORA]); }, [currentDataInfo.timeout4Dora]); useEffect(() => { + if (!currentDataInfo.generalError4Board.shouldShow) return; currentDataInfo.generalError4Board && setNotifications4SummaryPage((prevState) => [ ...prevState, @@ -348,9 +375,11 @@ const ReportStep = ({ handleSave }: ReportStepProps) => { type: 'error', }, ]); + shutReportInfosErrorStatus(selectedDateRange.startDate as string, generalErrorKey[METRIC_TYPES.BOARD]); }, [currentDataInfo.generalError4Board]); useEffect(() => { + if (!currentDataInfo.generalError4Dora.shouldShow) return; currentDataInfo.generalError4Dora && setNotifications4SummaryPage((prevState) => [ ...prevState, @@ -359,9 +388,11 @@ const ReportStep = ({ handleSave }: ReportStepProps) => { type: 'error', }, ]); + shutReportInfosErrorStatus(selectedDateRange.startDate as string, generalErrorKey[METRIC_TYPES.DORA]); }, [currentDataInfo.generalError4Dora]); useEffect(() => { + if (!currentDataInfo.generalError4Report.shouldShow) return; currentDataInfo.generalError4Report && setNotifications4SummaryPage((prevState) => [ ...prevState, @@ -370,6 +401,7 @@ const ReportStep = ({ handleSave }: ReportStepProps) => { type: 'error', }, ]); + shutReportInfosErrorStatus(selectedDateRange.startDate as string, generalErrorKey[METRIC_TYPES.ALL]); }, [currentDataInfo.generalError4Report]); useEffect(() => { @@ -393,10 +425,10 @@ const ReportStep = ({ handleSave }: ReportStepProps) => { onShowDetail={() => setPageType(REPORT_PAGE_TYPE.DORA)} doraReport={currentDataInfo.reportData} errorMessage={ - currentDataInfo.timeout4Dora || - currentDataInfo.timeout4Report || - currentDataInfo.generalError4Dora || - currentDataInfo.generalError4Report + currentDataInfo.timeout4Dora.message || + currentDataInfo.timeout4Report.message || + currentDataInfo.generalError4Dora.message || + currentDataInfo.generalError4Report.message } /> )} diff --git a/frontend/src/hooks/useGenerateReportEffect.ts b/frontend/src/hooks/useGenerateReportEffect.ts index 0277b2b9f3..3102ae305e 100644 --- a/frontend/src/hooks/useGenerateReportEffect.ts +++ b/frontend/src/hooks/useGenerateReportEffect.ts @@ -19,40 +19,50 @@ export interface useGenerateReportEffectInterface { startToRequestData: (params: ReportRequestDTO) => void; stopPollingReports: () => void; reportInfos: IReportInfo[]; + shutReportInfosErrorStatus: (id: string, errorKey: string) => void; + shutBoardMetricsError: (id: string) => void; + shutPipelineMetricsError: (id: string) => void; + shutSourceControlMetricsError: (id: string) => void; } interface IReportError { - timeout4Board: string; - timeout4Dora: string; - timeout4Report: string; - generalError4Board: string; - generalError4Dora: string; - generalError4Report: string; + timeout4Board: { message: string; shouldShow: boolean }; + timeout4Dora: { message: string; shouldShow: boolean }; + timeout4Report: { message: string; shouldShow: boolean }; + generalError4Board: { message: string; shouldShow: boolean }; + generalError4Dora: { message: string; shouldShow: boolean }; + generalError4Report: { message: string; shouldShow: boolean }; } export interface IReportInfo extends IReportError { id: string; reportData: ReportResponseDTO | undefined; + shouldShowBoardMetricsError: boolean; + shouldShowPipelineMetricsError: boolean; + shouldShowSourceControlMetricsError: boolean; } export const initReportInfo: IReportInfo = { id: '', - timeout4Board: DEFAULT_MESSAGE, - timeout4Dora: DEFAULT_MESSAGE, - timeout4Report: DEFAULT_MESSAGE, - generalError4Board: DEFAULT_MESSAGE, - generalError4Dora: DEFAULT_MESSAGE, - generalError4Report: DEFAULT_MESSAGE, + timeout4Board: { message: DEFAULT_MESSAGE, shouldShow: true }, + timeout4Dora: { message: DEFAULT_MESSAGE, shouldShow: true }, + timeout4Report: { message: DEFAULT_MESSAGE, shouldShow: true }, + generalError4Board: { message: DEFAULT_MESSAGE, shouldShow: true }, + generalError4Dora: { message: DEFAULT_MESSAGE, shouldShow: true }, + generalError4Report: { message: DEFAULT_MESSAGE, shouldShow: true }, + shouldShowBoardMetricsError: true, + shouldShowPipelineMetricsError: true, + shouldShowSourceControlMetricsError: true, reportData: undefined, }; -const timeoutErrorKey = { +export const timeoutErrorKey = { [METRIC_TYPES.BOARD]: 'timeout4Board', [METRIC_TYPES.DORA]: 'timeout4Dora', [METRIC_TYPES.ALL]: 'timeout4Report', }; -const generalErrorKey = { +export const generalErrorKey = { [METRIC_TYPES.BOARD]: 'generalError4Board', [METRIC_TYPES.DORA]: 'generalError4Dora', [METRIC_TYPES.ALL]: 'generalError4Report', @@ -131,7 +141,7 @@ export const useGenerateReportEffect = (): useGenerateReportEffectInterface => { hasPollingStarted = false; }; - const assembleReportData = (response: ReportResponseDTO) => { + const assembleReportData = (response: ReportResponseDTO): ReportResponseDTO => { const exportValidityTime = exportValidityTimeMapper(response.exportValidityTime); return { ...response, exportValidityTime: exportValidityTime }; }; @@ -140,21 +150,21 @@ export const useGenerateReportEffect = (): useGenerateReportEffectInterface => { if (metricTypes.length === 2) { setReportInfos((preReportInfos) => { return preReportInfos.map((reportInfo) => { - reportInfo.timeout4Report = DEFAULT_MESSAGE; + reportInfo.timeout4Report = { message: DEFAULT_MESSAGE, shouldShow: true }; return reportInfo; }); }); } else if (metricTypes.includes(METRIC_TYPES.BOARD)) { setReportInfos((preReportInfos) => { return preReportInfos.map((reportInfo) => { - reportInfo.timeout4Board = DEFAULT_MESSAGE; + reportInfo.timeout4Board = { message: DEFAULT_MESSAGE, shouldShow: true }; return reportInfo; }); }); } else { setReportInfos((preReportInfos) => { return preReportInfos.map((reportInfo) => { - reportInfo.timeout4Dora = DEFAULT_MESSAGE; + reportInfo.timeout4Dora = { message: DEFAULT_MESSAGE, shouldShow: true }; return reportInfo; }); }); @@ -173,7 +183,7 @@ export const useGenerateReportEffect = (): useGenerateReportEffectInterface => { if (currentRes.status === 'rejected') { const source: METRIC_TYPES = metricTypes.length === 2 ? METRIC_TYPES.ALL : metricTypes[0]; const errorKey = getErrorKey(currentRes.reason, source) as keyof IReportError; - resInfo[errorKey] = DATA_LOADING_FAILED; + resInfo[errorKey] = { message: DATA_LOADING_FAILED, shouldShow: true }; } return resInfo; }); @@ -235,9 +245,12 @@ export const useGenerateReportEffect = (): useGenerateReportEffectInterface => { if (matchedRes.status === 'fulfilled') { const { response } = matchedRes.value; reportInfo.reportData = assembleReportData(response); + reportInfo.shouldShowBoardMetricsError = true; + reportInfo.shouldShowPipelineMetricsError = true; + reportInfo.shouldShowSourceControlMetricsError = true; } else { const errorKey = getErrorKey(matchedRes.reason, METRIC_TYPES.ALL) as keyof IReportError; - reportInfo[errorKey] = DATA_LOADING_FAILED; + reportInfo[errorKey] = { message: DATA_LOADING_FAILED, shouldShow: true }; } return reportInfo; }); @@ -248,16 +261,65 @@ export const useGenerateReportEffect = (): useGenerateReportEffectInterface => { setReportInfos((preInfos) => { return preInfos.map((info) => { if (pollingIds.includes(info.id)) { - info.timeout4Report = DEFAULT_MESSAGE; + info.timeout4Report = { message: DEFAULT_MESSAGE, shouldShow: true }; } return info; }); }); }; + const shutReportInfosErrorStatus = (id: string, errorKey: string) => { + setReportInfos((preReportInfos) => { + return preReportInfos.map((reportInfo) => { + if (reportInfo.id === id) { + const key = errorKey as keyof IReportError; + reportInfo[key].shouldShow = false; + } + return reportInfo; + }); + }); + }; + + const shutBoardMetricsError = (id: string) => { + setReportInfos((preReportInfos) => { + return preReportInfos.map((reportInfo) => { + if (reportInfo.id === id) { + reportInfo.shouldShowBoardMetricsError = false; + } + return reportInfo; + }); + }); + }; + + const shutPipelineMetricsError = (id: string) => { + setReportInfos((preReportInfos) => { + return preReportInfos.map((reportInfo) => { + if (reportInfo.id === id) { + reportInfo.shouldShowPipelineMetricsError = false; + } + return reportInfo; + }); + }); + }; + + const shutSourceControlMetricsError = (id: string) => { + setReportInfos((preReportInfos) => { + return preReportInfos.map((reportInfo) => { + if (reportInfo.id === id) { + reportInfo.shouldShowSourceControlMetricsError = false; + } + return reportInfo; + }); + }); + }; + return { startToRequestData, stopPollingReports, reportInfos, + shutReportInfosErrorStatus, + shutBoardMetricsError, + shutPipelineMetricsError, + shutSourceControlMetricsError, }; }; From e17b832a0aa4d36d74cc39c67947fd20cfb95d38 Mon Sep 17 00:00:00 2001 From: GuangbinMa Date: Fri, 26 Apr 2024 14:43:53 +0800 Subject: [PATCH 13/31] ADM-879: [frontend] feat: modify style --- .../src/components/Common/DateRangeViewer/index.tsx | 6 +++++- .../src/components/Common/DateRangeViewer/style.tsx | 11 ++--------- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/frontend/src/components/Common/DateRangeViewer/index.tsx b/frontend/src/components/Common/DateRangeViewer/index.tsx index 0d8df39a10..413c59221d 100644 --- a/frontend/src/components/Common/DateRangeViewer/index.tsx +++ b/frontend/src/components/Common/DateRangeViewer/index.tsx @@ -10,6 +10,7 @@ import { import React, { useRef, useState, forwardRef, useEffect, useCallback } from 'react'; import { DateRange } from '@src/context/config/configSlice'; import { formatDate } from '@src/utils/util'; +import { theme } from '@src/theme'; type Props = { dateRanges: DateRange; @@ -60,7 +61,10 @@ const DateRangeViewer = ({ dateRanges, changeDateRange, selectedDateRange, disab }); return ( - + {formatDate((selectedDateRange || dateRanges[0]).startDate as string)} {formatDate((selectedDateRange || dateRanges[0]).endDate as string)} diff --git a/frontend/src/components/Common/DateRangeViewer/style.tsx b/frontend/src/components/Common/DateRangeViewer/style.tsx index e1235b1fd1..760df7b6a7 100644 --- a/frontend/src/components/Common/DateRangeViewer/style.tsx +++ b/frontend/src/components/Common/DateRangeViewer/style.tsx @@ -4,11 +4,7 @@ import { Divider } from '@mui/material'; import styled from '@emotion/styled'; import { theme } from '@src/theme'; -interface DateRangeContainerProps { - disabled: boolean; -} - -export const DateRangeContainer = styled('div')(({ disabled }: DateRangeContainerProps) => ({ +export const DateRangeContainer = styled('div')(({ color }) => ({ position: 'relative', display: 'flex', justifyContent: 'flex-start', @@ -19,10 +15,7 @@ export const DateRangeContainer = styled('div')(({ disabled }: DateRangeContaine width: 'fit-content', padding: '.75rem', fontSize: '.875rem', - - ...(disabled && { - color: theme.palette.text.disabled, - }), + color: color, })); export const DateRangeExpandContainer = styled.div({ From 8f540e0bc4554d47d5a80bec9dc6a77a4b17fd97 Mon Sep 17 00:00:00 2001 From: Leiqiuhong Date: Fri, 26 Apr 2024 16:56:11 +0800 Subject: [PATCH 14/31] ADM-879-new feat: modify the notification opportunity --- frontend/src/containers/ReportStep/index.tsx | 34 +++++++++++-------- frontend/src/hooks/useGenerateReportEffect.ts | 18 ++++++---- 2 files changed, 31 insertions(+), 21 deletions(-) diff --git a/frontend/src/containers/ReportStep/index.tsx b/frontend/src/containers/ReportStep/index.tsx index 2542faffe8..d3624087c8 100644 --- a/frontend/src/containers/ReportStep/index.tsx +++ b/frontend/src/containers/ReportStep/index.tsx @@ -66,6 +66,7 @@ const ReportStep = ({ handleSave }: ReportStepProps) => { shutBoardMetricsError, shutPipelineMetricsError, shutSourceControlMetricsError, + getHasPollingStarted, } = useGenerateReportEffect(); useEffect(() => { @@ -229,7 +230,7 @@ const ReportStep = ({ handleSave }: ReportStepProps) => { // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - useLayoutEffect(() => { + useEffect(() => { exportValidityTimeMin && isCsvFileGeneratedAtEnd && dispatch( @@ -239,7 +240,7 @@ const ReportStep = ({ handleSave }: ReportStepProps) => { ); }, [dispatch, exportValidityTimeMin, isCsvFileGeneratedAtEnd]); - useLayoutEffect(() => { + useEffect(() => { if (exportValidityTimeMin && isCsvFileGeneratedAtEnd) { const startTime = Date.now(); const timer = setInterval(() => { @@ -264,17 +265,20 @@ const ReportStep = ({ handleSave }: ReportStepProps) => { } }, [dispatch, exportValidityTimeMin, isCsvFileGeneratedAtEnd]); - useLayoutEffect(() => { + useEffect(() => { dispatch(closeAllNotifications()); }, [dispatch, pageType]); useEffect(() => { - setExportValidityTimeMin(currentDataInfo.reportData?.exportValidityTime); - currentDataInfo.reportData && - setIsCsvFileGeneratedAtEnd( - currentDataInfo.reportData.allMetricsCompleted && currentDataInfo.reportData.isSuccessfulCreateCsvFile, - ); - }, [dispatch, currentDataInfo.reportData]); + console.log(getHasPollingStarted(), 123); + if (getHasPollingStarted()) return; + const successfulReportInfos = reportInfos.filter((reportInfo) => reportInfo.reportData); + if (successfulReportInfos.length === 0) return; + setExportValidityTimeMin(successfulReportInfos[0].reportData?.exportValidityTime); + setIsCsvFileGeneratedAtEnd( + successfulReportInfos.some((reportInfo) => reportInfo.reportData?.isSuccessfulCreateCsvFile), + ); + }, [dispatch, reportInfos]); useEffect(() => { if (isSummaryPage && notifications4SummaryPage.length > 0) { @@ -328,7 +332,7 @@ const ReportStep = ({ handleSave }: ReportStepProps) => { useEffect(() => { if (!currentDataInfo.timeout4Report.shouldShow) return; - currentDataInfo.timeout4Report && + currentDataInfo.timeout4Report.message && setNotifications4SummaryPage((prevState) => [ ...prevState, { @@ -341,7 +345,7 @@ const ReportStep = ({ handleSave }: ReportStepProps) => { useEffect(() => { if (!currentDataInfo.timeout4Board.shouldShow) return; - currentDataInfo.timeout4Board && + currentDataInfo.timeout4Board.message && setNotifications4SummaryPage((prevState) => [ ...prevState, { @@ -354,7 +358,7 @@ const ReportStep = ({ handleSave }: ReportStepProps) => { useEffect(() => { if (!currentDataInfo.timeout4Dora.shouldShow) return; - currentDataInfo.timeout4Dora && + currentDataInfo.timeout4Dora.message && setNotifications4SummaryPage((prevState) => [ ...prevState, { @@ -367,7 +371,7 @@ const ReportStep = ({ handleSave }: ReportStepProps) => { useEffect(() => { if (!currentDataInfo.generalError4Board.shouldShow) return; - currentDataInfo.generalError4Board && + currentDataInfo.generalError4Board.message && setNotifications4SummaryPage((prevState) => [ ...prevState, { @@ -380,7 +384,7 @@ const ReportStep = ({ handleSave }: ReportStepProps) => { useEffect(() => { if (!currentDataInfo.generalError4Dora.shouldShow) return; - currentDataInfo.generalError4Dora && + currentDataInfo.generalError4Dora.message && setNotifications4SummaryPage((prevState) => [ ...prevState, { @@ -393,7 +397,7 @@ const ReportStep = ({ handleSave }: ReportStepProps) => { useEffect(() => { if (!currentDataInfo.generalError4Report.shouldShow) return; - currentDataInfo.generalError4Report && + currentDataInfo.generalError4Report.message && setNotifications4SummaryPage((prevState) => [ ...prevState, { diff --git a/frontend/src/hooks/useGenerateReportEffect.ts b/frontend/src/hooks/useGenerateReportEffect.ts index 3102ae305e..ef243f479f 100644 --- a/frontend/src/hooks/useGenerateReportEffect.ts +++ b/frontend/src/hooks/useGenerateReportEffect.ts @@ -23,6 +23,7 @@ export interface useGenerateReportEffectInterface { shutBoardMetricsError: (id: string) => void; shutPipelineMetricsError: (id: string) => void; shutSourceControlMetricsError: (id: string) => void; + getHasPollingStarted: () => boolean; } interface IReportError { @@ -80,7 +81,7 @@ export const useGenerateReportEffect = (): useGenerateReportEffectInterface => { const [reportInfos, setReportInfos] = useState( dateRanges.map((dateRange) => ({ ...initReportInfo, id: dateRange?.startDate || '' })), ); - let hasPollingStarted = false; + const hasPollingStarted = useRef(false); const startToRequestData = async (params: ReportRequestDTO) => { const { metricTypes } = params; @@ -100,8 +101,8 @@ export const useGenerateReportEffect = (): useGenerateReportEffectInterface => { updateErrorAfterFetchReport(res, metricTypes); - if (hasPollingStarted) return; - hasPollingStarted = true; + if (hasPollingStarted.current) return; + hasPollingStarted.current = true; const { pollingInfos, pollingInterval } = assemblePollingParams(res); @@ -138,7 +139,7 @@ export const useGenerateReportEffect = (): useGenerateReportEffectInterface => { const stopPollingReports = () => { window.clearTimeout(timerIdRef.current); - hasPollingStarted = false; + hasPollingStarted.current = false; }; const assembleReportData = (response: ReportResponseDTO): ReportResponseDTO => { @@ -204,7 +205,7 @@ export const useGenerateReportEffect = (): useGenerateReportEffectInterface => { return { callbackUrl: (v as PromiseFulfilledResult).value.callbackUrl, id: v.id }; }); - const pollingInterval = (fulfilledResponses[0] as PromiseFulfilledResult).value.interval; + const pollingInterval = (fulfilledResponses[0] as PromiseFulfilledResult)?.value.interval; return { pollingInfos, pollingInterval }; }; @@ -230,7 +231,7 @@ export const useGenerateReportEffect = (): useGenerateReportEffectInterface => { (pollingResponseWithId) => pollingResponseWithId.status === 'fulfilled' && !pollingResponseWithId.value.response.allMetricsCompleted && - hasPollingStarted, + hasPollingStarted.current, ) .map((pollingResponseWithId) => pollingInfos.find((pollingInfo) => pollingInfo.id === pollingResponseWithId.id)!); return nextPollingInfos; @@ -313,6 +314,10 @@ export const useGenerateReportEffect = (): useGenerateReportEffectInterface => { }); }; + const getHasPollingStarted = () => { + return hasPollingStarted.current; + }; + return { startToRequestData, stopPollingReports, @@ -321,5 +326,6 @@ export const useGenerateReportEffect = (): useGenerateReportEffectInterface => { shutBoardMetricsError, shutPipelineMetricsError, shutSourceControlMetricsError, + getHasPollingStarted, }; }; From bfcf1e04e6c347d52f9145f5fed39c026f5df86c Mon Sep 17 00:00:00 2001 From: Leiqiuhong Date: Fri, 26 Apr 2024 17:26:58 +0800 Subject: [PATCH 15/31] ADM-879-new feat: remove the notifications when change dateRange in report page --- frontend/src/containers/ReportStep/index.tsx | 70 +++++++++++++++++--- 1 file changed, 59 insertions(+), 11 deletions(-) diff --git a/frontend/src/containers/ReportStep/index.tsx b/frontend/src/containers/ReportStep/index.tsx index d3624087c8..fd4adbfa33 100644 --- a/frontend/src/containers/ReportStep/index.tsx +++ b/frontend/src/containers/ReportStep/index.tsx @@ -21,6 +21,12 @@ import { timeoutErrorKey, useGenerateReportEffect, } from '@src/hooks/useGenerateReportEffect'; +import { + addNotification, + closeAllNotifications, + closeNotification, + Notification, +} from '@src/context/notification/NotificationSlice'; import { BOARD_METRICS, CALENDAR, @@ -29,7 +35,6 @@ import { REPORT_PAGE_TYPE, REQUIRED_DATA, } from '@src/constants/resources'; -import { addNotification, closeAllNotifications, Notification } from '@src/context/notification/NotificationSlice'; import { IPipelineConfig, selectMetricsContent } from '@src/context/Metrics/metricsSlice'; import { backStep, selectTimeStamp } from '@src/context/stepper/StepperSlice'; import React, { useEffect, useLayoutEffect, useMemo, useState } from 'react'; @@ -43,6 +48,7 @@ import { useAppDispatch } from '@src/hooks/useAppDispatch'; import { BoardDetail, DoraDetail } from './ReportDetail'; import { METRIC_TYPES } from '@src/constants/commons'; import { useAppSelector } from '@src/hooks'; +import { uniqueId } from 'lodash'; import get from 'lodash/get'; export interface ReportStepProps { @@ -69,14 +75,11 @@ const ReportStep = ({ handleSave }: ReportStepProps) => { getHasPollingStarted, } = useGenerateReportEffect(); - useEffect(() => { - setCurrentDataInfo(reportInfos.find((singleResult) => singleResult.id === selectedDateRange.startDate)!); - }, [reportInfos, selectedDateRange]); - const [exportValidityTimeMin, setExportValidityTimeMin] = useState(undefined); const [pageType, setPageType] = useState(REPORT_PAGE_TYPE.SUMMARY); const [isCsvFileGeneratedAtEnd, setIsCsvFileGeneratedAtEnd] = useState(false); const [notifications4SummaryPage, setNotifications4SummaryPage] = useState[]>([]); + const [notificationIds, setNotificationIds] = useState([]); const csvTimeStamp = useAppSelector(selectTimeStamp); const { @@ -222,6 +225,14 @@ const ReportStep = ({ handleSave }: ReportStepProps) => { jiraBoardSetting: undefined, }; + useEffect(() => { + notificationIds.forEach((notificationId) => { + closeNotification(notificationId); + }); + setNotificationIds([]); + setCurrentDataInfo(reportInfos.find((singleResult) => singleResult.id === selectedDateRange.startDate)!); + }, [reportInfos, selectedDateRange]); + useEffect(() => { setPageType(onlySelectClassification ? REPORT_PAGE_TYPE.BOARD : REPORT_PAGE_TYPE.SUMMARY); return () => { @@ -291,9 +302,12 @@ const ReportStep = ({ handleSave }: ReportStepProps) => { useEffect(() => { if (!currentDataInfo.shouldShowBoardMetricsError) return; if (currentDataInfo.reportData?.reportMetricsError.boardMetricsError) { + const notificationId = uniqueId(); + setNotificationIds((pre) => [...pre, notificationId]); setNotifications4SummaryPage((prevState) => [ ...prevState, { + id: notificationId, message: MESSAGE.FAILED_TO_GET_DATA('Board Metrics'), type: 'error', }, @@ -305,9 +319,12 @@ const ReportStep = ({ handleSave }: ReportStepProps) => { useEffect(() => { if (!currentDataInfo.shouldShowPipelineMetricsError) return; if (currentDataInfo.reportData?.reportMetricsError.pipelineMetricsError) { + const notificationId = uniqueId(); + setNotificationIds((pre) => [...pre, notificationId]); setNotifications4SummaryPage((prevState) => [ ...prevState, { + id: notificationId, message: MESSAGE.FAILED_TO_GET_DATA('Buildkite'), type: 'error', }, @@ -319,9 +336,12 @@ const ReportStep = ({ handleSave }: ReportStepProps) => { useEffect(() => { if (!currentDataInfo.shouldShowSourceControlMetricsError) return; if (currentDataInfo.reportData?.reportMetricsError.sourceControlMetricsError) { + const notificationId = uniqueId(); + setNotificationIds((pre) => [...pre, notificationId]); setNotifications4SummaryPage((prevState) => [ ...prevState, { + id: notificationId, message: MESSAGE.FAILED_TO_GET_DATA('GitHub'), type: 'error', }, @@ -332,79 +352,107 @@ const ReportStep = ({ handleSave }: ReportStepProps) => { useEffect(() => { if (!currentDataInfo.timeout4Report.shouldShow) return; - currentDataInfo.timeout4Report.message && + if (currentDataInfo.timeout4Report.message) { + const notificationId = uniqueId(); + setNotificationIds((pre) => [...pre, notificationId]); setNotifications4SummaryPage((prevState) => [ ...prevState, { + id: notificationId, message: MESSAGE.LOADING_TIMEOUT('Report'), type: 'error', }, ]); + } shutReportInfosErrorStatus(selectedDateRange.startDate as string, timeoutErrorKey[METRIC_TYPES.ALL]); }, [currentDataInfo.timeout4Report]); useEffect(() => { if (!currentDataInfo.timeout4Board.shouldShow) return; - currentDataInfo.timeout4Board.message && + if (currentDataInfo.timeout4Board.message) { + const notificationId = uniqueId(); + setNotificationIds((pre) => [...pre, notificationId]); setNotifications4SummaryPage((prevState) => [ ...prevState, { + id: notificationId, message: MESSAGE.LOADING_TIMEOUT('Board metrics'), type: 'error', }, ]); + } + shutReportInfosErrorStatus(selectedDateRange.startDate as string, timeoutErrorKey[METRIC_TYPES.BOARD]); }, [currentDataInfo.timeout4Board]); useEffect(() => { if (!currentDataInfo.timeout4Dora.shouldShow) return; - currentDataInfo.timeout4Dora.message && + if (currentDataInfo.timeout4Dora.message) { + const notificationId = uniqueId(); + setNotificationIds((pre) => [...pre, notificationId]); setNotifications4SummaryPage((prevState) => [ ...prevState, { + id: notificationId, message: MESSAGE.LOADING_TIMEOUT('DORA metrics'), type: 'error', }, ]); + } + shutReportInfosErrorStatus(selectedDateRange.startDate as string, timeoutErrorKey[METRIC_TYPES.DORA]); }, [currentDataInfo.timeout4Dora]); useEffect(() => { if (!currentDataInfo.generalError4Board.shouldShow) return; - currentDataInfo.generalError4Board.message && + if (currentDataInfo.generalError4Board.message) { + const notificationId = uniqueId(); + setNotificationIds((pre) => [...pre, notificationId]); setNotifications4SummaryPage((prevState) => [ ...prevState, { + id: notificationId, message: MESSAGE.FAILED_TO_REQUEST, type: 'error', }, ]); + } + shutReportInfosErrorStatus(selectedDateRange.startDate as string, generalErrorKey[METRIC_TYPES.BOARD]); }, [currentDataInfo.generalError4Board]); useEffect(() => { if (!currentDataInfo.generalError4Dora.shouldShow) return; - currentDataInfo.generalError4Dora.message && + if (currentDataInfo.generalError4Dora.message) { + const notificationId = uniqueId(); + setNotificationIds((pre) => [...pre, notificationId]); setNotifications4SummaryPage((prevState) => [ ...prevState, { + id: notificationId, message: MESSAGE.FAILED_TO_REQUEST, type: 'error', }, ]); + } shutReportInfosErrorStatus(selectedDateRange.startDate as string, generalErrorKey[METRIC_TYPES.DORA]); }, [currentDataInfo.generalError4Dora]); useEffect(() => { if (!currentDataInfo.generalError4Report.shouldShow) return; - currentDataInfo.generalError4Report.message && + if (currentDataInfo.generalError4Report.message) { + const notificationId = uniqueId(); + setNotificationIds((pre) => [...pre, notificationId]); setNotifications4SummaryPage((prevState) => [ ...prevState, { + id: notificationId, message: MESSAGE.FAILED_TO_REQUEST, type: 'error', }, ]); + } + shutReportInfosErrorStatus(selectedDateRange.startDate as string, generalErrorKey[METRIC_TYPES.ALL]); }, [currentDataInfo.generalError4Report]); From b311a274f079a052cd28580cbd53577ce3a2aa07 Mon Sep 17 00:00:00 2001 From: Leiqiuhong Date: Sun, 28 Apr 2024 14:17:45 +0800 Subject: [PATCH 16/31] ADM-879-new fix: fix notification --- frontend/src/containers/ReportStep/index.tsx | 19 ++++++++---- frontend/src/hooks/useGenerateReportEffect.ts | 29 +++++++++---------- 2 files changed, 27 insertions(+), 21 deletions(-) diff --git a/frontend/src/containers/ReportStep/index.tsx b/frontend/src/containers/ReportStep/index.tsx index fd4adbfa33..b7f54624a6 100644 --- a/frontend/src/containers/ReportStep/index.tsx +++ b/frontend/src/containers/ReportStep/index.tsx @@ -37,13 +37,13 @@ import { } from '@src/constants/resources'; import { IPipelineConfig, selectMetricsContent } from '@src/context/Metrics/metricsSlice'; import { backStep, selectTimeStamp } from '@src/context/stepper/StepperSlice'; -import React, { useEffect, useLayoutEffect, useMemo, useState } from 'react'; import { StyledCalendarWrapper } from '@src/containers/ReportStep/style'; import { ReportButtonGroup } from '@src/containers/ReportButtonGroup'; import DateRangeViewer from '@src/components/Common/DateRangeViewer'; import { ReportResponseDTO } from '@src/clients/report/dto/response'; import BoardMetrics from '@src/containers/ReportStep/BoardMetrics'; import DoraMetrics from '@src/containers/ReportStep/DoraMetrics'; +import React, { useEffect, useMemo, useState } from 'react'; import { useAppDispatch } from '@src/hooks/useAppDispatch'; import { BoardDetail, DoraDetail } from './ReportDetail'; import { METRIC_TYPES } from '@src/constants/commons'; @@ -72,7 +72,7 @@ const ReportStep = ({ handleSave }: ReportStepProps) => { shutBoardMetricsError, shutPipelineMetricsError, shutSourceControlMetricsError, - getHasPollingStarted, + hasPollingStarted, } = useGenerateReportEffect(); const [exportValidityTimeMin, setExportValidityTimeMin] = useState(undefined); @@ -231,6 +231,7 @@ const ReportStep = ({ handleSave }: ReportStepProps) => { }); setNotificationIds([]); setCurrentDataInfo(reportInfos.find((singleResult) => singleResult.id === selectedDateRange.startDate)!); + // eslint-disable-next-line react-hooks/exhaustive-deps }, [reportInfos, selectedDateRange]); useEffect(() => { @@ -281,15 +282,14 @@ const ReportStep = ({ handleSave }: ReportStepProps) => { }, [dispatch, pageType]); useEffect(() => { - console.log(getHasPollingStarted(), 123); - if (getHasPollingStarted()) return; + if (hasPollingStarted) return; const successfulReportInfos = reportInfos.filter((reportInfo) => reportInfo.reportData); if (successfulReportInfos.length === 0) return; setExportValidityTimeMin(successfulReportInfos[0].reportData?.exportValidityTime); setIsCsvFileGeneratedAtEnd( successfulReportInfos.some((reportInfo) => reportInfo.reportData?.isSuccessfulCreateCsvFile), ); - }, [dispatch, reportInfos]); + }, [dispatch, reportInfos, hasPollingStarted]); useEffect(() => { if (isSummaryPage && notifications4SummaryPage.length > 0) { @@ -314,6 +314,7 @@ const ReportStep = ({ handleSave }: ReportStepProps) => { ]); } shutBoardMetricsError(selectedDateRange.startDate as string); + // eslint-disable-next-line react-hooks/exhaustive-deps }, [currentDataInfo.reportData?.reportMetricsError.boardMetricsError]); useEffect(() => { @@ -331,6 +332,7 @@ const ReportStep = ({ handleSave }: ReportStepProps) => { ]); } shutPipelineMetricsError(selectedDateRange.startDate as string); + // eslint-disable-next-line react-hooks/exhaustive-deps }, [currentDataInfo.reportData?.reportMetricsError.pipelineMetricsError]); useEffect(() => { @@ -348,6 +350,7 @@ const ReportStep = ({ handleSave }: ReportStepProps) => { ]); } shutSourceControlMetricsError(selectedDateRange.startDate as string); + // eslint-disable-next-line react-hooks/exhaustive-deps }, [currentDataInfo.reportData?.reportMetricsError.sourceControlMetricsError]); useEffect(() => { @@ -365,6 +368,7 @@ const ReportStep = ({ handleSave }: ReportStepProps) => { ]); } shutReportInfosErrorStatus(selectedDateRange.startDate as string, timeoutErrorKey[METRIC_TYPES.ALL]); + // eslint-disable-next-line react-hooks/exhaustive-deps }, [currentDataInfo.timeout4Report]); useEffect(() => { @@ -383,6 +387,7 @@ const ReportStep = ({ handleSave }: ReportStepProps) => { } shutReportInfosErrorStatus(selectedDateRange.startDate as string, timeoutErrorKey[METRIC_TYPES.BOARD]); + // eslint-disable-next-line react-hooks/exhaustive-deps }, [currentDataInfo.timeout4Board]); useEffect(() => { @@ -401,6 +406,7 @@ const ReportStep = ({ handleSave }: ReportStepProps) => { } shutReportInfosErrorStatus(selectedDateRange.startDate as string, timeoutErrorKey[METRIC_TYPES.DORA]); + // eslint-disable-next-line react-hooks/exhaustive-deps }, [currentDataInfo.timeout4Dora]); useEffect(() => { @@ -419,6 +425,7 @@ const ReportStep = ({ handleSave }: ReportStepProps) => { } shutReportInfosErrorStatus(selectedDateRange.startDate as string, generalErrorKey[METRIC_TYPES.BOARD]); + // eslint-disable-next-line react-hooks/exhaustive-deps }, [currentDataInfo.generalError4Board]); useEffect(() => { @@ -436,6 +443,7 @@ const ReportStep = ({ handleSave }: ReportStepProps) => { ]); } shutReportInfosErrorStatus(selectedDateRange.startDate as string, generalErrorKey[METRIC_TYPES.DORA]); + // eslint-disable-next-line react-hooks/exhaustive-deps }, [currentDataInfo.generalError4Dora]); useEffect(() => { @@ -454,6 +462,7 @@ const ReportStep = ({ handleSave }: ReportStepProps) => { } shutReportInfosErrorStatus(selectedDateRange.startDate as string, generalErrorKey[METRIC_TYPES.ALL]); + // eslint-disable-next-line react-hooks/exhaustive-deps }, [currentDataInfo.generalError4Report]); useEffect(() => { diff --git a/frontend/src/hooks/useGenerateReportEffect.ts b/frontend/src/hooks/useGenerateReportEffect.ts index ef243f479f..ce56de78aa 100644 --- a/frontend/src/hooks/useGenerateReportEffect.ts +++ b/frontend/src/hooks/useGenerateReportEffect.ts @@ -23,7 +23,7 @@ export interface useGenerateReportEffectInterface { shutBoardMetricsError: (id: string) => void; shutPipelineMetricsError: (id: string) => void; shutSourceControlMetricsError: (id: string) => void; - getHasPollingStarted: () => boolean; + hasPollingStarted: boolean; } interface IReportError { @@ -81,8 +81,8 @@ export const useGenerateReportEffect = (): useGenerateReportEffectInterface => { const [reportInfos, setReportInfos] = useState( dateRanges.map((dateRange) => ({ ...initReportInfo, id: dateRange?.startDate || '' })), ); - const hasPollingStarted = useRef(false); - + const [hasPollingStarted, setHasPollingStarted] = useState(false); + let nextHasPollingStarted = false; const startToRequestData = async (params: ReportRequestDTO) => { const { metricTypes } = params; resetTimeoutMessage(metricTypes); @@ -101,8 +101,9 @@ export const useGenerateReportEffect = (): useGenerateReportEffectInterface => { updateErrorAfterFetchReport(res, metricTypes); - if (hasPollingStarted.current) return; - hasPollingStarted.current = true; + if (hasPollingStarted) return; + nextHasPollingStarted = true; + setHasPollingStarted(nextHasPollingStarted); const { pollingInfos, pollingInterval } = assemblePollingParams(res); @@ -116,10 +117,6 @@ export const useGenerateReportEffect = (): useGenerateReportEffectInterface => { pollingInfos: Record[]; interval: number; }) => { - if (pollingInfos.length === 0) { - stopPollingReports(); - return; - } const pollingIds: string[] = pollingInfos.map((pollingInfo) => pollingInfo.id); initReportInfosTimeout4Report(pollingIds); @@ -132,6 +129,10 @@ export const useGenerateReportEffect = (): useGenerateReportEffectInterface => { updateReportInfosAfterPolling(pollingResponsesWithId); const nextPollingInfos = getNextPollingInfos(pollingResponsesWithId, pollingInfos); + if (nextPollingInfos.length === 0) { + stopPollingReports(); + return; + } timerIdRef.current = window.setTimeout(() => { pollingReport({ pollingInfos: nextPollingInfos, interval }); }, interval * 1000); @@ -139,7 +140,7 @@ export const useGenerateReportEffect = (): useGenerateReportEffectInterface => { const stopPollingReports = () => { window.clearTimeout(timerIdRef.current); - hasPollingStarted.current = false; + setHasPollingStarted(false); }; const assembleReportData = (response: ReportResponseDTO): ReportResponseDTO => { @@ -231,7 +232,7 @@ export const useGenerateReportEffect = (): useGenerateReportEffectInterface => { (pollingResponseWithId) => pollingResponseWithId.status === 'fulfilled' && !pollingResponseWithId.value.response.allMetricsCompleted && - hasPollingStarted.current, + nextHasPollingStarted, ) .map((pollingResponseWithId) => pollingInfos.find((pollingInfo) => pollingInfo.id === pollingResponseWithId.id)!); return nextPollingInfos; @@ -314,10 +315,6 @@ export const useGenerateReportEffect = (): useGenerateReportEffectInterface => { }); }; - const getHasPollingStarted = () => { - return hasPollingStarted.current; - }; - return { startToRequestData, stopPollingReports, @@ -326,6 +323,6 @@ export const useGenerateReportEffect = (): useGenerateReportEffectInterface => { shutBoardMetricsError, shutPipelineMetricsError, shutSourceControlMetricsError, - getHasPollingStarted, + hasPollingStarted, }; }; From 57cc4783b7adaf2ce8d7915cca1a9d2392b7bd5c Mon Sep 17 00:00:00 2001 From: Leiqiuhong Date: Sun, 28 Apr 2024 17:02:56 +0800 Subject: [PATCH 17/31] ADM-879-new fix: fix test --- .../hooks/useGenerateReportEffect.test.tsx | 171 +++++++++++------- .../Common/DateRangeViewer/index.tsx | 2 +- frontend/src/containers/ReportStep/index.tsx | 4 +- 3 files changed, 109 insertions(+), 68 deletions(-) diff --git a/frontend/__tests__/hooks/useGenerateReportEffect.test.tsx b/frontend/__tests__/hooks/useGenerateReportEffect.test.tsx index cf7411582c..641069c3df 100644 --- a/frontend/__tests__/hooks/useGenerateReportEffect.test.tsx +++ b/frontend/__tests__/hooks/useGenerateReportEffect.test.tsx @@ -7,7 +7,12 @@ import { UnknownError } from '@src/errors/UnknownError'; import { HttpStatusCode } from 'axios'; import clearAllMocks = jest.clearAllMocks; import resetAllMocks = jest.resetAllMocks; +import { AXIOS_REQUEST_ERROR_CODE } from '@src/constants/resources'; +import { updateDateRange } from '@src/context/config/configSlice'; +import { setupStore } from '@test/utils/setupStoreUtil'; import { METRIC_TYPES } from '@src/constants/commons'; +import React, { ReactNode } from 'react'; +import { Provider } from 'react-redux'; const MOCK_GENERATE_REPORT_REQUEST_PARAMS_WITH_BOARD_METRIC_TYPE = { ...MOCK_GENERATE_REPORT_REQUEST_PARAMS, @@ -18,11 +23,37 @@ const MOCK_GENERATE_REPORT_REQUEST_PARAMS_WITH_DORA_METRIC_TYPE = { metricTypes: [METRIC_TYPES.DORA], }; +let store = setupStore(); + +const Wrapper = ({ children }: { children: ReactNode }) => { + return {children}; +}; + +const setup = () => + renderHook(() => useGenerateReportEffect(), { + wrapper: Wrapper, + }); + +// jest.useFakeTimers(); + describe('use generate report effect', () => { afterAll(() => { clearAllMocks(); }); beforeEach(() => { + store = setupStore(); + store.dispatch( + updateDateRange([ + { + startDate: '2024-02-04T00:00:00.000+08:00', + endDate: '2024-02-17T23:59:59.999+08:00', + }, + { + startDate: '2024-02-18T00:00:00.000+08:00', + endDate: '2024-02-28T23:59:59.999+08:00', + }, + ]), + ); jest.useFakeTimers(); }); afterEach(() => { @@ -30,26 +61,57 @@ describe('use generate report effect', () => { jest.useRealTimers(); }); - it('should set "Data loading failed" for board metrics when board data retrieval times out', async () => { - reportClient.retrieveByUrl = jest.fn().mockRejectedValue(new TimeoutError('5xx error', 503)); + it('should set "Data loading failed" for all board metrics when board data retrieval times out', async () => { + reportClient.retrieveByUrl = jest + .fn() + .mockRejectedValue(new TimeoutError('timeout error', AXIOS_REQUEST_ERROR_CODE.TIMEOUT)); + reportClient.polling = jest.fn(); - const { result } = renderHook(() => useGenerateReportEffect()); + const { result } = setup(); - await waitFor(() => { - result.current.startToRequestData(MOCK_GENERATE_REPORT_REQUEST_PARAMS_WITH_BOARD_METRIC_TYPE); - expect(result.current.timeout4Board).toEqual('Data loading failed'); + await act(async () => { + await result.current.startToRequestData(MOCK_GENERATE_REPORT_REQUEST_PARAMS_WITH_BOARD_METRIC_TYPE); }); + + expect(result.current.reportInfos.length).toEqual(2); + expect(result.current.reportInfos[0].timeout4Board.message).toEqual('Data loading failed'); + expect(result.current.reportInfos[0].timeout4Board.shouldShow).toEqual(true); + expect(result.current.reportInfos[0].reportData).toEqual(undefined); + expect(result.current.reportInfos[1].timeout4Board.message).toEqual('Data loading failed'); + expect(result.current.reportInfos[1].timeout4Board.shouldShow).toEqual(true); + expect(result.current.reportInfos[1].reportData).toEqual(undefined); + expect(reportClient.polling).toHaveBeenCalledTimes(0); }); - it('should call polling report and setTimeout when request board data given pollingReport response return 204', async () => { + it('should set "Data loading failed" for dora metrics when dora data retrieval times out', async () => { + reportClient.retrieveByUrl = jest + .fn() + .mockRejectedValueOnce(new TimeoutError('timeout error', AXIOS_REQUEST_ERROR_CODE.TIMEOUT)) + .mockResolvedValueOnce(async () => MOCK_RETRIEVE_REPORT_RESPONSE); + reportClient.polling = jest .fn() - .mockImplementation(async () => ({ status: HttpStatusCode.NoContent, response: MOCK_REPORT_RESPONSE })); - reportClient.retrieveByUrl = jest + .mockImplementation(async () => ({ status: HttpStatusCode.Ok, response: MOCK_REPORT_RESPONSE })); + const { result } = setup(); + + await act(async () => { + await result.current.startToRequestData(MOCK_GENERATE_REPORT_REQUEST_PARAMS_WITH_DORA_METRIC_TYPE); + }); + + expect(result.current.reportInfos[0].timeout4Dora.message).toEqual('Data loading failed'); + expect(result.current.reportInfos[0].timeout4Dora.shouldShow).toEqual(true); + expect(result.current.reportInfos[0].reportData).toEqual(undefined); + expect(result.current.reportInfos[1].timeout4Dora.message).toEqual(''); + expect(result.current.reportInfos[1].reportData).toBeTruthy(); + }); + + it('should call polling report and setTimeout when request board data given pollingReport response return 200', async () => { + reportClient.polling = jest .fn() - .mockImplementation(async () => ({ response: MOCK_RETRIEVE_REPORT_RESPONSE })); + .mockImplementation(async () => ({ status: HttpStatusCode.Ok, response: MOCK_REPORT_RESPONSE })); + reportClient.retrieveByUrl = jest.fn().mockImplementation(async () => MOCK_RETRIEVE_REPORT_RESPONSE); - const { result } = renderHook(() => useGenerateReportEffect()); + const { result } = setup(); await waitFor(() => { result.current.startToRequestData(MOCK_GENERATE_REPORT_REQUEST_PARAMS_WITH_BOARD_METRIC_TYPE); @@ -58,81 +120,60 @@ describe('use generate report effect', () => { jest.runOnlyPendingTimers(); await waitFor(() => { - expect(reportClient.polling).toHaveBeenCalledTimes(1); + expect(reportClient.polling).toHaveBeenCalledTimes(2); }); }); it('should call polling report more than one time when metrics is loading', async () => { reportClient.polling = jest.fn().mockImplementation(async () => ({ - status: HttpStatusCode.NoContent, + status: HttpStatusCode.Ok, response: { ...MOCK_REPORT_RESPONSE, allMetricsCompleted: false }, })); + reportClient.polling = jest + .fn() + .mockReturnValueOnce({ + status: HttpStatusCode.Ok, + response: { ...MOCK_REPORT_RESPONSE, allMetricsCompleted: false }, + }) + .mockReturnValueOnce({ + status: HttpStatusCode.Ok, + response: { ...MOCK_REPORT_RESPONSE, allMetricsCompleted: true }, + }) + .mockReturnValueOnce({ + status: HttpStatusCode.Ok, + response: { ...MOCK_REPORT_RESPONSE, allMetricsCompleted: true }, + }); reportClient.retrieveByUrl = jest .fn() - .mockImplementation(async () => ({ response: MOCK_RETRIEVE_REPORT_RESPONSE })); + .mockReturnValueOnce(MOCK_RETRIEVE_REPORT_RESPONSE) + .mockReturnValueOnce({ ...MOCK_RETRIEVE_REPORT_RESPONSE, callbackUrl: '/url/1234' }); - const { result } = renderHook(() => useGenerateReportEffect()); + const { result } = setup(); - await waitFor(() => { - result.current.startToRequestData(MOCK_GENERATE_REPORT_REQUEST_PARAMS); - }); - act(() => { + await act(async () => { + await result.current.startToRequestData(MOCK_GENERATE_REPORT_REQUEST_PARAMS); jest.advanceTimersByTime(10000); }); - await waitFor(() => { - expect(reportClient.polling).toHaveBeenCalledTimes(2); - }); + expect(reportClient.polling).toHaveBeenCalledTimes(3); }); it('should call polling report only once when request board data given dora data retrieval is called before', async () => { reportClient.polling = jest .fn() - .mockImplementation(async () => ({ status: HttpStatusCode.NoContent, response: MOCK_REPORT_RESPONSE })); - reportClient.retrieveByUrl = jest - .fn() - .mockImplementation(async () => ({ response: MOCK_RETRIEVE_REPORT_RESPONSE })); + .mockImplementation(async () => ({ status: HttpStatusCode.Ok, response: MOCK_REPORT_RESPONSE })); + reportClient.retrieveByUrl = jest.fn().mockImplementation(async () => MOCK_RETRIEVE_REPORT_RESPONSE); - const { result } = renderHook(() => useGenerateReportEffect()); + const { result } = setup(); - await waitFor(() => { - result.current.startToRequestData(MOCK_GENERATE_REPORT_REQUEST_PARAMS_WITH_BOARD_METRIC_TYPE); - result.current.startToRequestData(MOCK_GENERATE_REPORT_REQUEST_PARAMS_WITH_DORA_METRIC_TYPE); + await waitFor(async () => { + await result.current.startToRequestData(MOCK_GENERATE_REPORT_REQUEST_PARAMS_WITH_BOARD_METRIC_TYPE); + await result.current.startToRequestData(MOCK_GENERATE_REPORT_REQUEST_PARAMS_WITH_DORA_METRIC_TYPE); }); jest.runOnlyPendingTimers(); - await waitFor(() => { - expect(reportClient.polling).toHaveBeenCalledTimes(1); - }); - }); - - it('should set "Data loading failed" for dora metrics when dora data retrieval times out', async () => { - reportClient.retrieveByUrl = jest.fn().mockRejectedValue(new TimeoutError('5xx error', 503)); - - const { result } = renderHook(() => useGenerateReportEffect()); - - await waitFor(() => { - result.current.startToRequestData(MOCK_GENERATE_REPORT_REQUEST_PARAMS_WITH_DORA_METRIC_TYPE); - expect(result.current.timeout4Dora).toEqual('Data loading failed'); - }); - }); - - it('should set "Data loading failed" for report when polling times out', async () => { - reportClient.polling = jest.fn().mockImplementation(async () => { - throw new TimeoutError('5xx error', 503); - }); - - reportClient.retrieveByUrl = jest - .fn() - .mockImplementation(async () => ({ response: MOCK_RETRIEVE_REPORT_RESPONSE })); - - const { result } = renderHook(() => useGenerateReportEffect()); - - await waitFor(() => { - result.current.startToRequestData(MOCK_GENERATE_REPORT_REQUEST_PARAMS); - expect(result.current.timeout4Report).toEqual('Data loading failed'); - }); + expect(reportClient.polling).toHaveBeenCalledTimes(2); }); it('should call polling report and setTimeout when request dora data given pollingReport response return 204', async () => { @@ -187,7 +228,7 @@ describe('use generate report effect', () => { await waitFor(() => { result.current.startToRequestData(MOCK_GENERATE_REPORT_REQUEST_PARAMS_WITH_BOARD_METRIC_TYPE); - expect(result.current.generalError4Board).toEqual('Data loading failed'); + expect(result.current.reportInfos[0].generalError4Board).toEqual('Data loading failed'); }); }); @@ -198,7 +239,7 @@ describe('use generate report effect', () => { await waitFor(() => { result.current.startToRequestData(MOCK_GENERATE_REPORT_REQUEST_PARAMS_WITH_DORA_METRIC_TYPE); - expect(result.current.generalError4Dora).toEqual('Data loading failed'); + expect(result.current.reportInfos[0].generalError4Dora).toEqual('Data loading failed'); }); }); @@ -212,7 +253,7 @@ describe('use generate report effect', () => { await waitFor(() => { result.current.startToRequestData(MOCK_GENERATE_REPORT_REQUEST_PARAMS); - expect(result.current.generalError4Report).toEqual('Data loading failed'); + expect(result.current.reportInfos[0].generalError4Report).toEqual('Data loading failed'); }); }); @@ -223,7 +264,7 @@ describe('use generate report effect', () => { await waitFor(() => { result.current.startToRequestData(MOCK_GENERATE_REPORT_REQUEST_PARAMS); - expect(result.current.timeout4Report).toEqual('Data loading failed'); + expect(result.current.reportInfos[0].timeout4Report).toEqual('Data loading failed'); }); }); }); diff --git a/frontend/src/components/Common/DateRangeViewer/index.tsx b/frontend/src/components/Common/DateRangeViewer/index.tsx index 413c59221d..2f59852421 100644 --- a/frontend/src/components/Common/DateRangeViewer/index.tsx +++ b/frontend/src/components/Common/DateRangeViewer/index.tsx @@ -18,7 +18,7 @@ type Props = { changeDateRange?: (dateRange: Record) => void; expandColor?: string; expandBackgroundColor?: string; - disabledAll: boolean; + disabledAll?: boolean; }; const DateRangeViewer = ({ dateRanges, changeDateRange, selectedDateRange, disabledAll = true }: Props) => { diff --git a/frontend/src/containers/ReportStep/index.tsx b/frontend/src/containers/ReportStep/index.tsx index b7f54624a6..9924d28f18 100644 --- a/frontend/src/containers/ReportStep/index.tsx +++ b/frontend/src/containers/ReportStep/index.tsx @@ -197,8 +197,8 @@ const ReportStep = ({ handleSave }: ReportStepProps) => { }); const basicReportRequestBody = { - startTime: formatDateToTimestampString(startDate), - endTime: formatDateToTimestampString(endDate), + startTime: null, + endTime: null, considerHoliday: calendarType === CALENDAR.CHINA, csvTimeStamp, metrics, From ba67186641ced61974963ace39555ac6f3744170 Mon Sep 17 00:00:00 2001 From: Leiqiuhong Date: Sun, 28 Apr 2024 22:37:56 +0800 Subject: [PATCH 18/31] ADM-879-new test: fix test --- .../containers/ReportStep/ReportStep.test.tsx | 160 ++++++++++++------ .../hooks/useGenerateReportEffect.test.tsx | 121 ++++--------- frontend/src/containers/ReportStep/index.tsx | 24 ++- frontend/src/hooks/useGenerateReportEffect.ts | 2 +- 4 files changed, 145 insertions(+), 162 deletions(-) diff --git a/frontend/__tests__/containers/ReportStep/ReportStep.test.tsx b/frontend/__tests__/containers/ReportStep/ReportStep.test.tsx index 90033308b7..9cc8b21ee9 100644 --- a/frontend/__tests__/containers/ReportStep/ReportStep.test.tsx +++ b/frontend/__tests__/containers/ReportStep/ReportStep.test.tsx @@ -7,7 +7,6 @@ import { EXPORT_METRIC_DATA, EXPORT_PIPELINE_DATA, LEAD_TIME_FOR_CHANGES, - MOCK_DATE_RANGE, MOCK_JIRA_VERIFY_RESPONSE, MOCK_REPORT_RESPONSE, PREVIOUS, @@ -27,14 +26,14 @@ import { addADeploymentFrequencySetting, updateDeploymentFrequencySettings } fro import { addNotification } from '@src/context/notification/NotificationSlice'; import { useGenerateReportEffect } from '@src/hooks/useGenerateReportEffect'; import { render, renderHook, screen, waitFor } from '@testing-library/react'; +import { DEFAULT_MESSAGE, MESSAGE } from '@src/constants/resources'; import { useExportCsvEffect } from '@src/hooks/useExportCsvEffect'; import { backStep } from '@src/context/stepper/StepperSlice'; import { setupStore } from '../../utils/setupStoreUtil'; import userEvent from '@testing-library/user-event'; import ReportStep from '@src/containers/ReportStep'; -import { MESSAGE } from '@src/constants/resources'; import { Provider } from 'react-redux'; -import React from 'react'; +import { ReactNode } from 'react'; jest.mock('@src/context/notification/NotificationSlice', () => ({ ...jest.requireActual('@src/context/notification/NotificationSlice'), @@ -54,12 +53,14 @@ jest.mock('@src/hooks/useExportCsvEffect', () => ({ })); jest.mock('@src/hooks/useGenerateReportEffect', () => ({ + ...jest.requireActual('@src/hooks/useGenerateReportEffect'), useGenerateReportEffect: jest.fn().mockReturnValue({ startToRequestData: jest.fn(), - startToRequestDoraData: jest.fn(), stopPollingReports: jest.fn(), - isServerError: false, - errorMessage: '', + shutReportInfosErrorStatus: jest.fn(), + shutBoardMetricsError: jest.fn(), + shutPipelineMetricsError: jest.fn(), + shutSourceControlMetricsError: jest.fn(), }), })); @@ -77,20 +78,60 @@ jest.mock('@src/utils/util', () => ({ formatMillisecondsToHours: jest.fn().mockImplementation((time) => time / 60 / 60 / 1000), })); -let store = null; +let store = setupStore(); + +const emptyValueDateRange = { + startDate: '2024-02-04T00:00:00.000+08:00', + endDate: '2024-02-17T23:59:59.999+08:00', +}; + +const fullValueDateRange = { + startDate: '2024-02-18T00:00:00.000+08:00', + endDate: '2024-02-28T23:59:59.999+08:00', +}; + describe('Report Step', () => { - const { result: reportHook } = renderHook(() => useGenerateReportEffect()); + const { result: reportHook } = renderHook(() => useGenerateReportEffect(), { + wrapper: ({ children }: { children: ReactNode }) => { + return {children}; + }, + }); beforeEach(() => { + store = setupStore(); resetReportHook(); }); const resetReportHook = async () => { - reportHook.current.startToRequestData = jest.fn(); - reportHook.current.stopPollingReports = jest.fn(); - reportHook.current.reportData = { ...MOCK_REPORT_RESPONSE, exportValidityTime: 30 }; + reportHook.current.reportInfos = [ + { + id: fullValueDateRange.startDate, + timeout4Board: { message: DEFAULT_MESSAGE, shouldShow: true }, + timeout4Dora: { message: DEFAULT_MESSAGE, shouldShow: true }, + timeout4Report: { message: DEFAULT_MESSAGE, shouldShow: true }, + generalError4Board: { message: DEFAULT_MESSAGE, shouldShow: true }, + generalError4Dora: { message: DEFAULT_MESSAGE, shouldShow: true }, + generalError4Report: { message: DEFAULT_MESSAGE, shouldShow: true }, + shouldShowBoardMetricsError: true, + shouldShowPipelineMetricsError: true, + shouldShowSourceControlMetricsError: true, + reportData: { ...MOCK_REPORT_RESPONSE, exportValidityTime: 30 }, + }, + { + id: emptyValueDateRange.startDate, + timeout4Board: { message: DEFAULT_MESSAGE, shouldShow: true }, + timeout4Dora: { message: DEFAULT_MESSAGE, shouldShow: true }, + timeout4Report: { message: DEFAULT_MESSAGE, shouldShow: true }, + generalError4Board: { message: DEFAULT_MESSAGE, shouldShow: true }, + generalError4Dora: { message: DEFAULT_MESSAGE, shouldShow: true }, + generalError4Report: { message: DEFAULT_MESSAGE, shouldShow: true }, + shouldShowBoardMetricsError: true, + shouldShowPipelineMetricsError: true, + shouldShowSourceControlMetricsError: true, + reportData: { ...EMPTY_REPORT_VALUES }, + }, + ]; }; const handleSaveMock = jest.fn(); - const setup = (params: string[], dateRange?: DateRange) => { - store = setupStore(); + const setup = (params: string[], dateRange: DateRange = [fullValueDateRange]) => { dateRange && store.dispatch(updateDateRange(dateRange)); store.dispatch( updateJiraVerifyResponse({ @@ -129,13 +170,12 @@ describe('Report Step', () => { ); }; afterEach(() => { - store = null; jest.clearAllMocks(); }); describe('render correctly', () => { it('should render report page', () => { - setup(REQUIRED_DATA_LIST); + setup(REQUIRED_DATA_LIST, [emptyValueDateRange]); expect(screen.getByText('Board Metrics')).toBeInTheDocument(); expect(screen.getByText('Velocity')).toBeInTheDocument(); @@ -148,9 +188,7 @@ describe('Report Step', () => { }); it('should render loading page when report data is empty', () => { - reportHook.current.reportData = EMPTY_REPORT_VALUES; - - setup(REQUIRED_DATA_LIST); + setup(REQUIRED_DATA_LIST, [emptyValueDateRange]); expect(screen.getAllByTestId('loading-page')).toHaveLength(7); }); @@ -169,7 +207,6 @@ describe('Report Step', () => { it('should render the velocity component with correct props', async () => { setup([REQUIRED_DATA_LIST[1]]); - expect(screen.getByText('20')).toBeInTheDocument(); expect(screen.getByText('14')).toBeInTheDocument(); }); @@ -255,7 +292,7 @@ describe('Report Step', () => { it.each([[REQUIRED_DATA_LIST[2]], [REQUIRED_DATA_LIST[5]]])( 'should render detail page when clicking show more button given metric %s', async (requiredData) => { - setup([requiredData], MOCK_DATE_RANGE); + setup([requiredData]); await userEvent.click(screen.getByText(SHOW_MORE)); @@ -340,8 +377,8 @@ describe('Report Step', () => { expect(result.current.fetchExportData).toBeCalledWith({ csvTimeStamp: 0, dataType: 'pipeline', - endDate: '', - startDate: '', + endDate: '2024-02-28T23:59:59.999+08:00', + startDate: '2024-02-18T00:00:00.000+08:00', }); }); }); @@ -377,8 +414,8 @@ describe('Report Step', () => { expect(result.current.fetchExportData).toBeCalledWith({ csvTimeStamp: 0, dataType: 'board', - endDate: '', - startDate: '', + endDate: '2024-02-28T23:59:59.999+08:00', + startDate: '2024-02-18T00:00:00.000+08:00', }); }); }); @@ -403,8 +440,8 @@ describe('Report Step', () => { expect(result.current.fetchExportData).toBeCalledWith({ csvTimeStamp: 0, dataType: 'metric', - endDate: '', - startDate: '', + endDate: '2024-02-28T23:59:59.999+08:00', + startDate: '2024-02-18T00:00:00.000+08:00', }); }); @@ -419,40 +456,47 @@ describe('Report Step', () => { const error = 'error'; it('should call addNotification when having timeout4Board error', () => { - reportHook.current.timeout4Board = error; + reportHook.current.reportInfos = reportHook.current.reportInfos.slice(1); + reportHook.current.reportInfos[0].timeout4Board = { message: error, shouldShow: true }; - setup(REQUIRED_DATA_LIST); + setup(REQUIRED_DATA_LIST, [emptyValueDateRange]); + expect(addNotification).toHaveBeenCalledTimes(1); - expect(addNotification).toBeCalledWith({ + expect(addNotification).toHaveBeenCalledWith({ + id: expect.any(String), message: MESSAGE.LOADING_TIMEOUT('Board metrics'), type: 'error', }); }); it('should call addNotification when having timeout4Dora error', () => { - reportHook.current.timeout4Dora = error; + reportHook.current.reportInfos = reportHook.current.reportInfos.slice(1); + reportHook.current.reportInfos[0].timeout4Dora = { message: error, shouldShow: true }; - setup(REQUIRED_DATA_LIST); + setup(REQUIRED_DATA_LIST, [emptyValueDateRange]); - expect(addNotification).toBeCalledWith({ + expect(addNotification).toHaveBeenCalledWith({ + id: expect.any(String), message: MESSAGE.LOADING_TIMEOUT('DORA metrics'), type: 'error', }); }); it('should call addNotification when having timeout4Report error', () => { - reportHook.current.timeout4Report = error; + reportHook.current.reportInfos = reportHook.current.reportInfos.slice(1); + reportHook.current.reportInfos[0].timeout4Report = { message: error, shouldShow: true }; - setup(REQUIRED_DATA_LIST); + setup(REQUIRED_DATA_LIST, [emptyValueDateRange]); - expect(addNotification).toBeCalledWith({ + expect(addNotification).toHaveBeenCalledWith({ + id: expect.any(String), message: MESSAGE.LOADING_TIMEOUT('Report'), type: 'error', }); }); it('should call addNotification when having boardMetricsError', () => { - reportHook.current.reportData = { + reportHook.current.reportInfos[0].reportData = { ...MOCK_REPORT_RESPONSE, reportMetricsError: { boardMetricsError: { @@ -466,14 +510,15 @@ describe('Report Step', () => { setup(REQUIRED_DATA_LIST); - expect(addNotification).toBeCalledWith({ + expect(addNotification).toHaveBeenCalledWith({ + id: expect.any(String), message: MESSAGE.FAILED_TO_GET_DATA('Board Metrics'), type: 'error', }); }); it('should call addNotification when having pipelineMetricsError', () => { - reportHook.current.reportData = { + reportHook.current.reportInfos[0].reportData = { ...MOCK_REPORT_RESPONSE, reportMetricsError: { boardMetricsError: null, @@ -486,15 +531,16 @@ describe('Report Step', () => { }; setup(REQUIRED_DATA_LIST); - - expect(addNotification).toBeCalledWith({ + expect(addNotification).toHaveBeenCalledTimes(2); + expect(addNotification).toHaveBeenCalledWith({ + id: expect.any(String), message: MESSAGE.FAILED_TO_GET_DATA('Buildkite'), type: 'error', }); }); it('should call addNotification when having sourceControlMetricsError', () => { - reportHook.current.reportData = { + reportHook.current.reportInfos[0].reportData = { ...MOCK_REPORT_RESPONSE, reportMetricsError: { boardMetricsError: null, @@ -508,48 +554,52 @@ describe('Report Step', () => { setup(REQUIRED_DATA_LIST); - expect(addNotification).toBeCalledWith({ + expect(addNotification).toHaveBeenCalledWith({ + id: expect.any(String), message: MESSAGE.FAILED_TO_GET_DATA('GitHub'), type: 'error', }); }); it('should call addNotification when having generalError4Board error', () => { - reportHook.current.generalError4Board = error; + reportHook.current.reportInfos[1].generalError4Board = { message: error, shouldShow: true }; - setup(REQUIRED_DATA_LIST); + setup(REQUIRED_DATA_LIST, [emptyValueDateRange]); - expect(addNotification).toBeCalledWith({ + expect(addNotification).toHaveBeenCalledWith({ + id: expect.any(String), message: MESSAGE.FAILED_TO_REQUEST, type: 'error', }); }); it('should call addNotification when having generalError4Dora error', () => { - reportHook.current.generalError4Dora = error; + reportHook.current.reportInfos[1].generalError4Dora = { message: error, shouldShow: true }; - setup(REQUIRED_DATA_LIST); + setup(REQUIRED_DATA_LIST, [emptyValueDateRange]); - expect(addNotification).toBeCalledWith({ + expect(addNotification).toHaveBeenCalledWith({ + id: expect.any(String), message: MESSAGE.FAILED_TO_REQUEST, type: 'error', }); }); it('should call addNotification when having generalError4Report error', () => { - reportHook.current.generalError4Report = error; + reportHook.current.reportInfos[1].generalError4Report = { message: error, shouldShow: true }; - setup(REQUIRED_DATA_LIST); + setup(REQUIRED_DATA_LIST, [emptyValueDateRange]); - expect(addNotification).toBeCalledWith({ + expect(addNotification).toHaveBeenCalledWith({ + id: expect.any(String), message: MESSAGE.FAILED_TO_REQUEST, type: 'error', }); }); it('should retry startToRequestData when click the retry button in Board Metrics', async () => { - reportHook.current.generalError4Report = error; - setup(REQUIRED_DATA_LIST); + reportHook.current.reportInfos[1].generalError4Report = { message: error, shouldShow: true }; + setup(REQUIRED_DATA_LIST, [emptyValueDateRange]); await userEvent.click(screen.getAllByText(RETRY)[0]); @@ -559,8 +609,8 @@ describe('Report Step', () => { }); it('should retry startToRequestData when click the retry button in Dora Metrics', async () => { - reportHook.current.generalError4Report = error; - setup(REQUIRED_DATA_LIST); + reportHook.current.reportInfos[1].generalError4Report = { message: error, shouldShow: true }; + setup(REQUIRED_DATA_LIST, [emptyValueDateRange]); await userEvent.click(screen.getAllByText(RETRY)[1]); diff --git a/frontend/__tests__/hooks/useGenerateReportEffect.test.tsx b/frontend/__tests__/hooks/useGenerateReportEffect.test.tsx index 641069c3df..60744bf620 100644 --- a/frontend/__tests__/hooks/useGenerateReportEffect.test.tsx +++ b/frontend/__tests__/hooks/useGenerateReportEffect.test.tsx @@ -1,18 +1,18 @@ import { MOCK_GENERATE_REPORT_REQUEST_PARAMS, MOCK_REPORT_RESPONSE, MOCK_RETRIEVE_REPORT_RESPONSE } from '../fixtures'; -import { useGenerateReportEffect } from '@src/hooks/useGenerateReportEffect'; +import { generalErrorKey, IReportError, useGenerateReportEffect } from '@src/hooks/useGenerateReportEffect'; +import { AXIOS_REQUEST_ERROR_CODE } from '@src/constants/resources'; +import { updateDateRange } from '@src/context/config/configSlice'; import { act, renderHook, waitFor } from '@testing-library/react'; import { reportClient } from '@src/clients/report/ReportClient'; +import { setupStore } from '@test/utils/setupStoreUtil'; import { TimeoutError } from '@src/errors/TimeoutError'; import { UnknownError } from '@src/errors/UnknownError'; -import { HttpStatusCode } from 'axios'; -import clearAllMocks = jest.clearAllMocks; -import resetAllMocks = jest.resetAllMocks; -import { AXIOS_REQUEST_ERROR_CODE } from '@src/constants/resources'; -import { updateDateRange } from '@src/context/config/configSlice'; -import { setupStore } from '@test/utils/setupStoreUtil'; import { METRIC_TYPES } from '@src/constants/commons'; import React, { ReactNode } from 'react'; import { Provider } from 'react-redux'; +import { HttpStatusCode } from 'axios'; +import clearAllMocks = jest.clearAllMocks; +import resetAllMocks = jest.resetAllMocks; const MOCK_GENERATE_REPORT_REQUEST_PARAMS_WITH_BOARD_METRIC_TYPE = { ...MOCK_GENERATE_REPORT_REQUEST_PARAMS, @@ -176,95 +176,32 @@ describe('use generate report effect', () => { expect(reportClient.polling).toHaveBeenCalledTimes(2); }); - it('should call polling report and setTimeout when request dora data given pollingReport response return 204', async () => { - reportClient.polling = jest - .fn() - .mockImplementation(async () => ({ status: HttpStatusCode.NoContent, response: MOCK_REPORT_RESPONSE })); - - reportClient.retrieveByUrl = jest - .fn() - .mockImplementation(async () => ({ response: MOCK_RETRIEVE_REPORT_RESPONSE })); - - const { result } = renderHook(() => useGenerateReportEffect()); - - await waitFor(() => { - result.current.startToRequestData(MOCK_GENERATE_REPORT_REQUEST_PARAMS_WITH_DORA_METRIC_TYPE); - }); - - jest.runOnlyPendingTimers(); - - await waitFor(() => { - expect(reportClient.polling).toHaveBeenCalledTimes(1); - }); - }); - - it('should call polling report only once when request dora data given board data retrieval is called before', async () => { - reportClient.polling = jest - .fn() - .mockImplementation(async () => ({ status: HttpStatusCode.NoContent, response: MOCK_REPORT_RESPONSE })); - - reportClient.retrieveByUrl = jest - .fn() - .mockImplementation(async () => ({ response: MOCK_RETRIEVE_REPORT_RESPONSE })); - - const { result } = renderHook(() => useGenerateReportEffect()); - - await waitFor(() => { - result.current.startToRequestData(MOCK_GENERATE_REPORT_REQUEST_PARAMS_WITH_DORA_METRIC_TYPE); - result.current.startToRequestData(MOCK_GENERATE_REPORT_REQUEST_PARAMS_WITH_BOARD_METRIC_TYPE); - }); - - jest.runOnlyPendingTimers(); - - await waitFor(() => { - expect(reportClient.polling).toHaveBeenCalledTimes(1); - }); - }); - - it('should set "Data loading failed" for board metric when request board data given UnknownException', async () => { - reportClient.retrieveByUrl = jest.fn().mockRejectedValue(new UnknownError()); - - const { result } = renderHook(() => useGenerateReportEffect()); - - await waitFor(() => { - result.current.startToRequestData(MOCK_GENERATE_REPORT_REQUEST_PARAMS_WITH_BOARD_METRIC_TYPE); - expect(result.current.reportInfos[0].generalError4Board).toEqual('Data loading failed'); - }); - }); - - it('should set "Data loading failed" for dora metric when request dora data given UnknownException', async () => { + it.each([ + { + params: MOCK_GENERATE_REPORT_REQUEST_PARAMS_WITH_BOARD_METRIC_TYPE, + errorKey: generalErrorKey[METRIC_TYPES.BOARD], + }, + { + params: MOCK_GENERATE_REPORT_REQUEST_PARAMS_WITH_DORA_METRIC_TYPE, + errorKey: generalErrorKey[METRIC_TYPES.DORA], + }, + { + params: MOCK_GENERATE_REPORT_REQUEST_PARAMS, + errorKey: generalErrorKey[METRIC_TYPES.ALL], + }, + ])('should set "Data loading failed" for board metric when request board data given UnknownException', async (_) => { reportClient.retrieveByUrl = jest.fn().mockRejectedValue(new UnknownError()); - const { result } = renderHook(() => useGenerateReportEffect()); - - await waitFor(() => { - result.current.startToRequestData(MOCK_GENERATE_REPORT_REQUEST_PARAMS_WITH_DORA_METRIC_TYPE); - expect(result.current.reportInfos[0].generalError4Dora).toEqual('Data loading failed'); - }); - }); - - it('should set "Data loading failed" for report when polling given UnknownException', async () => { - reportClient.polling = jest.fn().mockRejectedValue(new UnknownError()); - reportClient.retrieveByUrl = jest - .fn() - .mockImplementation(async () => ({ response: MOCK_RETRIEVE_REPORT_RESPONSE })); - - const { result } = renderHook(() => useGenerateReportEffect()); + const { result } = setup(); - await waitFor(() => { - result.current.startToRequestData(MOCK_GENERATE_REPORT_REQUEST_PARAMS); - expect(result.current.reportInfos[0].generalError4Report).toEqual('Data loading failed'); + await act(async () => { + await result.current.startToRequestData(_.params); }); - }); + const errorKey = _.errorKey as keyof IReportError; - it('should set "Data loading failed" for report when all data retrieval times out', async () => { - reportClient.retrieveByUrl = jest.fn().mockRejectedValue(new TimeoutError('5xx error', 503)); - - const { result } = renderHook(() => useGenerateReportEffect()); - - await waitFor(() => { - result.current.startToRequestData(MOCK_GENERATE_REPORT_REQUEST_PARAMS); - expect(result.current.reportInfos[0].timeout4Report).toEqual('Data loading failed'); - }); + expect(result.current.reportInfos[0][errorKey].message).toEqual('Data loading failed'); + expect(result.current.reportInfos[0][errorKey].shouldShow).toEqual(true); + expect(result.current.reportInfos[1][errorKey].message).toEqual('Data loading failed'); + expect(result.current.reportInfos[1][errorKey].shouldShow).toEqual(true); }); }); diff --git a/frontend/src/containers/ReportStep/index.tsx b/frontend/src/containers/ReportStep/index.tsx index 9924d28f18..cbcf00ff0b 100644 --- a/frontend/src/containers/ReportStep/index.tsx +++ b/frontend/src/containers/ReportStep/index.tsx @@ -1,19 +1,11 @@ import { filterAndMapCycleTimeSettings, - formatDateToTimestampString, formatDuplicatedNameWithSuffix, getJiraBoardToken, getRealDoneStatus, onlyEmptyAndDoneState, sortDateRanges, } from '@src/utils/util'; -import { - DateRange, - isOnlySelectClassification, - isSelectBoardMetrics, - isSelectDoraMetrics, - selectConfig, -} from '@src/context/config/configSlice'; import { generalErrorKey, initReportInfo, @@ -27,6 +19,12 @@ import { closeNotification, Notification, } from '@src/context/notification/NotificationSlice'; +import { + isOnlySelectClassification, + isSelectBoardMetrics, + isSelectDoraMetrics, + selectConfig, +} from '@src/context/config/configSlice'; import { BOARD_METRICS, CALENDAR, @@ -49,7 +47,6 @@ import { BoardDetail, DoraDetail } from './ReportDetail'; import { METRIC_TYPES } from '@src/constants/commons'; import { useAppSelector } from '@src/hooks'; import { uniqueId } from 'lodash'; -import get from 'lodash/get'; export interface ReportStepProps { handleSave: () => void; @@ -58,9 +55,9 @@ export interface ReportStepProps { const ReportStep = ({ handleSave }: ReportStepProps) => { const dispatch = useAppDispatch(); const configData = useAppSelector(selectConfig); - const dateRanges: DateRange = get(configData, 'basic.dateRange', []); + const descendingDateRanges = sortDateRanges(configData.basic.dateRange); const [selectedDateRange, setSelectedDateRange] = useState>( - dateRanges[0], + descendingDateRanges[0], ); const [currentDataInfo, setCurrentDataInfo] = useState(initReportInfo); @@ -96,9 +93,8 @@ const ReportStep = ({ handleSave }: ReportStepProps) => { leadTimeForChanges, } = useAppSelector(selectMetricsContent); - const descendingDateRanges = sortDateRanges(configData.basic.dateRange); - const startDate = configData.basic.dateRange[0]?.startDate ?? ''; - const endDate = configData.basic.dateRange[0]?.endDate ?? ''; + const startDate = (selectedDateRange?.startDate ?? '') as string; + const endDate = (selectedDateRange?.endDate ?? '') as string; const { metrics, calendarType } = configData.basic; const boardingMappingStates = [...new Set(cycleTimeSettings.map((item) => item.value))]; const isOnlyEmptyAndDoneState = onlyEmptyAndDoneState(boardingMappingStates); diff --git a/frontend/src/hooks/useGenerateReportEffect.ts b/frontend/src/hooks/useGenerateReportEffect.ts index ce56de78aa..f71d3c1f8b 100644 --- a/frontend/src/hooks/useGenerateReportEffect.ts +++ b/frontend/src/hooks/useGenerateReportEffect.ts @@ -26,7 +26,7 @@ export interface useGenerateReportEffectInterface { hasPollingStarted: boolean; } -interface IReportError { +export interface IReportError { timeout4Board: { message: string; shouldShow: boolean }; timeout4Dora: { message: string; shouldShow: boolean }; timeout4Report: { message: string; shouldShow: boolean }; From 054813f88eb2a8deec1b1e49090ec1fca6caa76b Mon Sep 17 00:00:00 2001 From: Leiqiuhong Date: Mon, 29 Apr 2024 10:14:03 +0800 Subject: [PATCH 19/31] ADM-879-new test: add unit tests --- .../__tests__/client/ReportClient.test.ts | 5 +- .../MetricsStepper/MetricsStepper.test.tsx | 59 +++++++++---- .../hooks/useGenerateReportEffect.test.tsx | 82 +++++++++++++++---- frontend/src/hooks/useGenerateReportEffect.ts | 4 +- 4 files changed, 113 insertions(+), 37 deletions(-) diff --git a/frontend/__tests__/client/ReportClient.test.ts b/frontend/__tests__/client/ReportClient.test.ts index 41066ff8dc..d75f5d0032 100644 --- a/frontend/__tests__/client/ReportClient.test.ts +++ b/frontend/__tests__/client/ReportClient.test.ts @@ -21,9 +21,8 @@ describe('report client', () => { afterAll(() => server.close()); it('should get response when generate report request status 202', async () => { - const excepted = { - response: MOCK_RETRIEVE_REPORT_RESPONSE, - }; + const excepted = MOCK_RETRIEVE_REPORT_RESPONSE; + server.use( rest.post(MOCK_REPORT_URL, (req, res, ctx) => res(ctx.status(HttpStatusCode.Accepted), ctx.json(MOCK_RETRIEVE_REPORT_RESPONSE)), diff --git a/frontend/__tests__/containers/MetricsStepper/MetricsStepper.test.tsx b/frontend/__tests__/containers/MetricsStepper/MetricsStepper.test.tsx index 502300bdf0..cb4b3c782d 100644 --- a/frontend/__tests__/containers/MetricsStepper/MetricsStepper.test.tsx +++ b/frontend/__tests__/containers/MetricsStepper/MetricsStepper.test.tsx @@ -11,6 +11,7 @@ import { TEST_PROJECT_NAME, VELOCITY, COMMON_TIME_FORMAT, + MOCK_REPORT_RESPONSE, } from '../../fixtures'; import { updateCycleTimeSettings, @@ -26,8 +27,8 @@ import { updatePipelineToolVerifyState, updateSourceControlVerifyState, } from '@src/context/config/configSlice'; +import { ASSIGNEE_FILTER_TYPES, DEFAULT_MESSAGE } from '@src/constants/resources'; import { act, render, screen, waitFor } from '@testing-library/react'; -import { ASSIGNEE_FILTER_TYPES } from '@src/constants/resources'; import MetricsStepper from '@src/containers/MetricsStepper'; import { setupStore } from '../../utils/setupStoreUtil'; import userEvent from '@testing-library/user-event'; @@ -57,6 +58,11 @@ const mockValidationCheckContext = { getDuplicatedPipeLineIds: jest.fn().mockReturnValue([]), }; +const mockDateRange = { + startDate: '2024-04-28T00:00:00.000+08:00', + endDate: '2024-04-28T23:59:59.999+08:00', +}; + jest.mock('@src/hooks/useMetricsStepValidationCheckContext', () => ({ useMetricsStepValidationCheckContext: () => mockValidationCheckContext, })); @@ -89,12 +95,29 @@ jest.mock('@src/utils/util', () => ({ })); jest.mock('@src/hooks/useGenerateReportEffect', () => ({ + ...jest.requireActual('@src/hooks/useGenerateReportEffect'), useGenerateReportEffect: jest.fn().mockReturnValue({ startToRequestData: jest.fn(), - startToRequestDoraData: jest.fn(), stopPollingReports: jest.fn(), - isServerError: false, - errorMessage: '', + shutReportInfosErrorStatus: jest.fn(), + shutBoardMetricsError: jest.fn(), + shutPipelineMetricsError: jest.fn(), + shutSourceControlMetricsError: jest.fn(), + reportInfos: [ + { + id: mockDateRange.startDate, + timeout4Board: { message: DEFAULT_MESSAGE, shouldShow: true }, + timeout4Dora: { message: DEFAULT_MESSAGE, shouldShow: true }, + timeout4Report: { message: DEFAULT_MESSAGE, shouldShow: true }, + generalError4Board: { message: DEFAULT_MESSAGE, shouldShow: true }, + generalError4Dora: { message: DEFAULT_MESSAGE, shouldShow: true }, + generalError4Report: { message: DEFAULT_MESSAGE, shouldShow: true }, + shouldShowBoardMetricsError: true, + shouldShowPipelineMetricsError: true, + shouldShowSourceControlMetricsError: true, + reportData: { ...MOCK_REPORT_RESPONSE, exportValidityTime: 30 }, + }, + ], }), })); @@ -128,17 +151,17 @@ const fillMetricsPageDate = async () => { act(() => { store.dispatch(saveTargetFields([{ name: 'mockClassification', key: 'mockClassification', flag: true }])); store.dispatch(saveUsers(['mockUsers'])); - store.dispatch(saveDoneColumn(['Done', 'Canceled'])), - store.dispatch( - updateCycleTimeSettings([ - { column: 'Testing', status: 'testing', value: 'Done' }, - { column: 'Testing', status: 'test', value: 'Done' }, - ]), - ); - store.dispatch(updateTreatFlagCardAsBlock(false)), - store.dispatch( - updateDeploymentFrequencySettings({ updateId: 0, label: 'organization', value: 'mock new organization' }), - ); + store.dispatch(saveDoneColumn(['Done', 'Canceled'])); + store.dispatch( + updateCycleTimeSettings([ + { column: 'Testing', status: 'testing', value: 'Done' }, + { column: 'Testing', status: 'test', value: 'Done' }, + ]), + ); + store.dispatch(updateTreatFlagCardAsBlock(false)); + store.dispatch( + updateDeploymentFrequencySettings({ updateId: 0, label: 'organization', value: 'mock new organization' }), + ); store.dispatch( updateDeploymentFrequencySettings({ updateId: 0, label: 'pipelineName', value: 'mock new pipelineName' }), ); @@ -355,8 +378,8 @@ describe('MetricsStepper', () => { calendarType: 'Regular Calendar(Weekend Considered)', dateRange: [ { - endDate: dayjs().endOf('date').add(0, 'day').format(COMMON_TIME_FORMAT), - startDate: dayjs().startOf('date').format(COMMON_TIME_FORMAT), + endDate: mockDateRange.endDate, + startDate: mockDateRange.startDate, }, ], metrics: ['Velocity'], @@ -388,7 +411,7 @@ describe('MetricsStepper', () => { await fillConfigPageData(); await userEvent.click(screen.getByText(NEXT)); await fillMetricsPageDate(); - waitFor(() => { + await waitFor(() => { expect(screen.getByText(NEXT)).toBeInTheDocument(); }); await userEvent.click(screen.getByText(NEXT)); diff --git a/frontend/__tests__/hooks/useGenerateReportEffect.test.tsx b/frontend/__tests__/hooks/useGenerateReportEffect.test.tsx index 60744bf620..38ad134de2 100644 --- a/frontend/__tests__/hooks/useGenerateReportEffect.test.tsx +++ b/frontend/__tests__/hooks/useGenerateReportEffect.test.tsx @@ -1,5 +1,11 @@ +import { + generalErrorKey, + IReportError, + IReportInfo, + useGenerateReportEffect, + IUseGenerateReportEffectInterface, +} from '@src/hooks/useGenerateReportEffect'; import { MOCK_GENERATE_REPORT_REQUEST_PARAMS, MOCK_REPORT_RESPONSE, MOCK_RETRIEVE_REPORT_RESPONSE } from '../fixtures'; -import { generalErrorKey, IReportError, useGenerateReportEffect } from '@src/hooks/useGenerateReportEffect'; import { AXIOS_REQUEST_ERROR_CODE } from '@src/constants/resources'; import { updateDateRange } from '@src/context/config/configSlice'; import { act, renderHook, waitFor } from '@testing-library/react'; @@ -34,7 +40,16 @@ const setup = () => wrapper: Wrapper, }); -// jest.useFakeTimers(); +const dateRanges = [ + { + startDate: '2024-02-04T00:00:00.000+08:00', + endDate: '2024-02-17T23:59:59.999+08:00', + }, + { + startDate: '2024-02-18T00:00:00.000+08:00', + endDate: '2024-02-28T23:59:59.999+08:00', + }, +]; describe('use generate report effect', () => { afterAll(() => { @@ -42,18 +57,7 @@ describe('use generate report effect', () => { }); beforeEach(() => { store = setupStore(); - store.dispatch( - updateDateRange([ - { - startDate: '2024-02-04T00:00:00.000+08:00', - endDate: '2024-02-17T23:59:59.999+08:00', - }, - { - startDate: '2024-02-18T00:00:00.000+08:00', - endDate: '2024-02-28T23:59:59.999+08:00', - }, - ]), - ); + store.dispatch(updateDateRange(dateRanges)); jest.useFakeTimers(); }); afterEach(() => { @@ -204,4 +208,54 @@ describe('use generate report effect', () => { expect(result.current.reportInfos[1][errorKey].message).toEqual('Data loading failed'); expect(result.current.reportInfos[1][errorKey].shouldShow).toEqual(true); }); + + it.each([ + { + errorKey: 'boardMetricsError', + stateKey: 'shouldShowBoardMetricsError', + updateMethod: 'shutBoardMetricsError', + }, + { + errorKey: 'pipelineMetricsError', + stateKey: 'shouldShowPipelineMetricsError', + updateMethod: 'shutPipelineMetricsError', + }, + { + errorKey: 'sourceControlMetricsError', + stateKey: 'shouldShowSourceControlMetricsError', + updateMethod: 'shutSourceControlMetricsError', + }, + ])('should update the status when call the update method', async (_) => { + reportClient.polling = jest.fn().mockImplementation(async () => ({ + status: HttpStatusCode.Ok, + response: { + ...MOCK_REPORT_RESPONSE, + reportMetricsError: { + [_.errorKey]: { + status: 400, + message: 'error', + }, + }, + }, + })); + reportClient.retrieveByUrl = jest.fn().mockImplementation(async () => MOCK_RETRIEVE_REPORT_RESPONSE); + const { result } = setup(); + + await act(async () => { + await result.current.startToRequestData(MOCK_GENERATE_REPORT_REQUEST_PARAMS_WITH_BOARD_METRIC_TYPE); + }); + + expect(result.current.reportInfos[0][_.stateKey as keyof IReportInfo]).toEqual(true); + expect(result.current.reportInfos[1][_.stateKey as keyof IReportInfo]).toEqual(true); + + await act(async () => { + const updateMethod = result.current[_.updateMethod as keyof IUseGenerateReportEffectInterface] as ( + id: string, + ) => void; + updateMethod(dateRanges[0].startDate); + }); + + expect(result.current.reportInfos[0][_.stateKey as keyof IReportInfo]).toEqual(false); + expect(result.current.reportInfos[1][_.stateKey as keyof IReportInfo]).toEqual(true); + }); }); diff --git a/frontend/src/hooks/useGenerateReportEffect.ts b/frontend/src/hooks/useGenerateReportEffect.ts index f71d3c1f8b..c48f77e84f 100644 --- a/frontend/src/hooks/useGenerateReportEffect.ts +++ b/frontend/src/hooks/useGenerateReportEffect.ts @@ -15,7 +15,7 @@ export type PromiseSettledResultWithId = PromiseSettledResult & { id: string; }; -export interface useGenerateReportEffectInterface { +export interface IUseGenerateReportEffectInterface { startToRequestData: (params: ReportRequestDTO) => void; stopPollingReports: () => void; reportInfos: IReportInfo[]; @@ -73,7 +73,7 @@ const getErrorKey = (error: Error, source: METRIC_TYPES): string => { return error instanceof TimeoutError ? timeoutErrorKey[source] : generalErrorKey[source]; }; -export const useGenerateReportEffect = (): useGenerateReportEffectInterface => { +export const useGenerateReportEffect = (): IUseGenerateReportEffectInterface => { const reportPath = '/reports'; const configData = useAppSelector(selectConfig); const timerIdRef = useRef(); From bdd5a17cbf8345b2feaed4e1317d503b01400566 Mon Sep 17 00:00:00 2001 From: Leiqiuhong Date: Mon, 29 Apr 2024 12:46:41 +0800 Subject: [PATCH 20/31] ADM-879-new refactor: extract method --- .../MetricsStepper/MetricsStepper.test.tsx | 4 +- .../containers/ReportStep/ReportStep.test.tsx | 6 + .../hooks/useGenerateReportEffect.test.tsx | 32 +++- frontend/src/containers/ReportStep/index.tsx | 152 +++++------------- frontend/src/hooks/useGenerateReportEffect.ts | 6 +- 5 files changed, 78 insertions(+), 122 deletions(-) diff --git a/frontend/__tests__/containers/MetricsStepper/MetricsStepper.test.tsx b/frontend/__tests__/containers/MetricsStepper/MetricsStepper.test.tsx index cb4b3c782d..a0491031dc 100644 --- a/frontend/__tests__/containers/MetricsStepper/MetricsStepper.test.tsx +++ b/frontend/__tests__/containers/MetricsStepper/MetricsStepper.test.tsx @@ -59,8 +59,8 @@ const mockValidationCheckContext = { }; const mockDateRange = { - startDate: '2024-04-28T00:00:00.000+08:00', - endDate: '2024-04-28T23:59:59.999+08:00', + startDate: dayjs().startOf('date').add(0, 'day').format(COMMON_TIME_FORMAT), + endDate: dayjs().endOf('date').format(COMMON_TIME_FORMAT), }; jest.mock('@src/hooks/useMetricsStepValidationCheckContext', () => ({ diff --git a/frontend/__tests__/containers/ReportStep/ReportStep.test.tsx b/frontend/__tests__/containers/ReportStep/ReportStep.test.tsx index 9cc8b21ee9..561394886a 100644 --- a/frontend/__tests__/containers/ReportStep/ReportStep.test.tsx +++ b/frontend/__tests__/containers/ReportStep/ReportStep.test.tsx @@ -619,4 +619,10 @@ describe('Report Step', () => { }); }); }); + + // describe('edge scene', () => { + // it('12', () => { + // reportHook.current.hasPollingStarted = true; + // }); + // }); }); diff --git a/frontend/__tests__/hooks/useGenerateReportEffect.test.tsx b/frontend/__tests__/hooks/useGenerateReportEffect.test.tsx index 38ad134de2..f74c902e83 100644 --- a/frontend/__tests__/hooks/useGenerateReportEffect.test.tsx +++ b/frontend/__tests__/hooks/useGenerateReportEffect.test.tsx @@ -4,6 +4,7 @@ import { IReportInfo, useGenerateReportEffect, IUseGenerateReportEffectInterface, + timeoutErrorKey, } from '@src/hooks/useGenerateReportEffect'; import { MOCK_GENERATE_REPORT_REQUEST_PARAMS, MOCK_REPORT_RESPONSE, MOCK_RETRIEVE_REPORT_RESPONSE } from '../fixtures'; import { AXIOS_REQUEST_ERROR_CODE } from '@src/constants/resources'; @@ -139,10 +140,7 @@ describe('use generate report effect', () => { status: HttpStatusCode.Ok, response: { ...MOCK_REPORT_RESPONSE, allMetricsCompleted: false }, }) - .mockReturnValueOnce({ - status: HttpStatusCode.Ok, - response: { ...MOCK_REPORT_RESPONSE, allMetricsCompleted: true }, - }) + .mockRejectedValue(new TimeoutError('timeout error', AXIOS_REQUEST_ERROR_CODE.TIMEOUT)) .mockReturnValueOnce({ status: HttpStatusCode.Ok, response: { ...MOCK_REPORT_RESPONSE, allMetricsCompleted: true }, @@ -160,6 +158,12 @@ describe('use generate report effect', () => { }); expect(reportClient.polling).toHaveBeenCalledTimes(3); + expect(result.current.reportInfos[0][timeoutErrorKey[METRIC_TYPES.ALL] as keyof IReportError].message).toEqual( + 'Data loading failed', + ); + expect(result.current.reportInfos[0][timeoutErrorKey[METRIC_TYPES.ALL] as keyof IReportError].shouldShow).toEqual( + true, + ); }); it('should call polling report only once when request board data given dora data retrieval is called before', async () => { @@ -225,7 +229,7 @@ describe('use generate report effect', () => { stateKey: 'shouldShowSourceControlMetricsError', updateMethod: 'shutSourceControlMetricsError', }, - ])('should update the status when call the update method', async (_) => { + ])('should update the report error status when call the update method', async (_) => { reportClient.polling = jest.fn().mockImplementation(async () => ({ status: HttpStatusCode.Ok, response: { @@ -258,4 +262,22 @@ describe('use generate report effect', () => { expect(result.current.reportInfos[0][_.stateKey as keyof IReportInfo]).toEqual(false); expect(result.current.reportInfos[1][_.stateKey as keyof IReportInfo]).toEqual(true); }); + + it('should update the network error status when call the update method', async () => { + reportClient.retrieveByUrl = jest.fn().mockImplementation(async () => MOCK_RETRIEVE_REPORT_RESPONSE); + reportClient.polling = jest + .fn() + .mockRejectedValue(new TimeoutError('timeout error', AXIOS_REQUEST_ERROR_CODE.TIMEOUT)); + const { result } = setup(); + await act(async () => { + await result.current.startToRequestData(MOCK_GENERATE_REPORT_REQUEST_PARAMS_WITH_BOARD_METRIC_TYPE); + }); + expect(result.current.reportInfos[0].timeout4Dora.shouldShow).toEqual(true); + expect(result.current.reportInfos[1].timeout4Dora.shouldShow).toEqual(true); + await act(async () => { + await result.current.shutReportInfosErrorStatus(dateRanges[0].startDate, timeoutErrorKey[METRIC_TYPES.DORA]); + }); + expect(result.current.reportInfos[0].timeout4Dora.shouldShow).toEqual(false); + expect(result.current.reportInfos[1].timeout4Dora.shouldShow).toEqual(true); + }); }); diff --git a/frontend/src/containers/ReportStep/index.tsx b/frontend/src/containers/ReportStep/index.tsx index cbcf00ff0b..87feba8e7f 100644 --- a/frontend/src/containers/ReportStep/index.tsx +++ b/frontend/src/containers/ReportStep/index.tsx @@ -9,6 +9,7 @@ import { import { generalErrorKey, initReportInfo, + IReportError, IReportInfo, timeoutErrorKey, useGenerateReportEffect, @@ -52,6 +53,12 @@ export interface ReportStepProps { handleSave: () => void; } +const timeoutNotificationMessages = { + [timeoutErrorKey[METRIC_TYPES.BOARD]]: 'Board metrics', + [timeoutErrorKey[METRIC_TYPES.DORA]]: 'DORA metrics', + [timeoutErrorKey[METRIC_TYPES.ALL]]: 'Report', +}; + const ReportStep = ({ handleSave }: ReportStepProps) => { const dispatch = useAppDispatch(); const configData = useAppSelector(selectConfig); @@ -59,7 +66,7 @@ const ReportStep = ({ handleSave }: ReportStepProps) => { const [selectedDateRange, setSelectedDateRange] = useState>( descendingDateRanges[0], ); - const [currentDataInfo, setCurrentDataInfo] = useState(initReportInfo); + const [currentDataInfo, setCurrentDataInfo] = useState(initReportInfo()); const { startToRequestData, @@ -93,8 +100,8 @@ const ReportStep = ({ handleSave }: ReportStepProps) => { leadTimeForChanges, } = useAppSelector(selectMetricsContent); - const startDate = (selectedDateRange?.startDate ?? '') as string; - const endDate = (selectedDateRange?.endDate ?? '') as string; + const startDate = selectedDateRange?.startDate as string; + const endDate = selectedDateRange?.endDate as string; const { metrics, calendarType } = configData.basic; const boardingMappingStates = [...new Set(cycleTimeSettings.map((item) => item.value))]; const isOnlyEmptyAndDoneState = onlyEmptyAndDoneState(boardingMappingStates); @@ -350,116 +357,17 @@ const ReportStep = ({ handleSave }: ReportStepProps) => { }, [currentDataInfo.reportData?.reportMetricsError.sourceControlMetricsError]); useEffect(() => { - if (!currentDataInfo.timeout4Report.shouldShow) return; - if (currentDataInfo.timeout4Report.message) { - const notificationId = uniqueId(); - setNotificationIds((pre) => [...pre, notificationId]); - setNotifications4SummaryPage((prevState) => [ - ...prevState, - { - id: notificationId, - message: MESSAGE.LOADING_TIMEOUT('Report'), - type: 'error', - }, - ]); - } - shutReportInfosErrorStatus(selectedDateRange.startDate as string, timeoutErrorKey[METRIC_TYPES.ALL]); + Object.values(timeoutErrorKey).forEach((value) => handleTimeoutAndGeneralError(value)); + Object.values(generalErrorKey).forEach((value) => handleTimeoutAndGeneralError(value)); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [currentDataInfo.timeout4Report]); - - useEffect(() => { - if (!currentDataInfo.timeout4Board.shouldShow) return; - if (currentDataInfo.timeout4Board.message) { - const notificationId = uniqueId(); - setNotificationIds((pre) => [...pre, notificationId]); - setNotifications4SummaryPage((prevState) => [ - ...prevState, - { - id: notificationId, - message: MESSAGE.LOADING_TIMEOUT('Board metrics'), - type: 'error', - }, - ]); - } - - shutReportInfosErrorStatus(selectedDateRange.startDate as string, timeoutErrorKey[METRIC_TYPES.BOARD]); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [currentDataInfo.timeout4Board]); - - useEffect(() => { - if (!currentDataInfo.timeout4Dora.shouldShow) return; - if (currentDataInfo.timeout4Dora.message) { - const notificationId = uniqueId(); - setNotificationIds((pre) => [...pre, notificationId]); - setNotifications4SummaryPage((prevState) => [ - ...prevState, - { - id: notificationId, - message: MESSAGE.LOADING_TIMEOUT('DORA metrics'), - type: 'error', - }, - ]); - } - - shutReportInfosErrorStatus(selectedDateRange.startDate as string, timeoutErrorKey[METRIC_TYPES.DORA]); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [currentDataInfo.timeout4Dora]); - - useEffect(() => { - if (!currentDataInfo.generalError4Board.shouldShow) return; - if (currentDataInfo.generalError4Board.message) { - const notificationId = uniqueId(); - setNotificationIds((pre) => [...pre, notificationId]); - setNotifications4SummaryPage((prevState) => [ - ...prevState, - { - id: notificationId, - message: MESSAGE.FAILED_TO_REQUEST, - type: 'error', - }, - ]); - } - - shutReportInfosErrorStatus(selectedDateRange.startDate as string, generalErrorKey[METRIC_TYPES.BOARD]); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [currentDataInfo.generalError4Board]); - - useEffect(() => { - if (!currentDataInfo.generalError4Dora.shouldShow) return; - if (currentDataInfo.generalError4Dora.message) { - const notificationId = uniqueId(); - setNotificationIds((pre) => [...pre, notificationId]); - setNotifications4SummaryPage((prevState) => [ - ...prevState, - { - id: notificationId, - message: MESSAGE.FAILED_TO_REQUEST, - type: 'error', - }, - ]); - } - shutReportInfosErrorStatus(selectedDateRange.startDate as string, generalErrorKey[METRIC_TYPES.DORA]); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [currentDataInfo.generalError4Dora]); - - useEffect(() => { - if (!currentDataInfo.generalError4Report.shouldShow) return; - if (currentDataInfo.generalError4Report.message) { - const notificationId = uniqueId(); - setNotificationIds((pre) => [...pre, notificationId]); - setNotifications4SummaryPage((prevState) => [ - ...prevState, - { - id: notificationId, - message: MESSAGE.FAILED_TO_REQUEST, - type: 'error', - }, - ]); - } - - shutReportInfosErrorStatus(selectedDateRange.startDate as string, generalErrorKey[METRIC_TYPES.ALL]); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [currentDataInfo.generalError4Report]); + }, [ + currentDataInfo.timeout4Board, + currentDataInfo.timeout4Report, + currentDataInfo.timeout4Dora, + currentDataInfo.generalError4Board, + currentDataInfo.generalError4Dora, + currentDataInfo.generalError4Report, + ]); useEffect(() => { startToRequestData(basicReportRequestBody); @@ -504,6 +412,26 @@ const ReportStep = ({ handleSave }: ReportStepProps) => { setPageType(REPORT_PAGE_TYPE.SUMMARY); }; + const handleTimeoutAndGeneralError = (value: string) => { + const errorKey = value as keyof IReportError; + if (!currentDataInfo[errorKey].shouldShow) return; + if (currentDataInfo[errorKey].message) { + const notificationId = uniqueId(); + setNotificationIds((pre) => [...pre, notificationId]); + setNotifications4SummaryPage((prevState) => [ + ...prevState, + { + id: notificationId, + message: timeoutNotificationMessages[errorKey] + ? MESSAGE.LOADING_TIMEOUT(timeoutNotificationMessages[errorKey]) + : MESSAGE.FAILED_TO_REQUEST, + type: 'error', + }, + ]); + } + shutReportInfosErrorStatus(selectedDateRange.startDate as string, errorKey); + }; + return ( <> {startDate && endDate && ( diff --git a/frontend/src/hooks/useGenerateReportEffect.ts b/frontend/src/hooks/useGenerateReportEffect.ts index c48f77e84f..32b34261aa 100644 --- a/frontend/src/hooks/useGenerateReportEffect.ts +++ b/frontend/src/hooks/useGenerateReportEffect.ts @@ -43,7 +43,7 @@ export interface IReportInfo extends IReportError { shouldShowSourceControlMetricsError: boolean; } -export const initReportInfo: IReportInfo = { +export const initReportInfo = (): IReportInfo => ({ id: '', timeout4Board: { message: DEFAULT_MESSAGE, shouldShow: true }, timeout4Dora: { message: DEFAULT_MESSAGE, shouldShow: true }, @@ -55,7 +55,7 @@ export const initReportInfo: IReportInfo = { shouldShowPipelineMetricsError: true, shouldShowSourceControlMetricsError: true, reportData: undefined, -}; +}); export const timeoutErrorKey = { [METRIC_TYPES.BOARD]: 'timeout4Board', @@ -79,7 +79,7 @@ export const useGenerateReportEffect = (): IUseGenerateReportEffectInterface => const timerIdRef = useRef(); const dateRanges: DateRange = get(configData, 'basic.dateRange', []); const [reportInfos, setReportInfos] = useState( - dateRanges.map((dateRange) => ({ ...initReportInfo, id: dateRange?.startDate || '' })), + dateRanges.map((dateRange) => ({ ...initReportInfo(), id: dateRange?.startDate as string })), ); const [hasPollingStarted, setHasPollingStarted] = useState(false); let nextHasPollingStarted = false; From 48a48d9794bd7931193faa809cbb0f23942930c6 Mon Sep 17 00:00:00 2001 From: Leiqiuhong Date: Mon, 29 Apr 2024 16:56:20 +0800 Subject: [PATCH 21/31] ADM-879-new refactor: remove useless code --- frontend/src/containers/MetricsStep/index.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/src/containers/MetricsStep/index.tsx b/frontend/src/containers/MetricsStep/index.tsx index 0fca089aff..55cfb4ba41 100644 --- a/frontend/src/containers/MetricsStep/index.tsx +++ b/frontend/src/containers/MetricsStep/index.tsx @@ -46,7 +46,6 @@ import { Loading } from '@src/components/Loading'; import ReworkSettings from './ReworkSettings'; import { Advance } from './Advance/Advance'; import isEmpty from 'lodash/isEmpty'; -import { theme } from '@src/theme'; import merge from 'lodash/merge'; const MetricsStep = () => { From e02b15e573d2c13863924031950ce2d8a1afbb2c Mon Sep 17 00:00:00 2001 From: Leiqiuhong Date: Mon, 29 Apr 2024 23:20:31 +0800 Subject: [PATCH 22/31] ADM-879-new test: add unit test --- .../containers/ReportStep/ReportStep.test.tsx | 74 +++++++++++++++++-- .../hooks/useGenerateReportEffect.test.tsx | 4 - frontend/src/hooks/useGenerateReportEffect.ts | 2 +- 3 files changed, 68 insertions(+), 12 deletions(-) diff --git a/frontend/__tests__/containers/ReportStep/ReportStep.test.tsx b/frontend/__tests__/containers/ReportStep/ReportStep.test.tsx index 561394886a..16472aef6e 100644 --- a/frontend/__tests__/containers/ReportStep/ReportStep.test.tsx +++ b/frontend/__tests__/containers/ReportStep/ReportStep.test.tsx @@ -23,9 +23,10 @@ import { updatePipelineToolVerifyResponse, } from '@src/context/config/configSlice'; import { addADeploymentFrequencySetting, updateDeploymentFrequencySettings } from '@src/context/Metrics/metricsSlice'; +import { act, render, renderHook, screen, waitFor } from '@testing-library/react'; +import { closeNotification } from '@src/context/notification/NotificationSlice'; import { addNotification } from '@src/context/notification/NotificationSlice'; import { useGenerateReportEffect } from '@src/hooks/useGenerateReportEffect'; -import { render, renderHook, screen, waitFor } from '@testing-library/react'; import { DEFAULT_MESSAGE, MESSAGE } from '@src/constants/resources'; import { useExportCsvEffect } from '@src/hooks/useExportCsvEffect'; import { backStep } from '@src/context/stepper/StepperSlice'; @@ -38,6 +39,7 @@ import { ReactNode } from 'react'; jest.mock('@src/context/notification/NotificationSlice', () => ({ ...jest.requireActual('@src/context/notification/NotificationSlice'), addNotification: jest.fn().mockReturnValue({ type: 'ADD_NOTIFICATION' }), + closeNotification: jest.fn(), })); jest.mock('@src/context/stepper/StepperSlice', () => ({ @@ -618,11 +620,69 @@ describe('Report Step', () => { expect(useGenerateReportEffect().startToRequestData).toHaveBeenCalledTimes(2); }); }); - }); - // describe('edge scene', () => { - // it('12', () => { - // reportHook.current.hasPollingStarted = true; - // }); - // }); + it('should not show notification in sending request', async () => { + reportHook.current.hasPollingStarted = true; + setup(REQUIRED_DATA_LIST, [emptyValueDateRange]); + + expect(addNotification).toHaveBeenCalledTimes(0); + }); + + it('should not show notification when the requests all failed', () => { + reportHook.current.hasPollingStarted = false; + reportHook.current.reportInfos[0].reportData = undefined; + reportHook.current.reportInfos[1].reportData = undefined; + setup(REQUIRED_DATA_LIST, [fullValueDateRange]); + expect(addNotification).toHaveBeenCalledTimes(0); + }); + + it('should show "file will expire ..." notification when request is successful', () => { + reportHook.current.hasPollingStarted = false; + setup(REQUIRED_DATA_LIST, [fullValueDateRange]); + expect(addNotification).toHaveBeenCalledWith({ + message: MESSAGE.EXPIRE_INFORMATION(30), + }); + }); + + it('should not show notifications after shown once', () => { + reportHook.current.reportInfos = reportHook.current.reportInfos.slice(1); + reportHook.current.reportInfos[0].generalError4Report = { shouldShow: false, message: 'error' }; + reportHook.current.reportInfos[0].generalError4Dora = { shouldShow: false, message: 'error' }; + reportHook.current.reportInfos[0].generalError4Board = { shouldShow: false, message: 'error' }; + reportHook.current.reportInfos[0].timeout4Dora = { shouldShow: false, message: 'error' }; + reportHook.current.reportInfos[0].timeout4Board = { shouldShow: false, message: 'error' }; + reportHook.current.reportInfos[0].timeout4Report = { shouldShow: false, message: 'error' }; + reportHook.current.reportInfos[0].reportData!.reportMetricsError = { + boardMetricsError: { status: 400, message: 'error' }, + pipelineMetricsError: { status: 400, message: 'error' }, + sourceControlMetricsError: { status: 400, message: 'error' }, + }; + reportHook.current.reportInfos[0].shouldShowBoardMetricsError = false; + reportHook.current.reportInfos[0].shouldShowPipelineMetricsError = false; + reportHook.current.reportInfos[0].shouldShowSourceControlMetricsError = false; + setup(REQUIRED_DATA_LIST, [emptyValueDateRange]); + expect(addNotification).toHaveBeenCalledTimes(0); + }); + + it('should close error notification when change dateRange', async () => { + reportHook.current.reportInfos[1].timeout4Board = { shouldShow: true, message: 'error' }; + const { getByTestId, getByText } = setup(REQUIRED_DATA_LIST, [fullValueDateRange, emptyValueDateRange]); + const expandMoreIcon = getByTestId('ExpandMoreIcon'); + await act(async () => { + await userEvent.click(expandMoreIcon); + }); + const secondDateRange = await getByText(/2024\/02\/04/); + + await userEvent.click(secondDateRange); + await userEvent.click(expandMoreIcon); + const firstDateRange = screen.getByText(/2024\/02\/18/); + await userEvent.click(firstDateRange); + expect(addNotification).toHaveBeenCalledWith({ + id: expect.any(String), + message: MESSAGE.LOADING_TIMEOUT('Board metrics'), + type: 'error', + }); + expect(closeNotification).toHaveBeenCalledTimes(1); + }); + }); }); diff --git a/frontend/__tests__/hooks/useGenerateReportEffect.test.tsx b/frontend/__tests__/hooks/useGenerateReportEffect.test.tsx index f74c902e83..0e63402326 100644 --- a/frontend/__tests__/hooks/useGenerateReportEffect.test.tsx +++ b/frontend/__tests__/hooks/useGenerateReportEffect.test.tsx @@ -130,10 +130,6 @@ describe('use generate report effect', () => { }); it('should call polling report more than one time when metrics is loading', async () => { - reportClient.polling = jest.fn().mockImplementation(async () => ({ - status: HttpStatusCode.Ok, - response: { ...MOCK_REPORT_RESPONSE, allMetricsCompleted: false }, - })); reportClient.polling = jest .fn() .mockReturnValueOnce({ diff --git a/frontend/src/hooks/useGenerateReportEffect.ts b/frontend/src/hooks/useGenerateReportEffect.ts index 32b34261aa..294a23fa9c 100644 --- a/frontend/src/hooks/useGenerateReportEffect.ts +++ b/frontend/src/hooks/useGenerateReportEffect.ts @@ -79,7 +79,7 @@ export const useGenerateReportEffect = (): IUseGenerateReportEffectInterface => const timerIdRef = useRef(); const dateRanges: DateRange = get(configData, 'basic.dateRange', []); const [reportInfos, setReportInfos] = useState( - dateRanges.map((dateRange) => ({ ...initReportInfo(), id: dateRange?.startDate as string })), + dateRanges.map((dateRange) => ({ ...initReportInfo(), id: dateRange.startDate as string })), ); const [hasPollingStarted, setHasPollingStarted] = useState(false); let nextHasPollingStarted = false; From 1e133c97cc8426a5751d366510a98f3cc40d44d5 Mon Sep 17 00:00:00 2001 From: Leiqiuhong Date: Tue, 30 Apr 2024 10:57:00 +0800 Subject: [PATCH 23/31] ADM-879-new fix: fix sonarcloud --- frontend/src/hooks/useGenerateReportEffect.ts | 40 +++++++++---------- 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/frontend/src/hooks/useGenerateReportEffect.ts b/frontend/src/hooks/useGenerateReportEffect.ts index 294a23fa9c..7d8c07e5a2 100644 --- a/frontend/src/hooks/useGenerateReportEffect.ts +++ b/frontend/src/hooks/useGenerateReportEffect.ts @@ -126,7 +126,24 @@ export const useGenerateReportEffect = (): IUseGenerateReportEffectInterface => const pollingResponses = await Promise.allSettled(pollingQueue); const pollingResponsesWithId = assemblePollingResWithId(pollingResponses, pollingInfos); - updateReportInfosAfterPolling(pollingResponsesWithId); + setReportInfos((preReportInfos) => { + return preReportInfos.map((reportInfo) => { + const matchedRes = pollingResponsesWithId.find((singleRes) => singleRes.id === reportInfo.id); + if (!matchedRes) return reportInfo; + + if (matchedRes.status === 'fulfilled') { + const { response } = matchedRes.value; + reportInfo.reportData = assembleReportData(response); + reportInfo.shouldShowBoardMetricsError = true; + reportInfo.shouldShowPipelineMetricsError = true; + reportInfo.shouldShowSourceControlMetricsError = true; + } else { + const errorKey = getErrorKey(matchedRes.reason, METRIC_TYPES.ALL) as keyof IReportError; + reportInfo[errorKey] = { message: DATA_LOADING_FAILED, shouldShow: true }; + } + return reportInfo; + }); + }); const nextPollingInfos = getNextPollingInfos(pollingResponsesWithId, pollingInfos); if (nextPollingInfos.length === 0) { @@ -238,27 +255,6 @@ export const useGenerateReportEffect = (): IUseGenerateReportEffectInterface => return nextPollingInfos; }; - const updateReportInfosAfterPolling = (pollingResponsesWithId: PromiseSettledResultWithId[]) => { - setReportInfos((preReportInfos) => { - return preReportInfos.map((reportInfo) => { - const matchedRes = pollingResponsesWithId.find((singleRes) => singleRes.id === reportInfo.id); - if (!matchedRes) return reportInfo; - - if (matchedRes.status === 'fulfilled') { - const { response } = matchedRes.value; - reportInfo.reportData = assembleReportData(response); - reportInfo.shouldShowBoardMetricsError = true; - reportInfo.shouldShowPipelineMetricsError = true; - reportInfo.shouldShowSourceControlMetricsError = true; - } else { - const errorKey = getErrorKey(matchedRes.reason, METRIC_TYPES.ALL) as keyof IReportError; - reportInfo[errorKey] = { message: DATA_LOADING_FAILED, shouldShow: true }; - } - return reportInfo; - }); - }); - }; - const initReportInfosTimeout4Report = (pollingIds: string[]) => { setReportInfos((preInfos) => { return preInfos.map((info) => { From c51e4a697df6652b32dd484175c10788b60b6748 Mon Sep 17 00:00:00 2001 From: Leiqiuhong Date: Tue, 30 Apr 2024 11:11:35 +0800 Subject: [PATCH 24/31] ADM-879-new refactor: fix rename --- .../hooks/useGenerateReportEffect.test.tsx | 16 ++++++++-------- frontend/src/containers/ReportStep/index.tsx | 14 +++++++------- frontend/src/hooks/useGenerateReportEffect.ts | 6 +++--- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/frontend/__tests__/hooks/useGenerateReportEffect.test.tsx b/frontend/__tests__/hooks/useGenerateReportEffect.test.tsx index 0e63402326..b5b7039043 100644 --- a/frontend/__tests__/hooks/useGenerateReportEffect.test.tsx +++ b/frontend/__tests__/hooks/useGenerateReportEffect.test.tsx @@ -1,10 +1,10 @@ import { - generalErrorKey, + GeneralErrorKey, IReportError, IReportInfo, useGenerateReportEffect, IUseGenerateReportEffectInterface, - timeoutErrorKey, + TimeoutErrorKey, } from '@src/hooks/useGenerateReportEffect'; import { MOCK_GENERATE_REPORT_REQUEST_PARAMS, MOCK_REPORT_RESPONSE, MOCK_RETRIEVE_REPORT_RESPONSE } from '../fixtures'; import { AXIOS_REQUEST_ERROR_CODE } from '@src/constants/resources'; @@ -154,10 +154,10 @@ describe('use generate report effect', () => { }); expect(reportClient.polling).toHaveBeenCalledTimes(3); - expect(result.current.reportInfos[0][timeoutErrorKey[METRIC_TYPES.ALL] as keyof IReportError].message).toEqual( + expect(result.current.reportInfos[0][TimeoutErrorKey[METRIC_TYPES.ALL] as keyof IReportError].message).toEqual( 'Data loading failed', ); - expect(result.current.reportInfos[0][timeoutErrorKey[METRIC_TYPES.ALL] as keyof IReportError].shouldShow).toEqual( + expect(result.current.reportInfos[0][TimeoutErrorKey[METRIC_TYPES.ALL] as keyof IReportError].shouldShow).toEqual( true, ); }); @@ -183,15 +183,15 @@ describe('use generate report effect', () => { it.each([ { params: MOCK_GENERATE_REPORT_REQUEST_PARAMS_WITH_BOARD_METRIC_TYPE, - errorKey: generalErrorKey[METRIC_TYPES.BOARD], + errorKey: GeneralErrorKey[METRIC_TYPES.BOARD], }, { params: MOCK_GENERATE_REPORT_REQUEST_PARAMS_WITH_DORA_METRIC_TYPE, - errorKey: generalErrorKey[METRIC_TYPES.DORA], + errorKey: GeneralErrorKey[METRIC_TYPES.DORA], }, { params: MOCK_GENERATE_REPORT_REQUEST_PARAMS, - errorKey: generalErrorKey[METRIC_TYPES.ALL], + errorKey: GeneralErrorKey[METRIC_TYPES.ALL], }, ])('should set "Data loading failed" for board metric when request board data given UnknownException', async (_) => { reportClient.retrieveByUrl = jest.fn().mockRejectedValue(new UnknownError()); @@ -271,7 +271,7 @@ describe('use generate report effect', () => { expect(result.current.reportInfos[0].timeout4Dora.shouldShow).toEqual(true); expect(result.current.reportInfos[1].timeout4Dora.shouldShow).toEqual(true); await act(async () => { - await result.current.shutReportInfosErrorStatus(dateRanges[0].startDate, timeoutErrorKey[METRIC_TYPES.DORA]); + await result.current.shutReportInfosErrorStatus(dateRanges[0].startDate, TimeoutErrorKey[METRIC_TYPES.DORA]); }); expect(result.current.reportInfos[0].timeout4Dora.shouldShow).toEqual(false); expect(result.current.reportInfos[1].timeout4Dora.shouldShow).toEqual(true); diff --git a/frontend/src/containers/ReportStep/index.tsx b/frontend/src/containers/ReportStep/index.tsx index 87feba8e7f..3338fd1c92 100644 --- a/frontend/src/containers/ReportStep/index.tsx +++ b/frontend/src/containers/ReportStep/index.tsx @@ -7,11 +7,11 @@ import { sortDateRanges, } from '@src/utils/util'; import { - generalErrorKey, + GeneralErrorKey, initReportInfo, IReportError, IReportInfo, - timeoutErrorKey, + TimeoutErrorKey, useGenerateReportEffect, } from '@src/hooks/useGenerateReportEffect'; import { @@ -54,9 +54,9 @@ export interface ReportStepProps { } const timeoutNotificationMessages = { - [timeoutErrorKey[METRIC_TYPES.BOARD]]: 'Board metrics', - [timeoutErrorKey[METRIC_TYPES.DORA]]: 'DORA metrics', - [timeoutErrorKey[METRIC_TYPES.ALL]]: 'Report', + [TimeoutErrorKey[METRIC_TYPES.BOARD]]: 'Board metrics', + [TimeoutErrorKey[METRIC_TYPES.DORA]]: 'DORA metrics', + [TimeoutErrorKey[METRIC_TYPES.ALL]]: 'Report', }; const ReportStep = ({ handleSave }: ReportStepProps) => { @@ -357,8 +357,8 @@ const ReportStep = ({ handleSave }: ReportStepProps) => { }, [currentDataInfo.reportData?.reportMetricsError.sourceControlMetricsError]); useEffect(() => { - Object.values(timeoutErrorKey).forEach((value) => handleTimeoutAndGeneralError(value)); - Object.values(generalErrorKey).forEach((value) => handleTimeoutAndGeneralError(value)); + Object.values(TimeoutErrorKey).forEach((value) => handleTimeoutAndGeneralError(value)); + Object.values(GeneralErrorKey).forEach((value) => handleTimeoutAndGeneralError(value)); // eslint-disable-next-line react-hooks/exhaustive-deps }, [ currentDataInfo.timeout4Board, diff --git a/frontend/src/hooks/useGenerateReportEffect.ts b/frontend/src/hooks/useGenerateReportEffect.ts index 7d8c07e5a2..e5f0ae00dd 100644 --- a/frontend/src/hooks/useGenerateReportEffect.ts +++ b/frontend/src/hooks/useGenerateReportEffect.ts @@ -57,20 +57,20 @@ export const initReportInfo = (): IReportInfo => ({ reportData: undefined, }); -export const timeoutErrorKey = { +export const TimeoutErrorKey = { [METRIC_TYPES.BOARD]: 'timeout4Board', [METRIC_TYPES.DORA]: 'timeout4Dora', [METRIC_TYPES.ALL]: 'timeout4Report', }; -export const generalErrorKey = { +export const GeneralErrorKey = { [METRIC_TYPES.BOARD]: 'generalError4Board', [METRIC_TYPES.DORA]: 'generalError4Dora', [METRIC_TYPES.ALL]: 'generalError4Report', }; const getErrorKey = (error: Error, source: METRIC_TYPES): string => { - return error instanceof TimeoutError ? timeoutErrorKey[source] : generalErrorKey[source]; + return error instanceof TimeoutError ? TimeoutErrorKey[source] : GeneralErrorKey[source]; }; export const useGenerateReportEffect = (): IUseGenerateReportEffectInterface => { From 1a9e4d86201f0275375a74ed6a186072387a1e15 Mon Sep 17 00:00:00 2001 From: Leiqiuhong Date: Tue, 30 Apr 2024 11:18:16 +0800 Subject: [PATCH 25/31] ADM-879-new refactor: rename --- frontend/__tests__/hooks/useGenerateReportEffect.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/__tests__/hooks/useGenerateReportEffect.test.tsx b/frontend/__tests__/hooks/useGenerateReportEffect.test.tsx index b5b7039043..fcd3a49e64 100644 --- a/frontend/__tests__/hooks/useGenerateReportEffect.test.tsx +++ b/frontend/__tests__/hooks/useGenerateReportEffect.test.tsx @@ -193,7 +193,7 @@ describe('use generate report effect', () => { params: MOCK_GENERATE_REPORT_REQUEST_PARAMS, errorKey: GeneralErrorKey[METRIC_TYPES.ALL], }, - ])('should set "Data loading failed" for board metric when request board data given UnknownException', async (_) => { + ])('should set "Data loading failed" for board metric when request given UnknownException', async (_) => { reportClient.retrieveByUrl = jest.fn().mockRejectedValue(new UnknownError()); const { result } = setup(); From 047a23bf5e69710834525430c0186c5fa5b4a1f4 Mon Sep 17 00:00:00 2001 From: Leiqiuhong Date: Tue, 30 Apr 2024 11:48:44 +0800 Subject: [PATCH 26/31] ADM-879-new fix: fix sonarcloud --- frontend/src/hooks/useGenerateReportEffect.ts | 34 +++++++++++-------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/frontend/src/hooks/useGenerateReportEffect.ts b/frontend/src/hooks/useGenerateReportEffect.ts index e5f0ae00dd..14dd1866df 100644 --- a/frontend/src/hooks/useGenerateReportEffect.ts +++ b/frontend/src/hooks/useGenerateReportEffect.ts @@ -126,6 +126,24 @@ export const useGenerateReportEffect = (): IUseGenerateReportEffectInterface => const pollingResponses = await Promise.allSettled(pollingQueue); const pollingResponsesWithId = assemblePollingResWithId(pollingResponses, pollingInfos); + updateReportInfosAfterPolling(pollingResponsesWithId); + + const nextPollingInfos = getNextPollingInfos(pollingResponsesWithId, pollingInfos); + if (nextPollingInfos.length === 0) { + stopPollingReports(); + return; + } + timerIdRef.current = window.setTimeout(() => { + pollingReport({ pollingInfos: nextPollingInfos, interval }); + }, interval * 1000); + }; + + const stopPollingReports = () => { + window.clearTimeout(timerIdRef.current); + setHasPollingStarted(false); + }; + + function updateReportInfosAfterPolling(pollingResponsesWithId: PromiseSettledResultWithId[]) { setReportInfos((preReportInfos) => { return preReportInfos.map((reportInfo) => { const matchedRes = pollingResponsesWithId.find((singleRes) => singleRes.id === reportInfo.id); @@ -144,21 +162,7 @@ export const useGenerateReportEffect = (): IUseGenerateReportEffectInterface => return reportInfo; }); }); - - const nextPollingInfos = getNextPollingInfos(pollingResponsesWithId, pollingInfos); - if (nextPollingInfos.length === 0) { - stopPollingReports(); - return; - } - timerIdRef.current = window.setTimeout(() => { - pollingReport({ pollingInfos: nextPollingInfos, interval }); - }, interval * 1000); - }; - - const stopPollingReports = () => { - window.clearTimeout(timerIdRef.current); - setHasPollingStarted(false); - }; + } const assembleReportData = (response: ReportResponseDTO): ReportResponseDTO => { const exportValidityTime = exportValidityTimeMapper(response.exportValidityTime); From 70e945ebf7afaa1f7bf3fdeeb0b26074b5ab7d67 Mon Sep 17 00:00:00 2001 From: Leiqiuhong Date: Tue, 30 Apr 2024 12:16:16 +0800 Subject: [PATCH 27/31] ADM-879-new fix: fix sonarcloud --- frontend/src/hooks/useGenerateReportEffect.ts | 45 ++++++++++--------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/frontend/src/hooks/useGenerateReportEffect.ts b/frontend/src/hooks/useGenerateReportEffect.ts index 14dd1866df..89690d6527 100644 --- a/frontend/src/hooks/useGenerateReportEffect.ts +++ b/frontend/src/hooks/useGenerateReportEffect.ts @@ -110,6 +110,28 @@ export const useGenerateReportEffect = (): IUseGenerateReportEffectInterface => await pollingReport({ pollingInfos, interval: pollingInterval }); }; + function getReportInfosAfterPolling( + preReportInfos: IReportInfo[], + pollingResponsesWithId: PromiseSettledResultWithId[], + ) { + return preReportInfos.map((reportInfo) => { + const matchedRes = pollingResponsesWithId.find((singleRes) => singleRes.id === reportInfo.id); + if (!matchedRes) return reportInfo; + + if (matchedRes.status === 'fulfilled') { + const { response } = matchedRes.value; + reportInfo.reportData = assembleReportData(response); + reportInfo.shouldShowBoardMetricsError = true; + reportInfo.shouldShowPipelineMetricsError = true; + reportInfo.shouldShowSourceControlMetricsError = true; + } else { + const errorKey = getErrorKey(matchedRes.reason, METRIC_TYPES.ALL) as keyof IReportError; + reportInfo[errorKey] = { message: DATA_LOADING_FAILED, shouldShow: true }; + } + return reportInfo; + }); + } + const pollingReport = async ({ pollingInfos, interval, @@ -126,7 +148,7 @@ export const useGenerateReportEffect = (): IUseGenerateReportEffectInterface => const pollingResponses = await Promise.allSettled(pollingQueue); const pollingResponsesWithId = assemblePollingResWithId(pollingResponses, pollingInfos); - updateReportInfosAfterPolling(pollingResponsesWithId); + setReportInfos((preReportInfos) => getReportInfosAfterPolling(preReportInfos, pollingResponsesWithId)); const nextPollingInfos = getNextPollingInfos(pollingResponsesWithId, pollingInfos); if (nextPollingInfos.length === 0) { @@ -143,27 +165,6 @@ export const useGenerateReportEffect = (): IUseGenerateReportEffectInterface => setHasPollingStarted(false); }; - function updateReportInfosAfterPolling(pollingResponsesWithId: PromiseSettledResultWithId[]) { - setReportInfos((preReportInfos) => { - return preReportInfos.map((reportInfo) => { - const matchedRes = pollingResponsesWithId.find((singleRes) => singleRes.id === reportInfo.id); - if (!matchedRes) return reportInfo; - - if (matchedRes.status === 'fulfilled') { - const { response } = matchedRes.value; - reportInfo.reportData = assembleReportData(response); - reportInfo.shouldShowBoardMetricsError = true; - reportInfo.shouldShowPipelineMetricsError = true; - reportInfo.shouldShowSourceControlMetricsError = true; - } else { - const errorKey = getErrorKey(matchedRes.reason, METRIC_TYPES.ALL) as keyof IReportError; - reportInfo[errorKey] = { message: DATA_LOADING_FAILED, shouldShow: true }; - } - return reportInfo; - }); - }); - } - const assembleReportData = (response: ReportResponseDTO): ReportResponseDTO => { const exportValidityTime = exportValidityTimeMapper(response.exportValidityTime); return { ...response, exportValidityTime: exportValidityTime }; From be01f15eaf364ba79c912efed6f2d797267831b8 Mon Sep 17 00:00:00 2001 From: Leiqiuhong Date: Tue, 30 Apr 2024 13:04:55 +0800 Subject: [PATCH 28/31] ADM-879-new fix: fix pr issues --- .../MetricsStepper/MetricsStepper.test.tsx | 7 +- .../containers/ReportStep/ReportStep.test.tsx | 8 +- frontend/__tests__/fixtures.ts | 10 ++- .../hooks/useGenerateReportEffect.test.tsx | 33 ++++---- frontend/src/containers/ReportStep/index.tsx | 16 ++-- frontend/src/hooks/useGenerateReportEffect.ts | 82 +++++++++---------- 6 files changed, 77 insertions(+), 79 deletions(-) diff --git a/frontend/__tests__/containers/MetricsStepper/MetricsStepper.test.tsx b/frontend/__tests__/containers/MetricsStepper/MetricsStepper.test.tsx index 258a73ac02..0c2e319b5d 100644 --- a/frontend/__tests__/containers/MetricsStepper/MetricsStepper.test.tsx +++ b/frontend/__tests__/containers/MetricsStepper/MetricsStepper.test.tsx @@ -26,8 +26,8 @@ import { updateTreatFlagCardAsBlock, } from '@src/context/Metrics/metricsSlice'; import { ASSIGNEE_FILTER_TYPES, DEFAULT_MESSAGE } from '@src/constants/resources'; +import { updateDateRange, updateMetrics } from '@src/context/config/configSlice'; import { act, render, screen, waitFor, within } from '@testing-library/react'; -import { updateMetrics } from '@src/context/config/configSlice'; import MetricsStepper from '@src/containers/MetricsStepper'; import { setupStore } from '../../utils/setupStoreUtil'; import userEvent from '@testing-library/user-event'; @@ -58,8 +58,8 @@ const mockValidationCheckContext = { }; const mockDateRange = { - startDate: dayjs().startOf('date').add(0, 'day').format(COMMON_TIME_FORMAT), - endDate: dayjs().endOf('date').format(COMMON_TIME_FORMAT), + startDate: '2024-02-18T00:00:00.000+08:00', + endDate: '2024-02-28T23:59:59.999+08:00', }; jest.mock('@src/hooks/useMetricsStepValidationCheckContext', () => ({ @@ -183,6 +183,7 @@ const fillMetricsPageDate = async () => { updateDeploymentFrequencySettings({ updateId: 0, label: 'pipelineName', value: 'mock new pipelineName' }), ); store.dispatch(updateDeploymentFrequencySettings({ updateId: 0, label: 'step', value: 'mock new step' })); + store.dispatch(updateDateRange([mockDateRange])); }); }; diff --git a/frontend/__tests__/containers/ReportStep/ReportStep.test.tsx b/frontend/__tests__/containers/ReportStep/ReportStep.test.tsx index 16472aef6e..8eb3ebbebc 100644 --- a/frontend/__tests__/containers/ReportStep/ReportStep.test.tsx +++ b/frontend/__tests__/containers/ReportStep/ReportStep.test.tsx @@ -621,14 +621,14 @@ describe('Report Step', () => { }); }); - it('should not show notification in sending request', async () => { + it('should not show notification when sending request', async () => { reportHook.current.hasPollingStarted = true; setup(REQUIRED_DATA_LIST, [emptyValueDateRange]); expect(addNotification).toHaveBeenCalledTimes(0); }); - it('should not show notification when the requests all failed', () => { + it('should not show notification given the requests all failed', () => { reportHook.current.hasPollingStarted = false; reportHook.current.reportInfos[0].reportData = undefined; reportHook.current.reportInfos[1].reportData = undefined; @@ -636,7 +636,7 @@ describe('Report Step', () => { expect(addNotification).toHaveBeenCalledTimes(0); }); - it('should show "file will expire ..." notification when request is successful', () => { + it('should show "file will expire ..." notification given the request is successful', () => { reportHook.current.hasPollingStarted = false; setup(REQUIRED_DATA_LIST, [fullValueDateRange]); expect(addNotification).toHaveBeenCalledWith({ @@ -644,7 +644,7 @@ describe('Report Step', () => { }); }); - it('should not show notifications after shown once', () => { + it('should not show notifications given shown once', () => { reportHook.current.reportInfos = reportHook.current.reportInfos.slice(1); reportHook.current.reportInfos[0].generalError4Report = { shouldShow: false, message: 'error' }; reportHook.current.reportInfos[0].generalError4Dora = { shouldShow: false, message: 'error' }; diff --git a/frontend/__tests__/fixtures.ts b/frontend/__tests__/fixtures.ts index 47d570ae23..aedd61ce43 100644 --- a/frontend/__tests__/fixtures.ts +++ b/frontend/__tests__/fixtures.ts @@ -288,10 +288,14 @@ export const MOCK_IMPORT_FILE = { metrics: [], }; -export const MOCK_DATE_RANGE = [ +export const MockedDateRanges = [ { - startDate: '2023-04-04T00:00:00+08:00', - endDate: '2023-04-18T00:00:00+08:00', + startDate: '2024-02-04T00:00:00.000+08:00', + endDate: '2024-02-17T23:59:59.999+08:00', + }, + { + startDate: '2024-02-18T00:00:00.000+08:00', + endDate: '2024-02-28T23:59:59.999+08:00', }, ]; diff --git a/frontend/__tests__/hooks/useGenerateReportEffect.test.tsx b/frontend/__tests__/hooks/useGenerateReportEffect.test.tsx index fcd3a49e64..5e64967a13 100644 --- a/frontend/__tests__/hooks/useGenerateReportEffect.test.tsx +++ b/frontend/__tests__/hooks/useGenerateReportEffect.test.tsx @@ -3,10 +3,15 @@ import { IReportError, IReportInfo, useGenerateReportEffect, - IUseGenerateReportEffectInterface, + IUseGenerateReportEffect, TimeoutErrorKey, } from '@src/hooks/useGenerateReportEffect'; -import { MOCK_GENERATE_REPORT_REQUEST_PARAMS, MOCK_REPORT_RESPONSE, MOCK_RETRIEVE_REPORT_RESPONSE } from '../fixtures'; +import { + MOCK_GENERATE_REPORT_REQUEST_PARAMS, + MOCK_REPORT_RESPONSE, + MOCK_RETRIEVE_REPORT_RESPONSE, + MockedDateRanges, +} from '../fixtures'; import { AXIOS_REQUEST_ERROR_CODE } from '@src/constants/resources'; import { updateDateRange } from '@src/context/config/configSlice'; import { act, renderHook, waitFor } from '@testing-library/react'; @@ -41,24 +46,13 @@ const setup = () => wrapper: Wrapper, }); -const dateRanges = [ - { - startDate: '2024-02-04T00:00:00.000+08:00', - endDate: '2024-02-17T23:59:59.999+08:00', - }, - { - startDate: '2024-02-18T00:00:00.000+08:00', - endDate: '2024-02-28T23:59:59.999+08:00', - }, -]; - describe('use generate report effect', () => { afterAll(() => { clearAllMocks(); }); beforeEach(() => { store = setupStore(); - store.dispatch(updateDateRange(dateRanges)); + store.dispatch(updateDateRange(MockedDateRanges)); jest.useFakeTimers(); }); afterEach(() => { @@ -249,10 +243,8 @@ describe('use generate report effect', () => { expect(result.current.reportInfos[1][_.stateKey as keyof IReportInfo]).toEqual(true); await act(async () => { - const updateMethod = result.current[_.updateMethod as keyof IUseGenerateReportEffectInterface] as ( - id: string, - ) => void; - updateMethod(dateRanges[0].startDate); + const updateMethod = result.current[_.updateMethod as keyof IUseGenerateReportEffect] as (id: string) => void; + updateMethod(MockedDateRanges[0].startDate); }); expect(result.current.reportInfos[0][_.stateKey as keyof IReportInfo]).toEqual(false); @@ -271,7 +263,10 @@ describe('use generate report effect', () => { expect(result.current.reportInfos[0].timeout4Dora.shouldShow).toEqual(true); expect(result.current.reportInfos[1].timeout4Dora.shouldShow).toEqual(true); await act(async () => { - await result.current.shutReportInfosErrorStatus(dateRanges[0].startDate, TimeoutErrorKey[METRIC_TYPES.DORA]); + await result.current.closeReportInfosErrorStatus( + MockedDateRanges[0].startDate, + TimeoutErrorKey[METRIC_TYPES.DORA], + ); }); expect(result.current.reportInfos[0].timeout4Dora.shouldShow).toEqual(false); expect(result.current.reportInfos[1].timeout4Dora.shouldShow).toEqual(true); diff --git a/frontend/src/containers/ReportStep/index.tsx b/frontend/src/containers/ReportStep/index.tsx index 3338fd1c92..d81efacb20 100644 --- a/frontend/src/containers/ReportStep/index.tsx +++ b/frontend/src/containers/ReportStep/index.tsx @@ -72,10 +72,10 @@ const ReportStep = ({ handleSave }: ReportStepProps) => { startToRequestData, reportInfos, stopPollingReports, - shutReportInfosErrorStatus, - shutBoardMetricsError, - shutPipelineMetricsError, - shutSourceControlMetricsError, + closeReportInfosErrorStatus, + closeBoardMetricsError, + closePipelineMetricsError, + closeSourceControlMetricsError, hasPollingStarted, } = useGenerateReportEffect(); @@ -316,7 +316,7 @@ const ReportStep = ({ handleSave }: ReportStepProps) => { }, ]); } - shutBoardMetricsError(selectedDateRange.startDate as string); + closeBoardMetricsError(selectedDateRange.startDate as string); // eslint-disable-next-line react-hooks/exhaustive-deps }, [currentDataInfo.reportData?.reportMetricsError.boardMetricsError]); @@ -334,7 +334,7 @@ const ReportStep = ({ handleSave }: ReportStepProps) => { }, ]); } - shutPipelineMetricsError(selectedDateRange.startDate as string); + closePipelineMetricsError(selectedDateRange.startDate as string); // eslint-disable-next-line react-hooks/exhaustive-deps }, [currentDataInfo.reportData?.reportMetricsError.pipelineMetricsError]); @@ -352,7 +352,7 @@ const ReportStep = ({ handleSave }: ReportStepProps) => { }, ]); } - shutSourceControlMetricsError(selectedDateRange.startDate as string); + closeSourceControlMetricsError(selectedDateRange.startDate as string); // eslint-disable-next-line react-hooks/exhaustive-deps }, [currentDataInfo.reportData?.reportMetricsError.sourceControlMetricsError]); @@ -429,7 +429,7 @@ const ReportStep = ({ handleSave }: ReportStepProps) => { }, ]); } - shutReportInfosErrorStatus(selectedDateRange.startDate as string, errorKey); + closeReportInfosErrorStatus(selectedDateRange.startDate as string, errorKey); }; return ( diff --git a/frontend/src/hooks/useGenerateReportEffect.ts b/frontend/src/hooks/useGenerateReportEffect.ts index 89690d6527..aebb99a5ab 100644 --- a/frontend/src/hooks/useGenerateReportEffect.ts +++ b/frontend/src/hooks/useGenerateReportEffect.ts @@ -15,24 +15,29 @@ export type PromiseSettledResultWithId = PromiseSettledResult & { id: string; }; -export interface IUseGenerateReportEffectInterface { +export interface IUseGenerateReportEffect { startToRequestData: (params: ReportRequestDTO) => void; stopPollingReports: () => void; reportInfos: IReportInfo[]; - shutReportInfosErrorStatus: (id: string, errorKey: string) => void; - shutBoardMetricsError: (id: string) => void; - shutPipelineMetricsError: (id: string) => void; - shutSourceControlMetricsError: (id: string) => void; + closeReportInfosErrorStatus: (id: string, errorKey: string) => void; + closeBoardMetricsError: (id: string) => void; + closePipelineMetricsError: (id: string) => void; + closeSourceControlMetricsError: (id: string) => void; hasPollingStarted: boolean; } +interface IErrorInfo { + message: string; + shouldShow: boolean; +} + export interface IReportError { - timeout4Board: { message: string; shouldShow: boolean }; - timeout4Dora: { message: string; shouldShow: boolean }; - timeout4Report: { message: string; shouldShow: boolean }; - generalError4Board: { message: string; shouldShow: boolean }; - generalError4Dora: { message: string; shouldShow: boolean }; - generalError4Report: { message: string; shouldShow: boolean }; + timeout4Board: IErrorInfo; + timeout4Dora: IErrorInfo; + timeout4Report: IErrorInfo; + generalError4Board: IErrorInfo; + generalError4Dora: IErrorInfo; + generalError4Report: IErrorInfo; } export interface IReportInfo extends IReportError { @@ -69,17 +74,20 @@ export const GeneralErrorKey = { [METRIC_TYPES.ALL]: 'generalError4Report', }; +const REJECTED = 'rejected'; +const FULFILLED = 'fulfilled'; + const getErrorKey = (error: Error, source: METRIC_TYPES): string => { return error instanceof TimeoutError ? TimeoutErrorKey[source] : GeneralErrorKey[source]; }; -export const useGenerateReportEffect = (): IUseGenerateReportEffectInterface => { +export const useGenerateReportEffect = (): IUseGenerateReportEffect => { const reportPath = '/reports'; const configData = useAppSelector(selectConfig); const timerIdRef = useRef(); - const dateRanges: DateRange = get(configData, 'basic.dateRange', []); + const dateRangeList: DateRange = get(configData, 'basic.dateRange', []); const [reportInfos, setReportInfos] = useState( - dateRanges.map((dateRange) => ({ ...initReportInfo(), id: dateRange.startDate as string })), + dateRangeList.map((dateRange) => ({ ...initReportInfo(), id: dateRange.startDate as string })), ); const [hasPollingStarted, setHasPollingStarted] = useState(false); let nextHasPollingStarted = false; @@ -87,7 +95,7 @@ export const useGenerateReportEffect = (): IUseGenerateReportEffectInterface => const { metricTypes } = params; resetTimeoutMessage(metricTypes); const res: PromiseSettledResult[] = await Promise.allSettled( - dateRanges.map(({ startDate, endDate }) => + dateRangeList.map(({ startDate, endDate }) => reportClient.retrieveByUrl( { ...params, @@ -118,7 +126,7 @@ export const useGenerateReportEffect = (): IUseGenerateReportEffectInterface => const matchedRes = pollingResponsesWithId.find((singleRes) => singleRes.id === reportInfo.id); if (!matchedRes) return reportInfo; - if (matchedRes.status === 'fulfilled') { + if (matchedRes.status === FULFILLED) { const { response } = matchedRes.value; reportInfo.reportData = assembleReportData(response); reportInfo.shouldShowBoardMetricsError = true; @@ -171,40 +179,30 @@ export const useGenerateReportEffect = (): IUseGenerateReportEffectInterface => }; const resetTimeoutMessage = (metricTypes: string[]) => { - if (metricTypes.length === 2) { - setReportInfos((preReportInfos) => { - return preReportInfos.map((reportInfo) => { + setReportInfos((preReportInfos) => { + return preReportInfos.map((reportInfo) => { + if (metricTypes.length === 2) { reportInfo.timeout4Report = { message: DEFAULT_MESSAGE, shouldShow: true }; - return reportInfo; - }); - }); - } else if (metricTypes.includes(METRIC_TYPES.BOARD)) { - setReportInfos((preReportInfos) => { - return preReportInfos.map((reportInfo) => { + } else if (metricTypes.includes(METRIC_TYPES.BOARD)) { reportInfo.timeout4Board = { message: DEFAULT_MESSAGE, shouldShow: true }; - return reportInfo; - }); - }); - } else { - setReportInfos((preReportInfos) => { - return preReportInfos.map((reportInfo) => { + } else { reportInfo.timeout4Dora = { message: DEFAULT_MESSAGE, shouldShow: true }; - return reportInfo; - }); + } + return reportInfo; }); - } + }); }; const updateErrorAfterFetchReport = ( res: PromiseSettledResult[], metricTypes: METRIC_TYPES[], ) => { - if (res.filter(({ status }) => status === 'rejected').length === 0) return; + if (res.filter(({ status }) => status === REJECTED).length === 0) return; setReportInfos((preReportInfos: IReportInfo[]) => { return preReportInfos.map((resInfo, index) => { const currentRes = res[index]; - if (currentRes.status === 'rejected') { + if (currentRes.status === REJECTED) { const source: METRIC_TYPES = metricTypes.length === 2 ? METRIC_TYPES.ALL : metricTypes[0]; const errorKey = getErrorKey(currentRes.reason, source) as keyof IReportError; resInfo[errorKey] = { message: DATA_LOADING_FAILED, shouldShow: true }; @@ -221,7 +219,7 @@ export const useGenerateReportEffect = (): IUseGenerateReportEffectInterface => })); const fulfilledResponses: PromiseSettledResultWithId[] = resWithIds.filter( - ({ status }) => status === 'fulfilled', + ({ status }) => status === FULFILLED, ); const pollingInfos: Record[] = fulfilledResponses.map((v) => { @@ -252,7 +250,7 @@ export const useGenerateReportEffect = (): IUseGenerateReportEffectInterface => const nextPollingInfos: Record[] = pollingResponsesWithId .filter( (pollingResponseWithId) => - pollingResponseWithId.status === 'fulfilled' && + pollingResponseWithId.status === FULFILLED && !pollingResponseWithId.value.response.allMetricsCompleted && nextHasPollingStarted, ) @@ -320,10 +318,10 @@ export const useGenerateReportEffect = (): IUseGenerateReportEffectInterface => startToRequestData, stopPollingReports, reportInfos, - shutReportInfosErrorStatus, - shutBoardMetricsError, - shutPipelineMetricsError, - shutSourceControlMetricsError, + closeReportInfosErrorStatus: shutReportInfosErrorStatus, + closeBoardMetricsError: shutBoardMetricsError, + closePipelineMetricsError: shutPipelineMetricsError, + closeSourceControlMetricsError: shutSourceControlMetricsError, hasPollingStarted, }; }; From 618921cf84ea86429393b9ee8cd45a2b70da1865 Mon Sep 17 00:00:00 2001 From: Leiqiuhong Date: Tue, 30 Apr 2024 14:22:16 +0800 Subject: [PATCH 29/31] ADM-879-new fix: fix sonarcloud --- .../DateRangeViewer/DateRangeViewer.test.tsx | 2 +- .../Common/DateRangeViewer/index.tsx | 22 ++++++++++--------- frontend/src/containers/MetricsStep/index.tsx | 2 +- frontend/src/containers/ReportStep/index.tsx | 2 +- frontend/src/hooks/useGenerateReportEffect.ts | 2 +- 5 files changed, 16 insertions(+), 14 deletions(-) diff --git a/frontend/__tests__/components/Common/DateRangeViewer/DateRangeViewer.test.tsx b/frontend/__tests__/components/Common/DateRangeViewer/DateRangeViewer.test.tsx index 9a3c22332f..879e0abe61 100644 --- a/frontend/__tests__/components/Common/DateRangeViewer/DateRangeViewer.test.tsx +++ b/frontend/__tests__/components/Common/DateRangeViewer/DateRangeViewer.test.tsx @@ -5,7 +5,7 @@ import { render } from '@testing-library/react'; describe('DateRangeViewer', () => { const setup = (dateRanges: DateRange) => { - return render(); + return render(); }; const mockDateRanges = [ { diff --git a/frontend/src/components/Common/DateRangeViewer/index.tsx b/frontend/src/components/Common/DateRangeViewer/index.tsx index 2f59852421..0f5a3f69d4 100644 --- a/frontend/src/components/Common/DateRangeViewer/index.tsx +++ b/frontend/src/components/Common/DateRangeViewer/index.tsx @@ -13,15 +13,13 @@ import { formatDate } from '@src/utils/util'; import { theme } from '@src/theme'; type Props = { - dateRanges: DateRange; + dateRangeList: DateRange; selectedDateRange?: Record; changeDateRange?: (dateRange: Record) => void; - expandColor?: string; - expandBackgroundColor?: string; disabledAll?: boolean; }; -const DateRangeViewer = ({ dateRanges, changeDateRange, selectedDateRange, disabledAll = true }: Props) => { +const DateRangeViewer = ({ dateRangeList, changeDateRange, selectedDateRange, disabledAll = true }: Props) => { const [showMoreDateRange, setShowMoreDateRange] = useState(false); const DateRangeExpandRef = useRef(null); @@ -31,8 +29,8 @@ const DateRangeViewer = ({ dateRanges, changeDateRange, selectedDateRange, disab } }, []); - const handleClick = (index: number) => { - changeDateRange && changeDateRange(dateRanges[index]); + const handleClick = (key: string) => { + changeDateRange && changeDateRange(dateRangeList.find((dateRange) => dateRange.startDate === key)!); setShowMoreDateRange(false); }; @@ -46,10 +44,14 @@ const DateRangeViewer = ({ dateRanges, changeDateRange, selectedDateRange, disab const DateRangeExpand = forwardRef((props, ref: React.ForwardedRef) => { return ( - {dateRanges.map((dateRange, index) => { + {dateRangeList.map((dateRange) => { const disabled = dateRange.disabled || disabledAll; return ( - handleClick(index)} key={index}> + handleClick(dateRange.startDate!)} + key={dateRange.startDate!} + > {formatDate(dateRange.startDate as string)} {formatDate(dateRange.endDate as string)} @@ -65,9 +67,9 @@ const DateRangeViewer = ({ dateRanges, changeDateRange, selectedDateRange, disab data-test-id={'date-range'} color={disabledAll ? theme.palette.text.disabled : theme.palette.text.primary} > - {formatDate((selectedDateRange || dateRanges[0]).startDate as string)} + {formatDate((selectedDateRange || dateRangeList[0]).startDate as string)} - {formatDate((selectedDateRange || dateRanges[0]).endDate as string)} + {formatDate((selectedDateRange || dateRangeList[0]).endDate as string)} setShowMoreDateRange(true)} /> diff --git a/frontend/src/containers/MetricsStep/index.tsx b/frontend/src/containers/MetricsStep/index.tsx index 55cfb4ba41..14e917095e 100644 --- a/frontend/src/containers/MetricsStep/index.tsx +++ b/frontend/src/containers/MetricsStep/index.tsx @@ -128,7 +128,7 @@ const MetricsStep = () => { <> {startDate && endDate && ( - + )} {isShowCrewsAndRealDone && ( diff --git a/frontend/src/containers/ReportStep/index.tsx b/frontend/src/containers/ReportStep/index.tsx index d81efacb20..6183fbe859 100644 --- a/frontend/src/containers/ReportStep/index.tsx +++ b/frontend/src/containers/ReportStep/index.tsx @@ -437,7 +437,7 @@ const ReportStep = ({ handleSave }: ReportStepProps) => { {startDate && endDate && ( setSelectedDateRange(dateRange)} disabledAll={false} diff --git a/frontend/src/hooks/useGenerateReportEffect.ts b/frontend/src/hooks/useGenerateReportEffect.ts index aebb99a5ab..ba03c492d9 100644 --- a/frontend/src/hooks/useGenerateReportEffect.ts +++ b/frontend/src/hooks/useGenerateReportEffect.ts @@ -16,7 +16,7 @@ export type PromiseSettledResultWithId = PromiseSettledResult & { }; export interface IUseGenerateReportEffect { - startToRequestData: (params: ReportRequestDTO) => void; + startToRequestData: (params: ReportRequestDTO) => Promise; stopPollingReports: () => void; reportInfos: IReportInfo[]; closeReportInfosErrorStatus: (id: string, errorKey: string) => void; From 56d715376c00391ff2e6ff9a57294acb92776476 Mon Sep 17 00:00:00 2001 From: Leiqiuhong Date: Tue, 30 Apr 2024 14:27:26 +0800 Subject: [PATCH 30/31] ADM-879-new fix: rename --- .../containers/MetricsStepper/MetricsStepper.test.tsx | 8 ++++---- .../__tests__/containers/ReportStep/ReportStep.test.tsx | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/frontend/__tests__/containers/MetricsStepper/MetricsStepper.test.tsx b/frontend/__tests__/containers/MetricsStepper/MetricsStepper.test.tsx index 0c2e319b5d..91970318a7 100644 --- a/frontend/__tests__/containers/MetricsStepper/MetricsStepper.test.tsx +++ b/frontend/__tests__/containers/MetricsStepper/MetricsStepper.test.tsx @@ -98,10 +98,10 @@ jest.mock('@src/hooks/useGenerateReportEffect', () => ({ useGenerateReportEffect: jest.fn().mockReturnValue({ startToRequestData: jest.fn(), stopPollingReports: jest.fn(), - shutReportInfosErrorStatus: jest.fn(), - shutBoardMetricsError: jest.fn(), - shutPipelineMetricsError: jest.fn(), - shutSourceControlMetricsError: jest.fn(), + closeReportInfosErrorStatus: jest.fn(), + closeBoardMetricsError: jest.fn(), + closePipelineMetricsError: jest.fn(), + closeSourceControlMetricsError: jest.fn(), reportInfos: [ { id: mockDateRange.startDate, diff --git a/frontend/__tests__/containers/ReportStep/ReportStep.test.tsx b/frontend/__tests__/containers/ReportStep/ReportStep.test.tsx index 8eb3ebbebc..69373d4042 100644 --- a/frontend/__tests__/containers/ReportStep/ReportStep.test.tsx +++ b/frontend/__tests__/containers/ReportStep/ReportStep.test.tsx @@ -59,10 +59,10 @@ jest.mock('@src/hooks/useGenerateReportEffect', () => ({ useGenerateReportEffect: jest.fn().mockReturnValue({ startToRequestData: jest.fn(), stopPollingReports: jest.fn(), - shutReportInfosErrorStatus: jest.fn(), - shutBoardMetricsError: jest.fn(), - shutPipelineMetricsError: jest.fn(), - shutSourceControlMetricsError: jest.fn(), + closeReportInfosErrorStatus: jest.fn(), + closeBoardMetricsError: jest.fn(), + closePipelineMetricsError: jest.fn(), + closeSourceControlMetricsError: jest.fn(), }), })); From 4eadc84cad3313f122762db57e4930b369ebb820 Mon Sep 17 00:00:00 2001 From: Leiqiuhong Date: Tue, 30 Apr 2024 14:38:17 +0800 Subject: [PATCH 31/31] ADM-879-new fix: fix test field name --- frontend/__tests__/hooks/useGenerateReportEffect.test.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/__tests__/hooks/useGenerateReportEffect.test.tsx b/frontend/__tests__/hooks/useGenerateReportEffect.test.tsx index 5e64967a13..59474be55c 100644 --- a/frontend/__tests__/hooks/useGenerateReportEffect.test.tsx +++ b/frontend/__tests__/hooks/useGenerateReportEffect.test.tsx @@ -207,17 +207,17 @@ describe('use generate report effect', () => { { errorKey: 'boardMetricsError', stateKey: 'shouldShowBoardMetricsError', - updateMethod: 'shutBoardMetricsError', + updateMethod: 'closeBoardMetricsError', }, { errorKey: 'pipelineMetricsError', stateKey: 'shouldShowPipelineMetricsError', - updateMethod: 'shutPipelineMetricsError', + updateMethod: 'closePipelineMetricsError', }, { errorKey: 'sourceControlMetricsError', stateKey: 'shouldShowSourceControlMetricsError', - updateMethod: 'shutSourceControlMetricsError', + updateMethod: 'closeSourceControlMetricsError', }, ])('should update the report error status when call the update method', async (_) => { reportClient.polling = jest.fn().mockImplementation(async () => ({