mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[ResponseOps][Connectors] Show a licensing message if the user does not have the sufficient license for system actions. (#201396)
If a user does not have a sufficient license for a connector and the rule is already configured with such a connector we show the following message: <img width="1026" alt="Screenshot 2024-11-22 at 1 48 10 PM" src="https://github.com/user-attachments/assets/4b3d7197-ff3c-4673-9b37-9ca627dab0db"> This PR does the same for system actions. <img width="1162" alt="Screenshot 2024-11-22 at 1 03 06 PM" src="https://github.com/user-attachments/assets/d1cbd479-ff65-453d-889a-ae7f5cd2b63b"> ## Testing 1. Create a rule with a case action in Platinum license 2. Downgrade to basic 3. Verify that a licensing message is showing for the case action. Verify in all solutions. Issue: https://github.com/elastic/kibana/issues/189978 ### Checklist Check the PR satisfies following conditions. Reviewers should verify this PR satisfies this list as well. - [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
This commit is contained in:
parent
7dfda396c0
commit
e020595908
4 changed files with 205 additions and 26 deletions
|
@ -21,6 +21,7 @@ import {
|
|||
import { ActionTypeModel } from '../../common';
|
||||
import { RuleActionsMessageProps } from './rule_actions_message';
|
||||
import { RuleActionsSystemActionsItem } from './rule_actions_system_actions_item';
|
||||
import { I18nProvider } from '@kbn/i18n-react';
|
||||
|
||||
jest.mock('../hooks', () => ({
|
||||
useRuleFormState: jest.fn(),
|
||||
|
@ -81,7 +82,7 @@ const { validateParamsForWarnings } = jest.requireMock(
|
|||
'../validation/validate_params_for_warnings'
|
||||
);
|
||||
|
||||
const mockConnectors = [getConnector('1', { id: 'action-1' })];
|
||||
const mockConnectors = [getConnector('1', { id: 'action-1', isSystemAction: true })];
|
||||
|
||||
const mockActionTypes = [getActionType('1')];
|
||||
|
||||
|
@ -260,4 +261,59 @@ describe('ruleActionsSystemActionsItem', () => {
|
|||
|
||||
expect(screen.getByText('warning message!')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
describe('licensing', () => {
|
||||
it('should render the licensing message if the user does not have the sufficient license', async () => {
|
||||
const mockConnectorsWithLicensing = [
|
||||
getConnector('1', { id: 'action-1', isSystemAction: true }),
|
||||
];
|
||||
const mockActionTypesWithLicensing = [
|
||||
getActionType('1', {
|
||||
enabledInLicense: false,
|
||||
minimumLicenseRequired: 'platinum' as const,
|
||||
}),
|
||||
];
|
||||
|
||||
const actionTypeRegistry = new TypeRegistry<ActionTypeModel>();
|
||||
actionTypeRegistry.register(
|
||||
getActionTypeModel('1', {
|
||||
id: 'actionType-1',
|
||||
validateParams: mockValidate,
|
||||
})
|
||||
);
|
||||
useRuleFormState.mockReturnValue({
|
||||
plugins: {
|
||||
actionTypeRegistry,
|
||||
http: {
|
||||
basePath: {
|
||||
publicBaseUrl: 'publicUrl',
|
||||
},
|
||||
},
|
||||
},
|
||||
actionsParamsErrors: {},
|
||||
selectedRuleType: {
|
||||
...ruleType,
|
||||
enabledInLicense: false,
|
||||
minimumLicenseRequired: 'platinum' as const,
|
||||
},
|
||||
aadTemplateFields: [],
|
||||
connectors: mockConnectorsWithLicensing,
|
||||
connectorTypes: mockActionTypesWithLicensing,
|
||||
});
|
||||
|
||||
render(
|
||||
<I18nProvider>
|
||||
<RuleActionsSystemActionsItem
|
||||
action={getAction('1', { actionTypeId: 'actionType-1' })}
|
||||
index={0}
|
||||
producerId="stackAlerts"
|
||||
/>
|
||||
</I18nProvider>
|
||||
);
|
||||
|
||||
expect(
|
||||
await screen.findByText('This feature requires a Platinum license.')
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -28,7 +28,7 @@ import { RuleActionParam, RuleSystemAction } from '@kbn/alerting-types';
|
|||
import { SavedObjectAttribute } from '@kbn/core/types';
|
||||
import { css } from '@emotion/react';
|
||||
import { useRuleFormDispatch, useRuleFormState } from '../hooks';
|
||||
import { RuleFormParamsErrors } from '../../common';
|
||||
import { ActionConnector, RuleFormParamsErrors } from '../../common';
|
||||
import {
|
||||
ACTION_ERROR_TOOLTIP,
|
||||
ACTION_WARNING_TITLE,
|
||||
|
@ -38,6 +38,11 @@ import {
|
|||
import { RuleActionsMessage } from './rule_actions_message';
|
||||
import { validateParamsForWarnings } from '../validation';
|
||||
import { getAvailableActionVariables } from '../../action_variables';
|
||||
import {
|
||||
IsDisabledResult,
|
||||
IsEnabledResult,
|
||||
checkActionFormActionTypeEnabled,
|
||||
} from '../utils/check_action_type_enabled';
|
||||
|
||||
interface RuleActionsSystemActionsItemProps {
|
||||
action: RuleSystemAction;
|
||||
|
@ -45,6 +50,64 @@ interface RuleActionsSystemActionsItemProps {
|
|||
producerId: string;
|
||||
}
|
||||
|
||||
interface SystemActionAccordionContentProps extends RuleActionsSystemActionsItemProps {
|
||||
connector: ActionConnector;
|
||||
checkEnabledResult?: IsEnabledResult | IsDisabledResult | null;
|
||||
warning?: string | null;
|
||||
onParamsChange: (key: string, value: RuleActionParam) => void;
|
||||
}
|
||||
|
||||
const SystemActionAccordionContent: React.FC<SystemActionAccordionContentProps> = React.memo(
|
||||
({ connector, checkEnabledResult, action, index, producerId, warning, onParamsChange }) => {
|
||||
const { aadTemplateFields } = useRuleFormState();
|
||||
const { euiTheme } = useEuiTheme();
|
||||
const plain = useEuiBackgroundColor('plain');
|
||||
|
||||
if (!connector || !checkEnabledResult) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!checkEnabledResult.isEnabled) {
|
||||
return (
|
||||
<EuiFlexGroup
|
||||
direction="column"
|
||||
style={{
|
||||
padding: euiTheme.size.l,
|
||||
backgroundColor: plain,
|
||||
borderRadius: euiTheme.border.radius.medium,
|
||||
}}
|
||||
>
|
||||
<EuiFlexItem>{checkEnabledResult.messageCard}</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiFlexGroup
|
||||
data-test-subj="ruleActionsSystemActionsItemAccordionContent"
|
||||
direction="column"
|
||||
style={{
|
||||
padding: euiTheme.size.l,
|
||||
backgroundColor: plain,
|
||||
}}
|
||||
>
|
||||
<EuiFlexItem>
|
||||
<RuleActionsMessage
|
||||
useDefaultMessage
|
||||
action={action}
|
||||
index={index}
|
||||
connector={connector}
|
||||
producerId={producerId}
|
||||
warning={warning}
|
||||
templateFields={aadTemplateFields}
|
||||
onParamsChange={onParamsChange}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export const RuleActionsSystemActionsItem = (props: RuleActionsSystemActionsItemProps) => {
|
||||
const { action, index, producerId } = props;
|
||||
|
||||
|
@ -54,7 +117,6 @@ export const RuleActionsSystemActionsItem = (props: RuleActionsSystemActionsItem
|
|||
selectedRuleType,
|
||||
connectorTypes,
|
||||
connectors,
|
||||
aadTemplateFields,
|
||||
} = useRuleFormState();
|
||||
|
||||
const [isOpen, setIsOpen] = useState(true);
|
||||
|
@ -64,7 +126,6 @@ export const RuleActionsSystemActionsItem = (props: RuleActionsSystemActionsItem
|
|||
const [warning, setWarning] = useState<string | null>(null);
|
||||
|
||||
const subdued = useEuiBackgroundColor('subdued');
|
||||
const plain = useEuiBackgroundColor('plain');
|
||||
const { euiTheme } = useEuiTheme();
|
||||
|
||||
const dispatch = useRuleFormDispatch();
|
||||
|
@ -156,6 +217,13 @@ export const RuleActionsSystemActionsItem = (props: RuleActionsSystemActionsItem
|
|||
]
|
||||
);
|
||||
|
||||
const checkEnabledResult = useMemo(() => {
|
||||
if (!actionType) {
|
||||
return null;
|
||||
}
|
||||
return checkActionFormActionTypeEnabled(actionType, []);
|
||||
}, [actionType]);
|
||||
|
||||
return (
|
||||
<EuiAccordion
|
||||
data-test-subj="ruleActionsSystemActionsItem"
|
||||
|
@ -247,27 +315,15 @@ export const RuleActionsSystemActionsItem = (props: RuleActionsSystemActionsItem
|
|||
</EuiPanel>
|
||||
}
|
||||
>
|
||||
<EuiFlexGroup
|
||||
data-test-subj="ruleActionsSystemActionsItemAccordionContent"
|
||||
direction="column"
|
||||
style={{
|
||||
padding: euiTheme.size.l,
|
||||
backgroundColor: plain,
|
||||
}}
|
||||
>
|
||||
<EuiFlexItem>
|
||||
<RuleActionsMessage
|
||||
useDefaultMessage
|
||||
action={action}
|
||||
index={index}
|
||||
connector={connector}
|
||||
producerId={producerId}
|
||||
warning={warning}
|
||||
templateFields={aadTemplateFields}
|
||||
onParamsChange={onParamsChange}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<SystemActionAccordionContent
|
||||
action={action}
|
||||
index={index}
|
||||
producerId={producerId}
|
||||
warning={warning}
|
||||
connector={connector}
|
||||
checkEnabledResult={checkEnabledResult}
|
||||
onParamsChange={onParamsChange}
|
||||
/>
|
||||
</EuiAccordion>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -209,4 +209,64 @@ describe('action_type_form', () => {
|
|||
|
||||
expect(setActionParamsProperty).toHaveBeenCalledWith('my-key', 'my-value', 1);
|
||||
});
|
||||
|
||||
describe('licensing', () => {
|
||||
const actionTypeIndexDefaultWithLicensing = {
|
||||
...actionTypeIndexDefault,
|
||||
'.test-system-action': {
|
||||
...actionTypeIndexDefault['.test-system-action'],
|
||||
enabledInLicense: false,
|
||||
minimumLicenseRequired: 'platinum' as const,
|
||||
},
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
const actionType = actionTypeRegistryMock.createMockActionTypeModel({
|
||||
id: '.test-system-action-with-license',
|
||||
iconClass: 'test',
|
||||
selectMessage: 'test',
|
||||
validateParams: (): Promise<GenericValidationResult<unknown>> => {
|
||||
const validationResult = { errors: {} };
|
||||
return Promise.resolve(validationResult);
|
||||
},
|
||||
actionConnectorFields: null,
|
||||
actionParamsFields: mockedActionParamsFields,
|
||||
defaultActionParams: {
|
||||
dedupKey: 'test',
|
||||
eventAction: 'resolve',
|
||||
},
|
||||
isSystemActionType: true,
|
||||
});
|
||||
|
||||
actionTypeRegistry.get.mockReturnValue(actionType);
|
||||
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should render the licensing message if the user does not have the sufficient license', async () => {
|
||||
render(
|
||||
<I18nProvider>
|
||||
<SystemActionTypeForm
|
||||
actionConnector={actionConnector}
|
||||
actionItem={actionItem}
|
||||
connectors={connectors}
|
||||
onDeleteAction={jest.fn()}
|
||||
setActionParamsProperty={jest.fn()}
|
||||
index={1}
|
||||
actionTypesIndex={actionTypeIndexDefaultWithLicensing}
|
||||
actionTypeRegistry={actionTypeRegistry}
|
||||
messageVariables={{ context: [], state: [], params: [] }}
|
||||
summaryMessageVariables={{ context: [], state: [], params: [] }}
|
||||
producerId={AlertConsumers.INFRASTRUCTURE}
|
||||
featureId={AlertConsumers.INFRASTRUCTURE}
|
||||
ruleTypeId={'test'}
|
||||
/>
|
||||
</I18nProvider>
|
||||
);
|
||||
|
||||
expect(
|
||||
await screen.findByText('This feature requires a Platinum license.')
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -28,6 +28,7 @@ import { isEmpty, partition, some } from 'lodash';
|
|||
import { ActionVariable, RuleActionParam } from '@kbn/alerting-plugin/common';
|
||||
import { ActionGroupWithMessageVariables } from '@kbn/triggers-actions-ui-types';
|
||||
import { transformActionVariables } from '@kbn/alerts-ui-shared/src/action_variables/transforms';
|
||||
import { checkActionFormActionTypeEnabled } from '@kbn/alerts-ui-shared/src/rule_form/utils/check_action_type_enabled';
|
||||
import { TECH_PREVIEW_DESCRIPTION, TECH_PREVIEW_LABEL } from '../translations';
|
||||
import {
|
||||
IErrorObject,
|
||||
|
@ -167,8 +168,12 @@ export const SystemActionTypeForm = ({
|
|||
};
|
||||
|
||||
const ParamsFieldsComponent = actionTypeRegistered.actionParamsFields;
|
||||
const checkEnabledResult = checkActionFormActionTypeEnabled(
|
||||
actionTypesIndex[actionConnector.actionTypeId],
|
||||
[]
|
||||
);
|
||||
|
||||
const accordionContent = (
|
||||
const accordionContent = checkEnabledResult.isEnabled ? (
|
||||
<>
|
||||
<EuiSplitPanel.Inner color="plain">
|
||||
{ParamsFieldsComponent ? (
|
||||
|
@ -212,6 +217,8 @@ export const SystemActionTypeForm = ({
|
|||
) : null}
|
||||
</EuiSplitPanel.Inner>
|
||||
</>
|
||||
) : (
|
||||
checkEnabledResult.messageCard
|
||||
);
|
||||
|
||||
return (
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue