Compare commits
9 Commits
sriram/SQL
...
moustafab/
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4cd3e32a96 | ||
|
|
1cbe2bad7c | ||
|
|
c790bdff06 | ||
|
|
aeba2401a9 | ||
|
|
7af42967ee | ||
|
|
45d7169f8b | ||
|
|
138000a80d | ||
|
|
35b43aae84 | ||
|
|
9e40214c84 |
@@ -4677,4 +4677,4 @@
|
||||
"count": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -177,4 +177,32 @@ describe('Rules group tests', () => {
|
||||
expect(ui.editGroupButton.query()).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Ungrouped rules', () => {
|
||||
const ungroupedGroup: CombinedRuleGroup = {
|
||||
name: 'no_group_for_rule_TestRule',
|
||||
rules: [
|
||||
mockCombinedRule({
|
||||
name: 'TestRule',
|
||||
rulerRule: mockGrafanaRulerRule({ namespace_uid: 'folder-123' }),
|
||||
}),
|
||||
],
|
||||
totals: {},
|
||||
};
|
||||
|
||||
const namespace: CombinedRuleNamespace = {
|
||||
name: 'TestNamespace',
|
||||
rulesSource: 'grafana',
|
||||
groups: [ungroupedGroup],
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
mockUseHasRuler(true, GRAFANA_RULER_CONFIG);
|
||||
});
|
||||
|
||||
it('Should display rule name with (Ungrouped) suffix in grouped view', async () => {
|
||||
renderRulesGroup(namespace, ungroupedGroup);
|
||||
expect(await screen.findByText(/TestRule \(Ungrouped\)/)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -13,7 +13,7 @@ import { useRulesAccess } from '../../utils/accessControlHooks';
|
||||
import { GRAFANA_RULES_SOURCE_NAME, getRulesSourceName, isCloudRulesSource } from '../../utils/datasource';
|
||||
import { makeFolderLink } from '../../utils/misc';
|
||||
import { groups } from '../../utils/navigation';
|
||||
import { isFederatedRuleGroup, isPluginProvidedRule, rulerRuleType } from '../../utils/rules';
|
||||
import { isFederatedRuleGroup, isPluginProvidedRule, isUngroupedRuleGroup, rulerRuleType } from '../../utils/rules';
|
||||
import { CollapseToggle } from '../CollapseToggle';
|
||||
import { RuleLocation } from '../RuleLocation';
|
||||
import { GrafanaRuleFolderExporter } from '../export/GrafanaRuleFolderExporter';
|
||||
@@ -164,11 +164,16 @@ export const RulesGroup = React.memo(({ group, namespace, expandAll, viewMode }:
|
||||
}
|
||||
|
||||
// ungrouped rules are rules that are in the "default" group name
|
||||
const groupName = isListView ? (
|
||||
<RuleLocation namespace={decodeGrafanaNamespace(namespace).name} />
|
||||
) : (
|
||||
<RuleLocation namespace={decodeGrafanaNamespace(namespace).name} group={group.name} />
|
||||
);
|
||||
let groupName = <RuleLocation namespace={decodeGrafanaNamespace(namespace).name} group={group.name} />;
|
||||
if (isListView) {
|
||||
groupName = <RuleLocation namespace={decodeGrafanaNamespace(namespace).name} />;
|
||||
} else if (isUngroupedRuleGroup(group.name)) {
|
||||
const firstRuleName = group.rules[0]?.name ?? t('alerting.rules-group.unknown-rule', 'Unknown Rule');
|
||||
const groupDisplayName = t('alerting.rules-group.ungrouped-suffix', '{{ruleName}} (Ungrouped)', {
|
||||
ruleName: firstRuleName,
|
||||
});
|
||||
groupName = <RuleLocation namespace={decodeGrafanaNamespace(namespace).name} group={groupDisplayName} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.wrapper} data-testid="rule-group">
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
import { render, screen } from 'test/test-utils';
|
||||
import { byRole, byText } from 'testing-library-selector';
|
||||
|
||||
import { AccessControlAction } from 'app/types/accessControl';
|
||||
import { GrafanaPromRuleGroupDTO } from 'app/types/unified-alerting-dto';
|
||||
|
||||
import { mockFolderApi, setupMswServer } from '../mockApi';
|
||||
import { grantUserPermissions, mockFolder, mockGrafanaPromAlertingRule } from '../mocks';
|
||||
import { NO_GROUP_PREFIX } from '../utils/rules';
|
||||
|
||||
import { GrafanaRuleGroupListItem } from './PaginatedGrafanaLoader';
|
||||
|
||||
const server = setupMswServer();
|
||||
|
||||
const ui = {
|
||||
treeItem: byRole('treeitem'),
|
||||
groupLink: (name: string | RegExp) => byRole('link', { name }),
|
||||
ungroupedText: byText(/\(Ungrouped\)/),
|
||||
};
|
||||
|
||||
describe('GrafanaRuleGroupListItem', () => {
|
||||
beforeEach(() => {
|
||||
grantUserPermissions([AccessControlAction.AlertingRuleRead]);
|
||||
mockFolderApi(server).folder('folder-123', mockFolder({ uid: 'folder-123', title: 'TestFolder' }));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
server.resetHandlers();
|
||||
});
|
||||
|
||||
it('should display rule name with (Ungrouped) suffix for ungrouped rules', async () => {
|
||||
const grafanaRule = mockGrafanaPromAlertingRule({ name: 'My Alert Rule' });
|
||||
const ungroupedGroup: GrafanaPromRuleGroupDTO = {
|
||||
name: `${NO_GROUP_PREFIX}test-rule-uid`,
|
||||
file: 'TestFolder',
|
||||
folderUid: 'folder-123',
|
||||
interval: 60,
|
||||
rules: [grafanaRule],
|
||||
};
|
||||
|
||||
render(<GrafanaRuleGroupListItem group={ungroupedGroup} namespaceName="TestFolder" />);
|
||||
|
||||
expect(await ui.treeItem.find()).toBeInTheDocument();
|
||||
expect(await ui.groupLink(/My Alert Rule \(Ungrouped\)/).find()).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should display normal group name for grouped rules', async () => {
|
||||
const grafanaRule = mockGrafanaPromAlertingRule({ name: 'My Alert Rule' });
|
||||
const groupedGroup: GrafanaPromRuleGroupDTO = {
|
||||
name: 'MyGroup',
|
||||
file: 'TestFolder',
|
||||
folderUid: 'folder-123',
|
||||
interval: 60,
|
||||
rules: [grafanaRule],
|
||||
};
|
||||
|
||||
render(<GrafanaRuleGroupListItem group={groupedGroup} namespaceName="TestFolder" />);
|
||||
|
||||
expect(await ui.groupLink('MyGroup').find()).toBeInTheDocument();
|
||||
expect(screen.queryByText(/Ungrouped/)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render link to group details page with correct URL', async () => {
|
||||
const grafanaRule = mockGrafanaPromAlertingRule({ name: 'My Alert Rule' });
|
||||
const groupedGroup: GrafanaPromRuleGroupDTO = {
|
||||
name: 'MyGroup',
|
||||
file: 'TestFolder',
|
||||
folderUid: 'folder-123',
|
||||
interval: 60,
|
||||
rules: [grafanaRule],
|
||||
};
|
||||
|
||||
render(<GrafanaRuleGroupListItem group={groupedGroup} namespaceName="TestFolder" />);
|
||||
|
||||
const link = await ui.groupLink('MyGroup').find();
|
||||
expect(link).toHaveAttribute(
|
||||
'href',
|
||||
expect.stringContaining('/alerting/grafana/namespaces/folder-123/groups/MyGroup/view')
|
||||
);
|
||||
});
|
||||
|
||||
it('should render as treeitem with correct aria attributes', async () => {
|
||||
const grafanaRule = mockGrafanaPromAlertingRule({ name: 'My Alert Rule' });
|
||||
const group: GrafanaPromRuleGroupDTO = {
|
||||
name: 'TestGroup',
|
||||
file: 'TestFolder',
|
||||
folderUid: 'folder-123',
|
||||
interval: 60,
|
||||
rules: [grafanaRule],
|
||||
};
|
||||
|
||||
render(<GrafanaRuleGroupListItem group={group} namespaceName="TestFolder" />);
|
||||
|
||||
const treeItem = await ui.treeItem.find();
|
||||
expect(treeItem).toHaveAttribute('aria-expanded', 'false');
|
||||
expect(treeItem).toHaveAttribute('aria-selected', 'false');
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,7 @@
|
||||
import { groupBy, isEmpty } from 'lodash';
|
||||
import { useEffect, useMemo, useRef } from 'react';
|
||||
|
||||
import { t } from '@grafana/i18n';
|
||||
import { Icon, Stack, Text } from '@grafana/ui';
|
||||
import { GrafanaRuleGroupIdentifier, GrafanaRulesSourceSymbol } from 'app/types/unified-alerting';
|
||||
import { GrafanaPromRuleGroupDTO, PromRuleGroupDTO } from 'app/types/unified-alerting-dto';
|
||||
@@ -9,6 +10,7 @@ import { FolderActionsButton } from '../components/folder-actions/FolderActionsB
|
||||
import { GrafanaNoRulesCTA } from '../components/rules/NoRulesCTA';
|
||||
import { GRAFANA_RULES_SOURCE_NAME } from '../utils/datasource';
|
||||
import { groups } from '../utils/navigation';
|
||||
import { isUngroupedRuleGroup } from '../utils/rules';
|
||||
|
||||
import { GrafanaGroupLoader } from './GrafanaGroupLoader';
|
||||
import { DataSourceSection } from './components/DataSourceSection';
|
||||
@@ -161,10 +163,15 @@ export function GrafanaRuleGroupListItem({ group, namespaceName }: GrafanaRuleGr
|
||||
|
||||
const detailsLink = groups.detailsPageLink(GRAFANA_RULES_SOURCE_NAME, group.folderUid, group.name);
|
||||
|
||||
const firstRuleName = group.rules[0]?.name ?? t('alerting.rules-group.unknown-rule', 'Unknown Rule');
|
||||
const groupDisplayName = isUngroupedRuleGroup(group.name)
|
||||
? t('alerting.rules-group.ungrouped-suffix', '{{ruleName}} (Ungrouped)', { ruleName: firstRuleName })
|
||||
: group.name;
|
||||
|
||||
return (
|
||||
<ListGroup
|
||||
key={group.name}
|
||||
name={group.name}
|
||||
name={groupDisplayName}
|
||||
metaRight={<GroupIntervalIndicator seconds={group.interval} />}
|
||||
href={detailsLink}
|
||||
isOpen={false}
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
import { render, screen } from 'test/test-utils';
|
||||
import { byRole } from 'testing-library-selector';
|
||||
|
||||
import { PromApplication } from 'app/types/unified-alerting-dto';
|
||||
|
||||
import { NO_GROUP_PREFIX } from '../../utils/rules';
|
||||
|
||||
import { RuleLocation } from './RuleLocation';
|
||||
|
||||
const ui = {
|
||||
groupLink: (name: string) => byRole('link', { name }),
|
||||
};
|
||||
|
||||
describe('RuleLocation', () => {
|
||||
describe('ungrouped rules', () => {
|
||||
it('should display "Ungrouped" text for groups with no_group_for_rule_ prefix', () => {
|
||||
const { container } = render(
|
||||
<RuleLocation namespace="TestNamespace" group={`${NO_GROUP_PREFIX}test-rule-uid`} application="grafana" />
|
||||
);
|
||||
|
||||
expect(container).toHaveTextContent('Ungrouped');
|
||||
expect(container).not.toHaveTextContent(`${NO_GROUP_PREFIX}test-rule-uid`);
|
||||
});
|
||||
|
||||
it('should render "Ungrouped" as link when groupUrl is provided', () => {
|
||||
render(
|
||||
<RuleLocation
|
||||
namespace="TestNamespace"
|
||||
group={`${NO_GROUP_PREFIX}test-rule-uid`}
|
||||
groupUrl="/alerting/grafana/namespaces/folder-123/groups/test-group/view"
|
||||
application="grafana"
|
||||
/>
|
||||
);
|
||||
|
||||
const link = ui.groupLink('Ungrouped').get();
|
||||
expect(link).toHaveAttribute('href', '/alerting/grafana/namespaces/folder-123/groups/test-group/view');
|
||||
});
|
||||
|
||||
it('should render "Ungrouped" as text when groupUrl is not provided', () => {
|
||||
const { container } = render(
|
||||
<RuleLocation namespace="TestNamespace" group={`${NO_GROUP_PREFIX}test-rule-uid`} application="grafana" />
|
||||
);
|
||||
|
||||
expect(screen.queryByRole('link')).not.toBeInTheDocument();
|
||||
expect(container).toHaveTextContent('Ungrouped');
|
||||
});
|
||||
});
|
||||
|
||||
describe('grouped rules', () => {
|
||||
it('should display normal group name for regular groups', () => {
|
||||
const { container } = render(<RuleLocation namespace="TestNamespace" group="MyGroup" application="grafana" />);
|
||||
|
||||
expect(container).toHaveTextContent('MyGroup');
|
||||
expect(container).not.toHaveTextContent('Ungrouped');
|
||||
});
|
||||
|
||||
it('should render group name as link when groupUrl is provided', () => {
|
||||
render(
|
||||
<RuleLocation
|
||||
namespace="TestNamespace"
|
||||
group="MyGroup"
|
||||
groupUrl="/alerting/grafana/namespaces/folder-123/groups/MyGroup/view"
|
||||
application="grafana"
|
||||
/>
|
||||
);
|
||||
|
||||
const link = ui.groupLink('MyGroup').get();
|
||||
expect(link).toHaveAttribute('href', '/alerting/grafana/namespaces/folder-123/groups/MyGroup/view');
|
||||
});
|
||||
|
||||
it('should render group name as text when groupUrl is not provided', () => {
|
||||
const { container } = render(<RuleLocation namespace="TestNamespace" group="MyGroup" application="grafana" />);
|
||||
|
||||
expect(screen.queryByRole('link')).not.toBeInTheDocument();
|
||||
expect(container).toHaveTextContent('MyGroup');
|
||||
});
|
||||
});
|
||||
|
||||
describe('namespace and group display', () => {
|
||||
it('should display namespace and group correctly', () => {
|
||||
const { container } = render(<RuleLocation namespace="TestNamespace" group="MyGroup" application="grafana" />);
|
||||
|
||||
expect(container).toHaveTextContent('TestNamespace');
|
||||
expect(container).toHaveTextContent('MyGroup');
|
||||
});
|
||||
});
|
||||
|
||||
describe('grafana application', () => {
|
||||
it('should not render data source tooltip for grafana application', () => {
|
||||
render(<RuleLocation namespace="TestNamespace" group="MyGroup" application="grafana" />);
|
||||
|
||||
expect(screen.queryByRole('tooltip')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('datasource application', () => {
|
||||
const mockRulesSource = {
|
||||
uid: 'prometheus-1',
|
||||
name: 'Prometheus',
|
||||
ruleSourceType: 'datasource' as const,
|
||||
};
|
||||
|
||||
it('should render content for datasource application', () => {
|
||||
const { container } = render(
|
||||
<RuleLocation
|
||||
namespace="TestNamespace"
|
||||
group="MyGroup"
|
||||
rulesSource={mockRulesSource}
|
||||
application={PromApplication.Prometheus}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(container).toHaveTextContent('TestNamespace');
|
||||
expect(container).toHaveTextContent('MyGroup');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,7 +1,10 @@
|
||||
import { t } from '@grafana/i18n';
|
||||
import { Icon, Stack, TextLink, Tooltip } from '@grafana/ui';
|
||||
import { RulesSourceIdentifier } from 'app/types/unified-alerting';
|
||||
import { RulesSourceApplication } from 'app/types/unified-alerting-dto';
|
||||
|
||||
import { isUngroupedRuleGroup } from '../../utils/rules';
|
||||
|
||||
import { DataSourceIcon } from './DataSourceIcon';
|
||||
|
||||
interface RuleLocationProps {
|
||||
@@ -15,6 +18,7 @@ interface RuleLocationProps {
|
||||
export function RuleLocation({ namespace, group, groupUrl, rulesSource, application }: RuleLocationProps) {
|
||||
const isGrafanaApp = application === 'grafana';
|
||||
const isDataSourceApp = !!rulesSource && !!application && !isGrafanaApp;
|
||||
const groupText = isUngroupedRuleGroup(group) ? t('alerting.rules-group.ungrouped', 'Ungrouped') : group;
|
||||
|
||||
return (
|
||||
<Stack direction="row" alignItems="center" gap={0.5}>
|
||||
@@ -32,10 +36,10 @@ export function RuleLocation({ namespace, group, groupUrl, rulesSource, applicat
|
||||
<Icon size="sm" name="angle-right" />
|
||||
{groupUrl ? (
|
||||
<TextLink href={groupUrl} color="secondary" variant="bodySmall" inline={false}>
|
||||
{group}
|
||||
{groupText}
|
||||
</TextLink>
|
||||
) : (
|
||||
group
|
||||
groupText
|
||||
)}
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
getRuleGroupLocationFromCombinedRule,
|
||||
getRuleGroupLocationFromRuleWithLocation,
|
||||
getRulePluginOrigin,
|
||||
isUngroupedRuleGroup,
|
||||
} from './rules';
|
||||
|
||||
describe('getRuleOrigin', () => {
|
||||
@@ -123,3 +124,22 @@ describe('ruleGroupLocation', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('isUngroupedRuleGroup', () => {
|
||||
it('should return true for group names starting with NO_GROUP_PREFIX', () => {
|
||||
expect(isUngroupedRuleGroup('no_group_for_rule_abc123')).toBe(true);
|
||||
expect(isUngroupedRuleGroup('no_group_for_rule_')).toBe(true);
|
||||
expect(isUngroupedRuleGroup('no_group_for_rule_test-rule-uid')).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false for group names not starting with NO_GROUP_PREFIX', () => {
|
||||
expect(isUngroupedRuleGroup('MyGroup')).toBe(false);
|
||||
expect(isUngroupedRuleGroup('group-1')).toBe(false);
|
||||
expect(isUngroupedRuleGroup('')).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false for group names that contain but do not start with NO_GROUP_PREFIX', () => {
|
||||
expect(isUngroupedRuleGroup('prefix_no_group_for_rule_abc123')).toBe(false);
|
||||
expect(isUngroupedRuleGroup('MyGroup_no_group_for_rule_')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -563,3 +563,6 @@ export function getRuleUID(rule?: RulerRuleDTO | Rule) {
|
||||
|
||||
return ruleUid;
|
||||
}
|
||||
|
||||
export const NO_GROUP_PREFIX = 'no_group_for_rule_';
|
||||
export const isUngroupedRuleGroup = (group: string): boolean => group.startsWith(NO_GROUP_PREFIX);
|
||||
|
||||
@@ -2656,7 +2656,10 @@
|
||||
"rules-group": {
|
||||
"deleting": "Deleting",
|
||||
"text-federated": "Federated",
|
||||
"text-provisioned": "Provisioned"
|
||||
"text-provisioned": "Provisioned",
|
||||
"ungrouped": "Ungrouped",
|
||||
"ungrouped-suffix": "{{ruleName}} (Ungrouped)",
|
||||
"unknown-rule": "Unknown Rule"
|
||||
},
|
||||
"search": {
|
||||
"property": {
|
||||
|
||||
Reference in New Issue
Block a user