Skip to content

Commit

Permalink
Make deprcated APIs internal in serverless
Browse files Browse the repository at this point in the history
  • Loading branch information
cnasikas committed Oct 30, 2024
1 parent 97f227e commit ef48ca3
Show file tree
Hide file tree
Showing 7 changed files with 220 additions and 50 deletions.
8 changes: 7 additions & 1 deletion x-pack/plugins/cases/server/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,12 +122,18 @@ export class CasePlugin
const router = core.http.createRouter<CasesRequestHandlerContext>();
const telemetryUsageCounter = plugins.usageCollection?.createUsageCounter(APP_ID);

const isServerless = plugins.cloud?.isServerlessEnabled;

registerRoutes({
router,
routes: [...getExternalRoutes(), ...getInternalRoutes(this.userProfileService)],
routes: [
...getExternalRoutes({ isServerless }),
...getInternalRoutes(this.userProfileService),
],
logger: this.logger,
kibanaVersion: this.kibanaVersion,
telemetryUsageCounter,
isServerless,
});

plugins.licensing.featureUsage.register(LICENSING_CASE_ASSIGNMENT_FEATURE, 'platinum');
Expand Down
62 changes: 62 additions & 0 deletions x-pack/plugins/cases/server/routes/api/cases/get_case.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* 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 { createCasesClientMock } from '../../../client/mocks';
import { getCaseRoute } from './get_case';
import { httpServerMock, loggingSystemMock } from '@kbn/core/server/mocks';

describe('getCaseRoute', () => {
const casesClientMock = createCasesClientMock();
const logger = loggingSystemMock.createLogger();
const response = httpServerMock.createResponseFactory();
const kibanaVersion = '8.17';
const context = { cases: { getCasesClient: jest.fn().mockResolvedValue(casesClientMock) } };

it('throws a bad request if the includeComments is set in serverless', async () => {
const router = getCaseRoute({ isServerless: true });
const request = httpServerMock.createKibanaRequest({
path: '/api/cases/{case_id}/?includeComments=true',
query: { includeComments: true },
params: { case_id: 'foo' },
});

await expect(
// @ts-expect-error: no need to create the context
router.handler({ response, request, logger, kibanaVersion, context })
).rejects.toThrowErrorMatchingInlineSnapshot(`
"Failed to retrieve case in route case id: foo
include comments: true: Error: includeComments is not supported"
`);
});

it('does not throw a bad request if the includeComments is set in non-serverless', async () => {
const router = getCaseRoute({ isServerless: false });
const request = httpServerMock.createKibanaRequest({
path: '/api/cases/{case_id}/?includeComments=true',
query: { includeComments: true },
params: { case_id: 'foo' },
});

await expect(
// @ts-expect-error: no need to create the context
router.handler({ response, request, logger, kibanaVersion, context })
).resolves.not.toThrow();
});

it('does not throw a bad request if the includeComments is not set in serverless', async () => {
const router = getCaseRoute({ isServerless: true });
const request = httpServerMock.createKibanaRequest({
path: '/api/cases/{case_id}',
params: { case_id: 'foo' },
});

await expect(
// @ts-expect-error: no need to create the context
router.handler({ response, request, logger, kibanaVersion, context })
).resolves.not.toThrow();
});
});
92 changes: 49 additions & 43 deletions x-pack/plugins/cases/server/routes/api/cases/get_case.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* 2.0.
*/

import Boom from '@hapi/boom';
import { schema } from '@kbn/config-schema';

import type { caseApiV1 } from '../../../../common/types/api';
Expand All @@ -26,53 +27,58 @@ const params = {
}),
};

export const getCaseRoute = createCasesRoute({
method: 'get',
path: CASE_DETAILS_URL,
params,
routerOptions: {
access: 'public',
summary: `Get a case`,
tags: ['oas-tag:cases'],
},
handler: async ({ context, request, response, logger, kibanaVersion }) => {
try {
const isIncludeCommentsParamProvidedByTheUser =
request.url.searchParams.has('includeComments');
export const getCaseRoute = ({ isServerless }: { isServerless?: boolean }) =>
createCasesRoute({
method: 'get',
path: CASE_DETAILS_URL,
params,
routerOptions: {
access: 'public',
summary: `Get a case`,
tags: ['oas-tag:cases'],
},
handler: async ({ context, request, response, logger, kibanaVersion }) => {
try {
const isIncludeCommentsParamProvidedByTheUser =
request.url.searchParams.has('includeComments');

if (isIncludeCommentsParamProvidedByTheUser) {
logDeprecatedEndpoint(
logger,
request.headers,
`The query parameter 'includeComments' of the get case API '${CASE_DETAILS_URL}' is deprecated`
);
}
if (isServerless && isIncludeCommentsParamProvidedByTheUser) {
throw Boom.badRequest('includeComments is not supported');
}

const caseContext = await context.cases;
const casesClient = await caseContext.getCasesClient();
const id = request.params.case_id;
if (isIncludeCommentsParamProvidedByTheUser) {
logDeprecatedEndpoint(
logger,
request.headers,
`The query parameter 'includeComments' of the get case API '${CASE_DETAILS_URL}' is deprecated`
);
}

const res: caseDomainV1.Case = await casesClient.cases.get({
id,
includeComments: request.query.includeComments,
});
const caseContext = await context.cases;
const casesClient = await caseContext.getCasesClient();
const id = request.params.case_id;

return response.ok({
...(isIncludeCommentsParamProvidedByTheUser && {
headers: {
...getWarningHeader(kibanaVersion, 'Deprecated query parameter includeComments'),
},
}),
body: res,
});
} catch (error) {
throw createCaseError({
message: `Failed to retrieve case in route case id: ${request.params.case_id} \ninclude comments: ${request.query.includeComments}: ${error}`,
error,
});
}
},
});
const res: caseDomainV1.Case = await casesClient.cases.get({
id,
includeComments: request.query.includeComments,
});

return response.ok({
...(isIncludeCommentsParamProvidedByTheUser && {
headers: {
...getWarningHeader(kibanaVersion, 'Deprecated query parameter includeComments'),
},
}),
body: res,
});
} catch (error) {
throw createCaseError({
message: `Failed to retrieve case in route case id: ${request.params.case_id} \ninclude comments: ${request.query.includeComments}: ${error}`,
error,
});
}
},
});

export const resolveCaseRoute = createCasesRoute({
method: 'get',
Expand Down
4 changes: 2 additions & 2 deletions x-pack/plugins/cases/server/routes/api/get_external_routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,11 @@ import { postCaseConfigureRoute } from './configure/post_configure';
import { getAllAlertsAttachedToCaseRoute } from './comments/get_alerts';
import { findUserActionsRoute } from './user_actions/find_user_actions';

export const getExternalRoutes = () =>
export const getExternalRoutes = ({ isServerless }: { isServerless?: boolean }) =>
[
deleteCaseRoute,
findCaseRoute,
getCaseRoute,
getCaseRoute({ isServerless }),
resolveCaseRoute,
patchCaseRoute,
postCaseRoute,
Expand Down
96 changes: 94 additions & 2 deletions x-pack/plugins/cases/server/routes/api/register_routes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,14 @@ describe('registerRoutes', () => {
}),
] as CaseRoute[];

const initApi = (casesRoutes: CaseRoute[]) => {
const initApi = (casesRoutes: CaseRoute[], isServerless?: boolean) => {
registerRoutes({
router,
logger,
routes: casesRoutes,
kibanaVersion: '8.2.0',
telemetryUsageCounter,
isServerless,
});

const simulateRequest = async ({
Expand Down Expand Up @@ -171,7 +172,13 @@ describe('registerRoutes', () => {

expect(router[method]).toHaveBeenCalledTimes(1);
expect(router[method]).toBeCalledWith(
{ path, validate: expect.anything() },
{
path,
options: {
access: 'internal',
},
validate: expect.anything(),
},
expect.anything()
);
}
Expand Down Expand Up @@ -329,4 +336,89 @@ describe('registerRoutes', () => {
});
});
});

describe('serverless', () => {
it('sets the deprecated APIs as internals in serverless', async () => {
const deprecatedRoute = createCasesRoute({
method: 'get',
path: '/deprecated',
options: { deprecated: true },
routerOptions: { access: 'public' },
handler: async () => response.ok(),
});

initApi([deprecatedRoute], true);

expect(router.get).toHaveBeenCalledWith(
expect.objectContaining({
options: {
access: 'internal',
},
}),
expect.anything()
);
});

it('respects the access property of the route in non-serverless', async () => {
const deprecatedRoute = createCasesRoute({
method: 'get',
path: '/deprecated',
options: { deprecated: true },
routerOptions: { access: 'public' },
handler: async () => response.ok(),
});

initApi([deprecatedRoute], false);

expect(router.get).toHaveBeenCalledWith(
expect.objectContaining({
options: {
access: 'public',
},
}),
expect.anything()
);
});
});

describe('route access', () => {
it('mark the API as internal if it does not specify its access', async () => {
const route = createCasesRoute({
method: 'get',
path: '/foo',
handler: async () => response.ok(),
});

initApi([route]);

expect(router.get).toHaveBeenCalledWith(
expect.objectContaining({
options: {
access: 'internal',
},
}),
expect.anything()
);
});

it('does not mark the API as internal if it does specify its access', async () => {
const route = createCasesRoute({
method: 'get',
path: '/foo',
routerOptions: { access: 'public' },
handler: async () => response.ok(),
});

initApi([route]);

expect(router.get).toHaveBeenCalledWith(
expect.objectContaining({
options: {
access: 'public',
},
}),
expect.anything()
);
});
});
});
7 changes: 5 additions & 2 deletions x-pack/plugins/cases/server/routes/api/register_routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,15 +71,18 @@ const logAndIncreaseDeprecationTelemetryCounters = ({
};

export const registerRoutes = (deps: RegisterRoutesDeps) => {
const { router, routes, logger, kibanaVersion, telemetryUsageCounter } = deps;
const { router, routes, logger, kibanaVersion, telemetryUsageCounter, isServerless } = deps;

routes.forEach((route) => {
const { method, path, params, options, routerOptions, handler } = route;

const routeAccess =
isServerless && options?.deprecated ? 'internal' : routerOptions?.access ?? 'internal';

(router[method] as RouteRegistrar<typeof method, CasesRequestHandlerContext>)(
{
path,
options: routerOptions,
options: { ...routerOptions, access: routeAccess },
validate: {
params: params?.params ?? escapeHatch,
query: params?.query ?? escapeHatch,
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/cases/server/routes/api/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export interface RegisterRoutesDeps {
logger: Logger;
kibanaVersion: PluginInitializerContext['env']['packageInfo']['version'];
telemetryUsageCounter?: TelemetryUsageCounter;
isServerless?: boolean;
}

export interface TotalCommentByCase {
Expand Down

0 comments on commit ef48ca3

Please sign in to comment.