@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
text/typescript
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,
}