mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[Cases] Migrate user actions connector ID V2 (#112710)
* Making progress * Fleshing out the extraction logic * Finishing migration logic and starting more tests * Finishing migration unit tests * Making progress on services * Finishing transform to es schema * Finishing transform functionality and unit tests * reverting migration data updates * Cleaning up type errors * fixing test error * Working migration tests * Refactoring retrieval of connector fields * Refactoring connector id in and tests in frontend * Fixing tests and finished refactoring parse string * Fixing integration test * Fixing integration tests * Removing some duplicate code and updating test name * Fixing create connector user action bug * Addressing feedback and logging error * Moving parsing function to common * Fixing type errors * Fixing type errors * Addressing feedback * Fixing lint errors * Adjusting import for user action changes Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
525d3a5920
commit
70f635b14d
58 changed files with 7960 additions and 1182 deletions
|
@ -87,8 +87,11 @@ const CaseBasicRt = rt.type({
|
|||
owner: rt.string,
|
||||
});
|
||||
|
||||
export const CaseExternalServiceBasicRt = rt.type({
|
||||
connector_id: rt.union([rt.string, rt.null]),
|
||||
/**
|
||||
* 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({
|
||||
connector_name: rt.string,
|
||||
external_id: rt.string,
|
||||
external_title: rt.string,
|
||||
|
@ -97,7 +100,14 @@ export const CaseExternalServiceBasicRt = rt.type({
|
|||
pushed_by: UserRT,
|
||||
});
|
||||
|
||||
const CaseFullExternalServiceRt = rt.union([CaseExternalServiceBasicRt, rt.null]);
|
||||
export const CaseExternalServiceBasicRt = rt.intersection([
|
||||
rt.type({
|
||||
connector_id: rt.union([rt.string, rt.null]),
|
||||
}),
|
||||
CaseUserActionExternalServiceRt,
|
||||
]);
|
||||
|
||||
export const CaseFullExternalServiceRt = rt.union([CaseExternalServiceBasicRt, rt.null]);
|
||||
|
||||
export const CaseAttributesRt = rt.intersection([
|
||||
CaseBasicRt,
|
||||
|
|
|
@ -34,7 +34,6 @@ const UserActionRt = rt.union([
|
|||
rt.literal('push-to-service'),
|
||||
]);
|
||||
|
||||
// TO DO change state to status
|
||||
const CaseUserActionBasicRT = rt.type({
|
||||
action_field: UserActionFieldRt,
|
||||
action: UserActionRt,
|
||||
|
@ -51,6 +50,8 @@ 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 }),
|
||||
]);
|
||||
|
|
|
@ -84,14 +84,22 @@ export const ConnectorTypeFieldsRt = rt.union([
|
|||
ConnectorSwimlaneTypeFieldsRt,
|
||||
]);
|
||||
|
||||
export const CaseConnectorRt = rt.intersection([
|
||||
rt.type({
|
||||
id: rt.string,
|
||||
name: rt.string,
|
||||
}),
|
||||
/**
|
||||
* 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,
|
||||
}),
|
||||
CaseUserActionConnectorRt,
|
||||
]);
|
||||
|
||||
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>;
|
||||
|
|
|
@ -12,3 +12,4 @@ export * from './constants';
|
|||
export * from './api';
|
||||
export * from './ui/types';
|
||||
export * from './utils/connectors_api';
|
||||
export * from './utils/user_actions';
|
||||
|
|
|
@ -66,7 +66,9 @@ export interface CaseUserActions {
|
|||
caseId: string;
|
||||
commentId: string | null;
|
||||
newValue: string | null;
|
||||
newValConnectorId: string | null;
|
||||
oldValue: string | null;
|
||||
oldValConnectorId: string | null;
|
||||
}
|
||||
|
||||
export interface CaseExternalService {
|
||||
|
|
18
x-pack/plugins/cases/common/utils/user_actions.ts
Normal file
18
x-pack/plugins/cases/common/utils/user_actions.ts
Normal file
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* 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');
|
||||
}
|
8
x-pack/plugins/cases/public/common/user_actions/index.ts
Normal file
8
x-pack/plugins/cases/public/common/user_actions/index.ts
Normal file
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
export * from './parsers';
|
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* 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',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
77
x-pack/plugins/cases/public/common/user_actions/parsers.ts
Normal file
77
x-pack/plugins/cases/public/common/user_actions/parsers.ts
Normal file
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* 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,
|
||||
};
|
||||
};
|
|
@ -0,0 +1,187 @@
|
|||
/*
|
||||
* 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,
|
||||
};
|
|
@ -5,23 +5,33 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { ConnectorTypeFields } from '../../../common';
|
||||
import { CaseUserActions } from '../../containers/types';
|
||||
import { parseStringAsConnector } from '../../common/user_actions';
|
||||
|
||||
export const getConnectorFieldsFromUserActions = (id: string, userActions: CaseUserActions[]) => {
|
||||
export const getConnectorFieldsFromUserActions = (
|
||||
id: string,
|
||||
userActions: CaseUserActions[]
|
||||
): ConnectorTypeFields['fields'] => {
|
||||
try {
|
||||
for (const action of [...userActions].reverse()) {
|
||||
if (action.actionField.length === 1 && action.actionField[0] === 'connector') {
|
||||
if (action.oldValue && action.newValue) {
|
||||
const oldValue = JSON.parse(action.oldValue);
|
||||
const newValue = JSON.parse(action.newValue);
|
||||
const parsedNewConnector = parseStringAsConnector(
|
||||
action.newValConnectorId,
|
||||
action.newValue
|
||||
);
|
||||
|
||||
if (newValue.id === id) {
|
||||
return newValue.fields;
|
||||
}
|
||||
if (parsedNewConnector && id === parsedNewConnector.id) {
|
||||
return parsedNewConnector.fields;
|
||||
}
|
||||
|
||||
if (oldValue.id === id) {
|
||||
return oldValue.fields;
|
||||
}
|
||||
const parsedOldConnector = parseStringAsConnector(
|
||||
action.oldValConnectorId,
|
||||
action.oldValue
|
||||
);
|
||||
|
||||
if (parsedOldConnector && id === parsedOldConnector.id) {
|
||||
return parsedOldConnector.fields;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
import React from 'react';
|
||||
import { mount } from 'enzyme';
|
||||
|
||||
import { CaseStatuses } from '../../../common';
|
||||
import { CaseStatuses, ConnectorTypes } 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,50 +142,74 @@ 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
|
||||
);
|
||||
});
|
||||
|
||||
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,
|
||||
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('');
|
||||
});
|
||||
|
||||
expect(result).toEqual('selected My Connector 2 as incident management system');
|
||||
});
|
||||
it('returns an empty string when the encoded new value is null', () => {
|
||||
const result = getConnectorLabelTitle({
|
||||
action: getUserAction(['connector'], 'update', { newValue: null }),
|
||||
connectors,
|
||||
});
|
||||
|
||||
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,
|
||||
expect(result).toEqual('');
|
||||
});
|
||||
|
||||
expect(result).toEqual('removed external incident management system');
|
||||
});
|
||||
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,
|
||||
});
|
||||
|
||||
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,
|
||||
expect(result).toEqual('selected My Connector 2 as incident management system');
|
||||
});
|
||||
|
||||
expect(result).toEqual('changed connector field');
|
||||
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');
|
||||
});
|
||||
});
|
||||
|
||||
describe('toStringArray', () => {
|
||||
|
|
|
@ -23,10 +23,11 @@ import {
|
|||
CommentType,
|
||||
Comment,
|
||||
CommentRequestActionsType,
|
||||
noneConnectorId,
|
||||
} from '../../../common';
|
||||
import { CaseUserActions } from '../../containers/types';
|
||||
import { CaseServices } from '../../containers/use_get_case_user_actions';
|
||||
import { parseString } from '../../containers/utils';
|
||||
import { parseStringAsConnector, parseStringAsExternalService } from '../../common/user_actions';
|
||||
import { Tags } from '../tag_list/tags';
|
||||
import { UserActionUsernameWithAvatar } from './user_action_username_with_avatar';
|
||||
import { UserActionTimestamp } from './user_action_timestamp';
|
||||
|
@ -97,23 +98,27 @@ export const getConnectorLabelTitle = ({
|
|||
action: CaseUserActions;
|
||||
connectors: ActionConnector[];
|
||||
}) => {
|
||||
const oldValue = parseString(`${action.oldValue}`);
|
||||
const newValue = parseString(`${action.newValue}`);
|
||||
const oldConnector = parseStringAsConnector(action.oldValConnectorId, action.oldValue);
|
||||
const newConnector = parseStringAsConnector(action.newValConnectorId, action.newValue);
|
||||
|
||||
if (oldValue === null || newValue === null) {
|
||||
if (!oldConnector || !newConnector) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// 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
|
||||
// if the ids are the same, assume we just changed the fields
|
||||
if (oldConnector.id === newConnector.id) {
|
||||
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) => {
|
||||
|
@ -133,7 +138,8 @@ const getTagsLabelTitle = (action: CaseUserActions) => {
|
|||
};
|
||||
|
||||
export const getPushedServiceLabelTitle = (action: CaseUserActions, firstPush: boolean) => {
|
||||
const pushedVal = JSON.parse(action.newValue ?? '') as CaseFullExternalService;
|
||||
const externalService = parseStringAsExternalService(action.newValConnectorId, action.newValue);
|
||||
|
||||
return (
|
||||
<EuiFlexGroup
|
||||
alignItems="baseline"
|
||||
|
@ -143,12 +149,12 @@ export const getPushedServiceLabelTitle = (action: CaseUserActions, firstPush: b
|
|||
>
|
||||
<EuiFlexItem data-test-subj="pushed-label">
|
||||
{`${firstPush ? i18n.PUSHED_NEW_INCIDENT : i18n.UPDATE_INCIDENT} ${
|
||||
pushedVal?.connector_name
|
||||
externalService?.connector_name
|
||||
}`}
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiLink data-test-subj="pushed-value" href={pushedVal?.external_url} target="_blank">
|
||||
{pushedVal?.external_title}
|
||||
<EuiLink data-test-subj="pushed-value" href={externalService?.external_url} target="_blank">
|
||||
{externalService?.external_title}
|
||||
</EuiLink>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
@ -157,20 +163,19 @@ export const getPushedServiceLabelTitle = (action: CaseUserActions, firstPush: b
|
|||
|
||||
export const getPushInfo = (
|
||||
caseServices: CaseServices,
|
||||
// a JSON parse failure will result in null for parsedValue
|
||||
parsedValue: { connector_id: string | null; connector_name: string } | null,
|
||||
externalService: CaseFullExternalService | undefined,
|
||||
index: number
|
||||
) =>
|
||||
parsedValue != null && parsedValue.connector_id != null
|
||||
externalService != null && externalService.connector_id != null
|
||||
? {
|
||||
firstPush: caseServices[parsedValue.connector_id]?.firstPushIndex === index,
|
||||
parsedConnectorId: parsedValue.connector_id,
|
||||
parsedConnectorName: parsedValue.connector_name,
|
||||
firstPush: caseServices[externalService.connector_id]?.firstPushIndex === index,
|
||||
parsedConnectorId: externalService.connector_id,
|
||||
parsedConnectorName: externalService.connector_name,
|
||||
}
|
||||
: {
|
||||
firstPush: false,
|
||||
parsedConnectorId: 'none',
|
||||
parsedConnectorName: 'none',
|
||||
parsedConnectorId: noneConnectorId,
|
||||
parsedConnectorName: noneConnectorId,
|
||||
};
|
||||
|
||||
const getUpdateActionIcon = (actionField: string): string => {
|
||||
|
|
|
@ -35,7 +35,7 @@ import {
|
|||
Ecs,
|
||||
} from '../../../common';
|
||||
import { CaseServices } from '../../containers/use_get_case_user_actions';
|
||||
import { parseString } from '../../containers/utils';
|
||||
import { parseStringAsExternalService } from '../../common/user_actions';
|
||||
import { OnUpdateFields } from '../case_view';
|
||||
import {
|
||||
getConnectorLabelTitle,
|
||||
|
@ -512,10 +512,14 @@ export const UserActionTree = React.memo(
|
|||
|
||||
// Pushed information
|
||||
if (action.actionField.length === 1 && action.actionField[0] === 'pushed') {
|
||||
const parsedValue = parseString(`${action.newValue}`);
|
||||
const parsedExternalService = parseStringAsExternalService(
|
||||
action.newValConnectorId,
|
||||
action.newValue
|
||||
);
|
||||
|
||||
const { firstPush, parsedConnectorId, parsedConnectorName } = getPushInfo(
|
||||
caseServices,
|
||||
parsedValue,
|
||||
parsedExternalService,
|
||||
index
|
||||
);
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ import { ActionLicense, AllCases, Case, CasesStatus, CaseUserActions, Comment }
|
|||
|
||||
import {
|
||||
AssociationType,
|
||||
CaseUserActionConnector,
|
||||
CaseResponse,
|
||||
CasesFindResponse,
|
||||
CasesResponse,
|
||||
|
@ -19,6 +20,9 @@ import {
|
|||
CommentResponse,
|
||||
CommentType,
|
||||
ConnectorTypes,
|
||||
isCreateConnector,
|
||||
isPush,
|
||||
isUpdateConnector,
|
||||
SECURITY_SOLUTION_OWNER,
|
||||
UserAction,
|
||||
UserActionField,
|
||||
|
@ -240,7 +244,9 @@ 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,
|
||||
|
@ -308,12 +314,7 @@ 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,
|
||||
|
@ -328,8 +329,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',
|
||||
|
@ -350,7 +351,7 @@ export const pushedCaseSnake = {
|
|||
type: ConnectorTypes.jira,
|
||||
fields: null,
|
||||
},
|
||||
external_service: basicPushSnake,
|
||||
external_service: { ...basicPushSnake, connector_id: pushConnectorId },
|
||||
};
|
||||
|
||||
export const reporters: string[] = ['alexis', 'kim', 'maria', 'steph'];
|
||||
|
@ -385,17 +386,20 @@ const basicActionSnake = {
|
|||
comment_id: null,
|
||||
owner: SECURITY_SOLUTION_OWNER,
|
||||
};
|
||||
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 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 caseUserActionsSnake: CaseUserActionsResponse = [
|
||||
getUserActionSnake(['description'], 'create'),
|
||||
|
@ -405,17 +409,76 @@ export const caseUserActionsSnake: CaseUserActionsResponse = [
|
|||
|
||||
// user actions
|
||||
|
||||
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 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 getAlertUserAction = () => ({
|
||||
...basicAction,
|
||||
|
|
|
@ -18,7 +18,9 @@ import {
|
|||
basicPushSnake,
|
||||
caseUserActions,
|
||||
elasticUser,
|
||||
getJiraConnectorWithoutId,
|
||||
getUserAction,
|
||||
jiraFields,
|
||||
} from './mock';
|
||||
import * as api from './api';
|
||||
|
||||
|
@ -299,15 +301,14 @@ 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,
|
||||
|
@ -346,15 +347,14 @@ 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,11 +392,7 @@ describe('useGetCaseUserActions', () => {
|
|||
const userActions = [
|
||||
...caseUserActions,
|
||||
getUserAction(['pushed'], 'push-to-service'),
|
||||
{
|
||||
...getUserAction(['connector'], 'update'),
|
||||
oldValue: JSON.stringify({ id: '123', fields: { issueType: '10006', priority: null } }),
|
||||
newValue: JSON.stringify({ id: '123', fields: { issueType: '10006', priority: 'High' } }),
|
||||
},
|
||||
createUpdateConnectorFields123HighPriorityUserAction(),
|
||||
];
|
||||
|
||||
const result = getPushedInfo(userActions, '123');
|
||||
|
@ -418,11 +414,7 @@ describe('useGetCaseUserActions', () => {
|
|||
const userActions = [
|
||||
...caseUserActions,
|
||||
getUserAction(['pushed'], 'push-to-service'),
|
||||
{
|
||||
...getUserAction(['connector'], 'update'),
|
||||
oldValue: JSON.stringify({ id: '123', fields: { issueType: '10006', priority: null } }),
|
||||
newValue: JSON.stringify({ id: '456', fields: { issueTypes: ['10'], severity: '6' } }),
|
||||
},
|
||||
createChangeConnector123To456UserAction(),
|
||||
];
|
||||
|
||||
const result = getPushedInfo(userActions, '123');
|
||||
|
@ -444,16 +436,8 @@ describe('useGetCaseUserActions', () => {
|
|||
const userActions = [
|
||||
...caseUserActions,
|
||||
getUserAction(['pushed'], 'push-to-service'),
|
||||
{
|
||||
...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 } }),
|
||||
},
|
||||
createChangeConnector123To456UserAction(),
|
||||
createChangeConnector456To123UserAction(),
|
||||
];
|
||||
|
||||
const result = getPushedInfo(userActions, '123');
|
||||
|
@ -474,22 +458,10 @@ describe('useGetCaseUserActions', () => {
|
|||
it('Change fields and connector after push - hasDataToPush: true', () => {
|
||||
const userActions = [
|
||||
...caseUserActions,
|
||||
{
|
||||
...getUserAction(['connector'], 'update'),
|
||||
oldValue: JSON.stringify({ id: '123', fields: { issueType: '10006', priority: null } }),
|
||||
newValue: JSON.stringify({ id: '123', fields: { issueType: '10006', priority: 'High' } }),
|
||||
},
|
||||
createUpdateConnectorFields123HighPriorityUserAction(),
|
||||
getUserAction(['pushed'], 'push-to-service'),
|
||||
{
|
||||
...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' } }),
|
||||
},
|
||||
createChangeConnector123HighPriorityTo456UserAction(),
|
||||
createChangeConnector456To123PriorityLowUserAction(),
|
||||
];
|
||||
|
||||
const result = getPushedInfo(userActions, '123');
|
||||
|
@ -510,22 +482,10 @@ describe('useGetCaseUserActions', () => {
|
|||
it('Change only connector after push - hasDataToPush: false', () => {
|
||||
const userActions = [
|
||||
...caseUserActions,
|
||||
{
|
||||
...getUserAction(['connector'], 'update'),
|
||||
oldValue: JSON.stringify({ id: '123', fields: { issueType: '10006', priority: null } }),
|
||||
newValue: JSON.stringify({ id: '123', fields: { issueType: '10006', priority: 'High' } }),
|
||||
},
|
||||
createUpdateConnectorFields123HighPriorityUserAction(),
|
||||
getUserAction(['pushed'], 'push-to-service'),
|
||||
{
|
||||
...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' } }),
|
||||
},
|
||||
createChangeConnector123HighPriorityTo456UserAction(),
|
||||
createChangeConnector456To123HighPriorityUserAction(),
|
||||
];
|
||||
|
||||
const result = getPushedInfo(userActions, '123');
|
||||
|
@ -547,45 +507,24 @@ 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,
|
||||
{
|
||||
...getUserAction(['connector'], 'update'),
|
||||
oldValue: JSON.stringify({ id: '123', fields: { issueType: '10006', priority: null } }),
|
||||
newValue: JSON.stringify({ id: '123', fields: { issueType: '10006', priority: 'High' } }),
|
||||
},
|
||||
createUpdateConnectorFields123HighPriorityUserAction(),
|
||||
pushAction123,
|
||||
{
|
||||
...getUserAction(['connector'], 'update'),
|
||||
oldValue: JSON.stringify({ id: '123', fields: { issueType: '10006', priority: 'High' } }),
|
||||
newValue: JSON.stringify({ id: '456', fields: { issueTypes: ['10'], severity: '6' } }),
|
||||
},
|
||||
createChangeConnector123HighPriorityTo456UserAction(),
|
||||
pushAction456,
|
||||
{
|
||||
...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' } }),
|
||||
},
|
||||
createChangeConnector456To123PriorityLowUserAction(),
|
||||
createChangeConnector123LowPriorityTo456UserAction(),
|
||||
createChangeConnector456To123PriorityLowUserAction(),
|
||||
];
|
||||
|
||||
const result = getPushedInfo(userActions, '123');
|
||||
|
@ -617,34 +556,22 @@ 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', {
|
||||
newValConnectorId: '456',
|
||||
newValue: JSON.stringify(push456),
|
||||
};
|
||||
});
|
||||
|
||||
const userActions = [
|
||||
...caseUserActions,
|
||||
{
|
||||
...getUserAction(['connector'], 'update'),
|
||||
oldValue: JSON.stringify({ id: '123', fields: { issueType: '10006', priority: null } }),
|
||||
newValue: JSON.stringify({ id: '123', fields: { issueType: '10006', priority: 'High' } }),
|
||||
},
|
||||
createUpdateConnectorFields123HighPriorityUserAction(),
|
||||
pushAction123,
|
||||
{
|
||||
...getUserAction(['connector'], 'update'),
|
||||
oldValue: JSON.stringify({ id: '123', fields: { issueType: '10006', priority: 'High' } }),
|
||||
newValue: JSON.stringify({ id: '456', fields: { issueTypes: ['10'], severity: '6' } }),
|
||||
},
|
||||
createChangeConnector123HighPriorityTo456UserAction(),
|
||||
pushAction456,
|
||||
{
|
||||
...getUserAction(['connector'], 'update'),
|
||||
oldValue: JSON.stringify({ id: '456', fields: { issueTypes: ['10'], severity: '6' } }),
|
||||
newValue: JSON.stringify({ id: '123', fields: { issueType: '10006', priority: 'High' } }),
|
||||
},
|
||||
createChangeConnector456To123HighPriorityUserAction(),
|
||||
];
|
||||
|
||||
const result = getPushedInfo(userActions, '123');
|
||||
|
@ -675,22 +602,10 @@ describe('useGetCaseUserActions', () => {
|
|||
it('Changing other connectors fields does not count as an update', () => {
|
||||
const userActions = [
|
||||
...caseUserActions,
|
||||
{
|
||||
...getUserAction(['connector'], 'update'),
|
||||
oldValue: JSON.stringify({ id: '123', fields: { issueType: '10006', priority: null } }),
|
||||
newValue: JSON.stringify({ id: '123', fields: { issueType: '10006', priority: 'High' } }),
|
||||
},
|
||||
createUpdateConnectorFields123HighPriorityUserAction(),
|
||||
getUserAction(['pushed'], 'push-to-service'),
|
||||
{
|
||||
...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' } }),
|
||||
},
|
||||
createChangeConnector123HighPriorityTo456UserAction(),
|
||||
createUpdateConnectorFields456HighPriorityUserAction(),
|
||||
];
|
||||
|
||||
const result = getPushedInfo(userActions, '123');
|
||||
|
@ -709,3 +624,83 @@ 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',
|
||||
});
|
||||
|
|
|
@ -18,7 +18,8 @@ import {
|
|||
} from '../../common';
|
||||
import { getCaseUserActions, getSubCaseUserActions } from './api';
|
||||
import * as i18n from './translations';
|
||||
import { convertToCamelCase, parseString } from './utils';
|
||||
import { convertToCamelCase } from './utils';
|
||||
import { parseStringAsConnector, parseStringAsExternalService } from '../common/user_actions';
|
||||
import { useToasts } from '../common/lib/kibana';
|
||||
|
||||
export interface CaseService extends CaseExternalService {
|
||||
|
@ -58,8 +59,24 @@ export interface UseGetCaseUserActions extends CaseUserActionsState {
|
|||
) => Promise<void>;
|
||||
}
|
||||
|
||||
const getExternalService = (value: string): CaseExternalService | null =>
|
||||
convertToCamelCase<CaseFullExternalService, CaseExternalService>(parseString(`${value}`));
|
||||
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 groupConnectorFields = (
|
||||
userActions: CaseUserActions[]
|
||||
|
@ -69,22 +86,26 @@ const groupConnectorFields = (
|
|||
return acc;
|
||||
}
|
||||
|
||||
const oldValue = parseString(`${mua.oldValue}`);
|
||||
const newValue = parseString(`${mua.newValue}`);
|
||||
const oldConnector = parseStringAsConnector(mua.oldValConnectorId, mua.oldValue);
|
||||
const newConnector = parseStringAsConnector(mua.newValConnectorId, mua.newValue);
|
||||
|
||||
if (oldValue == null || newValue == null) {
|
||||
if (!oldConnector || !newConnector) {
|
||||
return acc;
|
||||
}
|
||||
|
||||
return {
|
||||
...acc,
|
||||
[oldValue.id]: [
|
||||
...(acc[oldValue.id] || []),
|
||||
...(oldValue.id === newValue.id ? [oldValue.fields, newValue.fields] : [oldValue.fields]),
|
||||
[oldConnector.id]: [
|
||||
...(acc[oldConnector.id] || []),
|
||||
...(oldConnector.id === newConnector.id
|
||||
? [oldConnector.fields, newConnector.fields]
|
||||
: [oldConnector.fields]),
|
||||
],
|
||||
[newValue.id]: [
|
||||
...(acc[newValue.id] || []),
|
||||
...(oldValue.id === newValue.id ? [oldValue.fields, newValue.fields] : [newValue.fields]),
|
||||
[newConnector.id]: [
|
||||
...(acc[newConnector.id] || []),
|
||||
...(oldConnector.id === newConnector.id
|
||||
? [oldConnector.fields, newConnector.fields]
|
||||
: [newConnector.fields]),
|
||||
],
|
||||
};
|
||||
}, {} as Record<string, Array<CaseConnector['fields']>>);
|
||||
|
@ -137,9 +158,7 @@ export const getPushedInfo = (
|
|||
const hasDataToPushForConnector = (connectorId: string): boolean => {
|
||||
const caseUserActionsReversed = [...caseUserActions].reverse();
|
||||
const lastPushOfConnectorReversedIndex = caseUserActionsReversed.findIndex(
|
||||
(mua) =>
|
||||
mua.action === 'push-to-service' &&
|
||||
getExternalService(`${mua.newValue}`)?.connectorId === connectorId
|
||||
(mua) => mua.action === 'push-to-service' && mua.newValConnectorId === connectorId
|
||||
);
|
||||
|
||||
if (lastPushOfConnectorReversedIndex === -1) {
|
||||
|
@ -190,7 +209,7 @@ export const getPushedInfo = (
|
|||
return acc;
|
||||
}
|
||||
|
||||
const externalService = getExternalService(`${cua.newValue}`);
|
||||
const externalService = getExternalService(cua.newValConnectorId, cua.newValue);
|
||||
if (externalService === null) {
|
||||
return acc;
|
||||
}
|
||||
|
|
|
@ -36,14 +36,6 @@ 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)) {
|
||||
|
|
|
@ -106,7 +106,7 @@ async function getSubCase({
|
|||
caseId,
|
||||
subCaseId: newSubCase.id,
|
||||
fields: ['status', 'sub_case'],
|
||||
newValue: JSON.stringify({ status: newSubCase.attributes.status }),
|
||||
newValue: { status: newSubCase.attributes.status },
|
||||
owner: newSubCase.attributes.owner,
|
||||
}),
|
||||
],
|
||||
|
@ -220,7 +220,7 @@ const addGeneratedAlerts = async (
|
|||
subCaseId: updatedCase.subCaseId,
|
||||
commentId: newComment.id,
|
||||
fields: ['comment'],
|
||||
newValue: JSON.stringify(query),
|
||||
newValue: query,
|
||||
owner: newComment.attributes.owner,
|
||||
}),
|
||||
],
|
||||
|
@ -408,7 +408,7 @@ export const addComment = async (
|
|||
subCaseId: updatedCase.subCaseId,
|
||||
commentId: newComment.id,
|
||||
fields: ['comment'],
|
||||
newValue: JSON.stringify(query),
|
||||
newValue: query,
|
||||
owner: newComment.attributes.owner,
|
||||
}),
|
||||
],
|
||||
|
|
|
@ -17,6 +17,7 @@ import {
|
|||
SUB_CASE_SAVED_OBJECT,
|
||||
CaseResponse,
|
||||
CommentPatchRequest,
|
||||
CommentRequest,
|
||||
} from '../../../common';
|
||||
import { AttachmentService, CasesService } from '../../services';
|
||||
import { CasesClientArgs } from '..';
|
||||
|
@ -193,12 +194,12 @@ export async function update(
|
|||
subCaseId: subCaseID,
|
||||
commentId: updatedComment.id,
|
||||
fields: ['comment'],
|
||||
newValue: JSON.stringify(queryRestAttributes),
|
||||
oldValue: JSON.stringify(
|
||||
// casting because typescript is complaining that it's not a Record<string, unknown> even though it is
|
||||
newValue: queryRestAttributes as CommentRequest,
|
||||
oldValue:
|
||||
// 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,
|
||||
}),
|
||||
],
|
||||
|
|
|
@ -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: JSON.stringify(query),
|
||||
newValue: query,
|
||||
owner: newCase.attributes.owner,
|
||||
}),
|
||||
],
|
||||
|
|
|
@ -168,7 +168,7 @@ export async function deleteCases(ids: string[], clientArgs: CasesClientArgs): P
|
|||
'settings',
|
||||
OWNER_FIELD,
|
||||
'comment',
|
||||
...(ENABLE_CASE_CONNECTOR ? ['sub_case'] : []),
|
||||
...(ENABLE_CASE_CONNECTOR ? ['sub_case' as const] : []),
|
||||
],
|
||||
owner: caseInfo.attributes.owner,
|
||||
})
|
||||
|
|
|
@ -231,8 +231,10 @@ export const userActions: CaseUserActionsResponse = [
|
|||
username: 'elastic',
|
||||
},
|
||||
new_value:
|
||||
'{"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}}',
|
||||
'{"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',
|
||||
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,
|
||||
|
@ -248,7 +250,9 @@ 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_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"}',
|
||||
'{"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,
|
||||
old_value: null,
|
||||
action_id: '0a801750-6647-11eb-a291-51bf6b175a53',
|
||||
case_id: 'fcdedd20-6646-11eb-a291-51bf6b175a53',
|
||||
|
@ -265,6 +269,8 @@ 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',
|
||||
|
@ -282,6 +288,8 @@ 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',
|
||||
|
@ -297,8 +305,10 @@ 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_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"}',
|
||||
'{"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',
|
||||
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,
|
||||
|
@ -315,6 +325,8 @@ 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',
|
||||
|
|
|
@ -241,7 +241,7 @@ export const push = async (
|
|||
actionBy: { username, full_name, email },
|
||||
caseId,
|
||||
fields: ['pushed'],
|
||||
newValue: JSON.stringify(externalService),
|
||||
newValue: externalService,
|
||||
owner: myCase.attributes.owner,
|
||||
}),
|
||||
],
|
||||
|
|
|
@ -799,8 +799,10 @@ 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_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"}',
|
||||
// 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,
|
||||
old_value: null,
|
||||
action_id: '9b91d8f0-6647-11eb-a291-51bf6b175a53',
|
||||
case_id: 'fcdedd20-6646-11eb-a291-51bf6b175a53',
|
||||
|
|
|
@ -20,6 +20,8 @@ import {
|
|||
CommentRequestUserType,
|
||||
CommentRequestAlertType,
|
||||
CommentRequestActionsType,
|
||||
CaseUserActionResponse,
|
||||
isPush,
|
||||
} from '../../../common';
|
||||
import { ActionsClient } from '../../../../actions/server';
|
||||
import { CasesClientGetAlertsResponse } from '../../client/alerts/types';
|
||||
|
@ -55,22 +57,36 @@ export const getLatestPushInfo = (
|
|||
userActions: CaseUserActionsResponse
|
||||
): { index: number; pushedInfo: CaseFullExternalService } | null => {
|
||||
for (const [index, action] of [...userActions].reverse().entries()) {
|
||||
if (action.action === 'push-to-service' && action.new_value)
|
||||
if (
|
||||
isPush(action.action, action.action_field) &&
|
||||
isValidNewValue(action) &&
|
||||
connectorId === action.new_val_connector_id
|
||||
) {
|
||||
try {
|
||||
const pushedInfo = JSON.parse(action.new_value);
|
||||
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 };
|
||||
}
|
||||
// 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 },
|
||||
};
|
||||
} catch (e) {
|
||||
// Silence JSON parse errors
|
||||
// ignore parse failures and check the next user action
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
|
106
x-pack/plugins/cases/server/client/user_actions/get.test.ts
Normal file
106
x-pack/plugins/cases/server/client/user_actions/get.test.ts
Normal file
|
@ -0,0 +1,106 @@
|
|||
/*
|
||||
* 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 });
|
|
@ -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 } from '../../common';
|
||||
import { createCaseError, checkEnabledCaseConnectorOrThrow, SUB_CASE_REF_NAME } from '../../common';
|
||||
import { CasesClientArgs } from '..';
|
||||
import { Operations } from '../../authorization';
|
||||
import { UserActionGet } from './client';
|
||||
|
@ -40,23 +40,12 @@ export const get = async (
|
|||
operation: Operations.getUserActions,
|
||||
});
|
||||
|
||||
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 ?? '',
|
||||
},
|
||||
];
|
||||
}, [])
|
||||
);
|
||||
const resultsToEncode =
|
||||
subCaseId == null
|
||||
? extractAttributesWithoutSubCases(userActions)
|
||||
: extractAttributes(userActions);
|
||||
|
||||
return CaseUserActionsResponseRt.encode(resultsToEncode);
|
||||
} catch (error) {
|
||||
throw createCaseError({
|
||||
message: `Failed to retrieve user actions case id: ${caseId} sub case id: ${subCaseId}: ${error}`,
|
||||
|
@ -65,3 +54,21 @@ 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);
|
||||
}
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
* 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.
|
||||
|
@ -15,3 +17,30 @@ 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}`;
|
||||
|
|
|
@ -30,322 +30,324 @@ const create_7_14_0_case = ({
|
|||
},
|
||||
});
|
||||
|
||||
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('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() });
|
||||
|
||||
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 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,
|
||||
});
|
||||
|
||||
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 [
|
||||
expect(migratedConnector.references.length).toBe(0);
|
||||
expect(migratedConnector.attributes.connector).not.toHaveProperty('id');
|
||||
expect(migratedConnector.attributes.connector).toMatchInlineSnapshot(`
|
||||
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,
|
||||
},
|
||||
"fields": null,
|
||||
"name": "none",
|
||||
"type": ".none",
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
const migratedConnector = caseConnectorIdMigration(
|
||||
caseSavedObject
|
||||
) as SavedObjectSanitizedDoc<CaseAttributes>;
|
||||
it('does not create a reference when the connector is undefined', () => {
|
||||
const caseSavedObject = create_7_14_0_case();
|
||||
|
||||
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 [
|
||||
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 {
|
||||
"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',
|
||||
},
|
||||
},
|
||||
"fields": null,
|
||||
"name": "none",
|
||||
"type": ".none",
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
const migratedConnector = caseConnectorIdMigration(
|
||||
caseSavedObject
|
||||
) as SavedObjectSanitizedDoc<CaseAttributes>;
|
||||
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,
|
||||
});
|
||||
|
||||
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",
|
||||
},
|
||||
}
|
||||
`);
|
||||
expect(migratedConnector.references).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
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 {
|
||||
"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',
|
||||
},
|
||||
},
|
||||
"fields": null,
|
||||
"name": "none",
|
||||
"type": ".none",
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
const migratedConnector = caseConnectorIdMigration(
|
||||
caseSavedObject
|
||||
) as SavedObjectSanitizedDoc<CaseAttributes>;
|
||||
it('does not create a reference when the external_service is null', () => {
|
||||
const caseSavedObject = create_7_14_0_case({ externalService: null });
|
||||
|
||||
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",
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
const migratedConnector = caseConnectorIdMigration(
|
||||
caseSavedObject
|
||||
) as SavedObjectSanitizedDoc<CaseAttributes>;
|
||||
|
||||
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,
|
||||
},
|
||||
expect(migratedConnector.references.length).toBe(0);
|
||||
expect(migratedConnector.attributes.external_service).toBeNull();
|
||||
});
|
||||
|
||||
const migratedConnector = caseConnectorIdMigration(
|
||||
caseSavedObject
|
||||
) as SavedObjectSanitizedDoc<CaseAttributes>;
|
||||
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();
|
||||
|
||||
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 [
|
||||
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 {
|
||||
"id": "123",
|
||||
"name": "connectorId",
|
||||
"type": "action",
|
||||
"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,
|
||||
},
|
||||
});
|
||||
|
||||
const migratedConnector = caseConnectorIdMigration(
|
||||
caseSavedObject
|
||||
) as SavedObjectSanitizedDoc<CaseAttributes>;
|
||||
|
||||
expect(migratedConnector.references.length).toBe(1);
|
||||
expect(migratedConnector.attributes.connector).not.toHaveProperty('id');
|
||||
expect(migratedConnector.attributes.connector).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"id": "100",
|
||||
"name": "pushConnectorId",
|
||||
"type": "action",
|
||||
"fields": null,
|
||||
"name": "connector",
|
||||
"type": ".jira",
|
||||
}
|
||||
`);
|
||||
expect(migratedConnector.references).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"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',
|
||||
},
|
||||
},
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
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",
|
||||
},
|
||||
}
|
||||
`);
|
||||
expect(migratedConnector.references).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"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',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
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",
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
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",
|
||||
},
|
||||
]
|
||||
`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -14,7 +14,11 @@ import {
|
|||
} from '../../../../../../src/core/server';
|
||||
import { ESConnectorFields } from '../../services';
|
||||
import { ConnectorTypes, CaseType } from '../../../common';
|
||||
import { transformConnectorIdToReference, transformPushConnectorIdToReference } from './utils';
|
||||
import {
|
||||
transformConnectorIdToReference,
|
||||
transformPushConnectorIdToReference,
|
||||
} from '../../services/user_actions/transform';
|
||||
import { CONNECTOR_ID_REFERENCE_NAME, PUSH_CONNECTOR_ID_REFERENCE_NAME } from '../../common';
|
||||
|
||||
interface UnsanitizedCaseConnector {
|
||||
connector_id: string;
|
||||
|
@ -50,11 +54,13 @@ 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);
|
||||
const { transformedConnector, references: connectorReferences } = transformConnectorIdToReference(
|
||||
CONNECTOR_ID_REFERENCE_NAME,
|
||||
connector
|
||||
);
|
||||
|
||||
const { transformedPushConnector, references: pushConnectorReferences } =
|
||||
transformPushConnectorIdToReference(external_service);
|
||||
transformPushConnectorIdToReference(PUSH_CONNECTOR_ID_REFERENCE_NAME, external_service);
|
||||
|
||||
const { references = [] } = doc;
|
||||
|
||||
|
|
|
@ -40,87 +40,89 @@ const create_7_14_0_configSchema = (connector?: ESCaseConnectorWithId) => ({
|
|||
},
|
||||
});
|
||||
|
||||
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('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());
|
||||
|
||||
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');
|
||||
});
|
||||
|
||||
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,
|
||||
expect(migratedConnector.references.length).toBe(0);
|
||||
expect(migratedConnector.attributes.connector).not.toHaveProperty('id');
|
||||
});
|
||||
|
||||
const migratedConnector = configureConnectorIdMigration(
|
||||
configureSavedObject
|
||||
) as SavedObjectSanitizedDoc<ESCasesConfigureAttributes>;
|
||||
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();
|
||||
|
||||
expect(migratedConnector.references).toEqual([
|
||||
{ id: '123', type: ACTION_SAVED_OBJECT_TYPE, name: CONNECTOR_ID_REFERENCE_NAME },
|
||||
]);
|
||||
expect(migratedConnector.attributes.connector).not.toHaveProperty('id');
|
||||
});
|
||||
const migratedConnector = configureConnectorIdMigration(
|
||||
configureSavedObject
|
||||
) as SavedObjectSanitizedDoc<ESCasesConfigureAttributes>;
|
||||
|
||||
it('returns the other attributes and default connector when the connector is undefined', () => {
|
||||
const configureSavedObject = create_7_14_0_configSchema();
|
||||
expect(migratedConnector.references.length).toBe(0);
|
||||
expect(migratedConnector.attributes.connector).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"fields": null,
|
||||
"name": "none",
|
||||
"type": ".none",
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
const migratedConnector = configureConnectorIdMigration(
|
||||
configureSavedObject
|
||||
) as SavedObjectSanitizedDoc<ESCasesConfigureAttributes>;
|
||||
it('creates a reference using the connector id', () => {
|
||||
const configureSavedObject = create_7_14_0_configSchema({
|
||||
id: '123',
|
||||
fields: null,
|
||||
name: 'connector',
|
||||
type: ConnectorTypes.jira,
|
||||
});
|
||||
|
||||
expect(migratedConnector).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"attributes": Object {
|
||||
"closure_type": "close-by-pushing",
|
||||
"connector": Object {
|
||||
"fields": null,
|
||||
"name": "none",
|
||||
"type": ".none",
|
||||
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",
|
||||
},
|
||||
},
|
||||
"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",
|
||||
}
|
||||
`);
|
||||
"id": "1",
|
||||
"references": Array [],
|
||||
"type": "cases-configure",
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -13,7 +13,8 @@ import {
|
|||
} from '../../../../../../src/core/server';
|
||||
import { ConnectorTypes } from '../../../common';
|
||||
import { addOwnerToSO, SanitizedCaseOwner } from '.';
|
||||
import { transformConnectorIdToReference } from './utils';
|
||||
import { transformConnectorIdToReference } from '../../services/user_actions/transform';
|
||||
import { CONNECTOR_ID_REFERENCE_NAME } from '../../common';
|
||||
|
||||
interface UnsanitizedConfigureConnector {
|
||||
connector_id: string;
|
||||
|
@ -34,8 +35,10 @@ 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);
|
||||
const { transformedConnector, references: connectorReferences } = transformConnectorIdToReference(
|
||||
CONNECTOR_ID_REFERENCE_NAME,
|
||||
connector
|
||||
);
|
||||
const { references = [] } = doc;
|
||||
|
||||
return {
|
||||
|
|
|
@ -5,24 +5,17 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
|
||||
import {
|
||||
SavedObjectUnsanitizedDoc,
|
||||
SavedObjectSanitizedDoc,
|
||||
} from '../../../../../../src/core/server';
|
||||
import { ConnectorTypes, SECURITY_SOLUTION_OWNER } from '../../../common';
|
||||
import { 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;
|
||||
}
|
||||
|
@ -38,52 +31,6 @@ 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>>
|
||||
|
|
|
@ -0,0 +1,562 @@
|
|||
/*
|
||||
* 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();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,159 @@
|
|||
/*
|
||||
* 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,
|
||||
};
|
|
@ -1,229 +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 { 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",
|
||||
},
|
||||
]
|
||||
`);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,73 +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 { 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,
|
||||
};
|
||||
};
|
|
@ -40,6 +40,7 @@ import {
|
|||
createSavedObjectReferences,
|
||||
createCaseSavedObjectResponse,
|
||||
basicCaseFields,
|
||||
createSOFindResponse,
|
||||
} from '../test_utils';
|
||||
import { ESCaseAttributes } from './types';
|
||||
|
||||
|
@ -87,13 +88,6 @@ 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
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { SavedObject, SavedObjectReference } from 'kibana/server';
|
||||
import { SavedObject, SavedObjectReference, SavedObjectsFindResult } 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,3 +198,14 @@ 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,
|
||||
});
|
||||
|
|
|
@ -0,0 +1,332 @@
|
|||
/*
|
||||
* 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",
|
||||
},
|
||||
],
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { SavedObject, SavedObjectsUpdateResponse } from 'kibana/server';
|
||||
import { SavedObject, SavedObjectReference, SavedObjectsUpdateResponse } from 'kibana/server';
|
||||
import { get, isPlainObject, isString } from 'lodash';
|
||||
import deepEqual from 'fast-deep-equal';
|
||||
|
||||
|
@ -23,8 +23,68 @@ 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';
|
||||
|
||||
export const transformNewUserAction = ({
|
||||
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 = ({
|
||||
actionField,
|
||||
action,
|
||||
actionAt,
|
||||
|
@ -55,103 +115,43 @@ export const transformNewUserAction = ({
|
|||
owner,
|
||||
});
|
||||
|
||||
interface BuildCaseUserAction {
|
||||
action: UserAction;
|
||||
actionAt: string;
|
||||
actionBy: User;
|
||||
caseId: string;
|
||||
owner: string;
|
||||
fields: UserActionField | unknown[];
|
||||
newValue?: string | unknown;
|
||||
oldValue?: string | unknown;
|
||||
subCaseId?: string;
|
||||
}
|
||||
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 BuildCommentUserActionItem extends BuildCaseUserAction {
|
||||
interface BuildCommentUserActionItem extends BuildCaseUserActionParams {
|
||||
commentId: string;
|
||||
}
|
||||
|
||||
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}`,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
],
|
||||
});
|
||||
export const buildCommentUserActionItem = (params: BuildCommentUserActionItem): UserActionItem => {
|
||||
const { commentId } = params;
|
||||
const { attributes, references } = buildCaseUserActionItem(params);
|
||||
|
||||
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,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
],
|
||||
});
|
||||
return {
|
||||
attributes,
|
||||
references: [
|
||||
...references,
|
||||
{
|
||||
type: CASE_COMMENT_SAVED_OBJECT,
|
||||
name: COMMENT_REF_NAME,
|
||||
id: commentId,
|
||||
},
|
||||
],
|
||||
};
|
||||
};
|
||||
|
||||
const userActionFieldsAllowed: UserActionField = [
|
||||
'comment',
|
||||
|
@ -278,8 +278,8 @@ const buildGenericCaseUserActions = <T extends OwnerEntity>({
|
|||
caseId,
|
||||
subCaseId,
|
||||
fields: [field],
|
||||
newValue: JSON.stringify(updatedValue),
|
||||
oldValue: JSON.stringify(origValue),
|
||||
newValue: updatedValue,
|
||||
oldValue: origValue,
|
||||
owner: originalItem.attributes.owner,
|
||||
}),
|
||||
];
|
||||
|
|
557
x-pack/plugins/cases/server/services/user_actions/index.test.ts
Normal file
557
x-pack/plugins/cases/server/services/user_actions/index.test.ts
Normal file
|
@ -0,0 +1,557 @@
|
|||
/*
|
||||
* 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');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -5,7 +5,12 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { Logger, SavedObjectReference } from 'kibana/server';
|
||||
import {
|
||||
Logger,
|
||||
SavedObjectReference,
|
||||
SavedObjectsFindResponse,
|
||||
SavedObjectsFindResult,
|
||||
} from 'kibana/server';
|
||||
|
||||
import {
|
||||
CASE_SAVED_OBJECT,
|
||||
|
@ -13,8 +18,17 @@ 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;
|
||||
|
@ -33,12 +47,16 @@ interface PostCaseUserActionArgs extends ClientArgs {
|
|||
export class CaseUserActionService {
|
||||
constructor(private readonly log: Logger) {}
|
||||
|
||||
public async getAll({ unsecuredSavedObjectsClient, caseId, subCaseId }: GetCaseUserActionArgs) {
|
||||
public async getAll({
|
||||
unsecuredSavedObjectsClient,
|
||||
caseId,
|
||||
subCaseId,
|
||||
}: GetCaseUserActionArgs): Promise<SavedObjectsFindResponse<CaseUserActionResponse>> {
|
||||
try {
|
||||
const id = subCaseId ?? caseId;
|
||||
const type = subCaseId ? SUB_CASE_SAVED_OBJECT : CASE_SAVED_OBJECT;
|
||||
|
||||
return await unsecuredSavedObjectsClient.find<CaseUserActionAttributes>({
|
||||
const userActions = await unsecuredSavedObjectsClient.find<CaseUserActionAttributes>({
|
||||
type: CASE_USER_ACTION_SAVED_OBJECT,
|
||||
hasReference: { type, id },
|
||||
page: 1,
|
||||
|
@ -46,17 +64,22 @@ 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) {
|
||||
public async bulkCreate({
|
||||
unsecuredSavedObjectsClient,
|
||||
actions,
|
||||
}: PostCaseUserActionArgs): Promise<void> {
|
||||
try {
|
||||
this.log.debug(`Attempting to POST a new case user action`);
|
||||
|
||||
return await unsecuredSavedObjectsClient.bulkCreate<CaseUserActionAttributes>(
|
||||
await unsecuredSavedObjectsClient.bulkCreate<CaseUserActionAttributes>(
|
||||
actions.map((action) => ({ type: CASE_USER_ACTION_SAVED_OBJECT, ...action }))
|
||||
);
|
||||
} catch (error) {
|
||||
|
@ -65,3 +88,71 @@ 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;
|
||||
}
|
||||
|
|
1246
x-pack/plugins/cases/server/services/user_actions/transform.test.ts
Normal file
1246
x-pack/plugins/cases/server/services/user_actions/transform.test.ts
Normal file
File diff suppressed because it is too large
Load diff
320
x-pack/plugins/cases/server/services/user_actions/transform.ts
Normal file
320
x-pack/plugins/cases/server/services/user_actions/transform.ts
Normal file
|
@ -0,0 +1,320 @@
|
|||
/*
|
||||
* 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,
|
||||
};
|
||||
};
|
14
x-pack/plugins/cases/server/services/user_actions/types.ts
Normal file
14
x-pack/plugins/cases/server/services/user_actions/types.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
/*
|
||||
* 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',
|
||||
}
|
|
@ -85,7 +85,7 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
});
|
||||
});
|
||||
|
||||
it('should create a user action when creating a case', async () => {
|
||||
it('should create a user action when deleting a case', async () => {
|
||||
const postedCase = await createCase(supertest, getPostCaseRequest());
|
||||
await deleteCases({ supertest, caseIDs: [postedCase.id] });
|
||||
const userActions = await getCaseUserActions({ supertest, caseID: postedCase.id });
|
||||
|
@ -106,6 +106,8 @@ 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: '',
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
import expect from '@kbn/expect';
|
||||
import { join } from 'path';
|
||||
import { SavedObject } from 'kibana/server';
|
||||
import supertest from 'supertest';
|
||||
import { ObjectRemover as ActionsRemover } from '../../../../../alerting_api_integration/common/lib';
|
||||
import {
|
||||
deleteAllCaseItems,
|
||||
|
@ -29,15 +30,16 @@ import {
|
|||
CaseUserActionAttributes,
|
||||
CASE_COMMENT_SAVED_OBJECT,
|
||||
CasePostRequest,
|
||||
CaseUserActionResponse,
|
||||
} from '../../../../../../plugins/cases/common';
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default ({ getService }: FtrProviderContext): void => {
|
||||
const supertest = getService('supertest');
|
||||
const supertestService = getService('supertest');
|
||||
const es = getService('es');
|
||||
|
||||
describe('import and export cases', () => {
|
||||
const actionsRemover = new ActionsRemover(supertest);
|
||||
const actionsRemover = new ActionsRemover(supertestService);
|
||||
|
||||
afterEach(async () => {
|
||||
await deleteAllCaseItems(es);
|
||||
|
@ -46,14 +48,14 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
|
||||
it('exports a case with its associated user actions and comments', async () => {
|
||||
const caseRequest = getPostCaseRequest();
|
||||
const postedCase = await createCase(supertest, caseRequest);
|
||||
const postedCase = await createCase(supertestService, caseRequest);
|
||||
await createComment({
|
||||
supertest,
|
||||
supertest: supertestService,
|
||||
caseId: postedCase.id,
|
||||
params: postCommentUserReq,
|
||||
});
|
||||
|
||||
const { text } = await supertest
|
||||
const { text } = await supertestService
|
||||
.post(`/api/saved_objects/_export`)
|
||||
.send({
|
||||
type: ['cases'],
|
||||
|
@ -72,7 +74,7 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
});
|
||||
|
||||
it('imports a case with a comment and user actions', async () => {
|
||||
await supertest
|
||||
await supertestService
|
||||
.post('/api/saved_objects/_import')
|
||||
.query({ overwrite: true })
|
||||
.attach(
|
||||
|
@ -85,12 +87,12 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
.set('kbn-xsrf', 'true')
|
||||
.expect(200);
|
||||
|
||||
const findResponse = await findCases({ supertest, query: {} });
|
||||
const findResponse = await findCases({ supertest: supertestService, query: {} });
|
||||
expect(findResponse.total).to.eql(1);
|
||||
expect(findResponse.cases[0].title).to.eql('A case to export');
|
||||
expect(findResponse.cases[0].description).to.eql('a description');
|
||||
|
||||
const { body: commentsResponse }: { body: CommentsResponse } = await supertest
|
||||
const { body: commentsResponse }: { body: CommentsResponse } = await supertestService
|
||||
.get(`${CASES_URL}/${findResponse.cases[0].id}/comments/_find`)
|
||||
.send()
|
||||
.expect(200);
|
||||
|
@ -99,7 +101,7 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
expect(comment.comment).to.eql('A comment for my case');
|
||||
|
||||
const userActions = await getCaseUserActions({
|
||||
supertest,
|
||||
supertest: supertestService,
|
||||
caseID: findResponse.cases[0].id,
|
||||
});
|
||||
|
||||
|
@ -118,7 +120,7 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
});
|
||||
|
||||
it('imports a case with a connector', async () => {
|
||||
await supertest
|
||||
await supertestService
|
||||
.post('/api/saved_objects/_import')
|
||||
.query({ overwrite: true })
|
||||
.attach(
|
||||
|
@ -133,37 +135,58 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
|
||||
actionsRemover.add('default', '1cd34740-06ad-11ec-babc-0b08808e8e01', 'action', 'actions');
|
||||
|
||||
const findResponse = await findCases({ supertest, query: {} });
|
||||
expect(findResponse.total).to.eql(1);
|
||||
expect(findResponse.cases[0].title).to.eql('A case with a connector');
|
||||
expect(findResponse.cases[0].description).to.eql('super description');
|
||||
await expectImportToHaveOneCase(supertestService);
|
||||
|
||||
const userActions = await getCaseUserActions({
|
||||
supertest,
|
||||
caseID: findResponse.cases[0].id,
|
||||
supertest: supertestService,
|
||||
caseID: '2e85c3f0-06ad-11ec-babc-0b08808e8e01',
|
||||
});
|
||||
|
||||
expect(userActions).to.have.length(3);
|
||||
expect(userActions[0].action).to.eql('create');
|
||||
expect(includesAllCreateCaseActionFields(userActions[0].action_field)).to.eql(true);
|
||||
|
||||
expect(userActions[1].action).to.eql('push-to-service');
|
||||
expect(userActions[1].action_field).to.eql(['pushed']);
|
||||
expect(userActions[1].old_value).to.eql(null);
|
||||
|
||||
const parsedPushNewValue = JSON.parse(userActions[1].new_value!);
|
||||
expect(parsedPushNewValue.connector_name).to.eql('A jira connector');
|
||||
expect(parsedPushNewValue.connector_id).to.eql('1cd34740-06ad-11ec-babc-0b08808e8e01');
|
||||
|
||||
expect(userActions[2].action).to.eql('update');
|
||||
expect(userActions[2].action_field).to.eql(['connector']);
|
||||
|
||||
const parsedUpdateNewValue = JSON.parse(userActions[2].new_value!);
|
||||
expect(parsedUpdateNewValue.id).to.eql('none');
|
||||
expectImportToHaveCreateCaseUserAction(userActions[0]);
|
||||
expectImportToHavePushUserAction(userActions[1]);
|
||||
expectImportToHaveUpdateConnector(userActions[2]);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const expectImportToHaveOneCase = async (supertestService: supertest.SuperTest<supertest.Test>) => {
|
||||
const findResponse = await findCases({ supertest: supertestService, query: {} });
|
||||
expect(findResponse.total).to.eql(1);
|
||||
expect(findResponse.cases[0].title).to.eql('A case with a connector');
|
||||
expect(findResponse.cases[0].description).to.eql('super description');
|
||||
};
|
||||
|
||||
const expectImportToHaveCreateCaseUserAction = (userAction: CaseUserActionResponse) => {
|
||||
expect(userAction.action).to.eql('create');
|
||||
expect(includesAllCreateCaseActionFields(userAction.action_field)).to.eql(true);
|
||||
};
|
||||
|
||||
const expectImportToHavePushUserAction = (userAction: CaseUserActionResponse) => {
|
||||
expect(userAction.action).to.eql('push-to-service');
|
||||
expect(userAction.action_field).to.eql(['pushed']);
|
||||
expect(userAction.old_value).to.eql(null);
|
||||
|
||||
const parsedPushNewValue = JSON.parse(userAction.new_value!);
|
||||
expect(parsedPushNewValue.connector_name).to.eql('A jira connector');
|
||||
expect(parsedPushNewValue).to.not.have.property('connector_id');
|
||||
expect(userAction.new_val_connector_id).to.eql('1cd34740-06ad-11ec-babc-0b08808e8e01');
|
||||
};
|
||||
|
||||
const expectImportToHaveUpdateConnector = (userAction: CaseUserActionResponse) => {
|
||||
expect(userAction.action).to.eql('update');
|
||||
expect(userAction.action_field).to.eql(['connector']);
|
||||
|
||||
const parsedUpdateNewValue = JSON.parse(userAction.new_value!);
|
||||
expect(parsedUpdateNewValue).to.not.have.property('id');
|
||||
// the new val connector id is null because it is the none connector
|
||||
expect(userAction.new_val_connector_id).to.eql(null);
|
||||
|
||||
const parsedUpdateOldValue = JSON.parse(userAction.old_value!);
|
||||
expect(parsedUpdateOldValue).to.not.have.property('id');
|
||||
expect(userAction.old_val_connector_id).to.eql('1cd34740-06ad-11ec-babc-0b08808e8e01');
|
||||
};
|
||||
|
||||
const ndjsonToObject = (input: string) => {
|
||||
return input.split('\n').map((str) => JSON.parse(str));
|
||||
};
|
||||
|
|
|
@ -126,6 +126,8 @@ 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,
|
||||
|
@ -165,6 +167,8 @@ 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: '',
|
||||
|
|
|
@ -114,6 +114,8 @@ 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',
|
||||
|
@ -127,6 +129,9 @@ 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: '',
|
||||
|
@ -138,7 +143,7 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
description: postedCase.description,
|
||||
title: postedCase.title,
|
||||
tags: postedCase.tags,
|
||||
connector: postedCase.connector,
|
||||
connector: restCaseConnector,
|
||||
settings: postedCase.settings,
|
||||
owner: postedCase.owner,
|
||||
});
|
||||
|
|
|
@ -148,7 +148,9 @@ 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: '',
|
||||
|
|
|
@ -48,6 +48,15 @@ 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')
|
||||
|
@ -73,7 +82,10 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
]);
|
||||
expect(body[0].action).to.eql('create');
|
||||
expect(body[0].old_value).to.eql(null);
|
||||
expect(JSON.parse(body[0].new_value)).to.eql(userActionPostResp);
|
||||
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);
|
||||
});
|
||||
|
||||
it(`on close case, user action: 'update' should be called with actionFields: ['status']`, async () => {
|
||||
|
@ -147,18 +159,19 @@ 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 () => {
|
||||
|
|
|
@ -12,6 +12,10 @@ 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) {
|
||||
|
@ -41,14 +45,18 @@ 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,
|
||||
|
@ -77,5 +85,142 @@ 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);
|
||||
}
|
||||
|
|
|
@ -275,6 +275,8 @@ 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: '',
|
||||
|
@ -284,7 +286,6 @@ 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',
|
||||
|
|
|
@ -108,8 +108,10 @@ 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.connector_id).to.eql(configure.connector.id);
|
||||
expect(newValue).to.not.have.property('connector_id');
|
||||
expect(newValue.pushed_by).to.eql(defaultUser);
|
||||
});
|
||||
});
|
||||
|
|
Binary file not shown.
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue