mirror of https://github.com/grafana/grafana.git
Alerting: Add extension point for rule view page enrichment section (#110498)
* add enrichment to rule page view WIP * add Alert enrichment tab to rule view page * fix to view dummy component * move rule view enrichment tab to enterprise * remove better file changes * remove console log * add test for enrichment tab * run yarn i18n-extract * update directory structure * remove .betterer.results changes * Convert React.createElement call to JSX syntax * revert removed lines * revert removed lines * revert removed lines * fix failing test * fix lint error --------- Co-authored-by: Sonia Aguilar <soniaaguilarpeiron@gmail.com>
This commit is contained in:
parent
4e05bb36f2
commit
53cd0882ed
|
@ -1723,11 +1723,6 @@
|
||||||
"count": 1
|
"count": 1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"public/app/features/alerting/unified/components/rule-viewer/RuleViewer.tsx": {
|
|
||||||
"react-hooks/rules-of-hooks": {
|
|
||||||
"count": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"public/app/features/alerting/unified/components/rules/Filter/RulesFilter.v1.tsx": {
|
"public/app/features/alerting/unified/components/rules/Filter/RulesFilter.v1.tsx": {
|
||||||
"no-restricted-syntax": {
|
"no-restricted-syntax": {
|
||||||
"count": 4
|
"count": 4
|
||||||
|
|
|
@ -32,6 +32,7 @@ import { PromAlertingRuleState, PromRuleType } from 'app/types/unified-alerting-
|
||||||
|
|
||||||
import { logError } from '../../Analytics';
|
import { logError } from '../../Analytics';
|
||||||
import { defaultPageNav } from '../../RuleViewer';
|
import { defaultPageNav } from '../../RuleViewer';
|
||||||
|
import { useRuleViewExtensionsNav } from '../../enterprise-components/rule-view-page/navigation';
|
||||||
import { shouldUseAlertingListViewV2, shouldUsePrometheusRulesPrimary } from '../../featureToggles';
|
import { shouldUseAlertingListViewV2, shouldUsePrometheusRulesPrimary } from '../../featureToggles';
|
||||||
import { isError, useAsync } from '../../hooks/useAsync';
|
import { isError, useAsync } from '../../hooks/useAsync';
|
||||||
import { useRuleLocation } from '../../hooks/useCombinedRule';
|
import { useRuleLocation } from '../../hooks/useCombinedRule';
|
||||||
|
@ -71,6 +72,7 @@ import { History } from './tabs/History';
|
||||||
import { InstancesList } from './tabs/Instances';
|
import { InstancesList } from './tabs/Instances';
|
||||||
import { QueryResults } from './tabs/Query';
|
import { QueryResults } from './tabs/Query';
|
||||||
import { Routing } from './tabs/Routing';
|
import { Routing } from './tabs/Routing';
|
||||||
|
import { RulePageEnrichmentSectionExtension } from './tabs/extensions/RuleViewerExtension';
|
||||||
|
|
||||||
export enum ActiveTab {
|
export enum ActiveTab {
|
||||||
Query = 'query',
|
Query = 'query',
|
||||||
|
@ -79,6 +81,7 @@ export enum ActiveTab {
|
||||||
Routing = 'routing',
|
Routing = 'routing',
|
||||||
Details = 'details',
|
Details = 'details',
|
||||||
VersionHistory = 'version-history',
|
VersionHistory = 'version-history',
|
||||||
|
Enrichment = 'enrichment',
|
||||||
}
|
}
|
||||||
|
|
||||||
const prometheusRulesPrimary = shouldUsePrometheusRulesPrimary();
|
const prometheusRulesPrimary = shouldUsePrometheusRulesPrimary();
|
||||||
|
@ -87,6 +90,7 @@ const alertingListViewV2 = shouldUseAlertingListViewV2();
|
||||||
const RuleViewer = () => {
|
const RuleViewer = () => {
|
||||||
const { rule, identifier } = useAlertRule();
|
const { rule, identifier } = useAlertRule();
|
||||||
const { pageNav, activeTab } = usePageNav(rule);
|
const { pageNav, activeTab } = usePageNav(rule);
|
||||||
|
const styles = useStyles2(getStyles);
|
||||||
|
|
||||||
// GMA /api/v1/rules endpoint is strongly consistent, so we don't need to check for consistency
|
// GMA /api/v1/rules endpoint is strongly consistent, so we don't need to check for consistency
|
||||||
const shouldUseConsistencyCheck = isGrafanaRuleIdentifier(identifier)
|
const shouldUseConsistencyCheck = isGrafanaRuleIdentifier(identifier)
|
||||||
|
@ -128,7 +132,7 @@ const RuleViewer = () => {
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
actions={<RuleActionsButtons rule={rule} rulesSource={rule.namespace.rulesSource} />}
|
actions={<RuleActionsButtons rule={rule} rulesSource={rule.namespace.rulesSource} />}
|
||||||
info={createMetadata(rule)}
|
info={createMetadata(rule, styles)}
|
||||||
subTitle={
|
subTitle={
|
||||||
<Stack direction="column">
|
<Stack direction="column">
|
||||||
{summary}
|
{summary}
|
||||||
|
@ -171,6 +175,7 @@ const RuleViewer = () => {
|
||||||
{activeTab === ActiveTab.VersionHistory && rulerRuleType.grafana.rule(rule.rulerRule) && (
|
{activeTab === ActiveTab.VersionHistory && rulerRuleType.grafana.rule(rule.rulerRule) && (
|
||||||
<AlertVersionHistory rule={rule.rulerRule} />
|
<AlertVersionHistory rule={rule.rulerRule} />
|
||||||
)}
|
)}
|
||||||
|
{activeTab === ActiveTab.Enrichment && <RulePageEnrichmentSectionExtension />}
|
||||||
</TabContent>
|
</TabContent>
|
||||||
</Stack>
|
</Stack>
|
||||||
{duplicateRuleIdentifier && (
|
{duplicateRuleIdentifier && (
|
||||||
|
@ -185,7 +190,7 @@ const RuleViewer = () => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const createMetadata = (rule: CombinedRule): PageInfoItem[] => {
|
const createMetadata = (rule: CombinedRule, styles: ReturnType<typeof getStyles>): PageInfoItem[] => {
|
||||||
const { labels, annotations, group, rulerRule } = rule;
|
const { labels, annotations, group, rulerRule } = rule;
|
||||||
const metadata: PageInfoItem[] = [];
|
const metadata: PageInfoItem[] = [];
|
||||||
|
|
||||||
|
@ -198,7 +203,6 @@ const createMetadata = (rule: CombinedRule): PageInfoItem[] => {
|
||||||
const hasLabels = labelsSize(labels) > 0;
|
const hasLabels = labelsSize(labels) > 0;
|
||||||
|
|
||||||
const interval = group.interval;
|
const interval = group.interval;
|
||||||
const styles = useStyles2(getStyles);
|
|
||||||
|
|
||||||
// if the alert rule uses simplified routing, we'll show a link to the contact point
|
// if the alert rule uses simplified routing, we'll show a link to the contact point
|
||||||
if (rulerRuleType.grafana.alertingRule(rulerRule)) {
|
if (rulerRuleType.grafana.alertingRule(rulerRule)) {
|
||||||
|
@ -438,6 +442,12 @@ function usePageNav(rule: CombinedRule) {
|
||||||
|
|
||||||
const groupDetailsUrl = groups.detailsPageLink(dataSourceUID, namespaceString, groupName);
|
const groupDetailsUrl = groups.detailsPageLink(dataSourceUID, namespaceString, groupName);
|
||||||
|
|
||||||
|
const setActiveTabFromString = (tab: string) => {
|
||||||
|
if (isValidTab(tab)) {
|
||||||
|
setActiveTab(tab);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const pageNav: NavModelItem = {
|
const pageNav: NavModelItem = {
|
||||||
...defaultPageNav,
|
...defaultPageNav,
|
||||||
text: rule.name,
|
text: rule.name,
|
||||||
|
@ -483,6 +493,8 @@ function usePageNav(rule: CombinedRule) {
|
||||||
},
|
},
|
||||||
hideFromTabs: !isGrafanaAlertRule && !isGrafanaRecordingRule,
|
hideFromTabs: !isGrafanaAlertRule && !isGrafanaRecordingRule,
|
||||||
},
|
},
|
||||||
|
// Enterprise extensions can append additional tabs here
|
||||||
|
...useRuleViewExtensionsNav(activeTab, setActiveTabFromString),
|
||||||
],
|
],
|
||||||
parentItem: {
|
parentItem: {
|
||||||
text: groupName,
|
text: groupName,
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
import { ComponentType } from 'react';
|
||||||
|
|
||||||
|
import { t } from '@grafana/i18n';
|
||||||
|
import { withErrorBoundary } from '@grafana/ui';
|
||||||
|
|
||||||
|
import { logError } from '../../../../Analytics';
|
||||||
|
|
||||||
|
export interface RuleViewerExtensionProps {}
|
||||||
|
|
||||||
|
let InternalRulePageEnrichmentSection: ComponentType<RuleViewerExtensionProps> | null = null;
|
||||||
|
|
||||||
|
export const RulePageEnrichmentSectionExtension: ComponentType<RuleViewerExtensionProps> = (props) => {
|
||||||
|
if (!InternalRulePageEnrichmentSection) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const WrappedComponent = withErrorBoundary(InternalRulePageEnrichmentSection, {
|
||||||
|
title: t(
|
||||||
|
'alerting.enrichment.error-boundary.rule-viewer-section-extension',
|
||||||
|
'Rule Viewer Enrichment Section failed to load'
|
||||||
|
),
|
||||||
|
style: 'alertbox',
|
||||||
|
errorLogger: logError,
|
||||||
|
});
|
||||||
|
|
||||||
|
return <WrappedComponent {...props} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function addRulePageEnrichmentSection(component: ComponentType<RuleViewerExtensionProps>) {
|
||||||
|
InternalRulePageEnrichmentSection = component;
|
||||||
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
import { css } from '@emotion/css';
|
||||||
|
|
||||||
|
import { FeatureState, NavModelItem } from '@grafana/data';
|
||||||
|
import { t } from '@grafana/i18n';
|
||||||
|
import { FeatureBadge, useStyles2 } from '@grafana/ui';
|
||||||
|
|
||||||
|
type SetActiveTab = (tab: string) => void;
|
||||||
|
|
||||||
|
type RuleViewTabBuilderArgs = {
|
||||||
|
activeTab: string;
|
||||||
|
setActiveTab: SetActiveTab;
|
||||||
|
};
|
||||||
|
|
||||||
|
type RuleViewTabBuilder = (args: RuleViewTabBuilderArgs) => NavModelItem;
|
||||||
|
|
||||||
|
const ruleViewTabBuilders: RuleViewTabBuilder[] = [];
|
||||||
|
|
||||||
|
export function registerRuleViewTab(builder: RuleViewTabBuilder) {
|
||||||
|
ruleViewTabBuilders.push(builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getRuleViewExtensionTabs(args: RuleViewTabBuilderArgs): NavModelItem[] {
|
||||||
|
return ruleViewTabBuilders.map((builder) => builder(args));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function addEnrichmentSection() {
|
||||||
|
registerRuleViewTab(({ activeTab, setActiveTab }) => {
|
||||||
|
const tabId = 'enrichment';
|
||||||
|
return {
|
||||||
|
text: t('alerting.use-page-nav.page-nav.text.enrichment', 'Alert enrichment'),
|
||||||
|
active: activeTab === tabId,
|
||||||
|
onClick: () => setActiveTab(tabId),
|
||||||
|
tabSuffix: () => <EnrichmentTabSuffix />,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ONLY FOR TESTS: resets the registered tabs between tests
|
||||||
|
export function __clearRuleViewTabsForTests() {
|
||||||
|
ruleViewTabBuilders.splice(0, ruleViewTabBuilders.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getStyles() {
|
||||||
|
return {
|
||||||
|
tabSuffix: css({
|
||||||
|
marginLeft: 8,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function EnrichmentTabSuffix() {
|
||||||
|
const styles = useStyles2(getStyles);
|
||||||
|
return (
|
||||||
|
<span className={styles.tabSuffix}>
|
||||||
|
<FeatureBadge featureState={FeatureState.new} />
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
import { beforeEach, describe, expect, it } from '@jest/globals';
|
||||||
|
|
||||||
|
import { __clearRuleViewTabsForTests, addEnrichmentSection, getRuleViewExtensionTabs } from './extensions';
|
||||||
|
|
||||||
|
describe('rule-view-page navigation', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
__clearRuleViewTabsForTests();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not include Alert enrichment tab when not registered', () => {
|
||||||
|
const tabs = getRuleViewExtensionTabs({ activeTab: 'query', setActiveTab: () => {} });
|
||||||
|
const hasEnrichment = tabs.some((t) => t.text === 'Alert enrichment');
|
||||||
|
expect(hasEnrichment).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('includes Alert enrichment tab when registered (enterprise + toggle on)', () => {
|
||||||
|
addEnrichmentSection();
|
||||||
|
const tabs = getRuleViewExtensionTabs({ activeTab: 'query', setActiveTab: () => {} });
|
||||||
|
const enrichment = tabs.find((t) => t.text === 'Alert enrichment');
|
||||||
|
expect(enrichment).toBeTruthy();
|
||||||
|
expect(enrichment!.active).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('marks Alert enrichment tab active when selected', () => {
|
||||||
|
addEnrichmentSection();
|
||||||
|
const tabs = getRuleViewExtensionTabs({ activeTab: 'enrichment', setActiveTab: () => {} });
|
||||||
|
const enrichment = tabs.find((t) => t.text === 'Alert enrichment');
|
||||||
|
expect(enrichment).toBeTruthy();
|
||||||
|
expect(enrichment!.active).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { NavModelItem } from '@grafana/data';
|
||||||
|
|
||||||
|
import { getRuleViewExtensionTabs } from './extensions';
|
||||||
|
|
||||||
|
export function useRuleViewExtensionsNav(activeTab: string, setActiveTab: (tab: string) => void): NavModelItem[] {
|
||||||
|
return getRuleViewExtensionTabs({ activeTab, setActiveTab });
|
||||||
|
}
|
|
@ -1121,7 +1121,8 @@
|
||||||
},
|
},
|
||||||
"enrichment": {
|
"enrichment": {
|
||||||
"error-boundary": {
|
"error-boundary": {
|
||||||
"notification-message-section-extension": "Notification Message Section Extension failed to load"
|
"notification-message-section-extension": "Notification Message Section Extension failed to load",
|
||||||
|
"rule-viewer-section-extension": "Rule Viewer Enrichment Section failed to load"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"error-modal": {
|
"error-modal": {
|
||||||
|
@ -3042,6 +3043,7 @@
|
||||||
"page-nav": {
|
"page-nav": {
|
||||||
"text": {
|
"text": {
|
||||||
"details": "Details",
|
"details": "Details",
|
||||||
|
"enrichment": "Alert enrichment",
|
||||||
"history": "History",
|
"history": "History",
|
||||||
"instances": "Instances",
|
"instances": "Instances",
|
||||||
"query-and-conditions": "Query and conditions",
|
"query-and-conditions": "Query and conditions",
|
||||||
|
|
Loading…
Reference in New Issue