mirror of
https://github.com/elastic/kibana.git
synced 2025-04-25 02:09:32 -04:00
[Cases] Migrate user actions connector ID (#108272)
* 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 Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
6f2815a764
commit
10ac814d8f
57 changed files with 7905 additions and 1150 deletions
|
@ -87,8 +87,11 @@ const CaseBasicRt = rt.type({
|
||||||
owner: rt.string,
|
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,
|
connector_name: rt.string,
|
||||||
external_id: rt.string,
|
external_id: rt.string,
|
||||||
external_title: rt.string,
|
external_title: rt.string,
|
||||||
|
@ -97,7 +100,14 @@ export const CaseExternalServiceBasicRt = rt.type({
|
||||||
pushed_by: UserRT,
|
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([
|
export const CaseAttributesRt = rt.intersection([
|
||||||
CaseBasicRt,
|
CaseBasicRt,
|
||||||
|
|
|
@ -34,7 +34,6 @@ const UserActionRt = rt.union([
|
||||||
rt.literal('push-to-service'),
|
rt.literal('push-to-service'),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// TO DO change state to status
|
|
||||||
const CaseUserActionBasicRT = rt.type({
|
const CaseUserActionBasicRT = rt.type({
|
||||||
action_field: UserActionFieldRt,
|
action_field: UserActionFieldRt,
|
||||||
action: UserActionRt,
|
action: UserActionRt,
|
||||||
|
@ -51,6 +50,8 @@ const CaseUserActionResponseRT = rt.intersection([
|
||||||
action_id: rt.string,
|
action_id: rt.string,
|
||||||
case_id: rt.string,
|
case_id: rt.string,
|
||||||
comment_id: rt.union([rt.string, rt.null]),
|
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 }),
|
rt.partial({ sub_case_id: rt.string }),
|
||||||
]);
|
]);
|
||||||
|
|
|
@ -84,14 +84,22 @@ export const ConnectorTypeFieldsRt = rt.union([
|
||||||
ConnectorSwimlaneTypeFieldsRt,
|
ConnectorSwimlaneTypeFieldsRt,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export const CaseConnectorRt = rt.intersection([
|
/**
|
||||||
rt.type({
|
* This type represents the connector's format when it is encoded within a user action.
|
||||||
id: rt.string,
|
*/
|
||||||
name: rt.string,
|
export const CaseUserActionConnectorRt = rt.intersection([
|
||||||
}),
|
rt.type({ name: rt.string }),
|
||||||
ConnectorTypeFieldsRt,
|
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 CaseConnector = rt.TypeOf<typeof CaseConnectorRt>;
|
||||||
export type ConnectorTypeFields = rt.TypeOf<typeof ConnectorTypeFieldsRt>;
|
export type ConnectorTypeFields = rt.TypeOf<typeof ConnectorTypeFieldsRt>;
|
||||||
export type ConnectorJiraTypeFields = rt.TypeOf<typeof ConnectorJiraTypeFieldsRt>;
|
export type ConnectorJiraTypeFields = rt.TypeOf<typeof ConnectorJiraTypeFieldsRt>;
|
||||||
|
|
|
@ -12,3 +12,4 @@ export * from './constants';
|
||||||
export * from './api';
|
export * from './api';
|
||||||
export * from './ui/types';
|
export * from './ui/types';
|
||||||
export * from './utils/connectors_api';
|
export * from './utils/connectors_api';
|
||||||
|
export * from './utils/user_actions';
|
||||||
|
|
|
@ -66,7 +66,9 @@ export interface CaseUserActions {
|
||||||
caseId: string;
|
caseId: string;
|
||||||
commentId: string | null;
|
commentId: string | null;
|
||||||
newValue: string | null;
|
newValue: string | null;
|
||||||
|
newValConnectorId: string | null;
|
||||||
oldValue: string | null;
|
oldValue: string | null;
|
||||||
|
oldValConnectorId: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CaseExternalService {
|
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.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { ConnectorTypeFields } from '../../../common';
|
||||||
import { CaseUserActions } from '../../containers/types';
|
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 {
|
try {
|
||||||
for (const action of [...userActions].reverse()) {
|
for (const action of [...userActions].reverse()) {
|
||||||
if (action.actionField.length === 1 && action.actionField[0] === 'connector') {
|
if (action.actionField.length === 1 && action.actionField[0] === 'connector') {
|
||||||
if (action.oldValue && action.newValue) {
|
const parsedNewConnector = parseStringAsConnector(
|
||||||
const oldValue = JSON.parse(action.oldValue);
|
action.newValConnectorId,
|
||||||
const newValue = JSON.parse(action.newValue);
|
action.newValue
|
||||||
|
);
|
||||||
|
|
||||||
if (newValue.id === id) {
|
if (parsedNewConnector && id === parsedNewConnector.id) {
|
||||||
return newValue.fields;
|
return parsedNewConnector.fields;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (oldValue.id === id) {
|
const parsedOldConnector = parseStringAsConnector(
|
||||||
return oldValue.fields;
|
action.oldValConnectorId,
|
||||||
}
|
action.oldValue
|
||||||
|
);
|
||||||
|
|
||||||
|
if (parsedOldConnector && id === parsedOldConnector.id) {
|
||||||
|
return parsedOldConnector.fields;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { mount } from 'enzyme';
|
import { mount } from 'enzyme';
|
||||||
|
|
||||||
import { CaseStatuses } from '../../../common';
|
import { CaseStatuses, ConnectorTypes } from '../../../common';
|
||||||
import { basicPush, getUserAction } from '../../containers/mock';
|
import { basicPush, getUserAction } from '../../containers/mock';
|
||||||
import {
|
import {
|
||||||
getLabelTitle,
|
getLabelTitle,
|
||||||
|
@ -129,7 +129,7 @@ describe('User action tree helpers', () => {
|
||||||
`${i18n.PUSHED_NEW_INCIDENT} ${basicPush.connectorName}`
|
`${i18n.PUSHED_NEW_INCIDENT} ${basicPush.connectorName}`
|
||||||
);
|
);
|
||||||
expect(wrapper.find(`[data-test-subj="pushed-value"]`).first().prop('href')).toEqual(
|
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}`
|
`${i18n.UPDATE_INCIDENT} ${basicPush.connectorName}`
|
||||||
);
|
);
|
||||||
expect(wrapper.find(`[data-test-subj="pushed-value"]`).first().prop('href')).toEqual(
|
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', () => {
|
describe('getConnectorLabelTitle', () => {
|
||||||
const action = {
|
it('returns an empty string when the encoded old value is null', () => {
|
||||||
...getUserAction(['connector'], 'update'),
|
const result = getConnectorLabelTitle({
|
||||||
oldValue: JSON.stringify({ id: 'servicenow-1' }),
|
action: getUserAction(['connector'], 'update', { oldValue: null }),
|
||||||
newValue: JSON.stringify({ id: 'resilient-2' }),
|
connectors,
|
||||||
};
|
});
|
||||||
const result: string | JSX.Element = getConnectorLabelTitle({
|
|
||||||
action,
|
expect(result).toEqual('');
|
||||||
connectors,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
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', () => {
|
expect(result).toEqual('');
|
||||||
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('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', () => {
|
expect(result).toEqual('selected My Connector 2 as incident management system');
|
||||||
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('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', () => {
|
describe('toStringArray', () => {
|
||||||
|
|
|
@ -23,10 +23,11 @@ import {
|
||||||
CommentType,
|
CommentType,
|
||||||
Comment,
|
Comment,
|
||||||
CommentRequestActionsType,
|
CommentRequestActionsType,
|
||||||
|
noneConnectorId,
|
||||||
} from '../../../common';
|
} from '../../../common';
|
||||||
import { CaseUserActions } from '../../containers/types';
|
import { CaseUserActions } from '../../containers/types';
|
||||||
import { CaseServices } from '../../containers/use_get_case_user_actions';
|
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 { Tags } from '../tag_list/tags';
|
||||||
import { UserActionUsernameWithAvatar } from './user_action_username_with_avatar';
|
import { UserActionUsernameWithAvatar } from './user_action_username_with_avatar';
|
||||||
import { UserActionTimestamp } from './user_action_timestamp';
|
import { UserActionTimestamp } from './user_action_timestamp';
|
||||||
|
@ -97,23 +98,27 @@ export const getConnectorLabelTitle = ({
|
||||||
action: CaseUserActions;
|
action: CaseUserActions;
|
||||||
connectors: ActionConnector[];
|
connectors: ActionConnector[];
|
||||||
}) => {
|
}) => {
|
||||||
const oldValue = parseString(`${action.oldValue}`);
|
const oldConnector = parseStringAsConnector(action.oldValConnectorId, action.oldValue);
|
||||||
const newValue = parseString(`${action.newValue}`);
|
const newConnector = parseStringAsConnector(action.newValConnectorId, action.newValue);
|
||||||
|
|
||||||
if (oldValue === null || newValue === null) {
|
if (!oldConnector || !newConnector) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Connector changed
|
// if the ids are the same, assume we just changed the fields
|
||||||
if (oldValue.id !== newValue.id) {
|
if (oldConnector.id === newConnector.id) {
|
||||||
const newConnector = connectors.find((c) => c.id === newValue.id);
|
|
||||||
return newValue.id != null && newValue.id !== 'none' && newConnector != null
|
|
||||||
? i18n.SELECTED_THIRD_PARTY(newConnector.name)
|
|
||||||
: i18n.REMOVED_THIRD_PARTY;
|
|
||||||
} else {
|
|
||||||
// Field changed
|
|
||||||
return i18n.CHANGED_CONNECTOR_FIELD;
|
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) => {
|
const getTagsLabelTitle = (action: CaseUserActions) => {
|
||||||
|
@ -133,7 +138,8 @@ const getTagsLabelTitle = (action: CaseUserActions) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getPushedServiceLabelTitle = (action: CaseUserActions, firstPush: boolean) => {
|
export const getPushedServiceLabelTitle = (action: CaseUserActions, firstPush: boolean) => {
|
||||||
const pushedVal = JSON.parse(action.newValue ?? '') as CaseFullExternalService;
|
const externalService = parseStringAsExternalService(action.newValConnectorId, action.newValue);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EuiFlexGroup
|
<EuiFlexGroup
|
||||||
alignItems="baseline"
|
alignItems="baseline"
|
||||||
|
@ -143,12 +149,12 @@ export const getPushedServiceLabelTitle = (action: CaseUserActions, firstPush: b
|
||||||
>
|
>
|
||||||
<EuiFlexItem data-test-subj="pushed-label">
|
<EuiFlexItem data-test-subj="pushed-label">
|
||||||
{`${firstPush ? i18n.PUSHED_NEW_INCIDENT : i18n.UPDATE_INCIDENT} ${
|
{`${firstPush ? i18n.PUSHED_NEW_INCIDENT : i18n.UPDATE_INCIDENT} ${
|
||||||
pushedVal?.connector_name
|
externalService?.connector_name
|
||||||
}`}
|
}`}
|
||||||
</EuiFlexItem>
|
</EuiFlexItem>
|
||||||
<EuiFlexItem grow={false}>
|
<EuiFlexItem grow={false}>
|
||||||
<EuiLink data-test-subj="pushed-value" href={pushedVal?.external_url} target="_blank">
|
<EuiLink data-test-subj="pushed-value" href={externalService?.external_url} target="_blank">
|
||||||
{pushedVal?.external_title}
|
{externalService?.external_title}
|
||||||
</EuiLink>
|
</EuiLink>
|
||||||
</EuiFlexItem>
|
</EuiFlexItem>
|
||||||
</EuiFlexGroup>
|
</EuiFlexGroup>
|
||||||
|
@ -157,20 +163,19 @@ export const getPushedServiceLabelTitle = (action: CaseUserActions, firstPush: b
|
||||||
|
|
||||||
export const getPushInfo = (
|
export const getPushInfo = (
|
||||||
caseServices: CaseServices,
|
caseServices: CaseServices,
|
||||||
// a JSON parse failure will result in null for parsedValue
|
externalService: CaseFullExternalService | undefined,
|
||||||
parsedValue: { connector_id: string | null; connector_name: string } | null,
|
|
||||||
index: number
|
index: number
|
||||||
) =>
|
) =>
|
||||||
parsedValue != null && parsedValue.connector_id != null
|
externalService != null && externalService.connector_id != null
|
||||||
? {
|
? {
|
||||||
firstPush: caseServices[parsedValue.connector_id]?.firstPushIndex === index,
|
firstPush: caseServices[externalService.connector_id]?.firstPushIndex === index,
|
||||||
parsedConnectorId: parsedValue.connector_id,
|
parsedConnectorId: externalService.connector_id,
|
||||||
parsedConnectorName: parsedValue.connector_name,
|
parsedConnectorName: externalService.connector_name,
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
firstPush: false,
|
firstPush: false,
|
||||||
parsedConnectorId: 'none',
|
parsedConnectorId: noneConnectorId,
|
||||||
parsedConnectorName: 'none',
|
parsedConnectorName: noneConnectorId,
|
||||||
};
|
};
|
||||||
|
|
||||||
const getUpdateActionIcon = (actionField: string): string => {
|
const getUpdateActionIcon = (actionField: string): string => {
|
||||||
|
|
|
@ -35,7 +35,7 @@ import {
|
||||||
Ecs,
|
Ecs,
|
||||||
} from '../../../common';
|
} from '../../../common';
|
||||||
import { CaseServices } from '../../containers/use_get_case_user_actions';
|
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 { OnUpdateFields } from '../case_view';
|
||||||
import {
|
import {
|
||||||
getConnectorLabelTitle,
|
getConnectorLabelTitle,
|
||||||
|
@ -512,10 +512,14 @@ export const UserActionTree = React.memo(
|
||||||
|
|
||||||
// Pushed information
|
// Pushed information
|
||||||
if (action.actionField.length === 1 && action.actionField[0] === 'pushed') {
|
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(
|
const { firstPush, parsedConnectorId, parsedConnectorName } = getPushInfo(
|
||||||
caseServices,
|
caseServices,
|
||||||
parsedValue,
|
parsedExternalService,
|
||||||
index
|
index
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ import { ActionLicense, AllCases, Case, CasesStatus, CaseUserActions, Comment }
|
||||||
|
|
||||||
import {
|
import {
|
||||||
AssociationType,
|
AssociationType,
|
||||||
|
CaseUserActionConnector,
|
||||||
CaseResponse,
|
CaseResponse,
|
||||||
CasesFindResponse,
|
CasesFindResponse,
|
||||||
CasesResponse,
|
CasesResponse,
|
||||||
|
@ -19,6 +20,9 @@ import {
|
||||||
CommentResponse,
|
CommentResponse,
|
||||||
CommentType,
|
CommentType,
|
||||||
ConnectorTypes,
|
ConnectorTypes,
|
||||||
|
isCreateConnector,
|
||||||
|
isPush,
|
||||||
|
isUpdateConnector,
|
||||||
SECURITY_SOLUTION_OWNER,
|
SECURITY_SOLUTION_OWNER,
|
||||||
UserAction,
|
UserAction,
|
||||||
UserActionField,
|
UserActionField,
|
||||||
|
@ -240,7 +244,9 @@ export const pushedCase: Case = {
|
||||||
const basicAction = {
|
const basicAction = {
|
||||||
actionAt: basicCreatedAt,
|
actionAt: basicCreatedAt,
|
||||||
actionBy: elasticUser,
|
actionBy: elasticUser,
|
||||||
|
oldValConnectorId: null,
|
||||||
oldValue: null,
|
oldValue: null,
|
||||||
|
newValConnectorId: null,
|
||||||
newValue: 'what a cool value',
|
newValue: 'what a cool value',
|
||||||
caseId: basicCaseId,
|
caseId: basicCaseId,
|
||||||
commentId: null,
|
commentId: null,
|
||||||
|
@ -308,12 +314,7 @@ export const basicCaseSnake: CaseResponse = {
|
||||||
closed_at: null,
|
closed_at: null,
|
||||||
closed_by: null,
|
closed_by: null,
|
||||||
comments: [basicCommentSnake],
|
comments: [basicCommentSnake],
|
||||||
connector: {
|
connector: { id: 'none', name: 'My Connector', type: ConnectorTypes.none, fields: null },
|
||||||
id: 'none',
|
|
||||||
name: 'My Connector',
|
|
||||||
type: ConnectorTypes.none,
|
|
||||||
fields: null,
|
|
||||||
},
|
|
||||||
created_at: basicCreatedAt,
|
created_at: basicCreatedAt,
|
||||||
created_by: elasticUserSnake,
|
created_by: elasticUserSnake,
|
||||||
external_service: null,
|
external_service: null,
|
||||||
|
@ -328,8 +329,8 @@ export const casesStatusSnake: CasesStatusResponse = {
|
||||||
count_open_cases: 20,
|
count_open_cases: 20,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const pushConnectorId = '123';
|
||||||
export const pushSnake = {
|
export const pushSnake = {
|
||||||
connector_id: '123',
|
|
||||||
connector_name: 'connector name',
|
connector_name: 'connector name',
|
||||||
external_id: 'external_id',
|
external_id: 'external_id',
|
||||||
external_title: 'external title',
|
external_title: 'external title',
|
||||||
|
@ -350,7 +351,7 @@ export const pushedCaseSnake = {
|
||||||
type: ConnectorTypes.jira,
|
type: ConnectorTypes.jira,
|
||||||
fields: null,
|
fields: null,
|
||||||
},
|
},
|
||||||
external_service: basicPushSnake,
|
external_service: { ...basicPushSnake, connector_id: pushConnectorId },
|
||||||
};
|
};
|
||||||
|
|
||||||
export const reporters: string[] = ['alexis', 'kim', 'maria', 'steph'];
|
export const reporters: string[] = ['alexis', 'kim', 'maria', 'steph'];
|
||||||
|
@ -385,17 +386,20 @@ const basicActionSnake = {
|
||||||
comment_id: null,
|
comment_id: null,
|
||||||
owner: SECURITY_SOLUTION_OWNER,
|
owner: SECURITY_SOLUTION_OWNER,
|
||||||
};
|
};
|
||||||
export const getUserActionSnake = (af: UserActionField, a: UserAction) => ({
|
export const getUserActionSnake = (af: UserActionField, a: UserAction) => {
|
||||||
...basicActionSnake,
|
const isPushToService = a === 'push-to-service' && af[0] === 'pushed';
|
||||||
action_id: `${af[0]}-${a}`,
|
|
||||||
action_field: af,
|
return {
|
||||||
action: a,
|
...basicActionSnake,
|
||||||
comment_id: af[0] === 'comment' ? basicCommentId : null,
|
action_id: `${af[0]}-${a}`,
|
||||||
new_value:
|
action_field: af,
|
||||||
a === 'push-to-service' && af[0] === 'pushed'
|
action: a,
|
||||||
? JSON.stringify(basicPushSnake)
|
comment_id: af[0] === 'comment' ? basicCommentId : null,
|
||||||
: basicAction.newValue,
|
new_value: isPushToService ? JSON.stringify(basicPushSnake) : basicAction.newValue,
|
||||||
});
|
new_val_connector_id: isPushToService ? pushConnectorId : null,
|
||||||
|
old_val_connector_id: null,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export const caseUserActionsSnake: CaseUserActionsResponse = [
|
export const caseUserActionsSnake: CaseUserActionsResponse = [
|
||||||
getUserActionSnake(['description'], 'create'),
|
getUserActionSnake(['description'], 'create'),
|
||||||
|
@ -405,17 +409,76 @@ export const caseUserActionsSnake: CaseUserActionsResponse = [
|
||||||
|
|
||||||
// user actions
|
// user actions
|
||||||
|
|
||||||
export const getUserAction = (af: UserActionField, a: UserAction) => ({
|
export const getUserAction = (
|
||||||
...basicAction,
|
af: UserActionField,
|
||||||
actionId: `${af[0]}-${a}`,
|
a: UserAction,
|
||||||
actionField: af,
|
overrides?: Partial<CaseUserActions>
|
||||||
action: a,
|
): CaseUserActions => {
|
||||||
commentId: af[0] === 'comment' ? basicCommentId : null,
|
return {
|
||||||
newValue:
|
...basicAction,
|
||||||
a === 'push-to-service' && af[0] === 'pushed'
|
actionId: `${af[0]}-${a}`,
|
||||||
? JSON.stringify(basicPushSnake)
|
actionField: af,
|
||||||
: basicAction.newValue,
|
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 = () => ({
|
export const getAlertUserAction = () => ({
|
||||||
...basicAction,
|
...basicAction,
|
||||||
|
|
|
@ -18,7 +18,9 @@ import {
|
||||||
basicPushSnake,
|
basicPushSnake,
|
||||||
caseUserActions,
|
caseUserActions,
|
||||||
elasticUser,
|
elasticUser,
|
||||||
|
getJiraConnectorWithoutId,
|
||||||
getUserAction,
|
getUserAction,
|
||||||
|
jiraFields,
|
||||||
} from './mock';
|
} from './mock';
|
||||||
import * as api from './api';
|
import * as api from './api';
|
||||||
|
|
||||||
|
@ -299,15 +301,14 @@ describe('useGetCaseUserActions', () => {
|
||||||
const pushAction123 = getUserAction(['pushed'], 'push-to-service');
|
const pushAction123 = getUserAction(['pushed'], 'push-to-service');
|
||||||
const push456 = {
|
const push456 = {
|
||||||
...basicPushSnake,
|
...basicPushSnake,
|
||||||
connector_id: '456',
|
|
||||||
connector_name: 'other connector name',
|
connector_name: 'other connector name',
|
||||||
external_id: 'other_external_id',
|
external_id: 'other_external_id',
|
||||||
};
|
};
|
||||||
|
|
||||||
const pushAction456 = {
|
const pushAction456 = getUserAction(['pushed'], 'push-to-service', {
|
||||||
...getUserAction(['pushed'], 'push-to-service'),
|
|
||||||
newValue: JSON.stringify(push456),
|
newValue: JSON.stringify(push456),
|
||||||
};
|
newValConnectorId: '456',
|
||||||
|
});
|
||||||
|
|
||||||
const userActions = [
|
const userActions = [
|
||||||
...caseUserActions,
|
...caseUserActions,
|
||||||
|
@ -346,15 +347,14 @@ describe('useGetCaseUserActions', () => {
|
||||||
const pushAction123 = getUserAction(['pushed'], 'push-to-service');
|
const pushAction123 = getUserAction(['pushed'], 'push-to-service');
|
||||||
const push456 = {
|
const push456 = {
|
||||||
...basicPushSnake,
|
...basicPushSnake,
|
||||||
connector_id: '456',
|
|
||||||
connector_name: 'other connector name',
|
connector_name: 'other connector name',
|
||||||
external_id: 'other_external_id',
|
external_id: 'other_external_id',
|
||||||
};
|
};
|
||||||
|
|
||||||
const pushAction456 = {
|
const pushAction456 = getUserAction(['pushed'], 'push-to-service', {
|
||||||
...getUserAction(['pushed'], 'push-to-service'),
|
|
||||||
newValue: JSON.stringify(push456),
|
newValue: JSON.stringify(push456),
|
||||||
};
|
newValConnectorId: '456',
|
||||||
|
});
|
||||||
|
|
||||||
const userActions = [
|
const userActions = [
|
||||||
...caseUserActions,
|
...caseUserActions,
|
||||||
|
@ -392,11 +392,7 @@ describe('useGetCaseUserActions', () => {
|
||||||
const userActions = [
|
const userActions = [
|
||||||
...caseUserActions,
|
...caseUserActions,
|
||||||
getUserAction(['pushed'], 'push-to-service'),
|
getUserAction(['pushed'], 'push-to-service'),
|
||||||
{
|
createUpdateConnectorFields123HighPriorityUserAction(),
|
||||||
...getUserAction(['connector'], 'update'),
|
|
||||||
oldValue: JSON.stringify({ id: '123', fields: { issueType: '10006', priority: null } }),
|
|
||||||
newValue: JSON.stringify({ id: '123', fields: { issueType: '10006', priority: 'High' } }),
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const result = getPushedInfo(userActions, '123');
|
const result = getPushedInfo(userActions, '123');
|
||||||
|
@ -418,11 +414,7 @@ describe('useGetCaseUserActions', () => {
|
||||||
const userActions = [
|
const userActions = [
|
||||||
...caseUserActions,
|
...caseUserActions,
|
||||||
getUserAction(['pushed'], 'push-to-service'),
|
getUserAction(['pushed'], 'push-to-service'),
|
||||||
{
|
createChangeConnector123To456UserAction(),
|
||||||
...getUserAction(['connector'], 'update'),
|
|
||||||
oldValue: JSON.stringify({ id: '123', fields: { issueType: '10006', priority: null } }),
|
|
||||||
newValue: JSON.stringify({ id: '456', fields: { issueTypes: ['10'], severity: '6' } }),
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const result = getPushedInfo(userActions, '123');
|
const result = getPushedInfo(userActions, '123');
|
||||||
|
@ -444,16 +436,8 @@ describe('useGetCaseUserActions', () => {
|
||||||
const userActions = [
|
const userActions = [
|
||||||
...caseUserActions,
|
...caseUserActions,
|
||||||
getUserAction(['pushed'], 'push-to-service'),
|
getUserAction(['pushed'], 'push-to-service'),
|
||||||
{
|
createChangeConnector123To456UserAction(),
|
||||||
...getUserAction(['connector'], 'update'),
|
createChangeConnector456To123UserAction(),
|
||||||
oldValue: JSON.stringify({ id: '123', fields: { issueType: '10006', priority: null } }),
|
|
||||||
newValue: JSON.stringify({ id: '456', fields: { issueTypes: ['10'], severity: '6' } }),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
...getUserAction(['connector'], 'update'),
|
|
||||||
oldValue: JSON.stringify({ id: '456', fields: { issueTypes: ['10'], severity: '6' } }),
|
|
||||||
newValue: JSON.stringify({ id: '123', fields: { issueType: '10006', priority: null } }),
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const result = getPushedInfo(userActions, '123');
|
const result = getPushedInfo(userActions, '123');
|
||||||
|
@ -474,22 +458,10 @@ describe('useGetCaseUserActions', () => {
|
||||||
it('Change fields and connector after push - hasDataToPush: true', () => {
|
it('Change fields and connector after push - hasDataToPush: true', () => {
|
||||||
const userActions = [
|
const userActions = [
|
||||||
...caseUserActions,
|
...caseUserActions,
|
||||||
{
|
createUpdateConnectorFields123HighPriorityUserAction(),
|
||||||
...getUserAction(['connector'], 'update'),
|
|
||||||
oldValue: JSON.stringify({ id: '123', fields: { issueType: '10006', priority: null } }),
|
|
||||||
newValue: JSON.stringify({ id: '123', fields: { issueType: '10006', priority: 'High' } }),
|
|
||||||
},
|
|
||||||
getUserAction(['pushed'], 'push-to-service'),
|
getUserAction(['pushed'], 'push-to-service'),
|
||||||
{
|
createChangeConnector123HighPriorityTo456UserAction(),
|
||||||
...getUserAction(['connector'], 'update'),
|
createChangeConnector456To123PriorityLowUserAction(),
|
||||||
oldValue: JSON.stringify({ id: '123', fields: { issueType: '10006', priority: 'High' } }),
|
|
||||||
newValue: JSON.stringify({ id: '456', fields: { issueTypes: ['10'], severity: '6' } }),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
...getUserAction(['connector'], 'update'),
|
|
||||||
oldValue: JSON.stringify({ id: '456', fields: { issueTypes: ['10'], severity: '6' } }),
|
|
||||||
newValue: JSON.stringify({ id: '123', fields: { issueType: '10006', priority: 'Low' } }),
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const result = getPushedInfo(userActions, '123');
|
const result = getPushedInfo(userActions, '123');
|
||||||
|
@ -510,22 +482,10 @@ describe('useGetCaseUserActions', () => {
|
||||||
it('Change only connector after push - hasDataToPush: false', () => {
|
it('Change only connector after push - hasDataToPush: false', () => {
|
||||||
const userActions = [
|
const userActions = [
|
||||||
...caseUserActions,
|
...caseUserActions,
|
||||||
{
|
createUpdateConnectorFields123HighPriorityUserAction(),
|
||||||
...getUserAction(['connector'], 'update'),
|
|
||||||
oldValue: JSON.stringify({ id: '123', fields: { issueType: '10006', priority: null } }),
|
|
||||||
newValue: JSON.stringify({ id: '123', fields: { issueType: '10006', priority: 'High' } }),
|
|
||||||
},
|
|
||||||
getUserAction(['pushed'], 'push-to-service'),
|
getUserAction(['pushed'], 'push-to-service'),
|
||||||
{
|
createChangeConnector123HighPriorityTo456UserAction(),
|
||||||
...getUserAction(['connector'], 'update'),
|
createChangeConnector456To123HighPriorityUserAction(),
|
||||||
oldValue: JSON.stringify({ id: '123', fields: { issueType: '10006', priority: 'High' } }),
|
|
||||||
newValue: JSON.stringify({ id: '456', fields: { issueTypes: ['10'], severity: '6' } }),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
...getUserAction(['connector'], 'update'),
|
|
||||||
oldValue: JSON.stringify({ id: '456', fields: { issueTypes: ['10'], severity: '6' } }),
|
|
||||||
newValue: JSON.stringify({ id: '123', fields: { issueType: '10006', priority: 'High' } }),
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const result = getPushedInfo(userActions, '123');
|
const result = getPushedInfo(userActions, '123');
|
||||||
|
@ -547,45 +507,24 @@ describe('useGetCaseUserActions', () => {
|
||||||
const pushAction123 = getUserAction(['pushed'], 'push-to-service');
|
const pushAction123 = getUserAction(['pushed'], 'push-to-service');
|
||||||
const push456 = {
|
const push456 = {
|
||||||
...basicPushSnake,
|
...basicPushSnake,
|
||||||
connector_id: '456',
|
|
||||||
connector_name: 'other connector name',
|
connector_name: 'other connector name',
|
||||||
external_id: 'other_external_id',
|
external_id: 'other_external_id',
|
||||||
};
|
};
|
||||||
|
|
||||||
const pushAction456 = {
|
const pushAction456 = getUserAction(['pushed'], 'push-to-service', {
|
||||||
...getUserAction(['pushed'], 'push-to-service'),
|
|
||||||
newValue: JSON.stringify(push456),
|
newValue: JSON.stringify(push456),
|
||||||
};
|
newValConnectorId: '456',
|
||||||
|
});
|
||||||
|
|
||||||
const userActions = [
|
const userActions = [
|
||||||
...caseUserActions,
|
...caseUserActions,
|
||||||
{
|
createUpdateConnectorFields123HighPriorityUserAction(),
|
||||||
...getUserAction(['connector'], 'update'),
|
|
||||||
oldValue: JSON.stringify({ id: '123', fields: { issueType: '10006', priority: null } }),
|
|
||||||
newValue: JSON.stringify({ id: '123', fields: { issueType: '10006', priority: 'High' } }),
|
|
||||||
},
|
|
||||||
pushAction123,
|
pushAction123,
|
||||||
{
|
createChangeConnector123HighPriorityTo456UserAction(),
|
||||||
...getUserAction(['connector'], 'update'),
|
|
||||||
oldValue: JSON.stringify({ id: '123', fields: { issueType: '10006', priority: 'High' } }),
|
|
||||||
newValue: JSON.stringify({ id: '456', fields: { issueTypes: ['10'], severity: '6' } }),
|
|
||||||
},
|
|
||||||
pushAction456,
|
pushAction456,
|
||||||
{
|
createChangeConnector456To123PriorityLowUserAction(),
|
||||||
...getUserAction(['connector'], 'update'),
|
createChangeConnector123LowPriorityTo456UserAction(),
|
||||||
oldValue: JSON.stringify({ id: '456', fields: { issueTypes: ['10'], severity: '6' } }),
|
createChangeConnector456To123PriorityLowUserAction(),
|
||||||
newValue: JSON.stringify({ id: '123', fields: { issueType: '10006', priority: 'Low' } }),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
...getUserAction(['connector'], 'update'),
|
|
||||||
oldValue: JSON.stringify({ id: '123', fields: { issueType: '10006', priority: 'Low' } }),
|
|
||||||
newValue: JSON.stringify({ id: '456', fields: { issueTypes: ['10'], severity: '6' } }),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
...getUserAction(['connector'], 'update'),
|
|
||||||
oldValue: JSON.stringify({ id: '456', fields: { issueTypes: ['10'], severity: '6' } }),
|
|
||||||
newValue: JSON.stringify({ id: '123', fields: { issueType: '10006', priority: 'Low' } }),
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const result = getPushedInfo(userActions, '123');
|
const result = getPushedInfo(userActions, '123');
|
||||||
|
@ -617,34 +556,22 @@ describe('useGetCaseUserActions', () => {
|
||||||
const pushAction123 = getUserAction(['pushed'], 'push-to-service');
|
const pushAction123 = getUserAction(['pushed'], 'push-to-service');
|
||||||
const push456 = {
|
const push456 = {
|
||||||
...basicPushSnake,
|
...basicPushSnake,
|
||||||
connector_id: '456',
|
|
||||||
connector_name: 'other connector name',
|
connector_name: 'other connector name',
|
||||||
external_id: 'other_external_id',
|
external_id: 'other_external_id',
|
||||||
};
|
};
|
||||||
|
|
||||||
const pushAction456 = {
|
const pushAction456 = getUserAction(['pushed'], 'push-to-service', {
|
||||||
...getUserAction(['pushed'], 'push-to-service'),
|
newValConnectorId: '456',
|
||||||
newValue: JSON.stringify(push456),
|
newValue: JSON.stringify(push456),
|
||||||
};
|
});
|
||||||
|
|
||||||
const userActions = [
|
const userActions = [
|
||||||
...caseUserActions,
|
...caseUserActions,
|
||||||
{
|
createUpdateConnectorFields123HighPriorityUserAction(),
|
||||||
...getUserAction(['connector'], 'update'),
|
|
||||||
oldValue: JSON.stringify({ id: '123', fields: { issueType: '10006', priority: null } }),
|
|
||||||
newValue: JSON.stringify({ id: '123', fields: { issueType: '10006', priority: 'High' } }),
|
|
||||||
},
|
|
||||||
pushAction123,
|
pushAction123,
|
||||||
{
|
createChangeConnector123HighPriorityTo456UserAction(),
|
||||||
...getUserAction(['connector'], 'update'),
|
|
||||||
oldValue: JSON.stringify({ id: '123', fields: { issueType: '10006', priority: 'High' } }),
|
|
||||||
newValue: JSON.stringify({ id: '456', fields: { issueTypes: ['10'], severity: '6' } }),
|
|
||||||
},
|
|
||||||
pushAction456,
|
pushAction456,
|
||||||
{
|
createChangeConnector456To123HighPriorityUserAction(),
|
||||||
...getUserAction(['connector'], 'update'),
|
|
||||||
oldValue: JSON.stringify({ id: '456', fields: { issueTypes: ['10'], severity: '6' } }),
|
|
||||||
newValue: JSON.stringify({ id: '123', fields: { issueType: '10006', priority: 'High' } }),
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const result = getPushedInfo(userActions, '123');
|
const result = getPushedInfo(userActions, '123');
|
||||||
|
@ -675,22 +602,10 @@ describe('useGetCaseUserActions', () => {
|
||||||
it('Changing other connectors fields does not count as an update', () => {
|
it('Changing other connectors fields does not count as an update', () => {
|
||||||
const userActions = [
|
const userActions = [
|
||||||
...caseUserActions,
|
...caseUserActions,
|
||||||
{
|
createUpdateConnectorFields123HighPriorityUserAction(),
|
||||||
...getUserAction(['connector'], 'update'),
|
|
||||||
oldValue: JSON.stringify({ id: '123', fields: { issueType: '10006', priority: null } }),
|
|
||||||
newValue: JSON.stringify({ id: '123', fields: { issueType: '10006', priority: 'High' } }),
|
|
||||||
},
|
|
||||||
getUserAction(['pushed'], 'push-to-service'),
|
getUserAction(['pushed'], 'push-to-service'),
|
||||||
{
|
createChangeConnector123HighPriorityTo456UserAction(),
|
||||||
...getUserAction(['connector'], 'update'),
|
createUpdateConnectorFields456HighPriorityUserAction(),
|
||||||
oldValue: JSON.stringify({ id: '123', fields: { issueType: '10006', priority: 'High' } }),
|
|
||||||
newValue: JSON.stringify({ id: '456', fields: { issueTypes: ['10'], severity: '6' } }),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
...getUserAction(['connector'], 'update'),
|
|
||||||
oldValue: JSON.stringify({ id: '456', fields: { issueTypes: ['10'], severity: '6' } }),
|
|
||||||
newValue: JSON.stringify({ id: '456', fields: { issueTypes: ['10'], severity: '3' } }),
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const result = getPushedInfo(userActions, '123');
|
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';
|
} from '../../common';
|
||||||
import { getCaseUserActions, getSubCaseUserActions } from './api';
|
import { getCaseUserActions, getSubCaseUserActions } from './api';
|
||||||
import * as i18n from './translations';
|
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';
|
import { useToasts } from '../common/lib/kibana';
|
||||||
|
|
||||||
export interface CaseService extends CaseExternalService {
|
export interface CaseService extends CaseExternalService {
|
||||||
|
@ -58,8 +59,24 @@ export interface UseGetCaseUserActions extends CaseUserActionsState {
|
||||||
) => Promise<void>;
|
) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const getExternalService = (value: string): CaseExternalService | null =>
|
const unknownExternalServiceConnectorId = 'unknown';
|
||||||
convertToCamelCase<CaseFullExternalService, CaseExternalService>(parseString(`${value}`));
|
|
||||||
|
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 = (
|
const groupConnectorFields = (
|
||||||
userActions: CaseUserActions[]
|
userActions: CaseUserActions[]
|
||||||
|
@ -69,22 +86,26 @@ const groupConnectorFields = (
|
||||||
return acc;
|
return acc;
|
||||||
}
|
}
|
||||||
|
|
||||||
const oldValue = parseString(`${mua.oldValue}`);
|
const oldConnector = parseStringAsConnector(mua.oldValConnectorId, mua.oldValue);
|
||||||
const newValue = parseString(`${mua.newValue}`);
|
const newConnector = parseStringAsConnector(mua.newValConnectorId, mua.newValue);
|
||||||
|
|
||||||
if (oldValue == null || newValue == null) {
|
if (!oldConnector || !newConnector) {
|
||||||
return acc;
|
return acc;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...acc,
|
...acc,
|
||||||
[oldValue.id]: [
|
[oldConnector.id]: [
|
||||||
...(acc[oldValue.id] || []),
|
...(acc[oldConnector.id] || []),
|
||||||
...(oldValue.id === newValue.id ? [oldValue.fields, newValue.fields] : [oldValue.fields]),
|
...(oldConnector.id === newConnector.id
|
||||||
|
? [oldConnector.fields, newConnector.fields]
|
||||||
|
: [oldConnector.fields]),
|
||||||
],
|
],
|
||||||
[newValue.id]: [
|
[newConnector.id]: [
|
||||||
...(acc[newValue.id] || []),
|
...(acc[newConnector.id] || []),
|
||||||
...(oldValue.id === newValue.id ? [oldValue.fields, newValue.fields] : [newValue.fields]),
|
...(oldConnector.id === newConnector.id
|
||||||
|
? [oldConnector.fields, newConnector.fields]
|
||||||
|
: [newConnector.fields]),
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
}, {} as Record<string, Array<CaseConnector['fields']>>);
|
}, {} as Record<string, Array<CaseConnector['fields']>>);
|
||||||
|
@ -137,9 +158,7 @@ export const getPushedInfo = (
|
||||||
const hasDataToPushForConnector = (connectorId: string): boolean => {
|
const hasDataToPushForConnector = (connectorId: string): boolean => {
|
||||||
const caseUserActionsReversed = [...caseUserActions].reverse();
|
const caseUserActionsReversed = [...caseUserActions].reverse();
|
||||||
const lastPushOfConnectorReversedIndex = caseUserActionsReversed.findIndex(
|
const lastPushOfConnectorReversedIndex = caseUserActionsReversed.findIndex(
|
||||||
(mua) =>
|
(mua) => mua.action === 'push-to-service' && mua.newValConnectorId === connectorId
|
||||||
mua.action === 'push-to-service' &&
|
|
||||||
getExternalService(`${mua.newValue}`)?.connectorId === connectorId
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (lastPushOfConnectorReversedIndex === -1) {
|
if (lastPushOfConnectorReversedIndex === -1) {
|
||||||
|
@ -190,7 +209,7 @@ export const getPushedInfo = (
|
||||||
return acc;
|
return acc;
|
||||||
}
|
}
|
||||||
|
|
||||||
const externalService = getExternalService(`${cua.newValue}`);
|
const externalService = getExternalService(cua.newValConnectorId, cua.newValue);
|
||||||
if (externalService === null) {
|
if (externalService === null) {
|
||||||
return acc;
|
return acc;
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,14 +36,6 @@ import * as i18n from './translations';
|
||||||
|
|
||||||
export const getTypedPayload = <T>(a: unknown): T => a as T;
|
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[] =>
|
export const convertArrayToCamelCase = (arrayOfSnakes: unknown[]): unknown[] =>
|
||||||
arrayOfSnakes.reduce((acc: unknown[], value) => {
|
arrayOfSnakes.reduce((acc: unknown[], value) => {
|
||||||
if (isArray(value)) {
|
if (isArray(value)) {
|
||||||
|
|
|
@ -106,7 +106,7 @@ async function getSubCase({
|
||||||
caseId,
|
caseId,
|
||||||
subCaseId: newSubCase.id,
|
subCaseId: newSubCase.id,
|
||||||
fields: ['status', 'sub_case'],
|
fields: ['status', 'sub_case'],
|
||||||
newValue: JSON.stringify({ status: newSubCase.attributes.status }),
|
newValue: { status: newSubCase.attributes.status },
|
||||||
owner: newSubCase.attributes.owner,
|
owner: newSubCase.attributes.owner,
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
|
@ -220,7 +220,7 @@ const addGeneratedAlerts = async (
|
||||||
subCaseId: updatedCase.subCaseId,
|
subCaseId: updatedCase.subCaseId,
|
||||||
commentId: newComment.id,
|
commentId: newComment.id,
|
||||||
fields: ['comment'],
|
fields: ['comment'],
|
||||||
newValue: JSON.stringify(query),
|
newValue: query,
|
||||||
owner: newComment.attributes.owner,
|
owner: newComment.attributes.owner,
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
|
@ -408,7 +408,7 @@ export const addComment = async (
|
||||||
subCaseId: updatedCase.subCaseId,
|
subCaseId: updatedCase.subCaseId,
|
||||||
commentId: newComment.id,
|
commentId: newComment.id,
|
||||||
fields: ['comment'],
|
fields: ['comment'],
|
||||||
newValue: JSON.stringify(query),
|
newValue: query,
|
||||||
owner: newComment.attributes.owner,
|
owner: newComment.attributes.owner,
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
|
|
|
@ -17,6 +17,7 @@ import {
|
||||||
SUB_CASE_SAVED_OBJECT,
|
SUB_CASE_SAVED_OBJECT,
|
||||||
CaseResponse,
|
CaseResponse,
|
||||||
CommentPatchRequest,
|
CommentPatchRequest,
|
||||||
|
CommentRequest,
|
||||||
} from '../../../common';
|
} from '../../../common';
|
||||||
import { AttachmentService, CasesService } from '../../services';
|
import { AttachmentService, CasesService } from '../../services';
|
||||||
import { CasesClientArgs } from '..';
|
import { CasesClientArgs } from '..';
|
||||||
|
@ -193,12 +194,12 @@ export async function update(
|
||||||
subCaseId: subCaseID,
|
subCaseId: subCaseID,
|
||||||
commentId: updatedComment.id,
|
commentId: updatedComment.id,
|
||||||
fields: ['comment'],
|
fields: ['comment'],
|
||||||
newValue: JSON.stringify(queryRestAttributes),
|
// casting because typescript is complaining that it's not a Record<string, unknown> even though it is
|
||||||
oldValue: JSON.stringify(
|
newValue: queryRestAttributes as CommentRequest,
|
||||||
|
oldValue:
|
||||||
// We are interested only in ContextBasicRt attributes
|
// We are interested only in ContextBasicRt attributes
|
||||||
// myComment.attribute contains also CommentAttributesBasicRt attributes
|
// myComment.attribute contains also CommentAttributesBasicRt attributes
|
||||||
pick(Object.keys(queryRestAttributes), myComment.attributes)
|
pick(Object.keys(queryRestAttributes), myComment.attributes),
|
||||||
),
|
|
||||||
owner: myComment.attributes.owner,
|
owner: myComment.attributes.owner,
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
|
|
|
@ -106,7 +106,7 @@ export const create = async (
|
||||||
actionBy: { username, full_name, email },
|
actionBy: { username, full_name, email },
|
||||||
caseId: newCase.id,
|
caseId: newCase.id,
|
||||||
fields: ['description', 'status', 'tags', 'title', 'connector', 'settings', OWNER_FIELD],
|
fields: ['description', 'status', 'tags', 'title', 'connector', 'settings', OWNER_FIELD],
|
||||||
newValue: JSON.stringify(query),
|
newValue: query,
|
||||||
owner: newCase.attributes.owner,
|
owner: newCase.attributes.owner,
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
|
|
|
@ -168,7 +168,7 @@ export async function deleteCases(ids: string[], clientArgs: CasesClientArgs): P
|
||||||
'settings',
|
'settings',
|
||||||
OWNER_FIELD,
|
OWNER_FIELD,
|
||||||
'comment',
|
'comment',
|
||||||
...(ENABLE_CASE_CONNECTOR ? ['sub_case'] : []),
|
...(ENABLE_CASE_CONNECTOR ? ['sub_case' as const] : []),
|
||||||
],
|
],
|
||||||
owner: caseInfo.attributes.owner,
|
owner: caseInfo.attributes.owner,
|
||||||
})
|
})
|
||||||
|
|
|
@ -231,8 +231,10 @@ export const userActions: CaseUserActionsResponse = [
|
||||||
username: 'elastic',
|
username: 'elastic',
|
||||||
},
|
},
|
||||||
new_value:
|
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_value: null,
|
||||||
|
old_val_connector_id: null,
|
||||||
action_id: 'fd830c60-6646-11eb-a291-51bf6b175a53',
|
action_id: 'fd830c60-6646-11eb-a291-51bf6b175a53',
|
||||||
case_id: 'fcdedd20-6646-11eb-a291-51bf6b175a53',
|
case_id: 'fcdedd20-6646-11eb-a291-51bf6b175a53',
|
||||||
comment_id: null,
|
comment_id: null,
|
||||||
|
@ -248,7 +250,9 @@ export const userActions: CaseUserActionsResponse = [
|
||||||
username: 'elastic',
|
username: 'elastic',
|
||||||
},
|
},
|
||||||
new_value:
|
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,
|
old_value: null,
|
||||||
action_id: '0a801750-6647-11eb-a291-51bf6b175a53',
|
action_id: '0a801750-6647-11eb-a291-51bf6b175a53',
|
||||||
case_id: 'fcdedd20-6646-11eb-a291-51bf6b175a53',
|
case_id: 'fcdedd20-6646-11eb-a291-51bf6b175a53',
|
||||||
|
@ -265,6 +269,8 @@ export const userActions: CaseUserActionsResponse = [
|
||||||
username: 'elastic',
|
username: 'elastic',
|
||||||
},
|
},
|
||||||
new_value: '{"type":"alert","alertId":"alert-id-1","index":".siem-signals-default-000008"}',
|
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,
|
old_value: null,
|
||||||
action_id: '7373eb60-6647-11eb-a291-51bf6b175a53',
|
action_id: '7373eb60-6647-11eb-a291-51bf6b175a53',
|
||||||
case_id: 'fcdedd20-6646-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"}',
|
new_value: '{"type":"alert","alertId":"alert-id-2","index":".siem-signals-default-000008"}',
|
||||||
old_value: null,
|
old_value: null,
|
||||||
|
new_val_connector_id: null,
|
||||||
|
old_val_connector_id: null,
|
||||||
action_id: '7abc6410-6647-11eb-a291-51bf6b175a53',
|
action_id: '7abc6410-6647-11eb-a291-51bf6b175a53',
|
||||||
case_id: 'fcdedd20-6646-11eb-a291-51bf6b175a53',
|
case_id: 'fcdedd20-6646-11eb-a291-51bf6b175a53',
|
||||||
comment_id: 'comment-alert-2',
|
comment_id: 'comment-alert-2',
|
||||||
|
@ -297,8 +305,10 @@ export const userActions: CaseUserActionsResponse = [
|
||||||
username: 'elastic',
|
username: 'elastic',
|
||||||
},
|
},
|
||||||
new_value:
|
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_value: null,
|
||||||
|
old_val_connector_id: null,
|
||||||
action_id: '9b91d8f0-6647-11eb-a291-51bf6b175a53',
|
action_id: '9b91d8f0-6647-11eb-a291-51bf6b175a53',
|
||||||
case_id: 'fcdedd20-6646-11eb-a291-51bf6b175a53',
|
case_id: 'fcdedd20-6646-11eb-a291-51bf6b175a53',
|
||||||
comment_id: null,
|
comment_id: null,
|
||||||
|
@ -315,6 +325,8 @@ export const userActions: CaseUserActionsResponse = [
|
||||||
},
|
},
|
||||||
new_value: '{"comment":"a comment!","type":"user"}',
|
new_value: '{"comment":"a comment!","type":"user"}',
|
||||||
old_value: null,
|
old_value: null,
|
||||||
|
new_val_connector_id: null,
|
||||||
|
old_val_connector_id: null,
|
||||||
action_id: '0818e5e0-6648-11eb-a291-51bf6b175a53',
|
action_id: '0818e5e0-6648-11eb-a291-51bf6b175a53',
|
||||||
case_id: 'fcdedd20-6646-11eb-a291-51bf6b175a53',
|
case_id: 'fcdedd20-6646-11eb-a291-51bf6b175a53',
|
||||||
comment_id: 'comment-user-1',
|
comment_id: 'comment-user-1',
|
||||||
|
|
|
@ -241,7 +241,7 @@ export const push = async (
|
||||||
actionBy: { username, full_name, email },
|
actionBy: { username, full_name, email },
|
||||||
caseId,
|
caseId,
|
||||||
fields: ['pushed'],
|
fields: ['pushed'],
|
||||||
newValue: JSON.stringify(externalService),
|
newValue: externalService,
|
||||||
owner: myCase.attributes.owner,
|
owner: myCase.attributes.owner,
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
|
|
|
@ -799,8 +799,10 @@ describe('utils', () => {
|
||||||
username: 'elastic',
|
username: 'elastic',
|
||||||
},
|
},
|
||||||
new_value:
|
new_value:
|
||||||
// The connector id is 123
|
// 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"}',
|
'{"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,
|
old_value: null,
|
||||||
action_id: '9b91d8f0-6647-11eb-a291-51bf6b175a53',
|
action_id: '9b91d8f0-6647-11eb-a291-51bf6b175a53',
|
||||||
case_id: 'fcdedd20-6646-11eb-a291-51bf6b175a53',
|
case_id: 'fcdedd20-6646-11eb-a291-51bf6b175a53',
|
||||||
|
|
|
@ -20,6 +20,8 @@ import {
|
||||||
CommentRequestUserType,
|
CommentRequestUserType,
|
||||||
CommentRequestAlertType,
|
CommentRequestAlertType,
|
||||||
CommentRequestActionsType,
|
CommentRequestActionsType,
|
||||||
|
CaseUserActionResponse,
|
||||||
|
isPush,
|
||||||
} from '../../../common';
|
} from '../../../common';
|
||||||
import { ActionsClient } from '../../../../actions/server';
|
import { ActionsClient } from '../../../../actions/server';
|
||||||
import { CasesClientGetAlertsResponse } from '../../client/alerts/types';
|
import { CasesClientGetAlertsResponse } from '../../client/alerts/types';
|
||||||
|
@ -55,22 +57,36 @@ export const getLatestPushInfo = (
|
||||||
userActions: CaseUserActionsResponse
|
userActions: CaseUserActionsResponse
|
||||||
): { index: number; pushedInfo: CaseFullExternalService } | null => {
|
): { index: number; pushedInfo: CaseFullExternalService } | null => {
|
||||||
for (const [index, action] of [...userActions].reverse().entries()) {
|
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 {
|
try {
|
||||||
const pushedInfo = JSON.parse(action.new_value);
|
const pushedInfo = JSON.parse(action.new_value);
|
||||||
if (pushedInfo.connector_id === connectorId) {
|
// We returned the index of the element in the userActions array.
|
||||||
// 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
|
||||||
// As we traverse the userActions in reverse we need to calculate the index of a normal traversal
|
return {
|
||||||
return { index: userActions.length - index - 1, pushedInfo };
|
index: userActions.length - index - 1,
|
||||||
}
|
pushedInfo: { ...pushedInfo, connector_id: connectorId },
|
||||||
|
};
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Silence JSON parse errors
|
// ignore parse failures and check the next user action
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
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 => {
|
const getCommentContent = (comment: CommentResponse): string => {
|
||||||
if (comment.type === CommentType.user) {
|
if (comment.type === CommentType.user) {
|
||||||
return comment.comment;
|
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.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { SavedObjectReference, SavedObjectsFindResponse } from 'kibana/server';
|
||||||
import {
|
import {
|
||||||
CASE_COMMENT_SAVED_OBJECT,
|
|
||||||
CASE_SAVED_OBJECT,
|
|
||||||
CaseUserActionsResponse,
|
CaseUserActionsResponse,
|
||||||
CaseUserActionsResponseRt,
|
CaseUserActionsResponseRt,
|
||||||
SUB_CASE_SAVED_OBJECT,
|
SUB_CASE_SAVED_OBJECT,
|
||||||
|
CaseUserActionResponse,
|
||||||
} from '../../../common';
|
} from '../../../common';
|
||||||
import { createCaseError, checkEnabledCaseConnectorOrThrow } from '../../common';
|
import { createCaseError, checkEnabledCaseConnectorOrThrow, SUB_CASE_REF_NAME } from '../../common';
|
||||||
import { CasesClientArgs } from '..';
|
import { CasesClientArgs } from '..';
|
||||||
import { Operations } from '../../authorization';
|
import { Operations } from '../../authorization';
|
||||||
import { UserActionGet } from './client';
|
import { UserActionGet } from './client';
|
||||||
|
@ -40,23 +40,12 @@ export const get = async (
|
||||||
operation: Operations.getUserActions,
|
operation: Operations.getUserActions,
|
||||||
});
|
});
|
||||||
|
|
||||||
return CaseUserActionsResponseRt.encode(
|
const resultsToEncode =
|
||||||
userActions.saved_objects.reduce<CaseUserActionsResponse>((acc, ua) => {
|
subCaseId == null
|
||||||
if (subCaseId == null && ua.references.some((uar) => uar.type === SUB_CASE_SAVED_OBJECT)) {
|
? extractAttributesWithoutSubCases(userActions)
|
||||||
return acc;
|
: extractAttributes(userActions);
|
||||||
}
|
|
||||||
return [
|
return CaseUserActionsResponseRt.encode(resultsToEncode);
|
||||||
...acc,
|
|
||||||
{
|
|
||||||
...ua.attributes,
|
|
||||||
action_id: ua.id,
|
|
||||||
case_id: ua.references.find((r) => r.type === CASE_SAVED_OBJECT)?.id ?? '',
|
|
||||||
comment_id: ua.references.find((r) => r.type === CASE_COMMENT_SAVED_OBJECT)?.id ?? null,
|
|
||||||
sub_case_id: ua.references.find((r) => r.type === SUB_CASE_SAVED_OBJECT)?.id ?? '',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}, [])
|
|
||||||
);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw createCaseError({
|
throw createCaseError({
|
||||||
message: `Failed to retrieve user actions case id: ${caseId} sub case id: ${subCaseId}: ${error}`,
|
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.
|
* 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
|
* The name of the saved object reference indicating the action connector ID. This is stored in the Saved Object reference
|
||||||
* field's name property.
|
* 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.
|
* 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';
|
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', () => {
|
describe('case migrations', () => {
|
||||||
it('does not create a reference when the connector.id is none', () => {
|
describe('7.15.0 connector ID migration', () => {
|
||||||
const caseSavedObject = create_7_14_0_case({ connector: getNoneCaseConnector() });
|
it('does not create a reference when the connector.id is none', () => {
|
||||||
|
const caseSavedObject = create_7_14_0_case({ connector: getNoneCaseConnector() });
|
||||||
|
|
||||||
const migratedConnector = caseConnectorIdMigration(
|
const migratedConnector = caseConnectorIdMigration(
|
||||||
caseSavedObject
|
caseSavedObject
|
||||||
) as SavedObjectSanitizedDoc<CaseAttributes>;
|
) as SavedObjectSanitizedDoc<CaseAttributes>;
|
||||||
|
|
||||||
expect(migratedConnector.references.length).toBe(0);
|
expect(migratedConnector.references.length).toBe(0);
|
||||||
expect(migratedConnector.attributes.connector).not.toHaveProperty('id');
|
expect(migratedConnector.attributes.connector).not.toHaveProperty('id');
|
||||||
expect(migratedConnector.attributes.connector).toMatchInlineSnapshot(`
|
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 [
|
|
||||||
Object {
|
Object {
|
||||||
"id": "1",
|
"fields": null,
|
||||||
"name": "awesome",
|
"name": "none",
|
||||||
"type": "hello",
|
"type": ".none",
|
||||||
},
|
}
|
||||||
]
|
`);
|
||||||
`);
|
|
||||||
});
|
|
||||||
|
|
||||||
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(
|
it('does not create a reference when the connector is undefined', () => {
|
||||||
caseSavedObject
|
const caseSavedObject = create_7_14_0_case();
|
||||||
) as SavedObjectSanitizedDoc<CaseAttributes>;
|
|
||||||
|
|
||||||
expect(migratedConnector.references.length).toBe(1);
|
const migratedConnector = caseConnectorIdMigration(
|
||||||
expect(migratedConnector.attributes.connector).not.toHaveProperty('id');
|
caseSavedObject
|
||||||
expect(migratedConnector.attributes.connector).toMatchInlineSnapshot(`
|
) as SavedObjectSanitizedDoc<CaseAttributes>;
|
||||||
Object {
|
|
||||||
"fields": null,
|
expect(migratedConnector.references.length).toBe(0);
|
||||||
"name": "connector",
|
expect(migratedConnector.attributes.connector).not.toHaveProperty('id');
|
||||||
"type": ".jira",
|
expect(migratedConnector.attributes.connector).toMatchInlineSnapshot(`
|
||||||
}
|
|
||||||
`);
|
|
||||||
expect(migratedConnector.references).toMatchInlineSnapshot(`
|
|
||||||
Array [
|
|
||||||
Object {
|
Object {
|
||||||
"id": "123",
|
"fields": null,
|
||||||
"name": "connectorId",
|
"name": "none",
|
||||||
"type": "action",
|
"type": ".none",
|
||||||
},
|
}
|
||||||
]
|
`);
|
||||||
`);
|
|
||||||
});
|
|
||||||
|
|
||||||
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(
|
it('sets the connector to the default none connector if the connector.id is undefined', () => {
|
||||||
caseSavedObject
|
const caseSavedObject = create_7_14_0_case({
|
||||||
) as SavedObjectSanitizedDoc<CaseAttributes>;
|
connector: {
|
||||||
|
fields: null,
|
||||||
|
name: ConnectorTypes.jira,
|
||||||
|
type: ConnectorTypes.jira,
|
||||||
|
} as ESCaseConnectorWithId,
|
||||||
|
});
|
||||||
|
|
||||||
expect(migratedConnector.references.length).toBe(1);
|
const migratedConnector = caseConnectorIdMigration(
|
||||||
expect(migratedConnector.attributes.external_service).not.toHaveProperty('connector_id');
|
caseSavedObject
|
||||||
expect(migratedConnector.attributes.external_service).toMatchInlineSnapshot(`
|
) as SavedObjectSanitizedDoc<CaseAttributes>;
|
||||||
Object {
|
|
||||||
"connector_name": ".jira",
|
expect(migratedConnector.references.length).toBe(0);
|
||||||
"external_id": "100",
|
expect(migratedConnector.attributes.connector).not.toHaveProperty('id');
|
||||||
"external_title": "awesome",
|
expect(migratedConnector.attributes.connector).toMatchInlineSnapshot(`
|
||||||
"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 {
|
Object {
|
||||||
"id": "100",
|
"fields": null,
|
||||||
"name": "pushConnectorId",
|
"name": "none",
|
||||||
"type": "action",
|
"type": ".none",
|
||||||
},
|
}
|
||||||
]
|
`);
|
||||||
`);
|
|
||||||
});
|
|
||||||
|
|
||||||
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(
|
it('does not create a reference when the external_service is null', () => {
|
||||||
caseSavedObject
|
const caseSavedObject = create_7_14_0_case({ externalService: null });
|
||||||
) as SavedObjectSanitizedDoc<CaseAttributes>;
|
|
||||||
|
|
||||||
expect(migratedConnector.references.length).toBe(0);
|
const migratedConnector = caseConnectorIdMigration(
|
||||||
expect(migratedConnector.attributes.external_service).not.toHaveProperty('connector_id');
|
caseSavedObject
|
||||||
expect(migratedConnector.attributes.external_service).toMatchInlineSnapshot(`
|
) as SavedObjectSanitizedDoc<CaseAttributes>;
|
||||||
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', () => {
|
expect(migratedConnector.references.length).toBe(0);
|
||||||
const caseSavedObject = create_7_14_0_case({
|
expect(migratedConnector.attributes.external_service).toBeNull();
|
||||||
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(
|
it('does not create a reference when the external_service is undefined and sets external_service to null', () => {
|
||||||
caseSavedObject
|
const caseSavedObject = create_7_14_0_case();
|
||||||
) as SavedObjectSanitizedDoc<CaseAttributes>;
|
|
||||||
|
|
||||||
expect(migratedConnector.references.length).toBe(2);
|
const migratedConnector = caseConnectorIdMigration(
|
||||||
expect(migratedConnector.attributes.external_service).not.toHaveProperty('connector_id');
|
caseSavedObject
|
||||||
expect(migratedConnector.attributes.external_service).toMatchInlineSnapshot(`
|
) as SavedObjectSanitizedDoc<CaseAttributes>;
|
||||||
Object {
|
|
||||||
"connector_name": ".jira",
|
expect(migratedConnector.references.length).toBe(0);
|
||||||
"external_id": "100",
|
expect(migratedConnector.attributes.external_service).toBeNull();
|
||||||
"external_title": "awesome",
|
});
|
||||||
"external_url": "http://www.google.com",
|
|
||||||
"pushed_at": "2019-11-25T21:54:48.952Z",
|
it('does not create a reference when the external_service.connector_id is none', () => {
|
||||||
"pushed_by": Object {
|
const caseSavedObject = create_7_14_0_case({
|
||||||
"email": "testemail@elastic.co",
|
externalService: createExternalService({ connector_id: noneConnectorId }),
|
||||||
"full_name": "elastic",
|
});
|
||||||
"username": "elastic",
|
|
||||||
},
|
const migratedConnector = caseConnectorIdMigration(
|
||||||
}
|
caseSavedObject
|
||||||
`);
|
) as SavedObjectSanitizedDoc<CaseAttributes>;
|
||||||
expect(migratedConnector.attributes.connector).not.toHaveProperty('id');
|
|
||||||
expect(migratedConnector.attributes.connector).toMatchInlineSnapshot(`
|
expect(migratedConnector.references.length).toBe(0);
|
||||||
Object {
|
expect(migratedConnector.attributes.external_service).toMatchInlineSnapshot(`
|
||||||
"fields": null,
|
|
||||||
"name": "connector",
|
|
||||||
"type": ".jira",
|
|
||||||
}
|
|
||||||
`);
|
|
||||||
expect(migratedConnector.references).toMatchInlineSnapshot(`
|
|
||||||
Array [
|
|
||||||
Object {
|
Object {
|
||||||
"id": "123",
|
"connector_name": ".jira",
|
||||||
"name": "connectorId",
|
"external_id": "100",
|
||||||
"type": "action",
|
"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 {
|
Object {
|
||||||
"id": "100",
|
"fields": null,
|
||||||
"name": "pushConnectorId",
|
"name": "connector",
|
||||||
"type": "action",
|
"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';
|
} from '../../../../../../src/core/server';
|
||||||
import { ESConnectorFields } from '../../services';
|
import { ESConnectorFields } from '../../services';
|
||||||
import { ConnectorTypes, CaseType } from '../../../common';
|
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 {
|
interface UnsanitizedCaseConnector {
|
||||||
connector_id: string;
|
connector_id: string;
|
||||||
|
@ -50,11 +54,13 @@ export const caseConnectorIdMigration = (
|
||||||
// removing the id field since it will be stored in the references instead
|
// removing the id field since it will be stored in the references instead
|
||||||
const { connector, external_service, ...restAttributes } = doc.attributes;
|
const { connector, external_service, ...restAttributes } = doc.attributes;
|
||||||
|
|
||||||
const { transformedConnector, references: connectorReferences } =
|
const { transformedConnector, references: connectorReferences } = transformConnectorIdToReference(
|
||||||
transformConnectorIdToReference(connector);
|
CONNECTOR_ID_REFERENCE_NAME,
|
||||||
|
connector
|
||||||
|
);
|
||||||
|
|
||||||
const { transformedPushConnector, references: pushConnectorReferences } =
|
const { transformedPushConnector, references: pushConnectorReferences } =
|
||||||
transformPushConnectorIdToReference(external_service);
|
transformPushConnectorIdToReference(PUSH_CONNECTOR_ID_REFERENCE_NAME, external_service);
|
||||||
|
|
||||||
const { references = [] } = doc;
|
const { references = [] } = doc;
|
||||||
|
|
||||||
|
|
|
@ -40,87 +40,89 @@ const create_7_14_0_configSchema = (connector?: ESCaseConnectorWithId) => ({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('7.15.0 connector ID migration', () => {
|
describe('configuration migrations', () => {
|
||||||
it('does not create a reference when the connector ID is none', () => {
|
describe('7.15.0 connector ID migration', () => {
|
||||||
const configureSavedObject = create_7_14_0_configSchema(getNoneCaseConnector());
|
it('does not create a reference when the connector ID is none', () => {
|
||||||
|
const configureSavedObject = create_7_14_0_configSchema(getNoneCaseConnector());
|
||||||
|
|
||||||
const migratedConnector = configureConnectorIdMigration(
|
const migratedConnector = configureConnectorIdMigration(
|
||||||
configureSavedObject
|
configureSavedObject
|
||||||
) as SavedObjectSanitizedDoc<ESCasesConfigureAttributes>;
|
) as SavedObjectSanitizedDoc<ESCasesConfigureAttributes>;
|
||||||
|
|
||||||
expect(migratedConnector.references.length).toBe(0);
|
expect(migratedConnector.references.length).toBe(0);
|
||||||
expect(migratedConnector.attributes.connector).not.toHaveProperty('id');
|
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,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const migratedConnector = configureConnectorIdMigration(
|
it('does not create a reference when the connector is undefined and defaults it to the none connector', () => {
|
||||||
configureSavedObject
|
const configureSavedObject = create_7_14_0_configSchema();
|
||||||
) as SavedObjectSanitizedDoc<ESCasesConfigureAttributes>;
|
|
||||||
|
|
||||||
expect(migratedConnector.references).toEqual([
|
const migratedConnector = configureConnectorIdMigration(
|
||||||
{ id: '123', type: ACTION_SAVED_OBJECT_TYPE, name: CONNECTOR_ID_REFERENCE_NAME },
|
configureSavedObject
|
||||||
]);
|
) as SavedObjectSanitizedDoc<ESCasesConfigureAttributes>;
|
||||||
expect(migratedConnector.attributes.connector).not.toHaveProperty('id');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns the other attributes and default connector when the connector is undefined', () => {
|
expect(migratedConnector.references.length).toBe(0);
|
||||||
const configureSavedObject = create_7_14_0_configSchema();
|
expect(migratedConnector.attributes.connector).toMatchInlineSnapshot(`
|
||||||
|
Object {
|
||||||
|
"fields": null,
|
||||||
|
"name": "none",
|
||||||
|
"type": ".none",
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
const migratedConnector = configureConnectorIdMigration(
|
it('creates a reference using the connector id', () => {
|
||||||
configureSavedObject
|
const configureSavedObject = create_7_14_0_configSchema({
|
||||||
) as SavedObjectSanitizedDoc<ESCasesConfigureAttributes>;
|
id: '123',
|
||||||
|
fields: null,
|
||||||
|
name: 'connector',
|
||||||
|
type: ConnectorTypes.jira,
|
||||||
|
});
|
||||||
|
|
||||||
expect(migratedConnector).toMatchInlineSnapshot(`
|
const migratedConnector = configureConnectorIdMigration(
|
||||||
Object {
|
configureSavedObject
|
||||||
"attributes": Object {
|
) as SavedObjectSanitizedDoc<ESCasesConfigureAttributes>;
|
||||||
"closure_type": "close-by-pushing",
|
|
||||||
"connector": Object {
|
expect(migratedConnector.references).toEqual([
|
||||||
"fields": null,
|
{ id: '123', type: ACTION_SAVED_OBJECT_TYPE, name: CONNECTOR_ID_REFERENCE_NAME },
|
||||||
"name": "none",
|
]);
|
||||||
"type": ".none",
|
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",
|
"id": "1",
|
||||||
"created_by": Object {
|
"references": Array [],
|
||||||
"email": "testemail@elastic.co",
|
"type": "cases-configure",
|
||||||
"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",
|
|
||||||
}
|
|
||||||
`);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -13,7 +13,8 @@ import {
|
||||||
} from '../../../../../../src/core/server';
|
} from '../../../../../../src/core/server';
|
||||||
import { ConnectorTypes } from '../../../common';
|
import { ConnectorTypes } from '../../../common';
|
||||||
import { addOwnerToSO, SanitizedCaseOwner } from '.';
|
import { addOwnerToSO, SanitizedCaseOwner } from '.';
|
||||||
import { transformConnectorIdToReference } from './utils';
|
import { transformConnectorIdToReference } from '../../services/user_actions/transform';
|
||||||
|
import { CONNECTOR_ID_REFERENCE_NAME } from '../../common';
|
||||||
|
|
||||||
interface UnsanitizedConfigureConnector {
|
interface UnsanitizedConfigureConnector {
|
||||||
connector_id: string;
|
connector_id: string;
|
||||||
|
@ -34,8 +35,10 @@ export const configureConnectorIdMigration = (
|
||||||
): SavedObjectSanitizedDoc<unknown> => {
|
): SavedObjectSanitizedDoc<unknown> => {
|
||||||
// removing the id field since it will be stored in the references instead
|
// removing the id field since it will be stored in the references instead
|
||||||
const { connector, ...restAttributes } = doc.attributes;
|
const { connector, ...restAttributes } = doc.attributes;
|
||||||
const { transformedConnector, references: connectorReferences } =
|
const { transformedConnector, references: connectorReferences } = transformConnectorIdToReference(
|
||||||
transformConnectorIdToReference(connector);
|
CONNECTOR_ID_REFERENCE_NAME,
|
||||||
|
connector
|
||||||
|
);
|
||||||
const { references = [] } = doc;
|
const { references = [] } = doc;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -5,24 +5,17 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* eslint-disable @typescript-eslint/naming-convention */
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
SavedObjectUnsanitizedDoc,
|
SavedObjectUnsanitizedDoc,
|
||||||
SavedObjectSanitizedDoc,
|
SavedObjectSanitizedDoc,
|
||||||
} from '../../../../../../src/core/server';
|
} from '../../../../../../src/core/server';
|
||||||
import { ConnectorTypes, SECURITY_SOLUTION_OWNER } from '../../../common';
|
import { SECURITY_SOLUTION_OWNER } from '../../../common';
|
||||||
|
|
||||||
export { caseMigrations } from './cases';
|
export { caseMigrations } from './cases';
|
||||||
export { configureMigrations } from './configuration';
|
export { configureMigrations } from './configuration';
|
||||||
|
export { userActionsMigrations } from './user_actions';
|
||||||
export { createCommentsMigrations, CreateCommentsMigrationsDeps } from './comments';
|
export { createCommentsMigrations, CreateCommentsMigrationsDeps } from './comments';
|
||||||
|
|
||||||
interface UserActions {
|
|
||||||
action_field: string[];
|
|
||||||
new_value: string;
|
|
||||||
old_value: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SanitizedCaseOwner {
|
export interface SanitizedCaseOwner {
|
||||||
owner: string;
|
owner: string;
|
||||||
}
|
}
|
||||||
|
@ -38,52 +31,6 @@ export const addOwnerToSO = <T = Record<string, unknown>>(
|
||||||
references: doc.references || [],
|
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 = {
|
export const connectorMappingsMigrations = {
|
||||||
'7.14.0': (
|
'7.14.0': (
|
||||||
doc: SavedObjectUnsanitizedDoc<Record<string, unknown>>
|
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,
|
createSavedObjectReferences,
|
||||||
createCaseSavedObjectResponse,
|
createCaseSavedObjectResponse,
|
||||||
basicCaseFields,
|
basicCaseFields,
|
||||||
|
createSOFindResponse,
|
||||||
} from '../test_utils';
|
} from '../test_utils';
|
||||||
import { ESCaseAttributes } from './types';
|
import { ESCaseAttributes } from './types';
|
||||||
|
|
||||||
|
@ -87,13 +88,6 @@ const createFindSO = (
|
||||||
score: 0,
|
score: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
const createSOFindResponse = (savedObjects: Array<SavedObjectsFindResult<ESCaseAttributes>>) => ({
|
|
||||||
saved_objects: savedObjects,
|
|
||||||
total: savedObjects.length,
|
|
||||||
per_page: savedObjects.length,
|
|
||||||
page: 1,
|
|
||||||
});
|
|
||||||
|
|
||||||
const createCaseUpdateParams = (
|
const createCaseUpdateParams = (
|
||||||
connector?: CaseConnector,
|
connector?: CaseConnector,
|
||||||
externalService?: CaseFullExternalService
|
externalService?: CaseFullExternalService
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { SavedObject, SavedObjectReference } from 'kibana/server';
|
import { SavedObject, SavedObjectReference, SavedObjectsFindResult } from 'kibana/server';
|
||||||
import { ESConnectorFields } from '.';
|
import { ESConnectorFields } from '.';
|
||||||
import { CONNECTOR_ID_REFERENCE_NAME, PUSH_CONNECTOR_ID_REFERENCE_NAME } from '../common';
|
import { CONNECTOR_ID_REFERENCE_NAME, PUSH_CONNECTOR_ID_REFERENCE_NAME } from '../common';
|
||||||
import {
|
import {
|
||||||
|
@ -54,7 +54,7 @@ export const createESJiraConnector = (
|
||||||
{ key: 'parent', value: '2' },
|
{ key: 'parent', value: '2' },
|
||||||
],
|
],
|
||||||
type: ConnectorTypes.jira,
|
type: ConnectorTypes.jira,
|
||||||
...(overrides && { ...overrides }),
|
...overrides,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -94,7 +94,7 @@ export const createExternalService = (
|
||||||
email: 'testemail@elastic.co',
|
email: 'testemail@elastic.co',
|
||||||
username: 'elastic',
|
username: 'elastic',
|
||||||
},
|
},
|
||||||
...(overrides && { ...overrides }),
|
...overrides,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const basicCaseFields = {
|
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.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { SavedObject, SavedObjectsUpdateResponse } from 'kibana/server';
|
import { SavedObject, SavedObjectReference, SavedObjectsUpdateResponse } from 'kibana/server';
|
||||||
import { get, isPlainObject, isString } from 'lodash';
|
import { get, isPlainObject, isString } from 'lodash';
|
||||||
import deepEqual from 'fast-deep-equal';
|
import deepEqual from 'fast-deep-equal';
|
||||||
|
|
||||||
|
@ -23,8 +23,68 @@ import {
|
||||||
} from '../../../common';
|
} from '../../../common';
|
||||||
import { isTwoArraysDifference } from '../../client/utils';
|
import { isTwoArraysDifference } from '../../client/utils';
|
||||||
import { UserActionItem } from '.';
|
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,
|
actionField,
|
||||||
action,
|
action,
|
||||||
actionAt,
|
actionAt,
|
||||||
|
@ -55,103 +115,43 @@ export const transformNewUserAction = ({
|
||||||
owner,
|
owner,
|
||||||
});
|
});
|
||||||
|
|
||||||
interface BuildCaseUserAction {
|
const createCaseReferences = (caseId: string, subCaseId?: string): SavedObjectReference[] => [
|
||||||
action: UserAction;
|
{
|
||||||
actionAt: string;
|
type: CASE_SAVED_OBJECT,
|
||||||
actionBy: User;
|
name: CASE_REF_NAME,
|
||||||
caseId: string;
|
id: caseId,
|
||||||
owner: string;
|
},
|
||||||
fields: UserActionField | unknown[];
|
...(subCaseId
|
||||||
newValue?: string | unknown;
|
? [
|
||||||
oldValue?: string | unknown;
|
{
|
||||||
subCaseId?: string;
|
type: SUB_CASE_SAVED_OBJECT,
|
||||||
}
|
name: SUB_CASE_REF_NAME,
|
||||||
|
id: subCaseId,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
];
|
||||||
|
|
||||||
interface BuildCommentUserActionItem extends BuildCaseUserAction {
|
interface BuildCommentUserActionItem extends BuildCaseUserActionParams {
|
||||||
commentId: string;
|
commentId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const buildCommentUserActionItem = ({
|
export const buildCommentUserActionItem = (params: BuildCommentUserActionItem): UserActionItem => {
|
||||||
action,
|
const { commentId } = params;
|
||||||
actionAt,
|
const { attributes, references } = buildCaseUserActionItem(params);
|
||||||
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 buildCaseUserActionItem = ({
|
return {
|
||||||
action,
|
attributes,
|
||||||
actionAt,
|
references: [
|
||||||
actionBy,
|
...references,
|
||||||
caseId,
|
{
|
||||||
fields,
|
type: CASE_COMMENT_SAVED_OBJECT,
|
||||||
newValue,
|
name: COMMENT_REF_NAME,
|
||||||
oldValue,
|
id: commentId,
|
||||||
subCaseId,
|
},
|
||||||
owner,
|
],
|
||||||
}: BuildCaseUserAction): UserActionItem => ({
|
};
|
||||||
attributes: transformNewUserAction({
|
};
|
||||||
actionField: fields as UserActionField,
|
|
||||||
action,
|
|
||||||
actionAt,
|
|
||||||
owner,
|
|
||||||
...actionBy,
|
|
||||||
newValue: newValue as string,
|
|
||||||
oldValue: oldValue as string,
|
|
||||||
}),
|
|
||||||
references: [
|
|
||||||
{
|
|
||||||
type: CASE_SAVED_OBJECT,
|
|
||||||
name: `associated-${CASE_SAVED_OBJECT}`,
|
|
||||||
id: caseId,
|
|
||||||
},
|
|
||||||
...(subCaseId
|
|
||||||
? [
|
|
||||||
{
|
|
||||||
type: SUB_CASE_SAVED_OBJECT,
|
|
||||||
name: `associated-${SUB_CASE_SAVED_OBJECT}`,
|
|
||||||
id: subCaseId,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
: []),
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
const userActionFieldsAllowed: UserActionField = [
|
const userActionFieldsAllowed: UserActionField = [
|
||||||
'comment',
|
'comment',
|
||||||
|
@ -278,8 +278,8 @@ const buildGenericCaseUserActions = <T extends OwnerEntity>({
|
||||||
caseId,
|
caseId,
|
||||||
subCaseId,
|
subCaseId,
|
||||||
fields: [field],
|
fields: [field],
|
||||||
newValue: JSON.stringify(updatedValue),
|
newValue: updatedValue,
|
||||||
oldValue: JSON.stringify(origValue),
|
oldValue: origValue,
|
||||||
owner: originalItem.attributes.owner,
|
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.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Logger, SavedObjectReference } from 'kibana/server';
|
import {
|
||||||
|
Logger,
|
||||||
|
SavedObjectReference,
|
||||||
|
SavedObjectsFindResponse,
|
||||||
|
SavedObjectsFindResult,
|
||||||
|
} from 'kibana/server';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
CASE_SAVED_OBJECT,
|
CASE_SAVED_OBJECT,
|
||||||
|
@ -13,8 +18,17 @@ import {
|
||||||
CaseUserActionAttributes,
|
CaseUserActionAttributes,
|
||||||
MAX_DOCS_PER_PAGE,
|
MAX_DOCS_PER_PAGE,
|
||||||
SUB_CASE_SAVED_OBJECT,
|
SUB_CASE_SAVED_OBJECT,
|
||||||
|
CaseUserActionResponse,
|
||||||
|
CASE_COMMENT_SAVED_OBJECT,
|
||||||
|
isCreateConnector,
|
||||||
|
isPush,
|
||||||
|
isUpdateConnector,
|
||||||
} from '../../../common';
|
} from '../../../common';
|
||||||
import { ClientArgs } from '..';
|
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 {
|
interface GetCaseUserActionArgs extends ClientArgs {
|
||||||
caseId: string;
|
caseId: string;
|
||||||
|
@ -33,12 +47,16 @@ interface PostCaseUserActionArgs extends ClientArgs {
|
||||||
export class CaseUserActionService {
|
export class CaseUserActionService {
|
||||||
constructor(private readonly log: Logger) {}
|
constructor(private readonly log: Logger) {}
|
||||||
|
|
||||||
public async getAll({ unsecuredSavedObjectsClient, caseId, subCaseId }: GetCaseUserActionArgs) {
|
public async getAll({
|
||||||
|
unsecuredSavedObjectsClient,
|
||||||
|
caseId,
|
||||||
|
subCaseId,
|
||||||
|
}: GetCaseUserActionArgs): Promise<SavedObjectsFindResponse<CaseUserActionResponse>> {
|
||||||
try {
|
try {
|
||||||
const id = subCaseId ?? caseId;
|
const id = subCaseId ?? caseId;
|
||||||
const type = subCaseId ? SUB_CASE_SAVED_OBJECT : CASE_SAVED_OBJECT;
|
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,
|
type: CASE_USER_ACTION_SAVED_OBJECT,
|
||||||
hasReference: { type, id },
|
hasReference: { type, id },
|
||||||
page: 1,
|
page: 1,
|
||||||
|
@ -46,17 +64,22 @@ export class CaseUserActionService {
|
||||||
sortField: 'action_at',
|
sortField: 'action_at',
|
||||||
sortOrder: 'asc',
|
sortOrder: 'asc',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return transformFindResponseToExternalModel(userActions);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.log.error(`Error on GET case user action case id: ${caseId}: ${error}`);
|
this.log.error(`Error on GET case user action case id: ${caseId}: ${error}`);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async bulkCreate({ unsecuredSavedObjectsClient, actions }: PostCaseUserActionArgs) {
|
public async bulkCreate({
|
||||||
|
unsecuredSavedObjectsClient,
|
||||||
|
actions,
|
||||||
|
}: PostCaseUserActionArgs): Promise<void> {
|
||||||
try {
|
try {
|
||||||
this.log.debug(`Attempting to POST a new case user action`);
|
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 }))
|
actions.map((action) => ({ type: CASE_USER_ACTION_SAVED_OBJECT, ...action }))
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} 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());
|
const postedCase = await createCase(supertest, getPostCaseRequest());
|
||||||
await deleteCases({ supertest, caseIDs: [postedCase.id] });
|
await deleteCases({ supertest, caseIDs: [postedCase.id] });
|
||||||
const userActions = await getCaseUserActions({ supertest, caseID: postedCase.id });
|
const userActions = await getCaseUserActions({ supertest, caseID: postedCase.id });
|
||||||
|
@ -106,6 +106,8 @@ export default ({ getService }: FtrProviderContext): void => {
|
||||||
action_by: defaultUser,
|
action_by: defaultUser,
|
||||||
old_value: null,
|
old_value: null,
|
||||||
new_value: null,
|
new_value: null,
|
||||||
|
new_val_connector_id: null,
|
||||||
|
old_val_connector_id: null,
|
||||||
case_id: `${postedCase.id}`,
|
case_id: `${postedCase.id}`,
|
||||||
comment_id: null,
|
comment_id: null,
|
||||||
sub_case_id: '',
|
sub_case_id: '',
|
||||||
|
|
|
@ -126,6 +126,8 @@ export default ({ getService }: FtrProviderContext): void => {
|
||||||
action: 'update',
|
action: 'update',
|
||||||
action_by: defaultUser,
|
action_by: defaultUser,
|
||||||
new_value: CaseStatuses.closed,
|
new_value: CaseStatuses.closed,
|
||||||
|
new_val_connector_id: null,
|
||||||
|
old_val_connector_id: null,
|
||||||
old_value: CaseStatuses.open,
|
old_value: CaseStatuses.open,
|
||||||
case_id: `${postedCase.id}`,
|
case_id: `${postedCase.id}`,
|
||||||
comment_id: null,
|
comment_id: null,
|
||||||
|
@ -165,6 +167,8 @@ export default ({ getService }: FtrProviderContext): void => {
|
||||||
action_by: defaultUser,
|
action_by: defaultUser,
|
||||||
new_value: CaseStatuses['in-progress'],
|
new_value: CaseStatuses['in-progress'],
|
||||||
old_value: CaseStatuses.open,
|
old_value: CaseStatuses.open,
|
||||||
|
old_val_connector_id: null,
|
||||||
|
new_val_connector_id: null,
|
||||||
case_id: `${postedCase.id}`,
|
case_id: `${postedCase.id}`,
|
||||||
comment_id: null,
|
comment_id: null,
|
||||||
sub_case_id: '',
|
sub_case_id: '',
|
||||||
|
|
|
@ -114,6 +114,8 @@ export default ({ getService }: FtrProviderContext): void => {
|
||||||
const { new_value, ...rest } = creationUserAction as CaseUserActionResponse;
|
const { new_value, ...rest } = creationUserAction as CaseUserActionResponse;
|
||||||
const parsedNewValue = JSON.parse(new_value!);
|
const parsedNewValue = JSON.parse(new_value!);
|
||||||
|
|
||||||
|
const { id: connectorId, ...restCaseConnector } = postedCase.connector;
|
||||||
|
|
||||||
expect(rest).to.eql({
|
expect(rest).to.eql({
|
||||||
action_field: [
|
action_field: [
|
||||||
'description',
|
'description',
|
||||||
|
@ -127,6 +129,9 @@ export default ({ getService }: FtrProviderContext): void => {
|
||||||
action: 'create',
|
action: 'create',
|
||||||
action_by: defaultUser,
|
action_by: defaultUser,
|
||||||
old_value: null,
|
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}`,
|
case_id: `${postedCase.id}`,
|
||||||
comment_id: null,
|
comment_id: null,
|
||||||
sub_case_id: '',
|
sub_case_id: '',
|
||||||
|
@ -138,7 +143,7 @@ export default ({ getService }: FtrProviderContext): void => {
|
||||||
description: postedCase.description,
|
description: postedCase.description,
|
||||||
title: postedCase.title,
|
title: postedCase.title,
|
||||||
tags: postedCase.tags,
|
tags: postedCase.tags,
|
||||||
connector: postedCase.connector,
|
connector: restCaseConnector,
|
||||||
settings: postedCase.settings,
|
settings: postedCase.settings,
|
||||||
owner: postedCase.owner,
|
owner: postedCase.owner,
|
||||||
});
|
});
|
||||||
|
|
|
@ -148,7 +148,9 @@ export default ({ getService }: FtrProviderContext): void => {
|
||||||
action: 'create',
|
action: 'create',
|
||||||
action_by: defaultUser,
|
action_by: defaultUser,
|
||||||
new_value: `{"comment":"${postCommentUserReq.comment}","type":"${postCommentUserReq.type}","owner":"securitySolutionFixture"}`,
|
new_value: `{"comment":"${postCommentUserReq.comment}","type":"${postCommentUserReq.type}","owner":"securitySolutionFixture"}`,
|
||||||
|
new_val_connector_id: null,
|
||||||
old_value: null,
|
old_value: null,
|
||||||
|
old_val_connector_id: null,
|
||||||
case_id: `${postedCase.id}`,
|
case_id: `${postedCase.id}`,
|
||||||
comment_id: `${patchedCase.comments![0].id}`,
|
comment_id: `${patchedCase.comments![0].id}`,
|
||||||
sub_case_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 () => {
|
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
|
const { body: postedCase } = await supertest
|
||||||
.post(CASES_URL)
|
.post(CASES_URL)
|
||||||
.set('kbn-xsrf', 'true')
|
.set('kbn-xsrf', 'true')
|
||||||
|
@ -73,7 +82,10 @@ export default ({ getService }: FtrProviderContext): void => {
|
||||||
]);
|
]);
|
||||||
expect(body[0].action).to.eql('create');
|
expect(body[0].action).to.eql('create');
|
||||||
expect(body[0].old_value).to.eql(null);
|
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 () => {
|
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.length).to.eql(2);
|
||||||
expect(body[1].action_field).to.eql(['connector']);
|
expect(body[1].action_field).to.eql(['connector']);
|
||||||
expect(body[1].action).to.eql('update');
|
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({
|
expect(JSON.parse(body[1].old_value)).to.eql({
|
||||||
id: 'none',
|
|
||||||
name: 'none',
|
name: 'none',
|
||||||
type: '.none',
|
type: '.none',
|
||||||
fields: null,
|
fields: null,
|
||||||
});
|
});
|
||||||
expect(JSON.parse(body[1].new_value)).to.eql({
|
expect(JSON.parse(body[1].new_value)).to.eql({
|
||||||
id: '123',
|
|
||||||
name: 'Connector',
|
name: 'Connector',
|
||||||
type: '.jira',
|
type: '.jira',
|
||||||
fields: { issueType: 'Task', priority: 'High', parent: null },
|
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 () => {
|
it(`on update tags, user action: 'add' and 'delete' should be called with actionFields: ['tags']`, async () => {
|
||||||
|
|
|
@ -12,6 +12,10 @@ import {
|
||||||
SECURITY_SOLUTION_OWNER,
|
SECURITY_SOLUTION_OWNER,
|
||||||
} from '../../../../../../plugins/cases/common/constants';
|
} from '../../../../../../plugins/cases/common/constants';
|
||||||
import { getCaseUserActions } from '../../../../common/lib/utils';
|
import { getCaseUserActions } from '../../../../common/lib/utils';
|
||||||
|
import {
|
||||||
|
CaseUserActionResponse,
|
||||||
|
CaseUserActionsResponse,
|
||||||
|
} from '../../../../../../plugins/cases/common';
|
||||||
|
|
||||||
// eslint-disable-next-line import/no-default-export
|
// eslint-disable-next-line import/no-default-export
|
||||||
export default function createGetTests({ getService }: FtrProviderContext) {
|
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.length).eql(1);
|
||||||
expect(connectorUserAction.action_field[0]).eql('connector');
|
expect(connectorUserAction.action_field[0]).eql('connector');
|
||||||
|
expect(connectorUserAction.old_val_connector_id).to.eql(
|
||||||
|
'c1900ac0-017f-11eb-93f8-d161651bf509'
|
||||||
|
);
|
||||||
expect(oldValue).to.eql({
|
expect(oldValue).to.eql({
|
||||||
id: 'c1900ac0-017f-11eb-93f8-d161651bf509',
|
|
||||||
name: 'none',
|
name: 'none',
|
||||||
type: '.none',
|
type: '.none',
|
||||||
fields: null,
|
fields: null,
|
||||||
});
|
});
|
||||||
|
expect(connectorUserAction.new_val_connector_id).to.eql(
|
||||||
|
'b1900ac0-017f-11eb-93f8-d161651bf509'
|
||||||
|
);
|
||||||
expect(newValue).to.eql({
|
expect(newValue).to.eql({
|
||||||
id: 'b1900ac0-017f-11eb-93f8-d161651bf509',
|
|
||||||
name: 'none',
|
name: 'none',
|
||||||
type: '.none',
|
type: '.none',
|
||||||
fields: null,
|
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: 'push-to-service',
|
||||||
action_by: defaultUser,
|
action_by: defaultUser,
|
||||||
old_value: null,
|
old_value: null,
|
||||||
|
old_val_connector_id: null,
|
||||||
|
new_val_connector_id: connector.id,
|
||||||
case_id: `${postedCase.id}`,
|
case_id: `${postedCase.id}`,
|
||||||
comment_id: null,
|
comment_id: null,
|
||||||
sub_case_id: '',
|
sub_case_id: '',
|
||||||
|
@ -284,7 +286,6 @@ export default ({ getService }: FtrProviderContext): void => {
|
||||||
expect(parsedNewValue).to.eql({
|
expect(parsedNewValue).to.eql({
|
||||||
pushed_at: pushedCase.external_service!.pushed_at,
|
pushed_at: pushedCase.external_service!.pushed_at,
|
||||||
pushed_by: defaultUser,
|
pushed_by: defaultUser,
|
||||||
connector_id: connector.id,
|
|
||||||
connector_name: connector.name,
|
connector_name: connector.name,
|
||||||
external_id: '123',
|
external_id: '123',
|
||||||
external_title: 'INC01',
|
external_title: 'INC01',
|
||||||
|
|
|
@ -108,8 +108,10 @@ export default ({ getService }: FtrProviderContext): void => {
|
||||||
expect(body[1].action_field).to.eql(['pushed']);
|
expect(body[1].action_field).to.eql(['pushed']);
|
||||||
expect(body[1].action).to.eql('push-to-service');
|
expect(body[1].action).to.eql('push-to-service');
|
||||||
expect(body[1].old_value).to.eql(null);
|
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);
|
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);
|
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