Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Map 581 move question on body worn cameras to details screen #672

Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
MAP-581 moved body camera question to report details page and updated…
… unit tests
GurnankCheema committed Jan 22, 2024

Verified

This commit was signed with the committer’s verified signature.
mgyucht Miles Yucht
commit e4993922a25646504eb736b4208e26ded69b806c
21 changes: 0 additions & 21 deletions server/config/forms/evidenceForm.js
Original file line number Diff line number Diff line change
@@ -39,27 +39,6 @@ const completeSchema = joi.object({
'NO',
'NOT_KNOWN'
)('Select yes if any part of the incident captured on CCTV').alter(optionalForPartialValidation),

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is input validation and has been moved to /useOfForceDetailsForm. ts

bodyWornCamera: requiredOneOfMsg(
'YES',
'NO',
'NOT_KNOWN'
)('Select yes if any part of the incident was captured on a body-worn camera').alter(optionalForPartialValidation),
bodyWornCameraNumbers: joi
.when('bodyWornCamera', {
is: 'YES',
then: arrayOfObjects({
cameraNum: requiredStringMsg('Enter the body-worn camera number').alter(optionalForPartialValidation),
})
.min(1)
.message('Enter the body-worn camera number')
.ruleset.unique('cameraNum')
.message("Camera '{#value.cameraNum}' has already been added - remove this camera")
.required()
.alter(minZeroForPartialValidation),
otherwise: joi.any().strip(),
})
.meta({ firstFieldName: 'bodyWornCameraNumbers[0]' }),
})

module.exports = {
174 changes: 1 addition & 173 deletions server/config/forms/evidenceValidation.test.js
Original file line number Diff line number Diff line change
@@ -20,8 +20,6 @@ const validInput = () => ({
],
photographsTaken: 'true',
cctvRecording: 'YES',
bodyWornCamera: 'YES',
bodyWornCameraNumbers: [{ cameraNum: 'ABC123' }, { cameraNum: '' }],
})

describe("'complete' validation", () => {
@@ -34,8 +32,6 @@ describe("'complete' validation", () => {

expect(formResponse).toEqual({
baggedEvidence: true,
bodyWornCamera: 'YES',
bodyWornCameraNumbers: [{ cameraNum: 'ABC123' }],
cctvRecording: 'YES',
evidenceTagAndDescription: [{ description: 'A Description', evidenceTagReference: '12345' }],
photographsTaken: true,
@@ -58,10 +54,6 @@ describe("'complete' validation", () => {
href: '#cctvRecording',
text: 'Select yes if any part of the incident captured on CCTV',
},
{
href: '#bodyWornCamera',
text: 'Select yes if any part of the incident was captured on a body-worn camera',
},
])

expect(formResponse).toEqual({})
@@ -85,8 +77,6 @@ describe("'complete' validation", () => {
])

expect(formResponse).toEqual({
bodyWornCamera: 'YES',
bodyWornCameraNumbers: [{ cameraNum: 'ABC123' }],
cctvRecording: 'YES',
photographsTaken: true,
})
@@ -122,8 +112,6 @@ describe("'complete' validation", () => {

expect(formResponse).toEqual({
baggedEvidence: true,
bodyWornCamera: 'YES',
bodyWornCameraNumbers: [{ cameraNum: 'ABC123' }],
cctvRecording: 'YES',
evidenceTagAndDescription: [
{ description: 'A Description', evidenceTagReference: '12345' },
@@ -152,8 +140,6 @@ describe("'complete' validation", () => {

expect(formResponse).toEqual({
baggedEvidence: true,
bodyWornCamera: 'YES',
bodyWornCameraNumbers: [{ cameraNum: 'ABC123' }],
cctvRecording: 'YES',
photographsTaken: true,
})
@@ -177,8 +163,6 @@ describe("'complete' validation", () => {

expect(formResponse).toEqual({
baggedEvidence: true,
bodyWornCamera: 'YES',
bodyWornCameraNumbers: [{ cameraNum: 'ABC123' }],
cctvRecording: 'YES',
photographsTaken: true,
evidenceTagAndDescription: [{ description: '', evidenceTagReference: 'ref-1' }],
@@ -198,123 +182,16 @@ describe("'complete' validation", () => {

expect(formResponse).toEqual({
baggedEvidence: false,
bodyWornCamera: 'YES',
bodyWornCameraNumbers: [{ cameraNum: 'ABC123' }],
cctvRecording: 'YES',
photographsTaken: true,
})
})
})

describe('Body Worn Cameras', () => {
it('Conditional field not selected - errors and filters out camera number list', () => {
const input = { ...validInput(), bodyWornCamera: undefined, bodyWornCameraNumbers: [{ cameraNum: 'AAA123' }] }
const { errors, formResponse } = check(input)

expect(errors).toEqual([
{
href: '#bodyWornCamera',
text: 'Select yes if any part of the incident was captured on a body-worn camera',
},
])

expect(formResponse).toEqual({
baggedEvidence: true,
cctvRecording: 'YES',
evidenceTagAndDescription: [{ description: 'A Description', evidenceTagReference: '12345' }],
photographsTaken: true,
})
})

it('Conditional field must be one of allowed values', () => {
const input = { ...validInput(), bodyWornCamera: 'BOB', bodyWornCameraNumbers: [{ cameraNum: 'AAA123' }] }
const { errors, formResponse } = check(input)

expect(errors).toEqual([
{
href: '#bodyWornCamera',
text: 'Select yes if any part of the incident was captured on a body-worn camera',
},
])

expect(formResponse).toEqual({
baggedEvidence: true,
cctvRecording: 'YES',
bodyWornCamera: 'BOB',
evidenceTagAndDescription: [{ description: 'A Description', evidenceTagReference: '12345' }],
photographsTaken: true,
})
})

it('Conditional field selected: Yes, check at least one number is present', () => {
const input = { ...validInput(), bodyWornCamera: 'YES', bodyWornCameraNumbers: [] }
const { errors, formResponse } = check(input)

expect(errors).toEqual([
{
href: '#bodyWornCameraNumbers[0]',
text: 'Enter the body-worn camera number',
},
])

expect(formResponse).toEqual({
baggedEvidence: true,
bodyWornCamera: 'YES',
cctvRecording: 'YES',
evidenceTagAndDescription: [{ description: 'A Description', evidenceTagReference: '12345' }],
photographsTaken: true,
})
})

it('Conditional field selected: Yes, empty numbers are ignored', () => {
const input = {
...validInput(),
bodyWornCamera: 'YES',
bodyWornCameraNumbers: [{ cameraNum: 'AAA' }, { cameraNum: '' }, { cameraNum: 'BBB' }],
}
const { errors, formResponse } = check(input)

expect(errors).toEqual([])

expect(formResponse).toEqual({
baggedEvidence: true,
bodyWornCamera: 'YES',
bodyWornCameraNumbers: [{ cameraNum: 'AAA' }, { cameraNum: 'BBB' }],
cctvRecording: 'YES',
evidenceTagAndDescription: [{ description: 'A Description', evidenceTagReference: '12345' }],
photographsTaken: true,
})
})

it('Duplicate camera numbers are rejected', () => {
const input = {
...validInput(),
bodyWornCamera: 'YES',
bodyWornCameraNumbers: [{ cameraNum: 'AAA' }, { cameraNum: '' }, { cameraNum: 'AAA' }],
}
const { errors, formResponse } = check(input)

expect(errors).toEqual([
{
href: '#bodyWornCameraNumbers[1]',
text: "Camera 'AAA' has already been added - remove this camera",
},
])

expect(formResponse).toEqual({
baggedEvidence: true,
bodyWornCamera: 'YES',
bodyWornCameraNumbers: [{ cameraNum: 'AAA' }, { cameraNum: 'AAA' }],
cctvRecording: 'YES',
evidenceTagAndDescription: [{ description: 'A Description', evidenceTagReference: '12345' }],
photographsTaken: true,
})
})

describe('Evidence tags', () => {
it('Duplicate evidence tags are rejected', () => {
const input = {
...validInput(),
bodyWornCamera: 'NO',
evidenceTagAndDescription: [
{ description: 'D2', evidenceTagReference: '12345' },
{ description: 'D1', evidenceTagReference: '12345' },
@@ -331,7 +208,6 @@ describe("'complete' validation", () => {

expect(formResponse).toEqual({
baggedEvidence: true,
bodyWornCamera: 'NO',
cctvRecording: 'YES',
evidenceTagAndDescription: [
{ description: 'D2', evidenceTagReference: '12345' },
@@ -340,45 +216,6 @@ describe("'complete' validation", () => {
photographsTaken: true,
})
})

it('Conditional field selected: Yes, unknown fields are ignored, known fields are trimmed', () => {
const input = {
...validInput(),
bodyWornCamera: 'YES',
bodyWornCameraNumbers: [{ cameraNum: ' AAA ', age: '29' }, { cameraNum: '' }, { cameraNum: 'BBB' }],
}
const { errors, formResponse } = check(input)

expect(errors).toEqual([])

expect(formResponse).toEqual({
baggedEvidence: true,
bodyWornCamera: 'YES',
bodyWornCameraNumbers: [{ cameraNum: 'AAA' }, { cameraNum: 'BBB' }],
cctvRecording: 'YES',
evidenceTagAndDescription: [{ description: 'A Description', evidenceTagReference: '12345' }],
photographsTaken: true,
})
})

it('Conditional field selected: No, numbers are not required', () => {
const input = {
...validInput(),
bodyWornCamera: 'NO',
bodyWornCameraNumbers: [{ cameraNum: 'AAA' }, { cameraNum: '' }, { cameraNum: 'AAA' }],
}
const { errors, formResponse } = check(input)

expect(errors).toEqual([])

expect(formResponse).toEqual({
baggedEvidence: true,
bodyWornCamera: 'NO',
cctvRecording: 'YES',
evidenceTagAndDescription: [{ description: 'A Description', evidenceTagReference: '12345' }],
photographsTaken: true,
})
})
})
})

@@ -394,12 +231,6 @@ describe("'partial' validation", () => {
const { errors, formResponse } = check(validInput())
expect(formResponse).toEqual({
baggedEvidence: true,
bodyWornCamera: 'YES',
bodyWornCameraNumbers: [
{
cameraNum: 'ABC123',
},
],
cctvRecording: 'YES',
evidenceTagAndDescription: [
{
@@ -416,12 +247,9 @@ describe("'partial' validation", () => {
const { errors, formResponse } = check({
baggedEvidence: 'true',
evidenceTagAndDescription: [],
bodyWornCamera: 'YES',
bodyWornCameraNumbers: [],
})
expect(formResponse).toEqual({
baggedEvidence: true,
bodyWornCamera: 'YES',
})
expect(errors).toEqual([])
})
32 changes: 31 additions & 1 deletion server/config/forms/useOfForceDetailsForm.js
Original file line number Diff line number Diff line change
@@ -2,13 +2,43 @@ const joi = require('@hapi/joi')
const { validations } = require('./validations')
const { buildValidationSpec } = require('../../services/validation')

const { requiredBooleanMsg, requiredOneOfMsg, requiredIntegerRangeMsg, optionalForPartialValidation } = validations
const {
requiredBooleanMsg,
requiredOneOfMsg,
requiredIntegerRangeMsg,
optionalForPartialValidation,
arrayOfObjects,
requiredStringMsg,
minZeroForPartialValidation,
} = validations

const completeSchema = joi.object({
positiveCommunication: requiredBooleanMsg('Select yes if positive communication was used').alter(
optionalForPartialValidation
),

bodyWornCamera: requiredOneOfMsg(
'YES',
'NO',
'NOT_KNOWN'
)('Select yes if any part of the incident was captured on a body-worn camera').alter(optionalForPartialValidation),

bodyWornCameraNumbers: joi
.when('bodyWornCamera', {
is: 'YES',
then: arrayOfObjects({
cameraNum: requiredStringMsg('Enter the body-worn camera number').alter(optionalForPartialValidation),
})
.min(1)
.message('Enter the body-worn camera number')
.ruleset.unique('cameraNum')
.message("Camera '{#value.cameraNum}' has already been added - remove this camera")
.required()
.alter(minZeroForPartialValidation),
otherwise: joi.any().strip(),
})
.meta({ firstFieldName: 'bodyWornCameraNumbers[0]' }),

personalProtectionTechniques: requiredBooleanMsg('Select yes if any personal protection techniques were used').alter(
optionalForPartialValidation
),
146 changes: 144 additions & 2 deletions server/config/forms/useOfForceDetailsValidation.test.js
Original file line number Diff line number Diff line change
@@ -11,6 +11,7 @@ let validInput = {}
beforeEach(() => {
validInput = {
positiveCommunication: 'true',
bodyWornCamera: 'NO',
personalProtectionTechniques: 'true',
batonDrawn: 'true',
batonUsed: 'true',
@@ -38,6 +39,7 @@ describe('complete schema', () => {

expect(formResponse).toEqual({
positiveCommunication: true,
bodyWornCamera: 'NO',
personalProtectionTechniques: true,
batonDrawn: true,
batonUsed: true,
@@ -54,7 +56,7 @@ describe('complete schema', () => {
})
})

it('Should return 9 error massages if no input field is completed', () => {
it('Should return 10 error massages if no input field is completed', () => {
const input = {}
const { errors, formResponse } = check(input)

@@ -63,6 +65,10 @@ describe('complete schema', () => {
href: '#positiveCommunication',
text: 'Select yes if positive communication was used',
},
{
href: '#bodyWornCamera',
text: 'Select yes if any part of the incident was captured on a body-worn camera',
},
{
href: '#personalProtectionTechniques',
text: 'Select yes if any personal protection techniques were used',
@@ -97,7 +103,7 @@ describe('complete schema', () => {
},
])

expect(errors.length).toEqual(9)
expect(errors.length).toEqual(10)

expect(formResponse).toEqual({})
})
@@ -118,6 +124,141 @@ describe('complete schema', () => {
])
})

it("Not selecting an option for 'body worn cameras' returns a validation error message", () => {
const input = {
...validInput,
bodyWornCamera: undefined,
}
const { errors } = check(input)
expect(errors).toEqual([
{
href: '#bodyWornCamera',
text: 'Select yes if any part of the incident was captured on a body-worn camera',
},
])
})

it('Not adding camera numbers but selecting YES for bodyWornCameras returns validation error messages', () => {
validInput.bodyWornCamera = 'YES'
const { errors } = check(validInput)

expect(errors).toEqual([{ href: '#bodyWornCameraNumbers[0]', text: '"bodyWornCameraNumbers" is required' }])
})

it('Should return validation error if more than one body-worn camera with same identifier', () => {
validInput.bodyWornCamera = 'YES'
validInput.bodyWornCameraNumbers = [{ cameraNum: '1' }, { cameraNum: '1' }]
const { errors } = check(validInput)

expect(errors).toEqual([
{ href: '#bodyWornCameraNumbers[1]', text: "Camera '1' has already been added - remove this camera" },
])
})

it('Should not return validation error if all body-worn camera identifiers are unique', () => {
validInput.bodyWornCamera = 'YES'
validInput.bodyWornCameraNumbers = [{ cameraNum: '1' }, { cameraNum: '2' }]
const { errors } = check(validInput)

expect(errors).toEqual([])
})

it('Should trim empty-string body-worn camera identifiers', () => {
validInput.bodyWornCamera = 'YES'
validInput.bodyWornCameraNumbers = [
{ cameraNum: ' AAA ', age: '29' },
{ cameraNum: '' },
{ cameraNum: 'BBB' },
]

const { errors, formResponse } = check(validInput)

expect(errors).toEqual([])

expect(formResponse).toEqual({
batonDrawn: true,
batonUsed: true,
bodyWornCamera: 'YES',
bodyWornCameraNumbers: [
{
cameraNum: 'AAA',
},
{
cameraNum: 'BBB',
},
],
escortingHold: true,
guidingHold: true,
guidingHoldOfficersInvolved: 2,
handcuffsApplied: true,
painInducingTechniques: true,
painInducingTechniquesUsed: ['FINAL_LOCK_FLEXION', 'THUMB_LOCK'],
pavaDrawn: true,
pavaUsed: true,
personalProtectionTechniques: true,
positiveCommunication: true,
restraint: true,
restraintPositions: ['STANDING', 'FACE_DOWN'],
})
})

it('Body-worn camera identifiers are not required when bodyWornCamera is NO', () => {
validInput.bodyWornCamer = 'NO'
validInput.bodyWornCameraNumbers = [{ cameraNum: 'AAA' }, { cameraNum: '' }, { cameraNum: 'AAA' }]

const { errors, formResponse } = check(validInput)

expect(errors).toEqual([])

expect(formResponse).toEqual({
batonDrawn: true,
batonUsed: true,
bodyWornCamera: 'NO',
escortingHold: true,
guidingHold: true,
guidingHoldOfficersInvolved: 2,
handcuffsApplied: true,
painInducingTechniques: true,
painInducingTechniquesUsed: ['FINAL_LOCK_FLEXION', 'THUMB_LOCK'],
pavaDrawn: true,
pavaUsed: true,
personalProtectionTechniques: true,
positiveCommunication: true,
restraint: true,
restraintPositions: ['STANDING', 'FACE_DOWN'],
})
})

it('Body worn camera field must be one of allowed values', () => {
validInput.bodyWornCamera = 'SOMETHING_RANDOM'
validInput.bodyWornCameraNumbers = [{ cameraNum: 'AAA' }, { cameraNum: '' }, { cameraNum: 'AAA' }]
const { errors, formResponse } = check(validInput)

expect(errors).toEqual([
{
href: '#bodyWornCamera',
text: 'Select yes if any part of the incident was captured on a body-worn camera',
},
])

expect(formResponse).toEqual({
batonDrawn: true,
batonUsed: true,
bodyWornCamera: 'SOMETHING_RANDOM',
escortingHold: true,
guidingHold: true,
guidingHoldOfficersInvolved: 2,
handcuffsApplied: true,
painInducingTechniques: true,
painInducingTechniquesUsed: ['FINAL_LOCK_FLEXION', 'THUMB_LOCK'],
pavaDrawn: true,
pavaUsed: true,
personalProtectionTechniques: true,
positiveCommunication: true,
restraint: true,
restraintPositions: ['STANDING', 'FACE_DOWN'],
})
})
it("Not selecting an option for 'personal protection techniques' returns a validation error message", () => {
const input = {
...validInput,
@@ -408,6 +549,7 @@ describe('partial schema', () => {

expect(formResponse).toEqual({
positiveCommunication: true,
bodyWornCamera: 'NO',
personalProtectionTechniques: true,
batonDrawn: true,
batonUsed: true,
4 changes: 2 additions & 2 deletions server/data/UseOfForceReport.ts
Original file line number Diff line number Diff line change
@@ -7,6 +7,8 @@ export type IncidentDetails = {

export type UseOfForceDetails = {
positiveCommunication: boolean
bodyWornCamera: string
bodyWornCameraNumbers: { cameraNum: string }[]
personalProtectionTechniques: boolean
batonDrawn: boolean
batonUsed: boolean
@@ -46,8 +48,6 @@ export type Evidence = {
baggedEvidence: boolean
photographsTaken: boolean
cctvRecording: string
bodyWornCamera: string
bodyWornCameraNumbers: { cameraNum: string }[]
}

export type InvolvedStaff = {
11 changes: 6 additions & 5 deletions server/routes/creatingReports/createReport.test.ts
Original file line number Diff line number Diff line change
@@ -38,6 +38,8 @@ describe('GET /section/form', () => {
})

const validUseOfForceDetailsRequest = {
bodyWornCamera: 'YES',
bodyWornCameraNumbers: [{ cameraNum: 'ABC123' }],
positiveCommunication: 'false',
personalProtectionTechniques: 'false',
batonDrawn: 'false',
@@ -55,6 +57,8 @@ const validUseofForceDetailUpdate = [
1,
'useOfForceDetails',
{
bodyWornCamera: 'YES',
bodyWornCameraNumbers: [{ cameraNum: 'ABC123' }],
batonDrawn: false,
guidingHold: false,
escortingHold: false,
@@ -107,7 +111,7 @@ describe('POST save and return to tasklist', () => {
test('Submitting invalid update is allowed', () => {
return request(app)
.post(`/report/1/use-of-force-details`)
.send({ ...validUseOfForceDetailsRequest, batonDrawn: null, submitType: 'save-and-return' })
.send({ ...validUseOfForceDetailsRequest, batonDrawn: null, bodyWornCamera: null, submitType: 'save-and-return' })
.expect(302)
.expect('Location', '/report/1/report-use-of-force')
.expect(() => {
@@ -132,6 +136,7 @@ describe('POST save and return to tasklist', () => {
...validUseOfForceDetailsRequest,
restraint: 'true',
restraintPositions: ['not a valid value'],
bodyWornCamera: ['another invalid input'],
submitType: 'save-and-return',
})
.expect(302)
@@ -186,8 +191,6 @@ describe('Submitting evidence page', () => {
.send({
submitType,
baggedEvidence: 'true',
bodyWornCamera: 'YES',
bodyWornCameraNumbers: [{ cameraNum: 'ABC123' }],
cctvRecording: 'YES',
evidenceTagAndDescription: [{ description: 'A Description', evidenceTagReference: '12345' }],
photographsTaken: 'true',
@@ -199,8 +202,6 @@ describe('Submitting evidence page', () => {

expect(draftReportService.process).toBeCalledWith(user, 1, 'evidence', {
baggedEvidence: true,
bodyWornCamera: 'YES',
bodyWornCameraNumbers: [{ cameraNum: 'ABC123' }],
cctvRecording: 'YES',
evidenceTagAndDescription: [{ description: 'A Description', evidenceTagReference: '12345' }],
photographsTaken: true,
4 changes: 2 additions & 2 deletions server/services/drafts/reportStatusChecker.test.ts
Original file line number Diff line number Diff line change
@@ -22,13 +22,13 @@ describe('statusCheck', () => {
positiveCommunication: false,
personalProtectionTechniques: true,
painInducingTechniques: false,
bodyWornCamera: 'YES',
bodyWornCameraNumbers: [{ cameraNum: '1111' }, { cameraNum: '2222' }],
},
evidence: {
cctvRecording: 'NO',
baggedEvidence: true,
bodyWornCamera: 'YES',
photographsTaken: false,
bodyWornCameraNumbers: [{ cameraNum: '1111' }, { cameraNum: '2222' }],
evidenceTagAndDescription: [
{ description: 'aaaaa', evidenceTagReference: '1111' },
{ description: 'bbbb', evidenceTagReference: '2222' },
1 change: 1 addition & 0 deletions server/services/reportDetailBuilder.test.ts
Original file line number Diff line number Diff line change
@@ -107,6 +107,7 @@ describe('Build details', () => {
reporterName: 'A User',
submittedDate: new Date('2015-03-25T12:00:00.000Z'),
useOfForceDetails: {
bodyCameras: undefined,
batonDrawn: undefined,
controlAndRestraintUsed: undefined,
guidingHoldUsed: undefined,
10 changes: 5 additions & 5 deletions server/services/reportSummary.ts
Original file line number Diff line number Diff line change
@@ -68,6 +68,11 @@ const createUseOfForceDetails = (

painInducingTechniques: getPainInducingTechniques(details),
handcuffsApplied: details.handcuffsApplied,
bodyCameras: whenPresent(details.bodyWornCamera, value =>
value === BodyWornCameras.YES.value
? `${YES} - ${extractCommaSeparatedList('cameraNum', details.bodyWornCameraNumbers)}` || YES
: toLabel(BodyWornCameras, value)
),
}
}

@@ -108,11 +113,6 @@ const createEvidence = (evidence: Partial<Evidence> = {}) => {
evidenceBaggedTagged: baggedAndTaggedEvidence(evidence.evidenceTagAndDescription, evidence.baggedEvidence),
photographs: evidence.photographsTaken,
cctv: toLabel(Cctv, evidence.cctvRecording),
bodyCameras: whenPresent(evidence.bodyWornCamera, value =>
value === Cctv.YES.value
? `${YES} - ${extractCommaSeparatedList('cameraNum', evidence.bodyWornCameraNumbers)}` || YES
: toLabel(BodyWornCameras, value)
),
}
}

103 changes: 1 addition & 102 deletions server/views/formPages/incident/evidence.html
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
{% extends "../formTemplate.html" %}
{% from "govuk/components/input/macro.njk" import govukInput %}
{% from "govuk/components/radios/macro.njk" import govukRadios %}
{% from "govuk/components/select/macro.njk" import govukSelect %}
{% from "govuk/components/checkboxes/macro.njk" import govukCheckboxes %}
{% import "../incidentMacros.njk" as incidentMacro %}
{% from "govuk/components/fieldset/macro.njk" import govukFieldset %}
{% from "govuk/components/button/macro.njk" import govukButton %}
@@ -195,105 +193,6 @@ <h1 class="govuk-heading-xl mainHeading">{{ pageTitle }} </h1>
})
}}
</div>

<!-- Q4 -->
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this has moved to the useOfForceDetails.html


<div class="govuk-!-margin-bottom-9
{% if errors | findError('bodyWornCamera') %}
govuk-form-group--error
{% set cameraErrMsg = errors | findError('bodyWornCamera') %}
{% endif %}">
<fieldset class="govuk-fieldset">
<legend class="govuk-fieldset__legend ">
Was any part of the incident captured on a body-worn camera?
</legend>
<span id="cameraErrMsg" class="govuk-error-message">
<span class="govuk-visually-hidden">Error:</span>
{{ cameraErrMsg.text}}
</span>

<div class="govuk-radios" data-module="govuk-radios">
<div class=" govuk-radios">
<div class="govuk-radios__item">
<input class="govuk-radios__input" id="bodyWornCamera" name="bodyWornCamera" type="radio" value="YES" data-aria-controls="body-worn-camera-conditional" {% if data.bodyWornCamera === 'YES' %} checked="checked" {% endif %}>
<label class="govuk-label govuk-radios__label" for="bodyWornCamera">
Yes
</label>
</div>
<!-- hidden panel starts here -->
<div class="govuk-radios__conditional govuk-radios__conditional--hidden" id="body-worn-camera-conditional">

<!-- add another starts here -->
<div class="add-another-camera" id="bodyWornCameraNumbers">
{% macro addCameraNumber(index, value, showRemove) %}
{% call govukFieldset({ classes: 'add-another__item' }) %}
<div class="govuk-grid-row">
<div class="govuk-grid-column-one-third" id="bodyWornCameraNumbers[{{index}}]">
{{
govukInput({
label: {
html: 'Camera number'
},
id: 'bodyWornCameraNumbers[' + index + '][cameraNum]',
name: 'bodyWornCameraNumbers[' + index + '][cameraNum]',
value: value,
errorMessage: errors | findErrors(['bodyWornCameraNumbers[' + index + ']', 'bodyWornCameraNumbers[' + index + '][cameraNum]']),
attributes: {
'data-name': 'bodyWornCameraNumbers[%index%][cameraNum]',
'data-id': 'bodyWornCameraNumbers[%index%][cameraNum]'
}
})
}}
</div>
<div class="govuk-grid-column-one-third remove-button-container govuk-!-margin-top-6">
{% if showRemove %}
<button type="button" class="govuk-button govuk-button--secondary add-another__remove-button">
Remove
</button>
{% endif %}
</div>
</div>
{% endcall %}
{% endmacro%}

{% for camera in data.bodyWornCameraNumbers %}
{{ addCameraNumber(index = loop.index0, value = camera.cameraNum, showRemove = loop.length != 1) }}
{% else %}
{{ addCameraNumber(index = 0, value = null, showRemove = false) }}
{% endfor %}
<div class="button-action">
{{
govukButton({
text: 'Add another',
classes: 'govuk-button--secondary add-another__add-button govuk-!-margin-bottom-4',
attributes: { 'data-qa-add-another-camera': true }
})
}}
</div>
</div>
<!-- add another ends here -->
</div>
<!-- hidden panel end -->

<div class="govuk-radios__item">
<input class="govuk-radios__input" id="body-worn-camera-no" name="bodyWornCamera" type="radio" value="NO" {% if data.bodyWornCamera === 'NO' %} checked="checked" {% endif %}>
<label class="govuk-label govuk-radios__label" for="body-worn-camera-no">
No
</label>
</div>
<div class="govuk-radios__item">
<input class="govuk-radios__input" id="body-worn-camera-notKnown" name="bodyWornCamera" type="radio" value="NOT_KNOWN" {% if data.bodyWornCamera === 'NOT_KNOWN' %} checked="checked" {% endif %}>
<label class="govuk-label govuk-radios__label" for="body-worn-camera-notKnown">
Not known
</label>
</div>
</div>
</div>
<!-- end of radios -->
</fieldset>
</div>
<!-- end of this Q4 component -->

</div>
<!-- end of govuk-grid-column-full-->
</div>
@@ -302,4 +201,4 @@ <h1 class="govuk-heading-xl mainHeading">{{ pageTitle }} </h1>

{% block script %}
<script src="/assets/add-another-evidence.js"></script>
{% endblock %}
{% endblock %}
28 changes: 20 additions & 8 deletions server/views/formPages/incident/useOfForceDetails.html
Original file line number Diff line number Diff line change
@@ -31,7 +31,15 @@ <h1 class="govuk-heading-xl mainHeading">{{ pageTitle }}</h1>
errorMessage: errors | findError('positiveCommunication')
})}}

<!-- Q2 -->
<!-- Q2 new question body-worn camera question which was previously in the evidence page -->
{{ incidentMacro.radioWithNestedTextBox({
text: "Was any part of the incident captured on a body-worn camera?",
name: "bodyWornCamera",
value: data,
errors: errors
}) }}

<!-- Q3 -->
{{ incidentMacro.radio( {
text: "Were any personal protection techniques used?",
name: "personalProtectionTechniques",
@@ -40,7 +48,7 @@ <h1 class="govuk-heading-xl mainHeading">{{ pageTitle }}</h1>
errorMessage: errors | findError('personalProtectionTechniques')
})}}

<!-- Q3 -->
<!-- Q4 -->
{{ incidentMacro.radiosWithNestedRadios({
primaryQuestion: {
text: "Was a baton drawn by anyone during this incident?",
@@ -61,7 +69,7 @@ <h1 class="govuk-heading-xl mainHeading">{{ pageTitle }}</h1>
}
)}}

<!-- Q4 -->
<!-- Q5 -->
{{ incidentMacro.radiosWithNestedRadios({
primaryQuestion: {
text: "Was PAVA drawn by anyone during this incident?",
@@ -83,7 +91,7 @@ <h1 class="govuk-heading-xl mainHeading">{{ pageTitle }}</h1>
}
)}}

<!-- Q5 -->
<!-- Q6 -->
{{ incidentMacro.radiosWithNestedRadios({
primaryQuestion: {
text: "Was a guiding hold used?",
@@ -112,7 +120,7 @@ <h1 class="govuk-heading-xl mainHeading">{{ pageTitle }}</h1>
}
)}}

<!-- Q6 -->
<!-- Q7 -->
{{ incidentMacro.radio( {
text: "Was an escorting hold used?",
name: "escortingHold",
@@ -121,7 +129,7 @@ <h1 class="govuk-heading-xl mainHeading">{{ pageTitle }}</h1>
errorMessage: errors | findError('escortingHold')
})}}

<!-- Q7 -->
<!-- Q8 -->
<div id="control-and-restraint">
{{ incidentMacro.radiosWithNestedCheckboxes({
primaryQuestion: {
@@ -142,7 +150,7 @@ <h1 class="govuk-heading-xl mainHeading">{{ pageTitle }}</h1>
}}
</div>

<!-- Q8 -->
<!-- Q9 -->
<div id="pain-inducing-techniques">
{{ incidentMacro.radiosWithNestedCheckboxes({
primaryQuestion: {
@@ -163,7 +171,7 @@ <h1 class="govuk-heading-xl mainHeading">{{ pageTitle }}</h1>
}}
</div>

<!-- Q9 -->
<!-- 10 -->
{{ incidentMacro.radio( {
text: "Were handcuffs applied?",
name: "handcuffsApplied",
@@ -174,4 +182,8 @@ <h1 class="govuk-heading-xl mainHeading">{{ pageTitle }}</h1>
</div>
</div>

{% endblock %}

{% block script %}
<script src="/assets/add-another-evidence.js"></script>
{% endblock %}
106 changes: 106 additions & 0 deletions server/views/formPages/incidentMacros.njk
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
{% from "govuk/components/input/macro.njk" import govukInput %}
{% from "govuk/components/fieldset/macro.njk" import govukFieldset %}
{% from "govuk/components/button/macro.njk" import govukButton %}

{% macro radio(question) %}
{% if question.errorMessage.text %}
{% set govukFormGroupError = 'govuk-form-group--error' %}
@@ -48,6 +52,108 @@
{% endmacro %}


{% macro radioWithNestedTextBox(question) %}
<div class="govuk-!-margin-bottom-9
{% if question.errors | findError('bodyWornCamera') %}
govuk-form-group--error
{% set cameraErrMsg = question.errors | findError('bodyWornCamera') %}
{% endif %}">

<fieldset class="govuk-fieldset">
<legend class="govuk-fieldset__legend ">
Was any part of the incident captured on a body-worn camera?
</legend>
<span id="cameraErrMsg" class="govuk-error-message">
<span class="govuk-visually-hidden">Error:</span>
{{ cameraErrMsg.text }}
</span>

<div class="govuk-radios" data-module="govuk-radios">
<div class=" govuk-radios">
<div class="govuk-radios__item">
<input class="govuk-radios__input" id="bodyWornCamera" name="bodyWornCamera"
type="radio" value="YES" data-aria-controls="body-worn-camera-conditional"
{% if question.value.bodyWornCamera === 'YES' %} checked="checked" {% endif %}>
<label class="govuk-label govuk-radios__label" for="bodyWornCamera">
Yes
</label>
</div>
<!-- hidden panel starts here -->
<div class="govuk-radios__conditional govuk-radios__conditional--hidden" id="body-worn-camera-conditional">

<!-- add another starts here -->
<div class="add-another-camera" id="bodyWornCameraNumbers">

{% for camera in question.value.bodyWornCameraNumbers %}
{{ addCameraNumber(question.errors, index = loop.index0, value = camera.cameraNum, showRemove = loop.length != 1) }}
{% else %}
{{ addCameraNumber(question.errors, index = 0, value = null, showRemove = false) }}
{% endfor %}

<div class="button-action">
{{
govukButton({
text: 'Add another',
classes: 'govuk-button--secondary add-another__add-button govuk-!-margin-bottom-4',
attributes: { 'data-qa-add-another-camera': true }
})
}}
</div>
</div>
<!-- add another ends here -->
</div>
<!-- hidden panel end -->

<div class="govuk-radios__item">
<input class="govuk-radios__input" id="body-worn-camera-no" name="bodyWornCamera" type="radio" value="NO" {% if question.value.bodyWornCamera === 'NO' %} checked="checked" {% endif %}>
<label class="govuk-label govuk-radios__label" for="body-worn-camera-no">
No
</label>
</div>
<div class="govuk-radios__item">
<input class="govuk-radios__input" id="body-worn-camera-notKnown" name="bodyWornCamera" type="radio" value="NOT_KNOWN" {% if question.value.bodyWornCamera === 'NOT_KNOWN' %} checked="checked" {% endif %}>
<label class="govuk-label govuk-radios__label" for="body-worn-camera-notKnown">
Not known
</label>
</div>
</div>
</div>
<!-- end of radios -->
</fieldset>
</div>
{% endmacro %}

{% macro addCameraNumber(errors, index, value, showRemove) %}
{% call govukFieldset({ classes: 'add-another__item' }) %}
<div class="govuk-grid-row">
<div class="govuk-grid-column-one-third" id="bodyWornCameraNumbers[{{index}}]">
{{
govukInput({
errorMessage: errors | findErrors(['bodyWornCameraNumbers[' + index + ']', 'bodyWornCameraNumbers[' + index + '][cameraNum]']),
label: {
html: 'Camera number'
},
id: 'bodyWornCameraNumbers[' + index + '][cameraNum]',
name: 'bodyWornCameraNumbers[' + index + '][cameraNum]',
value: value,
attributes: {
'data-name': 'bodyWornCameraNumbers[%index%][cameraNum]',
'data-id': 'bodyWornCameraNumbers[%index%][cameraNum]'
}
})
}}
</div>
<div class="govuk-grid-column-one-third remove-button-container govuk-!-margin-top-6">
{% if showRemove %}
<button type="button" class="govuk-button govuk-button--secondary add-another__remove-button">
Remove
</button>
{% endif %}
</div>
</div>
{% endcall %}
{% endmacro%}

{% macro radiosWithNestedRadios(question) %}

{% set radio_button_orientation = 'govuk-radios--inline' if question.orientation === 'inline' else 'govuk-radios'%}
21 changes: 11 additions & 10 deletions server/views/pages/reportDetailMacro.njk
Original file line number Diff line number Diff line change
@@ -180,6 +180,16 @@
print: print
})
}}

{{
reportDetailsMacros.tableRow({
label: 'Was any part of the incident captured on a body-worn camera?',
'data-qa': 'bodyCameras',
dataValue: data.useOfForceDetails.bodyCameras | capitalize,
print: print
})
}}

{{
reportDetailsMacros.tableRow({
label: 'Were any personal protection techniques used?',
@@ -357,13 +367,4 @@
print: print
})
}}
{{
reportDetailsMacros.tableRow({
label: 'Was any part of the incident captured on a body-worn camera?',
'data-qa': 'bodyCameras',
dataValue: data.evidence.bodyCameras | capitalize,
print: print
})
}}

{% endmacro %}
{% endmacro %}