-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feature/improve-missing-event-processing (#431)
* feature/improve-missing-event-processing https://eaflood.atlassian.net/browse/CI-327 * Update version number * Update Azure Functions extension bundle version * Schedule message replay for partial task run data * Improve code maintainability * Revert "Improve code maintainability" This reverts commit 3481b71. * Improve code maintainability * Pass minimum required to software under test * Make delay based functions shared utilities * Remove file extension * Pause before potential message replay * Prepare mock data for missing event detection * Remove obsolete Jest mock * Detect and handle missing events * Improve code maintainability * Improve comments * Propagate message publication error after pausing
- Loading branch information
1 parent
90c88b8
commit ecd15f4
Showing
34 changed files
with
1,453 additions
and
374 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
const doIfMaximumDelayForPiServerIndexingIsNotExceeded = | ||
require('../../Shared/timeseries-functions/do-if-maximum-delay-for-pi-server-indexing-is-not-exceeded') | ||
const PartialFewsDataError = require('../../Shared/message-replay/partial-fews-data-error') | ||
const JSONStream = require('jsonstream-next') | ||
const { pipeline, Transform } = require('stream') | ||
const { createGzip } = require('zlib') | ||
const { promisify } = require('util') | ||
const pipe = promisify(pipeline) | ||
|
||
module.exports = async function (context, taskRunData, jsonStream) { | ||
const buffers = [] | ||
let buffersLength = 0 | ||
|
||
const preStringifyObjectTransform = new Transform({ | ||
// Object mode is required to manipulate JSON. | ||
objectMode: true, | ||
transform: async (object, encoding, done) => { | ||
try { | ||
await checkForMissingEventsIfNeeded(context, taskRunData, object) | ||
done(null, [object.key, object.value]) | ||
} catch (err) { | ||
done(err) | ||
} | ||
} | ||
}) | ||
|
||
const byteArrayTransform = new Transform({ | ||
transform: (chunk, encoding, done) => { | ||
buffers.push(chunk) | ||
buffersLength += chunk.length | ||
done() | ||
} | ||
}) | ||
|
||
const transforms = { preStringifyObjectTransform, byteArrayTransform } | ||
await runPipe(jsonStream, transforms) | ||
return Buffer.concat(buffers, buffersLength) | ||
} | ||
async function runPipe (jsonStream, transforms) { | ||
// Check for missing events if required before applying minification and gzip compression. | ||
// Minification is achieved using a stream compatible version of JSON.stringify(JSON.parse(jsonString)). | ||
const gzip = createGzip() | ||
await pipe( | ||
jsonStream, | ||
// Emit keys and values from the stream. | ||
JSONStream.parse('$*'), | ||
// Check for missing events and transform the keys and values into the form required by JSONStream.stringifyObject. | ||
// NOTE - Missing event processing is performed using asynchronous functions because the utility function | ||
// used during processng (doIfMaximumDelayForPiServerIndexingIsNotExceeded) is asynchronous. | ||
await transforms.preStringifyObjectTransform, | ||
// Minify the contents of the stream through the removal of new lines and use of | ||
// JSON.stringify with no indentation. | ||
JSONStream.stringifyObject('{', ',', '}', 0), | ||
gzip, | ||
transforms.byteArrayTransform | ||
) | ||
} | ||
|
||
async function checkForMissingEventsIfNeeded (context, taskRunData, fewsData) { | ||
const noActionTakenMessage = `Skipping missing event detection for ${taskRunData.sourceDetails}` | ||
|
||
await doIfMaximumDelayForPiServerIndexingIsNotExceeded( | ||
{ fn: checkForMissingEvents, context, taskRunData, noActionTakenMessage }, fewsData | ||
) | ||
} | ||
|
||
async function checkForMissingEvents (context, taskRunData, fewsData) { | ||
// NOTE - This function is asynchronous because it is called asynchronously by the | ||
// utility function doIfMaximumDelayForPiServerIndexingIsNotExceeded. This function | ||
// could be enhanced to include asynchronous processng (for example, to record missing | ||
// event details in the staging database), but this is not required currently. | ||
// | ||
// Missing events occur when timeSeries header data has an empty array of associated | ||
// events rather than no associated events. Missing events can be an indication that | ||
// PI Server indexing for a task run has not completed yet, so prepare to schedule | ||
// replay of the message. | ||
if (fewsData.key === 'timeSeries' && fewsData.value.filter(data => data?.events?.length === 0).length > 0) { | ||
throw new PartialFewsDataError( | ||
context, | ||
taskRunData.message, | ||
`Missing events detected for ${taskRunData.sourceDetails} - preparing to schedule message replay` | ||
) | ||
} else if (fewsData.key === 'timeSeries') { | ||
context.log(`No missing events detected for ${taskRunData.sourceDetails}`) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.