Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 16 additions & 21 deletions x-pack/plugins/cases/common/api/cases/case.ts
Original file line number Diff line number Diff line change
Expand Up @@ -329,17 +329,24 @@ export const AllTagsFindRequestRt = rt.partial({

export const AllReportersFindRequestRt = AllTagsFindRequestRt;

export const CasesBulkGetRequestRt = rt.intersection([
rt.type({
ids: rt.array(rt.string),
}),
rt.partial({
fields: rt.union([rt.undefined, rt.array(rt.string), rt.string]),
}),
]);
export const CasesBulkGetRequestRt = rt.type({
ids: rt.array(rt.string),
});

export const CasesBulkGetResponseFieldsRt = rt.type({
id: rt.string,
description: rt.string,
title: rt.string,
owner: rt.string,
version: rt.string,
totalComments: rt.number,
status: CaseStatusRt,
created_at: rt.string,
created_by: UserRt,
});

export const CasesBulkGetResponseRt = rt.type({
cases: CasesRt,
cases: rt.array(CasesBulkGetResponseFieldsRt),
errors: rt.array(
rt.type({
error: rt.string,
Expand Down Expand Up @@ -375,15 +382,3 @@ export type CasesByAlertId = rt.TypeOf<typeof CasesByAlertIdRt>;

export type CasesBulkGetRequest = rt.TypeOf<typeof CasesBulkGetRequestRt>;
export type CasesBulkGetResponse = rt.TypeOf<typeof CasesBulkGetResponseRt>;
export type CasesBulkGetRequestCertainFields<Field extends keyof Case = keyof Case> = Omit<
CasesBulkGetRequest,
'fields'
> & {
fields?: Field[];
};
export type CasesBulkGetResponseCertainFields<Field extends keyof Case = keyof Case> = Omit<
CasesBulkGetResponse,
'cases'
> & {
cases: Array<Pick<Case, Field | 'id' | 'version' | 'owner'>>;
};
7 changes: 1 addition & 6 deletions x-pack/plugins/cases/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,7 @@ export {
ExternalReferenceStorageType,
} from './api';

export type {
Case,
Cases,
CasesBulkGetRequestCertainFields,
CasesBulkGetResponseCertainFields,
} from './api';
export type { Case, Cases, CasesBulkGetRequest, CasesBulkGetResponse } from './api';

export type {
CaseUI,
Expand Down
14 changes: 4 additions & 10 deletions x-pack/plugins/cases/public/api/decoders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,12 @@ import type {
CasesFindResponse,
CasesStatusResponse,
CasesMetricsResponse,
CasesBulkGetResponseCertainFields,
Case,
CasesBulkGetResponse,
} from '../../common/api';
import {
CasesBulkGetResponseRt,
CasesFindResponseRt,
CasesStatusResponseRt,
CasesRt,
getTypeForCertainFieldsFromArray,
CasesMetricsResponseRt,
} from '../../common/api';

Expand All @@ -41,12 +39,8 @@ export const decodeCasesMetricsResponse = (metrics?: CasesMetricsResponse) =>
fold(throwErrors(createToasterPlainError), identity)
);

export const decodeCasesBulkGetResponse = <Field extends keyof Case = keyof Case>(
res: CasesBulkGetResponseCertainFields<Field>,
fields?: string[]
) => {
const typeToDecode = getTypeForCertainFieldsFromArray(CasesRt, fields);
pipe(typeToDecode.decode(res.cases), fold(throwErrors(createToasterPlainError), identity));
export const decodeCasesBulkGetResponse = (res: CasesBulkGetResponse) => {
pipe(CasesBulkGetResponseRt.decode(res), fold(throwErrors(createToasterPlainError), identity));

return res;
};
30 changes: 18 additions & 12 deletions x-pack/plugins/cases/public/api/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,27 +50,33 @@ describe('api', () => {

describe('bulkGetCases', () => {
const http = httpServiceMock.createStartContract({ basePath: '' });
http.post.mockResolvedValue({ cases: [{ title: 'test' }], errors: [] });
const snakeCase = casesSnake[0];
const theCase = {
id: snakeCase.id,
description: snakeCase.description,
owner: snakeCase.owner,
title: snakeCase.title,
version: snakeCase.version,
status: snakeCase.status,
created_at: snakeCase.created_at,
created_by: snakeCase.created_by,
totalComments: snakeCase.totalComment,
};

it('should return the correct cases with a subset of fields', async () => {
expect(await bulkGetCases({ http, params: { ids: ['test'], fields: ['title'] } })).toEqual({
cases: [{ title: 'test' }],
errors: [],
});
});
http.post.mockResolvedValue({ cases: [theCase], errors: [] });

it('should return the correct cases with all fields', async () => {
http.post.mockResolvedValueOnce({ cases: casesSnake, errors: [] });
it('should return the correct cases ', async () => {
http.post.mockResolvedValueOnce({ cases: [theCase], errors: [] });
expect(await bulkGetCases({ http, params: { ids: ['test'] } })).toEqual({
cases: casesSnake,
cases: [theCase],
errors: [],
});
});

it('should have been called with the correct path', async () => {
await bulkGetCases({ http, params: { ids: ['test'], fields: ['title'] } });
await bulkGetCases({ http, params: { ids: ['test'] } });
expect(http.post).toHaveBeenCalledWith('/internal/cases/_bulk_get', {
body: '{"ids":["test"],"fields":["title"]}',
body: '{"ids":["test"]}',
});
});
});
Expand Down
24 changes: 9 additions & 15 deletions x-pack/plugins/cases/public/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,8 @@ import {
INTERNAL_BULK_GET_CASES_URL,
} from '../../common/constants';
import type {
Case,
CasesBulkGetRequestCertainFields,
CasesBulkGetResponseCertainFields,
CasesBulkGetRequest,
CasesBulkGetResponse,
CasesFindRequest,
CasesFindResponse,
CasesMetricsRequest,
Expand Down Expand Up @@ -68,20 +67,15 @@ export const getCasesMetrics = async ({
return convertToCamelCase(decodeCasesMetricsResponse(res));
};

export const bulkGetCases = async <Field extends keyof Case = keyof Case>({
export const bulkGetCases = async ({
http,
signal,
params,
}: HTTPService & { params: CasesBulkGetRequestCertainFields<Field> }): Promise<
CasesBulkGetResponseCertainFields<Field>
> => {
const res = await http.post<CasesBulkGetResponseCertainFields<Field>>(
INTERNAL_BULK_GET_CASES_URL,
{
body: JSON.stringify({ ...params }),
signal,
}
);
}: HTTPService & { params: CasesBulkGetRequest }): Promise<CasesBulkGetResponse> => {
const res = await http.post<CasesBulkGetResponse>(INTERNAL_BULK_GET_CASES_URL, {
body: JSON.stringify({ ...params }),
signal,
});

return decodeCasesBulkGetResponse(res, params.fields);
return decodeCasesBulkGetResponse(res);
};
35 changes: 21 additions & 14 deletions x-pack/plugins/cases/public/client/api/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,27 +84,34 @@ describe('createClientAPI', () => {
describe('bulkGet', () => {
const http = httpServiceMock.createStartContract({ basePath: '' });
const api = createClientAPI({ http });
http.post.mockResolvedValue({ cases: [{ title: 'test' }], errors: [] });

it('should return the correct cases with a subset of fields', async () => {
expect(await api.cases.bulkGet({ ids: ['test'], fields: ['title'] })).toEqual({
cases: [{ title: 'test' }],
errors: [],
});
});

it('should return the correct cases with all fields', async () => {
http.post.mockResolvedValueOnce({ cases: casesSnake, errors: [] });
expect(await api.cases.bulkGet({ ids: ['test'], fields: ['title'] })).toEqual({
cases: casesSnake,
const snakeCase = casesSnake[0];
const theCase = {
id: snakeCase.id,
description: snakeCase.description,
owner: snakeCase.owner,
title: snakeCase.title,
version: snakeCase.version,
status: snakeCase.status,
created_at: snakeCase.created_at,
created_by: snakeCase.created_by,
totalComments: snakeCase.totalComment,
};

http.post.mockResolvedValue({ cases: [theCase], errors: [] });

it('should return the correct cases', async () => {
http.post.mockResolvedValueOnce({ cases: [theCase], errors: [] });
expect(await api.cases.bulkGet({ ids: ['test'] })).toEqual({
cases: [theCase],
errors: [],
});
});

it('should have been called with the correct path', async () => {
await api.cases.bulkGet({ ids: ['test'], fields: ['title'] });
await api.cases.bulkGet({ ids: ['test'] });
expect(http.post).toHaveBeenCalledWith('/internal/cases/_bulk_get', {
body: '{"ids":["test"],"fields":["title"]}',
body: '{"ids":["test"]}',
});
});
});
Expand Down
10 changes: 3 additions & 7 deletions x-pack/plugins/cases/public/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,8 @@ import type { LicensingPluginStart } from '@kbn/licensing-plugin/public';
import type { FilesSetup, FilesStart } from '@kbn/files-plugin/public';
import type { SavedObjectsManagementPluginStart } from '@kbn/saved-objects-management-plugin/public';
import type {
Case,
CasesBulkGetRequestCertainFields,
CasesBulkGetResponseCertainFields,
CasesBulkGetRequest,
CasesBulkGetResponse,
CasesByAlertId,
CasesByAlertIDRequest,
CasesFindRequest,
Expand Down Expand Up @@ -106,10 +105,7 @@ export interface CasesUiStart {
find: (query: CasesFindRequest, signal?: AbortSignal) => Promise<CasesFindResponseUI>;
getCasesStatus: (query: CasesStatusRequest, signal?: AbortSignal) => Promise<CasesStatus>;
getCasesMetrics: (query: CasesMetricsRequest, signal?: AbortSignal) => Promise<CasesMetrics>;
bulkGet: <Field extends keyof Case = keyof Case>(
params: CasesBulkGetRequestCertainFields<Field>,
signal?: AbortSignal
) => Promise<CasesBulkGetResponseCertainFields<Field>>;
bulkGet: (params: CasesBulkGetRequest, signal?: AbortSignal) => Promise<CasesBulkGetResponse>;
};
};
ui: {
Expand Down
46 changes: 0 additions & 46 deletions x-pack/plugins/cases/server/client/cases/bulk_get.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,6 @@
* 2.0.
*/

import type { Case } from '../../../common/api';
import { getTypeProps, CaseRt } from '../../../common/api';
import { mockCases } from '../../mocks';
import { createCasesClientMockArgs } from '../mocks';
import { bulkGet } from './bulk_get';

Expand All @@ -27,47 +24,4 @@ describe('bulkGet', () => {
);
});
});

describe('throwErrorIfFieldsAreInvalid', () => {
const caseSO = mockCases[0];
const clientArgs = createCasesClientMockArgs();
clientArgs.services.caseService.getCases.mockResolvedValue({ saved_objects: [caseSO] });
clientArgs.services.attachmentService.getter.getCaseCommentStats.mockResolvedValue(new Map());

clientArgs.authorization.getAndEnsureAuthorizedEntities.mockResolvedValue({
authorized: [caseSO],
unauthorized: [],
});

const typeProps = getTypeProps(CaseRt) ?? {};
const validFields = Object.keys(typeProps) as Array<keyof Case>;

beforeEach(() => {
jest.clearAllMocks();
});

it.each(validFields)('supports valid field: %s', async (field) => {
const ids = ['test'];

await expect(bulkGet({ ids, fields: [field] }, clientArgs)).resolves.not.toThrow();
});

it('throws if the requested field is not valid', async () => {
const ids = ['test'];

// @ts-expect-error
await expect(bulkGet({ ids, fields: ['not-valid'] }, clientArgs)).rejects.toThrow(
'Failed to bulk get cases: test: Error: Field: not-valid is not supported'
);
});

it('throws for nested fields', async () => {
const ids = ['test'];

// @ts-expect-error
await expect(bulkGet({ ids, fields: ['created_by.username'] }, clientArgs)).rejects.toThrow(
'Failed to bulk get cases: test: Error: Field: created_by.username is not supported'
);
});
});
});
Loading