Skip to content

Commit

Permalink
[8.x] [ResponseOps][Connectors] Show a licensing message if the user …
Browse files Browse the repository at this point in the history
…does not have the sufficient license for system actions. (#201396) (#201494)

# Backport

This will backport the following commits from `main` to `8.x`:
- [[ResponseOps][Connectors] Show a licensing message if the user does
not have the sufficient license for system actions.
(#201396)](#201396)

<!--- Backport version: 9.4.3 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT [{"author":{"name":"Christos
Nasikas","email":"[email protected]"},"sourceCommit":{"committedDate":"2024-11-23T09:17:11Z","message":"[ResponseOps][Connectors]
Show a licensing message if the user does not have the sufficient
license for system actions. (#201396)\n\nIf a user does not have a
sufficient license for a connector and the\r\nrule is already configured
with such a connector we show the following\r\nmessage:\r\n\r\n<img
width=\"1026\" alt=\"Screenshot 2024-11-22 at 1 48
10 PM\"\r\nsrc=\"https://github.com/user-attachments/assets/4b3d7197-ff3c-4673-9b37-9ca627dab0db\">\r\n\r\nThis
PR does the same for system actions.\r\n\r\n<img width=\"1162\"
alt=\"Screenshot 2024-11-22 at 1 03
06 PM\"\r\nsrc=\"https://github.com/user-attachments/assets/d1cbd479-ff65-453d-889a-ae7f5cd2b63b\">\r\n\r\n##
Testing\r\n\r\n1. Create a rule with a case action in Platinum
license\r\n2. Downgrade to basic\r\n3. Verify that a licensing message
is showing for the case action.\r\nVerify in all
solutions.\r\n\r\nIssue:
https://github.com/elastic/kibana/issues/189978\r\n\r\n###
Checklist\r\n\r\nCheck the PR satisfies following conditions.
\r\n\r\nReviewers should verify this PR satisfies this list as
well.\r\n\r\n- [x] [Unit or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common
scenarios","sha":"e02059590899bd67379a5ee2243eb004a2e86952","branchLabelMapping":{"^v9.0.0$":"main","^v8.18.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["bug","release_note:skip","Team:ResponseOps","v9.0.0","Feature:Alerting/RuleActions","Feature:Alerting/RulesManagement","backport:prev-minor","ci:build-serverless-image","v8.18.0"],"title":"[ResponseOps][Connectors]
Show a licensing message if the user does not have the sufficient
license for system
actions.","number":201396,"url":"https://github.com/elastic/kibana/pull/201396","mergeCommit":{"message":"[ResponseOps][Connectors]
Show a licensing message if the user does not have the sufficient
license for system actions. (#201396)\n\nIf a user does not have a
sufficient license for a connector and the\r\nrule is already configured
with such a connector we show the following\r\nmessage:\r\n\r\n<img
width=\"1026\" alt=\"Screenshot 2024-11-22 at 1 48
10 PM\"\r\nsrc=\"https://github.com/user-attachments/assets/4b3d7197-ff3c-4673-9b37-9ca627dab0db\">\r\n\r\nThis
PR does the same for system actions.\r\n\r\n<img width=\"1162\"
alt=\"Screenshot 2024-11-22 at 1 03
06 PM\"\r\nsrc=\"https://github.com/user-attachments/assets/d1cbd479-ff65-453d-889a-ae7f5cd2b63b\">\r\n\r\n##
Testing\r\n\r\n1. Create a rule with a case action in Platinum
license\r\n2. Downgrade to basic\r\n3. Verify that a licensing message
is showing for the case action.\r\nVerify in all
solutions.\r\n\r\nIssue:
https://github.com/elastic/kibana/issues/189978\r\n\r\n###
Checklist\r\n\r\nCheck the PR satisfies following conditions.
\r\n\r\nReviewers should verify this PR satisfies this list as
well.\r\n\r\n- [x] [Unit or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common
scenarios","sha":"e02059590899bd67379a5ee2243eb004a2e86952"}},"sourceBranch":"main","suggestedTargetBranches":["8.x"],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","branchLabelMappingKey":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/201396","number":201396,"mergeCommit":{"message":"[ResponseOps][Connectors]
Show a licensing message if the user does not have the sufficient
license for system actions. (#201396)\n\nIf a user does not have a
sufficient license for a connector and the\r\nrule is already configured
with such a connector we show the following\r\nmessage:\r\n\r\n<img
width=\"1026\" alt=\"Screenshot 2024-11-22 at 1 48
10 PM\"\r\nsrc=\"https://github.com/user-attachments/assets/4b3d7197-ff3c-4673-9b37-9ca627dab0db\">\r\n\r\nThis
PR does the same for system actions.\r\n\r\n<img width=\"1162\"
alt=\"Screenshot 2024-11-22 at 1 03
06 PM\"\r\nsrc=\"https://github.com/user-attachments/assets/d1cbd479-ff65-453d-889a-ae7f5cd2b63b\">\r\n\r\n##
Testing\r\n\r\n1. Create a rule with a case action in Platinum
license\r\n2. Downgrade to basic\r\n3. Verify that a licensing message
is showing for the case action.\r\nVerify in all
solutions.\r\n\r\nIssue:
https://github.com/elastic/kibana/issues/189978\r\n\r\n###
Checklist\r\n\r\nCheck the PR satisfies following conditions.
\r\n\r\nReviewers should verify this PR satisfies this list as
well.\r\n\r\n- [x] [Unit or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common
scenarios","sha":"e02059590899bd67379a5ee2243eb004a2e86952"}},{"branch":"8.x","label":"v8.18.0","branchLabelMappingKey":"^v8.18.0$","isSourceBranch":false,"state":"NOT_CREATED"}]}]
BACKPORT-->

Co-authored-by: Christos Nasikas <[email protected]>
  • Loading branch information
kibanamachine and cnasikas authored Nov 23, 2024
1 parent 7ac111e commit baa4538
Show file tree
Hide file tree
Showing 4 changed files with 205 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down Expand Up @@ -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')];

Expand Down Expand Up @@ -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();
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -38,13 +38,76 @@ 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;
index: number;
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;

Expand All @@ -54,7 +117,6 @@ export const RuleActionsSystemActionsItem = (props: RuleActionsSystemActionsItem
selectedRuleType,
connectorTypes,
connectors,
aadTemplateFields,
} = useRuleFormState();

const [isOpen, setIsOpen] = useState(true);
Expand All @@ -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();
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -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();
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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 ? (
Expand Down Expand Up @@ -212,6 +217,8 @@ export const SystemActionTypeForm = ({
) : null}
</EuiSplitPanel.Inner>
</>
) : (
checkEnabledResult.messageCard
);

return (
Expand Down

0 comments on commit baa4538

Please sign in to comment.