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.

323 lines (320 loc) 13.7 kB
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