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.

433 lines (430 loc) 14.2 kB
import { ListType } from './types.js'; import localsHelper, { getRouteLocals } from '../../utils/localsHelper.js'; import { RequestStatus, ReportType, LoadType } from '../../types/UserReports.js'; import { buildMyReportListRow } from './my-reports-list-item/utils.js'; import logger from '../../utils/logger.js'; import { shouldRunExpiryCheck, expireFinishedReports, recordExpiryCheck } from '../../utils/ReportStatus/getReportStatus.js'; import { captureDprError } from '../../utils/captureError.js'; import { buildLoadRequestAction } from './my-reports-list-item/my-reports-list-item-actions/utils.js'; import { renderTruncateAsHtml } from '../truncate/utils.js'; /** * Initialises the "My Reports" component data * * @param {Request} req * @param {Response} res * @param {Services} services * @return {*} {DprMyReport} */ const initMyReports = async (req, res, services, options) => { const { bookmarkingEnabled, subscriptionsEnabled } = localsHelper.getValues(res); await checkExpiredAndGetReports(res, services); const messages = setMessages(res); return { ...(subscriptionsEnabled && { subscriptions: await initSubscribed(req, res, options) }), ...(bookmarkingEnabled && { bookmarks: await initBookmarks(res, req, services) }), requested: await initRequested(req, res, options), viewed: await initViewed(req, res, options), ...(messages && { messages }), }; }; /** * Sets the messages if any * * @param {Response} res * @return {*} {MyReportsMessages} */ const setMessages = (res) => { const subscriptionMessages = { subscribedMessage: res.locals['subscribedMessage'] || [], unsubscribedMessage: res.locals['unsubscribedMessage'] || [], }; const { removedReports } = res.locals; return { subscriptionMessages, removedReports, }; }; const checkExpiredAndGetReports = async (res, services) => { const { requestedReports, recentlyViewedReports } = localsHelper.getValues(res); if (shouldRunExpiryCheck(res.req.session)) { try { const refreshedReports = await expireFinishedReports({ requestedReports, recentlyViewedReports, services, res, }); res.locals['requestedReports'] = refreshedReports.requestedReports; res.locals['recentlyViewedReports'] = refreshedReports.recentlyViewedReports; recordExpiryCheck(res.req.session); } catch (error) { logger.error(error); } } return { requestedReports, recentlyViewedReports, }; }; /** * Init bookmarks list * * @param {Request} req * @param {Response} res * @param {Services} services * @return {*} {DprMyReportListConfig} */ const initBookmarks = async (res, req, services, options) => { const totalItems = await buildBookmarkListItems(res, req, services); const totals = buildTotals(res, totalItems, ListType.BOOKMARKS, options); const items = cutItemsToSize(totalItems, options); return { title: 'Bookmarks', listType: ListType.BOOKMARKS, headings: buildHeadings(ListType.BOOKMARKS), items, totals, }; }; /** * Init Requested Reports list * * @param {Request} req * @param {Response} res * @return {*} */ const initRequested = async (req, res, options) => { const { csrfToken } = localsHelper.getValues(res); const totalItems = await buildListItems(req, res, ListType.REQUESTED); const totals = buildTotals(res, totalItems, ListType.REQUESTED, options); const items = cutItemsToSize(totalItems, options); return { title: 'Requested reports', listType: ListType.REQUESTED, csrfToken, headings: buildHeadings(ListType.REQUESTED), items, totals, }; }; /** * Init Viewed reports list * * @param {Request} req * @param {Response} res * @return {*} */ const initViewed = async (req, res, options) => { const { csrfToken } = localsHelper.getValues(res); const totalItems = await buildListItems(req, res, ListType.VIEWED); const totals = buildTotals(res, totalItems, ListType.VIEWED, options); const items = cutItemsToSize(totalItems, options); return { title: 'Recently viewed', listType: ListType.VIEWED, csrfToken, headings: buildHeadings(ListType.VIEWED), items, totals, }; }; const initSubscribed = async (req, res, options) => { const { csrfToken } = localsHelper.getValues(res); const totalItems = await buildListItems(req, res, ListType.SUBSCRIPTIONS); const totals = buildTotals(res, totalItems, ListType.SUBSCRIPTIONS, options); const items = cutItemsToSize(totalItems, options); return { title: 'Subscriptions', listType: ListType.SUBSCRIPTIONS, csrfToken, headings: buildHeadings(ListType.SUBSCRIPTIONS), items, totals, }; }; // ---------------------------------------------- // LIST ITEMS // ----------------------------------------------- /** * Builds the requested & views list items * * @param {Request} req * @param {Response} res * @param {ListType} listType * @return {*} {DprMyReportItem[]} */ const buildListItems = async (req, res, listType) => { const listData = await getDataForList(res, listType); if (!listData) { return []; } return listData.map((data) => { const status = data.status; return buildMyReportListRow(data, status, req, res, listType); }); }; const cutItemsToSize = (items, options) => { return options?.maxRows ? items.slice(0, options.maxRows) : items; }; /** * Builds the list totals * * @param {Response} res * @param {DprMyReportItem[]} items * @param {ListType} listType * @param {(MyReportsOptions | undefined)} [options] * @return {*} {MyReportsListTotals} */ const buildTotals = (res, items, listType, options) => { if (!options?.maxRows) { return { total: items.length, shown: items.length, }; } const { maxRows } = options; const total = items.length; const shown = items.length > maxRows ? maxRows : items.length; let href; if (maxRows < total) { const { requestedListPath, recentlyViewedListPath, bookmarkListPath } = getRouteLocals(res); switch (listType) { case ListType.BOOKMARKS: href = `${bookmarkListPath}/list`; break; case ListType.REQUESTED: href = `${requestedListPath}/list`; break; case ListType.VIEWED: href = `${recentlyViewedListPath}/list`; break; default: href = `${requestedListPath}/list`; break; } } return { total: items.length, shown, href, maxRows, }; }; /** * Builds the bookmark list items * * @param {Response} res * @param {Services} services * @return {*} {Promise<DprMyReportItem[]>} */ const buildBookmarkListItems = async (res, req, services) => { const { bookmarks } = localsHelper.getValues(res); // loop it if (!bookmarks) { return []; } // gather data for loop. const mappedBookmarks = await mapBookmarks(bookmarks, services, res); return mappedBookmarks.map(bookmark => { const { name, reportName, type, description } = bookmark; return { title: { productName: name, reportName, reportType: type, }, description: renderTruncateAsHtml({ stringValue: description, classes: 'govuk-body-s', charLength: 50 }), actions: buildBookmarkActionsCell(bookmark, res, req), }; }); }; /** * Map bookmarks to display data * * @param {BookmarkStoreData[]} bookmarks * @param {Services} services * @param {Response} res * @return {*} */ const mapBookmarks = async (bookmarks, services, res) => { const { token, definitionsPath } = localsHelper.getValues(res); const mapped = await Promise.all(bookmarks.map(async (bm) => { try { const resolved = await resolveBookmarkDefinition(bm, services, token, definitionsPath); return { id: resolved.sourceId, reportId: bm.reportId, name: resolved.name, reportName: resolved.reportName, type: resolved.reportType, description: resolved.description, loadType: resolved.loadType, }; } catch (error) { const meta = { reportId: bm.reportId, id: bm.variantId || bm.id, }; captureDprError(error, `Unable to get info for bookmark`, meta); return null; } })); return mapped.filter((bm) => bm !== null); }; /** * Resolves the bookmark definition for the bookmark * * @param {BookmarkStoreData} bm * @param {Services} services * @param {string} token * @param {string} definitionsPath * @return {*} */ const resolveBookmarkDefinition = async (bm, services, token, definitionsPath) => { const { id, reportId, type, variantId } = bm; const sourceId = variantId || id; const summary = await services.reportingService.getDefinitionSummary(token, reportId, definitionsPath); if (!type || type === ReportType.REPORT) { const definition = await services.reportingService.getDefinition(token, reportId, sourceId, definitionsPath); const defSummary = summary.variants.find(v => v.id === sourceId); return { sourceId, reportType: ReportType.REPORT, reportName: definition.variant.name, name: definition.name, description: definition.variant.description || definition.description || '', loadType: defSummary?.loadType ? defSummary.loadType : LoadType.ASYNC, }; } const definition = await services.dashboardService.getDefinition(token, reportId, sourceId, definitionsPath); const defSummary = summary.dashboards?.find(d => d.id === sourceId); return { sourceId, reportType: type, reportName: definition?.name || '', name: summary?.name || '', description: definition?.description || summary?.description || '', loadType: defSummary?.loadType ? defSummary.loadType : LoadType.ASYNC, }; }; /** * Builds the bookmark actions cell * * @param {*} data * @param {Response} res * @return {*} {DprMyReportActions} */ const buildBookmarkActionsCell = (data, res, req) => { const { load, request } = buildLoadRequestAction(res, req, data); const bookmark = buildBookmarkRemoveAction(res, data); return { ...(load && { load }), ...(request && { request }), bookmark, }; }; /** * Build the bookmark link config * * @param {Response} res * @param {MappedBookmarks} data * @return {*} */ const buildBookmarkRemoveAction = (res, data) => { const { reportId, id, type: reportType } = data; const { csrfToken } = localsHelper.getValues(res); const { bookmarkActionEndpoint } = localsHelper.getRouteLocals(res); return { reportId, id, reportType, csrfToken, bookmarkActionEndpoint, linkType: 'remove', linkText: 'Remove bookmark', }; }; /** * Gets the relevant data to build the list * * @param {Response} res * @param {ListType} listType * @return {*} {(StoredReportData[] | undefined)} */ const getDataForList = async (res, listType) => { const { requestedReports, recentlyViewedReports, subscriptions } = localsHelper.getValues(res); switch (listType) { case ListType.REQUESTED: // Only show reports in the list that have not been viewed return requestedReports.filter(report => { return report.timestamp ? !report.timestamp.lastViewed : false; }); case ListType.VIEWED: // Only show READY or EXPIRED reports return recentlyViewedReports.filter(report => { return Boolean(report.status === RequestStatus.READY || Boolean(report.executionId?.length && report.status === RequestStatus.EXPIRED)); }); case ListType.SUBSCRIPTIONS: return subscriptions; default: return undefined; } }; // ---------------------------------------------- // HEADINGS // ----------------------------------------------- /** * Builds the headings * * @param {ListType} listType * @return {*} {DprMyReportHeading[]} */ const buildHeadings = (listType) => { return ALL_HEADINGS.filter(heading => heading.showIn.includes(listType)); }; const ALL_HEADINGS = [ { key: 'title', name: 'Product', classes: 'dpr-my-reports__cell--title', showIn: [ListType.BOOKMARKS, ListType.REQUESTED, ListType.VIEWED, ListType.SUBSCRIPTIONS], }, { key: 'description', name: 'Description', classes: 'dpr-my-reports__cell--description', showIn: [ListType.BOOKMARKS, ListType.SUBSCRIPTIONS], }, { key: 'filters', name: 'Filters', classes: 'dpr-my-reports__cell--filters', showIn: [ListType.REQUESTED, ListType.VIEWED], }, { key: 'status', name: 'Status', classes: 'dpr-my-reports__cell--status', showIn: [ListType.REQUESTED, ListType.VIEWED], }, { key: 'schedule', name: 'Schedule', classes: 'dpr-my-reports__cell--status', showIn: [ListType.SUBSCRIPTIONS], }, { key: 'actions', name: 'Actions', classes: 'dpr-my-reports__cell--actions', showIn: [ListType.BOOKMARKS, ListType.REQUESTED, ListType.VIEWED, ListType.SUBSCRIPTIONS], }, ]; var utils = { initMyReports, }; export { utils as default, initMyReports, initRequested, initSubscribed, initViewed }; //# sourceMappingURL=utils.js.map