Revert "[Cases] Migrate user actions connector ID (#108272)"

This reverts commit 10ac814d8f.
This commit is contained in:
Tyler Smalley 2021-09-20 15:35:54 -07:00
parent 817a0fba74
commit e07b0e593e
57 changed files with 1143 additions and 7898 deletions

View file

@ -87,11 +87,8 @@ const CaseBasicRt = rt.type({
owner: rt.string,
});
/**
* This represents the push to service UserAction. It lacks the connector_id because that is stored in a different field
* within the user action object in the API response.
*/
export const CaseUserActionExternalServiceRt = rt.type({
export const CaseExternalServiceBasicRt = rt.type({
connector_id: rt.union([rt.string, rt.null]),
connector_name: rt.string,
external_id: rt.string,
external_title: rt.string,
@ -100,14 +97,7 @@ export const CaseUserActionExternalServiceRt = rt.type({
pushed_by: UserRT,
});
export const CaseExternalServiceBasicRt = rt.intersection([
rt.type({
connector_id: rt.union([rt.string, rt.null]),
}),
CaseUserActionExternalServiceRt,
]);
export const CaseFullExternalServiceRt = rt.union([CaseExternalServiceBasicRt, rt.null]);
const CaseFullExternalServiceRt = rt.union([CaseExternalServiceBasicRt, rt.null]);
export const CaseAttributesRt = rt.intersection([
CaseBasicRt,

View file

@ -34,6 +34,7 @@ const UserActionRt = rt.union([
rt.literal('push-to-service'),
]);
// TO DO change state to status
const CaseUserActionBasicRT = rt.type({
action_field: UserActionFieldRt,
action: UserActionRt,
@ -50,8 +51,6 @@ const CaseUserActionResponseRT = rt.intersection([
action_id: rt.string,
case_id: rt.string,
comment_id: rt.union([rt.string, rt.null]),
new_val_connector_id: rt.union([rt.string, rt.null]),
old_val_connector_id: rt.union([rt.string, rt.null]),
}),
rt.partial({ sub_case_id: rt.string }),
]);

View file

@ -84,22 +84,14 @@ export const ConnectorTypeFieldsRt = rt.union([
ConnectorSwimlaneTypeFieldsRt,
]);
/**
* This type represents the connector's format when it is encoded within a user action.
*/
export const CaseUserActionConnectorRt = rt.intersection([
rt.type({ name: rt.string }),
ConnectorTypeFieldsRt,
]);
export const CaseConnectorRt = rt.intersection([
rt.type({
id: rt.string,
name: rt.string,
}),
CaseUserActionConnectorRt,
ConnectorTypeFieldsRt,
]);
export type CaseUserActionConnector = rt.TypeOf<typeof CaseUserActionConnectorRt>;
export type CaseConnector = rt.TypeOf<typeof CaseConnectorRt>;
export type ConnectorTypeFields = rt.TypeOf<typeof ConnectorTypeFieldsRt>;
export type ConnectorJiraTypeFields = rt.TypeOf<typeof ConnectorJiraTypeFieldsRt>;

View file

@ -12,4 +12,3 @@ export * from './constants';
export * from './api';
export * from './ui/types';
export * from './utils/connectors_api';
export * from './utils/user_actions';

View file

@ -66,9 +66,7 @@ export interface CaseUserActions {
caseId: string;
commentId: string | null;
newValue: string | null;
newValConnectorId: string | null;
oldValue: string | null;
oldValConnectorId: string | null;
}
export interface CaseExternalService {

View file

@ -1,18 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export function isCreateConnector(action?: string, actionFields?: string[]): boolean {
return action === 'create' && actionFields != null && actionFields.includes('connector');
}
export function isUpdateConnector(action?: string, actionFields?: string[]): boolean {
return action === 'update' && actionFields != null && actionFields.includes('connector');
}
export function isPush(action?: string, actionFields?: string[]): boolean {
return action === 'push-to-service' && actionFields != null && actionFields.includes('pushed');
}

View file

@ -1,8 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export * from './parsers';

View file

@ -1,86 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { ConnectorTypes, noneConnectorId } from '../../../common';
import { parseStringAsConnector, parseStringAsExternalService } from './parsers';
describe('user actions utility functions', () => {
describe('parseStringAsConnector', () => {
it('return null if the data is null', () => {
expect(parseStringAsConnector('', null)).toBeNull();
});
it('return null if the data is not a json object', () => {
expect(parseStringAsConnector('', 'blah')).toBeNull();
});
it('return null if the data is not a valid connector', () => {
expect(parseStringAsConnector('', JSON.stringify({ a: '1' }))).toBeNull();
});
it('return null if id is null but the data is a connector other than none', () => {
expect(
parseStringAsConnector(
null,
JSON.stringify({ type: ConnectorTypes.jira, name: '', fields: null })
)
).toBeNull();
});
it('return the id as the none connector if the data is the none connector', () => {
expect(
parseStringAsConnector(
null,
JSON.stringify({ type: ConnectorTypes.none, name: '', fields: null })
)
).toEqual({ id: noneConnectorId, type: ConnectorTypes.none, name: '', fields: null });
});
it('returns a decoded connector with the specified id', () => {
expect(
parseStringAsConnector(
'a',
JSON.stringify({ type: ConnectorTypes.jira, name: 'hi', fields: null })
)
).toEqual({ id: 'a', type: ConnectorTypes.jira, name: 'hi', fields: null });
});
});
describe('parseStringAsExternalService', () => {
it('returns null when the data is null', () => {
expect(parseStringAsExternalService('', null)).toBeNull();
});
it('returns null when the data is not valid json', () => {
expect(parseStringAsExternalService('', 'blah')).toBeNull();
});
it('returns null when the data is not a valid external service object', () => {
expect(parseStringAsExternalService('', JSON.stringify({ a: '1' }))).toBeNull();
});
it('returns the decoded external service with the connector_id field added', () => {
const externalServiceInfo = {
connector_name: 'name',
external_id: '1',
external_title: 'title',
external_url: 'abc',
pushed_at: '1',
pushed_by: {
username: 'a',
email: 'a@a.com',
full_name: 'a',
},
};
expect(parseStringAsExternalService('500', JSON.stringify(externalServiceInfo))).toEqual({
...externalServiceInfo,
connector_id: '500',
});
});
});
});

View file

@ -1,77 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import {
CaseUserActionConnectorRt,
CaseConnector,
ConnectorTypes,
noneConnectorId,
CaseFullExternalService,
CaseUserActionExternalServiceRt,
} from '../../../common';
export const parseStringAsConnector = (
id: string | null,
encodedData: string | null
): CaseConnector | null => {
if (encodedData == null) {
return null;
}
const decodedConnector = parseString(encodedData);
if (!CaseUserActionConnectorRt.is(decodedConnector)) {
return null;
}
if (id == null && decodedConnector.type === ConnectorTypes.none) {
return {
...decodedConnector,
id: noneConnectorId,
};
} else if (id == null) {
return null;
} else {
// id does not equal null or undefined and the connector type does not equal none
// so return the connector with its id
return {
...decodedConnector,
id,
};
}
};
const parseString = (params: string | null): unknown | null => {
if (params == null) {
return null;
}
try {
return JSON.parse(params);
} catch {
return null;
}
};
export const parseStringAsExternalService = (
id: string | null,
encodedData: string | null
): CaseFullExternalService => {
if (encodedData == null) {
return null;
}
const decodedExternalService = parseString(encodedData);
if (!CaseUserActionExternalServiceRt.is(decodedExternalService)) {
return null;
}
return {
...decodedExternalService,
connector_id: id,
};
};

View file

@ -1,187 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { CaseUserActionConnector, ConnectorTypes } from '../../../common';
import { CaseUserActions } from '../../containers/types';
import { getConnectorFieldsFromUserActions } from './helpers';
describe('helpers', () => {
describe('getConnectorFieldsFromUserActions', () => {
it('returns null when it cannot find the connector id', () => {
expect(getConnectorFieldsFromUserActions('a', [])).toBeNull();
});
it('returns null when the value fields are not valid encoded fields', () => {
expect(
getConnectorFieldsFromUserActions('a', [createUserAction({ newValue: 'a', oldValue: 'a' })])
).toBeNull();
});
it('returns null when it cannot find the connector id in a non empty array', () => {
expect(
getConnectorFieldsFromUserActions('a', [
createUserAction({
newValue: JSON.stringify({ a: '1' }),
oldValue: JSON.stringify({ a: '1' }),
}),
])
).toBeNull();
});
it('returns the fields when it finds the connector id in the new value', () => {
expect(
getConnectorFieldsFromUserActions('a', [
createUserAction({
newValue: createEncodedJiraConnector(),
oldValue: JSON.stringify({ a: '1' }),
newValConnectorId: 'a',
}),
])
).toEqual(defaultJiraFields);
});
it('returns the fields when it finds the connector id in the new value and the old value is null', () => {
expect(
getConnectorFieldsFromUserActions('a', [
createUserAction({
newValue: createEncodedJiraConnector(),
newValConnectorId: 'a',
}),
])
).toEqual(defaultJiraFields);
});
it('returns the fields when it finds the connector id in the old value', () => {
const expectedFields = { ...defaultJiraFields, issueType: '5' };
expect(
getConnectorFieldsFromUserActions('id-to-find', [
createUserAction({
newValue: createEncodedJiraConnector(),
oldValue: createEncodedJiraConnector({
fields: expectedFields,
}),
newValConnectorId: 'b',
oldValConnectorId: 'id-to-find',
}),
])
).toEqual(expectedFields);
});
it('returns the fields when it finds the connector id in the second user action', () => {
const expectedFields = { ...defaultJiraFields, issueType: '5' };
expect(
getConnectorFieldsFromUserActions('id-to-find', [
createUserAction({
newValue: createEncodedJiraConnector(),
oldValue: createEncodedJiraConnector(),
newValConnectorId: 'b',
oldValConnectorId: 'a',
}),
createUserAction({
newValue: createEncodedJiraConnector(),
oldValue: createEncodedJiraConnector({ fields: expectedFields }),
newValConnectorId: 'b',
oldValConnectorId: 'id-to-find',
}),
])
).toEqual(expectedFields);
});
it('ignores a parse failure and finds the right user action', () => {
expect(
getConnectorFieldsFromUserActions('none', [
createUserAction({
newValue: 'b',
newValConnectorId: null,
}),
createUserAction({
newValue: createEncodedJiraConnector({
type: ConnectorTypes.none,
name: '',
fields: null,
}),
newValConnectorId: null,
}),
])
).toBeNull();
});
it('returns null when the id matches but the encoded value is null', () => {
expect(
getConnectorFieldsFromUserActions('b', [
createUserAction({
newValue: null,
newValConnectorId: 'b',
}),
])
).toBeNull();
});
it('returns null when the action fields is not of length 1', () => {
expect(
getConnectorFieldsFromUserActions('id-to-find', [
createUserAction({
newValue: JSON.stringify({ a: '1', fields: { hello: '1' } }),
oldValue: JSON.stringify({ a: '1', fields: { hi: '2' } }),
newValConnectorId: 'b',
oldValConnectorId: 'id-to-find',
actionField: ['connector', 'connector'],
}),
])
).toBeNull();
});
it('matches the none connector the searched for id is none', () => {
expect(
getConnectorFieldsFromUserActions('none', [
createUserAction({
newValue: createEncodedJiraConnector({
type: ConnectorTypes.none,
name: '',
fields: null,
}),
newValConnectorId: null,
}),
])
).toBeNull();
});
});
});
function createUserAction(fields: Partial<CaseUserActions>): CaseUserActions {
return {
action: 'update',
actionAt: '',
actionBy: {},
actionField: ['connector'],
actionId: '',
caseId: '',
commentId: '',
newValConnectorId: null,
oldValConnectorId: null,
newValue: null,
oldValue: null,
...fields,
};
}
function createEncodedJiraConnector(fields?: Partial<CaseUserActionConnector>): string {
return JSON.stringify({
type: ConnectorTypes.jira,
name: 'name',
fields: defaultJiraFields,
...fields,
});
}
const defaultJiraFields = {
issueType: '1',
parent: null,
priority: null,
};

View file

@ -5,33 +5,23 @@
* 2.0.
*/
import { ConnectorTypeFields } from '../../../common';
import { CaseUserActions } from '../../containers/types';
import { parseStringAsConnector } from '../../common/user_actions';
export const getConnectorFieldsFromUserActions = (
id: string,
userActions: CaseUserActions[]
): ConnectorTypeFields['fields'] => {
export const getConnectorFieldsFromUserActions = (id: string, userActions: CaseUserActions[]) => {
try {
for (const action of [...userActions].reverse()) {
if (action.actionField.length === 1 && action.actionField[0] === 'connector') {
const parsedNewConnector = parseStringAsConnector(
action.newValConnectorId,
action.newValue
);
if (action.oldValue && action.newValue) {
const oldValue = JSON.parse(action.oldValue);
const newValue = JSON.parse(action.newValue);
if (parsedNewConnector && id === parsedNewConnector.id) {
return parsedNewConnector.fields;
}
if (newValue.id === id) {
return newValue.fields;
}
const parsedOldConnector = parseStringAsConnector(
action.oldValConnectorId,
action.oldValue
);
if (parsedOldConnector && id === parsedOldConnector.id) {
return parsedOldConnector.fields;
if (oldValue.id === id) {
return oldValue.fields;
}
}
}
}

View file

@ -8,7 +8,7 @@
import React from 'react';
import { mount } from 'enzyme';
import { CaseStatuses, ConnectorTypes } from '../../../common';
import { CaseStatuses } from '../../../common';
import { basicPush, getUserAction } from '../../containers/mock';
import {
getLabelTitle,
@ -129,7 +129,7 @@ describe('User action tree helpers', () => {
`${i18n.PUSHED_NEW_INCIDENT} ${basicPush.connectorName}`
);
expect(wrapper.find(`[data-test-subj="pushed-value"]`).first().prop('href')).toEqual(
JSON.parse(action.newValue!).external_url
JSON.parse(action.newValue).external_url
);
});
@ -142,74 +142,50 @@ describe('User action tree helpers', () => {
`${i18n.UPDATE_INCIDENT} ${basicPush.connectorName}`
);
expect(wrapper.find(`[data-test-subj="pushed-value"]`).first().prop('href')).toEqual(
JSON.parse(action.newValue!).external_url
JSON.parse(action.newValue).external_url
);
});
describe('getConnectorLabelTitle', () => {
it('returns an empty string when the encoded old value is null', () => {
const result = getConnectorLabelTitle({
action: getUserAction(['connector'], 'update', { oldValue: null }),
connectors,
});
expect(result).toEqual('');
it('label title generated for update connector - change connector', () => {
const action = {
...getUserAction(['connector'], 'update'),
oldValue: JSON.stringify({ id: 'servicenow-1' }),
newValue: JSON.stringify({ id: 'resilient-2' }),
};
const result: string | JSX.Element = getConnectorLabelTitle({
action,
connectors,
});
it('returns an empty string when the encoded new value is null', () => {
const result = getConnectorLabelTitle({
action: getUserAction(['connector'], 'update', { newValue: null }),
connectors,
});
expect(result).toEqual('selected My Connector 2 as incident management system');
});
expect(result).toEqual('');
it('label title generated for update connector - change connector to none', () => {
const action = {
...getUserAction(['connector'], 'update'),
oldValue: JSON.stringify({ id: 'servicenow-1' }),
newValue: JSON.stringify({ id: 'none' }),
};
const result: string | JSX.Element = getConnectorLabelTitle({
action,
connectors,
});
it('returns the change connector label', () => {
const result: string | JSX.Element = getConnectorLabelTitle({
action: getUserAction(['connector'], 'update', {
oldValue: JSON.stringify({
type: ConnectorTypes.serviceNowITSM,
name: 'a',
fields: null,
}),
oldValConnectorId: 'servicenow-1',
newValue: JSON.stringify({ type: ConnectorTypes.resilient, name: 'a', fields: null }),
newValConnectorId: 'resilient-2',
}),
connectors,
});
expect(result).toEqual('removed external incident management system');
});
expect(result).toEqual('selected My Connector 2 as incident management system');
it('label title generated for update connector - field change', () => {
const action = {
...getUserAction(['connector'], 'update'),
oldValue: JSON.stringify({ id: 'servicenow-1' }),
newValue: JSON.stringify({ id: 'servicenow-1' }),
};
const result: string | JSX.Element = getConnectorLabelTitle({
action,
connectors,
});
it('returns the removed connector label', () => {
const result: string | JSX.Element = getConnectorLabelTitle({
action: getUserAction(['connector'], 'update', {
oldValue: JSON.stringify({ type: ConnectorTypes.serviceNowITSM, name: '', fields: null }),
oldValConnectorId: 'servicenow-1',
newValue: JSON.stringify({ type: ConnectorTypes.none, name: '', fields: null }),
newValConnectorId: 'none',
}),
connectors,
});
expect(result).toEqual('removed external incident management system');
});
it('returns the connector fields changed label', () => {
const result: string | JSX.Element = getConnectorLabelTitle({
action: getUserAction(['connector'], 'update', {
oldValue: JSON.stringify({ type: ConnectorTypes.serviceNowITSM, name: '', fields: null }),
oldValConnectorId: 'servicenow-1',
newValue: JSON.stringify({ type: ConnectorTypes.serviceNowITSM, name: '', fields: null }),
newValConnectorId: 'servicenow-1',
}),
connectors,
});
expect(result).toEqual('changed connector field');
});
expect(result).toEqual('changed connector field');
});
describe('toStringArray', () => {

View file

@ -23,11 +23,10 @@ import {
CommentType,
Comment,
CommentRequestActionsType,
noneConnectorId,
} from '../../../common';
import { CaseUserActions } from '../../containers/types';
import { CaseServices } from '../../containers/use_get_case_user_actions';
import { parseStringAsConnector, parseStringAsExternalService } from '../../common/user_actions';
import { parseString } from '../../containers/utils';
import { Tags } from '../tag_list/tags';
import { UserActionUsernameWithAvatar } from './user_action_username_with_avatar';
import { UserActionTimestamp } from './user_action_timestamp';
@ -98,27 +97,23 @@ export const getConnectorLabelTitle = ({
action: CaseUserActions;
connectors: ActionConnector[];
}) => {
const oldConnector = parseStringAsConnector(action.oldValConnectorId, action.oldValue);
const newConnector = parseStringAsConnector(action.newValConnectorId, action.newValue);
const oldValue = parseString(`${action.oldValue}`);
const newValue = parseString(`${action.newValue}`);
if (!oldConnector || !newConnector) {
if (oldValue === null || newValue === null) {
return '';
}
// if the ids are the same, assume we just changed the fields
if (oldConnector.id === newConnector.id) {
// Connector changed
if (oldValue.id !== newValue.id) {
const newConnector = connectors.find((c) => c.id === newValue.id);
return newValue.id != null && newValue.id !== 'none' && newConnector != null
? i18n.SELECTED_THIRD_PARTY(newConnector.name)
: i18n.REMOVED_THIRD_PARTY;
} else {
// Field changed
return i18n.CHANGED_CONNECTOR_FIELD;
}
// ids are not the same so check and see if the id is a valid connector and then return its name
// if the connector id is the none connector value then it must have been removed
const newConnectorActionInfo = connectors.find((c) => c.id === newConnector.id);
if (newConnector.id !== noneConnectorId && newConnectorActionInfo != null) {
return i18n.SELECTED_THIRD_PARTY(newConnectorActionInfo.name);
}
// it wasn't a valid connector or it was the none connector, so it must have been removed
return i18n.REMOVED_THIRD_PARTY;
};
const getTagsLabelTitle = (action: CaseUserActions) => {
@ -138,8 +133,7 @@ const getTagsLabelTitle = (action: CaseUserActions) => {
};
export const getPushedServiceLabelTitle = (action: CaseUserActions, firstPush: boolean) => {
const externalService = parseStringAsExternalService(action.newValConnectorId, action.newValue);
const pushedVal = JSON.parse(action.newValue ?? '') as CaseFullExternalService;
return (
<EuiFlexGroup
alignItems="baseline"
@ -149,12 +143,12 @@ export const getPushedServiceLabelTitle = (action: CaseUserActions, firstPush: b
>
<EuiFlexItem data-test-subj="pushed-label">
{`${firstPush ? i18n.PUSHED_NEW_INCIDENT : i18n.UPDATE_INCIDENT} ${
externalService?.connector_name
pushedVal?.connector_name
}`}
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiLink data-test-subj="pushed-value" href={externalService?.external_url} target="_blank">
{externalService?.external_title}
<EuiLink data-test-subj="pushed-value" href={pushedVal?.external_url} target="_blank">
{pushedVal?.external_title}
</EuiLink>
</EuiFlexItem>
</EuiFlexGroup>
@ -163,19 +157,20 @@ export const getPushedServiceLabelTitle = (action: CaseUserActions, firstPush: b
export const getPushInfo = (
caseServices: CaseServices,
externalService: CaseFullExternalService | undefined,
// a JSON parse failure will result in null for parsedValue
parsedValue: { connector_id: string | null; connector_name: string } | null,
index: number
) =>
externalService != null && externalService.connector_id != null
parsedValue != null && parsedValue.connector_id != null
? {
firstPush: caseServices[externalService.connector_id]?.firstPushIndex === index,
parsedConnectorId: externalService.connector_id,
parsedConnectorName: externalService.connector_name,
firstPush: caseServices[parsedValue.connector_id]?.firstPushIndex === index,
parsedConnectorId: parsedValue.connector_id,
parsedConnectorName: parsedValue.connector_name,
}
: {
firstPush: false,
parsedConnectorId: noneConnectorId,
parsedConnectorName: noneConnectorId,
parsedConnectorId: 'none',
parsedConnectorName: 'none',
};
const getUpdateActionIcon = (actionField: string): string => {

View file

@ -35,7 +35,7 @@ import {
Ecs,
} from '../../../common';
import { CaseServices } from '../../containers/use_get_case_user_actions';
import { parseStringAsExternalService } from '../../common/user_actions';
import { parseString } from '../../containers/utils';
import { OnUpdateFields } from '../case_view';
import {
getConnectorLabelTitle,
@ -512,14 +512,10 @@ export const UserActionTree = React.memo(
// Pushed information
if (action.actionField.length === 1 && action.actionField[0] === 'pushed') {
const parsedExternalService = parseStringAsExternalService(
action.newValConnectorId,
action.newValue
);
const parsedValue = parseString(`${action.newValue}`);
const { firstPush, parsedConnectorId, parsedConnectorName } = getPushInfo(
caseServices,
parsedExternalService,
parsedValue,
index
);

View file

@ -9,7 +9,6 @@ import { ActionLicense, AllCases, Case, CasesStatus, CaseUserActions, Comment }
import {
AssociationType,
CaseUserActionConnector,
CaseResponse,
CasesFindResponse,
CasesResponse,
@ -20,9 +19,6 @@ import {
CommentResponse,
CommentType,
ConnectorTypes,
isCreateConnector,
isPush,
isUpdateConnector,
SECURITY_SOLUTION_OWNER,
UserAction,
UserActionField,
@ -244,9 +240,7 @@ export const pushedCase: Case = {
const basicAction = {
actionAt: basicCreatedAt,
actionBy: elasticUser,
oldValConnectorId: null,
oldValue: null,
newValConnectorId: null,
newValue: 'what a cool value',
caseId: basicCaseId,
commentId: null,
@ -314,7 +308,12 @@ export const basicCaseSnake: CaseResponse = {
closed_at: null,
closed_by: null,
comments: [basicCommentSnake],
connector: { id: 'none', name: 'My Connector', type: ConnectorTypes.none, fields: null },
connector: {
id: 'none',
name: 'My Connector',
type: ConnectorTypes.none,
fields: null,
},
created_at: basicCreatedAt,
created_by: elasticUserSnake,
external_service: null,
@ -329,8 +328,8 @@ export const casesStatusSnake: CasesStatusResponse = {
count_open_cases: 20,
};
export const pushConnectorId = '123';
export const pushSnake = {
connector_id: '123',
connector_name: 'connector name',
external_id: 'external_id',
external_title: 'external title',
@ -351,7 +350,7 @@ export const pushedCaseSnake = {
type: ConnectorTypes.jira,
fields: null,
},
external_service: { ...basicPushSnake, connector_id: pushConnectorId },
external_service: basicPushSnake,
};
export const reporters: string[] = ['alexis', 'kim', 'maria', 'steph'];
@ -386,20 +385,17 @@ const basicActionSnake = {
comment_id: null,
owner: SECURITY_SOLUTION_OWNER,
};
export const getUserActionSnake = (af: UserActionField, a: UserAction) => {
const isPushToService = a === 'push-to-service' && af[0] === 'pushed';
return {
...basicActionSnake,
action_id: `${af[0]}-${a}`,
action_field: af,
action: a,
comment_id: af[0] === 'comment' ? basicCommentId : null,
new_value: isPushToService ? JSON.stringify(basicPushSnake) : basicAction.newValue,
new_val_connector_id: isPushToService ? pushConnectorId : null,
old_val_connector_id: null,
};
};
export const getUserActionSnake = (af: UserActionField, a: UserAction) => ({
...basicActionSnake,
action_id: `${af[0]}-${a}`,
action_field: af,
action: a,
comment_id: af[0] === 'comment' ? basicCommentId : null,
new_value:
a === 'push-to-service' && af[0] === 'pushed'
? JSON.stringify(basicPushSnake)
: basicAction.newValue,
});
export const caseUserActionsSnake: CaseUserActionsResponse = [
getUserActionSnake(['description'], 'create'),
@ -409,76 +405,17 @@ export const caseUserActionsSnake: CaseUserActionsResponse = [
// user actions
export const getUserAction = (
af: UserActionField,
a: UserAction,
overrides?: Partial<CaseUserActions>
): CaseUserActions => {
return {
...basicAction,
actionId: `${af[0]}-${a}`,
actionField: af,
action: a,
commentId: af[0] === 'comment' ? basicCommentId : null,
...getValues(a, af, overrides),
};
};
const getValues = (
userAction: UserAction,
actionFields: UserActionField,
overrides?: Partial<CaseUserActions>
): Partial<CaseUserActions> => {
if (isCreateConnector(userAction, actionFields)) {
return {
newValue:
overrides?.newValue === undefined ? JSON.stringify(basicCaseSnake) : overrides.newValue,
newValConnectorId: overrides?.newValConnectorId ?? null,
oldValue: null,
oldValConnectorId: null,
};
} else if (isUpdateConnector(userAction, actionFields)) {
return {
newValue:
overrides?.newValue === undefined
? JSON.stringify({ name: 'My Connector', type: ConnectorTypes.none, fields: null })
: overrides.newValue,
newValConnectorId: overrides?.newValConnectorId ?? null,
oldValue:
overrides?.oldValue === undefined
? JSON.stringify({ name: 'My Connector2', type: ConnectorTypes.none, fields: null })
: overrides.oldValue,
oldValConnectorId: overrides?.oldValConnectorId ?? null,
};
} else if (isPush(userAction, actionFields)) {
return {
newValue:
overrides?.newValue === undefined ? JSON.stringify(basicPushSnake) : overrides?.newValue,
newValConnectorId:
overrides?.newValConnectorId === undefined ? pushConnectorId : overrides.newValConnectorId,
oldValue: overrides?.oldValue ?? null,
oldValConnectorId: overrides?.oldValConnectorId ?? null,
};
} else {
return {
newValue: overrides?.newValue === undefined ? basicAction.newValue : overrides.newValue,
newValConnectorId: overrides?.newValConnectorId ?? null,
oldValue: overrides?.oldValue ?? null,
oldValConnectorId: overrides?.oldValConnectorId ?? null,
};
}
};
export const getJiraConnectorWithoutId = (overrides?: Partial<CaseUserActionConnector>) => {
return JSON.stringify({
name: 'jira1',
type: ConnectorTypes.jira,
...jiraFields,
...overrides,
});
};
export const jiraFields = { fields: { issueType: '10006', priority: null, parent: null } };
export const getUserAction = (af: UserActionField, a: UserAction) => ({
...basicAction,
actionId: `${af[0]}-${a}`,
actionField: af,
action: a,
commentId: af[0] === 'comment' ? basicCommentId : null,
newValue:
a === 'push-to-service' && af[0] === 'pushed'
? JSON.stringify(basicPushSnake)
: basicAction.newValue,
});
export const getAlertUserAction = () => ({
...basicAction,

View file

@ -18,9 +18,7 @@ import {
basicPushSnake,
caseUserActions,
elasticUser,
getJiraConnectorWithoutId,
getUserAction,
jiraFields,
} from './mock';
import * as api from './api';
@ -301,14 +299,15 @@ describe('useGetCaseUserActions', () => {
const pushAction123 = getUserAction(['pushed'], 'push-to-service');
const push456 = {
...basicPushSnake,
connector_id: '456',
connector_name: 'other connector name',
external_id: 'other_external_id',
};
const pushAction456 = getUserAction(['pushed'], 'push-to-service', {
const pushAction456 = {
...getUserAction(['pushed'], 'push-to-service'),
newValue: JSON.stringify(push456),
newValConnectorId: '456',
});
};
const userActions = [
...caseUserActions,
@ -347,14 +346,15 @@ describe('useGetCaseUserActions', () => {
const pushAction123 = getUserAction(['pushed'], 'push-to-service');
const push456 = {
...basicPushSnake,
connector_id: '456',
connector_name: 'other connector name',
external_id: 'other_external_id',
};
const pushAction456 = getUserAction(['pushed'], 'push-to-service', {
const pushAction456 = {
...getUserAction(['pushed'], 'push-to-service'),
newValue: JSON.stringify(push456),
newValConnectorId: '456',
});
};
const userActions = [
...caseUserActions,
@ -392,7 +392,11 @@ describe('useGetCaseUserActions', () => {
const userActions = [
...caseUserActions,
getUserAction(['pushed'], 'push-to-service'),
createUpdateConnectorFields123HighPriorityUserAction(),
{
...getUserAction(['connector'], 'update'),
oldValue: JSON.stringify({ id: '123', fields: { issueType: '10006', priority: null } }),
newValue: JSON.stringify({ id: '123', fields: { issueType: '10006', priority: 'High' } }),
},
];
const result = getPushedInfo(userActions, '123');
@ -414,7 +418,11 @@ describe('useGetCaseUserActions', () => {
const userActions = [
...caseUserActions,
getUserAction(['pushed'], 'push-to-service'),
createChangeConnector123To456UserAction(),
{
...getUserAction(['connector'], 'update'),
oldValue: JSON.stringify({ id: '123', fields: { issueType: '10006', priority: null } }),
newValue: JSON.stringify({ id: '456', fields: { issueTypes: ['10'], severity: '6' } }),
},
];
const result = getPushedInfo(userActions, '123');
@ -436,8 +444,16 @@ describe('useGetCaseUserActions', () => {
const userActions = [
...caseUserActions,
getUserAction(['pushed'], 'push-to-service'),
createChangeConnector123To456UserAction(),
createChangeConnector456To123UserAction(),
{
...getUserAction(['connector'], 'update'),
oldValue: JSON.stringify({ id: '123', fields: { issueType: '10006', priority: null } }),
newValue: JSON.stringify({ id: '456', fields: { issueTypes: ['10'], severity: '6' } }),
},
{
...getUserAction(['connector'], 'update'),
oldValue: JSON.stringify({ id: '456', fields: { issueTypes: ['10'], severity: '6' } }),
newValue: JSON.stringify({ id: '123', fields: { issueType: '10006', priority: null } }),
},
];
const result = getPushedInfo(userActions, '123');
@ -458,10 +474,22 @@ describe('useGetCaseUserActions', () => {
it('Change fields and connector after push - hasDataToPush: true', () => {
const userActions = [
...caseUserActions,
createUpdateConnectorFields123HighPriorityUserAction(),
{
...getUserAction(['connector'], 'update'),
oldValue: JSON.stringify({ id: '123', fields: { issueType: '10006', priority: null } }),
newValue: JSON.stringify({ id: '123', fields: { issueType: '10006', priority: 'High' } }),
},
getUserAction(['pushed'], 'push-to-service'),
createChangeConnector123HighPriorityTo456UserAction(),
createChangeConnector456To123PriorityLowUserAction(),
{
...getUserAction(['connector'], 'update'),
oldValue: JSON.stringify({ id: '123', fields: { issueType: '10006', priority: 'High' } }),
newValue: JSON.stringify({ id: '456', fields: { issueTypes: ['10'], severity: '6' } }),
},
{
...getUserAction(['connector'], 'update'),
oldValue: JSON.stringify({ id: '456', fields: { issueTypes: ['10'], severity: '6' } }),
newValue: JSON.stringify({ id: '123', fields: { issueType: '10006', priority: 'Low' } }),
},
];
const result = getPushedInfo(userActions, '123');
@ -482,10 +510,22 @@ describe('useGetCaseUserActions', () => {
it('Change only connector after push - hasDataToPush: false', () => {
const userActions = [
...caseUserActions,
createUpdateConnectorFields123HighPriorityUserAction(),
{
...getUserAction(['connector'], 'update'),
oldValue: JSON.stringify({ id: '123', fields: { issueType: '10006', priority: null } }),
newValue: JSON.stringify({ id: '123', fields: { issueType: '10006', priority: 'High' } }),
},
getUserAction(['pushed'], 'push-to-service'),
createChangeConnector123HighPriorityTo456UserAction(),
createChangeConnector456To123HighPriorityUserAction(),
{
...getUserAction(['connector'], 'update'),
oldValue: JSON.stringify({ id: '123', fields: { issueType: '10006', priority: 'High' } }),
newValue: JSON.stringify({ id: '456', fields: { issueTypes: ['10'], severity: '6' } }),
},
{
...getUserAction(['connector'], 'update'),
oldValue: JSON.stringify({ id: '456', fields: { issueTypes: ['10'], severity: '6' } }),
newValue: JSON.stringify({ id: '123', fields: { issueType: '10006', priority: 'High' } }),
},
];
const result = getPushedInfo(userActions, '123');
@ -507,24 +547,45 @@ describe('useGetCaseUserActions', () => {
const pushAction123 = getUserAction(['pushed'], 'push-to-service');
const push456 = {
...basicPushSnake,
connector_id: '456',
connector_name: 'other connector name',
external_id: 'other_external_id',
};
const pushAction456 = getUserAction(['pushed'], 'push-to-service', {
const pushAction456 = {
...getUserAction(['pushed'], 'push-to-service'),
newValue: JSON.stringify(push456),
newValConnectorId: '456',
});
};
const userActions = [
...caseUserActions,
createUpdateConnectorFields123HighPriorityUserAction(),
{
...getUserAction(['connector'], 'update'),
oldValue: JSON.stringify({ id: '123', fields: { issueType: '10006', priority: null } }),
newValue: JSON.stringify({ id: '123', fields: { issueType: '10006', priority: 'High' } }),
},
pushAction123,
createChangeConnector123HighPriorityTo456UserAction(),
{
...getUserAction(['connector'], 'update'),
oldValue: JSON.stringify({ id: '123', fields: { issueType: '10006', priority: 'High' } }),
newValue: JSON.stringify({ id: '456', fields: { issueTypes: ['10'], severity: '6' } }),
},
pushAction456,
createChangeConnector456To123PriorityLowUserAction(),
createChangeConnector123LowPriorityTo456UserAction(),
createChangeConnector456To123PriorityLowUserAction(),
{
...getUserAction(['connector'], 'update'),
oldValue: JSON.stringify({ id: '456', fields: { issueTypes: ['10'], severity: '6' } }),
newValue: JSON.stringify({ id: '123', fields: { issueType: '10006', priority: 'Low' } }),
},
{
...getUserAction(['connector'], 'update'),
oldValue: JSON.stringify({ id: '123', fields: { issueType: '10006', priority: 'Low' } }),
newValue: JSON.stringify({ id: '456', fields: { issueTypes: ['10'], severity: '6' } }),
},
{
...getUserAction(['connector'], 'update'),
oldValue: JSON.stringify({ id: '456', fields: { issueTypes: ['10'], severity: '6' } }),
newValue: JSON.stringify({ id: '123', fields: { issueType: '10006', priority: 'Low' } }),
},
];
const result = getPushedInfo(userActions, '123');
@ -556,22 +617,34 @@ describe('useGetCaseUserActions', () => {
const pushAction123 = getUserAction(['pushed'], 'push-to-service');
const push456 = {
...basicPushSnake,
connector_id: '456',
connector_name: 'other connector name',
external_id: 'other_external_id',
};
const pushAction456 = getUserAction(['pushed'], 'push-to-service', {
newValConnectorId: '456',
const pushAction456 = {
...getUserAction(['pushed'], 'push-to-service'),
newValue: JSON.stringify(push456),
});
};
const userActions = [
...caseUserActions,
createUpdateConnectorFields123HighPriorityUserAction(),
{
...getUserAction(['connector'], 'update'),
oldValue: JSON.stringify({ id: '123', fields: { issueType: '10006', priority: null } }),
newValue: JSON.stringify({ id: '123', fields: { issueType: '10006', priority: 'High' } }),
},
pushAction123,
createChangeConnector123HighPriorityTo456UserAction(),
{
...getUserAction(['connector'], 'update'),
oldValue: JSON.stringify({ id: '123', fields: { issueType: '10006', priority: 'High' } }),
newValue: JSON.stringify({ id: '456', fields: { issueTypes: ['10'], severity: '6' } }),
},
pushAction456,
createChangeConnector456To123HighPriorityUserAction(),
{
...getUserAction(['connector'], 'update'),
oldValue: JSON.stringify({ id: '456', fields: { issueTypes: ['10'], severity: '6' } }),
newValue: JSON.stringify({ id: '123', fields: { issueType: '10006', priority: 'High' } }),
},
];
const result = getPushedInfo(userActions, '123');
@ -602,10 +675,22 @@ describe('useGetCaseUserActions', () => {
it('Changing other connectors fields does not count as an update', () => {
const userActions = [
...caseUserActions,
createUpdateConnectorFields123HighPriorityUserAction(),
{
...getUserAction(['connector'], 'update'),
oldValue: JSON.stringify({ id: '123', fields: { issueType: '10006', priority: null } }),
newValue: JSON.stringify({ id: '123', fields: { issueType: '10006', priority: 'High' } }),
},
getUserAction(['pushed'], 'push-to-service'),
createChangeConnector123HighPriorityTo456UserAction(),
createUpdateConnectorFields456HighPriorityUserAction(),
{
...getUserAction(['connector'], 'update'),
oldValue: JSON.stringify({ id: '123', fields: { issueType: '10006', priority: 'High' } }),
newValue: JSON.stringify({ id: '456', fields: { issueTypes: ['10'], severity: '6' } }),
},
{
...getUserAction(['connector'], 'update'),
oldValue: JSON.stringify({ id: '456', fields: { issueTypes: ['10'], severity: '6' } }),
newValue: JSON.stringify({ id: '456', fields: { issueTypes: ['10'], severity: '3' } }),
},
];
const result = getPushedInfo(userActions, '123');
@ -624,83 +709,3 @@ describe('useGetCaseUserActions', () => {
});
});
});
const jira123HighPriorityFields = {
fields: { ...jiraFields.fields, priority: 'High' },
};
const jira123LowPriorityFields = {
fields: { ...jiraFields.fields, priority: 'Low' },
};
const jira456Fields = {
fields: { issueType: '10', parent: null, priority: null },
};
const jira456HighPriorityFields = {
fields: { ...jira456Fields.fields, priority: 'High' },
};
const createUpdateConnectorFields123HighPriorityUserAction = () =>
getUserAction(['connector'], 'update', {
oldValue: getJiraConnectorWithoutId(),
newValue: getJiraConnectorWithoutId(jira123HighPriorityFields),
oldValConnectorId: '123',
newValConnectorId: '123',
});
const createUpdateConnectorFields456HighPriorityUserAction = () =>
getUserAction(['connector'], 'update', {
oldValue: getJiraConnectorWithoutId(jira456Fields),
newValue: getJiraConnectorWithoutId(jira456HighPriorityFields),
oldValConnectorId: '456',
newValConnectorId: '456',
});
const createChangeConnector123HighPriorityTo456UserAction = () =>
getUserAction(['connector'], 'update', {
oldValue: getJiraConnectorWithoutId(jira123HighPriorityFields),
oldValConnectorId: '123',
newValue: getJiraConnectorWithoutId(jira456Fields),
newValConnectorId: '456',
});
const createChangeConnector123To456UserAction = () =>
getUserAction(['connector'], 'update', {
oldValue: getJiraConnectorWithoutId(),
oldValConnectorId: '123',
newValue: getJiraConnectorWithoutId(jira456Fields),
newValConnectorId: '456',
});
const createChangeConnector123LowPriorityTo456UserAction = () =>
getUserAction(['connector'], 'update', {
oldValue: getJiraConnectorWithoutId(jira123LowPriorityFields),
oldValConnectorId: '123',
newValue: getJiraConnectorWithoutId(jira456Fields),
newValConnectorId: '456',
});
const createChangeConnector456To123UserAction = () =>
getUserAction(['connector'], 'update', {
oldValue: getJiraConnectorWithoutId(jira456Fields),
oldValConnectorId: '456',
newValue: getJiraConnectorWithoutId(),
newValConnectorId: '123',
});
const createChangeConnector456To123HighPriorityUserAction = () =>
getUserAction(['connector'], 'update', {
oldValue: getJiraConnectorWithoutId(jira456Fields),
oldValConnectorId: '456',
newValue: getJiraConnectorWithoutId(jira123HighPriorityFields),
newValConnectorId: '123',
});
const createChangeConnector456To123PriorityLowUserAction = () =>
getUserAction(['connector'], 'update', {
oldValue: getJiraConnectorWithoutId(jira456Fields),
oldValConnectorId: '456',
newValue: getJiraConnectorWithoutId(jira123LowPriorityFields),
newValConnectorId: '123',
});

View file

@ -18,8 +18,7 @@ import {
} from '../../common';
import { getCaseUserActions, getSubCaseUserActions } from './api';
import * as i18n from './translations';
import { convertToCamelCase } from './utils';
import { parseStringAsConnector, parseStringAsExternalService } from '../common/user_actions';
import { convertToCamelCase, parseString } from './utils';
import { useToasts } from '../common/lib/kibana';
export interface CaseService extends CaseExternalService {
@ -59,24 +58,8 @@ export interface UseGetCaseUserActions extends CaseUserActionsState {
) => Promise<void>;
}
const unknownExternalServiceConnectorId = 'unknown';
const getExternalService = (
connectorId: string | null,
encodedValue: string | null
): CaseExternalService | null => {
const decodedValue = parseStringAsExternalService(connectorId, encodedValue);
if (decodedValue == null) {
return null;
}
return {
...convertToCamelCase<CaseFullExternalService, CaseExternalService>(decodedValue),
// if in the rare case that the connector id is null we'll set it to unknown if we need to reference it in the UI
// anywhere. The id would only ever be null if a migration failed or some logic error within the backend occurred
connectorId: connectorId ?? unknownExternalServiceConnectorId,
};
};
const getExternalService = (value: string): CaseExternalService | null =>
convertToCamelCase<CaseFullExternalService, CaseExternalService>(parseString(`${value}`));
const groupConnectorFields = (
userActions: CaseUserActions[]
@ -86,26 +69,22 @@ const groupConnectorFields = (
return acc;
}
const oldConnector = parseStringAsConnector(mua.oldValConnectorId, mua.oldValue);
const newConnector = parseStringAsConnector(mua.newValConnectorId, mua.newValue);
const oldValue = parseString(`${mua.oldValue}`);
const newValue = parseString(`${mua.newValue}`);
if (!oldConnector || !newConnector) {
if (oldValue == null || newValue == null) {
return acc;
}
return {
...acc,
[oldConnector.id]: [
...(acc[oldConnector.id] || []),
...(oldConnector.id === newConnector.id
? [oldConnector.fields, newConnector.fields]
: [oldConnector.fields]),
[oldValue.id]: [
...(acc[oldValue.id] || []),
...(oldValue.id === newValue.id ? [oldValue.fields, newValue.fields] : [oldValue.fields]),
],
[newConnector.id]: [
...(acc[newConnector.id] || []),
...(oldConnector.id === newConnector.id
? [oldConnector.fields, newConnector.fields]
: [newConnector.fields]),
[newValue.id]: [
...(acc[newValue.id] || []),
...(oldValue.id === newValue.id ? [oldValue.fields, newValue.fields] : [newValue.fields]),
],
};
}, {} as Record<string, Array<CaseConnector['fields']>>);
@ -158,7 +137,9 @@ export const getPushedInfo = (
const hasDataToPushForConnector = (connectorId: string): boolean => {
const caseUserActionsReversed = [...caseUserActions].reverse();
const lastPushOfConnectorReversedIndex = caseUserActionsReversed.findIndex(
(mua) => mua.action === 'push-to-service' && mua.newValConnectorId === connectorId
(mua) =>
mua.action === 'push-to-service' &&
getExternalService(`${mua.newValue}`)?.connectorId === connectorId
);
if (lastPushOfConnectorReversedIndex === -1) {
@ -209,7 +190,7 @@ export const getPushedInfo = (
return acc;
}
const externalService = getExternalService(cua.newValConnectorId, cua.newValue);
const externalService = getExternalService(`${cua.newValue}`);
if (externalService === null) {
return acc;
}

View file

@ -36,6 +36,14 @@ import * as i18n from './translations';
export const getTypedPayload = <T>(a: unknown): T => a as T;
export const parseString = (params: string) => {
try {
return JSON.parse(params);
} catch {
return null;
}
};
export const convertArrayToCamelCase = (arrayOfSnakes: unknown[]): unknown[] =>
arrayOfSnakes.reduce((acc: unknown[], value) => {
if (isArray(value)) {

View file

@ -106,7 +106,7 @@ async function getSubCase({
caseId,
subCaseId: newSubCase.id,
fields: ['status', 'sub_case'],
newValue: { status: newSubCase.attributes.status },
newValue: JSON.stringify({ status: newSubCase.attributes.status }),
owner: newSubCase.attributes.owner,
}),
],
@ -220,7 +220,7 @@ const addGeneratedAlerts = async (
subCaseId: updatedCase.subCaseId,
commentId: newComment.id,
fields: ['comment'],
newValue: query,
newValue: JSON.stringify(query),
owner: newComment.attributes.owner,
}),
],
@ -408,7 +408,7 @@ export const addComment = async (
subCaseId: updatedCase.subCaseId,
commentId: newComment.id,
fields: ['comment'],
newValue: query,
newValue: JSON.stringify(query),
owner: newComment.attributes.owner,
}),
],

View file

@ -17,7 +17,6 @@ import {
SUB_CASE_SAVED_OBJECT,
CaseResponse,
CommentPatchRequest,
CommentRequest,
} from '../../../common';
import { AttachmentService, CasesService } from '../../services';
import { CasesClientArgs } from '..';
@ -194,12 +193,12 @@ export async function update(
subCaseId: subCaseID,
commentId: updatedComment.id,
fields: ['comment'],
// casting because typescript is complaining that it's not a Record<string, unknown> even though it is
newValue: queryRestAttributes as CommentRequest,
oldValue:
newValue: JSON.stringify(queryRestAttributes),
oldValue: JSON.stringify(
// We are interested only in ContextBasicRt attributes
// myComment.attribute contains also CommentAttributesBasicRt attributes
pick(Object.keys(queryRestAttributes), myComment.attributes),
pick(Object.keys(queryRestAttributes), myComment.attributes)
),
owner: myComment.attributes.owner,
}),
],

View file

@ -106,7 +106,7 @@ export const create = async (
actionBy: { username, full_name, email },
caseId: newCase.id,
fields: ['description', 'status', 'tags', 'title', 'connector', 'settings', OWNER_FIELD],
newValue: query,
newValue: JSON.stringify(query),
owner: newCase.attributes.owner,
}),
],

View file

@ -168,7 +168,7 @@ export async function deleteCases(ids: string[], clientArgs: CasesClientArgs): P
'settings',
OWNER_FIELD,
'comment',
...(ENABLE_CASE_CONNECTOR ? ['sub_case' as const] : []),
...(ENABLE_CASE_CONNECTOR ? ['sub_case'] : []),
],
owner: caseInfo.attributes.owner,
})

View file

@ -231,10 +231,8 @@ export const userActions: CaseUserActionsResponse = [
username: 'elastic',
},
new_value:
'{"title":"Case SIR","tags":["sir"],"description":"testing sir","connector":{"name":"ServiceNow SN","type":".servicenow-sir","fields":{"category":"Denial of Service","destIp":true,"malwareHash":true,"malwareUrl":true,"priority":"2","sourceIp":true,"subcategory":"45"}},"settings":{"syncAlerts":true}}',
new_val_connector_id: '456',
'{"title":"Case SIR","tags":["sir"],"description":"testing sir","connector":{"id":"456","name":"ServiceNow SN","type":".servicenow-sir","fields":{"category":"Denial of Service","destIp":true,"malwareHash":true,"malwareUrl":true,"priority":"2","sourceIp":true,"subcategory":"45"}},"settings":{"syncAlerts":true}}',
old_value: null,
old_val_connector_id: null,
action_id: 'fd830c60-6646-11eb-a291-51bf6b175a53',
case_id: 'fcdedd20-6646-11eb-a291-51bf6b175a53',
comment_id: null,
@ -250,9 +248,7 @@ export const userActions: CaseUserActionsResponse = [
username: 'elastic',
},
new_value:
'{"pushed_at":"2021-02-03T17:41:26.108Z","pushed_by":{"username":"elastic","full_name":"Elastic","email":"elastic@elastic.co"},"connector_name":"ServiceNow SN","external_id":"external-id","external_title":"SIR0010037","external_url":"https://dev92273.service-now.com/nav_to.do?uri=sn_si_incident.do?sys_id=external-id"}',
new_val_connector_id: '456',
old_val_connector_id: null,
'{"pushed_at":"2021-02-03T17:41:26.108Z","pushed_by":{"username":"elastic","full_name":"Elastic","email":"elastic@elastic.co"},"connector_id":"456","connector_name":"ServiceNow SN","external_id":"external-id","external_title":"SIR0010037","external_url":"https://dev92273.service-now.com/nav_to.do?uri=sn_si_incident.do?sys_id=external-id"}',
old_value: null,
action_id: '0a801750-6647-11eb-a291-51bf6b175a53',
case_id: 'fcdedd20-6646-11eb-a291-51bf6b175a53',
@ -269,8 +265,6 @@ export const userActions: CaseUserActionsResponse = [
username: 'elastic',
},
new_value: '{"type":"alert","alertId":"alert-id-1","index":".siem-signals-default-000008"}',
new_val_connector_id: null,
old_val_connector_id: null,
old_value: null,
action_id: '7373eb60-6647-11eb-a291-51bf6b175a53',
case_id: 'fcdedd20-6646-11eb-a291-51bf6b175a53',
@ -288,8 +282,6 @@ export const userActions: CaseUserActionsResponse = [
},
new_value: '{"type":"alert","alertId":"alert-id-2","index":".siem-signals-default-000008"}',
old_value: null,
new_val_connector_id: null,
old_val_connector_id: null,
action_id: '7abc6410-6647-11eb-a291-51bf6b175a53',
case_id: 'fcdedd20-6646-11eb-a291-51bf6b175a53',
comment_id: 'comment-alert-2',
@ -305,10 +297,8 @@ export const userActions: CaseUserActionsResponse = [
username: 'elastic',
},
new_value:
'{"pushed_at":"2021-02-03T17:45:29.400Z","pushed_by":{"username":"elastic","full_name":"Elastic","email":"elastic@elastic.co"},"connector_name":"ServiceNow SN","external_id":"external-id","external_title":"SIR0010037","external_url":"https://dev92273.service-now.com/nav_to.do?uri=sn_si_incident.do?sys_id=external-id"}',
new_val_connector_id: '456',
'{"pushed_at":"2021-02-03T17:45:29.400Z","pushed_by":{"username":"elastic","full_name":"Elastic","email":"elastic@elastic.co"},"connector_id":"456","connector_name":"ServiceNow SN","external_id":"external-id","external_title":"SIR0010037","external_url":"https://dev92273.service-now.com/nav_to.do?uri=sn_si_incident.do?sys_id=external-id"}',
old_value: null,
old_val_connector_id: null,
action_id: '9b91d8f0-6647-11eb-a291-51bf6b175a53',
case_id: 'fcdedd20-6646-11eb-a291-51bf6b175a53',
comment_id: null,
@ -325,8 +315,6 @@ export const userActions: CaseUserActionsResponse = [
},
new_value: '{"comment":"a comment!","type":"user"}',
old_value: null,
new_val_connector_id: null,
old_val_connector_id: null,
action_id: '0818e5e0-6648-11eb-a291-51bf6b175a53',
case_id: 'fcdedd20-6646-11eb-a291-51bf6b175a53',
comment_id: 'comment-user-1',

View file

@ -241,7 +241,7 @@ export const push = async (
actionBy: { username, full_name, email },
caseId,
fields: ['pushed'],
newValue: externalService,
newValue: JSON.stringify(externalService),
owner: myCase.attributes.owner,
}),
],

View file

@ -799,10 +799,8 @@ describe('utils', () => {
username: 'elastic',
},
new_value:
// The connector id is 123
'{"pushed_at":"2021-02-03T17:45:29.400Z","pushed_by":{"username":"elastic","full_name":"Elastic","email":"elastic@elastic.co"},"connector_name":"ServiceNow SN","external_id":"external-id","external_title":"SIR0010037","external_url":"https://dev92273.service-now.com/nav_to.do?uri=sn_si_incident.do?sys_id=external-id"}',
new_val_connector_id: '123',
old_val_connector_id: null,
// The connector id is 123
'{"pushed_at":"2021-02-03T17:45:29.400Z","pushed_by":{"username":"elastic","full_name":"Elastic","email":"elastic@elastic.co"},"connector_id":"123","connector_name":"ServiceNow SN","external_id":"external-id","external_title":"SIR0010037","external_url":"https://dev92273.service-now.com/nav_to.do?uri=sn_si_incident.do?sys_id=external-id"}',
old_value: null,
action_id: '9b91d8f0-6647-11eb-a291-51bf6b175a53',
case_id: 'fcdedd20-6646-11eb-a291-51bf6b175a53',

View file

@ -20,8 +20,6 @@ import {
CommentRequestUserType,
CommentRequestAlertType,
CommentRequestActionsType,
CaseUserActionResponse,
isPush,
} from '../../../common';
import { ActionsClient } from '../../../../actions/server';
import { CasesClientGetAlertsResponse } from '../../client/alerts/types';
@ -57,36 +55,22 @@ export const getLatestPushInfo = (
userActions: CaseUserActionsResponse
): { index: number; pushedInfo: CaseFullExternalService } | null => {
for (const [index, action] of [...userActions].reverse().entries()) {
if (
isPush(action.action, action.action_field) &&
isValidNewValue(action) &&
connectorId === action.new_val_connector_id
) {
if (action.action === 'push-to-service' && action.new_value)
try {
const pushedInfo = JSON.parse(action.new_value);
// We returned the index of the element in the userActions array.
// As we traverse the userActions in reverse we need to calculate the index of a normal traversal
return {
index: userActions.length - index - 1,
pushedInfo: { ...pushedInfo, connector_id: connectorId },
};
if (pushedInfo.connector_id === connectorId) {
// We returned the index of the element in the userActions array.
// As we traverse the userActions in reverse we need to calculate the index of a normal traversal
return { index: userActions.length - index - 1, pushedInfo };
}
} catch (e) {
// ignore parse failures and check the next user action
// Silence JSON parse errors
}
}
}
return null;
};
type NonNullNewValueAction = Omit<CaseUserActionResponse, 'new_value' | 'new_val_connector_id'> & {
new_value: string;
new_val_connector_id: string;
};
const isValidNewValue = (userAction: CaseUserActionResponse): userAction is NonNullNewValueAction =>
userAction.new_val_connector_id != null && userAction.new_value != null;
const getCommentContent = (comment: CommentResponse): string => {
if (comment.type === CommentType.user) {
return comment.comment;

View file

@ -1,106 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { CaseUserActionResponse, SUB_CASE_SAVED_OBJECT } from '../../../common';
import { SUB_CASE_REF_NAME } from '../../common';
import { extractAttributesWithoutSubCases } from './get';
describe('get', () => {
describe('extractAttributesWithoutSubCases', () => {
it('returns an empty array when given an empty array', () => {
expect(
extractAttributesWithoutSubCases({ ...getFindResponseFields(), saved_objects: [] })
).toEqual([]);
});
it('filters out saved objects with a sub case reference', () => {
expect(
extractAttributesWithoutSubCases({
...getFindResponseFields(),
saved_objects: [
{
type: 'a',
references: [{ name: SUB_CASE_REF_NAME, type: SUB_CASE_SAVED_OBJECT, id: '1' }],
id: 'b',
score: 0,
attributes: {} as CaseUserActionResponse,
},
],
})
).toEqual([]);
});
it('filters out saved objects with a sub case reference with other references', () => {
expect(
extractAttributesWithoutSubCases({
...getFindResponseFields(),
saved_objects: [
{
type: 'a',
references: [
{ name: SUB_CASE_REF_NAME, type: SUB_CASE_SAVED_OBJECT, id: '1' },
{ name: 'a', type: 'b', id: '5' },
],
id: 'b',
score: 0,
attributes: {} as CaseUserActionResponse,
},
],
})
).toEqual([]);
});
it('keeps saved objects that do not have a sub case reference', () => {
expect(
extractAttributesWithoutSubCases({
...getFindResponseFields(),
saved_objects: [
{
type: 'a',
references: [
{ name: SUB_CASE_REF_NAME, type: 'awesome', id: '1' },
{ name: 'a', type: 'b', id: '5' },
],
id: 'b',
score: 0,
attributes: { field: '1' } as unknown as CaseUserActionResponse,
},
],
})
).toEqual([{ field: '1' }]);
});
it('filters multiple saved objects correctly', () => {
expect(
extractAttributesWithoutSubCases({
...getFindResponseFields(),
saved_objects: [
{
type: 'a',
references: [
{ name: SUB_CASE_REF_NAME, type: 'awesome', id: '1' },
{ name: 'a', type: 'b', id: '5' },
],
id: 'b',
score: 0,
attributes: { field: '2' } as unknown as CaseUserActionResponse,
},
{
type: 'a',
references: [{ name: SUB_CASE_REF_NAME, type: SUB_CASE_SAVED_OBJECT, id: '1' }],
id: 'b',
score: 0,
attributes: { field: '1' } as unknown as CaseUserActionResponse,
},
],
})
).toEqual([{ field: '2' }]);
});
});
});
const getFindResponseFields = () => ({ page: 1, per_page: 1, total: 0 });

View file

@ -5,14 +5,14 @@
* 2.0.
*/
import { SavedObjectReference, SavedObjectsFindResponse } from 'kibana/server';
import {
CASE_COMMENT_SAVED_OBJECT,
CASE_SAVED_OBJECT,
CaseUserActionsResponse,
CaseUserActionsResponseRt,
SUB_CASE_SAVED_OBJECT,
CaseUserActionResponse,
} from '../../../common';
import { createCaseError, checkEnabledCaseConnectorOrThrow, SUB_CASE_REF_NAME } from '../../common';
import { createCaseError, checkEnabledCaseConnectorOrThrow } from '../../common';
import { CasesClientArgs } from '..';
import { Operations } from '../../authorization';
import { UserActionGet } from './client';
@ -40,12 +40,23 @@ export const get = async (
operation: Operations.getUserActions,
});
const resultsToEncode =
subCaseId == null
? extractAttributesWithoutSubCases(userActions)
: extractAttributes(userActions);
return CaseUserActionsResponseRt.encode(resultsToEncode);
return CaseUserActionsResponseRt.encode(
userActions.saved_objects.reduce<CaseUserActionsResponse>((acc, ua) => {
if (subCaseId == null && ua.references.some((uar) => uar.type === SUB_CASE_SAVED_OBJECT)) {
return acc;
}
return [
...acc,
{
...ua.attributes,
action_id: ua.id,
case_id: ua.references.find((r) => r.type === CASE_SAVED_OBJECT)?.id ?? '',
comment_id: ua.references.find((r) => r.type === CASE_COMMENT_SAVED_OBJECT)?.id ?? null,
sub_case_id: ua.references.find((r) => r.type === SUB_CASE_SAVED_OBJECT)?.id ?? '',
},
];
}, [])
);
} catch (error) {
throw createCaseError({
message: `Failed to retrieve user actions case id: ${caseId} sub case id: ${subCaseId}: ${error}`,
@ -54,21 +65,3 @@ export const get = async (
});
}
};
export function extractAttributesWithoutSubCases(
userActions: SavedObjectsFindResponse<CaseUserActionResponse>
): CaseUserActionsResponse {
// exclude user actions relating to sub cases from the results
const hasSubCaseReference = (references: SavedObjectReference[]) =>
references.find((ref) => ref.type === SUB_CASE_SAVED_OBJECT && ref.name === SUB_CASE_REF_NAME);
return userActions.saved_objects
.filter((so) => !hasSubCaseReference(so.references))
.map((so) => so.attributes);
}
function extractAttributes(
userActions: SavedObjectsFindResponse<CaseUserActionResponse>
): CaseUserActionsResponse {
return userActions.saved_objects.map((so) => so.attributes);
}

View file

@ -5,8 +5,6 @@
* 2.0.
*/
import { CASE_COMMENT_SAVED_OBJECT, CASE_SAVED_OBJECT, SUB_CASE_SAVED_OBJECT } from '../../common';
/**
* The name of the saved object reference indicating the action connector ID. This is stored in the Saved Object reference
* field's name property.
@ -17,30 +15,3 @@ export const CONNECTOR_ID_REFERENCE_NAME = 'connectorId';
* The name of the saved object reference indicating the action connector ID that was used to push a case.
*/
export const PUSH_CONNECTOR_ID_REFERENCE_NAME = 'pushConnectorId';
/**
* The name of the saved object reference indicating the action connector ID that was used for
* adding a connector, or updating the existing connector for a user action's old_value field.
*/
export const USER_ACTION_OLD_ID_REF_NAME = 'oldConnectorId';
/**
* The name of the saved object reference indicating the action connector ID that was used for pushing a case,
* for a user action's old_value field.
*/
export const USER_ACTION_OLD_PUSH_ID_REF_NAME = 'oldPushConnectorId';
/**
* The name of the saved object reference indicating the caseId reference
*/
export const CASE_REF_NAME = `associated-${CASE_SAVED_OBJECT}`;
/**
* The name of the saved object reference indicating the commentId reference
*/
export const COMMENT_REF_NAME = `associated-${CASE_COMMENT_SAVED_OBJECT}`;
/**
* The name of the saved object reference indicating the subCaseId reference
*/
export const SUB_CASE_REF_NAME = `associated-${SUB_CASE_SAVED_OBJECT}`;

View file

@ -30,324 +30,322 @@ const create_7_14_0_case = ({
},
});
describe('case migrations', () => {
describe('7.15.0 connector ID migration', () => {
it('does not create a reference when the connector.id is none', () => {
const caseSavedObject = create_7_14_0_case({ connector: getNoneCaseConnector() });
describe('7.15.0 connector ID migration', () => {
it('does not create a reference when the connector.id is none', () => {
const caseSavedObject = create_7_14_0_case({ connector: getNoneCaseConnector() });
const migratedConnector = caseConnectorIdMigration(
caseSavedObject
) as SavedObjectSanitizedDoc<CaseAttributes>;
const migratedConnector = caseConnectorIdMigration(
caseSavedObject
) as SavedObjectSanitizedDoc<CaseAttributes>;
expect(migratedConnector.references.length).toBe(0);
expect(migratedConnector.attributes.connector).not.toHaveProperty('id');
expect(migratedConnector.attributes.connector).toMatchInlineSnapshot(`
Object {
"fields": null,
"name": "none",
"type": ".none",
}
`);
expect(migratedConnector.references.length).toBe(0);
expect(migratedConnector.attributes.connector).not.toHaveProperty('id');
expect(migratedConnector.attributes.connector).toMatchInlineSnapshot(`
Object {
"fields": null,
"name": "none",
"type": ".none",
}
`);
});
it('does not create a reference when the connector is undefined', () => {
const caseSavedObject = create_7_14_0_case();
const migratedConnector = caseConnectorIdMigration(
caseSavedObject
) as SavedObjectSanitizedDoc<CaseAttributes>;
expect(migratedConnector.references.length).toBe(0);
expect(migratedConnector.attributes.connector).not.toHaveProperty('id');
expect(migratedConnector.attributes.connector).toMatchInlineSnapshot(`
Object {
"fields": null,
"name": "none",
"type": ".none",
}
`);
});
it('sets the connector to the default none connector if the connector.id is undefined', () => {
const caseSavedObject = create_7_14_0_case({
connector: {
fields: null,
name: ConnectorTypes.jira,
type: ConnectorTypes.jira,
} as ESCaseConnectorWithId,
});
it('does not create a reference when the connector is undefined', () => {
const caseSavedObject = create_7_14_0_case();
const migratedConnector = caseConnectorIdMigration(
caseSavedObject
) as SavedObjectSanitizedDoc<CaseAttributes>;
const migratedConnector = caseConnectorIdMigration(
caseSavedObject
) as SavedObjectSanitizedDoc<CaseAttributes>;
expect(migratedConnector.references.length).toBe(0);
expect(migratedConnector.attributes.connector).not.toHaveProperty('id');
expect(migratedConnector.attributes.connector).toMatchInlineSnapshot(`
Object {
"fields": null,
"name": "none",
"type": ".none",
}
`);
});
expect(migratedConnector.references.length).toBe(0);
expect(migratedConnector.attributes.connector).not.toHaveProperty('id');
expect(migratedConnector.attributes.connector).toMatchInlineSnapshot(`
Object {
"fields": null,
"name": "none",
"type": ".none",
}
`);
it('does not create a reference when the external_service is null', () => {
const caseSavedObject = create_7_14_0_case({ externalService: null });
const migratedConnector = caseConnectorIdMigration(
caseSavedObject
) as SavedObjectSanitizedDoc<CaseAttributes>;
expect(migratedConnector.references.length).toBe(0);
expect(migratedConnector.attributes.external_service).toBeNull();
});
it('does not create a reference when the external_service is undefined and sets external_service to null', () => {
const caseSavedObject = create_7_14_0_case();
const migratedConnector = caseConnectorIdMigration(
caseSavedObject
) as SavedObjectSanitizedDoc<CaseAttributes>;
expect(migratedConnector.references.length).toBe(0);
expect(migratedConnector.attributes.external_service).toBeNull();
});
it('does not create a reference when the external_service.connector_id is none', () => {
const caseSavedObject = create_7_14_0_case({
externalService: createExternalService({ connector_id: noneConnectorId }),
});
it('sets the connector to the default none connector if the connector.id is undefined', () => {
const caseSavedObject = create_7_14_0_case({
connector: {
fields: null,
name: ConnectorTypes.jira,
type: ConnectorTypes.jira,
} as ESCaseConnectorWithId,
});
const migratedConnector = caseConnectorIdMigration(
caseSavedObject
) as SavedObjectSanitizedDoc<CaseAttributes>;
const migratedConnector = caseConnectorIdMigration(
caseSavedObject
) as SavedObjectSanitizedDoc<CaseAttributes>;
expect(migratedConnector.references.length).toBe(0);
expect(migratedConnector.attributes.connector).not.toHaveProperty('id');
expect(migratedConnector.attributes.connector).toMatchInlineSnapshot(`
Object {
"fields": null,
"name": "none",
"type": ".none",
}
`);
});
it('does not create a reference when the external_service is null', () => {
const caseSavedObject = create_7_14_0_case({ externalService: null });
const migratedConnector = caseConnectorIdMigration(
caseSavedObject
) as SavedObjectSanitizedDoc<CaseAttributes>;
expect(migratedConnector.references.length).toBe(0);
expect(migratedConnector.attributes.external_service).toBeNull();
});
it('does not create a reference when the external_service is undefined and sets external_service to null', () => {
const caseSavedObject = create_7_14_0_case();
const migratedConnector = caseConnectorIdMigration(
caseSavedObject
) as SavedObjectSanitizedDoc<CaseAttributes>;
expect(migratedConnector.references.length).toBe(0);
expect(migratedConnector.attributes.external_service).toBeNull();
});
it('does not create a reference when the external_service.connector_id is none', () => {
const caseSavedObject = create_7_14_0_case({
externalService: createExternalService({ connector_id: noneConnectorId }),
});
const migratedConnector = caseConnectorIdMigration(
caseSavedObject
) as SavedObjectSanitizedDoc<CaseAttributes>;
expect(migratedConnector.references.length).toBe(0);
expect(migratedConnector.attributes.external_service).toMatchInlineSnapshot(`
Object {
"connector_name": ".jira",
"external_id": "100",
"external_title": "awesome",
"external_url": "http://www.google.com",
"pushed_at": "2019-11-25T21:54:48.952Z",
"pushed_by": Object {
"email": "testemail@elastic.co",
"full_name": "elastic",
"username": "elastic",
},
}
`);
});
it('preserves the existing references when migrating', () => {
const caseSavedObject = {
...create_7_14_0_case(),
references: [{ id: '1', name: 'awesome', type: 'hello' }],
};
const migratedConnector = caseConnectorIdMigration(
caseSavedObject
) as SavedObjectSanitizedDoc<CaseAttributes>;
expect(migratedConnector.references.length).toBe(1);
expect(migratedConnector.references).toMatchInlineSnapshot(`
Array [
Object {
"id": "1",
"name": "awesome",
"type": "hello",
},
]
`);
});
it('creates a connector reference and removes the connector.id field', () => {
const caseSavedObject = create_7_14_0_case({
connector: {
id: '123',
fields: null,
name: 'connector',
type: ConnectorTypes.jira,
expect(migratedConnector.references.length).toBe(0);
expect(migratedConnector.attributes.external_service).toMatchInlineSnapshot(`
Object {
"connector_name": ".jira",
"external_id": "100",
"external_title": "awesome",
"external_url": "http://www.google.com",
"pushed_at": "2019-11-25T21:54:48.952Z",
"pushed_by": Object {
"email": "testemail@elastic.co",
"full_name": "elastic",
"username": "elastic",
},
});
}
`);
});
const migratedConnector = caseConnectorIdMigration(
caseSavedObject
) as SavedObjectSanitizedDoc<CaseAttributes>;
it('preserves the existing references when migrating', () => {
const caseSavedObject = {
...create_7_14_0_case(),
references: [{ id: '1', name: 'awesome', type: 'hello' }],
};
expect(migratedConnector.references.length).toBe(1);
expect(migratedConnector.attributes.connector).not.toHaveProperty('id');
expect(migratedConnector.attributes.connector).toMatchInlineSnapshot(`
const migratedConnector = caseConnectorIdMigration(
caseSavedObject
) as SavedObjectSanitizedDoc<CaseAttributes>;
expect(migratedConnector.references.length).toBe(1);
expect(migratedConnector.references).toMatchInlineSnapshot(`
Array [
Object {
"fields": null,
"name": "connector",
"type": ".jira",
}
`);
expect(migratedConnector.references).toMatchInlineSnapshot(`
Array [
Object {
"id": "123",
"name": "connectorId",
"type": "action",
},
]
`);
"id": "1",
"name": "awesome",
"type": "hello",
},
]
`);
});
it('creates a connector reference and removes the connector.id field', () => {
const caseSavedObject = create_7_14_0_case({
connector: {
id: '123',
fields: null,
name: 'connector',
type: ConnectorTypes.jira,
},
});
it('creates a push connector reference and removes the connector_id field', () => {
const caseSavedObject = create_7_14_0_case({
externalService: {
connector_id: '100',
connector_name: '.jira',
external_id: '100',
external_title: 'awesome',
external_url: 'http://www.google.com',
pushed_at: '2019-11-25T21:54:48.952Z',
pushed_by: {
full_name: 'elastic',
email: 'testemail@elastic.co',
username: 'elastic',
},
},
});
const migratedConnector = caseConnectorIdMigration(
caseSavedObject
) as SavedObjectSanitizedDoc<CaseAttributes>;
const migratedConnector = caseConnectorIdMigration(
caseSavedObject
) as SavedObjectSanitizedDoc<CaseAttributes>;
expect(migratedConnector.references.length).toBe(1);
expect(migratedConnector.attributes.external_service).not.toHaveProperty('connector_id');
expect(migratedConnector.attributes.external_service).toMatchInlineSnapshot(`
expect(migratedConnector.references.length).toBe(1);
expect(migratedConnector.attributes.connector).not.toHaveProperty('id');
expect(migratedConnector.attributes.connector).toMatchInlineSnapshot(`
Object {
"fields": null,
"name": "connector",
"type": ".jira",
}
`);
expect(migratedConnector.references).toMatchInlineSnapshot(`
Array [
Object {
"connector_name": ".jira",
"external_id": "100",
"external_title": "awesome",
"external_url": "http://www.google.com",
"pushed_at": "2019-11-25T21:54:48.952Z",
"pushed_by": Object {
"email": "testemail@elastic.co",
"full_name": "elastic",
"username": "elastic",
},
}
`);
expect(migratedConnector.references).toMatchInlineSnapshot(`
Array [
Object {
"id": "100",
"name": "pushConnectorId",
"type": "action",
},
]
`);
"id": "123",
"name": "connectorId",
"type": "action",
},
]
`);
});
it('creates a push connector reference and removes the connector_id field', () => {
const caseSavedObject = create_7_14_0_case({
externalService: {
connector_id: '100',
connector_name: '.jira',
external_id: '100',
external_title: 'awesome',
external_url: 'http://www.google.com',
pushed_at: '2019-11-25T21:54:48.952Z',
pushed_by: {
full_name: 'elastic',
email: 'testemail@elastic.co',
username: 'elastic',
},
},
});
it('does not create a reference and preserves the existing external_service fields when connector_id is null', () => {
const caseSavedObject = create_7_14_0_case({
externalService: {
connector_id: null,
connector_name: '.jira',
external_id: '100',
external_title: 'awesome',
external_url: 'http://www.google.com',
pushed_at: '2019-11-25T21:54:48.952Z',
pushed_by: {
full_name: 'elastic',
email: 'testemail@elastic.co',
username: 'elastic',
},
const migratedConnector = caseConnectorIdMigration(
caseSavedObject
) as SavedObjectSanitizedDoc<CaseAttributes>;
expect(migratedConnector.references.length).toBe(1);
expect(migratedConnector.attributes.external_service).not.toHaveProperty('connector_id');
expect(migratedConnector.attributes.external_service).toMatchInlineSnapshot(`
Object {
"connector_name": ".jira",
"external_id": "100",
"external_title": "awesome",
"external_url": "http://www.google.com",
"pushed_at": "2019-11-25T21:54:48.952Z",
"pushed_by": Object {
"email": "testemail@elastic.co",
"full_name": "elastic",
"username": "elastic",
},
});
const migratedConnector = caseConnectorIdMigration(
caseSavedObject
) as SavedObjectSanitizedDoc<CaseAttributes>;
expect(migratedConnector.references.length).toBe(0);
expect(migratedConnector.attributes.external_service).not.toHaveProperty('connector_id');
expect(migratedConnector.attributes.external_service).toMatchInlineSnapshot(`
}
`);
expect(migratedConnector.references).toMatchInlineSnapshot(`
Array [
Object {
"connector_name": ".jira",
"external_id": "100",
"external_title": "awesome",
"external_url": "http://www.google.com",
"pushed_at": "2019-11-25T21:54:48.952Z",
"pushed_by": Object {
"email": "testemail@elastic.co",
"full_name": "elastic",
"username": "elastic",
},
}
`);
"id": "100",
"name": "pushConnectorId",
"type": "action",
},
]
`);
});
it('does not create a reference and preserves the existing external_service fields when connector_id is null', () => {
const caseSavedObject = create_7_14_0_case({
externalService: {
connector_id: null,
connector_name: '.jira',
external_id: '100',
external_title: 'awesome',
external_url: 'http://www.google.com',
pushed_at: '2019-11-25T21:54:48.952Z',
pushed_by: {
full_name: 'elastic',
email: 'testemail@elastic.co',
username: 'elastic',
},
},
});
it('migrates both connector and external_service when provided', () => {
const caseSavedObject = create_7_14_0_case({
externalService: {
connector_id: '100',
connector_name: '.jira',
external_id: '100',
external_title: 'awesome',
external_url: 'http://www.google.com',
pushed_at: '2019-11-25T21:54:48.952Z',
pushed_by: {
full_name: 'elastic',
email: 'testemail@elastic.co',
username: 'elastic',
},
},
connector: {
id: '123',
fields: null,
name: 'connector',
type: ConnectorTypes.jira,
},
});
const migratedConnector = caseConnectorIdMigration(
caseSavedObject
) as SavedObjectSanitizedDoc<CaseAttributes>;
const migratedConnector = caseConnectorIdMigration(
caseSavedObject
) as SavedObjectSanitizedDoc<CaseAttributes>;
expect(migratedConnector.references.length).toBe(0);
expect(migratedConnector.attributes.external_service).not.toHaveProperty('connector_id');
expect(migratedConnector.attributes.external_service).toMatchInlineSnapshot(`
Object {
"connector_name": ".jira",
"external_id": "100",
"external_title": "awesome",
"external_url": "http://www.google.com",
"pushed_at": "2019-11-25T21:54:48.952Z",
"pushed_by": Object {
"email": "testemail@elastic.co",
"full_name": "elastic",
"username": "elastic",
},
}
`);
});
expect(migratedConnector.references.length).toBe(2);
expect(migratedConnector.attributes.external_service).not.toHaveProperty('connector_id');
expect(migratedConnector.attributes.external_service).toMatchInlineSnapshot(`
Object {
"connector_name": ".jira",
"external_id": "100",
"external_title": "awesome",
"external_url": "http://www.google.com",
"pushed_at": "2019-11-25T21:54:48.952Z",
"pushed_by": Object {
"email": "testemail@elastic.co",
"full_name": "elastic",
"username": "elastic",
},
}
`);
expect(migratedConnector.attributes.connector).not.toHaveProperty('id');
expect(migratedConnector.attributes.connector).toMatchInlineSnapshot(`
Object {
"fields": null,
"name": "connector",
"type": ".jira",
}
`);
expect(migratedConnector.references).toMatchInlineSnapshot(`
Array [
Object {
"id": "123",
"name": "connectorId",
"type": "action",
},
Object {
"id": "100",
"name": "pushConnectorId",
"type": "action",
},
]
`);
it('migrates both connector and external_service when provided', () => {
const caseSavedObject = create_7_14_0_case({
externalService: {
connector_id: '100',
connector_name: '.jira',
external_id: '100',
external_title: 'awesome',
external_url: 'http://www.google.com',
pushed_at: '2019-11-25T21:54:48.952Z',
pushed_by: {
full_name: 'elastic',
email: 'testemail@elastic.co',
username: 'elastic',
},
},
connector: {
id: '123',
fields: null,
name: 'connector',
type: ConnectorTypes.jira,
},
});
const migratedConnector = caseConnectorIdMigration(
caseSavedObject
) as SavedObjectSanitizedDoc<CaseAttributes>;
expect(migratedConnector.references.length).toBe(2);
expect(migratedConnector.attributes.external_service).not.toHaveProperty('connector_id');
expect(migratedConnector.attributes.external_service).toMatchInlineSnapshot(`
Object {
"connector_name": ".jira",
"external_id": "100",
"external_title": "awesome",
"external_url": "http://www.google.com",
"pushed_at": "2019-11-25T21:54:48.952Z",
"pushed_by": Object {
"email": "testemail@elastic.co",
"full_name": "elastic",
"username": "elastic",
},
}
`);
expect(migratedConnector.attributes.connector).not.toHaveProperty('id');
expect(migratedConnector.attributes.connector).toMatchInlineSnapshot(`
Object {
"fields": null,
"name": "connector",
"type": ".jira",
}
`);
expect(migratedConnector.references).toMatchInlineSnapshot(`
Array [
Object {
"id": "123",
"name": "connectorId",
"type": "action",
},
Object {
"id": "100",
"name": "pushConnectorId",
"type": "action",
},
]
`);
});
});

View file

@ -14,11 +14,7 @@ import {
} from '../../../../../../src/core/server';
import { ESConnectorFields } from '../../services';
import { ConnectorTypes, CaseType } from '../../../common';
import {
transformConnectorIdToReference,
transformPushConnectorIdToReference,
} from '../../services/user_actions/transform';
import { CONNECTOR_ID_REFERENCE_NAME, PUSH_CONNECTOR_ID_REFERENCE_NAME } from '../../common';
import { transformConnectorIdToReference, transformPushConnectorIdToReference } from './utils';
interface UnsanitizedCaseConnector {
connector_id: string;
@ -54,13 +50,11 @@ export const caseConnectorIdMigration = (
// removing the id field since it will be stored in the references instead
const { connector, external_service, ...restAttributes } = doc.attributes;
const { transformedConnector, references: connectorReferences } = transformConnectorIdToReference(
CONNECTOR_ID_REFERENCE_NAME,
connector
);
const { transformedConnector, references: connectorReferences } =
transformConnectorIdToReference(connector);
const { transformedPushConnector, references: pushConnectorReferences } =
transformPushConnectorIdToReference(PUSH_CONNECTOR_ID_REFERENCE_NAME, external_service);
transformPushConnectorIdToReference(external_service);
const { references = [] } = doc;

View file

@ -40,89 +40,87 @@ const create_7_14_0_configSchema = (connector?: ESCaseConnectorWithId) => ({
},
});
describe('configuration migrations', () => {
describe('7.15.0 connector ID migration', () => {
it('does not create a reference when the connector ID is none', () => {
const configureSavedObject = create_7_14_0_configSchema(getNoneCaseConnector());
describe('7.15.0 connector ID migration', () => {
it('does not create a reference when the connector ID is none', () => {
const configureSavedObject = create_7_14_0_configSchema(getNoneCaseConnector());
const migratedConnector = configureConnectorIdMigration(
configureSavedObject
) as SavedObjectSanitizedDoc<ESCasesConfigureAttributes>;
const migratedConnector = configureConnectorIdMigration(
configureSavedObject
) as SavedObjectSanitizedDoc<ESCasesConfigureAttributes>;
expect(migratedConnector.references.length).toBe(0);
expect(migratedConnector.attributes.connector).not.toHaveProperty('id');
expect(migratedConnector.references.length).toBe(0);
expect(migratedConnector.attributes.connector).not.toHaveProperty('id');
});
it('does not create a reference when the connector is undefined and defaults it to the none connector', () => {
const configureSavedObject = create_7_14_0_configSchema();
const migratedConnector = configureConnectorIdMigration(
configureSavedObject
) as SavedObjectSanitizedDoc<ESCasesConfigureAttributes>;
expect(migratedConnector.references.length).toBe(0);
expect(migratedConnector.attributes.connector).toMatchInlineSnapshot(`
Object {
"fields": null,
"name": "none",
"type": ".none",
}
`);
});
it('creates a reference using the connector id', () => {
const configureSavedObject = create_7_14_0_configSchema({
id: '123',
fields: null,
name: 'connector',
type: ConnectorTypes.jira,
});
it('does not create a reference when the connector is undefined and defaults it to the none connector', () => {
const configureSavedObject = create_7_14_0_configSchema();
const migratedConnector = configureConnectorIdMigration(
configureSavedObject
) as SavedObjectSanitizedDoc<ESCasesConfigureAttributes>;
const migratedConnector = configureConnectorIdMigration(
configureSavedObject
) as SavedObjectSanitizedDoc<ESCasesConfigureAttributes>;
expect(migratedConnector.references).toEqual([
{ id: '123', type: ACTION_SAVED_OBJECT_TYPE, name: CONNECTOR_ID_REFERENCE_NAME },
]);
expect(migratedConnector.attributes.connector).not.toHaveProperty('id');
});
expect(migratedConnector.references.length).toBe(0);
expect(migratedConnector.attributes.connector).toMatchInlineSnapshot(`
Object {
"fields": null,
"name": "none",
"type": ".none",
}
`);
});
it('returns the other attributes and default connector when the connector is undefined', () => {
const configureSavedObject = create_7_14_0_configSchema();
it('creates a reference using the connector id', () => {
const configureSavedObject = create_7_14_0_configSchema({
id: '123',
fields: null,
name: 'connector',
type: ConnectorTypes.jira,
});
const migratedConnector = configureConnectorIdMigration(
configureSavedObject
) as SavedObjectSanitizedDoc<ESCasesConfigureAttributes>;
const migratedConnector = configureConnectorIdMigration(
configureSavedObject
) as SavedObjectSanitizedDoc<ESCasesConfigureAttributes>;
expect(migratedConnector.references).toEqual([
{ id: '123', type: ACTION_SAVED_OBJECT_TYPE, name: CONNECTOR_ID_REFERENCE_NAME },
]);
expect(migratedConnector.attributes.connector).not.toHaveProperty('id');
});
it('returns the other attributes and default connector when the connector is undefined', () => {
const configureSavedObject = create_7_14_0_configSchema();
const migratedConnector = configureConnectorIdMigration(
configureSavedObject
) as SavedObjectSanitizedDoc<ESCasesConfigureAttributes>;
expect(migratedConnector).toMatchInlineSnapshot(`
Object {
"attributes": Object {
"closure_type": "close-by-pushing",
"connector": Object {
"fields": null,
"name": "none",
"type": ".none",
},
"created_at": "2020-04-09T09:43:51.778Z",
"created_by": Object {
"email": "testemail@elastic.co",
"full_name": "elastic",
"username": "elastic",
},
"owner": "securitySolution",
"updated_at": "2020-04-09T09:43:51.778Z",
"updated_by": Object {
"email": "testemail@elastic.co",
"full_name": "elastic",
"username": "elastic",
},
expect(migratedConnector).toMatchInlineSnapshot(`
Object {
"attributes": Object {
"closure_type": "close-by-pushing",
"connector": Object {
"fields": null,
"name": "none",
"type": ".none",
},
"id": "1",
"references": Array [],
"type": "cases-configure",
}
`);
});
"created_at": "2020-04-09T09:43:51.778Z",
"created_by": Object {
"email": "testemail@elastic.co",
"full_name": "elastic",
"username": "elastic",
},
"owner": "securitySolution",
"updated_at": "2020-04-09T09:43:51.778Z",
"updated_by": Object {
"email": "testemail@elastic.co",
"full_name": "elastic",
"username": "elastic",
},
},
"id": "1",
"references": Array [],
"type": "cases-configure",
}
`);
});
});

View file

@ -13,8 +13,7 @@ import {
} from '../../../../../../src/core/server';
import { ConnectorTypes } from '../../../common';
import { addOwnerToSO, SanitizedCaseOwner } from '.';
import { transformConnectorIdToReference } from '../../services/user_actions/transform';
import { CONNECTOR_ID_REFERENCE_NAME } from '../../common';
import { transformConnectorIdToReference } from './utils';
interface UnsanitizedConfigureConnector {
connector_id: string;
@ -35,10 +34,8 @@ export const configureConnectorIdMigration = (
): SavedObjectSanitizedDoc<unknown> => {
// removing the id field since it will be stored in the references instead
const { connector, ...restAttributes } = doc.attributes;
const { transformedConnector, references: connectorReferences } = transformConnectorIdToReference(
CONNECTOR_ID_REFERENCE_NAME,
connector
);
const { transformedConnector, references: connectorReferences } =
transformConnectorIdToReference(connector);
const { references = [] } = doc;
return {

View file

@ -5,17 +5,24 @@
* 2.0.
*/
/* eslint-disable @typescript-eslint/naming-convention */
import {
SavedObjectUnsanitizedDoc,
SavedObjectSanitizedDoc,
} from '../../../../../../src/core/server';
import { SECURITY_SOLUTION_OWNER } from '../../../common';
import { ConnectorTypes, SECURITY_SOLUTION_OWNER } from '../../../common';
export { caseMigrations } from './cases';
export { configureMigrations } from './configuration';
export { userActionsMigrations } from './user_actions';
export { createCommentsMigrations, CreateCommentsMigrationsDeps } from './comments';
interface UserActions {
action_field: string[];
new_value: string;
old_value: string;
}
export interface SanitizedCaseOwner {
owner: string;
}
@ -31,6 +38,52 @@ export const addOwnerToSO = <T = Record<string, unknown>>(
references: doc.references || [],
});
export const userActionsMigrations = {
'7.10.0': (doc: SavedObjectUnsanitizedDoc<UserActions>): SavedObjectSanitizedDoc<UserActions> => {
const { action_field, new_value, old_value, ...restAttributes } = doc.attributes;
if (
action_field == null ||
!Array.isArray(action_field) ||
action_field[0] !== 'connector_id'
) {
return { ...doc, references: doc.references || [] };
}
return {
...doc,
attributes: {
...restAttributes,
action_field: ['connector'],
new_value:
new_value != null
? JSON.stringify({
id: new_value,
name: 'none',
type: ConnectorTypes.none,
fields: null,
})
: new_value,
old_value:
old_value != null
? JSON.stringify({
id: old_value,
name: 'none',
type: ConnectorTypes.none,
fields: null,
})
: old_value,
},
references: doc.references || [],
};
},
'7.14.0': (
doc: SavedObjectUnsanitizedDoc<Record<string, unknown>>
): SavedObjectSanitizedDoc<SanitizedCaseOwner> => {
return addOwnerToSO(doc);
},
};
export const connectorMappingsMigrations = {
'7.14.0': (
doc: SavedObjectUnsanitizedDoc<Record<string, unknown>>

View file

@ -1,562 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
/* eslint-disable @typescript-eslint/naming-convention */
import { SavedObjectMigrationContext, SavedObjectSanitizedDoc } from 'kibana/server';
import { migrationMocks } from 'src/core/server/mocks';
import { CaseUserActionAttributes, CASE_USER_ACTION_SAVED_OBJECT } from '../../../common';
import {
createConnectorObject,
createExternalService,
createJiraConnector,
} from '../../services/test_utils';
import { userActionsConnectorIdMigration } from './user_actions';
const create_7_14_0_userAction = (
params: {
action?: string;
action_field?: string[];
new_value?: string | null | object;
old_value?: string | null | object;
} = {}
) => {
const { new_value, old_value, ...restParams } = params;
return {
type: CASE_USER_ACTION_SAVED_OBJECT,
id: '1',
attributes: {
...restParams,
new_value: new_value && typeof new_value === 'object' ? JSON.stringify(new_value) : new_value,
old_value: old_value && typeof old_value === 'object' ? JSON.stringify(old_value) : old_value,
},
};
};
describe('user action migrations', () => {
describe('7.15.0 connector ID migration', () => {
describe('userActionsConnectorIdMigration', () => {
let context: jest.Mocked<SavedObjectMigrationContext>;
beforeEach(() => {
context = migrationMocks.createContext();
});
describe('push user action', () => {
it('extracts the external_service connector_id to references for a new pushed user action', () => {
const userAction = create_7_14_0_userAction({
action: 'push-to-service',
action_field: ['pushed'],
new_value: createExternalService(),
old_value: null,
});
const migratedUserAction = userActionsConnectorIdMigration(
userAction,
context
) as SavedObjectSanitizedDoc<CaseUserActionAttributes>;
const parsedExternalService = JSON.parse(migratedUserAction.attributes.new_value!);
expect(parsedExternalService).not.toHaveProperty('connector_id');
expect(parsedExternalService).toMatchInlineSnapshot(`
Object {
"connector_name": ".jira",
"external_id": "100",
"external_title": "awesome",
"external_url": "http://www.google.com",
"pushed_at": "2019-11-25T21:54:48.952Z",
"pushed_by": Object {
"email": "testemail@elastic.co",
"full_name": "elastic",
"username": "elastic",
},
}
`);
expect(migratedUserAction.references).toMatchInlineSnapshot(`
Array [
Object {
"id": "100",
"name": "pushConnectorId",
"type": "action",
},
]
`);
expect(migratedUserAction.attributes.old_value).toBeNull();
});
it('extract the external_service connector_id to references for new and old pushed user action', () => {
const userAction = create_7_14_0_userAction({
action: 'push-to-service',
action_field: ['pushed'],
new_value: createExternalService(),
old_value: createExternalService({ connector_id: '5' }),
});
const migratedUserAction = userActionsConnectorIdMigration(
userAction,
context
) as SavedObjectSanitizedDoc<CaseUserActionAttributes>;
const parsedNewExternalService = JSON.parse(migratedUserAction.attributes.new_value!);
const parsedOldExternalService = JSON.parse(migratedUserAction.attributes.old_value!);
expect(parsedNewExternalService).not.toHaveProperty('connector_id');
expect(parsedOldExternalService).not.toHaveProperty('connector_id');
expect(migratedUserAction.references).toEqual([
{ id: '100', name: 'pushConnectorId', type: 'action' },
{ id: '5', name: 'oldPushConnectorId', type: 'action' },
]);
});
it('preserves the existing references after extracting the external_service connector_id field', () => {
const userAction = {
...create_7_14_0_userAction({
action: 'push-to-service',
action_field: ['pushed'],
new_value: createExternalService(),
old_value: createExternalService({ connector_id: '5' }),
}),
references: [{ id: '500', name: 'someReference', type: 'ref' }],
};
const migratedUserAction = userActionsConnectorIdMigration(
userAction,
context
) as SavedObjectSanitizedDoc<CaseUserActionAttributes>;
const parsedNewExternalService = JSON.parse(migratedUserAction.attributes.new_value!);
const parsedOldExternalService = JSON.parse(migratedUserAction.attributes.old_value!);
expect(parsedNewExternalService).not.toHaveProperty('connector_id');
expect(parsedOldExternalService).not.toHaveProperty('connector_id');
expect(migratedUserAction.references).toEqual([
{ id: '500', name: 'someReference', type: 'ref' },
{ id: '100', name: 'pushConnectorId', type: 'action' },
{ id: '5', name: 'oldPushConnectorId', type: 'action' },
]);
});
it('leaves the object unmodified when it is not a valid push user action', () => {
const userAction = create_7_14_0_userAction({
action: 'push-to-service',
action_field: ['invalid field'],
new_value: 'hello',
old_value: null,
});
const migratedUserAction = userActionsConnectorIdMigration(
userAction,
context
) as SavedObjectSanitizedDoc<CaseUserActionAttributes>;
expect(migratedUserAction.attributes.old_value).toBeNull();
expect(migratedUserAction).toMatchInlineSnapshot(`
Object {
"attributes": Object {
"action": "push-to-service",
"action_field": Array [
"invalid field",
],
"new_value": "hello",
"old_value": null,
},
"id": "1",
"references": Array [],
"type": "cases-user-actions",
}
`);
});
it('leaves the object unmodified when it new value is invalid json', () => {
const userAction = create_7_14_0_userAction({
action: 'push-to-service',
action_field: ['pushed'],
new_value: '{a',
old_value: null,
});
const migratedUserAction = userActionsConnectorIdMigration(
userAction,
context
) as SavedObjectSanitizedDoc<CaseUserActionAttributes>;
expect(migratedUserAction.attributes.old_value).toBeNull();
expect(migratedUserAction.attributes.new_value).toEqual('{a');
expect(migratedUserAction).toMatchInlineSnapshot(`
Object {
"attributes": Object {
"action": "push-to-service",
"action_field": Array [
"pushed",
],
"new_value": "{a",
"old_value": null,
},
"id": "1",
"references": Array [],
"type": "cases-user-actions",
}
`);
});
it('logs an error new value is invalid json', () => {
const userAction = create_7_14_0_userAction({
action: 'push-to-service',
action_field: ['pushed'],
new_value: '{a',
old_value: null,
});
userActionsConnectorIdMigration(userAction, context);
expect(context.log.error).toHaveBeenCalled();
});
});
describe('update connector user action', () => {
it('extracts the connector id to references for a new create connector user action', () => {
const userAction = create_7_14_0_userAction({
action: 'update',
action_field: ['connector'],
new_value: createJiraConnector(),
old_value: null,
});
const migratedUserAction = userActionsConnectorIdMigration(
userAction,
context
) as SavedObjectSanitizedDoc<CaseUserActionAttributes>;
const parsedConnector = JSON.parse(migratedUserAction.attributes.new_value!);
expect(parsedConnector).not.toHaveProperty('id');
expect(parsedConnector).toMatchInlineSnapshot(`
Object {
"fields": Object {
"issueType": "bug",
"parent": "2",
"priority": "high",
},
"name": ".jira",
"type": ".jira",
}
`);
expect(migratedUserAction.references).toMatchInlineSnapshot(`
Array [
Object {
"id": "1",
"name": "connectorId",
"type": "action",
},
]
`);
expect(migratedUserAction.attributes.old_value).toBeNull();
});
it('extracts the connector id to references for a new and old create connector user action', () => {
const userAction = create_7_14_0_userAction({
action: 'update',
action_field: ['connector'],
new_value: createJiraConnector(),
old_value: { ...createJiraConnector(), id: '5' },
});
const migratedUserAction = userActionsConnectorIdMigration(
userAction,
context
) as SavedObjectSanitizedDoc<CaseUserActionAttributes>;
const parsedNewConnector = JSON.parse(migratedUserAction.attributes.new_value!);
const parsedOldConnector = JSON.parse(migratedUserAction.attributes.new_value!);
expect(parsedNewConnector).not.toHaveProperty('id');
expect(parsedOldConnector).not.toHaveProperty('id');
expect(migratedUserAction.references).toEqual([
{ id: '1', name: 'connectorId', type: 'action' },
{ id: '5', name: 'oldConnectorId', type: 'action' },
]);
});
it('preserves the existing references after extracting the connector.id field', () => {
const userAction = {
...create_7_14_0_userAction({
action: 'update',
action_field: ['connector'],
new_value: createJiraConnector(),
old_value: { ...createJiraConnector(), id: '5' },
}),
references: [{ id: '500', name: 'someReference', type: 'ref' }],
};
const migratedUserAction = userActionsConnectorIdMigration(
userAction,
context
) as SavedObjectSanitizedDoc<CaseUserActionAttributes>;
const parsedNewConnectorId = JSON.parse(migratedUserAction.attributes.new_value!);
const parsedOldConnectorId = JSON.parse(migratedUserAction.attributes.old_value!);
expect(parsedNewConnectorId).not.toHaveProperty('id');
expect(parsedOldConnectorId).not.toHaveProperty('id');
expect(migratedUserAction.references).toEqual([
{ id: '500', name: 'someReference', type: 'ref' },
{ id: '1', name: 'connectorId', type: 'action' },
{ id: '5', name: 'oldConnectorId', type: 'action' },
]);
});
it('leaves the object unmodified when it is not a valid create connector user action', () => {
const userAction = create_7_14_0_userAction({
action: 'update',
action_field: ['invalid action'],
new_value: 'new json value',
old_value: 'old value',
});
const migratedUserAction = userActionsConnectorIdMigration(
userAction,
context
) as SavedObjectSanitizedDoc<CaseUserActionAttributes>;
expect(migratedUserAction).toMatchInlineSnapshot(`
Object {
"attributes": Object {
"action": "update",
"action_field": Array [
"invalid action",
],
"new_value": "new json value",
"old_value": "old value",
},
"id": "1",
"references": Array [],
"type": "cases-user-actions",
}
`);
});
it('leaves the object unmodified when old_value is invalid json', () => {
const userAction = create_7_14_0_userAction({
action: 'update',
action_field: ['connector'],
new_value: '{}',
old_value: '{b',
});
const migratedUserAction = userActionsConnectorIdMigration(
userAction,
context
) as SavedObjectSanitizedDoc<CaseUserActionAttributes>;
expect(migratedUserAction).toMatchInlineSnapshot(`
Object {
"attributes": Object {
"action": "update",
"action_field": Array [
"connector",
],
"new_value": "{}",
"old_value": "{b",
},
"id": "1",
"references": Array [],
"type": "cases-user-actions",
}
`);
});
it('logs an error message when old_value is invalid json', () => {
const userAction = create_7_14_0_userAction({
action: 'update',
action_field: ['connector'],
new_value: createJiraConnector(),
old_value: '{b',
});
userActionsConnectorIdMigration(userAction, context);
expect(context.log.error).toHaveBeenCalled();
});
});
describe('create connector user action', () => {
it('extracts the connector id to references for a new create connector user action', () => {
const userAction = create_7_14_0_userAction({
action: 'create',
action_field: ['connector'],
new_value: createConnectorObject(),
old_value: null,
});
const migratedUserAction = userActionsConnectorIdMigration(
userAction,
context
) as SavedObjectSanitizedDoc<CaseUserActionAttributes>;
const parsedConnector = JSON.parse(migratedUserAction.attributes.new_value!);
expect(parsedConnector.connector).not.toHaveProperty('id');
expect(parsedConnector).toMatchInlineSnapshot(`
Object {
"connector": Object {
"fields": Object {
"issueType": "bug",
"parent": "2",
"priority": "high",
},
"name": ".jira",
"type": ".jira",
},
}
`);
expect(migratedUserAction.references).toMatchInlineSnapshot(`
Array [
Object {
"id": "1",
"name": "connectorId",
"type": "action",
},
]
`);
expect(migratedUserAction.attributes.old_value).toBeNull();
});
it('extracts the connector id to references for a new and old create connector user action', () => {
const userAction = create_7_14_0_userAction({
action: 'create',
action_field: ['connector'],
new_value: createConnectorObject(),
old_value: createConnectorObject({ id: '5' }),
});
const migratedUserAction = userActionsConnectorIdMigration(
userAction,
context
) as SavedObjectSanitizedDoc<CaseUserActionAttributes>;
const parsedNewConnector = JSON.parse(migratedUserAction.attributes.new_value!);
const parsedOldConnector = JSON.parse(migratedUserAction.attributes.new_value!);
expect(parsedNewConnector.connector).not.toHaveProperty('id');
expect(parsedOldConnector.connector).not.toHaveProperty('id');
expect(migratedUserAction.references).toEqual([
{ id: '1', name: 'connectorId', type: 'action' },
{ id: '5', name: 'oldConnectorId', type: 'action' },
]);
});
it('preserves the existing references after extracting the connector.id field', () => {
const userAction = {
...create_7_14_0_userAction({
action: 'create',
action_field: ['connector'],
new_value: createConnectorObject(),
old_value: createConnectorObject({ id: '5' }),
}),
references: [{ id: '500', name: 'someReference', type: 'ref' }],
};
const migratedUserAction = userActionsConnectorIdMigration(
userAction,
context
) as SavedObjectSanitizedDoc<CaseUserActionAttributes>;
const parsedNewConnectorId = JSON.parse(migratedUserAction.attributes.new_value!);
const parsedOldConnectorId = JSON.parse(migratedUserAction.attributes.old_value!);
expect(parsedNewConnectorId.connector).not.toHaveProperty('id');
expect(parsedOldConnectorId.connector).not.toHaveProperty('id');
expect(migratedUserAction.references).toEqual([
{ id: '500', name: 'someReference', type: 'ref' },
{ id: '1', name: 'connectorId', type: 'action' },
{ id: '5', name: 'oldConnectorId', type: 'action' },
]);
});
it('leaves the object unmodified when it is not a valid create connector user action', () => {
const userAction = create_7_14_0_userAction({
action: 'create',
action_field: ['invalid action'],
new_value: 'new json value',
old_value: 'old value',
});
const migratedUserAction = userActionsConnectorIdMigration(
userAction,
context
) as SavedObjectSanitizedDoc<CaseUserActionAttributes>;
expect(migratedUserAction).toMatchInlineSnapshot(`
Object {
"attributes": Object {
"action": "create",
"action_field": Array [
"invalid action",
],
"new_value": "new json value",
"old_value": "old value",
},
"id": "1",
"references": Array [],
"type": "cases-user-actions",
}
`);
});
it('leaves the object unmodified when new_value is invalid json', () => {
const userAction = create_7_14_0_userAction({
action: 'create',
action_field: ['connector'],
new_value: 'new json value',
old_value: 'old value',
});
const migratedUserAction = userActionsConnectorIdMigration(
userAction,
context
) as SavedObjectSanitizedDoc<CaseUserActionAttributes>;
expect(migratedUserAction).toMatchInlineSnapshot(`
Object {
"attributes": Object {
"action": "create",
"action_field": Array [
"connector",
],
"new_value": "new json value",
"old_value": "old value",
},
"id": "1",
"references": Array [],
"type": "cases-user-actions",
}
`);
});
it('logs an error message when new_value is invalid json', () => {
const userAction = create_7_14_0_userAction({
action: 'create',
action_field: ['connector'],
new_value: 'new json value',
old_value: 'old value',
});
userActionsConnectorIdMigration(userAction, context);
expect(context.log.error).toHaveBeenCalled();
});
});
});
});
});

View file

@ -1,159 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
/* eslint-disable @typescript-eslint/naming-convention */
import { addOwnerToSO, SanitizedCaseOwner } from '.';
import {
SavedObjectUnsanitizedDoc,
SavedObjectSanitizedDoc,
SavedObjectMigrationContext,
LogMeta,
} from '../../../../../../src/core/server';
import { ConnectorTypes, isCreateConnector, isPush, isUpdateConnector } from '../../../common';
import { extractConnectorIdFromJson } from '../../services/user_actions/transform';
import { UserActionFieldType } from '../../services/user_actions/types';
interface UserActions {
action_field: string[];
new_value: string;
old_value: string;
}
interface UserActionUnmigratedConnectorDocument {
action?: string;
action_field?: string[];
new_value?: string | null;
old_value?: string | null;
}
interface UserActionLogMeta extends LogMeta {
migrations: { userAction: { id: string } };
}
export function userActionsConnectorIdMigration(
doc: SavedObjectUnsanitizedDoc<UserActionUnmigratedConnectorDocument>,
context: SavedObjectMigrationContext
): SavedObjectSanitizedDoc<unknown> {
const originalDocWithReferences = { ...doc, references: doc.references ?? [] };
if (!isConnectorUserAction(doc.attributes.action, doc.attributes.action_field)) {
return originalDocWithReferences;
}
try {
return formatDocumentWithConnectorReferences(doc);
} catch (error) {
logError(doc.id, context, error);
return originalDocWithReferences;
}
}
function isConnectorUserAction(action?: string, actionFields?: string[]): boolean {
return (
isCreateConnector(action, actionFields) ||
isUpdateConnector(action, actionFields) ||
isPush(action, actionFields)
);
}
function formatDocumentWithConnectorReferences(
doc: SavedObjectUnsanitizedDoc<UserActionUnmigratedConnectorDocument>
): SavedObjectSanitizedDoc<unknown> {
const { new_value, old_value, action, action_field, ...restAttributes } = doc.attributes;
const { references = [] } = doc;
const { transformedActionDetails: transformedNewValue, references: newValueConnectorRefs } =
extractConnectorIdFromJson({
action,
actionFields: action_field,
actionDetails: new_value,
fieldType: UserActionFieldType.New,
});
const { transformedActionDetails: transformedOldValue, references: oldValueConnectorRefs } =
extractConnectorIdFromJson({
action,
actionFields: action_field,
actionDetails: old_value,
fieldType: UserActionFieldType.Old,
});
return {
...doc,
attributes: {
...restAttributes,
action,
action_field,
new_value: transformedNewValue,
old_value: transformedOldValue,
},
references: [...references, ...newValueConnectorRefs, ...oldValueConnectorRefs],
};
}
function logError(id: string, context: SavedObjectMigrationContext, error: Error) {
context.log.error<UserActionLogMeta>(
`Failed to migrate user action connector doc id: ${id} version: ${context.migrationVersion} error: ${error.message}`,
{
migrations: {
userAction: {
id,
},
},
}
);
}
export const userActionsMigrations = {
'7.10.0': (doc: SavedObjectUnsanitizedDoc<UserActions>): SavedObjectSanitizedDoc<UserActions> => {
const { action_field, new_value, old_value, ...restAttributes } = doc.attributes;
if (
action_field == null ||
!Array.isArray(action_field) ||
action_field[0] !== 'connector_id'
) {
return { ...doc, references: doc.references || [] };
}
return {
...doc,
attributes: {
...restAttributes,
action_field: ['connector'],
new_value:
new_value != null
? JSON.stringify({
id: new_value,
name: 'none',
type: ConnectorTypes.none,
fields: null,
})
: new_value,
old_value:
old_value != null
? JSON.stringify({
id: old_value,
name: 'none',
type: ConnectorTypes.none,
fields: null,
})
: old_value,
},
references: doc.references || [],
};
},
'7.14.0': (
doc: SavedObjectUnsanitizedDoc<Record<string, unknown>>
): SavedObjectSanitizedDoc<SanitizedCaseOwner> => {
return addOwnerToSO(doc);
},
'7.16.0': userActionsConnectorIdMigration,
};

View file

@ -0,0 +1,229 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { noneConnectorId } from '../../../common';
import { createExternalService, createJiraConnector } from '../../services/test_utils';
import { transformConnectorIdToReference, transformPushConnectorIdToReference } from './utils';
describe('migration utils', () => {
describe('transformConnectorIdToReference', () => {
it('returns the default none connector when the connector is undefined', () => {
expect(transformConnectorIdToReference().transformedConnector).toMatchInlineSnapshot(`
Object {
"connector": Object {
"fields": null,
"name": "none",
"type": ".none",
},
}
`);
});
it('returns the default none connector when the id is undefined', () => {
expect(transformConnectorIdToReference({ id: undefined }).transformedConnector)
.toMatchInlineSnapshot(`
Object {
"connector": Object {
"fields": null,
"name": "none",
"type": ".none",
},
}
`);
});
it('returns the default none connector when the id is none', () => {
expect(transformConnectorIdToReference({ id: noneConnectorId }).transformedConnector)
.toMatchInlineSnapshot(`
Object {
"connector": Object {
"fields": null,
"name": "none",
"type": ".none",
},
}
`);
});
it('returns the default none connector when the id is none and other fields are defined', () => {
expect(
transformConnectorIdToReference({ ...createJiraConnector(), id: noneConnectorId })
.transformedConnector
).toMatchInlineSnapshot(`
Object {
"connector": Object {
"fields": null,
"name": "none",
"type": ".none",
},
}
`);
});
it('returns an empty array of references when the connector is undefined', () => {
expect(transformConnectorIdToReference().references.length).toBe(0);
});
it('returns an empty array of references when the id is undefined', () => {
expect(transformConnectorIdToReference({ id: undefined }).references.length).toBe(0);
});
it('returns an empty array of references when the id is the none connector', () => {
expect(transformConnectorIdToReference({ id: noneConnectorId }).references.length).toBe(0);
});
it('returns an empty array of references when the id is the none connector and other fields are defined', () => {
expect(
transformConnectorIdToReference({ ...createJiraConnector(), id: noneConnectorId })
.references.length
).toBe(0);
});
it('returns a jira connector', () => {
const transformedFields = transformConnectorIdToReference(createJiraConnector());
expect(transformedFields.transformedConnector).toMatchInlineSnapshot(`
Object {
"connector": Object {
"fields": Object {
"issueType": "bug",
"parent": "2",
"priority": "high",
},
"name": ".jira",
"type": ".jira",
},
}
`);
expect(transformedFields.references).toMatchInlineSnapshot(`
Array [
Object {
"id": "1",
"name": "connectorId",
"type": "action",
},
]
`);
});
});
describe('transformPushConnectorIdToReference', () => {
it('sets external_service to null when it is undefined', () => {
expect(transformPushConnectorIdToReference().transformedPushConnector).toMatchInlineSnapshot(`
Object {
"external_service": null,
}
`);
});
it('sets external_service to null when it is null', () => {
expect(transformPushConnectorIdToReference(null).transformedPushConnector)
.toMatchInlineSnapshot(`
Object {
"external_service": null,
}
`);
});
it('returns an object when external_service is defined but connector_id is undefined', () => {
expect(
transformPushConnectorIdToReference({ connector_id: undefined }).transformedPushConnector
).toMatchInlineSnapshot(`
Object {
"external_service": Object {},
}
`);
});
it('returns an object when external_service is defined but connector_id is null', () => {
expect(transformPushConnectorIdToReference({ connector_id: null }).transformedPushConnector)
.toMatchInlineSnapshot(`
Object {
"external_service": Object {},
}
`);
});
it('returns an object when external_service is defined but connector_id is none', () => {
const otherFields = { otherField: 'hi' };
expect(
transformPushConnectorIdToReference({ ...otherFields, connector_id: noneConnectorId })
.transformedPushConnector
).toMatchInlineSnapshot(`
Object {
"external_service": Object {
"otherField": "hi",
},
}
`);
});
it('returns an empty array of references when the external_service is undefined', () => {
expect(transformPushConnectorIdToReference().references.length).toBe(0);
});
it('returns an empty array of references when the external_service is null', () => {
expect(transformPushConnectorIdToReference(null).references.length).toBe(0);
});
it('returns an empty array of references when the connector_id is undefined', () => {
expect(
transformPushConnectorIdToReference({ connector_id: undefined }).references.length
).toBe(0);
});
it('returns an empty array of references when the connector_id is null', () => {
expect(
transformPushConnectorIdToReference({ connector_id: undefined }).references.length
).toBe(0);
});
it('returns an empty array of references when the connector_id is the none connector', () => {
expect(
transformPushConnectorIdToReference({ connector_id: noneConnectorId }).references.length
).toBe(0);
});
it('returns an empty array of references when the connector_id is the none connector and other fields are defined', () => {
expect(
transformPushConnectorIdToReference({
...createExternalService(),
connector_id: noneConnectorId,
}).references.length
).toBe(0);
});
it('returns the external_service connector', () => {
const transformedFields = transformPushConnectorIdToReference(createExternalService());
expect(transformedFields.transformedPushConnector).toMatchInlineSnapshot(`
Object {
"external_service": Object {
"connector_name": ".jira",
"external_id": "100",
"external_title": "awesome",
"external_url": "http://www.google.com",
"pushed_at": "2019-11-25T21:54:48.952Z",
"pushed_by": Object {
"email": "testemail@elastic.co",
"full_name": "elastic",
"username": "elastic",
},
},
}
`);
expect(transformedFields.references).toMatchInlineSnapshot(`
Array [
Object {
"id": "100",
"name": "pushConnectorId",
"type": "action",
},
]
`);
});
});
});

View file

@ -0,0 +1,73 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
/* eslint-disable @typescript-eslint/naming-convention */
import { noneConnectorId } from '../../../common';
import { SavedObjectReference } from '../../../../../../src/core/server';
import { ACTION_SAVED_OBJECT_TYPE } from '../../../../actions/server';
import {
getNoneCaseConnector,
CONNECTOR_ID_REFERENCE_NAME,
PUSH_CONNECTOR_ID_REFERENCE_NAME,
} from '../../common';
export const transformConnectorIdToReference = (connector?: {
id?: string;
}): { transformedConnector: Record<string, unknown>; references: SavedObjectReference[] } => {
const { id: connectorId, ...restConnector } = connector ?? {};
const references = createConnectorReference(
connectorId,
ACTION_SAVED_OBJECT_TYPE,
CONNECTOR_ID_REFERENCE_NAME
);
const { id: ignoreNoneId, ...restNoneConnector } = getNoneCaseConnector();
const connectorFieldsToReturn =
connector && references.length > 0 ? restConnector : restNoneConnector;
return {
transformedConnector: {
connector: connectorFieldsToReturn,
},
references,
};
};
const createConnectorReference = (
id: string | null | undefined,
type: string,
name: string
): SavedObjectReference[] => {
return id && id !== noneConnectorId
? [
{
id,
type,
name,
},
]
: [];
};
export const transformPushConnectorIdToReference = (
external_service?: { connector_id?: string | null } | null
): { transformedPushConnector: Record<string, unknown>; references: SavedObjectReference[] } => {
const { connector_id: pushConnectorId, ...restExternalService } = external_service ?? {};
const references = createConnectorReference(
pushConnectorId,
ACTION_SAVED_OBJECT_TYPE,
PUSH_CONNECTOR_ID_REFERENCE_NAME
);
return {
transformedPushConnector: { external_service: external_service ? restExternalService : null },
references,
};
};

View file

@ -40,7 +40,6 @@ import {
createSavedObjectReferences,
createCaseSavedObjectResponse,
basicCaseFields,
createSOFindResponse,
} from '../test_utils';
import { ESCaseAttributes } from './types';
@ -88,6 +87,13 @@ const createFindSO = (
score: 0,
});
const createSOFindResponse = (savedObjects: Array<SavedObjectsFindResult<ESCaseAttributes>>) => ({
saved_objects: savedObjects,
total: savedObjects.length,
per_page: savedObjects.length,
page: 1,
});
const createCaseUpdateParams = (
connector?: CaseConnector,
externalService?: CaseFullExternalService

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import { SavedObject, SavedObjectReference, SavedObjectsFindResult } from 'kibana/server';
import { SavedObject, SavedObjectReference } from 'kibana/server';
import { ESConnectorFields } from '.';
import { CONNECTOR_ID_REFERENCE_NAME, PUSH_CONNECTOR_ID_REFERENCE_NAME } from '../common';
import {
@ -54,7 +54,7 @@ export const createESJiraConnector = (
{ key: 'parent', value: '2' },
],
type: ConnectorTypes.jira,
...overrides,
...(overrides && { ...overrides }),
};
};
@ -94,7 +94,7 @@ export const createExternalService = (
email: 'testemail@elastic.co',
username: 'elastic',
},
...overrides,
...(overrides && { ...overrides }),
});
export const basicCaseFields = {
@ -198,14 +198,3 @@ export const createSavedObjectReferences = ({
]
: []),
];
export const createConnectorObject = (overrides?: Partial<CaseConnector>) => ({
connector: { ...createJiraConnector(), ...overrides },
});
export const createSOFindResponse = <T>(savedObjects: Array<SavedObjectsFindResult<T>>) => ({
saved_objects: savedObjects,
total: savedObjects.length,
per_page: savedObjects.length,
page: 1,
});

View file

@ -1,332 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { UserActionField } from '../../../common';
import { createConnectorObject, createExternalService, createJiraConnector } from '../test_utils';
import { buildCaseUserActionItem } from './helpers';
const defaultFields = () => ({
actionAt: 'now',
actionBy: {
email: 'a',
full_name: 'j',
username: '1',
},
caseId: '300',
owner: 'securitySolution',
});
describe('user action helpers', () => {
describe('buildCaseUserActionItem', () => {
describe('push user action', () => {
it('extracts the external_service connector_id to references for a new pushed user action', () => {
const userAction = buildCaseUserActionItem({
...defaultFields(),
action: 'push-to-service',
fields: ['pushed'],
newValue: createExternalService(),
});
const parsedExternalService = JSON.parse(userAction.attributes.new_value!);
expect(parsedExternalService).not.toHaveProperty('connector_id');
expect(parsedExternalService).toMatchInlineSnapshot(`
Object {
"connector_name": ".jira",
"external_id": "100",
"external_title": "awesome",
"external_url": "http://www.google.com",
"pushed_at": "2019-11-25T21:54:48.952Z",
"pushed_by": Object {
"email": "testemail@elastic.co",
"full_name": "elastic",
"username": "elastic",
},
}
`);
expect(userAction.references).toMatchInlineSnapshot(`
Array [
Object {
"id": "300",
"name": "associated-cases",
"type": "cases",
},
Object {
"id": "100",
"name": "pushConnectorId",
"type": "action",
},
]
`);
expect(userAction.attributes.old_value).toBeNull();
});
it('extract the external_service connector_id to references for new and old pushed user action', () => {
const userAction = buildCaseUserActionItem({
...defaultFields(),
action: 'push-to-service',
fields: ['pushed'],
newValue: createExternalService(),
oldValue: createExternalService({ connector_id: '5' }),
});
const parsedNewExternalService = JSON.parse(userAction.attributes.new_value!);
const parsedOldExternalService = JSON.parse(userAction.attributes.old_value!);
expect(parsedNewExternalService).not.toHaveProperty('connector_id');
expect(parsedOldExternalService).not.toHaveProperty('connector_id');
expect(userAction.references).toEqual([
{ id: '300', name: 'associated-cases', type: 'cases' },
{ id: '100', name: 'pushConnectorId', type: 'action' },
{ id: '5', name: 'oldPushConnectorId', type: 'action' },
]);
});
it('leaves the object unmodified when it is not a valid push user action', () => {
const userAction = buildCaseUserActionItem({
...defaultFields(),
action: 'push-to-service',
fields: ['invalid field'] as unknown as UserActionField,
newValue: 'hello' as unknown as Record<string, unknown>,
});
expect(userAction.attributes.old_value).toBeNull();
expect(userAction).toMatchInlineSnapshot(`
Object {
"attributes": Object {
"action": "push-to-service",
"action_at": "now",
"action_by": Object {
"email": "a",
"full_name": "j",
"username": "1",
},
"action_field": Array [
"invalid field",
],
"new_value": "hello",
"old_value": null,
"owner": "securitySolution",
},
"references": Array [
Object {
"id": "300",
"name": "associated-cases",
"type": "cases",
},
],
}
`);
});
});
describe('update connector user action', () => {
it('extracts the connector id to references for a new create connector user action', () => {
const userAction = buildCaseUserActionItem({
...defaultFields(),
action: 'update',
fields: ['connector'],
newValue: createJiraConnector(),
});
const parsedConnector = JSON.parse(userAction.attributes.new_value!);
expect(parsedConnector).not.toHaveProperty('id');
expect(parsedConnector).toMatchInlineSnapshot(`
Object {
"fields": Object {
"issueType": "bug",
"parent": "2",
"priority": "high",
},
"name": ".jira",
"type": ".jira",
}
`);
expect(userAction.references).toMatchInlineSnapshot(`
Array [
Object {
"id": "300",
"name": "associated-cases",
"type": "cases",
},
Object {
"id": "1",
"name": "connectorId",
"type": "action",
},
]
`);
expect(userAction.attributes.old_value).toBeNull();
});
it('extracts the connector id to references for a new and old create connector user action', () => {
const userAction = buildCaseUserActionItem({
...defaultFields(),
action: 'update',
fields: ['connector'],
newValue: createJiraConnector(),
oldValue: { ...createJiraConnector(), id: '5' },
});
const parsedNewConnector = JSON.parse(userAction.attributes.new_value!);
const parsedOldConnector = JSON.parse(userAction.attributes.new_value!);
expect(parsedNewConnector).not.toHaveProperty('id');
expect(parsedOldConnector).not.toHaveProperty('id');
expect(userAction.references).toEqual([
{ id: '300', name: 'associated-cases', type: 'cases' },
{ id: '1', name: 'connectorId', type: 'action' },
{ id: '5', name: 'oldConnectorId', type: 'action' },
]);
});
it('leaves the object unmodified when it is not a valid create connector user action', () => {
const userAction = buildCaseUserActionItem({
...defaultFields(),
action: 'update',
fields: ['invalid field'] as unknown as UserActionField,
newValue: 'hello' as unknown as Record<string, unknown>,
oldValue: 'old value' as unknown as Record<string, unknown>,
});
expect(userAction).toMatchInlineSnapshot(`
Object {
"attributes": Object {
"action": "update",
"action_at": "now",
"action_by": Object {
"email": "a",
"full_name": "j",
"username": "1",
},
"action_field": Array [
"invalid field",
],
"new_value": "hello",
"old_value": "old value",
"owner": "securitySolution",
},
"references": Array [
Object {
"id": "300",
"name": "associated-cases",
"type": "cases",
},
],
}
`);
});
});
describe('create connector user action', () => {
it('extracts the connector id to references for a new create connector user action', () => {
const userAction = buildCaseUserActionItem({
...defaultFields(),
action: 'create',
fields: ['connector'],
newValue: createConnectorObject(),
});
const parsedConnector = JSON.parse(userAction.attributes.new_value!);
expect(parsedConnector.connector).not.toHaveProperty('id');
expect(parsedConnector).toMatchInlineSnapshot(`
Object {
"connector": Object {
"fields": Object {
"issueType": "bug",
"parent": "2",
"priority": "high",
},
"name": ".jira",
"type": ".jira",
},
}
`);
expect(userAction.references).toMatchInlineSnapshot(`
Array [
Object {
"id": "300",
"name": "associated-cases",
"type": "cases",
},
Object {
"id": "1",
"name": "connectorId",
"type": "action",
},
]
`);
expect(userAction.attributes.old_value).toBeNull();
});
it('extracts the connector id to references for a new and old create connector user action', () => {
const userAction = buildCaseUserActionItem({
...defaultFields(),
action: 'create',
fields: ['connector'],
newValue: createConnectorObject(),
oldValue: createConnectorObject({ id: '5' }),
});
const parsedNewConnector = JSON.parse(userAction.attributes.new_value!);
const parsedOldConnector = JSON.parse(userAction.attributes.new_value!);
expect(parsedNewConnector.connector).not.toHaveProperty('id');
expect(parsedOldConnector.connector).not.toHaveProperty('id');
expect(userAction.references).toEqual([
{ id: '300', name: 'associated-cases', type: 'cases' },
{ id: '1', name: 'connectorId', type: 'action' },
{ id: '5', name: 'oldConnectorId', type: 'action' },
]);
});
it('leaves the object unmodified when it is not a valid create connector user action', () => {
const userAction = buildCaseUserActionItem({
...defaultFields(),
action: 'create',
fields: ['invalid action'] as unknown as UserActionField,
newValue: 'new json value' as unknown as Record<string, unknown>,
oldValue: 'old value' as unknown as Record<string, unknown>,
});
expect(userAction).toMatchInlineSnapshot(`
Object {
"attributes": Object {
"action": "create",
"action_at": "now",
"action_by": Object {
"email": "a",
"full_name": "j",
"username": "1",
},
"action_field": Array [
"invalid action",
],
"new_value": "new json value",
"old_value": "old value",
"owner": "securitySolution",
},
"references": Array [
Object {
"id": "300",
"name": "associated-cases",
"type": "cases",
},
],
}
`);
});
});
});
});

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import { SavedObject, SavedObjectReference, SavedObjectsUpdateResponse } from 'kibana/server';
import { SavedObject, SavedObjectsUpdateResponse } from 'kibana/server';
import { get, isPlainObject, isString } from 'lodash';
import deepEqual from 'fast-deep-equal';
@ -23,68 +23,8 @@ import {
} from '../../../common';
import { isTwoArraysDifference } from '../../client/utils';
import { UserActionItem } from '.';
import { extractConnectorId } from './transform';
import { UserActionFieldType } from './types';
import { CASE_REF_NAME, COMMENT_REF_NAME, SUB_CASE_REF_NAME } from '../../common';
interface BuildCaseUserActionParams {
action: UserAction;
actionAt: string;
actionBy: User;
caseId: string;
owner: string;
fields: UserActionField;
newValue?: Record<string, unknown> | string | null;
oldValue?: Record<string, unknown> | string | null;
subCaseId?: string;
}
export const buildCaseUserActionItem = ({
action,
actionAt,
actionBy,
caseId,
fields,
newValue,
oldValue,
subCaseId,
owner,
}: BuildCaseUserActionParams): UserActionItem => {
const { transformedActionDetails: transformedNewValue, references: newValueReferences } =
extractConnectorId({
action,
actionFields: fields,
actionDetails: newValue,
fieldType: UserActionFieldType.New,
});
const { transformedActionDetails: transformedOldValue, references: oldValueReferences } =
extractConnectorId({
action,
actionFields: fields,
actionDetails: oldValue,
fieldType: UserActionFieldType.Old,
});
return {
attributes: transformNewUserAction({
actionField: fields,
action,
actionAt,
owner,
...actionBy,
newValue: transformedNewValue,
oldValue: transformedOldValue,
}),
references: [
...createCaseReferences(caseId, subCaseId),
...newValueReferences,
...oldValueReferences,
],
};
};
const transformNewUserAction = ({
export const transformNewUserAction = ({
actionField,
action,
actionAt,
@ -115,43 +55,103 @@ const transformNewUserAction = ({
owner,
});
const createCaseReferences = (caseId: string, subCaseId?: string): SavedObjectReference[] => [
{
type: CASE_SAVED_OBJECT,
name: CASE_REF_NAME,
id: caseId,
},
...(subCaseId
? [
{
type: SUB_CASE_SAVED_OBJECT,
name: SUB_CASE_REF_NAME,
id: subCaseId,
},
]
: []),
];
interface BuildCaseUserAction {
action: UserAction;
actionAt: string;
actionBy: User;
caseId: string;
owner: string;
fields: UserActionField | unknown[];
newValue?: string | unknown;
oldValue?: string | unknown;
subCaseId?: string;
}
interface BuildCommentUserActionItem extends BuildCaseUserActionParams {
interface BuildCommentUserActionItem extends BuildCaseUserAction {
commentId: string;
}
export const buildCommentUserActionItem = (params: BuildCommentUserActionItem): UserActionItem => {
const { commentId } = params;
const { attributes, references } = buildCaseUserActionItem(params);
export const buildCommentUserActionItem = ({
action,
actionAt,
actionBy,
caseId,
commentId,
fields,
newValue,
oldValue,
subCaseId,
owner,
}: BuildCommentUserActionItem): UserActionItem => ({
attributes: transformNewUserAction({
actionField: fields as UserActionField,
action,
actionAt,
owner,
...actionBy,
newValue: newValue as string,
oldValue: oldValue as string,
}),
references: [
{
type: CASE_SAVED_OBJECT,
name: `associated-${CASE_SAVED_OBJECT}`,
id: caseId,
},
{
type: CASE_COMMENT_SAVED_OBJECT,
name: `associated-${CASE_COMMENT_SAVED_OBJECT}`,
id: commentId,
},
...(subCaseId
? [
{
type: SUB_CASE_SAVED_OBJECT,
id: subCaseId,
name: `associated-${SUB_CASE_SAVED_OBJECT}`,
},
]
: []),
],
});
return {
attributes,
references: [
...references,
{
type: CASE_COMMENT_SAVED_OBJECT,
name: COMMENT_REF_NAME,
id: commentId,
},
],
};
};
export const buildCaseUserActionItem = ({
action,
actionAt,
actionBy,
caseId,
fields,
newValue,
oldValue,
subCaseId,
owner,
}: BuildCaseUserAction): UserActionItem => ({
attributes: transformNewUserAction({
actionField: fields as UserActionField,
action,
actionAt,
owner,
...actionBy,
newValue: newValue as string,
oldValue: oldValue as string,
}),
references: [
{
type: CASE_SAVED_OBJECT,
name: `associated-${CASE_SAVED_OBJECT}`,
id: caseId,
},
...(subCaseId
? [
{
type: SUB_CASE_SAVED_OBJECT,
name: `associated-${SUB_CASE_SAVED_OBJECT}`,
id: subCaseId,
},
]
: []),
],
});
const userActionFieldsAllowed: UserActionField = [
'comment',
@ -278,8 +278,8 @@ const buildGenericCaseUserActions = <T extends OwnerEntity>({
caseId,
subCaseId,
fields: [field],
newValue: updatedValue,
oldValue: origValue,
newValue: JSON.stringify(updatedValue),
oldValue: JSON.stringify(origValue),
owner: originalItem.attributes.owner,
}),
];

View file

@ -1,557 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { SavedObject, SavedObjectsFindResult } from 'kibana/server';
import { transformFindResponseToExternalModel, UserActionItem } from '.';
import {
CaseUserActionAttributes,
CASE_USER_ACTION_SAVED_OBJECT,
UserAction,
UserActionField,
} from '../../../common';
import {
createConnectorObject,
createExternalService,
createJiraConnector,
createSOFindResponse,
} from '../test_utils';
import { buildCaseUserActionItem, buildCommentUserActionItem } from './helpers';
const createConnectorUserAction = (
subCaseId?: string,
overrides?: Partial<CaseUserActionAttributes>
): SavedObject<CaseUserActionAttributes> => {
return {
...createUserActionSO({
action: 'create',
fields: ['connector'],
newValue: createConnectorObject(),
subCaseId,
}),
...(overrides && { ...overrides }),
};
};
const updateConnectorUserAction = ({
subCaseId,
overrides,
oldValue,
}: {
subCaseId?: string;
overrides?: Partial<CaseUserActionAttributes>;
oldValue?: string | null | Record<string, unknown>;
} = {}): SavedObject<CaseUserActionAttributes> => {
return {
...createUserActionSO({
action: 'update',
fields: ['connector'],
newValue: createJiraConnector(),
oldValue,
subCaseId,
}),
...(overrides && { ...overrides }),
};
};
const pushConnectorUserAction = ({
subCaseId,
overrides,
oldValue,
}: {
subCaseId?: string;
overrides?: Partial<CaseUserActionAttributes>;
oldValue?: string | null | Record<string, unknown>;
} = {}): SavedObject<CaseUserActionAttributes> => {
return {
...createUserActionSO({
action: 'push-to-service',
fields: ['pushed'],
newValue: createExternalService(),
oldValue,
subCaseId,
}),
...(overrides && { ...overrides }),
};
};
const createUserActionFindSO = (
userAction: SavedObject<CaseUserActionAttributes>
): SavedObjectsFindResult<CaseUserActionAttributes> => ({
...userAction,
score: 0,
});
const createUserActionSO = ({
action,
fields,
subCaseId,
newValue,
oldValue,
attributesOverrides,
commentId,
}: {
action: UserAction;
fields: UserActionField;
subCaseId?: string;
newValue?: string | null | Record<string, unknown>;
oldValue?: string | null | Record<string, unknown>;
attributesOverrides?: Partial<CaseUserActionAttributes>;
commentId?: string;
}): SavedObject<CaseUserActionAttributes> => {
const defaultParams = {
action,
actionAt: 'abc',
actionBy: {
email: 'a',
username: 'b',
full_name: 'abc',
},
caseId: '1',
subCaseId,
fields,
newValue,
oldValue,
owner: 'securitySolution',
};
let userAction: UserActionItem;
if (commentId) {
userAction = buildCommentUserActionItem({
commentId,
...defaultParams,
});
} else {
userAction = buildCaseUserActionItem(defaultParams);
}
return {
type: CASE_USER_ACTION_SAVED_OBJECT,
id: '100',
attributes: {
...userAction.attributes,
...(attributesOverrides && { ...attributesOverrides }),
},
references: userAction.references,
};
};
describe('CaseUserActionService', () => {
describe('transformFindResponseToExternalModel', () => {
it('does not populate the ids when the response is an empty array', () => {
expect(transformFindResponseToExternalModel(createSOFindResponse([]))).toMatchInlineSnapshot(`
Object {
"page": 1,
"per_page": 0,
"saved_objects": Array [],
"total": 0,
}
`);
});
it('preserves the saved object fields and attributes when inject the ids', () => {
const transformed = transformFindResponseToExternalModel(
createSOFindResponse([createUserActionFindSO(createConnectorUserAction())])
);
expect(transformed).toMatchInlineSnapshot(`
Object {
"page": 1,
"per_page": 1,
"saved_objects": Array [
Object {
"attributes": Object {
"action": "create",
"action_at": "abc",
"action_by": Object {
"email": "a",
"full_name": "abc",
"username": "b",
},
"action_field": Array [
"connector",
],
"action_id": "100",
"case_id": "1",
"comment_id": null,
"new_val_connector_id": "1",
"new_value": "{\\"connector\\":{\\"name\\":\\".jira\\",\\"type\\":\\".jira\\",\\"fields\\":{\\"issueType\\":\\"bug\\",\\"priority\\":\\"high\\",\\"parent\\":\\"2\\"}}}",
"old_val_connector_id": null,
"old_value": null,
"owner": "securitySolution",
"sub_case_id": "",
},
"id": "100",
"references": Array [
Object {
"id": "1",
"name": "associated-cases",
"type": "cases",
},
Object {
"id": "1",
"name": "connectorId",
"type": "action",
},
],
"score": 0,
"type": "cases-user-actions",
},
],
"total": 1,
}
`);
});
it('populates the new_val_connector_id for multiple user actions', () => {
const transformed = transformFindResponseToExternalModel(
createSOFindResponse([
createUserActionFindSO(createConnectorUserAction()),
createUserActionFindSO(createConnectorUserAction()),
])
);
expect(transformed.saved_objects[0].attributes.new_val_connector_id).toEqual('1');
expect(transformed.saved_objects[1].attributes.new_val_connector_id).toEqual('1');
});
it('populates the old_val_connector_id for multiple user actions', () => {
const transformed = transformFindResponseToExternalModel(
createSOFindResponse([
createUserActionFindSO(
createUserActionSO({
action: 'create',
fields: ['connector'],
oldValue: createConnectorObject(),
})
),
createUserActionFindSO(
createUserActionSO({
action: 'create',
fields: ['connector'],
oldValue: createConnectorObject({ id: '10' }),
})
),
])
);
expect(transformed.saved_objects[0].attributes.old_val_connector_id).toEqual('1');
expect(transformed.saved_objects[1].attributes.old_val_connector_id).toEqual('10');
});
describe('reference ids', () => {
it('sets case_id to an empty string when it cannot find the reference', () => {
const userAction = {
...createConnectorUserAction(),
references: [],
};
const transformed = transformFindResponseToExternalModel(
createSOFindResponse([createUserActionFindSO(userAction)])
);
expect(transformed.saved_objects[0].attributes.case_id).toEqual('');
});
it('sets comment_id to null when it cannot find the reference', () => {
const userAction = {
...createUserActionSO({ action: 'create', fields: ['connector'], commentId: '5' }),
references: [],
};
const transformed = transformFindResponseToExternalModel(
createSOFindResponse([createUserActionFindSO(userAction)])
);
expect(transformed.saved_objects[0].attributes.comment_id).toBeNull();
});
it('sets sub_case_id to an empty string when it cannot find the reference', () => {
const userAction = {
...createUserActionSO({ action: 'create', fields: ['connector'], subCaseId: '5' }),
references: [],
};
const transformed = transformFindResponseToExternalModel(
createSOFindResponse([createUserActionFindSO(userAction)])
);
expect(transformed.saved_objects[0].attributes.comment_id).toBeNull();
});
it('sets case_id correctly when it finds the reference', () => {
const userAction = createConnectorUserAction();
const transformed = transformFindResponseToExternalModel(
createSOFindResponse([createUserActionFindSO(userAction)])
);
expect(transformed.saved_objects[0].attributes.case_id).toEqual('1');
});
it('sets comment_id correctly when it finds the reference', () => {
const userAction = createUserActionSO({
action: 'create',
fields: ['connector'],
commentId: '5',
});
const transformed = transformFindResponseToExternalModel(
createSOFindResponse([createUserActionFindSO(userAction)])
);
expect(transformed.saved_objects[0].attributes.comment_id).toEqual('5');
});
it('sets sub_case_id correctly when it finds the reference', () => {
const userAction = {
...createUserActionSO({ action: 'create', fields: ['connector'], subCaseId: '5' }),
};
const transformed = transformFindResponseToExternalModel(
createSOFindResponse([createUserActionFindSO(userAction)])
);
expect(transformed.saved_objects[0].attributes.sub_case_id).toEqual('5');
});
it('sets action_id correctly to the saved object id', () => {
const userAction = {
...createUserActionSO({ action: 'create', fields: ['connector'], subCaseId: '5' }),
};
const transformed = transformFindResponseToExternalModel(
createSOFindResponse([createUserActionFindSO(userAction)])
);
expect(transformed.saved_objects[0].attributes.action_id).toEqual('100');
});
});
describe('create connector', () => {
it('does not populate the new_val_connector_id when it cannot find the reference', () => {
const userAction = { ...createConnectorUserAction(), references: [] };
const transformed = transformFindResponseToExternalModel(
createSOFindResponse([createUserActionFindSO(userAction)])
);
expect(transformed.saved_objects[0].attributes.new_val_connector_id).toBeNull();
});
it('does not populate the old_val_connector_id when it cannot find the reference', () => {
const userAction = { ...createConnectorUserAction(), references: [] };
const transformed = transformFindResponseToExternalModel(
createSOFindResponse([createUserActionFindSO(userAction)])
);
expect(transformed.saved_objects[0].attributes.old_val_connector_id).toBeNull();
});
it('does not populate the new_val_connector_id when the reference exists but the action and fields are invalid', () => {
const validUserAction = createConnectorUserAction();
const invalidUserAction = {
...validUserAction,
attributes: { ...validUserAction.attributes, action: 'invalid' },
};
const transformed = transformFindResponseToExternalModel(
createSOFindResponse([
createUserActionFindSO(invalidUserAction as SavedObject<CaseUserActionAttributes>),
])
);
expect(transformed.saved_objects[0].attributes.new_val_connector_id).toBeNull();
});
it('does not populate the old_val_connector_id when the reference exists but the action and fields are invalid', () => {
const validUserAction = createUserActionSO({
action: 'create',
fields: ['connector'],
oldValue: createConnectorObject(),
});
const invalidUserAction = {
...validUserAction,
attributes: { ...validUserAction.attributes, action: 'invalid' },
};
const transformed = transformFindResponseToExternalModel(
createSOFindResponse([
createUserActionFindSO(invalidUserAction as SavedObject<CaseUserActionAttributes>),
])
);
expect(transformed.saved_objects[0].attributes.old_val_connector_id).toBeNull();
});
it('populates the new_val_connector_id', () => {
const userAction = createConnectorUserAction();
const transformed = transformFindResponseToExternalModel(
createSOFindResponse([createUserActionFindSO(userAction)])
);
expect(transformed.saved_objects[0].attributes.new_val_connector_id).toEqual('1');
});
it('populates the old_val_connector_id', () => {
const userAction = createUserActionSO({
action: 'create',
fields: ['connector'],
oldValue: createConnectorObject(),
});
const transformed = transformFindResponseToExternalModel(
createSOFindResponse([createUserActionFindSO(userAction)])
);
expect(transformed.saved_objects[0].attributes.old_val_connector_id).toEqual('1');
});
});
describe('update connector', () => {
it('does not populate the new_val_connector_id when it cannot find the reference', () => {
const userAction = { ...updateConnectorUserAction(), references: [] };
const transformed = transformFindResponseToExternalModel(
createSOFindResponse([createUserActionFindSO(userAction)])
);
expect(transformed.saved_objects[0].attributes.new_val_connector_id).toBeNull();
});
it('does not populate the old_val_connector_id when it cannot find the reference', () => {
const userAction = {
...updateConnectorUserAction({ oldValue: createJiraConnector() }),
references: [],
};
const transformed = transformFindResponseToExternalModel(
createSOFindResponse([createUserActionFindSO(userAction)])
);
expect(transformed.saved_objects[0].attributes.old_val_connector_id).toBeNull();
});
it('does not populate the new_val_connector_id when the reference exists but the action and fields are invalid', () => {
const validUserAction = updateConnectorUserAction();
const invalidUserAction = {
...validUserAction,
attributes: { ...validUserAction.attributes, action: 'invalid' },
};
const transformed = transformFindResponseToExternalModel(
createSOFindResponse([
createUserActionFindSO(invalidUserAction as SavedObject<CaseUserActionAttributes>),
])
);
expect(transformed.saved_objects[0].attributes.new_val_connector_id).toBeNull();
});
it('does not populate the old_val_connector_id when the reference exists but the action and fields are invalid', () => {
const validUserAction = updateConnectorUserAction({ oldValue: createJiraConnector() });
const invalidUserAction = {
...validUserAction,
attributes: { ...validUserAction.attributes, action: 'invalid' },
};
const transformed = transformFindResponseToExternalModel(
createSOFindResponse([
createUserActionFindSO(invalidUserAction as SavedObject<CaseUserActionAttributes>),
])
);
expect(transformed.saved_objects[0].attributes.old_val_connector_id).toBeNull();
});
it('populates the new_val_connector_id', () => {
const userAction = updateConnectorUserAction();
const transformed = transformFindResponseToExternalModel(
createSOFindResponse([createUserActionFindSO(userAction)])
);
expect(transformed.saved_objects[0].attributes.new_val_connector_id).toEqual('1');
});
it('populates the old_val_connector_id', () => {
const userAction = updateConnectorUserAction({ oldValue: createJiraConnector() });
const transformed = transformFindResponseToExternalModel(
createSOFindResponse([createUserActionFindSO(userAction)])
);
expect(transformed.saved_objects[0].attributes.old_val_connector_id).toEqual('1');
});
});
describe('push connector', () => {
it('does not populate the new_val_connector_id when it cannot find the reference', () => {
const userAction = { ...pushConnectorUserAction(), references: [] };
const transformed = transformFindResponseToExternalModel(
createSOFindResponse([createUserActionFindSO(userAction)])
);
expect(transformed.saved_objects[0].attributes.new_val_connector_id).toBeNull();
});
it('does not populate the old_val_connector_id when it cannot find the reference', () => {
const userAction = {
...pushConnectorUserAction({ oldValue: createExternalService() }),
references: [],
};
const transformed = transformFindResponseToExternalModel(
createSOFindResponse([createUserActionFindSO(userAction)])
);
expect(transformed.saved_objects[0].attributes.old_val_connector_id).toBeNull();
});
it('does not populate the new_val_connector_id when the reference exists but the action and fields are invalid', () => {
const validUserAction = pushConnectorUserAction();
const invalidUserAction = {
...validUserAction,
attributes: { ...validUserAction.attributes, action: 'invalid' },
};
const transformed = transformFindResponseToExternalModel(
createSOFindResponse([
createUserActionFindSO(invalidUserAction as SavedObject<CaseUserActionAttributes>),
])
);
expect(transformed.saved_objects[0].attributes.new_val_connector_id).toBeNull();
});
it('does not populate the old_val_connector_id when the reference exists but the action and fields are invalid', () => {
const validUserAction = pushConnectorUserAction({ oldValue: createExternalService() });
const invalidUserAction = {
...validUserAction,
attributes: { ...validUserAction.attributes, action: 'invalid' },
};
const transformed = transformFindResponseToExternalModel(
createSOFindResponse([
createUserActionFindSO(invalidUserAction as SavedObject<CaseUserActionAttributes>),
])
);
expect(transformed.saved_objects[0].attributes.old_val_connector_id).toBeNull();
});
it('populates the new_val_connector_id', () => {
const userAction = pushConnectorUserAction();
const transformed = transformFindResponseToExternalModel(
createSOFindResponse([createUserActionFindSO(userAction)])
);
expect(transformed.saved_objects[0].attributes.new_val_connector_id).toEqual('100');
});
it('populates the old_val_connector_id', () => {
const userAction = pushConnectorUserAction({ oldValue: createExternalService() });
const transformed = transformFindResponseToExternalModel(
createSOFindResponse([createUserActionFindSO(userAction)])
);
expect(transformed.saved_objects[0].attributes.old_val_connector_id).toEqual('100');
});
});
});
});

View file

@ -5,12 +5,7 @@
* 2.0.
*/
import {
Logger,
SavedObjectReference,
SavedObjectsFindResponse,
SavedObjectsFindResult,
} from 'kibana/server';
import { Logger, SavedObjectReference } from 'kibana/server';
import {
CASE_SAVED_OBJECT,
@ -18,17 +13,8 @@ import {
CaseUserActionAttributes,
MAX_DOCS_PER_PAGE,
SUB_CASE_SAVED_OBJECT,
CaseUserActionResponse,
CASE_COMMENT_SAVED_OBJECT,
isCreateConnector,
isPush,
isUpdateConnector,
} from '../../../common';
import { ClientArgs } from '..';
import { UserActionFieldType } from './types';
import { CASE_REF_NAME, COMMENT_REF_NAME, SUB_CASE_REF_NAME } from '../../common';
import { ConnectorIdReferenceName, PushConnectorIdReferenceName } from './transform';
import { findConnectorIdReference } from '../transform';
interface GetCaseUserActionArgs extends ClientArgs {
caseId: string;
@ -47,16 +33,12 @@ interface PostCaseUserActionArgs extends ClientArgs {
export class CaseUserActionService {
constructor(private readonly log: Logger) {}
public async getAll({
unsecuredSavedObjectsClient,
caseId,
subCaseId,
}: GetCaseUserActionArgs): Promise<SavedObjectsFindResponse<CaseUserActionResponse>> {
public async getAll({ unsecuredSavedObjectsClient, caseId, subCaseId }: GetCaseUserActionArgs) {
try {
const id = subCaseId ?? caseId;
const type = subCaseId ? SUB_CASE_SAVED_OBJECT : CASE_SAVED_OBJECT;
const userActions = await unsecuredSavedObjectsClient.find<CaseUserActionAttributes>({
return await unsecuredSavedObjectsClient.find<CaseUserActionAttributes>({
type: CASE_USER_ACTION_SAVED_OBJECT,
hasReference: { type, id },
page: 1,
@ -64,22 +46,17 @@ export class CaseUserActionService {
sortField: 'action_at',
sortOrder: 'asc',
});
return transformFindResponseToExternalModel(userActions);
} catch (error) {
this.log.error(`Error on GET case user action case id: ${caseId}: ${error}`);
throw error;
}
}
public async bulkCreate({
unsecuredSavedObjectsClient,
actions,
}: PostCaseUserActionArgs): Promise<void> {
public async bulkCreate({ unsecuredSavedObjectsClient, actions }: PostCaseUserActionArgs) {
try {
this.log.debug(`Attempting to POST a new case user action`);
await unsecuredSavedObjectsClient.bulkCreate<CaseUserActionAttributes>(
return await unsecuredSavedObjectsClient.bulkCreate<CaseUserActionAttributes>(
actions.map((action) => ({ type: CASE_USER_ACTION_SAVED_OBJECT, ...action }))
);
} catch (error) {
@ -88,71 +65,3 @@ export class CaseUserActionService {
}
}
}
export function transformFindResponseToExternalModel(
userActions: SavedObjectsFindResponse<CaseUserActionAttributes>
): SavedObjectsFindResponse<CaseUserActionResponse> {
return {
...userActions,
saved_objects: userActions.saved_objects.map((so) => ({
...so,
...transformToExternalModel(so),
})),
};
}
function transformToExternalModel(
userAction: SavedObjectsFindResult<CaseUserActionAttributes>
): SavedObjectsFindResult<CaseUserActionResponse> {
const { references } = userAction;
const newValueConnectorId = getConnectorIdFromReferences(UserActionFieldType.New, userAction);
const oldValueConnectorId = getConnectorIdFromReferences(UserActionFieldType.Old, userAction);
const caseId = findReferenceId(CASE_REF_NAME, CASE_SAVED_OBJECT, references) ?? '';
const commentId =
findReferenceId(COMMENT_REF_NAME, CASE_COMMENT_SAVED_OBJECT, references) ?? null;
const subCaseId = findReferenceId(SUB_CASE_REF_NAME, SUB_CASE_SAVED_OBJECT, references) ?? '';
return {
...userAction,
attributes: {
...userAction.attributes,
action_id: userAction.id,
case_id: caseId,
comment_id: commentId,
sub_case_id: subCaseId,
new_val_connector_id: newValueConnectorId,
old_val_connector_id: oldValueConnectorId,
},
};
}
function getConnectorIdFromReferences(
fieldType: UserActionFieldType,
userAction: SavedObjectsFindResult<CaseUserActionAttributes>
): string | null {
const {
// eslint-disable-next-line @typescript-eslint/naming-convention
attributes: { action, action_field },
references,
} = userAction;
if (isCreateConnector(action, action_field) || isUpdateConnector(action, action_field)) {
return findConnectorIdReference(ConnectorIdReferenceName[fieldType], references)?.id ?? null;
} else if (isPush(action, action_field)) {
return (
findConnectorIdReference(PushConnectorIdReferenceName[fieldType], references)?.id ?? null
);
}
return null;
}
function findReferenceId(
name: string,
type: string,
references: SavedObjectReference[]
): string | undefined {
return references.find((ref) => ref.name === name && ref.type === type)?.id;
}

View file

@ -1,320 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
/* eslint-disable @typescript-eslint/naming-convention */
import * as rt from 'io-ts';
import { isString } from 'lodash';
import { SavedObjectReference } from '../../../../../../src/core/server';
import {
CaseAttributes,
CaseConnector,
CaseConnectorRt,
CaseExternalServiceBasicRt,
isCreateConnector,
isPush,
isUpdateConnector,
noneConnectorId,
} from '../../../common';
import {
CONNECTOR_ID_REFERENCE_NAME,
getNoneCaseConnector,
PUSH_CONNECTOR_ID_REFERENCE_NAME,
USER_ACTION_OLD_ID_REF_NAME,
USER_ACTION_OLD_PUSH_ID_REF_NAME,
} from '../../common';
import { ACTION_SAVED_OBJECT_TYPE } from '../../../../actions/server';
import { UserActionFieldType } from './types';
/**
* Extracts the connector id from a json encoded string and formats it as a saved object reference. This will remove
* the field it extracted the connector id from.
*/
export function extractConnectorIdFromJson({
action,
actionFields,
actionDetails,
fieldType,
}: {
action?: string;
actionFields?: string[];
actionDetails?: string | null;
fieldType: UserActionFieldType;
}): { transformedActionDetails?: string | null; references: SavedObjectReference[] } {
if (!action || !actionFields || !actionDetails) {
return { transformedActionDetails: actionDetails, references: [] };
}
const decodedJson = JSON.parse(actionDetails);
return extractConnectorIdHelper({
action,
actionFields,
actionDetails: decodedJson,
fieldType,
});
}
/**
* Extracts the connector id from an unencoded object and formats it as a saved object reference.
* This will remove the field it extracted the connector id from.
*/
export function extractConnectorId({
action,
actionFields,
actionDetails,
fieldType,
}: {
action: string;
actionFields: string[];
actionDetails?: Record<string, unknown> | string | null;
fieldType: UserActionFieldType;
}): {
transformedActionDetails?: string | null;
references: SavedObjectReference[];
} {
if (!actionDetails || isString(actionDetails)) {
// the action was null, undefined, or a regular string so just return it unmodified and not encoded
return { transformedActionDetails: actionDetails, references: [] };
}
try {
return extractConnectorIdHelper({
action,
actionFields,
actionDetails,
fieldType,
});
} catch (error) {
return { transformedActionDetails: encodeActionDetails(actionDetails), references: [] };
}
}
function encodeActionDetails(actionDetails: Record<string, unknown>): string | null {
try {
return JSON.stringify(actionDetails);
} catch (error) {
return null;
}
}
/**
* Internal helper function for extracting the connector id. This is only exported for usage in unit tests.
* This function handles encoding the transformed fields as a json string
*/
export function extractConnectorIdHelper({
action,
actionFields,
actionDetails,
fieldType,
}: {
action: string;
actionFields: string[];
actionDetails: unknown;
fieldType: UserActionFieldType;
}): { transformedActionDetails: string; references: SavedObjectReference[] } {
let transformedActionDetails: unknown = actionDetails;
let referencesToReturn: SavedObjectReference[] = [];
try {
if (isCreateCaseConnector(action, actionFields, actionDetails)) {
const { transformedActionDetails: transformedConnectorPortion, references } =
transformConnectorFromCreateAndUpdateAction(actionDetails.connector, fieldType);
// the above call only transforms the connector portion of the action details so let's add back
// the rest of the details and we'll overwrite the connector portion when the transformed one
transformedActionDetails = {
...actionDetails,
...transformedConnectorPortion,
};
referencesToReturn = references;
} else if (isUpdateCaseConnector(action, actionFields, actionDetails)) {
const {
transformedActionDetails: { connector: transformedConnector },
references,
} = transformConnectorFromCreateAndUpdateAction(actionDetails, fieldType);
transformedActionDetails = transformedConnector;
referencesToReturn = references;
} else if (isPushConnector(action, actionFields, actionDetails)) {
({ transformedActionDetails, references: referencesToReturn } =
transformConnectorFromPushAction(actionDetails, fieldType));
}
} catch (error) {
// ignore any errors, we'll just return whatever was passed in for action details in that case
}
return {
transformedActionDetails: JSON.stringify(transformedActionDetails),
references: referencesToReturn,
};
}
function isCreateCaseConnector(
action: string,
actionFields: string[],
actionDetails: unknown
): actionDetails is { connector: CaseConnector } {
try {
const unsafeCase = actionDetails as CaseAttributes;
return (
isCreateConnector(action, actionFields) &&
unsafeCase.connector !== undefined &&
CaseConnectorRt.is(unsafeCase.connector)
);
} catch {
return false;
}
}
export const ConnectorIdReferenceName: Record<UserActionFieldType, ConnectorIdRefNameType> = {
[UserActionFieldType.New]: CONNECTOR_ID_REFERENCE_NAME,
[UserActionFieldType.Old]: USER_ACTION_OLD_ID_REF_NAME,
};
function transformConnectorFromCreateAndUpdateAction(
connector: CaseConnector,
fieldType: UserActionFieldType
): {
transformedActionDetails: { connector: unknown };
references: SavedObjectReference[];
} {
const { transformedConnector, references } = transformConnectorIdToReference(
ConnectorIdReferenceName[fieldType],
connector
);
return {
transformedActionDetails: transformedConnector,
references,
};
}
type ConnectorIdRefNameType =
| typeof CONNECTOR_ID_REFERENCE_NAME
| typeof USER_ACTION_OLD_ID_REF_NAME;
export const transformConnectorIdToReference = (
referenceName: ConnectorIdRefNameType,
connector?: {
id?: string;
}
): {
transformedConnector: { connector: unknown };
references: SavedObjectReference[];
} => {
const { id: connectorId, ...restConnector } = connector ?? {};
const references = createConnectorReference(connectorId, ACTION_SAVED_OBJECT_TYPE, referenceName);
const { id: ignoreNoneId, ...restNoneConnector } = getNoneCaseConnector();
const connectorFieldsToReturn =
connector && isConnectorIdValid(connectorId) ? restConnector : restNoneConnector;
return {
transformedConnector: {
connector: connectorFieldsToReturn,
},
references,
};
};
const createConnectorReference = (
id: string | null | undefined,
type: string,
name: string
): SavedObjectReference[] => {
return isConnectorIdValid(id)
? [
{
id,
type,
name,
},
]
: [];
};
const isConnectorIdValid = (id: string | null | undefined): id is string =>
id != null && id !== noneConnectorId;
function isUpdateCaseConnector(
action: string,
actionFields: string[],
actionDetails: unknown
): actionDetails is CaseConnector {
try {
return isUpdateConnector(action, actionFields) && CaseConnectorRt.is(actionDetails);
} catch {
return false;
}
}
type CaseExternalService = rt.TypeOf<typeof CaseExternalServiceBasicRt>;
function isPushConnector(
action: string,
actionFields: string[],
actionDetails: unknown
): actionDetails is CaseExternalService {
try {
return isPush(action, actionFields) && CaseExternalServiceBasicRt.is(actionDetails);
} catch {
return false;
}
}
export const PushConnectorIdReferenceName: Record<UserActionFieldType, PushConnectorIdRefNameType> =
{
[UserActionFieldType.New]: PUSH_CONNECTOR_ID_REFERENCE_NAME,
[UserActionFieldType.Old]: USER_ACTION_OLD_PUSH_ID_REF_NAME,
};
function transformConnectorFromPushAction(
externalService: CaseExternalService,
fieldType: UserActionFieldType
): {
transformedActionDetails: {} | null;
references: SavedObjectReference[];
} {
const { transformedPushConnector, references } = transformPushConnectorIdToReference(
PushConnectorIdReferenceName[fieldType],
externalService
);
return {
transformedActionDetails: transformedPushConnector.external_service,
references,
};
}
type PushConnectorIdRefNameType =
| typeof PUSH_CONNECTOR_ID_REFERENCE_NAME
| typeof USER_ACTION_OLD_PUSH_ID_REF_NAME;
export const transformPushConnectorIdToReference = (
referenceName: PushConnectorIdRefNameType,
external_service?: { connector_id?: string | null } | null
): {
transformedPushConnector: { external_service: {} | null };
references: SavedObjectReference[];
} => {
const { connector_id: pushConnectorId, ...restExternalService } = external_service ?? {};
const references = createConnectorReference(
pushConnectorId,
ACTION_SAVED_OBJECT_TYPE,
referenceName
);
return {
transformedPushConnector: { external_service: external_service ? restExternalService : null },
references,
};
};

View file

@ -1,14 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
/**
* Indicates whether which user action field is being parsed, the new_value or the old_value.
*/
export enum UserActionFieldType {
New = 'New',
Old = 'Old',
}

View file

@ -85,7 +85,7 @@ export default ({ getService }: FtrProviderContext): void => {
});
});
it('should create a user action when deleting a case', async () => {
it('should create a user action when creating a case', async () => {
const postedCase = await createCase(supertest, getPostCaseRequest());
await deleteCases({ supertest, caseIDs: [postedCase.id] });
const userActions = await getCaseUserActions({ supertest, caseID: postedCase.id });
@ -106,8 +106,6 @@ export default ({ getService }: FtrProviderContext): void => {
action_by: defaultUser,
old_value: null,
new_value: null,
new_val_connector_id: null,
old_val_connector_id: null,
case_id: `${postedCase.id}`,
comment_id: null,
sub_case_id: '',

View file

@ -126,8 +126,6 @@ export default ({ getService }: FtrProviderContext): void => {
action: 'update',
action_by: defaultUser,
new_value: CaseStatuses.closed,
new_val_connector_id: null,
old_val_connector_id: null,
old_value: CaseStatuses.open,
case_id: `${postedCase.id}`,
comment_id: null,
@ -167,8 +165,6 @@ export default ({ getService }: FtrProviderContext): void => {
action_by: defaultUser,
new_value: CaseStatuses['in-progress'],
old_value: CaseStatuses.open,
old_val_connector_id: null,
new_val_connector_id: null,
case_id: `${postedCase.id}`,
comment_id: null,
sub_case_id: '',

View file

@ -114,8 +114,6 @@ export default ({ getService }: FtrProviderContext): void => {
const { new_value, ...rest } = creationUserAction as CaseUserActionResponse;
const parsedNewValue = JSON.parse(new_value!);
const { id: connectorId, ...restCaseConnector } = postedCase.connector;
expect(rest).to.eql({
action_field: [
'description',
@ -129,9 +127,6 @@ export default ({ getService }: FtrProviderContext): void => {
action: 'create',
action_by: defaultUser,
old_value: null,
old_val_connector_id: null,
// the connector id will be null here because it the connector is none
new_val_connector_id: null,
case_id: `${postedCase.id}`,
comment_id: null,
sub_case_id: '',
@ -143,7 +138,7 @@ export default ({ getService }: FtrProviderContext): void => {
description: postedCase.description,
title: postedCase.title,
tags: postedCase.tags,
connector: restCaseConnector,
connector: postedCase.connector,
settings: postedCase.settings,
owner: postedCase.owner,
});

View file

@ -148,9 +148,7 @@ export default ({ getService }: FtrProviderContext): void => {
action: 'create',
action_by: defaultUser,
new_value: `{"comment":"${postCommentUserReq.comment}","type":"${postCommentUserReq.type}","owner":"securitySolutionFixture"}`,
new_val_connector_id: null,
old_value: null,
old_val_connector_id: null,
case_id: `${postedCase.id}`,
comment_id: `${patchedCase.comments![0].id}`,
sub_case_id: '',

View file

@ -48,15 +48,6 @@ export default ({ getService }: FtrProviderContext): void => {
});
it(`on new case, user action: 'create' should be called with actionFields: ['description', 'status', 'tags', 'title', 'connector', 'settings, owner]`, async () => {
const { id: connectorId, ...restConnector } = userActionPostResp.connector;
const userActionNewValueNoId = {
...userActionPostResp,
connector: {
...restConnector,
},
};
const { body: postedCase } = await supertest
.post(CASES_URL)
.set('kbn-xsrf', 'true')
@ -82,10 +73,7 @@ export default ({ getService }: FtrProviderContext): void => {
]);
expect(body[0].action).to.eql('create');
expect(body[0].old_value).to.eql(null);
expect(body[0].old_val_connector_id).to.eql(null);
// this will be null because it is for the none connector
expect(body[0].new_val_connector_id).to.eql(null);
expect(JSON.parse(body[0].new_value)).to.eql(userActionNewValueNoId);
expect(JSON.parse(body[0].new_value)).to.eql(userActionPostResp);
});
it(`on close case, user action: 'update' should be called with actionFields: ['status']`, async () => {
@ -159,19 +147,18 @@ export default ({ getService }: FtrProviderContext): void => {
expect(body.length).to.eql(2);
expect(body[1].action_field).to.eql(['connector']);
expect(body[1].action).to.eql('update');
// this is null because it is the none connector
expect(body[1].old_val_connector_id).to.eql(null);
expect(JSON.parse(body[1].old_value)).to.eql({
id: 'none',
name: 'none',
type: '.none',
fields: null,
});
expect(JSON.parse(body[1].new_value)).to.eql({
id: '123',
name: 'Connector',
type: '.jira',
fields: { issueType: 'Task', priority: 'High', parent: null },
});
expect(body[1].new_val_connector_id).to.eql('123');
});
it(`on update tags, user action: 'add' and 'delete' should be called with actionFields: ['tags']`, async () => {

View file

@ -12,10 +12,6 @@ import {
SECURITY_SOLUTION_OWNER,
} from '../../../../../../plugins/cases/common/constants';
import { getCaseUserActions } from '../../../../common/lib/utils';
import {
CaseUserActionResponse,
CaseUserActionsResponse,
} from '../../../../../../plugins/cases/common';
// eslint-disable-next-line import/no-default-export
export default function createGetTests({ getService }: FtrProviderContext) {
@ -45,18 +41,14 @@ export default function createGetTests({ getService }: FtrProviderContext) {
expect(connectorUserAction.action_field.length).eql(1);
expect(connectorUserAction.action_field[0]).eql('connector');
expect(connectorUserAction.old_val_connector_id).to.eql(
'c1900ac0-017f-11eb-93f8-d161651bf509'
);
expect(oldValue).to.eql({
id: 'c1900ac0-017f-11eb-93f8-d161651bf509',
name: 'none',
type: '.none',
fields: null,
});
expect(connectorUserAction.new_val_connector_id).to.eql(
'b1900ac0-017f-11eb-93f8-d161651bf509'
);
expect(newValue).to.eql({
id: 'b1900ac0-017f-11eb-93f8-d161651bf509',
name: 'none',
type: '.none',
fields: null,
@ -85,142 +77,5 @@ export default function createGetTests({ getService }: FtrProviderContext) {
}
});
});
describe('7.13 connector id extraction', () => {
let userActions: CaseUserActionsResponse;
before(async () => {
await esArchiver.load(
'x-pack/test/functional/es_archives/cases/migrations/7.13_user_actions'
);
});
after(async () => {
await esArchiver.unload(
'x-pack/test/functional/es_archives/cases/migrations/7.13_user_actions'
);
});
describe('none connector case', () => {
it('removes the connector id from the case create user action and sets the ids to null', async () => {
userActions = await getCaseUserActions({
supertest,
caseID: 'aa8ac630-005e-11ec-91f1-6daf2ab59fb5',
});
const userAction = getUserActionById(
userActions,
'ab43b5f0-005e-11ec-91f1-6daf2ab59fb5'
)!;
const newValDecoded = JSON.parse(userAction.new_value!);
expect(newValDecoded.description).to.be('a description');
expect(newValDecoded.title).to.be('a case');
expect(newValDecoded.connector).not.have.property('id');
// the connector id should be none so it should be removed
expect(userAction.new_val_connector_id).to.be(null);
expect(userAction.old_val_connector_id).to.be(null);
});
it('sets the connector ids to null for a create user action with null new and old values', async () => {
const userAction = getUserActionById(
userActions,
'b3094de0-005e-11ec-91f1-6daf2ab59fb5'
)!;
expect(userAction.new_val_connector_id).to.be(null);
expect(userAction.old_val_connector_id).to.be(null);
});
});
describe('case with many user actions', () => {
before(async () => {
userActions = await getCaseUserActions({
supertest,
caseID: 'e6fa9370-005e-11ec-91f1-6daf2ab59fb5',
});
});
it('removes the connector id field for a created case user action', async () => {
const userAction = getUserActionById(
userActions,
'e7882d70-005e-11ec-91f1-6daf2ab59fb5'
)!;
const newValDecoded = JSON.parse(userAction.new_value!);
expect(newValDecoded.description).to.be('a description');
expect(newValDecoded.title).to.be('a case');
expect(newValDecoded.connector).to.not.have.property('id');
expect(userAction.new_val_connector_id).to.be('d92243b0-005e-11ec-91f1-6daf2ab59fb5');
expect(userAction.old_val_connector_id).to.be(null);
});
it('removes the connector id from the external service new value', async () => {
const userAction = getUserActionById(
userActions,
'e9471b80-005e-11ec-91f1-6daf2ab59fb5'
)!;
const newValDecoded = JSON.parse(userAction.new_value!);
expect(newValDecoded.connector_name).to.be('a jira connector');
expect(newValDecoded).to.not.have.property('connector_id');
expect(userAction.new_val_connector_id).to.be('d92243b0-005e-11ec-91f1-6daf2ab59fb5');
expect(userAction.old_val_connector_id).to.be(null);
});
it('sets the connector ids to null for a comment user action', async () => {
const userAction = getUserActionById(
userActions,
'efe9de50-005e-11ec-91f1-6daf2ab59fb5'
)!;
const newValDecoded = JSON.parse(userAction.new_value!);
expect(newValDecoded.comment).to.be('a comment');
expect(userAction.new_val_connector_id).to.be(null);
expect(userAction.old_val_connector_id).to.be(null);
});
it('removes the connector id for an update connector action', async () => {
const userAction = getUserActionById(
userActions,
'16cd9e30-005f-11ec-91f1-6daf2ab59fb5'
)!;
const newValDecoded = JSON.parse(userAction.new_value!);
const oldValDecoded = JSON.parse(userAction.old_value!);
expect(newValDecoded.name).to.be('a different jira connector');
expect(oldValDecoded.name).to.be('a jira connector');
expect(newValDecoded).to.not.have.property('id');
expect(oldValDecoded).to.not.have.property('id');
expect(userAction.new_val_connector_id).to.be('0a572860-005f-11ec-91f1-6daf2ab59fb5');
expect(userAction.old_val_connector_id).to.be('d92243b0-005e-11ec-91f1-6daf2ab59fb5');
});
it('removes the connector id from the external service new value for second push', async () => {
const userAction = getUserActionById(
userActions,
'1ea33bb0-005f-11ec-91f1-6daf2ab59fb5'
)!;
const newValDecoded = JSON.parse(userAction.new_value!);
expect(newValDecoded.connector_name).to.be('a different jira connector');
expect(newValDecoded).to.not.have.property('connector_id');
expect(userAction.new_val_connector_id).to.be('0a572860-005f-11ec-91f1-6daf2ab59fb5');
expect(userAction.old_val_connector_id).to.be(null);
});
});
});
});
}
function getUserActionById(
userActions: CaseUserActionsResponse,
id: string
): CaseUserActionResponse | undefined {
return userActions.find((userAction) => userAction.action_id === id);
}

View file

@ -275,8 +275,6 @@ export default ({ getService }: FtrProviderContext): void => {
action: 'push-to-service',
action_by: defaultUser,
old_value: null,
old_val_connector_id: null,
new_val_connector_id: connector.id,
case_id: `${postedCase.id}`,
comment_id: null,
sub_case_id: '',
@ -286,6 +284,7 @@ export default ({ getService }: FtrProviderContext): void => {
expect(parsedNewValue).to.eql({
pushed_at: pushedCase.external_service!.pushed_at,
pushed_by: defaultUser,
connector_id: connector.id,
connector_name: connector.name,
external_id: '123',
external_title: 'INC01',

View file

@ -108,10 +108,8 @@ export default ({ getService }: FtrProviderContext): void => {
expect(body[1].action_field).to.eql(['pushed']);
expect(body[1].action).to.eql('push-to-service');
expect(body[1].old_value).to.eql(null);
expect(body[1].old_val_connector_id).to.eql(null);
expect(body[1].new_val_connector_id).to.eql(configure.connector.id);
const newValue = JSON.parse(body[1].new_value);
expect(newValue).to.not.have.property('connector_id');
expect(newValue.connector_id).to.eql(configure.connector.id);
expect(newValue.pushed_by).to.eql(defaultUser);
});
});