Skip to content

Commit

Permalink
[Actions] makes savedObjectId field optional (#79186)
Browse files Browse the repository at this point in the history
This PR makes the `savedObjectId` parameter optional in the Jira, ServiceNow and IBM Resilient Connectors.
This allows them to execute without this field outside of Alerts, as it is currently populated using the `alertId` which isn't available in other places.
Additionally this adds an optional field in the `Params` Components for all three of the connectors, which allows users to provide a value for the `savedObjectId` field if the so wish.
  • Loading branch information
gmmorris authored Oct 5, 2020
1 parent de130ab commit 4fdf2f1
Show file tree
Hide file tree
Showing 16 changed files with 296 additions and 84 deletions.
16 changes: 15 additions & 1 deletion x-pack/plugins/actions/common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,25 @@ export interface ActionResult {
}

// the result returned from an action type executor function
const ActionTypeExecutorResultStatusValues = ['ok', 'error'] as const;
type ActionTypeExecutorResultStatus = typeof ActionTypeExecutorResultStatusValues[number];

export interface ActionTypeExecutorResult<Data> {
actionId: string;
status: 'ok' | 'error';
status: ActionTypeExecutorResultStatus;
message?: string;
serviceMessage?: string;
data?: Data;
retry?: null | boolean | Date;
}

export function isActionTypeExecutorResult(
result: unknown
): result is ActionTypeExecutorResult<unknown> {
const unsafeResult = result as ActionTypeExecutorResult<unknown>;
return (
unsafeResult &&
typeof unsafeResult?.actionId === 'string' &&
ActionTypeExecutorResultStatusValues.includes(unsafeResult?.status)
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export const ExecutorSubActionSchema = schema.oneOf([
]);

export const ExecutorSubActionPushParamsSchema = schema.object({
savedObjectId: schema.string(),
savedObjectId: schema.nullable(schema.string()),
title: schema.string(),
description: schema.nullable(schema.string()),
externalId: schema.nullable(schema.string()),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export const ExecutorSubActionSchema = schema.oneOf([
]);

export const ExecutorSubActionPushParamsSchema = schema.object({
savedObjectId: schema.string(),
savedObjectId: schema.nullable(schema.string()),
title: schema.string(),
description: schema.nullable(schema.string()),
externalId: schema.nullable(schema.string()),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export const ExecutorSubActionSchema = schema.oneOf([
]);

export const ExecutorSubActionPushParamsSchema = schema.object({
savedObjectId: schema.string(),
savedObjectId: schema.nullable(schema.string()),
title: schema.string(),
description: schema.nullable(schema.string()),
comment: schema.nullable(schema.string()),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import { fromNullable, Option } from 'fp-ts/lib/Option';
import { ActionVariable } from '../../../types';

export function extractActionVariable(
actionVariables: ActionVariable[],
variableName: string
): Option<ActionVariable> {
return fromNullable(actionVariables?.find((variable) => variable.name === variableName));
}
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ describe('JiraParamsFields renders', () => {
errors={{ title: [] }}
editAction={() => {}}
index={0}
messageVariables={[]}
messageVariables={[{ name: 'alertId', description: '' }]}
docLinks={{ ELASTIC_WEBSITE_URL: '', DOC_LINK_VERSION: '' } as DocLinksStart}
toastNotifications={mocks.notifications.toasts}
http={mocks.http}
Expand All @@ -107,6 +107,27 @@ describe('JiraParamsFields renders', () => {
expect(wrapper.find('[data-test-subj="descriptionTextArea"]').length > 0).toBeTruthy();
expect(wrapper.find('[data-test-subj="labelsComboBox"]').length > 0).toBeTruthy();
expect(wrapper.find('[data-test-subj="commentsTextArea"]').length > 0).toBeTruthy();

// ensure savedObjectIdInput isnt rendered
expect(wrapper.find('[data-test-subj="savedObjectIdInput"]').length === 0).toBeTruthy();
});

test('the savedObjectId fields is rendered if we cant find an alertId in the messageVariables', () => {
const wrapper = mountWithIntl(
<JiraParamsFields
actionParams={actionParams}
errors={{ title: [] }}
editAction={() => {}}
index={0}
messageVariables={[]}
docLinks={{ ELASTIC_WEBSITE_URL: '', DOC_LINK_VERSION: '' } as DocLinksStart}
toastNotifications={mocks.notifications.toasts}
http={mocks.http}
actionConnector={connector}
/>
);

expect(wrapper.find('[data-test-subj="savedObjectIdInput"]').length > 0).toBeTruthy();
});

test('it shows loading when loading issue types', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,20 @@

import React, { Fragment, useEffect, useState, useMemo } from 'react';
import { map } from 'lodash/fp';
import { EuiFormRow, EuiComboBox, EuiSelectOption, EuiHorizontalRule } from '@elastic/eui';
import { isSome } from 'fp-ts/lib/Option';
import { i18n } from '@kbn/i18n';
import { EuiSelect } from '@elastic/eui';
import { EuiFlexGroup } from '@elastic/eui';
import { EuiFlexItem } from '@elastic/eui';
import { EuiSpacer } from '@elastic/eui';
import {
EuiFormRow,
EuiComboBox,
EuiSelectOption,
EuiHorizontalRule,
EuiSelect,
EuiFormControlLayout,
EuiIconTip,
EuiFlexGroup,
EuiFlexItem,
EuiSpacer,
} from '@elastic/eui';

import { ActionParamsProps } from '../../../../types';
import { TextAreaWithMessageVariables } from '../../text_area_with_message_variables';
Expand All @@ -20,6 +28,7 @@ import { JiraActionParams } from './types';
import { useGetIssueTypes } from './use_get_issue_types';
import { useGetFieldsByIssueType } from './use_get_fields_by_issue_type';
import { SearchIssues } from './search_issues';
import { extractActionVariable } from '../extract_action_variable';

const JiraParamsFields: React.FunctionComponent<ActionParamsProps<JiraActionParams>> = ({
actionParams,
Expand All @@ -38,6 +47,10 @@ const JiraParamsFields: React.FunctionComponent<ActionParamsProps<JiraActionPara
const [firstLoad, setFirstLoad] = useState(false);
const [prioritiesSelectOptions, setPrioritiesSelectOptions] = useState<EuiSelectOption[]>([]);

const isActionBeingConfiguredByAnAlert = messageVariables
? isSome(extractActionVariable(messageVariables, 'alertId'))
: false;

useEffect(() => {
setFirstLoad(true);
}, []);
Expand Down Expand Up @@ -127,7 +140,7 @@ const JiraParamsFields: React.FunctionComponent<ActionParamsProps<JiraActionPara
if (!actionParams.subAction) {
editAction('subAction', 'pushToService', index);
}
if (!savedObjectId && messageVariables?.find((variable) => variable.name === 'alertId')) {
if (!savedObjectId && isActionBeingConfiguredByAnAlert) {
editSubActionProperty('savedObjectId', '{{alertId}}');
}
// eslint-disable-next-line react-hooks/exhaustive-deps
Expand Down Expand Up @@ -261,6 +274,45 @@ const JiraParamsFields: React.FunctionComponent<ActionParamsProps<JiraActionPara
/>
</EuiFormRow>
<EuiSpacer size="m" />
{!isActionBeingConfiguredByAnAlert && (
<Fragment>
<EuiFormRow
fullWidth
label={i18n.translate(
'xpack.triggersActionsUI.components.builtinActionTypes.jira.savedObjectIdFieldLabel',
{
defaultMessage: 'Object ID (optional)',
}
)}
>
<EuiFlexItem>
<EuiFormControlLayout
fullWidth
append={
<EuiIconTip
content={i18n.translate(
'xpack.triggersActionsUI.components.builtinActionTypes.jira.savedObjectIdFieldHelp',
{
defaultMessage:
'JIRA will associate this action with the ID of a Kibana saved object.',
}
)}
/>
}
>
<TextFieldWithMessageVariables
index={index}
editAction={editSubActionProperty}
messageVariables={messageVariables}
paramsProperty={'savedObjectId'}
inputTargetValue={savedObjectId}
/>
</EuiFormControlLayout>
</EuiFlexItem>
</EuiFormRow>
<EuiSpacer size="m" />
</Fragment>
)}
{hasLabels && (
<>
<EuiFlexGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ describe('ResilientParamsFields renders', () => {
errors={{ title: [] }}
editAction={() => {}}
index={0}
messageVariables={[]}
messageVariables={[{ name: 'alertId', description: '' }]}
docLinks={{ ELASTIC_WEBSITE_URL: '', DOC_LINK_VERSION: '' } as DocLinksStart}
toastNotifications={mocks.notifications.toasts}
http={mocks.http}
Expand All @@ -100,6 +100,27 @@ describe('ResilientParamsFields renders', () => {
expect(wrapper.find('[data-test-subj="titleInput"]').length > 0).toBeTruthy();
expect(wrapper.find('[data-test-subj="descriptionTextArea"]').length > 0).toBeTruthy();
expect(wrapper.find('[data-test-subj="commentsTextArea"]').length > 0).toBeTruthy();

// ensure savedObjectIdInput isnt rendered
expect(wrapper.find('[data-test-subj="savedObjectIdInput"]').length === 0).toBeTruthy();
});

test('the savedObjectId fields is rendered if we cant find an alertId in the messageVariables', () => {
const wrapper = mountWithIntl(
<ResilientParamsFields
actionParams={actionParams}
errors={{ title: [] }}
editAction={() => {}}
index={0}
messageVariables={[]}
docLinks={{ ELASTIC_WEBSITE_URL: '', DOC_LINK_VERSION: '' } as DocLinksStart}
toastNotifications={mocks.notifications.toasts}
http={mocks.http}
actionConnector={connector}
/>
);

expect(wrapper.find('[data-test-subj="savedObjectIdInput"]').length > 0).toBeTruthy();
});

test('it shows loading when loading incident types', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,11 @@ import {
EuiTitle,
EuiComboBoxOptionOption,
EuiSelectOption,
EuiFormControlLayout,
EuiIconTip,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { isSome } from 'fp-ts/lib/Option';

import { ActionParamsProps } from '../../../../types';
import { ResilientActionParams } from './types';
Expand All @@ -23,6 +26,7 @@ import { TextFieldWithMessageVariables } from '../../text_field_with_message_var

import { useGetIncidentTypes } from './use_get_incident_types';
import { useGetSeverity } from './use_get_severity';
import { extractActionVariable } from '../extract_action_variable';

const ResilientParamsFields: React.FunctionComponent<ActionParamsProps<ResilientActionParams>> = ({
actionParams,
Expand All @@ -38,6 +42,10 @@ const ResilientParamsFields: React.FunctionComponent<ActionParamsProps<Resilient
const { title, description, comments, incidentTypes, severityCode, savedObjectId } =
actionParams.subActionParams || {};

const isActionBeingConfiguredByAnAlert = messageVariables
? isSome(extractActionVariable(messageVariables, 'alertId'))
: false;

const [incidentTypesComboBoxOptions, setIncidentTypesComboBoxOptions] = useState<
Array<EuiComboBoxOptionOption<string>>
>([]);
Expand Down Expand Up @@ -98,7 +106,7 @@ const ResilientParamsFields: React.FunctionComponent<ActionParamsProps<Resilient
if (!actionParams.subAction) {
editAction('subAction', 'pushToService', index);
}
if (!savedObjectId && messageVariables?.find((variable) => variable.name === 'alertId')) {
if (!savedObjectId && isActionBeingConfiguredByAnAlert) {
editSubActionProperty('savedObjectId', '{{alertId}}');
}
// eslint-disable-next-line react-hooks/exhaustive-deps
Expand Down Expand Up @@ -218,6 +226,43 @@ const ResilientParamsFields: React.FunctionComponent<ActionParamsProps<Resilient
errors={errors.title as string[]}
/>
</EuiFormRow>
{!isActionBeingConfiguredByAnAlert && (
<Fragment>
<EuiFormRow
fullWidth
label={i18n.translate(
'xpack.triggersActionsUI.components.builtinActionTypes.resilient.savedObjectIdFieldLabel',
{
defaultMessage: 'Object ID (optional)',
}
)}
>
<EuiFormControlLayout
fullWidth
append={
<EuiIconTip
content={i18n.translate(
'xpack.triggersActionsUI.components.builtinActionTypes.resilient.savedObjectIdFieldHelp',
{
defaultMessage:
'IBM Resilient will associate this action with the ID of a Kibana saved object.',
}
)}
/>
}
>
<TextFieldWithMessageVariables
index={index}
editAction={editSubActionProperty}
messageVariables={messageVariables}
paramsProperty={'savedObjectId'}
inputTargetValue={savedObjectId}
/>
</EuiFormControlLayout>
</EuiFormRow>
<EuiSpacer size="m" />
</Fragment>
)}
<TextAreaWithMessageVariables
index={index}
editAction={editSubActionProperty}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ describe('ServiceNowParamsFields renders', () => {
errors={{ title: [] }}
editAction={() => {}}
index={0}
messageVariables={[]}
messageVariables={[{ name: 'alertId', description: '' }]}
docLinks={{ ELASTIC_WEBSITE_URL: '', DOC_LINK_VERSION: '' } as DocLinksStart}
toastNotifications={mocks.notifications.toasts}
http={mocks.http}
Expand All @@ -46,5 +46,41 @@ describe('ServiceNowParamsFields renders', () => {
expect(wrapper.find('[data-test-subj="titleInput"]').length > 0).toBeTruthy();
expect(wrapper.find('[data-test-subj="descriptionTextArea"]').length > 0).toBeTruthy();
expect(wrapper.find('[data-test-subj="commentTextArea"]').length > 0).toBeTruthy();

// ensure savedObjectIdInput isnt rendered
expect(wrapper.find('[data-test-subj="savedObjectIdInput"]').length === 0).toBeTruthy();
});

test('the savedObjectId fields is rendered if we cant find an alertId in the messageVariables', () => {
const mocks = coreMock.createSetup();
const actionParams = {
subAction: 'pushToService',
subActionParams: {
title: 'sn title',
description: 'some description',
comment: 'comment for sn',
severity: '1',
urgency: '2',
impact: '3',
savedObjectId: '123',
externalId: null,
},
};

const wrapper = mountWithIntl(
<ServiceNowParamsFields
actionParams={actionParams}
errors={{ title: [] }}
editAction={() => {}}
index={0}
messageVariables={[]}
docLinks={{ ELASTIC_WEBSITE_URL: '', DOC_LINK_VERSION: '' } as DocLinksStart}
toastNotifications={mocks.notifications.toasts}
http={mocks.http}
/>
);

// ensure savedObjectIdInput isnt rendered
expect(wrapper.find('[data-test-subj="savedObjectIdInput"]').length > 0).toBeTruthy();
});
});
Loading

0 comments on commit 4fdf2f1

Please sign in to comment.