Alerting: Remove react-enable (#77955)

This commit is contained in:
Gilles De Mey
2023-11-09 18:35:03 +01:00
committed by GitHub
parent 36dba7584d
commit 323ee7c38c
12 changed files with 94 additions and 255 deletions

View File

@@ -1,12 +1,11 @@
import React from 'react';
import { Disable, Enable } from 'react-enable';
import { config } from '@grafana/runtime';
import { withErrorBoundary } from '@grafana/ui';
import { SafeDynamicImport } from 'app/core/components/DynamicImports/SafeDynamicImport';
import { GrafanaRouteComponentProps } from 'app/core/navigation/types';
import { AlertingPageWrapper } from './components/AlertingPageWrapper';
import { AlertingFeature } from './features';
const DetailViewV1 = SafeDynamicImport(() => import('./components/rule-viewer/RuleViewer.v1'));
const DetailViewV2 = SafeDynamicImport(() => import('./components/rule-viewer/v2/RuleViewer.v2'));
@@ -16,17 +15,12 @@ type RuleViewerProps = GrafanaRouteComponentProps<{
sourceName: string;
}>;
const RuleViewer = (props: RuleViewerProps): JSX.Element => {
return (
<AlertingPageWrapper>
<Enable feature={AlertingFeature.DetailsViewV2}>
<DetailViewV2 {...props} />
</Enable>
<Disable feature={AlertingFeature.DetailsViewV2}>
<DetailViewV1 {...props} />
</Disable>
</AlertingPageWrapper>
);
};
const newAlertDetailView = Boolean(config.featureToggles.alertingDetailsViewV2) === true;
const RuleViewer = (props: RuleViewerProps): JSX.Element => (
<AlertingPageWrapper>
{newAlertDetailView ? <DetailViewV2 {...props} /> : <DetailViewV1 {...props} />}
</AlertingPageWrapper>
);
export default withErrorBoundary(RuleViewer, { style: 'page' });

View File

@@ -1,20 +1,14 @@
import Mousetrap from 'mousetrap';
import React, { PropsWithChildren, useEffect, useState } from 'react';
import { Features, ToggleFeatures } from 'react-enable';
import React, { PropsWithChildren } from 'react';
import { useLocation } from 'react-use';
import { NavModelItem } from '@grafana/data';
import { Page } from 'app/core/components/Page/Page';
import FEATURES from '../features';
import { AlertmanagerProvider, useAlertmanager } from '../state/AlertmanagerContext';
import { AlertManagerPicker } from './AlertManagerPicker';
import { NoAlertManagerWarning } from './NoAlertManagerWarning';
const SHOW_TOGGLES_KEY_COMBO = 'ctrl+1';
const combokeys = new Mousetrap(document.body);
/**
* This is the main alerting page wrapper, used by the alertmanager page wrapper and the alert rules list view
*/
@@ -25,25 +19,10 @@ interface AlertingPageWrapperProps extends PropsWithChildren {
actions?: React.ReactNode;
}
export const AlertingPageWrapper = ({ children, pageId, pageNav, actions, isLoading }: AlertingPageWrapperProps) => {
const [showFeatureToggle, setShowFeatureToggles] = useState(false);
useEffect(() => {
combokeys.bind(SHOW_TOGGLES_KEY_COMBO, () => {
setShowFeatureToggles((show) => !show);
});
return () => {
combokeys.unbind(SHOW_TOGGLES_KEY_COMBO);
};
}, []);
return (
<Features features={FEATURES}>
<Page pageNav={pageNav} navId={pageId} actions={actions}>
<Page.Contents isLoading={isLoading}>{children}</Page.Contents>
</Page>
{showFeatureToggle ? <ToggleFeatures defaultOpen={true} /> : null}
</Features>
<Page pageNav={pageNav} navId={pageId} actions={actions}>
<Page.Contents isLoading={isLoading}>{children}</Page.Contents>
</Page>
);
};

View File

@@ -1,21 +0,0 @@
import { FeatureDescription } from 'react-enable/dist/FeatureState';
import { config } from '@grafana/runtime';
export enum AlertingFeature {
NotificationPoliciesV2MatchingInstances = 'notification-policies.v2.matching-instances',
DetailsViewV2 = 'details-view.v2',
ContactPointsV2 = 'contact-points.v2',
}
const FEATURES: FeatureDescription[] = [
{
name: AlertingFeature.NotificationPoliciesV2MatchingInstances,
defaultValue: config.featureToggles.alertingNotificationsPoliciesMatchingInstances,
},
{
name: AlertingFeature.DetailsViewV2,
defaultValue: false,
},
];
export default FEATURES;

View File

@@ -1,11 +1,7 @@
import { renderHook } from '@testing-library/react';
import * as comlink from 'comlink';
import React from 'react';
import { Features } from 'react-enable';
import { FeatureDescription } from 'react-enable/dist/FeatureState';
import { createWorker } from './createRouteGroupsMatcherWorker';
import { AlertingFeature } from './features';
import { useRouteGroupsMatcher } from './useRouteGroupsMatcher';
jest.mock('./createRouteGroupsMatcherWorker');
@@ -20,24 +16,8 @@ beforeEach(() => {
});
describe('useRouteGroupsMatcher', () => {
it('should not load web worker if the feature flag is disabled', function () {
const featureFlag = getInstancePreviewFeature(false);
const { result } = renderHook(() => useRouteGroupsMatcher(), {
wrapper: ({ children }) => <Features features={[featureFlag]}>{children}</Features>,
});
expect(createWorkerMock).not.toHaveBeenCalled();
expect(wrapMock).not.toHaveBeenCalled();
expect(result.current.getRouteGroupsMap).toBeDefined();
});
it('should load web worker if the feature flag is enabled', function () {
const featureFlag = getInstancePreviewFeature(true);
const { result } = renderHook(() => useRouteGroupsMatcher(), {
wrapper: ({ children }) => <Features features={[featureFlag]}>{children}</Features>,
});
const { result } = renderHook(() => useRouteGroupsMatcher());
expect(createWorkerMock).toHaveBeenCalledTimes(1);
expect(wrapMock).toHaveBeenCalledTimes(1);
@@ -45,14 +25,11 @@ describe('useRouteGroupsMatcher', () => {
});
it('getMatchedRouteGroups should throw error if loading worker failed', async function () {
const featureFlag = getInstancePreviewFeature(true);
createWorkerMock.mockImplementation(() => {
throw new DOMException('Failed to load worker');
});
const { result } = renderHook(() => useRouteGroupsMatcher(), {
wrapper: ({ children }) => <Features features={[featureFlag]}>{children}</Features>,
});
const { result } = renderHook(() => useRouteGroupsMatcher());
expect(createWorkerMock).toHaveBeenCalledTimes(1);
expect(wrapMock).toHaveBeenCalledTimes(0); // When loading worker failed we shouldn't call wrap
@@ -61,10 +38,3 @@ describe('useRouteGroupsMatcher', () => {
}).rejects.toThrowError(Error);
});
});
function getInstancePreviewFeature(enabled: boolean): FeatureDescription {
return {
name: AlertingFeature.NotificationPoliciesV2MatchingInstances,
defaultValue: enabled,
};
}

View File

@@ -1,6 +1,5 @@
import * as comlink from 'comlink';
import { useCallback, useEffect } from 'react';
import { useEnabled } from 'react-enable';
import { logError } from '@grafana/runtime';
@@ -9,7 +8,6 @@ import { Labels } from '../../../types/unified-alerting-dto';
import { logInfo } from './Analytics';
import { createWorker } from './createRouteGroupsMatcherWorker';
import { AlertingFeature } from './features';
import type { RouteGroupsMatcher } from './routeGroupsMatcher';
let routeMatcher: comlink.Remote<RouteGroupsMatcher> | undefined;
@@ -45,74 +43,57 @@ function loadWorker() {
return { disposeWorker };
}
function validateWorker(
toggleEnabled: boolean,
matcher: typeof routeMatcher
): asserts matcher is comlink.Remote<RouteGroupsMatcher> {
if (!toggleEnabled) {
throw new Error('Matching routes preview is disabled');
}
function validateWorker(matcher: typeof routeMatcher): asserts matcher is comlink.Remote<RouteGroupsMatcher> {
if (!routeMatcher) {
throw new Error('Route Matcher has not been initialized');
}
}
export function useRouteGroupsMatcher() {
const workerPreviewEnabled = useEnabled(AlertingFeature.NotificationPoliciesV2MatchingInstances);
useEffect(() => {
if (workerPreviewEnabled) {
const { disposeWorker } = loadWorker();
return disposeWorker;
}
const { disposeWorker } = loadWorker();
return disposeWorker;
return () => null;
}, [workerPreviewEnabled]);
}, []);
const getRouteGroupsMap = useCallback(
async (rootRoute: RouteWithID, alertGroups: AlertmanagerGroup[]) => {
validateWorker(workerPreviewEnabled, routeMatcher);
const getRouteGroupsMap = useCallback(async (rootRoute: RouteWithID, alertGroups: AlertmanagerGroup[]) => {
validateWorker(routeMatcher);
const startTime = performance.now();
const startTime = performance.now();
const result = await routeMatcher.getRouteGroupsMap(rootRoute, alertGroups);
const result = await routeMatcher.getRouteGroupsMap(rootRoute, alertGroups);
const timeSpent = performance.now() - startTime;
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',
});
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 result;
}, []);
const matchInstancesToRoute = useCallback(
async (rootRoute: RouteWithID, instancesToMatch: Labels[]) => {
validateWorker(workerPreviewEnabled, routeMatcher);
const matchInstancesToRoute = useCallback(async (rootRoute: RouteWithID, instancesToMatch: Labels[]) => {
validateWorker(routeMatcher);
const startTime = performance.now();
const startTime = performance.now();
const result = await routeMatcher.matchInstancesToRoute(rootRoute, instancesToMatch);
const result = await routeMatcher.matchInstancesToRoute(rootRoute, instancesToMatch);
const timeSpent = performance.now() - startTime;
const timeSpent = performance.now() - startTime;
logInfo(`Instances Matched in ${timeSpent} ms`, {
matchingTime: timeSpent.toString(),
instancesToMatchCount: instancesToMatch.length.toString(),
// Counting all nested routes might be too time-consuming, so we only count the first level
topLevelRoutesCount: rootRoute.routes?.length.toString() ?? '0',
});
logInfo(`Instances Matched in ${timeSpent} ms`, {
matchingTime: timeSpent.toString(),
instancesToMatchCount: instancesToMatch.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 result;
}, []);
return { getRouteGroupsMap, matchInstancesToRoute };
}