@planning-inspectorate/dynamic-forms 1.8.0 → 1.9.1
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/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{% extends layoutTemplate %}
|
|
2
2
|
|
|
3
|
-
{% from "govuk/components/error-summary/macro.njk" import govukErrorSummary %}
|
|
4
|
-
{% from "govuk/components/textarea/macro.njk" import govukTextarea %}
|
|
5
|
-
{% from "govuk/components/label/macro.njk" import govukLabel %}
|
|
6
3
|
{% from "govuk/components/button/macro.njk" import govukButton %}
|
|
7
4
|
{% from "govuk/components/checkboxes/macro.njk" import govukCheckboxes %}
|
|
8
|
-
{% from "govuk/components/
|
|
5
|
+
{% from "govuk/components/details/macro.njk" import govukDetails %}
|
|
6
|
+
{% from "govuk/components/error-summary/macro.njk" import govukErrorSummary %}
|
|
7
|
+
{% from "govuk/components/label/macro.njk" import govukLabel %}
|
|
9
8
|
{% from "govuk/components/table/macro.njk" import govukTable %}
|
|
9
|
+
{% from "govuk/components/textarea/macro.njk" import govukTextarea %}
|
|
10
10
|
|
|
11
11
|
{% block before_content %}
|
|
12
12
|
<div class="govuk-grid-row">
|
|
@@ -24,14 +24,9 @@
|
|
|
24
24
|
{{ super() }}
|
|
25
25
|
{% endblock before_content %}
|
|
26
26
|
|
|
27
|
-
{% set columnWidth = 'govuk-grid-column-two-thirds' %}
|
|
28
|
-
{% if showSuggestionsUi %}
|
|
29
|
-
{% set columnWidth = 'govuk-grid-column-one-half' %}
|
|
30
|
-
{% endif %}
|
|
31
|
-
|
|
32
27
|
{% block content %}
|
|
33
28
|
<div class="govuk-grid-row">
|
|
34
|
-
<div class="
|
|
29
|
+
<div class="govuk-grid-column-two-thirds">
|
|
35
30
|
<form action="" method="post" novalidate>
|
|
36
31
|
<input type="hidden" name="_csrf" value="{{ _csrf }}">
|
|
37
32
|
|
|
@@ -53,15 +48,59 @@
|
|
|
53
48
|
{% include question.html ignore missing %}
|
|
54
49
|
{% endif %}
|
|
55
50
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
51
|
+
{% if showSuggestionsUi %}
|
|
52
|
+
<h2 class="govuk-heading-m">Redaction suggestions</h2>
|
|
53
|
+
{% set hasSuggestions = redactionSuggestions.length > 0 %}
|
|
54
|
+
{% if hasSuggestions %}
|
|
55
|
+
<p class="govuk-body">
|
|
56
|
+
We have suggested some common redactions and hidden these for you.
|
|
57
|
+
To make these visible again, 'Reject' each suggestion.
|
|
58
|
+
</p>
|
|
59
|
+
{% set suggestionTableRows = [] %}
|
|
60
|
+
{% for s in redactionSuggestions %}
|
|
61
|
+
{% set category = s.category %}
|
|
62
|
+
{% if s.subCategory %}
|
|
63
|
+
{% set category = category + " (" + s.subCategory + ")" %}
|
|
64
|
+
{% endif %}
|
|
65
|
+
{% set action = 'Accept' %}
|
|
66
|
+
{% if s.accepted %}
|
|
67
|
+
{% set action = 'Reject' %}
|
|
68
|
+
{% endif %}
|
|
69
|
+
{% set suggestionTableRows = (suggestionTableRows.push([
|
|
70
|
+
{text: s.text},
|
|
71
|
+
{text: category },
|
|
72
|
+
{
|
|
73
|
+
html: '<a href="#" class="govuk-link" data-redact="suggestion-reject-' + loop.index0 + '">' + action + '</a>'
|
|
74
|
+
}
|
|
75
|
+
]), suggestionTableRows) %}
|
|
76
|
+
{% endfor %}
|
|
77
|
+
{{ govukTable({
|
|
78
|
+
head: [
|
|
79
|
+
{
|
|
80
|
+
text: "Redaction suggestion"
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
text: "Type of information"
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
text: "Action"
|
|
87
|
+
}
|
|
88
|
+
],
|
|
89
|
+
rows: suggestionTableRows
|
|
90
|
+
}) }}
|
|
91
|
+
{% else %}
|
|
92
|
+
<p class="govuk-body">
|
|
93
|
+
No redaction suggestions found.
|
|
94
|
+
</p>
|
|
95
|
+
{% endif %}
|
|
96
|
+
{% endif %}
|
|
97
|
+
|
|
98
|
+
<h2 class="govuk-heading-m">Redaction preview</h2>
|
|
60
99
|
|
|
61
|
-
{{
|
|
100
|
+
{{ govukDetails({
|
|
101
|
+
summaryText: question.summaryText or "Original",
|
|
62
102
|
html: question.value,
|
|
63
|
-
id: question.fieldName + "-unredacted"
|
|
64
|
-
classes: "govuk-!-margin-top-0"
|
|
103
|
+
id: question.fieldName + "-unredacted"
|
|
65
104
|
}) }}
|
|
66
105
|
|
|
67
106
|
{{ govukTextarea({
|
|
@@ -75,7 +114,11 @@
|
|
|
75
114
|
errorMessage: errors[question.fieldName] and {
|
|
76
115
|
text: errors[question.fieldName].msg
|
|
77
116
|
},
|
|
78
|
-
classes: question.inputClasses
|
|
117
|
+
classes: question.inputClasses,
|
|
118
|
+
attributes: {
|
|
119
|
+
readonly: true
|
|
120
|
+
},
|
|
121
|
+
rows: textAreaRows or 8
|
|
79
122
|
}) }}
|
|
80
123
|
|
|
81
124
|
<div class="govuk-button-group">
|
|
@@ -108,48 +151,6 @@
|
|
|
108
151
|
}) }}
|
|
109
152
|
</form>
|
|
110
153
|
</div>
|
|
111
|
-
{% if showSuggestionsUi %}
|
|
112
|
-
<div class="govuk-grid-column-one-half">
|
|
113
|
-
<h2 class="govuk-heading-m">Redaction suggestions</h2>
|
|
114
|
-
<p class="govuk-body">
|
|
115
|
-
This section includes system suggestions for redactions.
|
|
116
|
-
Each row shows the text that is suggested for redaction and the category detected.
|
|
117
|
-
They are all redacted by default, you can reject any that should not be redacted by clicking the "Reject" link next to each suggestion.
|
|
118
|
-
</p>
|
|
119
|
-
{% set suggestionTableRows = [] %}
|
|
120
|
-
{% for s in redactionSuggestions %}
|
|
121
|
-
{% set category = s.category %}
|
|
122
|
-
{% if s.subCategory %}
|
|
123
|
-
{% set category = category + " (" + s.subCategory + ")" %}
|
|
124
|
-
{% endif %}
|
|
125
|
-
{% set action = 'Accept' %}
|
|
126
|
-
{% if s.accepted %}
|
|
127
|
-
{% set action = 'Reject' %}
|
|
128
|
-
{% endif %}
|
|
129
|
-
{% set suggestionTableRows = (suggestionTableRows.push([
|
|
130
|
-
{text: s.text},
|
|
131
|
-
{text: category },
|
|
132
|
-
{
|
|
133
|
-
html: '<a href="#" class="govuk-link" data-redact="suggestion-reject-' + loop.index0 + '">' + action + '</a>'
|
|
134
|
-
}
|
|
135
|
-
]), suggestionTableRows) %}
|
|
136
|
-
{% endfor %}
|
|
137
|
-
{{ govukTable({
|
|
138
|
-
head: [
|
|
139
|
-
{
|
|
140
|
-
text: "Text"
|
|
141
|
-
},
|
|
142
|
-
{
|
|
143
|
-
text: "Category"
|
|
144
|
-
},
|
|
145
|
-
{
|
|
146
|
-
text: "Action"
|
|
147
|
-
}
|
|
148
|
-
],
|
|
149
|
-
rows: suggestionTableRows
|
|
150
|
-
}) }}
|
|
151
|
-
</div>
|
|
152
|
-
{% endif %}
|
|
153
154
|
|
|
154
155
|
<script {% if cspNonce %}nonce={{ cspNonce }}{% endif %}>
|
|
155
156
|
/**
|
|
@@ -160,9 +161,8 @@
|
|
|
160
161
|
* @param {HTMLDivElement} unredactedText
|
|
161
162
|
* @param {HTMLAnchorElement[]} rejectLinks
|
|
162
163
|
* @param {import('@azure/ai-text-analytics').PiiEntity[]} suggestions
|
|
163
|
-
* @param {HTMLAnchorElement} toggleOriginal
|
|
164
164
|
*/
|
|
165
|
-
function registerEventListeners({textArea, redactSelectedText, redactUndo, redactUndoAll, unredactedText, rejectLinks, suggestions
|
|
165
|
+
function registerEventListeners({textArea, redactSelectedText, redactUndo, redactUndoAll, unredactedText, rejectLinks, suggestions}) {
|
|
166
166
|
const formGroup = textArea.parentElement;
|
|
167
167
|
const errorMessage = document.createElement('p');
|
|
168
168
|
errorMessage.id = '{{ question.fieldName }}-error';
|
|
@@ -230,16 +230,6 @@
|
|
|
230
230
|
}
|
|
231
231
|
});
|
|
232
232
|
}
|
|
233
|
-
toggleOriginal.addEventListener('click', (event) => {
|
|
234
|
-
event.preventDefault();
|
|
235
|
-
if (unredactedText.classList.contains('govuk-!-display-none')) {
|
|
236
|
-
unredactedText.classList.remove('govuk-!-display-none');
|
|
237
|
-
toggleOriginal.text = 'Hide';
|
|
238
|
-
} else {
|
|
239
|
-
unredactedText.classList.add('govuk-!-display-none');
|
|
240
|
-
toggleOriginal.text = 'Show';
|
|
241
|
-
}
|
|
242
|
-
});
|
|
243
233
|
}
|
|
244
234
|
|
|
245
235
|
function initRedact() {
|
|
@@ -247,8 +237,7 @@
|
|
|
247
237
|
const redactSelectedText = document.querySelector('#redact-selected-text');
|
|
248
238
|
const redactUndo = document.querySelector('#redact-undo');
|
|
249
239
|
const redactUndoAll = document.querySelector('#redact-undo-all');
|
|
250
|
-
const unredactedText = document.querySelector('#{{ question.fieldName }}-unredacted');
|
|
251
|
-
const toggleOriginal = document.querySelector('#toggle-original');
|
|
240
|
+
const unredactedText = document.querySelector('#{{ question.fieldName }}-unredacted > div');
|
|
252
241
|
const rejectLinks = document.querySelectorAll('[data-redact]');
|
|
253
242
|
{% if not redactionSuggestions %}
|
|
254
243
|
{% set redactionSuggestions = [] %}
|
|
@@ -270,11 +259,8 @@
|
|
|
270
259
|
if (!unredactedText || !(unredactedText instanceof HTMLDivElement)) {
|
|
271
260
|
return;
|
|
272
261
|
}
|
|
273
|
-
if (!toggleOriginal || !(toggleOriginal instanceof HTMLAnchorElement)) {
|
|
274
|
-
return;
|
|
275
|
-
}
|
|
276
262
|
|
|
277
|
-
registerEventListeners({textArea, redactSelectedText, redactUndo, redactUndoAll, unredactedText, rejectLinks, suggestions
|
|
263
|
+
registerEventListeners({textArea, redactSelectedText, redactUndo, redactUndoAll, unredactedText, rejectLinks, suggestions});
|
|
278
264
|
}
|
|
279
265
|
|
|
280
266
|
initRedact();
|
|
@@ -27,8 +27,18 @@ export default class TextEntryRedactQuestion extends Question {
|
|
|
27
27
|
* @param {string|undefined} [params.label] if defined this show as a label for the input and the question will just be a standard h1
|
|
28
28
|
* @param {boolean} [params.onlyShowRedactedValueForSummary] whether to only show redacted value for summary
|
|
29
29
|
* @param {boolean} [params.useRedactedFieldNameForSave] whether to use the redacted field name when saving answers
|
|
30
|
+
* @param {boolean} [params.showSuggestionsUi] use the suggestions UI for this question
|
|
31
|
+
* @param {string} [params.summaryText] summaryText to use with the details component
|
|
30
32
|
*/
|
|
31
|
-
constructor({
|
|
33
|
+
constructor({
|
|
34
|
+
textEntryCheckbox,
|
|
35
|
+
label,
|
|
36
|
+
onlyShowRedactedValueForSummary,
|
|
37
|
+
useRedactedFieldNameForSave,
|
|
38
|
+
showSuggestionsUi,
|
|
39
|
+
summaryText,
|
|
40
|
+
...params
|
|
41
|
+
}) {
|
|
32
42
|
super({
|
|
33
43
|
...params,
|
|
34
44
|
viewFolder: 'text-entry-redact'
|
|
@@ -38,6 +48,8 @@ export default class TextEntryRedactQuestion extends Question {
|
|
|
38
48
|
this.label = label;
|
|
39
49
|
this.onlyShowRedactedValueForSummary = onlyShowRedactedValueForSummary;
|
|
40
50
|
this.useRedactedFieldNameForSave = useRedactedFieldNameForSave;
|
|
51
|
+
this.showSuggestionsUi = showSuggestionsUi;
|
|
52
|
+
this.summaryText = summaryText;
|
|
41
53
|
}
|
|
42
54
|
|
|
43
55
|
async getDataToSave(req, journeyResponse) {
|
|
@@ -58,6 +70,8 @@ export default class TextEntryRedactQuestion extends Question {
|
|
|
58
70
|
viewModel.question.value = payload ? payload[viewModel.question.fieldName] : viewModel.question.value;
|
|
59
71
|
viewModel.question.valueRedacted =
|
|
60
72
|
journey.response.answers[this.fieldName + 'Redacted'] || viewModel.question.value;
|
|
73
|
+
viewModel.question.summaryText = this.summaryText;
|
|
74
|
+
viewModel.showSuggestionsUi = this.showSuggestionsUi;
|
|
61
75
|
return viewModel;
|
|
62
76
|
}
|
|
63
77
|
|
|
@@ -12,7 +12,7 @@ describe('./src/dynamic-forms/components/text-entry-redact/question.js', () => {
|
|
|
12
12
|
const HINT = 'hint';
|
|
13
13
|
const LABEL = 'A label';
|
|
14
14
|
|
|
15
|
-
function createQuestion() {
|
|
15
|
+
function createQuestion(showSuggestionsUi = false) {
|
|
16
16
|
return new TextEntryRedactQuestion({
|
|
17
17
|
title: TITLE,
|
|
18
18
|
question: QUESTION,
|
|
@@ -20,7 +20,8 @@ describe('./src/dynamic-forms/components/text-entry-redact/question.js', () => {
|
|
|
20
20
|
validators: VALIDATORS,
|
|
21
21
|
html: HTML,
|
|
22
22
|
hint: HINT,
|
|
23
|
-
label: LABEL
|
|
23
|
+
label: LABEL,
|
|
24
|
+
showSuggestionsUi
|
|
24
25
|
});
|
|
25
26
|
}
|
|
26
27
|
it('should create', () => {
|
|
@@ -72,11 +73,10 @@ describe('./src/dynamic-forms/components/text-entry-redact/question.js', () => {
|
|
|
72
73
|
const view = mockRes.render.mock.calls[0].result;
|
|
73
74
|
assert.ok(view);
|
|
74
75
|
assert.match(view, /Redaction Question/);
|
|
75
|
-
assert.match(view, /govuk-grid-column-two-thirds">\s+<form/);
|
|
76
76
|
assert.doesNotMatch(view, /Redaction suggestions/);
|
|
77
77
|
});
|
|
78
78
|
it('should render suggestions view', () => {
|
|
79
|
-
const question = createQuestion();
|
|
79
|
+
const question = createQuestion(true);
|
|
80
80
|
const section = {
|
|
81
81
|
name: 'section-name'
|
|
82
82
|
};
|
|
@@ -94,7 +94,6 @@ describe('./src/dynamic-forms/components/text-entry-redact/question.js', () => {
|
|
|
94
94
|
}
|
|
95
95
|
};
|
|
96
96
|
const customViewData = {
|
|
97
|
-
showSuggestionsUi: true,
|
|
98
97
|
layoutTemplate: 'lib/test-layout.njk',
|
|
99
98
|
question: {
|
|
100
99
|
question: 'Redaction Question',
|
|
@@ -113,8 +112,51 @@ describe('./src/dynamic-forms/components/text-entry-redact/question.js', () => {
|
|
|
113
112
|
const view = mockRes.render.mock.calls[0].result;
|
|
114
113
|
assert.ok(view);
|
|
115
114
|
assert.match(view, /Redaction Question/);
|
|
116
|
-
assert.match(view, /govuk-grid-column-one-half">\s+<form/);
|
|
117
|
-
assert.doesNotMatch(view, /govuk-grid-column-two-thirds">\s+<form/);
|
|
118
115
|
assert.match(view, /Redaction suggestions/);
|
|
116
|
+
assert.match(view, /No redaction suggestions found\./);
|
|
117
|
+
});
|
|
118
|
+
it('should render suggestions view with data', () => {
|
|
119
|
+
const question = createQuestion(true);
|
|
120
|
+
const section = {
|
|
121
|
+
name: 'section-name'
|
|
122
|
+
};
|
|
123
|
+
const journey = {
|
|
124
|
+
baseUrl: '',
|
|
125
|
+
taskListUrl: 'task',
|
|
126
|
+
journeyTemplate: 'template',
|
|
127
|
+
journeyTitle: 'title',
|
|
128
|
+
journeyId: 'manage-representations',
|
|
129
|
+
getBackLink: () => {
|
|
130
|
+
return 'back';
|
|
131
|
+
},
|
|
132
|
+
response: {
|
|
133
|
+
answers: {}
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
const customViewData = {
|
|
137
|
+
layoutTemplate: 'lib/test-layout.njk',
|
|
138
|
+
question: {
|
|
139
|
+
question: 'Redaction Question',
|
|
140
|
+
fieldName: 'field-name',
|
|
141
|
+
value: 'value',
|
|
142
|
+
valueRedacted: 'value-redacted'
|
|
143
|
+
},
|
|
144
|
+
redactionSuggestions: [
|
|
145
|
+
{ category: 'Person', suggestion: 'Test Person' },
|
|
146
|
+
{ category: 'Address', suggestion: '123 Fake Street' }
|
|
147
|
+
]
|
|
148
|
+
};
|
|
149
|
+
const nunjucks = configureNunjucks();
|
|
150
|
+
const mockRes = {
|
|
151
|
+
render: mock.fn((view, data) => nunjucks.render(view + '.njk', data))
|
|
152
|
+
};
|
|
153
|
+
const viewModel = question.prepQuestionForRendering(section, journey, customViewData);
|
|
154
|
+
question.renderAction(mockRes, viewModel);
|
|
155
|
+
assert.strictEqual(mockRes.render.mock.callCount(), 1);
|
|
156
|
+
const view = mockRes.render.mock.calls[0].result;
|
|
157
|
+
assert.ok(view);
|
|
158
|
+
assert.match(view, /Redaction Question/);
|
|
159
|
+
assert.match(view, /Redaction suggestions/);
|
|
160
|
+
assert.match(view, /We have suggested some common redactions/);
|
|
119
161
|
});
|
|
120
162
|
});
|