UNPKG

@ministryofjustice/hmpps-digital-prison-reporting-frontend

Version:

The Digital Prison Reporting Frontend contains templates and code to help display data effectively in UI applications.

204 lines (172 loc) 5.73 kB
import dayjs from 'dayjs' import { Request, Response } from 'express' import { ReportType, RequestedReport, RequestStatus } from '../types/UserReports' import { AsyncReportUtilsParams } from '../types/AsyncReportUtils' import { ChildReportExecutionData } from '../types/ExecutionData' import logger from './logger' import { Services } from '../types/Services' import localsHelper from './localsHelper' interface GetStatusUtilsResponse { status: RequestStatus errorMessage?: string reportData?: RequestedReport | undefined } interface StatusResponseError { userMessage: string developerMessage: string } interface StatusResponse { status: RequestStatus error: StatusResponseError } const BAD_REQUEST_STATUSES: Array<RequestStatus> = [RequestStatus.ABORTED, RequestStatus.FAILED, RequestStatus.EXPIRED] const IN_PROGRESS_REQUEST_STATUSES: Array<RequestStatus> = [ RequestStatus.SUBMITTED, RequestStatus.STARTED, RequestStatus.PICKED, ] function findWorstStatusResponse(statusRequests: Array<Promise<StatusResponse>>): Promise<StatusResponse> { return Promise.all(statusRequests).then((statusResponses) => { const badStatus = statusResponses.find( (response) => typeof response.status === 'number' || BAD_REQUEST_STATUSES.includes(response.status as RequestStatus), ) if (badStatus) { return badStatus } const inProgressStatus = statusResponses.find((response) => IN_PROGRESS_REQUEST_STATUSES.includes(response.status as RequestStatus), ) if (inProgressStatus) { return inProgressStatus } return statusResponses[0] }) } const getStatusByReportType = async ( services: Services, req: Request, res: Response, token: string, userId: string, ) => { const { definitionsPath } = localsHelper.getValues(res) const { type, reportId, executionId, id, tableId } = req.body const requestedReport = await services.requestedReportService.getReportByExecutionId(executionId, userId) const reports = requestedReport.childExecutionData ?? [] if (type === ReportType.REPORT) { reports.push({ executionId, tableId, variantId: id }) } const statusRequests = reports.map((executionData: ChildReportExecutionData) => services.reportingService.getAsyncReportStatus( token, reportId, executionData.variantId, executionData.executionId, definitionsPath, executionData.tableId, ), ) if (type === ReportType.DASHBOARD) { statusRequests.push( services.dashboardService.getAsyncStatus(token, reportId, id, executionId, definitionsPath, tableId), ) } const statusResponse = await findWorstStatusResponse(statusRequests) return { status: statusResponse.status, statusResponse, } } export const getStatus = async ({ req, res, services }: AsyncReportUtilsParams): Promise<GetStatusUtilsResponse> => { const { token, userId } = localsHelper.getValues(res) const { status: currentStatus, requestedAt } = req.body const timeoutExemptStatuses = [RequestStatus.READY, RequestStatus.EXPIRED, RequestStatus.FAILED] let status let errorMessage let statusResponse try { ;({ status, statusResponse } = await getStatusByReportType(services, req, res, token, userId)) if ( shouldTimeoutRequest({ requestedAt, compareTime: new Date(), durationMins: 15 }) && !timeoutExemptStatuses.includes(status) ) { throw new Error('Request taking too long. Request Halted') } if (status === RequestStatus.FAILED) { logger.error(`Error: ${JSON.stringify(statusResponse.error)}`) const { userMessage, developerMessage } = statusResponse.error if (userMessage || developerMessage) { errorMessage = statusResponse.error } else { errorMessage = { developerMessage: statusResponse.error, } } } } catch (error) { logger.error(`Error: ${JSON.stringify(error)}`) const { data } = error errorMessage = data || { userMessage: error.message } status = currentStatus === RequestStatus.FINISHED ? RequestStatus.EXPIRED : RequestStatus.FAILED } if (typeof status === 'number') { status = RequestStatus.FAILED } return { status, ...(errorMessage && { errorMessage }), } } /** * Gets the expired status of a viewed report * - if reports status was READY/FINISHED * - And the new Status is an error status * - it means the report has expired * * @param {AsyncReportUtilsParams} { req, res, services } * @return {*} */ export const getExpiredStatus = async ({ req, res, services }: AsyncReportUtilsParams) => { const { token, userId } = localsHelper.getValues(res) const { executionId, status: currentStatus } = req.body let errorMessage let status try { ;({ status } = await getStatusByReportType(services, req, res, token, userId)) } catch (error) { const { data } = error errorMessage = (data ?? {}).userMessage status = currentStatus === RequestStatus.READY || currentStatus === RequestStatus.FINISHED ? RequestStatus.EXPIRED : RequestStatus.FAILED } const result: GetStatusUtilsResponse = { status, ...(errorMessage && { errorMessage }), } return { isExpired: result.status === RequestStatus.EXPIRED, executionId, } } export const shouldTimeoutRequest = ({ requestedAt, compareTime, durationMins, }: { requestedAt: Date compareTime: Date durationMins: number }) => { const compareDate = dayjs(compareTime) const requestDate = dayjs(requestedAt) const result = compareDate.diff(requestDate, 'minutes') return result >= durationMins } export default { getStatus, getExpiredStatus, shouldTimeoutRequest, }