Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Actions] makes savedObjectId field optional #79186

Merged
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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()),
gmmorris marked this conversation as resolved.
Show resolved Hide resolved
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()),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm curious if this includes the SO type and spaceId. We can of course assume the spaceId for now. Is the type encoded in the string, or assumed to be alert? Could use a comment describing it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not touching the behaviour in this PR, just focusing on decoupling it from Alerting so that it's technically possible to execute.
It is in theory assuming this is an Alert, but apparently it isn't actually being used properly yet, so we decided to keep it because we already have this being sent to these services, but once we try to actually use these to generate URLs we'll definitely need to add the type.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LOL, github seems to have the diff confused here 🤔

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 a Kibana Saved Object ID.',
}
)}
/>
}
>
<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 a Kibana Saved Object ID.',
}
)}
/>
}
>
<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