[Alerting UI] Alert and Connector flyouts Save and Save&Test buttons should be active by default. (#86708) (#87380)
* Alert and Connector flyouts Save and Save&Test buttons should be active by default. * fixed typechecks * fixed typechecks * refactored repeted code * fixed typechecks * fixed typechecks * fixed typechecks * fixed due to comments * fixed failing tests * fixed due to comments * fixed due to comments * fixed due to comments * fixed typescript checks
This commit is contained in:
parent
abdd93b84a
commit
fbc0d59d78
|
@ -15,7 +15,12 @@ import { act } from 'react-dom/test-utils';
|
|||
import { coreMock } from 'src/core/public/mocks';
|
||||
import { actionTypeRegistryMock } from '../../../triggers_actions_ui/public/application/action_type_registry.mock';
|
||||
import { alertTypeRegistryMock } from '../../../triggers_actions_ui/public/application/alert_type_registry.mock';
|
||||
import { ValidationResult, Alert } from '../../../triggers_actions_ui/public/types';
|
||||
import {
|
||||
ValidationResult,
|
||||
Alert,
|
||||
ConnectorValidationResult,
|
||||
GenericValidationResult,
|
||||
} from '../../../triggers_actions_ui/public/types';
|
||||
import { AlertForm } from '../../../triggers_actions_ui/public/application/sections/alert_form/alert_form';
|
||||
import ActionForm from '../../../triggers_actions_ui/public/application/sections/action_connector_form/action_form';
|
||||
import { Legacy } from '../legacy_shims';
|
||||
|
@ -88,8 +93,13 @@ describe('alert_form', () => {
|
|||
id: 'alert-action-type',
|
||||
iconClass: '',
|
||||
selectMessage: '',
|
||||
validateConnector: validationMethod,
|
||||
validateParams: validationMethod,
|
||||
validateConnector: (): ConnectorValidationResult<unknown, unknown> => {
|
||||
return {};
|
||||
},
|
||||
validateParams: (): GenericValidationResult<unknown> => {
|
||||
const validationResult = { errors: {} };
|
||||
return validationResult;
|
||||
},
|
||||
actionConnectorFields: null,
|
||||
actionParamsFields: mockedActionParamsFields,
|
||||
};
|
||||
|
|
|
@ -32,7 +32,7 @@ export function getActionType(): ActionTypeModel {
|
|||
iconClass: 'securityAnalyticsApp',
|
||||
selectMessage: i18n.CASE_CONNECTOR_DESC,
|
||||
actionTypeTitle: i18n.CASE_CONNECTOR_TITLE,
|
||||
validateConnector: () => ({ errors: {} }),
|
||||
validateConnector: () => ({ config: { errors: {} }, secrets: { errors: {} } }),
|
||||
validateParams,
|
||||
actionConnectorFields: null,
|
||||
actionParamsFields: lazy(() => import('./fields')),
|
||||
|
|
|
@ -253,7 +253,7 @@ export const IndexThresholdAlertTypeExpression: React.FunctionComponent<
|
|||
fullWidth
|
||||
name="thresholdTimeField"
|
||||
data-test-subj="thresholdAlertTimeFieldSelect"
|
||||
value={timeField}
|
||||
value={timeField || ''}
|
||||
onChange={(e) => {
|
||||
setAlertParams('timeField', e.target.value);
|
||||
}}
|
||||
|
|
|
@ -48,12 +48,18 @@ describe('connector validation', () => {
|
|||
} as EmailActionConnector;
|
||||
|
||||
expect(actionTypeModel.validateConnector(actionConnector)).toEqual({
|
||||
errors: {
|
||||
from: [],
|
||||
port: [],
|
||||
host: [],
|
||||
user: [],
|
||||
password: [],
|
||||
config: {
|
||||
errors: {
|
||||
from: [],
|
||||
port: [],
|
||||
host: [],
|
||||
},
|
||||
},
|
||||
secrets: {
|
||||
errors: {
|
||||
user: [],
|
||||
password: [],
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
@ -78,12 +84,18 @@ describe('connector validation', () => {
|
|||
} as EmailActionConnector;
|
||||
|
||||
expect(actionTypeModel.validateConnector(actionConnector)).toEqual({
|
||||
errors: {
|
||||
from: [],
|
||||
port: [],
|
||||
host: [],
|
||||
user: [],
|
||||
password: [],
|
||||
config: {
|
||||
errors: {
|
||||
from: [],
|
||||
port: [],
|
||||
host: [],
|
||||
},
|
||||
},
|
||||
secrets: {
|
||||
errors: {
|
||||
user: [],
|
||||
password: [],
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
@ -103,12 +115,18 @@ describe('connector validation', () => {
|
|||
} as EmailActionConnector;
|
||||
|
||||
expect(actionTypeModel.validateConnector(actionConnector)).toEqual({
|
||||
errors: {
|
||||
from: [],
|
||||
port: ['Port is required.'],
|
||||
host: ['Host is required.'],
|
||||
user: [],
|
||||
password: [],
|
||||
config: {
|
||||
errors: {
|
||||
from: [],
|
||||
port: ['Port is required.'],
|
||||
host: ['Host is required.'],
|
||||
},
|
||||
},
|
||||
secrets: {
|
||||
errors: {
|
||||
user: [],
|
||||
password: [],
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
@ -132,12 +150,18 @@ describe('connector validation', () => {
|
|||
} as EmailActionConnector;
|
||||
|
||||
expect(actionTypeModel.validateConnector(actionConnector)).toEqual({
|
||||
errors: {
|
||||
from: [],
|
||||
port: [],
|
||||
host: [],
|
||||
user: [],
|
||||
password: ['Password is required when username is used.'],
|
||||
config: {
|
||||
errors: {
|
||||
from: [],
|
||||
port: [],
|
||||
host: [],
|
||||
},
|
||||
},
|
||||
secrets: {
|
||||
errors: {
|
||||
user: [],
|
||||
password: ['Password is required when username is used.'],
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
@ -161,12 +185,18 @@ describe('connector validation', () => {
|
|||
} as EmailActionConnector;
|
||||
|
||||
expect(actionTypeModel.validateConnector(actionConnector)).toEqual({
|
||||
errors: {
|
||||
from: [],
|
||||
port: [],
|
||||
host: [],
|
||||
user: ['Username is required when password is used.'],
|
||||
password: [],
|
||||
config: {
|
||||
errors: {
|
||||
from: [],
|
||||
port: [],
|
||||
host: [],
|
||||
},
|
||||
},
|
||||
secrets: {
|
||||
errors: {
|
||||
user: ['Username is required when password is used.'],
|
||||
password: [],
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,7 +5,11 @@
|
|||
*/
|
||||
import { lazy } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { ActionTypeModel, ValidationResult } from '../../../../types';
|
||||
import {
|
||||
ActionTypeModel,
|
||||
ConnectorValidationResult,
|
||||
GenericValidationResult,
|
||||
} from '../../../../types';
|
||||
import { EmailActionParams, EmailConfig, EmailSecrets, EmailActionConnector } from '../types';
|
||||
|
||||
export function getActionType(): ActionTypeModel<EmailConfig, EmailSecrets, EmailActionParams> {
|
||||
|
@ -25,18 +29,25 @@ export function getActionType(): ActionTypeModel<EmailConfig, EmailSecrets, Emai
|
|||
defaultMessage: 'Send to email',
|
||||
}
|
||||
),
|
||||
validateConnector: (action: EmailActionConnector): ValidationResult => {
|
||||
const validationResult = { errors: {} };
|
||||
const errors = {
|
||||
validateConnector: (
|
||||
action: EmailActionConnector
|
||||
): ConnectorValidationResult<Omit<EmailConfig, 'secure' | 'hasAuth'>, EmailSecrets> => {
|
||||
const configErrors = {
|
||||
from: new Array<string>(),
|
||||
port: new Array<string>(),
|
||||
host: new Array<string>(),
|
||||
};
|
||||
const secretsErrors = {
|
||||
user: new Array<string>(),
|
||||
password: new Array<string>(),
|
||||
};
|
||||
validationResult.errors = errors;
|
||||
|
||||
const validationResult = {
|
||||
config: { errors: configErrors },
|
||||
secrets: { errors: secretsErrors },
|
||||
};
|
||||
if (!action.config.from) {
|
||||
errors.from.push(
|
||||
configErrors.from.push(
|
||||
i18n.translate(
|
||||
'xpack.triggersActionsUI.components.builtinActionTypes.error.requiredFromText',
|
||||
{
|
||||
|
@ -46,7 +57,7 @@ export function getActionType(): ActionTypeModel<EmailConfig, EmailSecrets, Emai
|
|||
);
|
||||
}
|
||||
if (action.config.from && !action.config.from.trim().match(mailformat)) {
|
||||
errors.from.push(
|
||||
configErrors.from.push(
|
||||
i18n.translate(
|
||||
'xpack.triggersActionsUI.components.builtinActionTypes.error.formatFromText',
|
||||
{
|
||||
|
@ -56,7 +67,7 @@ export function getActionType(): ActionTypeModel<EmailConfig, EmailSecrets, Emai
|
|||
);
|
||||
}
|
||||
if (!action.config.port) {
|
||||
errors.port.push(
|
||||
configErrors.port.push(
|
||||
i18n.translate(
|
||||
'xpack.triggersActionsUI.components.builtinActionTypes.error.requiredPortText',
|
||||
{
|
||||
|
@ -66,7 +77,7 @@ export function getActionType(): ActionTypeModel<EmailConfig, EmailSecrets, Emai
|
|||
);
|
||||
}
|
||||
if (!action.config.host) {
|
||||
errors.host.push(
|
||||
configErrors.host.push(
|
||||
i18n.translate(
|
||||
'xpack.triggersActionsUI.components.builtinActionTypes.error.requiredHostText',
|
||||
{
|
||||
|
@ -76,7 +87,7 @@ export function getActionType(): ActionTypeModel<EmailConfig, EmailSecrets, Emai
|
|||
);
|
||||
}
|
||||
if (action.config.hasAuth && !action.secrets.user && !action.secrets.password) {
|
||||
errors.user.push(
|
||||
secretsErrors.user.push(
|
||||
i18n.translate(
|
||||
'xpack.triggersActionsUI.components.builtinActionTypes.error.requiredAuthUserNameText',
|
||||
{
|
||||
|
@ -86,7 +97,7 @@ export function getActionType(): ActionTypeModel<EmailConfig, EmailSecrets, Emai
|
|||
);
|
||||
}
|
||||
if (action.config.hasAuth && !action.secrets.user && !action.secrets.password) {
|
||||
errors.password.push(
|
||||
secretsErrors.password.push(
|
||||
i18n.translate(
|
||||
'xpack.triggersActionsUI.components.builtinActionTypes.error.requiredAuthPasswordText',
|
||||
{
|
||||
|
@ -96,7 +107,7 @@ export function getActionType(): ActionTypeModel<EmailConfig, EmailSecrets, Emai
|
|||
);
|
||||
}
|
||||
if (action.secrets.user && !action.secrets.password) {
|
||||
errors.password.push(
|
||||
secretsErrors.password.push(
|
||||
i18n.translate(
|
||||
'xpack.triggersActionsUI.components.builtinActionTypes.error.requiredPasswordText',
|
||||
{
|
||||
|
@ -106,7 +117,7 @@ export function getActionType(): ActionTypeModel<EmailConfig, EmailSecrets, Emai
|
|||
);
|
||||
}
|
||||
if (!action.secrets.user && action.secrets.password) {
|
||||
errors.user.push(
|
||||
secretsErrors.user.push(
|
||||
i18n.translate(
|
||||
'xpack.triggersActionsUI.components.builtinActionTypes.error.requiredUserText',
|
||||
{
|
||||
|
@ -117,8 +128,9 @@ export function getActionType(): ActionTypeModel<EmailConfig, EmailSecrets, Emai
|
|||
}
|
||||
return validationResult;
|
||||
},
|
||||
validateParams: (actionParams: EmailActionParams): ValidationResult => {
|
||||
const validationResult = { errors: {} };
|
||||
validateParams: (
|
||||
actionParams: EmailActionParams
|
||||
): GenericValidationResult<EmailActionParams> => {
|
||||
const errors = {
|
||||
to: new Array<string>(),
|
||||
cc: new Array<string>(),
|
||||
|
@ -126,7 +138,7 @@ export function getActionType(): ActionTypeModel<EmailConfig, EmailSecrets, Emai
|
|||
message: new Array<string>(),
|
||||
subject: new Array<string>(),
|
||||
};
|
||||
validationResult.errors = errors;
|
||||
const validationResult = { errors };
|
||||
if (
|
||||
(!(actionParams.to instanceof Array) || actionParams.to.length === 0) &&
|
||||
(!(actionParams.cc instanceof Array) || actionParams.cc.length === 0) &&
|
||||
|
|
|
@ -192,7 +192,7 @@ export const EmailActionConnectorFields: React.FunctionComponent<
|
|||
}
|
||||
)}
|
||||
disabled={readOnly}
|
||||
checked={hasAuth}
|
||||
checked={hasAuth || false}
|
||||
onChange={(e) => {
|
||||
editActionConfig('hasAuth', e.target.checked);
|
||||
if (!e.target.checked) {
|
||||
|
|
|
@ -42,8 +42,13 @@ describe('index connector validation', () => {
|
|||
} as EsIndexActionConnector;
|
||||
|
||||
expect(actionTypeModel.validateConnector(actionConnector)).toEqual({
|
||||
errors: {
|
||||
index: [],
|
||||
config: {
|
||||
errors: {
|
||||
index: [],
|
||||
},
|
||||
},
|
||||
secrets: {
|
||||
errors: {},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
@ -62,8 +67,13 @@ describe('index connector validation with minimal config', () => {
|
|||
} as EsIndexActionConnector;
|
||||
|
||||
expect(actionTypeModel.validateConnector(actionConnector)).toEqual({
|
||||
errors: {
|
||||
index: [],
|
||||
config: {
|
||||
errors: {
|
||||
index: [],
|
||||
},
|
||||
},
|
||||
secrets: {
|
||||
errors: {},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,7 +5,11 @@
|
|||
*/
|
||||
import { lazy } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { ActionTypeModel, ValidationResult } from '../../../../types';
|
||||
import {
|
||||
ActionTypeModel,
|
||||
GenericValidationResult,
|
||||
ConnectorValidationResult,
|
||||
} from '../../../../types';
|
||||
import { EsIndexActionConnector, EsIndexConfig, IndexActionParams } from '../types';
|
||||
|
||||
export function getActionType(): ActionTypeModel<EsIndexConfig, unknown, IndexActionParams> {
|
||||
|
@ -24,14 +28,15 @@ export function getActionType(): ActionTypeModel<EsIndexConfig, unknown, IndexAc
|
|||
defaultMessage: 'Index data',
|
||||
}
|
||||
),
|
||||
validateConnector: (action: EsIndexActionConnector): ValidationResult => {
|
||||
const validationResult = { errors: {} };
|
||||
const errors = {
|
||||
validateConnector: (
|
||||
action: EsIndexActionConnector
|
||||
): ConnectorValidationResult<Pick<EsIndexConfig, 'index'>, unknown> => {
|
||||
const configErrors = {
|
||||
index: new Array<string>(),
|
||||
};
|
||||
validationResult.errors = errors;
|
||||
const validationResult = { config: { errors: configErrors }, secrets: { errors: {} } };
|
||||
if (!action.config.index) {
|
||||
errors.index.push(
|
||||
configErrors.index.push(
|
||||
i18n.translate(
|
||||
'xpack.triggersActionsUI.components.builtinActionTypes.indexAction.error.requiredIndexText',
|
||||
{
|
||||
|
@ -44,12 +49,13 @@ export function getActionType(): ActionTypeModel<EsIndexConfig, unknown, IndexAc
|
|||
},
|
||||
actionConnectorFields: lazy(() => import('./es_index_connector')),
|
||||
actionParamsFields: lazy(() => import('./es_index_params')),
|
||||
validateParams: (actionParams: IndexActionParams): ValidationResult => {
|
||||
const validationResult = { errors: {} };
|
||||
validateParams: (
|
||||
actionParams: IndexActionParams
|
||||
): GenericValidationResult<IndexActionParams> => {
|
||||
const errors = {
|
||||
documents: new Array<string>(),
|
||||
};
|
||||
validationResult.errors = errors;
|
||||
const validationResult = { errors };
|
||||
if (!actionParams.documents?.length || Object.keys(actionParams.documents[0]).length === 0) {
|
||||
errors.documents.push(
|
||||
i18n.translate(
|
||||
|
|
|
@ -38,7 +38,11 @@ export const IndexParamsFields = ({
|
|||
paramsProperty={'documents'}
|
||||
data-test-subj="documentToIndex"
|
||||
inputTargetValue={
|
||||
documents && documents.length > 0 ? ((documents[0] as unknown) as string) : undefined
|
||||
documents === null
|
||||
? '{}' // need this to trigger validation
|
||||
: documents && documents.length > 0
|
||||
? ((documents[0] as unknown) as string)
|
||||
: undefined
|
||||
}
|
||||
label={i18n.translate(
|
||||
'xpack.triggersActionsUI.components.builtinActionTypes.indexAction.documentsFieldLabel',
|
||||
|
|
|
@ -44,11 +44,17 @@ describe('jira connector validation', () => {
|
|||
} as JiraActionConnector;
|
||||
|
||||
expect(actionTypeModel.validateConnector(actionConnector)).toEqual({
|
||||
errors: {
|
||||
apiUrl: [],
|
||||
email: [],
|
||||
apiToken: [],
|
||||
projectKey: [],
|
||||
config: {
|
||||
errors: {
|
||||
apiUrl: [],
|
||||
projectKey: [],
|
||||
},
|
||||
},
|
||||
secrets: {
|
||||
errors: {
|
||||
apiToken: [],
|
||||
email: [],
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
@ -65,11 +71,17 @@ describe('jira connector validation', () => {
|
|||
} as unknown) as JiraActionConnector;
|
||||
|
||||
expect(actionTypeModel.validateConnector(actionConnector)).toEqual({
|
||||
errors: {
|
||||
apiUrl: ['URL is required.'],
|
||||
email: [],
|
||||
apiToken: ['API token or password is required'],
|
||||
projectKey: ['Project key is required'],
|
||||
config: {
|
||||
errors: {
|
||||
apiUrl: ['URL is required.'],
|
||||
projectKey: ['Project key is required'],
|
||||
},
|
||||
},
|
||||
secrets: {
|
||||
errors: {
|
||||
apiToken: ['API token or password is required'],
|
||||
email: [],
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
@ -82,7 +94,7 @@ describe('jira action params validation', () => {
|
|||
};
|
||||
|
||||
expect(actionTypeModel.validateParams(actionParams)).toEqual({
|
||||
errors: { summary: [] },
|
||||
errors: { 'subActionParams.incident.summary': [] },
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -93,7 +105,7 @@ describe('jira action params validation', () => {
|
|||
|
||||
expect(actionTypeModel.validateParams(actionParams)).toEqual({
|
||||
errors: {
|
||||
summary: ['Summary is required.'],
|
||||
'subActionParams.incident.summary': ['Summary is required.'],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,46 +5,56 @@
|
|||
*/
|
||||
|
||||
import { lazy } from 'react';
|
||||
import { ValidationResult, ActionTypeModel } from '../../../../types';
|
||||
import {
|
||||
GenericValidationResult,
|
||||
ActionTypeModel,
|
||||
ConnectorValidationResult,
|
||||
} from '../../../../types';
|
||||
import { connectorConfiguration } from './config';
|
||||
import logo from './logo.svg';
|
||||
import { JiraActionConnector, JiraConfig, JiraSecrets, JiraActionParams } from './types';
|
||||
import * as i18n from './translations';
|
||||
import { isValidUrl } from '../../../lib/value_validators';
|
||||
|
||||
const validateConnector = (action: JiraActionConnector): ValidationResult => {
|
||||
const validationResult = {
|
||||
errors: {
|
||||
apiUrl: new Array<string>(),
|
||||
projectKey: new Array<string>(),
|
||||
email: new Array<string>(),
|
||||
apiToken: new Array<string>(),
|
||||
},
|
||||
const validateConnector = (
|
||||
action: JiraActionConnector
|
||||
): ConnectorValidationResult<JiraConfig, JiraSecrets> => {
|
||||
const configErrors = {
|
||||
apiUrl: new Array<string>(),
|
||||
projectKey: new Array<string>(),
|
||||
};
|
||||
const secretsErrors = {
|
||||
email: new Array<string>(),
|
||||
apiToken: new Array<string>(),
|
||||
};
|
||||
|
||||
const validationResult = {
|
||||
config: { errors: configErrors },
|
||||
secrets: { errors: secretsErrors },
|
||||
};
|
||||
const { errors } = validationResult;
|
||||
|
||||
if (!action.config.apiUrl) {
|
||||
errors.apiUrl = [...errors.apiUrl, i18n.API_URL_REQUIRED];
|
||||
configErrors.apiUrl = [...configErrors.apiUrl, i18n.API_URL_REQUIRED];
|
||||
}
|
||||
|
||||
if (action.config.apiUrl) {
|
||||
if (!isValidUrl(action.config.apiUrl)) {
|
||||
errors.apiUrl = [...errors.apiUrl, i18n.API_URL_INVALID];
|
||||
configErrors.apiUrl = [...configErrors.apiUrl, i18n.API_URL_INVALID];
|
||||
} else if (!isValidUrl(action.config.apiUrl, 'https:')) {
|
||||
errors.apiUrl = [...errors.apiUrl, i18n.API_URL_REQUIRE_HTTPS];
|
||||
configErrors.apiUrl = [...configErrors.apiUrl, i18n.API_URL_REQUIRE_HTTPS];
|
||||
}
|
||||
}
|
||||
|
||||
if (!action.config.projectKey) {
|
||||
errors.projectKey = [...errors.projectKey, i18n.JIRA_PROJECT_KEY_REQUIRED];
|
||||
configErrors.projectKey = [...configErrors.projectKey, i18n.JIRA_PROJECT_KEY_REQUIRED];
|
||||
}
|
||||
|
||||
if (!action.secrets.email) {
|
||||
errors.email = [...errors.email, i18n.JIRA_EMAIL_REQUIRED];
|
||||
secretsErrors.email = [...secretsErrors.email, i18n.JIRA_EMAIL_REQUIRED];
|
||||
}
|
||||
|
||||
if (!action.secrets.apiToken) {
|
||||
errors.apiToken = [...errors.apiToken, i18n.JIRA_API_TOKEN_REQUIRED];
|
||||
secretsErrors.apiToken = [...secretsErrors.apiToken, i18n.JIRA_API_TOKEN_REQUIRED];
|
||||
}
|
||||
|
||||
return validationResult;
|
||||
|
@ -58,18 +68,19 @@ export function getActionType(): ActionTypeModel<JiraConfig, JiraSecrets, JiraAc
|
|||
actionTypeTitle: connectorConfiguration.name,
|
||||
validateConnector,
|
||||
actionConnectorFields: lazy(() => import('./jira_connectors')),
|
||||
validateParams: (actionParams: JiraActionParams): ValidationResult => {
|
||||
const validationResult = { errors: {} };
|
||||
validateParams: (actionParams: JiraActionParams): GenericValidationResult<unknown> => {
|
||||
const errors = {
|
||||
summary: new Array<string>(),
|
||||
'subActionParams.incident.summary': new Array<string>(),
|
||||
};
|
||||
const validationResult = {
|
||||
errors,
|
||||
};
|
||||
validationResult.errors = errors;
|
||||
if (
|
||||
actionParams.subActionParams &&
|
||||
actionParams.subActionParams.incident &&
|
||||
!actionParams.subActionParams.incident.summary?.length
|
||||
) {
|
||||
errors.summary.push(i18n.SUMMARY_REQUIRED);
|
||||
errors['subActionParams.incident.summary'].push(i18n.SUMMARY_REQUIRED);
|
||||
}
|
||||
return validationResult;
|
||||
},
|
||||
|
|
|
@ -31,13 +31,13 @@ const JiraConnectorFields: React.FC<ActionConnectorFieldsProps<JiraActionConnect
|
|||
}) => {
|
||||
const { apiUrl, projectKey } = action.config;
|
||||
|
||||
const isApiUrlInvalid: boolean = errors.apiUrl.length > 0 && apiUrl != null;
|
||||
const isApiUrlInvalid: boolean = errors.apiUrl.length > 0 && apiUrl !== undefined;
|
||||
|
||||
const { email, apiToken } = action.secrets;
|
||||
|
||||
const isProjectKeyInvalid: boolean = errors.projectKey.length > 0 && projectKey != null;
|
||||
const isEmailInvalid: boolean = errors.email.length > 0 && email != null;
|
||||
const isApiTokenInvalid: boolean = errors.apiToken.length > 0 && apiToken != null;
|
||||
const isProjectKeyInvalid: boolean = errors.projectKey.length > 0 && projectKey !== undefined;
|
||||
const isEmailInvalid: boolean = errors.email.length > 0 && email !== undefined;
|
||||
const isApiTokenInvalid: boolean = errors.apiToken.length > 0 && apiToken !== undefined;
|
||||
|
||||
const handleOnChangeActionConfig = useCallback(
|
||||
(key: string, value: string) => editActionConfig(key, value),
|
||||
|
|
|
@ -48,7 +48,7 @@ const defaultProps = {
|
|||
actionConnector: connector,
|
||||
actionParams,
|
||||
editAction,
|
||||
errors: { summary: [] },
|
||||
errors: { 'subActionParams.incident.summary': [] },
|
||||
index: 0,
|
||||
messageVariables: [],
|
||||
};
|
||||
|
@ -244,7 +244,7 @@ describe('JiraParamsFields renders', () => {
|
|||
test('If summary has errors, form row is invalid', () => {
|
||||
const newProps = {
|
||||
...defaultProps,
|
||||
errors: { summary: ['error'] },
|
||||
errors: { 'subActionParams.incident.summary': ['error'] },
|
||||
};
|
||||
const wrapper = mount(<JiraParamsFields {...newProps} />);
|
||||
const summary = wrapper.find('[data-test-subj="summary-row"]').first();
|
||||
|
|
|
@ -249,8 +249,11 @@ const JiraParamsFields: React.FunctionComponent<ActionParamsProps<JiraActionPara
|
|||
<EuiFormRow
|
||||
data-test-subj="summary-row"
|
||||
fullWidth
|
||||
error={errors.summary}
|
||||
isInvalid={errors.summary.length > 0 && incident.summary !== undefined}
|
||||
error={errors['subActionParams.incident.summary']}
|
||||
isInvalid={
|
||||
errors['subActionParams.incident.summary'].length > 0 &&
|
||||
incident.summary !== undefined
|
||||
}
|
||||
label={i18n.translate(
|
||||
'xpack.triggersActionsUI.components.builtinActionTypes.jira.summaryFieldLabel',
|
||||
{
|
||||
|
@ -264,7 +267,7 @@ const JiraParamsFields: React.FunctionComponent<ActionParamsProps<JiraActionPara
|
|||
messageVariables={messageVariables}
|
||||
paramsProperty={'summary'}
|
||||
inputTargetValue={incident.summary ?? undefined}
|
||||
errors={errors.summary as string[]}
|
||||
errors={errors['subActionParams.incident.summary'] as string[]}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiSpacer size="m" />
|
||||
|
@ -327,7 +330,6 @@ const JiraParamsFields: React.FunctionComponent<ActionParamsProps<JiraActionPara
|
|||
defaultMessage: 'Description',
|
||||
}
|
||||
)}
|
||||
errors={errors.description as string[]}
|
||||
/>
|
||||
)}
|
||||
<TextAreaWithMessageVariables
|
||||
|
@ -342,7 +344,6 @@ const JiraParamsFields: React.FunctionComponent<ActionParamsProps<JiraActionPara
|
|||
defaultMessage: 'Additional comments',
|
||||
}
|
||||
)}
|
||||
errors={errors.comments as string[]}
|
||||
/>
|
||||
</>
|
||||
</>
|
||||
|
|
|
@ -9,7 +9,6 @@ import { UserConfiguredActionConnector } from '../../../../types';
|
|||
import { ExecutorSubActionPushParams } from '../../../../../../actions/server/builtin_action_types/jira/types';
|
||||
|
||||
export type JiraActionConnector = UserConfiguredActionConnector<JiraConfig, JiraSecrets>;
|
||||
|
||||
export interface JiraActionParams {
|
||||
subAction: string;
|
||||
subActionParams: ExecutorSubActionPushParams;
|
||||
|
|
|
@ -42,16 +42,20 @@ describe('pagerduty connector validation', () => {
|
|||
} as PagerDutyActionConnector;
|
||||
|
||||
expect(actionTypeModel.validateConnector(actionConnector)).toEqual({
|
||||
errors: {
|
||||
routingKey: [],
|
||||
secrets: {
|
||||
errors: {
|
||||
routingKey: [],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
delete actionConnector.config.apiUrl;
|
||||
actionConnector.secrets.routingKey = 'test1';
|
||||
expect(actionTypeModel.validateConnector(actionConnector)).toEqual({
|
||||
errors: {
|
||||
routingKey: [],
|
||||
secrets: {
|
||||
errors: {
|
||||
routingKey: [],
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
@ -68,8 +72,10 @@ describe('pagerduty connector validation', () => {
|
|||
} as PagerDutyActionConnector;
|
||||
|
||||
expect(actionTypeModel.validateConnector(actionConnector)).toEqual({
|
||||
errors: {
|
||||
routingKey: ['An integration key / routing key is required.'],
|
||||
secrets: {
|
||||
errors: {
|
||||
routingKey: ['An integration key / routing key is required.'],
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
|
|
@ -6,7 +6,11 @@
|
|||
import { lazy } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import moment from 'moment';
|
||||
import { ActionTypeModel, ValidationResult } from '../../../../types';
|
||||
import {
|
||||
ActionTypeModel,
|
||||
GenericValidationResult,
|
||||
ConnectorValidationResult,
|
||||
} from '../../../../types';
|
||||
import {
|
||||
PagerDutyActionConnector,
|
||||
PagerDutyConfig,
|
||||
|
@ -36,15 +40,18 @@ export function getActionType(): ActionTypeModel<
|
|||
defaultMessage: 'Send to PagerDuty',
|
||||
}
|
||||
),
|
||||
validateConnector: (action: PagerDutyActionConnector): ValidationResult => {
|
||||
const validationResult = { errors: {} };
|
||||
const errors = {
|
||||
validateConnector: (
|
||||
action: PagerDutyActionConnector
|
||||
): ConnectorValidationResult<PagerDutyConfig, PagerDutySecrets> => {
|
||||
const secretsErrors = {
|
||||
routingKey: new Array<string>(),
|
||||
};
|
||||
validationResult.errors = errors;
|
||||
const validationResult = {
|
||||
secrets: { errors: secretsErrors },
|
||||
};
|
||||
|
||||
if (!action.secrets.routingKey) {
|
||||
errors.routingKey.push(
|
||||
secretsErrors.routingKey.push(
|
||||
i18n.translate(
|
||||
'xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.error.requiredRoutingKeyText',
|
||||
{
|
||||
|
@ -55,14 +62,17 @@ export function getActionType(): ActionTypeModel<
|
|||
}
|
||||
return validationResult;
|
||||
},
|
||||
validateParams: (actionParams: PagerDutyActionParams): ValidationResult => {
|
||||
const validationResult = { errors: {} };
|
||||
validateParams: (
|
||||
actionParams: PagerDutyActionParams
|
||||
): GenericValidationResult<
|
||||
Pick<PagerDutyActionParams, 'summary' | 'timestamp' | 'dedupKey'>
|
||||
> => {
|
||||
const errors = {
|
||||
summary: new Array<string>(),
|
||||
timestamp: new Array<string>(),
|
||||
dedupKey: new Array<string>(),
|
||||
};
|
||||
validationResult.errors = errors;
|
||||
const validationResult = { errors };
|
||||
if (
|
||||
!actionParams.dedupKey?.length &&
|
||||
(actionParams.eventAction === 'resolve' || actionParams.eventAction === 'acknowledge')
|
||||
|
|
|
@ -44,11 +44,17 @@ describe('resilient connector validation', () => {
|
|||
} as ResilientActionConnector;
|
||||
|
||||
expect(actionTypeModel.validateConnector(actionConnector)).toEqual({
|
||||
errors: {
|
||||
apiUrl: [],
|
||||
apiKeyId: [],
|
||||
apiKeySecret: [],
|
||||
orgId: [],
|
||||
config: {
|
||||
errors: {
|
||||
apiUrl: [],
|
||||
orgId: [],
|
||||
},
|
||||
},
|
||||
secrets: {
|
||||
errors: {
|
||||
apiKeySecret: [],
|
||||
apiKeyId: [],
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
@ -65,11 +71,17 @@ describe('resilient connector validation', () => {
|
|||
} as unknown) as ResilientActionConnector;
|
||||
|
||||
expect(actionTypeModel.validateConnector(actionConnector)).toEqual({
|
||||
errors: {
|
||||
apiUrl: ['URL is required.'],
|
||||
apiKeyId: [],
|
||||
apiKeySecret: ['Secret is required'],
|
||||
orgId: ['Organization ID is required'],
|
||||
config: {
|
||||
errors: {
|
||||
apiUrl: ['URL is required.'],
|
||||
orgId: ['Organization ID is required'],
|
||||
},
|
||||
},
|
||||
secrets: {
|
||||
errors: {
|
||||
apiKeySecret: ['Secret is required'],
|
||||
apiKeyId: [],
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
@ -82,7 +94,7 @@ describe('resilient action params validation', () => {
|
|||
};
|
||||
|
||||
expect(actionTypeModel.validateParams(actionParams)).toEqual({
|
||||
errors: { name: [] },
|
||||
errors: { 'subActionParams.incident.name': [] },
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -93,7 +105,7 @@ describe('resilient action params validation', () => {
|
|||
|
||||
expect(actionTypeModel.validateParams(actionParams)).toEqual({
|
||||
errors: {
|
||||
name: ['Name is required.'],
|
||||
'subActionParams.incident.name': ['Name is required.'],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,7 +5,11 @@
|
|||
*/
|
||||
|
||||
import { lazy } from 'react';
|
||||
import { ValidationResult, ActionTypeModel } from '../../../../types';
|
||||
import {
|
||||
GenericValidationResult,
|
||||
ActionTypeModel,
|
||||
ConnectorValidationResult,
|
||||
} from '../../../../types';
|
||||
import { connectorConfiguration } from './config';
|
||||
import logo from './logo.svg';
|
||||
import {
|
||||
|
@ -17,38 +21,45 @@ import {
|
|||
import * as i18n from './translations';
|
||||
import { isValidUrl } from '../../../lib/value_validators';
|
||||
|
||||
const validateConnector = (action: ResilientActionConnector): ValidationResult => {
|
||||
const validationResult = { errors: {} };
|
||||
const errors = {
|
||||
const validateConnector = (
|
||||
action: ResilientActionConnector
|
||||
): ConnectorValidationResult<ResilientConfig, ResilientSecrets> => {
|
||||
const configErrors = {
|
||||
apiUrl: new Array<string>(),
|
||||
orgId: new Array<string>(),
|
||||
};
|
||||
const secretsErrors = {
|
||||
apiKeyId: new Array<string>(),
|
||||
apiKeySecret: new Array<string>(),
|
||||
};
|
||||
validationResult.errors = errors;
|
||||
|
||||
const validationResult = {
|
||||
config: { errors: configErrors },
|
||||
secrets: { errors: secretsErrors },
|
||||
};
|
||||
|
||||
if (!action.config.apiUrl) {
|
||||
errors.apiUrl = [...errors.apiUrl, i18n.API_URL_REQUIRED];
|
||||
configErrors.apiUrl = [...configErrors.apiUrl, i18n.API_URL_REQUIRED];
|
||||
}
|
||||
|
||||
if (action.config.apiUrl) {
|
||||
if (!isValidUrl(action.config.apiUrl)) {
|
||||
errors.apiUrl = [...errors.apiUrl, i18n.API_URL_INVALID];
|
||||
configErrors.apiUrl = [...configErrors.apiUrl, i18n.API_URL_INVALID];
|
||||
} else if (!isValidUrl(action.config.apiUrl, 'https:')) {
|
||||
errors.apiUrl = [...errors.apiUrl, i18n.API_URL_REQUIRE_HTTPS];
|
||||
configErrors.apiUrl = [...configErrors.apiUrl, i18n.API_URL_REQUIRE_HTTPS];
|
||||
}
|
||||
}
|
||||
|
||||
if (!action.config.orgId) {
|
||||
errors.orgId = [...errors.orgId, i18n.ORG_ID_REQUIRED];
|
||||
configErrors.orgId = [...configErrors.orgId, i18n.ORG_ID_REQUIRED];
|
||||
}
|
||||
|
||||
if (!action.secrets.apiKeyId) {
|
||||
errors.apiKeyId = [...errors.apiKeyId, i18n.API_KEY_ID_REQUIRED];
|
||||
secretsErrors.apiKeyId = [...secretsErrors.apiKeyId, i18n.API_KEY_ID_REQUIRED];
|
||||
}
|
||||
|
||||
if (!action.secrets.apiKeySecret) {
|
||||
errors.apiKeySecret = [...errors.apiKeySecret, i18n.API_KEY_SECRET_REQUIRED];
|
||||
secretsErrors.apiKeySecret = [...secretsErrors.apiKeySecret, i18n.API_KEY_SECRET_REQUIRED];
|
||||
}
|
||||
|
||||
return validationResult;
|
||||
|
@ -66,18 +77,19 @@ export function getActionType(): ActionTypeModel<
|
|||
actionTypeTitle: connectorConfiguration.name,
|
||||
validateConnector,
|
||||
actionConnectorFields: lazy(() => import('./resilient_connectors')),
|
||||
validateParams: (actionParams: ResilientActionParams): ValidationResult => {
|
||||
const validationResult = { errors: {} };
|
||||
validateParams: (actionParams: ResilientActionParams): GenericValidationResult<unknown> => {
|
||||
const errors = {
|
||||
name: new Array<string>(),
|
||||
'subActionParams.incident.name': new Array<string>(),
|
||||
};
|
||||
const validationResult = {
|
||||
errors,
|
||||
};
|
||||
validationResult.errors = errors;
|
||||
if (
|
||||
actionParams.subActionParams &&
|
||||
actionParams.subActionParams.incident &&
|
||||
!actionParams.subActionParams.incident.name?.length
|
||||
) {
|
||||
errors.name.push(i18n.NAME_REQUIRED);
|
||||
errors['subActionParams.incident.name'].push(i18n.NAME_REQUIRED);
|
||||
}
|
||||
return validationResult;
|
||||
},
|
||||
|
|
|
@ -29,13 +29,14 @@ const ResilientConnectorFields: React.FC<ActionConnectorFieldsProps<ResilientAct
|
|||
readOnly,
|
||||
}) => {
|
||||
const { apiUrl, orgId } = action.config;
|
||||
const isApiUrlInvalid: boolean = errors.apiUrl.length > 0 && apiUrl != null;
|
||||
const isApiUrlInvalid: boolean = errors.apiUrl.length > 0 && apiUrl !== undefined;
|
||||
|
||||
const { apiKeyId, apiKeySecret } = action.secrets;
|
||||
|
||||
const isOrgIdInvalid: boolean = errors.orgId.length > 0 && orgId != null;
|
||||
const isApiKeyInvalid: boolean = errors.apiKeyId.length > 0 && apiKeyId != null;
|
||||
const isApiKeySecretInvalid: boolean = errors.apiKeySecret.length > 0 && apiKeySecret != null;
|
||||
const isOrgIdInvalid: boolean = errors.orgId.length > 0 && orgId !== undefined;
|
||||
const isApiKeyInvalid: boolean = errors.apiKeyId.length > 0 && apiKeyId !== undefined;
|
||||
const isApiKeySecretInvalid: boolean =
|
||||
errors.apiKeySecret.length > 0 && apiKeySecret !== undefined;
|
||||
|
||||
const handleOnChangeActionConfig = useCallback(
|
||||
(key: string, value: string) => editActionConfig(key, value),
|
||||
|
|
|
@ -42,7 +42,7 @@ const connector = {
|
|||
const editAction = jest.fn();
|
||||
const defaultProps = {
|
||||
actionParams,
|
||||
errors: { name: [] },
|
||||
errors: { 'subActionParams.incident.name': [] },
|
||||
editAction,
|
||||
index: 0,
|
||||
messageVariables: [],
|
||||
|
@ -128,7 +128,7 @@ describe('ResilientParamsFields renders', () => {
|
|||
test('If name has errors, form row is invalid', () => {
|
||||
const newProps = {
|
||||
...defaultProps,
|
||||
errors: { name: ['error'] },
|
||||
errors: { 'subActionParams.incident.name': ['error'] },
|
||||
};
|
||||
const wrapper = mount(<ResilientParamsFields {...newProps} />);
|
||||
const title = wrapper.find('[data-test-subj="nameInput"]').first();
|
||||
|
|
|
@ -210,8 +210,10 @@ const ResilientParamsFields: React.FunctionComponent<ActionParamsProps<Resilient
|
|||
<EuiSpacer size="m" />
|
||||
<EuiFormRow
|
||||
fullWidth
|
||||
error={errors.name}
|
||||
isInvalid={errors.name.length > 0 && incident.name !== undefined}
|
||||
error={errors['subActionParams.incident.name']}
|
||||
isInvalid={
|
||||
errors['subActionParams.incident.name'].length > 0 && incident.name !== undefined
|
||||
}
|
||||
label={i18n.translate(
|
||||
'xpack.triggersActionsUI.components.builtinActionTypes.resilient.nameFieldLabel',
|
||||
{ defaultMessage: 'Name (required)' }
|
||||
|
@ -223,7 +225,7 @@ const ResilientParamsFields: React.FunctionComponent<ActionParamsProps<Resilient
|
|||
messageVariables={messageVariables}
|
||||
paramsProperty={'name'}
|
||||
inputTargetValue={incident.name ?? undefined}
|
||||
errors={errors.name as string[]}
|
||||
errors={errors['subActionParams.incident.name'] as string[]}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<TextAreaWithMessageVariables
|
||||
|
@ -236,7 +238,6 @@ const ResilientParamsFields: React.FunctionComponent<ActionParamsProps<Resilient
|
|||
'xpack.triggersActionsUI.components.builtinActionTypes.resilient.descriptionTextAreaFieldLabel',
|
||||
{ defaultMessage: 'Description' }
|
||||
)}
|
||||
errors={errors.description as string[]}
|
||||
/>
|
||||
<TextAreaWithMessageVariables
|
||||
index={index}
|
||||
|
@ -248,7 +249,6 @@ const ResilientParamsFields: React.FunctionComponent<ActionParamsProps<Resilient
|
|||
'xpack.triggersActionsUI.components.builtinActionTypes.resilient.commentsTextAreaFieldLabel',
|
||||
{ defaultMessage: 'Additional comments' }
|
||||
)}
|
||||
errors={errors.comments as string[]}
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
|
|
|
@ -38,7 +38,12 @@ describe('server-log connector validation', () => {
|
|||
};
|
||||
|
||||
expect(actionTypeModel.validateConnector(actionConnector)).toEqual({
|
||||
errors: {},
|
||||
config: {
|
||||
errors: {},
|
||||
},
|
||||
secrets: {
|
||||
errors: {},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,7 +5,11 @@
|
|||
*/
|
||||
import { lazy } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { ActionTypeModel, ValidationResult } from '../../../../types';
|
||||
import {
|
||||
ActionTypeModel,
|
||||
GenericValidationResult,
|
||||
ConnectorValidationResult,
|
||||
} from '../../../../types';
|
||||
import { ServerLogActionParams } from '../types';
|
||||
|
||||
export function getActionType(): ActionTypeModel<unknown, unknown, ServerLogActionParams> {
|
||||
|
@ -24,15 +28,16 @@ export function getActionType(): ActionTypeModel<unknown, unknown, ServerLogActi
|
|||
defaultMessage: 'Send to Server log',
|
||||
}
|
||||
),
|
||||
validateConnector: (): ValidationResult => {
|
||||
return { errors: {} };
|
||||
validateConnector: (): ConnectorValidationResult<unknown, unknown> => {
|
||||
return { config: { errors: {} }, secrets: { errors: {} } };
|
||||
},
|
||||
validateParams: (actionParams: ServerLogActionParams): ValidationResult => {
|
||||
const validationResult = { errors: {} };
|
||||
validateParams: (
|
||||
actionParams: ServerLogActionParams
|
||||
): GenericValidationResult<Pick<ServerLogActionParams, 'message'>> => {
|
||||
const errors = {
|
||||
message: new Array<string>(),
|
||||
};
|
||||
validationResult.errors = errors;
|
||||
const validationResult = { errors };
|
||||
if (!actionParams.message?.length) {
|
||||
errors.message.push(
|
||||
i18n.translate(
|
||||
|
|
|
@ -43,10 +43,16 @@ describe('servicenow connector validation', () => {
|
|||
} as ServiceNowActionConnector;
|
||||
|
||||
expect(actionTypeModel.validateConnector(actionConnector)).toEqual({
|
||||
errors: {
|
||||
apiUrl: [],
|
||||
username: [],
|
||||
password: [],
|
||||
config: {
|
||||
errors: {
|
||||
apiUrl: [],
|
||||
},
|
||||
},
|
||||
secrets: {
|
||||
errors: {
|
||||
username: [],
|
||||
password: [],
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
@ -63,10 +69,16 @@ describe('servicenow connector validation', () => {
|
|||
} as unknown) as ServiceNowActionConnector;
|
||||
|
||||
expect(actionTypeModel.validateConnector(actionConnector)).toEqual({
|
||||
errors: {
|
||||
apiUrl: ['URL is required.'],
|
||||
username: [],
|
||||
password: ['Password is required.'],
|
||||
config: {
|
||||
errors: {
|
||||
apiUrl: ['URL is required.'],
|
||||
},
|
||||
},
|
||||
secrets: {
|
||||
errors: {
|
||||
username: [],
|
||||
password: ['Password is required.'],
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
@ -79,7 +91,7 @@ describe('servicenow action params validation', () => {
|
|||
};
|
||||
|
||||
expect(actionTypeModel.validateParams(actionParams)).toEqual({
|
||||
errors: { short_description: [] },
|
||||
errors: { ['subActionParams.incident.short_description']: [] },
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -90,7 +102,7 @@ describe('servicenow action params validation', () => {
|
|||
|
||||
expect(actionTypeModel.validateParams(actionParams)).toEqual({
|
||||
errors: {
|
||||
short_description: ['Short description is required.'],
|
||||
['subActionParams.incident.short_description']: ['Short description is required.'],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,7 +5,11 @@
|
|||
*/
|
||||
|
||||
import { lazy } from 'react';
|
||||
import { ValidationResult, ActionTypeModel } from '../../../../types';
|
||||
import {
|
||||
GenericValidationResult,
|
||||
ActionTypeModel,
|
||||
ConnectorValidationResult,
|
||||
} from '../../../../types';
|
||||
import { connectorConfiguration } from './config';
|
||||
import logo from './logo.svg';
|
||||
import {
|
||||
|
@ -17,33 +21,40 @@ import {
|
|||
import * as i18n from './translations';
|
||||
import { isValidUrl } from '../../../lib/value_validators';
|
||||
|
||||
const validateConnector = (action: ServiceNowActionConnector): ValidationResult => {
|
||||
const validationResult = { errors: {} };
|
||||
const errors = {
|
||||
const validateConnector = (
|
||||
action: ServiceNowActionConnector
|
||||
): ConnectorValidationResult<ServiceNowConfig, ServiceNowSecrets> => {
|
||||
const configErrors = {
|
||||
apiUrl: new Array<string>(),
|
||||
};
|
||||
const secretsErrors = {
|
||||
username: new Array<string>(),
|
||||
password: new Array<string>(),
|
||||
};
|
||||
validationResult.errors = errors;
|
||||
|
||||
const validationResult = {
|
||||
config: { errors: configErrors },
|
||||
secrets: { errors: secretsErrors },
|
||||
};
|
||||
|
||||
if (!action.config.apiUrl) {
|
||||
errors.apiUrl = [...errors.apiUrl, i18n.API_URL_REQUIRED];
|
||||
configErrors.apiUrl = [...configErrors.apiUrl, i18n.API_URL_REQUIRED];
|
||||
}
|
||||
|
||||
if (action.config.apiUrl) {
|
||||
if (!isValidUrl(action.config.apiUrl)) {
|
||||
errors.apiUrl = [...errors.apiUrl, i18n.API_URL_INVALID];
|
||||
configErrors.apiUrl = [...configErrors.apiUrl, i18n.API_URL_INVALID];
|
||||
} else if (!isValidUrl(action.config.apiUrl, 'https:')) {
|
||||
errors.apiUrl = [...errors.apiUrl, i18n.API_URL_REQUIRE_HTTPS];
|
||||
configErrors.apiUrl = [...configErrors.apiUrl, i18n.API_URL_REQUIRE_HTTPS];
|
||||
}
|
||||
}
|
||||
|
||||
if (!action.secrets.username) {
|
||||
errors.username = [...errors.username, i18n.USERNAME_REQUIRED];
|
||||
secretsErrors.username = [...secretsErrors.username, i18n.USERNAME_REQUIRED];
|
||||
}
|
||||
|
||||
if (!action.secrets.password) {
|
||||
errors.password = [...errors.password, i18n.PASSWORD_REQUIRED];
|
||||
secretsErrors.password = [...secretsErrors.password, i18n.PASSWORD_REQUIRED];
|
||||
}
|
||||
|
||||
return validationResult;
|
||||
|
@ -61,18 +72,20 @@ export function getActionType(): ActionTypeModel<
|
|||
actionTypeTitle: connectorConfiguration.name,
|
||||
validateConnector,
|
||||
actionConnectorFields: lazy(() => import('./servicenow_connectors')),
|
||||
validateParams: (actionParams: ServiceNowActionParams): ValidationResult => {
|
||||
const validationResult = { errors: {} };
|
||||
validateParams: (actionParams: ServiceNowActionParams): GenericValidationResult<unknown> => {
|
||||
const errors = {
|
||||
short_description: new Array<string>(),
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
'subActionParams.incident.short_description': new Array<string>(),
|
||||
};
|
||||
const validationResult = {
|
||||
errors,
|
||||
};
|
||||
validationResult.errors = errors;
|
||||
if (
|
||||
actionParams.subActionParams &&
|
||||
actionParams.subActionParams.incident &&
|
||||
!actionParams.subActionParams.incident.short_description?.length
|
||||
) {
|
||||
errors.short_description.push(i18n.TITLE_REQUIRED);
|
||||
errors['subActionParams.incident.short_description'].push(i18n.TITLE_REQUIRED);
|
||||
}
|
||||
return validationResult;
|
||||
},
|
||||
|
|
|
@ -31,12 +31,12 @@ const ServiceNowConnectorFields: React.FC<
|
|||
const { docLinks } = useKibana().services;
|
||||
const { apiUrl } = action.config;
|
||||
|
||||
const isApiUrlInvalid: boolean = errors.apiUrl.length > 0 && apiUrl != null;
|
||||
const isApiUrlInvalid: boolean = errors.apiUrl.length > 0 && apiUrl !== undefined;
|
||||
|
||||
const { username, password } = action.secrets;
|
||||
|
||||
const isUsernameInvalid: boolean = errors.username.length > 0 && username != null;
|
||||
const isPasswordInvalid: boolean = errors.password.length > 0 && password != null;
|
||||
const isUsernameInvalid: boolean = errors.username.length > 0 && username !== undefined;
|
||||
const isPasswordInvalid: boolean = errors.password.length > 0 && password !== undefined;
|
||||
|
||||
const handleOnChangeActionConfig = useCallback(
|
||||
(key: string, value: string) => editActionConfig(key, value),
|
||||
|
|
|
@ -35,7 +35,7 @@ const editAction = jest.fn();
|
|||
const defaultProps = {
|
||||
actionConnector: connector,
|
||||
actionParams,
|
||||
errors: { short_description: [] },
|
||||
errors: { ['subActionParams.incident.short_description']: [] },
|
||||
editAction,
|
||||
index: 0,
|
||||
messageVariables: [],
|
||||
|
@ -58,7 +58,8 @@ describe('ServiceNowParamsFields renders', () => {
|
|||
test('If short_description has errors, form row is invalid', () => {
|
||||
const newProps = {
|
||||
...defaultProps,
|
||||
errors: { short_description: ['error'] },
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
errors: { 'subActionParams.incident.short_description': ['error'] },
|
||||
};
|
||||
const wrapper = mount(<ServiceNowParamsFields {...newProps} />);
|
||||
const title = wrapper.find('[data-test-subj="short_descriptionInput"]').first();
|
||||
|
|
|
@ -180,8 +180,11 @@ const ServiceNowParamsFields: React.FunctionComponent<
|
|||
<EuiSpacer size="m" />
|
||||
<EuiFormRow
|
||||
fullWidth
|
||||
error={errors.short_description}
|
||||
isInvalid={errors.short_description.length > 0 && incident.short_description !== undefined}
|
||||
error={errors['subActionParams.incident.short_description']}
|
||||
isInvalid={
|
||||
errors['subActionParams.incident.short_description'].length > 0 &&
|
||||
incident.short_description !== undefined
|
||||
}
|
||||
label={i18n.translate(
|
||||
'xpack.triggersActionsUI.components.builtinActionTypes.serviceNow.titleFieldLabel',
|
||||
{ defaultMessage: 'Short description (required)' }
|
||||
|
@ -193,7 +196,7 @@ const ServiceNowParamsFields: React.FunctionComponent<
|
|||
messageVariables={messageVariables}
|
||||
paramsProperty={'short_description'}
|
||||
inputTargetValue={incident?.short_description ?? undefined}
|
||||
errors={errors.short_description as string[]}
|
||||
errors={errors['subActionParams.incident.short_description'] as string[]}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<TextAreaWithMessageVariables
|
||||
|
@ -206,7 +209,6 @@ const ServiceNowParamsFields: React.FunctionComponent<
|
|||
'xpack.triggersActionsUI.components.builtinActionTypes.serviceNow.descriptionTextAreaFieldLabel',
|
||||
{ defaultMessage: 'Description' }
|
||||
)}
|
||||
errors={errors.description as string[]}
|
||||
/>
|
||||
<TextAreaWithMessageVariables
|
||||
index={index}
|
||||
|
@ -218,7 +220,6 @@ const ServiceNowParamsFields: React.FunctionComponent<
|
|||
'xpack.triggersActionsUI.components.builtinActionTypes.serviceNow.commentsTextAreaFieldLabel',
|
||||
{ defaultMessage: 'Additional comments' }
|
||||
)}
|
||||
errors={errors.comments as string[]}
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
|
|
|
@ -40,8 +40,13 @@ describe('slack connector validation', () => {
|
|||
} as SlackActionConnector;
|
||||
|
||||
expect(actionTypeModel.validateConnector(actionConnector)).toEqual({
|
||||
errors: {
|
||||
webhookUrl: [],
|
||||
config: {
|
||||
errors: {},
|
||||
},
|
||||
secrets: {
|
||||
errors: {
|
||||
webhookUrl: [],
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
@ -56,8 +61,13 @@ describe('slack connector validation', () => {
|
|||
} as SlackActionConnector;
|
||||
|
||||
expect(actionTypeModel.validateConnector(actionConnector)).toEqual({
|
||||
errors: {
|
||||
webhookUrl: ['Webhook URL is required.'],
|
||||
config: {
|
||||
errors: {},
|
||||
},
|
||||
secrets: {
|
||||
errors: {
|
||||
webhookUrl: ['Webhook URL is required.'],
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
@ -74,8 +84,13 @@ describe('slack connector validation', () => {
|
|||
} as SlackActionConnector;
|
||||
|
||||
expect(actionTypeModel.validateConnector(actionConnector)).toEqual({
|
||||
errors: {
|
||||
webhookUrl: ['Webhook URL must start with https://.'],
|
||||
config: {
|
||||
errors: {},
|
||||
},
|
||||
secrets: {
|
||||
errors: {
|
||||
webhookUrl: ['Webhook URL must start with https://.'],
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
@ -92,8 +107,13 @@ describe('slack connector validation', () => {
|
|||
} as SlackActionConnector;
|
||||
|
||||
expect(actionTypeModel.validateConnector(actionConnector)).toEqual({
|
||||
errors: {
|
||||
webhookUrl: ['Webhook URL is invalid.'],
|
||||
config: {
|
||||
errors: {},
|
||||
},
|
||||
secrets: {
|
||||
errors: {
|
||||
webhookUrl: ['Webhook URL is invalid.'],
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,7 +5,11 @@
|
|||
*/
|
||||
import { lazy } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { ActionTypeModel, ValidationResult } from '../../../../types';
|
||||
import {
|
||||
ActionTypeModel,
|
||||
GenericValidationResult,
|
||||
ConnectorValidationResult,
|
||||
} from '../../../../types';
|
||||
import { SlackActionParams, SlackSecrets, SlackActionConnector } from '../types';
|
||||
import { isValidUrl } from '../../../lib/value_validators';
|
||||
|
||||
|
@ -25,14 +29,15 @@ export function getActionType(): ActionTypeModel<unknown, SlackSecrets, SlackAct
|
|||
defaultMessage: 'Send to Slack',
|
||||
}
|
||||
),
|
||||
validateConnector: (action: SlackActionConnector): ValidationResult => {
|
||||
const validationResult = { errors: {} };
|
||||
const errors = {
|
||||
validateConnector: (
|
||||
action: SlackActionConnector
|
||||
): ConnectorValidationResult<unknown, SlackSecrets> => {
|
||||
const secretsErrors = {
|
||||
webhookUrl: new Array<string>(),
|
||||
};
|
||||
validationResult.errors = errors;
|
||||
const validationResult = { config: { errors: {} }, secrets: { errors: secretsErrors } };
|
||||
if (!action.secrets.webhookUrl) {
|
||||
errors.webhookUrl.push(
|
||||
secretsErrors.webhookUrl.push(
|
||||
i18n.translate(
|
||||
'xpack.triggersActionsUI.components.builtinActionTypes.slackAction.error.requiredWebhookUrlText',
|
||||
{
|
||||
|
@ -42,7 +47,7 @@ export function getActionType(): ActionTypeModel<unknown, SlackSecrets, SlackAct
|
|||
);
|
||||
} else if (action.secrets.webhookUrl) {
|
||||
if (!isValidUrl(action.secrets.webhookUrl)) {
|
||||
errors.webhookUrl.push(
|
||||
secretsErrors.webhookUrl.push(
|
||||
i18n.translate(
|
||||
'xpack.triggersActionsUI.components.builtinActionTypes.slackAction.error.invalidWebhookUrlText',
|
||||
{
|
||||
|
@ -51,7 +56,7 @@ export function getActionType(): ActionTypeModel<unknown, SlackSecrets, SlackAct
|
|||
)
|
||||
);
|
||||
} else if (!isValidUrl(action.secrets.webhookUrl, 'https:')) {
|
||||
errors.webhookUrl.push(
|
||||
secretsErrors.webhookUrl.push(
|
||||
i18n.translate(
|
||||
'xpack.triggersActionsUI.components.builtinActionTypes.slackAction.error.requireHttpsWebhookUrlText',
|
||||
{
|
||||
|
@ -63,12 +68,13 @@ export function getActionType(): ActionTypeModel<unknown, SlackSecrets, SlackAct
|
|||
}
|
||||
return validationResult;
|
||||
},
|
||||
validateParams: (actionParams: SlackActionParams): ValidationResult => {
|
||||
const validationResult = { errors: {} };
|
||||
validateParams: (
|
||||
actionParams: SlackActionParams
|
||||
): GenericValidationResult<SlackActionParams> => {
|
||||
const errors = {
|
||||
message: new Array<string>(),
|
||||
};
|
||||
validationResult.errors = errors;
|
||||
const validationResult = { errors };
|
||||
if (!actionParams.message?.length) {
|
||||
errors.message.push(
|
||||
i18n.translate(
|
||||
|
|
|
@ -39,8 +39,13 @@ describe('teams connector validation', () => {
|
|||
} as TeamsActionConnector;
|
||||
|
||||
expect(actionTypeModel.validateConnector(actionConnector)).toEqual({
|
||||
errors: {
|
||||
webhookUrl: [],
|
||||
config: {
|
||||
errors: {},
|
||||
},
|
||||
secrets: {
|
||||
errors: {
|
||||
webhookUrl: [],
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
@ -55,8 +60,13 @@ describe('teams connector validation', () => {
|
|||
} as TeamsActionConnector;
|
||||
|
||||
expect(actionTypeModel.validateConnector(actionConnector)).toEqual({
|
||||
errors: {
|
||||
webhookUrl: ['Webhook URL is required.'],
|
||||
config: {
|
||||
errors: {},
|
||||
},
|
||||
secrets: {
|
||||
errors: {
|
||||
webhookUrl: ['Webhook URL is required.'],
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
@ -73,8 +83,13 @@ describe('teams connector validation', () => {
|
|||
} as TeamsActionConnector;
|
||||
|
||||
expect(actionTypeModel.validateConnector(actionConnector)).toEqual({
|
||||
errors: {
|
||||
webhookUrl: ['Webhook URL is invalid.'],
|
||||
config: {
|
||||
errors: {},
|
||||
},
|
||||
secrets: {
|
||||
errors: {
|
||||
webhookUrl: ['Webhook URL is invalid.'],
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
@ -91,8 +106,13 @@ describe('teams connector validation', () => {
|
|||
} as TeamsActionConnector;
|
||||
|
||||
expect(actionTypeModel.validateConnector(actionConnector)).toEqual({
|
||||
errors: {
|
||||
webhookUrl: ['Webhook URL must start with https://.'],
|
||||
config: {
|
||||
errors: {},
|
||||
},
|
||||
secrets: {
|
||||
errors: {
|
||||
webhookUrl: ['Webhook URL must start with https://.'],
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
|
|
@ -6,7 +6,11 @@
|
|||
import { lazy } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import teamsSvg from './teams.svg';
|
||||
import { ActionTypeModel, ValidationResult } from '../../../../types';
|
||||
import {
|
||||
ActionTypeModel,
|
||||
GenericValidationResult,
|
||||
ConnectorValidationResult,
|
||||
} from '../../../../types';
|
||||
import { TeamsActionParams, TeamsSecrets, TeamsActionConnector } from '../types';
|
||||
import { isValidUrl } from '../../../lib/value_validators';
|
||||
|
||||
|
@ -26,14 +30,15 @@ export function getActionType(): ActionTypeModel<unknown, TeamsSecrets, TeamsAct
|
|||
defaultMessage: 'Send a message to a Microsoft Teams channel.',
|
||||
}
|
||||
),
|
||||
validateConnector: (action: TeamsActionConnector): ValidationResult => {
|
||||
const validationResult = { errors: {} };
|
||||
const errors = {
|
||||
validateConnector: (
|
||||
action: TeamsActionConnector
|
||||
): ConnectorValidationResult<unknown, TeamsSecrets> => {
|
||||
const secretsErrors = {
|
||||
webhookUrl: new Array<string>(),
|
||||
};
|
||||
validationResult.errors = errors;
|
||||
const validationResult = { config: { errors: {} }, secrets: { errors: secretsErrors } };
|
||||
if (!action.secrets.webhookUrl) {
|
||||
errors.webhookUrl.push(
|
||||
secretsErrors.webhookUrl.push(
|
||||
i18n.translate(
|
||||
'xpack.triggersActionsUI.components.builtinActionTypes.teamsAction.error.requiredWebhookUrlText',
|
||||
{
|
||||
|
@ -43,7 +48,7 @@ export function getActionType(): ActionTypeModel<unknown, TeamsSecrets, TeamsAct
|
|||
);
|
||||
} else if (action.secrets.webhookUrl) {
|
||||
if (!isValidUrl(action.secrets.webhookUrl)) {
|
||||
errors.webhookUrl.push(
|
||||
secretsErrors.webhookUrl.push(
|
||||
i18n.translate(
|
||||
'xpack.triggersActionsUI.components.builtinActionTypes.teamsAction.error.invalidWebhookUrlText',
|
||||
{
|
||||
|
@ -52,7 +57,7 @@ export function getActionType(): ActionTypeModel<unknown, TeamsSecrets, TeamsAct
|
|||
)
|
||||
);
|
||||
} else if (!isValidUrl(action.secrets.webhookUrl, 'https:')) {
|
||||
errors.webhookUrl.push(
|
||||
secretsErrors.webhookUrl.push(
|
||||
i18n.translate(
|
||||
'xpack.triggersActionsUI.components.builtinActionTypes.teamsAction.error.requireHttpsWebhookUrlText',
|
||||
{
|
||||
|
@ -64,12 +69,13 @@ export function getActionType(): ActionTypeModel<unknown, TeamsSecrets, TeamsAct
|
|||
}
|
||||
return validationResult;
|
||||
},
|
||||
validateParams: (actionParams: TeamsActionParams): ValidationResult => {
|
||||
const validationResult = { errors: {} };
|
||||
validateParams: (
|
||||
actionParams: TeamsActionParams
|
||||
): GenericValidationResult<TeamsActionParams> => {
|
||||
const errors = {
|
||||
message: new Array<string>(),
|
||||
};
|
||||
validationResult.errors = errors;
|
||||
const validationResult = { errors };
|
||||
if (!actionParams.message?.length) {
|
||||
errors.message.push(
|
||||
i18n.translate(
|
||||
|
|
|
@ -47,11 +47,17 @@ describe('webhook connector validation', () => {
|
|||
} as WebhookActionConnector;
|
||||
|
||||
expect(actionTypeModel.validateConnector(actionConnector)).toEqual({
|
||||
errors: {
|
||||
url: [],
|
||||
method: [],
|
||||
user: [],
|
||||
password: [],
|
||||
config: {
|
||||
errors: {
|
||||
url: [],
|
||||
method: [],
|
||||
},
|
||||
},
|
||||
secrets: {
|
||||
errors: {
|
||||
user: [],
|
||||
password: [],
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
@ -75,11 +81,17 @@ describe('webhook connector validation', () => {
|
|||
} as WebhookActionConnector;
|
||||
|
||||
expect(actionTypeModel.validateConnector(actionConnector)).toEqual({
|
||||
errors: {
|
||||
url: [],
|
||||
method: [],
|
||||
user: [],
|
||||
password: [],
|
||||
config: {
|
||||
errors: {
|
||||
url: [],
|
||||
method: [],
|
||||
},
|
||||
},
|
||||
secrets: {
|
||||
errors: {
|
||||
user: [],
|
||||
password: [],
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
@ -99,11 +111,17 @@ describe('webhook connector validation', () => {
|
|||
} as WebhookActionConnector;
|
||||
|
||||
expect(actionTypeModel.validateConnector(actionConnector)).toEqual({
|
||||
errors: {
|
||||
url: ['URL is required.'],
|
||||
method: [],
|
||||
user: [],
|
||||
password: ['Password is required when username is used.'],
|
||||
config: {
|
||||
errors: {
|
||||
url: ['URL is required.'],
|
||||
method: [],
|
||||
},
|
||||
},
|
||||
secrets: {
|
||||
errors: {
|
||||
user: [],
|
||||
password: ['Password is required when username is used.'],
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
@ -125,11 +143,17 @@ describe('webhook connector validation', () => {
|
|||
} as WebhookActionConnector;
|
||||
|
||||
expect(actionTypeModel.validateConnector(actionConnector)).toEqual({
|
||||
errors: {
|
||||
url: ['URL is invalid.'],
|
||||
method: [],
|
||||
user: [],
|
||||
password: [],
|
||||
config: {
|
||||
errors: {
|
||||
url: ['URL is invalid.'],
|
||||
method: [],
|
||||
},
|
||||
},
|
||||
secrets: {
|
||||
errors: {
|
||||
user: [],
|
||||
password: [],
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,7 +5,11 @@
|
|||
*/
|
||||
import { lazy } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { ActionTypeModel, ValidationResult } from '../../../../types';
|
||||
import {
|
||||
ActionTypeModel,
|
||||
GenericValidationResult,
|
||||
ConnectorValidationResult,
|
||||
} from '../../../../types';
|
||||
import {
|
||||
WebhookActionParams,
|
||||
WebhookConfig,
|
||||
|
@ -34,17 +38,23 @@ export function getActionType(): ActionTypeModel<
|
|||
defaultMessage: 'Webhook data',
|
||||
}
|
||||
),
|
||||
validateConnector: (action: WebhookActionConnector): ValidationResult => {
|
||||
const validationResult = { errors: {} };
|
||||
const errors = {
|
||||
validateConnector: (
|
||||
action: WebhookActionConnector
|
||||
): ConnectorValidationResult<Pick<WebhookConfig, 'url' | 'method'>, WebhookSecrets> => {
|
||||
const configErrors = {
|
||||
url: new Array<string>(),
|
||||
method: new Array<string>(),
|
||||
};
|
||||
const secretsErrors = {
|
||||
user: new Array<string>(),
|
||||
password: new Array<string>(),
|
||||
};
|
||||
validationResult.errors = errors;
|
||||
const validationResult = {
|
||||
config: { errors: configErrors },
|
||||
secrets: { errors: secretsErrors },
|
||||
};
|
||||
if (!action.config.url) {
|
||||
errors.url.push(
|
||||
configErrors.url.push(
|
||||
i18n.translate(
|
||||
'xpack.triggersActionsUI.components.builtinActionTypes.webhookAction.error.requiredUrlText',
|
||||
{
|
||||
|
@ -54,8 +64,8 @@ export function getActionType(): ActionTypeModel<
|
|||
);
|
||||
}
|
||||
if (action.config.url && !isValidUrl(action.config.url)) {
|
||||
errors.url = [
|
||||
...errors.url,
|
||||
configErrors.url = [
|
||||
...configErrors.url,
|
||||
i18n.translate(
|
||||
'xpack.triggersActionsUI.components.builtinActionTypes.webhookAction.error.invalidUrlTextField',
|
||||
{
|
||||
|
@ -65,7 +75,7 @@ export function getActionType(): ActionTypeModel<
|
|||
];
|
||||
}
|
||||
if (!action.config.method) {
|
||||
errors.method.push(
|
||||
configErrors.method.push(
|
||||
i18n.translate(
|
||||
'xpack.triggersActionsUI.sections.addAction.webhookAction.error.requiredMethodText',
|
||||
{
|
||||
|
@ -75,7 +85,7 @@ export function getActionType(): ActionTypeModel<
|
|||
);
|
||||
}
|
||||
if (action.config.hasAuth && !action.secrets.user && !action.secrets.password) {
|
||||
errors.user.push(
|
||||
secretsErrors.user.push(
|
||||
i18n.translate(
|
||||
'xpack.triggersActionsUI.sections.addAction.webhookAction.error.requiredAuthUserNameText',
|
||||
{
|
||||
|
@ -85,7 +95,7 @@ export function getActionType(): ActionTypeModel<
|
|||
);
|
||||
}
|
||||
if (action.config.hasAuth && !action.secrets.user && !action.secrets.password) {
|
||||
errors.password.push(
|
||||
secretsErrors.password.push(
|
||||
i18n.translate(
|
||||
'xpack.triggersActionsUI.sections.addAction.webhookAction.error.requiredAuthPasswordText',
|
||||
{
|
||||
|
@ -95,7 +105,7 @@ export function getActionType(): ActionTypeModel<
|
|||
);
|
||||
}
|
||||
if (action.secrets.user && !action.secrets.password) {
|
||||
errors.password.push(
|
||||
secretsErrors.password.push(
|
||||
i18n.translate(
|
||||
'xpack.triggersActionsUI.sections.addAction.webhookAction.error.requiredPasswordText',
|
||||
{
|
||||
|
@ -105,7 +115,7 @@ export function getActionType(): ActionTypeModel<
|
|||
);
|
||||
}
|
||||
if (!action.secrets.user && action.secrets.password) {
|
||||
errors.user.push(
|
||||
secretsErrors.user.push(
|
||||
i18n.translate(
|
||||
'xpack.triggersActionsUI.sections.addAction.webhookAction.error.requiredUserText',
|
||||
{
|
||||
|
@ -116,11 +126,13 @@ export function getActionType(): ActionTypeModel<
|
|||
}
|
||||
return validationResult;
|
||||
},
|
||||
validateParams: (actionParams: WebhookActionParams): ValidationResult => {
|
||||
const validationResult = { errors: {} };
|
||||
validateParams: (
|
||||
actionParams: WebhookActionParams
|
||||
): GenericValidationResult<WebhookActionParams> => {
|
||||
const errors = {
|
||||
body: new Array<string>(),
|
||||
};
|
||||
const validationResult = { errors };
|
||||
validationResult.errors = errors;
|
||||
if (!actionParams.body?.length) {
|
||||
errors.body.push(
|
||||
|
|
|
@ -3,8 +3,15 @@
|
|||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import { throwIfAbsent, throwIfIsntContained, isValidUrl } from './value_validators';
|
||||
import {
|
||||
throwIfAbsent,
|
||||
throwIfIsntContained,
|
||||
isValidUrl,
|
||||
getConnectorWithInvalidatedFields,
|
||||
getAlertWithInvalidatedFields,
|
||||
} from './value_validators';
|
||||
import uuid from 'uuid';
|
||||
import { Alert, UserConfiguredActionConnector } from '../../types';
|
||||
|
||||
describe('throwIfAbsent', () => {
|
||||
test('throws if value is absent', () => {
|
||||
|
@ -93,3 +100,178 @@ describe('isValidUrl', () => {
|
|||
expect(isValidUrl('https://www.elastic.co/', 'https:')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getConnectorWithInvalidatedFields', () => {
|
||||
test('set nulls to all required undefined fields in connector secrets', () => {
|
||||
const connector: UserConfiguredActionConnector<{}, { webhookUrl: string }> = {
|
||||
secrets: {} as any,
|
||||
id: 'test',
|
||||
actionTypeId: '.slack',
|
||||
name: 'slack',
|
||||
config: {},
|
||||
isPreconfigured: false,
|
||||
};
|
||||
const secretsErrors = { webhookUrl: ['Webhook URL is required.'] };
|
||||
const configErrors = {};
|
||||
const baseConnectorErrors = {};
|
||||
getConnectorWithInvalidatedFields(connector, configErrors, secretsErrors, baseConnectorErrors);
|
||||
expect(connector.secrets.webhookUrl).toBeNull();
|
||||
});
|
||||
|
||||
test('set nulls to all required undefined fields in connector config', () => {
|
||||
const connector: UserConfiguredActionConnector<{ apiUrl: string }, {}> = {
|
||||
secrets: {},
|
||||
id: 'test',
|
||||
actionTypeId: '.jira',
|
||||
name: 'jira',
|
||||
config: {} as any,
|
||||
isPreconfigured: false,
|
||||
};
|
||||
const secretsErrors = {};
|
||||
const configErrors = { apiUrl: ['apiUrl is required'] };
|
||||
const baseConnectorErrors = {};
|
||||
getConnectorWithInvalidatedFields(connector, configErrors, secretsErrors, baseConnectorErrors);
|
||||
expect(connector.config.apiUrl).toBeNull();
|
||||
});
|
||||
|
||||
test('do not set nulls to the invalid fields with values in the connector properties, config and secrets', () => {
|
||||
const connector: UserConfiguredActionConnector<{}, { webhookUrl: string }> = {
|
||||
secrets: {
|
||||
webhookUrl: 'http://test',
|
||||
},
|
||||
id: 'test',
|
||||
actionTypeId: '.slack',
|
||||
name: 'slack',
|
||||
config: {},
|
||||
isPreconfigured: false,
|
||||
};
|
||||
const secretsErrors = { webhookUrl: ['Webhook URL must start with https://.'] };
|
||||
const configErrors = {};
|
||||
const baseConnectorErrors = {};
|
||||
getConnectorWithInvalidatedFields(connector, configErrors, secretsErrors, baseConnectorErrors);
|
||||
expect(connector.secrets.webhookUrl).toEqual('http://test');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getAlertWithInvalidatedFields', () => {
|
||||
test('set nulls to all required undefined fields in alert', () => {
|
||||
const alert: Alert = {
|
||||
params: {},
|
||||
consumer: 'test',
|
||||
schedule: {
|
||||
interval: '1m',
|
||||
},
|
||||
actions: [],
|
||||
tags: [],
|
||||
muteAll: false,
|
||||
enabled: false,
|
||||
mutedInstanceIds: [],
|
||||
} as any;
|
||||
const baseAlertErrors = { name: ['Name is required.'] };
|
||||
const actionsErrors = {};
|
||||
const paramsErrors = {};
|
||||
getAlertWithInvalidatedFields(alert, paramsErrors, baseAlertErrors, actionsErrors);
|
||||
expect(alert.name).toBeNull();
|
||||
});
|
||||
|
||||
test('set nulls to all required undefined fields in alert params', () => {
|
||||
const alert: Alert = {
|
||||
name: 'test',
|
||||
alertTypeId: '.threshold',
|
||||
id: '123',
|
||||
params: {},
|
||||
consumer: 'test',
|
||||
schedule: {
|
||||
interval: '1m',
|
||||
},
|
||||
actions: [],
|
||||
tags: [],
|
||||
muteAll: false,
|
||||
enabled: false,
|
||||
mutedInstanceIds: [],
|
||||
createdBy: '',
|
||||
apiKeyOwner: '',
|
||||
createdAt: new Date(),
|
||||
executionStatus: {
|
||||
status: 'ok',
|
||||
lastExecutionDate: new Date(),
|
||||
},
|
||||
notifyWhen: 'onActionGroupChange',
|
||||
throttle: '',
|
||||
updatedAt: new Date(),
|
||||
updatedBy: '',
|
||||
};
|
||||
const baseAlertErrors = {};
|
||||
const actionsErrors = {};
|
||||
const paramsErrors = { index: ['Index is required.'] };
|
||||
getAlertWithInvalidatedFields(alert, paramsErrors, baseAlertErrors, actionsErrors);
|
||||
expect(alert.params.index).toBeNull();
|
||||
});
|
||||
|
||||
test('do not set nulls to the invalid fields with values in the connector properties, config and secrets', () => {
|
||||
const alert: Alert = {
|
||||
name: 'test',
|
||||
id: '123',
|
||||
params: {},
|
||||
consumer: '@@@@',
|
||||
schedule: {
|
||||
interval: '1m',
|
||||
},
|
||||
actions: [],
|
||||
tags: [],
|
||||
muteAll: false,
|
||||
enabled: false,
|
||||
mutedInstanceIds: [],
|
||||
} as any;
|
||||
const baseAlertErrors = { consumer: ['Consumer is invalid.'] };
|
||||
const actionsErrors = {};
|
||||
const paramsErrors = {};
|
||||
getAlertWithInvalidatedFields(alert, paramsErrors, baseAlertErrors, actionsErrors);
|
||||
expect(alert.consumer).toEqual('@@@@');
|
||||
});
|
||||
|
||||
test('if complex alert action fields which is required is set to nulls if it is undefined', () => {
|
||||
const alert: Alert = {
|
||||
name: 'test',
|
||||
alertTypeId: '.threshold',
|
||||
id: '123',
|
||||
params: {},
|
||||
consumer: 'test',
|
||||
schedule: {
|
||||
interval: '1m',
|
||||
},
|
||||
actions: [
|
||||
{
|
||||
actionTypeId: 'test',
|
||||
group: 'qwer',
|
||||
id: '123',
|
||||
params: {
|
||||
incident: {
|
||||
field: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
tags: [],
|
||||
muteAll: false,
|
||||
enabled: false,
|
||||
mutedInstanceIds: [],
|
||||
createdBy: '',
|
||||
apiKeyOwner: '',
|
||||
createdAt: new Date(),
|
||||
executionStatus: {
|
||||
status: 'ok',
|
||||
lastExecutionDate: new Date(),
|
||||
},
|
||||
notifyWhen: 'onActionGroupChange',
|
||||
throttle: '',
|
||||
updatedAt: new Date(),
|
||||
updatedBy: '',
|
||||
};
|
||||
const baseAlertErrors = {};
|
||||
const actionsErrors = { '123': { 'incident.field.name': ['Name is required.'] } };
|
||||
const paramsErrors = {};
|
||||
getAlertWithInvalidatedFields(alert, paramsErrors, baseAlertErrors, actionsErrors);
|
||||
expect((alert.actions[0].params as any).incident.field.name).toBeNull();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -4,7 +4,8 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { constant } from 'lodash';
|
||||
import { constant, get, set } from 'lodash';
|
||||
import { UserConfiguredActionConnector, IErrorObject, Alert } from '../../types';
|
||||
|
||||
export function throwIfAbsent<T>(message: string) {
|
||||
return (value: T | undefined): T => {
|
||||
|
@ -43,3 +44,58 @@ export const isValidUrl = (urlString: string, protocol?: string) => {
|
|||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
export function getConnectorWithInvalidatedFields(
|
||||
connector: UserConfiguredActionConnector<Record<string, unknown>, Record<string, unknown>>,
|
||||
configErrors: IErrorObject,
|
||||
secretsErrors: IErrorObject,
|
||||
baseConnectorErrors: IErrorObject
|
||||
) {
|
||||
Object.keys(configErrors).forEach((errorKey) => {
|
||||
if (configErrors[errorKey].length >= 1 && get(connector.config, errorKey) === undefined) {
|
||||
set(connector.config, errorKey, null);
|
||||
}
|
||||
});
|
||||
Object.keys(secretsErrors).forEach((errorKey) => {
|
||||
if (secretsErrors[errorKey].length >= 1 && get(connector.secrets, errorKey) === undefined) {
|
||||
set(connector.secrets, errorKey, null);
|
||||
}
|
||||
});
|
||||
Object.keys(baseConnectorErrors).forEach((errorKey) => {
|
||||
if (baseConnectorErrors[errorKey].length >= 1 && get(connector, errorKey) === undefined) {
|
||||
set(connector, errorKey, null);
|
||||
}
|
||||
});
|
||||
return connector;
|
||||
}
|
||||
|
||||
export function getAlertWithInvalidatedFields(
|
||||
alert: Alert,
|
||||
paramsErrors: IErrorObject,
|
||||
baseAlertErrors: IErrorObject,
|
||||
actionsErrors: Record<string, IErrorObject>
|
||||
) {
|
||||
Object.keys(paramsErrors).forEach((errorKey) => {
|
||||
if (paramsErrors[errorKey].length >= 1 && get(alert.params, errorKey) === undefined) {
|
||||
set(alert.params, errorKey, null);
|
||||
}
|
||||
});
|
||||
Object.keys(baseAlertErrors).forEach((errorKey) => {
|
||||
if (baseAlertErrors[errorKey].length >= 1 && get(alert, errorKey) === undefined) {
|
||||
set(alert, errorKey, null);
|
||||
}
|
||||
});
|
||||
Object.keys(actionsErrors).forEach((actionId) => {
|
||||
const actionToValidate = alert.actions.find((action) => action.id === actionId);
|
||||
Object.keys(actionsErrors[actionId]).forEach((errorKey) => {
|
||||
if (
|
||||
actionToValidate &&
|
||||
actionsErrors[actionId][errorKey].length >= 1 &&
|
||||
get(actionToValidate!.params, errorKey) === undefined
|
||||
) {
|
||||
set(actionToValidate!.params, errorKey, null);
|
||||
}
|
||||
});
|
||||
});
|
||||
return alert;
|
||||
}
|
||||
|
|
|
@ -6,7 +6,11 @@
|
|||
import * as React from 'react';
|
||||
import { mountWithIntl } from '@kbn/test/jest';
|
||||
import { actionTypeRegistryMock } from '../../action_type_registry.mock';
|
||||
import { ValidationResult, UserConfiguredActionConnector } from '../../../types';
|
||||
import {
|
||||
UserConfiguredActionConnector,
|
||||
GenericValidationResult,
|
||||
ConnectorValidationResult,
|
||||
} from '../../../types';
|
||||
import { ActionConnectorForm } from './action_connector_form';
|
||||
const actionTypeRegistry = actionTypeRegistryMock.create();
|
||||
jest.mock('../../../common/lib/kibana');
|
||||
|
@ -17,10 +21,10 @@ describe('action_connector_form', () => {
|
|||
id: 'my-action-type',
|
||||
iconClass: 'test',
|
||||
selectMessage: 'test',
|
||||
validateConnector: (): ValidationResult => {
|
||||
return { errors: {} };
|
||||
validateConnector: (): ConnectorValidationResult<unknown, unknown> => {
|
||||
return {};
|
||||
},
|
||||
validateParams: (): ValidationResult => {
|
||||
validateParams: (): GenericValidationResult<unknown> => {
|
||||
const validationResult = { errors: {} };
|
||||
return validationResult;
|
||||
},
|
||||
|
|
|
@ -23,6 +23,7 @@ import {
|
|||
IErrorObject,
|
||||
ActionTypeRegistryContract,
|
||||
UserConfiguredActionConnector,
|
||||
ActionTypeModel,
|
||||
} from '../../../types';
|
||||
import { hasSaveActionsCapability } from '../../lib/capabilities';
|
||||
import { useKibana } from '../../../common/lib/kibana';
|
||||
|
@ -47,6 +48,31 @@ export function validateBaseProperties(actionObject: ActionConnector) {
|
|||
return validationResult;
|
||||
}
|
||||
|
||||
export function getConnectorErrors<ConnectorConfig, ConnectorSecrets>(
|
||||
connector: UserConfiguredActionConnector<ConnectorConfig, ConnectorSecrets>,
|
||||
actionTypeModel: ActionTypeModel
|
||||
) {
|
||||
const connectorValidationResult = actionTypeModel?.validateConnector(connector);
|
||||
const configErrors = (connectorValidationResult.config
|
||||
? connectorValidationResult.config.errors
|
||||
: {}) as IErrorObject;
|
||||
const secretsErrors = (connectorValidationResult.secrets
|
||||
? connectorValidationResult.secrets.errors
|
||||
: {}) as IErrorObject;
|
||||
const connectorBaseErrors = validateBaseProperties(connector).errors;
|
||||
const connectorErrors = {
|
||||
...configErrors,
|
||||
...secretsErrors,
|
||||
...connectorBaseErrors,
|
||||
} as IErrorObject;
|
||||
return {
|
||||
configErrors,
|
||||
secretsErrors,
|
||||
connectorBaseErrors,
|
||||
connectorErrors,
|
||||
};
|
||||
}
|
||||
|
||||
interface ActionConnectorProps<
|
||||
ConnectorConfig = Record<string, any>,
|
||||
ConnectorSecrets = Record<string, any>
|
||||
|
|
|
@ -8,7 +8,13 @@ import { mountWithIntl, nextTick } from '@kbn/test/jest';
|
|||
import { coreMock } from '../../../../../../../src/core/public/mocks';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { actionTypeRegistryMock } from '../../action_type_registry.mock';
|
||||
import { ValidationResult, Alert, AlertAction } from '../../../types';
|
||||
import {
|
||||
ValidationResult,
|
||||
Alert,
|
||||
AlertAction,
|
||||
ConnectorValidationResult,
|
||||
GenericValidationResult,
|
||||
} from '../../../types';
|
||||
import ActionForm from './action_form';
|
||||
import { useKibana } from '../../../common/lib/kibana';
|
||||
import {
|
||||
|
@ -44,10 +50,10 @@ describe('action_form', () => {
|
|||
id: 'my-action-type',
|
||||
iconClass: 'test',
|
||||
selectMessage: 'test',
|
||||
validateConnector: (): ValidationResult => {
|
||||
return { errors: {} };
|
||||
validateConnector: (): ConnectorValidationResult<unknown, unknown> => {
|
||||
return {};
|
||||
},
|
||||
validateParams: (): ValidationResult => {
|
||||
validateParams: (): GenericValidationResult<unknown> => {
|
||||
const validationResult = { errors: {} };
|
||||
return validationResult;
|
||||
},
|
||||
|
@ -59,10 +65,10 @@ describe('action_form', () => {
|
|||
id: 'disabled-by-config',
|
||||
iconClass: 'test',
|
||||
selectMessage: 'test',
|
||||
validateConnector: (): ValidationResult => {
|
||||
return { errors: {} };
|
||||
validateConnector: (): ConnectorValidationResult<unknown, unknown> => {
|
||||
return {};
|
||||
},
|
||||
validateParams: (): ValidationResult => {
|
||||
validateParams: (): GenericValidationResult<unknown> => {
|
||||
const validationResult = { errors: {} };
|
||||
return validationResult;
|
||||
},
|
||||
|
@ -74,8 +80,8 @@ describe('action_form', () => {
|
|||
id: '.jira',
|
||||
iconClass: 'test',
|
||||
selectMessage: 'test',
|
||||
validateConnector: (): ValidationResult => {
|
||||
return { errors: {} };
|
||||
validateConnector: (): ConnectorValidationResult<unknown, unknown> => {
|
||||
return {};
|
||||
},
|
||||
validateParams: (): ValidationResult => {
|
||||
const validationResult = { errors: {} };
|
||||
|
@ -89,10 +95,10 @@ describe('action_form', () => {
|
|||
id: 'disabled-by-license',
|
||||
iconClass: 'test',
|
||||
selectMessage: 'test',
|
||||
validateConnector: (): ValidationResult => {
|
||||
return { errors: {} };
|
||||
validateConnector: (): ConnectorValidationResult<unknown, unknown> => {
|
||||
return {};
|
||||
},
|
||||
validateParams: (): ValidationResult => {
|
||||
validateParams: (): GenericValidationResult<unknown> => {
|
||||
const validationResult = { errors: {} };
|
||||
return validationResult;
|
||||
},
|
||||
|
@ -104,10 +110,10 @@ describe('action_form', () => {
|
|||
id: 'preconfigured',
|
||||
iconClass: 'test',
|
||||
selectMessage: 'test',
|
||||
validateConnector: (): ValidationResult => {
|
||||
return { errors: {} };
|
||||
validateConnector: (): ConnectorValidationResult<unknown, unknown> => {
|
||||
return {};
|
||||
},
|
||||
validateParams: (): ValidationResult => {
|
||||
validateParams: (): GenericValidationResult<unknown> => {
|
||||
const validationResult = { errors: {} };
|
||||
return validationResult;
|
||||
},
|
||||
|
|
|
@ -8,7 +8,7 @@ import { mountWithIntl } from '@kbn/test/jest';
|
|||
import { coreMock } from '../../../../../../../src/core/public/mocks';
|
||||
import { actionTypeRegistryMock } from '../../action_type_registry.mock';
|
||||
import { ActionTypeMenu } from './action_type_menu';
|
||||
import { ValidationResult } from '../../../types';
|
||||
import { ConnectorValidationResult, GenericValidationResult } from '../../../types';
|
||||
import { useKibana } from '../../../common/lib/kibana';
|
||||
jest.mock('../../../common/lib/kibana');
|
||||
const actionTypeRegistry = actionTypeRegistryMock.create();
|
||||
|
@ -38,10 +38,10 @@ describe('connector_add_flyout', () => {
|
|||
id: 'my-action-type',
|
||||
iconClass: 'test',
|
||||
selectMessage: 'test',
|
||||
validateConnector: (): ValidationResult => {
|
||||
return { errors: {} };
|
||||
validateConnector: (): ConnectorValidationResult<unknown, unknown> => {
|
||||
return {};
|
||||
},
|
||||
validateParams: (): ValidationResult => {
|
||||
validateParams: (): GenericValidationResult<unknown> => {
|
||||
const validationResult = { errors: {} };
|
||||
return validationResult;
|
||||
},
|
||||
|
@ -75,10 +75,10 @@ describe('connector_add_flyout', () => {
|
|||
id: 'my-action-type',
|
||||
iconClass: 'test',
|
||||
selectMessage: 'test',
|
||||
validateConnector: (): ValidationResult => {
|
||||
return { errors: {} };
|
||||
validateConnector: (): ConnectorValidationResult<unknown, unknown> => {
|
||||
return {};
|
||||
},
|
||||
validateParams: (): ValidationResult => {
|
||||
validateParams: (): GenericValidationResult<unknown> => {
|
||||
const validationResult = { errors: {} };
|
||||
return validationResult;
|
||||
},
|
||||
|
@ -112,10 +112,10 @@ describe('connector_add_flyout', () => {
|
|||
id: 'my-action-type',
|
||||
iconClass: 'test',
|
||||
selectMessage: 'test',
|
||||
validateConnector: (): ValidationResult => {
|
||||
return { errors: {} };
|
||||
validateConnector: (): ConnectorValidationResult<unknown, unknown> => {
|
||||
return {};
|
||||
},
|
||||
validateParams: (): ValidationResult => {
|
||||
validateParams: (): GenericValidationResult<unknown> => {
|
||||
const validationResult = { errors: {} };
|
||||
return validationResult;
|
||||
},
|
||||
|
|
|
@ -8,7 +8,7 @@ import { mountWithIntl } from '@kbn/test/jest';
|
|||
import { coreMock } from '../../../../../../../src/core/public/mocks';
|
||||
import ConnectorAddFlyout from './connector_add_flyout';
|
||||
import { actionTypeRegistryMock } from '../../action_type_registry.mock';
|
||||
import { ValidationResult } from '../../../types';
|
||||
import { ConnectorValidationResult, GenericValidationResult } from '../../../types';
|
||||
import { useKibana } from '../../../common/lib/kibana';
|
||||
jest.mock('../../../common/lib/kibana');
|
||||
|
||||
|
@ -196,10 +196,10 @@ function createActionType() {
|
|||
id: `my-action-type-${++count}`,
|
||||
iconClass: 'test',
|
||||
selectMessage: 'test',
|
||||
validateConnector: (): ValidationResult => {
|
||||
return { errors: {} };
|
||||
validateConnector: (): ConnectorValidationResult<unknown, unknown> => {
|
||||
return {};
|
||||
},
|
||||
validateParams: (): ValidationResult => {
|
||||
validateParams: (): GenericValidationResult<unknown> => {
|
||||
const validationResult = { errors: {} };
|
||||
return validationResult;
|
||||
},
|
||||
|
|
|
@ -23,18 +23,14 @@ import {
|
|||
import { HttpSetup } from 'kibana/public';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { ActionTypeMenu } from './action_type_menu';
|
||||
import { ActionConnectorForm, validateBaseProperties } from './action_connector_form';
|
||||
import {
|
||||
ActionType,
|
||||
ActionConnector,
|
||||
IErrorObject,
|
||||
ActionTypeRegistryContract,
|
||||
} from '../../../types';
|
||||
import { ActionConnectorForm, getConnectorErrors } from './action_connector_form';
|
||||
import { ActionType, ActionConnector, ActionTypeRegistryContract } from '../../../types';
|
||||
import { connectorReducer } from './connector_reducer';
|
||||
import { hasSaveActionsCapability } from '../../lib/capabilities';
|
||||
import { createActionConnector } from '../../lib/action_connector_api';
|
||||
import { VIEW_LICENSE_OPTIONS_LINK } from '../../../common/constants';
|
||||
import { useKibana } from '../../../common/lib/kibana';
|
||||
import { getConnectorWithInvalidatedFields } from '../../lib/value_validators';
|
||||
|
||||
export interface ConnectorAddFlyoutProps {
|
||||
onClose: () => void;
|
||||
|
@ -91,6 +87,7 @@ const ConnectorAddFlyout: React.FunctionComponent<ConnectorAddFlyoutProps> = ({
|
|||
|
||||
let currentForm;
|
||||
let actionTypeModel;
|
||||
let saveButton;
|
||||
if (!actionType) {
|
||||
currentForm = (
|
||||
<ActionTypeMenu
|
||||
|
@ -103,64 +100,121 @@ const ConnectorAddFlyout: React.FunctionComponent<ConnectorAddFlyoutProps> = ({
|
|||
} else {
|
||||
actionTypeModel = actionTypeRegistry.get(actionType.id);
|
||||
|
||||
const errors = {
|
||||
...actionTypeModel?.validateConnector(connector).errors,
|
||||
...validateBaseProperties(connector).errors,
|
||||
} as IErrorObject;
|
||||
hasErrors = !!Object.keys(errors).find((errorKey) => errors[errorKey].length >= 1);
|
||||
const {
|
||||
configErrors,
|
||||
connectorBaseErrors,
|
||||
connectorErrors,
|
||||
secretsErrors,
|
||||
} = getConnectorErrors(connector, actionTypeModel);
|
||||
hasErrors = !!Object.keys(connectorErrors).find(
|
||||
(errorKey) => connectorErrors[errorKey].length >= 1
|
||||
);
|
||||
|
||||
currentForm = (
|
||||
<ActionConnectorForm
|
||||
actionTypeName={actionType.name}
|
||||
connector={connector}
|
||||
dispatch={dispatch}
|
||||
errors={errors}
|
||||
errors={connectorErrors}
|
||||
actionTypeRegistry={actionTypeRegistry}
|
||||
consumer={consumer}
|
||||
/>
|
||||
);
|
||||
}
|
||||
const onActionConnectorSave = async (): Promise<ActionConnector | undefined> =>
|
||||
await createActionConnector({ http, connector })
|
||||
.then((savedConnector) => {
|
||||
if (toasts) {
|
||||
toasts.addSuccess(
|
||||
i18n.translate(
|
||||
'xpack.triggersActionsUI.sections.addConnectorForm.updateSuccessNotificationText',
|
||||
{
|
||||
defaultMessage: "Created '{connectorName}'",
|
||||
values: {
|
||||
connectorName: savedConnector.name,
|
||||
},
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
return savedConnector;
|
||||
})
|
||||
.catch((errorRes) => {
|
||||
toasts.addDanger(
|
||||
errorRes.body?.message ??
|
||||
i18n.translate(
|
||||
'xpack.triggersActionsUI.sections.addConnectorForm.updateErrorNotificationText',
|
||||
{ defaultMessage: 'Cannot create a connector.' }
|
||||
)
|
||||
);
|
||||
return undefined;
|
||||
});
|
||||
|
||||
const onSaveClicked = async () => {
|
||||
setIsSaving(true);
|
||||
const savedAction = await onActionConnectorSave();
|
||||
setIsSaving(false);
|
||||
if (savedAction) {
|
||||
closeFlyout();
|
||||
if (reloadConnectors) {
|
||||
await reloadConnectors();
|
||||
const onActionConnectorSave = async (): Promise<ActionConnector | undefined> =>
|
||||
await createActionConnector({ http, connector })
|
||||
.then((savedConnector) => {
|
||||
if (toasts) {
|
||||
toasts.addSuccess(
|
||||
i18n.translate(
|
||||
'xpack.triggersActionsUI.sections.addConnectorForm.updateSuccessNotificationText',
|
||||
{
|
||||
defaultMessage: "Created '{connectorName}'",
|
||||
values: {
|
||||
connectorName: savedConnector.name,
|
||||
},
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
return savedConnector;
|
||||
})
|
||||
.catch((errorRes) => {
|
||||
toasts.addDanger(
|
||||
errorRes.body?.message ??
|
||||
i18n.translate(
|
||||
'xpack.triggersActionsUI.sections.addConnectorForm.updateErrorNotificationText',
|
||||
{ defaultMessage: 'Cannot create a connector.' }
|
||||
)
|
||||
);
|
||||
return undefined;
|
||||
});
|
||||
|
||||
const onSaveClicked = async () => {
|
||||
if (hasErrors) {
|
||||
setConnector(
|
||||
getConnectorWithInvalidatedFields(
|
||||
connector,
|
||||
configErrors,
|
||||
secretsErrors,
|
||||
connectorBaseErrors
|
||||
)
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
return savedAction;
|
||||
};
|
||||
setIsSaving(true);
|
||||
const savedAction = await onActionConnectorSave();
|
||||
setIsSaving(false);
|
||||
if (savedAction) {
|
||||
closeFlyout();
|
||||
if (reloadConnectors) {
|
||||
await reloadConnectors();
|
||||
}
|
||||
}
|
||||
return savedAction;
|
||||
};
|
||||
|
||||
saveButton = (
|
||||
<Fragment>
|
||||
{onTestConnector && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
color="secondary"
|
||||
data-test-subj="saveAndTestNewActionButton"
|
||||
type="submit"
|
||||
isLoading={isSaving}
|
||||
onClick={async () => {
|
||||
const savedConnector = await onSaveClicked();
|
||||
if (savedConnector) {
|
||||
onTestConnector(savedConnector);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.triggersActionsUI.sections.actionConnectorAdd.saveAndTestButtonLabel"
|
||||
defaultMessage="Save & Test"
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
fill
|
||||
color="secondary"
|
||||
data-test-subj="saveNewActionButton"
|
||||
type="submit"
|
||||
isLoading={isSaving}
|
||||
onClick={onSaveClicked}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.triggersActionsUI.sections.actionConnectorAdd.saveButtonLabel"
|
||||
defaultMessage="Save"
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiFlyout onClose={closeFlyout} aria-labelledby="flyoutActionAddTitle" size="m">
|
||||
|
@ -245,48 +299,7 @@ const ConnectorAddFlyout: React.FunctionComponent<ConnectorAddFlyoutProps> = ({
|
|||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexGroup justifyContent="spaceBetween">
|
||||
{canSave && actionTypeModel && actionType ? (
|
||||
<Fragment>
|
||||
{onTestConnector && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
color="secondary"
|
||||
data-test-subj="saveAndTestNewActionButton"
|
||||
type="submit"
|
||||
isDisabled={hasErrors}
|
||||
isLoading={isSaving}
|
||||
onClick={async () => {
|
||||
const savedConnector = await onSaveClicked();
|
||||
if (savedConnector) {
|
||||
onTestConnector(savedConnector);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.triggersActionsUI.sections.actionConnectorAdd.saveAndTestButtonLabel"
|
||||
defaultMessage="Save & Test"
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
fill
|
||||
color="secondary"
|
||||
data-test-subj="saveNewActionButton"
|
||||
type="submit"
|
||||
isDisabled={hasErrors}
|
||||
isLoading={isSaving}
|
||||
onClick={onSaveClicked}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.triggersActionsUI.sections.actionConnectorAdd.saveButtonLabel"
|
||||
defaultMessage="Save"
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</Fragment>
|
||||
) : null}
|
||||
{canSave && actionTypeModel && actionType ? saveButton : null}
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
|
|
@ -7,7 +7,7 @@ import * as React from 'react';
|
|||
import { mountWithIntl } from '@kbn/test/jest';
|
||||
import { ConnectorAddModal } from './connector_add_modal';
|
||||
import { actionTypeRegistryMock } from '../../action_type_registry.mock';
|
||||
import { ValidationResult, ActionType } from '../../../types';
|
||||
import { ActionType, ConnectorValidationResult, GenericValidationResult } from '../../../types';
|
||||
import { useKibana } from '../../../common/lib/kibana';
|
||||
import { coreMock } from '../../../../../../../src/core/public/mocks';
|
||||
|
||||
|
@ -37,10 +37,10 @@ describe('connector_add_modal', () => {
|
|||
id: 'my-action-type',
|
||||
iconClass: 'test',
|
||||
selectMessage: 'test',
|
||||
validateConnector: (): ValidationResult => {
|
||||
return { errors: {} };
|
||||
validateConnector: (): ConnectorValidationResult<unknown, unknown> => {
|
||||
return {};
|
||||
},
|
||||
validateParams: (): ValidationResult => {
|
||||
validateParams: (): GenericValidationResult<unknown> => {
|
||||
const validationResult = { errors: {} };
|
||||
return validationResult;
|
||||
},
|
||||
|
|
|
@ -17,18 +17,14 @@ import {
|
|||
import { EuiButtonEmpty } from '@elastic/eui';
|
||||
import { EuiOverlayMask } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { ActionConnectorForm, validateBaseProperties } from './action_connector_form';
|
||||
import { ActionConnectorForm, getConnectorErrors } from './action_connector_form';
|
||||
import { connectorReducer } from './connector_reducer';
|
||||
import { createActionConnector } from '../../lib/action_connector_api';
|
||||
import './connector_add_modal.scss';
|
||||
import { hasSaveActionsCapability } from '../../lib/capabilities';
|
||||
import {
|
||||
ActionType,
|
||||
ActionConnector,
|
||||
IErrorObject,
|
||||
ActionTypeRegistryContract,
|
||||
} from '../../../types';
|
||||
import { ActionType, ActionConnector, ActionTypeRegistryContract } from '../../../types';
|
||||
import { useKibana } from '../../../common/lib/kibana';
|
||||
import { getConnectorWithInvalidatedFields } from '../../lib/value_validators';
|
||||
|
||||
interface ConnectorAddModalProps {
|
||||
actionType: ActionType;
|
||||
|
@ -80,11 +76,13 @@ export const ConnectorAddModal = ({
|
|||
}, [initialConnector, onClose]);
|
||||
|
||||
const actionTypeModel = actionTypeRegistry.get(actionType.id);
|
||||
const errors = {
|
||||
...actionTypeModel?.validateConnector(connector).errors,
|
||||
...validateBaseProperties(connector).errors,
|
||||
} as IErrorObject;
|
||||
hasErrors = !!Object.keys(errors).find((errorKey) => errors[errorKey].length >= 1);
|
||||
const { configErrors, connectorBaseErrors, connectorErrors, secretsErrors } = getConnectorErrors(
|
||||
connector,
|
||||
actionTypeModel
|
||||
);
|
||||
hasErrors = !!Object.keys(connectorErrors).find(
|
||||
(errorKey) => connectorErrors[errorKey].length >= 1
|
||||
);
|
||||
|
||||
const onActionConnectorSave = async (): Promise<ActionConnector | undefined> =>
|
||||
await createActionConnector({ http, connector })
|
||||
|
@ -143,7 +141,7 @@ export const ConnectorAddModal = ({
|
|||
actionTypeName={actionType.name}
|
||||
dispatch={dispatch}
|
||||
serverError={serverError}
|
||||
errors={errors}
|
||||
errors={connectorErrors}
|
||||
actionTypeRegistry={actionTypeRegistry}
|
||||
consumer={consumer}
|
||||
/>
|
||||
|
@ -164,9 +162,19 @@ export const ConnectorAddModal = ({
|
|||
data-test-subj="saveActionButtonModal"
|
||||
type="submit"
|
||||
iconType="check"
|
||||
isDisabled={hasErrors}
|
||||
isLoading={isSaving}
|
||||
onClick={async () => {
|
||||
if (hasErrors) {
|
||||
setConnector(
|
||||
getConnectorWithInvalidatedFields(
|
||||
connector,
|
||||
configErrors,
|
||||
secretsErrors,
|
||||
connectorBaseErrors
|
||||
)
|
||||
);
|
||||
return;
|
||||
}
|
||||
setIsSaving(true);
|
||||
const savedAction = await onActionConnectorSave();
|
||||
setIsSaving(false);
|
||||
|
|
|
@ -7,7 +7,7 @@ import * as React from 'react';
|
|||
import { mountWithIntl } from '@kbn/test/jest';
|
||||
import { coreMock } from '../../../../../../../src/core/public/mocks';
|
||||
import { actionTypeRegistryMock } from '../../action_type_registry.mock';
|
||||
import { ValidationResult } from '../../../types';
|
||||
import { ConnectorValidationResult, GenericValidationResult } from '../../../types';
|
||||
import ConnectorEditFlyout from './connector_edit_flyout';
|
||||
import { useKibana } from '../../../common/lib/kibana';
|
||||
|
||||
|
@ -49,10 +49,10 @@ describe('connector_edit_flyout', () => {
|
|||
id: 'test-action-type-id',
|
||||
iconClass: 'test',
|
||||
selectMessage: 'test',
|
||||
validateConnector: (): ValidationResult => {
|
||||
return { errors: {} };
|
||||
validateConnector: (): ConnectorValidationResult<unknown, unknown> => {
|
||||
return {};
|
||||
},
|
||||
validateParams: (): ValidationResult => {
|
||||
validateParams: (): GenericValidationResult<unknown> => {
|
||||
const validationResult = { errors: {} };
|
||||
return validationResult;
|
||||
},
|
||||
|
@ -93,10 +93,10 @@ describe('connector_edit_flyout', () => {
|
|||
id: 'test-action-type-id',
|
||||
iconClass: 'test',
|
||||
selectMessage: 'test',
|
||||
validateConnector: (): ValidationResult => {
|
||||
return { errors: {} };
|
||||
validateConnector: (): ConnectorValidationResult<unknown, unknown> => {
|
||||
return {};
|
||||
},
|
||||
validateParams: (): ValidationResult => {
|
||||
validateParams: (): GenericValidationResult<unknown> => {
|
||||
const validationResult = { errors: {} };
|
||||
return validationResult;
|
||||
},
|
||||
|
|
|
@ -24,9 +24,9 @@ import {
|
|||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { Option, none, some } from 'fp-ts/lib/Option';
|
||||
import { ActionConnectorForm, validateBaseProperties } from './action_connector_form';
|
||||
import { ActionConnectorForm, getConnectorErrors } from './action_connector_form';
|
||||
import { TestConnectorForm } from './test_connector_form';
|
||||
import { ActionConnector, ActionTypeRegistryContract, IErrorObject } from '../../../types';
|
||||
import { ActionConnector, ActionTypeRegistryContract } from '../../../types';
|
||||
import { connectorReducer } from './connector_reducer';
|
||||
import { updateActionConnector, executeAction } from '../../lib/action_connector_api';
|
||||
import { hasSaveActionsCapability } from '../../lib/capabilities';
|
||||
|
@ -36,6 +36,7 @@ import {
|
|||
} from '../../../../../actions/common';
|
||||
import './connector_edit_flyout.scss';
|
||||
import { useKibana } from '../../../common/lib/kibana';
|
||||
import { getConnectorWithInvalidatedFields } from '../../lib/value_validators';
|
||||
|
||||
export interface ConnectorEditFlyoutProps {
|
||||
initialConnector: ActionConnector;
|
||||
|
@ -108,14 +109,22 @@ export const ConnectorEditFlyout = ({
|
|||
}, [onClose]);
|
||||
|
||||
const actionTypeModel = actionTypeRegistry.get(connector.actionTypeId);
|
||||
const errorsInConnectorConfig = (!connector.isPreconfigured
|
||||
? {
|
||||
...actionTypeModel?.validateConnector(connector).errors,
|
||||
...validateBaseProperties(connector).errors,
|
||||
}
|
||||
: {}) as IErrorObject;
|
||||
const hasErrorsInConnectorConfig = !!Object.keys(errorsInConnectorConfig).find(
|
||||
(errorKey) => errorsInConnectorConfig[errorKey].length >= 1
|
||||
const {
|
||||
configErrors,
|
||||
connectorBaseErrors,
|
||||
connectorErrors,
|
||||
secretsErrors,
|
||||
} = !connector.isPreconfigured
|
||||
? getConnectorErrors(connector, actionTypeModel)
|
||||
: {
|
||||
configErrors: {},
|
||||
connectorBaseErrors: {},
|
||||
connectorErrors: {},
|
||||
secretsErrors: {},
|
||||
};
|
||||
|
||||
const hasErrors = !!Object.keys(connectorErrors).find(
|
||||
(errorKey) => connectorErrors[errorKey].length >= 1
|
||||
);
|
||||
|
||||
const onActionConnectorSave = async (): Promise<ActionConnector | undefined> =>
|
||||
|
@ -209,6 +218,18 @@ export const ConnectorEditFlyout = ({
|
|||
};
|
||||
|
||||
const onSaveClicked = async (closeAfterSave: boolean = true) => {
|
||||
if (hasErrors) {
|
||||
setConnector(
|
||||
'connector',
|
||||
getConnectorWithInvalidatedFields(
|
||||
connector,
|
||||
configErrors,
|
||||
secretsErrors,
|
||||
connectorBaseErrors
|
||||
)
|
||||
);
|
||||
return;
|
||||
}
|
||||
setIsSaving(true);
|
||||
const savedAction = await onActionConnectorSave();
|
||||
setIsSaving(false);
|
||||
|
@ -260,7 +281,7 @@ export const ConnectorEditFlyout = ({
|
|||
!connector.isPreconfigured ? (
|
||||
<ActionConnectorForm
|
||||
connector={connector}
|
||||
errors={errorsInConnectorConfig}
|
||||
errors={connectorErrors}
|
||||
actionTypeName={connector.actionType}
|
||||
dispatch={(changes) => {
|
||||
setHasChanges(true);
|
||||
|
@ -326,7 +347,6 @@ export const ConnectorEditFlyout = ({
|
|||
<EuiButton
|
||||
color="secondary"
|
||||
data-test-subj="saveEditedActionButton"
|
||||
isDisabled={hasErrorsInConnectorConfig || !hasChanges}
|
||||
isLoading={isSaving || isExecutingAction}
|
||||
onClick={async () => {
|
||||
await onSaveClicked(false);
|
||||
|
@ -344,7 +364,6 @@ export const ConnectorEditFlyout = ({
|
|||
color="secondary"
|
||||
data-test-subj="saveAndCloseEditedActionButton"
|
||||
type="submit"
|
||||
isDisabled={hasErrorsInConnectorConfig || !hasChanges}
|
||||
isLoading={isSaving || isExecutingAction}
|
||||
onClick={async () => {
|
||||
await onSaveClicked();
|
||||
|
|
|
@ -7,7 +7,11 @@ import React, { lazy } from 'react';
|
|||
import { I18nProvider } from '@kbn/i18n/react';
|
||||
import TestConnectorForm from './test_connector_form';
|
||||
import { none, some } from 'fp-ts/lib/Option';
|
||||
import { ActionConnector, ValidationResult } from '../../../types';
|
||||
import {
|
||||
ActionConnector,
|
||||
ConnectorValidationResult,
|
||||
GenericValidationResult,
|
||||
} from '../../../types';
|
||||
import { actionTypeRegistryMock } from '../../action_type_registry.mock';
|
||||
import { EuiFormRow, EuiFieldText, EuiText, EuiLink, EuiForm, EuiSelect } from '@elastic/eui';
|
||||
import { mountWithIntl } from '@kbn/test/jest';
|
||||
|
@ -47,10 +51,10 @@ const actionType = {
|
|||
id: 'my-action-type',
|
||||
iconClass: 'test',
|
||||
selectMessage: 'test',
|
||||
validateConnector: (): ValidationResult => {
|
||||
return { errors: {} };
|
||||
validateConnector: (): ConnectorValidationResult<unknown, unknown> => {
|
||||
return {};
|
||||
},
|
||||
validateParams: (): ValidationResult => {
|
||||
validateParams: (): GenericValidationResult<unknown> => {
|
||||
const validationResult = { errors: {} };
|
||||
return validationResult;
|
||||
},
|
||||
|
|
|
@ -20,7 +20,7 @@ import { Option, map, getOrElse } from 'fp-ts/lib/Option';
|
|||
import { pipe } from 'fp-ts/lib/pipeable';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { ActionConnector, ActionTypeRegistryContract } from '../../../types';
|
||||
import { ActionConnector, ActionTypeRegistryContract, IErrorObject } from '../../../types';
|
||||
import { ActionTypeExecutorResult } from '../../../../../actions/common';
|
||||
|
||||
export interface ConnectorAddFlyoutProps {
|
||||
|
@ -47,8 +47,8 @@ export const TestConnectorForm = ({
|
|||
const actionTypeModel = actionTypeRegistry.get(connector.actionTypeId);
|
||||
const ParamsFieldsComponent = actionTypeModel.actionParamsFields;
|
||||
|
||||
const actionErrors = actionTypeModel?.validateParams(actionParams);
|
||||
const hasErrors = !!Object.values(actionErrors.errors).find((errors) => errors.length > 0);
|
||||
const actionErrors = actionTypeModel?.validateParams(actionParams).errors as IErrorObject;
|
||||
const hasErrors = !!Object.values(actionErrors).find((errors) => errors.length > 0);
|
||||
|
||||
const steps = [
|
||||
{
|
||||
|
@ -67,7 +67,7 @@ export const TestConnectorForm = ({
|
|||
<ParamsFieldsComponent
|
||||
actionParams={actionParams}
|
||||
index={0}
|
||||
errors={actionErrors.errors}
|
||||
errors={actionErrors}
|
||||
editAction={(field, value) =>
|
||||
setActionParams({
|
||||
...actionParams,
|
||||
|
|
|
@ -14,7 +14,11 @@ import { actionTypeRegistryMock } from '../../../action_type_registry.mock';
|
|||
import { useKibana } from '../../../../common/lib/kibana';
|
||||
|
||||
jest.mock('../../../../common/lib/kibana');
|
||||
import { ActionConnector } from '../../../../types';
|
||||
import {
|
||||
ActionConnector,
|
||||
ConnectorValidationResult,
|
||||
GenericValidationResult,
|
||||
} from '../../../../types';
|
||||
import { times } from 'lodash';
|
||||
|
||||
jest.mock('../../../lib/action_connector_api', () => ({
|
||||
|
@ -145,6 +149,26 @@ describe('actions_connectors_list component with items', () => {
|
|||
},
|
||||
] = await mocks.getStartServices();
|
||||
|
||||
const mockedActionParamsFields = React.lazy(async () => ({
|
||||
default() {
|
||||
return <React.Fragment />;
|
||||
},
|
||||
}));
|
||||
|
||||
actionTypeRegistry.get.mockReturnValue({
|
||||
id: 'test',
|
||||
iconClass: 'test',
|
||||
selectMessage: 'test',
|
||||
validateConnector: (): ConnectorValidationResult<unknown, unknown> => {
|
||||
return {};
|
||||
},
|
||||
validateParams: (): GenericValidationResult<unknown> => {
|
||||
const validationResult = { errors: {} };
|
||||
return validationResult;
|
||||
},
|
||||
actionConnectorFields: null,
|
||||
actionParamsFields: mockedActionParamsFields,
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
useKibanaMock().services.actionTypeRegistry = actionTypeRegistry;
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
|
|
|
@ -11,7 +11,12 @@ import { EuiFormLabel } from '@elastic/eui';
|
|||
import { coreMock } from '../../../../../../../src/core/public/mocks';
|
||||
import AlertAdd from './alert_add';
|
||||
import { actionTypeRegistryMock } from '../../action_type_registry.mock';
|
||||
import { Alert, ValidationResult } from '../../../types';
|
||||
import {
|
||||
Alert,
|
||||
ConnectorValidationResult,
|
||||
GenericValidationResult,
|
||||
ValidationResult,
|
||||
} from '../../../types';
|
||||
import { alertTypeRegistryMock } from '../../alert_type_registry.mock';
|
||||
import { ReactWrapper } from 'enzyme';
|
||||
import { ALERTS_FEATURE_ID } from '../../../../../alerts/common';
|
||||
|
@ -107,10 +112,10 @@ describe('alert_add', () => {
|
|||
id: 'my-action-type',
|
||||
iconClass: 'test',
|
||||
selectMessage: 'test',
|
||||
validateConnector: (): ValidationResult => {
|
||||
return { errors: {} };
|
||||
validateConnector: (): ConnectorValidationResult<unknown, unknown> => {
|
||||
return {};
|
||||
},
|
||||
validateParams: (): ValidationResult => {
|
||||
validateParams: (): GenericValidationResult<unknown> => {
|
||||
const validationResult = { errors: {} };
|
||||
return validationResult;
|
||||
},
|
||||
|
|
|
@ -10,11 +10,10 @@ import { i18n } from '@kbn/i18n';
|
|||
import {
|
||||
ActionTypeRegistryContract,
|
||||
Alert,
|
||||
AlertAction,
|
||||
AlertTypeRegistryContract,
|
||||
IErrorObject,
|
||||
AlertUpdates,
|
||||
} from '../../../types';
|
||||
import { AlertForm, isValidAlert, validateBaseProperties } from './alert_form';
|
||||
import { AlertForm, getAlertErrors, isValidAlert } from './alert_form';
|
||||
import { alertReducer, InitialAlert, InitialAlertReducer } from './alert_reducer';
|
||||
import { createAlert } from '../../lib/alert_api';
|
||||
import { HealthCheck } from '../../components/health_check';
|
||||
|
@ -23,6 +22,7 @@ import { hasShowActionsCapability } from '../../lib/capabilities';
|
|||
import AlertAddFooter from './alert_add_footer';
|
||||
import { HealthContextProvider } from '../../context/health_context';
|
||||
import { useKibana } from '../../../common/lib/kibana';
|
||||
import { getAlertWithInvalidatedFields } from '../../lib/value_validators';
|
||||
|
||||
export interface AlertAddProps<MetaData = Record<string, any>> {
|
||||
consumer: string;
|
||||
|
@ -86,7 +86,9 @@ const AlertAdd = ({
|
|||
const canShowActions = hasShowActionsCapability(capabilities);
|
||||
|
||||
useEffect(() => {
|
||||
setAlertProperty('alertTypeId', alertTypeId ?? null);
|
||||
if (alertTypeId) {
|
||||
setAlertProperty('alertTypeId', alertTypeId);
|
||||
}
|
||||
}, [alertTypeId]);
|
||||
|
||||
const closeFlyout = useCallback(() => {
|
||||
|
@ -106,42 +108,27 @@ const AlertAdd = ({
|
|||
};
|
||||
|
||||
const alertType = alert.alertTypeId ? alertTypeRegistry.get(alert.alertTypeId) : null;
|
||||
const errors = {
|
||||
...(alertType ? alertType.validate(alert.params).errors : []),
|
||||
...validateBaseProperties(alert).errors,
|
||||
} as IErrorObject;
|
||||
const hasErrors = !isValidAlert(alert, errors);
|
||||
|
||||
const actionsErrors: Array<{
|
||||
errors: IErrorObject;
|
||||
}> = alert.actions.map((alertAction: AlertAction) =>
|
||||
actionTypeRegistry.get(alertAction.actionTypeId)?.validateParams(alertAction.params)
|
||||
const { alertActionsErrors, alertBaseErrors, alertErrors, alertParamsErrors } = getAlertErrors(
|
||||
alert as Alert,
|
||||
actionTypeRegistry,
|
||||
alertType
|
||||
);
|
||||
|
||||
const hasActionErrors =
|
||||
actionsErrors.find(
|
||||
(errorObj: { errors: IErrorObject }) =>
|
||||
errorObj &&
|
||||
!!Object.keys(errorObj.errors).find((errorKey) => errorObj.errors[errorKey].length >= 1)
|
||||
) !== undefined;
|
||||
|
||||
// Confirm before saving if user is able to add actions but hasn't added any to this alert
|
||||
const shouldConfirmSave = canShowActions && alert.actions?.length === 0;
|
||||
|
||||
async function onSaveAlert(): Promise<Alert | undefined> {
|
||||
try {
|
||||
if (isValidAlert(alert, errors)) {
|
||||
const newAlert = await createAlert({ http, alert });
|
||||
toasts.addSuccess(
|
||||
i18n.translate('xpack.triggersActionsUI.sections.alertAdd.saveSuccessNotificationText', {
|
||||
defaultMessage: 'Created alert "{alertName}"',
|
||||
values: {
|
||||
alertName: newAlert.name,
|
||||
},
|
||||
})
|
||||
);
|
||||
return newAlert;
|
||||
}
|
||||
const newAlert = await createAlert({ http, alert: alert as AlertUpdates });
|
||||
toasts.addSuccess(
|
||||
i18n.translate('xpack.triggersActionsUI.sections.alertAdd.saveSuccessNotificationText', {
|
||||
defaultMessage: 'Created alert "{alertName}"',
|
||||
values: {
|
||||
alertName: newAlert.name,
|
||||
},
|
||||
})
|
||||
);
|
||||
return newAlert;
|
||||
} catch (errorRes) {
|
||||
toasts.addDanger(
|
||||
errorRes.body?.message ??
|
||||
|
@ -176,7 +163,7 @@ const AlertAdd = ({
|
|||
<AlertForm
|
||||
alert={alert}
|
||||
dispatch={dispatch}
|
||||
errors={errors}
|
||||
errors={alertErrors}
|
||||
canChangeTrigger={canChangeTrigger}
|
||||
operation={i18n.translate(
|
||||
'xpack.triggersActionsUI.sections.alertAdd.operationName',
|
||||
|
@ -191,9 +178,20 @@ const AlertAdd = ({
|
|||
</EuiFlyoutBody>
|
||||
<AlertAddFooter
|
||||
isSaving={isSaving}
|
||||
hasErrors={hasErrors || hasActionErrors}
|
||||
onSave={async () => {
|
||||
setIsSaving(true);
|
||||
if (!isValidAlert(alert, alertErrors, alertActionsErrors)) {
|
||||
setAlert(
|
||||
getAlertWithInvalidatedFields(
|
||||
alert as Alert,
|
||||
alertParamsErrors,
|
||||
alertBaseErrors,
|
||||
alertActionsErrors
|
||||
)
|
||||
);
|
||||
setIsSaving(false);
|
||||
return;
|
||||
}
|
||||
if (shouldConfirmSave) {
|
||||
setIsConfirmAlertSaveModalOpen(true);
|
||||
} else {
|
||||
|
|
|
@ -17,12 +17,11 @@ import { useHealthContext } from '../../context/health_context';
|
|||
|
||||
interface AlertAddFooterProps {
|
||||
isSaving: boolean;
|
||||
hasErrors: boolean;
|
||||
onSave: () => void;
|
||||
onCancel: () => void;
|
||||
}
|
||||
|
||||
export const AlertAddFooter = ({ isSaving, hasErrors, onSave, onCancel }: AlertAddFooterProps) => {
|
||||
export const AlertAddFooter = ({ isSaving, onSave, onCancel }: AlertAddFooterProps) => {
|
||||
const { loadingHealthCheck } = useHealthContext();
|
||||
|
||||
return (
|
||||
|
@ -42,7 +41,7 @@ export const AlertAddFooter = ({ isSaving, hasErrors, onSave, onCancel }: AlertA
|
|||
data-test-subj="saveAlertButton"
|
||||
type="submit"
|
||||
iconType="check"
|
||||
isDisabled={hasErrors || loadingHealthCheck}
|
||||
isDisabled={loadingHealthCheck}
|
||||
isLoading={isSaving}
|
||||
onClick={onSave}
|
||||
>
|
||||
|
|
|
@ -8,7 +8,12 @@ import { mountWithIntl, nextTick } from '@kbn/test/jest';
|
|||
import { act } from 'react-dom/test-utils';
|
||||
import { coreMock } from '../../../../../../../src/core/public/mocks';
|
||||
import { actionTypeRegistryMock } from '../../action_type_registry.mock';
|
||||
import { ValidationResult, Alert } from '../../../types';
|
||||
import {
|
||||
ValidationResult,
|
||||
Alert,
|
||||
ConnectorValidationResult,
|
||||
GenericValidationResult,
|
||||
} from '../../../types';
|
||||
import { alertTypeRegistryMock } from '../../alert_type_registry.mock';
|
||||
import { ReactWrapper } from 'enzyme';
|
||||
import AlertEdit from './alert_edit';
|
||||
|
@ -65,10 +70,10 @@ describe('alert_edit', () => {
|
|||
id: 'my-action-type',
|
||||
iconClass: 'test',
|
||||
selectMessage: 'test',
|
||||
validateConnector: (): ValidationResult => {
|
||||
return { errors: {} };
|
||||
validateConnector: (): ConnectorValidationResult<unknown, unknown> => {
|
||||
return {};
|
||||
},
|
||||
validateParams: (): ValidationResult => {
|
||||
validateParams: (): GenericValidationResult<unknown> => {
|
||||
const validationResult = { errors: {} };
|
||||
return validationResult;
|
||||
},
|
||||
|
|
|
@ -20,19 +20,14 @@ import {
|
|||
EuiSpacer,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
ActionTypeRegistryContract,
|
||||
Alert,
|
||||
AlertAction,
|
||||
AlertTypeRegistryContract,
|
||||
IErrorObject,
|
||||
} from '../../../types';
|
||||
import { AlertForm, validateBaseProperties } from './alert_form';
|
||||
import { ActionTypeRegistryContract, Alert, AlertTypeRegistryContract } from '../../../types';
|
||||
import { AlertForm, getAlertErrors, isValidAlert } from './alert_form';
|
||||
import { alertReducer, ConcreteAlertReducer } from './alert_reducer';
|
||||
import { updateAlert } from '../../lib/alert_api';
|
||||
import { HealthCheck } from '../../components/health_check';
|
||||
import { HealthContextProvider } from '../../context/health_context';
|
||||
import { useKibana } from '../../../common/lib/kibana';
|
||||
import { getAlertWithInvalidatedFields } from '../../lib/value_validators';
|
||||
|
||||
export interface AlertEditProps<MetaData = Record<string, any>> {
|
||||
initialAlert: Alert;
|
||||
|
@ -64,40 +59,41 @@ export const AlertEdit = ({
|
|||
http,
|
||||
notifications: { toasts },
|
||||
} = useKibana().services;
|
||||
const setAlert = (value: Alert) => {
|
||||
dispatch({ command: { type: 'setAlert' }, payload: { key: 'alert', value } });
|
||||
};
|
||||
|
||||
const alertType = alertTypeRegistry.get(alert.alertTypeId);
|
||||
|
||||
const errors = {
|
||||
...(alertType ? alertType.validate(alert.params).errors : []),
|
||||
...validateBaseProperties(alert).errors,
|
||||
} as IErrorObject;
|
||||
const hasErrors = !!Object.keys(errors).find((errorKey) => errors[errorKey].length >= 1);
|
||||
|
||||
const actionsErrors: Array<{
|
||||
errors: IErrorObject;
|
||||
}> = alert.actions.map((alertAction: AlertAction) =>
|
||||
actionTypeRegistry.get(alertAction.actionTypeId)?.validateParams(alertAction.params)
|
||||
const { alertActionsErrors, alertBaseErrors, alertErrors, alertParamsErrors } = getAlertErrors(
|
||||
alert as Alert,
|
||||
actionTypeRegistry,
|
||||
alertType
|
||||
);
|
||||
|
||||
const hasActionErrors =
|
||||
actionsErrors.find(
|
||||
(errorObj: { errors: IErrorObject }) =>
|
||||
errorObj &&
|
||||
!!Object.keys(errorObj.errors).find((errorKey) => errorObj.errors[errorKey].length >= 1)
|
||||
) !== undefined;
|
||||
|
||||
async function onSaveAlert(): Promise<Alert | undefined> {
|
||||
try {
|
||||
const newAlert = await updateAlert({ http, alert, id: alert.id });
|
||||
toasts.addSuccess(
|
||||
i18n.translate('xpack.triggersActionsUI.sections.alertEdit.saveSuccessNotificationText', {
|
||||
defaultMessage: "Updated '{alertName}'",
|
||||
values: {
|
||||
alertName: newAlert.name,
|
||||
},
|
||||
})
|
||||
);
|
||||
return newAlert;
|
||||
if (isValidAlert(alert, alertErrors, alertActionsErrors) && !hasActionsWithBrokenConnector) {
|
||||
const newAlert = await updateAlert({ http, alert, id: alert.id });
|
||||
toasts.addSuccess(
|
||||
i18n.translate('xpack.triggersActionsUI.sections.alertEdit.saveSuccessNotificationText', {
|
||||
defaultMessage: "Updated '{alertName}'",
|
||||
values: {
|
||||
alertName: newAlert.name,
|
||||
},
|
||||
})
|
||||
);
|
||||
return newAlert;
|
||||
} else {
|
||||
setAlert(
|
||||
getAlertWithInvalidatedFields(
|
||||
alert as Alert,
|
||||
alertParamsErrors,
|
||||
alertBaseErrors,
|
||||
alertActionsErrors
|
||||
)
|
||||
);
|
||||
}
|
||||
} catch (errorRes) {
|
||||
toasts.addDanger(
|
||||
errorRes.body?.message ??
|
||||
|
@ -147,7 +143,7 @@ export const AlertEdit = ({
|
|||
<AlertForm
|
||||
alert={alert}
|
||||
dispatch={dispatch}
|
||||
errors={errors}
|
||||
errors={alertErrors}
|
||||
actionTypeRegistry={actionTypeRegistry}
|
||||
alertTypeRegistry={alertTypeRegistry}
|
||||
canChangeTrigger={false}
|
||||
|
@ -181,7 +177,6 @@ export const AlertEdit = ({
|
|||
data-test-subj="saveEditedAlertButton"
|
||||
type="submit"
|
||||
iconType="check"
|
||||
isDisabled={hasErrors || hasActionErrors || hasActionsWithBrokenConnector}
|
||||
isLoading={isSaving}
|
||||
onClick={async () => {
|
||||
setIsSaving(true);
|
||||
|
|
|
@ -9,7 +9,13 @@ import { ReactWrapper } from 'enzyme';
|
|||
import { act } from 'react-dom/test-utils';
|
||||
import { actionTypeRegistryMock } from '../../action_type_registry.mock';
|
||||
import { alertTypeRegistryMock } from '../../alert_type_registry.mock';
|
||||
import { ValidationResult, Alert, AlertType } from '../../../types';
|
||||
import {
|
||||
ValidationResult,
|
||||
Alert,
|
||||
AlertType,
|
||||
ConnectorValidationResult,
|
||||
GenericValidationResult,
|
||||
} from '../../../types';
|
||||
import { AlertForm } from './alert_form';
|
||||
import { coreMock } from 'src/core/public/mocks';
|
||||
import { ALERTS_FEATURE_ID, RecoveredActionGroup } from '../../../../../alerts/common';
|
||||
|
@ -39,10 +45,17 @@ describe('alert_form', () => {
|
|||
id: 'my-action-type',
|
||||
iconClass: 'test',
|
||||
selectMessage: 'test',
|
||||
validateConnector: (): ValidationResult => {
|
||||
return { errors: {} };
|
||||
validateConnector: (): ConnectorValidationResult<unknown, unknown> => {
|
||||
return {
|
||||
config: {
|
||||
errors: {},
|
||||
},
|
||||
secrets: {
|
||||
errors: {},
|
||||
},
|
||||
};
|
||||
},
|
||||
validateParams: (): ValidationResult => {
|
||||
validateParams: (): GenericValidationResult<unknown> => {
|
||||
const validationResult = { errors: {} };
|
||||
return validationResult;
|
||||
},
|
||||
|
@ -152,7 +165,7 @@ describe('alert_form', () => {
|
|||
alertTypeRegistry.has.mockReturnValue(true);
|
||||
actionTypeRegistry.list.mockReturnValue([actionType]);
|
||||
actionTypeRegistry.has.mockReturnValue(true);
|
||||
|
||||
actionTypeRegistry.get.mockReturnValue(actionType);
|
||||
const initialAlert = ({
|
||||
name: 'test',
|
||||
params: {},
|
||||
|
@ -171,7 +184,7 @@ describe('alert_form', () => {
|
|||
<AlertForm
|
||||
alert={initialAlert}
|
||||
dispatch={() => {}}
|
||||
errors={{ name: [], interval: [] }}
|
||||
errors={{ name: [], interval: [], alertTypeId: [] }}
|
||||
operation="create"
|
||||
actionTypeRegistry={actionTypeRegistry}
|
||||
alertTypeRegistry={alertTypeRegistry}
|
||||
|
@ -323,6 +336,7 @@ describe('alert_form', () => {
|
|||
},
|
||||
]);
|
||||
alertTypeRegistry.has.mockReturnValue(true);
|
||||
actionTypeRegistry.get.mockReturnValue(actionType);
|
||||
|
||||
const initialAlert = ({
|
||||
name: 'non alerting consumer test',
|
||||
|
@ -342,7 +356,7 @@ describe('alert_form', () => {
|
|||
<AlertForm
|
||||
alert={initialAlert}
|
||||
dispatch={() => {}}
|
||||
errors={{ name: [], interval: [] }}
|
||||
errors={{ name: [], interval: [], alertTypeId: [] }}
|
||||
operation="create"
|
||||
actionTypeRegistry={actionTypeRegistry}
|
||||
alertTypeRegistry={alertTypeRegistry}
|
||||
|
@ -404,7 +418,7 @@ describe('alert_form', () => {
|
|||
<AlertForm
|
||||
alert={initialAlert}
|
||||
dispatch={() => {}}
|
||||
errors={{ name: [], interval: [] }}
|
||||
errors={{ name: [], interval: [], alertTypeId: [] }}
|
||||
operation="create"
|
||||
actionTypeRegistry={actionTypeRegistry}
|
||||
alertTypeRegistry={alertTypeRegistry}
|
||||
|
|
|
@ -31,6 +31,7 @@ import {
|
|||
EuiNotificationBadge,
|
||||
EuiErrorBoundary,
|
||||
EuiToolTip,
|
||||
EuiCallOut,
|
||||
} from '@elastic/eui';
|
||||
import { capitalize, isObject } from 'lodash';
|
||||
import { KibanaFeature } from '../../../../../features/public';
|
||||
|
@ -100,24 +101,71 @@ export function validateBaseProperties(alertObject: InitialAlert): ValidationRes
|
|||
if (!alertObject.alertTypeId) {
|
||||
errors.alertTypeId.push(
|
||||
i18n.translate('xpack.triggersActionsUI.sections.alertForm.error.requiredAlertTypeIdText', {
|
||||
defaultMessage: 'Alert trigger is required.',
|
||||
defaultMessage: 'Alert type is required.',
|
||||
})
|
||||
);
|
||||
}
|
||||
const emptyConnectorActions = alertObject.actions.find(
|
||||
(actionItem) => /^\d+$/.test(actionItem.id) && Object.keys(actionItem.params).length > 0
|
||||
);
|
||||
if (emptyConnectorActions !== undefined) {
|
||||
errors.actionConnectors.push(
|
||||
i18n.translate('xpack.triggersActionsUI.sections.alertForm.error.requiredActionConnector', {
|
||||
defaultMessage: 'Action connector for {actionTypeId} is required.',
|
||||
values: { actionTypeId: emptyConnectorActions.actionTypeId },
|
||||
})
|
||||
);
|
||||
}
|
||||
return validationResult;
|
||||
}
|
||||
|
||||
const hasErrors: (errors: IErrorObject) => boolean = (errors) =>
|
||||
export function getAlertErrors(
|
||||
alert: Alert,
|
||||
actionTypeRegistry: ActionTypeRegistryContract,
|
||||
alertTypeModel: AlertTypeModel | null
|
||||
) {
|
||||
const alertParamsErrors: IErrorObject = alertTypeModel
|
||||
? alertTypeModel.validate(alert.params).errors
|
||||
: [];
|
||||
const alertBaseErrors = validateBaseProperties(alert).errors as IErrorObject;
|
||||
const alertErrors = {
|
||||
...alertParamsErrors,
|
||||
...alertBaseErrors,
|
||||
} as IErrorObject;
|
||||
|
||||
const alertActionsErrors = alert.actions.reduce((prev, alertAction: AlertAction) => {
|
||||
return {
|
||||
...prev,
|
||||
[alertAction.id]: actionTypeRegistry
|
||||
.get(alertAction.actionTypeId)
|
||||
?.validateParams(alertAction.params).errors,
|
||||
};
|
||||
}, {}) as Record<string, IErrorObject>;
|
||||
return {
|
||||
alertParamsErrors,
|
||||
alertBaseErrors,
|
||||
alertActionsErrors,
|
||||
alertErrors,
|
||||
};
|
||||
}
|
||||
|
||||
export const hasObjectErrors: (errors: IErrorObject) => boolean = (errors) =>
|
||||
!!Object.values(errors).find((errorList) => {
|
||||
if (isObject(errorList)) return hasErrors(errorList as IErrorObject);
|
||||
if (isObject(errorList)) return hasObjectErrors(errorList as IErrorObject);
|
||||
return errorList.length >= 1;
|
||||
});
|
||||
|
||||
export function isValidAlert(
|
||||
alertObject: InitialAlert | Alert,
|
||||
validationResult: IErrorObject
|
||||
validationResult: IErrorObject,
|
||||
actionsErrors: Record<string, IErrorObject>
|
||||
): alertObject is Alert {
|
||||
return !hasErrors(validationResult);
|
||||
return (
|
||||
!hasObjectErrors(validationResult) &&
|
||||
Object.keys(actionsErrors).find((actionErrorsKey) =>
|
||||
hasObjectErrors(actionsErrors[actionErrorsKey])
|
||||
) === undefined
|
||||
);
|
||||
}
|
||||
|
||||
function getProducerFeatureName(producer: string, kibanaFeatures: KibanaFeature[]) {
|
||||
|
@ -558,33 +606,42 @@ export const AlertForm = ({
|
|||
alertTypeModel &&
|
||||
alert.alertTypeId &&
|
||||
selectedAlertType ? (
|
||||
<ActionForm
|
||||
actions={alert.actions}
|
||||
setHasActionsDisabled={setHasActionsDisabled}
|
||||
setHasActionsWithBrokenConnector={setHasActionsWithBrokenConnector}
|
||||
messageVariables={selectedAlertType.actionVariables}
|
||||
defaultActionGroupId={defaultActionGroupId}
|
||||
isActionGroupDisabledForActionType={(actionGroupId: string, actionTypeId: string) =>
|
||||
isActionGroupDisabledForActionType(selectedAlertType, actionGroupId, actionTypeId)
|
||||
}
|
||||
actionGroups={selectedAlertType.actionGroups.map((actionGroup) =>
|
||||
actionGroup.id === selectedAlertType.recoveryActionGroup.id
|
||||
? {
|
||||
...actionGroup,
|
||||
omitOptionalMessageVariables: true,
|
||||
defaultActionMessage: recoveredActionGroupMessage,
|
||||
}
|
||||
: { ...actionGroup, defaultActionMessage: alertTypeModel?.defaultActionMessage }
|
||||
)}
|
||||
getDefaultActionParams={getDefaultActionParams}
|
||||
setActionIdByIndex={(id: string, index: number) => setActionProperty('id', id, index)}
|
||||
setActionGroupIdByIndex={(group: string, index: number) =>
|
||||
setActionProperty('group', group, index)
|
||||
}
|
||||
setActions={setActions}
|
||||
setActionParamsProperty={setActionParamsProperty}
|
||||
actionTypeRegistry={actionTypeRegistry}
|
||||
/>
|
||||
<>
|
||||
{errors.actionConnectors.length >= 1 ? (
|
||||
<Fragment>
|
||||
<EuiSpacer />
|
||||
<EuiCallOut color="danger" size="s" title={errors.actionConnectors} />
|
||||
<EuiSpacer />
|
||||
</Fragment>
|
||||
) : null}
|
||||
<ActionForm
|
||||
actions={alert.actions}
|
||||
setHasActionsDisabled={setHasActionsDisabled}
|
||||
setHasActionsWithBrokenConnector={setHasActionsWithBrokenConnector}
|
||||
messageVariables={selectedAlertType.actionVariables}
|
||||
defaultActionGroupId={defaultActionGroupId}
|
||||
isActionGroupDisabledForActionType={(actionGroupId: string, actionTypeId: string) =>
|
||||
isActionGroupDisabledForActionType(selectedAlertType, actionGroupId, actionTypeId)
|
||||
}
|
||||
actionGroups={selectedAlertType.actionGroups.map((actionGroup) =>
|
||||
actionGroup.id === selectedAlertType.recoveryActionGroup.id
|
||||
? {
|
||||
...actionGroup,
|
||||
omitOptionalMessageVariables: true,
|
||||
defaultActionMessage: recoveredActionGroupMessage,
|
||||
}
|
||||
: { ...actionGroup, defaultActionMessage: alertTypeModel?.defaultActionMessage }
|
||||
)}
|
||||
getDefaultActionParams={getDefaultActionParams}
|
||||
setActionIdByIndex={(id: string, index: number) => setActionProperty('id', id, index)}
|
||||
setActionGroupIdByIndex={(group: string, index: number) =>
|
||||
setActionProperty('group', group, index)
|
||||
}
|
||||
setActions={setActions}
|
||||
setActionParamsProperty={setActionParamsProperty}
|
||||
actionTypeRegistry={actionTypeRegistry}
|
||||
/>
|
||||
</>
|
||||
) : null}
|
||||
</Fragment>
|
||||
);
|
||||
|
@ -801,6 +858,13 @@ export const AlertForm = ({
|
|||
</EuiFlexGroup>
|
||||
</EuiFormRow>
|
||||
<EuiSpacer />
|
||||
{errors.alertTypeId.length >= 1 && alert.alertTypeId !== undefined ? (
|
||||
<Fragment>
|
||||
<EuiSpacer />
|
||||
<EuiCallOut color="danger" size="s" title={errors.alertTypeId} />
|
||||
<EuiSpacer />
|
||||
</Fragment>
|
||||
) : null}
|
||||
{alertTypeNodes}
|
||||
</Fragment>
|
||||
) : alertTypesIndex ? (
|
||||
|
|
|
@ -5,7 +5,13 @@
|
|||
*/
|
||||
|
||||
import { TypeRegistry } from './type_registry';
|
||||
import { ValidationResult, AlertTypeModel, ActionTypeModel } from '../types';
|
||||
import {
|
||||
ValidationResult,
|
||||
AlertTypeModel,
|
||||
ActionTypeModel,
|
||||
ConnectorValidationResult,
|
||||
GenericValidationResult,
|
||||
} from '../types';
|
||||
import { actionTypeRegistryMock } from './action_type_registry.mock';
|
||||
|
||||
export const ExpressionComponent: React.FunctionComponent = () => {
|
||||
|
@ -35,10 +41,10 @@ const getTestActionType = (
|
|||
id: id || 'my-action-type',
|
||||
iconClass: iconClass || 'test',
|
||||
selectMessage: selectedMessage || 'test',
|
||||
validateConnector: (): ValidationResult => {
|
||||
return { errors: {} };
|
||||
validateConnector: (): ConnectorValidationResult<unknown, unknown> => {
|
||||
return {};
|
||||
},
|
||||
validateParams: (): ValidationResult => {
|
||||
validateParams: (): GenericValidationResult<unknown> => {
|
||||
const validationResult = { errors: {} };
|
||||
return validationResult;
|
||||
},
|
||||
|
|
|
@ -79,8 +79,10 @@ export interface ActionTypeModel<ActionConfig = any, ActionSecrets = any, Action
|
|||
actionTypeTitle?: string;
|
||||
validateConnector: (
|
||||
connector: UserConfiguredActionConnector<ActionConfig, ActionSecrets>
|
||||
) => ValidationResult;
|
||||
validateParams: (actionParams: any) => ValidationResult;
|
||||
) => ConnectorValidationResult<Partial<ActionConfig>, Partial<ActionSecrets>>;
|
||||
validateParams: (
|
||||
actionParams: ActionParams
|
||||
) => GenericValidationResult<Partial<ActionParams> | unknown>;
|
||||
actionConnectorFields: React.LazyExoticComponent<
|
||||
ComponentType<
|
||||
ActionConnectorFieldsProps<UserConfiguredActionConnector<ActionConfig, ActionSecrets>>
|
||||
|
@ -89,10 +91,19 @@ export interface ActionTypeModel<ActionConfig = any, ActionSecrets = any, Action
|
|||
actionParamsFields: React.LazyExoticComponent<ComponentType<ActionParamsProps<ActionParams>>>;
|
||||
}
|
||||
|
||||
export interface GenericValidationResult<T> {
|
||||
errors: Record<Extract<keyof T, string>, string[] | unknown>;
|
||||
}
|
||||
|
||||
export interface ValidationResult {
|
||||
errors: Record<string, any>;
|
||||
}
|
||||
|
||||
export interface ConnectorValidationResult<Config, Secrets> {
|
||||
config?: GenericValidationResult<Config>;
|
||||
secrets?: GenericValidationResult<Secrets>;
|
||||
}
|
||||
|
||||
interface ActionConnectorProps<Config, Secrets> {
|
||||
secrets: Secrets;
|
||||
id: string;
|
||||
|
|
|
@ -256,6 +256,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
|
|||
const searchResultsBeforeEdit = await pageObjects.triggersActionsUI.getConnectorsList();
|
||||
expect(searchResultsBeforeEdit.length).to.eql(1);
|
||||
|
||||
expect(await testSubjects.exists('preConfiguredTitleMessage')).to.be(true);
|
||||
await find.clickByCssSelector('[data-test-subj="connectorsTableCell-name"] button');
|
||||
|
||||
expect(await testSubjects.exists('preconfiguredBadge')).to.be(true);
|
||||
|
|
Loading…
Reference in a new issue