Remove feature flag and legacy navigation code
This commit removes the alertingNavigationV2 feature flag and all legacy navigation code, making the V2 navigation the only option. This simplifies the codebase by removing: - Feature flag checks in backend and frontend - buildAlertNavLinksLegacy function (114 lines) - Legacy tab code in ContactPoints and NotificationPoliciesPage - Related test code for legacy navigation Backend changes: - navtree.go: Remove feature flag check, delete legacy function - navtree_alerting_test.go: Remove legacy tests, update V2 tests Frontend changes: - Home.tsx: Remove feature flag, Insights tab always hidden - ContactPoints.tsx: Remove tabs, show only contact points - NotificationPoliciesPage.tsx: Remove tabs, show only policies This branch is designed for a one-time change to reduce change surface compared to maintaining both legacy and V2 navigation. 🤖 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
774551589b
commit
b3625b95e3
@@ -433,12 +433,7 @@ func (s *ServiceImpl) buildDashboardNavLinks(c *contextmodel.ReqContext) []*navt
|
||||
}
|
||||
|
||||
func (s *ServiceImpl) buildAlertNavLinks(c *contextmodel.ReqContext) *navtree.NavLink {
|
||||
//nolint:staticcheck // not yet migrated to OpenFeature
|
||||
if !s.features.IsEnabled(c.Req.Context(), featuremgmt.FlagAlertingNavigationV2) {
|
||||
return s.buildAlertNavLinksLegacy(c)
|
||||
}
|
||||
|
||||
// V2 Navigation - New grouped structure
|
||||
// V2 Navigation - Grouped structure
|
||||
hasAccess := ac.HasAccess(s.accessControl, c)
|
||||
var alertChildNavs []*navtree.NavLink
|
||||
|
||||
@@ -640,121 +635,6 @@ func (s *ServiceImpl) buildAlertNavLinks(c *contextmodel.ReqContext) *navtree.Na
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ServiceImpl) buildAlertNavLinksLegacy(c *contextmodel.ReqContext) *navtree.NavLink {
|
||||
hasAccess := ac.HasAccess(s.accessControl, c)
|
||||
var alertChildNavs []*navtree.NavLink
|
||||
|
||||
//nolint:staticcheck // not yet migrated to OpenFeature
|
||||
if s.features.IsEnabled(c.Req.Context(), featuremgmt.FlagAlertingTriage) {
|
||||
if hasAccess(ac.EvalAny(ac.EvalPermission(ac.ActionAlertingRuleRead), ac.EvalPermission(ac.ActionAlertingRuleExternalRead))) {
|
||||
alertChildNavs = append(alertChildNavs, &navtree.NavLink{
|
||||
Text: "Alert activity", SubTitle: "Visualize active and pending alerts", Id: "alert-alerts", Url: s.cfg.AppSubURL + "/alerting/alerts", Icon: "bell", IsNew: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if hasAccess(ac.EvalAny(ac.EvalPermission(ac.ActionAlertingRuleRead), ac.EvalPermission(ac.ActionAlertingRuleExternalRead))) {
|
||||
alertChildNavs = append(alertChildNavs, &navtree.NavLink{
|
||||
Text: "Alert rules", SubTitle: "Rules that determine whether an alert will fire", Id: "alert-list", Url: s.cfg.AppSubURL + "/alerting/list", Icon: "list-ul",
|
||||
})
|
||||
}
|
||||
|
||||
contactPointsPerms := []ac.Evaluator{
|
||||
ac.EvalPermission(ac.ActionAlertingNotificationsRead),
|
||||
ac.EvalPermission(ac.ActionAlertingNotificationsExternalRead),
|
||||
|
||||
ac.EvalPermission(ac.ActionAlertingReceiversRead),
|
||||
ac.EvalPermission(ac.ActionAlertingReceiversReadSecrets),
|
||||
ac.EvalPermission(ac.ActionAlertingReceiversCreate),
|
||||
|
||||
ac.EvalPermission(ac.ActionAlertingNotificationsTemplatesRead),
|
||||
ac.EvalPermission(ac.ActionAlertingNotificationsTemplatesWrite),
|
||||
ac.EvalPermission(ac.ActionAlertingNotificationsTemplatesDelete),
|
||||
}
|
||||
|
||||
if hasAccess(ac.EvalAny(contactPointsPerms...)) {
|
||||
alertChildNavs = append(alertChildNavs, &navtree.NavLink{
|
||||
Text: "Contact points", SubTitle: "Choose how to notify your contact points when an alert instance fires", Id: "receivers", Url: s.cfg.AppSubURL + "/alerting/notifications",
|
||||
Icon: "comment-alt-share",
|
||||
})
|
||||
}
|
||||
|
||||
if hasAccess(ac.EvalAny(
|
||||
ac.EvalPermission(ac.ActionAlertingNotificationsRead),
|
||||
ac.EvalPermission(ac.ActionAlertingNotificationsExternalRead),
|
||||
ac.EvalPermission(ac.ActionAlertingRoutesRead),
|
||||
ac.EvalPermission(ac.ActionAlertingRoutesWrite),
|
||||
ac.EvalPermission(ac.ActionAlertingNotificationsTimeIntervalsRead),
|
||||
ac.EvalPermission(ac.ActionAlertingNotificationsTimeIntervalsWrite),
|
||||
)) {
|
||||
alertChildNavs = append(alertChildNavs, &navtree.NavLink{Text: "Notification policies", SubTitle: "Determine how alerts are routed to contact points", Id: "am-routes", Url: s.cfg.AppSubURL + "/alerting/routes", Icon: "sitemap"})
|
||||
}
|
||||
|
||||
if hasAccess(ac.EvalAny(
|
||||
ac.EvalPermission(ac.ActionAlertingInstanceRead),
|
||||
ac.EvalPermission(ac.ActionAlertingInstancesExternalRead),
|
||||
ac.EvalPermission(ac.ActionAlertingSilencesRead),
|
||||
)) {
|
||||
alertChildNavs = append(alertChildNavs, &navtree.NavLink{Text: "Silences", SubTitle: "Stop notifications from one or more alerting rules", Id: "silences", Url: s.cfg.AppSubURL + "/alerting/silences", Icon: "bell-slash"})
|
||||
}
|
||||
|
||||
if hasAccess(ac.EvalAny(ac.EvalPermission(ac.ActionAlertingInstanceRead), ac.EvalPermission(ac.ActionAlertingInstancesExternalRead))) {
|
||||
alertChildNavs = append(alertChildNavs, &navtree.NavLink{Text: "Alert groups", SubTitle: "See grouped alerts with active notifications", Id: "groups", Url: s.cfg.AppSubURL + "/alerting/groups", Icon: "layer-group"})
|
||||
}
|
||||
|
||||
//nolint:staticcheck // not yet migrated to OpenFeature
|
||||
if s.features.IsEnabled(c.Req.Context(), featuremgmt.FlagAlertingCentralAlertHistory) {
|
||||
if hasAccess(ac.EvalAny(ac.EvalPermission(ac.ActionAlertingRuleRead))) {
|
||||
alertChildNavs = append(alertChildNavs, &navtree.NavLink{
|
||||
Text: "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: "alerts-history",
|
||||
Url: s.cfg.AppSubURL + "/alerting/history",
|
||||
Icon: "history",
|
||||
})
|
||||
}
|
||||
}
|
||||
//nolint:staticcheck // not yet migrated to OpenFeature
|
||||
if c.GetOrgRole() == org.RoleAdmin && s.features.IsEnabled(c.Req.Context(), featuremgmt.FlagAlertRuleRestore) && s.features.IsEnabled(c.Req.Context(), featuremgmt.FlagAlertingRuleRecoverDeleted) {
|
||||
alertChildNavs = append(alertChildNavs, &navtree.NavLink{
|
||||
Text: "Recently deleted",
|
||||
SubTitle: "Any items listed here for more than 30 days will be automatically deleted.",
|
||||
Id: "alerts/recently-deleted",
|
||||
Url: s.cfg.AppSubURL + "/alerting/recently-deleted",
|
||||
})
|
||||
}
|
||||
|
||||
if c.GetOrgRole() == org.RoleAdmin {
|
||||
alertChildNavs = append(alertChildNavs, &navtree.NavLink{
|
||||
Text: "Settings", Id: "alerting-admin", Url: s.cfg.AppSubURL + "/alerting/admin",
|
||||
Icon: "cog",
|
||||
})
|
||||
}
|
||||
|
||||
if hasAccess(ac.EvalAny(ac.EvalPermission(ac.ActionAlertingRuleCreate), ac.EvalPermission(ac.ActionAlertingRuleExternalWrite))) {
|
||||
alertChildNavs = append(alertChildNavs, &navtree.NavLink{
|
||||
Text: "Create alert rule", SubTitle: "Create an alert rule", Id: "alert",
|
||||
Icon: "plus", Url: s.cfg.AppSubURL + "/alerting/new", HideFromTabs: true, IsCreateAction: true,
|
||||
})
|
||||
}
|
||||
|
||||
if len(alertChildNavs) > 0 {
|
||||
var alertNav = navtree.NavLink{
|
||||
Text: "Alerting",
|
||||
SubTitle: "Learn about problems in your systems moments after they occur",
|
||||
Id: navtree.NavIDAlerting,
|
||||
Icon: "bell",
|
||||
Children: alertChildNavs,
|
||||
SortWeight: navtree.WeightAlerting,
|
||||
Url: s.cfg.AppSubURL + "/alerting",
|
||||
}
|
||||
|
||||
return &alertNav
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ServiceImpl) buildDataConnectionsNavLink(c *contextmodel.ReqContext) *navtree.NavLink {
|
||||
hasAccess := ac.HasAccess(s.accessControl, c)
|
||||
|
||||
|
||||
@@ -83,88 +83,12 @@ func hasChildWithId(parent *navtree.NavLink, childId string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func TestBuildAlertNavLinks_FeatureToggle(t *testing.T) {
|
||||
func TestBuildAlertNavLinks(t *testing.T) {
|
||||
reqCtx := setupTestContext()
|
||||
permissions := fullPermissions()
|
||||
|
||||
t.Run("Should use legacy navigation when flag is off", func(t *testing.T) {
|
||||
service := setupTestService(permissions) // No feature flags
|
||||
|
||||
navLink := service.buildAlertNavLinks(reqCtx)
|
||||
require.NotNil(t, navLink)
|
||||
require.Equal(t, "Alerting", navLink.Text)
|
||||
require.Equal(t, navtree.NavIDAlerting, navLink.Id)
|
||||
|
||||
// Legacy structure: flat children without nested items
|
||||
require.NotEmpty(t, navLink.Children)
|
||||
alertList := findNavLink(navLink, "alert-list")
|
||||
receivers := findNavLink(navLink, "receivers")
|
||||
|
||||
require.NotNil(t, alertList, "Should have alert-list in legacy navigation")
|
||||
require.NotNil(t, receivers, "Should have receivers in legacy navigation")
|
||||
require.Empty(t, alertList.Children, "Legacy items should not have nested children")
|
||||
require.Empty(t, receivers.Children, "Legacy items should not have nested children")
|
||||
})
|
||||
|
||||
t.Run("Should use V2 navigation when flag is on", func(t *testing.T) {
|
||||
service := setupTestService(permissions, "alertingNavigationV2")
|
||||
|
||||
navLink := service.buildAlertNavLinks(reqCtx)
|
||||
require.NotNil(t, navLink)
|
||||
require.Equal(t, "Alerting", navLink.Text)
|
||||
require.Equal(t, navtree.NavIDAlerting, navLink.Id)
|
||||
|
||||
// V2 structure: grouped parents with nested children
|
||||
require.NotEmpty(t, navLink.Children)
|
||||
|
||||
// Verify all expected parent items exist with children
|
||||
expectedParents := []string{"alert-rules", "notification-config", "insights", "alerting-settings"}
|
||||
for _, parentId := range expectedParents {
|
||||
parent := findNavLink(navLink, parentId)
|
||||
require.NotNil(t, parent, "Should have %s parent in V2 navigation", parentId)
|
||||
require.NotEmpty(t, parent.Children, "V2 parent %s should have children", parentId)
|
||||
}
|
||||
|
||||
// Verify alert-rules has expected tab
|
||||
alertRules := findNavLink(navLink, "alert-rules")
|
||||
require.True(t, hasChildWithId(alertRules, "alert-rules-list"), "Should have alert-rules-list tab")
|
||||
})
|
||||
}
|
||||
|
||||
func TestBuildAlertNavLinks_Legacy(t *testing.T) {
|
||||
reqCtx := setupTestContext()
|
||||
|
||||
t.Run("Should include all expected items in legacy navigation", func(t *testing.T) {
|
||||
service := setupTestService(fullPermissions())
|
||||
navLink := service.buildAlertNavLinksLegacy(reqCtx)
|
||||
require.NotNil(t, navLink)
|
||||
|
||||
expectedIds := []string{"alert-list", "receivers", "am-routes", "alerting-admin"}
|
||||
for _, expectedId := range expectedIds {
|
||||
require.NotNil(t, findNavLink(navLink, expectedId), "Should have %s in legacy navigation", expectedId)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Should respect permissions in legacy navigation", func(t *testing.T) {
|
||||
limitedPermissions := []ac.Permission{
|
||||
{Action: ac.ActionAlertingRuleRead, Scope: "*"},
|
||||
}
|
||||
limitedService := setupTestService(limitedPermissions)
|
||||
|
||||
navLink := limitedService.buildAlertNavLinksLegacy(reqCtx)
|
||||
require.NotNil(t, navLink)
|
||||
|
||||
require.NotNil(t, findNavLink(navLink, "alert-list"), "Should have alert rules with read permission")
|
||||
require.Nil(t, findNavLink(navLink, "receivers"), "Should not have contact points without notification permissions")
|
||||
})
|
||||
}
|
||||
|
||||
func TestBuildAlertNavLinks_V2(t *testing.T) {
|
||||
reqCtx := setupTestContext()
|
||||
allFeatureFlags := []string{"alertingNavigationV2", "alertingTriage", "alertingCentralAlertHistory", "alertRuleRestore", "alertingRuleRecoverDeleted"}
|
||||
allFeatureFlags := []string{"alertingTriage", "alertingCentralAlertHistory", "alertRuleRestore", "alertingRuleRecoverDeleted"}
|
||||
service := setupTestService(fullPermissions(), allFeatureFlags...)
|
||||
|
||||
t.Run("Should have correct parent structure in V2 navigation", func(t *testing.T) {
|
||||
t.Run("Should have correct parent structure", func(t *testing.T) {
|
||||
navLink := service.buildAlertNavLinks(reqCtx)
|
||||
require.NotNil(t, navLink)
|
||||
require.NotEmpty(t, navLink.Children)
|
||||
@@ -173,7 +97,7 @@ func TestBuildAlertNavLinks_V2(t *testing.T) {
|
||||
parentIds := []string{"alert-rules", "notification-config", "insights", "alerting-settings"}
|
||||
for _, parentId := range parentIds {
|
||||
parent := findNavLink(navLink, parentId)
|
||||
require.NotNil(t, parent, "Should have parent %s in V2 navigation", parentId)
|
||||
require.NotNil(t, parent, "Should have parent %s", parentId)
|
||||
require.NotEmpty(t, parent.Children, "Parent %s should have children", parentId)
|
||||
}
|
||||
})
|
||||
@@ -202,11 +126,11 @@ func TestBuildAlertNavLinks_V2(t *testing.T) {
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Should respect permissions in V2 navigation", func(t *testing.T) {
|
||||
t.Run("Should respect permissions", func(t *testing.T) {
|
||||
limitedPermissions := []ac.Permission{
|
||||
{Action: ac.ActionAlertingRuleRead, Scope: "*"},
|
||||
}
|
||||
limitedService := setupTestService(limitedPermissions, "alertingNavigationV2")
|
||||
limitedService := setupTestService(limitedPermissions)
|
||||
|
||||
navLink := limitedService.buildAlertNavLinks(reqCtx)
|
||||
require.NotNil(t, navLink)
|
||||
@@ -215,7 +139,7 @@ func TestBuildAlertNavLinks_V2(t *testing.T) {
|
||||
require.Nil(t, findNavLink(navLink, "notification-config"), "Should not have notification-config without permissions")
|
||||
})
|
||||
|
||||
t.Run("Should exclude future items from V2 navigation", func(t *testing.T) {
|
||||
t.Run("Should exclude future items", func(t *testing.T) {
|
||||
navLink := service.buildAlertNavLinks(reqCtx)
|
||||
require.NotNil(t, navLink)
|
||||
|
||||
|
||||
@@ -1,113 +1,11 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { GrafanaTheme2, UrlQueryMap } from '@grafana/data';
|
||||
import { t } from '@grafana/i18n';
|
||||
import { Tab, TabContent, TabsBar, useStyles2 } from '@grafana/ui';
|
||||
import { useQueryParams } from 'app/core/hooks/useQueryParams';
|
||||
import { useMuteTimings } from 'app/features/alerting/unified/components/mute-timings/useMuteTimings';
|
||||
import { NotificationPoliciesList } from 'app/features/alerting/unified/components/notification-policies/NotificationPoliciesList';
|
||||
import { AlertmanagerAction, useAlertmanagerAbility } from 'app/features/alerting/unified/hooks/useAbilities';
|
||||
|
||||
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';
|
||||
|
||||
enum ActiveTab {
|
||||
NotificationPolicies = 'notification_policies',
|
||||
TimeIntervals = 'time_intervals',
|
||||
}
|
||||
|
||||
const NotificationPoliciesTabs = () => {
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
// Alertmanager logic and data hooks
|
||||
const { selectedAlertmanager = '' } = useAlertmanager();
|
||||
const [policiesSupported, canSeePoliciesTab] = useAlertmanagerAbility(AlertmanagerAction.ViewNotificationPolicyTree);
|
||||
const [timingsSupported, canSeeTimingsTab] = useAlertmanagerAbility(AlertmanagerAction.ViewTimeInterval);
|
||||
const availableTabs = [
|
||||
canSeePoliciesTab && ActiveTab.NotificationPolicies,
|
||||
canSeeTimingsTab && ActiveTab.TimeIntervals,
|
||||
].filter((tab) => !!tab);
|
||||
const { data: muteTimings = [] } = useMuteTimings({
|
||||
alertmanager: selectedAlertmanager,
|
||||
skip: !canSeeTimingsTab,
|
||||
});
|
||||
|
||||
// Tab state management
|
||||
const [queryParams, setQueryParams] = useQueryParams();
|
||||
const { tab } = getActiveTabFromUrl(queryParams, availableTabs[0]);
|
||||
const [activeTab, setActiveTab] = useState<ActiveTab>(tab);
|
||||
|
||||
const muteTimingsTabActive = activeTab === ActiveTab.TimeIntervals;
|
||||
const policyTreeTabActive = activeTab === ActiveTab.NotificationPolicies;
|
||||
|
||||
const numberOfMuteTimings = muteTimings.length;
|
||||
|
||||
return (
|
||||
<>
|
||||
<GrafanaAlertmanagerWarning currentAlertmanager={selectedAlertmanager} />
|
||||
<TabsBar>
|
||||
{policiesSupported && canSeePoliciesTab && (
|
||||
<Tab
|
||||
label={t('alerting.notification-policies-tabs.label-notification-policies', 'Notification Policies')}
|
||||
active={policyTreeTabActive}
|
||||
onChangeTab={() => {
|
||||
setActiveTab(ActiveTab.NotificationPolicies);
|
||||
setQueryParams({ tab: ActiveTab.NotificationPolicies });
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{timingsSupported && canSeeTimingsTab && (
|
||||
<Tab
|
||||
label={t('alerting.notification-policies-tabs.label-time-intervals', 'Time intervals')}
|
||||
active={muteTimingsTabActive}
|
||||
counter={numberOfMuteTimings}
|
||||
onChangeTab={() => {
|
||||
setActiveTab(ActiveTab.TimeIntervals);
|
||||
setQueryParams({ tab: ActiveTab.TimeIntervals });
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</TabsBar>
|
||||
<TabContent className={styles.tabContent}>
|
||||
{policyTreeTabActive && <NotificationPoliciesList />}
|
||||
{muteTimingsTabActive && <TimeIntervalsTable />}
|
||||
</TabContent>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => ({
|
||||
tabContent: css({
|
||||
marginTop: theme.spacing(2),
|
||||
}),
|
||||
});
|
||||
|
||||
interface QueryParamValues {
|
||||
tab: ActiveTab;
|
||||
}
|
||||
|
||||
function getActiveTabFromUrl(queryParams: UrlQueryMap, defaultTab: ActiveTab): QueryParamValues {
|
||||
let tab = defaultTab;
|
||||
|
||||
if (queryParams.tab === ActiveTab.NotificationPolicies) {
|
||||
tab = ActiveTab.NotificationPolicies;
|
||||
}
|
||||
|
||||
if (queryParams.tab === ActiveTab.TimeIntervals) {
|
||||
tab = ActiveTab.TimeIntervals;
|
||||
}
|
||||
|
||||
return {
|
||||
tab,
|
||||
};
|
||||
}
|
||||
|
||||
const NotificationPoliciesContent = () => {
|
||||
const { selectedAlertmanager = '' } = useAlertmanager();
|
||||
return (
|
||||
@@ -119,22 +17,13 @@ const NotificationPoliciesContent = () => {
|
||||
};
|
||||
|
||||
function NotificationPoliciesPage() {
|
||||
const useV2Nav = shouldUseAlertingNavigationV2();
|
||||
const { navId, pageNav } = useNotificationConfigNav();
|
||||
|
||||
// In V2 mode, show only notification policies (no internal tabs)
|
||||
if (useV2Nav) {
|
||||
return (
|
||||
<AlertmanagerPageWrapper navId={navId || 'am-routes'} pageNav={pageNav} accessType="notification">
|
||||
<NotificationPoliciesContent />
|
||||
</AlertmanagerPageWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
// Legacy mode: Show internal tabs (backward compatible)
|
||||
// Show only notification policies (no internal tabs)
|
||||
// Time intervals are accessible via the sidebar navigation
|
||||
return (
|
||||
<AlertmanagerPageWrapper navId={navId || 'am-routes'} pageNav={pageNav} accessType="notification">
|
||||
<NotificationPoliciesTabs />
|
||||
<NotificationPoliciesContent />
|
||||
</AlertmanagerPageWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,31 +1,12 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { Trans, t } from '@grafana/i18n';
|
||||
import {
|
||||
Alert,
|
||||
Button,
|
||||
EmptyState,
|
||||
LinkButton,
|
||||
LoadingPlaceholder,
|
||||
Pagination,
|
||||
Stack,
|
||||
Tab,
|
||||
TabContent,
|
||||
TabsBar,
|
||||
Text,
|
||||
useStyles2,
|
||||
} from '@grafana/ui';
|
||||
import { Alert, Button, EmptyState, LinkButton, LoadingPlaceholder, Pagination, Stack } from '@grafana/ui';
|
||||
import { contextSrv } from 'app/core/services/context_srv';
|
||||
import { shouldUseK8sApi } from 'app/features/alerting/unified/utils/k8s/utils';
|
||||
import { makeAMLink, stringifyErrorLike } from 'app/features/alerting/unified/utils/misc';
|
||||
import { AccessControlAction } from 'app/types/accessControl';
|
||||
|
||||
import { shouldUseAlertingNavigationV2 } from '../../featureToggles';
|
||||
import { AlertmanagerAction, useAlertmanagerAbility } from '../../hooks/useAbilities';
|
||||
import { usePagination } from '../../hooks/usePagination';
|
||||
import { useURLSearchParams } from '../../hooks/useURLSearchParams';
|
||||
import { useNotificationConfigNav } from '../../navigation/useNotificationConfigNav';
|
||||
import { useAlertmanager } from '../../state/AlertmanagerContext';
|
||||
import { isExtraConfig } from '../../utils/alertmanager/extraConfigs';
|
||||
@@ -35,7 +16,6 @@ import { AlertmanagerPageWrapper } from '../AlertingPageWrapper';
|
||||
import { GrafanaAlertmanagerWarning } from '../GrafanaAlertmanagerWarning';
|
||||
|
||||
import { ContactPoint } from './ContactPoint';
|
||||
import { NotificationTemplates } from './NotificationTemplates';
|
||||
import { ContactPointsFilter } from './components/ContactPointsFilter';
|
||||
import { GlobalConfigAlert } from './components/GlobalConfigAlert';
|
||||
import { useContactPointsWithStatus } from './useContactPoints';
|
||||
@@ -43,11 +23,6 @@ import { useContactPointsSearch } from './useContactPointsSearch';
|
||||
import { ALL_CONTACT_POINTS, useExportContactPoint } from './useExportContactPoint';
|
||||
import { ContactPointWithMetadata } from './utils';
|
||||
|
||||
export enum ActiveTab {
|
||||
ContactPoints = 'contact_points',
|
||||
NotificationTemplates = 'templates',
|
||||
}
|
||||
|
||||
const DEFAULT_PAGE_SIZE = 10;
|
||||
|
||||
const ContactPointsTab = () => {
|
||||
@@ -157,122 +132,15 @@ const ContactPointsTab = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const NotificationTemplatesTab = () => {
|
||||
const [createTemplateSupported, createTemplateAllowed] = useAlertmanagerAbility(
|
||||
AlertmanagerAction.CreateNotificationTemplate
|
||||
);
|
||||
|
||||
return (
|
||||
<Stack direction="column" gap={1}>
|
||||
<Stack direction="row" alignItems="center" justifyContent="space-between">
|
||||
<Text variant="body" color="secondary">
|
||||
<Trans i18nKey="alerting.notification-templates-tab.create-notification-templates-customize-notifications">
|
||||
Create notification templates to customize your notifications.
|
||||
</Trans>
|
||||
</Text>
|
||||
{createTemplateSupported && (
|
||||
<LinkButton
|
||||
icon="plus"
|
||||
variant="primary"
|
||||
href="/alerting/notifications/templates/new"
|
||||
disabled={!createTemplateAllowed}
|
||||
>
|
||||
<Trans i18nKey="alerting.notification-templates-tab.add-notification-template-group">
|
||||
Add notification template group
|
||||
</Trans>
|
||||
</LinkButton>
|
||||
)}
|
||||
</Stack>
|
||||
<NotificationTemplates />
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
const useTabQueryParam = (defaultTab: ActiveTab) => {
|
||||
const [queryParams, setQueryParams] = useURLSearchParams();
|
||||
const param = useMemo(() => {
|
||||
const queryParam = queryParams.get('tab');
|
||||
|
||||
if (!queryParam || !Object.values(ActiveTab).map(String).includes(queryParam)) {
|
||||
return defaultTab;
|
||||
}
|
||||
|
||||
return queryParam || defaultTab;
|
||||
}, [defaultTab, queryParams]);
|
||||
|
||||
const setParam = (tab: ActiveTab) => setQueryParams({ tab });
|
||||
return [param, setParam] as const;
|
||||
};
|
||||
|
||||
export const ContactPointsPageContents = () => {
|
||||
const { selectedAlertmanager } = useAlertmanager();
|
||||
const useV2Nav = shouldUseAlertingNavigationV2();
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
// All hooks must be called unconditionally before any early returns
|
||||
const [, canViewContactPoints] = useAlertmanagerAbility(AlertmanagerAction.ViewContactPoint);
|
||||
const [, canCreateContactPoints] = useAlertmanagerAbility(AlertmanagerAction.CreateContactPoint);
|
||||
const [, showTemplatesTab] = useAlertmanagerAbility(AlertmanagerAction.ViewNotificationTemplate);
|
||||
|
||||
const showContactPointsTab = canViewContactPoints || canCreateContactPoints;
|
||||
|
||||
// Depending on permissions, user may not have access to all tabs,
|
||||
// but we can default to picking the first one that they definitely _do_ have access to
|
||||
const defaultTab = [
|
||||
showContactPointsTab && ActiveTab.ContactPoints,
|
||||
showTemplatesTab && ActiveTab.NotificationTemplates,
|
||||
].filter((tab) => !!tab)[0];
|
||||
|
||||
const [activeTab, setActiveTab] = useTabQueryParam(defaultTab);
|
||||
|
||||
const { contactPoints } = useContactPointsWithStatus({
|
||||
alertmanager: selectedAlertmanager!,
|
||||
});
|
||||
|
||||
// In V2 navigation mode, show only contact points (no internal tabs)
|
||||
// Show only contact points (no internal tabs)
|
||||
// Templates are accessible via the sidebar navigation
|
||||
if (useV2Nav) {
|
||||
return (
|
||||
<>
|
||||
<GrafanaAlertmanagerWarning currentAlertmanager={selectedAlertmanager!} />
|
||||
<ContactPointsTab />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
// Legacy mode: Show internal tabs (backward compatible)
|
||||
|
||||
const showingContactPoints = activeTab === ActiveTab.ContactPoints;
|
||||
const showNotificationTemplates = activeTab === ActiveTab.NotificationTemplates;
|
||||
|
||||
return (
|
||||
<>
|
||||
<GrafanaAlertmanagerWarning currentAlertmanager={selectedAlertmanager!} />
|
||||
<Stack direction="column">
|
||||
<TabsBar>
|
||||
{showContactPointsTab && (
|
||||
<Tab
|
||||
label={t('alerting.contact-points-page-contents.label-contact-points', 'Contact Points')}
|
||||
active={showingContactPoints}
|
||||
counter={contactPoints.length}
|
||||
onChangeTab={() => setActiveTab(ActiveTab.ContactPoints)}
|
||||
/>
|
||||
)}
|
||||
{showTemplatesTab && (
|
||||
<Tab
|
||||
label={t('alerting.contact-points-page-contents.label-notification-templates', 'Notification Templates')}
|
||||
active={showNotificationTemplates}
|
||||
onChangeTab={() => setActiveTab(ActiveTab.NotificationTemplates)}
|
||||
/>
|
||||
)}
|
||||
</TabsBar>
|
||||
<TabContent className={styles.tabContent}>
|
||||
<Stack direction="column">
|
||||
{showingContactPoints && <ContactPointsTab />}
|
||||
{showNotificationTemplates && <NotificationTemplatesTab />}
|
||||
</Stack>
|
||||
</TabContent>
|
||||
</Stack>
|
||||
<ContactPointsTab />
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -303,12 +171,6 @@ const ContactPointsList = ({ contactPoints, search, pageSize = DEFAULT_PAGE_SIZE
|
||||
);
|
||||
};
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => ({
|
||||
tabContent: css({
|
||||
marginTop: theme.spacing(2),
|
||||
}),
|
||||
});
|
||||
|
||||
function ContactPointsPage() {
|
||||
const { navId, pageNav } = useNotificationConfigNav();
|
||||
return (
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useMemo, useState } from 'react';
|
||||
|
||||
import { t } from '@grafana/i18n';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { Box, Stack, Tab, TabContent, TabsBar } from '@grafana/ui';
|
||||
|
||||
import { AlertingPageWrapper } from '../components/AlertingPageWrapper';
|
||||
@@ -15,9 +14,8 @@ import { PluginIntegrations } from './PluginIntegrations';
|
||||
import SyntheticMonitoringCard from './SyntheticMonitoringCard';
|
||||
|
||||
function Home() {
|
||||
// When V2 navigation is enabled, don't show Insights tab on Home page
|
||||
// (Insights is available via the sidebar Insights menu instead)
|
||||
const insightsEnabled = (insightsIsAvailable() || isLocalDevEnv()) && !config.featureToggles.alertingNavigationV2;
|
||||
// Insights tab is not shown on Home page - Insights is available via the sidebar menu instead
|
||||
const insightsEnabled = false;
|
||||
|
||||
const [activeTab, setActiveTab] = useState<'insights' | 'overview'>(insightsEnabled ? 'insights' : 'overview');
|
||||
// Memoize the scene so it's only created once and properly initialized
|
||||
|
||||
Reference in New Issue
Block a user