Skip to content

Commit a19ff54

Browse files
PUB-2960 Create style guide for Magistrates Adult Court List (#1520)
* Create style guide for Magistrates Adult Court List * Add routes file changes * Fixed failing unit tests * Fixed failing unit tests * Removed redundant empty line * Removed doh * Update following schema changes * Update code following schema changes * Revert file change * Update Magistrates Adult Court List default sensitivity to classified --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent d3f9622 commit a19ff54

17 files changed

+974
-2
lines changed
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { Response } from 'express';
2+
import { PipRequest } from '../../models/request/PipRequest';
3+
import { cloneDeep } from 'lodash';
4+
import { PublicationService } from '../../service/PublicationService';
5+
import { LocationService } from '../../service/LocationService';
6+
import { ListParseHelperService } from '../../service/ListParseHelperService';
7+
import { HttpStatusCode } from 'axios';
8+
import { formatMetaDataListType, isUnexpectedListType, isValidList, isValidListType } from '../../helpers/listHelper';
9+
import { MagistratesAdultCourtListService } from '../../service/listManipulation/MagistratesAdultCourtListService';
10+
import { formatDate } from '../../helpers/dateTimeHelper';
11+
12+
const publicationService = new PublicationService();
13+
const courtService = new LocationService();
14+
const helperService = new ListParseHelperService();
15+
const magistratesAdultCourtListService = new MagistratesAdultCourtListService();
16+
17+
const listPath = 'magistrates-adult-court-list';
18+
19+
20+
export default class MagistratesAdultCourtListController {
21+
public async get(req: PipRequest, res: Response, listType: string): Promise<void> {
22+
const artefactId = req.query.artefactId as string;
23+
const payload = await publicationService.getIndividualPublicationJson(artefactId, req.user?.['userId']);
24+
const metadata = await publicationService.getIndividualPublicationMetadata(artefactId, req.user?.['userId']);
25+
const metadataListType = formatMetaDataListType(metadata);
26+
27+
if (isValidList(payload, metadata) && isValidListType(metadataListType, listType)) {
28+
const listData = magistratesAdultCourtListService.processPayload(payload as JSON, req.lng);
29+
const returnedLocation = await courtService.getLocationById(metadata['locationId']);
30+
const locationName = courtService.findCourtName(returnedLocation, req.lng, listPath);
31+
32+
const printDate = payload['document'].data.job.printdate;
33+
const publishedDate = formatDate(MagistratesAdultCourtListController.toIsoDate(printDate), 'dd MMMM yyyy', req.lng);
34+
35+
const printStartTime = payload['document'].info?.start_time;
36+
const publishedTime = printStartTime ? helperService.publicationTimeInUkTime(printStartTime) : '';
37+
38+
res.render(`style-guide/${listPath}`, {
39+
...cloneDeep(req.i18n.getDataByLanguage(req.lng)[listPath]),
40+
...cloneDeep(req.i18n.getDataByLanguage(req.lng)['list-template']),
41+
listData: listData,
42+
contentDate: helperService.contentDateInUtcTime(metadata['contentDate'], req.lng),
43+
locationName: locationName,
44+
provenance: metadata.provenance,
45+
publishedDate,
46+
publishedTime,
47+
});
48+
} else if (
49+
payload === HttpStatusCode.NotFound ||
50+
metadata === HttpStatusCode.NotFound ||
51+
isUnexpectedListType(metadataListType, listType)
52+
) {
53+
res.render('list-not-found', req.i18n.getDataByLanguage(req.lng)['list-not-found']);
54+
} else {
55+
res.render('error', req.i18n.getDataByLanguage(req.lng).error);
56+
}
57+
}
58+
59+
private static toIsoDate(date: string): string {
60+
const dateParts = date.split('/');
61+
return new Date(Number(dateParts[2]), Number(dateParts[1]) - 1, Number(dateParts[0])).toISOString();
62+
}
63+
}

src/main/resources/listLookup.json

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -909,5 +909,25 @@
909909
"restrictedProvenances": [],
910910
"defaultSensitivity": "",
911911
"isNonStrategic": true
912+
},
913+
"MAGISTRATES_ADULT_COURT_LIST_DAILY": {
914+
"friendlyName": "Magistrates Adult Court List - Daily",
915+
"welshFriendlyName": "Rhestr Llys Ynadon Oedolion - Dyddiol",
916+
"shortenedFriendlyName": "Magistrates Adult Court List - Daily",
917+
"url": "magistrates-adult-court-list-daily",
918+
"jurisdictionTypes": ["Magistrates Court"],
919+
"restrictedProvenances": [],
920+
"defaultSensitivity": "CLASSIFIED",
921+
"isNonStrategic": false
922+
},
923+
"MAGISTRATES_ADULT_COURT_LIST_FUTURE": {
924+
"friendlyName": "Magistrates Adult Court List - Future",
925+
"welshFriendlyName": "Rhestr Llys Ynadon Oedolion – Dyfodol",
926+
"shortenedFriendlyName": "Magistrates Adult Court List - Future",
927+
"url": "magistrates-adult-court-list-future",
928+
"jurisdictionTypes": ["Magistrates Court"],
929+
"restrictedProvenances": [],
930+
"defaultSensitivity": "CLASSIFIED",
931+
"isNonStrategic": false
912932
}
913933
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
{
2+
"title": "Rhestr Safonol y Llys Ynadon",
3+
"heading": "Rhestr Safonol y Llys Ynadon ar gyfer",
4+
"listDate": "Rhestr ar gyfer",
5+
"listUpdated": "Diweddarwyd diwethaf",
6+
"at": "am",
7+
"restrictionInformationHeading": "Cyfyngiadau ar gyhoeddi neu ysgrifennu am yr achosion hyn.",
8+
"restrictionInformationP1": "Rhaid i chi wirio a oes unrhyw gyfyngiadau riportio yn berthnasol cyn cyhoeddi manylion am unrhyw un o'r achosion a restrir yma, naill ai'n ysgrifenedig, mewn darllediad neu ar y rhyngrwyd, gan gynnwys y cyfryngau cymdeithasol.",
9+
"restrictionInformationBoldText": "Byddwch yn euog o ddirmyg llys os byddwch yn cyhoeddi unrhyw wybodaeth sydd wedi'i diogelu gan gyfyngiad riportio. Gallwch gael dirwy, eich dedfrydu i garchar, neu'r ddau.",
10+
"restrictionInformationP2": "Bydd cyfyngiadau penodol a orchmynnir gan y llys yn cael eu crybwyll ar yr achosion a restrir yma.",
11+
"restrictionInformationP3": "Fodd bynnag, nid yw'r cyfyngiadau bob amser yn cael eu rhestru. Mae rhai yn berthnasol yn awtomatig. Er enghraifft, anhysbysrwydd a roddir i ddioddefwyr rhai troseddau rhywiol.",
12+
"restrictionInformationP4": "I ganfod pa gyfyngiadau riportio sy'n berthnasol ar achos penodol, cysylltwch â'r:",
13+
"restrictionBulletPoint1": "llys yn uniongyrchol",
14+
"restrictionBulletPoint2": "Gwasanaeth Llysoedd a Thribiwnlysoedd EM ar 0330 808 4407",
15+
"sittingAt": "Yn eistedd yn",
16+
"lja": "LJA:",
17+
"sessionStart": "Yn eistedd yn",
18+
"tableHeaders": [
19+
"Amser Cychwyn y Bloc",
20+
"Enw'r Diffynnydd",
21+
"Dyddiad Geni",
22+
"Cyfeiriad",
23+
"Oedran",
24+
"Hysbysydd",
25+
"Cyfeirnod yr Achos",
26+
"Cod y Drosedd",
27+
"Teitl y Drosedd",
28+
"Crynodeb o’r Drosedd"
29+
],
30+
"dataSource": "Data Source: "
31+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
{
2+
"title": "Magistrates Standard List",
3+
"heading": "Magistrates Standard List for",
4+
"listDate": "List for",
5+
"listUpdated": "Last updated",
6+
"at": "at",
7+
"restrictionInformationHeading": "Restrictions on publishing or writing about these cases",
8+
"restrictionInformationP1": "You must check if any reporting restrictions apply before publishing details on any of the cases listed here either in writing, in a broadcast or by internet, including social media.",
9+
"restrictionInformationBoldText": "You'll be in contempt of court if you publish any information which is protected by a reporting restriction. You could get a fine, prison sentence or both.",
10+
"restrictionInformationP2": "Specific restrictions ordered by the court will be mentioned on the cases listed here.",
11+
"restrictionInformationP3": "However, restrictions are not always listed. Some apply automatically. For example, anonymity given to the victims of certain sexual offences.",
12+
"restrictionInformationP4": "To find out which reporting restrictions apply on a specific case, contact:",
13+
"restrictionBulletPoint1": "the court directly",
14+
"restrictionBulletPoint2": "HM Courts and Tribunals Service on 0330 808 4407",
15+
"sittingAt": "Sitting at",
16+
"lja": "LJA:",
17+
"sessionStart": "Session start",
18+
"tableHeaders": [
19+
"Block Start",
20+
"Defendant Name",
21+
"Date of Birth",
22+
"Address",
23+
"Age",
24+
"Informant",
25+
"Case Number",
26+
"Offence Code",
27+
"Offence Title",
28+
"Offence Summary"
29+
],
30+
"dataSource": "Data Source: "
31+
}

src/main/routes/routes.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,10 @@ export default function (app: Application): void {
190190
app.get('/iac-daily-list-additional-cases', app.locals.container.cradle.iacDailyListController.get);
191191
app.get('/magistrates-public-list', app.locals.container.cradle.magistratesPublicListController.get);
192192
app.get('/magistrates-standard-list', app.locals.container.cradle.magistratesStandardListController.get);
193+
app.get('/magistrates-adult-court-list-daily', (req, res) =>
194+
app.locals.container.cradle.magistratesAdultCourtListController.get(req, res, 'magistrates-adult-court-list-daily'));
195+
app.get('/magistrates-adult-court-list-future', (req, res) =>
196+
app.locals.container.cradle.magistratesAdultCourtListController.get(req, res, 'magistrates-adult-court-list-future'));
193197

194198
//Non-Strategic Paths
195199
app.get('/cst-weekly-hearing-list', (req, res) =>
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import { ListParseHelperService } from '../ListParseHelperService';
2+
3+
const helperService = new ListParseHelperService();
4+
5+
export class MagistratesAdultCourtListService {
6+
public processPayload(payload: JSON, language: string): any[] {
7+
const results = [];
8+
payload['document'].data.job.sessions.forEach(sessionNode => {
9+
const cases = this.buildCases(sessionNode, language);
10+
results.push({
11+
lja: sessionNode.lja,
12+
courtName: sessionNode.court,
13+
courtRoom: sessionNode.room,
14+
sessionStartTime: helperService.publicationTimeInUkTime(sessionNode.sstart),
15+
cases,
16+
});
17+
});
18+
return results;
19+
}
20+
21+
private buildCases(sessionNode: any, language: string): any[] {
22+
const cases = [];
23+
sessionNode.blocks.forEach(blockNode => {
24+
blockNode.cases.forEach(caseNode => {
25+
const caseInfo = {
26+
blockStartTime: helperService.publicationTimeInUkTime(blockNode.bstart),
27+
caseNumber: caseNode.caseno,
28+
defendantName: caseNode.def_name,
29+
defendantDob: caseNode.def_dob ? caseNode.def_dob : '',
30+
defendantAge: caseNode.def_age ? caseNode.def_age : '',
31+
defendantAddress: this.formatDefendantAddress(caseNode.def_addr),
32+
informant: caseNode.inf,
33+
offence: this.processOffences(caseNode.offences, language)
34+
}
35+
cases.push(caseInfo);
36+
});
37+
});
38+
return cases;
39+
}
40+
41+
private formatDefendantAddress(addressNode: any): string {
42+
const formattedAddress = [];
43+
44+
formattedAddress.push(addressNode.line1 ? addressNode.line1 : '');
45+
formattedAddress.push(addressNode.line2 ? addressNode.line2 : '');
46+
formattedAddress.push(addressNode.line3 ? addressNode.line3 : '');
47+
formattedAddress.push(addressNode.line4 ? addressNode.line4 : '');
48+
formattedAddress.push(addressNode.line5 ? addressNode.line5 : '');
49+
formattedAddress.push(addressNode.pcode ? addressNode.pcode : '');
50+
51+
return formattedAddress.filter(line => line.trim().length > 0).join(', ');
52+
}
53+
54+
private processOffences(offencesNode: any, language: string): any {
55+
const offenceCodes = [];
56+
const offenceTitles = [];
57+
const offenceSummaries = [];
58+
59+
offencesNode.forEach(offenceNode => {
60+
offenceCodes.push(offenceNode.code);
61+
offenceTitles.push(language === 'cy' && offenceNode.cy_title ? offenceNode.cy_title : offenceNode.title);
62+
offenceSummaries.push(language === 'cy' && offenceNode.cy_sum ? offenceNode.cy_sum :offenceNode.sum);
63+
});
64+
return {
65+
offenceCode: offenceCodes.filter(line => line.trim().length > 0).join(', '),
66+
offenceTitle: offenceTitles.filter(line => line.trim().length > 0).join(', '),
67+
offenceSummary: offenceSummaries.filter(line => line.trim().length > 0).join(', ')
68+
}
69+
}
70+
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
{% from "govuk/components/table/macro.njk" import govukTable as 'govukTable' %}
2+
{% from "../macros/common-components.njk" import goBack, searchInput %}
3+
{% from "govuk/components/warning-text/macro.njk" import govukWarningText %}
4+
5+
{% extends "../list-template.njk" %}
6+
{% block pageTitle %}
7+
{{ title }}
8+
{% endblock %}
9+
10+
{% block beforeContent %}
11+
{{ super() }}
12+
{{ goBack(text = backButton, cspNonce = cspNonce) }}
13+
{% endblock %}
14+
15+
{% block content %}
16+
<div class="parent-box">
17+
<h1 id='page-heading' class="govuk-heading-l">{{ heading }} {{ locationName }}</h1>
18+
<p class="govuk-body govuk-!-font-weight-bold govuk-!-margin-bottom-1">{{ listDate }} {{ contentDate }}</p>
19+
{% if publishedTime | length %}
20+
<p class="govuk-body">{{ listUpdated }} {{ publishedDate }} {{ at }} {{ publishedTime }}</p>
21+
{% else %}
22+
<p class="govuk-body">{{ listUpdated }} {{ publishedDate }}</p>
23+
{% endif %}
24+
25+
<div class="govuk-grid restriction-list-section govuk-!-margin-bottom-5">
26+
<div class="govuk-body">
27+
<h3>{{ restrictionInformationHeading }}</h3>
28+
<p class="govuk-body">{{ restrictionInformationP1 }}</p>
29+
<div class="govuk-warning-text">
30+
<span class="govuk-warning-text__icon align-warning-icon" aria-hidden="true">!</span>
31+
<strong class="govuk-warning-text__text">
32+
<span class="govuk-warning-text__assistive">Warning</span>
33+
{{ restrictionInformationBoldText }}
34+
</strong>
35+
</div>
36+
<p class="govuk-body">{{ restrictionInformationP2 }}</p>
37+
<p class="govuk-body">{{ restrictionInformationP3 }}</p>
38+
<p class="govuk-body">{{ restrictionInformationP4 }}</p>
39+
<ul class="govuk-list govuk-list--bullet">
40+
<li>{{ restrictionBulletPoint1 }}</li>
41+
<li>{{ restrictionBulletPoint2 }}</li>
42+
</ul>
43+
</div>
44+
</div>
45+
46+
{{ searchInput(text = searchCases) }}
47+
<div class="search-area">
48+
{% set listSessionCount = 0 %}
49+
<div class="govuk-accordion" data-module="govuk-accordion" id="accordion-default-1">
50+
{% for listSession in listData %}
51+
{% set listSessionCount = listSessionCount + 1 %}
52+
<div class="govuk-accordion__section govuk-accordion__section--expanded">
53+
<div class="govuk-accordion__section-header">
54+
<h2 class="govuk-accordion__section-heading">
55+
<span class="govuk-accordion__section-button" id="accordion-default-heading-{{ listSessionCount }}">{{ listSession.courtName }}</span>
56+
</h2>
57+
<p class="govuk-body govuk-!-font-weight-bold govuk-!-margin-bottom-1">{{ sittingAt }} {{ listSession.courtRoom }}</p>
58+
<p class="govuk-body govuk-!-margin-bottom-1">{{ lja }} {{ listSession.lja }}</p>
59+
<p class="govuk-body">{{ sessionStart }} {{ listSession.sessionStartTime }}</p>
60+
</div>
61+
<div id="accordion-default-content-{{ listSessionCount }}" class="govuk-accordion__section-content" aria-labelledby="accordion-default-heading-{{ listSessionCount }}">
62+
<div class="parent-box overflow-table">
63+
<table class="govuk-table overflow-table" data-module="moj-sortable-table">
64+
<thead class="govuk-table__head">
65+
<tr class="govuk-table__row">
66+
{% for header in tableHeaders %}
67+
<th scope="col" class="govuk-table__header" aria-sort="none">{{ header }}</th>
68+
{% endfor %}
69+
</tr>
70+
</thead>
71+
<tbody class="govuk-table__body">
72+
{% for row in listSession.cases %}
73+
<tr class="govuk-table__row">
74+
<td class="govuk-table__cell" data-sort-value="{{ row.blockStartTime | timeToSortValue }}">{{ row.blockStartTime }}</td>
75+
<td class="govuk-table__cell">{{ row.defendantName }}</td>
76+
<td class="govuk-table__cell" data-sort-value="{{ row.defendantDob | dateToSortValue }}">{{ row.defendantDob }}</td>
77+
<td class="govuk-table__cell">{{ row.defendantAddress }}</td>
78+
<td class="govuk-table__cell">{{ row.defendantAge }}</td>
79+
<td class="govuk-table__cell">{{ row.informant }}</td>
80+
<td class="govuk-table__cell">{{ row.caseNumber }}</td>
81+
<td class="govuk-table__cell">{{ row.offence.offenceCode }}</td>
82+
<td class="govuk-table__cell">{{ row.offence.offenceTitle }}</td>
83+
<td class="govuk-table__cell">{{ row.offence.offenceSummary }}</td>
84+
</tr>
85+
{% endfor %}
86+
</tbody>
87+
</table>
88+
</div>
89+
</div>
90+
</div>
91+
{% endfor %}
92+
</div>
93+
<p class="govuk-body govuk-!-font-size-14 data-source">{{ dataSource }}: {{ provenance | convertDataSourceName(lng) }}</p>
94+
{{ super() }}
95+
</div>
96+
</div>
97+
{% endblock %}
98+
99+
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import sinon from 'sinon';
2+
import { PublicationService } from '../../../../main/service/PublicationService';
3+
import { testAccessibility } from '../../common/pa11yHelper';
4+
import {
5+
MagistratesAdultCourtListService
6+
} from '../../../../main/service/listManipulation/MagistratesAdultCourtListService';
7+
import { LocationService } from '../../../../main/service/LocationService';
8+
import { testArtefactJsonData, testArtefactMetadata, testLocationData } from '../../common/testData';
9+
10+
const userId = '1'
11+
const urlDailyList = '/magistrates-adult-court-list-daily';
12+
const urlFutureList = '/magistrates-adult-court-list-future';
13+
14+
const jsonData = testArtefactJsonData('magistratesAdultCourtList.json');
15+
const metadata = testArtefactMetadata()[0];
16+
const locationData = testLocationData();
17+
18+
const metadataDailyList = metadata;
19+
metadataDailyList.listType = 'MAGISTRATES_ADULT_COURT_LIST_DAILY';
20+
const metadataFutureList = metadata;
21+
metadataFutureList.listType = 'MAGISTRATES_ADULT_COURT_LIST_FUTURE';
22+
23+
sinon.stub(MagistratesAdultCourtListService.prototype, 'processPayload').resolves(jsonData);
24+
sinon.stub(LocationService.prototype, 'getLocationById').resolves(locationData);
25+
sinon.stub(PublicationService.prototype, 'getIndividualPublicationJson').resolves(jsonData);
26+
27+
const metadataStub = sinon.stub(PublicationService.prototype, 'getIndividualPublicationMetadata');
28+
metadataStub.withArgs('abc', userId).returns(metadataDailyList);
29+
metadataStub.withArgs('def', userId).returns(metadataFutureList);
30+
31+
describe('Accessibility - Magistrates Adult Court List Daily Page', () => {
32+
testAccessibility(`${urlDailyList}?artefactId=abc`);
33+
});
34+
35+
describe('Accessibility - Magistrates Adult Court List Future Page', () => {
36+
testAccessibility(`${urlFutureList}?artefactId=def`);
37+
});

0 commit comments

Comments
 (0)