mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[ResponseOps][Stack Connectors] Opsgenie connector UI (#142411)
* Starting opsgenie backend * Adding more integration tests * Updating readme * Starting ui * Adding hash and alias * Fixing tests * Switch to platinum for now * Adding server side translations * Fixing merge issues * Fixing file location error * Working ui * Default alias is working * Almost working validation fails sometimes * Adding end to end tests * Adding more tests * Adding note and description fields * Removing todo * Fixing test errors * Addressing feedback * Trying to fix test flakiness
This commit is contained in:
parent
66041ca2c2
commit
e71a522567
35 changed files with 1586 additions and 103 deletions
|
@ -13,3 +13,5 @@ export enum AdditionalEmailServices {
|
|||
}
|
||||
|
||||
export const INTERNAL_BASE_STACK_CONNECTORS_API_PATH = '/internal/stack_connectors';
|
||||
|
||||
export { OpsgenieSubActions, OpsgenieConnectorTypeId } from './opsgenie';
|
||||
|
|
13
x-pack/plugins/stack_connectors/common/opsgenie.ts
Normal file
13
x-pack/plugins/stack_connectors/common/opsgenie.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export enum OpsgenieSubActions {
|
||||
CreateAlert = 'createAlert',
|
||||
CloseAlert = 'closeAlert',
|
||||
}
|
||||
|
||||
export const OpsgenieConnectorTypeId = '.opsgenie';
|
|
@ -16,6 +16,7 @@ import {
|
|||
getSlackConnectorType,
|
||||
getTeamsConnectorType,
|
||||
getWebhookConnectorType,
|
||||
getOpsgenieConnectorType,
|
||||
getXmattersConnectorType,
|
||||
} from './stack';
|
||||
|
||||
|
@ -56,5 +57,6 @@ export function registerConnectorTypes({
|
|||
connectorTypeRegistry.register(getServiceNowSIRConnectorType());
|
||||
connectorTypeRegistry.register(getJiraConnectorType());
|
||||
connectorTypeRegistry.register(getResilientConnectorType());
|
||||
connectorTypeRegistry.register(getOpsgenieConnectorType());
|
||||
connectorTypeRegistry.register(getTeamsConnectorType());
|
||||
}
|
||||
|
|
|
@ -13,4 +13,5 @@ export { getServiceNowITOMConnectorType } from './servicenow_itom';
|
|||
export { getSlackConnectorType } from './slack';
|
||||
export { getTeamsConnectorType } from './teams';
|
||||
export { getWebhookConnectorType } from './webhook';
|
||||
export { getOpsgenieConnectorType } from './opsgenie';
|
||||
export { getXmattersConnectorType } from './xmatters';
|
||||
|
|
|
@ -0,0 +1,115 @@
|
|||
/*
|
||||
* 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 React from 'react';
|
||||
import OpsgenieConnectorFields from './connector';
|
||||
import { ConnectorFormTestProvider } from '../../lib/test_utils';
|
||||
import { act, screen, render } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
jest.mock('@kbn/triggers-actions-ui-plugin/public/common/lib/kibana');
|
||||
|
||||
const actionConnector = {
|
||||
actionTypeId: '.opsgenie',
|
||||
name: 'opsgenie',
|
||||
config: {
|
||||
apiUrl: 'https://test.com',
|
||||
},
|
||||
secrets: {
|
||||
apiKey: 'secret',
|
||||
},
|
||||
isDeprecated: false,
|
||||
};
|
||||
|
||||
describe('OpsgenieConnectorFields renders', () => {
|
||||
const onSubmit = jest.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('renders the fields', async () => {
|
||||
render(
|
||||
<ConnectorFormTestProvider connector={actionConnector} onSubmit={onSubmit}>
|
||||
<OpsgenieConnectorFields
|
||||
readOnly={false}
|
||||
isEdit={false}
|
||||
registerPreSubmitValidator={() => {}}
|
||||
/>
|
||||
</ConnectorFormTestProvider>
|
||||
);
|
||||
|
||||
expect(screen.getByTestId('config.apiUrl-input')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('secrets.apiKey-input')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
describe('Validation', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
const tests: Array<[string, string]> = [
|
||||
['config.apiUrl-input', 'not-valid'],
|
||||
['secrets.apiKey-input', ''],
|
||||
];
|
||||
|
||||
it('connector validation succeeds when connector config is valid', async () => {
|
||||
const { getByTestId } = render(
|
||||
<ConnectorFormTestProvider connector={actionConnector} onSubmit={onSubmit}>
|
||||
<OpsgenieConnectorFields
|
||||
readOnly={false}
|
||||
isEdit={false}
|
||||
registerPreSubmitValidator={() => {}}
|
||||
/>
|
||||
</ConnectorFormTestProvider>
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
userEvent.click(getByTestId('form-test-provide-submit'));
|
||||
});
|
||||
|
||||
expect(onSubmit).toBeCalledWith({
|
||||
data: {
|
||||
actionTypeId: '.opsgenie',
|
||||
name: 'opsgenie',
|
||||
config: {
|
||||
apiUrl: 'https://test.com',
|
||||
},
|
||||
secrets: {
|
||||
apiKey: 'secret',
|
||||
},
|
||||
isDeprecated: false,
|
||||
},
|
||||
isValid: true,
|
||||
});
|
||||
});
|
||||
|
||||
it.each(tests)('validates correctly %p', async (field, value) => {
|
||||
const res = render(
|
||||
<ConnectorFormTestProvider connector={actionConnector} onSubmit={onSubmit}>
|
||||
<OpsgenieConnectorFields
|
||||
readOnly={false}
|
||||
isEdit={false}
|
||||
registerPreSubmitValidator={() => {}}
|
||||
/>
|
||||
</ConnectorFormTestProvider>
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
await userEvent.type(res.getByTestId(field), `{selectall}{backspace}${value}`, {
|
||||
delay: 10,
|
||||
});
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
userEvent.click(res.getByTestId('form-test-provide-submit'));
|
||||
});
|
||||
|
||||
expect(onSubmit).toHaveBeenCalledWith({ data: {}, isValid: false });
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* 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 React from 'react';
|
||||
import type {
|
||||
ActionConnectorFieldsProps,
|
||||
ConfigFieldSchema,
|
||||
SecretsFieldSchema,
|
||||
} from '@kbn/triggers-actions-ui-plugin/public';
|
||||
import { SimpleConnectorForm } from '@kbn/triggers-actions-ui-plugin/public';
|
||||
import * as i18n from './translations';
|
||||
|
||||
const configFormSchema: ConfigFieldSchema[] = [
|
||||
{ id: 'apiUrl', label: i18n.API_URL_LABEL, isUrlField: true },
|
||||
];
|
||||
|
||||
const secretsFormSchema: SecretsFieldSchema[] = [
|
||||
{ id: 'apiKey', label: i18n.API_KEY_LABEL, isPasswordField: true },
|
||||
];
|
||||
|
||||
const OpsgenieConnectorFields: React.FC<ActionConnectorFieldsProps> = ({ readOnly, isEdit }) => {
|
||||
return (
|
||||
<SimpleConnectorForm
|
||||
isEdit={isEdit}
|
||||
readOnly={readOnly}
|
||||
configFormSchema={configFormSchema}
|
||||
secretsFormSchema={secretsFormSchema}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export { OpsgenieConnectorFields as default };
|
|
@ -0,0 +1,10 @@
|
|||
/*
|
||||
* 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 { AlertProvidedActionVariables } from '@kbn/triggers-actions-ui-plugin/public';
|
||||
|
||||
export const DEFAULT_ALIAS = `{{${AlertProvidedActionVariables.ruleId}}}:{{${AlertProvidedActionVariables.alertId}}}`;
|
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export { getConnectorType as getOpsgenieConnectorType } from './model';
|
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* 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 { EuiIcon } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import { LogoProps } from '../../types';
|
||||
|
||||
const Logo = (props: LogoProps) => <EuiIcon type="casesApp" size="xl" />;
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export { Logo as default };
|
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
* 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 { TypeRegistry } from '@kbn/triggers-actions-ui-plugin/public/application/type_registry';
|
||||
import { registerConnectorTypes } from '../..';
|
||||
import type { ActionTypeModel as ConnectorTypeModel } from '@kbn/triggers-actions-ui-plugin/public/types';
|
||||
import { registrationServicesMock } from '../../../mocks';
|
||||
import { OpsgenieConnectorTypeId, OpsgenieSubActions } from '../../../../common';
|
||||
|
||||
let connectorTypeModel: ConnectorTypeModel;
|
||||
|
||||
beforeAll(() => {
|
||||
const connectorTypeRegistry = new TypeRegistry<ConnectorTypeModel>();
|
||||
registerConnectorTypes({ connectorTypeRegistry, services: registrationServicesMock });
|
||||
const getResult = connectorTypeRegistry.get(OpsgenieConnectorTypeId);
|
||||
if (getResult !== null) {
|
||||
connectorTypeModel = getResult;
|
||||
}
|
||||
});
|
||||
|
||||
describe('connectorTypeRegistry.get() works', () => {
|
||||
it('sets the id field in the connector type static data to the correct opsgenie value', () => {
|
||||
expect(connectorTypeModel.id).toEqual(OpsgenieConnectorTypeId);
|
||||
});
|
||||
});
|
||||
|
||||
describe('opsgenie action params validation', () => {
|
||||
it('results in no errors when the action params are valid for creating an alert', async () => {
|
||||
const actionParams = {
|
||||
subAction: OpsgenieSubActions.CreateAlert,
|
||||
subActionParams: {
|
||||
message: 'hello',
|
||||
},
|
||||
};
|
||||
|
||||
expect(await connectorTypeModel.validateParams(actionParams)).toEqual({
|
||||
errors: {
|
||||
'subActionParams.message': [],
|
||||
'subActionParams.alias': [],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('results in no errors when the action params are valid for closing an alert', async () => {
|
||||
const actionParams = {
|
||||
subAction: OpsgenieSubActions.CloseAlert,
|
||||
subActionParams: {
|
||||
alias: '123',
|
||||
},
|
||||
};
|
||||
|
||||
expect(await connectorTypeModel.validateParams(actionParams)).toEqual({
|
||||
errors: {
|
||||
'subActionParams.message': [],
|
||||
'subActionParams.alias': [],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('sets the message error when the message is missing for creating an alert', async () => {
|
||||
const actionParams = {
|
||||
subAction: OpsgenieSubActions.CreateAlert,
|
||||
subActionParams: {},
|
||||
};
|
||||
|
||||
expect(await connectorTypeModel.validateParams(actionParams)).toEqual({
|
||||
errors: {
|
||||
'subActionParams.message': ['Message is required.'],
|
||||
'subActionParams.alias': [],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('sets the alias error when the alias is missing for closing an alert', async () => {
|
||||
const actionParams = {
|
||||
subAction: OpsgenieSubActions.CloseAlert,
|
||||
subActionParams: {},
|
||||
};
|
||||
|
||||
expect(await connectorTypeModel.validateParams(actionParams)).toEqual({
|
||||
errors: {
|
||||
'subActionParams.message': [],
|
||||
'subActionParams.alias': ['Alias is required.'],
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* 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 { lazy } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
ActionTypeModel as ConnectorTypeModel,
|
||||
GenericValidationResult,
|
||||
} from '@kbn/triggers-actions-ui-plugin/public';
|
||||
import { RecursivePartial } from '@elastic/eui';
|
||||
import { OpsgenieSubActions } from '../../../../common';
|
||||
import type {
|
||||
OpsgenieActionConfig,
|
||||
OpsgenieActionParams,
|
||||
OpsgenieActionSecrets,
|
||||
} from '../../../../server/connector_types/stack';
|
||||
import { DEFAULT_ALIAS } from './constants';
|
||||
|
||||
const SELECT_MESSAGE = i18n.translate(
|
||||
'xpack.stackConnectors.components.opsgenie.selectMessageText',
|
||||
{
|
||||
defaultMessage: 'Create or close an alert in Opsgenie.',
|
||||
}
|
||||
);
|
||||
|
||||
const TITLE = i18n.translate('xpack.stackConnectors.components.opsgenie.connectorTypeTitle', {
|
||||
defaultMessage: 'Opsgenie',
|
||||
});
|
||||
|
||||
export const getConnectorType = (): ConnectorTypeModel<
|
||||
OpsgenieActionConfig,
|
||||
OpsgenieActionSecrets,
|
||||
OpsgenieActionParams
|
||||
> => {
|
||||
return {
|
||||
id: '.opsgenie',
|
||||
iconClass: lazy(() => import('./logo')),
|
||||
selectMessage: SELECT_MESSAGE,
|
||||
actionTypeTitle: TITLE,
|
||||
validateParams: async (
|
||||
actionParams: RecursivePartial<OpsgenieActionParams>
|
||||
): Promise<GenericValidationResult<unknown>> => {
|
||||
const translations = await import('./translations');
|
||||
const errors = {
|
||||
'subActionParams.message': new Array<string>(),
|
||||
'subActionParams.alias': new Array<string>(),
|
||||
};
|
||||
|
||||
const validationResult = {
|
||||
errors,
|
||||
};
|
||||
|
||||
if (
|
||||
actionParams.subAction === OpsgenieSubActions.CreateAlert &&
|
||||
!actionParams?.subActionParams?.message?.length
|
||||
) {
|
||||
errors['subActionParams.message'].push(translations.MESSAGE_IS_REQUIRED);
|
||||
}
|
||||
|
||||
if (
|
||||
actionParams.subAction === OpsgenieSubActions.CloseAlert &&
|
||||
!actionParams?.subActionParams?.alias?.length
|
||||
) {
|
||||
errors['subActionParams.alias'].push(translations.ALIAS_IS_REQUIRED);
|
||||
}
|
||||
|
||||
return validationResult;
|
||||
},
|
||||
actionConnectorFields: lazy(() => import('./connector')),
|
||||
actionParamsFields: lazy(() => import('./params')),
|
||||
defaultActionParams: {
|
||||
subAction: OpsgenieSubActions.CreateAlert,
|
||||
subActionParams: {
|
||||
alias: DEFAULT_ALIAS,
|
||||
},
|
||||
},
|
||||
defaultRecoveredActionParams: {
|
||||
subAction: OpsgenieSubActions.CloseAlert,
|
||||
subActionParams: {
|
||||
alias: DEFAULT_ALIAS,
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
|
@ -0,0 +1,247 @@
|
|||
/*
|
||||
* 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 React from 'react';
|
||||
import { act, screen, render, fireEvent } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import OpsgenieParamFields from './params';
|
||||
import { OpsgenieSubActions } from '../../../../common';
|
||||
import { OpsgenieActionParams } from '../../../../server/connector_types/stack';
|
||||
|
||||
describe('OpsgenieParamFields', () => {
|
||||
const editAction = jest.fn();
|
||||
const createAlertActionParams: OpsgenieActionParams = {
|
||||
subAction: OpsgenieSubActions.CreateAlert,
|
||||
subActionParams: { message: 'hello', alias: '123' },
|
||||
};
|
||||
|
||||
const closeAlertActionParams: OpsgenieActionParams = {
|
||||
subAction: OpsgenieSubActions.CloseAlert,
|
||||
subActionParams: { alias: '456' },
|
||||
};
|
||||
|
||||
const connector = {
|
||||
secrets: { apiKey: '123' },
|
||||
config: { apiUrl: 'http://test.com' },
|
||||
id: 'test',
|
||||
actionTypeId: '.test',
|
||||
name: 'Test',
|
||||
isPreconfigured: false,
|
||||
isDeprecated: false,
|
||||
};
|
||||
|
||||
const defaultCreateAlertProps = {
|
||||
actionParams: createAlertActionParams,
|
||||
errors: {
|
||||
'subActionParams.message': [],
|
||||
'subActionParams.alias': [],
|
||||
},
|
||||
editAction,
|
||||
index: 0,
|
||||
messageVariables: [],
|
||||
actionConnector: connector,
|
||||
};
|
||||
|
||||
const defaultCloseAlertProps = {
|
||||
actionParams: closeAlertActionParams,
|
||||
errors: {
|
||||
'subActionParams.message': [],
|
||||
'subActionParams.alias': [],
|
||||
},
|
||||
editAction,
|
||||
index: 0,
|
||||
messageVariables: [],
|
||||
actionConnector: connector,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('renders the create alert component', async () => {
|
||||
render(<OpsgenieParamFields {...defaultCreateAlertProps} />);
|
||||
|
||||
expect(screen.getByText('Message')).toBeInTheDocument();
|
||||
expect(screen.getByText('Alias')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('opsgenie-subActionSelect'));
|
||||
|
||||
expect(screen.getByDisplayValue('hello')).toBeInTheDocument();
|
||||
expect(screen.getByDisplayValue('123')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders the close alert component', async () => {
|
||||
render(<OpsgenieParamFields {...defaultCloseAlertProps} />);
|
||||
|
||||
expect(screen.queryByText('Message')).not.toBeInTheDocument();
|
||||
expect(screen.getByText('Alias')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('opsgenie-subActionSelect'));
|
||||
|
||||
expect(screen.queryByDisplayValue('hello')).not.toBeInTheDocument();
|
||||
expect(screen.queryByDisplayValue('123')).not.toBeInTheDocument();
|
||||
expect(screen.getByDisplayValue('456')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('calls editAction when the message field is changed', async () => {
|
||||
render(<OpsgenieParamFields {...defaultCreateAlertProps} />);
|
||||
|
||||
fireEvent.change(screen.getByDisplayValue('hello'), { target: { value: 'a new message' } });
|
||||
|
||||
expect(editAction).toBeCalledTimes(1);
|
||||
expect(editAction.mock.calls[0]).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
"subActionParams",
|
||||
Object {
|
||||
"alias": "123",
|
||||
"message": "a new message",
|
||||
},
|
||||
0,
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
it('calls editAction when the description field is changed', async () => {
|
||||
render(<OpsgenieParamFields {...defaultCreateAlertProps} />);
|
||||
|
||||
fireEvent.change(screen.getByTestId('descriptionTextArea'), {
|
||||
target: { value: 'a new description' },
|
||||
});
|
||||
|
||||
expect(editAction).toBeCalledTimes(1);
|
||||
expect(editAction.mock.calls[0]).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
"subActionParams",
|
||||
Object {
|
||||
"alias": "123",
|
||||
"description": "a new description",
|
||||
"message": "hello",
|
||||
},
|
||||
0,
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
it('calls editAction when the alias field is changed for closeAlert', async () => {
|
||||
render(<OpsgenieParamFields {...defaultCloseAlertProps} />);
|
||||
|
||||
fireEvent.change(screen.getByDisplayValue('456'), { target: { value: 'a new alias' } });
|
||||
|
||||
expect(editAction).toBeCalledTimes(1);
|
||||
expect(editAction.mock.calls[0]).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
"subActionParams",
|
||||
Object {
|
||||
"alias": "a new alias",
|
||||
},
|
||||
0,
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
it('does not render the create or close alert components if the subAction is undefined', async () => {
|
||||
render(<OpsgenieParamFields {...{ ...defaultCreateAlertProps, actionParams: {} }} />);
|
||||
|
||||
expect(screen.queryByTestId('opsgenie-alias-row')).not.toBeInTheDocument();
|
||||
expect(screen.queryByText('Message')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('preserves the previous alias value when switching between the create and close alert event actions', async () => {
|
||||
const { rerender } = render(<OpsgenieParamFields {...defaultCreateAlertProps} />);
|
||||
|
||||
expect(screen.getByDisplayValue('hello')).toBeInTheDocument();
|
||||
expect(screen.getByDisplayValue('123')).toBeInTheDocument();
|
||||
|
||||
fireEvent.change(screen.getByDisplayValue('123'), { target: { value: 'a new alias' } });
|
||||
expect(editAction).toBeCalledTimes(1);
|
||||
|
||||
rerender(
|
||||
<OpsgenieParamFields
|
||||
{...{
|
||||
...defaultCloseAlertProps,
|
||||
actionParams: {
|
||||
...defaultCloseAlertProps.actionParams,
|
||||
subActionParams: {
|
||||
alias: 'a new alias',
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(screen.queryByDisplayValue('hello')).not.toBeInTheDocument();
|
||||
|
||||
expect(editAction).toBeCalledTimes(2);
|
||||
|
||||
expect(editAction.mock.calls[1]).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
"subActionParams",
|
||||
Object {
|
||||
"alias": "a new alias",
|
||||
},
|
||||
0,
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
it('only preserves the previous alias value when switching between the create and close alert event actions', async () => {
|
||||
const { rerender } = render(<OpsgenieParamFields {...defaultCreateAlertProps} />);
|
||||
|
||||
expect(screen.getByDisplayValue('hello')).toBeInTheDocument();
|
||||
expect(screen.getByDisplayValue('123')).toBeInTheDocument();
|
||||
|
||||
fireEvent.change(screen.getByDisplayValue('123'), { target: { value: 'a new alias' } });
|
||||
expect(editAction).toBeCalledTimes(1);
|
||||
|
||||
rerender(
|
||||
<OpsgenieParamFields
|
||||
{...{
|
||||
...defaultCloseAlertProps,
|
||||
actionParams: {
|
||||
...defaultCloseAlertProps.actionParams,
|
||||
subActionParams: {
|
||||
message: 'hello',
|
||||
alias: 'a new alias',
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(screen.queryByDisplayValue('hello')).not.toBeInTheDocument();
|
||||
|
||||
expect(editAction).toBeCalledTimes(2);
|
||||
|
||||
expect(editAction.mock.calls[1]).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
"subActionParams",
|
||||
Object {
|
||||
"alias": "a new alias",
|
||||
},
|
||||
0,
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
it('calls editAction when changing the subAction', async () => {
|
||||
render(<OpsgenieParamFields {...defaultCreateAlertProps} />);
|
||||
|
||||
act(() =>
|
||||
userEvent.selectOptions(
|
||||
screen.getByTestId('opsgenie-subActionSelect'),
|
||||
screen.getByText('Close Alert')
|
||||
)
|
||||
);
|
||||
|
||||
expect(editAction).toBeCalledTimes(1);
|
||||
expect(editAction.mock.calls[0]).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
"subAction",
|
||||
"closeAlert",
|
||||
0,
|
||||
]
|
||||
`);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,219 @@
|
|||
/*
|
||||
* 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 React, { useCallback, useEffect, useRef } from 'react';
|
||||
import {
|
||||
ActionParamsProps,
|
||||
TextAreaWithMessageVariables,
|
||||
TextFieldWithMessageVariables,
|
||||
} from '@kbn/triggers-actions-ui-plugin/public';
|
||||
import { EuiFormRow, EuiSelect, RecursivePartial } from '@elastic/eui';
|
||||
import { OpsgenieSubActions } from '../../../../common';
|
||||
import type {
|
||||
OpsgenieActionParams,
|
||||
OpsgenieCloseAlertParams,
|
||||
OpsgenieCreateAlertParams,
|
||||
} from '../../../../server/connector_types/stack';
|
||||
import * as i18n from './translations';
|
||||
|
||||
type SubActionProps<SubActionParams> = Omit<
|
||||
ActionParamsProps<OpsgenieActionParams>,
|
||||
'actionParams' | 'editAction'
|
||||
> & {
|
||||
subActionParams?: RecursivePartial<SubActionParams>;
|
||||
editSubAction: ActionParamsProps<OpsgenieActionParams>['editAction'];
|
||||
};
|
||||
|
||||
const CreateAlertComponent: React.FC<SubActionProps<OpsgenieCreateAlertParams>> = ({
|
||||
editSubAction,
|
||||
errors,
|
||||
index,
|
||||
messageVariables,
|
||||
subActionParams,
|
||||
}) => {
|
||||
const isMessageInvalid =
|
||||
errors['subActionParams.message'] !== undefined &&
|
||||
errors['subActionParams.message'].length > 0 &&
|
||||
subActionParams?.message !== undefined;
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiFormRow
|
||||
data-test-subj="opsgenie-message-row"
|
||||
fullWidth
|
||||
error={errors['subActionParams.message']}
|
||||
label={i18n.MESSAGE_FIELD_LABEL}
|
||||
isInvalid={isMessageInvalid}
|
||||
>
|
||||
<TextFieldWithMessageVariables
|
||||
index={index}
|
||||
editAction={editSubAction}
|
||||
messageVariables={messageVariables}
|
||||
paramsProperty={'message'}
|
||||
inputTargetValue={subActionParams?.message}
|
||||
errors={errors['subActionParams.message'] as string[]}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<TextAreaWithMessageVariables
|
||||
index={index}
|
||||
editAction={editSubAction}
|
||||
messageVariables={messageVariables}
|
||||
paramsProperty={'description'}
|
||||
inputTargetValue={subActionParams?.description}
|
||||
label={i18n.DESCRIPTION_FIELD_LABEL}
|
||||
/>
|
||||
<EuiFormRow data-test-subj="opsgenie-alias-row" fullWidth label={i18n.ALIAS_FIELD_LABEL}>
|
||||
<TextFieldWithMessageVariables
|
||||
index={index}
|
||||
editAction={editSubAction}
|
||||
messageVariables={messageVariables}
|
||||
paramsProperty={'alias'}
|
||||
inputTargetValue={subActionParams?.alias}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
CreateAlertComponent.displayName = 'CreateAlertComponent';
|
||||
|
||||
const CloseAlertComponent: React.FC<SubActionProps<OpsgenieCloseAlertParams>> = ({
|
||||
editSubAction,
|
||||
errors,
|
||||
index,
|
||||
messageVariables,
|
||||
subActionParams,
|
||||
}) => {
|
||||
const isAliasInvalid =
|
||||
errors['subActionParams.alias'] !== undefined &&
|
||||
errors['subActionParams.alias'].length > 0 &&
|
||||
subActionParams?.alias !== undefined;
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiFormRow
|
||||
data-test-subj="opsgenie-alias-row"
|
||||
fullWidth
|
||||
error={errors['subActionParams.alias']}
|
||||
isInvalid={isAliasInvalid}
|
||||
label={i18n.ALIAS_FIELD_LABEL}
|
||||
>
|
||||
<TextFieldWithMessageVariables
|
||||
index={index}
|
||||
editAction={editSubAction}
|
||||
messageVariables={messageVariables}
|
||||
paramsProperty={'alias'}
|
||||
inputTargetValue={subActionParams?.alias}
|
||||
errors={errors['subActionParams.alias'] as string[]}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<TextAreaWithMessageVariables
|
||||
index={index}
|
||||
editAction={editSubAction}
|
||||
messageVariables={messageVariables}
|
||||
paramsProperty={'note'}
|
||||
inputTargetValue={subActionParams?.note}
|
||||
label={i18n.NOTE_FIELD_LABEL}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
CloseAlertComponent.displayName = 'CloseAlertComponent';
|
||||
|
||||
const actionOptions = [
|
||||
{
|
||||
value: OpsgenieSubActions.CreateAlert,
|
||||
text: i18n.CREATE_ALERT_ACTION,
|
||||
},
|
||||
{
|
||||
value: OpsgenieSubActions.CloseAlert,
|
||||
text: i18n.CLOSE_ALERT_ACTION,
|
||||
},
|
||||
];
|
||||
|
||||
const OpsgenieParamFields: React.FC<ActionParamsProps<OpsgenieActionParams>> = ({
|
||||
actionParams,
|
||||
editAction,
|
||||
errors,
|
||||
index,
|
||||
messageVariables,
|
||||
}) => {
|
||||
const { subAction, subActionParams } = actionParams;
|
||||
|
||||
const currentSubAction = useRef<string>(subAction ?? OpsgenieSubActions.CreateAlert);
|
||||
|
||||
const onActionChange = useCallback(
|
||||
(event: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
editAction('subAction', event.target.value, index);
|
||||
},
|
||||
[editAction, index]
|
||||
);
|
||||
|
||||
const editSubAction = useCallback(
|
||||
(key, value) => {
|
||||
editAction('subActionParams', { ...subActionParams, [key]: value }, index);
|
||||
},
|
||||
[editAction, index, subActionParams]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!subAction) {
|
||||
editAction('subAction', OpsgenieSubActions.CreateAlert, index);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [index, subAction]);
|
||||
|
||||
useEffect(() => {
|
||||
if (subAction != null && currentSubAction.current !== subAction) {
|
||||
currentSubAction.current = subAction;
|
||||
const params = subActionParams?.alias ? { alias: subActionParams.alias } : undefined;
|
||||
editAction('subActionParams', params, index);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [subAction, currentSubAction]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiFormRow fullWidth label={i18n.ACTION_LABEL}>
|
||||
<EuiSelect
|
||||
fullWidth
|
||||
data-test-subj="opsgenie-subActionSelect"
|
||||
options={actionOptions}
|
||||
hasNoInitialSelection={subAction == null}
|
||||
value={subAction}
|
||||
onChange={onActionChange}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
|
||||
{subAction != null && subAction === OpsgenieSubActions.CreateAlert && (
|
||||
<CreateAlertComponent
|
||||
subActionParams={subActionParams}
|
||||
editSubAction={editSubAction}
|
||||
errors={errors}
|
||||
index={index}
|
||||
messageVariables={messageVariables}
|
||||
/>
|
||||
)}
|
||||
|
||||
{subAction != null && subAction === OpsgenieSubActions.CloseAlert && (
|
||||
<CloseAlertComponent
|
||||
subActionParams={subActionParams}
|
||||
editSubAction={editSubAction}
|
||||
errors={errors}
|
||||
index={index}
|
||||
messageVariables={messageVariables}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
OpsgenieParamFields.displayName = 'OpsgenieParamFields';
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export { OpsgenieParamFields as default };
|
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
* 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 { i18n } from '@kbn/i18n';
|
||||
|
||||
export const API_URL_LABEL = i18n.translate(
|
||||
'xpack.stackConnectors.components.opsgenie.apiUrlTextFieldLabel',
|
||||
{
|
||||
defaultMessage: 'URL',
|
||||
}
|
||||
);
|
||||
|
||||
export const API_KEY_LABEL = i18n.translate(
|
||||
'xpack.stackConnectors.components.opsgenie.apiKeySecret',
|
||||
{
|
||||
defaultMessage: 'API Key',
|
||||
}
|
||||
);
|
||||
|
||||
export const ACTION_LABEL = i18n.translate(
|
||||
'xpack.stackConnectors.components.opsgenie.actionLabel',
|
||||
{
|
||||
defaultMessage: 'Action',
|
||||
}
|
||||
);
|
||||
|
||||
export const CREATE_ALERT_ACTION = i18n.translate(
|
||||
'xpack.stackConnectors.components.opsgenie.createAlertAction',
|
||||
{
|
||||
defaultMessage: 'Create Alert',
|
||||
}
|
||||
);
|
||||
|
||||
export const CLOSE_ALERT_ACTION = i18n.translate(
|
||||
'xpack.stackConnectors.components.opsgenie.closeAlertAction',
|
||||
{
|
||||
defaultMessage: 'Close Alert',
|
||||
}
|
||||
);
|
||||
|
||||
export const MESSAGE_FIELD_LABEL = i18n.translate(
|
||||
'xpack.stackConnectors.components.opsgenie.messageLabel',
|
||||
{
|
||||
defaultMessage: 'Message',
|
||||
}
|
||||
);
|
||||
|
||||
export const NOTE_FIELD_LABEL = i18n.translate(
|
||||
'xpack.stackConnectors.components.opsgenie.noteLabel',
|
||||
{
|
||||
defaultMessage: 'Note (optional)',
|
||||
}
|
||||
);
|
||||
|
||||
export const DESCRIPTION_FIELD_LABEL = i18n.translate(
|
||||
'xpack.stackConnectors.components.opsgenie.descriptionLabel',
|
||||
{
|
||||
defaultMessage: 'Description (optional)',
|
||||
}
|
||||
);
|
||||
|
||||
export const MESSAGE_IS_REQUIRED = i18n.translate(
|
||||
'xpack.stackConnectors.components.opsgenie.requiredMessageTextField',
|
||||
{
|
||||
defaultMessage: 'Message is required.',
|
||||
}
|
||||
);
|
||||
|
||||
export const ALIAS_FIELD_LABEL = i18n.translate(
|
||||
'xpack.stackConnectors.components.opsgenie.aliasLabel',
|
||||
{
|
||||
defaultMessage: 'Alias',
|
||||
}
|
||||
);
|
||||
|
||||
export const ALIAS_IS_REQUIRED = i18n.translate(
|
||||
'xpack.stackConnectors.components.opsgenie.requiredAliasTextField',
|
||||
{
|
||||
defaultMessage: 'Alias is required.',
|
||||
}
|
||||
);
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* 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 { UserConfiguredActionConnector } from '@kbn/triggers-actions-ui-plugin/public/types';
|
||||
import type {
|
||||
OpsgenieActionConfig,
|
||||
OpsgenieActionSecrets,
|
||||
} from '../../../../server/connector_types/stack';
|
||||
|
||||
export type OpsgenieActionConnector = UserConfiguredActionConnector<
|
||||
OpsgenieActionConfig,
|
||||
OpsgenieActionSecrets
|
||||
>;
|
|
@ -45,7 +45,6 @@ export {
|
|||
SlackConnectorTypeId,
|
||||
TeamsConnectorTypeId,
|
||||
WebhookConnectorTypeId,
|
||||
OpsgenieConnectorTypeId,
|
||||
XmattersConnectorTypeId,
|
||||
} from './stack';
|
||||
export type {
|
||||
|
|
|
@ -49,7 +49,16 @@ export {
|
|||
} from './webhook';
|
||||
export type { ActionParamsType as WebhookActionParams } from './webhook';
|
||||
|
||||
export { getOpsgenieConnectorType, OpsgenieConnectorTypeId } from './opsgenie';
|
||||
export { getOpsgenieConnectorType } from './opsgenie';
|
||||
export type {
|
||||
OpsgenieActionConfig,
|
||||
OpsgenieActionSecrets,
|
||||
OpsgenieActionParams,
|
||||
OpsgenieCloseAlertSubActionParams,
|
||||
OpsgenieCreateAlertSubActionParams,
|
||||
OpsgenieCloseAlertParams,
|
||||
OpsgenieCreateAlertParams,
|
||||
} from './opsgenie';
|
||||
|
||||
export {
|
||||
getConnectorType as getXmattersConnectorType,
|
||||
|
|
|
@ -12,7 +12,7 @@ import { actionsConfigMock } from '@kbn/actions-plugin/server/actions_config.moc
|
|||
import { actionsMock } from '@kbn/actions-plugin/server/mocks';
|
||||
import { loggingSystemMock } from '@kbn/core-logging-server-mocks';
|
||||
import { MockedLogger } from '@kbn/logging-mocks';
|
||||
import { OpsgenieConnectorTypeId } from '.';
|
||||
import { OpsgenieConnectorTypeId } from '../../../../common';
|
||||
import { OpsgenieConnector } from './connector';
|
||||
import * as utils from '@kbn/actions-plugin/server/lib/axios_utils';
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
import crypto from 'crypto';
|
||||
import { ServiceParams, SubActionConnector } from '@kbn/actions-plugin/server';
|
||||
import { AxiosError } from 'axios';
|
||||
import { OpsgenieSubActions } from '../../../../common';
|
||||
import { CloseAlertParamsSchema, CreateAlertParamsSchema, Response } from './schema';
|
||||
import { CloseAlertParams, Config, CreateAlertParams, Secrets } from './types';
|
||||
import * as i18n from './translations';
|
||||
|
@ -25,13 +26,13 @@ export class OpsgenieConnector extends SubActionConnector<Config, Secrets> {
|
|||
|
||||
this.registerSubAction({
|
||||
method: this.createAlert.name,
|
||||
name: 'createAlert',
|
||||
name: OpsgenieSubActions.CreateAlert,
|
||||
schema: CreateAlertParamsSchema,
|
||||
});
|
||||
|
||||
this.registerSubAction({
|
||||
method: this.closeAlert.name,
|
||||
name: 'closeAlert',
|
||||
name: OpsgenieSubActions.CloseAlert,
|
||||
schema: CloseAlertParamsSchema,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -15,13 +15,12 @@ import {
|
|||
SubActionConnectorType,
|
||||
ValidatorType,
|
||||
} from '@kbn/actions-plugin/server/sub_action_framework/types';
|
||||
import { OpsgenieConnectorTypeId } from '../../../../common';
|
||||
import { OpsgenieConnector } from './connector';
|
||||
import { ConfigSchema, SecretsSchema } from './schema';
|
||||
import { Config, Secrets } from './types';
|
||||
import * as i18n from './translations';
|
||||
|
||||
export const OpsgenieConnectorTypeId = '.opsgenie';
|
||||
|
||||
export const getOpsgenieConnectorType = (): SubActionConnectorType<Config, Secrets> => {
|
||||
return {
|
||||
Service: OpsgenieConnector,
|
||||
|
@ -37,3 +36,13 @@ export const getOpsgenieConnectorType = (): SubActionConnectorType<Config, Secre
|
|||
],
|
||||
};
|
||||
};
|
||||
|
||||
export type {
|
||||
Config as OpsgenieActionConfig,
|
||||
Secrets as OpsgenieActionSecrets,
|
||||
Params as OpsgenieActionParams,
|
||||
CreateAlertSubActionParams as OpsgenieCreateAlertSubActionParams,
|
||||
CloseAlertSubActionParams as OpsgenieCloseAlertSubActionParams,
|
||||
CreateAlertParams as OpsgenieCreateAlertParams,
|
||||
CloseAlertParams as OpsgenieCloseAlertParams,
|
||||
} from './types';
|
||||
|
|
|
@ -11,9 +11,22 @@ import {
|
|||
CreateAlertParamsSchema,
|
||||
SecretsSchema,
|
||||
} from './schema';
|
||||
import { OpsgenieSubActions } from '../../../../common';
|
||||
|
||||
export type Config = TypeOf<typeof ConfigSchema>;
|
||||
export type Secrets = TypeOf<typeof SecretsSchema>;
|
||||
|
||||
export type CreateAlertParams = TypeOf<typeof CreateAlertParamsSchema>;
|
||||
export type CloseAlertParams = TypeOf<typeof CloseAlertParamsSchema>;
|
||||
|
||||
export interface CreateAlertSubActionParams {
|
||||
subAction: OpsgenieSubActions.CreateAlert;
|
||||
subActionParams: CreateAlertParams;
|
||||
}
|
||||
|
||||
export interface CloseAlertSubActionParams {
|
||||
subAction: OpsgenieSubActions.CloseAlert;
|
||||
subActionParams: CloseAlertParams;
|
||||
}
|
||||
|
||||
export type Params = CreateAlertSubActionParams | CloseAlertSubActionParams;
|
||||
|
|
|
@ -13,7 +13,7 @@ import type { ChartsPluginSetup } from '@kbn/charts-plugin/public';
|
|||
import type { DataPublicPluginStart } from '@kbn/data-plugin/public';
|
||||
import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public';
|
||||
import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public';
|
||||
import type { IconType, EuiFlyoutSize } from '@elastic/eui';
|
||||
import type { IconType, EuiFlyoutSize, RecursivePartial } from '@elastic/eui';
|
||||
import { EuiDataGridColumn, EuiDataGridControlColumn, EuiDataGridSorting } from '@elastic/eui';
|
||||
import {
|
||||
ActionType,
|
||||
|
@ -204,8 +204,8 @@ export interface ActionTypeModel<ActionConfig = any, ActionSecrets = any, Action
|
|||
ComponentType<ActionConnectorFieldsProps>
|
||||
> | null;
|
||||
actionParamsFields: React.LazyExoticComponent<ComponentType<ActionParamsProps<ActionParams>>>;
|
||||
defaultActionParams?: Partial<ActionParams>;
|
||||
defaultRecoveredActionParams?: Partial<ActionParams>;
|
||||
defaultActionParams?: RecursivePartial<ActionParams>;
|
||||
defaultRecoveredActionParams?: RecursivePartial<ActionParams>;
|
||||
customConnectorSelectItem?: CustomConnectorSelectionItem;
|
||||
isExperimental?: boolean;
|
||||
}
|
||||
|
|
29
x-pack/test/functional/services/actions/common.ts
Normal file
29
x-pack/test/functional/services/actions/common.ts
Normal file
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* 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 { ProvidedType } from '@kbn/test';
|
||||
import { FtrProviderContext } from '../../ftr_provider_context';
|
||||
|
||||
export type ActionsCommon = ProvidedType<typeof ActionsCommonServiceProvider>;
|
||||
|
||||
export function ActionsCommonServiceProvider({ getService, getPageObject }: FtrProviderContext) {
|
||||
const testSubjects = getService('testSubjects');
|
||||
|
||||
return {
|
||||
async openNewConnectorForm(name: string) {
|
||||
const createBtn = await testSubjects.find('createActionButton');
|
||||
const createBtnIsVisible = await createBtn.isDisplayed();
|
||||
if (createBtnIsVisible) {
|
||||
await createBtn.click();
|
||||
} else {
|
||||
await testSubjects.click('createFirstActionButton');
|
||||
}
|
||||
|
||||
await testSubjects.click(`.${name}-card`);
|
||||
},
|
||||
};
|
||||
}
|
19
x-pack/test/functional/services/actions/index.ts
Normal file
19
x-pack/test/functional/services/actions/index.ts
Normal file
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* 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 { FtrProviderContext } from '../../ftr_provider_context';
|
||||
import { ActionsCommonServiceProvider } from './common';
|
||||
import { ActionsOpsgenieServiceProvider } from './opsgenie';
|
||||
|
||||
export function ActionsServiceProvider(context: FtrProviderContext) {
|
||||
const common = ActionsCommonServiceProvider(context);
|
||||
|
||||
return {
|
||||
opsgenie: ActionsOpsgenieServiceProvider(context, common),
|
||||
common: ActionsCommonServiceProvider(context),
|
||||
};
|
||||
}
|
48
x-pack/test/functional/services/actions/opsgenie.ts
Normal file
48
x-pack/test/functional/services/actions/opsgenie.ts
Normal file
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* 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 expect from '@kbn/expect';
|
||||
import { FtrProviderContext } from '../../ftr_provider_context';
|
||||
import type { ActionsCommon } from './common';
|
||||
|
||||
export interface ConnectorFormFields {
|
||||
name: string;
|
||||
apiUrl: string;
|
||||
apiKey: string;
|
||||
}
|
||||
|
||||
export function ActionsOpsgenieServiceProvider(
|
||||
{ getService, getPageObject }: FtrProviderContext,
|
||||
common: ActionsCommon
|
||||
) {
|
||||
const testSubjects = getService('testSubjects');
|
||||
|
||||
return {
|
||||
async createNewConnector(fields: ConnectorFormFields) {
|
||||
await common.openNewConnectorForm('opsgenie');
|
||||
await this.setConnectorFields(fields);
|
||||
|
||||
const flyOutSaveButton = await testSubjects.find('create-connector-flyout-save-btn');
|
||||
expect(await flyOutSaveButton.isEnabled()).to.be(true);
|
||||
await flyOutSaveButton.click();
|
||||
},
|
||||
|
||||
async setConnectorFields({ name, apiUrl, apiKey }: ConnectorFormFields) {
|
||||
await testSubjects.setValue('nameInput', name);
|
||||
await testSubjects.setValue('config.apiUrl-input', apiUrl);
|
||||
await testSubjects.setValue('secrets.apiKey-input', apiKey);
|
||||
},
|
||||
|
||||
async updateConnectorFields(fields: ConnectorFormFields) {
|
||||
await this.setConnectorFields(fields);
|
||||
|
||||
const editFlyOutSaveButton = await testSubjects.find('edit-connector-flyout-save-btn');
|
||||
expect(await editFlyOutSaveButton.isEnabled()).to.be(true);
|
||||
await editFlyOutSaveButton.click();
|
||||
},
|
||||
};
|
||||
}
|
|
@ -70,6 +70,8 @@ import { SearchSessionsService } from './search_sessions';
|
|||
import { ObservabilityProvider } from './observability';
|
||||
// import { CompareImagesProvider } from './compare_images';
|
||||
import { CasesServiceProvider } from './cases';
|
||||
import { ActionsServiceProvider } from './actions';
|
||||
import { RulesServiceProvider } from './rules';
|
||||
import { AiopsProvider } from './aiops';
|
||||
|
||||
// define the name and providers for services that should be
|
||||
|
@ -130,6 +132,8 @@ export const services = {
|
|||
searchSessions: SearchSessionsService,
|
||||
observability: ObservabilityProvider,
|
||||
// compareImages: CompareImagesProvider,
|
||||
actions: ActionsServiceProvider,
|
||||
rules: RulesServiceProvider,
|
||||
cases: CasesServiceProvider,
|
||||
aiops: AiopsProvider,
|
||||
};
|
||||
|
|
83
x-pack/test/functional/services/rules/common.ts
Normal file
83
x-pack/test/functional/services/rules/common.ts
Normal file
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* 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 expect from '@kbn/expect';
|
||||
import { ProvidedType } from '@kbn/test';
|
||||
import { FtrProviderContext } from '../../ftr_provider_context';
|
||||
|
||||
export type RulesCommon = ProvidedType<typeof RulesCommonServiceProvider>;
|
||||
|
||||
export function RulesCommonServiceProvider({ getService, getPageObject }: FtrProviderContext) {
|
||||
const testSubjects = getService('testSubjects');
|
||||
const comboBox = getService('comboBox');
|
||||
const find = getService('find');
|
||||
const retry = getService('retry');
|
||||
const browser = getService('browser');
|
||||
|
||||
return {
|
||||
async clickCreateAlertButton() {
|
||||
const createBtn = await find.byCssSelector(
|
||||
'[data-test-subj="createRuleButton"],[data-test-subj="createFirstRuleButton"]'
|
||||
);
|
||||
await createBtn.click();
|
||||
},
|
||||
|
||||
async cancelRuleCreation() {
|
||||
await testSubjects.click('cancelSaveRuleButton');
|
||||
await testSubjects.existOrFail('confirmRuleCloseModal');
|
||||
await testSubjects.click('confirmRuleCloseModal > confirmModalConfirmButton');
|
||||
await testSubjects.missingOrFail('confirmRuleCloseModal');
|
||||
},
|
||||
|
||||
async setNotifyThrottleInput(value: string = '10') {
|
||||
await testSubjects.click('notifyWhenSelect');
|
||||
await testSubjects.click('onThrottleInterval');
|
||||
await testSubjects.setValue('throttleInput', value);
|
||||
},
|
||||
|
||||
async defineIndexThresholdAlert(alertName: string) {
|
||||
await browser.refresh();
|
||||
await this.clickCreateAlertButton();
|
||||
await testSubjects.scrollIntoView('ruleNameInput');
|
||||
await testSubjects.setValue('ruleNameInput', alertName);
|
||||
await testSubjects.click(`.index-threshold-SelectOption`);
|
||||
await testSubjects.scrollIntoView('selectIndexExpression');
|
||||
await testSubjects.click('selectIndexExpression');
|
||||
const indexComboBox = await find.byCssSelector('#indexSelectSearchBox');
|
||||
await indexComboBox.click();
|
||||
await indexComboBox.type('k');
|
||||
const filterSelectItem = await find.byCssSelector(`.euiFilterSelectItem`);
|
||||
await filterSelectItem.click();
|
||||
await testSubjects.click('thresholdAlertTimeFieldSelect');
|
||||
await retry.try(async () => {
|
||||
const fieldOptions = await find.allByCssSelector('#thresholdTimeField option');
|
||||
expect(fieldOptions[1]).not.to.be(undefined);
|
||||
await fieldOptions[1].click();
|
||||
});
|
||||
await testSubjects.click('closePopover');
|
||||
// need this two out of popup clicks to close them
|
||||
const nameInput = await testSubjects.find('ruleNameInput');
|
||||
await nameInput.click();
|
||||
|
||||
await testSubjects.click('whenExpression');
|
||||
await testSubjects.click('whenExpressionSelect');
|
||||
await retry.try(async () => {
|
||||
const aggTypeOptions = await find.allByCssSelector('#aggTypeField option');
|
||||
expect(aggTypeOptions[1]).not.to.be(undefined);
|
||||
await aggTypeOptions[1].click();
|
||||
});
|
||||
|
||||
await testSubjects.click('ofExpressionPopover');
|
||||
const ofComboBox = await find.byCssSelector('#ofField');
|
||||
await ofComboBox.click();
|
||||
const ofOptionsString = await comboBox.getOptionsList('availablefieldsOptionsComboBox');
|
||||
const ofOptions = ofOptionsString.trim().split('\n');
|
||||
expect(ofOptions.length > 0).to.be(true);
|
||||
await comboBox.set('availablefieldsOptionsComboBox', ofOptions[0]);
|
||||
},
|
||||
};
|
||||
}
|
15
x-pack/test/functional/services/rules/index.ts
Normal file
15
x-pack/test/functional/services/rules/index.ts
Normal file
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* 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 { FtrProviderContext } from '../../ftr_provider_context';
|
||||
import { RulesCommonServiceProvider } from './common';
|
||||
|
||||
export function RulesServiceProvider(context: FtrProviderContext) {
|
||||
return {
|
||||
common: RulesCommonServiceProvider(context),
|
||||
};
|
||||
}
|
|
@ -17,8 +17,8 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
|
|||
const supertest = getService('supertest');
|
||||
const find = getService('find');
|
||||
const retry = getService('retry');
|
||||
const comboBox = getService('comboBox');
|
||||
const browser = getService('browser');
|
||||
const rules = getService('rules');
|
||||
|
||||
async function getAlertsByName(name: string) {
|
||||
const {
|
||||
|
@ -62,44 +62,6 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
|
|||
await nameInput.click();
|
||||
}
|
||||
|
||||
async function defineIndexThresholdAlert(alertName: string) {
|
||||
await pageObjects.triggersActionsUI.clickCreateAlertButton();
|
||||
await testSubjects.setValue('ruleNameInput', alertName);
|
||||
await testSubjects.click(`.index-threshold-SelectOption`);
|
||||
await testSubjects.click('selectIndexExpression');
|
||||
const indexComboBox = await find.byCssSelector('#indexSelectSearchBox');
|
||||
await indexComboBox.click();
|
||||
await indexComboBox.type('k');
|
||||
const filterSelectItem = await find.byCssSelector(`.euiFilterSelectItem`);
|
||||
await filterSelectItem.click();
|
||||
await testSubjects.click('thresholdAlertTimeFieldSelect');
|
||||
await retry.try(async () => {
|
||||
const fieldOptions = await find.allByCssSelector('#thresholdTimeField option');
|
||||
expect(fieldOptions[1]).not.to.be(undefined);
|
||||
await fieldOptions[1].click();
|
||||
});
|
||||
await testSubjects.click('closePopover');
|
||||
// need this two out of popup clicks to close them
|
||||
const nameInput = await testSubjects.find('ruleNameInput');
|
||||
await nameInput.click();
|
||||
|
||||
await testSubjects.click('whenExpression');
|
||||
await testSubjects.click('whenExpressionSelect');
|
||||
await retry.try(async () => {
|
||||
const aggTypeOptions = await find.allByCssSelector('#aggTypeField option');
|
||||
expect(aggTypeOptions[1]).not.to.be(undefined);
|
||||
await aggTypeOptions[1].click();
|
||||
});
|
||||
|
||||
await testSubjects.click('ofExpressionPopover');
|
||||
const ofComboBox = await find.byCssSelector('#ofField');
|
||||
await ofComboBox.click();
|
||||
const ofOptionsString = await comboBox.getOptionsList('availablefieldsOptionsComboBox');
|
||||
const ofOptions = ofOptionsString.trim().split('\n');
|
||||
expect(ofOptions.length > 0).to.be(true);
|
||||
await comboBox.set('availablefieldsOptionsComboBox', ofOptions[0]);
|
||||
}
|
||||
|
||||
async function defineAlwaysFiringAlert(alertName: string) {
|
||||
await pageObjects.triggersActionsUI.clickCreateAlertButton();
|
||||
await testSubjects.setValue('ruleNameInput', alertName);
|
||||
|
@ -107,10 +69,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
|
|||
}
|
||||
|
||||
async function discardNewRuleCreation() {
|
||||
await testSubjects.click('cancelSaveRuleButton');
|
||||
await testSubjects.existOrFail('confirmRuleCloseModal');
|
||||
await testSubjects.click('confirmRuleCloseModal > confirmModalConfirmButton');
|
||||
await testSubjects.missingOrFail('confirmRuleCloseModal');
|
||||
await rules.common.cancelRuleCreation();
|
||||
}
|
||||
|
||||
describe('create alert', function () {
|
||||
|
@ -128,7 +87,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
|
|||
|
||||
it('should create an alert', async () => {
|
||||
const alertName = generateUniqueKey();
|
||||
await defineIndexThresholdAlert(alertName);
|
||||
await rules.common.defineIndexThresholdAlert(alertName);
|
||||
|
||||
await testSubjects.click('notifyWhenSelect');
|
||||
await testSubjects.click('onThrottleInterval');
|
||||
|
|
|
@ -6,29 +6,31 @@
|
|||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import { findIndex } from 'lodash';
|
||||
import { FtrProviderContext } from '../../ftr_provider_context';
|
||||
import { ObjectRemover } from '../../lib/object_remover';
|
||||
import { generateUniqueKey, getTestActionData } from '../../lib/get_test_data';
|
||||
import { FtrProviderContext } from '../../../ftr_provider_context';
|
||||
import { ObjectRemover } from '../../../lib/object_remover';
|
||||
import { generateUniqueKey } from '../../../lib/get_test_data';
|
||||
import {
|
||||
getConnectorByName,
|
||||
createSlackConnectorAndObjectRemover,
|
||||
createSlackConnector,
|
||||
} from './utils';
|
||||
|
||||
export default ({ getPageObjects, getService }: FtrProviderContext) => {
|
||||
export default ({ getPageObjects, getPageObject, getService }: FtrProviderContext) => {
|
||||
const testSubjects = getService('testSubjects');
|
||||
const pageObjects = getPageObjects(['common', 'triggersActionsUI', 'header']);
|
||||
const find = getService('find');
|
||||
const retry = getService('retry');
|
||||
const supertest = getService('supertest');
|
||||
const objectRemover = new ObjectRemover(supertest);
|
||||
let objectRemover: ObjectRemover;
|
||||
const browser = getService('browser');
|
||||
|
||||
describe('Connectors', function () {
|
||||
describe('General connector functionality', function () {
|
||||
before(async () => {
|
||||
const { body: createdAction } = await supertest
|
||||
.post(`/api/actions/connector`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send(getTestActionData())
|
||||
.expect(200);
|
||||
objectRemover = await createSlackConnectorAndObjectRemover({ getService });
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await pageObjects.common.navigateToApp('triggersActionsConnectors');
|
||||
objectRemover.add(createdAction.id, 'action', 'actions');
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
|
@ -66,14 +68,17 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
|
|||
actionType: 'Slack',
|
||||
},
|
||||
]);
|
||||
const connector = await getConnector(connectorName);
|
||||
const connector = await getConnectorByName(connectorName, supertest);
|
||||
objectRemover.add(connector.id, 'action', 'actions');
|
||||
});
|
||||
|
||||
it('should edit a connector', async () => {
|
||||
const connectorName = generateUniqueKey();
|
||||
const updatedConnectorName = `${connectorName}updated`;
|
||||
const createdAction = await createConnector(connectorName);
|
||||
const createdAction = await createSlackConnector({
|
||||
name: connectorName,
|
||||
supertest,
|
||||
});
|
||||
objectRemover.add(createdAction.id, 'action', 'actions');
|
||||
await browser.refresh();
|
||||
|
||||
|
@ -169,7 +174,10 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
|
|||
|
||||
it('should reset connector when canceling an edit', async () => {
|
||||
const connectorName = generateUniqueKey();
|
||||
const createdAction = await createConnector(connectorName);
|
||||
const createdAction = await createSlackConnector({
|
||||
name: connectorName,
|
||||
supertest,
|
||||
});
|
||||
objectRemover.add(createdAction.id, 'action', 'actions');
|
||||
await browser.refresh();
|
||||
|
||||
|
@ -197,8 +205,11 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
|
|||
|
||||
it('should delete a connector', async () => {
|
||||
const connectorName = generateUniqueKey();
|
||||
await createConnector(connectorName);
|
||||
const createdAction = await createConnector(generateUniqueKey());
|
||||
await createSlackConnector({ name: connectorName, supertest });
|
||||
const createdAction = await createSlackConnector({
|
||||
name: generateUniqueKey(),
|
||||
supertest,
|
||||
});
|
||||
objectRemover.add(createdAction.id, 'action', 'actions');
|
||||
await browser.refresh();
|
||||
|
||||
|
@ -223,8 +234,11 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
|
|||
|
||||
it('should bulk delete connectors', async () => {
|
||||
const connectorName = generateUniqueKey();
|
||||
await createConnector(connectorName);
|
||||
const createdAction = await createConnector(generateUniqueKey());
|
||||
await createSlackConnector({ name: connectorName, supertest });
|
||||
const createdAction = await createSlackConnector({
|
||||
name: generateUniqueKey(),
|
||||
supertest,
|
||||
});
|
||||
objectRemover.add(createdAction.id, 'action', 'actions');
|
||||
await browser.refresh();
|
||||
|
||||
|
@ -279,22 +293,6 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
|
|||
});
|
||||
});
|
||||
|
||||
async function createConnector(connectorName: string) {
|
||||
const { body: createdAction } = await supertest
|
||||
.post(`/api/actions/connector`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({
|
||||
name: connectorName,
|
||||
config: {},
|
||||
secrets: {
|
||||
webhookUrl: 'https://test.com',
|
||||
},
|
||||
connector_type_id: '.slack',
|
||||
})
|
||||
.expect(200);
|
||||
return createdAction;
|
||||
}
|
||||
|
||||
async function createIndexConnector(connectorName: string, indexName: string) {
|
||||
const { body: createdAction } = await supertest
|
||||
.post(`/api/actions/connector`)
|
||||
|
@ -311,13 +309,4 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
|
|||
.expect(200);
|
||||
return createdAction;
|
||||
}
|
||||
|
||||
async function getConnector(name: string) {
|
||||
const { body } = await supertest
|
||||
.get(`/api/actions/connectors`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.expect(200);
|
||||
const i = findIndex(body, (c: any) => c.name === name);
|
||||
return body[i];
|
||||
}
|
||||
};
|
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* 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 { FtrProviderContext } from '../../../ftr_provider_context';
|
||||
|
||||
export default ({ loadTestFile }: FtrProviderContext) => {
|
||||
describe('Connectors', function () {
|
||||
loadTestFile(require.resolve('./general'));
|
||||
loadTestFile(require.resolve('./opsgenie'));
|
||||
});
|
||||
};
|
|
@ -0,0 +1,254 @@
|
|||
/*
|
||||
* 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 expect from '@kbn/expect';
|
||||
import { FtrProviderContext } from '../../../ftr_provider_context';
|
||||
import { ObjectRemover } from '../../../lib/object_remover';
|
||||
import { generateUniqueKey } from '../../../lib/get_test_data';
|
||||
import { createConnector, createSlackConnectorAndObjectRemover, getConnectorByName } from './utils';
|
||||
|
||||
export default ({ getPageObjects, getService }: FtrProviderContext) => {
|
||||
const testSubjects = getService('testSubjects');
|
||||
const pageObjects = getPageObjects(['common', 'triggersActionsUI', 'header']);
|
||||
const find = getService('find');
|
||||
const retry = getService('retry');
|
||||
const supertest = getService('supertest');
|
||||
const actions = getService('actions');
|
||||
const rules = getService('rules');
|
||||
const browser = getService('browser');
|
||||
let objectRemover: ObjectRemover;
|
||||
|
||||
describe('Opsgenie', () => {
|
||||
before(async () => {
|
||||
objectRemover = await createSlackConnectorAndObjectRemover({ getService });
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await objectRemover.removeAll();
|
||||
});
|
||||
|
||||
describe('connector page', () => {
|
||||
beforeEach(async () => {
|
||||
await pageObjects.common.navigateToApp('triggersActionsConnectors');
|
||||
});
|
||||
|
||||
it('should create the connector', async () => {
|
||||
const connectorName = generateUniqueKey();
|
||||
|
||||
await actions.opsgenie.createNewConnector({
|
||||
name: connectorName,
|
||||
apiUrl: 'https://test.com',
|
||||
apiKey: 'apiKey',
|
||||
});
|
||||
|
||||
const toastTitle = await pageObjects.common.closeToast();
|
||||
expect(toastTitle).to.eql(`Created '${connectorName}'`);
|
||||
|
||||
await pageObjects.triggersActionsUI.searchConnectors(connectorName);
|
||||
|
||||
const searchResults = await pageObjects.triggersActionsUI.getConnectorsList();
|
||||
expect(searchResults).to.eql([
|
||||
{
|
||||
name: connectorName,
|
||||
actionType: 'Opsgenie',
|
||||
},
|
||||
]);
|
||||
const connector = await getConnectorByName(connectorName, supertest);
|
||||
objectRemover.add(connector.id, 'action', 'actions');
|
||||
});
|
||||
|
||||
it('should edit the connector', async () => {
|
||||
const connectorName = generateUniqueKey();
|
||||
const updatedConnectorName = `${connectorName}updated`;
|
||||
const createdAction = await createOpsgenieConnector(connectorName);
|
||||
objectRemover.add(createdAction.id, 'action', 'actions');
|
||||
browser.refresh();
|
||||
|
||||
await pageObjects.triggersActionsUI.searchConnectors(connectorName);
|
||||
|
||||
const searchResultsBeforeEdit = await pageObjects.triggersActionsUI.getConnectorsList();
|
||||
expect(searchResultsBeforeEdit.length).to.eql(1);
|
||||
|
||||
await find.clickByCssSelector('[data-test-subj="connectorsTableCell-name"] button');
|
||||
await actions.opsgenie.updateConnectorFields({
|
||||
name: updatedConnectorName,
|
||||
apiUrl: 'https://test.com',
|
||||
apiKey: 'apiKey',
|
||||
});
|
||||
|
||||
const toastTitle = await pageObjects.common.closeToast();
|
||||
expect(toastTitle).to.eql(`Updated '${updatedConnectorName}'`);
|
||||
|
||||
await testSubjects.click('euiFlyoutCloseButton');
|
||||
await pageObjects.triggersActionsUI.searchConnectors(updatedConnectorName);
|
||||
|
||||
const searchResultsAfterEdit = await pageObjects.triggersActionsUI.getConnectorsList();
|
||||
expect(searchResultsAfterEdit).to.eql([
|
||||
{
|
||||
name: updatedConnectorName,
|
||||
actionType: 'Opsgenie',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should reset connector when canceling an edit', async () => {
|
||||
const connectorName = generateUniqueKey();
|
||||
const createdAction = await createOpsgenieConnector(connectorName);
|
||||
objectRemover.add(createdAction.id, 'action', 'actions');
|
||||
browser.refresh();
|
||||
|
||||
await pageObjects.triggersActionsUI.searchConnectors(connectorName);
|
||||
|
||||
const searchResultsBeforeEdit = await pageObjects.triggersActionsUI.getConnectorsList();
|
||||
expect(searchResultsBeforeEdit.length).to.eql(1);
|
||||
|
||||
await find.clickByCssSelector('[data-test-subj="connectorsTableCell-name"] button');
|
||||
|
||||
await testSubjects.setValue('nameInput', 'some test name to cancel');
|
||||
await testSubjects.click('edit-connector-flyout-close-btn');
|
||||
await testSubjects.click('confirmModalConfirmButton');
|
||||
|
||||
await find.waitForDeletedByCssSelector(
|
||||
'[data-test-subj="edit-connector-flyout-close-btn"]'
|
||||
);
|
||||
|
||||
await pageObjects.triggersActionsUI.searchConnectors(connectorName);
|
||||
|
||||
await find.clickByCssSelector('[data-test-subj="connectorsTableCell-name"] button');
|
||||
expect(await testSubjects.getAttribute('nameInput', 'value')).to.eql(connectorName);
|
||||
await testSubjects.click('euiFlyoutCloseButton');
|
||||
});
|
||||
|
||||
it('should disable the run button when the message field is not filled', async () => {
|
||||
const connectorName = generateUniqueKey();
|
||||
const createdAction = await createOpsgenieConnector(connectorName);
|
||||
objectRemover.add(createdAction.id, 'action', 'actions');
|
||||
browser.refresh();
|
||||
|
||||
await pageObjects.triggersActionsUI.searchConnectors(connectorName);
|
||||
|
||||
const searchResultsBeforeEdit = await pageObjects.triggersActionsUI.getConnectorsList();
|
||||
expect(searchResultsBeforeEdit.length).to.eql(1);
|
||||
|
||||
await find.clickByCssSelector('[data-test-subj="connectorsTableCell-name"] button');
|
||||
|
||||
await find.clickByCssSelector('[data-test-subj="testConnectorTab"]');
|
||||
|
||||
expect(await (await testSubjects.find('executeActionButton')).isEnabled()).to.be(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('alerts page', () => {
|
||||
const defaultAlias = '{{rule.id}}:{{alert.id}}';
|
||||
const connectorName = generateUniqueKey();
|
||||
|
||||
before(async () => {
|
||||
const createdAction = await createOpsgenieConnector(connectorName);
|
||||
objectRemover.add(createdAction.id, 'action', 'actions');
|
||||
|
||||
await pageObjects.common.navigateToApp('triggersActions');
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await setupRule();
|
||||
await selectOpsgenieConnectorInRuleAction(connectorName);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await rules.common.cancelRuleCreation();
|
||||
});
|
||||
|
||||
it('should default to the create alert action', async () => {
|
||||
expect(await testSubjects.getAttribute('opsgenie-subActionSelect', 'value')).to.eql(
|
||||
'createAlert'
|
||||
);
|
||||
|
||||
expect(await testSubjects.getAttribute('aliasInput', 'value')).to.eql(defaultAlias);
|
||||
});
|
||||
|
||||
it('should default to the close alert action when setting the run when to recovered', async () => {
|
||||
await testSubjects.click('addNewActionConnectorActionGroup-0');
|
||||
await testSubjects.click('addNewActionConnectorActionGroup-0-option-recovered');
|
||||
|
||||
expect(await testSubjects.getAttribute('opsgenie-subActionSelect', 'value')).to.eql(
|
||||
'closeAlert'
|
||||
);
|
||||
expect(await testSubjects.getAttribute('aliasInput', 'value')).to.eql(defaultAlias);
|
||||
});
|
||||
|
||||
it('should preserve the alias when switching between create and close alert actions', async () => {
|
||||
await testSubjects.setValue('aliasInput', 'new alias');
|
||||
await testSubjects.selectValue('opsgenie-subActionSelect', 'closeAlert');
|
||||
|
||||
expect(await testSubjects.getAttribute('opsgenie-subActionSelect', 'value')).to.be(
|
||||
'closeAlert'
|
||||
);
|
||||
expect(await testSubjects.getAttribute('aliasInput', 'value')).to.be('new alias');
|
||||
});
|
||||
|
||||
it('should not preserve the message when switching to close alert and back to create alert', async () => {
|
||||
await testSubjects.setValue('messageInput', 'a message');
|
||||
await testSubjects.selectValue('opsgenie-subActionSelect', 'closeAlert');
|
||||
|
||||
await testSubjects.missingOrFail('messageInput');
|
||||
await retry.waitFor('message input to be displayed', async () => {
|
||||
await testSubjects.selectValue('opsgenie-subActionSelect', 'createAlert');
|
||||
return await testSubjects.exists('messageInput');
|
||||
});
|
||||
|
||||
expect(await testSubjects.getAttribute('messageInput', 'value')).to.be('');
|
||||
});
|
||||
|
||||
it('should not preserve the alias when switching run when to recover', async () => {
|
||||
await testSubjects.setValue('aliasInput', 'an alias');
|
||||
await testSubjects.click('addNewActionConnectorActionGroup-0');
|
||||
await testSubjects.click('addNewActionConnectorActionGroup-0-option-recovered');
|
||||
|
||||
await testSubjects.missingOrFail('messageInput');
|
||||
|
||||
expect(await testSubjects.getAttribute('aliasInput', 'value')).to.be(defaultAlias);
|
||||
});
|
||||
|
||||
it('should not preserve the alias when switching run when to threshold met', async () => {
|
||||
await testSubjects.click('addNewActionConnectorActionGroup-0');
|
||||
await testSubjects.click('addNewActionConnectorActionGroup-0-option-recovered');
|
||||
await testSubjects.missingOrFail('messageInput');
|
||||
|
||||
await testSubjects.setValue('aliasInput', 'an alias');
|
||||
await testSubjects.click('addNewActionConnectorActionGroup-0');
|
||||
await testSubjects.click('addNewActionConnectorActionGroup-0-option-threshold met');
|
||||
await testSubjects.exists('messageInput');
|
||||
|
||||
expect(await testSubjects.getAttribute('aliasInput', 'value')).to.be(defaultAlias);
|
||||
});
|
||||
});
|
||||
|
||||
const setupRule = async () => {
|
||||
const alertName = generateUniqueKey();
|
||||
await retry.try(async () => {
|
||||
await rules.common.defineIndexThresholdAlert(alertName);
|
||||
});
|
||||
|
||||
await rules.common.setNotifyThrottleInput();
|
||||
};
|
||||
|
||||
const selectOpsgenieConnectorInRuleAction = async (name: string) => {
|
||||
await testSubjects.click('.opsgenie-alerting-ActionTypeSelectOption');
|
||||
await testSubjects.selectValue('comboBoxInput', name);
|
||||
};
|
||||
|
||||
const createOpsgenieConnector = async (name: string) => {
|
||||
return createConnector({
|
||||
name,
|
||||
config: { apiUrl: 'https//test.com' },
|
||||
secrets: { apiKey: '1234' },
|
||||
connectorTypeId: '.opsgenie',
|
||||
supertest,
|
||||
});
|
||||
};
|
||||
});
|
||||
};
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* 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 type SuperTest from 'supertest';
|
||||
import { findIndex } from 'lodash';
|
||||
|
||||
import { ObjectRemover } from '../../../lib/object_remover';
|
||||
import { FtrProviderContext } from '../../../ftr_provider_context';
|
||||
import { getTestActionData } from '../../../lib/get_test_data';
|
||||
|
||||
export const createSlackConnectorAndObjectRemover = async ({
|
||||
getService,
|
||||
}: {
|
||||
getService: FtrProviderContext['getService'];
|
||||
}) => {
|
||||
const supertest = getService('supertest');
|
||||
const objectRemover = new ObjectRemover(supertest);
|
||||
|
||||
const testData = getTestActionData();
|
||||
const createdAction = await createSlackConnector({
|
||||
name: testData.name,
|
||||
supertest,
|
||||
});
|
||||
objectRemover.add(createdAction.id, 'action', 'actions');
|
||||
|
||||
return objectRemover;
|
||||
};
|
||||
|
||||
export const createSlackConnector = async ({
|
||||
name,
|
||||
supertest,
|
||||
}: {
|
||||
name: string;
|
||||
supertest: SuperTest.SuperTest<SuperTest.Test>;
|
||||
}) => {
|
||||
const connector = await createConnector({
|
||||
name,
|
||||
config: {},
|
||||
secrets: { webhookUrl: 'https://test.com' },
|
||||
connectorTypeId: '.slack',
|
||||
supertest,
|
||||
});
|
||||
|
||||
return connector;
|
||||
};
|
||||
|
||||
export const getConnectorByName = async (
|
||||
name: string,
|
||||
supertest: SuperTest.SuperTest<SuperTest.Test>
|
||||
) => {
|
||||
const { body } = await supertest
|
||||
.get(`/api/actions/connectors`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.expect(200);
|
||||
const i = findIndex(body, (c: any) => c.name === name);
|
||||
return body[i];
|
||||
};
|
||||
|
||||
export const createConnector = async ({
|
||||
name,
|
||||
config,
|
||||
secrets,
|
||||
connectorTypeId,
|
||||
supertest,
|
||||
}: {
|
||||
name: string;
|
||||
config: Record<string, unknown>;
|
||||
secrets: Record<string, unknown>;
|
||||
connectorTypeId: string;
|
||||
supertest: SuperTest.SuperTest<SuperTest.Test>;
|
||||
}) => {
|
||||
const { body: createdAction } = await supertest
|
||||
.post(`/api/actions/connector`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({
|
||||
name,
|
||||
config,
|
||||
secrets,
|
||||
connector_type_id: connectorTypeId,
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
return createdAction;
|
||||
};
|
|
@ -13,6 +13,7 @@ import { pageObjects } from './page_objects';
|
|||
|
||||
// .server-log is specifically not enabled
|
||||
const enabledActionTypes = [
|
||||
'.opsgenie',
|
||||
'.email',
|
||||
'.index',
|
||||
'.pagerduty',
|
||||
|
|
|
@ -18,6 +18,7 @@ export function TriggersActionsPageProvider({ getService }: FtrProviderContext)
|
|||
const find = getService('find');
|
||||
const retry = getService('retry');
|
||||
const testSubjects = getService('testSubjects');
|
||||
const rules = getService('rules');
|
||||
|
||||
function getRowItemData(row: CustomCheerio, $: CustomCheerioStatic) {
|
||||
return {
|
||||
|
@ -164,10 +165,7 @@ export function TriggersActionsPageProvider({ getService }: FtrProviderContext)
|
|||
await switchBtn.click();
|
||||
},
|
||||
async clickCreateAlertButton() {
|
||||
const createBtn = await find.byCssSelector(
|
||||
'[data-test-subj="createRuleButton"],[data-test-subj="createFirstRuleButton"]'
|
||||
);
|
||||
await createBtn.click();
|
||||
await rules.common.clickCreateAlertButton();
|
||||
},
|
||||
async setAlertName(value: string) {
|
||||
await testSubjects.setValue('ruleNameInput', value);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue