): void;
-}
-
// @public
export interface IRouter {
delete: (route: RouteConfig
, handler: RequestHandler
) => void;
@@ -839,7 +809,7 @@ export interface LegacyRequest extends Request {
// @public @deprecated (undocumented)
export interface LegacyServiceSetupDeps {
- // Warning: (ae-incompatible-release-tags) The symbol "core" is marked as @public, but its signature references "InternalCoreSetup" which is marked as @internal
+ // Warning: (ae-forgotten-export) The symbol "InternalCoreSetup" needs to be exported by the entry point index.d.ts
//
// (undocumented)
core: InternalCoreSetup & {
@@ -851,7 +821,7 @@ export interface LegacyServiceSetupDeps {
// @public @deprecated (undocumented)
export interface LegacyServiceStartDeps {
- // Warning: (ae-incompatible-release-tags) The symbol "core" is marked as @public, but its signature references "InternalCoreStart" which is marked as @internal
+ // Warning: (ae-forgotten-export) The symbol "InternalCoreStart" needs to be exported by the entry point index.d.ts
//
// (undocumented)
core: InternalCoreStart & {
diff --git a/src/core/server/server.test.ts b/src/core/server/server.test.ts
index aee6461580654e..f912a31901ad83 100644
--- a/src/core/server/server.test.ts
+++ b/src/core/server/server.test.ts
@@ -70,7 +70,10 @@ test('injects legacy dependency to context#setup()', async () => {
const pluginA = Symbol();
const pluginB = Symbol();
- const pluginDependencies = new Map([[pluginA, []], [pluginB, [pluginA]]]);
+ const pluginDependencies = new Map([
+ [pluginA, []],
+ [pluginB, [pluginA]],
+ ]);
mockPluginsService.discover.mockResolvedValue(pluginDependencies);
await server.setup();
diff --git a/src/core/server/server.ts b/src/core/server/server.ts
index 46974e204c7a42..6c38de03f0f2d3 100644
--- a/src/core/server/server.ts
+++ b/src/core/server/server.ts
@@ -39,7 +39,8 @@ import { config as uiSettingsConfig } from './ui_settings';
import { mapToObject } from '../utils/';
import { ContextService } from './context';
import { SavedObjectsServiceSetup } from './saved_objects/saved_objects_service';
-import { RequestHandlerContext, InternalCoreSetup } from '.';
+import { RequestHandlerContext } from '.';
+import { InternalCoreSetup } from './internal_types';
const coreId = Symbol('core');
diff --git a/src/core/server/ui_settings/ui_settings_client.ts b/src/core/server/ui_settings/ui_settings_client.ts
index 423ff2a1dfd909..1a0f29f6ae6d93 100644
--- a/src/core/server/ui_settings/ui_settings_client.ts
+++ b/src/core/server/ui_settings/ui_settings_client.ts
@@ -83,14 +83,11 @@ export class UiSettingsClient implements IUiSettingsClient {
async getAll() {
const raw = await this.getRaw();
- return Object.keys(raw).reduce(
- (all, key) => {
- const item = raw[key];
- all[key] = ('userValue' in item ? item.userValue : item.value) as T;
- return all;
- },
- {} as Record
- );
+ return Object.keys(raw).reduce((all, key) => {
+ const item = raw[key];
+ all[key] = ('userValue' in item ? item.userValue : item.value) as T;
+ return all;
+ }, {} as Record);
}
async getUserProvided(): Promise> {
diff --git a/src/core/utils/context.ts b/src/core/utils/context.ts
index 022c3e43300322..775c8906754100 100644
--- a/src/core/utils/context.ts
+++ b/src/core/utils/context.ts
@@ -254,23 +254,20 @@ export class ContextContainer>
return [...this.contextProviders]
.sort(sortByCoreFirst(this.coreId))
.filter(([contextName]) => contextsToBuild.has(contextName))
- .reduce(
- async (contextPromise, [contextName, { provider, source: providerSource }]) => {
- const resolvedContext = await contextPromise;
+ .reduce(async (contextPromise, [contextName, { provider, source: providerSource }]) => {
+ const resolvedContext = await contextPromise;
- // For the next provider, only expose the context available based on the dependencies of the plugin that
- // registered that provider.
- const exposedContext = pick(resolvedContext, [
- ...this.getContextNamesForSource(providerSource),
- ]) as Partial>;
+ // For the next provider, only expose the context available based on the dependencies of the plugin that
+ // registered that provider.
+ const exposedContext = pick(resolvedContext, [
+ ...this.getContextNamesForSource(providerSource),
+ ]) as Partial>;
- return {
- ...resolvedContext,
- [contextName]: await provider(exposedContext, ...contextArgs),
- };
- },
- Promise.resolve({}) as Promise>
- );
+ return {
+ ...resolvedContext,
+ [contextName]: await provider(exposedContext, ...contextArgs),
+ };
+ }, Promise.resolve({}) as Promise>);
}
private getContextNamesForSource(
diff --git a/src/core/utils/map_utils.test.ts b/src/core/utils/map_utils.test.ts
index 0d9b2a6129de0a..315ae3328c47f6 100644
--- a/src/core/utils/map_utils.test.ts
+++ b/src/core/utils/map_utils.test.ts
@@ -42,7 +42,11 @@ describe('groupIntoMap', () => {
const groupBy = (item: { id: number }) => item.id;
expect(groupIntoMap([{ id: 1 }, { id: 2 }, { id: 3 }], groupBy)).toEqual(
- new Map([[1, [{ id: 1 }]], [2, [{ id: 2 }]], [3, [{ id: 3 }]]])
+ new Map([
+ [1, [{ id: 1 }]],
+ [2, [{ id: 2 }]],
+ [3, [{ id: 3 }]],
+ ])
);
});
@@ -93,7 +97,12 @@ describe('mapValuesOfMap', () => {
map.set(even, 2);
map.set(odd, 1);
- expect(mapValuesOfMap(map, mapper)).toEqual(new Map([[even, 6], [odd, 3]]));
+ expect(mapValuesOfMap(map, mapper)).toEqual(
+ new Map([
+ [even, 6],
+ [odd, 3],
+ ])
+ );
expect(map.get(odd)).toEqual(1);
expect(map.get(even)).toEqual(2);
});
diff --git a/src/core/utils/merge.ts b/src/core/utils/merge.ts
index aead3f35ba841b..8e5d9f4860d955 100644
--- a/src/core/utils/merge.ts
+++ b/src/core/utils/merge.ts
@@ -66,20 +66,17 @@ const mergeObjects = , U extends Record
- [...new Set([...Object.keys(baseObj), ...Object.keys(overrideObj)])].reduce(
- (merged, key) => {
- const baseVal = baseObj[key];
- const overrideVal = overrideObj[key];
+ [...new Set([...Object.keys(baseObj), ...Object.keys(overrideObj)])].reduce((merged, key) => {
+ const baseVal = baseObj[key];
+ const overrideVal = overrideObj[key];
- if (isMergable(baseVal) && isMergable(overrideVal)) {
- merged[key] = mergeObjects(baseVal, overrideVal);
- } else if (overrideVal !== undefined) {
- merged[key] = overrideVal;
- } else if (baseVal !== undefined) {
- merged[key] = baseVal;
- }
+ if (isMergable(baseVal) && isMergable(overrideVal)) {
+ merged[key] = mergeObjects(baseVal, overrideVal);
+ } else if (overrideVal !== undefined) {
+ merged[key] = overrideVal;
+ } else if (baseVal !== undefined) {
+ merged[key] = baseVal;
+ }
- return merged;
- },
- {} as any
- );
+ return merged;
+ }, {} as any);
diff --git a/src/core/utils/pick.ts b/src/core/utils/pick.ts
index 77854f9af680b4..08288343d90778 100644
--- a/src/core/utils/pick.ts
+++ b/src/core/utils/pick.ts
@@ -18,14 +18,11 @@
*/
export function pick(obj: T, keys: K[]): Pick {
- return keys.reduce(
- (acc, key) => {
- if (obj.hasOwnProperty(key)) {
- acc[key] = obj[key];
- }
+ return keys.reduce((acc, key) => {
+ if (obj.hasOwnProperty(key)) {
+ acc[key] = obj[key];
+ }
- return acc;
- },
- {} as Pick
- );
+ return acc;
+ }, {} as Pick);
}
diff --git a/src/dev/build/tasks/nodejs_modules/clean_client_modules_on_dll_task.js b/src/dev/build/tasks/nodejs_modules/clean_client_modules_on_dll_task.js
index 3ae7d33d24d68a..5c0462ce86fa94 100644
--- a/src/dev/build/tasks/nodejs_modules/clean_client_modules_on_dll_task.js
+++ b/src/dev/build/tasks/nodejs_modules/clean_client_modules_on_dll_task.js
@@ -49,9 +49,18 @@ export const CleanClientModulesOnDLLTask = {
`${baseDir}/src/plugins/*/server/index.js`,
`!${baseDir}/src/plugins/**/public`
]);
+ const discoveredNewPlatformXpackPlugins = await globby([
+ `${baseDir}/x-pack/plugins/*/server/index.js`,
+ `!${baseDir}/x-pack/plugins/**/public`
+ ]);
// Compose all the needed entries
- const serverEntries = [ ...mainCodeEntries, ...discoveredLegacyCorePluginEntries, ...discoveredPluginEntries];
+ const serverEntries = [
+ ...mainCodeEntries,
+ ...discoveredLegacyCorePluginEntries,
+ ...discoveredPluginEntries,
+ ...discoveredNewPlatformXpackPlugins
+ ];
// Get the dependencies found searching through the server
// side code entries that were provided
diff --git a/src/dev/build/tasks/os_packages/docker_generator/resources/bin/kibana-docker b/src/dev/build/tasks/os_packages/docker_generator/resources/bin/kibana-docker
index 0926ef365c894c..6609b905b81eca 100755
--- a/src/dev/build/tasks/os_packages/docker_generator/resources/bin/kibana-docker
+++ b/src/dev/build/tasks/os_packages/docker_generator/resources/bin/kibana-docker
@@ -181,6 +181,7 @@ kibana_vars=(
xpack.security.secureCookies
xpack.security.sessionTimeout
telemetry.enabled
+ telemetry.sendUsageFrom
)
longopts=''
diff --git a/src/dev/jest/config.js b/src/dev/jest/config.js
index 0c785a84bb4692..f5c20da89dcfaf 100644
--- a/src/dev/jest/config.js
+++ b/src/dev/jest/config.js
@@ -103,6 +103,7 @@ export default {
'packages/kbn-pm/dist/index.js'
],
snapshotSerializers: [
+ '/src/plugins/kibana_react/public/util/test_helpers/react_mount_serializer.ts',
'/node_modules/enzyme-to-json/serializer',
],
reporters: [
diff --git a/src/dev/license_checker/valid.ts b/src/dev/license_checker/valid.ts
index 8fe09db0a58746..9142955185a1a0 100644
--- a/src/dev/license_checker/valid.ts
+++ b/src/dev/license_checker/valid.ts
@@ -36,24 +36,21 @@ interface Options {
* violations or returns undefined.
*/
export function assertLicensesValid({ packages, validLicenses }: Options) {
- const invalidMsgs = packages.reduce(
- (acc, pkg) => {
- const invalidLicenses = pkg.licenses.filter(license => !validLicenses.includes(license));
+ const invalidMsgs = packages.reduce((acc, pkg) => {
+ const invalidLicenses = pkg.licenses.filter(license => !validLicenses.includes(license));
- if (pkg.licenses.length && !invalidLicenses.length) {
- return acc;
- }
+ if (pkg.licenses.length && !invalidLicenses.length) {
+ return acc;
+ }
- return acc.concat(dedent`
+ return acc.concat(dedent`
${pkg.name}
version: ${pkg.version}
all licenses: ${pkg.licenses}
invalid licenses: ${invalidLicenses.join(', ')}
path: ${pkg.relative}
`);
- },
- [] as string[]
- );
+ }, [] as string[]);
if (invalidMsgs.length) {
throw createFailError(
diff --git a/src/legacy/core_plugins/console/index.ts b/src/legacy/core_plugins/console/index.ts
index 30a85f4e7d342b..caef3ff6f99f38 100644
--- a/src/legacy/core_plugins/console/index.ts
+++ b/src/legacy/core_plugins/console/index.ts
@@ -56,7 +56,6 @@ export default function(kibana: any) {
const npSrc = resolve(__dirname, 'np_ready/public');
let defaultVars: any;
- const apps: any[] = [];
return new kibana.Plugin({
id: 'console',
require: ['elasticsearch'],
@@ -181,8 +180,6 @@ export default function(kibana: any) {
},
uiExports: {
- apps,
- hacks: ['plugins/console/quarantined/hacks/register'],
devTools: [`${npSrc}/legacy`],
styleSheetPaths: resolve(__dirname, 'public/quarantined/index.scss'),
diff --git a/src/legacy/core_plugins/console/np_ready/public/application/containers/main/main.tsx b/src/legacy/core_plugins/console/np_ready/public/application/containers/main/main.tsx
index 4e5afbdb5821e9..518630c5a07c1c 100644
--- a/src/legacy/core_plugins/console/np_ready/public/application/containers/main/main.tsx
+++ b/src/legacy/core_plugins/console/np_ready/public/application/containers/main/main.tsx
@@ -70,7 +70,7 @@ export function Main() {
};
return (
- <>
+
setShowSettings(false)} /> : null}
{showHelp ? setShowHelp(false)} /> : null}
- >
+
);
}
diff --git a/src/legacy/core_plugins/console/np_ready/public/legacy.ts b/src/legacy/core_plugins/console/np_ready/public/legacy.ts
index 1a2d312823f6f2..8c60ff23648be3 100644
--- a/src/legacy/core_plugins/console/np_ready/public/legacy.ts
+++ b/src/legacy/core_plugins/console/np_ready/public/legacy.ts
@@ -24,80 +24,31 @@ import 'brace/mode/json';
import 'brace/mode/text';
/* eslint-disable @kbn/eslint/no-restricted-paths */
-import { toastNotifications as notifications } from 'ui/notify';
import { npSetup, npStart } from 'ui/new_platform';
-import uiRoutes from 'ui/routes';
-import { DOC_LINK_VERSION } from 'ui/documentation_links';
import { I18nContext } from 'ui/i18n';
import { ResizeChecker } from 'ui/resize_checker';
-import 'ui/capabilities/route_setup';
/* eslint-enable @kbn/eslint/no-restricted-paths */
-import template from '../../public/quarantined/index.html';
-import { App, AppUnmount, NotificationsSetup } from '../../../../../core/public';
-
export interface XPluginSet {
+ devTools: DevToolsSetup;
+ feature_catalogue: FeatureCatalogueSetup;
__LEGACY: {
I18nContext: any;
ResizeChecker: any;
- docLinkVersion: string;
};
}
import { plugin } from '.';
+import { DevToolsSetup } from '../../../../../plugins/dev_tools/public';
+import { FeatureCatalogueSetup } from '../../../../../plugins/feature_catalogue/public';
const pluginInstance = plugin({} as any);
-const anyObject = {} as any;
-
-uiRoutes.when('/dev_tools/console', {
- requireUICapability: 'dev_tools.show',
- controller: function RootController($scope) {
- // Stub out this config for now...
- $scope.topNavMenu = [];
-
- $scope.initReactApp = () => {
- const targetElement = document.querySelector('#consoleRoot');
- if (!targetElement) {
- const message = `Could not mount Console App!`;
- npSetup.core.fatalErrors.add(message);
- throw new Error(message);
- }
-
- let unmount: AppUnmount | Promise;
-
- const mockedSetupCore = {
- ...npSetup.core,
- notifications: (notifications as unknown) as NotificationsSetup,
- application: {
- register(app: App): void {
- try {
- unmount = app.mount(anyObject, { element: targetElement, appBasePath: '' });
- } catch (e) {
- npSetup.core.fatalErrors.add(e);
- }
- },
- registerMountContext() {},
- },
- };
-
- pluginInstance.setup(mockedSetupCore, {
- ...npSetup.plugins,
- __LEGACY: {
- I18nContext,
- ResizeChecker,
- docLinkVersion: DOC_LINK_VERSION,
- },
- });
- pluginInstance.start(npStart.core);
-
- $scope.$on('$destroy', async () => {
- if (unmount) {
- const fn = await unmount;
- fn();
- }
- });
- };
+pluginInstance.setup(npSetup.core, {
+ ...npSetup.plugins,
+ __LEGACY: {
+ I18nContext,
+ ResizeChecker,
},
- template,
});
+pluginInstance.start(npStart.core);
diff --git a/src/legacy/core_plugins/console/np_ready/public/plugin.ts b/src/legacy/core_plugins/console/np_ready/public/plugin.ts
index 188a738d597944..f02b0b5e729992 100644
--- a/src/legacy/core_plugins/console/np_ready/public/plugin.ts
+++ b/src/legacy/core_plugins/console/np_ready/public/plugin.ts
@@ -18,26 +18,55 @@
*/
import { render, unmountComponentAtNode } from 'react-dom';
+import { i18n } from '@kbn/i18n';
+import { FeatureCatalogueCategory } from 'ui/registry/feature_catalogue';
import { PluginInitializerContext, Plugin, CoreStart, CoreSetup } from '../../../../../core/public';
import { XPluginSet } from './legacy';
-import { boot } from './application';
export class ConsoleUIPlugin implements Plugin {
// @ts-ignore
constructor(private readonly ctx: PluginInitializerContext) {}
- async setup({ application, notifications }: CoreSetup, pluginSet: XPluginSet) {
+ async setup({ notifications }: CoreSetup, pluginSet: XPluginSet) {
const {
- __LEGACY: { docLinkVersion, I18nContext, ResizeChecker },
+ __LEGACY: { I18nContext, ResizeChecker },
+ devTools,
+ feature_catalogue,
} = pluginSet;
- application.register({
+ feature_catalogue.register({
+ id: 'console',
+ title: i18n.translate('console.devToolsTitle', {
+ defaultMessage: 'Console',
+ }),
+ description: i18n.translate('console.devToolsDescription', {
+ defaultMessage: 'Skip cURL and use this JSON interface to work with your data directly.',
+ }),
+ icon: 'consoleApp',
+ path: '/app/kibana#/dev_tools/console',
+ showOnHomePage: true,
+ category: FeatureCatalogueCategory.ADMIN,
+ });
+
+ devTools.register({
id: 'console',
order: 1,
- title: 'Console',
- mount(ctx, { element }) {
- render(boot({ docLinkVersion, I18nContext, ResizeChecker, notifications }), element);
+ title: i18n.translate('console.consoleDisplayName', {
+ defaultMessage: 'Console',
+ }),
+ enableRouting: false,
+ async mount(ctx, { element }) {
+ const { boot } = await import('./application');
+ render(
+ boot({
+ docLinkVersion: ctx.core.docLinks.DOC_LINK_VERSION,
+ I18nContext,
+ ResizeChecker,
+ notifications,
+ }),
+ element
+ );
return () => {
unmountComponentAtNode(element);
};
diff --git a/src/legacy/core_plugins/console/public/quarantined/index.html b/src/legacy/core_plugins/console/public/quarantined/index.html
deleted file mode 100644
index 66a693d4b2af78..00000000000000
--- a/src/legacy/core_plugins/console/public/quarantined/index.html
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/src/legacy/core_plugins/data/public/filter/filter_bar/_index.scss b/src/legacy/core_plugins/data/public/filter/filter_bar/_index.scss
index 5333aff8b87da3..9e2478cb0704ea 100644
--- a/src/legacy/core_plugins/data/public/filter/filter_bar/_index.scss
+++ b/src/legacy/core_plugins/data/public/filter/filter_bar/_index.scss
@@ -1,3 +1,4 @@
@import 'variables';
@import 'global_filter_group';
@import 'global_filter_item';
+@import 'filter_editor/index';
diff --git a/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/_filter_editor.scss b/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/_filter_editor.scss
new file mode 100644
index 00000000000000..a5fac10e4693fb
--- /dev/null
+++ b/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/_filter_editor.scss
@@ -0,0 +1,3 @@
+.globalFilterEditor__fieldInput {
+ max-width: $euiSize * 13;
+}
diff --git a/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/_index.scss b/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/_index.scss
new file mode 100644
index 00000000000000..21ba32ec6a6fea
--- /dev/null
+++ b/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/_index.scss
@@ -0,0 +1 @@
+@import 'filter_editor';
\ No newline at end of file
diff --git a/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/index.tsx b/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/index.tsx
index 5dd5c056477896..84da576e8205cd 100644
--- a/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/index.tsx
+++ b/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/index.tsx
@@ -30,6 +30,7 @@ import {
EuiPopoverTitle,
EuiSpacer,
EuiSwitch,
+ EuiSwitchEvent,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react';
@@ -245,6 +246,7 @@ class FilterEditorUI extends Component {
private renderFieldInput() {
const { selectedIndexPattern, selectedField } = this.state;
const fields = selectedIndexPattern ? getFilterableFields(selectedIndexPattern) : [];
+
return (
{
onChange={this.onFieldChange}
singleSelection={{ asPlainText: true }}
isClearable={false}
+ className="globalFilterEditor__fieldInput"
data-test-subj="filterFieldSuggestionList"
/>
@@ -431,7 +434,7 @@ class FilterEditorUI extends Component {
this.setState({ selectedOperator, params });
};
- private onCustomLabelSwitchChange = (event: React.ChangeEvent) => {
+ private onCustomLabelSwitchChange = (event: EuiSwitchEvent) => {
const useCustomLabel = event.target.checked;
const customLabel = event.target.checked ? '' : null;
this.setState({ useCustomLabel, customLabel });
diff --git a/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/lib/filter_editor_utils.test.ts b/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/lib/filter_editor_utils.test.ts
index dbff5096f2287d..7ee3e375c0967e 100644
--- a/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/lib/filter_editor_utils.test.ts
+++ b/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/lib/filter_editor_utils.test.ts
@@ -17,7 +17,8 @@
* under the License.
*/
-import { mockFields, mockIndexPattern } from '../../../../index_patterns';
+/* eslint-disable @kbn/eslint/no-restricted-paths */
+import { stubIndexPattern, stubFields } from '../../../../../../../../plugins/data/public/stubs';
import { IndexPattern, Field } from '../../../../index';
import {
buildFilter,
@@ -45,8 +46,8 @@ import { esFilters } from '../../../../../../../../plugins/data/public';
jest.mock('ui/new_platform');
-const mockedFields = mockFields as Field[];
-const mockedIndexPattern = mockIndexPattern as IndexPattern;
+const mockedFields = stubFields as Field[];
+const mockedIndexPattern = stubIndexPattern as IndexPattern;
describe('Filter editor utils', () => {
describe('getQueryDslFromFilter', () => {
@@ -171,14 +172,14 @@ describe('Filter editor utils', () => {
describe('getOperatorOptions', () => {
it('returns range for number fields', () => {
- const [field] = mockFields.filter(({ type }) => type === 'number');
+ const [field] = stubFields.filter(({ type }) => type === 'number');
const operatorOptions = getOperatorOptions(field as Field);
const rangeOperator = operatorOptions.find(operator => operator.type === 'range');
expect(rangeOperator).not.toBeUndefined();
});
it('does not return range for string fields', () => {
- const [field] = mockFields.filter(({ type }) => type === 'string');
+ const [field] = stubFields.filter(({ type }) => type === 'string');
const operatorOptions = getOperatorOptions(field as Field);
const rangeOperator = operatorOptions.find(operator => operator.type === 'range');
expect(rangeOperator).toBeUndefined();
diff --git a/src/legacy/core_plugins/data/public/index.ts b/src/legacy/core_plugins/data/public/index.ts
index 60828b4a2a2025..2412541e8c5c8f 100644
--- a/src/legacy/core_plugins/data/public/index.ts
+++ b/src/legacy/core_plugins/data/public/index.ts
@@ -38,7 +38,7 @@ export {
IndexPatterns,
StaticIndexPattern,
} from './index_patterns';
-export { Query, QueryBarInput } from './query';
+export { QueryBarInput } from './query';
export { SearchBar, SearchBarProps, SavedQueryAttributes, SavedQuery } from './search';
/** @public static code */
@@ -58,6 +58,4 @@ export {
IndexPatternMissingIndices,
NoDefaultIndexPattern,
NoDefinedIndexPatterns,
- mockFields,
- mockIndexPattern,
} from './index_patterns';
diff --git a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_patterns.ts b/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_patterns.ts
index 4767b6d3a3ca7a..2c58af9deaf49d 100644
--- a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_patterns.ts
+++ b/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_patterns.ts
@@ -52,11 +52,13 @@ export class IndexPatterns {
}
private async refreshSavedObjectsCache() {
- this.savedObjectsCache = (await this.savedObjectsClient.find({
- type: 'index-pattern',
- fields: [],
- perPage: 10000,
- })).savedObjects;
+ this.savedObjectsCache = (
+ await this.savedObjectsClient.find({
+ type: 'index-pattern',
+ fields: [],
+ perPage: 10000,
+ })
+ ).savedObjects;
}
getIds = async (refresh: boolean = false) => {
diff --git a/src/legacy/core_plugins/data/public/index_patterns/index_patterns_service.ts b/src/legacy/core_plugins/data/public/index_patterns/index_patterns_service.ts
index bdeeb787c983d6..9ce1b5f2e4a208 100644
--- a/src/legacy/core_plugins/data/public/index_patterns/index_patterns_service.ts
+++ b/src/legacy/core_plugins/data/public/index_patterns/index_patterns_service.ts
@@ -24,11 +24,11 @@ import {
NotificationsStart,
} from 'src/core/public';
import { Field, FieldList, FieldListInterface, FieldType } from './fields';
-import { createFlattenHitWrapper } from './index_patterns';
import { createIndexPatternSelect } from './components';
import { setNotifications } from './services';
import {
+ createFlattenHitWrapper,
formatHitProvider,
IndexPattern,
IndexPatterns,
@@ -92,8 +92,6 @@ export {
INDEX_PATTERN_ILLEGAL_CHARACTERS_VISIBLE,
isFilterable,
validateIndexPattern,
- mockFields,
- mockIndexPattern,
} from './utils';
/** @public */
diff --git a/src/legacy/core_plugins/data/public/index_patterns/utils.ts b/src/legacy/core_plugins/data/public/index_patterns/utils.ts
index 62f5ddbe9e2b05..1c877f4f142513 100644
--- a/src/legacy/core_plugins/data/public/index_patterns/utils.ts
+++ b/src/legacy/core_plugins/data/public/index_patterns/utils.ts
@@ -19,8 +19,7 @@
import { find, get } from 'lodash';
-import { Field, FieldType } from './fields';
-import { StaticIndexPattern } from './index_patterns';
+import { Field } from './fields';
import { getFilterableKbnTypeNames } from '../../../../../plugins/data/public';
import { SavedObjectsClientContract, SimpleSavedObject } from '../../../../../core/public';
@@ -139,69 +138,3 @@ export function getRoutes() {
sourceFilters: '/management/kibana/index_patterns/{{id}}?_a=(tab:sourceFilters)',
};
}
-
-export const mockFields: FieldType[] = [
- {
- name: 'machine.os',
- esTypes: ['text'],
- type: 'string',
- aggregatable: false,
- searchable: false,
- filterable: true,
- },
- {
- name: 'machine.os.raw',
- type: 'string',
- esTypes: ['keyword'],
- aggregatable: true,
- searchable: true,
- filterable: true,
- },
- {
- name: 'not.filterable',
- type: 'string',
- esTypes: ['text'],
- aggregatable: true,
- searchable: false,
- filterable: false,
- },
- {
- name: 'bytes',
- type: 'number',
- esTypes: ['long'],
- aggregatable: true,
- searchable: true,
- filterable: true,
- },
- {
- name: '@timestamp',
- type: 'date',
- esTypes: ['date'],
- aggregatable: true,
- searchable: true,
- filterable: true,
- },
- {
- name: 'clientip',
- type: 'ip',
- esTypes: ['ip'],
- aggregatable: true,
- searchable: true,
- filterable: true,
- },
- {
- name: 'bool.field',
- type: 'boolean',
- esTypes: ['boolean'],
- aggregatable: true,
- searchable: true,
- filterable: true,
- },
-];
-
-export const mockIndexPattern: StaticIndexPattern = {
- id: 'logstash-*',
- fields: mockFields,
- title: 'logstash-*',
- timeFieldName: '@timestamp',
-};
diff --git a/src/legacy/core_plugins/data/public/mocks.ts b/src/legacy/core_plugins/data/public/mocks.ts
index d3b5944127965d..39d1296ddf8bc4 100644
--- a/src/legacy/core_plugins/data/public/mocks.ts
+++ b/src/legacy/core_plugins/data/public/mocks.ts
@@ -18,12 +18,10 @@
*/
import { indexPatternsServiceMock } from './index_patterns/index_patterns_service.mock';
-import { queryServiceMock } from './query/query_service.mock';
function createDataSetupMock() {
return {
indexPatterns: indexPatternsServiceMock.createSetupContract(),
- query: queryServiceMock.createSetupContract(),
};
}
diff --git a/src/legacy/core_plugins/data/public/plugin.ts b/src/legacy/core_plugins/data/public/plugin.ts
index 76beb4ee560535..2059f61fde59ed 100644
--- a/src/legacy/core_plugins/data/public/plugin.ts
+++ b/src/legacy/core_plugins/data/public/plugin.ts
@@ -19,7 +19,6 @@
import { CoreSetup, CoreStart, Plugin } from 'kibana/public';
import { SearchService, SearchStart, createSearchBar, StatetfulSearchBarProps } from './search';
-import { QueryService, QuerySetup } from './query';
import { IndexPatternsService, IndexPatternsSetup, IndexPatternsStart } from './index_patterns';
import { Storage, IStorageWrapper } from '../../../../../src/plugins/kibana_utils/public';
import { DataPublicPluginStart } from '../../../../plugins/data/public';
@@ -42,7 +41,6 @@ export interface DataPluginStartDependencies {
* @public
*/
export interface DataSetup {
- query: QuerySetup;
indexPatterns: IndexPatternsSetup;
}
@@ -52,7 +50,6 @@ export interface DataSetup {
* @public
*/
export interface DataStart {
- query: QuerySetup;
indexPatterns: IndexPatternsStart;
search: SearchStart;
ui: {
@@ -74,7 +71,6 @@ export interface DataStart {
export class DataPlugin implements Plugin {
private readonly indexPatterns: IndexPatternsService = new IndexPatternsService();
- private readonly query: QueryService = new QueryService();
private readonly search: SearchService = new SearchService();
private setupApi!: DataSetup;
@@ -85,7 +81,6 @@ export class DataPlugin implements Plugin
PersistedLog: mockPersistedLogFactory,
}));
-jest.mock('../lib/fetch_index_patterns', () => ({
+jest.mock('./fetch_index_patterns', () => ({
fetchIndexPatterns: mockFetchIndexPatterns,
}));
diff --git a/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar_input.tsx b/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar_input.tsx
index 5576427b1592a6..31a17315db7dd3 100644
--- a/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar_input.tsx
+++ b/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar_input.tsx
@@ -38,18 +38,22 @@ import {
AutocompleteSuggestion,
AutocompleteSuggestionType,
PersistedLog,
+ toUser,
+ fromUser,
+ matchPairs,
+ getQueryLog,
+ Query,
} from '../../../../../../../plugins/data/public';
import {
withKibana,
KibanaReactContextValue,
+ toMountPoint,
} from '../../../../../../../plugins/kibana_react/public';
import { IndexPattern, StaticIndexPattern } from '../../../index_patterns';
-import { Query, getQueryLog } from '../index';
-import { fromUser, matchPairs, toUser } from '../lib';
import { QueryLanguageSwitcher } from './language_switcher';
import { SuggestionsComponent } from './typeahead/suggestions_component';
-import { fetchIndexPatterns } from '../lib/fetch_index_patterns';
import { IDataPluginServices } from '../../../types';
+import { fetchIndexPatterns } from './fetch_index_patterns';
interface Props {
kibana: KibanaReactContextValue;
@@ -361,7 +365,7 @@ export class QueryBarInputUI extends Component {
id: 'data.query.queryBar.KQLNestedQuerySyntaxInfoTitle',
defaultMessage: 'KQL nested query syntax',
}),
- text: (
+ text: toMountPoint(
(queryLanguage ? getQueryLog(uiSettings!, storage, appName, queryLanguage) : undefined),
- [queryLanguage]
+ () =>
+ queryLanguage && uiSettings && storage && appName
+ ? getQueryLog(uiSettings!, storage, appName, queryLanguage)
+ : undefined,
+ [appName, queryLanguage, uiSettings, storage]
);
function onClickSubmitButton(event: React.MouseEvent) {
@@ -298,7 +305,7 @@ function QueryBarTopRowUI(props: Props) {
id: 'data.query.queryBar.luceneSyntaxWarningTitle',
defaultMessage: 'Lucene syntax warning',
}),
- text: (
+ text: toMountPoint(
void;
onQuerySubmit?: (payload: { dateRange: TimeRange; query?: Query }) => void;
// User has saved the current state as a saved query
onSaved?: (savedQuery: SavedQuery) => void;
@@ -207,6 +207,18 @@ class SearchBarUI extends Component {
);
}
+ /*
+ * This Function is here to show the toggle in saved query form
+ * in case you the date range (from/to)
+ */
+ private shouldRenderTimeFilterInSavedQueryForm() {
+ const { dateRangeFrom, dateRangeTo, showDatePicker } = this.props;
+ return (
+ showDatePicker ||
+ (!showDatePicker && dateRangeFrom !== undefined && dateRangeTo !== undefined)
+ );
+ }
+
public setFilterBarHeight = () => {
requestAnimationFrame(() => {
const height =
@@ -300,6 +312,9 @@ class SearchBarUI extends Component {
dateRangeFrom: queryAndDateRange.dateRange.from,
dateRangeTo: queryAndDateRange.dateRange.to,
});
+ if (this.props.onQueryChange) {
+ this.props.onQueryChange(queryAndDateRange);
+ }
};
public onQueryBarSubmit = (queryAndDateRange: { dateRange?: TimeRange; query?: Query }) => {
@@ -441,7 +456,7 @@ class SearchBarUI extends Component {
onSave={this.onSave}
onClose={() => this.setState({ showSaveQueryModal: false })}
showFilterOption={this.props.showFilterBar}
- showTimeFilterOption={this.props.showDatePicker}
+ showTimeFilterOption={this.shouldRenderTimeFilterInSavedQueryForm()}
/>
) : null}
{this.state.showSaveNewQueryModal ? (
@@ -450,7 +465,7 @@ class SearchBarUI extends Component {
onSave={savedQueryMeta => this.onSave(savedQueryMeta, true)}
onClose={() => this.setState({ showSaveNewQueryModal: false })}
showFilterOption={this.props.showFilterBar}
- showTimeFilterOption={this.props.showDatePicker}
+ showTimeFilterOption={this.shouldRenderTimeFilterInSavedQueryForm()}
/>
) : null}
diff --git a/src/legacy/core_plugins/data/public/search/search_bar/index.tsx b/src/legacy/core_plugins/data/public/search/search_bar/index.tsx
index ebde9d60b0b51e..f369bf997c1a91 100644
--- a/src/legacy/core_plugins/data/public/search/search_bar/index.tsx
+++ b/src/legacy/core_plugins/data/public/search/search_bar/index.tsx
@@ -17,9 +17,7 @@
* under the License.
*/
-import { RefreshInterval, TimeRange } from 'src/plugins/data/public';
-import { Query } from '../../query/query_bar';
-import { esFilters } from '../../../../../../plugins/data/public';
+import { RefreshInterval, TimeRange, Query, esFilters } from 'src/plugins/data/public';
export * from './components';
diff --git a/src/legacy/core_plugins/input_control_vis/public/components/editor/list_control_editor.test.js b/src/legacy/core_plugins/input_control_vis/public/components/editor/list_control_editor.test.js
index 96c0802d3772a2..ea029af9e48908 100644
--- a/src/legacy/core_plugins/input_control_vis/public/components/editor/list_control_editor.test.js
+++ b/src/legacy/core_plugins/input_control_vis/public/components/editor/list_control_editor.test.js
@@ -236,7 +236,7 @@ test('handleCheckboxOptionChange - multiselect', async () => {
component.update();
const checkbox = findTestSubject(component, 'listControlMultiselectInput');
- checkbox.simulate('change', { target: { checked: true } });
+ checkbox.simulate('click');
sinon.assert.notCalled(handleFieldNameChange);
sinon.assert.notCalled(handleIndexPatternChange);
sinon.assert.notCalled(handleNumberOptionChange);
@@ -247,7 +247,9 @@ test('handleCheckboxOptionChange - multiselect', async () => {
expectedControlIndex,
expectedOptionName,
sinon.match((evt) => {
- if (evt.target.checked === true) {
+ // Synthetic `evt.target.checked` does not get altered by EuiSwitch,
+ // but its aria attribute is correctly updated
+ if (evt.target.getAttribute('aria-checked') === 'true') {
return true;
}
return false;
diff --git a/src/legacy/core_plugins/input_control_vis/public/components/editor/options_tab.test.js b/src/legacy/core_plugins/input_control_vis/public/components/editor/options_tab.test.js
index 39f5f6a50a5a63..8784f0e79ca8d2 100644
--- a/src/legacy/core_plugins/input_control_vis/public/components/editor/options_tab.test.js
+++ b/src/legacy/core_plugins/input_control_vis/public/components/editor/options_tab.test.js
@@ -47,8 +47,8 @@ describe('OptionsTab', () => {
it('should update updateFiltersOnChange', () => {
const component = mountWithIntl();
- const checkbox = component.find('[data-test-subj="inputControlEditorUpdateFiltersOnChangeCheckbox"] input[type="checkbox"]');
- checkbox.simulate('change', { target: { checked: true } });
+ const checkbox = component.find('[data-test-subj="inputControlEditorUpdateFiltersOnChangeCheckbox"] button');
+ checkbox.simulate('click');
expect(props.setValue).toHaveBeenCalledTimes(1);
expect(props.setValue).toHaveBeenCalledWith('updateFiltersOnChange', true);
@@ -56,8 +56,8 @@ describe('OptionsTab', () => {
it('should update useTimeFilter', () => {
const component = mountWithIntl();
- const checkbox = component.find('[data-test-subj="inputControlEditorUseTimeFilterCheckbox"] input[type="checkbox"]');
- checkbox.simulate('change', { target: { checked: true } });
+ const checkbox = component.find('[data-test-subj="inputControlEditorUseTimeFilterCheckbox"] button');
+ checkbox.simulate('click');
expect(props.setValue).toHaveBeenCalledTimes(1);
expect(props.setValue).toHaveBeenCalledWith('useTimeFilter', true);
@@ -65,8 +65,8 @@ describe('OptionsTab', () => {
it('should update pinFilters', () => {
const component = mountWithIntl();
- const checkbox = component.find('[data-test-subj="inputControlEditorPinFiltersCheckbox"] input[type="checkbox"]');
- checkbox.simulate('change', { target: { checked: true } });
+ const checkbox = component.find('[data-test-subj="inputControlEditorPinFiltersCheckbox"] button');
+ checkbox.simulate('click');
expect(props.setValue).toHaveBeenCalledTimes(1);
expect(props.setValue).toHaveBeenCalledWith('pinFilters', true);
diff --git a/src/legacy/core_plugins/interpreter/public/renderers/visualization.tsx b/src/legacy/core_plugins/interpreter/public/renderers/visualization.tsx
index 9de6cdeaf5ec3e..f15cdf23fe15b8 100644
--- a/src/legacy/core_plugins/interpreter/public/renderers/visualization.tsx
+++ b/src/legacy/core_plugins/interpreter/public/renderers/visualization.tsx
@@ -21,7 +21,7 @@ import chrome from 'ui/chrome';
import React from 'react';
import { render, unmountComponentAtNode } from 'react-dom';
// @ts-ignore
-import { VisProvider } from '../../../../ui/public/visualize/loader/vis';
+import { Vis } from '../../../../ui/public/visualize/loader/vis';
import { Visualization } from '../../../../ui/public/visualize/components';
export const visualization = () => ({
@@ -33,8 +33,6 @@ export const visualization = () => ({
const visType = config.visType || visConfig.type;
const $injector = await chrome.dangerouslyGetActiveInjector();
const $rootScope = $injector.get('$rootScope') as any;
- const Private = $injector.get('Private') as any;
- const Vis = Private(VisProvider);
if (handlers.vis) {
// special case in visualize, we need to render first (without executing the expression), for maps to work
diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/index.tsx b/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/index.tsx
index c7ada18f9e1f25..2ca4ed1e2343d0 100644
--- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/index.tsx
+++ b/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/index.tsx
@@ -83,9 +83,11 @@ function MetricsAxisOptions(props: ValidationVisOptionsProps)
// stores previous aggs' custom labels
const [lastCustomLabels, setLastCustomLabels] = useState({} as { [key: string]: string });
// stores previous aggs' field and type
- const [lastSeriesAgg, setLastSeriesAgg] = useState({} as {
- [key: string]: { type: string; field: string };
- });
+ const [lastSeriesAgg, setLastSeriesAgg] = useState(
+ {} as {
+ [key: string]: { type: string; field: string };
+ }
+ );
const updateAxisTitle = () => {
const axes = cloneDeep(stateParams.valueAxes);
diff --git a/src/legacy/core_plugins/kibana/index.js b/src/legacy/core_plugins/kibana/index.js
index 24cd4369123956..c7cda8aec0165d 100644
--- a/src/legacy/core_plugins/kibana/index.js
+++ b/src/legacy/core_plugins/kibana/index.js
@@ -62,7 +62,7 @@ export default function (kibana) {
uiExports: {
hacks: [
- 'plugins/kibana/dev_tools/hacks/hide_empty_tools',
+ 'plugins/kibana/dev_tools',
],
fieldFormats: ['plugins/kibana/field_formats/register'],
savedObjectTypes: [
diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.tsx b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.tsx
index 5fa3a938ed9dfc..656b54040ad99a 100644
--- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.tsx
+++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.tsx
@@ -26,19 +26,16 @@ import { IInjector } from 'ui/chrome';
// @ts-ignore
import * as filterActions from 'plugins/kibana/discover/doc_table/actions/filter';
-// @ts-ignore
-import { getFilterGenerator } from 'ui/filter_manager';
-
import {
AppStateClass as TAppStateClass,
AppState as TAppState,
} from 'ui/state_management/app_state';
import { KbnUrl } from 'ui/url/kbn_url';
-import { TimeRange } from 'src/plugins/data/public';
+import { TimeRange, Query } from 'src/plugins/data/public';
import { IndexPattern } from 'ui/index_patterns';
import { IPrivate } from 'ui/private';
-import { StaticIndexPattern, Query, SavedQuery } from 'plugins/data';
+import { StaticIndexPattern, SavedQuery } from 'plugins/data';
import moment from 'moment';
import { Subscription } from 'rxjs';
diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx
index 548a66297a3f9c..d82b89339b0d00 100644
--- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx
+++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx
@@ -50,12 +50,13 @@ import {
import { KbnUrl } from 'ui/url/kbn_url';
import { IndexPattern } from 'ui/index_patterns';
import { IPrivate } from 'ui/private';
-import { Query, SavedQuery } from 'src/legacy/core_plugins/data/public';
+import { SavedQuery } from 'src/legacy/core_plugins/data/public';
import { SaveOptions } from 'ui/saved_objects/saved_object';
import { capabilities } from 'ui/capabilities';
import { Subscription } from 'rxjs';
import { npStart } from 'ui/new_platform';
import { SavedObjectFinder } from 'ui/saved_objects/components/saved_object_finder';
+import { Query } from '../../../../../plugins/data/public';
import { start as data } from '../../../data/public/legacy';
import {
diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state_manager.ts b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state_manager.ts
index 8ffabe5add1c34..1a42ed837a9de3 100644
--- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state_manager.ts
+++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state_manager.ts
@@ -27,9 +27,9 @@ import { migrateLegacyQuery } from 'ui/utils/migrate_legacy_query';
import { Moment } from 'moment';
import { DashboardContainer } from 'src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public';
+import { Query } from 'src/plugins/data/public';
import { ViewMode } from '../../../../../../src/plugins/embeddable/public';
import { esFilters } from '../../../../../../src/plugins/data/public';
-import { Query } from '../../../data/public';
import { getAppStateDefaults, migrateAppState } from './lib';
import { convertPanelStateToSavedDashboardPanel } from './lib/embeddable_saved_object_converters';
diff --git a/src/legacy/core_plugins/kibana/public/dashboard/migrations/move_filters_to_query.ts b/src/legacy/core_plugins/kibana/public/dashboard/migrations/move_filters_to_query.ts
index 8522495b9dedb2..e82fc58670e392 100644
--- a/src/legacy/core_plugins/kibana/public/dashboard/migrations/move_filters_to_query.ts
+++ b/src/legacy/core_plugins/kibana/public/dashboard/migrations/move_filters_to_query.ts
@@ -17,8 +17,7 @@
* under the License.
*/
-import { Query } from 'src/legacy/core_plugins/data/public';
-import { esFilters } from '../../../../../../plugins/data/public';
+import { esFilters, Query } from '../../../../../../plugins/data/public';
export interface Pre600FilterQuery {
// pre 6.0.0 global query:queryString:options were stored per dashboard and would
diff --git a/src/legacy/core_plugins/kibana/public/dashboard/saved_dashboard/saved_dashboard.d.ts b/src/legacy/core_plugins/kibana/public/dashboard/saved_dashboard/saved_dashboard.d.ts
index 5b860b0a2cc7c1..5b24aa13f4f77a 100644
--- a/src/legacy/core_plugins/kibana/public/dashboard/saved_dashboard/saved_dashboard.d.ts
+++ b/src/legacy/core_plugins/kibana/public/dashboard/saved_dashboard/saved_dashboard.d.ts
@@ -19,9 +19,7 @@
import { SearchSource } from 'ui/courier';
import { SavedObject } from 'ui/saved_objects/saved_object';
-import { RefreshInterval } from 'src/plugins/data/public';
-import { Query } from 'src/legacy/core_plugins/data/public';
-import { esFilters } from '../../../../../../plugins/data/public';
+import { esFilters, Query, RefreshInterval } from '../../../../../../plugins/data/public';
export interface SavedObjectDashboard extends SavedObject {
id?: string;
diff --git a/src/legacy/core_plugins/kibana/public/dashboard/types.ts b/src/legacy/core_plugins/kibana/public/dashboard/types.ts
index 5aaca7b62094f8..3c2c87a502da46 100644
--- a/src/legacy/core_plugins/kibana/public/dashboard/types.ts
+++ b/src/legacy/core_plugins/kibana/public/dashboard/types.ts
@@ -18,7 +18,6 @@
*/
import { AppState } from 'ui/state_management/app_state';
-import { Query } from 'src/legacy/core_plugins/data/public';
import { AppState as TAppState } from 'ui/state_management/app_state';
import { ViewMode } from 'src/plugins/embeddable/public';
import {
@@ -29,7 +28,7 @@ import {
RawSavedDashboardPanel640To720,
RawSavedDashboardPanel730ToLatest,
} from './migrations/types';
-import { esFilters } from '../../../../../plugins/data/public';
+import { Query, esFilters } from '../../../../../plugins/data/public';
export type NavAction = (anchorElement?: any) => void;
diff --git a/src/legacy/core_plugins/kibana/public/dev_tools/_index.scss b/src/legacy/core_plugins/kibana/public/dev_tools/_index.scss
index 563b140fd2eade..2e88d2e1285e3c 100644
--- a/src/legacy/core_plugins/kibana/public/dev_tools/_index.scss
+++ b/src/legacy/core_plugins/kibana/public/dev_tools/_index.scss
@@ -16,3 +16,6 @@
}
}
+.devApp {
+ height: 100%;
+}
diff --git a/src/legacy/core_plugins/kibana/public/dev_tools/application.tsx b/src/legacy/core_plugins/kibana/public/dev_tools/application.tsx
new file mode 100644
index 00000000000000..3945d8d8dc8568
--- /dev/null
+++ b/src/legacy/core_plugins/kibana/public/dev_tools/application.tsx
@@ -0,0 +1,184 @@
+/*
+ * 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 { I18nProvider } from '@kbn/i18n/react';
+import { i18n } from '@kbn/i18n';
+import { EuiTab, EuiTabs, EuiToolTip } from '@elastic/eui';
+import { HashRouter as Router, Switch, Route, Redirect } from 'react-router-dom';
+import * as React from 'react';
+import ReactDOM from 'react-dom';
+import { useEffect, useRef } from 'react';
+
+import { AppMountContext } from 'kibana/public';
+import { DevTool } from '../../../../../plugins/dev_tools/public';
+
+interface DevToolsWrapperProps {
+ devTools: readonly DevTool[];
+ activeDevTool: DevTool;
+ appMountContext: AppMountContext;
+ updateRoute: (newRoute: string) => void;
+}
+
+interface MountedDevToolDescriptor {
+ devTool: DevTool;
+ mountpoint: HTMLElement;
+ unmountHandler: () => void;
+}
+
+function DevToolsWrapper({
+ devTools,
+ activeDevTool,
+ appMountContext,
+ updateRoute,
+}: DevToolsWrapperProps) {
+ const mountedTool = useRef(null);
+
+ useEffect(
+ () => () => {
+ if (mountedTool.current) {
+ mountedTool.current.unmountHandler();
+ }
+ },
+ []
+ );
+
+ return (
+
+
+ {devTools.map(currentDevTool => (
+
+ {
+ if (!currentDevTool.disabled) {
+ updateRoute(`/dev_tools/${currentDevTool.id}`);
+ }
+ }}
+ >
+ {currentDevTool.title}
+
+
+ ))}
+
+ {
+ if (
+ element &&
+ (mountedTool.current === null ||
+ mountedTool.current.devTool !== activeDevTool ||
+ mountedTool.current.mountpoint !== element)
+ ) {
+ if (mountedTool.current) {
+ mountedTool.current.unmountHandler();
+ }
+ const unmountHandler = await activeDevTool.mount(appMountContext, {
+ element,
+ appBasePath: '',
+ });
+ mountedTool.current = {
+ devTool: activeDevTool,
+ mountpoint: element,
+ unmountHandler,
+ };
+ }
+ }}
+ />
+
+ );
+}
+
+function redirectOnMissingCapabilities(appMountContext: AppMountContext) {
+ if (!appMountContext.core.application.capabilities.dev_tools.show) {
+ window.location.hash = '/home';
+ return true;
+ }
+ return false;
+}
+
+function setBadge(appMountContext: AppMountContext) {
+ if (appMountContext.core.application.capabilities.dev_tools.save) {
+ return;
+ }
+ appMountContext.core.chrome.setBadge({
+ text: i18n.translate('kbn.devTools.badge.readOnly.text', {
+ defaultMessage: 'Read only',
+ }),
+ tooltip: i18n.translate('kbn.devTools.badge.readOnly.tooltip', {
+ defaultMessage: 'Unable to save',
+ }),
+ iconType: 'glasses',
+ });
+}
+
+function setBreadcrumbs(appMountContext: AppMountContext) {
+ appMountContext.core.chrome.setBreadcrumbs([
+ {
+ text: i18n.translate('kbn.devTools.k7BreadcrumbsDevToolsLabel', {
+ defaultMessage: 'Dev Tools',
+ }),
+ href: '#/dev_tools',
+ },
+ ]);
+}
+
+export function renderApp(
+ element: HTMLElement,
+ appMountContext: AppMountContext,
+ basePath: string,
+ devTools: readonly DevTool[]
+) {
+ if (redirectOnMissingCapabilities(appMountContext)) {
+ return () => {};
+ }
+ setBadge(appMountContext);
+ setBreadcrumbs(appMountContext);
+ ReactDOM.render(
+
+
+
+ {devTools.map(devTool => (
+ (
+
+ )}
+ />
+ ))}
+
+
+
+
+
+ ,
+ element
+ );
+
+ return () => ReactDOM.unmountComponentAtNode(element);
+}
diff --git a/src/legacy/core_plugins/kibana/public/dev_tools/hacks/__tests__/hide_empty_tools.js b/src/legacy/core_plugins/kibana/public/dev_tools/hacks/__tests__/hide_empty_tools.js
deleted file mode 100644
index 25c7b945b9dfb4..00000000000000
--- a/src/legacy/core_plugins/kibana/public/dev_tools/hacks/__tests__/hide_empty_tools.js
+++ /dev/null
@@ -1,59 +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 { hideEmptyDevTools } from '../hide_empty_tools';
-import { npStart } from 'ui/new_platform';
-
-describe('hide dev tools', function () {
- let updateNavLink;
-
- function PrivateWithoutTools() {
- return [];
- }
-
- function PrivateWithTools() {
- return ['tool1', 'tool2'];
- }
-
- function isHidden() {
- return updateNavLink.calledWith('kibana:dev_tools', { hidden: true });
- }
-
- beforeEach(function () {
- const coreNavLinks = npStart.core.chrome.navLinks;
- updateNavLink = sinon.spy(coreNavLinks, 'update');
- });
-
- it('should hide the app if there are no dev tools', function () {
- hideEmptyDevTools(PrivateWithTools);
- expect(isHidden()).to.be(false);
- });
-
- it('should not hide the app if there are tools', function () {
- hideEmptyDevTools(PrivateWithoutTools);
- expect(isHidden()).to.be(true);
- });
-
- afterEach(function () {
- updateNavLink.restore();
- });
-});
diff --git a/src/legacy/core_plugins/kibana/public/dev_tools/index.js b/src/legacy/core_plugins/kibana/public/dev_tools/index.js
deleted file mode 100644
index e36e75f6837ab8..00000000000000
--- a/src/legacy/core_plugins/kibana/public/dev_tools/index.js
+++ /dev/null
@@ -1,77 +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 uiRoutes from 'ui/routes';
-import { i18n } from '@kbn/i18n';
-import { DevToolsRegistryProvider } from 'ui/registry/dev_tools';
-import { FeatureCatalogueRegistryProvider, FeatureCatalogueCategory } from 'ui/registry/feature_catalogue';
-import 'ui/directives/kbn_href';
-import './directives/dev_tools_app';
-
-uiRoutes
- .when('/dev_tools', {
- resolve: {
- redirect(Private, kbnUrl) {
- const items = Private(DevToolsRegistryProvider).inOrder;
- kbnUrl.redirect(items[0].url.substring(1));
- }
- }
- });
-
-uiRoutes.defaults(/^\/dev_tools(\/|$)/, {
- badge: uiCapabilities => {
- if (uiCapabilities.dev_tools.save) {
- return undefined;
- }
-
- return {
- text: i18n.translate('kbn.devTools.badge.readOnly.text', {
- defaultMessage: 'Read only',
- }),
- tooltip: i18n.translate('kbn.devTools.badge.readOnly.tooltip', {
- defaultMessage: 'Unable to save',
- }),
- iconType: 'glasses'
- };
- },
- k7Breadcrumbs: () => [
- {
- text: i18n.translate('kbn.devTools.k7BreadcrumbsDevToolsLabel', {
- defaultMessage: 'Dev Tools'
- }),
- href: '#/dev_tools'
- }
- ]
-});
-
-FeatureCatalogueRegistryProvider.register(() => {
- return {
- id: 'console',
- title: i18n.translate('kbn.devTools.consoleTitle', {
- defaultMessage: 'Console'
- }),
- description: i18n.translate('kbn.devTools.consoleDescription', {
- defaultMessage: 'Skip cURL and use this JSON interface to work with your data directly.'
- }),
- icon: 'consoleApp',
- path: '/app/kibana#/dev_tools/console',
- showOnHomePage: true,
- category: FeatureCatalogueCategory.ADMIN
- };
-});
diff --git a/src/legacy/core_plugins/kibana/public/dev_tools/index.ts b/src/legacy/core_plugins/kibana/public/dev_tools/index.ts
new file mode 100644
index 00000000000000..74708e36a98aac
--- /dev/null
+++ b/src/legacy/core_plugins/kibana/public/dev_tools/index.ts
@@ -0,0 +1,34 @@
+/*
+ * 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 { npSetup, npStart } from 'ui/new_platform';
+
+import { DevToolsPlugin } from './plugin';
+import { localApplicationService } from '../local_application_service';
+
+const instance = new DevToolsPlugin();
+
+instance.setup(npSetup.core, {
+ __LEGACY: {
+ localApplicationService,
+ },
+});
+instance.start(npStart.core, {
+ newPlatformDevTools: npStart.plugins.devTools,
+});
diff --git a/src/legacy/core_plugins/kibana/public/dev_tools/partials/dev_tools_app.html b/src/legacy/core_plugins/kibana/public/dev_tools/partials/dev_tools_app.html
deleted file mode 100644
index 6c076092c76d5d..00000000000000
--- a/src/legacy/core_plugins/kibana/public/dev_tools/partials/dev_tools_app.html
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
-
-
-
diff --git a/src/legacy/core_plugins/kibana/public/dev_tools/plugin.ts b/src/legacy/core_plugins/kibana/public/dev_tools/plugin.ts
new file mode 100644
index 00000000000000..ec9af1a6acd929
--- /dev/null
+++ b/src/legacy/core_plugins/kibana/public/dev_tools/plugin.ts
@@ -0,0 +1,71 @@
+/*
+ * 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.
+ */
+
+// This import makes sure dev tools are registered before the app is.
+import 'uiExports/devTools';
+
+import { CoreSetup, CoreStart, Plugin } from 'kibana/public';
+
+import { LocalApplicationService } from '../local_application_service';
+import { DevTool, DevToolsStart } from '../../../../../plugins/dev_tools/public';
+
+export interface DevToolsPluginSetupDependencies {
+ __LEGACY: {
+ localApplicationService: LocalApplicationService;
+ };
+}
+
+export interface DevToolsPluginStartDependencies {
+ newPlatformDevTools: DevToolsStart;
+}
+
+export class DevToolsPlugin implements Plugin {
+ private getSortedDevTools: (() => readonly DevTool[]) | null = null;
+
+ public setup(
+ core: CoreSetup,
+ { __LEGACY: { localApplicationService } }: DevToolsPluginSetupDependencies
+ ) {
+ localApplicationService.register({
+ id: 'dev_tools',
+ title: 'Dev Tools',
+ mount: async (appMountContext, params) => {
+ if (!this.getSortedDevTools) {
+ throw new Error('not started yet');
+ }
+ const { renderApp } = await import('./application');
+ return renderApp(
+ params.element,
+ appMountContext,
+ params.appBasePath,
+ this.getSortedDevTools()
+ );
+ },
+ });
+ }
+
+ public start(core: CoreStart, { newPlatformDevTools }: DevToolsPluginStartDependencies) {
+ this.getSortedDevTools = newPlatformDevTools.getSortedDevTools;
+ if (this.getSortedDevTools().length === 0) {
+ core.chrome.navLinks.update('kibana:dev_tools', {
+ hidden: true,
+ });
+ }
+ }
+}
diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context/api/__tests__/_stubs.js b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/__tests__/_stubs.js
index f472ff9250eb5b..b3d37083b37f73 100644
--- a/src/legacy/core_plugins/kibana/public/discover/angular/context/api/__tests__/_stubs.js
+++ b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/__tests__/_stubs.js
@@ -26,7 +26,8 @@ export function createIndexPatternsStub() {
get: sinon.spy(indexPatternId =>
Promise.resolve({
id: indexPatternId,
- isTimeNanosBased: () => false
+ isTimeNanosBased: () => false,
+ popularizeField: () => {},
})
),
};
diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/__tests__/action_add_filter.js b/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/__tests__/action_add_filter.js
index b136b03bd500bf..5a445a65939eda 100644
--- a/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/__tests__/action_add_filter.js
+++ b/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/__tests__/action_add_filter.js
@@ -19,32 +19,33 @@
import expect from '@kbn/expect';
import ngMock from 'ng_mock';
-import sinon from 'sinon';
import { getServices } from '../../../../kibana_services';
import { createStateStub } from './_utils';
import { QueryParameterActionsProvider } from '../actions';
-
+import { createIndexPatternsStub } from '../../api/__tests__/_stubs';
+import { npStart } from 'ui/new_platform';
describe('context app', function () {
beforeEach(ngMock.module('kibana'));
+ beforeEach(ngMock.module(function createServiceStubs($provide) {
+ $provide.value('indexPatterns', createIndexPatternsStub());
+ }));
+
describe('action addFilter', function () {
- let filterManagerStub;
let addFilter;
beforeEach(ngMock.inject(function createPrivateStubs(Private) {
- filterManagerStub = createQueryFilterStub();
- Private.stub(getServices().FilterBarQueryFilterProvider, filterManagerStub);
-
+ Private.stub(getServices().FilterBarQueryFilterProvider);
addFilter = Private(QueryParameterActionsProvider).addFilter;
}));
it('should pass the given arguments to the filterManager', function () {
const state = createStateStub();
+ const filterManagerAddStub = npStart.plugins.data.query.filterManager.addFilters;
addFilter(state)('FIELD_NAME', 'FIELD_VALUE', 'FILTER_OPERATION');
- const filterManagerAddStub = filterManagerStub.addFilters;
//get the generated filter
const generatedFilter = filterManagerAddStub.firstCall.args[0][0];
const queryKeys = Object.keys(generatedFilter.query.match_phrase);
@@ -55,20 +56,12 @@ describe('context app', function () {
it('should pass the index pattern id to the filterManager', function () {
const state = createStateStub();
+ const filterManagerAddStub = npStart.plugins.data.query.filterManager.addFilters;
addFilter(state)('FIELD_NAME', 'FIELD_VALUE', 'FILTER_OPERATION');
- const filterManagerAddStub = filterManagerStub.addFilters;
const generatedFilter = filterManagerAddStub.firstCall.args[0][0];
- expect(filterManagerAddStub.calledOnce).to.be(true);
expect(generatedFilter.meta.index).to.eql('INDEX_PATTERN_ID');
});
});
});
-
-function createQueryFilterStub() {
- return {
- addFilters: sinon.stub(),
- getAppFilters: sinon.stub(),
- };
-}
diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/actions.js b/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/actions.js
index 9f7b180e8fe7db..10fe6c0e2eda1f 100644
--- a/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/actions.js
+++ b/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/actions.js
@@ -18,7 +18,8 @@
*/
import _ from 'lodash';
-import { getServices, getFilterGenerator } from '../../../kibana_services';
+import { generateFilters } from '../../../../../../../../plugins/data/public';
+import { npStart } from 'ui/new_platform';
import {
MAX_CONTEXT_SIZE,
@@ -27,9 +28,8 @@ import {
} from './constants';
-export function QueryParameterActionsProvider(indexPatterns, Private) {
- const queryFilter = Private(getServices().FilterBarQueryFilterProvider);
- const filterGen = getFilterGenerator(queryFilter);
+export function QueryParameterActionsProvider(indexPatterns) {
+ const { filterManager } = npStart.plugins.data.query;
const setPredecessorCount = (state) => (predecessorCount) => (
state.queryParameters.predecessorCount = clamp(
@@ -55,13 +55,13 @@ export function QueryParameterActionsProvider(indexPatterns, Private) {
);
const updateFilters = () => filters => {
- queryFilter.setFilters(filters);
+ filterManager.setFilters(filters);
};
const addFilter = (state) => async (field, values, operation) => {
const indexPatternId = state.queryParameters.indexPatternId;
- const newFilters = filterGen.generate(field, values, operation, indexPatternId);
- queryFilter.addFilters(newFilters);
+ const newFilters = generateFilters(filterManager, field, values, operation, indexPatternId);
+ filterManager.addFilters(newFilters);
const indexPattern = await indexPatterns.get(indexPatternId);
indexPattern.popularizeField(field.name, 1);
};
diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/discover.js b/src/legacy/core_plugins/kibana/public/discover/angular/discover.js
index ed5049aa912e0a..8ee23bfb005a2a 100644
--- a/src/legacy/core_plugins/kibana/public/discover/angular/discover.js
+++ b/src/legacy/core_plugins/kibana/public/discover/angular/discover.js
@@ -31,7 +31,6 @@ import './doc_table';
import { getSort } from './doc_table/lib/get_sort';
import { getSortForSearchSource } from './doc_table/lib/get_sort_for_search_source';
import * as columnActions from './doc_table/actions/columns';
-import * as filterActions from './doc_table/actions/filter';
import indexTemplate from './discover.html';
import { showOpenSearchPanel } from '../top_nav/show_open_search_panel';
@@ -41,7 +40,6 @@ import { getPainlessError } from './get_painless_error';
import {
angular,
buildVislibDimensions,
- getFilterGenerator,
getRequestInspectorStats,
getResponseInspectorStats,
getServices,
@@ -57,7 +55,7 @@ import {
subscribeWithScope,
tabifyAggResponse,
vislibSeriesResponseHandlerProvider,
- VisProvider,
+ Vis,
SavedObjectSaveModal,
} from '../kibana_services';
@@ -76,7 +74,7 @@ const {
import { getRootBreadcrumbs, getSavedSearchBreadcrumbs } from '../breadcrumbs';
import { extractTimeFilter, changeTimeFilter } from '../../../../data/public';
import { start as data } from '../../../../data/public/legacy';
-
+import { generateFilters } from '../../../../../../plugins/data/public';
const { savedQueryService } = data.search.services;
@@ -190,13 +188,11 @@ function discoverController(
localStorage,
uiCapabilities
) {
- const Vis = Private(VisProvider);
const responseHandler = vislibSeriesResponseHandlerProvider().handler;
const getUnhashableStates = Private(getUnhashableStatesProvider);
const shareContextMenuExtensions = Private(ShareContextMenuExtensionsRegistryProvider);
const queryFilter = Private(FilterBarQueryFilterProvider);
- const filterGen = getFilterGenerator(queryFilter);
const inspectorAdapters = {
requests: new RequestAdapter()
@@ -901,7 +897,8 @@ function discoverController(
// TODO: On array fields, negating does not negate the combination, rather all terms
$scope.filterQuery = function (field, values, operation) {
$scope.indexPattern.popularizeField(field, 1);
- filterActions.addFilter(field, values, operation, $scope.indexPattern.id, $scope.state, filterGen);
+ const newFilters = generateFilters(queryFilter, field, values, operation, $scope.indexPattern.id);
+ return queryFilter.addFilters(newFilters);
};
$scope.addColumn = function addColumn(columnName) {
diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/__tests__/actions/filter.js b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/__tests__/actions/filter.js
deleted file mode 100644
index 1f5db791469b95..00000000000000
--- a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/__tests__/actions/filter.js
+++ /dev/null
@@ -1,66 +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 { addFilter } from '../../actions/filter';
-import StubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern';
-import NoDigestPromises from 'test_utils/no_digest_promises';
-import expect from '@kbn/expect';
-import ngMock from 'ng_mock';
-import sinon from 'sinon';
-
-function getFilterGeneratorStub() {
- return {
- add: sinon.stub()
- };
-}
-
-describe('doc table filter actions', function () {
- NoDigestPromises.activateForSuite();
-
- let filterGen;
- let indexPattern;
-
- beforeEach(ngMock.module(
- 'kibana',
- function ($provide) {
- $provide.service('indexPatterns', require('fixtures/mock_index_patterns'));
- }
- ));
-
- beforeEach(ngMock.inject(function (Private) {
- indexPattern = Private(StubbedLogstashIndexPatternProvider);
- filterGen = getFilterGeneratorStub();
- }));
-
- describe('add', function () {
-
- it('should defer to the FilterManager when dealing with a lucene query', function () {
- const state = {
- query: { query: 'foo', language: 'lucene' }
- };
- const args = ['foo', ['bar'], '+', indexPattern, ];
- addFilter('foo', ['bar'], '+', indexPattern, state, filterGen);
- expect(filterGen.add.calledOnce).to.be(true);
- expect(filterGen.add.calledWith(...args)).to.be(true);
- });
-
- });
-
-
-});
diff --git a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_field_search.test.tsx b/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_field_search.test.tsx
index badfbb4b14a4c1..5054f7b4bdad16 100644
--- a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_field_search.test.tsx
+++ b/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_field_search.test.tsx
@@ -121,7 +121,7 @@ describe('DiscoverFieldSearch', () => {
// @ts-ignore
(aggregtableButtonGroup.props() as EuiButtonGroupProps).onChange('aggregatable-true', null);
});
- missingSwitch.simulate('change', { target: { value: false } });
+ missingSwitch.simulate('click');
expect(onChange).toBeCalledTimes(2);
});
diff --git a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_field_search.tsx b/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_field_search.tsx
index 3d93487d9e6ccd..d5f6b63d121992 100644
--- a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_field_search.tsx
+++ b/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_field_search.tsx
@@ -29,6 +29,7 @@ import {
EuiPopoverTitle,
EuiSelect,
EuiSwitch,
+ EuiSwitchEvent,
EuiForm,
EuiFormRow,
EuiButtonGroup,
@@ -154,7 +155,7 @@ export function DiscoverFieldSearch({ onChange, value, types }: Props) {
setActiveFiltersCount(activeFiltersCount + diff);
};
- const handleMissingChange = (e: React.ChangeEvent
) => {
+ const handleMissingChange = (e: EuiSwitchEvent) => {
const missingValue = e.target.checked;
handleValueChange('missing', missingValue);
};
diff --git a/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable.ts b/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable.ts
index 732fb6d2e4e709..c575465a377e24 100644
--- a/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable.ts
+++ b/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable.ts
@@ -25,10 +25,12 @@ import { npStart } from 'ui/new_platform';
import {
esFilters,
TimeRange,
+ FilterManager,
onlyDisabledFiltersChanged,
+ generateFilters,
getTime,
+ Query,
} from '../../../../../../plugins/data/public';
-import { Query } from '../../../../data/public';
import {
APPLY_FILTER_TRIGGER,
Container,
@@ -43,7 +45,6 @@ import { getSortForSearchSource } from '../angular/doc_table/lib/get_sort_for_se
import {
Adapters,
angular,
- getFilterGenerator,
getRequestInspectorStats,
getResponseInspectorStats,
getServices,
@@ -72,18 +73,6 @@ interface SearchScope extends ng.IScope {
isLoading?: boolean;
}
-export interface FilterManager {
- generate: (
- field: {
- name: string;
- scripted: boolean;
- },
- values: string | string[],
- operation: string,
- index: number
- ) => esFilters.Filter[];
-}
-
interface SearchEmbeddableConfig {
$rootScope: ng.IRootScopeService;
$compile: ng.ICompileService;
@@ -107,7 +96,7 @@ export class SearchEmbeddable extends Embeddable
private autoRefreshFetchSubscription?: Subscription;
private subscription?: Subscription;
public readonly type = SEARCH_EMBEDDABLE_TYPE;
- private filterGen: FilterManager;
+ private filterManager: FilterManager;
private abortController?: AbortController;
private prevTimeRange?: TimeRange;
@@ -134,7 +123,7 @@ export class SearchEmbeddable extends Embeddable
parent
);
- this.filterGen = getFilterGenerator(queryFilter);
+ this.filterManager = queryFilter as FilterManager;
this.savedSearch = savedSearch;
this.$rootScope = $rootScope;
this.$compile = $compile;
@@ -251,7 +240,7 @@ export class SearchEmbeddable extends Embeddable
};
searchScope.filter = async (field, value, operator) => {
- let filters = this.filterGen.generate(field, value, operator, indexPattern.id);
+ let filters = generateFilters(this.filterManager, field, value, operator, indexPattern.id);
filters = filters.map(filter => ({
...filter,
$state: { store: esFilters.FilterStateStore.APP_STATE },
diff --git a/src/legacy/core_plugins/kibana/public/discover/embeddable/types.ts b/src/legacy/core_plugins/kibana/public/discover/embeddable/types.ts
index 5473ec0e7b8b4d..2d940ad8cba98a 100644
--- a/src/legacy/core_plugins/kibana/public/discover/embeddable/types.ts
+++ b/src/legacy/core_plugins/kibana/public/discover/embeddable/types.ts
@@ -17,13 +17,11 @@
* under the License.
*/
-import { TimeRange } from 'src/plugins/data/public';
-import { Query } from 'src/legacy/core_plugins/data/public';
import { EmbeddableInput, EmbeddableOutput, IEmbeddable } from 'src/plugins/embeddable/public';
import { StaticIndexPattern } from '../kibana_services';
import { SavedSearch } from '../types';
import { SortOrder } from '../angular/doc_table/components/table_header/helpers';
-import { esFilters } from '../../../../../../plugins/data/public';
+import { esFilters, TimeRange, Query } from '../../../../../../plugins/data/public';
export interface SearchInput extends EmbeddableInput {
timeRange: TimeRange;
diff --git a/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts b/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts
index b78d05e68acade..d0eb115e32676e 100644
--- a/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts
+++ b/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts
@@ -84,8 +84,6 @@ export { angular };
export { buildVislibDimensions } from 'ui/visualize/loader/pipeline_helpers/build_pipeline';
// @ts-ignore
export { callAfterBindingsWorkaround } from 'ui/compat';
-// @ts-ignore
-export { getFilterGenerator } from 'ui/filter_manager';
export {
getRequestInspectorStats,
getResponseInspectorStats,
@@ -114,7 +112,7 @@ export { tabifyAggResponse } from 'ui/agg_response/tabify';
export { vislibSeriesResponseHandlerProvider } from 'ui/vis/response_handlers/vislib';
// EXPORT types
-export { VisProvider } from 'ui/vis';
+export { Vis } from 'ui/vis';
export { StaticIndexPattern, IndexPatterns, IndexPattern, FieldType } from 'ui/index_patterns';
export { SearchSource } from 'ui/courier';
export { ElasticSearchHit } from 'ui/registry/doc_views_types';
diff --git a/src/legacy/core_plugins/kibana/public/kibana.js b/src/legacy/core_plugins/kibana/public/kibana.js
index fe741a357cbfe7..c5b9d86b57aaea 100644
--- a/src/legacy/core_plugins/kibana/public/kibana.js
+++ b/src/legacy/core_plugins/kibana/public/kibana.js
@@ -37,7 +37,6 @@ import 'uiExports/navbarExtensions';
import 'uiExports/contextMenuActions';
import 'uiExports/managementSections';
import 'uiExports/indexManagement';
-import 'uiExports/devTools';
import 'uiExports/docViews';
import 'uiExports/embeddableFactories';
import 'uiExports/embeddableActions';
diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/__jest__/extract_export_details.test.ts b/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/__jest__/extract_export_details.test.ts
index a6ed2e36839f4b..4ecc3583e76cea 100644
--- a/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/__jest__/extract_export_details.test.ts
+++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/__jest__/extract_export_details.test.ts
@@ -62,7 +62,10 @@ describe('extractExportDetails', () => {
[
[
objLine('1', 'index-pattern'),
- detailsLine(1, [{ id: '2', type: 'index-pattern' }, { id: '3', type: 'index-pattern' }]),
+ detailsLine(1, [
+ { id: '2', type: 'index-pattern' },
+ { id: '3', type: 'index-pattern' },
+ ]),
].join(''),
],
{
@@ -75,7 +78,10 @@ describe('extractExportDetails', () => {
expect(result).toEqual({
exportedCount: 1,
missingRefCount: 2,
- missingReferences: [{ id: '2', type: 'index-pattern' }, { id: '3', type: 'index-pattern' }],
+ missingReferences: [
+ { id: '2', type: 'index-pattern' },
+ { id: '3', type: 'index-pattern' },
+ ],
});
});
diff --git a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable.ts b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable.ts
index 60cf7c7ec19288..0b75c6ffa1ffbb 100644
--- a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable.ts
+++ b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable.ts
@@ -33,11 +33,12 @@ import { npStart } from 'ui/new_platform';
import { IExpressionLoaderParams } from '../../../../expressions/public/np_ready/public/types';
import { start as expressions } from '../../../../expressions/public/legacy';
import { VISUALIZE_EMBEDDABLE_TYPE } from './constants';
-import { Query } from '../../../../data/public';
import {
TimeRange,
+ Query,
onlyDisabledFiltersChanged,
esFilters,
+ mapAndFlattenFilters,
} from '../../../../../../plugins/data/public';
import {
EmbeddableInput,
@@ -47,7 +48,6 @@ import {
APPLY_FILTER_TRIGGER,
} from '../../../../../../plugins/embeddable/public';
import { dispatchRenderComplete } from '../../../../../../plugins/kibana_utils/public';
-import { mapAndFlattenFilters } from '../../../../../../plugins/data/public';
const getKeys = (o: T): Array => Object.keys(o) as Array;
diff --git a/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/_saved_vis.js b/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/_saved_vis.js
index f8adaed0bf584a..aec80b8d13551a 100644
--- a/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/_saved_vis.js
+++ b/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/_saved_vis.js
@@ -25,7 +25,7 @@
* NOTE: It's a type of SavedObject, but specific to visualizations.
*/
-import { VisProvider } from 'ui/vis';
+import { Vis } from 'ui/vis';
import { uiModules } from 'ui/modules';
import { updateOldState } from 'ui/vis/vis_update_state';
import { VisualizeConstants } from '../visualize_constants';
@@ -39,7 +39,6 @@ import {
uiModules
.get('app/visualize')
.factory('SavedVis', function (Promise, savedSearches, Private) {
- const Vis = Private(VisProvider);
const SavedObject = Private(SavedObjectProvider);
createLegacyClass(SavedVis).inherits(SavedObject);
function SavedVis(opts) {
diff --git a/src/legacy/core_plugins/newsfeed/constants.ts b/src/legacy/core_plugins/newsfeed/constants.ts
new file mode 100644
index 00000000000000..55a0c51c2ac653
--- /dev/null
+++ b/src/legacy/core_plugins/newsfeed/constants.ts
@@ -0,0 +1,23 @@
+/*
+ * 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 PLUGIN_ID = 'newsfeed';
+export const DEFAULT_SERVICE_URLROOT = 'https://feeds.elastic.co';
+export const DEV_SERVICE_URLROOT = 'https://feeds-staging.elastic.co';
+export const DEFAULT_SERVICE_PATH = '/kibana/v{VERSION}.json';
diff --git a/src/legacy/core_plugins/newsfeed/index.ts b/src/legacy/core_plugins/newsfeed/index.ts
new file mode 100644
index 00000000000000..cf8852be09a1ef
--- /dev/null
+++ b/src/legacy/core_plugins/newsfeed/index.ts
@@ -0,0 +1,71 @@
+/*
+ * 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 { LegacyPluginApi, LegacyPluginSpec, ArrayOrItem } from 'src/legacy/plugin_discovery/types';
+import { Legacy } from 'kibana';
+import { NewsfeedPluginInjectedConfig } from '../../../plugins/newsfeed/types';
+import {
+ PLUGIN_ID,
+ DEFAULT_SERVICE_URLROOT,
+ DEV_SERVICE_URLROOT,
+ DEFAULT_SERVICE_PATH,
+} from './constants';
+
+// eslint-disable-next-line import/no-default-export
+export default function(kibana: LegacyPluginApi): ArrayOrItem {
+ const pluginSpec: Legacy.PluginSpecOptions = {
+ id: PLUGIN_ID,
+ config(Joi: any) {
+ // NewsfeedPluginInjectedConfig in Joi form
+ return Joi.object({
+ enabled: Joi.boolean().default(true),
+ service: Joi.object({
+ pathTemplate: Joi.string().default(DEFAULT_SERVICE_PATH),
+ urlRoot: Joi.when('$prod', {
+ is: true,
+ then: Joi.string().default(DEFAULT_SERVICE_URLROOT),
+ otherwise: Joi.string().default(DEV_SERVICE_URLROOT),
+ }),
+ }).default(),
+ defaultLanguage: Joi.string().default('en'),
+ mainInterval: Joi.number().default(120 * 1000), // (2min) How often to retry failed fetches, and/or check if newsfeed items need to be refreshed from remote
+ fetchInterval: Joi.number().default(86400 * 1000), // (1day) How often to fetch remote and reset the last fetched time
+ }).default();
+ },
+ uiExports: {
+ styleSheetPaths: resolve(__dirname, 'public/index.scss'),
+ injectDefaultVars(server): NewsfeedPluginInjectedConfig {
+ const config = server.config();
+ return {
+ newsfeed: {
+ service: {
+ pathTemplate: config.get('newsfeed.service.pathTemplate') as string,
+ urlRoot: config.get('newsfeed.service.urlRoot') as string,
+ },
+ defaultLanguage: config.get('newsfeed.defaultLanguage') as string,
+ mainInterval: config.get('newsfeed.mainInterval') as number,
+ fetchInterval: config.get('newsfeed.fetchInterval') as number,
+ },
+ };
+ },
+ },
+ };
+ return new kibana.Plugin(pluginSpec);
+}
diff --git a/src/legacy/core_plugins/newsfeed/package.json b/src/legacy/core_plugins/newsfeed/package.json
new file mode 100644
index 00000000000000..d4d753f32b0f96
--- /dev/null
+++ b/src/legacy/core_plugins/newsfeed/package.json
@@ -0,0 +1,4 @@
+{
+ "name": "newsfeed",
+ "version": "kibana"
+}
diff --git a/src/legacy/core_plugins/newsfeed/public/index.scss b/src/legacy/core_plugins/newsfeed/public/index.scss
new file mode 100644
index 00000000000000..a77132379041cb
--- /dev/null
+++ b/src/legacy/core_plugins/newsfeed/public/index.scss
@@ -0,0 +1,3 @@
+@import 'src/legacy/ui/public/styles/styling_constants';
+
+@import './np_ready/components/header_alert/_index';
diff --git a/src/legacy/core_plugins/newsfeed/public/np_ready/components/header_alert/_index.scss b/src/legacy/core_plugins/newsfeed/public/np_ready/components/header_alert/_index.scss
new file mode 100644
index 00000000000000..e25dbd25daaf5b
--- /dev/null
+++ b/src/legacy/core_plugins/newsfeed/public/np_ready/components/header_alert/_index.scss
@@ -0,0 +1,27 @@
+@import '@elastic/eui/src/components/header/variables';
+
+.kbnNews__flyout {
+ top: $euiHeaderChildSize + 1px;
+ height: calc(100% - #{$euiHeaderChildSize});
+}
+
+.kbnNewsFeed__headerAlert.euiHeaderAlert {
+ margin-bottom: $euiSizeL;
+ padding: 0 $euiSizeS $euiSizeL;
+ border-bottom: $euiBorderThin;
+ border-top: none;
+
+ .euiHeaderAlert__title {
+ @include euiTitle('xs');
+ margin-bottom: $euiSizeS;
+ }
+
+ .euiHeaderAlert__text {
+ @include euiFontSizeS;
+ margin-bottom: $euiSize;
+ }
+
+ .euiHeaderAlert__action {
+ @include euiFontSizeS;
+ }
+}
diff --git a/src/legacy/core_plugins/newsfeed/public/np_ready/components/header_alert/header_alert.tsx b/src/legacy/core_plugins/newsfeed/public/np_ready/components/header_alert/header_alert.tsx
new file mode 100644
index 00000000000000..c3c3e4144fca8a
--- /dev/null
+++ b/src/legacy/core_plugins/newsfeed/public/np_ready/components/header_alert/header_alert.tsx
@@ -0,0 +1,76 @@
+/*
+ * 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 React from 'react';
+import PropTypes from 'prop-types';
+import classNames from 'classnames';
+
+import { EuiFlexGroup, EuiFlexItem, EuiI18n } from '@elastic/eui';
+
+interface IEuiHeaderAlertProps {
+ action: JSX.Element;
+ className?: string;
+ date: string;
+ text: string;
+ title: string;
+ badge?: JSX.Element;
+ rest?: string[];
+}
+
+export const EuiHeaderAlert = ({
+ action,
+ className,
+ date,
+ text,
+ title,
+ badge,
+ ...rest
+}: IEuiHeaderAlertProps) => {
+ const classes = classNames('euiHeaderAlert', 'kbnNewsFeed__headerAlert', className);
+
+ const badgeContent = badge || null;
+
+ return (
+
+ {(dismiss: any) => (
+
+
+
+ {date}
+
+ {badgeContent}
+
+
+
{title}
+
{text}
+
{action}
+
+ )}
+
+ );
+};
+
+EuiHeaderAlert.propTypes = {
+ action: PropTypes.node,
+ className: PropTypes.string,
+ date: PropTypes.node.isRequired,
+ text: PropTypes.node,
+ title: PropTypes.node.isRequired,
+ badge: PropTypes.node,
+};
diff --git a/src/legacy/core_plugins/region_map/public/__tests__/region_map_visualization.js b/src/legacy/core_plugins/region_map/public/__tests__/region_map_visualization.js
index 9bfa413257967c..b57fbd637f0b72 100644
--- a/src/legacy/core_plugins/region_map/public/__tests__/region_map_visualization.js
+++ b/src/legacy/core_plugins/region_map/public/__tests__/region_map_visualization.js
@@ -22,7 +22,7 @@ import ngMock from 'ng_mock';
import _ from 'lodash';
import ChoroplethLayer from '../choropleth_layer';
import LogstashIndexPatternStubProvider from 'fixtures/stubbed_logstash_index_pattern';
-import * as visModule from 'ui/vis';
+import { Vis } from 'ui/vis';
import { ImageComparator } from 'test_utils/image_comparator';
import worldJson from './world.json';
import EMS_CATALOGUE from '../../../../ui/public/vis/__tests__/map/ems_mocks/sample_manifest.json';
@@ -50,7 +50,6 @@ const PIXEL_DIFF = 96;
describe('RegionMapsVisualizationTests', function () {
let domNode;
let RegionMapsVisualization;
- let Vis;
let indexPattern;
let vis;
let dependencies;
@@ -113,7 +112,6 @@ describe('RegionMapsVisualizationTests', function () {
visualizationsSetup.types.registerVisualization(() => createRegionMapTypeDefinition(dependencies));
}
- Vis = Private(visModule.VisProvider);
RegionMapsVisualization = createRegionMapVisualization(dependencies);
indexPattern = Private(LogstashIndexPatternStubProvider);
diff --git a/src/legacy/core_plugins/region_map/public/components/region_map_options.tsx b/src/legacy/core_plugins/region_map/public/components/region_map_options.tsx
index 9749c7fa8e2f9a..8306b3274a9144 100644
--- a/src/legacy/core_plugins/region_map/public/components/region_map_options.tsx
+++ b/src/legacy/core_plugins/region_map/public/components/region_map_options.tsx
@@ -82,7 +82,10 @@ function RegionMapOptions(props: RegionMapOptionsProps) {
const setField = useCallback(
(paramName: 'selectedJoinField', value: FileLayerField['name']) => {
if (stateParams.selectedLayer) {
- setValue(paramName, stateParams.selectedLayer.fields.find(f => f.name === value));
+ setValue(
+ paramName,
+ stateParams.selectedLayer.fields.find(f => f.name === value)
+ );
}
},
[setValue, stateParams.selectedLayer]
diff --git a/src/legacy/core_plugins/telemetry/common/constants.ts b/src/legacy/core_plugins/telemetry/common/constants.ts
index d7f34d1f8f8eb0..7b0c62276f2902 100644
--- a/src/legacy/core_plugins/telemetry/common/constants.ts
+++ b/src/legacy/core_plugins/telemetry/common/constants.ts
@@ -59,6 +59,12 @@ export const PRIVACY_STATEMENT_URL = `https://www.elastic.co/legal/privacy-state
*/
export const KIBANA_LOCALIZATION_STATS_TYPE = 'localization';
+/**
+ * The type name used to publish telemetry plugin stats.
+ * @type {string}
+ */
+export const TELEMETRY_STATS_TYPE = 'telemetry';
+
/**
* UI metric usage type
* @type {string}
diff --git a/src/legacy/core_plugins/telemetry/index.ts b/src/legacy/core_plugins/telemetry/index.ts
index 50a25423b5eb82..9993f2dbf0b861 100644
--- a/src/legacy/core_plugins/telemetry/index.ts
+++ b/src/legacy/core_plugins/telemetry/index.ts
@@ -27,12 +27,13 @@ import { i18n } from '@kbn/i18n';
import mappings from './mappings.json';
import { CONFIG_TELEMETRY, getConfigTelemetryDesc } from './common/constants';
import { getXpackConfigWithDeprecated } from './common/get_xpack_config_with_deprecated';
-import { telemetryPlugin, getTelemetryOptIn } from './server';
+import { telemetryPlugin, replaceTelemetryInjectedVars, FetcherTask } from './server';
import {
createLocalizationUsageCollector,
createTelemetryUsageCollector,
createUiMetricUsageCollector,
+ createTelemetryPluginUsageCollector,
} from './server/collectors';
const ENDPOINT_VERSION = 'v2';
@@ -46,20 +47,15 @@ const telemetry = (kibana: any) => {
config(Joi: typeof JoiNamespace) {
return Joi.object({
enabled: Joi.boolean().default(true),
+ allowChangingOptInStatus: Joi.boolean().default(true),
optIn: Joi.when('allowChangingOptInStatus', {
is: false,
- then: Joi.valid(true),
- otherwise: Joi.boolean()
- .allow(null)
- .default(null),
+ then: Joi.valid(true).default(true),
+ otherwise: Joi.boolean().default(true),
}),
- allowChangingOptInStatus: Joi.boolean().default(true),
// `config` is used internally and not intended to be set
config: Joi.string().default(Joi.ref('$defaultConfigPath')),
banner: Joi.boolean().default(true),
- lastVersionChecked: Joi.string()
- .allow('')
- .default(''),
url: Joi.when('$dev', {
is: true,
then: Joi.string().default(
@@ -69,6 +65,18 @@ const telemetry = (kibana: any) => {
`https://telemetry.elastic.co/xpack/${ENDPOINT_VERSION}/send`
),
}),
+ optInStatusUrl: Joi.when('$dev', {
+ is: true,
+ then: Joi.string().default(
+ `https://telemetry-staging.elastic.co/opt_in_status/${ENDPOINT_VERSION}/send`
+ ),
+ otherwise: Joi.string().default(
+ `https://telemetry.elastic.co/opt_in_status/${ENDPOINT_VERSION}/send`
+ ),
+ }),
+ sendUsageFrom: Joi.string()
+ .allow(['server', 'browser'])
+ .default('browser'),
}).default();
},
uiExports: {
@@ -89,30 +97,8 @@ const telemetry = (kibana: any) => {
},
},
async replaceInjectedVars(originalInjectedVars: any, request: any) {
- const config = request.server.config();
- const optIn = config.get('telemetry.optIn');
- const allowChangingOptInStatus = config.get('telemetry.allowChangingOptInStatus');
- const currentKibanaVersion = getCurrentKibanaVersion(request.server);
- let telemetryOptedIn: boolean | null;
-
- if (typeof optIn === 'boolean' && !allowChangingOptInStatus) {
- // When not allowed to change optIn status and an optIn value is set, we'll overwrite with that
- telemetryOptedIn = optIn;
- } else {
- telemetryOptedIn = await getTelemetryOptIn({
- request,
- currentKibanaVersion,
- });
- if (telemetryOptedIn === null) {
- // In the senario there's no value set in telemetryOptedIn, we'll return optIn value
- telemetryOptedIn = optIn;
- }
- }
-
- return {
- ...originalInjectedVars,
- telemetryOptedIn,
- };
+ const telemetryInjectedVars = await replaceTelemetryInjectedVars(request);
+ return Object.assign({}, originalInjectedVars, telemetryInjectedVars);
},
injectDefaultVars(server: Server) {
const config = server.config();
@@ -123,17 +109,23 @@ const telemetry = (kibana: any) => {
config.get('telemetry.allowChangingOptInStatus') !== false &&
getXpackConfigWithDeprecated(config, 'telemetry.banner'),
telemetryOptedIn: config.get('telemetry.optIn'),
+ telemetryOptInStatusUrl: config.get('telemetry.optInStatusUrl'),
allowChangingOptInStatus: config.get('telemetry.allowChangingOptInStatus'),
+ telemetrySendUsageFrom: config.get('telemetry.sendUsageFrom'),
};
},
hacks: ['plugins/telemetry/hacks/telemetry_init', 'plugins/telemetry/hacks/telemetry_opt_in'],
mappings,
},
- async init(server: Server) {
+ postInit(server: Server) {
+ const fetcherTask = new FetcherTask(server);
+ fetcherTask.start();
+ },
+ init(server: Server) {
const initializerContext = {
env: {
packageInfo: {
- version: getCurrentKibanaVersion(server),
+ version: server.config().get('pkg.version'),
},
},
config: {
@@ -156,9 +148,9 @@ const telemetry = (kibana: any) => {
log: server.log,
} as any) as CoreSetup;
- await telemetryPlugin(initializerContext).setup(coreSetup);
-
+ telemetryPlugin(initializerContext).setup(coreSetup);
// register collectors
+ server.usage.collectorSet.register(createTelemetryPluginUsageCollector(server));
server.usage.collectorSet.register(createLocalizationUsageCollector(server));
server.usage.collectorSet.register(createTelemetryUsageCollector(server));
server.usage.collectorSet.register(createUiMetricUsageCollector(server));
@@ -168,7 +160,3 @@ const telemetry = (kibana: any) => {
// eslint-disable-next-line import/no-default-export
export default telemetry;
-
-function getCurrentKibanaVersion(server: Server): string {
- return server.config().get('pkg.version');
-}
diff --git a/src/legacy/core_plugins/telemetry/mappings.json b/src/legacy/core_plugins/telemetry/mappings.json
index 1245ef88f58929..95c6ebfc7dc793 100644
--- a/src/legacy/core_plugins/telemetry/mappings.json
+++ b/src/legacy/core_plugins/telemetry/mappings.json
@@ -4,7 +4,15 @@
"enabled": {
"type": "boolean"
},
+ "sendUsageFrom": {
+ "ignore_above": 256,
+ "type": "keyword"
+ },
+ "lastReported": {
+ "type": "date"
+ },
"lastVersionChecked": {
+ "ignore_above": 256,
"type": "keyword"
}
}
diff --git a/src/legacy/core_plugins/telemetry/public/components/__snapshots__/telemetry_form.test.js.snap b/src/legacy/core_plugins/telemetry/public/components/__snapshots__/telemetry_form.test.js.snap
index b96313fd700ac5..a7f8d72e016f87 100644
--- a/src/legacy/core_plugins/telemetry/public/components/__snapshots__/telemetry_form.test.js.snap
+++ b/src/legacy/core_plugins/telemetry/public/components/__snapshots__/telemetry_form.test.js.snap
@@ -34,7 +34,8 @@ exports[`TelemetryForm renders as expected when allows to change optIn status 1`
save={[Function]}
setting={
Object {
- "defVal": false,
+ "ariaName": "Provide usage statistics",
+ "defVal": true,
"description":
Help us improve the Elastic Stack by providing usage statistics for basic features. We will not share this data outside of Elastic.
diff --git a/src/legacy/core_plugins/telemetry/public/components/telemetry_form.js b/src/legacy/core_plugins/telemetry/public/components/telemetry_form.js
index 80eb2da59c47e8..6c6ace71af4d09 100644
--- a/src/legacy/core_plugins/telemetry/public/components/telemetry_form.js
+++ b/src/legacy/core_plugins/telemetry/public/components/telemetry_form.js
@@ -33,6 +33,7 @@ import { getConfigTelemetryDesc, PRIVACY_STATEMENT_URL } from '../../common/cons
import { OptInExampleFlyout } from './opt_in_details_component';
import { Field } from 'ui/management';
import { FormattedMessage } from '@kbn/i18n/react';
+import { i18n } from '@kbn/i18n';
const SEARCH_TERMS = ['telemetry', 'usage', 'data', 'usage data'];
@@ -116,7 +117,8 @@ export class TelemetryForm extends Component {
type: 'boolean',
value: telemetryOptInProvider.getOptIn() || false,
description: this.renderDescription(),
- defVal: false,
+ defVal: true,
+ ariaName: i18n.translate('telemetry.provideUsageStatisticsLabel', { defaultMessage: 'Provide usage statistics' })
}}
save={this.toggleOptIn}
clear={this.toggleOptIn}
diff --git a/src/legacy/core_plugins/telemetry/public/hacks/telemetry_init.ts b/src/legacy/core_plugins/telemetry/public/hacks/telemetry_init.ts
index 364871380a529c..1930d65d5c09b9 100644
--- a/src/legacy/core_plugins/telemetry/public/hacks/telemetry_init.ts
+++ b/src/legacy/core_plugins/telemetry/public/hacks/telemetry_init.ts
@@ -25,13 +25,21 @@ import { isUnauthenticated } from '../services';
import { Telemetry } from './telemetry';
// @ts-ignore
import { fetchTelemetry } from './fetch_telemetry';
+// @ts-ignore
+import { isOptInHandleOldSettings } from './welcome_banner/handle_old_settings';
+import { TelemetryOptInProvider } from '../services';
function telemetryInit($injector: any) {
const $http = $injector.get('$http');
+ const Private = $injector.get('Private');
+ const config = $injector.get('config');
+ const telemetryOptInProvider = Private(TelemetryOptInProvider);
const telemetryEnabled = npStart.core.injectedMetadata.getInjectedVar('telemetryEnabled');
+ const telemetryOptedIn = isOptInHandleOldSettings(config, telemetryOptInProvider);
+ const sendUsageFrom = npStart.core.injectedMetadata.getInjectedVar('telemetrySendUsageFrom');
- if (telemetryEnabled) {
+ if (telemetryEnabled && telemetryOptedIn && sendUsageFrom === 'browser') {
// no telemetry for non-logged in users
if (isUnauthenticated()) {
return;
diff --git a/src/legacy/core_plugins/telemetry/public/hacks/welcome_banner/click_banner.test.js b/src/legacy/core_plugins/telemetry/public/hacks/welcome_banner/click_banner.test.js
index 6e9a9fc8443ba0..54557f100f4aa4 100644
--- a/src/legacy/core_plugins/telemetry/public/hacks/welcome_banner/click_banner.test.js
+++ b/src/legacy/core_plugins/telemetry/public/hacks/welcome_banner/click_banner.test.js
@@ -54,7 +54,7 @@ const getTelemetryOptInProvider = ({ simulateFailure = false, simulateError = fa
addBasePath: (url) => url
};
- const provider = new TelemetryOptInProvider(injector, chrome);
+ const provider = new TelemetryOptInProvider(injector, chrome, false);
if (simulateError) {
provider.setOptIn = () => Promise.reject('unhandled error');
diff --git a/src/legacy/core_plugins/telemetry/public/hacks/welcome_banner/handle_old_settings.js b/src/legacy/core_plugins/telemetry/public/hacks/welcome_banner/handle_old_settings.js
index 31091e19520533..4f0f2983477e07 100644
--- a/src/legacy/core_plugins/telemetry/public/hacks/welcome_banner/handle_old_settings.js
+++ b/src/legacy/core_plugins/telemetry/public/hacks/welcome_banner/handle_old_settings.js
@@ -27,8 +27,9 @@ import { CONFIG_TELEMETRY } from '../../../common/constants';
* @param {Object} config The advanced settings config object.
* @return {Boolean} {@code true} if the banner should still be displayed. {@code false} if the banner should not be displayed.
*/
+const CONFIG_ALLOW_REPORT = 'xPackMonitoring:allowReport';
+
export async function handleOldSettings(config, telemetryOptInProvider) {
- const CONFIG_ALLOW_REPORT = 'xPackMonitoring:allowReport';
const CONFIG_SHOW_BANNER = 'xPackMonitoring:showBanner';
const oldAllowReportSetting = config.get(CONFIG_ALLOW_REPORT, null);
const oldTelemetrySetting = config.get(CONFIG_TELEMETRY, null);
@@ -62,3 +63,24 @@ export async function handleOldSettings(config, telemetryOptInProvider) {
return true;
}
+
+
+export async function isOptInHandleOldSettings(config, telemetryOptInProvider) {
+ const currentOptInSettting = telemetryOptInProvider.getOptIn();
+
+ if (typeof currentOptInSettting === 'boolean') {
+ return currentOptInSettting;
+ }
+
+ const oldTelemetrySetting = config.get(CONFIG_TELEMETRY, null);
+ if (typeof oldTelemetrySetting === 'boolean') {
+ return oldTelemetrySetting;
+ }
+
+ const oldAllowReportSetting = config.get(CONFIG_ALLOW_REPORT, null);
+ if (typeof oldAllowReportSetting === 'boolean') {
+ return oldAllowReportSetting;
+ }
+
+ return null;
+}
diff --git a/src/legacy/core_plugins/telemetry/public/hacks/welcome_banner/handle_old_settings.test.js b/src/legacy/core_plugins/telemetry/public/hacks/welcome_banner/handle_old_settings.test.js
index f26ca0ca0e3c51..d78a4a3e923623 100644
--- a/src/legacy/core_plugins/telemetry/public/hacks/welcome_banner/handle_old_settings.test.js
+++ b/src/legacy/core_plugins/telemetry/public/hacks/welcome_banner/handle_old_settings.test.js
@@ -49,7 +49,7 @@ const getTelemetryOptInProvider = (enabled, { simulateFailure = false } = {}) =>
}
};
- return new TelemetryOptInProvider($injector, chrome);
+ return new TelemetryOptInProvider($injector, chrome, false);
};
describe('handle_old_settings', () => {
diff --git a/src/legacy/core_plugins/telemetry/public/services/telemetry_opt_in.test.js b/src/legacy/core_plugins/telemetry/public/services/telemetry_opt_in.test.js
index 26f14fc87d937c..b0ebb9e7382f64 100644
--- a/src/legacy/core_plugins/telemetry/public/services/telemetry_opt_in.test.js
+++ b/src/legacy/core_plugins/telemetry/public/services/telemetry_opt_in.test.js
@@ -48,7 +48,7 @@ describe('TelemetryOptInProvider', () => {
}
};
- const provider = new TelemetryOptInProvider(mockInjector, mockChrome);
+ const provider = new TelemetryOptInProvider(mockInjector, mockChrome, false);
return {
provider,
mockHttp,
diff --git a/src/legacy/core_plugins/telemetry/public/services/telemetry_opt_in.ts b/src/legacy/core_plugins/telemetry/public/services/telemetry_opt_in.ts
index 4d27bad352cd4d..9b32f88df1218a 100644
--- a/src/legacy/core_plugins/telemetry/public/services/telemetry_opt_in.ts
+++ b/src/legacy/core_plugins/telemetry/public/services/telemetry_opt_in.ts
@@ -26,7 +26,36 @@ import { i18n } from '@kbn/i18n';
let bannerId: string | null = null;
let currentOptInStatus = false;
-export function TelemetryOptInProvider($injector: any, chrome: any) {
+async function sendOptInStatus($injector: any, chrome: any, enabled: boolean) {
+ const telemetryOptInStatusUrl = npStart.core.injectedMetadata.getInjectedVar(
+ 'telemetryOptInStatusUrl'
+ ) as string;
+ const $http = $injector.get('$http');
+
+ try {
+ const optInStatus = await $http.post(
+ chrome.addBasePath('/api/telemetry/v2/clusters/_opt_in_stats'),
+ {
+ enabled,
+ unencrypted: false,
+ }
+ );
+
+ if (optInStatus.data && optInStatus.data.length) {
+ return await fetch(telemetryOptInStatusUrl, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify(optInStatus.data),
+ });
+ }
+ } catch (err) {
+ // Sending the ping is best-effort. Telemetry tries to send the ping once and discards it immediately if sending fails.
+ // swallow any errors
+ }
+}
+export function TelemetryOptInProvider($injector: any, chrome: any, sendOptInStatusChange = true) {
currentOptInStatus = npStart.core.injectedMetadata.getInjectedVar('telemetryOptedIn') as boolean;
const allowChangingOptInStatus = npStart.core.injectedMetadata.getInjectedVar(
'allowChangingOptInStatus'
@@ -41,11 +70,17 @@ export function TelemetryOptInProvider($injector: any, chrome: any) {
bannerId = id;
},
setOptIn: async (enabled: boolean) => {
+ if (!allowChangingOptInStatus) {
+ return;
+ }
setCanTrackUiMetrics(enabled);
const $http = $injector.get('$http');
try {
await $http.post(chrome.addBasePath('/api/telemetry/v2/optIn'), { enabled });
+ if (sendOptInStatusChange) {
+ await sendOptInStatus($injector, chrome, enabled);
+ }
currentOptInStatus = enabled;
} catch (error) {
toastNotifications.addError(error, {
diff --git a/src/legacy/core_plugins/telemetry/server/collection_manager.ts b/src/legacy/core_plugins/telemetry/server/collection_manager.ts
index fef0a9b0f9f40b..799d9f4ee9c8b6 100644
--- a/src/legacy/core_plugins/telemetry/server/collection_manager.ts
+++ b/src/legacy/core_plugins/telemetry/server/collection_manager.ts
@@ -17,32 +17,187 @@
* under the License.
*/
-class TelemetryCollectionManager {
- private getterMethod?: any;
- private collectionTitle?: string;
- private getterMethodPriority = 0;
-
- public setStatsGetter = (statsGetter: any, title: string, priority = 0) => {
- if (priority >= this.getterMethodPriority) {
- this.getterMethod = statsGetter;
- this.collectionTitle = title;
- this.getterMethodPriority = priority;
+import { encryptTelemetry } from './collectors';
+import { CallCluster } from '../../elasticsearch';
+
+export type EncryptedStatsGetterConfig = { unencrypted: false } & {
+ server: any;
+ start: string;
+ end: string;
+};
+
+export type UnencryptedStatsGetterConfig = { unencrypted: true } & {
+ req: any;
+ start: string;
+ end: string;
+};
+
+export interface ClusterDetails {
+ clusterUuid: string;
+}
+
+export interface StatsCollectionConfig {
+ callCluster: CallCluster;
+ server: any;
+ start: string;
+ end: string;
+}
+
+export type StatsGetterConfig = UnencryptedStatsGetterConfig | EncryptedStatsGetterConfig;
+export type ClusterDetailsGetter = (config: StatsCollectionConfig) => Promise;
+export type StatsGetter = (
+ clustersDetails: ClusterDetails[],
+ config: StatsCollectionConfig
+) => Promise;
+
+interface CollectionConfig {
+ title: string;
+ priority: number;
+ esCluster: string;
+ statsGetter: StatsGetter;
+ clusterDetailsGetter: ClusterDetailsGetter;
+}
+interface Collection {
+ statsGetter: StatsGetter;
+ clusterDetailsGetter: ClusterDetailsGetter;
+ esCluster: string;
+ title: string;
+}
+
+export class TelemetryCollectionManager {
+ private usageGetterMethodPriority = -1;
+ private collections: Collection[] = [];
+
+ public setCollection = (collectionConfig: CollectionConfig) => {
+ const { title, priority, esCluster, statsGetter, clusterDetailsGetter } = collectionConfig;
+
+ if (typeof priority !== 'number') {
+ throw new Error('priority must be set.');
+ }
+ if (priority === this.usageGetterMethodPriority) {
+ throw new Error(`A Usage Getter with the same priority is already set.`);
}
+
+ if (priority > this.usageGetterMethodPriority) {
+ if (!statsGetter) {
+ throw Error('Stats getter method not set.');
+ }
+ if (!esCluster) {
+ throw Error('esCluster name must be set for the getCluster method.');
+ }
+ if (!clusterDetailsGetter) {
+ throw Error('Cluser UUIds method is not set.');
+ }
+
+ this.collections.unshift({
+ statsGetter,
+ clusterDetailsGetter,
+ esCluster,
+ title,
+ });
+ this.usageGetterMethodPriority = priority;
+ }
+ };
+
+ private getStatsCollectionConfig = async (
+ collection: Collection,
+ config: StatsGetterConfig
+ ): Promise => {
+ const { start, end } = config;
+ const server = config.unencrypted ? config.req.server : config.server;
+ const { callWithRequest, callWithInternalUser } = server.plugins.elasticsearch.getCluster(
+ collection.esCluster
+ );
+ const callCluster = config.unencrypted
+ ? (...args: any[]) => callWithRequest(config.req, ...args)
+ : callWithInternalUser;
+
+ return { server, callCluster, start, end };
+ };
+
+ private getOptInStatsForCollection = async (
+ collection: Collection,
+ optInStatus: boolean,
+ statsCollectionConfig: StatsCollectionConfig
+ ) => {
+ const clustersDetails = await collection.clusterDetailsGetter(statsCollectionConfig);
+ return clustersDetails.map(({ clusterUuid }) => ({
+ cluster_uuid: clusterUuid,
+ opt_in_status: optInStatus,
+ }));
};
- getCollectionTitle = () => {
- return this.collectionTitle;
+ private getUsageForCollection = async (
+ collection: Collection,
+ statsCollectionConfig: StatsCollectionConfig
+ ) => {
+ const clustersDetails = await collection.clusterDetailsGetter(statsCollectionConfig);
+
+ if (clustersDetails.length === 0) {
+ // don't bother doing a further lookup, try next collection.
+ return;
+ }
+
+ return await collection.statsGetter(clustersDetails, statsCollectionConfig);
};
- public getStatsGetter = () => {
- if (!this.getterMethod) {
- throw Error('Stats getter method not set.');
+ public getOptInStats = async (optInStatus: boolean, config: StatsGetterConfig) => {
+ for (const collection of this.collections) {
+ const statsCollectionConfig = await this.getStatsCollectionConfig(collection, config);
+ try {
+ const optInStats = await this.getOptInStatsForCollection(
+ collection,
+ optInStatus,
+ statsCollectionConfig
+ );
+ if (optInStats && optInStats.length) {
+ statsCollectionConfig.server.log(
+ ['debug', 'telemetry', 'collection'],
+ `Got Opt In stats using ${collection.title} collection.`
+ );
+ if (config.unencrypted) {
+ return optInStats;
+ }
+ const isDev = statsCollectionConfig.server.config().get('env.dev');
+ return encryptTelemetry(optInStats, isDev);
+ }
+ } catch (err) {
+ statsCollectionConfig.server.log(
+ ['debu', 'telemetry', 'collection'],
+ `Failed to collect any opt in stats with registered collections.`
+ );
+ // swallow error to try next collection;
+ }
}
- return {
- getStats: this.getterMethod,
- priority: this.getterMethodPriority,
- title: this.collectionTitle,
- };
+
+ return [];
+ };
+ public getStats = async (config: StatsGetterConfig) => {
+ for (const collection of this.collections) {
+ const statsCollectionConfig = await this.getStatsCollectionConfig(collection, config);
+ try {
+ const usageData = await this.getUsageForCollection(collection, statsCollectionConfig);
+ if (usageData && usageData.length) {
+ statsCollectionConfig.server.log(
+ ['debug', 'telemetry', 'collection'],
+ `Got Usage using ${collection.title} collection.`
+ );
+ if (config.unencrypted) {
+ return usageData;
+ }
+ const isDev = statsCollectionConfig.server.config().get('env.dev');
+ return encryptTelemetry(usageData, isDev);
+ }
+ } catch (err) {
+ statsCollectionConfig.server.log(
+ ['debu', 'telemetry', 'collection'],
+ `Failed to collect any usage with registered collections.`
+ );
+ // swallow error to try next collection;
+ }
+ }
+
+ return [];
};
}
diff --git a/src/legacy/core_plugins/telemetry/server/collectors/index.ts b/src/legacy/core_plugins/telemetry/server/collectors/index.ts
index 0bc1d50fab1be6..f963ecec0477cf 100644
--- a/src/legacy/core_plugins/telemetry/server/collectors/index.ts
+++ b/src/legacy/core_plugins/telemetry/server/collectors/index.ts
@@ -21,3 +21,4 @@ export { encryptTelemetry } from './encryption';
export { createTelemetryUsageCollector } from './usage';
export { createUiMetricUsageCollector } from './ui_metric';
export { createLocalizationUsageCollector } from './localization';
+export { createTelemetryPluginUsageCollector } from './telemetry_plugin';
diff --git a/src/legacy/ui/public/registry/dev_tools.js b/src/legacy/core_plugins/telemetry/server/collectors/telemetry_plugin/index.ts
similarity index 83%
rename from src/legacy/ui/public/registry/dev_tools.js
rename to src/legacy/core_plugins/telemetry/server/collectors/telemetry_plugin/index.ts
index 1741f39f86375c..e96c47741f79c9 100644
--- a/src/legacy/ui/public/registry/dev_tools.js
+++ b/src/legacy/core_plugins/telemetry/server/collectors/telemetry_plugin/index.ts
@@ -17,11 +17,4 @@
* under the License.
*/
-import { uiRegistry } from './_registry';
-
-export const DevToolsRegistryProvider = uiRegistry({
- name: 'devTools',
- index: ['name'],
- order: ['order']
-});
-
+export { createTelemetryPluginUsageCollector } from './telemetry_plugin_collector';
diff --git a/src/legacy/core_plugins/telemetry/server/collectors/telemetry_plugin/telemetry_plugin_collector.ts b/src/legacy/core_plugins/telemetry/server/collectors/telemetry_plugin/telemetry_plugin_collector.ts
new file mode 100644
index 00000000000000..a172ba7dc69559
--- /dev/null
+++ b/src/legacy/core_plugins/telemetry/server/collectors/telemetry_plugin/telemetry_plugin_collector.ts
@@ -0,0 +1,75 @@
+/*
+ * 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 { TELEMETRY_STATS_TYPE } from '../../../common/constants';
+import { getTelemetrySavedObject, TelemetrySavedObject } from '../../telemetry_repository';
+import { getTelemetryOptIn, getTelemetrySendUsageFrom } from '../../telemetry_config';
+export interface TelemetryUsageStats {
+ opt_in_status?: boolean | null;
+ usage_fetcher?: 'browser' | 'server';
+ last_reported?: number;
+}
+
+export function createCollectorFetch(server: any) {
+ return async function fetchUsageStats(): Promise {
+ const config = server.config();
+ const configTelemetrySendUsageFrom = config.get('telemetry.sendUsageFrom');
+ const allowChangingOptInStatus = config.get('telemetry.allowChangingOptInStatus');
+ const configTelemetryOptIn = config.get('telemetry.optIn');
+ const currentKibanaVersion = config.get('pkg.version');
+
+ let telemetrySavedObject: TelemetrySavedObject = {};
+
+ try {
+ const { getSavedObjectsRepository } = server.savedObjects;
+ const { callWithInternalUser } = server.plugins.elasticsearch.getCluster('admin');
+ const internalRepository = getSavedObjectsRepository(callWithInternalUser);
+ telemetrySavedObject = await getTelemetrySavedObject(internalRepository);
+ } catch (err) {
+ // no-op
+ }
+
+ return {
+ opt_in_status: getTelemetryOptIn({
+ currentKibanaVersion,
+ telemetrySavedObject,
+ allowChangingOptInStatus,
+ configTelemetryOptIn,
+ }),
+ last_reported: telemetrySavedObject ? telemetrySavedObject.lastReported : undefined,
+ usage_fetcher: getTelemetrySendUsageFrom({
+ telemetrySavedObject,
+ configTelemetrySendUsageFrom,
+ }),
+ };
+ };
+}
+
+/*
+ * @param {Object} server
+ * @return {Object} kibana usage stats type collection object
+ */
+export function createTelemetryPluginUsageCollector(server: any) {
+ const { collectorSet } = server.usage;
+ return collectorSet.makeUsageCollector({
+ type: TELEMETRY_STATS_TYPE,
+ isReady: () => true,
+ fetch: createCollectorFetch(server),
+ });
+}
diff --git a/src/legacy/core_plugins/telemetry/server/collectors/usage/ensure_deep_object.ts b/src/legacy/core_plugins/telemetry/server/collectors/usage/ensure_deep_object.ts
index 6594c7f8e7a6f0..3b7a9355da746f 100644
--- a/src/legacy/core_plugins/telemetry/server/collectors/usage/ensure_deep_object.ts
+++ b/src/legacy/core_plugins/telemetry/server/collectors/usage/ensure_deep_object.ts
@@ -42,19 +42,16 @@ export function ensureDeepObject(obj: any): any {
return obj.map(item => ensureDeepObject(item));
}
- return Object.keys(obj).reduce(
- (fullObject, propertyKey) => {
- const propertyValue = obj[propertyKey];
- if (!propertyKey.includes(separator)) {
- fullObject[propertyKey] = ensureDeepObject(propertyValue);
- } else {
- walk(fullObject, propertyKey.split(separator), propertyValue);
- }
+ return Object.keys(obj).reduce((fullObject, propertyKey) => {
+ const propertyValue = obj[propertyKey];
+ if (!propertyKey.includes(separator)) {
+ fullObject[propertyKey] = ensureDeepObject(propertyValue);
+ } else {
+ walk(fullObject, propertyKey.split(separator), propertyValue);
+ }
- return fullObject;
- },
- {} as any
- );
+ return fullObject;
+ }, {} as any);
}
function walk(obj: any, keys: string[], value: any) {
diff --git a/src/legacy/core_plugins/telemetry/server/fetcher.ts b/src/legacy/core_plugins/telemetry/server/fetcher.ts
new file mode 100644
index 00000000000000..9edd8457f2b89e
--- /dev/null
+++ b/src/legacy/core_plugins/telemetry/server/fetcher.ts
@@ -0,0 +1,143 @@
+/*
+ * 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 moment from 'moment';
+// @ts-ignore
+import fetch from 'node-fetch';
+import { telemetryCollectionManager } from './collection_manager';
+import { getTelemetryOptIn, getTelemetrySendUsageFrom } from './telemetry_config';
+import { getTelemetrySavedObject, updateTelemetrySavedObject } from './telemetry_repository';
+import { REPORT_INTERVAL_MS } from '../common/constants';
+import { getXpackConfigWithDeprecated } from '../common/get_xpack_config_with_deprecated';
+
+export class FetcherTask {
+ private readonly checkDurationMs = 60 * 1000 * 5;
+ private intervalId?: NodeJS.Timeout;
+ private lastReported?: number;
+ private isSending = false;
+ private server: any;
+
+ constructor(server: any) {
+ this.server = server;
+ }
+
+ private getInternalRepository = () => {
+ const { getSavedObjectsRepository } = this.server.savedObjects;
+ const { callWithInternalUser } = this.server.plugins.elasticsearch.getCluster('admin');
+ const internalRepository = getSavedObjectsRepository(callWithInternalUser);
+ return internalRepository;
+ };
+
+ private getCurrentConfigs = async () => {
+ const internalRepository = this.getInternalRepository();
+ const telemetrySavedObject = await getTelemetrySavedObject(internalRepository);
+ const config = this.server.config();
+ const currentKibanaVersion = config.get('pkg.version');
+ const configTelemetrySendUsageFrom = config.get('telemetry.sendUsageFrom');
+ const allowChangingOptInStatus = config.get('telemetry.allowChangingOptInStatus');
+ const configTelemetryOptIn = config.get('telemetry.optIn');
+ const telemetryUrl = getXpackConfigWithDeprecated(config, 'telemetry.url') as string;
+
+ return {
+ telemetryOptIn: getTelemetryOptIn({
+ currentKibanaVersion,
+ telemetrySavedObject,
+ allowChangingOptInStatus,
+ configTelemetryOptIn,
+ }),
+ telemetrySendUsageFrom: getTelemetrySendUsageFrom({
+ telemetrySavedObject,
+ configTelemetrySendUsageFrom,
+ }),
+ telemetryUrl,
+ };
+ };
+
+ private updateLastReported = async () => {
+ const internalRepository = this.getInternalRepository();
+ this.lastReported = Date.now();
+ updateTelemetrySavedObject(internalRepository, {
+ lastReported: this.lastReported,
+ });
+ };
+
+ private shouldSendReport = ({ telemetryOptIn, telemetrySendUsageFrom }: any) => {
+ if (telemetryOptIn && telemetrySendUsageFrom === 'server') {
+ if (!this.lastReported || Date.now() - this.lastReported > REPORT_INTERVAL_MS) {
+ return true;
+ }
+ }
+ return false;
+ };
+
+ private fetchTelemetry = async () => {
+ return await telemetryCollectionManager.getStats({
+ unencrypted: false,
+ server: this.server,
+ start: moment()
+ .subtract(20, 'minutes')
+ .toISOString(),
+ end: moment().toISOString(),
+ });
+ };
+
+ private sendTelemetry = async (url: string, cluster: any): Promise => {
+ this.server.log(['debug', 'telemetry', 'fetcher'], `Sending usage stats.`);
+ await fetch(url, {
+ method: 'post',
+ body: cluster,
+ });
+ };
+
+ private sendIfDue = async () => {
+ if (this.isSending) {
+ return;
+ }
+ try {
+ const telemetryConfig = await this.getCurrentConfigs();
+ if (!this.shouldSendReport(telemetryConfig)) {
+ return;
+ }
+
+ // mark that we are working so future requests are ignored until we're done
+ this.isSending = true;
+ const clusters = await this.fetchTelemetry();
+ for (const cluster of clusters) {
+ await this.sendTelemetry(telemetryConfig.telemetryUrl, cluster);
+ }
+
+ await this.updateLastReported();
+ } catch (err) {
+ this.server.log(
+ ['warning', 'telemetry', 'fetcher'],
+ `Error sending telemetry usage data: ${err}`
+ );
+ }
+ this.isSending = false;
+ };
+
+ public start = () => {
+ this.intervalId = setInterval(() => this.sendIfDue(), this.checkDurationMs);
+ };
+ public stop = () => {
+ if (this.intervalId) {
+ clearInterval(this.intervalId);
+ }
+ };
+}
diff --git a/src/legacy/core_plugins/telemetry/server/index.ts b/src/legacy/core_plugins/telemetry/server/index.ts
index aa13fab9a5f819..02752ca773488e 100644
--- a/src/legacy/core_plugins/telemetry/server/index.ts
+++ b/src/legacy/core_plugins/telemetry/server/index.ts
@@ -21,7 +21,8 @@ import { PluginInitializerContext } from 'src/core/server';
import { TelemetryPlugin } from './plugin';
import * as constants from '../common/constants';
-export { getTelemetryOptIn } from './get_telemetry_opt_in';
+export { FetcherTask } from './fetcher';
+export { replaceTelemetryInjectedVars } from './telemetry_config';
export { telemetryCollectionManager } from './collection_manager';
export const telemetryPlugin = (initializerContext: PluginInitializerContext) =>
diff --git a/src/legacy/core_plugins/telemetry/server/plugin.ts b/src/legacy/core_plugins/telemetry/server/plugin.ts
index 813aa0df09e8c8..f2628090c08afb 100644
--- a/src/legacy/core_plugins/telemetry/server/plugin.ts
+++ b/src/legacy/core_plugins/telemetry/server/plugin.ts
@@ -19,8 +19,7 @@
import { CoreSetup, PluginInitializerContext } from 'src/core/server';
import { registerRoutes } from './routes';
-import { telemetryCollectionManager } from './collection_manager';
-import { getStats } from './telemetry_collection';
+import { registerCollection } from './telemetry_collection';
export class TelemetryPlugin {
private readonly currentKibanaVersion: string;
@@ -29,9 +28,9 @@ export class TelemetryPlugin {
this.currentKibanaVersion = initializerContext.env.packageInfo.version;
}
- public async setup(core: CoreSetup) {
+ public setup(core: CoreSetup) {
const currentKibanaVersion = this.currentKibanaVersion;
- telemetryCollectionManager.setStatsGetter(getStats, 'local');
+ registerCollection();
registerRoutes({ core, currentKibanaVersion });
}
}
diff --git a/src/legacy/core_plugins/telemetry/server/routes/index.ts b/src/legacy/core_plugins/telemetry/server/routes/index.ts
index 549b3ef6068ec4..66a7b2c97f3aeb 100644
--- a/src/legacy/core_plugins/telemetry/server/routes/index.ts
+++ b/src/legacy/core_plugins/telemetry/server/routes/index.ts
@@ -18,8 +18,9 @@
*/
import { CoreSetup } from 'src/core/server';
-import { registerOptInRoutes } from './opt_in';
-import { registerTelemetryDataRoutes } from './telemetry_stats';
+import { registerTelemetryOptInRoutes } from './telemetry_opt_in';
+import { registerTelemetryUsageStatsRoutes } from './telemetry_usage_stats';
+import { registerTelemetryOptInStatsRoutes } from './telemetry_opt_in_stats';
interface RegisterRoutesParams {
core: CoreSetup;
@@ -27,6 +28,7 @@ interface RegisterRoutesParams {
}
export function registerRoutes({ core, currentKibanaVersion }: RegisterRoutesParams) {
- registerTelemetryDataRoutes(core);
- registerOptInRoutes({ core, currentKibanaVersion });
+ registerTelemetryOptInRoutes({ core, currentKibanaVersion });
+ registerTelemetryUsageStatsRoutes(core);
+ registerTelemetryOptInStatsRoutes(core);
}
diff --git a/src/legacy/core_plugins/telemetry/server/routes/opt_in.ts b/src/legacy/core_plugins/telemetry/server/routes/opt_in.ts
deleted file mode 100644
index 3a7194890b5700..00000000000000
--- a/src/legacy/core_plugins/telemetry/server/routes/opt_in.ts
+++ /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 Joi from 'joi';
-import { boomify } from 'boom';
-import { CoreSetup } from 'src/core/server';
-
-interface RegisterOptInRoutesParams {
- core: CoreSetup;
- currentKibanaVersion: string;
-}
-
-export interface SavedObjectAttributes {
- enabled?: boolean;
- lastVersionChecked: string;
-}
-
-export function registerOptInRoutes({ core, currentKibanaVersion }: RegisterOptInRoutesParams) {
- const { server } = core.http as any;
-
- server.route({
- method: 'POST',
- path: '/api/telemetry/v2/optIn',
- options: {
- validate: {
- payload: Joi.object({
- enabled: Joi.bool().required(),
- }),
- },
- },
- handler: async (req: any, h: any) => {
- const savedObjectsClient = req.getSavedObjectsClient();
- const savedObject: SavedObjectAttributes = {
- enabled: req.payload.enabled,
- lastVersionChecked: currentKibanaVersion,
- };
- const options = {
- id: 'telemetry',
- overwrite: true,
- };
- try {
- await savedObjectsClient.create('telemetry', savedObject, options);
- } catch (err) {
- return boomify(err);
- }
- return h.response({}).code(200);
- },
- });
-}
diff --git a/src/legacy/core_plugins/telemetry/server/routes/telemetry_opt_in.ts b/src/legacy/core_plugins/telemetry/server/routes/telemetry_opt_in.ts
new file mode 100644
index 00000000000000..596c5c17c353e9
--- /dev/null
+++ b/src/legacy/core_plugins/telemetry/server/routes/telemetry_opt_in.ts
@@ -0,0 +1,97 @@
+/*
+ * 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';
+import moment from 'moment';
+import { boomify } from 'boom';
+import { CoreSetup } from 'src/core/server';
+import { getTelemetryAllowChangingOptInStatus } from '../telemetry_config';
+import { sendTelemetryOptInStatus } from './telemetry_opt_in_stats';
+
+import {
+ TelemetrySavedObjectAttributes,
+ updateTelemetrySavedObject,
+} from '../telemetry_repository';
+
+interface RegisterOptInRoutesParams {
+ core: CoreSetup;
+ currentKibanaVersion: string;
+}
+
+export function registerTelemetryOptInRoutes({
+ core,
+ currentKibanaVersion,
+}: RegisterOptInRoutesParams) {
+ const { server } = core.http as any;
+
+ server.route({
+ method: 'POST',
+ path: '/api/telemetry/v2/optIn',
+ options: {
+ validate: {
+ payload: Joi.object({
+ enabled: Joi.bool().required(),
+ }),
+ },
+ },
+ handler: async (req: any, h: any) => {
+ try {
+ const newOptInStatus = req.payload.enabled;
+ const attributes: TelemetrySavedObjectAttributes = {
+ enabled: newOptInStatus,
+ lastVersionChecked: currentKibanaVersion,
+ };
+ const config = req.server.config();
+ const savedObjectsClient = req.getSavedObjectsClient();
+ const configTelemetryAllowChangingOptInStatus = config.get(
+ 'telemetry.allowChangingOptInStatus'
+ );
+
+ const allowChangingOptInStatus = getTelemetryAllowChangingOptInStatus({
+ telemetrySavedObject: savedObjectsClient,
+ configTelemetryAllowChangingOptInStatus,
+ });
+ if (!allowChangingOptInStatus) {
+ return h.response({ error: 'Not allowed to change Opt-in Status.' }).code(400);
+ }
+
+ const sendUsageFrom = config.get('telemetry.sendUsageFrom');
+ if (sendUsageFrom === 'server') {
+ const optInStatusUrl = config.get('telemetry.optInStatusUrl');
+ await sendTelemetryOptInStatus(
+ { optInStatusUrl, newOptInStatus },
+ {
+ start: moment()
+ .subtract(20, 'minutes')
+ .toISOString(),
+ end: moment().toISOString(),
+ server: req.server,
+ unencrypted: false,
+ }
+ );
+ }
+
+ await updateTelemetrySavedObject(savedObjectsClient, attributes);
+ return h.response({}).code(200);
+ } catch (err) {
+ return boomify(err);
+ }
+ },
+ });
+}
diff --git a/src/legacy/core_plugins/telemetry/server/routes/telemetry_opt_in_stats.ts b/src/legacy/core_plugins/telemetry/server/routes/telemetry_opt_in_stats.ts
new file mode 100644
index 00000000000000..d3bf6dbb77d7a1
--- /dev/null
+++ b/src/legacy/core_plugins/telemetry/server/routes/telemetry_opt_in_stats.ts
@@ -0,0 +1,87 @@
+/*
+ * 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.
+ */
+
+// @ts-ignore
+import fetch from 'node-fetch';
+import Joi from 'joi';
+import moment from 'moment';
+import { CoreSetup } from 'src/core/server';
+import { telemetryCollectionManager, StatsGetterConfig } from '../collection_manager';
+
+interface SendTelemetryOptInStatusConfig {
+ optInStatusUrl: string;
+ newOptInStatus: boolean;
+}
+
+export async function sendTelemetryOptInStatus(
+ config: SendTelemetryOptInStatusConfig,
+ statsGetterConfig: StatsGetterConfig
+) {
+ const { optInStatusUrl, newOptInStatus } = config;
+ const optInStatus = await telemetryCollectionManager.getOptInStats(
+ newOptInStatus,
+ statsGetterConfig
+ );
+
+ await fetch(optInStatusUrl, {
+ method: 'post',
+ body: optInStatus,
+ });
+}
+
+export function registerTelemetryOptInStatsRoutes(core: CoreSetup) {
+ const { server } = core.http as any;
+
+ server.route({
+ method: 'POST',
+ path: '/api/telemetry/v2/clusters/_opt_in_stats',
+ options: {
+ validate: {
+ payload: Joi.object({
+ enabled: Joi.bool().required(),
+ unencrypted: Joi.bool().default(true),
+ }),
+ },
+ },
+ handler: async (req: any, h: any) => {
+ try {
+ const newOptInStatus = req.payload.enabled;
+ const unencrypted = req.payload.unencrypted;
+ const statsGetterConfig = {
+ start: moment()
+ .subtract(20, 'minutes')
+ .toISOString(),
+ end: moment().toISOString(),
+ server: req.server,
+ req,
+ unencrypted,
+ };
+
+ const optInStatus = await telemetryCollectionManager.getOptInStats(
+ newOptInStatus,
+ statsGetterConfig
+ );
+
+ return h.response(optInStatus).code(200);
+ } catch (err) {
+ return h.response([]).code(200);
+ }
+ },
+ });
+}
diff --git a/src/legacy/core_plugins/telemetry/server/routes/telemetry_stats.ts b/src/legacy/core_plugins/telemetry/server/routes/telemetry_usage_stats.ts
similarity index 80%
rename from src/legacy/core_plugins/telemetry/server/routes/telemetry_stats.ts
rename to src/legacy/core_plugins/telemetry/server/routes/telemetry_usage_stats.ts
index 8a91d24b34ed21..c14314ca4da241 100644
--- a/src/legacy/core_plugins/telemetry/server/routes/telemetry_stats.ts
+++ b/src/legacy/core_plugins/telemetry/server/routes/telemetry_usage_stats.ts
@@ -20,10 +20,9 @@
import Joi from 'joi';
import { boomify } from 'boom';
import { CoreSetup } from 'src/core/server';
-import { encryptTelemetry } from '../collectors';
import { telemetryCollectionManager } from '../collection_manager';
-export function registerTelemetryDataRoutes(core: CoreSetup) {
+export function registerTelemetryUsageStatsRoutes(core: CoreSetup) {
const { server } = core.http as any;
server.route({
@@ -45,17 +44,17 @@ export function registerTelemetryDataRoutes(core: CoreSetup) {
const start = req.payload.timeRange.min;
const end = req.payload.timeRange.max;
const unencrypted = req.payload.unencrypted;
- const isDev = config.get('env.dev');
try {
- const { getStats, title } = telemetryCollectionManager.getStatsGetter();
- server.log(['debug', 'telemetry'], `Using Stats Getter: ${title}`);
-
- const usageData = await getStats(req, config, start, end, unencrypted);
-
- if (unencrypted) return usageData;
- return encryptTelemetry(usageData, isDev);
+ return await telemetryCollectionManager.getStats({
+ unencrypted,
+ server,
+ req,
+ start,
+ end,
+ });
} catch (err) {
+ const isDev = config.get('env.dev');
if (isDev) {
// don't ignore errors when running in dev mode
return boomify(err, { statusCode: err.status || 500 });
diff --git a/src/legacy/core_plugins/telemetry/server/telemetry_collection/__tests__/get_cluster_stats.js b/src/legacy/core_plugins/telemetry/server/telemetry_collection/__tests__/get_cluster_stats.js
index 9ca609cd88778c..d60b330db7b5b5 100644
--- a/src/legacy/core_plugins/telemetry/server/telemetry_collection/__tests__/get_cluster_stats.js
+++ b/src/legacy/core_plugins/telemetry/server/telemetry_collection/__tests__/get_cluster_stats.js
@@ -35,9 +35,9 @@ export function mockGetClusterStats(callCluster, clusterStats, req) {
.returns(clusterStats);
}
-describe('get_cluster_stats', () => {
+describe.skip('get_cluster_stats', () => {
- it('uses callCluster to get cluster.stats API', () => {
+ it('uses callCluster to get cluster.stats API', async () => {
const callCluster = sinon.stub();
const response = Promise.resolve({});
diff --git a/src/legacy/core_plugins/telemetry/server/telemetry_collection/__tests__/get_local_stats.js b/src/legacy/core_plugins/telemetry/server/telemetry_collection/__tests__/get_local_stats.js
index d0de9cc365a712..4cbdf18df4a744 100644
--- a/src/legacy/core_plugins/telemetry/server/telemetry_collection/__tests__/get_local_stats.js
+++ b/src/legacy/core_plugins/telemetry/server/telemetry_collection/__tests__/get_local_stats.js
@@ -26,7 +26,6 @@ import { mockGetClusterStats } from './get_cluster_stats';
import { omit } from 'lodash';
import {
getLocalStats,
- getLocalStatsWithCaller,
handleLocalStats,
} from '../get_local_stats';
@@ -153,7 +152,7 @@ describe('get_local_stats', () => {
});
});
- describe('getLocalStatsWithCaller', () => {
+ describe.skip('getLocalStats', () => {
it('returns expected object without xpack data when X-Pack fails to respond', async () => {
const callClusterUsageFailed = sinon.stub();
@@ -162,8 +161,10 @@ describe('get_local_stats', () => {
Promise.resolve(clusterInfo),
Promise.resolve(clusterStats),
);
-
- const result = await getLocalStatsWithCaller(getMockServer(), callClusterUsageFailed);
+ const result = await getLocalStats({
+ server: getMockServer(),
+ callCluster: callClusterUsageFailed,
+ });
expect(result.cluster_uuid).to.eql(combinedStatsResult.cluster_uuid);
expect(result.cluster_name).to.eql(combinedStatsResult.cluster_name);
expect(result.cluster_stats).to.eql(combinedStatsResult.cluster_stats);
@@ -184,51 +185,13 @@ describe('get_local_stats', () => {
Promise.resolve(clusterStats),
);
- const result = await getLocalStatsWithCaller(getMockServer(callCluster, kibana), callCluster);
+ const result = await getLocalStats({
+ server: getMockServer(callCluster, kibana),
+ callCluster,
+ });
+
expect(result.stack_stats.xpack).to.eql(combinedStatsResult.stack_stats.xpack);
expect(result.stack_stats.kibana).to.eql(combinedStatsResult.stack_stats.kibana);
});
});
-
- describe('getLocalStats', () => {
- it('uses callWithInternalUser from data cluster', async () => {
- const getCluster = sinon.stub();
- const req = { server: getMockServer(getCluster) };
- const callWithInternalUser = sinon.stub();
-
- getCluster.withArgs('data').returns({ callWithInternalUser });
-
- mockGetLocalStats(
- callWithInternalUser,
- Promise.resolve(clusterInfo),
- Promise.resolve(clusterStats),
- );
-
- const result = await getLocalStats(req, { useInternalUser: true });
- expect(result.cluster_uuid).to.eql(combinedStatsResult.cluster_uuid);
- expect(result.cluster_name).to.eql(combinedStatsResult.cluster_name);
- expect(result.version).to.eql(combinedStatsResult.version);
- expect(result.cluster_stats).to.eql(combinedStatsResult.cluster_stats);
- });
- it('uses callWithRequest from data cluster', async () => {
- const getCluster = sinon.stub();
- const req = { server: getMockServer(getCluster) };
- const callWithRequest = sinon.stub();
-
- getCluster.withArgs('data').returns({ callWithRequest });
-
- mockGetLocalStats(
- callWithRequest,
- Promise.resolve(clusterInfo),
- Promise.resolve(clusterStats),
- req
- );
-
- const result = await getLocalStats(req, { useInternalUser: false });
- expect(result.cluster_uuid).to.eql(combinedStatsResult.cluster_uuid);
- expect(result.cluster_name).to.eql(combinedStatsResult.cluster_name);
- expect(result.version).to.eql(combinedStatsResult.version);
- expect(result.cluster_stats).to.eql(combinedStatsResult.cluster_stats);
- });
- });
});
diff --git a/src/legacy/core_plugins/telemetry/server/telemetry_collection/get_cluster_stats.js b/src/legacy/core_plugins/telemetry/server/telemetry_collection/get_cluster_stats.ts
similarity index 65%
rename from src/legacy/core_plugins/telemetry/server/telemetry_collection/get_cluster_stats.js
rename to src/legacy/core_plugins/telemetry/server/telemetry_collection/get_cluster_stats.ts
index a840c39812e2c8..4abd95f0cf66db 100644
--- a/src/legacy/core_plugins/telemetry/server/telemetry_collection/get_cluster_stats.js
+++ b/src/legacy/core_plugins/telemetry/server/telemetry_collection/get_cluster_stats.ts
@@ -17,18 +17,24 @@
* under the License.
*/
+import { CallCluster } from 'src/legacy/core_plugins/elasticsearch';
import { TIMEOUT } from './constants';
-
+import { ClusterDetailsGetter } from '../collection_manager';
/**
* Get the cluster stats from the connected cluster.
*
* This is the equivalent to GET /_cluster/stats?timeout=30s.
- *
- * @param {function} callCluster The callWithInternalUser handler (exposed for testing)
- * @return {Promise} The response from Elasticsearch equivalent to GET /_cluster/stats.
*/
-export function getClusterStats(callCluster) {
- return callCluster('cluster.stats', {
- timeout: TIMEOUT
+export async function getClusterStats(callCluster: CallCluster) {
+ return await callCluster('cluster.stats', {
+ timeout: TIMEOUT,
});
}
+
+/**
+ * Get the cluster uuids from the connected cluster.
+ */
+export const getClusterUuids: ClusterDetailsGetter = async ({ callCluster }) => {
+ const result = await getClusterStats(callCluster);
+ return [{ clusterUuid: result.cluster_uuid }];
+};
diff --git a/src/legacy/core_plugins/telemetry/server/telemetry_collection/get_local_stats.js b/src/legacy/core_plugins/telemetry/server/telemetry_collection/get_local_stats.ts
similarity index 63%
rename from src/legacy/core_plugins/telemetry/server/telemetry_collection/get_local_stats.js
rename to src/legacy/core_plugins/telemetry/server/telemetry_collection/get_local_stats.ts
index 67fc721306c213..e11c6b1277d5bf 100644
--- a/src/legacy/core_plugins/telemetry/server/telemetry_collection/get_local_stats.js
+++ b/src/legacy/core_plugins/telemetry/server/telemetry_collection/get_local_stats.ts
@@ -18,9 +18,12 @@
*/
import { get, omit } from 'lodash';
+// @ts-ignore
import { getClusterInfo } from './get_cluster_info';
import { getClusterStats } from './get_cluster_stats';
+// @ts-ignore
import { getKibana, handleKibanaStats } from './get_kibana';
+import { StatsGetter } from '../collection_manager';
/**
* Handle the separate local calls by combining them into a single object response that looks like the
@@ -30,9 +33,9 @@ import { getKibana, handleKibanaStats } from './get_kibana';
* @param {Object} clusterStats Cluster stats (GET /_cluster/stats)
* @return {Object} A combined object containing the different responses.
*/
-export function handleLocalStats(server, clusterInfo, clusterStats, kibana) {
+export function handleLocalStats(server: any, clusterInfo: any, clusterStats: any, kibana: any) {
return {
- timestamp: (new Date()).toISOString(),
+ timestamp: new Date().toISOString(),
cluster_uuid: get(clusterInfo, 'cluster_uuid'),
cluster_name: get(clusterInfo, 'cluster_name'),
version: get(clusterInfo, 'version.number'),
@@ -40,7 +43,7 @@ export function handleLocalStats(server, clusterInfo, clusterStats, kibana) {
collection: 'local',
stack_stats: {
kibana: handleKibanaStats(server, kibana),
- }
+ },
};
}
@@ -51,28 +54,16 @@ export function handleLocalStats(server, clusterInfo, clusterStats, kibana) {
* @param {function} callCluster The callWithInternalUser handler (exposed for testing)
* @return {Promise} The object containing the current Elasticsearch cluster's telemetry.
*/
-export async function getLocalStatsWithCaller(server, callCluster) {
- const [ clusterInfo, clusterStats, kibana ] = await Promise.all([
- getClusterInfo(callCluster), // cluster info
- getClusterStats(callCluster), // cluster stats (not to be confused with cluster _state_)
- getKibana(server, callCluster),
- ]);
-
- return handleLocalStats(server, clusterInfo, clusterStats, kibana);
-}
-
-
-/**
- * Get statistics for the connected Elasticsearch cluster.
- *
- * @param {Object} req The incoming request
- * @param {Boolean} useRequestUser callWithRequest, otherwise callWithInternalUser
- * @return {Promise} The cluster object containing telemetry.
- */
-export async function getLocalStats(req, { useInternalUser = false } = {}) {
- const { server } = req;
- const { callWithRequest, callWithInternalUser } = server.plugins.elasticsearch.getCluster('data');
- const callCluster = useInternalUser ? callWithInternalUser : (...args) => callWithRequest(req, ...args);
-
- return await getLocalStatsWithCaller(server, callCluster);
-}
+export const getLocalStats: StatsGetter = async (clustersDetails, config) => {
+ const { server, callCluster } = config;
+ return await Promise.all(
+ clustersDetails.map(async clustersDetail => {
+ const [clusterInfo, clusterStats, kibana] = await Promise.all([
+ getClusterInfo(callCluster), // cluster info
+ getClusterStats(callCluster), // cluster stats (not to be confused with cluster _state_)
+ getKibana(server, callCluster),
+ ]);
+ return handleLocalStats(server, clusterInfo, clusterStats, kibana);
+ })
+ );
+};
diff --git a/src/legacy/core_plugins/telemetry/server/telemetry_collection/get_stats.ts b/src/legacy/core_plugins/telemetry/server/telemetry_collection/get_stats.ts
deleted file mode 100644
index 024272e0f805ca..00000000000000
--- a/src/legacy/core_plugins/telemetry/server/telemetry_collection/get_stats.ts
+++ /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.
- */
-
-// @ts-ignore
-import { getLocalStats } from './get_local_stats';
-
-/**
- * Get the telemetry data.
- *
- * @param {Object} req The incoming request.
- * @param {Object} config Kibana config.
- * @param {String} start The start time of the request (likely 20m ago).
- * @param {String} end The end time of the request.
- * @param {Boolean} unencrypted Is the request payload going to be unencrypted.
- * @return {Promise} An array of telemetry objects.
- */
-export async function getStats(
- req: any,
- config: any,
- start: string,
- end: string,
- unencrypted: boolean
-) {
- return [
- await getLocalStats(req, {
- useInternalUser: !unencrypted,
- }),
- ];
-}
diff --git a/src/legacy/core_plugins/telemetry/server/telemetry_collection/index.ts b/src/legacy/core_plugins/telemetry/server/telemetry_collection/index.ts
index f33727d82f44ce..7f228dbc5e6f69 100644
--- a/src/legacy/core_plugins/telemetry/server/telemetry_collection/index.ts
+++ b/src/legacy/core_plugins/telemetry/server/telemetry_collection/index.ts
@@ -19,6 +19,5 @@
// @ts-ignore
export { getLocalStats } from './get_local_stats';
-
-// @ts-ignore
-export { getStats } from './get_stats';
+export { getClusterUuids } from './get_cluster_stats';
+export { registerCollection } from './register_collection';
diff --git a/src/legacy/core_plugins/telemetry/server/telemetry_collection/register_collection.ts b/src/legacy/core_plugins/telemetry/server/telemetry_collection/register_collection.ts
new file mode 100644
index 00000000000000..faf8e9de79194b
--- /dev/null
+++ b/src/legacy/core_plugins/telemetry/server/telemetry_collection/register_collection.ts
@@ -0,0 +1,51 @@
+/*
+ * 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.
+ */
+
+/*
+ * 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 { telemetryCollectionManager } from '../collection_manager';
+import { getLocalStats } from './get_local_stats';
+import { getClusterUuids } from './get_cluster_stats';
+
+export function registerCollection() {
+ telemetryCollectionManager.setCollection({
+ esCluster: 'data',
+ title: 'local',
+ priority: 0,
+ statsGetter: getLocalStats,
+ clusterDetailsGetter: getClusterUuids,
+ });
+}
diff --git a/src/legacy/core_plugins/telemetry/server/telemetry_config/get_telemetry_allow_changing_opt_in_status.ts b/src/legacy/core_plugins/telemetry/server/telemetry_config/get_telemetry_allow_changing_opt_in_status.ts
new file mode 100644
index 00000000000000..9fa4fbc5e0227c
--- /dev/null
+++ b/src/legacy/core_plugins/telemetry/server/telemetry_config/get_telemetry_allow_changing_opt_in_status.ts
@@ -0,0 +1,39 @@
+/*
+ * 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 { TelemetrySavedObject } from '../telemetry_repository/get_telemetry_saved_object';
+
+interface GetTelemetryAllowChangingOptInStatus {
+ configTelemetryAllowChangingOptInStatus: boolean;
+ telemetrySavedObject: TelemetrySavedObject;
+}
+
+export function getTelemetryAllowChangingOptInStatus({
+ telemetrySavedObject,
+ configTelemetryAllowChangingOptInStatus,
+}: GetTelemetryAllowChangingOptInStatus) {
+ if (!telemetrySavedObject) {
+ return configTelemetryAllowChangingOptInStatus;
+ }
+
+ if (typeof telemetrySavedObject.telemetryAllowChangingOptInStatus === 'undefined') {
+ return configTelemetryAllowChangingOptInStatus;
+ }
+
+ return telemetrySavedObject.telemetryAllowChangingOptInStatus;
+}
diff --git a/src/legacy/core_plugins/telemetry/server/get_telemetry_opt_in.test.ts b/src/legacy/core_plugins/telemetry/server/telemetry_config/get_telemetry_opt_in.test.ts
similarity index 63%
rename from src/legacy/core_plugins/telemetry/server/get_telemetry_opt_in.test.ts
rename to src/legacy/core_plugins/telemetry/server/telemetry_config/get_telemetry_opt_in.test.ts
index 67ad3aaae427d9..efc4a020e0ff05 100644
--- a/src/legacy/core_plugins/telemetry/server/get_telemetry_opt_in.test.ts
+++ b/src/legacy/core_plugins/telemetry/server/telemetry_config/get_telemetry_opt_in.test.ts
@@ -18,72 +18,47 @@
*/
import { getTelemetryOptIn } from './get_telemetry_opt_in';
+import { TelemetrySavedObject } from '../telemetry_repository/get_telemetry_saved_object';
-describe('get_telemetry_opt_in', () => {
- it('returns false when request path is not /app*', async () => {
- const params = getCallGetTelemetryOptInParams({
- requestPath: '/foo/bar',
- });
-
- const result = await callGetTelemetryOptIn(params);
-
- expect(result).toBe(false);
- });
-
- it('returns null when saved object not found', async () => {
+describe('getTelemetryOptIn', () => {
+ it('returns null when saved object not found', () => {
const params = getCallGetTelemetryOptInParams({
savedObjectNotFound: true,
});
- const result = await callGetTelemetryOptIn(params);
+ const result = callGetTelemetryOptIn(params);
expect(result).toBe(null);
});
- it('returns false when saved object forbidden', async () => {
+ it('returns false when saved object forbidden', () => {
const params = getCallGetTelemetryOptInParams({
savedObjectForbidden: true,
});
- const result = await callGetTelemetryOptIn(params);
+ const result = callGetTelemetryOptIn(params);
expect(result).toBe(false);
});
- it('throws an error on unexpected saved object error', async () => {
- const params = getCallGetTelemetryOptInParams({
- savedObjectOtherError: true,
- });
-
- let threw = false;
- try {
- await callGetTelemetryOptIn(params);
- } catch (err) {
- threw = true;
- expect(err.message).toBe(SavedObjectOtherErrorMessage);
- }
-
- expect(threw).toBe(true);
- });
-
- it('returns null if enabled is null or undefined', async () => {
+ it('returns null if enabled is null or undefined', () => {
for (const enabled of [null, undefined]) {
const params = getCallGetTelemetryOptInParams({
enabled,
});
- const result = await callGetTelemetryOptIn(params);
+ const result = callGetTelemetryOptIn(params);
expect(result).toBe(null);
}
});
- it('returns true when enabled is true', async () => {
+ it('returns true when enabled is true', () => {
const params = getCallGetTelemetryOptInParams({
enabled: true,
});
- const result = await callGetTelemetryOptIn(params);
+ const result = callGetTelemetryOptIn(params);
expect(result).toBe(true);
});
@@ -146,24 +121,24 @@ describe('get_telemetry_opt_in', () => {
});
interface CallGetTelemetryOptInParams {
- requestPath: string;
savedObjectNotFound: boolean;
savedObjectForbidden: boolean;
- savedObjectOtherError: boolean;
- enabled: boolean | null | undefined;
lastVersionChecked?: any; // should be a string, but test with non-strings
currentKibanaVersion: string;
result?: boolean | null;
+ enabled: boolean | null | undefined;
+ configTelemetryOptIn: boolean | null;
+ allowChangingOptInStatus: boolean;
}
const DefaultParams = {
- requestPath: '/app/something',
savedObjectNotFound: false,
savedObjectForbidden: false,
- savedObjectOtherError: false,
enabled: true,
lastVersionChecked: '8.0.0',
currentKibanaVersion: '8.0.0',
+ configTelemetryOptIn: null,
+ allowChangingOptInStatus: true,
};
function getCallGetTelemetryOptInParams(
@@ -172,43 +147,28 @@ function getCallGetTelemetryOptInParams(
return { ...DefaultParams, ...overrides };
}
-async function callGetTelemetryOptIn(params: CallGetTelemetryOptInParams): Promise {
- const { currentKibanaVersion } = params;
- const request = getMockRequest(params);
- return await getTelemetryOptIn({ request, currentKibanaVersion });
-}
-
-function getMockRequest(params: CallGetTelemetryOptInParams): any {
- return {
- path: params.requestPath,
- getSavedObjectsClient() {
- return getMockSavedObjectsClient(params);
- },
- };
+function callGetTelemetryOptIn(params: CallGetTelemetryOptInParams) {
+ const { currentKibanaVersion, configTelemetryOptIn, allowChangingOptInStatus } = params;
+ const telemetrySavedObject = getMockTelemetrySavedObject(params);
+ return getTelemetryOptIn({
+ currentKibanaVersion,
+ telemetrySavedObject,
+ allowChangingOptInStatus,
+ configTelemetryOptIn,
+ });
}
-const SavedObjectNotFoundMessage = 'savedObjectNotFound';
-const SavedObjectForbiddenMessage = 'savedObjectForbidden';
-const SavedObjectOtherErrorMessage = 'savedObjectOtherError';
+function getMockTelemetrySavedObject(params: CallGetTelemetryOptInParams): TelemetrySavedObject {
+ const { savedObjectNotFound, savedObjectForbidden } = params;
+ if (savedObjectForbidden) {
+ return false;
+ }
+ if (savedObjectNotFound) {
+ return null;
+ }
-function getMockSavedObjectsClient(params: CallGetTelemetryOptInParams) {
return {
- async get(type: string, id: string) {
- if (params.savedObjectNotFound) throw new Error(SavedObjectNotFoundMessage);
- if (params.savedObjectForbidden) throw new Error(SavedObjectForbiddenMessage);
- if (params.savedObjectOtherError) throw new Error(SavedObjectOtherErrorMessage);
-
- const enabled = params.enabled;
- const lastVersionChecked = params.lastVersionChecked;
- return { attributes: { enabled, lastVersionChecked } };
- },
- errors: {
- isNotFoundError(error: any) {
- return error.message === SavedObjectNotFoundMessage;
- },
- isForbiddenError(error: any) {
- return error.message === SavedObjectForbiddenMessage;
- },
- },
+ enabled: params.enabled,
+ lastVersionChecked: params.lastVersionChecked,
};
}
diff --git a/src/legacy/core_plugins/telemetry/server/get_telemetry_opt_in.ts b/src/legacy/core_plugins/telemetry/server/telemetry_config/get_telemetry_opt_in.ts
similarity index 58%
rename from src/legacy/core_plugins/telemetry/server/get_telemetry_opt_in.ts
rename to src/legacy/core_plugins/telemetry/server/telemetry_config/get_telemetry_opt_in.ts
index c8bd4a4b6dfbd4..d83ffdf69b5767 100644
--- a/src/legacy/core_plugins/telemetry/server/get_telemetry_opt_in.ts
+++ b/src/legacy/core_plugins/telemetry/server/telemetry_config/get_telemetry_opt_in.ts
@@ -18,67 +18,51 @@
*/
import semver from 'semver';
+import { TelemetrySavedObject } from '../telemetry_repository/get_telemetry_saved_object';
-import { SavedObjectAttributes } from './routes/opt_in';
-
-interface GetTelemetryOptIn {
- request: any;
+interface GetTelemetryOptInConfig {
+ telemetrySavedObject: TelemetrySavedObject;
currentKibanaVersion: string;
+ allowChangingOptInStatus: boolean;
+ configTelemetryOptIn: boolean | null;
}
-// Returns whether telemetry has been opt'ed into or not.
-// Returns null not set, meaning Kibana should prompt in the UI.
-export async function getTelemetryOptIn({
- request,
+type GetTelemetryOptIn = (config: GetTelemetryOptInConfig) => null | boolean;
+
+export const getTelemetryOptIn: GetTelemetryOptIn = ({
+ telemetrySavedObject,
currentKibanaVersion,
-}: GetTelemetryOptIn): Promise {
- const isRequestingApplication = request.path.startsWith('/app');
+ allowChangingOptInStatus,
+ configTelemetryOptIn,
+}) => {
+ if (typeof configTelemetryOptIn === 'boolean' && !allowChangingOptInStatus) {
+ return configTelemetryOptIn;
+ }
- // Prevent interstitial screens (such as the space selector) from prompting for telemetry
- if (!isRequestingApplication) {
+ if (telemetrySavedObject === false) {
return false;
}
- const savedObjectsClient = request.getSavedObjectsClient();
-
- let savedObject;
- try {
- savedObject = await savedObjectsClient.get('telemetry', 'telemetry');
- } catch (error) {
- if (savedObjectsClient.errors.isNotFoundError(error)) {
- return null;
- }
-
- // if we aren't allowed to get the telemetry document, we can assume that we won't
- // be able to opt into telemetry either, so we're returning `false` here instead of null
- if (savedObjectsClient.errors.isForbiddenError(error)) {
- return false;
- }
-
- throw error;
+ if (telemetrySavedObject === null || typeof telemetrySavedObject.enabled !== 'boolean') {
+ return configTelemetryOptIn;
}
- const { attributes }: { attributes: SavedObjectAttributes } = savedObject;
-
- // if enabled is already null, return null
- if (attributes.enabled == null) return null;
-
- const enabled = !!attributes.enabled;
+ const savedOptIn = telemetrySavedObject.enabled;
// if enabled is true, return it
- if (enabled === true) return enabled;
+ if (savedOptIn === true) return savedOptIn;
// Additional check if they've already opted out (enabled: false):
// - if the Kibana version has changed by at least a minor version,
// return null to re-prompt.
- const lastKibanaVersion = attributes.lastVersionChecked;
+ const lastKibanaVersion = telemetrySavedObject.lastVersionChecked;
// if the last kibana version isn't set, or is somehow not a string, return null
if (typeof lastKibanaVersion !== 'string') return null;
// if version hasn't changed, just return enabled value
- if (lastKibanaVersion === currentKibanaVersion) return enabled;
+ if (lastKibanaVersion === currentKibanaVersion) return savedOptIn;
const lastSemver = parseSemver(lastKibanaVersion);
const currentSemver = parseSemver(currentKibanaVersion);
@@ -93,8 +77,8 @@ export async function getTelemetryOptIn({
}
// current version X.Y is not greater than last version X.Y, return enabled
- return enabled;
-}
+ return savedOptIn;
+};
function parseSemver(version: string): semver.SemVer | null {
// semver functions both return nulls AND throw exceptions: "it depends!"
diff --git a/src/legacy/core_plugins/telemetry/server/telemetry_config/get_telemetry_send_usage_from.test.ts b/src/legacy/core_plugins/telemetry/server/telemetry_config/get_telemetry_send_usage_from.test.ts
new file mode 100644
index 00000000000000..69868a97a931d3
--- /dev/null
+++ b/src/legacy/core_plugins/telemetry/server/telemetry_config/get_telemetry_send_usage_from.test.ts
@@ -0,0 +1,85 @@
+/*
+ * 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 { getTelemetrySendUsageFrom } from './get_telemetry_send_usage_from';
+import { TelemetrySavedObject } from '../telemetry_repository/get_telemetry_saved_object';
+
+describe('getTelemetrySendUsageFrom', () => {
+ it('returns kibana.yml config when saved object not found', () => {
+ const params: CallGetTelemetryUsageFetcherParams = {
+ savedObjectNotFound: true,
+ configSendUsageFrom: 'browser',
+ };
+
+ const result = callGetTelemetryUsageFetcher(params);
+
+ expect(result).toBe('browser');
+ });
+
+ it('returns kibana.yml config when saved object forbidden', () => {
+ const params: CallGetTelemetryUsageFetcherParams = {
+ savedObjectForbidden: true,
+ configSendUsageFrom: 'browser',
+ };
+
+ const result = callGetTelemetryUsageFetcher(params);
+
+ expect(result).toBe('browser');
+ });
+
+ it('returns kibana.yml config when saved object sendUsageFrom is undefined', () => {
+ const params: CallGetTelemetryUsageFetcherParams = {
+ savedSendUsagefrom: undefined,
+ configSendUsageFrom: 'server',
+ };
+
+ const result = callGetTelemetryUsageFetcher(params);
+
+ expect(result).toBe('server');
+ });
+});
+
+interface CallGetTelemetryUsageFetcherParams {
+ savedObjectNotFound?: boolean;
+ savedObjectForbidden?: boolean;
+ savedSendUsagefrom?: 'browser' | 'server';
+ configSendUsageFrom: 'browser' | 'server';
+}
+
+function callGetTelemetryUsageFetcher(params: CallGetTelemetryUsageFetcherParams) {
+ const telemetrySavedObject = getMockTelemetrySavedObject(params);
+ const configTelemetrySendUsageFrom = params.configSendUsageFrom;
+ return getTelemetrySendUsageFrom({ configTelemetrySendUsageFrom, telemetrySavedObject });
+}
+
+function getMockTelemetrySavedObject(
+ params: CallGetTelemetryUsageFetcherParams
+): TelemetrySavedObject {
+ const { savedObjectNotFound, savedObjectForbidden } = params;
+ if (savedObjectForbidden) {
+ return false;
+ }
+ if (savedObjectNotFound) {
+ return null;
+ }
+
+ return {
+ sendUsageFrom: params.savedSendUsagefrom,
+ };
+}
diff --git a/src/legacy/core_plugins/telemetry/server/telemetry_config/get_telemetry_send_usage_from.ts b/src/legacy/core_plugins/telemetry/server/telemetry_config/get_telemetry_send_usage_from.ts
new file mode 100644
index 00000000000000..9e4ae14b6097c2
--- /dev/null
+++ b/src/legacy/core_plugins/telemetry/server/telemetry_config/get_telemetry_send_usage_from.ts
@@ -0,0 +1,39 @@
+/*
+ * 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 { TelemetrySavedObject } from '../telemetry_repository/get_telemetry_saved_object';
+
+interface GetTelemetryUsageFetcherConfig {
+ configTelemetrySendUsageFrom: 'browser' | 'server';
+ telemetrySavedObject: TelemetrySavedObject;
+}
+
+export function getTelemetrySendUsageFrom({
+ telemetrySavedObject,
+ configTelemetrySendUsageFrom,
+}: GetTelemetryUsageFetcherConfig) {
+ if (!telemetrySavedObject) {
+ return configTelemetrySendUsageFrom;
+ }
+
+ if (typeof telemetrySavedObject.sendUsageFrom === 'undefined') {
+ return configTelemetrySendUsageFrom;
+ }
+
+ return telemetrySavedObject.sendUsageFrom;
+}
diff --git a/src/legacy/core_plugins/telemetry/server/telemetry_config/index.ts b/src/legacy/core_plugins/telemetry/server/telemetry_config/index.ts
new file mode 100644
index 00000000000000..ab30dac1c36660
--- /dev/null
+++ b/src/legacy/core_plugins/telemetry/server/telemetry_config/index.ts
@@ -0,0 +1,23 @@
+/*
+ * 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 { replaceTelemetryInjectedVars } from './replace_injected_vars';
+export { getTelemetryOptIn } from './get_telemetry_opt_in';
+export { getTelemetrySendUsageFrom } from './get_telemetry_send_usage_from';
+export { getTelemetryAllowChangingOptInStatus } from './get_telemetry_allow_changing_opt_in_status';
diff --git a/src/legacy/core_plugins/telemetry/server/telemetry_config/replace_injected_vars.ts b/src/legacy/core_plugins/telemetry/server/telemetry_config/replace_injected_vars.ts
new file mode 100644
index 00000000000000..90d1f9cfdac65f
--- /dev/null
+++ b/src/legacy/core_plugins/telemetry/server/telemetry_config/replace_injected_vars.ts
@@ -0,0 +1,63 @@
+/*
+ * 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 { getTelemetrySavedObject } from '../telemetry_repository';
+import { getTelemetryOptIn } from './get_telemetry_opt_in';
+import { getTelemetrySendUsageFrom } from './get_telemetry_send_usage_from';
+import { getTelemetryAllowChangingOptInStatus } from './get_telemetry_allow_changing_opt_in_status';
+
+export async function replaceTelemetryInjectedVars(request: any) {
+ const config = request.server.config();
+ const configTelemetrySendUsageFrom = config.get('telemetry.sendUsageFrom');
+ const configTelemetryOptIn = config.get('telemetry.optIn');
+ const configTelemetryAllowChangingOptInStatus = config.get('telemetry.allowChangingOptInStatus');
+ const isRequestingApplication = request.path.startsWith('/app');
+
+ // Prevent interstitial screens (such as the space selector) from prompting for telemetry
+ if (!isRequestingApplication) {
+ return {
+ telemetryOptedIn: false,
+ };
+ }
+
+ const currentKibanaVersion = config.get('pkg.version');
+ const savedObjectsClient = request.getSavedObjectsClient();
+ const telemetrySavedObject = await getTelemetrySavedObject(savedObjectsClient);
+ const allowChangingOptInStatus = getTelemetryAllowChangingOptInStatus({
+ configTelemetryAllowChangingOptInStatus,
+ telemetrySavedObject,
+ });
+
+ const telemetryOptedIn = getTelemetryOptIn({
+ configTelemetryOptIn,
+ allowChangingOptInStatus,
+ telemetrySavedObject,
+ currentKibanaVersion,
+ });
+
+ const telemetrySendUsageFrom = getTelemetrySendUsageFrom({
+ configTelemetrySendUsageFrom,
+ telemetrySavedObject,
+ });
+
+ return {
+ telemetryOptedIn,
+ telemetrySendUsageFrom,
+ };
+}
diff --git a/src/legacy/core_plugins/telemetry/server/telemetry_repository/get_telemetry_saved_object.test.ts b/src/legacy/core_plugins/telemetry/server/telemetry_repository/get_telemetry_saved_object.test.ts
new file mode 100644
index 00000000000000..7cc177878de4d0
--- /dev/null
+++ b/src/legacy/core_plugins/telemetry/server/telemetry_repository/get_telemetry_saved_object.test.ts
@@ -0,0 +1,104 @@
+/*
+ * 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 { getTelemetrySavedObject } from './get_telemetry_saved_object';
+import { SavedObjectsErrorHelpers } from '../../../../../core/server';
+
+describe('getTelemetrySavedObject', () => {
+ it('returns null when saved object not found', async () => {
+ const params = getCallGetTelemetrySavedObjectParams({
+ savedObjectNotFound: true,
+ });
+
+ const result = await callGetTelemetrySavedObject(params);
+
+ expect(result).toBe(null);
+ });
+
+ it('returns false when saved object forbidden', async () => {
+ const params = getCallGetTelemetrySavedObjectParams({
+ savedObjectForbidden: true,
+ });
+
+ const result = await callGetTelemetrySavedObject(params);
+
+ expect(result).toBe(false);
+ });
+
+ it('throws an error on unexpected saved object error', async () => {
+ const params = getCallGetTelemetrySavedObjectParams({
+ savedObjectOtherError: true,
+ });
+
+ let threw = false;
+ try {
+ await callGetTelemetrySavedObject(params);
+ } catch (err) {
+ threw = true;
+ expect(err.message).toBe(SavedObjectOtherErrorMessage);
+ }
+
+ expect(threw).toBe(true);
+ });
+});
+
+interface CallGetTelemetrySavedObjectParams {
+ savedObjectNotFound: boolean;
+ savedObjectForbidden: boolean;
+ savedObjectOtherError: boolean;
+ result?: any;
+}
+
+const DefaultParams = {
+ savedObjectNotFound: false,
+ savedObjectForbidden: false,
+ savedObjectOtherError: false,
+};
+
+function getCallGetTelemetrySavedObjectParams(
+ overrides: Partial
+): CallGetTelemetrySavedObjectParams {
+ return { ...DefaultParams, ...overrides };
+}
+
+async function callGetTelemetrySavedObject(params: CallGetTelemetrySavedObjectParams) {
+ const savedObjectsClient = getMockSavedObjectsClient(params);
+ return await getTelemetrySavedObject(savedObjectsClient);
+}
+
+const SavedObjectForbiddenMessage = 'savedObjectForbidden';
+const SavedObjectOtherErrorMessage = 'savedObjectOtherError';
+
+function getMockSavedObjectsClient(params: CallGetTelemetrySavedObjectParams) {
+ return {
+ async get(type: string, id: string) {
+ if (params.savedObjectNotFound) throw SavedObjectsErrorHelpers.createGenericNotFoundError();
+ if (params.savedObjectForbidden)
+ throw SavedObjectsErrorHelpers.decorateForbiddenError(
+ new Error(SavedObjectForbiddenMessage)
+ );
+ if (params.savedObjectOtherError)
+ throw SavedObjectsErrorHelpers.decorateGeneralError(
+ new Error(SavedObjectOtherErrorMessage)
+ );
+
+ return { attributes: { enabled: null } };
+ },
+ };
+}
diff --git a/src/legacy/core_plugins/telemetry/server/telemetry_repository/get_telemetry_saved_object.ts b/src/legacy/core_plugins/telemetry/server/telemetry_repository/get_telemetry_saved_object.ts
new file mode 100644
index 00000000000000..91965ef201ecb5
--- /dev/null
+++ b/src/legacy/core_plugins/telemetry/server/telemetry_repository/get_telemetry_saved_object.ts
@@ -0,0 +1,43 @@
+/*
+ * 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 { TelemetrySavedObjectAttributes } from './';
+import { SavedObjectsErrorHelpers } from '../../../../../core/server';
+
+export type TelemetrySavedObject = TelemetrySavedObjectAttributes | null | false;
+type GetTelemetrySavedObject = (repository: any) => Promise;
+
+export const getTelemetrySavedObject: GetTelemetrySavedObject = async (repository: any) => {
+ try {
+ const { attributes } = await repository.get('telemetry', 'telemetry');
+ return attributes;
+ } catch (error) {
+ if (SavedObjectsErrorHelpers.isNotFoundError(error)) {
+ return null;
+ }
+
+ // if we aren't allowed to get the telemetry document, we can assume that we won't
+ // be able to opt into telemetry either, so we're returning `false` here instead of null
+ if (SavedObjectsErrorHelpers.isForbiddenError(error)) {
+ return false;
+ }
+
+ throw error;
+ }
+};
diff --git a/src/legacy/core_plugins/telemetry/server/telemetry_repository/index.ts b/src/legacy/core_plugins/telemetry/server/telemetry_repository/index.ts
new file mode 100644
index 00000000000000..f3629abc1620cc
--- /dev/null
+++ b/src/legacy/core_plugins/telemetry/server/telemetry_repository/index.ts
@@ -0,0 +1,29 @@
+/*
+ * 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 { getTelemetrySavedObject, TelemetrySavedObject } from './get_telemetry_saved_object';
+export { updateTelemetrySavedObject } from './update_telemetry_saved_object';
+
+export interface TelemetrySavedObjectAttributes {
+ enabled?: boolean | null;
+ lastVersionChecked?: string;
+ sendUsageFrom?: 'browser' | 'server';
+ lastReported?: number;
+ telemetryAllowChangingOptInStatus?: boolean;
+}
diff --git a/src/legacy/core_plugins/kibana/public/dev_tools/hacks/hide_empty_tools.js b/src/legacy/core_plugins/telemetry/server/telemetry_repository/update_telemetry_saved_object.ts
similarity index 57%
rename from src/legacy/core_plugins/kibana/public/dev_tools/hacks/hide_empty_tools.js
rename to src/legacy/core_plugins/telemetry/server/telemetry_repository/update_telemetry_saved_object.ts
index 4dc55194562e5a..b66e01faaa6bc7 100644
--- a/src/legacy/core_plugins/kibana/public/dev_tools/hacks/hide_empty_tools.js
+++ b/src/legacy/core_plugins/telemetry/server/telemetry_repository/update_telemetry_saved_object.ts
@@ -17,17 +17,22 @@
* under the License.
*/
-import { uiModules } from 'ui/modules';
-import { DevToolsRegistryProvider } from 'ui/registry/dev_tools';
-import { npStart } from 'ui/new_platform';
+import { TelemetrySavedObjectAttributes } from './';
+import { SavedObjectsErrorHelpers } from '../../../../../core/server';
-export function hideEmptyDevTools(Private) {
- const hasTools = !!Private(DevToolsRegistryProvider).length;
- if (!hasTools) {
- npStart.core.chrome.navLinks.update('kibana:dev_tools', {
- hidden: true
- });
+export async function updateTelemetrySavedObject(
+ savedObjectsClient: any,
+ savedObjectAttributes: TelemetrySavedObjectAttributes
+) {
+ try {
+ return await savedObjectsClient.update('telemetry', 'telemetry', savedObjectAttributes);
+ } catch (err) {
+ if (SavedObjectsErrorHelpers.isNotFoundError(err)) {
+ return await savedObjectsClient.create('telemetry', savedObjectAttributes, {
+ id: 'telemetry',
+ overwrite: true,
+ });
+ }
+ throw err;
}
}
-
-uiModules.get('kibana').run(hideEmptyDevTools);
diff --git a/src/legacy/core_plugins/tile_map/public/__tests__/coordinate_maps_visualization.js b/src/legacy/core_plugins/tile_map/public/__tests__/coordinate_maps_visualization.js
index 751f92fe88215b..0e3c4fdd9d3557 100644
--- a/src/legacy/core_plugins/tile_map/public/__tests__/coordinate_maps_visualization.js
+++ b/src/legacy/core_plugins/tile_map/public/__tests__/coordinate_maps_visualization.js
@@ -20,7 +20,7 @@
import expect from '@kbn/expect';
import ngMock from 'ng_mock';
import LogstashIndexPatternStubProvider from 'fixtures/stubbed_logstash_index_pattern';
-import * as visModule from 'ui/vis';
+import { Vis } from 'ui/vis';
import { ImageComparator } from 'test_utils/image_comparator';
import dummyESResponse from './dummy_es_response.json';
import initial from './initial.png';
@@ -65,7 +65,6 @@ let visRegComplete = false;
describe('CoordinateMapsVisualizationTest', function () {
let domNode;
let CoordinateMapsVisualization;
- let Vis;
let indexPattern;
let vis;
let dependencies;
@@ -91,7 +90,6 @@ describe('CoordinateMapsVisualizationTest', function () {
}
- Vis = Private(visModule.VisProvider);
CoordinateMapsVisualization = createTileMapVisualization(dependencies);
indexPattern = Private(LogstashIndexPatternStubProvider);
diff --git a/src/legacy/core_plugins/timelion/public/vis/timelion_request_handler.ts b/src/legacy/core_plugins/timelion/public/vis/timelion_request_handler.ts
index 6239e4027c392a..35dea4a0deb9b2 100644
--- a/src/legacy/core_plugins/timelion/public/vis/timelion_request_handler.ts
+++ b/src/legacy/core_plugins/timelion/public/vis/timelion_request_handler.ts
@@ -22,8 +22,7 @@ import { buildEsQuery, getEsQueryConfig } from '@kbn/es-query';
// @ts-ignore
import { timezoneProvider } from 'ui/vis/lib/timezone';
import { KIBANA_CONTEXT_NAME } from 'src/plugins/expressions/public';
-import { Query } from 'src/legacy/core_plugins/data/public';
-import { TimeRange, esFilters } from 'src/plugins/data/public';
+import { Query, TimeRange, esFilters } from 'src/plugins/data/public';
import { VisParams } from 'ui/vis';
import { i18n } from '@kbn/i18n';
import { TimelionVisualizationDependencies } from '../plugin';
diff --git a/src/legacy/core_plugins/ui_metric/index.ts b/src/legacy/core_plugins/ui_metric/index.ts
index 6c957f23b5c40d..964e3497accba7 100644
--- a/src/legacy/core_plugins/ui_metric/index.ts
+++ b/src/legacy/core_plugins/ui_metric/index.ts
@@ -39,13 +39,13 @@ export default function(kibana: any) {
injectDefaultVars(server: Server) {
const config = server.config();
return {
+ uiMetricEnabled: config.get('ui_metric.enabled'),
debugUiMetric: config.get('ui_metric.debug'),
};
},
mappings: require('./mappings.json'),
hacks: ['plugins/ui_metric/hacks/ui_metric_init'],
},
-
init(server: Legacy.Server) {
registerUiMetricRoute(server);
},
diff --git a/src/legacy/core_plugins/ui_metric/public/hacks/ui_metric_init.ts b/src/legacy/core_plugins/ui_metric/public/hacks/ui_metric_init.ts
index 7aafc82cfe4c63..983434f09922b3 100644
--- a/src/legacy/core_plugins/ui_metric/public/hacks/ui_metric_init.ts
+++ b/src/legacy/core_plugins/ui_metric/public/hacks/ui_metric_init.ts
@@ -20,15 +20,26 @@
// @ts-ignore
import { uiModules } from 'ui/modules';
import chrome from 'ui/chrome';
-import { createAnalyticsReporter, setTelemetryReporter } from '../services/telemetry_analytics';
+import { kfetch } from 'ui/kfetch';
+import {
+ createAnalyticsReporter,
+ setTelemetryReporter,
+ trackUserAgent,
+} from '../services/telemetry_analytics';
+import { isUnauthenticated } from '../../../telemetry/public/services';
function telemetryInit($injector: any) {
- const localStorage = $injector.get('localStorage');
+ const uiMetricEnabled = chrome.getInjected('uiMetricEnabled');
const debug = chrome.getInjected('debugUiMetric');
- const $http = $injector.get('$http');
- const basePath = chrome.getBasePath();
- const uiReporter = createAnalyticsReporter({ localStorage, $http, basePath, debug });
+ if (!uiMetricEnabled || isUnauthenticated()) {
+ return;
+ }
+ const localStorage = $injector.get('localStorage');
+
+ const uiReporter = createAnalyticsReporter({ localStorage, debug, kfetch });
setTelemetryReporter(uiReporter);
+ uiReporter.start();
+ trackUserAgent('kibana');
}
uiModules.get('kibana').run(telemetryInit);
diff --git a/src/legacy/core_plugins/ui_metric/public/index.ts b/src/legacy/core_plugins/ui_metric/public/index.ts
index b1e78b56d05d0a..5c327234b1e7cd 100644
--- a/src/legacy/core_plugins/ui_metric/public/index.ts
+++ b/src/legacy/core_plugins/ui_metric/public/index.ts
@@ -17,5 +17,5 @@
* under the License.
*/
-export { createUiStatsReporter } from './services/telemetry_analytics';
-export { METRIC_TYPE } from '@kbn/analytics';
+export { createUiStatsReporter, trackUserAgent } from './services/telemetry_analytics';
+export { METRIC_TYPE, UiStatsMetricType } from '@kbn/analytics';
diff --git a/src/legacy/core_plugins/ui_metric/public/services/telemetry_analytics.ts b/src/legacy/core_plugins/ui_metric/public/services/telemetry_analytics.ts
index 7310ee5b5f1720..ee928b8a1d9ee8 100644
--- a/src/legacy/core_plugins/ui_metric/public/services/telemetry_analytics.ts
+++ b/src/legacy/core_plugins/ui_metric/public/services/telemetry_analytics.ts
@@ -17,7 +17,9 @@
* under the License.
*/
-import { createReporter, Reporter, UiStatsMetricType } from '@kbn/analytics';
+import { Reporter, UiStatsMetricType } from '@kbn/analytics';
+// @ts-ignore
+import { addSystemApiHeader } from 'ui/system_api';
let telemetryReporter: Reporter;
@@ -39,28 +41,36 @@ export const createUiStatsReporter = (appName: string) => (
}
};
+export const trackUserAgent = (appName: string) => {
+ if (telemetryReporter) {
+ return telemetryReporter.reportUserAgent(appName);
+ }
+};
+
interface AnalyicsReporterConfig {
localStorage: any;
- basePath: string;
debug: boolean;
- $http: ng.IHttpService;
+ kfetch: any;
}
export function createAnalyticsReporter(config: AnalyicsReporterConfig) {
- const { localStorage, basePath, debug } = config;
+ const { localStorage, debug, kfetch } = config;
- return createReporter({
+ return new Reporter({
debug,
storage: localStorage,
async http(report) {
- const url = `${basePath}/api/telemetry/report`;
- await fetch(url, {
+ const response = await kfetch({
method: 'POST',
- headers: {
- 'kbn-xsrf': 'true',
- },
- body: JSON.stringify({ report }),
+ pathname: '/api/telemetry/report',
+ body: JSON.stringify(report),
+ headers: addSystemApiHeader({}),
});
+
+ if (response.status !== 'ok') {
+ throw Error('Unable to store report.');
+ }
+ return response;
},
});
}
diff --git a/src/legacy/core_plugins/ui_metric/server/routes/api/ui_metric.ts b/src/legacy/core_plugins/ui_metric/server/routes/api/ui_metric.ts
index 8a7950c46fa317..e2de23ea806e44 100644
--- a/src/legacy/core_plugins/ui_metric/server/routes/api/ui_metric.ts
+++ b/src/legacy/core_plugins/ui_metric/server/routes/api/ui_metric.ts
@@ -18,7 +18,6 @@
*/
import Joi from 'joi';
-import Boom from 'boom';
import { Report } from '@kbn/analytics';
import { Server } from 'hapi';
@@ -27,15 +26,27 @@ export async function storeReport(server: any, report: Report) {
const { callWithInternalUser } = server.plugins.elasticsearch.getCluster('admin');
const internalRepository = getSavedObjectsRepository(callWithInternalUser);
- const metricKeys = Object.keys(report.uiStatsMetrics);
- return Promise.all(
- metricKeys.map(async key => {
- const metric = report.uiStatsMetrics[key];
+ const uiStatsMetrics = report.uiStatsMetrics ? Object.entries(report.uiStatsMetrics) : [];
+ const userAgents = report.userAgent ? Object.entries(report.userAgent) : [];
+ return Promise.all([
+ ...userAgents.map(async ([key, metric]) => {
+ const { userAgent } = metric;
+ const savedObjectId = `${key}:${userAgent}`;
+ return await internalRepository.create(
+ 'ui-metric',
+ { count: 1 },
+ {
+ id: savedObjectId,
+ overwrite: true,
+ }
+ );
+ }),
+ ...uiStatsMetrics.map(async ([key, metric]) => {
const { appName, eventName } = metric;
const savedObjectId = `${appName}:${eventName}`;
- return internalRepository.incrementCounter('ui-metric', savedObjectId, 'count');
- })
- );
+ return await internalRepository.incrementCounter('ui-metric', savedObjectId, 'count');
+ }),
+ ]);
}
export function registerUiMetricRoute(server: Server) {
@@ -45,36 +56,46 @@ export function registerUiMetricRoute(server: Server) {
options: {
validate: {
payload: Joi.object({
- report: Joi.object({
- uiStatsMetrics: Joi.object()
- .pattern(
- /.*/,
- Joi.object({
- key: Joi.string().required(),
- type: Joi.string().required(),
- appName: Joi.string().required(),
- eventName: Joi.string().required(),
- stats: Joi.object({
- min: Joi.number(),
- sum: Joi.number(),
- max: Joi.number(),
- avg: Joi.number(),
- }).allow(null),
- })
- )
- .allow(null),
- }),
+ reportVersion: Joi.number().optional(),
+ userAgent: Joi.object()
+ .pattern(
+ /.*/,
+ Joi.object({
+ key: Joi.string().required(),
+ type: Joi.string().required(),
+ appName: Joi.string().required(),
+ userAgent: Joi.string().required(),
+ })
+ )
+ .allow(null)
+ .optional(),
+ uiStatsMetrics: Joi.object()
+ .pattern(
+ /.*/,
+ Joi.object({
+ key: Joi.string().required(),
+ type: Joi.string().required(),
+ appName: Joi.string().required(),
+ eventName: Joi.string().required(),
+ stats: Joi.object({
+ min: Joi.number(),
+ sum: Joi.number(),
+ max: Joi.number(),
+ avg: Joi.number(),
+ }).allow(null),
+ })
+ )
+ .allow(null),
}),
},
},
handler: async (req: any, h: any) => {
- const { report } = req.payload;
-
try {
+ const report = req.payload;
await storeReport(server, report);
- return {};
+ return { status: 'ok' };
} catch (error) {
- return new Boom('Something went wrong', { statusCode: error.status });
+ return { status: 'fail' };
}
},
});
diff --git a/src/legacy/core_plugins/vis_type_metric/public/__tests__/metric_vis.js b/src/legacy/core_plugins/vis_type_metric/public/__tests__/metric_vis.js
index 7f626df6a4ea3d..384beb3764e2e0 100644
--- a/src/legacy/core_plugins/vis_type_metric/public/__tests__/metric_vis.js
+++ b/src/legacy/core_plugins/vis_type_metric/public/__tests__/metric_vis.js
@@ -21,7 +21,7 @@ import $ from 'jquery';
import ngMock from 'ng_mock';
import expect from '@kbn/expect';
-import { VisProvider } from 'ui/vis';
+import { Vis } from 'ui/vis';
import LogstashIndexPatternStubProvider from 'fixtures/stubbed_logstash_index_pattern';
import { createMetricVisTypeDefinition } from '../metric_vis_type';
@@ -34,7 +34,6 @@ describe('metric_vis - createMetricVisTypeDefinition', () => {
beforeEach(
ngMock.inject(Private => {
setup = () => {
- const Vis = Private(VisProvider);
const metricVisType = createMetricVisTypeDefinition();
const indexPattern = Private(LogstashIndexPatternStubProvider);
diff --git a/src/legacy/core_plugins/vis_type_table/public/__tests__/table_vis_controller.js b/src/legacy/core_plugins/vis_type_table/public/__tests__/table_vis_controller.js
index abebf8190dc9fc..4153ce2da36a73 100644
--- a/src/legacy/core_plugins/vis_type_table/public/__tests__/table_vis_controller.js
+++ b/src/legacy/core_plugins/vis_type_table/public/__tests__/table_vis_controller.js
@@ -21,7 +21,7 @@ import $ from 'jquery';
import expect from '@kbn/expect';
import ngMock from 'ng_mock';
import { legacyResponseHandlerProvider } from 'ui/vis/response_handlers/legacy';
-import { VisProvider } from 'ui/vis';
+import { Vis } from 'ui/vis';
import { VisFactoryProvider } from 'ui/vis/vis_factory';
import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern';
import { AppStateProvider } from 'ui/state_management/app_state';
@@ -36,7 +36,6 @@ describe('Table Vis - Controller', async function () {
let Private;
let $scope;
let $el;
- let Vis;
let fixtures;
let AppState;
let tableAggResponse;
@@ -63,7 +62,6 @@ describe('Table Vis - Controller', async function () {
$compile = $injector.get('$compile');
fixtures = require('fixtures/fake_hierarchical_data');
AppState = Private(AppStateProvider);
- Vis = Private(VisProvider);
tableAggResponse = legacyResponseHandlerProvider().handler;
})
);
diff --git a/src/legacy/core_plugins/vis_type_table/public/agg_table/__tests__/agg_table.js b/src/legacy/core_plugins/vis_type_table/public/agg_table/__tests__/agg_table.js
index d22ff92c4d3f61..13e8a4fd9535a1 100644
--- a/src/legacy/core_plugins/vis_type_table/public/agg_table/__tests__/agg_table.js
+++ b/src/legacy/core_plugins/vis_type_table/public/agg_table/__tests__/agg_table.js
@@ -25,7 +25,7 @@ import fixtures from 'fixtures/fake_hierarchical_data';
import sinon from 'sinon';
import { legacyResponseHandlerProvider } from 'ui/vis/response_handlers/legacy';
import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern';
-import { VisProvider } from 'ui/vis';
+import { Vis } from 'ui/vis';
import { tabifyAggResponse } from 'ui/agg_response/tabify';
import { round } from 'lodash';
@@ -36,7 +36,6 @@ import { setup as visualizationsSetup } from '../../../../visualizations/public/
describe('Table Vis - AggTable Directive', function () {
let $rootScope;
let $compile;
- let Vis;
let indexPattern;
let settings;
let tableAggResponse;
@@ -113,7 +112,6 @@ describe('Table Vis - AggTable Directive', function () {
ngMock.inject(function ($injector, Private, config) {
tableAggResponse = legacyResponseHandlerProvider().handler;
indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider);
- Vis = Private(VisProvider);
settings = config;
$rootScope = $injector.get('$rootScope');
diff --git a/src/legacy/core_plugins/vis_type_table/public/agg_table/__tests__/agg_table_group.js b/src/legacy/core_plugins/vis_type_table/public/agg_table/__tests__/agg_table_group.js
index e8359e37d61860..f4e3a8e36605cf 100644
--- a/src/legacy/core_plugins/vis_type_table/public/agg_table/__tests__/agg_table_group.js
+++ b/src/legacy/core_plugins/vis_type_table/public/agg_table/__tests__/agg_table_group.js
@@ -23,13 +23,12 @@ import expect from '@kbn/expect';
import fixtures from 'fixtures/fake_hierarchical_data';
import { legacyResponseHandlerProvider } from 'ui/vis/response_handlers/legacy';
import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern';
-import { VisProvider } from 'ui/vis';
+import { Vis } from 'ui/vis';
import { tabifyAggResponse } from 'ui/agg_response/tabify';
describe('Table Vis - AggTableGroup Directive', function () {
let $rootScope;
let $compile;
- let Vis;
let indexPattern;
let tableAggResponse;
const tabifiedData = {};
@@ -69,7 +68,6 @@ describe('Table Vis - AggTableGroup Directive', function () {
tableAggResponse = legacyResponseHandlerProvider().handler;
indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider);
- Vis = Private(VisProvider);
$rootScope = $injector.get('$rootScope');
$compile = $injector.get('$compile');
diff --git a/src/legacy/core_plugins/vis_type_tagcloud/public/components/__tests__/tag_cloud_visualization.js b/src/legacy/core_plugins/vis_type_tagcloud/public/components/__tests__/tag_cloud_visualization.js
index 69d08d8cb3f74f..0cb903faac47c5 100644
--- a/src/legacy/core_plugins/vis_type_tagcloud/public/components/__tests__/tag_cloud_visualization.js
+++ b/src/legacy/core_plugins/vis_type_tagcloud/public/components/__tests__/tag_cloud_visualization.js
@@ -20,7 +20,7 @@
import expect from '@kbn/expect';
import ngMock from 'ng_mock';
import LogstashIndexPatternStubProvider from 'fixtures/stubbed_logstash_index_pattern';
-import * as visModule from 'ui/vis';
+import { Vis } from 'ui/vis';
import { ImageComparator } from 'test_utils/image_comparator';
import { TagCloudVisualization } from '../tag_cloud_visualization';
import basicdrawPng from './basicdraw.png';
@@ -33,7 +33,6 @@ const PIXEL_DIFF = 64;
describe('TagCloudVisualizationTest', function () {
let domNode;
- let Vis;
let indexPattern;
let vis;
let imageComparator;
@@ -57,7 +56,6 @@ describe('TagCloudVisualizationTest', function () {
beforeEach(ngMock.module('kibana'));
beforeEach(ngMock.inject((Private) => {
- Vis = Private(visModule.VisProvider);
indexPattern = Private(LogstashIndexPatternStubProvider);
}));
diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_editor.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_editor.js
index 842d3aa6c4ad75..2b42c22ad7c435 100644
--- a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_editor.js
+++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_editor.js
@@ -44,8 +44,7 @@ const APP_NAME = 'VisEditor';
export class VisEditor extends Component {
constructor(props) {
super(props);
- const { vis } = props;
- this.appState = vis.API.getAppState();
+ this.appState = props.appState;
this.localStorage = new Storage(window.localStorage);
this.state = {
model: props.visParams,
@@ -183,7 +182,6 @@ export class VisEditor extends Component {
dirty={this.state.dirty}
autoApply={this.state.autoApply}
model={model}
- appState={this.appState}
savedObj={this.props.savedObj}
timeRange={this.props.timeRange}
uiState={this.uiState}
@@ -239,4 +237,5 @@ VisEditor.propTypes = {
isEditorMode: PropTypes.bool,
savedObj: PropTypes.object,
timeRange: PropTypes.object,
+ appState: PropTypes.object,
};
diff --git a/src/legacy/core_plugins/vis_type_vega/public/__tests__/vega_visualization.js b/src/legacy/core_plugins/vis_type_vega/public/__tests__/vega_visualization.js
index 46725a2c5d01fc..191f35d2e03ea3 100644
--- a/src/legacy/core_plugins/vis_type_vega/public/__tests__/vega_visualization.js
+++ b/src/legacy/core_plugins/vis_type_vega/public/__tests__/vega_visualization.js
@@ -23,7 +23,7 @@ import ngMock from 'ng_mock';
import $ from 'jquery';
import { createVegaVisualization } from '../vega_visualization';
import LogstashIndexPatternStubProvider from 'fixtures/stubbed_logstash_index_pattern';
-import * as visModule from 'ui/vis';
+import { Vis } from 'ui/vis';
import { ImageComparator } from 'test_utils/image_comparator';
import vegaliteGraph from '!!raw-loader!./vegalite_graph.hjson';
@@ -50,7 +50,6 @@ const PIXEL_DIFF = 30;
describe('VegaVisualizations', () => {
let domNode;
let VegaVisualization;
- let Vis;
let indexPattern;
let vis;
let imageComparator;
@@ -73,7 +72,6 @@ describe('VegaVisualizations', () => {
);
}
- Vis = Private(visModule.VisProvider);
VegaVisualization = createVegaVisualization(vegaVisualizationDependencies);
indexPattern = Private(LogstashIndexPatternStubProvider);
diff --git a/src/legacy/core_plugins/vis_type_vega/public/vega_request_handler.ts b/src/legacy/core_plugins/vis_type_vega/public/vega_request_handler.ts
index b4c32f37eb90c1..accc52c1e5a147 100644
--- a/src/legacy/core_plugins/vis_type_vega/public/vega_request_handler.ts
+++ b/src/legacy/core_plugins/vis_type_vega/public/vega_request_handler.ts
@@ -18,11 +18,9 @@
*/
import { timefilter } from 'ui/timefilter';
-import { TimeRange } from 'src/plugins/data/public';
-import { Query } from 'src/legacy/core_plugins/data/public';
-import { buildEsQuery, getEsQueryConfig } from '@kbn/es-query';
-import { esFilters } from '../../../../plugins/data/public';
+import { buildEsQuery, getEsQueryConfig } from '@kbn/es-query';
+import { esFilters, TimeRange, Query } from '../../../../plugins/data/public';
// @ts-ignore
import { VegaParser } from './data_model/vega_parser';
diff --git a/src/legacy/core_plugins/visualizations/public/index.ts b/src/legacy/core_plugins/visualizations/public/index.ts
index ad86c9ddb14c59..ca79f547890f9c 100644
--- a/src/legacy/core_plugins/visualizations/public/index.ts
+++ b/src/legacy/core_plugins/visualizations/public/index.ts
@@ -39,7 +39,6 @@ export { DefaultEditorSize } from 'ui/vis/editor_size';
import * as types from 'ui/vis/vis';
export type Vis = types.Vis;
export type VisParams = types.VisParams;
-export type VisProvider = types.VisProvider;
export type VisState = types.VisState;
export { VisualizationController } from 'ui/vis/vis_types/vis_type';
export { Status } from 'ui/vis/update_status';
diff --git a/src/legacy/plugin_discovery/types.ts b/src/legacy/plugin_discovery/types.ts
index c14daa37f57060..dfd36f2aa7b2b2 100644
--- a/src/legacy/plugin_discovery/types.ts
+++ b/src/legacy/plugin_discovery/types.ts
@@ -62,7 +62,6 @@ export interface LegacyPluginOptions {
}>;
apps: any;
hacks: string[];
- devTools: string[];
styleSheetPaths: string;
injectDefaultVars: (server: Server) => Record;
noParse: string[];
diff --git a/src/legacy/ui/public/agg_response/tabify/__tests__/_get_columns.js b/src/legacy/ui/public/agg_response/tabify/__tests__/_get_columns.js
index 27b390ed7e4711..39303e94adc6f1 100644
--- a/src/legacy/ui/public/agg_response/tabify/__tests__/_get_columns.js
+++ b/src/legacy/ui/public/agg_response/tabify/__tests__/_get_columns.js
@@ -20,15 +20,13 @@
import expect from '@kbn/expect';
import ngMock from 'ng_mock';
import { tabifyGetColumns } from '../_get_columns';
-import { VisProvider } from '../../../vis';
+import { Vis } from '../../../vis';
import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern';
describe('get columns', function () {
- let Vis;
let indexPattern;
beforeEach(ngMock.module('kibana'));
beforeEach(ngMock.inject(function (Private) {
- Vis = Private(VisProvider);
indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider);
}));
diff --git a/src/legacy/ui/public/agg_response/tabify/__tests__/_integration.js b/src/legacy/ui/public/agg_response/tabify/__tests__/_integration.js
index fd8df904b600f0..dabd66a22fcd28 100644
--- a/src/legacy/ui/public/agg_response/tabify/__tests__/_integration.js
+++ b/src/legacy/ui/public/agg_response/tabify/__tests__/_integration.js
@@ -22,16 +22,14 @@ import fixtures from 'fixtures/fake_hierarchical_data';
import expect from '@kbn/expect';
import ngMock from 'ng_mock';
import { tabifyAggResponse } from '../tabify';
-import { VisProvider } from '../../../vis';
+import { Vis } from '../../../vis';
import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern';
describe('tabifyAggResponse Integration', function () {
- let Vis;
let indexPattern;
beforeEach(ngMock.module('kibana'));
beforeEach(ngMock.inject(function (Private) {
- Vis = Private(VisProvider);
indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider);
}));
diff --git a/src/legacy/ui/public/agg_response/tabify/__tests__/_response_writer.js b/src/legacy/ui/public/agg_response/tabify/__tests__/_response_writer.js
index 09668c638d6952..001132f97c95a6 100644
--- a/src/legacy/ui/public/agg_response/tabify/__tests__/_response_writer.js
+++ b/src/legacy/ui/public/agg_response/tabify/__tests__/_response_writer.js
@@ -20,11 +20,10 @@
import expect from '@kbn/expect';
import ngMock from 'ng_mock';
import { TabbedAggResponseWriter } from '../_response_writer';
-import { VisProvider } from '../../../vis';
+import { Vis } from '../../../vis';
import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern';
describe('TabbedAggResponseWriter class', function () {
- let Vis;
let Private;
let indexPattern;
@@ -32,7 +31,6 @@ describe('TabbedAggResponseWriter class', function () {
beforeEach(ngMock.inject(function ($injector) {
Private = $injector.get('Private');
- Vis = Private(VisProvider);
indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider);
}));
diff --git a/src/legacy/ui/public/agg_types/__tests__/buckets/_terms_other_bucket_helper.js b/src/legacy/ui/public/agg_types/__tests__/buckets/_terms_other_bucket_helper.js
index dcadeacb3d1fea..759a6b80d8faf9 100644
--- a/src/legacy/ui/public/agg_types/__tests__/buckets/_terms_other_bucket_helper.js
+++ b/src/legacy/ui/public/agg_types/__tests__/buckets/_terms_other_bucket_helper.js
@@ -20,7 +20,7 @@
import expect from '@kbn/expect';
import ngMock from 'ng_mock';
import { buildOtherBucketAgg, mergeOtherBucketAggResponse, updateMissingBucket } from '../../buckets/_terms_other_bucket_helper';
-import { VisProvider } from '../../../vis';
+import { Vis } from '../../../vis';
import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern';
const visConfigSingleTerm = {
@@ -158,7 +158,6 @@ describe('Terms Agg Other bucket helper', () => {
function init(aggConfig) {
ngMock.module('kibana');
ngMock.inject((Private) => {
- const Vis = Private(VisProvider);
const indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider);
vis = new Vis(indexPattern, aggConfig);
diff --git a/src/legacy/ui/public/agg_types/agg_configs.ts b/src/legacy/ui/public/agg_types/agg_configs.ts
index 675d37d05c33c1..7c0245f30a1fda 100644
--- a/src/legacy/ui/public/agg_types/agg_configs.ts
+++ b/src/legacy/ui/public/agg_types/agg_configs.ts
@@ -253,13 +253,10 @@ export class AggConfigs {
// collect all the aggregations
const aggregations = this.aggs
.filter(agg => agg.enabled && agg.type)
- .reduce(
- (requestValuesAggs, agg: AggConfig) => {
- const aggs = agg.getRequestAggs();
- return aggs ? requestValuesAggs.concat(aggs) : requestValuesAggs;
- },
- [] as AggConfig[]
- );
+ .reduce((requestValuesAggs, agg: AggConfig) => {
+ const aggs = agg.getRequestAggs();
+ return aggs ? requestValuesAggs.concat(aggs) : requestValuesAggs;
+ }, [] as AggConfig[]);
// move metrics to the end
return _.sortBy(aggregations, (agg: AggConfig) =>
agg.type.type === AggGroupNames.Metrics ? 1 : 0
@@ -282,13 +279,10 @@ export class AggConfigs {
* @return {array[AggConfig]}
*/
getResponseAggs(): AggConfig[] {
- return this.getRequestAggs().reduce(
- function(responseValuesAggs, agg: AggConfig) {
- const aggs = agg.getResponseAggs();
- return aggs ? responseValuesAggs.concat(aggs) : responseValuesAggs;
- },
- [] as AggConfig[]
- );
+ return this.getRequestAggs().reduce(function(responseValuesAggs, agg: AggConfig) {
+ const aggs = agg.getResponseAggs();
+ return aggs ? responseValuesAggs.concat(aggs) : responseValuesAggs;
+ }, [] as AggConfig[]);
}
/**
diff --git a/src/legacy/ui/public/agg_types/buckets/filters.ts b/src/legacy/ui/public/agg_types/buckets/filters.ts
index 44a97abb7a1d77..a8d509d507c6b9 100644
--- a/src/legacy/ui/public/agg_types/buckets/filters.ts
+++ b/src/legacy/ui/public/agg_types/buckets/filters.ts
@@ -27,10 +27,9 @@ import { buildEsQuery } from '@kbn/es-query';
import { FiltersParamEditor, FilterValue } from '../../vis/editors/default/controls/filters';
import { createFilterFilters } from './create_filter/filters';
import { BucketAggType, IBucketAggConfig } from './_bucket_agg_type';
-import { setup as data } from '../../../../core_plugins/data/public/legacy';
import { Storage } from '../../../../../plugins/kibana_utils/public';
+import { getQueryLog } from '../../../../../plugins/data/public';
-const { getQueryLog } = data.query.helpers;
const config = chrome.getUiSettingsClient();
const storage = new Storage(window.localStorage);
diff --git a/src/legacy/ui/public/agg_types/buckets/geo_hash.test.ts b/src/legacy/ui/public/agg_types/buckets/geo_hash.test.ts
index 5c599f16e09c2e..effa49f0ade6b2 100644
--- a/src/legacy/ui/public/agg_types/buckets/geo_hash.test.ts
+++ b/src/legacy/ui/public/agg_types/buckets/geo_hash.test.ts
@@ -156,8 +156,9 @@ describe('Geohash Agg', () => {
describe('aggregation options', () => {
it('should only create geohash_grid and geo_centroid aggregations when isFilteredByCollar is false', () => {
const aggConfigs = getAggConfigs({ isFilteredByCollar: false });
- const requestAggs = geoHashBucketAgg.getRequestAggs(aggConfigs
- .aggs[0] as IBucketGeoHashGridAggConfig) as IBucketGeoHashGridAggConfig[];
+ const requestAggs = geoHashBucketAgg.getRequestAggs(
+ aggConfigs.aggs[0] as IBucketGeoHashGridAggConfig
+ ) as IBucketGeoHashGridAggConfig[];
expect(requestAggs.length).toEqual(2);
expect(requestAggs[0].type.name).toEqual('geohash_grid');
@@ -166,8 +167,9 @@ describe('Geohash Agg', () => {
it('should only create filter and geohash_grid aggregations when useGeocentroid is false', () => {
const aggConfigs = getAggConfigs({ useGeocentroid: false });
- const requestAggs = geoHashBucketAgg.getRequestAggs(aggConfigs
- .aggs[0] as IBucketGeoHashGridAggConfig) as IBucketGeoHashGridAggConfig[];
+ const requestAggs = geoHashBucketAgg.getRequestAggs(
+ aggConfigs.aggs[0] as IBucketGeoHashGridAggConfig
+ ) as IBucketGeoHashGridAggConfig[];
expect(requestAggs.length).toEqual(2);
expect(requestAggs[0].type.name).toEqual('filter');
@@ -179,36 +181,43 @@ describe('Geohash Agg', () => {
let originalRequestAggs: IBucketGeoHashGridAggConfig[];
beforeEach(() => {
- originalRequestAggs = geoHashBucketAgg.getRequestAggs(getAggConfigs()
- .aggs[0] as IBucketGeoHashGridAggConfig) as IBucketGeoHashGridAggConfig[];
+ originalRequestAggs = geoHashBucketAgg.getRequestAggs(
+ getAggConfigs().aggs[0] as IBucketGeoHashGridAggConfig
+ ) as IBucketGeoHashGridAggConfig[];
});
it('should change geo_bounding_box filter aggregation and vis session state when map movement is outside map collar', () => {
- const [, geoBoxingBox] = geoHashBucketAgg.getRequestAggs(getAggConfigs({
- mapBounds: {
- top_left: { lat: 10.0, lon: -10.0 },
- bottom_right: { lat: 9.0, lon: -9.0 },
- },
- }).aggs[0] as IBucketGeoHashGridAggConfig) as IBucketGeoHashGridAggConfig[];
+ const [, geoBoxingBox] = geoHashBucketAgg.getRequestAggs(
+ getAggConfigs({
+ mapBounds: {
+ top_left: { lat: 10.0, lon: -10.0 },
+ bottom_right: { lat: 9.0, lon: -9.0 },
+ },
+ }).aggs[0] as IBucketGeoHashGridAggConfig
+ ) as IBucketGeoHashGridAggConfig[];
expect(originalRequestAggs[1].params).not.toEqual(geoBoxingBox.params);
});
it('should not change geo_bounding_box filter aggregation and vis session state when map movement is within map collar', () => {
- const [, geoBoxingBox] = geoHashBucketAgg.getRequestAggs(getAggConfigs({
- mapBounds: {
- top_left: { lat: 1, lon: -1 },
- bottom_right: { lat: -1, lon: 1 },
- },
- }).aggs[0] as IBucketGeoHashGridAggConfig) as IBucketGeoHashGridAggConfig[];
+ const [, geoBoxingBox] = geoHashBucketAgg.getRequestAggs(
+ getAggConfigs({
+ mapBounds: {
+ top_left: { lat: 1, lon: -1 },
+ bottom_right: { lat: -1, lon: 1 },
+ },
+ }).aggs[0] as IBucketGeoHashGridAggConfig
+ ) as IBucketGeoHashGridAggConfig[];
expect(originalRequestAggs[1].params).toEqual(geoBoxingBox.params);
});
it('should change geo_bounding_box filter aggregation and vis session state when map zoom level changes', () => {
- const [, geoBoxingBox] = geoHashBucketAgg.getRequestAggs(getAggConfigs({
- mapZoom: -1,
- }).aggs[0] as IBucketGeoHashGridAggConfig) as IBucketGeoHashGridAggConfig[];
+ const [, geoBoxingBox] = geoHashBucketAgg.getRequestAggs(
+ getAggConfigs({
+ mapZoom: -1,
+ }).aggs[0] as IBucketGeoHashGridAggConfig
+ ) as IBucketGeoHashGridAggConfig[];
expect(originalRequestAggs[1].lastMapCollar).not.toEqual(geoBoxingBox.lastMapCollar);
});
diff --git a/src/legacy/ui/public/agg_types/buckets/range.test.ts b/src/legacy/ui/public/agg_types/buckets/range.test.ts
index f7cae60cce773b..1b423e64c48aef 100644
--- a/src/legacy/ui/public/agg_types/buckets/range.test.ts
+++ b/src/legacy/ui/public/agg_types/buckets/range.test.ts
@@ -72,7 +72,10 @@ describe('Range Agg', () => {
schema: 'segment',
params: {
field: 'bytes',
- ranges: [{ from: 0, to: 1000 }, { from: 1000, to: 2000 }],
+ ranges: [
+ { from: 0, to: 1000 },
+ { from: 1000, to: 2000 },
+ ],
},
},
],
diff --git a/src/legacy/ui/public/agg_types/buckets/range.ts b/src/legacy/ui/public/agg_types/buckets/range.ts
index 348fccdab3fe37..230675ab487ade 100644
--- a/src/legacy/ui/public/agg_types/buckets/range.ts
+++ b/src/legacy/ui/public/agg_types/buckets/range.ts
@@ -98,7 +98,10 @@ export const rangeBucketAgg = new BucketAggType({
},
{
name: 'ranges',
- default: [{ from: 0, to: 1000 }, { from: 1000, to: 2000 }],
+ default: [
+ { from: 0, to: 1000 },
+ { from: 1000, to: 2000 },
+ ],
editorComponent: RangesEditor,
write(aggConfig: IBucketAggConfig, output: Record) {
output.params.ranges = aggConfig.params.ranges;
diff --git a/src/legacy/ui/public/agg_types/filter/prop_filter.ts b/src/legacy/ui/public/agg_types/filter/prop_filter.ts
index 45f350ea6adc81..e6b5f3831e65d9 100644
--- a/src/legacy/ui/public/agg_types/filter/prop_filter.ts
+++ b/src/legacy/ui/public/agg_types/filter/prop_filter.ts
@@ -59,24 +59,21 @@ function propFilter(prop: P) {
return list;
}
- const options = filters.reduce(
- (acc, filter) => {
- let type = 'include';
- let value = filter;
+ const options = filters.reduce((acc, filter) => {
+ let type = 'include';
+ let value = filter;
- if (filter.charAt(0) === '!') {
- type = 'exclude';
- value = filter.substr(1);
- }
+ if (filter.charAt(0) === '!') {
+ type = 'exclude';
+ value = filter.substr(1);
+ }
- if (!acc[type]) {
- acc[type] = [];
- }
- acc[type].push(value);
- return acc;
- },
- {} as { [type: string]: string[] }
- );
+ if (!acc[type]) {
+ acc[type] = [];
+ }
+ acc[type].push(value);
+ return acc;
+ }, {} as { [type: string]: string[] });
return list.filter(item => {
const value = item[prop];
diff --git a/src/legacy/ui/public/agg_types/metrics/metric_agg_type.ts b/src/legacy/ui/public/agg_types/metrics/metric_agg_type.ts
index 66bc205cead132..c1f5528825bcc4 100644
--- a/src/legacy/ui/public/agg_types/metrics/metric_agg_type.ts
+++ b/src/legacy/ui/public/agg_types/metrics/metric_agg_type.ts
@@ -58,8 +58,9 @@ export class MetricAggType<
config.getValue ||
((agg, bucket) => {
// Metric types where an empty set equals `zero`
- const isSettableToZero = [METRIC_TYPES.CARDINALITY, METRIC_TYPES.SUM].includes(agg.type
- .name as METRIC_TYPES);
+ const isSettableToZero = [METRIC_TYPES.CARDINALITY, METRIC_TYPES.SUM].includes(
+ agg.type.name as METRIC_TYPES
+ );
// Return proper values when no buckets are present
// `Count` handles empty sets properly
diff --git a/src/legacy/ui/public/agg_types/metrics/percentile_ranks.test.ts b/src/legacy/ui/public/agg_types/metrics/percentile_ranks.test.ts
index f3882ca57161f1..7461b5cf07ee7c 100644
--- a/src/legacy/ui/public/agg_types/metrics/percentile_ranks.test.ts
+++ b/src/legacy/ui/public/agg_types/metrics/percentile_ranks.test.ts
@@ -63,8 +63,9 @@ describe('AggTypesMetricsPercentileRanksProvider class', function() {
});
it('uses the custom label if it is set', function() {
- const responseAggs: any = percentileRanksMetricAgg.getResponseAggs(aggConfigs
- .aggs[0] as IPercentileRanksAggConfig);
+ const responseAggs: any = percentileRanksMetricAgg.getResponseAggs(
+ aggConfigs.aggs[0] as IPercentileRanksAggConfig
+ );
const percentileRankLabelFor5kBytes = responseAggs[0].makeLabel();
const percentileRankLabelFor10kBytes = responseAggs[1].makeLabel();
diff --git a/src/legacy/ui/public/agg_types/metrics/percentiles.test.ts b/src/legacy/ui/public/agg_types/metrics/percentiles.test.ts
index 1503f43b22dc30..c9f4bcc3862a03 100644
--- a/src/legacy/ui/public/agg_types/metrics/percentiles.test.ts
+++ b/src/legacy/ui/public/agg_types/metrics/percentiles.test.ts
@@ -63,8 +63,9 @@ describe('AggTypesMetricsPercentilesProvider class', () => {
});
it('uses the custom label if it is set', () => {
- const responseAggs: any = percentilesMetricAgg.getResponseAggs(aggConfigs
- .aggs[0] as IPercentileAggConfig);
+ const responseAggs: any = percentilesMetricAgg.getResponseAggs(
+ aggConfigs.aggs[0] as IPercentileAggConfig
+ );
const ninetyFifthPercentileLabel = responseAggs[0].makeLabel();
diff --git a/src/legacy/ui/public/agg_types/metrics/std_deviation.test.ts b/src/legacy/ui/public/agg_types/metrics/std_deviation.test.ts
index ae09b5cd789774..3125026a521854 100644
--- a/src/legacy/ui/public/agg_types/metrics/std_deviation.test.ts
+++ b/src/legacy/ui/public/agg_types/metrics/std_deviation.test.ts
@@ -58,8 +58,9 @@ describe('AggTypeMetricStandardDeviationProvider class', () => {
it('uses the custom label if it is set', () => {
const aggConfigs = getAggConfigs('custom label');
- const responseAggs: any = stdDeviationMetricAgg.getResponseAggs(aggConfigs
- .aggs[0] as IStdDevAggConfig);
+ const responseAggs: any = stdDeviationMetricAgg.getResponseAggs(
+ aggConfigs.aggs[0] as IStdDevAggConfig
+ );
const lowerStdDevLabel = responseAggs[0].makeLabel();
const upperStdDevLabel = responseAggs[1].makeLabel();
@@ -71,8 +72,9 @@ describe('AggTypeMetricStandardDeviationProvider class', () => {
it('uses the default labels if custom label is not set', () => {
const aggConfigs = getAggConfigs();
- const responseAggs: any = stdDeviationMetricAgg.getResponseAggs(aggConfigs
- .aggs[0] as IStdDevAggConfig);
+ const responseAggs: any = stdDeviationMetricAgg.getResponseAggs(
+ aggConfigs.aggs[0] as IStdDevAggConfig
+ );
const lowerStdDevLabel = responseAggs[0].makeLabel();
const upperStdDevLabel = responseAggs[1].makeLabel();
diff --git a/src/legacy/ui/public/filter_manager/__tests__/filter_generator.js b/src/legacy/ui/public/filter_manager/__tests__/filter_generator.js
deleted file mode 100644
index 5b6455bf20847e..00000000000000
--- a/src/legacy/ui/public/filter_manager/__tests__/filter_generator.js
+++ /dev/null
@@ -1,166 +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 _ from 'lodash';
-import sinon from 'sinon';
-import MockState from 'fixtures/mock_state';
-import expect from '@kbn/expect';
-import ngMock from 'ng_mock';
-import { getFilterGenerator } from '..';
-import { FilterBarQueryFilterProvider } from '../../filter_manager/query_filter';
-import { uniqFilters, esFilters } from '../../../../../plugins/data/public';
-
-let queryFilter;
-let filterGen;
-let appState;
-
-function checkAddFilters(length, comps, idx) {
- idx = idx || 0;
- const filters = queryFilter.addFilters.getCall(idx).args[0];
-
- expect(filters.length).to.be(length);
- if (!Array.isArray(comps)) return;
- comps.forEach(function (comp, i) {
- expect(filters[i]).to.eql(comp);
- });
-}
-
-describe('Filter Manager', function () {
- beforeEach(ngMock.module(
- 'kibana',
- 'kibana/global_state',
- function ($provide) {
- $provide.service('indexPatterns', require('fixtures/mock_index_patterns'));
-
- appState = new MockState({ filters: [] });
- $provide.service('getAppState', function () {
- return function () { return appState; };
- });
- }
- ));
-
- beforeEach(ngMock.inject(function (_$rootScope_, Private) {
-
- // mock required queryFilter methods, used in the manager
- queryFilter = Private(FilterBarQueryFilterProvider);
- filterGen = getFilterGenerator(queryFilter);
- sinon.stub(queryFilter, 'getAppFilters').callsFake(() => appState.filters);
- sinon.stub(queryFilter, 'addFilters').callsFake((filters) => {
- if (!Array.isArray(filters)) filters = [filters];
- appState.filters = uniqFilters(appState.filters.concat(filters));
- });
- }));
-
- it('should have an `add` function', function () {
- expect(filterGen.add).to.be.a(Function);
- });
-
- it('should add a filter', function () {
- filterGen.add('myField', 1, '+', 'myIndex');
- expect(queryFilter.addFilters.callCount).to.be(1);
- checkAddFilters(1, [{
- meta: { index: 'myIndex', negate: false },
- query: { match_phrase: { myField: 1 } }
- }]);
- });
-
- it('should add multiple filters if passed an array of values', function () {
- filterGen.add('myField', [1, 2, 3], '+', 'myIndex');
- expect(queryFilter.addFilters.callCount).to.be(1);
- checkAddFilters(3, [{
- meta: { index: 'myIndex', negate: false },
- query: { match_phrase: { myField: 1 } }
- }, {
- meta: { index: 'myIndex', negate: false },
- query: { match_phrase: { myField: 2 } }
- }, {
- meta: { index: 'myIndex', negate: false },
- query: { match_phrase: { myField: 3 } }
- }]);
- });
-
- it('should add an exists filter if _exists_ is used as the field', function () {
- filterGen.add('_exists_', 'myField', '+', 'myIndex');
- checkAddFilters(1, [{
- meta: { index: 'myIndex', negate: false },
- exists: { field: 'myField' }
- }]);
- });
-
- it('should negate existing filter instead of added a conflicting filter', function () {
- filterGen.add('myField', 1, '+', 'myIndex');
- checkAddFilters(1, [{
- meta: { index: 'myIndex', negate: false },
- query: { match_phrase: { myField: 1 } }
- }], 0);
- expect(appState.filters).to.have.length(1);
-
- // NOTE: negating exists filters also forces disabled to false
- filterGen.add('myField', 1, '-', 'myIndex');
- checkAddFilters(1, [{
- meta: { index: 'myIndex', negate: true, disabled: false },
- query: { match_phrase: { myField: 1 } }
- }], 1);
- expect(appState.filters).to.have.length(1);
-
- filterGen.add('_exists_', 'myField', '+', 'myIndex');
- checkAddFilters(1, [{
- meta: { index: 'myIndex', negate: false },
- exists: { field: 'myField' }
- }], 2);
- expect(appState.filters).to.have.length(2);
-
- filterGen.add('_exists_', 'myField', '-', 'myIndex');
- checkAddFilters(1, [{
- meta: { index: 'myIndex', negate: true, disabled: false },
- exists: { field: 'myField' }
- }], 3);
- expect(appState.filters).to.have.length(2);
-
- const scriptedField = { name: 'scriptedField', scripted: true, script: 1, lang: 'painless' };
- filterGen.add(scriptedField, 1, '+', 'myIndex');
- checkAddFilters(1, [{
- meta: { index: 'myIndex', negate: false, field: 'scriptedField' },
- script: esFilters.getPhraseScript(scriptedField, 1)
- }], 4);
- expect(appState.filters).to.have.length(3);
-
- filterGen.add(scriptedField, 1, '-', 'myIndex');
- checkAddFilters(1, [{
- meta: { index: 'myIndex', negate: true, disabled: false, field: 'scriptedField' },
- script: esFilters.getPhraseScript(scriptedField, 1)
- }], 5);
- expect(appState.filters).to.have.length(3);
- });
-
- it('should enable matching filters being changed', function () {
- _.each([true, false], function (negate) {
- appState.filters = [{
- query: { match_phrase: { myField: 1 } },
- meta: { disabled: true, negate: negate }
- }];
- expect(appState.filters.length).to.be(1);
- expect(appState.filters[0].meta.disabled).to.be(true);
-
- filterGen.add('myField', 1, '+', 'myIndex');
- expect(appState.filters.length).to.be(1);
- expect(appState.filters[0].meta.disabled).to.be(false);
- });
- });
-});
diff --git a/src/legacy/ui/public/filter_manager/filter_generator.js b/src/legacy/ui/public/filter_manager/filter_generator.js
deleted file mode 100644
index e11e0ff6653a7e..00000000000000
--- a/src/legacy/ui/public/filter_manager/filter_generator.js
+++ /dev/null
@@ -1,98 +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 _ from 'lodash';
-import { esFilters } from '../../../../plugins/data/public';
-
-// Adds a filter to a passed state
-export function getFilterGenerator(queryFilter) {
- const filterGen = {};
-
- filterGen.generate = (field, values, operation, index) => {
- values = Array.isArray(values) ? values : [values];
- const fieldName = _.isObject(field) ? field.name : field;
- const filters = _.flatten([queryFilter.getAppFilters()]);
- const newFilters = [];
-
- const negate = (operation === '-');
-
- // TODO: On array fields, negating does not negate the combination, rather all terms
- _.each(values, function (value) {
- let filter;
- const existing = _.find(filters, function (filter) {
- if (!filter) return;
-
- if (fieldName === '_exists_' && filter.exists) {
- return filter.exists.field === value;
- }
-
- if (esFilters.isPhraseFilter(filter)) {
- return esFilters.getPhraseFilterField(filter) === fieldName && esFilters.getPhraseFilterValue(filter) === value;
- }
-
- if (filter.script) {
- return filter.meta.field === fieldName && filter.script.script.params.value === value;
- }
- });
-
- if (existing) {
- existing.meta.disabled = false;
- if (existing.meta.negate !== negate) {
- existing.meta.negate = !existing.meta.negate;
- }
- newFilters.push(existing);
- return;
- }
-
- switch (fieldName) {
- case '_exists_':
- filter = {
- meta: { negate, index },
- exists: {
- field: value
- }
- };
- break;
- default:
- if (field.scripted) {
- filter = {
- meta: { negate, index, field: fieldName },
- script: esFilters.getPhraseScript(field, value)
- };
- } else {
- filter = { meta: { negate, index }, query: { match_phrase: {} } };
- filter.query.match_phrase[fieldName] = value;
- }
-
- break;
- }
-
- newFilters.push(filter);
- });
-
- return newFilters;
- };
-
- filterGen.add = function (field, values, operation, index) {
- const newFilters = this.generate(field, values, operation, index);
- return queryFilter.addFilters(newFilters);
- };
-
- return filterGen;
-}
diff --git a/src/legacy/ui/public/filter_manager/index.js b/src/legacy/ui/public/filter_manager/index.js
index 6adc4e0965ccde..ce99d4cac3017c 100644
--- a/src/legacy/ui/public/filter_manager/index.js
+++ b/src/legacy/ui/public/filter_manager/index.js
@@ -17,4 +17,3 @@
* under the License.
*/
-export { getFilterGenerator } from './filter_generator';
diff --git a/src/legacy/ui/public/index_patterns/__mocks__/index.ts b/src/legacy/ui/public/index_patterns/__mocks__/index.ts
index 85c07cb3b1df17..2dd3f370c6d6aa 100644
--- a/src/legacy/ui/public/index_patterns/__mocks__/index.ts
+++ b/src/legacy/ui/public/index_patterns/__mocks__/index.ts
@@ -45,6 +45,4 @@ export {
IndexPatternMissingIndices,
NoDefaultIndexPattern,
NoDefinedIndexPatterns,
- mockFields,
- mockIndexPattern,
} from '../../../../core_plugins/data/public';
diff --git a/src/legacy/ui/public/index_patterns/index.ts b/src/legacy/ui/public/index_patterns/index.ts
index 67c370cad82a50..3b4952ac815192 100644
--- a/src/legacy/ui/public/index_patterns/index.ts
+++ b/src/legacy/ui/public/index_patterns/index.ts
@@ -47,8 +47,6 @@ export {
IndexPatternMissingIndices,
NoDefaultIndexPattern,
NoDefinedIndexPatterns,
- mockFields,
- mockIndexPattern,
} from '../../../core_plugins/data/public';
// types
diff --git a/src/legacy/ui/public/legacy_compat/angular_config.tsx b/src/legacy/ui/public/legacy_compat/angular_config.tsx
index 58b8422cb2f8a9..788718e8484308 100644
--- a/src/legacy/ui/public/legacy_compat/angular_config.tsx
+++ b/src/legacy/ui/public/legacy_compat/angular_config.tsx
@@ -40,6 +40,7 @@ import { fatalError } from 'ui/notify';
import { capabilities } from 'ui/capabilities';
// @ts-ignore
import { modifyUrl } from 'ui/url';
+import { toMountPoint } from '../../../../plugins/kibana_react/public';
// @ts-ignore
import { UrlOverflowService } from '../error_url_overflow';
import { npStart } from '../new_platform';
@@ -329,7 +330,7 @@ const $setupUrlOverflowHandling = (newPlatform: CoreStart) => (
title: i18n.translate('common.ui.chrome.bigUrlWarningNotificationTitle', {
defaultMessage: 'The URL is big and Kibana might stop working',
}),
- text: (
+ text: toMountPoint(
{},
+ },
inspector: {
registerView: () => undefined,
__LEGACY: {
@@ -97,6 +100,9 @@ export const npStart = {
registerRenderer: sinon.fake(),
registerType: sinon.fake(),
},
+ devTools: {
+ getSortedDevTools: () => [],
+ },
data: {
autocomplete: {
getProvider: sinon.fake(),
diff --git a/src/legacy/ui/public/new_platform/new_platform.ts b/src/legacy/ui/public/new_platform/new_platform.ts
index 0c7b28e7da3df2..9ee5d8580a90b9 100644
--- a/src/legacy/ui/public/new_platform/new_platform.ts
+++ b/src/legacy/ui/public/new_platform/new_platform.ts
@@ -28,6 +28,7 @@ import {
Start as InspectorStart,
} from '../../../../plugins/inspector/public';
import { EuiUtilsStart } from '../../../../plugins/eui_utils/public';
+import { DevToolsSetup, DevToolsStart } from '../../../../plugins/dev_tools/public';
import {
FeatureCatalogueSetup,
FeatureCatalogueStart,
@@ -40,6 +41,7 @@ export interface PluginsSetup {
feature_catalogue: FeatureCatalogueSetup;
inspector: InspectorSetup;
uiActions: IUiActionsSetup;
+ devTools: DevToolsSetup;
}
export interface PluginsStart {
@@ -50,6 +52,7 @@ export interface PluginsStart {
feature_catalogue: FeatureCatalogueStart;
inspector: InspectorStart;
uiActions: IUiActionsStart;
+ devTools: DevToolsStart;
}
export const npSetup = {
diff --git a/src/legacy/ui/public/utils/migrate_legacy_query.ts b/src/legacy/ui/public/utils/migrate_legacy_query.ts
index 06e819d6a64a27..8d9b50d5a66b2a 100644
--- a/src/legacy/ui/public/utils/migrate_legacy_query.ts
+++ b/src/legacy/ui/public/utils/migrate_legacy_query.ts
@@ -18,7 +18,7 @@
*/
import { has } from 'lodash';
-import { Query } from 'plugins/data';
+import { Query } from 'src/plugins/data/public';
/**
* Creates a standardized query object from old queries that were either strings or pure ES query DSL
diff --git a/src/legacy/ui/public/vis/__tests__/_agg_config.js b/src/legacy/ui/public/vis/__tests__/_agg_config.js
index 46d7ed4601f29b..2e2e0c31bdb8af 100644
--- a/src/legacy/ui/public/vis/__tests__/_agg_config.js
+++ b/src/legacy/ui/public/vis/__tests__/_agg_config.js
@@ -20,7 +20,7 @@
import sinon from 'sinon';
import expect from '@kbn/expect';
import ngMock from 'ng_mock';
-import { VisProvider } from '..';
+import { Vis } from '..';
import { AggType } from '../../agg_types/agg_type';
import { AggConfig } from '../../agg_types/agg_config';
import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern';
@@ -28,12 +28,10 @@ import { fieldFormats } from '../../registry/field_formats';
describe('AggConfig', function () {
- let Vis;
let indexPattern;
beforeEach(ngMock.module('kibana'));
beforeEach(ngMock.inject(function (Private) {
- Vis = Private(VisProvider);
indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider);
}));
diff --git a/src/legacy/ui/public/vis/__tests__/_agg_configs.js b/src/legacy/ui/public/vis/__tests__/_agg_configs.js
index 96e4aa943ed1da..3240bd56f1502e 100644
--- a/src/legacy/ui/public/vis/__tests__/_agg_configs.js
+++ b/src/legacy/ui/public/vis/__tests__/_agg_configs.js
@@ -22,7 +22,7 @@ import sinon from 'sinon';
import expect from '@kbn/expect';
import ngMock from 'ng_mock';
import { AggConfig } from '../../agg_types/agg_config';
-import { VisProvider } from '..';
+import { Vis } from '..';
import { AggConfigs } from '../../agg_types/agg_configs';
import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern';
import { Schemas } from '../editors/default/schemas';
@@ -30,13 +30,11 @@ import { AggGroupNames } from '../editors/default/agg_groups';
describe('AggConfigs', function () {
- let Vis;
let indexPattern;
beforeEach(ngMock.module('kibana'));
beforeEach(ngMock.inject(function (Private) {
// load main deps
- Vis = Private(VisProvider);
indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider);
}));
diff --git a/src/legacy/ui/public/vis/__tests__/_vis.js b/src/legacy/ui/public/vis/__tests__/_vis.js
index 5a2f93ab35b500..1d5e2de6dafe37 100644
--- a/src/legacy/ui/public/vis/__tests__/_vis.js
+++ b/src/legacy/ui/public/vis/__tests__/_vis.js
@@ -20,13 +20,12 @@
import _ from 'lodash';
import ngMock from 'ng_mock';
import expect from '@kbn/expect';
-import { VisProvider } from '..';
+import { Vis } from '..';
import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern';
import { start as visualizations } from '../../../../core_plugins/visualizations/public/np_ready/public/legacy';
describe('Vis Class', function () {
let indexPattern;
- let Vis;
let visTypes;
let vis;
@@ -43,7 +42,6 @@ describe('Vis Class', function () {
beforeEach(ngMock.module('kibana'));
beforeEach(ngMock.inject(function (Private) {
- Vis = Private(VisProvider);
indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider);
visTypes = visualizations.types;
}));
diff --git a/src/legacy/ui/public/vis/editors/default/components/agg.test.tsx b/src/legacy/ui/public/vis/editors/default/components/agg.test.tsx
index 7806b1c0f78fbc..661ece5944fa34 100644
--- a/src/legacy/ui/public/vis/editors/default/components/agg.test.tsx
+++ b/src/legacy/ui/public/vis/editors/default/components/agg.test.tsx
@@ -248,10 +248,9 @@ describe('DefaultEditorAgg component', () => {
expect(compHistogram.find(DefaultEditorAggParams).props()).toHaveProperty('disabledParams', [
'min_doc_count',
]);
- expect(compDateHistogram.find(DefaultEditorAggParams).props()).toHaveProperty(
- 'disabledParams',
- ['min_doc_count']
- );
+ expect(
+ compDateHistogram.find(DefaultEditorAggParams).props()
+ ).toHaveProperty('disabledParams', ['min_doc_count']);
});
it('should set error when agg is not histogram or date_histogram', () => {
diff --git a/src/legacy/ui/public/vis/editors/default/components/agg_group_state.tsx b/src/legacy/ui/public/vis/editors/default/components/agg_group_state.tsx
index 8e8926f027cad0..980889743c20d1 100644
--- a/src/legacy/ui/public/vis/editors/default/components/agg_group_state.tsx
+++ b/src/legacy/ui/public/vis/editors/default/components/agg_group_state.tsx
@@ -53,13 +53,10 @@ function aggGroupReducer(state: AggsState, action: AggsAction): AggsState {
}
function initAggsState(group: AggConfig[]): AggsState {
- return group.reduce(
- (state, agg) => {
- state[agg.id] = { touched: false, valid: true };
- return state;
- },
- {} as AggsState
- );
+ return group.reduce((state, agg) => {
+ state[agg.id] = { touched: false, valid: true };
+ return state;
+ }, {} as AggsState);
}
export { aggGroupReducer, initAggsState };
diff --git a/src/legacy/ui/public/vis/editors/default/components/agg_params_helper.test.ts b/src/legacy/ui/public/vis/editors/default/components/agg_params_helper.test.ts
index 5fb241714b2e8c..eb6bef48876420 100644
--- a/src/legacy/ui/public/vis/editors/default/components/agg_params_helper.test.ts
+++ b/src/legacy/ui/public/vis/editors/default/components/agg_params_helper.test.ts
@@ -121,7 +121,10 @@ describe('DefaultEditorAggParams helpers', () => {
},
schema: {},
getIndexPattern: jest.fn(() => ({
- fields: [{ name: '@timestamp', type: 'date' }, { name: 'geo_desc', type: 'string' }],
+ fields: [
+ { name: '@timestamp', type: 'date' },
+ { name: 'geo_desc', type: 'string' },
+ ],
})),
params: {
orderBy: 'orderBy',
diff --git a/src/legacy/ui/public/vis/editors/default/controls/auto_precision.tsx b/src/legacy/ui/public/vis/editors/default/controls/auto_precision.tsx
index 3b6aebe8c2b0c5..53f74465e90a5e 100644
--- a/src/legacy/ui/public/vis/editors/default/controls/auto_precision.tsx
+++ b/src/legacy/ui/public/vis/editors/default/controls/auto_precision.tsx
@@ -23,7 +23,7 @@ import { EuiSwitch, EuiFormRow } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { AggParamEditorProps } from '..';
-function AutoPrecisionParamEditor({ value, setValue }: AggParamEditorProps) {
+function AutoPrecisionParamEditor({ value = false, setValue }: AggParamEditorProps) {
const label = i18n.translate('common.ui.aggTypes.changePrecisionLabel', {
defaultMessage: 'Change precision on map zoom',
});
diff --git a/src/legacy/ui/public/vis/editors/default/controls/components/number_list/utils.test.ts b/src/legacy/ui/public/vis/editors/default/controls/components/number_list/utils.test.ts
index 3928c0a62eeaa7..c6772cc1087627 100644
--- a/src/legacy/ui/public/vis/editors/default/controls/components/number_list/utils.test.ts
+++ b/src/legacy/ui/public/vis/editors/default/controls/components/number_list/utils.test.ts
@@ -35,7 +35,10 @@ describe('NumberList utils', () => {
let range: Range;
beforeEach(() => {
- modelList = [{ value: 1, id: '1', isInvalid: false }, { value: 2, id: '2', isInvalid: false }];
+ modelList = [
+ { value: 1, id: '1', isInvalid: false },
+ { value: 2, id: '2', isInvalid: false },
+ ];
range = {
min: 1,
max: 10,
diff --git a/src/legacy/ui/public/vis/editors/default/controls/filter.tsx b/src/legacy/ui/public/vis/editors/default/controls/filter.tsx
index 4ebe7b0d835d7c..e847a95ead4780 100644
--- a/src/legacy/ui/public/vis/editors/default/controls/filter.tsx
+++ b/src/legacy/ui/public/vis/editors/default/controls/filter.tsx
@@ -20,7 +20,8 @@
import React, { useState } from 'react';
import { EuiForm, EuiButtonIcon, EuiFieldText, EuiFormRow, EuiSpacer } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
-import { Query, QueryBarInput } from 'plugins/data';
+import { QueryBarInput } from 'plugins/data';
+import { Query } from 'src/plugins/data/public';
import { AggConfig } from '../../..';
import { npStart } from '../../../../new_platform';
import { Storage } from '../../../../../../../plugins/kibana_utils/public';
diff --git a/src/legacy/ui/public/vis/editors/default/controls/filters.tsx b/src/legacy/ui/public/vis/editors/default/controls/filters.tsx
index fe72e8dddd68d4..aa654d26a23fd6 100644
--- a/src/legacy/ui/public/vis/editors/default/controls/filters.tsx
+++ b/src/legacy/ui/public/vis/editors/default/controls/filters.tsx
@@ -21,7 +21,7 @@ import React, { useState, useEffect } from 'react';
import { omit, isEqual } from 'lodash';
import { htmlIdGenerator, EuiButton, EuiSpacer } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
-import { Query } from 'plugins/data';
+import { Query } from 'src/plugins/data/public';
import chrome from '../../../../chrome';
import { FilterRow } from './filter';
import { AggParamEditorProps } from '..';
diff --git a/src/legacy/ui/public/vis/editors/default/controls/switch.tsx b/src/legacy/ui/public/vis/editors/default/controls/switch.tsx
index a5fc9682bd9543..de675386d91003 100644
--- a/src/legacy/ui/public/vis/editors/default/controls/switch.tsx
+++ b/src/legacy/ui/public/vis/editors/default/controls/switch.tsx
@@ -30,7 +30,7 @@ interface SwitchParamEditorProps extends AggParamEditorProps {
}
function SwitchParamEditor({
- value,
+ value = false,
setValue,
dataTestSubj,
displayToolTip,
diff --git a/src/legacy/ui/public/vis/editors/default/controls/use_geocentroid.tsx b/src/legacy/ui/public/vis/editors/default/controls/use_geocentroid.tsx
index 6da32690912e7e..932a4d19b495c0 100644
--- a/src/legacy/ui/public/vis/editors/default/controls/use_geocentroid.tsx
+++ b/src/legacy/ui/public/vis/editors/default/controls/use_geocentroid.tsx
@@ -23,7 +23,7 @@ import { EuiSwitch, EuiFormRow } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { AggParamEditorProps } from '..';
-function UseGeocentroidParamEditor({ value, setValue }: AggParamEditorProps) {
+function UseGeocentroidParamEditor({ value = false, setValue }: AggParamEditorProps) {
const label = i18n.translate('common.ui.aggTypes.placeMarkersOffGridLabel', {
defaultMessage: 'Place markers off grid (use geocentroid)',
});
diff --git a/src/legacy/ui/public/vis/editors/default/utils.tsx b/src/legacy/ui/public/vis/editors/default/utils.tsx
index efc424488ec410..4f82298aaca41f 100644
--- a/src/legacy/ui/public/vis/editors/default/utils.tsx
+++ b/src/legacy/ui/public/vis/editors/default/utils.tsx
@@ -44,24 +44,21 @@ function groupAndSortBy<
TGroupBy extends string = 'type',
TLabelName extends string = 'title'
>(objects: T[], groupBy: TGroupBy, labelName: TLabelName): ComboBoxGroupedOptions {
- const groupedOptions = objects.reduce(
- (array, obj) => {
- const group = array.find(element => element.label === obj[groupBy]);
- const option = {
- label: obj[labelName],
- target: obj,
- };
+ const groupedOptions = objects.reduce((array, obj) => {
+ const group = array.find(element => element.label === obj[groupBy]);
+ const option = {
+ label: obj[labelName],
+ target: obj,
+ };
- if (group && group.options) {
- group.options.push(option);
- } else {
- array.push({ label: obj[groupBy], options: [option] });
- }
+ if (group && group.options) {
+ group.options.push(option);
+ } else {
+ array.push({ label: obj[groupBy], options: [option] });
+ }
- return array;
- },
- [] as Array>
- );
+ return array;
+ }, [] as Array>);
groupedOptions.sort(sortByLabel);
diff --git a/src/legacy/ui/public/vis/index.d.ts b/src/legacy/ui/public/vis/index.d.ts
index 5a2a7edb6329a0..791ce2563e0f17 100644
--- a/src/legacy/ui/public/vis/index.d.ts
+++ b/src/legacy/ui/public/vis/index.d.ts
@@ -18,5 +18,5 @@
*/
export { AggConfig } from '../agg_types/agg_config';
-export { Vis, VisProvider, VisParams, VisState } from './vis';
+export { Vis, VisParams, VisState } from './vis';
export { VisualizationController, VisType } from './vis_types/vis_type';
diff --git a/src/legacy/ui/public/vis/index.js b/src/legacy/ui/public/vis/index.js
index 711b465a1e8a53..05cd030f7d1006 100644
--- a/src/legacy/ui/public/vis/index.js
+++ b/src/legacy/ui/public/vis/index.js
@@ -17,4 +17,4 @@
* under the License.
*/
-export { VisProvider } from './vis';
+export { Vis } from './vis';
diff --git a/src/legacy/ui/public/vis/map/map_messages.js b/src/legacy/ui/public/vis/map/map_messages.js
index 7571547ecf0d48..a9a636ab9e4cfc 100644
--- a/src/legacy/ui/public/vis/map/map_messages.js
+++ b/src/legacy/ui/public/vis/map/map_messages.js
@@ -24,6 +24,7 @@ import {
EuiSpacer,
EuiButtonEmpty
} from '@elastic/eui';
+import { toMountPoint } from '../../../../../plugins/kibana_react/public';
export const createZoomWarningMsg = (function () {
let disableZoomMsg = false;
@@ -107,7 +108,7 @@ export const createZoomWarningMsg = (function () {
const zoomToast = ({
title: 'No additional zoom levels',
- text: ,
+ text: toMountPoint(),
'data-test-subj': 'maxZoomWarning',
});
diff --git a/src/legacy/ui/public/vis/vis.d.ts b/src/legacy/ui/public/vis/vis.d.ts
index be73cccf06a599..e16562641801ea 100644
--- a/src/legacy/ui/public/vis/vis.d.ts
+++ b/src/legacy/ui/public/vis/vis.d.ts
@@ -30,8 +30,6 @@ export interface Vis {
[key: string]: any;
}
-export type VisProvider = (...dependencies: any[]) => Vis;
-
export interface VisParams {
[key: string]: any;
}
diff --git a/src/legacy/ui/public/vis/vis.js b/src/legacy/ui/public/vis/vis.js
index c1fff1556e3ade..304289a5cfa073 100644
--- a/src/legacy/ui/public/vis/vis.js
+++ b/src/legacy/ui/public/vis/vis.js
@@ -38,181 +38,178 @@ import { start as visualizations } from '../../../core_plugins/visualizations/pu
import '../directives/bind';
-export function VisProvider(Private, getAppState) {
- const visTypes = visualizations.types;
-
- class Vis extends EventEmitter {
- constructor(indexPattern, visState) {
- super();
- visState = visState || {};
-
- if (_.isString(visState)) {
- visState = {
- type: visState
- };
- }
- this.indexPattern = indexPattern;
- this._setUiState(new PersistedState());
- this.setCurrentState(visState);
- this.setState(this.getCurrentState(), false);
-
- // Session state is for storing information that is transitory, and will not be saved with the visualization.
- // For instance, map bounds, which depends on the view port, browser window size, etc.
- this.sessionState = {};
-
- this.API = {
- SearchSource: SearchSource,
- events: {
- filter: data => this.eventsSubject.next({ name: 'filterBucket', data }),
- brush: data => this.eventsSubject.next({ name: 'brush', data }),
- },
- getAppState,
+const visTypes = visualizations.types;
+
+class Vis extends EventEmitter {
+ constructor(indexPattern, visState) {
+ super();
+ visState = visState || {};
+
+ if (_.isString(visState)) {
+ visState = {
+ type: visState
};
}
+ this.indexPattern = indexPattern;
+ this._setUiState(new PersistedState());
+ this.setCurrentState(visState);
+ this.setState(this.getCurrentState(), false);
+
+ // Session state is for storing information that is transitory, and will not be saved with the visualization.
+ // For instance, map bounds, which depends on the view port, browser window size, etc.
+ this.sessionState = {};
+
+ this.API = {
+ SearchSource: SearchSource,
+ events: {
+ filter: data => this.eventsSubject.next({ name: 'filterBucket', data }),
+ brush: data => this.eventsSubject.next({ name: 'brush', data }),
+ },
+ };
+ }
- setCurrentState(state) {
- this.title = state.title || '';
- const type = state.type || this.type;
- if (_.isString(type)) {
- this.type = visTypes.get(type);
- if (!this.type) {
- throw new Error(`Invalid type "${type}"`);
- }
- } else {
- this.type = type;
+ setCurrentState(state) {
+ this.title = state.title || '';
+ const type = state.type || this.type;
+ if (_.isString(type)) {
+ this.type = visTypes.get(type);
+ if (!this.type) {
+ throw new Error(`Invalid type "${type}"`);
}
+ } else {
+ this.type = type;
+ }
- this.params = _.defaults({},
- _.cloneDeep(state.params || {}),
- _.cloneDeep(this.type.visConfig.defaults || {})
- );
+ this.params = _.defaults({},
+ _.cloneDeep(state.params || {}),
+ _.cloneDeep(this.type.visConfig.defaults || {})
+ );
- updateVisualizationConfig(state.params, this.params);
+ updateVisualizationConfig(state.params, this.params);
- if (state.aggs || !this.aggs) {
- this.aggs = new AggConfigs(this.indexPattern, state.aggs ? state.aggs.aggs || state.aggs : [], this.type.schemas.all);
- }
+ if (state.aggs || !this.aggs) {
+ this.aggs = new AggConfigs(this.indexPattern, state.aggs ? state.aggs.aggs || state.aggs : [], this.type.schemas.all);
}
+ }
- setState(state, updateCurrentState = true) {
- this._state = _.cloneDeep(state);
- if (updateCurrentState) {
- this.setCurrentState(this._state);
- }
+ setState(state, updateCurrentState = true) {
+ this._state = _.cloneDeep(state);
+ if (updateCurrentState) {
+ this.setCurrentState(this._state);
}
+ }
- updateState() {
- this.setState(this.getCurrentState(true));
- this.emit('update');
- }
+ updateState() {
+ this.setState(this.getCurrentState(true));
+ this.emit('update');
+ }
- forceReload() {
- this.emit('reload');
- }
+ forceReload() {
+ this.emit('reload');
+ }
- getCurrentState(includeDisabled) {
- return {
- title: this.title,
- type: this.type.name,
- params: _.cloneDeep(this.params),
- aggs: this.aggs.aggs
- .map(agg => agg.toJSON())
- .filter(agg => includeDisabled || agg.enabled)
- .filter(Boolean)
- };
- }
+ getCurrentState(includeDisabled) {
+ return {
+ title: this.title,
+ type: this.type.name,
+ params: _.cloneDeep(this.params),
+ aggs: this.aggs.aggs
+ .map(agg => agg.toJSON())
+ .filter(agg => includeDisabled || agg.enabled)
+ .filter(Boolean)
+ };
+ }
- getSerializableState(state) {
- return {
- title: state.title,
- type: state.type,
- params: _.cloneDeep(state.params),
- aggs: state.aggs.aggs
- .map(agg => agg.toJSON())
- .filter(agg => agg.enabled)
- .filter(Boolean)
- };
- }
+ getSerializableState(state) {
+ return {
+ title: state.title,
+ type: state.type,
+ params: _.cloneDeep(state.params),
+ aggs: state.aggs.aggs
+ .map(agg => agg.toJSON())
+ .filter(agg => agg.enabled)
+ .filter(Boolean)
+ };
+ }
- copyCurrentState(includeDisabled = false) {
- const state = this.getCurrentState(includeDisabled);
- state.aggs = new AggConfigs(this.indexPattern, state.aggs.aggs || state.aggs, this.type.schemas.all);
- return state;
- }
+ copyCurrentState(includeDisabled = false) {
+ const state = this.getCurrentState(includeDisabled);
+ state.aggs = new AggConfigs(this.indexPattern, state.aggs.aggs || state.aggs, this.type.schemas.all);
+ return state;
+ }
- getStateInternal(includeDisabled) {
- return {
- title: this._state.title,
- type: this._state.type,
- params: this._state.params,
- aggs: this._state.aggs
- .filter(agg => includeDisabled || agg.enabled)
- };
- }
+ getStateInternal(includeDisabled) {
+ return {
+ title: this._state.title,
+ type: this._state.type,
+ params: this._state.params,
+ aggs: this._state.aggs
+ .filter(agg => includeDisabled || agg.enabled)
+ };
+ }
- getEnabledState() {
- return this.getStateInternal(false);
- }
+ getEnabledState() {
+ return this.getStateInternal(false);
+ }
- getAggConfig() {
- return this.aggs.clone({ enabledOnly: true });
- }
+ getAggConfig() {
+ return this.aggs.clone({ enabledOnly: true });
+ }
- getState() {
- return this.getStateInternal(true);
- }
+ getState() {
+ return this.getStateInternal(true);
+ }
- isHierarchical() {
- if (_.isFunction(this.type.hierarchicalData)) {
- return !!this.type.hierarchicalData(this);
- } else {
- return !!this.type.hierarchicalData;
- }
+ isHierarchical() {
+ if (_.isFunction(this.type.hierarchicalData)) {
+ return !!this.type.hierarchicalData(this);
+ } else {
+ return !!this.type.hierarchicalData;
}
+ }
- hasSchemaAgg(schemaName, aggTypeName) {
- const aggs = this.aggs.bySchemaName(schemaName) || [];
- return aggs.some(function (agg) {
- if (!agg.type || !agg.type.name) return false;
- return agg.type.name === aggTypeName;
- });
- }
+ hasSchemaAgg(schemaName, aggTypeName) {
+ const aggs = this.aggs.bySchemaName(schemaName) || [];
+ return aggs.some(function (agg) {
+ if (!agg.type || !agg.type.name) return false;
+ return agg.type.name === aggTypeName;
+ });
+ }
- hasUiState() {
- return !!this.__uiState;
- }
+ hasUiState() {
+ return !!this.__uiState;
+ }
- /***
- * this should not be used outside of visualize
- * @param uiState
- * @private
- */
- _setUiState(uiState) {
- if (uiState instanceof PersistedState) {
- this.__uiState = uiState;
- }
+ /***
+ * this should not be used outside of visualize
+ * @param uiState
+ * @private
+ */
+ _setUiState(uiState) {
+ if (uiState instanceof PersistedState) {
+ this.__uiState = uiState;
}
+ }
- getUiState() {
- return this.__uiState;
- }
+ getUiState() {
+ return this.__uiState;
+ }
- /**
- * Currently this is only used to extract map-specific information
- * (e.g. mapZoom, mapCenter).
- */
- uiStateVal(key, val) {
- if (this.hasUiState()) {
- if (_.isUndefined(val)) {
- return this.__uiState.get(key);
- }
- return this.__uiState.set(key, val);
+ /**
+ * Currently this is only used to extract map-specific information
+ * (e.g. mapZoom, mapCenter).
+ */
+ uiStateVal(key, val) {
+ if (this.hasUiState()) {
+ if (_.isUndefined(val)) {
+ return this.__uiState.get(key);
}
- return val;
+ return this.__uiState.set(key, val);
}
+ return val;
}
+}
- Vis.prototype.type = 'histogram';
+Vis.prototype.type = 'histogram';
- return Vis;
-}
+export { Vis };
diff --git a/src/legacy/ui/public/vis/vis_types/__tests__/vislib_vis_legend.js b/src/legacy/ui/public/vis/vis_types/__tests__/vislib_vis_legend.js
index 18eef99f05a753..afb3fea15a4307 100644
--- a/src/legacy/ui/public/vis/vis_types/__tests__/vislib_vis_legend.js
+++ b/src/legacy/ui/public/vis/vis_types/__tests__/vislib_vis_legend.js
@@ -21,7 +21,7 @@ import $ from 'jquery';
import _ from 'lodash';
import expect from '@kbn/expect';
import ngMock from 'ng_mock';
-import { VisProvider } from '../../vis';
+import { Vis } from '../../vis';
import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern';
describe('visualize_legend directive', function () {
@@ -29,7 +29,6 @@ describe('visualize_legend directive', function () {
let $compile;
let $timeout;
let $el;
- let Vis;
let indexPattern;
let fixtures;
@@ -39,7 +38,6 @@ describe('visualize_legend directive', function () {
$compile = $injector.get('$compile');
$timeout = $injector.get('$timeout');
fixtures = require('fixtures/fake_hierarchical_data');
- Vis = Private(VisProvider);
indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider);
}));
diff --git a/src/legacy/ui/public/vislib/__tests__/visualizations/pie_chart.js b/src/legacy/ui/public/vislib/__tests__/visualizations/pie_chart.js
index a4f62e6ceb4310..5692ae65af2816 100644
--- a/src/legacy/ui/public/vislib/__tests__/visualizations/pie_chart.js
+++ b/src/legacy/ui/public/vislib/__tests__/visualizations/pie_chart.js
@@ -24,7 +24,7 @@ import _ from 'lodash';
import fixtures from 'fixtures/fake_hierarchical_data';
import $ from 'jquery';
import FixturesVislibVisFixtureProvider from 'fixtures/vislib/_vis_fixture';
-import { VisProvider } from '../../../vis';
+import { Vis } from '../../../vis';
import '../../../persisted_state';
import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern';
import { vislibSlicesResponseHandlerProvider } from '../../../vis/response_handlers/vislib';
@@ -113,7 +113,6 @@ describe('No global chart settings', function () {
addTooltip: true
};
let chart1;
- let Vis;
let persistedState;
let indexPattern;
let responseHandler;
@@ -123,7 +122,6 @@ describe('No global chart settings', function () {
beforeEach(ngMock.module('kibana'));
beforeEach(ngMock.inject(function (Private, $injector) {
chart1 = Private(FixturesVislibVisFixtureProvider)(visLibParams1);
- Vis = Private(VisProvider);
persistedState = new ($injector.get('PersistedState'))();
indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider);
responseHandler = vislibSlicesResponseHandlerProvider().handler;
@@ -203,7 +201,6 @@ describe('Vislib PieChart Class Test Suite', function () {
addTooltip: true
};
let vis;
- let Vis;
let persistedState;
let indexPattern;
let data;
@@ -213,7 +210,6 @@ describe('Vislib PieChart Class Test Suite', function () {
beforeEach(ngMock.module('kibana'));
beforeEach(ngMock.inject(function (Private, $injector) {
vis = Private(FixturesVislibVisFixtureProvider)(visLibParams);
- Vis = Private(VisProvider);
persistedState = new ($injector.get('PersistedState'))();
indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider);
responseHandler = vislibSlicesResponseHandlerProvider().handler;
diff --git a/src/legacy/ui/public/visualize/loader/pipeline_helpers/build_pipeline.test.ts b/src/legacy/ui/public/visualize/loader/pipeline_helpers/build_pipeline.test.ts
index 056f3e8cfc586a..70e0c1f1382fad 100644
--- a/src/legacy/ui/public/visualize/loader/pipeline_helpers/build_pipeline.test.ts
+++ b/src/legacy/ui/public/visualize/loader/pipeline_helpers/build_pipeline.test.ts
@@ -172,7 +172,10 @@ describe('visualize loader pipeline helpers: build pipeline', () => {
const visState = { ...visStateDef, params: { foo: 'bar' } };
const schemas = {
...schemasDef,
- metric: [{ ...schemaConfig, accessor: 0 }, { ...schemaConfig, accessor: 1 }],
+ metric: [
+ { ...schemaConfig, accessor: 0 },
+ { ...schemaConfig, accessor: 1 },
+ ],
};
const actual = buildPipelineVisFunction.table(visState, schemas, uiState);
expect(actual).toMatchSnapshot();
@@ -192,7 +195,10 @@ describe('visualize loader pipeline helpers: build pipeline', () => {
const visState = { ...visStateDef, params: { foo: 'bar' } };
const schemas = {
...schemasDef,
- metric: [{ ...schemaConfig, accessor: 0 }, { ...schemaConfig, accessor: 1 }],
+ metric: [
+ { ...schemaConfig, accessor: 0 },
+ { ...schemaConfig, accessor: 1 },
+ ],
split_row: [2, 4],
bucket: [3],
};
@@ -250,7 +256,10 @@ describe('visualize loader pipeline helpers: build pipeline', () => {
const visState = { ...visStateDef, params: { metric: {} } };
const schemas = {
...schemasDef,
- metric: [{ ...schemaConfig, accessor: 0 }, { ...schemaConfig, accessor: 1 }],
+ metric: [
+ { ...schemaConfig, accessor: 0 },
+ { ...schemaConfig, accessor: 1 },
+ ],
};
const actual = buildPipelineVisFunction.metric(visState, schemas, uiState);
expect(actual).toMatchSnapshot();
@@ -260,7 +269,10 @@ describe('visualize loader pipeline helpers: build pipeline', () => {
const visState = { ...visStateDef, params: { metric: {} } };
const schemas = {
...schemasDef,
- metric: [{ ...schemaConfig, accessor: 0 }, { ...schemaConfig, accessor: 1 }],
+ metric: [
+ { ...schemaConfig, accessor: 0 },
+ { ...schemaConfig, accessor: 1 },
+ ],
group: [{ accessor: 2 }],
};
const actual = buildPipelineVisFunction.metric(visState, schemas, uiState);
diff --git a/src/legacy/ui/public/visualize/loader/utils/query_geohash_bounds.ts b/src/legacy/ui/public/visualize/loader/utils/query_geohash_bounds.ts
index 912afab74bef45..36759551a17236 100644
--- a/src/legacy/ui/public/visualize/loader/utils/query_geohash_bounds.ts
+++ b/src/legacy/ui/public/visualize/loader/utils/query_geohash_bounds.ts
@@ -22,11 +22,10 @@ import { get } from 'lodash';
import { toastNotifications } from 'ui/notify';
import { AggConfig } from 'ui/vis';
-import { Query } from 'src/legacy/core_plugins/data/public';
import { timefilter } from 'ui/timefilter';
import { Vis } from '../../../vis';
+import { esFilters, Query } from '../../../../../../plugins/data/public';
import { SearchSource } from '../../../courier';
-import { esFilters } from '../../../../../../plugins/data/public';
interface QueryGeohashBoundsParams {
filters?: esFilters.Filter[];
diff --git a/src/legacy/ui/public/visualize/loader/vis.js b/src/legacy/ui/public/visualize/loader/vis.js
index 1942fd58afebbd..29208563055d00 100644
--- a/src/legacy/ui/public/visualize/loader/vis.js
+++ b/src/legacy/ui/public/visualize/loader/vis.js
@@ -33,114 +33,109 @@ import { PersistedState } from '../../persisted_state';
import { start as visualizations } from '../../../../core_plugins/visualizations/public/np_ready/public/legacy';
-export function VisProvider(getAppState) {
- const visTypes = visualizations.types;
-
- class Vis extends EventEmitter {
- constructor(visState = { type: 'histogram' }) {
- super();
-
- this._setUiState(new PersistedState());
- this.setState(visState);
-
- // Session state is for storing information that is transitory, and will not be saved with the visualization.
- // For instance, map bounds, which depends on the view port, browser window size, etc.
- this.sessionState = {};
-
- this.API = {
- events: {
- filter: data => {
- if (!this.eventsSubject) return;
- this.eventsSubject.next({ name: 'filterBucket', data });
- },
- brush: data => {
- if (!this.eventsSubject) return;
- this.eventsSubject.next({ name: 'brush', data });
- },
+const visTypes = visualizations.types;
+
+export class Vis extends EventEmitter {
+ constructor(visState = { type: 'histogram' }) {
+ super();
+
+ this._setUiState(new PersistedState());
+ this.setState(visState);
+
+ // Session state is for storing information that is transitory, and will not be saved with the visualization.
+ // For instance, map bounds, which depends on the view port, browser window size, etc.
+ this.sessionState = {};
+
+ this.API = {
+ events: {
+ filter: data => {
+ if (!this.eventsSubject) return;
+ this.eventsSubject.next({ name: 'filterBucket', data });
},
- getAppState,
- };
- }
+ brush: data => {
+ if (!this.eventsSubject) return;
+ this.eventsSubject.next({ name: 'brush', data });
+ },
+ },
+ };
+ }
- setState(state) {
- this.title = state.title || '';
- const type = state.type || this.type;
- if (_.isString(type)) {
- this.type = visTypes.get(type);
- if (!this.type) {
- throw new Error(`Invalid type "${type}"`);
- }
- } else {
- this.type = type;
+ setState(state) {
+ this.title = state.title || '';
+ const type = state.type || this.type;
+ if (_.isString(type)) {
+ this.type = visTypes.get(type);
+ if (!this.type) {
+ throw new Error(`Invalid type "${type}"`);
}
-
- this.params = _.defaultsDeep({},
- _.cloneDeep(state.params || {}),
- _.cloneDeep(this.type.visConfig.defaults || {})
- );
+ } else {
+ this.type = type;
}
- setCurrentState(state) {
- this.setState(state);
- }
+ this.params = _.defaultsDeep({},
+ _.cloneDeep(state.params || {}),
+ _.cloneDeep(this.type.visConfig.defaults || {})
+ );
+ }
- getState() {
- return {
- title: this.title,
- type: this.type.name,
- params: _.cloneDeep(this.params),
- };
- }
+ setCurrentState(state) {
+ this.setState(state);
+ }
- updateState() {
- this.emit('update');
- }
+ getState() {
+ return {
+ title: this.title,
+ type: this.type.name,
+ params: _.cloneDeep(this.params),
+ };
+ }
- forceReload() {
- this.emit('reload');
- }
+ updateState() {
+ this.emit('update');
+ }
- isHierarchical() {
- if (_.isFunction(this.type.hierarchicalData)) {
- return !!this.type.hierarchicalData(this);
- } else {
- return !!this.type.hierarchicalData;
- }
- }
+ forceReload() {
+ this.emit('reload');
+ }
- hasUiState() {
- return !!this.__uiState;
+ isHierarchical() {
+ if (_.isFunction(this.type.hierarchicalData)) {
+ return !!this.type.hierarchicalData(this);
+ } else {
+ return !!this.type.hierarchicalData;
}
+ }
- /***
- * this should not be used outside of visualize
- * @param uiState
- * @private
- */
- _setUiState(uiState) {
- if (uiState instanceof PersistedState) {
- this.__uiState = uiState;
- }
- }
+ hasUiState() {
+ return !!this.__uiState;
+ }
- getUiState() {
- return this.__uiState;
+ /***
+ * this should not be used outside of visualize
+ * @param uiState
+ * @private
+ */
+ _setUiState(uiState) {
+ if (uiState instanceof PersistedState) {
+ this.__uiState = uiState;
}
+ }
- /**
- * Currently this is only used to extract map-specific information
- * (e.g. mapZoom, mapCenter).
- */
- uiStateVal(key, val) {
- if (this.hasUiState()) {
- if (_.isUndefined(val)) {
- return this.__uiState.get(key);
- }
- return this.__uiState.set(key, val);
+ getUiState() {
+ return this.__uiState;
+ }
+
+ /**
+ * Currently this is only used to extract map-specific information
+ * (e.g. mapZoom, mapCenter).
+ */
+ uiStateVal(key, val) {
+ if (this.hasUiState()) {
+ if (_.isUndefined(val)) {
+ return this.__uiState.get(key);
}
- return val;
+ return this.__uiState.set(key, val);
}
+ return val;
}
-
- return Vis;
}
diff --git a/src/plugins/dashboard_embeddable_container/public/actions/replace_panel_flyout.tsx b/src/plugins/dashboard_embeddable_container/public/actions/replace_panel_flyout.tsx
index 02e5f45fae3bd6..36efd0bcba676a 100644
--- a/src/plugins/dashboard_embeddable_container/public/actions/replace_panel_flyout.tsx
+++ b/src/plugins/dashboard_embeddable_container/public/actions/replace_panel_flyout.tsx
@@ -19,15 +19,9 @@
import { i18n } from '@kbn/i18n';
import React from 'react';
-import {
- EuiFlyout,
- EuiFlyoutBody,
- EuiFlyoutHeader,
- EuiTitle,
- EuiGlobalToastListToast as Toast,
-} from '@elastic/eui';
+import { EuiFlyout, EuiFlyoutBody, EuiFlyoutHeader, EuiTitle } from '@elastic/eui';
import { DashboardPanelState } from '../embeddable';
-import { NotificationsStart } from '../../../../core/public';
+import { NotificationsStart, Toast } from '../../../../core/public';
import {
IContainer,
IEmbeddable,
diff --git a/src/plugins/dashboard_embeddable_container/public/embeddable/dashboard_container.tsx b/src/plugins/dashboard_embeddable_container/public/embeddable/dashboard_container.tsx
index 8b258f35584388..6cefd11c912f19 100644
--- a/src/plugins/dashboard_embeddable_container/public/embeddable/dashboard_container.tsx
+++ b/src/plugins/dashboard_embeddable_container/public/embeddable/dashboard_container.tsx
@@ -20,7 +20,7 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { I18nProvider } from '@kbn/i18n/react';
-import { RefreshInterval, TimeRange, Query } from '../../../data/public';
+import { RefreshInterval, TimeRange, Query, esFilters } from '../../../data/public';
import { CoreStart } from '../../../../core/public';
import { IUiActionsStart } from '../ui_actions_plugin';
import {
@@ -37,7 +37,6 @@ import { createPanelState } from './panel';
import { DashboardPanelState } from './types';
import { DashboardViewport } from './viewport/dashboard_viewport';
import { Start as InspectorStartContract } from '../../../inspector/public';
-import { esFilters } from '../../../../plugins/data/public';
import {
KibanaContextProvider,
KibanaReactContext,
diff --git a/src/plugins/dashboard_embeddable_container/public/plugin.tsx b/src/plugins/dashboard_embeddable_container/public/plugin.tsx
index eadf70a36416a6..dbb5a06da9cd94 100644
--- a/src/plugins/dashboard_embeddable_container/public/plugin.tsx
+++ b/src/plugins/dashboard_embeddable_container/public/plugin.tsx
@@ -61,9 +61,10 @@ export class DashboardEmbeddableContainerPublicPlugin
const { application, notifications, overlays } = core;
const { embeddable, inspector, uiActions } = plugins;
- const SavedObjectFinder: React.FC<
- Exclude
- > = props => (
+ const SavedObjectFinder: React.FC> = props => (
string);
params?: any;
+ value?: string | ((formatter?: FilterValueFormatter) => string);
}
export interface Filter {
diff --git a/src/plugins/data/common/es_query/filters/phrase_filter.test.ts b/src/plugins/data/common/es_query/filters/phrase_filter.test.ts
index ec13e28c583d18..250ec792fbb573 100644
--- a/src/plugins/data/common/es_query/filters/phrase_filter.test.ts
+++ b/src/plugins/data/common/es_query/filters/phrase_filter.test.ts
@@ -18,7 +18,7 @@
*/
import { buildInlineScriptForPhraseFilter, buildPhraseFilter } from './phrase_filter';
-import { IndexPattern } from './types';
+import { IndexPattern } from '../../types';
import { getField } from '../__tests__/fields_mock';
describe('Phrase filter builder', () => {
diff --git a/src/plugins/data/common/es_query/filters/phrase_filter.ts b/src/plugins/data/common/es_query/filters/phrase_filter.ts
index 15c5c9d4ad2e6d..35110c924fe611 100644
--- a/src/plugins/data/common/es_query/filters/phrase_filter.ts
+++ b/src/plugins/data/common/es_query/filters/phrase_filter.ts
@@ -19,7 +19,7 @@
import { get, isPlainObject } from 'lodash';
import { Filter, FilterMeta } from './meta_filter';
-import { IndexPattern, Field } from './types';
+import { IndexPattern, Field } from '../../types';
export type PhraseFilterMeta = FilterMeta & {
params?: {
diff --git a/src/plugins/data/common/es_query/filters/phrases_filter.ts b/src/plugins/data/common/es_query/filters/phrases_filter.ts
index e4606695c0f6a5..e207a3ff5961be 100644
--- a/src/plugins/data/common/es_query/filters/phrases_filter.ts
+++ b/src/plugins/data/common/es_query/filters/phrases_filter.ts
@@ -18,7 +18,7 @@
*/
import { Filter, FilterMeta } from './meta_filter';
-import { Field, IndexPattern } from './types';
+import { Field, IndexPattern } from '../../types';
import { getPhraseScript } from './phrase_filter';
export type PhrasesFilterMeta = FilterMeta & {
diff --git a/src/plugins/data/common/es_query/filters/query_string_filter.test.ts b/src/plugins/data/common/es_query/filters/query_string_filter.test.ts
index 839e4f6359257e..5a580db0c57b82 100644
--- a/src/plugins/data/common/es_query/filters/query_string_filter.test.ts
+++ b/src/plugins/data/common/es_query/filters/query_string_filter.test.ts
@@ -18,7 +18,7 @@
*/
import { buildQueryFilter } from './query_string_filter';
-import { IndexPattern } from './types';
+import { IndexPattern } from '../../types';
describe('Phrase filter builder', () => {
let indexPattern: IndexPattern;
diff --git a/src/plugins/data/common/es_query/filters/query_string_filter.ts b/src/plugins/data/common/es_query/filters/query_string_filter.ts
index 901dc724aa4e49..d2374162b195f4 100644
--- a/src/plugins/data/common/es_query/filters/query_string_filter.ts
+++ b/src/plugins/data/common/es_query/filters/query_string_filter.ts
@@ -18,7 +18,7 @@
*/
import { Filter, FilterMeta } from './meta_filter';
-import { IndexPattern } from './types';
+import { IndexPattern } from '../../types';
export type QueryStringFilterMeta = FilterMeta;
diff --git a/src/plugins/data/common/es_query/filters/range_filter.test.ts b/src/plugins/data/common/es_query/filters/range_filter.test.ts
index 9008dc2a672944..017bb8e9cb7c5b 100644
--- a/src/plugins/data/common/es_query/filters/range_filter.test.ts
+++ b/src/plugins/data/common/es_query/filters/range_filter.test.ts
@@ -19,7 +19,7 @@
import { each } from 'lodash';
import { buildRangeFilter, RangeFilter } from './range_filter';
-import { IndexPattern, Field } from './types';
+import { IndexPattern, Field } from '../../types';
import { getField } from '../__tests__/fields_mock';
describe('Range filter builder', () => {
diff --git a/src/plugins/data/common/es_query/filters/range_filter.ts b/src/plugins/data/common/es_query/filters/range_filter.ts
index d7931f191e52b1..c2513a9dc0c5e1 100644
--- a/src/plugins/data/common/es_query/filters/range_filter.ts
+++ b/src/plugins/data/common/es_query/filters/range_filter.ts
@@ -18,7 +18,7 @@
*/
import { map, reduce, mapValues, get, keys, pick } from 'lodash';
import { Filter, FilterMeta } from './meta_filter';
-import { Field, IndexPattern } from './types';
+import { Field, IndexPattern } from '../../types';
const OPERANDS_IN_RANGE = 2;
diff --git a/src/plugins/data/common/es_query/filters/types.ts b/src/plugins/data/common/es_query/filters/types.ts
index 28147350619995..a242df4811c059 100644
--- a/src/plugins/data/common/es_query/filters/types.ts
+++ b/src/plugins/data/common/es_query/filters/types.ts
@@ -49,9 +49,3 @@ export enum FILTERS {
GEO_BOUNDING_BOX = 'geo_bounding_box',
GEO_POLYGON = 'geo_polygon',
}
-
-// We can't import the real types from the data plugin, so need to either duplicate
-// them here or figure out another solution, perhaps housing them in this package
-// will be replaces after Fieds / IndexPattern will be moved into new platform
-export type Field = any;
-export type IndexPattern = any;
diff --git a/src/plugins/data/common/types.ts b/src/plugins/data/common/types.ts
index 9eda75d8abd0b6..ec8d8b006317fd 100644
--- a/src/plugins/data/common/types.ts
+++ b/src/plugins/data/common/types.ts
@@ -21,3 +21,10 @@ export * from './field_formats/types';
export * from './timefilter/types';
export * from './query/types';
export * from './kbn_field_types/types';
+
+// We can't import the real types from the data plugin, so need to either duplicate
+// them here or figure out another solution, perhaps housing them in this package
+// will be replaces after Fieds / IndexPattern will be moved into new platform
+export type Field = any;
+export type IndexPattern = any;
+export type StaticIndexPattern = any;
diff --git a/src/plugins/data/public/autocomplete_provider/types.ts b/src/plugins/data/public/autocomplete_provider/types.ts
index 1f2d8f914dde3e..d838e54e9ead49 100644
--- a/src/plugins/data/public/autocomplete_provider/types.ts
+++ b/src/plugins/data/public/autocomplete_provider/types.ts
@@ -17,8 +17,8 @@
* under the License.
*/
-import { StaticIndexPattern, Field } from 'ui/index_patterns';
import { AutocompleteProviderRegister } from '.';
+import { Field, StaticIndexPattern } from '..';
export type AutocompletePublicPluginSetup = Pick<
AutocompleteProviderRegister,
diff --git a/src/plugins/data/public/index_patterns/field.stub.ts b/src/plugins/data/public/index_patterns/field.stub.ts
new file mode 100644
index 00000000000000..315894cd212c40
--- /dev/null
+++ b/src/plugins/data/public/index_patterns/field.stub.ts
@@ -0,0 +1,79 @@
+/*
+ * 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 { Field } from '../../common';
+
+export const stubFields: Field[] = [
+ {
+ name: 'machine.os',
+ esTypes: ['text'],
+ type: 'string',
+ aggregatable: false,
+ searchable: false,
+ filterable: true,
+ },
+ {
+ name: 'machine.os.raw',
+ type: 'string',
+ esTypes: ['keyword'],
+ aggregatable: true,
+ searchable: true,
+ filterable: true,
+ },
+ {
+ name: 'not.filterable',
+ type: 'string',
+ esTypes: ['text'],
+ aggregatable: true,
+ searchable: false,
+ filterable: false,
+ },
+ {
+ name: 'bytes',
+ type: 'number',
+ esTypes: ['long'],
+ aggregatable: true,
+ searchable: true,
+ filterable: true,
+ },
+ {
+ name: '@timestamp',
+ type: 'date',
+ esTypes: ['date'],
+ aggregatable: true,
+ searchable: true,
+ filterable: true,
+ },
+ {
+ name: 'clientip',
+ type: 'ip',
+ esTypes: ['ip'],
+ aggregatable: true,
+ searchable: true,
+ filterable: true,
+ },
+ {
+ name: 'bool.field',
+ type: 'boolean',
+ esTypes: ['boolean'],
+ aggregatable: true,
+ searchable: true,
+ filterable: true,
+ },
+];
diff --git a/src/legacy/core_plugins/console/public/quarantined/hacks/register.js b/src/plugins/data/public/index_patterns/index_pattern.stub.ts
similarity index 72%
rename from src/legacy/core_plugins/console/public/quarantined/hacks/register.js
rename to src/plugins/data/public/index_patterns/index_pattern.stub.ts
index b5df1c1af99c5b..444e65cd0cd4b9 100644
--- a/src/legacy/core_plugins/console/public/quarantined/hacks/register.js
+++ b/src/plugins/data/public/index_patterns/index_pattern.stub.ts
@@ -17,14 +17,12 @@
* under the License.
*/
-import { DevToolsRegistryProvider } from 'ui/registry/dev_tools';
-import { i18n } from '@kbn/i18n';
+import { IndexPattern } from '../../common';
+import { stubFields } from './field.stub';
-DevToolsRegistryProvider.register(() => ({
- order: 1,
- name: 'console',
- display: i18n.translate('console.consoleDisplayName', {
- defaultMessage: 'Console',
- }),
- url: '#/dev_tools/console',
-}));
+export const stubIndexPattern: IndexPattern = {
+ id: 'logstash-*',
+ fields: stubFields,
+ title: 'logstash-*',
+ timeFieldName: '@timestamp',
+};
diff --git a/src/plugins/data/public/query/filter_manager/index.ts b/src/plugins/data/public/query/filter_manager/index.ts
index 7955cdd825ee6c..ce7a479151797c 100644
--- a/src/plugins/data/public/query/filter_manager/index.ts
+++ b/src/plugins/data/public/query/filter_manager/index.ts
@@ -22,3 +22,4 @@ export { FilterManager } from './filter_manager';
export { uniqFilters } from './lib/uniq_filters';
export { mapAndFlattenFilters } from './lib/map_and_flatten_filters';
export { onlyDisabledFiltersChanged } from './lib/only_disabled';
+export { generateFilters } from './lib/generate_filters';
diff --git a/src/plugins/data/public/query/filter_manager/lib/generate_filter.test.ts b/src/plugins/data/public/query/filter_manager/lib/generate_filter.test.ts
new file mode 100644
index 00000000000000..46cf0fd9c111ec
--- /dev/null
+++ b/src/plugins/data/public/query/filter_manager/lib/generate_filter.test.ts
@@ -0,0 +1,130 @@
+/*
+ * 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 { generateFilters } from './generate_filters';
+import { FilterManager } from '../filter_manager';
+import { esFilters } from '../../..';
+
+const INDEX_NAME = 'my-index';
+const EXISTS_FIELD_NAME = '_exists_';
+const FIELD = {
+ name: 'my-field',
+};
+const PHRASE_VALUE = 'my-value';
+
+describe('Generate filters', () => {
+ let mockFilterManager: FilterManager;
+ let filtersArray: esFilters.Filter[];
+
+ beforeEach(() => {
+ filtersArray = [];
+ mockFilterManager = {
+ getAppFilters: () => {
+ return filtersArray;
+ },
+ } as FilterManager;
+ });
+
+ it('should create exists filter', () => {
+ const filters = generateFilters(
+ mockFilterManager,
+ EXISTS_FIELD_NAME,
+ FIELD.name,
+ '',
+ INDEX_NAME
+ );
+ expect(filters).toHaveLength(1);
+ expect(filters[0].meta.index === INDEX_NAME);
+ expect(filters[0].meta.negate).toBeFalsy();
+ expect(esFilters.isExistsFilter(filters[0])).toBeTruthy();
+ });
+
+ it('should create negated exists filter', () => {
+ const filters = generateFilters(
+ mockFilterManager,
+ EXISTS_FIELD_NAME,
+ FIELD.name,
+ '-',
+ INDEX_NAME
+ );
+ expect(filters).toHaveLength(1);
+ expect(filters[0].meta.index === INDEX_NAME);
+ expect(filters[0].meta.negate).toBeTruthy();
+ expect(esFilters.isExistsFilter(filters[0])).toBeTruthy();
+ });
+
+ it('should update and re-enable EXISTING exists filter', () => {
+ const filter = esFilters.buildExistsFilter(FIELD, { id: INDEX_NAME });
+ filter.meta.disabled = true;
+ filtersArray.push(filter);
+
+ const filters = generateFilters(mockFilterManager, '_exists_', FIELD.name, '-', INDEX_NAME);
+ expect(filters).toHaveLength(1);
+ expect(filters[0].meta.index === INDEX_NAME);
+ expect(filters[0].meta.negate).toBeTruthy();
+ expect(filters[0].meta.disabled).toBeFalsy();
+ expect(esFilters.isExistsFilter(filters[0])).toBeTruthy();
+ });
+
+ it('should create phrase filter', () => {
+ const filters = generateFilters(mockFilterManager, FIELD, PHRASE_VALUE, '', INDEX_NAME);
+ expect(filters).toHaveLength(1);
+ expect(filters[0].meta.index === INDEX_NAME);
+ expect(filters[0].meta.negate).toBeFalsy();
+ expect(esFilters.isPhraseFilter(filters[0])).toBeTruthy();
+ expect((filters[0] as esFilters.PhraseFilter).query.match_phrase).toEqual({
+ [FIELD.name]: PHRASE_VALUE,
+ });
+ });
+
+ it('should create negated phrase filter', () => {
+ const filters = generateFilters(mockFilterManager, FIELD, PHRASE_VALUE, '-', INDEX_NAME);
+ expect(filters).toHaveLength(1);
+ expect(filters[0].meta.index === INDEX_NAME);
+ expect(filters[0].meta.negate).toBeTruthy();
+ expect(esFilters.isPhraseFilter(filters[0])).toBeTruthy();
+ expect((filters[0] as esFilters.PhraseFilter).query.match_phrase).toEqual({
+ [FIELD.name]: PHRASE_VALUE,
+ });
+ });
+
+ it('should create multiple phrase filters', () => {
+ const ANOTHER_PHRASE = 'another-value';
+ const filters = generateFilters(
+ mockFilterManager,
+ FIELD,
+ [PHRASE_VALUE, ANOTHER_PHRASE],
+ '',
+ INDEX_NAME
+ );
+ expect(filters).toHaveLength(2);
+ expect(filters[0].meta.index === INDEX_NAME);
+ expect(filters[0].meta.negate).toBeFalsy();
+ expect(filters[1].meta.index === INDEX_NAME);
+ expect(filters[1].meta.negate).toBeFalsy();
+ expect(esFilters.isPhraseFilter(filters[0])).toBeTruthy();
+ expect(esFilters.isPhraseFilter(filters[1])).toBeTruthy();
+ expect((filters[0] as esFilters.PhraseFilter).query.match_phrase).toEqual({
+ [FIELD.name]: PHRASE_VALUE,
+ });
+ expect((filters[1] as esFilters.PhraseFilter).query.match_phrase).toEqual({
+ [FIELD.name]: ANOTHER_PHRASE,
+ });
+ });
+});
diff --git a/src/plugins/data/public/query/filter_manager/lib/generate_filters.ts b/src/plugins/data/public/query/filter_manager/lib/generate_filters.ts
new file mode 100644
index 00000000000000..5c4cdc2717338f
--- /dev/null
+++ b/src/plugins/data/public/query/filter_manager/lib/generate_filters.ts
@@ -0,0 +1,112 @@
+/*
+ * 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 _ from 'lodash';
+import { FilterManager, esFilters, Field } from '../../..';
+
+function getExistingFilter(
+ appFilters: esFilters.Filter[],
+ fieldName: string,
+ value: any
+): esFilters.Filter | undefined {
+ // TODO: On array fields, negating does not negate the combination, rather all terms
+ return _.find(appFilters, function(filter) {
+ if (!filter) return;
+
+ if (fieldName === '_exists_' && esFilters.isExistsFilter(filter)) {
+ return filter.exists!.field === value;
+ }
+
+ if (esFilters.isPhraseFilter(filter)) {
+ return (
+ esFilters.getPhraseFilterField(filter) === fieldName &&
+ esFilters.getPhraseFilterValue(filter) === value
+ );
+ }
+
+ if (esFilters.isScriptedPhraseFilter(filter)) {
+ return filter.meta.field === fieldName && filter.meta.script!.script.params.value === value;
+ }
+ });
+}
+
+function updateExistingFilter(existingFilter: esFilters.Filter, negate: boolean) {
+ existingFilter.meta.disabled = false;
+ if (existingFilter.meta.negate !== negate) {
+ existingFilter.meta.negate = !existingFilter.meta.negate;
+ }
+}
+
+/**
+ * Generate filter objects, as a result of triggering a filter action on a
+ * specific index pattern field.
+ *
+ * @param {FilterManager} filterManager - The active filter manager to lookup for existing filters
+ * @param {Field | string} field - The field for which filters should be generated
+ * @param {any} values - One or more values to filter for.
+ * @param {string} operation - "-" to create a negated filter
+ * @param {string} index - Index string to generate filters for
+ *
+ * @returns {object} An array of filters to be added back to filterManager
+ */
+export function generateFilters(
+ filterManager: FilterManager,
+ field: Field | string,
+ values: any,
+ operation: string,
+ index: string
+): esFilters.Filter[] {
+ values = Array.isArray(values) ? values : [values];
+ const fieldObj = _.isObject(field)
+ ? field
+ : {
+ name: field,
+ };
+ const fieldName = fieldObj.name;
+ const newFilters: esFilters.Filter[] = [];
+ const appFilters = filterManager.getAppFilters();
+
+ const negate = operation === '-';
+ let filter;
+
+ _.each(values, function(value) {
+ const existing = getExistingFilter(appFilters, fieldName, value);
+
+ if (existing) {
+ updateExistingFilter(existing, negate);
+ filter = existing;
+ } else {
+ const tmpIndexPattern = { id: index };
+ switch (fieldName) {
+ case '_exists_':
+ filter = esFilters.buildExistsFilter(fieldObj, tmpIndexPattern);
+ break;
+ default:
+ filter = esFilters.buildPhraseFilter(fieldObj, value, tmpIndexPattern);
+ break;
+ }
+
+ filter.meta.negate = negate;
+ }
+
+ newFilters.push(filter);
+ });
+
+ return newFilters;
+}
diff --git a/src/plugins/data/public/query/filter_manager/lib/mappers/map_geo_polygon.test.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_geo_polygon.test.ts
index 3afa3891a24bb8..1847296016c732 100644
--- a/src/plugins/data/public/query/filter_manager/lib/mappers/map_geo_polygon.test.ts
+++ b/src/plugins/data/public/query/filter_manager/lib/mappers/map_geo_polygon.test.ts
@@ -30,7 +30,10 @@ describe('filter manager utilities', () => {
},
geo_polygon: {
point: {
- points: [{ lat: 5, lon: 10 }, { lat: 15, lon: 20 }],
+ points: [
+ { lat: 5, lon: 10 },
+ { lat: 15, lon: 20 },
+ ],
},
},
} as esFilters.GeoPolygonFilter;
diff --git a/src/plugins/data/public/query/index.tsx b/src/plugins/data/public/query/index.tsx
index 9d7c2ffc56f70b..224c2f3a04076b 100644
--- a/src/plugins/data/public/query/index.tsx
+++ b/src/plugins/data/public/query/index.tsx
@@ -17,6 +17,8 @@
* under the License.
*/
+export * from './lib';
+
export * from './query_service';
export * from './filter_manager';
diff --git a/src/plugins/data/public/query/lib/from_user.test.ts b/src/plugins/data/public/query/lib/from_user.test.ts
new file mode 100644
index 00000000000000..b74a1a07dc356d
--- /dev/null
+++ b/src/plugins/data/public/query/lib/from_user.test.ts
@@ -0,0 +1,45 @@
+/*
+ * 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 { fromUser } from '../';
+
+describe('user input helpers', function() {
+ describe('user input parser', function() {
+ it('should return the input if passed an object', function() {
+ expect(fromUser({ foo: 'bar' })).toEqual({ foo: 'bar' });
+ });
+
+ it('unless the object is empty, then convert it to an empty string', function() {
+ expect(fromUser({})).toEqual('');
+ });
+
+ it('should pass through input strings that not start with {', function() {
+ expect(fromUser('foo')).toEqual('foo');
+ expect(fromUser('400')).toEqual('400');
+ expect(fromUser('true')).toEqual('true');
+ });
+
+ it('should parse valid JSON and return the object instead of a string', function() {
+ expect(fromUser('{}')).toEqual({});
+
+ // invalid json remains a string
+ expect(fromUser('{a:b}')).toEqual('{a:b}');
+ });
+ });
+});
diff --git a/src/legacy/core_plugins/data/public/query/query_bar/lib/from_user.ts b/src/plugins/data/public/query/lib/from_user.ts
similarity index 96%
rename from src/legacy/core_plugins/data/public/query/query_bar/lib/from_user.ts
rename to src/plugins/data/public/query/lib/from_user.ts
index 15eebaa0b9fd63..fbb1726fc99ea2 100644
--- a/src/legacy/core_plugins/data/public/query/query_bar/lib/from_user.ts
+++ b/src/plugins/data/public/query/lib/from_user.ts
@@ -28,6 +28,10 @@ import _ from 'lodash';
export function fromUser(userInput: object | string) {
const matchAll = '';
+ if (_.isEmpty(userInput)) {
+ return '';
+ }
+
if (_.isObject(userInput)) {
return userInput;
}
diff --git a/src/legacy/core_plugins/data/public/query/query_bar/lib/get_query_log.ts b/src/plugins/data/public/query/lib/get_query_log.ts
similarity index 94%
rename from src/legacy/core_plugins/data/public/query/query_bar/lib/get_query_log.ts
rename to src/plugins/data/public/query/lib/get_query_log.ts
index 66424d9a1d6a35..67073a9078046c 100644
--- a/src/legacy/core_plugins/data/public/query/query_bar/lib/get_query_log.ts
+++ b/src/plugins/data/public/query/lib/get_query_log.ts
@@ -19,7 +19,7 @@
import { UiSettingsClientContract } from 'src/core/public';
import { IStorageWrapper } from 'src/plugins/kibana_utils/public';
-import { PersistedLog } from '../../../../../../../plugins/data/public';
+import { PersistedLog } from '../persisted_log';
export function getQueryLog(
uiSettings: UiSettingsClientContract,
diff --git a/src/legacy/core_plugins/data/public/query/query_bar/lib/index.ts b/src/plugins/data/public/query/lib/index.ts
similarity index 93%
rename from src/legacy/core_plugins/data/public/query/query_bar/lib/index.ts
rename to src/plugins/data/public/query/lib/index.ts
index 852f9fd269b32b..19ac37fb59ae75 100644
--- a/src/legacy/core_plugins/data/public/query/query_bar/lib/index.ts
+++ b/src/plugins/data/public/query/lib/index.ts
@@ -20,3 +20,5 @@
export * from './match_pairs';
export * from './from_user';
export * from './to_user';
+export * from './to_user';
+export * from './get_query_log';
diff --git a/src/legacy/core_plugins/data/public/query/query_bar/lib/match_pairs.ts b/src/plugins/data/public/query/lib/match_pairs.ts
similarity index 100%
rename from src/legacy/core_plugins/data/public/query/query_bar/lib/match_pairs.ts
rename to src/plugins/data/public/query/lib/match_pairs.ts
diff --git a/src/plugins/data/public/query/lib/to_user.test.ts b/src/plugins/data/public/query/lib/to_user.test.ts
new file mode 100644
index 00000000000000..d13afa251ecb1d
--- /dev/null
+++ b/src/plugins/data/public/query/lib/to_user.test.ts
@@ -0,0 +1,46 @@
+/*
+ * 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 { toUser } from '../';
+
+describe('user input helpers', function() {
+ describe('model presentation formatter', function() {
+ it('should present objects as strings', function() {
+ expect(toUser({ foo: 'bar' })).toBe('{"foo":"bar"}');
+ });
+
+ it('should present query_string queries as strings', function() {
+ expect(toUser({ query_string: { query: 'lucene query string' } })).toBe(
+ 'lucene query string'
+ );
+ });
+
+ it('should present query_string queries without a query as an empty string', function() {
+ expect(toUser({ query_string: {} })).toBe('');
+ });
+
+ it('should present string as strings', function() {
+ expect(toUser('foo')).toBe('foo');
+ });
+
+ it('should present numbers as strings', function() {
+ expect(toUser(400)).toBe('400');
+ });
+ });
+});
diff --git a/src/legacy/core_plugins/data/public/query/query_bar/lib/to_user.ts b/src/plugins/data/public/query/lib/to_user.ts
similarity index 89%
rename from src/legacy/core_plugins/data/public/query/query_bar/lib/to_user.ts
rename to src/plugins/data/public/query/lib/to_user.ts
index 1eb054ba405142..1fdb2d8ed03df1 100644
--- a/src/legacy/core_plugins/data/public/query/query_bar/lib/to_user.ts
+++ b/src/plugins/data/public/query/lib/to_user.ts
@@ -17,14 +17,12 @@
* under the License.
*/
-import angular from 'angular';
-
/**
* Take text from the model and present it to the user as a string
* @param text model value
* @returns {string}
*/
-export function toUser(text: { [key: string]: any } | string): string {
+export function toUser(text: { [key: string]: any } | string | number): string {
if (text == null) {
return '';
}
@@ -35,7 +33,7 @@ export function toUser(text: { [key: string]: any } | string): string {
if (text.query_string) {
return toUser(text.query_string.query);
}
- return angular.toJson(text);
+ return JSON.stringify(text);
}
return '' + text;
}
diff --git a/src/plugins/data/public/query/persisted_log/persisted_log.test.ts b/src/plugins/data/public/query/persisted_log/persisted_log.test.ts
index e0bc8f2c3525f5..87c1ec29c1aee1 100644
--- a/src/plugins/data/public/query/persisted_log/persisted_log.test.ts
+++ b/src/plugins/data/public/query/persisted_log/persisted_log.test.ts
@@ -36,12 +36,6 @@ const createMockStorage = () => ({
clear: jest.fn(),
});
-jest.mock('ui/chrome', () => {
- return {
- getBasePath: () => `/some/base/path`,
- };
-});
-
const historyName = 'testHistory';
const historyLimit = 10;
const payload = [
diff --git a/src/plugins/data/public/search/create_app_mount_context_search.test.ts b/src/plugins/data/public/search/create_app_mount_context_search.test.ts
index deab9af4e3a018..fa7cdbcda3082b 100644
--- a/src/plugins/data/public/search/create_app_mount_context_search.test.ts
+++ b/src/plugins/data/public/search/create_app_mount_context_search.test.ts
@@ -62,8 +62,10 @@ describe('Create app mount search context', () => {
});
},
});
- context
- .search({ greeting: 'hi' } as any, {}, 'mysearch')
- .subscribe(response => {}, () => {}, done);
+ context.search({ greeting: 'hi' } as any, {}, 'mysearch').subscribe(
+ response => {},
+ () => {},
+ done
+ );
});
});
diff --git a/src/plugins/data/public/search/es_search/es_search_strategy.ts b/src/plugins/data/public/search/es_search/es_search_strategy.ts
index 643ded120799e0..d29f3b6882b26b 100644
--- a/src/plugins/data/public/search/es_search/es_search_strategy.ts
+++ b/src/plugins/data/public/search/es_search/es_search_strategy.ts
@@ -20,6 +20,7 @@
import { Observable } from 'rxjs';
import { ES_SEARCH_STRATEGY, IEsSearchResponse } from '../../../common/search';
import { SYNC_SEARCH_STRATEGY } from '../sync_search_strategy';
+import { getEsPreference } from './get_es_preference';
import { TSearchStrategyProvider, ISearchStrategy, ISearchGeneric, ISearchContext } from '..';
export const esSearchStrategyProvider: TSearchStrategyProvider = (
@@ -27,11 +28,17 @@ export const esSearchStrategyProvider: TSearchStrategyProvider => {
return {
- search: (request, options) =>
- search(
+ search: (request, options) => {
+ if (typeof request.params.preference === 'undefined') {
+ const setPreference = context.core.uiSettings.get('courier:setRequestPreference');
+ const customPreference = context.core.uiSettings.get('courier:customRequestPreference');
+ request.params.preference = getEsPreference(setPreference, customPreference);
+ }
+ return search(
{ ...request, serverStrategy: ES_SEARCH_STRATEGY },
options,
SYNC_SEARCH_STRATEGY
- ) as Observable,
+ ) as Observable;
+ },
};
};
diff --git a/src/plugins/data/public/search/es_search/get_es_preference.test.ts b/src/plugins/data/public/search/es_search/get_es_preference.test.ts
new file mode 100644
index 00000000000000..27e6f9b48bbdd5
--- /dev/null
+++ b/src/plugins/data/public/search/es_search/get_es_preference.test.ts
@@ -0,0 +1,46 @@
+/*
+ * 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 { getEsPreference } from './get_es_preference';
+
+jest.useFakeTimers();
+
+describe('Get ES preference', () => {
+ test('returns the session ID if set to sessionId', () => {
+ const setPreference = 'sessionId';
+ const customPreference = 'foobar';
+ const sessionId = 'my_session_id';
+ const preference = getEsPreference(setPreference, customPreference, sessionId);
+ expect(preference).toBe(sessionId);
+ });
+
+ test('returns the custom preference if set to custom', () => {
+ const setPreference = 'custom';
+ const customPreference = 'foobar';
+ const preference = getEsPreference(setPreference, customPreference);
+ expect(preference).toBe(customPreference);
+ });
+
+ test('returns undefined if set to none', () => {
+ const setPreference = 'none';
+ const customPreference = 'foobar';
+ const preference = getEsPreference(setPreference, customPreference);
+ expect(preference).toBe(undefined);
+ });
+});
diff --git a/src/plugins/data/public/search/es_search/get_es_preference.ts b/src/plugins/data/public/search/es_search/get_es_preference.ts
new file mode 100644
index 00000000000000..200e5bacb7f182
--- /dev/null
+++ b/src/plugins/data/public/search/es_search/get_es_preference.ts
@@ -0,0 +1,29 @@
+/*
+ * 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 defaultSessionId = `${Date.now()}`;
+
+export function getEsPreference(
+ setRequestPreference: string,
+ customRequestPreference?: string,
+ sessionId: string = defaultSessionId
+) {
+ if (setRequestPreference === 'sessionId') return `${sessionId}`;
+ return setRequestPreference === 'custom' ? customRequestPreference : undefined;
+}
diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/actions/filter.js b/src/plugins/data/public/stubs.ts
similarity index 80%
rename from src/legacy/core_plugins/kibana/public/discover/angular/doc_table/actions/filter.js
rename to src/plugins/data/public/stubs.ts
index 1a2854ec154120..40a5e7d18f8d9f 100644
--- a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/actions/filter.js
+++ b/src/plugins/data/public/stubs.ts
@@ -17,10 +17,5 @@
* under the License.
*/
-export function addFilter(field, values = [], operation, index, state, filterGen) {
- if (!Array.isArray(values)) {
- values = [values];
- }
-
- filterGen.add(field, values, operation, index);
-}
+export { stubIndexPattern } from './index_patterns/index_pattern.stub';
+export { stubFields } from './index_patterns/field.stub';
diff --git a/src/plugins/data/public/suggestions_provider/types.ts b/src/plugins/data/public/suggestions_provider/types.ts
index eac380dde6a62f..988b5fcd43fa89 100644
--- a/src/plugins/data/public/suggestions_provider/types.ts
+++ b/src/plugins/data/public/suggestions_provider/types.ts
@@ -16,8 +16,6 @@
* specific language governing permissions and limitations
* under the License.
*/
-
-// Should be import { Field } from './index_patterns';
-export type Field = any;
+import { Field } from '..';
export type IGetSuggestions = (index: string, field: Field, query: string, boolFilter?: any) => any;
diff --git a/src/plugins/data/public/suggestions_provider/value_suggestions.test.ts b/src/plugins/data/public/suggestions_provider/value_suggestions.test.ts
index 13ccbbd9f3ddea..7dc8ff0fe133d1 100644
--- a/src/plugins/data/public/suggestions_provider/value_suggestions.test.ts
+++ b/src/plugins/data/public/suggestions_provider/value_suggestions.test.ts
@@ -19,9 +19,8 @@
// TODO: remove when index patterns are moved here.
jest.mock('ui/new_platform');
-jest.mock('ui/index_patterns');
-import { mockFields, mockIndexPattern } from 'ui/index_patterns';
+import { stubIndexPattern, stubFields } from '../stubs';
import { getSuggestionsProvider } from './value_suggestions';
import { UiSettingsClientContract } from 'kibana/public';
@@ -37,8 +36,8 @@ describe('getSuggestions', () => {
});
it('should return an empty array', async () => {
- const index = mockIndexPattern.id;
- const [field] = mockFields;
+ const index = stubIndexPattern.id;
+ const [field] = stubFields;
const query = '';
const suggestions = await getSuggestions(index, field, query);
expect(suggestions).toEqual([]);
@@ -54,8 +53,8 @@ describe('getSuggestions', () => {
});
it('should return true/false for boolean fields', async () => {
- const index = mockIndexPattern.id;
- const [field] = mockFields.filter(({ type }) => type === 'boolean');
+ const index = stubIndexPattern.id;
+ const [field] = stubFields.filter(({ type }) => type === 'boolean');
const query = '';
const suggestions = await getSuggestions(index, field, query);
expect(suggestions).toEqual([true, false]);
@@ -63,8 +62,8 @@ describe('getSuggestions', () => {
});
it('should return an empty array if the field type is not a string or boolean', async () => {
- const index = mockIndexPattern.id;
- const [field] = mockFields.filter(({ type }) => type !== 'string' && type !== 'boolean');
+ const index = stubIndexPattern.id;
+ const [field] = stubFields.filter(({ type }) => type !== 'string' && type !== 'boolean');
const query = '';
const suggestions = await getSuggestions(index, field, query);
expect(suggestions).toEqual([]);
@@ -72,8 +71,8 @@ describe('getSuggestions', () => {
});
it('should return an empty array if the field is not aggregatable', async () => {
- const index = mockIndexPattern.id;
- const [field] = mockFields.filter(({ aggregatable }) => !aggregatable);
+ const index = stubIndexPattern.id;
+ const [field] = stubFields.filter(({ aggregatable }) => !aggregatable);
const query = '';
const suggestions = await getSuggestions(index, field, query);
expect(suggestions).toEqual([]);
@@ -81,8 +80,8 @@ describe('getSuggestions', () => {
});
it('should otherwise request suggestions', async () => {
- const index = mockIndexPattern.id;
- const [field] = mockFields.filter(
+ const index = stubIndexPattern.id;
+ const [field] = stubFields.filter(
({ type, aggregatable }) => type === 'string' && aggregatable
);
const query = '';
@@ -91,8 +90,8 @@ describe('getSuggestions', () => {
});
it('should cache results if using the same index/field/query/filter', async () => {
- const index = mockIndexPattern.id;
- const [field] = mockFields.filter(
+ const index = stubIndexPattern.id;
+ const [field] = stubFields.filter(
({ type, aggregatable }) => type === 'string' && aggregatable
);
const query = '';
@@ -102,8 +101,8 @@ describe('getSuggestions', () => {
});
it('should cache results for only one minute', async () => {
- const index = mockIndexPattern.id;
- const [field] = mockFields.filter(
+ const index = stubIndexPattern.id;
+ const [field] = stubFields.filter(
({ type, aggregatable }) => type === 'string' && aggregatable
);
const query = '';
@@ -119,7 +118,7 @@ describe('getSuggestions', () => {
});
it('should not cache results if using a different index/field/query', async () => {
- const fields = mockFields.filter(
+ const fields = stubFields.filter(
({ type, aggregatable }) => type === 'string' && aggregatable
);
await getSuggestions('index', fields[0], '');
diff --git a/src/plugins/data/public/suggestions_provider/value_suggestions.ts b/src/plugins/data/public/suggestions_provider/value_suggestions.ts
index 03eaa5d9594d27..c769f64025b0ed 100644
--- a/src/plugins/data/public/suggestions_provider/value_suggestions.ts
+++ b/src/plugins/data/public/suggestions_provider/value_suggestions.ts
@@ -20,7 +20,8 @@
import { memoize } from 'lodash';
import { UiSettingsClientContract, HttpServiceBase } from 'src/core/public';
-import { IGetSuggestions, Field } from './types';
+import { IGetSuggestions } from './types';
+import { Field } from '..';
export function getSuggestionsProvider(
uiSettings: UiSettingsClientContract,
diff --git a/src/plugins/dev_tools/README.md b/src/plugins/dev_tools/README.md
new file mode 100644
index 00000000000000..1610411b9c98e0
--- /dev/null
+++ b/src/plugins/dev_tools/README.md
@@ -0,0 +1,29 @@
+# Dev tools plugin
+
+The ui/registry/dev_tools is removed in favor of the `dev_tools` plugin which exposes a register method in the setup contract.
+Registering app works mostly the same as registering apps in core.application.register.
+Routing will be handled by the id of the dev tool - your dev tool will be mounted when the URL matches `/app/kibana#/dev_tools/`.
+This API doesn't support angular, for registering angular dev tools, bootstrap a local module on mount into the given HTML element.
+
+During the migration this plugin exposes the registered dev tools in the start contract. This is necessary to keep the dev tools app
+which is still living in the legacy platform working and will be removed once everything is moved over to the new platform. It should
+not be used by other plugins.
+
+## Example registration
+
+```ts
+// For legacy plugins
+import { npSetup } from 'ui/new_platform';
+npSetup.plugins.dev_tools.register(/* same details here */);
+
+// For new plugins: first add 'dev_tools' to the list of `optionalPlugins`
+// in your kibana.json file. Then access the plugin directly in `setup`:
+
+class MyPlugin {
+ setup(core, plugins) {
+ if (plugins.dev_tools) {
+ plugins.dev_tools.register(/* same details here. */);
+ }
+ }
+}
+```
diff --git a/src/plugins/dev_tools/kibana.json b/src/plugins/dev_tools/kibana.json
new file mode 100644
index 00000000000000..307035c7ec6645
--- /dev/null
+++ b/src/plugins/dev_tools/kibana.json
@@ -0,0 +1,6 @@
+{
+ "id": "devTools",
+ "version": "kibana",
+ "server": false,
+ "ui": true
+}
diff --git a/src/plugins/dev_tools/public/index.ts b/src/plugins/dev_tools/public/index.ts
new file mode 100644
index 00000000000000..3a0d1455e2168f
--- /dev/null
+++ b/src/plugins/dev_tools/public/index.ts
@@ -0,0 +1,27 @@
+/*
+ * 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 { PluginInitializerContext } from 'kibana/public';
+import { DevToolsPlugin } from './plugin';
+
+export function plugin(initializerContext: PluginInitializerContext) {
+ return new DevToolsPlugin();
+}
+
+export * from './plugin';
diff --git a/src/plugins/dev_tools/public/plugin.ts b/src/plugins/dev_tools/public/plugin.ts
new file mode 100644
index 00000000000000..8098308c0882bc
--- /dev/null
+++ b/src/plugins/dev_tools/public/plugin.ts
@@ -0,0 +1,115 @@
+/*
+ * 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 { App, CoreSetup, Plugin } from 'kibana/public';
+import { sortBy } from 'lodash';
+
+export interface DevToolsSetup {
+ /**
+ * Register a developer tool. It will be available
+ * in the dev tools app under a separate tab.
+ *
+ * Registering dev tools works almost similar to registering
+ * applications in the core application service,
+ * but they will be rendered with a frame containing tabs
+ * to switch between the tools.
+ * @param devTool The dev tools descriptor
+ */
+ register: (devTool: DevTool) => void;
+}
+
+export interface DevToolsStart {
+ /**
+ * Returns all registered dev tools in an ordered array.
+ * This function is only exposed because the dev tools app
+ * actually rendering the tool has to stay in the legacy platform
+ * for now. Once it is moved into this plugin, this function
+ * becomes an implementation detail.
+ * @deprecated
+ */
+ getSortedDevTools: () => readonly DevTool[];
+}
+
+/**
+ * Descriptor for a dev tool. A dev tool works similar to an application
+ * registered in the core application service.
+ */
+export interface DevTool {
+ /**
+ * The id of the dev tools. This will become part of the URL path
+ * (`dev_tools/${devTool.id}`. It has to be unique among registered
+ * dev tools.
+ */
+ id: string;
+ /**
+ * The human readable name of the dev tool. Should be internationalized.
+ * This will be used as a label in the tab above the actual tool.
+ */
+ title: string;
+ mount: App['mount'];
+ /**
+ * Flag indicating to disable the tab of this dev tool. Navigating to a
+ * disabled dev tool will be treated as the navigation to an unknown route
+ * (redirect to the console).
+ */
+ disabled?: boolean;
+ /**
+ * Optional tooltip content of the tab.
+ */
+ tooltipContent?: string;
+ /**
+ * Flag indicating whether the dev tool will do routing within the `dev_tools/${devTool.id}/`
+ * prefix. If it is set to true, the dev tool is responsible to redirect
+ * the user when navigating to unknown URLs within the prefix. If set
+ * to false only the root URL of the dev tool will be recognized as valid.
+ */
+ enableRouting: boolean;
+ /**
+ * Number used to order the tabs.
+ */
+ order: number;
+}
+
+export class DevToolsPlugin implements Plugin {
+ private readonly devTools = new Map();
+
+ private getSortedDevTools(): readonly DevTool[] {
+ return sortBy([...this.devTools.values()], 'order');
+ }
+
+ public setup(core: CoreSetup) {
+ return {
+ register: (devTool: DevTool) => {
+ if (this.devTools.has(devTool.id)) {
+ throw new Error(
+ `Dev tool with id [${devTool.id}] has already been registered. Use a unique id.`
+ );
+ }
+
+ this.devTools.set(devTool.id, devTool);
+ },
+ };
+ }
+
+ public start() {
+ return {
+ getSortedDevTools: this.getSortedDevTools.bind(this),
+ };
+ }
+}
diff --git a/src/plugins/embeddable/public/tests/customize_panel_modal.test.tsx b/src/plugins/embeddable/public/tests/customize_panel_modal.test.tsx
index b11bd167e15f2e..70d7c99d3fb9d0 100644
--- a/src/plugins/embeddable/public/tests/customize_panel_modal.test.tsx
+++ b/src/plugins/embeddable/public/tests/customize_panel_modal.test.tsx
@@ -173,7 +173,7 @@ test('Can set title to an empty string', async () => {
);
const inputField = findTestSubject(component, 'customizePanelHideTitle');
- inputField.simulate('change');
+ inputField.simulate('click');
findTestSubject(component, 'saveNewTitleButton').simulate('click');
expect(inputField.props().value).toBeUndefined();
diff --git a/src/plugins/es_ui_shared/static/forms/components/fields/toggle_field.tsx b/src/plugins/es_ui_shared/static/forms/components/fields/toggle_field.tsx
index 417f3436a2c63c..0c075c497a4d07 100644
--- a/src/plugins/es_ui_shared/static/forms/components/fields/toggle_field.tsx
+++ b/src/plugins/es_ui_shared/static/forms/components/fields/toggle_field.tsx
@@ -18,7 +18,7 @@
*/
import React from 'react';
-import { EuiFormRow, EuiSwitch } from '@elastic/eui';
+import { EuiFormRow, EuiSwitch, EuiSwitchEvent } from '@elastic/eui';
import { FieldHook } from '../../hook_form_lib';
import { getFieldValidityAndErrorMessage } from '../helpers';
@@ -33,6 +33,14 @@ interface Props {
export const ToggleField = ({ field, euiFieldProps = {}, ...rest }: Props) => {
const { isInvalid, errorMessage } = getFieldValidityAndErrorMessage(field);
+ // Shim for sufficient overlap between EuiSwitchEvent and FieldHook[onChange] event
+ const onChange = (e: EuiSwitchEvent) => {
+ const event = ({ ...e, value: `${e.target.checked}` } as unknown) as React.ChangeEvent<{
+ value: string;
+ }>;
+ field.onChange(event);
+ };
+
return (
{
diff --git a/src/plugins/es_ui_shared/static/forms/helpers/serializers.ts b/src/plugins/es_ui_shared/static/forms/helpers/serializers.ts
index e288f61de8681d..0bb89cc1af593b 100644
--- a/src/plugins/es_ui_shared/static/forms/helpers/serializers.ts
+++ b/src/plugins/es_ui_shared/static/forms/helpers/serializers.ts
@@ -69,24 +69,21 @@ export const stripEmptyFields = (
): { [key: string]: any } => {
const { types = ['string', 'object'], recursive = false } = options || {};
- return Object.entries(object).reduce(
- (acc, [key, value]) => {
- const type = typeof value;
- const shouldStrip = types.includes(type as 'string');
+ return Object.entries(object).reduce((acc, [key, value]) => {
+ const type = typeof value;
+ const shouldStrip = types.includes(type as 'string');
- if (shouldStrip && type === 'string' && value.trim() === '') {
+ if (shouldStrip && type === 'string' && value.trim() === '') {
+ return acc;
+ } else if (type === 'object' && !Array.isArray(value) && value !== null) {
+ if (Object.keys(value).length === 0 && shouldStrip) {
return acc;
- } else if (type === 'object' && !Array.isArray(value) && value !== null) {
- if (Object.keys(value).length === 0 && shouldStrip) {
- return acc;
- } else if (recursive) {
- value = stripEmptyFields({ ...value }, options);
- }
+ } else if (recursive) {
+ value = stripEmptyFields({ ...value }, options);
}
+ }
- acc[key] = value;
- return acc;
- },
- {} as { [key: string]: any }
- );
+ acc[key] = value;
+ return acc;
+ }, {} as { [key: string]: any });
};
diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form.ts b/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form.ts
index 360182368ae638..3902b0615a33d8 100644
--- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form.ts
+++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form.ts
@@ -65,15 +65,12 @@ export function useForm(
const stripEmptyFields = (fields: FieldsMap): FieldsMap => {
if (formOptions.stripEmptyFields) {
- return Object.entries(fields).reduce(
- (acc, [key, field]) => {
- if (typeof field.value !== 'string' || field.value.trim() !== '') {
- acc[key] = field;
- }
- return acc;
- },
- {} as FieldsMap
- );
+ return Object.entries(fields).reduce((acc, [key, field]) => {
+ if (typeof field.value !== 'string' || field.value.trim() !== '') {
+ acc[key] = field;
+ }
+ return acc;
+ }, {} as FieldsMap);
}
return fields;
};
diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/lib/utils.ts b/src/plugins/es_ui_shared/static/forms/hook_form_lib/lib/utils.ts
index 66c3e8d983f983..62867a0c07a6b0 100644
--- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/lib/utils.ts
+++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/lib/utils.ts
@@ -50,10 +50,7 @@ export const mapFormFields = (
formFields: Record,
fn: (field: FieldHook) => any
) =>
- Object.entries(formFields).reduce(
- (acc, [key, field]) => {
- acc[key] = fn(field);
- return acc;
- },
- {} as Record
- );
+ Object.entries(formFields).reduce((acc, [key, field]) => {
+ acc[key] = fn(field);
+ return acc;
+ }, {} as Record);
diff --git a/src/plugins/expressions/public/interpreter_provider.ts b/src/plugins/expressions/public/interpreter_provider.ts
index cb84370ad69c52..15d6b1c025f549 100644
--- a/src/plugins/expressions/public/interpreter_provider.ts
+++ b/src/plugins/expressions/public/interpreter_provider.ts
@@ -163,9 +163,9 @@ export function interpreterProvider(config: InterpreterConfig): ExpressionInterp
// Check for missing required arguments
each(argDefs, argDef => {
- const { aliases, default: argDefault, name: argName, required } = argDef as (ArgumentType<
+ const { aliases, default: argDefault, name: argName, required } = argDef as ArgumentType<
any
- > & { name: string });
+ > & { name: string };
if (
typeof argDefault === 'undefined' &&
required &&
diff --git a/src/plugins/kibana_react/public/context/context.tsx b/src/plugins/kibana_react/public/context/context.tsx
index cbae5c4638ca2d..cbf2ad07b463e7 100644
--- a/src/plugins/kibana_react/public/context/context.tsx
+++ b/src/plugins/kibana_react/public/context/context.tsx
@@ -32,12 +32,11 @@ const defaultContextValue = {
export const context = createContext>(defaultContextValue);
-export const useKibana = (): KibanaReactContextValue<
- KibanaServices & Extra
-> =>
- useContext((context as unknown) as React.Context<
- KibanaReactContextValue
- >);
+export const useKibana = (): KibanaReactContextValue =>
+ useContext(
+ (context as unknown) as React.Context>
+ );
export const withKibana = }>(
type: React.ComponentType
diff --git a/src/plugins/kibana_react/public/index.ts b/src/plugins/kibana_react/public/index.ts
index cf025ec2e88d46..2d82f646c827b9 100644
--- a/src/plugins/kibana_react/public/index.ts
+++ b/src/plugins/kibana_react/public/index.ts
@@ -24,3 +24,4 @@ export * from './overlays';
export * from './ui_settings';
export * from './field_icon';
export * from './table_list_view';
+export { toMountPoint } from './util';
diff --git a/src/plugins/kibana_react/public/notifications/create_notifications.test.tsx b/src/plugins/kibana_react/public/notifications/create_notifications.test.tsx
index 35c503d590b2cd..4f64a2b95f512f 100644
--- a/src/plugins/kibana_react/public/notifications/create_notifications.test.tsx
+++ b/src/plugins/kibana_react/public/notifications/create_notifications.test.tsx
@@ -52,9 +52,20 @@ test('can display string element as title', () => {
wrapper.toasts.show({ title: 'foo' });
expect(notifications.toasts.add).toHaveBeenCalledTimes(1);
- expect(notifications.toasts.add.mock.calls[0][0]).toMatchObject({
- title: 'foo',
- });
+ expect(notifications.toasts.add.mock.calls[0][0]).toMatchInlineSnapshot(`
+ Object {
+ "color": undefined,
+ "iconType": undefined,
+ "onClose": undefined,
+ "text": MountPoint {
+ "reactNode": ,
+ },
+ "title": MountPoint {
+ "reactNode": "foo",
+ },
+ "toastLifeTimeMs": undefined,
+ }
+ `);
});
test('can display React element as title', () => {
@@ -67,10 +78,12 @@ test('can display React element as title', () => {
expect(notifications.toasts.add).toHaveBeenCalledTimes(1);
expect((notifications.toasts.add.mock.calls[0][0] as any).title).toMatchInlineSnapshot(`
-
- bar
-
- `);
+ MountPoint {
+ "reactNode":
+ bar
+
,
+ }
+ `);
});
test('can display React element as toast body', () => {
@@ -81,12 +94,14 @@ test('can display React element as toast body', () => {
expect(notifications.toasts.add).toHaveBeenCalledTimes(1);
expect((notifications.toasts.add.mock.calls[0][0] as any).text).toMatchInlineSnapshot(`
-
-
- baz
-
-
- `);
+ MountPoint {
+ "reactNode":
+
+ baz
+
+ ,
+ }
+ `);
});
test('can set toast properties', () => {
@@ -102,17 +117,21 @@ test('can set toast properties', () => {
});
expect(notifications.toasts.add.mock.calls[0][0]).toMatchInlineSnapshot(`
- Object {
- "color": "danger",
- "iconType": "foo",
- "onClose": undefined,
- "text":
- 1
- ,
- "title": "2",
- "toastLifeTimeMs": 3,
- }
- `);
+ Object {
+ "color": "danger",
+ "iconType": "foo",
+ "onClose": undefined,
+ "text": MountPoint {
+ "reactNode":
+ 1
+ ,
+ },
+ "title": MountPoint {
+ "reactNode": "2",
+ },
+ "toastLifeTimeMs": 3,
+ }
+ `);
});
test('can display success, warning and danger toasts', () => {
@@ -124,21 +143,48 @@ test('can display success, warning and danger toasts', () => {
wrapper.toasts.danger({ title: '3' });
expect(notifications.toasts.add).toHaveBeenCalledTimes(3);
- expect(notifications.toasts.add.mock.calls[0][0]).toMatchObject({
- title: '1',
- color: 'success',
- iconType: 'check',
- });
- expect(notifications.toasts.add.mock.calls[1][0]).toMatchObject({
- title: '2',
- color: 'warning',
- iconType: 'help',
- });
- expect(notifications.toasts.add.mock.calls[2][0]).toMatchObject({
- title: '3',
- color: 'danger',
- iconType: 'alert',
- });
+ expect(notifications.toasts.add.mock.calls[0][0]).toMatchInlineSnapshot(`
+ Object {
+ "color": "success",
+ "iconType": "check",
+ "onClose": undefined,
+ "text": MountPoint {
+ "reactNode": ,
+ },
+ "title": MountPoint {
+ "reactNode": "1",
+ },
+ "toastLifeTimeMs": undefined,
+ }
+ `);
+ expect(notifications.toasts.add.mock.calls[1][0]).toMatchInlineSnapshot(`
+ Object {
+ "color": "warning",
+ "iconType": "help",
+ "onClose": undefined,
+ "text": MountPoint {
+ "reactNode": ,
+ },
+ "title": MountPoint {
+ "reactNode": "2",
+ },
+ "toastLifeTimeMs": undefined,
+ }
+ `);
+ expect(notifications.toasts.add.mock.calls[2][0]).toMatchInlineSnapshot(`
+ Object {
+ "color": "danger",
+ "iconType": "alert",
+ "onClose": undefined,
+ "text": MountPoint {
+ "reactNode": ,
+ },
+ "title": MountPoint {
+ "reactNode": "3",
+ },
+ "toastLifeTimeMs": undefined,
+ }
+ `);
});
test('if body is not set, renders it empty', () => {
@@ -147,7 +193,9 @@ test('if body is not set, renders it empty', () => {
wrapper.toasts.success({ title: '1' });
- expect((notifications.toasts.add.mock.calls[0][0] as any).text).toMatchInlineSnapshot(
- ``
- );
+ expect((notifications.toasts.add.mock.calls[0][0] as any).text).toMatchInlineSnapshot(`
+ MountPoint {
+ "reactNode": ,
+ }
+ `);
});
diff --git a/src/plugins/kibana_react/public/notifications/create_notifications.tsx b/src/plugins/kibana_react/public/notifications/create_notifications.tsx
index 28c1d5391d1601..774f74863ee6f0 100644
--- a/src/plugins/kibana_react/public/notifications/create_notifications.tsx
+++ b/src/plugins/kibana_react/public/notifications/create_notifications.tsx
@@ -20,6 +20,7 @@
import * as React from 'react';
import { KibanaServices } from '../context/types';
import { KibanaReactNotifications } from './types';
+import { toMountPoint } from '../util';
export const createNotifications = (services: KibanaServices): KibanaReactNotifications => {
const show: KibanaReactNotifications['toasts']['show'] = ({
@@ -34,8 +35,8 @@ export const createNotifications = (services: KibanaServices): KibanaReactNotifi
throw new TypeError('Could not show notification as notifications service is not available.');
}
services.notifications!.toasts.add({
- title,
- text: <>{body || null}>,
+ title: toMountPoint(title),
+ text: toMountPoint(<>{body || null}>),
color,
iconType,
toastLifeTimeMs,
diff --git a/src/plugins/kibana_react/public/saved_objects/saved_object_save_modal.tsx b/src/plugins/kibana_react/public/saved_objects/saved_object_save_modal.tsx
index e1e7f1c536342d..bab710cdca5958 100644
--- a/src/plugins/kibana_react/public/saved_objects/saved_object_save_modal.tsx
+++ b/src/plugins/kibana_react/public/saved_objects/saved_object_save_modal.tsx
@@ -32,6 +32,7 @@ import {
EuiOverlayMask,
EuiSpacer,
EuiSwitch,
+ EuiSwitchEvent,
EuiTextArea,
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
@@ -227,7 +228,7 @@ export class SavedObjectSaveModal extends React.Component {
});
};
- private onCopyOnSaveChange = (event: React.ChangeEvent) => {
+ private onCopyOnSaveChange = (event: EuiSwitchEvent) => {
this.setState({
copyOnSave: event.target.checked,
});
diff --git a/src/plugins/kibana_react/public/table_list_view/table_list_view.tsx b/src/plugins/kibana_react/public/table_list_view/table_list_view.tsx
index 7d95c00e764190..dde8efa7e11066 100644
--- a/src/plugins/kibana_react/public/table_list_view/table_list_view.tsx
+++ b/src/plugins/kibana_react/public/table_list_view/table_list_view.tsx
@@ -38,6 +38,7 @@ import {
EuiCallOut,
} from '@elastic/eui';
import { ToastsStart, UiSettingsClientContract } from 'kibana/public';
+import { toMountPoint } from '../util';
export const EMPTY_FILTER = '';
@@ -166,7 +167,7 @@ class TableListView extends React.Component itemsById[id]));
} catch (error) {
this.props.toastNotifications.addDanger({
- title: (
+ title: toMountPoint(
{
+ const mount = (element: HTMLElement) => {
+ ReactDOM.render({node}, element);
+ return () => ReactDOM.unmountComponentAtNode(element);
+ };
+ // only used for tests and snapshots serialization
+ if (process.env.NODE_ENV !== 'production') {
+ mount.__reactMount__ = node;
+ }
+ return mount;
+};
diff --git a/src/plugins/kibana_react/public/util/test_helpers/react_mount_serializer.ts b/src/plugins/kibana_react/public/util/test_helpers/react_mount_serializer.ts
new file mode 100644
index 00000000000000..45ad4cb407175f
--- /dev/null
+++ b/src/plugins/kibana_react/public/util/test_helpers/react_mount_serializer.ts
@@ -0,0 +1,30 @@
+/*
+ * 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 test(value: any) {
+ return value && value.__reactMount__;
+}
+
+export function print(value: any, serialize: any) {
+ // there is no proper way to correctly indent multiline values
+ // so the trick here is to use the Object representation and rewriting the root object name
+ return serialize({
+ reactNode: value.__reactMount__,
+ }).replace('Object', 'MountPoint');
+}
diff --git a/src/plugins/newsfeed/constants.ts b/src/plugins/newsfeed/constants.ts
new file mode 100644
index 00000000000000..ddcbbb6cb1dbe6
--- /dev/null
+++ b/src/plugins/newsfeed/constants.ts
@@ -0,0 +1,22 @@
+/*
+ * 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 NEWSFEED_FALLBACK_LANGUAGE = 'en';
+export const NEWSFEED_LAST_FETCH_STORAGE_KEY = 'newsfeed.lastfetchtime';
+export const NEWSFEED_HASH_SET_STORAGE_KEY = 'newsfeed.hashes';
diff --git a/src/plugins/newsfeed/kibana.json b/src/plugins/newsfeed/kibana.json
new file mode 100644
index 00000000000000..9d49b42424a06c
--- /dev/null
+++ b/src/plugins/newsfeed/kibana.json
@@ -0,0 +1,6 @@
+{
+ "id": "newsfeed",
+ "version": "kibana",
+ "server": false,
+ "ui": true
+}
diff --git a/src/plugins/newsfeed/public/components/__snapshots__/empty_news.test.tsx.snap b/src/plugins/newsfeed/public/components/__snapshots__/empty_news.test.tsx.snap
new file mode 100644
index 00000000000000..8764b7664d4496
--- /dev/null
+++ b/src/plugins/newsfeed/public/components/__snapshots__/empty_news.test.tsx.snap
@@ -0,0 +1,27 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`empty_news rendering renders the default Empty News 1`] = `
+
+
+
+ }
+ data-test-subj="emptyNewsfeed"
+ iconType="documents"
+ title={
+
+
+
+ }
+ titleSize="s"
+/>
+`;
diff --git a/src/plugins/newsfeed/public/components/__snapshots__/loading_news.test.tsx.snap b/src/plugins/newsfeed/public/components/__snapshots__/loading_news.test.tsx.snap
new file mode 100644
index 00000000000000..2e88b0053535e6
--- /dev/null
+++ b/src/plugins/newsfeed/public/components/__snapshots__/loading_news.test.tsx.snap
@@ -0,0 +1,20 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`news_loading rendering renders the default News Loading 1`] = `
+
+
+
+ }
+ title={
+
+ }
+/>
+`;
diff --git a/src/plugins/newsfeed/public/components/empty_news.test.tsx b/src/plugins/newsfeed/public/components/empty_news.test.tsx
new file mode 100644
index 00000000000000..33702df00a583a
--- /dev/null
+++ b/src/plugins/newsfeed/public/components/empty_news.test.tsx
@@ -0,0 +1,32 @@
+/*
+ * 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 React from 'react';
+import { shallow } from 'enzyme';
+import toJson from 'enzyme-to-json';
+import { NewsEmptyPrompt } from './empty_news';
+
+describe('empty_news', () => {
+ describe('rendering', () => {
+ it('renders the default Empty News', () => {
+ const wrapper = shallow();
+ expect(toJson(wrapper)).toMatchSnapshot();
+ });
+ });
+});
diff --git a/src/plugins/newsfeed/public/components/empty_news.tsx b/src/plugins/newsfeed/public/components/empty_news.tsx
new file mode 100644
index 00000000000000..cec18e0bdec433
--- /dev/null
+++ b/src/plugins/newsfeed/public/components/empty_news.tsx
@@ -0,0 +1,44 @@
+/*
+ * 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 React from 'react';
+import { FormattedMessage } from '@kbn/i18n/react';
+import { EuiEmptyPrompt } from '@elastic/eui';
+
+export const NewsEmptyPrompt = () => {
+ return (
+
+
+
+ }
+ body={
+
+
+
+ }
+ />
+ );
+};
diff --git a/src/plugins/newsfeed/public/components/flyout_list.tsx b/src/plugins/newsfeed/public/components/flyout_list.tsx
new file mode 100644
index 00000000000000..8a99abe18a75f3
--- /dev/null
+++ b/src/plugins/newsfeed/public/components/flyout_list.tsx
@@ -0,0 +1,110 @@
+/*
+ * 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 React, { useCallback, useContext } from 'react';
+import {
+ EuiIcon,
+ EuiFlyout,
+ EuiFlyoutBody,
+ EuiFlyoutHeader,
+ EuiTitle,
+ EuiLink,
+ EuiFlyoutFooter,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiButtonEmpty,
+ EuiText,
+ EuiBadge,
+} from '@elastic/eui';
+import { FormattedMessage } from '@kbn/i18n/react';
+import { EuiHeaderAlert } from '../../../../legacy/core_plugins/newsfeed/public/np_ready/components/header_alert/header_alert';
+import { NewsfeedContext } from './newsfeed_header_nav_button';
+import { NewsfeedItem } from '../../types';
+import { NewsEmptyPrompt } from './empty_news';
+import { NewsLoadingPrompt } from './loading_news';
+
+export const NewsfeedFlyout = () => {
+ const { newsFetchResult, setFlyoutVisible } = useContext(NewsfeedContext);
+ const closeFlyout = useCallback(() => setFlyoutVisible(false), [setFlyoutVisible]);
+
+ return (
+
+
+
+
+
+
+
+
+
+ {!newsFetchResult ? (
+
+ ) : newsFetchResult.feedItems.length > 0 ? (
+ newsFetchResult.feedItems.map((item: NewsfeedItem) => {
+ return (
+
+ {item.linkText}
+
+
+ }
+ date={item.publishOn.format('DD MMMM YYYY')}
+ badge={{item.badge}}
+ />
+ );
+ })
+ ) : (
+
+ )}
+
+
+
+
+
+
+
+
+
+ {newsFetchResult ? (
+
+
+
+
+
+ ) : null}
+
+
+
+
+ );
+};
diff --git a/src/plugins/newsfeed/public/components/loading_news.test.tsx b/src/plugins/newsfeed/public/components/loading_news.test.tsx
new file mode 100644
index 00000000000000..ca449b8ee879e2
--- /dev/null
+++ b/src/plugins/newsfeed/public/components/loading_news.test.tsx
@@ -0,0 +1,32 @@
+/*
+ * 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 React from 'react';
+import { shallow } from 'enzyme';
+import toJson from 'enzyme-to-json';
+import { NewsLoadingPrompt } from './loading_news';
+
+describe('news_loading', () => {
+ describe('rendering', () => {
+ it('renders the default News Loading', () => {
+ const wrapper = shallow();
+ expect(toJson(wrapper)).toMatchSnapshot();
+ });
+ });
+});
diff --git a/src/plugins/newsfeed/public/components/loading_news.tsx b/src/plugins/newsfeed/public/components/loading_news.tsx
new file mode 100644
index 00000000000000..fcbc7970377d4e
--- /dev/null
+++ b/src/plugins/newsfeed/public/components/loading_news.tsx
@@ -0,0 +1,39 @@
+/*
+ * 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 React from 'react';
+import { FormattedMessage } from '@kbn/i18n/react';
+
+import { EuiEmptyPrompt } from '@elastic/eui';
+import { EuiLoadingKibana } from '@elastic/eui';
+
+export const NewsLoadingPrompt = () => {
+ return (
+ }
+ body={
+
+
+
+ }
+ />
+ );
+};
diff --git a/src/plugins/newsfeed/public/components/newsfeed_header_nav_button.tsx b/src/plugins/newsfeed/public/components/newsfeed_header_nav_button.tsx
new file mode 100644
index 00000000000000..da042f0fce7b6e
--- /dev/null
+++ b/src/plugins/newsfeed/public/components/newsfeed_header_nav_button.tsx
@@ -0,0 +1,82 @@
+/*
+ * 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 React, { useState, Fragment, useEffect } from 'react';
+import * as Rx from 'rxjs';
+import { EuiHeaderSectionItemButton, EuiIcon, EuiNotificationBadge } from '@elastic/eui';
+import { NewsfeedFlyout } from './flyout_list';
+import { FetchResult } from '../../types';
+
+export interface INewsfeedContext {
+ setFlyoutVisible: React.Dispatch>;
+ newsFetchResult: FetchResult | void | null;
+}
+export const NewsfeedContext = React.createContext({} as INewsfeedContext);
+
+export type NewsfeedApiFetchResult = Rx.Observable;
+
+export interface Props {
+ apiFetchResult: NewsfeedApiFetchResult;
+}
+
+export const NewsfeedNavButton = ({ apiFetchResult }: Props) => {
+ const [showBadge, setShowBadge] = useState(false);
+ const [flyoutVisible, setFlyoutVisible] = useState(false);
+ const [newsFetchResult, setNewsFetchResult] = useState(null);
+
+ useEffect(() => {
+ function handleStatusChange(fetchResult: FetchResult | void | null) {
+ if (fetchResult) {
+ setShowBadge(fetchResult.hasNew);
+ }
+ setNewsFetchResult(fetchResult);
+ }
+
+ const subscription = apiFetchResult.subscribe(res => handleStatusChange(res));
+ return () => subscription.unsubscribe();
+ }, [apiFetchResult]);
+
+ function showFlyout() {
+ setShowBadge(false);
+ setFlyoutVisible(!flyoutVisible);
+ }
+
+ return (
+
+
+
+
+ {showBadge ? (
+
+ ▪
+
+ ) : null}
+
+ {flyoutVisible ? : null}
+
+
+ );
+};
diff --git a/src/plugins/newsfeed/public/index.ts b/src/plugins/newsfeed/public/index.ts
new file mode 100644
index 00000000000000..1217de60d9638c
--- /dev/null
+++ b/src/plugins/newsfeed/public/index.ts
@@ -0,0 +1,25 @@
+/*
+ * 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 { PluginInitializerContext } from 'src/core/public';
+import { NewsfeedPublicPlugin } from './plugin';
+
+export function plugin(initializerContext: PluginInitializerContext) {
+ return new NewsfeedPublicPlugin(initializerContext);
+}
diff --git a/src/plugins/newsfeed/public/lib/api.test.ts b/src/plugins/newsfeed/public/lib/api.test.ts
new file mode 100644
index 00000000000000..4383b9e0f7dabe
--- /dev/null
+++ b/src/plugins/newsfeed/public/lib/api.test.ts
@@ -0,0 +1,698 @@
+/*
+ * 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 { take, tap, toArray } from 'rxjs/operators';
+import { interval, race } from 'rxjs';
+import sinon, { stub } from 'sinon';
+import moment from 'moment';
+import { HttpServiceBase } from 'src/core/public';
+import { NEWSFEED_HASH_SET_STORAGE_KEY, NEWSFEED_LAST_FETCH_STORAGE_KEY } from '../../constants';
+import { ApiItem, NewsfeedItem, NewsfeedPluginInjectedConfig } from '../../types';
+import { NewsfeedApiDriver, getApi } from './api';
+
+const localStorageGet = sinon.stub();
+const sessionStoragetGet = sinon.stub();
+
+Object.defineProperty(window, 'localStorage', {
+ value: {
+ getItem: localStorageGet,
+ setItem: stub(),
+ },
+ writable: true,
+});
+Object.defineProperty(window, 'sessionStorage', {
+ value: {
+ getItem: sessionStoragetGet,
+ setItem: stub(),
+ },
+ writable: true,
+});
+
+describe('NewsfeedApiDriver', () => {
+ const kibanaVersion = 'test_version';
+ const userLanguage = 'en';
+ const fetchInterval = 2000;
+ const getDriver = () => new NewsfeedApiDriver(kibanaVersion, userLanguage, fetchInterval);
+
+ afterEach(() => {
+ sinon.reset();
+ });
+
+ describe('shouldFetch', () => {
+ it('defaults to true', () => {
+ const driver = getDriver();
+ expect(driver.shouldFetch()).toBe(true);
+ });
+
+ it('returns true if last fetch time precedes page load time', () => {
+ sessionStoragetGet.throws('Wrong key passed!');
+ sessionStoragetGet.withArgs(NEWSFEED_LAST_FETCH_STORAGE_KEY).returns(322642800000); // 1980-03-23
+ const driver = getDriver();
+ expect(driver.shouldFetch()).toBe(true);
+ });
+
+ it('returns false if last fetch time is recent enough', () => {
+ sessionStoragetGet.throws('Wrong key passed!');
+ sessionStoragetGet.withArgs(NEWSFEED_LAST_FETCH_STORAGE_KEY).returns(3005017200000); // 2065-03-23
+ const driver = getDriver();
+ expect(driver.shouldFetch()).toBe(false);
+ });
+ });
+
+ describe('updateHashes', () => {
+ it('returns previous and current storage', () => {
+ const driver = getDriver();
+ const items: NewsfeedItem[] = [
+ {
+ title: 'Good news, everyone!',
+ description: 'good item description',
+ linkText: 'click here',
+ linkUrl: 'about:blank',
+ badge: 'test',
+ publishOn: moment(1572489035150),
+ expireOn: moment(1572489047858),
+ hash: 'hash1oneoneoneone',
+ },
+ ];
+ expect(driver.updateHashes(items)).toMatchInlineSnapshot(`
+ Object {
+ "current": Array [
+ "hash1oneoneoneone",
+ ],
+ "previous": Array [],
+ }
+ `);
+ });
+
+ it('concatenates the previous hashes with the current', () => {
+ localStorageGet.throws('Wrong key passed!');
+ localStorageGet.withArgs(NEWSFEED_HASH_SET_STORAGE_KEY).returns('happyness');
+ const driver = getDriver();
+ const items: NewsfeedItem[] = [
+ {
+ title: 'Better news, everyone!',
+ description: 'better item description',
+ linkText: 'click there',
+ linkUrl: 'about:blank',
+ badge: 'concatentated',
+ publishOn: moment(1572489035150),
+ expireOn: moment(1572489047858),
+ hash: 'three33hash',
+ },
+ ];
+ expect(driver.updateHashes(items)).toMatchInlineSnapshot(`
+ Object {
+ "current": Array [
+ "happyness",
+ "three33hash",
+ ],
+ "previous": Array [
+ "happyness",
+ ],
+ }
+ `);
+ });
+ });
+
+ it('Validates items for required fields', () => {
+ const driver = getDriver();
+ expect(driver.validateItem({})).toBe(false);
+ expect(
+ driver.validateItem({
+ title: 'Gadzooks!',
+ description: 'gadzooks item description',
+ linkText: 'click here',
+ linkUrl: 'about:blank',
+ badge: 'test',
+ publishOn: moment(1572489035150),
+ expireOn: moment(1572489047858),
+ hash: 'hash2twotwotwotwotwo',
+ })
+ ).toBe(true);
+ expect(
+ driver.validateItem({
+ title: 'Gadzooks!',
+ description: 'gadzooks item description',
+ linkText: 'click here',
+ linkUrl: 'about:blank',
+ publishOn: moment(1572489035150),
+ hash: 'hash2twotwotwotwotwo',
+ })
+ ).toBe(true);
+ expect(
+ driver.validateItem({
+ title: 'Gadzooks!',
+ description: 'gadzooks item description',
+ linkText: 'click here',
+ linkUrl: 'about:blank',
+ publishOn: moment(1572489035150),
+ // hash: 'hash2twotwotwotwotwo', // should fail because this is missing
+ })
+ ).toBe(false);
+ });
+
+ describe('modelItems', () => {
+ it('Models empty set with defaults', () => {
+ const driver = getDriver();
+ const apiItems: ApiItem[] = [];
+ expect(driver.modelItems(apiItems)).toMatchInlineSnapshot(`
+ Object {
+ "error": null,
+ "feedItems": Array [],
+ "hasNew": false,
+ "kibanaVersion": "test_version",
+ }
+ `);
+ });
+
+ it('Selects default language', () => {
+ const driver = getDriver();
+ const apiItems: ApiItem[] = [
+ {
+ title: {
+ en: 'speaking English',
+ es: 'habla Espanol',
+ },
+ description: {
+ en: 'language test',
+ es: 'idiomas',
+ },
+ languages: ['en', 'es'],
+ link_text: {
+ en: 'click here',
+ es: 'aqui',
+ },
+ link_url: {
+ en: 'xyzxyzxyz',
+ es: 'abcabc',
+ },
+ badge: {
+ en: 'firefighter',
+ es: 'bombero',
+ },
+ publish_on: new Date('2014-10-31T04:23:47Z'),
+ expire_on: new Date('2049-10-31T04:23:47Z'),
+ hash: 'abcabc1231123123hash',
+ },
+ ];
+ expect(driver.modelItems(apiItems)).toMatchObject({
+ error: null,
+ feedItems: [
+ {
+ badge: 'firefighter',
+ description: 'language test',
+ hash: 'abcabc1231',
+ linkText: 'click here',
+ linkUrl: 'xyzxyzxyz',
+ title: 'speaking English',
+ },
+ ],
+ hasNew: true,
+ kibanaVersion: 'test_version',
+ });
+ });
+
+ it("Falls back to English when user language isn't present", () => {
+ // Set Language to French
+ const driver = new NewsfeedApiDriver(kibanaVersion, 'fr', fetchInterval);
+ const apiItems: ApiItem[] = [
+ {
+ title: {
+ en: 'speaking English',
+ fr: 'Le Title',
+ },
+ description: {
+ en: 'not French',
+ fr: 'Le Description',
+ },
+ languages: ['en', 'fr'],
+ link_text: {
+ en: 'click here',
+ fr: 'Le Link Text',
+ },
+ link_url: {
+ en: 'xyzxyzxyz',
+ fr: 'le_url',
+ },
+ badge: {
+ en: 'firefighter',
+ fr: 'le_badge',
+ },
+ publish_on: new Date('2014-10-31T04:23:47Z'),
+ expire_on: new Date('2049-10-31T04:23:47Z'),
+ hash: 'frfrfrfr1231123123hash',
+ }, // fallback: no
+ {
+ title: {
+ en: 'speaking English',
+ es: 'habla Espanol',
+ },
+ description: {
+ en: 'not French',
+ es: 'no Espanol',
+ },
+ languages: ['en', 'es'],
+ link_text: {
+ en: 'click here',
+ es: 'aqui',
+ },
+ link_url: {
+ en: 'xyzxyzxyz',
+ es: 'abcabc',
+ },
+ badge: {
+ en: 'firefighter',
+ es: 'bombero',
+ },
+ publish_on: new Date('2014-10-31T04:23:47Z'),
+ expire_on: new Date('2049-10-31T04:23:47Z'),
+ hash: 'enenenen1231123123hash',
+ }, // fallback: yes
+ ];
+ expect(driver.modelItems(apiItems)).toMatchObject({
+ error: null,
+ feedItems: [
+ {
+ badge: 'le_badge',
+ description: 'Le Description',
+ hash: 'frfrfrfr12',
+ linkText: 'Le Link Text',
+ linkUrl: 'le_url',
+ title: 'Le Title',
+ },
+ {
+ badge: 'firefighter',
+ description: 'not French',
+ hash: 'enenenen12',
+ linkText: 'click here',
+ linkUrl: 'xyzxyzxyz',
+ title: 'speaking English',
+ },
+ ],
+ hasNew: true,
+ kibanaVersion: 'test_version',
+ });
+ });
+
+ it('Models multiple items into an API FetchResult', () => {
+ const driver = getDriver();
+ const apiItems: ApiItem[] = [
+ {
+ title: {
+ en: 'guess what',
+ },
+ description: {
+ en: 'this tests the modelItems function',
+ },
+ link_text: {
+ en: 'click here',
+ },
+ link_url: {
+ en: 'about:blank',
+ },
+ publish_on: new Date('2014-10-31T04:23:47Z'),
+ expire_on: new Date('2049-10-31T04:23:47Z'),
+ hash: 'abcabc1231123123hash',
+ },
+ {
+ title: {
+ en: 'guess when',
+ },
+ description: {
+ en: 'this also tests the modelItems function',
+ },
+ link_text: {
+ en: 'click here',
+ },
+ link_url: {
+ en: 'about:blank',
+ },
+ badge: {
+ en: 'hero',
+ },
+ publish_on: new Date('2014-10-31T04:23:47Z'),
+ expire_on: new Date('2049-10-31T04:23:47Z'),
+ hash: 'defdefdef456456456',
+ },
+ ];
+ expect(driver.modelItems(apiItems)).toMatchObject({
+ error: null,
+ feedItems: [
+ {
+ badge: null,
+ description: 'this tests the modelItems function',
+ hash: 'abcabc1231',
+ linkText: 'click here',
+ linkUrl: 'about:blank',
+ title: 'guess what',
+ },
+ {
+ badge: 'hero',
+ description: 'this also tests the modelItems function',
+ hash: 'defdefdef4',
+ linkText: 'click here',
+ linkUrl: 'about:blank',
+ title: 'guess when',
+ },
+ ],
+ hasNew: true,
+ kibanaVersion: 'test_version',
+ });
+ });
+
+ it('Filters expired', () => {
+ const driver = getDriver();
+ const apiItems: ApiItem[] = [
+ {
+ title: {
+ en: 'guess what',
+ },
+ description: {
+ en: 'this tests the modelItems function',
+ },
+ link_text: {
+ en: 'click here',
+ },
+ link_url: {
+ en: 'about:blank',
+ },
+ publish_on: new Date('2013-10-31T04:23:47Z'),
+ expire_on: new Date('2014-10-31T04:23:47Z'), // too old
+ hash: 'abcabc1231123123hash',
+ },
+ ];
+ expect(driver.modelItems(apiItems)).toMatchInlineSnapshot(`
+ Object {
+ "error": null,
+ "feedItems": Array [],
+ "hasNew": false,
+ "kibanaVersion": "test_version",
+ }
+ `);
+ });
+
+ it('Filters pre-published', () => {
+ const driver = getDriver();
+ const apiItems: ApiItem[] = [
+ {
+ title: {
+ en: 'guess what',
+ },
+ description: {
+ en: 'this tests the modelItems function',
+ },
+ link_text: {
+ en: 'click here',
+ },
+ link_url: {
+ en: 'about:blank',
+ },
+ publish_on: new Date('2055-10-31T04:23:47Z'), // too new
+ expire_on: new Date('2056-10-31T04:23:47Z'),
+ hash: 'abcabc1231123123hash',
+ },
+ ];
+ expect(driver.modelItems(apiItems)).toMatchInlineSnapshot(`
+ Object {
+ "error": null,
+ "feedItems": Array [],
+ "hasNew": false,
+ "kibanaVersion": "test_version",
+ }
+ `);
+ });
+ });
+});
+
+describe('getApi', () => {
+ const mockHttpGet = jest.fn();
+ let httpMock = ({
+ fetch: mockHttpGet,
+ } as unknown) as HttpServiceBase;
+ const getHttpMockWithItems = (mockApiItems: ApiItem[]) => (
+ arg1: string,
+ arg2: { method: string }
+ ) => {
+ if (
+ arg1 === 'http://fakenews.co/kibana-test/v6.8.2.json' &&
+ arg2.method &&
+ arg2.method === 'GET'
+ ) {
+ return Promise.resolve({ items: mockApiItems });
+ }
+ return Promise.reject('wrong args!');
+ };
+ let configMock: NewsfeedPluginInjectedConfig;
+
+ afterEach(() => {
+ jest.resetAllMocks();
+ });
+
+ beforeEach(() => {
+ configMock = {
+ newsfeed: {
+ service: {
+ urlRoot: 'http://fakenews.co',
+ pathTemplate: '/kibana-test/v{VERSION}.json',
+ },
+ defaultLanguage: 'en',
+ mainInterval: 86400000,
+ fetchInterval: 86400000,
+ },
+ };
+ httpMock = ({
+ fetch: mockHttpGet,
+ } as unknown) as HttpServiceBase;
+ });
+
+ it('creates a result', done => {
+ mockHttpGet.mockImplementationOnce(() => Promise.resolve({ items: [] }));
+ getApi(httpMock, configMock.newsfeed, '6.8.2').subscribe(result => {
+ expect(result).toMatchInlineSnapshot(`
+ Object {
+ "error": null,
+ "feedItems": Array [],
+ "hasNew": false,
+ "kibanaVersion": "6.8.2",
+ }
+ `);
+ done();
+ });
+ });
+
+ it('hasNew is true when the service returns hashes not in the cache', done => {
+ const mockApiItems: ApiItem[] = [
+ {
+ title: {
+ en: 'speaking English',
+ es: 'habla Espanol',
+ },
+ description: {
+ en: 'language test',
+ es: 'idiomas',
+ },
+ languages: ['en', 'es'],
+ link_text: {
+ en: 'click here',
+ es: 'aqui',
+ },
+ link_url: {
+ en: 'xyzxyzxyz',
+ es: 'abcabc',
+ },
+ badge: {
+ en: 'firefighter',
+ es: 'bombero',
+ },
+ publish_on: new Date('2014-10-31T04:23:47Z'),
+ expire_on: new Date('2049-10-31T04:23:47Z'),
+ hash: 'abcabc1231123123hash',
+ },
+ ];
+
+ mockHttpGet.mockImplementationOnce(getHttpMockWithItems(mockApiItems));
+
+ getApi(httpMock, configMock.newsfeed, '6.8.2').subscribe(result => {
+ expect(result).toMatchInlineSnapshot(`
+ Object {
+ "error": null,
+ "feedItems": Array [
+ Object {
+ "badge": "firefighter",
+ "description": "language test",
+ "expireOn": "2049-10-31T04:23:47.000Z",
+ "hash": "abcabc1231",
+ "linkText": "click here",
+ "linkUrl": "xyzxyzxyz",
+ "publishOn": "2014-10-31T04:23:47.000Z",
+ "title": "speaking English",
+ },
+ ],
+ "hasNew": true,
+ "kibanaVersion": "6.8.2",
+ }
+ `);
+ done();
+ });
+ });
+
+ it('hasNew is false when service returns hashes that are all stored', done => {
+ localStorageGet.throws('Wrong key passed!');
+ localStorageGet.withArgs(NEWSFEED_HASH_SET_STORAGE_KEY).returns('happyness');
+ const mockApiItems: ApiItem[] = [
+ {
+ title: { en: 'hasNew test' },
+ description: { en: 'test' },
+ link_text: { en: 'click here' },
+ link_url: { en: 'xyzxyzxyz' },
+ badge: { en: 'firefighter' },
+ publish_on: new Date('2014-10-31T04:23:47Z'),
+ expire_on: new Date('2049-10-31T04:23:47Z'),
+ hash: 'happyness',
+ },
+ ];
+ mockHttpGet.mockImplementationOnce(getHttpMockWithItems(mockApiItems));
+ getApi(httpMock, configMock.newsfeed, '6.8.2').subscribe(result => {
+ expect(result).toMatchInlineSnapshot(`
+ Object {
+ "error": null,
+ "feedItems": Array [
+ Object {
+ "badge": "firefighter",
+ "description": "test",
+ "expireOn": "2049-10-31T04:23:47.000Z",
+ "hash": "happyness",
+ "linkText": "click here",
+ "linkUrl": "xyzxyzxyz",
+ "publishOn": "2014-10-31T04:23:47.000Z",
+ "title": "hasNew test",
+ },
+ ],
+ "hasNew": false,
+ "kibanaVersion": "6.8.2",
+ }
+ `);
+ done();
+ });
+ });
+
+ it('forwards an error', done => {
+ mockHttpGet.mockImplementationOnce((arg1, arg2) => Promise.reject('sorry, try again later!'));
+
+ getApi(httpMock, configMock.newsfeed, '6.8.2').subscribe(result => {
+ expect(result).toMatchInlineSnapshot(`
+ Object {
+ "error": "sorry, try again later!",
+ "feedItems": Array [],
+ "hasNew": false,
+ "kibanaVersion": "6.8.2",
+ }
+ `);
+ done();
+ });
+ });
+
+ describe('Retry fetching', () => {
+ const successItems: ApiItem[] = [
+ {
+ title: { en: 'hasNew test' },
+ description: { en: 'test' },
+ link_text: { en: 'click here' },
+ link_url: { en: 'xyzxyzxyz' },
+ badge: { en: 'firefighter' },
+ publish_on: new Date('2014-10-31T04:23:47Z'),
+ expire_on: new Date('2049-10-31T04:23:47Z'),
+ hash: 'happyness',
+ },
+ ];
+
+ it("retries until fetch doesn't error", done => {
+ configMock.newsfeed.mainInterval = 10; // fast retry for testing
+ mockHttpGet
+ .mockImplementationOnce(() => Promise.reject('Sorry, try again later!'))
+ .mockImplementationOnce(() => Promise.reject('Sorry, internal server error!'))
+ .mockImplementationOnce(() => Promise.reject("Sorry, it's too cold to go outside!"))
+ .mockImplementationOnce(getHttpMockWithItems(successItems));
+
+ getApi(httpMock, configMock.newsfeed, '6.8.2')
+ .pipe(take(4), toArray())
+ .subscribe(result => {
+ expect(result).toMatchInlineSnapshot(`
+ Array [
+ Object {
+ "error": "Sorry, try again later!",
+ "feedItems": Array [],
+ "hasNew": false,
+ "kibanaVersion": "6.8.2",
+ },
+ Object {
+ "error": "Sorry, internal server error!",
+ "feedItems": Array [],
+ "hasNew": false,
+ "kibanaVersion": "6.8.2",
+ },
+ Object {
+ "error": "Sorry, it's too cold to go outside!",
+ "feedItems": Array [],
+ "hasNew": false,
+ "kibanaVersion": "6.8.2",
+ },
+ Object {
+ "error": null,
+ "feedItems": Array [
+ Object {
+ "badge": "firefighter",
+ "description": "test",
+ "expireOn": "2049-10-31T04:23:47.000Z",
+ "hash": "happyness",
+ "linkText": "click here",
+ "linkUrl": "xyzxyzxyz",
+ "publishOn": "2014-10-31T04:23:47.000Z",
+ "title": "hasNew test",
+ },
+ ],
+ "hasNew": false,
+ "kibanaVersion": "6.8.2",
+ },
+ ]
+ `);
+ done();
+ });
+ });
+
+ it("doesn't retry if fetch succeeds", done => {
+ configMock.newsfeed.mainInterval = 10; // fast retry for testing
+ mockHttpGet.mockImplementation(getHttpMockWithItems(successItems));
+
+ const timeout$ = interval(1000); // lets us capture some results after a short time
+ let timesFetched = 0;
+
+ const get$ = getApi(httpMock, configMock.newsfeed, '6.8.2').pipe(
+ tap(() => {
+ timesFetched++;
+ })
+ );
+
+ race(get$, timeout$).subscribe(() => {
+ expect(timesFetched).toBe(1); // first fetch was successful, so there was no retry
+ done();
+ });
+ });
+ });
+});
diff --git a/src/plugins/newsfeed/public/lib/api.ts b/src/plugins/newsfeed/public/lib/api.ts
new file mode 100644
index 00000000000000..6920dd9b2bccc2
--- /dev/null
+++ b/src/plugins/newsfeed/public/lib/api.ts
@@ -0,0 +1,194 @@
+/*
+ * 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 moment from 'moment';
+import { i18n } from '@kbn/i18n';
+import { catchError, filter, mergeMap, tap } from 'rxjs/operators';
+import { HttpServiceBase } from 'src/core/public';
+import {
+ NEWSFEED_FALLBACK_LANGUAGE,
+ NEWSFEED_LAST_FETCH_STORAGE_KEY,
+ NEWSFEED_HASH_SET_STORAGE_KEY,
+} from '../../constants';
+import { NewsfeedPluginInjectedConfig, ApiItem, NewsfeedItem, FetchResult } from '../../types';
+
+type ApiConfig = NewsfeedPluginInjectedConfig['newsfeed']['service'];
+
+export class NewsfeedApiDriver {
+ private readonly loadedTime = moment().utc(); // the date is compared to time in UTC format coming from the service
+
+ constructor(
+ private readonly kibanaVersion: string,
+ private readonly userLanguage: string,
+ private readonly fetchInterval: number
+ ) {}
+
+ shouldFetch(): boolean {
+ const lastFetchUtc: string | null = sessionStorage.getItem(NEWSFEED_LAST_FETCH_STORAGE_KEY);
+ if (lastFetchUtc == null) {
+ return true;
+ }
+ const last = moment(lastFetchUtc, 'x'); // parse as unix ms timestamp (already is UTC)
+
+ // does the last fetch time precede the time that the page was loaded?
+ if (this.loadedTime.diff(last) > 0) {
+ return true;
+ }
+
+ const now = moment.utc(); // always use UTC to compare timestamps that came from the service
+ const duration = moment.duration(now.diff(last));
+
+ return duration.asMilliseconds() > this.fetchInterval;
+ }
+
+ updateLastFetch() {
+ sessionStorage.setItem(NEWSFEED_LAST_FETCH_STORAGE_KEY, Date.now().toString());
+ }
+
+ updateHashes(items: NewsfeedItem[]): { previous: string[]; current: string[] } {
+ // replace localStorage hashes with new hashes
+ const stored: string | null = localStorage.getItem(NEWSFEED_HASH_SET_STORAGE_KEY);
+ let old: string[] = [];
+ if (stored != null) {
+ old = stored.split(',');
+ }
+
+ const newHashes = items.map(i => i.hash);
+ const updatedHashes = [...new Set(old.concat(newHashes))];
+ localStorage.setItem(NEWSFEED_HASH_SET_STORAGE_KEY, updatedHashes.join(','));
+
+ return { previous: old, current: updatedHashes };
+ }
+
+ fetchNewsfeedItems(http: HttpServiceBase, config: ApiConfig): Rx.Observable {
+ const urlPath = config.pathTemplate.replace('{VERSION}', this.kibanaVersion);
+ const fullUrl = config.urlRoot + urlPath;
+
+ return Rx.from(
+ http
+ .fetch(fullUrl, {
+ method: 'GET',
+ })
+ .then(({ items }) => this.modelItems(items))
+ );
+ }
+
+ validateItem(item: Partial) {
+ const hasMissing = [
+ item.title,
+ item.description,
+ item.linkText,
+ item.linkUrl,
+ item.publishOn,
+ item.hash,
+ ].includes(undefined);
+
+ return !hasMissing;
+ }
+
+ modelItems(items: ApiItem[]): FetchResult {
+ const feedItems: NewsfeedItem[] = items.reduce((accum: NewsfeedItem[], it: ApiItem) => {
+ let chosenLanguage = this.userLanguage;
+ const {
+ expire_on: expireOnUtc,
+ publish_on: publishOnUtc,
+ languages,
+ title,
+ description,
+ link_text: linkText,
+ link_url: linkUrl,
+ badge,
+ hash,
+ } = it;
+
+ if (moment(expireOnUtc).isBefore(Date.now())) {
+ return accum; // ignore item if expired
+ }
+
+ if (moment(publishOnUtc).isAfter(Date.now())) {
+ return accum; // ignore item if publish date hasn't occurred yet (pre-published)
+ }
+
+ if (languages && !languages.includes(chosenLanguage)) {
+ chosenLanguage = NEWSFEED_FALLBACK_LANGUAGE; // don't remove the item: fallback on a language
+ }
+
+ const tempItem: NewsfeedItem = {
+ title: title[chosenLanguage],
+ description: description[chosenLanguage],
+ linkText: linkText[chosenLanguage],
+ linkUrl: linkUrl[chosenLanguage],
+ badge: badge != null ? badge![chosenLanguage] : null,
+ publishOn: moment(publishOnUtc),
+ expireOn: moment(expireOnUtc),
+ hash: hash.slice(0, 10), // optimize for storage and faster parsing
+ };
+
+ if (!this.validateItem(tempItem)) {
+ return accum; // ignore if title, description, etc is missing
+ }
+
+ return [...accum, tempItem];
+ }, []);
+
+ // calculate hasNew
+ const { previous, current } = this.updateHashes(feedItems);
+ const hasNew = current.length > previous.length;
+
+ return {
+ error: null,
+ kibanaVersion: this.kibanaVersion,
+ hasNew,
+ feedItems,
+ };
+ }
+}
+
+/*
+ * Creates an Observable to newsfeed items, powered by the main interval
+ * Computes hasNew value from new item hashes saved in localStorage
+ */
+export function getApi(
+ http: HttpServiceBase,
+ config: NewsfeedPluginInjectedConfig['newsfeed'],
+ kibanaVersion: string
+): Rx.Observable {
+ const userLanguage = i18n.getLocale() || config.defaultLanguage;
+ const fetchInterval = config.fetchInterval;
+ const driver = new NewsfeedApiDriver(kibanaVersion, userLanguage, fetchInterval);
+
+ return Rx.timer(0, config.mainInterval).pipe(
+ filter(() => driver.shouldFetch()),
+ mergeMap(() =>
+ driver.fetchNewsfeedItems(http, config.service).pipe(
+ catchError(err => {
+ window.console.error(err);
+ return Rx.of({
+ error: err,
+ kibanaVersion,
+ hasNew: false,
+ feedItems: [],
+ });
+ })
+ )
+ ),
+ tap(() => driver.updateLastFetch())
+ );
+}
diff --git a/src/plugins/newsfeed/public/plugin.tsx b/src/plugins/newsfeed/public/plugin.tsx
new file mode 100644
index 00000000000000..5ea5e5b324717d
--- /dev/null
+++ b/src/plugins/newsfeed/public/plugin.tsx
@@ -0,0 +1,76 @@
+/*
+ * 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 { catchError, takeUntil } from 'rxjs/operators';
+import ReactDOM from 'react-dom';
+import React from 'react';
+import { I18nProvider } from '@kbn/i18n/react';
+import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from 'src/core/public';
+import { NewsfeedPluginInjectedConfig } from '../types';
+import { NewsfeedNavButton, NewsfeedApiFetchResult } from './components/newsfeed_header_nav_button';
+import { getApi } from './lib/api';
+
+export type Setup = void;
+export type Start = void;
+
+export class NewsfeedPublicPlugin implements Plugin {
+ private readonly kibanaVersion: string;
+ private readonly stop$ = new Rx.ReplaySubject(1);
+
+ constructor(initializerContext: PluginInitializerContext) {
+ this.kibanaVersion = initializerContext.env.packageInfo.version;
+ }
+
+ public setup(core: CoreSetup): Setup {}
+
+ public start(core: CoreStart): Start {
+ const api$ = this.fetchNewsfeed(core);
+ core.chrome.navControls.registerRight({
+ order: 1000,
+ mount: target => this.mount(api$, target),
+ });
+ }
+
+ public stop() {
+ this.stop$.next();
+ }
+
+ private fetchNewsfeed(core: CoreStart) {
+ const { http, injectedMetadata } = core;
+ const config = injectedMetadata.getInjectedVar(
+ 'newsfeed'
+ ) as NewsfeedPluginInjectedConfig['newsfeed'];
+
+ return getApi(http, config, this.kibanaVersion).pipe(
+ takeUntil(this.stop$), // stop the interval when stop method is called
+ catchError(() => Rx.of(null)) // do not throw error
+ );
+ }
+
+ private mount(api$: NewsfeedApiFetchResult, targetDomElement: HTMLElement) {
+ ReactDOM.render(
+
+
+ ,
+ targetDomElement
+ );
+ return () => ReactDOM.unmountComponentAtNode(targetDomElement);
+ }
+}
diff --git a/src/plugins/newsfeed/types.ts b/src/plugins/newsfeed/types.ts
new file mode 100644
index 00000000000000..78485c6ee4f590
--- /dev/null
+++ b/src/plugins/newsfeed/types.ts
@@ -0,0 +1,63 @@
+/*
+ * 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 { Moment } from 'moment';
+
+export interface NewsfeedPluginInjectedConfig {
+ newsfeed: {
+ service: {
+ urlRoot: string;
+ pathTemplate: string;
+ };
+ defaultLanguage: string;
+ mainInterval: number; // how often to check last updated time
+ fetchInterval: number; // how often to fetch remote service and set last updated
+ };
+}
+
+export interface ApiItem {
+ hash: string;
+ expire_on: Date;
+ publish_on: Date;
+ title: { [lang: string]: string };
+ description: { [lang: string]: string };
+ link_text: { [lang: string]: string };
+ link_url: { [lang: string]: string };
+ badge?: { [lang: string]: string } | null;
+ languages?: string[] | null;
+ image_url?: null; // not used phase 1
+}
+
+export interface NewsfeedItem {
+ title: string;
+ description: string;
+ linkText: string;
+ linkUrl: string;
+ badge: string | null;
+ publishOn: Moment;
+ expireOn: Moment;
+ hash: string;
+}
+
+export interface FetchResult {
+ kibanaVersion: string;
+ hasNew: boolean;
+ feedItems: NewsfeedItem[];
+ error: Error | null;
+}
diff --git a/tasks/function_test_groups.js b/tasks/function_test_groups.js
index 31656df2cb6447..f5a1e63617dfa0 100644
--- a/tasks/function_test_groups.js
+++ b/tasks/function_test_groups.js
@@ -41,6 +41,7 @@ export function getFunctionalTestGroupRunConfigs({ kibanaInstallDir } = {}) {
'scripts/functional_tests',
'--include-tag', tag,
'--config', 'test/functional/config.js',
+ '--config', 'test/ui_capabilities/newsfeed_err/config.ts',
// '--config', 'test/functional/config.firefox.js',
'--bail',
'--debug',
diff --git a/test/api_integration/apis/ui_metric/ui_metric.js b/test/api_integration/apis/ui_metric/ui_metric.js
index efa6be47b50c9b..f0c86f2904638c 100644
--- a/test/api_integration/apis/ui_metric/ui_metric.js
+++ b/test/api_integration/apis/ui_metric/ui_metric.js
@@ -18,48 +18,59 @@
*/
import expect from '@kbn/expect';
-import { ReportManager } from '@kbn/analytics';
+import { ReportManager, METRIC_TYPE } from '@kbn/analytics';
export default function ({ getService }) {
const supertest = getService('supertest');
const es = getService('es');
- const createMetric = (eventName) => ({
- key: ReportManager.createMetricKey({ appName: 'myApp', type: 'click', eventName }),
+ const createStatsMetric = (eventName) => ({
+ key: ReportManager.createMetricKey({ appName: 'myApp', type: METRIC_TYPE.CLICK, eventName }),
eventName,
appName: 'myApp',
- type: 'click',
+ type: METRIC_TYPE.CLICK,
stats: { sum: 1, avg: 1, min: 1, max: 1 },
});
+ const createUserAgentMetric = (appName) => ({
+ key: ReportManager.createMetricKey({ appName, type: METRIC_TYPE.USER_AGENT }),
+ appName,
+ type: METRIC_TYPE.USER_AGENT,
+ userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.87 Safari/537.36',
+ });
+
describe('ui_metric API', () => {
- const uiStatsMetric = createMetric('myEvent');
- const report = {
- uiStatsMetrics: {
- [uiStatsMetric.key]: uiStatsMetric,
- }
- };
+
it('increments the count field in the document defined by the {app}/{action_type} path', async () => {
+ const uiStatsMetric = createStatsMetric('myEvent');
+ const report = {
+ uiStatsMetrics: {
+ [uiStatsMetric.key]: uiStatsMetric,
+ }
+ };
await supertest
.post('/api/telemetry/report')
.set('kbn-xsrf', 'kibana')
.set('content-type', 'application/json')
- .send({ report })
+ .send(report)
.expect(200);
- return es.search({
- index: '.kibana',
- q: 'type:user-action',
- }).then(response => {
- const ids = response.hits.hits.map(({ _id }) => _id);
- expect(ids.includes('user-action:myApp:myEvent'));
- });
+ const response = await es.search({ index: '.kibana', q: 'type:ui-metric' });
+ const ids = response.hits.hits.map(({ _id }) => _id);
+ expect(ids.includes('ui-metric:myApp:myEvent')).to.eql(true);
});
it('supports multiple events', async () => {
- const uiStatsMetric1 = createMetric('myEvent1');
- const uiStatsMetric2 = createMetric('myEvent2');
+ const userAgentMetric = createUserAgentMetric('kibana');
+ const uiStatsMetric1 = createStatsMetric('myEvent');
+ const hrTime = process.hrtime();
+ const nano = hrTime[0] * 1000000000 + hrTime[1];
+ const uniqueEventName = `myEvent${nano}`;
+ const uiStatsMetric2 = createStatsMetric(uniqueEventName);
const report = {
+ userAgent: {
+ [userAgentMetric.key]: userAgentMetric,
+ },
uiStatsMetrics: {
[uiStatsMetric1.key]: uiStatsMetric1,
[uiStatsMetric2.key]: uiStatsMetric2,
@@ -69,17 +80,14 @@ export default function ({ getService }) {
.post('/api/telemetry/report')
.set('kbn-xsrf', 'kibana')
.set('content-type', 'application/json')
- .send({ report })
+ .send(report)
.expect(200);
- return es.search({
- index: '.kibana',
- q: 'type:user-action',
- }).then(response => {
- const ids = response.hits.hits.map(({ _id }) => _id);
- expect(ids.includes('user-action:myApp:myEvent1'));
- expect(ids.includes('user-action:myApp:myEvent2'));
- });
+ const response = await es.search({ index: '.kibana', q: 'type:ui-metric' });
+ const ids = response.hits.hits.map(({ _id }) => _id);
+ expect(ids.includes('ui-metric:myApp:myEvent')).to.eql(true);
+ expect(ids.includes(`ui-metric:myApp:${uniqueEventName}`)).to.eql(true);
+ expect(ids.includes(`ui-metric:kibana-user_agent:${userAgentMetric.userAgent}`)).to.eql(true);
});
});
}
diff --git a/test/common/config.js b/test/common/config.js
index 44e4bef99bf620..58161e545bd06a 100644
--- a/test/common/config.js
+++ b/test/common/config.js
@@ -17,6 +17,7 @@
* under the License.
*/
+import path from 'path';
import { format as formatUrl } from 'url';
import { OPTIMIZE_BUNDLE_DIR, esTestConfig, kbnTestConfig } from '@kbn/test';
import { services } from './services';
@@ -57,9 +58,12 @@ export default function () {
`--kibana.disableWelcomeScreen=true`,
'--telemetry.banner=false',
`--server.maxPayloadBytes=1679958`,
+ // newsfeed mock service
+ `--plugin-path=${path.join(__dirname, 'fixtures', 'plugins', 'newsfeed')}`,
+ `--newsfeed.service.urlRoot=${servers.kibana.protocol}://${servers.kibana.hostname}:${servers.kibana.port}`,
+ `--newsfeed.service.pathTemplate=/api/_newsfeed-FTS-external-service-simulators/kibana/v{VERSION}.json`,
],
},
-
services
};
}
diff --git a/test/common/fixtures/plugins/newsfeed/index.ts b/test/common/fixtures/plugins/newsfeed/index.ts
new file mode 100644
index 00000000000000..beee9bb5c6069d
--- /dev/null
+++ b/test/common/fixtures/plugins/newsfeed/index.ts
@@ -0,0 +1,33 @@
+/*
+ * 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 Hapi from 'hapi';
+import { initPlugin as initNewsfeed } from './newsfeed_simulation';
+
+const NAME = 'newsfeed-FTS-external-service-simulators';
+
+// eslint-disable-next-line import/no-default-export
+export default function(kibana: any) {
+ return new kibana.Plugin({
+ name: NAME,
+ init: (server: Hapi.Server) => {
+ initNewsfeed(server, `/api/_${NAME}`);
+ },
+ });
+}
diff --git a/test/common/fixtures/plugins/newsfeed/newsfeed_simulation.ts b/test/common/fixtures/plugins/newsfeed/newsfeed_simulation.ts
new file mode 100644
index 00000000000000..2a7ea3793324da
--- /dev/null
+++ b/test/common/fixtures/plugins/newsfeed/newsfeed_simulation.ts
@@ -0,0 +1,114 @@
+/*
+ * 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 Hapi from 'hapi';
+
+interface WebhookRequest extends Hapi.Request {
+ payload: string;
+}
+
+export async function initPlugin(server: Hapi.Server, path: string) {
+ server.route({
+ method: ['GET'],
+ path: `${path}/kibana/v{version}.json`,
+ options: {
+ cors: {
+ origin: ['*'],
+ additionalHeaders: [
+ 'Sec-Fetch-Mode',
+ 'Access-Control-Request-Method',
+ 'Access-Control-Request-Headers',
+ 'cache-control',
+ 'x-requested-with',
+ 'Origin',
+ 'User-Agent',
+ 'DNT',
+ 'content-type',
+ 'kbn-version',
+ ],
+ },
+ },
+ handler: newsfeedHandler,
+ });
+
+ server.route({
+ method: ['GET'],
+ path: `${path}/kibana/crash.json`,
+ options: {
+ cors: {
+ origin: ['*'],
+ additionalHeaders: [
+ 'Sec-Fetch-Mode',
+ 'Access-Control-Request-Method',
+ 'Access-Control-Request-Headers',
+ 'cache-control',
+ 'x-requested-with',
+ 'Origin',
+ 'User-Agent',
+ 'DNT',
+ 'content-type',
+ 'kbn-version',
+ ],
+ },
+ },
+ handler() {
+ throw new Error('Internal server error');
+ },
+ });
+}
+
+function newsfeedHandler(request: WebhookRequest, h: any) {
+ return htmlResponse(h, 200, JSON.stringify(mockNewsfeed(request.params.version)));
+}
+
+const mockNewsfeed = (version: string) => ({
+ items: [
+ {
+ title: { en: `You are functionally testing the newsfeed widget with fixtures!` },
+ description: { en: 'See test/common/fixtures/plugins/newsfeed/newsfeed_simulation' },
+ link_text: { en: 'Generic feed-viewer could go here' },
+ link_url: { en: 'https://feeds.elastic.co' },
+ languages: null,
+ badge: null,
+ image_url: null,
+ publish_on: '2019-06-21T00:00:00',
+ expire_on: '2019-12-31T00:00:00',
+ hash: '39ca7d409c7eb25f4c69a5a6a11309b2f5ced7ca3f9b3a0109517126e0fd91ca',
+ },
+ {
+ title: { en: 'Staging too!' },
+ description: { en: 'Hello world' },
+ link_text: { en: 'Generic feed-viewer could go here' },
+ link_url: { en: 'https://feeds-staging.elastic.co' },
+ languages: null,
+ badge: null,
+ image_url: null,
+ publish_on: '2019-06-21T00:00:00',
+ expire_on: '2019-12-31T00:00:00',
+ hash: 'db445c9443eb50ea2eb15f20edf89cf0f7dac2b058b11cafc2c8c288b6e4ce2a',
+ },
+ ],
+});
+
+function htmlResponse(h: any, code: number, text: string) {
+ return h
+ .response(text)
+ .type('application/json')
+ .code(code);
+}
diff --git a/test/common/fixtures/plugins/newsfeed/package.json b/test/common/fixtures/plugins/newsfeed/package.json
new file mode 100644
index 00000000000000..5291b1031b0a94
--- /dev/null
+++ b/test/common/fixtures/plugins/newsfeed/package.json
@@ -0,0 +1,7 @@
+{
+ "name": "newsfeed-fixtures",
+ "version": "0.0.0",
+ "kibana": {
+ "version": "kibana"
+ }
+}
diff --git a/test/functional/apps/discover/_shared_links.js b/test/functional/apps/discover/_shared_links.js
index f0d34207a87a2f..0b2b4f14f126dd 100644
--- a/test/functional/apps/discover/_shared_links.js
+++ b/test/functional/apps/discover/_shared_links.js
@@ -25,11 +25,12 @@ export default function ({ getService, getPageObjects }) {
const esArchiver = getService('esArchiver');
const kibanaServer = getService('kibanaServer');
const PageObjects = getPageObjects(['common', 'discover', 'share', 'timePicker']);
+ const browser = getService('browser');
describe('shared links', function describeIndexTests() {
let baseUrl;
- before(async function () {
+ async function setup({ storeStateInSessionStorage }) {
baseUrl = PageObjects.common.getHostPort();
log.debug('baseUrl = ' + baseUrl);
// browsers don't show the ':port' if it's 80 or 443 so we have to
@@ -47,9 +48,12 @@ export default function ({ getService, getPageObjects }) {
log.debug('load kibana index with default index pattern');
await esArchiver.load('discover');
-
await esArchiver.loadIfNeeded('logstash_functional');
+ await kibanaServer.uiSettings.replace({
+ 'state:storeInSessionStorage': storeStateInSessionStorage
+ });
+
log.debug('discover');
await PageObjects.common.navigateToApp('discover');
@@ -60,47 +64,103 @@ export default function ({ getService, getPageObjects }) {
await PageObjects.common.sleep(1000);
await PageObjects.share.clickShareTopNavButton();
- });
- describe('permalink', function () {
- it('should allow for copying the snapshot URL', async function () {
- const expectedUrl =
- baseUrl +
- '/app/kibana?_t=1453775307251#' +
- '/discover?_g=(refreshInterval:(pause:!t,value:0),time' +
- ':(from:\'2015-09-19T06:31:44.000Z\',to:\'2015-09' +
- '-23T18:31:44.000Z\'))&_a=(columns:!(_source),index:\'logstash-' +
- '*\',interval:auto,query:(language:kuery,query:\'\')' +
- ',sort:!(!(\'@timestamp\',desc)))';
- const actualUrl = await PageObjects.share.getSharedUrl();
- // strip the timestamp out of each URL
- expect(actualUrl.replace(/_t=\d{13}/, '_t=TIMESTAMP')).to.be(
- expectedUrl.replace(/_t=\d{13}/, '_t=TIMESTAMP')
- );
+ return async () => {
+ await kibanaServer.uiSettings.replace({
+ 'state:storeInSessionStorage': undefined
+ });
+ };
+ }
+
+ describe('shared links with state in query', async () => {
+ let teardown;
+ before(async function () {
+ teardown = await setup({ storeStateInSessionStorage: false });
+ });
+
+ after(async function () {
+ await teardown();
});
- it('should allow for copying the snapshot URL as a short URL', async function () {
- const re = new RegExp(baseUrl + '/goto/[0-9a-f]{32}$');
- await PageObjects.share.checkShortenUrl();
- await retry.try(async () => {
+ describe('permalink', function () {
+ it('should allow for copying the snapshot URL', async function () {
+ const expectedUrl =
+ baseUrl +
+ '/app/kibana?_t=1453775307251#' +
+ '/discover?_g=(refreshInterval:(pause:!t,value:0),time' +
+ ':(from:\'2015-09-19T06:31:44.000Z\',to:\'2015-09' +
+ '-23T18:31:44.000Z\'))&_a=(columns:!(_source),index:\'logstash-' +
+ '*\',interval:auto,query:(language:kuery,query:\'\')' +
+ ',sort:!(!(\'@timestamp\',desc)))';
+ const actualUrl = await PageObjects.share.getSharedUrl();
+ // strip the timestamp out of each URL
+ expect(actualUrl.replace(/_t=\d{13}/, '_t=TIMESTAMP')).to.be(
+ expectedUrl.replace(/_t=\d{13}/, '_t=TIMESTAMP')
+ );
+ });
+
+ it('should allow for copying the snapshot URL as a short URL', async function () {
+ const re = new RegExp(baseUrl + '/goto/[0-9a-f]{32}$');
+ await PageObjects.share.checkShortenUrl();
+ await retry.try(async () => {
+ const actualUrl = await PageObjects.share.getSharedUrl();
+ expect(actualUrl).to.match(re);
+ });
+ });
+
+ it('should allow for copying the saved object URL', async function () {
+ const expectedUrl =
+ baseUrl +
+ '/app/kibana#' +
+ '/discover/ab12e3c0-f231-11e6-9486-733b1ac9221a' +
+ '?_g=(refreshInterval%3A(pause%3A!t%2Cvalue%3A0)' +
+ '%2Ctime%3A(from%3A\'2015-09-19T06%3A31%3A44.000Z\'%2C' +
+ 'to%3A\'2015-09-23T18%3A31%3A44.000Z\'))';
+ await PageObjects.discover.loadSavedSearch('A Saved Search');
+ await PageObjects.share.clickShareTopNavButton();
+ await PageObjects.share.exportAsSavedObject();
const actualUrl = await PageObjects.share.getSharedUrl();
- expect(actualUrl).to.match(re);
+ expect(actualUrl).to.be(expectedUrl);
});
});
+ });
+
+ describe('shared links with state in sessionStorage', async () => {
+ let teardown;
+ before(async function () {
+ teardown = await setup({ storeStateInSessionStorage: true });
+ });
- it('should allow for copying the saved object URL', async function () {
- const expectedUrl =
- baseUrl +
- '/app/kibana#' +
- '/discover/ab12e3c0-f231-11e6-9486-733b1ac9221a' +
- '?_g=(refreshInterval%3A(pause%3A!t%2Cvalue%3A0)' +
- '%2Ctime%3A(from%3A\'2015-09-19T06%3A31%3A44.000Z\'%2C' +
- 'to%3A\'2015-09-23T18%3A31%3A44.000Z\'))';
- await PageObjects.discover.loadSavedSearch('A Saved Search');
- await PageObjects.share.clickShareTopNavButton();
- await PageObjects.share.exportAsSavedObject();
- const actualUrl = await PageObjects.share.getSharedUrl();
- expect(actualUrl).to.be(expectedUrl);
+ after(async function () {
+ await teardown();
+ });
+
+ describe('permalink', function () {
+ it('should allow for copying the snapshot URL as a short URL and should open it', async function () {
+ const re = new RegExp(baseUrl + '/goto/[0-9a-f]{32}$');
+ await PageObjects.share.checkShortenUrl();
+ let actualUrl;
+ await retry.try(async () => {
+ actualUrl = await PageObjects.share.getSharedUrl();
+ expect(actualUrl).to.match(re);
+ });
+
+ const actualTime = await PageObjects.timePicker.getTimeConfig();
+
+ await browser.clearSessionStorage();
+ await browser.get(actualUrl, false);
+ await retry.waitFor(
+ 'shortUrl resolves and opens',
+ async () => {
+ const resolvedUrl = await browser.getCurrentUrl();
+ expect(resolvedUrl).to.match(/discover/);
+ const resolvedTime = await PageObjects.timePicker.getTimeConfig();
+ expect(resolvedTime.start).to.equal(actualTime.start);
+ expect(resolvedTime.end).to.equal(actualTime.end);
+ return true;
+ }
+ );
+ });
});
});
});
diff --git a/test/functional/apps/home/_newsfeed.ts b/test/functional/apps/home/_newsfeed.ts
new file mode 100644
index 00000000000000..35d7ac8adefa53
--- /dev/null
+++ b/test/functional/apps/home/_newsfeed.ts
@@ -0,0 +1,62 @@
+/*
+ * 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 { FtrProviderContext } from '../../ftr_provider_context';
+
+export default function({ getService, getPageObjects }: FtrProviderContext) {
+ const globalNav = getService('globalNav');
+ const PageObjects = getPageObjects(['common', 'newsfeed']);
+
+ describe('Newsfeed', () => {
+ before(async () => {
+ await PageObjects.newsfeed.resetPage();
+ });
+
+ it('has red icon which is a sign of not checked news', async () => {
+ const hasCheckedNews = await PageObjects.newsfeed.getRedButtonSign();
+ expect(hasCheckedNews).to.be(true);
+ });
+
+ it('clicking on newsfeed icon should open you newsfeed', async () => {
+ await globalNav.clickNewsfeed();
+ const isOpen = await PageObjects.newsfeed.openNewsfeedPanel();
+ expect(isOpen).to.be(true);
+ });
+
+ it('no red icon, because all news is checked', async () => {
+ const hasCheckedNews = await PageObjects.newsfeed.getRedButtonSign();
+ expect(hasCheckedNews).to.be(false);
+ });
+
+ it('shows all news from newsfeed', async () => {
+ const objects = await PageObjects.newsfeed.getNewsfeedList();
+ expect(objects).to.eql([
+ '21 June 2019\nYou are functionally testing the newsfeed widget with fixtures!\nSee test/common/fixtures/plugins/newsfeed/newsfeed_simulation\nGeneric feed-viewer could go here',
+ '21 June 2019\nStaging too!\nHello world\nGeneric feed-viewer could go here',
+ ]);
+ });
+
+ it('clicking on newsfeed icon should close opened newsfeed', async () => {
+ await globalNav.clickNewsfeed();
+ const isOpen = await PageObjects.newsfeed.openNewsfeedPanel();
+ expect(isOpen).to.be(false);
+ });
+ });
+}
diff --git a/test/functional/apps/home/index.js b/test/functional/apps/home/index.js
index 17c93680088cbc..f3f564fbd29193 100644
--- a/test/functional/apps/home/index.js
+++ b/test/functional/apps/home/index.js
@@ -29,6 +29,7 @@ export default function ({ getService, loadTestFile }) {
loadTestFile(require.resolve('./_navigation'));
loadTestFile(require.resolve('./_home'));
+ loadTestFile(require.resolve('./_newsfeed'));
loadTestFile(require.resolve('./_add_data'));
loadTestFile(require.resolve('./_sample_data'));
});
diff --git a/test/functional/apps/visualize/input_control_vis/input_control_options.js b/test/functional/apps/visualize/input_control_vis/input_control_options.js
index b659d29b158b7d..4088ab6193a59f 100644
--- a/test/functional/apps/visualize/input_control_vis/input_control_options.js
+++ b/test/functional/apps/visualize/input_control_vis/input_control_options.js
@@ -133,13 +133,13 @@ export default function ({ getService, getPageObjects }) {
describe('updateFiltersOnChange is true', () => {
before(async () => {
await PageObjects.visualize.clickVisEditorTab('options');
- await PageObjects.visualize.checkCheckbox('inputControlEditorUpdateFiltersOnChangeCheckbox');
+ await PageObjects.visualize.checkSwitch('inputControlEditorUpdateFiltersOnChangeCheckbox');
await PageObjects.visualize.clickGo();
});
after(async () => {
await PageObjects.visualize.clickVisEditorTab('options');
- await PageObjects.visualize.uncheckCheckbox('inputControlEditorUpdateFiltersOnChangeCheckbox');
+ await PageObjects.visualize.uncheckSwitch('inputControlEditorUpdateFiltersOnChangeCheckbox');
await PageObjects.visualize.clickGo();
});
diff --git a/test/functional/page_objects/dashboard_page.js b/test/functional/page_objects/dashboard_page.js
index ca141114f976dc..af3a15e9b30153 100644
--- a/test/functional/page_objects/dashboard_page.js
+++ b/test/functional/page_objects/dashboard_page.js
@@ -347,7 +347,7 @@ export function DashboardPageProvider({ getService, getPageObjects }) {
async clickSave() {
log.debug('DashboardPage.clickSave');
- await testSubjects.clickWhenNotDisabled('confirmSaveSavedObjectButton');
+ await testSubjects.click('confirmSaveSavedObjectButton');
}
async pressEnterKey() {
@@ -543,9 +543,10 @@ export function DashboardPageProvider({ getService, getPageObjects }) {
async setSaveAsNewCheckBox(checked) {
log.debug('saveAsNewCheckbox: ' + checked);
const saveAsNewCheckbox = await testSubjects.find('saveAsNewCheckbox');
- const isAlreadyChecked = (await saveAsNewCheckbox.getAttribute('checked') === 'true');
+ const isAlreadyChecked = (await saveAsNewCheckbox.getAttribute('aria-checked') === 'true');
if (isAlreadyChecked !== checked) {
log.debug('Flipping save as new checkbox');
+ const saveAsNewCheckbox = await testSubjects.find('saveAsNewCheckbox');
await retry.try(() => saveAsNewCheckbox.click());
}
}
@@ -553,9 +554,10 @@ export function DashboardPageProvider({ getService, getPageObjects }) {
async setStoreTimeWithDashboard(checked) {
log.debug('Storing time with dashboard: ' + checked);
const storeTimeCheckbox = await testSubjects.find('storeTimeWithDashboard');
- const isAlreadyChecked = (await storeTimeCheckbox.getAttribute('checked') === 'true');
+ const isAlreadyChecked = (await storeTimeCheckbox.getAttribute('aria-checked') === 'true');
if (isAlreadyChecked !== checked) {
log.debug('Flipping store time checkbox');
+ const storeTimeCheckbox = await testSubjects.find('storeTimeWithDashboard');
await retry.try(() => storeTimeCheckbox.click());
}
}
diff --git a/test/functional/page_objects/index.ts b/test/functional/page_objects/index.ts
index 1e8c454f42cfe3..84562990191d1b 100644
--- a/test/functional/page_objects/index.ts
+++ b/test/functional/page_objects/index.ts
@@ -35,6 +35,7 @@ import { HeaderPageProvider } from './header_page';
import { HomePageProvider } from './home_page';
// @ts-ignore not TS yet
import { MonitoringPageProvider } from './monitoring_page';
+import { NewsfeedPageProvider } from './newsfeed_page';
// @ts-ignore not TS yet
import { PointSeriesPageProvider } from './point_series_page';
// @ts-ignore not TS yet
@@ -61,6 +62,7 @@ export const pageObjects = {
header: HeaderPageProvider,
home: HomePageProvider,
monitoring: MonitoringPageProvider,
+ newsfeed: NewsfeedPageProvider,
pointSeries: PointSeriesPageProvider,
settings: SettingsPageProvider,
share: SharePageProvider,
diff --git a/test/functional/page_objects/newsfeed_page.ts b/test/functional/page_objects/newsfeed_page.ts
new file mode 100644
index 00000000000000..24ff21f0b47de8
--- /dev/null
+++ b/test/functional/page_objects/newsfeed_page.ts
@@ -0,0 +1,73 @@
+/*
+ * 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 { FtrProviderContext } from '../ftr_provider_context';
+
+export function NewsfeedPageProvider({ getService, getPageObjects }: FtrProviderContext) {
+ const log = getService('log');
+ const retry = getService('retry');
+ const flyout = getService('flyout');
+ const testSubjects = getService('testSubjects');
+ const PageObjects = getPageObjects(['common']);
+
+ class NewsfeedPage {
+ async resetPage() {
+ await PageObjects.common.navigateToUrl('home');
+ }
+
+ async closeNewsfeedPanel() {
+ await flyout.ensureClosed('NewsfeedFlyout');
+ log.debug('clickNewsfeed icon');
+ await retry.waitFor('newsfeed flyout', async () => {
+ if (await testSubjects.exists('NewsfeedFlyout')) {
+ await testSubjects.click('NewsfeedFlyout > euiFlyoutCloseButton');
+ return false;
+ }
+ return true;
+ });
+ }
+
+ async openNewsfeedPanel() {
+ log.debug('clickNewsfeed icon');
+ return await testSubjects.exists('NewsfeedFlyout');
+ }
+
+ async getRedButtonSign() {
+ return await testSubjects.exists('showBadgeNews');
+ }
+
+ async getNewsfeedList() {
+ const list = await testSubjects.find('NewsfeedFlyout');
+ const cells = await list.findAllByCssSelector('[data-test-subj="newsHeadAlert"]');
+
+ const objects = [];
+ for (const cell of cells) {
+ objects.push(await cell.getVisibleText());
+ }
+
+ return objects;
+ }
+
+ async openNewsfeedEmptyPanel() {
+ return await testSubjects.exists('emptyNewsfeed');
+ }
+ }
+
+ return new NewsfeedPage();
+}
diff --git a/test/functional/page_objects/visual_builder_page.ts b/test/functional/page_objects/visual_builder_page.ts
index 570511bee4bc5b..4b65de57f12d81 100644
--- a/test/functional/page_objects/visual_builder_page.ts
+++ b/test/functional/page_objects/visual_builder_page.ts
@@ -305,9 +305,9 @@ export function VisualBuilderPageProvider({ getService, getPageObjects }: FtrPro
public async getRhythmChartLegendValue(nth = 0) {
await PageObjects.visualize.waitForVisualizationRenderingStabilized();
- const metricValue = (await find.allByCssSelector(
- `.echLegendItem .echLegendItem__displayValue`
- ))[nth];
+ const metricValue = (
+ await find.allByCssSelector(`.echLegendItem .echLegendItem__displayValue`)
+ )[nth];
await metricValue.moveMouseTo();
return await metricValue.getVisibleText();
}
diff --git a/test/functional/page_objects/visualize_page.js b/test/functional/page_objects/visualize_page.js
index f3a90f20b6686c..81d26a4b69478e 100644
--- a/test/functional/page_objects/visualize_page.js
+++ b/test/functional/page_objects/visualize_page.js
@@ -372,6 +372,28 @@ export function VisualizePageProvider({ getService, getPageObjects, updateBaseli
}
}
+ async isSwitchChecked(selector) {
+ const checkbox = await testSubjects.find(selector);
+ const isChecked = await checkbox.getAttribute('aria-checked');
+ return isChecked === 'true';
+ }
+
+ async checkSwitch(selector) {
+ const isChecked = await this.isSwitchChecked(selector);
+ if (!isChecked) {
+ log.debug(`checking switch ${selector}`);
+ await testSubjects.click(selector);
+ }
+ }
+
+ async uncheckSwitch(selector) {
+ const isChecked = await this.isSwitchChecked(selector);
+ if (isChecked) {
+ log.debug(`unchecking switch ${selector}`);
+ await testSubjects.click(selector);
+ }
+ }
+
async setSelectByOptionText(selectId, optionText) {
const selectField = await find.byCssSelector(`#${selectId}`);
const options = await find.allByCssSelector(`#${selectId} > option`);
@@ -1009,7 +1031,7 @@ export function VisualizePageProvider({ getService, getPageObjects, updateBaseli
async setIsFilteredByCollarCheckbox(value = true) {
await retry.try(async () => {
- const isChecked = await this.isChecked('isFilteredByCollarCheckbox');
+ const isChecked = await this.isSwitchChecked('isFilteredByCollarCheckbox');
if (isChecked !== value) {
await testSubjects.click('isFilteredByCollarCheckbox');
throw new Error('isFilteredByCollar not set correctly');
diff --git a/test/functional/services/browser.ts b/test/functional/services/browser.ts
index 97e02958f3787f..a8ce4270d42055 100644
--- a/test/functional/services/browser.ts
+++ b/test/functional/services/browser.ts
@@ -429,6 +429,15 @@ export async function BrowserProvider({ getService }: FtrProviderContext) {
);
}
+ /**
+ * Clears session storage for the focused window/frame.
+ *
+ * @return {Promise}
+ */
+ public async clearSessionStorage(): Promise {
+ await driver.executeScript('return window.sessionStorage.clear();');
+ }
+
/**
* Closes the currently focused window. In most environments, after the window has been
* closed, it is necessary to explicitly switch to whatever window is now focused.
diff --git a/test/functional/services/global_nav.ts b/test/functional/services/global_nav.ts
index 164ea999fa2793..df3aac67f22a1b 100644
--- a/test/functional/services/global_nav.ts
+++ b/test/functional/services/global_nav.ts
@@ -32,6 +32,10 @@ export function GlobalNavProvider({ getService }: FtrProviderContext) {
return await testSubjects.click('headerGlobalNav > logo');
}
+ public async clickNewsfeed(): Promise {
+ return await testSubjects.click('headerGlobalNav > newsfeed');
+ }
+
public async exists(): Promise {
return await testSubjects.exists('headerGlobalNav');
}
diff --git a/test/functional/services/remote/poll_for_log_entry.ts b/test/functional/services/remote/poll_for_log_entry.ts
index b6b68cc0d3cf97..71e2711906fce7 100644
--- a/test/functional/services/remote/poll_for_log_entry.ts
+++ b/test/functional/services/remote/poll_for_log_entry.ts
@@ -95,10 +95,7 @@ export function pollForLogEntry$(
[new logging.Entry('SEVERE', `ERROR FETCHING BROWSR LOGS: ${error.message}`)],
// pause 10 seconds then resubscribe
- Rx.of(1).pipe(
- delay(10 * 1000),
- mergeMapTo(resubscribe)
- )
+ Rx.of(1).pipe(delay(10 * 1000), mergeMapTo(resubscribe))
);
})
)
diff --git a/test/functional/services/saved_query_management_component.ts b/test/functional/services/saved_query_management_component.ts
index f134fde028e09d..d6de0be0c172e6 100644
--- a/test/functional/services/saved_query_management_component.ts
+++ b/test/functional/services/saved_query_management_component.ts
@@ -118,15 +118,17 @@ export function SavedQueryManagementComponentProvider({ getService }: FtrProvide
await testSubjects.setValue('saveQueryFormDescription', description);
const currentIncludeFiltersValue =
- (await testSubjects.getAttribute('saveQueryFormIncludeFiltersOption', 'checked')) ===
+ (await testSubjects.getAttribute('saveQueryFormIncludeFiltersOption', 'aria-checked')) ===
'true';
if (currentIncludeFiltersValue !== includeFilters) {
await testSubjects.click('saveQueryFormIncludeFiltersOption');
}
const currentIncludeTimeFilterValue =
- (await testSubjects.getAttribute('saveQueryFormIncludeTimeFilterOption', 'checked')) ===
- 'true';
+ (await testSubjects.getAttribute(
+ 'saveQueryFormIncludeTimeFilterOption',
+ 'aria-checked'
+ )) === 'true';
if (currentIncludeTimeFilterValue !== includeTimeFilter) {
await testSubjects.click('saveQueryFormIncludeTimeFilterOption');
}
diff --git a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/package.json b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/package.json
index 766e6168002c29..da1bb597f57308 100644
--- a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/package.json
+++ b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/package.json
@@ -7,7 +7,7 @@
},
"license": "Apache-2.0",
"dependencies": {
- "@elastic/eui": "14.8.0",
+ "@elastic/eui": "14.9.0",
"react": "^16.8.0",
"react-dom": "^16.8.0"
}
diff --git a/test/plugin_functional/plugins/core_plugin_chromeless/kibana.json b/test/plugin_functional/plugins/core_plugin_chromeless/kibana.json
new file mode 100644
index 00000000000000..a8a5616627726d
--- /dev/null
+++ b/test/plugin_functional/plugins/core_plugin_chromeless/kibana.json
@@ -0,0 +1,8 @@
+{
+ "id": "core_plugin_chromeless",
+ "version": "0.0.1",
+ "kibanaVersion": "kibana",
+ "configPath": ["core_plugin_chromeless"],
+ "server": false,
+ "ui": true
+}
diff --git a/test/plugin_functional/plugins/core_plugin_chromeless/package.json b/test/plugin_functional/plugins/core_plugin_chromeless/package.json
new file mode 100644
index 00000000000000..eff6c1e1f142a0
--- /dev/null
+++ b/test/plugin_functional/plugins/core_plugin_chromeless/package.json
@@ -0,0 +1,17 @@
+{
+ "name": "core_plugin_chromeless",
+ "version": "1.0.0",
+ "main": "target/test/plugin_functional/plugins/core_plugin_chromeless",
+ "kibana": {
+ "version": "kibana",
+ "templateVersion": "1.0.0"
+ },
+ "license": "Apache-2.0",
+ "scripts": {
+ "kbn": "node ../../../../scripts/kbn.js",
+ "build": "rm -rf './target' && tsc"
+ },
+ "devDependencies": {
+ "typescript": "3.5.3"
+ }
+}
diff --git a/test/plugin_functional/plugins/core_plugin_chromeless/public/application.tsx b/test/plugin_functional/plugins/core_plugin_chromeless/public/application.tsx
new file mode 100644
index 00000000000000..556a9ca140715f
--- /dev/null
+++ b/test/plugin_functional/plugins/core_plugin_chromeless/public/application.tsx
@@ -0,0 +1,74 @@
+/*
+ * 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 React from 'react';
+import { render, unmountComponentAtNode } from 'react-dom';
+import { BrowserRouter as Router, Route } from 'react-router-dom';
+import {
+ EuiPage,
+ EuiPageBody,
+ EuiPageContent,
+ EuiPageContentBody,
+ EuiPageContentHeader,
+ EuiPageContentHeaderSection,
+ EuiPageHeader,
+ EuiPageHeaderSection,
+ EuiTitle,
+} from '@elastic/eui';
+
+import { AppMountContext, AppMountParameters } from 'kibana/public';
+
+const Home = () => (
+
+
+
+
+ Welcome to Chromeless!
+
+
+
+
+
+
+
+ Chromeless home page section title
+
+
+
+ Where did all the chrome go?
+
+
+);
+
+const ChromelessApp = ({ basename }: { basename: string; context: AppMountContext }) => (
+
+
+
+
+
+);
+
+export const renderApp = (
+ context: AppMountContext,
+ { appBasePath, element }: AppMountParameters
+) => {
+ render(, element);
+
+ return () => unmountComponentAtNode(element);
+};
diff --git a/test/plugin_functional/plugins/core_plugin_chromeless/public/index.ts b/test/plugin_functional/plugins/core_plugin_chromeless/public/index.ts
new file mode 100644
index 00000000000000..6e9959ecbdf9e5
--- /dev/null
+++ b/test/plugin_functional/plugins/core_plugin_chromeless/public/index.ts
@@ -0,0 +1,30 @@
+/*
+ * 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 { PluginInitializer } from 'kibana/public';
+import {
+ CorePluginChromelessPlugin,
+ CorePluginChromelessPluginSetup,
+ CorePluginChromelessPluginStart,
+} from './plugin';
+
+export const plugin: PluginInitializer<
+ CorePluginChromelessPluginSetup,
+ CorePluginChromelessPluginStart
+> = () => new CorePluginChromelessPlugin();
diff --git a/src/legacy/core_plugins/kibana/public/dev_tools/directives/dev_tools_app.js b/test/plugin_functional/plugins/core_plugin_chromeless/public/plugin.tsx
similarity index 51%
rename from src/legacy/core_plugins/kibana/public/dev_tools/directives/dev_tools_app.js
rename to test/plugin_functional/plugins/core_plugin_chromeless/public/plugin.tsx
index c7cb877be676b1..03870410fb3342 100644
--- a/src/legacy/core_plugins/kibana/public/dev_tools/directives/dev_tools_app.js
+++ b/test/plugin_functional/plugins/core_plugin_chromeless/public/plugin.tsx
@@ -17,34 +17,31 @@
* under the License.
*/
-import { uiModules } from 'ui/modules';
-import { DevToolsRegistryProvider } from 'ui/registry/dev_tools';
-import template from '../partials/dev_tools_app.html';
+import { Plugin, CoreSetup } from 'kibana/public';
-uiModules
- .get('apps/dev_tools')
- .directive('kbnDevToolsApp', function (Private, $location) {
- const devToolsRegistry = Private(DevToolsRegistryProvider);
+export class CorePluginChromelessPlugin
+ implements Plugin {
+ public setup(core: CoreSetup, deps: {}) {
+ core.application.register({
+ id: 'chromeless',
+ title: 'Chromeless',
+ chromeless: true,
+ async mount(context, params) {
+ const { renderApp } = await import('./application');
+ return renderApp(context, params);
+ },
+ });
return {
- restrict: 'E',
- replace: true,
- template,
- transclude: true,
- scope: {
- topNavConfig: '='
+ getGreeting() {
+ return 'Hello from Plugin Chromeless!';
},
- bindToController: true,
- controllerAs: 'kbnDevToolsApp',
- controller() {
- this.devTools = devToolsRegistry.inOrder;
- this.currentPath = `#${$location.path()}`;
-
- this.onClick = (item, $event) => {
- if (item.disabled) {
- $event.preventDefault();
- }
- };
- }
};
- });
+ }
+
+ public start() {}
+ public stop() {}
+}
+
+export type CorePluginChromelessPluginSetup = ReturnType;
+export type CorePluginChromelessPluginStart = ReturnType;
diff --git a/test/plugin_functional/plugins/core_plugin_chromeless/tsconfig.json b/test/plugin_functional/plugins/core_plugin_chromeless/tsconfig.json
new file mode 100644
index 00000000000000..5fcaeafbb0d852
--- /dev/null
+++ b/test/plugin_functional/plugins/core_plugin_chromeless/tsconfig.json
@@ -0,0 +1,14 @@
+{
+ "extends": "../../../../tsconfig.json",
+ "compilerOptions": {
+ "outDir": "./target",
+ "skipLibCheck": true
+ },
+ "include": [
+ "index.ts",
+ "public/**/*.ts",
+ "public/**/*.tsx",
+ "../../../../typings/**/*",
+ ],
+ "exclude": []
+}
diff --git a/test/plugin_functional/plugins/demo_search/public/demo_search_strategy.ts b/test/plugin_functional/plugins/demo_search/public/demo_search_strategy.ts
index 377163251010c2..298eaaaf420e04 100644
--- a/test/plugin_functional/plugins/demo_search/public/demo_search_strategy.ts
+++ b/test/plugin_functional/plugins/demo_search/public/demo_search_strategy.ts
@@ -53,9 +53,7 @@ import { DEMO_SEARCH_STRATEGY, IDemoResponse } from '../common';
* @param context - context supplied by other plugins.
* @param search - a search function to access other strategies that have already been registered.
*/
-export const demoClientSearchStrategyProvider: TSearchStrategyProvider<
- typeof DEMO_SEARCH_STRATEGY
-> = (
+export const demoClientSearchStrategyProvider: TSearchStrategyProvider = (
context: ISearchContext,
search: ISearchGeneric
): ISearchStrategy => {
diff --git a/test/plugin_functional/plugins/demo_search/server/demo_search_strategy.ts b/test/plugin_functional/plugins/demo_search/server/demo_search_strategy.ts
index acb75b15196d69..d3f2360add6c03 100644
--- a/test/plugin_functional/plugins/demo_search/server/demo_search_strategy.ts
+++ b/test/plugin_functional/plugins/demo_search/server/demo_search_strategy.ts
@@ -20,9 +20,7 @@
import { TSearchStrategyProvider } from 'src/plugins/data/server';
import { DEMO_SEARCH_STRATEGY } from '../common';
-export const demoSearchStrategyProvider: TSearchStrategyProvider<
- typeof DEMO_SEARCH_STRATEGY
-> = () => {
+export const demoSearchStrategyProvider: TSearchStrategyProvider = () => {
return {
search: request => {
return Promise.resolve({
diff --git a/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json b/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json
index 7c5b6f6be58af2..4d0444265825a2 100644
--- a/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json
+++ b/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json
@@ -7,7 +7,7 @@
},
"license": "Apache-2.0",
"dependencies": {
- "@elastic/eui": "14.8.0",
+ "@elastic/eui": "14.9.0",
"react": "^16.8.0"
}
}
diff --git a/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/package.json b/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/package.json
index ef472b4026957e..196e64af399850 100644
--- a/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/package.json
+++ b/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/package.json
@@ -8,7 +8,7 @@
},
"license": "Apache-2.0",
"dependencies": {
- "@elastic/eui": "14.8.0",
+ "@elastic/eui": "14.9.0",
"react": "^16.8.0"
},
"scripts": {
diff --git a/test/plugin_functional/plugins/kbn_tp_sample_panel_action/package.json b/test/plugin_functional/plugins/kbn_tp_sample_panel_action/package.json
index 277bb09ac745c5..33e60128d08065 100644
--- a/test/plugin_functional/plugins/kbn_tp_sample_panel_action/package.json
+++ b/test/plugin_functional/plugins/kbn_tp_sample_panel_action/package.json
@@ -8,7 +8,7 @@
},
"license": "Apache-2.0",
"dependencies": {
- "@elastic/eui": "14.8.0",
+ "@elastic/eui": "14.9.0",
"react": "^16.8.0"
},
"scripts": {
diff --git a/test/plugin_functional/test_suites/core_plugins/applications.ts b/test/plugin_functional/test_suites/core_plugins/applications.ts
index eec2ec019a5150..c16847dab9dc23 100644
--- a/test/plugin_functional/test_suites/core_plugins/applications.ts
+++ b/test/plugin_functional/test_suites/core_plugins/applications.ts
@@ -91,6 +91,22 @@ export default function({ getService, getPageObjects }: PluginFunctionalProvider
await testSubjects.existOrFail('fooAppPageA');
});
+ it('chromeless applications are not visible in apps list', async () => {
+ expect(await appsMenu.linkExists('Chromeless')).to.be(false);
+ });
+
+ it('navigating to chromeless application hides chrome', async () => {
+ await PageObjects.common.navigateToApp('chromeless');
+ await loadingScreenNotShown();
+ expect(await testSubjects.exists('headerGlobalNav')).to.be(false);
+ });
+
+ it('navigating away from chromeless application shows chrome', async () => {
+ await PageObjects.common.navigateToApp('foo');
+ await loadingScreenNotShown();
+ expect(await testSubjects.exists('headerGlobalNav')).to.be(true);
+ });
+
it('can navigate from NP apps to legacy apps', async () => {
await appsMenu.clickLink('Management');
await loadingScreenShown();
diff --git a/src/legacy/core_plugins/data/public/query/query_service.mock.ts b/test/ui_capabilities/newsfeed_err/config.ts
similarity index 52%
rename from src/legacy/core_plugins/data/public/query/query_service.mock.ts
rename to test/ui_capabilities/newsfeed_err/config.ts
index 19a00632ca1921..1f5f770e8447ce 100644
--- a/src/legacy/core_plugins/data/public/query/query_service.mock.ts
+++ b/test/ui_capabilities/newsfeed_err/config.ts
@@ -17,35 +17,29 @@
* under the License.
*/
-import { QueryService, QuerySetup } from '.';
+import { FtrConfigProviderContext } from '@kbn/test/types/ftr';
+// @ts-ignore untyped module
+import getFunctionalConfig from '../../functional/config';
-type QueryServiceClientContract = PublicMethodsOf;
+// eslint-disable-next-line import/no-default-export
+export default async ({ readConfigFile }: FtrConfigProviderContext) => {
+ const functionalConfig = await getFunctionalConfig({ readConfigFile });
-const createSetupContractMock = () => {
- const setupContract: jest.Mocked = {
- helpers: {
- fromUser: jest.fn(),
- toUser: jest.fn(),
- getQueryLog: jest.fn(),
- },
- };
+ return {
+ ...functionalConfig,
- return setupContract;
-};
-
-const createMock = () => {
- const mocked: jest.Mocked = {
- setup: jest.fn(),
- start: jest.fn(),
- stop: jest.fn(),
- };
+ testFiles: [require.resolve('./test')],
- mocked.setup.mockReturnValue(createSetupContractMock());
- return mocked;
-};
+ kbnTestServer: {
+ ...functionalConfig.kbnTestServer,
+ serverArgs: [
+ ...functionalConfig.kbnTestServer.serverArgs,
+ `--newsfeed.service.pathTemplate=/api/_newsfeed-FTS-external-service-simulators/kibana/crash.json`,
+ ],
+ },
-export const queryServiceMock = {
- create: createMock,
- createSetupContract: createSetupContractMock,
- createStartContract: createSetupContractMock,
+ junit: {
+ reportName: 'Newsfeed Error Handling',
+ },
+ };
};
diff --git a/test/ui_capabilities/newsfeed_err/test.ts b/test/ui_capabilities/newsfeed_err/test.ts
new file mode 100644
index 00000000000000..2aa81f34028a02
--- /dev/null
+++ b/test/ui_capabilities/newsfeed_err/test.ts
@@ -0,0 +1,60 @@
+/*
+ * 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 { FtrProviderContext } from '../../functional/ftr_provider_context';
+
+// eslint-disable-next-line import/no-default-export
+export default function uiCapabilitiesTests({ getService, getPageObjects }: FtrProviderContext) {
+ const globalNav = getService('globalNav');
+ const PageObjects = getPageObjects(['common', 'newsfeed']);
+
+ describe('Newsfeed icon button handle errors', function() {
+ this.tags('ciGroup6');
+
+ before(async () => {
+ await PageObjects.newsfeed.resetPage();
+ });
+
+ it('clicking on newsfeed icon should open you empty newsfeed', async () => {
+ await globalNav.clickNewsfeed();
+ const isOpen = await PageObjects.newsfeed.openNewsfeedPanel();
+ expect(isOpen).to.be(true);
+
+ const hasNewsfeedEmptyPanel = await PageObjects.newsfeed.openNewsfeedEmptyPanel();
+ expect(hasNewsfeedEmptyPanel).to.be(true);
+ });
+
+ it('no red icon', async () => {
+ const hasCheckedNews = await PageObjects.newsfeed.getRedButtonSign();
+ expect(hasCheckedNews).to.be(false);
+ });
+
+ it('shows empty panel due to error response', async () => {
+ const objects = await PageObjects.newsfeed.getNewsfeedList();
+ expect(objects).to.eql([]);
+ });
+
+ it('clicking on newsfeed icon should close opened newsfeed', async () => {
+ await globalNav.clickNewsfeed();
+ const isOpen = await PageObjects.newsfeed.openNewsfeedPanel();
+ expect(isOpen).to.be(false);
+ });
+ });
+}
diff --git a/x-pack/dev-tools/jest/create_jest_config.js b/x-pack/dev-tools/jest/create_jest_config.js
index 0fba7d2cefbd52..9d601e680cf87b 100644
--- a/x-pack/dev-tools/jest/create_jest_config.js
+++ b/x-pack/dev-tools/jest/create_jest_config.js
@@ -47,7 +47,10 @@ export function createJestConfig({ kibanaDirectory, xPackKibanaDirectory }) {
// since ESM modules are not natively supported in Jest yet (https://github.com/facebook/jest/issues/4842)
'[/\\\\]node_modules(?![\\/\\\\]@elastic[\\/\\\\]eui)(?![\\/\\\\]monaco-editor)[/\\\\].+\\.js$',
],
- snapshotSerializers: [`${kibanaDirectory}/node_modules/enzyme-to-json/serializer`],
+ snapshotSerializers: [
+ `${kibanaDirectory}/node_modules/enzyme-to-json/serializer`,
+ `${kibanaDirectory}/src/plugins/kibana_react/public/util/test_helpers/react_mount_serializer.ts`
+ ],
reporters: [
'default',
[
diff --git a/x-pack/legacy/common/eui_draggable/index.d.ts b/x-pack/legacy/common/eui_draggable/index.d.ts
index a85da7a69534cb..322966b3c982ef 100644
--- a/x-pack/legacy/common/eui_draggable/index.d.ts
+++ b/x-pack/legacy/common/eui_draggable/index.d.ts
@@ -8,7 +8,7 @@ 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)
+type FirstArgumentOf = Func extends (arg1: infer FirstArgument, ...rest: any[]) => any
? FirstArgument
: never;
export type DragHandleProps = FirstArgumentOf<
diff --git a/x-pack/legacy/common/eui_styled_components/eui_styled_components.tsx b/x-pack/legacy/common/eui_styled_components/eui_styled_components.tsx
index 8e5fba31ac5a44..8becf6892ff92c 100644
--- a/x-pack/legacy/common/eui_styled_components/eui_styled_components.tsx
+++ b/x-pack/legacy/common/eui_styled_components/eui_styled_components.tsx
@@ -38,6 +38,6 @@ const {
injectGlobal,
keyframes,
withTheme,
-} = styledComponents as ThemedStyledComponentsModule;
+} = (styledComponents as unknown) as ThemedStyledComponentsModule;
export { css, euiStyled, EuiThemeProvider, injectGlobal, keyframes, withTheme };
diff --git a/x-pack/legacy/plugins/actions/server/lib/task_runner_factory.test.ts b/x-pack/legacy/plugins/actions/server/lib/task_runner_factory.test.ts
index 3e71725713070d..a5bf42bc2cc019 100644
--- a/x-pack/legacy/plugins/actions/server/lib/task_runner_factory.test.ts
+++ b/x-pack/legacy/plugins/actions/server/lib/task_runner_factory.test.ts
@@ -111,11 +111,9 @@ test('executes the task by calling the executor with proper parameters', async (
expect(runnerResult).toBeUndefined();
expect(spaceIdToNamespace).toHaveBeenCalledWith('test');
- expect(mockedEncryptedSavedObjectsPlugin.getDecryptedAsInternalUser).toHaveBeenCalledWith(
- 'action_task_params',
- '3',
- { namespace: 'namespace-test' }
- );
+ expect(
+ mockedEncryptedSavedObjectsPlugin.getDecryptedAsInternalUser
+ ).toHaveBeenCalledWith('action_task_params', '3', { namespace: 'namespace-test' });
expect(mockedActionExecutor.execute).toHaveBeenCalledWith({
actionId: '2',
params: { baz: true },
diff --git a/x-pack/legacy/plugins/apm/common/apm_saved_object_constants.ts b/x-pack/legacy/plugins/apm/common/apm_saved_object_constants.ts
new file mode 100644
index 00000000000000..ac43b700117c62
--- /dev/null
+++ b/x-pack/legacy/plugins/apm/common/apm_saved_object_constants.ts
@@ -0,0 +1,14 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+// APM Services telemetry
+export const APM_SERVICES_TELEMETRY_SAVED_OBJECT_TYPE =
+ 'apm-services-telemetry';
+export const APM_SERVICES_TELEMETRY_SAVED_OBJECT_ID = 'apm-services-telemetry';
+
+// APM indices
+export const APM_INDICES_SAVED_OBJECT_TYPE = 'apm-indices';
+export const APM_INDICES_SAVED_OBJECT_ID = 'apm-indices';
diff --git a/x-pack/legacy/plugins/apm/common/projections/util/merge_projection/index.ts b/x-pack/legacy/plugins/apm/common/projections/util/merge_projection/index.ts
index 9a8f11c6493c50..522f6d39ac71a2 100644
--- a/x-pack/legacy/plugins/apm/common/projections/util/merge_projection/index.ts
+++ b/x-pack/legacy/plugins/apm/common/projections/util/merge_projection/index.ts
@@ -21,14 +21,14 @@ type SourceProjection = Omit, 'body'> & {
};
type DeepMerge = U extends PlainObject
- ? (T extends PlainObject
- ? (Omit &
- {
- [key in keyof U]: T extends { [k in key]: any }
- ? DeepMerge
- : U[key];
- })
- : U)
+ ? T extends PlainObject
+ ? Omit &
+ {
+ [key in keyof U]: T extends { [k in key]: any }
+ ? DeepMerge
+ : U[key];
+ }
+ : U
: U;
export function mergeProjection<
diff --git a/x-pack/legacy/plugins/grokdebugger/public/sections/grokdebugger/directives/grokdebugger/index.js b/x-pack/legacy/plugins/apm/common/transaction_types.ts
similarity index 61%
rename from x-pack/legacy/plugins/grokdebugger/public/sections/grokdebugger/directives/grokdebugger/index.js
rename to x-pack/legacy/plugins/apm/common/transaction_types.ts
index b33973ed71736d..4dd59af63047d5 100644
--- a/x-pack/legacy/plugins/grokdebugger/public/sections/grokdebugger/directives/grokdebugger/index.js
+++ b/x-pack/legacy/plugins/apm/common/transaction_types.ts
@@ -4,4 +4,6 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import './grokdebugger';
+export const TRANSACTION_PAGE_LOAD = 'page-load';
+export const TRANSACTION_ROUTE_CHANGE = 'route-change';
+export const TRANSACTION_REQUEST = 'request';
diff --git a/x-pack/legacy/plugins/apm/index.ts b/x-pack/legacy/plugins/apm/index.ts
index 556bce9d37bb54..bfbfb4bb99c6a5 100644
--- a/x-pack/legacy/plugins/apm/index.ts
+++ b/x-pack/legacy/plugins/apm/index.ts
@@ -7,13 +7,10 @@
import { i18n } from '@kbn/i18n';
import { Server } from 'hapi';
import { resolve } from 'path';
-import {
- InternalCoreSetup,
- PluginInitializerContext
-} from '../../../../src/core/server';
+import { PluginInitializerContext } from '../../../../src/core/server';
import { LegacyPluginInitializer } from '../../../../src/legacy/types';
import mappings from './mappings.json';
-import { plugin } from './server/new-platform/index';
+import { plugin } from './server/new-platform';
export const apm: LegacyPluginInitializer = kibana => {
return new kibana.Plugin({
@@ -48,7 +45,10 @@ export const apm: LegacyPluginInitializer = kibana => {
},
hacks: ['plugins/apm/hacks/toggle_app_link_in_nav'],
savedObjectSchemas: {
- 'apm-telemetry': {
+ 'apm-services-telemetry': {
+ isNamespaceAgnostic: true
+ },
+ 'apm-indices': {
isNamespaceAgnostic: true
}
},
@@ -90,7 +90,7 @@ export const apm: LegacyPluginInitializer = kibana => {
catalogue: ['apm'],
privileges: {
all: {
- api: ['apm'],
+ api: ['apm', 'apm_write'],
catalogue: ['apm'],
savedObject: {
all: [],
@@ -111,12 +111,13 @@ export const apm: LegacyPluginInitializer = kibana => {
});
const initializerContext = {} as PluginInitializerContext;
- const core = {
- http: {
- server
- }
- } as InternalCoreSetup;
- plugin(initializerContext).setup(core);
+ const legacySetup = {
+ server
+ };
+ plugin(initializerContext).setup(
+ server.newPlatform.setup.core,
+ legacySetup
+ );
}
});
};
diff --git a/x-pack/legacy/plugins/apm/mappings.json b/x-pack/legacy/plugins/apm/mappings.json
index 0b31798242fadc..02296606b1c012 100644
--- a/x-pack/legacy/plugins/apm/mappings.json
+++ b/x-pack/legacy/plugins/apm/mappings.json
@@ -1,5 +1,5 @@
{
- "apm-telemetry": {
+ "apm-services-telemetry": {
"properties": {
"has_any_services": {
"type": "boolean"
diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/MachineLearningFlyout/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/MachineLearningFlyout/index.tsx
index 53f1893a168aca..69f0cf61af2427 100644
--- a/x-pack/legacy/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/MachineLearningFlyout/index.tsx
+++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/MachineLearningFlyout/index.tsx
@@ -6,6 +6,7 @@
import { i18n } from '@kbn/i18n';
import React, { Component } from 'react';
+import { toMountPoint } from '../../../../../../../../../../src/plugins/kibana_react/public';
import { startMLJob } from '../../../../../services/rest/ml';
import { IUrlParams } from '../../../../../context/UrlParamsContext/types';
import { MLJobLink } from '../../../../shared/Links/MachineLearningLinks/MLJobLink';
@@ -71,7 +72,7 @@ export class MachineLearningFlyout extends Component {
defaultMessage: 'Job creation failed'
}
),
- text: (
+ text: toMountPoint(
{i18n.translate(
'xpack.apm.serviceDetails.enableAnomalyDetectionPanel.jobCreationFailedNotificationText',
@@ -105,7 +106,7 @@ export class MachineLearningFlyout extends Component {
defaultMessage: 'Job successfully created'
}
),
- text: (
+ text: toMountPoint(
{i18n.translate(
'xpack.apm.serviceDetails.enableAnomalyDetectionPanel.jobCreatedNotificationText',
diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/WatcherFlyout.tsx b/x-pack/legacy/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/WatcherFlyout.tsx
index 291208b2d90322..d52c869b958722 100644
--- a/x-pack/legacy/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/WatcherFlyout.tsx
+++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/WatcherFlyout.tsx
@@ -30,6 +30,7 @@ import { padLeft, range } from 'lodash';
import moment from 'moment-timezone';
import React, { Component } from 'react';
import styled from 'styled-components';
+import { toMountPoint } from '../../../../../../../../../src/plugins/kibana_react/public';
import { KibanaCoreContext } from '../../../../../../observability/public';
import { IUrlParams } from '../../../../context/UrlParamsContext/types';
import { KibanaLink } from '../../../shared/Links/KibanaLink';
@@ -219,7 +220,7 @@ export class WatcherFlyout extends Component<
defaultMessage: 'Watch creation failed'
}
),
- text: (
+ text: toMountPoint(
{i18n.translate(
'xpack.apm.serviceDetails.enableErrorReportsPanel.watchCreationFailedNotificationText',
@@ -243,7 +244,7 @@ export class WatcherFlyout extends Component<
defaultMessage: 'New watch created!'
}
),
- text: (
+ text: toMountPoint(
{i18n.translate(
'xpack.apm.serviceDetails.enableErrorReportsPanel.watchCreatedNotificationText',
diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceMetrics/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/ServiceMetrics/index.tsx
index 276d309cbb3e39..8005fc17f2a20c 100644
--- a/x-pack/legacy/plugins/apm/public/components/app/ServiceMetrics/index.tsx
+++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceMetrics/index.tsx
@@ -29,9 +29,7 @@ export function ServiceMetrics({ agentName }: ServiceMetricsProps) {
const { data } = useServiceMetricCharts(urlParams, agentName);
const { start, end } = urlParams;
- const localFiltersConfig: React.ComponentProps<
- typeof LocalUIFilters
- > = useMemo(
+ const localFiltersConfig: React.ComponentProps = useMemo(
() => ({
filterNames: ['host', 'containerId', 'podName'],
params: {
diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceNodeOverview/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/ServiceNodeOverview/index.tsx
index b69076b3a1f704..a118871a5e268a 100644
--- a/x-pack/legacy/plugins/apm/public/components/app/ServiceNodeOverview/index.tsx
+++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceNodeOverview/index.tsx
@@ -34,9 +34,7 @@ const ServiceNodeOverview = () => {
const { uiFilters, urlParams } = useUrlParams();
const { serviceName, start, end } = urlParams;
- const localFiltersConfig: React.ComponentProps<
- typeof LocalUIFilters
- > = useMemo(
+ const localFiltersConfig: React.ComponentProps = useMemo(
() => ({
filterNames: ['host', 'containerId', 'podName'],
params: {
diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceOverview/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/ServiceOverview/index.tsx
index d03e70fc99cc69..0702e092a714f5 100644
--- a/x-pack/legacy/plugins/apm/public/components/app/ServiceOverview/index.tsx
+++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceOverview/index.tsx
@@ -9,6 +9,7 @@ import { EuiLink } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React, { useEffect, useMemo } from 'react';
import url from 'url';
+import { toMountPoint } from '../../../../../../../../src/plugins/kibana_react/public';
import { useFetcher } from '../../../hooks/useFetcher';
import { NoServicesMessage } from './NoServicesMessage';
import { ServiceList } from './ServiceList';
@@ -55,7 +56,7 @@ export function ServiceOverview() {
defaultMessage:
'Legacy data was detected within the selected time range'
}),
- text: (
+ text: toMountPoint(
{i18n.translate('xpack.apm.serviceOverview.toastText', {
defaultMessage:
@@ -84,9 +85,7 @@ export function ServiceOverview() {
useTrackPageview({ app: 'apm', path: 'services_overview' });
useTrackPageview({ app: 'apm', path: 'services_overview', delay: 15000 });
- const localFiltersConfig: React.ComponentProps<
- typeof LocalUIFilters
- > = useMemo(
+ const localFiltersConfig: React.ComponentProps = useMemo(
() => ({
filterNames: ['host', 'agentName'],
projection: PROJECTION.SERVICES
diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/Distribution/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/Distribution/index.tsx
index db0ddb56e70880..fc86f4bb78afbe 100644
--- a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/Distribution/index.tsx
+++ b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/Distribution/index.tsx
@@ -195,10 +195,9 @@ export const TransactionDistribution: FunctionComponent = (
}
backgroundHover={(bucket: IChartPoint) => bucket.y > 0 && bucket.sample}
tooltipHeader={(bucket: IChartPoint) =>
- `${timeFormatter(bucket.x0, { withUnit: false })} - ${timeFormatter(
- bucket.x,
- { withUnit: false }
- )} ${unit}`
+ `${timeFormatter(bucket.x0, {
+ withUnit: false
+ })} - ${timeFormatter(bucket.x, { withUnit: false })} ${unit}`
}
tooltipFooter={(bucket: IChartPoint) =>
!bucket.sample &&
diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionOverview/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/TransactionOverview/index.tsx
index d81b7417570a57..f016052df56a2b 100644
--- a/x-pack/legacy/plugins/apm/public/components/app/TransactionOverview/index.tsx
+++ b/x-pack/legacy/plugins/apm/public/components/app/TransactionOverview/index.tsx
@@ -94,9 +94,7 @@ export function TransactionOverview() {
}
}, [http, serviceName, transactionType]);
- const localFiltersConfig: React.ComponentProps<
- typeof LocalUIFilters
- > = useMemo(
+ const localFiltersConfig: React.ComponentProps = useMemo(
() => ({
filterNames: ['transactionResult', 'host', 'containerId', 'podName'],
params: {
diff --git a/x-pack/legacy/plugins/apm/public/components/shared/KueryBar/index.tsx b/x-pack/legacy/plugins/apm/public/components/shared/KueryBar/index.tsx
index 7c0b6f24f87a7b..9918f162a01f43 100644
--- a/x-pack/legacy/plugins/apm/public/components/shared/KueryBar/index.tsx
+++ b/x-pack/legacy/plugins/apm/public/components/shared/KueryBar/index.tsx
@@ -103,12 +103,14 @@ export function KueryBar() {
const boolFilter = getBoolFilter(urlParams);
try {
- const suggestions = (await getSuggestions(
- inputValue,
- selectionStart,
- indexPattern,
- boolFilter
- ))
+ const suggestions = (
+ await getSuggestions(
+ inputValue,
+ selectionStart,
+ indexPattern,
+ boolFilter
+ )
+ )
.filter(suggestion => !startsWith(suggestion.text, 'span.'))
.slice(0, 15);
diff --git a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/Section.tsx b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/Section.tsx
index 6f67b2458ea102..0aaeae3e4ce44f 100644
--- a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/Section.tsx
+++ b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/Section.tsx
@@ -5,17 +5,18 @@
*/
import React from 'react';
+import { isEmpty } from 'lodash';
import { i18n } from '@kbn/i18n';
import { EuiText } from '@elastic/eui';
import { KeyValueTable } from '../KeyValueTable';
import { KeyValuePair } from '../../../utils/flattenObject';
interface Props {
- keyValuePairs?: KeyValuePair[];
+ keyValuePairs: KeyValuePair[];
}
export function Section({ keyValuePairs }: Props) {
- if (keyValuePairs) {
+ if (!isEmpty(keyValuePairs)) {
return ;
}
return (
diff --git a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/__test__/MetadataTable.test.tsx b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/__test__/MetadataTable.test.tsx
index bdf895f423913e..4398c129aa7b84 100644
--- a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/__test__/MetadataTable.test.tsx
+++ b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/__test__/MetadataTable.test.tsx
@@ -21,7 +21,10 @@ describe('MetadataTable', () => {
label: 'Bar',
required: false,
properties: ['props.A', 'props.B'],
- rows: [{ key: 'props.A', value: 'A' }, { key: 'props.B', value: 'B' }]
+ rows: [
+ { key: 'props.A', value: 'A' },
+ { key: 'props.B', value: 'B' }
+ ]
}
] as unknown) as SectionsWithRows;
const output = render();
diff --git a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/__test__/Section.test.tsx b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/__test__/Section.test.tsx
index 7e68b2f84eeadf..4378c7fdeee0c3 100644
--- a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/__test__/Section.test.tsx
+++ b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/__test__/Section.test.tsx
@@ -11,7 +11,7 @@ import { expectTextsInDocument } from '../../../../utils/testHelpers';
describe('Section', () => {
it('shows "empty state message" if no data is available', () => {
- const output = render();
- expectTextsInDocument(output, ['No data available']);
+ const component = render();
+ expectTextsInDocument(component, ['No data available']);
});
});
diff --git a/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/index.tsx b/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/index.tsx
index ca14be237d22bb..b7963b5c75a927 100644
--- a/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/index.tsx
+++ b/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/index.tsx
@@ -78,29 +78,26 @@ interface StackframesGroup {
}
export function getGroupedStackframes(stackframes: IStackframe[]) {
- return stackframes.reduce(
- (acc, stackframe) => {
- const prevGroup = last(acc);
- const shouldAppend =
- prevGroup &&
- prevGroup.isLibraryFrame === stackframe.library_frame &&
- !prevGroup.excludeFromGrouping &&
- !stackframe.exclude_from_grouping;
+ return stackframes.reduce((acc, stackframe) => {
+ const prevGroup = last(acc);
+ const shouldAppend =
+ prevGroup &&
+ prevGroup.isLibraryFrame === stackframe.library_frame &&
+ !prevGroup.excludeFromGrouping &&
+ !stackframe.exclude_from_grouping;
- // append to group
- if (shouldAppend) {
- prevGroup.stackframes.push(stackframe);
- return acc;
- }
-
- // create new group
- acc.push({
- isLibraryFrame: Boolean(stackframe.library_frame),
- excludeFromGrouping: Boolean(stackframe.exclude_from_grouping),
- stackframes: [stackframe]
- });
+ // append to group
+ if (shouldAppend) {
+ prevGroup.stackframes.push(stackframe);
return acc;
- },
- [] as StackframesGroup[]
- );
+ }
+
+ // create new group
+ acc.push({
+ isLibraryFrame: Boolean(stackframe.library_frame),
+ excludeFromGrouping: Boolean(stackframe.exclude_from_grouping),
+ stackframes: [stackframe]
+ });
+ return acc;
+ }, [] as StackframesGroup[]);
}
diff --git a/x-pack/legacy/plugins/apm/public/components/shared/charts/CustomPlot/VoronoiPlot.js b/x-pack/legacy/plugins/apm/public/components/shared/charts/CustomPlot/VoronoiPlot.js
index 52afdffcb0839b..d765a57a56a180 100644
--- a/x-pack/legacy/plugins/apm/public/components/shared/charts/CustomPlot/VoronoiPlot.js
+++ b/x-pack/legacy/plugins/apm/public/components/shared/charts/CustomPlot/VoronoiPlot.js
@@ -32,7 +32,10 @@ class VoronoiPlot extends PureComponent {
onMouseLeave={this.props.onMouseLeave}
>
{
formatYShort={t => `${asDecimal(t)} occ.`}
formatYLong={t => `${asDecimal(t)} occurrences`}
tooltipHeader={bucket =>
- `${timeFormatter(bucket.x0, { withUnit: false })} - ${timeFormatter(
- bucket.x,
- { withUnit: false }
- )} ${unit}`
+ `${timeFormatter(bucket.x0, {
+ withUnit: false
+ })} - ${timeFormatter(bucket.x, { withUnit: false })} ${unit}`
}
width={800}
/>
diff --git a/x-pack/legacy/plugins/apm/public/components/shared/charts/Histogram/index.js b/x-pack/legacy/plugins/apm/public/components/shared/charts/Histogram/index.js
index 7b9586634c7d0b..50c94fe88e6ad2 100644
--- a/x-pack/legacy/plugins/apm/public/components/shared/charts/Histogram/index.js
+++ b/x-pack/legacy/plugins/apm/public/components/shared/charts/Histogram/index.js
@@ -209,7 +209,10 @@ export class HistogramInner extends PureComponent {
)}
{
return {
...bucket,
diff --git a/x-pack/legacy/plugins/apm/public/components/shared/charts/TransactionCharts/ChoroplethMap/ChoroplethToolTip.tsx b/x-pack/legacy/plugins/apm/public/components/shared/charts/TransactionCharts/ChoroplethMap/ChoroplethToolTip.tsx
index 12872fd64a3c42..adcce161c7ac1c 100644
--- a/x-pack/legacy/plugins/apm/public/components/shared/charts/TransactionCharts/ChoroplethMap/ChoroplethToolTip.tsx
+++ b/x-pack/legacy/plugins/apm/public/components/shared/charts/TransactionCharts/ChoroplethMap/ChoroplethToolTip.tsx
@@ -19,7 +19,7 @@ export const ChoroplethToolTip: React.SFC<{
{name}
{i18n.translate(
- 'xpack.apm.metrics.pageLoadCharts.RegionMapChart.ToolTip.avgPageLoadDuration',
+ 'xpack.apm.metrics.durationByCountryMap.RegionMapChart.ToolTip.avgPageLoadDuration',
{
defaultMessage: 'Avg. page load duration:'
}
@@ -31,7 +31,7 @@ export const ChoroplethToolTip: React.SFC<{
(
{i18n.translate(
- 'xpack.apm.metrics.pageLoadCharts.RegionMapChart.ToolTip.countPageLoads',
+ 'xpack.apm.metrics.durationByCountryMap.RegionMapChart.ToolTip.countPageLoads',
{
values: { docCount: asInteger(docCount) },
defaultMessage: '{docCount} page loads'
diff --git a/x-pack/legacy/plugins/apm/public/components/shared/charts/TransactionCharts/PageLoadCharts/index.tsx b/x-pack/legacy/plugins/apm/public/components/shared/charts/TransactionCharts/DurationByCountryMap/index.tsx
similarity index 88%
rename from x-pack/legacy/plugins/apm/public/components/shared/charts/TransactionCharts/PageLoadCharts/index.tsx
rename to x-pack/legacy/plugins/apm/public/components/shared/charts/TransactionCharts/DurationByCountryMap/index.tsx
index 40c6150149eb5c..6176397170797c 100644
--- a/x-pack/legacy/plugins/apm/public/components/shared/charts/TransactionCharts/PageLoadCharts/index.tsx
+++ b/x-pack/legacy/plugins/apm/public/components/shared/charts/TransactionCharts/DurationByCountryMap/index.tsx
@@ -10,7 +10,7 @@ import React from 'react';
import { useAvgDurationByCountry } from '../../../../../hooks/useAvgDurationByCountry';
import { ChoroplethMap } from '../ChoroplethMap';
-export const PageLoadCharts: React.SFC = () => {
+export const DurationByCountryMap: React.SFC = () => {
const { data } = useAvgDurationByCountry();
return (
@@ -20,7 +20,7 @@ export const PageLoadCharts: React.SFC = () => {
{i18n.translate(
- 'xpack.apm.metrics.pageLoadCharts.avgPageLoadByCountryLabel',
+ 'xpack.apm.metrics.durationByCountryMap.avgPageLoadByCountryLabel',
{
defaultMessage:
'Avg. page load duration distribution by country'
diff --git a/x-pack/legacy/plugins/apm/public/components/shared/charts/TransactionCharts/index.tsx b/x-pack/legacy/plugins/apm/public/components/shared/charts/TransactionCharts/index.tsx
index 132067a6c32b71..94f30a8a2325a6 100644
--- a/x-pack/legacy/plugins/apm/public/components/shared/charts/TransactionCharts/index.tsx
+++ b/x-pack/legacy/plugins/apm/public/components/shared/charts/TransactionCharts/index.tsx
@@ -34,7 +34,12 @@ import { LicenseContext } from '../../../../context/LicenseContext';
import { TransactionLineChart } from './TransactionLineChart';
import { isValidCoordinateValue } from '../../../../utils/isValidCoordinateValue';
import { getTimeFormatter } from '../../../../utils/formatters';
-import { PageLoadCharts } from './PageLoadCharts';
+import { DurationByCountryMap } from './DurationByCountryMap';
+import {
+ TRANSACTION_PAGE_LOAD,
+ TRANSACTION_ROUTE_CHANGE,
+ TRANSACTION_REQUEST
+} from '../../../../../common/transaction_types';
interface TransactionChartProps {
hasMLJob: boolean;
@@ -55,8 +60,6 @@ const ShiftedEuiText = styled(EuiText)`
top: 5px;
`;
-const RUM_PAGE_LOAD_TYPE = 'page-load';
-
export class TransactionCharts extends Component {
public getMaxY = (responseTimeSeries: TimeSeries[]) => {
const coordinates = flatten(
@@ -200,19 +203,19 @@ export class TransactionCharts extends Component {
- {transactionType === RUM_PAGE_LOAD_TYPE ? (
+ {transactionType === TRANSACTION_PAGE_LOAD && (
<>
-
+
>
- ) : null}
+ )}
>
);
}
}
function tpmLabel(type?: string) {
- return type === 'request'
+ return type === TRANSACTION_REQUEST
? i18n.translate(
'xpack.apm.metrics.transactionChart.requestsPerMinuteLabel',
{
@@ -229,14 +232,14 @@ function tpmLabel(type?: string) {
function responseTimeLabel(type?: string) {
switch (type) {
- case RUM_PAGE_LOAD_TYPE:
+ case TRANSACTION_PAGE_LOAD:
return i18n.translate(
'xpack.apm.metrics.transactionChart.pageLoadTimesLabel',
{
defaultMessage: 'Page load times'
}
);
- case 'route-change':
+ case TRANSACTION_ROUTE_CHANGE:
return i18n.translate(
'xpack.apm.metrics.transactionChart.routeChangeTimesLabel',
{
diff --git a/x-pack/legacy/plugins/apm/public/hooks/useAvgDurationByCountry.ts b/x-pack/legacy/plugins/apm/public/hooks/useAvgDurationByCountry.ts
index b794332d4aa631..6b3fa1f0d98f7a 100644
--- a/x-pack/legacy/plugins/apm/public/hooks/useAvgDurationByCountry.ts
+++ b/x-pack/legacy/plugins/apm/public/hooks/useAvgDurationByCountry.ts
@@ -9,7 +9,7 @@ import { useUrlParams } from './useUrlParams';
export function useAvgDurationByCountry() {
const {
- urlParams: { serviceName, start, end },
+ urlParams: { serviceName, start, end, transactionName },
uiFilters
} = useUrlParams();
@@ -24,13 +24,14 @@ export function useAvgDurationByCountry() {
query: {
start,
end,
- uiFilters: JSON.stringify(uiFilters)
+ uiFilters: JSON.stringify(uiFilters),
+ transactionName
}
}
});
}
},
- [serviceName, start, end, uiFilters]
+ [serviceName, start, end, uiFilters, transactionName]
);
return {
diff --git a/x-pack/legacy/plugins/apm/public/hooks/useFetcher.tsx b/x-pack/legacy/plugins/apm/public/hooks/useFetcher.tsx
index ba74b0175ff71a..bc6382841be3f1 100644
--- a/x-pack/legacy/plugins/apm/public/hooks/useFetcher.tsx
+++ b/x-pack/legacy/plugins/apm/public/hooks/useFetcher.tsx
@@ -8,6 +8,7 @@ import React, { useContext, useEffect, useState, useMemo } from 'react';
import { idx } from '@kbn/elastic-idx';
import { i18n } from '@kbn/i18n';
import { IHttpFetchError } from 'src/core/public';
+import { toMountPoint } from '../../../../../../src/plugins/kibana_react/public';
import { LoadingIndicatorContext } from '../context/LoadingIndicatorContext';
import { useComponentId } from './useComponentId';
import { useKibanaCore } from '../../../observability/public';
@@ -92,7 +93,7 @@ export function useFetcher(
title: i18n.translate('xpack.apm.fetcher.error.title', {
defaultMessage: `Error while fetching resource`
}),
- text: (
+ text: toMountPoint(
{i18n.translate('xpack.apm.fetcher.error.status', {
diff --git a/x-pack/legacy/plugins/apm/public/selectors/__tests__/chartSelectors.test.ts b/x-pack/legacy/plugins/apm/public/selectors/__tests__/chartSelectors.test.ts
index 80a1b96efb3d6d..2b0263f69db8fa 100644
--- a/x-pack/legacy/plugins/apm/public/selectors/__tests__/chartSelectors.test.ts
+++ b/x-pack/legacy/plugins/apm/public/selectors/__tests__/chartSelectors.test.ts
@@ -35,9 +35,18 @@ describe('chartSelectors', () => {
describe('getResponseTimeSeries', () => {
const apmTimeseries = {
responseTimes: {
- avg: [{ x: 0, y: 100 }, { x: 1000, y: 200 }],
- p95: [{ x: 0, y: 200 }, { x: 1000, y: 300 }],
- p99: [{ x: 0, y: 300 }, { x: 1000, y: 400 }]
+ avg: [
+ { x: 0, y: 100 },
+ { x: 1000, y: 200 }
+ ],
+ p95: [
+ { x: 0, y: 200 },
+ { x: 1000, y: 300 }
+ ],
+ p99: [
+ { x: 0, y: 300 },
+ { x: 1000, y: 400 }
+ ]
},
tpmBuckets: [],
overallAvgDuration: 200
@@ -49,21 +58,30 @@ describe('chartSelectors', () => {
).toEqual([
{
color: '#3185fc',
- data: [{ x: 0, y: 100 }, { x: 1000, y: 200 }],
+ data: [
+ { x: 0, y: 100 },
+ { x: 1000, y: 200 }
+ ],
legendValue: '0 ms',
title: 'Avg.',
type: 'linemark'
},
{
color: '#e6c220',
- data: [{ x: 0, y: 200 }, { x: 1000, y: 300 }],
+ data: [
+ { x: 0, y: 200 },
+ { x: 1000, y: 300 }
+ ],
title: '95th percentile',
titleShort: '95th',
type: 'linemark'
},
{
color: '#f98510',
- data: [{ x: 0, y: 300 }, { x: 1000, y: 400 }],
+ data: [
+ { x: 0, y: 300 },
+ { x: 1000, y: 400 }
+ ],
title: '99th percentile',
titleShort: '99th',
type: 'linemark'
@@ -87,7 +105,13 @@ describe('chartSelectors', () => {
p99: []
},
tpmBuckets: [
- { key: 'HTTP 2xx', dataPoints: [{ x: 0, y: 5 }, { x: 0, y: 2 }] },
+ {
+ key: 'HTTP 2xx',
+ dataPoints: [
+ { x: 0, y: 5 },
+ { x: 0, y: 2 }
+ ]
+ },
{ key: 'HTTP 4xx', dataPoints: [{ x: 0, y: 1 }] },
{ key: 'HTTP 5xx', dataPoints: [{ x: 0, y: 0 }] }
],
@@ -99,7 +123,10 @@ describe('chartSelectors', () => {
expect(getTpmSeries(apmTimeseries, transactionType)).toEqual([
{
color: successColor,
- data: [{ x: 0, y: 5 }, { x: 0, y: 2 }],
+ data: [
+ { x: 0, y: 5 },
+ { x: 0, y: 2 }
+ ],
legendValue: '3.5 tpm',
title: 'HTTP 2xx',
type: 'linemark'
@@ -220,9 +247,18 @@ describe('chartSelectors', () => {
describe('when empty', () => {
it('produces an empty series', () => {
const responseTimes = {
- avg: [{ x: 0, y: 1 }, { x: 100, y: 1 }],
- p95: [{ x: 0, y: 1 }, { x: 100, y: 1 }],
- p99: [{ x: 0, y: 1 }, { x: 100, y: 1 }]
+ avg: [
+ { x: 0, y: 1 },
+ { x: 100, y: 1 }
+ ],
+ p95: [
+ { x: 0, y: 1 },
+ { x: 100, y: 1 }
+ ],
+ p99: [
+ { x: 0, y: 1 },
+ { x: 100, y: 1 }
+ ]
};
const series = getTpmSeries(
{ ...apmTimeseries, responseTimes, tpmBuckets: [] },
diff --git a/x-pack/legacy/plugins/apm/public/utils/testHelpers.tsx b/x-pack/legacy/plugins/apm/public/utils/testHelpers.tsx
index a18882120fe757..a224df9e59e58f 100644
--- a/x-pack/legacy/plugins/apm/public/utils/testHelpers.tsx
+++ b/x-pack/legacy/plugins/apm/public/utils/testHelpers.tsx
@@ -97,6 +97,7 @@ interface MockSetup {
start: number;
end: number;
client: any;
+ internalClient: any;
config: {
get: any;
has: any;
@@ -122,12 +123,21 @@ export async function inspectSearchParams(
}
});
+ const internalClientSpy = jest.fn().mockReturnValueOnce({
+ hits: {
+ total: 0
+ }
+ });
+
const mockSetup = {
start: 1528113600000,
end: 1528977600000,
client: {
search: clientSpy
} as any,
+ internalClient: {
+ search: internalClientSpy
+ } as any,
config: {
get: () => 'myIndex' as any,
has: () => true
@@ -153,8 +163,15 @@ export async function inspectSearchParams(
// we're only extracting the search params
}
+ let params;
+ if (clientSpy.mock.calls.length) {
+ params = clientSpy.mock.calls[0][0];
+ } else {
+ params = internalClientSpy.mock.calls[0][0];
+ }
+
return {
- params: clientSpy.mock.calls[0][0],
+ params,
teardown: () => clientSpy.mockClear()
};
}
diff --git a/x-pack/legacy/plugins/apm/server/lib/apm_telemetry/__test__/apm_telemetry.test.ts b/x-pack/legacy/plugins/apm/server/lib/apm_telemetry/__test__/index.test.ts
similarity index 83%
rename from x-pack/legacy/plugins/apm/server/lib/apm_telemetry/__test__/apm_telemetry.test.ts
rename to x-pack/legacy/plugins/apm/server/lib/apm_telemetry/__test__/index.test.ts
index 6db6e8848ef07e..26cae303542a40 100644
--- a/x-pack/legacy/plugins/apm/server/lib/apm_telemetry/__test__/apm_telemetry.test.ts
+++ b/x-pack/legacy/plugins/apm/server/lib/apm_telemetry/__test__/index.test.ts
@@ -5,11 +5,11 @@
*/
import { SavedObjectAttributes } from 'src/core/server';
+import { createApmTelementry, storeApmServicesTelemetry } from '../index';
import {
- APM_TELEMETRY_DOC_ID,
- createApmTelementry,
- storeApmTelemetry
-} from '../apm_telemetry';
+ APM_SERVICES_TELEMETRY_SAVED_OBJECT_TYPE,
+ APM_SERVICES_TELEMETRY_SAVED_OBJECT_ID
+} from '../../../../common/apm_saved_object_constants';
describe('apm_telemetry', () => {
describe('createApmTelementry', () => {
@@ -44,7 +44,7 @@ describe('apm_telemetry', () => {
});
});
- describe('storeApmTelemetry', () => {
+ describe('storeApmServicesTelemetry', () => {
let server: any;
let apmTelemetry: SavedObjectAttributes;
let savedObjectsClientInstance: any;
@@ -75,24 +75,24 @@ describe('apm_telemetry', () => {
});
it('should call savedObjectsClient create with the given ApmTelemetry object', () => {
- storeApmTelemetry(server, apmTelemetry);
+ storeApmServicesTelemetry(server, apmTelemetry);
expect(savedObjectsClientInstance.create.mock.calls[0][1]).toBe(
apmTelemetry
);
});
it('should call savedObjectsClient create with the apm-telemetry document type and ID', () => {
- storeApmTelemetry(server, apmTelemetry);
+ storeApmServicesTelemetry(server, apmTelemetry);
expect(savedObjectsClientInstance.create.mock.calls[0][0]).toBe(
- 'apm-telemetry'
+ APM_SERVICES_TELEMETRY_SAVED_OBJECT_TYPE
);
expect(savedObjectsClientInstance.create.mock.calls[0][2].id).toBe(
- APM_TELEMETRY_DOC_ID
+ APM_SERVICES_TELEMETRY_SAVED_OBJECT_ID
);
});
it('should call savedObjectsClient create with overwrite: true', () => {
- storeApmTelemetry(server, apmTelemetry);
+ storeApmServicesTelemetry(server, apmTelemetry);
expect(savedObjectsClientInstance.create.mock.calls[0][2].overwrite).toBe(
true
);
diff --git a/x-pack/legacy/plugins/apm/server/lib/apm_telemetry/apm_telemetry.ts b/x-pack/legacy/plugins/apm/server/lib/apm_telemetry/apm_telemetry.ts
deleted file mode 100644
index 54106cce10bac0..00000000000000
--- a/x-pack/legacy/plugins/apm/server/lib/apm_telemetry/apm_telemetry.ts
+++ /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 { Server } from 'hapi';
-import { countBy } from 'lodash';
-import { SavedObjectAttributes } from 'src/core/server';
-import { isAgentName } from '../../../common/agent_name';
-import { getSavedObjectsClient } from '../helpers/saved_objects_client';
-
-export const APM_TELEMETRY_DOC_ID = 'apm-telemetry';
-
-export function createApmTelementry(
- agentNames: string[] = []
-): SavedObjectAttributes {
- const validAgentNames = agentNames.filter(isAgentName);
- return {
- has_any_services: validAgentNames.length > 0,
- services_per_agent: countBy(validAgentNames)
- };
-}
-
-export async function storeApmTelemetry(
- server: Server,
- apmTelemetry: SavedObjectAttributes
-) {
- try {
- const savedObjectsClient = getSavedObjectsClient(server);
- await savedObjectsClient.create('apm-telemetry', apmTelemetry, {
- id: APM_TELEMETRY_DOC_ID,
- overwrite: true
- });
- } catch (e) {
- // eslint-disable-next-line no-console
- console.error('Could not send APM telemetry:', e.message);
- }
-}
diff --git a/x-pack/legacy/plugins/apm/server/lib/apm_telemetry/index.ts b/x-pack/legacy/plugins/apm/server/lib/apm_telemetry/index.ts
index 754666b0a9fa2e..640072d6ec4d8b 100644
--- a/x-pack/legacy/plugins/apm/server/lib/apm_telemetry/index.ts
+++ b/x-pack/legacy/plugins/apm/server/lib/apm_telemetry/index.ts
@@ -4,9 +4,77 @@
* you may not use this file except in compliance with the Elastic License.
*/
-export {
- storeApmTelemetry,
- createApmTelementry,
- APM_TELEMETRY_DOC_ID
-} from './apm_telemetry';
-export { makeApmUsageCollector } from './make_apm_usage_collector';
+import { Server } from 'hapi';
+import { countBy } from 'lodash';
+import { SavedObjectAttributes } from 'src/core/server';
+import { CoreSetup } from 'src/core/server';
+import { isAgentName } from '../../../common/agent_name';
+import { getInternalSavedObjectsClient } from '../helpers/saved_objects_client';
+import {
+ APM_SERVICES_TELEMETRY_SAVED_OBJECT_TYPE,
+ APM_SERVICES_TELEMETRY_SAVED_OBJECT_ID
+} from '../../../common/apm_saved_object_constants';
+import { LegacySetup } from '../../new-platform/plugin';
+
+export function createApmTelementry(
+ agentNames: string[] = []
+): SavedObjectAttributes {
+ const validAgentNames = agentNames.filter(isAgentName);
+ return {
+ has_any_services: validAgentNames.length > 0,
+ services_per_agent: countBy(validAgentNames)
+ };
+}
+
+export async function storeApmServicesTelemetry(
+ server: Server,
+ apmTelemetry: SavedObjectAttributes
+) {
+ try {
+ const internalSavedObjectsClient = getInternalSavedObjectsClient(server);
+ await internalSavedObjectsClient.create(
+ APM_SERVICES_TELEMETRY_SAVED_OBJECT_TYPE,
+ apmTelemetry,
+ {
+ id: APM_SERVICES_TELEMETRY_SAVED_OBJECT_ID,
+ overwrite: true
+ }
+ );
+ } catch (e) {
+ server.log(['error'], `Unable to save APM telemetry data: ${e.message}`);
+ }
+}
+
+interface LegacySetupWithUsageCollector extends LegacySetup {
+ server: LegacySetup['server'] & {
+ usage: {
+ collectorSet: {
+ makeUsageCollector: (options: unknown) => unknown;
+ register: (options: unknown) => unknown;
+ };
+ };
+ };
+}
+
+export function makeApmUsageCollector(
+ core: CoreSetup,
+ { server }: LegacySetupWithUsageCollector
+) {
+ const apmUsageCollector = server.usage.collectorSet.makeUsageCollector({
+ type: 'apm',
+ fetch: async () => {
+ const internalSavedObjectsClient = getInternalSavedObjectsClient(server);
+ try {
+ const apmTelemetrySavedObject = await internalSavedObjectsClient.get(
+ APM_SERVICES_TELEMETRY_SAVED_OBJECT_TYPE,
+ APM_SERVICES_TELEMETRY_SAVED_OBJECT_ID
+ );
+ return apmTelemetrySavedObject.attributes;
+ } catch (err) {
+ return createApmTelementry();
+ }
+ },
+ isReady: () => true
+ });
+ server.usage.collectorSet.register(apmUsageCollector);
+}
diff --git a/x-pack/legacy/plugins/apm/server/lib/apm_telemetry/make_apm_usage_collector.ts b/x-pack/legacy/plugins/apm/server/lib/apm_telemetry/make_apm_usage_collector.ts
deleted file mode 100644
index 8a91bd8781fe77..00000000000000
--- a/x-pack/legacy/plugins/apm/server/lib/apm_telemetry/make_apm_usage_collector.ts
+++ /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 { InternalCoreSetup } from 'src/core/server';
-import { getSavedObjectsClient } from '../helpers/saved_objects_client';
-import { APM_TELEMETRY_DOC_ID, createApmTelementry } from './apm_telemetry';
-
-export interface CoreSetupWithUsageCollector extends InternalCoreSetup {
- http: InternalCoreSetup['http'] & {
- server: {
- usage: {
- collectorSet: {
- makeUsageCollector: (options: unknown) => unknown;
- register: (options: unknown) => unknown;
- };
- };
- };
- };
-}
-
-export function makeApmUsageCollector(core: CoreSetupWithUsageCollector) {
- const { server } = core.http;
-
- const apmUsageCollector = server.usage.collectorSet.makeUsageCollector({
- type: 'apm',
- fetch: async () => {
- const savedObjectsClient = getSavedObjectsClient(server);
- try {
- const apmTelemetrySavedObject = await savedObjectsClient.get(
- 'apm-telemetry',
- APM_TELEMETRY_DOC_ID
- );
- return apmTelemetrySavedObject.attributes;
- } catch (err) {
- return createApmTelementry();
- }
- },
- isReady: () => true
- });
- server.usage.collectorSet.register(apmUsageCollector);
-}
diff --git a/x-pack/legacy/plugins/apm/server/lib/errors/distribution/__tests__/get_buckets.test.ts b/x-pack/legacy/plugins/apm/server/lib/errors/distribution/__tests__/get_buckets.test.ts
index b7081c43465bf8..5bbd6be14a7082 100644
--- a/x-pack/legacy/plugins/apm/server/lib/errors/distribution/__tests__/get_buckets.test.ts
+++ b/x-pack/legacy/plugins/apm/server/lib/errors/distribution/__tests__/get_buckets.test.ts
@@ -31,6 +31,9 @@ describe('timeseriesFetcher', () => {
client: {
search: clientSpy
} as any,
+ internalClient: {
+ search: clientSpy
+ } as any,
config: {
get: () => 'myIndex' as any,
has: () => true
diff --git a/x-pack/legacy/plugins/apm/server/lib/helpers/es_client.ts b/x-pack/legacy/plugins/apm/server/lib/helpers/es_client.ts
index ee41599454dd69..9c111910f16f9c 100644
--- a/x-pack/legacy/plugins/apm/server/lib/helpers/es_client.ts
+++ b/x-pack/legacy/plugins/apm/server/lib/helpers/es_client.ts
@@ -66,8 +66,12 @@ async function getParamsForSearchRequest(
apmOptions?: APMOptions
) {
const uiSettings = req.getUiSettingsService();
+ const { server } = req;
const [indices, includeFrozen] = await Promise.all([
- getApmIndices(req.server),
+ getApmIndices({
+ config: server.config(),
+ savedObjectsClient: server.savedObjects.getScopedSavedObjectsClient(req)
+ }),
uiSettings.get('search:includeFrozen')
]);
@@ -92,10 +96,23 @@ interface APMOptions {
includeLegacyData: boolean;
}
-export function getESClient(req: Legacy.Request) {
+interface ClientCreateOptions {
+ clientAsInternalUser?: boolean;
+}
+
+export type ESClient = ReturnType;
+
+export function getESClient(
+ req: Legacy.Request,
+ { clientAsInternalUser = false }: ClientCreateOptions = {}
+) {
const cluster = req.server.plugins.elasticsearch.getCluster('data');
const query = req.query as Record;
+ const callMethod = clientAsInternalUser
+ ? cluster.callWithInternalUser.bind(cluster)
+ : cluster.callWithRequest.bind(cluster, req);
+
return {
search: async <
TDocument = unknown,
@@ -121,20 +138,18 @@ export function getESClient(req: Legacy.Request) {
console.log(JSON.stringify(nextParams.body, null, 4));
}
- return (cluster.callWithRequest(
- req,
- 'search',
- nextParams
- ) as unknown) as Promise>;
+ return (callMethod('search', nextParams) as unknown) as Promise<
+ ESSearchResponse
+ >;
},
index: (params: APMIndexDocumentParams) => {
- return cluster.callWithRequest(req, 'index', params);
+ return callMethod('index', params);
},
delete: (params: IndicesDeleteParams) => {
- return cluster.callWithRequest(req, 'delete', params);
+ return callMethod('delete', params);
},
indicesCreate: (params: IndicesCreateParams) => {
- return cluster.callWithRequest(req, 'indices.create', params);
+ return callMethod('indices.create', params);
}
};
}
diff --git a/x-pack/legacy/plugins/apm/server/lib/helpers/saved_objects_client.test.ts b/x-pack/legacy/plugins/apm/server/lib/helpers/saved_objects_client.test.ts
index 3af1d8c706f46f..c685ffdd801dc0 100644
--- a/x-pack/legacy/plugins/apm/server/lib/helpers/saved_objects_client.test.ts
+++ b/x-pack/legacy/plugins/apm/server/lib/helpers/saved_objects_client.test.ts
@@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { getSavedObjectsClient } from './saved_objects_client';
+import { getInternalSavedObjectsClient } from './saved_objects_client';
describe('saved_objects/client', () => {
describe('getSavedObjectsClient', () => {
@@ -31,7 +31,7 @@ describe('saved_objects/client', () => {
});
it('should use internal user "admin"', () => {
- getSavedObjectsClient(server);
+ getInternalSavedObjectsClient(server);
expect(server.plugins.elasticsearch.getCluster).toHaveBeenCalledWith(
'admin'
@@ -39,7 +39,7 @@ describe('saved_objects/client', () => {
});
it('should call getSavedObjectsRepository with a cluster using the internal user context', () => {
- getSavedObjectsClient(server);
+ getInternalSavedObjectsClient(server);
expect(
server.savedObjects.getSavedObjectsRepository
@@ -47,9 +47,9 @@ describe('saved_objects/client', () => {
});
it('should return a SavedObjectsClient initialized with the saved objects internal repository', () => {
- const result = getSavedObjectsClient(server);
+ const internalSavedObjectsClient = getInternalSavedObjectsClient(server);
- expect(result).toBe(savedObjectsClientInstance);
+ expect(internalSavedObjectsClient).toBe(savedObjectsClientInstance);
expect(server.savedObjects.SavedObjectsClient).toHaveBeenCalledWith(
internalRepository
);
diff --git a/x-pack/legacy/plugins/apm/server/lib/helpers/saved_objects_client.ts b/x-pack/legacy/plugins/apm/server/lib/helpers/saved_objects_client.ts
index 81dd8b34c88472..f164ca39d51c0f 100644
--- a/x-pack/legacy/plugins/apm/server/lib/helpers/saved_objects_client.ts
+++ b/x-pack/legacy/plugins/apm/server/lib/helpers/saved_objects_client.ts
@@ -6,7 +6,10 @@
import { Server } from 'hapi';
-export function getSavedObjectsClient(server: Server, clusterName = 'admin') {
+export function getInternalSavedObjectsClient(
+ server: Server,
+ clusterName = 'admin'
+) {
const { SavedObjectsClient, getSavedObjectsRepository } = server.savedObjects;
const { callWithInternalUser } = server.plugins.elasticsearch.getCluster(
clusterName
diff --git a/x-pack/legacy/plugins/apm/server/lib/helpers/setup_request.test.ts b/x-pack/legacy/plugins/apm/server/lib/helpers/setup_request.test.ts
index 57de438be7f2ad..f43b9ea11487a1 100644
--- a/x-pack/legacy/plugins/apm/server/lib/helpers/setup_request.test.ts
+++ b/x-pack/legacy/plugins/apm/server/lib/helpers/setup_request.test.ts
@@ -21,6 +21,7 @@ jest.mock('../settings/apm_indices/get_apm_indices', () => ({
function getMockRequest() {
const callWithRequestSpy = jest.fn();
+ const callWithInternalUserSpy = jest.fn();
const mockRequest = ({
params: {},
query: {},
@@ -28,14 +29,20 @@ function getMockRequest() {
config: () => ({ get: () => 'apm-*' }),
plugins: {
elasticsearch: {
- getCluster: () => ({ callWithRequest: callWithRequestSpy })
+ getCluster: () => ({
+ callWithRequest: callWithRequestSpy,
+ callWithInternalUser: callWithInternalUserSpy
+ })
}
+ },
+ savedObjects: {
+ getScopedSavedObjectsClient: () => ({ get: async () => false })
}
},
getUiSettingsService: () => ({ get: async () => false })
} as any) as Legacy.Request;
- return { callWithRequestSpy, mockRequest };
+ return { callWithRequestSpy, callWithInternalUserSpy, mockRequest };
}
describe('setupRequest', () => {
@@ -57,6 +64,27 @@ describe('setupRequest', () => {
});
});
+ it('should call callWithInternalUser with default args', async () => {
+ const { mockRequest, callWithInternalUserSpy } = getMockRequest();
+ const { internalClient } = await setupRequest(mockRequest);
+ await internalClient.search({
+ index: 'apm-*',
+ body: { foo: 'bar' }
+ } as any);
+ expect(callWithInternalUserSpy).toHaveBeenCalledWith('search', {
+ index: 'apm-*',
+ body: {
+ foo: 'bar',
+ query: {
+ bool: {
+ filter: [{ range: { 'observer.version_major': { gte: 7 } } }]
+ }
+ }
+ },
+ ignore_throttled: true
+ });
+ });
+
describe('observer.version_major filter', () => {
describe('if index is apm-*', () => {
it('should merge `observer.version_major` filter with existing boolean filters', async () => {
diff --git a/x-pack/legacy/plugins/apm/server/lib/helpers/setup_request.ts b/x-pack/legacy/plugins/apm/server/lib/helpers/setup_request.ts
index 3ec519d5e71b57..dcc034287863af 100644
--- a/x-pack/legacy/plugins/apm/server/lib/helpers/setup_request.ts
+++ b/x-pack/legacy/plugins/apm/server/lib/helpers/setup_request.ts
@@ -31,17 +31,21 @@ export type Setup = PromiseReturnType;
export async function setupRequest(req: Legacy.Request) {
const query = (req.query as unknown) as APMRequestQuery;
const { server } = req;
+ const savedObjectsClient = server.savedObjects.getScopedSavedObjectsClient(
+ req
+ );
const config = server.config();
const [uiFiltersES, indices] = await Promise.all([
decodeUiFilters(server, query.uiFilters),
- getApmIndices(server)
+ getApmIndices({ config, savedObjectsClient })
]);
return {
start: moment.utc(query.start).valueOf(),
end: moment.utc(query.end).valueOf(),
uiFiltersES,
- client: getESClient(req),
+ client: getESClient(req, { clientAsInternalUser: false }),
+ internalClient: getESClient(req, { clientAsInternalUser: true }),
config,
indices
};
diff --git a/x-pack/legacy/plugins/apm/server/lib/index_pattern/index.ts b/x-pack/legacy/plugins/apm/server/lib/index_pattern/index.ts
index 0b9407b288b1df..1aff1d772c5c23 100644
--- a/x-pack/legacy/plugins/apm/server/lib/index_pattern/index.ts
+++ b/x-pack/legacy/plugins/apm/server/lib/index_pattern/index.ts
@@ -4,18 +4,21 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { Server } from 'hapi';
-import { getSavedObjectsClient } from '../helpers/saved_objects_client';
+import { getInternalSavedObjectsClient } from '../helpers/saved_objects_client';
import apmIndexPattern from '../../../../../../../src/legacy/core_plugins/kibana/server/tutorials/apm/index_pattern.json';
export async function getAPMIndexPattern(server: Server) {
const config = server.config();
const apmIndexPatternTitle = config.get('apm_oss.indexPattern');
- const savedObjectsClient = getSavedObjectsClient(server);
+ const internalSavedObjectsClient = getInternalSavedObjectsClient(server);
try {
- return await savedObjectsClient.get('index-pattern', apmIndexPattern.id);
+ return await internalSavedObjectsClient.get(
+ 'index-pattern',
+ apmIndexPattern.id
+ );
} catch (error) {
// if GET fails, then create a new index pattern saved object
- return await savedObjectsClient.create(
+ return await internalSavedObjectsClient.create(
'index-pattern',
{
...apmIndexPattern.attributes,
diff --git a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/create_agent_config_index.ts b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/create_agent_config_index.ts
index 861732ee039232..434eda8c0f46eb 100644
--- a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/create_agent_config_index.ts
+++ b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/create_agent_config_index.ts
@@ -4,16 +4,23 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { InternalCoreSetup } from 'src/core/server';
+import { CoreSetup } from 'src/core/server';
import { CallCluster } from '../../../../../../../../src/legacy/core_plugins/elasticsearch';
import { getApmIndices } from '../apm_indices/get_apm_indices';
+import { LegacySetup } from '../../../new-platform/plugin';
+import { getInternalSavedObjectsClient } from '../../helpers/saved_objects_client';
export async function createApmAgentConfigurationIndex(
- core: InternalCoreSetup
+ core: CoreSetup,
+ { server }: LegacySetup
) {
try {
- const { server } = core.http;
- const indices = await getApmIndices(server);
+ const config = server.config();
+ const internalSavedObjectsClient = getInternalSavedObjectsClient(server);
+ const indices = await getApmIndices({
+ savedObjectsClient: internalSavedObjectsClient,
+ config
+ });
const index = indices['apm_oss.apmAgentConfigurationIndex'];
const { callWithInternalUser } = server.plugins.elasticsearch.getCluster(
'admin'
diff --git a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/create_or_update_configuration.ts b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/create_or_update_configuration.ts
index 25a4f5141498f4..23faa4b74cf8fb 100644
--- a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/create_or_update_configuration.ts
+++ b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/create_or_update_configuration.ts
@@ -21,7 +21,7 @@ export async function createOrUpdateConfiguration({
>;
setup: Setup;
}) {
- const { client, indices } = setup;
+ const { internalClient, indices } = setup;
const params: APMIndexDocumentParams = {
refresh: true,
@@ -44,5 +44,5 @@ export async function createOrUpdateConfiguration({
params.id = configurationId;
}
- return client.index(params);
+ return internalClient.index(params);
}
diff --git a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/delete_configuration.ts b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/delete_configuration.ts
index 896363c054ba78..ed20a58b271e10 100644
--- a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/delete_configuration.ts
+++ b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/delete_configuration.ts
@@ -13,7 +13,7 @@ export async function deleteConfiguration({
configurationId: string;
setup: Setup;
}) {
- const { client, indices } = setup;
+ const { internalClient, indices } = setup;
const params = {
refresh: 'wait_for',
@@ -21,5 +21,5 @@ export async function deleteConfiguration({
id: configurationId
};
- return client.delete(params);
+ return internalClient.delete(params);
}
diff --git a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/get_environments/get_existing_environments_for_service.ts b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/get_environments/get_existing_environments_for_service.ts
index d5aa389cea3357..52efc2b50305b9 100644
--- a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/get_environments/get_existing_environments_for_service.ts
+++ b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/get_environments/get_existing_environments_for_service.ts
@@ -19,7 +19,7 @@ export async function getExistingEnvironmentsForService({
serviceName: string | undefined;
setup: Setup;
}) {
- const { client, indices } = setup;
+ const { internalClient, indices } = setup;
const bool = serviceName
? { filter: [{ term: { [SERVICE_NAME]: serviceName } }] }
@@ -42,7 +42,7 @@ export async function getExistingEnvironmentsForService({
}
};
- const resp = await client.search(params);
+ const resp = await internalClient.search(params);
const buckets = idx(resp.aggregations, _ => _.environments.buckets) || [];
return buckets.map(bucket => bucket.key as string);
}
diff --git a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/list_configurations.ts b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/list_configurations.ts
index 283f30b51441d3..dd4d019ef7263b 100644
--- a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/list_configurations.ts
+++ b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/list_configurations.ts
@@ -12,13 +12,13 @@ export type AgentConfigurationListAPIResponse = PromiseReturnType<
typeof listConfigurations
>;
export async function listConfigurations({ setup }: { setup: Setup }) {
- const { client, indices } = setup;
+ const { internalClient, indices } = setup;
const params = {
index: indices['apm_oss.apmAgentConfigurationIndex']
};
- const resp = await client.search(params);
+ const resp = await internalClient.search(params);
return resp.hits.hits.map(item => ({
id: item._id,
...item._source
diff --git a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/mark_applied_by_agent.ts b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/mark_applied_by_agent.ts
index e5349edb67f308..b7b9c21172140a 100644
--- a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/mark_applied_by_agent.ts
+++ b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/mark_applied_by_agent.ts
@@ -16,7 +16,7 @@ export async function markAppliedByAgent({
body: AgentConfiguration;
setup: Setup;
}) {
- const { client, indices } = setup;
+ const { internalClient, indices } = setup;
const params = {
index: indices['apm_oss.apmAgentConfigurationIndex'],
@@ -27,5 +27,5 @@ export async function markAppliedByAgent({
}
};
- return client.index(params);
+ return internalClient.index(params);
}
diff --git a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/search.test.ts b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/search.test.ts
index 400bd0207771af..dcf7329b229d85 100644
--- a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/search.test.ts
+++ b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/search.test.ts
@@ -16,6 +16,7 @@ describe('search configurations', () => {
setup: ({
config: { get: () => '' },
client: { search: async () => searchMocks },
+ internalClient: { search: async () => searchMocks },
indices: {
apm_oss: {
sourcemapIndices: 'myIndex',
@@ -41,6 +42,7 @@ describe('search configurations', () => {
setup: ({
config: { get: () => '' },
client: { search: async () => searchMocks },
+ internalClient: { search: async () => searchMocks },
indices: {
apm_oss: {
sourcemapIndices: 'myIndex',
diff --git a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/search.ts b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/search.ts
index 35d76d745cf4f1..969bbc542f8a6e 100644
--- a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/search.ts
+++ b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/search.ts
@@ -20,7 +20,7 @@ export async function searchConfigurations({
environment?: string;
setup: Setup;
}) {
- const { client, indices } = setup;
+ const { internalClient, indices } = setup;
// sorting order
// 1. exact match: service.name AND service.environment (eg. opbeans-node / production)
@@ -49,7 +49,9 @@ export async function searchConfigurations({
}
};
- const resp = await client.search(params);
+ const resp = await internalClient.search(
+ params
+ );
const { hits } = resp.hits;
const exactMatch = hits.find(
diff --git a/x-pack/legacy/plugins/apm/server/lib/settings/apm_indices/get_apm_indices.ts b/x-pack/legacy/plugins/apm/server/lib/settings/apm_indices/get_apm_indices.ts
index cd237a52640990..e942a26da373ef 100644
--- a/x-pack/legacy/plugins/apm/server/lib/settings/apm_indices/get_apm_indices.ts
+++ b/x-pack/legacy/plugins/apm/server/lib/settings/apm_indices/get_apm_indices.ts
@@ -4,12 +4,14 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { Server } from 'hapi';
import { merge } from 'lodash';
import { KibanaConfig } from 'src/legacy/server/kbn_server';
-import { getSavedObjectsClient } from '../../helpers/saved_objects_client';
-import { Setup } from '../../helpers/setup_request';
+import { Server } from 'hapi';
import { PromiseReturnType } from '../../../../typings/common';
+import {
+ APM_INDICES_SAVED_OBJECT_TYPE,
+ APM_INDICES_SAVED_OBJECT_ID
+} from '../../../../common/apm_saved_object_constants';
export interface ApmIndicesConfig {
'apm_oss.sourcemapIndices': string;
@@ -23,11 +25,13 @@ export interface ApmIndicesConfig {
export type ApmIndicesName = keyof ApmIndicesConfig;
-export const APM_INDICES_SAVED_OBJECT_TYPE = 'apm-indices';
-export const APM_INDICES_SAVED_OBJECT_ID = 'apm-indices';
+export type ScopedSavedObjectsClient = ReturnType<
+ Server['savedObjects']['getScopedSavedObjectsClient']
+>;
-async function getApmIndicesSavedObject(server: Server) {
- const savedObjectsClient = getSavedObjectsClient(server, 'data');
+async function getApmIndicesSavedObject(
+ savedObjectsClient: ScopedSavedObjectsClient
+) {
const apmIndices = await savedObjectsClient.get>(
APM_INDICES_SAVED_OBJECT_TYPE,
APM_INDICES_SAVED_OBJECT_ID
@@ -53,13 +57,21 @@ function getApmIndicesConfig(config: KibanaConfig): ApmIndicesConfig {
};
}
-export async function getApmIndices(server: Server) {
+export async function getApmIndices({
+ savedObjectsClient,
+ config
+}: {
+ savedObjectsClient: ScopedSavedObjectsClient;
+ config: KibanaConfig;
+}) {
try {
- const apmIndicesSavedObject = await getApmIndicesSavedObject(server);
- const apmIndicesConfig = getApmIndicesConfig(server.config());
+ const apmIndicesSavedObject = await getApmIndicesSavedObject(
+ savedObjectsClient
+ );
+ const apmIndicesConfig = getApmIndicesConfig(config);
return merge({}, apmIndicesConfig, apmIndicesSavedObject);
} catch (error) {
- return getApmIndicesConfig(server.config());
+ return getApmIndicesConfig(config);
}
}
@@ -74,16 +86,15 @@ const APM_UI_INDICES: ApmIndicesName[] = [
];
export async function getApmIndexSettings({
- setup,
- server
+ config,
+ savedObjectsClient
}: {
- setup: Setup;
- server: Server;
+ config: KibanaConfig;
+ savedObjectsClient: ScopedSavedObjectsClient;
}) {
- const { config } = setup;
let apmIndicesSavedObject: PromiseReturnType;
try {
- apmIndicesSavedObject = await getApmIndicesSavedObject(server);
+ apmIndicesSavedObject = await getApmIndicesSavedObject(savedObjectsClient);
} catch (error) {
if (error.output && error.output.statusCode === 404) {
apmIndicesSavedObject = {};
diff --git a/x-pack/legacy/plugins/apm/server/lib/settings/apm_indices/save_apm_indices.ts b/x-pack/legacy/plugins/apm/server/lib/settings/apm_indices/save_apm_indices.ts
index 8de47c5c44144f..e57e64942ab898 100644
--- a/x-pack/legacy/plugins/apm/server/lib/settings/apm_indices/save_apm_indices.ts
+++ b/x-pack/legacy/plugins/apm/server/lib/settings/apm_indices/save_apm_indices.ts
@@ -4,19 +4,16 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { Server } from 'hapi';
-import { getSavedObjectsClient } from '../../helpers/saved_objects_client';
+import { ApmIndicesConfig, ScopedSavedObjectsClient } from './get_apm_indices';
import {
- ApmIndicesConfig,
APM_INDICES_SAVED_OBJECT_TYPE,
APM_INDICES_SAVED_OBJECT_ID
-} from './get_apm_indices';
+} from '../../../../common/apm_saved_object_constants';
export async function saveApmIndices(
- server: Server,
+ savedObjectsClient: ScopedSavedObjectsClient,
apmIndicesSavedObject: Partial
) {
- const savedObjectsClient = getSavedObjectsClient(server, 'data');
return await savedObjectsClient.create(
APM_INDICES_SAVED_OBJECT_TYPE,
apmIndicesSavedObject,
diff --git a/x-pack/legacy/plugins/apm/server/lib/transaction_groups/fetcher.test.ts b/x-pack/legacy/plugins/apm/server/lib/transaction_groups/fetcher.test.ts
index 99553690359cf5..ca10183bb259e4 100644
--- a/x-pack/legacy/plugins/apm/server/lib/transaction_groups/fetcher.test.ts
+++ b/x-pack/legacy/plugins/apm/server/lib/transaction_groups/fetcher.test.ts
@@ -13,6 +13,9 @@ function getSetup() {
client: {
search: jest.fn()
} as any,
+ internalClient: {
+ search: jest.fn()
+ } as any,
config: {
get: jest.fn((key: string) => {
switch (key) {
diff --git a/x-pack/legacy/plugins/apm/server/lib/transactions/avg_duration_by_country/index.ts b/x-pack/legacy/plugins/apm/server/lib/transactions/avg_duration_by_country/index.ts
index e092942a25ba65..ed6bdf203f2d44 100644
--- a/x-pack/legacy/plugins/apm/server/lib/transactions/avg_duration_by_country/index.ts
+++ b/x-pack/legacy/plugins/apm/server/lib/transactions/avg_duration_by_country/index.ts
@@ -9,19 +9,26 @@ import {
PROCESSOR_EVENT,
SERVICE_NAME,
TRANSACTION_DURATION,
- TRANSACTION_TYPE
+ TRANSACTION_TYPE,
+ TRANSACTION_NAME
} from '../../../../common/elasticsearch_fieldnames';
import { Setup } from '../../helpers/setup_request';
import { rangeFilter } from '../../helpers/range_filter';
+import { TRANSACTION_PAGE_LOAD } from '../../../../common/transaction_types';
export async function getTransactionAvgDurationByCountry({
setup,
- serviceName
+ serviceName,
+ transactionName
}: {
setup: Setup;
serviceName: string;
+ transactionName?: string;
}) {
const { uiFiltersES, client, start, end, indices } = setup;
+ const transactionNameFilter = transactionName
+ ? [{ term: { [TRANSACTION_NAME]: transactionName } }]
+ : [];
const params = {
index: indices['apm_oss.transactionIndices'],
body: {
@@ -30,8 +37,9 @@ export async function getTransactionAvgDurationByCountry({
bool: {
filter: [
{ term: { [SERVICE_NAME]: serviceName } },
+ ...transactionNameFilter,
{ term: { [PROCESSOR_EVENT]: 'transaction' } },
- { term: { [TRANSACTION_TYPE]: 'page-load' } },
+ { term: { [TRANSACTION_TYPE]: TRANSACTION_PAGE_LOAD } },
{ exists: { field: CLIENT_GEO_COUNTRY_ISO_CODE } },
{ range: rangeFilter(start, end) },
...uiFiltersES
diff --git a/x-pack/legacy/plugins/apm/server/lib/transactions/breakdown/index.test.ts b/x-pack/legacy/plugins/apm/server/lib/transactions/breakdown/index.test.ts
index 67816d67a29a2a..2648851789c662 100644
--- a/x-pack/legacy/plugins/apm/server/lib/transactions/breakdown/index.test.ts
+++ b/x-pack/legacy/plugins/apm/server/lib/transactions/breakdown/index.test.ts
@@ -30,6 +30,7 @@ describe('getTransactionBreakdown', () => {
start: 0,
end: 500000,
client: { search: clientSpy } as any,
+ internalClient: { search: clientSpy } as any,
config: {
get: () => 'myIndex' as any,
has: () => true
@@ -54,6 +55,7 @@ describe('getTransactionBreakdown', () => {
start: 0,
end: 500000,
client: { search: clientSpy } as any,
+ internalClient: { search: clientSpy } as any,
config: {
get: () => 'myIndex' as any,
has: () => true
@@ -95,6 +97,7 @@ describe('getTransactionBreakdown', () => {
start: 0,
end: 500000,
client: { search: clientSpy } as any,
+ internalClient: { search: clientSpy } as any,
config: {
get: () => 'myIndex' as any,
has: () => true
@@ -135,6 +138,7 @@ describe('getTransactionBreakdown', () => {
start: 0,
end: 500000,
client: { search: clientSpy } as any,
+ internalClient: { search: clientSpy } as any,
config: {
get: () => 'myIndex' as any,
has: () => true
@@ -159,6 +163,7 @@ describe('getTransactionBreakdown', () => {
start: 0,
end: 500000,
client: { search: clientSpy } as any,
+ internalClient: { search: clientSpy } as any,
config: {
get: () => 'myIndex' as any,
has: () => true
diff --git a/x-pack/legacy/plugins/apm/server/lib/transactions/breakdown/index.ts b/x-pack/legacy/plugins/apm/server/lib/transactions/breakdown/index.ts
index a21c4f38ac30cc..3d425415de8326 100644
--- a/x-pack/legacy/plugins/apm/server/lib/transactions/breakdown/index.ts
+++ b/x-pack/legacy/plugins/apm/server/lib/transactions/breakdown/index.ts
@@ -151,56 +151,53 @@ export async function getTransactionBreakdown({
const bucketsByDate = idx(resp.aggregations, _ => _.by_date.buckets) || [];
- const timeseriesPerSubtype = bucketsByDate.reduce(
- (prev, bucket) => {
- const formattedValues = formatBucket(bucket);
- const time = bucket.key;
-
- const updatedSeries = kpiNames.reduce((p, kpiName) => {
- const { name, percentage } = formattedValues.find(
- val => val.name === kpiName
- ) || {
- name: kpiName,
- percentage: null
- };
-
- if (!p[name]) {
- p[name] = [];
- }
- return {
- ...p,
- [name]: p[name].concat({
- x: time,
- y: percentage
- })
- };
- }, prev);
-
- const lastValues = Object.values(updatedSeries).map(last);
-
- // If for a given timestamp, some series have data, but others do not,
- // we have to set any null values to 0 to make sure the stacked area chart
- // is drawn correctly.
- // If we set all values to 0, the chart always displays null values as 0,
- // and the chart looks weird.
- const hasAnyValues = lastValues.some(value => value.y !== null);
- const hasNullValues = lastValues.some(value => value.y === null);
-
- if (hasAnyValues && hasNullValues) {
- Object.values(updatedSeries).forEach(series => {
- const value = series[series.length - 1];
- const isEmpty = value.y === null;
- if (isEmpty) {
- // local mutation to prevent complicated map/reduce calls
- value.y = 0;
- }
- });
+ const timeseriesPerSubtype = bucketsByDate.reduce((prev, bucket) => {
+ const formattedValues = formatBucket(bucket);
+ const time = bucket.key;
+
+ const updatedSeries = kpiNames.reduce((p, kpiName) => {
+ const { name, percentage } = formattedValues.find(
+ val => val.name === kpiName
+ ) || {
+ name: kpiName,
+ percentage: null
+ };
+
+ if (!p[name]) {
+ p[name] = [];
}
+ return {
+ ...p,
+ [name]: p[name].concat({
+ x: time,
+ y: percentage
+ })
+ };
+ }, prev);
+
+ const lastValues = Object.values(updatedSeries).map(last);
+
+ // If for a given timestamp, some series have data, but others do not,
+ // we have to set any null values to 0 to make sure the stacked area chart
+ // is drawn correctly.
+ // If we set all values to 0, the chart always displays null values as 0,
+ // and the chart looks weird.
+ const hasAnyValues = lastValues.some(value => value.y !== null);
+ const hasNullValues = lastValues.some(value => value.y === null);
+
+ if (hasAnyValues && hasNullValues) {
+ Object.values(updatedSeries).forEach(series => {
+ const value = series[series.length - 1];
+ const isEmpty = value.y === null;
+ if (isEmpty) {
+ // local mutation to prevent complicated map/reduce calls
+ value.y = 0;
+ }
+ });
+ }
- return updatedSeries;
- },
- {} as Record>
- );
+ return updatedSeries;
+ }, {} as Record>);
const timeseries = kpis.map(kpi => ({
title: kpi.name,
diff --git a/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_anomaly_data/index.test.ts b/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_anomaly_data/index.test.ts
index cddc66e52cf701..3b9e80c901fe92 100644
--- a/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_anomaly_data/index.test.ts
+++ b/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_anomaly_data/index.test.ts
@@ -26,6 +26,7 @@ describe('getAnomalySeries', () => {
start: 0,
end: 500000,
client: { search: clientSpy } as any,
+ internalClient: { search: clientSpy } as any,
config: {
get: () => 'myIndex' as any,
has: () => true
diff --git a/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_timeseries_data/fetcher.test.ts b/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_timeseries_data/fetcher.test.ts
index 5056a100de3ce6..0345b0815679f6 100644
--- a/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_timeseries_data/fetcher.test.ts
+++ b/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_timeseries_data/fetcher.test.ts
@@ -21,6 +21,7 @@ describe('timeseriesFetcher', () => {
start: 1528113600000,
end: 1528977600000,
client: { search: clientSpy } as any,
+ internalClient: { search: clientSpy } as any,
config: {
get: () => 'myIndex' as any,
has: () => true
diff --git a/x-pack/legacy/plugins/apm/server/lib/ui_filters/local_ui_filters/config.ts b/x-pack/legacy/plugins/apm/server/lib/ui_filters/local_ui_filters/config.ts
index 5d10a4ae27060a..a0149bec728c5c 100644
--- a/x-pack/legacy/plugins/apm/server/lib/ui_filters/local_ui_filters/config.ts
+++ b/x-pack/legacy/plugins/apm/server/lib/ui_filters/local_ui_filters/config.ts
@@ -61,17 +61,14 @@ export const localUIFilterNames = Object.keys(
filtersByName
) as LocalUIFilterName[];
-export const localUIFilters = localUIFilterNames.reduce(
- (acc, key) => {
- const field = filtersByName[key];
+export const localUIFilters = localUIFilterNames.reduce((acc, key) => {
+ const field = filtersByName[key];
- return {
- ...acc,
- [key]: {
- ...field,
- name: key
- }
- };
- },
- {} as LocalUIFilterMap
-);
+ return {
+ ...acc,
+ [key]: {
+ ...field,
+ name: key
+ }
+ };
+}, {} as LocalUIFilterMap);
diff --git a/x-pack/legacy/plugins/apm/server/new-platform/plugin.ts b/x-pack/legacy/plugins/apm/server/new-platform/plugin.ts
index 0458c8e4fedf04..e1cb1774469f2a 100644
--- a/x-pack/legacy/plugins/apm/server/new-platform/plugin.ts
+++ b/x-pack/legacy/plugins/apm/server/new-platform/plugin.ts
@@ -4,16 +4,20 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { InternalCoreSetup } from 'src/core/server';
+import { Server } from 'hapi';
+import { CoreSetup } from 'src/core/server';
import { makeApmUsageCollector } from '../lib/apm_telemetry';
-import { CoreSetupWithUsageCollector } from '../lib/apm_telemetry/make_apm_usage_collector';
import { createApmAgentConfigurationIndex } from '../lib/settings/agent_configuration/create_agent_config_index';
import { createApmApi } from '../routes/create_apm_api';
+export interface LegacySetup {
+ server: Server;
+}
+
export class Plugin {
- public setup(core: InternalCoreSetup) {
- createApmApi().init(core);
- createApmAgentConfigurationIndex(core);
- makeApmUsageCollector(core as CoreSetupWithUsageCollector);
+ public setup(core: CoreSetup, __LEGACY: LegacySetup) {
+ createApmApi().init(core, __LEGACY);
+ createApmAgentConfigurationIndex(core, __LEGACY);
+ makeApmUsageCollector(core, __LEGACY);
}
}
diff --git a/x-pack/legacy/plugins/apm/server/routes/create_api/index.test.ts b/x-pack/legacy/plugins/apm/server/routes/create_api/index.test.ts
index b0461f5cb3b68d..18fe547a34cf01 100644
--- a/x-pack/legacy/plugins/apm/server/routes/create_api/index.test.ts
+++ b/x-pack/legacy/plugins/apm/server/routes/create_api/index.test.ts
@@ -5,23 +5,25 @@
*/
import * as t from 'io-ts';
import { createApi } from './index';
-import { InternalCoreSetup } from 'src/core/server';
+import { CoreSetup } from 'src/core/server';
import { Params } from '../typings';
+import { LegacySetup } from '../../new-platform/plugin';
-const getCoreMock = () =>
+const getCoreMock = () => (({} as unknown) as CoreSetup);
+
+const getLegacyMock = () =>
(({
- http: {
- server: {
- route: jest.fn()
- }
+ server: {
+ route: jest.fn()
}
- } as unknown) as InternalCoreSetup & {
- http: { server: { route: ReturnType } };
+ } as unknown) as LegacySetup & {
+ server: { route: ReturnType };
});
describe('createApi', () => {
it('registers a route with the server', () => {
const coreMock = getCoreMock();
+ const legacySetupMock = getLegacyMock();
createApi()
.add(() => ({
@@ -36,11 +38,19 @@ describe('createApi', () => {
},
handler: async () => null
}))
- .init(coreMock);
+ .add(() => ({
+ path: '/baz',
+ method: 'PUT',
+ options: {
+ tags: ['access:apm', 'access:apm_write']
+ },
+ handler: async () => null
+ }))
+ .init(coreMock, legacySetupMock);
- expect(coreMock.http.server.route).toHaveBeenCalledTimes(2);
+ expect(legacySetupMock.server.route).toHaveBeenCalledTimes(3);
- const firstRoute = coreMock.http.server.route.mock.calls[0][0];
+ const firstRoute = legacySetupMock.server.route.mock.calls[0][0];
expect(firstRoute).toEqual({
method: 'GET',
@@ -51,7 +61,7 @@ describe('createApi', () => {
handler: expect.any(Function)
});
- const secondRoute = coreMock.http.server.route.mock.calls[1][0];
+ const secondRoute = legacySetupMock.server.route.mock.calls[1][0];
expect(secondRoute).toEqual({
method: 'POST',
@@ -61,11 +71,23 @@ describe('createApi', () => {
path: '/bar',
handler: expect.any(Function)
});
+
+ const thirdRoute = legacySetupMock.server.route.mock.calls[2][0];
+
+ expect(thirdRoute).toEqual({
+ method: 'PUT',
+ options: {
+ tags: ['access:apm', 'access:apm_write']
+ },
+ path: '/baz',
+ handler: expect.any(Function)
+ });
});
describe('when validating', () => {
const initApi = (params: Params) => {
const core = getCoreMock();
+ const legacySetupMock = getLegacyMock();
const handler = jest.fn();
createApi()
.add(() => ({
@@ -73,9 +95,9 @@ describe('createApi', () => {
params,
handler
}))
- .init(core);
+ .init(core, legacySetupMock);
- const route = core.http.server.route.mock.calls[0][0];
+ const route = legacySetupMock.server.route.mock.calls[0][0];
const routeHandler = route.handler;
diff --git a/x-pack/legacy/plugins/apm/server/routes/create_api/index.ts b/x-pack/legacy/plugins/apm/server/routes/create_api/index.ts
index f969e4d6024ca5..66f28a9bf6d445 100644
--- a/x-pack/legacy/plugins/apm/server/routes/create_api/index.ts
+++ b/x-pack/legacy/plugins/apm/server/routes/create_api/index.ts
@@ -5,7 +5,7 @@
*/
import { merge, pick, omit, difference } from 'lodash';
import Boom from 'boom';
-import { InternalCoreSetup } from 'src/core/server';
+import { CoreSetup } from 'src/core/server';
import { Request, ResponseToolkit } from 'hapi';
import * as t from 'io-ts';
import { PathReporter } from 'io-ts/lib/PathReporter';
@@ -18,6 +18,7 @@ import {
Params
} from '../typings';
import { jsonRt } from '../../../common/runtime_types/json_rt';
+import { LegacySetup } from '../../new-platform/plugin';
const debugRt = t.partial({ _debug: jsonRt.pipe(t.boolean) });
@@ -29,15 +30,14 @@ export function createApi() {
factoryFns.push(fn);
return this as any;
},
- init(core: InternalCoreSetup) {
- const { server } = core.http;
+ init(core: CoreSetup, __LEGACY: LegacySetup) {
+ const { server } = __LEGACY;
factoryFns.forEach(fn => {
- const { params = {}, ...route } = fn(core) as Route<
- string,
- HttpMethod,
- Params,
- any
- >;
+ const {
+ params = {},
+ options = { tags: ['access:apm'] },
+ ...route
+ } = fn(core, __LEGACY) as Route;
const bodyRt = params.body;
const fallbackBodyRt = bodyRt || t.null;
@@ -54,9 +54,7 @@ export function createApi() {
server.route(
merge(
{
- options: {
- tags: ['access:apm']
- },
+ options,
method: 'GET'
},
route,
@@ -70,41 +68,38 @@ export function createApi() {
const parsedParams = (Object.keys(rts) as Array<
keyof typeof rts
- >).reduce(
- (acc, key) => {
- const codec = rts[key];
- const value = paramMap[key];
+ >).reduce((acc, key) => {
+ const codec = rts[key];
+ const value = paramMap[key];
- const result = codec.decode(value);
+ const result = codec.decode(value);
- if (isLeft(result)) {
- throw Boom.badRequest(PathReporter.report(result)[0]);
- }
+ if (isLeft(result)) {
+ throw Boom.badRequest(PathReporter.report(result)[0]);
+ }
- const strippedKeys = difference(
- Object.keys(value || {}),
- Object.keys(result.right || {})
- );
+ const strippedKeys = difference(
+ Object.keys(value || {}),
+ Object.keys(result.right || {})
+ );
- if (strippedKeys.length) {
- throw Boom.badRequest(
- `Unknown keys specified: ${strippedKeys}`
- );
- }
+ if (strippedKeys.length) {
+ throw Boom.badRequest(
+ `Unknown keys specified: ${strippedKeys}`
+ );
+ }
- // hide _debug from route handlers
- const parsedValue =
- key === 'query'
- ? omit(result.right, '_debug')
- : result.right;
+ // hide _debug from route handlers
+ const parsedValue =
+ key === 'query'
+ ? omit(result.right, '_debug')
+ : result.right;
- return {
- ...acc,
- [key]: parsedValue
- };
- },
- {} as Record
- );
+ return {
+ ...acc,
+ [key]: parsedValue
+ };
+ }, {} as Record);
return route.handler(
request,
diff --git a/x-pack/legacy/plugins/apm/server/routes/index_pattern.ts b/x-pack/legacy/plugins/apm/server/routes/index_pattern.ts
index 100df4dc238fe9..92e1284f3ed74e 100644
--- a/x-pack/legacy/plugins/apm/server/routes/index_pattern.ts
+++ b/x-pack/legacy/plugins/apm/server/routes/index_pattern.ts
@@ -9,15 +9,14 @@ import { createRoute } from './create_route';
import { getKueryBarIndexPattern } from '../lib/index_pattern/getKueryBarIndexPattern';
import { setupRequest } from '../lib/helpers/setup_request';
-export const indexPatternRoute = createRoute(core => ({
+export const indexPatternRoute = createRoute((core, { server }) => ({
path: '/api/apm/index_pattern',
handler: async () => {
- const { server } = core.http;
return await getAPMIndexPattern(server);
}
}));
-export const kueryBarIndexPatternRoute = createRoute(core => ({
+export const kueryBarIndexPatternRoute = createRoute(() => ({
path: '/api/apm/kuery_bar_index_pattern',
params: {
query: t.partial({
@@ -30,9 +29,7 @@ export const kueryBarIndexPatternRoute = createRoute(core => ({
},
handler: async (request, { query }) => {
const { processorEvent } = query;
-
const setup = await setupRequest(request);
-
return getKueryBarIndexPattern({ request, processorEvent, setup });
}
}));
diff --git a/x-pack/legacy/plugins/apm/server/routes/services.ts b/x-pack/legacy/plugins/apm/server/routes/services.ts
index 85d53925db86e4..4b955c7a6e981f 100644
--- a/x-pack/legacy/plugins/apm/server/routes/services.ts
+++ b/x-pack/legacy/plugins/apm/server/routes/services.ts
@@ -6,7 +6,10 @@
import * as t from 'io-ts';
import { AgentName } from '../../typings/es_schemas/ui/fields/Agent';
-import { createApmTelementry, storeApmTelemetry } from '../lib/apm_telemetry';
+import {
+ createApmTelementry,
+ storeApmServicesTelemetry
+} from '../lib/apm_telemetry';
import { setupRequest } from '../lib/helpers/setup_request';
import { getServiceAgentName } from '../lib/services/get_service_agent_name';
import { getServices } from '../lib/services/get_services';
@@ -16,7 +19,7 @@ import { createRoute } from './create_route';
import { uiFiltersRt, rangeRt } from './default_api_types';
import { getServiceMap } from '../lib/services/map';
-export const servicesRoute = createRoute(core => ({
+export const servicesRoute = createRoute((core, { server }) => ({
path: '/api/apm/services',
params: {
query: t.intersection([uiFiltersRt, rangeRt])
@@ -24,14 +27,13 @@ export const servicesRoute = createRoute(core => ({
handler: async req => {
const setup = await setupRequest(req);
const services = await getServices(setup);
- const { server } = core.http;
// Store telemetry data derived from services
const agentNames = services.items.map(
({ agentName }) => agentName as AgentName
);
const apmTelemetry = createApmTelementry(agentNames);
- storeApmTelemetry(server, apmTelemetry);
+ storeApmServicesTelemetry(server, apmTelemetry);
return services;
}
diff --git a/x-pack/legacy/plugins/apm/server/routes/settings/agent_configuration.ts b/x-pack/legacy/plugins/apm/server/routes/settings/agent_configuration.ts
index d25ad949d6ddef..2867cef28d952b 100644
--- a/x-pack/legacy/plugins/apm/server/routes/settings/agent_configuration.ts
+++ b/x-pack/legacy/plugins/apm/server/routes/settings/agent_configuration.ts
@@ -31,6 +31,9 @@ export const agentConfigurationRoute = createRoute(core => ({
export const deleteAgentConfigurationRoute = createRoute(() => ({
method: 'DELETE',
path: '/api/apm/settings/agent-configuration/{configurationId}',
+ options: {
+ tags: ['access:apm', 'access:apm_write']
+ },
params: {
path: t.type({
configurationId: t.string
@@ -108,6 +111,9 @@ export const createAgentConfigurationRoute = createRoute(() => ({
params: {
body: agentPayloadRt
},
+ options: {
+ tags: ['access:apm', 'access:apm_write']
+ },
handler: async (req, { body }) => {
const setup = await setupRequest(req);
return await createOrUpdateConfiguration({ configuration: body, setup });
@@ -117,6 +123,9 @@ export const createAgentConfigurationRoute = createRoute(() => ({
export const updateAgentConfigurationRoute = createRoute(() => ({
method: 'PUT',
path: '/api/apm/settings/agent-configuration/{configurationId}',
+ options: {
+ tags: ['access:apm', 'access:apm_write']
+ },
params: {
path: t.type({
configurationId: t.string
diff --git a/x-pack/legacy/plugins/apm/server/routes/settings/apm_indices.ts b/x-pack/legacy/plugins/apm/server/routes/settings/apm_indices.ts
index 3c82a35ec79033..4afcf135a1a76d 100644
--- a/x-pack/legacy/plugins/apm/server/routes/settings/apm_indices.ts
+++ b/x-pack/legacy/plugins/apm/server/routes/settings/apm_indices.ts
@@ -5,7 +5,6 @@
*/
import * as t from 'io-ts';
-import { setupRequest } from '../../lib/helpers/setup_request';
import { createRoute } from '../create_route';
import {
getApmIndices,
@@ -14,28 +13,33 @@ import {
import { saveApmIndices } from '../../lib/settings/apm_indices/save_apm_indices';
// get list of apm indices and values
-export const apmIndexSettingsRoute = createRoute(core => ({
+export const apmIndexSettingsRoute = createRoute((core, { server }) => ({
method: 'GET',
path: '/api/apm/settings/apm-index-settings',
handler: async req => {
- const { server } = core.http;
- const setup = await setupRequest(req);
- return await getApmIndexSettings({ setup, server });
+ const config = server.config();
+ const savedObjectsClient = req.server.savedObjects.getScopedSavedObjectsClient(
+ req
+ );
+ return await getApmIndexSettings({ config, savedObjectsClient });
}
}));
// get apm indices configuration object
-export const apmIndicesRoute = createRoute(core => ({
+export const apmIndicesRoute = createRoute((core, { server }) => ({
method: 'GET',
path: '/api/apm/settings/apm-indices',
handler: async req => {
- const { server } = core.http;
- return await getApmIndices(server);
+ const config = server.config();
+ const savedObjectsClient = req.server.savedObjects.getScopedSavedObjectsClient(
+ req
+ );
+ return await getApmIndices({ config, savedObjectsClient });
}
}));
// save ui indices
-export const saveApmIndicesRoute = createRoute(core => ({
+export const saveApmIndicesRoute = createRoute(() => ({
method: 'POST',
path: '/api/apm/settings/apm-indices/save',
params: {
@@ -50,7 +54,9 @@ export const saveApmIndicesRoute = createRoute(core => ({
})
},
handler: async (req, { body }) => {
- const { server } = core.http;
- return await saveApmIndices(server, body);
+ const savedObjectsClient = req.server.savedObjects.getScopedSavedObjectsClient(
+ req
+ );
+ return await saveApmIndices(savedObjectsClient, body);
}
}));
diff --git a/x-pack/legacy/plugins/apm/server/routes/transaction_groups.ts b/x-pack/legacy/plugins/apm/server/routes/transaction_groups.ts
index cde9fd1dd4ca99..0b5c29fc298578 100644
--- a/x-pack/legacy/plugins/apm/server/routes/transaction_groups.ts
+++ b/x-pack/legacy/plugins/apm/server/routes/transaction_groups.ts
@@ -150,14 +150,20 @@ export const transactionGroupsAvgDurationByCountry = createRoute(() => ({
path: t.type({
serviceName: t.string
}),
- query: t.intersection([uiFiltersRt, rangeRt])
+ query: t.intersection([
+ uiFiltersRt,
+ rangeRt,
+ t.partial({ transactionName: t.string })
+ ])
},
handler: async (req, { path, query }) => {
const setup = await setupRequest(req);
const { serviceName } = path;
+ const { transactionName } = query;
return getTransactionAvgDurationByCountry({
serviceName,
+ transactionName,
setup
});
}
diff --git a/x-pack/legacy/plugins/apm/server/routes/typings.ts b/x-pack/legacy/plugins/apm/server/routes/typings.ts
index a0ddffe044c151..77d96d3677494c 100644
--- a/x-pack/legacy/plugins/apm/server/routes/typings.ts
+++ b/x-pack/legacy/plugins/apm/server/routes/typings.ts
@@ -6,9 +6,10 @@
import t from 'io-ts';
import { Request, ResponseToolkit } from 'hapi';
-import { InternalCoreSetup } from 'src/core/server';
+import { CoreSetup } from 'src/core/server';
import { PickByValue, Optional } from 'utility-types';
import { FetchOptions } from '../../public/services/rest/callApi';
+import { LegacySetup } from '../new-platform/plugin';
export interface Params {
query?: t.HasProps;
@@ -33,6 +34,9 @@ export interface Route<
path: TPath;
method?: TMethod;
params?: TParams;
+ options?: {
+ tags: Array<'access:apm' | 'access:apm_write'>;
+ };
handler: (
req: Request,
params: DecodeParams,
@@ -45,7 +49,10 @@ export type RouteFactoryFn<
TMethod extends HttpMethod | undefined,
TParams extends Params,
TReturn
-> = (core: InternalCoreSetup) => Route;
+> = (
+ core: CoreSetup,
+ __LEGACY: LegacySetup
+) => Route;
export interface RouteState {
[key: string]: {
@@ -76,7 +83,7 @@ export interface ServerAPI {
};
}
>;
- init: (core: InternalCoreSetup) => void;
+ init: (core: CoreSetup, __LEGACY: LegacySetup) => void;
}
// without this, TS does not recognize possible existence of `params` in `options` below
@@ -88,7 +95,9 @@ type GetOptionalParamKeys = keyof PickByValue<
{
[key in keyof TParams]: TParams[key] extends t.PartialType
? false
- : (TParams[key] extends t.Any ? true : false);
+ : TParams[key] extends t.Any
+ ? true
+ : false;
},
false
>;
diff --git a/x-pack/legacy/plugins/apm/server/routes/ui_filters.ts b/x-pack/legacy/plugins/apm/server/routes/ui_filters.ts
index 9d36946d29cf65..36508e53acce76 100644
--- a/x-pack/legacy/plugins/apm/server/routes/ui_filters.ts
+++ b/x-pack/legacy/plugins/apm/server/routes/ui_filters.ts
@@ -45,9 +45,11 @@ export const uiFiltersEnvironmentsRoute = createRoute(() => ({
const filterNamesRt = t.type({
filterNames: jsonRt.pipe(
t.array(
- t.keyof(Object.fromEntries(
- localUIFilterNames.map(filterName => [filterName, null])
- ) as Record)
+ t.keyof(
+ Object.fromEntries(
+ localUIFilterNames.map(filterName => [filterName, null])
+ ) as Record
+ )
)
)
});
diff --git a/x-pack/legacy/plugins/apm/typings/elasticsearch/aggregations.ts b/x-pack/legacy/plugins/apm/typings/elasticsearch/aggregations.ts
index 9f17b0197a5b21..b694e9526e2b88 100644
--- a/x-pack/legacy/plugins/apm/typings/elasticsearch/aggregations.ts
+++ b/x-pack/legacy/plugins/apm/typings/elasticsearch/aggregations.ts
@@ -202,22 +202,19 @@ interface AggregationResponsePart<
TDocument
>
>
- : (TAggregationOptionsMap extends {
+ : TAggregationOptionsMap extends {
filters: {
filters: Record;
};
}
- ? {
- buckets: {
- [key in keyof TAggregationOptionsMap['filters']['filters']]: {
- doc_count: number;
- } & AggregationResponseMap<
- TAggregationOptionsMap['aggs'],
- TDocument
- >;
- };
- }
- : never);
+ ? {
+ buckets: {
+ [key in keyof TAggregationOptionsMap['filters']['filters']]: {
+ doc_count: number;
+ } & AggregationResponseMap;
+ };
+ }
+ : never;
sampler: {
doc_count: number;
} & AggregationResponseMap;
diff --git a/x-pack/legacy/plugins/apm/typings/elasticsearch/index.ts b/x-pack/legacy/plugins/apm/typings/elasticsearch/index.ts
index 56cd0ff23a3fb5..eff39838bd957e 100644
--- a/x-pack/legacy/plugins/apm/typings/elasticsearch/index.ts
+++ b/x-pack/legacy/plugins/apm/typings/elasticsearch/index.ts
@@ -36,8 +36,7 @@ export type ESSearchResponse<
TDocument
>;
}
- : {}) &
- ({
+ : {}) & {
hits: Omit['hits'], 'total'> &
(TOptions['restTotalHitsAsInt'] extends true
? {
@@ -49,7 +48,7 @@ export type ESSearchResponse<
relation: 'eq' | 'gte';
};
});
- });
+ };
export interface ESFilter {
[key: string]: {
diff --git a/x-pack/legacy/plugins/beats_management/common/config_block_validation.ts b/x-pack/legacy/plugins/beats_management/common/config_block_validation.ts
index 92137bdf5cc6e6..8972084018d986 100644
--- a/x-pack/legacy/plugins/beats_management/common/config_block_validation.ts
+++ b/x-pack/legacy/plugins/beats_management/common/config_block_validation.ts
@@ -27,20 +27,17 @@ export const validateConfigurationBlocks = (configurationBlocks: ConfigurationBl
);
}
- const interfaceConfig = blockSchema.configs.reduce(
- (props, config) => {
- if (config.options) {
- props[config.id] = t.keyof(Object.fromEntries(
- config.options.map(opt => [opt.value, null])
- ) as Record);
- } else if (config.validation) {
- props[config.id] = validationMap[config.validation];
- }
+ const interfaceConfig = blockSchema.configs.reduce((props, config) => {
+ if (config.options) {
+ props[config.id] = t.keyof(
+ Object.fromEntries(config.options.map(opt => [opt.value, null])) as Record
+ );
+ } else if (config.validation) {
+ props[config.id] = validationMap[config.validation];
+ }
- return props;
- },
- {} as t.Props
- );
+ return props;
+ }, {} as t.Props);
const runtimeInterface = createConfigurationBlockInterface(
t.literal(blockSchema.id),
diff --git a/x-pack/legacy/plugins/beats_management/common/domain_types.ts b/x-pack/legacy/plugins/beats_management/common/domain_types.ts
index 0d5c67d09da8aa..bc77abc9815be8 100644
--- a/x-pack/legacy/plugins/beats_management/common/domain_types.ts
+++ b/x-pack/legacy/plugins/beats_management/common/domain_types.ts
@@ -13,9 +13,9 @@ export const OutputTypesArray = ['elasticsearch', 'logstash', 'kafka', 'redis'];
// We can also pass in optional params to create spacific runtime checks that
// can be used to validate blocs on the API and UI
export const createConfigurationBlockInterface = (
- configType: t.LiteralType | t.KeyofC> = t.keyof(Object.fromEntries(
- configBlockSchemas.map(s => [s.id, null])
- ) as Record),
+ configType: t.LiteralType | t.KeyofC> = t.keyof(
+ Object.fromEntries(configBlockSchemas.map(s => [s.id, null])) as Record
+ ),
beatConfigInterface: t.Mixed = t.Dictionary
) =>
t.interface(
diff --git a/x-pack/legacy/plugins/beats_management/public/components/autocomplete_field/index.tsx b/x-pack/legacy/plugins/beats_management/public/components/autocomplete_field/index.tsx
index 479be32ce1e127..3ac2ff72c0116d 100644
--- a/x-pack/legacy/plugins/beats_management/public/components/autocomplete_field/index.tsx
+++ b/x-pack/legacy/plugins/beats_management/public/components/autocomplete_field/index.tsx
@@ -265,13 +265,11 @@ const withSuggestionsHidden = (state: AutocompleteFieldState) => ({
selectedIndex: null,
});
-const FixedEuiFieldSearch: React.SFC<
- React.InputHTMLAttributes &
- EuiFieldSearchProps & {
- inputRef?: (element: HTMLInputElement | null) => void;
- onSearch: (value: string) => void;
- }
-> = EuiFieldSearch as any;
+const FixedEuiFieldSearch: React.SFC &
+ EuiFieldSearchProps & {
+ inputRef?: (element: HTMLInputElement | null) => void;
+ onSearch: (value: string) => void;
+ }> = EuiFieldSearch as any;
const AutocompleteContainer = styled.div`
position: relative;
diff --git a/x-pack/legacy/plugins/beats_management/public/components/navigation/breadcrumb/provider.tsx b/x-pack/legacy/plugins/beats_management/public/components/navigation/breadcrumb/provider.tsx
index 8736663dd08af8..f15e08c2ca230a 100644
--- a/x-pack/legacy/plugins/beats_management/public/components/navigation/breadcrumb/provider.tsx
+++ b/x-pack/legacy/plugins/beats_management/public/components/navigation/breadcrumb/provider.tsx
@@ -53,16 +53,13 @@ export class BreadcrumbProvider extends Component {
- if (crumbStorageItem.parents) {
- crumbs = crumbs.concat(crumbStorageItem.parents);
- }
- crumbs.push(crumbStorageItem.breadcrumb);
- return crumbs;
- },
- [] as Breadcrumb[]
- ),
+ breadcrumbs: breadcrumbs.reduce((crumbs, crumbStorageItem) => {
+ if (crumbStorageItem.parents) {
+ crumbs = crumbs.concat(crumbStorageItem.parents);
+ }
+ crumbs.push(crumbStorageItem.breadcrumb);
+ return crumbs;
+ }, [] as Breadcrumb[]),
addCrumb: this.addCrumb,
removeCrumb: this.removeCrumb,
};
diff --git a/x-pack/legacy/plugins/beats_management/public/components/table/table_type_configs.tsx b/x-pack/legacy/plugins/beats_management/public/components/table/table_type_configs.tsx
index a93000a3e80f04..6f03f884563e16 100644
--- a/x-pack/legacy/plugins/beats_management/public/components/table/table_type_configs.tsx
+++ b/x-pack/legacy/plugins/beats_management/public/components/table/table_type_configs.tsx
@@ -246,7 +246,10 @@ export const BeatsTableType: TableType = {
name: i18n.translate('xpack.beatsManagement.beatsTable.typeLabel', {
defaultMessage: 'Type',
}),
- options: uniq(data.map(({ type }: { type: any }) => ({ value: type })), 'value'),
+ options: uniq(
+ data.map(({ type }: { type: any }) => ({ value: type })),
+ 'value'
+ ),
},
],
}),
diff --git a/x-pack/legacy/plugins/beats_management/public/lib/adapters/beats/rest_beats_adapter.ts b/x-pack/legacy/plugins/beats_management/public/lib/adapters/beats/rest_beats_adapter.ts
index 4a7f768bd47408..b2e11461007fd2 100644
--- a/x-pack/legacy/plugins/beats_management/public/lib/adapters/beats/rest_beats_adapter.ts
+++ b/x-pack/legacy/plugins/beats_management/public/lib/adapters/beats/rest_beats_adapter.ts
@@ -26,9 +26,9 @@ export class RestBeatsAdapter implements CMBeatsAdapter {
public async getBeatWithToken(enrollmentToken: string): Promise {
try {
- return (await this.REST.get>(
- `/api/beats/agent/unknown/${enrollmentToken}`
- )).item;
+ return (
+ await this.REST.get>(`/api/beats/agent/unknown/${enrollmentToken}`)
+ ).item;
} catch (e) {
return null;
}
@@ -59,16 +59,20 @@ export class RestBeatsAdapter implements CMBeatsAdapter {
public async removeTagsFromBeats(
removals: BeatsTagAssignment[]
): Promise {
- return (await this.REST.post(`/api/beats/agents_tags/removals`, {
- removals,
- })).results;
+ return (
+ await this.REST.post(`/api/beats/agents_tags/removals`, {
+ removals,
+ })
+ ).results;
}
public async assignTagsToBeats(
assignments: BeatsTagAssignment[]
): Promise {
- return (await this.REST.post(`/api/beats/agents_tags/assignments`, {
- assignments,
- })).results;
+ return (
+ await this.REST.post(`/api/beats/agents_tags/assignments`, {
+ assignments,
+ })
+ ).results;
}
}
diff --git a/x-pack/legacy/plugins/beats_management/public/lib/adapters/tags/rest_tags_adapter.ts b/x-pack/legacy/plugins/beats_management/public/lib/adapters/tags/rest_tags_adapter.ts
index 31c55b92721936..190c9e265463dc 100644
--- a/x-pack/legacy/plugins/beats_management/public/lib/adapters/tags/rest_tags_adapter.ts
+++ b/x-pack/legacy/plugins/beats_management/public/lib/adapters/tags/rest_tags_adapter.ts
@@ -20,9 +20,9 @@ export class RestTagsAdapter implements CMTagsAdapter {
public async getTagsWithIds(tagIds: string[]): Promise {
try {
- return (await this.REST.get>(
- `/api/beats/tags/${uniq(tagIds).join(',')}`
- )).items;
+ return (
+ await this.REST.get>(`/api/beats/tags/${uniq(tagIds).join(',')}`)
+ ).items;
} catch (e) {
return [];
}
@@ -37,9 +37,9 @@ export class RestTagsAdapter implements CMTagsAdapter {
}
public async delete(tagIds: string[]): Promise {
- return (await this.REST.delete(
- `/api/beats/tags/${uniq(tagIds).join(',')}`
- )).success;
+ return (
+ await this.REST.delete(`/api/beats/tags/${uniq(tagIds).join(',')}`)
+ ).success;
}
public async upsertTag(tag: BeatTag): Promise {
@@ -53,9 +53,11 @@ export class RestTagsAdapter implements CMTagsAdapter {
public async getAssignable(beats: CMBeat[]) {
try {
- return (await this.REST.get>(
- `/api/beats/tags/assignable/${beats.map(beat => beat.id).join(',')}`
- )).items;
+ return (
+ await this.REST.get>(
+ `/api/beats/tags/assignable/${beats.map(beat => beat.id).join(',')}`
+ )
+ ).items;
} catch (e) {
return [];
}
diff --git a/x-pack/legacy/plugins/beats_management/public/lib/adapters/tokens/rest_tokens_adapter.ts b/x-pack/legacy/plugins/beats_management/public/lib/adapters/tokens/rest_tokens_adapter.ts
index 8274764e759aba..92cfcc935ad9b0 100644
--- a/x-pack/legacy/plugins/beats_management/public/lib/adapters/tokens/rest_tokens_adapter.ts
+++ b/x-pack/legacy/plugins/beats_management/public/lib/adapters/tokens/rest_tokens_adapter.ts
@@ -12,12 +12,11 @@ export class RestTokensAdapter implements CMTokensAdapter {
constructor(private readonly REST: RestAPIAdapter) {}
public async createEnrollmentTokens(numTokens: number = 1): Promise {
- const results = (await this.REST.post>(
- '/api/beats/enrollment_tokens',
- {
+ const results = (
+ await this.REST.post>('/api/beats/enrollment_tokens', {
num_tokens: numTokens,
- }
- )).results;
+ })
+ ).results;
return results.map(result => result.item);
}
}
diff --git a/x-pack/legacy/plugins/beats_management/public/lib/compose/scripts.ts b/x-pack/legacy/plugins/beats_management/public/lib/compose/scripts.ts
index 4718abc1b71537..83129384a77dfa 100644
--- a/x-pack/legacy/plugins/beats_management/public/lib/compose/scripts.ts
+++ b/x-pack/legacy/plugins/beats_management/public/lib/compose/scripts.ts
@@ -22,7 +22,11 @@ import { FrontendLibs } from '../types';
export function compose(basePath: string): FrontendLibs {
const api = new NodeAxiosAPIAdapter('elastic', 'changeme', basePath);
- const esAdapter = new MemoryElasticsearchAdapter(() => true, () => '', []);
+ const esAdapter = new MemoryElasticsearchAdapter(
+ () => true,
+ () => '',
+ []
+ );
const elasticsearchLib = new ElasticsearchLib(esAdapter);
const configBlocks = new ConfigBlocksLib(
new RestConfigBlocksAdapter(api),
diff --git a/x-pack/legacy/plugins/beats_management/server/lib/tags.ts b/x-pack/legacy/plugins/beats_management/server/lib/tags.ts
index cfcc2c4eda3979..df9c65034115bb 100644
--- a/x-pack/legacy/plugins/beats_management/server/lib/tags.ts
+++ b/x-pack/legacy/plugins/beats_management/server/lib/tags.ts
@@ -40,15 +40,12 @@ export class CMTagsDomain {
public async getNonConflictingTags(user: FrameworkUser, existingTagIds: string[]) {
const tags = await this.adapter.getTagsWithIds(user, existingTagIds);
const existingUniqueBlockTypes = uniq(
- tags.reduce(
- (existingUniqueTypes, tag) => {
- if (tag.hasConfigurationBlocksTypes) {
- existingUniqueTypes = existingUniqueTypes.concat(tag.hasConfigurationBlocksTypes);
- }
- return existingUniqueTypes;
- },
- [] as string[]
- )
+ tags.reduce((existingUniqueTypes, tag) => {
+ if (tag.hasConfigurationBlocksTypes) {
+ existingUniqueTypes = existingUniqueTypes.concat(tag.hasConfigurationBlocksTypes);
+ }
+ return existingUniqueTypes;
+ }, [] as string[])
).filter(type => UNIQUENESS_ENFORCING_TYPES.includes(type));
const safeTags = await this.adapter.getWithoutConfigTypes(user, existingUniqueBlockTypes);
diff --git a/x-pack/legacy/plugins/beats_management/server/rest_api/__tests__/beats_assignments.test.ts b/x-pack/legacy/plugins/beats_management/server/rest_api/__tests__/beats_assignments.test.ts
index bb0376ab87d290..156304443431d1 100644
--- a/x-pack/legacy/plugins/beats_management/server/rest_api/__tests__/beats_assignments.test.ts
+++ b/x-pack/legacy/plugins/beats_management/server/rest_api/__tests__/beats_assignments.test.ts
@@ -73,7 +73,10 @@ describe('assign_tags_to_beats', () => {
authorization: 'loggedin',
},
payload: {
- assignments: [{ beatId: 'foo', tag: 'development' }, { beatId: 'bar', tag: 'development' }],
+ assignments: [
+ { beatId: 'foo', tag: 'development' },
+ { beatId: 'bar', tag: 'development' },
+ ],
},
});
@@ -115,7 +118,10 @@ describe('assign_tags_to_beats', () => {
authorization: 'loggedin',
},
payload: {
- assignments: [{ beatId: 'bar', tag: 'development' }, { beatId: 'bar', tag: 'production' }],
+ assignments: [
+ { beatId: 'bar', tag: 'development' },
+ { beatId: 'bar', tag: 'production' },
+ ],
},
});
diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/browser/location.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/browser/location.ts
index c80c37bb1ed560..e592c6d22ef737 100644
--- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/browser/location.ts
+++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/browser/location.ts
@@ -32,7 +32,10 @@ export function location(): ExpressionFunction<'location', null, {}, Promise {
const fn = functionWrapper(csv);
const expected = {
type: 'datatable',
- columns: [{ name: 'name', type: 'string' }, { name: 'number', type: 'string' }],
+ columns: [
+ { name: 'name', type: 'string' },
+ { name: 'number', type: 'string' },
+ ],
rows: [
{ name: 'one', number: '1' },
{ name: 'two', number: '2' },
diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/get_flot_axis_config.test.js b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/get_flot_axis_config.test.js
index bfc3212fa234ca..a5b65e6bdc62b9 100644
--- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/get_flot_axis_config.test.js
+++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/get_flot_axis_config.test.js
@@ -84,10 +84,16 @@ describe('getFlotAxisConfig', () => {
describe('ticks', () => {
it('adds a tick mark mapping for string columns', () => {
let result = getFlotAxisConfig('x', true, { columns, ticks });
- expect(result.ticks).toEqual([[2, 'product1'], [1, 'product2']]);
+ expect(result.ticks).toEqual([
+ [2, 'product1'],
+ [1, 'product2'],
+ ]);
result = getFlotAxisConfig('x', xAxisConfig, { columns, ticks });
- expect(result.ticks).toEqual([[2, 'product1'], [1, 'product2']]);
+ expect(result.ticks).toEqual([
+ [2, 'product1'],
+ [1, 'product2'],
+ ]);
});
});
diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/get_tick_hash.test.js b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/get_tick_hash.test.js
index f44d37abe166f9..74c13be3abd7b9 100644
--- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/get_tick_hash.test.js
+++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/get_tick_hash.test.js
@@ -30,7 +30,12 @@ describe('getTickHash', () => {
x: { type: 'number', role: 'dimension', expression: 'id' },
y: { type: 'boolean', role: 'dimension', expression: 'running' },
};
- const rows = [{ x: 1, y: true }, { x: 2, y: true }, { x: 1, y: false }, { x: 2, y: false }];
+ const rows = [
+ { x: 1, y: true },
+ { x: 2, y: true },
+ { x: 1, y: false },
+ { x: 2, y: false },
+ ];
expect(getTickHash(columns, rows)).toEqual({
x: { hash: {}, counter: 0 },
diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/math.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/math.ts
index c180b6186ee921..718f2d8b11e1a9 100644
--- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/math.ts
+++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/math.ts
@@ -44,7 +44,10 @@ export function math(): ExpressionFunction<'math', Context, Arguments, number> {
}
const mathContext = isDatatable(context)
- ? pivotObjectArray(context.rows, context.columns.map(col => col.name))
+ ? pivotObjectArray(
+ context.rows,
+ context.columns.map(col => col.name)
+ )
: { value: context };
try {
diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/server/pointseries/index.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/server/pointseries/index.ts
index 5889864df3c1df..5d5c3c1a735a02 100644
--- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/server/pointseries/index.ts
+++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/server/pointseries/index.ts
@@ -187,7 +187,10 @@ export function pointseries(): ExpressionFunction<
// Then compute that 1 value for each measure
Object.values(measureKeys).forEach(valueRows => {
const subtable = { type: 'datatable', columns: context.columns, rows: valueRows };
- const subScope = pivotObjectArray(subtable.rows, subtable.columns.map(col => col.name));
+ const subScope = pivotObjectArray(
+ subtable.rows,
+ subtable.columns.map(col => col.name)
+ );
const measureValues = measureNames.map(measure => {
try {
const ev = evaluate(args[measure], subScope);
diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/pie/plugins/pie.js b/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/pie/plugins/pie.js
index 9229def146f8b0..042cbe83fb10fc 100644
--- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/pie/plugins/pie.js
+++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/pie/plugins/pie.js
@@ -683,7 +683,14 @@ function init(plot) {
const p4Y = radius * Math.sin(s.startAngle + s.angle / 1.5);
const p5X = radius * Math.cos(s.startAngle + s.angle);
const p5Y = radius * Math.sin(s.startAngle + s.angle);
- const arrPoly = [[0, 0], [p1X, p1Y], [p2X, p2Y], [p3X, p3Y], [p4X, p4Y], [p5X, p5Y]];
+ const arrPoly = [
+ [0, 0],
+ [p1X, p1Y],
+ [p2X, p2Y],
+ [p3X, p3Y],
+ [p4X, p4Y],
+ [p5X, p5Y],
+ ];
const arrPoint = [x, y];
// TODO: perhaps do some mathmatical trickery here with the Y-coordinate to compensate for pie tilt?
diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/axis_config/__examples__/__snapshots__/simple_template.examples.storyshot b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/axis_config/__examples__/__snapshots__/simple_template.examples.storyshot
index bf68d217f18abb..0b9358714e71c6 100644
--- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/axis_config/__examples__/__snapshots__/simple_template.examples.storyshot
+++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/axis_config/__examples__/__snapshots__/simple_template.examples.storyshot
@@ -13,23 +13,26 @@ exports[`Storyshots arguments/AxisConfig simple 1`] = `