Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -519,6 +519,54 @@ describe('AppMenuRegistry', () => {
});
});

describe('deleteItem', () => {
it('should remove a registered item by ID', () => {
const item: DiscoverAppMenuItemType = {
id: 'to-delete',
order: 1,
label: 'Delete Me',
iconType: 'trash',
run: jest.fn(),
};

registry.registerItem(item);
expect(registry.getItem('to-delete')).toBeDefined();

registry.deleteItem('to-delete');
expect(registry.getItem('to-delete')).toBeUndefined();

const config = registry.getAppMenuConfig();
expect(config.items).toHaveLength(0);
});

it('should not throw when deleting a non-existent item', () => {
expect(() => registry.deleteItem('non-existent')).not.toThrow();
});

it('should only remove the specified item, leaving others intact', () => {
registry.registerItem({
id: 'keep',
order: 1,
label: 'Keep',
iconType: 'check',
run: jest.fn(),
});
registry.registerItem({
id: 'remove',
order: 2,
label: 'Remove',
iconType: 'trash',
run: jest.fn(),
});

registry.deleteItem('remove');

const config = registry.getAppMenuConfig();
expect(config.items).toHaveLength(1);
expect(config.items?.[0].id).toBe('keep');
});
});

describe('mergePopoverItems', () => {
it('should merge popover items from source menu into target submenu', () => {
const targetMenu: DiscoverAppMenuItemType = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,14 @@ export class AppMenuRegistry {
}
}

/**
* Remove a menu item by ID.
* @param id The ID of the menu item to remove
*/
public deleteItem(id: string): void {
this.items.delete(id);
}

/**
* Get a menu item by ID.
* @param id The ID of the menu item to retrieve
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ export enum AppMenuActionId {
alerts = 'alerts',
inspect = 'inspect',
createRule = 'createRule',
legacyRules = 'legacyRules',
backgroundsearch = 'backgroundSearch',
manageRulesAndConnectors = 'manageRulesAndConnectors',
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,21 +23,21 @@ import { createSearchSource } from '../../../state_management/utils/create_searc
import type { DiscoverInternalState } from '../../../state_management/redux';
import { selectTab } from '../../../state_management/redux';

export const EsQueryValidConsumer: RuleCreationValidConsumer[] = [
const EsQueryValidConsumer: RuleCreationValidConsumer[] = [
AlertConsumers.INFRASTRUCTURE,
AlertConsumers.LOGS,
AlertConsumers.OBSERVABILITY,
STACK_ALERTS_FEATURE_ID,
];

export interface EsQueryAlertMetaData extends RuleTypeMetaData {
interface EsQueryAlertMetaData extends RuleTypeMetaData {
isManagementPage?: boolean;
adHocDataViewList: DataView[];
}

const RuleFormFlyoutWithType = RuleFormFlyout<EsQueryAlertMetaData>;

export const CreateAlertFlyout: React.FC<{
const CreateAlertFlyout: React.FC<{
discoverParams: AppMenuDiscoverParams;
services: DiscoverServices;
tabId: string;
Expand Down Expand Up @@ -193,12 +193,12 @@ export const getAlertsAppMenuItem = ({
};
};

export function getTimeField(dataView: DataView | undefined) {
function getTimeField(dataView: DataView | undefined) {
const dateFields = dataView?.fields.getByType('date');
return dataView?.timeFieldName || dateFields?.[0]?.name;
}

export function getManageRulesUrl(services: DiscoverServices) {
function getManageRulesUrl(services: DiscoverServices) {
return services.application.getUrlForApp(
services.application.isAppRegistered('rules')
? 'rules'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,59 +96,29 @@ describe('getCreateRuleMenuItem', () => {
});

describe('Nested Items', () => {
it('should have "Create rule" item for ES|QL rules', () => {
it('should have "Create v2 ES|QL rule" item for ES|QL rules', () => {
const menuItem = getCreateRuleMenu();
const createRuleItem = menuItem.items?.find((item) => item.id === 'create-rule');

expect(createRuleItem).toBeDefined();
expect(createRuleItem?.label).toBe('Create rule');
expect(createRuleItem?.label).toBe('Create v2 ES|QL rule');
expect(createRuleItem?.testId).toBe('discoverCreateRuleButton');
expect(createRuleItem?.iconType).toBe('bell');
expect(createRuleItem?.order).toBe(1);
expect(createRuleItem?.run).toBeDefined();
expect(typeof createRuleItem?.run).toBe('function');
});

it('should have "Create legacy rules" submenu with legacy items', () => {
it('should have "Create v1 rules" submenu that starts empty (populated at merge time)', () => {
const menuItem = getCreateRuleMenu();
const legacyRulesItem = menuItem.items?.find((item) => item.id === 'legacy-rules');

expect(legacyRulesItem).toBeDefined();
expect(legacyRulesItem?.label).toBe('Create legacy rules');
expect(legacyRulesItem?.label).toBe('Create v1 rules');
expect(legacyRulesItem?.testId).toBe('discoverLegacyRulesButton');
expect(legacyRulesItem?.order).toBe(2);
expect(legacyRulesItem?.items).toBeDefined();
expect(legacyRulesItem?.items?.length).toBeGreaterThan(0);
});

it('should include "Search threshold rule" in legacy items when ES_QUERY_ID is authorized', () => {
const menuItem = getCreateRuleMenu();
const legacyRulesItem = menuItem.items?.find((item) => item.id === 'legacy-rules');
const searchThresholdItem = legacyRulesItem?.items?.find(
(item) => item.id === 'legacy-search-threshold'
);

expect(searchThresholdItem).toBeDefined();
expect(searchThresholdItem?.label).toBe('Search threshold rule');
expect(searchThresholdItem?.testId).toBe('discoverLegacySearchThresholdButton');
expect(searchThresholdItem?.iconType).toBe('bell');
expect(searchThresholdItem?.run).toBeDefined();
});

it('should include "Manage rules and connectors" in legacy items', () => {
const menuItem = getCreateRuleMenu();
const legacyRulesItem = menuItem.items?.find((item) => item.id === 'legacy-rules');
const manageRulesItem = legacyRulesItem?.items?.find(
(item) => item.id === 'manage-rules-connectors'
);

expect(manageRulesItem).toBeDefined();
expect(manageRulesItem?.label).toBe('Manage rules and connectors');
expect(manageRulesItem?.testId).toBe('discoverManageRulesButton');
expect(manageRulesItem?.iconType).toBe('tableOfContents');
expect(manageRulesItem?.href).toBe(
'/app/management/insightsAndAlerting/triggersActions/rules'
);
expect(legacyRulesItem?.items).toHaveLength(0);
});

it('should not include "Search threshold rule" when ES_QUERY_ID is not authorized', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,10 @@ import type { AggregateQuery } from '@kbn/es-query';
import { i18n } from '@kbn/i18n';
import type { DiscoverAppMenuItemType, DiscoverAppMenuPopoverItem } from '@kbn/discover-utils';
import { AppMenuActionId } from '@kbn/discover-utils';
import { ES_QUERY_ID } from '@kbn/rule-data-utils';
import type { DiscoverInternalState } from '../../../state_management/redux';
import { selectTab } from '../../../state_management/redux/selectors';
import type { AppMenuDiscoverParams } from './types';
import type { DiscoverServices } from '../../../../../build_services';
import { CreateAlertFlyout, getManageRulesUrl, getTimeField } from './get_alerts';

export function CreateESQLRuleFlyout({
services,
Expand Down Expand Up @@ -75,59 +73,11 @@ export const getCreateRuleMenuItem = ({
tabId: string;
getState: () => DiscoverInternalState;
}): DiscoverAppMenuItemType => {
const { dataView, isEsqlMode } = discoverParams;
const timeField = getTimeField(dataView);
const hasTimeFieldName = !isEsqlMode ? Boolean(dataView?.timeFieldName) : Boolean(timeField);

const legacyItems: DiscoverAppMenuPopoverItem[] = [];

if (services.capabilities.management?.insightsAndAlerting?.triggersActions) {
if (discoverParams.authorizedRuleTypeIds.includes(ES_QUERY_ID)) {
legacyItems.push({
id: 'legacy-search-threshold',
order: 1,
label: i18n.translate('discover.localMenu.legacySearchThresholdTitle', {
defaultMessage: 'Search threshold rule',
}),
iconType: 'bell',
testId: 'discoverLegacySearchThresholdButton',
disableButton: !hasTimeFieldName,
tooltipContent: hasTimeFieldName
? undefined
: i18n.translate('discover.localMenu.legacyMissedTimeFieldToolTip', {
defaultMessage: 'Data view does not have a time field.',
}),
run: ({ context: { onFinishAction } }) => {
return (
<CreateAlertFlyout
onFinishAction={onFinishAction}
discoverParams={discoverParams}
services={services}
tabId={tabId}
getState={getState}
/>
);
},
});
}

legacyItems.push({
id: 'manage-rules-connectors',
order: Number.MAX_SAFE_INTEGER,
label: i18n.translate('discover.localMenu.manageRulesAndConnectors', {
defaultMessage: 'Manage rules and connectors',
}),
iconType: 'tableOfContents',
testId: 'discoverManageRulesButton',
href: getManageRulesUrl(services),
});
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We no longer need to re-craft this manually, it comes over when we merge in the Alerts menu into the v2 Rules sub-menu

const createRuleItem: DiscoverAppMenuPopoverItem = {
id: 'create-rule',
order: 1,
label: i18n.translate('discover.localMenu.createRuleTitle', {
defaultMessage: 'Create rule',
defaultMessage: 'Create v2 ES|QL rule',
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We will be explicit for M1, decide on much better naming and labeling for M2

}),
iconType: 'bell',
testId: 'discoverCreateRuleButton',
Expand All @@ -148,17 +98,13 @@ export const getCreateRuleMenuItem = ({
id: 'legacy-rules',
order: 2,
label: i18n.translate('discover.localMenu.legacyRulesTitle', {
defaultMessage: 'Create legacy rules',
defaultMessage: 'Create v1 rules',
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Must avoid the word "legacy" for v1 rules (v1 is also terrible but we will fix for M2)

}),
testId: 'discoverLegacyRulesButton',
items: legacyItems,
items: [],
};

const items: DiscoverAppMenuPopoverItem[] = [createRuleItem];

if (legacyItems.length > 0) {
items.push(legacyRulesItem);
}
const items: DiscoverAppMenuPopoverItem[] = [createRuleItem, legacyRulesItem];

return {
id: AppMenuActionId.createRule,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,11 +127,6 @@ export const useTopNavLinks = ({
const inspectAppMenuItem = getInspectAppMenuItem({ onOpenInspector });
items.push(inspectAppMenuItem);

const showLegacyAlerts =
services.triggersActionsUi &&
discoverParams.authorizedRuleTypeIds.length &&
(!canCreateESQLRule || !discoverParams.isEsqlMode);

if (showCreateRuleV2) {
const createRuleV2 = getCreateRuleMenuItem({
discoverParams,
Expand All @@ -142,7 +137,7 @@ export const useTopNavLinks = ({
items.push(createRuleV2);
}

if (showLegacyAlerts) {
if (services.triggersActionsUi && discoverParams.authorizedRuleTypeIds.length) {
const alertsAppMenuItem = getAlertsAppMenuItem({
discoverParams,
services,
Expand Down Expand Up @@ -233,7 +228,6 @@ export const useTopNavLinks = ({
hasUnsavedChanges,
totalHitsState,
intl,
canCreateESQLRule,
showCreateRuleV2,
]);

Expand All @@ -248,20 +242,6 @@ export const useTopNavLinks = ({

newAppMenuRegistry.registerItems(appMenuItems);

// Register legacyRules as a top-level item when v2 rules are enabled
// This allows profile extensions to add items to it via registerPopoverItem
// The items are then merged into the createRule menu's legacy submenu
if (showCreateRuleV2) {
newAppMenuRegistry.registerItem({
id: AppMenuActionId.legacyRules,
label: '', // Not displayed directly - items are pulled into the v2 menu's submenu
iconType: 'empty',
order: Number.MAX_SAFE_INTEGER,
hidden: 'all', // Hide at all breakpoints since this is just a container for registry items
items: [],
});
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need to register this "dummy" item, we re-use the alerts one, then remove/delete it after we've merged its contents into the sub-nav.

// Only show the ES|QL button in classic mode (not in ES|QL mode)
// The "Switch to Classic" option is now in the tab menu when in ES|QL mode
if (services.uiSettings.get(ENABLE_ESQL) && !isEsqlMode) {
Expand Down Expand Up @@ -404,14 +384,16 @@ export const useTopNavLinks = ({

const registry = getAppMenu(discoverParams).appMenuRegistry(newAppMenuRegistry);

// Merge items from legacyRules into the createRule menu's legacy-rules submenu
// This allows profile extensions to add rule types to the v2 rules menu
// When v2 rules are enabled, profile extensions have registered their rule types
// into the alerts menu as usual. Move those items into the v2 createRule menu's
// legacy-rules submenu, then remove the alerts menu since v2 replaces it.
if (showCreateRuleV2) {
registry.mergePopoverItems(
AppMenuActionId.createRule,
'legacy-rules',
AppMenuActionId.legacyRules
AppMenuActionId.alerts
);
registry.deleteItem(AppMenuActionId.alerts);
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

new line to remove the old "alerts" menu item, we've merged its contents into the v2 Rules sub-nav item for M1

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: is there any benefit in exposing the deleteItem method from the registry over just deleting it in mergePopoverItems?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair point, I think I like how explicit it is here, so we can see the flow in line.

}

return registry;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,10 +139,7 @@ const registerCustomThresholdRuleAction = (
},
};

// Register to legacy alerts menu
registry.registerPopoverItem(AppMenuActionId.alerts, popoverItem);
// Register to v2 rules menu's legacy submenu
registry.registerPopoverItem(AppMenuActionId.legacyRules, popoverItem);
};

const registerCreateSLOAction = (
Expand Down Expand Up @@ -191,9 +188,6 @@ const registerCreateSLOAction = (
},
};

// Register to legacy alerts menu
registry.registerPopoverItem(AppMenuActionId.alerts, popoverItem);
// Register to v2 rules menu's legacy submenu
registry.registerPopoverItem(AppMenuActionId.legacyRules, popoverItem);
}
};
Loading