Remove Insights from navigation sidebar
Remove Insights from sidebar navigation to match main branch behavior. Insights should remain as a tab on the Home page, not a separate navigation item. Changes: - Remove Insights parent and tabs from backend navigation (navtree.go) - Remove /alerting/insights route from routes.tsx - Delete InsightsPage.tsx component - Delete useInsightsNav hook and test files - Update backend tests to remove Insights references All navigation tests pass successfully. 🤖 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
a5d240751d
commit
307cce059c
@@ -563,42 +563,7 @@ func (s *ServiceImpl) buildAlertNavLinks(c *contextmodel.ReqContext) *navtree.Na
|
||||
})
|
||||
}
|
||||
|
||||
// 4. Insights (parent with tabs: System Insights, Alert state history)
|
||||
var insightsChildren []*navtree.NavLink
|
||||
|
||||
// System Insights
|
||||
if hasAccess(ac.EvalAny(ac.EvalPermission(ac.ActionAlertingRuleRead), ac.EvalPermission(ac.ActionAlertingRuleExternalRead))) {
|
||||
insightsChildren = append(insightsChildren, &navtree.NavLink{
|
||||
Text: "System Insights", SubTitle: "View system insights and analytics", Id: "insights-system", Url: s.cfg.AppSubURL + "/alerting/insights", Icon: "chart-line",
|
||||
})
|
||||
}
|
||||
|
||||
// Alert state history
|
||||
//nolint:staticcheck // not yet migrated to OpenFeature
|
||||
if s.features.IsEnabled(c.Req.Context(), featuremgmt.FlagAlertingCentralAlertHistory) {
|
||||
if hasAccess(ac.EvalAny(ac.EvalPermission(ac.ActionAlertingRuleRead))) {
|
||||
insightsChildren = append(insightsChildren, &navtree.NavLink{
|
||||
Text: "Alert state history",
|
||||
SubTitle: "View a history of all alert events generated by your Grafana-managed alert rules. All alert events are displayed regardless of whether silences or mute timings are set.",
|
||||
Id: "insights-history",
|
||||
Url: s.cfg.AppSubURL + "/alerting/history",
|
||||
Icon: "history",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if len(insightsChildren) > 0 {
|
||||
alertChildNavs = append(alertChildNavs, &navtree.NavLink{
|
||||
Text: "Insights",
|
||||
SubTitle: "Analytics and history for alerting",
|
||||
Id: "insights",
|
||||
Url: s.cfg.AppSubURL + "/alerting/insights",
|
||||
Icon: "chart-line",
|
||||
Children: insightsChildren,
|
||||
})
|
||||
}
|
||||
|
||||
// 5. Settings (parent with tab: Settings)
|
||||
// 4. Settings (parent with tab: Settings)
|
||||
if c.GetOrgRole() == org.RoleAdmin {
|
||||
settingsChildren := []*navtree.NavLink{
|
||||
{
|
||||
|
||||
@@ -118,7 +118,7 @@ func TestBuildAlertNavLinks_FeatureToggle(t *testing.T) {
|
||||
require.NotEmpty(t, navLink.Children)
|
||||
|
||||
// Verify all expected parent items exist with children
|
||||
expectedParents := []string{"alert-rules", "notification-config", "insights", "alerting-settings"}
|
||||
expectedParents := []string{"alert-rules", "notification-config", "alerting-settings"}
|
||||
for _, parentId := range expectedParents {
|
||||
parent := findNavLink(navLink, parentId)
|
||||
require.NotNil(t, parent, "Should have %s parent in V2 navigation", parentId)
|
||||
@@ -170,7 +170,7 @@ func TestBuildAlertNavLinks_V2(t *testing.T) {
|
||||
require.NotEmpty(t, navLink.Children)
|
||||
|
||||
// Verify all parent items exist with children
|
||||
parentIds := []string{"alert-rules", "notification-config", "insights", "alerting-settings"}
|
||||
parentIds := []string{"alert-rules", "notification-config", "alerting-settings"}
|
||||
for _, parentId := range parentIds {
|
||||
parent := findNavLink(navLink, parentId)
|
||||
require.NotNil(t, parent, "Should have parent %s in V2 navigation", parentId)
|
||||
@@ -189,7 +189,6 @@ func TestBuildAlertNavLinks_V2(t *testing.T) {
|
||||
}{
|
||||
{"alert-rules", []string{"alert-rules-list", "alert-rules-recently-deleted"}},
|
||||
{"notification-config", []string{"notification-config-contact-points", "notification-config-policies", "notification-config-templates", "notification-config-time-intervals"}},
|
||||
{"insights", []string{"insights-system", "insights-history"}},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
||||
@@ -223,13 +223,6 @@ export function getAlertingRoutes(cfg = config): RouteDescriptor[] {
|
||||
)
|
||||
),
|
||||
},
|
||||
{
|
||||
path: '/alerting/insights',
|
||||
roles: evaluateAccess([AccessControlAction.AlertingRuleRead, AccessControlAction.AlertingRuleExternalRead]),
|
||||
component: importAlertingComponent(
|
||||
() => import(/* webpackChunkName: "InsightsPage" */ 'app/features/alerting/unified/insights/InsightsPage')
|
||||
),
|
||||
},
|
||||
{
|
||||
path: '/alerting/recently-deleted/',
|
||||
roles: () => ['Admin'],
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { Trans, t } from '@grafana/i18n';
|
||||
|
||||
import { AlertingPageWrapper } from '../components/AlertingPageWrapper';
|
||||
import { getInsightsScenes, insightsIsAvailable } from '../home/Insights';
|
||||
import { useInsightsNav } from '../navigation/useInsightsNav';
|
||||
import { isLocalDevEnv } from '../utils/misc';
|
||||
import { withPageErrorBoundary } from '../withPageErrorBoundary';
|
||||
|
||||
function InsightsPage() {
|
||||
const insightsEnabled = insightsIsAvailable() || isLocalDevEnv();
|
||||
const { navId, pageNav } = useInsightsNav();
|
||||
// Memoize the scene so it's only created once and properly initialized
|
||||
const insightsScene = useMemo(() => getInsightsScenes(), []);
|
||||
|
||||
if (!insightsEnabled) {
|
||||
return (
|
||||
<AlertingPageWrapper
|
||||
navId={navId || 'insights'}
|
||||
pageNav={pageNav}
|
||||
subTitle={t('alerting.insights.subtitle', 'Analytics and history for alerting')}
|
||||
>
|
||||
<div>
|
||||
<Trans i18nKey="alerting.insights.not-available">
|
||||
Insights are not available. Please configure the required data sources.
|
||||
</Trans>
|
||||
</div>
|
||||
</AlertingPageWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<AlertingPageWrapper
|
||||
navId={navId || 'insights'}
|
||||
pageNav={pageNav}
|
||||
subTitle={t('alerting.insights.subtitle', 'Analytics and history for alerting')}
|
||||
>
|
||||
<insightsScene.Component model={insightsScene} />
|
||||
</AlertingPageWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
export default withPageErrorBoundary(InsightsPage);
|
||||
@@ -1,118 +0,0 @@
|
||||
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';
|
||||
|
||||
describe('useInsightsNav', () => {
|
||||
const mockNavIndex = {
|
||||
insights: {
|
||||
id: 'insights',
|
||||
text: 'Insights',
|
||||
url: '/alerting/insights',
|
||||
},
|
||||
'insights-system': {
|
||||
id: 'insights-system',
|
||||
text: 'System Insights',
|
||||
url: '/alerting/insights',
|
||||
},
|
||||
'insights-history': {
|
||||
id: 'insights-history',
|
||||
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;
|
||||
const store = configureStore(defaultPreloadedState);
|
||||
const wrapper = getWrapper({
|
||||
store,
|
||||
renderWithRouter: true,
|
||||
historyOptions: {
|
||||
initialEntries: ['/alerting/insights'],
|
||||
},
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useInsightsNav(), { wrapper });
|
||||
|
||||
expect(result.current.navId).toBe('insights');
|
||||
expect(result.current.pageNav).toBeDefined();
|
||||
// eslint-disable-next-line testing-library/no-node-access
|
||||
expect(result.current.pageNav?.children).toBeDefined();
|
||||
});
|
||||
|
||||
it('should set active tab based on current path', () => {
|
||||
config.featureToggles.alertingNavigationV2 = true;
|
||||
const store = configureStore(defaultPreloadedState);
|
||||
const wrapper = getWrapper({
|
||||
store,
|
||||
renderWithRouter: true,
|
||||
historyOptions: {
|
||||
initialEntries: ['/alerting/history'],
|
||||
},
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useInsightsNav(), { wrapper });
|
||||
|
||||
// eslint-disable-next-line testing-library/no-node-access
|
||||
const historyTab = result.current.pageNav?.children?.find((tab) => tab.id === 'insights-history');
|
||||
expect(historyTab?.active).toBe(true);
|
||||
});
|
||||
|
||||
it('should filter tabs based on permissions', () => {
|
||||
config.featureToggles.alertingNavigationV2 = true;
|
||||
const limitedNavIndex = {
|
||||
insights: mockNavIndex.insights,
|
||||
'insights-system': mockNavIndex['insights-system'],
|
||||
// Missing 'insights-history' - user doesn't have permission
|
||||
};
|
||||
const store = configureStore({
|
||||
navIndex: limitedNavIndex,
|
||||
});
|
||||
const wrapper = getWrapper({
|
||||
store,
|
||||
renderWithRouter: true,
|
||||
historyOptions: {
|
||||
initialEntries: ['/alerting/insights'],
|
||||
},
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useInsightsNav(), { wrapper });
|
||||
|
||||
// eslint-disable-next-line testing-library/no-node-access
|
||||
expect(result.current.pageNav?.children?.length).toBe(1);
|
||||
// eslint-disable-next-line testing-library/no-node-access
|
||||
expect(result.current.pageNav?.children?.[0].id).toBe('insights-system');
|
||||
});
|
||||
});
|
||||
@@ -1,79 +0,0 @@
|
||||
import { useLocation } from 'react-router-dom-v5-compat';
|
||||
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
// All available tabs
|
||||
const allTabs = [
|
||||
{
|
||||
id: 'insights-system',
|
||||
text: t('alerting.navigation.system-insights', 'System Insights'),
|
||||
url: '/alerting/insights',
|
||||
active: location.pathname === '/alerting/insights',
|
||||
icon: 'chart-line',
|
||||
parentItem: insightsNav,
|
||||
},
|
||||
{
|
||||
id: 'insights-history',
|
||||
text: t('alerting.navigation.alert-state-history', 'Alert state history'),
|
||||
url: '/alerting/history',
|
||||
active: location.pathname === '/alerting/history',
|
||||
icon: 'history',
|
||||
parentItem: insightsNav,
|
||||
},
|
||||
].filter((tab) => {
|
||||
// Filter based on permissions - if nav item doesn't exist, user doesn't have permission
|
||||
const navItem = navIndex[tab.id];
|
||||
return navItem !== undefined;
|
||||
});
|
||||
|
||||
// Create pageNav that represents the Insights page with tabs as children
|
||||
const pageNav: NavModelItem = {
|
||||
...insightsNav,
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
children: allTabs as NavModelItem[],
|
||||
};
|
||||
|
||||
return {
|
||||
navId: 'insights',
|
||||
pageNav,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user