[Rollups] Server NP migration (#55606)
This commit is contained in:
parent
6d03ad440f
commit
86e186c63b
|
@ -4,12 +4,22 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { LICENSE_TYPE_BASIC, LicenseType } from '../../../common/constants';
|
||||
|
||||
export const PLUGIN = {
|
||||
ID: 'rollup',
|
||||
MINIMUM_LICENSE_REQUIRED: LICENSE_TYPE_BASIC as LicenseType,
|
||||
getI18nName: (i18n: any): string => {
|
||||
return i18n.translate('xpack.rollupJobs.appName', {
|
||||
defaultMessage: 'Rollup jobs',
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export const CONFIG_ROLLUPS = 'rollups:enableIndexPatterns';
|
||||
|
||||
export const API_BASE_PATH = '/api/rollup';
|
||||
|
||||
export {
|
||||
UIM_APP_NAME,
|
||||
UIM_APP_LOAD,
|
||||
|
|
|
@ -5,20 +5,13 @@
|
|||
*/
|
||||
|
||||
import { resolve } from 'path';
|
||||
import { PLUGIN, CONFIG_ROLLUPS } from './common';
|
||||
import { registerLicenseChecker } from './server/lib/register_license_checker';
|
||||
import { rollupDataEnricher } from './rollup_data_enricher';
|
||||
import { registerRollupSearchStrategy } from './server/lib/search_strategies';
|
||||
import {
|
||||
registerIndicesRoute,
|
||||
registerFieldsForWildcardRoute,
|
||||
registerSearchRoute,
|
||||
registerJobsRoute,
|
||||
} from './server/routes/api';
|
||||
import { registerRollupUsageCollector } from './server/usage';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { PluginInitializerContext } from 'src/core/server';
|
||||
import { RollupSetup } from '../../../plugins/rollup/server';
|
||||
import { PLUGIN, CONFIG_ROLLUPS } from './common';
|
||||
import { plugin } from './server';
|
||||
|
||||
export function rollup(kibana) {
|
||||
export function rollup(kibana: any) {
|
||||
return new kibana.Plugin({
|
||||
id: PLUGIN.ID,
|
||||
configPrefix: 'xpack.rollup',
|
||||
|
@ -45,22 +38,30 @@ export function rollup(kibana) {
|
|||
visualize: ['plugins/rollup/legacy'],
|
||||
search: ['plugins/rollup/legacy'],
|
||||
},
|
||||
init: function(server) {
|
||||
const { usageCollection } = server.newPlatform.setup.plugins;
|
||||
registerLicenseChecker(server);
|
||||
registerIndicesRoute(server);
|
||||
registerFieldsForWildcardRoute(server);
|
||||
registerSearchRoute(server);
|
||||
registerJobsRoute(server);
|
||||
registerRollupUsageCollector(usageCollection, server);
|
||||
if (
|
||||
server.plugins.index_management &&
|
||||
server.plugins.index_management.addIndexManagementDataEnricher
|
||||
) {
|
||||
server.plugins.index_management.addIndexManagementDataEnricher(rollupDataEnricher);
|
||||
}
|
||||
init(server: any) {
|
||||
const { core: coreSetup, plugins } = server.newPlatform.setup;
|
||||
const { usageCollection, metrics } = plugins;
|
||||
|
||||
registerRollupSearchStrategy(this.kbnServer);
|
||||
const rollupSetup = (plugins.rollup as unknown) as RollupSetup;
|
||||
|
||||
const initContext = ({
|
||||
config: rollupSetup.__legacy.config,
|
||||
logger: rollupSetup.__legacy.logger,
|
||||
} as unknown) as PluginInitializerContext;
|
||||
|
||||
const rollupPluginInstance = plugin(initContext);
|
||||
|
||||
rollupPluginInstance.setup(coreSetup, {
|
||||
usageCollection,
|
||||
metrics,
|
||||
__LEGACY: {
|
||||
plugins: {
|
||||
xpack_main: server.plugins.xpack_main,
|
||||
rollup: server.plugins[PLUGIN.ID],
|
||||
index_management: server.plugins.index_management,
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
14
x-pack/legacy/plugins/rollup/kibana.json
Normal file
14
x-pack/legacy/plugins/rollup/kibana.json
Normal file
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"id": "rollup",
|
||||
"version": "kibana",
|
||||
"requiredPlugins": [
|
||||
"home",
|
||||
"index_management",
|
||||
"metrics"
|
||||
],
|
||||
"optionalPlugins": [
|
||||
"usageCollection"
|
||||
],
|
||||
"server": true,
|
||||
"ui": false
|
||||
}
|
|
@ -4,7 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export const elasticsearchJsPlugin = (Client, config, components) => {
|
||||
export const elasticsearchJsPlugin = (Client: any, config: any, components: any) => {
|
||||
const ca = components.clientAction.factory;
|
||||
|
||||
Client.prototype.rollup = components.clientAction.namespaceFactory();
|
|
@ -4,4 +4,4 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export { isEsErrorFactory } from './is_es_error_factory';
|
||||
export { registerRollupUsageCollector } from './register';
|
|
@ -5,25 +5,31 @@
|
|||
*/
|
||||
|
||||
import { get } from 'lodash';
|
||||
import { UsageCollectionSetup } from 'src/plugins/usage_collection/server';
|
||||
import { CallCluster } from 'src/legacy/core_plugins/elasticsearch';
|
||||
|
||||
interface IdToFlagMap {
|
||||
[key: string]: boolean;
|
||||
}
|
||||
|
||||
const ROLLUP_USAGE_TYPE = 'rollups';
|
||||
|
||||
// elasticsearch index.max_result_window default value
|
||||
const ES_MAX_RESULT_WINDOW_DEFAULT_VALUE = 1000;
|
||||
|
||||
function getIdFromSavedObjectId(savedObjectId) {
|
||||
function getIdFromSavedObjectId(savedObjectId: string) {
|
||||
// The saved object ID is formatted `{TYPE}:{ID}`.
|
||||
return savedObjectId.split(':')[1];
|
||||
}
|
||||
|
||||
function createIdToFlagMap(ids) {
|
||||
function createIdToFlagMap(ids: string[]) {
|
||||
return ids.reduce((map, id) => {
|
||||
map[id] = true;
|
||||
return map;
|
||||
}, {});
|
||||
}, {} as any);
|
||||
}
|
||||
|
||||
async function fetchRollupIndexPatterns(kibanaIndex, callCluster) {
|
||||
async function fetchRollupIndexPatterns(kibanaIndex: string, callCluster: CallCluster) {
|
||||
const searchParams = {
|
||||
size: ES_MAX_RESULT_WINDOW_DEFAULT_VALUE,
|
||||
index: kibanaIndex,
|
||||
|
@ -50,7 +56,11 @@ async function fetchRollupIndexPatterns(kibanaIndex, callCluster) {
|
|||
});
|
||||
}
|
||||
|
||||
async function fetchRollupSavedSearches(kibanaIndex, callCluster, rollupIndexPatternToFlagMap) {
|
||||
async function fetchRollupSavedSearches(
|
||||
kibanaIndex: string,
|
||||
callCluster: CallCluster,
|
||||
rollupIndexPatternToFlagMap: IdToFlagMap
|
||||
) {
|
||||
const searchParams = {
|
||||
size: ES_MAX_RESULT_WINDOW_DEFAULT_VALUE,
|
||||
index: kibanaIndex,
|
||||
|
@ -86,19 +96,19 @@ async function fetchRollupSavedSearches(kibanaIndex, callCluster, rollupIndexPat
|
|||
const searchSource = JSON.parse(searchSourceJSON);
|
||||
|
||||
if (rollupIndexPatternToFlagMap[searchSource.index]) {
|
||||
const id = getIdFromSavedObjectId(savedObjectId);
|
||||
const id = getIdFromSavedObjectId(savedObjectId) as string;
|
||||
rollupSavedSearches.push(id);
|
||||
}
|
||||
|
||||
return rollupSavedSearches;
|
||||
}, []);
|
||||
}, [] as string[]);
|
||||
}
|
||||
|
||||
async function fetchRollupVisualizations(
|
||||
kibanaIndex,
|
||||
callCluster,
|
||||
rollupIndexPatternToFlagMap,
|
||||
rollupSavedSearchesToFlagMap
|
||||
kibanaIndex: string,
|
||||
callCluster: CallCluster,
|
||||
rollupIndexPatternToFlagMap: IdToFlagMap,
|
||||
rollupSavedSearchesToFlagMap: IdToFlagMap
|
||||
) {
|
||||
const searchParams = {
|
||||
size: ES_MAX_RESULT_WINDOW_DEFAULT_VALUE,
|
||||
|
@ -135,7 +145,7 @@ async function fetchRollupVisualizations(
|
|||
savedSearchRefName,
|
||||
kibanaSavedObjectMeta: { searchSourceJSON },
|
||||
},
|
||||
references = [],
|
||||
references = [] as any[],
|
||||
},
|
||||
} = visualization;
|
||||
|
||||
|
@ -164,13 +174,14 @@ async function fetchRollupVisualizations(
|
|||
};
|
||||
}
|
||||
|
||||
export function registerRollupUsageCollector(usageCollection, server) {
|
||||
const kibanaIndex = server.config().get('kibana.index');
|
||||
|
||||
export function registerRollupUsageCollector(
|
||||
usageCollection: UsageCollectionSetup,
|
||||
kibanaIndex: string
|
||||
): void {
|
||||
const collector = usageCollection.makeUsageCollector({
|
||||
type: ROLLUP_USAGE_TYPE,
|
||||
isReady: () => true,
|
||||
fetch: async callCluster => {
|
||||
fetch: async (callCluster: CallCluster) => {
|
||||
const rollupIndexPatterns = await fetchRollupIndexPatterns(kibanaIndex, callCluster);
|
||||
const rollupIndexPatternToFlagMap = createIdToFlagMap(rollupIndexPatterns);
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import { PluginInitializerContext } from 'src/core/server';
|
||||
import { RollupsServerPlugin } from './plugin';
|
||||
|
||||
export { wrapCustomError } from './wrap_custom_error';
|
||||
export { wrapEsError } from './wrap_es_error';
|
||||
export { wrapUnknownError } from './wrap_unknown_error';
|
||||
export const plugin = (ctx: PluginInitializerContext) => new RollupsServerPlugin(ctx);
|
|
@ -1,21 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { once } from 'lodash';
|
||||
import { elasticsearchJsPlugin } from '../../client/elasticsearch_rollup';
|
||||
|
||||
const callWithRequest = once(server => {
|
||||
const client = server.newPlatform.setup.core.elasticsearch.createClient('rollup', {
|
||||
plugins: [elasticsearchJsPlugin],
|
||||
});
|
||||
return (request, ...args) => client.asScoped(request).callAsCurrentUser(...args);
|
||||
});
|
||||
|
||||
export const callWithRequestFactory = (server, request) => {
|
||||
return (...args) => {
|
||||
return callWithRequest(server)(request, ...args);
|
||||
};
|
||||
};
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* 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 { ElasticsearchServiceSetup } from 'kibana/server';
|
||||
import { once } from 'lodash';
|
||||
import { elasticsearchJsPlugin } from '../../client/elasticsearch_rollup';
|
||||
|
||||
const callWithRequest = once((elasticsearchService: ElasticsearchServiceSetup) => {
|
||||
const config = { plugins: [elasticsearchJsPlugin] };
|
||||
return elasticsearchService.createClient('rollup', config);
|
||||
});
|
||||
|
||||
export const callWithRequestFactory = (
|
||||
elasticsearchService: ElasticsearchServiceSetup,
|
||||
request: any
|
||||
) => {
|
||||
return (...args: any[]) => {
|
||||
return (
|
||||
callWithRequest(elasticsearchService)
|
||||
.asScoped(request)
|
||||
// @ts-ignore
|
||||
.callAsCurrentUser(...args)
|
||||
);
|
||||
};
|
||||
};
|
|
@ -1,145 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import { set } from 'lodash';
|
||||
import { checkLicense } from '../check_license';
|
||||
|
||||
describe('check_license', function() {
|
||||
let mockLicenseInfo;
|
||||
beforeEach(() => (mockLicenseInfo = {}));
|
||||
|
||||
describe('license information is undefined', () => {
|
||||
beforeEach(() => (mockLicenseInfo = undefined));
|
||||
|
||||
it('should set isAvailable to false', () => {
|
||||
expect(checkLicense(mockLicenseInfo).isAvailable).to.be(false);
|
||||
});
|
||||
|
||||
it('should set showLinks to true', () => {
|
||||
expect(checkLicense(mockLicenseInfo).showLinks).to.be(true);
|
||||
});
|
||||
|
||||
it('should set enableLinks to false', () => {
|
||||
expect(checkLicense(mockLicenseInfo).enableLinks).to.be(false);
|
||||
});
|
||||
|
||||
it('should set a message', () => {
|
||||
expect(checkLicense(mockLicenseInfo).message).to.not.be(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
describe('license information is not available', () => {
|
||||
beforeEach(() => (mockLicenseInfo.isAvailable = () => false));
|
||||
|
||||
it('should set isAvailable to false', () => {
|
||||
expect(checkLicense(mockLicenseInfo).isAvailable).to.be(false);
|
||||
});
|
||||
|
||||
it('should set showLinks to true', () => {
|
||||
expect(checkLicense(mockLicenseInfo).showLinks).to.be(true);
|
||||
});
|
||||
|
||||
it('should set enableLinks to false', () => {
|
||||
expect(checkLicense(mockLicenseInfo).enableLinks).to.be(false);
|
||||
});
|
||||
|
||||
it('should set a message', () => {
|
||||
expect(checkLicense(mockLicenseInfo).message).to.not.be(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
describe('license information is available', () => {
|
||||
beforeEach(() => {
|
||||
mockLicenseInfo.isAvailable = () => true;
|
||||
set(mockLicenseInfo, 'license.getType', () => 'basic');
|
||||
});
|
||||
|
||||
describe('& license is > basic', () => {
|
||||
beforeEach(() => set(mockLicenseInfo, 'license.isOneOf', () => true));
|
||||
|
||||
describe('& license is active', () => {
|
||||
beforeEach(() => set(mockLicenseInfo, 'license.isActive', () => true));
|
||||
|
||||
it('should set isAvailable to true', () => {
|
||||
expect(checkLicense(mockLicenseInfo).isAvailable).to.be(true);
|
||||
});
|
||||
|
||||
it('should set showLinks to true', () => {
|
||||
expect(checkLicense(mockLicenseInfo).showLinks).to.be(true);
|
||||
});
|
||||
|
||||
it('should set enableLinks to true', () => {
|
||||
expect(checkLicense(mockLicenseInfo).enableLinks).to.be(true);
|
||||
});
|
||||
|
||||
it('should not set a message', () => {
|
||||
expect(checkLicense(mockLicenseInfo).message).to.be(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
describe('& license is expired', () => {
|
||||
beforeEach(() => set(mockLicenseInfo, 'license.isActive', () => false));
|
||||
|
||||
it('should set isAvailable to false', () => {
|
||||
expect(checkLicense(mockLicenseInfo).isAvailable).to.be(false);
|
||||
});
|
||||
|
||||
it('should set showLinks to true', () => {
|
||||
expect(checkLicense(mockLicenseInfo).showLinks).to.be(true);
|
||||
});
|
||||
|
||||
it('should set enableLinks to false', () => {
|
||||
expect(checkLicense(mockLicenseInfo).enableLinks).to.be(false);
|
||||
});
|
||||
|
||||
it('should set a message', () => {
|
||||
expect(checkLicense(mockLicenseInfo).message).to.not.be(undefined);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('& license is basic', () => {
|
||||
beforeEach(() => set(mockLicenseInfo, 'license.isOneOf', () => true));
|
||||
|
||||
describe('& license is active', () => {
|
||||
beforeEach(() => set(mockLicenseInfo, 'license.isActive', () => true));
|
||||
|
||||
it('should set isAvailable to true', () => {
|
||||
expect(checkLicense(mockLicenseInfo).isAvailable).to.be(true);
|
||||
});
|
||||
|
||||
it('should set showLinks to true', () => {
|
||||
expect(checkLicense(mockLicenseInfo).showLinks).to.be(true);
|
||||
});
|
||||
|
||||
it('should set enableLinks to true', () => {
|
||||
expect(checkLicense(mockLicenseInfo).enableLinks).to.be(true);
|
||||
});
|
||||
|
||||
it('should not set a message', () => {
|
||||
expect(checkLicense(mockLicenseInfo).message).to.be(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
describe('& license is expired', () => {
|
||||
beforeEach(() => set(mockLicenseInfo, 'license.isActive', () => false));
|
||||
|
||||
it('should set isAvailable to false', () => {
|
||||
expect(checkLicense(mockLicenseInfo).isAvailable).to.be(false);
|
||||
});
|
||||
|
||||
it('should set showLinks to true', () => {
|
||||
expect(checkLicense(mockLicenseInfo).showLinks).to.be(true);
|
||||
});
|
||||
|
||||
it('should set a message', () => {
|
||||
expect(checkLicense(mockLicenseInfo).message).to.not.be(undefined);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,66 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export function checkLicense(xpackLicenseInfo) {
|
||||
const pluginName = 'Rollups';
|
||||
|
||||
// If, for some reason, we cannot get the license information
|
||||
// from Elasticsearch, assume worst case and disable
|
||||
if (!xpackLicenseInfo || !xpackLicenseInfo.isAvailable()) {
|
||||
return {
|
||||
isAvailable: false,
|
||||
showLinks: true,
|
||||
enableLinks: false,
|
||||
message: i18n.translate('xpack.rollupJobs.checkLicense.errorUnavailableMessage', {
|
||||
defaultMessage:
|
||||
'You cannot use {pluginName} because license information is not available at this time.',
|
||||
values: { pluginName },
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
const VALID_LICENSE_MODES = ['trial', 'basic', 'standard', 'gold', 'platinum', 'enterprise'];
|
||||
|
||||
const isLicenseModeValid = xpackLicenseInfo.license.isOneOf(VALID_LICENSE_MODES);
|
||||
const isLicenseActive = xpackLicenseInfo.license.isActive();
|
||||
const licenseType = xpackLicenseInfo.license.getType();
|
||||
|
||||
// License is not valid
|
||||
if (!isLicenseModeValid) {
|
||||
return {
|
||||
isAvailable: false,
|
||||
showLinks: false,
|
||||
message: i18n.translate('xpack.rollupJobs.checkLicense.errorUnsupportedMessage', {
|
||||
defaultMessage:
|
||||
'Your {licenseType} license does not support {pluginName}. Please upgrade your license.',
|
||||
values: { licenseType, pluginName },
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
// License is valid but not active
|
||||
if (!isLicenseActive) {
|
||||
return {
|
||||
isAvailable: false,
|
||||
showLinks: true,
|
||||
enableLinks: false,
|
||||
message: i18n.translate('xpack.rollupJobs.checkLicense.errorExpiredMessage', {
|
||||
defaultMessage:
|
||||
'You cannot use {pluginName} because your {licenseType} license has expired',
|
||||
values: { licenseType, pluginName },
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
// License is valid and active
|
||||
return {
|
||||
isAvailable: true,
|
||||
showLinks: true,
|
||||
enableLinks: true,
|
||||
};
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import { wrapCustomError } from '../wrap_custom_error';
|
||||
|
||||
describe('wrap_custom_error', () => {
|
||||
describe('#wrapCustomError', () => {
|
||||
it('should return a Boom object', () => {
|
||||
const originalError = new Error('I am an error');
|
||||
const statusCode = 404;
|
||||
const wrappedError = wrapCustomError(originalError, statusCode);
|
||||
|
||||
expect(wrappedError.isBoom).to.be(true);
|
||||
expect(wrappedError.output.statusCode).to.equal(statusCode);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,39 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import { wrapEsError } from '../wrap_es_error';
|
||||
|
||||
describe('wrap_es_error', () => {
|
||||
describe('#wrapEsError', () => {
|
||||
let originalError;
|
||||
beforeEach(() => {
|
||||
originalError = new Error('I am an error');
|
||||
originalError.statusCode = 404;
|
||||
originalError.response = '{}';
|
||||
});
|
||||
|
||||
it('should return a Boom object', () => {
|
||||
const wrappedError = wrapEsError(originalError);
|
||||
|
||||
expect(wrappedError.isBoom).to.be(true);
|
||||
});
|
||||
|
||||
it('should return the correct Boom object', () => {
|
||||
const wrappedError = wrapEsError(originalError);
|
||||
|
||||
expect(wrappedError.output.statusCode).to.be(originalError.statusCode);
|
||||
expect(wrappedError.output.payload.message).to.be(originalError.message);
|
||||
});
|
||||
|
||||
it('should return the correct Boom object with custom message', () => {
|
||||
const wrappedError = wrapEsError(originalError, { 404: 'No encontrado!' });
|
||||
|
||||
expect(wrappedError.output.statusCode).to.be(originalError.statusCode);
|
||||
expect(wrappedError.output.payload.message).to.be('No encontrado!');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,19 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import { wrapUnknownError } from '../wrap_unknown_error';
|
||||
|
||||
describe('wrap_unknown_error', () => {
|
||||
describe('#wrapUnknownError', () => {
|
||||
it('should return a Boom object', () => {
|
||||
const originalError = new Error('I am an error');
|
||||
const wrappedError = wrapUnknownError(originalError);
|
||||
|
||||
expect(wrappedError.isBoom).to.be(true);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,18 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import Boom from 'boom';
|
||||
|
||||
/**
|
||||
* Wraps a custom error into a Boom error response and returns it
|
||||
*
|
||||
* @param err Object error
|
||||
* @param statusCode Error status code
|
||||
* @return Object Boom error response
|
||||
*/
|
||||
export function wrapCustomError(err, statusCode) {
|
||||
return Boom.boomify(err, { statusCode });
|
||||
}
|
|
@ -1,59 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import Boom from 'boom';
|
||||
|
||||
function extractCausedByChain(causedBy = {}, accumulator = []) {
|
||||
const { reason, caused_by } = causedBy; // eslint-disable-line camelcase
|
||||
|
||||
if (reason) {
|
||||
accumulator.push(reason);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line camelcase
|
||||
if (caused_by) {
|
||||
return extractCausedByChain(caused_by, accumulator);
|
||||
}
|
||||
|
||||
return accumulator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps an error thrown by the ES JS client into a Boom error response and returns it
|
||||
*
|
||||
* @param err Object Error thrown by ES JS client
|
||||
* @param statusCodeToMessageMap Object Optional map of HTTP status codes => error messages
|
||||
* @return Object Boom error response
|
||||
*/
|
||||
export function wrapEsError(err, statusCodeToMessageMap = {}) {
|
||||
const { statusCode, response } = err;
|
||||
|
||||
const {
|
||||
error: {
|
||||
root_cause = [], // eslint-disable-line camelcase
|
||||
caused_by, // eslint-disable-line camelcase
|
||||
} = {},
|
||||
} = JSON.parse(response);
|
||||
|
||||
// If no custom message if specified for the error's status code, just
|
||||
// wrap the error as a Boom error response and return it
|
||||
if (!statusCodeToMessageMap[statusCode]) {
|
||||
const boomError = Boom.boomify(err, { statusCode });
|
||||
|
||||
// The caused_by chain has the most information so use that if it's available. If not then
|
||||
// settle for the root_cause.
|
||||
const causedByChain = extractCausedByChain(caused_by);
|
||||
const defaultCause = root_cause.length ? extractCausedByChain(root_cause[0]) : undefined;
|
||||
|
||||
boomError.output.payload.cause = causedByChain.length ? causedByChain : defaultCause;
|
||||
return boomError;
|
||||
}
|
||||
|
||||
// Otherwise, use the custom message to create a Boom error response and
|
||||
// return it
|
||||
const message = statusCodeToMessageMap[statusCode];
|
||||
return new Boom(message, { statusCode });
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import Boom from 'boom';
|
||||
|
||||
/**
|
||||
* Wraps an unknown error into a Boom error response and returns it
|
||||
*
|
||||
* @param err Object Unknown error
|
||||
* @return Object Boom error response
|
||||
*/
|
||||
export function wrapUnknownError(err) {
|
||||
return Boom.boomify(err);
|
||||
}
|
|
@ -4,4 +4,4 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export { checkLicense } from './check_license';
|
||||
export { isEsError } from './is_es_error';
|
|
@ -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;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import * as legacyElasticsearch from 'elasticsearch';
|
||||
|
||||
const esErrorsParent = legacyElasticsearch.errors._Abstract;
|
||||
|
||||
export function isEsError(err: Error) {
|
||||
return err instanceof esErrorsParent;
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import { isEsErrorFactory } from '../is_es_error_factory';
|
||||
import { set } from 'lodash';
|
||||
|
||||
class MockAbstractEsError {}
|
||||
|
||||
describe('is_es_error_factory', () => {
|
||||
let mockServer;
|
||||
let isEsError;
|
||||
|
||||
beforeEach(() => {
|
||||
const mockEsErrors = {
|
||||
_Abstract: MockAbstractEsError,
|
||||
};
|
||||
mockServer = {};
|
||||
set(mockServer, 'plugins.elasticsearch.getCluster', () => ({ errors: mockEsErrors }));
|
||||
|
||||
isEsError = isEsErrorFactory(mockServer);
|
||||
});
|
||||
|
||||
describe('#isEsErrorFactory', () => {
|
||||
it('should return a function', () => {
|
||||
expect(isEsError).to.be.a(Function);
|
||||
});
|
||||
|
||||
describe('returned function', () => {
|
||||
it('should return true if passed-in err is a known esError', () => {
|
||||
const knownEsError = new MockAbstractEsError();
|
||||
expect(isEsError(knownEsError)).to.be(true);
|
||||
});
|
||||
|
||||
it('should return false if passed-in err is not a known esError', () => {
|
||||
const unknownEsError = {};
|
||||
expect(isEsError(unknownEsError)).to.be(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,18 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { memoize } from 'lodash';
|
||||
|
||||
const esErrorsFactory = memoize(server => {
|
||||
return server.plugins.elasticsearch.getCluster('admin').errors;
|
||||
});
|
||||
|
||||
export function isEsErrorFactory(server) {
|
||||
const esErrors = esErrorsFactory(server);
|
||||
return function isEsError(err) {
|
||||
return err instanceof esErrors._Abstract;
|
||||
};
|
||||
}
|
|
@ -37,10 +37,10 @@ export function mergeJobConfigurations(jobs = []) {
|
|||
throw new Error('No capabilities available');
|
||||
}
|
||||
|
||||
const allAggs = {};
|
||||
const allAggs: { [key: string]: any } = {};
|
||||
|
||||
// For each job, look through all of its fields
|
||||
jobs.forEach(job => {
|
||||
jobs.forEach((job: { fields: { [key: string]: any } }) => {
|
||||
const fields = job.fields;
|
||||
const fieldNames = Object.keys(fields);
|
||||
|
||||
|
@ -49,7 +49,7 @@ export function mergeJobConfigurations(jobs = []) {
|
|||
const fieldAggs = fields[fieldName];
|
||||
|
||||
// Look through each field's capabilities (aggregations)
|
||||
fieldAggs.forEach(agg => {
|
||||
fieldAggs.forEach((agg: { agg: string; interval: string }) => {
|
||||
const aggName = agg.agg;
|
||||
const aggDoesntExist = !allAggs[aggName];
|
||||
const fieldDoesntExist = allAggs[aggName] && !allAggs[aggName][fieldName];
|
|
@ -1,66 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import { licensePreRoutingFactory } from '../license_pre_routing_factory';
|
||||
|
||||
describe('license_pre_routing_factory', () => {
|
||||
describe('#reportingFeaturePreRoutingFactory', () => {
|
||||
let mockServer;
|
||||
let mockLicenseCheckResults;
|
||||
|
||||
beforeEach(() => {
|
||||
mockServer = {
|
||||
plugins: {
|
||||
xpack_main: {
|
||||
info: {
|
||||
feature: () => ({
|
||||
getLicenseCheckResults: () => mockLicenseCheckResults,
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
it('only instantiates one instance per server', () => {
|
||||
const firstInstance = licensePreRoutingFactory(mockServer);
|
||||
const secondInstance = licensePreRoutingFactory(mockServer);
|
||||
|
||||
expect(firstInstance).to.be(secondInstance);
|
||||
});
|
||||
|
||||
describe('isAvailable is false', () => {
|
||||
beforeEach(() => {
|
||||
mockLicenseCheckResults = {
|
||||
isAvailable: false,
|
||||
};
|
||||
});
|
||||
|
||||
it('replies with 403', () => {
|
||||
const licensePreRouting = licensePreRoutingFactory(mockServer);
|
||||
const response = licensePreRouting();
|
||||
expect(response).to.be.an(Error);
|
||||
expect(response.isBoom).to.be(true);
|
||||
expect(response.output.statusCode).to.be(403);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isAvailable is true', () => {
|
||||
beforeEach(() => {
|
||||
mockLicenseCheckResults = {
|
||||
isAvailable: true,
|
||||
};
|
||||
});
|
||||
|
||||
it('replies with nothing', () => {
|
||||
const licensePreRouting = licensePreRoutingFactory(mockServer);
|
||||
const response = licensePreRouting();
|
||||
expect(response).to.be(null);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,28 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { once } from 'lodash';
|
||||
import { wrapCustomError } from '../error_wrappers';
|
||||
import { PLUGIN } from '../../../common';
|
||||
|
||||
export const licensePreRoutingFactory = once(server => {
|
||||
const xpackMainPlugin = server.plugins.xpack_main;
|
||||
|
||||
// License checking and enable/disable logic
|
||||
function licensePreRouting() {
|
||||
const licenseCheckResults = xpackMainPlugin.info.feature(PLUGIN.ID).getLicenseCheckResults();
|
||||
if (!licenseCheckResults.isAvailable) {
|
||||
const error = new Error(licenseCheckResults.message);
|
||||
const statusCode = 403;
|
||||
const wrappedError = wrapCustomError(error, statusCode);
|
||||
return wrappedError;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return licensePreRouting;
|
||||
});
|
|
@ -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;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import { licensePreRoutingFactory } from '.';
|
||||
import {
|
||||
LICENSE_STATUS_VALID,
|
||||
LICENSE_STATUS_INVALID,
|
||||
} from '../../../../../common/constants/license_status';
|
||||
import { kibanaResponseFactory } from '../../../../../../../src/core/server';
|
||||
|
||||
describe('licensePreRoutingFactory()', () => {
|
||||
let mockServer;
|
||||
let mockLicenseCheckResults;
|
||||
|
||||
beforeEach(() => {
|
||||
mockServer = {
|
||||
plugins: {
|
||||
xpack_main: {
|
||||
info: {
|
||||
feature: () => ({
|
||||
getLicenseCheckResults: () => mockLicenseCheckResults,
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
describe('status is invalid', () => {
|
||||
beforeEach(() => {
|
||||
mockLicenseCheckResults = {
|
||||
status: LICENSE_STATUS_INVALID,
|
||||
};
|
||||
});
|
||||
|
||||
it('replies with 403', () => {
|
||||
const routeWithLicenseCheck = licensePreRoutingFactory(mockServer, () => {});
|
||||
const stubRequest = {};
|
||||
const response = routeWithLicenseCheck({}, stubRequest, kibanaResponseFactory);
|
||||
expect(response.status).to.be(403);
|
||||
});
|
||||
});
|
||||
|
||||
describe('status is valid', () => {
|
||||
beforeEach(() => {
|
||||
mockLicenseCheckResults = {
|
||||
status: LICENSE_STATUS_VALID,
|
||||
};
|
||||
});
|
||||
|
||||
it('replies with nothing', () => {
|
||||
const routeWithLicenseCheck = licensePreRoutingFactory(mockServer, () => null);
|
||||
const stubRequest = {};
|
||||
const response = routeWithLicenseCheck({}, stubRequest, kibanaResponseFactory);
|
||||
expect(response).to.be(null);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* 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 {
|
||||
KibanaRequest,
|
||||
KibanaResponseFactory,
|
||||
RequestHandler,
|
||||
RequestHandlerContext,
|
||||
} from 'src/core/server';
|
||||
import { PLUGIN } from '../../../common';
|
||||
import { LICENSE_STATUS_VALID } from '../../../../../common/constants/license_status';
|
||||
import { ServerShim } from '../../types';
|
||||
|
||||
export const licensePreRoutingFactory = (
|
||||
server: ServerShim,
|
||||
handler: RequestHandler<any, any, any>
|
||||
): RequestHandler<any, any, any> => {
|
||||
const xpackMainPlugin = server.plugins.xpack_main;
|
||||
|
||||
// License checking and enable/disable logic
|
||||
return function licensePreRouting(
|
||||
ctx: RequestHandlerContext,
|
||||
request: KibanaRequest,
|
||||
response: KibanaResponseFactory
|
||||
) {
|
||||
const licenseCheckResults = xpackMainPlugin.info.feature(PLUGIN.ID).getLicenseCheckResults();
|
||||
const { status } = licenseCheckResults;
|
||||
|
||||
if (status !== LICENSE_STATUS_VALID) {
|
||||
return response.customError({
|
||||
body: {
|
||||
message: licenseCheckResults.messsage,
|
||||
},
|
||||
statusCode: 403,
|
||||
});
|
||||
}
|
||||
|
||||
return handler(ctx, request, response);
|
||||
};
|
||||
};
|
|
@ -6,9 +6,9 @@
|
|||
|
||||
import { mergeJobConfigurations } from './jobs_compatibility';
|
||||
|
||||
export function getCapabilitiesForRollupIndices(indices) {
|
||||
export function getCapabilitiesForRollupIndices(indices: { [key: string]: any }) {
|
||||
const indexNames = Object.keys(indices);
|
||||
const capabilities = {};
|
||||
const capabilities = {} as { [key: string]: any };
|
||||
|
||||
indexNames.forEach(index => {
|
||||
try {
|
|
@ -6,13 +6,18 @@
|
|||
|
||||
// Merge rollup capabilities information with field information
|
||||
|
||||
export interface Field {
|
||||
name?: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export const mergeCapabilitiesWithFields = (
|
||||
rollupIndexCapabilities,
|
||||
fieldsFromFieldCapsApi,
|
||||
previousFields = []
|
||||
rollupIndexCapabilities: { [key: string]: any },
|
||||
fieldsFromFieldCapsApi: { [key: string]: any },
|
||||
previousFields: Field[] = []
|
||||
) => {
|
||||
const rollupFields = [...previousFields];
|
||||
const rollupFieldNames = [];
|
||||
const rollupFieldNames: string[] = [];
|
||||
|
||||
Object.keys(rollupIndexCapabilities).forEach(agg => {
|
||||
// Field names of the aggregation
|
|
@ -1,24 +0,0 @@
|
|||
/*
|
||||
|
||||
|
||||
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { mirrorPluginStatus } from '../../../../../server/lib/mirror_plugin_status';
|
||||
import { checkLicense } from '../check_license';
|
||||
import { PLUGIN } from '../../../common';
|
||||
|
||||
export function registerLicenseChecker(server) {
|
||||
const xpackMainPlugin = server.plugins.xpack_main;
|
||||
const rollupPlugin = server.plugins[PLUGIN.ID];
|
||||
|
||||
mirrorPluginStatus(xpackMainPlugin, rollupPlugin);
|
||||
xpackMainPlugin.status.once('green', () => {
|
||||
// Register a function that is called whenever the xpack info changes,
|
||||
// to re-compute the license check results for this plugin
|
||||
xpackMainPlugin.info.feature(PLUGIN.ID).registerLicenseCheckResultsGenerator(checkLicense);
|
||||
});
|
||||
}
|
|
@ -4,9 +4,14 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { unitsMap } from '@elastic/datemath';
|
||||
import dateMath from '@elastic/datemath';
|
||||
|
||||
export type Unit = 'ms' | 's' | 'm' | 'h' | 'd' | 'w' | 'M' | 'y';
|
||||
|
||||
export const leastCommonInterval = (num = 0, base = 0) =>
|
||||
Math.max(Math.ceil(num / base) * base, base);
|
||||
export const isCalendarInterval = ({ unit, value }) =>
|
||||
value === 1 && ['calendar', 'mixed'].includes(unitsMap[unit].type);
|
||||
|
||||
export const isCalendarInterval = ({ unit, value }: { unit: Unit; value: number }) => {
|
||||
const { unitsMap } = dateMath;
|
||||
return value === 1 && ['calendar', 'mixed'].includes(unitsMap[unit].type);
|
||||
};
|
|
@ -1,32 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import { getRollupSearchStrategy } from './rollup_search_strategy';
|
||||
import { getRollupSearchRequest } from './rollup_search_request';
|
||||
import { getRollupSearchCapabilities } from './rollup_search_capabilities';
|
||||
import {
|
||||
AbstractSearchRequest,
|
||||
DefaultSearchCapabilities,
|
||||
AbstractSearchStrategy,
|
||||
} from '../../../../../../../src/plugins/vis_type_timeseries/server';
|
||||
|
||||
export const registerRollupSearchStrategy = kbnServer =>
|
||||
kbnServer.afterPluginsInit(() => {
|
||||
if (!kbnServer.newPlatform.setup.plugins.metrics) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { addSearchStrategy } = kbnServer.newPlatform.setup.plugins.metrics;
|
||||
|
||||
const RollupSearchRequest = getRollupSearchRequest(AbstractSearchRequest);
|
||||
const RollupSearchCapabilities = getRollupSearchCapabilities(DefaultSearchCapabilities);
|
||||
const RollupSearchStrategy = getRollupSearchStrategy(
|
||||
AbstractSearchStrategy,
|
||||
RollupSearchRequest,
|
||||
RollupSearchCapabilities
|
||||
);
|
||||
|
||||
addSearchStrategy(new RollupSearchStrategy(kbnServer));
|
||||
});
|
|
@ -6,45 +6,22 @@
|
|||
import { registerRollupSearchStrategy } from './register_rollup_search_strategy';
|
||||
|
||||
describe('Register Rollup Search Strategy', () => {
|
||||
let kbnServer;
|
||||
let metrics;
|
||||
let routeDependencies;
|
||||
let addSearchStrategy;
|
||||
|
||||
beforeEach(() => {
|
||||
const afterPluginsInit = jest.fn(callback => callback());
|
||||
|
||||
kbnServer = {
|
||||
afterPluginsInit,
|
||||
newPlatform: {
|
||||
setup: { plugins: {} },
|
||||
},
|
||||
routeDependencies = {
|
||||
router: jest.fn().mockName('router'),
|
||||
elasticsearchService: jest.fn().mockName('elasticsearchService'),
|
||||
elasticsearch: jest.fn().mockName('elasticsearch'),
|
||||
};
|
||||
|
||||
metrics = {
|
||||
addSearchStrategy: jest.fn().mockName('addSearchStrategy'),
|
||||
AbstractSearchRequest: jest.fn().mockName('AbstractSearchRequest'),
|
||||
AbstractSearchStrategy: jest.fn().mockName('AbstractSearchStrategy'),
|
||||
DefaultSearchCapabilities: jest.fn().mockName('DefaultSearchCapabilities'),
|
||||
};
|
||||
addSearchStrategy = jest.fn().mockName('addSearchStrategy');
|
||||
});
|
||||
|
||||
test('should run initialization on "afterPluginsInit" hook', () => {
|
||||
registerRollupSearchStrategy(kbnServer);
|
||||
test('should run initialization', () => {
|
||||
registerRollupSearchStrategy(routeDependencies, addSearchStrategy);
|
||||
|
||||
expect(kbnServer.afterPluginsInit).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should run initialization if metrics plugin available', () => {
|
||||
registerRollupSearchStrategy({
|
||||
...kbnServer,
|
||||
newPlatform: { setup: { plugins: { metrics } } },
|
||||
});
|
||||
|
||||
expect(metrics.addSearchStrategy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should not run initialization if metrics plugin unavailable', () => {
|
||||
registerRollupSearchStrategy(kbnServer);
|
||||
|
||||
expect(metrics.addSearchStrategy).not.toHaveBeenCalled();
|
||||
expect(addSearchStrategy).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* 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 { getRollupSearchStrategy } from './rollup_search_strategy';
|
||||
import { getRollupSearchRequest } from './rollup_search_request';
|
||||
import { getRollupSearchCapabilities } from './rollup_search_capabilities';
|
||||
import {
|
||||
AbstractSearchRequest,
|
||||
DefaultSearchCapabilities,
|
||||
AbstractSearchStrategy,
|
||||
} from '../../../../../../../src/plugins/vis_type_timeseries/server';
|
||||
import { RouteDependencies } from '../../types';
|
||||
|
||||
export const registerRollupSearchStrategy = (
|
||||
{ elasticsearchService }: RouteDependencies,
|
||||
addSearchStrategy: (searchStrategy: any) => void
|
||||
) => {
|
||||
const RollupSearchRequest = getRollupSearchRequest(AbstractSearchRequest);
|
||||
const RollupSearchCapabilities = getRollupSearchCapabilities(DefaultSearchCapabilities);
|
||||
const RollupSearchStrategy = getRollupSearchStrategy(
|
||||
AbstractSearchStrategy,
|
||||
RollupSearchRequest,
|
||||
RollupSearchCapabilities
|
||||
);
|
||||
|
||||
addSearchStrategy(new RollupSearchStrategy(elasticsearchService));
|
||||
};
|
|
@ -4,24 +4,29 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import { get, has } from 'lodash';
|
||||
import { KibanaRequest } from 'kibana/server';
|
||||
import { leastCommonInterval, isCalendarInterval } from './lib/interval_helper';
|
||||
|
||||
export const getRollupSearchCapabilities = DefaultSearchCapabilities =>
|
||||
export const getRollupSearchCapabilities = (DefaultSearchCapabilities: any) =>
|
||||
class RollupSearchCapabilities extends DefaultSearchCapabilities {
|
||||
constructor(req, fieldsCapabilities, rollupIndex) {
|
||||
constructor(
|
||||
req: KibanaRequest,
|
||||
fieldsCapabilities: { [key: string]: any },
|
||||
rollupIndex: string
|
||||
) {
|
||||
super(req, fieldsCapabilities);
|
||||
|
||||
this.rollupIndex = rollupIndex;
|
||||
this.availableMetrics = get(fieldsCapabilities, `${rollupIndex}.aggs`, {});
|
||||
}
|
||||
|
||||
get dateHistogram() {
|
||||
public get dateHistogram() {
|
||||
const [dateHistogram] = Object.values(this.availableMetrics.date_histogram);
|
||||
|
||||
return dateHistogram;
|
||||
}
|
||||
|
||||
get defaultTimeInterval() {
|
||||
public get defaultTimeInterval() {
|
||||
return (
|
||||
this.dateHistogram.fixed_interval ||
|
||||
this.dateHistogram.calendar_interval ||
|
||||
|
@ -34,16 +39,16 @@ export const getRollupSearchCapabilities = DefaultSearchCapabilities =>
|
|||
);
|
||||
}
|
||||
|
||||
get searchTimezone() {
|
||||
public get searchTimezone() {
|
||||
return get(this.dateHistogram, 'time_zone', null);
|
||||
}
|
||||
|
||||
get whiteListedMetrics() {
|
||||
public get whiteListedMetrics() {
|
||||
const baseRestrictions = this.createUiRestriction({
|
||||
count: this.createUiRestriction(),
|
||||
});
|
||||
|
||||
const getFields = fields =>
|
||||
const getFields = (fields: { [key: string]: any }) =>
|
||||
Object.keys(fields).reduce(
|
||||
(acc, item) => ({
|
||||
...acc,
|
||||
|
@ -61,20 +66,20 @@ export const getRollupSearchCapabilities = DefaultSearchCapabilities =>
|
|||
);
|
||||
}
|
||||
|
||||
get whiteListedGroupByFields() {
|
||||
public get whiteListedGroupByFields() {
|
||||
return this.createUiRestriction({
|
||||
everything: true,
|
||||
terms: has(this.availableMetrics, 'terms'),
|
||||
});
|
||||
}
|
||||
|
||||
get whiteListedTimerangeModes() {
|
||||
public get whiteListedTimerangeModes() {
|
||||
return this.createUiRestriction({
|
||||
last_value: true,
|
||||
});
|
||||
}
|
||||
|
||||
getValidTimeInterval(userIntervalString) {
|
||||
getValidTimeInterval(userIntervalString: string) {
|
||||
const parsedRollupJobInterval = this.parseInterval(this.defaultTimeInterval);
|
||||
const inRollupJobUnit = this.convertIntervalToUnit(
|
||||
userIntervalString,
|
|
@ -5,9 +5,16 @@
|
|||
*/
|
||||
const SEARCH_METHOD = 'rollup.search';
|
||||
|
||||
export const getRollupSearchRequest = AbstractSearchRequest =>
|
||||
interface Search {
|
||||
index: string;
|
||||
body: {
|
||||
[key: string]: any;
|
||||
};
|
||||
}
|
||||
|
||||
export const getRollupSearchRequest = (AbstractSearchRequest: any) =>
|
||||
class RollupSearchRequest extends AbstractSearchRequest {
|
||||
async search(searches) {
|
||||
async search(searches: Search[]) {
|
||||
const requests = searches.map(({ body, index }) =>
|
||||
this.callWithRequest(SEARCH_METHOD, {
|
||||
body,
|
|
@ -4,31 +4,32 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import { indexBy, isString } from 'lodash';
|
||||
import { ElasticsearchServiceSetup, KibanaRequest } from 'kibana/server';
|
||||
import { callWithRequestFactory } from '../call_with_request_factory';
|
||||
import { mergeCapabilitiesWithFields } from '../merge_capabilities_with_fields';
|
||||
import { getCapabilitiesForRollupIndices } from '../map_capabilities';
|
||||
|
||||
const ROLLUP_INDEX_CAPABILITIES_METHOD = 'rollup.rollupIndexCapabilities';
|
||||
|
||||
const getRollupIndices = rollupData => Object.keys(rollupData);
|
||||
const getRollupIndices = (rollupData: { [key: string]: any[] }) => Object.keys(rollupData);
|
||||
|
||||
const isIndexPatternContainsWildcard = indexPattern => indexPattern.includes('*');
|
||||
const isIndexPatternValid = indexPattern =>
|
||||
const isIndexPatternContainsWildcard = (indexPattern: string) => indexPattern.includes('*');
|
||||
const isIndexPatternValid = (indexPattern: string) =>
|
||||
indexPattern && isString(indexPattern) && !isIndexPatternContainsWildcard(indexPattern);
|
||||
|
||||
export const getRollupSearchStrategy = (
|
||||
AbstractSearchStrategy,
|
||||
RollupSearchRequest,
|
||||
RollupSearchCapabilities
|
||||
AbstractSearchStrategy: any,
|
||||
RollupSearchRequest: any,
|
||||
RollupSearchCapabilities: any
|
||||
) =>
|
||||
class RollupSearchStrategy extends AbstractSearchStrategy {
|
||||
name = 'rollup';
|
||||
|
||||
constructor(server) {
|
||||
super(server, callWithRequestFactory, RollupSearchRequest);
|
||||
constructor(elasticsearchService: ElasticsearchServiceSetup) {
|
||||
super(elasticsearchService, callWithRequestFactory, RollupSearchRequest);
|
||||
}
|
||||
|
||||
getRollupData(req, indexPattern) {
|
||||
getRollupData(req: KibanaRequest, indexPattern: string) {
|
||||
const callWithRequest = this.getCallWithRequestInstance(req);
|
||||
|
||||
return callWithRequest(ROLLUP_INDEX_CAPABILITIES_METHOD, {
|
||||
|
@ -36,7 +37,7 @@ export const getRollupSearchStrategy = (
|
|||
}).catch(() => Promise.resolve({}));
|
||||
}
|
||||
|
||||
async checkForViability(req, indexPattern) {
|
||||
async checkForViability(req: KibanaRequest, indexPattern: string) {
|
||||
let isViable = false;
|
||||
let capabilities = null;
|
||||
|
||||
|
@ -60,7 +61,14 @@ export const getRollupSearchStrategy = (
|
|||
};
|
||||
}
|
||||
|
||||
async getFieldsForWildcard(req, indexPattern, { fieldsCapabilities, rollupIndex }) {
|
||||
async getFieldsForWildcard(
|
||||
req: KibanaRequest,
|
||||
indexPattern: string,
|
||||
{
|
||||
fieldsCapabilities,
|
||||
rollupIndex,
|
||||
}: { fieldsCapabilities: { [key: string]: any }; rollupIndex: string }
|
||||
) {
|
||||
const fields = await super.getFieldsForWildcard(req, indexPattern);
|
||||
const fieldsFromFieldCapsApi = indexBy(fields, 'name');
|
||||
const rollupIndexCapabilities = fieldsCapabilities[rollupIndex].aggs;
|
95
x-pack/legacy/plugins/rollup/server/plugin.ts
Normal file
95
x-pack/legacy/plugins/rollup/server/plugin.ts
Normal file
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
* 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 { CoreSetup, Plugin, PluginInitializerContext, Logger } from 'src/core/server';
|
||||
import { first } from 'rxjs/operators';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { UsageCollectionSetup } from 'src/plugins/usage_collection/server';
|
||||
import { VisTypeTimeseriesSetup } from 'src/plugins/vis_type_timeseries/server';
|
||||
import { registerLicenseChecker } from '../../../server/lib/register_license_checker';
|
||||
import { PLUGIN } from '../common';
|
||||
import { ServerShim, RouteDependencies } from './types';
|
||||
|
||||
import {
|
||||
registerIndicesRoute,
|
||||
registerFieldsForWildcardRoute,
|
||||
registerSearchRoute,
|
||||
registerJobsRoute,
|
||||
} from './routes/api';
|
||||
|
||||
import { registerRollupUsageCollector } from './collectors';
|
||||
|
||||
import { rollupDataEnricher } from './rollup_data_enricher';
|
||||
import { registerRollupSearchStrategy } from './lib/search_strategies';
|
||||
|
||||
export class RollupsServerPlugin implements Plugin<void, void, any, any> {
|
||||
log: Logger;
|
||||
|
||||
constructor(private readonly initializerContext: PluginInitializerContext) {
|
||||
this.log = initializerContext.logger.get();
|
||||
}
|
||||
|
||||
async setup(
|
||||
{ http, elasticsearch: elasticsearchService }: CoreSetup,
|
||||
{
|
||||
__LEGACY: serverShim,
|
||||
usageCollection,
|
||||
metrics,
|
||||
}: {
|
||||
__LEGACY: ServerShim;
|
||||
usageCollection?: UsageCollectionSetup;
|
||||
metrics?: VisTypeTimeseriesSetup;
|
||||
}
|
||||
) {
|
||||
const elasticsearch = await elasticsearchService.adminClient;
|
||||
const router = http.createRouter();
|
||||
const routeDependencies: RouteDependencies = {
|
||||
elasticsearch,
|
||||
elasticsearchService,
|
||||
router,
|
||||
};
|
||||
|
||||
registerLicenseChecker(
|
||||
serverShim as any,
|
||||
PLUGIN.ID,
|
||||
PLUGIN.getI18nName(i18n),
|
||||
PLUGIN.MINIMUM_LICENSE_REQUIRED
|
||||
);
|
||||
|
||||
registerIndicesRoute(routeDependencies, serverShim);
|
||||
registerFieldsForWildcardRoute(routeDependencies, serverShim);
|
||||
registerSearchRoute(routeDependencies, serverShim);
|
||||
registerJobsRoute(routeDependencies, serverShim);
|
||||
|
||||
if (usageCollection) {
|
||||
this.initializerContext.config.legacy.globalConfig$
|
||||
.pipe(first())
|
||||
.toPromise()
|
||||
.then(config => {
|
||||
registerRollupUsageCollector(usageCollection, config.kibana.index);
|
||||
})
|
||||
.catch(e => {
|
||||
this.log.warn(`Registering Rollup collector failed: ${e}`);
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
serverShim.plugins.index_management &&
|
||||
serverShim.plugins.index_management.addIndexManagementDataEnricher
|
||||
) {
|
||||
serverShim.plugins.index_management.addIndexManagementDataEnricher(rollupDataEnricher);
|
||||
}
|
||||
|
||||
if (metrics) {
|
||||
const { addSearchStrategy } = metrics;
|
||||
registerRollupSearchStrategy(routeDependencies, addSearchStrategy);
|
||||
}
|
||||
}
|
||||
|
||||
start() {}
|
||||
|
||||
stop() {}
|
||||
}
|
|
@ -4,14 +4,21 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export const rollupDataEnricher = async (indicesList, callWithRequest) => {
|
||||
interface Index {
|
||||
name: string;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export const rollupDataEnricher = async (indicesList: Index[], callWithRequest: any) => {
|
||||
if (!indicesList || !indicesList.length) {
|
||||
return indicesList;
|
||||
}
|
||||
|
||||
const params = {
|
||||
path: '/_all/_rollup/data',
|
||||
method: 'GET',
|
||||
};
|
||||
|
||||
try {
|
||||
const rollupJobData = await callWithRequest('transport.request', params);
|
||||
return indicesList.map(index => {
|
||||
|
@ -22,7 +29,7 @@ export const rollupDataEnricher = async (indicesList, callWithRequest) => {
|
|||
};
|
||||
});
|
||||
} catch (e) {
|
||||
//swallow exceptions and return original list
|
||||
// swallow exceptions and return original list
|
||||
return indicesList;
|
||||
}
|
||||
};
|
|
@ -1,93 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import Joi from 'joi';
|
||||
import { callWithRequestFactory } from '../../lib/call_with_request_factory';
|
||||
import { isEsErrorFactory } from '../../lib/is_es_error_factory';
|
||||
import { wrapEsError, wrapUnknownError } from '../../lib/error_wrappers';
|
||||
import { licensePreRoutingFactory } from '../../lib/license_pre_routing_factory';
|
||||
import indexBy from 'lodash/collection/indexBy';
|
||||
import { getCapabilitiesForRollupIndices } from '../../lib/map_capabilities';
|
||||
import { mergeCapabilitiesWithFields } from '../../lib/merge_capabilities_with_fields';
|
||||
import querystring from 'querystring';
|
||||
|
||||
/**
|
||||
* Get list of fields for rollup index pattern, in the format of regular index pattern fields
|
||||
*/
|
||||
export function registerFieldsForWildcardRoute(server) {
|
||||
const isEsError = isEsErrorFactory(server);
|
||||
const licensePreRouting = licensePreRoutingFactory(server);
|
||||
|
||||
server.route({
|
||||
path: '/api/index_patterns/rollup/_fields_for_wildcard',
|
||||
method: 'GET',
|
||||
config: {
|
||||
pre: [licensePreRouting],
|
||||
validate: {
|
||||
query: Joi.object()
|
||||
.keys({
|
||||
pattern: Joi.string().required(),
|
||||
meta_fields: Joi.array()
|
||||
.items(Joi.string())
|
||||
.default([]),
|
||||
params: Joi.object()
|
||||
.keys({
|
||||
rollup_index: Joi.string().required(),
|
||||
})
|
||||
.required(),
|
||||
})
|
||||
.default(),
|
||||
},
|
||||
},
|
||||
handler: async request => {
|
||||
const { pattern, meta_fields: metaFields, params } = request.query;
|
||||
|
||||
// Format call to standard index pattern `fields for wildcard`
|
||||
const standardRequestQuery = querystring.stringify({ pattern, meta_fields: metaFields });
|
||||
const standardRequest = {
|
||||
url: `${request.getBasePath()}/api/index_patterns/_fields_for_wildcard?${standardRequestQuery}`,
|
||||
method: 'GET',
|
||||
headers: request.headers,
|
||||
};
|
||||
|
||||
try {
|
||||
// Make call and use field information from response
|
||||
const standardResponse = await server.inject(standardRequest);
|
||||
const fields = standardResponse.result && standardResponse.result.fields;
|
||||
|
||||
const rollupIndex = params.rollup_index;
|
||||
const callWithRequest = callWithRequestFactory(server, request);
|
||||
|
||||
const rollupFields = [];
|
||||
const fieldsFromFieldCapsApi = indexBy(fields, 'name');
|
||||
const rollupIndexCapabilities = getCapabilitiesForRollupIndices(
|
||||
await callWithRequest('rollup.rollupIndexCapabilities', {
|
||||
indexPattern: rollupIndex,
|
||||
})
|
||||
)[rollupIndex].aggs;
|
||||
|
||||
// Keep meta fields
|
||||
metaFields.forEach(
|
||||
field => fieldsFromFieldCapsApi[field] && rollupFields.push(fieldsFromFieldCapsApi[field])
|
||||
);
|
||||
|
||||
const mergedRollupFields = mergeCapabilitiesWithFields(
|
||||
rollupIndexCapabilities,
|
||||
fieldsFromFieldCapsApi,
|
||||
rollupFields
|
||||
);
|
||||
|
||||
return {
|
||||
fields: mergedRollupFields,
|
||||
};
|
||||
} catch (err) {
|
||||
if (isEsError(err)) {
|
||||
return wrapEsError(err);
|
||||
}
|
||||
return wrapUnknownError(err);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
131
x-pack/legacy/plugins/rollup/server/routes/api/index_patterns.ts
Normal file
131
x-pack/legacy/plugins/rollup/server/routes/api/index_patterns.ts
Normal file
|
@ -0,0 +1,131 @@
|
|||
/*
|
||||
* 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 { schema } from '@kbn/config-schema';
|
||||
import { RequestHandler } from 'src/core/server';
|
||||
|
||||
import { indexBy } from 'lodash';
|
||||
import { IndexPatternsFetcher } from '../../shared_imports';
|
||||
import { RouteDependencies, ServerShim } from '../../types';
|
||||
import { callWithRequestFactory } from '../../lib/call_with_request_factory';
|
||||
import { isEsError } from '../../lib/is_es_error';
|
||||
import { licensePreRoutingFactory } from '../../lib/license_pre_routing_factory';
|
||||
import { getCapabilitiesForRollupIndices } from '../../lib/map_capabilities';
|
||||
import { mergeCapabilitiesWithFields, Field } from '../../lib/merge_capabilities_with_fields';
|
||||
|
||||
const parseMetaFields = (metaFields: string | string[]) => {
|
||||
let parsedFields: string[] = [];
|
||||
if (typeof metaFields === 'string') {
|
||||
parsedFields = JSON.parse(metaFields);
|
||||
} else {
|
||||
parsedFields = metaFields;
|
||||
}
|
||||
return parsedFields;
|
||||
};
|
||||
|
||||
const getFieldsForWildcardRequest = async (context: any, request: any, response: any) => {
|
||||
const { callAsCurrentUser } = context.core.elasticsearch.dataClient;
|
||||
const indexPatterns = new IndexPatternsFetcher(callAsCurrentUser);
|
||||
const { pattern, meta_fields: metaFields } = request.query;
|
||||
|
||||
let parsedFields: string[] = [];
|
||||
try {
|
||||
parsedFields = parseMetaFields(metaFields);
|
||||
} catch (error) {
|
||||
return response.badRequest({
|
||||
body: error,
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
const fields = await indexPatterns.getFieldsForWildcard({
|
||||
pattern,
|
||||
metaFields: parsedFields,
|
||||
});
|
||||
|
||||
return response.ok({
|
||||
body: { fields },
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
return response.notFound();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get list of fields for rollup index pattern, in the format of regular index pattern fields
|
||||
*/
|
||||
export function registerFieldsForWildcardRoute(deps: RouteDependencies, legacy: ServerShim) {
|
||||
const handler: RequestHandler<any, any, any> = async (ctx, request, response) => {
|
||||
const { params, meta_fields: metaFields } = request.query;
|
||||
|
||||
try {
|
||||
// Make call and use field information from response
|
||||
const { payload } = await getFieldsForWildcardRequest(ctx, request, response);
|
||||
const fields = payload.fields;
|
||||
const parsedParams = JSON.parse(params);
|
||||
const rollupIndex = parsedParams.rollup_index;
|
||||
const callWithRequest = callWithRequestFactory(deps.elasticsearchService, request);
|
||||
const rollupFields: Field[] = [];
|
||||
const fieldsFromFieldCapsApi: { [key: string]: any } = indexBy(fields, 'name');
|
||||
const rollupIndexCapabilities = getCapabilitiesForRollupIndices(
|
||||
await callWithRequest('rollup.rollupIndexCapabilities', {
|
||||
indexPattern: rollupIndex,
|
||||
})
|
||||
)[rollupIndex].aggs;
|
||||
// Keep meta fields
|
||||
metaFields.forEach(
|
||||
(field: string) =>
|
||||
fieldsFromFieldCapsApi[field] && rollupFields.push(fieldsFromFieldCapsApi[field])
|
||||
);
|
||||
const mergedRollupFields = mergeCapabilitiesWithFields(
|
||||
rollupIndexCapabilities,
|
||||
fieldsFromFieldCapsApi,
|
||||
rollupFields
|
||||
);
|
||||
return response.ok({ body: { fields: mergedRollupFields } });
|
||||
} catch (err) {
|
||||
if (isEsError(err)) {
|
||||
return response.customError({ statusCode: err.statusCode, body: err });
|
||||
}
|
||||
return response.internalError({ body: err });
|
||||
}
|
||||
};
|
||||
|
||||
deps.router.get(
|
||||
{
|
||||
path: '/api/index_patterns/rollup/_fields_for_wildcard',
|
||||
validate: {
|
||||
query: schema.object({
|
||||
pattern: schema.string(),
|
||||
meta_fields: schema.arrayOf(schema.string(), {
|
||||
defaultValue: [],
|
||||
}),
|
||||
params: schema.string({
|
||||
validate(value) {
|
||||
try {
|
||||
const params = JSON.parse(value);
|
||||
const keys = Object.keys(params);
|
||||
const { rollup_index: rollupIndex } = params;
|
||||
|
||||
if (!rollupIndex) {
|
||||
return '[request query.params]: "rollup_index" is required';
|
||||
} else if (keys.length > 1) {
|
||||
const invalidParams = keys.filter(key => key !== 'rollup_index');
|
||||
return `[request query.params]: ${invalidParams.join(', ')} is not allowed`;
|
||||
}
|
||||
} catch (err) {
|
||||
return '[request query.params]: expected JSON string';
|
||||
}
|
||||
},
|
||||
}),
|
||||
}),
|
||||
},
|
||||
},
|
||||
licensePreRoutingFactory(legacy, handler)
|
||||
);
|
||||
}
|
|
@ -1,128 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import { callWithRequestFactory } from '../../lib/call_with_request_factory';
|
||||
import { isEsErrorFactory } from '../../lib/is_es_error_factory';
|
||||
import { wrapEsError, wrapUnknownError } from '../../lib/error_wrappers';
|
||||
import { licensePreRoutingFactory } from '../../lib/license_pre_routing_factory';
|
||||
import { getCapabilitiesForRollupIndices } from '../../lib/map_capabilities';
|
||||
|
||||
function isNumericField(fieldCapability) {
|
||||
const numericTypes = [
|
||||
'long',
|
||||
'integer',
|
||||
'short',
|
||||
'byte',
|
||||
'double',
|
||||
'float',
|
||||
'half_float',
|
||||
'scaled_float',
|
||||
];
|
||||
return numericTypes.some(numericType => fieldCapability[numericType] != null);
|
||||
}
|
||||
|
||||
export function registerIndicesRoute(server) {
|
||||
const isEsError = isEsErrorFactory(server);
|
||||
const licensePreRouting = licensePreRoutingFactory(server);
|
||||
|
||||
/**
|
||||
* Returns a list of all rollup index names
|
||||
*/
|
||||
server.route({
|
||||
path: '/api/rollup/indices',
|
||||
method: 'GET',
|
||||
config: {
|
||||
pre: [licensePreRouting],
|
||||
},
|
||||
handler: async request => {
|
||||
const callWithRequest = callWithRequestFactory(server, request);
|
||||
try {
|
||||
const data = await callWithRequest('rollup.rollupIndexCapabilities', {
|
||||
indexPattern: '_all',
|
||||
});
|
||||
return getCapabilitiesForRollupIndices(data);
|
||||
} catch (err) {
|
||||
if (isEsError(err)) {
|
||||
return wrapEsError(err);
|
||||
}
|
||||
return wrapUnknownError(err);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Returns information on validity of an index pattern for creating a rollup job:
|
||||
* - Does the index pattern match any indices?
|
||||
* - Does the index pattern match rollup indices?
|
||||
* - Which date fields, numeric fields, and keyword fields are available in the matching indices?
|
||||
*/
|
||||
server.route({
|
||||
path: '/api/rollup/index_pattern_validity/{indexPattern}',
|
||||
method: 'GET',
|
||||
config: {
|
||||
pre: [licensePreRouting],
|
||||
},
|
||||
handler: async request => {
|
||||
const callWithRequest = callWithRequestFactory(server, request);
|
||||
|
||||
try {
|
||||
const { indexPattern } = request.params;
|
||||
const [fieldCapabilities, rollupIndexCapabilities] = await Promise.all([
|
||||
callWithRequest('rollup.fieldCapabilities', { indexPattern }),
|
||||
callWithRequest('rollup.rollupIndexCapabilities', { indexPattern }),
|
||||
]);
|
||||
|
||||
const doesMatchIndices = Object.entries(fieldCapabilities.fields).length !== 0;
|
||||
const doesMatchRollupIndices = Object.entries(rollupIndexCapabilities).length !== 0;
|
||||
|
||||
const dateFields = [];
|
||||
const numericFields = [];
|
||||
const keywordFields = [];
|
||||
|
||||
const fieldCapabilitiesEntries = Object.entries(fieldCapabilities.fields);
|
||||
fieldCapabilitiesEntries.forEach(([fieldName, fieldCapability]) => {
|
||||
if (fieldCapability.date) {
|
||||
dateFields.push(fieldName);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isNumericField(fieldCapability)) {
|
||||
numericFields.push(fieldName);
|
||||
return;
|
||||
}
|
||||
|
||||
if (fieldCapability.keyword) {
|
||||
keywordFields.push(fieldName);
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
doesMatchIndices,
|
||||
doesMatchRollupIndices,
|
||||
dateFields,
|
||||
numericFields,
|
||||
keywordFields,
|
||||
};
|
||||
} catch (err) {
|
||||
// 404s are still valid results.
|
||||
if (err.statusCode === 404) {
|
||||
return {
|
||||
doesMatchIndices: false,
|
||||
doesMatchRollupIndices: false,
|
||||
dateFields: [],
|
||||
numericFields: [],
|
||||
keywordFields: [],
|
||||
};
|
||||
}
|
||||
|
||||
if (isEsError(err)) {
|
||||
return wrapEsError(err);
|
||||
}
|
||||
|
||||
return wrapUnknownError(err);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
175
x-pack/legacy/plugins/rollup/server/routes/api/indices.ts
Normal file
175
x-pack/legacy/plugins/rollup/server/routes/api/indices.ts
Normal file
|
@ -0,0 +1,175 @@
|
|||
/*
|
||||
* 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 { schema } from '@kbn/config-schema';
|
||||
import { RequestHandler } from 'src/core/server';
|
||||
import { callWithRequestFactory } from '../../lib/call_with_request_factory';
|
||||
import { isEsError } from '../../lib/is_es_error';
|
||||
import { licensePreRoutingFactory } from '../../lib/license_pre_routing_factory';
|
||||
import { getCapabilitiesForRollupIndices } from '../../lib/map_capabilities';
|
||||
import { API_BASE_PATH } from '../../../common';
|
||||
import { RouteDependencies, ServerShim } from '../../types';
|
||||
|
||||
type NumericField =
|
||||
| 'long'
|
||||
| 'integer'
|
||||
| 'short'
|
||||
| 'byte'
|
||||
| 'scaled_float'
|
||||
| 'double'
|
||||
| 'float'
|
||||
| 'half_float';
|
||||
|
||||
interface FieldCapability {
|
||||
date?: any;
|
||||
keyword?: any;
|
||||
long?: any;
|
||||
integer?: any;
|
||||
short?: any;
|
||||
byte?: any;
|
||||
double?: any;
|
||||
float?: any;
|
||||
half_float?: any;
|
||||
scaled_float?: any;
|
||||
}
|
||||
|
||||
interface FieldCapabilities {
|
||||
fields: FieldCapability[];
|
||||
}
|
||||
|
||||
function isNumericField(fieldCapability: FieldCapability) {
|
||||
const numericTypes = [
|
||||
'long',
|
||||
'integer',
|
||||
'short',
|
||||
'byte',
|
||||
'double',
|
||||
'float',
|
||||
'half_float',
|
||||
'scaled_float',
|
||||
];
|
||||
return numericTypes.some(numericType => fieldCapability[numericType as NumericField] != null);
|
||||
}
|
||||
|
||||
export function registerIndicesRoute(deps: RouteDependencies, legacy: ServerShim) {
|
||||
const getIndicesHandler: RequestHandler<any, any, any> = async (ctx, request, response) => {
|
||||
const callWithRequest = callWithRequestFactory(deps.elasticsearchService, request);
|
||||
|
||||
try {
|
||||
const data = await callWithRequest('rollup.rollupIndexCapabilities', {
|
||||
indexPattern: '_all',
|
||||
});
|
||||
return response.ok({ body: getCapabilitiesForRollupIndices(data) });
|
||||
} catch (err) {
|
||||
if (isEsError(err)) {
|
||||
return response.customError({ statusCode: err.statusCode, body: err });
|
||||
}
|
||||
return response.internalError({ body: err });
|
||||
}
|
||||
};
|
||||
|
||||
const validateIndexPatternHandler: RequestHandler<any, any, any> = async (
|
||||
ctx,
|
||||
request,
|
||||
response
|
||||
) => {
|
||||
const callWithRequest = callWithRequestFactory(deps.elasticsearchService, request);
|
||||
|
||||
try {
|
||||
const { indexPattern } = request.params;
|
||||
const [fieldCapabilities, rollupIndexCapabilities]: [
|
||||
FieldCapabilities,
|
||||
{ [key: string]: any }
|
||||
] = await Promise.all([
|
||||
callWithRequest('rollup.fieldCapabilities', { indexPattern }),
|
||||
callWithRequest('rollup.rollupIndexCapabilities', { indexPattern }),
|
||||
]);
|
||||
|
||||
const doesMatchIndices = Object.entries(fieldCapabilities.fields).length !== 0;
|
||||
const doesMatchRollupIndices = Object.entries(rollupIndexCapabilities).length !== 0;
|
||||
|
||||
const dateFields: string[] = [];
|
||||
const numericFields: string[] = [];
|
||||
const keywordFields: string[] = [];
|
||||
|
||||
const fieldCapabilitiesEntries = Object.entries(fieldCapabilities.fields);
|
||||
|
||||
fieldCapabilitiesEntries.forEach(
|
||||
([fieldName, fieldCapability]: [string, FieldCapability]) => {
|
||||
if (fieldCapability.date) {
|
||||
dateFields.push(fieldName);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isNumericField(fieldCapability)) {
|
||||
numericFields.push(fieldName);
|
||||
return;
|
||||
}
|
||||
|
||||
if (fieldCapability.keyword) {
|
||||
keywordFields.push(fieldName);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const body = {
|
||||
doesMatchIndices,
|
||||
doesMatchRollupIndices,
|
||||
dateFields,
|
||||
numericFields,
|
||||
keywordFields,
|
||||
};
|
||||
|
||||
return response.ok({ body });
|
||||
} catch (err) {
|
||||
// 404s are still valid results.
|
||||
if (err.statusCode === 404) {
|
||||
const notFoundBody = {
|
||||
doesMatchIndices: false,
|
||||
doesMatchRollupIndices: false,
|
||||
dateFields: [],
|
||||
numericFields: [],
|
||||
keywordFields: [],
|
||||
};
|
||||
return response.ok({ body: notFoundBody });
|
||||
}
|
||||
|
||||
if (isEsError(err)) {
|
||||
return response.customError({ statusCode: err.statusCode, body: err });
|
||||
}
|
||||
|
||||
return response.internalError({ body: err });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a list of all rollup index names
|
||||
*/
|
||||
deps.router.get(
|
||||
{
|
||||
path: `${API_BASE_PATH}/indices`,
|
||||
validate: false,
|
||||
},
|
||||
licensePreRoutingFactory(legacy, getIndicesHandler)
|
||||
);
|
||||
|
||||
/**
|
||||
* Returns information on validity of an index pattern for creating a rollup job:
|
||||
* - Does the index pattern match any indices?
|
||||
* - Does the index pattern match rollup indices?
|
||||
* - Which date fields, numeric fields, and keyword fields are available in the matching indices?
|
||||
*/
|
||||
deps.router.get(
|
||||
{
|
||||
path: `${API_BASE_PATH}/index_pattern_validity/{indexPattern}`,
|
||||
validate: {
|
||||
params: schema.object({
|
||||
indexPattern: schema.string(),
|
||||
}),
|
||||
},
|
||||
},
|
||||
licensePreRoutingFactory(legacy, validateIndexPatternHandler)
|
||||
);
|
||||
}
|
|
@ -1,153 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import { callWithRequestFactory } from '../../lib/call_with_request_factory';
|
||||
import { isEsErrorFactory } from '../../lib/is_es_error_factory';
|
||||
import { licensePreRoutingFactory } from '../../lib/license_pre_routing_factory';
|
||||
import { wrapEsError, wrapUnknownError } from '../../lib/error_wrappers';
|
||||
|
||||
export function registerJobsRoute(server) {
|
||||
const isEsError = isEsErrorFactory(server);
|
||||
const licensePreRouting = licensePreRoutingFactory(server);
|
||||
|
||||
server.route({
|
||||
path: '/api/rollup/jobs',
|
||||
method: 'GET',
|
||||
config: {
|
||||
pre: [licensePreRouting],
|
||||
},
|
||||
handler: async request => {
|
||||
try {
|
||||
const callWithRequest = callWithRequestFactory(server, request);
|
||||
return await callWithRequest('rollup.jobs');
|
||||
} catch (err) {
|
||||
if (isEsError(err)) {
|
||||
return wrapEsError(err);
|
||||
}
|
||||
|
||||
return wrapUnknownError(err);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
server.route({
|
||||
path: '/api/rollup/create',
|
||||
method: 'PUT',
|
||||
config: {
|
||||
pre: [licensePreRouting],
|
||||
},
|
||||
handler: async request => {
|
||||
try {
|
||||
const { id, ...rest } = request.payload.job;
|
||||
|
||||
const callWithRequest = callWithRequestFactory(server, request);
|
||||
|
||||
// Create job.
|
||||
await callWithRequest('rollup.createJob', {
|
||||
id,
|
||||
body: rest,
|
||||
});
|
||||
|
||||
// Then request the newly created job.
|
||||
const results = await callWithRequest('rollup.job', { id });
|
||||
return results.jobs[0];
|
||||
} catch (err) {
|
||||
if (isEsError(err)) {
|
||||
return wrapEsError(err);
|
||||
}
|
||||
|
||||
return wrapUnknownError(err);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
server.route({
|
||||
path: '/api/rollup/start',
|
||||
method: 'POST',
|
||||
config: {
|
||||
pre: [licensePreRouting],
|
||||
},
|
||||
handler: async request => {
|
||||
try {
|
||||
const { jobIds } = request.payload;
|
||||
|
||||
const callWithRequest = callWithRequestFactory(server, request);
|
||||
return await Promise.all(
|
||||
jobIds.map(id => callWithRequest('rollup.startJob', { id }))
|
||||
).then(() => ({ success: true }));
|
||||
} catch (err) {
|
||||
if (isEsError(err)) {
|
||||
return wrapEsError(err);
|
||||
}
|
||||
|
||||
return wrapUnknownError(err);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
server.route({
|
||||
path: '/api/rollup/stop',
|
||||
method: 'POST',
|
||||
config: {
|
||||
pre: [licensePreRouting],
|
||||
},
|
||||
handler: async request => {
|
||||
try {
|
||||
const { jobIds } = request.payload;
|
||||
// For our API integration tests we need to wait for the jobs to be stopped
|
||||
// in order to be able to delete them sequencially.
|
||||
const { waitForCompletion } = request.query;
|
||||
const callWithRequest = callWithRequestFactory(server, request);
|
||||
|
||||
const stopRollupJob = id =>
|
||||
callWithRequest('rollup.stopJob', {
|
||||
id,
|
||||
waitForCompletion: waitForCompletion === 'true',
|
||||
});
|
||||
|
||||
return await Promise.all(jobIds.map(stopRollupJob)).then(() => ({ success: true }));
|
||||
} catch (err) {
|
||||
if (isEsError(err)) {
|
||||
return wrapEsError(err);
|
||||
}
|
||||
|
||||
return wrapUnknownError(err);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
server.route({
|
||||
path: '/api/rollup/delete',
|
||||
method: 'POST',
|
||||
config: {
|
||||
pre: [licensePreRouting],
|
||||
},
|
||||
handler: async request => {
|
||||
try {
|
||||
const { jobIds } = request.payload;
|
||||
|
||||
const callWithRequest = callWithRequestFactory(server, request);
|
||||
return await Promise.all(
|
||||
jobIds.map(id => callWithRequest('rollup.deleteJob', { id }))
|
||||
).then(() => ({ success: true }));
|
||||
} catch (err) {
|
||||
// There is an issue opened on ES to handle the following error correctly
|
||||
// https://github.com/elastic/elasticsearch/issues/42908
|
||||
// Until then we'll modify the response here.
|
||||
if (err.response && err.response.includes('Job must be [STOPPED] before deletion')) {
|
||||
err.status = 400;
|
||||
err.statusCode = 400;
|
||||
err.displayName = 'Bad request';
|
||||
err.message = JSON.parse(err.response).task_failures[0].reason.reason;
|
||||
}
|
||||
if (isEsError(err)) {
|
||||
throw wrapEsError(err);
|
||||
}
|
||||
|
||||
throw wrapUnknownError(err);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
178
x-pack/legacy/plugins/rollup/server/routes/api/jobs.ts
Normal file
178
x-pack/legacy/plugins/rollup/server/routes/api/jobs.ts
Normal file
|
@ -0,0 +1,178 @@
|
|||
/*
|
||||
* 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 { schema } from '@kbn/config-schema';
|
||||
import { RequestHandler } from 'src/core/server';
|
||||
import { callWithRequestFactory } from '../../lib/call_with_request_factory';
|
||||
import { isEsError } from '../../lib/is_es_error';
|
||||
import { licensePreRoutingFactory } from '../../lib/license_pre_routing_factory';
|
||||
import { API_BASE_PATH } from '../../../common';
|
||||
import { RouteDependencies, ServerShim } from '../../types';
|
||||
|
||||
export function registerJobsRoute(deps: RouteDependencies, legacy: ServerShim) {
|
||||
const getJobsHandler: RequestHandler<any, any, any> = async (ctx, request, response) => {
|
||||
const callWithRequest = callWithRequestFactory(deps.elasticsearchService, request);
|
||||
|
||||
try {
|
||||
const data = await callWithRequest('rollup.jobs');
|
||||
return response.ok({ body: data });
|
||||
} catch (err) {
|
||||
if (isEsError(err)) {
|
||||
return response.customError({ statusCode: err.statusCode, body: err });
|
||||
}
|
||||
return response.internalError({ body: err });
|
||||
}
|
||||
};
|
||||
|
||||
const createJobsHandler: RequestHandler<any, any, any> = async (ctx, request, response) => {
|
||||
try {
|
||||
const { id, ...rest } = request.body.job;
|
||||
const callWithRequest = callWithRequestFactory(deps.elasticsearchService, request);
|
||||
// Create job.
|
||||
await callWithRequest('rollup.createJob', {
|
||||
id,
|
||||
body: rest,
|
||||
});
|
||||
// Then request the newly created job.
|
||||
const results = await callWithRequest('rollup.job', { id });
|
||||
return response.ok({ body: results.jobs[0] });
|
||||
} catch (err) {
|
||||
if (isEsError(err)) {
|
||||
return response.customError({ statusCode: err.statusCode, body: err });
|
||||
}
|
||||
return response.internalError({ body: err });
|
||||
}
|
||||
};
|
||||
|
||||
const startJobsHandler: RequestHandler<any, any, any> = async (ctx, request, response) => {
|
||||
try {
|
||||
const { jobIds } = request.body;
|
||||
const callWithRequest = callWithRequestFactory(deps.elasticsearchService, request);
|
||||
|
||||
const data = await Promise.all(
|
||||
jobIds.map((id: string) => callWithRequest('rollup.startJob', { id }))
|
||||
).then(() => ({ success: true }));
|
||||
return response.ok({ body: data });
|
||||
} catch (err) {
|
||||
if (isEsError(err)) {
|
||||
return response.customError({ statusCode: err.statusCode, body: err });
|
||||
}
|
||||
return response.internalError({ body: err });
|
||||
}
|
||||
};
|
||||
|
||||
const stopJobsHandler: RequestHandler<any, any, any> = async (ctx, request, response) => {
|
||||
try {
|
||||
const { jobIds } = request.body;
|
||||
// For our API integration tests we need to wait for the jobs to be stopped
|
||||
// in order to be able to delete them sequencially.
|
||||
const { waitForCompletion } = request.query;
|
||||
const callWithRequest = callWithRequestFactory(deps.elasticsearchService, request);
|
||||
const stopRollupJob = (id: string) =>
|
||||
callWithRequest('rollup.stopJob', {
|
||||
id,
|
||||
waitForCompletion: waitForCompletion === 'true',
|
||||
});
|
||||
const data = await Promise.all(jobIds.map(stopRollupJob)).then(() => ({ success: true }));
|
||||
return response.ok({ body: data });
|
||||
} catch (err) {
|
||||
if (isEsError(err)) {
|
||||
return response.customError({ statusCode: err.statusCode, body: err });
|
||||
}
|
||||
return response.internalError({ body: err });
|
||||
}
|
||||
};
|
||||
|
||||
const deleteJobsHandler: RequestHandler<any, any, any> = async (ctx, request, response) => {
|
||||
try {
|
||||
const { jobIds } = request.body;
|
||||
const callWithRequest = callWithRequestFactory(deps.elasticsearchService, request);
|
||||
const data = await Promise.all(
|
||||
jobIds.map((id: string) => callWithRequest('rollup.deleteJob', { id }))
|
||||
).then(() => ({ success: true }));
|
||||
return response.ok({ body: data });
|
||||
} catch (err) {
|
||||
// There is an issue opened on ES to handle the following error correctly
|
||||
// https://github.com/elastic/elasticsearch/issues/42908
|
||||
// Until then we'll modify the response here.
|
||||
if (err.response && err.response.includes('Job must be [STOPPED] before deletion')) {
|
||||
err.status = 400;
|
||||
err.statusCode = 400;
|
||||
err.displayName = 'Bad request';
|
||||
err.message = JSON.parse(err.response).task_failures[0].reason.reason;
|
||||
}
|
||||
if (isEsError(err)) {
|
||||
return response.customError({ statusCode: err.statusCode, body: err });
|
||||
}
|
||||
return response.internalError({ body: err });
|
||||
}
|
||||
};
|
||||
|
||||
deps.router.get(
|
||||
{
|
||||
path: `${API_BASE_PATH}/jobs`,
|
||||
validate: false,
|
||||
},
|
||||
licensePreRoutingFactory(legacy, getJobsHandler)
|
||||
);
|
||||
|
||||
deps.router.put(
|
||||
{
|
||||
path: `${API_BASE_PATH}/create`,
|
||||
validate: {
|
||||
body: schema.object({
|
||||
job: schema.object(
|
||||
{
|
||||
id: schema.string(),
|
||||
},
|
||||
{ allowUnknowns: true }
|
||||
),
|
||||
}),
|
||||
},
|
||||
},
|
||||
licensePreRoutingFactory(legacy, createJobsHandler)
|
||||
);
|
||||
|
||||
deps.router.post(
|
||||
{
|
||||
path: `${API_BASE_PATH}/start`,
|
||||
validate: {
|
||||
body: schema.object({
|
||||
jobIds: schema.arrayOf(schema.string()),
|
||||
}),
|
||||
query: schema.maybe(
|
||||
schema.object({
|
||||
waitForCompletion: schema.maybe(schema.string()),
|
||||
})
|
||||
),
|
||||
},
|
||||
},
|
||||
licensePreRoutingFactory(legacy, startJobsHandler)
|
||||
);
|
||||
|
||||
deps.router.post(
|
||||
{
|
||||
path: `${API_BASE_PATH}/stop`,
|
||||
validate: {
|
||||
body: schema.object({
|
||||
jobIds: schema.arrayOf(schema.string()),
|
||||
}),
|
||||
},
|
||||
},
|
||||
licensePreRoutingFactory(legacy, stopJobsHandler)
|
||||
);
|
||||
|
||||
deps.router.post(
|
||||
{
|
||||
path: `${API_BASE_PATH}/delete`,
|
||||
validate: {
|
||||
body: schema.object({
|
||||
jobIds: schema.arrayOf(schema.string()),
|
||||
}),
|
||||
},
|
||||
},
|
||||
licensePreRoutingFactory(legacy, deleteJobsHandler)
|
||||
);
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { callWithRequestFactory } from '../../lib/call_with_request_factory';
|
||||
import { isEsErrorFactory } from '../../lib/is_es_error_factory';
|
||||
import { licensePreRoutingFactory } from '../../lib/license_pre_routing_factory';
|
||||
import { wrapEsError, wrapUnknownError } from '../../lib/error_wrappers';
|
||||
|
||||
export function registerSearchRoute(server) {
|
||||
const isEsError = isEsErrorFactory(server);
|
||||
const licensePreRouting = licensePreRoutingFactory(server);
|
||||
|
||||
server.route({
|
||||
path: '/api/rollup/search',
|
||||
method: 'POST',
|
||||
config: {
|
||||
pre: [licensePreRouting],
|
||||
},
|
||||
handler: async request => {
|
||||
const callWithRequest = callWithRequestFactory(server, request);
|
||||
|
||||
try {
|
||||
const requests = request.payload.map(({ index, query }) =>
|
||||
callWithRequest('rollup.search', {
|
||||
index,
|
||||
rest_total_hits_as_int: true,
|
||||
body: query,
|
||||
})
|
||||
);
|
||||
|
||||
return await Promise.all(requests);
|
||||
} catch (err) {
|
||||
if (isEsError(err)) {
|
||||
return wrapEsError(err);
|
||||
}
|
||||
|
||||
return wrapUnknownError(err);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
50
x-pack/legacy/plugins/rollup/server/routes/api/search.ts
Normal file
50
x-pack/legacy/plugins/rollup/server/routes/api/search.ts
Normal file
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* 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 { schema } from '@kbn/config-schema';
|
||||
import { RequestHandler } from 'src/core/server';
|
||||
import { callWithRequestFactory } from '../../lib/call_with_request_factory';
|
||||
import { isEsError } from '../../lib/is_es_error';
|
||||
import { licensePreRoutingFactory } from '../../lib/license_pre_routing_factory';
|
||||
import { API_BASE_PATH } from '../../../common';
|
||||
import { RouteDependencies, ServerShim } from '../../types';
|
||||
|
||||
export function registerSearchRoute(deps: RouteDependencies, legacy: ServerShim) {
|
||||
const handler: RequestHandler<any, any, any> = async (ctx, request, response) => {
|
||||
const callWithRequest = callWithRequestFactory(deps.elasticsearchService, request);
|
||||
try {
|
||||
const requests = request.body.map(({ index, query }: { index: string; query: any }) =>
|
||||
callWithRequest('rollup.search', {
|
||||
index,
|
||||
rest_total_hits_as_int: true,
|
||||
body: query,
|
||||
})
|
||||
);
|
||||
const data = await Promise.all(requests);
|
||||
return response.ok({ body: data });
|
||||
} catch (err) {
|
||||
if (isEsError(err)) {
|
||||
return response.customError({ statusCode: err.statusCode, body: err });
|
||||
}
|
||||
return response.internalError({ body: err });
|
||||
}
|
||||
};
|
||||
|
||||
deps.router.post(
|
||||
{
|
||||
path: `${API_BASE_PATH}/search`,
|
||||
validate: {
|
||||
body: schema.arrayOf(
|
||||
schema.object({
|
||||
index: schema.string(),
|
||||
query: schema.any(),
|
||||
})
|
||||
),
|
||||
},
|
||||
},
|
||||
licensePreRoutingFactory(legacy, handler)
|
||||
);
|
||||
}
|
|
@ -4,4 +4,4 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export { registerLicenseChecker } from './register_license_checker';
|
||||
export { IndexPatternsFetcher } from '../../../../../src/plugins/data/server';
|
22
x-pack/legacy/plugins/rollup/server/types.ts
Normal file
22
x-pack/legacy/plugins/rollup/server/types.ts
Normal file
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* 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 { IRouter, ElasticsearchServiceSetup, IClusterClient } from 'src/core/server';
|
||||
import { XPackMainPlugin } from '../../xpack_main/server/xpack_main';
|
||||
|
||||
export interface ServerShim {
|
||||
plugins: {
|
||||
xpack_main: XPackMainPlugin;
|
||||
rollup: any;
|
||||
index_management: any;
|
||||
};
|
||||
}
|
||||
|
||||
export interface RouteDependencies {
|
||||
router: IRouter;
|
||||
elasticsearchService: ElasticsearchServiceSetup;
|
||||
elasticsearch: IClusterClient;
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export { registerRollupUsageCollector } from './collector';
|
6
x-pack/plugins/rollup/kibana.json
Normal file
6
x-pack/plugins/rollup/kibana.json
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"id": "rollup",
|
||||
"version": "8.0.0",
|
||||
"kibanaVersion": "kibana",
|
||||
"server": true
|
||||
}
|
12
x-pack/plugins/rollup/server/index.ts
Normal file
12
x-pack/plugins/rollup/server/index.ts
Normal file
|
@ -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;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { PluginInitializerContext } from 'src/core/server';
|
||||
import { RollupPlugin } from './plugin';
|
||||
|
||||
export const plugin = (initContext: PluginInitializerContext) => new RollupPlugin(initContext);
|
||||
|
||||
export { RollupSetup } from './plugin';
|
35
x-pack/plugins/rollup/server/plugin.ts
Normal file
35
x-pack/plugins/rollup/server/plugin.ts
Normal file
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* 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 { Plugin, PluginInitializerContext } from 'src/core/server';
|
||||
|
||||
export class RollupPlugin implements Plugin<RollupSetup> {
|
||||
private readonly initContext: PluginInitializerContext;
|
||||
|
||||
constructor(initContext: PluginInitializerContext) {
|
||||
this.initContext = initContext;
|
||||
}
|
||||
|
||||
public setup() {
|
||||
return {
|
||||
__legacy: {
|
||||
config: this.initContext.config,
|
||||
logger: this.initContext.logger,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public start() {}
|
||||
public stop() {}
|
||||
}
|
||||
|
||||
export interface RollupSetup {
|
||||
/** @deprecated */
|
||||
__legacy: {
|
||||
config: PluginInitializerContext['config'];
|
||||
logger: PluginInitializerContext['logger'];
|
||||
};
|
||||
}
|
|
@ -10253,9 +10253,6 @@
|
|||
"xpack.reporting.shareContextMenu.csvReportsButtonLabel": "CSV レポート",
|
||||
"xpack.reporting.shareContextMenu.pdfReportsButtonLabel": "PDF レポート",
|
||||
"xpack.rollupJobs.appTitle": "ロールアップジョブ",
|
||||
"xpack.rollupJobs.checkLicense.errorExpiredMessage": "{licenseType} ライセンスが期限切れのため {pluginName} を使用できません",
|
||||
"xpack.rollupJobs.checkLicense.errorUnavailableMessage": "現在ライセンス情報が利用できないため {pluginName} を使用できません。",
|
||||
"xpack.rollupJobs.checkLicense.errorUnsupportedMessage": "ご使用の {licenseType} ライセンスは {pluginName} をサポートしていません。ライセンスをアップグレードしてください。",
|
||||
"xpack.rollupJobs.create.backButton.label": "戻る",
|
||||
"xpack.rollupJobs.create.dateTypeField": "日付",
|
||||
"xpack.rollupJobs.create.errors.dateHistogramFieldMissing": "日付フィールドが必要です。",
|
||||
|
|
|
@ -10252,9 +10252,6 @@
|
|||
"xpack.reporting.shareContextMenu.csvReportsButtonLabel": "CSV 报告",
|
||||
"xpack.reporting.shareContextMenu.pdfReportsButtonLabel": "PDF 报告",
|
||||
"xpack.rollupJobs.appTitle": "汇总/打包作业",
|
||||
"xpack.rollupJobs.checkLicense.errorExpiredMessage": "您不能使用 {pluginName},因为您的 {licenseType} 许可证已过期",
|
||||
"xpack.rollupJobs.checkLicense.errorUnavailableMessage": "您不能使用 {pluginName},因为许可证信息当前不可用。",
|
||||
"xpack.rollupJobs.checkLicense.errorUnsupportedMessage": "您的 {licenseType} 许可证不支持 {pluginName}。请升级您的许可。",
|
||||
"xpack.rollupJobs.create.backButton.label": "上一步",
|
||||
"xpack.rollupJobs.create.dateTypeField": "日期",
|
||||
"xpack.rollupJobs.create.errors.dateHistogramFieldMissing": "“日期”字段必填。",
|
||||
|
|
|
@ -32,28 +32,42 @@ export default function({ getService }) {
|
|||
it('"pattern" is required', async () => {
|
||||
uri = `${BASE_URI}`;
|
||||
({ body } = await supertest.get(uri).expect(400));
|
||||
expect(body.message).to.contain('"pattern" is required');
|
||||
expect(body.message).to.contain(
|
||||
'[request query.pattern]: expected value of type [string]'
|
||||
);
|
||||
});
|
||||
|
||||
it('"params" is required', async () => {
|
||||
params = { pattern: 'foo' };
|
||||
uri = `${BASE_URI}?${querystring.stringify(params)}`;
|
||||
({ body } = await supertest.get(uri).expect(400));
|
||||
expect(body.message).to.contain('"params" is required');
|
||||
expect(body.message).to.contain(
|
||||
'[request query.params]: expected value of type [string]'
|
||||
);
|
||||
});
|
||||
|
||||
it('"params" must be an object', async () => {
|
||||
params = { pattern: 'foo', params: 'bar' };
|
||||
it('"params" must be a valid JSON string', async () => {
|
||||
params = { pattern: 'foo', params: 'foobarbaz' };
|
||||
uri = `${BASE_URI}?${querystring.stringify(params)}`;
|
||||
({ body } = await supertest.get(uri).expect(400));
|
||||
expect(body.message).to.contain('"params" must be an object');
|
||||
expect(body.message).to.contain('[request query.params]: expected JSON string');
|
||||
});
|
||||
|
||||
it('"params" must be an object that only accepts a "rollup_index" property', async () => {
|
||||
params = { pattern: 'foo', params: JSON.stringify({ someProp: 'bar' }) };
|
||||
it('"params" requires a "rollup_index" property', async () => {
|
||||
params = { pattern: 'foo', params: JSON.stringify({}) };
|
||||
uri = `${BASE_URI}?${querystring.stringify(params)}`;
|
||||
({ body } = await supertest.get(uri).expect(400));
|
||||
expect(body.message).to.contain('"someProp" is not allowed');
|
||||
expect(body.message).to.contain('[request query.params]: "rollup_index" is required');
|
||||
});
|
||||
|
||||
it('"params" only accepts a "rollup_index" property', async () => {
|
||||
params = {
|
||||
pattern: 'foo',
|
||||
params: JSON.stringify({ rollup_index: 'my_index', someProp: 'bar' }),
|
||||
};
|
||||
uri = `${BASE_URI}?${querystring.stringify(params)}`;
|
||||
({ body } = await supertest.get(uri).expect(400));
|
||||
expect(body.message).to.contain('[request query.params]: someProp is not allowed');
|
||||
});
|
||||
|
||||
it('"meta_fields" must be an Array', async () => {
|
||||
|
@ -64,7 +78,9 @@ export default function({ getService }) {
|
|||
};
|
||||
uri = `${BASE_URI}?${querystring.stringify(params)}`;
|
||||
({ body } = await supertest.get(uri).expect(400));
|
||||
expect(body.message).to.contain('"meta_fields" must be an array');
|
||||
expect(body.message).to.contain(
|
||||
'[request query.meta_fields]: expected value of type [array]'
|
||||
);
|
||||
});
|
||||
|
||||
it('should return 404 the rollup index to query does not exist', async () => {
|
||||
|
@ -73,7 +89,7 @@ export default function({ getService }) {
|
|||
params: JSON.stringify({ rollup_index: 'bar' }),
|
||||
})}`;
|
||||
({ body } = await supertest.get(uri).expect(404));
|
||||
expect(body.message).to.contain('no such index [bar]');
|
||||
expect(body.message).to.contain('[index_not_found_exception] no such index [bar]');
|
||||
});
|
||||
});
|
||||
|
||||
|
|
Loading…
Reference in a new issue