Skip to content

Commit

Permalink
Add missing information to /health/info page (#376)
Browse files Browse the repository at this point in the history
https://eaflood.atlassian.net/browse/WATER-4099

Finish off the work previously started on the `/health/info` page. The data that is missing that needs to be added in this PR is:
- Confirm Redis is running
- Display the latest results for the import app

The changes to add the Redis info have been moved into a separate PR #402 so that this PR is just focusing on the import information.
  • Loading branch information
Jozzey authored Sep 5, 2023
1 parent e73dbf3 commit 290dddb
Show file tree
Hide file tree
Showing 4 changed files with 304 additions and 89 deletions.
90 changes: 90 additions & 0 deletions app/services/health/fetch-import-jobs.service.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
'use strict'

/**
* Fetches details of the pg-boss jobs run by the Import service in the last 24hrs
* @module FetchImportJobs
*/

const { db } = require('../../../db/db.js')

/**
* Returns data required to populate the Import tasks of our `/health/info` page.
*/
async function go () {
const PGBOSS_JOBS_ARRAY = [
'import.bill-runs',
'import.charge-versions',
'import.charging-data',
'import.tracker',
'licence-import.delete-removed-documents',
'licence-import.import-company',
'licence-import.import-licence',
'licence-import.import-purpose-condition-types',
'licence-import.queue-companies',
'licence-import.queue-licences',
'nald-import.delete-removed-documents',
'nald-import.import-licence',
'nald-import.queue-licences',
'nald-import.s3-download'
]
const currentDateMinusOneDay = _subtractDaysFromCurrentDate(1)

return db
.select('name')
.count({ completedCount: db.raw("CASE WHEN state = 'completed' THEN 1 END") })
.count({ failedCount: db.raw("CASE WHEN state = 'failed' THEN 1 END") })
.count({ activeCount: db.raw("CASE WHEN state IN ('active', 'created') THEN 1 END") })
.max('completedon as maxCompletedonDate')
.from(
(db
.select(
'name',
'state',
'completedon'
)
.from('water_import.job')
.whereIn('state', ['failed', 'completed', 'active', 'created'])
.whereIn('name', PGBOSS_JOBS_ARRAY)
.where((builder) =>
builder
.where('createdon', '>', currentDateMinusOneDay)
.orWhere('completedon', '>', currentDateMinusOneDay)
)
.unionAll(
db
.select(
'name',
'state',
'completedon'
)
.from('water_import.archive')
.whereIn('state', ['failed', 'completed', 'active', 'created'])
.whereIn('name', PGBOSS_JOBS_ARRAY)
.where((builder) =>
builder
.where('createdon', '>', currentDateMinusOneDay)
.orWhere('completedon', '>', currentDateMinusOneDay)
)
))
.as('jobs')
)
.groupBy('name')
}

/**
* Calculates the current date minus the number of days passed to it
*
* @param {Number} days The number of days to be deducted from the currect date
*
* @returns {Date} The current date minus the number days passed to the function
*/
function _subtractDaysFromCurrentDate (days) {
const date = new Date()
date.setDate(date.getDate() - days)

return date
}

module.exports = {
go
}
152 changes: 76 additions & 76 deletions app/services/health/info.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ const exec = util.promisify(ChildProcess.exec)
const redis = require('@redis/client')

const ChargingModuleRequestLib = require('../../lib/charging-module-request.lib.js')
const FetchImportJobs = require('./fetch-import-jobs.service.js')
const { formatLongDateTime } = require('../../presenters/base.presenter.js')
const RequestLib = require('../../lib/request.lib.js')
const LegacyRequestLib = require('../../lib/legacy-request.lib.js')

Expand All @@ -26,58 +28,18 @@ const servicesConfig = require('../../../config/services.config.js')
* array per row, where each row array contains multiple `{ text: '...' }` elements, one for each cell in the row.
*/
async function go () {
const virusScannerData = await _getVirusScannerData()
const redisConnectivityData = await _getRedisConnectivityData()

const addressFacadeData = await _getAddressFacadeData()
const chargingModuleData = await _getChargingModuleData()
const appData = await _getAppData()
const chargingModuleData = await _getChargingModuleData()
const redisConnectivityData = await _getRedisConnectivityData()
const virusScannerData = await _getVirusScannerData()

return {
virusScannerData,
redisConnectivityData,
addressFacadeData,
appData,
chargingModuleData,
appData
}
}

/**
* Receives an array and returns it in the format required by the nunjucks template in the view.
*/
function _mapArrayToTextCells (rows) {
return rows.map(row => {
return row.map(cell => {
return { text: cell }
})
})
}

async function _getVirusScannerData () {
try {
const { stdout, stderr } = await exec('clamdscan --version')
return stderr ? `ERROR: ${stderr}` : stdout
} catch (error) {
return `ERROR: ${error.message}`
}
}

async function _getRedisConnectivityData () {
try {
const client = redis.createClient({
socket: {
host: redisConfig.host,
port: redisConfig.port
},
password: redisConfig.password
})

await client.connect()
await client.disconnect()

return 'Up and running'
} catch (error) {
return 'Error connecting to Redis'
redisConnectivityData,
virusScannerData
}
}

Expand All @@ -92,43 +54,20 @@ async function _getAddressFacadeData () {
return _parseFailedRequestResult(result)
}

async function _getChargingModuleData () {
const result = await ChargingModuleRequestLib.get('status')

if (result.succeeded) {
return result.response.info.dockerTag
}

return _parseFailedRequestResult(result)
}

function _getImportJobsData () {
return _mapArrayToTextCells([
[
'Cell 1.1',
'Cell 1.2'
],
[
'Cell 2.1',
'Cell 2.2'
]
])
}

async function _getAppData () {
const healthInfoPath = 'health/info'

const services = [
{ name: 'Service - foreground', serviceName: 'water' },
{ name: 'Service - background', serviceName: 'background' },
{ name: 'Reporting', serviceName: 'reporting' },
{ name: 'Import', serviceName: 'import' },
{ name: 'Tactical CRM', serviceName: 'crm' },
{ name: 'External UI', serviceName: 'external' },
{ name: 'Internal UI', serviceName: 'internal' },
{ name: 'Service - foreground', serviceName: 'water' },
{ name: 'Service - background', serviceName: 'background' },
{ name: 'Returns', serviceName: 'returns' },
{ name: 'Tactical CRM', serviceName: 'crm' },
{ name: 'Tactical IDM', serviceName: 'idm' },
{ name: 'Permit repository', serviceName: 'permits' },
{ name: 'Returns', serviceName: 'returns' }
{ name: 'Reporting', serviceName: 'reporting' },
{ name: 'Permit repository', serviceName: 'permits' }
]

for (const service of services) {
Expand All @@ -137,7 +76,7 @@ async function _getAppData () {
if (result.succeeded) {
service.version = result.response.body.version
service.commit = result.response.body.commit
service.jobs = service.name === 'Import' ? _getImportJobsData() : []
service.jobs = service.name === 'Import' ? await _getImportJobsData() : []
} else {
service.version = _parseFailedRequestResult(result)
service.commit = ''
Expand All @@ -147,6 +86,67 @@ async function _getAppData () {
return services
}

async function _getChargingModuleData () {
const result = await ChargingModuleRequestLib.get('status')

if (result.succeeded) {
return result.response.info.dockerTag
}

return _parseFailedRequestResult(result)
}

async function _getImportJobsData () {
try {
const importJobs = await FetchImportJobs.go()

return _mapArrayToTextCells(importJobs)
} catch (error) {
return []
}
}

async function _getRedisConnectivityData () {
try {
const client = redis.createClient({
socket: {
host: redisConfig.host,
port: redisConfig.port
},
password: redisConfig.password
})

await client.connect()
await client.disconnect()

return 'Up and running'
} catch (error) {
// `error` value not returned for security reasons as it gives out the IP address and port of Redis
return 'Error connecting to Redis'
}
}

async function _getVirusScannerData () {
try {
const { stdout, stderr } = await exec('clamdscan --version')
return stderr ? `ERROR: ${stderr}` : stdout
} catch (error) {
return `ERROR: ${error.message}`
}
}

function _mapArrayToTextCells (rows) {
return rows.map(row => {
return [
...[{ text: row.name }],
...[{ text: row.completedCount }],
...[{ text: row.failedCount }],
...[{ text: row.activeCount }],
...[{ text: row.maxCompletedonDate ? formatLongDateTime(row.maxCompletedonDate) : '-' }]
]
})
}

function _parseFailedRequestResult (result) {
if (result.response.statusCode) {
return `ERROR: ${result.response.statusCode} - ${result.response.body}`
Expand Down
13 changes: 11 additions & 2 deletions app/views/info.njk
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,19 @@
firstCellIsHeader: false,
head: [
{
text: "Job name"
text: "Import task name (data for the last 24 hrs)"
},
{
text: "Last updated"
text: "Completed"
},
{
text: "Failed"
},
{
text: "Active"
},
{
text: "Last job completed"
}
],
rows: app.jobs
Expand Down
Loading

0 comments on commit 290dddb

Please sign in to comment.