mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Response Ops][Cases] Refactoring tech debt after sub cases removal (#124181)
* Starting aggs refactor * Removing so client from params adding aggs * Refactoring some comments * Addressing feedback * asArray returns empty array * Fixing type error
This commit is contained in:
parent
d9aa72c7f8
commit
d9d3230b94
26 changed files with 390 additions and 373 deletions
|
@ -19,6 +19,10 @@ import { sortOrderSchema } from './common_schemas';
|
|||
* - reverse_nested
|
||||
* - terms
|
||||
*
|
||||
* Not fully supported:
|
||||
* - filter
|
||||
* - filters
|
||||
*
|
||||
* Not implemented:
|
||||
* - adjacency_matrix
|
||||
* - auto_date_histogram
|
||||
|
@ -27,7 +31,6 @@ import { sortOrderSchema } from './common_schemas';
|
|||
* - date_histogram
|
||||
* - date_range
|
||||
* - diversified_sampler
|
||||
* - filters
|
||||
* - geo_distance
|
||||
* - geohash_grid
|
||||
* - geotile_grid
|
||||
|
@ -44,9 +47,26 @@ import { sortOrderSchema } from './common_schemas';
|
|||
* - variable_width_histogram
|
||||
*/
|
||||
|
||||
// TODO: it would be great if we could recursively build the schema since the aggregation have be nested
|
||||
// For more details see how the types are defined in the elasticsearch javascript client:
|
||||
// https://github.com/elastic/elasticsearch-js/blob/4ad5daeaf401ce8ebb28b940075e0a67e56ff9ce/src/api/typesWithBodyKey.ts#L5295
|
||||
const termSchema = s.object({
|
||||
term: s.recordOf(s.string(), s.oneOf([s.string(), s.boolean(), s.number()])),
|
||||
});
|
||||
|
||||
// TODO: it would be great if we could recursively build the schema since the aggregation have be nested
|
||||
// For more details see how the types are defined in the elasticsearch javascript client:
|
||||
// https://github.com/elastic/elasticsearch-js/blob/4ad5daeaf401ce8ebb28b940075e0a67e56ff9ce/src/api/typesWithBodyKey.ts#L5295
|
||||
const boolSchema = s.object({
|
||||
bool: s.object({
|
||||
must_not: s.oneOf([termSchema]),
|
||||
}),
|
||||
});
|
||||
|
||||
export const bucketAggsSchemas: Record<string, ObjectType> = {
|
||||
filter: s.object({
|
||||
term: s.recordOf(s.string(), s.oneOf([s.string(), s.boolean(), s.number()])),
|
||||
filter: termSchema,
|
||||
filters: s.object({
|
||||
filters: s.recordOf(s.string(), s.oneOf([termSchema, boolSchema])),
|
||||
}),
|
||||
histogram: s.object({
|
||||
field: s.maybe(s.string()),
|
||||
|
|
|
@ -53,7 +53,6 @@ async function createCommentableCase({
|
|||
lensEmbeddableFactory: LensServerPluginSetup['lensEmbeddableFactory'];
|
||||
}): Promise<CommentableCase> {
|
||||
const caseInfo = await caseService.getCase({
|
||||
unsecuredSavedObjectsClient,
|
||||
id,
|
||||
});
|
||||
|
||||
|
|
|
@ -60,7 +60,6 @@ export async function deleteAll(
|
|||
|
||||
try {
|
||||
const comments = await caseService.getAllCaseComments({
|
||||
unsecuredSavedObjectsClient,
|
||||
id: caseID,
|
||||
});
|
||||
|
||||
|
|
|
@ -250,7 +250,7 @@ export async function getAll(
|
|||
{ caseID }: GetAllArgs,
|
||||
clientArgs: CasesClientArgs
|
||||
): Promise<AllCommentsResponse> {
|
||||
const { unsecuredSavedObjectsClient, caseService, logger, authorization } = clientArgs;
|
||||
const { caseService, logger, authorization } = clientArgs;
|
||||
|
||||
try {
|
||||
const { filter, ensureSavedObjectsAreAuthorized } = await authorization.getAuthorizationFilter(
|
||||
|
@ -258,7 +258,6 @@ export async function getAll(
|
|||
);
|
||||
|
||||
const comments = await caseService.getAllCaseComments({
|
||||
unsecuredSavedObjectsClient,
|
||||
id: caseID,
|
||||
options: {
|
||||
filter,
|
||||
|
|
|
@ -50,7 +50,6 @@ async function createCommentableCase({
|
|||
lensEmbeddableFactory,
|
||||
}: CombinedCaseParams) {
|
||||
const caseInfo = await caseService.getCase({
|
||||
unsecuredSavedObjectsClient,
|
||||
id: caseID,
|
||||
});
|
||||
|
||||
|
|
|
@ -73,7 +73,6 @@ export const create = async (
|
|||
});
|
||||
|
||||
const newCase = await caseService.postNewCase({
|
||||
unsecuredSavedObjectsClient,
|
||||
attributes: transformNewCase({
|
||||
user,
|
||||
newCase: query,
|
||||
|
|
|
@ -30,7 +30,7 @@ export async function deleteCases(ids: string[], clientArgs: CasesClientArgs): P
|
|||
authorization,
|
||||
} = clientArgs;
|
||||
try {
|
||||
const cases = await caseService.getCases({ unsecuredSavedObjectsClient, caseIds: ids });
|
||||
const cases = await caseService.getCases({ caseIds: ids });
|
||||
const entities = new Map<string, OwnerEntity>();
|
||||
|
||||
for (const theCase of cases.saved_objects) {
|
||||
|
@ -52,7 +52,6 @@ export async function deleteCases(ids: string[], clientArgs: CasesClientArgs): P
|
|||
|
||||
const deleteCasesMapper = async (id: string) =>
|
||||
caseService.deleteCase({
|
||||
unsecuredSavedObjectsClient,
|
||||
id,
|
||||
});
|
||||
|
||||
|
@ -63,7 +62,6 @@ export async function deleteCases(ids: string[], clientArgs: CasesClientArgs): P
|
|||
|
||||
const getCommentsMapper = async (id: string) =>
|
||||
caseService.getAllCaseComments({
|
||||
unsecuredSavedObjectsClient,
|
||||
id,
|
||||
});
|
||||
|
||||
|
|
|
@ -15,13 +15,12 @@ import {
|
|||
CasesFindRequest,
|
||||
CasesFindRequestRt,
|
||||
throwErrors,
|
||||
caseStatuses,
|
||||
CasesFindResponseRt,
|
||||
excess,
|
||||
} from '../../../common/api';
|
||||
|
||||
import { createCaseError } from '../../common/error';
|
||||
import { transformCases } from '../../common/utils';
|
||||
import { asArray, transformCases } from '../../common/utils';
|
||||
import { constructQueryOptions } from '../utils';
|
||||
import { includeFieldsRequiredForAuthentication } from '../../authorization/utils';
|
||||
import { Operations } from '../../authorization';
|
||||
|
@ -36,7 +35,7 @@ export const find = async (
|
|||
params: CasesFindRequest,
|
||||
clientArgs: CasesClientArgs
|
||||
): Promise<CasesFindResponse> => {
|
||||
const { unsecuredSavedObjectsClient, caseService, authorization, logger } = clientArgs;
|
||||
const { caseService, authorization, logger } = clientArgs;
|
||||
|
||||
try {
|
||||
const queryParams = pipe(
|
||||
|
@ -55,45 +54,38 @@ export const find = async (
|
|||
owner: queryParams.owner,
|
||||
};
|
||||
|
||||
const caseQueries = constructQueryOptions({ ...queryArgs, authorizationFilter });
|
||||
const cases = await caseService.findCasesGroupedByID({
|
||||
unsecuredSavedObjectsClient,
|
||||
caseOptions: {
|
||||
...queryParams,
|
||||
...caseQueries,
|
||||
searchFields:
|
||||
queryParams.searchFields != null
|
||||
? Array.isArray(queryParams.searchFields)
|
||||
? queryParams.searchFields
|
||||
: [queryParams.searchFields]
|
||||
: queryParams.searchFields,
|
||||
fields: includeFieldsRequiredForAuthentication(queryParams.fields),
|
||||
},
|
||||
const statusStatsOptions = constructQueryOptions({
|
||||
...queryArgs,
|
||||
status: undefined,
|
||||
authorizationFilter,
|
||||
});
|
||||
const caseQueryOptions = constructQueryOptions({ ...queryArgs, authorizationFilter });
|
||||
|
||||
ensureSavedObjectsAreAuthorized([...cases.casesMap.values()]);
|
||||
|
||||
// casesStatuses are bounded by us. No need to limit concurrent calls.
|
||||
const [openCases, inProgressCases, closedCases] = await Promise.all([
|
||||
...caseStatuses.map((status) => {
|
||||
const statusQuery = constructQueryOptions({ ...queryArgs, status, authorizationFilter });
|
||||
return caseService.findCaseStatusStats({
|
||||
unsecuredSavedObjectsClient,
|
||||
caseOptions: statusQuery,
|
||||
ensureSavedObjectsAreAuthorized,
|
||||
});
|
||||
const [cases, statusStats] = await Promise.all([
|
||||
caseService.findCasesGroupedByID({
|
||||
caseOptions: {
|
||||
...queryParams,
|
||||
...caseQueryOptions,
|
||||
searchFields: asArray(queryParams.searchFields),
|
||||
fields: includeFieldsRequiredForAuthentication(queryParams.fields),
|
||||
},
|
||||
}),
|
||||
caseService.getCaseStatusStats({
|
||||
searchOptions: statusStatsOptions,
|
||||
}),
|
||||
]);
|
||||
|
||||
ensureSavedObjectsAreAuthorized([...cases.casesMap.values()]);
|
||||
|
||||
return CasesFindResponseRt.encode(
|
||||
transformCases({
|
||||
casesMap: cases.casesMap,
|
||||
page: cases.page,
|
||||
perPage: cases.perPage,
|
||||
total: cases.total,
|
||||
countOpenCases: openCases,
|
||||
countInProgressCases: inProgressCases,
|
||||
countClosedCases: closedCases,
|
||||
countOpenCases: statusStats.open,
|
||||
countInProgressCases: statusStats['in-progress'],
|
||||
countClosedCases: statusStats.closed,
|
||||
})
|
||||
);
|
||||
} catch (error) {
|
||||
|
|
|
@ -59,7 +59,7 @@ export const getCasesByAlertID = async (
|
|||
{ alertID, options }: CasesByAlertIDParams,
|
||||
clientArgs: CasesClientArgs
|
||||
): Promise<CasesByAlertId> => {
|
||||
const { unsecuredSavedObjectsClient, caseService, logger, authorization } = clientArgs;
|
||||
const { caseService, logger, authorization } = clientArgs;
|
||||
|
||||
try {
|
||||
const queryParams = pipe(
|
||||
|
@ -79,7 +79,6 @@ export const getCasesByAlertID = async (
|
|||
// This will likely only return one comment saved object, the response aggregation will contain
|
||||
// the keys we need to retrieve the cases
|
||||
const commentsWithAlert = await caseService.getCaseIdsByAlertId({
|
||||
unsecuredSavedObjectsClient,
|
||||
alertId: alertID,
|
||||
filter,
|
||||
});
|
||||
|
@ -100,7 +99,6 @@ export const getCasesByAlertID = async (
|
|||
}
|
||||
|
||||
const casesInfo = await caseService.getCases({
|
||||
unsecuredSavedObjectsClient,
|
||||
caseIds,
|
||||
});
|
||||
|
||||
|
@ -157,11 +155,10 @@ export const get = async (
|
|||
{ id, includeComments }: GetParams,
|
||||
clientArgs: CasesClientArgs
|
||||
): Promise<CaseResponse> => {
|
||||
const { unsecuredSavedObjectsClient, caseService, logger, authorization } = clientArgs;
|
||||
const { caseService, logger, authorization } = clientArgs;
|
||||
|
||||
try {
|
||||
const theCase: SavedObject<CaseAttributes> = await caseService.getCase({
|
||||
unsecuredSavedObjectsClient,
|
||||
id,
|
||||
});
|
||||
|
||||
|
@ -179,7 +176,6 @@ export const get = async (
|
|||
}
|
||||
|
||||
const theComments = await caseService.getAllCaseComments({
|
||||
unsecuredSavedObjectsClient,
|
||||
id,
|
||||
options: {
|
||||
sortField: 'created_at',
|
||||
|
@ -209,14 +205,13 @@ export const resolve = async (
|
|||
{ id, includeComments }: GetParams,
|
||||
clientArgs: CasesClientArgs
|
||||
): Promise<CaseResolveResponse> => {
|
||||
const { unsecuredSavedObjectsClient, caseService, logger, authorization } = clientArgs;
|
||||
const { caseService, logger, authorization } = clientArgs;
|
||||
|
||||
try {
|
||||
const {
|
||||
saved_object: resolvedSavedObject,
|
||||
...resolveData
|
||||
}: SavedObjectsResolveResponse<CaseAttributes> = await caseService.getResolveCase({
|
||||
unsecuredSavedObjectsClient,
|
||||
id,
|
||||
});
|
||||
|
||||
|
@ -240,7 +235,6 @@ export const resolve = async (
|
|||
}
|
||||
|
||||
const theComments = await caseService.getAllCaseComments({
|
||||
unsecuredSavedObjectsClient,
|
||||
id: resolvedSavedObject.id,
|
||||
options: {
|
||||
sortField: 'created_at',
|
||||
|
|
|
@ -142,12 +142,10 @@ export const push = async (
|
|||
/* Start of update case with push information */
|
||||
const [myCase, myCaseConfigure, comments] = await Promise.all([
|
||||
caseService.getCase({
|
||||
unsecuredSavedObjectsClient,
|
||||
id: caseId,
|
||||
}),
|
||||
caseConfigureService.find({ unsecuredSavedObjectsClient }),
|
||||
caseService.getAllCaseComments({
|
||||
unsecuredSavedObjectsClient,
|
||||
id: caseId,
|
||||
options: {
|
||||
fields: [],
|
||||
|
@ -177,7 +175,6 @@ export const push = async (
|
|||
const [updatedCase, updatedComments] = await Promise.all([
|
||||
caseService.patchCase({
|
||||
originalCase: myCase,
|
||||
unsecuredSavedObjectsClient,
|
||||
caseId,
|
||||
updatedAttributes: {
|
||||
...(shouldMarkAsClosed
|
||||
|
|
|
@ -97,17 +97,14 @@ function getID(
|
|||
async function getAlertComments({
|
||||
casesToSync,
|
||||
caseService,
|
||||
unsecuredSavedObjectsClient,
|
||||
}: {
|
||||
casesToSync: UpdateRequestWithOriginalCase[];
|
||||
caseService: CasesService;
|
||||
unsecuredSavedObjectsClient: SavedObjectsClientContract;
|
||||
}): Promise<SavedObjectsFindResponse<CommentAttributes>> {
|
||||
const idsOfCasesToSync = casesToSync.map(({ updateReq }) => updateReq.id);
|
||||
|
||||
// getAllCaseComments will by default get all the comments, unless page or perPage fields are set
|
||||
return caseService.getAllCaseComments({
|
||||
unsecuredSavedObjectsClient,
|
||||
id: idsOfCasesToSync,
|
||||
options: {
|
||||
filter: nodeBuilder.is(`${CASE_COMMENT_SAVED_OBJECT}.attributes.type`, CommentType.alert),
|
||||
|
@ -166,7 +163,6 @@ async function updateAlerts({
|
|||
const totalAlerts = await getAlertComments({
|
||||
casesToSync,
|
||||
caseService,
|
||||
unsecuredSavedObjectsClient,
|
||||
});
|
||||
|
||||
// create an array of requests that indicate the id, index, and status to update an alert
|
||||
|
@ -253,7 +249,6 @@ export const update = async (
|
|||
|
||||
try {
|
||||
const myCases = await caseService.getCases({
|
||||
unsecuredSavedObjectsClient,
|
||||
caseIds: query.cases.map((q) => q.id),
|
||||
});
|
||||
|
||||
|
@ -320,7 +315,6 @@ export const update = async (
|
|||
const { username, full_name, email } = user;
|
||||
const updatedDt = new Date().toISOString();
|
||||
const updatedCases = await caseService.patchCases({
|
||||
unsecuredSavedObjectsClient,
|
||||
cases: updateCases.map(({ updateReq, originalCase }) => {
|
||||
// intentionally removing owner from the case so that we don't accidentally allow it to be updated
|
||||
const { id: caseId, version, owner, ...updateCaseAttributes } = updateReq;
|
||||
|
|
|
@ -11,7 +11,6 @@ import { AttachmentsSubClient, createAttachmentsSubClient } from './attachments/
|
|||
import { UserActionsSubClient, createUserActionsSubClient } from './user_actions/client';
|
||||
import { CasesClientInternal, createCasesClientInternal } from './client_internal';
|
||||
import { ConfigureSubClient, createConfigurationSubClient } from './configure/client';
|
||||
import { createStatsSubClient, StatsSubClient } from './stats/client';
|
||||
import { createMetricsSubClient, MetricsSubClient } from './metrics/client';
|
||||
|
||||
/**
|
||||
|
@ -23,7 +22,6 @@ export class CasesClient {
|
|||
private readonly _attachments: AttachmentsSubClient;
|
||||
private readonly _userActions: UserActionsSubClient;
|
||||
private readonly _configure: ConfigureSubClient;
|
||||
private readonly _stats: StatsSubClient;
|
||||
private readonly _metrics: MetricsSubClient;
|
||||
|
||||
constructor(args: CasesClientArgs) {
|
||||
|
@ -32,7 +30,6 @@ export class CasesClient {
|
|||
this._attachments = createAttachmentsSubClient(args, this, this._casesClientInternal);
|
||||
this._userActions = createUserActionsSubClient(args);
|
||||
this._configure = createConfigurationSubClient(args, this._casesClientInternal);
|
||||
this._stats = createStatsSubClient(args);
|
||||
this._metrics = createMetricsSubClient(args, this);
|
||||
}
|
||||
|
||||
|
@ -64,13 +61,6 @@ export class CasesClient {
|
|||
return this._configure;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves an interface for retrieving statistics related to the cases entities.
|
||||
*/
|
||||
public get stats() {
|
||||
return this._stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves an interface for retrieving metrics related to the cases entities.
|
||||
*/
|
||||
|
|
|
@ -91,17 +91,25 @@ export class CasesClientFactory {
|
|||
logger: this.logger,
|
||||
});
|
||||
|
||||
const caseService = new CasesService(this.logger, this.options?.securityPluginStart?.authc);
|
||||
const unsecuredSavedObjectsClient = savedObjectsService.getScopedClient(request, {
|
||||
includedHiddenTypes: SAVED_OBJECT_TYPES,
|
||||
// this tells the security plugin to not perform SO authorization and audit logging since we are handling
|
||||
// that manually using our Authorization class and audit logger.
|
||||
excludedWrappers: ['security'],
|
||||
});
|
||||
|
||||
const attachmentService = new AttachmentService(this.logger);
|
||||
const caseService = new CasesService({
|
||||
log: this.logger,
|
||||
authentication: this.options?.securityPluginStart?.authc,
|
||||
unsecuredSavedObjectsClient,
|
||||
attachmentService,
|
||||
});
|
||||
const userInfo = caseService.getUser({ request });
|
||||
|
||||
return createCasesClient({
|
||||
alertsService: new AlertService(scopedClusterClient, this.logger),
|
||||
unsecuredSavedObjectsClient: savedObjectsService.getScopedClient(request, {
|
||||
includedHiddenTypes: SAVED_OBJECT_TYPES,
|
||||
// this tells the security plugin to not perform SO authorization and audit logging since we are handling
|
||||
// that manually using our Authorization class and audit logger.
|
||||
excludedWrappers: ['security'],
|
||||
}),
|
||||
unsecuredSavedObjectsClient,
|
||||
// We only want these fields from the userInfo object
|
||||
user: { username: userInfo.username, email: userInfo.email, full_name: userInfo.full_name },
|
||||
caseService,
|
||||
|
|
|
@ -5,10 +5,11 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { CaseMetricsResponse } from '../../../common/api';
|
||||
import { CaseMetricsResponse, CasesStatusRequest, CasesStatusResponse } from '../../../common/api';
|
||||
import { CasesClient } from '../client';
|
||||
|
||||
import { CasesClientArgs } from '../types';
|
||||
import { getStatusTotalsByType } from './get_cases_metrics';
|
||||
|
||||
import { getCaseMetrics, CaseMetricsParams } from './get_case_metrics';
|
||||
|
||||
|
@ -17,6 +18,10 @@ import { getCaseMetrics, CaseMetricsParams } from './get_case_metrics';
|
|||
*/
|
||||
export interface MetricsSubClient {
|
||||
getCaseMetrics(params: CaseMetricsParams): Promise<CaseMetricsResponse>;
|
||||
/**
|
||||
* Retrieves the total number of open, closed, and in-progress cases.
|
||||
*/
|
||||
getStatusTotalsByType(params: CasesStatusRequest): Promise<CasesStatusResponse>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -30,6 +35,8 @@ export const createMetricsSubClient = (
|
|||
): MetricsSubClient => {
|
||||
const casesSubClient: MetricsSubClient = {
|
||||
getCaseMetrics: (params: CaseMetricsParams) => getCaseMetrics(params, casesClient, clientArgs),
|
||||
getStatusTotalsByType: (params: CasesStatusRequest) =>
|
||||
getStatusTotalsByType(params, clientArgs),
|
||||
};
|
||||
|
||||
return Object.freeze(casesSubClient);
|
||||
|
|
|
@ -105,10 +105,9 @@ const checkAndThrowIfInvalidFeatures = (
|
|||
};
|
||||
|
||||
const checkAuthorization = async (params: CaseMetricsParams, clientArgs: CasesClientArgs) => {
|
||||
const { caseService, unsecuredSavedObjectsClient, authorization } = clientArgs;
|
||||
const { caseService, authorization } = clientArgs;
|
||||
|
||||
const caseInfo = await caseService.getCase({
|
||||
unsecuredSavedObjectsClient,
|
||||
id: params.caseId,
|
||||
});
|
||||
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* 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 Boom from '@hapi/boom';
|
||||
import { pipe } from 'fp-ts/lib/pipeable';
|
||||
import { fold } from 'fp-ts/lib/Either';
|
||||
import { identity } from 'fp-ts/lib/function';
|
||||
|
||||
import {
|
||||
CasesStatusRequest,
|
||||
CasesStatusResponse,
|
||||
excess,
|
||||
CasesStatusRequestRt,
|
||||
throwErrors,
|
||||
CasesStatusResponseRt,
|
||||
} from '../../../common/api';
|
||||
import { CasesClientArgs } from '../types';
|
||||
import { Operations } from '../../authorization';
|
||||
import { constructQueryOptions } from '../utils';
|
||||
import { createCaseError } from '../../common/error';
|
||||
|
||||
export async function getStatusTotalsByType(
|
||||
params: CasesStatusRequest,
|
||||
clientArgs: CasesClientArgs
|
||||
): Promise<CasesStatusResponse> {
|
||||
const { caseService, logger, authorization } = clientArgs;
|
||||
|
||||
try {
|
||||
const queryParams = pipe(
|
||||
excess(CasesStatusRequestRt).decode(params),
|
||||
fold(throwErrors(Boom.badRequest), identity)
|
||||
);
|
||||
|
||||
const { filter: authorizationFilter } = await authorization.getAuthorizationFilter(
|
||||
Operations.getCaseStatuses
|
||||
);
|
||||
|
||||
const options = constructQueryOptions({
|
||||
owner: queryParams.owner,
|
||||
authorizationFilter,
|
||||
});
|
||||
|
||||
const statusStats = await caseService.getCaseStatusStats({
|
||||
searchOptions: options,
|
||||
});
|
||||
|
||||
return CasesStatusResponseRt.encode({
|
||||
count_open_cases: statusStats.open,
|
||||
count_in_progress_cases: statusStats['in-progress'],
|
||||
count_closed_cases: statusStats.closed,
|
||||
});
|
||||
} catch (error) {
|
||||
throw createCaseError({ message: `Failed to get status stats: ${error}`, error, logger });
|
||||
}
|
||||
}
|
|
@ -13,7 +13,6 @@ import { CasesSubClient } from './cases/client';
|
|||
import { ConfigureSubClient } from './configure/client';
|
||||
import { CasesClientFactory } from './factory';
|
||||
import { MetricsSubClient } from './metrics/client';
|
||||
import { StatsSubClient } from './stats/client';
|
||||
import { UserActionsSubClient } from './user_actions/client';
|
||||
|
||||
type CasesSubClientMock = jest.Mocked<CasesSubClient>;
|
||||
|
@ -38,6 +37,7 @@ type MetricsSubClientMock = jest.Mocked<MetricsSubClient>;
|
|||
const createMetricsSubClientMock = (): MetricsSubClientMock => {
|
||||
return {
|
||||
getCaseMetrics: jest.fn(),
|
||||
getStatusTotalsByType: jest.fn(),
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -75,14 +75,6 @@ const createConfigureSubClientMock = (): ConfigureSubClientMock => {
|
|||
};
|
||||
};
|
||||
|
||||
type StatsSubClientMock = jest.Mocked<StatsSubClient>;
|
||||
|
||||
const createStatsSubClientMock = (): StatsSubClientMock => {
|
||||
return {
|
||||
getStatusTotalsByType: jest.fn(),
|
||||
};
|
||||
};
|
||||
|
||||
export interface CasesClientMock extends CasesClient {
|
||||
cases: CasesSubClientMock;
|
||||
attachments: AttachmentsSubClientMock;
|
||||
|
@ -95,7 +87,6 @@ export const createCasesClientMock = (): CasesClientMock => {
|
|||
attachments: createAttachmentsSubClientMock(),
|
||||
userActions: createUserActionsSubClientMock(),
|
||||
configure: createConfigureSubClientMock(),
|
||||
stats: createStatsSubClientMock(),
|
||||
metrics: createMetricsSubClientMock(),
|
||||
};
|
||||
return client as unknown as CasesClientMock;
|
||||
|
|
|
@ -1,88 +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 Boom from '@hapi/boom';
|
||||
import { pipe } from 'fp-ts/lib/pipeable';
|
||||
import { fold } from 'fp-ts/lib/Either';
|
||||
import { identity } from 'fp-ts/lib/function';
|
||||
|
||||
import { CasesClientArgs } from '..';
|
||||
import {
|
||||
CasesStatusRequest,
|
||||
CasesStatusResponse,
|
||||
CasesStatusResponseRt,
|
||||
caseStatuses,
|
||||
throwErrors,
|
||||
excess,
|
||||
CasesStatusRequestRt,
|
||||
} from '../../../common/api';
|
||||
import { Operations } from '../../authorization';
|
||||
import { createCaseError } from '../../common/error';
|
||||
import { constructQueryOptions } from '../utils';
|
||||
|
||||
/**
|
||||
* Statistics API contract.
|
||||
*/
|
||||
export interface StatsSubClient {
|
||||
/**
|
||||
* Retrieves the total number of open, closed, and in-progress cases.
|
||||
*/
|
||||
getStatusTotalsByType(params: CasesStatusRequest): Promise<CasesStatusResponse>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the interface for retrieving the number of open, closed, and in progress cases.
|
||||
*
|
||||
* @ignore
|
||||
*/
|
||||
export function createStatsSubClient(clientArgs: CasesClientArgs): StatsSubClient {
|
||||
return Object.freeze({
|
||||
getStatusTotalsByType: (params: CasesStatusRequest) =>
|
||||
getStatusTotalsByType(params, clientArgs),
|
||||
});
|
||||
}
|
||||
|
||||
async function getStatusTotalsByType(
|
||||
params: CasesStatusRequest,
|
||||
clientArgs: CasesClientArgs
|
||||
): Promise<CasesStatusResponse> {
|
||||
const { unsecuredSavedObjectsClient, caseService, logger, authorization } = clientArgs;
|
||||
|
||||
try {
|
||||
const queryParams = pipe(
|
||||
excess(CasesStatusRequestRt).decode(params),
|
||||
fold(throwErrors(Boom.badRequest), identity)
|
||||
);
|
||||
|
||||
const { filter: authorizationFilter, ensureSavedObjectsAreAuthorized } =
|
||||
await authorization.getAuthorizationFilter(Operations.getCaseStatuses);
|
||||
|
||||
// casesStatuses are bounded by us. No need to limit concurrent calls.
|
||||
const [openCases, inProgressCases, closedCases] = await Promise.all([
|
||||
...caseStatuses.map((status) => {
|
||||
const statusQuery = constructQueryOptions({
|
||||
owner: queryParams.owner,
|
||||
status,
|
||||
authorizationFilter,
|
||||
});
|
||||
return caseService.findCaseStatusStats({
|
||||
unsecuredSavedObjectsClient,
|
||||
caseOptions: statusQuery,
|
||||
ensureSavedObjectsAreAuthorized,
|
||||
});
|
||||
}),
|
||||
]);
|
||||
|
||||
return CasesStatusResponseRt.encode({
|
||||
count_open_cases: openCases,
|
||||
count_in_progress_cases: inProgressCases,
|
||||
count_closed_cases: closedCases,
|
||||
});
|
||||
} catch (error) {
|
||||
throw createCaseError({ message: `Failed to get status stats: ${error}`, error, logger });
|
||||
}
|
||||
}
|
|
@ -118,7 +118,6 @@ export class CommentableCase {
|
|||
try {
|
||||
const updatedCase = await this.caseService.patchCase({
|
||||
originalCase: this.caseInfo,
|
||||
unsecuredSavedObjectsClient: this.unsecuredSavedObjectsClient,
|
||||
caseId: this.caseInfo.id,
|
||||
updatedAttributes: {
|
||||
updated_at: date,
|
||||
|
@ -282,7 +281,6 @@ export class CommentableCase {
|
|||
public async encode(): Promise<CaseResponse> {
|
||||
try {
|
||||
const comments = await this.caseService.getAllCaseComments({
|
||||
unsecuredSavedObjectsClient: this.unsecuredSavedObjectsClient,
|
||||
id: this.caseInfo.id,
|
||||
options: {
|
||||
fields: [],
|
||||
|
|
|
@ -28,6 +28,7 @@ import {
|
|||
flattenCommentSavedObject,
|
||||
extractLensReferencesFromCommentString,
|
||||
getOrUpdateLensReferences,
|
||||
asArray,
|
||||
} from './utils';
|
||||
|
||||
interface CommentReference {
|
||||
|
@ -940,4 +941,26 @@ describe('common utils', () => {
|
|||
expect(expectedReferences).toEqual(expect.arrayContaining(updatedReferences));
|
||||
});
|
||||
});
|
||||
|
||||
describe('asArray', () => {
|
||||
it('returns an empty array when the field is undefined', () => {
|
||||
expect(asArray(undefined)).toEqual([]);
|
||||
});
|
||||
|
||||
it('returns an empty array when the field is null', () => {
|
||||
expect(asArray(null)).toEqual([]);
|
||||
});
|
||||
|
||||
it('leaves the string array as is when it is already an array', () => {
|
||||
expect(asArray(['value'])).toEqual(['value']);
|
||||
});
|
||||
|
||||
it('returns an array of one item when passed a string', () => {
|
||||
expect(asArray('value')).toEqual(['value']);
|
||||
});
|
||||
|
||||
it('returns an array of one item when passed a number', () => {
|
||||
expect(asArray(100)).toEqual([100]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -360,3 +360,11 @@ export const getOrUpdateLensReferences = (
|
|||
|
||||
return currentNonLensReferences.concat(newCommentLensReferences);
|
||||
};
|
||||
|
||||
export const asArray = <T>(field?: T | T[] | null): T[] => {
|
||||
if (field === undefined || field === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return Array.isArray(field) ? field : [field];
|
||||
};
|
||||
|
|
|
@ -21,7 +21,7 @@ export function initGetCasesStatusApi({ router, logger }: RouteDeps) {
|
|||
try {
|
||||
const client = await context.cases.getCasesClient();
|
||||
return response.ok({
|
||||
body: await client.stats.getStatusTotalsByType(request.query as CasesStatusRequest),
|
||||
body: await client.metrics.getStatusTotalsByType(request.query as CasesStatusRequest),
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error(`Failed to get status stats in route: ${error}`);
|
||||
|
|
|
@ -9,6 +9,7 @@ import {
|
|||
Logger,
|
||||
SavedObject,
|
||||
SavedObjectReference,
|
||||
SavedObjectsClientContract,
|
||||
SavedObjectsUpdateOptions,
|
||||
} from 'kibana/server';
|
||||
|
||||
|
@ -62,6 +63,11 @@ interface BulkUpdateAttachmentArgs extends ClientArgs {
|
|||
comments: UpdateArgs[];
|
||||
}
|
||||
|
||||
interface CommentStats {
|
||||
nonAlerts: number;
|
||||
alerts: number;
|
||||
}
|
||||
|
||||
export class AttachmentService {
|
||||
constructor(private readonly log: Logger) {}
|
||||
|
||||
|
@ -279,4 +285,104 @@ export class AttachmentService {
|
|||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
public async getCaseCommentStats({
|
||||
unsecuredSavedObjectsClient,
|
||||
caseIds,
|
||||
}: {
|
||||
unsecuredSavedObjectsClient: SavedObjectsClientContract;
|
||||
caseIds: string[];
|
||||
}): Promise<Map<string, CommentStats>> {
|
||||
if (caseIds.length <= 0) {
|
||||
return new Map();
|
||||
}
|
||||
|
||||
interface AggsResult {
|
||||
references: {
|
||||
caseIds: {
|
||||
buckets: Array<{
|
||||
key: string;
|
||||
doc_count: number;
|
||||
reverse: {
|
||||
comments: {
|
||||
buckets: {
|
||||
alerts: {
|
||||
doc_count: number;
|
||||
};
|
||||
nonAlerts: {
|
||||
doc_count: number;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}>;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
const res = await unsecuredSavedObjectsClient.find<unknown, AggsResult>({
|
||||
hasReference: caseIds.map((id) => ({ type: CASE_SAVED_OBJECT, id })),
|
||||
hasReferenceOperator: 'OR',
|
||||
type: CASE_COMMENT_SAVED_OBJECT,
|
||||
perPage: 0,
|
||||
aggs: AttachmentService.buildCommentStatsAggs(caseIds),
|
||||
});
|
||||
|
||||
return (
|
||||
res.aggregations?.references.caseIds.buckets.reduce((acc, idBucket) => {
|
||||
acc.set(idBucket.key, {
|
||||
nonAlerts: idBucket.reverse.comments.buckets.nonAlerts.doc_count,
|
||||
alerts: idBucket.reverse.comments.buckets.alerts.doc_count,
|
||||
});
|
||||
return acc;
|
||||
}, new Map<string, CommentStats>()) ?? new Map()
|
||||
);
|
||||
}
|
||||
|
||||
private static buildCommentStatsAggs(
|
||||
ids: string[]
|
||||
): Record<string, estypes.AggregationsAggregationContainer> {
|
||||
return {
|
||||
references: {
|
||||
nested: {
|
||||
path: `${CASE_COMMENT_SAVED_OBJECT}.references`,
|
||||
},
|
||||
aggregations: {
|
||||
caseIds: {
|
||||
terms: {
|
||||
field: `${CASE_COMMENT_SAVED_OBJECT}.references.id`,
|
||||
size: ids.length,
|
||||
},
|
||||
aggregations: {
|
||||
reverse: {
|
||||
reverse_nested: {},
|
||||
aggregations: {
|
||||
comments: {
|
||||
filters: {
|
||||
filters: {
|
||||
alerts: {
|
||||
term: {
|
||||
[`${CASE_COMMENT_SAVED_OBJECT}.attributes.type`]: CommentType.alert,
|
||||
},
|
||||
},
|
||||
nonAlerts: {
|
||||
bool: {
|
||||
must_not: {
|
||||
term: {
|
||||
[`${CASE_COMMENT_SAVED_OBJECT}.attributes.type`]: CommentType.alert,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,6 +40,7 @@ import {
|
|||
createSOFindResponse,
|
||||
} from '../test_utils';
|
||||
import { ESCaseAttributes } from './types';
|
||||
import { AttachmentService } from '../attachments';
|
||||
|
||||
const createUpdateSOResponse = ({
|
||||
connector,
|
||||
|
@ -117,12 +118,17 @@ const createCasePatchParams = ({
|
|||
describe('CasesService', () => {
|
||||
const unsecuredSavedObjectsClient = savedObjectsClientMock.create();
|
||||
const mockLogger = loggerMock.create();
|
||||
const attachmentService = new AttachmentService(mockLogger);
|
||||
|
||||
let service: CasesService;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
service = new CasesService(mockLogger);
|
||||
service = new CasesService({
|
||||
log: mockLogger,
|
||||
unsecuredSavedObjectsClient,
|
||||
attachmentService,
|
||||
});
|
||||
});
|
||||
|
||||
describe('transforms the external model to the Elasticsearch model', () => {
|
||||
|
@ -134,7 +140,6 @@ describe('CasesService', () => {
|
|||
|
||||
await service.patchCase({
|
||||
caseId: '1',
|
||||
unsecuredSavedObjectsClient,
|
||||
updatedAttributes: createCasePostParams(createJiraConnector(), createExternalService()),
|
||||
originalCase: {} as SavedObject<CaseAttributes>,
|
||||
});
|
||||
|
@ -181,7 +186,6 @@ describe('CasesService', () => {
|
|||
|
||||
await service.patchCase({
|
||||
caseId: '1',
|
||||
unsecuredSavedObjectsClient,
|
||||
updatedAttributes: createCasePostParams(createJiraConnector(), createExternalService()),
|
||||
originalCase: {} as SavedObject<CaseAttributes>,
|
||||
});
|
||||
|
@ -213,7 +217,6 @@ describe('CasesService', () => {
|
|||
|
||||
await service.patchCase({
|
||||
caseId: '1',
|
||||
unsecuredSavedObjectsClient,
|
||||
updatedAttributes: createCasePostParams(createJiraConnector(), createExternalService()),
|
||||
originalCase: {} as SavedObject<CaseAttributes>,
|
||||
});
|
||||
|
@ -249,7 +252,6 @@ describe('CasesService', () => {
|
|||
|
||||
await service.patchCase({
|
||||
caseId: '1',
|
||||
unsecuredSavedObjectsClient,
|
||||
updatedAttributes: createCaseUpdateParams(createJiraConnector()),
|
||||
originalCase: {} as SavedObject<CaseAttributes>,
|
||||
});
|
||||
|
@ -278,7 +280,6 @@ describe('CasesService', () => {
|
|||
|
||||
await service.patchCase({
|
||||
caseId: '1',
|
||||
unsecuredSavedObjectsClient,
|
||||
updatedAttributes: createCasePostParams(getNoneCaseConnector(), createExternalService()),
|
||||
originalCase: {} as SavedObject<CaseAttributes>,
|
||||
});
|
||||
|
@ -307,7 +308,6 @@ describe('CasesService', () => {
|
|||
|
||||
await service.patchCase({
|
||||
caseId: '1',
|
||||
unsecuredSavedObjectsClient,
|
||||
updatedAttributes: createCasePostParams(createJiraConnector(), createExternalService()),
|
||||
originalCase: {
|
||||
references: [{ id: 'a', name: 'awesome', type: 'hello' }],
|
||||
|
@ -344,7 +344,6 @@ describe('CasesService', () => {
|
|||
|
||||
await service.patchCase({
|
||||
caseId: '1',
|
||||
unsecuredSavedObjectsClient,
|
||||
updatedAttributes: createCasePatchParams({ externalService: createExternalService() }),
|
||||
originalCase: {
|
||||
references: [
|
||||
|
@ -378,7 +377,6 @@ describe('CasesService', () => {
|
|||
|
||||
await service.patchCase({
|
||||
caseId: '1',
|
||||
unsecuredSavedObjectsClient,
|
||||
updatedAttributes: createCasePostParams(getNoneCaseConnector(), createExternalService()),
|
||||
originalCase: {} as SavedObject<CaseAttributes>,
|
||||
});
|
||||
|
@ -408,7 +406,6 @@ describe('CasesService', () => {
|
|||
|
||||
await service.patchCase({
|
||||
caseId: '1',
|
||||
unsecuredSavedObjectsClient,
|
||||
updatedAttributes: createCaseUpdateParams(),
|
||||
originalCase: {} as SavedObject<CaseAttributes>,
|
||||
});
|
||||
|
@ -428,7 +425,6 @@ describe('CasesService', () => {
|
|||
|
||||
await service.patchCase({
|
||||
caseId: '1',
|
||||
unsecuredSavedObjectsClient,
|
||||
updatedAttributes: createCaseUpdateParams(getNoneCaseConnector()),
|
||||
originalCase: {} as SavedObject<CaseAttributes>,
|
||||
});
|
||||
|
@ -452,7 +448,6 @@ describe('CasesService', () => {
|
|||
);
|
||||
|
||||
await service.postNewCase({
|
||||
unsecuredSavedObjectsClient,
|
||||
attributes: createCasePostParams(createJiraConnector()),
|
||||
id: '1',
|
||||
});
|
||||
|
@ -468,7 +463,6 @@ describe('CasesService', () => {
|
|||
);
|
||||
|
||||
await service.postNewCase({
|
||||
unsecuredSavedObjectsClient,
|
||||
attributes: createCasePostParams(createJiraConnector(), createExternalService()),
|
||||
id: '1',
|
||||
});
|
||||
|
@ -560,7 +554,6 @@ describe('CasesService', () => {
|
|||
);
|
||||
|
||||
await service.postNewCase({
|
||||
unsecuredSavedObjectsClient,
|
||||
attributes: createCasePostParams(createJiraConnector(), createExternalService()),
|
||||
id: '1',
|
||||
});
|
||||
|
@ -589,7 +582,6 @@ describe('CasesService', () => {
|
|||
);
|
||||
|
||||
await service.postNewCase({
|
||||
unsecuredSavedObjectsClient,
|
||||
attributes: createCasePostParams(
|
||||
createJiraConnector({ setFieldsToNull: true }),
|
||||
createExternalService()
|
||||
|
@ -608,7 +600,6 @@ describe('CasesService', () => {
|
|||
);
|
||||
|
||||
await service.postNewCase({
|
||||
unsecuredSavedObjectsClient,
|
||||
attributes: createCasePostParams(getNoneCaseConnector()),
|
||||
id: '1',
|
||||
});
|
||||
|
@ -624,7 +615,6 @@ describe('CasesService', () => {
|
|||
);
|
||||
|
||||
await service.postNewCase({
|
||||
unsecuredSavedObjectsClient,
|
||||
attributes: createCasePostParams(getNoneCaseConnector()),
|
||||
id: '1',
|
||||
});
|
||||
|
@ -655,7 +645,6 @@ describe('CasesService', () => {
|
|||
);
|
||||
|
||||
const res = await service.patchCases({
|
||||
unsecuredSavedObjectsClient,
|
||||
cases: [
|
||||
{
|
||||
caseId: '1',
|
||||
|
@ -710,7 +699,6 @@ describe('CasesService', () => {
|
|||
|
||||
const res = await service.patchCase({
|
||||
caseId: '1',
|
||||
unsecuredSavedObjectsClient,
|
||||
updatedAttributes: createCaseUpdateParams(),
|
||||
originalCase: {} as SavedObject<CaseAttributes>,
|
||||
});
|
||||
|
@ -735,7 +723,6 @@ describe('CasesService', () => {
|
|||
|
||||
const res = await service.patchCase({
|
||||
caseId: '1',
|
||||
unsecuredSavedObjectsClient,
|
||||
updatedAttributes: createCaseUpdateParams(),
|
||||
originalCase: {} as SavedObject<CaseAttributes>,
|
||||
});
|
||||
|
@ -755,7 +742,6 @@ describe('CasesService', () => {
|
|||
|
||||
const res = await service.patchCase({
|
||||
caseId: '1',
|
||||
unsecuredSavedObjectsClient,
|
||||
updatedAttributes: createCaseUpdateParams(),
|
||||
originalCase: {} as SavedObject<CaseAttributes>,
|
||||
});
|
||||
|
@ -771,7 +757,6 @@ describe('CasesService', () => {
|
|||
|
||||
const res = await service.patchCase({
|
||||
caseId: '1',
|
||||
unsecuredSavedObjectsClient,
|
||||
updatedAttributes: createCaseUpdateParams(),
|
||||
originalCase: {} as SavedObject<CaseAttributes>,
|
||||
});
|
||||
|
@ -803,7 +788,6 @@ describe('CasesService', () => {
|
|||
|
||||
const res = await service.patchCase({
|
||||
caseId: '1',
|
||||
unsecuredSavedObjectsClient,
|
||||
updatedAttributes: createCaseUpdateParams(),
|
||||
originalCase: {} as SavedObject<CaseAttributes>,
|
||||
});
|
||||
|
@ -834,7 +818,6 @@ describe('CasesService', () => {
|
|||
|
||||
const res = await service.patchCase({
|
||||
caseId: '1',
|
||||
unsecuredSavedObjectsClient,
|
||||
updatedAttributes: createCaseUpdateParams(),
|
||||
originalCase: {} as SavedObject<CaseAttributes>,
|
||||
});
|
||||
|
@ -858,7 +841,6 @@ describe('CasesService', () => {
|
|||
|
||||
const res = await service.patchCase({
|
||||
caseId: '1',
|
||||
unsecuredSavedObjectsClient,
|
||||
updatedAttributes: createCaseUpdateParams(),
|
||||
originalCase: {} as SavedObject<CaseAttributes>,
|
||||
});
|
||||
|
@ -895,7 +877,6 @@ describe('CasesService', () => {
|
|||
|
||||
const res = await service.patchCase({
|
||||
caseId: '1',
|
||||
unsecuredSavedObjectsClient,
|
||||
updatedAttributes: createCaseUpdateParams(),
|
||||
originalCase: {} as SavedObject<CaseAttributes>,
|
||||
});
|
||||
|
@ -922,7 +903,6 @@ describe('CasesService', () => {
|
|||
|
||||
const res = await service.patchCase({
|
||||
caseId: '1',
|
||||
unsecuredSavedObjectsClient,
|
||||
updatedAttributes: createCaseUpdateParams(),
|
||||
originalCase: {} as SavedObject<CaseAttributes>,
|
||||
});
|
||||
|
@ -958,7 +938,6 @@ describe('CasesService', () => {
|
|||
);
|
||||
|
||||
const res = await service.postNewCase({
|
||||
unsecuredSavedObjectsClient,
|
||||
attributes: createCasePostParams(getNoneCaseConnector()),
|
||||
id: '1',
|
||||
});
|
||||
|
@ -979,7 +958,7 @@ describe('CasesService', () => {
|
|||
]);
|
||||
unsecuredSavedObjectsClient.find.mockReturnValue(Promise.resolve(findMockReturn));
|
||||
|
||||
const res = await service.findCases({ unsecuredSavedObjectsClient });
|
||||
const res = await service.findCases();
|
||||
expect(res.saved_objects[0].attributes.connector.id).toMatchInlineSnapshot(`"1"`);
|
||||
expect(
|
||||
res.saved_objects[0].attributes.external_service?.connector_id
|
||||
|
@ -996,7 +975,7 @@ describe('CasesService', () => {
|
|||
]);
|
||||
unsecuredSavedObjectsClient.find.mockReturnValue(Promise.resolve(findMockReturn));
|
||||
|
||||
const res = await service.findCases({ unsecuredSavedObjectsClient });
|
||||
const res = await service.findCases();
|
||||
const { saved_objects: ignored, ...findResponseFields } = res;
|
||||
expect(findResponseFields).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
|
@ -1025,7 +1004,7 @@ describe('CasesService', () => {
|
|||
})
|
||||
);
|
||||
|
||||
const res = await service.getCases({ unsecuredSavedObjectsClient, caseIds: ['a'] });
|
||||
const res = await service.getCases({ caseIds: ['a'] });
|
||||
|
||||
expect(res.saved_objects[0].attributes.connector.id).toMatchInlineSnapshot(`"1"`);
|
||||
expect(
|
||||
|
@ -1050,7 +1029,7 @@ describe('CasesService', () => {
|
|||
)
|
||||
);
|
||||
|
||||
const res = await service.getCase({ unsecuredSavedObjectsClient, id: 'a' });
|
||||
const res = await service.getCase({ id: 'a' });
|
||||
|
||||
expect(res.attributes.connector.id).toMatchInlineSnapshot(`"1"`);
|
||||
expect(res.attributes.external_service?.connector_id).toMatchInlineSnapshot(`"100"`);
|
||||
|
@ -1062,7 +1041,7 @@ describe('CasesService', () => {
|
|||
createCaseSavedObjectResponse({ externalService: createExternalService() })
|
||||
)
|
||||
);
|
||||
const res = await service.getCase({ unsecuredSavedObjectsClient, id: 'a' });
|
||||
const res = await service.getCase({ id: 'a' });
|
||||
|
||||
expect(res.attributes.connector).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
|
@ -1078,7 +1057,7 @@ describe('CasesService', () => {
|
|||
unsecuredSavedObjectsClient.get.mockReturnValue(
|
||||
Promise.resolve(createCaseSavedObjectResponse())
|
||||
);
|
||||
const res = await service.getCase({ unsecuredSavedObjectsClient, id: 'a' });
|
||||
const res = await service.getCase({ id: 'a' });
|
||||
|
||||
expect(res.attributes.external_service?.connector_id).toMatchInlineSnapshot(`"none"`);
|
||||
});
|
||||
|
@ -1087,7 +1066,7 @@ describe('CasesService', () => {
|
|||
unsecuredSavedObjectsClient.get.mockReturnValue(
|
||||
Promise.resolve(createCaseSavedObjectResponse())
|
||||
);
|
||||
const res = await service.getCase({ unsecuredSavedObjectsClient, id: 'a' });
|
||||
const res = await service.getCase({ id: 'a' });
|
||||
|
||||
expect(res.attributes.external_service).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
|
@ -1118,7 +1097,7 @@ describe('CasesService', () => {
|
|||
],
|
||||
} as unknown as SavedObject<ESCaseAttributes>)
|
||||
);
|
||||
const res = await service.getCase({ unsecuredSavedObjectsClient, id: 'a' });
|
||||
const res = await service.getCase({ id: 'a' });
|
||||
|
||||
expect(res.attributes.connector).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
|
@ -1138,7 +1117,7 @@ describe('CasesService', () => {
|
|||
attributes: { external_service: null },
|
||||
} as SavedObject<ESCaseAttributes>)
|
||||
);
|
||||
const res = await service.getCase({ unsecuredSavedObjectsClient, id: 'a' });
|
||||
const res = await service.getCase({ id: 'a' });
|
||||
|
||||
expect(res.attributes.connector).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import pMap from 'p-map';
|
||||
import {
|
||||
KibanaRequest,
|
||||
Logger,
|
||||
|
@ -26,7 +25,6 @@ import { SecurityPluginSetup } from '../../../../security/server';
|
|||
import {
|
||||
CASE_COMMENT_SAVED_OBJECT,
|
||||
CASE_SAVED_OBJECT,
|
||||
MAX_CONCURRENT_SEARCHES,
|
||||
MAX_DOCS_PER_PAGE,
|
||||
} from '../../../common/constants';
|
||||
import {
|
||||
|
@ -34,17 +32,16 @@ import {
|
|||
CaseResponse,
|
||||
CasesFindRequest,
|
||||
CommentAttributes,
|
||||
CommentType,
|
||||
User,
|
||||
CaseAttributes,
|
||||
CaseStatuses,
|
||||
caseStatuses,
|
||||
} from '../../../common/api';
|
||||
import { SavedObjectFindOptionsKueryNode } from '../../common/types';
|
||||
import { defaultSortField, flattenCaseSavedObject, groupTotalAlertsByID } from '../../common/utils';
|
||||
import { defaultSortField, flattenCaseSavedObject } from '../../common/utils';
|
||||
import { defaultPage, defaultPerPage } from '../../routes/api';
|
||||
import { ClientArgs } from '..';
|
||||
import { combineFilters } from '../../client/utils';
|
||||
import { includeFieldsRequiredForAuthentication } from '../../authorization/utils';
|
||||
import { EnsureSOAuthCallback } from '../../authorization';
|
||||
import {
|
||||
transformSavedObjectToExternalModel,
|
||||
transformAttributesToESModel,
|
||||
|
@ -54,8 +51,9 @@ import {
|
|||
transformFindResponseToExternalModel,
|
||||
} from './transform';
|
||||
import { ESCaseAttributes } from './types';
|
||||
import { AttachmentService } from '../attachments';
|
||||
|
||||
interface GetCaseIdsByAlertIdArgs extends ClientArgs {
|
||||
interface GetCaseIdsByAlertIdArgs {
|
||||
alertId: string;
|
||||
filter?: KueryNode;
|
||||
}
|
||||
|
@ -65,31 +63,25 @@ interface PushedArgs {
|
|||
pushed_by: User;
|
||||
}
|
||||
|
||||
interface GetCaseArgs extends ClientArgs {
|
||||
interface GetCaseArgs {
|
||||
id: string;
|
||||
}
|
||||
|
||||
interface GetCasesArgs extends ClientArgs {
|
||||
interface GetCasesArgs {
|
||||
caseIds: string[];
|
||||
}
|
||||
|
||||
interface FindCommentsArgs {
|
||||
unsecuredSavedObjectsClient: SavedObjectsClientContract;
|
||||
id: string | string[];
|
||||
options?: SavedObjectFindOptionsKueryNode;
|
||||
}
|
||||
|
||||
interface FindCaseCommentsArgs {
|
||||
unsecuredSavedObjectsClient: SavedObjectsClientContract;
|
||||
id: string | string[];
|
||||
options?: SavedObjectFindOptionsKueryNode;
|
||||
}
|
||||
|
||||
interface FindCasesArgs extends ClientArgs {
|
||||
options?: SavedObjectFindOptionsKueryNode;
|
||||
}
|
||||
|
||||
interface PostCaseArgs extends ClientArgs {
|
||||
interface PostCaseArgs {
|
||||
attributes: CaseAttributes;
|
||||
id: string;
|
||||
}
|
||||
|
@ -100,9 +92,9 @@ interface PatchCase {
|
|||
originalCase: SavedObject<CaseAttributes>;
|
||||
version?: string;
|
||||
}
|
||||
type PatchCaseArgs = PatchCase & ClientArgs;
|
||||
type PatchCaseArgs = PatchCase;
|
||||
|
||||
interface PatchCasesArgs extends ClientArgs {
|
||||
interface PatchCasesArgs {
|
||||
cases: PatchCase[];
|
||||
}
|
||||
|
||||
|
@ -110,11 +102,6 @@ interface GetUserArgs {
|
|||
request: KibanaRequest;
|
||||
}
|
||||
|
||||
interface CaseCommentStats {
|
||||
commentTotals: Map<string, number>;
|
||||
alertTotals: Map<string, number>;
|
||||
}
|
||||
|
||||
interface CasesMapWithPageInfo {
|
||||
casesMap: Map<string, CaseResponse>;
|
||||
page: number;
|
||||
|
@ -135,10 +122,27 @@ interface GetReportersArgs {
|
|||
}
|
||||
|
||||
export class CasesService {
|
||||
constructor(
|
||||
private readonly log: Logger,
|
||||
private readonly authentication?: SecurityPluginSetup['authc']
|
||||
) {}
|
||||
private readonly log: Logger;
|
||||
private readonly authentication?: SecurityPluginSetup['authc'];
|
||||
private readonly unsecuredSavedObjectsClient: SavedObjectsClientContract;
|
||||
private readonly attachmentService: AttachmentService;
|
||||
|
||||
constructor({
|
||||
log,
|
||||
authentication,
|
||||
unsecuredSavedObjectsClient,
|
||||
attachmentService,
|
||||
}: {
|
||||
log: Logger;
|
||||
authentication?: SecurityPluginSetup['authc'];
|
||||
unsecuredSavedObjectsClient: SavedObjectsClientContract;
|
||||
attachmentService: AttachmentService;
|
||||
}) {
|
||||
this.log = log;
|
||||
this.authentication = authentication;
|
||||
this.unsecuredSavedObjectsClient = unsecuredSavedObjectsClient;
|
||||
this.attachmentService = attachmentService;
|
||||
}
|
||||
|
||||
private buildCaseIdsAggs = (
|
||||
size: number = 100
|
||||
|
@ -159,7 +163,6 @@ export class CasesService {
|
|||
});
|
||||
|
||||
public async getCaseIdsByAlertId({
|
||||
unsecuredSavedObjectsClient,
|
||||
alertId,
|
||||
filter,
|
||||
}: GetCaseIdsByAlertIdArgs): Promise<
|
||||
|
@ -172,7 +175,7 @@ export class CasesService {
|
|||
filter,
|
||||
]);
|
||||
|
||||
const response = await unsecuredSavedObjectsClient.find<
|
||||
const response = await this.unsecuredSavedObjectsClient.find<
|
||||
CommentAttributes,
|
||||
GetCaseIdsByAlertIdAggs
|
||||
>({
|
||||
|
@ -204,35 +207,32 @@ export class CasesService {
|
|||
* Returns a map of all cases.
|
||||
*/
|
||||
public async findCasesGroupedByID({
|
||||
unsecuredSavedObjectsClient,
|
||||
caseOptions,
|
||||
}: {
|
||||
unsecuredSavedObjectsClient: SavedObjectsClientContract;
|
||||
caseOptions: FindCaseOptions;
|
||||
}): Promise<CasesMapWithPageInfo> {
|
||||
const cases = await this.findCases({
|
||||
unsecuredSavedObjectsClient,
|
||||
options: caseOptions,
|
||||
});
|
||||
const cases = await this.findCases(caseOptions);
|
||||
|
||||
const casesMap = cases.saved_objects.reduce((accMap, caseInfo) => {
|
||||
accMap.set(caseInfo.id, caseInfo);
|
||||
return accMap;
|
||||
}, new Map<string, SavedObjectsFindResult<CaseAttributes>>());
|
||||
|
||||
const totalCommentsForCases = await this.getCaseCommentStats({
|
||||
unsecuredSavedObjectsClient,
|
||||
ids: Array.from(casesMap.keys()),
|
||||
const commentTotals = await this.attachmentService.getCaseCommentStats({
|
||||
unsecuredSavedObjectsClient: this.unsecuredSavedObjectsClient,
|
||||
caseIds: Array.from(casesMap.keys()),
|
||||
});
|
||||
|
||||
const casesWithComments = new Map<string, CaseResponse>();
|
||||
for (const [id, caseInfo] of casesMap.entries()) {
|
||||
const { alerts, nonAlerts } = commentTotals.get(id) ?? { alerts: 0, nonAlerts: 0 };
|
||||
|
||||
casesWithComments.set(
|
||||
id,
|
||||
flattenCaseSavedObject({
|
||||
savedObject: caseInfo,
|
||||
totalComment: totalCommentsForCases.commentTotals.get(id) ?? 0,
|
||||
totalAlerts: totalCommentsForCases.alertTotals.get(id) ?? 0,
|
||||
totalComment: nonAlerts,
|
||||
totalAlerts: alerts,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
@ -245,107 +245,69 @@ export class CasesService {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the number of cases that exist with a given status (open, closed, etc).
|
||||
*/
|
||||
public async findCaseStatusStats({
|
||||
unsecuredSavedObjectsClient,
|
||||
caseOptions,
|
||||
ensureSavedObjectsAreAuthorized,
|
||||
public async getCaseStatusStats({
|
||||
searchOptions,
|
||||
}: {
|
||||
unsecuredSavedObjectsClient: SavedObjectsClientContract;
|
||||
caseOptions: SavedObjectFindOptionsKueryNode;
|
||||
ensureSavedObjectsAreAuthorized: EnsureSOAuthCallback;
|
||||
}): Promise<number> {
|
||||
const cases = await this.findCases({
|
||||
unsecuredSavedObjectsClient,
|
||||
options: {
|
||||
...caseOptions,
|
||||
page: 1,
|
||||
perPage: MAX_DOCS_PER_PAGE,
|
||||
searchOptions: SavedObjectFindOptionsKueryNode;
|
||||
}): Promise<{
|
||||
[status in CaseStatuses]: number;
|
||||
}> {
|
||||
const cases = await this.unsecuredSavedObjectsClient.find<
|
||||
ESCaseAttributes,
|
||||
{
|
||||
statuses: {
|
||||
buckets: Array<{
|
||||
key: string;
|
||||
doc_count: number;
|
||||
}>;
|
||||
};
|
||||
}
|
||||
>({
|
||||
...searchOptions,
|
||||
type: CASE_SAVED_OBJECT,
|
||||
perPage: 0,
|
||||
aggs: {
|
||||
statuses: {
|
||||
terms: {
|
||||
field: `${CASE_SAVED_OBJECT}.attributes.status`,
|
||||
size: caseStatuses.length,
|
||||
order: { _key: 'asc' },
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// make sure that the retrieved cases were correctly filtered by owner
|
||||
ensureSavedObjectsAreAuthorized(
|
||||
cases.saved_objects.map((caseInfo) => ({ id: caseInfo.id, owner: caseInfo.attributes.owner }))
|
||||
);
|
||||
|
||||
return cases.saved_objects.length;
|
||||
const statusBuckets = CasesService.getStatusBuckets(cases.aggregations?.statuses.buckets);
|
||||
return {
|
||||
open: statusBuckets?.get('open') ?? 0,
|
||||
'in-progress': statusBuckets?.get('in-progress') ?? 0,
|
||||
closed: statusBuckets?.get('closed') ?? 0,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of total comments and alerts for a case
|
||||
*/
|
||||
public async getCaseCommentStats({
|
||||
unsecuredSavedObjectsClient,
|
||||
ids,
|
||||
}: {
|
||||
unsecuredSavedObjectsClient: SavedObjectsClientContract;
|
||||
ids: string[];
|
||||
}): Promise<CaseCommentStats> {
|
||||
if (ids.length <= 0) {
|
||||
return {
|
||||
commentTotals: new Map<string, number>(),
|
||||
alertTotals: new Map<string, number>(),
|
||||
};
|
||||
}
|
||||
|
||||
const getCommentsMapper = async (id: string) =>
|
||||
this.getAllCaseComments({
|
||||
unsecuredSavedObjectsClient,
|
||||
id,
|
||||
options: { page: 1, perPage: 1 },
|
||||
});
|
||||
|
||||
// Ensuring we don't do too many concurrent get running.
|
||||
const allComments = await pMap(ids, getCommentsMapper, {
|
||||
concurrency: MAX_CONCURRENT_SEARCHES,
|
||||
});
|
||||
|
||||
const alerts = await this.getAllCaseComments({
|
||||
unsecuredSavedObjectsClient,
|
||||
id: ids,
|
||||
options: {
|
||||
filter: nodeBuilder.is(`${CASE_COMMENT_SAVED_OBJECT}.attributes.type`, CommentType.alert),
|
||||
},
|
||||
});
|
||||
|
||||
const getID = (comments: SavedObjectsFindResponse<unknown>) => {
|
||||
return comments.saved_objects.length > 0
|
||||
? comments.saved_objects[0].references.find((ref) => ref.type === CASE_SAVED_OBJECT)?.id
|
||||
: undefined;
|
||||
};
|
||||
|
||||
const groupedComments = allComments.reduce((acc, comments) => {
|
||||
const id = getID(comments);
|
||||
if (id) {
|
||||
acc.set(id, comments.total);
|
||||
}
|
||||
private static getStatusBuckets(
|
||||
buckets: Array<{ key: string; doc_count: number }> | undefined
|
||||
): Map<string, number> | undefined {
|
||||
return buckets?.reduce((acc, bucket) => {
|
||||
acc.set(bucket.key, bucket.doc_count);
|
||||
return acc;
|
||||
}, new Map<string, number>());
|
||||
|
||||
const groupedAlerts = groupTotalAlertsByID({ comments: alerts });
|
||||
return { commentTotals: groupedComments, alertTotals: groupedAlerts };
|
||||
}
|
||||
|
||||
public async deleteCase({ unsecuredSavedObjectsClient, id: caseId }: GetCaseArgs) {
|
||||
public async deleteCase({ id: caseId }: GetCaseArgs) {
|
||||
try {
|
||||
this.log.debug(`Attempting to DELETE case ${caseId}`);
|
||||
return await unsecuredSavedObjectsClient.delete(CASE_SAVED_OBJECT, caseId);
|
||||
return await this.unsecuredSavedObjectsClient.delete(CASE_SAVED_OBJECT, caseId);
|
||||
} catch (error) {
|
||||
this.log.error(`Error on DELETE case ${caseId}: ${error}`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
public async getCase({
|
||||
unsecuredSavedObjectsClient,
|
||||
id: caseId,
|
||||
}: GetCaseArgs): Promise<SavedObject<CaseAttributes>> {
|
||||
public async getCase({ id: caseId }: GetCaseArgs): Promise<SavedObject<CaseAttributes>> {
|
||||
try {
|
||||
this.log.debug(`Attempting to GET case ${caseId}`);
|
||||
const caseSavedObject = await unsecuredSavedObjectsClient.get<ESCaseAttributes>(
|
||||
const caseSavedObject = await this.unsecuredSavedObjectsClient.get<ESCaseAttributes>(
|
||||
CASE_SAVED_OBJECT,
|
||||
caseId
|
||||
);
|
||||
|
@ -357,12 +319,11 @@ export class CasesService {
|
|||
}
|
||||
|
||||
public async getResolveCase({
|
||||
unsecuredSavedObjectsClient,
|
||||
id: caseId,
|
||||
}: GetCaseArgs): Promise<SavedObjectsResolveResponse<CaseAttributes>> {
|
||||
try {
|
||||
this.log.debug(`Attempting to resolve case ${caseId}`);
|
||||
const resolveCaseResult = await unsecuredSavedObjectsClient.resolve<ESCaseAttributes>(
|
||||
const resolveCaseResult = await this.unsecuredSavedObjectsClient.resolve<ESCaseAttributes>(
|
||||
CASE_SAVED_OBJECT,
|
||||
caseId
|
||||
);
|
||||
|
@ -377,12 +338,11 @@ export class CasesService {
|
|||
}
|
||||
|
||||
public async getCases({
|
||||
unsecuredSavedObjectsClient,
|
||||
caseIds,
|
||||
}: GetCasesArgs): Promise<SavedObjectsBulkResponse<CaseAttributes>> {
|
||||
try {
|
||||
this.log.debug(`Attempting to GET cases ${caseIds.join(', ')}`);
|
||||
const cases = await unsecuredSavedObjectsClient.bulkGet<ESCaseAttributes>(
|
||||
const cases = await this.unsecuredSavedObjectsClient.bulkGet<ESCaseAttributes>(
|
||||
caseIds.map((caseId) => ({ type: CASE_SAVED_OBJECT, id: caseId }))
|
||||
);
|
||||
return transformBulkResponseToExternalModel(cases);
|
||||
|
@ -392,13 +352,12 @@ export class CasesService {
|
|||
}
|
||||
}
|
||||
|
||||
public async findCases({
|
||||
unsecuredSavedObjectsClient,
|
||||
options,
|
||||
}: FindCasesArgs): Promise<SavedObjectsFindResponse<CaseAttributes>> {
|
||||
public async findCases(
|
||||
options?: SavedObjectFindOptionsKueryNode
|
||||
): Promise<SavedObjectsFindResponse<CaseAttributes>> {
|
||||
try {
|
||||
this.log.debug(`Attempting to find cases`);
|
||||
const cases = await unsecuredSavedObjectsClient.find<ESCaseAttributes>({
|
||||
const cases = await this.unsecuredSavedObjectsClient.find<ESCaseAttributes>({
|
||||
sortField: defaultSortField,
|
||||
...options,
|
||||
type: CASE_SAVED_OBJECT,
|
||||
|
@ -421,21 +380,20 @@ export class CasesService {
|
|||
}
|
||||
|
||||
private async getAllComments({
|
||||
unsecuredSavedObjectsClient,
|
||||
id,
|
||||
options,
|
||||
}: FindCommentsArgs): Promise<SavedObjectsFindResponse<CommentAttributes>> {
|
||||
try {
|
||||
this.log.debug(`Attempting to GET all comments internal for id ${JSON.stringify(id)}`);
|
||||
if (options?.page !== undefined || options?.perPage !== undefined) {
|
||||
return unsecuredSavedObjectsClient.find<CommentAttributes>({
|
||||
return this.unsecuredSavedObjectsClient.find<CommentAttributes>({
|
||||
type: CASE_COMMENT_SAVED_OBJECT,
|
||||
sortField: defaultSortField,
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
||||
return unsecuredSavedObjectsClient.find<CommentAttributes>({
|
||||
return this.unsecuredSavedObjectsClient.find<CommentAttributes>({
|
||||
type: CASE_COMMENT_SAVED_OBJECT,
|
||||
page: 1,
|
||||
perPage: MAX_DOCS_PER_PAGE,
|
||||
|
@ -453,7 +411,6 @@ export class CasesService {
|
|||
* to override this pass in the either the page or perPage options.
|
||||
*/
|
||||
public async getAllCaseComments({
|
||||
unsecuredSavedObjectsClient,
|
||||
id,
|
||||
options,
|
||||
}: FindCaseCommentsArgs): Promise<SavedObjectsFindResponse<CommentAttributes>> {
|
||||
|
@ -470,7 +427,6 @@ export class CasesService {
|
|||
|
||||
this.log.debug(`Attempting to GET all comments for case caseID ${JSON.stringify(id)}`);
|
||||
return await this.getAllComments({
|
||||
unsecuredSavedObjectsClient,
|
||||
id,
|
||||
options: {
|
||||
hasReferenceOperator: 'OR',
|
||||
|
@ -485,14 +441,11 @@ export class CasesService {
|
|||
}
|
||||
}
|
||||
|
||||
public async getReporters({
|
||||
unsecuredSavedObjectsClient,
|
||||
filter,
|
||||
}: GetReportersArgs): Promise<User[]> {
|
||||
public async getReporters({ filter }: GetReportersArgs): Promise<User[]> {
|
||||
try {
|
||||
this.log.debug(`Attempting to GET all reporters`);
|
||||
|
||||
const results = await unsecuredSavedObjectsClient.find<
|
||||
const results = await this.unsecuredSavedObjectsClient.find<
|
||||
ESCaseAttributes,
|
||||
{
|
||||
reporters: {
|
||||
|
@ -549,11 +502,11 @@ export class CasesService {
|
|||
}
|
||||
}
|
||||
|
||||
public async getTags({ unsecuredSavedObjectsClient, filter }: GetTagsArgs): Promise<string[]> {
|
||||
public async getTags({ filter }: GetTagsArgs): Promise<string[]> {
|
||||
try {
|
||||
this.log.debug(`Attempting to GET all cases`);
|
||||
|
||||
const results = await unsecuredSavedObjectsClient.find<
|
||||
const results = await this.unsecuredSavedObjectsClient.find<
|
||||
ESCaseAttributes,
|
||||
{ tags: { buckets: Array<{ key: string }> } }
|
||||
>({
|
||||
|
@ -604,15 +557,11 @@ export class CasesService {
|
|||
}
|
||||
}
|
||||
|
||||
public async postNewCase({
|
||||
unsecuredSavedObjectsClient,
|
||||
attributes,
|
||||
id,
|
||||
}: PostCaseArgs): Promise<SavedObject<CaseAttributes>> {
|
||||
public async postNewCase({ attributes, id }: PostCaseArgs): Promise<SavedObject<CaseAttributes>> {
|
||||
try {
|
||||
this.log.debug(`Attempting to POST a new case`);
|
||||
const transformedAttributes = transformAttributesToESModel(attributes);
|
||||
const createdCase = await unsecuredSavedObjectsClient.create<ESCaseAttributes>(
|
||||
const createdCase = await this.unsecuredSavedObjectsClient.create<ESCaseAttributes>(
|
||||
CASE_SAVED_OBJECT,
|
||||
transformedAttributes.attributes,
|
||||
{ id, references: transformedAttributes.referenceHandler.build() }
|
||||
|
@ -625,7 +574,6 @@ export class CasesService {
|
|||
}
|
||||
|
||||
public async patchCase({
|
||||
unsecuredSavedObjectsClient,
|
||||
caseId,
|
||||
updatedAttributes,
|
||||
originalCase,
|
||||
|
@ -635,7 +583,7 @@ export class CasesService {
|
|||
this.log.debug(`Attempting to UPDATE case ${caseId}`);
|
||||
const transformedAttributes = transformAttributesToESModel(updatedAttributes);
|
||||
|
||||
const updatedCase = await unsecuredSavedObjectsClient.update<ESCaseAttributes>(
|
||||
const updatedCase = await this.unsecuredSavedObjectsClient.update<ESCaseAttributes>(
|
||||
CASE_SAVED_OBJECT,
|
||||
caseId,
|
||||
transformedAttributes.attributes,
|
||||
|
@ -653,7 +601,6 @@ export class CasesService {
|
|||
}
|
||||
|
||||
public async patchCases({
|
||||
unsecuredSavedObjectsClient,
|
||||
cases,
|
||||
}: PatchCasesArgs): Promise<SavedObjectsBulkUpdateResponse<CaseAttributes>> {
|
||||
try {
|
||||
|
@ -670,7 +617,7 @@ export class CasesService {
|
|||
};
|
||||
});
|
||||
|
||||
const updatedCases = await unsecuredSavedObjectsClient.bulkUpdate<ESCaseAttributes>(
|
||||
const updatedCases = await this.unsecuredSavedObjectsClient.bulkUpdate<ESCaseAttributes>(
|
||||
bulkUpdate
|
||||
);
|
||||
return transformUpdateResponsesToExternalModels(updatedCases);
|
||||
|
|
|
@ -37,9 +37,8 @@ export const createCaseServiceMock = (): CaseServiceMock => {
|
|||
postNewCase: jest.fn(),
|
||||
patchCase: jest.fn(),
|
||||
patchCases: jest.fn(),
|
||||
getCaseCommentStats: jest.fn(),
|
||||
findCaseStatusStats: jest.fn(),
|
||||
findCasesGroupedByID: jest.fn(),
|
||||
getCaseStatusStats: jest.fn(),
|
||||
};
|
||||
|
||||
// the cast here is required because jest.Mocked tries to include private members and would throw an error
|
||||
|
@ -108,6 +107,7 @@ export const createAttachmentServiceMock = (): AttachmentServiceMock => {
|
|||
getAllAlertsAttachToCase: jest.fn(),
|
||||
countAlertsAttachedToCase: jest.fn(),
|
||||
executeCaseActionsAggregations: jest.fn(),
|
||||
getCaseCommentStats: jest.fn(),
|
||||
};
|
||||
|
||||
// the cast here is required because jest.Mocked tries to include private members and would throw an error
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue