From 0b4d54a3c25378294f5e40c72b10cdd901574fb2 Mon Sep 17 00:00:00 2001 From: Mandy Date: Fri, 12 Jul 2024 13:41:11 +0800 Subject: [PATCH] ADM-973 [frontend] retry all when retry (#1524) * ADM-973 [frontend] feat: fix share report style * ADM-973 [frontend] feat: do not regenerate report Id when retry * ADM-973 [frontend] feat: retry all when retry * ADM-973 [frontend] feat: hide alert when close trigger * ADM-973 [frontend] feat: add test for retry situation and sort reportInfo * ADM-973 [frontend] feat: change download file name to reportId * ADM-973 [frontend] feat: add test for sortReportInfo * ADM-973 [frontend] feat: remove useless code * ADM-973 [frontend] feat: fix sonar --- .../hooks/useGenerateReportEffect.test.tsx | 30 +++++++++++++ frontend/__tests__/utils/Util.test.tsx | 42 +++++++++++++++++++ frontend/src/clients/report/CSVClient.ts | 5 +-- .../ReportStep/ReportContent/index.tsx | 32 +++++--------- frontend/src/containers/ReportStep/index.tsx | 20 +-------- frontend/src/containers/ShareReport/index.tsx | 3 +- frontend/src/hooks/useGenerateReportEffect.ts | 17 ++++++-- .../src/layouts/ShareReportTrigger/index.tsx | 1 + .../src/layouts/ShareReportTrigger/style.tsx | 3 +- frontend/src/utils/util.ts | 10 ++++- 10 files changed, 111 insertions(+), 52 deletions(-) diff --git a/frontend/__tests__/hooks/useGenerateReportEffect.test.tsx b/frontend/__tests__/hooks/useGenerateReportEffect.test.tsx index d323952b1..7b175c401 100644 --- a/frontend/__tests__/hooks/useGenerateReportEffect.test.tsx +++ b/frontend/__tests__/hooks/useGenerateReportEffect.test.tsx @@ -280,3 +280,33 @@ describe('use generate report effect', () => { expect(result.current.reportInfos[1].timeout4Dora.shouldShow).toEqual(true); }); }); + +describe('generateReportId execution', () => { + it('should not call generateReportId when call startToRequestData method given reportId is already generated', async () => { + reportClient.generateReportId = jest.fn().mockResolvedValue({ reportId: 'mockReportId' }); + reportClient.polling = jest + .fn() + .mockImplementation(async () => ({ status: HttpStatusCode.Ok, response: MOCK_REPORT_RESPONSE })); + reportClient.retrieveByUrl = jest.fn().mockImplementation(async () => MOCK_RETRIEVE_REPORT_RESPONSE); + + const { result } = setup(); + + await waitFor(() => { + result.current.startToRequestData(MOCK_GENERATE_REPORT_REQUEST_PARAMS_WITH_BOARD_METRIC_TYPE); + }); + + jest.runOnlyPendingTimers(); + + await waitFor(() => { + expect(reportClient.generateReportId).toHaveBeenCalledTimes(1); + }); + + await waitFor(() => { + result.current.startToRequestData(MOCK_GENERATE_REPORT_REQUEST_PARAMS_WITH_BOARD_METRIC_TYPE); + }); + + await waitFor(() => { + expect(reportClient.generateReportId).toHaveBeenCalledTimes(1); + }); + }); +}); diff --git a/frontend/__tests__/utils/Util.test.tsx b/frontend/__tests__/utils/Util.test.tsx index 7a1c6e427..0f8848645 100644 --- a/frontend/__tests__/utils/Util.test.tsx +++ b/frontend/__tests__/utils/Util.test.tsx @@ -16,6 +16,7 @@ import { sortDateRanges, sortDisabledOptions, sortLegend, + sortReportInfos, transformToCleanedBuildKiteEmoji, updateResponseCrews, valueFormatter, @@ -33,6 +34,7 @@ import { import { CleanedBuildKiteEmoji, OriginBuildKiteEmoji } from '@src/constants/emojis/emoji'; import { ICycleTimeSetting, IPipelineConfig } from '@src/context/Metrics/metricsSlice'; import { IPipeline } from '@src/context/config/pipelineTool/verifyResponseSlice'; +import { IReportInfo } from '../../src/hooks/useGenerateReportEffect'; import { BoardInfoResponse } from '@src/hooks/useGetBoardInfo'; import { EMPTY_STRING } from '@src/constants/commons'; import { PIPELINE_TOOL_TYPES } from '../fixtures'; @@ -447,6 +449,46 @@ describe('sortDateRanges function', () => { }); }); +describe('sortReportInfos function', () => { + const reportInfos = [ + { + id: '2024-03-19T00:00:00.000+08:00', + reportData: {}, + }, + { + id: '2024-02-01T00:00:00.000+08:00', + reportData: {}, + }, + { + id: '2024-04-01T00:00:00.000+08:00', + reportData: {}, + }, + ]; + const expectResult = [ + { + id: '2024-04-01T00:00:00.000+08:00', + reportData: {}, + }, + { + id: '2024-03-19T00:00:00.000+08:00', + reportData: {}, + }, + { + id: '2024-02-01T00:00:00.000+08:00', + reportData: {}, + }, + ]; + it('should descend reportInfos', () => { + const sortedReportInfos = sortReportInfos(reportInfos as IReportInfo[]); + expect(sortedReportInfos).toStrictEqual(expectResult); + }); + + it('should ascend reportInfos', () => { + const sortedReportInfos = sortReportInfos(reportInfos as IReportInfo[], false); + expect(sortedReportInfos).toStrictEqual(expectResult.reverse()); + }); +}); + describe('combineBoardInfo function', () => { const boardInfoResponses: BoardInfoResponse[] = [ { diff --git a/frontend/src/clients/report/CSVClient.ts b/frontend/src/clients/report/CSVClient.ts index d4bea1281..b151a4af2 100644 --- a/frontend/src/clients/report/CSVClient.ts +++ b/frontend/src/clients/report/CSVClient.ts @@ -4,7 +4,6 @@ import { downloadCSV } from '@src/utils/util'; import dayjs from 'dayjs'; export class CSVClient extends HttpClient { - parseTimeStampToHumanDate = (csvTimeStamp: number | undefined): string => dayjs(csvTimeStamp).format('HHmmSSS'); parseCollectionDateToHumanDate = (date: string) => dayjs(date).format('YYYYMMDD'); exportCSVData = async (params: CSVReportRequestDTO) => { @@ -19,9 +18,7 @@ export class CSVClient extends HttpClient { .then((res) => { const exportedFilename = `${params.dataType}-${this.parseCollectionDateToHumanDate( params.startDate, - )}-${this.parseCollectionDateToHumanDate(params.endDate)}-${this.parseTimeStampToHumanDate( - params.reportId, - )}.csv`; + )}-${this.parseCollectionDateToHumanDate(params.endDate)}-${params.reportId}.csv`; downloadCSV(exportedFilename, res.data); }) .catch((e) => { diff --git a/frontend/src/containers/ReportStep/ReportContent/index.tsx b/frontend/src/containers/ReportStep/ReportContent/index.tsx index 78d9d184d..87a631ab7 100644 --- a/frontend/src/containers/ReportStep/ReportContent/index.tsx +++ b/frontend/src/containers/ReportStep/ReportContent/index.tsx @@ -6,7 +6,7 @@ import { TimeoutErrorKey, useGenerateReportEffect, } from '@src/hooks/useGenerateReportEffect'; -import { sortDateRanges } from '@src/utils/util'; +import { sortDateRanges, sortReportInfos } from '@src/utils/util'; import { addNotification, @@ -54,8 +54,7 @@ import { uniqueId } from 'lodash'; export interface ReportContentProps { metrics: string[]; dateRanges: DateRangeList; - startToRequestDoraData: () => void; - startToRequestBoardData: () => void; + startToRequestData: () => void; reportInfos: IReportInfo[]; handleSave?: () => void; reportId?: number; @@ -75,16 +74,8 @@ export interface DateRangeRequestResult { } const ReportContent = (props: ReportContentProps) => { - const { - metrics, - dateRanges, - startToRequestDoraData, - startToRequestBoardData, - reportInfos, - handleSave, - reportId, - hideButtons = false, - } = props; + const { metrics, dateRanges, reportInfos, handleSave, reportId, startToRequestData, hideButtons = false } = props; + const dispatch = useAppDispatch(); const descendingDateRanges = sortDateRanges(dateRanges); @@ -98,6 +89,7 @@ const ReportContent = (props: ReportContentProps) => { return `${formattedStart}-${formattedEnd}`; }); + const ascendingReportInfos = sortReportInfos(reportInfos, false); const [selectedDateRange, setSelectedDateRange] = useState(descendingDateRanges[0]); const [currentDataInfo, setCurrentDataInfo] = useState(initReportInfo()); @@ -258,7 +250,7 @@ const ReportContent = (props: ReportContentProps) => { {shouldShowBoardMetrics && ( setPageType(REPORT_PAGE_TYPE.BOARD)} boardReport={currentDataInfo.reportData} errorMessage={getErrorMessage4Board()} @@ -267,7 +259,7 @@ const ReportContent = (props: ReportContentProps) => { )} {shouldShowDoraMetrics && ( setPageType(REPORT_PAGE_TYPE.DORA)} doraReport={currentDataInfo.reportData} metrics={metrics} @@ -383,10 +375,6 @@ const ReportContent = (props: ReportContentProps) => { setPageType(newValue === CHART_INDEX.BOARD ? REPORT_PAGE_TYPE.BOARD_CHART : REPORT_PAGE_TYPE.DORA_CHART); }; - const handleChartRetry = () => { - pageType === REPORT_PAGE_TYPE.DORA_CHART ? startToRequestDoraData() : startToRequestBoardData(); - }; - const tabProps = (index: number) => { return { id: `simple-tab-${index}`, @@ -403,9 +391,9 @@ const ReportContent = (props: ReportContentProps) => { case REPORT_PAGE_TYPE.DORA: return !!reportData && showDoraDetail(reportData); case REPORT_PAGE_TYPE.BOARD_CHART: - return showBoardChart(reportInfos); + return showBoardChart(ascendingReportInfos); case REPORT_PAGE_TYPE.DORA_CHART: - return showDoraChart(reportInfos.map((infos) => infos.reportData)); + return showDoraChart(ascendingReportInfos.map((infos) => infos.reportData)); } }; @@ -430,7 +418,7 @@ const ReportContent = (props: ReportContentProps) => { {startDate && endDate && ( {shouldShowChartRetryButton() && ( - + )} diff --git a/frontend/src/containers/ReportStep/index.tsx b/frontend/src/containers/ReportStep/index.tsx index 8323fa9ad..905dfebc4 100644 --- a/frontend/src/containers/ReportStep/index.tsx +++ b/frontend/src/containers/ReportStep/index.tsx @@ -15,10 +15,10 @@ import { selectConfig, selectDateRange, } from '@src/context/config/configSlice'; -import { BOARD_METRICS, DORA_METRICS, MESSAGE, RequiredData } from '@src/constants/resources'; import { IPipelineConfig, selectMetricsContent } from '@src/context/Metrics/metricsSlice'; import { selectReportId, selectTimeStamp } from '@src/context/stepper/StepperSlice'; import { ReportResponseDTO } from '@src/clients/report/dto/response'; +import { MESSAGE, RequiredData } from '@src/constants/resources'; import { useAppDispatch } from '@src/hooks/useAppDispatch'; import { MetricTypes } from '@src/constants/commons'; import ReportContent from './ReportContent'; @@ -170,21 +170,6 @@ const ReportStep = ({ handleSave }: ReportStepProps) => { ...(shouldShowDoraMetrics ? getDoraSetting() : {}), }; - const boardReportRequestBody = { - ...basicReportRequestBody, - metrics: metrics.filter((metric) => BOARD_METRICS.includes(metric)), - metricTypes: [MetricTypes.Board], - buildKiteSetting: undefined, - codebaseSetting: undefined, - }; - - const doraReportRequestBody = { - ...basicReportRequestBody, - metrics: metrics.filter((metric) => DORA_METRICS.includes(metric)), - metricTypes: [MetricTypes.DORA], - jiraBoardSetting: undefined, - }; - useEffect(() => { exportValidityTimeMin && isCsvFileGeneratedAtEnd && @@ -242,8 +227,7 @@ const ReportStep = ({ handleSave }: ReportStepProps) => { startToRequestData(doraReportRequestBody)} - startToRequestBoardData={() => startToRequestData(boardReportRequestBody)} + startToRequestData={() => startToRequestData(basicReportRequestBody)} reportInfos={reportInfos} handleSave={handleSave} reportId={reportId} diff --git a/frontend/src/containers/ShareReport/index.tsx b/frontend/src/containers/ShareReport/index.tsx index 96a1a7e3e..708326b54 100644 --- a/frontend/src/containers/ShareReport/index.tsx +++ b/frontend/src/containers/ShareReport/index.tsx @@ -28,8 +28,7 @@ const ShareReport = () => { metrics={metrics} dateRanges={dateRanges} reportInfos={reportInfos} - startToRequestBoardData={getData} - startToRequestDoraData={getData} + startToRequestData={getData} hideButtons /> diff --git a/frontend/src/hooks/useGenerateReportEffect.ts b/frontend/src/hooks/useGenerateReportEffect.ts index ee78a9b8a..1b009dfc7 100644 --- a/frontend/src/hooks/useGenerateReportEffect.ts +++ b/frontend/src/hooks/useGenerateReportEffect.ts @@ -104,6 +104,7 @@ export const useGenerateReportEffect = (): IUseGenerateReportEffect => { dateRangeList.map((dateRange) => ({ ...initReportInfo(), id: dateRange.startDate as string })), ); const [hasPollingStarted, setHasPollingStarted] = useState(false); + const [reportId, setReportId] = useState(''); let nextHasPollingStarted = false; const startToRequestData = async (params: ReportRequestDTO) => { @@ -115,8 +116,17 @@ export const useGenerateReportEffect = (): IUseGenerateReportEffect => { resetReportPageLoadingStatus(dateRangeList); - const reportIdRes = await reportClient.generateReportId(); + if (!reportId) { + const reportIdRes = await reportClient.generateReportId(); + setReportId(reportIdRes.reportId); + dispatch(updateReportId(reportIdRes.reportId)); + await retrieveUrlAndPolling(params, reportIdRes.reportId); + } else { + await retrieveUrlAndPolling(params, reportId); + } + }; + const retrieveUrlAndPolling = async (params: ReportRequestDTO, reportId: string) => { const res: PromiseSettledResult[] = await Promise.allSettled( dateRangeList.map(({ startDate, endDate }) => reportClient.retrieveByUrl( @@ -125,16 +135,15 @@ export const useGenerateReportEffect = (): IUseGenerateReportEffect => { startTime: formatDateToTimestampString(startDate!), endTime: formatDateToTimestampString(endDate!), }, - `${reportPath}/${reportIdRes.reportId}`, + `${reportPath}/${reportId}`, ), ), ); - updateErrorAfterFetchReport(res, metricTypes); + updateErrorAfterFetchReport(res, params.metricTypes); const { pollingInfos, pollingInterval } = assemblePollingParams(res); - dispatch(updateReportId(reportIdRes.reportId)); resetPollingLoadingStatusBeforePolling(pollingInfos.map((item) => item.id)); await pollingReport({ pollingInfos, interval: pollingInterval }); }; diff --git a/frontend/src/layouts/ShareReportTrigger/index.tsx b/frontend/src/layouts/ShareReportTrigger/index.tsx index bb159c48e..d313891c9 100644 --- a/frontend/src/layouts/ShareReportTrigger/index.tsx +++ b/frontend/src/layouts/ShareReportTrigger/index.tsx @@ -42,6 +42,7 @@ const ShareReportTrigger = () => { const handleClick = (event: React.MouseEvent) => { if (canShare) { setAnchorEl(anchorEl ? null : event.currentTarget); + setShowAlert(false); } }; diff --git a/frontend/src/layouts/ShareReportTrigger/style.tsx b/frontend/src/layouts/ShareReportTrigger/style.tsx index c9d44b823..b0b67d0ab 100644 --- a/frontend/src/layouts/ShareReportTrigger/style.tsx +++ b/frontend/src/layouts/ShareReportTrigger/style.tsx @@ -48,6 +48,7 @@ export const LinkLine = styled.div({ display: 'flex', alignItems: 'center', margin: '1.5rem 0', + height: '2.125rem', a: { cursor: 'pointer', marginRight: '0.625rem', @@ -59,7 +60,7 @@ export const LinkLine = styled.div({ export const ShareIconWrapper = styled.span(({ disabled }: { disabled: boolean }) => ({ padding: '0.5rem', - cursor: disabled ? 'unset !important' : 'pointer', + cursor: disabled ? 'not-allowed !important' : 'pointer', marginLeft: '0.2rem', '> svg': { color: disabled ? theme.main.errorMessage.color : 'white', diff --git a/frontend/src/utils/util.ts b/frontend/src/utils/util.ts index 0411e3346..9e065117f 100644 --- a/frontend/src/utils/util.ts +++ b/frontend/src/utils/util.ts @@ -15,6 +15,7 @@ import { IPipeline } from '@src/context/config/pipelineTool/verifyResponseSlice' import { ITrendInfo } from '@src/containers/ReportStep/ChartAndTitleWrapper'; import { includes, isEqual, sortBy, uniq, uniqBy } from 'lodash'; import { DateRangeList } from '@src/context/config/configSlice'; +import { IReportInfo } from '../hooks/useGenerateReportEffect'; import { BoardInfoResponse } from '@src/hooks/useGetBoardInfo'; import { DATE_FORMAT_TEMPLATE } from '@src/constants/template'; import duration from 'dayjs/plugin/duration'; @@ -115,7 +116,14 @@ export const formatDateToTimestampString = (date: string) => { export const sortDateRanges = (dateRanges: DateRangeList, descending = true) => { const result = [...dateRanges].sort((a, b) => { - return dayjs(b.startDate as string).diff(dayjs(a.startDate as string)); + return dayjs(b.startDate).diff(dayjs(a.startDate)); + }); + return descending ? result : result.reverse(); +}; + +export const sortReportInfos = (reportInfos: IReportInfo[], descending = true) => { + const result = [...reportInfos].sort((a, b) => { + return dayjs(b.id).diff(dayjs(a.id)); }); return descending ? result : result.reverse(); };