@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
JavaScript
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