@ministryofjustice/hmpps-digital-prison-reporting-frontend 3.18.2 → 3.19.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.
@@ -488,12 +488,13 @@ class ToggleButton extends DprClientClass {
488
488
  const urlParams = new URLSearchParams(window.location.search);
489
489
 
490
490
  urlParams.keys().forEach((key) => {
491
- const toggle = document.getElementById(key);
492
- if (toggle) {
491
+ const element = document.getElementById(key);
492
+ if (element && element.classList.contains('dpr-toggle-button')) {
493
+ console.log('is toggle', key);
493
494
  const value = urlParams.get(key);
494
- const icons = Array.from(toggle.getElementsByClassName('dpr-icon-wrapper'));
495
+ const icons = Array.from(element.getElementsByClassName('dpr-icon-wrapper'));
495
496
 
496
- const toggleContainer = toggle.parentNode.parentNode;
497
+ const toggleContainer = element.parentNode.parentNode;
497
498
  const contentContainers = Array.from(toggleContainer.getElementsByClassName('dpr-toggle-content'));
498
499
 
499
500
  icons.forEach((icon) => {
@@ -719,31 +720,82 @@ class DataTable extends DprClientClass {
719
720
  }
720
721
  }
721
722
 
723
+ class DprPollingStatusClass extends DprClientClass {
724
+ getPollingFrquency () {
725
+ return '2000' // 2 seconds
726
+ }
727
+
728
+ getPollingStatuses () {
729
+ return ['SUBMITTED', 'STARTED', 'PICKED']
730
+ }
731
+
732
+ getEndStatuses () {
733
+ return ['FINISHED', 'FAILED', 'EXPIRED']
734
+ }
735
+
736
+ async getRequestStatus (metaData, csrfToken) {
737
+ return this.getStatus('/getStatus/', metaData, csrfToken)
738
+ }
739
+
740
+ async getExpiredStatus (metaData, csrfToken) {
741
+ return this.getStatus('/getExpiredStatus/', metaData, csrfToken)
742
+ }
743
+
744
+ async getStatus (endpoint, body, csrfToken) {
745
+ let response;
746
+ await fetch(endpoint, {
747
+ method: 'post',
748
+ headers: {
749
+ Accept: 'application/json',
750
+ 'Content-Type': 'application/json',
751
+ 'CSRF-Token': csrfToken,
752
+ },
753
+ body: JSON.stringify({
754
+ ...body,
755
+ }),
756
+ })
757
+ .then((res) => res.json())
758
+ .then((res) => {
759
+ response = res;
760
+ })
761
+ .catch((error) => console.error('Error:', error));
762
+
763
+ return response
764
+ }
765
+ }
766
+
722
767
  /* eslint-disable class-methods-use-this */
723
768
 
724
- class dprAsyncPolling extends DprClientClass {
769
+ class DprAsyncPolling extends DprPollingStatusClass {
725
770
  static getModuleName () {
726
771
  return 'async-polling-content'
727
772
  }
728
773
 
729
- async initialise () {
730
- this.POLLING_STATUSES = ['SUBMITTED', 'STARTED', 'PICKED'];
731
- this.POLLING_FREQUENCY = '2000'; // 2 seconds
774
+ initialise () {
775
+ this.POLLING_STATUSES = this.getPollingStatuses();
776
+ this.POLLING_FREQUENCY = this.getPollingFrquency();
732
777
 
733
778
  this.statusSection = document.getElementById('async-request-polling-status');
734
779
  this.retryRequestButton = document.getElementById('retry-async-request');
735
780
  this.cancelRequestButton = document.getElementById('cancel-async-request');
736
781
  this.viewReportButton = document.getElementById('view-async-report-button');
737
782
 
783
+ this.requestData = this.statusSection.getAttribute('data-request-data');
784
+
738
785
  this.initCancelRequestButton();
739
786
  this.initPollingStatus();
740
787
  }
741
788
 
742
789
  initPollingStatus () {
743
790
  const status = this.statusSection.getAttribute('data-current-status');
791
+
744
792
  if (this.POLLING_STATUSES.includes(status)) {
745
- setTimeout(() => {
746
- window.location.reload();
793
+ setInterval(async () => {
794
+ if (this.requestData) {
795
+ const meta = JSON.parse(this.requestData);
796
+ await this.getRequestStatus(meta, this.csrfToken);
797
+ window.location.reload();
798
+ }
747
799
  }, this.POLLING_FREQUENCY);
748
800
  }
749
801
  }
@@ -819,11 +871,29 @@ class Search extends DprClientClass {
819
871
  if (searchBox) {
820
872
  searchBox.addEventListener('keyup', (e) => {
821
873
  this.updateSearchListing(e.target.value);
874
+
875
+ // Update Query Params
876
+ const queryParams = new URLSearchParams(window.location.search);
877
+ queryParams.set(searchBox.id, e.target.value);
878
+ window.history.replaceState(null, null, `?${queryParams.toString()}`);
822
879
  });
823
880
 
824
- this.updateSearchListing('');
881
+ this.initInputFromQueryParams();
825
882
  }
826
883
  }
884
+
885
+ // eslint-disable-next-line
886
+ initInputFromQueryParams () {
887
+ const urlParams = new URLSearchParams(window.location.search);
888
+
889
+ urlParams.forEach((value, key) => {
890
+ const element = document.getElementById(key);
891
+ if (element && element.classList.contains('dpr-search-box')) {
892
+ element.value = value;
893
+ this.updateSearchListing(element.value);
894
+ }
895
+ });
896
+ }
827
897
  }
828
898
 
829
899
  class BookmarkToggle extends DprClientClass {
@@ -959,6 +1029,81 @@ class DateInput extends DprClientClass {
959
1029
  }
960
1030
  }
961
1031
 
1032
+ /* eslint-disable class-methods-use-this */
1033
+
1034
+ class DprAsyncRequestList extends DprPollingStatusClass {
1035
+ static getModuleName () {
1036
+ return 'async-request-list'
1037
+ }
1038
+
1039
+ initialise () {
1040
+ this.END_STATUSES = this.getEndStatuses();
1041
+ this.POLLING_FREQUENCY = this.getPollingFrquency();
1042
+
1043
+ this.requestList = document.getElementById('dpr-async-request-component');
1044
+ this.requestData = this.requestList.getAttribute('data-request-data');
1045
+ this.csrfToken = this.requestList.getAttribute('data-csrf-token');
1046
+ this.initPollingStatus();
1047
+ }
1048
+
1049
+ initPollingStatus () {
1050
+ setInterval(async () => {
1051
+ if (this.requestData) {
1052
+ const meta = JSON.parse(this.requestData);
1053
+ await Promise.all(
1054
+ meta.map(async (metaData) => {
1055
+ // Don't poll if current state is an end state
1056
+ if (!this.END_STATUSES.includes(metaData.status)) {
1057
+ const response = await this.getRequestStatus(metaData, this.csrfToken);
1058
+
1059
+ // Reload if new status is an end state
1060
+ if (this.END_STATUSES.includes(response.status)) {
1061
+ window.location.reload();
1062
+ }
1063
+ }
1064
+ }),
1065
+ );
1066
+ }
1067
+ }, this.POLLING_FREQUENCY);
1068
+ }
1069
+ }
1070
+
1071
+ /* eslint-disable class-methods-use-this */
1072
+
1073
+ class DprRecentlyViewedList extends DprPollingStatusClass {
1074
+ static getModuleName () {
1075
+ return 'recently-viewed-list'
1076
+ }
1077
+
1078
+ initialise () {
1079
+ this.POLLING_STATUSES = [];
1080
+ this.POLLING_FREQUENCY = '300000'; // 5 mins
1081
+
1082
+ this.viewedList = document.getElementById('dpr-recently-viewed-component');
1083
+ this.viewedReportData = this.viewedList.getAttribute('data-request-data');
1084
+ this.csrfToken = this.viewedList.getAttribute('data-csrf-token');
1085
+ this.initPollingStatus();
1086
+ }
1087
+
1088
+ initPollingStatus () {
1089
+ setInterval(async () => {
1090
+ if (this.viewedReportData) {
1091
+ const meta = JSON.parse(this.viewedReportData);
1092
+ await Promise.all(
1093
+ meta.map(async (metaData) => {
1094
+ if (metaData.status !== 'EXPIRED') {
1095
+ const response = await this.getExpiredStatus(metaData, this.csrfToken);
1096
+ if (response.isExpired) {
1097
+ window.location.reload();
1098
+ }
1099
+ }
1100
+ }),
1101
+ );
1102
+ }
1103
+ }, this.POLLING_FREQUENCY);
1104
+ }
1105
+ }
1106
+
962
1107
  /* eslint-disable no-new */
963
1108
 
964
1109
  /**
@@ -981,10 +1126,12 @@ function initAll () {
981
1126
  Pagination,
982
1127
  IconButtonList,
983
1128
  DataTable,
984
- dprAsyncPolling,
1129
+ DprAsyncPolling,
985
1130
  Search,
986
1131
  BookmarkToggle,
987
1132
  DateInput,
1133
+ DprAsyncRequestList,
1134
+ DprRecentlyViewedList,
988
1135
  ];
989
1136
 
990
1137
  components.forEach((Component) => {
@@ -260,7 +260,6 @@ export default {
260
260
  if (executionId && tableId) {
261
261
  redirect = await updateStore(req, services, fields, querySummaryData, executionId, tableId)
262
262
  }
263
-
264
263
  return redirect
265
264
  },
266
265
 
@@ -1,10 +1,6 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
3
  const AsyncReport_1 = require("../../types/AsyncReport");
7
- const reportStatusUtils_1 = __importDefault(require("../../utils/reportStatusUtils"));
8
4
  exports.default = {
9
5
  cancelRequest: async ({ req, res, services }) => {
10
6
  var _a;
@@ -16,49 +12,26 @@ exports.default = {
16
12
  }
17
13
  },
18
14
  renderPolling: async ({ req, res, services }) => {
19
- var _a, _b;
15
+ var _a;
20
16
  const csrfToken = res.locals.csrfToken || 'csrfToken';
21
- const token = ((_a = res.locals.user) === null || _a === void 0 ? void 0 : _a.token) ? res.locals.user.token : 'token';
22
17
  const { reportId, variantId, executionId } = req.params;
23
- let reportData = await services.asyncReportsStore.getReportByExecutionId(executionId);
24
- let statusResponse;
25
- if (reportStatusUtils_1.default.timeoutRequest(reportData.timestamp.requested) && reportData.status !== AsyncReport_1.RequestStatus.READY) {
26
- statusResponse = {
27
- status: AsyncReport_1.RequestStatus.FAILED,
28
- errorMessage: 'Request taking too long. Request Halted',
29
- };
30
- }
31
- else if (reportData.status === AsyncReport_1.RequestStatus.FAILED) {
32
- statusResponse = {
33
- status: AsyncReport_1.RequestStatus.FAILED,
34
- errorMessage: reportData.errorMessage,
35
- };
36
- }
37
- else if (reportData.status === AsyncReport_1.RequestStatus.ABORTED) {
38
- statusResponse = {
39
- status: AsyncReport_1.RequestStatus.ABORTED,
40
- };
41
- }
42
- else {
43
- statusResponse = await reportStatusUtils_1.default.getStatus(token, reportId, variantId, executionId, reportData.status, services, reportData.dataProductDefinitionsPath);
44
- }
45
- const { status, errorMessage } = statusResponse;
46
- if (statusResponse.reportData)
47
- reportData = statusResponse.reportData;
18
+ const reportData = await services.asyncReportsStore.getReportByExecutionId(executionId);
19
+ const { reportName, name, description, status, tableId, query, timestamp, url, errorMessage } = reportData;
48
20
  return {
49
21
  pollingRenderData: {
50
- reportName: reportData.reportName,
51
- variantName: reportData.name,
52
- description: reportData.description,
22
+ reportName,
23
+ variantName: name,
24
+ description,
53
25
  executionId,
54
26
  reportId,
55
27
  variantId,
56
28
  status,
57
- tableId: reportData.tableId,
58
- querySummary: reportData.query.summary,
29
+ tableId,
30
+ querySummary: query.summary,
31
+ requestedAt: timestamp.requested,
59
32
  csrfToken,
60
- ...(((_b = reportData.url.report) === null || _b === void 0 ? void 0 : _b.fullUrl) && { reportUrl: reportData.url.report.fullUrl }),
61
- ...(reportData.url.request.fullUrl && { requestUrl: reportData.url.request.fullUrl }),
33
+ ...(((_a = url.report) === null || _a === void 0 ? void 0 : _a.fullUrl) && { reportUrl: url.report.fullUrl }),
34
+ ...(url.request.fullUrl && { requestUrl: url.request.fullUrl }),
62
35
  ...(errorMessage && { errorMessage }),
63
36
  },
64
37
  };
@@ -1,11 +1,11 @@
1
1
  import { RequestStatus } from '../../types/AsyncReport'
2
2
  import { AsyncReportUtilsParams } from '../../types/AsyncReportUtils'
3
- import ReportStatusUtils from '../../utils/reportStatusUtils'
4
3
 
5
4
  export default {
6
5
  cancelRequest: async ({ req, res, services }: AsyncReportUtilsParams) => {
7
6
  const token = res.locals.user?.token ? res.locals.user.token : 'token'
8
7
  const { reportId, variantId, executionId } = req.body
8
+
9
9
  const response = await services.reportingService.cancelAsyncRequest(token, reportId, variantId, executionId)
10
10
  if (response && response.cancellationSucceeded) {
11
11
  await services.asyncReportsStore.updateStatus(executionId, RequestStatus.ABORTED)
@@ -14,52 +14,26 @@ export default {
14
14
 
15
15
  renderPolling: async ({ req, res, services }: AsyncReportUtilsParams) => {
16
16
  const csrfToken = (res.locals.csrfToken as unknown as string) || 'csrfToken'
17
- const token = res.locals.user?.token ? res.locals.user.token : 'token'
18
17
  const { reportId, variantId, executionId } = req.params
19
- let reportData = await services.asyncReportsStore.getReportByExecutionId(executionId)
20
- let statusResponse
21
- if (ReportStatusUtils.timeoutRequest(reportData.timestamp.requested) && reportData.status !== RequestStatus.READY) {
22
- statusResponse = {
23
- status: RequestStatus.FAILED,
24
- errorMessage: 'Request taking too long. Request Halted',
25
- }
26
- } else if (reportData.status === RequestStatus.FAILED) {
27
- statusResponse = {
28
- status: RequestStatus.FAILED,
29
- errorMessage: reportData.errorMessage,
30
- }
31
- } else if (reportData.status === RequestStatus.ABORTED) {
32
- statusResponse = {
33
- status: RequestStatus.ABORTED,
34
- }
35
- } else {
36
- statusResponse = await ReportStatusUtils.getStatus(
37
- token,
38
- reportId,
39
- variantId,
40
- executionId,
41
- reportData.status,
42
- services,
43
- reportData.dataProductDefinitionsPath,
44
- )
45
- }
46
- const { status, errorMessage } = statusResponse
47
- if (statusResponse.reportData) reportData = statusResponse.reportData
18
+
19
+ const reportData = await services.asyncReportsStore.getReportByExecutionId(executionId)
20
+ const { reportName, name, description, status, tableId, query, timestamp, url, errorMessage } = reportData
48
21
 
49
22
  return {
50
23
  pollingRenderData: {
51
- reportName: reportData.reportName,
52
- variantName: reportData.name,
53
- description: reportData.description,
24
+ reportName,
25
+ variantName: name,
26
+ description,
54
27
  executionId,
55
28
  reportId,
56
29
  variantId,
57
30
  status,
58
- tableId: reportData.tableId,
59
- querySummary: reportData.query.summary,
31
+ tableId,
32
+ querySummary: query.summary,
33
+ requestedAt: timestamp.requested,
60
34
  csrfToken,
61
- ...(reportData.url.report?.fullUrl && { reportUrl: reportData.url.report.fullUrl }),
62
- ...(reportData.url.request.fullUrl && { requestUrl: reportData.url.request.fullUrl }),
35
+ ...(url.report?.fullUrl && { reportUrl: url.report.fullUrl }),
36
+ ...(url.request.fullUrl && { requestUrl: url.request.fullUrl }),
63
37
  ...(errorMessage && { errorMessage }),
64
38
  },
65
39
  }
@@ -18,10 +18,21 @@
18
18
  {% set errorMessage = data.errorMessage %}
19
19
  {% set executionId = data.executionId %}
20
20
  {% set csrfToken = data.csrfToken %}
21
+ {% set requestedAt = data.requestedAt %}
21
22
  {% set descriptionClasses = 'govuk-!-margin-top-4 govuk-!-margin-bottom-6'%}
22
23
 
24
+ {% set meta = {
25
+ reportId: reportId,
26
+ variantId: variantId,
27
+ executionId: executionId,
28
+ status: status,
29
+ requestedAt: requestedAt
30
+ }%}
31
+
23
32
  <div id="async-request-polling-status"
24
- data-current-status={{ status }}
33
+ data-current-status='{{ status }}'
34
+ data-request-data='{{ meta | dump | safe }}'
35
+ data-csrf-token="{{ csrfToken }}"
25
36
  data-dpr-module="async-polling-content">
26
37
 
27
38
  {% set filtersHtml %}
@@ -0,0 +1,131 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.filterReports = exports.setDataFromStatus = exports.formatCardData = void 0;
27
+ const AsyncReport_1 = require("../../types/AsyncReport");
28
+ const reportStatusHelper_1 = require("../../utils/reportStatusHelper");
29
+ const ReportListHelper = __importStar(require("../../utils/reportsListHelper"));
30
+ const formatCardData = (requestedReportsData) => {
31
+ const reportData = JSON.parse(JSON.stringify(requestedReportsData));
32
+ const { executionId, reportId, variantId, name: text, description, query, status, timestamp, dataProductDefinitionsPath, } = reportData;
33
+ const summary = query.summary;
34
+ return {
35
+ id: executionId,
36
+ text,
37
+ description,
38
+ ...(0, exports.setDataFromStatus)(status, reportData),
39
+ tag: 'MIS',
40
+ summary,
41
+ status,
42
+ meta: {
43
+ reportId,
44
+ variantId,
45
+ executionId,
46
+ status,
47
+ requestedAt: timestamp.requested,
48
+ dataProductDefinitionsPath,
49
+ },
50
+ };
51
+ };
52
+ exports.formatCardData = formatCardData;
53
+ const setDataFromStatus = (status, requestedReportsData) => {
54
+ let timestamp;
55
+ let href;
56
+ const { url, timestamp: time } = requestedReportsData;
57
+ const retryParam = `&retryId=${requestedReportsData.executionId}`;
58
+ switch (status) {
59
+ case AsyncReport_1.RequestStatus.FAILED: {
60
+ const failedTime = time.failed ? new Date(time.failed).toLocaleString() : new Date().toLocaleString();
61
+ href = `${url.polling.fullUrl}`;
62
+ timestamp = `Failed at: ${failedTime}`;
63
+ break;
64
+ }
65
+ case AsyncReport_1.RequestStatus.ABORTED: {
66
+ href = `${url.request.fullUrl}${retryParam}`;
67
+ timestamp = `Aborted at: ${new Date(time.aborted).toLocaleString()}`;
68
+ break;
69
+ }
70
+ case AsyncReport_1.RequestStatus.FINISHED:
71
+ href = url.report.fullUrl;
72
+ timestamp = `Ready at: ${new Date(time.completed).toLocaleString()}`;
73
+ break;
74
+ case AsyncReport_1.RequestStatus.EXPIRED: {
75
+ href = `${url.request.fullUrl}${retryParam}`;
76
+ timestamp = `Expired at: ${new Date(time.expired).toLocaleString()}`;
77
+ break;
78
+ }
79
+ case AsyncReport_1.RequestStatus.SUBMITTED:
80
+ case AsyncReport_1.RequestStatus.STARTED:
81
+ case AsyncReport_1.RequestStatus.PICKED:
82
+ href = url.polling.fullUrl;
83
+ timestamp = `Requested at: ${new Date(time.requested).toLocaleString()}`;
84
+ break;
85
+ default:
86
+ timestamp = `Last viewed: ${new Date(time.lastViewed).toLocaleString()}`;
87
+ break;
88
+ }
89
+ return {
90
+ timestamp,
91
+ href,
92
+ };
93
+ };
94
+ exports.setDataFromStatus = setDataFromStatus;
95
+ const filterReports = (report) => {
96
+ return !report.timestamp.lastViewed && !report.timestamp.retried;
97
+ };
98
+ exports.filterReports = filterReports;
99
+ exports.default = {
100
+ getRequestStatus: async ({ req, res, services }) => {
101
+ const { executionId, status: currentStatus } = req.body;
102
+ const response = await (0, reportStatusHelper_1.getStatus)({ req, res, services });
103
+ if (currentStatus !== response.status) {
104
+ await services.asyncReportsStore.updateStatus(executionId, response.status, response.errorMessage);
105
+ response.reportData = await services.asyncReportsStore.getReportByExecutionId(executionId);
106
+ }
107
+ return response;
108
+ },
109
+ renderList: async ({ services, res, maxRows, }) => {
110
+ const csrfToken = res.locals.csrfToken || 'csrfToken';
111
+ const requestedReportsData = await services.asyncReportsStore.getAllReports();
112
+ let cardData = await ReportListHelper.formatCards(requestedReportsData, exports.filterReports, exports.formatCardData);
113
+ if (maxRows)
114
+ cardData = cardData.slice(0, maxRows);
115
+ const head = {
116
+ title: 'Requested Reports',
117
+ icon: 'hourglass',
118
+ id: 'requested-reports',
119
+ ...(cardData.length && { href: './async-reports/requested' }),
120
+ ...(!cardData.length && { emptyMessage: 'You have 0 requested reports' }),
121
+ };
122
+ return {
123
+ head,
124
+ cardData,
125
+ tableData: ReportListHelper.formatTable(cardData),
126
+ total: ReportListHelper.getTotals(cardData, maxRows),
127
+ meta: ReportListHelper.getMeta(cardData),
128
+ csrfToken,
129
+ };
130
+ },
131
+ };
@@ -0,0 +1,133 @@
1
+ import { CardData, RenderTableListResponse } from '../table-card-group/types'
2
+ import { AsyncReportUtilsParams } from '../../types/AsyncReportUtils'
3
+ import { AsyncReportData, RequestStatus } from '../../types/AsyncReport'
4
+ import { getStatus } from '../../utils/reportStatusHelper'
5
+ import * as ReportListHelper from '../../utils/reportsListHelper'
6
+
7
+ export const formatCardData = (requestedReportsData: AsyncReportData): CardData => {
8
+ const reportData: AsyncReportData = JSON.parse(JSON.stringify(requestedReportsData))
9
+ const {
10
+ executionId,
11
+ reportId,
12
+ variantId,
13
+ name: text,
14
+ description,
15
+ query,
16
+ status,
17
+ timestamp,
18
+ dataProductDefinitionsPath,
19
+ } = reportData
20
+ const summary = query.summary as { name: string; value: string }[]
21
+
22
+ return {
23
+ id: executionId,
24
+ text,
25
+ description,
26
+ ...setDataFromStatus(status, reportData),
27
+ tag: 'MIS',
28
+ summary,
29
+ status,
30
+ meta: {
31
+ reportId,
32
+ variantId,
33
+ executionId,
34
+ status,
35
+ requestedAt: timestamp.requested,
36
+ dataProductDefinitionsPath,
37
+ },
38
+ }
39
+ }
40
+
41
+ export const setDataFromStatus = (status: RequestStatus, requestedReportsData: AsyncReportData) => {
42
+ let timestamp
43
+ let href
44
+ const { url, timestamp: time } = requestedReportsData
45
+ const retryParam = `&retryId=${requestedReportsData.executionId}`
46
+ switch (status) {
47
+ case RequestStatus.FAILED: {
48
+ const failedTime = time.failed ? new Date(time.failed).toLocaleString() : new Date().toLocaleString()
49
+ href = `${url.polling.fullUrl}`
50
+ timestamp = `Failed at: ${failedTime}`
51
+ break
52
+ }
53
+ case RequestStatus.ABORTED: {
54
+ href = `${url.request.fullUrl}${retryParam}`
55
+ timestamp = `Aborted at: ${new Date(time.aborted).toLocaleString()}`
56
+ break
57
+ }
58
+ case RequestStatus.FINISHED:
59
+ href = url.report.fullUrl
60
+ timestamp = `Ready at: ${new Date(time.completed).toLocaleString()}`
61
+ break
62
+ case RequestStatus.EXPIRED: {
63
+ href = `${url.request.fullUrl}${retryParam}`
64
+ timestamp = `Expired at: ${new Date(time.expired).toLocaleString()}`
65
+ break
66
+ }
67
+ case RequestStatus.SUBMITTED:
68
+ case RequestStatus.STARTED:
69
+ case RequestStatus.PICKED:
70
+ href = url.polling.fullUrl
71
+ timestamp = `Requested at: ${new Date(time.requested).toLocaleString()}`
72
+ break
73
+ default:
74
+ timestamp = `Last viewed: ${new Date(time.lastViewed).toLocaleString()}`
75
+ break
76
+ }
77
+
78
+ return {
79
+ timestamp,
80
+ href,
81
+ }
82
+ }
83
+
84
+ export const filterReports = (report: AsyncReportData) => {
85
+ return !report.timestamp.lastViewed && !report.timestamp.retried
86
+ }
87
+
88
+ export default {
89
+ getRequestStatus: async ({ req, res, services }: AsyncReportUtilsParams) => {
90
+ const { executionId, status: currentStatus } = req.body
91
+ const response = await getStatus({ req, res, services })
92
+
93
+ if (currentStatus !== response.status) {
94
+ await services.asyncReportsStore.updateStatus(
95
+ executionId,
96
+ response.status as RequestStatus,
97
+ response.errorMessage,
98
+ )
99
+ response.reportData = await services.asyncReportsStore.getReportByExecutionId(executionId)
100
+ }
101
+
102
+ return response
103
+ },
104
+
105
+ renderList: async ({
106
+ services,
107
+ res,
108
+ maxRows,
109
+ }: { maxRows?: number } & AsyncReportUtilsParams): Promise<RenderTableListResponse> => {
110
+ const csrfToken = (res.locals.csrfToken as unknown as string) || 'csrfToken'
111
+ const requestedReportsData: AsyncReportData[] = await services.asyncReportsStore.getAllReports()
112
+
113
+ let cardData = await ReportListHelper.formatCards(requestedReportsData, filterReports, formatCardData)
114
+ if (maxRows) cardData = cardData.slice(0, maxRows)
115
+
116
+ const head = {
117
+ title: 'Requested Reports',
118
+ icon: 'hourglass',
119
+ id: 'requested-reports',
120
+ ...(cardData.length && { href: './async-reports/requested' }),
121
+ ...(!cardData.length && { emptyMessage: 'You have 0 requested reports' }),
122
+ }
123
+
124
+ return {
125
+ head,
126
+ cardData,
127
+ tableData: ReportListHelper.formatTable(cardData),
128
+ total: ReportListHelper.getTotals(cardData, maxRows),
129
+ meta: ReportListHelper.getMeta(cardData),
130
+ csrfToken,
131
+ }
132
+ },
133
+ }
@@ -0,0 +1,12 @@
1
+ {% from "components/table-card-group/view.njk" import dprTableCardGroup %}
2
+
3
+ {% macro dprAsyncRequestList(head, cardData, tableData, total, meta, csrfToken) %}
4
+ <div id="dpr-async-request-component" data-dpr-module="async-request-list" data-request-data='{{ meta | dump | safe }}' data-csrf-token="{{ csrfToken }}">
5
+ {{ dprTableCardGroup(
6
+ head,
7
+ cardData,
8
+ tableData,
9
+ total
10
+ )}}
11
+ </div>
12
+ {% endmacro %}