Files
oneuptime/App/FeatureSet/Workers/Jobs/AlertEpisode/ResolveInactiveEpisodes.ts
Nawaz Dhandala ea71c8bd75 feat: Implement Workflow API and Queue Management
- Added ManualAPI for manually triggering workflows via GET and POST requests.
- Introduced WorkflowAPI for updating workflows with authorization checks.
- Created documentation for JavaScript and Webhook components.
- Established WorkflowFeatureSet to initialize routing and job processing.
- Developed QueueWorkflow service for managing workflow queue operations.
- Implemented RunWorkflow service to execute workflows with error handling and logging.
- Added utility for loading component metadata dynamically.
2026-04-01 22:05:19 +01:00

136 lines
4.3 KiB
TypeScript

import RunCron from "../../Utils/Cron";
import OneUptimeDate from "Common/Types/Date";
import { EVERY_FIVE_MINUTE } from "Common/Utils/CronTime";
import AlertEpisodeService from "Common/Server/Services/AlertEpisodeService";
import AlertGroupingRuleService from "Common/Server/Services/AlertGroupingRuleService";
import logger from "Common/Server/Utils/Logger";
import AlertEpisode from "Common/Models/DatabaseModels/AlertEpisode";
import AlertGroupingRule from "Common/Models/DatabaseModels/AlertGroupingRule";
import QueryHelper from "Common/Server/Types/Database/QueryHelper";
RunCron(
"AlertEpisode:ResolveInactiveEpisodes",
{
schedule: EVERY_FIVE_MINUTE,
runOnStartup: false,
},
async () => {
/*
* Find active episodes that have been inactive for too long
* and resolve them due to inactivity
*/
try {
// Get all active episodes
const activeEpisodes: Array<AlertEpisode> =
await AlertEpisodeService.findBy({
query: {
resolvedAt: QueryHelper.isNull(),
},
select: {
_id: true,
projectId: true,
alertGroupingRuleId: true,
lastAlertAddedAt: true,
},
props: {
isRoot: true,
},
limit: 1000,
skip: 0,
});
logger.debug(
`AlertEpisode:ResolveInactiveEpisodes - Found ${activeEpisodes.length} active episodes`,
);
const promises: Array<Promise<void>> = [];
for (const episode of activeEpisodes) {
promises.push(checkAndResolveInactiveEpisode(episode));
}
await Promise.allSettled(promises);
} catch (error) {
logger.error(`AlertEpisode:ResolveInactiveEpisodes - Error: ${error}`);
}
},
);
type CheckAndResolveInactiveEpisodeFunction = (
episode: AlertEpisode,
) => Promise<void>;
const checkAndResolveInactiveEpisode: CheckAndResolveInactiveEpisodeFunction =
async (episode: AlertEpisode): Promise<void> => {
try {
if (!episode.id || !episode.projectId) {
return;
}
// Get inactivity timeout from the grouping rule (only if enabled)
let inactivityTimeoutMinutes: number = 0;
let enableInactivityTimeout: boolean = false;
if (episode.alertGroupingRuleId) {
const rule: AlertGroupingRule | null =
await AlertGroupingRuleService.findOneById({
id: episode.alertGroupingRuleId,
select: {
enableInactivityTimeout: true,
inactivityTimeoutMinutes: true,
},
props: {
isRoot: true,
},
});
if (rule) {
enableInactivityTimeout = rule.enableInactivityTimeout || false;
if (
enableInactivityTimeout &&
rule.inactivityTimeoutMinutes !== undefined
) {
inactivityTimeoutMinutes = rule.inactivityTimeoutMinutes;
}
}
}
// If inactivity timeout is not enabled or is 0, don't resolve inactive episodes
if (!enableInactivityTimeout || inactivityTimeoutMinutes <= 0) {
return;
}
// Check if episode has been inactive for too long
const lastAlertAddedAt: Date =
episode.lastAlertAddedAt || episode.createdAt || new Date();
const minutesSinceLastAlert: number =
OneUptimeDate.getDifferenceInMinutes(
lastAlertAddedAt,
OneUptimeDate.getCurrentDate(),
);
if (minutesSinceLastAlert < inactivityTimeoutMinutes) {
logger.debug(
`AlertEpisode:ResolveInactiveEpisodes - Episode ${episode.id} is still active (${minutesSinceLastAlert} minutes since last alert, timeout: ${inactivityTimeoutMinutes})`,
);
return;
}
// Episode has been inactive for too long - resolve it
logger.info(
`AlertEpisode:ResolveInactiveEpisodes - Resolving episode ${episode.id} due to inactivity (${minutesSinceLastAlert} minutes since last alert)`,
);
await AlertEpisodeService.resolveEpisode(
episode.id,
undefined, // No user - auto-resolved due to inactivity
true, // Cascade to alerts - resolve all member alerts as well
);
} catch (error) {
logger.error(
`AlertEpisode:ResolveInactiveEpisodes - Error processing episode ${episode.id}: ${error}`,
);
}
};