mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[ES Query] Make rule created in Discover visible in Observability (#171364)
## Summary
Closes #170497
<img width="483" alt="Screenshot 2023-11-16 at 1 25 18 PM"
src="4d974eab
-9641-4618-b52a-2facf4c07667">
Adds scope dropdown to ES Query rules created from Discovery. If Logs or
Metrics are selected, rules created here will be visible in
Observability.
Also makes `Logs` the default consumer when creating a rule from either
Discovery and Observability.
### Checklist
- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
---------
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
c07b501e54
commit
8d1cafff0d
14 changed files with 150 additions and 137 deletions
|
@ -5,6 +5,14 @@
|
|||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
import { AlertConsumers } from '../alerts_as_data_rbac';
|
||||
import { STACK_ALERTS_FEATURE_ID } from './stack_rules';
|
||||
|
||||
export * from './stack_rules';
|
||||
export * from './o11y_rules';
|
||||
|
||||
export type RuleCreationValidConsumer =
|
||||
| typeof AlertConsumers.LOGS
|
||||
| typeof AlertConsumers.INFRASTRUCTURE
|
||||
| typeof AlertConsumers.OBSERVABILITY
|
||||
| typeof STACK_ALERTS_FEATURE_ID;
|
||||
|
|
|
@ -13,14 +13,24 @@ import { FormattedMessage } from '@kbn/i18n-react';
|
|||
import type { DataView } from '@kbn/data-plugin/common';
|
||||
import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render';
|
||||
import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
|
||||
import { STACK_ALERTS_FEATURE_ID } from '@kbn/rule-data-utils';
|
||||
import {
|
||||
AlertConsumers,
|
||||
ES_QUERY_ID,
|
||||
RuleCreationValidConsumer,
|
||||
STACK_ALERTS_FEATURE_ID,
|
||||
} from '@kbn/rule-data-utils';
|
||||
import { DiscoverStateContainer } from '../../services/discover_state';
|
||||
import { DiscoverServices } from '../../../../build_services';
|
||||
|
||||
const container = document.createElement('div');
|
||||
let isOpen = false;
|
||||
|
||||
const ALERT_TYPE_ID = '.es-query';
|
||||
const EsQueryValidConsumer: RuleCreationValidConsumer[] = [
|
||||
AlertConsumers.INFRASTRUCTURE,
|
||||
AlertConsumers.LOGS,
|
||||
AlertConsumers.OBSERVABILITY,
|
||||
STACK_ALERTS_FEATURE_ID,
|
||||
];
|
||||
|
||||
interface AlertsPopoverProps {
|
||||
onClose: () => void;
|
||||
|
@ -98,7 +108,7 @@ export function AlertsPopover({
|
|||
|
||||
return triggersActionsUi?.getAddRuleFlyout({
|
||||
metadata: discoverMetadata,
|
||||
consumer: STACK_ALERTS_FEATURE_ID,
|
||||
consumer: 'alerts',
|
||||
onClose: (_, metadata) => {
|
||||
onFinishFlyoutInteraction(metadata as EsQueryAlertMetaData);
|
||||
onClose();
|
||||
|
@ -107,8 +117,12 @@ export function AlertsPopover({
|
|||
onFinishFlyoutInteraction(metadata as EsQueryAlertMetaData);
|
||||
},
|
||||
canChangeTrigger: false,
|
||||
ruleTypeId: ALERT_TYPE_ID,
|
||||
ruleTypeId: ES_QUERY_ID,
|
||||
initialValues: { params: getParams() },
|
||||
validConsumers: EsQueryValidConsumer,
|
||||
useRuleProducer: true,
|
||||
// Default to the Logs consumer if it's available. This should fall back to Stack Alerts if it's not.
|
||||
initialSelectedConsumer: AlertConsumers.LOGS,
|
||||
});
|
||||
}, [alertFlyoutVisible, triggersActionsUi, discoverMetadata, getParams, onClose, stateContainer]);
|
||||
|
||||
|
|
|
@ -71,14 +71,12 @@
|
|||
"@kbn/react-kibana-context-render",
|
||||
"@kbn/unified-data-table",
|
||||
"@kbn/no-data-page-plugin",
|
||||
"@kbn/rule-data-utils",
|
||||
"@kbn/global-search-plugin",
|
||||
"@kbn/resizable-layout",
|
||||
"@kbn/unsaved-changes-badge",
|
||||
"@kbn/rule-data-utils",
|
||||
"@kbn/core-chrome-browser",
|
||||
"@kbn/core-plugins-server"
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*"
|
||||
]
|
||||
"exclude": ["target/**/*"]
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ import { useLoadRuleTypes } from '@kbn/triggers-actions-ui-plugin/public';
|
|||
import { ALERTS_FEATURE_ID } from '@kbn/alerting-plugin/common';
|
||||
import { useBreadcrumbs } from '@kbn/observability-shared-plugin/public';
|
||||
|
||||
import { AlertConsumers } from '@kbn/rule-data-utils';
|
||||
import { RULES_LOGS_PATH, RULES_PATH } from '../../../common/locators/paths';
|
||||
import { useKibana } from '../../utils/kibana_react';
|
||||
import { usePluginContext } from '../../hooks/use_plugin_context';
|
||||
|
@ -144,6 +145,7 @@ export function RulesPage({ activeTab = RULES_TAB_NAME }: RulesPageProps) {
|
|||
consumer={ALERTS_FEATURE_ID}
|
||||
filteredRuleTypes={filteredRuleTypes}
|
||||
validConsumers={observabilityRuleCreationValidConsumers}
|
||||
initialSelectedConsumer={AlertConsumers.LOGS}
|
||||
onClose={() => {
|
||||
setAddRuleFlyoutVisibility(false);
|
||||
}}
|
||||
|
|
|
@ -75,4 +75,12 @@ function registerNavigation(alerting: AlertingSetup) {
|
|||
return;
|
||||
}
|
||||
);
|
||||
alerting.registerNavigation(
|
||||
'observability',
|
||||
ES_QUERY_ID,
|
||||
(rule: SanitizedRule<EsQueryRuleParams<SearchType.searchSource>>) => {
|
||||
if (isSearchSourceRule(rule.params)) return `/app/discover#/viewAlert/${rule.id}`;
|
||||
return;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
@ -340,87 +340,6 @@ describe('rule_add', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('should NOT allow to save the rule if the consumer is not set', async () => {
|
||||
(triggersActionsUiConfig as jest.Mock).mockResolvedValue({
|
||||
minimumScheduleInterval: { value: '1m', enforce: false },
|
||||
});
|
||||
const onClose = jest.fn();
|
||||
await setup({
|
||||
initialValues: {
|
||||
name: 'Simple rule',
|
||||
consumer: 'alerts',
|
||||
ruleTypeId: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID,
|
||||
tags: ['uptime', 'logs'],
|
||||
schedule: {
|
||||
interval: '1h',
|
||||
},
|
||||
},
|
||||
onClose,
|
||||
ruleTypesOverwrite: [
|
||||
{
|
||||
id: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID,
|
||||
name: 'Threshold Rule',
|
||||
actionGroups: [
|
||||
{
|
||||
id: 'testActionGroup',
|
||||
name: 'Test Action Group',
|
||||
},
|
||||
],
|
||||
enabledInLicense: true,
|
||||
defaultActionGroupId: 'threshold.fired',
|
||||
minimumLicenseRequired: 'basic',
|
||||
recoveryActionGroup: { id: 'recovered', name: 'Recovered' },
|
||||
producer: ALERTS_FEATURE_ID,
|
||||
authorizedConsumers: {
|
||||
alerts: { read: true, all: true },
|
||||
apm: { read: true, all: true },
|
||||
discover: { read: true, all: true },
|
||||
infrastructure: { read: true, all: true },
|
||||
logs: { read: true, all: true },
|
||||
ml: { read: true, all: true },
|
||||
monitoring: { read: true, all: true },
|
||||
siem: { read: true, all: true },
|
||||
// Setting SLO all to false, this shouldn't show up
|
||||
slo: { read: true, all: false },
|
||||
stackAlerts: { read: true, all: true },
|
||||
uptime: { read: true, all: true },
|
||||
},
|
||||
actionVariables: {
|
||||
context: [],
|
||||
state: [],
|
||||
params: [],
|
||||
},
|
||||
},
|
||||
],
|
||||
ruleTypeModelOverwrite: {
|
||||
id: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID,
|
||||
iconClass: 'test',
|
||||
description: 'test',
|
||||
documentationUrl: null,
|
||||
validate: (): ValidationResult => {
|
||||
return { errors: {} };
|
||||
},
|
||||
ruleParamsExpression: TestExpression,
|
||||
requiresAppContext: false,
|
||||
},
|
||||
validConsumers: [AlertConsumers.INFRASTRUCTURE, AlertConsumers.LOGS],
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
await nextTick();
|
||||
wrapper.update();
|
||||
});
|
||||
|
||||
wrapper.find('[data-test-subj="saveRuleButton"]').last().simulate('click');
|
||||
|
||||
await act(async () => {
|
||||
await nextTick();
|
||||
wrapper.update();
|
||||
});
|
||||
|
||||
expect(createRule).toBeCalledTimes(0);
|
||||
});
|
||||
|
||||
it('should set consumer automatically if only 1 authorized consumer exists', async () => {
|
||||
(triggersActionsUiConfig as jest.Mock).mockResolvedValue({
|
||||
minimumScheduleInterval: { value: '1m', enforce: false },
|
||||
|
|
|
@ -37,7 +37,7 @@ import { HealthContextProvider } from '../../context/health_context';
|
|||
import { useKibana } from '../../../common/lib/kibana';
|
||||
import { hasRuleChanged, haveRuleParamsChanged } from './has_rule_changed';
|
||||
import { getRuleWithInvalidatedFields } from '../../lib/value_validators';
|
||||
import { DEFAULT_RULE_INTERVAL } from '../../constants';
|
||||
import { DEFAULT_RULE_INTERVAL, MULTI_CONSUMER_RULE_TYPE_IDS } from '../../constants';
|
||||
import { triggersActionsUiConfig } from '../../../common/lib/config_api';
|
||||
import { getInitialInterval } from './get_initial_interval';
|
||||
import { ToastWithCircuitBreakerContent } from '../../components/toast_with_circuit_breaker_content';
|
||||
|
@ -65,6 +65,7 @@ const RuleAdd = ({
|
|||
filteredRuleTypes,
|
||||
validConsumers,
|
||||
useRuleProducer,
|
||||
initialSelectedConsumer,
|
||||
...props
|
||||
}: RuleAddProps) => {
|
||||
const onSaveHandler = onSave ?? reloadRules;
|
||||
|
@ -84,7 +85,6 @@ const RuleAdd = ({
|
|||
...(initialValues ? initialValues : {}),
|
||||
};
|
||||
}, [ruleTypeId, consumer, initialValues]);
|
||||
|
||||
const [{ rule }, dispatch] = useReducer(ruleReducer as InitialRuleReducer, {
|
||||
rule: initialRule,
|
||||
});
|
||||
|
@ -97,9 +97,14 @@ const RuleAdd = ({
|
|||
props.ruleTypeIndex
|
||||
);
|
||||
const [changedFromDefaultInterval, setChangedFromDefaultInterval] = useState<boolean>(false);
|
||||
|
||||
const selectableConsumer = useMemo(
|
||||
() => rule.ruleTypeId && MULTI_CONSUMER_RULE_TYPE_IDS.includes(rule.ruleTypeId),
|
||||
[rule]
|
||||
);
|
||||
const [selectedConsumer, setSelectedConsumer] = useState<
|
||||
RuleCreationValidConsumer | null | undefined
|
||||
>();
|
||||
>(selectableConsumer ? initialSelectedConsumer : null);
|
||||
|
||||
const setRule = (value: InitialRule) => {
|
||||
dispatch({ command: { type: 'setRule' }, payload: { key: 'rule', value } });
|
||||
|
@ -218,12 +223,14 @@ const RuleAdd = ({
|
|||
getRuleErrors(
|
||||
{
|
||||
...rule,
|
||||
...(selectedConsumer !== undefined ? { consumer: selectedConsumer } : {}),
|
||||
...(selectableConsumer && selectedConsumer !== undefined
|
||||
? { consumer: selectedConsumer }
|
||||
: {}),
|
||||
} as Rule,
|
||||
ruleType,
|
||||
config
|
||||
),
|
||||
[rule, selectedConsumer, ruleType, config]
|
||||
[rule, selectedConsumer, selectableConsumer, ruleType, config]
|
||||
);
|
||||
|
||||
// Confirm before saving if user is able to add actions but hasn't added any to this rule
|
||||
|
@ -235,7 +242,7 @@ const RuleAdd = ({
|
|||
http,
|
||||
rule: {
|
||||
...rule,
|
||||
...(selectedConsumer ? { consumer: selectedConsumer } : {}),
|
||||
...(selectableConsumer && selectedConsumer ? { consumer: selectedConsumer } : {}),
|
||||
} as RuleUpdates,
|
||||
});
|
||||
toasts.addSuccess(
|
||||
|
@ -298,6 +305,7 @@ const RuleAdd = ({
|
|||
}
|
||||
)}
|
||||
validConsumers={validConsumers}
|
||||
selectedConsumer={selectedConsumer}
|
||||
actionTypeRegistry={actionTypeRegistry}
|
||||
ruleTypeRegistry={ruleTypeRegistry}
|
||||
metadata={metadata}
|
||||
|
@ -305,7 +313,6 @@ const RuleAdd = ({
|
|||
hideGrouping={hideGrouping}
|
||||
hideInterval={hideInterval}
|
||||
onChangeMetaData={onChangeMetaData}
|
||||
selectedConsumer={selectedConsumer}
|
||||
setConsumer={setSelectedConsumer}
|
||||
useRuleProducer={useRuleProducer}
|
||||
/>
|
||||
|
|
|
@ -596,7 +596,7 @@ describe('rule_form', () => {
|
|||
expect(mockSetConsumer).toHaveBeenLastCalledWith('infrastructure');
|
||||
});
|
||||
|
||||
it('should be able to select multiple consumer', async () => {
|
||||
it('should render multiple consumers in the dropdown and select the first one in the list if no default is specified', async () => {
|
||||
await setup({
|
||||
initialRuleOverwrite: {
|
||||
name: 'Simple rule',
|
||||
|
@ -660,7 +660,7 @@ describe('rule_form', () => {
|
|||
wrapper.update();
|
||||
});
|
||||
|
||||
expect(mockSetConsumer).toHaveBeenLastCalledWith(null);
|
||||
expect(mockSetConsumer).toHaveBeenLastCalledWith('infrastructure');
|
||||
});
|
||||
|
||||
it('should not display the consumer select for invalid rule types', async () => {
|
||||
|
|
|
@ -169,6 +169,7 @@ export const RuleForm = ({
|
|||
setHasActionsDisabled,
|
||||
setHasActionsWithBrokenConnector,
|
||||
setConsumer = NOOP,
|
||||
selectedConsumer,
|
||||
operation,
|
||||
ruleTypeRegistry,
|
||||
actionTypeRegistry,
|
||||
|
@ -177,7 +178,6 @@ export const RuleForm = ({
|
|||
hideGrouping = false,
|
||||
hideInterval,
|
||||
connectorFeatureId = AlertingConnectorFeatureId,
|
||||
selectedConsumer,
|
||||
validConsumers,
|
||||
onChangeMetaData,
|
||||
useRuleProducer,
|
||||
|
@ -253,11 +253,11 @@ export const RuleForm = ({
|
|||
validConsumers,
|
||||
})
|
||||
)
|
||||
.filter((item) => {
|
||||
return rule.consumer === ALERTS_FEATURE_ID
|
||||
.filter((item) =>
|
||||
rule.consumer === ALERTS_FEATURE_ID
|
||||
? !item.ruleTypeModel.requiresAppContext
|
||||
: item.ruleType!.producer === rule.consumer;
|
||||
});
|
||||
: item.ruleType!.producer === rule.consumer
|
||||
);
|
||||
|
||||
const availableRuleTypesResult = getAvailableRuleTypes(ruleTypes);
|
||||
setAvailableRuleTypes(availableRuleTypesResult);
|
||||
|
@ -427,6 +427,7 @@ export const RuleForm = ({
|
|||
const selectedRuleType = availableRuleTypes.find(
|
||||
({ ruleType: availableRuleType }) => availableRuleType.id === rule.ruleTypeId
|
||||
);
|
||||
|
||||
if (!selectedRuleType?.ruleType?.authorizedConsumers) {
|
||||
return [];
|
||||
}
|
||||
|
@ -812,6 +813,7 @@ export const RuleForm = ({
|
|||
consumers={authorizedConsumers}
|
||||
onChange={setConsumer}
|
||||
errors={errors}
|
||||
selectedConsumer={selectedConsumer}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</>
|
||||
|
|
|
@ -21,29 +21,30 @@ describe('RuleFormConsumerSelectionModal', () => {
|
|||
|
||||
it('renders correctly', async () => {
|
||||
render(
|
||||
<RuleFormConsumerSelection consumers={mockConsumers} onChange={mockOnChange} errors={{}} />
|
||||
<RuleFormConsumerSelection
|
||||
selectedConsumer={null}
|
||||
consumers={mockConsumers}
|
||||
onChange={mockOnChange}
|
||||
errors={{}}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(screen.getByTestId('ruleFormConsumerSelect')).toBeInTheDocument();
|
||||
expect(screen.getByText('Select a scope')).toBeInTheDocument();
|
||||
fireEvent.click(screen.getByTestId('comboBoxToggleListButton'));
|
||||
expect(screen.getByText('Logs')).toBeInTheDocument();
|
||||
expect(screen.getByText('Metrics')).toBeInTheDocument();
|
||||
expect(screen.getByText('Stack Rules')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should initialize dropdown to null', () => {
|
||||
render(
|
||||
<RuleFormConsumerSelection consumers={mockConsumers} onChange={mockOnChange} errors={{}} />
|
||||
);
|
||||
|
||||
// Selects first option if no initial value is provided
|
||||
expect(mockOnChange).toHaveBeenLastCalledWith(null);
|
||||
mockOnChange.mockClear();
|
||||
});
|
||||
|
||||
it('should be able to select infrastructure and call onChange', () => {
|
||||
render(
|
||||
<RuleFormConsumerSelection consumers={mockConsumers} onChange={mockOnChange} errors={{}} />
|
||||
<RuleFormConsumerSelection
|
||||
selectedConsumer={null}
|
||||
consumers={mockConsumers}
|
||||
onChange={mockOnChange}
|
||||
errors={{}}
|
||||
/>
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByTestId('comboBoxToggleListButton'));
|
||||
|
@ -53,7 +54,12 @@ describe('RuleFormConsumerSelectionModal', () => {
|
|||
|
||||
it('should be able to select logs and call onChange', () => {
|
||||
render(
|
||||
<RuleFormConsumerSelection consumers={mockConsumers} onChange={mockOnChange} errors={{}} />
|
||||
<RuleFormConsumerSelection
|
||||
selectedConsumer={null}
|
||||
consumers={mockConsumers}
|
||||
onChange={mockOnChange}
|
||||
errors={{}}
|
||||
/>
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByTestId('comboBoxToggleListButton'));
|
||||
|
@ -64,6 +70,7 @@ describe('RuleFormConsumerSelectionModal', () => {
|
|||
it('should be able to show errors when there is one', () => {
|
||||
render(
|
||||
<RuleFormConsumerSelection
|
||||
selectedConsumer={null}
|
||||
consumers={mockConsumers}
|
||||
onChange={mockOnChange}
|
||||
errors={{ consumer: ['Scope is required'] }}
|
||||
|
@ -74,23 +81,55 @@ describe('RuleFormConsumerSelectionModal', () => {
|
|||
|
||||
it('should display nothing if there is only 1 consumer to select', () => {
|
||||
render(
|
||||
<RuleFormConsumerSelection consumers={['stackAlerts']} onChange={mockOnChange} errors={{}} />
|
||||
<RuleFormConsumerSelection
|
||||
selectedConsumer={null}
|
||||
consumers={['stackAlerts']}
|
||||
onChange={mockOnChange}
|
||||
errors={{}}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(mockOnChange).toHaveBeenLastCalledWith('stackAlerts');
|
||||
expect(screen.queryByTestId('ruleFormConsumerSelect')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should display nothing if observability is one of the consumer', () => {
|
||||
it('should display nothing if observability is one of the consumers', () => {
|
||||
render(
|
||||
<RuleFormConsumerSelection
|
||||
selectedConsumer={null}
|
||||
consumers={['logs', 'observability']}
|
||||
onChange={mockOnChange}
|
||||
errors={{}}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(mockOnChange).toHaveBeenLastCalledWith('observability');
|
||||
expect(screen.queryByTestId('ruleFormConsumerSelect')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should display the initial selected consumer', () => {
|
||||
render(
|
||||
<RuleFormConsumerSelection
|
||||
selectedConsumer={'logs'}
|
||||
consumers={mockConsumers}
|
||||
onChange={mockOnChange}
|
||||
errors={{}}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(screen.getByText('Logs')).toBeInTheDocument();
|
||||
expect(() => screen.getByText('Select a scope')).toThrow();
|
||||
});
|
||||
|
||||
it('should not display the initial selected consumer if it is not a selectable option', () => {
|
||||
render(
|
||||
<RuleFormConsumerSelection
|
||||
selectedConsumer={'logs'}
|
||||
consumers={['stackAlerts', 'infrastructure']}
|
||||
onChange={mockOnChange}
|
||||
errors={{}}
|
||||
/>
|
||||
);
|
||||
expect(() => screen.getByText('Logs')).toThrow();
|
||||
expect(screen.getByText('Select a scope')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,10 +5,10 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useMemo, useState, useCallback, useEffect } from 'react';
|
||||
import React, { useMemo, useCallback, useEffect } from 'react';
|
||||
import { EuiComboBox, EuiFormRow, EuiComboBoxOptionOption } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { AlertConsumers } from '@kbn/rule-data-utils';
|
||||
import { AlertConsumers, STACK_ALERTS_FEATURE_ID } from '@kbn/rule-data-utils';
|
||||
import { IErrorObject, RuleCreationValidConsumer } from '../../../types';
|
||||
|
||||
const SELECT_LABEL: string = i18n.translate(
|
||||
|
@ -67,33 +67,41 @@ export interface RuleFormConsumerSelectionProps {
|
|||
consumers: RuleCreationValidConsumer[];
|
||||
onChange: (consumer: RuleCreationValidConsumer | null) => void;
|
||||
errors: IErrorObject;
|
||||
selectedConsumer: RuleCreationValidConsumer | null | undefined;
|
||||
}
|
||||
|
||||
const SINGLE_SELECTION = { asPlainText: true };
|
||||
|
||||
export const RuleFormConsumerSelection = (props: RuleFormConsumerSelectionProps) => {
|
||||
const { consumers, errors, onChange } = props;
|
||||
const [selectedConsumer, setSelectedConsumer] = useState<RuleCreationValidConsumer | undefined>();
|
||||
const { consumers, errors, onChange, selectedConsumer } = props;
|
||||
const isInvalid = errors?.consumer?.length > 0;
|
||||
const handleOnChange = useCallback(
|
||||
(selected: Array<EuiComboBoxOptionOption<RuleCreationValidConsumer>>) => {
|
||||
if (selected.length > 0) {
|
||||
const newSelectedConsumer = selected[0];
|
||||
setSelectedConsumer(newSelectedConsumer.value);
|
||||
onChange(newSelectedConsumer.value!);
|
||||
} else {
|
||||
setSelectedConsumer(undefined);
|
||||
onChange(null);
|
||||
}
|
||||
},
|
||||
[onChange]
|
||||
);
|
||||
const validatedSelectedConsumer = useMemo(() => {
|
||||
if (
|
||||
selectedConsumer &&
|
||||
consumers.includes(selectedConsumer) &&
|
||||
featureNameMap[selectedConsumer]
|
||||
) {
|
||||
return selectedConsumer;
|
||||
}
|
||||
return null;
|
||||
}, [selectedConsumer, consumers]);
|
||||
const selectedOptions = useMemo(
|
||||
() =>
|
||||
selectedConsumer
|
||||
? [{ value: selectedConsumer, label: featureNameMap[selectedConsumer] }]
|
||||
validatedSelectedConsumer
|
||||
? [{ value: validatedSelectedConsumer, label: featureNameMap[validatedSelectedConsumer] }]
|
||||
: [],
|
||||
[selectedConsumer]
|
||||
[validatedSelectedConsumer]
|
||||
);
|
||||
|
||||
const formattedSelectOptions: Array<EuiComboBoxOptionOption<RuleCreationValidConsumer>> =
|
||||
|
@ -115,8 +123,14 @@ export const RuleFormConsumerSelection = (props: RuleFormConsumerSelectionProps)
|
|||
}, [consumers]);
|
||||
|
||||
useEffect(() => {
|
||||
// At initialization we set to NULL to know that nobody selected anything
|
||||
onChange(null);
|
||||
// At initialization, select Stack Alerts, or the first value
|
||||
if (!validatedSelectedConsumer) {
|
||||
if (consumers.includes(STACK_ALERTS_FEATURE_ID)) {
|
||||
onChange(STACK_ALERTS_FEATURE_ID);
|
||||
return;
|
||||
}
|
||||
onChange(consumers[0] as RuleCreationValidConsumer);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
|
|
|
@ -40,7 +40,10 @@ import {
|
|||
RuleExecutionStatusErrorReasons,
|
||||
RuleLastRunOutcomeValues,
|
||||
} from '@kbn/alerting-plugin/common';
|
||||
import { ruleDetailsRoute as commonRuleDetailsRoute } from '@kbn/rule-data-utils';
|
||||
import {
|
||||
ruleDetailsRoute as commonRuleDetailsRoute,
|
||||
STACK_ALERTS_FEATURE_ID,
|
||||
} from '@kbn/rule-data-utils';
|
||||
import { MaintenanceWindowCallout } from '@kbn/alerts-ui-shared';
|
||||
import {
|
||||
Rule,
|
||||
|
@ -1004,6 +1007,7 @@ export const RulesList = ({
|
|||
ruleTypeRegistry={ruleTypeRegistry}
|
||||
ruleTypeIndex={ruleTypesState.data}
|
||||
onSave={refreshRules}
|
||||
initialSelectedConsumer={STACK_ALERTS_FEATURE_ID}
|
||||
/>
|
||||
</Suspense>
|
||||
)}
|
||||
|
|
|
@ -25,7 +25,7 @@ import type {
|
|||
EuiSuperSelectOption,
|
||||
EuiDataGridOnColumnResizeHandler,
|
||||
} from '@elastic/eui';
|
||||
import type { AlertConsumers, STACK_ALERTS_FEATURE_ID, ValidFeatureId } from '@kbn/rule-data-utils';
|
||||
import type { RuleCreationValidConsumer, ValidFeatureId } from '@kbn/rule-data-utils';
|
||||
import { EuiDataGridColumn, EuiDataGridControlColumn, EuiDataGridSorting } from '@elastic/eui';
|
||||
import { HttpSetup } from '@kbn/core/public';
|
||||
import { KueryNode } from '@kbn/es-query';
|
||||
|
@ -468,6 +468,7 @@ export interface RuleAddProps<MetaData = Record<string, any>> {
|
|||
filteredRuleTypes?: string[];
|
||||
validConsumers?: RuleCreationValidConsumer[];
|
||||
useRuleProducer?: boolean;
|
||||
initialSelectedConsumer?: RuleCreationValidConsumer | null;
|
||||
}
|
||||
export interface RuleDefinitionProps {
|
||||
rule: Rule;
|
||||
|
@ -852,8 +853,4 @@ export interface NotifyWhenSelectOptions {
|
|||
value: EuiSuperSelectOption<RuleNotifyWhenType>;
|
||||
}
|
||||
|
||||
export type RuleCreationValidConsumer =
|
||||
| typeof AlertConsumers.LOGS
|
||||
| typeof AlertConsumers.INFRASTRUCTURE
|
||||
| typeof AlertConsumers.OBSERVABILITY
|
||||
| typeof STACK_ALERTS_FEATURE_ID;
|
||||
export type { RuleCreationValidConsumer } from '@kbn/rule-data-utils';
|
||||
|
|
|
@ -143,6 +143,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) {
|
|||
feature: {
|
||||
actions: ['all'],
|
||||
stackAlerts: ['all'],
|
||||
logs: ['all'],
|
||||
discover: ['all'],
|
||||
advancedSettings: ['all'],
|
||||
indexPatterns: ['all'],
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue