computeCustomMetrics
Metadata
- Version:
1.0.0 - Type: Generic Function
- Function Type:
Notifier - Kind:
function - Mode:
Enabled - Source File:
integrations/generic/functions/function_notifier_computeCustomMetrics.js
Description
This function computes two key response metrics for every case in TheHive: – Time‑to‑Respond (TTR): delay (in minutes) between the case's start date and the earliest task in the "3 ‑ Communication", "4 ‑ Containment", or "5 ‑ Eradication" task groups. – Time‑to‑Contain (TTC): delay (in minutes) between the case's start date and the latest task in the "4 ‑ Containment" task group.
It can run in two modes:
• Batch mode (no input object) – loops through a page of cases to back‑fill or refresh metrics.
• Event‑driven mode (input is a case payload) – updates metrics for the affected case only.
The function writes both the raw task timestamp and the computed metric to the following custom fields
(create them in your Case template):
• timestamp‑time‑to‑respond (Number – epoch ms)
• time‑to‑respond‑in‑minutes (Number)
• timestamp‑time‑to‑contain (Number – epoch ms)
• time‑to‑contain‑in‑minutes (Number)
Param:
– input: Either an empty object (batch trigger) or the Case JSON injected by TheHive when the
notification fires. This triggers a search to find all valid cases to update.
– context: Utility object providing access to TheHive API helpers (query, caze, etc.). This applies the metrics computation only on the notified case object.
Prerequisites: • The custom fields listed above must exist in the tenant. • Task groups must follow the naming convention shown here. • You may adjust page size, task groups, or field names to suit your workflows & even your own computation logic for your custom metrics. Those are shown as examples, relying on SOC-101 Metrics definition and SANS Incident Handler's handbook style tasks.
Code
function mainRespondMetricTrigger(input, context) {
const taskGroup = ["3 - Communication", "4 - Containment", "5 - Eradication"];
const timestampCustomField = "timestamp-time-to-respond";
const metricCustomField = "time-to-respond-in-minutes";
const timestampType = "earliest";
const customStartDateField = "startDate";
if (Object.entries(input).length === 0 && input.constructor === Object) {
const filtersQuery = [
{
_name: "listCase"
},
{
_name: "page",
from: 0,
to: 10
}
];
processCases(taskGroup, timestampCustomField, metricCustomField, filtersQuery, context, timestampType, customStartDateField);
} else {
processSingleCase(input, taskGroup, timestampCustomField, metricCustomField, context, timestampType, customStartDateField);
}
}
function mainContainMetricTrigger(input, context) {
const taskGroup = ["4 - Containment"];
const timestampCustomField = "timestamp-time-to-contain";
const metricCustomField = "time-to-contain-in-minutes";
const timestampType = "latest";
const customStartDateField = "endDate";
if (Object.entries(input).length === 0 && input.constructor === Object) {
const filtersQuery = [
{
_name: "listCase"
},
{
_name: "page",
from: 0,
to: 10
}
];
processCases(taskGroup, timestampCustomField, metricCustomField, filtersQuery, context, timestampType, customStartDateField);
} else {
processSingleCase(input, taskGroup, timestampCustomField, metricCustomField, context, timestampType, customStartDateField);
}
}
function updateOrAddCustomFields(customFields, customFieldUpdates) {
customFieldUpdates.forEach(customFieldUpdate => {
if (customFieldUpdate.value === null || customFieldUpdate.value === '') {
return;
}
let fieldFound = false;
customFields = customFields.map(field => {
if (field.name === customFieldUpdate.name) {
fieldFound = true;
return { ...field, value: customFieldUpdate.value };
}
return field;
});
if (!fieldFound) {
customFields.push({
name: customFieldUpdate.name,
value: customFieldUpdate.value
});
}
});
return customFields;
}
function processSingleCase(caze, taskGroups, timestampCustomField, metricCustomField, context, timestampType = "earliest", customStartDateField = "startDate") {
console.log("---------------------");
const caseId = caze._id;
const caseCustomFields = caze.customFields;
const caseStartDate = caze.startDate;
//const caseStartDate = caze[customStartDateField];
console.log(`Processing case ID: ${caseId}`);
console.log(`Case Number: ${caze.number}`);
const taskFilters = [
{
_name: "getCase",
idOrName: caseId
},
{
_name: "tasks"
}
];
const tasksOfCase = context.query.execute(taskFilters);
const filteredTasks = tasksOfCase.filter(task => taskGroups.includes(task.group));
console.log(`Filtered tasks in case: ${filteredTasks.length}`);
const filteredTasksWithStartDate = filteredTasks.filter(task => task.startDate !== undefined);
console.log(`Filtered tasks with start date in case: ${filteredTasksWithStartDate.length}`);
const startDates = filteredTasksWithStartDate.map(task => new Date(task.startDate).getTime());
let targetStartDate;
if (timestampType === "earliest") {
targetStartDate = Math.min(...startDates);
console.log(`Earliest ${customStartDateField}: ${targetStartDate}`);
} else if (timestampType === "latest") {
targetStartDate = Math.max(...startDates);
console.log(`Latest ${customStartDateField}: ${targetStartDate}`);
} else {
console.error("Invalid timestampType. Please use 'earliest' or 'latest'.");
return;
}
let metricTimeMinutes = null;
let timestampValue = targetStartDate;
if (Number.isFinite(targetStartDate) && Number.isFinite(caseStartDate)) {
metricTimeMinutes = (targetStartDate - caseStartDate) / (60 * 1000);
} else {
console.log(`CaseStartDate ${caseStartDate}: targetStartDate ${targetStartDate}`);
timestampValue = null;
}
console.log(`Value for ${timestampCustomField}: ${timestampValue}`);
console.log(`Value for ${metricCustomField}: ${metricTimeMinutes}`);
const customFieldToAdd = [
{
name: timestampCustomField,
value: timestampValue
},
{
name: metricCustomField,
value: metricTimeMinutes
}
];
const customFieldsUpdated = updateOrAddCustomFields(caseCustomFields, customFieldToAdd);
const caseUpdate = {
customFields: customFieldsUpdated
};
context.caze.update(caseId, caseUpdate);
console.log("Case update completed!");
console.log("--");
}
function processCases(taskGroups, timestampCustomField, metricCustomField, filtersQuery, context, timestampType = "earliest", customStartDateField = "startDate") {
console.log("---------------------");
console.log(`Listing all cases with ${metricCustomField}`);
const list = context.query.execute(filtersQuery);
const count = list.length;
console.log(`The count of objects in the list is: ${count}`);
list.forEach(caze => {
processSingleCase(caze, taskGroups, timestampCustomField, metricCustomField, context, timestampType, customStartDateField);
});
}
function handle(input, context) {
if (Object.entries(input).length === 0 && input.constructor === Object) {
console.log("HANDLE - No input -- Triggering basic behaviour on a list of cases");
mainRespondMetricTrigger(input, context);
mainContainMetricTrigger(input, context);
} else {
console.log("HANDLE - Input Found -- Event-driven");
//processSingleCase(input, ["4 - Containment"], "timestamp-time-to-contain", "time-to-contain-in-minutes", context, "latest", "endDate");
mainRespondMetricTrigger(input, context);
mainContainMetricTrigger(input, context);
}
}
This documentation is auto-generated. Do not edit manually.