diff --git a/.eslintrc.js b/.eslintrc.js
index 3161a25b70870..3778bd374da61 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -17,9 +17,6 @@
* under the License.
*/
-const { readdirSync } = require('fs');
-const { resolve } = require('path');
-
const APACHE_2_0_LICENSE_HEADER = `
/*
* Licensed to Elasticsearch B.V. under one or more contributor
@@ -288,7 +285,7 @@ module.exports = {
},
{
target: [
- '(src|x-pack)/legacy/**/*',
+ 'src/legacy/**/*',
'(src|x-pack)/plugins/**/(public|server)/**/*',
'examples/**/*',
],
@@ -319,14 +316,11 @@ module.exports = {
},
{
target: [
- '(src|x-pack)/legacy/**/*',
+ 'src/legacy/**/*',
'(src|x-pack)/plugins/**/(public|server)/**/*',
'examples/**/*',
'!(src|x-pack)/**/*.test.*',
'!(x-pack/)?test/**/*',
- // next folder contains legacy browser tests which can't be migrated to jest
- // which import np files
- '!src/legacy/core_plugins/kibana/public/__tests__/**/*',
],
from: [
'(src|x-pack)/plugins/**/(public|server)/**/*',
@@ -341,14 +335,6 @@ module.exports = {
'(src|x-pack)/plugins/**/*',
'!(src|x-pack)/plugins/**/server/**/*',
- 'src/legacy/core_plugins/**/*',
- '!src/legacy/core_plugins/**/server/**/*',
- '!src/legacy/core_plugins/**/index.{js,mjs,ts,tsx}',
-
- 'x-pack/legacy/plugins/**/*',
- '!x-pack/legacy/plugins/**/server/**/*',
- '!x-pack/legacy/plugins/**/index.{js,mjs,ts,tsx}',
-
'examples/**/*',
'!examples/**/server/**/*',
],
@@ -370,12 +356,7 @@ module.exports = {
},
{
target: ['src/core/**/*'],
- from: [
- 'plugins/**/*',
- 'src/plugins/**/*',
- 'src/legacy/core_plugins/**/*',
- 'src/legacy/ui/**/*',
- ],
+ from: ['plugins/**/*', 'src/plugins/**/*', 'src/legacy/ui/**/*'],
errorMessage: 'The core cannot depend on any plugins.',
},
{
@@ -388,12 +369,6 @@ module.exports = {
target: [
'test/plugin_functional/plugins/**/public/np_ready/**/*',
'test/plugin_functional/plugins/**/server/np_ready/**/*',
- 'src/legacy/core_plugins/**/public/np_ready/**/*',
- 'src/legacy/core_plugins/vis_type_*/public/**/*',
- '!src/legacy/core_plugins/vis_type_*/public/legacy*',
- 'src/legacy/core_plugins/**/server/np_ready/**/*',
- 'x-pack/legacy/plugins/**/public/np_ready/**/*',
- 'x-pack/legacy/plugins/**/server/np_ready/**/*',
],
allowSameFolder: true,
errorMessage:
@@ -443,22 +418,14 @@ module.exports = {
settings: {
// instructs import/no-extraneous-dependencies to treat certain modules
// as core modules, even if they aren't listed in package.json
- 'import/core-modules': ['plugins', 'legacy/ui'],
+ 'import/core-modules': ['plugins'],
'import/resolver': {
'@kbn/eslint-import-resolver-kibana': {
forceNode: false,
rootPackageName: 'kibana',
kibanaPath: '.',
- pluginMap: readdirSync(resolve(__dirname, 'x-pack/legacy/plugins')).reduce(
- (acc, name) => {
- if (!name.startsWith('_')) {
- acc[name] = `x-pack/legacy/plugins/${name}`;
- }
- return acc;
- },
- {}
- ),
+ pluginMap: {},
},
},
},
@@ -764,16 +731,6 @@ module.exports = {
},
},
- /**
- * GIS overrides
- */
- {
- files: ['x-pack/legacy/plugins/maps/**/*.js'],
- rules: {
- 'react/prefer-stateless-function': [0, { ignorePureComponents: false }],
- },
- },
-
/**
* ML overrides
*/
@@ -812,7 +769,7 @@ module.exports = {
},
{
// typescript only for front and back end
- files: ['x-pack/{,legacy/}plugins/security_solution/**/*.{ts,tsx}'],
+ files: ['x-pack/plugins/security_solution/**/*.{ts,tsx}'],
rules: {
// This will be turned on after bug fixes are complete
// '@typescript-eslint/explicit-member-accessibility': 'warn',
@@ -858,7 +815,7 @@ module.exports = {
// },
{
// typescript and javascript for front and back end
- files: ['x-pack/{,legacy/}plugins/security_solution/**/*.{js,mjs,ts,tsx}'],
+ files: ['x-pack/plugins/security_solution/**/*.{js,mjs,ts,tsx}'],
plugins: ['eslint-plugin-node', 'react'],
env: {
mocha: true,
@@ -1089,7 +1046,7 @@ module.exports = {
{
// typescript only for front and back end
files: [
- 'x-pack/{,legacy/}plugins/{alerts,alerting_builtins,actions,task_manager,event_log}/**/*.{ts,tsx}',
+ 'x-pack/plugins/{alerts,alerting_builtins,actions,task_manager,event_log}/**/*.{ts,tsx}',
],
rules: {
'@typescript-eslint/no-explicit-any': 'error',
@@ -1238,10 +1195,7 @@ module.exports = {
* TSVB overrides
*/
{
- files: [
- 'src/plugins/vis_type_timeseries/**/*.{js,mjs,ts,tsx}',
- 'src/legacy/core_plugins/vis_type_timeseries/**/*.{js,mjs,ts,tsx}',
- ],
+ files: ['src/plugins/vis_type_timeseries/**/*.{js,mjs,ts,tsx}'],
rules: {
'import/no-default-export': 'error',
},
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 0bdddddab8de5..f4e620dea95a9 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -65,14 +65,15 @@
# Client Side Monitoring (lives in APM directories but owned by Uptime)
/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm @elastic/uptime
+/x-pack/plugins/apm/e2e/cypress/integration/csm_dashboard.feature @elastic/uptime
/x-pack/plugins/apm/public/application/csmApp.tsx @elastic/uptime
/x-pack/plugins/apm/public/components/app/RumDashboard @elastic/uptime
/x-pack/plugins/apm/server/lib/rum_client @elastic/uptime
/x-pack/plugins/apm/server/routes/rum_client.ts @elastic/uptime
-/x-pack/plugins/apm/server/projections/rum_overview.ts @elastic/uptime
+/x-pack/plugins/apm/server/projections/rum_page_load_transactions.ts @elastic/uptime
# Beats
-/x-pack/legacy/plugins/beats_management/ @elastic/beats
+/x-pack/plugins/beats_management/ @elastic/beats
# Canvas
/x-pack/plugins/canvas/ @elastic/kibana-canvas
@@ -86,16 +87,13 @@
/x-pack/plugins/global_search_bar/ @elastic/kibana-core-ui
# Observability UIs
-/x-pack/legacy/plugins/infra/ @elastic/logs-metrics-ui
/x-pack/plugins/infra/ @elastic/logs-metrics-ui
/x-pack/plugins/ingest_manager/ @elastic/ingest-management
-/x-pack/legacy/plugins/ingest_manager/ @elastic/ingest-management
/x-pack/plugins/observability/ @elastic/observability-ui
/x-pack/plugins/monitoring/ @elastic/stack-monitoring-ui
/x-pack/plugins/uptime @elastic/uptime
# Machine Learning
-/x-pack/legacy/plugins/ml/ @elastic/ml-ui
/x-pack/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
@@ -107,7 +105,6 @@
/x-pack/test/functional/services/transform.ts @elastic/ml-ui
# Maps
-/x-pack/legacy/plugins/maps/ @elastic/kibana-gis
/x-pack/plugins/maps/ @elastic/kibana-gis
/x-pack/test/api_integration/apis/maps/ @elastic/kibana-gis
/x-pack/test/functional/apps/maps/ @elastic/kibana-gis
@@ -234,13 +231,8 @@ x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json @elastic/kib
/src/plugins/dev_tools/ @elastic/es-ui
/src/plugins/console/ @elastic/es-ui
/src/plugins/es_ui_shared/ @elastic/es-ui
-/x-pack/legacy/plugins/cross_cluster_replication/ @elastic/es-ui
+/x-pack/plugins/cross_cluster_replication/ @elastic/es-ui
/x-pack/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/rollup/ @elastic/es-ui
-/x-pack/legacy/plugins/snapshot_restore/ @elastic/es-ui
-/x-pack/legacy/plugins/upgrade_assistant/ @elastic/es-ui
/x-pack/plugins/console_extensions/ @elastic/es-ui
/x-pack/plugins/es_ui_shared/ @elastic/es-ui
/x-pack/plugins/grokdebugger/ @elastic/es-ui
diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc
index b426621fed296..5a4a60c2e628e 100644
--- a/docs/developer/plugin-list.asciidoc
+++ b/docs/developer/plugin-list.asciidoc
@@ -504,6 +504,10 @@ in their infrastructure.
|Contains HTTP endpoints and UiSettings that are slated for removal.
+|{kib-repo}blob/{branch}/x-pack/plugins/drilldowns/url_drilldown/README.md[urlDrilldown]
+|NOTE: This plugin contains implementation of URL drilldown. For drilldowns infrastructure code refer to ui_actions_enhanced plugin.
+
+
|===
include::{kibana-root}/src/plugins/dashboard/README.asciidoc[leveloffset=+1]
diff --git a/docs/user/dashboard/url-drilldown.asciidoc b/docs/user/dashboard/url-drilldown.asciidoc
index e6daf89d72718..ee879256a1fae 100644
--- a/docs/user/dashboard/url-drilldown.asciidoc
+++ b/docs/user/dashboard/url-drilldown.asciidoc
@@ -238,3 +238,14 @@ Tip: Consider using <> helper for date formatting.
| Aggregation field behind the selected range, if available.
|===
+
+[float]
+[[disable]]
+==== Disable URL drilldown
+
+You can disable URL drilldown feature on your {kib} instance by disabling the plugin:
+
+["source","yml"]
+-----------
+url_drilldown.enabled: false
+-----------
diff --git a/kibana.d.ts b/kibana.d.ts
index b707405ffbeaf..50f8b8690d944 100644
--- a/kibana.d.ts
+++ b/kibana.d.ts
@@ -28,7 +28,6 @@ export { Public, Server };
/**
* All exports from TS ambient definitions (where types are added for JS source in a .d.ts file).
*/
-import * as LegacyKibanaPluginSpec from './src/legacy/plugin_discovery/plugin_spec/plugin_spec_options';
import * as LegacyKibanaServer from './src/legacy/server/kbn_server';
/**
@@ -39,8 +38,4 @@ export namespace Legacy {
export type Request = LegacyKibanaServer.Request;
export type ResponseToolkit = LegacyKibanaServer.ResponseToolkit;
export type Server = LegacyKibanaServer.Server;
-
- export type InitPluginFunction = LegacyKibanaPluginSpec.InitPluginFunction;
- export type UiExports = LegacyKibanaPluginSpec.UiExports;
- export type PluginSpecOptions = LegacyKibanaPluginSpec.PluginSpecOptions;
}
diff --git a/src/cli/serve/serve.js b/src/cli/serve/serve.js
index d8bd39b9dcdf4..a1715cf3dba2c 100644
--- a/src/cli/serve/serve.js
+++ b/src/cli/serve/serve.js
@@ -48,11 +48,6 @@ const CAN_CLUSTER = canRequire(CLUSTER_MANAGER_PATH);
const REPL_PATH = resolve(__dirname, '../repl');
const CAN_REPL = canRequire(REPL_PATH);
-// xpack is installed in both dev and the distributable, it's optional if
-// install is a link to the source, not an actual install
-const XPACK_DIR = resolve(__dirname, '../../../x-pack');
-const XPACK_INSTALLED = canRequire(XPACK_DIR);
-
const pathCollector = function () {
const paths = [];
return function (path) {
@@ -137,16 +132,7 @@ function applyConfigOverrides(rawConfig, opts, extraCliOptions) {
if (opts.logFile) set('logging.dest', opts.logFile);
set('plugins.scanDirs', _.compact([].concat(get('plugins.scanDirs'), opts.pluginDir)));
- set(
- 'plugins.paths',
- _.compact(
- [].concat(
- get('plugins.paths'),
- opts.pluginPath,
- XPACK_INSTALLED && !opts.oss ? [XPACK_DIR] : []
- )
- )
- );
+ set('plugins.paths', _.compact([].concat(get('plugins.paths'), opts.pluginPath)));
merge(extraCliOptions);
merge(readKeystore());
diff --git a/src/core/public/injected_metadata/injected_metadata_service.ts b/src/core/public/injected_metadata/injected_metadata_service.ts
index 5b51bc823d166..bd8c9e91f15a2 100644
--- a/src/core/public/injected_metadata/injected_metadata_service.ts
+++ b/src/core/public/injected_metadata/injected_metadata_service.ts
@@ -58,19 +58,6 @@ export interface InjectedMetadataParams {
uiPlugins: InjectedPluginMetadata[];
anonymousStatusPage: boolean;
legacyMetadata: {
- app: {
- id: string;
- title: string;
- };
- bundleId: string;
- version: string;
- branch: string;
- buildNum: number;
- buildSha: string;
- basePath: string;
- serverName: string;
- devMode: boolean;
- category?: AppCategory;
uiSettings: {
defaults: Record;
user?: Record;
@@ -167,18 +154,6 @@ export interface InjectedMetadataSetup {
getPlugins: () => InjectedPluginMetadata[];
getAnonymousStatusPage: () => boolean;
getLegacyMetadata: () => {
- app: {
- id: string;
- title: string;
- };
- bundleId: string;
- version: string;
- branch: string;
- buildNum: number;
- buildSha: string;
- basePath: string;
- serverName: string;
- devMode: boolean;
uiSettings: {
defaults: Record;
user?: Record | undefined;
diff --git a/src/core/server/index.ts b/src/core/server/index.ts
index e136c699f7246..70ef93963c69f 100644
--- a/src/core/server/index.ts
+++ b/src/core/server/index.ts
@@ -339,14 +339,7 @@ export {
SavedObjectsMigrationVersion,
} from './types';
-export {
- LegacyServiceSetupDeps,
- LegacyServiceStartDeps,
- LegacyServiceDiscoverPlugins,
- LegacyConfig,
- LegacyUiExports,
- LegacyInternals,
-} from './legacy';
+export { LegacyServiceSetupDeps, LegacyServiceStartDeps, LegacyConfig } from './legacy';
export {
CoreStatus,
diff --git a/src/core/server/legacy/config/ensure_valid_configuration.test.ts b/src/core/server/legacy/config/ensure_valid_configuration.test.ts
index 702840b8a0a6a..700fe69954655 100644
--- a/src/core/server/legacy/config/ensure_valid_configuration.test.ts
+++ b/src/core/server/legacy/config/ensure_valid_configuration.test.ts
@@ -39,17 +39,12 @@ describe('ensureValidConfiguration', () => {
configService as any,
{
settings: 'settings',
- pluginSpecs: 'pluginSpecs',
- disabledPluginSpecs: 'disabledPluginSpecs',
- pluginExtendedConfig: 'pluginExtendedConfig',
- uiExports: 'uiExports',
+ legacyConfig: 'pluginExtendedConfig',
} as any
);
expect(getUnusedConfigKeys).toHaveBeenCalledTimes(1);
expect(getUnusedConfigKeys).toHaveBeenCalledWith({
coreHandledConfigPaths: ['core', 'elastic'],
- pluginSpecs: 'pluginSpecs',
- disabledPluginSpecs: 'disabledPluginSpecs',
settings: 'settings',
legacyConfig: 'pluginExtendedConfig',
});
diff --git a/src/core/server/legacy/config/ensure_valid_configuration.ts b/src/core/server/legacy/config/ensure_valid_configuration.ts
index 5cd1603ea65fb..34f98b9b3a795 100644
--- a/src/core/server/legacy/config/ensure_valid_configuration.ts
+++ b/src/core/server/legacy/config/ensure_valid_configuration.ts
@@ -19,19 +19,17 @@
import { getUnusedConfigKeys } from './get_unused_config_keys';
import { ConfigService } from '../../config';
-import { LegacyServiceDiscoverPlugins } from '../types';
import { CriticalError } from '../../errors';
+import { LegacyServiceSetupConfig } from '../types';
export async function ensureValidConfiguration(
configService: ConfigService,
- { pluginSpecs, disabledPluginSpecs, pluginExtendedConfig, settings }: LegacyServiceDiscoverPlugins
+ { legacyConfig, settings }: LegacyServiceSetupConfig
) {
const unusedConfigKeys = await getUnusedConfigKeys({
coreHandledConfigPaths: await configService.getUsedPaths(),
- pluginSpecs,
- disabledPluginSpecs,
settings,
- legacyConfig: pluginExtendedConfig,
+ legacyConfig,
});
if (unusedConfigKeys.length > 0) {
diff --git a/src/core/server/legacy/config/get_unused_config_keys.test.ts b/src/core/server/legacy/config/get_unused_config_keys.test.ts
index f8506b5744030..6ce69fca0270a 100644
--- a/src/core/server/legacy/config/get_unused_config_keys.test.ts
+++ b/src/core/server/legacy/config/get_unused_config_keys.test.ts
@@ -17,7 +17,7 @@
* under the License.
*/
-import { LegacyPluginSpec, LegacyConfig, LegacyVars } from '../types';
+import { LegacyConfig, LegacyVars } from '../types';
import { getUnusedConfigKeys } from './get_unused_config_keys';
describe('getUnusedConfigKeys', () => {
@@ -35,8 +35,6 @@ describe('getUnusedConfigKeys', () => {
expect(
await getUnusedConfigKeys({
coreHandledConfigPaths: [],
- pluginSpecs: [],
- disabledPluginSpecs: [],
settings: {},
legacyConfig: getConfig(),
})
@@ -47,8 +45,6 @@ describe('getUnusedConfigKeys', () => {
expect(
await getUnusedConfigKeys({
coreHandledConfigPaths: [],
- pluginSpecs: [],
- disabledPluginSpecs: [],
settings: {
presentInBoth: true,
alsoInBoth: 'someValue',
@@ -65,8 +61,6 @@ describe('getUnusedConfigKeys', () => {
expect(
await getUnusedConfigKeys({
coreHandledConfigPaths: [],
- pluginSpecs: [],
- disabledPluginSpecs: [],
settings: {
presentInBoth: true,
},
@@ -82,8 +76,6 @@ describe('getUnusedConfigKeys', () => {
expect(
await getUnusedConfigKeys({
coreHandledConfigPaths: [],
- pluginSpecs: [],
- disabledPluginSpecs: [],
settings: {
presentInBoth: true,
onlyInSetting: 'value',
@@ -99,8 +91,6 @@ describe('getUnusedConfigKeys', () => {
expect(
await getUnusedConfigKeys({
coreHandledConfigPaths: [],
- pluginSpecs: [],
- disabledPluginSpecs: [],
settings: {
elasticsearch: {
username: 'foo',
@@ -121,8 +111,6 @@ describe('getUnusedConfigKeys', () => {
expect(
await getUnusedConfigKeys({
coreHandledConfigPaths: [],
- pluginSpecs: [],
- disabledPluginSpecs: [],
settings: {
env: 'development',
},
@@ -139,8 +127,6 @@ describe('getUnusedConfigKeys', () => {
expect(
await getUnusedConfigKeys({
coreHandledConfigPaths: [],
- pluginSpecs: [],
- disabledPluginSpecs: [],
settings: {
prop: ['a', 'b', 'c'],
},
@@ -152,40 +138,10 @@ describe('getUnusedConfigKeys', () => {
});
});
- it('ignores config for plugins that are disabled', async () => {
- expect(
- await getUnusedConfigKeys({
- coreHandledConfigPaths: [],
- pluginSpecs: [],
- disabledPluginSpecs: [
- ({
- id: 'foo',
- getConfigPrefix: () => 'foo.bar',
- } as unknown) as LegacyPluginSpec,
- ],
- settings: {
- foo: {
- bar: {
- unused: true,
- },
- },
- plugin: {
- missingProp: false,
- },
- },
- legacyConfig: getConfig({
- prop: 'a',
- }),
- })
- ).toEqual(['plugin.missingProp']);
- });
-
it('ignores properties managed by the new platform', async () => {
expect(
await getUnusedConfigKeys({
coreHandledConfigPaths: ['core', 'foo.bar'],
- pluginSpecs: [],
- disabledPluginSpecs: [],
settings: {
core: {
prop: 'value',
@@ -204,8 +160,6 @@ describe('getUnusedConfigKeys', () => {
expect(
await getUnusedConfigKeys({
coreHandledConfigPaths: ['core', 'array'],
- pluginSpecs: [],
- disabledPluginSpecs: [],
settings: {
core: {
prop: 'value',
diff --git a/src/core/server/legacy/config/get_unused_config_keys.ts b/src/core/server/legacy/config/get_unused_config_keys.ts
index c15c3b270df05..5bbe169033e39 100644
--- a/src/core/server/legacy/config/get_unused_config_keys.ts
+++ b/src/core/server/legacy/config/get_unused_config_keys.ts
@@ -19,30 +19,20 @@
import { difference } from 'lodash';
import { getFlattenedObject } from '@kbn/std';
-import { unset } from '../../../../legacy/utils';
import { hasConfigPathIntersection } from '../../config';
-import { LegacyPluginSpec, LegacyConfig, LegacyVars } from '../types';
+import { LegacyConfig, LegacyVars } from '../types';
const getFlattenedKeys = (object: object) => Object.keys(getFlattenedObject(object));
export async function getUnusedConfigKeys({
coreHandledConfigPaths,
- pluginSpecs,
- disabledPluginSpecs,
settings,
legacyConfig,
}: {
coreHandledConfigPaths: string[];
- pluginSpecs: LegacyPluginSpec[];
- disabledPluginSpecs: LegacyPluginSpec[];
settings: LegacyVars;
legacyConfig: LegacyConfig;
}) {
- // remove config values from disabled plugins
- for (const spec of disabledPluginSpecs) {
- unset(settings, spec.getConfigPrefix());
- }
-
const inputKeys = getFlattenedKeys(settings);
const appliedKeys = getFlattenedKeys(legacyConfig.get());
diff --git a/src/core/server/legacy/index.ts b/src/core/server/legacy/index.ts
index 6b0963e3129c6..1a0bc8955be0f 100644
--- a/src/core/server/legacy/index.ts
+++ b/src/core/server/legacy/index.ts
@@ -20,8 +20,6 @@
/** @internal */
export { ensureValidConfiguration } from './config';
/** @internal */
-export { LegacyInternals } from './legacy_internals';
-/** @internal */
export { LegacyService, ILegacyService } from './legacy_service';
/** @internal */
export * from './types';
diff --git a/src/core/server/legacy/legacy_internals.test.ts b/src/core/server/legacy/legacy_internals.test.ts
deleted file mode 100644
index 935e36a989a0c..0000000000000
--- a/src/core/server/legacy/legacy_internals.test.ts
+++ /dev/null
@@ -1,211 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { Server } from 'hapi';
-
-import { configMock } from '../config/mocks';
-import { httpServiceMock } from '../http/http_service.mock';
-import { httpServerMock } from '../http/http_server.mocks';
-import { findLegacyPluginSpecsMock } from './legacy_service.test.mocks';
-import { LegacyInternals } from './legacy_internals';
-import { ILegacyInternals, LegacyConfig, LegacyVars, LegacyUiExports } from './types';
-
-function varsProvider(vars: LegacyVars, configValue?: any) {
- return {
- fn: jest.fn().mockReturnValue(vars),
- pluginSpec: {
- readConfigValue: jest.fn().mockReturnValue(configValue),
- },
- };
-}
-
-describe('LegacyInternals', () => {
- describe('getInjectedUiAppVars()', () => {
- let uiExports: LegacyUiExports;
- let config: LegacyConfig;
- let server: Server;
- let legacyInternals: ILegacyInternals;
-
- beforeEach(async () => {
- uiExports = findLegacyPluginSpecsMock().uiExports;
- config = configMock.create() as any;
- server = httpServiceMock.createInternalSetupContract().server;
- legacyInternals = new LegacyInternals(uiExports, config, server);
- });
-
- it('gets with no injectors', async () => {
- await expect(legacyInternals.getInjectedUiAppVars('core')).resolves.toMatchInlineSnapshot(
- `Object {}`
- );
- });
-
- it('gets with no matching injectors', async () => {
- const injector = jest.fn().mockResolvedValue({ not: 'core' });
- legacyInternals.injectUiAppVars('not-core', injector);
-
- await expect(legacyInternals.getInjectedUiAppVars('core')).resolves.toMatchInlineSnapshot(
- `Object {}`
- );
- expect(injector).not.toHaveBeenCalled();
- });
-
- it('gets with single matching injector', async () => {
- const injector = jest.fn().mockResolvedValue({ is: 'core' });
- legacyInternals.injectUiAppVars('core', injector);
-
- await expect(legacyInternals.getInjectedUiAppVars('core')).resolves.toMatchInlineSnapshot(`
- Object {
- "is": "core",
- }
- `);
- expect(injector).toHaveBeenCalled();
- });
-
- it('gets with multiple matching injectors', async () => {
- const injectors = [
- jest.fn().mockResolvedValue({ is: 'core' }),
- jest.fn().mockReturnValue({ sync: 'injector' }),
- jest.fn().mockResolvedValue({ is: 'merged-core' }),
- ];
-
- injectors.forEach((injector) => legacyInternals.injectUiAppVars('core', injector));
-
- await expect(legacyInternals.getInjectedUiAppVars('core')).resolves.toMatchInlineSnapshot(`
- Object {
- "is": "merged-core",
- "sync": "injector",
- }
- `);
- expect(injectors[0]).toHaveBeenCalled();
- expect(injectors[1]).toHaveBeenCalled();
- expect(injectors[2]).toHaveBeenCalled();
- });
- });
-
- describe('getVars()', () => {
- let uiExports: LegacyUiExports;
- let config: LegacyConfig;
- let server: Server;
- let legacyInternals: LegacyInternals;
-
- beforeEach(async () => {
- uiExports = findLegacyPluginSpecsMock().uiExports;
- config = configMock.create() as any;
- server = httpServiceMock.createInternalSetupContract().server;
- legacyInternals = new LegacyInternals(uiExports, config, server);
- });
-
- it('gets: no default injectors, no injected vars replacers, no ui app injectors, no inject arg', async () => {
- const vars = await legacyInternals.getVars('core', httpServerMock.createRawRequest());
-
- expect(vars).toMatchInlineSnapshot(`Object {}`);
- });
-
- it('gets: with default injectors, no injected vars replacers, no ui app injectors, no inject arg', async () => {
- uiExports.defaultInjectedVarProviders = [
- varsProvider({ alpha: 'alpha' }),
- varsProvider({ gamma: 'gamma' }),
- varsProvider({ alpha: 'beta' }),
- ];
-
- const vars = await legacyInternals.getVars('core', httpServerMock.createRawRequest());
-
- expect(vars).toMatchInlineSnapshot(`
- Object {
- "alpha": "beta",
- "gamma": "gamma",
- }
- `);
- });
-
- it('gets: no default injectors, with injected vars replacers, with ui app injectors, no inject arg', async () => {
- uiExports.injectedVarsReplacers = [
- jest.fn(async (vars) => ({ ...vars, added: 'key' })),
- jest.fn((vars) => vars),
- jest.fn((vars) => ({ replaced: 'all' })),
- jest.fn(async (vars) => ({ ...vars, added: 'last-key' })),
- ];
-
- const request = httpServerMock.createRawRequest();
- const vars = await legacyInternals.getVars('core', request);
-
- expect(vars).toMatchInlineSnapshot(`
- Object {
- "added": "last-key",
- "replaced": "all",
- }
- `);
- });
-
- it('gets: no default injectors, no injected vars replacers, with ui app injectors, no inject arg', async () => {
- legacyInternals.injectUiAppVars('core', async () => ({ is: 'core' }));
- legacyInternals.injectUiAppVars('core', () => ({ sync: 'injector' }));
- legacyInternals.injectUiAppVars('core', async () => ({ is: 'merged-core' }));
-
- const vars = await legacyInternals.getVars('core', httpServerMock.createRawRequest());
-
- expect(vars).toMatchInlineSnapshot(`
- Object {
- "is": "merged-core",
- "sync": "injector",
- }
- `);
- });
-
- it('gets: no default injectors, no injected vars replacers, no ui app injectors, with inject arg', async () => {
- const vars = await legacyInternals.getVars('core', httpServerMock.createRawRequest(), {
- injected: 'arg',
- });
-
- expect(vars).toMatchInlineSnapshot(`
- Object {
- "injected": "arg",
- }
- `);
- });
-
- it('gets: with default injectors, with injected vars replacers, with ui app injectors, with inject arg', async () => {
- uiExports.defaultInjectedVarProviders = [
- varsProvider({ alpha: 'alpha' }),
- varsProvider({ gamma: 'gamma' }),
- varsProvider({ alpha: 'beta' }),
- ];
- uiExports.injectedVarsReplacers = [jest.fn(async (vars) => ({ ...vars, gamma: 'delta' }))];
-
- legacyInternals.injectUiAppVars('core', async () => ({ is: 'core' }));
- legacyInternals.injectUiAppVars('core', () => ({ sync: 'injector' }));
- legacyInternals.injectUiAppVars('core', async () => ({ is: 'merged-core' }));
-
- const vars = await legacyInternals.getVars('core', httpServerMock.createRawRequest(), {
- injected: 'arg',
- sync: 'arg',
- });
-
- expect(vars).toMatchInlineSnapshot(`
- Object {
- "alpha": "beta",
- "gamma": "delta",
- "injected": "arg",
- "is": "merged-core",
- "sync": "arg",
- }
- `);
- });
- });
-});
diff --git a/src/core/server/legacy/legacy_internals.ts b/src/core/server/legacy/legacy_internals.ts
deleted file mode 100644
index 628ca4ed12f6b..0000000000000
--- a/src/core/server/legacy/legacy_internals.ts
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { Server } from 'hapi';
-
-import { KibanaRequest, LegacyRequest } from '../http';
-import { ensureRawRequest } from '../http/router';
-import { mergeVars } from './merge_vars';
-import { ILegacyInternals, LegacyVars, VarsInjector, LegacyConfig, LegacyUiExports } from './types';
-
-/**
- * @internal
- * @deprecated
- */
-export class LegacyInternals implements ILegacyInternals {
- private readonly injectors = new Map>();
- private cachedDefaultVars?: LegacyVars;
-
- constructor(
- private readonly uiExports: LegacyUiExports,
- private readonly config: LegacyConfig,
- private readonly server: Server
- ) {}
-
- private get defaultVars(): LegacyVars {
- if (this.cachedDefaultVars) {
- return this.cachedDefaultVars;
- }
-
- const { defaultInjectedVarProviders = [] } = this.uiExports;
-
- return (this.cachedDefaultVars = defaultInjectedVarProviders.reduce(
- (vars, { fn, pluginSpec }) =>
- mergeVars(vars, fn(this.server, pluginSpec.readConfigValue(this.config, []))),
- {}
- ));
- }
-
- private replaceVars(vars: LegacyVars, request: KibanaRequest | LegacyRequest) {
- const { injectedVarsReplacers = [] } = this.uiExports;
-
- return injectedVarsReplacers.reduce(
- async (injected, replacer) =>
- replacer(await injected, ensureRawRequest(request), this.server),
- Promise.resolve(vars)
- );
- }
-
- public injectUiAppVars(id: string, injector: VarsInjector) {
- if (!this.injectors.has(id)) {
- this.injectors.set(id, new Set());
- }
-
- this.injectors.get(id)!.add(injector);
- }
-
- public getInjectedUiAppVars(id: string) {
- return [...(this.injectors.get(id) || [])].reduce(
- async (promise, injector) => ({
- ...(await promise),
- ...(await injector()),
- }),
- Promise.resolve({})
- );
- }
-
- public async getVars(
- id: string,
- request: KibanaRequest | LegacyRequest,
- injected: LegacyVars = {}
- ) {
- return this.replaceVars(
- mergeVars(this.defaultVars, await this.getInjectedUiAppVars(id), injected),
- request
- );
- }
-}
diff --git a/src/core/server/legacy/legacy_service.mock.ts b/src/core/server/legacy/legacy_service.mock.ts
index ab501bd6bb53b..781874f702cf8 100644
--- a/src/core/server/legacy/legacy_service.mock.ts
+++ b/src/core/server/legacy/legacy_service.mock.ts
@@ -18,26 +18,13 @@
*/
import type { PublicMethodsOf } from '@kbn/utility-types';
import { LegacyService } from './legacy_service';
-import { LegacyConfig, LegacyServiceDiscoverPlugins, LegacyServiceSetupDeps } from './types';
+import { LegacyConfig, LegacyServiceSetupDeps } from './types';
type LegacyServiceMock = jest.Mocked & { legacyId: symbol }>;
-const createDiscoverPluginsMock = (): LegacyServiceDiscoverPlugins => ({
- pluginSpecs: [],
- uiExports: {},
- navLinks: [],
- pluginExtendedConfig: {
- get: jest.fn(),
- has: jest.fn(),
- set: jest.fn(),
- },
- disabledPluginSpecs: [],
- settings: {},
-});
-
const createLegacyServiceMock = (): LegacyServiceMock => ({
legacyId: Symbol(),
- discoverPlugins: jest.fn().mockResolvedValue(createDiscoverPluginsMock()),
+ setupLegacyConfig: jest.fn(),
setup: jest.fn(),
start: jest.fn(),
stop: jest.fn(),
@@ -52,6 +39,5 @@ const createLegacyConfigMock = (): jest.Mocked => ({
export const legacyServiceMock = {
create: createLegacyServiceMock,
createSetupContract: (deps: LegacyServiceSetupDeps) => createLegacyServiceMock().setup(deps),
- createDiscoverPlugins: createDiscoverPluginsMock,
createLegacyConfig: createLegacyConfigMock,
};
diff --git a/src/core/server/legacy/legacy_service.test.mocks.ts b/src/core/server/legacy/legacy_service.test.mocks.ts
deleted file mode 100644
index 9ad554d63add0..0000000000000
--- a/src/core/server/legacy/legacy_service.test.mocks.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { LegacyVars } from './types';
-
-export const findLegacyPluginSpecsMock = jest.fn().mockImplementation((settings: LegacyVars) => ({
- pluginSpecs: [],
- pluginExtendedConfig: {
- has: jest.fn(),
- get: jest.fn().mockReturnValue(settings),
- set: jest.fn(),
- },
- disabledPluginSpecs: [],
- uiExports: {},
- navLinks: [],
-}));
-jest.doMock('./plugins/find_legacy_plugin_specs', () => ({
- findLegacyPluginSpecs: findLegacyPluginSpecsMock,
-}));
-
-export const logLegacyThirdPartyPluginDeprecationWarningMock = jest.fn();
-jest.doMock('./plugins/log_legacy_plugins_warning', () => ({
- logLegacyThirdPartyPluginDeprecationWarning: logLegacyThirdPartyPluginDeprecationWarningMock,
-}));
diff --git a/src/core/server/legacy/legacy_service.test.ts b/src/core/server/legacy/legacy_service.test.ts
index a6fe95deb3979..57009f0d35c16 100644
--- a/src/core/server/legacy/legacy_service.test.ts
+++ b/src/core/server/legacy/legacy_service.test.ts
@@ -19,10 +19,6 @@
jest.mock('../../../legacy/server/kbn_server');
jest.mock('./cluster_manager');
-import {
- findLegacyPluginSpecsMock,
- logLegacyThirdPartyPluginDeprecationWarningMock,
-} from './legacy_service.test.mocks';
import { BehaviorSubject, throwError } from 'rxjs';
import { REPO_ROOT } from '@kbn/dev-utils';
@@ -44,8 +40,7 @@ import { capabilitiesServiceMock } from '../capabilities/capabilities_service.mo
import { httpResourcesMock } from '../http_resources/http_resources_service.mock';
import { setupMock as renderingServiceMock } from '../rendering/__mocks__/rendering_service';
import { environmentServiceMock } from '../environment/environment_service.mock';
-import { findLegacyPluginSpecs } from './plugins';
-import { LegacyVars, LegacyServiceSetupDeps, LegacyServiceStartDeps } from './types';
+import { LegacyServiceSetupDeps, LegacyServiceStartDeps } from './types';
import { LegacyService } from './legacy_service';
import { coreMock } from '../mocks';
import { statusServiceMock } from '../status/status_service.mock';
@@ -73,7 +68,6 @@ beforeEach(() => {
configService = configServiceMock.create();
environmentSetup = environmentServiceMock.createSetupContract();
- findLegacyPluginSpecsMock.mockClear();
MockKbnServer.prototype.ready = jest.fn().mockReturnValue(Promise.resolve());
MockKbnServer.prototype.listen = jest.fn();
@@ -149,10 +143,10 @@ describe('once LegacyService is set up with connection info', () => {
coreId,
env,
logger,
- configService: configService as any,
+ configService,
});
- await legacyService.discoverPlugins();
+ await legacyService.setupLegacyConfig();
await legacyService.setup(setupDeps);
await legacyService.start(startDeps);
@@ -160,13 +154,14 @@ describe('once LegacyService is set up with connection info', () => {
expect(MockKbnServer).toHaveBeenCalledWith(
{ path: { autoListen: true }, server: { autoListen: true } }, // Because of the mock, path also gets the value
expect.objectContaining({ get: expect.any(Function) }),
- expect.any(Object),
- { disabledPluginSpecs: [], pluginSpecs: [], uiExports: {}, navLinks: [] }
+ expect.any(Object)
+ );
+ expect(MockKbnServer.mock.calls[0][1].get()).toEqual(
+ expect.objectContaining({
+ path: expect.objectContaining({ autoListen: true }),
+ server: expect.objectContaining({ autoListen: true }),
+ })
);
- expect(MockKbnServer.mock.calls[0][1].get()).toEqual({
- path: { autoListen: true },
- server: { autoListen: true },
- });
const [mockKbnServer] = MockKbnServer.mock.instances;
expect(mockKbnServer.listen).toHaveBeenCalledTimes(1);
@@ -182,7 +177,7 @@ describe('once LegacyService is set up with connection info', () => {
logger,
configService: configService as any,
});
- await legacyService.discoverPlugins();
+ await legacyService.setupLegacyConfig();
await legacyService.setup(setupDeps);
await legacyService.start(startDeps);
@@ -190,13 +185,12 @@ describe('once LegacyService is set up with connection info', () => {
expect(MockKbnServer).toHaveBeenCalledWith(
{ path: { autoListen: false }, server: { autoListen: true } },
expect.objectContaining({ get: expect.any(Function) }),
- expect.any(Object),
- { disabledPluginSpecs: [], pluginSpecs: [], uiExports: {}, navLinks: [] }
+ expect.any(Object)
);
- expect(MockKbnServer.mock.calls[0][1].get()).toEqual({
- path: { autoListen: false },
- server: { autoListen: true },
- });
+
+ const legacyConfig = MockKbnServer.mock.calls[0][1].get();
+ expect(legacyConfig.path.autoListen).toBe(false);
+ expect(legacyConfig.server.autoListen).toBe(true);
const [mockKbnServer] = MockKbnServer.mock.instances;
expect(mockKbnServer.ready).toHaveBeenCalledTimes(1);
@@ -214,7 +208,7 @@ describe('once LegacyService is set up with connection info', () => {
configService: configService as any,
});
- await legacyService.discoverPlugins();
+ await legacyService.setupLegacyConfig();
await legacyService.setup(setupDeps);
await expect(legacyService.start(startDeps)).rejects.toThrowErrorMatchingInlineSnapshot(
`"something failed"`
@@ -234,11 +228,11 @@ describe('once LegacyService is set up with connection info', () => {
configService: configService as any,
});
- await expect(legacyService.discoverPlugins()).rejects.toThrowErrorMatchingInlineSnapshot(
+ await expect(legacyService.setupLegacyConfig()).rejects.toThrowErrorMatchingInlineSnapshot(
`"something failed"`
);
await expect(legacyService.setup(setupDeps)).rejects.toThrowErrorMatchingInlineSnapshot(
- `"Legacy service has not discovered legacy plugins yet. Ensure LegacyService.discoverPlugins() is called before LegacyService.setup()"`
+ `"Legacy config not initialized yet. Ensure LegacyService.setupLegacyConfig() is called before LegacyService.setup()"`
);
await expect(legacyService.start(startDeps)).rejects.toThrowErrorMatchingInlineSnapshot(
`"Legacy service is not setup yet."`
@@ -255,7 +249,7 @@ describe('once LegacyService is set up with connection info', () => {
logger,
configService: configService as any,
});
- await legacyService.discoverPlugins();
+ await legacyService.setupLegacyConfig();
await legacyService.setup(setupDeps);
await legacyService.start(startDeps);
@@ -276,7 +270,7 @@ describe('once LegacyService is set up with connection info', () => {
logger,
configService: configService as any,
});
- await legacyService.discoverPlugins();
+ await legacyService.setupLegacyConfig();
await legacyService.setup(setupDeps);
await legacyService.start(startDeps);
@@ -301,7 +295,7 @@ describe('once LegacyService is set up with connection info', () => {
logger,
configService: configService as any,
});
- await legacyService.discoverPlugins();
+ await legacyService.setupLegacyConfig();
await legacyService.setup(setupDeps);
await legacyService.start(startDeps);
@@ -321,7 +315,7 @@ describe('once LegacyService is set up without connection info', () => {
let legacyService: LegacyService;
beforeEach(async () => {
legacyService = new LegacyService({ coreId, env, logger, configService: configService as any });
- await legacyService.discoverPlugins();
+ await legacyService.setupLegacyConfig();
await legacyService.setup(setupDeps);
await legacyService.start(startDeps);
});
@@ -331,13 +325,13 @@ describe('once LegacyService is set up without connection info', () => {
expect(MockKbnServer).toHaveBeenCalledWith(
{ path: {}, server: { autoListen: true } },
expect.objectContaining({ get: expect.any(Function) }),
- expect.any(Object),
- { disabledPluginSpecs: [], pluginSpecs: [], uiExports: {}, navLinks: [] }
+ expect.any(Object)
+ );
+ expect(MockKbnServer.mock.calls[0][1].get()).toEqual(
+ expect.objectContaining({
+ server: expect.objectContaining({ autoListen: true }),
+ })
);
- expect(MockKbnServer.mock.calls[0][1].get()).toEqual({
- path: {},
- server: { autoListen: true },
- });
});
test('reconfigures logging configuration if new config is received.', async () => {
@@ -375,7 +369,7 @@ describe('once LegacyService is set up in `devClusterMaster` mode', () => {
configService: configService as any,
});
- await devClusterLegacyService.discoverPlugins();
+ await devClusterLegacyService.setupLegacyConfig();
await devClusterLegacyService.setup(setupDeps);
await devClusterLegacyService.start(startDeps);
@@ -404,7 +398,7 @@ describe('once LegacyService is set up in `devClusterMaster` mode', () => {
configService: configService as any,
});
- await devClusterLegacyService.discoverPlugins();
+ await devClusterLegacyService.setupLegacyConfig();
await devClusterLegacyService.setup(setupDeps);
await devClusterLegacyService.start(startDeps);
@@ -434,50 +428,6 @@ describe('start', () => {
});
});
-describe('#discoverPlugins()', () => {
- it('calls findLegacyPluginSpecs with correct parameters', async () => {
- const legacyService = new LegacyService({
- coreId,
- env,
- logger,
- configService: configService as any,
- });
-
- await legacyService.discoverPlugins();
- expect(findLegacyPluginSpecs).toHaveBeenCalledTimes(1);
- expect(findLegacyPluginSpecs).toHaveBeenCalledWith(expect.any(Object), logger, env.packageInfo);
- });
-
- it(`logs deprecations for legacy third party plugins`, async () => {
- const pluginSpecs = [{ getId: () => 'pluginA' }, { getId: () => 'pluginB' }];
- findLegacyPluginSpecsMock.mockImplementation(
- (settings) =>
- Promise.resolve({
- pluginSpecs,
- pluginExtendedConfig: settings,
- disabledPluginSpecs: [],
- uiExports: {},
- navLinks: [],
- }) as any
- );
-
- const legacyService = new LegacyService({
- coreId,
- env,
- logger,
- configService: configService as any,
- });
-
- await legacyService.discoverPlugins();
-
- expect(logLegacyThirdPartyPluginDeprecationWarningMock).toHaveBeenCalledTimes(1);
- expect(logLegacyThirdPartyPluginDeprecationWarningMock).toHaveBeenCalledWith({
- specs: pluginSpecs,
- log: expect.any(Object),
- });
- });
-});
-
test('Sets the server.uuid property on the legacy configuration', async () => {
configService.atPath.mockReturnValue(new BehaviorSubject({ autoListen: true }));
const legacyService = new LegacyService({
@@ -489,23 +439,8 @@ test('Sets the server.uuid property on the legacy configuration', async () => {
environmentSetup.instanceUuid = 'UUID_FROM_SERVICE';
- const configSetMock = jest.fn();
-
- findLegacyPluginSpecsMock.mockImplementation((settings: LegacyVars) => ({
- pluginSpecs: [],
- pluginExtendedConfig: {
- has: jest.fn(),
- get: jest.fn().mockReturnValue(settings),
- set: configSetMock,
- },
- disabledPluginSpecs: [],
- uiExports: {},
- navLinks: [],
- }));
-
- await legacyService.discoverPlugins();
+ const { legacyConfig } = await legacyService.setupLegacyConfig();
await legacyService.setup(setupDeps);
- expect(configSetMock).toHaveBeenCalledTimes(1);
- expect(configSetMock).toHaveBeenCalledWith('server.uuid', 'UUID_FROM_SERVICE');
+ expect(legacyConfig.get('server.uuid')).toBe('UUID_FROM_SERVICE');
});
diff --git a/src/core/server/legacy/legacy_service.ts b/src/core/server/legacy/legacy_service.ts
index 4dc22be2a9971..086e20c98c1a3 100644
--- a/src/core/server/legacy/legacy_service.ts
+++ b/src/core/server/legacy/legacy_service.ts
@@ -16,11 +16,14 @@
* specific language governing permissions and limitations
* under the License.
*/
-import type { PublicMethodsOf } from '@kbn/utility-types';
+
import { combineLatest, ConnectableObservable, EMPTY, Observable, Subscription } from 'rxjs';
import { first, map, publishReplay, tap } from 'rxjs/operators';
-
+import type { PublicMethodsOf } from '@kbn/utility-types';
import { PathConfigType } from '@kbn/utils';
+
+// @ts-expect-error legacy config class
+import { Config as LegacyConfigClass } from '../../../legacy/server/config';
import { CoreService } from '../../types';
import { Config } from '../config';
import { CoreContext } from '../core_context';
@@ -28,17 +31,7 @@ import { CspConfigType, config as cspConfig } from '../csp';
import { DevConfig, DevConfigType, config as devConfig } from '../dev';
import { BasePathProxyServer, HttpConfig, HttpConfigType, config as httpConfig } from '../http';
import { Logger } from '../logging';
-import { findLegacyPluginSpecs, logLegacyThirdPartyPluginDeprecationWarning } from './plugins';
-import {
- ILegacyInternals,
- LegacyServiceSetupDeps,
- LegacyServiceStartDeps,
- LegacyPlugins,
- LegacyServiceDiscoverPlugins,
- LegacyConfig,
- LegacyVars,
-} from './types';
-import { LegacyInternals } from './legacy_internals';
+import { LegacyServiceSetupDeps, LegacyServiceStartDeps, LegacyConfig, LegacyVars } from './types';
import { CoreSetup, CoreStart } from '..';
interface LegacyKbnServer {
@@ -80,9 +73,7 @@ export class LegacyService implements CoreService {
private setupDeps?: LegacyServiceSetupDeps;
private update$?: ConnectableObservable<[Config, PathConfigType]>;
private legacyRawConfig?: LegacyConfig;
- private legacyPlugins?: LegacyPlugins;
private settings?: LegacyVars;
- public legacyInternals?: ILegacyInternals;
constructor(private readonly coreContext: CoreContext) {
const { logger, configService } = coreContext;
@@ -97,11 +88,11 @@ export class LegacyService implements CoreService {
).pipe(map(([http, csp]) => new HttpConfig(http, csp)));
}
- public async discoverPlugins(): Promise {
- this.update$ = combineLatest(
+ public async setupLegacyConfig() {
+ this.update$ = combineLatest([
this.coreContext.configService.getConfig$(),
- this.coreContext.configService.atPath('path')
- ).pipe(
+ this.coreContext.configService.atPath('path'),
+ ]).pipe(
tap(([config, pathConfig]) => {
if (this.kbnServer !== undefined) {
this.kbnServer.applyLoggingConfiguration(getLegacyRawConfig(config, pathConfig));
@@ -120,74 +111,33 @@ export class LegacyService implements CoreService {
)
.toPromise();
- const {
- pluginSpecs,
- pluginExtendedConfig,
- disabledPluginSpecs,
- uiExports,
- navLinks,
- } = await findLegacyPluginSpecs(
- this.settings,
- this.coreContext.logger,
- this.coreContext.env.packageInfo
- );
-
- logLegacyThirdPartyPluginDeprecationWarning({
- specs: pluginSpecs,
- log: this.log,
- });
-
- this.legacyPlugins = {
- pluginSpecs,
- disabledPluginSpecs,
- uiExports,
- navLinks,
- };
-
- this.legacyRawConfig = pluginExtendedConfig;
-
- // check for unknown uiExport types
- if (uiExports.unknown && uiExports.unknown.length > 0) {
- throw new Error(
- `Unknown uiExport types: ${uiExports.unknown
- .map(({ pluginSpec, type }) => `${type} from ${pluginSpec.getId()}`)
- .join(', ')}`
- );
- }
+ this.legacyRawConfig = LegacyConfigClass.withDefaultSchema(this.settings);
return {
- pluginSpecs,
- disabledPluginSpecs,
- uiExports,
- navLinks,
- pluginExtendedConfig,
settings: this.settings,
+ legacyConfig: this.legacyRawConfig!,
};
}
public async setup(setupDeps: LegacyServiceSetupDeps) {
this.log.debug('setting up legacy service');
- if (!this.legacyPlugins) {
+ if (!this.legacyRawConfig) {
throw new Error(
- 'Legacy service has not discovered legacy plugins yet. Ensure LegacyService.discoverPlugins() is called before LegacyService.setup()'
+ 'Legacy config not initialized yet. Ensure LegacyService.setupLegacyConfig() is called before LegacyService.setup()'
);
}
// propagate the instance uuid to the legacy config, as it was the legacy way to access it.
this.legacyRawConfig!.set('server.uuid', setupDeps.core.environment.instanceUuid);
+
this.setupDeps = setupDeps;
- this.legacyInternals = new LegacyInternals(
- this.legacyPlugins.uiExports,
- this.legacyRawConfig!,
- setupDeps.core.http.server
- );
}
public async start(startDeps: LegacyServiceStartDeps) {
const { setupDeps } = this;
- if (!setupDeps || !this.legacyPlugins) {
+ if (!setupDeps || !this.legacyRawConfig) {
throw new Error('Legacy service is not setup yet.');
}
@@ -201,8 +151,7 @@ export class LegacyService implements CoreService {
this.settings!,
this.legacyRawConfig!,
setupDeps,
- startDeps,
- this.legacyPlugins!
+ startDeps
);
}
}
@@ -245,8 +194,7 @@ export class LegacyService implements CoreService {
settings: LegacyVars,
config: LegacyConfig,
setupDeps: LegacyServiceSetupDeps,
- startDeps: LegacyServiceStartDeps,
- legacyPlugins: LegacyPlugins
+ startDeps: LegacyServiceStartDeps
) {
const coreStart: CoreStart = {
capabilities: startDeps.core.capabilities,
@@ -337,36 +285,26 @@ export class LegacyService implements CoreService {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const KbnServer = require('../../../legacy/server/kbn_server');
- const kbnServer: LegacyKbnServer = new KbnServer(
- settings,
- config,
- {
- env: {
- mode: this.coreContext.env.mode,
- packageInfo: this.coreContext.env.packageInfo,
- },
- setupDeps: {
- core: coreSetup,
- plugins: setupDeps.plugins,
- },
- startDeps: {
- core: coreStart,
- plugins: startDeps.plugins,
- },
- __internals: {
- http: {
- registerStaticDir: setupDeps.core.http.registerStaticDir,
- },
- hapiServer: setupDeps.core.http.server,
- uiPlugins: setupDeps.uiPlugins,
- elasticsearch: setupDeps.core.elasticsearch,
- rendering: setupDeps.core.rendering,
- legacy: this.legacyInternals,
- },
- logger: this.coreContext.logger,
+ const kbnServer: LegacyKbnServer = new KbnServer(settings, config, {
+ env: {
+ mode: this.coreContext.env.mode,
+ packageInfo: this.coreContext.env.packageInfo,
},
- legacyPlugins
- );
+ setupDeps: {
+ core: coreSetup,
+ plugins: setupDeps.plugins,
+ },
+ startDeps: {
+ core: coreStart,
+ plugins: startDeps.plugins,
+ },
+ __internals: {
+ hapiServer: setupDeps.core.http.server,
+ uiPlugins: setupDeps.uiPlugins,
+ rendering: setupDeps.core.rendering,
+ },
+ logger: this.coreContext.logger,
+ });
// The kbnWorkerType check is necessary to prevent the repl
// from being started multiple times in different processes.
diff --git a/src/core/server/legacy/plugins/collect_ui_exports.js b/src/core/server/legacy/plugins/collect_ui_exports.js
deleted file mode 100644
index 842ab554d79d1..0000000000000
--- a/src/core/server/legacy/plugins/collect_ui_exports.js
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-// eslint-disable-next-line @kbn/eslint/no-restricted-paths
-export { collectUiExports } from '../../../../legacy/ui/ui_exports/collect_ui_exports';
diff --git a/src/core/server/legacy/plugins/find_legacy_plugin_specs.ts b/src/core/server/legacy/plugins/find_legacy_plugin_specs.ts
deleted file mode 100644
index cb4277b130a88..0000000000000
--- a/src/core/server/legacy/plugins/find_legacy_plugin_specs.ts
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { Observable, merge, forkJoin } from 'rxjs';
-import { toArray, tap, distinct, map } from 'rxjs/operators';
-
-import {
- findPluginSpecs,
- defaultConfig,
- // @ts-expect-error
-} from '../../../../legacy/plugin_discovery/find_plugin_specs.js';
-// @ts-expect-error
-import { collectUiExports as collectLegacyUiExports } from './collect_ui_exports';
-
-import { LoggerFactory } from '../../logging';
-import { PackageInfo } from '../../config';
-import { LegacyUiExports, LegacyPluginSpec, LegacyPluginPack, LegacyConfig } from '../types';
-
-export async function findLegacyPluginSpecs(
- settings: unknown,
- loggerFactory: LoggerFactory,
- packageInfo: PackageInfo
-) {
- const configToMutate: LegacyConfig = defaultConfig(settings);
- const {
- pack$,
- invalidDirectoryError$,
- invalidPackError$,
- otherError$,
- deprecation$,
- invalidVersionSpec$,
- spec$,
- disabledSpec$,
- }: {
- pack$: Observable;
- invalidDirectoryError$: Observable<{ path: string }>;
- invalidPackError$: Observable<{ path: string }>;
- otherError$: Observable;
- deprecation$: Observable<{ spec: LegacyPluginSpec; message: string }>;
- invalidVersionSpec$: Observable;
- spec$: Observable;
- disabledSpec$: Observable;
- } = findPluginSpecs(settings, configToMutate) as any;
-
- const logger = loggerFactory.get('legacy-plugins');
-
- const log$ = merge(
- pack$.pipe(
- tap((definition) => {
- const path = definition.getPath();
- logger.debug(`Found plugin at ${path}`, { path });
- })
- ),
-
- invalidDirectoryError$.pipe(
- tap((error) => {
- logger.warn(`Unable to scan directory for plugins "${error.path}"`, {
- err: error,
- dir: error.path,
- });
- })
- ),
-
- invalidPackError$.pipe(
- tap((error) => {
- logger.warn(`Skipping non-plugin directory at ${error.path}`, {
- path: error.path,
- });
- })
- ),
-
- otherError$.pipe(
- tap((error) => {
- // rethrow unhandled errors, which will fail the server
- throw error;
- })
- ),
-
- invalidVersionSpec$.pipe(
- map((spec) => {
- const name = spec.getId();
- const pluginVersion = spec.getExpectedKibanaVersion();
- const kibanaVersion = packageInfo.version;
- return `Plugin "${name}" was disabled because it expected Kibana version "${pluginVersion}", and found "${kibanaVersion}".`;
- }),
- distinct(),
- tap((message) => {
- logger.warn(message);
- })
- ),
-
- deprecation$.pipe(
- tap(({ spec, message }) => {
- const deprecationLogger = loggerFactory.get(
- 'plugins',
- spec.getConfigPrefix(),
- 'config',
- 'deprecation'
- );
- deprecationLogger.warn(message);
- })
- )
- );
-
- const [disabledPluginSpecs, pluginSpecs] = await forkJoin(
- disabledSpec$.pipe(toArray()),
- spec$.pipe(toArray()),
- log$.pipe(toArray())
- ).toPromise();
- const uiExports: LegacyUiExports = collectLegacyUiExports(pluginSpecs);
-
- return {
- disabledPluginSpecs,
- pluginSpecs,
- pluginExtendedConfig: configToMutate,
- uiExports,
- navLinks: [],
- };
-}
diff --git a/src/core/server/legacy/plugins/index.ts b/src/core/server/legacy/plugins/index.ts
deleted file mode 100644
index 7ec5dbc1983ab..0000000000000
--- a/src/core/server/legacy/plugins/index.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-export { findLegacyPluginSpecs } from './find_legacy_plugin_specs';
-export { logLegacyThirdPartyPluginDeprecationWarning } from './log_legacy_plugins_warning';
diff --git a/src/core/server/legacy/plugins/log_legacy_plugins_warning.test.ts b/src/core/server/legacy/plugins/log_legacy_plugins_warning.test.ts
deleted file mode 100644
index 2317f1036ce42..0000000000000
--- a/src/core/server/legacy/plugins/log_legacy_plugins_warning.test.ts
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { loggerMock } from '../../logging/logger.mock';
-import { logLegacyThirdPartyPluginDeprecationWarning } from './log_legacy_plugins_warning';
-import { LegacyPluginSpec } from '../types';
-
-const createPluginSpec = ({ id, path }: { id: string; path: string }): LegacyPluginSpec => {
- return {
- getId: () => id,
- getExpectedKibanaVersion: () => 'kibana',
- getConfigPrefix: () => 'plugin.config',
- getPack: () => ({
- getPath: () => path,
- }),
- };
-};
-
-describe('logLegacyThirdPartyPluginDeprecationWarning', () => {
- let log: ReturnType;
-
- beforeEach(() => {
- log = loggerMock.create();
- });
-
- it('logs warning for third party plugins', () => {
- logLegacyThirdPartyPluginDeprecationWarning({
- specs: [createPluginSpec({ id: 'plugin', path: '/some-external-path' })],
- log,
- });
- expect(log.warn).toHaveBeenCalledTimes(1);
- expect(log.warn.mock.calls[0]).toMatchInlineSnapshot(`
- Array [
- "Some installed third party plugin(s) [plugin] are using the legacy plugin format and will no longer work in a future Kibana release. Please refer to https://ela.st/kibana-breaking-changes-8-0 for a list of breaking changes and https://ela.st/kibana-platform-migration for documentation on how to migrate legacy plugins.",
- ]
- `);
- });
-
- it('lists all the deprecated plugins and only log once', () => {
- logLegacyThirdPartyPluginDeprecationWarning({
- specs: [
- createPluginSpec({ id: 'pluginA', path: '/abs/path/to/pluginA' }),
- createPluginSpec({ id: 'pluginB', path: '/abs/path/to/pluginB' }),
- createPluginSpec({ id: 'pluginC', path: '/abs/path/to/pluginC' }),
- ],
- log,
- });
- expect(log.warn).toHaveBeenCalledTimes(1);
- expect(log.warn.mock.calls[0]).toMatchInlineSnapshot(`
- Array [
- "Some installed third party plugin(s) [pluginA, pluginB, pluginC] are using the legacy plugin format and will no longer work in a future Kibana release. Please refer to https://ela.st/kibana-breaking-changes-8-0 for a list of breaking changes and https://ela.st/kibana-platform-migration for documentation on how to migrate legacy plugins.",
- ]
- `);
- });
-
- it('does not log warning for internal legacy plugins', () => {
- logLegacyThirdPartyPluginDeprecationWarning({
- specs: [
- createPluginSpec({
- id: 'plugin',
- path: '/absolute/path/to/kibana/src/legacy/core_plugins',
- }),
- createPluginSpec({
- id: 'plugin',
- path: '/absolute/path/to/kibana/x-pack',
- }),
- ],
- log,
- });
-
- expect(log.warn).not.toHaveBeenCalled();
- });
-});
diff --git a/src/core/server/legacy/plugins/log_legacy_plugins_warning.ts b/src/core/server/legacy/plugins/log_legacy_plugins_warning.ts
deleted file mode 100644
index 4a4a1b1b0e60b..0000000000000
--- a/src/core/server/legacy/plugins/log_legacy_plugins_warning.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { Logger } from '../../logging';
-import { LegacyPluginSpec } from '../types';
-
-const internalPaths = ['/src/legacy/core_plugins', '/x-pack'];
-
-// Use shortened URLs so destinations can be updated if/when documentation moves
-// All platform team members have access to edit these
-const breakingChangesUrl = 'https://ela.st/kibana-breaking-changes-8-0';
-const migrationGuideUrl = 'https://ela.st/kibana-platform-migration';
-
-export const logLegacyThirdPartyPluginDeprecationWarning = ({
- specs,
- log,
-}: {
- specs: LegacyPluginSpec[];
- log: Logger;
-}) => {
- const thirdPartySpecs = specs.filter(isThirdPartyPluginSpec);
- if (thirdPartySpecs.length > 0) {
- const pluginIds = thirdPartySpecs.map((spec) => spec.getId());
- log.warn(
- `Some installed third party plugin(s) [${pluginIds.join(
- ', '
- )}] are using the legacy plugin format and will no longer work in a future Kibana release. ` +
- `Please refer to ${breakingChangesUrl} for a list of breaking changes ` +
- `and ${migrationGuideUrl} for documentation on how to migrate legacy plugins.`
- );
- }
-};
-
-const isThirdPartyPluginSpec = (spec: LegacyPluginSpec): boolean => {
- const pluginPath = spec.getPack().getPath();
- return !internalPaths.some((internalPath) => pluginPath.indexOf(internalPath) > -1);
-};
diff --git a/src/core/server/legacy/types.ts b/src/core/server/legacy/types.ts
index 1105308fd44cf..12bfddfff1961 100644
--- a/src/core/server/legacy/types.ts
+++ b/src/core/server/legacy/types.ts
@@ -17,10 +17,6 @@
* under the License.
*/
-import { Server } from 'hapi';
-
-import { ChromeNavLink } from '../../public';
-import { KibanaRequest, LegacyRequest } from '../http';
import { InternalCoreSetup, InternalCoreStart } from '../internal_types';
import { PluginsServiceSetup, PluginsServiceStart, UiPlugins } from '../plugins';
import { InternalRenderingServiceSetup } from '../rendering';
@@ -50,91 +46,6 @@ export interface LegacyConfig {
set(config: LegacyVars): void;
}
-/**
- * @internal
- * @deprecated
- */
-export interface LegacyPluginPack {
- getPath(): string;
-}
-
-/**
- * @internal
- * @deprecated
- */
-export interface LegacyPluginSpec {
- getId: () => unknown;
- getExpectedKibanaVersion: () => string;
- getConfigPrefix: () => string;
- getPack: () => LegacyPluginPack;
-}
-
-/**
- * @internal
- * @deprecated
- */
-export interface VarsProvider {
- fn: (server: Server, configValue: any) => LegacyVars;
- pluginSpec: {
- readConfigValue(config: any, key: string | string[]): any;
- };
-}
-
-/**
- * @internal
- * @deprecated
- */
-export type VarsInjector = () => LegacyVars;
-
-/**
- * @internal
- * @deprecated
- */
-export type VarsReplacer = (
- vars: LegacyVars,
- request: LegacyRequest,
- server: Server
-) => LegacyVars | Promise;
-
-/**
- * @internal
- * @deprecated
- */
-export type LegacyNavLinkSpec = Partial & {
- id: string;
- title: string;
- url: string;
-};
-
-/**
- * @internal
- * @deprecated
- */
-export type LegacyAppSpec = Partial & {
- pluginId?: string;
- listed?: boolean;
-};
-
-/**
- * @internal
- * @deprecated
- */
-export type LegacyNavLink = Omit & {
- order: number;
-};
-
-/**
- * @internal
- * @deprecated
- */
-export interface LegacyUiExports {
- defaultInjectedVarProviders?: VarsProvider[];
- injectedVarsReplacers?: VarsReplacer[];
- navLinkSpecs?: LegacyNavLinkSpec[] | null;
- uiAppSpecs?: Array;
- unknown?: [{ pluginSpec: LegacyPluginSpec; type: unknown }];
-}
-
/**
* @public
* @deprecated
@@ -158,43 +69,7 @@ export interface LegacyServiceStartDeps {
* @internal
* @deprecated
*/
-export interface ILegacyInternals {
- /**
- * Inject UI app vars for a particular plugin
- */
- injectUiAppVars(id: string, injector: VarsInjector): void;
-
- /**
- * Get all the merged injected UI app vars for a particular plugin
- */
- getInjectedUiAppVars(id: string): Promise;
-
- /**
- * Get the metadata vars for a particular plugin
- */
- getVars(
- id: string,
- request: KibanaRequest | LegacyRequest,
- injected?: LegacyVars
- ): Promise;
-}
-
-/**
- * @internal
- * @deprecated
- */
-export interface LegacyPlugins {
- disabledPluginSpecs: LegacyPluginSpec[];
- pluginSpecs: LegacyPluginSpec[];
- uiExports: LegacyUiExports;
- navLinks: LegacyNavLink[];
-}
-
-/**
- * @internal
- * @deprecated
- */
-export interface LegacyServiceDiscoverPlugins extends LegacyPlugins {
- pluginExtendedConfig: LegacyConfig;
+export interface LegacyServiceSetupConfig {
+ legacyConfig: LegacyConfig;
settings: LegacyVars;
}
diff --git a/src/core/server/rendering/__mocks__/params.ts b/src/core/server/rendering/__mocks__/params.ts
index 0901cec768cd2..ae3830f703a53 100644
--- a/src/core/server/rendering/__mocks__/params.ts
+++ b/src/core/server/rendering/__mocks__/params.ts
@@ -20,19 +20,16 @@
import { mockCoreContext } from '../../core_context.mock';
import { httpServiceMock } from '../../http/http_service.mock';
import { pluginServiceMock } from '../../plugins/plugins_service.mock';
-import { legacyServiceMock } from '../../legacy/legacy_service.mock';
import { statusServiceMock } from '../../status/status_service.mock';
const context = mockCoreContext.create();
const http = httpServiceMock.createInternalSetupContract();
const uiPlugins = pluginServiceMock.createUiPlugins();
-const legacyPlugins = legacyServiceMock.createDiscoverPlugins();
const status = statusServiceMock.createInternalSetupContract();
export const mockRenderingServiceParams = context;
export const mockRenderingSetupDeps = {
http,
- legacyPlugins,
uiPlugins,
status,
};
diff --git a/src/core/server/rendering/__mocks__/rendering_service.ts b/src/core/server/rendering/__mocks__/rendering_service.ts
index 179a09b8619b0..01d084f9ae53c 100644
--- a/src/core/server/rendering/__mocks__/rendering_service.ts
+++ b/src/core/server/rendering/__mocks__/rendering_service.ts
@@ -27,11 +27,9 @@ export const setupMock: jest.Mocked = {
render: jest.fn(),
};
export const mockSetup = jest.fn().mockResolvedValue(setupMock);
-export const mockStart = jest.fn();
export const mockStop = jest.fn();
export const mockRenderingService: jest.Mocked = {
setup: mockSetup,
- start: mockStart,
stop: mockStop,
};
export const RenderingService = jest.fn(
diff --git a/src/core/server/rendering/__snapshots__/rendering_service.test.ts.snap b/src/core/server/rendering/__snapshots__/rendering_service.test.ts.snap
index ab828a1780425..07ca59a48c6b0 100644
--- a/src/core/server/rendering/__snapshots__/rendering_service.test.ts.snap
+++ b/src/core/server/rendering/__snapshots__/rendering_service.test.ts.snap
@@ -27,15 +27,6 @@ Object {
"translationsUrl": "/mock-server-basepath/translations/en.json",
},
"legacyMetadata": Object {
- "app": Object {},
- "basePath": "/mock-server-basepath",
- "branch": Any,
- "buildNum": Any,
- "buildSha": Any,
- "bundleId": "app:core",
- "devMode": true,
- "nav": Array [],
- "serverName": "http-server-test",
"uiSettings": Object {
"defaults": Object {
"registered": Object {
@@ -44,7 +35,6 @@ Object {
},
"user": Object {},
},
- "version": Any,
},
"serverBasePath": "/mock-server-basepath",
"uiPlugins": Array [],
@@ -80,15 +70,6 @@ Object {
"translationsUrl": "/mock-server-basepath/translations/en.json",
},
"legacyMetadata": Object {
- "app": Object {},
- "basePath": "/mock-server-basepath",
- "branch": Any,
- "buildNum": Any,
- "buildSha": Any,
- "bundleId": "app:core",
- "devMode": true,
- "nav": Array [],
- "serverName": "http-server-test",
"uiSettings": Object {
"defaults": Object {
"registered": Object {
@@ -97,7 +78,6 @@ Object {
},
"user": Object {},
},
- "version": Any,
},
"serverBasePath": "/mock-server-basepath",
"uiPlugins": Array [],
@@ -133,15 +113,6 @@ Object {
"translationsUrl": "/mock-server-basepath/translations/en.json",
},
"legacyMetadata": Object {
- "app": Object {},
- "basePath": "/mock-server-basepath",
- "branch": Any,
- "buildNum": Any,
- "buildSha": Any,
- "bundleId": "app:core",
- "devMode": true,
- "nav": Array [],
- "serverName": "http-server-test",
"uiSettings": Object {
"defaults": Object {
"registered": Object {
@@ -154,7 +125,6 @@ Object {
},
},
},
- "version": Any,
},
"serverBasePath": "/mock-server-basepath",
"uiPlugins": Array [],
@@ -190,15 +160,6 @@ Object {
"translationsUrl": "/translations/en.json",
},
"legacyMetadata": Object {
- "app": Object {},
- "basePath": "",
- "branch": Any,
- "buildNum": Any,
- "buildSha": Any,
- "bundleId": "app:core",
- "devMode": true,
- "nav": Array [],
- "serverName": "http-server-test",
"uiSettings": Object {
"defaults": Object {
"registered": Object {
@@ -207,7 +168,6 @@ Object {
},
"user": Object {},
},
- "version": Any,
},
"serverBasePath": "/mock-server-basepath",
"uiPlugins": Array [],
@@ -243,15 +203,6 @@ Object {
"translationsUrl": "/mock-server-basepath/translations/en.json",
},
"legacyMetadata": Object {
- "app": Object {},
- "basePath": "/mock-server-basepath",
- "branch": Any,
- "buildNum": Any,
- "buildSha": Any,
- "bundleId": "app:core",
- "devMode": true,
- "nav": Array [],
- "serverName": "http-server-test",
"uiSettings": Object {
"defaults": Object {
"registered": Object {
@@ -260,7 +211,6 @@ Object {
},
"user": Object {},
},
- "version": Any,
},
"serverBasePath": "/mock-server-basepath",
"uiPlugins": Array [],
diff --git a/src/core/server/rendering/rendering_service.test.ts b/src/core/server/rendering/rendering_service.test.ts
index 254bafed5b194..08978cd1df64d 100644
--- a/src/core/server/rendering/rendering_service.test.ts
+++ b/src/core/server/rendering/rendering_service.test.ts
@@ -43,12 +43,6 @@ const INJECTED_METADATA = {
version: expect.any(String),
},
},
- legacyMetadata: {
- branch: expect.any(String),
- buildNum: expect.any(Number),
- buildSha: expect.any(String),
- version: expect.any(String),
- },
};
const { createKibanaRequest, createRawRequest } = httpServerMock;
@@ -72,13 +66,6 @@ describe('RenderingService', () => {
registered: { name: 'title' },
});
render = (await service.setup(mockRenderingSetupDeps)).render;
- await service.start({
- legacy: {
- legacyInternals: {
- getVars: () => ({}),
- },
- },
- } as any);
});
it('renders "core" page', async () => {
diff --git a/src/core/server/rendering/rendering_service.tsx b/src/core/server/rendering/rendering_service.tsx
index 7761c89044f6f..738787f940905 100644
--- a/src/core/server/rendering/rendering_service.tsx
+++ b/src/core/server/rendering/rendering_service.tsx
@@ -20,14 +20,11 @@
import React from 'react';
import { renderToStaticMarkup } from 'react-dom/server';
import { take } from 'rxjs/operators';
-
import { i18n } from '@kbn/i18n';
import { UiPlugins } from '../plugins';
-import { CoreService } from '../../types';
import { CoreContext } from '../core_context';
import { Template } from './views';
-import { LegacyService } from '../legacy';
import {
IRenderOptions,
RenderingSetupDeps,
@@ -36,25 +33,20 @@ import {
} from './types';
/** @internal */
-export class RenderingService implements CoreService {
- private legacyInternals?: LegacyService['legacyInternals'];
+export class RenderingService {
constructor(private readonly coreContext: CoreContext) {}
public async setup({
http,
status,
- legacyPlugins,
uiPlugins,
}: RenderingSetupDeps): Promise {
return {
render: async (
request,
uiSettings,
- { app = { getId: () => 'core' }, includeUserSettings = true, vars }: IRenderOptions = {}
+ { includeUserSettings = true, vars }: IRenderOptions = {}
) => {
- if (!this.legacyInternals) {
- throw new Error('Cannot render before "start"');
- }
const env = {
mode: this.coreContext.env.mode,
packageInfo: this.coreContext.env.packageInfo,
@@ -65,7 +57,6 @@ export class RenderingService implements CoreService ({
id,
@@ -96,16 +87,6 @@ export class RenderingService implements CoreService;
}>;
legacyMetadata: {
- app: { getId(): string };
- bundleId: string;
- nav: LegacyNavLink[];
- version: string;
- branch: string;
- buildNum: number;
- buildSha: string;
- serverName: string;
- devMode: boolean;
- basePath: string;
uiSettings: {
defaults: Record;
user: Record>;
@@ -78,7 +67,6 @@ export interface RenderingMetadata {
/** @internal */
export interface RenderingSetupDeps {
http: InternalHttpServiceSetup;
- legacyPlugins: LegacyServiceDiscoverPlugins;
status: InternalStatusServiceSetup;
uiPlugins: UiPlugins;
}
@@ -91,14 +79,6 @@ export interface IRenderOptions {
*/
includeUserSettings?: boolean;
- /**
- * Render the bootstrapped HTML content for an optional legacy application.
- * Defaults to `core`.
- * @deprecated for legacy use only, remove with ui_render_mixin
- * @internal
- */
- app?: { getId(): string };
-
/**
* Inject custom vars into the page metadata.
* @deprecated for legacy use only, remove with ui_render_mixin
diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md
index 8a764d9bd2f66..cc51d27589ce7 100644
--- a/src/core/server/server.api.md
+++ b/src/core/server/server.api.md
@@ -864,10 +864,6 @@ export interface IndexSettingsDeprecationInfo {
// @public (undocumented)
export interface IRenderOptions {
- // @internal @deprecated
- app?: {
- getId(): string;
- };
includeUserSettings?: boolean;
// @internal @deprecated
vars?: Record;
@@ -1286,21 +1282,6 @@ export class LegacyElasticsearchErrorHelpers {
static isNotAuthorizedError(error: any): error is LegacyElasticsearchError;
}
-// Warning: (ae-forgotten-export) The symbol "ILegacyInternals" needs to be exported by the entry point index.d.ts
-//
-// @internal @deprecated (undocumented)
-export class LegacyInternals implements ILegacyInternals {
- constructor(uiExports: LegacyUiExports, config: LegacyConfig, server: Server);
- // (undocumented)
- getInjectedUiAppVars(id: string): Promise>;
- // (undocumented)
- getVars(id: string, request: KibanaRequest | LegacyRequest, injected?: LegacyVars): Promise>;
- // Warning: (ae-forgotten-export) The symbol "VarsInjector" needs to be exported by the entry point index.d.ts
- //
- // (undocumented)
- injectUiAppVars(id: string, injector: VarsInjector): void;
- }
-
// @public @deprecated (undocumented)
export interface LegacyRequest extends Request {
}
@@ -1312,16 +1293,6 @@ export class LegacyScopedClusterClient implements ILegacyScopedClusterClient {
callAsInternalUser(endpoint: string, clientParams?: Record, options?: LegacyCallAPIOptions): Promise;
}
-// Warning: (ae-forgotten-export) The symbol "LegacyPlugins" needs to be exported by the entry point index.d.ts
-//
-// @internal @deprecated (undocumented)
-export interface LegacyServiceDiscoverPlugins extends LegacyPlugins {
- // (undocumented)
- pluginExtendedConfig: LegacyConfig;
- // (undocumented)
- settings: LegacyVars;
-}
-
// @public @deprecated (undocumented)
export interface LegacyServiceSetupDeps {
// Warning: (ae-forgotten-export) The symbol "LegacyCoreSetup" needs to be exported by the entry point index.d.ts
@@ -1346,31 +1317,6 @@ export interface LegacyServiceStartDeps {
plugins: Record;
}
-// @internal @deprecated (undocumented)
-export interface LegacyUiExports {
- // Warning: (ae-forgotten-export) The symbol "VarsProvider" needs to be exported by the entry point index.d.ts
- //
- // (undocumented)
- defaultInjectedVarProviders?: VarsProvider[];
- // Warning: (ae-forgotten-export) The symbol "VarsReplacer" needs to be exported by the entry point index.d.ts
- //
- // (undocumented)
- injectedVarsReplacers?: VarsReplacer[];
- // Warning: (ae-forgotten-export) The symbol "LegacyNavLinkSpec" needs to be exported by the entry point index.d.ts
- //
- // (undocumented)
- navLinkSpecs?: LegacyNavLinkSpec[] | null;
- // Warning: (ae-forgotten-export) The symbol "LegacyAppSpec" needs to be exported by the entry point index.d.ts
- //
- // (undocumented)
- uiAppSpecs?: Array;
- // (undocumented)
- unknown?: [{
- pluginSpec: LegacyPluginSpec;
- type: unknown;
- }];
-}
-
// Warning: (ae-forgotten-export) The symbol "lifecycleResponseFactory" needs to be exported by the entry point index.d.ts
//
// @public
@@ -2734,7 +2680,6 @@ export const validBodyOutput: readonly ["data", "stream"];
// Warnings were encountered during analysis:
//
// src/core/server/http/router/response.ts:316:3 - (ae-forgotten-export) The symbol "KibanaResponse" needs to be exported by the entry point index.d.ts
-// src/core/server/legacy/types.ts:135:16 - (ae-forgotten-export) The symbol "LegacyPluginSpec" needs to be exported by the entry point index.d.ts
// src/core/server/plugins/types.ts:274:3 - (ae-forgotten-export) The symbol "KibanaConfigType" needs to be exported by the entry point index.d.ts
// src/core/server/plugins/types.ts:274:3 - (ae-forgotten-export) The symbol "SharedGlobalConfigKeys" needs to be exported by the entry point index.d.ts
// src/core/server/plugins/types.ts:277:3 - (ae-forgotten-export) The symbol "SavedObjectsConfigType" needs to be exported by the entry point index.d.ts
diff --git a/src/core/server/server.ts b/src/core/server/server.ts
index 8502f563cb0c2..5935636d54f9d 100644
--- a/src/core/server/server.ts
+++ b/src/core/server/server.ts
@@ -113,11 +113,12 @@ export class Server {
const { pluginTree, uiPlugins } = await this.plugins.discover({
environment: environmentSetup,
});
- const legacyPlugins = await this.legacy.discoverPlugins();
+ const legacyConfigSetup = await this.legacy.setupLegacyConfig();
// Immediately terminate in case of invalid configuration
+ // This needs to be done after plugin discovery
await this.configService.validate();
- await ensureValidConfiguration(this.configService, legacyPlugins);
+ await ensureValidConfiguration(this.configService, legacyConfigSetup);
const contextServiceSetup = this.context.setup({
// We inject a fake "legacy plugin" with dependencies on every plugin so that legacy plugins:
@@ -166,7 +167,6 @@ export class Server {
const renderingSetup = await this.rendering.setup({
http: httpSetup,
status: statusSetup,
- legacyPlugins,
uiPlugins,
});
@@ -248,10 +248,6 @@ export class Server {
await this.http.start();
- await this.rendering.start({
- legacy: this.legacy,
- });
-
return this.coreStart;
}
diff --git a/src/dev/jest/config.js b/src/dev/jest/config.js
index 5d31db63773fa..3c556a4f1ba3c 100644
--- a/src/dev/jest/config.js
+++ b/src/dev/jest/config.js
@@ -55,7 +55,6 @@ export default {
'@elastic/eui$': '/node_modules/@elastic/eui/test-env',
'@elastic/eui/lib/(.*)?': '/node_modules/@elastic/eui/test-env/$1',
'^src/plugins/(.*)': '/src/plugins/$1',
- '^uiExports/(.*)': '/src/dev/jest/mocks/file_mock.js',
'^test_utils/(.*)': '/src/test_utils/public/$1',
'^fixtures/(.*)': '/src/fixtures/$1',
'\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$':
diff --git a/src/legacy/plugin_discovery/README.md b/src/legacy/plugin_discovery/README.md
deleted file mode 100644
index 83e7c10d16fff..0000000000000
--- a/src/legacy/plugin_discovery/README.md
+++ /dev/null
@@ -1,148 +0,0 @@
-# Plugin Discovery
-
-The plugin discovery module defines the core plugin loading logic used by the Kibana server. It exports functions for
-
-
-## `findPluginSpecs(settings, [config])`
-
-Finds [`PluginSpec`][PluginSpec] objects
-
-### params
- - `settings`: the same settings object accepted by [`KbnServer`][KbnServer]
- - `[config]`: Optional - a [`Config`][Config] service. Using this param causes `findPluginSpecs()` to modify `config`'s schema to support the configuration for each discovered [`PluginSpec`][PluginSpec]. If you can, please use the [`Config`][Config] service produced by `extendedConfig$` rather than passing in an existing service so that `findPluginSpecs()` is side-effect free.
-
-### return value
-
-`findPluginSpecs()` returns an object of Observables which produce values at different parts of the process. Since the Observables are all aware of their own dependencies you can subscribe to any combination (within the same tick) and only the necessary plugin logic will be executed.
-
-If you *never* subscribe to any of the Observables then plugin discovery won't actually run.
-
- - `pack$`: emits every [`PluginPack`][PluginPack] found
- - `invalidDirectoryError$: Observable`: emits [`InvalidDirectoryError`][Errors]s caused by `settings.plugins.scanDirs` values that don't point to actual directories. `findPluginSpecs()` will not abort when this error is encountered.
- - `invalidPackError$: Observable`: emits [`InvalidPackError`][Errors]s caused by children of `settings.plugins.scanDirs` or `settings.plugins.paths` values which don't meet the requirements of a [`PluginPack`][PluginPack] (probably missing a `package.json`). `findPluginSpecs()` will not abort when this error is encountered.
- - `deprecation$: Observable`: emits deprecation warnings that are produces when reading each [`PluginPack`][PluginPack]'s configuration
- - `extendedConfig$: Observable`: emits the [`Config`][Config] service that was passed to `findPluginSpecs()` (or created internally if none was passed) after it has been extended with the configuration from each plugin
- - `spec$: Observable`: emits every *enabled* [`PluginSpec`][PluginSpec] defined by the discovered [`PluginPack`][PluginPack]s
- - `disabledSpec$: Observable`: emits every *disabled* [`PluginSpec`][PluginSpec] defined by the discovered [`PluginPack`][PluginPack]s
- - `invalidVersionSpec$: Observable`: emits every [`PluginSpec`][PluginSpec] who's required kibana version does not match the version exposed by `config.get('pkg.version')`
-
-### example
-
-Just get the plugin specs, only fail if there is an uncaught error of some sort:
-```js
-const { pack$ } = findPluginSpecs(settings);
-const packs = await pack$.pipe(toArray()).toPromise()
-```
-
-Just log the deprecation messages:
-```js
-const { deprecation$ } = findPluginSpecs(settings);
-for (const warning of await deprecation$.pipe(toArray()).toPromise()) {
- console.log('DEPRECATION:', warning)
-}
-```
-
-Get the packs but fail if any packs are invalid:
-```js
-const { pack$, invalidDirectoryError$ } = findPluginSpecs(settings);
-const packs = await Rx.merge(
- pack$.pipe(toArray()),
-
- // if we ever get an InvalidDirectoryError, throw it
- // into the stream so that all streams are unsubscribed,
- // the discovery process is aborted, and the promise rejects
- invalidDirectoryError$.pipe(
- map(error => { throw error })
- ),
-).toPromise()
-```
-
-Handle everything
-```js
-const {
- pack$,
- invalidDirectoryError$,
- invalidPackError$,
- deprecation$,
- extendedConfig$,
- spec$,
- disabledSpecs$,
- invalidVersionSpec$,
-} = findPluginSpecs(settings);
-
-Rx.merge(
- pack$.pipe(
- tap(pluginPack => console.log('Found plugin pack', pluginPack))
- ),
-
- invalidDirectoryError$.pipe(
- tap(error => console.log('Invalid directory error', error))
- ),
-
- invalidPackError$.pipe(
- tap(error => console.log('Invalid plugin pack error', error))
- ),
-
- deprecation$.pipe(
- tap(msg => console.log('DEPRECATION:', msg))
- ),
-
- extendedConfig$.pipe(
- tap(config => console.log('config service extended by plugins', config))
- ),
-
- spec$.pipe(
- tap(pluginSpec => console.log('enabled plugin spec found', spec))
- ),
-
- disabledSpec$.pipe(
- tap(pluginSpec => console.log('disabled plugin spec found', spec))
- ),
-
- invalidVersionSpec$.pipe(
- tap(pluginSpec => console.log('plugin spec with invalid version found', spec))
- ),
-)
-.toPromise()
-.then(() => {
- console.log('plugin discovery complete')
-})
-.catch((error) => {
- console.log('plugin discovery failed', error)
-})
-
-```
-
-## `reduceExportSpecs(pluginSpecs, reducers, [defaults={}])`
-
-Reduces every value exported by the [`PluginSpec`][PluginSpec]s to produce a single value. If an exported value is an array each item in the array will be reduced individually. If the exported value is `undefined` it will be ignored. The reducer is called with the signature:
-
-```js
-reducer(
- // the result of the previous reducer call, or `defaults`
- acc: any,
- // the exported value, found at `uiExports[type]` or `uiExports[type][i]`
- // in the PluginSpec config.
- spec: any,
- // the key in `uiExports` where this export was found
- type: string,
- // the PluginSpec which exported this spec
- pluginSpec: PluginSpec
-)
-```
-
-## `new PluginPack(options)` class
-
-Only exported so that `PluginPack` instances can be created in tests and used in place of on-disk plugin fixtures. Use `findPluginSpecs()`, or the cached result of a call to `findPluginSpecs()` (like `kbnServer.pluginSpecs`) any time you might need access to `PluginPack` objects in distributed code.
-
-### params
-
- - `options.path`: absolute path to where this plugin pack was found, this is normally a direct child of `./src/legacy/core_plugins` or `./plugins`
- - `options.pkg`: the parsed `package.json` for this pack, used for defaults in `PluginSpec` objects defined by this pack
- - `options.provider`: the default export of the pack, a function which is called with the `PluginSpec` class which should return one or more `PluginSpec` objects.
-
-[PluginPack]: ./plugin_pack/plugin_pack.js "PluginPath class definition"
-[PluginSpec]: ./plugin_spec/plugin_spec.js "PluginSpec class definition"
-[Errors]: ./errors.js "PluginDiscover specific error types"
-[KbnServer]: ../server/kbn_server.js "KbnServer class definition"
-[Config]: ../server/config/config.js "KbnServer/Config class definition"
diff --git a/src/legacy/plugin_discovery/__tests__/find_plugin_specs.js b/src/legacy/plugin_discovery/__tests__/find_plugin_specs.js
deleted file mode 100644
index e6af23d69c549..0000000000000
--- a/src/legacy/plugin_discovery/__tests__/find_plugin_specs.js
+++ /dev/null
@@ -1,219 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { resolve } from 'path';
-import { toArray } from 'rxjs/operators';
-
-import expect from '@kbn/expect';
-import { isEqual } from 'lodash';
-import { findPluginSpecs } from '../find_plugin_specs';
-import { PluginSpec } from '../plugin_spec';
-
-const PLUGIN_FIXTURES = resolve(__dirname, 'fixtures/plugins');
-const CONFLICT_FIXTURES = resolve(__dirname, 'fixtures/conflicts');
-
-describe('plugin discovery', () => {
- describe('findPluginSpecs()', function () {
- this.timeout(10000);
-
- describe('spec$', () => {
- it('finds specs for specified plugin paths', async () => {
- const { spec$ } = findPluginSpecs({
- plugins: {
- paths: [
- resolve(PLUGIN_FIXTURES, 'foo'),
- resolve(PLUGIN_FIXTURES, 'bar'),
- resolve(PLUGIN_FIXTURES, 'broken'),
- ],
- },
- });
-
- const specs = await spec$.pipe(toArray()).toPromise();
- expect(specs).to.have.length(3);
- specs.forEach((spec) => {
- expect(spec).to.be.a(PluginSpec);
- });
- expect(specs.map((s) => s.getId()).sort()).to.eql(['bar:one', 'bar:two', 'foo']);
- });
-
- it('finds all specs in scanDirs', async () => {
- const { spec$ } = findPluginSpecs({
- // used to ensure the dev_mode plugin is enabled
- env: 'development',
-
- plugins: {
- scanDirs: [PLUGIN_FIXTURES],
- },
- });
-
- const specs = await spec$.pipe(toArray()).toPromise();
- expect(specs).to.have.length(3);
- specs.forEach((spec) => {
- expect(spec).to.be.a(PluginSpec);
- });
- expect(specs.map((s) => s.getId()).sort()).to.eql(['bar:one', 'bar:two', 'foo']);
- });
-
- it('does not find disabled plugins', async () => {
- const { spec$ } = findPluginSpecs({
- 'bar:one': {
- enabled: false,
- },
-
- plugins: {
- paths: [
- resolve(PLUGIN_FIXTURES, 'foo'),
- resolve(PLUGIN_FIXTURES, 'bar'),
- resolve(PLUGIN_FIXTURES, 'broken'),
- ],
- },
- });
-
- const specs = await spec$.pipe(toArray()).toPromise();
- expect(specs).to.have.length(2);
- specs.forEach((spec) => {
- expect(spec).to.be.a(PluginSpec);
- });
- expect(specs.map((s) => s.getId()).sort()).to.eql(['bar:two', 'foo']);
- });
-
- it('dedupes duplicate packs', async () => {
- const { spec$ } = findPluginSpecs({
- plugins: {
- scanDirs: [PLUGIN_FIXTURES],
- paths: [
- resolve(PLUGIN_FIXTURES, 'foo'),
- resolve(PLUGIN_FIXTURES, 'foo'),
- resolve(PLUGIN_FIXTURES, 'bar'),
- resolve(PLUGIN_FIXTURES, 'bar'),
- resolve(PLUGIN_FIXTURES, 'broken'),
- resolve(PLUGIN_FIXTURES, 'broken'),
- ],
- },
- });
-
- const specs = await spec$.pipe(toArray()).toPromise();
- expect(specs).to.have.length(3);
- specs.forEach((spec) => {
- expect(spec).to.be.a(PluginSpec);
- });
- expect(specs.map((s) => s.getId()).sort()).to.eql(['bar:one', 'bar:two', 'foo']);
- });
-
- describe('conflicting plugin spec ids', () => {
- it('fails with informative message', async () => {
- const { spec$ } = findPluginSpecs({
- plugins: {
- scanDirs: [],
- paths: [resolve(CONFLICT_FIXTURES, 'foo')],
- },
- });
-
- try {
- await spec$.pipe(toArray()).toPromise();
- throw new Error('expected spec$ to throw an error');
- } catch (error) {
- expect(error.message).to.contain('Multiple plugins found with the id "foo"');
- expect(error.message).to.contain(CONFLICT_FIXTURES);
- }
- });
- });
- });
-
- describe('packageJson$', () => {
- const checkPackageJsons = (packageJsons) => {
- expect(packageJsons).to.have.length(2);
- const package1 = packageJsons.find((packageJson) =>
- isEqual(
- {
- directoryPath: resolve(PLUGIN_FIXTURES, 'foo'),
- contents: {
- name: 'foo',
- version: 'kibana',
- },
- },
- packageJson
- )
- );
- expect(package1).to.be.an(Object);
- const package2 = packageJsons.find((packageJson) =>
- isEqual(
- {
- directoryPath: resolve(PLUGIN_FIXTURES, 'bar'),
- contents: {
- name: 'foo',
- version: 'kibana',
- },
- },
- packageJson
- )
- );
- expect(package2).to.be.an(Object);
- };
-
- it('finds packageJson for specified plugin paths', async () => {
- const { packageJson$ } = findPluginSpecs({
- plugins: {
- paths: [
- resolve(PLUGIN_FIXTURES, 'foo'),
- resolve(PLUGIN_FIXTURES, 'bar'),
- resolve(PLUGIN_FIXTURES, 'broken'),
- ],
- },
- });
-
- const packageJsons = await packageJson$.pipe(toArray()).toPromise();
- checkPackageJsons(packageJsons);
- });
-
- it('finds all packageJsons in scanDirs', async () => {
- const { packageJson$ } = findPluginSpecs({
- // used to ensure the dev_mode plugin is enabled
- env: 'development',
-
- plugins: {
- scanDirs: [PLUGIN_FIXTURES],
- },
- });
-
- const packageJsons = await packageJson$.pipe(toArray()).toPromise();
- checkPackageJsons(packageJsons);
- });
-
- it('dedupes duplicate packageJson', async () => {
- const { packageJson$ } = findPluginSpecs({
- plugins: {
- scanDirs: [PLUGIN_FIXTURES],
- paths: [
- resolve(PLUGIN_FIXTURES, 'foo'),
- resolve(PLUGIN_FIXTURES, 'foo'),
- resolve(PLUGIN_FIXTURES, 'bar'),
- resolve(PLUGIN_FIXTURES, 'bar'),
- resolve(PLUGIN_FIXTURES, 'broken'),
- resolve(PLUGIN_FIXTURES, 'broken'),
- ],
- },
- });
-
- const packageJsons = await packageJson$.pipe(toArray()).toPromise();
- checkPackageJsons(packageJsons);
- });
- });
- });
-});
diff --git a/src/legacy/plugin_discovery/__tests__/fixtures/conflicts/foo/index.js b/src/legacy/plugin_discovery/__tests__/fixtures/conflicts/foo/index.js
deleted file mode 100644
index fcbe3487463b7..0000000000000
--- a/src/legacy/plugin_discovery/__tests__/fixtures/conflicts/foo/index.js
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-export default function (kibana) {
- return [
- // two plugins exported without ids will both inherit
- // the id of the pack and conflict
- new kibana.Plugin({}),
- new kibana.Plugin({}),
- ];
-}
diff --git a/src/legacy/plugin_discovery/__tests__/fixtures/conflicts/foo/package.json b/src/legacy/plugin_discovery/__tests__/fixtures/conflicts/foo/package.json
deleted file mode 100644
index e43c2f0bc984c..0000000000000
--- a/src/legacy/plugin_discovery/__tests__/fixtures/conflicts/foo/package.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "name": "foo",
- "version": "kibana"
-}
diff --git a/src/legacy/plugin_discovery/__tests__/fixtures/plugins/bar/index.js b/src/legacy/plugin_discovery/__tests__/fixtures/plugins/bar/index.js
deleted file mode 100644
index 0eef126f2255a..0000000000000
--- a/src/legacy/plugin_discovery/__tests__/fixtures/plugins/bar/index.js
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-export default function (kibana) {
- return [
- new kibana.Plugin({
- id: 'bar:one',
- }),
- new kibana.Plugin({
- id: 'bar:two',
- }),
- ];
-}
diff --git a/src/legacy/plugin_discovery/__tests__/fixtures/plugins/bar/package.json b/src/legacy/plugin_discovery/__tests__/fixtures/plugins/bar/package.json
deleted file mode 100644
index e43c2f0bc984c..0000000000000
--- a/src/legacy/plugin_discovery/__tests__/fixtures/plugins/bar/package.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "name": "foo",
- "version": "kibana"
-}
diff --git a/src/legacy/plugin_discovery/__tests__/fixtures/plugins/broken/index.js b/src/legacy/plugin_discovery/__tests__/fixtures/plugins/broken/index.js
deleted file mode 100644
index 59f4a2649f019..0000000000000
--- a/src/legacy/plugin_discovery/__tests__/fixtures/plugins/broken/index.js
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-export default {
- foo: 'bar',
-};
diff --git a/src/legacy/plugin_discovery/__tests__/fixtures/plugins/broken/package1.json b/src/legacy/plugin_discovery/__tests__/fixtures/plugins/broken/package1.json
deleted file mode 100644
index 81ddb6221d515..0000000000000
--- a/src/legacy/plugin_discovery/__tests__/fixtures/plugins/broken/package1.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "name": "baz",
- "version": "kibana"
-}
diff --git a/src/legacy/plugin_discovery/__tests__/fixtures/plugins/foo/index.js b/src/legacy/plugin_discovery/__tests__/fixtures/plugins/foo/index.js
deleted file mode 100644
index e43a1dcedb372..0000000000000
--- a/src/legacy/plugin_discovery/__tests__/fixtures/plugins/foo/index.js
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-module.exports = function (kibana) {
- return new kibana.Plugin({
- id: 'foo',
- });
-};
diff --git a/src/legacy/plugin_discovery/__tests__/fixtures/plugins/foo/package.json b/src/legacy/plugin_discovery/__tests__/fixtures/plugins/foo/package.json
deleted file mode 100644
index e43c2f0bc984c..0000000000000
--- a/src/legacy/plugin_discovery/__tests__/fixtures/plugins/foo/package.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "name": "foo",
- "version": "kibana"
-}
diff --git a/src/legacy/plugin_discovery/errors.js b/src/legacy/plugin_discovery/errors.js
deleted file mode 100644
index 02d81b32d1fd1..0000000000000
--- a/src/legacy/plugin_discovery/errors.js
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-const errorCodeProperty = Symbol('pluginDiscovery/errorCode');
-
-/**
- * Thrown when reading a plugin directory fails, wraps failure
- * @type {String}
- */
-const ERROR_INVALID_DIRECTORY = 'ERROR_INVALID_DIRECTORY';
-export function createInvalidDirectoryError(sourceError, path) {
- sourceError[errorCodeProperty] = ERROR_INVALID_DIRECTORY;
- sourceError.path = path;
- return sourceError;
-}
-export function isInvalidDirectoryError(error) {
- return error && error[errorCodeProperty] === ERROR_INVALID_DIRECTORY;
-}
-
-/**
- * Thrown when trying to create a PluginPack for a path that
- * is not a valid plugin definition
- * @type {String}
- */
-const ERROR_INVALID_PACK = 'ERROR_INVALID_PACK';
-export function createInvalidPackError(path, reason) {
- const error = new Error(`PluginPack${path ? ` at "${path}"` : ''} ${reason}`);
- error[errorCodeProperty] = ERROR_INVALID_PACK;
- error.path = path;
- return error;
-}
-export function isInvalidPackError(error) {
- return error && error[errorCodeProperty] === ERROR_INVALID_PACK;
-}
-
-/**
- * Thrown when trying to load a PluginSpec that is invalid for some reason
- * @type {String}
- */
-const ERROR_INVALID_PLUGIN = 'ERROR_INVALID_PLUGIN';
-export function createInvalidPluginError(spec, reason) {
- const error = new Error(
- `Plugin from ${spec.getId()} at ${spec.getPack().getPath()} is invalid because ${reason}`
- );
- error[errorCodeProperty] = ERROR_INVALID_PLUGIN;
- error.spec = spec;
- return error;
-}
-export function isInvalidPluginError(error) {
- return error && error[errorCodeProperty] === ERROR_INVALID_PLUGIN;
-}
-
-/**
- * Thrown when trying to load a PluginSpec whose version is incompatible
- * @type {String}
- */
-const ERROR_INCOMPATIBLE_PLUGIN_VERSION = 'ERROR_INCOMPATIBLE_PLUGIN_VERSION';
-export function createIncompatiblePluginVersionError(spec) {
- const error = new Error(
- `Plugin ${spec.getId()} is only compatible with Kibana version ${spec.getExpectedKibanaVersion()}`
- );
- error[errorCodeProperty] = ERROR_INCOMPATIBLE_PLUGIN_VERSION;
- error.spec = spec;
- return error;
-}
-export function isIncompatiblePluginVersionError(error) {
- return error && error[errorCodeProperty] === ERROR_INCOMPATIBLE_PLUGIN_VERSION;
-}
diff --git a/src/legacy/plugin_discovery/find_plugin_specs.js b/src/legacy/plugin_discovery/find_plugin_specs.js
deleted file mode 100644
index b97476bb456a5..0000000000000
--- a/src/legacy/plugin_discovery/find_plugin_specs.js
+++ /dev/null
@@ -1,234 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import * as Rx from 'rxjs';
-import {
- distinct,
- toArray,
- mergeMap,
- share,
- shareReplay,
- filter,
- last,
- map,
- tap,
-} from 'rxjs/operators';
-import { realpathSync } from 'fs';
-
-import { Config } from '../server/config';
-
-import { extendConfigService, disableConfigExtension } from './plugin_config';
-
-import {
- createPack$,
- createPackageJsonAtPath$,
- createPackageJsonsInDirectory$,
-} from './plugin_pack';
-
-import { isInvalidDirectoryError, isInvalidPackError } from './errors';
-
-export function defaultConfig(settings) {
- return Config.withDefaultSchema(settings);
-}
-
-function bufferAllResults(observable) {
- return observable.pipe(
- // buffer all results into a single array
- toArray(),
- // merge the array back into the stream when complete
- mergeMap((array) => array)
- );
-}
-
-/**
- * Determine a distinct value for each result from find$
- * so they can be deduplicated
- * @param {{error?,pack?}} result
- * @return {Any}
- */
-function getDistinctKeyForFindResult(result) {
- // errors are distinct by their message
- if (result.error) {
- return result.error.message;
- }
-
- // packs are distinct by their absolute and real path
- if (result.packageJson) {
- return realpathSync(result.packageJson.directoryPath);
- }
-
- // non error/pack results shouldn't exist, but if they do they are all unique
- return result;
-}
-
-function groupSpecsById(specs) {
- const specsById = new Map();
- for (const spec of specs) {
- const id = spec.getId();
- if (specsById.has(id)) {
- specsById.get(id).push(spec);
- } else {
- specsById.set(id, [spec]);
- }
- }
- return specsById;
-}
-
-/**
- * Creates a collection of observables for discovering pluginSpecs
- * using Kibana's defaults, settings, and config service
- *
- * @param {Object} settings
- * @param {ConfigService} [configToMutate] when supplied **it is mutated** to
- * include the config from discovered plugin specs
- * @return {Object}
- */
-export function findPluginSpecs(settings, configToMutate) {
- const config$ = Rx.defer(async () => {
- if (configToMutate) {
- return configToMutate;
- }
-
- return defaultConfig(settings);
- }).pipe(shareReplay());
-
- // find plugin packs in configured paths/dirs
- const packageJson$ = config$.pipe(
- mergeMap((config) =>
- Rx.merge(
- ...config.get('plugins.paths').map(createPackageJsonAtPath$),
- ...config.get('plugins.scanDirs').map(createPackageJsonsInDirectory$)
- )
- ),
- distinct(getDistinctKeyForFindResult),
- share()
- );
-
- const pack$ = createPack$(packageJson$).pipe(share());
-
- const extendConfig$ = config$.pipe(
- mergeMap((config) =>
- pack$.pipe(
- // get the specs for each found plugin pack
- mergeMap(({ pack }) => (pack ? pack.getPluginSpecs() : [])),
- // make sure that none of the plugin specs have conflicting ids, fail
- // early if conflicts detected or merge the specs back into the stream
- toArray(),
- mergeMap((allSpecs) => {
- for (const [id, specs] of groupSpecsById(allSpecs)) {
- if (specs.length > 1) {
- throw new Error(
- `Multiple plugins found with the id "${id}":\n${specs
- .map((spec) => ` - ${id} at ${spec.getPath()}`)
- .join('\n')}`
- );
- }
- }
-
- return allSpecs;
- }),
- mergeMap(async (spec) => {
- // extend the config service with this plugin spec and
- // collect its deprecations messages if some of its
- // settings are outdated
- const deprecations = [];
- await extendConfigService(spec, config, settings, (message) => {
- deprecations.push({ spec, message });
- });
-
- return {
- spec,
- deprecations,
- };
- }),
- // extend the config with all plugins before determining enabled status
- bufferAllResults,
- map(({ spec, deprecations }) => {
- const isRightVersion = spec.isVersionCompatible(config.get('pkg.version'));
- const enabled = isRightVersion && spec.isEnabled(config);
- return {
- config,
- spec,
- deprecations,
- enabledSpecs: enabled ? [spec] : [],
- disabledSpecs: enabled ? [] : [spec],
- invalidVersionSpecs: isRightVersion ? [] : [spec],
- };
- }),
- // determine which plugins are disabled before actually removing things from the config
- bufferAllResults,
- tap((result) => {
- for (const spec of result.disabledSpecs) {
- disableConfigExtension(spec, config);
- }
- })
- )
- ),
- share()
- );
-
- return {
- // package JSONs found when searching configure paths
- packageJson$: packageJson$.pipe(
- mergeMap((result) => (result.packageJson ? [result.packageJson] : []))
- ),
-
- // plugin packs found when searching configured paths
- pack$: pack$.pipe(mergeMap((result) => (result.pack ? [result.pack] : []))),
-
- // errors caused by invalid directories of plugin directories
- invalidDirectoryError$: pack$.pipe(
- mergeMap((result) => (isInvalidDirectoryError(result.error) ? [result.error] : []))
- ),
-
- // errors caused by directories that we expected to be plugin but were invalid
- invalidPackError$: pack$.pipe(
- mergeMap((result) => (isInvalidPackError(result.error) ? [result.error] : []))
- ),
-
- otherError$: pack$.pipe(
- mergeMap((result) => (isUnhandledError(result.error) ? [result.error] : []))
- ),
-
- // { spec, message } objects produced when transforming deprecated
- // settings for a plugin spec
- deprecation$: extendConfig$.pipe(mergeMap((result) => result.deprecations)),
-
- // the config service we extended with all of the plugin specs,
- // only emitted once it is fully extended by all
- extendedConfig$: extendConfig$.pipe(
- mergeMap((result) => result.config),
- filter(Boolean),
- last()
- ),
-
- // all enabled PluginSpec objects
- spec$: extendConfig$.pipe(mergeMap((result) => result.enabledSpecs)),
-
- // all disabled PluginSpec objects
- disabledSpec$: extendConfig$.pipe(mergeMap((result) => result.disabledSpecs)),
-
- // all PluginSpec objects that were disabled because their version was incompatible
- invalidVersionSpec$: extendConfig$.pipe(mergeMap((result) => result.invalidVersionSpecs)),
- };
-}
-
-function isUnhandledError(error) {
- return error != null && !isInvalidDirectoryError(error) && !isInvalidPackError(error);
-}
diff --git a/src/legacy/plugin_discovery/index.js b/src/legacy/plugin_discovery/index.js
deleted file mode 100644
index b60806f6cbc23..0000000000000
--- a/src/legacy/plugin_discovery/index.js
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-export { findPluginSpecs } from './find_plugin_specs';
-export { reduceExportSpecs } from './plugin_exports';
-export { PluginPack } from './plugin_pack';
diff --git a/src/legacy/plugin_discovery/plugin_config/__tests__/extend_config_service.js b/src/legacy/plugin_discovery/plugin_config/__tests__/extend_config_service.js
deleted file mode 100644
index 40f84f6f54b3b..0000000000000
--- a/src/legacy/plugin_discovery/plugin_config/__tests__/extend_config_service.js
+++ /dev/null
@@ -1,162 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import sinon from 'sinon';
-import expect from '@kbn/expect';
-
-import { Config } from '../../../server/config';
-import { PluginPack } from '../../plugin_pack';
-import { extendConfigService, disableConfigExtension } from '../extend_config_service';
-import * as SchemaNS from '../schema';
-import * as SettingsNS from '../settings';
-
-describe('plugin discovery/extend config service', () => {
- const sandbox = sinon.createSandbox();
- afterEach(() => sandbox.restore());
-
- const pluginSpec = new PluginPack({
- path: '/dev/null',
- pkg: {
- name: 'test',
- version: 'kibana',
- },
- provider: ({ Plugin }) =>
- new Plugin({
- configPrefix: 'foo.bar.baz',
-
- config: (Joi) =>
- Joi.object({
- enabled: Joi.boolean().default(true),
- test: Joi.string().default('bonk'),
- }).default(),
- }),
- })
- .getPluginSpecs()
- .pop();
-
- describe('extendConfigService()', () => {
- it('calls getSettings, getSchema, and Config.extendSchema() correctly', async () => {
- const rootSettings = {
- foo: {
- bar: {
- enabled: false,
- },
- },
- };
- const schema = {
- validate: () => {},
- };
- const configPrefix = 'foo.bar';
- const config = {
- extendSchema: sandbox.stub(),
- };
- const pluginSpec = {
- getConfigPrefix: sandbox.stub().returns(configPrefix),
- };
-
- const getSettings = sandbox.stub(SettingsNS, 'getSettings').returns(rootSettings.foo.bar);
-
- const getSchema = sandbox.stub(SchemaNS, 'getSchema').returns(schema);
-
- await extendConfigService(pluginSpec, config, rootSettings);
-
- sinon.assert.calledOnce(getSettings);
- sinon.assert.calledWithExactly(getSettings, pluginSpec, rootSettings);
-
- sinon.assert.calledOnce(getSchema);
- sinon.assert.calledWithExactly(getSchema, pluginSpec);
-
- sinon.assert.calledOnce(config.extendSchema);
- sinon.assert.calledWithExactly(
- config.extendSchema,
- schema,
- rootSettings.foo.bar,
- configPrefix
- );
- });
-
- it('adds the schema for a plugin spec to its config prefix', async () => {
- const config = Config.withDefaultSchema();
- expect(config.has('foo.bar.baz')).to.be(false);
- await extendConfigService(pluginSpec, config);
- expect(config.has('foo.bar.baz')).to.be(true);
- });
-
- it('initializes it with the default settings', async () => {
- const config = Config.withDefaultSchema();
- await extendConfigService(pluginSpec, config);
- expect(config.get('foo.bar.baz.enabled')).to.be(true);
- expect(config.get('foo.bar.baz.test')).to.be('bonk');
- });
-
- it('initializes it with values from root settings if defined', async () => {
- const config = Config.withDefaultSchema();
- await extendConfigService(pluginSpec, config, {
- foo: {
- bar: {
- baz: {
- test: 'hello world',
- },
- },
- },
- });
-
- expect(config.get('foo.bar.baz.test')).to.be('hello world');
- });
-
- it('throws if root settings are invalid', async () => {
- const config = Config.withDefaultSchema();
- try {
- await extendConfigService(pluginSpec, config, {
- foo: {
- bar: {
- baz: {
- test: {
- 'not a string': true,
- },
- },
- },
- },
- });
- throw new Error('Expected extendConfigService() to throw because of bad settings');
- } catch (error) {
- expect(error.message).to.contain('"test" must be a string');
- }
- });
- });
-
- describe('disableConfigExtension()', () => {
- it('removes added config', async () => {
- const config = Config.withDefaultSchema();
- await extendConfigService(pluginSpec, config);
- expect(config.has('foo.bar.baz.test')).to.be(true);
- await disableConfigExtension(pluginSpec, config);
- expect(config.has('foo.bar.baz.test')).to.be(false);
- });
-
- it('leaves {configPrefix}.enabled config', async () => {
- const config = Config.withDefaultSchema();
- expect(config.has('foo.bar.baz.enabled')).to.be(false);
- await extendConfigService(pluginSpec, config);
- expect(config.get('foo.bar.baz.enabled')).to.be(true);
- await disableConfigExtension(pluginSpec, config);
- expect(config.get('foo.bar.baz.enabled')).to.be(false);
- });
- });
-});
diff --git a/src/legacy/plugin_discovery/plugin_config/__tests__/schema.js b/src/legacy/plugin_discovery/plugin_config/__tests__/schema.js
deleted file mode 100644
index 78adb1e680e20..0000000000000
--- a/src/legacy/plugin_discovery/plugin_config/__tests__/schema.js
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import expect from '@kbn/expect';
-
-import { PluginPack } from '../../plugin_pack';
-import { getSchema, getStubSchema } from '../schema';
-
-describe('plugin discovery/schema', () => {
- function createPluginSpec(configProvider) {
- return new PluginPack({
- path: '/dev/null',
- pkg: {
- name: 'test',
- version: 'kibana',
- },
- provider: ({ Plugin }) =>
- new Plugin({
- configPrefix: 'foo.bar.baz',
- config: configProvider,
- }),
- })
- .getPluginSpecs()
- .pop();
- }
-
- describe('getSchema()', () => {
- it('calls the config provider and returns its return value', async () => {
- const pluginSpec = createPluginSpec(() => 'foo');
- expect(await getSchema(pluginSpec)).to.be('foo');
- });
-
- it('supports config provider that returns a promise', async () => {
- const pluginSpec = createPluginSpec(() => Promise.resolve('foo'));
- expect(await getSchema(pluginSpec)).to.be('foo');
- });
-
- it('uses default schema when no config provider', async () => {
- const schema = await getSchema(createPluginSpec());
- expect(schema).to.be.an('object');
- expect(schema).to.have.property('validate').a('function');
- expect(schema.validate({}).value).to.eql({
- enabled: true,
- });
- });
-
- it('uses default schema when config returns falsy value', async () => {
- const schema = await getSchema(createPluginSpec(() => null));
- expect(schema).to.be.an('object');
- expect(schema).to.have.property('validate').a('function');
- expect(schema.validate({}).value).to.eql({
- enabled: true,
- });
- });
-
- it('uses default schema when config promise resolves to falsy value', async () => {
- const schema = await getSchema(createPluginSpec(() => Promise.resolve(null)));
- expect(schema).to.be.an('object');
- expect(schema).to.have.property('validate').a('function');
- expect(schema.validate({}).value).to.eql({
- enabled: true,
- });
- });
- });
-
- describe('getStubSchema()', () => {
- it('returns schema with enabled: false', async () => {
- const schema = await getStubSchema();
- expect(schema).to.be.an('object');
- expect(schema).to.have.property('validate').a('function');
- expect(schema.validate({}).value).to.eql({
- enabled: false,
- });
- });
- });
-});
diff --git a/src/legacy/plugin_discovery/plugin_config/__tests__/settings.js b/src/legacy/plugin_discovery/plugin_config/__tests__/settings.js
deleted file mode 100644
index 750c5ee6c6f50..0000000000000
--- a/src/legacy/plugin_discovery/plugin_config/__tests__/settings.js
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import expect from '@kbn/expect';
-
-import { PluginPack } from '../../plugin_pack';
-import { getSettings } from '../settings';
-
-describe('plugin_discovery/settings', () => {
- const pluginSpec = new PluginPack({
- path: '/dev/null',
- pkg: {
- name: 'test',
- version: 'kibana',
- },
- provider: ({ Plugin }) =>
- new Plugin({
- configPrefix: 'a.b.c',
- }),
- })
- .getPluginSpecs()
- .pop();
-
- describe('getSettings()', () => {
- it('reads settings from config prefix', async () => {
- const rootSettings = {
- a: {
- b: {
- c: {
- enabled: false,
- },
- },
- },
- };
-
- expect(await getSettings(pluginSpec, rootSettings)).to.eql({
- enabled: false,
- });
- });
-
- it('allows rootSettings to be undefined', async () => {
- expect(await getSettings(pluginSpec)).to.eql(undefined);
- });
- });
-});
diff --git a/src/legacy/plugin_discovery/plugin_config/extend_config_service.js b/src/legacy/plugin_discovery/plugin_config/extend_config_service.js
deleted file mode 100644
index a6d5d4ae5f990..0000000000000
--- a/src/legacy/plugin_discovery/plugin_config/extend_config_service.js
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { getSettings } from './settings';
-import { getSchema, getStubSchema } from './schema';
-
-/**
- * Extend a config service with the schema and settings for a
- * plugin spec and optionally call logDeprecation with warning
- * messages about deprecated settings that are used
- * @param {PluginSpec} spec
- * @param {Server.Config} config
- * @param {Object} rootSettings
- * @param {Function} [logDeprecation]
- * @return {Promise}
- */
-export async function extendConfigService(spec, config, rootSettings) {
- const settings = await getSettings(spec, rootSettings);
- const schema = await getSchema(spec);
- config.extendSchema(schema, settings, spec.getConfigPrefix());
-}
-
-/**
- * Disable the schema and settings applied to a config service for
- * a plugin spec
- * @param {PluginSpec} spec
- * @param {Server.Config} config
- * @return {undefined}
- */
-export function disableConfigExtension(spec, config) {
- const prefix = spec.getConfigPrefix();
- config.removeSchema(prefix);
- config.extendSchema(getStubSchema(), { enabled: false }, prefix);
-}
diff --git a/src/legacy/plugin_discovery/plugin_config/index.js b/src/legacy/plugin_discovery/plugin_config/index.js
deleted file mode 100644
index a27463bc9c7f5..0000000000000
--- a/src/legacy/plugin_discovery/plugin_config/index.js
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-export { extendConfigService, disableConfigExtension } from './extend_config_service';
diff --git a/src/legacy/plugin_discovery/plugin_config/schema.js b/src/legacy/plugin_discovery/plugin_config/schema.js
deleted file mode 100644
index 14d10aa5568da..0000000000000
--- a/src/legacy/plugin_discovery/plugin_config/schema.js
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import Joi from 'joi';
-
-const STUB_CONFIG_SCHEMA = Joi.object()
- .keys({
- enabled: Joi.valid(false).default(false),
- })
- .default();
-
-const DEFAULT_CONFIG_SCHEMA = Joi.object()
- .keys({
- enabled: Joi.boolean().default(true),
- })
- .default();
-
-/**
- * Get the config schema for a plugin spec
- * @param {PluginSpec} spec
- * @return {Promise}
- */
-export async function getSchema(spec) {
- const provider = spec.getConfigSchemaProvider();
- return (provider && (await provider(Joi))) || DEFAULT_CONFIG_SCHEMA;
-}
-
-export function getStubSchema() {
- return STUB_CONFIG_SCHEMA;
-}
diff --git a/src/legacy/plugin_discovery/plugin_config/settings.js b/src/legacy/plugin_discovery/plugin_config/settings.js
deleted file mode 100644
index e6a4741d76eca..0000000000000
--- a/src/legacy/plugin_discovery/plugin_config/settings.js
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { get } from 'lodash';
-
-/**
- * Get the settings for a pluginSpec from the raw root settings while
- * optionally calling logDeprecation() with warnings about deprecated
- * settings that were used
- * @param {PluginSpec} spec
- * @param {Object} rootSettings
- * @return {Promise}
- */
-export async function getSettings(spec, rootSettings) {
- const prefix = spec.getConfigPrefix();
- const rawSettings = get(rootSettings, prefix);
- return rawSettings;
-}
diff --git a/src/legacy/plugin_discovery/plugin_exports/__tests__/reduce_export_specs.js b/src/legacy/plugin_discovery/plugin_exports/__tests__/reduce_export_specs.js
deleted file mode 100644
index 3beaacc1a8293..0000000000000
--- a/src/legacy/plugin_discovery/plugin_exports/__tests__/reduce_export_specs.js
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import expect from '@kbn/expect';
-
-import { PluginPack } from '../../plugin_pack';
-import { reduceExportSpecs } from '../reduce_export_specs';
-
-const PLUGIN = new PluginPack({
- path: __dirname,
- pkg: {
- name: 'foo',
- version: 'kibana',
- },
- provider: ({ Plugin }) =>
- new Plugin({
- uiExports: {
- concatNames: {
- name: 'export1',
- },
-
- concat: ['export2', 'export3'],
- },
- }),
-});
-
-const REDUCERS = {
- concatNames(acc, spec, type, pluginSpec) {
- return {
- names: [].concat(acc.names || [], `${pluginSpec.getId()}:${spec.name}`),
- };
- },
- concat(acc, spec, type, pluginSpec) {
- return {
- names: [].concat(acc.names || [], `${pluginSpec.getId()}:${spec}`),
- };
- },
-};
-
-const PLUGIN_SPECS = PLUGIN.getPluginSpecs();
-
-describe('reduceExportSpecs', () => {
- it('combines ui exports from a list of plugin definitions', () => {
- const exports = reduceExportSpecs(PLUGIN_SPECS, REDUCERS);
- expect(exports).to.eql({
- names: ['foo:export1', 'foo:export2', 'foo:export3'],
- });
- });
-
- it('starts with the defaults', () => {
- const exports = reduceExportSpecs(PLUGIN_SPECS, REDUCERS, {
- names: ['default'],
- });
-
- expect(exports).to.eql({
- names: ['default', 'foo:export1', 'foo:export2', 'foo:export3'],
- });
- });
-});
diff --git a/src/legacy/plugin_discovery/plugin_exports/index.js b/src/legacy/plugin_discovery/plugin_exports/index.js
deleted file mode 100644
index 0e3511ea85dd4..0000000000000
--- a/src/legacy/plugin_discovery/plugin_exports/index.js
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-export { reduceExportSpecs } from './reduce_export_specs';
diff --git a/src/legacy/plugin_discovery/plugin_exports/reduce_export_specs.js b/src/legacy/plugin_discovery/plugin_exports/reduce_export_specs.js
deleted file mode 100644
index a3adc3091085d..0000000000000
--- a/src/legacy/plugin_discovery/plugin_exports/reduce_export_specs.js
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-/**
- * Combine the exportSpecs from a list of pluginSpecs
- * by calling the reducers for each export type
- * @param {Array} pluginSpecs
- * @param {Object} reducers
- * @param {Object} [defaults={}]
- * @return {Object}
- */
-export function reduceExportSpecs(pluginSpecs, reducers, defaults = {}) {
- return pluginSpecs.reduce((acc, pluginSpec) => {
- const specsByType = pluginSpec.getExportSpecs() || {};
- const types = Object.keys(specsByType);
-
- return types.reduce((acc, type) => {
- const reducer = reducers[type] || reducers.unknown;
-
- if (!reducer) {
- throw new Error(`Unknown export type ${type}`);
- }
-
- // convert specs to an array if not already one or
- // ignore the spec if it is undefined
- const specs = [].concat(specsByType[type] === undefined ? [] : specsByType[type]);
-
- return specs.reduce((acc, spec) => reducer(acc, spec, type, pluginSpec), acc);
- }, acc);
- }, defaults);
-}
diff --git a/src/legacy/plugin_discovery/plugin_pack/__tests__/create_pack.js b/src/legacy/plugin_discovery/plugin_pack/__tests__/create_pack.js
deleted file mode 100644
index b17bd69479ffa..0000000000000
--- a/src/legacy/plugin_discovery/plugin_pack/__tests__/create_pack.js
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { resolve } from 'path';
-import * as Rx from 'rxjs';
-import { toArray } from 'rxjs/operators';
-import expect from '@kbn/expect';
-
-import { createPack$ } from '../create_pack';
-import { PluginPack } from '../plugin_pack';
-
-import { PLUGINS_DIR, assertInvalidPackError } from './utils';
-
-describe('plugin discovery/create pack', () => {
- it('creates PluginPack', async () => {
- const packageJson$ = Rx.from([
- {
- packageJson: {
- directoryPath: resolve(PLUGINS_DIR, 'prebuilt'),
- contents: {
- name: 'prebuilt',
- },
- },
- },
- ]);
- const results = await createPack$(packageJson$).pipe(toArray()).toPromise();
- expect(results).to.have.length(1);
- expect(results[0]).to.only.have.keys(['pack']);
- const { pack } = results[0];
- expect(pack).to.be.a(PluginPack);
- });
-
- describe('errors thrown', () => {
- async function checkError(path, check) {
- const packageJson$ = Rx.from([
- {
- packageJson: {
- directoryPath: path,
- },
- },
- ]);
-
- const results = await createPack$(packageJson$).pipe(toArray()).toPromise();
- expect(results).to.have.length(1);
- expect(results[0]).to.only.have.keys(['error']);
- const { error } = results[0];
- await check(error);
- }
- it('default export is an object', () =>
- checkError(resolve(PLUGINS_DIR, 'exports_object'), (error) => {
- assertInvalidPackError(error);
- expect(error.message).to.contain('must export a function');
- }));
- it('default export is an number', () =>
- checkError(resolve(PLUGINS_DIR, 'exports_number'), (error) => {
- assertInvalidPackError(error);
- expect(error.message).to.contain('must export a function');
- }));
- it('default export is an string', () =>
- checkError(resolve(PLUGINS_DIR, 'exports_string'), (error) => {
- assertInvalidPackError(error);
- expect(error.message).to.contain('must export a function');
- }));
- it('directory with code that fails when required', () =>
- checkError(resolve(PLUGINS_DIR, 'broken_code'), (error) => {
- expect(error.message).to.contain("Cannot find module 'does-not-exist'");
- }));
- });
-});
diff --git a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/broken/package.json b/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/broken/package.json
deleted file mode 100644
index f830e8b60c02d..0000000000000
--- a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/broken/package.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "name":
-}
diff --git a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/broken_code/index.js b/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/broken_code/index.js
deleted file mode 100644
index bdb26504d6b6e..0000000000000
--- a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/broken_code/index.js
+++ /dev/null
@@ -1,7 +0,0 @@
-const brokenRequire = require('does-not-exist'); // eslint-disable-line
-
-module.exports = function (kibana) {
- return new kibana.Plugin({
- id: 'foo',
- });
-};
diff --git a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/broken_code/package.json b/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/broken_code/package.json
deleted file mode 100644
index e43c2f0bc984c..0000000000000
--- a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/broken_code/package.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "name": "foo",
- "version": "kibana"
-}
diff --git a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/exports_number/package.json b/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/exports_number/package.json
deleted file mode 100644
index e43c2f0bc984c..0000000000000
--- a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/exports_number/package.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "name": "foo",
- "version": "kibana"
-}
diff --git a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/exports_object/index.js b/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/exports_object/index.js
deleted file mode 100644
index 59f4a2649f019..0000000000000
--- a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/exports_object/index.js
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-export default {
- foo: 'bar',
-};
diff --git a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/exports_object/package.json b/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/exports_object/package.json
deleted file mode 100644
index e43c2f0bc984c..0000000000000
--- a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/exports_object/package.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "name": "foo",
- "version": "kibana"
-}
diff --git a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/exports_string/index.js b/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/exports_string/index.js
deleted file mode 100644
index 8900db15321ae..0000000000000
--- a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/exports_string/index.js
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-export default 'foo';
diff --git a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/exports_string/package.json b/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/exports_string/package.json
deleted file mode 100644
index e43c2f0bc984c..0000000000000
--- a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/exports_string/package.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "name": "foo",
- "version": "kibana"
-}
diff --git a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/foo/index.js b/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/foo/index.js
deleted file mode 100644
index e43a1dcedb372..0000000000000
--- a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/foo/index.js
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-module.exports = function (kibana) {
- return new kibana.Plugin({
- id: 'foo',
- });
-};
diff --git a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/foo/package.json b/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/foo/package.json
deleted file mode 100644
index e43c2f0bc984c..0000000000000
--- a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/foo/package.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "name": "foo",
- "version": "kibana"
-}
diff --git a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/index.js b/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/index.js
deleted file mode 100644
index edb1dd15673da..0000000000000
--- a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/index.js
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-console.log('hello world');
diff --git a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/lib/index.js b/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/lib/index.js
deleted file mode 100644
index 050ffdfbde9ea..0000000000000
--- a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/lib/index.js
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-export { myLib } from './my_lib';
diff --git a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/lib/my_lib.js b/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/lib/my_lib.js
deleted file mode 100644
index 94e511632d9a6..0000000000000
--- a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/lib/my_lib.js
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-export function myLib() {
- console.log('lib');
-}
diff --git a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/prebuilt/index.js b/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/prebuilt/index.js
deleted file mode 100644
index 89744b2dd3fd9..0000000000000
--- a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/prebuilt/index.js
+++ /dev/null
@@ -1,14 +0,0 @@
-/* eslint-disable */
-'use strict';
-
-Object.defineProperty(exports, "__esModule", {
- value: true
-});
-
-exports.default = function (_ref) {
- var Plugin = _ref.Plugin;
-
- return new Plugin({
- id: 'foo'
- });
-};
diff --git a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/prebuilt/package.json b/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/prebuilt/package.json
deleted file mode 100644
index b1b74e0e76b12..0000000000000
--- a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/prebuilt/package.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "name": "prebuilt"
-}
diff --git a/src/legacy/plugin_discovery/plugin_pack/__tests__/package_json_at_path.js b/src/legacy/plugin_discovery/plugin_pack/__tests__/package_json_at_path.js
deleted file mode 100644
index fa1033180954e..0000000000000
--- a/src/legacy/plugin_discovery/plugin_pack/__tests__/package_json_at_path.js
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { resolve } from 'path';
-import { toArray } from 'rxjs/operators';
-
-import expect from '@kbn/expect';
-
-import { createPackageJsonAtPath$ } from '../package_json_at_path';
-import { PLUGINS_DIR, assertInvalidPackError, assertInvalidDirectoryError } from './utils';
-
-describe('plugin discovery/plugin_pack', () => {
- describe('createPackageJsonAtPath$()', () => {
- it('returns an observable', () => {
- expect(createPackageJsonAtPath$()).to.have.property('subscribe').a('function');
- });
- it('gets the default provider from prebuilt babel modules', async () => {
- const results = await createPackageJsonAtPath$(resolve(PLUGINS_DIR, 'prebuilt'))
- .pipe(toArray())
- .toPromise();
- expect(results).to.have.length(1);
- expect(results[0]).to.only.have.keys(['packageJson']);
- expect(results[0].packageJson).to.be.an(Object);
- expect(results[0].packageJson.directoryPath).to.be(resolve(PLUGINS_DIR, 'prebuilt'));
- expect(results[0].packageJson.contents).to.eql({ name: 'prebuilt' });
- });
- describe('errors emitted as { error } results', () => {
- async function checkError(path, check) {
- const results = await createPackageJsonAtPath$(path).pipe(toArray()).toPromise();
- expect(results).to.have.length(1);
- expect(results[0]).to.only.have.keys(['error']);
- const { error } = results[0];
- await check(error);
- }
- it('undefined path', () =>
- checkError(undefined, (error) => {
- assertInvalidDirectoryError(error);
- expect(error.message).to.contain('path must be a string');
- }));
- it('relative path', () =>
- checkError('plugins/foo', (error) => {
- assertInvalidDirectoryError(error);
- expect(error.message).to.contain('path must be absolute');
- }));
- it('./relative path', () =>
- checkError('./plugins/foo', (error) => {
- assertInvalidDirectoryError(error);
- expect(error.message).to.contain('path must be absolute');
- }));
- it('non-existent path', () =>
- checkError(resolve(PLUGINS_DIR, 'baz'), (error) => {
- assertInvalidPackError(error);
- expect(error.message).to.contain('must be a directory');
- }));
- it('path to a file', () =>
- checkError(resolve(PLUGINS_DIR, 'index.js'), (error) => {
- assertInvalidPackError(error);
- expect(error.message).to.contain('must be a directory');
- }));
- it('directory without a package.json', () =>
- checkError(resolve(PLUGINS_DIR, 'lib'), (error) => {
- assertInvalidPackError(error);
- expect(error.message).to.contain('must have a package.json file');
- }));
- it('directory with an invalid package.json', () =>
- checkError(resolve(PLUGINS_DIR, 'broken'), (error) => {
- assertInvalidPackError(error);
- expect(error.message).to.contain('must have a valid package.json file');
- }));
- });
- });
-});
diff --git a/src/legacy/plugin_discovery/plugin_pack/__tests__/package_jsons_in_directory.js b/src/legacy/plugin_discovery/plugin_pack/__tests__/package_jsons_in_directory.js
deleted file mode 100644
index 37cb4cc064da7..0000000000000
--- a/src/legacy/plugin_discovery/plugin_pack/__tests__/package_jsons_in_directory.js
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { resolve } from 'path';
-
-import { toArray } from 'rxjs/operators';
-import expect from '@kbn/expect';
-
-import { createPackageJsonsInDirectory$ } from '../package_jsons_in_directory';
-
-import { PLUGINS_DIR, assertInvalidDirectoryError } from './utils';
-
-describe('plugin discovery/packs in directory', () => {
- describe('createPackageJsonsInDirectory$()', () => {
- describe('errors emitted as { error } results', () => {
- async function checkError(path, check) {
- const results = await createPackageJsonsInDirectory$(path).pipe(toArray()).toPromise();
- expect(results).to.have.length(1);
- expect(results[0]).to.only.have.keys('error');
- const { error } = results[0];
- await check(error);
- }
-
- it('undefined path', () =>
- checkError(undefined, (error) => {
- assertInvalidDirectoryError(error);
- expect(error.message).to.contain('path must be a string');
- }));
- it('relative path', () =>
- checkError('my/plugins', (error) => {
- assertInvalidDirectoryError(error);
- expect(error.message).to.contain('path must be absolute');
- }));
- it('./relative path', () =>
- checkError('./my/pluginsd', (error) => {
- assertInvalidDirectoryError(error);
- expect(error.message).to.contain('path must be absolute');
- }));
- it('non-existent path', () =>
- checkError(resolve(PLUGINS_DIR, 'notreal'), (error) => {
- assertInvalidDirectoryError(error);
- expect(error.message).to.contain('no such file or directory');
- }));
- it('path to a file', () =>
- checkError(resolve(PLUGINS_DIR, 'index.js'), (error) => {
- assertInvalidDirectoryError(error);
- expect(error.message).to.contain('not a directory');
- }));
- });
-
- it('includes child errors for invalid packageJsons within a valid directory', async () => {
- const results = await createPackageJsonsInDirectory$(PLUGINS_DIR).pipe(toArray()).toPromise();
-
- const errors = results.map((result) => result.error).filter(Boolean);
-
- const packageJsons = results.map((result) => result.packageJson).filter(Boolean);
-
- packageJsons.forEach((pack) => expect(pack).to.be.an(Object));
- // there should be one result for each item in PLUGINS_DIR
- expect(results).to.have.length(8);
- // three of the fixtures are errors of some sort
- expect(errors).to.have.length(2);
- // six of them are valid
- expect(packageJsons).to.have.length(6);
- });
- });
-});
diff --git a/src/legacy/plugin_discovery/plugin_pack/__tests__/plugin_pack.js b/src/legacy/plugin_discovery/plugin_pack/__tests__/plugin_pack.js
deleted file mode 100644
index 769fcd74ce6fb..0000000000000
--- a/src/legacy/plugin_discovery/plugin_pack/__tests__/plugin_pack.js
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import expect from '@kbn/expect';
-import sinon from 'sinon';
-
-import { PluginPack } from '../plugin_pack';
-import { PluginSpec } from '../../plugin_spec';
-
-describe('plugin discovery/plugin pack', () => {
- describe('constructor', () => {
- it('requires an object', () => {
- expect(() => {
- new PluginPack();
- }).to.throwError();
- });
- });
- describe('#getPkg()', () => {
- it('returns the `pkg` constructor argument', () => {
- const pkg = {};
- const pack = new PluginPack({ pkg });
- expect(pack.getPkg()).to.be(pkg);
- });
- });
- describe('#getPath()', () => {
- it('returns the `path` constructor argument', () => {
- const path = {};
- const pack = new PluginPack({ path });
- expect(pack.getPath()).to.be(path);
- });
- });
- describe('#getPluginSpecs()', () => {
- it('calls the `provider` constructor argument with an api including a single sub class of PluginSpec', () => {
- const provider = sinon.stub();
- const pack = new PluginPack({ provider });
- sinon.assert.notCalled(provider);
- pack.getPluginSpecs();
- sinon.assert.calledOnce(provider);
- sinon.assert.calledWithExactly(provider, {
- Plugin: sinon.match((Class) => {
- return Class.prototype instanceof PluginSpec;
- }, 'Subclass of PluginSpec'),
- });
- });
-
- it('casts undefined return value to array', () => {
- const pack = new PluginPack({ provider: () => undefined });
- expect(pack.getPluginSpecs()).to.eql([]);
- });
-
- it('casts single PluginSpec to an array', () => {
- const pack = new PluginPack({
- path: '/dev/null',
- pkg: { name: 'foo', version: 'kibana' },
- provider: ({ Plugin }) => new Plugin({}),
- });
-
- const specs = pack.getPluginSpecs();
- expect(specs).to.be.an('array');
- expect(specs).to.have.length(1);
- expect(specs[0]).to.be.a(PluginSpec);
- });
-
- it('returns an array of PluginSpec', () => {
- const pack = new PluginPack({
- path: '/dev/null',
- pkg: { name: 'foo', version: 'kibana' },
- provider: ({ Plugin }) => [new Plugin({}), new Plugin({})],
- });
-
- const specs = pack.getPluginSpecs();
- expect(specs).to.be.an('array');
- expect(specs).to.have.length(2);
- expect(specs[0]).to.be.a(PluginSpec);
- expect(specs[1]).to.be.a(PluginSpec);
- });
-
- it('throws if non-undefined return value is not an instance of api.Plugin', () => {
- let OtherPluginSpecClass;
- const otherPack = new PluginPack({
- path: '/dev/null',
- pkg: { name: 'foo', version: 'kibana' },
- provider: (api) => {
- OtherPluginSpecClass = api.Plugin;
- },
- });
-
- // call getPluginSpecs() on other pack to get it's api.Plugin class
- otherPack.getPluginSpecs();
-
- const badPacks = [
- new PluginPack({ provider: () => false }),
- new PluginPack({ provider: () => null }),
- new PluginPack({ provider: () => 1 }),
- new PluginPack({ provider: () => 'true' }),
- new PluginPack({ provider: () => true }),
- new PluginPack({ provider: () => new Date() }),
- new PluginPack({ provider: () => /foo.*bar/ }),
- new PluginPack({ provider: () => function () {} }),
- new PluginPack({ provider: () => new OtherPluginSpecClass({}) }),
- ];
-
- for (const pack of badPacks) {
- expect(() => pack.getPluginSpecs()).to.throwError((error) => {
- expect(error.message).to.contain('unexpected plugin export');
- });
- }
- });
- });
-});
diff --git a/src/legacy/plugin_discovery/plugin_pack/__tests__/utils.js b/src/legacy/plugin_discovery/plugin_pack/__tests__/utils.js
deleted file mode 100644
index adcf60d809ff7..0000000000000
--- a/src/legacy/plugin_discovery/plugin_pack/__tests__/utils.js
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { resolve } from 'path';
-import { inspect } from 'util';
-
-import { isInvalidPackError, isInvalidDirectoryError } from '../../errors';
-
-export const PLUGINS_DIR = resolve(__dirname, 'fixtures/plugins');
-
-export function assertInvalidDirectoryError(error) {
- if (!isInvalidDirectoryError(error)) {
- throw new Error(`Expected ${inspect(error)} to be an 'InvalidDirectoryError'`);
- }
-}
-
-export function assertInvalidPackError(error) {
- if (!isInvalidPackError(error)) {
- throw new Error(`Expected ${inspect(error)} to be an 'InvalidPackError'`);
- }
-}
diff --git a/src/legacy/plugin_discovery/plugin_pack/create_pack.js b/src/legacy/plugin_discovery/plugin_pack/create_pack.js
deleted file mode 100644
index 189c2ea324103..0000000000000
--- a/src/legacy/plugin_discovery/plugin_pack/create_pack.js
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { PluginPack } from './plugin_pack';
-import { map, catchError } from 'rxjs/operators';
-import { createInvalidPackError } from '../errors';
-
-function createPack(packageJson) {
- let provider = require(packageJson.directoryPath); // eslint-disable-line import/no-dynamic-require
- if (provider.__esModule) {
- provider = provider.default;
- }
- if (typeof provider !== 'function') {
- throw createInvalidPackError(packageJson.directoryPath, 'must export a function');
- }
-
- return new PluginPack({ path: packageJson.directoryPath, pkg: packageJson.contents, provider });
-}
-
-export const createPack$ = (packageJson$) =>
- packageJson$.pipe(
- map(({ error, packageJson }) => {
- if (error) {
- return { error };
- }
-
- if (!packageJson) {
- throw new Error('packageJson is required to create the pack');
- }
-
- return {
- pack: createPack(packageJson),
- };
- }),
- // createPack can throw errors, and we want them to be represented
- // like the errors we consume from createPackageJsonAtPath/Directory
- catchError((error) => [{ error }])
- );
diff --git a/src/legacy/plugin_discovery/plugin_pack/index.js b/src/legacy/plugin_discovery/plugin_pack/index.js
deleted file mode 100644
index 69e55baee660b..0000000000000
--- a/src/legacy/plugin_discovery/plugin_pack/index.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-export { createPack$ } from './create_pack';
-export { createPackageJsonAtPath$ } from './package_json_at_path';
-export { createPackageJsonsInDirectory$ } from './package_jsons_in_directory';
-export { PluginPack } from './plugin_pack';
diff --git a/src/legacy/plugin_discovery/plugin_pack/lib/fs.js b/src/legacy/plugin_discovery/plugin_pack/lib/fs.js
deleted file mode 100644
index 2b531e314df52..0000000000000
--- a/src/legacy/plugin_discovery/plugin_pack/lib/fs.js
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { stat, readdir } from 'fs';
-import { resolve, isAbsolute } from 'path';
-
-import { fromNode as fcb } from 'bluebird';
-import * as Rx from 'rxjs';
-import { catchError, mergeAll, filter, map, mergeMap } from 'rxjs/operators';
-
-import { createInvalidDirectoryError } from '../../errors';
-
-function assertAbsolutePath(path) {
- if (typeof path !== 'string') {
- throw createInvalidDirectoryError(new TypeError('path must be a string'), path);
- }
-
- if (!isAbsolute(path)) {
- throw createInvalidDirectoryError(new TypeError('path must be absolute'), path);
- }
-}
-
-async function statTest(path, test) {
- try {
- const stats = await fcb((cb) => stat(path, cb));
- return Boolean(test(stats));
- } catch (error) {
- if (error.code !== 'ENOENT') {
- throw error;
- }
- }
- return false;
-}
-
-/**
- * Determine if a path currently points to a directory
- * @param {String} path
- * @return {Promise}
- */
-export async function isDirectory(path) {
- assertAbsolutePath(path);
- return await statTest(path, (stat) => stat.isDirectory());
-}
-
-/**
- * Get absolute paths for child directories within a path
- * @param {string} path
- * @return {Promise>}
- */
-export const createChildDirectory$ = (path) =>
- Rx.defer(() => {
- assertAbsolutePath(path);
- return fcb((cb) => readdir(path, cb));
- }).pipe(
- catchError((error) => {
- throw createInvalidDirectoryError(error, path);
- }),
- mergeAll(),
- filter((name) => !name.startsWith('.')),
- map((name) => resolve(path, name)),
- mergeMap(async (absolute) => {
- if (await isDirectory(absolute)) {
- return [absolute];
- } else {
- return [];
- }
- }),
- mergeAll()
- );
diff --git a/src/legacy/plugin_discovery/plugin_pack/lib/index.js b/src/legacy/plugin_discovery/plugin_pack/lib/index.js
deleted file mode 100644
index 491deeda27516..0000000000000
--- a/src/legacy/plugin_discovery/plugin_pack/lib/index.js
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-export { isDirectory, createChildDirectory$ } from './fs';
diff --git a/src/legacy/plugin_discovery/plugin_pack/package_json_at_path.js b/src/legacy/plugin_discovery/plugin_pack/package_json_at_path.js
deleted file mode 100644
index 18629ef3ea802..0000000000000
--- a/src/legacy/plugin_discovery/plugin_pack/package_json_at_path.js
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { readFileSync } from 'fs';
-import * as Rx from 'rxjs';
-import { map, mergeMap, catchError } from 'rxjs/operators';
-import { resolve } from 'path';
-import { createInvalidPackError } from '../errors';
-// eslint-disable-next-line @kbn/eslint/no-restricted-paths
-import { isNewPlatformPlugin } from '../../../core/server/plugins';
-
-import { isDirectory } from './lib';
-
-async function createPackageJsonAtPath(path) {
- if (!(await isDirectory(path))) {
- throw createInvalidPackError(path, 'must be a directory');
- }
-
- let str;
- try {
- str = readFileSync(resolve(path, 'package.json'));
- } catch (err) {
- throw createInvalidPackError(path, 'must have a package.json file');
- }
-
- let pkg;
- try {
- pkg = JSON.parse(str);
- } catch (err) {
- throw createInvalidPackError(path, 'must have a valid package.json file');
- }
-
- return {
- directoryPath: path,
- contents: pkg,
- };
-}
-
-export const createPackageJsonAtPath$ = (path) =>
- // If plugin directory contains manifest file, we should skip it since it
- // should have been handled by the core plugin system already.
- Rx.defer(() => isNewPlatformPlugin(path)).pipe(
- mergeMap((isNewPlatformPlugin) => (isNewPlatformPlugin ? [] : createPackageJsonAtPath(path))),
- map((packageJson) => ({ packageJson })),
- catchError((error) => [{ error }])
- );
diff --git a/src/legacy/plugin_discovery/plugin_pack/package_jsons_in_directory.js b/src/legacy/plugin_discovery/plugin_pack/package_jsons_in_directory.js
deleted file mode 100644
index 5f0977f4829b8..0000000000000
--- a/src/legacy/plugin_discovery/plugin_pack/package_jsons_in_directory.js
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { mergeMap, catchError } from 'rxjs/operators';
-import { isInvalidDirectoryError } from '../errors';
-
-import { createChildDirectory$ } from './lib';
-import { createPackageJsonAtPath$ } from './package_json_at_path';
-
-/**
- * Finds the plugins within a directory. Results are
- * an array of objects with either `pack` or `error`
- * keys.
- *
- * - `{ error }` results are provided when the path is not
- * a directory, or one of the child directories is not a
- * valid plugin pack.
- * - `{ pack }` results are for discovered plugins defs
- *
- * @param {String} path
- * @return {Array<{pack}|{error}>}
- */
-export const createPackageJsonsInDirectory$ = (path) =>
- createChildDirectory$(path).pipe(
- mergeMap(createPackageJsonAtPath$),
- catchError((error) => {
- // this error is produced by createChildDirectory$() when the path
- // is invalid, we return them as an error result similar to how
- // createPackAtPath$ works when it finds invalid packs in a directory
- if (isInvalidDirectoryError(error)) {
- return [{ error }];
- }
-
- throw error;
- })
- );
diff --git a/src/legacy/plugin_discovery/plugin_pack/plugin_pack.js b/src/legacy/plugin_discovery/plugin_pack/plugin_pack.js
deleted file mode 100644
index 1baf3d104ca84..0000000000000
--- a/src/legacy/plugin_discovery/plugin_pack/plugin_pack.js
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { inspect } from 'util';
-
-import { PluginSpec } from '../plugin_spec';
-
-export class PluginPack {
- constructor({ path, pkg, provider }) {
- this._path = path;
- this._pkg = pkg;
- this._provider = provider;
- }
-
- /**
- * Get the contents of this plugin pack's package.json file
- * @return {Object}
- */
- getPkg() {
- return this._pkg;
- }
-
- /**
- * Get the absolute path to this plugin pack on disk
- * @return {String}
- */
- getPath() {
- return this._path;
- }
-
- /**
- * Invoke the plugin pack's provider to get the list
- * of specs defined in this plugin.
- * @return {Array}
- */
- getPluginSpecs() {
- const pack = this;
- const api = {
- Plugin: class ScopedPluginSpec extends PluginSpec {
- constructor(options) {
- super(pack, options);
- }
- },
- };
-
- const result = this._provider(api);
- const specs = [].concat(result === undefined ? [] : result);
-
- // verify that all specs are instances of passed "Plugin" class
- specs.forEach((spec) => {
- if (!(spec instanceof api.Plugin)) {
- throw new TypeError('unexpected plugin export ' + inspect(spec));
- }
- });
-
- return specs;
- }
-}
diff --git a/src/legacy/plugin_discovery/plugin_spec/__tests__/is_version_compatible.js b/src/legacy/plugin_discovery/plugin_spec/__tests__/is_version_compatible.js
deleted file mode 100644
index 897184496af37..0000000000000
--- a/src/legacy/plugin_discovery/plugin_spec/__tests__/is_version_compatible.js
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import expect from '@kbn/expect';
-
-import { isVersionCompatible } from '../is_version_compatible';
-
-describe('plugin discovery/plugin spec', () => {
- describe('isVersionCompatible()', () => {
- const tests = [
- ['kibana', '6.0.0', true],
- ['kibana', '6.0.0-rc1', true],
- ['6.0.0-rc1', '6.0.0', true],
- ['6.0.0', '6.0.0-rc1', true],
- ['6.0.0-rc2', '6.0.0-rc1', true],
- ['6.0.0-rc2', '6.0.0-rc3', true],
- ['foo', 'bar', false],
- ['6.0.0', '5.1.4', false],
- ['5.1.4', '6.0.0', false],
- ['5.1.4-SNAPSHOT', '6.0.0-rc2-SNAPSHOT', false],
- ['5.1.4', '6.0.0-rc2-SNAPSHOT', false],
- ['5.1.4-SNAPSHOT', '6.0.0', false],
- ['5.1.4-SNAPSHOT', '6.0.0-rc2', false],
- ];
-
- for (const [plugin, kibana, shouldPass] of tests) {
- it(`${shouldPass ? 'should' : `shouldn't`} allow plugin: ${plugin} kibana: ${kibana}`, () => {
- expect(isVersionCompatible(plugin, kibana)).to.be(shouldPass);
- });
- }
- });
-});
diff --git a/src/legacy/plugin_discovery/plugin_spec/__tests__/plugin_spec.js b/src/legacy/plugin_discovery/plugin_spec/__tests__/plugin_spec.js
deleted file mode 100644
index 02675f0bd60f8..0000000000000
--- a/src/legacy/plugin_discovery/plugin_spec/__tests__/plugin_spec.js
+++ /dev/null
@@ -1,496 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { resolve } from 'path';
-
-import expect from '@kbn/expect';
-import sinon from 'sinon';
-
-import { PluginPack } from '../../plugin_pack';
-import { PluginSpec } from '../plugin_spec';
-import * as IsVersionCompatibleNS from '../is_version_compatible';
-
-const fooPack = new PluginPack({
- path: '/dev/null',
- pkg: { name: 'foo', version: 'kibana' },
-});
-
-describe('plugin discovery/plugin spec', () => {
- describe('PluginSpec', () => {
- describe('validation', () => {
- it('throws if missing spec.id AND Pack has no name', () => {
- const pack = new PluginPack({ pkg: {} });
- expect(() => new PluginSpec(pack, {})).to.throwError((error) => {
- expect(error.message).to.contain('Unable to determine plugin id');
- });
- });
-
- it('throws if missing spec.kibanaVersion AND Pack has no version', () => {
- const pack = new PluginPack({ pkg: { name: 'foo' } });
- expect(() => new PluginSpec(pack, {})).to.throwError((error) => {
- expect(error.message).to.contain('Unable to determine plugin version');
- });
- });
-
- it('throws if spec.require is defined, but not an array', () => {
- function assert(require) {
- expect(() => new PluginSpec(fooPack, { require })).to.throwError((error) => {
- expect(error.message).to.contain('"plugin.require" must be an array of plugin ids');
- });
- }
-
- assert(null);
- assert('');
- assert('kibana');
- assert(1);
- assert(0);
- assert(/a.*b/);
- });
-
- it('throws if spec.publicDir is truthy and not a string', () => {
- function assert(publicDir) {
- expect(() => new PluginSpec(fooPack, { publicDir })).to.throwError((error) => {
- expect(error.message).to.contain(
- `The "path" argument must be of type string. Received type ${typeof publicDir}`
- );
- });
- }
-
- assert(1);
- assert(function () {});
- assert([]);
- assert(/a.*b/);
- });
-
- it('throws if spec.publicDir is not an absolute path', () => {
- function assert(publicDir) {
- expect(() => new PluginSpec(fooPack, { publicDir })).to.throwError((error) => {
- expect(error.message).to.contain('plugin.publicDir must be an absolute path');
- });
- }
-
- assert('relative/path');
- assert('./relative/path');
- });
-
- it('throws if spec.publicDir basename is not `public`', () => {
- function assert(publicDir) {
- expect(() => new PluginSpec(fooPack, { publicDir })).to.throwError((error) => {
- expect(error.message).to.contain('must end with a "public" directory');
- });
- }
-
- assert('/www');
- assert('/www/');
- assert('/www/public/my_plugin');
- assert('/www/public/my_plugin/');
- });
- });
-
- describe('#getPack()', () => {
- it('returns the pack', () => {
- const spec = new PluginSpec(fooPack, {});
- expect(spec.getPack()).to.be(fooPack);
- });
- });
-
- describe('#getPkg()', () => {
- it('returns the pkg from the pack', () => {
- const spec = new PluginSpec(fooPack, {});
- expect(spec.getPkg()).to.be(fooPack.getPkg());
- });
- });
-
- describe('#getPath()', () => {
- it('returns the path from the pack', () => {
- const spec = new PluginSpec(fooPack, {});
- expect(spec.getPath()).to.be(fooPack.getPath());
- });
- });
-
- describe('#getId()', () => {
- it('uses spec.id', () => {
- const spec = new PluginSpec(fooPack, {
- id: 'bar',
- });
-
- expect(spec.getId()).to.be('bar');
- });
-
- it('defaults to pack.pkg.name', () => {
- const spec = new PluginSpec(fooPack, {});
-
- expect(spec.getId()).to.be('foo');
- });
- });
-
- describe('#getVersion()', () => {
- it('uses spec.version', () => {
- const spec = new PluginSpec(fooPack, {
- version: 'bar',
- });
-
- expect(spec.getVersion()).to.be('bar');
- });
-
- it('defaults to pack.pkg.version', () => {
- const spec = new PluginSpec(fooPack, {});
-
- expect(spec.getVersion()).to.be('kibana');
- });
- });
-
- describe('#isEnabled()', () => {
- describe('spec.isEnabled is not defined', () => {
- function setup(configPrefix, configGetImpl) {
- const spec = new PluginSpec(fooPack, { configPrefix });
- const config = {
- get: sinon.spy(configGetImpl),
- has: sinon.stub(),
- };
-
- return { spec, config };
- }
-
- it('throws if not passed a config service', () => {
- const { spec } = setup('a.b.c', () => true);
-
- expect(() => spec.isEnabled()).to.throwError((error) => {
- expect(error.message).to.contain('must be called with a config service');
- });
- expect(() => spec.isEnabled(null)).to.throwError((error) => {
- expect(error.message).to.contain('must be called with a config service');
- });
- expect(() => spec.isEnabled({ get: () => {} })).to.throwError((error) => {
- expect(error.message).to.contain('must be called with a config service');
- });
- });
-
- it('returns true when config.get([...configPrefix, "enabled"]) returns true', () => {
- const { spec, config } = setup('d.e.f', () => true);
-
- expect(spec.isEnabled(config)).to.be(true);
- sinon.assert.calledOnce(config.get);
- sinon.assert.calledWithExactly(config.get, ['d', 'e', 'f', 'enabled']);
- });
-
- it('returns false when config.get([...configPrefix, "enabled"]) returns false', () => {
- const { spec, config } = setup('g.h.i', () => false);
-
- expect(spec.isEnabled(config)).to.be(false);
- sinon.assert.calledOnce(config.get);
- sinon.assert.calledWithExactly(config.get, ['g', 'h', 'i', 'enabled']);
- });
- });
-
- describe('spec.isEnabled is defined', () => {
- function setup(isEnabledImpl) {
- const isEnabled = sinon.spy(isEnabledImpl);
- const spec = new PluginSpec(fooPack, { isEnabled });
- const config = {
- get: sinon.stub(),
- has: sinon.stub(),
- };
-
- return { isEnabled, spec, config };
- }
-
- it('throws if not passed a config service', () => {
- const { spec } = setup(() => true);
-
- expect(() => spec.isEnabled()).to.throwError((error) => {
- expect(error.message).to.contain('must be called with a config service');
- });
- expect(() => spec.isEnabled(null)).to.throwError((error) => {
- expect(error.message).to.contain('must be called with a config service');
- });
- expect(() => spec.isEnabled({ get: () => {} })).to.throwError((error) => {
- expect(error.message).to.contain('must be called with a config service');
- });
- });
-
- it('does not check config if spec.isEnabled returns true', () => {
- const { spec, isEnabled, config } = setup(() => true);
-
- expect(spec.isEnabled(config)).to.be(true);
- sinon.assert.calledOnce(isEnabled);
- sinon.assert.notCalled(config.get);
- });
-
- it('does not check config if spec.isEnabled returns false', () => {
- const { spec, isEnabled, config } = setup(() => false);
-
- expect(spec.isEnabled(config)).to.be(false);
- sinon.assert.calledOnce(isEnabled);
- sinon.assert.notCalled(config.get);
- });
- });
- });
-
- describe('#getExpectedKibanaVersion()', () => {
- describe('has: spec.kibanaVersion,pkg.kibana.version,spec.version,pkg.version', () => {
- it('uses spec.kibanaVersion', () => {
- const pack = new PluginPack({
- path: '/dev/null',
- pkg: {
- name: 'expkv',
- version: '1.0.0',
- kibana: {
- version: '6.0.0',
- },
- },
- });
-
- const spec = new PluginSpec(pack, {
- version: '2.0.0',
- kibanaVersion: '5.0.0',
- });
-
- expect(spec.getExpectedKibanaVersion()).to.be('5.0.0');
- });
- });
- describe('missing: spec.kibanaVersion, has: pkg.kibana.version,spec.version,pkg.version', () => {
- it('uses pkg.kibana.version', () => {
- const pack = new PluginPack({
- path: '/dev/null',
- pkg: {
- name: 'expkv',
- version: '1.0.0',
- kibana: {
- version: '6.0.0',
- },
- },
- });
-
- const spec = new PluginSpec(pack, {
- version: '2.0.0',
- });
-
- expect(spec.getExpectedKibanaVersion()).to.be('6.0.0');
- });
- });
- describe('missing: spec.kibanaVersion,pkg.kibana.version, has: spec.version,pkg.version', () => {
- it('uses spec.version', () => {
- const pack = new PluginPack({
- path: '/dev/null',
- pkg: {
- name: 'expkv',
- version: '1.0.0',
- },
- });
-
- const spec = new PluginSpec(pack, {
- version: '2.0.0',
- });
-
- expect(spec.getExpectedKibanaVersion()).to.be('2.0.0');
- });
- });
- describe('missing: spec.kibanaVersion,pkg.kibana.version,spec.version, has: pkg.version', () => {
- it('uses pkg.version', () => {
- const pack = new PluginPack({
- path: '/dev/null',
- pkg: {
- name: 'expkv',
- version: '1.0.0',
- },
- });
-
- const spec = new PluginSpec(pack, {});
-
- expect(spec.getExpectedKibanaVersion()).to.be('1.0.0');
- });
- });
- });
-
- describe('#isVersionCompatible()', () => {
- it('passes this.getExpectedKibanaVersion() and arg to isVersionCompatible(), returns its result', () => {
- const spec = new PluginSpec(fooPack, { version: '1.0.0' });
- sinon.stub(spec, 'getExpectedKibanaVersion').returns('foo');
- const isVersionCompatible = sinon
- .stub(IsVersionCompatibleNS, 'isVersionCompatible')
- .returns('bar');
- expect(spec.isVersionCompatible('baz')).to.be('bar');
-
- sinon.assert.calledOnce(spec.getExpectedKibanaVersion);
- sinon.assert.calledWithExactly(spec.getExpectedKibanaVersion);
-
- sinon.assert.calledOnce(isVersionCompatible);
- sinon.assert.calledWithExactly(isVersionCompatible, 'foo', 'baz');
- });
- });
-
- describe('#getRequiredPluginIds()', () => {
- it('returns spec.require', () => {
- const spec = new PluginSpec(fooPack, { require: [1, 2, 3] });
- expect(spec.getRequiredPluginIds()).to.eql([1, 2, 3]);
- });
- });
-
- describe('#getPublicDir()', () => {
- describe('spec.publicDir === false', () => {
- it('returns null', () => {
- const spec = new PluginSpec(fooPack, { publicDir: false });
- expect(spec.getPublicDir()).to.be(null);
- });
- });
-
- describe('spec.publicDir is falsy', () => {
- it('returns public child of pack path', () => {
- function assert(publicDir) {
- const spec = new PluginSpec(fooPack, { publicDir });
- expect(spec.getPublicDir()).to.be(resolve('/dev/null/public'));
- }
-
- assert(0);
- assert('');
- assert(null);
- assert(undefined);
- assert(NaN);
- });
- });
-
- describe('spec.publicDir is an absolute path', () => {
- it('returns the path', () => {
- const spec = new PluginSpec(fooPack, {
- publicDir: '/var/www/public',
- });
-
- expect(spec.getPublicDir()).to.be('/var/www/public');
- });
- });
-
- // NOTE: see constructor tests for other truthy-tests that throw in constructor
- });
-
- describe('#getExportSpecs()', () => {
- it('returns spec.uiExports', () => {
- const spec = new PluginSpec(fooPack, {
- uiExports: 'foo',
- });
-
- expect(spec.getExportSpecs()).to.be('foo');
- });
- });
-
- describe('#getPreInitHandler()', () => {
- it('returns spec.preInit', () => {
- const spec = new PluginSpec(fooPack, {
- preInit: 'foo',
- });
-
- expect(spec.getPreInitHandler()).to.be('foo');
- });
- });
-
- describe('#getInitHandler()', () => {
- it('returns spec.init', () => {
- const spec = new PluginSpec(fooPack, {
- init: 'foo',
- });
-
- expect(spec.getInitHandler()).to.be('foo');
- });
- });
-
- describe('#getConfigPrefix()', () => {
- describe('spec.configPrefix is truthy', () => {
- it('returns spec.configPrefix', () => {
- const spec = new PluginSpec(fooPack, {
- configPrefix: 'foo.bar.baz',
- });
-
- expect(spec.getConfigPrefix()).to.be('foo.bar.baz');
- });
- });
- describe('spec.configPrefix is falsy', () => {
- it('returns spec.getId()', () => {
- function assert(configPrefix) {
- const spec = new PluginSpec(fooPack, { configPrefix });
- sinon.stub(spec, 'getId').returns('foo');
- expect(spec.getConfigPrefix()).to.be('foo');
- sinon.assert.calledOnce(spec.getId);
- }
-
- assert(false);
- assert(null);
- assert(undefined);
- assert('');
- assert(0);
- });
- });
- });
-
- describe('#getConfigSchemaProvider()', () => {
- it('returns spec.config', () => {
- const spec = new PluginSpec(fooPack, {
- config: 'foo',
- });
-
- expect(spec.getConfigSchemaProvider()).to.be('foo');
- });
- });
-
- describe('#readConfigValue()', () => {
- const spec = new PluginSpec(fooPack, {
- configPrefix: 'foo.bar',
- });
-
- const config = {
- get: sinon.stub(),
- };
-
- afterEach(() => config.get.resetHistory());
-
- describe('key = "foo"', () => {
- it('passes key as own array item', () => {
- spec.readConfigValue(config, 'foo');
- sinon.assert.calledOnce(config.get);
- sinon.assert.calledWithExactly(config.get, ['foo', 'bar', 'foo']);
- });
- });
-
- describe('key = "foo.bar"', () => {
- it('passes key as two array items', () => {
- spec.readConfigValue(config, 'foo.bar');
- sinon.assert.calledOnce(config.get);
- sinon.assert.calledWithExactly(config.get, ['foo', 'bar', 'foo', 'bar']);
- });
- });
-
- describe('key = ["foo", "bar"]', () => {
- it('merged keys into array', () => {
- spec.readConfigValue(config, ['foo', 'bar']);
- sinon.assert.calledOnce(config.get);
- sinon.assert.calledWithExactly(config.get, ['foo', 'bar', 'foo', 'bar']);
- });
- });
- });
-
- describe('#getDeprecationsProvider()', () => {
- it('returns spec.deprecations', () => {
- const spec = new PluginSpec(fooPack, {
- deprecations: 'foo',
- });
-
- expect(spec.getDeprecationsProvider()).to.be('foo');
- });
- });
- });
-});
diff --git a/src/legacy/plugin_discovery/plugin_spec/index.js b/src/legacy/plugin_discovery/plugin_spec/index.js
deleted file mode 100644
index 671d311b152e2..0000000000000
--- a/src/legacy/plugin_discovery/plugin_spec/index.js
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-export { PluginSpec } from './plugin_spec';
diff --git a/src/legacy/plugin_discovery/plugin_spec/is_version_compatible.js b/src/legacy/plugin_discovery/plugin_spec/is_version_compatible.js
deleted file mode 100644
index 6822c168f368d..0000000000000
--- a/src/legacy/plugin_discovery/plugin_spec/is_version_compatible.js
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { cleanVersion, versionSatisfies } from '../../utils/version';
-
-export function isVersionCompatible(version, compatibleWith) {
- // the special "kibana" version can be used to always be compatible,
- // but is intentionally not supported by the plugin installer
- if (version === 'kibana') {
- return true;
- }
-
- return versionSatisfies(cleanVersion(version), cleanVersion(compatibleWith));
-}
diff --git a/src/legacy/plugin_discovery/plugin_spec/plugin_spec.js b/src/legacy/plugin_discovery/plugin_spec/plugin_spec.js
deleted file mode 100644
index db1ec425f2ce5..0000000000000
--- a/src/legacy/plugin_discovery/plugin_spec/plugin_spec.js
+++ /dev/null
@@ -1,210 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { resolve, basename, isAbsolute as isAbsolutePath } from 'path';
-
-import { get, toPath } from 'lodash';
-
-import { createInvalidPluginError } from '../errors';
-import { isVersionCompatible } from './is_version_compatible';
-
-export class PluginSpec {
- /**
- * @param {PluginPack} pack The plugin pack that produced this spec
- * @param {Object} opts the options for this plugin
- * @param {String} [opts.id=pkg.name] the id for this plugin.
- * @param {Object} [opts.uiExports] a mapping of UiExport types to
- * UI modules or metadata about the UI module
- * @param {Array} [opts.require] the other plugins that this plugin
- * requires. These plugins must exist and be enabled for this plugin
- * to function. The require'd plugins will also be initialized first,
- * in order to make sure that dependencies provided by these plugins
- * are available
- * @param {String} [opts.version=pkg.version] the version of this plugin
- * @param {Function} [opts.init] A function that will be called to initialize
- * this plugin at the appropriate time.
- * @param {Function} [opts.configPrefix=this.id] The prefix to use for
- * configuration values in the main configuration service
- * @param {Function} [opts.config] A function that produces a configuration
- * schema using Joi, which is passed as its first argument.
- * @param {String|False} [opts.publicDir=path + '/public'] the public
- * directory for this plugin. The final directory must have the name "public",
- * though it can be located somewhere besides the root of the plugin. Set
- * this to false to disable exposure of a public directory
- */
- constructor(pack, options) {
- const {
- id,
- require,
- version,
- kibanaVersion,
- uiExports,
- uiCapabilities,
- publicDir,
- configPrefix,
- config,
- deprecations,
- preInit,
- init,
- postInit,
- isEnabled,
- } = options;
-
- this._id = id;
- this._pack = pack;
- this._version = version;
- this._kibanaVersion = kibanaVersion;
- this._require = require;
-
- this._publicDir = publicDir;
- this._uiExports = uiExports;
- this._uiCapabilities = uiCapabilities;
-
- this._configPrefix = configPrefix;
- this._configSchemaProvider = config;
- this._configDeprecationsProvider = deprecations;
-
- this._isEnabled = isEnabled;
- this._preInit = preInit;
- this._init = init;
- this._postInit = postInit;
-
- if (!this.getId()) {
- throw createInvalidPluginError(this, 'Unable to determine plugin id');
- }
-
- if (!this.getVersion()) {
- throw createInvalidPluginError(this, 'Unable to determine plugin version');
- }
-
- if (this.getRequiredPluginIds() !== undefined && !Array.isArray(this.getRequiredPluginIds())) {
- throw createInvalidPluginError(this, '"plugin.require" must be an array of plugin ids');
- }
-
- if (this._publicDir) {
- if (!isAbsolutePath(this._publicDir)) {
- throw createInvalidPluginError(this, 'plugin.publicDir must be an absolute path');
- }
- if (basename(this._publicDir) !== 'public') {
- throw createInvalidPluginError(
- this,
- `publicDir for plugin ${this.getId()} must end with a "public" directory.`
- );
- }
- }
- }
-
- getPack() {
- return this._pack;
- }
-
- getPkg() {
- return this._pack.getPkg();
- }
-
- getPath() {
- return this._pack.getPath();
- }
-
- getId() {
- return this._id || this.getPkg().name;
- }
-
- getVersion() {
- return this._version || this.getPkg().version;
- }
-
- isEnabled(config) {
- if (!config || typeof config.get !== 'function' || typeof config.has !== 'function') {
- throw new TypeError('PluginSpec#isEnabled() must be called with a config service');
- }
-
- if (this._isEnabled) {
- return this._isEnabled(config);
- }
-
- return Boolean(this.readConfigValue(config, 'enabled'));
- }
-
- getExpectedKibanaVersion() {
- // Plugins must specify their version, and by default that version should match
- // the version of kibana down to the patch level. If these two versions need
- // to diverge, they can specify a kibana.version in the package to indicate the
- // version of kibana the plugin is intended to work with.
- return (
- this._kibanaVersion || get(this.getPack().getPkg(), 'kibana.version') || this.getVersion()
- );
- }
-
- isVersionCompatible(actualKibanaVersion) {
- return isVersionCompatible(this.getExpectedKibanaVersion(), actualKibanaVersion);
- }
-
- getRequiredPluginIds() {
- return this._require;
- }
-
- getPublicDir() {
- if (this._publicDir === false) {
- return null;
- }
-
- if (!this._publicDir) {
- return resolve(this.getPack().getPath(), 'public');
- }
-
- return this._publicDir;
- }
-
- getExportSpecs() {
- return this._uiExports;
- }
-
- getUiCapabilitiesProvider() {
- return this._uiCapabilities;
- }
-
- getPreInitHandler() {
- return this._preInit;
- }
-
- getInitHandler() {
- return this._init;
- }
-
- getPostInitHandler() {
- return this._postInit;
- }
-
- getConfigPrefix() {
- return this._configPrefix || this.getId();
- }
-
- getConfigSchemaProvider() {
- return this._configSchemaProvider;
- }
-
- readConfigValue(config, key) {
- return config.get([...toPath(this.getConfigPrefix()), ...toPath(key)]);
- }
-
- getDeprecationsProvider() {
- return this._configDeprecationsProvider;
- }
-}
diff --git a/src/legacy/plugin_discovery/plugin_spec/plugin_spec_options.d.ts b/src/legacy/plugin_discovery/plugin_spec/plugin_spec_options.d.ts
deleted file mode 100644
index e1ed2f57375a4..0000000000000
--- a/src/legacy/plugin_discovery/plugin_spec/plugin_spec_options.d.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-import { Server } from '../../server/kbn_server';
-import { Capabilities } from '../../../core/server';
-
-export type InitPluginFunction = (server: Server) => void;
-export interface UiExports {
- injectDefaultVars?: (server: Server) => { [key: string]: any };
-}
-
-export interface PluginSpecOptions {
- id: string;
- require?: string[];
- publicDir?: string;
- uiExports?: UiExports;
- uiCapabilities?: Capabilities;
- init?: InitPluginFunction;
- config?: any;
-}
diff --git a/src/legacy/plugin_discovery/types.ts b/src/legacy/plugin_discovery/types.ts
deleted file mode 100644
index 700ca6fa68c95..0000000000000
--- a/src/legacy/plugin_discovery/types.ts
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { Server } from '../server/kbn_server';
-import { Capabilities } from '../../core/server';
-import { AppCategory } from '../../core/types';
-
-/**
- * Usage
- *
- * ```
- * const apmOss: LegacyPlugin = (kibana) => {
- * return new kibana.Plugin({
- * id: 'apm_oss',
- * // ...
- * });
- * };
- * ```
- */
-export type LegacyPluginInitializer = (kibana: LegacyPluginApi) => ArrayOrItem;
-
-export type ArrayOrItem = T | T[];
-
-export interface LegacyPluginApi {
- Plugin: new (options: Partial) => LegacyPluginSpec;
-}
-
-export interface LegacyPluginOptions {
- id: string;
- require: string[];
- version: string;
- kibanaVersion: 'kibana';
- uiExports: Partial<{
- app: Partial<{
- title: string;
- category?: AppCategory;
- description: string;
- main: string;
- icon: string;
- euiIconType: string;
- order: number;
- listed: boolean;
- }>;
- apps: any;
- hacks: string[];
- visualize: string[];
- devTools: string[];
- injectDefaultVars: (server: Server) => Record;
- home: string[];
- mappings: any;
- migrations: any;
- visTypes: string[];
- embeddableActions?: string[];
- embeddableFactories?: string[];
- uiSettingDefaults?: Record;
- interpreter: string | string[];
- }>;
- uiCapabilities?: Capabilities;
- publicDir: any;
- configPrefix: any;
- config: any;
- deprecations: any;
- preInit: any;
- init: InitPluginFunction;
- postInit: any;
- isEnabled: boolean;
-}
-
-export type InitPluginFunction = (server: Server) => void;
-
-export interface LegacyPluginSpec {
- getPack(): any;
- getPkg(): any;
- getPath(): string;
- getId(): string;
- getVersion(): string;
- isEnabled(config: any): boolean;
- getExpectedKibanaVersion(): string;
- isVersionCompatible(actualKibanaVersion: any): boolean;
- getRequiredPluginIds(): string[];
- getPublicDir(): string | null;
- getExportSpecs(): any;
- getUiCapabilitiesProvider(): any;
- getPreInitHandler(): any;
- getInitHandler(): any;
- getPostInitHandler(): any;
- getConfigPrefix(): string;
- getConfigSchemaProvider(): any;
- readConfigValue(config: any, key: string): any;
- getDeprecationsProvider(): any;
-}
diff --git a/src/legacy/server/config/schema.js b/src/legacy/server/config/schema.js
index f8736fb30f90e..a94766ef06926 100644
--- a/src/legacy/server/config/schema.js
+++ b/src/legacy/server/config/schema.js
@@ -131,6 +131,7 @@ export default () =>
}),
}).default(),
+ // still used by the legacy i18n mixin
plugins: Joi.object({
paths: Joi.array().items(Joi.string()).default([]),
scanDirs: Joi.array().items(Joi.string()).default([]),
@@ -146,71 +147,8 @@ export default () =>
status: Joi.object({
allowAnonymous: Joi.boolean().default(false),
}).default(),
- map: Joi.object({
- includeElasticMapsService: Joi.boolean().default(true),
- proxyElasticMapsServiceInMaps: Joi.boolean().default(false),
- tilemap: Joi.object({
- url: Joi.string(),
- options: Joi.object({
- attribution: Joi.string(),
- minZoom: Joi.number().min(0, 'Must be 0 or higher').default(0),
- maxZoom: Joi.number().default(10),
- tileSize: Joi.number(),
- subdomains: Joi.array().items(Joi.string()).single(),
- errorTileUrl: Joi.string().uri(),
- tms: Joi.boolean(),
- reuseTiles: Joi.boolean(),
- bounds: Joi.array().items(Joi.array().items(Joi.number()).min(2).required()).min(2),
- default: Joi.boolean(),
- }).default({
- default: true,
- }),
- }).default(),
- regionmap: Joi.object({
- includeElasticMapsService: Joi.boolean().default(true),
- layers: Joi.array()
- .items(
- Joi.object({
- url: Joi.string(),
- format: Joi.object({
- type: Joi.string().default('geojson'),
- }).default({
- type: 'geojson',
- }),
- meta: Joi.object({
- feature_collection_path: Joi.string().default('data'),
- }).default({
- feature_collection_path: 'data',
- }),
- attribution: Joi.string(),
- name: Joi.string(),
- fields: Joi.array().items(
- Joi.object({
- name: Joi.string(),
- description: Joi.string(),
- })
- ),
- })
- )
- .default([]),
- }).default(),
- manifestServiceUrl: Joi.string().default('').allow(''),
- emsFileApiUrl: Joi.string().default('https://vector.maps.elastic.co'),
- emsTileApiUrl: Joi.string().default('https://tiles.maps.elastic.co'),
- emsLandingPageUrl: Joi.string().default('https://maps.elastic.co/v7.9'),
- emsFontLibraryUrl: Joi.string().default(
- 'https://tiles.maps.elastic.co/fonts/{fontstack}/{range}.pbf'
- ),
- emsTileLayerId: Joi.object({
- bright: Joi.string().default('road_map'),
- desaturated: Joi.string().default('road_map_desaturated'),
- dark: Joi.string().default('dark_map'),
- }).default({
- bright: 'road_map',
- desaturated: 'road_map_desaturated',
- dark: 'dark_map',
- }),
- }).default(),
+
+ map: HANDLED_IN_NEW_PLATFORM,
i18n: Joi.object({
locale: Joi.string().default('en'),
diff --git a/src/legacy/server/kbn_server.d.ts b/src/legacy/server/kbn_server.d.ts
index 3cfda0e0696bb..1718a9a8f55da 100644
--- a/src/legacy/server/kbn_server.d.ts
+++ b/src/legacy/server/kbn_server.d.ts
@@ -26,11 +26,10 @@ import {
LoggerFactory,
PackageInfo,
LegacyServiceSetupDeps,
- LegacyServiceDiscoverPlugins,
} from '../../core/server';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
-import { LegacyConfig, ILegacyInternals } from '../../core/server/legacy';
+import { LegacyConfig } from '../../core/server/legacy';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { UiPlugins } from '../../core/server/plugins';
@@ -58,9 +57,7 @@ export interface PluginsSetup {
export interface KibanaCore {
__internals: {
- elasticsearch: LegacyServiceSetupDeps['core']['elasticsearch'];
hapiServer: LegacyServiceSetupDeps['core']['http']['server'];
- legacy: ILegacyInternals;
rendering: LegacyServiceSetupDeps['core']['rendering'];
uiPlugins: UiPlugins;
};
@@ -90,31 +87,18 @@ export interface NewPlatform {
stop: null;
}
-export type LegacyPlugins = Pick<
- LegacyServiceDiscoverPlugins,
- 'pluginSpecs' | 'disabledPluginSpecs' | 'uiExports'
->;
-
// eslint-disable-next-line import/no-default-export
export default class KbnServer {
public readonly newPlatform: NewPlatform;
public server: Server;
public inject: Server['inject'];
- public pluginSpecs: any[];
- public uiBundles: any;
- constructor(
- settings: Record,
- config: KibanaConfig,
- core: KibanaCore,
- legacyPlugins: LegacyPlugins
- );
+ constructor(settings: Record, config: KibanaConfig, core: KibanaCore);
public ready(): Promise;
public mixin(...fns: KbnMixinFunc[]): Promise;
public listen(): Promise;
public close(): Promise;
- public afterPluginsInit(callback: () => void): void;
public applyLoggingConfiguration(settings: any): void;
public config: KibanaConfig;
}
diff --git a/src/legacy/server/kbn_server.js b/src/legacy/server/kbn_server.js
index 107e5f6387833..e29563a7c6266 100644
--- a/src/legacy/server/kbn_server.js
+++ b/src/legacy/server/kbn_server.js
@@ -30,7 +30,6 @@ import { loggingMixin } from './logging';
import warningsMixin from './warnings';
import configCompleteMixin from './config/complete';
import { optimizeMixin } from '../../optimize';
-import * as Plugins from './plugins';
import { uiMixin } from '../ui';
import { i18nMixin } from './i18n';
@@ -47,9 +46,8 @@ export default class KbnServer {
* @param {Record} settings
* @param {KibanaConfig} config
* @param {KibanaCore} core
- * @param {LegacyPlugins} legacyPlugins
*/
- constructor(settings, config, core, legacyPlugins) {
+ constructor(settings, config, core) {
this.name = pkg.name;
this.version = pkg.version;
this.build = pkg.build || false;
@@ -74,14 +72,8 @@ export default class KbnServer {
stop: null,
};
- this.uiExports = legacyPlugins.uiExports;
- this.pluginSpecs = legacyPlugins.pluginSpecs;
- this.disabledPluginSpecs = legacyPlugins.disabledPluginSpecs;
-
this.ready = constant(
this.mixin(
- Plugins.waitForInitSetupMixin,
-
// Sets global HTTP behaviors
httpMixin,
@@ -93,22 +85,13 @@ export default class KbnServer {
// scan translations dirs, register locale files and initialize i18n engine.
i18nMixin,
- // find plugins and set this.plugins and this.pluginSpecs
- Plugins.scanMixin,
-
// tell the config we are done loading plugins
configCompleteMixin,
uiMixin,
// setup routes that serve the @kbn/optimizer output
- optimizeMixin,
-
- // initialize the plugins
- Plugins.initializeMixin,
-
- // notify any deferred setup logic that plugins have initialized
- Plugins.waitForInitResolveMixin
+ optimizeMixin
)
);
diff --git a/src/legacy/server/plugins/index.js b/src/legacy/server/plugins/index.js
deleted file mode 100644
index 1511b63b519ae..0000000000000
--- a/src/legacy/server/plugins/index.js
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-export { scanMixin } from './scan_mixin';
-export { initializeMixin } from './initialize_mixin';
-export { waitForInitSetupMixin, waitForInitResolveMixin } from './wait_for_plugins_init';
diff --git a/src/legacy/server/plugins/initialize_mixin.js b/src/legacy/server/plugins/initialize_mixin.js
deleted file mode 100644
index ccf4cd1c1a404..0000000000000
--- a/src/legacy/server/plugins/initialize_mixin.js
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { callPluginHook } from './lib';
-
-/**
- * KbnServer mixin that initializes all plugins found in ./scan mixin
- * @param {KbnServer} kbnServer
- * @param {Hapi.Server} server
- * @param {Config} config
- * @return {Promise}
- */
-export async function initializeMixin(kbnServer, server, config) {
- if (!config.get('plugins.initialize')) {
- server.log(['info'], 'Plugin initialization disabled.');
- return;
- }
-
- async function callHookOnPlugins(hookName) {
- const { plugins } = kbnServer;
- const ids = plugins.map((p) => p.id);
-
- for (const id of ids) {
- await callPluginHook(hookName, plugins, id, []);
- }
- }
-
- await callHookOnPlugins('preInit');
- await callHookOnPlugins('init');
- await callHookOnPlugins('postInit');
-}
diff --git a/src/legacy/server/plugins/lib/call_plugin_hook.js b/src/legacy/server/plugins/lib/call_plugin_hook.js
deleted file mode 100644
index b665869f5d25f..0000000000000
--- a/src/legacy/server/plugins/lib/call_plugin_hook.js
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { last } from 'lodash';
-
-export async function callPluginHook(hookName, plugins, id, history) {
- const plugin = plugins.find((plugin) => plugin.id === id);
-
- // make sure this is a valid plugin id
- if (!plugin) {
- if (history.length) {
- throw new Error(`Unmet requirement "${id}" for plugin "${last(history)}"`);
- } else {
- throw new Error(`Unknown plugin "${id}"`);
- }
- }
-
- const circleStart = history.indexOf(id);
- const path = [...history, id];
-
- // make sure we are not trying to load a dependency within itself
- if (circleStart > -1) {
- const circle = path.slice(circleStart);
- throw new Error(`circular dependency found: "${circle.join(' -> ')}"`);
- }
-
- // call hook on all dependencies
- for (const req of plugin.requiredIds) {
- await callPluginHook(hookName, plugins, req, path);
- }
-
- // call hook on this plugin
- await plugin[hookName]();
-}
diff --git a/src/legacy/server/plugins/lib/call_plugin_hook.test.js b/src/legacy/server/plugins/lib/call_plugin_hook.test.js
deleted file mode 100644
index 30dc2d91a9ab2..0000000000000
--- a/src/legacy/server/plugins/lib/call_plugin_hook.test.js
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import sinon from 'sinon';
-import { callPluginHook } from './call_plugin_hook';
-
-describe('server/plugins/callPluginHook', () => {
- it('should call in correct order based on requirements', async () => {
- const plugins = [
- {
- id: 'foo',
- init: sinon.spy(),
- preInit: sinon.spy(),
- requiredIds: ['bar', 'baz'],
- },
- {
- id: 'bar',
- init: sinon.spy(),
- preInit: sinon.spy(),
- requiredIds: [],
- },
- {
- id: 'baz',
- init: sinon.spy(),
- preInit: sinon.spy(),
- requiredIds: ['bar'],
- },
- ];
-
- await callPluginHook('init', plugins, 'foo', []);
- const [foo, bar, baz] = plugins;
- sinon.assert.calledOnce(foo.init);
- sinon.assert.calledTwice(bar.init);
- sinon.assert.calledOnce(baz.init);
- sinon.assert.callOrder(bar.init, baz.init, foo.init);
- });
-
- it('throws meaningful error when required plugin is missing', async () => {
- const plugins = [
- {
- id: 'foo',
- init: sinon.spy(),
- preInit: sinon.spy(),
- requiredIds: ['bar'],
- },
- ];
-
- try {
- await callPluginHook('init', plugins, 'foo', []);
- throw new Error('expected callPluginHook to throw');
- } catch (error) {
- expect(error.message).toContain('"bar" for plugin "foo"');
- }
- });
-
- it('throws meaningful error when dependencies are circular', async () => {
- const plugins = [
- {
- id: 'foo',
- init: sinon.spy(),
- preInit: sinon.spy(),
- requiredIds: ['bar'],
- },
- {
- id: 'bar',
- init: sinon.spy(),
- preInit: sinon.spy(),
- requiredIds: ['baz'],
- },
- {
- id: 'baz',
- init: sinon.spy(),
- preInit: sinon.spy(),
- requiredIds: ['foo'],
- },
- ];
-
- try {
- await callPluginHook('init', plugins, 'foo', []);
- throw new Error('expected callPluginHook to throw');
- } catch (error) {
- expect(error.message).toContain('foo -> bar -> baz -> foo');
- }
- });
-});
diff --git a/src/legacy/server/plugins/lib/index.js b/src/legacy/server/plugins/lib/index.js
deleted file mode 100644
index 2329d24498b6b..0000000000000
--- a/src/legacy/server/plugins/lib/index.js
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-export { callPluginHook } from './call_plugin_hook';
-export { Plugin } from './plugin';
diff --git a/src/legacy/server/plugins/lib/plugin.js b/src/legacy/server/plugins/lib/plugin.js
deleted file mode 100644
index 48389061199ff..0000000000000
--- a/src/legacy/server/plugins/lib/plugin.js
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { once } from 'lodash';
-
-/**
- * The server plugin class, used to extend the server
- * and add custom behavior. A "scoped" plugin class is
- * created by the PluginApi class and provided to plugin
- * providers that automatically binds all but the `opts`
- * arguments.
- *
- * @class Plugin
- * @param {KbnServer} kbnServer - the KbnServer this plugin
- * belongs to.
- * @param {PluginDefinition} def
- * @param {PluginSpec} spec
- */
-export class Plugin {
- constructor(kbnServer, spec) {
- this.kbnServer = kbnServer;
- this.spec = spec;
- this.pkg = spec.getPkg();
- this.path = spec.getPath();
- this.id = spec.getId();
- this.version = spec.getVersion();
- this.requiredIds = spec.getRequiredPluginIds() || [];
- this.externalPreInit = spec.getPreInitHandler();
- this.externalInit = spec.getInitHandler();
- this.externalPostInit = spec.getPostInitHandler();
- this.enabled = spec.isEnabled(kbnServer.config);
- this.configPrefix = spec.getConfigPrefix();
- this.publicDir = spec.getPublicDir();
-
- this.preInit = once(this.preInit);
- this.init = once(this.init);
- this.postInit = once(this.postInit);
- }
-
- async preInit() {
- if (this.externalPreInit) {
- return await this.externalPreInit(this.kbnServer.server);
- }
- }
-
- async init() {
- const { id, version, kbnServer, configPrefix } = this;
- const { config } = kbnServer;
-
- // setup the hapi register function and get on with it
- const register = async (server, options) => {
- this._server = server;
- this._options = options;
-
- server.logWithMetadata(['plugins', 'debug'], `Initializing plugin ${this.toString()}`, {
- plugin: this,
- });
-
- if (this.publicDir) {
- server.newPlatform.__internals.http.registerStaticDir(
- `/plugins/${id}/{path*}`,
- this.publicDir
- );
- }
-
- if (this.externalInit) {
- await this.externalInit(server, options);
- }
- };
-
- await kbnServer.server.register({
- plugin: { register, name: id, version },
- options: config.has(configPrefix) ? config.get(configPrefix) : null,
- });
- }
-
- async postInit() {
- if (this.externalPostInit) {
- return await this.externalPostInit(this.kbnServer.server);
- }
- }
-
- getServer() {
- return this._server;
- }
-
- getOptions() {
- return this._options;
- }
-
- toJSON() {
- return this.pkg;
- }
-
- toString() {
- return `${this.id}@${this.version}`;
- }
-}
diff --git a/src/legacy/server/plugins/scan_mixin.js b/src/legacy/server/plugins/scan_mixin.js
deleted file mode 100644
index 89ebaf920d9d1..0000000000000
--- a/src/legacy/server/plugins/scan_mixin.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-import { Plugin } from './lib';
-
-export async function scanMixin(kbnServer) {
- kbnServer.plugins = kbnServer.pluginSpecs.map((spec) => new Plugin(kbnServer, spec));
-}
diff --git a/src/legacy/server/plugins/wait_for_plugins_init.js b/src/legacy/server/plugins/wait_for_plugins_init.js
deleted file mode 100644
index 144eb5ef803cc..0000000000000
--- a/src/legacy/server/plugins/wait_for_plugins_init.js
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-/**
- * Tracks the individual queue for each kbnServer, rather than attaching
- * it to the kbnServer object via a property or something
- * @type {WeakMap}
- */
-const queues = new WeakMap();
-
-export function waitForInitSetupMixin(kbnServer) {
- queues.set(kbnServer, []);
-
- kbnServer.afterPluginsInit = function (callback) {
- const queue = queues.get(kbnServer);
-
- if (!queue) {
- throw new Error(
- 'Plugins have already initialized. Only use this method for setup logic that must wait for plugins to initialize.'
- );
- }
-
- queue.push(callback);
- };
-}
-
-export async function waitForInitResolveMixin(kbnServer, server, config) {
- const queue = queues.get(kbnServer);
- queues.set(kbnServer, null);
-
- // only actually call the callbacks if we are really initializing
- if (config.get('plugins.initialize')) {
- for (const cb of queue) {
- await cb();
- }
- }
-}
diff --git a/src/legacy/types.ts b/src/legacy/types.ts
deleted file mode 100644
index 43c9ac79538b1..0000000000000
--- a/src/legacy/types.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-export * from './plugin_discovery/types';
diff --git a/src/legacy/ui/__tests__/fixtures/plugin_async_foo/index.js b/src/legacy/ui/__tests__/fixtures/plugin_async_foo/index.js
deleted file mode 100644
index afe618c6d3d9c..0000000000000
--- a/src/legacy/ui/__tests__/fixtures/plugin_async_foo/index.js
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import Bluebird from 'bluebird';
-
-export default (kibana) =>
- new kibana.Plugin({
- config(Joi) {
- return Joi.object()
- .keys({
- enabled: Joi.boolean().default(true),
- delay: Joi.number().required(),
- shared: Joi.string(),
- })
- .default();
- },
-
- uiExports: {
- async injectDefaultVars(server, options) {
- await Bluebird.delay(options.delay);
- return { shared: options.shared };
- },
- },
- });
diff --git a/src/legacy/ui/__tests__/fixtures/plugin_async_foo/package.json b/src/legacy/ui/__tests__/fixtures/plugin_async_foo/package.json
deleted file mode 100644
index fc1c8d8088f1b..0000000000000
--- a/src/legacy/ui/__tests__/fixtures/plugin_async_foo/package.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "name": "plugin_async_foo",
- "version": "kibana"
-}
diff --git a/src/legacy/ui/__tests__/fixtures/plugin_bar/index.js b/src/legacy/ui/__tests__/fixtures/plugin_bar/index.js
deleted file mode 100644
index 975a1dc7c92e7..0000000000000
--- a/src/legacy/ui/__tests__/fixtures/plugin_bar/index.js
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-export default (kibana) =>
- new kibana.Plugin({
- config(Joi) {
- return Joi.object()
- .keys({
- enabled: Joi.boolean().default(true),
- shared: Joi.string(),
- })
- .default();
- },
-
- uiExports: {
- injectDefaultVars(server, options) {
- return { shared: options.shared };
- },
- },
- });
diff --git a/src/legacy/ui/__tests__/fixtures/plugin_bar/package.json b/src/legacy/ui/__tests__/fixtures/plugin_bar/package.json
deleted file mode 100644
index f79b807990dca..0000000000000
--- a/src/legacy/ui/__tests__/fixtures/plugin_bar/package.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "name": "plugin_bar",
- "version": "kibana"
-}
diff --git a/src/legacy/ui/__tests__/fixtures/plugin_foo/index.js b/src/legacy/ui/__tests__/fixtures/plugin_foo/index.js
deleted file mode 100644
index 975a1dc7c92e7..0000000000000
--- a/src/legacy/ui/__tests__/fixtures/plugin_foo/index.js
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-export default (kibana) =>
- new kibana.Plugin({
- config(Joi) {
- return Joi.object()
- .keys({
- enabled: Joi.boolean().default(true),
- shared: Joi.string(),
- })
- .default();
- },
-
- uiExports: {
- injectDefaultVars(server, options) {
- return { shared: options.shared };
- },
- },
- });
diff --git a/src/legacy/ui/__tests__/fixtures/plugin_foo/package.json b/src/legacy/ui/__tests__/fixtures/plugin_foo/package.json
deleted file mode 100644
index c1b7ddd35c9a2..0000000000000
--- a/src/legacy/ui/__tests__/fixtures/plugin_foo/package.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "name": "plugin_foo",
- "version": "kibana"
-}
diff --git a/src/legacy/ui/__tests__/fixtures/test_app/index.js b/src/legacy/ui/__tests__/fixtures/test_app/index.js
deleted file mode 100644
index 3eddefd618ce0..0000000000000
--- a/src/legacy/ui/__tests__/fixtures/test_app/index.js
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-export default (kibana) =>
- new kibana.Plugin({
- uiExports: {
- app: {
- name: 'test_app',
- main: 'plugins/test_app/index.js',
- },
-
- injectDefaultVars() {
- return {
- from_defaults: true,
- };
- },
- },
- init(server) {
- server.injectUiAppVars('test_app', () => ({
- from_test_app: true,
- }));
- },
- });
diff --git a/src/legacy/ui/__tests__/fixtures/test_app/package.json b/src/legacy/ui/__tests__/fixtures/test_app/package.json
deleted file mode 100644
index 3aeb029e4f4cc..0000000000000
--- a/src/legacy/ui/__tests__/fixtures/test_app/package.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "name": "test_app",
- "version": "kibana"
-}
diff --git a/src/legacy/ui/index.js b/src/legacy/ui/index.js
index 05373fa5d1964..5c06cb4677347 100644
--- a/src/legacy/ui/index.js
+++ b/src/legacy/ui/index.js
@@ -18,4 +18,3 @@
*/
export { uiMixin } from './ui_mixin';
-export { collectUiExports } from './ui_exports';
diff --git a/src/legacy/ui/ui_exports/README.md b/src/legacy/ui/ui_exports/README.md
deleted file mode 100644
index 7fb117b1c25b9..0000000000000
--- a/src/legacy/ui/ui_exports/README.md
+++ /dev/null
@@ -1,95 +0,0 @@
-# UI Exports
-
-When defining a Plugin, the `uiExports` key can be used to define a map of export types to values that will be used to configure the UI system. A common use for `uiExports` is `uiExports.app`, which defines the configuration of a [`UiApp`][UiApp] and teaches the UI System how to render, bundle and tell the user about an application.
-
-
-## `collectUiExports(pluginSpecs): { [type: string]: any }`
-
-This function produces the object commonly found at `kbnServer.uiExports`. This object is created by calling `collectPluginExports()` with a standard set of export type reducers and defaults for the UI System.
-
-### export type reducers
-
-The [`ui_export_types` module][UiExportTypes] defines the reducer used for each uiExports key (or `type`). The name of every export in [./ui_export_types/index.js][UiExportTypes] is a key that plugins can define in their `uiExports` specification and the value of those exports are reducers that `collectPluginExports()` will call to produce the merged result of all export specs.
-
-### example - UiApps
-
-Plugin authors can define a new UiApp in their plugin specification like so:
-
-```js
-// a single app export
-export default function (kibana) {
- return new kibana.Plugin({
- //...
- uiExports: {
- app: {
- // uiApp spec options go here
- }
- }
- })
-}
-
-// apps can also export multiple apps
-export default function (kibana) {
- return new kibana.Plugin({
- //...
- uiExports: {
- apps: [
- { /* uiApp spec options */ },
- { /* second uiApp spec options */ },
- ]
- }
- })
-}
-```
-
-To handle this export type, the [ui_export_types][UiExportTypes] module exports two reducers, one named `app` and the other `apps`.
-
-```js
-export const app = ...
-export const apps = ...
-```
-
-These reducers are defined in [`ui_export_types/ui_apps`][UiAppExportType] and have the exact same definition:
-
-```js
-// `wrap()` produces a reducer by wrapping a base reducer with modifiers.
-// All but the last argument are modifiers that take a reducer and return
-// an alternate reducer to use in it's place.
-//
-// Most wrappers call their target reducer with slightly different
-// arguments. This allows composing standard reducer modifications for
-// reuse, consistency, and easy reference (once you get the hang of it).
-wrap(
- // calls the next reducer with the `type` set to `uiAppSpecs`, ignoring
- // the key the plugin author used to define this spec ("app" or "apps"
- // in this example)
- alias('uiAppSpecs'),
-
- // calls the next reducer with the `spec` set to the result of calling
- // `applySpecDefaults(spec, type, pluginSpec)` which merges some defaults
- // from the `PluginSpec` because we want uiAppSpecs to be useful individually
- mapSpec(applySpecDefaults),
-
- // writes this spec to `acc[type]` (`acc.uiAppSpecs` in this example since
- // the type was set to `uiAppSpecs` by `alias()`). It does this by concatenating
- // the current value and the spec into an array. If either item is already
- // an array its items are added to the result individually. If either item
- // is undefined it is ignored.
- //
- // NOTE: since flatConcatAtType is last it isn't a wrapper, it's
- // just a normal reducer
- flatConcatAtType
-)
-```
-
-This reducer format was chosen so that it will be easier to look back at these reducers and see that `app` and `apps` export specs are written to `kbnServer.uiExports.uiAppSpecs`, with defaults applied, in an array.
-
-### defaults
-
-The [`ui_exports/ui_export_defaults`][UiExportDefaults] module defines the default shape of the uiExports object produced by `collectUiExports()`. The defaults generally describe the `uiExports` from the UI System itself, like default visTypes and such.
-
-[UiExportDefaults]: ./ui_export_defaults.js "uiExport defaults definition"
-[UiExportTypes]: ./ui_export_types/index.js "Index of default ui_export_types module"
-[UiAppExportType]: ./ui_export_types/ui_apps.js "UiApp extension type definition"
-[PluginSpec]: ../../plugin_discovery/plugin_spec/plugin_spec.js "PluginSpec class definition"
-[PluginDiscovery]: '../../plugin_discovery' "plugin_discovery module"
\ No newline at end of file
diff --git a/src/legacy/ui/ui_exports/collect_ui_exports.ts b/src/legacy/ui/ui_exports/collect_ui_exports.ts
deleted file mode 100644
index edb2a11dc0527..0000000000000
--- a/src/legacy/ui/ui_exports/collect_ui_exports.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { LegacyUiExports } from '../../../core/server';
-
-// @ts-ignore
-import { UI_EXPORT_DEFAULTS } from './ui_export_defaults';
-// @ts-ignore
-import * as uiExportTypeReducers from './ui_export_types';
-// @ts-ignore
-import { reduceExportSpecs } from '../../plugin_discovery';
-
-export function collectUiExports(pluginSpecs: unknown[]): LegacyUiExports {
- return reduceExportSpecs(pluginSpecs, uiExportTypeReducers, UI_EXPORT_DEFAULTS);
-}
diff --git a/src/legacy/ui/ui_exports/index.js b/src/legacy/ui/ui_exports/index.js
deleted file mode 100644
index 56db698dc7b03..0000000000000
--- a/src/legacy/ui/ui_exports/index.js
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-export { collectUiExports } from './collect_ui_exports';
diff --git a/src/legacy/ui/ui_exports/ui_export_defaults.js b/src/legacy/ui/ui_exports/ui_export_defaults.js
deleted file mode 100644
index 227954155ce88..0000000000000
--- a/src/legacy/ui/ui_exports/ui_export_defaults.js
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-export const UI_EXPORT_DEFAULTS = {};
diff --git a/src/legacy/ui/ui_exports/ui_export_types/index.js b/src/legacy/ui/ui_exports/ui_export_types/index.js
deleted file mode 100644
index 9ff6a53f4afb9..0000000000000
--- a/src/legacy/ui/ui_exports/ui_export_types/index.js
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-export { injectDefaultVars, replaceInjectedVars } from './modify_injected_vars';
-
-export {
- mappings,
- migrations,
- savedObjectSchemas,
- savedObjectsManagement,
- validations,
-} from './saved_object';
-
-export { taskDefinitions } from './task_definitions';
-
-export { link, links } from './ui_nav_links';
-
-export { uiSettingDefaults } from './ui_settings';
-
-export { unknown } from './unknown';
diff --git a/src/legacy/ui/ui_exports/ui_export_types/modify_injected_vars.js b/src/legacy/ui/ui_exports/ui_export_types/modify_injected_vars.js
deleted file mode 100644
index 4bb9f350bd959..0000000000000
--- a/src/legacy/ui/ui_exports/ui_export_types/modify_injected_vars.js
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { flatConcatAtType } from './reduce';
-import { wrap, alias, mapSpec } from './modify_reduce';
-
-export const replaceInjectedVars = wrap(alias('injectedVarsReplacers'), flatConcatAtType);
-
-export const injectDefaultVars = wrap(
- alias('defaultInjectedVarProviders'),
- mapSpec((spec, type, pluginSpec) => ({
- pluginSpec,
- fn: spec,
- })),
- flatConcatAtType
-);
diff --git a/src/legacy/ui/ui_exports/ui_export_types/modify_reduce/alias.js b/src/legacy/ui/ui_exports/ui_export_types/modify_reduce/alias.js
deleted file mode 100644
index a894e59a03c81..0000000000000
--- a/src/legacy/ui/ui_exports/ui_export_types/modify_reduce/alias.js
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-/**
- * Creates a reducer wrapper which, when called with a reducer, creates a new
- * reducer that replaces the `type` value with `newType` before delegating to
- * the wrapped reducer
- * @param {String} newType
- * @return {Function}
- */
-export const alias = (newType) => (next) => (acc, spec, type, pluginSpec) =>
- next(acc, spec, newType, pluginSpec);
diff --git a/src/legacy/ui/ui_exports/ui_export_types/modify_reduce/debug.js b/src/legacy/ui/ui_exports/ui_export_types/modify_reduce/debug.js
deleted file mode 100644
index c40bca59fe14c..0000000000000
--- a/src/legacy/ui/ui_exports/ui_export_types/modify_reduce/debug.js
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { mapSpec } from './map_spec';
-
-/**
- * Reducer wrapper which, replaces the `spec` with the details about the definition
- * of that spec
- * @type {Function}
- */
-export const debug = mapSpec((spec, type, pluginSpec) => ({
- spec,
- type,
- pluginSpec,
-}));
diff --git a/src/legacy/ui/ui_exports/ui_export_types/modify_reduce/index.js b/src/legacy/ui/ui_exports/ui_export_types/modify_reduce/index.js
deleted file mode 100644
index 54c81fefdd08a..0000000000000
--- a/src/legacy/ui/ui_exports/ui_export_types/modify_reduce/index.js
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-export { alias } from './alias';
-export { debug } from './debug';
-export { mapSpec } from './map_spec';
-export { wrap } from './wrap';
-export { uniqueKeys } from './unique_keys';
diff --git a/src/legacy/ui/ui_exports/ui_export_types/modify_reduce/map_spec.js b/src/legacy/ui/ui_exports/ui_export_types/modify_reduce/map_spec.js
deleted file mode 100644
index 5970c45e7445e..0000000000000
--- a/src/legacy/ui/ui_exports/ui_export_types/modify_reduce/map_spec.js
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-/**
- * Creates a reducer wrapper which, when called with a reducer, creates a new
- * reducer that replaces the `specs` value with the result of calling
- * `mapFn(spec, type, pluginSpec)` before delegating to the wrapped
- * reducer
- * @param {Function} mapFn receives `(specs, type, pluginSpec)`
- * @return {Function}
- */
-export const mapSpec = (mapFn) => (next) => (acc, spec, type, pluginSpec) =>
- next(acc, mapFn(spec, type, pluginSpec), type, pluginSpec);
diff --git a/src/legacy/ui/ui_exports/ui_export_types/modify_reduce/unique_keys.js b/src/legacy/ui/ui_exports/ui_export_types/modify_reduce/unique_keys.js
deleted file mode 100644
index dedcd057b09e3..0000000000000
--- a/src/legacy/ui/ui_exports/ui_export_types/modify_reduce/unique_keys.js
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-const pluginId = (pluginSpec) => (pluginSpec.id ? pluginSpec.id() : pluginSpec.getId());
-
-export const uniqueKeys = (sourceType) => (next) => (acc, spec, type, pluginSpec) => {
- const duplicates = Object.keys(spec).filter((key) => acc[type] && acc[type].hasOwnProperty(key));
-
- if (duplicates.length) {
- throw new Error(
- `${pluginId(pluginSpec)} defined duplicate ${sourceType || type} values: ${duplicates}`
- );
- }
-
- return next(acc, spec, type, pluginSpec);
-};
diff --git a/src/legacy/ui/ui_exports/ui_export_types/modify_reduce/wrap.js b/src/legacy/ui/ui_exports/ui_export_types/modify_reduce/wrap.js
deleted file mode 100644
index f84d83ed7c845..0000000000000
--- a/src/legacy/ui/ui_exports/ui_export_types/modify_reduce/wrap.js
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-/**
- * Wrap a function with any number of wrappers. Wrappers
- * are functions that take a reducer and return a reducer
- * that should be called in its place. The wrappers will
- * be called in reverse order for setup and then in the
- * order they are defined when the resulting reducer is
- * executed.
- *
- * const reducer = wrap(
- * next => (acc) => acc[1] = 'a',
- * next => (acc) => acc[1] = 'b',
- * next => (acc) => acc[1] = 'c'
- * )
- *
- * reducer('foo') //=> 'fco'
- *
- * @param {Function} ...wrappers
- * @param {Function} reducer
- * @return {Function}
- */
-export function wrap(...args) {
- const reducer = args[args.length - 1];
- const wrappers = args.slice(0, -1);
-
- return wrappers.reverse().reduce((acc, wrapper) => wrapper(acc), reducer);
-}
diff --git a/src/legacy/ui/ui_exports/ui_export_types/reduce/flat_concat_at_type.js b/src/legacy/ui/ui_exports/ui_export_types/reduce/flat_concat_at_type.js
deleted file mode 100644
index 5fcbcac463392..0000000000000
--- a/src/legacy/ui/ui_exports/ui_export_types/reduce/flat_concat_at_type.js
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { createTypeReducer, flatConcat } from './lib';
-
-/**
- * Reducer that merges two values concatenating all values
- * into a flattened array
- * @param {Any} [initial]
- * @return {Function}
- */
-export const flatConcatAtType = createTypeReducer(flatConcat);
diff --git a/src/legacy/ui/ui_exports/ui_export_types/reduce/flat_concat_values_at_type.js b/src/legacy/ui/ui_exports/ui_export_types/reduce/flat_concat_values_at_type.js
deleted file mode 100644
index 229c5be24aac5..0000000000000
--- a/src/legacy/ui/ui_exports/ui_export_types/reduce/flat_concat_values_at_type.js
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { createTypeReducer, flatConcat, mergeWith } from './lib';
-
-/**
- * Reducer that merges specs by concatenating the values of
- * all keys in accumulator and spec with the same logic as concat
- * @param {[type]} initial [description]
- * @return {[type]} [description]
- */
-export const flatConcatValuesAtType = createTypeReducer((objectA, objectB) =>
- mergeWith(objectA || {}, objectB || {}, flatConcat)
-);
diff --git a/src/legacy/ui/ui_exports/ui_export_types/reduce/index.js b/src/legacy/ui/ui_exports/ui_export_types/reduce/index.js
deleted file mode 100644
index 7dc1ba60fb3cb..0000000000000
--- a/src/legacy/ui/ui_exports/ui_export_types/reduce/index.js
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-export { mergeAtType } from './merge_at_type';
-export { flatConcatValuesAtType } from './flat_concat_values_at_type';
-export { flatConcatAtType } from './flat_concat_at_type';
diff --git a/src/legacy/ui/ui_exports/ui_export_types/reduce/lib/create_type_reducer.js b/src/legacy/ui/ui_exports/ui_export_types/reduce/lib/create_type_reducer.js
deleted file mode 100644
index bf4793c208308..0000000000000
--- a/src/legacy/ui/ui_exports/ui_export_types/reduce/lib/create_type_reducer.js
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-/**
- * Creates a reducer that reduces the values within `acc[type]` by calling
- * reducer with signature:
- *
- * reducer(acc[type], spec, type, pluginSpec)
- *
- * @param {Function} reducer
- * @return {Function}
- */
-export const createTypeReducer = (reducer) => (acc, spec, type, pluginSpec) => ({
- ...acc,
- [type]: reducer(acc[type], spec, type, pluginSpec),
-});
diff --git a/src/legacy/ui/ui_exports/ui_export_types/reduce/lib/flat_concat.js b/src/legacy/ui/ui_exports/ui_export_types/reduce/lib/flat_concat.js
deleted file mode 100644
index 1337c8a85d5b4..0000000000000
--- a/src/legacy/ui/ui_exports/ui_export_types/reduce/lib/flat_concat.js
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-/**
- * Concatenate two values into a single array, ignoring either
- * value if it is undefined and flattening the value if it is an array
- * @param {Array|T} a
- * @param {Array} b
- * @return {Array}
- */
-export const flatConcat = (a, b) => [].concat(a === undefined ? [] : a, b === undefined ? [] : b);
diff --git a/src/legacy/ui/ui_exports/ui_export_types/reduce/lib/index.js b/src/legacy/ui/ui_exports/ui_export_types/reduce/lib/index.js
deleted file mode 100644
index e4281caebe245..0000000000000
--- a/src/legacy/ui/ui_exports/ui_export_types/reduce/lib/index.js
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-export { flatConcat } from './flat_concat';
-export { mergeWith } from './merge_with';
-export { createTypeReducer } from './create_type_reducer';
diff --git a/src/legacy/ui/ui_exports/ui_export_types/reduce/lib/merge_with.js b/src/legacy/ui/ui_exports/ui_export_types/reduce/lib/merge_with.js
deleted file mode 100644
index 6c7d31e6fd74d..0000000000000
--- a/src/legacy/ui/ui_exports/ui_export_types/reduce/lib/merge_with.js
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-const uniqueConcat = (arrayA, arrayB) =>
- arrayB.reduce((acc, key) => (acc.includes(key) ? acc : acc.concat(key)), arrayA);
-
-/**
- * Assign the keys from both objA and objB to target after passing the
- * current and new value through merge as `(target[key], source[key])`
- * @param {Object} objA
- * @param {Object} objB
- * @param {Function} merge
- * @return {Object} target
- */
-export function mergeWith(objA, objB, merge) {
- const target = {};
- const keys = uniqueConcat(Object.keys(objA), Object.keys(objB));
- for (const key of keys) {
- target[key] = merge(objA[key], objB[key]);
- }
- return target;
-}
diff --git a/src/legacy/ui/ui_exports/ui_export_types/reduce/merge_at_type.js b/src/legacy/ui/ui_exports/ui_export_types/reduce/merge_at_type.js
deleted file mode 100644
index 4f5a501253851..0000000000000
--- a/src/legacy/ui/ui_exports/ui_export_types/reduce/merge_at_type.js
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { createTypeReducer } from './lib';
-
-export const mergeAtType = createTypeReducer((a, b) => ({
- ...a,
- ...b,
-}));
diff --git a/src/legacy/ui/ui_exports/ui_export_types/saved_object.js b/src/legacy/ui/ui_exports/ui_export_types/saved_object.js
deleted file mode 100644
index be6898d3e642c..0000000000000
--- a/src/legacy/ui/ui_exports/ui_export_types/saved_object.js
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { flatConcatAtType, mergeAtType } from './reduce';
-import { alias, mapSpec, uniqueKeys, wrap } from './modify_reduce';
-
-// mapping types
-export const mappings = wrap(
- alias('savedObjectMappings'),
- mapSpec((spec, type, pluginSpec) => ({
- pluginId: pluginSpec.getId(),
- properties: spec,
- })),
- flatConcatAtType
-);
-
-const pluginId = (pluginSpec) => (pluginSpec.id ? pluginSpec.id() : pluginSpec.getId());
-
-// Combines the `migrations` property of each plugin,
-// ensuring that properties are unique across plugins
-// and has migrations defined where the mappings are defined.
-// See saved_objects/migrations for more details.
-export const migrations = wrap(
- alias('savedObjectMigrations'),
- (next) => (acc, spec, type, pluginSpec) => {
- const mappings = pluginSpec.getExportSpecs().mappings || {};
- const invalidMigrationTypes = Object.keys(spec).filter((type) => !mappings[type]);
- if (invalidMigrationTypes.length) {
- throw new Error(
- 'Migrations and mappings must be defined together in the uiExports of a single plugin. ' +
- `${pluginId(pluginSpec)} defines migrations for types ${invalidMigrationTypes.join(
- ', '
- )} but does not define their mappings.`
- );
- }
- return next(acc, spec, type, pluginSpec);
- },
- uniqueKeys(),
- mergeAtType
-);
-
-export const savedObjectSchemas = wrap(uniqueKeys(), mergeAtType);
-
-export const savedObjectsManagement = wrap(uniqueKeys(), mergeAtType);
-
-// Combines the `validations` property of each plugin,
-// ensuring that properties are unique across plugins.
-// See saved_objects/validation for more details.
-export const validations = wrap(alias('savedObjectValidations'), uniqueKeys(), mergeAtType);
diff --git a/src/legacy/ui/ui_exports/ui_export_types/task_definitions.js b/src/legacy/ui/ui_exports/ui_export_types/task_definitions.js
deleted file mode 100644
index 8a0ed85d86f3e..0000000000000
--- a/src/legacy/ui/ui_exports/ui_export_types/task_definitions.js
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { mergeAtType } from './reduce';
-import { alias, wrap, uniqueKeys } from './modify_reduce';
-
-// How plugins define tasks that the task manager can run.
-export const taskDefinitions = wrap(alias('taskDefinitions'), uniqueKeys(), mergeAtType);
diff --git a/src/legacy/ui/ui_exports/ui_export_types/ui_nav_links.js b/src/legacy/ui/ui_exports/ui_export_types/ui_nav_links.js
deleted file mode 100644
index 34aff7463a249..0000000000000
--- a/src/legacy/ui/ui_exports/ui_export_types/ui_nav_links.js
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { flatConcatAtType } from './reduce';
-import { wrap, alias } from './modify_reduce';
-
-export const links = wrap(alias('navLinkSpecs'), flatConcatAtType);
-export const link = wrap(alias('navLinkSpecs'), flatConcatAtType);
diff --git a/src/legacy/ui/ui_exports/ui_export_types/ui_settings.js b/src/legacy/ui/ui_exports/ui_export_types/ui_settings.js
deleted file mode 100644
index 8d88490579c21..0000000000000
--- a/src/legacy/ui/ui_exports/ui_export_types/ui_settings.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { mergeAtType } from './reduce';
-import { wrap, uniqueKeys } from './modify_reduce';
-
-export const uiSettingDefaults = wrap(uniqueKeys(), mergeAtType);
diff --git a/src/legacy/ui/ui_exports/ui_export_types/unknown.js b/src/legacy/ui/ui_exports/ui_export_types/unknown.js
deleted file mode 100644
index a12a514d2e6bf..0000000000000
--- a/src/legacy/ui/ui_exports/ui_export_types/unknown.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { flatConcatAtType } from './reduce';
-import { wrap, alias, debug } from './modify_reduce';
-
-export const unknown = wrap(debug, alias('unknown'), flatConcatAtType);
diff --git a/src/legacy/ui/ui_render/ui_render_mixin.js b/src/legacy/ui/ui_render/ui_render_mixin.js
index e3b7c1e0c3ff9..2983dbbc28667 100644
--- a/src/legacy/ui/ui_render/ui_render_mixin.js
+++ b/src/legacy/ui/ui_render/ui_render_mixin.js
@@ -67,115 +67,108 @@ export function uiRenderMixin(kbnServer, server, config) {
},
});
- // register the bootstrap.js route after plugins are initialized so that we can
- // detect if any default auth strategies were registered
- kbnServer.afterPluginsInit(() => {
- const authEnabled = !!server.auth.settings.default;
-
- server.route({
- path: '/bootstrap.js',
- method: 'GET',
- config: {
- tags: ['api'],
- auth: authEnabled ? { mode: 'try' } : false,
- },
- async handler(request, h) {
- const soClient = kbnServer.newPlatform.start.core.savedObjects.getScopedClient(
- KibanaRequest.from(request)
- );
- const uiSettings = kbnServer.newPlatform.start.core.uiSettings.asScopedToClient(soClient);
-
- const darkMode =
- !authEnabled || request.auth.isAuthenticated
- ? await uiSettings.get('theme:darkMode')
- : false;
-
- const themeVersion =
- !authEnabled || request.auth.isAuthenticated
- ? await uiSettings.get('theme:version')
- : 'v7';
-
- const themeTag = `${themeVersion === 'v7' ? 'v7' : 'v8'}${darkMode ? 'dark' : 'light'}`;
-
- const buildHash = server.newPlatform.env.packageInfo.buildNum;
- const basePath = config.get('server.basePath');
-
- const regularBundlePath = `${basePath}/${buildHash}/bundles`;
-
- const styleSheetPaths = [
- `${regularBundlePath}/kbn-ui-shared-deps/${UiSharedDeps.baseCssDistFilename}`,
- ...(darkMode
- ? [
- themeVersion === 'v7'
- ? `${regularBundlePath}/kbn-ui-shared-deps/${UiSharedDeps.darkCssDistFilename}`
- : `${regularBundlePath}/kbn-ui-shared-deps/${UiSharedDeps.darkV8CssDistFilename}`,
- `${basePath}/node_modules/@kbn/ui-framework/dist/kui_dark.css`,
- `${basePath}/ui/legacy_dark_theme.css`,
- ]
- : [
- themeVersion === 'v7'
- ? `${regularBundlePath}/kbn-ui-shared-deps/${UiSharedDeps.lightCssDistFilename}`
- : `${regularBundlePath}/kbn-ui-shared-deps/${UiSharedDeps.lightV8CssDistFilename}`,
- `${basePath}/node_modules/@kbn/ui-framework/dist/kui_light.css`,
- `${basePath}/ui/legacy_light_theme.css`,
- ]),
- ];
-
- const kpUiPlugins = kbnServer.newPlatform.__internals.uiPlugins;
- const kpPluginPublicPaths = new Map();
- const kpPluginBundlePaths = new Set();
-
- // recursively iterate over the kpUiPlugin ids and their required bundles
- // to populate kpPluginPublicPaths and kpPluginBundlePaths
- (function readKpPlugins(ids) {
- for (const id of ids) {
- if (kpPluginPublicPaths.has(id)) {
- continue;
- }
-
- kpPluginPublicPaths.set(id, `${regularBundlePath}/plugin/${id}/`);
- kpPluginBundlePaths.add(`${regularBundlePath}/plugin/${id}/${id}.plugin.js`);
- readKpPlugins(kpUiPlugins.internal.get(id).requiredBundles);
+ const authEnabled = !!server.auth.settings.default;
+ server.route({
+ path: '/bootstrap.js',
+ method: 'GET',
+ config: {
+ tags: ['api'],
+ auth: authEnabled ? { mode: 'try' } : false,
+ },
+ async handler(request, h) {
+ const soClient = kbnServer.newPlatform.start.core.savedObjects.getScopedClient(
+ KibanaRequest.from(request)
+ );
+ const uiSettings = kbnServer.newPlatform.start.core.uiSettings.asScopedToClient(soClient);
+
+ const darkMode =
+ !authEnabled || request.auth.isAuthenticated
+ ? await uiSettings.get('theme:darkMode')
+ : false;
+
+ const themeVersion =
+ !authEnabled || request.auth.isAuthenticated ? await uiSettings.get('theme:version') : 'v7';
+
+ const themeTag = `${themeVersion === 'v7' ? 'v7' : 'v8'}${darkMode ? 'dark' : 'light'}`;
+
+ const buildHash = server.newPlatform.env.packageInfo.buildNum;
+ const basePath = config.get('server.basePath');
+
+ const regularBundlePath = `${basePath}/${buildHash}/bundles`;
+
+ const styleSheetPaths = [
+ `${regularBundlePath}/kbn-ui-shared-deps/${UiSharedDeps.baseCssDistFilename}`,
+ ...(darkMode
+ ? [
+ themeVersion === 'v7'
+ ? `${regularBundlePath}/kbn-ui-shared-deps/${UiSharedDeps.darkCssDistFilename}`
+ : `${regularBundlePath}/kbn-ui-shared-deps/${UiSharedDeps.darkV8CssDistFilename}`,
+ `${basePath}/node_modules/@kbn/ui-framework/dist/kui_dark.css`,
+ `${basePath}/ui/legacy_dark_theme.css`,
+ ]
+ : [
+ themeVersion === 'v7'
+ ? `${regularBundlePath}/kbn-ui-shared-deps/${UiSharedDeps.lightCssDistFilename}`
+ : `${regularBundlePath}/kbn-ui-shared-deps/${UiSharedDeps.lightV8CssDistFilename}`,
+ `${basePath}/node_modules/@kbn/ui-framework/dist/kui_light.css`,
+ `${basePath}/ui/legacy_light_theme.css`,
+ ]),
+ ];
+
+ const kpUiPlugins = kbnServer.newPlatform.__internals.uiPlugins;
+ const kpPluginPublicPaths = new Map();
+ const kpPluginBundlePaths = new Set();
+
+ // recursively iterate over the kpUiPlugin ids and their required bundles
+ // to populate kpPluginPublicPaths and kpPluginBundlePaths
+ (function readKpPlugins(ids) {
+ for (const id of ids) {
+ if (kpPluginPublicPaths.has(id)) {
+ continue;
}
- })(kpUiPlugins.public.keys());
-
- const jsDependencyPaths = [
- ...UiSharedDeps.jsDepFilenames.map(
- (filename) => `${regularBundlePath}/kbn-ui-shared-deps/${filename}`
- ),
- `${regularBundlePath}/kbn-ui-shared-deps/${UiSharedDeps.jsFilename}`,
-
- `${regularBundlePath}/core/core.entry.js`,
- ...kpPluginBundlePaths,
- ];
-
- // These paths should align with the bundle routes configured in
- // src/optimize/bundles_route/bundles_route.ts
- const publicPathMap = JSON.stringify({
- core: `${regularBundlePath}/core/`,
- 'kbn-ui-shared-deps': `${regularBundlePath}/kbn-ui-shared-deps/`,
- ...Object.fromEntries(kpPluginPublicPaths),
- });
-
- const bootstrap = new AppBootstrap({
- templateData: {
- themeTag,
- jsDependencyPaths,
- styleSheetPaths,
- publicPathMap,
- },
- });
-
- const body = await bootstrap.getJsFile();
- const etag = await bootstrap.getJsFileHash();
-
- return h
- .response(body)
- .header('cache-control', 'must-revalidate')
- .header('content-type', 'application/javascript')
- .etag(etag);
- },
- });
+
+ kpPluginPublicPaths.set(id, `${regularBundlePath}/plugin/${id}/`);
+ kpPluginBundlePaths.add(`${regularBundlePath}/plugin/${id}/${id}.plugin.js`);
+ readKpPlugins(kpUiPlugins.internal.get(id).requiredBundles);
+ }
+ })(kpUiPlugins.public.keys());
+
+ const jsDependencyPaths = [
+ ...UiSharedDeps.jsDepFilenames.map(
+ (filename) => `${regularBundlePath}/kbn-ui-shared-deps/${filename}`
+ ),
+ `${regularBundlePath}/kbn-ui-shared-deps/${UiSharedDeps.jsFilename}`,
+
+ `${regularBundlePath}/core/core.entry.js`,
+ ...kpPluginBundlePaths,
+ ];
+
+ // These paths should align with the bundle routes configured in
+ // src/optimize/bundles_route/bundles_route.ts
+ const publicPathMap = JSON.stringify({
+ core: `${regularBundlePath}/core/`,
+ 'kbn-ui-shared-deps': `${regularBundlePath}/kbn-ui-shared-deps/`,
+ ...Object.fromEntries(kpPluginPublicPaths),
+ });
+
+ const bootstrap = new AppBootstrap({
+ templateData: {
+ themeTag,
+ jsDependencyPaths,
+ styleSheetPaths,
+ publicPathMap,
+ },
+ });
+
+ const body = await bootstrap.getJsFile();
+ const etag = await bootstrap.getJsFileHash();
+
+ return h
+ .response(body)
+ .header('cache-control', 'must-revalidate')
+ .header('content-type', 'application/javascript')
+ .etag(etag);
+ },
});
server.route({
@@ -191,19 +184,17 @@ export function uiRenderMixin(kbnServer, server, config) {
});
async function renderApp(h) {
- const app = { getId: () => 'core' };
const { http } = kbnServer.newPlatform.setup.core;
const { savedObjects } = kbnServer.newPlatform.start.core;
- const { rendering, legacy } = kbnServer.newPlatform.__internals;
+ const { rendering } = kbnServer.newPlatform.__internals;
const req = KibanaRequest.from(h.request);
const uiSettings = kbnServer.newPlatform.start.core.uiSettings.asScopedToClient(
savedObjects.getScopedClient(req)
);
- const vars = await legacy.getVars(app.getId(), h.request, {
+ const vars = {
apmConfig: getApmConfig(h.request.path),
- });
+ };
const content = await rendering.render(h.request, uiSettings, {
- app,
includeUserSettings: true,
vars,
});
diff --git a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/exports_number/index.js b/src/plugins/management/common/contants.ts
similarity index 94%
rename from src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/exports_number/index.js
rename to src/plugins/management/common/contants.ts
index f24fc54e38d9a..6ff585510dab1 100644
--- a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/exports_number/index.js
+++ b/src/plugins/management/common/contants.ts
@@ -17,4 +17,4 @@
* under the License.
*/
-export default 1;
+export const MANAGEMENT_APP_ID = 'management';
diff --git a/src/plugins/management/public/index.ts b/src/plugins/management/public/index.ts
index f6c23ccf0143f..f3e25b90b73c7 100644
--- a/src/plugins/management/public/index.ts
+++ b/src/plugins/management/public/index.ts
@@ -32,3 +32,5 @@ export {
ManagementStart,
DefinedSections,
} from './types';
+
+export { MANAGEMENT_APP_ID } from '../common/contants';
diff --git a/src/plugins/management/public/plugin.ts b/src/plugins/management/public/plugin.ts
index 808578c470ae1..122e73796753c 100644
--- a/src/plugins/management/public/plugin.ts
+++ b/src/plugins/management/public/plugin.ts
@@ -33,6 +33,7 @@ import {
AppNavLinkStatus,
} from '../../../core/public';
+import { MANAGEMENT_APP_ID } from '../common/contants';
import {
ManagementSectionsService,
getSectionsServiceStartPrivate,
@@ -72,7 +73,7 @@ export class ManagementPlugin implements Plugin {
executeTriggerActions: jest.fn(),
fork: jest.fn(),
getAction: jest.fn(),
+ hasAction: jest.fn(),
getTrigger: jest.fn(),
getTriggerActions: jest.fn((id: TriggerId) => []),
getTriggerCompatibleActions: jest.fn(),
diff --git a/src/plugins/ui_actions/public/service/ui_actions_service.ts b/src/plugins/ui_actions/public/service/ui_actions_service.ts
index 6028177964fb7..ec5f3afa19c94 100644
--- a/src/plugins/ui_actions/public/service/ui_actions_service.ts
+++ b/src/plugins/ui_actions/public/service/ui_actions_service.ts
@@ -99,6 +99,10 @@ export class UiActionsService {
this.actions.delete(actionId);
};
+ public readonly hasAction = (actionId: string): boolean => {
+ return this.actions.has(actionId);
+ };
+
public readonly attachAction = (triggerId: T, actionId: string): void => {
const trigger = this.triggers.get(triggerId);
diff --git a/x-pack/.gitignore b/x-pack/.gitignore
index d73b6f64f036a..99e33dbb88e92 100644
--- a/x-pack/.gitignore
+++ b/x-pack/.gitignore
@@ -6,13 +6,9 @@
/test/functional/apps/reporting/reports/session
/test/reporting/configs/failure_debug/
/plugins/reporting/.chromium/
-/legacy/plugins/reporting/.chromium/
-/legacy/plugins/reporting/.phantom/
/plugins/reporting/chromium/
/plugins/reporting/.phantom/
/.aws-config.json
/.env
/.kibana-plugin-helpers.dev.*
-!/legacy/plugins/infra/**/target
.cache
-!/legacy/plugins/security_solution/**/target
diff --git a/x-pack/.i18nrc.json b/x-pack/.i18nrc.json
index a700781438706..66ae478b86828 100644
--- a/x-pack/.i18nrc.json
+++ b/x-pack/.i18nrc.json
@@ -9,8 +9,8 @@
"xpack.alerts": "plugins/alerts",
"xpack.eventLog": "plugins/event_log",
"xpack.alertingBuiltins": "plugins/alerting_builtins",
- "xpack.apm": ["legacy/plugins/apm", "plugins/apm"],
- "xpack.beatsManagement": ["legacy/plugins/beats_management", "plugins/beats_management"],
+ "xpack.apm": "plugins/apm",
+ "xpack.beatsManagement": "plugins/beats_management",
"xpack.canvas": "plugins/canvas",
"xpack.cloud": "plugins/cloud",
"xpack.dashboard": "plugins/dashboard_enhanced",
@@ -35,15 +35,15 @@
"xpack.lens": "plugins/lens",
"xpack.licenseMgmt": "plugins/license_management",
"xpack.licensing": "plugins/licensing",
- "xpack.logstash": ["plugins/logstash", "legacy/plugins/logstash"],
+ "xpack.logstash": ["plugins/logstash"],
"xpack.main": "legacy/plugins/xpack_main",
- "xpack.maps": ["plugins/maps", "legacy/plugins/maps"],
- "xpack.ml": ["plugins/ml", "legacy/plugins/ml"],
+ "xpack.maps": ["plugins/maps"],
+ "xpack.ml": ["plugins/ml"],
"xpack.monitoring": ["plugins/monitoring"],
"xpack.remoteClusters": "plugins/remote_clusters",
"xpack.painlessLab": "plugins/painless_lab",
"xpack.reporting": ["plugins/reporting"],
- "xpack.rollupJobs": ["legacy/plugins/rollup", "plugins/rollup"],
+ "xpack.rollupJobs": ["plugins/rollup"],
"xpack.searchProfiler": "plugins/searchprofiler",
"xpack.security": "plugins/security",
"xpack.server": "legacy/server",
@@ -55,6 +55,7 @@
"xpack.triggersActionsUI": "plugins/triggers_actions_ui",
"xpack.upgradeAssistant": "plugins/upgrade_assistant",
"xpack.uptime": ["plugins/uptime"],
+ "xpack.urlDrilldown": "plugins/drilldowns/url_drilldown",
"xpack.watcher": "plugins/watcher",
"xpack.observability": "plugins/observability"
},
diff --git a/x-pack/dev-tools/jest/create_jest_config.js b/x-pack/dev-tools/jest/create_jest_config.js
index e6f160ce8c654..eec7b0246d026 100644
--- a/x-pack/dev-tools/jest/create_jest_config.js
+++ b/x-pack/dev-tools/jest/create_jest_config.js
@@ -8,17 +8,15 @@ export function createJestConfig({ kibanaDirectory, rootDir, xPackKibanaDirector
const fileMockPath = `${kibanaDirectory}/src/dev/jest/mocks/file_mock.js`;
return {
rootDir,
- roots: ['/plugins', '/legacy/plugins', '/legacy/server'],
+ roots: ['/plugins'],
moduleFileExtensions: ['js', 'mjs', 'json', 'ts', 'tsx', 'node'],
moduleNameMapper: {
'@elastic/eui$': `${kibanaDirectory}/node_modules/@elastic/eui/test-env`,
'@elastic/eui/lib/(.*)?': `${kibanaDirectory}/node_modules/@elastic/eui/test-env/$1`,
'^fixtures/(.*)': `${kibanaDirectory}/src/fixtures/$1`,
- 'uiExports/(.*)': fileMockPath,
'^src/core/(.*)': `${kibanaDirectory}/src/core/$1`,
'^src/legacy/(.*)': `${kibanaDirectory}/src/legacy/$1`,
'^src/plugins/(.*)': `${kibanaDirectory}/src/plugins/$1`,
- '^legacy/plugins/xpack_main/(.*);': `${xPackKibanaDirectory}/legacy/plugins/xpack_main/public/$1`,
'\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': fileMockPath,
'\\.module.(css|scss)$': `${kibanaDirectory}/src/dev/jest/mocks/css_module_mock.js`,
'\\.(css|less|scss)$': `${kibanaDirectory}/src/dev/jest/mocks/style_mock.js`,
@@ -30,8 +28,6 @@ export function createJestConfig({ kibanaDirectory, rootDir, xPackKibanaDirector
'^(!!)?file-loader!': fileMockPath,
},
collectCoverageFrom: [
- 'legacy/plugins/**/*.{js,mjs,jsx,ts,tsx}',
- 'legacy/server/**/*.{js,mjs,jsx,ts,tsx}',
'plugins/**/*.{js,mjs,jsx,ts,tsx}',
'!**/{__test__,__snapshots__,__examples__,integration_tests,tests}/**',
'!**/*.test.{js,mjs,ts,tsx}',
diff --git a/x-pack/index.js b/x-pack/index.js
deleted file mode 100644
index cb68004c26d65..0000000000000
--- a/x-pack/index.js
+++ /dev/null
@@ -1,11 +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 { xpackMain } from './legacy/plugins/xpack_main';
-
-module.exports = function (kibana) {
- return [xpackMain(kibana)];
-};
diff --git a/x-pack/legacy/common/__tests__/poller.js b/x-pack/legacy/common/__tests__/poller.js
deleted file mode 100644
index 24558502a8d02..0000000000000
--- a/x-pack/legacy/common/__tests__/poller.js
+++ /dev/null
@@ -1,240 +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 sinon from 'sinon';
-import { Poller } from '../poller';
-
-describe('Poller', () => {
- const pollFrequencyInMillis = 20;
- let functionToPoll;
- let successFunction;
- let errorFunction;
- let poller;
- let clock;
-
- beforeEach(() => {
- clock = sinon.useFakeTimers();
- });
-
- afterEach(() => {
- clock.restore();
- if (poller) {
- poller.stop();
- }
- });
-
- // Allowing the Poller to poll requires intimate knowledge of the inner workings of the Poller.
- // We have to ensure that the Promises internal to the `_poll` method are resolved to queue up
- // the next setTimeout before incrementing the clock. The order of this differs slightly when the
- // `trailing` is set, hence the different `allowPoll` and `allowDelayPoll` functions.
- const queueNextPoll = async () => {
- await Promise.resolve();
- await Promise.resolve();
- };
-
- const allowPoll = async (interval) => {
- await queueNextPoll();
- clock.tick(interval);
- };
-
- const allowDelayPoll = async (interval) => {
- clock.tick(interval);
- await queueNextPoll();
- };
-
- describe('start()', () => {
- beforeEach(() => {
- functionToPoll = sinon.spy(() => {
- return Promise.resolve(42);
- });
- successFunction = sinon.spy();
- errorFunction = sinon.spy();
- poller = new Poller({
- functionToPoll,
- successFunction,
- errorFunction,
- pollFrequencyInMillis,
- });
- });
-
- describe(`when trailing isn't set`, () => {
- it(`polls immediately`, () => {
- poller.start();
- expect(functionToPoll.callCount).to.be(1);
- });
- });
-
- describe(`when trailing is set to true`, () => {
- beforeEach(() => {
- poller = new Poller({
- functionToPoll,
- successFunction,
- errorFunction,
- pollFrequencyInMillis,
- trailing: true,
- });
- });
-
- it('waits for pollFrequencyInMillis before polling', async () => {
- poller.start();
- expect(functionToPoll.callCount).to.be(0);
- allowDelayPoll(pollFrequencyInMillis);
- expect(functionToPoll.callCount).to.be(1);
- });
- });
-
- it('polls the functionToPoll multiple times', async () => {
- poller.start();
- await allowPoll(pollFrequencyInMillis * 2);
- expect(functionToPoll.callCount).to.be.greaterThan(1);
- });
-
- describe('when the function to poll succeeds', () => {
- it('calls the successFunction multiple times', async () => {
- poller.start();
- await allowPoll(pollFrequencyInMillis * 2);
- expect(successFunction.callCount).to.be.greaterThan(1);
- expect(errorFunction.callCount).to.be(0);
- });
- });
-
- describe('when the function to poll fails', () => {
- beforeEach(() => {
- functionToPoll = sinon.spy(() => {
- return Promise.reject(42);
- });
- });
-
- describe('when the continuePollingOnError option has not been set', () => {
- beforeEach(() => {
- poller = new Poller({
- functionToPoll,
- successFunction,
- errorFunction,
- pollFrequencyInMillis,
- });
- });
-
- it('calls the errorFunction exactly once and polling is stopped', async () => {
- poller.start();
- await allowPoll(pollFrequencyInMillis * 4);
- expect(poller.isRunning()).to.be(false);
- expect(successFunction.callCount).to.be(0);
- expect(errorFunction.callCount).to.be(1);
- });
- });
-
- describe('when the continuePollingOnError option has been set to true', () => {
- beforeEach(() => {
- poller = new Poller({
- functionToPoll,
- successFunction,
- errorFunction,
- pollFrequencyInMillis,
- continuePollingOnError: true,
- });
- });
-
- it('calls the errorFunction multiple times', async () => {
- poller.start();
- await allowPoll(pollFrequencyInMillis);
- await allowPoll(pollFrequencyInMillis);
- expect(successFunction.callCount).to.be(0);
- expect(errorFunction.callCount).to.be.greaterThan(1);
- });
-
- describe('when pollFrequencyErrorMultiplier has been set', () => {
- beforeEach(() => {
- poller = new Poller({
- functionToPoll,
- successFunction,
- errorFunction,
- pollFrequencyInMillis,
- continuePollingOnError: true,
- pollFrequencyErrorMultiplier: 2,
- });
- });
-
- it('waits for the multiplier * the pollFrequency', async () => {
- poller.start();
- await queueNextPoll();
- expect(functionToPoll.callCount).to.be(1);
- await allowPoll(pollFrequencyInMillis);
- expect(functionToPoll.callCount).to.be(1);
- await allowPoll(pollFrequencyInMillis);
- expect(functionToPoll.callCount).to.be(2);
- });
- });
- });
- });
- });
-
- describe('isRunning()', () => {
- beforeEach(() => {
- functionToPoll = sinon.spy(() => {
- return Promise.resolve(42);
- });
- poller = new Poller({
- functionToPoll,
- });
- });
-
- it('returns true immediately after invoking start()', () => {
- poller.start();
- expect(poller.isRunning()).to.be(true);
- });
-
- it('returns false after invoking stop', () => {
- poller.start();
- poller.stop();
- expect(poller.isRunning()).to.be(false);
- });
- });
-
- describe('stop()', () => {
- describe(`when successFunction isn't set`, () => {
- beforeEach(() => {
- functionToPoll = sinon.spy(() => {
- return Promise.resolve(42);
- });
- poller = new Poller({
- functionToPoll,
- pollFrequencyInMillis,
- });
- });
-
- it(`doesn't poll again`, async () => {
- poller.start();
- expect(functionToPoll.callCount).to.be(1);
- poller.stop();
- await allowPoll(pollFrequencyInMillis);
- expect(functionToPoll.callCount).to.be(1);
- });
- });
-
- describe(`when successFunction is a Promise`, () => {
- beforeEach(() => {
- functionToPoll = sinon.spy(() => {
- return Promise.resolve(42);
- });
- poller = new Poller({
- functionToPoll,
- successFunction: Promise.resolve(),
- pollFrequencyInMillis,
- });
- });
-
- it(`doesn't poll again when successFunction is a Promise`, async () => {
- poller.start();
- expect(functionToPoll.callCount).to.be(1);
- poller.stop();
- await allowPoll(pollFrequencyInMillis);
- expect(functionToPoll.callCount).to.be(1);
- });
- });
- });
-});
diff --git a/x-pack/legacy/common/constants/index.ts b/x-pack/legacy/common/constants/index.ts
deleted file mode 100644
index 4db0f994fd47e..0000000000000
--- a/x-pack/legacy/common/constants/index.ts
+++ /dev/null
@@ -1,23 +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 {
- LICENSE_STATUS_UNAVAILABLE,
- LICENSE_STATUS_INVALID,
- LICENSE_STATUS_EXPIRED,
- LICENSE_STATUS_VALID,
-} from './license_status';
-
-export {
- LICENSE_TYPE_BASIC,
- LICENSE_TYPE_STANDARD,
- LICENSE_TYPE_GOLD,
- LICENSE_TYPE_PLATINUM,
- LICENSE_TYPE_ENTERPRISE,
- LICENSE_TYPE_TRIAL,
- RANKED_LICENSE_TYPES,
- LicenseType,
-} from './license_types';
diff --git a/x-pack/legacy/common/constants/license_status.ts b/x-pack/legacy/common/constants/license_status.ts
deleted file mode 100644
index 5fdfa08d73959..0000000000000
--- a/x-pack/legacy/common/constants/license_status.ts
+++ /dev/null
@@ -1,10 +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 const LICENSE_STATUS_UNAVAILABLE = 'UNAVAILABLE';
-export const LICENSE_STATUS_INVALID = 'INVALID';
-export const LICENSE_STATUS_EXPIRED = 'EXPIRED';
-export const LICENSE_STATUS_VALID = 'VALID';
diff --git a/x-pack/legacy/common/constants/license_types.ts b/x-pack/legacy/common/constants/license_types.ts
deleted file mode 100644
index 8c329df2f85f7..0000000000000
--- a/x-pack/legacy/common/constants/license_types.ts
+++ /dev/null
@@ -1,31 +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 const LICENSE_TYPE_BASIC = 'basic';
-export const LICENSE_TYPE_STANDARD = 'standard';
-export const LICENSE_TYPE_GOLD = 'gold';
-export const LICENSE_TYPE_PLATINUM = 'platinum';
-export const LICENSE_TYPE_ENTERPRISE = 'enterprise';
-export const LICENSE_TYPE_TRIAL = 'trial';
-
-export type LicenseType =
- | typeof LICENSE_TYPE_BASIC
- | typeof LICENSE_TYPE_STANDARD
- | typeof LICENSE_TYPE_GOLD
- | typeof LICENSE_TYPE_PLATINUM
- | typeof LICENSE_TYPE_ENTERPRISE
- | typeof LICENSE_TYPE_TRIAL;
-
-// These are ordered from least featureful to most featureful, so we can assume that someone holding
-// a license at a particular index cannot access any features unlocked by the licenses that follow it.
-export const RANKED_LICENSE_TYPES = [
- LICENSE_TYPE_BASIC,
- LICENSE_TYPE_STANDARD,
- LICENSE_TYPE_GOLD,
- LICENSE_TYPE_PLATINUM,
- LICENSE_TYPE_ENTERPRISE,
- LICENSE_TYPE_TRIAL,
-];
diff --git a/x-pack/legacy/common/eui_draggable/index.d.ts b/x-pack/legacy/common/eui_draggable/index.d.ts
deleted file mode 100644
index 322966b3c982e..0000000000000
--- a/x-pack/legacy/common/eui_draggable/index.d.ts
+++ /dev/null
@@ -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 React from 'react';
-import { EuiDraggable, EuiDragDropContext } from '@elastic/eui';
-
-type PropsOf = T extends React.ComponentType ? ComponentProps : never;
-type FirstArgumentOf = Func extends (arg1: infer FirstArgument, ...rest: any[]) => any
- ? FirstArgument
- : never;
-export type DragHandleProps = FirstArgumentOf<
- Exclude['children'], React.ReactElement>
->['dragHandleProps'];
-export type DropResult = FirstArgumentOf['onDragEnd']>;
diff --git a/x-pack/legacy/common/eui_styled_components/index.ts b/x-pack/legacy/common/eui_styled_components/index.ts
deleted file mode 100644
index 9b3ed903627b4..0000000000000
--- a/x-pack/legacy/common/eui_styled_components/index.ts
+++ /dev/null
@@ -1,20 +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 {
- css,
- euiStyled,
- EuiTheme,
- EuiThemeProvider,
- createGlobalStyle,
- keyframes,
- withTheme,
-} from './eui_styled_components';
-
-export { css, euiStyled, EuiTheme, EuiThemeProvider, createGlobalStyle, keyframes, withTheme };
-// In order to to mimic the styled-components module we need to ignore the following
-// eslint-disable-next-line import/no-default-export
-export default euiStyled;
diff --git a/x-pack/legacy/common/poller.js b/x-pack/legacy/common/poller.js
deleted file mode 100644
index 09824ce9d6d23..0000000000000
--- a/x-pack/legacy/common/poller.js
+++ /dev/null
@@ -1,79 +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 _ from 'lodash';
-
-export class Poller {
- constructor(options) {
- this.functionToPoll = options.functionToPoll; // Must return a Promise
- this.successFunction = options.successFunction || _.noop;
- this.errorFunction = options.errorFunction || _.noop;
- this.pollFrequencyInMillis = options.pollFrequencyInMillis;
- this.trailing = options.trailing || false;
- this.continuePollingOnError = options.continuePollingOnError || false;
- this.pollFrequencyErrorMultiplier = options.pollFrequencyErrorMultiplier || 1;
- this._timeoutId = null;
- this._isRunning = false;
- }
-
- getPollFrequency() {
- return this.pollFrequencyInMillis;
- }
-
- _poll() {
- return this.functionToPoll()
- .then(this.successFunction)
- .then(() => {
- if (!this._isRunning) {
- return;
- }
-
- this._timeoutId = setTimeout(this._poll.bind(this), this.pollFrequencyInMillis);
- })
- .catch((e) => {
- this.errorFunction(e);
- if (!this._isRunning) {
- return;
- }
-
- if (this.continuePollingOnError) {
- this._timeoutId = setTimeout(
- this._poll.bind(this),
- this.pollFrequencyInMillis * this.pollFrequencyErrorMultiplier
- );
- } else {
- this.stop();
- }
- });
- }
-
- start() {
- if (this._isRunning) {
- return;
- }
-
- this._isRunning = true;
- if (this.trailing) {
- this._timeoutId = setTimeout(this._poll.bind(this), this.pollFrequencyInMillis);
- } else {
- this._poll();
- }
- }
-
- stop() {
- if (!this._isRunning) {
- return;
- }
-
- this._isRunning = false;
- clearTimeout(this._timeoutId);
- this._timeoutId = null;
- }
-
- isRunning() {
- return this._isRunning;
- }
-}
diff --git a/x-pack/legacy/plugins/xpack_main/index.js b/x-pack/legacy/plugins/xpack_main/index.js
deleted file mode 100644
index a3bd66e744fda..0000000000000
--- a/x-pack/legacy/plugins/xpack_main/index.js
+++ /dev/null
@@ -1,31 +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 { resolve } from 'path';
-import { setupXPackMain } from './server/lib/setup_xpack_main';
-import { xpackInfoRoute } from './server/routes/api/v1';
-
-export const xpackMain = (kibana) => {
- return new kibana.Plugin({
- id: 'xpack_main',
- configPrefix: 'xpack.xpack_main',
- publicDir: resolve(__dirname, 'public'),
- require: [],
-
- config(Joi) {
- return Joi.object({
- enabled: Joi.boolean().default(true),
- }).default();
- },
-
- init(server) {
- setupXPackMain(server);
-
- // register routes
- xpackInfoRoute(server);
- },
- });
-};
diff --git a/x-pack/legacy/plugins/xpack_main/server/lib/__tests__/setup_xpack_main.js b/x-pack/legacy/plugins/xpack_main/server/lib/__tests__/setup_xpack_main.js
deleted file mode 100644
index f49f44bed97a7..0000000000000
--- a/x-pack/legacy/plugins/xpack_main/server/lib/__tests__/setup_xpack_main.js
+++ /dev/null
@@ -1,68 +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 { BehaviorSubject } from 'rxjs';
-import sinon from 'sinon';
-import { XPackInfo } from '../xpack_info';
-import { setupXPackMain } from '../setup_xpack_main';
-
-describe('setupXPackMain()', () => {
- const sandbox = sinon.createSandbox();
-
- let mockServer;
- let mockStatusObservable;
- let mockElasticsearchPlugin;
-
- beforeEach(() => {
- sandbox.useFakeTimers();
-
- mockElasticsearchPlugin = {
- getCluster: sinon.stub(),
- };
-
- mockStatusObservable = sinon.stub({ subscribe() {} });
-
- mockServer = sinon.stub({
- plugins: {
- elasticsearch: mockElasticsearchPlugin,
- },
- newPlatform: {
- setup: {
- core: {
- status: {
- core$: {
- pipe() {
- return mockStatusObservable;
- },
- },
- },
- },
- plugins: { features: {}, licensing: { license$: new BehaviorSubject() } },
- },
- },
- events: { on() {} },
- log() {},
- config() {},
- expose() {},
- ext() {},
- });
-
- // Make sure plugins doesn't consume config
- const configGetStub = sinon
- .stub()
- .throws(new Error('`config.get` is called with unexpected key.'));
- mockServer.config.returns({ get: configGetStub });
- });
-
- afterEach(() => sandbox.restore());
-
- it('all extension hooks should be properly initialized.', () => {
- setupXPackMain(mockServer);
-
- sinon.assert.calledWithExactly(mockServer.expose, 'info', sinon.match.instanceOf(XPackInfo));
- sinon.assert.calledWithExactly(mockStatusObservable.subscribe, sinon.match.func);
- });
-});
diff --git a/x-pack/legacy/plugins/xpack_main/server/lib/__tests__/xpack_info.js b/x-pack/legacy/plugins/xpack_main/server/lib/__tests__/xpack_info.js
deleted file mode 100644
index 81fb822882817..0000000000000
--- a/x-pack/legacy/plugins/xpack_main/server/lib/__tests__/xpack_info.js
+++ /dev/null
@@ -1,398 +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 { createHash } from 'crypto';
-import { BehaviorSubject } from 'rxjs';
-import expect from '@kbn/expect';
-import sinon from 'sinon';
-import { XPackInfo } from '../xpack_info';
-import { licensingMock } from '../../../../../../plugins/licensing/server/mocks';
-
-function createLicense(license = {}, features = {}) {
- return licensingMock.createLicense({
- license: {
- uid: 'custom-uid',
- type: 'gold',
- mode: 'gold',
- status: 'active',
- expiryDateInMillis: 1286575200000,
- ...license,
- },
- features: {
- security: {
- description: 'Security for the Elastic Stack',
- isAvailable: true,
- isEnabled: true,
- },
- watcher: {
- description: 'Alerting, Notification and Automation for the Elastic Stack',
- isAvailable: true,
- isEnabled: false,
- },
- ...features,
- },
- });
-}
-
-function getSignature(object) {
- return createHash('md5').update(JSON.stringify(object)).digest('hex');
-}
-
-describe('XPackInfo', () => {
- let mockServer;
- let mockElasticsearchPlugin;
-
- beforeEach(() => {
- mockServer = sinon.stub({
- plugins: { elasticsearch: mockElasticsearchPlugin },
- events: { on() {} },
- newPlatform: {
- setup: {
- plugins: {
- licensing: {},
- },
- },
- },
- });
- });
-
- describe('refreshNow()', () => {
- it('delegates to the new platform licensing plugin', async () => {
- const refresh = sinon.spy();
-
- const xPackInfo = new XPackInfo(mockServer, {
- licensing: {
- license$: new BehaviorSubject(createLicense()),
- refresh: refresh,
- },
- });
-
- await xPackInfo.refreshNow();
-
- sinon.assert.calledOnce(refresh);
- });
- });
-
- describe('license', () => {
- let xPackInfo;
- let license$;
- beforeEach(async () => {
- license$ = new BehaviorSubject(createLicense());
- xPackInfo = new XPackInfo(mockServer, {
- licensing: {
- license$,
- refresh: () => null,
- },
- });
- });
-
- it('getUid() shows license uid returned from the license$.', async () => {
- expect(xPackInfo.license.getUid()).to.be('custom-uid');
-
- license$.next(createLicense({ uid: 'new-custom-uid' }));
-
- expect(xPackInfo.license.getUid()).to.be('new-custom-uid');
-
- license$.next(createLicense({ uid: undefined, error: 'error-reason' }));
-
- expect(xPackInfo.license.getUid()).to.be(undefined);
- });
-
- it('isActive() is based on the status returned from the backend.', async () => {
- expect(xPackInfo.license.isActive()).to.be(true);
-
- license$.next(createLicense({ status: 'expired' }));
- expect(xPackInfo.license.isActive()).to.be(false);
-
- license$.next(createLicense({ status: 'some other value' }));
- expect(xPackInfo.license.isActive()).to.be(false);
-
- license$.next(createLicense({ status: 'active' }));
- expect(xPackInfo.license.isActive()).to.be(true);
-
- license$.next(createLicense({ status: undefined, error: 'error-reason' }));
- expect(xPackInfo.license.isActive()).to.be(false);
- });
-
- it('getExpiryDateInMillis() is based on the value returned from the backend.', async () => {
- expect(xPackInfo.license.getExpiryDateInMillis()).to.be(1286575200000);
-
- license$.next(createLicense({ expiryDateInMillis: 10203040 }));
- expect(xPackInfo.license.getExpiryDateInMillis()).to.be(10203040);
-
- license$.next(createLicense({ expiryDateInMillis: undefined, error: 'error-reason' }));
- expect(xPackInfo.license.getExpiryDateInMillis()).to.be(undefined);
- });
-
- it('getType() is based on the value returned from the backend.', async () => {
- expect(xPackInfo.license.getType()).to.be('gold');
-
- license$.next(createLicense({ type: 'basic' }));
- expect(xPackInfo.license.getType()).to.be('basic');
-
- license$.next(createLicense({ type: undefined, error: 'error-reason' }));
- expect(xPackInfo.license.getType()).to.be(undefined);
- });
-
- it('isOneOf() correctly determines if current license is presented in the specified list.', async () => {
- expect(xPackInfo.license.isOneOf('gold')).to.be(true);
- expect(xPackInfo.license.isOneOf(['gold', 'basic'])).to.be(true);
- expect(xPackInfo.license.isOneOf(['platinum', 'basic'])).to.be(false);
- expect(xPackInfo.license.isOneOf('standard')).to.be(false);
-
- license$.next(createLicense({ mode: 'basic' }));
-
- expect(xPackInfo.license.isOneOf('basic')).to.be(true);
- expect(xPackInfo.license.isOneOf(['gold', 'basic'])).to.be(true);
- expect(xPackInfo.license.isOneOf(['platinum', 'gold'])).to.be(false);
- expect(xPackInfo.license.isOneOf('standard')).to.be(false);
- });
- });
-
- describe('feature', () => {
- let xPackInfo;
- let license$;
- beforeEach(async () => {
- license$ = new BehaviorSubject(
- createLicense(
- {},
- {
- feature: {
- isAvailable: false,
- isEnabled: true,
- },
- }
- )
- );
- xPackInfo = new XPackInfo(mockServer, {
- licensing: {
- license$,
- refresh: () => null,
- },
- });
- });
-
- it('isAvailable() checks whether particular feature is available.', async () => {
- const availableFeatureOne = xPackInfo.feature('security');
- const availableFeatureTwo = xPackInfo.feature('watcher');
- const unavailableFeatureOne = xPackInfo.feature('feature');
- const unavailableFeatureTwo = xPackInfo.feature('non-existing-feature');
-
- expect(availableFeatureOne.isAvailable()).to.be(true);
- expect(availableFeatureTwo.isAvailable()).to.be(true);
- expect(unavailableFeatureOne.isAvailable()).to.be(false);
- expect(unavailableFeatureTwo.isAvailable()).to.be(false);
- });
-
- it('isEnabled() checks whether particular feature is enabled.', async () => {
- const enabledFeatureOne = xPackInfo.feature('security');
- const enabledFeatureTwo = xPackInfo.feature('feature');
- const disabledFeatureOne = xPackInfo.feature('watcher');
- const disabledFeatureTwo = xPackInfo.feature('non-existing-feature');
-
- expect(enabledFeatureOne.isEnabled()).to.be(true);
- expect(enabledFeatureTwo.isEnabled()).to.be(true);
- expect(disabledFeatureOne.isEnabled()).to.be(false);
- expect(disabledFeatureTwo.isEnabled()).to.be(false);
- });
-
- it('registerLicenseCheckResultsGenerator() allows to fill in XPack Info feature specific info.', async () => {
- const securityFeature = xPackInfo.feature('security');
- const watcherFeature = xPackInfo.feature('watcher');
-
- expect(xPackInfo.toJSON().features.security).to.be(undefined);
- expect(xPackInfo.toJSON().features.watcher).to.be(undefined);
-
- securityFeature.registerLicenseCheckResultsGenerator((info) => {
- return {
- isXPackInfo: info instanceof XPackInfo,
- license: info.license.getType(),
- someCustomValue: 100500,
- };
- });
-
- expect(xPackInfo.toJSON().features.security).to.eql({
- isXPackInfo: true,
- license: 'gold',
- someCustomValue: 100500,
- });
- expect(xPackInfo.toJSON().features.watcher).to.be(undefined);
-
- watcherFeature.registerLicenseCheckResultsGenerator((info) => {
- return {
- isXPackInfo: info instanceof XPackInfo,
- license: info.license.getType(),
- someAnotherCustomValue: 500100,
- };
- });
-
- expect(xPackInfo.toJSON().features.security).to.eql({
- isXPackInfo: true,
- license: 'gold',
- someCustomValue: 100500,
- });
- expect(xPackInfo.toJSON().features.watcher).to.eql({
- isXPackInfo: true,
- license: 'gold',
- someAnotherCustomValue: 500100,
- });
-
- license$.next(createLicense({ type: 'platinum' }));
-
- expect(xPackInfo.toJSON().features.security).to.eql({
- isXPackInfo: true,
- license: 'platinum',
- someCustomValue: 100500,
- });
- expect(xPackInfo.toJSON().features.watcher).to.eql({
- isXPackInfo: true,
- license: 'platinum',
- someAnotherCustomValue: 500100,
- });
- });
-
- it('getLicenseCheckResults() correctly returns feature specific info.', async () => {
- const securityFeature = xPackInfo.feature('security');
- const watcherFeature = xPackInfo.feature('watcher');
-
- expect(securityFeature.getLicenseCheckResults()).to.be(undefined);
- expect(watcherFeature.getLicenseCheckResults()).to.be(undefined);
-
- securityFeature.registerLicenseCheckResultsGenerator((info) => {
- return {
- isXPackInfo: info instanceof XPackInfo,
- license: info.license.getType(),
- someCustomValue: 100500,
- };
- });
-
- expect(securityFeature.getLicenseCheckResults()).to.eql({
- isXPackInfo: true,
- license: 'gold',
- someCustomValue: 100500,
- });
- expect(watcherFeature.getLicenseCheckResults()).to.be(undefined);
-
- watcherFeature.registerLicenseCheckResultsGenerator((info) => {
- return {
- isXPackInfo: info instanceof XPackInfo,
- license: info.license.getType(),
- someAnotherCustomValue: 500100,
- };
- });
-
- expect(securityFeature.getLicenseCheckResults()).to.eql({
- isXPackInfo: true,
- license: 'gold',
- someCustomValue: 100500,
- });
- expect(watcherFeature.getLicenseCheckResults()).to.eql({
- isXPackInfo: true,
- license: 'gold',
- someAnotherCustomValue: 500100,
- });
-
- license$.next(createLicense({ type: 'platinum' }));
-
- expect(securityFeature.getLicenseCheckResults()).to.eql({
- isXPackInfo: true,
- license: 'platinum',
- someCustomValue: 100500,
- });
- expect(watcherFeature.getLicenseCheckResults()).to.eql({
- isXPackInfo: true,
- license: 'platinum',
- someAnotherCustomValue: 500100,
- });
- });
- });
-
- it('onLicenseInfoChange() allows to subscribe to license update', async () => {
- const license$ = new BehaviorSubject(createLicense());
-
- const xPackInfo = new XPackInfo(mockServer, {
- licensing: {
- license$,
- refresh: () => null,
- },
- });
-
- const watcherFeature = xPackInfo.feature('watcher');
- watcherFeature.registerLicenseCheckResultsGenerator((info) => ({
- type: info.license.getType(),
- }));
-
- const statuses = [];
- xPackInfo.onLicenseInfoChange(() => statuses.push(watcherFeature.getLicenseCheckResults()));
-
- license$.next(createLicense({ type: 'basic' }));
- expect(statuses).to.eql([{ type: 'basic' }]);
-
- license$.next(createLicense({ type: 'trial' }));
- expect(statuses).to.eql([{ type: 'basic' }, { type: 'trial' }]);
- });
-
- it('refreshNow() leads to onLicenseInfoChange()', async () => {
- const license$ = new BehaviorSubject(createLicense());
-
- const xPackInfo = new XPackInfo(mockServer, {
- licensing: {
- license$,
- refresh: () => license$.next({ type: 'basic' }),
- },
- });
-
- const watcherFeature = xPackInfo.feature('watcher');
-
- watcherFeature.registerLicenseCheckResultsGenerator((info) => ({
- type: info.license.getType(),
- }));
-
- const statuses = [];
- xPackInfo.onLicenseInfoChange(() => statuses.push(watcherFeature.getLicenseCheckResults()));
-
- await xPackInfo.refreshNow();
- expect(statuses).to.eql([{ type: 'basic' }]);
- });
-
- it('getSignature() returns correct signature.', async () => {
- const license$ = new BehaviorSubject(createLicense());
- const xPackInfo = new XPackInfo(mockServer, {
- licensing: {
- license$,
- refresh: () => null,
- },
- });
-
- expect(xPackInfo.getSignature()).to.be(
- getSignature({
- license: {
- type: 'gold',
- isActive: true,
- expiryDateInMillis: 1286575200000,
- },
- features: {},
- })
- );
-
- license$.next(createLicense({ type: 'platinum', expiryDateInMillis: 20304050 }));
-
- const expectedSignature = getSignature({
- license: {
- type: 'platinum',
- isActive: true,
- expiryDateInMillis: 20304050,
- },
- features: {},
- });
- expect(xPackInfo.getSignature()).to.be(expectedSignature);
-
- // Should stay the same after refresh if nothing changed.
- license$.next(createLicense({ type: 'platinum', expiryDateInMillis: 20304050 }));
-
- expect(xPackInfo.getSignature()).to.be(expectedSignature);
- });
-});
diff --git a/x-pack/legacy/plugins/xpack_main/server/lib/setup_xpack_main.js b/x-pack/legacy/plugins/xpack_main/server/lib/setup_xpack_main.js
deleted file mode 100644
index fd4e3c86d0ca7..0000000000000
--- a/x-pack/legacy/plugins/xpack_main/server/lib/setup_xpack_main.js
+++ /dev/null
@@ -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 { pairwise } from 'rxjs/operators';
-import { XPackInfo } from './xpack_info';
-
-/**
- * Setup the X-Pack Main plugin. This is fired every time that the Elasticsearch plugin becomes Green.
- *
- * This will ensure that X-Pack is installed on the Elasticsearch cluster, as well as trigger the initial
- * polling for _xpack/info.
- *
- * @param server {Object} The Kibana server object.
- */
-export function setupXPackMain(server) {
- const info = new XPackInfo(server, { licensing: server.newPlatform.setup.plugins.licensing });
-
- server.expose('info', info);
-
- // trigger an xpack info refresh whenever the elasticsearch plugin status changes
- server.newPlatform.setup.core.status.core$
- .pipe(pairwise())
- .subscribe(async ([coreLast, coreCurrent]) => {
- if (coreLast.elasticsearch.level !== coreCurrent.elasticsearch.level) {
- await info.refreshNow();
- }
- });
-
- return info;
-}
diff --git a/x-pack/legacy/plugins/xpack_main/server/lib/xpack_info.ts b/x-pack/legacy/plugins/xpack_main/server/lib/xpack_info.ts
deleted file mode 100644
index aa66532a2897d..0000000000000
--- a/x-pack/legacy/plugins/xpack_main/server/lib/xpack_info.ts
+++ /dev/null
@@ -1,240 +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 { createHash } from 'crypto';
-import { Legacy } from 'kibana';
-
-import { XPackInfoLicense } from './xpack_info_license';
-
-import { LicensingPluginSetup, ILicense } from '../../../../../plugins/licensing/server';
-
-export interface XPackInfoOptions {
- clusterSource?: string;
- pollFrequencyInMillis: number;
-}
-
-type LicenseGeneratorCheck = (xpackInfo: XPackInfo) => any;
-
-export interface XPackFeature {
- isAvailable(): boolean;
- isEnabled(): boolean;
- registerLicenseCheckResultsGenerator(generator: LicenseGeneratorCheck): void;
- getLicenseCheckResults(): any;
-}
-
-interface Deps {
- licensing: LicensingPluginSetup;
-}
-
-/**
- * A helper that provides a convenient way to access XPack Info returned by Elasticsearch.
- */
-export class XPackInfo {
- /**
- * XPack License object.
- * @type {XPackInfoLicense}
- * @private
- */
- _license: XPackInfoLicense;
-
- /**
- * Feature name <-> feature license check generator function mapping.
- * @type {Map}
- * @private
- */
- _featureLicenseCheckResultsGenerators = new Map();
-
- /**
- * Set of listener functions that will be called whenever the license
- * info changes
- * @type {Set}
- */
- _licenseInfoChangedListeners = new Set<() => void>();
-
- /**
- * Cache that may contain last xpack info API response or error, json representation
- * of xpack info and xpack info signature.
- * @type {{response: Object|undefined, error: Object|undefined, json: Object|undefined, signature: string|undefined}}
- * @private
- */
- private _cache: {
- license?: ILicense;
- error?: string;
- json?: Record;
- signature?: string;
- };
-
- /**
- * XPack License instance.
- * @returns {XPackInfoLicense}
- */
- public get license() {
- return this._license;
- }
-
- private readonly licensingPlugin: LicensingPluginSetup;
-
- /**
- * Constructs XPack info object.
- * @param {Hapi.Server} server HapiJS server instance.
- */
- constructor(server: Legacy.Server, deps: Deps) {
- if (!deps.licensing) {
- throw new Error('XPackInfo requires enabled Licensing plugin');
- }
- this.licensingPlugin = deps.licensing;
-
- this._cache = {};
-
- this.licensingPlugin.license$.subscribe((license: ILicense) => {
- if (license.isActive) {
- this._cache = {
- license,
- error: undefined,
- };
- } else {
- this._cache = {
- license,
- error: license.error,
- };
- }
-
- this._licenseInfoChangedListeners.forEach((fn) => fn());
- });
-
- this._license = new XPackInfoLicense(() => this._cache.license);
- }
-
- /**
- * Checks whether XPack info is available.
- * @returns {boolean}
- */
- isAvailable() {
- return Boolean(this._cache.license?.isAvailable);
- }
-
- /**
- * Checks whether ES was available
- * @returns {boolean}
- */
- isXpackUnavailable() {
- return (
- this._cache.error &&
- this._cache.error === 'X-Pack plugin is not installed on the Elasticsearch cluster.'
- );
- }
-
- /**
- * If present, describes the reason why XPack info is not available.
- * @returns {Error|string}
- */
- unavailableReason() {
- return this._cache.license?.getUnavailableReason();
- }
-
- onLicenseInfoChange(handler: () => void) {
- this._licenseInfoChangedListeners.add(handler);
- }
-
- /**
- * Queries server to get the updated XPack info.
- * @returns {Promise.}
- */
- async refreshNow() {
- await this.licensingPlugin.refresh();
- return this;
- }
-
- /**
- * Returns a wrapper around XPack info that gives an access to the properties of
- * the specific feature.
- * @param {string} name Name of the feature to get a wrapper for.
- * @returns {Object}
- */
- feature(name: string): XPackFeature {
- return {
- /**
- * Checks whether feature is available (permitted by the current license).
- * @returns {boolean}
- */
- isAvailable: () => {
- return Boolean(this._cache.license?.getFeature(name).isAvailable);
- },
-
- /**
- * Checks whether feature is enabled (not disabled by the configuration specifically).
- * @returns {boolean}
- */
- isEnabled: () => {
- return Boolean(this._cache.license?.getFeature(name).isEnabled);
- },
-
- /**
- * Registers a `generator` function that will be called with XPackInfo instance as
- * argument whenever XPack info changes. Whatever `generator` returns will be stored
- * in XPackInfo JSON representation and can be accessed with `getLicenseCheckResults`.
- * @param {Function} generator Function to call whenever XPackInfo changes.
- */
- registerLicenseCheckResultsGenerator: (generator: LicenseGeneratorCheck) => {
- this._featureLicenseCheckResultsGenerators.set(name, generator);
-
- // Since JSON representation and signature are cached we should invalidate them to
- // include results from newly registered generator when they are requested.
- this._cache.json = undefined;
- this._cache.signature = undefined;
- },
-
- /**
- * Returns license check results that were previously produced by the `generator` function.
- * @returns {Object}
- */
- getLicenseCheckResults: () => this.toJSON().features[name],
- };
- }
-
- /**
- * Extracts string md5 hash from the stringified version of license JSON representation.
- * @returns {string}
- */
- getSignature() {
- if (this._cache.signature) {
- return this._cache.signature;
- }
-
- this._cache.signature = createHash('md5').update(JSON.stringify(this.toJSON())).digest('hex');
-
- return this._cache.signature;
- }
-
- /**
- * Returns JSON representation of the license object that is suitable for serialization.
- * @returns {Object}
- */
- toJSON() {
- if (this._cache.json) {
- return this._cache.json;
- }
-
- this._cache.json = {
- license: {
- type: this.license.getType(),
- isActive: this.license.isActive(),
- expiryDateInMillis: this.license.getExpiryDateInMillis(),
- },
- features: {},
- };
-
- // Set response elements specific to each feature. To do this,
- // call the license check results generator for each feature, passing them
- // the xpack info object
- for (const [feature, licenseChecker] of this._featureLicenseCheckResultsGenerators) {
- // return value expected to be a dictionary object.
- this._cache.json.features[feature] = licenseChecker(this);
- }
-
- return this._cache.json;
- }
-}
diff --git a/x-pack/legacy/plugins/xpack_main/server/lib/xpack_info_license.test.js b/x-pack/legacy/plugins/xpack_main/server/lib/xpack_info_license.test.js
deleted file mode 100644
index ccb5742216ca7..0000000000000
--- a/x-pack/legacy/plugins/xpack_main/server/lib/xpack_info_license.test.js
+++ /dev/null
@@ -1,207 +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 { licensingMock } from '../../../../../plugins/licensing/server/mocks';
-import { XPackInfoLicense } from './xpack_info_license';
-
-function getXPackInfoLicense(getRawLicense) {
- return new XPackInfoLicense(getRawLicense);
-}
-
-describe('XPackInfoLicense', () => {
- const xpackInfoLicenseUndefined = getXPackInfoLicense(() => {});
- let xpackInfoLicense;
- let getRawLicense;
-
- beforeEach(() => {
- getRawLicense = jest.fn();
- xpackInfoLicense = getXPackInfoLicense(getRawLicense);
- });
-
- test('getUid returns uid field', () => {
- const uid = 'abc123';
-
- getRawLicense.mockReturnValue(licensingMock.createLicense({ license: { uid } }));
-
- expect(xpackInfoLicense.getUid()).toBe(uid);
- expect(getRawLicense).toHaveBeenCalledTimes(1);
-
- expect(xpackInfoLicenseUndefined.getUid()).toBe(undefined);
- });
-
- test('isActive returns true if status is active', () => {
- getRawLicense.mockReturnValue(licensingMock.createLicense({ license: { status: 'active' } }));
-
- expect(xpackInfoLicense.isActive()).toBe(true);
- expect(getRawLicense).toHaveBeenCalledTimes(1);
- });
-
- test('isActive returns false if status is not active', () => {
- getRawLicense.mockReturnValue(licensingMock.createLicense({ license: { status: 'aCtIvE' } })); // needs to match exactly
-
- expect(xpackInfoLicense.isActive()).toBe(false);
- expect(getRawLicense).toHaveBeenCalledTimes(1);
-
- expect(xpackInfoLicenseUndefined.isActive()).toBe(false);
- });
-
- test('getExpiryDateInMillis returns expiry_date_in_millis', () => {
- getRawLicense.mockReturnValue(
- licensingMock.createLicense({ license: { expiryDateInMillis: 123 } })
- );
-
- expect(xpackInfoLicense.getExpiryDateInMillis()).toBe(123);
- expect(getRawLicense).toHaveBeenCalledTimes(1);
-
- expect(xpackInfoLicenseUndefined.getExpiryDateInMillis()).toBe(undefined);
- });
-
- test('isOneOf returns true of the mode includes one of the types', () => {
- getRawLicense.mockReturnValue(licensingMock.createLicense({ license: { mode: 'platinum' } }));
-
- expect(xpackInfoLicense.isOneOf('platinum')).toBe(true);
- expect(getRawLicense).toHaveBeenCalledTimes(1);
-
- expect(xpackInfoLicense.isOneOf(['platinum'])).toBe(true);
- expect(getRawLicense).toHaveBeenCalledTimes(2);
- expect(xpackInfoLicense.isOneOf(['gold', 'platinum'])).toBe(true);
- expect(getRawLicense).toHaveBeenCalledTimes(3);
- expect(xpackInfoLicense.isOneOf(['platinum', 'gold'])).toBe(true);
- expect(getRawLicense).toHaveBeenCalledTimes(4);
- expect(xpackInfoLicense.isOneOf(['basic', 'gold'])).toBe(false);
- expect(getRawLicense).toHaveBeenCalledTimes(5);
- expect(xpackInfoLicense.isOneOf(['basic'])).toBe(false);
- expect(getRawLicense).toHaveBeenCalledTimes(6);
-
- expect(xpackInfoLicenseUndefined.isOneOf(['platinum', 'gold'])).toBe(false);
- });
-
- test('getType returns the type', () => {
- getRawLicense.mockReturnValue(licensingMock.createLicense({ license: { type: 'basic' } }));
-
- expect(xpackInfoLicense.getType()).toBe('basic');
- expect(getRawLicense).toHaveBeenCalledTimes(1);
-
- getRawLicense.mockReturnValue(licensingMock.createLicense({ license: { type: 'gold' } }));
-
- expect(xpackInfoLicense.getType()).toBe('gold');
- expect(getRawLicense).toHaveBeenCalledTimes(2);
-
- expect(xpackInfoLicenseUndefined.getType()).toBe(undefined);
- });
-
- test('getMode returns the mode', () => {
- getRawLicense.mockReturnValue(licensingMock.createLicense({ license: { mode: 'basic' } }));
-
- expect(xpackInfoLicense.getMode()).toBe('basic');
- expect(getRawLicense).toHaveBeenCalledTimes(1);
-
- getRawLicense.mockReturnValue(licensingMock.createLicense({ license: { mode: 'gold' } }));
-
- expect(xpackInfoLicense.getMode()).toBe('gold');
- expect(getRawLicense).toHaveBeenCalledTimes(2);
-
- expect(xpackInfoLicenseUndefined.getMode()).toBe(undefined);
- });
-
- test('isActiveLicense returns the true if active and typeChecker matches', () => {
- const expectAbc123 = (type) => type === 'abc123';
-
- getRawLicense.mockReturnValue(
- licensingMock.createLicense({ license: { status: 'active', mode: 'abc123' } })
- );
-
- expect(xpackInfoLicense.isActiveLicense(expectAbc123)).toBe(true);
- expect(getRawLicense).toHaveBeenCalledTimes(1);
-
- getRawLicense.mockReturnValue(
- licensingMock.createLicense({ license: { status: 'NOTactive', mode: 'abc123' } })
- );
-
- expect(xpackInfoLicense.isActiveLicense(expectAbc123)).toBe(false);
- expect(getRawLicense).toHaveBeenCalledTimes(2);
-
- getRawLicense.mockReturnValue(
- licensingMock.createLicense({ license: { status: 'NOTactive', mode: 'NOTabc123' } })
- );
-
- expect(xpackInfoLicense.isActiveLicense(expectAbc123)).toBe(false);
- expect(getRawLicense).toHaveBeenCalledTimes(3);
-
- getRawLicense.mockReturnValue(
- licensingMock.createLicense({ license: { status: 'active', mode: 'NOTabc123' } })
- );
-
- expect(xpackInfoLicense.isActiveLicense(expectAbc123)).toBe(false);
- expect(getRawLicense).toHaveBeenCalledTimes(4);
-
- expect(xpackInfoLicenseUndefined.isActive(expectAbc123)).toBe(false);
- });
-
- test('isBasic returns the true if active and basic', () => {
- getRawLicense.mockReturnValue(
- licensingMock.createLicense({ license: { status: 'active', mode: 'basic' } })
- );
-
- expect(xpackInfoLicense.isBasic()).toBe(true);
- expect(getRawLicense).toHaveBeenCalledTimes(1);
-
- getRawLicense.mockReturnValue(
- licensingMock.createLicense({ license: { status: 'NOTactive', mode: 'gold' } })
- );
-
- expect(xpackInfoLicense.isBasic()).toBe(false);
- expect(getRawLicense).toHaveBeenCalledTimes(2);
-
- getRawLicense.mockReturnValue(
- licensingMock.createLicense({ license: { status: 'NOTactive', mode: 'trial' } })
- );
-
- expect(xpackInfoLicense.isBasic()).toBe(false);
- expect(getRawLicense).toHaveBeenCalledTimes(3);
-
- getRawLicense.mockReturnValue(
- licensingMock.createLicense({ license: { status: 'active', mode: 'platinum' } })
- );
-
- expect(xpackInfoLicense.isBasic()).toBe(false);
- expect(getRawLicense).toHaveBeenCalledTimes(4);
-
- expect(xpackInfoLicenseUndefined.isBasic()).toBe(false);
- });
-
- test('isNotBasic returns the true if active and not basic', () => {
- getRawLicense.mockReturnValue(
- licensingMock.createLicense({ license: { status: 'active', mode: 'platinum' } })
- );
-
- expect(xpackInfoLicense.isNotBasic()).toBe(true);
- expect(getRawLicense).toHaveBeenCalledTimes(1);
-
- getRawLicense.mockReturnValue(
- licensingMock.createLicense({ license: { status: 'NOTactive', mode: 'gold' } })
- );
-
- expect(xpackInfoLicense.isNotBasic()).toBe(false);
- expect(getRawLicense).toHaveBeenCalledTimes(2);
-
- getRawLicense.mockReturnValue(
- licensingMock.createLicense({ license: { status: 'NOTactive', mode: 'trial' } })
- );
-
- expect(xpackInfoLicense.isNotBasic()).toBe(false);
- expect(getRawLicense).toHaveBeenCalledTimes(3);
-
- getRawLicense.mockReturnValue(
- licensingMock.createLicense({ license: { status: 'active', mode: 'basic' } })
- );
-
- expect(xpackInfoLicense.isNotBasic()).toBe(false);
- expect(getRawLicense).toHaveBeenCalledTimes(4);
-
- expect(xpackInfoLicenseUndefined.isNotBasic()).toBe(false);
- });
-});
diff --git a/x-pack/legacy/plugins/xpack_main/server/lib/xpack_info_license.ts b/x-pack/legacy/plugins/xpack_main/server/lib/xpack_info_license.ts
deleted file mode 100644
index dd53f63909475..0000000000000
--- a/x-pack/legacy/plugins/xpack_main/server/lib/xpack_info_license.ts
+++ /dev/null
@@ -1,111 +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 { ILicense } from '../../../../../plugins/licensing/server';
-
-/**
- * "View" for XPack Info license information.
- */
-export class XPackInfoLicense {
- /**
- * Function that retrieves license information from the XPack info object.
- * @type {Function}
- * @private
- */
- _getRawLicense: () => ILicense | undefined;
-
- constructor(getRawLicense: () => ILicense | undefined) {
- this._getRawLicense = getRawLicense;
- }
-
- /**
- * Returns unique identifier of the license.
- * @returns {string|undefined}
- */
- getUid() {
- return this._getRawLicense()?.uid;
- }
-
- /**
- * Indicates whether license is still active.
- * @returns {boolean}
- */
- isActive() {
- return Boolean(this._getRawLicense()?.isActive);
- }
-
- /**
- * Returns license expiration date in ms.
- *
- * Note: A basic license created after 6.3 will have no expiration, thus returning undefined.
- *
- * @returns {number|undefined}
- */
- getExpiryDateInMillis() {
- return this._getRawLicense()?.expiryDateInMillis;
- }
-
- /**
- * Checks if the license is represented in a specified license list.
- * @param {String} candidateLicenses List of the licenses to check against.
- * @returns {boolean}
- */
- isOneOf(candidateLicenses: string | string[]) {
- const candidates = Array.isArray(candidateLicenses) ? candidateLicenses : [candidateLicenses];
- const mode = this._getRawLicense()?.mode;
- return Boolean(mode && candidates.includes(mode));
- }
-
- /**
- * Returns type of the license (basic, gold etc.).
- * @returns {string|undefined}
- */
- getType() {
- return this._getRawLicense()?.type;
- }
-
- /**
- * Returns mode of the license (basic, gold etc.). This is the "effective" type of the license.
- * @returns {string|undefined}
- */
- getMode() {
- return this._getRawLicense()?.mode;
- }
-
- /**
- * Determine if the current license is active and the supplied {@code type}.
- *
- * @param {Function} typeChecker The license type checker.
- * @returns {boolean}
- */
- isActiveLicense(typeChecker: (mode: string) => boolean) {
- const license = this._getRawLicense();
-
- return Boolean(license?.isActive && typeChecker(license.mode as any));
- }
-
- /**
- * Determine if the license is an active, basic license.
- *
- * Note: This also verifies that the license is active. Therefore it is not safe to assume that !isBasic() === isNotBasic().
- *
- * @returns {boolean}
- */
- isBasic() {
- return this.isActiveLicense((mode) => mode === 'basic');
- }
-
- /**
- * Determine if the license is an active, non-basic license (e.g., standard, gold, platinum, or trial).
- *
- * Note: This also verifies that the license is active. Therefore it is not safe to assume that !isBasic() === isNotBasic().
- *
- * @returns {boolean}
- */
- isNotBasic() {
- return this.isActiveLicense((mode) => mode !== 'basic');
- }
-}
diff --git a/x-pack/legacy/plugins/xpack_main/server/routes/api/v1/__tests__/xpack_info.js b/x-pack/legacy/plugins/xpack_main/server/routes/api/v1/__tests__/xpack_info.js
deleted file mode 100644
index 540d9f63ea6c8..0000000000000
--- a/x-pack/legacy/plugins/xpack_main/server/routes/api/v1/__tests__/xpack_info.js
+++ /dev/null
@@ -1,85 +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 sinon from 'sinon';
-
-import { xpackInfoRoute } from '../xpack_info';
-
-describe('XPackInfo routes', () => {
- let serverStub;
- beforeEach(() => {
- serverStub = {
- route: sinon.stub(),
- plugins: {
- xpack_main: {
- info: sinon.stub({ isAvailable() {}, toJSON() {} }),
- },
- },
- };
-
- xpackInfoRoute(serverStub);
- });
-
- it('correctly initialize XPack Info route.', () => {
- sinon.assert.calledWithExactly(serverStub.route, {
- method: 'GET',
- path: '/api/xpack/v1/info',
- handler: sinon.match.func,
- });
- });
-
- it('replies with `Not Found` Boom error if `xpackInfo` is not available.', () => {
- serverStub.plugins.xpack_main.info.isAvailable.returns(false);
-
- const onRouteHandler = serverStub.route.firstCall.args[0].handler;
- const response = onRouteHandler();
-
- expect(response.isBoom).to.be(true);
- expect(response.message).to.be('Not Found');
- expect(response.output.statusCode).to.be(404);
- });
-
- it('replies with pre-processed `xpackInfo` if it is available.', () => {
- serverStub.plugins.xpack_main.info.isAvailable.returns(true);
- serverStub.plugins.xpack_main.info.toJSON.returns({
- license: {
- type: 'gold',
- isActive: true,
- expiryDateInMillis: 1509368280381,
- },
- features: {
- security: {
- showLogin: true,
- allowLogin: true,
- showLinks: false,
- allowRoleDocumentLevelSecurity: false,
- allowRoleFieldLevelSecurity: false,
- },
- },
- });
-
- const onRouteHandler = serverStub.route.firstCall.args[0].handler;
- const response = onRouteHandler();
-
- expect(response).to.eql({
- license: {
- type: 'gold',
- is_active: true,
- expiry_date_in_millis: 1509368280381,
- },
- features: {
- security: {
- show_login: true,
- allow_login: true,
- show_links: false,
- allow_role_document_level_security: false,
- allow_role_field_level_security: false,
- },
- },
- });
- });
-});
diff --git a/x-pack/legacy/plugins/xpack_main/server/routes/api/v1/xpack_info.js b/x-pack/legacy/plugins/xpack_main/server/routes/api/v1/xpack_info.js
deleted file mode 100644
index 3cc57ae9fcab4..0000000000000
--- a/x-pack/legacy/plugins/xpack_main/server/routes/api/v1/xpack_info.js
+++ /dev/null
@@ -1,25 +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';
-import { convertKeysToSnakeCaseDeep } from '../../../../../../server/lib/key_case_converter';
-
-/*
- * A route to provide the basic XPack info for the production cluster
- */
-export function xpackInfoRoute(server) {
- server.route({
- method: 'GET',
- path: '/api/xpack/v1/info',
- handler() {
- const xPackInfo = server.plugins.xpack_main.info;
-
- return xPackInfo.isAvailable()
- ? convertKeysToSnakeCaseDeep(xPackInfo.toJSON())
- : Boom.notFound();
- },
- });
-}
diff --git a/x-pack/legacy/plugins/xpack_main/server/xpack_main.d.ts b/x-pack/legacy/plugins/xpack_main/server/xpack_main.d.ts
deleted file mode 100644
index c2ec5662ad12e..0000000000000
--- a/x-pack/legacy/plugins/xpack_main/server/xpack_main.d.ts
+++ /dev/null
@@ -1,14 +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 KbnServer from 'src/legacy/server/kbn_server';
-import { KibanaFeature } from '../../../../plugins/features/server';
-import { XPackInfo, XPackInfoOptions } from './lib/xpack_info';
-export { XPackFeature } from './lib/xpack_info';
-
-export interface XPackMainPlugin {
- info: XPackInfo;
-}
diff --git a/x-pack/legacy/server/lib/__tests__/key_case_converter.js b/x-pack/legacy/server/lib/__tests__/key_case_converter.js
deleted file mode 100644
index 7ed9fa668ae66..0000000000000
--- a/x-pack/legacy/server/lib/__tests__/key_case_converter.js
+++ /dev/null
@@ -1,117 +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 { convertKeysToSnakeCaseDeep, convertKeysToCamelCaseDeep } from '../key_case_converter';
-
-describe('key_case_converter', () => {
- let testObject;
-
- beforeEach(() => {
- testObject = {
- topLevelKey1: {
- innerLevelKey1: 17,
- inner_level_key2: [19, 31],
- },
- top_level_key2: {
- innerLevelKey1: 'foo_fooFoo',
- inner_level_key2: [{ foo_bar: 29 }, { barBar: 37 }],
- },
- };
- });
-
- describe('convertKeysToSnakeCaseDeep', () => {
- it('should recursively convert camelCase keys to snake_case keys', () => {
- const expectedResultObject = {
- top_level_key_1: {
- inner_level_key_1: 17,
- inner_level_key_2: [19, 31],
- },
- top_level_key_2: {
- inner_level_key_1: 'foo_fooFoo',
- inner_level_key_2: [{ foo_bar: 29 }, { bar_bar: 37 }],
- },
- };
- expect(convertKeysToSnakeCaseDeep(testObject)).to.eql(expectedResultObject);
- });
-
- it('should not modify original object', () => {
- convertKeysToSnakeCaseDeep(testObject);
- expect(Object.keys(testObject)).to.contain('topLevelKey1');
- expect(Object.keys(testObject.topLevelKey1)).to.contain('innerLevelKey1');
- });
-
- it('should preserve inner arrays', () => {
- const result = convertKeysToSnakeCaseDeep(testObject);
- expect(testObject.topLevelKey1.inner_level_key2).to.be.an(Array);
- expect(result.top_level_key_1.inner_level_key_2).to.be.an(Array);
- });
-
- it('should preserve top-level arrays', () => {
- testObject = [{ foo_bar: 17 }, [19, { barBaz: 'qux' }]];
- const expectedResultObject = [{ foo_bar: 17 }, [19, { bar_baz: 'qux' }]];
- const result = convertKeysToSnakeCaseDeep(testObject);
- expect(testObject).to.be.an(Array);
- expect(testObject[1]).to.be.an(Array);
- expect(result).to.be.an(Array);
- expect(result[1]).to.be.an(Array);
- expect(result).to.eql(expectedResultObject);
- });
-
- it('should throw an error if something other an object or array is passed in', () => {
- const expectedErrorMessageRegexp = /Specified object should be an Object or Array/;
- expect(convertKeysToSnakeCaseDeep)
- .withArgs('neither an object nor an array')
- .to.throwException(expectedErrorMessageRegexp);
- });
- });
-
- describe('convertKeysToCamelCaseDeep', () => {
- it('should recursively convert snake_case keys to camelCase keys', () => {
- const expectedResultObject = {
- topLevelKey1: {
- innerLevelKey1: 17,
- innerLevelKey2: [19, 31],
- },
- topLevelKey2: {
- innerLevelKey1: 'foo_fooFoo',
- innerLevelKey2: [{ fooBar: 29 }, { barBar: 37 }],
- },
- };
- expect(convertKeysToCamelCaseDeep(testObject)).to.eql(expectedResultObject);
- });
-
- it('should not modify original object', () => {
- convertKeysToCamelCaseDeep(testObject);
- expect(Object.keys(testObject)).to.contain('top_level_key2');
- expect(Object.keys(testObject.topLevelKey1)).to.contain('inner_level_key2');
- });
-
- it('should preserve inner arrays', () => {
- const result = convertKeysToCamelCaseDeep(testObject);
- expect(testObject.topLevelKey1.inner_level_key2).to.be.an(Array);
- expect(result.topLevelKey1.innerLevelKey2).to.be.an(Array);
- });
-
- it('should preserve top-level arrays', () => {
- testObject = [{ foo_bar: 17 }, [19, { barBaz: 'qux' }]];
- const expectedResultObject = [{ fooBar: 17 }, [19, { barBaz: 'qux' }]];
- const result = convertKeysToCamelCaseDeep(testObject);
- expect(testObject).to.be.an(Array);
- expect(testObject[1]).to.be.an(Array);
- expect(result).to.be.an(Array);
- expect(result[1]).to.be.an(Array);
- expect(result).to.eql(expectedResultObject);
- });
-
- it('should throw an error if something other an object or array is passed in', () => {
- const expectedErrorMessageRegexp = /Specified object should be an Object or Array/;
- expect(convertKeysToCamelCaseDeep)
- .withArgs('neither an object nor an array')
- .to.throwException(expectedErrorMessageRegexp);
- });
- });
-});
diff --git a/x-pack/legacy/server/lib/__tests__/kibana_state.js b/x-pack/legacy/server/lib/__tests__/kibana_state.js
deleted file mode 100644
index d1b4142b10446..0000000000000
--- a/x-pack/legacy/server/lib/__tests__/kibana_state.js
+++ /dev/null
@@ -1,129 +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 rison from 'rison-node';
-import { parseKibanaState } from '../parse_kibana_state';
-
-const stateIndices = {
- global: '_g',
- app: '_a',
-};
-const globalTime =
- '(refreshInterval:(display:Off,pause:!f,value:0),time:(from:now-15m,mode:quick,to:now))';
-
-describe('Kibana state', function () {
- describe('type checking', function () {
- it('should throw if not given an object', function () {
- const fn = () => parseKibanaState('i am not an object', 'global');
- const fn2 = () => parseKibanaState(['arrays are not valid either'], 'global');
- expect(fn).to.throwException(/must be an object/i);
- expect(fn2).to.throwException(/must be an object/i);
- });
-
- it('should throw with invalid type', function () {
- const fn = () => parseKibanaState({}, 'this is an invalid state type');
- expect(fn).to.throwException(/unknown state type/i);
- });
- });
-
- describe('value of exists', function () {
- it('should be false if state does not exist', function () {
- const state = parseKibanaState({}, 'global');
- expect(state.exists).to.equal(false);
- });
-
- it('should be true if state exists', function () {
- const query = {};
- query[stateIndices.global] = rison.encode({ hello: 'world' });
- const state = parseKibanaState(query, 'global');
- expect(state.exists).to.equal(true);
- });
- });
-
- describe('instance methods', function () {
- let query;
-
- beforeEach(function () {
- query = {};
- query[stateIndices.global] = globalTime;
- });
-
- describe('get', function () {
- it('should return the value', function () {
- const state = parseKibanaState(query, 'global');
- const { refreshInterval } = rison.decode(globalTime);
- expect(state.get('refreshInterval')).to.eql(refreshInterval);
- });
-
- it('should use the default value for missing props', function () {
- const defaultValue = 'default value';
- const state = parseKibanaState(query, 'global');
- expect(state.get('no such value', defaultValue)).to.equal(defaultValue);
- });
- });
-
- describe('set', function () {
- it('should update the value of the state', function () {
- const state = parseKibanaState(query, 'global');
- expect(state.get('refreshInterval.pause')).to.equal(false);
-
- state.set(['refreshInterval', 'pause'], true);
- expect(state.get('refreshInterval.pause')).to.equal(true);
- });
-
- it('should create new properties', function () {
- const prop = 'newProp';
- const value = 12345;
- const state = parseKibanaState(query, 'global');
- expect(state.get(prop)).to.be(undefined);
-
- state.set(prop, value);
- expect(state.get(prop)).to.not.be(undefined);
- expect(state.get(prop)).to.equal(value);
- });
- });
-
- describe('removing properties', function () {
- it('should remove a single value', function () {
- const state = parseKibanaState(query, 'global');
- expect(state.get('refreshInterval')).to.be.an('object');
-
- state.removeProps('refreshInterval');
- expect(state.get('refreshInterval')).to.be(undefined);
- });
-
- it('should remove multiple values', function () {
- const state = parseKibanaState(query, 'global');
- expect(state.get('refreshInterval')).to.be.an('object');
- expect(state.get('time')).to.be.an('object');
-
- state.removeProps(['refreshInterval', 'time']);
- expect(state.get('refreshInterval')).to.be(undefined);
- expect(state.get('time')).to.be(undefined);
- });
- });
-
- describe('toString', function () {
- it('should rison encode the state', function () {
- const state = parseKibanaState(query, 'global');
- expect(state.toString()).to.equal(globalTime);
- });
- });
-
- describe('toQuery', function () {
- it('should return an object', function () {
- const state = parseKibanaState(query, 'global');
- expect(state.toQuery()).to.be.an('object');
- });
-
- it('should contain the kibana state property', function () {
- const state = parseKibanaState(query, 'global');
- expect(state.toQuery()).to.have.property(stateIndices.global, globalTime);
- });
- });
- });
-});
diff --git a/x-pack/legacy/server/lib/check_license/check_license.js b/x-pack/legacy/server/lib/check_license/check_license.js
deleted file mode 100644
index 7695755622310..0000000000000
--- a/x-pack/legacy/server/lib/check_license/check_license.js
+++ /dev/null
@@ -1,75 +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 {
- LICENSE_STATUS_UNAVAILABLE,
- LICENSE_STATUS_INVALID,
- LICENSE_STATUS_EXPIRED,
- LICENSE_STATUS_VALID,
- RANKED_LICENSE_TYPES,
-} from '../../../common/constants';
-
-export function checkLicense(pluginName, minimumLicenseRequired, xpackLicenseInfo) {
- if (!minimumLicenseRequired) {
- throw new Error(
- `Error checking license for plugin "${pluginName}". The minimum license required has not been provided.`
- );
- }
-
- if (!RANKED_LICENSE_TYPES.includes(minimumLicenseRequired)) {
- throw new Error(`Invalid license type supplied to checkLicense: ${minimumLicenseRequired}`);
- }
-
- // If, for some reason, we cannot get the license information
- // from Elasticsearch, assume worst case and disable
- if (!xpackLicenseInfo || !xpackLicenseInfo.isAvailable()) {
- return {
- status: LICENSE_STATUS_UNAVAILABLE,
- message: i18n.translate('xpack.server.checkLicense.errorUnavailableMessage', {
- defaultMessage:
- 'You cannot use {pluginName} because license information is not available at this time.',
- values: { pluginName },
- }),
- };
- }
-
- const { license } = xpackLicenseInfo;
- const isLicenseModeValid = license.isOneOf(
- [...RANKED_LICENSE_TYPES].splice(RANKED_LICENSE_TYPES.indexOf(minimumLicenseRequired))
- );
- const isLicenseActive = license.isActive();
- const licenseType = license.getType();
-
- // License is not valid
- if (!isLicenseModeValid) {
- return {
- status: LICENSE_STATUS_INVALID,
- message: i18n.translate('xpack.server.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 {
- status: LICENSE_STATUS_EXPIRED,
- message: i18n.translate('xpack.server.checkLicense.errorExpiredMessage', {
- defaultMessage:
- 'You cannot use {pluginName} because your {licenseType} license has expired.',
- values: { licenseType, pluginName },
- }),
- };
- }
-
- // License is valid and active
- return {
- status: LICENSE_STATUS_VALID,
- };
-}
diff --git a/x-pack/legacy/server/lib/check_license/check_license.test.js b/x-pack/legacy/server/lib/check_license/check_license.test.js
deleted file mode 100644
index 65b599ed4a5f6..0000000000000
--- a/x-pack/legacy/server/lib/check_license/check_license.test.js
+++ /dev/null
@@ -1,132 +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 { set } from '@elastic/safer-lodash-set';
-import { checkLicense } from './check_license';
-import {
- LICENSE_STATUS_UNAVAILABLE,
- LICENSE_STATUS_EXPIRED,
- LICENSE_STATUS_VALID,
- LICENSE_TYPE_BASIC,
-} from '../../../common/constants';
-
-describe('check_license', function () {
- const pluginName = 'Foo';
- const minimumLicenseRequired = LICENSE_TYPE_BASIC;
- let mockLicenseInfo;
- beforeEach(() => (mockLicenseInfo = {}));
-
- describe('license information is undefined', () => {
- beforeEach(() => (mockLicenseInfo = undefined));
-
- it('should set status to unavailable', () => {
- expect(checkLicense(pluginName, minimumLicenseRequired, mockLicenseInfo).status).toBe(
- LICENSE_STATUS_UNAVAILABLE
- );
- });
-
- it('should set a message', () => {
- expect(checkLicense(pluginName, minimumLicenseRequired, mockLicenseInfo).message).not.toBe(
- undefined
- );
- });
- });
-
- describe('license information is not available', () => {
- beforeEach(() => (mockLicenseInfo.isAvailable = () => false));
-
- it('should set status to unavailable', () => {
- expect(checkLicense(pluginName, minimumLicenseRequired, mockLicenseInfo).status).toBe(
- LICENSE_STATUS_UNAVAILABLE
- );
- });
-
- it('should set a message', () => {
- expect(checkLicense(pluginName, minimumLicenseRequired, mockLicenseInfo).message).not.toBe(
- undefined
- );
- });
- });
-
- describe('license information is available', () => {
- beforeEach(() => {
- mockLicenseInfo.isAvailable = () => true;
- set(mockLicenseInfo, 'license.getType', () => LICENSE_TYPE_BASIC);
- });
-
- describe('& license is trial, standard, gold, platinum', () => {
- beforeEach(() => set(mockLicenseInfo, 'license.isOneOf', () => true));
-
- describe('& license is active', () => {
- beforeEach(() => set(mockLicenseInfo, 'license.isActive', () => true));
-
- it('should set status to valid', () => {
- expect(checkLicense(pluginName, minimumLicenseRequired, mockLicenseInfo).status).toBe(
- LICENSE_STATUS_VALID
- );
- });
-
- it('should not set a message', () => {
- expect(checkLicense(pluginName, minimumLicenseRequired, mockLicenseInfo).message).toBe(
- undefined
- );
- });
- });
-
- describe('& license is expired', () => {
- beforeEach(() => set(mockLicenseInfo, 'license.isActive', () => false));
-
- it('should set status to inactive', () => {
- expect(checkLicense(pluginName, minimumLicenseRequired, mockLicenseInfo).status).toBe(
- LICENSE_STATUS_EXPIRED
- );
- });
-
- it('should set a message', () => {
- expect(
- checkLicense(pluginName, minimumLicenseRequired, mockLicenseInfo).message
- ).not.toBe(undefined);
- });
- });
- });
-
- describe('& license is basic', () => {
- beforeEach(() => set(mockLicenseInfo, 'license.isOneOf', () => true));
-
- describe('& license is active', () => {
- beforeEach(() => set(mockLicenseInfo, 'license.isActive', () => true));
-
- it('should set status to valid', () => {
- expect(checkLicense(pluginName, minimumLicenseRequired, mockLicenseInfo).status).toBe(
- LICENSE_STATUS_VALID
- );
- });
-
- it('should not set a message', () => {
- expect(checkLicense(pluginName, minimumLicenseRequired, mockLicenseInfo).message).toBe(
- undefined
- );
- });
- });
-
- describe('& license is expired', () => {
- beforeEach(() => set(mockLicenseInfo, 'license.isActive', () => false));
-
- it('should set status to inactive', () => {
- expect(checkLicense(pluginName, minimumLicenseRequired, mockLicenseInfo).status).toBe(
- LICENSE_STATUS_EXPIRED
- );
- });
-
- it('should set a message', () => {
- expect(
- checkLicense(pluginName, minimumLicenseRequired, mockLicenseInfo).message
- ).not.toBe(undefined);
- });
- });
- });
- });
-});
diff --git a/x-pack/legacy/server/lib/check_license/index.js b/x-pack/legacy/server/lib/check_license/index.js
deleted file mode 100644
index f2c070fd44b6e..0000000000000
--- a/x-pack/legacy/server/lib/check_license/index.js
+++ /dev/null
@@ -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 { checkLicense } from './check_license';
diff --git a/x-pack/legacy/server/lib/constants/index.ts b/x-pack/legacy/server/lib/constants/index.ts
deleted file mode 100644
index 2378aca824042..0000000000000
--- a/x-pack/legacy/server/lib/constants/index.ts
+++ /dev/null
@@ -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 { XPACK_INFO_API_DEFAULT_POLL_FREQUENCY_IN_MILLIS } from './xpack_info';
diff --git a/x-pack/legacy/server/lib/constants/xpack_info.ts b/x-pack/legacy/server/lib/constants/xpack_info.ts
deleted file mode 100644
index c58bb275245b6..0000000000000
--- a/x-pack/legacy/server/lib/constants/xpack_info.ts
+++ /dev/null
@@ -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 const XPACK_INFO_API_DEFAULT_POLL_FREQUENCY_IN_MILLIS = 30001; // 30 seconds
diff --git a/x-pack/legacy/server/lib/create_router/call_with_request_factory/call_with_request_factory.js b/x-pack/legacy/server/lib/create_router/call_with_request_factory/call_with_request_factory.js
deleted file mode 100644
index df1ce95b31655..0000000000000
--- a/x-pack/legacy/server/lib/create_router/call_with_request_factory/call_with_request_factory.js
+++ /dev/null
@@ -1,12 +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 const callWithRequestFactory = (server, pluginId, config) => {
- const { callWithRequest } = config
- ? server.plugins.elasticsearch.createCluster(pluginId, config)
- : server.plugins.elasticsearch.getCluster('data');
- return callWithRequest;
-};
diff --git a/x-pack/legacy/server/lib/create_router/call_with_request_factory/index.d.ts b/x-pack/legacy/server/lib/create_router/call_with_request_factory/index.d.ts
deleted file mode 100644
index 3537d1bf42079..0000000000000
--- a/x-pack/legacy/server/lib/create_router/call_with_request_factory/index.d.ts
+++ /dev/null
@@ -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 { Legacy } from 'kibana';
-import { LegacyAPICaller } from '../../../../../../src/core/server';
-
-export type CallWithRequest = (...args: any[]) => LegacyAPICaller;
-
-export declare function callWithRequestFactory(
- server: Legacy.Server,
- pluginId: string,
- config?: {
- plugins: any[];
- }
-): CallWithRequest;
diff --git a/x-pack/legacy/server/lib/create_router/call_with_request_factory/index.js b/x-pack/legacy/server/lib/create_router/call_with_request_factory/index.js
deleted file mode 100644
index 787814d87dff9..0000000000000
--- a/x-pack/legacy/server/lib/create_router/call_with_request_factory/index.js
+++ /dev/null
@@ -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 { callWithRequestFactory } from './call_with_request_factory';
diff --git a/x-pack/legacy/server/lib/create_router/error_wrappers/__tests__/wrap_custom_error.js b/x-pack/legacy/server/lib/create_router/error_wrappers/__tests__/wrap_custom_error.js
deleted file mode 100644
index f9c102be7a1ff..0000000000000
--- a/x-pack/legacy/server/lib/create_router/error_wrappers/__tests__/wrap_custom_error.js
+++ /dev/null
@@ -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);
- });
- });
-});
diff --git a/x-pack/legacy/server/lib/create_router/error_wrappers/__tests__/wrap_es_error.js b/x-pack/legacy/server/lib/create_router/error_wrappers/__tests__/wrap_es_error.js
deleted file mode 100644
index 8241dc4329137..0000000000000
--- a/x-pack/legacy/server/lib/create_router/error_wrappers/__tests__/wrap_es_error.js
+++ /dev/null
@@ -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!');
- });
- });
-});
diff --git a/x-pack/legacy/server/lib/create_router/error_wrappers/__tests__/wrap_unknown_error.js b/x-pack/legacy/server/lib/create_router/error_wrappers/__tests__/wrap_unknown_error.js
deleted file mode 100644
index 85e0b2b3033ad..0000000000000
--- a/x-pack/legacy/server/lib/create_router/error_wrappers/__tests__/wrap_unknown_error.js
+++ /dev/null
@@ -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);
- });
- });
-});
diff --git a/x-pack/legacy/server/lib/create_router/error_wrappers/index.d.ts b/x-pack/legacy/server/lib/create_router/error_wrappers/index.d.ts
deleted file mode 100644
index 1aaefb4e3727c..0000000000000
--- a/x-pack/legacy/server/lib/create_router/error_wrappers/index.d.ts
+++ /dev/null
@@ -1,12 +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';
-
-export declare function wrapCustomError(error: Error, statusCode: number): Boom;
-
-export declare function wrapEsError(error: Error, statusCodeToMessageMap?: object): Boom;
-
-export declare function wrapUnknownError(error: Error): Boom;
diff --git a/x-pack/legacy/server/lib/create_router/error_wrappers/index.js b/x-pack/legacy/server/lib/create_router/error_wrappers/index.js
deleted file mode 100644
index f275f15637091..0000000000000
--- a/x-pack/legacy/server/lib/create_router/error_wrappers/index.js
+++ /dev/null
@@ -1,9 +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 { wrapCustomError } from './wrap_custom_error';
-export { wrapEsError } from './wrap_es_error';
-export { wrapUnknownError } from './wrap_unknown_error';
diff --git a/x-pack/legacy/server/lib/create_router/error_wrappers/wrap_custom_error.js b/x-pack/legacy/server/lib/create_router/error_wrappers/wrap_custom_error.js
deleted file mode 100644
index 3295113d38ee5..0000000000000
--- a/x-pack/legacy/server/lib/create_router/error_wrappers/wrap_custom_error.js
+++ /dev/null
@@ -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 });
-}
diff --git a/x-pack/legacy/server/lib/create_router/error_wrappers/wrap_es_error.js b/x-pack/legacy/server/lib/create_router/error_wrappers/wrap_es_error.js
deleted file mode 100644
index 72be6321af8a2..0000000000000
--- a/x-pack/legacy/server/lib/create_router/error_wrappers/wrap_es_error.js
+++ /dev/null
@@ -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, include the additional information from ES, 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 });
-}
diff --git a/x-pack/legacy/server/lib/create_router/error_wrappers/wrap_unknown_error.js b/x-pack/legacy/server/lib/create_router/error_wrappers/wrap_unknown_error.js
deleted file mode 100644
index ffd915c513362..0000000000000
--- a/x-pack/legacy/server/lib/create_router/error_wrappers/wrap_unknown_error.js
+++ /dev/null
@@ -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);
-}
diff --git a/x-pack/legacy/server/lib/create_router/index.d.ts b/x-pack/legacy/server/lib/create_router/index.d.ts
deleted file mode 100644
index 76e5f4b599708..0000000000000
--- a/x-pack/legacy/server/lib/create_router/index.d.ts
+++ /dev/null
@@ -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 { Request, ResponseToolkit } from 'hapi';
-import { Legacy } from 'kibana';
-import { CallWithRequest } from './call_with_request_factory';
-
-export * from './error_wrappers';
-
-export type RouterRouteHandler = (
- req: Request,
- callWithRequest: ReturnType,
- responseToolkit: ResponseToolkit
-) => Promise;
-
-export type RouterRoute = (path: string, handler: RouterRouteHandler) => Router;
-
-export interface Router {
- get: RouterRoute;
- post: RouterRoute;
- put: RouterRoute;
- delete: RouterRoute;
- patch: RouterRoute;
- isEsError: any;
-}
-
-export declare function createRouter(
- server: Legacy.Server,
- pluginId: string,
- apiBasePath: string,
- config?: {
- plugins: any[];
- }
-): Router;
-
-export declare function isEsErrorFactory(server: Legacy.Server): any;
diff --git a/x-pack/legacy/server/lib/create_router/index.js b/x-pack/legacy/server/lib/create_router/index.js
deleted file mode 100644
index e4d66bdb5a48b..0000000000000
--- a/x-pack/legacy/server/lib/create_router/index.js
+++ /dev/null
@@ -1,61 +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';
-import { callWithRequestFactory } from './call_with_request_factory';
-import { isEsErrorFactory as createIsEsError } from './is_es_error_factory';
-import { wrapEsError, wrapUnknownError } from './error_wrappers';
-import { licensePreRoutingFactory } from './license_pre_routing_factory';
-
-export { wrapEsError, wrapUnknownError, wrapCustomError } from './error_wrappers';
-
-// Sometimes consumers will need to check if errors are ES errors, too.
-export const isEsErrorFactory = (server) => {
- return createIsEsError(server);
-};
-
-export const createRouter = (server, pluginId, apiBasePath = '', config) => {
- const isEsError = isEsErrorFactory(server);
-
- // NOTE: The license-checking logic depends on the xpack_main plugin, so if your plugin
- // consumes this helper, make sure it declares 'xpack_main' as a dependency.
- const licensePreRouting = licensePreRoutingFactory(server, pluginId);
-
- const callWithRequestInstance = callWithRequestFactory(server, pluginId, config);
-
- const requestHandler = (handler) => async (request, h) => {
- try {
- const callWithRequest = (...args) => {
- return callWithRequestInstance(request, ...args);
- };
- return await handler(request, callWithRequest, h);
- } catch (err) {
- if (err instanceof Boom) {
- throw err;
- }
-
- if (isEsError(err)) {
- throw wrapEsError(err);
- }
-
- throw wrapUnknownError(err);
- }
- };
-
- // Decorate base router with HTTP methods.
- return ['get', 'post', 'put', 'delete', 'patch'].reduce((router, methodName) => {
- router[methodName] = (subPath, handler) => {
- const method = methodName.toUpperCase();
- const path = apiBasePath + subPath;
- server.route({
- path,
- method,
- handler: requestHandler(handler),
- config: { pre: [licensePreRouting] },
- });
- };
- return router;
- }, {});
-};
diff --git a/x-pack/legacy/server/lib/create_router/is_es_error_factory/__tests__/is_es_error_factory.js b/x-pack/legacy/server/lib/create_router/is_es_error_factory/__tests__/is_es_error_factory.js
deleted file mode 100644
index ef6fbaf9c53d0..0000000000000
--- a/x-pack/legacy/server/lib/create_router/is_es_error_factory/__tests__/is_es_error_factory.js
+++ /dev/null
@@ -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 '@elastic/safer-lodash-set';
-
-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);
- });
- });
- });
-});
diff --git a/x-pack/legacy/server/lib/create_router/is_es_error_factory/index.js b/x-pack/legacy/server/lib/create_router/is_es_error_factory/index.js
deleted file mode 100644
index 441648a8701e0..0000000000000
--- a/x-pack/legacy/server/lib/create_router/is_es_error_factory/index.js
+++ /dev/null
@@ -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 { isEsErrorFactory } from './is_es_error_factory';
diff --git a/x-pack/legacy/server/lib/create_router/is_es_error_factory/is_es_error_factory.js b/x-pack/legacy/server/lib/create_router/is_es_error_factory/is_es_error_factory.js
deleted file mode 100644
index 80daac5bd496d..0000000000000
--- a/x-pack/legacy/server/lib/create_router/is_es_error_factory/is_es_error_factory.js
+++ /dev/null
@@ -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;
- };
-}
diff --git a/x-pack/legacy/server/lib/create_router/license_pre_routing_factory/__tests__/license_pre_routing_factory.js b/x-pack/legacy/server/lib/create_router/license_pre_routing_factory/__tests__/license_pre_routing_factory.js
deleted file mode 100644
index dde18a0ccd7dd..0000000000000
--- a/x-pack/legacy/server/lib/create_router/license_pre_routing_factory/__tests__/license_pre_routing_factory.js
+++ /dev/null
@@ -1,70 +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';
-import { LICENSE_STATUS_INVALID, LICENSE_STATUS_VALID } from '../../../../../common/constants';
-
-describe('license_pre_routing_factory', () => {
- describe('#reportingFeaturePreRoutingFactory', () => {
- let mockServer;
- let mockLicenseCheckResults;
-
- beforeEach(() => {
- mockServer = {
- plugins: {
- xpack_main: {
- info: {
- feature: () => ({
- getLicenseCheckResults: () => mockLicenseCheckResults,
- }),
- },
- },
- },
- };
- });
-
- it('instantiates a new instance per plugin', () => {
- const firstInstance = licensePreRoutingFactory(mockServer, 'foo');
- const secondInstance = licensePreRoutingFactory(mockServer, 'bar');
-
- expect(firstInstance).to.not.be(secondInstance);
- });
-
- describe('status is invalid', () => {
- beforeEach(() => {
- mockLicenseCheckResults = {
- status: LICENSE_STATUS_INVALID,
- };
- });
-
- it('replies with 403', () => {
- const licensePreRouting = licensePreRoutingFactory(mockServer);
- const stubRequest = {};
- expect(() => licensePreRouting(stubRequest)).to.throwException((response) => {
- expect(response).to.be.an(Error);
- expect(response.isBoom).to.be(true);
- expect(response.output.statusCode).to.be(403);
- });
- });
- });
-
- describe('status is valid', () => {
- beforeEach(() => {
- mockLicenseCheckResults = {
- status: LICENSE_STATUS_VALID,
- };
- });
-
- it('replies with nothing', () => {
- const licensePreRouting = licensePreRoutingFactory(mockServer);
- const stubRequest = {};
- const response = licensePreRouting(stubRequest);
- expect(response).to.be(null);
- });
- });
- });
-});
diff --git a/x-pack/legacy/server/lib/create_router/license_pre_routing_factory/index.js b/x-pack/legacy/server/lib/create_router/license_pre_routing_factory/index.js
deleted file mode 100644
index 0743e443955f4..0000000000000
--- a/x-pack/legacy/server/lib/create_router/license_pre_routing_factory/index.js
+++ /dev/null
@@ -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 { licensePreRoutingFactory } from './license_pre_routing_factory';
diff --git a/x-pack/legacy/server/lib/create_router/license_pre_routing_factory/license_pre_routing_factory.js b/x-pack/legacy/server/lib/create_router/license_pre_routing_factory/license_pre_routing_factory.js
deleted file mode 100644
index 81640ebb35ea9..0000000000000
--- a/x-pack/legacy/server/lib/create_router/license_pre_routing_factory/license_pre_routing_factory.js
+++ /dev/null
@@ -1,26 +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 { wrapCustomError } from '../error_wrappers';
-import { LICENSE_STATUS_VALID } from '../../../../common/constants';
-
-export const licensePreRoutingFactory = (server, pluginId) => {
- return () => {
- const xpackMainPlugin = server.plugins.xpack_main;
- const licenseCheckResults = xpackMainPlugin.info.feature(pluginId).getLicenseCheckResults();
-
- // Apps which don't have any license restrictions will return undefined license check results.
- if (licenseCheckResults) {
- if (licenseCheckResults.status !== LICENSE_STATUS_VALID) {
- const error = new Error(licenseCheckResults.message);
- const statusCode = 403;
- throw wrapCustomError(error, statusCode);
- }
- }
-
- return null;
- };
-};
diff --git a/x-pack/legacy/server/lib/key_case_converter.js b/x-pack/legacy/server/lib/key_case_converter.js
deleted file mode 100644
index a2a5452b3a1d9..0000000000000
--- a/x-pack/legacy/server/lib/key_case_converter.js
+++ /dev/null
@@ -1,52 +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 _ from 'lodash';
-
-// Note: This function uses _.clone. This will clone objects created by constructors other than Object
-// to plain Object objects. Uncloneable values such as functions, DOM nodes, Maps, Sets, and WeakMaps
-// will be cloned to the empty object.
-function convertKeysToSpecifiedCaseDeep(object, caseConversionFunction) {
- // Base case
- if (!(_.isPlainObject(object) || _.isArray(object))) {
- return object;
- }
-
- // Clone (so we don't modify the original object that was passed in)
- let newObject;
- if (Array.isArray(object)) {
- newObject = object.slice(0);
- } else {
- newObject = _.clone(object);
-
- // Convert top-level keys
- newObject = _.mapKeys(newObject, (value, key) => caseConversionFunction(key));
- }
-
- // Recursively convert nested object keys
- _.forEach(
- newObject,
- (value, key) => (newObject[key] = convertKeysToSpecifiedCaseDeep(value, caseConversionFunction))
- );
-
- return newObject;
-}
-
-function validateObject(object) {
- if (!(_.isPlainObject(object) || _.isArray(object))) {
- throw new Error('Specified object should be an Object or Array');
- }
-}
-
-export function convertKeysToSnakeCaseDeep(object) {
- validateObject(object);
- return convertKeysToSpecifiedCaseDeep(object, _.snakeCase);
-}
-
-export function convertKeysToCamelCaseDeep(object) {
- validateObject(object);
- return convertKeysToSpecifiedCaseDeep(object, _.camelCase);
-}
diff --git a/x-pack/legacy/server/lib/parse_kibana_state.js b/x-pack/legacy/server/lib/parse_kibana_state.js
deleted file mode 100644
index a6c9bfbb511c1..0000000000000
--- a/x-pack/legacy/server/lib/parse_kibana_state.js
+++ /dev/null
@@ -1,55 +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 { set } from '@elastic/safer-lodash-set';
-import { isPlainObject, omit, get } from 'lodash';
-import rison from 'rison-node';
-
-const stateTypeKeys = {
- global: '_g',
- app: '_a',
-};
-
-class KibanaState {
- constructor(query, type = 'global') {
- const propId = stateTypeKeys[type];
- if (!isPlainObject(query)) throw new TypeError('Query parameter must be an object');
- if (!propId) throw new TypeError(`Unknown state type: '${type}'`);
-
- const queryValue = query[propId];
-
- this.exists = Boolean(queryValue);
- this.state = queryValue ? rison.decode(queryValue) : {};
- this.type = type;
- }
-
- removeProps(props) {
- this.state = omit(this.state, props);
- }
-
- get(prop, defVal) {
- return get(this.state, prop, defVal);
- }
-
- set(prop, val) {
- return set(this.state, prop, val);
- }
-
- toString() {
- return rison.encode(this.state);
- }
-
- toQuery() {
- const index = stateTypeKeys[this.type];
- const output = {};
- output[index] = this.toString();
- return output;
- }
-}
-
-export function parseKibanaState(query, type) {
- return new KibanaState(query, type);
-}
diff --git a/x-pack/legacy/server/lib/register_license_checker/index.d.ts b/x-pack/legacy/server/lib/register_license_checker/index.d.ts
deleted file mode 100644
index 555008921df42..0000000000000
--- a/x-pack/legacy/server/lib/register_license_checker/index.d.ts
+++ /dev/null
@@ -1,15 +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 { Legacy } from 'kibana';
-import { LicenseType } from '../../../common/constants';
-
-export declare function registerLicenseChecker(
- server: Legacy.Server,
- pluginId: string,
- pluginName: string,
- minimumLicenseRequired: LicenseType
-): void;
diff --git a/x-pack/legacy/server/lib/register_license_checker/index.js b/x-pack/legacy/server/lib/register_license_checker/index.js
deleted file mode 100644
index 7b0f97c38d129..0000000000000
--- a/x-pack/legacy/server/lib/register_license_checker/index.js
+++ /dev/null
@@ -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 { registerLicenseChecker } from './register_license_checker';
diff --git a/x-pack/legacy/server/lib/register_license_checker/register_license_checker.js b/x-pack/legacy/server/lib/register_license_checker/register_license_checker.js
deleted file mode 100644
index 57cbe30c25cb2..0000000000000
--- a/x-pack/legacy/server/lib/register_license_checker/register_license_checker.js
+++ /dev/null
@@ -1,34 +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 { pairwise } from 'rxjs/operators';
-
-import { ServiceStatusLevels } from '../../../../../src/core/server';
-import { checkLicense } from '../check_license';
-
-export function registerLicenseChecker(server, pluginId, pluginName, minimumLicenseRequired) {
- const xpackMainPlugin = server.plugins.xpack_main;
- const subscription = server.newPlatform.setup.core.status.core$
- .pipe(pairwise())
- .subscribe(([coreLast, coreCurrent]) => {
- if (
- !subscription.closed &&
- coreLast.elasticsearch.level !== ServiceStatusLevels.available &&
- coreCurrent.elasticsearch.level === ServiceStatusLevels.available
- ) {
- // Unsubscribe as soon as ES becomes available so this function only runs once
- subscription.unsubscribe();
-
- // Register a function that is called whenever the xpack info changes,
- // to re-compute the license check results for this plugin
- xpackMainPlugin.info
- .feature(pluginId)
- .registerLicenseCheckResultsGenerator((xpackLicenseInfo) => {
- return checkLicense(pluginName, minimumLicenseRequired, xpackLicenseInfo);
- });
- }
- });
-}
diff --git a/x-pack/legacy/server/lib/watch_status_and_license_to_initialize.js b/x-pack/legacy/server/lib/watch_status_and_license_to_initialize.js
deleted file mode 100644
index 109dbbb20e35d..0000000000000
--- a/x-pack/legacy/server/lib/watch_status_and_license_to_initialize.js
+++ /dev/null
@@ -1,83 +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 * as Rx from 'rxjs';
-import { catchError, mergeMap, map, switchMap, tap } from 'rxjs/operators';
-
-export const RETRY_SCALE_DURATION = 100;
-export const RETRY_DURATION_MAX = 10000;
-
-const calculateDuration = (i) => {
- const duration = i * RETRY_SCALE_DURATION;
- if (duration > RETRY_DURATION_MAX) {
- return RETRY_DURATION_MAX;
- }
-
- return duration;
-};
-
-// we can't use a retryWhen here, because we want to propagate the red status and then retry
-const propagateRedStatusAndScaleRetry = () => {
- let i = 0;
- return (err, caught) =>
- Rx.concat(
- Rx.of({
- state: 'red',
- message: err.message,
- }),
- Rx.timer(calculateDuration(++i)).pipe(mergeMap(() => caught))
- );
-};
-
-export function watchStatusAndLicenseToInitialize(xpackMainPlugin, downstreamPlugin, initialize) {
- const xpackInfo = xpackMainPlugin.info;
- const xpackInfoFeature = xpackInfo.feature(downstreamPlugin.id);
-
- const upstreamStatus = xpackMainPlugin.status;
- const currentStatus$ = Rx.of({
- state: upstreamStatus.state,
- message: upstreamStatus.message,
- });
- const newStatus$ = Rx.fromEvent(
- upstreamStatus,
- 'change',
- null,
- (previousState, previousMsg, state, message) => {
- return {
- state,
- message,
- };
- }
- );
- const status$ = Rx.merge(currentStatus$, newStatus$);
-
- const currentLicense$ = Rx.of(xpackInfoFeature.getLicenseCheckResults());
- const newLicense$ = Rx.fromEventPattern(xpackInfo.onLicenseInfoChange.bind(xpackInfo)).pipe(
- map(() => xpackInfoFeature.getLicenseCheckResults())
- );
- const license$ = Rx.merge(currentLicense$, newLicense$);
-
- Rx.combineLatest(status$, license$)
- .pipe(
- map(([status, license]) => ({ status, license })),
- switchMap(({ status, license }) => {
- if (status.state !== 'green') {
- return Rx.of({ state: status.state, message: status.message });
- }
-
- return Rx.defer(() => initialize(license)).pipe(
- map(() => ({
- state: 'green',
- message: 'Ready',
- })),
- catchError(propagateRedStatusAndScaleRetry())
- );
- }),
- tap(({ state, message }) => {
- downstreamPlugin.status[state](message);
- })
- )
- .subscribe();
-}
diff --git a/x-pack/legacy/server/lib/watch_status_and_license_to_initialize.test.js b/x-pack/legacy/server/lib/watch_status_and_license_to_initialize.test.js
deleted file mode 100644
index 33282b7591db7..0000000000000
--- a/x-pack/legacy/server/lib/watch_status_and_license_to_initialize.test.js
+++ /dev/null
@@ -1,301 +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 { EventEmitter } from 'events';
-import {
- watchStatusAndLicenseToInitialize,
- RETRY_SCALE_DURATION,
- RETRY_DURATION_MAX,
-} from './watch_status_and_license_to_initialize';
-
-const createMockXpackMainPluginAndFeature = (featureId) => {
- const licenseChangeCallbacks = [];
-
- const mockFeature = {
- getLicenseCheckResults: jest.fn(),
- mock: {
- triggerLicenseChange: () => {
- for (const callback of licenseChangeCallbacks) {
- callback();
- }
- },
- setLicenseCheckResults: (value) => {
- mockFeature.getLicenseCheckResults.mockReturnValue(value);
- },
- },
- };
-
- const mockXpackMainPlugin = {
- info: {
- onLicenseInfoChange: (callback) => {
- licenseChangeCallbacks.push(callback);
- },
- feature: (id) => {
- if (id === featureId) {
- return mockFeature;
- }
- throw new Error('Unexpected feature');
- },
- },
- status: new EventEmitter(),
- mock: {
- setStatus: (state, message) => {
- mockXpackMainPlugin.status.state = state;
- mockXpackMainPlugin.status.message = message;
- mockXpackMainPlugin.status.emit('change', null, null, state, message);
- },
- },
- };
-
- return { mockXpackMainPlugin, mockFeature };
-};
-
-const createMockDownstreamPlugin = (id) => {
- const defaultImplementation = () => {
- throw new Error('Not implemented');
- };
- return {
- id,
- status: {
- disabled: jest.fn().mockImplementation(defaultImplementation),
- yellow: jest.fn().mockImplementation(defaultImplementation),
- green: jest.fn().mockImplementation(defaultImplementation),
- red: jest.fn().mockImplementation(defaultImplementation),
- },
- };
-};
-
-const advanceRetry = async (initializeCount) => {
- await Promise.resolve();
- let duration = initializeCount * RETRY_SCALE_DURATION;
- if (duration > RETRY_DURATION_MAX) {
- duration = RETRY_DURATION_MAX;
- }
- jest.advanceTimersByTime(duration);
-};
-
-['red', 'yellow', 'disabled'].forEach((state) => {
- test(`mirrors ${state} immediately`, () => {
- const pluginId = 'foo-plugin';
- const message = `${state} is now the state`;
- const { mockXpackMainPlugin } = createMockXpackMainPluginAndFeature(pluginId);
- mockXpackMainPlugin.mock.setStatus(state, message);
- const downstreamPlugin = createMockDownstreamPlugin(pluginId);
- const initializeMock = jest.fn();
- downstreamPlugin.status[state].mockImplementation(() => {});
-
- watchStatusAndLicenseToInitialize(mockXpackMainPlugin, downstreamPlugin, initializeMock);
-
- expect(initializeMock).not.toHaveBeenCalled();
- expect(downstreamPlugin.status[state]).toHaveBeenCalledTimes(1);
- expect(downstreamPlugin.status[state]).toHaveBeenCalledWith(message);
- });
-});
-
-test(`calls initialize and doesn't immediately set downstream status when the initial status is green`, () => {
- const pluginId = 'foo-plugin';
- const { mockXpackMainPlugin, mockFeature } = createMockXpackMainPluginAndFeature(pluginId);
- mockXpackMainPlugin.mock.setStatus('green', 'green is now the state');
- const licenseCheckResults = Symbol();
- mockFeature.mock.setLicenseCheckResults(licenseCheckResults);
- const downstreamPlugin = createMockDownstreamPlugin(pluginId);
- const initializeMock = jest.fn().mockImplementation(() => new Promise(() => {}));
-
- watchStatusAndLicenseToInitialize(mockXpackMainPlugin, downstreamPlugin, initializeMock);
-
- expect(initializeMock).toHaveBeenCalledTimes(1);
- expect(initializeMock).toHaveBeenCalledWith(licenseCheckResults);
- expect(downstreamPlugin.status.green).toHaveBeenCalledTimes(0);
-});
-
-test(`sets downstream plugin's status to green when initialize resolves`, (done) => {
- const pluginId = 'foo-plugin';
- const { mockXpackMainPlugin, mockFeature } = createMockXpackMainPluginAndFeature(pluginId);
- mockXpackMainPlugin.mock.setStatus('green', 'green is now the state');
- const licenseCheckResults = Symbol();
- mockFeature.mock.setLicenseCheckResults(licenseCheckResults);
- const downstreamPlugin = createMockDownstreamPlugin(pluginId);
- const initializeMock = jest.fn().mockImplementation(() => Promise.resolve());
-
- watchStatusAndLicenseToInitialize(mockXpackMainPlugin, downstreamPlugin, initializeMock);
-
- expect(initializeMock).toHaveBeenCalledTimes(1);
- expect(initializeMock).toHaveBeenCalledWith(licenseCheckResults);
- downstreamPlugin.status.green.mockImplementation((actualMessage) => {
- expect(actualMessage).toBe('Ready');
- done();
- });
-});
-
-test(`sets downstream plugin's status to red when initialize initially rejects, and continually polls initialize`, (done) => {
- jest.useFakeTimers();
-
- const pluginId = 'foo-plugin';
- const errorMessage = 'the error message';
- const { mockXpackMainPlugin, mockFeature } = createMockXpackMainPluginAndFeature(pluginId);
- mockXpackMainPlugin.mock.setStatus('green');
- const licenseCheckResults = Symbol();
- mockFeature.mock.setLicenseCheckResults(licenseCheckResults);
- const downstreamPlugin = createMockDownstreamPlugin(pluginId);
-
- let isRed = false;
- let initializeCount = 0;
- const initializeMock = jest.fn().mockImplementation(() => {
- ++initializeCount;
-
- // on the second retry, ensure we already set the status to red
- if (initializeCount === 2) {
- expect(isRed).toBe(true);
- }
-
- // this should theoretically continue indefinitely, but we only have so long to run the tests
- if (initializeCount === 100) {
- done();
- }
-
- // everytime this is called, we have to wait for a new promise to be resolved
- // allowing the Promise the we return below to run, and then advance the timers
- setImmediate(() => {
- advanceRetry(initializeCount);
- });
- return Promise.reject(new Error(errorMessage));
- });
-
- watchStatusAndLicenseToInitialize(mockXpackMainPlugin, downstreamPlugin, initializeMock);
-
- expect(initializeMock).toHaveBeenCalledTimes(1);
- expect(initializeMock).toHaveBeenCalledWith(licenseCheckResults);
- downstreamPlugin.status.red.mockImplementation((message) => {
- isRed = true;
- expect(message).toBe(errorMessage);
- });
-});
-
-test(`sets downstream plugin's status to green when initialize resolves after rejecting 10 times`, (done) => {
- jest.useFakeTimers();
-
- const pluginId = 'foo-plugin';
- const errorMessage = 'the error message';
- const { mockXpackMainPlugin, mockFeature } = createMockXpackMainPluginAndFeature(pluginId);
- mockXpackMainPlugin.mock.setStatus('green');
- const licenseCheckResults = Symbol();
- mockFeature.mock.setLicenseCheckResults(licenseCheckResults);
- const downstreamPlugin = createMockDownstreamPlugin(pluginId);
-
- let initializeCount = 0;
- const initializeMock = jest.fn().mockImplementation(() => {
- ++initializeCount;
-
- // everytime this is called, we have to wait for a new promise to be resolved
- // allowing the Promise the we return below to run, and then advance the timers
- setImmediate(() => {
- advanceRetry(initializeCount);
- });
-
- if (initializeCount >= 10) {
- return Promise.resolve();
- }
-
- return Promise.reject(new Error(errorMessage));
- });
-
- watchStatusAndLicenseToInitialize(mockXpackMainPlugin, downstreamPlugin, initializeMock);
-
- expect(initializeMock).toHaveBeenCalledTimes(1);
- expect(initializeMock).toHaveBeenCalledWith(licenseCheckResults);
- downstreamPlugin.status.red.mockImplementation((message) => {
- expect(initializeCount).toBeLessThan(10);
- expect(message).toBe(errorMessage);
- });
- downstreamPlugin.status.green.mockImplementation((message) => {
- expect(initializeCount).toBe(10);
- expect(message).toBe('Ready');
- done();
- });
-});
-
-test(`calls initialize twice when it gets a new license and the status is green`, (done) => {
- const pluginId = 'foo-plugin';
- const { mockXpackMainPlugin, mockFeature } = createMockXpackMainPluginAndFeature(pluginId);
- mockXpackMainPlugin.mock.setStatus('green');
- const firstLicenseCheckResults = Symbol();
- const secondLicenseCheckResults = Symbol();
- mockFeature.mock.setLicenseCheckResults(firstLicenseCheckResults);
- const downstreamPlugin = createMockDownstreamPlugin(pluginId);
- const initializeMock = jest.fn().mockImplementation(() => Promise.resolve());
-
- let count = 0;
- downstreamPlugin.status.green.mockImplementation((message) => {
- expect(message).toBe('Ready');
- ++count;
- if (count === 1) {
- mockFeature.mock.setLicenseCheckResults(secondLicenseCheckResults);
- mockFeature.mock.triggerLicenseChange();
- }
- if (count === 2) {
- expect(initializeMock).toHaveBeenCalledWith(firstLicenseCheckResults);
- expect(initializeMock).toHaveBeenCalledWith(secondLicenseCheckResults);
- expect(initializeMock).toHaveBeenCalledTimes(2);
- done();
- }
- });
-
- watchStatusAndLicenseToInitialize(mockXpackMainPlugin, downstreamPlugin, initializeMock);
-});
-
-test(`doesn't call initialize twice when it gets a new license when the status isn't green`, (done) => {
- const pluginId = 'foo-plugin';
- const redMessage = 'the red message';
- const { mockXpackMainPlugin, mockFeature } = createMockXpackMainPluginAndFeature(pluginId);
- mockXpackMainPlugin.mock.setStatus('green');
- const firstLicenseCheckResults = Symbol();
- const secondLicenseCheckResults = Symbol();
- mockFeature.mock.setLicenseCheckResults(firstLicenseCheckResults);
- const downstreamPlugin = createMockDownstreamPlugin(pluginId);
- const initializeMock = jest.fn().mockImplementation(() => Promise.resolve());
-
- downstreamPlugin.status.green.mockImplementation((message) => {
- expect(message).toBe('Ready');
- mockXpackMainPlugin.mock.setStatus('red', redMessage);
- mockFeature.mock.setLicenseCheckResults(secondLicenseCheckResults);
- mockFeature.mock.triggerLicenseChange();
- });
-
- downstreamPlugin.status.red.mockImplementation((message) => {
- expect(message).toBe(redMessage);
- expect(initializeMock).toHaveBeenCalledTimes(1);
- expect(initializeMock).toHaveBeenCalledWith(firstLicenseCheckResults);
- done();
- });
-
- watchStatusAndLicenseToInitialize(mockXpackMainPlugin, downstreamPlugin, initializeMock);
-});
-
-test(`calls initialize twice when the status changes to green twice`, (done) => {
- const pluginId = 'foo-plugin';
- const { mockXpackMainPlugin, mockFeature } = createMockXpackMainPluginAndFeature(pluginId);
- mockXpackMainPlugin.mock.setStatus('green');
- const licenseCheckResults = Symbol();
- mockFeature.mock.setLicenseCheckResults(licenseCheckResults);
- const downstreamPlugin = createMockDownstreamPlugin(pluginId);
- const initializeMock = jest.fn().mockImplementation(() => Promise.resolve());
-
- let count = 0;
- downstreamPlugin.status.green.mockImplementation((message) => {
- expect(message).toBe('Ready');
- ++count;
- if (count === 1) {
- mockXpackMainPlugin.mock.setStatus('green');
- }
- if (count === 2) {
- expect(initializeMock).toHaveBeenCalledWith(licenseCheckResults);
- expect(initializeMock).toHaveBeenCalledTimes(2);
- done();
- }
- });
-
- watchStatusAndLicenseToInitialize(mockXpackMainPlugin, downstreamPlugin, initializeMock);
-});
diff --git a/x-pack/legacy/server/lib/xpack_usage.js b/x-pack/legacy/server/lib/xpack_usage.js
deleted file mode 100644
index 50b50ba18c37f..0000000000000
--- a/x-pack/legacy/server/lib/xpack_usage.js
+++ /dev/null
@@ -1,16 +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 function xpackUsage(client) {
- /*
- * Get an object over the Usage API that as available/enabled data and some
- * select metadata for each of the X-Pack UI plugins
- */
- return client.transport.request({
- method: 'GET',
- path: '/_xpack/usage',
- });
-}
diff --git a/x-pack/plugins/actions/server/constants/plugin.ts b/x-pack/plugins/actions/server/constants/plugin.ts
index 7d20eb6990247..b82464bd92a18 100644
--- a/x-pack/plugins/actions/server/constants/plugin.ts
+++ b/x-pack/plugins/actions/server/constants/plugin.ts
@@ -4,11 +4,11 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { LICENSE_TYPE_BASIC, LicenseType } from '../../../../legacy/common/constants';
+import { LicenseType } from '../../../licensing/server';
export const PLUGIN = {
ID: 'actions',
- MINIMUM_LICENSE_REQUIRED: LICENSE_TYPE_BASIC as LicenseType, // TODO: supposed to be changed up on requirements
+ MINIMUM_LICENSE_REQUIRED: 'basic' as LicenseType, // TODO: supposed to be changed up on requirements
// eslint-disable-next-line @typescript-eslint/no-explicit-any
getI18nName: (i18n: any): string =>
i18n.translate('xpack.actions.appName', {
diff --git a/x-pack/plugins/alerts/server/constants/plugin.ts b/x-pack/plugins/alerts/server/constants/plugin.ts
index c180b68680841..4e1e0c59e0b48 100644
--- a/x-pack/plugins/alerts/server/constants/plugin.ts
+++ b/x-pack/plugins/alerts/server/constants/plugin.ts
@@ -4,11 +4,11 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { LICENSE_TYPE_BASIC, LicenseType } from '../../../../legacy/common/constants';
+import { LicenseType } from '../../../licensing/server';
export const PLUGIN = {
ID: 'alerts',
- MINIMUM_LICENSE_REQUIRED: LICENSE_TYPE_BASIC as LicenseType, // TODO: supposed to be changed up on requirements
+ MINIMUM_LICENSE_REQUIRED: 'basic' as LicenseType, // TODO: supposed to be changed up on requirements
// all plugins seem to use getI18nName with any
// eslint-disable-next-line @typescript-eslint/no-explicit-any
getI18nName: (i18n: any): string =>
diff --git a/x-pack/plugins/apm/common/service_health_status.ts b/x-pack/plugins/apm/common/service_health_status.ts
index 468f06ab97af8..1d4bcfb3b0e07 100644
--- a/x-pack/plugins/apm/common/service_health_status.ts
+++ b/x-pack/plugins/apm/common/service_health_status.ts
@@ -7,7 +7,7 @@
import { i18n } from '@kbn/i18n';
import { ANOMALY_SEVERITY } from '../../ml/common';
-import { EuiTheme } from '../../../legacy/common/eui_styled_components';
+import { EuiTheme } from '../../xpack_legacy/common';
export enum ServiceHealthStatus {
healthy = 'healthy',
diff --git a/x-pack/plugins/apm/e2e/cypress/integration/csm_dashboard.feature b/x-pack/plugins/apm/e2e/cypress/integration/csm_dashboard.feature
index ac4188a598458..7b894b6ca7aac 100644
--- a/x-pack/plugins/apm/e2e/cypress/integration/csm_dashboard.feature
+++ b/x-pack/plugins/apm/e2e/cypress/integration/csm_dashboard.feature
@@ -27,3 +27,11 @@ Feature: CSM Dashboard
Given a user clicks the page load breakdown filter
When the user selected the breakdown
Then breakdown series should appear in chart
+
+ Scenario: Search by url filter focus
+ When a user clicks inside url search field
+ Then it displays top pages in the suggestion popover
+
+ Scenario: Search by url filter
+ When a user enters a query in url search field
+ Then it should filter results based on query
diff --git a/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/url_search_filter.ts b/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/url_search_filter.ts
new file mode 100644
index 0000000000000..3b5dd70065055
--- /dev/null
+++ b/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/url_search_filter.ts
@@ -0,0 +1,65 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { When, Then } from 'cypress-cucumber-preprocessor/steps';
+import { DEFAULT_TIMEOUT } from './csm_dashboard';
+
+When(`a user clicks inside url search field`, () => {
+ // wait for all loading to finish
+ cy.get('kbnLoadingIndicator').should('not.be.visible');
+ cy.get('.euiStat__title-isLoading').should('not.be.visible');
+ cy.get('span[data-cy=csmUrlFilter]', DEFAULT_TIMEOUT).within(() => {
+ cy.get('input.euiFieldSearch').click();
+ });
+});
+
+Then(`it displays top pages in the suggestion popover`, () => {
+ cy.get('kbnLoadingIndicator').should('not.be.visible');
+
+ cy.get('div.euiPopover__panel-isOpen', DEFAULT_TIMEOUT).within(() => {
+ const listOfUrls = cy.get('li.euiSelectableListItem');
+ listOfUrls.should('have.length', 5);
+
+ const actualUrlsText = [
+ 'http://opbeans-node:3000/dashboardPage views: 17Page load duration: 109 ms ',
+ 'http://opbeans-node:3000/ordersPage views: 14Page load duration: 72 ms',
+ ];
+
+ cy.get('li.euiSelectableListItem')
+ .eq(0)
+ .should('have.text', actualUrlsText[0]);
+ cy.get('li.euiSelectableListItem')
+ .eq(1)
+ .should('have.text', actualUrlsText[1]);
+ });
+});
+
+When(`a user enters a query in url search field`, () => {
+ cy.get('kbnLoadingIndicator').should('not.be.visible');
+
+ cy.get('[data-cy=csmUrlFilter]').within(() => {
+ cy.get('input.euiSelectableSearch').type('cus');
+ });
+
+ cy.get('kbnLoadingIndicator').should('not.be.visible');
+});
+
+Then(`it should filter results based on query`, () => {
+ cy.get('kbnLoadingIndicator').should('not.be.visible');
+
+ cy.get('div.euiPopover__panel-isOpen', DEFAULT_TIMEOUT).within(() => {
+ const listOfUrls = cy.get('li.euiSelectableListItem');
+ listOfUrls.should('have.length', 1);
+
+ const actualUrlsText = [
+ 'http://opbeans-node:3000/customersPage views: 10Page load duration: 76 ms ',
+ ];
+
+ cy.get('li.euiSelectableListItem')
+ .eq(0)
+ .should('have.text', actualUrlsText[0]);
+ });
+});
diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/ClientMetrics/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/ClientMetrics/index.tsx
index 1edfd724dadd7..a77d27c4bc883 100644
--- a/x-pack/plugins/apm/public/components/app/RumDashboard/ClientMetrics/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/RumDashboard/ClientMetrics/index.tsx
@@ -22,7 +22,7 @@ const ClFlexGroup = styled(EuiFlexGroup)`
export function ClientMetrics() {
const { urlParams, uiFilters } = useUrlParams();
- const { start, end } = urlParams;
+ const { start, end, searchTerm } = urlParams;
const { data, status } = useFetcher(
(callApmApi) => {
@@ -31,13 +31,18 @@ export function ClientMetrics() {
return callApmApi({
pathname: '/api/apm/rum/client-metrics',
params: {
- query: { start, end, uiFilters: JSON.stringify(uiFilters) },
+ query: {
+ start,
+ end,
+ uiFilters: JSON.stringify(uiFilters),
+ urlQuery: searchTerm,
+ },
},
});
}
return Promise.resolve(null);
},
- [start, end, uiFilters]
+ [start, end, uiFilters, searchTerm]
);
const STAT_STYLE = { width: '240px' };
diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/index.tsx
index f97db3b42eecb..c8e45d2b2f672 100644
--- a/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/index.tsx
@@ -22,7 +22,7 @@ export interface PercentileRange {
export function PageLoadDistribution() {
const { urlParams, uiFilters } = useUrlParams();
- const { start, end } = urlParams;
+ const { start, end, searchTerm } = urlParams;
const [percentileRange, setPercentileRange] = useState({
min: null,
@@ -41,6 +41,7 @@ export function PageLoadDistribution() {
start,
end,
uiFilters: JSON.stringify(uiFilters),
+ urlQuery: searchTerm,
...(percentileRange.min && percentileRange.max
? {
minPercentile: String(percentileRange.min),
@@ -53,7 +54,14 @@ export function PageLoadDistribution() {
}
return Promise.resolve(null);
},
- [end, start, uiFilters, percentileRange.min, percentileRange.max]
+ [
+ end,
+ start,
+ uiFilters,
+ percentileRange.min,
+ percentileRange.max,
+ searchTerm,
+ ]
);
const onPercentileChange = (min: number, max: number) => {
diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/use_breakdowns.ts b/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/use_breakdowns.ts
index 814cf977c9569..d6a544333531f 100644
--- a/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/use_breakdowns.ts
+++ b/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/use_breakdowns.ts
@@ -17,7 +17,7 @@ interface Props {
export const useBreakdowns = ({ percentileRange, field, value }: Props) => {
const { urlParams, uiFilters } = useUrlParams();
- const { start, end } = urlParams;
+ const { start, end, searchTerm } = urlParams;
const { min: minP, max: maxP } = percentileRange ?? {};
@@ -32,6 +32,7 @@ export const useBreakdowns = ({ percentileRange, field, value }: Props) => {
end,
breakdown: value,
uiFilters: JSON.stringify(uiFilters),
+ urlQuery: searchTerm,
...(minP && maxP
? {
minPercentile: String(minP),
@@ -43,6 +44,6 @@ export const useBreakdowns = ({ percentileRange, field, value }: Props) => {
});
}
},
- [end, start, uiFilters, field, value, minP, maxP]
+ [end, start, uiFilters, field, value, minP, maxP, searchTerm]
);
};
diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/PageViewsTrend/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/PageViewsTrend/index.tsx
index 2991f9a15f085..f2da0955412e7 100644
--- a/x-pack/plugins/apm/public/components/app/RumDashboard/PageViewsTrend/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/RumDashboard/PageViewsTrend/index.tsx
@@ -16,7 +16,7 @@ import { BreakdownItem } from '../../../../../typings/ui_filters';
export function PageViewsTrend() {
const { urlParams, uiFilters } = useUrlParams();
- const { start, end } = urlParams;
+ const { start, end, searchTerm } = urlParams;
const [breakdown, setBreakdown] = useState(null);
@@ -30,6 +30,7 @@ export function PageViewsTrend() {
start,
end,
uiFilters: JSON.stringify(uiFilters),
+ urlQuery: searchTerm,
...(breakdown
? {
breakdowns: JSON.stringify(breakdown),
@@ -41,7 +42,7 @@ export function PageViewsTrend() {
}
return Promise.resolve(undefined);
},
- [end, start, uiFilters, breakdown]
+ [end, start, uiFilters, breakdown, searchTerm]
);
return (
diff --git a/x-pack/plugins/apm/public/components/shared/LocalUIFilters/ServiceNameFilter/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/ServiceNameFilter/index.tsx
similarity index 94%
rename from x-pack/plugins/apm/public/components/shared/LocalUIFilters/ServiceNameFilter/index.tsx
rename to x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/ServiceNameFilter/index.tsx
index cbf9ba009dce2..f10c9e888a193 100644
--- a/x-pack/plugins/apm/public/components/shared/LocalUIFilters/ServiceNameFilter/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/ServiceNameFilter/index.tsx
@@ -13,8 +13,8 @@ import {
import { i18n } from '@kbn/i18n';
import React, { useEffect, useCallback } from 'react';
import { useHistory } from 'react-router-dom';
-import { useUrlParams } from '../../../../hooks/useUrlParams';
-import { fromQuery, toQuery } from '../../Links/url_helpers';
+import { useUrlParams } from '../../../../../hooks/useUrlParams';
+import { fromQuery, toQuery } from '../../../../shared/Links/url_helpers';
interface Props {
serviceNames: string[];
diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/RenderOption.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/RenderOption.tsx
new file mode 100644
index 0000000000000..1a6f4e25fc315
--- /dev/null
+++ b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/RenderOption.tsx
@@ -0,0 +1,68 @@
+/*
+ * 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 React, { ReactNode } from 'react';
+import classNames from 'classnames';
+import { EuiHighlight, EuiSelectableOption } from '@elastic/eui';
+import styled from 'styled-components';
+import euiLightVars from '@elastic/eui/dist/eui_theme_light.json';
+
+const StyledSpan = styled.span`
+ color: ${euiLightVars.euiColorSecondaryText};
+ font-weight: 500;
+ :not(:last-of-type)::after {
+ content: '•';
+ margin: 0 4px;
+ }
+`;
+
+const StyledListSpan = styled.span`
+ display: block;
+ margin-top: 4px;
+ font-size: 12px;
+`;
+export type UrlOption = {
+ meta?: string[];
+} & EuiSelectableOption;
+
+export const formatOptions = (options: EuiSelectableOption[]) => {
+ return options.map((item: EuiSelectableOption) => ({
+ title: item.label,
+ ...item,
+ className: classNames(
+ 'euiSelectableTemplateSitewide__listItem',
+ item.className
+ ),
+ }));
+};
+
+export function selectableRenderOptions(
+ option: UrlOption,
+ searchValue: string
+) {
+ return (
+ <>
+
+ {option.label}
+
+ {renderOptionMeta(option.meta)}
+ >
+ );
+}
+
+function renderOptionMeta(meta?: string[]): ReactNode {
+ if (!meta || meta.length < 1) return;
+ return (
+
+ {meta.map((item: string) => (
+ {item}
+ ))}
+
+ );
+}
diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/SelectableUrlList.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/SelectableUrlList.tsx
new file mode 100644
index 0000000000000..298ec15b8480b
--- /dev/null
+++ b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/SelectableUrlList.tsx
@@ -0,0 +1,164 @@
+/*
+ * 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 React, { FormEvent, useRef, useState } from 'react';
+import {
+ EuiButtonEmpty,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiLoadingSpinner,
+ EuiPopover,
+ EuiPopoverTitle,
+ EuiSelectable,
+ EuiSelectableMessage,
+} from '@elastic/eui';
+import {
+ formatOptions,
+ selectableRenderOptions,
+ UrlOption,
+} from './RenderOption';
+import { I18LABELS } from '../../translations';
+
+interface Props {
+ data: {
+ items: UrlOption[];
+ total?: number;
+ };
+ loading: boolean;
+ onInputChange: (e: FormEvent) => void;
+ onTermChange: () => void;
+ onChange: (updatedOptions: UrlOption[]) => void;
+ searchValue: string;
+ onClose: () => void;
+}
+
+export function SelectableUrlList({
+ data,
+ loading,
+ onInputChange,
+ onTermChange,
+ onChange,
+ searchValue,
+ onClose,
+}: Props) {
+ const [popoverIsOpen, setPopoverIsOpen] = useState(false);
+ const [popoverRef, setPopoverRef] = useState(null);
+ const [searchRef, setSearchRef] = useState(null);
+
+ const titleRef = useRef(null);
+
+ const searchOnFocus = (e: React.FocusEvent) => {
+ setPopoverIsOpen(true);
+ };
+
+ const onSearchInput = (e: React.FormEvent) => {
+ onInputChange(e);
+ setPopoverIsOpen(true);
+ };
+
+ const searchOnBlur = (e: React.FocusEvent) => {
+ if (
+ !popoverRef?.contains(e.relatedTarget as HTMLElement) &&
+ !popoverRef?.contains(titleRef.current as HTMLDivElement)
+ ) {
+ setPopoverIsOpen(false);
+ }
+ };
+
+ const formattedOptions = formatOptions(data.items ?? []);
+
+ const closePopover = () => {
+ setPopoverIsOpen(false);
+ onClose();
+ if (searchRef) {
+ searchRef.blur();
+ }
+ };
+
+ const loadingMessage = (
+
+
+
+ {I18LABELS.loadingResults}
+
+ );
+
+ const emptyMessage = (
+
+ {I18LABELS.noResults}
+
+ );
+
+ const titleText = searchValue
+ ? I18LABELS.getSearchResultsLabel(data?.total ?? 0)
+ : I18LABELS.topPages;
+
+ function PopOverTitle() {
+ return (
+
+
+
+ {loading ? : titleText}
+
+
+ {
+ onTermChange();
+ setPopoverIsOpen(false);
+ }}
+ >
+ {I18LABELS.matchThisQuery}
+
+
+
+
+ );
+ }
+
+ return (
+
+ {(list, search) => (
+
+
+
+ )}
+
+ );
+}
diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/index.tsx
new file mode 100644
index 0000000000000..b88cf29740dcd
--- /dev/null
+++ b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/index.tsx
@@ -0,0 +1,132 @@
+/*
+ * 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 { EuiTitle } from '@elastic/eui';
+import useDebounce from 'react-use/lib/useDebounce';
+import React, { useEffect, useState, FormEvent, useCallback } from 'react';
+import { useHistory } from 'react-router-dom';
+import { useUrlParams } from '../../../../../hooks/useUrlParams';
+import { useFetcher } from '../../../../../hooks/useFetcher';
+import { I18LABELS } from '../../translations';
+import { fromQuery, toQuery } from '../../../../shared/Links/url_helpers';
+import { formatToSec } from '../../UXMetrics/KeyUXMetrics';
+import { SelectableUrlList } from './SelectableUrlList';
+import { UrlOption } from './RenderOption';
+
+interface Props {
+ onChange: (value: string[]) => void;
+}
+
+export function URLSearch({ onChange: onFilterChange }: Props) {
+ const history = useHistory();
+
+ const { urlParams, uiFilters } = useUrlParams();
+
+ const { start, end, serviceName } = urlParams;
+ const [searchValue, setSearchValue] = useState('');
+
+ const [debouncedValue, setDebouncedValue] = useState('');
+
+ useDebounce(
+ () => {
+ setSearchValue(debouncedValue);
+ },
+ 250,
+ [debouncedValue]
+ );
+
+ const updateSearchTerm = useCallback(
+ (searchTermN: string) => {
+ const newLocation = {
+ ...history.location,
+ search: fromQuery({
+ ...toQuery(history.location.search),
+ searchTerm: searchTermN,
+ }),
+ };
+ history.push(newLocation);
+ },
+ [history]
+ );
+
+ const [checkedUrls, setCheckedUrls] = useState([]);
+
+ const { data, status } = useFetcher(
+ (callApmApi) => {
+ if (start && end && serviceName) {
+ const { transactionUrl, ...restFilters } = uiFilters;
+
+ return callApmApi({
+ pathname: '/api/apm/rum-client/url-search',
+ params: {
+ query: {
+ start,
+ end,
+ uiFilters: JSON.stringify(restFilters),
+ urlQuery: searchValue,
+ },
+ },
+ });
+ }
+ return Promise.resolve(null);
+ },
+ [start, end, serviceName, uiFilters, searchValue]
+ );
+
+ useEffect(() => {
+ setCheckedUrls(uiFilters.transactionUrl || []);
+ }, [uiFilters]);
+
+ const onChange = (updatedOptions: UrlOption[]) => {
+ const clickedItems = updatedOptions.filter(
+ (option) => option.checked === 'on'
+ );
+
+ setCheckedUrls(clickedItems.map((item) => item.url));
+ };
+
+ const items: UrlOption[] = (data?.items ?? []).map((item) => ({
+ label: item.url,
+ key: item.url,
+ meta: [
+ I18LABELS.pageViews + ': ' + item.count,
+ I18LABELS.pageLoadDuration + ': ' + formatToSec(item.pld),
+ ],
+ url: item.url,
+ checked: checkedUrls?.includes(item.url) ? 'on' : undefined,
+ }));
+
+ const onInputChange = (e: FormEvent) => {
+ setDebouncedValue(e.currentTarget.value);
+ };
+
+ const isLoading = status !== 'success';
+
+ const onTermChange = () => {
+ updateSearchTerm(searchValue);
+ };
+
+ const onClose = () => {
+ onFilterChange(checkedUrls);
+ };
+
+ return (
+ <>
+
+ {I18LABELS.url}
+
+
+ >
+ );
+}
diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/UrlList.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/UrlList.tsx
new file mode 100644
index 0000000000000..437c005db37b0
--- /dev/null
+++ b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/UrlList.tsx
@@ -0,0 +1,74 @@
+/*
+ * 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 React from 'react';
+import { EuiFlexGrid, EuiFlexItem, EuiBadge } from '@elastic/eui';
+import styled from 'styled-components';
+import { i18n } from '@kbn/i18n';
+import { px, truncate, unit } from '../../../../style/variables';
+
+const BadgeText = styled.div`
+ display: inline-block;
+ ${truncate(px(unit * 12))};
+ vertical-align: middle;
+`;
+
+interface Props {
+ value: string[];
+ onRemove: (val: string) => void;
+}
+
+const formatUrlValue = (val: string) => {
+ const maxUrlToDisplay = 30;
+ const urlLength = val.length;
+ if (urlLength < maxUrlToDisplay) {
+ return val;
+ }
+ const urlObj = new URL(val);
+ if (urlObj.pathname === '/') {
+ return val;
+ }
+ const domainVal = urlObj.hostname;
+ const extraLength = urlLength - maxUrlToDisplay;
+ const extraDomain = domainVal.substring(0, extraLength);
+
+ if (urlObj.pathname.length + 7 > maxUrlToDisplay) {
+ return val.replace(domainVal, '..');
+ }
+
+ return val.replace(extraDomain, '..');
+};
+
+const removeFilterLabel = i18n.translate(
+ 'xpack.apm.uifilter.badge.removeFilter',
+ { defaultMessage: 'Remove filter' }
+);
+
+export function UrlList({ onRemove, value }: Props) {
+ return (
+
+ {value.map((val) => (
+
+ {
+ onRemove(val);
+ }}
+ onClickAriaLabel={removeFilterLabel}
+ iconOnClick={() => {
+ onRemove(val);
+ }}
+ iconOnClickAriaLabel={removeFilterLabel}
+ iconType="cross"
+ iconSide="right"
+ >
+ {formatUrlValue(val)}
+
+
+ ))}
+
+ );
+}
diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/index.tsx
new file mode 100644
index 0000000000000..9d3c8d012871f
--- /dev/null
+++ b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/index.tsx
@@ -0,0 +1,102 @@
+/*
+ * 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 React, { useCallback, useMemo } from 'react';
+import { EuiSpacer, EuiBadge } from '@elastic/eui';
+import { useHistory } from 'react-router-dom';
+import { Projection } from '../../../../../common/projections';
+import { useLocalUIFilters } from '../../../../hooks/useLocalUIFilters';
+import { URLSearch } from './URLSearch';
+import { LocalUIFilters } from '../../../shared/LocalUIFilters';
+import { UrlList } from './UrlList';
+import { useUrlParams } from '../../../../hooks/useUrlParams';
+import { fromQuery, toQuery } from '../../../shared/Links/url_helpers';
+
+const removeSearchTermLabel = i18n.translate(
+ 'xpack.apm.uiFilter.url.removeSearchTerm',
+ { defaultMessage: 'Clear url query' }
+);
+
+export function URLFilter() {
+ const history = useHistory();
+
+ const {
+ urlParams: { searchTerm },
+ } = useUrlParams();
+
+ const localUIFiltersConfig = useMemo(() => {
+ const config: React.ComponentProps = {
+ filterNames: ['transactionUrl'],
+ projection: Projection.rumOverview,
+ };
+
+ return config;
+ }, []);
+
+ const { filters, setFilterValue } = useLocalUIFilters({
+ ...localUIFiltersConfig,
+ });
+
+ const updateSearchTerm = useCallback(
+ (searchTermN?: string) => {
+ const newLocation = {
+ ...history.location,
+ search: fromQuery({
+ ...toQuery(history.location.search),
+ searchTerm: searchTermN,
+ }),
+ };
+ history.push(newLocation);
+ },
+ [history]
+ );
+
+ const { name, value: filterValue } = filters[0];
+
+ return (
+
+
+ {
+ setFilterValue('transactionUrl', value);
+ }}
+ />
+
+ {searchTerm && (
+ <>
+ {
+ updateSearchTerm();
+ }}
+ onClickAriaLabel={removeSearchTermLabel}
+ iconOnClick={() => {
+ updateSearchTerm();
+ }}
+ iconOnClickAriaLabel={removeSearchTermLabel}
+ iconType="cross"
+ iconSide="right"
+ >
+ *{searchTerm}*
+
+
+ >
+ )}
+ {filterValue.length > 0 && (
+ {
+ setFilterValue(
+ name,
+ filterValue.filter((v) => val !== v)
+ );
+ }}
+ value={filterValue}
+ />
+ )}
+
+
+ );
+}
diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/KeyUXMetrics.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/KeyUXMetrics.tsx
index 5c9a636adec8f..1d8360872afba 100644
--- a/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/KeyUXMetrics.tsx
+++ b/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/KeyUXMetrics.tsx
@@ -38,7 +38,7 @@ interface Props {
export function KeyUXMetrics({ data, loading }: Props) {
const { urlParams, uiFilters } = useUrlParams();
- const { start, end, serviceName } = urlParams;
+ const { start, end, serviceName, searchTerm } = urlParams;
const { data: longTaskData, status } = useFetcher(
(callApmApi) => {
@@ -46,13 +46,18 @@ export function KeyUXMetrics({ data, loading }: Props) {
return callApmApi({
pathname: '/api/apm/rum-client/long-task-metrics',
params: {
- query: { start, end, uiFilters: JSON.stringify(uiFilters) },
+ query: {
+ start,
+ end,
+ uiFilters: JSON.stringify(uiFilters),
+ urlQuery: searchTerm,
+ },
},
});
}
return Promise.resolve(null);
},
- [start, end, serviceName, uiFilters]
+ [start, end, serviceName, uiFilters, searchTerm]
);
// Note: FCP value is in ms unit
diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/index.tsx
index 94c3acfaa9727..3c7b4e39401de 100644
--- a/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/index.tsx
@@ -33,7 +33,7 @@ export interface UXMetrics {
export function UXMetrics() {
const { urlParams, uiFilters } = useUrlParams();
- const { start, end } = urlParams;
+ const { start, end, searchTerm } = urlParams;
const { data, status } = useFetcher(
(callApmApi) => {
@@ -42,13 +42,18 @@ export function UXMetrics() {
return callApmApi({
pathname: '/api/apm/rum-client/web-core-vitals',
params: {
- query: { start, end, uiFilters: JSON.stringify(uiFilters) },
+ query: {
+ start,
+ end,
+ uiFilters: JSON.stringify(uiFilters),
+ urlQuery: searchTerm,
+ },
},
});
}
return Promise.resolve(null);
},
- [start, end, uiFilters]
+ [start, end, uiFilters, searchTerm]
);
return (
diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdown/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdown/index.tsx
index 245f58370d3d7..2db6ef8fa6c06 100644
--- a/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdown/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdown/index.tsx
@@ -14,7 +14,7 @@ import { useUrlParams } from '../../../../hooks/useUrlParams';
export function VisitorBreakdown() {
const { urlParams, uiFilters } = useUrlParams();
- const { start, end } = urlParams;
+ const { start, end, searchTerm } = urlParams;
const { data, status } = useFetcher(
(callApmApi) => {
@@ -26,13 +26,14 @@ export function VisitorBreakdown() {
start,
end,
uiFilters: JSON.stringify(uiFilters),
+ urlQuery: searchTerm,
},
},
});
}
return Promise.resolve(null);
},
- [end, start, uiFilters]
+ [end, start, uiFilters, searchTerm]
);
return (
diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/index.tsx
index fa0551252b6a1..588831d55771d 100644
--- a/x-pack/plugins/apm/public/components/app/RumDashboard/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/RumDashboard/index.tsx
@@ -12,14 +12,15 @@ import {
EuiSpacer,
} from '@elastic/eui';
import { useTrackPageview } from '../../../../../observability/public';
-import { LocalUIFilters } from '../../shared/LocalUIFilters';
import { Projection } from '../../../../common/projections';
import { RumDashboard } from './RumDashboard';
-import { ServiceNameFilter } from '../../shared/LocalUIFilters/ServiceNameFilter';
import { useUrlParams } from '../../../hooks/useUrlParams';
import { useFetcher } from '../../../hooks/useFetcher';
import { RUM_AGENTS } from '../../../../common/agent_name';
import { EnvironmentFilter } from '../../shared/EnvironmentFilter';
+import { URLFilter } from './URLFilter';
+import { LocalUIFilters } from '../../shared/LocalUIFilters';
+import { ServiceNameFilter } from './URLFilter/ServiceNameFilter';
export function RumOverview() {
useTrackPageview({ app: 'apm', path: 'rum_overview' });
@@ -27,7 +28,7 @@ export function RumOverview() {
const localUIFiltersConfig = useMemo(() => {
const config: React.ComponentProps = {
- filterNames: ['transactionUrl', 'location', 'device', 'os', 'browser'],
+ filterNames: ['location', 'device', 'os', 'browser'],
projection: Projection.rumOverview,
};
@@ -63,6 +64,7 @@ export function RumOverview() {
+
<>
+
{' '}
>
diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/translations.ts b/x-pack/plugins/apm/public/components/app/RumDashboard/translations.ts
index 1fafb7d1ed4d0..714788ef468c6 100644
--- a/x-pack/plugins/apm/public/components/app/RumDashboard/translations.ts
+++ b/x-pack/plugins/apm/public/components/app/RumDashboard/translations.ts
@@ -79,6 +79,32 @@ export const I18LABELS = {
defaultMessage: 'Page load duration by region',
}
),
+ searchByUrl: i18n.translate('xpack.apm.rum.filters.searchByUrl', {
+ defaultMessage: 'Search by url',
+ }),
+ getSearchResultsLabel: (total: number) =>
+ i18n.translate('xpack.apm.rum.filters.searchResults', {
+ defaultMessage: '{total} Search results',
+ values: { total },
+ }),
+ topPages: i18n.translate('xpack.apm.rum.filters.topPages', {
+ defaultMessage: 'Top pages',
+ }),
+ select: i18n.translate('xpack.apm.rum.filters.select', {
+ defaultMessage: 'Select',
+ }),
+ url: i18n.translate('xpack.apm.rum.filters.url', {
+ defaultMessage: 'Url',
+ }),
+ matchThisQuery: i18n.translate('xpack.apm.rum.filters.url.matchThisQuery', {
+ defaultMessage: 'Match this query',
+ }),
+ loadingResults: i18n.translate('xpack.apm.rum.filters.url.loadingResults', {
+ defaultMessage: 'Loading results',
+ }),
+ noResults: i18n.translate('xpack.apm.rum.filters.url.noResults', {
+ defaultMessage: 'No results available',
+ }),
};
export const VisitorBreakdownLabel = i18n.translate(
diff --git a/x-pack/plugins/apm/server/lib/rum_client/get_client_metrics.ts b/x-pack/plugins/apm/server/lib/rum_client/get_client_metrics.ts
index b3f9646f64029..cf4a5538a208d 100644
--- a/x-pack/plugins/apm/server/lib/rum_client/get_client_metrics.ts
+++ b/x-pack/plugins/apm/server/lib/rum_client/get_client_metrics.ts
@@ -19,11 +19,14 @@ import {
export async function getClientMetrics({
setup,
+ urlQuery,
}: {
setup: Setup & SetupTimeRange & SetupUIFilters;
+ urlQuery?: string;
}) {
const projection = getRumPageLoadTransactionsProjection({
setup,
+ urlQuery,
});
const params = mergeProjection(projection, {
diff --git a/x-pack/plugins/apm/server/lib/rum_client/get_long_task_metrics.ts b/x-pack/plugins/apm/server/lib/rum_client/get_long_task_metrics.ts
index 1faee52034580..812cf9865bda8 100644
--- a/x-pack/plugins/apm/server/lib/rum_client/get_long_task_metrics.ts
+++ b/x-pack/plugins/apm/server/lib/rum_client/get_long_task_metrics.ts
@@ -14,12 +14,17 @@ import {
SetupTimeRange,
SetupUIFilters,
} from '../helpers/setup_request';
-import { SPAN_DURATION } from '../../../common/elasticsearch_fieldnames';
+import {
+ SPAN_DURATION,
+ TRANSACTION_ID,
+} from '../../../common/elasticsearch_fieldnames';
export async function getLongTaskMetrics({
setup,
+ urlQuery,
}: {
setup: Setup & SetupTimeRange & SetupUIFilters;
+ urlQuery?: string;
}) {
const projection = getRumLongTasksProjection({
setup,
@@ -28,9 +33,6 @@ export async function getLongTaskMetrics({
const params = mergeProjection(projection, {
body: {
size: 0,
- query: {
- bool: projection.body.query.bool,
- },
aggs: {
transIds: {
terms: {
@@ -59,10 +61,13 @@ export async function getLongTaskMetrics({
const response = await apmEventClient.search(params);
const { transIds } = response.aggregations ?? {};
- const validTransactions: string[] = await filterPageLoadTransactions(
+ const validTransactions: string[] = await filterPageLoadTransactions({
setup,
- (transIds?.buckets ?? []).map((bucket) => bucket.key as string)
- );
+ urlQuery,
+ transactionIds: (transIds?.buckets ?? []).map(
+ (bucket) => bucket.key as string
+ ),
+ });
let noOfLongTasks = 0;
let sumOfLongTasks = 0;
let longestLongTask = 0;
@@ -83,12 +88,18 @@ export async function getLongTaskMetrics({
};
}
-async function filterPageLoadTransactions(
- setup: Setup & SetupTimeRange & SetupUIFilters,
- transactionIds: string[]
-) {
+async function filterPageLoadTransactions({
+ setup,
+ urlQuery,
+ transactionIds,
+}: {
+ setup: Setup & SetupTimeRange & SetupUIFilters;
+ urlQuery?: string;
+ transactionIds: string[];
+}) {
const projection = getRumPageLoadTransactionsProjection({
setup,
+ urlQuery,
});
const params = mergeProjection(projection, {
@@ -99,14 +110,14 @@ async function filterPageLoadTransactions(
must: [
{
terms: {
- 'transaction.id': transactionIds,
+ [TRANSACTION_ID]: transactionIds,
},
},
],
filter: [...projection.body.query.bool.filter],
},
},
- _source: ['transaction.id'],
+ _source: [TRANSACTION_ID],
},
});
diff --git a/x-pack/plugins/apm/server/lib/rum_client/get_page_load_distribution.ts b/x-pack/plugins/apm/server/lib/rum_client/get_page_load_distribution.ts
index 3d8ab7a72654d..25de9f06fefc4 100644
--- a/x-pack/plugins/apm/server/lib/rum_client/get_page_load_distribution.ts
+++ b/x-pack/plugins/apm/server/lib/rum_client/get_page_load_distribution.ts
@@ -40,13 +40,16 @@ export async function getPageLoadDistribution({
setup,
minPercentile,
maxPercentile,
+ urlQuery,
}: {
setup: Setup & SetupTimeRange & SetupUIFilters;
minPercentile?: string;
maxPercentile?: string;
+ urlQuery?: string;
}) {
const projection = getRumPageLoadTransactionsProjection({
setup,
+ urlQuery,
});
const params = mergeProjection(projection, {
diff --git a/x-pack/plugins/apm/server/lib/rum_client/get_page_view_trends.ts b/x-pack/plugins/apm/server/lib/rum_client/get_page_view_trends.ts
index 543aa911b0b1f..ef4f8b16e0e7b 100644
--- a/x-pack/plugins/apm/server/lib/rum_client/get_page_view_trends.ts
+++ b/x-pack/plugins/apm/server/lib/rum_client/get_page_view_trends.ts
@@ -18,6 +18,7 @@ export async function getPageViewTrends({
}: {
setup: Setup & SetupTimeRange & SetupUIFilters;
breakdowns?: string;
+ urlQuery?: string;
}) {
const projection = getRumPageLoadTransactionsProjection({
setup,
diff --git a/x-pack/plugins/apm/server/lib/rum_client/get_pl_dist_breakdown.ts b/x-pack/plugins/apm/server/lib/rum_client/get_pl_dist_breakdown.ts
index 1945140e35777..d59817cc682a9 100644
--- a/x-pack/plugins/apm/server/lib/rum_client/get_pl_dist_breakdown.ts
+++ b/x-pack/plugins/apm/server/lib/rum_client/get_pl_dist_breakdown.ts
@@ -44,11 +44,13 @@ export const getPageLoadDistBreakdown = async ({
minDuration,
maxDuration,
breakdown,
+ urlQuery,
}: {
setup: Setup & SetupTimeRange & SetupUIFilters;
minDuration: number;
maxDuration: number;
breakdown: string;
+ urlQuery?: string;
}) => {
// convert secs to micros
const stepValues = getPLDChartSteps({
@@ -58,6 +60,7 @@ export const getPageLoadDistBreakdown = async ({
const projection = getRumPageLoadTransactionsProjection({
setup,
+ urlQuery,
});
const params = mergeProjection(projection, {
diff --git a/x-pack/plugins/apm/server/lib/rum_client/get_url_search.ts b/x-pack/plugins/apm/server/lib/rum_client/get_url_search.ts
new file mode 100644
index 0000000000000..a7117f275c17b
--- /dev/null
+++ b/x-pack/plugins/apm/server/lib/rum_client/get_url_search.ts
@@ -0,0 +1,67 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { mergeProjection } from '../../projections/util/merge_projection';
+import {
+ Setup,
+ SetupTimeRange,
+ SetupUIFilters,
+} from '../helpers/setup_request';
+import { getRumPageLoadTransactionsProjection } from '../../projections/rum_page_load_transactions';
+
+export async function getUrlSearch({
+ setup,
+ urlQuery,
+}: {
+ setup: Setup & SetupTimeRange & SetupUIFilters;
+ urlQuery?: string;
+}) {
+ const projection = getRumPageLoadTransactionsProjection({
+ setup,
+ urlQuery,
+ });
+
+ const params = mergeProjection(projection, {
+ body: {
+ size: 0,
+ aggs: {
+ totalUrls: {
+ cardinality: {
+ field: 'url.full',
+ },
+ },
+ urls: {
+ terms: {
+ field: 'url.full',
+ size: 10,
+ },
+ aggs: {
+ medianPLD: {
+ percentiles: {
+ field: 'transaction.duration.us',
+ percents: [50],
+ },
+ },
+ },
+ },
+ },
+ },
+ });
+
+ const { apmEventClient } = setup;
+
+ const response = await apmEventClient.search(params);
+ const { urls, totalUrls } = response.aggregations ?? {};
+
+ return {
+ total: totalUrls?.value || 0,
+ items: (urls?.buckets ?? []).map((bucket) => ({
+ url: bucket.key as string,
+ count: bucket.doc_count,
+ pld: bucket.medianPLD.values['50.0'] ?? 0,
+ })),
+ };
+}
diff --git a/x-pack/plugins/apm/server/lib/rum_client/get_visitor_breakdown.ts b/x-pack/plugins/apm/server/lib/rum_client/get_visitor_breakdown.ts
index 3493307929f42..1b4388afd7c5d 100644
--- a/x-pack/plugins/apm/server/lib/rum_client/get_visitor_breakdown.ts
+++ b/x-pack/plugins/apm/server/lib/rum_client/get_visitor_breakdown.ts
@@ -19,11 +19,14 @@ import {
export async function getVisitorBreakdown({
setup,
+ urlQuery,
}: {
setup: Setup & SetupTimeRange & SetupUIFilters;
+ urlQuery?: string;
}) {
const projection = getRumPageLoadTransactionsProjection({
setup,
+ urlQuery,
});
const params = mergeProjection(projection, {
diff --git a/x-pack/plugins/apm/server/lib/rum_client/get_web_core_vitals.ts b/x-pack/plugins/apm/server/lib/rum_client/get_web_core_vitals.ts
index 2ff0173b9ac12..fa34c2e25fecd 100644
--- a/x-pack/plugins/apm/server/lib/rum_client/get_web_core_vitals.ts
+++ b/x-pack/plugins/apm/server/lib/rum_client/get_web_core_vitals.ts
@@ -22,8 +22,10 @@ import {
export async function getWebCoreVitals({
setup,
+ urlQuery,
}: {
setup: Setup & SetupTimeRange & SetupUIFilters;
+ urlQuery?: string;
}) {
const projection = getRumPageLoadTransactionsProjection({
setup,
diff --git a/x-pack/plugins/apm/server/projections/rum_page_load_transactions.ts b/x-pack/plugins/apm/server/projections/rum_page_load_transactions.ts
index 27cd9b53f8349..3c3eaaca7efdb 100644
--- a/x-pack/plugins/apm/server/projections/rum_page_load_transactions.ts
+++ b/x-pack/plugins/apm/server/projections/rum_page_load_transactions.ts
@@ -19,8 +19,10 @@ import { TRANSACTION_PAGE_LOAD } from '../../common/transaction_types';
export function getRumPageLoadTransactionsProjection({
setup,
+ urlQuery,
}: {
setup: Setup & SetupTimeRange & SetupUIFilters;
+ urlQuery?: string;
}) {
const { start, end, uiFiltersES } = setup;
@@ -35,6 +37,17 @@ export function getRumPageLoadTransactionsProjection({
field: 'transaction.marks.navigationTiming.fetchStart',
},
},
+ ...(urlQuery
+ ? [
+ {
+ wildcard: {
+ 'url.full': {
+ value: `*${urlQuery}*`,
+ },
+ },
+ },
+ ]
+ : []),
...uiFiltersES,
],
};
diff --git a/x-pack/plugins/apm/server/routes/create_apm_api.ts b/x-pack/plugins/apm/server/routes/create_apm_api.ts
index 7d9a9ccc167e0..f975ab177f147 100644
--- a/x-pack/plugins/apm/server/routes/create_apm_api.ts
+++ b/x-pack/plugins/apm/server/routes/create_apm_api.ts
@@ -77,6 +77,7 @@ import {
rumServicesRoute,
rumVisitorsBreakdownRoute,
rumWebCoreVitals,
+ rumUrlSearch,
rumLongTaskMetrics,
} from './rum_client';
import {
@@ -173,6 +174,7 @@ const createApmApi = () => {
.add(rumServicesRoute)
.add(rumVisitorsBreakdownRoute)
.add(rumWebCoreVitals)
+ .add(rumUrlSearch)
.add(rumLongTaskMetrics)
// Observability dashboard
diff --git a/x-pack/plugins/apm/server/routes/rum_client.ts b/x-pack/plugins/apm/server/routes/rum_client.ts
index 179279b6f2d8a..e3a846f9fb5c7 100644
--- a/x-pack/plugins/apm/server/routes/rum_client.ts
+++ b/x-pack/plugins/apm/server/routes/rum_client.ts
@@ -16,37 +16,54 @@ import { getRumServices } from '../lib/rum_client/get_rum_services';
import { getVisitorBreakdown } from '../lib/rum_client/get_visitor_breakdown';
import { getWebCoreVitals } from '../lib/rum_client/get_web_core_vitals';
import { getLongTaskMetrics } from '../lib/rum_client/get_long_task_metrics';
+import { getUrlSearch } from '../lib/rum_client/get_url_search';
export const percentileRangeRt = t.partial({
minPercentile: t.string,
maxPercentile: t.string,
});
+const urlQueryRt = t.partial({ urlQuery: t.string });
+
export const rumClientMetricsRoute = createRoute(() => ({
path: '/api/apm/rum/client-metrics',
params: {
- query: t.intersection([uiFiltersRt, rangeRt]),
+ query: t.intersection([uiFiltersRt, rangeRt, urlQueryRt]),
},
handler: async ({ context, request }) => {
const setup = await setupRequest(context, request);
- return getClientMetrics({ setup });
+ const {
+ query: { urlQuery },
+ } = context.params;
+
+ return getClientMetrics({ setup, urlQuery });
},
}));
export const rumPageLoadDistributionRoute = createRoute(() => ({
path: '/api/apm/rum-client/page-load-distribution',
params: {
- query: t.intersection([uiFiltersRt, rangeRt, percentileRangeRt]),
+ query: t.intersection([
+ uiFiltersRt,
+ rangeRt,
+ percentileRangeRt,
+ urlQueryRt,
+ ]),
},
handler: async ({ context, request }) => {
const setup = await setupRequest(context, request);
const {
- query: { minPercentile, maxPercentile },
+ query: { minPercentile, maxPercentile, urlQuery },
} = context.params;
- return getPageLoadDistribution({ setup, minPercentile, maxPercentile });
+ return getPageLoadDistribution({
+ setup,
+ minPercentile,
+ maxPercentile,
+ urlQuery,
+ });
},
}));
@@ -57,6 +74,7 @@ export const rumPageLoadDistBreakdownRoute = createRoute(() => ({
uiFiltersRt,
rangeRt,
percentileRangeRt,
+ urlQueryRt,
t.type({ breakdown: t.string }),
]),
},
@@ -64,7 +82,7 @@ export const rumPageLoadDistBreakdownRoute = createRoute(() => ({
const setup = await setupRequest(context, request);
const {
- query: { minPercentile, maxPercentile, breakdown },
+ query: { minPercentile, maxPercentile, breakdown, urlQuery },
} = context.params;
return getPageLoadDistBreakdown({
@@ -72,6 +90,7 @@ export const rumPageLoadDistBreakdownRoute = createRoute(() => ({
minDuration: Number(minPercentile),
maxDuration: Number(maxPercentile),
breakdown,
+ urlQuery,
});
},
}));
@@ -82,6 +101,7 @@ export const rumPageViewsTrendRoute = createRoute(() => ({
query: t.intersection([
uiFiltersRt,
rangeRt,
+ urlQueryRt,
t.partial({ breakdowns: t.string }),
]),
},
@@ -89,10 +109,10 @@ export const rumPageViewsTrendRoute = createRoute(() => ({
const setup = await setupRequest(context, request);
const {
- query: { breakdowns },
+ query: { breakdowns, urlQuery },
} = context.params;
- return getPageViewTrends({ setup, breakdowns });
+ return getPageViewTrends({ setup, breakdowns, urlQuery });
},
}));
@@ -111,35 +131,63 @@ export const rumServicesRoute = createRoute(() => ({
export const rumVisitorsBreakdownRoute = createRoute(() => ({
path: '/api/apm/rum-client/visitor-breakdown',
params: {
- query: t.intersection([uiFiltersRt, rangeRt]),
+ query: t.intersection([uiFiltersRt, rangeRt, urlQueryRt]),
},
handler: async ({ context, request }) => {
const setup = await setupRequest(context, request);
- return getVisitorBreakdown({ setup });
+ const {
+ query: { urlQuery },
+ } = context.params;
+
+ return getVisitorBreakdown({ setup, urlQuery });
},
}));
export const rumWebCoreVitals = createRoute(() => ({
path: '/api/apm/rum-client/web-core-vitals',
params: {
- query: t.intersection([uiFiltersRt, rangeRt]),
+ query: t.intersection([uiFiltersRt, rangeRt, urlQueryRt]),
},
handler: async ({ context, request }) => {
const setup = await setupRequest(context, request);
- return getWebCoreVitals({ setup });
+ const {
+ query: { urlQuery },
+ } = context.params;
+
+ return getWebCoreVitals({ setup, urlQuery });
},
}));
export const rumLongTaskMetrics = createRoute(() => ({
path: '/api/apm/rum-client/long-task-metrics',
params: {
- query: t.intersection([uiFiltersRt, rangeRt]),
+ query: t.intersection([uiFiltersRt, rangeRt, urlQueryRt]),
},
handler: async ({ context, request }) => {
const setup = await setupRequest(context, request);
- return getLongTaskMetrics({ setup });
+ const {
+ query: { urlQuery },
+ } = context.params;
+
+ return getLongTaskMetrics({ setup, urlQuery });
+ },
+}));
+
+export const rumUrlSearch = createRoute(() => ({
+ path: '/api/apm/rum-client/url-search',
+ params: {
+ query: t.intersection([uiFiltersRt, rangeRt, urlQueryRt]),
+ },
+ handler: async ({ context, request }) => {
+ const setup = await setupRequest(context, request);
+
+ const {
+ query: { urlQuery },
+ } = context.params;
+
+ return getUrlSearch({ setup, urlQuery });
},
}));
diff --git a/x-pack/plugins/embeddable_enhanced/public/drilldowns/url_drilldown/README.md b/x-pack/plugins/drilldowns/url_drilldown/README.md
similarity index 65%
rename from x-pack/plugins/embeddable_enhanced/public/drilldowns/url_drilldown/README.md
rename to x-pack/plugins/drilldowns/url_drilldown/README.md
index 996723ccb914d..8eedc44ca35ae 100644
--- a/x-pack/plugins/embeddable_enhanced/public/drilldowns/url_drilldown/README.md
+++ b/x-pack/plugins/drilldowns/url_drilldown/README.md
@@ -1,24 +1,26 @@
-# Basic url drilldown implementation
+## URL drilldown
+
+> NOTE: This plugin contains implementation of URL drilldown. For drilldowns infrastructure code refer to `ui_actions_enhanced` plugin.
Url drilldown allows navigating to external URL or to internal kibana URL.
By using variables in url template result url can be dynamic and depend on user's interaction.
URL drilldown has 3 sources for variables:
-- Global static variables like, for example, `kibanaUrl`. Such variables won’t change depending on a place where url drilldown is used.
-- Context variables are dynamic and different depending on where drilldown is created and used.
-- Event variables depend on a trigger context. These variables are dynamically extracted from the action context when drilldown is executed.
+1. Global static variables like, for example, `kibanaUrl`. Such variables won’t change depending on a place where url drilldown is used.
+2. Context variables are dynamic and different depending on where drilldown is created and used.
+3. Event variables depend on a trigger context. These variables are dynamically extracted from the action context when drilldown is executed.
Difference between `event` and `context` variables, is that real `context` variables are available during drilldown creation (e.g. embeddable panel),
but `event` variables mapped from trigger context. Since there is no trigger context during drilldown creation, we have to provide some _mock_ variables for validating and previewing the URL.
In current implementation url drilldown has to be used inside the embeddable and with `ValueClickTrigger` or `RangeSelectTrigger`.
-- `context` variables extracted from `embeddable`
-- `event` variables extracted from `trigger` context
+* `context` variables extracted from `embeddable`
+* `event` variables extracted from `trigger` context
In future this basic url drilldown implementation would allow injecting more variables into `context` (e.g. `dashboard` app specific variables) and would allow providing support for new trigger types from outside.
This extensibility improvements are tracked here: https://github.com/elastic/kibana/issues/55324
In case a solution app has a use case for url drilldown that has to be different from current basic implementation and
-just extending variables list is not enough, then recommendation is to create own custom url drilldown and reuse building blocks from `ui_actions_enhanced`.
+just extending variables list is not enough, then recommendation is to create own custom url drilldown and reuse building blocks from `ui_actions_enhanced`.
\ No newline at end of file
diff --git a/x-pack/plugins/drilldowns/url_drilldown/kibana.json b/x-pack/plugins/drilldowns/url_drilldown/kibana.json
new file mode 100644
index 0000000000000..9bdd13fbfea26
--- /dev/null
+++ b/x-pack/plugins/drilldowns/url_drilldown/kibana.json
@@ -0,0 +1,8 @@
+{
+ "id": "urlDrilldown",
+ "version": "kibana",
+ "server": false,
+ "ui": true,
+ "requiredPlugins": ["embeddable", "uiActions", "uiActionsEnhanced"],
+ "requiredBundles": ["kibanaUtils", "kibanaReact"]
+}
diff --git a/x-pack/legacy/common/poller.d.ts b/x-pack/plugins/drilldowns/url_drilldown/public/index.ts
similarity index 54%
rename from x-pack/legacy/common/poller.d.ts
rename to x-pack/plugins/drilldowns/url_drilldown/public/index.ts
index df39d93a28a81..b040ef625bc1f 100644
--- a/x-pack/legacy/common/poller.d.ts
+++ b/x-pack/plugins/drilldowns/url_drilldown/public/index.ts
@@ -4,11 +4,9 @@
* you may not use this file except in compliance with the Elastic License.
*/
-export declare class Poller {
- constructor(options: any);
+import { PluginInitializerContext } from 'src/core/public';
+import { UrlDrilldownPlugin } from './plugin';
- public start(): void;
- public stop(): void;
- public isRunning(): boolean;
- public getPollFrequency(): number;
+export function plugin(context: PluginInitializerContext) {
+ return new UrlDrilldownPlugin(context);
}
diff --git a/x-pack/plugins/embeddable_enhanced/public/drilldowns/url_drilldown/i18n.ts b/x-pack/plugins/drilldowns/url_drilldown/public/lib/i18n.ts
similarity index 62%
rename from x-pack/plugins/embeddable_enhanced/public/drilldowns/url_drilldown/i18n.ts
rename to x-pack/plugins/drilldowns/url_drilldown/public/lib/i18n.ts
index 748f6f4cecedd..7e91c6b849035 100644
--- a/x-pack/plugins/embeddable_enhanced/public/drilldowns/url_drilldown/i18n.ts
+++ b/x-pack/plugins/drilldowns/url_drilldown/public/lib/i18n.ts
@@ -6,9 +6,6 @@
import { i18n } from '@kbn/i18n';
-export const txtUrlDrilldownDisplayName = i18n.translate(
- 'xpack.embeddableEnhanced.drilldowns.urlDrilldownDisplayName',
- {
- defaultMessage: 'Go to URL',
- }
-);
+export const txtUrlDrilldownDisplayName = i18n.translate('xpack.urlDrilldown.DisplayName', {
+ defaultMessage: 'Go to URL',
+});
diff --git a/x-pack/plugins/embeddable_enhanced/public/drilldowns/url_drilldown/index.ts b/x-pack/plugins/drilldowns/url_drilldown/public/lib/index.ts
similarity index 100%
rename from x-pack/plugins/embeddable_enhanced/public/drilldowns/url_drilldown/index.ts
rename to x-pack/plugins/drilldowns/url_drilldown/public/lib/index.ts
diff --git a/x-pack/plugins/embeddable_enhanced/public/drilldowns/url_drilldown/url_drilldown.test.ts b/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown.test.ts
similarity index 100%
rename from x-pack/plugins/embeddable_enhanced/public/drilldowns/url_drilldown/url_drilldown.test.ts
rename to x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown.test.ts
diff --git a/x-pack/plugins/embeddable_enhanced/public/drilldowns/url_drilldown/url_drilldown.tsx b/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown.tsx
similarity index 100%
rename from x-pack/plugins/embeddable_enhanced/public/drilldowns/url_drilldown/url_drilldown.tsx
rename to x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown.tsx
diff --git a/x-pack/plugins/embeddable_enhanced/public/drilldowns/url_drilldown/url_drilldown_scope.test.ts b/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown_scope.test.ts
similarity index 100%
rename from x-pack/plugins/embeddable_enhanced/public/drilldowns/url_drilldown/url_drilldown_scope.test.ts
rename to x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown_scope.test.ts
diff --git a/x-pack/plugins/embeddable_enhanced/public/drilldowns/url_drilldown/url_drilldown_scope.ts b/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown_scope.ts
similarity index 100%
rename from x-pack/plugins/embeddable_enhanced/public/drilldowns/url_drilldown/url_drilldown_scope.ts
rename to x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown_scope.ts
diff --git a/x-pack/plugins/drilldowns/url_drilldown/public/plugin.ts b/x-pack/plugins/drilldowns/url_drilldown/public/plugin.ts
new file mode 100644
index 0000000000000..82ce7a129f497
--- /dev/null
+++ b/x-pack/plugins/drilldowns/url_drilldown/public/plugin.ts
@@ -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 { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from 'src/core/public';
+import { EmbeddableSetup, EmbeddableStart } from '../../../../../src/plugins/embeddable/public';
+import {
+ AdvancedUiActionsSetup,
+ AdvancedUiActionsStart,
+ urlDrilldownGlobalScopeProvider,
+} from '../../../ui_actions_enhanced/public';
+import { UrlDrilldown } from './lib';
+import { createStartServicesGetter } from '../../../../../src/plugins/kibana_utils/public';
+
+export interface SetupDependencies {
+ embeddable: EmbeddableSetup;
+ uiActionsEnhanced: AdvancedUiActionsSetup;
+}
+
+export interface StartDependencies {
+ embeddable: EmbeddableStart;
+ uiActionsEnhanced: AdvancedUiActionsStart;
+}
+
+// eslint-disable-next-line
+export interface SetupContract {}
+
+// eslint-disable-next-line
+export interface StartContract {}
+
+export class UrlDrilldownPlugin
+ implements Plugin {
+ constructor(protected readonly context: PluginInitializerContext) {}
+
+ public setup(core: CoreSetup, plugins: SetupDependencies): SetupContract {
+ const startServices = createStartServicesGetter(core.getStartServices);
+ plugins.uiActionsEnhanced.registerDrilldown(
+ new UrlDrilldown({
+ getGlobalScope: urlDrilldownGlobalScopeProvider({ core }),
+ navigateToUrl: (url: string) =>
+ core.getStartServices().then(([{ application }]) => application.navigateToUrl(url)),
+ getSyntaxHelpDocsLink: () =>
+ startServices().core.docLinks.links.dashboard.urlDrilldownTemplateSyntax,
+ getVariablesHelpDocsLink: () =>
+ startServices().core.docLinks.links.dashboard.urlDrilldownVariables,
+ })
+ );
+
+ return {};
+ }
+
+ public start(core: CoreStart, plugins: StartDependencies): StartContract {
+ return {};
+ }
+
+ public stop() {}
+}
diff --git a/x-pack/plugins/embeddable_enhanced/kibana.json b/x-pack/plugins/embeddable_enhanced/kibana.json
index acada946fe0d1..8d49e3e26eb7b 100644
--- a/x-pack/plugins/embeddable_enhanced/kibana.json
+++ b/x-pack/plugins/embeddable_enhanced/kibana.json
@@ -3,6 +3,5 @@
"version": "kibana",
"server": false,
"ui": true,
- "requiredPlugins": ["embeddable", "kibanaReact", "uiActions", "uiActionsEnhanced"],
- "requiredBundles": ["kibanaUtils"]
+ "requiredPlugins": ["embeddable", "kibanaReact", "uiActions", "uiActionsEnhanced"]
}
diff --git a/x-pack/plugins/embeddable_enhanced/public/plugin.ts b/x-pack/plugins/embeddable_enhanced/public/plugin.ts
index 2138a372523b7..5d5ad852839d4 100644
--- a/x-pack/plugins/embeddable_enhanced/public/plugin.ts
+++ b/x-pack/plugins/embeddable_enhanced/public/plugin.ts
@@ -28,11 +28,8 @@ import {
UiActionsEnhancedDynamicActionManager as DynamicActionManager,
AdvancedUiActionsSetup,
AdvancedUiActionsStart,
- urlDrilldownGlobalScopeProvider,
} from '../../ui_actions_enhanced/public';
import { PanelNotificationsAction, ACTION_PANEL_NOTIFICATIONS } from './actions';
-import { UrlDrilldown } from './drilldowns';
-import { createStartServicesGetter } from '../../../../src/plugins/kibana_utils/public';
declare module '../../../../src/plugins/ui_actions/public' {
export interface ActionContextMapping {
@@ -64,23 +61,10 @@ export class EmbeddableEnhancedPlugin
public setup(core: CoreSetup, plugins: SetupDependencies): SetupContract {
this.setCustomEmbeddableFactoryProvider(plugins);
- const startServices = createStartServicesGetter(core.getStartServices);
const panelNotificationAction = new PanelNotificationsAction();
plugins.uiActionsEnhanced.registerAction(panelNotificationAction);
plugins.uiActionsEnhanced.attachAction(PANEL_NOTIFICATION_TRIGGER, panelNotificationAction.id);
- plugins.uiActionsEnhanced.registerDrilldown(
- new UrlDrilldown({
- getGlobalScope: urlDrilldownGlobalScopeProvider({ core }),
- navigateToUrl: (url: string) =>
- core.getStartServices().then(([{ application }]) => application.navigateToUrl(url)),
- getSyntaxHelpDocsLink: () =>
- startServices().core.docLinks.links.dashboard.urlDrilldownTemplateSyntax,
- getVariablesHelpDocsLink: () =>
- startServices().core.docLinks.links.dashboard.urlDrilldownVariables,
- })
- );
-
return {};
}
diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/metric_control/custom_metric_form.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/metric_control/custom_metric_form.tsx
index a785cb31c3cf4..262d94d8f3674 100644
--- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/metric_control/custom_metric_form.tsx
+++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/metric_control/custom_metric_form.tsx
@@ -27,10 +27,7 @@ import {
SNAPSHOT_CUSTOM_AGGREGATIONS,
SnapshotCustomAggregationRT,
} from '../../../../../../../common/http_api/snapshot_api';
-import {
- EuiTheme,
- withTheme,
-} from '../../../../../../../../../legacy/common/eui_styled_components';
+import { EuiTheme, withTheme } from '../../../../../../../../xpack_legacy/common';
interface SelectedOption {
label: string;
diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/metric_control/metrics_edit_mode.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/metric_control/metrics_edit_mode.tsx
index e75885ccbc917..831a0cde49cfb 100644
--- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/metric_control/metrics_edit_mode.tsx
+++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/metric_control/metrics_edit_mode.tsx
@@ -8,10 +8,7 @@ import { EuiFlexItem, EuiFlexGroup, EuiButtonIcon } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { getCustomMetricLabel } from '../../../../../../../common/formatters/get_custom_metric_label';
import { SnapshotCustomMetricInput } from '../../../../../../../common/http_api/snapshot_api';
-import {
- EuiTheme,
- withTheme,
-} from '../../../../../../../../../legacy/common/eui_styled_components';
+import { EuiTheme, withTheme } from '../../../../../../../../xpack_legacy/common';
interface Props {
theme: EuiTheme | undefined;
diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/metric_control/mode_switcher.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/metric_control/mode_switcher.tsx
index d1abcade5d660..956241545e8be 100644
--- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/metric_control/mode_switcher.tsx
+++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/metric_control/mode_switcher.tsx
@@ -9,10 +9,7 @@ import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import { CustomMetricMode } from './types';
import { SnapshotCustomMetricInput } from '../../../../../../../common/http_api/snapshot_api';
-import {
- EuiTheme,
- withTheme,
-} from '../../../../../../../../../legacy/common/eui_styled_components';
+import { EuiTheme, withTheme } from '../../../../../../../../xpack_legacy/common';
interface Props {
theme: EuiTheme | undefined;
diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/index.tsx
index 45ac538d9e394..0bef3c20ddd1a 100644
--- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/index.tsx
+++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/index.tsx
@@ -12,7 +12,7 @@ import { FormattedMessage } from '@kbn/i18n/react';
import styled from 'styled-components';
import { EuiErrorBoundary, EuiPanel, EuiEmptyPrompt, EuiCode } from '@elastic/eui';
import { CoreStart, AppMountParameters } from 'src/core/public';
-import { EuiThemeProvider } from '../../../../../legacy/common/eui_styled_components';
+import { EuiThemeProvider } from '../../../../xpack_legacy/common';
import {
IngestManagerSetupDeps,
IngestManagerConfigType,
diff --git a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipelines_clone.helpers.ts b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipelines_clone.helpers.ts
index f369bfe66f642..2559b93bd606d 100644
--- a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipelines_clone.helpers.ts
+++ b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipelines_clone.helpers.ts
@@ -5,10 +5,10 @@
*/
import { registerTestBed, TestBedConfig, TestBed } from '../../../../../test_utils';
-import { BASE_PATH } from '../../../common/constants';
import { PipelinesClone } from '../../../public/application/sections/pipelines_clone';
import { getFormActions, PipelineFormTestSubjects } from './pipeline_form.helpers';
import { WithAppDependencies } from './setup_environment';
+import { getClonePath, ROUTES } from '../../../public/application/services/navigation';
export type PipelinesCloneTestBed = TestBed & {
actions: ReturnType;
@@ -29,8 +29,8 @@ export const PIPELINE_TO_CLONE = {
const testBedConfig: TestBedConfig = {
memoryRouter: {
- initialEntries: [`${BASE_PATH}create/${PIPELINE_TO_CLONE.name}`],
- componentRoutePath: `${BASE_PATH}create/:name`,
+ initialEntries: [getClonePath({ clonedPipelineName: PIPELINE_TO_CLONE.name })],
+ componentRoutePath: ROUTES.clone,
},
doMountAsync: true,
};
diff --git a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipelines_create.helpers.ts b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipelines_create.helpers.ts
index ce5ab1faa01be..22f68f12804d6 100644
--- a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipelines_create.helpers.ts
+++ b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipelines_create.helpers.ts
@@ -5,10 +5,10 @@
*/
import { registerTestBed, TestBedConfig, TestBed } from '../../../../../test_utils';
-import { BASE_PATH } from '../../../common/constants';
import { PipelinesCreate } from '../../../public/application/sections/pipelines_create';
import { getFormActions, PipelineFormTestSubjects } from './pipeline_form.helpers';
import { WithAppDependencies } from './setup_environment';
+import { getCreatePath, ROUTES } from '../../../public/application/services/navigation';
export type PipelinesCreateTestBed = TestBed & {
actions: ReturnType;
@@ -16,8 +16,8 @@ export type PipelinesCreateTestBed = TestBed & {
const testBedConfig: TestBedConfig = {
memoryRouter: {
- initialEntries: [`${BASE_PATH}/create`],
- componentRoutePath: `${BASE_PATH}/create`,
+ initialEntries: [getCreatePath()],
+ componentRoutePath: ROUTES.create,
},
doMountAsync: true,
};
diff --git a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipelines_edit.helpers.ts b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipelines_edit.helpers.ts
index 31c9630086178..5e0739f78eecd 100644
--- a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipelines_edit.helpers.ts
+++ b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipelines_edit.helpers.ts
@@ -5,10 +5,10 @@
*/
import { registerTestBed, TestBedConfig, TestBed } from '../../../../../test_utils';
-import { BASE_PATH } from '../../../common/constants';
import { PipelinesEdit } from '../../../public/application/sections/pipelines_edit';
import { getFormActions, PipelineFormTestSubjects } from './pipeline_form.helpers';
import { WithAppDependencies } from './setup_environment';
+import { getEditPath, ROUTES } from '../../../public/application/services/navigation';
export type PipelinesEditTestBed = TestBed & {
actions: ReturnType;
@@ -29,8 +29,8 @@ export const PIPELINE_TO_EDIT = {
const testBedConfig: TestBedConfig = {
memoryRouter: {
- initialEntries: [`${BASE_PATH}edit/${PIPELINE_TO_EDIT.name}`],
- componentRoutePath: `${BASE_PATH}edit/:name`,
+ initialEntries: [getEditPath({ pipelineName: PIPELINE_TO_EDIT.name })],
+ componentRoutePath: ROUTES.edit,
},
doMountAsync: true,
};
diff --git a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipelines_list.helpers.ts b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipelines_list.helpers.ts
index 03ffe361bb5a6..43ca849e61aee 100644
--- a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipelines_list.helpers.ts
+++ b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipelines_list.helpers.ts
@@ -6,7 +6,6 @@
import { act } from 'react-dom/test-utils';
-import { BASE_PATH } from '../../../common/constants';
import {
registerTestBed,
TestBed,
@@ -16,11 +15,12 @@ import {
} from '../../../../../test_utils';
import { PipelinesList } from '../../../public/application/sections/pipelines_list';
import { WithAppDependencies } from './setup_environment';
+import { getListPath, ROUTES } from '../../../public/application/services/navigation';
const testBedConfig: TestBedConfig = {
memoryRouter: {
- initialEntries: [BASE_PATH],
- componentRoutePath: BASE_PATH,
+ initialEntries: [getListPath()],
+ componentRoutePath: ROUTES.list,
},
doMountAsync: true,
};
diff --git a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/ingest_pipelines_create.test.tsx b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/ingest_pipelines_create.test.tsx
index 6074c64d2bdb0..18ca71f2bb73a 100644
--- a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/ingest_pipelines_create.test.tsx
+++ b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/ingest_pipelines_create.test.tsx
@@ -185,30 +185,5 @@ describe(' ', () => {
expect(find('savePipelineError').find('li').length).toBe(8);
});
});
-
- describe('test pipeline', () => {
- beforeEach(async () => {
- await act(async () => {
- testBed = await setup();
-
- const { waitFor } = testBed;
-
- await waitFor('pipelineForm');
- });
- });
-
- test('should open the test pipeline flyout', async () => {
- const { actions, exists, find, waitFor } = testBed;
-
- await act(async () => {
- actions.clickAddDocumentsButton();
- await waitFor('testPipelineFlyout');
- });
-
- // Verify test pipeline flyout opens
- expect(exists('testPipelineFlyout')).toBe(true);
- expect(find('testPipelineFlyout.title').text()).toBe('Test pipeline');
- });
- });
});
});
diff --git a/x-pack/plugins/ingest_pipelines/common/constants.ts b/x-pack/plugins/ingest_pipelines/common/constants.ts
index 4c6c6fefaad83..0d6f977bfbfed 100644
--- a/x-pack/plugins/ingest_pipelines/common/constants.ts
+++ b/x-pack/plugins/ingest_pipelines/common/constants.ts
@@ -9,9 +9,9 @@ const basicLicense: LicenseType = 'basic';
export const PLUGIN_ID = 'ingest_pipelines';
-export const PLUGIN_MIN_LICENSE_TYPE = basicLicense;
+export const MANAGEMENT_APP_ID = 'management';
-export const BASE_PATH = '/';
+export const PLUGIN_MIN_LICENSE_TYPE = basicLicense;
export const API_BASE_PATH = '/api/ingest_pipelines';
diff --git a/x-pack/plugins/ingest_pipelines/kibana.json b/x-pack/plugins/ingest_pipelines/kibana.json
index 38d28fbba20b4..2fe87c5e7a162 100644
--- a/x-pack/plugins/ingest_pipelines/kibana.json
+++ b/x-pack/plugins/ingest_pipelines/kibana.json
@@ -3,7 +3,7 @@
"version": "8.0.0",
"server": true,
"ui": true,
- "requiredPlugins": ["licensing", "management", "features"],
+ "requiredPlugins": ["licensing", "management", "features", "share"],
"optionalPlugins": ["security", "usageCollection"],
"configPath": ["xpack", "ingest_pipelines"],
"requiredBundles": ["esUiShared", "kibanaReact"]
diff --git a/x-pack/plugins/ingest_pipelines/public/application/app.tsx b/x-pack/plugins/ingest_pipelines/public/application/app.tsx
index 55b59caab8d60..e78c4d3983183 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/app.tsx
+++ b/x-pack/plugins/ingest_pipelines/public/application/app.tsx
@@ -21,13 +21,14 @@ import {
} from '../shared_imports';
import { PipelinesList, PipelinesCreate, PipelinesEdit, PipelinesClone } from './sections';
+import { ROUTES } from './services/navigation';
export const AppWithoutRouter = () => (
-
-
-
-
+
+
+
+
{/* Catch all */}
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_form.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_form.tsx
index 5279bd718c16e..ffd82b0bbaf35 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_form.tsx
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_form.tsx
@@ -11,8 +11,6 @@ import { EuiButton, EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiSpacer } from
import { useForm, Form, FormConfig } from '../../../shared_imports';
import { Pipeline, Processor } from '../../../../common/types';
-import './pipeline_form.scss';
-
import { OnUpdateHandlerArg, OnUpdateHandler } from '../pipeline_processors_editor';
import { PipelineRequestFlyout } from './pipeline_request_flyout';
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_form_fields.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_form_fields.tsx
index 6033f34af6825..a7ffe7ba02caa 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_form_fields.tsx
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_form_fields.tsx
@@ -6,7 +6,7 @@
import React, { useState } from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
-import { EuiSpacer, EuiSwitch, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
+import { EuiSpacer, EuiSwitch } from '@elastic/eui';
import { Processor } from '../../../../common/types';
@@ -14,15 +14,11 @@ import { getUseField, getFormRow, Field } from '../../../shared_imports';
import {
ProcessorsEditorContextProvider,
- GlobalOnFailureProcessorsEditor,
- ProcessorsEditor,
OnUpdateHandler,
OnDoneLoadJsonHandler,
+ PipelineProcessorsEditor,
} from '../pipeline_processors_editor';
-import { ProcessorsHeader } from './processors_header';
-import { OnFailureProcessorsTitle } from './on_failure_processors_title';
-
interface Props {
processors: Processor[];
onFailure?: Processor[];
@@ -118,28 +114,12 @@ export const PipelineFormFields: React.FunctionComponent = ({
{/* Pipeline Processors Editor */}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
>
);
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/pipeline_processors_editor.helpers.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/pipeline_processors_editor.helpers.tsx
index e46e5156e30f3..10fb73df1ce1c 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/pipeline_processors_editor.helpers.tsx
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/pipeline_processors_editor.helpers.tsx
@@ -11,12 +11,7 @@ import { notificationServiceMock, scopedHistoryMock } from 'src/core/public/mock
import { LocationDescriptorObject } from 'history';
import { KibanaContextProvider } from 'src/plugins/kibana_react/public';
import { registerTestBed, TestBed } from '../../../../../../../test_utils';
-import {
- ProcessorsEditorContextProvider,
- Props,
- ProcessorsEditor,
- GlobalOnFailureProcessorsEditor,
-} from '../';
+import { ProcessorsEditorContextProvider, Props, PipelineProcessorsEditor } from '../';
import {
breadcrumbService,
@@ -90,7 +85,7 @@ const testBedSetup = registerTestBed(
(props: Props) => (
-
+
),
@@ -210,4 +205,5 @@ type TestSubject =
| 'processorSettingsFormFlyout'
| 'processorTypeSelector'
| 'pipelineEditorOnFailureTree'
+ | 'processorsEmptyPrompt'
| string;
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/pipeline_processors_editor.test.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/pipeline_processors_editor.test.tsx
index 74ae8b8894b9f..b80d238362118 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/pipeline_processors_editor.test.tsx
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/pipeline_processors_editor.test.tsx
@@ -55,6 +55,23 @@ describe('Pipeline Editor', () => {
expect(arg.getData()).toEqual(testProcessors);
});
+ describe('no processors', () => {
+ beforeEach(async () => {
+ testBed = await setup({
+ value: {
+ processors: [],
+ },
+ onFlyoutOpen: jest.fn(),
+ onUpdate,
+ });
+ });
+
+ it('displays an empty prompt if no processors are defined', () => {
+ const { exists } = testBed;
+ expect(exists('processorsEmptyPrompt')).toBe(true);
+ });
+ });
+
describe('processors', () => {
it('adds a new processor', async () => {
const { actions } = testBed;
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/add_processor_button.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/add_processor_button.tsx
index 4aabcc1d59d73..03b497320dfbc 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/add_processor_button.tsx
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/add_processor_button.tsx
@@ -6,30 +6,49 @@
import React, { FunctionComponent } from 'react';
import { i18n } from '@kbn/i18n';
-import { EuiButtonEmpty } from '@elastic/eui';
+import { EuiButtonEmpty, EuiButton } from '@elastic/eui';
import { usePipelineProcessorsContext } from '../context';
export interface Props {
onClick: () => void;
+ renderButtonAsLink?: boolean;
}
+const addProcessorButtonLabel = i18n.translate(
+ 'xpack.ingestPipelines.pipelineEditor.addProcessorButtonLabel',
+ {
+ defaultMessage: 'Add a processor',
+ }
+);
+
export const AddProcessorButton: FunctionComponent = (props) => {
- const { onClick } = props;
+ const { onClick, renderButtonAsLink } = props;
const {
state: { editor },
} = usePipelineProcessorsContext();
+
+ if (renderButtonAsLink) {
+ return (
+
+ {addProcessorButtonLabel}
+
+ );
+ }
+
return (
-
- {i18n.translate('xpack.ingestPipelines.pipelineEditor.addProcessorButtonLabel', {
- defaultMessage: 'Add a processor',
- })}
-
+ {addProcessorButtonLabel}
+
);
};
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/index.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/index.ts
index d476202aa43bb..2e62a81ffa153 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/index.ts
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/index.ts
@@ -19,3 +19,9 @@ export { OnDoneLoadJsonHandler, LoadFromJsonButton } from './load_from_json';
export { TestPipelineActions } from './test_pipeline';
export { PipelineProcessorsItemTooltip, Position } from './pipeline_processors_editor_item_tooltip';
+
+export { ProcessorsEmptyPrompt } from './processors_empty_prompt';
+
+export { ProcessorsHeader } from './processors_header';
+
+export { OnFailureProcessorsTitle } from './on_failure_processors_title';
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/load_from_json/button.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/load_from_json/button.tsx
index 21d15fc86a0ce..38700d6a7a87c 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/load_from_json/button.tsx
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/load_from_json/button.tsx
@@ -15,7 +15,7 @@ interface Props {
const i18nTexts = {
buttonLabel: i18n.translate('xpack.ingestPipelines.pipelineEditor.loadFromJson.buttonLabel', {
- defaultMessage: 'Import',
+ defaultMessage: 'Import processors',
}),
};
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/on_failure_processors_title.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/on_failure_processors_title.tsx
similarity index 96%
rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/on_failure_processors_title.tsx
rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/on_failure_processors_title.tsx
index 0beb5657b54cb..7adc37d1897d1 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/on_failure_processors_title.tsx
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/on_failure_processors_title.tsx
@@ -8,7 +8,7 @@ import React, { FunctionComponent } from 'react';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
-import { useKibana } from '../../../shared_imports';
+import { useKibana } from '../../../../shared_imports';
export const OnFailureProcessorsTitle: FunctionComponent = () => {
const { services } = useKibana();
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processor_form.container.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processor_form.container.tsx
index 332908d0756f2..c3b1799ac2a28 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processor_form.container.tsx
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processor_form.container.tsx
@@ -113,15 +113,15 @@ export const ProcessorFormContainer: FunctionComponent = ({
handleSubmit={handleSubmit}
/>
);
- } else {
- return (
-
- );
}
+
+ return (
+
+ );
};
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processors_empty_prompt.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processors_empty_prompt.tsx
new file mode 100644
index 0000000000000..3750ddda25d10
--- /dev/null
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processors_empty_prompt.tsx
@@ -0,0 +1,73 @@
+/*
+ * 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 React, { FunctionComponent } from 'react';
+import { i18n } from '@kbn/i18n';
+import { FormattedMessage } from '@kbn/i18n/react';
+import { EuiEmptyPrompt, EuiSpacer, EuiLink } from '@elastic/eui';
+import { useKibana } from '../../../../shared_imports';
+import { usePipelineProcessorsContext } from '../context';
+import { AddProcessorButton } from './add_processor_button';
+import { OnDoneLoadJsonHandler, LoadFromJsonButton } from './load_from_json';
+
+const i18nTexts = {
+ emptyPromptTitle: i18n.translate('xpack.ingestPipelines.pipelineEditor.emptyPrompt.title', {
+ defaultMessage: 'Add your first processor',
+ }),
+};
+
+export interface Props {
+ onLoadJson: OnDoneLoadJsonHandler;
+}
+
+export const ProcessorsEmptyPrompt: FunctionComponent = ({ onLoadJson }) => {
+ const { onTreeAction } = usePipelineProcessorsContext();
+ const { services } = useKibana();
+
+ return (
+ {i18nTexts.emptyPromptTitle}}
+ data-test-subj="processorsEmptyPrompt"
+ body={
+
+
+ {i18n.translate(
+ 'xpack.ingestPipelines.pipelineEditor.processorsDocumentationLink',
+ {
+ defaultMessage: 'Learn more.',
+ }
+ )}
+
+ ),
+ }}
+ />
+
+ }
+ actions={
+ <>
+ {
+ onTreeAction({ type: 'addProcessor', payload: { target: ['processors'] } });
+ }}
+ />
+
+
+
+
+ >
+ }
+ />
+ );
+};
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/processors_header.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processors_header.tsx
similarity index 78%
rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/processors_header.tsx
rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processors_header.tsx
index 43477affa8d94..24f3207d6bea4 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/processors_header.tsx
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processors_header.tsx
@@ -9,21 +9,32 @@ import { EuiFlexGroup, EuiFlexItem, EuiLink, EuiText, EuiTitle } from '@elastic/
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
-import { useKibana } from '../../../shared_imports';
+import { useKibana } from '../../../../shared_imports';
-import {
- LoadFromJsonButton,
- OnDoneLoadJsonHandler,
- TestPipelineActions,
-} from '../pipeline_processors_editor';
+import { LoadFromJsonButton, OnDoneLoadJsonHandler, TestPipelineActions } from './';
export interface Props {
onLoadJson: OnDoneLoadJsonHandler;
+ hasProcessors: boolean;
}
-export const ProcessorsHeader: FunctionComponent = ({ onLoadJson }) => {
+export const ProcessorsHeader: FunctionComponent = ({ onLoadJson, hasProcessors }) => {
const { services } = useKibana();
+ const ProcessorTitle: FunctionComponent = () => (
+
+
+ {i18n.translate('xpack.ingestPipelines.pipelineEditor.processorsTreeTitle', {
+ defaultMessage: 'Processors',
+ })}
+
+
+ );
+
+ if (!hasProcessors) {
+ return ;
+ }
+
return (
= ({ onLoadJson }) => {
-
-
- {i18n.translate('xpack.ingestPipelines.pipelineEditor.processorsTreeTitle', {
- defaultMessage: 'Processors',
- })}
-
-
+
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processors_tree/components/tree_node.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processors_tree/components/tree_node.tsx
index e9008e6f5b693..3a8299c017d8d 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processors_tree/components/tree_node.tsx
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processors_tree/components/tree_node.tsx
@@ -70,6 +70,7 @@ export const TreeNode: FunctionComponent = ({
/>
onAction({
type: 'addProcessor',
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processors_tree/processors_tree.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processors_tree/processors_tree.tsx
index 8b344a137f3a8..ffc0a1459b791 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processors_tree/processors_tree.tsx
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processors_tree/processors_tree.tsx
@@ -99,7 +99,7 @@ export const ProcessorsTree: FunctionComponent = memo((props) => {
-
+
{!processors.length && (
= memo((props) => {
onClick={() => {
onAction({ type: 'addProcessor', payload: { target: baseSelector } });
}}
+ renderButtonAsLink
/>
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/index.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/index.ts
index c462b19c79327..ca5184da25a07 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/index.ts
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/index.ts
@@ -15,3 +15,5 @@ export { OnUpdateHandlerArg, OnUpdateHandler } from './types';
export { SerializeResult } from './serialize';
export { LoadFromJsonButton, OnDoneLoadJsonHandler, TestPipelineActions } from './components';
+
+export { PipelineProcessorsEditor } from './pipeline_processors_editor';
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_form.scss b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/pipeline_processors_editor.scss
similarity index 100%
rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_form.scss
rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/pipeline_processors_editor.scss
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/pipeline_processors_editor.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/pipeline_processors_editor.tsx
new file mode 100644
index 0000000000000..beb165973d3cd
--- /dev/null
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/pipeline_processors_editor.tsx
@@ -0,0 +1,66 @@
+/*
+ * 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 React from 'react';
+import { EuiSpacer, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
+
+import { usePipelineProcessorsContext } from './context';
+import {
+ ProcessorsEmptyPrompt,
+ OnFailureProcessorsTitle,
+ ProcessorsHeader,
+ OnDoneLoadJsonHandler,
+} from './components';
+import { ProcessorsEditor, GlobalOnFailureProcessorsEditor } from './editors';
+
+import './pipeline_processors_editor.scss';
+
+interface Props {
+ onLoadJson: OnDoneLoadJsonHandler;
+}
+
+export const PipelineProcessorsEditor: React.FunctionComponent = ({ onLoadJson }) => {
+ const {
+ state: { processors: allProcessors },
+ } = usePipelineProcessorsContext();
+
+ const {
+ state: { processors, onFailure },
+ } = allProcessors;
+
+ const showEmptyPrompt = processors.length === 0 && onFailure.length === 0;
+
+ let content: React.ReactNode;
+
+ if (showEmptyPrompt) {
+ content = ;
+ } else {
+ content = (
+ <>
+
+
+
+
+
+
+
+ >
+ );
+ }
+
+ return (
+
+
+
+ 0} />
+
+
+ {content}
+
+
+
+ );
+};
diff --git a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_create/pipelines_create.tsx b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_create/pipelines_create.tsx
index acca1c4e03f40..d4aa11715248e 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_create/pipelines_create.tsx
+++ b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_create/pipelines_create.tsx
@@ -16,7 +16,7 @@ import {
EuiSpacer,
} from '@elastic/eui';
-import { BASE_PATH } from '../../../../common/constants';
+import { getListPath } from '../../services/navigation';
import { Pipeline } from '../../../../common/types';
import { useKibana } from '../../../shared_imports';
import { PipelineForm } from '../../components';
@@ -50,11 +50,11 @@ export const PipelinesCreate: React.FunctionComponent {
- history.push(BASE_PATH);
+ history.push(getListPath());
};
useEffect(() => {
diff --git a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_edit/pipelines_edit.tsx b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_edit/pipelines_edit.tsx
index e09cf4820771f..35ca1635ab9c3 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_edit/pipelines_edit.tsx
+++ b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_edit/pipelines_edit.tsx
@@ -17,11 +17,11 @@ import {
} from '@elastic/eui';
import { EuiCallOut } from '@elastic/eui';
-import { BASE_PATH } from '../../../../common/constants';
import { Pipeline } from '../../../../common/types';
import { useKibana, SectionLoading } from '../../../shared_imports';
-import { PipelineForm } from '../../components';
+import { getListPath } from '../../services/navigation';
+import { PipelineForm } from '../../components';
import { attemptToURIDecode } from '../shared';
interface MatchParams {
@@ -56,11 +56,11 @@ export const PipelinesEdit: React.FunctionComponent {
- history.push(BASE_PATH);
+ history.push(getListPath());
};
useEffect(() => {
diff --git a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/empty_list.tsx b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/empty_list.tsx
index eba69ff454911..7f4caa09b6df0 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/empty_list.tsx
+++ b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/empty_list.tsx
@@ -11,6 +11,7 @@ import { useHistory } from 'react-router-dom';
import { ScopedHistory } from 'kibana/public';
import { reactRouterNavigate } from '../../../../../../../src/plugins/kibana_react/public';
import { useKibana } from '../../../shared_imports';
+import { getCreatePath } from '../../services/navigation';
export const EmptyList: FunctionComponent = () => {
const { services } = useKibana();
@@ -44,7 +45,11 @@ export const EmptyList: FunctionComponent = () => {
}
actions={
-
+
{i18n.translate('xpack.ingestPipelines.list.table.emptyPrompt.createButtonLabel', {
defaultMessage: 'Create a pipeline',
})}
diff --git a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/main.tsx b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/main.tsx
index 88148f1bc5746..be31f86e30c27 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/main.tsx
+++ b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/main.tsx
@@ -24,9 +24,9 @@ import {
} from '@elastic/eui';
import { Pipeline } from '../../../../common/types';
-import { BASE_PATH } from '../../../../common/constants';
import { useKibana, SectionLoading } from '../../../shared_imports';
import { UIM_PIPELINES_LIST_LOAD } from '../../constants';
+import { getEditPath, getClonePath, getListPath } from '../../services/navigation';
import { EmptyList } from './empty_list';
import { PipelineTable } from './table';
@@ -67,17 +67,17 @@ export const PipelinesList: React.FunctionComponent = ({
}
}, [pipelineNameFromLocation, data]);
- const goToEditPipeline = (name: string) => {
- history.push(`${BASE_PATH}/edit/${encodeURIComponent(name)}`);
+ const goToEditPipeline = (pipelineName: string) => {
+ history.push(getEditPath({ pipelineName }));
};
- const goToClonePipeline = (name: string) => {
- history.push(`${BASE_PATH}/create/${encodeURIComponent(name)}`);
+ const goToClonePipeline = (clonedPipelineName: string) => {
+ history.push(getClonePath({ clonedPipelineName }));
};
const goHome = () => {
setShowFlyout(false);
- history.push(BASE_PATH);
+ history.push(getListPath());
};
if (data && data.length === 0) {
diff --git a/x-pack/plugins/ingest_pipelines/public/application/services/navigation.ts b/x-pack/plugins/ingest_pipelines/public/application/services/navigation.ts
new file mode 100644
index 0000000000000..3ac3de6eac710
--- /dev/null
+++ b/x-pack/plugins/ingest_pipelines/public/application/services/navigation.ts
@@ -0,0 +1,44 @@
+/*
+ * 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.
+ */
+
+const BASE_PATH = '/';
+
+const EDIT_PATH = 'edit';
+
+const CREATE_PATH = 'create';
+
+const _getEditPath = (name: string, encode = true): string => {
+ return `${BASE_PATH}${EDIT_PATH}/${encode ? encodeURIComponent(name) : name}`;
+};
+
+const _getCreatePath = (): string => {
+ return `${BASE_PATH}${CREATE_PATH}`;
+};
+
+const _getClonePath = (name: string, encode = true): string => {
+ return `${BASE_PATH}${CREATE_PATH}/${encode ? encodeURIComponent(name) : name}`;
+};
+const _getListPath = (name?: string): string => {
+ return `${BASE_PATH}${name ? `?pipeline=${encodeURIComponent(name)}` : ''}`;
+};
+
+export const ROUTES = {
+ list: _getListPath(),
+ edit: _getEditPath(':name', false),
+ create: _getCreatePath(),
+ clone: _getClonePath(':sourceName', false),
+};
+
+export const getListPath = ({
+ inspectedPipelineName,
+}: {
+ inspectedPipelineName?: string;
+} = {}): string => _getListPath(inspectedPipelineName);
+export const getEditPath = ({ pipelineName }: { pipelineName: string }): string =>
+ _getEditPath(pipelineName, true);
+export const getCreatePath = (): string => _getCreatePath();
+export const getClonePath = ({ clonedPipelineName }: { clonedPipelineName: string }): string =>
+ _getClonePath(clonedPipelineName, true);
diff --git a/x-pack/plugins/ingest_pipelines/public/index.ts b/x-pack/plugins/ingest_pipelines/public/index.ts
index 7247973703804..637d4aad7264a 100644
--- a/x-pack/plugins/ingest_pipelines/public/index.ts
+++ b/x-pack/plugins/ingest_pipelines/public/index.ts
@@ -9,3 +9,10 @@ import { IngestPipelinesPlugin } from './plugin';
export function plugin() {
return new IngestPipelinesPlugin();
}
+
+export {
+ INGEST_PIPELINES_APP_ULR_GENERATOR,
+ IngestPipelinesUrlGenerator,
+ IngestPipelinesUrlGeneratorState,
+ INGEST_PIPELINES_PAGES,
+} from './url_generator';
diff --git a/x-pack/plugins/ingest_pipelines/public/plugin.ts b/x-pack/plugins/ingest_pipelines/public/plugin.ts
index 339068f185d1d..6c2f4a0898327 100644
--- a/x-pack/plugins/ingest_pipelines/public/plugin.ts
+++ b/x-pack/plugins/ingest_pipelines/public/plugin.ts
@@ -10,10 +10,11 @@ import { CoreSetup, Plugin } from 'src/core/public';
import { PLUGIN_ID } from '../common/constants';
import { uiMetricService, apiService } from './application/services';
import { Dependencies } from './types';
+import { registerUrlGenerator } from './url_generator';
export class IngestPipelinesPlugin implements Plugin {
public setup(coreSetup: CoreSetup, plugins: Dependencies): void {
- const { management, usageCollection } = plugins;
+ const { management, usageCollection, share } = plugins;
const { http, getStartServices } = coreSetup;
// Initialize services
@@ -46,6 +47,8 @@ export class IngestPipelinesPlugin implements Plugin {
};
},
});
+
+ registerUrlGenerator(coreSetup, management, share);
}
public start() {}
diff --git a/x-pack/plugins/ingest_pipelines/public/types.ts b/x-pack/plugins/ingest_pipelines/public/types.ts
index 91783ea04fa9a..e968c87226d07 100644
--- a/x-pack/plugins/ingest_pipelines/public/types.ts
+++ b/x-pack/plugins/ingest_pipelines/public/types.ts
@@ -6,8 +6,10 @@
import { ManagementSetup } from 'src/plugins/management/public';
import { UsageCollectionSetup } from 'src/plugins/usage_collection/public';
+import { SharePluginSetup } from '../../../../src/plugins/share/public';
export interface Dependencies {
management: ManagementSetup;
usageCollection: UsageCollectionSetup;
+ share: SharePluginSetup;
}
diff --git a/x-pack/plugins/ingest_pipelines/public/url_generator.test.ts b/x-pack/plugins/ingest_pipelines/public/url_generator.test.ts
new file mode 100644
index 0000000000000..1267d526fb7d4
--- /dev/null
+++ b/x-pack/plugins/ingest_pipelines/public/url_generator.test.ts
@@ -0,0 +1,107 @@
+/*
+ * 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 { IngestPipelinesUrlGenerator, INGEST_PIPELINES_PAGES } from './url_generator';
+
+describe('IngestPipelinesUrlGenerator', () => {
+ const getAppBasePath = (absolute: boolean = false) => {
+ if (absolute) {
+ return Promise.resolve('http://localhost/app/test_app');
+ }
+ return Promise.resolve('/app/test_app');
+ };
+ const urlGenerator = new IngestPipelinesUrlGenerator(getAppBasePath);
+
+ describe('Pipelines List', () => {
+ it('generates relative url for list without pipelineId', async () => {
+ const url = await urlGenerator.createUrl({
+ page: INGEST_PIPELINES_PAGES.LIST,
+ });
+ expect(url).toBe('/app/test_app/');
+ });
+
+ it('generates absolute url for list without pipelineId', async () => {
+ const url = await urlGenerator.createUrl({
+ page: INGEST_PIPELINES_PAGES.LIST,
+ absolute: true,
+ });
+ expect(url).toBe('http://localhost/app/test_app/');
+ });
+ it('generates relative url for list with a pipelineId', async () => {
+ const url = await urlGenerator.createUrl({
+ page: INGEST_PIPELINES_PAGES.LIST,
+ pipelineId: 'pipeline_name',
+ });
+ expect(url).toBe('/app/test_app/?pipeline=pipeline_name');
+ });
+
+ it('generates absolute url for list with a pipelineId', async () => {
+ const url = await urlGenerator.createUrl({
+ page: INGEST_PIPELINES_PAGES.LIST,
+ pipelineId: 'pipeline_name',
+ absolute: true,
+ });
+ expect(url).toBe('http://localhost/app/test_app/?pipeline=pipeline_name');
+ });
+ });
+
+ describe('Pipeline Edit', () => {
+ it('generates relative url for pipeline edit', async () => {
+ const url = await urlGenerator.createUrl({
+ page: INGEST_PIPELINES_PAGES.EDIT,
+ pipelineId: 'pipeline_name',
+ });
+ expect(url).toBe('/app/test_app/edit/pipeline_name');
+ });
+
+ it('generates absolute url for pipeline edit', async () => {
+ const url = await urlGenerator.createUrl({
+ page: INGEST_PIPELINES_PAGES.EDIT,
+ pipelineId: 'pipeline_name',
+ absolute: true,
+ });
+ expect(url).toBe('http://localhost/app/test_app/edit/pipeline_name');
+ });
+ });
+
+ describe('Pipeline Clone', () => {
+ it('generates relative url for pipeline clone', async () => {
+ const url = await urlGenerator.createUrl({
+ page: INGEST_PIPELINES_PAGES.CLONE,
+ pipelineId: 'pipeline_name',
+ });
+ expect(url).toBe('/app/test_app/create/pipeline_name');
+ });
+
+ it('generates absolute url for pipeline clone', async () => {
+ const url = await urlGenerator.createUrl({
+ page: INGEST_PIPELINES_PAGES.CLONE,
+ pipelineId: 'pipeline_name',
+ absolute: true,
+ });
+ expect(url).toBe('http://localhost/app/test_app/create/pipeline_name');
+ });
+ });
+
+ describe('Pipeline Create', () => {
+ it('generates relative url for pipeline create', async () => {
+ const url = await urlGenerator.createUrl({
+ page: INGEST_PIPELINES_PAGES.CREATE,
+ pipelineId: 'pipeline_name',
+ });
+ expect(url).toBe('/app/test_app/create');
+ });
+
+ it('generates absolute url for pipeline create', async () => {
+ const url = await urlGenerator.createUrl({
+ page: INGEST_PIPELINES_PAGES.CREATE,
+ pipelineId: 'pipeline_name',
+ absolute: true,
+ });
+ expect(url).toBe('http://localhost/app/test_app/create');
+ });
+ });
+});
diff --git a/x-pack/plugins/ingest_pipelines/public/url_generator.ts b/x-pack/plugins/ingest_pipelines/public/url_generator.ts
new file mode 100644
index 0000000000000..043d449a0440a
--- /dev/null
+++ b/x-pack/plugins/ingest_pipelines/public/url_generator.ts
@@ -0,0 +1,98 @@
+/*
+ * 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 } from 'src/core/public';
+import { MANAGEMENT_APP_ID } from '../../../../src/plugins/management/public';
+import { UrlGeneratorsDefinition } from '../../../../src/plugins/share/public';
+import {
+ getClonePath,
+ getCreatePath,
+ getEditPath,
+ getListPath,
+} from './application/services/navigation';
+import { Dependencies } from './types';
+import { PLUGIN_ID } from '../common/constants';
+
+export const INGEST_PIPELINES_APP_ULR_GENERATOR = 'INGEST_PIPELINES_APP_URL_GENERATOR';
+
+export enum INGEST_PIPELINES_PAGES {
+ LIST = 'pipelines_list',
+ EDIT = 'pipeline_edit',
+ CREATE = 'pipeline_create',
+ CLONE = 'pipeline_clone',
+}
+
+interface UrlGeneratorState {
+ pipelineId: string;
+ absolute?: boolean;
+}
+export interface PipelinesListUrlGeneratorState extends Partial {
+ page: INGEST_PIPELINES_PAGES.LIST;
+}
+
+export interface PipelineEditUrlGeneratorState extends UrlGeneratorState {
+ page: INGEST_PIPELINES_PAGES.EDIT;
+}
+
+export interface PipelineCloneUrlGeneratorState extends UrlGeneratorState {
+ page: INGEST_PIPELINES_PAGES.CLONE;
+}
+
+export interface PipelineCreateUrlGeneratorState extends UrlGeneratorState {
+ page: INGEST_PIPELINES_PAGES.CREATE;
+}
+
+export type IngestPipelinesUrlGeneratorState =
+ | PipelinesListUrlGeneratorState
+ | PipelineEditUrlGeneratorState
+ | PipelineCloneUrlGeneratorState
+ | PipelineCreateUrlGeneratorState;
+
+export class IngestPipelinesUrlGenerator
+ implements UrlGeneratorsDefinition {
+ constructor(private readonly getAppBasePath: (absolute: boolean) => Promise) {}
+
+ public readonly id = INGEST_PIPELINES_APP_ULR_GENERATOR;
+
+ public readonly createUrl = async (state: IngestPipelinesUrlGeneratorState): Promise => {
+ switch (state.page) {
+ case INGEST_PIPELINES_PAGES.EDIT: {
+ return `${await this.getAppBasePath(!!state.absolute)}${getEditPath({
+ pipelineName: state.pipelineId,
+ })}`;
+ }
+ case INGEST_PIPELINES_PAGES.CREATE: {
+ return `${await this.getAppBasePath(!!state.absolute)}${getCreatePath()}`;
+ }
+ case INGEST_PIPELINES_PAGES.LIST: {
+ return `${await this.getAppBasePath(!!state.absolute)}${getListPath({
+ inspectedPipelineName: state.pipelineId,
+ })}`;
+ }
+ case INGEST_PIPELINES_PAGES.CLONE: {
+ return `${await this.getAppBasePath(!!state.absolute)}${getClonePath({
+ clonedPipelineName: state.pipelineId,
+ })}`;
+ }
+ }
+ };
+}
+
+export const registerUrlGenerator = (
+ coreSetup: CoreSetup,
+ management: Dependencies['management'],
+ share: Dependencies['share']
+) => {
+ const getAppBasePath = async (absolute = false) => {
+ const [coreStart] = await coreSetup.getStartServices();
+ return coreStart.application.getUrlForApp(MANAGEMENT_APP_ID, {
+ path: management.sections.section.ingest.getApp(PLUGIN_ID)!.basePath,
+ absolute: !!absolute,
+ });
+ };
+
+ share.urlGenerators.registerUrlGenerator(new IngestPipelinesUrlGenerator(getAppBasePath));
+};
diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/bucket_nesting_editor.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/bucket_nesting_editor.tsx
index 325f18ee9833a..3d692b1f7f5a8 100644
--- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/bucket_nesting_editor.tsx
+++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/bucket_nesting_editor.tsx
@@ -75,6 +75,10 @@ export function BucketNestingEditor({
defaultMessage: 'Top values for each {field}',
values: { field: fieldName },
}),
+ range: i18n.translate('xpack.lens.indexPattern.groupingOverallRanges', {
+ defaultMessage: 'Top values for each {field}',
+ values: { field: fieldName },
+ }),
};
const bottomLevelCopy: Record = {
@@ -90,6 +94,10 @@ export function BucketNestingEditor({
defaultMessage: 'Overall top {target}',
values: { target: target.fieldName },
}),
+ range: i18n.translate('xpack.lens.indexPattern.groupingSecondRanges', {
+ defaultMessage: 'Overall top {target}',
+ values: { target: target.fieldName },
+ }),
};
return (
diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx
index 153757ac37da1..2f64a36e0462e 100644
--- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx
+++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx
@@ -332,7 +332,6 @@ export function DimensionEditor(props: DimensionEditorProps) {
{!incompatibleSelectedOperationType && ParamEditor && (
<>
-
{
if (
type === 'date' &&
@@ -180,7 +179,7 @@ export const dateHistogramOperation: OperationDefinition
+ <>
{!intervalIsRestricted && (
)}
-
+ >
);
},
};
diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/filters/filters.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/filters/filters.tsx
index cc1e23cb82a49..9985ad7229ecc 100644
--- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/filters/filters.tsx
+++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/filters/filters.tsx
@@ -226,6 +226,7 @@ export const FilterList = ({
removeTitle={i18n.translate('xpack.lens.indexPattern.filters.removeFilter', {
defaultMessage: 'Remove a filter',
})}
+ isNotRemovable={localFilters.length === 1}
>
+ range.label ||
+ formatter.convert({
+ gte: isValidNumber(range.from) ? range.from : FROM_PLACEHOLDER,
+ lt: isValidNumber(range.to) ? range.to : TO_PLACEHOLDER,
+ });
+
+export const RangePopover = ({
+ range,
+ setRange,
+ Button,
+ isOpenByCreation,
+ setIsOpenByCreation,
+}: {
+ range: LocalRangeType;
+ setRange: (newRange: LocalRangeType) => void;
+ Button: React.FunctionComponent<{ onClick: MouseEventHandler }>;
+ isOpenByCreation: boolean;
+ setIsOpenByCreation: (open: boolean) => void;
+ formatter: IFieldFormat;
+}) => {
+ const [isPopoverOpen, setIsPopoverOpen] = useState(false);
+ const [tempRange, setTempRange] = useState(range);
+
+ const saveRangeAndReset = (newRange: LocalRangeType, resetRange = false) => {
+ if (resetRange) {
+ // reset the temporary range for later use
+ setTempRange(range);
+ }
+ // send the range back to the main state
+ setRange(newRange);
+ };
+ const { from, to } = tempRange;
+
+ const lteAppendLabel = i18n.translate('xpack.lens.indexPattern.ranges.lessThanOrEqualAppend', {
+ defaultMessage: '\u2264',
+ });
+ const lteTooltipContent = i18n.translate(
+ 'xpack.lens.indexPattern.ranges.lessThanOrEqualTooltip',
+ {
+ defaultMessage: 'Less than or equal to',
+ }
+ );
+ const ltPrependLabel = i18n.translate('xpack.lens.indexPattern.ranges.lessThanPrepend', {
+ defaultMessage: '\u003c',
+ });
+ const ltTooltipContent = i18n.translate('xpack.lens.indexPattern.ranges.lessThanTooltip', {
+ defaultMessage: 'Less than',
+ });
+
+ const onSubmit = () => {
+ setIsPopoverOpen(false);
+ setIsOpenByCreation(false);
+ saveRangeAndReset(tempRange, true);
+ };
+
+ return (
+ {
+ setIsPopoverOpen((isOpen) => !isOpen);
+ setIsOpenByCreation(false);
+ }}
+ />
+ }
+ data-test-subj="indexPattern-ranges-popover"
+ >
+
+
+
+ {
+ const newRange = {
+ ...tempRange,
+ from: target.value !== '' ? Number(target.value) : -Infinity,
+ };
+ setTempRange(newRange);
+ saveRangeAndReset(newRange);
+ }}
+ append={
+
+ {lteAppendLabel}
+
+ }
+ fullWidth
+ compressed
+ placeholder={FROM_PLACEHOLDER}
+ isInvalid={!isValidRange(tempRange)}
+ />
+
+
+
+
+
+ {
+ const newRange = {
+ ...tempRange,
+ to: target.value !== '' ? Number(target.value) : -Infinity,
+ };
+ setTempRange(newRange);
+ saveRangeAndReset(newRange);
+ }}
+ prepend={
+
+ {ltPrependLabel}
+
+ }
+ fullWidth
+ compressed
+ placeholder={TO_PLACEHOLDER}
+ isInvalid={!isValidRange(tempRange)}
+ onKeyDown={({ key }: React.KeyboardEvent) => {
+ if (keys.ENTER === key && onSubmit) {
+ onSubmit();
+ }
+ }}
+ />
+
+
+
+
+ );
+};
+
+export const AdvancedRangeEditor = ({
+ ranges,
+ setRanges,
+ onToggleEditor,
+ formatter,
+}: {
+ ranges: RangeTypeLens[];
+ setRanges: (newRanges: RangeTypeLens[]) => void;
+ onToggleEditor: () => void;
+ formatter: IFieldFormat;
+}) => {
+ // use a local state to store ids with range objects
+ const [localRanges, setLocalRanges] = useState(() =>
+ ranges.map((range) => ({ ...range, id: generateId() }))
+ );
+ // we need to force the open state of the popover from the outside in some scenarios
+ // so we need an extra state here
+ const [isOpenByCreation, setIsOpenByCreation] = useState(false);
+
+ const lastIndex = localRanges.length - 1;
+
+ // Update locally all the time, but bounce the parents prop function
+ // to aviod too many requests
+ useDebounce(
+ () => {
+ setRanges(localRanges.map(({ id, ...rest }) => ({ ...rest })));
+ },
+ TYPING_DEBOUNCE_TIME,
+ [localRanges]
+ );
+
+ const addNewRange = () => {
+ setLocalRanges([
+ ...localRanges,
+ {
+ id: generateId(),
+ from: localRanges[localRanges.length - 1].to,
+ to: Infinity,
+ label: '',
+ },
+ ]);
+ };
+
+ return (
+
+
+ {' '}
+ {i18n.translate('xpack.lens.indexPattern.ranges.customIntervalsRemoval', {
+ defaultMessage: 'Remove custom intervals',
+ })}
+
+
+ }
+ >
+ <>
+ setIsOpenByCreation(false)}
+ droppableId="RANGES_DROPPABLE_AREA"
+ items={localRanges}
+ >
+ {localRanges.map((range: LocalRangeType, idx: number) => (
+ {
+ const newRanges = localRanges.filter((_, i) => i !== idx);
+ setLocalRanges(newRanges);
+ }}
+ removeTitle={i18n.translate('xpack.lens.indexPattern.ranges.deleteRange', {
+ defaultMessage: 'Delete range',
+ })}
+ isNotRemovable={localRanges.length === 1}
+ >
+ {
+ const newRanges = [...localRanges];
+ if (newRange.id === newRanges[idx].id) {
+ newRanges[idx] = newRange;
+ } else {
+ newRanges.push(newRange);
+ }
+ setLocalRanges(newRanges);
+ }}
+ formatter={formatter}
+ Button={({ onClick }: { onClick: MouseEventHandler }) => (
+
+
+ {getBetterLabel(range, formatter)}
+
+
+ )}
+ />
+
+ ))}
+
+ {
+ addNewRange();
+ setIsOpenByCreation(true);
+ }}
+ label={i18n.translate('xpack.lens.indexPattern.ranges.addInterval', {
+ defaultMessage: 'Add interval',
+ })}
+ />
+ >
+
+ );
+};
diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/constants.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/constants.ts
new file mode 100644
index 0000000000000..5c3c3c19a2b0f
--- /dev/null
+++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/constants.ts
@@ -0,0 +1,20 @@
+/*
+ * 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 TYPING_DEBOUNCE_TIME = 256;
+// Taken from the Visualize editor
+export const FROM_PLACEHOLDER = '\u2212\u221E';
+export const TO_PLACEHOLDER = '+\u221E';
+
+export const DEFAULT_INTERVAL = 1000;
+export const AUTO_BARS = 'auto';
+export const MIN_HISTOGRAM_BARS = 1;
+export const SLICES = 6;
+
+export const MODES = {
+ Range: 'range',
+ Histogram: 'histogram',
+} as const;
diff --git a/x-pack/plugins/embeddable_enhanced/public/drilldowns/index.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/index.ts
similarity index 88%
rename from x-pack/plugins/embeddable_enhanced/public/drilldowns/index.ts
rename to x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/index.ts
index a8d5a179dbac1..ccae0c949af0d 100644
--- a/x-pack/plugins/embeddable_enhanced/public/drilldowns/index.ts
+++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/index.ts
@@ -4,4 +4,4 @@
* you may not use this file except in compliance with the Elastic License.
*/
-export * from './url_drilldown';
+export * from './ranges';
diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/range_editor.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/range_editor.tsx
new file mode 100644
index 0000000000000..5d5acf7778973
--- /dev/null
+++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/range_editor.tsx
@@ -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 React, { useEffect, useState } from 'react';
+import { i18n } from '@kbn/i18n';
+import { useDebounce } from 'react-use';
+import {
+ EuiButtonEmpty,
+ EuiFormRow,
+ EuiRange,
+ EuiFlexItem,
+ EuiFlexGroup,
+ EuiButtonIcon,
+ EuiToolTip,
+} from '@elastic/eui';
+import { IFieldFormat } from 'src/plugins/data/public';
+import { RangeColumnParams, UpdateParamsFnType, MODES_TYPES } from './ranges';
+import { AdvancedRangeEditor } from './advanced_editor';
+import { TYPING_DEBOUNCE_TIME, MODES, MIN_HISTOGRAM_BARS } from './constants';
+
+const BaseRangeEditor = ({
+ maxBars,
+ step,
+ maxHistogramBars,
+ onToggleEditor,
+ onMaxBarsChange,
+}: {
+ maxBars: number;
+ step: number;
+ maxHistogramBars: number;
+ onToggleEditor: () => void;
+ onMaxBarsChange: (newMaxBars: number) => void;
+}) => {
+ const [maxBarsValue, setMaxBarsValue] = useState(String(maxBars));
+
+ useDebounce(
+ () => {
+ onMaxBarsChange(Number(maxBarsValue));
+ },
+ TYPING_DEBOUNCE_TIME,
+ [maxBarsValue]
+ );
+
+ const granularityLabel = i18n.translate('xpack.lens.indexPattern.ranges.granularity', {
+ defaultMessage: 'Granularity',
+ });
+ const decreaseButtonLabel = i18n.translate('xpack.lens.indexPattern.ranges.decreaseButtonLabel', {
+ defaultMessage: 'Decrease granularity',
+ });
+ const increaseButtonLabel = i18n.translate('xpack.lens.indexPattern.ranges.increaseButtonLabel', {
+ defaultMessage: 'Increase granularity',
+ });
+
+ return (
+ <>
+
+
+
+
+
+ setMaxBarsValue('' + Math.max(Number(maxBarsValue) - step, MIN_HISTOGRAM_BARS))
+ }
+ aria-label={decreaseButtonLabel}
+ />
+
+
+
+ setMaxBarsValue(currentTarget.value)}
+ />
+
+
+
+
+ setMaxBarsValue('' + Math.min(Number(maxBarsValue) + step, maxHistogramBars))
+ }
+ aria-label={increaseButtonLabel}
+ />
+
+
+
+
+
+ onToggleEditor()}>
+ {i18n.translate('xpack.lens.indexPattern.ranges.customIntervalsToggle', {
+ defaultMessage: 'Create custom intervals',
+ })}
+
+ >
+ );
+};
+
+export const RangeEditor = ({
+ setParam,
+ params,
+ maxHistogramBars,
+ maxBars,
+ granularityStep,
+ onChangeMode,
+ rangeFormatter,
+}: {
+ params: RangeColumnParams;
+ maxHistogramBars: number;
+ maxBars: number;
+ granularityStep: number;
+ setParam: UpdateParamsFnType;
+ onChangeMode: (mode: MODES_TYPES) => void;
+ rangeFormatter: IFieldFormat;
+}) => {
+ const [isAdvancedEditor, toggleAdvancedEditor] = useState(params.type === MODES.Range);
+
+ // if the maxBars in the params is set to auto refresh it with the default value
+ // only on bootstrap
+ useEffect(() => {
+ if (params.maxBars !== maxBars) {
+ setParam('maxBars', maxBars);
+ }
+ }, [maxBars, params.maxBars, setParam]);
+
+ if (isAdvancedEditor) {
+ return (
+ {
+ setParam('ranges', ranges);
+ }}
+ onToggleEditor={() => {
+ onChangeMode(MODES.Histogram);
+ toggleAdvancedEditor(false);
+ }}
+ formatter={rangeFormatter}
+ />
+ );
+ }
+
+ return (
+ {
+ setParam('maxBars', newMaxBars);
+ }}
+ onToggleEditor={() => {
+ onChangeMode(MODES.Range);
+ toggleAdvancedEditor(true);
+ }}
+ />
+ );
+};
diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.test.tsx
new file mode 100644
index 0000000000000..2409406afcdbc
--- /dev/null
+++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.test.tsx
@@ -0,0 +1,555 @@
+/*
+ * 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 React from 'react';
+import { mount } from 'enzyme';
+import { act } from 'react-dom/test-utils';
+import { EuiFieldNumber, EuiRange, EuiButtonEmpty, EuiLink } from '@elastic/eui';
+import { IUiSettingsClient, SavedObjectsClientContract, HttpSetup } from 'kibana/public';
+import { IStorageWrapper } from 'src/plugins/kibana_utils/public';
+import { IndexPatternPrivateState, IndexPattern } from '../../../types';
+import { dataPluginMock } from '../../../../../../../../src/plugins/data/public/mocks';
+import { rangeOperation } from '../index';
+import { RangeIndexPatternColumn } from './ranges';
+import {
+ MODES,
+ DEFAULT_INTERVAL,
+ TYPING_DEBOUNCE_TIME,
+ MIN_HISTOGRAM_BARS,
+ SLICES,
+} from './constants';
+import { RangePopover } from './advanced_editor';
+import { DragDropBuckets } from '../shared_components';
+
+const dataPluginMockValue = dataPluginMock.createStartContract();
+// need to overwrite the formatter field first
+dataPluginMockValue.fieldFormats.deserialize = jest.fn().mockImplementation(() => {
+ return { convert: ({ gte, lt }: { gte: string; lt: string }) => `${gte} - ${lt}` };
+});
+
+type ReactMouseEvent = React.MouseEvent &
+ React.MouseEvent;
+
+const defaultOptions = {
+ storage: {} as IStorageWrapper,
+ // need this for MAX_HISTOGRAM value
+ uiSettings: ({
+ get: () => 100,
+ } as unknown) as IUiSettingsClient,
+ savedObjectsClient: {} as SavedObjectsClientContract,
+ dateRange: {
+ fromDate: 'now-1y',
+ toDate: 'now',
+ },
+ data: dataPluginMockValue,
+ http: {} as HttpSetup,
+};
+
+describe('ranges', () => {
+ let state: IndexPatternPrivateState;
+ const InlineOptions = rangeOperation.paramEditor!;
+ const sourceField = 'MyField';
+ const MAX_HISTOGRAM_VALUE = 100;
+ const GRANULARITY_DEFAULT_VALUE = (MAX_HISTOGRAM_VALUE - MIN_HISTOGRAM_BARS) / 2;
+ const GRANULARITY_STEP = (MAX_HISTOGRAM_VALUE - MIN_HISTOGRAM_BARS) / SLICES;
+
+ function setToHistogramMode() {
+ const column = state.layers.first.columns.col1 as RangeIndexPatternColumn;
+ column.dataType = 'number';
+ column.scale = 'interval';
+ column.params.type = MODES.Histogram;
+ }
+
+ function setToRangeMode() {
+ const column = state.layers.first.columns.col1 as RangeIndexPatternColumn;
+ column.dataType = 'string';
+ column.scale = 'ordinal';
+ column.params.type = MODES.Range;
+ }
+
+ function getDefaultState(): IndexPatternPrivateState {
+ return {
+ indexPatternRefs: [],
+ indexPatterns: {},
+ existingFields: {},
+ currentIndexPatternId: '1',
+ isFirstExistenceFetch: false,
+ layers: {
+ first: {
+ indexPatternId: '1',
+ columnOrder: ['col1', 'col2'],
+ columns: {
+ // Start with the histogram type
+ col1: {
+ label: sourceField,
+ dataType: 'number',
+ operationType: 'range',
+ scale: 'interval',
+ isBucketed: true,
+ sourceField,
+ params: {
+ type: MODES.Histogram,
+ ranges: [{ from: 0, to: DEFAULT_INTERVAL, label: '' }],
+ maxBars: 'auto',
+ },
+ },
+ col2: {
+ label: 'Count',
+ dataType: 'number',
+ isBucketed: false,
+ sourceField: 'Records',
+ operationType: 'count',
+ },
+ },
+ },
+ },
+ };
+ }
+
+ beforeAll(() => {
+ jest.useFakeTimers();
+ });
+
+ beforeEach(() => {
+ state = getDefaultState();
+ });
+
+ describe('toEsAggConfig', () => {
+ afterAll(() => setToHistogramMode());
+
+ it('should reflect params correctly', () => {
+ const esAggsConfig = rangeOperation.toEsAggsConfig(
+ state.layers.first.columns.col1 as RangeIndexPatternColumn,
+ 'col1',
+ {} as IndexPattern
+ );
+ expect(esAggsConfig).toEqual(
+ expect.objectContaining({
+ type: MODES.Histogram,
+ params: expect.objectContaining({
+ field: sourceField,
+ maxBars: null,
+ }),
+ })
+ );
+ });
+
+ it('should reflect the type correctly', () => {
+ setToRangeMode();
+
+ const esAggsConfig = rangeOperation.toEsAggsConfig(
+ state.layers.first.columns.col1 as RangeIndexPatternColumn,
+ 'col1',
+ {} as IndexPattern
+ );
+
+ expect(esAggsConfig).toEqual(
+ expect.objectContaining({
+ type: MODES.Range,
+ })
+ );
+ });
+ });
+
+ describe('getPossibleOperationForField', () => {
+ it('should return operation with the right type for number', () => {
+ expect(
+ rangeOperation.getPossibleOperationForField({
+ aggregatable: true,
+ searchable: true,
+ name: 'test',
+ displayName: 'test',
+ type: 'number',
+ })
+ ).toEqual({
+ dataType: 'number',
+ isBucketed: true,
+ scale: 'interval',
+ });
+ });
+
+ it('should not return operation if field type is not number', () => {
+ expect(
+ rangeOperation.getPossibleOperationForField({
+ aggregatable: false,
+ searchable: true,
+ name: 'test',
+ displayName: 'test',
+ type: 'string',
+ })
+ ).toEqual(undefined);
+ });
+ });
+
+ describe('paramEditor', () => {
+ describe('Modify intervals in basic mode', () => {
+ beforeEach(() => {
+ state = getDefaultState();
+ });
+
+ it('should start update the state with the default maxBars value', () => {
+ const setStateSpy = jest.fn();
+ mount(
+
+ );
+
+ expect(setStateSpy).toHaveBeenCalledWith({
+ ...state,
+ layers: {
+ first: {
+ ...state.layers.first,
+ columns: {
+ ...state.layers.first.columns,
+ col1: {
+ ...state.layers.first.columns.col1,
+ params: {
+ ...state.layers.first.columns.col1.params,
+ maxBars: GRANULARITY_DEFAULT_VALUE,
+ },
+ },
+ },
+ },
+ },
+ });
+ });
+
+ it('should update state when changing Max bars number', () => {
+ const setStateSpy = jest.fn();
+
+ const instance = mount(
+
+ );
+
+ act(() => {
+ instance.find(EuiRange).prop('onChange')!(
+ {
+ currentTarget: {
+ value: '' + MAX_HISTOGRAM_VALUE,
+ },
+ } as React.ChangeEvent,
+ true
+ );
+ jest.advanceTimersByTime(TYPING_DEBOUNCE_TIME * 4);
+
+ expect(setStateSpy).toHaveBeenCalledWith({
+ ...state,
+ layers: {
+ first: {
+ ...state.layers.first,
+ columns: {
+ ...state.layers.first.columns,
+ col1: {
+ ...state.layers.first.columns.col1,
+ params: {
+ ...state.layers.first.columns.col1.params,
+ maxBars: MAX_HISTOGRAM_VALUE,
+ },
+ },
+ },
+ },
+ },
+ });
+ });
+ });
+
+ it('should update the state using the plus or minus buttons by the step amount', () => {
+ const setStateSpy = jest.fn();
+
+ const instance = mount(
+
+ );
+
+ act(() => {
+ // minus button
+ instance
+ .find('[data-test-subj="lns-indexPattern-range-maxBars-minus"]')
+ .find('button')
+ .prop('onClick')!({} as ReactMouseEvent);
+ jest.advanceTimersByTime(TYPING_DEBOUNCE_TIME * 4);
+
+ expect(setStateSpy).toHaveBeenCalledWith({
+ ...state,
+ layers: {
+ first: {
+ ...state.layers.first,
+ columns: {
+ ...state.layers.first.columns,
+ col1: {
+ ...state.layers.first.columns.col1,
+ params: {
+ ...state.layers.first.columns.col1.params,
+ maxBars: GRANULARITY_DEFAULT_VALUE - GRANULARITY_STEP,
+ },
+ },
+ },
+ },
+ },
+ });
+
+ // plus button
+ instance
+ .find('[data-test-subj="lns-indexPattern-range-maxBars-plus"]')
+ .find('button')
+ .prop('onClick')!({} as ReactMouseEvent);
+ jest.advanceTimersByTime(TYPING_DEBOUNCE_TIME * 4);
+
+ expect(setStateSpy).toHaveBeenCalledWith({
+ ...state,
+ layers: {
+ first: {
+ ...state.layers.first,
+ columns: {
+ ...state.layers.first.columns,
+ col1: {
+ ...state.layers.first.columns.col1,
+ params: {
+ ...state.layers.first.columns.col1.params,
+ maxBars: GRANULARITY_DEFAULT_VALUE,
+ },
+ },
+ },
+ },
+ },
+ });
+ });
+ });
+ });
+
+ describe('Specify range intervals manually', () => {
+ // @ts-expect-error
+ window['__react-beautiful-dnd-disable-dev-warnings'] = true; // issue with enzyme & react-beautiful-dnd throwing errors: https://github.com/atlassian/react-beautiful-dnd/issues/1593
+
+ beforeEach(() => setToRangeMode());
+
+ it('should show one range interval to start with', () => {
+ const setStateSpy = jest.fn();
+
+ const instance = mount(
+
+ );
+
+ expect(instance.find(DragDropBuckets).children).toHaveLength(1);
+ });
+
+ it('should add a new range', () => {
+ const setStateSpy = jest.fn();
+
+ const instance = mount(
+
+ );
+
+ // This series of act clojures are made to make it work properly the update flush
+ act(() => {
+ instance.find(EuiButtonEmpty).prop('onClick')!({} as ReactMouseEvent);
+ });
+
+ act(() => {
+ // need another wrapping for this in order to work
+ instance.update();
+
+ expect(instance.find(RangePopover)).toHaveLength(2);
+
+ // edit the range and check
+ instance.find(RangePopover).find(EuiFieldNumber).first().prop('onChange')!({
+ target: {
+ value: '50',
+ },
+ } as React.ChangeEvent);
+ jest.advanceTimersByTime(TYPING_DEBOUNCE_TIME * 4);
+
+ expect(setStateSpy).toHaveBeenCalledWith({
+ ...state,
+ layers: {
+ first: {
+ ...state.layers.first,
+ columns: {
+ ...state.layers.first.columns,
+ col1: {
+ ...state.layers.first.columns.col1,
+ params: {
+ ...state.layers.first.columns.col1.params,
+ ranges: [
+ { from: 0, to: DEFAULT_INTERVAL, label: '' },
+ { from: 50, to: Infinity, label: '' },
+ ],
+ },
+ },
+ },
+ },
+ },
+ });
+ });
+ });
+
+ it('should open a popover to edit an existing range', () => {
+ const setStateSpy = jest.fn();
+
+ const instance = mount(
+
+ );
+
+ // This series of act clojures are made to make it work properly the update flush
+ act(() => {
+ instance.find(RangePopover).find(EuiLink).prop('onClick')!({} as ReactMouseEvent);
+ });
+
+ act(() => {
+ // need another wrapping for this in order to work
+ instance.update();
+
+ // edit the range "to" field
+ instance.find(RangePopover).find(EuiFieldNumber).last().prop('onChange')!({
+ target: {
+ value: '50',
+ },
+ } as React.ChangeEvent);
+ jest.advanceTimersByTime(TYPING_DEBOUNCE_TIME * 4);
+
+ expect(setStateSpy).toHaveBeenCalledWith({
+ ...state,
+ layers: {
+ first: {
+ ...state.layers.first,
+ columns: {
+ ...state.layers.first.columns,
+ col1: {
+ ...state.layers.first.columns.col1,
+ params: {
+ ...state.layers.first.columns.col1.params,
+ ranges: [{ from: 0, to: 50, label: '' }],
+ },
+ },
+ },
+ },
+ },
+ });
+ });
+ });
+
+ it('should not accept invalid ranges', () => {
+ const setStateSpy = jest.fn();
+
+ const instance = mount(
+
+ );
+
+ // This series of act clojures are made to make it work properly the update flush
+ act(() => {
+ instance.find(RangePopover).find(EuiLink).prop('onClick')!({} as ReactMouseEvent);
+ });
+
+ act(() => {
+ // need another wrapping for this in order to work
+ instance.update();
+
+ // edit the range "to" field
+ instance.find(RangePopover).find(EuiFieldNumber).last().prop('onChange')!({
+ target: {
+ value: '-1',
+ },
+ } as React.ChangeEvent);
+ });
+
+ act(() => {
+ instance.update();
+
+ // and check
+ expect(instance.find(RangePopover).find(EuiFieldNumber).last().prop('isInvalid')).toBe(
+ true
+ );
+ });
+ });
+
+ it('should be possible to remove a range if multiple', () => {
+ const setStateSpy = jest.fn();
+
+ // Add an extra range
+ (state.layers.first.columns.col1 as RangeIndexPatternColumn).params.ranges.push({
+ from: DEFAULT_INTERVAL,
+ to: 2 * DEFAULT_INTERVAL,
+ label: '',
+ });
+
+ const instance = mount(
+
+ );
+
+ expect(instance.find(RangePopover)).toHaveLength(2);
+
+ // This series of act closures are made to make it work properly the update flush
+ act(() => {
+ instance
+ .find('[data-test-subj="lns-customBucketContainer-remove"]')
+ .last()
+ .prop('onClick')!({} as ReactMouseEvent);
+ });
+
+ act(() => {
+ // need another wrapping for this in order to work
+ instance.update();
+
+ expect(instance.find(RangePopover)).toHaveLength(1);
+ });
+ });
+ });
+ });
+});
diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.tsx
new file mode 100644
index 0000000000000..530c2e962759b
--- /dev/null
+++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.tsx
@@ -0,0 +1,199 @@
+/*
+ * 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 React from 'react';
+import { i18n } from '@kbn/i18n';
+
+import { UI_SETTINGS } from '../../../../../../../../src/plugins/data/common';
+import { Range } from '../../../../../../../../src/plugins/expressions/common/expression_types/index';
+import { RangeEditor } from './range_editor';
+import { OperationDefinition } from '../index';
+import { FieldBasedIndexPatternColumn } from '../column_types';
+import { updateColumnParam, changeColumn } from '../../../state_helpers';
+import { MODES, AUTO_BARS, DEFAULT_INTERVAL, MIN_HISTOGRAM_BARS, SLICES } from './constants';
+
+type RangeType = Omit;
+export type RangeTypeLens = RangeType & { label: string };
+
+export type MODES_TYPES = typeof MODES[keyof typeof MODES];
+
+export interface RangeIndexPatternColumn extends FieldBasedIndexPatternColumn {
+ operationType: 'range';
+ params: {
+ type: MODES_TYPES;
+ maxBars: typeof AUTO_BARS | number;
+ ranges: RangeTypeLens[];
+ };
+}
+
+export type RangeColumnParams = RangeIndexPatternColumn['params'];
+export type UpdateParamsFnType = (
+ paramName: K,
+ value: RangeColumnParams[K]
+) => void;
+
+export const isValidNumber = (value: number | '') =>
+ value !== '' && !isNaN(value) && isFinite(value);
+export const isRangeWithin = (range: RangeTypeLens): boolean => range.from <= range.to;
+const isFullRange = ({ from, to }: RangeType) => isValidNumber(from) && isValidNumber(to);
+export const isValidRange = (range: RangeTypeLens): boolean => {
+ if (isFullRange(range)) {
+ return isRangeWithin(range);
+ }
+ return true;
+};
+
+function getEsAggsParams({ sourceField, params }: RangeIndexPatternColumn) {
+ if (params.type === MODES.Range) {
+ return {
+ field: sourceField,
+ ranges: params.ranges.filter(isValidRange).map>((range) => {
+ if (isFullRange(range)) {
+ return { from: range.from, to: range.to };
+ }
+ const partialRange: Partial = {};
+ // be careful with the fields to set on partial ranges
+ if (isValidNumber(range.from)) {
+ partialRange.from = range.from;
+ }
+ if (isValidNumber(range.to)) {
+ partialRange.to = range.to;
+ }
+ return partialRange;
+ }),
+ };
+ }
+ return {
+ field: sourceField,
+ // fallback to 0 in case of empty string
+ maxBars: params.maxBars === AUTO_BARS ? null : params.maxBars,
+ has_extended_bounds: false,
+ min_doc_count: 0,
+ extended_bounds: { min: '', max: '' },
+ };
+}
+
+export const rangeOperation: OperationDefinition = {
+ type: 'range',
+ displayName: i18n.translate('xpack.lens.indexPattern.ranges', {
+ defaultMessage: 'Ranges',
+ }),
+ priority: 4, // Higher than terms, so numbers get histogram
+ getPossibleOperationForField: ({ aggregationRestrictions, aggregatable, type }) => {
+ if (
+ type === 'number' &&
+ aggregatable &&
+ (!aggregationRestrictions || aggregationRestrictions.range)
+ ) {
+ return {
+ dataType: 'number',
+ isBucketed: true,
+ scale: 'interval',
+ };
+ }
+ },
+ buildColumn({ suggestedPriority, field }) {
+ return {
+ label: field.name,
+ dataType: 'number', // string for Range
+ operationType: 'range',
+ suggestedPriority,
+ sourceField: field.name,
+ isBucketed: true,
+ scale: 'interval', // ordinal for Range
+ params: {
+ type: MODES.Histogram,
+ ranges: [{ from: 0, to: DEFAULT_INTERVAL, label: '' }],
+ maxBars: AUTO_BARS,
+ },
+ };
+ },
+ isTransferable: (column, newIndexPattern) => {
+ const newField = newIndexPattern.fields.find((field) => field.name === column.sourceField);
+
+ return Boolean(
+ newField &&
+ newField.type === 'number' &&
+ newField.aggregatable &&
+ (!newField.aggregationRestrictions || newField.aggregationRestrictions.range)
+ );
+ },
+ onFieldChange: (oldColumn, indexPattern, field) => {
+ return {
+ ...oldColumn,
+ label: field.name,
+ sourceField: field.name,
+ };
+ },
+ toEsAggsConfig: (column, columnId) => {
+ const params = getEsAggsParams(column);
+ return {
+ id: columnId,
+ enabled: true,
+ type: column.params.type,
+ schema: 'segment',
+ params,
+ };
+ },
+ paramEditor: ({ state, setState, currentColumn, layerId, columnId, uiSettings, data }) => {
+ const rangeFormatter = data.fieldFormats.deserialize({ id: 'range' });
+ const MAX_HISTOGRAM_BARS = uiSettings.get(UI_SETTINGS.HISTOGRAM_MAX_BARS);
+ const granularityStep = (MAX_HISTOGRAM_BARS - MIN_HISTOGRAM_BARS) / SLICES;
+ const maxBarsDefaultValue = (MAX_HISTOGRAM_BARS - MIN_HISTOGRAM_BARS) / 2;
+
+ // Used to change one param at the time
+ const setParam: UpdateParamsFnType = (paramName, value) => {
+ setState(
+ updateColumnParam({
+ state,
+ layerId,
+ currentColumn,
+ paramName,
+ value,
+ })
+ );
+ };
+
+ // Useful to change more params at once
+ const onChangeMode = (newMode: MODES_TYPES) => {
+ const scale = newMode === MODES.Range ? 'ordinal' : 'interval';
+ const dataType = newMode === MODES.Range ? 'string' : 'number';
+ setState(
+ changeColumn({
+ state,
+ layerId,
+ columnId,
+ newColumn: {
+ ...currentColumn,
+ scale,
+ dataType,
+ params: {
+ type: newMode,
+ ranges: [{ from: 0, to: DEFAULT_INTERVAL, label: '' }],
+ maxBars: maxBarsDefaultValue,
+ },
+ },
+ keepParams: false,
+ })
+ );
+ };
+ return (
+
+ );
+ },
+};
diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/shared_components/buckets.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/shared_components/buckets.tsx
index 73378cea919a6..47380f7865578 100644
--- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/shared_components/buckets.tsx
+++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/shared_components/buckets.tsx
@@ -35,6 +35,7 @@ interface BucketContainerProps {
invalidMessage: string;
onRemoveClick: () => void;
removeTitle: string;
+ isNotRemovable?: boolean;
children: React.ReactNode;
dataTestSubj?: string;
}
@@ -46,6 +47,7 @@ const BucketContainer = ({
removeTitle,
children,
dataTestSubj,
+ isNotRemovable,
}: BucketContainerProps) => {
return (
@@ -75,6 +77,7 @@ const BucketContainer = ({
onClick={onRemoveClick}
aria-label={removeTitle}
title={removeTitle}
+ disabled={isNotRemovable}
/>
diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms.tsx
index 20c421008a746..c1a87a2013747 100644
--- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms.tsx
+++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms.tsx
@@ -6,7 +6,7 @@
import React from 'react';
import { i18n } from '@kbn/i18n';
-import { EuiForm, EuiFormRow, EuiRange, EuiSelect } from '@elastic/eui';
+import { EuiFormRow, EuiRange, EuiSelect } from '@elastic/eui';
import { IndexPatternColumn } from '../../indexpattern';
import { updateColumnParam } from '../../state_helpers';
import { DataType } from '../../../types';
@@ -171,7 +171,7 @@ export const termsOperation: OperationDefinition = {
}),
});
return (
-
+ <>
= {
})}
/>
-
+ >
);
},
};
diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/operations.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/operations.test.ts
index 4ac3fc89500f9..703431f724c5d 100644
--- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/operations.test.ts
+++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/operations.test.ts
@@ -225,29 +225,43 @@ describe('getOperationTypesForField', () => {
it('should list out all field-operation tuples for different operation meta data', () => {
expect(getAvailableOperationsByMetadata(expectedIndexPatterns[1])).toMatchInlineSnapshot(`
Array [
+ Object {
+ "operationMetaData": Object {
+ "dataType": "date",
+ "isBucketed": true,
+ "scale": "interval",
+ },
+ "operations": Array [
+ Object {
+ "field": "timestamp",
+ "operationType": "date_histogram",
+ "type": "field",
+ },
+ ],
+ },
Object {
"operationMetaData": Object {
"dataType": "number",
"isBucketed": true,
- "scale": "ordinal",
+ "scale": "interval",
},
"operations": Array [
Object {
"field": "bytes",
- "operationType": "terms",
+ "operationType": "range",
"type": "field",
},
],
},
Object {
"operationMetaData": Object {
- "dataType": "string",
+ "dataType": "number",
"isBucketed": true,
"scale": "ordinal",
},
"operations": Array [
Object {
- "field": "source",
+ "field": "bytes",
"operationType": "terms",
"type": "field",
},
@@ -255,14 +269,14 @@ describe('getOperationTypesForField', () => {
},
Object {
"operationMetaData": Object {
- "dataType": "date",
+ "dataType": "string",
"isBucketed": true,
- "scale": "interval",
+ "scale": "ordinal",
},
"operations": Array [
Object {
- "field": "timestamp",
- "operationType": "date_histogram",
+ "field": "source",
+ "operationType": "terms",
"type": "field",
},
],
diff --git a/x-pack/plugins/lens/public/lens_ui_telemetry/factory.test.ts b/x-pack/plugins/lens/public/lens_ui_telemetry/factory.test.ts
index 8bb1e086a37c2..fa7747dd18e42 100644
--- a/x-pack/plugins/lens/public/lens_ui_telemetry/factory.test.ts
+++ b/x-pack/plugins/lens/public/lens_ui_telemetry/factory.test.ts
@@ -83,7 +83,7 @@ describe('Lens UI telemetry', () => {
jest.runOnlyPendingTimers();
- expect(http.post).toHaveBeenCalledWith(`/api/lens/telemetry`, {
+ expect(http.post).toHaveBeenCalledWith(`/api/lens/stats`, {
body: JSON.stringify({
events: {
'2019-10-23': {
diff --git a/x-pack/plugins/lens/public/lens_ui_telemetry/factory.ts b/x-pack/plugins/lens/public/lens_ui_telemetry/factory.ts
index cb517acff4f7a..8f9ce7f2ceab8 100644
--- a/x-pack/plugins/lens/public/lens_ui_telemetry/factory.ts
+++ b/x-pack/plugins/lens/public/lens_ui_telemetry/factory.ts
@@ -86,7 +86,7 @@ export class LensReportManager {
this.readFromStorage();
if (Object.keys(this.events).length || Object.keys(this.suggestionEvents).length) {
try {
- await this.http.post(`${BASE_API_URL}/telemetry`, {
+ await this.http.post(`${BASE_API_URL}/stats`, {
body: JSON.stringify({
events: this.events,
suggestionEvents: this.suggestionEvents,
diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.tsx b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.tsx
index c7781c2e1d50c..ee22ee51301df 100644
--- a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.tsx
+++ b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.tsx
@@ -17,7 +17,6 @@ import {
EuiFormRow,
EuiText,
htmlIdGenerator,
- EuiForm,
EuiColorPicker,
EuiColorPickerProps,
EuiToolTip,
@@ -366,7 +365,7 @@ export function DimensionEditor(props: VisualizationDimensionEditorProps)
'auto';
return (
-
+ <>
)
}}
/>
-
+ >
);
}
diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_expression.tsx b/x-pack/plugins/lens/public/xy_visualization/xy_expression.tsx
index 9379c8a612eb2..24bf78dba2121 100644
--- a/x-pack/plugins/lens/public/xy_visualization/xy_expression.tsx
+++ b/x-pack/plugins/lens/public/xy_visualization/xy_expression.tsx
@@ -24,6 +24,7 @@ import {
ExpressionFunctionDefinition,
ExpressionRenderDefinition,
ExpressionValueSearchContext,
+ KibanaDatatable,
} from 'src/plugins/expressions/public';
import { IconType } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
@@ -251,6 +252,12 @@ export function XYChart({
({ id }) => id === filteredLayers[0].xAccessor
);
const xAxisFormatter = formatFactory(xAxisColumn && xAxisColumn.formatHint);
+ const layersAlreadyFormatted: Record = {};
+ // This is a safe formatter for the xAccessor that abstracts the knowledge of already formatted layers
+ const safeXAccessorLabelRenderer = (value: unknown): string =>
+ xAxisColumn && layersAlreadyFormatted[xAxisColumn.id]
+ ? (value as string)
+ : xAxisFormatter.convert(value);
const chartHasMoreThanOneSeries =
filteredLayers.length > 1 ||
@@ -364,7 +371,7 @@ export function XYChart({
theme={chartTheme}
baseTheme={chartBaseTheme}
tooltip={{
- headerFormatter: (d) => xAxisFormatter.convert(d.value),
+ headerFormatter: (d) => safeXAccessorLabelRenderer(d.value),
}}
rotation={shouldRotate ? 90 : 0}
xDomain={xDomain}
@@ -409,9 +416,15 @@ export function XYChart({
const points = [
{
- row: table.rows.findIndex(
- (row) => layer.xAccessor && row[layer.xAccessor] === xyGeometry.x
- ),
+ row: table.rows.findIndex((row) => {
+ if (layer.xAccessor) {
+ if (layersAlreadyFormatted[layer.xAccessor]) {
+ // stringify the value to compare with the chart value
+ return xAxisFormatter.convert(row[layer.xAccessor]) === xyGeometry.x;
+ }
+ return row[layer.xAccessor] === xyGeometry.x;
+ }
+ }),
column: table.columns.findIndex((col) => col.id === layer.xAccessor),
value: xyGeometry.x,
},
@@ -455,7 +468,7 @@ export function XYChart({
strokeWidth: 2,
}}
hide={filteredLayers[0].hide || !filteredLayers[0].xAccessor}
- tickFormat={(d) => xAxisFormatter.convert(d)}
+ tickFormat={(d) => safeXAccessorLabelRenderer(d)}
style={{
tickLabel: {
visible: tickLabelsVisibilitySettings?.x,
@@ -504,9 +517,43 @@ export function XYChart({
const table = data.tables[layerId];
+ const isPrimitive = (value: unknown): boolean =>
+ value != null && typeof value !== 'object';
+
+ // what if row values are not primitive? That is the case of, for instance, Ranges
+ // remaps them to their serialized version with the formatHint metadata
+ // In order to do it we need to make a copy of the table as the raw one is required for more features (filters, etc...) later on
+ const tableConverted: KibanaDatatable = {
+ ...table,
+ rows: table.rows.map((row) => {
+ const newRow = { ...row };
+ for (const column of table.columns) {
+ const record = newRow[column.id];
+ if (record && !isPrimitive(record)) {
+ newRow[column.id] = formatFactory(column.formatHint).convert(record);
+ }
+ }
+ return newRow;
+ }),
+ };
+
+ // save the id of the layer with the custom table
+ table.columns.reduce>(
+ (alreadyFormatted: Record, { id }) => {
+ if (alreadyFormatted[id]) {
+ return alreadyFormatted;
+ }
+ alreadyFormatted[id] = table.rows.some(
+ (row, i) => row[id] !== tableConverted.rows[i][id]
+ );
+ return alreadyFormatted;
+ },
+ layersAlreadyFormatted
+ );
+
// For date histogram chart type, we're getting the rows that represent intervals without data.
// To not display them in the legend, they need to be filtered out.
- const rows = table.rows.filter(
+ const rows = tableConverted.rows.filter(
(row) =>
!(xAccessor && typeof row[xAccessor] === 'undefined') &&
!(
@@ -559,19 +606,28 @@ export function XYChart({
// * Key - Y name
// * Formatted value - Y name
if (accessors.length > 1) {
- return d.seriesKeys
+ const result = d.seriesKeys
.map((key: string | number, i) => {
- if (i === 0 && splitHint) {
+ if (
+ i === 0 &&
+ splitHint &&
+ splitAccessor &&
+ !layersAlreadyFormatted[splitAccessor]
+ ) {
return formatFactory(splitHint).convert(key);
}
return splitAccessor && i === 0 ? key : columnToLabelMap[key] ?? '';
})
.join(' - ');
+ return result;
}
// For formatted split series, format the key
// This handles splitting by dates, for example
if (splitHint) {
+ if (splitAccessor && layersAlreadyFormatted[splitAccessor]) {
+ return d.seriesKeys[0];
+ }
return formatFactory(splitHint).convert(d.seriesKeys[0]);
}
// This handles both split and single-y cases:
diff --git a/x-pack/plugins/lens/server/routes/telemetry.ts b/x-pack/plugins/lens/server/routes/telemetry.ts
index 7925416ff5df2..06a7091104871 100644
--- a/x-pack/plugins/lens/server/routes/telemetry.ts
+++ b/x-pack/plugins/lens/server/routes/telemetry.ts
@@ -15,7 +15,7 @@ export async function initLensUsageRoute(setup: CoreSetup) {
const router = setup.http.createRouter();
router.post(
{
- path: `${BASE_API_URL}/telemetry`,
+ path: `${BASE_API_URL}/stats`,
validate: {
body: schema.object({
events: schema.mapOf(schema.string(), schema.mapOf(schema.string(), schema.number())),
diff --git a/x-pack/plugins/observability/kibana.json b/x-pack/plugins/observability/kibana.json
index 834982009b9d0..2b7c067f66bae 100644
--- a/x-pack/plugins/observability/kibana.json
+++ b/x-pack/plugins/observability/kibana.json
@@ -2,19 +2,9 @@
"id": "observability",
"version": "8.0.0",
"kibanaVersion": "kibana",
- "configPath": [
- "xpack",
- "observability"
- ],
- "optionalPlugins": [
- "licensing",
- "home"
- ],
+ "configPath": ["xpack", "observability"],
+ "optionalPlugins": ["licensing", "home", "usageCollection"],
"ui": true,
"server": true,
- "requiredBundles": [
- "data",
- "kibanaReact",
- "kibanaUtils"
- ]
+ "requiredBundles": ["data", "kibanaReact", "kibanaUtils"]
}
diff --git a/x-pack/plugins/observability/public/application/application.test.tsx b/x-pack/plugins/observability/public/application/application.test.tsx
index 19995ed233e8d..1304936860b77 100644
--- a/x-pack/plugins/observability/public/application/application.test.tsx
+++ b/x-pack/plugins/observability/public/application/application.test.tsx
@@ -3,15 +3,18 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
-
import { createMemoryHistory } from 'history';
import React from 'react';
import { Observable } from 'rxjs';
import { AppMountParameters, CoreStart } from 'src/core/public';
+import { ObservabilityPluginSetupDeps } from '../plugin';
import { renderApp } from './';
describe('renderApp', () => {
- it('renders', () => {
+ it('renders', async () => {
+ const plugins = ({
+ usageCollection: { reportUiStats: () => {} },
+ } as unknown) as ObservabilityPluginSetupDeps;
const core = ({
application: { currentAppId$: new Observable(), navigateToUrl: () => {} },
chrome: { docTitle: { change: () => {} }, setBreadcrumbs: () => {} },
@@ -24,7 +27,7 @@ describe('renderApp', () => {
} as unknown) as AppMountParameters;
expect(() => {
- const unmount = renderApp(core, params);
+ const unmount = renderApp(core, plugins, params);
unmount();
}).not.toThrowError();
});
diff --git a/x-pack/plugins/observability/public/application/index.tsx b/x-pack/plugins/observability/public/application/index.tsx
index fa691a7f41ddb..a6f1f7c5b7cf9 100644
--- a/x-pack/plugins/observability/public/application/index.tsx
+++ b/x-pack/plugins/observability/public/application/index.tsx
@@ -8,12 +8,16 @@ import React, { useEffect } from 'react';
import ReactDOM from 'react-dom';
import { Route, Router, Switch } from 'react-router-dom';
import { AppMountParameters, CoreStart } from '../../../../../src/core/public';
-import { RedirectAppLinks } from '../../../../../src/plugins/kibana_react/public';
-import { EuiThemeProvider } from '../../../../legacy/common/eui_styled_components';
+import {
+ KibanaContextProvider,
+ RedirectAppLinks,
+} from '../../../../../src/plugins/kibana_react/public';
+import { EuiThemeProvider } from '../../../xpack_legacy/common';
import { PluginContext } from '../context/plugin_context';
import { usePluginContext } from '../hooks/use_plugin_context';
import { useRouteParams } from '../hooks/use_route_params';
import { Breadcrumbs, routes } from '../routes';
+import { ObservabilityPluginSetupDeps } from '../plugin';
const observabilityLabelBreadcrumb = {
text: i18n.translate('xpack.observability.observability.breadcrumb.', {
@@ -51,22 +55,28 @@ function App() {
);
}
-export const renderApp = (core: CoreStart, { element, history }: AppMountParameters) => {
+export const renderApp = (
+ core: CoreStart,
+ plugins: ObservabilityPluginSetupDeps,
+ { element, history }: AppMountParameters
+) => {
const i18nCore = core.i18n;
const isDarkMode = core.uiSettings.get('theme:darkMode');
ReactDOM.render(
-
-
-
-
-
-
-
-
-
-
- ,
+
+
+
+
+
+
+
+
+
+
+
+
+ ,
element
);
return () => {
diff --git a/x-pack/plugins/observability/public/data_handler.ts b/x-pack/plugins/observability/public/data_handler.ts
index b0bdcf17b9066..cae21fd9fed52 100644
--- a/x-pack/plugins/observability/public/data_handler.ts
+++ b/x-pack/plugins/observability/public/data_handler.ts
@@ -4,12 +4,11 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { DataHandler } from './typings/fetch_overview_data';
-import { ObservabilityApp } from '../typings/common';
+import { DataHandler, ObservabilityFetchDataPlugins } from './typings/fetch_overview_data';
-const dataHandlers: Partial> = {};
+const dataHandlers: Partial> = {};
-export function registerDataHandler({
+export function registerDataHandler({
appName,
fetchData,
hasData,
@@ -17,19 +16,23 @@ export function registerDataHandler({
dataHandlers[appName] = { fetchData, hasData };
}
-export function unregisterDataHandler({ appName }: { appName: T }) {
+export function unregisterDataHandler({
+ appName,
+}: {
+ appName: T;
+}) {
delete dataHandlers[appName];
}
-export function getDataHandler(appName: T) {
+export function getDataHandler(appName: T) {
const dataHandler = dataHandlers[appName];
if (dataHandler) {
return dataHandler as DataHandler;
}
}
-export async function fetchHasData(): Promise> {
- const apps: ObservabilityApp[] = ['apm', 'uptime', 'infra_logs', 'infra_metrics'];
+export async function fetchHasData(): Promise> {
+ const apps: ObservabilityFetchDataPlugins[] = ['apm', 'uptime', 'infra_logs', 'infra_metrics'];
const promises = apps.map(async (app) => getDataHandler(app)?.hasData() || false);
diff --git a/x-pack/plugins/observability/public/hooks/use_theme.tsx b/x-pack/plugins/observability/public/hooks/use_theme.tsx
index d0449a4432d93..51a1ad5029538 100644
--- a/x-pack/plugins/observability/public/hooks/use_theme.tsx
+++ b/x-pack/plugins/observability/public/hooks/use_theme.tsx
@@ -5,7 +5,7 @@
*/
import { useContext } from 'react';
import { ThemeContext } from 'styled-components';
-import { EuiTheme } from '../../../../legacy/common/eui_styled_components';
+import { EuiTheme } from '../../../xpack_legacy/common';
export function useTheme() {
const theme: EuiTheme = useContext(ThemeContext);
diff --git a/x-pack/plugins/observability/public/pages/landing/index.tsx b/x-pack/plugins/observability/public/pages/landing/index.tsx
index 4d8bd4bf2c789..66a52091ae04d 100644
--- a/x-pack/plugins/observability/public/pages/landing/index.tsx
+++ b/x-pack/plugins/observability/public/pages/landing/index.tsx
@@ -21,6 +21,7 @@ import styled, { ThemeContext } from 'styled-components';
import { IngestManagerPanel } from '../../components/app/ingest_manager_panel';
import { WithHeaderLayout } from '../../components/app/layout/with_header';
import { usePluginContext } from '../../hooks/use_plugin_context';
+import { useTrackPageview } from '../../hooks/use_track_metric';
import { appsSection } from '../home/section';
const EuiCardWithoutPadding = styled(EuiCard)`
@@ -28,6 +29,9 @@ const EuiCardWithoutPadding = styled(EuiCard)`
`;
export function LandingPage() {
+ useTrackPageview({ app: 'observability', path: 'landing' });
+ useTrackPageview({ app: 'observability', path: 'landing', delay: 15000 });
+
const { core } = usePluginContext();
const theme = useContext(ThemeContext);
diff --git a/x-pack/plugins/observability/public/pages/overview/index.tsx b/x-pack/plugins/observability/public/pages/overview/index.tsx
index 10bbdaaae34a8..3d10e4abcbb42 100644
--- a/x-pack/plugins/observability/public/pages/overview/index.tsx
+++ b/x-pack/plugins/observability/public/pages/overview/index.tsx
@@ -8,6 +8,7 @@ import React, { useContext } from 'react';
import { ThemeContext } from 'styled-components';
import { EmptySection } from '../../components/app/empty_section';
import { WithHeaderLayout } from '../../components/app/layout/with_header';
+import { NewsFeed } from '../../components/app/news_feed';
import { Resources } from '../../components/app/resources';
import { AlertsSection } from '../../components/app/section/alerts';
import { APMSection } from '../../components/app/section/apm';
@@ -15,18 +16,18 @@ import { LogsSection } from '../../components/app/section/logs';
import { MetricsSection } from '../../components/app/section/metrics';
import { UptimeSection } from '../../components/app/section/uptime';
import { DatePicker, TimePickerTime } from '../../components/shared/data_picker';
-import { NewsFeed } from '../../components/app/news_feed';
import { fetchHasData } from '../../data_handler';
import { FETCH_STATUS, useFetcher } from '../../hooks/use_fetcher';
import { UI_SETTINGS, useKibanaUISettings } from '../../hooks/use_kibana_ui_settings';
import { usePluginContext } from '../../hooks/use_plugin_context';
+import { useTrackPageview } from '../../hooks/use_track_metric';
import { RouteParams } from '../../routes';
+import { getNewsFeed } from '../../services/get_news_feed';
import { getObservabilityAlerts } from '../../services/get_observability_alerts';
import { getAbsoluteTime } from '../../utils/date';
import { getBucketSize } from '../../utils/get_bucket_size';
import { getEmptySections } from './empty_section';
import { LoadingObservability } from './loading_observability';
-import { getNewsFeed } from '../../services/get_news_feed';
interface Props {
routeParams: RouteParams<'/overview'>;
@@ -41,6 +42,9 @@ function calculatetBucketSize({ start, end }: { start?: number; end?: number })
export function OverviewPage({ routeParams }: Props) {
const { core } = usePluginContext();
+ useTrackPageview({ app: 'observability', path: 'overview' });
+ useTrackPageview({ app: 'observability', path: 'overview', delay: 15000 });
+
const { data: alerts = [], status: alertStatus } = useFetcher(() => {
return getObservabilityAlerts({ core });
}, [core]);
diff --git a/x-pack/plugins/observability/public/plugin.ts b/x-pack/plugins/observability/public/plugin.ts
index 0a82f37d10a7b..be8abb4dcac78 100644
--- a/x-pack/plugins/observability/public/plugin.ts
+++ b/x-pack/plugins/observability/public/plugin.ts
@@ -23,7 +23,7 @@ export interface ObservabilityPluginSetup {
dashboard: { register: typeof registerDataHandler };
}
-interface SetupPlugins {
+export interface ObservabilityPluginSetupDeps {
home?: HomePublicPluginSetup;
}
@@ -34,7 +34,7 @@ export class Plugin implements PluginClass = (
export type HasData = () => Promise;
-export interface DataHandler {
+export type ObservabilityFetchDataPlugins = Exclude;
+
+export interface DataHandler<
+ T extends ObservabilityFetchDataPlugins = ObservabilityFetchDataPlugins
+> {
fetchData: FetchData;
hasData: HasData;
}
diff --git a/x-pack/plugins/observability/public/typings/section/index.ts b/x-pack/plugins/observability/public/typings/section/index.ts
index f336b6b981687..d70d8ac8617bb 100644
--- a/x-pack/plugins/observability/public/typings/section/index.ts
+++ b/x-pack/plugins/observability/public/typings/section/index.ts
@@ -4,10 +4,10 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { ObservabilityApp } from '../../../typings/common';
+import { ObservabilityFetchDataPlugins } from '../fetch_overview_data';
export interface ISection {
- id: ObservabilityApp | 'alert';
+ id: ObservabilityFetchDataPlugins | 'alert';
title: string;
icon: string;
description: string;
diff --git a/x-pack/plugins/observability/typings/common.ts b/x-pack/plugins/observability/typings/common.ts
index 19afac0c0d2b8..c1b01c847f164 100644
--- a/x-pack/plugins/observability/typings/common.ts
+++ b/x-pack/plugins/observability/typings/common.ts
@@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-export type ObservabilityApp = 'infra_metrics' | 'infra_logs' | 'apm' | 'uptime';
+export type ObservabilityApp = 'infra_metrics' | 'infra_logs' | 'apm' | 'uptime' | 'observability';
export type PromiseReturnType = Func extends (...args: any[]) => Promise
? Value
diff --git a/x-pack/plugins/security_solution/common/endpoint/types/trusted_apps.ts b/x-pack/plugins/security_solution/common/endpoint/types/trusted_apps.ts
index 93e3305078f8d..62793388e34a6 100644
--- a/x-pack/plugins/security_solution/common/endpoint/types/trusted_apps.ts
+++ b/x-pack/plugins/security_solution/common/endpoint/types/trusted_apps.ts
@@ -6,10 +6,14 @@
import { TypeOf } from '@kbn/config-schema';
import {
+ DeleteTrustedAppsRequestSchema,
GetTrustedAppsRequestSchema,
PostTrustedAppCreateRequestSchema,
} from '../schema/trusted_apps';
+/** API request params for deleting Trusted App entry */
+export type DeleteTrustedAppsRequestParams = TypeOf;
+
/** API request params for retrieving a list of Trusted Apps */
export type GetTrustedAppsListRequest = TypeOf;
diff --git a/x-pack/plugins/security_solution/public/common/mock/endpoint/app_root_provider.tsx b/x-pack/plugins/security_solution/public/common/mock/endpoint/app_root_provider.tsx
index 73c0f00573911..3b7262e8a8d7e 100644
--- a/x-pack/plugins/security_solution/public/common/mock/endpoint/app_root_provider.tsx
+++ b/x-pack/plugins/security_solution/public/common/mock/endpoint/app_root_provider.tsx
@@ -11,7 +11,7 @@ import { Router } from 'react-router-dom';
import { History } from 'history';
import { useObservable } from 'react-use';
import { Store } from 'redux';
-import { EuiThemeProvider } from '../../../../../../legacy/common/eui_styled_components';
+import { EuiThemeProvider } from '../../../../../xpack_legacy/common';
import { KibanaContextProvider } from '../../../../../../../src/plugins/kibana_react/public';
import { RouteCapture } from '../../components/endpoint/route_capture';
import { StartPlugins } from '../../../types';
diff --git a/x-pack/plugins/security_solution/public/graphql/introspection.json b/x-pack/plugins/security_solution/public/graphql/introspection.json
index 81f76e3af4fde..d10e6b7dc54f0 100644
--- a/x-pack/plugins/security_solution/public/graphql/introspection.json
+++ b/x-pack/plugins/security_solution/public/graphql/introspection.json
@@ -2186,103 +2186,6 @@
"isDeprecated": false,
"deprecationReason": null
},
- {
- "name": "Tls",
- "description": "",
- "args": [
- {
- "name": "filterQuery",
- "description": "",
- "type": { "kind": "SCALAR", "name": "String", "ofType": null },
- "defaultValue": null
- },
- {
- "name": "id",
- "description": "",
- "type": { "kind": "SCALAR", "name": "String", "ofType": null },
- "defaultValue": null
- },
- {
- "name": "ip",
- "description": "",
- "type": {
- "kind": "NON_NULL",
- "name": null,
- "ofType": { "kind": "SCALAR", "name": "String", "ofType": null }
- },
- "defaultValue": null
- },
- {
- "name": "pagination",
- "description": "",
- "type": {
- "kind": "NON_NULL",
- "name": null,
- "ofType": {
- "kind": "INPUT_OBJECT",
- "name": "PaginationInputPaginated",
- "ofType": null
- }
- },
- "defaultValue": null
- },
- {
- "name": "sort",
- "description": "",
- "type": {
- "kind": "NON_NULL",
- "name": null,
- "ofType": { "kind": "INPUT_OBJECT", "name": "TlsSortField", "ofType": null }
- },
- "defaultValue": null
- },
- {
- "name": "flowTarget",
- "description": "",
- "type": {
- "kind": "NON_NULL",
- "name": null,
- "ofType": { "kind": "ENUM", "name": "FlowTargetSourceDest", "ofType": null }
- },
- "defaultValue": null
- },
- {
- "name": "timerange",
- "description": "",
- "type": {
- "kind": "NON_NULL",
- "name": null,
- "ofType": { "kind": "INPUT_OBJECT", "name": "TimerangeInput", "ofType": null }
- },
- "defaultValue": null
- },
- {
- "name": "defaultIndex",
- "description": "",
- "type": {
- "kind": "NON_NULL",
- "name": null,
- "ofType": {
- "kind": "LIST",
- "name": null,
- "ofType": {
- "kind": "NON_NULL",
- "name": null,
- "ofType": { "kind": "SCALAR", "name": "String", "ofType": null }
- }
- }
- },
- "defaultValue": null
- }
- ],
- "type": {
- "kind": "NON_NULL",
- "name": null,
- "ofType": { "kind": "OBJECT", "name": "TlsData", "ofType": null }
- },
- "isDeprecated": false,
- "deprecationReason": null
- },
{
"name": "UncommonProcesses",
"description": "Gets UncommonProcesses based on a timerange, or all UncommonProcesses if no criteria is specified",
@@ -9297,238 +9200,6 @@
"enumValues": null,
"possibleTypes": null
},
- {
- "kind": "INPUT_OBJECT",
- "name": "TlsSortField",
- "description": "",
- "fields": null,
- "inputFields": [
- {
- "name": "field",
- "description": "",
- "type": {
- "kind": "NON_NULL",
- "name": null,
- "ofType": { "kind": "ENUM", "name": "TlsFields", "ofType": null }
- },
- "defaultValue": null
- },
- {
- "name": "direction",
- "description": "",
- "type": {
- "kind": "NON_NULL",
- "name": null,
- "ofType": { "kind": "ENUM", "name": "Direction", "ofType": null }
- },
- "defaultValue": null
- }
- ],
- "interfaces": null,
- "enumValues": null,
- "possibleTypes": null
- },
- {
- "kind": "ENUM",
- "name": "TlsFields",
- "description": "",
- "fields": null,
- "inputFields": null,
- "interfaces": null,
- "enumValues": [
- { "name": "_id", "description": "", "isDeprecated": false, "deprecationReason": null }
- ],
- "possibleTypes": null
- },
- {
- "kind": "OBJECT",
- "name": "TlsData",
- "description": "",
- "fields": [
- {
- "name": "edges",
- "description": "",
- "args": [],
- "type": {
- "kind": "NON_NULL",
- "name": null,
- "ofType": {
- "kind": "LIST",
- "name": null,
- "ofType": {
- "kind": "NON_NULL",
- "name": null,
- "ofType": { "kind": "OBJECT", "name": "TlsEdges", "ofType": null }
- }
- }
- },
- "isDeprecated": false,
- "deprecationReason": null
- },
- {
- "name": "totalCount",
- "description": "",
- "args": [],
- "type": {
- "kind": "NON_NULL",
- "name": null,
- "ofType": { "kind": "SCALAR", "name": "Float", "ofType": null }
- },
- "isDeprecated": false,
- "deprecationReason": null
- },
- {
- "name": "pageInfo",
- "description": "",
- "args": [],
- "type": {
- "kind": "NON_NULL",
- "name": null,
- "ofType": { "kind": "OBJECT", "name": "PageInfoPaginated", "ofType": null }
- },
- "isDeprecated": false,
- "deprecationReason": null
- },
- {
- "name": "inspect",
- "description": "",
- "args": [],
- "type": { "kind": "OBJECT", "name": "Inspect", "ofType": null },
- "isDeprecated": false,
- "deprecationReason": null
- }
- ],
- "inputFields": null,
- "interfaces": [],
- "enumValues": null,
- "possibleTypes": null
- },
- {
- "kind": "OBJECT",
- "name": "TlsEdges",
- "description": "",
- "fields": [
- {
- "name": "node",
- "description": "",
- "args": [],
- "type": {
- "kind": "NON_NULL",
- "name": null,
- "ofType": { "kind": "OBJECT", "name": "TlsNode", "ofType": null }
- },
- "isDeprecated": false,
- "deprecationReason": null
- },
- {
- "name": "cursor",
- "description": "",
- "args": [],
- "type": {
- "kind": "NON_NULL",
- "name": null,
- "ofType": { "kind": "OBJECT", "name": "CursorType", "ofType": null }
- },
- "isDeprecated": false,
- "deprecationReason": null
- }
- ],
- "inputFields": null,
- "interfaces": [],
- "enumValues": null,
- "possibleTypes": null
- },
- {
- "kind": "OBJECT",
- "name": "TlsNode",
- "description": "",
- "fields": [
- {
- "name": "_id",
- "description": "",
- "args": [],
- "type": { "kind": "SCALAR", "name": "String", "ofType": null },
- "isDeprecated": false,
- "deprecationReason": null
- },
- {
- "name": "timestamp",
- "description": "",
- "args": [],
- "type": { "kind": "SCALAR", "name": "Date", "ofType": null },
- "isDeprecated": false,
- "deprecationReason": null
- },
- {
- "name": "notAfter",
- "description": "",
- "args": [],
- "type": {
- "kind": "LIST",
- "name": null,
- "ofType": {
- "kind": "NON_NULL",
- "name": null,
- "ofType": { "kind": "SCALAR", "name": "String", "ofType": null }
- }
- },
- "isDeprecated": false,
- "deprecationReason": null
- },
- {
- "name": "subjects",
- "description": "",
- "args": [],
- "type": {
- "kind": "LIST",
- "name": null,
- "ofType": {
- "kind": "NON_NULL",
- "name": null,
- "ofType": { "kind": "SCALAR", "name": "String", "ofType": null }
- }
- },
- "isDeprecated": false,
- "deprecationReason": null
- },
- {
- "name": "ja3",
- "description": "",
- "args": [],
- "type": {
- "kind": "LIST",
- "name": null,
- "ofType": {
- "kind": "NON_NULL",
- "name": null,
- "ofType": { "kind": "SCALAR", "name": "String", "ofType": null }
- }
- },
- "isDeprecated": false,
- "deprecationReason": null
- },
- {
- "name": "issuers",
- "description": "",
- "args": [],
- "type": {
- "kind": "LIST",
- "name": null,
- "ofType": {
- "kind": "NON_NULL",
- "name": null,
- "ofType": { "kind": "SCALAR", "name": "String", "ofType": null }
- }
- },
- "isDeprecated": false,
- "deprecationReason": null
- }
- ],
- "inputFields": null,
- "interfaces": [],
- "enumValues": null,
- "possibleTypes": null
- },
{
"kind": "OBJECT",
"name": "UncommonProcessesData",
diff --git a/x-pack/plugins/security_solution/public/graphql/types.ts b/x-pack/plugins/security_solution/public/graphql/types.ts
index 0fb27f9f88ad3..ac8049ab11358 100644
--- a/x-pack/plugins/security_solution/public/graphql/types.ts
+++ b/x-pack/plugins/security_solution/public/graphql/types.ts
@@ -95,12 +95,6 @@ export interface NetworkHttpSortField {
direction: Direction;
}
-export interface TlsSortField {
- field: TlsFields;
-
- direction: Direction;
-}
-
export interface PageInfoTimeline {
pageIndex: number;
@@ -356,10 +350,6 @@ export enum NetworkDnsFields {
dnsBytesOut = 'dnsBytesOut',
}
-export enum TlsFields {
- _id = '_id',
-}
-
export enum DataProviderType {
default = 'default',
template = 'template',
@@ -570,8 +560,6 @@ export interface Source {
OverviewNetwork?: Maybe;
OverviewHost?: Maybe;
-
- Tls: TlsData;
/** Gets UncommonProcesses based on a timerange, or all UncommonProcesses if no criteria is specified */
UncommonProcesses: UncommonProcessesData;
/** Just a simple example to get the app name */
@@ -1904,36 +1892,6 @@ export interface OverviewHostData {
inspect?: Maybe;
}
-export interface TlsData {
- edges: TlsEdges[];
-
- totalCount: number;
-
- pageInfo: PageInfoPaginated;
-
- inspect?: Maybe;
-}
-
-export interface TlsEdges {
- node: TlsNode;
-
- cursor: CursorType;
-}
-
-export interface TlsNode {
- _id?: Maybe;
-
- timestamp?: Maybe;
-
- notAfter?: Maybe;
-
- subjects?: Maybe;
-
- ja3?: Maybe;
-
- issuers?: Maybe;
-}
-
export interface UncommonProcessesData {
edges: UncommonProcessesEdges[];
@@ -2577,23 +2535,6 @@ export interface OverviewHostSourceArgs {
defaultIndex: string[];
}
-export interface TlsSourceArgs {
- filterQuery?: Maybe;
-
- id?: Maybe;
-
- ip: string;
-
- pagination: PaginationInputPaginated;
-
- sort: TlsSortField;
-
- flowTarget: FlowTargetSourceDest;
-
- timerange: TimerangeInput;
-
- defaultIndex: string[];
-}
export interface UncommonProcessesSourceArgs {
timerange: TimerangeInput;
@@ -4068,92 +4009,6 @@ export namespace GetNetworkTopNFlowQuery {
};
}
-export namespace GetTlsQuery {
- export type Variables = {
- sourceId: string;
- filterQuery?: Maybe;
- flowTarget: FlowTargetSourceDest;
- ip: string;
- pagination: PaginationInputPaginated;
- sort: TlsSortField;
- timerange: TimerangeInput;
- defaultIndex: string[];
- inspect: boolean;
- };
-
- export type Query = {
- __typename?: 'Query';
-
- source: Source;
- };
-
- export type Source = {
- __typename?: 'Source';
-
- id: string;
-
- Tls: Tls;
- };
-
- export type Tls = {
- __typename?: 'TlsData';
-
- totalCount: number;
-
- edges: Edges[];
-
- pageInfo: PageInfo;
-
- inspect: Maybe;
- };
-
- export type Edges = {
- __typename?: 'TlsEdges';
-
- node: Node;
-
- cursor: Cursor;
- };
-
- export type Node = {
- __typename?: 'TlsNode';
-
- _id: Maybe;
-
- subjects: Maybe;
-
- ja3: Maybe;
-
- issuers: Maybe;
-
- notAfter: Maybe;
- };
-
- export type Cursor = {
- __typename?: 'CursorType';
-
- value: Maybe;
- };
-
- export type PageInfo = {
- __typename?: 'PageInfoPaginated';
-
- activePage: number;
-
- fakeTotalCount: number;
-
- showMorePagesIndicator: boolean;
- };
-
- export type Inspect = {
- __typename?: 'Inspect';
-
- dsl: string[];
-
- response: string[];
- };
-}
-
export namespace GetUsersQuery {
export type Variables = {
sourceId: string;
diff --git a/x-pack/plugins/security_solution/public/management/common/routing.ts b/x-pack/plugins/security_solution/public/management/common/routing.ts
index 40320ed794203..cb4ed9b098fce 100644
--- a/x-pack/plugins/security_solution/public/management/common/routing.ts
+++ b/x-pack/plugins/security_solution/public/management/common/routing.ts
@@ -90,10 +90,7 @@ export const getPolicyDetailPath = (policyId: string, search?: string) => {
})}${appendSearch(search)}`;
};
-const isDefaultOrMissing = (
- value: number | string | undefined,
- defaultValue: number | undefined
-) => {
+const isDefaultOrMissing = (value: T | undefined, defaultValue: T) => {
return value === undefined || value === defaultValue;
};
diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/vertical_divider.ts b/x-pack/plugins/security_solution/public/management/pages/policy/view/vertical_divider.ts
index 6a3aecb4a6503..b6f5c9b7421b5 100644
--- a/x-pack/plugins/security_solution/public/management/pages/policy/view/vertical_divider.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/vertical_divider.ts
@@ -5,7 +5,7 @@
*/
import styled from 'styled-components';
-import { EuiTheme } from '../../../../../../../legacy/common/eui_styled_components';
+import { EuiTheme } from '../../../../../../xpack_legacy/common';
type SpacingOptions = keyof EuiTheme['eui']['spacerSizes'];
diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/service/index.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/service/index.ts
index a3c5911aa3a86..4fb1e1b4575c8 100644
--- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/service/index.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/service/index.ts
@@ -5,19 +5,26 @@
*/
import { HttpStart } from 'kibana/public';
+
import {
TRUSTED_APPS_CREATE_API,
+ TRUSTED_APPS_DELETE_API,
TRUSTED_APPS_LIST_API,
} from '../../../../../common/endpoint/constants';
+
import {
+ DeleteTrustedAppsRequestParams,
GetTrustedListAppsResponse,
GetTrustedAppsListRequest,
PostTrustedAppCreateRequest,
PostTrustedAppCreateResponse,
} from '../../../../../common/endpoint/types/trusted_apps';
+import { resolvePathVariables } from './utils';
+
export interface TrustedAppsService {
getTrustedAppsList(request: GetTrustedAppsListRequest): Promise;
+ deleteTrustedApp(request: DeleteTrustedAppsRequestParams): Promise;
createTrustedApp(request: PostTrustedAppCreateRequest): Promise;
}
@@ -30,6 +37,10 @@ export class TrustedAppsHttpService implements TrustedAppsService {
});
}
+ async deleteTrustedApp(request: DeleteTrustedAppsRequestParams): Promise {
+ return this.http.delete(resolvePathVariables(TRUSTED_APPS_DELETE_API, request));
+ }
+
async createTrustedApp(request: PostTrustedAppCreateRequest) {
return this.http.post(TRUSTED_APPS_CREATE_API, {
body: JSON.stringify(request),
diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/service/utils.test.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/service/utils.test.ts
new file mode 100644
index 0000000000000..c937b318e8961
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/service/utils.test.ts
@@ -0,0 +1,44 @@
+/*
+ * 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 { resolvePathVariables } from './utils';
+
+describe('utils', () => {
+ describe('resolvePathVariables', () => {
+ it('should resolve defined variables', () => {
+ expect(resolvePathVariables('/segment1/{var1}/segment2', { var1: 'value1' })).toBe(
+ '/segment1/value1/segment2'
+ );
+ });
+
+ it('should not resolve undefined variables', () => {
+ expect(resolvePathVariables('/segment1/{var1}/segment2', {})).toBe(
+ '/segment1/{var1}/segment2'
+ );
+ });
+
+ it('should ignore unused variables', () => {
+ expect(resolvePathVariables('/segment1/{var1}/segment2', { var2: 'value2' })).toBe(
+ '/segment1/{var1}/segment2'
+ );
+ });
+
+ it('should replace multiple variable occurences', () => {
+ expect(resolvePathVariables('/{var1}/segment1/{var1}', { var1: 'value1' })).toBe(
+ '/value1/segment1/value1'
+ );
+ });
+
+ it('should replace multiple variables', () => {
+ const path = resolvePathVariables('/{var1}/segment1/{var2}', {
+ var1: 'value1',
+ var2: 'value2',
+ });
+
+ expect(path).toBe('/value1/segment1/value2');
+ });
+ });
+});
diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/service/utils.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/service/utils.ts
new file mode 100644
index 0000000000000..075d74da018b4
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/service/utils.ts
@@ -0,0 +1,10 @@
+/*
+ * 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 resolvePathVariables = (path: string, variables: { [K: string]: string | number }) =>
+ Object.keys(variables).reduce((acc, paramName) => {
+ return acc.replace(new RegExp(`\{${paramName}\}`, 'g'), String(variables[paramName]));
+ }, path);
diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/state/async_resource_state.test.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/state/async_resource_state.test.ts
index 5e00d833981ed..534a4ec14861b 100644
--- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/state/async_resource_state.test.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/state/async_resource_state.test.ts
@@ -13,6 +13,7 @@ import {
isLoadingResourceState,
isLoadedResourceState,
isFailedResourceState,
+ isStaleResourceState,
getLastLoadedResourceState,
getCurrentResourceError,
isOutdatedResourceState,
@@ -137,6 +138,24 @@ describe('AsyncResourceState', () => {
expect(isFailedResourceState(failedResourceStateInitially)).toBe(true);
});
});
+
+ describe('isStaleResourceState()', () => {
+ it('returns true for UninitialisedResourceState', () => {
+ expect(isStaleResourceState(uninitialisedResourceState)).toBe(true);
+ });
+
+ it('returns false for LoadingResourceState', () => {
+ expect(isStaleResourceState(loadingResourceStateInitially)).toBe(false);
+ });
+
+ it('returns true for LoadedResourceState', () => {
+ expect(isStaleResourceState(loadedResourceState)).toBe(true);
+ });
+
+ it('returns true for FailedResourceState', () => {
+ expect(isStaleResourceState(failedResourceStateInitially)).toBe(true);
+ });
+ });
});
describe('functions', () => {
diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/state/async_resource_state.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/state/async_resource_state.ts
index 4639a50a61865..bb868418e7f0d 100644
--- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/state/async_resource_state.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/state/async_resource_state.ts
@@ -35,7 +35,7 @@ export interface UninitialisedResourceState {
* @param Data - type of the data that is referenced by resource state
* @param Error - type of the error that can happen during attempt to update data
*/
-export interface LoadingResourceState {
+export interface LoadingResourceState {
type: 'LoadingResourceState';
previousState: StaleResourceState;
}
@@ -46,7 +46,7 @@ export interface LoadingResourceState {
*
* @param Data - type of the data that is referenced by resource state
*/
-export interface LoadedResourceState {
+export interface LoadedResourceState {
type: 'LoadedResourceState';
data: Data;
}
@@ -59,7 +59,7 @@ export interface LoadedResourceState {
* @param Data - type of the data that is referenced by resource state
* @param Error - type of the error that can happen during attempt to update data
*/
-export interface FailedResourceState {
+export interface FailedResourceState {
type: 'FailedResourceState';
error: Error;
lastLoadedState?: LoadedResourceState;
@@ -71,7 +71,7 @@ export interface FailedResourceState {
* @param Data - type of the data that is referenced by resource state
* @param Error - type of the error that can happen during attempt to update data
*/
-export type StaleResourceState =
+export type StaleResourceState =
| UninitialisedResourceState
| LoadedResourceState
| FailedResourceState;
@@ -82,7 +82,7 @@ export type StaleResourceState =
* @param Data - type of the data that is referenced by resource state
* @param Error - type of the error that can happen during attempt to update data
*/
-export type AsyncResourceState =
+export type AsyncResourceState =
| UninitialisedResourceState
| LoadingResourceState
| LoadedResourceState
@@ -106,6 +106,13 @@ export const isFailedResourceState = (
state: Immutable>
): state is Immutable> => state.type === 'FailedResourceState';
+export const isStaleResourceState = (
+ state: Immutable>
+): state is Immutable> =>
+ isUninitialisedResourceState(state) ||
+ isLoadedResourceState(state) ||
+ isFailedResourceState(state);
+
// Set of functions to work with AsyncResourceState
export const getLastLoadedResourceState = (
diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/state/trusted_apps_list_page_state.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/state/trusted_apps_list_page_state.ts
index 071557ec1a815..4c38ac0c4239a 100644
--- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/state/trusted_apps_list_page_state.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/state/trusted_apps_list_page_state.ts
@@ -4,10 +4,10 @@
* you may not use this file except in compliance with the Elastic License.
*/
+import { ServerApiError } from '../../../../common/types';
import { NewTrustedApp, TrustedApp } from '../../../../../common/endpoint/types/trusted_apps';
import { AsyncResourceState } from '.';
import { TrustedAppsUrlParams } from '../types';
-import { ServerApiError } from '../../../../common/types';
export interface PaginationInfo {
index: number;
@@ -18,6 +18,7 @@ export interface TrustedAppsListData {
items: TrustedApp[];
totalItemsCount: number;
paginationInfo: PaginationInfo;
+ timestamp: number;
}
/** Store State when an API request has been sent to create a new trusted app entry */
@@ -42,8 +43,14 @@ export interface TrustedAppsListPageState {
listView: {
currentListResourceState: AsyncResourceState;
currentPaginationInfo: PaginationInfo;
+ freshDataTimestamp: number;
show: TrustedAppsUrlParams['show'] | undefined;
};
+ deletionDialog: {
+ entry?: TrustedApp;
+ confirmed: boolean;
+ submissionResourceState: AsyncResourceState;
+ };
createView:
| undefined
| TrustedAppCreatePending
diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/action.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/action.ts
index 3a43ffe58262c..5315087c09655 100644
--- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/action.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/action.ts
@@ -4,6 +4,9 @@
* you may not use this file except in compliance with the Elastic License.
*/
+import { Action } from 'redux';
+
+import { TrustedApp } from '../../../../../common/endpoint/types';
import {
AsyncResourceState,
TrustedAppCreateFailure,
@@ -12,12 +15,30 @@ import {
TrustedAppsListData,
} from '../state';
-export interface TrustedAppsListResourceStateChanged {
- type: 'trustedAppsListResourceStateChanged';
+export type TrustedAppsListDataOutdated = Action<'trustedAppsListDataOutdated'>;
+
+interface ResourceStateChanged extends Action {
+ payload: { newState: AsyncResourceState };
+}
+
+export type TrustedAppsListResourceStateChanged = ResourceStateChanged<
+ 'trustedAppsListResourceStateChanged',
+ TrustedAppsListData
+>;
+
+export type TrustedAppDeletionSubmissionResourceStateChanged = ResourceStateChanged<
+ 'trustedAppDeletionSubmissionResourceStateChanged'
+>;
+
+export type TrustedAppDeletionDialogStarted = Action<'trustedAppDeletionDialogStarted'> & {
payload: {
- newState: AsyncResourceState;
+ entry: TrustedApp;
};
-}
+};
+
+export type TrustedAppDeletionDialogConfirmed = Action<'trustedAppDeletionDialogConfirmed'>;
+
+export type TrustedAppDeletionDialogClosed = Action<'trustedAppDeletionDialogClosed'>;
export interface UserClickedSaveNewTrustedAppButton {
type: 'userClickedSaveNewTrustedAppButton';
@@ -35,7 +56,12 @@ export interface ServerReturnedCreateTrustedAppFailure {
}
export type TrustedAppsPageAction =
+ | TrustedAppsListDataOutdated
| TrustedAppsListResourceStateChanged
+ | TrustedAppDeletionSubmissionResourceStateChanged
+ | TrustedAppDeletionDialogStarted
+ | TrustedAppDeletionDialogConfirmed
+ | TrustedAppDeletionDialogClosed
| UserClickedSaveNewTrustedAppButton
| ServerReturnedCreateTrustedAppSuccess
| ServerReturnedCreateTrustedAppFailure;
diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/middleware.test.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/middleware.test.ts
index e5f00ee0ccf81..19c2d3a62781f 100644
--- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/middleware.test.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/middleware.test.ts
@@ -10,8 +10,10 @@ import { createSpyMiddleware } from '../../../../common/store/test_utils';
import {
createFailedListViewWithPagination,
+ createListLoadedResourceState,
createLoadedListViewWithPagination,
createLoadingListViewWithPagination,
+ createSampleTrustedApp,
createSampleTrustedApps,
createServerApiError,
createUserChangedUrlAction,
@@ -22,6 +24,14 @@ import { PaginationInfo, TrustedAppsListPageState } from '../state';
import { initialTrustedAppsPageState, trustedAppsPageReducer } from './reducer';
import { createTrustedAppsPageMiddleware } from './middleware';
+const initialNow = 111111;
+const dateNowMock = jest.fn();
+dateNowMock.mockReturnValue(initialNow);
+
+Date.now = dateNowMock;
+
+const initialState = initialTrustedAppsPageState();
+
const createGetTrustedListAppsResponse = (pagination: PaginationInfo, totalItemsCount: number) => ({
data: createSampleTrustedApps(pagination),
page: pagination.index,
@@ -31,6 +41,7 @@ const createGetTrustedListAppsResponse = (pagination: PaginationInfo, totalItems
const createTrustedAppsServiceMock = (): jest.Mocked => ({
getTrustedAppsList: jest.fn(),
+ deleteTrustedApp: jest.fn(),
createTrustedApp: jest.fn(),
});
@@ -50,13 +61,19 @@ const createStoreSetup = (trustedAppsService: TrustedAppsService) => {
};
describe('middleware', () => {
- describe('refreshing list resource state', () => {
+ beforeEach(() => {
+ dateNowMock.mockReturnValue(initialNow);
+ });
+
+ describe('initial state', () => {
it('sets initial state properly', async () => {
expect(createStoreSetup(createTrustedAppsServiceMock()).store.getState()).toStrictEqual(
- initialTrustedAppsPageState
+ initialState
);
});
+ });
+ describe('refreshing list resource state', () => {
it('refreshes the list when location changes and data gets outdated', async () => {
const pagination = { index: 2, size: 50 };
const service = createTrustedAppsServiceMock();
@@ -69,17 +86,17 @@ describe('middleware', () => {
store.dispatch(createUserChangedUrlAction('/trusted_apps', '?page_index=2&page_size=50'));
expect(store.getState()).toStrictEqual({
- listView: createLoadingListViewWithPagination(pagination),
+ ...initialState,
+ listView: createLoadingListViewWithPagination(initialNow, pagination),
active: true,
- createView: undefined,
});
await spyMiddleware.waitForAction('trustedAppsListResourceStateChanged');
expect(store.getState()).toStrictEqual({
- listView: createLoadedListViewWithPagination(pagination, pagination, 500),
+ ...initialState,
+ listView: createLoadedListViewWithPagination(initialNow, pagination, pagination, 500),
active: true,
- createView: undefined,
});
});
@@ -100,13 +117,50 @@ describe('middleware', () => {
expect(service.getTrustedAppsList).toBeCalledTimes(1);
expect(store.getState()).toStrictEqual({
- listView: createLoadedListViewWithPagination(pagination, pagination, 500),
+ ...initialState,
+ listView: createLoadedListViewWithPagination(initialNow, pagination, pagination, 500),
active: true,
- createView: undefined,
});
});
- it('set list resource state to faile when failing to load data', async () => {
+ it('refreshes the list when data gets outdated with and outdate action', async () => {
+ const newNow = 222222;
+ const pagination = { index: 0, size: 10 };
+ const service = createTrustedAppsServiceMock();
+ const { store, spyMiddleware } = createStoreSetup(service);
+
+ service.getTrustedAppsList.mockResolvedValue(
+ createGetTrustedListAppsResponse(pagination, 500)
+ );
+
+ store.dispatch(createUserChangedUrlAction('/trusted_apps'));
+
+ await spyMiddleware.waitForAction('trustedAppsListResourceStateChanged');
+
+ dateNowMock.mockReturnValue(newNow);
+
+ store.dispatch({ type: 'trustedAppsListDataOutdated' });
+
+ expect(store.getState()).toStrictEqual({
+ ...initialState,
+ listView: createLoadingListViewWithPagination(
+ newNow,
+ pagination,
+ createListLoadedResourceState(pagination, 500, initialNow)
+ ),
+ active: true,
+ });
+
+ await spyMiddleware.waitForAction('trustedAppsListResourceStateChanged');
+
+ expect(store.getState()).toStrictEqual({
+ ...initialState,
+ listView: createLoadedListViewWithPagination(newNow, pagination, pagination, 500),
+ active: true,
+ });
+ });
+
+ it('set list resource state to failed when failing to load data', async () => {
const service = createTrustedAppsServiceMock();
const { store, spyMiddleware } = createStoreSetup(service);
@@ -117,12 +171,13 @@ describe('middleware', () => {
await spyMiddleware.waitForAction('trustedAppsListResourceStateChanged');
expect(store.getState()).toStrictEqual({
+ ...initialState,
listView: createFailedListViewWithPagination(
+ initialNow,
{ index: 2, size: 50 },
createServerApiError('Internal Server Error')
),
active: true,
- createView: undefined,
});
const infiniteLoopTest = async () => {
@@ -132,4 +187,151 @@ describe('middleware', () => {
await expect(infiniteLoopTest).rejects.not.toBeNull();
});
});
+
+ describe('submitting deletion dialog', () => {
+ const newNow = 222222;
+ const entry = createSampleTrustedApp(3);
+ const notFoundError = createServerApiError('Not Found');
+ const pagination = { index: 0, size: 10 };
+ const getTrustedAppsListResponse = createGetTrustedListAppsResponse(pagination, 500);
+ const listView = createLoadedListViewWithPagination(initialNow, pagination, pagination, 500);
+ const listViewNew = createLoadedListViewWithPagination(newNow, pagination, pagination, 500);
+ const testStartState = { ...initialState, listView, active: true };
+
+ it('does not submit when entry is undefined', async () => {
+ const service = createTrustedAppsServiceMock();
+ const { store, spyMiddleware } = createStoreSetup(service);
+
+ service.getTrustedAppsList.mockResolvedValue(getTrustedAppsListResponse);
+ service.deleteTrustedApp.mockResolvedValue();
+
+ store.dispatch(createUserChangedUrlAction('/trusted_apps'));
+
+ await spyMiddleware.waitForAction('trustedAppsListResourceStateChanged');
+
+ store.dispatch({ type: 'trustedAppDeletionDialogConfirmed' });
+
+ expect(store.getState()).toStrictEqual({
+ ...testStartState,
+ deletionDialog: { ...testStartState.deletionDialog, confirmed: true },
+ });
+ });
+
+ it('submits successfully when entry is defined', async () => {
+ const service = createTrustedAppsServiceMock();
+ const { store, spyMiddleware } = createStoreSetup(service);
+
+ service.getTrustedAppsList.mockResolvedValue(getTrustedAppsListResponse);
+ service.deleteTrustedApp.mockResolvedValue();
+
+ store.dispatch(createUserChangedUrlAction('/trusted_apps'));
+
+ await spyMiddleware.waitForAction('trustedAppsListResourceStateChanged');
+
+ dateNowMock.mockReturnValue(newNow);
+
+ store.dispatch({ type: 'trustedAppDeletionDialogStarted', payload: { entry } });
+ store.dispatch({ type: 'trustedAppDeletionDialogConfirmed' });
+
+ expect(store.getState()).toStrictEqual({
+ ...testStartState,
+ deletionDialog: {
+ entry,
+ confirmed: true,
+ submissionResourceState: {
+ type: 'LoadingResourceState',
+ previousState: { type: 'UninitialisedResourceState' },
+ },
+ },
+ });
+
+ await spyMiddleware.waitForAction('trustedAppDeletionSubmissionResourceStateChanged');
+ await spyMiddleware.waitForAction('trustedAppsListResourceStateChanged');
+
+ expect(store.getState()).toStrictEqual({ ...testStartState, listView: listViewNew });
+ expect(service.deleteTrustedApp).toBeCalledWith({ id: '3' });
+ expect(service.deleteTrustedApp).toBeCalledTimes(1);
+ });
+
+ it('does not submit twice', async () => {
+ const service = createTrustedAppsServiceMock();
+ const { store, spyMiddleware } = createStoreSetup(service);
+
+ service.getTrustedAppsList.mockResolvedValue(getTrustedAppsListResponse);
+ service.deleteTrustedApp.mockResolvedValue();
+
+ store.dispatch(createUserChangedUrlAction('/trusted_apps'));
+
+ await spyMiddleware.waitForAction('trustedAppsListResourceStateChanged');
+
+ dateNowMock.mockReturnValue(newNow);
+
+ store.dispatch({ type: 'trustedAppDeletionDialogStarted', payload: { entry } });
+ store.dispatch({ type: 'trustedAppDeletionDialogConfirmed' });
+ store.dispatch({ type: 'trustedAppDeletionDialogConfirmed' });
+
+ expect(store.getState()).toStrictEqual({
+ ...testStartState,
+ deletionDialog: {
+ entry,
+ confirmed: true,
+ submissionResourceState: {
+ type: 'LoadingResourceState',
+ previousState: { type: 'UninitialisedResourceState' },
+ },
+ },
+ });
+
+ await spyMiddleware.waitForAction('trustedAppDeletionSubmissionResourceStateChanged');
+ await spyMiddleware.waitForAction('trustedAppsListResourceStateChanged');
+
+ expect(store.getState()).toStrictEqual({ ...testStartState, listView: listViewNew });
+ expect(service.deleteTrustedApp).toBeCalledWith({ id: '3' });
+ expect(service.deleteTrustedApp).toBeCalledTimes(1);
+ });
+
+ it('does not submit when server response with failure', async () => {
+ const service = createTrustedAppsServiceMock();
+ const { store, spyMiddleware } = createStoreSetup(service);
+
+ service.getTrustedAppsList.mockResolvedValue(getTrustedAppsListResponse);
+ service.deleteTrustedApp.mockRejectedValue(notFoundError);
+
+ store.dispatch(createUserChangedUrlAction('/trusted_apps'));
+
+ await spyMiddleware.waitForAction('trustedAppsListResourceStateChanged');
+
+ store.dispatch({ type: 'trustedAppDeletionDialogStarted', payload: { entry } });
+ store.dispatch({ type: 'trustedAppDeletionDialogConfirmed' });
+
+ expect(store.getState()).toStrictEqual({
+ ...testStartState,
+ deletionDialog: {
+ entry,
+ confirmed: true,
+ submissionResourceState: {
+ type: 'LoadingResourceState',
+ previousState: { type: 'UninitialisedResourceState' },
+ },
+ },
+ });
+
+ await spyMiddleware.waitForAction('trustedAppDeletionSubmissionResourceStateChanged');
+
+ expect(store.getState()).toStrictEqual({
+ ...testStartState,
+ deletionDialog: {
+ entry,
+ confirmed: true,
+ submissionResourceState: {
+ type: 'FailedResourceState',
+ error: notFoundError,
+ lastLoadedState: undefined,
+ },
+ },
+ });
+ expect(service.deleteTrustedApp).toBeCalledWith({ id: '3' });
+ expect(service.deleteTrustedApp).toBeCalledTimes(1);
+ });
+ });
});
diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/middleware.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/middleware.ts
index bf9cacff5caf0..dd96c8d807048 100644
--- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/middleware.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/middleware.ts
@@ -16,15 +16,22 @@ import { TrustedAppsHttpService, TrustedAppsService } from '../service';
import {
AsyncResourceState,
+ getLastLoadedResourceState,
+ isStaleResourceState,
StaleResourceState,
TrustedAppsListData,
TrustedAppsListPageState,
} from '../state';
-import { TrustedAppsListResourceStateChanged } from './action';
+import {
+ TrustedAppDeletionSubmissionResourceStateChanged,
+ TrustedAppsListResourceStateChanged,
+} from './action';
import {
getCurrentListResourceState,
+ getDeletionDialogEntry,
+ getDeletionSubmissionResourceState,
getLastLoadedListResourceState,
getListCurrentPageIndex,
getListCurrentPageSize,
@@ -40,46 +47,98 @@ const createTrustedAppsListResourceStateChangedAction = (
payload: { newState },
});
-const refreshList = async (
+const refreshListIfNeeded = async (
store: ImmutableMiddlewareAPI,
trustedAppsService: TrustedAppsService
) => {
- store.dispatch(
- createTrustedAppsListResourceStateChangedAction({
- type: 'LoadingResourceState',
- // need to think on how to avoid the casting
- previousState: getCurrentListResourceState(store.getState()) as Immutable<
- StaleResourceState
- >,
- })
- );
-
- try {
- const pageIndex = getListCurrentPageIndex(store.getState());
- const pageSize = getListCurrentPageSize(store.getState());
- const response = await trustedAppsService.getTrustedAppsList({
- page: pageIndex + 1,
- per_page: pageSize,
- });
-
+ if (needsRefreshOfListData(store.getState())) {
store.dispatch(
createTrustedAppsListResourceStateChangedAction({
- type: 'LoadedResourceState',
- data: {
- items: response.data,
- totalItemsCount: response.total,
- paginationInfo: { index: pageIndex, size: pageSize },
- },
+ type: 'LoadingResourceState',
+ // need to think on how to avoid the casting
+ previousState: getCurrentListResourceState(store.getState()) as Immutable<
+ StaleResourceState
+ >,
})
);
- } catch (error) {
+
+ try {
+ const pageIndex = getListCurrentPageIndex(store.getState());
+ const pageSize = getListCurrentPageSize(store.getState());
+ const response = await trustedAppsService.getTrustedAppsList({
+ page: pageIndex + 1,
+ per_page: pageSize,
+ });
+
+ store.dispatch(
+ createTrustedAppsListResourceStateChangedAction({
+ type: 'LoadedResourceState',
+ data: {
+ items: response.data,
+ totalItemsCount: response.total,
+ paginationInfo: { index: pageIndex, size: pageSize },
+ timestamp: Date.now(),
+ },
+ })
+ );
+ } catch (error) {
+ store.dispatch(
+ createTrustedAppsListResourceStateChangedAction({
+ type: 'FailedResourceState',
+ error,
+ lastLoadedState: getLastLoadedListResourceState(store.getState()),
+ })
+ );
+ }
+ }
+};
+
+const createTrustedAppDeletionSubmissionResourceStateChanged = (
+ newState: Immutable
+): Immutable => ({
+ type: 'trustedAppDeletionSubmissionResourceStateChanged',
+ payload: { newState },
+});
+
+const submitDeletionIfNeeded = async (
+ store: ImmutableMiddlewareAPI,
+ trustedAppsService: TrustedAppsService
+) => {
+ const submissionResourceState = getDeletionSubmissionResourceState(store.getState());
+ const entry = getDeletionDialogEntry(store.getState());
+
+ if (isStaleResourceState(submissionResourceState) && entry !== undefined) {
store.dispatch(
- createTrustedAppsListResourceStateChangedAction({
- type: 'FailedResourceState',
- error,
- lastLoadedState: getLastLoadedListResourceState(store.getState()),
+ createTrustedAppDeletionSubmissionResourceStateChanged({
+ type: 'LoadingResourceState',
+ previousState: submissionResourceState,
})
);
+
+ try {
+ await trustedAppsService.deleteTrustedApp({ id: entry.id });
+
+ store.dispatch(
+ createTrustedAppDeletionSubmissionResourceStateChanged({
+ type: 'LoadedResourceState',
+ data: null,
+ })
+ );
+ store.dispatch({
+ type: 'trustedAppDeletionDialogClosed',
+ });
+ store.dispatch({
+ type: 'trustedAppsListDataOutdated',
+ });
+ } catch (error) {
+ store.dispatch(
+ createTrustedAppDeletionSubmissionResourceStateChanged({
+ type: 'FailedResourceState',
+ error,
+ lastLoadedState: getLastLoadedResourceState(submissionResourceState),
+ })
+ );
+ }
}
};
@@ -102,7 +161,9 @@ const createTrustedApp = async (
data: createdTrustedApp,
},
});
- refreshList(store, trustedAppsService);
+ store.dispatch({
+ type: 'trustedAppsListDataOutdated',
+ });
} catch (error) {
dispatch({
type: 'serverReturnedCreateTrustedAppFailure',
@@ -122,8 +183,12 @@ export const createTrustedAppsPageMiddleware = (
next(action);
// TODO: need to think if failed state is a good condition to consider need for refresh
- if (action.type === 'userChangedUrl' && needsRefreshOfListData(store.getState())) {
- await refreshList(store, trustedAppsService);
+ if (action.type === 'userChangedUrl' || action.type === 'trustedAppsListDataOutdated') {
+ await refreshListIfNeeded(store, trustedAppsService);
+ }
+
+ if (action.type === 'trustedAppDeletionDialogConfirmed') {
+ await submitDeletionIfNeeded(store, trustedAppsService);
}
if (action.type === 'userClickedSaveNewTrustedAppButton') {
diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/reducer.test.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/reducer.test.ts
index 76dd4b48e63d2..228f0932edd28 100644
--- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/reducer.test.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/reducer.test.ts
@@ -4,93 +4,180 @@
* you may not use this file except in compliance with the Elastic License.
*/
+import { AsyncResourceState } from '../state';
import { initialTrustedAppsPageState, trustedAppsPageReducer } from './reducer';
import {
+ createSampleTrustedApp,
createListLoadedResourceState,
createLoadedListViewWithPagination,
- createTrustedAppsListResourceStateChangedAction,
createUserChangedUrlAction,
+ createTrustedAppsListResourceStateChangedAction,
} from '../test_utils';
+const initialNow = 111111;
+const dateNowMock = jest.fn();
+dateNowMock.mockReturnValue(initialNow);
+
+Date.now = dateNowMock;
+
+const initialState = initialTrustedAppsPageState();
+
describe('reducer', () => {
describe('UserChangedUrl', () => {
it('makes page state active and extracts pagination parameters', () => {
const result = trustedAppsPageReducer(
- initialTrustedAppsPageState,
+ initialState,
createUserChangedUrlAction('/trusted_apps', '?page_index=5&page_size=50')
);
expect(result).toStrictEqual({
- listView: {
- ...initialTrustedAppsPageState.listView,
- currentPaginationInfo: { index: 5, size: 50 },
- },
+ ...initialState,
+ listView: { ...initialState.listView, currentPaginationInfo: { index: 5, size: 50 } },
active: true,
- createView: undefined,
});
});
it('extracts default pagination parameters when none provided', () => {
const result = trustedAppsPageReducer(
{
- ...initialTrustedAppsPageState,
- listView: {
- ...initialTrustedAppsPageState.listView,
- currentPaginationInfo: { index: 5, size: 50 },
- },
+ ...initialState,
+ listView: { ...initialState.listView, currentPaginationInfo: { index: 5, size: 50 } },
},
createUserChangedUrlAction('/trusted_apps', '?page_index=b&page_size=60')
);
- expect(result).toStrictEqual({
- ...initialTrustedAppsPageState,
- active: true,
- });
+ expect(result).toStrictEqual({ ...initialState, active: true });
});
it('extracts default pagination parameters when invalid provided', () => {
const result = trustedAppsPageReducer(
{
- ...initialTrustedAppsPageState,
- listView: {
- ...initialTrustedAppsPageState.listView,
- currentPaginationInfo: { index: 5, size: 50 },
- },
+ ...initialState,
+ listView: { ...initialState.listView, currentPaginationInfo: { index: 5, size: 50 } },
},
createUserChangedUrlAction('/trusted_apps')
);
- expect(result).toStrictEqual({
- ...initialTrustedAppsPageState,
- active: true,
- });
+ expect(result).toStrictEqual({ ...initialState, active: true });
});
it('makes page state inactive and resets list to uninitialised state when navigating away', () => {
const result = trustedAppsPageReducer(
- { listView: createLoadedListViewWithPagination(), active: true, createView: undefined },
+ { ...initialState, listView: createLoadedListViewWithPagination(initialNow), active: true },
createUserChangedUrlAction('/endpoints')
);
- expect(result).toStrictEqual(initialTrustedAppsPageState);
+ expect(result).toStrictEqual(initialState);
});
});
describe('TrustedAppsListResourceStateChanged', () => {
it('sets the current list resource state', () => {
- const listResourceState = createListLoadedResourceState({ index: 3, size: 50 }, 200);
+ const listResourceState = createListLoadedResourceState(
+ { index: 3, size: 50 },
+ 200,
+ initialNow
+ );
const result = trustedAppsPageReducer(
- initialTrustedAppsPageState,
+ initialState,
createTrustedAppsListResourceStateChangedAction(listResourceState)
);
expect(result).toStrictEqual({
- ...initialTrustedAppsPageState,
- listView: {
- ...initialTrustedAppsPageState.listView,
- currentListResourceState: listResourceState,
+ ...initialState,
+ listView: { ...initialState.listView, currentListResourceState: listResourceState },
+ });
+ });
+ });
+
+ describe('TrustedAppsListDataOutdated', () => {
+ it('sets the list view freshness timestamp', () => {
+ const newNow = 222222;
+ dateNowMock.mockReturnValue(newNow);
+
+ const result = trustedAppsPageReducer(initialState, { type: 'trustedAppsListDataOutdated' });
+
+ expect(result).toStrictEqual({
+ ...initialState,
+ listView: { ...initialState.listView, freshDataTimestamp: newNow },
+ });
+ });
+ });
+
+ describe('TrustedAppDeletionSubmissionResourceStateChanged', () => {
+ it('sets the deletion dialog submission resource state', () => {
+ const submissionResourceState: AsyncResourceState = {
+ type: 'LoadedResourceState',
+ data: null,
+ };
+ const result = trustedAppsPageReducer(initialState, {
+ type: 'trustedAppDeletionSubmissionResourceStateChanged',
+ payload: { newState: submissionResourceState },
+ });
+
+ expect(result).toStrictEqual({
+ ...initialState,
+ deletionDialog: { ...initialState.deletionDialog, submissionResourceState },
+ });
+ });
+ });
+
+ describe('TrustedAppDeletionDialogStarted', () => {
+ it('sets the deletion dialog state to started', () => {
+ const entry = createSampleTrustedApp(3);
+ const result = trustedAppsPageReducer(initialState, {
+ type: 'trustedAppDeletionDialogStarted',
+ payload: { entry },
+ });
+
+ expect(result).toStrictEqual({
+ ...initialState,
+ deletionDialog: { ...initialState.deletionDialog, entry },
+ });
+ });
+ });
+
+ describe('TrustedAppDeletionDialogConfirmed', () => {
+ it('sets the deletion dialog state to confirmed', () => {
+ const entry = createSampleTrustedApp(3);
+ const result = trustedAppsPageReducer(
+ {
+ ...initialState,
+ deletionDialog: {
+ entry,
+ confirmed: false,
+ submissionResourceState: { type: 'UninitialisedResourceState' },
+ },
+ },
+ { type: 'trustedAppDeletionDialogConfirmed' }
+ );
+
+ expect(result).toStrictEqual({
+ ...initialState,
+ deletionDialog: {
+ entry,
+ confirmed: true,
+ submissionResourceState: { type: 'UninitialisedResourceState' },
},
});
});
});
+
+ describe('TrustedAppDeletionDialogClosed', () => {
+ it('sets the deletion dialog state to confirmed', () => {
+ const result = trustedAppsPageReducer(
+ {
+ ...initialState,
+ deletionDialog: {
+ entry: createSampleTrustedApp(3),
+ confirmed: true,
+ submissionResourceState: { type: 'UninitialisedResourceState' },
+ },
+ },
+ { type: 'trustedAppDeletionDialogClosed' }
+ );
+
+ expect(result).toStrictEqual(initialState);
+ });
+ });
});
diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/reducer.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/reducer.ts
index d824a6e95c8d5..ec210254bf76f 100644
--- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/reducer.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/reducer.ts
@@ -19,9 +19,14 @@ import {
} from '../../../common/constants';
import {
+ TrustedAppDeletionDialogClosed,
+ TrustedAppDeletionDialogConfirmed,
+ TrustedAppDeletionDialogStarted,
+ TrustedAppDeletionSubmissionResourceStateChanged,
+ TrustedAppsListDataOutdated,
+ TrustedAppsListResourceStateChanged,
ServerReturnedCreateTrustedAppFailure,
ServerReturnedCreateTrustedAppSuccess,
- TrustedAppsListResourceStateChanged,
UserClickedSaveNewTrustedAppButton,
} from './action';
import { TrustedAppsListPageState } from '../state';
@@ -41,6 +46,16 @@ const isTrustedAppsPageLocation = (location: Immutable) => {
);
};
+const trustedAppsListDataOutdated: CaseReducer = (state, action) => {
+ return {
+ ...state,
+ listView: {
+ ...state.listView,
+ freshDataTimestamp: Date.now(),
+ },
+ };
+};
+
const trustedAppsListResourceStateChanged: CaseReducer = (
state,
action
@@ -54,6 +69,44 @@ const trustedAppsListResourceStateChanged: CaseReducer = (
+ state,
+ action
+) => {
+ return {
+ ...state,
+ deletionDialog: { ...state.deletionDialog, submissionResourceState: action.payload.newState },
+ };
+};
+
+const trustedAppDeletionDialogStarted: CaseReducer = (
+ state,
+ action
+) => {
+ return {
+ ...state,
+ deletionDialog: {
+ entry: action.payload.entry,
+ confirmed: false,
+ submissionResourceState: { type: 'UninitialisedResourceState' },
+ },
+ };
+};
+
+const trustedAppDeletionDialogConfirmed: CaseReducer = (
+ state,
+ action
+) => {
+ return { ...state, deletionDialog: { ...state.deletionDialog, confirmed: true } };
+};
+
+const trustedAppDeletionDialogClosed: CaseReducer = (
+ state,
+ action
+) => {
+ return { ...state, deletionDialog: initialDeletionDialogState() };
+};
+
const userChangedUrl: CaseReducer = (state, action) => {
if (isTrustedAppsPageLocation(action.payload)) {
const parsedUrlsParams = parse(action.payload.search.slice(1));
@@ -75,7 +128,7 @@ const userChangedUrl: CaseReducer = (state, action) => {
active: true,
};
} else {
- return initialTrustedAppsPageState;
+ return initialTrustedAppsPageState();
}
};
@@ -90,27 +143,49 @@ const trustedAppsCreateResourceChanged: CaseReducer<
};
};
-export const initialTrustedAppsPageState: TrustedAppsListPageState = {
+const initialDeletionDialogState = (): TrustedAppsListPageState['deletionDialog'] => ({
+ confirmed: false,
+ submissionResourceState: { type: 'UninitialisedResourceState' },
+});
+
+export const initialTrustedAppsPageState = (): TrustedAppsListPageState => ({
listView: {
currentListResourceState: { type: 'UninitialisedResourceState' },
currentPaginationInfo: {
index: MANAGEMENT_DEFAULT_PAGE,
size: MANAGEMENT_DEFAULT_PAGE_SIZE,
},
+ freshDataTimestamp: Date.now(),
show: undefined,
},
+ deletionDialog: initialDeletionDialogState(),
createView: undefined,
active: false,
-};
+});
export const trustedAppsPageReducer: StateReducer = (
- state = initialTrustedAppsPageState,
+ state = initialTrustedAppsPageState(),
action
) => {
switch (action.type) {
+ case 'trustedAppsListDataOutdated':
+ return trustedAppsListDataOutdated(state, action);
+
case 'trustedAppsListResourceStateChanged':
return trustedAppsListResourceStateChanged(state, action);
+ case 'trustedAppDeletionSubmissionResourceStateChanged':
+ return trustedAppDeletionSubmissionResourceStateChanged(state, action);
+
+ case 'trustedAppDeletionDialogStarted':
+ return trustedAppDeletionDialogStarted(state, action);
+
+ case 'trustedAppDeletionDialogConfirmed':
+ return trustedAppDeletionDialogConfirmed(state, action);
+
+ case 'trustedAppDeletionDialogClosed':
+ return trustedAppDeletionDialogClosed(state, action);
+
case 'userChangedUrl':
return userChangedUrl(state, action);
diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/selectors.test.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/selectors.test.ts
index 453afa1befa6b..0be4d0b05acc4 100644
--- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/selectors.test.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/selectors.test.ts
@@ -4,6 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
+import { AsyncResourceState, TrustedAppsListPageState } from '../state';
+import { initialTrustedAppsPageState } from './reducer';
import {
getCurrentListResourceState,
getLastLoadedListResourceState,
@@ -14,6 +16,12 @@ import {
getListTotalItemsCount,
isListLoading,
needsRefreshOfListData,
+ isDeletionDialogOpen,
+ isDeletionInProgress,
+ isDeletionSuccessful,
+ getDeletionError,
+ getDeletionDialogEntry,
+ getDeletionSubmissionResourceState,
} from './selectors';
import {
@@ -23,96 +31,118 @@ import {
createListFailedResourceState,
createListLoadedResourceState,
createLoadedListViewWithPagination,
+ createSampleTrustedApp,
createSampleTrustedApps,
+ createServerApiError,
createUninitialisedResourceState,
} from '../test_utils';
+const initialNow = 111111;
+const dateNowMock = jest.fn();
+dateNowMock.mockReturnValue(initialNow);
+
+Date.now = dateNowMock;
+
+const initialState = initialTrustedAppsPageState();
+
+const createStateWithDeletionSubmissionResourceState = (
+ submissionResourceState: AsyncResourceState
+): TrustedAppsListPageState => ({
+ ...initialState,
+ deletionDialog: { ...initialState.deletionDialog, submissionResourceState },
+});
+
describe('selectors', () => {
describe('needsRefreshOfListData()', () => {
it('returns false for outdated resource state and inactive state', () => {
- expect(
- needsRefreshOfListData({
- listView: createDefaultListView(),
- active: false,
- createView: undefined,
- })
- ).toBe(false);
+ expect(needsRefreshOfListData(initialState)).toBe(false);
});
it('returns true for outdated resource state and active state', () => {
- expect(
- needsRefreshOfListData({
- listView: createDefaultListView(),
- active: true,
- createView: undefined,
- })
- ).toBe(true);
+ expect(needsRefreshOfListData({ ...initialState, active: true })).toBe(true);
});
it('returns true when current loaded page index is outdated', () => {
- const listView = createLoadedListViewWithPagination({ index: 1, size: 20 });
+ const listView = createLoadedListViewWithPagination(initialNow, { index: 1, size: 20 });
- expect(needsRefreshOfListData({ listView, active: true, createView: undefined })).toBe(true);
+ expect(needsRefreshOfListData({ ...initialState, listView, active: true })).toBe(true);
});
it('returns true when current loaded page size is outdated', () => {
- const listView = createLoadedListViewWithPagination({ index: 0, size: 50 });
+ const listView = createLoadedListViewWithPagination(initialNow, { index: 0, size: 50 });
+
+ expect(needsRefreshOfListData({ ...initialState, listView, active: true })).toBe(true);
+ });
+
+ it('returns true when current loaded data timestamp is outdated', () => {
+ const listView = {
+ ...createLoadedListViewWithPagination(111111),
+ freshDataTimestamp: 222222,
+ };
- expect(needsRefreshOfListData({ listView, active: true, createView: undefined })).toBe(true);
+ expect(needsRefreshOfListData({ ...initialState, listView, active: true })).toBe(true);
});
it('returns false when current loaded data is up to date', () => {
- const listView = createLoadedListViewWithPagination();
+ const listView = createLoadedListViewWithPagination(initialNow);
- expect(needsRefreshOfListData({ listView, active: true, createView: undefined })).toBe(false);
+ expect(needsRefreshOfListData({ ...initialState, listView, active: true })).toBe(false);
});
});
describe('getCurrentListResourceState()', () => {
it('returns current list resource state', () => {
- const listView = createDefaultListView();
+ const state = { ...initialState, listView: createDefaultListView(initialNow) };
- expect(
- getCurrentListResourceState({ listView, active: false, createView: undefined })
- ).toStrictEqual(createUninitialisedResourceState());
+ expect(getCurrentListResourceState(state)).toStrictEqual(createUninitialisedResourceState());
});
});
describe('getLastLoadedListResourceState()', () => {
it('returns last loaded list resource state', () => {
- const listView = {
- currentListResourceState: createListComplexLoadingResourceState(
- createDefaultPaginationInfo(),
- 200
- ),
- currentPaginationInfo: createDefaultPaginationInfo(),
- show: undefined,
+ const state = {
+ ...initialState,
+ listView: {
+ currentListResourceState: createListComplexLoadingResourceState(
+ createDefaultPaginationInfo(),
+ 200,
+ initialNow
+ ),
+ currentPaginationInfo: createDefaultPaginationInfo(),
+ freshDataTimestamp: initialNow,
+ show: undefined,
+ },
};
- expect(
- getLastLoadedListResourceState({ listView, active: false, createView: undefined })
- ).toStrictEqual(createListLoadedResourceState(createDefaultPaginationInfo(), 200));
+ expect(getLastLoadedListResourceState(state)).toStrictEqual(
+ createListLoadedResourceState(createDefaultPaginationInfo(), 200, initialNow)
+ );
});
});
describe('getListItems()', () => {
it('returns empty list when no valid data loaded', () => {
- expect(
- getListItems({ listView: createDefaultListView(), active: false, createView: undefined })
- ).toStrictEqual([]);
+ const state = { ...initialState, listView: createDefaultListView(initialNow) };
+
+ expect(getListItems(state)).toStrictEqual([]);
});
it('returns last loaded list items', () => {
- const listView = {
- currentListResourceState: createListComplexLoadingResourceState(
- createDefaultPaginationInfo(),
- 200
- ),
- currentPaginationInfo: createDefaultPaginationInfo(),
- show: undefined,
+ const state = {
+ ...initialState,
+ listView: {
+ currentListResourceState: createListComplexLoadingResourceState(
+ createDefaultPaginationInfo(),
+ 200,
+ initialNow
+ ),
+ currentPaginationInfo: createDefaultPaginationInfo(),
+ freshDataTimestamp: initialNow,
+ show: undefined,
+ },
};
- expect(getListItems({ listView, active: false, createView: undefined })).toStrictEqual(
+ expect(getListItems(state)).toStrictEqual(
createSampleTrustedApps(createDefaultPaginationInfo())
);
});
@@ -120,100 +150,239 @@ describe('selectors', () => {
describe('getListTotalItemsCount()', () => {
it('returns 0 when no valid data loaded', () => {
- expect(
- getListTotalItemsCount({
- listView: createDefaultListView(),
- active: false,
- createView: undefined,
- })
- ).toBe(0);
+ const state = { ...initialState, listView: createDefaultListView(initialNow) };
+
+ expect(getListTotalItemsCount(state)).toBe(0);
});
it('returns last loaded total items count', () => {
- const listView = {
- currentListResourceState: createListComplexLoadingResourceState(
- createDefaultPaginationInfo(),
- 200
- ),
- currentPaginationInfo: createDefaultPaginationInfo(),
- show: undefined,
+ const state = {
+ ...initialState,
+ listView: {
+ currentListResourceState: createListComplexLoadingResourceState(
+ createDefaultPaginationInfo(),
+ 200,
+ initialNow
+ ),
+ currentPaginationInfo: createDefaultPaginationInfo(),
+ freshDataTimestamp: initialNow,
+ show: undefined,
+ },
};
- expect(getListTotalItemsCount({ listView, active: false, createView: undefined })).toBe(200);
+ expect(getListTotalItemsCount(state)).toBe(200);
});
});
describe('getListCurrentPageIndex()', () => {
it('returns page index', () => {
- expect(
- getListCurrentPageIndex({
- listView: createDefaultListView(),
- active: false,
- createView: undefined,
- })
- ).toBe(0);
+ const state = { ...initialState, listView: createDefaultListView(initialNow) };
+
+ expect(getListCurrentPageIndex(state)).toBe(0);
});
});
describe('getListCurrentPageSize()', () => {
- it('returns page index', () => {
- expect(
- getListCurrentPageSize({
- listView: createDefaultListView(),
- active: false,
- createView: undefined,
- })
- ).toBe(20);
+ it('returns page size', () => {
+ const state = { ...initialState, listView: createDefaultListView(initialNow) };
+
+ expect(getListCurrentPageSize(state)).toBe(20);
});
});
describe('getListErrorMessage()', () => {
it('returns undefined when not in failed state', () => {
- const listView = {
- currentListResourceState: createListComplexLoadingResourceState(
- createDefaultPaginationInfo(),
- 200
- ),
- currentPaginationInfo: createDefaultPaginationInfo(),
- show: undefined,
+ const state = {
+ ...initialState,
+ listView: {
+ currentListResourceState: createListComplexLoadingResourceState(
+ createDefaultPaginationInfo(),
+ 200,
+ initialNow
+ ),
+ currentPaginationInfo: createDefaultPaginationInfo(),
+ freshDataTimestamp: initialNow,
+ show: undefined,
+ },
};
- expect(
- getListErrorMessage({ listView, active: false, createView: undefined })
- ).toBeUndefined();
+ expect(getListErrorMessage(state)).toBeUndefined();
});
it('returns message when not in failed state', () => {
- const listView = {
- currentListResourceState: createListFailedResourceState('Internal Server Error'),
- currentPaginationInfo: createDefaultPaginationInfo(),
- show: undefined,
+ const state = {
+ ...initialState,
+ listView: {
+ currentListResourceState: createListFailedResourceState('Internal Server Error'),
+ currentPaginationInfo: createDefaultPaginationInfo(),
+ freshDataTimestamp: initialNow,
+ show: undefined,
+ },
};
- expect(getListErrorMessage({ listView, active: false, createView: undefined })).toBe(
- 'Internal Server Error'
- );
+ expect(getListErrorMessage(state)).toBe('Internal Server Error');
});
});
describe('isListLoading()', () => {
it('returns false when no loading is happening', () => {
- expect(
- isListLoading({ listView: createDefaultListView(), active: false, createView: undefined })
- ).toBe(false);
+ expect(isListLoading(initialState)).toBe(false);
});
it('returns true when loading is in progress', () => {
- const listView = {
- currentListResourceState: createListComplexLoadingResourceState(
- createDefaultPaginationInfo(),
- 200
- ),
- currentPaginationInfo: createDefaultPaginationInfo(),
- show: undefined,
+ const state = {
+ ...initialState,
+ listView: {
+ currentListResourceState: createListComplexLoadingResourceState(
+ createDefaultPaginationInfo(),
+ 200,
+ initialNow
+ ),
+ currentPaginationInfo: createDefaultPaginationInfo(),
+ freshDataTimestamp: initialNow,
+ show: undefined,
+ },
};
- expect(isListLoading({ listView, active: false, createView: undefined })).toBe(true);
+ expect(isListLoading(state)).toBe(true);
+ });
+ });
+
+ describe('isDeletionDialogOpen()', () => {
+ it('returns false when no entry is set', () => {
+ expect(isDeletionDialogOpen(initialState)).toBe(false);
+ });
+
+ it('returns true when entry is set', () => {
+ const state = {
+ ...initialState,
+ deletionDialog: {
+ ...initialState.deletionDialog,
+ entry: createSampleTrustedApp(5),
+ },
+ };
+
+ expect(isDeletionDialogOpen(state)).toBe(true);
+ });
+ });
+
+ describe('isDeletionInProgress()', () => {
+ it('returns false when resource state is uninitialised', () => {
+ expect(isDeletionInProgress(initialState)).toBe(false);
+ });
+
+ it('returns true when resource state is loading', () => {
+ const state = createStateWithDeletionSubmissionResourceState({
+ type: 'LoadingResourceState',
+ previousState: { type: 'UninitialisedResourceState' },
+ });
+
+ expect(isDeletionInProgress(state)).toBe(true);
+ });
+
+ it('returns false when resource state is loaded', () => {
+ const state = createStateWithDeletionSubmissionResourceState({
+ type: 'LoadedResourceState',
+ data: null,
+ });
+
+ expect(isDeletionInProgress(state)).toBe(false);
+ });
+
+ it('returns false when resource state is failed', () => {
+ const state = createStateWithDeletionSubmissionResourceState({
+ type: 'FailedResourceState',
+ error: createServerApiError('Not Found'),
+ });
+
+ expect(isDeletionInProgress(state)).toBe(false);
+ });
+ });
+
+ describe('isDeletionSuccessful()', () => {
+ it('returns false when resource state is uninitialised', () => {
+ expect(isDeletionSuccessful(initialState)).toBe(false);
+ });
+
+ it('returns false when resource state is loading', () => {
+ const state = createStateWithDeletionSubmissionResourceState({
+ type: 'LoadingResourceState',
+ previousState: { type: 'UninitialisedResourceState' },
+ });
+
+ expect(isDeletionSuccessful(state)).toBe(false);
+ });
+
+ it('returns true when resource state is loaded', () => {
+ const state = createStateWithDeletionSubmissionResourceState({
+ type: 'LoadedResourceState',
+ data: null,
+ });
+
+ expect(isDeletionSuccessful(state)).toBe(true);
+ });
+
+ it('returns false when resource state is failed', () => {
+ const state = createStateWithDeletionSubmissionResourceState({
+ type: 'FailedResourceState',
+ error: createServerApiError('Not Found'),
+ });
+
+ expect(isDeletionSuccessful(state)).toBe(false);
+ });
+ });
+
+ describe('getDeletionError()', () => {
+ it('returns undefined when resource state is uninitialised', () => {
+ expect(getDeletionError(initialState)).toBeUndefined();
+ });
+
+ it('returns undefined when resource state is loading', () => {
+ const state = createStateWithDeletionSubmissionResourceState({
+ type: 'LoadingResourceState',
+ previousState: { type: 'UninitialisedResourceState' },
+ });
+
+ expect(getDeletionError(state)).toBeUndefined();
+ });
+
+ it('returns undefined when resource state is loaded', () => {
+ const state = createStateWithDeletionSubmissionResourceState({
+ type: 'LoadedResourceState',
+ data: null,
+ });
+
+ expect(getDeletionError(state)).toBeUndefined();
+ });
+
+ it('returns error when resource state is failed', () => {
+ const state = createStateWithDeletionSubmissionResourceState({
+ type: 'FailedResourceState',
+ error: createServerApiError('Not Found'),
+ });
+
+ expect(getDeletionError(state)).toStrictEqual(createServerApiError('Not Found'));
+ });
+ });
+
+ describe('getDeletionSubmissionResourceState()', () => {
+ it('returns submission resource state', () => {
+ expect(getDeletionSubmissionResourceState(initialState)).toStrictEqual({
+ type: 'UninitialisedResourceState',
+ });
+ });
+ });
+
+ describe('getDeletionDialogEntry()', () => {
+ it('returns undefined when no entry is set', () => {
+ expect(getDeletionDialogEntry(initialState)).toBeUndefined();
+ });
+
+ it('returns entry when entry is set', () => {
+ const entry = createSampleTrustedApp(5);
+ const state = { ...initialState, deletionDialog: { ...initialState.deletionDialog, entry } };
+
+ expect(getDeletionDialogEntry(state)).toStrictEqual(entry);
});
});
});
diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/selectors.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/selectors.ts
index f074b21f79f4e..6239b425efe2f 100644
--- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/selectors.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/selectors.ts
@@ -5,12 +5,15 @@
*/
import { createSelector } from 'reselect';
+import { ServerApiError } from '../../../../common/types';
import { Immutable, NewTrustedApp, TrustedApp } from '../../../../../common/endpoint/types';
import {
AsyncResourceState,
getCurrentResourceError,
getLastLoadedResourceState,
+ isFailedResourceState,
+ isLoadedResourceState,
isLoadingResourceState,
isOutdatedResourceState,
LoadedResourceState,
@@ -32,12 +35,15 @@ const pageInfosEqual = (pageInfo1: PaginationInfo, pageInfo2: PaginationInfo): b
export const needsRefreshOfListData = (state: Immutable): boolean => {
const currentPageInfo = state.listView.currentPaginationInfo;
const currentPage = state.listView.currentListResourceState;
+ const freshDataTimestamp = state.listView.freshDataTimestamp;
return (
state.active &&
- isOutdatedResourceState(currentPage, (data) =>
- pageInfosEqual(currentPageInfo, data.paginationInfo)
- )
+ isOutdatedResourceState(currentPage, (data) => {
+ return (
+ pageInfosEqual(currentPageInfo, data.paginationInfo) && data.timestamp >= freshDataTimestamp
+ );
+ })
);
};
@@ -104,6 +110,38 @@ export const isListLoading = (state: Immutable): boole
return isLoadingResourceState(state.listView.currentListResourceState);
};
+export const isDeletionDialogOpen = (state: Immutable): boolean => {
+ return state.deletionDialog.entry !== undefined;
+};
+
+export const isDeletionInProgress = (state: Immutable): boolean => {
+ return isLoadingResourceState(state.deletionDialog.submissionResourceState);
+};
+
+export const isDeletionSuccessful = (state: Immutable): boolean => {
+ return isLoadedResourceState(state.deletionDialog.submissionResourceState);
+};
+
+export const getDeletionError = (
+ state: Immutable
+): Immutable | undefined => {
+ const submissionResourceState = state.deletionDialog.submissionResourceState;
+
+ return isFailedResourceState(submissionResourceState) ? submissionResourceState.error : undefined;
+};
+
+export const getDeletionSubmissionResourceState = (
+ state: Immutable
+): AsyncResourceState => {
+ return state.deletionDialog.submissionResourceState;
+};
+
+export const getDeletionDialogEntry = (
+ state: Immutable
+): Immutable | undefined => {
+ return state.deletionDialog.entry;
+};
+
export const isCreatePending: (state: Immutable) => boolean = ({
createView,
}) => {
diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/test_utils/index.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/test_utils/index.ts
index 70e4e1e685b01..020a87f526e52 100644
--- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/test_utils/index.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/test_utils/index.ts
@@ -4,10 +4,16 @@
* you may not use this file except in compliance with the Elastic License.
*/
+import { combineReducers, createStore } from 'redux';
import { ServerApiError } from '../../../../common/types';
import { TrustedApp } from '../../../../../common/endpoint/types';
import { RoutingAction } from '../../../../common/store/routing';
+import {
+ MANAGEMENT_STORE_GLOBAL_NAMESPACE,
+ MANAGEMENT_STORE_TRUSTED_APPS_NAMESPACE,
+} from '../../../common/constants';
+
import {
AsyncResourceState,
FailedResourceState,
@@ -20,30 +26,36 @@ import {
UninitialisedResourceState,
} from '../state';
+import { trustedAppsPageReducer } from '../store/reducer';
import { TrustedAppsListResourceStateChanged } from '../store/action';
-import { initialTrustedAppsPageState } from '../store/reducer';
const OS_LIST: Array = ['windows', 'macos', 'linux'];
-export const createSampleTrustedApps = (paginationInfo: PaginationInfo): TrustedApp[] => {
- return [...new Array(paginationInfo.size).keys()].map((i) => ({
- id: String(paginationInfo.index + i),
- name: `trusted app ${paginationInfo.index + i}`,
- description: `Trusted App ${paginationInfo.index + i}`,
+export const createSampleTrustedApp = (i: number): TrustedApp => {
+ return {
+ id: String(i),
+ name: `trusted app ${i}`,
+ description: `Trusted App ${i}`,
created_at: '1 minute ago',
created_by: 'someone',
os: OS_LIST[i % 3],
entries: [],
- }));
+ };
+};
+
+export const createSampleTrustedApps = (paginationInfo: PaginationInfo): TrustedApp[] => {
+ return [...new Array(paginationInfo.size).keys()].map(createSampleTrustedApp);
};
export const createTrustedAppsListData = (
paginationInfo: PaginationInfo,
- totalItemsCount: number
+ totalItemsCount: number,
+ timestamp: number
) => ({
items: createSampleTrustedApps(paginationInfo),
totalItemsCount,
paginationInfo,
+ timestamp,
});
export const createServerApiError = (message: string) => ({
@@ -58,10 +70,11 @@ export const createUninitialisedResourceState = (): UninitialisedResourceState =
export const createListLoadedResourceState = (
paginationInfo: PaginationInfo,
- totalItemsCount: number
+ totalItemsCount: number,
+ timestamp: number
): LoadedResourceState => ({
type: 'LoadedResourceState',
- data: createTrustedAppsListData(paginationInfo, totalItemsCount),
+ data: createTrustedAppsListData(paginationInfo, totalItemsCount, timestamp),
});
export const createListFailedResourceState = (
@@ -82,50 +95,64 @@ export const createListLoadingResourceState = (
export const createListComplexLoadingResourceState = (
paginationInfo: PaginationInfo,
- totalItemsCount: number
+ totalItemsCount: number,
+ timestamp: number
): LoadingResourceState =>
createListLoadingResourceState(
createListFailedResourceState(
'Internal Server Error',
- createListLoadedResourceState(paginationInfo, totalItemsCount)
+ createListLoadedResourceState(paginationInfo, totalItemsCount, timestamp)
)
);
export const createDefaultPaginationInfo = () => ({ index: 0, size: 20 });
-export const createDefaultListView = () => ({
- ...initialTrustedAppsPageState.listView,
+export const createDefaultListView = (
+ freshDataTimestamp: number
+): TrustedAppsListPageState['listView'] => ({
currentListResourceState: createUninitialisedResourceState(),
currentPaginationInfo: createDefaultPaginationInfo(),
+ freshDataTimestamp,
+ show: undefined,
});
export const createLoadingListViewWithPagination = (
+ freshDataTimestamp: number,
currentPaginationInfo: PaginationInfo,
previousState: StaleResourceState = createUninitialisedResourceState()
): TrustedAppsListPageState['listView'] => ({
- ...initialTrustedAppsPageState.listView,
currentListResourceState: { type: 'LoadingResourceState', previousState },
currentPaginationInfo,
+ freshDataTimestamp,
+ show: undefined,
});
export const createLoadedListViewWithPagination = (
+ freshDataTimestamp: number,
paginationInfo: PaginationInfo = createDefaultPaginationInfo(),
currentPaginationInfo: PaginationInfo = createDefaultPaginationInfo(),
totalItemsCount: number = 200
): TrustedAppsListPageState['listView'] => ({
- ...initialTrustedAppsPageState.listView,
- currentListResourceState: createListLoadedResourceState(paginationInfo, totalItemsCount),
+ currentListResourceState: createListLoadedResourceState(
+ paginationInfo,
+ totalItemsCount,
+ freshDataTimestamp
+ ),
currentPaginationInfo,
+ freshDataTimestamp,
+ show: undefined,
});
export const createFailedListViewWithPagination = (
+ freshDataTimestamp: number,
currentPaginationInfo: PaginationInfo,
error: ServerApiError,
lastLoadedState?: LoadedResourceState
): TrustedAppsListPageState['listView'] => ({
- ...initialTrustedAppsPageState.listView,
currentListResourceState: { type: 'FailedResourceState', error, lastLoadedState },
currentPaginationInfo,
+ freshDataTimestamp,
+ show: undefined,
});
export const createUserChangedUrlAction = (path: string, search: string = ''): RoutingAction => {
@@ -138,3 +165,13 @@ export const createTrustedAppsListResourceStateChangedAction = (
type: 'trustedAppsListResourceStateChanged',
payload: { newState },
});
+
+export const createGlobalNoMiddlewareStore = () => {
+ return createStore(
+ combineReducers({
+ [MANAGEMENT_STORE_GLOBAL_NAMESPACE]: combineReducers({
+ [MANAGEMENT_STORE_TRUSTED_APPS_NAMESPACE]: trustedAppsPageReducer,
+ }),
+ })
+ );
+};
diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/__snapshots__/trusted_app_deletion_dialog.test.tsx.snap b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/__snapshots__/trusted_app_deletion_dialog.test.tsx.snap
new file mode 100644
index 0000000000000..fdb20f229f144
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/__snapshots__/trusted_app_deletion_dialog.test.tsx.snap
@@ -0,0 +1,315 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`TrustedAppDeletionDialog renders correctly initially 1`] = `
+
+
+
+`;
+
+exports[`TrustedAppDeletionDialog renders correctly when deletion failed 1`] = `
+
+
+
+
+`;
+
+exports[`TrustedAppDeletionDialog renders correctly when deletion is in progress 1`] = `
+
+
+
+
+`;
+
+exports[`TrustedAppDeletionDialog renders correctly when dialog started 1`] = `
+
+
+
+
+`;
diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/__snapshots__/trusted_apps_list.test.tsx.snap b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/__snapshots__/trusted_apps_list.test.tsx.snap
index e0f846f5950f7..46885bd653dc2 100644
--- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/__snapshots__/trusted_apps_list.test.tsx.snap
+++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/__snapshots__/trusted_apps_list.test.tsx.snap
@@ -98,6 +98,22 @@ exports[`TrustedAppsList renders correctly initially 1`] = `
+
@@ -106,7 +122,7 @@ exports[`TrustedAppsList renders correctly initially 1`] = `
>
+
@@ -232,7 +264,7 @@ exports[`TrustedAppsList renders correctly when failed loading data for the firs
>
+
@@ -363,7 +411,7 @@ exports[`TrustedAppsList renders correctly when failed loading data for the seco
>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -2174,6 +2838,22 @@ exports[`TrustedAppsList renders correctly when loading data for the first time
+
@@ -2182,7 +2862,7 @@ exports[`TrustedAppsList renders correctly when loading data for the first time
>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -3890,7 +5186,7 @@ exports[`TrustedAppsList renders correctly when loading data for the second time
`;
-exports[`TrustedAppsList renders correctly when new page and page sie set (not loading yet) 1`] = `
+exports[`TrustedAppsList renders correctly when new page and page size set (not loading yet) 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/__snapshots__/trusted_apps_page.test.tsx.snap b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/__snapshots__/trusted_apps_page.test.tsx.snap
index 4529480ce6262..1fccfe012bfba 100644
--- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/__snapshots__/trusted_apps_page.test.tsx.snap
+++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/__snapshots__/trusted_apps_page.test.tsx.snap
@@ -296,6 +296,22 @@ Object {
+
@@ -304,7 +320,7 @@ Object {
>
+
@@ -562,7 +594,7 @@ Object {
>
) => {
+ const Wrapper: React.FC = ({ children }) => (
+
+ {children}
+
+ );
+
+ return render(
, { wrapper: Wrapper });
+};
+
+const createDialogStartAction = (): TrustedAppDeletionDialogStarted => ({
+ type: 'trustedAppDeletionDialogStarted',
+ payload: { entry: createSampleTrustedApp(3) },
+});
+
+const createDialogLoadingAction = (): TrustedAppDeletionSubmissionResourceStateChanged => ({
+ type: 'trustedAppDeletionSubmissionResourceStateChanged',
+ payload: {
+ newState: {
+ type: 'LoadingResourceState',
+ previousState: { type: 'UninitialisedResourceState' },
+ },
+ },
+});
+
+const createDialogFailedAction = (): TrustedAppDeletionSubmissionResourceStateChanged => ({
+ type: 'trustedAppDeletionSubmissionResourceStateChanged',
+ payload: {
+ newState: { type: 'FailedResourceState', error: createServerApiError('Not Found') },
+ },
+});
+
+describe('TrustedAppDeletionDialog', () => {
+ it('renders correctly initially', () => {
+ expect(renderDeletionDialog(createGlobalNoMiddlewareStore()).baseElement).toMatchSnapshot();
+ });
+
+ it('renders correctly when dialog started', () => {
+ const store = createGlobalNoMiddlewareStore();
+
+ store.dispatch(createDialogStartAction());
+
+ expect(renderDeletionDialog(store).baseElement).toMatchSnapshot();
+ });
+
+ it('renders correctly when deletion is in progress', () => {
+ const store = createGlobalNoMiddlewareStore();
+
+ store.dispatch(createDialogStartAction());
+ store.dispatch(createDialogLoadingAction());
+
+ expect(renderDeletionDialog(store).baseElement).toMatchSnapshot();
+ });
+
+ it('renders correctly when deletion failed', () => {
+ const store = createGlobalNoMiddlewareStore();
+
+ store.dispatch(createDialogStartAction());
+ store.dispatch(createDialogFailedAction());
+
+ expect(renderDeletionDialog(store).baseElement).toMatchSnapshot();
+ });
+
+ it('triggers confirmation action when confirm button clicked', async () => {
+ const store = createGlobalNoMiddlewareStore();
+
+ store.dispatch(createDialogStartAction());
+ store.dispatch = jest.fn();
+
+ (await renderDeletionDialog(store).findByTestId('trustedAppDeletionConfirm')).click();
+
+ expect(store.dispatch).toBeCalledWith({
+ type: 'trustedAppDeletionDialogConfirmed',
+ });
+ });
+
+ it('triggers closing action when cancel button clicked', async () => {
+ const store = createGlobalNoMiddlewareStore();
+
+ store.dispatch(createDialogStartAction());
+ store.dispatch = jest.fn();
+
+ (await renderDeletionDialog(store).findByTestId('trustedAppDeletionCancel')).click();
+
+ expect(store.dispatch).toBeCalledWith({
+ type: 'trustedAppDeletionDialogClosed',
+ });
+ });
+
+ it('does not trigger closing action when deletion in progress and cancel button clicked', async () => {
+ const store = createGlobalNoMiddlewareStore();
+
+ store.dispatch(createDialogStartAction());
+ store.dispatch(createDialogLoadingAction());
+
+ store.dispatch = jest.fn();
+
+ (await renderDeletionDialog(store).findByTestId('trustedAppDeletionCancel')).click();
+
+ expect(store.dispatch).not.toBeCalled();
+ });
+});
diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_app_deletion_dialog.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_app_deletion_dialog.tsx
new file mode 100644
index 0000000000000..846fa794ceefd
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_app_deletion_dialog.tsx
@@ -0,0 +1,115 @@
+/*
+ * 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 React, { memo, useCallback, useMemo } from 'react';
+import { useDispatch } from 'react-redux';
+import { Dispatch } from 'redux';
+import { FormattedMessage } from '@kbn/i18n/react';
+import {
+ EuiButton,
+ EuiButtonEmpty,
+ EuiModal,
+ EuiModalBody,
+ EuiModalFooter,
+ EuiModalHeader,
+ EuiModalHeaderTitle,
+ EuiOverlayMask,
+ EuiText,
+} from '@elastic/eui';
+
+import { Immutable, TrustedApp } from '../../../../../common/endpoint/types';
+import { AppAction } from '../../../../common/store/actions';
+import { useTrustedAppsSelector } from './hooks';
+import {
+ getDeletionDialogEntry,
+ isDeletionDialogOpen,
+ isDeletionInProgress,
+} from '../store/selectors';
+
+const CANCEL_SUBJ = 'trustedAppDeletionCancel';
+const CONFIRM_SUBJ = 'trustedAppDeletionConfirm';
+
+const getTranslations = (entry: Immutable
| undefined) => ({
+ title: (
+
+ ),
+ mainMessage: (
+ {entry?.name} }}
+ />
+ ),
+ subMessage: (
+
+ ),
+ cancelButton: (
+
+ ),
+ confirmButton: (
+
+ ),
+});
+
+export const TrustedAppDeletionDialog = memo(() => {
+ const dispatch = useDispatch>();
+ const isBusy = useTrustedAppsSelector(isDeletionInProgress);
+ const entry = useTrustedAppsSelector(getDeletionDialogEntry);
+ const translations = useMemo(() => getTranslations(entry), [entry]);
+ const onConfirm = useCallback(() => {
+ dispatch({ type: 'trustedAppDeletionDialogConfirmed' });
+ }, [dispatch]);
+ const onCancel = useCallback(() => {
+ if (!isBusy) {
+ dispatch({ type: 'trustedAppDeletionDialogClosed' });
+ }
+ }, [dispatch, isBusy]);
+
+ if (useTrustedAppsSelector(isDeletionDialogOpen)) {
+ return (
+
+
+
+ {translations.title}
+
+
+
+
+ {translations.mainMessage}
+ {translations.subMessage}
+
+
+
+
+
+ {translations.cancelButton}
+
+
+
+ {translations.confirmButton}
+
+
+
+
+ );
+ } else {
+ return <>>;
+ }
+});
+
+TrustedAppDeletionDialog.displayName = 'TrustedAppDeletionDialog';
diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_list.test.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_list.test.tsx
index 0362f5c7a9de6..a457ecd0d088f 100644
--- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_list.test.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_list.test.tsx
@@ -3,40 +3,28 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
-import { combineReducers, createStore } from 'redux';
import { render } from '@testing-library/react';
import React from 'react';
import { Provider } from 'react-redux';
-import {
- MANAGEMENT_STORE_GLOBAL_NAMESPACE,
- MANAGEMENT_STORE_TRUSTED_APPS_NAMESPACE,
-} from '../../../common/constants';
-import { trustedAppsPageReducer } from '../store/reducer';
import { TrustedAppsList } from './trusted_apps_list';
import {
+ createSampleTrustedApp,
createListFailedResourceState,
createListLoadedResourceState,
createListLoadingResourceState,
createTrustedAppsListResourceStateChangedAction,
createUserChangedUrlAction,
+ createGlobalNoMiddlewareStore,
} from '../test_utils';
jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => ({
htmlIdGenerator: () => () => 'mockId',
}));
-const createStoreSetup = () => {
- return createStore(
- combineReducers({
- [MANAGEMENT_STORE_GLOBAL_NAMESPACE]: combineReducers({
- [MANAGEMENT_STORE_TRUSTED_APPS_NAMESPACE]: trustedAppsPageReducer,
- }),
- })
- );
-};
+const now = 111111;
-const renderList = (store: ReturnType) => {
+const renderList = (store: ReturnType) => {
const Wrapper: React.FC = ({ children }) => {children} ;
return render( , { wrapper: Wrapper });
@@ -44,11 +32,11 @@ const renderList = (store: ReturnType) => {
describe('TrustedAppsList', () => {
it('renders correctly initially', () => {
- expect(renderList(createStoreSetup()).container).toMatchSnapshot();
+ expect(renderList(createGlobalNoMiddlewareStore()).container).toMatchSnapshot();
});
it('renders correctly when loading data for the first time', () => {
- const store = createStoreSetup();
+ const store = createGlobalNoMiddlewareStore();
store.dispatch(
createTrustedAppsListResourceStateChangedAction(createListLoadingResourceState())
@@ -58,7 +46,7 @@ describe('TrustedAppsList', () => {
});
it('renders correctly when failed loading data for the first time', () => {
- const store = createStoreSetup();
+ const store = createGlobalNoMiddlewareStore();
store.dispatch(
createTrustedAppsListResourceStateChangedAction(
@@ -70,23 +58,23 @@ describe('TrustedAppsList', () => {
});
it('renders correctly when loaded data', () => {
- const store = createStoreSetup();
+ const store = createGlobalNoMiddlewareStore();
store.dispatch(
createTrustedAppsListResourceStateChangedAction(
- createListLoadedResourceState({ index: 0, size: 20 }, 200)
+ createListLoadedResourceState({ index: 0, size: 20 }, 200, now)
)
);
expect(renderList(store).container).toMatchSnapshot();
});
- it('renders correctly when new page and page sie set (not loading yet)', () => {
- const store = createStoreSetup();
+ it('renders correctly when new page and page size set (not loading yet)', () => {
+ const store = createGlobalNoMiddlewareStore();
store.dispatch(
createTrustedAppsListResourceStateChangedAction(
- createListLoadedResourceState({ index: 0, size: 20 }, 200)
+ createListLoadedResourceState({ index: 0, size: 20 }, 200, now)
)
);
store.dispatch(createUserChangedUrlAction('/trusted_apps', '?page_index=2&page_size=50'));
@@ -95,11 +83,13 @@ describe('TrustedAppsList', () => {
});
it('renders correctly when loading data for the second time', () => {
- const store = createStoreSetup();
+ const store = createGlobalNoMiddlewareStore();
store.dispatch(
createTrustedAppsListResourceStateChangedAction(
- createListLoadingResourceState(createListLoadedResourceState({ index: 0, size: 20 }, 200))
+ createListLoadingResourceState(
+ createListLoadedResourceState({ index: 0, size: 20 }, 200, now)
+ )
)
);
@@ -107,17 +97,37 @@ describe('TrustedAppsList', () => {
});
it('renders correctly when failed loading data for the second time', () => {
- const store = createStoreSetup();
+ const store = createGlobalNoMiddlewareStore();
store.dispatch(
createTrustedAppsListResourceStateChangedAction(
createListFailedResourceState(
'Intenal Server Error',
- createListLoadedResourceState({ index: 0, size: 20 }, 200)
+ createListLoadedResourceState({ index: 0, size: 20 }, 200, now)
)
)
);
expect(renderList(store).container).toMatchSnapshot();
});
+
+ it('triggers deletion dialog when delete action clicked', async () => {
+ const store = createGlobalNoMiddlewareStore();
+
+ store.dispatch(
+ createTrustedAppsListResourceStateChangedAction(
+ createListLoadedResourceState({ index: 0, size: 20 }, 200, now)
+ )
+ );
+ store.dispatch = jest.fn();
+
+ (await renderList(store).findAllByTestId('trustedAppDeleteAction'))[0].click();
+
+ expect(store.dispatch).toBeCalledWith({
+ type: 'trustedAppDeletionDialogStarted',
+ payload: {
+ entry: createSampleTrustedApp(0),
+ },
+ });
+ });
});
diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_list.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_list.tsx
index ea834060d5223..c91512d477510 100644
--- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_list.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_list.tsx
@@ -4,12 +4,15 @@
* you may not use this file except in compliance with the Elastic License.
*/
+import { Dispatch } from 'redux';
import React, { memo, useCallback, useMemo } from 'react';
+import { useDispatch } from 'react-redux';
import { useHistory } from 'react-router-dom';
-import { EuiBasicTable, EuiBasicTableColumn } from '@elastic/eui';
+import { EuiBasicTable, EuiBasicTableColumn, EuiTableActionsColumnType } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { Immutable } from '../../../../../common/endpoint/types';
+import { AppAction } from '../../../../common/store/actions';
import { TrustedApp } from '../../../../../common/endpoint/types/trusted_apps';
import { MANAGEMENT_PAGE_SIZE_OPTIONS } from '../../../common/constants';
import { getTrustedAppsListPath } from '../../../common/routing';
@@ -28,7 +31,9 @@ import { useTrustedAppsSelector } from './hooks';
import { FormattedDate } from '../../../../common/components/formatted_date';
import { OS_TITLES } from './constants';
-const COLUMN_TITLES: Readonly<{ [K in keyof Omit]: string }> = {
+const COLUMN_TITLES: Readonly<
+ { [K in keyof Omit | 'actions']: string }
+> = {
name: i18n.translate('xpack.securitySolution.trustedapps.list.columns.name', {
defaultMessage: 'Name',
}),
@@ -41,9 +46,41 @@ const COLUMN_TITLES: Readonly<{ [K in keyof Omit]:
created_by: i18n.translate('xpack.securitySolution.trustedapps.list.columns.createdBy', {
defaultMessage: 'Created By',
}),
+ actions: i18n.translate('xpack.securitySolution.trustedapps.list.columns.actions', {
+ defaultMessage: 'Actions',
+ }),
};
-const getColumnDefinitions = (): Array>> => [
+type ActionsList = EuiTableActionsColumnType>['actions'];
+
+const getActionDefinitions = (dispatch: Dispatch>): ActionsList => [
+ {
+ name: i18n.translate('xpack.securitySolution.trustedapps.list.actions.delete', {
+ defaultMessage: 'Delete',
+ }),
+ description: i18n.translate(
+ 'xpack.securitySolution.trustedapps.list.actions.delete.description',
+ {
+ defaultMessage: 'Delete this entry',
+ }
+ ),
+ 'data-test-subj': 'trustedAppDeleteAction',
+ isPrimary: true,
+ icon: 'trash',
+ color: 'danger',
+ type: 'icon',
+ onClick: (item: Immutable) => {
+ dispatch({
+ type: 'trustedAppDeletionDialogStarted',
+ payload: { entry: item },
+ });
+ },
+ },
+];
+
+type ColumnsList = Array>>;
+
+const getColumnDefinitions = (dispatch: Dispatch>): ColumnsList => [
{
field: 'name',
name: COLUMN_TITLES.name,
@@ -72,6 +109,10 @@ const getColumnDefinitions = (): Array
field: 'created_by',
name: COLUMN_TITLES.created_by,
},
+ {
+ name: COLUMN_TITLES.actions,
+ actions: getActionDefinitions(dispatch),
+ },
];
export const TrustedAppsList = memo(() => {
@@ -79,11 +120,12 @@ export const TrustedAppsList = memo(() => {
const pageSize = useTrustedAppsSelector(getListCurrentPageSize);
const totalItemCount = useTrustedAppsSelector(getListTotalItemsCount);
const listItems = useTrustedAppsSelector(getListItems);
+ const dispatch = useDispatch();
const history = useHistory();
return (
getColumnDefinitions(dispatch), [dispatch])}
items={useMemo(() => [...listItems], [listItems])}
error={useTrustedAppsSelector(getListErrorMessage)}
loading={useTrustedAppsSelector(isListLoading)}
diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_notifications.test.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_notifications.test.tsx
new file mode 100644
index 0000000000000..cc45abf493582
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_notifications.test.tsx
@@ -0,0 +1,98 @@
+/*
+ * 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 React from 'react';
+import { Provider } from 'react-redux';
+import { render } from '@testing-library/react';
+
+import { NotificationsStart } from 'kibana/public';
+
+import { coreMock } from '../../../../../../../../src/core/public/mocks';
+import { KibanaContextProvider } from '../../../../../../../../src/plugins/kibana_react/public/context';
+
+import {
+ createGlobalNoMiddlewareStore,
+ createSampleTrustedApp,
+ createServerApiError,
+} from '../test_utils';
+
+import { TrustedAppsNotifications } from './trusted_apps_notifications';
+
+const mockNotifications = () => coreMock.createStart({ basePath: '/mock' }).notifications;
+
+const renderNotifications = (
+ store: ReturnType,
+ notifications: NotificationsStart
+) => {
+ const Wrapper: React.FC = ({ children }) => (
+
+ {children}
+
+ );
+
+ return render( , { wrapper: Wrapper });
+};
+
+describe('TrustedAppsNotifications', () => {
+ it('renders correctly initially', () => {
+ const notifications = mockNotifications();
+
+ renderNotifications(createGlobalNoMiddlewareStore(), notifications);
+
+ expect(notifications.toasts.addSuccess).not.toBeCalled();
+ expect(notifications.toasts.addDanger).not.toBeCalled();
+ });
+
+ it('shows success notification when deletion successful', () => {
+ const store = createGlobalNoMiddlewareStore();
+ const notifications = mockNotifications();
+
+ renderNotifications(store, notifications);
+
+ store.dispatch({
+ type: 'trustedAppDeletionDialogStarted',
+ payload: { entry: createSampleTrustedApp(3) },
+ });
+ store.dispatch({
+ type: 'trustedAppDeletionSubmissionResourceStateChanged',
+ payload: { newState: { type: 'LoadedResourceState', data: null } },
+ });
+ store.dispatch({
+ type: 'trustedAppDeletionDialogClosed',
+ });
+
+ expect(notifications.toasts.addSuccess).toBeCalledWith({
+ text: '"trusted app 3" has been removed from the Trusted Applications list.',
+ title: 'Successfully removed',
+ });
+ expect(notifications.toasts.addDanger).not.toBeCalled();
+ });
+
+ it('shows error notification when deletion fails', () => {
+ const store = createGlobalNoMiddlewareStore();
+ const notifications = mockNotifications();
+
+ renderNotifications(store, notifications);
+
+ store.dispatch({
+ type: 'trustedAppDeletionDialogStarted',
+ payload: { entry: createSampleTrustedApp(3) },
+ });
+ store.dispatch({
+ type: 'trustedAppDeletionSubmissionResourceStateChanged',
+ payload: {
+ newState: { type: 'FailedResourceState', error: createServerApiError('Not Found') },
+ },
+ });
+
+ expect(notifications.toasts.addSuccess).not.toBeCalled();
+ expect(notifications.toasts.addDanger).toBeCalledWith({
+ text:
+ 'Unable to remove "trusted app 3" from the Trusted Applications list. Reason: Not Found',
+ title: 'Removal failure',
+ });
+ });
+});
diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_notifications.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_notifications.tsx
new file mode 100644
index 0000000000000..9c0fe8eb6f0cb
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_notifications.tsx
@@ -0,0 +1,58 @@
+/*
+ * 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 React, { memo } from 'react';
+import { i18n } from '@kbn/i18n';
+
+import { ServerApiError } from '../../../../common/types';
+import { Immutable, TrustedApp } from '../../../../../common/endpoint/types';
+import { getDeletionDialogEntry, getDeletionError, isDeletionSuccessful } from '../store/selectors';
+
+import { useToasts } from '../../../../common/lib/kibana';
+import { useTrustedAppsSelector } from './hooks';
+
+const getDeletionErrorMessage = (error: ServerApiError, entry: Immutable) => {
+ return {
+ title: i18n.translate('xpack.securitySolution.trustedapps.deletionError.title', {
+ defaultMessage: 'Removal failure',
+ }),
+ text: i18n.translate('xpack.securitySolution.trustedapps.deletionError.text', {
+ defaultMessage:
+ 'Unable to remove "{name}" from the Trusted Applications list. Reason: {message}',
+ values: { name: entry.name, message: error.message },
+ }),
+ };
+};
+
+const getDeletionSuccessMessage = (entry: Immutable) => {
+ return {
+ title: i18n.translate('xpack.securitySolution.trustedapps.deletionSuccess.title', {
+ defaultMessage: 'Successfully removed',
+ }),
+ text: i18n.translate('xpack.securitySolution.trustedapps.deletionSuccess.text', {
+ defaultMessage: '"{name}" has been removed from the Trusted Applications list.',
+ values: { name: entry?.name },
+ }),
+ };
+};
+
+export const TrustedAppsNotifications = memo(() => {
+ const deletionError = useTrustedAppsSelector(getDeletionError);
+ const deletionDialogEntry = useTrustedAppsSelector(getDeletionDialogEntry);
+ const deletionSuccessful = useTrustedAppsSelector(isDeletionSuccessful);
+ const toasts = useToasts();
+
+ if (deletionError && deletionDialogEntry) {
+ toasts.addDanger(getDeletionErrorMessage(deletionError, deletionDialogEntry));
+ }
+
+ if (deletionSuccessful && deletionDialogEntry) {
+ toasts.addSuccess(getDeletionSuccessMessage(deletionDialogEntry));
+ }
+
+ return <>>;
+});
+
+TrustedAppsNotifications.displayName = 'TrustedAppsNotifications';
diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_page.test.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_page.test.tsx
index 12edcd95e237d..117d8f25b7039 100644
--- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_page.test.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_page.test.tsx
@@ -21,6 +21,15 @@ describe('TrustedAppsPage', () => {
let coreStart: AppContextTestRender['coreStart'];
let waitForAction: MiddlewareActionSpyHelper['waitForAction'];
let render: () => ReturnType;
+ const originalScrollTo = window.scrollTo;
+
+ beforeAll(() => {
+ window.scrollTo = () => {};
+ });
+
+ afterAll(() => {
+ window.scrollTo = originalScrollTo;
+ });
beforeEach(() => {
const mockedContext = createAppRootMockRenderer();
diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_page.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_page.tsx
index a0dae900eb30e..c1c23a3960962 100644
--- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_page.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_page.tsx
@@ -9,6 +9,8 @@ import { EuiButton } from '@elastic/eui';
import { useHistory } from 'react-router-dom';
import { AdministrationListPage } from '../../../components/administration_list_page';
import { TrustedAppsList } from './trusted_apps_list';
+import { TrustedAppDeletionDialog } from './trusted_app_deletion_dialog';
+import { TrustedAppsNotifications } from './trusted_apps_notifications';
import { CreateTrustedAppFlyout } from './components/create_trusted_app_flyout';
import { getTrustedAppsListPath } from '../../../common/routing';
import { useTrustedAppsSelector } from './hooks';
@@ -63,6 +65,8 @@ export const TrustedAppsPage = memo(() => {
}
actions={addButton}
>
+
+
{showAddFlout && (
= {
[MANAGEMENT_STORE_POLICY_LIST_NAMESPACE]: initialPolicyListState(),
[MANAGEMENT_STORE_POLICY_DETAILS_NAMESPACE]: initialPolicyDetailsState(),
[MANAGEMENT_STORE_ENDPOINTS_NAMESPACE]: initialEndpointListState,
- [MANAGEMENT_STORE_TRUSTED_APPS_NAMESPACE]: initialTrustedAppsPageState,
+ [MANAGEMENT_STORE_TRUSTED_APPS_NAMESPACE]: initialTrustedAppsPageState(),
};
/**
diff --git a/x-pack/plugins/security_solution/public/network/components/tls_table/columns.tsx b/x-pack/plugins/security_solution/public/network/components/tls_table/columns.tsx
index 33667a65a95e9..94de71017d339 100644
--- a/x-pack/plugins/security_solution/public/network/components/tls_table/columns.tsx
+++ b/x-pack/plugins/security_solution/public/network/components/tls_table/columns.tsx
@@ -8,9 +8,9 @@
import React from 'react';
import moment from 'moment';
-import { TlsNode } from '../../../graphql/types';
-import { Columns } from '../../../common/components/paginated_table';
+import { NetworkTlsNode } from '../../../../common/search_strategy';
+import { Columns } from '../../../common/components/paginated_table';
import {
getRowItemDraggables,
getRowItemDraggable,
@@ -21,11 +21,11 @@ import { PreferenceFormattedDate } from '../../../common/components/formatted_da
import * as i18n from './translations';
export type TlsColumns = [
- Columns,
- Columns,
- Columns,
- Columns,
- Columns
+ Columns,
+ Columns,
+ Columns,
+ Columns,
+ Columns
];
export const getTlsColumns = (tableId: string): TlsColumns => [
diff --git a/x-pack/plugins/security_solution/public/network/components/tls_table/mock.ts b/x-pack/plugins/security_solution/public/network/components/tls_table/mock.ts
index a90907eb38854..0e16d76d300de 100644
--- a/x-pack/plugins/security_solution/public/network/components/tls_table/mock.ts
+++ b/x-pack/plugins/security_solution/public/network/components/tls_table/mock.ts
@@ -4,9 +4,9 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { TlsData } from '../../../graphql/types';
+import { NetworkTlsStrategyResponse } from '../../../../common/search_strategy';
-export const mockTlsData: TlsData = {
+export const mockTlsData: NetworkTlsStrategyResponse = {
totalCount: 2,
edges: [
{
@@ -51,4 +51,5 @@ export const mockTlsData: TlsData = {
fakeTotalCount: 50,
showMorePagesIndicator: true,
},
+ rawResponse: {} as NetworkTlsStrategyResponse['rawResponse'],
};
diff --git a/x-pack/plugins/security_solution/public/network/containers/tls/index.gql_query.ts b/x-pack/plugins/security_solution/public/network/containers/tls/index.gql_query.ts
deleted file mode 100644
index f513a94d69667..0000000000000
--- a/x-pack/plugins/security_solution/public/network/containers/tls/index.gql_query.ts
+++ /dev/null
@@ -1,57 +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 gql from 'graphql-tag';
-
-export const tlsQuery = gql`
- query GetTlsQuery(
- $sourceId: ID!
- $filterQuery: String
- $flowTarget: FlowTargetSourceDest!
- $ip: String!
- $pagination: PaginationInputPaginated!
- $sort: TlsSortField!
- $timerange: TimerangeInput!
- $defaultIndex: [String!]!
- $inspect: Boolean!
- ) {
- source(id: $sourceId) {
- id
- Tls(
- filterQuery: $filterQuery
- flowTarget: $flowTarget
- ip: $ip
- pagination: $pagination
- sort: $sort
- timerange: $timerange
- defaultIndex: $defaultIndex
- ) {
- totalCount
- edges {
- node {
- _id
- subjects
- ja3
- issuers
- notAfter
- }
- cursor {
- value
- }
- }
- pageInfo {
- activePage
- fakeTotalCount
- showMorePagesIndicator
- }
- inspect @include(if: $inspect) {
- dsl
- response
- }
- }
- }
- }
-`;
diff --git a/x-pack/plugins/security_solution/public/network/containers/tls/index.tsx b/x-pack/plugins/security_solution/public/network/containers/tls/index.tsx
index 96dc6dd6e4418..f40675a1255ff 100644
--- a/x-pack/plugins/security_solution/public/network/containers/tls/index.tsx
+++ b/x-pack/plugins/security_solution/public/network/containers/tls/index.tsx
@@ -13,7 +13,7 @@ import { ESTermQuery } from '../../../../common/typed_json';
import { inputsModel, State } from '../../../common/store';
import { useKibana } from '../../../common/lib/kibana';
import { createFilter } from '../../../common/containers/helpers';
-import { TlsEdges, PageInfoPaginated, FlowTargetSourceDest } from '../../../graphql/types';
+import { PageInfoPaginated, FlowTargetSourceDest } from '../../../graphql/types';
import { generateTablePaginationOptions } from '../../../common/components/paginated_table/helpers';
import { networkModel, networkSelectors } from '../../store';
import {
@@ -39,7 +39,7 @@ export interface NetworkTlsArgs {
loadPage: (newActivePage: number) => void;
pageInfo: PageInfoPaginated;
refetch: inputsModel.Refetch;
- tls: TlsEdges[];
+ tls: NetworkTlsStrategyResponse['edges'];
totalCount: number;
}
@@ -81,6 +81,7 @@ export const useNetworkTls = ({
factoryQueryType: NetworkQueries.tls,
filterQuery: createFilter(filterQuery),
flowTarget,
+ id,
ip,
pagination: generateTablePaginationOptions(activePage, limit),
sort,
diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/handlers.ts b/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/handlers.ts
index ec4d1efb81b11..7cf3d467c10c2 100644
--- a/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/handlers.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/handlers.ts
@@ -6,6 +6,7 @@
import { RequestHandler, RequestHandlerContext } from 'kibana/server';
import {
+ DeleteTrustedAppsRequestParams,
GetTrustedAppsListRequest,
GetTrustedListAppsResponse,
PostTrustedAppCreateRequest,
@@ -13,7 +14,6 @@ import {
import { EndpointAppContext } from '../../types';
import { exceptionItemToTrustedAppItem, newTrustedAppItemToExceptionItem } from './utils';
import { ENDPOINT_TRUSTED_APPS_LIST_ID } from '../../../../../lists/common/constants';
-import { DeleteTrustedAppsRequestParams } from './types';
import { ExceptionListClient } from '../../../../../lists/server';
const exceptionListClientFromContext = (context: RequestHandlerContext): ExceptionListClient => {
diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/trusted_apps.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/trusted_apps.test.ts
index 2368dcda09a38..98c9b79f32d6b 100644
--- a/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/trusted_apps.test.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/trusted_apps.test.ts
@@ -18,6 +18,7 @@ import {
TRUSTED_APPS_LIST_API,
} from '../../../../common/endpoint/constants';
import {
+ DeleteTrustedAppsRequestParams,
GetTrustedAppsListRequest,
PostTrustedAppCreateRequest,
} from '../../../../common/endpoint/types';
@@ -30,7 +31,6 @@ import {
ExceptionListItemSchema,
FoundExceptionListItemSchema,
} from '../../../../../lists/common/schemas/response';
-import { DeleteTrustedAppsRequestParams } from './types';
import { getExceptionListItemSchemaMock } from '../../../../../lists/common/schemas/response/exception_list_item_schema.mock';
type RequestHandlerContextWithLists = ReturnType & {
diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/types.ts b/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/types.ts
deleted file mode 100644
index 13c8bcfc20793..0000000000000
--- a/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/types.ts
+++ /dev/null
@@ -1,10 +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 { TypeOf } from '@kbn/config-schema';
-import { DeleteTrustedAppsRequestSchema } from '../../../../common/endpoint/schema/trusted_apps';
-
-export type DeleteTrustedAppsRequestParams = TypeOf;
diff --git a/x-pack/plugins/security_solution/server/graphql/index.ts b/x-pack/plugins/security_solution/server/graphql/index.ts
index 7e25735707893..959aa4549d43f 100644
--- a/x-pack/plugins/security_solution/server/graphql/index.ts
+++ b/x-pack/plugins/security_solution/server/graphql/index.ts
@@ -26,7 +26,6 @@ import { toNumberSchema } from './scalar_to_number_array';
import { sourceStatusSchema } from './source_status';
import { sourcesSchema } from './sources';
import { timelineSchema } from './timeline';
-import { tlsSchema } from './tls';
import { uncommonProcessesSchema } from './uncommon_processes';
import { whoAmISchema } from './who_am_i';
import { matrixHistogramSchema } from './matrix_histogram';
@@ -53,7 +52,6 @@ export const schemas = [
sourceStatusSchema,
sharedSchema,
timelineSchema,
- tlsSchema,
uncommonProcessesSchema,
whoAmISchema,
];
diff --git a/x-pack/plugins/security_solution/server/graphql/tls/index.ts b/x-pack/plugins/security_solution/server/graphql/tls/index.ts
deleted file mode 100644
index 7d745742090a6..0000000000000
--- a/x-pack/plugins/security_solution/server/graphql/tls/index.ts
+++ /dev/null
@@ -1,8 +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 { createTlsResolvers } from './resolvers';
-export { tlsSchema } from './schema.gql';
diff --git a/x-pack/plugins/security_solution/server/graphql/tls/resolvers.ts b/x-pack/plugins/security_solution/server/graphql/tls/resolvers.ts
deleted file mode 100644
index bfa3fddc3c8a5..0000000000000
--- a/x-pack/plugins/security_solution/server/graphql/tls/resolvers.ts
+++ /dev/null
@@ -1,40 +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 { SourceResolvers } from '../../graphql/types';
-import { AppResolverOf, ChildResolverOf } from '../../lib/framework';
-import { TLS, TlsRequestOptions } from '../../lib/tls';
-import { createOptionsPaginated } from '../../utils/build_query/create_options';
-import { QuerySourceResolver } from '../sources/resolvers';
-
-export type QueryTlsResolver = ChildResolverOf<
- AppResolverOf,
- QuerySourceResolver
->;
-
-export interface TlsResolversDeps {
- tls: TLS;
-}
-
-export const createTlsResolvers = (
- libs: TlsResolversDeps
-): {
- Source: {
- Tls: QueryTlsResolver;
- };
-} => ({
- Source: {
- async Tls(source, args, { req }, info) {
- const options: TlsRequestOptions = {
- ...createOptionsPaginated(source, args, info),
- ip: args.ip,
- sort: args.sort,
- flowTarget: args.flowTarget,
- };
- return libs.tls.getTls(req, options);
- },
- },
-});
diff --git a/x-pack/plugins/security_solution/server/graphql/tls/schema.gql.ts b/x-pack/plugins/security_solution/server/graphql/tls/schema.gql.ts
deleted file mode 100644
index 452c615c65aa5..0000000000000
--- a/x-pack/plugins/security_solution/server/graphql/tls/schema.gql.ts
+++ /dev/null
@@ -1,47 +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 gql from 'graphql-tag';
-
-export const tlsSchema = gql`
- enum TlsFields {
- _id
- }
- type TlsNode {
- _id: String
- timestamp: Date
- notAfter: [String!]
- subjects: [String!]
- ja3: [String!]
- issuers: [String!]
- }
- input TlsSortField {
- field: TlsFields!
- direction: Direction!
- }
- type TlsEdges {
- node: TlsNode!
- cursor: CursorType!
- }
- type TlsData {
- edges: [TlsEdges!]!
- totalCount: Float!
- pageInfo: PageInfoPaginated!
- inspect: Inspect
- }
- extend type Source {
- Tls(
- filterQuery: String
- id: String
- ip: String!
- pagination: PaginationInputPaginated!
- sort: TlsSortField!
- flowTarget: FlowTargetSourceDest!
- timerange: TimerangeInput!
- defaultIndex: [String!]!
- ): TlsData!
- }
-`;
diff --git a/x-pack/plugins/security_solution/server/graphql/types.ts b/x-pack/plugins/security_solution/server/graphql/types.ts
index d568ae13adb95..daaddda0460df 100644
--- a/x-pack/plugins/security_solution/server/graphql/types.ts
+++ b/x-pack/plugins/security_solution/server/graphql/types.ts
@@ -97,12 +97,6 @@ export interface NetworkHttpSortField {
direction: Direction;
}
-export interface TlsSortField {
- field: TlsFields;
-
- direction: Direction;
-}
-
export interface PageInfoTimeline {
pageIndex: number;
@@ -358,10 +352,6 @@ export enum NetworkDnsFields {
dnsBytesOut = 'dnsBytesOut',
}
-export enum TlsFields {
- _id = '_id',
-}
-
export enum DataProviderType {
default = 'default',
template = 'template',
@@ -572,8 +562,6 @@ export interface Source {
OverviewNetwork?: Maybe;
OverviewHost?: Maybe;
-
- Tls: TlsData;
/** Gets UncommonProcesses based on a timerange, or all UncommonProcesses if no criteria is specified */
UncommonProcesses: UncommonProcessesData;
/** Just a simple example to get the app name */
@@ -1906,36 +1894,6 @@ export interface OverviewHostData {
inspect?: Maybe;
}
-export interface TlsData {
- edges: TlsEdges[];
-
- totalCount: number;
-
- pageInfo: PageInfoPaginated;
-
- inspect?: Maybe;
-}
-
-export interface TlsEdges {
- node: TlsNode;
-
- cursor: CursorType;
-}
-
-export interface TlsNode {
- _id?: Maybe;
-
- timestamp?: Maybe;
-
- notAfter?: Maybe;
-
- subjects?: Maybe;
-
- ja3?: Maybe;
-
- issuers?: Maybe;
-}
-
export interface UncommonProcessesData {
edges: UncommonProcessesEdges[];
@@ -2579,23 +2537,6 @@ export interface OverviewHostSourceArgs {
defaultIndex: string[];
}
-export interface TlsSourceArgs {
- filterQuery?: Maybe;
-
- id?: Maybe;
-
- ip: string;
-
- pagination: PaginationInputPaginated;
-
- sort: TlsSortField;
-
- flowTarget: FlowTargetSourceDest;
-
- timerange: TimerangeInput;
-
- defaultIndex: string[];
-}
export interface UncommonProcessesSourceArgs {
timerange: TimerangeInput;
@@ -3045,8 +2986,6 @@ export namespace SourceResolvers {
OverviewNetwork?: OverviewNetworkResolver, TypeParent, TContext>;
OverviewHost?: OverviewHostResolver, TypeParent, TContext>;
-
- Tls?: TlsResolver;
/** Gets UncommonProcesses based on a timerange, or all UncommonProcesses if no criteria is specified */
UncommonProcesses?: UncommonProcessesResolver;
/** Just a simple example to get the app name */
@@ -3430,30 +3369,6 @@ export namespace SourceResolvers {
defaultIndex: string[];
}
- export type TlsResolver = Resolver<
- R,
- Parent,
- TContext,
- TlsArgs
- >;
- export interface TlsArgs {
- filterQuery?: Maybe;
-
- id?: Maybe;
-
- ip: string;
-
- pagination: PaginationInputPaginated;
-
- sort: TlsSortField;
-
- flowTarget: FlowTargetSourceDest;
-
- timerange: TimerangeInput;
-
- defaultIndex: string[];
- }
-
export type UncommonProcessesResolver<
R = UncommonProcessesData,
Parent = Source,
@@ -7942,105 +7857,6 @@ export namespace OverviewHostDataResolvers {
> = Resolver;
}
-export namespace TlsDataResolvers {
- export interface Resolvers {
- edges?: EdgesResolver;
-
- totalCount?: TotalCountResolver;
-
- pageInfo?: PageInfoResolver;
-
- inspect?: InspectResolver, TypeParent, TContext>;
- }
-
- export type EdgesResolver = Resolver<
- R,
- Parent,
- TContext
- >;
- export type TotalCountResolver = Resolver<
- R,
- Parent,
- TContext
- >;
- export type PageInfoResolver<
- R = PageInfoPaginated,
- Parent = TlsData,
- TContext = SiemContext
- > = Resolver;
- export type InspectResolver<
- R = Maybe,
- Parent = TlsData,
- TContext = SiemContext
- > = Resolver;
-}
-
-export namespace TlsEdgesResolvers {
- export interface Resolvers {
- node?: NodeResolver;
-
- cursor?: CursorResolver;
- }
-
- export type NodeResolver = Resolver<
- R,
- Parent,
- TContext
- >;
- export type CursorResolver = Resolver<
- R,
- Parent,
- TContext
- >;
-}
-
-export namespace TlsNodeResolvers {
- export interface Resolvers {
- _id?: _IdResolver, TypeParent, TContext>;
-
- timestamp?: TimestampResolver, TypeParent, TContext>;
-
- notAfter?: NotAfterResolver, TypeParent, TContext>;
-
- subjects?: SubjectsResolver, TypeParent, TContext>;
-
- ja3?: Ja3Resolver, TypeParent, TContext>;
-
- issuers?: IssuersResolver, TypeParent, TContext>;
- }
-
- export type _IdResolver, Parent = TlsNode, TContext = SiemContext> = Resolver<
- R,
- Parent,
- TContext
- >;
- export type TimestampResolver<
- R = Maybe,
- Parent = TlsNode,
- TContext = SiemContext
- > = Resolver;
- export type NotAfterResolver<
- R = Maybe,
- Parent = TlsNode,
- TContext = SiemContext
- > = Resolver;
- export type SubjectsResolver<
- R = Maybe,
- Parent = TlsNode,
- TContext = SiemContext
- > = Resolver;
- export type Ja3Resolver, Parent = TlsNode, TContext = SiemContext> = Resolver<
- R,
- Parent,
- TContext
- >;
- export type IssuersResolver<
- R = Maybe,
- Parent = TlsNode,
- TContext = SiemContext
- > = Resolver;
-}
-
export namespace UncommonProcessesDataResolvers {
export interface Resolvers {
edges?: EdgesResolver;
@@ -9502,9 +9318,6 @@ export type IResolvers = {
NetworkHttpItem?: NetworkHttpItemResolvers.Resolvers;
OverviewNetworkData?: OverviewNetworkDataResolvers.Resolvers;
OverviewHostData?: OverviewHostDataResolvers.Resolvers;
- TlsData?: TlsDataResolvers.Resolvers;
- TlsEdges?: TlsEdgesResolvers.Resolvers;
- TlsNode?: TlsNodeResolvers.Resolvers;
UncommonProcessesData?: UncommonProcessesDataResolvers.Resolvers;
UncommonProcessesEdges?: UncommonProcessesEdgesResolvers.Resolvers;
UncommonProcessItem?: UncommonProcessItemResolvers.Resolvers;
diff --git a/x-pack/plugins/security_solution/server/init_server.ts b/x-pack/plugins/security_solution/server/init_server.ts
index 1463d7f0da284..2ef42eaee4b98 100644
--- a/x-pack/plugins/security_solution/server/init_server.ts
+++ b/x-pack/plugins/security_solution/server/init_server.ts
@@ -28,7 +28,6 @@ import { createTimelineResolvers } from './graphql/timeline';
import { createUncommonProcessesResolvers } from './graphql/uncommon_processes';
import { createWhoAmIResolvers } from './graphql/who_am_i';
import { AppBackendLibs } from './lib/types';
-import { createTlsResolvers } from './graphql/tls';
import { createMatrixHistogramResolvers } from './graphql/matrix_histogram';
export const initServer = (libs: AppBackendLibs) => {
@@ -55,7 +54,6 @@ export const initServer = (libs: AppBackendLibs) => {
createSourcesResolvers(libs) as IResolvers,
createSourceStatusResolvers(libs) as IResolvers,
createTimelineResolvers(libs) as IResolvers,
- createTlsResolvers(libs) as IResolvers,
createUncommonProcessesResolvers(libs) as IResolvers,
createWhoAmIResolvers() as IResolvers,
createKpiHostsResolvers(libs) as IResolvers,
diff --git a/x-pack/plugins/security_solution/server/lib/compose/kibana.ts b/x-pack/plugins/security_solution/server/lib/compose/kibana.ts
index 3c8abb4670fba..fda7ea772eaef 100644
--- a/x-pack/plugins/security_solution/server/lib/compose/kibana.ts
+++ b/x-pack/plugins/security_solution/server/lib/compose/kibana.ts
@@ -17,7 +17,6 @@ import { ElasticsearchKpiHostsAdapter } from '../kpi_hosts/elasticsearch_adapter
import { ElasticsearchIndexFieldAdapter, IndexFields } from '../index_fields';
import { ElasticsearchIpDetailsAdapter, IpDetails } from '../ip_details';
-import { ElasticsearchTlsAdapter, TLS } from '../tls';
import { KpiNetwork } from '../kpi_network';
import { ElasticsearchKpiNetworkAdapter } from '../kpi_network/elasticsearch_adapter';
@@ -50,7 +49,6 @@ export function compose(
fields: new IndexFields(new ElasticsearchIndexFieldAdapter()),
hosts: new Hosts(new ElasticsearchHostsAdapter(framework, endpointContext)),
ipDetails: new IpDetails(new ElasticsearchIpDetailsAdapter(framework)),
- tls: new TLS(new ElasticsearchTlsAdapter(framework)),
kpiHosts: new KpiHosts(new ElasticsearchKpiHostsAdapter(framework)),
kpiNetwork: new KpiNetwork(new ElasticsearchKpiNetworkAdapter(framework)),
matrixHistogram: new MatrixHistogram(new ElasticsearchMatrixHistogramAdapter(framework)),
diff --git a/x-pack/plugins/security_solution/server/lib/tls/elasticsearch_adapter.test.ts b/x-pack/plugins/security_solution/server/lib/tls/elasticsearch_adapter.test.ts
deleted file mode 100644
index 428685cbaddb8..0000000000000
--- a/x-pack/plugins/security_solution/server/lib/tls/elasticsearch_adapter.test.ts
+++ /dev/null
@@ -1,63 +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 { buildTlsQuery } from './query_tls.dsl';
-import { ElasticsearchTlsAdapter } from './elasticsearch_adapter';
-import expect from '@kbn/expect';
-import { FrameworkRequest, FrameworkAdapter } from '../framework';
-import { mockRequest, mockResponse, mockOptions, expectedTlsEdges, mockTlsQuery } from './mock';
-import { TlsData } from '../../graphql/types';
-
-jest.mock('./query_tls.dsl', () => {
- return {
- buildTlsQuery: jest.fn(),
- };
-});
-
-describe('elasticsearch_adapter', () => {
- describe('#getTls', () => {
- let data: TlsData;
- const mockCallWithRequest = jest.fn();
- const mockFramework: FrameworkAdapter = {
- callWithRequest: mockCallWithRequest,
- registerGraphQLEndpoint: jest.fn(),
- getIndexPatternsService: jest.fn(),
- };
-
- beforeAll(async () => {
- (buildTlsQuery as jest.Mock).mockReset();
- (buildTlsQuery as jest.Mock).mockReturnValue(mockTlsQuery);
-
- mockCallWithRequest.mockResolvedValue(mockResponse);
- jest.doMock('../framework', () => ({
- callWithRequest: mockCallWithRequest,
- }));
-
- const EsTls = new ElasticsearchTlsAdapter(mockFramework);
- data = await EsTls.getTls(mockRequest as FrameworkRequest, mockOptions);
- });
-
- afterAll(() => {
- mockCallWithRequest.mockRestore();
- (buildTlsQuery as jest.Mock).mockClear();
- });
-
- test('buildTlsQuery', () => {
- expect((buildTlsQuery as jest.Mock).mock.calls[0][0]).to.eql(mockOptions);
- });
-
- test('will return tlsEdges correctly', () => {
- expect(data.edges).to.eql(expectedTlsEdges);
- });
-
- test('will return inspect data', () => {
- expect(data.inspect).to.eql({
- dsl: [JSON.stringify(mockTlsQuery, null, 2)],
- response: [JSON.stringify(mockResponse, null, 2)],
- });
- });
- });
-});
diff --git a/x-pack/plugins/security_solution/server/lib/tls/elasticsearch_adapter.ts b/x-pack/plugins/security_solution/server/lib/tls/elasticsearch_adapter.ts
deleted file mode 100644
index ab9175951a8f5..0000000000000
--- a/x-pack/plugins/security_solution/server/lib/tls/elasticsearch_adapter.ts
+++ /dev/null
@@ -1,82 +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 { getOr } from 'lodash/fp';
-
-import { TlsData, TlsEdges } from '../../graphql/types';
-import { inspectStringifyObject } from '../../utils/build_query';
-import { DatabaseSearchResponse, FrameworkAdapter, FrameworkRequest } from '../framework';
-import { TermAggregation } from '../types';
-import { DEFAULT_MAX_TABLE_QUERY_SIZE } from '../../../common/constants';
-import { TlsRequestOptions } from './index';
-
-import { TlsAdapter, TlsBuckets } from './types';
-
-import { buildTlsQuery } from './query_tls.dsl';
-
-export class ElasticsearchTlsAdapter implements TlsAdapter {
- constructor(private readonly framework: FrameworkAdapter) {}
-
- public async getTls(request: FrameworkRequest, options: TlsRequestOptions): Promise {
- if (options.pagination && options.pagination.querySize >= DEFAULT_MAX_TABLE_QUERY_SIZE) {
- throw new Error(`No query size above ${DEFAULT_MAX_TABLE_QUERY_SIZE}`);
- }
- const dsl = buildTlsQuery(options);
- const response = await this.framework.callWithRequest(
- request,
- 'search',
- dsl
- );
-
- const { activePage, cursorStart, fakePossibleCount, querySize } = options.pagination;
- const totalCount = getOr(0, 'aggregations.count.value', response);
- const tlsEdges: TlsEdges[] = getTlsEdges(response, options);
- const fakeTotalCount = fakePossibleCount <= totalCount ? fakePossibleCount : totalCount;
- const edges = tlsEdges.splice(cursorStart, querySize - cursorStart);
- const inspect = {
- dsl: [inspectStringifyObject(dsl)],
- response: [inspectStringifyObject(response)],
- };
- const showMorePagesIndicator = totalCount > fakeTotalCount;
- return {
- edges,
- inspect,
- pageInfo: {
- activePage: activePage ? activePage : 0,
- fakeTotalCount,
- showMorePagesIndicator,
- },
- totalCount,
- };
- }
-}
-
-const getTlsEdges = (
- response: DatabaseSearchResponse,
- options: TlsRequestOptions
-): TlsEdges[] => {
- return formatTlsEdges(getOr([], 'aggregations.sha1.buckets', response));
-};
-
-export const formatTlsEdges = (buckets: TlsBuckets[]): TlsEdges[] => {
- return buckets.map((bucket: TlsBuckets) => {
- const edge: TlsEdges = {
- node: {
- _id: bucket.key,
- subjects: bucket.subjects.buckets.map(({ key }) => key),
- ja3: bucket.ja3.buckets.map(({ key }) => key),
- issuers: bucket.issuers.buckets.map(({ key }) => key),
- // eslint-disable-next-line @typescript-eslint/naming-convention
- notAfter: bucket.not_after.buckets.map(({ key_as_string }) => key_as_string),
- },
- cursor: {
- value: bucket.key,
- tiebreaker: null,
- },
- };
- return edge;
- });
-};
diff --git a/x-pack/plugins/security_solution/server/lib/tls/index.ts b/x-pack/plugins/security_solution/server/lib/tls/index.ts
deleted file mode 100644
index 25e3957cc99db..0000000000000
--- a/x-pack/plugins/security_solution/server/lib/tls/index.ts
+++ /dev/null
@@ -1,26 +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 { FlowTargetSourceDest, TlsSortField, TlsData } from '../../graphql/types';
-import { FrameworkRequest, RequestOptionsPaginated } from '../framework';
-
-import { TlsAdapter } from './types';
-
-export * from './elasticsearch_adapter';
-
-export interface TlsRequestOptions extends RequestOptionsPaginated {
- ip?: string;
- sort: TlsSortField;
- flowTarget: FlowTargetSourceDest;
-}
-
-export class TLS {
- constructor(private readonly adapter: TlsAdapter) {}
-
- public async getTls(req: FrameworkRequest, options: TlsRequestOptions): Promise {
- return this.adapter.getTls(req, options);
- }
-}
diff --git a/x-pack/plugins/security_solution/server/lib/tls/mock.ts b/x-pack/plugins/security_solution/server/lib/tls/mock.ts
deleted file mode 100644
index 62d5e1e61570a..0000000000000
--- a/x-pack/plugins/security_solution/server/lib/tls/mock.ts
+++ /dev/null
@@ -1,481 +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 { Direction, TlsFields, FlowTargetSourceDest } from '../../graphql/types';
-
-export const mockTlsQuery = {
- allowNoIndices: true,
- index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'],
- ignoreUnavailable: true,
- body: {
- aggs: {
- count: { cardinality: { field: 'tls.server_certificate.fingerprint.sha1' } },
- sha1: {
- terms: {
- field: 'tls.server_certificate.fingerprint.sha1',
- size: 10,
- order: { _key: 'desc' },
- },
- aggs: {
- issuers: { terms: { field: 'tls.server.issuer' } },
- subjects: { terms: { field: 'tls.server.subject' } },
- not_after: { terms: { field: 'tls.server.not_after' } },
- ja3: { terms: { field: 'tls.server.ja3s' } },
- },
- },
- },
- query: {
- bool: { filter: [{ range: { '@timestamp': { gte: 1570719927430, lte: 1570806327431 } } }] },
- },
- size: 0,
- track_total_hits: false,
- },
-};
-
-export const expectedTlsEdges = [
- {
- cursor: {
- tiebreaker: null,
- value: 'fff8dc95436e0e25ce46b1526a1a547e8cf3bb82',
- },
- node: {
- _id: 'fff8dc95436e0e25ce46b1526a1a547e8cf3bb82',
- subjects: ['*.1.nflxso.net'],
- issuers: ['DigiCert SHA2 Secure Server CA'],
- ja3: ['95d2dd53a89b334cddd5c22e81e7fe61'],
- notAfter: ['2019-10-27T12:00:00.000Z'],
- },
- },
- {
- cursor: {
- tiebreaker: null,
- value: 'fd8440c4b20978b173e0910e2639d114f0d405c5',
- },
- node: {
- _id: 'fd8440c4b20978b173e0910e2639d114f0d405c5',
- subjects: ['cogocast.net'],
- issuers: ['Amazon'],
- ja3: ['a111d93cdf31f993c40a8a9ef13e8d7e'],
- notAfter: ['2020-02-01T12:00:00.000Z'],
- },
- },
- {
- cursor: { tiebreaker: null, value: 'fcdc16645ebb3386adc96e7ba735c4745709b9dd' },
- node: {
- _id: 'fcdc16645ebb3386adc96e7ba735c4745709b9dd',
- subjects: ['player-devintever2.mountain.siriusxm.com'],
- issuers: ['Trustwave Organization Validation SHA256 CA, Level 1'],
- ja3: ['6fa3244afc6bb6f9fad207b6b52af26b'],
- notAfter: ['2020-03-06T21:57:09.000Z'],
- },
- },
- {
- cursor: { tiebreaker: null, value: 'fccf375789cb7e671502a7b0cc969f218a4b2c70' },
- node: {
- _id: 'fccf375789cb7e671502a7b0cc969f218a4b2c70',
- subjects: ['appleid.apple.com'],
- issuers: ['DigiCert SHA2 Extended Validation Server CA'],
- ja3: ['6fa3244afc6bb6f9fad207b6b52af26b'],
- notAfter: ['2020-07-04T12:00:00.000Z'],
- },
- },
- {
- cursor: { tiebreaker: null, value: 'fc4a296b706fa18ac50b96f5c0327c69db4a8981' },
- node: {
- _id: 'fc4a296b706fa18ac50b96f5c0327c69db4a8981',
- subjects: ['itunes.apple.com'],
- issuers: ['DigiCert SHA2 Extended Validation Server CA'],
- ja3: ['a441a33aaee795f498d6b764cc78989a'],
- notAfter: ['2020-03-24T12:00:00.000Z'],
- },
- },
- {
- cursor: { tiebreaker: null, value: 'fc2cbc41f6a0e9c0118de4fe40f299f7207b797e' },
- node: {
- _id: 'fc2cbc41f6a0e9c0118de4fe40f299f7207b797e',
- subjects: ['incapsula.com'],
- issuers: ['GlobalSign CloudSSL CA - SHA256 - G3'],
- ja3: ['6fa3244afc6bb6f9fad207b6b52af26b'],
- notAfter: ['2020-04-04T14:05:06.000Z'],
- },
- },
- {
- cursor: { tiebreaker: null, value: 'fb70d78ffa663a3a4374d841b3288d2de9759566' },
- node: {
- _id: 'fb70d78ffa663a3a4374d841b3288d2de9759566',
- subjects: ['*.siriusxm.com'],
- issuers: ['DigiCert Baltimore CA-2 G2'],
- ja3: ['535aca3d99fc247509cd50933cd71d37', '6fa3244afc6bb6f9fad207b6b52af26b'],
- notAfter: ['2021-10-27T12:00:00.000Z'],
- },
- },
- {
- cursor: { tiebreaker: null, value: 'fb59038dcec33ab3a01a6ae60d0835ad0e04ccf0' },
- node: {
- _id: 'fb59038dcec33ab3a01a6ae60d0835ad0e04ccf0',
- subjects: ['photos.amazon.eu'],
- issuers: ['Amazon'],
- ja3: ['6fa3244afc6bb6f9fad207b6b52af26b'],
- notAfter: ['2020-04-23T12:00:00.000Z'],
- },
- },
- {
- cursor: { tiebreaker: null, value: 'f9815293c883a6006f0b2d95a4895bdc501fd174' },
- node: {
- _id: 'f9815293c883a6006f0b2d95a4895bdc501fd174',
- subjects: ['cdn.hbo.com'],
- issuers: ['Sectigo RSA Organization Validation Secure Server CA'],
- ja3: ['6fa3244afc6bb6f9fad207b6b52af26b'],
- notAfter: ['2021-02-10T23:59:59.000Z'],
- },
- },
- {
- cursor: { tiebreaker: null, value: 'f8db6a69797e383dca2529727369595733123386' },
- node: {
- _id: 'f8db6a69797e383dca2529727369595733123386',
- subjects: ['www.google.com'],
- issuers: ['GTS CA 1O1'],
- ja3: ['a111d93cdf31f993c40a8a9ef13e8d7e'],
- notAfter: ['2019-12-10T13:32:54.000Z'],
- },
- },
-];
-
-export const mockRequest = {
- body: {
- operationName: 'GetTlsQuery',
- variables: {
- defaultIndex: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'],
- filterQuery: '',
- flowTarget: 'source',
- inspect: false,
- ip: '',
- pagination: { activePage: 0, cursorStart: 0, fakePossibleCount: 50, querySize: 10 },
- sort: { field: '_id', direction: 'desc' },
- sourceId: 'default',
- timerange: { interval: '12h', from: 1570716261267, to: 1570802661267 },
- },
- query:
- 'query GetTlsQuery($sourceId: ID!, $filterQuery: String, $flowTarget: FlowTarget!, $ip: String!, $pagination: PaginationInputPaginated!, $sort: TlsSortField!, $timerange: TimerangeInput!, $defaultIndex: [String!]!, $inspect: Boolean!) {\n source(id: $sourceId) {\n id\n Tls(filterQuery: $filterQuery, flowTarget: $flowTarget, ip: $ip, pagination: $pagination, sort: $sort, timerange: $timerange, defaultIndex: $defaultIndex) {\n totalCount\n edges {\n node {\n _id\n subjects\n ja3\n issuers\n notAfter\n __typename\n }\n cursor {\n value\n __typename\n }\n __typename\n }\n pageInfo {\n activePage\n fakeTotalCount\n showMorePagesIndicator\n __typename\n }\n inspect @include(if: $inspect) {\n dsl\n response\n __typename\n }\n __typename\n }\n __typename\n }\n}\n',
- },
-};
-
-export const mockResponse = {
- took: 92,
- timed_out: false,
- _shards: { total: 33, successful: 33, skipped: 0, failed: 0 },
- hits: { max_score: null, hits: [] },
- aggregations: {
- sha1: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 4597,
- buckets: [
- {
- key: 'fff8dc95436e0e25ce46b1526a1a547e8cf3bb82',
- doc_count: 1,
- not_after: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- { key: 1572177600000, key_as_string: '2019-10-27T12:00:00.000Z', doc_count: 1 },
- ],
- },
- issuers: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [{ key: 'DigiCert SHA2 Secure Server CA', doc_count: 1 }],
- },
- subjects: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [{ key: '*.1.nflxso.net', doc_count: 1 }],
- },
- ja3: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [{ key: '95d2dd53a89b334cddd5c22e81e7fe61', doc_count: 1 }],
- },
- },
- {
- key: 'fd8440c4b20978b173e0910e2639d114f0d405c5',
- doc_count: 1,
- not_after: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- { key: 1580558400000, key_as_string: '2020-02-01T12:00:00.000Z', doc_count: 1 },
- ],
- },
- issuers: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [{ key: 'Amazon', doc_count: 1 }],
- },
- subjects: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [{ key: 'cogocast.net', doc_count: 1 }],
- },
- ja3: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [{ key: 'a111d93cdf31f993c40a8a9ef13e8d7e', doc_count: 1 }],
- },
- },
- {
- key: 'fcdc16645ebb3386adc96e7ba735c4745709b9dd',
- doc_count: 1,
- not_after: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- { key: 1583531829000, key_as_string: '2020-03-06T21:57:09.000Z', doc_count: 1 },
- ],
- },
- issuers: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- { key: 'Trustwave Organization Validation SHA256 CA, Level 1', doc_count: 1 },
- ],
- },
- subjects: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [{ key: 'player-devintever2.mountain.siriusxm.com', doc_count: 1 }],
- },
- ja3: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [{ key: '6fa3244afc6bb6f9fad207b6b52af26b', doc_count: 1 }],
- },
- },
- {
- key: 'fccf375789cb7e671502a7b0cc969f218a4b2c70',
- doc_count: 1,
- not_after: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- { key: 1593864000000, key_as_string: '2020-07-04T12:00:00.000Z', doc_count: 1 },
- ],
- },
- issuers: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [{ key: 'DigiCert SHA2 Extended Validation Server CA', doc_count: 1 }],
- },
- subjects: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [{ key: 'appleid.apple.com', doc_count: 1 }],
- },
- ja3: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [{ key: '6fa3244afc6bb6f9fad207b6b52af26b', doc_count: 1 }],
- },
- },
- {
- key: 'fc4a296b706fa18ac50b96f5c0327c69db4a8981',
- doc_count: 2,
- not_after: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- { key: 1585051200000, key_as_string: '2020-03-24T12:00:00.000Z', doc_count: 2 },
- ],
- },
- issuers: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [{ key: 'DigiCert SHA2 Extended Validation Server CA', doc_count: 2 }],
- },
- subjects: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [{ key: 'itunes.apple.com', doc_count: 2 }],
- },
- ja3: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [{ key: 'a441a33aaee795f498d6b764cc78989a', doc_count: 2 }],
- },
- },
- {
- key: 'fc2cbc41f6a0e9c0118de4fe40f299f7207b797e',
- doc_count: 1,
- not_after: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- { key: 1586009106000, key_as_string: '2020-04-04T14:05:06.000Z', doc_count: 1 },
- ],
- },
- issuers: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [{ key: 'GlobalSign CloudSSL CA - SHA256 - G3', doc_count: 1 }],
- },
- subjects: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [{ key: 'incapsula.com', doc_count: 1 }],
- },
- ja3: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [{ key: '6fa3244afc6bb6f9fad207b6b52af26b', doc_count: 1 }],
- },
- },
- {
- key: 'fb70d78ffa663a3a4374d841b3288d2de9759566',
- doc_count: 325,
- not_after: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- { key: 1635336000000, key_as_string: '2021-10-27T12:00:00.000Z', doc_count: 325 },
- ],
- },
- issuers: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [{ key: 'DigiCert Baltimore CA-2 G2', doc_count: 325 }],
- },
- subjects: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [{ key: '*.siriusxm.com', doc_count: 325 }],
- },
- ja3: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- { key: '535aca3d99fc247509cd50933cd71d37', doc_count: 284 },
- { key: '6fa3244afc6bb6f9fad207b6b52af26b', doc_count: 39 },
- ],
- },
- },
- {
- key: 'fb59038dcec33ab3a01a6ae60d0835ad0e04ccf0',
- doc_count: 5,
- not_after: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- { key: 1587643200000, key_as_string: '2020-04-23T12:00:00.000Z', doc_count: 5 },
- ],
- },
- issuers: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [{ key: 'Amazon', doc_count: 5 }],
- },
- subjects: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [{ key: 'photos.amazon.eu', doc_count: 5 }],
- },
- ja3: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [{ key: '6fa3244afc6bb6f9fad207b6b52af26b', doc_count: 5 }],
- },
- },
- {
- key: 'f9815293c883a6006f0b2d95a4895bdc501fd174',
- doc_count: 29,
- not_after: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- { key: 1613001599000, key_as_string: '2021-02-10T23:59:59.000Z', doc_count: 29 },
- ],
- },
- issuers: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- { key: 'Sectigo RSA Organization Validation Secure Server CA', doc_count: 29 },
- ],
- },
- subjects: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [{ key: 'cdn.hbo.com', doc_count: 29 }],
- },
- ja3: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [{ key: '6fa3244afc6bb6f9fad207b6b52af26b', doc_count: 26 }],
- },
- },
- {
- key: 'f8db6a69797e383dca2529727369595733123386',
- doc_count: 5,
- not_after: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- { key: 1575984774000, key_as_string: '2019-12-10T13:32:54.000Z', doc_count: 5 },
- ],
- },
- issuers: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [{ key: 'GTS CA 1O1', doc_count: 5 }],
- },
- subjects: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [{ key: 'www.google.com', doc_count: 5 }],
- },
- ja3: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [{ key: 'a111d93cdf31f993c40a8a9ef13e8d7e', doc_count: 5 }],
- },
- },
- ],
- },
- count: { value: 364 },
- },
-};
-
-export const mockOptions = {
- defaultIndex: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'],
- sourceConfiguration: {
- fields: {
- container: 'docker.container.name',
- host: 'beat.hostname',
- message: ['message', '@message'],
- pod: 'kubernetes.pod.name',
- tiebreaker: '_doc',
- timestamp: '@timestamp',
- },
- },
- timerange: { interval: '12h', to: '2019-10-11T13:51:11.626Z', from: '2019-10-10T13:51:11.626Z' },
- pagination: { activePage: 0, cursorStart: 0, fakePossibleCount: 50, querySize: 10 },
- filterQuery: {},
- fields: [
- 'totalCount',
- '_id',
- 'subjects',
- 'ja3',
- 'issuers',
- 'notAfter',
- 'edges.cursor.value',
- 'pageInfo.activePage',
- 'pageInfo.fakeTotalCount',
- 'pageInfo.showMorePagesIndicator',
- 'inspect.dsl',
- 'inspect.response',
- ],
- ip: '',
- sort: { field: TlsFields._id, direction: Direction.desc },
- flowTarget: FlowTargetSourceDest.source,
-};
diff --git a/x-pack/plugins/security_solution/server/lib/tls/query_tls.dsl.ts b/x-pack/plugins/security_solution/server/lib/tls/query_tls.dsl.ts
deleted file mode 100644
index f6921ddcdf508..0000000000000
--- a/x-pack/plugins/security_solution/server/lib/tls/query_tls.dsl.ts
+++ /dev/null
@@ -1,107 +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 { assertUnreachable } from '../../../common/utility_types';
-import { createQueryFilterClauses } from '../../utils/build_query';
-
-import { TlsRequestOptions } from './index';
-import { TlsSortField, Direction, TlsFields } from '../../graphql/types';
-
-const getAggs = (querySize: number, sort: TlsSortField) => ({
- count: {
- cardinality: {
- field: 'tls.server.hash.sha1',
- },
- },
- sha1: {
- terms: {
- field: 'tls.server.hash.sha1',
- size: querySize,
- order: {
- ...getQueryOrder(sort),
- },
- },
- aggs: {
- issuers: {
- terms: {
- field: 'tls.server.issuer',
- },
- },
- subjects: {
- terms: {
- field: 'tls.server.subject',
- },
- },
- not_after: {
- terms: {
- field: 'tls.server.not_after',
- },
- },
- ja3: {
- terms: {
- field: 'tls.server.ja3s',
- },
- },
- },
- },
-});
-
-export const buildTlsQuery = ({
- ip,
- sort,
- filterQuery,
- flowTarget,
- pagination: { querySize },
- defaultIndex,
- sourceConfiguration: {
- fields: { timestamp },
- },
- timerange: { from, to },
-}: TlsRequestOptions) => {
- const defaultFilter = [
- ...createQueryFilterClauses(filterQuery),
- {
- range: {
- [timestamp]: { gte: from, lte: to, format: 'strict_date_optional_time' },
- },
- },
- ];
-
- const filter = ip ? [...defaultFilter, { term: { [`${flowTarget}.ip`]: ip } }] : defaultFilter;
-
- const dslQuery = {
- allowNoIndices: true,
- index: defaultIndex,
- ignoreUnavailable: true,
- body: {
- aggs: {
- ...getAggs(querySize, sort),
- },
- query: {
- bool: {
- filter,
- },
- },
- size: 0,
- track_total_hits: false,
- },
- };
-
- return dslQuery;
-};
-
-interface QueryOrder {
- _key: Direction;
-}
-
-const getQueryOrder = (sort: TlsSortField): QueryOrder => {
- switch (sort.field) {
- case TlsFields._id:
- return { _key: sort.direction };
- default:
- return assertUnreachable(sort.field);
- }
-};
diff --git a/x-pack/plugins/security_solution/server/lib/tls/types.ts b/x-pack/plugins/security_solution/server/lib/tls/types.ts
deleted file mode 100644
index f18ddc04e14a0..0000000000000
--- a/x-pack/plugins/security_solution/server/lib/tls/types.ts
+++ /dev/null
@@ -1,36 +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 { FrameworkRequest, RequestBasicOptions } from '../framework';
-import { TlsData } from '../../graphql/types';
-
-export interface TlsAdapter {
- getTls(request: FrameworkRequest, options: RequestBasicOptions): Promise;
-}
-
-export interface TlsBuckets {
- key: string;
- timestamp?: {
- value: number;
- value_as_string: string;
- };
-
- subjects: {
- buckets: Readonly>;
- };
-
- ja3: {
- buckets: Readonly