@ministryofjustice/hmpps-digital-prison-reporting-frontend
Version:
The Digital Prison Reporting Frontend contains templates and code to help display data effectively in UI applications.
323 lines (320 loc) • 13.7 kB
JavaScript
import { buildPollingAction } from '../../../components/my-reports/my-reports-list-item/my-reports-list-item-actions/utils.js';
import { qsToQueryObject, queryObjectToQs, extractFiltersFromQuery, normalizeQueryStringArray } from '../../../utils/queryMappers.js';
import { joinQueryStrings } from '../../../utils/urlHelper.js';
import localsHelper from '../../../utils/localsHelper.js';
import ColumnUtils from '../../../components/_reports/report-heading/report-columns/report-columns-form/utils.js';
import { getActiveJourneyValue } from '../../../utils/sessionHelper.js';
import { getFields } from '../../../utils/definitionUtils.js';
import { LoadType } from '../../../types/UserReports.js';
import { buildQuerySummary } from '../../../components/_async/request-details/utils.js';
import { ViewedReportBuilder } from '../my-reports/recently-viewed/builder.js';
import { getMyReport } from '../my-reports/utils.js';
/**
* Apply interactive query to a REPORT
*
* @param {Request} req
* @param {Response} res
* @param {Services} services
* @param {('columns' | 'filters')} applyType
* @return {*}
*/
const applyReportInteractiveQuery = async (req, res, services, applyType, loadType) => {
const { reportId, id } = req.params;
const { token, definitionsPath } = localsHelper.getValues(res);
// Get the definition
const definition = res.locals['definition'] ??
(await services.reportingService.getDefinition(token, reportId, id, definitionsPath));
const fields = getFields(definition) || [];
return applyInteractiveQuery(req, res, applyType, loadType, fields);
};
/**
* Apply interactive query to a DASHBOARD
*
* @param {Request} req
* @param {Response} res
* @param {Services} services
* @param {('columns' | 'filters')} applyType
* @return {*}
*/
const applyDashboardInteractiveQuery = async (req, res, applyType, loadType) => {
return applyInteractiveQuery(req, res, applyType, loadType);
};
/**
* Apply the interactive query
*
* @param {Request} req
* @param {Response} res
* @param {('columns' | 'filters')} applyType
* @param {components['schemas']['FieldDefinition'][]} fields
*/
const applyInteractiveQuery = async (req, res, applyType, loadType, fields) => {
const { tableId, id, reportId } = req.params;
// Get the stored interactive query data
const sessionkey = loadType === LoadType.ASYNC ? { id, tableId, reportId } : { id, reportId };
const queryDataFromSession = getActiveJourneyValue(req, sessionkey, 'currentReportSearch');
let queryData = {};
let filtersData = {};
let columnsData = {};
if (queryDataFromSession) {
queryData = qsToQueryObject(queryDataFromSession, '');
filtersData = qsToQueryObject(queryDataFromSession, 'filters.');
columnsData = qsToQueryObject(queryDataFromSession, 'columns');
}
// Set the query values from the current query
const preventDefault = queryData?.['preventDefault'];
const pageSize = queryData?.['pageSize'];
const selectedPage = applyType === 'columns' ? queryData?.['selectedPage'] : '1';
const sortColumn = queryData?.['sortColumn'];
const sortedAsc = queryData?.['sortedAsc'];
// Create merged form data
let formData = {
...(preventDefault && { preventDefault }),
...(selectedPage && { selectedPage }),
...(pageSize && { pageSize }),
...(sortColumn && { sortColumn }),
...(sortedAsc && { sortedAsc }),
...(filtersData && filtersData),
...(columnsData && columnsData),
};
formData =
applyType === 'columns' && fields
? applyColumns(req, queryData, formData, fields)
: applyFilters(req, queryData, formData);
const filtersString = queryObjectToQs(formData);
// Redirect back to report
res.redirect(`${req.baseUrl}?${filtersString}`);
};
/**
* Apply columns
*
* @param {Request} req
* @param {(Record<string, string | string[]>)} queryData
* @param {(Record<string, string | string[]>)} formData
* @param {components['schemas']['FieldDefinition'][]} fields
* @return {*}
*/
const applyColumns = (req, queryData, formData, fields) => {
const { columns: reqColumns } = req.body;
// Normalize the req columns - should always be an array
let bodyColumns = [];
if (reqColumns) {
bodyColumns = Array.isArray(reqColumns) ? reqColumns : [reqColumns];
}
// ensure mandatory cols are present and set the updated columns
const mandatoryCols = ColumnUtils.mandatoryColumns(fields);
const columns = Array.from(new Set([...mandatoryCols, ...bodyColumns]));
// Keep the filter values the same as current
let filters = {};
if (queryData) {
filters = Object.keys(queryData)
.filter(key => key.includes('filters.'))
.reduce((acc, key) => ({ ...acc, [key]: queryData[key] }), {});
}
return { ...formData, columns, ...filters };
};
/**
* Apply filters
*
* @param {(Record<string, string | string[]>)} queryData
* @param {(Record<string, string | string[]>)} formData
* @return {*}
*/
const applyFilters = (req, queryData, formData) => {
// Ensure current columns stay the same and are normalized
const columns = normalizeQueryStringArray(queryData?.['columns']) || [];
const filters = Object.fromEntries(Object.keys(req.body)
.filter(key => key.startsWith('filters.'))
.map(key => [key, req.body[key]]));
return { ...formData, ...filters, columns };
};
/**
* Reset filters action
*
* @param {Request} req
* @param {Response} res
*/
const resetFilters = (req, res, sessionKey) => {
// Create the reset querystring
const finalQuery = resetFiltersQueryString(req, sessionKey);
// Redirect with new query string - query string will handle all rendered elements
if (finalQuery) {
res.redirect(`${req.baseUrl}?${finalQuery}`);
}
};
/**
* Creates the query string to reset the filters
*
* @param {Request} req
* @param {{ id: string; reportId: string; tableId?: string }} sessionKey
* @return {*} {string}
*/
const resetFiltersQueryString = (req, sessionKey) => {
const defaultSessionKey = { id: sessionKey.id, reportId: sessionKey.reportId };
const defaultFiltersSearch = getActiveJourneyValue(req, defaultSessionKey, 'interactiveDefaultFiltersSearch');
const savedInteractiveDefaultsSearch = getActiveJourneyValue(req, defaultSessionKey, 'savedInteractiveDefaultsSearch');
// Set the effective qs: if saved defaults, use those. Otherwise use DPD defaults
const effectiveQueryString = savedInteractiveDefaultsSearch && savedInteractiveDefaultsSearch.length
? savedInteractiveDefaultsSearch
: defaultFiltersSearch;
// Get all the current stuff
const currentColumnsSearch = getActiveJourneyValue(req, sessionKey, 'currentReportColumnsSearch');
const currentSortSearch = getActiveJourneyValue(req, sessionKey, 'currentSortSearch');
const currentPageSizeSearch = getActiveJourneyValue(req, sessionKey, 'currentPageSizeSearch');
// Create the final querystring
return joinQueryStrings(effectiveQueryString, currentColumnsSearch, currentSortSearch, currentPageSizeSearch);
};
/**
* Creates the query string to reset the columns
*
* @param {Request} req
* @param {{ id: string; reportId: string; tableId?: string }} sessionKey
* @return {*} {string}
*/
const resetColumnsQueryString = (req, sessionKey) => {
// get the default DPD filters
const defaultSessionKey = { id: sessionKey.id, reportId: sessionKey.reportId };
const defaultColumnsSearch = getActiveJourneyValue(req, defaultSessionKey, 'defaultColumnsSearch');
// Get all the current stuff
const currentReportFiltersSearch = getActiveJourneyValue(req, sessionKey, 'currentReportFiltersSearch');
const currentSortSearch = getActiveJourneyValue(req, sessionKey, 'currentSortSearch');
const currentPageSizeSearch = getActiveJourneyValue(req, sessionKey, 'currentPageSizeSearch');
// Create the final querystring
return joinQueryStrings(defaultColumnsSearch, currentReportFiltersSearch, currentSortSearch, currentPageSizeSearch);
};
/**
* Creates the default query string
*
* @param {Request} req
* @return {*} {(string | undefined)}
*/
const createDefaultQueryString = (req) => {
const { id, reportId, tableId } = req.params;
// Get the interactive report defaults
const sessionKey = { id, reportId };
// Definition defaults
const defaultFiltersSearch = getActiveJourneyValue(req, sessionKey, 'interactiveDefaultFiltersSearch');
const defaultColumnsSearch = getActiveJourneyValue(req, sessionKey, 'defaultColumnsSearch');
const defaultSort = getActiveJourneyValue(req, sessionKey, 'defaultSortQueryString');
// User defined values
const savedInteractiveDefaultsSearch = getActiveJourneyValue(req, sessionKey, 'savedInteractiveDefaultsSearch');
const requestedSort = getActiveJourneyValue(req, { id, reportId, tableId }, 'requestedSortSearch');
/**
* A report will always have default columns.
* Redirect when the request has no query params,
* applying default columns and optional filters.
*/
const hasIncomingQueryParams = Object.keys(req.query).length > 0;
if (hasIncomingQueryParams || !defaultColumnsSearch) {
return undefined;
}
// Filters: saved defaults override interactive default
const filtersToApply = savedInteractiveDefaultsSearch && savedInteractiveDefaultsSearch.length
? savedInteractiveDefaultsSearch
: defaultFiltersSearch;
// Sort precedence: requested > default > none
const sortToApply = requestedSort ?? defaultSort;
// Build the query string progressively
let query = defaultColumnsSearch;
if (filtersToApply) {
query = joinQueryStrings(query, filtersToApply);
}
if (sortToApply) {
query = joinQueryStrings(query, sortToApply);
}
return query;
};
/**
* Redirects the report to use the defaults
*
* @param {Response} res
* @param {Request} req
* @return {*}
*/
const redirectWithDefaults = (res, req) => {
const finalQuery = createDefaultQueryString(req);
if (finalQuery) {
const baseUrl = req.originalUrl.split('?')[0].replace(/\/$/, '');
res.redirect(`${baseUrl}?${finalQuery}`);
return true;
}
return false;
};
/**
* Updates the report state to expired and redirects
*
* @param {Request} req
* @param {Response} res
* @param {Services} services
*/
const updateStateToExpiredAndRedirect = async (req, res, services) => {
const { tableId } = req.params;
const { dprUser } = localsHelper.getValues(res);
// set the report to expired
await services.recentlyViewedService.setToExpiredByTableId(tableId, dprUser.id);
await services.requestedReportService.setToExpiredByTableId(tableId, dprUser.id);
// get the updated report state
const updatedReportState = await getMyReport({ tableId }, 'recentlyViewedReports', services, dprUser.id);
const pollingUrl = updatedReportState ? buildPollingAction(res, req, updatedReportState).href : '/';
// redirect to polling page, or safe fallback
res.redirect(pollingUrl);
};
/**
* Updates the state for a viewed ASYNC report
*
* @param {Request} req
* @param {Response} res
* @param {Services} services
* @param {RequestedReport} reportStateData
* @param {string} userId
* @param {components['schemas']['FieldDefinition'][]} fields
*/
const updateLastViewedAsync = async (req, res, services, reportStateData, userId, fields) => {
const { type, reportId, reportName, description, id, name, executionId, tableId, query, url } = reportStateData;
const reportData = { type, reportId, reportName, description, id, name };
const executionData = { executionId, tableId };
const filtersQuery = extractFiltersFromQuery(req.query);
const interactiveQueryData = {
query: req.query,
querySummary: buildQuerySummary(filtersQuery, fields),
};
const recentlyViewedReportData = new ViewedReportBuilder(req, res)
.withReportData(reportData)
.withExecutionData(executionData)
.withInteractiveQuery(interactiveQueryData)
.withRequestData(query, url)
.build();
if (executionId)
await services.requestedReportService.updateLastViewed(executionId, userId);
await services.recentlyViewedService.setRecentlyViewed(recentlyViewedReportData, userId);
};
/**
* Updates the last viewed state for a sync report
*
* @param {Request} req
* @param {Services} services
* @param {*} stateData
* @param {string} userId
* @param {components['schemas']['FieldDefinition'][]} fields
*/
const updateLastViewedSync = async (req, res, services, reportData, userId, fields) => {
const filtersQuery = extractFiltersFromQuery(req.query);
const interactiveQueryData = {
query: req.query,
querySummary: buildQuerySummary(filtersQuery, fields),
};
const recentlyViewedReportData = new ViewedReportBuilder(req, res)
.withReportData(reportData)
.withInteractiveQuery(interactiveQueryData)
.build();
await services.recentlyViewedService?.setRecentlyViewed(recentlyViewedReportData, userId);
};
var ViewReportUtils = {
applyDashboardInteractiveQuery,
applyReportInteractiveQuery,
resetFilters,
resetFiltersQueryString,
resetColumnsQueryString,
redirectWithDefaults,
};
export { applyDashboardInteractiveQuery, applyReportInteractiveQuery, ViewReportUtils as default, redirectWithDefaults, updateLastViewedAsync, updateLastViewedSync, updateStateToExpiredAndRedirect };
//# sourceMappingURL=utils.js.map