[SIEM][Lists] Removes plugin dependencies, adds more unit tests, fixes more TypeScript types

* Removes plugin dependencies for better integration outside of Requests such as alerting
* Adds more unit tests
* Fixes more TypeScript types to be more normalized
* Makes this work with the user 'elastic' if security is turned off

- [x] [Unit or functional tests](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#cross-browser-compatibility) were updated or added to match the most common scenarios
This commit is contained in:
Frank Hassanabad 2020-04-29 19:58:27 -06:00 committed by GitHub
parent fba5128bd8
commit bcda1096e1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
59 changed files with 649 additions and 397 deletions

View file

@ -0,0 +1,46 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { httpServerMock } from 'src/core/server/mocks';
import { KibanaRequest } from 'src/core/server';
import { spacesServiceMock } from '../../spaces/server/spaces_service/spaces_service.mock';
import { getSpaceId } from './get_space_id';
describe('get_space_id', () => {
let request = KibanaRequest.from(httpServerMock.createRawRequest({}));
beforeEach(() => {
request = KibanaRequest.from(httpServerMock.createRawRequest({}));
jest.clearAllMocks();
});
afterEach(() => {
jest.clearAllMocks();
});
test('it returns "default" as the space id given a space id of "default"', () => {
const spaces = spacesServiceMock.createSetupContract();
const space = getSpaceId({ request, spaces });
expect(space).toEqual('default');
});
test('it returns "another-space" as the space id given a space id of "another-space"', () => {
const spaces = spacesServiceMock.createSetupContract('another-space');
const space = getSpaceId({ request, spaces });
expect(space).toEqual('another-space');
});
test('it returns "default" as the space id given a space id of undefined', () => {
const space = getSpaceId({ request, spaces: undefined });
expect(space).toEqual('default');
});
test('it returns "default" as the space id given a space id of null', () => {
const space = getSpaceId({ request, spaces: null });
expect(space).toEqual('default');
});
});

View file

@ -6,9 +6,9 @@
import { KibanaRequest } from 'kibana/server';
import { SpacesServiceSetup } from '../../../../spaces/server';
import { SpacesServiceSetup } from '../../spaces/server';
export const getSpace = ({
export const getSpaceId = ({
spaces,
request,
}: {

View file

@ -0,0 +1,67 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { httpServerMock } from 'src/core/server/mocks';
import { KibanaRequest } from 'src/core/server';
import { securityMock } from '../../security/server/mocks';
import { SecurityPluginSetup } from '../../security/server';
import { getUser } from './get_user';
describe('get_user', () => {
let request = KibanaRequest.from(httpServerMock.createRawRequest({}));
beforeEach(() => {
jest.clearAllMocks();
request = KibanaRequest.from(httpServerMock.createRawRequest({}));
});
afterEach(() => {
jest.clearAllMocks();
});
test('it returns "bob" as the user given a security request with "bob"', () => {
const security: SecurityPluginSetup = securityMock.createSetup();
security.authc.getCurrentUser = jest.fn().mockReturnValue({ username: 'bob' });
const user = getUser({ request, security });
expect(user).toEqual('bob');
});
test('it returns "alice" as the user given a security request with "alice"', () => {
const security: SecurityPluginSetup = securityMock.createSetup();
security.authc.getCurrentUser = jest.fn().mockReturnValue({ username: 'alice' });
const user = getUser({ request, security });
expect(user).toEqual('alice');
});
test('it returns "elastic" as the user given null as the current user', () => {
const security: SecurityPluginSetup = securityMock.createSetup();
security.authc.getCurrentUser = jest.fn().mockReturnValue(null);
const user = getUser({ request, security });
expect(user).toEqual('elastic');
});
test('it returns "elastic" as the user given undefined as the current user', () => {
const security: SecurityPluginSetup = securityMock.createSetup();
security.authc.getCurrentUser = jest.fn().mockReturnValue(undefined);
const user = getUser({ request, security });
expect(user).toEqual('elastic');
});
test('it returns "elastic" as the user given undefined as the plugin', () => {
const security: SecurityPluginSetup = securityMock.createSetup();
security.authc.getCurrentUser = jest.fn().mockReturnValue(undefined);
const user = getUser({ request, security: undefined });
expect(user).toEqual('elastic');
});
test('it returns "elastic" as the user given null as the plugin', () => {
const security: SecurityPluginSetup = securityMock.createSetup();
security.authc.getCurrentUser = jest.fn().mockReturnValue(undefined);
const user = getUser({ request, security: null });
expect(user).toEqual('elastic');
});
});

View file

@ -6,17 +6,21 @@
import { KibanaRequest } from 'kibana/server';
import { SecurityPluginSetup } from '../../../../security/server';
import { SecurityPluginSetup } from '../../security/server';
interface GetUserOptions {
security: SecurityPluginSetup;
export interface GetUserOptions {
security: SecurityPluginSetup | null | undefined;
request: KibanaRequest;
}
export const getUser = ({ security, request }: GetUserOptions): string => {
const authenticatedUser = security.authc.getCurrentUser(request);
if (authenticatedUser != null) {
return authenticatedUser.username;
if (security != null) {
const authenticatedUser = security.authc.getCurrentUser(request);
if (authenticatedUser != null) {
return authenticatedUser.username;
} else {
return 'elastic';
}
} else {
return 'elastic';
}

View file

@ -5,7 +5,7 @@
*/
import { first } from 'rxjs/operators';
import { ElasticsearchServiceSetup, Logger, PluginInitializerContext } from 'kibana/server';
import { Logger, PluginInitializerContext } from 'kibana/server';
import { CoreSetup } from 'src/core/server';
import { SecurityPluginSetup } from '../../security/server';
@ -16,12 +16,13 @@ import { initRoutes } from './routes/init_routes';
import { ListClient } from './services/lists/client';
import { ContextProvider, ContextProviderReturn, PluginsSetup } from './types';
import { createConfig$ } from './create_config';
import { getSpaceId } from './get_space_id';
import { getUser } from './get_user';
export class ListPlugin {
private readonly logger: Logger;
private spaces: SpacesServiceSetup | undefined | null;
private config: ConfigType | undefined | null;
private elasticsearch: ElasticsearchServiceSetup | undefined | null;
private security: SecurityPluginSetup | undefined | null;
constructor(private readonly initializerContext: PluginInitializerContext) {
@ -38,7 +39,6 @@ export class ListPlugin {
);
this.spaces = plugins.spaces?.spacesService;
this.config = config;
this.elasticsearch = core.elasticsearch;
this.security = plugins.security;
core.http.registerRouteHandlerContext('lists', this.createRouteHandlerContext());
@ -56,28 +56,28 @@ export class ListPlugin {
private createRouteHandlerContext = (): ContextProvider => {
return async (context, request): ContextProviderReturn => {
const { spaces, config, security, elasticsearch } = this;
const { spaces, config, security } = this;
const {
core: {
elasticsearch: { dataClient },
elasticsearch: {
dataClient: { callAsCurrentUser },
},
},
} = context;
if (config == null) {
throw new TypeError('Configuration is required for this plugin to operate');
} else if (elasticsearch == null) {
throw new TypeError('Elastic Search is required for this plugin to operate');
} else if (security == null) {
// TODO: This might be null, test authentication being turned off.
throw new TypeError('Security plugin is required for this plugin to operate');
} else {
const spaceId = getSpaceId({ request, spaces });
const user = getUser({ request, security });
return {
getListClient: (): ListClient =>
new ListClient({
callCluster: callAsCurrentUser,
config,
dataClient,
request,
security,
spaces,
spaceId,
user,
}),
};
}

View file

@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { TestReadable } from '../mocks/test_readable';
import { TestReadable } from '../mocks';
import { BufferLines } from './buffer_lines';

View file

@ -31,7 +31,7 @@ describe('crete_list_item', () => {
expect(listItem).toEqual(expected);
});
test('It calls "callAsCurrentUser" with body, index, and listIndex', async () => {
test('It calls "callCluster" with body, index, and listIndex', async () => {
const options = getCreateListItemOptionsMock();
await createListItem(options);
const body = getIndexESListItemMock();
@ -40,7 +40,7 @@ describe('crete_list_item', () => {
id: LIST_ITEM_ID,
index: LIST_ITEM_INDEX,
};
expect(options.dataClient.callAsCurrentUser).toBeCalledWith('index', expected);
expect(options.callCluster).toBeCalledWith('index', expected);
});
test('It returns an auto-generated id if id is sent in undefined', async () => {

View file

@ -6,6 +6,7 @@
import uuid from 'uuid';
import { CreateDocumentResponse } from 'elasticsearch';
import { APICaller } from 'kibana/server';
import {
IdOrUndefined,
@ -14,7 +15,6 @@ import {
MetaOrUndefined,
Type,
} from '../../../common/schemas';
import { DataClient } from '../../types';
import { transformListItemToElasticQuery } from '../utils';
export interface CreateListItemOptions {
@ -22,7 +22,7 @@ export interface CreateListItemOptions {
listId: string;
type: Type;
value: string;
dataClient: DataClient;
callCluster: APICaller;
listItemIndex: string;
user: string;
meta: MetaOrUndefined;
@ -35,7 +35,7 @@ export const createListItem = async ({
listId,
type,
value,
dataClient,
callCluster,
listItemIndex,
user,
meta,
@ -58,7 +58,7 @@ export const createListItem = async ({
...transformListItemToElasticQuery({ type, value }),
};
const response: CreateDocumentResponse = await dataClient.callAsCurrentUser('index', {
const response: CreateDocumentResponse = await callCluster('index', {
body,
id,
index: listItemIndex,

View file

@ -24,13 +24,13 @@ describe('crete_list_item_bulk', () => {
jest.clearAllMocks();
});
test('It calls "callAsCurrentUser" with body, index, and the bulk items', async () => {
test('It calls "callCluster" with body, index, and the bulk items', async () => {
const options = getCreateListItemBulkOptionsMock();
await createListItemsBulk(options);
const firstRecord: IndexEsListItemSchema = getIndexESListItemMock();
const secondRecord: IndexEsListItemSchema = getIndexESListItemMock(VALUE_2);
[firstRecord.tie_breaker_id, secondRecord.tie_breaker_id] = TIE_BREAKERS;
expect(options.dataClient.callAsCurrentUser).toBeCalledWith('bulk', {
expect(options.callCluster).toBeCalledWith('bulk', {
body: [
{ create: { _index: LIST_ITEM_INDEX } },
firstRecord,
@ -44,6 +44,6 @@ describe('crete_list_item_bulk', () => {
test('It should not call the dataClient when the values are empty', async () => {
const options = getCreateListItemBulkOptionsMock();
options.value = [];
expect(options.dataClient.callAsCurrentUser).not.toBeCalled();
expect(options.callCluster).not.toBeCalled();
});
});

View file

@ -5,9 +5,9 @@
*/
import uuid from 'uuid';
import { APICaller } from 'kibana/server';
import { transformListItemToElasticQuery } from '../utils';
import { DataClient } from '../../types';
import {
CreateEsBulkTypeSchema,
IndexEsListItemSchema,
@ -19,7 +19,7 @@ export interface CreateListItemsBulkOptions {
listId: string;
type: Type;
value: string[];
dataClient: DataClient;
callCluster: APICaller;
listItemIndex: string;
user: string;
meta: MetaOrUndefined;
@ -31,7 +31,7 @@ export const createListItemsBulk = async ({
listId,
type,
value,
dataClient,
callCluster,
listItemIndex,
user,
meta,
@ -63,7 +63,7 @@ export const createListItemsBulk = async ({
[]
);
await dataClient.callAsCurrentUser('bulk', {
await callCluster('bulk', {
body,
index: listItemIndex,
});

View file

@ -4,8 +4,12 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { LIST_ITEM_ID, LIST_ITEM_INDEX, getListItemResponseMock } from '../mocks';
import { getDeleteListItemOptionsMock } from '../mocks/get_delete_list_item_options_mock';
import {
LIST_ITEM_ID,
LIST_ITEM_INDEX,
getDeleteListItemOptionsMock,
getListItemResponseMock,
} from '../mocks';
import { getListItem } from './get_list_item';
import { deleteListItem } from './delete_list_item';
@ -37,6 +41,7 @@ describe('delete_list_item', () => {
const deletedListItem = await deleteListItem(options);
expect(deletedListItem).toEqual(listItem);
});
test('Delete calls "delete" if a list item is returned from "getListItem"', async () => {
const listItem = getListItemResponseMock();
((getListItem as unknown) as jest.Mock).mockResolvedValueOnce(listItem);
@ -46,6 +51,6 @@ describe('delete_list_item', () => {
id: LIST_ITEM_ID,
index: LIST_ITEM_INDEX,
};
expect(options.dataClient.callAsCurrentUser).toBeCalledWith('delete', deleteQuery);
expect(options.callCluster).toBeCalledWith('delete', deleteQuery);
});
});

View file

@ -4,27 +4,28 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { APICaller } from 'kibana/server';
import { Id, ListItemSchema } from '../../../common/schemas';
import { DataClient } from '../../types';
import { getListItem } from '.';
export interface DeleteListItemOptions {
id: Id;
dataClient: DataClient;
callCluster: APICaller;
listItemIndex: string;
}
export const deleteListItem = async ({
id,
dataClient,
callCluster,
listItemIndex,
}: DeleteListItemOptions): Promise<ListItemSchema | null> => {
const listItem = await getListItem({ dataClient, id, listItemIndex });
const listItem = await getListItem({ callCluster, id, listItemIndex });
if (listItem == null) {
return null;
} else {
await dataClient.callAsCurrentUser('delete', {
await callCluster('delete', {
id,
index: listItemIndex,
});

View file

@ -52,6 +52,6 @@ describe('delete_list_item_by_value', () => {
},
index: '.items',
};
expect(options.dataClient.callAsCurrentUser).toBeCalledWith('deleteByQuery', deleteByQuery);
expect(options.callCluster).toBeCalledWith('deleteByQuery', deleteByQuery);
});
});

View file

@ -4,9 +4,10 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { APICaller } from 'kibana/server';
import { ListItemArraySchema, Type } from '../../../common/schemas';
import { getQueryFilterFromTypeValue } from '../utils';
import { DataClient } from '../../types';
import { getListItemByValues } from './get_list_item_by_values';
@ -14,7 +15,7 @@ export interface DeleteListItemByValueOptions {
listId: string;
type: Type;
value: string;
dataClient: DataClient;
callCluster: APICaller;
listItemIndex: string;
}
@ -22,11 +23,11 @@ export const deleteListItemByValue = async ({
listId,
value,
type,
dataClient,
callCluster,
listItemIndex,
}: DeleteListItemByValueOptions): Promise<ListItemArraySchema> => {
const listItems = await getListItemByValues({
dataClient,
callCluster,
listId,
listItemIndex,
type,
@ -38,7 +39,7 @@ export const deleteListItemByValue = async ({
type,
value: values,
});
await dataClient.callAsCurrentUser('deleteByQuery', {
await callCluster('deleteByQuery', {
body: {
query: {
bool: {

View file

@ -4,8 +4,13 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { LIST_ID, LIST_INDEX, getDataClientMock, getListItemResponseMock } from '../mocks';
import { getSearchListItemMock } from '../mocks/get_search_list_item_mock';
import {
LIST_ID,
LIST_INDEX,
getCallClusterMock,
getListItemResponseMock,
getSearchListItemMock,
} from '../mocks';
import { getListItem } from './get_list_item';
@ -20,8 +25,8 @@ describe('get_list_item', () => {
test('it returns a list item as expected if the list item is found', async () => {
const data = getSearchListItemMock();
const dataClient = getDataClientMock(data);
const list = await getListItem({ dataClient, id: LIST_ID, listItemIndex: LIST_INDEX });
const callCluster = getCallClusterMock(data);
const list = await getListItem({ callCluster, id: LIST_ID, listItemIndex: LIST_INDEX });
const expected = getListItemResponseMock();
expect(list).toEqual(expected);
});
@ -29,8 +34,8 @@ describe('get_list_item', () => {
test('it returns null if the search is empty', async () => {
const data = getSearchListItemMock();
data.hits.hits = [];
const dataClient = getDataClientMock(data);
const list = await getListItem({ dataClient, id: LIST_ID, listItemIndex: LIST_INDEX });
const callCluster = getCallClusterMock(data);
const list = await getListItem({ callCluster, id: LIST_ID, listItemIndex: LIST_INDEX });
expect(list).toEqual(null);
});
});

View file

@ -5,36 +5,33 @@
*/
import { SearchResponse } from 'elasticsearch';
import { APICaller } from 'kibana/server';
import { Id, ListItemSchema, SearchEsListItemSchema } from '../../../common/schemas';
import { DataClient } from '../../types';
import { deriveTypeFromItem, transformElasticToListItem } from '../utils';
interface GetListItemOptions {
id: Id;
dataClient: DataClient;
callCluster: APICaller;
listItemIndex: string;
}
export const getListItem = async ({
id,
dataClient,
callCluster,
listItemIndex,
}: GetListItemOptions): Promise<ListItemSchema | null> => {
const listItemES: SearchResponse<SearchEsListItemSchema> = await dataClient.callAsCurrentUser(
'search',
{
body: {
query: {
term: {
_id: id,
},
const listItemES: SearchResponse<SearchEsListItemSchema> = await callCluster('search', {
body: {
query: {
term: {
_id: id,
},
},
ignoreUnavailable: true,
index: listItemIndex,
}
);
},
ignoreUnavailable: true,
index: listItemIndex,
});
if (listItemES.hits.hits.length) {
const type = deriveTypeFromItem({ item: listItemES.hits.hits[0]._source });

View file

@ -4,14 +4,15 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { APICaller } from 'kibana/server';
import { ListItemArraySchema, Type } from '../../../common/schemas';
import { DataClient } from '../../types';
import { getListItemByValues } from '.';
export interface GetListItemByValueOptions {
listId: string;
dataClient: DataClient;
callCluster: APICaller;
listItemIndex: string;
type: Type;
value: string;
@ -19,13 +20,13 @@ export interface GetListItemByValueOptions {
export const getListItemByValue = async ({
listId,
dataClient,
callCluster,
listItemIndex,
type,
value,
}: GetListItemByValueOptions): Promise<ListItemArraySchema> =>
getListItemByValues({
dataClient,
callCluster,
listId,
listItemIndex,
type,

View file

@ -4,8 +4,15 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { LIST_ID, LIST_ITEM_INDEX, TYPE, VALUE, VALUE_2, getDataClientMock } from '../mocks';
import { getSearchListItemMock } from '../mocks/get_search_list_item_mock';
import {
LIST_ID,
LIST_ITEM_INDEX,
TYPE,
VALUE,
VALUE_2,
getCallClusterMock,
getSearchListItemMock,
} from '../mocks';
import { getListItemByValues } from './get_list_item_by_values';
@ -21,27 +28,29 @@ describe('get_list_item_by_values', () => {
test('Returns a an empty array if the ES query is also empty', async () => {
const data = getSearchListItemMock();
data.hits.hits = [];
const dataClient = getDataClientMock(data);
const callCluster = getCallClusterMock(data);
const listItem = await getListItemByValues({
dataClient,
callCluster,
listId: LIST_ID,
listItemIndex: LIST_ITEM_INDEX,
type: TYPE,
value: [VALUE, VALUE_2],
});
expect(listItem).toEqual([]);
});
test('Returns transformed list item if the data exists within ES', async () => {
const data = getSearchListItemMock();
const dataClient = getDataClientMock(data);
const callCluster = getCallClusterMock(data);
const listItem = await getListItemByValues({
dataClient,
callCluster,
listId: LIST_ID,
listItemIndex: LIST_ITEM_INDEX,
type: TYPE,
value: [VALUE, VALUE_2],
});
expect(listItem).toEqual([
{
created_at: '2020-04-20T15:25:31.830Z',

View file

@ -5,14 +5,14 @@
*/
import { SearchResponse } from 'elasticsearch';
import { APICaller } from 'kibana/server';
import { ListItemArraySchema, SearchEsListItemSchema, Type } from '../../../common/schemas';
import { DataClient } from '../../types';
import { getQueryFilterFromTypeValue, transformElasticToListItem } from '../utils';
export interface GetListItemByValuesOptions {
listId: string;
dataClient: DataClient;
callCluster: APICaller;
listItemIndex: string;
type: Type;
value: string[];
@ -20,25 +20,22 @@ export interface GetListItemByValuesOptions {
export const getListItemByValues = async ({
listId,
dataClient,
callCluster,
listItemIndex,
type,
value,
}: GetListItemByValuesOptions): Promise<ListItemArraySchema> => {
const response: SearchResponse<SearchEsListItemSchema> = await dataClient.callAsCurrentUser(
'search',
{
body: {
query: {
bool: {
filter: getQueryFilterFromTypeValue({ listId, type, value }),
},
const response: SearchResponse<SearchEsListItemSchema> = await callCluster('search', {
body: {
query: {
bool: {
filter: getQueryFilterFromTypeValue({ listId, type, value }),
},
},
ignoreUnavailable: true,
index: listItemIndex,
size: value.length, // This has a limit on the number which is 10k
}
);
},
ignoreUnavailable: true,
index: listItemIndex,
size: value.length, // This has a limit on the number which is 10k
});
return transformElasticToListItem({ response, type });
};

View file

@ -4,17 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { httpServerMock } from 'src/core/server/mocks';
import { KibanaRequest } from 'src/core/server';
import { getSpace } from '../utils';
import { getListItemIndex } from './get_list_item_index';
jest.mock('../utils', () => ({
getSpace: jest.fn(),
}));
describe('get_list_item_index', () => {
beforeEach(() => {
jest.clearAllMocks();
@ -25,13 +16,9 @@ describe('get_list_item_index', () => {
});
test('Returns the list item index when there is a space', async () => {
((getSpace as unknown) as jest.Mock).mockReturnValueOnce('test-space');
const rawRequest = httpServerMock.createRawRequest({});
const request = KibanaRequest.from(rawRequest);
const listIndex = getListItemIndex({
listsItemsIndexName: 'lists-items-index',
request,
spaces: undefined,
spaceId: 'test-space',
});
expect(listIndex).toEqual('lists-items-index-test-space');
});

View file

@ -4,19 +4,12 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { KibanaRequest } from 'kibana/server';
import { SpacesServiceSetup } from '../../../../spaces/server';
import { getSpace } from '../utils';
interface GetListItemIndexOptions {
spaces: SpacesServiceSetup | undefined | null;
request: KibanaRequest;
export interface GetListItemIndexOptions {
spaceId: string;
listsItemsIndexName: string;
}
export const getListItemIndex = ({
spaces,
request,
spaceId,
listsItemsIndexName,
}: GetListItemIndexOptions): string => `${listsItemsIndexName}-${getSpace({ request, spaces })}`;
}: GetListItemIndexOptions): string => `${listsItemsIndexName}-${spaceId}`;

View file

@ -5,6 +5,7 @@
*/
import { CreateDocumentResponse } from 'elasticsearch';
import { APICaller } from 'kibana/server';
import {
Id,
@ -13,14 +14,13 @@ import {
UpdateEsListItemSchema,
} from '../../../common/schemas';
import { transformListItemToElasticQuery } from '../utils';
import { DataClient } from '../../types';
import { getListItem } from './get_list_item';
export interface UpdateListItemOptions {
id: Id;
value: string | null | undefined;
dataClient: DataClient;
callCluster: APICaller;
listItemIndex: string;
user: string;
meta: MetaOrUndefined;
@ -30,14 +30,14 @@ export interface UpdateListItemOptions {
export const updateListItem = async ({
id,
value,
dataClient,
callCluster,
listItemIndex,
user,
meta,
dateNow,
}: UpdateListItemOptions): Promise<ListItemSchema | null> => {
const updatedAt = dateNow ?? new Date().toISOString();
const listItem = await getListItem({ dataClient, id, listItemIndex });
const listItem = await getListItem({ callCluster, id, listItemIndex });
if (listItem == null) {
return null;
} else {
@ -48,7 +48,7 @@ export const updateListItem = async ({
...transformListItemToElasticQuery({ type: listItem.type, value: value ?? listItem.value }),
};
const response: CreateDocumentResponse = await dataClient.callAsCurrentUser('update', {
const response: CreateDocumentResponse = await callCluster('update', {
body: {
doc,
},

View file

@ -6,8 +6,9 @@
import { Readable } from 'stream';
import { APICaller } from 'kibana/server';
import { MetaOrUndefined, Type } from '../../../common/schemas';
import { DataClient } from '../../types';
import { BufferLines } from './buffer_lines';
import { getListItemByValues } from './get_list_item_by_values';
@ -16,7 +17,7 @@ import { createListItemsBulk } from './create_list_items_bulk';
export interface ImportListItemsToStreamOptions {
listId: string;
stream: Readable;
dataClient: DataClient;
callCluster: APICaller;
listItemIndex: string;
type: Type;
user: string;
@ -26,7 +27,7 @@ export interface ImportListItemsToStreamOptions {
export const importListItemsToStream = ({
listId,
stream,
dataClient,
callCluster,
listItemIndex,
type,
user,
@ -37,7 +38,7 @@ export const importListItemsToStream = ({
readBuffer.on('lines', async (lines: string[]) => {
await writeBufferToItems({
buffer: lines,
dataClient,
callCluster,
listId,
listItemIndex,
meta,
@ -54,7 +55,7 @@ export const importListItemsToStream = ({
export interface WriteBufferToItemsOptions {
listId: string;
dataClient: DataClient;
callCluster: APICaller;
listItemIndex: string;
buffer: string[];
type: Type;
@ -69,7 +70,7 @@ export interface LinesResult {
export const writeBufferToItems = async ({
listId,
dataClient,
callCluster,
listItemIndex,
buffer,
type,
@ -77,7 +78,7 @@ export const writeBufferToItems = async ({
meta,
}: WriteBufferToItemsOptions): Promise<LinesResult> => {
const items = await getListItemByValues({
dataClient,
callCluster,
listId,
listItemIndex,
type,
@ -89,7 +90,7 @@ export const writeBufferToItems = async ({
const linesProcessed = duplicatesRemoved.length;
const duplicatesFound = buffer.length - duplicatesRemoved.length;
await createListItemsBulk({
dataClient,
callCluster,
listId,
listItemIndex,
meta,

View file

@ -7,13 +7,13 @@
import {
LIST_ID,
LIST_ITEM_INDEX,
getDataClientMock,
getCallClusterMock,
getExportListItemsToStreamOptionsMock,
getResponseOptionsMock,
getSearchListItemMock,
getWriteNextResponseOptions,
getWriteResponseHitsToStreamOptionsMock,
} from '../mocks';
import { getSearchListItemMock } from '../mocks/get_search_list_item_mock';
import {
exportListItemsToStream,
@ -37,7 +37,7 @@ describe('write_list_items_to_stream', () => {
const options = getExportListItemsToStreamOptionsMock();
const firstResponse = getSearchListItemMock();
firstResponse.hits.hits = [];
options.dataClient = getDataClientMock(firstResponse);
options.callCluster = getCallClusterMock(firstResponse);
exportListItemsToStream(options);
let chunks: string[] = [];
@ -71,7 +71,7 @@ describe('write_list_items_to_stream', () => {
const firstResponse = getSearchListItemMock();
const secondResponse = getSearchListItemMock();
firstResponse.hits.hits = [...firstResponse.hits.hits, ...secondResponse.hits.hits];
options.dataClient = getDataClientMock(firstResponse);
options.callCluster = getCallClusterMock(firstResponse);
exportListItemsToStream(options);
let chunks: string[] = [];
@ -90,15 +90,14 @@ describe('write_list_items_to_stream', () => {
const firstResponse = getSearchListItemMock();
firstResponse.hits.hits[0].sort = ['some-sort-value'];
const secondResponse = getSearchListItemMock();
secondResponse.hits.hits[0]._source.ip = '255.255.255.255';
const jestCalls = jest.fn().mockResolvedValueOnce(firstResponse);
jestCalls.mockResolvedValueOnce(secondResponse);
const dataClient = getDataClientMock(firstResponse);
dataClient.callAsCurrentUser = jestCalls;
options.dataClient = dataClient;
options.callCluster = jest
.fn()
.mockResolvedValueOnce(firstResponse)
.mockResolvedValueOnce(secondResponse);
exportListItemsToStream(options);
@ -125,7 +124,7 @@ describe('write_list_items_to_stream', () => {
const listItem = getSearchListItemMock();
listItem.hits.hits[0].sort = ['sort-value-1'];
const options = getWriteNextResponseOptions();
options.dataClient = getDataClientMock(listItem);
options.callCluster = getCallClusterMock(listItem);
const searchAfter = await writeNextResponse(options);
expect(searchAfter).toEqual(['sort-value-1']);
});
@ -134,7 +133,7 @@ describe('write_list_items_to_stream', () => {
const listItem = getSearchListItemMock();
listItem.hits.hits = [];
const options = getWriteNextResponseOptions();
options.dataClient = getDataClientMock(listItem);
options.callCluster = getCallClusterMock(listItem);
const searchAfter = await writeNextResponse(options);
expect(searchAfter).toEqual(undefined);
});
@ -187,7 +186,7 @@ describe('write_list_items_to_stream', () => {
index: LIST_ITEM_INDEX,
size: 100,
};
expect(options.dataClient.callAsCurrentUser).toBeCalledWith('search', expected);
expect(options.callCluster).toBeCalledWith('search', expected);
});
test('It returns a simple response with expected values and size changed', async () => {
@ -205,7 +204,7 @@ describe('write_list_items_to_stream', () => {
index: LIST_ITEM_INDEX,
size: 33,
};
expect(options.dataClient.callAsCurrentUser).toBeCalledWith('search', expected);
expect(options.callCluster).toBeCalledWith('search', expected);
});
});

View file

@ -7,9 +7,9 @@
import { PassThrough } from 'stream';
import { SearchResponse } from 'elasticsearch';
import { APICaller } from 'kibana/server';
import { SearchEsListItemSchema } from '../../../common/schemas';
import { DataClient } from '../../types';
import { ErrorWithStatusCode } from '../../error_with_status_code';
/**
@ -20,7 +20,7 @@ export const SIZE = 100;
export interface ExportListItemsToStreamOptions {
listId: string;
dataClient: DataClient;
callCluster: APICaller;
listItemIndex: string;
stream: PassThrough;
stringToAppend: string | null | undefined;
@ -28,7 +28,7 @@ export interface ExportListItemsToStreamOptions {
export const exportListItemsToStream = ({
listId,
dataClient,
callCluster,
stream,
listItemIndex,
stringToAppend,
@ -37,7 +37,7 @@ export const exportListItemsToStream = ({
// and prevent the async await from bubbling up to the caller
setTimeout(async () => {
let searchAfter = await writeNextResponse({
dataClient,
callCluster,
listId,
listItemIndex,
searchAfter: undefined,
@ -46,7 +46,7 @@ export const exportListItemsToStream = ({
});
while (searchAfter != null) {
searchAfter = await writeNextResponse({
dataClient,
callCluster,
listId,
listItemIndex,
searchAfter,
@ -60,7 +60,7 @@ export const exportListItemsToStream = ({
export interface WriteNextResponseOptions {
listId: string;
dataClient: DataClient;
callCluster: APICaller;
listItemIndex: string;
stream: PassThrough;
searchAfter: string[] | undefined;
@ -69,14 +69,14 @@ export interface WriteNextResponseOptions {
export const writeNextResponse = async ({
listId,
dataClient,
callCluster,
stream,
listItemIndex,
searchAfter,
stringToAppend,
}: WriteNextResponseOptions): Promise<string[] | undefined> => {
const response = await getResponse({
dataClient,
callCluster,
listId,
listItemIndex,
searchAfter,
@ -100,7 +100,7 @@ export const getSearchAfterFromResponse = <T>({
: undefined;
export interface GetResponseOptions {
dataClient: DataClient;
callCluster: APICaller;
listId: string;
searchAfter: undefined | string[];
listItemIndex: string;
@ -108,13 +108,13 @@ export interface GetResponseOptions {
}
export const getResponse = async ({
dataClient,
callCluster,
searchAfter,
listId,
listItemIndex,
size = SIZE,
}: GetResponseOptions): Promise<SearchResponse<SearchEsListItemSchema>> => {
return dataClient.callAsCurrentUser('search', {
return callCluster('search', {
body: {
query: {
term: {

View file

@ -4,10 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { KibanaRequest, ScopedClusterClient } from 'src/core/server';
import { APICaller } from 'kibana/server';
import { SecurityPluginSetup } from '../../../../security/server';
import { SpacesServiceSetup } from '../../../../spaces/server';
import { ListItemArraySchema, ListItemSchema, ListSchema } from '../../../common/schemas';
import { ConfigType } from '../../config';
import {
@ -31,7 +29,6 @@ import {
importListItemsToStream,
updateListItem,
} from '../../services/items';
import { getUser } from '../../services/utils';
import {
createBootstrapIndex,
deleteAllIndex,
@ -64,47 +61,39 @@ import {
UpdateListOptions,
} from './client_types';
// TODO: Consider an interface and a factory
export class ListClient {
private readonly spaces: SpacesServiceSetup | undefined | null;
private readonly spaceId: string;
private readonly user: string;
private readonly config: ConfigType;
private readonly dataClient: Pick<
ScopedClusterClient,
'callAsCurrentUser' | 'callAsInternalUser'
>;
private readonly request: KibanaRequest;
private readonly security: SecurityPluginSetup;
private readonly callCluster: APICaller;
constructor({ request, spaces, config, dataClient, security }: ConstructorOptions) {
this.request = request;
this.spaces = spaces;
constructor({ spaceId, user, config, callCluster }: ConstructorOptions) {
this.spaceId = spaceId;
this.user = user;
this.config = config;
this.dataClient = dataClient;
this.security = security;
this.callCluster = callCluster;
}
public getListIndex = (): string => {
const {
spaces,
request,
spaceId,
config: { listIndex: listsIndexName },
} = this;
return getListIndex({ listsIndexName, request, spaces });
return getListIndex({ listsIndexName, spaceId });
};
public getListItemIndex = (): string => {
const {
spaces,
request,
spaceId,
config: { listItemIndex: listsItemsIndexName },
} = this;
return getListItemIndex({ listsItemsIndexName, request, spaces });
return getListItemIndex({ listsItemsIndexName, spaceId });
};
public getList = async ({ id }: GetListOptions): Promise<ListSchema | null> => {
const { dataClient } = this;
const { callCluster } = this;
const listIndex = this.getListIndex();
return getList({ dataClient, id, listIndex });
return getList({ callCluster, id, listIndex });
};
public createList = async ({
@ -114,10 +103,9 @@ export class ListClient {
type,
meta,
}: CreateListOptions): Promise<ListSchema> => {
const { dataClient, security, request } = this;
const { callCluster, user } = this;
const listIndex = this.getListIndex();
const user = getUser({ request, security });
return createList({ dataClient, description, id, listIndex, meta, name, type, user });
return createList({ callCluster, description, id, listIndex, meta, name, type, user });
};
public createListIfItDoesNotExist = async ({
@ -136,67 +124,51 @@ export class ListClient {
};
public getListIndexExists = async (): Promise<boolean> => {
const {
dataClient: { callAsCurrentUser },
} = this;
const { callCluster } = this;
const listIndex = this.getListIndex();
return getIndexExists(callAsCurrentUser, listIndex);
return getIndexExists(callCluster, listIndex);
};
public getListItemIndexExists = async (): Promise<boolean> => {
const {
dataClient: { callAsCurrentUser },
} = this;
const { callCluster } = this;
const listItemIndex = this.getListItemIndex();
return getIndexExists(callAsCurrentUser, listItemIndex);
return getIndexExists(callCluster, listItemIndex);
};
public createListBootStrapIndex = async (): Promise<unknown> => {
const {
dataClient: { callAsCurrentUser },
} = this;
const { callCluster } = this;
const listIndex = this.getListIndex();
return createBootstrapIndex(callAsCurrentUser, listIndex);
return createBootstrapIndex(callCluster, listIndex);
};
public createListItemBootStrapIndex = async (): Promise<unknown> => {
const {
dataClient: { callAsCurrentUser },
} = this;
const { callCluster } = this;
const listItemIndex = this.getListItemIndex();
return createBootstrapIndex(callAsCurrentUser, listItemIndex);
return createBootstrapIndex(callCluster, listItemIndex);
};
public getListPolicyExists = async (): Promise<boolean> => {
const {
dataClient: { callAsCurrentUser },
} = this;
const { callCluster } = this;
const listIndex = this.getListIndex();
return getPolicyExists(callAsCurrentUser, listIndex);
return getPolicyExists(callCluster, listIndex);
};
public getListItemPolicyExists = async (): Promise<boolean> => {
const {
dataClient: { callAsCurrentUser },
} = this;
const { callCluster } = this;
const listsItemIndex = this.getListItemIndex();
return getPolicyExists(callAsCurrentUser, listsItemIndex);
return getPolicyExists(callCluster, listsItemIndex);
};
public getListTemplateExists = async (): Promise<boolean> => {
const {
dataClient: { callAsCurrentUser },
} = this;
const { callCluster } = this;
const listIndex = this.getListIndex();
return getTemplateExists(callAsCurrentUser, listIndex);
return getTemplateExists(callCluster, listIndex);
};
public getListItemTemplateExists = async (): Promise<boolean> => {
const {
dataClient: { callAsCurrentUser },
} = this;
const { callCluster } = this;
const listItemIndex = this.getListItemIndex();
return getTemplateExists(callAsCurrentUser, listItemIndex);
return getTemplateExists(callCluster, listItemIndex);
};
public getListTemplate = (): Record<string, unknown> => {
@ -210,91 +182,71 @@ export class ListClient {
};
public setListTemplate = async (): Promise<unknown> => {
const {
dataClient: { callAsCurrentUser },
} = this;
const { callCluster } = this;
const template = this.getListTemplate();
const listIndex = this.getListIndex();
return setTemplate(callAsCurrentUser, listIndex, template);
return setTemplate(callCluster, listIndex, template);
};
public setListItemTemplate = async (): Promise<unknown> => {
const {
dataClient: { callAsCurrentUser },
} = this;
const { callCluster } = this;
const template = this.getListItemTemplate();
const listItemIndex = this.getListItemIndex();
return setTemplate(callAsCurrentUser, listItemIndex, template);
return setTemplate(callCluster, listItemIndex, template);
};
public setListPolicy = async (): Promise<unknown> => {
const {
dataClient: { callAsCurrentUser },
} = this;
const { callCluster } = this;
const listIndex = this.getListIndex();
return setPolicy(callAsCurrentUser, listIndex, listPolicy);
return setPolicy(callCluster, listIndex, listPolicy);
};
public setListItemPolicy = async (): Promise<unknown> => {
const {
dataClient: { callAsCurrentUser },
} = this;
const { callCluster } = this;
const listItemIndex = this.getListItemIndex();
return setPolicy(callAsCurrentUser, listItemIndex, listsItemsPolicy);
return setPolicy(callCluster, listItemIndex, listsItemsPolicy);
};
public deleteListIndex = async (): Promise<boolean> => {
const {
dataClient: { callAsCurrentUser },
} = this;
const { callCluster } = this;
const listIndex = this.getListIndex();
return deleteAllIndex(callAsCurrentUser, `${listIndex}-*`);
return deleteAllIndex(callCluster, `${listIndex}-*`);
};
public deleteListItemIndex = async (): Promise<boolean> => {
const {
dataClient: { callAsCurrentUser },
} = this;
const { callCluster } = this;
const listItemIndex = this.getListItemIndex();
return deleteAllIndex(callAsCurrentUser, `${listItemIndex}-*`);
return deleteAllIndex(callCluster, `${listItemIndex}-*`);
};
public deleteListPolicy = async (): Promise<unknown> => {
const {
dataClient: { callAsCurrentUser },
} = this;
const { callCluster } = this;
const listIndex = this.getListIndex();
return deletePolicy(callAsCurrentUser, listIndex);
return deletePolicy(callCluster, listIndex);
};
public deleteListItemPolicy = async (): Promise<unknown> => {
const {
dataClient: { callAsCurrentUser },
} = this;
const { callCluster } = this;
const listItemIndex = this.getListItemIndex();
return deletePolicy(callAsCurrentUser, listItemIndex);
return deletePolicy(callCluster, listItemIndex);
};
public deleteListTemplate = async (): Promise<unknown> => {
const {
dataClient: { callAsCurrentUser },
} = this;
const { callCluster } = this;
const listIndex = this.getListIndex();
return deleteTemplate(callAsCurrentUser, listIndex);
return deleteTemplate(callCluster, listIndex);
};
public deleteListItemTemplate = async (): Promise<unknown> => {
const {
dataClient: { callAsCurrentUser },
} = this;
const { callCluster } = this;
const listItemIndex = this.getListItemIndex();
return deleteTemplate(callAsCurrentUser, listItemIndex);
return deleteTemplate(callCluster, listItemIndex);
};
public deleteListItem = async ({ id }: DeleteListItemOptions): Promise<ListItemSchema | null> => {
const { dataClient } = this;
const { callCluster } = this;
const listItemIndex = this.getListItemIndex();
return deleteListItem({ dataClient, id, listItemIndex });
return deleteListItem({ callCluster, id, listItemIndex });
};
public deleteListItemByValue = async ({
@ -302,10 +254,10 @@ export class ListClient {
value,
type,
}: DeleteListItemByValueOptions): Promise<ListItemArraySchema> => {
const { dataClient } = this;
const { callCluster } = this;
const listItemIndex = this.getListItemIndex();
return deleteListItemByValue({
dataClient,
callCluster,
listId,
listItemIndex,
type,
@ -314,11 +266,11 @@ export class ListClient {
};
public deleteList = async ({ id }: DeleteListOptions): Promise<ListSchema | null> => {
const { dataClient } = this;
const { callCluster } = this;
const listIndex = this.getListIndex();
const listItemIndex = this.getListItemIndex();
return deleteList({
dataClient,
callCluster,
id,
listIndex,
listItemIndex,
@ -330,10 +282,10 @@ export class ListClient {
listId,
stream,
}: ExportListItemsToStreamOptions): void => {
const { dataClient } = this;
const { callCluster } = this;
const listItemIndex = this.getListItemIndex();
exportListItemsToStream({
dataClient,
callCluster,
listId,
listItemIndex,
stream,
@ -347,11 +299,10 @@ export class ListClient {
stream,
meta,
}: ImportListItemsToStreamOptions): Promise<void> => {
const { dataClient, security, request } = this;
const { callCluster, user } = this;
const listItemIndex = this.getListItemIndex();
const user = getUser({ request, security });
return importListItemsToStream({
dataClient,
callCluster,
listId,
listItemIndex,
meta,
@ -366,10 +317,10 @@ export class ListClient {
value,
type,
}: GetListItemByValueOptions): Promise<ListItemArraySchema> => {
const { dataClient } = this;
const { callCluster } = this;
const listItemIndex = this.getListItemIndex();
return getListItemByValue({
dataClient,
callCluster,
listId,
listItemIndex,
type,
@ -384,11 +335,10 @@ export class ListClient {
type,
meta,
}: CreateListItemOptions): Promise<ListItemSchema> => {
const { dataClient, security, request } = this;
const { callCluster, user } = this;
const listItemIndex = this.getListItemIndex();
const user = getUser({ request, security });
return createListItem({
dataClient,
callCluster,
id,
listId,
listItemIndex,
@ -404,11 +354,10 @@ export class ListClient {
value,
meta,
}: UpdateListItemOptions): Promise<ListItemSchema | null> => {
const { dataClient, security, request } = this;
const user = getUser({ request, security });
const { callCluster, user } = this;
const listItemIndex = this.getListItemIndex();
return updateListItem({
dataClient,
callCluster,
id,
listItemIndex,
meta,
@ -423,11 +372,10 @@ export class ListClient {
description,
meta,
}: UpdateListOptions): Promise<ListSchema | null> => {
const { dataClient, security, request } = this;
const user = getUser({ request, security });
const { callCluster, user } = this;
const listIndex = this.getListIndex();
return updateList({
dataClient,
callCluster,
description,
id,
listIndex,
@ -438,10 +386,10 @@ export class ListClient {
};
public getListItem = async ({ id }: GetListItemOptions): Promise<ListItemSchema | null> => {
const { dataClient } = this;
const { callCluster } = this;
const listItemIndex = this.getListItemIndex();
return getListItem({
dataClient,
callCluster,
id,
listItemIndex,
});
@ -452,10 +400,10 @@ export class ListClient {
listId,
value,
}: GetListItemsByValueOptions): Promise<ListItemArraySchema> => {
const { dataClient } = this;
const { callCluster } = this;
const listItemIndex = this.getListItemIndex();
return getListItemByValues({
dataClient,
callCluster,
listId,
listItemIndex,
type,

View file

@ -6,10 +6,9 @@
import { PassThrough, Readable } from 'stream';
import { KibanaRequest } from 'kibana/server';
import { APICaller, KibanaRequest } from 'kibana/server';
import { SecurityPluginSetup } from '../../../../security/server';
import { SpacesServiceSetup } from '../../../../spaces/server';
import {
Description,
DescriptionOrUndefined,
@ -21,14 +20,14 @@ import {
Type,
} from '../../../common/schemas';
import { ConfigType } from '../../config';
import { DataClient } from '../../types';
export interface ConstructorOptions {
callCluster: APICaller;
config: ConfigType;
dataClient: DataClient;
request: KibanaRequest;
spaces: SpacesServiceSetup | undefined | null;
security: SecurityPluginSetup;
spaceId: string;
user: string;
security: SecurityPluginSetup | undefined | null;
}
export interface GetListOptions {

View file

@ -31,7 +31,7 @@ describe('crete_list', () => {
expect(list).toEqual(expected);
});
test('It calls "callAsCurrentUser" with body, index, and listIndex', async () => {
test('It calls "callCluster" with body, index, and listIndex', async () => {
const options = getCreateListOptionsMock();
await createList(options);
const body = getIndexESListMock();
@ -40,7 +40,7 @@ describe('crete_list', () => {
id: LIST_ID,
index: LIST_INDEX,
};
expect(options.dataClient.callAsCurrentUser).toBeCalledWith('index', expected);
expect(options.callCluster).toBeCalledWith('index', expected);
});
test('It returns an auto-generated id if id is sent in undefined', async () => {

View file

@ -6,8 +6,8 @@
import uuid from 'uuid';
import { CreateDocumentResponse } from 'elasticsearch';
import { APICaller } from 'kibana/server';
import { DataClient } from '../../types';
import {
Description,
IdOrUndefined,
@ -23,7 +23,7 @@ export interface CreateListOptions {
type: Type;
name: Name;
description: Description;
dataClient: DataClient;
callCluster: APICaller;
listIndex: string;
user: string;
meta: MetaOrUndefined;
@ -36,7 +36,7 @@ export const createList = async ({
name,
type,
description,
dataClient,
callCluster,
listIndex,
user,
meta,
@ -55,7 +55,7 @@ export const createList = async ({
updated_at: createdAt,
updated_by: user,
};
const response: CreateDocumentResponse = await dataClient.callAsCurrentUser('index', {
const response: CreateDocumentResponse = await callCluster('index', {
body,
id,
index: listIndex,

View file

@ -52,7 +52,7 @@ describe('delete_list', () => {
body: { query: { term: { list_id: LIST_ID } } },
index: LIST_ITEM_INDEX,
};
expect(options.dataClient.callAsCurrentUser).toBeCalledWith('deleteByQuery', deleteByQuery);
expect(options.callCluster).toBeCalledWith('deleteByQuery', deleteByQuery);
});
test('Delete calls "delete" second if a list is returned from getList', async () => {
@ -64,13 +64,13 @@ describe('delete_list', () => {
id: LIST_ID,
index: LIST_INDEX,
};
expect(options.dataClient.callAsCurrentUser).toHaveBeenNthCalledWith(2, 'delete', deleteQuery);
expect(options.callCluster).toHaveBeenNthCalledWith(2, 'delete', deleteQuery);
});
test('Delete does not call data client if the list returns null', async () => {
((getList as unknown) as jest.Mock).mockResolvedValueOnce(null);
const options = getDeleteListOptionsMock();
await deleteList(options);
expect(options.dataClient.callAsCurrentUser).not.toHaveBeenCalled();
expect(options.callCluster).not.toHaveBeenCalled();
});
});

View file

@ -4,29 +4,30 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { APICaller } from 'kibana/server';
import { Id, ListSchema } from '../../../common/schemas';
import { DataClient } from '../../types';
import { getList } from './get_list';
export interface DeleteListOptions {
id: Id;
dataClient: DataClient;
callCluster: APICaller;
listIndex: string;
listItemIndex: string;
}
export const deleteList = async ({
id,
dataClient,
callCluster,
listIndex,
listItemIndex,
}: DeleteListOptions): Promise<ListSchema | null> => {
const list = await getList({ dataClient, id, listIndex });
const list = await getList({ callCluster, id, listIndex });
if (list == null) {
return null;
} else {
await dataClient.callAsCurrentUser('deleteByQuery', {
await callCluster('deleteByQuery', {
body: {
query: {
term: {
@ -37,7 +38,7 @@ export const deleteList = async ({
index: listItemIndex,
});
await dataClient.callAsCurrentUser('delete', {
await callCluster('delete', {
id,
index: listIndex,
});

View file

@ -7,7 +7,7 @@
import {
LIST_ID,
LIST_INDEX,
getDataClientMock,
getCallClusterMock,
getListResponseMock,
getSearchListMock,
} from '../mocks';
@ -25,8 +25,8 @@ describe('get_list', () => {
test('it returns a list as expected if the list is found', async () => {
const data = getSearchListMock();
const dataClient = getDataClientMock(data);
const list = await getList({ dataClient, id: LIST_ID, listIndex: LIST_INDEX });
const callCluster = getCallClusterMock(data);
const list = await getList({ callCluster, id: LIST_ID, listIndex: LIST_INDEX });
const expected = getListResponseMock();
expect(list).toEqual(expected);
});
@ -34,8 +34,8 @@ describe('get_list', () => {
test('it returns null if the search is empty', async () => {
const data = getSearchListMock();
data.hits.hits = [];
const dataClient = getDataClientMock(data);
const list = await getList({ dataClient, id: LIST_ID, listIndex: LIST_INDEX });
const callCluster = getCallClusterMock(data);
const list = await getList({ callCluster, id: LIST_ID, listIndex: LIST_INDEX });
expect(list).toEqual(null);
});
});

View file

@ -5,22 +5,22 @@
*/
import { SearchResponse } from 'elasticsearch';
import { APICaller } from 'kibana/server';
import { Id, ListSchema, SearchEsListSchema } from '../../../common/schemas';
import { DataClient } from '../../types';
interface GetListOptions {
id: Id;
dataClient: DataClient;
callCluster: APICaller;
listIndex: string;
}
export const getList = async ({
id,
dataClient,
callCluster,
listIndex,
}: GetListOptions): Promise<ListSchema | null> => {
const result: SearchResponse<SearchEsListSchema> = await dataClient.callAsCurrentUser('search', {
const result: SearchResponse<SearchEsListSchema> = await callCluster('search', {
body: {
query: {
term: {

View file

@ -4,17 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { httpServerMock } from 'src/core/server/mocks';
import { KibanaRequest } from 'src/core/server';
import { getSpace } from '../utils';
import { getListIndex } from './get_list_index';
jest.mock('../utils', () => ({
getSpace: jest.fn(),
}));
describe('get_list_index', () => {
beforeEach(() => {
jest.clearAllMocks();
@ -25,13 +16,9 @@ describe('get_list_index', () => {
});
test('Returns the list index when there is a space', async () => {
((getSpace as unknown) as jest.Mock).mockReturnValueOnce('test-space');
const rawRequest = httpServerMock.createRawRequest({});
const request = KibanaRequest.from(rawRequest);
const listIndex = getListIndex({
listsIndexName: 'lists-index',
request,
spaces: undefined,
spaceId: 'test-space',
});
expect(listIndex).toEqual('lists-index-test-space');
});

View file

@ -4,16 +4,10 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { KibanaRequest } from 'kibana/server';
import { SpacesServiceSetup } from '../../../../spaces/server';
import { getSpace } from '../utils';
interface GetListIndexOptions {
spaces: SpacesServiceSetup | undefined | null;
request: KibanaRequest;
spaceId: string;
listsIndexName: string;
}
export const getListIndex = ({ spaces, request, listsIndexName }: GetListIndexOptions): string =>
`${listsIndexName}-${getSpace({ request, spaces })}`;
export const getListIndex = ({ spaceId, listsIndexName }: GetListIndexOptions): string =>
`${listsIndexName}-${spaceId}`;

View file

@ -5,6 +5,7 @@
*/
import { CreateDocumentResponse } from 'elasticsearch';
import { APICaller } from 'kibana/server';
import {
DescriptionOrUndefined,
@ -14,13 +15,12 @@ import {
NameOrUndefined,
UpdateEsListSchema,
} from '../../../common/schemas';
import { DataClient } from '../../types';
import { getList } from '.';
export interface UpdateListOptions {
id: Id;
dataClient: DataClient;
callCluster: APICaller;
listIndex: string;
user: string;
name: NameOrUndefined;
@ -33,14 +33,14 @@ export const updateList = async ({
id,
name,
description,
dataClient,
callCluster,
listIndex,
user,
meta,
dateNow,
}: UpdateListOptions): Promise<ListSchema | null> => {
const updatedAt = dateNow ?? new Date().toISOString();
const list = await getList({ dataClient, id, listIndex });
const list = await getList({ callCluster, id, listIndex });
if (list == null) {
return null;
} else {
@ -51,7 +51,7 @@ export const updateList = async ({
updated_at: updatedAt,
updated_by: user,
};
const response: CreateDocumentResponse = await dataClient.callAsCurrentUser('update', {
const response: CreateDocumentResponse = await callCluster('update', {
body: { doc },
id,
index: listIndex,

View file

@ -5,15 +5,11 @@
*/
import { CreateDocumentResponse } from 'elasticsearch';
import { APICaller } from 'kibana/server';
import { LIST_INDEX } from './lists_services_mock_constants';
import { getShardMock } from './get_shard_mock';
interface DataClientReturn {
callAsCurrentUser: () => Promise<unknown>;
callAsInternalUser: () => Promise<never>;
}
export const getEmptyCreateDocumentResponseMock = (): CreateDocumentResponse => ({
_id: 'elastic-id-123',
_index: LIST_INDEX,
@ -24,11 +20,6 @@ export const getEmptyCreateDocumentResponseMock = (): CreateDocumentResponse =>
result: '',
});
export const getDataClientMock = (
callAsCurrentUserData: unknown = getEmptyCreateDocumentResponseMock()
): DataClientReturn => ({
callAsCurrentUser: jest.fn().mockResolvedValue(callAsCurrentUserData),
callAsInternalUser: (): Promise<never> => {
throw new Error('This function should not be calling "callAsInternalUser"');
},
});
export const getCallClusterMock = (
callCluster: unknown = getEmptyCreateDocumentResponseMock()
): APICaller => jest.fn().mockResolvedValue(callCluster);

View file

@ -6,7 +6,7 @@
import { CreateListItemsBulkOptions } from '../items';
import { getDataClientMock } from './get_data_client_mock';
import { getCallClusterMock } from './get_call_cluster_mock';
import {
DATE_NOW,
LIST_ID,
@ -20,7 +20,7 @@ import {
} from './lists_services_mock_constants';
export const getCreateListItemBulkOptionsMock = (): CreateListItemsBulkOptions => ({
dataClient: getDataClientMock(),
callCluster: getCallClusterMock(),
dateNow: DATE_NOW,
listId: LIST_ID,
listItemIndex: LIST_ITEM_INDEX,

View file

@ -6,7 +6,7 @@
import { CreateListItemOptions } from '../items';
import { getDataClientMock } from './get_data_client_mock';
import { getCallClusterMock } from './get_call_cluster_mock';
import {
DATE_NOW,
LIST_ID,
@ -19,7 +19,7 @@ import {
} from './lists_services_mock_constants';
export const getCreateListItemOptionsMock = (): CreateListItemOptions => ({
dataClient: getDataClientMock(),
callCluster: getCallClusterMock(),
dateNow: DATE_NOW,
id: LIST_ITEM_ID,
listId: LIST_ID,

View file

@ -6,7 +6,7 @@
import { CreateListOptions } from '../lists';
import { getDataClientMock } from './get_data_client_mock';
import { getCallClusterMock } from './get_call_cluster_mock';
import {
DATE_NOW,
DESCRIPTION,
@ -20,7 +20,7 @@ import {
} from './lists_services_mock_constants';
export const getCreateListOptionsMock = (): CreateListOptions => ({
dataClient: getDataClientMock(),
callCluster: getCallClusterMock(),
dateNow: DATE_NOW,
description: DESCRIPTION,
id: LIST_ID,

View file

@ -6,11 +6,11 @@
import { DeleteListItemByValueOptions } from '../items';
import { getDataClientMock } from './get_data_client_mock';
import { getCallClusterMock } from './get_call_cluster_mock';
import { LIST_ID, LIST_ITEM_INDEX, TYPE, VALUE } from './lists_services_mock_constants';
export const getDeleteListItemByValueOptionsMock = (): DeleteListItemByValueOptions => ({
dataClient: getDataClientMock(),
callCluster: getCallClusterMock(),
listId: LIST_ID,
listItemIndex: LIST_ITEM_INDEX,
type: TYPE,

View file

@ -6,11 +6,11 @@
import { DeleteListItemOptions } from '../items';
import { getDataClientMock } from './get_data_client_mock';
import { getCallClusterMock } from './get_call_cluster_mock';
import { LIST_ITEM_ID, LIST_ITEM_INDEX } from './lists_services_mock_constants';
export const getDeleteListItemOptionsMock = (): DeleteListItemOptions => ({
dataClient: getDataClientMock(),
callCluster: getCallClusterMock(),
id: LIST_ITEM_ID,
listItemIndex: LIST_ITEM_INDEX,
});

View file

@ -6,11 +6,11 @@
import { DeleteListOptions } from '../lists';
import { getDataClientMock } from './get_data_client_mock';
import { getCallClusterMock } from './get_call_cluster_mock';
import { LIST_ID, LIST_INDEX, LIST_ITEM_INDEX } from './lists_services_mock_constants';
export const getDeleteListOptionsMock = (): DeleteListOptions => ({
dataClient: getDataClientMock(),
callCluster: getCallClusterMock(),
id: LIST_ID,
listIndex: LIST_INDEX,
listItemIndex: LIST_ITEM_INDEX,

View file

@ -5,12 +5,12 @@
*/
import { ImportListItemsToStreamOptions } from '../items';
import { getDataClientMock } from './get_data_client_mock';
import { getCallClusterMock } from './get_call_cluster_mock';
import { LIST_ID, LIST_ITEM_INDEX, META, TYPE, USER } from './lists_services_mock_constants';
import { TestReadable } from './test_readable';
export const getImportListItemsToStreamOptionsMock = (): ImportListItemsToStreamOptions => ({
dataClient: getDataClientMock(),
callCluster: getCallClusterMock(),
listId: LIST_ID,
listItemIndex: LIST_ITEM_INDEX,
meta: META,

View file

@ -6,11 +6,11 @@
import { GetListItemByValueOptions } from '../items';
import { getDataClientMock } from './get_data_client_mock';
import { getCallClusterMock } from './get_call_cluster_mock';
import { LIST_ID, LIST_ITEM_INDEX, TYPE, VALUE } from './lists_services_mock_constants';
export const getListItemByValueOptionsMocks = (): GetListItemByValueOptions => ({
dataClient: getDataClientMock(),
callCluster: getCallClusterMock(),
listId: LIST_ID,
listItemIndex: LIST_ITEM_INDEX,
type: TYPE,

View file

@ -6,11 +6,11 @@
import { GetListItemByValuesOptions } from '../items';
import { getDataClientMock } from './get_data_client_mock';
import { getCallClusterMock } from './get_call_cluster_mock';
import { LIST_ID, LIST_ITEM_INDEX, TYPE, VALUE, VALUE_2 } from './lists_services_mock_constants';
export const getListItemByValuesOptionsMocks = (): GetListItemByValuesOptions => ({
dataClient: getDataClientMock(),
callCluster: getCallClusterMock(),
listId: LIST_ID,
listItemIndex: LIST_ITEM_INDEX,
type: TYPE,

View file

@ -5,7 +5,7 @@
*/
import { UpdateListItemOptions } from '../items';
import { getDataClientMock } from './get_data_client_mock';
import { getCallClusterMock } from './get_call_cluster_mock';
import {
DATE_NOW,
LIST_ITEM_ID,
@ -16,7 +16,7 @@ import {
} from './lists_services_mock_constants';
export const getUpdateListItemOptionsMock = (): UpdateListItemOptions => ({
dataClient: getDataClientMock(),
callCluster: getCallClusterMock(),
dateNow: DATE_NOW,
id: LIST_ITEM_ID,
listItemIndex: LIST_ITEM_INDEX,

View file

@ -5,7 +5,7 @@
*/
import { UpdateListOptions } from '../lists';
import { getDataClientMock } from './get_data_client_mock';
import { getCallClusterMock } from './get_call_cluster_mock';
import {
DATE_NOW,
DESCRIPTION,
@ -17,7 +17,7 @@ import {
} from './lists_services_mock_constants';
export const getUpdateListOptionsMock = (): UpdateListOptions => ({
dataClient: getDataClientMock(),
callCluster: getCallClusterMock(),
dateNow: DATE_NOW,
description: DESCRIPTION,
id: LIST_ID,

View file

@ -5,12 +5,12 @@
*/
import { WriteBufferToItemsOptions } from '../items';
import { getDataClientMock } from './get_data_client_mock';
import { getCallClusterMock } from './get_call_cluster_mock';
import { LIST_ID, LIST_ITEM_INDEX, META, TYPE, USER } from './lists_services_mock_constants';
export const getWriteBufferToItemsOptionsMock = (): WriteBufferToItemsOptions => ({
buffer: [],
dataClient: getDataClientMock(),
callCluster: getCallClusterMock(),
listId: LIST_ID,
listItemIndex: LIST_ITEM_INDEX,
meta: META,

View file

@ -13,12 +13,12 @@ import {
WriteResponseHitsToStreamOptions,
} from '../items';
import { getDataClientMock } from './get_data_client_mock';
import { LIST_ID, LIST_ITEM_INDEX } from './lists_services_mock_constants';
import { getSearchListItemMock } from './get_search_list_item_mock';
import { getCallClusterMock } from './get_call_cluster_mock';
export const getExportListItemsToStreamOptionsMock = (): ExportListItemsToStreamOptions => ({
dataClient: getDataClientMock(getSearchListItemMock()),
callCluster: getCallClusterMock(getSearchListItemMock()),
listId: LIST_ID,
listItemIndex: LIST_ITEM_INDEX,
stream: new Stream.PassThrough(),
@ -26,7 +26,7 @@ export const getExportListItemsToStreamOptionsMock = (): ExportListItemsToStream
});
export const getWriteNextResponseOptions = (): WriteNextResponseOptions => ({
dataClient: getDataClientMock(getSearchListItemMock()),
callCluster: getCallClusterMock(getSearchListItemMock()),
listId: LIST_ID,
listItemIndex: LIST_ITEM_INDEX,
searchAfter: [],
@ -35,7 +35,7 @@ export const getWriteNextResponseOptions = (): WriteNextResponseOptions => ({
});
export const getResponseOptionsMock = (): GetResponseOptions => ({
dataClient: getDataClientMock(),
callCluster: getCallClusterMock(),
listId: LIST_ID,
listItemIndex: LIST_ITEM_INDEX,
searchAfter: [],

View file

@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
export * from './get_data_client_mock';
export * from './get_call_cluster_mock';
export * from './get_delete_list_options_mock';
export * from './get_create_list_options_mock';
export * from './get_list_response_mock';
@ -27,3 +27,5 @@ export * from './get_update_list_item_options_mock';
export * from './get_write_buffer_to_items_options_mock';
export * from './get_import_list_items_to_stream_options_mock';
export * from './get_write_list_items_to_stream_options_mock';
export * from './get_search_list_item_mock';
export * from './test_readable';

View file

@ -10,6 +10,14 @@ import { Type } from '../../../common/schemas';
import { deriveTypeFromItem } from './derive_type_from_es_type';
describe('derive_type_from_es_type', () => {
beforeEach(() => {
jest.clearAllMocks();
});
afterEach(() => {
jest.clearAllMocks();
});
test('it returns the item ip if it exists', () => {
const item = getSearchEsListItemMock();
const derivedType = deriveTypeFromItem({ item });

View file

@ -0,0 +1,92 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { QueryFilterType, getQueryFilterFromTypeValue } from './get_query_filter_from_type_value';
describe('get_query_filter_from_type_value', () => {
beforeEach(() => {
jest.clearAllMocks();
});
afterEach(() => {
jest.clearAllMocks();
});
test('it returns an ip if given an ip', () => {
const queryFilter = getQueryFilterFromTypeValue({
listId: 'list-123',
type: 'ip',
value: ['127.0.0.1'],
});
const expected: QueryFilterType = [
{ term: { list_id: 'list-123' } },
{ terms: { ip: ['127.0.0.1'] } },
];
expect(queryFilter).toEqual(expected);
});
test('it returns two ip if given two ip', () => {
const queryFilter = getQueryFilterFromTypeValue({
listId: 'list-123',
type: 'ip',
value: ['127.0.0.1', '127.0.0.2'],
});
const expected: QueryFilterType = [
{ term: { list_id: 'list-123' } },
{ terms: { ip: ['127.0.0.1', '127.0.0.2'] } },
];
expect(queryFilter).toEqual(expected);
});
test('it returns a keyword if given a keyword', () => {
const queryFilter = getQueryFilterFromTypeValue({
listId: 'list-123',
type: 'keyword',
value: ['host-name-1'],
});
const expected: QueryFilterType = [
{ term: { list_id: 'list-123' } },
{ terms: { keyword: ['host-name-1'] } },
];
expect(queryFilter).toEqual(expected);
});
test('it returns two keywords if given two values', () => {
const queryFilter = getQueryFilterFromTypeValue({
listId: 'list-123',
type: 'keyword',
value: ['host-name-1', 'host-name-2'],
});
const expected: QueryFilterType = [
{ term: { list_id: 'list-123' } },
{ terms: { keyword: ['host-name-1', 'host-name-2'] } },
];
expect(queryFilter).toEqual(expected);
});
test('it returns an empty keyword given an empty value', () => {
const queryFilter = getQueryFilterFromTypeValue({
listId: 'list-123',
type: 'keyword',
value: [],
});
const expected: QueryFilterType = [
{ term: { list_id: 'list-123' } },
{ terms: { keyword: [] } },
];
expect(queryFilter).toEqual(expected);
});
test('it returns an empty ip given an empty value', () => {
const queryFilter = getQueryFilterFromTypeValue({
listId: 'list-123',
type: 'ip',
value: [],
});
const expected: QueryFilterType = [{ term: { list_id: 'list-123' } }, { terms: { ip: [] } }];
expect(queryFilter).toEqual(expected);
});
});

View file

@ -7,6 +7,4 @@
export * from './get_query_filter_from_type_value';
export * from './transform_elastic_to_list_item';
export * from './transform_list_item_to_elastic_query';
export * from './get_user';
export * from './derive_type_from_es_type';
export * from './get_space';

View file

@ -0,0 +1,69 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { ListItemArraySchema } from '../../../common/schemas';
import { getListItemResponseMock, getSearchListItemMock } from '../mocks';
import { transformElasticToListItem } from './transform_elastic_to_list_item';
describe('transform_elastic_to_list_item', () => {
beforeEach(() => {
jest.clearAllMocks();
});
afterEach(() => {
jest.clearAllMocks();
});
test('it transforms an elastic type to a list item type', () => {
const response = getSearchListItemMock();
const queryFilter = transformElasticToListItem({
response,
type: 'ip',
});
const expected: ListItemArraySchema = [getListItemResponseMock()];
expect(queryFilter).toEqual(expected);
});
test('it transforms an elastic keyword type to a list item type', () => {
const response = getSearchListItemMock();
response.hits.hits[0]._source.ip = undefined;
response.hits.hits[0]._source.keyword = 'host-name-example';
const queryFilter = transformElasticToListItem({
response,
type: 'keyword',
});
const listItemResponse = getListItemResponseMock();
listItemResponse.type = 'keyword';
listItemResponse.value = 'host-name-example';
const expected: ListItemArraySchema = [listItemResponse];
expect(queryFilter).toEqual(expected);
});
test('it does a throw if it cannot determine the list item type from "ip"', () => {
const response = getSearchListItemMock();
response.hits.hits[0]._source.ip = undefined;
response.hits.hits[0]._source.keyword = 'host-name-example';
expect(() =>
transformElasticToListItem({
response,
type: 'ip',
})
).toThrow('Was expecting ip to not be null/undefined');
});
test('it does a throw if it cannot determine the list item type from "keyword"', () => {
const response = getSearchListItemMock();
response.hits.hits[0]._source.ip = '127.0.0.1';
response.hits.hits[0]._source.keyword = undefined;
expect(() =>
transformElasticToListItem({
response,
type: 'keyword',
})
).toThrow('Was expecting keyword to not be null/undefined');
});
});

View file

@ -9,13 +9,15 @@ import { SearchResponse } from 'elasticsearch';
import { ListItemArraySchema, SearchEsListItemSchema, Type } from '../../../common/schemas';
import { ErrorWithStatusCode } from '../../error_with_status_code';
export interface TransformElasticToListItemOptions {
response: SearchResponse<SearchEsListItemSchema>;
type: Type;
}
export const transformElasticToListItem = ({
response,
type,
}: {
response: SearchResponse<SearchEsListItemSchema>;
type: Type;
}): ListItemArraySchema => {
}: TransformElasticToListItemOptions): ListItemArraySchema => {
return response.hits.hits.map(hit => {
const {
_id,
@ -64,11 +66,10 @@ export const transformElasticToListItem = ({
};
}
}
// TypeScript is not happy unless I have this line here
return assertUnreachable();
});
};
export const assertUnreachable = (): never => {
const assertUnreachable = (): never => {
throw new Error('Unknown type in elastic_to_list_items');
};

View file

@ -0,0 +1,47 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { EsDataTypeUnion, Type } from '../../../common/schemas';
import { transformListItemToElasticQuery } from './transform_list_item_to_elastic_query';
describe('transform_elastic_to_elastic_query', () => {
beforeEach(() => {
jest.clearAllMocks();
});
afterEach(() => {
jest.clearAllMocks();
});
test('it transforms a ip type and value to a union', () => {
const elasticQuery = transformListItemToElasticQuery({
type: 'ip',
value: '127.0.0.1',
});
const expected: EsDataTypeUnion = { ip: '127.0.0.1' };
expect(elasticQuery).toEqual(expected);
});
test('it transforms a keyword type and value to a union', () => {
const elasticQuery = transformListItemToElasticQuery({
type: 'keyword',
value: 'host-name',
});
const expected: EsDataTypeUnion = { keyword: 'host-name' };
expect(elasticQuery).toEqual(expected);
});
test('it throws if the type is not known', () => {
const type: Type = 'made-up' as Type;
expect(() =>
transformListItemToElasticQuery({
type,
value: 'some-value',
})
).toThrow('Unknown type: "made-up" in transformListItemToElasticQuery');
});
});

View file

@ -12,8 +12,6 @@ export const transformListItemToElasticQuery = ({
}: {
type: Type;
value: string;
// We disable the consistent return since we want to use typescript for exhaustive type checks
// eslint-disable-next-line consistent-return
}): EsDataTypeUnion => {
switch (type) {
case 'ip': {
@ -27,4 +25,9 @@ export const transformListItemToElasticQuery = ({
};
}
}
return assertUnreachable(type);
};
const assertUnreachable = (type: string): never => {
throw new Error(`Unknown type: "${type}" in transformListItemToElasticQuery`);
};

View file

@ -4,18 +4,17 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { IContextProvider, RequestHandler, ScopedClusterClient } from 'kibana/server';
import { IContextProvider, RequestHandler } from 'kibana/server';
import { SecurityPluginSetup } from '../../security/server';
import { SpacesPluginSetup } from '../../spaces/server';
import { ListClient } from './services/lists/client';
export type DataClient = Pick<ScopedClusterClient, 'callAsCurrentUser' | 'callAsInternalUser'>;
export type ContextProvider = IContextProvider<RequestHandler<unknown, unknown, unknown>, 'lists'>;
export interface PluginsSetup {
security: SecurityPluginSetup;
security: SecurityPluginSetup | undefined | null;
spaces: SpacesPluginSetup | undefined | null;
}