* Initial move of searchprofiler into new platform directory, lots of things need testing * Whitespace, clean up types and remove unused files * First iteration of end-to-end plugin working - Updated license check to only check for presence of basic license (not search profiler as a feature - Updated the payload: removed types from validation - Also added README in public regarding the location of styles * Added extractProfilerErrorMessage function to interface with new error reporting from profiler endpoint * Fix paths to test_utils * Update I18n for search profiler * Fix react hooks ordering bug with license status updates and fix test (wait for first license object before rendering) * Added index.ts file to common in searchprofiler route Marked types and values as internal Removed unnecessary "async" from function Update import to not use "src" alias Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com> Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
parent
0c6ed2aa14
commit
582faa0eb1
104 changed files with 527 additions and 509 deletions
156
.github/CODEOWNERS
vendored
Normal file
156
.github/CODEOWNERS
vendored
Normal file
|
@ -0,0 +1,156 @@
|
||||||
|
# GitHub CODEOWNERS definition
|
||||||
|
# Identify which groups will be pinged by changes to different parts of the codebase.
|
||||||
|
# For more info, see https://help.github.com/articles/about-codeowners/
|
||||||
|
|
||||||
|
# App
|
||||||
|
/x-pack/legacy/plugins/lens/ @elastic/kibana-app
|
||||||
|
/x-pack/legacy/plugins/graph/ @elastic/kibana-app
|
||||||
|
/src/plugins/share/ @elastic/kibana-app
|
||||||
|
/src/legacy/server/url_shortening/ @elastic/kibana-app
|
||||||
|
/src/legacy/server/sample_data/ @elastic/kibana-app
|
||||||
|
/src/legacy/core_plugins/kibana/public/dashboard/ @elastic/kibana-app
|
||||||
|
/src/legacy/core_plugins/kibana/public/discover/ @elastic/kibana-app
|
||||||
|
/src/legacy/core_plugins/kibana/public/visualize/ @elastic/kibana-app
|
||||||
|
/src/legacy/core_plugins/kibana/public/local_application_service/ @elastic/kibana-app
|
||||||
|
/src/legacy/core_plugins/kibana/public/home/ @elastic/kibana-app
|
||||||
|
/src/legacy/core_plugins/kibana/public/dev_tools/ @elastic/kibana-app
|
||||||
|
/src/legacy/core_plugins/metrics/ @elastic/kibana-app
|
||||||
|
/src/legacy/core_plugins/vis_type_vislib/ @elastic/kibana-app
|
||||||
|
/src/plugins/home/ @elastic/kibana-app
|
||||||
|
/src/plugins/kibana_legacy/ @elastic/kibana-app
|
||||||
|
/src/plugins/timelion/ @elastic/kibana-app
|
||||||
|
/src/plugins/dev_tools/ @elastic/kibana-app
|
||||||
|
|
||||||
|
# App Architecture
|
||||||
|
/src/plugins/data/ @elastic/kibana-app-arch
|
||||||
|
/src/plugins/embeddable/ @elastic/kibana-app-arch
|
||||||
|
/src/plugins/expressions/ @elastic/kibana-app-arch
|
||||||
|
/src/plugins/kibana_react/ @elastic/kibana-app-arch
|
||||||
|
/src/plugins/kibana_utils/ @elastic/kibana-app-arch
|
||||||
|
/src/plugins/navigation/ @elastic/kibana-app-arch
|
||||||
|
/src/plugins/ui_actions/ @elastic/kibana-app-arch
|
||||||
|
/src/plugins/visualizations/ @elastic/kibana-app-arch
|
||||||
|
/x-pack/plugins/advanced_ui_actions/ @elastic/kibana-app-arch
|
||||||
|
/src/legacy/core_plugins/data/ @elastic/kibana-app-arch
|
||||||
|
/src/legacy/core_plugins/elasticsearch/lib/create_proxy.js @elastic/kibana-app-arch
|
||||||
|
/src/legacy/core_plugins/embeddable_api/ @elastic/kibana-app-arch
|
||||||
|
/src/legacy/core_plugins/interpreter/ @elastic/kibana-app-arch
|
||||||
|
/src/legacy/core_plugins/kibana_react/ @elastic/kibana-app-arch
|
||||||
|
/src/legacy/core_plugins/kibana/public/management/ @elastic/kibana-app-arch
|
||||||
|
/src/legacy/core_plugins/kibana/server/field_formats/ @elastic/kibana-app-arch
|
||||||
|
/src/legacy/core_plugins/kibana/server/routes/api/management/ @elastic/kibana-app-arch
|
||||||
|
/src/legacy/core_plugins/kibana/server/routes/api/suggestions/ @elastic/kibana-app-arch
|
||||||
|
/src/legacy/core_plugins/visualizations/ @elastic/kibana-app-arch
|
||||||
|
/src/legacy/server/index_patterns/ @elastic/kibana-app-arch
|
||||||
|
|
||||||
|
# APM
|
||||||
|
/x-pack/legacy/plugins/apm/ @elastic/apm-ui
|
||||||
|
/x-pack/test/functional/apps/apm/ @elastic/apm-ui
|
||||||
|
/src/legacy/core_plugins/apm_oss/ @elastic/apm-ui
|
||||||
|
|
||||||
|
# Beats
|
||||||
|
/x-pack/legacy/plugins/beats_management/ @elastic/beats
|
||||||
|
|
||||||
|
# Canvas
|
||||||
|
/x-pack/legacy/plugins/canvas/ @elastic/kibana-canvas
|
||||||
|
|
||||||
|
# Logs & Metrics UI
|
||||||
|
/x-pack/legacy/plugins/infra/ @elastic/logs-metrics-ui
|
||||||
|
/x-pack/legacy/plugins/integrations_manager/ @elastic/epm
|
||||||
|
|
||||||
|
# Machine Learning
|
||||||
|
/x-pack/legacy/plugins/ml/ @elastic/ml-ui
|
||||||
|
/x-pack/test/functional/apps/machine_learning/ @elastic/ml-ui
|
||||||
|
/x-pack/test/functional/services/machine_learning/ @elastic/ml-ui
|
||||||
|
/x-pack/test/functional/services/ml.ts @elastic/ml-ui
|
||||||
|
# ML team owns the transform plugin, ES team added here for visibility
|
||||||
|
# because the plugin lives in Kibana's Elasticsearch management section.
|
||||||
|
/x-pack/legacy/plugins/transform/ @elastic/ml-ui @elastic/es-ui
|
||||||
|
/x-pack/test/functional/apps/transform/ @elastic/ml-ui
|
||||||
|
/x-pack/test/functional/services/transform_ui/ @elastic/ml-ui
|
||||||
|
/x-pack/test/functional/services/transform.ts @elastic/ml-ui
|
||||||
|
|
||||||
|
# Maps
|
||||||
|
/x-pack/legacy/plugins/maps/ @elastic/kibana-gis
|
||||||
|
/x-pack/test/api_integration/apis/maps/ @elastic/kibana-gis
|
||||||
|
/x-pack/test/functional/apps/maps/ @elastic/kibana-gis
|
||||||
|
/x-pack/test/functional/es_archives/maps/ @elastic/kibana-gis
|
||||||
|
/x-pack/test/visual_regression/tests/maps/index.js @elastic/kibana-gis
|
||||||
|
|
||||||
|
# Operations
|
||||||
|
/src/dev/ @elastic/kibana-operations
|
||||||
|
/src/setup_node_env/ @elastic/kibana-operations
|
||||||
|
/src/optimize/ @elastic/kibana-operations
|
||||||
|
/packages/*eslint*/ @elastic/kibana-operations
|
||||||
|
/packages/*babel*/ @elastic/kibana-operations
|
||||||
|
/packages/kbn-dev-utils*/ @elastic/kibana-operations
|
||||||
|
/packages/kbn-es/ @elastic/kibana-operations
|
||||||
|
/packages/kbn-pm/ @elastic/kibana-operations
|
||||||
|
/packages/kbn-test/ @elastic/kibana-operations
|
||||||
|
/packages/kbn-ui-shared-deps/ @elastic/kibana-operations
|
||||||
|
/src/legacy/server/keystore/ @elastic/kibana-operations
|
||||||
|
/src/legacy/server/pid/ @elastic/kibana-operations
|
||||||
|
/src/legacy/server/sass/ @elastic/kibana-operations
|
||||||
|
/src/legacy/server/utils/ @elastic/kibana-operations
|
||||||
|
/src/legacy/server/warnings/ @elastic/kibana-operations
|
||||||
|
|
||||||
|
# Platform
|
||||||
|
/src/core/ @elastic/kibana-platform
|
||||||
|
/config/kibana.yml @elastic/kibana-platform
|
||||||
|
/x-pack/plugins/features/ @elastic/kibana-platform
|
||||||
|
/x-pack/plugins/licensing/ @elastic/kibana-platform
|
||||||
|
/packages/kbn-config-schema/ @elastic/kibana-platform
|
||||||
|
/src/legacy/server/config/ @elastic/kibana-platform
|
||||||
|
/src/legacy/server/http/ @elastic/kibana-platform
|
||||||
|
/src/legacy/server/i18n/ @elastic/kibana-platform
|
||||||
|
/src/legacy/server/logging/ @elastic/kibana-platform
|
||||||
|
/src/legacy/server/saved_objects/ @elastic/kibana-platform
|
||||||
|
/src/legacy/server/status/ @elastic/kibana-platform
|
||||||
|
|
||||||
|
# Security
|
||||||
|
/src/core/server/csp/ @elastic/kibana-security @elastic/kibana-platform
|
||||||
|
/x-pack/legacy/plugins/security/ @elastic/kibana-security
|
||||||
|
/x-pack/legacy/plugins/spaces/ @elastic/kibana-security
|
||||||
|
/x-pack/plugins/spaces/ @elastic/kibana-security
|
||||||
|
/x-pack/legacy/plugins/encrypted_saved_objects/ @elastic/kibana-security
|
||||||
|
/x-pack/plugins/encrypted_saved_objects/ @elastic/kibana-security
|
||||||
|
/x-pack/plugins/security/ @elastic/kibana-security
|
||||||
|
/x-pack/test/api_integration/apis/security/ @elastic/kibana-security
|
||||||
|
|
||||||
|
# Kibana Localization
|
||||||
|
/src/dev/i18n/ @elastic/kibana-localization
|
||||||
|
|
||||||
|
# Pulse
|
||||||
|
/packages/kbn-analytics/ @elastic/pulse
|
||||||
|
/src/legacy/core_plugins/ui_metric/ @elastic/pulse
|
||||||
|
/src/plugins/usage_collection/ @elastic/pulse
|
||||||
|
/x-pack/legacy/plugins/telemetry/ @elastic/pulse
|
||||||
|
|
||||||
|
# Kibana Alerting Services
|
||||||
|
/x-pack/legacy/plugins/alerting/ @elastic/kibana-alerting-services
|
||||||
|
/x-pack/legacy/plugins/actions/ @elastic/kibana-alerting-services
|
||||||
|
/x-pack/plugins/event_log/ @elastic/kibana-alerting-services
|
||||||
|
/x-pack/plugins/task_manager/ @elastic/kibana-alerting-services
|
||||||
|
/x-pack/test/alerting_api_integration/ @elastic/kibana-alerting-services
|
||||||
|
/x-pack/test/plugin_api_integration/plugins/task_manager/ @elastic/kibana-alerting-services
|
||||||
|
/x-pack/test/plugin_api_integration/test_suites/task_manager/ @elastic/kibana-alerting-services
|
||||||
|
/x-pack/legacy/plugins/triggers_actions_ui/ @elastic/kibana-alerting-services
|
||||||
|
/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/ @elastic/kibana-alerting-services
|
||||||
|
/x-pack/test/functional_with_es_ssl/fixtures/plugins/alerts/ @elastic/kibana-alerting-services
|
||||||
|
|
||||||
|
# Design
|
||||||
|
**/*.scss @elastic/kibana-design
|
||||||
|
|
||||||
|
# Elasticsearch UI
|
||||||
|
/src/legacy/core_plugins/console/ @elastic/es-ui
|
||||||
|
/src/plugins/es_ui_shared/ @elastic/es-ui
|
||||||
|
/x-pack/legacy/plugins/console_extensions/ @elastic/es-ui
|
||||||
|
/x-pack/legacy/plugins/cross_cluster_replication/ @elastic/es-ui
|
||||||
|
/x-pack/legacy/plugins/index_lifecycle_management/ @elastic/es-ui
|
||||||
|
/x-pack/legacy/plugins/index_management/ @elastic/es-ui
|
||||||
|
/x-pack/legacy/plugins/license_management/ @elastic/es-ui
|
||||||
|
/x-pack/legacy/plugins/remote_clusters/ @elastic/es-ui
|
||||||
|
/x-pack/legacy/plugins/rollup/ @elastic/es-ui
|
||||||
|
/x-pack/plugins/searchprofiler/ @elastic/es-ui
|
||||||
|
/x-pack/legacy/plugins/snapshot_restore/ @elastic/es-ui
|
||||||
|
/x-pack/legacy/plugins/watcher/ @elastic/es-ui
|
|
@ -27,9 +27,9 @@
|
||||||
"xpack.main": "legacy/plugins/xpack_main",
|
"xpack.main": "legacy/plugins/xpack_main",
|
||||||
"xpack.monitoring": "legacy/plugins/monitoring",
|
"xpack.monitoring": "legacy/plugins/monitoring",
|
||||||
"xpack.remoteClusters": "legacy/plugins/remote_clusters",
|
"xpack.remoteClusters": "legacy/plugins/remote_clusters",
|
||||||
"xpack.reporting": [ "plugins/reporting", "legacy/plugins/reporting" ],
|
"xpack.reporting": ["plugins/reporting", "legacy/plugins/reporting"],
|
||||||
"xpack.rollupJobs": "legacy/plugins/rollup",
|
"xpack.rollupJobs": "legacy/plugins/rollup",
|
||||||
"xpack.searchProfiler": "legacy/plugins/searchprofiler",
|
"xpack.searchProfiler": "plugins/searchprofiler",
|
||||||
"xpack.siem": "legacy/plugins/siem",
|
"xpack.siem": "legacy/plugins/siem",
|
||||||
"xpack.security": ["legacy/plugins/security", "plugins/security"],
|
"xpack.security": ["legacy/plugins/security", "plugins/security"],
|
||||||
"xpack.server": "legacy/server",
|
"xpack.server": "legacy/server",
|
||||||
|
|
|
@ -1,26 +0,0 @@
|
||||||
# Profiler
|
|
||||||
|
|
||||||
A UI for the query and aggregation profiler in Elasticsearch
|
|
||||||
|
|
||||||
## Development
|
|
||||||
|
|
||||||
Assuming you've checked out x-plugins next to kibana...
|
|
||||||
|
|
||||||
- Run `yarn kbn bootstrap`
|
|
||||||
- Run `yarn start` to watch for and sync files on change
|
|
||||||
- Open a new terminal to run Kibana - use `yarn start` to launch it in dev mode
|
|
||||||
- Kibana will automatically restart as files are synced
|
|
||||||
- If you need debugging output, run `DEBUG=reporting yarn start` instead
|
|
||||||
|
|
||||||
If you have installed this somewhere other than via x-plugins, and next to the kibana repo, you'll need to change the `pathToKibana` setting in `gulpfile.js`
|
|
||||||
|
|
||||||
## Testing
|
|
||||||
|
|
||||||
To run the server tests, change into `x-plugins/kibana` and run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
mocha --debug --compilers js:@babel/register plugins/profiler/**/__tests__/**/*.js
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
--kbnServer.tests_bundle.pluginId
|
|
|
@ -5,12 +5,10 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { resolve } from 'path';
|
import { resolve } from 'path';
|
||||||
import Boom from 'boom';
|
|
||||||
|
|
||||||
import { CoreSetup } from 'src/core/server';
|
// TODO:
|
||||||
import { Server } from 'src/legacy/server/kbn_server';
|
// Until we can process SCSS in new platform, this part of Searchprofiler
|
||||||
import { LegacySetup } from './server/np_ready/types';
|
// legacy must remain here.
|
||||||
import { plugin } from './server/np_ready';
|
|
||||||
|
|
||||||
export const searchprofiler = (kibana: any) => {
|
export const searchprofiler = (kibana: any) => {
|
||||||
const publicSrc = resolve(__dirname, 'public');
|
const publicSrc = resolve(__dirname, 'public');
|
||||||
|
@ -22,43 +20,8 @@ export const searchprofiler = (kibana: any) => {
|
||||||
publicDir: publicSrc,
|
publicDir: publicSrc,
|
||||||
|
|
||||||
uiExports: {
|
uiExports: {
|
||||||
// NP Ready
|
styleSheetPaths: `${publicSrc}/index.scss`,
|
||||||
devTools: [`${publicSrc}/legacy`],
|
|
||||||
styleSheetPaths: `${publicSrc}/np_ready/application/index.scss`,
|
|
||||||
// Legacy
|
|
||||||
home: ['plugins/searchprofiler/register_feature'],
|
|
||||||
},
|
|
||||||
init(server: Server) {
|
|
||||||
const serverPlugin = plugin();
|
|
||||||
const thisPlugin = this;
|
|
||||||
|
|
||||||
const commonRouteConfig = {
|
|
||||||
pre: [
|
|
||||||
function forbidApiAccess() {
|
|
||||||
const licenseCheckResults = server.plugins.xpack_main.info
|
|
||||||
.feature(thisPlugin.id)
|
|
||||||
.getLicenseCheckResults();
|
|
||||||
if (licenseCheckResults.showAppLink && licenseCheckResults.enableAppLink) {
|
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
throw Boom.forbidden(licenseCheckResults.message);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
const legacySetup: LegacySetup = {
|
|
||||||
route: (args: Parameters<typeof server.route>[0]) => server.route(args),
|
|
||||||
plugins: {
|
|
||||||
__LEGACY: {
|
|
||||||
thisPlugin,
|
|
||||||
xpackMain: server.plugins.xpack_main,
|
|
||||||
elasticsearch: server.plugins.elasticsearch,
|
|
||||||
commonRouteConfig,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
serverPlugin.setup({} as CoreSetup, legacySetup);
|
|
||||||
},
|
},
|
||||||
|
init() {},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* eslint-disable @kbn/eslint/no-restricted-paths */
|
|
||||||
import { npSetup } from 'ui/new_platform';
|
|
||||||
import { I18nContext } from 'ui/i18n';
|
|
||||||
// @ts-ignore
|
|
||||||
import { xpackInfo } from 'plugins/xpack_main/services/xpack_info';
|
|
||||||
// @ts-ignore
|
|
||||||
import { formatAngularHttpError } from 'ui/notify/lib';
|
|
||||||
import 'ui/autoload/all';
|
|
||||||
|
|
||||||
import { plugin } from './np_ready';
|
|
||||||
|
|
||||||
const pluginInstance = plugin({} as any);
|
|
||||||
|
|
||||||
pluginInstance.setup(npSetup.core, {
|
|
||||||
...npSetup.plugins,
|
|
||||||
__LEGACY: {
|
|
||||||
I18nContext,
|
|
||||||
licenseEnabled: xpackInfo.get('features.searchprofiler.enableAppLink'),
|
|
||||||
notifications: npSetup.core.notifications.toasts,
|
|
||||||
formatAngularHttpError,
|
|
||||||
},
|
|
||||||
});
|
|
|
@ -1,62 +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';
|
|
||||||
import {
|
|
||||||
Plugin,
|
|
||||||
CoreStart,
|
|
||||||
CoreSetup,
|
|
||||||
PluginInitializerContext,
|
|
||||||
ToastsStart,
|
|
||||||
} from 'src/core/public';
|
|
||||||
|
|
||||||
import { DevToolsSetup } from '../../../../../../src/plugins/dev_tools/public';
|
|
||||||
|
|
||||||
export class SearchProfilerUIPlugin implements Plugin {
|
|
||||||
constructor(ctx: PluginInitializerContext) {}
|
|
||||||
|
|
||||||
async setup(
|
|
||||||
core: CoreSetup,
|
|
||||||
plugins: {
|
|
||||||
__LEGACY: {
|
|
||||||
I18nContext: any;
|
|
||||||
licenseEnabled: boolean;
|
|
||||||
notifications: ToastsStart;
|
|
||||||
formatAngularHttpError: any;
|
|
||||||
};
|
|
||||||
dev_tools: DevToolsSetup;
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
const { http } = core;
|
|
||||||
const {
|
|
||||||
__LEGACY: { I18nContext, licenseEnabled, notifications, formatAngularHttpError },
|
|
||||||
dev_tools,
|
|
||||||
} = plugins;
|
|
||||||
dev_tools.register({
|
|
||||||
id: 'searchprofiler',
|
|
||||||
title: i18n.translate('xpack.searchProfiler.pageDisplayName', {
|
|
||||||
defaultMessage: 'Search Profiler',
|
|
||||||
}),
|
|
||||||
order: 5,
|
|
||||||
enableRouting: false,
|
|
||||||
async mount(ctx, params) {
|
|
||||||
const { boot } = await import('./application/boot');
|
|
||||||
return boot({
|
|
||||||
http,
|
|
||||||
licenseEnabled,
|
|
||||||
el: params.element,
|
|
||||||
I18nContext,
|
|
||||||
notifications,
|
|
||||||
formatAngularHttpError,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async start(core: CoreStart, plugins: any) {}
|
|
||||||
|
|
||||||
async stop() {}
|
|
||||||
}
|
|
|
@ -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 {
|
|
||||||
FeatureCatalogueRegistryProvider,
|
|
||||||
FeatureCatalogueCategory,
|
|
||||||
} from 'ui/registry/feature_catalogue';
|
|
||||||
|
|
||||||
import { i18n } from '@kbn/i18n';
|
|
||||||
|
|
||||||
FeatureCatalogueRegistryProvider.register(() => {
|
|
||||||
return {
|
|
||||||
id: 'searchprofiler',
|
|
||||||
title: i18n.translate('xpack.searchProfiler.registryProviderTitle', {
|
|
||||||
defaultMessage: 'Search Profiler',
|
|
||||||
}),
|
|
||||||
description: i18n.translate('xpack.searchProfiler.registryProviderDescription', {
|
|
||||||
defaultMessage: 'Quickly check the performance of any Elasticsearch query.',
|
|
||||||
}),
|
|
||||||
icon: 'searchProfilerApp',
|
|
||||||
path: '/app/kibana#/dev_tools/searchprofiler',
|
|
||||||
showOnHomePage: false,
|
|
||||||
category: FeatureCatalogueCategory.ADMIN,
|
|
||||||
};
|
|
||||||
});
|
|
|
@ -1,81 +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', () => {
|
|
||||||
let mockLicenseInfo: any;
|
|
||||||
beforeEach(() => (mockLicenseInfo = {}));
|
|
||||||
|
|
||||||
describe('license information is not available', () => {
|
|
||||||
beforeEach(() => (mockLicenseInfo.isAvailable = () => false));
|
|
||||||
|
|
||||||
it('should set showLinks to true', () => {
|
|
||||||
expect(checkLicense(mockLicenseInfo).showAppLink).to.be(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should set enableLinks to false', () => {
|
|
||||||
expect(checkLicense(mockLicenseInfo).enableAppLink).to.be(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
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 showLinks to true', () => {
|
|
||||||
expect(checkLicense(mockLicenseInfo).showAppLink).to.be(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should set enableLinks to true', () => {
|
|
||||||
expect(checkLicense(mockLicenseInfo).enableAppLink).to.be(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('& license is expired', () => {
|
|
||||||
beforeEach(() => set(mockLicenseInfo, 'license.isActive', () => false));
|
|
||||||
|
|
||||||
it('should set showLinks to true', () => {
|
|
||||||
expect(checkLicense(mockLicenseInfo).showAppLink).to.be(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should set enableLinks to false', () => {
|
|
||||||
expect(checkLicense(mockLicenseInfo).enableAppLink).to.be(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('& license is basic', () => {
|
|
||||||
beforeEach(() => set(mockLicenseInfo, 'license.isOneOf', () => false));
|
|
||||||
|
|
||||||
describe('& license is active', () => {
|
|
||||||
beforeEach(() => set(mockLicenseInfo, 'license.isActive', () => true));
|
|
||||||
|
|
||||||
it('should set showLinks to false', () => {
|
|
||||||
expect(checkLicense(mockLicenseInfo).showAppLink).to.be(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('& license is expired', () => {
|
|
||||||
beforeEach(() => set(mockLicenseInfo, 'license.isActive', () => false));
|
|
||||||
|
|
||||||
it('should set showLinks to false', () => {
|
|
||||||
expect(checkLicense(mockLicenseInfo).showAppLink).to.be(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -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 { i18n } from '@kbn/i18n';
|
|
||||||
import { XPackInfo } from '../../../../xpack_main/server/lib/xpack_info';
|
|
||||||
|
|
||||||
export function checkLicense(
|
|
||||||
xpackLicenseInfo: XPackInfo
|
|
||||||
): { showAppLink: boolean; enableAppLink: boolean; message: string | undefined } {
|
|
||||||
if (!xpackLicenseInfo || !xpackLicenseInfo.isAvailable()) {
|
|
||||||
return {
|
|
||||||
showAppLink: true,
|
|
||||||
enableAppLink: false,
|
|
||||||
message: i18n.translate('xpack.searchProfiler.unavailableLicenseInformationMessage', {
|
|
||||||
defaultMessage:
|
|
||||||
'Search Profiler is unavailable - license information is not available at this time.',
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const isLicenseActive = xpackLicenseInfo.license.isActive();
|
|
||||||
let message: string | undefined;
|
|
||||||
if (!isLicenseActive) {
|
|
||||||
message = i18n.translate('xpack.searchProfiler.licenseHasExpiredMessage', {
|
|
||||||
defaultMessage: 'Search Profiler is unavailable - license has expired.',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
xpackLicenseInfo.license.isOneOf([
|
|
||||||
'trial',
|
|
||||||
'basic',
|
|
||||||
'standard',
|
|
||||||
'gold',
|
|
||||||
'platinum',
|
|
||||||
'enterprise',
|
|
||||||
])
|
|
||||||
) {
|
|
||||||
return {
|
|
||||||
showAppLink: true,
|
|
||||||
enableAppLink: isLicenseActive,
|
|
||||||
message,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
message = i18n.translate('xpack.searchProfiler.upgradeLicenseMessage', {
|
|
||||||
defaultMessage:
|
|
||||||
'Search Profiler is unavailable for the current {licenseInfo} license. Please upgrade your license.',
|
|
||||||
values: { licenseInfo: xpackLicenseInfo.license.getType() },
|
|
||||||
});
|
|
||||||
return {
|
|
||||||
showAppLink: false,
|
|
||||||
enableAppLink: false,
|
|
||||||
message,
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,38 +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 { CoreSetup, Plugin } from 'src/core/server';
|
|
||||||
import { LegacySetup } from './types';
|
|
||||||
import { checkLicense } from './lib';
|
|
||||||
// @ts-ignore
|
|
||||||
import { mirrorPluginStatus } from '../../../../server/lib/mirror_plugin_status';
|
|
||||||
|
|
||||||
import * as profileRoute from './routes/profile';
|
|
||||||
|
|
||||||
export class SearchProfilerServerPlugin implements Plugin {
|
|
||||||
async setup(
|
|
||||||
core: CoreSetup,
|
|
||||||
{
|
|
||||||
route,
|
|
||||||
plugins: {
|
|
||||||
__LEGACY: { thisPlugin, elasticsearch, xpackMain, commonRouteConfig },
|
|
||||||
},
|
|
||||||
}: LegacySetup
|
|
||||||
) {
|
|
||||||
mirrorPluginStatus(xpackMain, thisPlugin);
|
|
||||||
(xpackMain as any).status.once('green', () => {
|
|
||||||
// Register a function that is called whenever the xpack info changes,
|
|
||||||
// to re-compute the license check results for this plugin
|
|
||||||
xpackMain.info.feature(thisPlugin.id).registerLicenseCheckResultsGenerator(checkLicense);
|
|
||||||
});
|
|
||||||
|
|
||||||
profileRoute.register({ elasticsearch }, route, commonRouteConfig);
|
|
||||||
}
|
|
||||||
|
|
||||||
async start() {}
|
|
||||||
|
|
||||||
stop(): void {}
|
|
||||||
}
|
|
|
@ -1,53 +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 { RequestShim, ServerShim, RegisterRoute } from '../types';
|
|
||||||
|
|
||||||
export const handler = async (server: ServerShim, request: RequestShim) => {
|
|
||||||
const { callWithRequest } = server.elasticsearch.getCluster('data');
|
|
||||||
let parsed = request.payload.query;
|
|
||||||
parsed.profile = true;
|
|
||||||
parsed = JSON.stringify(parsed, null, 2);
|
|
||||||
|
|
||||||
const body = {
|
|
||||||
index: request.payload.index,
|
|
||||||
body: parsed,
|
|
||||||
};
|
|
||||||
try {
|
|
||||||
const resp = await callWithRequest(request, 'search', body);
|
|
||||||
return {
|
|
||||||
ok: true,
|
|
||||||
resp,
|
|
||||||
};
|
|
||||||
} catch (err) {
|
|
||||||
return {
|
|
||||||
ok: false,
|
|
||||||
err,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const register = (server: ServerShim, route: RegisterRoute, commonConfig: any) => {
|
|
||||||
route({
|
|
||||||
path: '/api/searchprofiler/profile',
|
|
||||||
method: 'POST',
|
|
||||||
config: {
|
|
||||||
...commonConfig,
|
|
||||||
validate: {
|
|
||||||
payload: Joi.object()
|
|
||||||
.keys({
|
|
||||||
query: Joi.object().required(),
|
|
||||||
index: Joi.string().required(),
|
|
||||||
type: Joi.string().optional(),
|
|
||||||
})
|
|
||||||
.required(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
handler: req => {
|
|
||||||
return handler(server, { headers: req.headers, payload: req.payload as any });
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
|
@ -1,33 +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 { ServerRoute } from 'hapi';
|
|
||||||
import { ElasticsearchPlugin, Request } from 'src/legacy/core_plugins/elasticsearch';
|
|
||||||
import { XPackMainPlugin } from '../../../xpack_main/server/xpack_main';
|
|
||||||
|
|
||||||
export type RegisterRoute = (args: ServerRoute & { config: any }) => void;
|
|
||||||
|
|
||||||
export interface LegacyPlugins {
|
|
||||||
__LEGACY: {
|
|
||||||
thisPlugin: any;
|
|
||||||
elasticsearch: ElasticsearchPlugin;
|
|
||||||
xpackMain: XPackMainPlugin;
|
|
||||||
commonRouteConfig: any;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface LegacySetup {
|
|
||||||
route: RegisterRoute;
|
|
||||||
plugins: LegacyPlugins;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ServerShim {
|
|
||||||
elasticsearch: ElasticsearchPlugin;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RequestShim extends Request {
|
|
||||||
payload: any;
|
|
||||||
}
|
|
14
x-pack/plugins/searchprofiler/common/constants.ts
Normal file
14
x-pack/plugins/searchprofiler/common/constants.ts
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
/*
|
||||||
|
* 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 { LicenseType } from '../../licensing/common/types';
|
||||||
|
|
||||||
|
const basicLicense: LicenseType = 'basic';
|
||||||
|
|
||||||
|
/** @internal */
|
||||||
|
export const PLUGIN = Object.freeze({
|
||||||
|
id: 'searchprofiler',
|
||||||
|
minimumLicenseType: basicLicense,
|
||||||
|
});
|
|
@ -4,4 +4,6 @@
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export { checkLicense } from './check_license';
|
export { PLUGIN } from './constants';
|
||||||
|
|
||||||
|
export { LicenseStatus } from './types';
|
|
@ -3,8 +3,9 @@
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
* or more contributor license agreements. Licensed under the Elastic License;
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
*/
|
*/
|
||||||
import { SearchProfilerServerPlugin } from './plugin';
|
|
||||||
|
|
||||||
export const plugin = () => {
|
/** @internal */
|
||||||
return new SearchProfilerServerPlugin();
|
export interface LicenseStatus {
|
||||||
};
|
valid: boolean;
|
||||||
|
message?: string;
|
||||||
|
}
|
8
x-pack/plugins/searchprofiler/kibana.json
Normal file
8
x-pack/plugins/searchprofiler/kibana.json
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"id": "searchprofiler",
|
||||||
|
"version": "8.0.0",
|
||||||
|
"kibanaVersion": "kibana",
|
||||||
|
"requiredPlugins": ["dev_tools", "home", "licensing"],
|
||||||
|
"server": true,
|
||||||
|
"ui": true
|
||||||
|
}
|
3
x-pack/plugins/searchprofiler/public/README.md
Normal file
3
x-pack/plugins/searchprofiler/public/README.md
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
## Please note
|
||||||
|
|
||||||
|
See x-pack/legacy/plugins/searchprofiler/public for styles.
|
|
@ -4,17 +4,18 @@
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
*/
|
*/
|
||||||
import { render, unmountComponentAtNode } from 'react-dom';
|
import { render, unmountComponentAtNode } from 'react-dom';
|
||||||
|
import { HttpStart as Http, ToastsSetup } from 'kibana/public';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { HttpStart as Http, ToastsSetup } from 'src/core/public';
|
|
||||||
|
import { LicenseStatus } from '../../common';
|
||||||
import { App } from '.';
|
import { App } from '.';
|
||||||
|
|
||||||
export interface Dependencies {
|
export interface Dependencies {
|
||||||
el: HTMLElement;
|
el: HTMLElement;
|
||||||
http: Http;
|
http: Http;
|
||||||
licenseEnabled: boolean;
|
|
||||||
I18nContext: any;
|
I18nContext: any;
|
||||||
notifications: ToastsSetup;
|
notifications: ToastsSetup;
|
||||||
formatAngularHttpError: any;
|
initialLicenseStatus: LicenseStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AppDependencies = Omit<Dependencies, 'el'>;
|
export type AppDependencies = Omit<Dependencies, 'el'>;
|
|
@ -4,7 +4,7 @@
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { registerTestBed } from '../../../../../../../../test_utils';
|
import { registerTestBed } from '../../../../../../test_utils';
|
||||||
import { HighlightDetailsFlyout, Props } from '.';
|
import { HighlightDetailsFlyout, Props } from '.';
|
||||||
|
|
||||||
describe('Highlight Details Flyout', () => {
|
describe('Highlight Details Flyout', () => {
|
|
@ -4,7 +4,7 @@
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { registerTestBed } from '../../../../../../../test_utils';
|
import { registerTestBed } from '../../../../../test_utils';
|
||||||
|
|
||||||
import { LicenseWarningNotice } from './license_warning_notice';
|
import { LicenseWarningNotice } from './license_warning_notice';
|
||||||
|
|
|
@ -36,7 +36,7 @@ export const LicenseWarningNotice = () => {
|
||||||
title={i18n.translate('xpack.searchProfiler.licenseErrorMessageTitle', {
|
title={i18n.translate('xpack.searchProfiler.licenseErrorMessageTitle', {
|
||||||
defaultMessage: 'License error',
|
defaultMessage: 'License error',
|
||||||
})}
|
})}
|
||||||
color="warning"
|
color="danger"
|
||||||
iconType="alert"
|
iconType="alert"
|
||||||
style={{ padding: '16px' }}
|
style={{ padding: '16px' }}
|
||||||
>
|
>
|
|
@ -1,3 +1,9 @@
|
||||||
|
/*
|
||||||
|
* 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 const breakdown = {
|
export const breakdown = {
|
||||||
advance: 0,
|
advance: 0,
|
||||||
advance_count: 0,
|
advance_count: 0,
|
|
@ -1,3 +1,9 @@
|
||||||
|
/*
|
||||||
|
* 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 const inputTimes = [
|
export const inputTimes = [
|
||||||
{
|
{
|
||||||
type: 'BooleanQuery',
|
type: 'BooleanQuery',
|
|
@ -4,7 +4,7 @@
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { registerTestBed } from '../../../../../../../../../test_utils';
|
import { registerTestBed } from '../../../../../../../test_utils';
|
||||||
import { searchResponse } from './fixtures/search_response';
|
import { searchResponse } from './fixtures/search_response';
|
||||||
import { ProfileTree, Props } from '../profile_tree';
|
import { ProfileTree, Props } from '../profile_tree';
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { registerTestBed } from '../../../../../../../test_utils';
|
import { registerTestBed } from '../../../../../test_utils';
|
||||||
|
|
||||||
import { SearchProfilerTabs, Props } from './searchprofiler_tabs';
|
import { SearchProfilerTabs, Props } from './searchprofiler_tabs';
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
* or more contributor license agreements. Licensed under the Elastic License;
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
*/
|
*/
|
||||||
import { registerTestBed } from '../../../../../../../../../test_utils';
|
import { registerTestBed } from '../../../../../../../test_utils';
|
||||||
import { EmptyTreePlaceHolder } from '.';
|
import { EmptyTreePlaceHolder } from '.';
|
||||||
|
|
||||||
describe('EmptyTreePlaceholder', () => {
|
describe('EmptyTreePlaceholder', () => {
|
|
@ -4,7 +4,7 @@
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { registerTestBed } from '../../../../../../../../../test_utils';
|
import { registerTestBed } from '../../../../../../../test_utils';
|
||||||
import { ProfileLoadingPlaceholder } from '.';
|
import { ProfileLoadingPlaceholder } from '.';
|
||||||
|
|
||||||
describe('Profile Loading Placeholder', () => {
|
describe('Profile Loading Placeholder', () => {
|
|
@ -42,7 +42,7 @@ function hasAggregations(profileResponse: ShardSerialized[]) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Main = () => {
|
export const Main = () => {
|
||||||
const { licenseEnabled } = useAppContext();
|
const { getLicenseStatus } = useAppContext();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
activeTab,
|
activeTab,
|
||||||
|
@ -63,7 +63,7 @@ export const Main = () => {
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const renderLicenseWarning = () => {
|
const renderLicenseWarning = () => {
|
||||||
return !licenseEnabled ? (
|
return !getLicenseStatus().valid ? (
|
||||||
<>
|
<>
|
||||||
<LicenseWarningNotice />
|
<LicenseWarningNotice />
|
||||||
<EuiSpacer size="s" />
|
<EuiSpacer size="s" />
|
||||||
|
@ -84,7 +84,7 @@ export const Main = () => {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (licenseEnabled && pristine) {
|
if (getLicenseStatus().valid && pristine) {
|
||||||
return <EmptyTreePlaceHolder />;
|
return <EmptyTreePlaceHolder />;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
* or more contributor license agreements. Licensed under the Elastic License;
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useRef, memo, useCallback } from 'react';
|
import React, { useRef, memo, useCallback } from 'react';
|
||||||
import { i18n } from '@kbn/i18n';
|
import { i18n } from '@kbn/i18n';
|
||||||
import {
|
import {
|
||||||
|
@ -39,7 +40,7 @@ export const ProfileQueryEditor = memo(() => {
|
||||||
|
|
||||||
const dispatch = useProfilerActionContext();
|
const dispatch = useProfilerActionContext();
|
||||||
|
|
||||||
const { licenseEnabled, notifications } = useAppContext();
|
const { getLicenseStatus, notifications } = useAppContext();
|
||||||
const requestProfile = useRequestProfile();
|
const requestProfile = useRequestProfile();
|
||||||
|
|
||||||
const handleProfileClick = async () => {
|
const handleProfileClick = async () => {
|
||||||
|
@ -65,6 +66,7 @@ export const ProfileQueryEditor = memo(() => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const onEditorReady = useCallback(editorInstance => (editorRef.current = editorInstance), []);
|
const onEditorReady = useCallback(editorInstance => (editorRef.current = editorInstance), []);
|
||||||
|
const licenseEnabled = getLicenseStatus().valid;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EuiFlexGroup
|
<EuiFlexGroup
|
|
@ -4,26 +4,45 @@
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useContext, createContext } from 'react';
|
import React, { useContext, createContext, useCallback } from 'react';
|
||||||
|
|
||||||
import { HttpSetup, ToastsSetup } from 'kibana/public';
|
import { HttpSetup, ToastsSetup } from 'kibana/public';
|
||||||
|
import { LicenseStatus } from '../../../common';
|
||||||
|
|
||||||
|
export interface ContextArgs {
|
||||||
|
http: HttpSetup;
|
||||||
|
notifications: ToastsSetup;
|
||||||
|
initialLicenseStatus: LicenseStatus;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ContextValue {
|
export interface ContextValue {
|
||||||
http: HttpSetup;
|
http: HttpSetup;
|
||||||
notifications: ToastsSetup;
|
notifications: ToastsSetup;
|
||||||
licenseEnabled: boolean;
|
getLicenseStatus: () => LicenseStatus;
|
||||||
formatAngularHttpError: (error: any) => string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const AppContext = createContext<ContextValue>(null as any);
|
const AppContext = createContext<ContextValue>(null as any);
|
||||||
|
|
||||||
export const AppContextProvider = ({
|
export const AppContextProvider = ({
|
||||||
children,
|
children,
|
||||||
value,
|
args: { http, notifications, initialLicenseStatus },
|
||||||
}: {
|
}: {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
value: ContextValue;
|
args: ContextArgs;
|
||||||
}) => {
|
}) => {
|
||||||
return <AppContext.Provider value={value}>{children}</AppContext.Provider>;
|
const getLicenseStatus = useCallback(() => initialLicenseStatus, [initialLicenseStatus]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AppContext.Provider
|
||||||
|
value={{
|
||||||
|
http,
|
||||||
|
notifications,
|
||||||
|
getLicenseStatus,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</AppContext.Provider>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useAppContext = () => {
|
export const useAppContext = () => {
|
|
@ -10,7 +10,7 @@ jest.mock('./worker', () => {
|
||||||
return { workerModule: { id: 'ace/mode/json_worker', src: '' } };
|
return { workerModule: { id: 'ace/mode/json_worker', src: '' } };
|
||||||
});
|
});
|
||||||
|
|
||||||
import { registerTestBed } from '../../../../../../../test_utils';
|
import { registerTestBed } from '../../../../../test_utils';
|
||||||
import { Editor, Props } from '.';
|
import { Editor, Props } from '.';
|
||||||
|
|
||||||
describe('Editor Component', () => {
|
describe('Editor Component', () => {
|
|
@ -10,10 +10,7 @@ import { Editor as AceEditor } from 'brace';
|
||||||
import { initializeEditor } from './init_editor';
|
import { initializeEditor } from './init_editor';
|
||||||
import { useUIAceKeyboardMode } from './use_ui_ace_keyboard_mode';
|
import { useUIAceKeyboardMode } from './use_ui_ace_keyboard_mode';
|
||||||
|
|
||||||
interface EditorShim {
|
type EditorShim = ReturnType<typeof createEditorShim>;
|
||||||
getValue(): string;
|
|
||||||
focus(): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type EditorInstance = EditorShim;
|
export type EditorInstance = EditorShim;
|
||||||
|
|
||||||
|
@ -23,7 +20,7 @@ export interface Props {
|
||||||
onEditorReady: (editor: EditorShim) => void;
|
onEditorReady: (editor: EditorShim) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const createEditorShim = (aceEditor: AceEditor): EditorShim => {
|
const createEditorShim = (aceEditor: AceEditor) => {
|
||||||
return {
|
return {
|
||||||
getValue() {
|
getValue() {
|
||||||
return aceEditor.getValue();
|
return aceEditor.getValue();
|
||||||
|
@ -40,15 +37,13 @@ export const Editor = memo(({ licenseEnabled, initialValue, onEditorReady }: Pro
|
||||||
|
|
||||||
const [textArea, setTextArea] = useState<HTMLTextAreaElement | null>(null);
|
const [textArea, setTextArea] = useState<HTMLTextAreaElement | null>(null);
|
||||||
|
|
||||||
if (licenseEnabled) {
|
useUIAceKeyboardMode(textArea);
|
||||||
useUIAceKeyboardMode(textArea);
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const divEl = containerRef.current;
|
const divEl = containerRef.current;
|
||||||
editorInstanceRef.current = initializeEditor({ el: divEl, licenseEnabled });
|
editorInstanceRef.current = initializeEditor({ el: divEl, licenseEnabled });
|
||||||
editorInstanceRef.current.setValue(initialValue, 1);
|
editorInstanceRef.current.setValue(initialValue, 1);
|
||||||
setTextArea(containerRef.current!.querySelector('textarea'));
|
setTextArea(licenseEnabled ? containerRef.current!.querySelector('textarea') : null);
|
||||||
|
|
||||||
onEditorReady(createEditorShim(editorInstanceRef.current));
|
onEditorReady(createEditorShim(editorInstanceRef.current));
|
||||||
}, [initialValue, onEditorReady, licenseEnabled]);
|
}, [initialValue, onEditorReady, licenseEnabled]);
|
|
@ -19,8 +19,22 @@ interface ReturnValue {
|
||||||
error?: string;
|
error?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const extractProfilerErrorMessage = (e: any): string | undefined => {
|
||||||
|
if (e.body?.attributes?.error?.reason) {
|
||||||
|
const { reason, line, col } = e.body.attributes.error;
|
||||||
|
return `${reason} at line: ${line - 1} col: ${col}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.body?.message) {
|
||||||
|
return e.body.message;
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
export const useRequestProfile = () => {
|
export const useRequestProfile = () => {
|
||||||
const { http, notifications, formatAngularHttpError, licenseEnabled } = useAppContext();
|
const { http, notifications, getLicenseStatus } = useAppContext();
|
||||||
|
const licenseEnabled = getLicenseStatus().valid;
|
||||||
return async ({ query, index }: Args): Promise<ReturnValue> => {
|
return async ({ query, index }: Args): Promise<ReturnValue> => {
|
||||||
if (!licenseEnabled) {
|
if (!licenseEnabled) {
|
||||||
return { data: null };
|
return { data: null };
|
||||||
|
@ -39,7 +53,7 @@ export const useRequestProfile = () => {
|
||||||
return { data: parsed.profile.shards };
|
return { data: parsed.profile.shards };
|
||||||
}
|
}
|
||||||
|
|
||||||
const payload: Record<string, any> = { query };
|
const payload: Record<string, any> = { query: parsed };
|
||||||
|
|
||||||
if (index == null || index === '') {
|
if (index == null || index === '') {
|
||||||
payload.index = '_all';
|
payload.index = '_all';
|
||||||
|
@ -59,11 +73,13 @@ export const useRequestProfile = () => {
|
||||||
|
|
||||||
return { data: resp.resp.profile.shards };
|
return { data: resp.resp.profile.shards };
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
try {
|
const profilerErrorMessage = extractProfilerErrorMessage(e);
|
||||||
// Is this a known error type?
|
if (profilerErrorMessage) {
|
||||||
const errorString = formatAngularHttpError(e);
|
notifications.addError(e, {
|
||||||
notifications.addError(e, { title: errorString });
|
title: e.message,
|
||||||
} catch (_) {
|
toastMessage: profilerErrorMessage,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
// Otherwise just report the original error
|
// Otherwise just report the original error
|
||||||
notifications.addError(e, {
|
notifications.addError(e, {
|
||||||
title: i18n.translate('xpack.searchProfiler.errorSomethingWentWrongTitle', {
|
title: i18n.translate('xpack.searchProfiler.errorSomethingWentWrongTitle', {
|
|
@ -10,16 +10,10 @@ import { ProfileContextProvider } from './contexts/profiler_context';
|
||||||
|
|
||||||
import { AppDependencies } from './boot';
|
import { AppDependencies } from './boot';
|
||||||
|
|
||||||
export function App({
|
export function App({ I18nContext, initialLicenseStatus, notifications, http }: AppDependencies) {
|
||||||
I18nContext,
|
|
||||||
licenseEnabled,
|
|
||||||
notifications,
|
|
||||||
http,
|
|
||||||
formatAngularHttpError,
|
|
||||||
}: AppDependencies) {
|
|
||||||
return (
|
return (
|
||||||
<I18nContext>
|
<I18nContext>
|
||||||
<AppContextProvider value={{ licenseEnabled, notifications, http, formatAngularHttpError }}>
|
<AppContextProvider args={{ initialLicenseStatus, notifications, http }}>
|
||||||
<ProfileContextProvider>
|
<ProfileContextProvider>
|
||||||
<Main />
|
<Main />
|
||||||
</ProfileContextProvider>
|
</ProfileContextProvider>
|
69
x-pack/plugins/searchprofiler/public/plugin.ts
Normal file
69
x-pack/plugins/searchprofiler/public/plugin.ts
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License;
|
||||||
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { i18n } from '@kbn/i18n';
|
||||||
|
import { Plugin, CoreStart, CoreSetup, PluginInitializerContext } from 'kibana/public';
|
||||||
|
import { first } from 'rxjs/operators';
|
||||||
|
|
||||||
|
import { FeatureCatalogueCategory } from '../../../../src/plugins/home/public';
|
||||||
|
import { LICENSE_CHECK_STATE } from '../../licensing/public';
|
||||||
|
|
||||||
|
import { PLUGIN } from '../common';
|
||||||
|
import { AppPublicPluginDependencies } from './types';
|
||||||
|
|
||||||
|
export class SearchProfilerUIPlugin implements Plugin<void, void, AppPublicPluginDependencies> {
|
||||||
|
constructor(ctx: PluginInitializerContext) {}
|
||||||
|
|
||||||
|
async setup(
|
||||||
|
{ http, getStartServices }: CoreSetup,
|
||||||
|
{ dev_tools, home, licensing }: AppPublicPluginDependencies
|
||||||
|
) {
|
||||||
|
home.featureCatalogue.register({
|
||||||
|
id: PLUGIN.id,
|
||||||
|
title: i18n.translate('xpack.searchProfiler.registryProviderTitle', {
|
||||||
|
defaultMessage: 'Search Profiler',
|
||||||
|
}),
|
||||||
|
description: i18n.translate('xpack.searchProfiler.registryProviderDescription', {
|
||||||
|
defaultMessage: 'Quickly check the performance of any Elasticsearch query.',
|
||||||
|
}),
|
||||||
|
icon: 'searchProfilerApp',
|
||||||
|
path: '/app/kibana#/dev_tools/searchprofiler',
|
||||||
|
showOnHomePage: false,
|
||||||
|
category: FeatureCatalogueCategory.ADMIN,
|
||||||
|
});
|
||||||
|
|
||||||
|
dev_tools.register({
|
||||||
|
id: 'searchprofiler',
|
||||||
|
title: i18n.translate('xpack.searchProfiler.pageDisplayName', {
|
||||||
|
defaultMessage: 'Search Profiler',
|
||||||
|
}),
|
||||||
|
order: 5,
|
||||||
|
enableRouting: false,
|
||||||
|
mount: async (ctx, params) => {
|
||||||
|
const [coreStart] = await getStartServices();
|
||||||
|
const { notifications, i18n: i18nDep } = coreStart;
|
||||||
|
const { boot } = await import('./application/boot');
|
||||||
|
|
||||||
|
const license = await licensing.license$.pipe(first()).toPromise();
|
||||||
|
const { state, message } = license.check(PLUGIN.id, PLUGIN.minimumLicenseType);
|
||||||
|
const initialLicenseStatus =
|
||||||
|
state === LICENSE_CHECK_STATE.Valid ? { valid: true } : { valid: false, message };
|
||||||
|
|
||||||
|
return boot({
|
||||||
|
http,
|
||||||
|
initialLicenseStatus,
|
||||||
|
el: params.element,
|
||||||
|
I18nContext: i18nDep.Context,
|
||||||
|
notifications: notifications.toasts,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async start(core: CoreStart, plugins: any) {}
|
||||||
|
|
||||||
|
async stop() {}
|
||||||
|
}
|
15
x-pack/plugins/searchprofiler/public/types.ts
Normal file
15
x-pack/plugins/searchprofiler/public/types.ts
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
/*
|
||||||
|
* 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 { HomePublicPluginSetup } from '../../../../src/plugins/home/public';
|
||||||
|
import { DevToolsSetup } from '../../../../src/plugins/dev_tools/public';
|
||||||
|
import { LicensingPluginSetup } from '../../licensing/public';
|
||||||
|
|
||||||
|
export interface AppPublicPluginDependencies {
|
||||||
|
licensing: LicensingPluginSetup;
|
||||||
|
home: HomePublicPluginSetup;
|
||||||
|
dev_tools: DevToolsSetup;
|
||||||
|
}
|
11
x-pack/plugins/searchprofiler/server/index.ts
Normal file
11
x-pack/plugins/searchprofiler/server/index.ts
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License;
|
||||||
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
|
*/
|
||||||
|
import { PluginInitializerContext } from 'kibana/server';
|
||||||
|
import { SearchProfilerServerPlugin } from './plugin';
|
||||||
|
|
||||||
|
export const plugin = (ctx: PluginInitializerContext) => {
|
||||||
|
return new SearchProfilerServerPlugin(ctx);
|
||||||
|
};
|
59
x-pack/plugins/searchprofiler/server/plugin.ts
Normal file
59
x-pack/plugins/searchprofiler/server/plugin.ts
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
/*
|
||||||
|
* 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';
|
||||||
|
import { CoreSetup, Logger, Plugin, PluginInitializerContext } from 'kibana/server';
|
||||||
|
|
||||||
|
import { LICENSE_CHECK_STATE } from '../../licensing/common/types';
|
||||||
|
|
||||||
|
import { LicenseStatus, PLUGIN } from '../common';
|
||||||
|
import { AppServerPluginDependencies } from './types';
|
||||||
|
import * as profileRoute from './routes/profile';
|
||||||
|
|
||||||
|
export class SearchProfilerServerPlugin implements Plugin {
|
||||||
|
licenseStatus: LicenseStatus;
|
||||||
|
log: Logger;
|
||||||
|
|
||||||
|
constructor({ logger }: PluginInitializerContext) {
|
||||||
|
this.log = logger.get();
|
||||||
|
this.licenseStatus = { valid: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
async setup({ http }: CoreSetup, { licensing, elasticsearch }: AppServerPluginDependencies) {
|
||||||
|
const router = http.createRouter();
|
||||||
|
profileRoute.register({
|
||||||
|
elasticsearch,
|
||||||
|
router,
|
||||||
|
getLicenseStatus: () => this.licenseStatus,
|
||||||
|
log: this.log,
|
||||||
|
});
|
||||||
|
|
||||||
|
licensing.license$.subscribe(license => {
|
||||||
|
const { state, message } = license.check(PLUGIN.id, PLUGIN.minimumLicenseType);
|
||||||
|
const hasRequiredLicense = state === LICENSE_CHECK_STATE.Valid;
|
||||||
|
if (hasRequiredLicense) {
|
||||||
|
this.licenseStatus = { valid: true };
|
||||||
|
} else {
|
||||||
|
this.licenseStatus = {
|
||||||
|
valid: false,
|
||||||
|
message:
|
||||||
|
message ||
|
||||||
|
// Ensure that there is a message when license check fails
|
||||||
|
i18n.translate('xpack.searchProfiler.licenseCheckErrorMessage', {
|
||||||
|
defaultMessage: 'License check failed',
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
if (message) {
|
||||||
|
this.log.info(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
start() {}
|
||||||
|
|
||||||
|
stop() {}
|
||||||
|
}
|
70
x-pack/plugins/searchprofiler/server/routes/profile.ts
Normal file
70
x-pack/plugins/searchprofiler/server/routes/profile.ts
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
/*
|
||||||
|
* 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 { RouteDependencies } from '../types';
|
||||||
|
|
||||||
|
export const register = ({ router, getLicenseStatus, log }: RouteDependencies) => {
|
||||||
|
router.post(
|
||||||
|
{
|
||||||
|
path: '/api/searchprofiler/profile',
|
||||||
|
validate: {
|
||||||
|
body: schema.object({
|
||||||
|
query: schema.object({}, { allowUnknowns: true }),
|
||||||
|
index: schema.string(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
async (ctx, request, response) => {
|
||||||
|
const currentLicenseStatus = getLicenseStatus();
|
||||||
|
if (!currentLicenseStatus.valid) {
|
||||||
|
return response.forbidden({
|
||||||
|
body: {
|
||||||
|
message: currentLicenseStatus.message!,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
core: { elasticsearch },
|
||||||
|
} = ctx;
|
||||||
|
|
||||||
|
const {
|
||||||
|
body: { query, index },
|
||||||
|
} = request;
|
||||||
|
|
||||||
|
const parsed = {
|
||||||
|
// Activate profiler mode for this query.
|
||||||
|
profile: true,
|
||||||
|
...query,
|
||||||
|
};
|
||||||
|
|
||||||
|
const body = {
|
||||||
|
index,
|
||||||
|
body: JSON.stringify(parsed, null, 2),
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
const resp = await elasticsearch.dataClient.callAsCurrentUser('search', body);
|
||||||
|
return response.ok({
|
||||||
|
body: {
|
||||||
|
ok: true,
|
||||||
|
resp,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
log.error(err);
|
||||||
|
return response.customError({
|
||||||
|
statusCode: err.status || 500,
|
||||||
|
body: err.body
|
||||||
|
? {
|
||||||
|
message: err.message,
|
||||||
|
attributes: err.body,
|
||||||
|
}
|
||||||
|
: err,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue