mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[Actionable Observability][BUG] Fix connectors doesn't appear in the Rule Details page after creating first connector (#133737) (#138484)
* Move rule actions to the hook which is right call
* Update naming
* Fix when a connector is deleted from connector page in Stack Management
* Fix tests
* Update tests and mock loadAllActions API
* Remove unused import
* Call the connectors API only when the rule has actions
(cherry picked from commit 44026a5e01
)
Co-authored-by: Faisal Kanout <faisal.kanout@elastic.co>
This commit is contained in:
parent
cfad70beee
commit
10a06de21d
5 changed files with 109 additions and 83 deletions
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { useEffect, useState, useCallback } from 'react';
|
||||
import { ActionConnector, loadAllActions } from '@kbn/triggers-actions-ui-plugin/public';
|
||||
import { intersectionBy } from 'lodash';
|
||||
import { FetchRuleActionConnectorsProps } from '../pages/rule_details/types';
|
||||
import { ACTIONS_LOAD_ERROR } from '../pages/rule_details/translations';
|
||||
|
||||
interface FetchActionConnectors {
|
||||
isLoadingActionConnectors: boolean;
|
||||
actionConnectors: Array<ActionConnector<Record<string, unknown>>>;
|
||||
errorActionConnectors?: string;
|
||||
}
|
||||
|
||||
export function useFetchRuleActionConnectors({
|
||||
http,
|
||||
ruleActions,
|
||||
}: FetchRuleActionConnectorsProps) {
|
||||
const [actionConnectors, setActionConnector] = useState<FetchActionConnectors>({
|
||||
isLoadingActionConnectors: true,
|
||||
actionConnectors: [] as Array<ActionConnector<Record<string, unknown>>>,
|
||||
errorActionConnectors: undefined,
|
||||
});
|
||||
|
||||
const fetchRuleActionConnectors = useCallback(async () => {
|
||||
try {
|
||||
if (!ruleActions || ruleActions.length <= 0) {
|
||||
setActionConnector((oldState: FetchActionConnectors) => ({
|
||||
...oldState,
|
||||
isLoadingActionConnectors: false,
|
||||
actionConnectors: [],
|
||||
}));
|
||||
return;
|
||||
}
|
||||
const allActions = await loadAllActions({
|
||||
http,
|
||||
});
|
||||
const actions = intersectionBy(allActions, ruleActions, 'actionTypeId');
|
||||
setActionConnector((oldState: FetchActionConnectors) => ({
|
||||
...oldState,
|
||||
isLoadingActionConnectors: false,
|
||||
actionConnectors: actions,
|
||||
}));
|
||||
} catch (error) {
|
||||
setActionConnector((oldState: FetchActionConnectors) => ({
|
||||
...oldState,
|
||||
isLoadingActionConnectors: false,
|
||||
errorActionConnectors: ACTIONS_LOAD_ERROR(
|
||||
error instanceof Error ? error.message : typeof error === 'string' ? error : ''
|
||||
),
|
||||
}));
|
||||
}
|
||||
}, [http, ruleActions]);
|
||||
useEffect(() => {
|
||||
fetchRuleActionConnectors();
|
||||
}, [fetchRuleActionConnectors]);
|
||||
|
||||
return {
|
||||
...actionConnectors,
|
||||
reloadRuleActionConnectors: fetchRuleActionConnectors,
|
||||
};
|
||||
}
|
|
@ -1,51 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { useEffect, useState, useCallback } from 'react';
|
||||
import { ActionConnector, loadAllActions } from '@kbn/triggers-actions-ui-plugin/public';
|
||||
import { FetchRuleActionsProps } from '../pages/rule_details/types';
|
||||
import { ACTIONS_LOAD_ERROR } from '../pages/rule_details/translations';
|
||||
|
||||
interface FetchActions {
|
||||
isLoadingActions: boolean;
|
||||
allActions: Array<ActionConnector<Record<string, unknown>>>;
|
||||
errorActions: string | undefined;
|
||||
}
|
||||
|
||||
export function useFetchRuleActions({ http }: FetchRuleActionsProps) {
|
||||
const [ruleActions, setRuleActions] = useState<FetchActions>({
|
||||
isLoadingActions: true,
|
||||
allActions: [] as Array<ActionConnector<Record<string, unknown>>>,
|
||||
errorActions: undefined,
|
||||
});
|
||||
|
||||
const fetchRuleActions = useCallback(async () => {
|
||||
try {
|
||||
const response = await loadAllActions({
|
||||
http,
|
||||
});
|
||||
setRuleActions((oldState: FetchActions) => ({
|
||||
...oldState,
|
||||
isLoadingActions: false,
|
||||
allActions: response,
|
||||
}));
|
||||
} catch (error) {
|
||||
setRuleActions((oldState: FetchActions) => ({
|
||||
...oldState,
|
||||
isLoadingActions: false,
|
||||
errorActions: ACTIONS_LOAD_ERROR(
|
||||
error instanceof Error ? error.message : typeof error === 'string' ? error : ''
|
||||
),
|
||||
}));
|
||||
}
|
||||
}, [http]);
|
||||
useEffect(() => {
|
||||
fetchRuleActions();
|
||||
}, [fetchRuleActions]);
|
||||
|
||||
return { ...ruleActions, reloadRuleActions: fetchRuleActions };
|
||||
}
|
|
@ -5,7 +5,9 @@
|
|||
* 2.0.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { ReactWrapper, mount } from 'enzyme';
|
||||
import { mount } from 'enzyme';
|
||||
import { nextTick } from '@kbn/test-jest-helpers';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { Actions } from './actions';
|
||||
import { observabilityPublicPluginsStartMock } from '../../../observability_public_plugins_start.mock';
|
||||
import { kibanaStartMock } from '../../../utils/kibana_react.mock';
|
||||
|
@ -17,14 +19,11 @@ jest.mock('../../../utils/kibana_react', () => ({
|
|||
useKibana: jest.fn(() => mockUseKibanaReturnValue),
|
||||
}));
|
||||
|
||||
jest.mock('../../../hooks/use_fetch_rule_actions', () => ({
|
||||
useFetchRuleActions: jest.fn(),
|
||||
jest.mock('@kbn/triggers-actions-ui-plugin/public/application/lib/action_connector_api', () => ({
|
||||
loadAllActions: jest.fn(),
|
||||
}));
|
||||
|
||||
const { useFetchRuleActions } = jest.requireMock('../../../hooks/use_fetch_rule_actions');
|
||||
|
||||
describe('Actions', () => {
|
||||
let wrapper: ReactWrapper<any>;
|
||||
async function setup() {
|
||||
const ruleActions = [
|
||||
{
|
||||
|
@ -38,26 +37,28 @@ describe('Actions', () => {
|
|||
actionTypeId: '.slack',
|
||||
},
|
||||
];
|
||||
const allActions = [
|
||||
const { loadAllActions } = jest.requireMock(
|
||||
'@kbn/triggers-actions-ui-plugin/public/application/lib/action_connector_api'
|
||||
);
|
||||
loadAllActions.mockResolvedValueOnce([
|
||||
{
|
||||
id: 1,
|
||||
name: 'Server log',
|
||||
id: 'a0d2f6c0-e682-11ec-843b-213c67313f8c',
|
||||
name: 'Email',
|
||||
config: {},
|
||||
actionTypeId: '.email',
|
||||
},
|
||||
{
|
||||
id: 'f57cabc0-e660-11ec-8241-7deb55b17f15',
|
||||
name: 'logs',
|
||||
config: {},
|
||||
actionTypeId: '.server-log',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
id: '05b7ab30-e683-11ec-843b-213c67313f8c',
|
||||
name: 'Slack',
|
||||
actionTypeId: '.slack',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 'Email',
|
||||
actionTypeId: '.email',
|
||||
},
|
||||
];
|
||||
useFetchRuleActions.mockReturnValue({
|
||||
allActions,
|
||||
});
|
||||
]);
|
||||
|
||||
const actionTypeRegistryMock =
|
||||
observabilityPublicPluginsStartMock.createStart().triggersActionsUi.actionTypeRegistry;
|
||||
|
@ -67,13 +68,18 @@ describe('Actions', () => {
|
|||
{ id: '.email', iconClass: 'email' },
|
||||
{ id: '.index', iconClass: 'indexOpen' },
|
||||
]);
|
||||
wrapper = mount(
|
||||
const wrapper = mount(
|
||||
<Actions ruleActions={ruleActions} actionTypeRegistry={actionTypeRegistryMock} />
|
||||
);
|
||||
await act(async () => {
|
||||
await nextTick();
|
||||
wrapper.update();
|
||||
});
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
it("renders action connector icons for user's selected rule actions", async () => {
|
||||
await setup();
|
||||
const wrapper = await setup();
|
||||
wrapper.debug();
|
||||
expect(wrapper.find('[data-euiicon-type]').length).toBe(2);
|
||||
expect(wrapper.find('[data-euiicon-type="logsApp"]').length).toBe(1);
|
||||
|
|
|
@ -14,11 +14,10 @@ import {
|
|||
IconType,
|
||||
EuiLoadingSpinner,
|
||||
} from '@elastic/eui';
|
||||
import { intersectionBy } from 'lodash';
|
||||
import { suspendedComponentWithProps } from '@kbn/triggers-actions-ui-plugin/public';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { ActionsProps } from '../types';
|
||||
import { useFetchRuleActions } from '../../../hooks/use_fetch_rule_actions';
|
||||
import { useFetchRuleActionConnectors } from '../../../hooks/use_fetch_rule_action_connectors';
|
||||
import { useKibana } from '../../../utils/kibana_react';
|
||||
|
||||
export function Actions({ ruleActions, actionTypeRegistry }: ActionsProps) {
|
||||
|
@ -26,13 +25,18 @@ export function Actions({ ruleActions, actionTypeRegistry }: ActionsProps) {
|
|||
http,
|
||||
notifications: { toasts },
|
||||
} = useKibana().services;
|
||||
const { isLoadingActions, allActions, errorActions } = useFetchRuleActions({ http });
|
||||
const { isLoadingActionConnectors, actionConnectors, errorActionConnectors } =
|
||||
useFetchRuleActionConnectors({
|
||||
http,
|
||||
ruleActions,
|
||||
});
|
||||
useEffect(() => {
|
||||
if (errorActions) {
|
||||
toasts.addDanger({ title: errorActions });
|
||||
if (errorActionConnectors) {
|
||||
toasts.addDanger({ title: errorActionConnectors });
|
||||
}
|
||||
}, [errorActions, toasts]);
|
||||
if (ruleActions && ruleActions.length <= 0)
|
||||
}, [errorActionConnectors, toasts]);
|
||||
|
||||
if (!actionConnectors || actionConnectors.length <= 0)
|
||||
return (
|
||||
<EuiFlexItem>
|
||||
<EuiText size="s">
|
||||
|
@ -49,11 +53,10 @@ export function Actions({ ruleActions, actionTypeRegistry }: ActionsProps) {
|
|||
? actionGroup?.iconClass
|
||||
: suspendedComponentWithProps(actionGroup?.iconClass as React.ComponentType);
|
||||
}
|
||||
const actions = intersectionBy(allActions, ruleActions, 'actionTypeId');
|
||||
if (isLoadingActions) return <EuiLoadingSpinner size="s" />;
|
||||
if (isLoadingActionConnectors) return <EuiLoadingSpinner size="s" />;
|
||||
return (
|
||||
<EuiFlexGroup direction="column">
|
||||
{actions.map(({ actionTypeId, name }) => (
|
||||
{actionConnectors.map(({ actionTypeId, name }) => (
|
||||
<React.Fragment key={actionTypeId}>
|
||||
<EuiFlexGroup alignItems="center" gutterSize="s" component="span">
|
||||
<EuiFlexItem grow={false}>
|
||||
|
|
|
@ -36,8 +36,9 @@ export interface FetchRuleSummaryProps {
|
|||
ruleId: string;
|
||||
http: HttpSetup;
|
||||
}
|
||||
export interface FetchRuleActionsProps {
|
||||
export interface FetchRuleActionConnectorsProps {
|
||||
http: HttpSetup;
|
||||
ruleActions: any[];
|
||||
}
|
||||
|
||||
export interface FetchRuleExecutionLogProps {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue