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.

363 lines (321 loc) 11.5 kB
/* eslint-disable no-param-reassign */ import { Request, Response, NextFunction } from 'express' // Utils import FiltersFormUtils from '../async-filters-form/utils' import LocalsHelper from '../../../utils/localsHelper' import FiltersUtils from '../../_filters/utils' import { removeDuplicates } from '../../../utils/reportStoreHelper' import UserStoreItemBuilder from '../../../utils/UserStoreItemBuilder' // Types import type ReportingService from '../../../services/reportingService' import { ReportType, RequestFormData, RequestStatus } from '../../../types/UserReports' import type { ExecutionData, ChildReportExecutionData } from '../../../types/ExecutionData' import type { AsyncReportUtilsParams, RequestDataResult, RequestReportData } from '../../../types/AsyncReportUtils' import type { RenderFiltersReturnValue, SetQueryFromFiltersResult } from '../async-filters-form/types' import type { components } from '../../../types/api' import type { DashboardDefinition } from '../../_dashboards/dashboard/types' import type { Services } from '../../../types/Services' import type DashboardService from '../../../services/dashboardService' /** * Updates the store with the request details * * @param {Request} req * @param {Response} res * @param {Services} services * @param {SetQueryFromFiltersResult} querySummaryData * @param {string} executionData * @return {*} {Promise<string>} */ export const updateStore = async ({ req, res, services, queryData, executionData, childExecutionData, }: { req: Request res: Response services: Services queryData?: SetQueryFromFiltersResult executionData: ExecutionData childExecutionData: Array<ChildReportExecutionData> }): Promise<string> => { const { search, id, type } = req.body const { userId, definitionsPath, dpdPathFromQuery } = LocalsHelper.getValues(res) await removeDuplicates({ storeService: services.requestedReportService, userId, id, search }) await removeDuplicates({ storeService: services.recentlyViewedService, userId, id, search }) const reportData: RequestFormData = req.body let requestedReportData switch (type) { case ReportType.REPORT: requestedReportData = new UserStoreItemBuilder(reportData) .addExecutionData(executionData) .addChildExecutionData(childExecutionData) .addFilters(queryData.filterData) .addSortData(queryData.sortData) .addDefinitionsPath(definitionsPath, dpdPathFromQuery) .addRequestUrls() .addQuery(queryData) .addStatus(RequestStatus.SUBMITTED) .addTimestamp() .build() break case ReportType.DASHBOARD: { requestedReportData = new UserStoreItemBuilder(reportData) .addExecutionData(executionData) .addChildExecutionData(childExecutionData) .addFilters(queryData.filterData) .addDefinitionsPath(definitionsPath, dpdPathFromQuery) .addRequestUrls() .addQuery(queryData) .addStatus(RequestStatus.SUBMITTED) .addTimestamp() .addMetrics(JSON.parse(req.body.sections)) .build() break } default: break } await services.requestedReportService.addReport(userId, requestedReportData) return requestedReportData.url.polling.pathname } async function requestChildReports( childVariants: Array<components['schemas']['ChildVariantDefinition']>, reportingService: ReportingService, token: string, reportId: string, queryData: SetQueryFromFiltersResult, dataProductDefinitionsPath: string, ): Promise<Array<ChildReportExecutionData>> { return Promise.all( childVariants.map((childVariant) => reportingService .requestAsyncReport(token, reportId, childVariant.id, { ...queryData.query, dataProductDefinitionsPath, }) .then((response) => { const { executionId, tableId } = response return { executionId, tableId, variantId: childVariant.id } }), ), ) } const requestProduct = async ({ req, res, token, dashboardService, reportingService, }: { req: Request res: Response token: string dashboardService: DashboardService reportingService: ReportingService }): Promise<{ executionData: ExecutionData childExecutionData: Array<ChildReportExecutionData> queryData: SetQueryFromFiltersResult }> => { const { definitionsPath: dataProductDefinitionsPath } = LocalsHelper.getValues(res) const { reportId, id, type } = req.body let fields let queryData let executionId let tableId let definition let childVariants: components['schemas']['ChildVariantDefinition'][] = [] if (type === ReportType.REPORT) { definition = await reportingService.getDefinition(token, reportId, id, dataProductDefinitionsPath) fields = definition ? definition.variant.specification.fields : [] queryData = FiltersFormUtils.setQueryFromFilters(req, fields) ;({ executionId, tableId } = await reportingService.requestAsyncReport(token, reportId, id, { ...queryData.query, dataProductDefinitionsPath, })) childVariants = definition.variant.childVariants ?? [] } if (type === ReportType.DASHBOARD) { definition = await dashboardService.getDefinition(token, id, reportId, dataProductDefinitionsPath) fields = definition ? definition.filterFields : [] queryData = FiltersFormUtils.setQueryFromFilters(req, fields) ;({ executionId, tableId } = await dashboardService.requestAsyncDashboard(token, reportId, id, { ...queryData.query, dataProductDefinitionsPath, })) } const childExecutionData = await requestChildReports( childVariants, reportingService, token, reportId, queryData, dataProductDefinitionsPath, ) return { executionData: { executionId, tableId }, childExecutionData, queryData, } } const renderDashboardRequestData = async ({ token, reportId, definitionPath, services, definition, }: { token: string reportId: string definitionPath: string services: Services definition: DashboardDefinition }) => { const productDefinitions = await services.reportingService.getDefinitions(token, definitionPath) const productDefinition = productDefinitions.find( (def: components['schemas']['ReportDefinitionSummary']) => def.id === reportId, ) const reportName = productDefinition.name const { name, description, sections, filterFields: fields } = definition return { reportName, name, description, sections: sections || [], fields, } } const renderReportRequestData = async (definition: components['schemas']['SingleVariantReportDefinition']) => { return { definition, reportName: definition.name, name: definition.variant.name, description: definition.variant.description || definition.description, template: definition.variant.specification, fields: definition?.variant?.specification?.fields, interactive: definition?.variant?.interactive, } } const getDefintionByType = async (req: Request, res: Response, next: NextFunction, services: Services) => { const { token, definitionsPath } = LocalsHelper.getValues(res) const { reportId, id, variantId, type } = req.params let definition if (type === ReportType.REPORT) { definition = await services.reportingService.getDefinition(token, reportId, variantId || id, definitionsPath) } if (type === ReportType.DASHBOARD) { definition = await services.dashboardService.getDefinition(token, variantId || id, reportId, definitionsPath) } return definition } export default { /** * Sends the request for the async report * * @param {AsyncReportUtilsParams} { req, res, services } * @return {*} */ request: async ({ req, res, services }: AsyncReportUtilsParams) => { const { token } = LocalsHelper.getValues(res) const requestArgs = { req, res, token } const { executionData, queryData, childExecutionData } = await requestProduct({ ...requestArgs, dashboardService: services.dashboardService, reportingService: services.reportingService, }) let redirect = '' if (executionData) { redirect = await updateStore({ req, res, services, queryData, executionData, childExecutionData, }) } return redirect }, cancelRequest: async ({ req, res, services }: AsyncReportUtilsParams) => { const { token, userId, definitionsPath } = LocalsHelper.getValues(res) const { reportId, id, executionId, type } = req.body let service if (type === ReportType.REPORT) service = services.reportingService if (type === ReportType.DASHBOARD) service = services.dashboardService const response = await service.cancelAsyncRequest(token, reportId, id, executionId, definitionsPath) if (response && response.cancellationSucceeded) { await services.requestedReportService.updateStatus(executionId, userId, RequestStatus.ABORTED) } }, /** * Returns the data required for rendering the async filters component * * @param {AsyncReportUtilsParams} { req, res, dataSources } * @return {*} */ renderRequest: async ({ req, res, services, next }: AsyncReportUtilsParams): Promise<RequestDataResult | boolean> => { try { const { token, csrfToken, definitionsPath: definitionPath, dpdPathFromQuery } = LocalsHelper.getValues(res) const { reportId, type, id } = req.params const { definition } = req.body const definitionApiArgs = { token, reportId, definitionPath, services } let name let reportName let description let template let fields: components['schemas']['FieldDefinition'][] let sections let interactive let defaultInteractiveQueryString let filtersData if (type === ReportType.REPORT) { ;({ name, reportName, description, fields, interactive } = await renderReportRequestData(definition)) } if (type === ReportType.DASHBOARD) { ;({ name, reportName, description, sections, fields } = await renderDashboardRequestData({ ...definitionApiArgs, definition, })) } if (fields) { filtersData = <RenderFiltersReturnValue>await FiltersFormUtils.renderFilters(fields, interactive) filtersData.filters = FiltersUtils.setFilterValuesFromRequest(filtersData.filters, req) defaultInteractiveQueryString = FiltersUtils.setFilterQueryFromFilterDefinition(fields, true) } const reportData: RequestReportData = { reportName, name, description, reportId, id, ...(dpdPathFromQuery && { definitionPath }), ...(defaultInteractiveQueryString?.length && { defaultInteractiveQueryString }), csrfToken, template, sections, type: type as ReportType, } return { title: `Request ${type}`, filtersDescription: `Customise your ${type} using the filters below and submit your request.`, filtersData, reportData, } } catch (error) { next(error) return false } }, getFiltersFromReqBody: (req: Request) => { return Object.keys(req.body) .filter((attr) => attr.includes('filters.')) .filter((attr) => !!req.body[attr]) .map((attr) => { return { name: attr, value: req.body[attr] } }) }, getDefintionByType, }