mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
Add indicator to rule details page if broken connector (#112017)
* Add indicator to rule details page if broken connector * Adding unit test * Updating disabled state and adding warning callout * Adding back the tooltip * eslint Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
c76082e006
commit
9574b4b86b
2 changed files with 413 additions and 386 deletions
|
@ -39,6 +39,10 @@ jest.mock('react-router-dom', () => ({
|
|||
}),
|
||||
}));
|
||||
|
||||
jest.mock('../../../lib/action_connector_api', () => ({
|
||||
loadAllActions: jest.fn().mockResolvedValue([]),
|
||||
}));
|
||||
|
||||
jest.mock('../../../lib/capabilities', () => ({
|
||||
hasAllPrivilege: jest.fn(() => true),
|
||||
hasSaveAlertsCapability: jest.fn(() => true),
|
||||
|
@ -60,24 +64,22 @@ const authorizedConsumers = {
|
|||
};
|
||||
const recoveryActionGroup: ActionGroup<'recovered'> = { id: 'recovered', name: 'Recovered' };
|
||||
|
||||
describe('alert_details', () => {
|
||||
// mock Api handlers
|
||||
const alertType: AlertType = {
|
||||
id: '.noop',
|
||||
name: 'No Op',
|
||||
actionGroups: [{ id: 'default', name: 'Default' }],
|
||||
recoveryActionGroup,
|
||||
actionVariables: { context: [], state: [], params: [] },
|
||||
defaultActionGroupId: 'default',
|
||||
minimumLicenseRequired: 'basic',
|
||||
producer: ALERTS_FEATURE_ID,
|
||||
authorizedConsumers,
|
||||
enabledInLicense: true,
|
||||
};
|
||||
|
||||
describe('alert_details', () => {
|
||||
it('renders the alert name as a title', () => {
|
||||
const alert = mockAlert();
|
||||
const alertType: AlertType = {
|
||||
id: '.noop',
|
||||
name: 'No Op',
|
||||
actionGroups: [{ id: 'default', name: 'Default' }],
|
||||
recoveryActionGroup,
|
||||
actionVariables: { context: [], state: [], params: [] },
|
||||
defaultActionGroupId: 'default',
|
||||
minimumLicenseRequired: 'basic',
|
||||
producer: ALERTS_FEATURE_ID,
|
||||
authorizedConsumers,
|
||||
enabledInLicense: true,
|
||||
};
|
||||
|
||||
expect(
|
||||
shallow(
|
||||
<AlertDetails alert={alert} alertType={alertType} actionTypes={[]} {...mockAlertApis} />
|
||||
|
@ -87,19 +89,6 @@ describe('alert_details', () => {
|
|||
|
||||
it('renders the alert type badge', () => {
|
||||
const alert = mockAlert();
|
||||
const alertType: AlertType = {
|
||||
id: '.noop',
|
||||
name: 'No Op',
|
||||
actionGroups: [{ id: 'default', name: 'Default' }],
|
||||
recoveryActionGroup,
|
||||
actionVariables: { context: [], state: [], params: [] },
|
||||
defaultActionGroupId: 'default',
|
||||
minimumLicenseRequired: 'basic',
|
||||
producer: ALERTS_FEATURE_ID,
|
||||
authorizedConsumers,
|
||||
enabledInLicense: true,
|
||||
};
|
||||
|
||||
expect(
|
||||
shallow(
|
||||
<AlertDetails alert={alert} alertType={alertType} actionTypes={[]} {...mockAlertApis} />
|
||||
|
@ -118,19 +107,6 @@ describe('alert_details', () => {
|
|||
},
|
||||
},
|
||||
});
|
||||
const alertType: AlertType = {
|
||||
id: '.noop',
|
||||
name: 'No Op',
|
||||
actionGroups: [{ id: 'default', name: 'Default' }],
|
||||
recoveryActionGroup,
|
||||
actionVariables: { context: [], state: [], params: [] },
|
||||
defaultActionGroupId: 'default',
|
||||
minimumLicenseRequired: 'basic',
|
||||
producer: ALERTS_FEATURE_ID,
|
||||
authorizedConsumers,
|
||||
enabledInLicense: true,
|
||||
};
|
||||
|
||||
expect(
|
||||
shallow(
|
||||
<AlertDetails alert={alert} alertType={alertType} actionTypes={[]} {...mockAlertApis} />
|
||||
|
@ -155,19 +131,6 @@ describe('alert_details', () => {
|
|||
],
|
||||
});
|
||||
|
||||
const alertType: AlertType = {
|
||||
id: '.noop',
|
||||
name: 'No Op',
|
||||
actionGroups: [{ id: 'default', name: 'Default' }],
|
||||
recoveryActionGroup,
|
||||
actionVariables: { context: [], state: [], params: [] },
|
||||
defaultActionGroupId: 'default',
|
||||
minimumLicenseRequired: 'basic',
|
||||
producer: ALERTS_FEATURE_ID,
|
||||
authorizedConsumers,
|
||||
enabledInLicense: true,
|
||||
};
|
||||
|
||||
const actionTypes: ActionType[] = [
|
||||
{
|
||||
id: '.server-log',
|
||||
|
@ -212,18 +175,6 @@ describe('alert_details', () => {
|
|||
},
|
||||
],
|
||||
});
|
||||
const alertType: AlertType = {
|
||||
id: '.noop',
|
||||
name: 'No Op',
|
||||
actionGroups: [{ id: 'default', name: 'Default' }],
|
||||
recoveryActionGroup,
|
||||
actionVariables: { context: [], state: [], params: [] },
|
||||
defaultActionGroupId: 'default',
|
||||
producer: ALERTS_FEATURE_ID,
|
||||
minimumLicenseRequired: 'basic',
|
||||
authorizedConsumers,
|
||||
enabledInLicense: true,
|
||||
};
|
||||
const actionTypes: ActionType[] = [
|
||||
{
|
||||
id: '.server-log',
|
||||
|
@ -273,20 +224,6 @@ describe('alert_details', () => {
|
|||
describe('links', () => {
|
||||
it('links to the app that created the alert', () => {
|
||||
const alert = mockAlert();
|
||||
|
||||
const alertType: AlertType = {
|
||||
id: '.noop',
|
||||
name: 'No Op',
|
||||
actionGroups: [{ id: 'default', name: 'Default' }],
|
||||
recoveryActionGroup,
|
||||
actionVariables: { context: [], state: [], params: [] },
|
||||
defaultActionGroupId: 'default',
|
||||
producer: ALERTS_FEATURE_ID,
|
||||
authorizedConsumers,
|
||||
minimumLicenseRequired: 'basic',
|
||||
enabledInLicense: true,
|
||||
};
|
||||
|
||||
expect(
|
||||
shallow(
|
||||
<AlertDetails alert={alert} alertType={alertType} actionTypes={[]} {...mockAlertApis} />
|
||||
|
@ -296,19 +233,6 @@ describe('alert_details', () => {
|
|||
|
||||
it('links to the Edit flyout', () => {
|
||||
const alert = mockAlert();
|
||||
|
||||
const alertType: AlertType = {
|
||||
id: '.noop',
|
||||
name: 'No Op',
|
||||
actionGroups: [{ id: 'default', name: 'Default' }],
|
||||
recoveryActionGroup,
|
||||
actionVariables: { context: [], state: [], params: [] },
|
||||
defaultActionGroupId: 'default',
|
||||
producer: ALERTS_FEATURE_ID,
|
||||
authorizedConsumers,
|
||||
minimumLicenseRequired: 'basic',
|
||||
enabledInLicense: true,
|
||||
};
|
||||
const pageHeaderProps = shallow(
|
||||
<AlertDetails alert={alert} alertType={alertType} actionTypes={[]} {...mockAlertApis} />
|
||||
)
|
||||
|
@ -316,22 +240,22 @@ describe('alert_details', () => {
|
|||
.props() as EuiPageHeaderProps;
|
||||
const rightSideItems = pageHeaderProps.rightSideItems;
|
||||
expect(!!rightSideItems && rightSideItems[2]!).toMatchInlineSnapshot(`
|
||||
<React.Fragment>
|
||||
<EuiButtonEmpty
|
||||
data-test-subj="openEditAlertFlyoutButton"
|
||||
disabled={false}
|
||||
iconType="pencil"
|
||||
name="edit"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Edit"
|
||||
id="xpack.triggersActionsUI.sections.alertDetails.editAlertButtonLabel"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
</React.Fragment>
|
||||
`);
|
||||
<React.Fragment>
|
||||
<EuiButtonEmpty
|
||||
data-test-subj="openEditAlertFlyoutButton"
|
||||
disabled={false}
|
||||
iconType="pencil"
|
||||
name="edit"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Edit"
|
||||
id="xpack.triggersActionsUI.sections.alertDetails.editAlertButtonLabel"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
</React.Fragment>
|
||||
`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -341,20 +265,6 @@ describe('disable button', () => {
|
|||
const alert = mockAlert({
|
||||
enabled: true,
|
||||
});
|
||||
|
||||
const alertType: AlertType = {
|
||||
id: '.noop',
|
||||
name: 'No Op',
|
||||
actionGroups: [{ id: 'default', name: 'Default' }],
|
||||
recoveryActionGroup,
|
||||
actionVariables: { context: [], state: [], params: [] },
|
||||
defaultActionGroupId: 'default',
|
||||
producer: ALERTS_FEATURE_ID,
|
||||
authorizedConsumers,
|
||||
minimumLicenseRequired: 'basic',
|
||||
enabledInLicense: true,
|
||||
};
|
||||
|
||||
const enableButton = shallow(
|
||||
<AlertDetails alert={alert} alertType={alertType} actionTypes={[]} {...mockAlertApis} />
|
||||
)
|
||||
|
@ -368,55 +278,43 @@ describe('disable button', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('should render a enable button when alert is disabled', () => {
|
||||
it('should render a enable button and empty state when alert is disabled', async () => {
|
||||
const alert = mockAlert({
|
||||
enabled: false,
|
||||
});
|
||||
|
||||
const alertType: AlertType = {
|
||||
id: '.noop',
|
||||
name: 'No Op',
|
||||
actionGroups: [{ id: 'default', name: 'Default' }],
|
||||
recoveryActionGroup,
|
||||
actionVariables: { context: [], state: [], params: [] },
|
||||
defaultActionGroupId: 'default',
|
||||
producer: ALERTS_FEATURE_ID,
|
||||
authorizedConsumers,
|
||||
minimumLicenseRequired: 'basic',
|
||||
enabledInLicense: true,
|
||||
};
|
||||
|
||||
const enableButton = shallow(
|
||||
const wrapper = mountWithIntl(
|
||||
<AlertDetails alert={alert} alertType={alertType} actionTypes={[]} {...mockAlertApis} />
|
||||
)
|
||||
.find(EuiSwitch)
|
||||
.find('[name="enable"]')
|
||||
.first();
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
await nextTick();
|
||||
wrapper.update();
|
||||
});
|
||||
const enableButton = wrapper.find(EuiSwitch).find('[name="enable"]').first();
|
||||
const disabledEmptyPrompt = wrapper.find('[data-test-subj="disabledEmptyPrompt"]');
|
||||
const disabledEmptyPromptAction = wrapper.find('[data-test-subj="disabledEmptyPromptAction"]');
|
||||
|
||||
expect(enableButton.props()).toMatchObject({
|
||||
checked: false,
|
||||
disabled: false,
|
||||
});
|
||||
expect(disabledEmptyPrompt.exists()).toBeTruthy();
|
||||
expect(disabledEmptyPromptAction.exists()).toBeTruthy();
|
||||
|
||||
disabledEmptyPromptAction.first().simulate('click');
|
||||
|
||||
await act(async () => {
|
||||
await nextTick();
|
||||
wrapper.update();
|
||||
});
|
||||
|
||||
expect(mockAlertApis.enableAlert).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should enable the alert when alert is disabled and button is clicked', () => {
|
||||
it('should disable the alert when alert is enabled and button is clicked', () => {
|
||||
const alert = mockAlert({
|
||||
enabled: true,
|
||||
});
|
||||
|
||||
const alertType: AlertType = {
|
||||
id: '.noop',
|
||||
name: 'No Op',
|
||||
actionGroups: [{ id: 'default', name: 'Default' }],
|
||||
recoveryActionGroup,
|
||||
actionVariables: { context: [], state: [], params: [] },
|
||||
defaultActionGroupId: 'default',
|
||||
producer: ALERTS_FEATURE_ID,
|
||||
authorizedConsumers,
|
||||
minimumLicenseRequired: 'basic',
|
||||
enabledInLicense: true,
|
||||
};
|
||||
|
||||
const disableAlert = jest.fn();
|
||||
const enableButton = shallow(
|
||||
<AlertDetails
|
||||
|
@ -439,24 +337,10 @@ describe('disable button', () => {
|
|||
expect(disableAlert).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should disable the alert when alert is enabled and button is clicked', () => {
|
||||
it('should enable the alert when alert is disabled and button is clicked', () => {
|
||||
const alert = mockAlert({
|
||||
enabled: false,
|
||||
});
|
||||
|
||||
const alertType: AlertType = {
|
||||
id: '.noop',
|
||||
name: 'No Op',
|
||||
actionGroups: [{ id: 'default', name: 'Default' }],
|
||||
recoveryActionGroup,
|
||||
actionVariables: { context: [], state: [], params: [] },
|
||||
defaultActionGroupId: 'default',
|
||||
producer: ALERTS_FEATURE_ID,
|
||||
authorizedConsumers,
|
||||
minimumLicenseRequired: 'basic',
|
||||
enabledInLicense: true,
|
||||
};
|
||||
|
||||
const enableAlert = jest.fn();
|
||||
const enableButton = shallow(
|
||||
<AlertDetails
|
||||
|
@ -492,19 +376,6 @@ describe('disable button', () => {
|
|||
},
|
||||
});
|
||||
|
||||
const alertType: AlertType = {
|
||||
id: '.noop',
|
||||
name: 'No Op',
|
||||
actionGroups: [{ id: 'default', name: 'Default' }],
|
||||
recoveryActionGroup,
|
||||
actionVariables: { context: [], state: [], params: [] },
|
||||
defaultActionGroupId: 'default',
|
||||
producer: ALERTS_FEATURE_ID,
|
||||
authorizedConsumers,
|
||||
minimumLicenseRequired: 'basic',
|
||||
enabledInLicense: true,
|
||||
};
|
||||
|
||||
const disableAlert = jest.fn();
|
||||
const enableAlert = jest.fn();
|
||||
const wrapper = mountWithIntl(
|
||||
|
@ -565,19 +436,6 @@ describe('disable button', () => {
|
|||
},
|
||||
});
|
||||
|
||||
const alertType: AlertType = {
|
||||
id: '.noop',
|
||||
name: 'No Op',
|
||||
actionGroups: [{ id: 'default', name: 'Default' }],
|
||||
recoveryActionGroup,
|
||||
actionVariables: { context: [], state: [], params: [] },
|
||||
defaultActionGroupId: 'default',
|
||||
producer: ALERTS_FEATURE_ID,
|
||||
authorizedConsumers,
|
||||
minimumLicenseRequired: 'basic',
|
||||
enabledInLicense: true,
|
||||
};
|
||||
|
||||
const disableAlert = jest.fn(async () => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 6000));
|
||||
});
|
||||
|
@ -630,27 +488,12 @@ describe('mute button', () => {
|
|||
enabled: true,
|
||||
muteAll: false,
|
||||
});
|
||||
|
||||
const alertType: AlertType = {
|
||||
id: '.noop',
|
||||
name: 'No Op',
|
||||
actionGroups: [{ id: 'default', name: 'Default' }],
|
||||
recoveryActionGroup,
|
||||
actionVariables: { context: [], state: [], params: [] },
|
||||
defaultActionGroupId: 'default',
|
||||
producer: ALERTS_FEATURE_ID,
|
||||
authorizedConsumers,
|
||||
minimumLicenseRequired: 'basic',
|
||||
enabledInLicense: true,
|
||||
};
|
||||
|
||||
const enableButton = shallow(
|
||||
<AlertDetails alert={alert} alertType={alertType} actionTypes={[]} {...mockAlertApis} />
|
||||
)
|
||||
.find(EuiSwitch)
|
||||
.find('[name="mute"]')
|
||||
.first();
|
||||
|
||||
expect(enableButton.props()).toMatchObject({
|
||||
checked: false,
|
||||
disabled: false,
|
||||
|
@ -662,27 +505,12 @@ describe('mute button', () => {
|
|||
enabled: true,
|
||||
muteAll: true,
|
||||
});
|
||||
|
||||
const alertType: AlertType = {
|
||||
id: '.noop',
|
||||
name: 'No Op',
|
||||
actionGroups: [{ id: 'default', name: 'Default' }],
|
||||
recoveryActionGroup,
|
||||
actionVariables: { context: [], state: [], params: [] },
|
||||
defaultActionGroupId: 'default',
|
||||
producer: ALERTS_FEATURE_ID,
|
||||
authorizedConsumers,
|
||||
minimumLicenseRequired: 'basic',
|
||||
enabledInLicense: true,
|
||||
};
|
||||
|
||||
const enableButton = shallow(
|
||||
<AlertDetails alert={alert} alertType={alertType} actionTypes={[]} {...mockAlertApis} />
|
||||
)
|
||||
.find(EuiSwitch)
|
||||
.find('[name="mute"]')
|
||||
.first();
|
||||
|
||||
expect(enableButton.props()).toMatchObject({
|
||||
checked: true,
|
||||
disabled: false,
|
||||
|
@ -694,20 +522,6 @@ describe('mute button', () => {
|
|||
enabled: true,
|
||||
muteAll: false,
|
||||
});
|
||||
|
||||
const alertType: AlertType = {
|
||||
id: '.noop',
|
||||
name: 'No Op',
|
||||
actionGroups: [{ id: 'default', name: 'Default' }],
|
||||
recoveryActionGroup,
|
||||
actionVariables: { context: [], state: [], params: [] },
|
||||
defaultActionGroupId: 'default',
|
||||
producer: ALERTS_FEATURE_ID,
|
||||
authorizedConsumers,
|
||||
minimumLicenseRequired: 'basic',
|
||||
enabledInLicense: true,
|
||||
};
|
||||
|
||||
const muteAlert = jest.fn();
|
||||
const enableButton = shallow(
|
||||
<AlertDetails
|
||||
|
@ -721,7 +535,6 @@ describe('mute button', () => {
|
|||
.find(EuiSwitch)
|
||||
.find('[name="mute"]')
|
||||
.first();
|
||||
|
||||
enableButton.simulate('click');
|
||||
const handler = enableButton.prop('onChange');
|
||||
expect(typeof handler).toEqual('function');
|
||||
|
@ -735,20 +548,6 @@ describe('mute button', () => {
|
|||
enabled: true,
|
||||
muteAll: true,
|
||||
});
|
||||
|
||||
const alertType: AlertType = {
|
||||
id: '.noop',
|
||||
name: 'No Op',
|
||||
actionGroups: [{ id: 'default', name: 'Default' }],
|
||||
recoveryActionGroup,
|
||||
actionVariables: { context: [], state: [], params: [] },
|
||||
defaultActionGroupId: 'default',
|
||||
producer: ALERTS_FEATURE_ID,
|
||||
authorizedConsumers,
|
||||
minimumLicenseRequired: 'basic',
|
||||
enabledInLicense: true,
|
||||
};
|
||||
|
||||
const unmuteAlert = jest.fn();
|
||||
const enableButton = shallow(
|
||||
<AlertDetails
|
||||
|
@ -762,7 +561,6 @@ describe('mute button', () => {
|
|||
.find(EuiSwitch)
|
||||
.find('[name="mute"]')
|
||||
.first();
|
||||
|
||||
enableButton.simulate('click');
|
||||
const handler = enableButton.prop('onChange');
|
||||
expect(typeof handler).toEqual('function');
|
||||
|
@ -776,27 +574,12 @@ describe('mute button', () => {
|
|||
enabled: false,
|
||||
muteAll: false,
|
||||
});
|
||||
|
||||
const alertType: AlertType = {
|
||||
id: '.noop',
|
||||
name: 'No Op',
|
||||
actionGroups: [{ id: 'default', name: 'Default' }],
|
||||
recoveryActionGroup,
|
||||
actionVariables: { context: [], state: [], params: [] },
|
||||
defaultActionGroupId: 'default',
|
||||
producer: ALERTS_FEATURE_ID,
|
||||
authorizedConsumers,
|
||||
minimumLicenseRequired: 'basic',
|
||||
enabledInLicense: true,
|
||||
};
|
||||
|
||||
const enableButton = shallow(
|
||||
<AlertDetails alert={alert} alertType={alertType} actionTypes={[]} {...mockAlertApis} />
|
||||
)
|
||||
.find(EuiSwitch)
|
||||
.find('[name="mute"]')
|
||||
.first();
|
||||
|
||||
expect(enableButton.props()).toMatchObject({
|
||||
checked: false,
|
||||
disabled: true,
|
||||
|
@ -843,20 +626,6 @@ describe('edit button', () => {
|
|||
},
|
||||
],
|
||||
});
|
||||
|
||||
const alertType: AlertType = {
|
||||
id: '.noop',
|
||||
name: 'No Op',
|
||||
actionGroups: [{ id: 'default', name: 'Default' }],
|
||||
recoveryActionGroup,
|
||||
actionVariables: { context: [], state: [], params: [] },
|
||||
defaultActionGroupId: 'default',
|
||||
producer: 'alerting',
|
||||
authorizedConsumers,
|
||||
minimumLicenseRequired: 'basic',
|
||||
enabledInLicense: true,
|
||||
};
|
||||
|
||||
const pageHeaderProps = shallow(
|
||||
<AlertDetails
|
||||
alert={alert}
|
||||
|
@ -869,27 +638,27 @@ describe('edit button', () => {
|
|||
.props() as EuiPageHeaderProps;
|
||||
const rightSideItems = pageHeaderProps.rightSideItems;
|
||||
expect(!!rightSideItems && rightSideItems[2]!).toMatchInlineSnapshot(`
|
||||
<React.Fragment>
|
||||
<EuiButtonEmpty
|
||||
data-test-subj="openEditAlertFlyoutButton"
|
||||
disabled={false}
|
||||
iconType="pencil"
|
||||
name="edit"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Edit"
|
||||
id="xpack.triggersActionsUI.sections.alertDetails.editAlertButtonLabel"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
</React.Fragment>
|
||||
`);
|
||||
<React.Fragment>
|
||||
<EuiButtonEmpty
|
||||
data-test-subj="openEditAlertFlyoutButton"
|
||||
disabled={false}
|
||||
iconType="pencil"
|
||||
name="edit"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Edit"
|
||||
id="xpack.triggersActionsUI.sections.alertDetails.editAlertButtonLabel"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
</React.Fragment>
|
||||
`);
|
||||
});
|
||||
|
||||
it('should not render an edit button when alert editable but actions arent', () => {
|
||||
const { hasExecuteActionsCapability } = jest.requireMock('../../../lib/capabilities');
|
||||
hasExecuteActionsCapability.mockReturnValue(false);
|
||||
hasExecuteActionsCapability.mockReturnValueOnce(false);
|
||||
const alert = mockAlert({
|
||||
enabled: true,
|
||||
muteAll: false,
|
||||
|
@ -902,20 +671,6 @@ describe('edit button', () => {
|
|||
},
|
||||
],
|
||||
});
|
||||
|
||||
const alertType: AlertType = {
|
||||
id: '.noop',
|
||||
name: 'No Op',
|
||||
actionGroups: [{ id: 'default', name: 'Default' }],
|
||||
recoveryActionGroup,
|
||||
actionVariables: { context: [], state: [], params: [] },
|
||||
defaultActionGroupId: 'default',
|
||||
producer: 'alerting',
|
||||
authorizedConsumers,
|
||||
minimumLicenseRequired: 'basic',
|
||||
enabledInLicense: true,
|
||||
};
|
||||
|
||||
expect(
|
||||
shallow(
|
||||
<AlertDetails
|
||||
|
@ -934,26 +689,12 @@ describe('edit button', () => {
|
|||
|
||||
it('should render an edit button when alert editable but actions arent when there are no actions on the alert', async () => {
|
||||
const { hasExecuteActionsCapability } = jest.requireMock('../../../lib/capabilities');
|
||||
hasExecuteActionsCapability.mockReturnValue(false);
|
||||
hasExecuteActionsCapability.mockReturnValueOnce(false);
|
||||
const alert = mockAlert({
|
||||
enabled: true,
|
||||
muteAll: false,
|
||||
actions: [],
|
||||
});
|
||||
|
||||
const alertType: AlertType = {
|
||||
id: '.noop',
|
||||
name: 'No Op',
|
||||
actionGroups: [{ id: 'default', name: 'Default' }],
|
||||
recoveryActionGroup,
|
||||
actionVariables: { context: [], state: [], params: [] },
|
||||
defaultActionGroupId: 'default',
|
||||
producer: 'alerting',
|
||||
authorizedConsumers,
|
||||
minimumLicenseRequired: 'basic',
|
||||
enabledInLicense: true,
|
||||
};
|
||||
|
||||
const pageHeaderProps = shallow(
|
||||
<AlertDetails
|
||||
alert={alert}
|
||||
|
@ -966,41 +707,221 @@ describe('edit button', () => {
|
|||
.props() as EuiPageHeaderProps;
|
||||
const rightSideItems = pageHeaderProps.rightSideItems;
|
||||
expect(!!rightSideItems && rightSideItems[2]!).toMatchInlineSnapshot(`
|
||||
<React.Fragment>
|
||||
<EuiButtonEmpty
|
||||
data-test-subj="openEditAlertFlyoutButton"
|
||||
disabled={false}
|
||||
iconType="pencil"
|
||||
name="edit"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Edit"
|
||||
id="xpack.triggersActionsUI.sections.alertDetails.editAlertButtonLabel"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
</React.Fragment>
|
||||
`);
|
||||
<React.Fragment>
|
||||
<EuiButtonEmpty
|
||||
data-test-subj="openEditAlertFlyoutButton"
|
||||
disabled={false}
|
||||
iconType="pencil"
|
||||
name="edit"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Edit"
|
||||
id="xpack.triggersActionsUI.sections.alertDetails.editAlertButtonLabel"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
</React.Fragment>
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('broken connector indicator', () => {
|
||||
const actionTypes: ActionType[] = [
|
||||
{
|
||||
id: '.server-log',
|
||||
name: 'Server log',
|
||||
enabled: true,
|
||||
enabledInConfig: true,
|
||||
enabledInLicense: true,
|
||||
minimumLicenseRequired: 'basic',
|
||||
},
|
||||
];
|
||||
ruleTypeRegistry.has.mockReturnValue(true);
|
||||
const alertTypeR: AlertTypeModel = {
|
||||
id: 'my-alert-type',
|
||||
iconClass: 'test',
|
||||
description: 'Alert when testing',
|
||||
documentationUrl: 'https://localhost.local/docs',
|
||||
validate: () => {
|
||||
return { errors: {} };
|
||||
},
|
||||
alertParamsExpression: jest.fn(),
|
||||
requiresAppContext: false,
|
||||
};
|
||||
ruleTypeRegistry.get.mockReturnValue(alertTypeR);
|
||||
useKibanaMock().services.ruleTypeRegistry = ruleTypeRegistry;
|
||||
const { loadAllActions } = jest.requireMock('../../../lib/action_connector_api');
|
||||
loadAllActions.mockResolvedValue([
|
||||
{
|
||||
secrets: {},
|
||||
isMissingSecrets: false,
|
||||
id: 'connector-id-1',
|
||||
actionTypeId: '.server-log',
|
||||
name: 'Test connector',
|
||||
config: {},
|
||||
isPreconfigured: false,
|
||||
},
|
||||
{
|
||||
secrets: {},
|
||||
isMissingSecrets: false,
|
||||
id: 'connector-id-2',
|
||||
actionTypeId: '.server-log',
|
||||
name: 'Test connector 2',
|
||||
config: {},
|
||||
isPreconfigured: false,
|
||||
},
|
||||
]);
|
||||
|
||||
it('should not render broken connector indicator or warning if all rule actions connectors exist', async () => {
|
||||
const alert = mockAlert({
|
||||
enabled: true,
|
||||
muteAll: false,
|
||||
actions: [
|
||||
{
|
||||
group: 'default',
|
||||
id: 'connector-id-1',
|
||||
params: {},
|
||||
actionTypeId: '.server-log',
|
||||
},
|
||||
{
|
||||
group: 'default',
|
||||
id: 'connector-id-2',
|
||||
params: {},
|
||||
actionTypeId: '.server-log',
|
||||
},
|
||||
],
|
||||
});
|
||||
const wrapper = mountWithIntl(
|
||||
<AlertDetails
|
||||
alert={alert}
|
||||
alertType={alertType}
|
||||
actionTypes={actionTypes}
|
||||
{...mockAlertApis}
|
||||
/>
|
||||
);
|
||||
await act(async () => {
|
||||
await nextTick();
|
||||
wrapper.update();
|
||||
});
|
||||
const brokenConnectorIndicator = wrapper
|
||||
.find('[data-test-subj="actionWithBrokenConnector"]')
|
||||
.first();
|
||||
const brokenConnectorWarningBanner = wrapper
|
||||
.find('[data-test-subj="actionWithBrokenConnectorWarningBanner"]')
|
||||
.first();
|
||||
expect(brokenConnectorIndicator.exists()).toBeFalsy();
|
||||
expect(brokenConnectorWarningBanner.exists()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should render broken connector indicator and warning if any rule actions connector does not exist', async () => {
|
||||
const alert = mockAlert({
|
||||
enabled: true,
|
||||
muteAll: false,
|
||||
actions: [
|
||||
{
|
||||
group: 'default',
|
||||
id: 'connector-id-1',
|
||||
params: {},
|
||||
actionTypeId: '.server-log',
|
||||
},
|
||||
{
|
||||
group: 'default',
|
||||
id: 'connector-id-2',
|
||||
params: {},
|
||||
actionTypeId: '.server-log',
|
||||
},
|
||||
{
|
||||
group: 'default',
|
||||
id: 'connector-id-doesnt-exist',
|
||||
params: {},
|
||||
actionTypeId: '.server-log',
|
||||
},
|
||||
],
|
||||
});
|
||||
const wrapper = mountWithIntl(
|
||||
<AlertDetails
|
||||
alert={alert}
|
||||
alertType={alertType}
|
||||
actionTypes={actionTypes}
|
||||
{...mockAlertApis}
|
||||
/>
|
||||
);
|
||||
await act(async () => {
|
||||
await nextTick();
|
||||
wrapper.update();
|
||||
});
|
||||
const brokenConnectorIndicator = wrapper
|
||||
.find('[data-test-subj="actionWithBrokenConnector"]')
|
||||
.first();
|
||||
const brokenConnectorWarningBanner = wrapper
|
||||
.find('[data-test-subj="actionWithBrokenConnectorWarningBanner"]')
|
||||
.first();
|
||||
const brokenConnectorWarningBannerAction = wrapper
|
||||
.find('[data-test-subj="actionWithBrokenConnectorWarningBannerEdit"]')
|
||||
.first();
|
||||
expect(brokenConnectorIndicator.exists()).toBeTruthy();
|
||||
expect(brokenConnectorWarningBanner.exists()).toBeTruthy();
|
||||
expect(brokenConnectorWarningBannerAction.exists()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should render broken connector indicator and warning with no edit button if any rule actions connector does not exist and user has no edit access', async () => {
|
||||
const alert = mockAlert({
|
||||
enabled: true,
|
||||
muteAll: false,
|
||||
actions: [
|
||||
{
|
||||
group: 'default',
|
||||
id: 'connector-id-1',
|
||||
params: {},
|
||||
actionTypeId: '.server-log',
|
||||
},
|
||||
{
|
||||
group: 'default',
|
||||
id: 'connector-id-2',
|
||||
params: {},
|
||||
actionTypeId: '.server-log',
|
||||
},
|
||||
{
|
||||
group: 'default',
|
||||
id: 'connector-id-doesnt-exist',
|
||||
params: {},
|
||||
actionTypeId: '.server-log',
|
||||
},
|
||||
],
|
||||
});
|
||||
const { hasExecuteActionsCapability } = jest.requireMock('../../../lib/capabilities');
|
||||
hasExecuteActionsCapability.mockReturnValue(false);
|
||||
const wrapper = mountWithIntl(
|
||||
<AlertDetails
|
||||
alert={alert}
|
||||
alertType={alertType}
|
||||
actionTypes={actionTypes}
|
||||
{...mockAlertApis}
|
||||
/>
|
||||
);
|
||||
await act(async () => {
|
||||
await nextTick();
|
||||
wrapper.update();
|
||||
});
|
||||
const brokenConnectorIndicator = wrapper
|
||||
.find('[data-test-subj="actionWithBrokenConnector"]')
|
||||
.first();
|
||||
const brokenConnectorWarningBanner = wrapper
|
||||
.find('[data-test-subj="actionWithBrokenConnectorWarningBanner"]')
|
||||
.first();
|
||||
const brokenConnectorWarningBannerAction = wrapper
|
||||
.find('[data-test-subj="actionWithBrokenConnectorWarningBannerEdit"]')
|
||||
.first();
|
||||
expect(brokenConnectorIndicator.exists()).toBeTruthy();
|
||||
expect(brokenConnectorWarningBanner.exists()).toBeTruthy();
|
||||
expect(brokenConnectorWarningBannerAction.exists()).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('refresh button', () => {
|
||||
it('should call requestRefresh when clicked', () => {
|
||||
it('should call requestRefresh when clicked', async () => {
|
||||
const alert = mockAlert();
|
||||
const alertType: AlertType = {
|
||||
id: '.noop',
|
||||
name: 'No Op',
|
||||
actionGroups: [{ id: 'default', name: 'Default' }],
|
||||
recoveryActionGroup,
|
||||
actionVariables: { context: [], state: [], params: [] },
|
||||
defaultActionGroupId: 'default',
|
||||
minimumLicenseRequired: 'basic',
|
||||
producer: ALERTS_FEATURE_ID,
|
||||
authorizedConsumers,
|
||||
enabledInLicense: true,
|
||||
};
|
||||
|
||||
const requestRefresh = jest.fn();
|
||||
const wrapper = mountWithIntl(
|
||||
<AlertDetails
|
||||
|
@ -1012,6 +933,10 @@ describe('refresh button', () => {
|
|||
/>
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
await nextTick();
|
||||
wrapper.update();
|
||||
});
|
||||
const refreshButton = wrapper.find('[data-test-subj="refreshAlertsButton"]').first();
|
||||
expect(refreshButton.exists()).toBeTruthy();
|
||||
|
||||
|
|
|
@ -22,13 +22,16 @@ import {
|
|||
EuiButtonEmpty,
|
||||
EuiButton,
|
||||
EuiLoadingSpinner,
|
||||
EuiIconTip,
|
||||
EuiEmptyPrompt,
|
||||
EuiPageTemplate,
|
||||
} from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { AlertExecutionStatusErrorReasons } from '../../../../../../alerting/common';
|
||||
import { hasAllPrivilege, hasExecuteActionsCapability } from '../../../lib/capabilities';
|
||||
import { getAlertingSectionBreadcrumb, getAlertDetailsBreadcrumb } from '../../../lib/breadcrumb';
|
||||
import { getCurrentDocTitle } from '../../../lib/doc_title';
|
||||
import { Alert, AlertType, ActionType } from '../../../../types';
|
||||
import { Alert, AlertType, ActionType, ActionConnector } from '../../../../types';
|
||||
import {
|
||||
ComponentOpts as BulkOperationsComponentOpts,
|
||||
withBulkAlertOperations,
|
||||
|
@ -40,6 +43,7 @@ import { routeToRuleDetails } from '../../../constants';
|
|||
import { alertsErrorReasonTranslationsMapping } from '../../alerts_list/translations';
|
||||
import { useKibana } from '../../../../common/lib/kibana';
|
||||
import { alertReducer } from '../../alert_form/alert_reducer';
|
||||
import { loadAllActions as loadConnectors } from '../../../lib/action_connector_api';
|
||||
|
||||
export type AlertDetailsProps = {
|
||||
alert: Alert;
|
||||
|
@ -72,6 +76,9 @@ export const AlertDetails: React.FunctionComponent<AlertDetailsProps> = ({
|
|||
dispatch({ command: { type: 'setAlert' }, payload: { key: 'alert', value } });
|
||||
};
|
||||
|
||||
const [hasActionsWithBrokenConnector, setHasActionsWithBrokenConnector] =
|
||||
useState<boolean>(false);
|
||||
|
||||
// Set breadcrumb and page title
|
||||
useEffect(() => {
|
||||
setBreadcrumbs([
|
||||
|
@ -82,6 +89,28 @@ export const AlertDetails: React.FunctionComponent<AlertDetailsProps> = ({
|
|||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
// Determine if any attached action has an issue with its connector
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
let loadedConnectors: ActionConnector[] = [];
|
||||
try {
|
||||
loadedConnectors = await loadConnectors({ http });
|
||||
} catch (err) {
|
||||
loadedConnectors = [];
|
||||
}
|
||||
|
||||
if (loadedConnectors.length > 0) {
|
||||
const hasActionWithBrokenConnector = alert.actions.some(
|
||||
(action) => !loadedConnectors.find((connector) => connector.id === action.id)
|
||||
);
|
||||
if (setHasActionsWithBrokenConnector) {
|
||||
setHasActionsWithBrokenConnector(hasActionWithBrokenConnector);
|
||||
}
|
||||
}
|
||||
})();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const canExecuteActions = hasExecuteActionsCapability(capabilities);
|
||||
const canSaveAlert =
|
||||
hasAllPrivilege(alert, alertType) &&
|
||||
|
@ -197,13 +226,27 @@ export const AlertDetails: React.FunctionComponent<AlertDetailsProps> = ({
|
|||
{uniqueActions && uniqueActions.length ? (
|
||||
<>
|
||||
<EuiText size="s">
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.triggersActionsUI.sections.alertsList.alertsListTable.columns.actionsTex"
|
||||
defaultMessage="Actions"
|
||||
<FormattedMessage
|
||||
id="xpack.triggersActionsUI.sections.alertsList.alertsListTable.columns.actionsTex"
|
||||
defaultMessage="Actions"
|
||||
/>{' '}
|
||||
{hasActionsWithBrokenConnector && (
|
||||
<EuiIconTip
|
||||
data-test-subj="actionWithBrokenConnector"
|
||||
type="alert"
|
||||
color="danger"
|
||||
content={i18n.translate(
|
||||
'xpack.triggersActionsUI.sections.alertsList.alertsListTable.columns.actionsWarningTooltip',
|
||||
{
|
||||
defaultMessage:
|
||||
'Unable to load one of the connectors associated with this rule. Edit the rule to select a new connector.',
|
||||
}
|
||||
)}
|
||||
position="right"
|
||||
/>
|
||||
</p>
|
||||
)}
|
||||
</EuiText>
|
||||
|
||||
<EuiSpacer size="xs" />
|
||||
<EuiFlexGroup wrap gutterSize="s">
|
||||
{uniqueActions.map((action, index) => (
|
||||
|
@ -358,6 +401,42 @@ export const AlertDetails: React.FunctionComponent<AlertDetailsProps> = ({
|
|||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
) : null}
|
||||
{hasActionsWithBrokenConnector && (
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiCallOut
|
||||
color="warning"
|
||||
data-test-subj="actionWithBrokenConnectorWarningBanner"
|
||||
size="s"
|
||||
title={i18n.translate(
|
||||
'xpack.triggersActionsUI.sections.alertDetails.actionWithBrokenConnectorWarningBannerTitle',
|
||||
{
|
||||
defaultMessage:
|
||||
'There is an issue with one of the connectors associated with this rule.',
|
||||
}
|
||||
)}
|
||||
>
|
||||
{hasEditButton && (
|
||||
<EuiFlexGroup gutterSize="s" wrap={true}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
data-test-subj="actionWithBrokenConnectorWarningBannerEdit"
|
||||
color="warning"
|
||||
onClick={() => setEditFlyoutVisibility(true)}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.triggersActionsUI.sections.alertDetails.actionWithBrokenConnectorWarningBannerEditText"
|
||||
defaultMessage="Edit rule"
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
)}
|
||||
</EuiCallOut>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
)}
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem>
|
||||
{alert.enabled ? (
|
||||
|
@ -370,23 +449,46 @@ export const AlertDetails: React.FunctionComponent<AlertDetailsProps> = ({
|
|||
) : (
|
||||
<>
|
||||
<EuiSpacer />
|
||||
<EuiCallOut
|
||||
title={i18n.translate(
|
||||
'xpack.triggersActionsUI.sections.alertDetails.alerts.disabledRuleTitle',
|
||||
{
|
||||
defaultMessage: 'Disabled Rule',
|
||||
<EuiPageTemplate template="empty">
|
||||
<EuiEmptyPrompt
|
||||
data-test-subj="disabledEmptyPrompt"
|
||||
title={
|
||||
<h2>
|
||||
<FormattedMessage
|
||||
id="xpack.triggersActionsUI.sections.alertDetails.alerts.disabledRuleTitle"
|
||||
defaultMessage="Disabled Rule"
|
||||
/>
|
||||
</h2>
|
||||
}
|
||||
)}
|
||||
color="warning"
|
||||
iconType="help"
|
||||
>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.triggersActionsUI.sections.alertDetails.alertInstances.disabledRule"
|
||||
defaultMessage="This rule is disabled and cannot be displayed. Toggle Disable ↑ to activate it."
|
||||
/>
|
||||
</p>
|
||||
</EuiCallOut>
|
||||
body={
|
||||
<>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.triggersActionsUI.sections.alertDetails.alertInstances.disabledRule"
|
||||
defaultMessage="This rule is disabled and cannot be displayed."
|
||||
/>
|
||||
</p>
|
||||
</>
|
||||
}
|
||||
actions={[
|
||||
<EuiButton
|
||||
data-test-subj="disabledEmptyPromptAction"
|
||||
color="primary"
|
||||
fill
|
||||
disabled={isEnabledUpdating}
|
||||
onClick={async () => {
|
||||
setIsEnabledUpdating(true);
|
||||
setIsEnabled(true);
|
||||
await enableAlert(alert);
|
||||
requestRefresh();
|
||||
setIsEnabledUpdating(false);
|
||||
}}
|
||||
>
|
||||
Enable
|
||||
</EuiButton>,
|
||||
]}
|
||||
/>
|
||||
</EuiPageTemplate>
|
||||
</>
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue