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.

469 lines (411 loc) 13.4 kB
import parseUrl from 'parseurl' import { Url } from 'url' import { Request, Response } from 'express' import Dict = NodeJS.Dict import type { Columns } from '../../components/_reports/report-columns-form/types' import type { Template } from '../../types/Templates' import type { AsyncReportUtilsParams } from '../../types/AsyncReportUtils' import type { DataTable } from '../DataTableBuilder/types' import type { components } from '../../types/api' import type { AsyncSummary, RequestedReport } from '../../types/UserReports' import { LoadType, ReportType } from '../../types/UserReports' import ReportQuery from '../../types/ReportQuery' import CollatedSummaryBuilder from '../CollatedSummaryBuilder/CollatedSummaryBuilder' import PaginationUtils from '../../components/_reports/report-pagination/utils' import TotalsUtils from '../../components/_reports/report-totals/utils' import ReportFiltersUtils from '../../components/_filters/utils' import ColumnUtils from '../../components/_reports/report-columns-form/utils' import ReportActionsUtils from '../../components/_reports/report-actions/utils' import UserReportsUtils from '../../components/user-reports/utils' import LocalsHelper from '../localsHelper' import { DownloadActionParams } from '../../components/_reports/report-actions/types' import { Services } from '../../types/Services' import { ChildData } from '../ParentChildDataTableBuilder/types' import DataTableUtils from '../../components/_reports/report-data-table/utils' export const getData = async ({ res, req, services, token, userId, }: AsyncReportUtilsParams & { token: string; userId: string }) => { const { definitionsPath: definitionPath } = LocalsHelper.getValues(res) const { reportId, variantId, id, tableId } = req.params const reportVariantId = variantId || id // Get the definition const definition: components['schemas']['SingleVariantReportDefinition'] = await services.reportingService.getDefinition(token, reportId, reportVariantId, definitionPath) // Get the request data const requestData: RequestedReport = await services.requestedReportService.getReportByTableId(tableId, userId) // Get the reportData const { reportData, reportQuery } = await getReportData(definition, services, token, req, res) // Get the summary data, if applicable const summariesData = !definition.variant.summaries ? [] : await getSummariesData(definition, services, token, req, res) // Get the child data, if applicable const childData: ChildData[] = !definition.variant.childVariants ? [] : await getChildData(definition, services, token, req, res, requestData) return { definition, requestData, reportData, summariesData, childData, reportQuery, } } const getReportData = async ( reportDefinition: components['schemas']['SingleVariantReportDefinition'], services: Services, token: string, req: Request, res: Response, ) => { const { definitionsPath } = LocalsHelper.getValues(res) const { reportId, variantId, id, tableId } = req.params const reportVariantId = variantId || id const { variant } = reportDefinition const { specification } = variant const reportQuery = new ReportQuery({ fields: specification.fields, template: specification.template as Template, queryParams: req.query, definitionsPath, }) const reportData = await services.reportingService.getAsyncReport( token, reportId, reportVariantId, tableId, reportQuery.toRecordWithFilterPrefix(true), ) return { reportData, reportQuery, } } export const getSummariesData = async ( reportDefinition: components['schemas']['SingleVariantReportDefinition'], services: Services, token: string, req: Request, res: Response, ): Promise<AsyncSummary[]> => { const { reportId, variantId, id, tableId } = req.params const { definitionsPath: dataProductDefinitionsPath } = LocalsHelper.getValues(res) const reportVariantId = variantId || id return Promise.all( reportDefinition.variant.summaries.map(async (summary) => { const summaryReport = await services.reportingService.getAsyncSummaryReport( token, reportId, reportVariantId, tableId, summary.id, { dataProductDefinitionsPath, }, ) return { ...summary, data: summaryReport, } }), ) } export const getChildData = async ( reportDefinition: components['schemas']['SingleVariantReportDefinition'], services: Services, token: string, req: Request, res: Response, requestData: RequestedReport, ): Promise<ChildData[]> => { const { definitionsPath: dataProductDefinitionsPath } = LocalsHelper.getValues(res) const { reportId } = req.params return Promise.all( reportDefinition.variant.childVariants.map(async (childVariant) => { const { specification } = childVariant const query = new ReportQuery({ fields: specification.fields, template: reportDefinition.variant.specification.template, queryParams: req.query, definitionsPath: dataProductDefinitionsPath, }).toRecordWithFilterPrefix(true) const { tableId: childTableId } = requestData.childExecutionData.find((e) => e.variantId === childVariant.id) const childReport = await services.reportingService.getAsyncReport( token, reportId, childVariant.id, childTableId, query, ) return { id: childVariant.id, data: childReport, } }), ) } /** * Entry point function to render the report * * @param {AsyncReportUtilsParams} { req, res, services } * @return {*} */ const renderReport = async ({ req, res, services }: AsyncReportUtilsParams) => { const { token, userId } = LocalsHelper.getValues(res) // Get the report data const { definition, requestData, reportData, childData, summariesData, reportQuery } = await getData({ req, res, services, token, userId, }) // Get the columns const columns = ColumnUtils.getColumns(definition.variant.specification, <string[]>req.query.columns) // Get the data table const dataTable: DataTable[] = DataTableUtils.createDataTable( definition, columns, reportData, childData, summariesData, reportQuery, ) // Get the template data const templateData = await getTemplateData( req, res, services, definition, requestData, summariesData, dataTable, columns, reportQuery, ) const renderData = { ...templateData, dataTable, } if (Object.keys(requestData).length) { UserReportsUtils.updateLastViewed({ req, services, reportStateData: requestData, userId, search: renderData.search, href: renderData.pathname, filters: templateData.filterData.filters, }) } return { renderData } } const getTemplateData = async ( req: Request, res: Response, services: Services, definition: components['schemas']['SingleVariantReportDefinition'], requestData: RequestedReport, summariesData: AsyncSummary[], dataTable: DataTable[], columns: Columns, reportQuery: ReportQuery, ) => { const url = parseUrl(req) const urls = setUrls(url) const definitionData = extractDataFromDefinition(definition) const { fields, specification } = definitionData const requestedData = extractDataFromRequest(requestData) const count = await getCount(definition, requestData, services, res, reportQuery) const interactive = true const filterData = await ReportFiltersUtils.getFilters({ fields, req, interactive, }) const features = await setFeatures(services, res, requestData, definition, columns, count, urls) const meta = setMetaData(definition, res) let reportSummaries if (summariesData.length) { const collatedSummaryBuilder = new CollatedSummaryBuilder(specification, summariesData) reportSummaries = collatedSummaryBuilder.collatePageSummaries() } let pagination let totals if (meta.template === 'list') { pagination = PaginationUtils.getPaginationData(url, count) const { pageSize, currentPage, totalRows } = pagination totals = TotalsUtils.getTotals(pageSize, currentPage, totalRows, dataTable[0].rowCount) } return { ...(showColumns(specification) && { columns }), filterData, count, ...meta, ...features, ...requestedData, ...urls, ...(pagination && { pagination }), ...(totals && { totals }), ...(reportSummaries && { reportSummaries }), } } const showColumns = (specification: components['schemas']['Specification']) => { const { template } = specification return !['row-section', 'row-section-child', 'summary', 'summary-section'].includes(template) } const setMetaData = (definition: components['schemas']['SingleVariantReportDefinition'], res: Response) => { const { classification } = definition.variant const { template } = definition.variant.specification const { csrfToken } = LocalsHelper.getValues(res) return { csrfToken, classification, template, loadType: LoadType.ASYNC, type: ReportType.REPORT, } } const setUrls = (url: Url) => { const { search, pathname } = url const reportUrl = pathname.replace('/download-disabled', '').replace('/download-disabled?', '') const reportSearch = search const encodedSearch = search ? encodeURIComponent(search) : undefined return { reportUrl, reportSearch, encodedSearch, search, pathname, } } const setFeatures = async ( services: Services, res: Response, requestData: RequestedReport, definition: components['schemas']['SingleVariantReportDefinition'], columns: Columns, count: number, urls: Dict<string>, ) => { const { csrfToken, userId, bookmarkingEnabled, downloadingEnabled } = LocalsHelper.getValues(res) const { reportId } = requestData const id = requestData.variantId || requestData.id const { variant } = definition let canDownload if (downloadingEnabled) { canDownload = await services.downloadPermissionService.downloadEnabled(userId, reportId, id) } let bookmarked if (bookmarkingEnabled) { bookmarked = await services.bookmarkService.isBookmarked(id, reportId, userId) } const actions = setActions(csrfToken, variant, requestData, columns, canDownload, count, urls.pathname, urls.search) const { printable } = variant return { actions, canDownload, bookmarked, printable, } } const getCount = async ( definition: components['schemas']['SingleVariantReportDefinition'], requestData: RequestedReport, services: Services, res: Response, reportQuery: ReportQuery, ) => { const { token } = LocalsHelper.getValues(res) const { tableId, reportId } = requestData const id = requestData.variantId || requestData.id return !definition.variant.interactive ? services.reportingService.getAsyncCount(token, tableId) : services.reportingService.getAsyncInteractiveCount(token, tableId, reportId, id, reportQuery) } const extractDataFromDefinition = (definition: components['schemas']['SingleVariantReportDefinition']) => { const { variant } = definition const { classification, printable, specification } = variant const { template, fields } = specification const { interactive } = <components['schemas']['VariantDefinition'] & { interactive?: boolean }>variant return { classification, printable, specification, template, interactive, fields, variant, } } const extractDataFromRequest = (requestData: RequestedReport) => { return { reportName: requestData.reportName, name: requestData.name, description: requestData.description, requestedTimestamp: new Date(requestData.timestamp.requested).toLocaleString(), reportId: requestData.reportId, tableId: requestData.tableId, id: requestData.variantId || requestData.id, executionId: requestData.executionId, querySummary: requestData.query.summary, requestUrl: requestData.url.request, defaultQuery: requestData.url.report.default, dataProductDefinitionsPath: requestData.dataProductDefinitionsPath, } } const setActions = ( csrfToken: string, variant: components['schemas']['VariantDefinition'], requestData: RequestedReport, columns: Columns, canDownload: boolean, count: number, currentUrl: string, currentQueryParams: string, ) => { const { reportName, name, id, variantId, reportId, tableId, executionId, dataProductDefinitionsPath, url } = requestData const requestUrl = url.request.fullUrl const { printable } = variant const ID = variantId || id const downloadConfig: DownloadActionParams = { enabled: count > 0 && canDownload !== undefined, name, reportName, csrfToken, reportId, id: ID, tableId, columns: columns.value, definitionPath: dataProductDefinitionsPath, loadType: LoadType.ASYNC, canDownload, currentUrl, currentQueryParams, } const shareConfig = { reportName, name, url: requestUrl, } const refreshConfig = { url: requestUrl, executionId, } return ReportActionsUtils.getActions({ download: downloadConfig, print: { enabled: printable }, share: shareConfig, refresh: refreshConfig, copy: { url: requestUrl }, }) } export default { renderReport, }