@ministryofjustice/hmpps-digital-prison-reporting-frontend 3.12.2 → 3.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dpr/assets/js/all.mjs +6 -1
- package/dpr/components/async-filters/utils.js +7 -3
- package/dpr/components/async-filters/utils.ts +8 -3
- package/dpr/components/async-filters/view.njk +1 -0
- package/dpr/components/async-report-list/utils.js +18 -60
- package/dpr/components/async-report-list/utils.ts +27 -79
- package/dpr/components/async-report-list/view.njk +11 -27
- package/dpr/components/async-reports-list/utils/recentlyViewedUtils.js +11 -5
- package/dpr/components/async-reports-list/utils/recentlyViewedUtils.ts +10 -5
- package/dpr/components/icon-button-list/utils.js +2 -2
- package/dpr/components/icon-button-list/utils.ts +2 -2
- package/dpr/routes/asyncReports.js +4 -4
- package/dpr/routes/asyncReports.ts +4 -4
- package/dpr/services/recentlyViewedService.js +7 -6
- package/dpr/services/recentlyViewedService.ts +7 -6
- package/dpr/services/requestedReportsService.js +7 -6
- package/dpr/services/requestedReportsService.ts +7 -6
- package/dpr/types/AsyncReport.ts +1 -0
- package/dpr/types/RecentlyViewed.ts +1 -0
- package/dpr/utils/renderAsyncReport.js +74 -0
- package/dpr/utils/renderAsyncReport.ts +90 -0
- package/dpr/views/async-report.njk +37 -1
- package/package.json +1 -1
- package/package.zip +0 -0
package/dpr/assets/js/all.mjs
CHANGED
|
@@ -792,6 +792,7 @@ class AsyncFilters extends DprQueryParamClass {
|
|
|
792
792
|
|
|
793
793
|
const params = new URLSearchParams(search);
|
|
794
794
|
params.delete('retryId');
|
|
795
|
+
params.delete('refreshId');
|
|
795
796
|
const href = `${origin}${pathname}?${params.toString()}`;
|
|
796
797
|
|
|
797
798
|
document.getElementById('async-filters-form-href').value = href;
|
|
@@ -816,9 +817,13 @@ class AsyncFilters extends DprQueryParamClass {
|
|
|
816
817
|
initRetryInputFromQueryParams () {
|
|
817
818
|
this.queryParams = new URLSearchParams(window.location.search);
|
|
818
819
|
const retryId = this.queryParams.get('retryId');
|
|
820
|
+
const refreshId = this.queryParams.get('refreshId');
|
|
819
821
|
if (retryId) {
|
|
820
822
|
document.getElementById('async-filters-retry-id').value = retryId;
|
|
821
823
|
}
|
|
824
|
+
if (refreshId) {
|
|
825
|
+
document.getElementById('async-filters-refresh-id').value = refreshId;
|
|
826
|
+
}
|
|
822
827
|
}
|
|
823
828
|
}
|
|
824
829
|
|
|
@@ -992,7 +997,7 @@ class dprAsyncPolling extends DprClientClass {
|
|
|
992
997
|
|
|
993
998
|
async initialise () {
|
|
994
999
|
this.POLLING_STATUSES = ['SUBMITTED', 'STARTED', 'PICKED'];
|
|
995
|
-
this.POLLING_FREQUENCY = '
|
|
1000
|
+
this.POLLING_FREQUENCY = '3000'; // 3 seconds
|
|
996
1001
|
|
|
997
1002
|
this.statusSection = document.getElementById('async-request-polling-status');
|
|
998
1003
|
this.retryRequestButton = document.getElementById('retry-async-request');
|
|
@@ -122,12 +122,16 @@ exports.default = {
|
|
|
122
122
|
}
|
|
123
123
|
});
|
|
124
124
|
const token = ((_a = res.locals.user) === null || _a === void 0 ? void 0 : _a.token) ? res.locals.user.token : 'token';
|
|
125
|
-
const { reportId, variantId, retryId } = req.body;
|
|
125
|
+
const { reportId, variantId, retryId, refreshId } = req.body;
|
|
126
126
|
const response = await dataSources.requestAsyncReport(token, reportId, variantId, query);
|
|
127
127
|
const { executionId, tableId } = response;
|
|
128
128
|
if (retryId) {
|
|
129
|
-
await asyncReportsStore.
|
|
130
|
-
await recentlyViewedStoreService.
|
|
129
|
+
await asyncReportsStore.setReportTimestamp(retryId, 'retry');
|
|
130
|
+
await recentlyViewedStoreService.setReportTimestamp(retryId, 'retry');
|
|
131
|
+
}
|
|
132
|
+
if (refreshId) {
|
|
133
|
+
await asyncReportsStore.setReportTimestamp(refreshId, 'refresh');
|
|
134
|
+
await recentlyViewedStoreService.setReportTimestamp(refreshId, 'refresh');
|
|
131
135
|
}
|
|
132
136
|
if (executionId && tableId) {
|
|
133
137
|
const reportData = await asyncReportsStore.addReport({
|
|
@@ -136,13 +136,18 @@ export default {
|
|
|
136
136
|
})
|
|
137
137
|
|
|
138
138
|
const token = res.locals.user?.token ? res.locals.user.token : 'token'
|
|
139
|
-
const { reportId, variantId, retryId } = req.body
|
|
139
|
+
const { reportId, variantId, retryId, refreshId } = req.body
|
|
140
140
|
const response = await dataSources.requestAsyncReport(token, reportId, variantId, query)
|
|
141
141
|
const { executionId, tableId } = response
|
|
142
142
|
|
|
143
143
|
if (retryId) {
|
|
144
|
-
await asyncReportsStore.
|
|
145
|
-
await recentlyViewedStoreService.
|
|
144
|
+
await asyncReportsStore.setReportTimestamp(retryId, 'retry')
|
|
145
|
+
await recentlyViewedStoreService.setReportTimestamp(retryId, 'retry')
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (refreshId) {
|
|
149
|
+
await asyncReportsStore.setReportTimestamp(refreshId, 'refresh')
|
|
150
|
+
await recentlyViewedStoreService.setReportTimestamp(refreshId, 'refresh')
|
|
146
151
|
}
|
|
147
152
|
|
|
148
153
|
if (executionId && tableId) {
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
<input type="hidden" name="href" id="async-filters-form-href" value="">
|
|
32
32
|
<input type="hidden" name="search" id="async-filters-form-search" value="">
|
|
33
33
|
<input type="hidden" name="retryId" id="async-filters-retry-id" value="">
|
|
34
|
+
<input type="hidden" name="refreshId" id="async-filters-refesh-id" value="">
|
|
34
35
|
|
|
35
36
|
<div class="dpr-async-controls__section">
|
|
36
37
|
<h3 class="govuk-heading-s">Filters</h3>
|
|
@@ -3,68 +3,26 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.renderReport = exports.initDataSources = void 0;
|
|
7
|
-
/* eslint-disable no-underscore-dangle */
|
|
8
6
|
const parseurl_1 = __importDefault(require("parseurl"));
|
|
9
7
|
const utils_1 = __importDefault(require("../data-table/utils"));
|
|
10
8
|
const utils_2 = __importDefault(require("../async-columns/utils"));
|
|
11
9
|
const utils_3 = __importDefault(require("../pagination/utils"));
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
10
|
+
exports.default = {
|
|
11
|
+
getRenderData: (req, definition, reportData, count, reportStateData) => {
|
|
12
|
+
const { columns: reqColumns } = req.query;
|
|
13
|
+
const { fields } = definition.variant.specification;
|
|
14
|
+
const columns = utils_2.default.getColumns(fields, reqColumns);
|
|
15
|
+
const url = (0, parseurl_1.default)(req);
|
|
16
|
+
const pagination = utils_3.default.getPaginationData(url, count);
|
|
17
|
+
const rows = utils_1.default.mapData(reportData, fields, columns.value);
|
|
18
|
+
const head = utils_1.default.mapAsyncHeader(fields, columns.value);
|
|
19
|
+
const { query } = reportStateData;
|
|
20
|
+
return {
|
|
21
|
+
rows,
|
|
22
|
+
head,
|
|
23
|
+
columns,
|
|
24
|
+
pagination,
|
|
25
|
+
appliedFilters: query.summary,
|
|
26
|
+
};
|
|
27
|
+
},
|
|
28
28
|
};
|
|
29
|
-
exports.initDataSources = initDataSources;
|
|
30
|
-
const renderReport = async ({ req, res, next, asyncReportsStore, recentlyViewedStoreService, dataSources, }) => {
|
|
31
|
-
const { columns: reqColumns } = req.query;
|
|
32
|
-
const dataPromises = (0, exports.initDataSources)({ req, res, next, dataSources, asyncReportsStore });
|
|
33
|
-
let renderData = {};
|
|
34
|
-
let reportStateData;
|
|
35
|
-
if (dataPromises) {
|
|
36
|
-
await Promise.all(dataPromises).then((resolvedData) => {
|
|
37
|
-
const definition = resolvedData[0];
|
|
38
|
-
const reportData = resolvedData[1];
|
|
39
|
-
const count = resolvedData[2];
|
|
40
|
-
reportStateData = resolvedData[3];
|
|
41
|
-
const fieldDefinition = definition.variant.specification.fields;
|
|
42
|
-
const { classification } = definition.variant;
|
|
43
|
-
const columns = utils_2.default.getColumns(fieldDefinition, reqColumns);
|
|
44
|
-
const url = (0, parseurl_1.default)(req);
|
|
45
|
-
const pagination = utils_3.default.getPaginationData(url, count);
|
|
46
|
-
const actions = utils_4.default.initReportActions(definition.variant, reportStateData);
|
|
47
|
-
const rows = utils_1.default.mapData(reportData, fieldDefinition, columns.value);
|
|
48
|
-
const head = utils_1.default.mapAsyncHeader(fieldDefinition, columns.value);
|
|
49
|
-
const { reportName, name: variantName, query, description } = reportStateData;
|
|
50
|
-
renderData = {
|
|
51
|
-
variantName,
|
|
52
|
-
reportName,
|
|
53
|
-
description,
|
|
54
|
-
rows,
|
|
55
|
-
head,
|
|
56
|
-
columns,
|
|
57
|
-
pagination,
|
|
58
|
-
actions,
|
|
59
|
-
appliedFilters: query.summary,
|
|
60
|
-
classification,
|
|
61
|
-
};
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
if (Object.keys(renderData).length && Object.keys(reportStateData).length) {
|
|
65
|
-
await asyncReportsStore.updateLastViewed(reportStateData.executionId);
|
|
66
|
-
await recentlyViewedStoreService.setRecentlyViewed(reportStateData);
|
|
67
|
-
}
|
|
68
|
-
return { renderData };
|
|
69
|
-
};
|
|
70
|
-
exports.renderReport = renderReport;
|
|
@@ -1,88 +1,36 @@
|
|
|
1
|
-
/* eslint-disable no-underscore-dangle */
|
|
2
1
|
import parseUrl from 'parseurl'
|
|
2
|
+
import { Request } from 'express'
|
|
3
3
|
import DataTableUtils from '../data-table/utils'
|
|
4
4
|
import ColumnUtils from '../async-columns/utils'
|
|
5
5
|
import PaginationUtils from '../pagination/utils'
|
|
6
|
-
import ReportActionsUtils from '../icon-button-list/utils'
|
|
7
6
|
import { components } from '../../types/api'
|
|
8
7
|
import Dict = NodeJS.Dict
|
|
9
|
-
|
|
10
|
-
import { AsyncReportUtilsParams } from '../../types/AsyncReportUtils'
|
|
11
8
|
import { AsyncReportData } from '../../types/AsyncReport'
|
|
12
9
|
|
|
13
|
-
export
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
asyncReportsStore,
|
|
40
|
-
recentlyViewedStoreService,
|
|
41
|
-
dataSources,
|
|
42
|
-
}: AsyncReportUtilsParams) => {
|
|
43
|
-
const { columns: reqColumns } = req.query
|
|
44
|
-
|
|
45
|
-
const dataPromises = initDataSources({ req, res, next, dataSources, asyncReportsStore })
|
|
46
|
-
|
|
47
|
-
let renderData = {}
|
|
48
|
-
let reportStateData: AsyncReportData
|
|
49
|
-
if (dataPromises) {
|
|
50
|
-
await Promise.all(dataPromises).then((resolvedData) => {
|
|
51
|
-
const definition = resolvedData[0] as unknown as components['schemas']['SingleVariantReportDefinition']
|
|
52
|
-
const reportData = <Array<Dict<string>>>resolvedData[1]
|
|
53
|
-
const count = <number>resolvedData[2]
|
|
54
|
-
reportStateData = <AsyncReportData>resolvedData[3]
|
|
55
|
-
|
|
56
|
-
const fieldDefinition = definition.variant.specification.fields
|
|
57
|
-
const { classification } = definition.variant
|
|
58
|
-
|
|
59
|
-
const columns = ColumnUtils.getColumns(fieldDefinition, <string[]>reqColumns)
|
|
60
|
-
const url = parseUrl(req)
|
|
61
|
-
const pagination = PaginationUtils.getPaginationData(url, count)
|
|
62
|
-
const actions = ReportActionsUtils.initReportActions(definition.variant, reportStateData)
|
|
63
|
-
const rows = DataTableUtils.mapData(reportData, fieldDefinition, columns.value)
|
|
64
|
-
const head = DataTableUtils.mapAsyncHeader(fieldDefinition, columns.value)
|
|
65
|
-
const { reportName, name: variantName, query, description } = reportStateData
|
|
66
|
-
|
|
67
|
-
renderData = {
|
|
68
|
-
variantName,
|
|
69
|
-
reportName,
|
|
70
|
-
description,
|
|
71
|
-
rows,
|
|
72
|
-
head,
|
|
73
|
-
columns,
|
|
74
|
-
pagination,
|
|
75
|
-
actions,
|
|
76
|
-
appliedFilters: query.summary,
|
|
77
|
-
classification,
|
|
78
|
-
}
|
|
79
|
-
})
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
if (Object.keys(renderData).length && Object.keys(reportStateData).length) {
|
|
83
|
-
await asyncReportsStore.updateLastViewed(reportStateData.executionId)
|
|
84
|
-
await recentlyViewedStoreService.setRecentlyViewed(reportStateData)
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
return { renderData }
|
|
10
|
+
export default {
|
|
11
|
+
getRenderData: (
|
|
12
|
+
req: Request,
|
|
13
|
+
definition: components['schemas']['SingleVariantReportDefinition'],
|
|
14
|
+
reportData: Array<Dict<string>>,
|
|
15
|
+
count: number,
|
|
16
|
+
reportStateData: AsyncReportData,
|
|
17
|
+
) => {
|
|
18
|
+
const { columns: reqColumns } = req.query
|
|
19
|
+
const { fields } = definition.variant.specification
|
|
20
|
+
|
|
21
|
+
const columns = ColumnUtils.getColumns(fields, <string[]>reqColumns)
|
|
22
|
+
const url = parseUrl(req)
|
|
23
|
+
const pagination = PaginationUtils.getPaginationData(url, count)
|
|
24
|
+
const rows = DataTableUtils.mapData(reportData, fields, columns.value)
|
|
25
|
+
const head = DataTableUtils.mapAsyncHeader(fields, columns.value)
|
|
26
|
+
const { query } = reportStateData
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
rows,
|
|
30
|
+
head,
|
|
31
|
+
columns,
|
|
32
|
+
pagination,
|
|
33
|
+
appliedFilters: query.summary,
|
|
34
|
+
}
|
|
35
|
+
},
|
|
88
36
|
}
|
|
@@ -1,46 +1,30 @@
|
|
|
1
|
-
{% from "../data-table/view.njk" import dprDataTable %}
|
|
2
1
|
{% from "../async-columns/view.njk" import dprAsyncColumns %}
|
|
3
2
|
{% from "../pagination/view.njk" import dprPagination %}
|
|
4
3
|
{% from "../async-data-table/view.njk" import dprAsyncDataTable %}
|
|
5
4
|
{% from "../async-applied-filters/view.njk" import dprAsyncAppliedFiltersList %}
|
|
6
|
-
{% from "../icon-button-list/view.njk" import dprIconButtonList %}
|
|
7
5
|
{% from "govuk/components/details/macro.njk" import govukDetails %}
|
|
8
6
|
|
|
9
7
|
{% macro dprAsyncReportList(options) %}
|
|
10
8
|
|
|
11
9
|
{% set columns = options.columns %}
|
|
12
|
-
{% set appliedFilters = options.appliedFilters %}
|
|
13
10
|
{% set pagination = options.pagination %}
|
|
14
|
-
{% set
|
|
15
|
-
{% set variantName = options.variantName %}
|
|
16
|
-
{% set description = options.description %}
|
|
17
|
-
{% set actions = options.actions %}
|
|
18
|
-
|
|
19
|
-
<div class="govuk-width-container report-list-container">
|
|
20
|
-
<div class="drp-async-reports-heading-container">
|
|
21
|
-
<h1 class="govuk-heading-l govuk-!-margin-bottom-2">{{ reportName }} - {{ variantName }}</h1>
|
|
22
|
-
<p class="govuk-body-s govuk-!-margin-bottom-0">
|
|
23
|
-
<i>{{ description }}</i>
|
|
24
|
-
</p>
|
|
25
|
-
{{ dprIconButtonList(actions) }}
|
|
26
|
-
</div>
|
|
11
|
+
{% set appliedFilters = options.appliedFilters %}
|
|
27
12
|
|
|
28
|
-
|
|
13
|
+
{{ dprAsyncAppliedFiltersList(appliedFilters) }}
|
|
29
14
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
15
|
+
<div class="columns-select-wrapper">
|
|
16
|
+
{% set columnsHtml %}
|
|
17
|
+
{{ dprAsyncColumns(columns) }}
|
|
18
|
+
{% endset %}
|
|
34
19
|
|
|
35
|
-
|
|
20
|
+
{{ govukDetails({
|
|
36
21
|
summaryText: "Columns",
|
|
37
22
|
html: columnsHtml
|
|
38
23
|
}) }}
|
|
39
|
-
|
|
24
|
+
</div>
|
|
40
25
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
</div>
|
|
26
|
+
<div class='dpr-table-container'>
|
|
27
|
+
{{ dprAsyncDataTable(options) }}
|
|
28
|
+
{{ dprPagination(pagination) }}
|
|
45
29
|
</div>
|
|
46
30
|
{% endmacro %}
|
|
@@ -9,7 +9,7 @@ const formatCards = async (recentlyViewedStoreService, asyncReportsStore, dataSo
|
|
|
9
9
|
const requestedReportsData = await recentlyViewedStoreService.getAllReports();
|
|
10
10
|
return Promise.all(requestedReportsData
|
|
11
11
|
.filter((report) => {
|
|
12
|
-
return !report.timestamp.retried;
|
|
12
|
+
return !report.timestamp.retried && !report.timestamp.refresh;
|
|
13
13
|
})
|
|
14
14
|
.map((report) => {
|
|
15
15
|
return formatCardData(report, dataSources, token, recentlyViewedStoreService, asyncReportsStore);
|
|
@@ -19,15 +19,21 @@ const formatCardData = async (reportData, dataSources, token, recentlyViewedStor
|
|
|
19
19
|
const { executionId: id, variantName: text, description, query, url, timestamp, executionId, reportId, variantId, dataProductDefinitionsPath, } = reportData;
|
|
20
20
|
let { status } = reportData;
|
|
21
21
|
const statusResponse = await utils_1.default.getStatus(token, reportId, variantId, executionId, status, dataSources, asyncReportsStore, dataProductDefinitionsPath);
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
let href;
|
|
23
|
+
if (statusResponse.status === AsyncReport_1.RequestStatus.EXPIRED) {
|
|
24
|
+
status = statusResponse.status;
|
|
25
|
+
href = `${url.request.fullUrl}&retryId=${executionId}`;
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
status = AsyncReport_1.RequestStatus.READY;
|
|
29
|
+
href = url.report.fullUrl;
|
|
30
|
+
}
|
|
25
31
|
return {
|
|
26
32
|
id,
|
|
27
33
|
text,
|
|
28
34
|
description,
|
|
29
35
|
tag: 'MIS',
|
|
30
|
-
summary,
|
|
36
|
+
summary: query.summary,
|
|
31
37
|
href,
|
|
32
38
|
timestamp: `Last viewed: ${new Date(timestamp.lastViewed).toLocaleString()}`,
|
|
33
39
|
status,
|
|
@@ -17,7 +17,7 @@ const formatCards = async (
|
|
|
17
17
|
return Promise.all(
|
|
18
18
|
requestedReportsData
|
|
19
19
|
.filter((report: RecentlyViewedReportData) => {
|
|
20
|
-
return !report.timestamp.retried
|
|
20
|
+
return !report.timestamp.retried && !report.timestamp.refresh
|
|
21
21
|
})
|
|
22
22
|
.map((report: RecentlyViewedReportData) => {
|
|
23
23
|
return formatCardData(report, dataSources, token, recentlyViewedStoreService, asyncReportsStore)
|
|
@@ -57,16 +57,21 @@ const formatCardData = async (
|
|
|
57
57
|
dataProductDefinitionsPath,
|
|
58
58
|
)
|
|
59
59
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
60
|
+
let href
|
|
61
|
+
if (statusResponse.status === RequestStatus.EXPIRED) {
|
|
62
|
+
status = statusResponse.status
|
|
63
|
+
href = `${url.request.fullUrl}&retryId=${executionId}`
|
|
64
|
+
} else {
|
|
65
|
+
status = RequestStatus.READY
|
|
66
|
+
href = url.report.fullUrl
|
|
67
|
+
}
|
|
63
68
|
|
|
64
69
|
return {
|
|
65
70
|
id,
|
|
66
71
|
text,
|
|
67
72
|
description,
|
|
68
73
|
tag: 'MIS',
|
|
69
|
-
summary,
|
|
74
|
+
summary: query.summary as { name: string; value: string }[],
|
|
70
75
|
href,
|
|
71
76
|
timestamp: `Last viewed: ${new Date(timestamp.lastViewed).toLocaleString()}`,
|
|
72
77
|
status,
|
|
@@ -6,7 +6,7 @@ const FULL_BUTTON_LIST = [
|
|
|
6
6
|
icon: 'refresh',
|
|
7
7
|
disabled: false,
|
|
8
8
|
tooltipText: 'Refresh report',
|
|
9
|
-
ariaLabelText: '
|
|
9
|
+
ariaLabelText: 'Refresh report',
|
|
10
10
|
},
|
|
11
11
|
{
|
|
12
12
|
id: 'printable',
|
|
@@ -59,7 +59,7 @@ exports.default = {
|
|
|
59
59
|
actions.push({
|
|
60
60
|
type: 'refresh',
|
|
61
61
|
data: {
|
|
62
|
-
href: reportData.url.request.fullUrl
|
|
62
|
+
href: `${reportData.url.request.fullUrl}&refreshId=${reportData.executionId}`,
|
|
63
63
|
},
|
|
64
64
|
});
|
|
65
65
|
// PRINT
|
|
@@ -8,7 +8,7 @@ const FULL_BUTTON_LIST = [
|
|
|
8
8
|
icon: 'refresh',
|
|
9
9
|
disabled: false,
|
|
10
10
|
tooltipText: 'Refresh report',
|
|
11
|
-
ariaLabelText: '
|
|
11
|
+
ariaLabelText: 'Refresh report',
|
|
12
12
|
},
|
|
13
13
|
{
|
|
14
14
|
id: 'printable',
|
|
@@ -64,7 +64,7 @@ export default {
|
|
|
64
64
|
actions.push({
|
|
65
65
|
type: 'refresh',
|
|
66
66
|
data: {
|
|
67
|
-
href: reportData.url.request.fullUrl
|
|
67
|
+
href: `${reportData.url.request.fullUrl}&refreshId=${reportData.executionId}`,
|
|
68
68
|
},
|
|
69
69
|
})
|
|
70
70
|
|
|
@@ -27,8 +27,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
27
27
|
};
|
|
28
28
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
29
|
const utils_1 = __importDefault(require("../components/async-filters/utils"));
|
|
30
|
-
const AsyncReportListUtils = __importStar(require("../components/async-report-list/utils"));
|
|
31
30
|
const utils_2 = __importDefault(require("../components/async-polling/utils"));
|
|
31
|
+
const AsyncReportUtils = __importStar(require("../utils/renderAsyncReport"));
|
|
32
32
|
const asyncReportsUtils_1 = __importDefault(require("../components/async-reports-list/utils/asyncReportsUtils"));
|
|
33
33
|
const recentlyViewedUtils_1 = __importDefault(require("../components/async-reports-list/utils/recentlyViewedUtils"));
|
|
34
34
|
function routes({ router, asyncReportsStore, recentlyViewedStoreService, dataSources, layoutPath, templatePath = 'dpr/views/', }) {
|
|
@@ -120,9 +120,9 @@ function routes({ router, asyncReportsStore, recentlyViewedStoreService, dataSou
|
|
|
120
120
|
next();
|
|
121
121
|
}
|
|
122
122
|
};
|
|
123
|
-
const
|
|
123
|
+
const getReportHandler = async (req, res, next) => {
|
|
124
124
|
try {
|
|
125
|
-
const reportRenderData = await
|
|
125
|
+
const reportRenderData = await AsyncReportUtils.getReport({
|
|
126
126
|
req,
|
|
127
127
|
res,
|
|
128
128
|
dataSources,
|
|
@@ -145,7 +145,7 @@ function routes({ router, asyncReportsStore, recentlyViewedStoreService, dataSou
|
|
|
145
145
|
router.post('/requestReport/', asyncRequestHandler, asyncErrorHandler);
|
|
146
146
|
router.post('/cancelRequest/', cancelRequestHandler, asyncErrorHandler);
|
|
147
147
|
router.get('/async-reports/:reportId/:variantId/request/:executionId', pollingHandler, asyncErrorHandler);
|
|
148
|
-
router.get('/async-reports/:reportId/:variantId/request/:tableId/report',
|
|
148
|
+
router.get('/async-reports/:reportId/:variantId/request/:tableId/report', getReportHandler, asyncErrorHandler);
|
|
149
149
|
router.get('/async-reports/requested', async (req, res) => {
|
|
150
150
|
res.render(`${templatePath}/async-reports`, {
|
|
151
151
|
title: 'Requested Reports',
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import type { RequestHandler, Router } from 'express'
|
|
2
2
|
import AsyncFiltersUtils from '../components/async-filters/utils'
|
|
3
|
-
import * as AsyncReportListUtils from '../components/async-report-list/utils'
|
|
4
3
|
import AsyncPollingUtils from '../components/async-polling/utils'
|
|
5
4
|
import AsyncReportStoreService from '../services/requestedReportsService'
|
|
6
5
|
import ReportingService from '../services/reportingService'
|
|
7
6
|
import RecentlyViewedStoreService from '../services/recentlyViewedService'
|
|
7
|
+
import * as AsyncReportUtils from '../utils/renderAsyncReport'
|
|
8
8
|
|
|
9
9
|
import AsyncReportslistUtils from '../components/async-reports-list/utils/asyncReportsUtils'
|
|
10
10
|
import RecentReportslistUtils from '../components/async-reports-list/utils/recentlyViewedUtils'
|
|
@@ -112,9 +112,9 @@ export default function routes({
|
|
|
112
112
|
}
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
-
const
|
|
115
|
+
const getReportHandler: RequestHandler = async (req, res, next) => {
|
|
116
116
|
try {
|
|
117
|
-
const reportRenderData = await
|
|
117
|
+
const reportRenderData = await AsyncReportUtils.getReport({
|
|
118
118
|
req,
|
|
119
119
|
res,
|
|
120
120
|
dataSources,
|
|
@@ -137,7 +137,7 @@ export default function routes({
|
|
|
137
137
|
router.post('/requestReport/', asyncRequestHandler, asyncErrorHandler)
|
|
138
138
|
router.post('/cancelRequest/', cancelRequestHandler, asyncErrorHandler)
|
|
139
139
|
router.get('/async-reports/:reportId/:variantId/request/:executionId', pollingHandler, asyncErrorHandler)
|
|
140
|
-
router.get('/async-reports/:reportId/:variantId/request/:tableId/report',
|
|
140
|
+
router.get('/async-reports/:reportId/:variantId/request/:tableId/report', getReportHandler, asyncErrorHandler)
|
|
141
141
|
|
|
142
142
|
router.get('/async-reports/requested', async (req, res) => {
|
|
143
143
|
res.render(`${templatePath}/async-reports`, {
|
|
@@ -82,14 +82,15 @@ class RecentlyViewedStoreService extends userStoreService_1.default {
|
|
|
82
82
|
this.recentlyViewedReports[index] = report;
|
|
83
83
|
await this.saveRecentlyViewedState();
|
|
84
84
|
}
|
|
85
|
-
async
|
|
86
|
-
const
|
|
87
|
-
if (
|
|
85
|
+
async setReportTimestamp(executionId, type) {
|
|
86
|
+
const report = await this.getReportByExecutionId(executionId);
|
|
87
|
+
if (report) {
|
|
88
88
|
const timestamp = {
|
|
89
|
-
...
|
|
90
|
-
retried: new Date(),
|
|
89
|
+
...report.timestamp,
|
|
90
|
+
...(type === 'retry' && { retried: new Date() }),
|
|
91
|
+
...(type === 'refresh' && { refresh: new Date() }),
|
|
91
92
|
};
|
|
92
|
-
await this.updateReport(
|
|
93
|
+
await this.updateReport(executionId, { timestamp });
|
|
93
94
|
}
|
|
94
95
|
}
|
|
95
96
|
async setToExpired(id) {
|
|
@@ -106,14 +106,15 @@ export default class RecentlyViewedStoreService extends UserStoreService {
|
|
|
106
106
|
await this.saveRecentlyViewedState()
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
-
async
|
|
110
|
-
const
|
|
111
|
-
if (
|
|
109
|
+
async setReportTimestamp(executionId: string, type: string) {
|
|
110
|
+
const report = await this.getReportByExecutionId(executionId)
|
|
111
|
+
if (report) {
|
|
112
112
|
const timestamp: AsyncReportsTimestamp = {
|
|
113
|
-
...
|
|
114
|
-
retried: new Date(),
|
|
113
|
+
...report.timestamp,
|
|
114
|
+
...(type === 'retry' && { retried: new Date() }),
|
|
115
|
+
...(type === 'refresh' && { refresh: new Date() }),
|
|
115
116
|
}
|
|
116
|
-
await this.updateReport(
|
|
117
|
+
await this.updateReport(executionId, { timestamp })
|
|
117
118
|
}
|
|
118
119
|
}
|
|
119
120
|
|
|
@@ -97,14 +97,15 @@ class AsyncReportStoreService extends userStoreService_1.default {
|
|
|
97
97
|
this.requestedReports[index] = report;
|
|
98
98
|
await this.saveRequestedReportState();
|
|
99
99
|
}
|
|
100
|
-
async
|
|
101
|
-
const
|
|
102
|
-
if (
|
|
100
|
+
async setReportTimestamp(executionId, type) {
|
|
101
|
+
const report = await this.getReportByExecutionId(executionId);
|
|
102
|
+
if (report) {
|
|
103
103
|
const timestamp = {
|
|
104
|
-
...
|
|
105
|
-
retried: new Date(),
|
|
104
|
+
...report.timestamp,
|
|
105
|
+
...(type === 'retry' && { retried: new Date() }),
|
|
106
|
+
...(type === 'refresh' && { refresh: new Date() }),
|
|
106
107
|
};
|
|
107
|
-
await this.updateReport(
|
|
108
|
+
await this.updateReport(executionId, { timestamp });
|
|
108
109
|
}
|
|
109
110
|
}
|
|
110
111
|
async updateLastViewed(id) {
|
|
@@ -129,14 +129,15 @@ export default class AsyncReportStoreService extends UserStoreService {
|
|
|
129
129
|
await this.saveRequestedReportState()
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
-
async
|
|
133
|
-
const
|
|
134
|
-
if (
|
|
132
|
+
async setReportTimestamp(executionId: string, type: string) {
|
|
133
|
+
const report = await this.getReportByExecutionId(executionId)
|
|
134
|
+
if (report) {
|
|
135
135
|
const timestamp: AsyncReportsTimestamp = {
|
|
136
|
-
...
|
|
137
|
-
retried: new Date(),
|
|
136
|
+
...report.timestamp,
|
|
137
|
+
...(type === 'retry' && { retried: new Date() }),
|
|
138
|
+
...(type === 'refresh' && { refresh: new Date() }),
|
|
138
139
|
}
|
|
139
|
-
await this.updateReport(
|
|
140
|
+
await this.updateReport(executionId, { timestamp })
|
|
140
141
|
}
|
|
141
142
|
}
|
|
142
143
|
|
package/dpr/types/AsyncReport.ts
CHANGED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.getReport = exports.initDataSources = void 0;
|
|
7
|
+
const utils_1 = __importDefault(require("../components/async-report-list/utils"));
|
|
8
|
+
const utils_2 = __importDefault(require("../components/icon-button-list/utils"));
|
|
9
|
+
const initDataSources = ({ req, res, next, asyncReportsStore, dataSources }) => {
|
|
10
|
+
var _a;
|
|
11
|
+
const token = ((_a = res.locals.user) === null || _a === void 0 ? void 0 : _a.token) ? res.locals.user.token : 'token';
|
|
12
|
+
const { reportId, variantId: reportVariantId, tableId } = req.params;
|
|
13
|
+
const { selectedPage = 1, pageSize = 10 } = req.query;
|
|
14
|
+
const dataProductDefinitionsPath = req.query.dataProductDefinitionsPath;
|
|
15
|
+
const reportDefinitionPromise = dataSources.getDefinition(token, reportId, reportVariantId, dataProductDefinitionsPath);
|
|
16
|
+
const reportDataPromise = dataSources.getAsyncReport(token, reportId, reportVariantId, tableId, {
|
|
17
|
+
selectedPage: +selectedPage,
|
|
18
|
+
pageSize: +pageSize,
|
|
19
|
+
dataProductDefinitionsPath,
|
|
20
|
+
});
|
|
21
|
+
const reportDataCountPromise = dataSources.getAsyncCount(token, tableId);
|
|
22
|
+
const stateDataPromise = asyncReportsStore.getReportByTableId(tableId);
|
|
23
|
+
return [reportDefinitionPromise, reportDataPromise, reportDataCountPromise, stateDataPromise];
|
|
24
|
+
};
|
|
25
|
+
exports.initDataSources = initDataSources;
|
|
26
|
+
const getReport = async ({ req, res, next, asyncReportsStore, recentlyViewedStoreService, dataSources, }) => {
|
|
27
|
+
const dataPromises = (0, exports.initDataSources)({ req, res, next, dataSources, asyncReportsStore });
|
|
28
|
+
let renderData = {};
|
|
29
|
+
let reportStateData;
|
|
30
|
+
if (dataPromises) {
|
|
31
|
+
await Promise.all(dataPromises).then((resolvedData) => {
|
|
32
|
+
const definition = resolvedData[0];
|
|
33
|
+
const reportData = resolvedData[1];
|
|
34
|
+
const count = resolvedData[2];
|
|
35
|
+
reportStateData = resolvedData[3];
|
|
36
|
+
const { classification } = definition.variant;
|
|
37
|
+
const { template } = definition.variant.specification;
|
|
38
|
+
const { reportName, name: variantName, description, timestamp } = reportStateData;
|
|
39
|
+
const actions = utils_2.default.initReportActions(definition.variant, reportStateData);
|
|
40
|
+
renderData = {
|
|
41
|
+
variantName,
|
|
42
|
+
reportName,
|
|
43
|
+
description,
|
|
44
|
+
classification,
|
|
45
|
+
template,
|
|
46
|
+
actions,
|
|
47
|
+
requestedTimestamp: new Date(timestamp.requested).toLocaleString(),
|
|
48
|
+
};
|
|
49
|
+
switch (template) {
|
|
50
|
+
case 'list':
|
|
51
|
+
renderData = {
|
|
52
|
+
...renderData,
|
|
53
|
+
...utils_1.default.getRenderData(req, definition, reportData, count, reportStateData),
|
|
54
|
+
};
|
|
55
|
+
break;
|
|
56
|
+
case 'listWithSections':
|
|
57
|
+
// TODO: add list eith sections utils here
|
|
58
|
+
break;
|
|
59
|
+
default:
|
|
60
|
+
renderData = {
|
|
61
|
+
...renderData,
|
|
62
|
+
...utils_1.default.getRenderData(req, definition, reportData, count, reportStateData),
|
|
63
|
+
};
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
if (Object.keys(renderData).length && Object.keys(reportStateData).length) {
|
|
69
|
+
await asyncReportsStore.updateLastViewed(reportStateData.executionId);
|
|
70
|
+
await recentlyViewedStoreService.setRecentlyViewed(reportStateData);
|
|
71
|
+
}
|
|
72
|
+
return { renderData };
|
|
73
|
+
};
|
|
74
|
+
exports.getReport = getReport;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { components } from '../types/api'
|
|
2
|
+
import Dict = NodeJS.Dict
|
|
3
|
+
import { AsyncReportUtilsParams } from '../types/AsyncReportUtils'
|
|
4
|
+
import { AsyncReportData } from '../types/AsyncReport'
|
|
5
|
+
import AsyncReportListUtils from '../components/async-report-list/utils'
|
|
6
|
+
import ReportActionsUtils from '../components/icon-button-list/utils'
|
|
7
|
+
|
|
8
|
+
export const initDataSources = ({ req, res, next, asyncReportsStore, dataSources }: AsyncReportUtilsParams) => {
|
|
9
|
+
const token = res.locals.user?.token ? res.locals.user.token : 'token'
|
|
10
|
+
const { reportId, variantId: reportVariantId, tableId } = req.params
|
|
11
|
+
const { selectedPage = 1, pageSize = 10 } = req.query
|
|
12
|
+
const dataProductDefinitionsPath = <string>req.query.dataProductDefinitionsPath
|
|
13
|
+
const reportDefinitionPromise = dataSources.getDefinition(
|
|
14
|
+
token,
|
|
15
|
+
reportId,
|
|
16
|
+
reportVariantId,
|
|
17
|
+
dataProductDefinitionsPath,
|
|
18
|
+
)
|
|
19
|
+
const reportDataPromise = dataSources.getAsyncReport(token, reportId, reportVariantId, tableId, {
|
|
20
|
+
selectedPage: +selectedPage,
|
|
21
|
+
pageSize: +pageSize,
|
|
22
|
+
dataProductDefinitionsPath,
|
|
23
|
+
})
|
|
24
|
+
const reportDataCountPromise = dataSources.getAsyncCount(token, tableId)
|
|
25
|
+
const stateDataPromise = asyncReportsStore.getReportByTableId(tableId)
|
|
26
|
+
|
|
27
|
+
return [reportDefinitionPromise, reportDataPromise, reportDataCountPromise, stateDataPromise]
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const getReport = async ({
|
|
31
|
+
req,
|
|
32
|
+
res,
|
|
33
|
+
next,
|
|
34
|
+
asyncReportsStore,
|
|
35
|
+
recentlyViewedStoreService,
|
|
36
|
+
dataSources,
|
|
37
|
+
}: AsyncReportUtilsParams) => {
|
|
38
|
+
const dataPromises = initDataSources({ req, res, next, dataSources, asyncReportsStore })
|
|
39
|
+
|
|
40
|
+
let renderData = {}
|
|
41
|
+
let reportStateData: AsyncReportData
|
|
42
|
+
if (dataPromises) {
|
|
43
|
+
await Promise.all(dataPromises).then((resolvedData) => {
|
|
44
|
+
const definition = resolvedData[0] as unknown as components['schemas']['SingleVariantReportDefinition']
|
|
45
|
+
const reportData = <Array<Dict<string>>>resolvedData[1]
|
|
46
|
+
const count = <number>resolvedData[2]
|
|
47
|
+
reportStateData = <AsyncReportData>resolvedData[3]
|
|
48
|
+
|
|
49
|
+
const { classification } = definition.variant
|
|
50
|
+
const { template } = definition.variant.specification
|
|
51
|
+
const { reportName, name: variantName, description, timestamp } = reportStateData
|
|
52
|
+
const actions = ReportActionsUtils.initReportActions(definition.variant, reportStateData)
|
|
53
|
+
|
|
54
|
+
renderData = {
|
|
55
|
+
variantName,
|
|
56
|
+
reportName,
|
|
57
|
+
description,
|
|
58
|
+
classification,
|
|
59
|
+
template,
|
|
60
|
+
actions,
|
|
61
|
+
requestedTimestamp: new Date(timestamp.requested).toLocaleString(),
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
switch (template) {
|
|
65
|
+
case 'list':
|
|
66
|
+
renderData = {
|
|
67
|
+
...renderData,
|
|
68
|
+
...AsyncReportListUtils.getRenderData(req, definition, reportData, count, reportStateData),
|
|
69
|
+
}
|
|
70
|
+
break
|
|
71
|
+
case 'listWithSections':
|
|
72
|
+
// TODO: add list eith sections utils here
|
|
73
|
+
break
|
|
74
|
+
default:
|
|
75
|
+
renderData = {
|
|
76
|
+
...renderData,
|
|
77
|
+
...AsyncReportListUtils.getRenderData(req, definition, reportData, count, reportStateData),
|
|
78
|
+
}
|
|
79
|
+
break
|
|
80
|
+
}
|
|
81
|
+
})
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (Object.keys(renderData).length && Object.keys(reportStateData).length) {
|
|
85
|
+
await asyncReportsStore.updateLastViewed(reportStateData.executionId)
|
|
86
|
+
await recentlyViewedStoreService.setRecentlyViewed(reportStateData)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return { renderData }
|
|
90
|
+
}
|
|
@@ -1,10 +1,46 @@
|
|
|
1
1
|
{% extends layoutPath %}
|
|
2
2
|
{% from "../components/async-report-list/view.njk" import dprAsyncReportList %}
|
|
3
|
+
{% from "../components/icon-button-list/view.njk" import dprIconButtonList %}
|
|
3
4
|
|
|
4
5
|
{% set mainClasses = "app-container govuk-body" %}
|
|
5
6
|
|
|
6
7
|
{% block content %}
|
|
8
|
+
{% set template = renderData.template %}
|
|
9
|
+
{% set reportName = renderData.reportName %}
|
|
10
|
+
{% set variantName = renderData.variantName %}
|
|
11
|
+
{% set description = renderData.description %}
|
|
12
|
+
{% set actions = renderData.actions %}
|
|
13
|
+
{% set requestedTimestamp = renderData.requestedTimestamp %}
|
|
14
|
+
|
|
7
15
|
<div class="govuk-width-container">
|
|
8
|
-
|
|
16
|
+
<div class="govuk-width-container report-list-container">
|
|
17
|
+
|
|
18
|
+
<div class="drp-async-reports-heading-container">
|
|
19
|
+
<h1 class="govuk-heading-l govuk-!-margin-bottom-2">{{ reportName }} - {{ variantName }}</h1>
|
|
20
|
+
<p class="govuk-body-s govuk-!-margin-bottom-2">
|
|
21
|
+
<i>{{ description }}</i>
|
|
22
|
+
</p>
|
|
23
|
+
<p class="govuk-body-s govuk-!-margin-bottom-0">
|
|
24
|
+
<strong>Requested at:</strong>
|
|
25
|
+
{{ requestedTimestamp }}
|
|
26
|
+
</p>
|
|
27
|
+
|
|
28
|
+
{{ dprIconButtonList(actions) }}
|
|
29
|
+
</div>
|
|
30
|
+
|
|
31
|
+
{% if template === 'list' %}
|
|
32
|
+
|
|
33
|
+
{{ dprAsyncReportList(renderData) }}
|
|
34
|
+
|
|
35
|
+
{% elif(template === 'listWithSection') %}
|
|
36
|
+
|
|
37
|
+
{# TODO: Add template view here #}
|
|
38
|
+
|
|
39
|
+
{% else %}
|
|
40
|
+
|
|
41
|
+
{{ dprAsyncReportList(renderData) }}
|
|
42
|
+
|
|
43
|
+
{% endif %}
|
|
44
|
+
</div>
|
|
9
45
|
</div>
|
|
10
46
|
{% endblock %}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ministryofjustice/hmpps-digital-prison-reporting-frontend",
|
|
3
3
|
"description": "The Digital Prison Reporting Frontend contains templates and code to help display data effectively in UI applications.",
|
|
4
|
-
"version": "3.
|
|
4
|
+
"version": "3.13.0",
|
|
5
5
|
"main": "dpr/assets/js/all.mjs",
|
|
6
6
|
"sass": "dpr/all.scss",
|
|
7
7
|
"engines": {
|
package/package.zip
CHANGED
|
Binary file
|