feat(alerting): remove alertingNavigationV2 feature flag
Remove the alertingNavigationV2 feature toggle and make V2 navigation the only option. Changes: - Remove feature flag from registry.go and regenerate toggle files - Remove shouldUseAlertingNavigationV2() function from featureToggles.ts - Clean up all navigation hooks (useNotificationConfigNav, useInsightsNav, useAlertRulesNav, useAlertActivityNav) - Remove legacy navigation logic from page components (TimeIntervalsPage, Templates) - Update MegaMenu to always use flattened alerting sidebar navigation - Simplify all test files by removing feature flag mocking and legacy test cases Net result: ~500 lines of code removed, simpler codebase with single navigation implementation. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.5
parent
b3625b95e3
commit
661cd58bae
@@ -547,11 +547,6 @@ export interface FeatureToggles {
|
||||
*/
|
||||
alertingCentralAlertHistory?: boolean;
|
||||
/**
|
||||
* Enable new grouped navigation structure for Alerting
|
||||
* @default false
|
||||
*/
|
||||
alertingNavigationV2?: boolean;
|
||||
/**
|
||||
* Preserve plugin proxy trailing slash.
|
||||
* @default false
|
||||
*/
|
||||
|
||||
@@ -907,14 +907,6 @@ var (
|
||||
Owner: grafanaAlertingSquad,
|
||||
FrontendOnly: false, // changes navtree from backend
|
||||
},
|
||||
{
|
||||
Name: "alertingNavigationV2",
|
||||
Description: "Enable new grouped navigation structure for Alerting",
|
||||
Stage: FeatureStageExperimental,
|
||||
Owner: grafanaAlertingSquad,
|
||||
FrontendOnly: false, // changes navtree from backend
|
||||
Expression: "false", // Off by default
|
||||
},
|
||||
{
|
||||
Name: "pluginProxyPreserveTrailingSlash",
|
||||
Description: "Preserve plugin proxy trailing slash.",
|
||||
|
||||
Generated
-1
@@ -125,7 +125,6 @@ alertingSavedSearches,experimental,@grafana/alerting-squad,false,false,true
|
||||
alertingDisableSendAlertsExternal,experimental,@grafana/alerting-squad,false,false,false
|
||||
preserveDashboardStateWhenNavigating,experimental,@grafana/dashboards-squad,false,false,false
|
||||
alertingCentralAlertHistory,experimental,@grafana/alerting-squad,false,false,false
|
||||
alertingNavigationV2,experimental,@grafana/alerting-squad,false,false,false
|
||||
pluginProxyPreserveTrailingSlash,GA,@grafana/plugins-platform-backend,false,false,false
|
||||
azureMonitorPrometheusExemplars,GA,@grafana/partner-datasources,false,false,false
|
||||
authZGRPCServer,experimental,@grafana/identity-access-team,false,false,false
|
||||
|
||||
|
Generated
-4
@@ -379,10 +379,6 @@ const (
|
||||
// Enables the new central alert history.
|
||||
FlagAlertingCentralAlertHistory = "alertingCentralAlertHistory"
|
||||
|
||||
// FlagAlertingNavigationV2
|
||||
// Enable new grouped navigation structure for Alerting
|
||||
FlagAlertingNavigationV2 = "alertingNavigationV2"
|
||||
|
||||
// FlagPluginProxyPreserveTrailingSlash
|
||||
// Preserve plugin proxy trailing slash.
|
||||
FlagPluginProxyPreserveTrailingSlash = "pluginProxyPreserveTrailingSlash"
|
||||
|
||||
+2
-1
@@ -352,7 +352,8 @@
|
||||
"metadata": {
|
||||
"name": "alertingNavigationV2",
|
||||
"resourceVersion": "1767827323622",
|
||||
"creationTimestamp": "2026-01-07T23:08:43Z"
|
||||
"creationTimestamp": "2026-01-07T23:08:43Z",
|
||||
"deletionTimestamp": "2026-01-12T18:34:54Z"
|
||||
},
|
||||
"spec": {
|
||||
"description": "Enable new grouped navigation structure for Alerting",
|
||||
|
||||
@@ -11,7 +11,6 @@ import { reportInteraction } from '@grafana/runtime';
|
||||
import { ScrollContainer, useStyles2 } from '@grafana/ui';
|
||||
import { useGrafana } from 'app/core/context/GrafanaContext';
|
||||
import { setBookmark } from 'app/core/reducers/navBarTree';
|
||||
import { shouldUseAlertingNavigationV2 } from 'app/features/alerting/unified/featureToggles';
|
||||
import { useDispatch, useSelector } from 'app/types/store';
|
||||
|
||||
import { MegaMenuExtensionPoint } from './MegaMenuExtensionPoint';
|
||||
@@ -38,15 +37,14 @@ export const MegaMenu = memo(
|
||||
const pinnedItems = usePinnedItems();
|
||||
|
||||
// Remove profile + help from tree
|
||||
// For Alerting V2 navigation, flatten the sidebar to show only top-level items (hide nested children/tabs)
|
||||
const useV2Nav = shouldUseAlertingNavigationV2();
|
||||
// For Alerting navigation, flatten the sidebar to show only top-level items (hide nested children/tabs)
|
||||
const navItems = navTree
|
||||
.filter((item) => item.id !== 'profile' && item.id !== 'help')
|
||||
.map((item) => {
|
||||
const enriched = enrichWithInteractionTracking(item, state.megaMenuDocked);
|
||||
// If this is Alerting section and V2 navigation is enabled, flatten children for sidebar display
|
||||
// If this is Alerting section, flatten children for sidebar display
|
||||
// Children are still available in navIndex for breadcrumbs and page navigation
|
||||
if (useV2Nav && item.id === 'alerting' && enriched.children) {
|
||||
if (item.id === 'alerting' && enriched.children) {
|
||||
return {
|
||||
...enriched,
|
||||
children: enriched.children.map((child) => ({
|
||||
|
||||
@@ -140,37 +140,33 @@ const getRootRoute = async () => {
|
||||
};
|
||||
|
||||
describe('NotificationPolicies', () => {
|
||||
describe('V2 Navigation Mode', () => {
|
||||
testWithFeatureToggles({ enable: ['alertingNavigationV2'] });
|
||||
beforeEach(() => {
|
||||
setupDataSources(dataSources.am);
|
||||
grantUserPermissions([
|
||||
AccessControlAction.AlertingNotificationsRead,
|
||||
AccessControlAction.AlertingNotificationsWrite,
|
||||
...PERMISSIONS_NOTIFICATION_POLICIES,
|
||||
]);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
setupDataSources(dataSources.am);
|
||||
grantUserPermissions([
|
||||
AccessControlAction.AlertingNotificationsRead,
|
||||
AccessControlAction.AlertingNotificationsWrite,
|
||||
...PERMISSIONS_NOTIFICATION_POLICIES,
|
||||
]);
|
||||
});
|
||||
it('shows only notification policies without internal tabs', async () => {
|
||||
renderNotificationPolicies();
|
||||
|
||||
it('shows only notification policies without internal tabs', async () => {
|
||||
renderNotificationPolicies();
|
||||
// Should show notification policies directly
|
||||
expect(await ui.rootRouteContainer.find()).toBeInTheDocument();
|
||||
|
||||
// Should show notification policies directly
|
||||
expect(await ui.rootRouteContainer.find()).toBeInTheDocument();
|
||||
// Should not have tabs
|
||||
expect(screen.queryByRole('tab')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Should not have tabs
|
||||
expect(screen.queryByRole('tab')).not.toBeInTheDocument();
|
||||
});
|
||||
it('does not show time intervals tab', async () => {
|
||||
renderNotificationPolicies();
|
||||
|
||||
it('does not show time intervals tab in V2 mode', async () => {
|
||||
renderNotificationPolicies();
|
||||
// Should show notification policies
|
||||
expect(await ui.rootRouteContainer.find()).toBeInTheDocument();
|
||||
|
||||
// Should show notification policies
|
||||
expect(await ui.rootRouteContainer.find()).toBeInTheDocument();
|
||||
|
||||
// Should not show time intervals tab
|
||||
expect(screen.queryByText(/time intervals/i)).not.toBeInTheDocument();
|
||||
});
|
||||
// Should not show time intervals tab
|
||||
expect(screen.queryByText(/time intervals/i)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
// combobox hack :/
|
||||
|
||||
@@ -8,7 +8,6 @@ import DuplicateMessageTemplate from './components/contact-points/DuplicateMessa
|
||||
import EditMessageTemplate from './components/contact-points/EditMessageTemplate';
|
||||
import NewMessageTemplate from './components/contact-points/NewMessageTemplate';
|
||||
import { NotificationTemplates } from './components/contact-points/NotificationTemplates';
|
||||
import { shouldUseAlertingNavigationV2 } from './featureToggles';
|
||||
import { AlertmanagerAction, useAlertmanagerAbility } from './hooks/useAbilities';
|
||||
import { useNotificationConfigNav } from './navigation/useNotificationConfigNav';
|
||||
import { withPageErrorBoundary } from './withPageErrorBoundary';
|
||||
@@ -45,12 +44,9 @@ const TemplatesList = () => {
|
||||
};
|
||||
|
||||
function NotificationTemplatesRoutes() {
|
||||
const useV2Nav = shouldUseAlertingNavigationV2();
|
||||
|
||||
return (
|
||||
<Routes>
|
||||
{/* In V2 mode, show templates list on base route */}
|
||||
{useV2Nav && <Route path="" element={<TemplatesList />} />}
|
||||
<Route path="" element={<TemplatesList />} />
|
||||
<Route path="new" element={<NewMessageTemplate />} />
|
||||
<Route path=":name/edit" element={<EditMessageTemplate />} />
|
||||
<Route path=":name/duplicate" element={<DuplicateMessageTemplate />} />
|
||||
@@ -59,20 +55,13 @@ function NotificationTemplatesRoutes() {
|
||||
}
|
||||
|
||||
function NotificationTemplatesPage() {
|
||||
const useV2Nav = shouldUseAlertingNavigationV2();
|
||||
const { navId, pageNav } = useNotificationConfigNav();
|
||||
|
||||
// In V2 mode, wrap with page wrapper for proper navigation
|
||||
if (useV2Nav) {
|
||||
return (
|
||||
<AlertmanagerPageWrapper navId={navId || 'receivers'} pageNav={pageNav} accessType="notification">
|
||||
<NotificationTemplatesRoutes />
|
||||
</AlertmanagerPageWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
// In legacy mode, just render routes (templates are accessed via ContactPoints page tabs)
|
||||
return <NotificationTemplatesRoutes />;
|
||||
return (
|
||||
<AlertmanagerPageWrapper navId={navId || 'receivers'} pageNav={pageNav} accessType="notification">
|
||||
<NotificationTemplatesRoutes />
|
||||
</AlertmanagerPageWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
export default withPageErrorBoundary(NotificationTemplatesPage);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { render, screen, testWithFeatureToggles } from 'test/test-utils';
|
||||
import { render, screen } from 'test/test-utils';
|
||||
|
||||
import { configureStore } from 'app/store/configureStore';
|
||||
import { AccessControlAction } from 'app/types/accessControl';
|
||||
@@ -20,62 +20,46 @@ const alertManager = mockDataSource({
|
||||
});
|
||||
|
||||
describe('TimeIntervalsPage', () => {
|
||||
describe('V2 Navigation Mode', () => {
|
||||
testWithFeatureToggles({ enable: ['alertingNavigationV2'] });
|
||||
beforeEach(() => {
|
||||
setupDataSources(alertManager);
|
||||
setAlertmanagerConfig(GRAFANA_RULES_SOURCE_NAME, defaultConfig);
|
||||
setTimeIntervalsListEmpty(); // Mock empty time intervals list so component renders
|
||||
grantUserPermissions([
|
||||
AccessControlAction.AlertingNotificationsRead,
|
||||
AccessControlAction.AlertingTimeIntervalsRead,
|
||||
]);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
setupDataSources(alertManager);
|
||||
setAlertmanagerConfig(GRAFANA_RULES_SOURCE_NAME, defaultConfig);
|
||||
setTimeIntervalsListEmpty(); // Mock empty time intervals list so component renders
|
||||
grantUserPermissions([
|
||||
AccessControlAction.AlertingNotificationsRead,
|
||||
AccessControlAction.AlertingTimeIntervalsRead,
|
||||
]);
|
||||
it('renders time intervals table', async () => {
|
||||
const mockNavIndex = {
|
||||
'notification-config': {
|
||||
id: 'notification-config',
|
||||
text: 'Notification configuration',
|
||||
url: '/alerting/notifications',
|
||||
},
|
||||
'notification-config-time-intervals': {
|
||||
id: 'notification-config-time-intervals',
|
||||
text: 'Time intervals',
|
||||
url: '/alerting/time-intervals',
|
||||
},
|
||||
};
|
||||
const store = configureStore({
|
||||
navIndex: mockNavIndex,
|
||||
});
|
||||
|
||||
it('renders time intervals table', async () => {
|
||||
const mockNavIndex = {
|
||||
'notification-config': {
|
||||
id: 'notification-config',
|
||||
text: 'Notification configuration',
|
||||
url: '/alerting/notifications',
|
||||
},
|
||||
'notification-config-time-intervals': {
|
||||
id: 'notification-config-time-intervals',
|
||||
text: 'Time intervals',
|
||||
url: '/alerting/time-intervals',
|
||||
},
|
||||
};
|
||||
const store = configureStore({
|
||||
navIndex: mockNavIndex,
|
||||
});
|
||||
|
||||
render(<TimeIntervalsPage />, {
|
||||
store,
|
||||
historyOptions: {
|
||||
initialEntries: ['/alerting/time-intervals'],
|
||||
},
|
||||
});
|
||||
|
||||
// Should show time intervals content
|
||||
// When empty, it shows "You haven't created any time intervals yet"
|
||||
// When loading, it shows "Loading time intervals..."
|
||||
// When error, it shows "Error loading time intervals"
|
||||
// All contain "time intervals" - use getAllByText since there are multiple matches (tab, description, empty state)
|
||||
const timeIntervalsTexts = await screen.findAllByText(/time intervals/i, {}, { timeout: 5000 });
|
||||
expect(timeIntervalsTexts.length).toBeGreaterThan(0);
|
||||
render(<TimeIntervalsPage />, {
|
||||
store,
|
||||
historyOptions: {
|
||||
initialEntries: ['/alerting/time-intervals'],
|
||||
},
|
||||
});
|
||||
|
||||
it('returns null in legacy mode', () => {
|
||||
// This test verifies that the component returns null when V2 is disabled
|
||||
// The feature toggle is controlled by testWithFeatureToggles, so we test it separately
|
||||
const { container } = render(<TimeIntervalsPage />, {
|
||||
historyOptions: {
|
||||
initialEntries: ['/alerting/time-intervals'],
|
||||
},
|
||||
});
|
||||
// In V2 mode (enabled by testWithFeatureToggles), it should render content
|
||||
expect(container).not.toBeEmptyDOMElement();
|
||||
});
|
||||
// Should show time intervals content
|
||||
// When empty, it shows "You haven't created any time intervals yet"
|
||||
// When loading, it shows "Loading time intervals..."
|
||||
// When error, it shows "Error loading time intervals"
|
||||
// All contain "time intervals" - use getAllByText since there are multiple matches (tab, description, empty state)
|
||||
const timeIntervalsTexts = await screen.findAllByText(/time intervals/i, {}, { timeout: 5000 });
|
||||
expect(timeIntervalsTexts.length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { AlertmanagerPageWrapper } from './components/AlertingPageWrapper';
|
||||
import { GrafanaAlertmanagerWarning } from './components/GrafanaAlertmanagerWarning';
|
||||
import { TimeIntervalsTable } from './components/mute-timings/MuteTimingsTable';
|
||||
import { shouldUseAlertingNavigationV2 } from './featureToggles';
|
||||
import { useNotificationConfigNav } from './navigation/useNotificationConfigNav';
|
||||
import { useAlertmanager } from './state/AlertmanagerContext';
|
||||
import { withPageErrorBoundary } from './withPageErrorBoundary';
|
||||
@@ -20,21 +19,13 @@ function TimeIntervalsPageContent() {
|
||||
}
|
||||
|
||||
function TimeIntervalsPage() {
|
||||
const useV2Nav = shouldUseAlertingNavigationV2();
|
||||
const { navId, pageNav } = useNotificationConfigNav();
|
||||
|
||||
// In V2 mode, wrap with page wrapper for proper navigation
|
||||
// AlertmanagerPageWrapper provides AlertmanagerContext, so TimeIntervalsPageContent can use useAlertmanager
|
||||
if (useV2Nav) {
|
||||
return (
|
||||
<AlertmanagerPageWrapper navId={navId || 'am-routes'} pageNav={pageNav} accessType="notification">
|
||||
<TimeIntervalsPageContent />
|
||||
</AlertmanagerPageWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
// Legacy mode: not used (handled by NotificationPoliciesPage)
|
||||
return null;
|
||||
return (
|
||||
<AlertmanagerPageWrapper navId={navId || 'am-routes'} pageNav={pageNav} accessType="notification">
|
||||
<TimeIntervalsPageContent />
|
||||
</AlertmanagerPageWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
export default withPageErrorBoundary(TimeIntervalsPage);
|
||||
|
||||
+13
-17
@@ -178,28 +178,24 @@ describe('contact points', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('V2 Navigation Mode', () => {
|
||||
testWithFeatureToggles({ enable: ['alertingNavigationV2'] });
|
||||
test('shows only contact points without internal tabs', async () => {
|
||||
renderWithProvider(<ContactPointsPageContents />);
|
||||
|
||||
test('shows only contact points without internal tabs', async () => {
|
||||
renderWithProvider(<ContactPointsPageContents />);
|
||||
// Should show contact points directly
|
||||
expect(await screen.findByText(/create contact point/i)).toBeInTheDocument();
|
||||
|
||||
// Should show contact points directly
|
||||
expect(await screen.findByText(/create contact point/i)).toBeInTheDocument();
|
||||
// Should not have tabs
|
||||
expect(screen.queryByRole('tab')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Should not have tabs
|
||||
expect(screen.queryByRole('tab')).not.toBeInTheDocument();
|
||||
});
|
||||
test('does not show templates tab', async () => {
|
||||
renderWithProvider(<ContactPointsPageContents />);
|
||||
|
||||
test('does not show templates tab in V2 mode', async () => {
|
||||
renderWithProvider(<ContactPointsPageContents />);
|
||||
// Should show contact points
|
||||
expect(await screen.findByText(/create contact point/i)).toBeInTheDocument();
|
||||
|
||||
// Should show contact points
|
||||
expect(await screen.findByText(/create contact point/i)).toBeInTheDocument();
|
||||
|
||||
// Should not show templates tab
|
||||
expect(screen.queryByText(/notification templates/i)).not.toBeInTheDocument();
|
||||
});
|
||||
// Should not show templates tab
|
||||
expect(screen.queryByText(/notification templates/i)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
describe('templates tab', () => {
|
||||
|
||||
@@ -31,8 +31,3 @@ export const shouldUseFullyCompatibleBackendFilters = () =>
|
||||
* Saved searches feature - allows users to save and apply search queries on the Alert Rules page.
|
||||
*/
|
||||
export const shouldUseSavedSearches = () => config.featureToggles.alertingSavedSearches ?? false;
|
||||
|
||||
/**
|
||||
* New grouped navigation structure for Alerting
|
||||
*/
|
||||
export const shouldUseAlertingNavigationV2 = () => config.featureToggles.alertingNavigationV2 ?? false;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { getWrapper } from 'test/test-utils';
|
||||
|
||||
import { config } from '@grafana/runtime';
|
||||
import { configureStore } from 'app/store/configureStore';
|
||||
|
||||
import { useAlertActivityNav } from './useAlertActivityNav';
|
||||
@@ -23,58 +22,13 @@ describe('useAlertActivityNav', () => {
|
||||
text: 'Active notifications',
|
||||
url: '/alerting/groups',
|
||||
},
|
||||
groups: {
|
||||
id: 'groups',
|
||||
text: 'Alert groups',
|
||||
url: '/alerting/groups',
|
||||
},
|
||||
'alert-alerts': {
|
||||
id: 'alert-alerts',
|
||||
text: 'Alerts',
|
||||
url: '/alerting/alerts',
|
||||
},
|
||||
};
|
||||
|
||||
const defaultPreloadedState = {
|
||||
navIndex: mockNavIndex,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
config.featureToggles.alertingNavigationV2 = false;
|
||||
});
|
||||
|
||||
it('should return legacy navId when feature flag is off for /alerting/groups', () => {
|
||||
const wrapper = getWrapper({
|
||||
preloadedState: defaultPreloadedState,
|
||||
renderWithRouter: true,
|
||||
historyOptions: {
|
||||
initialEntries: ['/alerting/groups'],
|
||||
},
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useAlertActivityNav(), { wrapper });
|
||||
|
||||
expect(result.current.navId).toBe('groups');
|
||||
expect(result.current.pageNav).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return legacy navId when feature flag is off for /alerting/alerts', () => {
|
||||
const wrapper = getWrapper({
|
||||
preloadedState: defaultPreloadedState,
|
||||
renderWithRouter: true,
|
||||
historyOptions: {
|
||||
initialEntries: ['/alerting/alerts'],
|
||||
},
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useAlertActivityNav(), { wrapper });
|
||||
|
||||
expect(result.current.navId).toBe('alert-alerts');
|
||||
expect(result.current.pageNav).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return V2 navigation when feature flag is on for Alerts tab', () => {
|
||||
config.featureToggles.alertingNavigationV2 = true;
|
||||
it('should return navigation with pageNav for Alerts tab', () => {
|
||||
const store = configureStore(defaultPreloadedState);
|
||||
const wrapper = getWrapper({
|
||||
store,
|
||||
@@ -94,8 +48,7 @@ describe('useAlertActivityNav', () => {
|
||||
expect(result.current.pageNav?.text).toBe('Alert activity');
|
||||
});
|
||||
|
||||
it('should return V2 navigation when feature flag is on for Active notifications tab', () => {
|
||||
config.featureToggles.alertingNavigationV2 = true;
|
||||
it('should return navigation with pageNav for Active notifications tab', () => {
|
||||
const store = configureStore(defaultPreloadedState);
|
||||
const wrapper = getWrapper({
|
||||
store,
|
||||
@@ -116,7 +69,6 @@ describe('useAlertActivityNav', () => {
|
||||
});
|
||||
|
||||
it('should set active tab based on current path', () => {
|
||||
config.featureToggles.alertingNavigationV2 = true;
|
||||
const store = configureStore(defaultPreloadedState);
|
||||
const wrapper = getWrapper({
|
||||
store,
|
||||
@@ -138,7 +90,6 @@ describe('useAlertActivityNav', () => {
|
||||
});
|
||||
|
||||
it('should filter tabs based on permissions', () => {
|
||||
config.featureToggles.alertingNavigationV2 = true;
|
||||
const limitedNavIndex = {
|
||||
'alert-activity': mockNavIndex['alert-activity'],
|
||||
'alert-activity-alerts': mockNavIndex['alert-activity-alerts'],
|
||||
@@ -163,13 +114,9 @@ describe('useAlertActivityNav', () => {
|
||||
expect(result.current.pageNav?.children?.[0].id).toBe('alert-activity-alerts');
|
||||
});
|
||||
|
||||
it('should fallback to legacy when alert-activity nav is missing', () => {
|
||||
config.featureToggles.alertingNavigationV2 = true;
|
||||
it('should return undefined when alert-activity nav is missing', () => {
|
||||
const store = configureStore({
|
||||
navIndex: {
|
||||
groups: mockNavIndex.groups,
|
||||
'alert-alerts': mockNavIndex['alert-alerts'],
|
||||
},
|
||||
navIndex: {},
|
||||
});
|
||||
const wrapper = getWrapper({
|
||||
store,
|
||||
@@ -181,7 +128,7 @@ describe('useAlertActivityNav', () => {
|
||||
|
||||
const { result } = renderHook(() => useAlertActivityNav(), { wrapper });
|
||||
|
||||
expect(result.current.navId).toBe('groups');
|
||||
expect(result.current.navId).toBeUndefined();
|
||||
expect(result.current.pageNav).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,48 +4,12 @@ import { NavModelItem } from '@grafana/data';
|
||||
import { t } from '@grafana/i18n';
|
||||
import { useSelector } from 'app/types/store';
|
||||
|
||||
import { shouldUseAlertingNavigationV2 } from '../featureToggles';
|
||||
|
||||
export function useAlertActivityNav() {
|
||||
const location = useLocation();
|
||||
const navIndex = useSelector((state) => state.navIndex);
|
||||
const useV2Nav = shouldUseAlertingNavigationV2();
|
||||
|
||||
// If V2 navigation is not enabled, return legacy navId
|
||||
if (!useV2Nav) {
|
||||
if (location.pathname === '/alerting/groups') {
|
||||
return {
|
||||
navId: 'groups',
|
||||
pageNav: undefined,
|
||||
};
|
||||
}
|
||||
if (location.pathname === '/alerting/alerts') {
|
||||
return {
|
||||
navId: 'alert-alerts',
|
||||
pageNav: undefined,
|
||||
};
|
||||
}
|
||||
return {
|
||||
navId: undefined,
|
||||
pageNav: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
const alertActivityNav = navIndex['alert-activity'];
|
||||
if (!alertActivityNav) {
|
||||
// Fallback to legacy
|
||||
if (location.pathname === '/alerting/groups') {
|
||||
return {
|
||||
navId: 'groups',
|
||||
pageNav: undefined,
|
||||
};
|
||||
}
|
||||
if (location.pathname === '/alerting/alerts') {
|
||||
return {
|
||||
navId: 'alert-alerts',
|
||||
pageNav: undefined,
|
||||
};
|
||||
}
|
||||
return {
|
||||
navId: undefined,
|
||||
pageNav: undefined,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { getWrapper } from 'test/test-utils';
|
||||
|
||||
import { config } from '@grafana/runtime';
|
||||
import { configureStore } from 'app/store/configureStore';
|
||||
|
||||
import { useAlertRulesNav } from './useAlertRulesNav';
|
||||
@@ -24,38 +23,13 @@ describe('useAlertRulesNav', () => {
|
||||
text: 'Recently deleted',
|
||||
url: '/alerting/recently-deleted',
|
||||
},
|
||||
'alert-list': {
|
||||
id: 'alert-list',
|
||||
text: 'Alert rules',
|
||||
url: '/alerting/list',
|
||||
},
|
||||
};
|
||||
|
||||
const defaultPreloadedState = {
|
||||
navIndex: mockNavIndex,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
config.featureToggles.alertingNavigationV2 = false;
|
||||
});
|
||||
|
||||
it('should return legacy navId when feature flag is off', () => {
|
||||
const wrapper = getWrapper({
|
||||
preloadedState: defaultPreloadedState,
|
||||
renderWithRouter: true,
|
||||
historyOptions: {
|
||||
initialEntries: ['/alerting/list'],
|
||||
},
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useAlertRulesNav(), { wrapper });
|
||||
|
||||
expect(result.current.navId).toBe('alert-list');
|
||||
expect(result.current.pageNav).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return V2 navigation when feature flag is on', () => {
|
||||
config.featureToggles.alertingNavigationV2 = true;
|
||||
it('should return navigation with pageNav', () => {
|
||||
const store = configureStore(defaultPreloadedState);
|
||||
const wrapper = getWrapper({
|
||||
store,
|
||||
@@ -76,7 +50,6 @@ describe('useAlertRulesNav', () => {
|
||||
});
|
||||
|
||||
it('should filter tabs based on permissions', () => {
|
||||
config.featureToggles.alertingNavigationV2 = true;
|
||||
const limitedNavIndex = {
|
||||
'alert-rules': mockNavIndex['alert-rules'],
|
||||
'alert-rules-list': mockNavIndex['alert-rules-list'],
|
||||
@@ -102,7 +75,6 @@ describe('useAlertRulesNav', () => {
|
||||
});
|
||||
|
||||
it('should set active tab based on current path', () => {
|
||||
config.featureToggles.alertingNavigationV2 = true;
|
||||
const store = configureStore(defaultPreloadedState);
|
||||
const wrapper = getWrapper({
|
||||
store,
|
||||
|
||||
@@ -4,26 +4,14 @@ import { NavModelItem } from '@grafana/data';
|
||||
import { t } from '@grafana/i18n';
|
||||
import { useSelector } from 'app/types/store';
|
||||
|
||||
import { shouldUseAlertingNavigationV2 } from '../featureToggles';
|
||||
|
||||
export function useAlertRulesNav() {
|
||||
const location = useLocation();
|
||||
const navIndex = useSelector((state) => state.navIndex);
|
||||
const useV2Nav = shouldUseAlertingNavigationV2();
|
||||
|
||||
// If V2 navigation is not enabled, return legacy navId
|
||||
if (!useV2Nav) {
|
||||
return {
|
||||
navId: 'alert-list',
|
||||
pageNav: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
const alertRulesNav = navIndex['alert-rules'];
|
||||
if (!alertRulesNav) {
|
||||
// Fallback to legacy if V2 nav doesn't exist
|
||||
return {
|
||||
navId: 'alert-list',
|
||||
navId: undefined,
|
||||
pageNav: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { getWrapper } from 'test/test-utils';
|
||||
|
||||
import { config } from '@grafana/runtime';
|
||||
import { configureStore } from 'app/store/configureStore';
|
||||
|
||||
import { useInsightsNav } from './useInsightsNav';
|
||||
@@ -23,38 +22,13 @@ describe('useInsightsNav', () => {
|
||||
text: 'Alert state history',
|
||||
url: '/alerting/history',
|
||||
},
|
||||
'alerts-history': {
|
||||
id: 'alerts-history',
|
||||
text: 'History',
|
||||
url: '/alerting/history',
|
||||
},
|
||||
};
|
||||
|
||||
const defaultPreloadedState = {
|
||||
navIndex: mockNavIndex,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
config.featureToggles.alertingNavigationV2 = false;
|
||||
});
|
||||
|
||||
it('should return legacy navId when feature flag is off', () => {
|
||||
const wrapper = getWrapper({
|
||||
preloadedState: defaultPreloadedState,
|
||||
renderWithRouter: true,
|
||||
historyOptions: {
|
||||
initialEntries: ['/alerting/history'],
|
||||
},
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useInsightsNav(), { wrapper });
|
||||
|
||||
expect(result.current.navId).toBe('alerts-history');
|
||||
expect(result.current.pageNav).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return V2 navigation when feature flag is on', () => {
|
||||
config.featureToggles.alertingNavigationV2 = true;
|
||||
it('should return navigation with pageNav', () => {
|
||||
const store = configureStore(defaultPreloadedState);
|
||||
const wrapper = getWrapper({
|
||||
store,
|
||||
@@ -73,7 +47,6 @@ describe('useInsightsNav', () => {
|
||||
});
|
||||
|
||||
it('should set active tab based on current path', () => {
|
||||
config.featureToggles.alertingNavigationV2 = true;
|
||||
const store = configureStore(defaultPreloadedState);
|
||||
const wrapper = getWrapper({
|
||||
store,
|
||||
@@ -91,7 +64,6 @@ describe('useInsightsNav', () => {
|
||||
});
|
||||
|
||||
it('should filter tabs based on permissions', () => {
|
||||
config.featureToggles.alertingNavigationV2 = true;
|
||||
const limitedNavIndex = {
|
||||
insights: mockNavIndex.insights,
|
||||
'insights-system': mockNavIndex['insights-system'],
|
||||
|
||||
@@ -4,37 +4,12 @@ import { NavModelItem } from '@grafana/data';
|
||||
import { t } from '@grafana/i18n';
|
||||
import { useSelector } from 'app/types/store';
|
||||
|
||||
import { shouldUseAlertingNavigationV2 } from '../featureToggles';
|
||||
|
||||
export function useInsightsNav() {
|
||||
const location = useLocation();
|
||||
const navIndex = useSelector((state) => state.navIndex);
|
||||
const useV2Nav = shouldUseAlertingNavigationV2();
|
||||
|
||||
// If V2 navigation is not enabled, return legacy navId
|
||||
if (!useV2Nav) {
|
||||
if (location.pathname === '/alerting/history') {
|
||||
return {
|
||||
navId: 'alerts-history',
|
||||
pageNav: undefined,
|
||||
};
|
||||
}
|
||||
// For insights page, it doesn't exist in legacy, so return undefined
|
||||
return {
|
||||
navId: undefined,
|
||||
pageNav: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
const insightsNav = navIndex.insights;
|
||||
if (!insightsNav) {
|
||||
// Fallback to legacy
|
||||
if (location.pathname === '/alerting/history') {
|
||||
return {
|
||||
navId: 'alerts-history',
|
||||
pageNav: undefined,
|
||||
};
|
||||
}
|
||||
return {
|
||||
navId: undefined,
|
||||
pageNav: undefined,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { getWrapper } from 'test/test-utils';
|
||||
|
||||
import { config } from '@grafana/runtime';
|
||||
import { configureStore } from 'app/store/configureStore';
|
||||
|
||||
import { useNotificationConfigNav } from './useNotificationConfigNav';
|
||||
@@ -31,17 +30,7 @@ describe('useNotificationConfigNav', () => {
|
||||
'notification-config-time-intervals': {
|
||||
id: 'notification-config-time-intervals',
|
||||
text: 'Time intervals',
|
||||
url: '/alerting/routes?tab=time_intervals',
|
||||
},
|
||||
receivers: {
|
||||
id: 'receivers',
|
||||
text: 'Contact points',
|
||||
url: '/alerting/notifications',
|
||||
},
|
||||
'am-routes': {
|
||||
id: 'am-routes',
|
||||
text: 'Notification policies',
|
||||
url: '/alerting/routes',
|
||||
url: '/alerting/time-intervals',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -49,27 +38,7 @@ describe('useNotificationConfigNav', () => {
|
||||
navIndex: mockNavIndex,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
config.featureToggles.alertingNavigationV2 = false;
|
||||
});
|
||||
|
||||
it('should return legacy navId when feature flag is off', () => {
|
||||
const wrapper = getWrapper({
|
||||
preloadedState: defaultPreloadedState,
|
||||
renderWithRouter: true,
|
||||
historyOptions: {
|
||||
initialEntries: ['/alerting/notifications'],
|
||||
},
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useNotificationConfigNav(), { wrapper });
|
||||
|
||||
expect(result.current.navId).toBe('receivers');
|
||||
expect(result.current.pageNav).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return V2 navigation when feature flag is on', () => {
|
||||
config.featureToggles.alertingNavigationV2 = true;
|
||||
it('should return navigation with pageNav', () => {
|
||||
const store = configureStore(defaultPreloadedState);
|
||||
const wrapper = getWrapper({
|
||||
store,
|
||||
@@ -87,8 +56,7 @@ describe('useNotificationConfigNav', () => {
|
||||
expect(result.current.pageNav?.children).toBeDefined();
|
||||
});
|
||||
|
||||
it('should detect time intervals tab from V2 path', () => {
|
||||
config.featureToggles.alertingNavigationV2 = true;
|
||||
it('should detect time intervals tab from path', () => {
|
||||
const store = configureStore(defaultPreloadedState);
|
||||
const wrapper = getWrapper({
|
||||
store,
|
||||
@@ -108,7 +76,6 @@ describe('useNotificationConfigNav', () => {
|
||||
});
|
||||
|
||||
it('should filter tabs based on permissions', () => {
|
||||
config.featureToggles.alertingNavigationV2 = true;
|
||||
const limitedNavIndex = {
|
||||
'notification-config': mockNavIndex['notification-config'],
|
||||
'notification-config-contact-points': mockNavIndex['notification-config-contact-points'],
|
||||
|
||||
@@ -4,32 +4,9 @@ import { NavModelItem } from '@grafana/data';
|
||||
import { t } from '@grafana/i18n';
|
||||
import { useSelector } from 'app/types/store';
|
||||
|
||||
import { shouldUseAlertingNavigationV2 } from '../featureToggles';
|
||||
|
||||
export function useNotificationConfigNav() {
|
||||
const location = useLocation();
|
||||
const navIndex = useSelector((state) => state.navIndex);
|
||||
const useV2Nav = shouldUseAlertingNavigationV2();
|
||||
|
||||
// If V2 navigation is not enabled, return legacy navId based on current path
|
||||
if (!useV2Nav) {
|
||||
if (location.pathname.includes('/alerting/notifications/templates')) {
|
||||
return {
|
||||
navId: 'receivers',
|
||||
pageNav: undefined,
|
||||
};
|
||||
}
|
||||
if (location.pathname === '/alerting/routes') {
|
||||
return {
|
||||
navId: 'am-routes',
|
||||
pageNav: undefined,
|
||||
};
|
||||
}
|
||||
return {
|
||||
navId: 'receivers',
|
||||
pageNav: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
const notificationConfigNav = navIndex['notification-config'];
|
||||
if (!notificationConfigNav) {
|
||||
@@ -53,10 +30,7 @@ export function useNotificationConfigNav() {
|
||||
}
|
||||
|
||||
// Check if we're on the time intervals page
|
||||
// In V2 mode, check for dedicated route; in legacy mode, check for query param
|
||||
const isTimeIntervalsTab = useV2Nav
|
||||
? location.pathname === '/alerting/time-intervals'
|
||||
: location.pathname === '/alerting/routes' && location.search.includes('tab=time_intervals');
|
||||
const isTimeIntervalsTab = location.pathname === '/alerting/time-intervals';
|
||||
|
||||
// All available tabs
|
||||
const allTabs = [
|
||||
@@ -87,7 +61,7 @@ export function useNotificationConfigNav() {
|
||||
{
|
||||
id: 'notification-config-time-intervals',
|
||||
text: t('alerting.navigation.time-intervals', 'Time intervals'),
|
||||
url: useV2Nav ? '/alerting/time-intervals' : '/alerting/routes?tab=time_intervals',
|
||||
url: '/alerting/time-intervals',
|
||||
active: isTimeIntervalsTab,
|
||||
icon: 'clock-nine',
|
||||
parentItem: notificationConfigNav,
|
||||
|
||||
Reference in New Issue
Block a user