mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Cases] Refactor decode logic (#158515)
This PR refactors a couple things: 1. Moves the decodeOrThrow logic inside of the `try/catch` within the client. This handles the scenario where another plugin is calling the client. This way we wrap the error such that it is more consumable by other plugins. This also makes the code more consistent. 2. Adds `decodeWithExcessOrThrow` to a couple places we didn't have it in the client 3. Adds `decodeWithExcessOrThrow` to a few places in the HTTP route handlers where we were access fields from the query params or body but weren't validating that they existed 4. Ensured we had `decodeOrThrow` in the service layer before returning to the client 5. Unskipped a few tests now that we have the `strict` io-ts types in place
This commit is contained in:
parent
815fddb9f0
commit
6547d33635
35 changed files with 1041 additions and 234 deletions
|
@ -28,7 +28,6 @@ import {
|
|||
BulkCreateCommentRequestRt,
|
||||
BulkGetAttachmentsRequestRt,
|
||||
BulkGetAttachmentsResponseRt,
|
||||
FindCommentsArgsRt,
|
||||
} from '.';
|
||||
|
||||
describe('Comments', () => {
|
||||
|
@ -750,35 +749,6 @@ describe('Comments', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('FindCommentsArgsRt', () => {
|
||||
const defaultRequest = {
|
||||
caseID: 'basic-case-id',
|
||||
queryParams: {
|
||||
page: 1,
|
||||
perPage: 10,
|
||||
sortOrder: 'asc',
|
||||
},
|
||||
};
|
||||
|
||||
it('has expected attributes in request', () => {
|
||||
const query = FindCommentsArgsRt.decode(defaultRequest);
|
||||
|
||||
expect(query).toStrictEqual({
|
||||
_tag: 'Right',
|
||||
right: defaultRequest,
|
||||
});
|
||||
});
|
||||
|
||||
it('removes foo:bar attributes from request', () => {
|
||||
const query = FindCommentsArgsRt.decode({ ...defaultRequest, foo: 'bar' });
|
||||
|
||||
expect(query).toStrictEqual({
|
||||
_tag: 'Right',
|
||||
right: defaultRequest,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('BulkCreateCommentRequestRt', () => {
|
||||
const defaultRequest = [
|
||||
{
|
||||
|
|
|
@ -298,13 +298,6 @@ export const FindCommentsQueryParamsRt = rt.exact(
|
|||
})
|
||||
);
|
||||
|
||||
export const FindCommentsArgsRt = rt.intersection([
|
||||
rt.strict({
|
||||
caseID: rt.string,
|
||||
}),
|
||||
rt.strict({ queryParams: rt.union([FindCommentsQueryParamsRt, rt.undefined]) }),
|
||||
]);
|
||||
|
||||
export const BulkCreateCommentRequestRt = rt.array(CommentRequestRt);
|
||||
|
||||
export const BulkGetAttachmentsRequestRt = rt.strict({
|
||||
|
|
|
@ -61,7 +61,7 @@ const CaseUserActionBasicWithoutConnectorIdRt = rt.intersection([
|
|||
UserActionCommonAttributesRt,
|
||||
]);
|
||||
|
||||
const CaseUserActionDeprecatedResponseRt = rt.intersection([
|
||||
export const CaseUserActionDeprecatedResponseRt = rt.intersection([
|
||||
CaseUserActionBasicRt,
|
||||
CaseUserActionInjectedDeprecatedIdsRt,
|
||||
]);
|
||||
|
|
|
@ -15,7 +15,6 @@ import {
|
|||
describe('Metrics case', () => {
|
||||
describe('SingleCaseMetricsRequestRt', () => {
|
||||
const defaultRequest = {
|
||||
caseId: 'basic-case-id',
|
||||
features: ['alerts.count', 'lifespan'],
|
||||
};
|
||||
|
||||
|
|
|
@ -73,10 +73,6 @@ const AlertUsersMetricsRt = rt.strict({
|
|||
});
|
||||
|
||||
export const SingleCaseMetricsRequestRt = rt.strict({
|
||||
/**
|
||||
* The ID of the case.
|
||||
*/
|
||||
caseId: rt.string,
|
||||
/**
|
||||
* The metrics to retrieve.
|
||||
*/
|
||||
|
|
|
@ -27,8 +27,6 @@ import { validateRegisteredAttachments } from './validators';
|
|||
export const addComment = async (addArgs: AddArgs, clientArgs: CasesClientArgs): Promise<Case> => {
|
||||
const { comment, caseId } = addArgs;
|
||||
|
||||
const query = decodeWithExcessOrThrow(CommentRequestRt)(comment);
|
||||
|
||||
const {
|
||||
logger,
|
||||
authorization,
|
||||
|
@ -36,8 +34,11 @@ export const addComment = async (addArgs: AddArgs, clientArgs: CasesClientArgs):
|
|||
externalReferenceAttachmentTypeRegistry,
|
||||
} = clientArgs;
|
||||
|
||||
decodeCommentRequest(comment, externalReferenceAttachmentTypeRegistry);
|
||||
try {
|
||||
const query = decodeWithExcessOrThrow(CommentRequestRt)(comment);
|
||||
|
||||
decodeCommentRequest(comment, externalReferenceAttachmentTypeRegistry);
|
||||
|
||||
const savedObjectID = SavedObjectsUtils.generateId();
|
||||
|
||||
await authorization.ensureAuthorized({
|
||||
|
|
|
@ -20,19 +20,12 @@ import { Operations } from '../../authorization';
|
|||
import type { BulkCreateArgs } from './types';
|
||||
import { validateRegisteredAttachments } from './validators';
|
||||
|
||||
/**
|
||||
* Bulk create attachments to a case.
|
||||
*
|
||||
* @ignore
|
||||
*/
|
||||
export const bulkCreate = async (
|
||||
args: BulkCreateArgs,
|
||||
clientArgs: CasesClientArgs
|
||||
): Promise<Case> => {
|
||||
const { attachments, caseId } = args;
|
||||
|
||||
decodeWithExcessOrThrow(BulkCreateCommentRequestRt)(attachments);
|
||||
|
||||
const {
|
||||
logger,
|
||||
authorization,
|
||||
|
@ -40,16 +33,18 @@ export const bulkCreate = async (
|
|||
persistableStateAttachmentTypeRegistry,
|
||||
} = clientArgs;
|
||||
|
||||
attachments.forEach((attachment) => {
|
||||
decodeCommentRequest(attachment, externalReferenceAttachmentTypeRegistry);
|
||||
validateRegisteredAttachments({
|
||||
query: attachment,
|
||||
persistableStateAttachmentTypeRegistry,
|
||||
externalReferenceAttachmentTypeRegistry,
|
||||
});
|
||||
});
|
||||
|
||||
try {
|
||||
decodeWithExcessOrThrow(BulkCreateCommentRequestRt)(attachments);
|
||||
|
||||
attachments.forEach((attachment) => {
|
||||
decodeCommentRequest(attachment, externalReferenceAttachmentTypeRegistry);
|
||||
validateRegisteredAttachments({
|
||||
query: attachment,
|
||||
persistableStateAttachmentTypeRegistry,
|
||||
externalReferenceAttachmentTypeRegistry,
|
||||
});
|
||||
});
|
||||
|
||||
const [attachmentsWithIds, entities]: [Array<{ id: string } & CommentRequest>, OwnerEntity[]] =
|
||||
attachments.reduce<[Array<{ id: string } & CommentRequest>, OwnerEntity[]]>(
|
||||
([a, e], attachment) => {
|
||||
|
|
|
@ -18,9 +18,9 @@ describe('get', () => {
|
|||
|
||||
it('Invalid total items results in error', async () => {
|
||||
await expect(() =>
|
||||
findComment({ caseID: 'mock-id', queryParams: { page: 2, perPage: 9001 } }, clientArgs)
|
||||
).rejects.toThrow(
|
||||
'The number of documents is too high. Paginating through more than 10,000 documents is not possible.'
|
||||
findComment({ caseID: 'mock-id', findQueryParams: { page: 2, perPage: 9001 } }, clientArgs)
|
||||
).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||
`"Failed to find comments case id: mock-id: Error: The number of documents is too high. Paginating through more than 10,000 documents is not possible."`
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -28,10 +28,12 @@ describe('get', () => {
|
|||
await expect(
|
||||
findComment(
|
||||
// @ts-expect-error: excess attribute
|
||||
{ caseID: 'mock-id', queryParams: { page: 2, perPage: 9001 }, foo: 'bar' },
|
||||
{ caseID: 'mock-id', findQueryParams: { page: 2, perPage: 9001, foo: 'bar' } },
|
||||
clientArgs
|
||||
)
|
||||
).rejects.toThrow('invalid keys "foo"');
|
||||
).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||
`"Failed to find comments case id: mock-id: Error: invalid keys \\"foo\\""`
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -20,7 +20,7 @@ import type { FindCommentsArgs, GetAllAlertsAttachToCase, GetAllArgs, GetArgs }
|
|||
|
||||
import { CASE_COMMENT_SAVED_OBJECT, CASE_SAVED_OBJECT } from '../../../common/constants';
|
||||
import {
|
||||
FindCommentsArgsRt,
|
||||
FindCommentsQueryParamsRt,
|
||||
CommentType,
|
||||
CommentsRt,
|
||||
CommentRt,
|
||||
|
@ -112,7 +112,7 @@ export const getAllAlertsAttachToCase = async (
|
|||
* Retrieves the attachments for a case entity. This support pagination.
|
||||
*/
|
||||
export async function find(
|
||||
data: FindCommentsArgs,
|
||||
{ caseID, findQueryParams }: FindCommentsArgs,
|
||||
clientArgs: CasesClientArgs
|
||||
): Promise<CommentsFindResponse> {
|
||||
const {
|
||||
|
@ -121,11 +121,11 @@ export async function find(
|
|||
authorization,
|
||||
} = clientArgs;
|
||||
|
||||
const { caseID, queryParams } = decodeWithExcessOrThrow(FindCommentsArgsRt)(data);
|
||||
|
||||
validateFindCommentsPagination(queryParams);
|
||||
|
||||
try {
|
||||
const queryParams = decodeWithExcessOrThrow(FindCommentsQueryParamsRt)(findQueryParams);
|
||||
|
||||
validateFindCommentsPagination(queryParams);
|
||||
|
||||
const { filter: authorizationFilter, ensureSavedObjectsAreAuthorized } =
|
||||
await authorization.getAuthorizationFilter(Operations.findComments);
|
||||
|
||||
|
|
|
@ -80,7 +80,7 @@ export interface FindCommentsArgs {
|
|||
/**
|
||||
* Optional parameters for filtering the returned attachments
|
||||
*/
|
||||
queryParams?: FindCommentsQueryParams;
|
||||
findQueryParams?: FindCommentsQueryParams;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -11,6 +11,7 @@ import { CaseCommentModel } from '../../common/models';
|
|||
import { createCaseError } from '../../common/error';
|
||||
import { isCommentRequestTypeExternalReference } from '../../../common/utils/attachments';
|
||||
import type { Case } from '../../../common/api';
|
||||
import { CommentPatchRequestRt, decodeWithExcessOrThrow } from '../../../common/api';
|
||||
import { CASE_SAVED_OBJECT } from '../../../common/constants';
|
||||
import type { CasesClientArgs } from '..';
|
||||
import { decodeCommentRequest } from '../utils';
|
||||
|
@ -38,7 +39,7 @@ export async function update(
|
|||
id: queryCommentId,
|
||||
version: queryCommentVersion,
|
||||
...queryRestAttributes
|
||||
} = queryParams;
|
||||
} = decodeWithExcessOrThrow(CommentPatchRequestRt)(queryParams);
|
||||
|
||||
decodeCommentRequest(queryRestAttributes, externalReferenceAttachmentTypeRegistry);
|
||||
|
||||
|
|
|
@ -90,7 +90,9 @@ describe('create', () => {
|
|||
await expect(
|
||||
// @ts-expect-error foo is an invalid field
|
||||
create({ ...theCase, foo: 'bar' }, clientArgs)
|
||||
).rejects.toThrowErrorMatchingInlineSnapshot(`"invalid keys \\"foo\\""`);
|
||||
).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||
`"Failed to create case: Error: invalid keys \\"foo\\""`
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -40,19 +40,19 @@ export const create = async (data: CasePostRequest, clientArgs: CasesClientArgs)
|
|||
authorization: auth,
|
||||
} = clientArgs;
|
||||
|
||||
const query = decodeWithExcessOrThrow(CasePostRequestRt)(data);
|
||||
|
||||
if (query.title.length > MAX_TITLE_LENGTH) {
|
||||
throw Boom.badRequest(
|
||||
`The length of the title is too long. The maximum length is ${MAX_TITLE_LENGTH}.`
|
||||
);
|
||||
}
|
||||
|
||||
if (query.tags.some(isInvalidTag)) {
|
||||
throw Boom.badRequest('A tag must contain at least one non-space character');
|
||||
}
|
||||
|
||||
try {
|
||||
const query = decodeWithExcessOrThrow(CasePostRequestRt)(data);
|
||||
|
||||
if (query.title.length > MAX_TITLE_LENGTH) {
|
||||
throw Boom.badRequest(
|
||||
`The length of the title is too long. The maximum length is ${MAX_TITLE_LENGTH}.`
|
||||
);
|
||||
}
|
||||
|
||||
if (query.tags.some(isInvalidTag)) {
|
||||
throw Boom.badRequest('A tag must contain at least one non-space character');
|
||||
}
|
||||
|
||||
const savedObjectID = SavedObjectsUtils.generateId();
|
||||
|
||||
await auth.ensureAuthorized({
|
||||
|
|
|
@ -33,6 +33,7 @@ export async function deleteCases(ids: string[], clientArgs: CasesClientArgs): P
|
|||
authorization,
|
||||
fileService,
|
||||
} = clientArgs;
|
||||
|
||||
try {
|
||||
const cases = await caseService.getCases({ caseIds: ids });
|
||||
const entities = new Map<string, OwnerEntity>();
|
||||
|
|
|
@ -266,7 +266,9 @@ describe('update', () => {
|
|||
},
|
||||
clientArgs
|
||||
)
|
||||
).rejects.toThrowErrorMatchingInlineSnapshot(`"invalid keys \\"foo\\""`);
|
||||
).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||
`"Failed to update case, ids: [{\\"id\\":\\"mock-id-1\\",\\"version\\":\\"WzAsMV0=\\"}]: Error: invalid keys \\"foo\\""`
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -314,9 +314,9 @@ export const update = async (
|
|||
authorization,
|
||||
} = clientArgs;
|
||||
|
||||
const query = decodeWithExcessOrThrow(CasesPatchRequestRt)(cases);
|
||||
|
||||
try {
|
||||
const query = decodeWithExcessOrThrow(CasesPatchRequestRt)(cases);
|
||||
|
||||
const myCases = await caseService.getCases({
|
||||
caseIds: query.cases.map((q) => q.id),
|
||||
});
|
||||
|
|
|
@ -30,6 +30,7 @@ import {
|
|||
ConfigurationRt,
|
||||
FindActionConnectorResponseRt,
|
||||
decodeWithExcessOrThrow,
|
||||
ConfigurationRequestRt,
|
||||
} from '../../../common/api';
|
||||
import { MAX_CONCURRENT_SEARCHES } from '../../../common/constants';
|
||||
import { createCaseError } from '../../common/error';
|
||||
|
@ -138,6 +139,7 @@ export async function get(
|
|||
logger,
|
||||
authorization,
|
||||
} = clientArgs;
|
||||
|
||||
try {
|
||||
const queryParams = decodeWithExcessOrThrow(GetConfigurationFindRequestRt)(params);
|
||||
|
||||
|
@ -339,7 +341,7 @@ export async function update(
|
|||
}
|
||||
|
||||
async function create(
|
||||
configuration: ConfigurationRequest,
|
||||
configRequest: ConfigurationRequest,
|
||||
clientArgs: CasesClientArgs,
|
||||
casesClientInternal: CasesClientInternal
|
||||
): Promise<Configuration> {
|
||||
|
@ -350,7 +352,11 @@ async function create(
|
|||
user,
|
||||
authorization,
|
||||
} = clientArgs;
|
||||
|
||||
try {
|
||||
const validatedConfigurationRequest =
|
||||
decodeWithExcessOrThrow(ConfigurationRequestRt)(configRequest);
|
||||
|
||||
let error = null;
|
||||
|
||||
const { filter: authorizationFilter, ensureSavedObjectsAreAuthorized } =
|
||||
|
@ -364,7 +370,7 @@ async function create(
|
|||
);
|
||||
|
||||
const filter = combineAuthorizedAndOwnerFilter(
|
||||
configuration.owner,
|
||||
validatedConfigurationRequest.owner,
|
||||
authorizationFilter,
|
||||
Operations.createConfiguration.savedObjectType
|
||||
);
|
||||
|
@ -399,7 +405,7 @@ async function create(
|
|||
|
||||
await authorization.ensureAuthorized({
|
||||
operation: Operations.createConfiguration,
|
||||
entities: [{ owner: configuration.owner, id: savedObjectID }],
|
||||
entities: [{ owner: validatedConfigurationRequest.owner, id: savedObjectID }],
|
||||
});
|
||||
|
||||
const creationDate = new Date().toISOString();
|
||||
|
@ -408,22 +414,22 @@ async function create(
|
|||
try {
|
||||
mappings = (
|
||||
await casesClientInternal.configuration.createMappings({
|
||||
connector: configuration.connector,
|
||||
owner: configuration.owner,
|
||||
connector: validatedConfigurationRequest.connector,
|
||||
owner: validatedConfigurationRequest.owner,
|
||||
refresh: false,
|
||||
})
|
||||
).mappings;
|
||||
} catch (e) {
|
||||
error = e.isBoom
|
||||
? e.output.payload.message
|
||||
: `Error creating mapping for ${configuration.connector.name}`;
|
||||
: `Error creating mapping for ${validatedConfigurationRequest.connector.name}`;
|
||||
}
|
||||
|
||||
const post = await caseConfigureService.post({
|
||||
unsecuredSavedObjectsClient,
|
||||
attributes: {
|
||||
...configuration,
|
||||
connector: configuration.connector,
|
||||
...validatedConfigurationRequest,
|
||||
connector: validatedConfigurationRequest.connector,
|
||||
created_at: creationDate,
|
||||
created_by: user,
|
||||
updated_at: null,
|
||||
|
|
|
@ -10,7 +10,6 @@ import type {
|
|||
CasesMetricsRequest,
|
||||
CasesStatusRequest,
|
||||
CasesStatusResponse,
|
||||
SingleCaseMetricsRequest,
|
||||
CasesMetricsResponse,
|
||||
} from '../../../common/api';
|
||||
import type { CasesClient } from '../client';
|
||||
|
@ -19,12 +18,13 @@ import type { CasesClientArgs } from '../types';
|
|||
import { getStatusTotalsByType } from './get_status_totals';
|
||||
import { getCaseMetrics } from './get_case_metrics';
|
||||
import { getCasesMetrics } from './get_cases_metrics';
|
||||
import type { GetCaseMetricsParams } from './types';
|
||||
|
||||
/**
|
||||
* API for interacting with the metrics.
|
||||
*/
|
||||
export interface MetricsSubClient {
|
||||
getCaseMetrics(params: SingleCaseMetricsRequest): Promise<SingleCaseMetricsResponse>;
|
||||
getCaseMetrics(params: GetCaseMetricsParams): Promise<SingleCaseMetricsResponse>;
|
||||
getCasesMetrics(params: CasesMetricsRequest): Promise<CasesMetricsResponse>;
|
||||
/**
|
||||
* Retrieves the total number of open, closed, and in-progress cases.
|
||||
|
@ -42,7 +42,7 @@ export const createMetricsSubClient = (
|
|||
casesClient: CasesClient
|
||||
): MetricsSubClient => {
|
||||
const casesSubClient: MetricsSubClient = {
|
||||
getCaseMetrics: (params: SingleCaseMetricsRequest) =>
|
||||
getCaseMetrics: (params: GetCaseMetricsParams) =>
|
||||
getCaseMetrics(params, casesClient, clientArgs),
|
||||
getCasesMetrics: (params: CasesMetricsRequest) =>
|
||||
getCasesMetrics(params, casesClient, clientArgs),
|
||||
|
|
|
@ -6,25 +6,36 @@
|
|||
*/
|
||||
import { merge } from 'lodash';
|
||||
|
||||
import type { SingleCaseMetricsRequest, SingleCaseMetricsResponse } from '../../../common/api';
|
||||
import { SingleCaseMetricsResponseRt } from '../../../common/api';
|
||||
import type { SingleCaseMetricsResponse } from '../../../common/api';
|
||||
import {
|
||||
SingleCaseMetricsResponseRt,
|
||||
SingleCaseMetricsRequestRt,
|
||||
decodeWithExcessOrThrow,
|
||||
} from '../../../common/api';
|
||||
import { Operations } from '../../authorization';
|
||||
import { createCaseError } from '../../common/error';
|
||||
import type { CasesClient } from '../client';
|
||||
import type { CasesClientArgs } from '../types';
|
||||
import type { GetCaseMetricsParams } from './types';
|
||||
import { buildHandlers } from './utils';
|
||||
import { decodeOrThrow } from '../../../common/api/runtime_types';
|
||||
|
||||
export const getCaseMetrics = async (
|
||||
params: SingleCaseMetricsRequest,
|
||||
{ caseId, features }: GetCaseMetricsParams,
|
||||
casesClient: CasesClient,
|
||||
clientArgs: CasesClientArgs
|
||||
): Promise<SingleCaseMetricsResponse> => {
|
||||
const { logger } = clientArgs;
|
||||
|
||||
try {
|
||||
await checkAuthorization(params, clientArgs);
|
||||
const handlers = buildHandlers(params, casesClient, clientArgs);
|
||||
const queryParams = decodeWithExcessOrThrow(SingleCaseMetricsRequestRt)({ features });
|
||||
|
||||
await checkAuthorization(caseId, clientArgs);
|
||||
const handlers = buildHandlers(
|
||||
{ caseId, features: queryParams.features },
|
||||
casesClient,
|
||||
clientArgs
|
||||
);
|
||||
|
||||
const computedMetrics = await Promise.all(
|
||||
Array.from(handlers).map(async (handler) => {
|
||||
|
@ -40,23 +51,20 @@ export const getCaseMetrics = async (
|
|||
} catch (error) {
|
||||
throw createCaseError({
|
||||
logger,
|
||||
message: `Failed to retrieve metrics within client for case id: ${params.caseId}: ${error}`,
|
||||
message: `Failed to retrieve metrics within client for case id: ${caseId}: ${error}`,
|
||||
error,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const checkAuthorization = async (
|
||||
params: SingleCaseMetricsRequest,
|
||||
clientArgs: CasesClientArgs
|
||||
) => {
|
||||
const checkAuthorization = async (caseId: string, clientArgs: CasesClientArgs) => {
|
||||
const {
|
||||
services: { caseService },
|
||||
authorization,
|
||||
} = clientArgs;
|
||||
|
||||
const caseInfo = await caseService.getCase({
|
||||
id: params.caseId,
|
||||
id: caseId,
|
||||
});
|
||||
|
||||
await authorization.ensureAuthorized({
|
||||
|
|
|
@ -26,9 +26,9 @@ export const getCasesMetrics = async (
|
|||
): Promise<CasesMetricsResponse> => {
|
||||
const { logger } = clientArgs;
|
||||
|
||||
const queryParams = decodeWithExcessOrThrow(CasesMetricsRequestRt)(params);
|
||||
|
||||
try {
|
||||
const queryParams = decodeWithExcessOrThrow(CasesMetricsRequestRt)(params);
|
||||
|
||||
const handlers = buildHandlers(queryParams, casesClient, clientArgs);
|
||||
|
||||
const computedMetrics = await Promise.all(
|
||||
|
|
|
@ -37,3 +37,8 @@ export interface AllCasesBaseHandlerCommonOptions extends BaseHandlerCommonOptio
|
|||
to?: string;
|
||||
owner?: string | string[];
|
||||
}
|
||||
|
||||
export interface GetCaseMetricsParams {
|
||||
caseId: string;
|
||||
features: string[];
|
||||
}
|
||||
|
|
|
@ -14,15 +14,15 @@ import { AlertDetails } from './alerts/details';
|
|||
import { Actions } from './actions';
|
||||
import { Connectors } from './connectors';
|
||||
import { Lifespan } from './lifespan';
|
||||
import type { MetricsHandler } from './types';
|
||||
import type { GetCaseMetricsParams, MetricsHandler } from './types';
|
||||
import { MTTR } from './all_cases/mttr';
|
||||
|
||||
const isSingleCaseMetrics = (
|
||||
params: SingleCaseMetricsRequest | CasesMetricsRequest
|
||||
): params is SingleCaseMetricsRequest => (params as SingleCaseMetricsRequest).caseId != null;
|
||||
params: GetCaseMetricsParams | CasesMetricsRequest
|
||||
): params is GetCaseMetricsParams => (params as GetCaseMetricsParams).caseId != null;
|
||||
|
||||
export const buildHandlers = (
|
||||
params: SingleCaseMetricsRequest | CasesMetricsRequest,
|
||||
params: GetCaseMetricsParams | CasesMetricsRequest,
|
||||
casesClient: CasesClient,
|
||||
clientArgs: CasesClientArgs
|
||||
): Set<MetricsHandler<unknown>> => {
|
||||
|
|
|
@ -8,64 +8,93 @@
|
|||
import { omit } from 'lodash';
|
||||
import { CaseStatuses } from '@kbn/cases-components';
|
||||
import { ConnectorTypes, CaseSeverity, SECURITY_SOLUTION_OWNER } from '../../../common';
|
||||
import { CaseTransformedAttributesRt, getPartialCaseTransformedAttributesRt } from './case';
|
||||
import {
|
||||
CaseTransformedAttributesRt,
|
||||
getPartialCaseTransformedAttributesRt,
|
||||
OwnerRt,
|
||||
} from './case';
|
||||
import { decodeOrThrow } from '../../../common/api';
|
||||
|
||||
describe('getPartialCaseTransformedAttributesRt', () => {
|
||||
const theCaseAttributes = {
|
||||
closed_at: null,
|
||||
closed_by: null,
|
||||
connector: {
|
||||
id: 'none',
|
||||
name: 'none',
|
||||
type: ConnectorTypes.none,
|
||||
fields: null,
|
||||
},
|
||||
created_at: '2019-11-25T21:54:48.952Z',
|
||||
created_by: {
|
||||
full_name: 'elastic',
|
||||
email: 'testemail@elastic.co',
|
||||
username: 'elastic',
|
||||
},
|
||||
severity: CaseSeverity.LOW,
|
||||
duration: null,
|
||||
description: 'This is a brand new case of a bad meanie defacing data',
|
||||
external_service: null,
|
||||
title: 'Super Bad Security Issue',
|
||||
status: CaseStatuses.open,
|
||||
tags: ['defacement'],
|
||||
updated_at: '2019-11-25T21:54:48.952Z',
|
||||
updated_by: {
|
||||
full_name: 'elastic',
|
||||
email: 'testemail@elastic.co',
|
||||
username: 'elastic',
|
||||
},
|
||||
settings: {
|
||||
syncAlerts: true,
|
||||
},
|
||||
owner: SECURITY_SOLUTION_OWNER,
|
||||
assignees: [],
|
||||
};
|
||||
const caseTransformedAttributesProps = CaseTransformedAttributesRt.types.reduce(
|
||||
(acc, type) => ({ ...acc, ...type.type.props }),
|
||||
{}
|
||||
);
|
||||
describe('case types', () => {
|
||||
describe('getPartialCaseTransformedAttributesRt', () => {
|
||||
const theCaseAttributes = {
|
||||
closed_at: null,
|
||||
closed_by: null,
|
||||
connector: {
|
||||
id: 'none',
|
||||
name: 'none',
|
||||
type: ConnectorTypes.none,
|
||||
fields: null,
|
||||
},
|
||||
created_at: '2019-11-25T21:54:48.952Z',
|
||||
created_by: {
|
||||
full_name: 'elastic',
|
||||
email: 'testemail@elastic.co',
|
||||
username: 'elastic',
|
||||
},
|
||||
severity: CaseSeverity.LOW,
|
||||
duration: null,
|
||||
description: 'This is a brand new case of a bad meanie defacing data',
|
||||
external_service: null,
|
||||
title: 'Super Bad Security Issue',
|
||||
status: CaseStatuses.open,
|
||||
tags: ['defacement'],
|
||||
updated_at: '2019-11-25T21:54:48.952Z',
|
||||
updated_by: {
|
||||
full_name: 'elastic',
|
||||
email: 'testemail@elastic.co',
|
||||
username: 'elastic',
|
||||
},
|
||||
settings: {
|
||||
syncAlerts: true,
|
||||
},
|
||||
owner: SECURITY_SOLUTION_OWNER,
|
||||
assignees: [],
|
||||
};
|
||||
const caseTransformedAttributesProps = CaseTransformedAttributesRt.types.reduce(
|
||||
(acc, type) => ({ ...acc, ...type.type.props }),
|
||||
{}
|
||||
);
|
||||
|
||||
const type = getPartialCaseTransformedAttributesRt();
|
||||
const type = getPartialCaseTransformedAttributesRt();
|
||||
|
||||
it.each(Object.keys(caseTransformedAttributesProps))('does not throw if %s is omitted', (key) => {
|
||||
const theCase = omit(theCaseAttributes, key);
|
||||
const decodedRes = type.decode(theCase);
|
||||
it.each(Object.keys(caseTransformedAttributesProps))(
|
||||
'does not throw if %s is omitted',
|
||||
(key) => {
|
||||
const theCase = omit(theCaseAttributes, key);
|
||||
const decodedRes = type.decode(theCase);
|
||||
|
||||
expect(decodedRes._tag).toEqual('Right');
|
||||
// @ts-expect-error: the check above ensures that right exists
|
||||
expect(decodedRes.right).toEqual(theCase);
|
||||
expect(decodedRes._tag).toEqual('Right');
|
||||
// @ts-expect-error: the check above ensures that right exists
|
||||
expect(decodedRes.right).toEqual(theCase);
|
||||
}
|
||||
);
|
||||
|
||||
it('removes excess properties', () => {
|
||||
const decodedRes = type.decode({ description: 'test', 'not-exists': 'excess' });
|
||||
|
||||
expect(decodedRes._tag).toEqual('Right');
|
||||
// @ts-expect-error: the check above ensures that right exists
|
||||
expect(decodedRes.right).toEqual({ description: 'test' });
|
||||
});
|
||||
});
|
||||
|
||||
it('removes excess properties', () => {
|
||||
const decodedRes = type.decode({ description: 'test', 'not-exists': 'excess' });
|
||||
describe('OwnerRt', () => {
|
||||
it('strips excess fields from the result', () => {
|
||||
const res = decodeOrThrow(OwnerRt)({
|
||||
owner: 'yes',
|
||||
created_at: '123',
|
||||
});
|
||||
|
||||
expect(decodedRes._tag).toEqual('Right');
|
||||
// @ts-expect-error: the check above ensures that right exists
|
||||
expect(decodedRes.right).toEqual({ description: 'test' });
|
||||
expect(res).toStrictEqual({
|
||||
owner: 'yes',
|
||||
});
|
||||
});
|
||||
|
||||
it('throws an error when owner is not present', () => {
|
||||
expect(() => decodeOrThrow(OwnerRt)({})).toThrowErrorMatchingInlineSnapshot(
|
||||
`"Invalid value \\"undefined\\" supplied to \\"owner\\""`
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import type { SavedObject } from '@kbn/core-saved-objects-server';
|
||||
import type { Type } from 'io-ts';
|
||||
import { exact, partial } from 'io-ts';
|
||||
import { exact, partial, strict, string } from 'io-ts';
|
||||
import type { CaseAttributes } from '../../../common/api';
|
||||
import { CaseAttributesRt } from '../../../common/api';
|
||||
import type { ConnectorPersisted } from './connectors';
|
||||
|
@ -64,3 +64,5 @@ export const getPartialCaseTransformedAttributesRt = (): Type<Partial<CaseAttrib
|
|||
|
||||
export type CaseSavedObject = SavedObject<CasePersistedAttributes>;
|
||||
export type CaseSavedObjectTransformed = SavedObject<CaseTransformedAttributes>;
|
||||
|
||||
export const OwnerRt = strict({ owner: string });
|
||||
|
|
|
@ -29,7 +29,7 @@ export const findCommentsRoute = createCasesRoute({
|
|||
return response.ok({
|
||||
body: await client.attachments.find({
|
||||
caseID: request.params.case_id,
|
||||
queryParams: query,
|
||||
findQueryParams: query,
|
||||
}),
|
||||
});
|
||||
} catch (error) {
|
||||
|
|
|
@ -11,7 +11,10 @@ import { INTERNAL_DELETE_FILE_ATTACHMENTS_URL } from '../../../../common/constan
|
|||
import { createCasesRoute } from '../create_cases_route';
|
||||
import { createCaseError } from '../../../common/error';
|
||||
import { escapeHatch } from '../utils';
|
||||
import type { BulkDeleteFileAttachmentsRequest } from '../../../../common/api';
|
||||
import {
|
||||
BulkDeleteFileAttachmentsRequestRt,
|
||||
decodeWithExcessOrThrow,
|
||||
} from '../../../../common/api';
|
||||
|
||||
export const bulkDeleteFileAttachments = createCasesRoute({
|
||||
method: 'post',
|
||||
|
@ -27,7 +30,7 @@ export const bulkDeleteFileAttachments = createCasesRoute({
|
|||
const caseContext = await context.cases;
|
||||
const client = await caseContext.getCasesClient();
|
||||
|
||||
const requestBody = request.body as BulkDeleteFileAttachmentsRequest;
|
||||
const requestBody = decodeWithExcessOrThrow(BulkDeleteFileAttachmentsRequestRt)(request.body);
|
||||
|
||||
await client.attachments.bulkDeleteFileAttachments({
|
||||
caseId: request.params.case_id,
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import type { BulkGetAttachmentsRequest } from '../../../../common/api';
|
||||
import { BulkGetAttachmentsRequestRt, decodeWithExcessOrThrow } from '../../../../common/api';
|
||||
|
||||
import { INTERNAL_BULK_GET_ATTACHMENTS_URL } from '../../../../common/constants';
|
||||
import { createCaseError } from '../../../common/error';
|
||||
|
@ -26,12 +26,13 @@ export const bulkGetAttachmentsRoute = createCasesRoute({
|
|||
try {
|
||||
const caseContext = await context.cases;
|
||||
const client = await caseContext.getCasesClient();
|
||||
const body = request.body as BulkGetAttachmentsRequest;
|
||||
|
||||
const requestBody = decodeWithExcessOrThrow(BulkGetAttachmentsRequestRt)(request.body);
|
||||
|
||||
return response.ok({
|
||||
body: await client.attachments.bulkGet({
|
||||
caseID: request.params.case_id,
|
||||
attachmentIDs: body.ids,
|
||||
attachmentIDs: requestBody.ids,
|
||||
}),
|
||||
});
|
||||
} catch (error) {
|
||||
|
|
|
@ -13,10 +13,10 @@
|
|||
* connector.id.
|
||||
*/
|
||||
|
||||
import { omit } from 'lodash';
|
||||
import { omit, unset } from 'lodash';
|
||||
import type { CaseAttributes, CaseConnector, CaseFullExternalService } from '../../../common/api';
|
||||
import { CaseSeverity, CaseStatuses } from '../../../common/api';
|
||||
import { CASE_SAVED_OBJECT } from '../../../common/constants';
|
||||
import { CASE_SAVED_OBJECT, SECURITY_SOLUTION_OWNER } from '../../../common/constants';
|
||||
import { savedObjectsClientMock } from '@kbn/core/server/mocks';
|
||||
import type {
|
||||
SavedObject,
|
||||
|
@ -1882,6 +1882,48 @@ describe('CasesService', () => {
|
|||
'external_service'
|
||||
);
|
||||
|
||||
describe('getCaseIdsByAlertId', () => {
|
||||
it('strips excess fields', async () => {
|
||||
const findMockReturn = createSOFindResponse([createFindSO({ caseId: '2' })]);
|
||||
unsecuredSavedObjectsClient.find.mockResolvedValue(findMockReturn);
|
||||
|
||||
const res = await service.getCaseIdsByAlertId({ alertId: '1' });
|
||||
expect(res).toStrictEqual({
|
||||
saved_objects: [
|
||||
{
|
||||
id: '2',
|
||||
score: 0,
|
||||
references: [],
|
||||
type: CASE_SAVED_OBJECT,
|
||||
attributes: { owner: SECURITY_SOLUTION_OWNER },
|
||||
},
|
||||
],
|
||||
total: 1,
|
||||
per_page: 1,
|
||||
page: 1,
|
||||
});
|
||||
});
|
||||
|
||||
it('throws an error when the owner field is not present', async () => {
|
||||
const findMockReturn = createSOFindResponse([createFindSO({ caseId: '2' })]);
|
||||
unset(findMockReturn, 'saved_objects[0].attributes.owner');
|
||||
unsecuredSavedObjectsClient.find.mockResolvedValue(findMockReturn);
|
||||
|
||||
await expect(
|
||||
service.getCaseIdsByAlertId({ alertId: '1' })
|
||||
).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||
`"Invalid value \\"undefined\\" supplied to \\"owner\\""`
|
||||
);
|
||||
});
|
||||
|
||||
it('does not throw an error when the owner field exists', async () => {
|
||||
const findMockReturn = createSOFindResponse([createFindSO({ caseId: '2' })]);
|
||||
unsecuredSavedObjectsClient.find.mockResolvedValue(findMockReturn);
|
||||
|
||||
await expect(service.getCaseIdsByAlertId({ alertId: '1' })).resolves.not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getCase', () => {
|
||||
it('decodes correctly', async () => {
|
||||
unsecuredSavedObjectsClient.get.mockResolvedValue(createCaseSavedObjectResponse());
|
||||
|
@ -1902,13 +1944,67 @@ describe('CasesService', () => {
|
|||
}
|
||||
);
|
||||
|
||||
// TODO: Unskip when all types are converted to strict
|
||||
it.skip('strips out excess attributes', async () => {
|
||||
const theCase = createCaseSavedObjectResponse();
|
||||
it('strips out excess attributes', async () => {
|
||||
const theCase = createCaseSavedObjectResponse({
|
||||
connector: createESJiraConnector(),
|
||||
externalService: null,
|
||||
});
|
||||
const attributes = { ...theCase.attributes, 'not-exists': 'not-exists' };
|
||||
unsecuredSavedObjectsClient.get.mockResolvedValue({ ...theCase, attributes });
|
||||
|
||||
await expect(service.getCase({ id: 'a' })).resolves.toEqual({ attributes });
|
||||
await expect(service.getCase({ id: 'a' })).resolves.toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"attributes": Object {
|
||||
"assignees": Array [],
|
||||
"closed_at": null,
|
||||
"closed_by": null,
|
||||
"connector": Object {
|
||||
"fields": Object {
|
||||
"issueType": "bug",
|
||||
"parent": "2",
|
||||
"priority": "high",
|
||||
},
|
||||
"id": "1",
|
||||
"name": ".jira",
|
||||
"type": ".jira",
|
||||
},
|
||||
"created_at": "2019-11-25T21:54:48.952Z",
|
||||
"created_by": Object {
|
||||
"email": "testemail@elastic.co",
|
||||
"full_name": "elastic",
|
||||
"username": "elastic",
|
||||
},
|
||||
"description": "This is a brand new case of a bad meanie defacing data",
|
||||
"duration": null,
|
||||
"external_service": null,
|
||||
"owner": "securitySolution",
|
||||
"settings": Object {
|
||||
"syncAlerts": true,
|
||||
},
|
||||
"severity": "low",
|
||||
"status": "open",
|
||||
"tags": Array [
|
||||
"defacement",
|
||||
],
|
||||
"title": "Super Bad Security Issue",
|
||||
"updated_at": "2019-11-25T21:54:48.952Z",
|
||||
"updated_by": Object {
|
||||
"email": "testemail@elastic.co",
|
||||
"full_name": "elastic",
|
||||
"username": "elastic",
|
||||
},
|
||||
},
|
||||
"id": "1",
|
||||
"references": Array [
|
||||
Object {
|
||||
"id": "1",
|
||||
"name": "connectorId",
|
||||
"type": "action",
|
||||
},
|
||||
],
|
||||
"type": "cases",
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -1938,8 +2034,7 @@ describe('CasesService', () => {
|
|||
}
|
||||
);
|
||||
|
||||
// TODO: Unskip when all types are converted to strict
|
||||
it.skip('strips out excess attributes', async () => {
|
||||
it('strips out excess attributes', async () => {
|
||||
const theCase = createCaseSavedObjectResponse();
|
||||
const attributes = { ...theCase.attributes, 'not-exists': 'not-exists' };
|
||||
unsecuredSavedObjectsClient.resolve.mockResolvedValue({
|
||||
|
@ -1947,7 +2042,64 @@ describe('CasesService', () => {
|
|||
outcome: 'exactMatch',
|
||||
});
|
||||
|
||||
await expect(service.getResolveCase({ id: 'a' })).resolves.toEqual({ attributes });
|
||||
await expect(service.getResolveCase({ id: 'a' })).resolves.toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"outcome": "exactMatch",
|
||||
"saved_object": Object {
|
||||
"attributes": Object {
|
||||
"assignees": Array [],
|
||||
"closed_at": null,
|
||||
"closed_by": null,
|
||||
"connector": Object {
|
||||
"fields": null,
|
||||
"id": "none",
|
||||
"name": "none",
|
||||
"type": ".none",
|
||||
},
|
||||
"created_at": "2019-11-25T21:54:48.952Z",
|
||||
"created_by": Object {
|
||||
"email": "testemail@elastic.co",
|
||||
"full_name": "elastic",
|
||||
"username": "elastic",
|
||||
},
|
||||
"description": "This is a brand new case of a bad meanie defacing data",
|
||||
"duration": null,
|
||||
"external_service": Object {
|
||||
"connector_id": "none",
|
||||
"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",
|
||||
},
|
||||
},
|
||||
"owner": "securitySolution",
|
||||
"settings": Object {
|
||||
"syncAlerts": true,
|
||||
},
|
||||
"severity": "low",
|
||||
"status": "open",
|
||||
"tags": Array [
|
||||
"defacement",
|
||||
],
|
||||
"title": "Super Bad Security Issue",
|
||||
"updated_at": "2019-11-25T21:54:48.952Z",
|
||||
"updated_by": Object {
|
||||
"email": "testemail@elastic.co",
|
||||
"full_name": "elastic",
|
||||
"username": "elastic",
|
||||
},
|
||||
},
|
||||
"id": "1",
|
||||
"references": Array [],
|
||||
"type": "cases",
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -2065,15 +2217,72 @@ describe('CasesService', () => {
|
|||
}
|
||||
);
|
||||
|
||||
// TODO: Unskip when all types are converted to strict
|
||||
it.skip('strips out excess attributes', async () => {
|
||||
it('strips out excess attributes', async () => {
|
||||
const theCase = createCaseSavedObjectResponse();
|
||||
const attributes = { ...theCase.attributes, 'not-exists': 'not-exists' };
|
||||
unsecuredSavedObjectsClient.bulkGet.mockResolvedValue({
|
||||
saved_objects: [{ ...theCase, attributes }],
|
||||
});
|
||||
|
||||
await expect(service.getCases({ caseIds: ['a', 'b'] })).resolves.toEqual({ attributes });
|
||||
await expect(service.getCases({ caseIds: ['a', 'b'] })).resolves.toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"saved_objects": Array [
|
||||
Object {
|
||||
"attributes": Object {
|
||||
"assignees": Array [],
|
||||
"closed_at": null,
|
||||
"closed_by": null,
|
||||
"connector": Object {
|
||||
"fields": null,
|
||||
"id": "none",
|
||||
"name": "none",
|
||||
"type": ".none",
|
||||
},
|
||||
"created_at": "2019-11-25T21:54:48.952Z",
|
||||
"created_by": Object {
|
||||
"email": "testemail@elastic.co",
|
||||
"full_name": "elastic",
|
||||
"username": "elastic",
|
||||
},
|
||||
"description": "This is a brand new case of a bad meanie defacing data",
|
||||
"duration": null,
|
||||
"external_service": Object {
|
||||
"connector_id": "none",
|
||||
"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",
|
||||
},
|
||||
},
|
||||
"owner": "securitySolution",
|
||||
"settings": Object {
|
||||
"syncAlerts": true,
|
||||
},
|
||||
"severity": "low",
|
||||
"status": "open",
|
||||
"tags": Array [
|
||||
"defacement",
|
||||
],
|
||||
"title": "Super Bad Security Issue",
|
||||
"updated_at": "2019-11-25T21:54:48.952Z",
|
||||
"updated_by": Object {
|
||||
"email": "testemail@elastic.co",
|
||||
"full_name": "elastic",
|
||||
"username": "elastic",
|
||||
},
|
||||
},
|
||||
"id": "1",
|
||||
"references": Array [],
|
||||
"type": "cases",
|
||||
},
|
||||
],
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -2107,8 +2316,7 @@ describe('CasesService', () => {
|
|||
}
|
||||
);
|
||||
|
||||
// TODO: Unskip when all types are converted to strict
|
||||
it.skip('strips out excess attributes', async () => {
|
||||
it('strips out excess attributes', async () => {
|
||||
const theCase = createCaseSavedObjectResponse();
|
||||
const attributes = { ...theCase.attributes, 'not-exists': 'not-exists' };
|
||||
const findMockReturn = createSOFindResponse([
|
||||
|
@ -2118,7 +2326,123 @@ describe('CasesService', () => {
|
|||
|
||||
unsecuredSavedObjectsClient.find.mockResolvedValue(findMockReturn);
|
||||
|
||||
await expect(service.findCases()).resolves.toEqual({ attributes });
|
||||
await expect(service.findCases()).resolves.toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"page": 1,
|
||||
"per_page": 2,
|
||||
"saved_objects": Array [
|
||||
Object {
|
||||
"attributes": Object {
|
||||
"assignees": Array [],
|
||||
"closed_at": null,
|
||||
"closed_by": null,
|
||||
"connector": Object {
|
||||
"fields": null,
|
||||
"id": "none",
|
||||
"name": "none",
|
||||
"type": ".none",
|
||||
},
|
||||
"created_at": "2019-11-25T21:54:48.952Z",
|
||||
"created_by": Object {
|
||||
"email": "testemail@elastic.co",
|
||||
"full_name": "elastic",
|
||||
"username": "elastic",
|
||||
},
|
||||
"description": "This is a brand new case of a bad meanie defacing data",
|
||||
"duration": null,
|
||||
"external_service": Object {
|
||||
"connector_id": "none",
|
||||
"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",
|
||||
},
|
||||
},
|
||||
"owner": "securitySolution",
|
||||
"settings": Object {
|
||||
"syncAlerts": true,
|
||||
},
|
||||
"severity": "low",
|
||||
"status": "open",
|
||||
"tags": Array [
|
||||
"defacement",
|
||||
],
|
||||
"title": "Super Bad Security Issue",
|
||||
"updated_at": "2019-11-25T21:54:48.952Z",
|
||||
"updated_by": Object {
|
||||
"email": "testemail@elastic.co",
|
||||
"full_name": "elastic",
|
||||
"username": "elastic",
|
||||
},
|
||||
},
|
||||
"id": "1",
|
||||
"references": Array [],
|
||||
"score": 0,
|
||||
"type": "cases",
|
||||
},
|
||||
Object {
|
||||
"attributes": Object {
|
||||
"assignees": Array [],
|
||||
"closed_at": null,
|
||||
"closed_by": null,
|
||||
"connector": Object {
|
||||
"fields": null,
|
||||
"id": "none",
|
||||
"name": "none",
|
||||
"type": ".none",
|
||||
},
|
||||
"created_at": "2019-11-25T21:54:48.952Z",
|
||||
"created_by": Object {
|
||||
"email": "testemail@elastic.co",
|
||||
"full_name": "elastic",
|
||||
"username": "elastic",
|
||||
},
|
||||
"description": "This is a brand new case of a bad meanie defacing data",
|
||||
"duration": null,
|
||||
"external_service": Object {
|
||||
"connector_id": "none",
|
||||
"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",
|
||||
},
|
||||
},
|
||||
"owner": "securitySolution",
|
||||
"settings": Object {
|
||||
"syncAlerts": true,
|
||||
},
|
||||
"severity": "low",
|
||||
"status": "open",
|
||||
"tags": Array [
|
||||
"defacement",
|
||||
],
|
||||
"title": "Super Bad Security Issue",
|
||||
"updated_at": "2019-11-25T21:54:48.952Z",
|
||||
"updated_by": Object {
|
||||
"email": "testemail@elastic.co",
|
||||
"full_name": "elastic",
|
||||
"username": "elastic",
|
||||
},
|
||||
},
|
||||
"id": "1",
|
||||
"references": Array [],
|
||||
"score": 0,
|
||||
"type": "cases",
|
||||
},
|
||||
],
|
||||
"total": 2,
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -2150,8 +2474,7 @@ describe('CasesService', () => {
|
|||
}
|
||||
);
|
||||
|
||||
// TODO: Unskip when all types are converted to strict
|
||||
it.skip('strips out excess attributes', async () => {
|
||||
it('strips out excess attributes', async () => {
|
||||
const theCase = createCaseSavedObjectResponse();
|
||||
const attributes = { ...theCase.attributes, 'not-exists': 'not-exists' };
|
||||
unsecuredSavedObjectsClient.create.mockResolvedValue({ ...theCase, attributes });
|
||||
|
@ -2161,7 +2484,61 @@ describe('CasesService', () => {
|
|||
attributes: createCasePostParams({ connector: createJiraConnector() }),
|
||||
id: '1',
|
||||
})
|
||||
).resolves.toEqual({ attributes });
|
||||
).resolves.toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"attributes": Object {
|
||||
"assignees": Array [],
|
||||
"closed_at": null,
|
||||
"closed_by": null,
|
||||
"connector": Object {
|
||||
"fields": null,
|
||||
"id": "none",
|
||||
"name": "none",
|
||||
"type": ".none",
|
||||
},
|
||||
"created_at": "2019-11-25T21:54:48.952Z",
|
||||
"created_by": Object {
|
||||
"email": "testemail@elastic.co",
|
||||
"full_name": "elastic",
|
||||
"username": "elastic",
|
||||
},
|
||||
"description": "This is a brand new case of a bad meanie defacing data",
|
||||
"duration": null,
|
||||
"external_service": Object {
|
||||
"connector_id": "none",
|
||||
"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",
|
||||
},
|
||||
},
|
||||
"owner": "securitySolution",
|
||||
"settings": Object {
|
||||
"syncAlerts": true,
|
||||
},
|
||||
"severity": "low",
|
||||
"status": "open",
|
||||
"tags": Array [
|
||||
"defacement",
|
||||
],
|
||||
"title": "Super Bad Security Issue",
|
||||
"updated_at": "2019-11-25T21:54:48.952Z",
|
||||
"updated_by": Object {
|
||||
"email": "testemail@elastic.co",
|
||||
"full_name": "elastic",
|
||||
"username": "elastic",
|
||||
},
|
||||
},
|
||||
"id": "1",
|
||||
"references": Array [],
|
||||
"type": "cases",
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -53,6 +53,7 @@ import {
|
|||
CaseTransformedAttributesRt,
|
||||
CasePersistedStatus,
|
||||
getPartialCaseTransformedAttributesRt,
|
||||
OwnerRt,
|
||||
} from '../../common/types/case';
|
||||
import type {
|
||||
GetCaseIdsByAlertIdArgs,
|
||||
|
@ -134,7 +135,15 @@ export class CasesService {
|
|||
aggs: this.buildCaseIdsAggs(MAX_DOCS_PER_PAGE),
|
||||
filter: combinedFilter,
|
||||
});
|
||||
return response;
|
||||
|
||||
const owners: Array<SavedObjectsFindResult<{ owner: string }>> = [];
|
||||
for (const so of response.saved_objects) {
|
||||
const validatedAttributes = decodeOrThrow(OwnerRt)(so.attributes);
|
||||
|
||||
owners.push(Object.assign(so, { attributes: validatedAttributes }));
|
||||
}
|
||||
|
||||
return Object.assign(response, { saved_objects: owners });
|
||||
} catch (error) {
|
||||
this.log.error(`Error on GET all cases for alert id ${alertId}: ${error}`);
|
||||
throw error;
|
||||
|
@ -199,7 +208,7 @@ export class CasesService {
|
|||
[status in CaseStatuses]: number;
|
||||
}> {
|
||||
const cases = await this.unsecuredSavedObjectsClient.find<
|
||||
CasePersistedAttributes,
|
||||
unknown,
|
||||
{
|
||||
statuses: {
|
||||
buckets: Array<{
|
||||
|
@ -459,7 +468,7 @@ export class CasesService {
|
|||
this.log.debug(`Attempting to GET all reporters`);
|
||||
|
||||
const results = await this.unsecuredSavedObjectsClient.find<
|
||||
CasePersistedAttributes,
|
||||
unknown,
|
||||
{
|
||||
reporters: {
|
||||
buckets: Array<{
|
||||
|
@ -521,7 +530,7 @@ export class CasesService {
|
|||
this.log.debug(`Attempting to GET all cases`);
|
||||
|
||||
const results = await this.unsecuredSavedObjectsClient.find<
|
||||
CasePersistedAttributes,
|
||||
unknown,
|
||||
{ tags: { buckets: Array<{ key: string }> } }
|
||||
>({
|
||||
type: CASE_SAVED_OBJECT,
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { set, omit } from 'lodash';
|
||||
import { set, omit, unset } from 'lodash';
|
||||
import { loggerMock } from '@kbn/logging-mocks';
|
||||
import { savedObjectsClientMock } from '@kbn/core/server/mocks';
|
||||
import type {
|
||||
|
@ -1599,6 +1599,60 @@ describe('CaseUserActionService', () => {
|
|||
|
||||
const pushes = [{ date: new Date(), connectorId: '123' }];
|
||||
|
||||
describe('getAll', () => {
|
||||
it('does not throw when the required fields are present', async () => {
|
||||
unsecuredSavedObjectsClient.find.mockResolvedValue(
|
||||
createSOFindResponse([{ ...createUserActionSO(), score: 0 }])
|
||||
);
|
||||
|
||||
await expect(service.getAll('1')).resolves.not.toThrow();
|
||||
});
|
||||
|
||||
it('throws when payload does not exist', async () => {
|
||||
const findMockReturn = createSOFindResponse([{ ...createUserActionSO(), score: 0 }]);
|
||||
unset(findMockReturn, 'saved_objects[0].attributes.payload');
|
||||
|
||||
unsecuredSavedObjectsClient.find.mockResolvedValue(findMockReturn);
|
||||
|
||||
await expect(service.getAll('1')).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||
`"Invalid value \\"undefined\\" supplied to \\"payload\\""`
|
||||
);
|
||||
});
|
||||
|
||||
it('strips excess fields', async () => {
|
||||
unsecuredSavedObjectsClient.find.mockResolvedValue(
|
||||
createSOFindResponse([
|
||||
{
|
||||
...createUserActionSO({
|
||||
attributesOverrides: {
|
||||
// @ts-expect-error foo is not a valid field for attributesOverrides
|
||||
foo: 'bar',
|
||||
},
|
||||
}),
|
||||
score: 0,
|
||||
},
|
||||
])
|
||||
);
|
||||
|
||||
const res = await service.getAll('1');
|
||||
expect(res).toStrictEqual(
|
||||
createSOFindResponse([
|
||||
{
|
||||
...createUserActionSO({
|
||||
attributesOverrides: {
|
||||
// @ts-expect-error these fields are populated by the legacy transformation logic but aren't valid for the override type
|
||||
action_id: '100',
|
||||
case_id: '1',
|
||||
comment_id: null,
|
||||
},
|
||||
}),
|
||||
score: 0,
|
||||
},
|
||||
])
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getConnectorFieldsBeforeLatestPush', () => {
|
||||
const getAggregations = (
|
||||
userAction: SavedObject<CaseUserActionAttributesWithoutConnectorId>
|
||||
|
@ -1677,7 +1731,7 @@ describe('CaseUserActionService', () => {
|
|||
);
|
||||
});
|
||||
|
||||
it.skip('strips out excess attributes', async () => {
|
||||
it('strips out excess attributes', async () => {
|
||||
const userAction = createUserActionSO();
|
||||
const attributes = { ...userAction.attributes, 'not-exists': 'not-exists' };
|
||||
const userActionWithExtraAttributes = { ...userAction, attributes, score: 0 };
|
||||
|
@ -1687,9 +1741,38 @@ describe('CaseUserActionService', () => {
|
|||
unsecuredSavedObjectsClient.find.mockResolvedValue({ ...soFindRes, aggregations });
|
||||
soSerializerMock.rawToSavedObject.mockReturnValue(userActionWithExtraAttributes);
|
||||
|
||||
await expect(service.getConnectorFieldsBeforeLatestPush('1', pushes)).resolves.toEqual({
|
||||
attributes: userAction.attributes,
|
||||
});
|
||||
await expect(service.getConnectorFieldsBeforeLatestPush('1', pushes)).resolves
|
||||
.toMatchInlineSnapshot(`
|
||||
Map {
|
||||
"servicenow" => Object {
|
||||
"attributes": Object {
|
||||
"action": "create",
|
||||
"comment_id": null,
|
||||
"created_at": "abc",
|
||||
"created_by": Object {
|
||||
"email": "a",
|
||||
"full_name": "abc",
|
||||
"username": "b",
|
||||
},
|
||||
"owner": "securitySolution",
|
||||
"payload": Object {
|
||||
"title": "a new title",
|
||||
},
|
||||
"type": "title",
|
||||
},
|
||||
"id": "100",
|
||||
"references": Array [
|
||||
Object {
|
||||
"id": "1",
|
||||
"name": "associated-cases",
|
||||
"type": "cases",
|
||||
},
|
||||
],
|
||||
"score": 0,
|
||||
"type": "cases-user-actions",
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -1738,16 +1821,41 @@ describe('CaseUserActionService', () => {
|
|||
);
|
||||
});
|
||||
|
||||
// TODO: Unskip when all types are converted to strict
|
||||
it.skip('strips out excess attributes', async () => {
|
||||
it('strips out excess attributes', async () => {
|
||||
const userAction = createUserActionSO();
|
||||
const attributes = { ...userAction.attributes, 'not-exists': 'not-exists' };
|
||||
const soFindRes = createSOFindResponse([{ ...userAction, attributes, score: 0 }]);
|
||||
unsecuredSavedObjectsClient.find.mockResolvedValue(soFindRes);
|
||||
|
||||
await expect(service.getMostRecentUserAction('123')).resolves.toEqual({
|
||||
attributes: userAction.attributes,
|
||||
});
|
||||
await expect(service.getMostRecentUserAction('123')).resolves.toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"attributes": Object {
|
||||
"action": "create",
|
||||
"comment_id": null,
|
||||
"created_at": "abc",
|
||||
"created_by": Object {
|
||||
"email": "a",
|
||||
"full_name": "abc",
|
||||
"username": "b",
|
||||
},
|
||||
"owner": "securitySolution",
|
||||
"payload": Object {
|
||||
"title": "a new title",
|
||||
},
|
||||
"type": "title",
|
||||
},
|
||||
"id": "100",
|
||||
"references": Array [
|
||||
Object {
|
||||
"id": "1",
|
||||
"name": "associated-cases",
|
||||
"type": "cases",
|
||||
},
|
||||
],
|
||||
"score": 0,
|
||||
"type": "cases-user-actions",
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -1840,7 +1948,7 @@ describe('CaseUserActionService', () => {
|
|||
);
|
||||
});
|
||||
|
||||
it.skip('strips out excess attributes', async () => {
|
||||
it('strips out excess attributes', async () => {
|
||||
const userAction = createUserActionSO();
|
||||
const pushUserAction = pushConnectorUserAction();
|
||||
const attributes = { ...userAction.attributes, 'not-exists': 'not-exists' };
|
||||
|
@ -1851,9 +1959,97 @@ describe('CaseUserActionService', () => {
|
|||
unsecuredSavedObjectsClient.find.mockResolvedValue({ ...soFindRes, aggregations });
|
||||
soSerializerMock.rawToSavedObject.mockReturnValue(userActionWithExtraAttributes);
|
||||
|
||||
await expect(service.getCaseConnectorInformation('1')).resolves.toEqual({
|
||||
attributes: userAction.attributes,
|
||||
});
|
||||
await expect(service.getCaseConnectorInformation('1')).resolves
|
||||
.toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"connectorId": undefined,
|
||||
"fields": Object {
|
||||
"attributes": Object {
|
||||
"action": "create",
|
||||
"comment_id": null,
|
||||
"created_at": "abc",
|
||||
"created_by": Object {
|
||||
"email": "a",
|
||||
"full_name": "abc",
|
||||
"username": "b",
|
||||
},
|
||||
"owner": "securitySolution",
|
||||
"payload": Object {
|
||||
"title": "a new title",
|
||||
},
|
||||
"type": "title",
|
||||
},
|
||||
"id": "100",
|
||||
"references": Array [
|
||||
Object {
|
||||
"id": "1",
|
||||
"name": "associated-cases",
|
||||
"type": "cases",
|
||||
},
|
||||
],
|
||||
"score": 0,
|
||||
"type": "cases-user-actions",
|
||||
},
|
||||
"push": Object {
|
||||
"mostRecent": Object {
|
||||
"attributes": Object {
|
||||
"action": "create",
|
||||
"comment_id": null,
|
||||
"created_at": "abc",
|
||||
"created_by": Object {
|
||||
"email": "a",
|
||||
"full_name": "abc",
|
||||
"username": "b",
|
||||
},
|
||||
"owner": "securitySolution",
|
||||
"payload": Object {
|
||||
"title": "a new title",
|
||||
},
|
||||
"type": "title",
|
||||
},
|
||||
"id": "100",
|
||||
"references": Array [
|
||||
Object {
|
||||
"id": "1",
|
||||
"name": "associated-cases",
|
||||
"type": "cases",
|
||||
},
|
||||
],
|
||||
"score": 0,
|
||||
"type": "cases-user-actions",
|
||||
},
|
||||
"oldest": Object {
|
||||
"attributes": Object {
|
||||
"action": "create",
|
||||
"comment_id": null,
|
||||
"created_at": "abc",
|
||||
"created_by": Object {
|
||||
"email": "a",
|
||||
"full_name": "abc",
|
||||
"username": "b",
|
||||
},
|
||||
"owner": "securitySolution",
|
||||
"payload": Object {
|
||||
"title": "a new title",
|
||||
},
|
||||
"type": "title",
|
||||
},
|
||||
"id": "100",
|
||||
"references": Array [
|
||||
Object {
|
||||
"id": "1",
|
||||
"name": "associated-cases",
|
||||
"type": "cases",
|
||||
},
|
||||
],
|
||||
"score": 0,
|
||||
"type": "cases-user-actions",
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -1945,21 +2141,143 @@ describe('CaseUserActionService', () => {
|
|||
);
|
||||
});
|
||||
|
||||
it.skip('strips out excess attributes', async () => {
|
||||
it('strips out excess attributes', async () => {
|
||||
const userAction = createUserActionSO();
|
||||
const pushUserAction = pushConnectorUserAction();
|
||||
const attributes = { ...pushUserAction.attributes, 'not-exists': 'not-exists' };
|
||||
const pushActionWithExtraAttributes = { ...userAction, attributes, score: 0 };
|
||||
const pushActionWithExtraAttributes = { ...pushUserAction, attributes, score: 0 };
|
||||
const aggregations = getAggregations(userAction, pushActionWithExtraAttributes);
|
||||
const soFindRes = createSOFindResponse([{ ...userAction, score: 0 }]);
|
||||
|
||||
unsecuredSavedObjectsClient.find.mockResolvedValue({ ...soFindRes, aggregations });
|
||||
soSerializerMock.rawToSavedObject.mockReturnValueOnce(userAction);
|
||||
soSerializerMock.rawToSavedObject.mockReturnValueOnce(pushActionWithExtraAttributes);
|
||||
soSerializerMock.rawToSavedObject.mockReturnValueOnce(pushActionWithExtraAttributes);
|
||||
|
||||
await expect(service.getCaseConnectorInformation('1')).resolves.toEqual({
|
||||
attributes: userAction.attributes,
|
||||
});
|
||||
await expect(service.getCaseConnectorInformation('1')).resolves
|
||||
.toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"connectorId": undefined,
|
||||
"fields": Object {
|
||||
"attributes": Object {
|
||||
"action": "create",
|
||||
"comment_id": null,
|
||||
"created_at": "abc",
|
||||
"created_by": Object {
|
||||
"email": "a",
|
||||
"full_name": "abc",
|
||||
"username": "b",
|
||||
},
|
||||
"owner": "securitySolution",
|
||||
"payload": Object {
|
||||
"title": "a new title",
|
||||
},
|
||||
"type": "title",
|
||||
},
|
||||
"id": "100",
|
||||
"references": Array [
|
||||
Object {
|
||||
"id": "1",
|
||||
"name": "associated-cases",
|
||||
"type": "cases",
|
||||
},
|
||||
],
|
||||
"type": "cases-user-actions",
|
||||
},
|
||||
"push": Object {
|
||||
"mostRecent": Object {
|
||||
"attributes": Object {
|
||||
"action": "push_to_service",
|
||||
"comment_id": null,
|
||||
"created_at": "abc",
|
||||
"created_by": Object {
|
||||
"email": "a",
|
||||
"full_name": "abc",
|
||||
"username": "b",
|
||||
},
|
||||
"owner": "securitySolution",
|
||||
"payload": Object {
|
||||
"externalService": Object {
|
||||
"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": Object {
|
||||
"email": "testemail@elastic.co",
|
||||
"full_name": "elastic",
|
||||
"username": "elastic",
|
||||
},
|
||||
},
|
||||
},
|
||||
"type": "pushed",
|
||||
},
|
||||
"id": "100",
|
||||
"references": Array [
|
||||
Object {
|
||||
"id": "1",
|
||||
"name": "associated-cases",
|
||||
"type": "cases",
|
||||
},
|
||||
Object {
|
||||
"id": "100",
|
||||
"name": "pushConnectorId",
|
||||
"type": "action",
|
||||
},
|
||||
],
|
||||
"score": 0,
|
||||
"type": "cases-user-actions",
|
||||
},
|
||||
"oldest": Object {
|
||||
"attributes": Object {
|
||||
"action": "push_to_service",
|
||||
"comment_id": null,
|
||||
"created_at": "abc",
|
||||
"created_by": Object {
|
||||
"email": "a",
|
||||
"full_name": "abc",
|
||||
"username": "b",
|
||||
},
|
||||
"owner": "securitySolution",
|
||||
"payload": Object {
|
||||
"externalService": Object {
|
||||
"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": Object {
|
||||
"email": "testemail@elastic.co",
|
||||
"full_name": "elastic",
|
||||
"username": "elastic",
|
||||
},
|
||||
},
|
||||
},
|
||||
"type": "pushed",
|
||||
},
|
||||
"id": "100",
|
||||
"references": Array [
|
||||
Object {
|
||||
"id": "1",
|
||||
"name": "associated-cases",
|
||||
"type": "cases",
|
||||
},
|
||||
Object {
|
||||
"id": "100",
|
||||
"name": "pushConnectorId",
|
||||
"type": "action",
|
||||
},
|
||||
],
|
||||
"score": 0,
|
||||
"type": "cases-user-actions",
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,12 +5,20 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { SavedObjectsFindResponse, SavedObjectsRawDoc } from '@kbn/core/server';
|
||||
import type {
|
||||
SavedObjectsFindResponse,
|
||||
SavedObjectsFindResult,
|
||||
SavedObjectsRawDoc,
|
||||
} from '@kbn/core/server';
|
||||
|
||||
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
import type { KueryNode } from '@kbn/es-query';
|
||||
import type { CaseUserActionDeprecatedResponse } from '../../../common/api';
|
||||
import { decodeOrThrow, ActionTypes } from '../../../common/api';
|
||||
import {
|
||||
decodeOrThrow,
|
||||
ActionTypes,
|
||||
CaseUserActionDeprecatedResponseRt,
|
||||
} from '../../../common/api';
|
||||
import {
|
||||
CASE_SAVED_OBJECT,
|
||||
CASE_USER_ACTION_SAVED_OBJECT,
|
||||
|
@ -515,10 +523,22 @@ export class CaseUserActionService {
|
|||
sortOrder: 'asc',
|
||||
});
|
||||
|
||||
return legacyTransformFindResponseToExternalModel(
|
||||
const transformedUserActions = legacyTransformFindResponseToExternalModel(
|
||||
userActions,
|
||||
this.context.persistableStateAttachmentTypeRegistry
|
||||
);
|
||||
|
||||
const validatedUserActions: Array<SavedObjectsFindResult<CaseUserActionDeprecatedResponse>> =
|
||||
[];
|
||||
for (const so of transformedUserActions.saved_objects) {
|
||||
const validatedAttributes = decodeOrThrow(CaseUserActionDeprecatedResponseRt)(
|
||||
so.attributes
|
||||
);
|
||||
|
||||
validatedUserActions.push(Object.assign(so, { attributes: validatedAttributes }));
|
||||
}
|
||||
|
||||
return Object.assign(transformedUserActions, { saved_objects: validatedUserActions });
|
||||
} catch (error) {
|
||||
this.context.log.error(`Error on GET case user action case id: ${caseId}: ${error}`);
|
||||
throw error;
|
||||
|
@ -636,7 +656,7 @@ export class CaseUserActionService {
|
|||
|
||||
public async getCaseUserActionStats({ caseId }: { caseId: string }) {
|
||||
const response = await this.context.unsecuredSavedObjectsClient.find<
|
||||
UserActionPersistedAttributes,
|
||||
unknown,
|
||||
UserActionsStatsAggsResult
|
||||
>({
|
||||
type: CASE_USER_ACTION_SAVED_OBJECT,
|
||||
|
@ -680,7 +700,7 @@ export class CaseUserActionService {
|
|||
|
||||
public async getUsers({ caseId }: { caseId: string }): Promise<GetUsersResponse> {
|
||||
const response = await this.context.unsecuredSavedObjectsClient.find<
|
||||
UserActionPersistedAttributes,
|
||||
unknown,
|
||||
ParticipantsAggsResult
|
||||
>({
|
||||
type: CASE_USER_ACTION_SAVED_OBJECT,
|
||||
|
|
70
x-pack/plugins/cases/server/services/user_actions/operations/__snapshots__/find.test.ts.snap
generated
Normal file
70
x-pack/plugins/cases/server/services/user_actions/operations/__snapshots__/find.test.ts.snap
generated
Normal file
|
@ -0,0 +1,70 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`UserActionsService: Finder Decoding: find strips out excess attributes 1`] = `
|
||||
Object {
|
||||
"page": 1,
|
||||
"per_page": 1,
|
||||
"saved_objects": Array [
|
||||
Object {
|
||||
"attributes": Object {
|
||||
"action": "create",
|
||||
"comment_id": null,
|
||||
"created_at": "abc",
|
||||
"created_by": Object {
|
||||
"email": "a",
|
||||
"full_name": "abc",
|
||||
"username": "b",
|
||||
},
|
||||
"owner": "securitySolution",
|
||||
"payload": Object {
|
||||
"title": "a new title",
|
||||
},
|
||||
"type": "title",
|
||||
},
|
||||
"id": "100",
|
||||
"references": Array [
|
||||
Object {
|
||||
"id": "1",
|
||||
"name": "associated-cases",
|
||||
"type": "cases",
|
||||
},
|
||||
],
|
||||
"score": 0,
|
||||
"type": "cases-user-actions",
|
||||
},
|
||||
],
|
||||
"total": 1,
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`UserActionsService: Finder Decoding: findStatusChanges strips out excess attributes 1`] = `
|
||||
Array [
|
||||
Object {
|
||||
"attributes": Object {
|
||||
"action": "create",
|
||||
"comment_id": null,
|
||||
"created_at": "abc",
|
||||
"created_by": Object {
|
||||
"email": "a",
|
||||
"full_name": "abc",
|
||||
"username": "b",
|
||||
},
|
||||
"owner": "securitySolution",
|
||||
"payload": Object {
|
||||
"title": "a new title",
|
||||
},
|
||||
"type": "title",
|
||||
},
|
||||
"id": "100",
|
||||
"references": Array [
|
||||
Object {
|
||||
"id": "1",
|
||||
"name": "associated-cases",
|
||||
"type": "cases",
|
||||
},
|
||||
],
|
||||
"score": 0,
|
||||
"type": "cases-user-actions",
|
||||
},
|
||||
]
|
||||
`;
|
|
@ -135,16 +135,13 @@ describe('UserActionsService: Finder', () => {
|
|||
);
|
||||
});
|
||||
|
||||
// TODO: Unskip when all types are converted to strict
|
||||
it.skip('strips out excess attributes', async () => {
|
||||
it('strips out excess attributes', async () => {
|
||||
const userAction = createUserActionSO();
|
||||
const attributes = { ...userAction.attributes, 'not-exists': 'not-exists' };
|
||||
const soFindRes = createSOFindResponse([{ ...userAction, attributes, score: 0 }]);
|
||||
method(soFindRes);
|
||||
|
||||
await expect(finder[soMethodName]({ caseId: '1' })).resolves.toEqual({
|
||||
attributes: userAction.attributes,
|
||||
});
|
||||
await expect(finder[soMethodName]({ caseId: '1' })).resolves.toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -70,11 +70,11 @@ export class UserProfileService {
|
|||
}
|
||||
|
||||
public async suggest(request: KibanaRequest): Promise<UserProfile[]> {
|
||||
const params = decodeWithExcessOrThrow(SuggestUserProfilesRequestRt)(request.body);
|
||||
|
||||
const { name, size, owners } = params;
|
||||
|
||||
try {
|
||||
const params = decodeWithExcessOrThrow(SuggestUserProfilesRequestRt)(request.body);
|
||||
|
||||
const { name, size, owners } = params;
|
||||
|
||||
this.validateInitialization();
|
||||
|
||||
const licensingService = new LicensingService(
|
||||
|
@ -110,7 +110,7 @@ export class UserProfileService {
|
|||
} catch (error) {
|
||||
throw createCaseError({
|
||||
logger: this.logger,
|
||||
message: `Failed to retrieve suggested user profiles in service for name: ${name} owners: [${owners}]: ${error}`,
|
||||
message: `Failed to retrieve suggested user profiles in service: ${error}`,
|
||||
error,
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue