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

chore(j-s): add slack lock for daily court hearing arrangements #17468

Merged
merged 14 commits into from
Jan 14, 2025
Merged
Show file tree
Hide file tree
Changes from 12 commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,20 @@ export class InternalCaseController {
return this.internalCaseService.archive()
}

@Post('cases/postHearingArrangements')
async postHearingArrangements(
@Body() { date }: { date: Date },
): Promise<void> {
this.logger.debug(
`Post internal summary of all cases that have court hearing arrangement at ${date}`,
)

const cases = await this.internalCaseService.getCaseHearingArrangements(
new Date(date),
)
await this.eventService.postDailyHearingArrangementEvents(date, cases)
}

@Get('cases/indictments/defendant/:defendantNationalId')
@ApiOkResponse({
type: Case,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,29 @@ export class InternalCaseService {
return { caseArchived: true }
}

async getCaseHearingArrangements(date: Date): Promise<Case[]> {
const startOfDay = new Date(date.setHours(0, 0, 0, 0))
const endOfDay = new Date(date.setHours(23, 59, 59, 999))

return this.caseModel.findAll({
include: [
{
model: DateLog,
as: 'dateLogs',
where: {
date_type: ['ARRAIGNMENT_DATE', 'COURT_DATE'],
date: {
[Op.gte]: startOfDay,
[Op.lte]: endOfDay,
},
},
required: true,
},
],
order: [[{ model: DateLog, as: 'dateLogs' }, 'date', 'ASC']],
})
}

async deliverProsecutorToCourt(
theCase: Case,
user: TUser,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ import type { User as TUser } from '@island.is/judicial-system/types'
import {
CaseState,
CaseType,
DefendantEventType,
indictmentCases,
investigationCases,
restrictionCases,
Expand All @@ -39,7 +38,6 @@ import {

import { nowFactory } from '../../factories'
import { defenderRule, prisonSystemStaffRule } from '../../guards'
import { DefendantService } from '../defendant'
import { EventService } from '../event'
import { User } from '../user'
import { TransitionCaseDto } from './dto/transitionCase.dto'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,51 @@ export class EventService {
}
}

async postDailyHearingArrangementEvents(date: Date, cases: Case[]) {
const title = `:judge: Fyrirtökur ${formatDate(date)}`

const arrangementTexts = cases.map((theCase) => {
return `>${theCase.courtCaseNumber}: ${
formatDate(
DateLog.courtDate(theCase.dateLogs)?.date ??
DateLog.arraignmentDate(theCase.dateLogs)?.date,
'p',
) ?? date
}`
})

const arrangementSummary =
arrangementTexts.length > 0
? arrangementTexts.join('\n')
: '>Engar fyrirtökur á dagskrá'

try {
if (!this.config.url) {
return
}

await fetch(`${this.config.url}`, {
method: 'POST',
headers: { 'Content-type': 'application/json' },
body: JSON.stringify({
blocks: [
{
type: 'section',
text: {
type: 'mrkdwn',
text: `*${title}*\n${arrangementSummary}`,
},
},
],
}),
})
} catch (error) {
this.logger.error(`Failed to post court hearing arrangement summary`, {
error,
})
}
}

async postErrorEvent(
message: string,
info: { [key: string]: string | boolean | Date | undefined },
Expand Down
47 changes: 40 additions & 7 deletions apps/judicial-system/scheduler/src/app/app.service.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import fetch from 'node-fetch'

import { Inject, Injectable } from '@nestjs/common'
import { BadGatewayException, Inject, Injectable } from '@nestjs/common'

import { type Logger, LOGGER_PROVIDER } from '@island.is/logging'
import { type ConfigType } from '@island.is/nest/config'
Expand All @@ -24,12 +24,8 @@ export class AppService {
@Inject(LOGGER_PROVIDER) private readonly logger: Logger,
) {}

async run() {
this.logger.info('Scheduler starting')

const startTime = now()

this.messageService
private addMessagesForIndictmentsWaitingForConfirmationToQueue() {
return this.messageService
.sendMessagesToQueue([
{
type: MessageType.NOTIFICATION_DISPATCH,
Expand All @@ -42,6 +38,10 @@ export class AppService {
// Tolerate failure, but log
this.logger.error('Failed to dispatch notifications', { reason }),
)
}

private async archiveCases() {
const startTime = now()

let done = false

Expand Down Expand Up @@ -76,6 +76,39 @@ export class AppService {
!done &&
minutesBetween(startTime, now()) < this.config.timeToLiveMinutes
)
}

private async postDailyHearingArrangementSummary() {
const today = now()
try {
const res = await fetch(
`${this.config.backendUrl}/api/internal/cases/postHearingArrangements`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
authorization: `Bearer ${this.config.backendAccessToken}`,
},
body: JSON.stringify({ date: today }),
},
)

if (!res.ok) {
throw new BadGatewayException(
'Unexpected error occurred while fetching cases',
)
}
} catch (error) {
throw new BadGatewayException(`Failed to fetch cases: ${error.message}`)
}
}
thorhildurt marked this conversation as resolved.
Show resolved Hide resolved

async run() {
this.logger.info('Scheduler starting')

await this.addMessagesForIndictmentsWaitingForConfirmationToQueue()
thorhildurt marked this conversation as resolved.
Show resolved Hide resolved
await this.archiveCases()
await this.postDailyHearingArrangementSummary()

this.logger.info('Scheduler done')
}
Expand Down
29 changes: 26 additions & 3 deletions apps/judicial-system/scheduler/src/app/test/run.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ describe('AppService - Run', () => {
beforeEach(() => {
mockNow.mockClear()
mockFetch.mockClear()

mockNow.mockReturnValue(new Date('2020-01-01T00:01:00.000Z'))

givenWhenThen = async (): Promise<Then> => {
Expand Down Expand Up @@ -75,7 +76,7 @@ describe('AppService - Run', () => {
body: { type: 'INDICTMENTS_WAITING_FOR_CONFIRMATION' },
},
])
expect(fetch).toHaveBeenCalledTimes(3)
expect(fetch).toHaveBeenCalledTimes(4)
expect(fetch).toHaveBeenCalledWith(
`${appModuleConfig().backendUrl}/api/internal/cases/archive`,
{
Expand All @@ -86,6 +87,19 @@ describe('AppService - Run', () => {
},
},
)
expect(fetch).toHaveBeenCalledWith(
`${
appModuleConfig().backendUrl
}/api/internal/cases/postHearingArrangements`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
authorization: `Bearer ${appModuleConfig().backendAccessToken}`,
},
body: JSON.stringify({ date: new Date('2020-01-01T00:01:00.000Z') }),
},
)
})
})

Expand All @@ -102,8 +116,17 @@ describe('AppService - Run', () => {
await givenWhenThen()
})

it('should call the backend twice', () => {
expect(fetch).toHaveBeenCalledTimes(2)
it('should attempt archiving twice', () => {
expect(fetch).toHaveBeenNthCalledWith(
1,
`${appModuleConfig().backendUrl}/api/internal/cases/archive`,
expect.any(Object),
)
expect(fetch).toHaveBeenNthCalledWith(
2,
`${appModuleConfig().backendUrl}/api/internal/cases/archive`,
expect.any(Object),
)
})
})

Expand Down
Loading