@ministryofjustice/hmpps-digital-prison-reporting-frontend 4.15.4 → 4.16.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/components/_async/async-filters-form/view.njk +2 -2
- package/dpr/components/_catalogue/catalogue-list/utils.test.ts +1 -1
- package/dpr/components/_charts/chart/Buckets.js +2 -0
- package/dpr/components/_charts/chart/Buckets.js.map +7 -0
- package/dpr/components/_charts/chart/Buckets.ts +198 -0
- package/dpr/components/_charts/chart/DashboardVisualisation.js +2 -0
- package/dpr/components/_charts/chart/DashboardVisualisation.js.map +7 -0
- package/dpr/components/_charts/chart/DashboardVisualisation.ts +43 -0
- package/dpr/components/_charts/chart/heatmap/HeatmapChart.js +2 -0
- package/dpr/components/_charts/chart/heatmap/HeatmapChart.js.map +7 -0
- package/dpr/components/_charts/chart/heatmap/HeatmapChart.ts +144 -0
- package/dpr/components/_charts/utils.js +1 -1
- package/dpr/components/_charts/utils.js.map +3 -3
- package/dpr/components/_charts/utils.ts +1 -1
- package/dpr/components/_dashboards/dashboard/types.js.map +1 -1
- package/dpr/components/_dashboards/dashboard/types.ts +4 -4
- package/dpr/components/_dashboards/scorecard/Scorecard.js +2 -0
- package/dpr/components/_dashboards/scorecard/Scorecard.js.map +7 -0
- package/dpr/components/_dashboards/scorecard/Scorecard.ts +315 -0
- package/dpr/components/_dashboards/scorecard/types.js +1 -1
- package/dpr/components/_dashboards/scorecard/types.js.map +1 -1
- package/dpr/components/_dashboards/scorecard/types.ts +20 -1
- package/dpr/components/_dashboards/scorecard/utils.js +1 -1
- package/dpr/components/_dashboards/scorecard/utils.js.map +3 -3
- package/dpr/components/_dashboards/scorecard/utils.test.ts +8 -437
- package/dpr/components/_dashboards/scorecard/utils.ts +2 -265
- package/dpr/components/_dashboards/scorecard/view.njk +2 -2
- package/dpr/components/_filters/types.d.js.map +1 -1
- package/dpr/components/_filters/types.d.ts +1 -0
- package/dpr/components/_filters/utils.js +1 -1
- package/dpr/components/_filters/utils.js.map +3 -3
- package/dpr/components/_filters/utils.ts +17 -12
- package/dpr/routes/journeys/request-report/filters/tests.cy.js +1 -1
- package/dpr/routes/journeys/request-report/filters/tests.cy.js.map +3 -3
- package/dpr/routes/journeys/request-report/filters/tests.cy.ts +40 -0
- package/dpr/routes/journeys/view-report/async/dashboard/utils.js +1 -1
- package/dpr/routes/journeys/view-report/async/dashboard/utils.js.map +3 -3
- package/dpr/routes/journeys/view-report/async/dashboard/utils.ts +3 -2
- package/dpr/types/api.d.js.map +1 -1
- package/dpr/types/api.d.ts +2 -0
- package/dpr/utils/datasetHelper.js +1 -1
- package/dpr/utils/datasetHelper.js.map +2 -2
- package/dpr/utils/datasetHelper.ts +1 -1
- package/package.json +1 -1
- package/dpr/components/_charts/chart/heatmap/Heatmap.js +0 -2
- package/dpr/components/_charts/chart/heatmap/Heatmap.js.map +0 -7
- package/dpr/components/_charts/chart/heatmap/Heatmap.ts +0 -278
|
@@ -1,278 +0,0 @@
|
|
|
1
|
-
/* eslint-disable prefer-destructuring */
|
|
2
|
-
import dayjs from 'dayjs'
|
|
3
|
-
import { withAlphaHex } from 'with-alpha-hex'
|
|
4
|
-
import { Granularity } from '../../../_inputs/granular-date-range/types'
|
|
5
|
-
import { DashboardDataResponse } from '../../../../types/Metrics'
|
|
6
|
-
import {
|
|
7
|
-
DashboardVisualisation,
|
|
8
|
-
DashboardVisualisationColumns,
|
|
9
|
-
DashboardVisualisationType,
|
|
10
|
-
MatrixDashboardVisualisationBucket,
|
|
11
|
-
MatrixDashboardVisualisationOptions,
|
|
12
|
-
} from '../../../_dashboards/dashboard/types'
|
|
13
|
-
import { ChartData, ChartType, MatrixChartData, UnitType } from '../../../../types/Charts'
|
|
14
|
-
import DatasetHelper from '../../../../utils/datasetHelper'
|
|
15
|
-
|
|
16
|
-
class HeatmapChart {
|
|
17
|
-
private baseColour = '#1d70b8'
|
|
18
|
-
|
|
19
|
-
private ragColours: string[] = ['#00703c', '#ffdd00', '#d4351c']
|
|
20
|
-
|
|
21
|
-
private buckets: MatrixDashboardVisualisationBucket[] = []
|
|
22
|
-
|
|
23
|
-
private bucketCount: number
|
|
24
|
-
|
|
25
|
-
private onlyBucketColoursDefined: boolean
|
|
26
|
-
|
|
27
|
-
private granularity: Granularity
|
|
28
|
-
|
|
29
|
-
private responseData: DashboardDataResponse[]
|
|
30
|
-
|
|
31
|
-
private data: MatrixChartData[] = []
|
|
32
|
-
|
|
33
|
-
private definition: DashboardVisualisation
|
|
34
|
-
|
|
35
|
-
private columns: DashboardVisualisationColumns
|
|
36
|
-
|
|
37
|
-
private unit: UnitType
|
|
38
|
-
|
|
39
|
-
private type: ChartType
|
|
40
|
-
|
|
41
|
-
private dayDateFormat = 'DD/MM/YYYY'
|
|
42
|
-
|
|
43
|
-
private hasRag = false
|
|
44
|
-
|
|
45
|
-
private valueKey: string
|
|
46
|
-
|
|
47
|
-
private options: MatrixDashboardVisualisationOptions = {}
|
|
48
|
-
|
|
49
|
-
private label: string
|
|
50
|
-
|
|
51
|
-
constructor(responseData: DashboardDataResponse[], granularity: Granularity, definition: DashboardVisualisation) {
|
|
52
|
-
this.granularity = granularity
|
|
53
|
-
this.initFromDefinition(definition)
|
|
54
|
-
this.initFromResponseData(responseData)
|
|
55
|
-
this.initBuckets()
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
private initFromResponseData = (responseData: DashboardDataResponse[]) => {
|
|
59
|
-
this.hasRag = responseData[0][this.valueKey].rag !== undefined
|
|
60
|
-
this.responseData = responseData
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
private initFromDefinition = (definition: DashboardVisualisation) => {
|
|
64
|
-
this.definition = definition
|
|
65
|
-
this.columns = definition.columns
|
|
66
|
-
this.unit = this.columns.measures[0].unit ? this.columns.measures[0].unit : undefined
|
|
67
|
-
this.type = this.definition.type.split('-')[0] as ChartType
|
|
68
|
-
|
|
69
|
-
this.options = <MatrixDashboardVisualisationOptions>definition.options
|
|
70
|
-
this.baseColour = this.options.baseColour || this.baseColour
|
|
71
|
-
this.valueKey = this.columns.measures[1].id
|
|
72
|
-
this.label = this.columns.measures[1].display
|
|
73
|
-
this.onlyBucketColoursDefined = this.options?.buckets?.every(
|
|
74
|
-
(bucket) => !bucket.max && !bucket.min && bucket.hexColour !== undefined,
|
|
75
|
-
)
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
private initBuckets = () => {
|
|
79
|
-
const { buckets } = this.options
|
|
80
|
-
this.setBucketCount()
|
|
81
|
-
this.initBucketColours()
|
|
82
|
-
if (buckets) {
|
|
83
|
-
if (this.onlyBucketColoursDefined) {
|
|
84
|
-
this.initAutomaticThresholdBucket()
|
|
85
|
-
} else {
|
|
86
|
-
this.initCustomThresholdBuckets()
|
|
87
|
-
}
|
|
88
|
-
} else if (!this.hasRag) this.initAutomaticThresholdBucket()
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
private initCustomThresholdBuckets = () => {
|
|
92
|
-
this.buckets = this.options.buckets.map((bucket, i) => {
|
|
93
|
-
return {
|
|
94
|
-
...this.buckets[i],
|
|
95
|
-
...bucket,
|
|
96
|
-
}
|
|
97
|
-
})
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Initialises the bucket thresholds by defining the range between the min and max
|
|
102
|
-
* and dividing into 3 equal parts
|
|
103
|
-
*/
|
|
104
|
-
private initAutomaticThresholdBucket = () => {
|
|
105
|
-
const { min, max, bucketSize } = this.setAutomaticThresholdSize()
|
|
106
|
-
let maxValue = 0
|
|
107
|
-
this.buckets = this.buckets.map((bucket, i) => {
|
|
108
|
-
let minValue = min
|
|
109
|
-
if (i !== 0) minValue = maxValue + 1
|
|
110
|
-
maxValue = bucketSize * (i + 1)
|
|
111
|
-
if (i === this.buckets.length - 1) maxValue = max
|
|
112
|
-
|
|
113
|
-
return {
|
|
114
|
-
hexColour: this.options?.buckets ? this.options.buckets[i]?.hexColour : bucket.hexColour,
|
|
115
|
-
min: minValue,
|
|
116
|
-
max: maxValue,
|
|
117
|
-
}
|
|
118
|
-
})
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
private initBucketColours = () => {
|
|
122
|
-
const { useRagColours } = this.options
|
|
123
|
-
if (useRagColours && this.bucketCount === 3) {
|
|
124
|
-
this.buckets = Array.from(new Array(this.bucketCount)).map((d, i) => {
|
|
125
|
-
return {
|
|
126
|
-
hexColour: this.ragColours[i],
|
|
127
|
-
}
|
|
128
|
-
})
|
|
129
|
-
} else {
|
|
130
|
-
const alphaDivision = 1 / this.bucketCount
|
|
131
|
-
this.buckets = Array.from(new Array(this.bucketCount)).map((d, i) => {
|
|
132
|
-
const division = alphaDivision * (i + 1)
|
|
133
|
-
return {
|
|
134
|
-
hexColour: withAlphaHex(this.baseColour, division),
|
|
135
|
-
}
|
|
136
|
-
})
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
private setAutomaticThresholdSize = () => {
|
|
141
|
-
const values = this.responseData.map((resData) => Number(resData[this.valueKey].raw))
|
|
142
|
-
const min = Math.min(...values)
|
|
143
|
-
const max = Math.max(...values)
|
|
144
|
-
const bucketSize = Math.ceil((max - min) / this.bucketCount)
|
|
145
|
-
|
|
146
|
-
return {
|
|
147
|
-
min,
|
|
148
|
-
max,
|
|
149
|
-
bucketSize,
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
private setBucketCount = () => {
|
|
154
|
-
const { buckets } = this.options
|
|
155
|
-
if (this.hasRag) {
|
|
156
|
-
this.bucketCount =
|
|
157
|
-
Math.max(...this.responseData.map((resData: DashboardDataResponse) => resData[this.valueKey].rag)) + 1
|
|
158
|
-
} else if (buckets) {
|
|
159
|
-
this.bucketCount = buckets.length
|
|
160
|
-
} else {
|
|
161
|
-
this.bucketCount = 3
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
private validateDefinition = () => {
|
|
166
|
-
const { id, columns, type } = this.definition
|
|
167
|
-
const errors = []
|
|
168
|
-
|
|
169
|
-
// Validate measures
|
|
170
|
-
if (columns.measures.length !== 2) {
|
|
171
|
-
errors.push(`Measures should only have 2 columns defined. Only found ${columns.measures.length}`)
|
|
172
|
-
} else if (type === DashboardVisualisationType.MATRIX_TIMESERIES) {
|
|
173
|
-
if (columns.measures[0].id !== 'ts') {
|
|
174
|
-
errors.push(`measure at index 0 has incorrect ID. Expected ID to be "ts". Found "${columns.measures[0].id}"`)
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// Throw the error
|
|
179
|
-
if (errors.length) {
|
|
180
|
-
const message = `Validation: Visualisaton definition: ID: ${id}, type: ${type}, errors: ${errors.join(',')}`
|
|
181
|
-
throw new Error(message)
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
private initTimeseriesData = () => {
|
|
186
|
-
const timeBlockData = DatasetHelper.groupRowsByTimestamp(this.responseData)
|
|
187
|
-
|
|
188
|
-
this.data = timeBlockData.map((tsData) => {
|
|
189
|
-
const { raw, rag } = tsData[0][this.valueKey]
|
|
190
|
-
const tsRaw = tsData[0].ts.raw
|
|
191
|
-
|
|
192
|
-
const v = Number(raw)
|
|
193
|
-
const r = rag !== undefined ? Number(tsData[0][this.valueKey].rag) : undefined
|
|
194
|
-
let x
|
|
195
|
-
let y
|
|
196
|
-
|
|
197
|
-
switch (this.granularity) {
|
|
198
|
-
case 'hourly':
|
|
199
|
-
break
|
|
200
|
-
case 'weekly':
|
|
201
|
-
x = dayjs(tsRaw, this.dayDateFormat).format('ddd')
|
|
202
|
-
y = dayjs(tsRaw, this.dayDateFormat).week()
|
|
203
|
-
break
|
|
204
|
-
case 'daily':
|
|
205
|
-
x = dayjs(tsRaw, this.dayDateFormat).format('MMM YY')
|
|
206
|
-
y = dayjs(tsRaw, this.dayDateFormat).format('D')
|
|
207
|
-
break
|
|
208
|
-
case 'monthly':
|
|
209
|
-
{
|
|
210
|
-
const ts = (<string>tsRaw).split(' ')
|
|
211
|
-
x = ts[1]
|
|
212
|
-
y = ts[0]
|
|
213
|
-
}
|
|
214
|
-
break
|
|
215
|
-
case 'annually':
|
|
216
|
-
x = 'year'
|
|
217
|
-
y = <string>tsRaw
|
|
218
|
-
break
|
|
219
|
-
default:
|
|
220
|
-
x = dayjs(tsRaw, this.dayDateFormat).format('MMM YY')
|
|
221
|
-
y = dayjs(tsRaw, this.dayDateFormat).format('D')
|
|
222
|
-
break
|
|
223
|
-
}
|
|
224
|
-
return { y, x, v, r }
|
|
225
|
-
})
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
private binDataIntoBuckets = () => {
|
|
229
|
-
this.data = this.data.map((d) => {
|
|
230
|
-
const { v, r } = d
|
|
231
|
-
let c
|
|
232
|
-
if (r !== undefined) {
|
|
233
|
-
c = this.buckets[r].hexColour
|
|
234
|
-
} else {
|
|
235
|
-
this.buckets.forEach((bucket) => {
|
|
236
|
-
const { min, max } = bucket
|
|
237
|
-
// First bucket
|
|
238
|
-
if (!min && max && v <= max) {
|
|
239
|
-
c = bucket.hexColour
|
|
240
|
-
}
|
|
241
|
-
// middle buckets
|
|
242
|
-
if (min && v >= bucket.min && max && v <= bucket.max) {
|
|
243
|
-
c = bucket.hexColour
|
|
244
|
-
}
|
|
245
|
-
// last bucket
|
|
246
|
-
if (min && !max && v >= min) {
|
|
247
|
-
c = bucket.hexColour
|
|
248
|
-
}
|
|
249
|
-
})
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
return { ...d, c }
|
|
253
|
-
})
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
build = (): ChartData => {
|
|
257
|
-
this.validateDefinition()
|
|
258
|
-
this.initTimeseriesData()
|
|
259
|
-
this.binDataIntoBuckets()
|
|
260
|
-
|
|
261
|
-
return {
|
|
262
|
-
type: this.type,
|
|
263
|
-
unit: this.unit,
|
|
264
|
-
timeseries: true,
|
|
265
|
-
data: {
|
|
266
|
-
datasets: [
|
|
267
|
-
{
|
|
268
|
-
label: this.label,
|
|
269
|
-
data: this.data,
|
|
270
|
-
},
|
|
271
|
-
],
|
|
272
|
-
},
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
export { HeatmapChart }
|
|
278
|
-
export default HeatmapChart
|