Files
grafana/public/app/features/alerting/unified/useRouteGroupsMatcher.ts
Konrad Lalik 2f0728ac67 Alerting: Matching instances preview for notification policies (#68882)
* Basic implementation in web worker

* Move instances discovery to the worker

* Remove filtering from the worker

* Use normalized routes, use rtk query for alert groups fetching

* Reorganize matchers utilities to be available for web workers

* Move object matchers to the machers util file, rename worker

* Move worker code to a separate hook, add perf logging

* Add a mock for the web worker code, fix tests

* Fix tests warnings

* Remove notification policy feature flag

* Add normalizeRoute tests, change the regex match to test for label matching

* Move worker init to the file scope

* Simplify useAsyncFn hook

* Use CorsWorker as a workaround for web workers loading from CDN

* Use a feature flag to enable/disable worker-based preview, add worker error handling

* Add POC for react-enable working with grafana feature toggles

* Code cleanup

* Remove console error, add useRouteGroupsMatcher tests

* Fix tests mock
2023-05-30 15:15:22 +02:00

89 lines
2.7 KiB
TypeScript

import * as comlink from 'comlink';
import { useCallback, useEffect } from 'react';
import { useEnabled } from 'react-enable';
import { logError } from '@grafana/runtime';
import { AlertmanagerGroup, RouteWithID } from '../../../plugins/datasource/alertmanager/types';
import { logInfo } from './Analytics';
import { createWorker } from './createRouteGroupsMatcherWorker';
import { AlertingFeature } from './features';
import type { RouteGroupsMatcher } from './routeGroupsMatcher.worker';
let routeMatcher: comlink.Remote<RouteGroupsMatcher> | undefined;
// Load worker loads the worker if it's not loaded yet
// and returns a function to dispose of the worker
// We do it to enable feature toggling. If the feature is disabled we don't wont to load the worker code at all
// An alternative way would be to move all this code to the hook below, but it will create and terminate the worker much more often
function loadWorker() {
let worker: Worker | undefined;
if (routeMatcher === undefined) {
try {
worker = createWorker();
routeMatcher = comlink.wrap<RouteGroupsMatcher>(worker);
} catch (e: unknown) {
if (e instanceof Error) {
logError(e);
}
}
}
const disposeWorker = () => {
if (worker && routeMatcher) {
routeMatcher[comlink.releaseProxy]();
worker.terminate();
routeMatcher = undefined;
worker = undefined;
}
};
return { disposeWorker };
}
export function useRouteGroupsMatcher() {
const workerPreviewEnabled = useEnabled(AlertingFeature.NotificationPoliciesV2MatchingInstances);
useEffect(() => {
if (workerPreviewEnabled) {
const { disposeWorker } = loadWorker();
return disposeWorker;
}
return () => null;
}, [workerPreviewEnabled]);
const getRouteGroupsMap = useCallback(
async (rootRoute: RouteWithID, alertGroups: AlertmanagerGroup[]) => {
if (!workerPreviewEnabled) {
throw new Error('Matching routes preview is disabled');
}
if (!routeMatcher) {
throw new Error('Route Matcher has not been initialized');
}
const startTime = performance.now();
const result = await routeMatcher.getRouteGroupsMap(rootRoute, alertGroups);
const timeSpent = performance.now() - startTime;
logInfo(`Route Groups Matched in ${timeSpent} ms`, {
matchingTime: timeSpent.toString(),
alertGroupsCount: alertGroups.length.toString(),
// Counting all nested routes might be too time-consuming, so we only count the first level
topLevelRoutesCount: rootRoute.routes?.length.toString() ?? '0',
});
return result;
},
[workerPreviewEnabled]
);
return { getRouteGroupsMap };
}