Skip to content

Commit

Permalink
Merge pull request #3 from angorayc/data-quality-api-2
Browse files Browse the repository at this point in the history
add stats api
  • Loading branch information
angorayc authored Dec 12, 2022
2 parents ea3bed9 + 2d45059 commit d97694f
Show file tree
Hide file tree
Showing 23 changed files with 655 additions and 37 deletions.
1 change: 1 addition & 0 deletions packages/kbn-securitysolution-es-utils/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ RUNTIME_DEPS = [
]

TYPES_DEPS = [
"//packages/core/http/core-http-server:npm_module_types",
"@npm//@elastic/elasticsearch",
"@npm//@hapi/boom",
"@npm//tslib",
Expand Down
1 change: 1 addition & 0 deletions packages/kbn-securitysolution-es-utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,4 @@ export * from './src/set_index_template';
export * from './src/set_policy';
export * from './src/set_template';
export * from './src/transform_error';
export * from './src/build_response';
65 changes: 65 additions & 0 deletions packages/kbn-securitysolution-es-utils/src/build_response/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* 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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import type { CustomHttpResponseOptions, KibanaResponseFactory } from '@kbn/core-http-server';

const statusToErrorMessage = (
statusCode: number
):
| 'Bad Request'
| 'Unauthorized'
| 'Forbidden'
| 'Not Found'
| 'Conflict'
| 'Internal Error'
| '(unknown error)' => {
switch (statusCode) {
case 400:
return 'Bad Request';
case 401:
return 'Unauthorized';
case 403:
return 'Forbidden';
case 404:
return 'Not Found';
case 409:
return 'Conflict';
case 500:
return 'Internal Error';
default:
return '(unknown error)';
}
};

export class ResponseFactory {
constructor(private response: KibanaResponseFactory) {}

error<T>({ statusCode, body, headers }: CustomHttpResponseOptions<T>) {
const contentType: CustomHttpResponseOptions<T>['headers'] = {
'content-type': 'application/json',
};
const defaultedHeaders: CustomHttpResponseOptions<T>['headers'] = {
...contentType,
...(headers ?? {}),
};

return this.response.custom({
body: Buffer.from(
JSON.stringify({
message: body ?? statusToErrorMessage(statusCode),
status_code: statusCode,
})
),
headers: defaultedHeaders,
statusCode,
});
}
}

export const buildResponse = (response: KibanaResponseFactory): ResponseFactory =>
new ResponseFactory(response);
13 changes: 13 additions & 0 deletions x-pack/plugins/data_quality/common/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

export const PLUGIN_ID = 'dataQuality';
export const PLUGIN_NAME = 'dataQuality';

export const BASE_PATH = '/internal/data_quality';
export const GET_INDEX_STATS = `${BASE_PATH}/stats/{pattern}`;
export const GET_INDEX_MAPPINGS = `${BASE_PATH}/mappings/{pattern}`;
11 changes: 11 additions & 0 deletions x-pack/plugins/data_quality/server/__mocks__/request.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
* 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 { httpServerMock } from '@kbn/core/server/mocks';

export const requestMock = {
create: httpServerMock.createKibanaRequest,
};
56 changes: 56 additions & 0 deletions x-pack/plugins/data_quality/server/__mocks__/request_context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* 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 { coreMock } from '@kbn/core/server/mocks';
import { licensingMock } from '@kbn/licensing-plugin/server/mocks';

export const createMockClients = () => {
const core = coreMock.createRequestHandlerContext();
const license = licensingMock.createLicenseMock();

return {
core,
clusterClient: core.elasticsearch.client,
savedObjectsClient: core.savedObjects.client,

licensing: {
...licensingMock.createRequestHandlerContext({ license }),
license,
},

config: createMockConfig(),
appClient: createAppClientMock(),
};
};

type MockClients = ReturnType<typeof createMockClients>;

const convertRequestContextMock = <T>(context: T) => {
return coreMock.createCustomRequestHandlerContext(context);
};

const createMockConfig = () => ({});

const createAppClientMock = () => ({});

const createRequestContextMock = (clients: MockClients = createMockClients()) => {
return {
core: clients.core,
};
};

const createTools = () => {
const clients = createMockClients();
const context = createRequestContextMock(clients);

return { clients, context };
};

export const requestContextMock = {
create: createRequestContextMock,
convertContext: convertRequestContextMock,
createTools,
};
12 changes: 12 additions & 0 deletions x-pack/plugins/data_quality/server/__mocks__/response.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/*
* 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 { httpServerMock } from '@kbn/core/server/mocks';

export const responseMock = {
create: httpServerMock.createResponseFactory,
};
94 changes: 94 additions & 0 deletions x-pack/plugins/data_quality/server/__mocks__/server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*
* 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 { httpServiceMock } from '@kbn/core/server/mocks';
import type { RequestHandler, RouteConfig, KibanaRequest } from '@kbn/core/server';
import type { RequestHandlerContext } from '@kbn/core-http-request-handler-context-server';

import { requestMock } from './request';
import { responseMock as responseFactoryMock } from './response';
import { requestContextMock } from './request_context';
import { responseAdapter } from './test_adapters';

interface Route {
config: RouteConfig<unknown, unknown, unknown, 'get' | 'post' | 'delete' | 'patch' | 'put'>;
handler: RequestHandler;
}

const getRoute = (routerMock: MockServer['router']): Route => {
const routeCalls = [
...routerMock.get.mock.calls,
...routerMock.post.mock.calls,
...routerMock.put.mock.calls,
...routerMock.patch.mock.calls,
...routerMock.delete.mock.calls,
];

const [route] = routeCalls;
if (!route) {
throw new Error('No route registered!');
}

const [config, handler] = route;
return { config, handler };
};

const buildResultMock = () => ({ ok: jest.fn((x) => x), badRequest: jest.fn((x) => x) });

class MockServer {
constructor(
public readonly router = httpServiceMock.createRouter(),
private responseMock = responseFactoryMock.create(),
private contextMock = requestContextMock.convertContext(requestContextMock.create()),
private resultMock = buildResultMock()
) {}

public validate(request: KibanaRequest) {
this.validateRequest(request);
return this.resultMock;
}

public async inject(request: KibanaRequest, context: RequestHandlerContext = this.contextMock) {
const validatedRequest = this.validateRequest(request);
const [rejection] = this.resultMock.badRequest.mock.calls;
if (rejection) {
throw new Error(`Request was rejected with message: '${rejection}'`);
}

await this.getRoute().handler(context, validatedRequest, this.responseMock);
return responseAdapter(this.responseMock);
}

private getRoute(): Route {
return getRoute(this.router);
}

private maybeValidate(part: any, validator?: any): any {
return typeof validator === 'function' ? validator(part, this.resultMock) : part;
}

private validateRequest(request: KibanaRequest): KibanaRequest {
const validations = this.getRoute().config.validate;
if (!validations) {
return request;
}

const validatedRequest = requestMock.create({
path: request.route.path,
method: request.route.method,
body: this.maybeValidate(request.body, validations.body),
query: this.maybeValidate(request.query, validations.query),
params: this.maybeValidate(request.params, validations.params),
});

return validatedRequest;
}
}
const createMockServer = () => new MockServer();

export const serverMock = {
create: createMockServer,
};
64 changes: 64 additions & 0 deletions x-pack/plugins/data_quality/server/__mocks__/test_adapters.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* 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 type { responseMock } from './response';

type ResponseMock = ReturnType<typeof responseMock.create>;
type Method = keyof ResponseMock;

type MockCall = any; // eslint-disable-line @typescript-eslint/no-explicit-any

interface ResponseCall {
body: any; // eslint-disable-line @typescript-eslint/no-explicit-any
status: number;
}

/**
* @internal
*/
export interface Response extends ResponseCall {
calls: ResponseCall[];
}

const buildResponses = (method: Method, calls: MockCall[]): ResponseCall[] => {
if (!calls.length) return [];

switch (method) {
case 'ok':
return calls.map(([call]) => ({ status: 200, body: call.body }));
case 'custom':
return calls.map(([call]) => ({
status: call.statusCode,
body: JSON.parse(call.body),
}));
case 'customError':
return calls.map(([call]) => ({
status: call.statusCode,
body: call.body,
}));
default:
throw new Error(`Encountered unexpected call to response.${method}`);
}
};

export const responseAdapter = (response: ResponseMock): Response => {
const methods = Object.keys(response) as Method[];
const calls = methods
.reduce<Response['calls']>((responses, method) => {
const methodMock = response[method];
return [...responses, ...buildResponses(method, methodMock.mock.calls)];
}, [])
.sort((call, other) => other.status - call.status);

const [{ body, status }] = calls;

return {
body,
status,
calls,
};
};
2 changes: 1 addition & 1 deletion x-pack/plugins/data_quality/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@ export function plugin(initializerContext: PluginInitializerContext) {
return new DataQualityPlugin(initializerContext);
}

export { DataQualityPluginSetup, DataQualityPluginStart } from './types';
export type { DataQualityPluginSetup, DataQualityPluginStart } from './types';
18 changes: 18 additions & 0 deletions x-pack/plugins/data_quality/server/lib/fetch_stats.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { IndicesStatsResponse } from '@elastic/elasticsearch/lib/api/types';
import type { IScopedClusterClient } from '@kbn/core/server';

export const fetchStats = async (
client: IScopedClusterClient,
indexName: string
): Promise<IndicesStatsResponse> =>
await client.asCurrentUser.indices.stats({
expand_wildcards: ['open'],
index: indexName,
});
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@
* 2.0.
*/

export const PLUGIN_ID = 'dataQuality';
export const PLUGIN_NAME = 'dataQuality';
export * from './fetch_mappings';
export * from './fetch_stats';
6 changes: 3 additions & 3 deletions x-pack/plugins/data_quality/server/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import { PluginInitializerContext, CoreSetup, CoreStart, Plugin, Logger } from '@kbn/core/server';

import { DataQualityPluginSetup, DataQualityPluginStart } from './types';
import { defineRoutes } from './routes';
import { getIndexMappingsRoute, getIndexStatsRoute } from './routes';

export class DataQualityPlugin implements Plugin<DataQualityPluginSetup, DataQualityPluginStart> {
private readonly logger: Logger;
Expand All @@ -22,8 +22,8 @@ export class DataQualityPlugin implements Plugin<DataQualityPluginSetup, DataQua
const router = core.http.createRouter();

// Register server side APIs
defineRoutes(router);

getIndexMappingsRoute(router);
getIndexStatsRoute(router);
return {};
}

Expand Down
Loading

0 comments on commit d97694f

Please sign in to comment.