): 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 & {
@@ -1492,6 +1462,8 @@ export interface SavedObjectsLegacyService {
// (undocumented)
schema: SavedObjectsSchema;
// (undocumented)
+ setScopedSavedObjectsClientFactory: SavedObjectsClientProvider['setClientFactory'];
+ // (undocumented)
types: string[];
}
diff --git a/src/core/server/server.test.ts b/src/core/server/server.test.ts
index aee6461580654..f912a31901ad8 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 46974e204c7a4..6c38de03f0f2d 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 423ff2a1dfd90..1a0f29f6ae6d9 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 022c3e4330032..775c890675410 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 0d9b2a6129de0..315ae3328c47f 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 aead3f35ba841..8e5d9f4860d95 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 77854f9af680b..08288343d9077 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 3ae7d33d24d68..5c0462ce86fa9 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 0926ef365c894..6609b905b81ec 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 0c785a84bb469..f5c20da89dcfa 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 8fe09db0a5874..9142955185a1a 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/es_archiver/lib/indices/__tests__/create_index_stream.js b/src/es_archiver/lib/indices/__tests__/create_index_stream.js
index 830512f3476ed..4ce12ab3376a3 100644
--- a/src/es_archiver/lib/indices/__tests__/create_index_stream.js
+++ b/src/es_archiver/lib/indices/__tests__/create_index_stream.js
@@ -113,7 +113,6 @@ describe('esArchiver: createCreateIndexStream()', () => {
sinon.assert.calledWith(client.indices.create, {
method: 'PUT',
index: 'index',
- include_type_name: false,
body: {
settings: undefined,
mappings: undefined,
diff --git a/src/es_archiver/lib/indices/create_index_stream.js b/src/es_archiver/lib/indices/create_index_stream.js
index 746f0d689ce56..0daccbee91bd0 100644
--- a/src/es_archiver/lib/indices/create_index_stream.js
+++ b/src/es_archiver/lib/indices/create_index_stream.js
@@ -41,9 +41,6 @@ export function createCreateIndexStream({ client, stats, skipExisting, log }) {
async function handleIndex(record) {
const { index, settings, mappings, aliases } = record.value;
-
- // Determine if the mapping belongs to a pre-7.0 instance, for BWC tests, mainly
- const isPre7Mapping = !!mappings && Object.keys(mappings).length > 0 && !mappings.properties;
const isKibana = index.startsWith('.kibana');
async function attemptToCreate(attemptNumber = 1) {
@@ -55,7 +52,6 @@ export function createCreateIndexStream({ client, stats, skipExisting, log }) {
await client.indices.create({
method: 'PUT',
index,
- include_type_name: isPre7Mapping,
body: {
settings,
mappings,
diff --git a/src/legacy/core_plugins/console/server/api_server/spec/generated/indices.create.json b/src/legacy/core_plugins/console/server/api_server/spec/generated/indices.create.json
index 1970f88b30958..8227e38d3c6d9 100644
--- a/src/legacy/core_plugins/console/server/api_server/spec/generated/indices.create.json
+++ b/src/legacy/core_plugins/console/server/api_server/spec/generated/indices.create.json
@@ -1,7 +1,6 @@
{
"indices.create": {
"url_params": {
- "include_type_name": "__flag__",
"wait_for_active_shards": "",
"timeout": "",
"master_timeout": ""
diff --git a/src/legacy/core_plugins/console/server/api_server/spec/generated/indices.get.json b/src/legacy/core_plugins/console/server/api_server/spec/generated/indices.get.json
index f515e73b250a7..7ca9e88274aa5 100644
--- a/src/legacy/core_plugins/console/server/api_server/spec/generated/indices.get.json
+++ b/src/legacy/core_plugins/console/server/api_server/spec/generated/indices.get.json
@@ -1,7 +1,6 @@
{
"indices.get": {
"url_params": {
- "include_type_name": "__flag__",
"local": "__flag__",
"ignore_unavailable": "__flag__",
"allow_no_indices": "__flag__",
diff --git a/src/legacy/core_plugins/console/server/api_server/spec/generated/indices.get_field_mapping.json b/src/legacy/core_plugins/console/server/api_server/spec/generated/indices.get_field_mapping.json
index 362b266ecb183..ea952435566ed 100644
--- a/src/legacy/core_plugins/console/server/api_server/spec/generated/indices.get_field_mapping.json
+++ b/src/legacy/core_plugins/console/server/api_server/spec/generated/indices.get_field_mapping.json
@@ -1,7 +1,6 @@
{
"indices.get_field_mapping": {
"url_params": {
- "include_type_name": "__flag__",
"include_defaults": "__flag__",
"ignore_unavailable": "__flag__",
"allow_no_indices": "__flag__",
diff --git a/src/legacy/core_plugins/console/server/api_server/spec/generated/indices.get_template.json b/src/legacy/core_plugins/console/server/api_server/spec/generated/indices.get_template.json
index d5f52ec76b374..f5902929c25cc 100644
--- a/src/legacy/core_plugins/console/server/api_server/spec/generated/indices.get_template.json
+++ b/src/legacy/core_plugins/console/server/api_server/spec/generated/indices.get_template.json
@@ -1,7 +1,6 @@
{
"indices.get_template": {
"url_params": {
- "include_type_name": "__flag__",
"flat_settings": "__flag__",
"master_timeout": "",
"local": "__flag__"
diff --git a/src/legacy/core_plugins/console/server/api_server/spec/generated/indices.put_template.json b/src/legacy/core_plugins/console/server/api_server/spec/generated/indices.put_template.json
index 8b3480f24d8fb..54a7625a2713c 100644
--- a/src/legacy/core_plugins/console/server/api_server/spec/generated/indices.put_template.json
+++ b/src/legacy/core_plugins/console/server/api_server/spec/generated/indices.put_template.json
@@ -1,7 +1,6 @@
{
"indices.put_template": {
"url_params": {
- "include_type_name": "__flag__",
"order": "",
"create": "__flag__",
"timeout": "",
diff --git a/src/legacy/core_plugins/console/server/api_server/spec/generated/indices.rollover.json b/src/legacy/core_plugins/console/server/api_server/spec/generated/indices.rollover.json
index 7fa76a687eb77..19e0f1f909ab8 100644
--- a/src/legacy/core_plugins/console/server/api_server/spec/generated/indices.rollover.json
+++ b/src/legacy/core_plugins/console/server/api_server/spec/generated/indices.rollover.json
@@ -1,7 +1,6 @@
{
"indices.rollover": {
"url_params": {
- "include_type_name": "__flag__",
"timeout": "",
"dry_run": "__flag__",
"master_timeout": "",
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 5333aff8b87da..9e2478cb0704e 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 0000000000000..a5fac10e4693f
--- /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 0000000000000..21ba32ec6a6fe
--- /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 5dd5c05647789..84da576e8205c 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 dbff5096f2287..7ee3e375c0967 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 60828b4a2a202..c3892fa581fc4 100644
--- a/src/legacy/core_plugins/data/public/index.ts
+++ b/src/legacy/core_plugins/data/public/index.ts
@@ -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 4767b6d3a3ca7..2c58af9deaf49 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 bdeeb787c983d..9ce1b5f2e4a20 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 62f5ddbe9e2b0..1c877f4f14251 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/query/query_bar/components/query_bar_input.tsx b/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar_input.tsx
index 5576427b1592a..77c9169d03aa4 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
@@ -42,6 +42,7 @@ import {
import {
withKibana,
KibanaReactContextValue,
+ toMountPoint,
} from '../../../../../../../plugins/kibana_react/public';
import { IndexPattern, StaticIndexPattern } from '../../../index_patterns';
import { Query, getQueryLog } from '../index';
@@ -361,7 +362,7 @@ export class QueryBarInputUI extends Component {
id: 'data.query.queryBar.KQLNestedQuerySyntaxInfoTitle',
defaultMessage: 'KQL nested query syntax',
}),
- text: (
+ text: toMountPoint(
{
['className', { watchDepth: 'reference' }],
['pluginDataStart', { watchDepth: 'reference' }],
]);
- })
- .directive('applyFiltersPopover', () => {
- return {
- restrict: 'E',
- template: '',
- compile: (elem: any) => {
- const child = document.createElement('apply-filters-popover-helper');
-
- // Copy attributes to the child directive
- for (const attr of elem[0].attributes) {
- child.setAttribute(attr.name, attr.value);
- }
-
- // Add a key attribute that will force a full rerender every time that
- // a filter changes.
- child.setAttribute('key', 'key');
-
- // Append helper directive
- elem.append(child);
-
- const linkFn = ($scope: any, _: any, $attr: any) => {
- // Watch only for filter changes to update key.
- $scope.$watch(
- () => {
- return $scope.$eval($attr.filters) || [];
- },
- (newVal: any) => {
- $scope.key = Date.now();
- },
- true
- );
- };
-
- return linkFn;
- },
- };
- })
- .directive('applyFiltersPopoverHelper', (reactDirective: any) =>
- reactDirective(wrapInI18nContext(ApplyFiltersPopover), [
- ['filters', { watchDepth: 'collection' }],
- ['onCancel', { watchDepth: 'reference' }],
- ['onSubmit', { watchDepth: 'reference' }],
- ['indexPatterns', { watchDepth: 'collection' }],
-
- // Key is needed to trigger a full rerender of the component
- 'key',
- ])
- );
+ });
uiModules.get('kibana/index_patterns').value('indexPatterns', indexPatterns);
});
diff --git a/src/legacy/core_plugins/expressions/public/np_ready/public/execute.ts b/src/legacy/core_plugins/expressions/public/np_ready/public/execute.ts
index 8043e0fb6e3f9..45d5c07cd1b26 100644
--- a/src/legacy/core_plugins/expressions/public/np_ready/public/execute.ts
+++ b/src/legacy/core_plugins/expressions/public/np_ready/public/execute.ts
@@ -17,7 +17,7 @@
* under the License.
*/
-import { fromExpression } from '@kbn/interpreter/target/common';
+import { fromExpression, toExpression } from '@kbn/interpreter/target/common';
import { DataAdapter, RequestAdapter, Adapters } from '../../../../../../plugins/inspector/public';
import { getInterpreter } from './services';
import { ExpressionAST, IExpressionLoaderParams, IInterpreterResult } from './types';
@@ -38,17 +38,18 @@ export class ExpressionDataHandler {
private inspectorAdapters: Adapters;
private promise: Promise;
+ public isPending: boolean = true;
constructor(expression: string | ExpressionAST, params: IExpressionLoaderParams) {
if (typeof expression === 'string') {
this.expression = expression;
this.ast = fromExpression(expression) as ExpressionAST;
} else {
this.ast = expression;
- this.expression = '';
+ this.expression = toExpression(this.ast);
}
this.abortController = new AbortController();
- this.inspectorAdapters = this.getActiveInspectorAdapters();
+ this.inspectorAdapters = params.inspectorAdapters || this.getActiveInspectorAdapters();
const getInitialContext = () => ({
type: 'kibana_context',
@@ -58,11 +59,21 @@ export class ExpressionDataHandler {
const defaultContext = { type: 'null' };
const interpreter = getInterpreter();
- this.promise = interpreter.interpretAst(this.ast, params.context || defaultContext, {
- getInitialContext,
- inspectorAdapters: this.inspectorAdapters,
- abortSignal: this.abortController.signal,
- });
+ this.promise = interpreter
+ .interpretAst(this.ast, params.context || defaultContext, {
+ getInitialContext,
+ inspectorAdapters: this.inspectorAdapters,
+ abortSignal: this.abortController.signal,
+ })
+ .then(
+ (v: IInterpreterResult) => {
+ this.isPending = false;
+ return v;
+ },
+ () => {
+ this.isPending = false;
+ }
+ );
}
cancel = () => {
diff --git a/src/legacy/core_plugins/expressions/public/np_ready/public/loader.test.ts b/src/legacy/core_plugins/expressions/public/np_ready/public/loader.test.ts
index a3caa1c47b150..4c3bc76af351d 100644
--- a/src/legacy/core_plugins/expressions/public/np_ready/public/loader.test.ts
+++ b/src/legacy/core_plugins/expressions/public/np_ready/public/loader.test.ts
@@ -67,7 +67,7 @@ describe('execute helper function', () => {
});
describe('ExpressionLoader', () => {
- const expressionString = '';
+ const expressionString = 'demodata';
describe('constructor', () => {
it('accepts expression string', () => {
@@ -134,6 +134,8 @@ describe('ExpressionLoader', () => {
(ExpressionDataHandler as jest.Mock).mockImplementationOnce(() => ({
getData: () => true,
cancel: cancelMock,
+ isPending: () => true,
+ inspect: () => {},
}));
const expressionLoader = new ExpressionLoader(element, expressionString, {});
@@ -160,10 +162,15 @@ describe('ExpressionLoader', () => {
(ExpressionDataHandler as jest.Mock).mockImplementationOnce(() => ({
getData,
cancel: cancelMock,
+ isPending: () => true,
+ inspect: () => {},
}));
+
(ExpressionDataHandler as jest.Mock).mockImplementationOnce(() => ({
getData,
cancel: cancelMock,
+ isPending: () => true,
+ inspect: () => {},
}));
const expressionLoader = new ExpressionLoader(element, expressionString, {});
@@ -193,6 +200,8 @@ describe('ExpressionLoader', () => {
(ExpressionDataHandler as jest.Mock).mockImplementationOnce(() => ({
getData,
cancel: cancelMock,
+ isPending: () => true,
+ inspect: () => {},
}));
const expressionLoader = new ExpressionLoader(element, expressionString, {});
diff --git a/src/legacy/core_plugins/expressions/public/np_ready/public/loader.ts b/src/legacy/core_plugins/expressions/public/np_ready/public/loader.ts
index 709fbc78a9b52..2213cd30010b2 100644
--- a/src/legacy/core_plugins/expressions/public/np_ready/public/loader.ts
+++ b/src/legacy/core_plugins/expressions/public/np_ready/public/loader.ts
@@ -38,11 +38,12 @@ export class ExpressionLoader {
private loadingSubject: Subject;
private data: Data;
private params: IExpressionLoaderParams = {};
+ private ignoreNextResponse = false;
constructor(
element: HTMLElement,
- expression: string | ExpressionAST,
- params: IExpressionLoaderParams
+ expression?: string | ExpressionAST,
+ params?: IExpressionLoaderParams
) {
this.dataSubject = new Subject();
this.data$ = this.dataSubject.asObservable().pipe(share());
@@ -65,7 +66,9 @@ export class ExpressionLoader {
this.setParams(params);
- this.loadData(expression, this.params);
+ if (expression) {
+ this.loadData(expression, this.params);
+ }
}
destroy() {
@@ -117,9 +120,10 @@ export class ExpressionLoader {
update(expression?: string | ExpressionAST, params?: IExpressionLoaderParams): void {
this.setParams(params);
+ this.loadingSubject.next();
if (expression) {
this.loadData(expression, this.params);
- } else {
+ } else if (this.data) {
this.render(this.data);
}
}
@@ -128,18 +132,22 @@ export class ExpressionLoader {
expression: string | ExpressionAST,
params: IExpressionLoaderParams
): Promise => {
- this.loadingSubject.next();
- if (this.dataHandler) {
+ if (this.dataHandler && this.dataHandler.isPending) {
+ this.ignoreNextResponse = true;
this.dataHandler.cancel();
}
this.setParams(params);
this.dataHandler = new ExpressionDataHandler(expression, params);
+ if (!params.inspectorAdapters) params.inspectorAdapters = this.dataHandler.inspect();
const data = await this.dataHandler.getData();
+ if (this.ignoreNextResponse) {
+ this.ignoreNextResponse = false;
+ return;
+ }
this.dataSubject.next(data);
};
private render(data: Data): void {
- this.loadingSubject.next();
this.renderHandler.render(data, this.params.extraHandlers);
}
@@ -148,23 +156,16 @@ export class ExpressionLoader {
return;
}
- if (params.searchContext && this.params.searchContext) {
+ if (params.searchContext) {
this.params.searchContext = _.defaults(
{},
params.searchContext,
- this.params.searchContext
+ this.params.searchContext || {}
) as any;
}
if (params.extraHandlers && this.params) {
this.params.extraHandlers = params.extraHandlers;
}
-
- if (!Object.keys(this.params).length) {
- this.params = {
- ...params,
- searchContext: { type: 'kibana_context', ...(params.searchContext || {}) },
- };
- }
}
}
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 96c0802d3772a..ea029af9e4890 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 39f5f6a50a5a6..8784f0e79ca8d 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/functions/esaggs.ts b/src/legacy/core_plugins/interpreter/public/functions/esaggs.ts
index d232a97c3c34c..bcb8d00663e01 100644
--- a/src/legacy/core_plugins/interpreter/public/functions/esaggs.ts
+++ b/src/legacy/core_plugins/interpreter/public/functions/esaggs.ts
@@ -22,9 +22,15 @@ import { i18n } from '@kbn/i18n';
import { AggConfigs } from 'ui/agg_types/agg_configs';
import { createFormat } from 'ui/visualize/loader/pipeline_helpers/utilities';
import chrome from 'ui/chrome';
-import { TimeRange } from 'src/plugins/data/public';
+
+import { Query, TimeRange, esFilters } from 'src/plugins/data/public';
import { SearchSource } from '../../../../ui/public/courier/search_source';
-import { FilterBarQueryFilterProvider } from '../../../../ui/public/filter_manager/query_filter';
+// @ts-ignore
+import {
+ FilterBarQueryFilterProvider,
+ QueryFilter,
+} from '../../../../ui/public/filter_manager/query_filter';
+
import { buildTabularInspectorData } from '../../../../ui/public/inspector/build_tabular_inspector_data';
import {
getRequestInspectorStats,
@@ -32,15 +38,30 @@ import {
} from '../../../../ui/public/courier/utils/courier_inspector_utils';
import { calculateObjectHash } from '../../../../ui/public/vis/lib/calculate_object_hash';
import { getTime } from '../../../../ui/public/timefilter';
-import { RequestHandlerParams } from '../../../../ui/public/visualize/loader/embedded_visualize_handler';
-import { KibanaContext, KibanaDatatable } from '../../common';
-import { ExpressionFunction, KibanaDatatableColumn } from '../../types';
-import { start as data } from '../../../data/public/legacy';
+
+export interface RequestHandlerParams {
+ searchSource: SearchSource;
+ aggs: AggConfigs;
+ timeRange?: TimeRange;
+ query?: Query;
+ filters?: esFilters.Filter[];
+ forceFetch: boolean;
+ queryFilter: QueryFilter;
+ uiState?: PersistedState;
+ partialRows?: boolean;
+ inspectorAdapters: Adapters;
+ metricsAtAllLevels?: boolean;
+ visParams?: any;
+ abortSignal?: AbortSignal;
+}
// @ts-ignore
import { tabifyAggResponse } from '../../../../ui/public/agg_response/tabify/tabify';
-// @ts-ignore
-import { SearchSourceProvider } from '../../../../ui/public/courier/search_source';
+import { KibanaContext, KibanaDatatable } from '../../common';
+import { ExpressionFunction, KibanaDatatableColumn } from '../../types';
+import { start as data } from '../../../data/public/legacy';
+import { PersistedState } from '../../../../ui/public/persisted_state';
+import { Adapters } from '../../../../../plugins/inspector/public';
const name = 'esaggs';
diff --git a/src/legacy/core_plugins/interpreter/public/renderers/visualization.ts b/src/legacy/core_plugins/interpreter/public/renderers/visualization.tsx
similarity index 73%
rename from src/legacy/core_plugins/interpreter/public/renderers/visualization.ts
rename to src/legacy/core_plugins/interpreter/public/renderers/visualization.tsx
index bedba6bfacede..9de6cdeaf5ec3 100644
--- a/src/legacy/core_plugins/interpreter/public/renderers/visualization.ts
+++ b/src/legacy/core_plugins/interpreter/public/renderers/visualization.tsx
@@ -18,9 +18,11 @@
*/
import chrome from 'ui/chrome';
-import { visualizationLoader } from 'ui/visualize/loader/visualization_loader';
+import React from 'react';
+import { render, unmountComponentAtNode } from 'react-dom';
// @ts-ignore
-import { VisProvider } from 'ui/visualize/loader/vis';
+import { VisProvider } from '../../../../ui/public/visualize/loader/vis';
+import { Visualization } from '../../../../ui/public/visualize/components';
export const visualization = () => ({
name: 'visualization',
@@ -50,17 +52,27 @@ export const visualization = () => ({
type: visType,
params: visConfig,
});
- handlers.vis.eventsSubject = handlers.eventsSubject;
}
+ handlers.vis.eventsSubject = { next: handlers.event };
+
const uiState = handlers.uiState || handlers.vis.getUiState();
- handlers.onDestroy(() => visualizationLoader.destroy());
+ handlers.onDestroy(() => {
+ unmountComponentAtNode(domNode);
+ });
- await visualizationLoader
- .render(domNode, handlers.vis, visData, handlers.vis.params, uiState, params)
- .then(() => {
- if (handlers.done) handlers.done();
- });
+ const listenOnChange = params ? params.listenOnChange : false;
+ render(
+ ,
+ domNode
+ );
},
});
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 c7ada18f9e1f2..2ca4ed1e2343d 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/public/dashboard/dashboard_app.html b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.html
index 68c8131fa1a7b..f644f3811e3e0 100644
--- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.html
+++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.html
@@ -42,13 +42,6 @@
index-patterns="indexPatterns"
>
-
-
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 adf0e1e084a64..548a66297a3f9 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
@@ -56,9 +56,7 @@ 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 { extractTimeFilter, changeTimeFilter } from '../../../../../plugins/data/public';
import { start as data } from '../../../data/public/legacy';
-import { esFilters } from '../../../../../plugins/data/public';
import {
DashboardContainer,
@@ -417,31 +415,6 @@ export class DashboardAppController {
queryFilter.setFilters(filters);
};
- $scope.onCancelApplyFilters = () => {
- $scope.appState.$newFilters = [];
- };
-
- $scope.onApplyFilters = filters => {
- if (filters.length) {
- // All filters originated from one visualization.
- const indexPatternId = filters[0].meta.index;
- const indexPattern = _.find(
- $scope.indexPatterns,
- (p: IndexPattern) => p.id === indexPatternId
- );
- if (indexPattern && indexPattern.timeFieldName) {
- const { timeRangeFilter, restOfFilters } = extractTimeFilter(
- indexPattern.timeFieldName,
- filters
- );
- queryFilter.addFilters(restOfFilters);
- if (timeRangeFilter) changeTimeFilter(timefilter, timeRangeFilter);
- }
- }
-
- $scope.appState.$newFilters = [];
- };
-
$scope.onQuerySaved = savedQuery => {
$scope.savedQuery = savedQuery;
};
@@ -514,12 +487,6 @@ export class DashboardAppController {
}
);
- $scope.$watch('appState.$newFilters', (filters: esFilters.Filter[] = []) => {
- if (filters.length === 1) {
- $scope.onApplyFilters(filters);
- }
- });
-
$scope.indexPatterns = [];
$scope.$watch('model.query', (newQuery: Query) => {
diff --git a/src/legacy/core_plugins/kibana/public/dashboard/listing/__snapshots__/dashboard_listing.test.js.snap b/src/legacy/core_plugins/kibana/public/dashboard/listing/__snapshots__/dashboard_listing.test.js.snap
index e23102a0785fc..1ed05035f5f4c 100644
--- a/src/legacy/core_plugins/kibana/public/dashboard/listing/__snapshots__/dashboard_listing.test.js.snap
+++ b/src/legacy/core_plugins/kibana/public/dashboard/listing/__snapshots__/dashboard_listing.test.js.snap
@@ -1,7 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`after fetch hideWriteControls 1`] = `
-
`;
exports[`after fetch initialFilter 1`] = `
-
`;
exports[`after fetch renders call to action when no dashboards exist 1`] = `
-
`;
exports[`after fetch renders table rows 1`] = `
-
`;
exports[`after fetch renders warning when listingLimit is exceeded 1`] = `
-
`;
exports[`renders empty page in before initial fetch to avoid flickering 1`] = `
-
`;
diff --git a/src/legacy/core_plugins/kibana/public/dashboard/listing/dashboard_listing.js b/src/legacy/core_plugins/kibana/public/dashboard/listing/dashboard_listing.js
index d8216361562e2..c222fcd3c928c 100644
--- a/src/legacy/core_plugins/kibana/public/dashboard/listing/dashboard_listing.js
+++ b/src/legacy/core_plugins/kibana/public/dashboard/listing/dashboard_listing.js
@@ -23,8 +23,9 @@ import { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
import { EuiLink, EuiButton, EuiEmptyPrompt } from '@elastic/eui';
+import { npStart } from 'ui/new_platform';
-import { TableListView } from './../../table_list_view';
+import { TableListView } from '../../../../../../../src/plugins/kibana_react/public';
export const EMPTY_FILTER = '';
@@ -58,6 +59,8 @@ export class DashboardListing extends React.Component {
tableListTitle={i18n.translate('kbn.dashboard.listing.dashboardsTitle', {
defaultMessage: 'Dashboards',
})}
+ toastNotifications={npStart.core.notifications.toasts}
+ uiSettings={npStart.core.uiSettings}
/>
);
}
diff --git a/src/legacy/core_plugins/kibana/public/dashboard/listing/dashboard_listing.test.js b/src/legacy/core_plugins/kibana/public/dashboard/listing/dashboard_listing.test.js
index 57de395525e1b..be542c60bfe7a 100644
--- a/src/legacy/core_plugins/kibana/public/dashboard/listing/dashboard_listing.test.js
+++ b/src/legacy/core_plugins/kibana/public/dashboard/listing/dashboard_listing.test.js
@@ -42,6 +42,17 @@ jest.mock(
{ virtual: true }
);
+jest.mock('ui/new_platform', () => {
+ return {
+ npStart: {
+ core: {
+ notifications: { toasts: { } },
+ uiSettings: { get: jest.fn(() => 10) },
+ },
+ },
+ };
+});
+
import React from 'react';
import { shallow } from 'enzyme';
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 badfbb4b14a4c..5054f7b4bdad1 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 3d93487d9e6cc..d5f6b63d12199 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/home/components/__snapshots__/home.test.js.snap b/src/legacy/core_plugins/kibana/public/home/components/__snapshots__/home.test.js.snap
index 71c336b1d48d2..0bf8c808ae920 100644
--- a/src/legacy/core_plugins/kibana/public/home/components/__snapshots__/home.test.js.snap
+++ b/src/legacy/core_plugins/kibana/public/home/components/__snapshots__/home.test.js.snap
@@ -1072,10 +1072,8 @@ exports[`home welcome should show the normal home page if welcome screen is disa
exports[`home welcome should show the welcome screen if enabled, and there are no index patterns defined 1`] = `
`;
diff --git a/src/legacy/core_plugins/kibana/public/home/components/__snapshots__/welcome.test.tsx.snap b/src/legacy/core_plugins/kibana/public/home/components/__snapshots__/welcome.test.tsx.snap
new file mode 100644
index 0000000000000..5a6c6eba5c8db
--- /dev/null
+++ b/src/legacy/core_plugins/kibana/public/home/components/__snapshots__/welcome.test.tsx.snap
@@ -0,0 +1,188 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render a Welcome screen with no telemetry disclaimer 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`should render a Welcome screen with the telemetry disclaimer 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
diff --git a/src/legacy/core_plugins/kibana/public/home/components/home.js b/src/legacy/core_plugins/kibana/public/home/components/home.js
index 7f67b7ea0f3e1..3266bbb79c625 100644
--- a/src/legacy/core_plugins/kibana/public/home/components/home.js
+++ b/src/legacy/core_plugins/kibana/public/home/components/home.js
@@ -51,6 +51,7 @@ export class Home extends Component {
getServices().getInjected('disableWelcomeScreen') ||
props.localStorage.getItem(KEY_ENABLE_WELCOME) === 'false'
);
+ const showTelemetryDisclaimer = getServices().getInjected('allowChangingOptInStatus');
this.state = {
// If welcome is enabled, we wait for loading to complete
@@ -60,6 +61,7 @@ export class Home extends Component {
isLoading: isWelcomeEnabled,
isNewKibanaInstance: false,
isWelcomeEnabled,
+ showTelemetryDisclaimer,
};
}
@@ -228,10 +230,7 @@ export class Home extends Component {
);
}
@@ -254,10 +253,6 @@ export class Home extends Component {
Home.propTypes = {
addBasePath: PropTypes.func.isRequired,
- fetchTelemetry: PropTypes.func.isRequired,
- getTelemetryBannerId: PropTypes.func.isRequired,
- setOptIn: PropTypes.func.isRequired,
- shouldShowTelemetryOptIn: PropTypes.bool,
directories: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.string.isRequired,
diff --git a/src/legacy/core_plugins/kibana/public/home/components/home_app.js b/src/legacy/core_plugins/kibana/public/home/components/home_app.js
index e4a6753e0771a..f8476a0c09670 100644
--- a/src/legacy/core_plugins/kibana/public/home/components/home_app.js
+++ b/src/legacy/core_plugins/kibana/public/home/components/home_app.js
@@ -31,8 +31,6 @@ import { getServices } from '../kibana_services';
export function HomeApp({ directories }) {
const {
- telemetryOptInProvider,
- shouldShowTelemetryOptIn,
getInjected,
savedObjectsClient,
getBasePath,
@@ -85,10 +83,6 @@ export function HomeApp({ directories }) {
find={savedObjectsClient.find}
localStorage={localStorage}
urlBasePath={getBasePath()}
- shouldShowTelemetryOptIn={shouldShowTelemetryOptIn}
- setOptIn={telemetryOptInProvider.setOptIn}
- fetchTelemetry={telemetryOptInProvider.fetchExample}
- getTelemetryBannerId={telemetryOptInProvider.getBannerId}
/>
diff --git a/src/legacy/core_plugins/kibana/public/home/components/telemetry_opt_in/telemetry_opt_in_card.tsx b/src/legacy/core_plugins/kibana/public/home/components/telemetry_opt_in/telemetry_opt_in_card.tsx
deleted file mode 100644
index 572188d9c9b93..0000000000000
--- a/src/legacy/core_plugins/kibana/public/home/components/telemetry_opt_in/telemetry_opt_in_card.tsx
+++ /dev/null
@@ -1,81 +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 React from 'react';
-import { FormattedMessage } from '@kbn/i18n/react';
-import {
- // @ts-ignore
- EuiCard,
- EuiButton,
-} from '@elastic/eui';
-import { OptInMessage } from '../../../../../telemetry/public/components/opt_in_message';
-
-export interface Props {
- urlBasePath: string;
- onConfirm: () => void;
- onDecline: () => void;
- fetchTelemetry: () => Promise;
-}
-
-export function renderTelemetryOptInCard({
- urlBasePath,
- fetchTelemetry,
- onConfirm,
- onDecline,
-}: Props) {
- return (
-
- }
- description={}
- footer={
-
- }
- />
- );
-}
diff --git a/src/legacy/core_plugins/kibana/public/home/components/welcome.test.tsx b/src/legacy/core_plugins/kibana/public/home/components/welcome.test.tsx
new file mode 100644
index 0000000000000..195a527707af6
--- /dev/null
+++ b/src/legacy/core_plugins/kibana/public/home/components/welcome.test.tsx
@@ -0,0 +1,52 @@
+/*
+ * 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 { shallow } from 'enzyme';
+import { Welcome } from './welcome';
+
+jest.mock('../kibana_services', () => ({
+ getServices: () => ({
+ addBasePath: (path: string) => `root${path}`,
+ trackUiMetric: () => {},
+ METRIC_TYPE: {
+ LOADED: 'loaded',
+ CLICK: 'click',
+ },
+ }),
+}));
+
+test('should render a Welcome screen with the telemetry disclaimer', () => {
+ const component = shallow(
+ // @ts-ignore
+ {}} showTelemetryDisclaimer={true} />
+ );
+
+ expect(component).toMatchSnapshot();
+});
+
+test('should render a Welcome screen with no telemetry disclaimer', () => {
+ // @ts-ignore
+ const component = shallow(
+ // @ts-ignore
+ {}} showTelemetryDisclaimer={false} />
+ );
+
+ expect(component).toMatchSnapshot();
+});
diff --git a/src/legacy/core_plugins/kibana/public/home/components/welcome.tsx b/src/legacy/core_plugins/kibana/public/home/components/welcome.tsx
index afe43a23e18cb..d919a4ecf239c 100644
--- a/src/legacy/core_plugins/kibana/public/home/components/welcome.tsx
+++ b/src/legacy/core_plugins/kibana/public/home/components/welcome.tsx
@@ -25,6 +25,8 @@
import React from 'react';
import {
+ EuiLink,
+ EuiTextColor,
EuiTitle,
EuiSpacer,
EuiFlexGroup,
@@ -37,29 +39,18 @@ import { FormattedMessage } from '@kbn/i18n/react';
import { getServices } from '../kibana_services';
import { SampleDataCard } from './sample_data';
-import { TelemetryOptInCard } from './telemetry_opt_in';
interface Props {
urlBasePath: string;
- onSkip: () => {};
- fetchTelemetry: () => Promise;
- setOptIn: (enabled: boolean) => Promise;
- getTelemetryBannerId: () => string;
- shouldShowTelemetryOptIn: boolean;
-}
-
-interface State {
- step: number;
+ onSkip: () => void;
+ showTelemetryDisclaimer: boolean;
}
/**
* Shows a full-screen welcome page that gives helpful quick links to beginners.
*/
-export class Welcome extends React.PureComponent {
+export class Welcome extends React.Component {
private services = getServices();
- public readonly state: State = {
- step: 0,
- };
private hideOnEsc = (e: KeyboardEvent) => {
if (e.key === 'Escape') {
@@ -72,19 +63,11 @@ export class Welcome extends React.PureComponent {
window.location.href = path;
}
- private async handleTelemetrySelection(confirm: boolean) {
- const metricName = `telemetryOptIn${confirm ? 'Confirm' : 'Decline'}`;
- this.services.trackUiMetric(this.services.METRIC_TYPE.CLICK, metricName);
- await this.props.setOptIn(confirm);
- const bannerId = this.props.getTelemetryBannerId();
- this.services.banners.remove(bannerId);
- this.setState(() => ({ step: 1 }));
- }
-
private onSampleDataDecline = () => {
this.services.trackUiMetric(this.services.METRIC_TYPE.CLICK, 'sampleDataDecline');
this.props.onSkip();
};
+
private onSampleDataConfirm = () => {
this.services.trackUiMetric(this.services.METRIC_TYPE.CLICK, 'sampleDataConfirm');
this.redirecToSampleData();
@@ -92,12 +75,6 @@ export class Welcome extends React.PureComponent {
componentDidMount() {
this.services.trackUiMetric(this.services.METRIC_TYPE.LOADED, 'welcomeScreenMount');
- if (this.props.shouldShowTelemetryOptIn) {
- this.services.trackUiMetric(
- this.services.METRIC_TYPE.COUNT,
- 'welcomeScreenWithTelemetryOptIn'
- );
- }
document.addEventListener('keydown', this.hideOnEsc);
}
@@ -106,8 +83,7 @@ export class Welcome extends React.PureComponent {
}
render() {
- const { urlBasePath, shouldShowTelemetryOptIn, fetchTelemetry } = this.props;
- const { step } = this.state;
+ const { urlBasePath, showTelemetryDisclaimer } = this.props;
return (
@@ -137,20 +113,39 @@ export class Welcome extends React.PureComponent {
- {shouldShowTelemetryOptIn && step === 0 && (
-
- )}
- {(!shouldShowTelemetryOptIn || step === 1) && (
-
+
+
+ {showTelemetryDisclaimer && (
+
+
+
+
+
+
+
+
+
+
)}
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 a6ed2e36839f4..4ecc3583e76ce 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/editor/editor.js b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js
index f501161136801..58a0075e94b99 100644
--- a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js
+++ b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js
@@ -31,7 +31,7 @@ import editorTemplate from './editor.html';
import { DashboardConstants } from '../../dashboard/dashboard_constants';
import { VisualizeConstants } from '../visualize_constants';
import { getEditBreadcrumbs, getCreateBreadcrumbs } from '../breadcrumbs';
-import { extractTimeFilter, changeTimeFilter } from '../../../../../../plugins/data/public';
+
import { addHelpMenuToAppChrome } from '../help_menu/help_menu_util';
import {
@@ -342,23 +342,6 @@ function VisEditor(
queryFilter.setFilters(filters);
};
- $scope.onCancelApplyFilters = () => {
- $scope.state.$newFilters = [];
- };
-
- $scope.onApplyFilters = filters => {
- const { timeRangeFilter, restOfFilters } = extractTimeFilter($scope.indexPattern.timeFieldName, filters);
- queryFilter.addFilters(restOfFilters);
- if (timeRangeFilter) changeTimeFilter(timefilter, timeRangeFilter);
- $scope.state.$newFilters = [];
- };
-
- $scope.$watch('state.$newFilters', (filters = []) => {
- if (filters.length === 1) {
- $scope.onApplyFilters(filters);
- }
- });
-
$scope.showSaveQuery = capabilities.visualize.saveQuery;
$scope.$watch(() => capabilities.visualize.saveQuery, (newCapability) => {
@@ -457,6 +440,12 @@ function VisEditor(
next: $scope.fetch
}));
+ subscriptions.add(subscribeWithScope($scope, timefilter.getAutoRefreshFetch$(), {
+ next: () => {
+ $scope.vis.forceReload();
+ }
+ }));
+
$scope.$on('$destroy', function () {
if ($scope._handler) {
$scope._handler.destroy();
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 318686b26f6f2..60cf7c7ec1928 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
@@ -17,39 +17,53 @@
* under the License.
*/
-import _ from 'lodash';
-import { EmbeddedVisualizeHandler } from 'ui/visualize/loader/embedded_visualize_handler';
+import _, { forEach } from 'lodash';
+import { StaticIndexPattern } from 'ui/index_patterns';
+import { PersistedState } from 'ui/persisted_state';
import { Subscription } from 'rxjs';
import * as Rx from 'rxjs';
+import { buildPipeline } from 'ui/visualize/loader/pipeline_helpers';
+import { SavedObject } from 'ui/saved_objects/saved_object';
+import { Vis } from 'ui/vis';
+import { SearchSource } from 'ui/courier';
+import { queryGeohashBounds } from 'ui/visualize/loader/utils';
+import { getTableAggs } from 'ui/visualize/loader/pipeline_helpers/utilities';
+import { AppState } from 'ui/state_management/app_state';
+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,
onlyDisabledFiltersChanged,
esFilters,
} from '../../../../../../plugins/data/public';
-import { Query } from '../../../../data/public';
-import { VISUALIZE_EMBEDDABLE_TYPE } from './constants';
-
import {
- AppState,
- Container,
- Embeddable,
EmbeddableInput,
EmbeddableOutput,
- PersistedState,
- StaticIndexPattern,
- VisSavedObject,
- VisualizeLoader,
- VisualizeLoaderParams,
- VisualizeUpdateParams,
-} from '../kibana_services';
+ Embeddable,
+ Container,
+ 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;
+export interface VisSavedObject extends SavedObject {
+ vis: Vis;
+ description?: string;
+ searchSource: SearchSource;
+ title: string;
+ uiStateJSON?: string;
+ destroy: () => void;
+}
+
export interface VisualizeEmbeddableConfiguration {
savedVisualization: VisSavedObject;
indexPatterns?: StaticIndexPattern[];
editUrl: string;
- loader: VisualizeLoader;
editable: boolean;
appState?: AppState;
uiState?: PersistedState;
@@ -73,24 +87,28 @@ export interface VisualizeOutput extends EmbeddableOutput {
visTypeName: string;
}
+type ExpressionLoader = InstanceType;
+
export class VisualizeEmbeddable extends Embeddable {
+ private handler?: ExpressionLoader;
private savedVisualization: VisSavedObject;
- private loader: VisualizeLoader;
private appState: AppState | undefined;
private uiState: PersistedState;
- private handler?: EmbeddedVisualizeHandler;
private timeRange?: TimeRange;
private query?: Query;
private title?: string;
private filters?: esFilters.Filter[];
private visCustomizations: VisualizeInput['vis'];
- private subscription: Subscription;
+ private subscriptions: Subscription[] = [];
+ private expression: string = '';
+ private actions: any = {};
+ private vis: Vis;
+ private domNode: any;
public readonly type = VISUALIZE_EMBEDDABLE_TYPE;
constructor(
{
savedVisualization,
- loader,
editUrl,
indexPatterns,
editable,
@@ -112,8 +130,12 @@ export class VisualizeEmbeddable extends Embeddable {
- this.handleChanges();
- });
+ this.subscriptions.push(
+ Rx.merge(this.getOutput$(), this.getInput$()).subscribe(() => {
+ this.handleChanges();
+ })
+ );
}
public getVisualizationDescription() {
return this.savedVisualization.description;
}
- public getInspectorAdapters() {
+ public getInspectorAdapters = () => {
if (!this.handler) {
return undefined;
}
- return this.handler.inspectorAdapters;
- }
+ return this.handler.inspect();
+ };
+
+ public openInspector = () => {
+ if (this.handler) {
+ return this.handler.openInspector(this.getTitle() || '');
+ }
+ };
/**
* Transfers all changes in the containerState.customization into
@@ -170,87 +202,148 @@ export class VisualizeEmbeddable extends Embeddable {
+ if (event.disabled || !eventName) {
+ return;
+ } else {
+ this.actions[eventName] = event.defaultAction;
+ }
+ });
+
+ // This is a hack to give maps visualizations access to data in the
+ // globalState, since they can no longer access it via searchSource.
+ // TODO: Remove this as a part of elastic/kibana#30593
+ this.vis.API.getGeohashBounds = () => {
+ return queryGeohashBounds(this.savedVisualization.vis, {
+ filters: this.filters,
+ query: this.query,
+ searchSource: this.savedVisualization.searchSource,
+ });
+ };
+
+ // this is a hack to make editor still work, will be removed once we clean up editor
+ this.vis.hasInspector = () => {
+ const visTypesWithoutInspector = ['markdown', 'input_control_vis', 'metrics', 'vega'];
+ if (visTypesWithoutInspector.includes(this.vis.type.name)) {
+ return false;
+ }
+ return this.getInspectorAdapters();
};
+
+ this.vis.openInspector = this.openInspector;
+
+ const div = document.createElement('div');
+ div.className = `visualize panel-content panel-content--fullWidth`;
+ domNode.appendChild(div);
+ this.domNode = div;
+
+ this.handler = new expressions.ExpressionLoader(this.domNode);
+
+ this.subscriptions.push(
+ this.handler.events$.subscribe(async event => {
+ if (this.actions[event.name]) {
+ event.data.aggConfigs = getTableAggs(this.vis);
+ const filters: esFilters.Filter[] = this.actions[event.name](event.data) || [];
+ const mappedFilters = mapAndFlattenFilters(filters);
+ const timeFieldName = this.vis.indexPattern.timeFieldName;
+
+ npStart.plugins.uiActions.executeTriggerActions(APPLY_FILTER_TRIGGER, {
+ embeddable: this,
+ filters: mappedFilters,
+ timeFieldName,
+ });
+ }
+ })
+ );
+
+ div.setAttribute('data-title', this.output.title || '');
+
if (this.savedVisualization.description) {
- dataAttrs.description = this.savedVisualization.description;
+ div.setAttribute('data-description', this.savedVisualization.description);
}
- const handlerParams: VisualizeLoaderParams = {
- appState: this.appState,
- uiState: this.uiState,
- // Append visualization to container instead of replacing its content
- append: true,
- timeRange: _.cloneDeep(this.input.timeRange),
- query: this.query,
- filters: this.filters,
- cssClass: `panel-content panel-content--fullWidth`,
- dataAttrs,
- };
+ div.setAttribute('data-test-subj', 'visualizationLoader');
+ div.setAttribute('data-shared-item', '');
+ div.setAttribute('data-rendering-count', '0');
+ div.setAttribute('data-render-complete', 'false');
+
+ this.subscriptions.push(
+ this.handler.loading$.subscribe(() => {
+ div.setAttribute('data-render-complete', 'false');
+ div.setAttribute('data-loading', '');
+ })
+ );
- this.handler = this.loader.embedVisualizationWithSavedObject(
- domNode,
- this.savedVisualization,
- handlerParams
+ this.subscriptions.push(
+ this.handler.render$.subscribe(count => {
+ div.removeAttribute('data-loading');
+ div.setAttribute('data-render-complete', 'true');
+ div.setAttribute('data-rendering-count', count.toString());
+ dispatchRenderComplete(div);
+ })
);
+
+ this.updateHandler();
}
public destroy() {
super.destroy();
- if (this.subscription) {
- this.subscription.unsubscribe();
- }
+ this.subscriptions.forEach(s => s.unsubscribe());
this.uiState.off('change', this.uiStateChangeHandler);
+ this.savedVisualization.vis.removeListener('reload', this.reload);
+ this.savedVisualization.vis.removeListener('update', this.handleVisUpdate);
this.savedVisualization.destroy();
if (this.handler) {
this.handler.destroy();
@@ -258,12 +351,44 @@ export class VisualizeEmbeddable extends Embeddable {
+ this.handleVisUpdate();
+ };
+
+ private async updateHandler() {
+ const expressionParams: IExpressionLoaderParams = {
+ searchContext: {
+ type: 'kibana_context',
+ timeRange: this.timeRange,
+ query: this.input.query,
+ filters: this.input.filters,
+ },
+ extraHandlers: {
+ vis: this.vis,
+ uiState: this.uiState,
+ },
+ };
+ this.expression = await buildPipeline(this.vis, {
+ searchSource: this.savedVisualization.searchSource,
+ timeRange: this.timeRange,
+ });
+
+ this.vis.filters = { timeRange: this.timeRange };
+
if (this.handler) {
- this.handler.reload();
+ this.handler.update(this.expression, expressionParams);
}
}
+ private handleVisUpdate = async () => {
+ if (this.appState) {
+ this.appState.vis = this.savedVisualization.vis.getState();
+ this.appState.save();
+ }
+
+ this.updateHandler();
+ };
+
private uiStateChangeHandler = () => {
this.updateInput({
...this.uiState.toJSON(),
diff --git a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx
index c1ce4f67cfdb3..15ad9a33232ef 100644
--- a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx
+++ b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx
@@ -36,7 +36,6 @@ import {
EmbeddableFactory,
EmbeddableOutput,
ErrorEmbeddable,
- getVisualizeLoader,
VisSavedObject,
} from '../kibana_services';
@@ -131,7 +130,6 @@ export class VisualizeEmbeddableFactory extends EmbeddableFactory<
const visId = savedObject.id as string;
const editUrl = visId ? addBasePath(`/app/kibana${savedVisualizations.urlFor(visId)}`) : '';
- const loader = await getVisualizeLoader();
const isLabsEnabled = config.get('visualize:enableLabs');
if (!isLabsEnabled && savedObject.vis.type.stage === 'experimental') {
@@ -143,7 +141,6 @@ export class VisualizeEmbeddableFactory extends EmbeddableFactory<
return new VisualizeEmbeddable(
{
savedVisualization: savedObject,
- loader,
indexPatterns,
editUrl,
editable: this.isEditable(),
diff --git a/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts b/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts
index 7e8435bbdc65e..5c6d06b5eaeb6 100644
--- a/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts
+++ b/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts
@@ -42,7 +42,7 @@ import { timefilter } from 'ui/timefilter';
// Saved objects
import { SavedObjectsClientProvider } from 'ui/saved_objects';
// @ts-ignore
-import { SavedObjectProvider } from 'ui/saved_objects/saved_object';
+import { SavedObject, SavedObjectProvider } from 'ui/saved_objects/saved_object';
import { SavedObjectRegistryProvider } from 'ui/saved_objects/saved_object_registry';
import { createUiStatsReporter, METRIC_TYPE } from '../../../ui_metric/public';
@@ -105,7 +105,6 @@ export { absoluteToParsedUrl } from 'ui/url/absolute_to_parsed_url';
export { KibanaParsedUrl } from 'ui/url/kibana_parsed_url';
export { migrateLegacyQuery } from 'ui/utils/migrate_legacy_query';
export { subscribeWithScope } from 'ui/utils/subscribe_with_scope';
-export { getVisualizeLoader } from 'ui/visualize/loader';
export { SavedObjectSaveModal } from 'ui/saved_objects/components/saved_object_save_modal';
export {
Container,
@@ -121,12 +120,8 @@ export { METRIC_TYPE };
export { StaticIndexPattern } from 'ui/index_patterns';
export { AppState } from 'ui/state_management/app_state';
export { VisType } from 'ui/vis';
-export { VisualizeLoader } from 'ui/visualize/loader';
-export {
- VisSavedObject,
- VisualizeLoaderParams,
- VisualizeUpdateParams,
-} from 'ui/visualize/loader/types';
// export const
export { FeatureCatalogueCategory } from 'ui/registry/feature_catalogue';
+
+export { VisSavedObject } from './embeddable/visualize_embeddable';
diff --git a/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing_table.js b/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing_table.js
index fbd70a0d8c0f7..efab03303aa80 100644
--- a/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing_table.js
+++ b/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing_table.js
@@ -21,13 +21,13 @@ import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
-import { TableListView } from './../../table_list_view';
+import { TableListView } from '../../../../../../../src/plugins/kibana_react/public';
import { EuiIcon, EuiBetaBadge, EuiLink, EuiButton, EuiEmptyPrompt } from '@elastic/eui';
import { getServices } from '../kibana_services';
-const { capabilities } = getServices();
+const { capabilities, toastNotifications, uiSettings } = getServices();
class VisualizeListingTable extends Component {
constructor(props) {
@@ -57,6 +57,8 @@ class VisualizeListingTable extends Component {
tableListTitle={i18n.translate('kbn.visualize.listing.table.listTitle', {
defaultMessage: 'Visualizations',
})}
+ toastNotifications={toastNotifications}
+ uiSettings={uiSettings}
/>
);
}
diff --git a/src/legacy/core_plugins/newsfeed/constants.ts b/src/legacy/core_plugins/newsfeed/constants.ts
new file mode 100644
index 0000000000000..55a0c51c2ac65
--- /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 0000000000000..cf8852be09a1e
--- /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 0000000000000..d4d753f32b0f9
--- /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 0000000000000..a77132379041c
--- /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 0000000000000..e25dbd25daaf5
--- /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 0000000000000..c3c3e4144fca8
--- /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/components/region_map_options.tsx b/src/legacy/core_plugins/region_map/public/components/region_map_options.tsx
index 9749c7fa8e2f9..8306b3274a914 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 ab1397b2cc232..7b0c62276f290 100644
--- a/src/legacy/core_plugins/telemetry/common/constants.ts
+++ b/src/legacy/core_plugins/telemetry/common/constants.ts
@@ -51,7 +51,7 @@ export const LOCALSTORAGE_KEY = 'telemetry.data';
/**
* Link to the Elastic Telemetry privacy statement.
*/
-export const PRIVACY_STATEMENT_URL = `https://www.elastic.co/legal/telemetry-privacy-statement`;
+export const PRIVACY_STATEMENT_URL = `https://www.elastic.co/legal/privacy-statement`;
/**
* The type name used within the Monitoring index to publish localization stats.
@@ -59,6 +59,12 @@ export const PRIVACY_STATEMENT_URL = `https://www.elastic.co/legal/telemetry-pri
*/
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 50a25423b5eb8..9993f2dbf0b86 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 1245ef88f5892..95c6ebfc7dc79 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__/opt_in_message.test.tsx.snap b/src/legacy/core_plugins/telemetry/public/components/__snapshots__/opt_in_message.test.tsx.snap
new file mode 100644
index 0000000000000..c80485332fa8a
--- /dev/null
+++ b/src/legacy/core_plugins/telemetry/public/components/__snapshots__/opt_in_message.test.tsx.snap
@@ -0,0 +1,24 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`OptInMessage renders as expected 1`] = `
+
+
+
+ ,
+ }
+ }
+ />
+
+`;
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 e1aead3798de7..a7f8d72e016f8 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.
@@ -52,7 +53,7 @@ exports[`TelemetryForm renders as expected when allows to change optIn status 1`
{
const title = (
);
return (
@@ -45,12 +45,18 @@ export class OptInBanner extends React.PureComponent {
this.props.optInClick(true)}>
-
+
this.props.optInClick(false)}>
-
+
diff --git a/src/legacy/core_plugins/telemetry/public/components/opt_in_message.test.tsx b/src/legacy/core_plugins/telemetry/public/components/opt_in_message.test.tsx
new file mode 100644
index 0000000000000..1a9fabceda907
--- /dev/null
+++ b/src/legacy/core_plugins/telemetry/public/components/opt_in_message.test.tsx
@@ -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.
+ */
+import React from 'react';
+import { shallowWithIntl } from 'test_utils/enzyme_helpers';
+import { OptInMessage } from './opt_in_message';
+
+describe('OptInMessage', () => {
+ it('renders as expected', () => {
+ expect(
+ shallowWithIntl( [])} />)
+ ).toMatchSnapshot();
+ });
+});
diff --git a/src/legacy/core_plugins/telemetry/public/components/opt_in_message.tsx b/src/legacy/core_plugins/telemetry/public/components/opt_in_message.tsx
index 928bb1015b715..4221d78516e10 100644
--- a/src/legacy/core_plugins/telemetry/public/components/opt_in_message.tsx
+++ b/src/legacy/core_plugins/telemetry/public/components/opt_in_message.tsx
@@ -21,8 +21,7 @@ import * as React from 'react';
import { EuiLink } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
-import { getConfigTelemetryDesc, PRIVACY_STATEMENT_URL } from '../../common/constants';
-import { OptInExampleFlyout } from './opt_in_details_component';
+import { PRIVACY_STATEMENT_URL } from '../../common/constants';
interface Props {
fetchTelemetry: () => Promise;
@@ -46,60 +45,22 @@ export class OptInMessage extends React.PureComponent {
};
render() {
- const { showDetails, showExample } = this.state;
-
- const getDetails = () => (
-
-
-
- ),
- telemetryPrivacyStatementLink: (
-
-
-
- ),
- }}
- />
- );
-
- const getFlyoutDetails = () => (
- this.setState({ showExample: false })}
- fetchTelemetry={this.props.fetchTelemetry}
- />
- );
-
- const getReadMore = () => (
- this.setState({ showDetails: true })}>
-
-
- );
-
return (
- {getConfigTelemetryDesc()} {!showDetails && getReadMore()}
- {showDetails && (
-
- {getDetails()}
- {showExample && getFlyoutDetails()}
-
- )}
+
+
+
+ ),
+ }}
+ />
);
}
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 80eb2da59c47e..6c6ace71af4d0 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 364871380a529..1930d65d5c09b 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 6e9a9fc8443ba..54557f100f4aa 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 31091e1952053..4f0f2983477e0 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 f26ca0ca0e3c5..d78a4a3e92362 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 26f14fc87d937..b0ebb9e7382f6 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 4d27bad352cd4..9b32f88df1218 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 fef0a9b0f9f40..799d9f4ee9c8b 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 0bc1d50fab1be..f963ecec0477c 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/visualize/loader/index.ts b/src/legacy/core_plugins/telemetry/server/collectors/telemetry_plugin/index.ts
similarity index 90%
rename from src/legacy/ui/public/visualize/loader/index.ts
rename to src/legacy/core_plugins/telemetry/server/collectors/telemetry_plugin/index.ts
index 0ebe8e3a2300f..e96c47741f79c 100644
--- a/src/legacy/ui/public/visualize/loader/index.ts
+++ b/src/legacy/core_plugins/telemetry/server/collectors/telemetry_plugin/index.ts
@@ -17,4 +17,4 @@
* under the License.
*/
-export * from './visualize_loader';
+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 0000000000000..a172ba7dc6955
--- /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 6594c7f8e7a6f..3b7a9355da746 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 0000000000000..9edd8457f2b89
--- /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 aa13fab9a5f81..02752ca773488 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 813aa0df09e8c..f2628090c08af 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 549b3ef6068ec..66a7b2c97f3ae 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 3a7194890b570..0000000000000
--- 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 0000000000000..596c5c17c353e
--- /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 0000000000000..d3bf6dbb77d7a
--- /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 8a91d24b34ed2..c14314ca4da24 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 9ca609cd88778..d60b330db7b5b 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 d0de9cc365a71..4cbdf18df4a74 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 a840c39812e2c..4abd95f0cf66d 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 67fc721306c21..e11c6b1277d5b 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/index.ts b/src/legacy/core_plugins/telemetry/server/telemetry_collection/index.ts
index f33727d82f44c..7f228dbc5e6f6 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 0000000000000..faf8e9de79194
--- /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/test/plugin_functional/plugins/kbn_tp_visualize_embedding/index.js b/src/legacy/core_plugins/telemetry/server/telemetry_config/get_telemetry_allow_changing_opt_in_status.ts
similarity index 53%
rename from test/plugin_functional/plugins/kbn_tp_visualize_embedding/index.js
rename to src/legacy/core_plugins/telemetry/server/telemetry_config/get_telemetry_allow_changing_opt_in_status.ts
index 1ec4ea2b9e096..9fa4fbc5e0227 100644
--- a/test/plugin_functional/plugins/kbn_tp_visualize_embedding/index.js
+++ b/src/legacy/core_plugins/telemetry/server/telemetry_config/get_telemetry_allow_changing_opt_in_status.ts
@@ -16,24 +16,24 @@
* specific language governing permissions and limitations
* under the License.
*/
+import { TelemetrySavedObject } from '../telemetry_repository/get_telemetry_saved_object';
-export default function (kibana) {
- return new kibana.Plugin({
- uiExports: {
- app: {
- title: 'Embedding Vis',
- description: 'This is a sample plugin to test embedding of visualizations',
- main: 'plugins/kbn_tp_visualize_embedding/app',
- }
- },
+interface GetTelemetryAllowChangingOptInStatus {
+ configTelemetryAllowChangingOptInStatus: boolean;
+ telemetrySavedObject: TelemetrySavedObject;
+}
+
+export function getTelemetryAllowChangingOptInStatus({
+ telemetrySavedObject,
+ configTelemetryAllowChangingOptInStatus,
+}: GetTelemetryAllowChangingOptInStatus) {
+ if (!telemetrySavedObject) {
+ return configTelemetryAllowChangingOptInStatus;
+ }
+
+ if (typeof telemetrySavedObject.telemetryAllowChangingOptInStatus === 'undefined') {
+ return configTelemetryAllowChangingOptInStatus;
+ }
- init(server) {
- // The following lines copy over some configuration variables from Kibana
- // to this plugin. This will be needed when embedding visualizations, so that e.g.
- // region map is able to get its configuration.
- server.injectUiAppVars('kbn_tp_visualize_embedding', async () => {
- return await server.getInjectedUiAppVars('kibana');
- });
- }
- });
+ 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 67ad3aaae427d..efc4a020e0ff0 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 c8bd4a4b6dfbd..d83ffdf69b576 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 0000000000000..69868a97a931d
--- /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 0000000000000..9e4ae14b6097c
--- /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 0000000000000..ab30dac1c3666
--- /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 0000000000000..90d1f9cfdac65
--- /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 0000000000000..7cc177878de4d
--- /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 0000000000000..91965ef201ecb
--- /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 0000000000000..f3629abc1620c
--- /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/telemetry/server/telemetry_repository/update_telemetry_saved_object.ts b/src/legacy/core_plugins/telemetry/server/telemetry_repository/update_telemetry_saved_object.ts
new file mode 100644
index 0000000000000..b66e01faaa6bc
--- /dev/null
+++ b/src/legacy/core_plugins/telemetry/server/telemetry_repository/update_telemetry_saved_object.ts
@@ -0,0 +1,38 @@
+/*
+ * 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 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;
+ }
+}
diff --git a/src/legacy/core_plugins/tile_map/public/tile_map_visualization.js b/src/legacy/core_plugins/tile_map/public/tile_map_visualization.js
index ca798b6bf2470..560a5c93c938c 100644
--- a/src/legacy/core_plugins/tile_map/public/tile_map_visualization.js
+++ b/src/legacy/core_plugins/tile_map/public/tile_map_visualization.js
@@ -79,6 +79,7 @@ export const createTileMapVisualization = ({ serviceSettings, $injector }) => {
return;
}
if (precisionChange) {
+ updateGeohashAgg();
this.vis.updateState();
} else {
//when we filter queries by collar
diff --git a/src/legacy/core_plugins/ui_metric/index.ts b/src/legacy/core_plugins/ui_metric/index.ts
index 6c957f23b5c40..964e3497accba 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 7aafc82cfe4c6..983434f09922b 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 b1e78b56d05d0..5c327234b1e7c 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 7310ee5b5f172..ee928b8a1d9ee 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 8a7950c46fa31..e2de23ea806e4 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_timeseries/public/components/vis_editor_visualization.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_editor_visualization.js
index f873cf9c178f8..ae82dc41fa9bc 100644
--- a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_editor_visualization.js
+++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_editor_visualization.js
@@ -75,13 +75,13 @@ class VisEditorVisualizationUI extends Component {
this._handler = await embeddables.getEmbeddableFactory('visualization').createFromObject(savedObj, {
vis: {},
timeRange: timeRange,
- filters: appState.filters || [],
+ filters: appState ? appState.filters || [] : [],
});
- this._handler.render(this._visEl.current);
+ await this._handler.render(this._visEl.current);
this._subscription = this._handler.handler.data$.subscribe(data => {
- this.setPanelInterval(data.visData);
- onDataChange(data);
+ this.setPanelInterval(data.value.visData);
+ onDataChange(data.value);
});
}
diff --git a/src/legacy/ui/public/agg_types/agg_configs.ts b/src/legacy/ui/public/agg_types/agg_configs.ts
index 675d37d05c33c..7c0245f30a1fd 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/geo_hash.test.ts b/src/legacy/ui/public/agg_types/buckets/geo_hash.test.ts
index 5c599f16e09c2..effa49f0ade6b 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 f7cae60cce773..1b423e64c48ae 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 348fccdab3fe3..230675ab487ad 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 45f350ea6adc8..e6b5f3831e65d 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 66bc205cead13..c1f5528825bcc 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 f3882ca57161f..7461b5cf07ee7 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 1503f43b22dc3..c9f4bcc3862a0 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 ae09b5cd78977..3125026a52185 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/courier/search_strategy/default_search_strategy.js b/src/legacy/ui/public/courier/search_strategy/default_search_strategy.js
index 7d9865c137e62..42a9b64136454 100644
--- a/src/legacy/ui/public/courier/search_strategy/default_search_strategy.js
+++ b/src/legacy/ui/public/courier/search_strategy/default_search_strategy.js
@@ -66,10 +66,9 @@ function search({ searchRequests, es, config, esShardTimeout }) {
const abortController = new AbortController();
const searchParams = getSearchParams(config, esShardTimeout);
const promises = searchRequests.map(({ index, body }) => {
- const searching = es.search({ index: index.title || index, body, ...searchParams })
- .catch(({ response }) => JSON.parse(response));
+ const searching = es.search({ index: index.title || index, body, ...searchParams });
abortController.signal.addEventListener('abort', searching.abort);
- return searching;
+ return searching.catch(({ response }) => JSON.parse(response));
});
return {
searching: Promise.all(promises),
diff --git a/src/legacy/ui/public/courier/search_strategy/default_search_strategy.test.js b/src/legacy/ui/public/courier/search_strategy/default_search_strategy.test.js
index 953ca4fe800f1..a1ea53e8b5b47 100644
--- a/src/legacy/ui/public/courier/search_strategy/default_search_strategy.test.js
+++ b/src/legacy/ui/public/courier/search_strategy/default_search_strategy.test.js
@@ -27,16 +27,29 @@ function getConfigStub(config = {}) {
};
}
+const msearchMockResponse = Promise.resolve([]);
+msearchMockResponse.abort = jest.fn();
+const msearchMock = jest.fn().mockReturnValue(msearchMockResponse);
+
+const searchMockResponse = Promise.resolve([]);
+searchMockResponse.abort = jest.fn();
+const searchMock = jest.fn().mockReturnValue(searchMockResponse);
+
describe('defaultSearchStrategy', function () {
describe('search', function () {
let searchArgs;
beforeEach(() => {
- const msearchMock = jest.fn().mockReturnValue(Promise.resolve([]));
- const searchMock = jest.fn().mockReturnValue(Promise.resolve([]));
+ msearchMockResponse.abort.mockClear();
+ msearchMock.mockClear();
+
+ searchMockResponse.abort.mockClear();
+ searchMock.mockClear();
searchArgs = {
- searchRequests: [],
+ searchRequests: [{
+ index: { title: 'foo' }
+ }],
es: {
msearch: msearchMock,
search: searchMock,
@@ -73,5 +86,21 @@ describe('defaultSearchStrategy', function () {
await search(searchArgs);
expect(searchArgs.es.msearch.mock.calls[0][0]).toHaveProperty('ignore_throttled', false);
});
+
+ test('should properly call abort with msearch', () => {
+ searchArgs.config = getConfigStub({
+ 'courier:batchSearches': true
+ });
+ search(searchArgs).abort();
+ expect(msearchMockResponse.abort).toHaveBeenCalled();
+ });
+
+ test('should properly abort with search', async () => {
+ searchArgs.config = getConfigStub({
+ 'courier:batchSearches': false
+ });
+ search(searchArgs).abort();
+ expect(searchMockResponse.abort).toHaveBeenCalled();
+ });
});
});
diff --git a/src/legacy/ui/public/index_patterns/__mocks__/index.ts b/src/legacy/ui/public/index_patterns/__mocks__/index.ts
index 85c07cb3b1df1..2dd3f370c6d6a 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 67c370cad82a5..3b4952ac81519 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 58b8422cb2f8a..788718e848430 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(
{
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 8e8926f027cad..980889743c20d 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 5fb241714b2e8..eb6bef4887642 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 3b6aebe8c2b0c..53f74465e90a5 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 3928c0a62eeaa..c6772cc108762 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/switch.tsx b/src/legacy/ui/public/vis/editors/default/controls/switch.tsx
index a5fc9682bd954..de675386d9100 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 6da32690912e7..932a4d19b495c 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/default.html b/src/legacy/ui/public/vis/editors/default/default.html
index 3e7a94c77ac42..2a759815f57f2 100644
--- a/src/legacy/ui/public/vis/editors/default/default.html
+++ b/src/legacy/ui/public/vis/editors/default/default.html
@@ -11,11 +11,6 @@
diff --git a/src/legacy/ui/public/vis/editors/default/utils.tsx b/src/legacy/ui/public/vis/editors/default/utils.tsx
index efc424488ec41..4f82298aaca41 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/map/map_messages.js b/src/legacy/ui/public/vis/map/map_messages.js
index 7571547ecf0d4..a9a636ab9e4cf 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/visualize/components/visualization_chart.test.js b/src/legacy/ui/public/visualize/components/visualization_chart.test.js
index 280370cdfe995..09d24ab71097a 100644
--- a/src/legacy/ui/public/visualize/components/visualization_chart.test.js
+++ b/src/legacy/ui/public/visualize/components/visualization_chart.test.js
@@ -57,46 +57,10 @@ describe('', () => {
expect(wrapper.text()).toBe('Test Visualization visualization, not yet accessible');
});
- it('should emit render start and render end', async () => {
- const renderStart = jest.fn();
- const renderComplete = jest.fn();
- const domNode = document.createElement('div');
- domNode.addEventListener('renderStart', renderStart);
- domNode.addEventListener('renderComplete', renderComplete);
-
- mount(, {
- attachTo: domNode
- });
-
- jest.runAllTimers();
- await renderPromise;
- expect(renderStart).toHaveBeenCalledTimes(1);
- expect(renderComplete).toHaveBeenCalledTimes(1);
-
- });
-
it('should render visualization', async () => {
const wrapper = mount();
jest.runAllTimers();
await renderPromise;
expect(wrapper.find('.visChart').text()).toMatch(/markdown/);
});
-
- it('should re-render on param change', async () => {
- const renderComplete = jest.fn();
- const wrapper = mount();
- const domNode = wrapper.getDOMNode();
- domNode.addEventListener('renderComplete', renderComplete);
- jest.runAllTimers();
- await renderPromise;
- expect(renderComplete).toHaveBeenCalledTimes(1);
-
- vis.params.markdown = 'new text';
- wrapper.setProps({ vis });
- jest.runAllTimers();
- await renderPromise;
-
- expect(wrapper.find('.visChart').text()).toBe('new text');
- expect(renderComplete).toHaveBeenCalledTimes(2);
- });
});
diff --git a/src/legacy/ui/public/visualize/components/visualization_chart.tsx b/src/legacy/ui/public/visualize/components/visualization_chart.tsx
index 06e44a4fd6e1c..eb7f130ec1a54 100644
--- a/src/legacy/ui/public/visualize/components/visualization_chart.tsx
+++ b/src/legacy/ui/public/visualize/components/visualization_chart.tsx
@@ -19,13 +19,9 @@
import React from 'react';
import * as Rx from 'rxjs';
-import { debounceTime, filter, share, switchMap, tap } from 'rxjs/operators';
+import { debounceTime, filter, share, switchMap } from 'rxjs/operators';
import { PersistedState } from '../../persisted_state';
-import {
- dispatchRenderComplete,
- dispatchRenderStart,
-} from '../../../../../plugins/kibana_utils/public';
import { ResizeChecker } from '../../resize_checker';
import { Vis, VisualizationController } from '../../vis';
import { getUpdateStatus } from '../../vis/update_status';
@@ -59,11 +55,6 @@ class VisualizationChart extends React.Component {
const render$ = this.renderSubject.asObservable().pipe(share());
const success$ = render$.pipe(
- tap(() => {
- if (this.chartDiv.current) {
- dispatchRenderStart(this.chartDiv.current);
- }
- }),
filter(
({ vis, visData, container }) => vis && container && (!vis.type.requiresSearch || visData)
),
@@ -85,8 +76,8 @@ class VisualizationChart extends React.Component {
const requestError$ = render$.pipe(filter(({ vis }) => vis.requestError));
this.renderSubscription = Rx.merge(success$, requestError$).subscribe(() => {
- if (this.chartDiv.current !== null) {
- dispatchRenderComplete(this.chartDiv.current);
+ if (this.props.onInit) {
+ this.props.onInit();
}
});
}
@@ -111,19 +102,11 @@ class VisualizationChart extends React.Component {
throw new Error('chartDiv and currentDiv reference should always be present.');
}
- const { vis, onInit } = this.props;
+ const { vis } = this.props;
const Visualization = vis.type.visualization;
this.visualization = new Visualization(this.chartDiv.current, vis);
- if (onInit) {
- // In case the visualization implementation has an isLoaded function, we
- // call that and wait for the result to resolve (in case it was a promise).
- const visLoaded =
- this.visualization && this.visualization.isLoaded && this.visualization.isLoaded();
- Promise.resolve(visLoaded).then(onInit);
- }
-
// We know that containerDiv.current will never be null, since we will always
// have rendered and the div is always rendered into the tree (i.e. not
// inside any condition).
diff --git a/src/legacy/ui/public/visualize/loader/__snapshots__/embedded_visualize_handler.test.ts.snap b/src/legacy/ui/public/visualize/loader/__snapshots__/embedded_visualize_handler.test.ts.snap
deleted file mode 100644
index 6650731942e7e..0000000000000
--- a/src/legacy/ui/public/visualize/loader/__snapshots__/embedded_visualize_handler.test.ts.snap
+++ /dev/null
@@ -1,30 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`EmbeddedVisualizeHandler data$ observable can be used to get response data in the correct format 1`] = `
-Object {
- "params": Object {},
- "visConfig": Object {},
- "visData": Object {},
- "visType": "histogram",
-}
-`;
-
-exports[`EmbeddedVisualizeHandler update should add provided data- attributes to the html element 1`] = `
-
-`;
-
-exports[`EmbeddedVisualizeHandler update should remove null data- attributes from the html element 1`] = `
-
-`;
diff --git a/src/legacy/ui/public/visualize/loader/__tests__/visualization_loader.js b/src/legacy/ui/public/visualize/loader/__tests__/visualization_loader.js
deleted file mode 100644
index ffce391fc1a07..0000000000000
--- a/src/legacy/ui/public/visualize/loader/__tests__/visualization_loader.js
+++ /dev/null
@@ -1,56 +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 'jquery';
-import expect from '@kbn/expect';
-import ngMock from 'ng_mock';
-
-import { setupAndTeardownInjectorStub } from 'test_utils/stub_get_active_injector';
-
-import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern';
-
-import { VisProvider } from '../../../vis';
-import { visualizationLoader } from '../visualization_loader';
-
-describe('visualization loader', () => {
- let vis;
-
- beforeEach(ngMock.module('kibana', 'kibana/directive'));
- beforeEach(ngMock.inject((_$rootScope_, savedVisualizations, Private) => {
- const indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider);
-
- // Create a new Vis object
- const Vis = Private(VisProvider);
- vis = new Vis(indexPattern, {
- type: 'markdown',
- params: { markdown: 'this is test' },
- });
-
- }));
- setupAndTeardownInjectorStub();
-
- it('should render visualization', async () => {
- const element = document.createElement('div');
- expect(visualizationLoader.render).to.be.a('function');
- visualizationLoader.render(element, vis, null, vis.params);
- expect($(element).find('.visualization').length).to.be(1);
- });
-
-
-});
diff --git a/src/legacy/ui/public/visualize/loader/__tests__/visualize_loader.js b/src/legacy/ui/public/visualize/loader/__tests__/visualize_loader.js
deleted file mode 100644
index 3fff184ffd199..0000000000000
--- a/src/legacy/ui/public/visualize/loader/__tests__/visualize_loader.js
+++ /dev/null
@@ -1,478 +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 angular from 'angular';
-import expect from '@kbn/expect';
-import ngMock from 'ng_mock';
-import sinon from 'sinon';
-import { cloneDeep } from 'lodash';
-
-import { setupAndTeardownInjectorStub } from 'test_utils/stub_get_active_injector';
-
-import FixturesStubbedSearchSourceProvider from 'fixtures/stubbed_search_source';
-import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern';
-
-import { VisProvider } from '../../../vis';
-import { getVisualizeLoader } from '../visualize_loader';
-import { EmbeddedVisualizeHandler } from '../embedded_visualize_handler';
-import { Inspector } from '../../../inspector/inspector';
-import { dispatchRenderComplete } from '../../../../../../plugins/kibana_utils/public';
-import { PipelineDataLoader } from '../pipeline_data_loader';
-import { PersistedState } from '../../../persisted_state';
-import { DataAdapter, RequestAdapter } from '../../../inspector/adapters';
-
-describe('visualize loader', () => {
-
- let DataLoader;
- let searchSource;
- let vis;
- let $rootScope;
- let loader;
- let mockedSavedObject;
- let sandbox;
-
- function createSavedObject() {
- return {
- vis,
- searchSource,
- };
- }
-
- async function timeout(delay = 0) {
- return new Promise(resolve => {
- setTimeout(resolve, delay);
- });
- }
-
- function newContainer() {
- return angular.element('');
- }
-
- function embedWithParams(params) {
- const container = newContainer();
- loader.embedVisualizationWithSavedObject(container[0], createSavedObject(), params);
- $rootScope.$digest();
- return container.find('[data-test-subj="visualizationLoader"]');
- }
-
- beforeEach(ngMock.module('kibana', 'kibana/directive'));
- beforeEach(ngMock.inject((_$rootScope_, savedVisualizations, Private) => {
- $rootScope = _$rootScope_;
- searchSource = Private(FixturesStubbedSearchSourceProvider);
- const indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider);
-
- DataLoader = PipelineDataLoader;
- // Create a new Vis object
- const Vis = Private(VisProvider);
- vis = new Vis(indexPattern, {
- type: 'pie',
- title: 'testVis',
- params: {},
- aggs: [
- { type: 'count', schema: 'metric' },
- {
- type: 'range',
- schema: 'bucket',
- params: {
- field: 'bytes',
- ranges: [
- { from: 0, to: 1000 },
- { from: 1000, to: 2000 }
- ]
- }
- }
- ]
- });
- vis.type.requestHandler = 'courier';
- vis.type.responseHandler = 'none';
- vis.type.requiresSearch = false;
-
- // Setup savedObject
- mockedSavedObject = createSavedObject();
-
- sandbox = sinon.sandbox.create();
- // Mock savedVisualizations.get to return 'mockedSavedObject' when id is 'exists'
- sandbox.stub(savedVisualizations, 'get').callsFake((id) =>
- id === 'exists' ? Promise.resolve(mockedSavedObject) : Promise.reject()
- );
- }));
- setupAndTeardownInjectorStub();
- beforeEach(async () => {
- loader = await getVisualizeLoader();
- });
-
- afterEach(() => {
- if (sandbox) {
- sandbox.restore();
- }
- });
-
- describe('getVisualizeLoader', () => {
-
- it('should return a promise', () => {
- expect(getVisualizeLoader().then).to.be.a('function');
- });
-
- it('should resolve to an object', async () => {
- const visualizeLoader = await getVisualizeLoader();
- expect(visualizeLoader).to.be.an('object');
- });
-
- });
-
- describe('service', () => {
-
- describe('getVisualizationList', () => {
-
- it('should be a function', async () => {
- expect(loader.getVisualizationList).to.be.a('function');
- });
-
- });
-
- describe('embedVisualizationWithSavedObject', () => {
-
- it('should be a function', () => {
- expect(loader.embedVisualizationWithSavedObject).to.be.a('function');
- });
-
- it('should render the visualize element', () => {
- const container = newContainer();
- loader.embedVisualizationWithSavedObject(container[0], createSavedObject(), {});
- expect(container.find('[data-test-subj="visualizationLoader"]').length).to.be(1);
- });
-
- it('should not mutate vis.params', () => {
- const container = newContainer();
- const savedObject = createSavedObject();
- const paramsBefore = cloneDeep(vis.params);
- loader.embedVisualizationWithSavedObject(container[0], savedObject, {});
- const paramsAfter = cloneDeep(vis.params);
- expect(paramsBefore).to.eql(paramsAfter);
- });
-
- it('should replace content of container by default', () => {
- const container = angular.element('');
- loader.embedVisualizationWithSavedObject(container[0], createSavedObject(), {});
- expect(container.find('#prevContent').length).to.be(0);
- });
-
- it('should append content to container when using append parameter', () => {
- const container = angular.element('');
- loader.embedVisualizationWithSavedObject(container[0], createSavedObject(), {
- append: true
- });
- expect(container.children().length).to.be(2);
- expect(container.find('#prevContent').length).to.be(1);
- });
-
- it('should apply css classes from parameters', () => {
- const vis = embedWithParams({ cssClass: 'my-css-class another-class' });
- expect(vis.hasClass('my-css-class')).to.be(true);
- expect(vis.hasClass('another-class')).to.be(true);
- });
-
- it('should apply data attributes from dataAttrs parameter', () => {
- const vis = embedWithParams({
- dataAttrs: {
- 'foo': '',
- 'with-dash': 'value',
- }
- });
- expect(vis.attr('data-foo')).to.be('');
- expect(vis.attr('data-with-dash')).to.be('value');
- });
- });
-
- describe('embedVisualizationWithId', () => {
-
- it('should be a function', async () => {
- expect(loader.embedVisualizationWithId).to.be.a('function');
- });
-
- it('should reject if the id was not found', () => {
- const resolveSpy = sinon.spy();
- const rejectSpy = sinon.spy();
- const container = newContainer();
- return loader.embedVisualizationWithId(container[0], 'not-existing', {})
- .then(resolveSpy, rejectSpy)
- .then(() => {
- expect(resolveSpy.called).to.be(false);
- expect(rejectSpy.calledOnce).to.be(true);
- });
- });
-
- it('should render a visualize element, if id was found', async () => {
- const container = newContainer();
- await loader.embedVisualizationWithId(container[0], 'exists', {});
- expect(container.find('[data-test-subj="visualizationLoader"]').length).to.be(1);
- });
-
- });
-
- describe('EmbeddedVisualizeHandler', () => {
- it('should be returned from embedVisualizationWithId via a promise', async () => {
- const handler = await loader.embedVisualizationWithId(newContainer()[0], 'exists', {});
- expect(handler instanceof EmbeddedVisualizeHandler).to.be(true);
- });
-
- it('should be returned from embedVisualizationWithSavedObject', async () => {
- const handler = loader.embedVisualizationWithSavedObject(newContainer()[0], createSavedObject(), {});
- expect(handler instanceof EmbeddedVisualizeHandler).to.be(true);
- });
-
- it('should give access to the visualize element', () => {
- const container = newContainer();
- const handler = loader.embedVisualizationWithSavedObject(container[0], createSavedObject(), {});
- expect(handler.getElement()).to.be(container.find('[data-test-subj="visualizationLoader"]')[0]);
- });
-
- it('should allow opening the inspector of the visualization and return its session', () => {
- const handler = loader.embedVisualizationWithSavedObject(newContainer()[0], createSavedObject(), {});
- sandbox.spy(Inspector, 'open');
- const inspectorSession = handler.openInspector();
- expect(Inspector.open.calledOnce).to.be(true);
- expect(inspectorSession.close).to.be.a('function');
- inspectorSession.close();
- });
-
- describe('inspector', () => {
-
- describe('hasInspector()', () => {
- it('should forward to inspectors hasInspector', () => {
- const handler = loader.embedVisualizationWithSavedObject(newContainer()[0], createSavedObject(), {});
- sinon.spy(Inspector, 'isAvailable');
- handler.hasInspector();
- expect(Inspector.isAvailable.calledOnce).to.be(true);
- const adapters = Inspector.isAvailable.lastCall.args[0];
- expect(adapters.data).to.be.a(DataAdapter);
- expect(adapters.requests).to.be.a(RequestAdapter);
- });
-
- it('should return hasInspectors result', () => {
- const handler = loader.embedVisualizationWithSavedObject(newContainer()[0], createSavedObject(), {});
- const stub = sinon.stub(Inspector, 'isAvailable');
- stub.returns(true);
- expect(handler.hasInspector()).to.be(true);
- stub.returns(false);
- expect(handler.hasInspector()).to.be(false);
- });
-
- afterEach(() => {
- Inspector.isAvailable.restore();
- });
- });
-
- describe('openInspector()', () => {
-
- beforeEach(() => {
- sinon.stub(Inspector, 'open');
- });
-
- it('should call openInspector with all attached inspectors', () => {
- const handler = loader.embedVisualizationWithSavedObject(newContainer()[0], createSavedObject(), {});
- handler.openInspector();
- expect(Inspector.open.calledOnce).to.be(true);
- const adapters = Inspector.open.lastCall.args[0];
- expect(adapters).to.be(handler.inspectorAdapters);
- });
-
- it('should pass the vis title to the openInspector call', () => {
- const handler = loader.embedVisualizationWithSavedObject(newContainer()[0], createSavedObject(), {});
- handler.openInspector();
- expect(Inspector.open.calledOnce).to.be(true);
- const params = Inspector.open.lastCall.args[1];
- expect(params.title).to.be('testVis');
- });
-
- afterEach(() => {
- Inspector.open.restore();
- });
- });
-
- describe('inspectorAdapters', () => {
-
- it('should register none for none requestHandler', () => {
- const savedObj = createSavedObject();
- savedObj.vis.type.requestHandler = 'none';
- const handler = loader.embedVisualizationWithSavedObject(newContainer()[0], savedObj, {});
- expect(handler.inspectorAdapters).to.eql({});
- });
-
- it('should attach data and request handler for courier', () => {
- const handler = loader.embedVisualizationWithSavedObject(newContainer()[0], createSavedObject(), {});
- expect(handler.inspectorAdapters.data).to.be.a(DataAdapter);
- expect(handler.inspectorAdapters.requests).to.be.a(RequestAdapter);
- });
-
- it('should allow enabling data adapter manually', () => {
- const handler = loader.embedVisualizationWithSavedObject(newContainer()[0], createSavedObject(), {});
- expect(handler.inspectorAdapters.data).to.be.a(DataAdapter);
- });
-
- it('should allow enabling requests adapter manually', () => {
- const handler = loader.embedVisualizationWithSavedObject(newContainer()[0], createSavedObject(), {});
- expect(handler.inspectorAdapters.requests).to.be.a(RequestAdapter);
- });
-
- it('should allow adding custom inspector adapters via the custom key', () => {
- const Foodapter = class { };
- const Bardapter = class { };
- const savedObj = createSavedObject();
- savedObj.vis.type.inspectorAdapters = {
- custom: { foo: Foodapter, bar: Bardapter }
- };
- const handler = loader.embedVisualizationWithSavedObject(newContainer()[0], savedObj, {});
- expect(handler.inspectorAdapters.foo).to.be.a(Foodapter);
- expect(handler.inspectorAdapters.bar).to.be.a(Bardapter);
- });
-
- it('should not share adapter instances between vis instances', () => {
- const Foodapter = class { };
- const savedObj1 = createSavedObject();
- const savedObj2 = createSavedObject();
- savedObj1.vis.type.inspectorAdapters = { custom: { foo: Foodapter } };
- savedObj2.vis.type.inspectorAdapters = { custom: { foo: Foodapter } };
- const handler1 = loader.embedVisualizationWithSavedObject(newContainer()[0], savedObj1, {});
- const handler2 = loader.embedVisualizationWithSavedObject(newContainer()[0], savedObj2, {});
- expect(handler1.inspectorAdapters.foo).to.be.a(Foodapter);
- expect(handler2.inspectorAdapters.foo).to.be.a(Foodapter);
- expect(handler1.inspectorAdapters.foo).not.to.be(handler2.inspectorAdapters.foo);
- expect(handler1.inspectorAdapters.data).to.be.a(DataAdapter);
- expect(handler2.inspectorAdapters.data).to.be.a(DataAdapter);
- expect(handler1.inspectorAdapters.data).not.to.be(handler2.inspectorAdapters.data);
- });
- });
-
- });
-
- it('should have whenFirstRenderComplete returns a promise resolving on first renderComplete event', async () => {
- const container = newContainer();
- const handler = loader.embedVisualizationWithSavedObject(container[0], createSavedObject(), {});
- const spy = sinon.spy();
- handler.whenFirstRenderComplete().then(spy);
- expect(spy.notCalled).to.be(true);
- dispatchRenderComplete(container.find('[data-test-subj="visualizationLoader"]')[0]);
- await timeout();
- expect(spy.calledOnce).to.be(true);
- });
-
- it('should add listeners via addRenderCompleteListener that triggers on renderComplete events', async () => {
- const container = newContainer();
- const handler = loader.embedVisualizationWithSavedObject(container[0], createSavedObject(), {});
- const spy = sinon.spy();
- handler.addRenderCompleteListener(spy);
- expect(spy.notCalled).to.be(true);
- dispatchRenderComplete(container.find('[data-test-subj="visualizationLoader"]')[0]);
- await timeout();
- expect(spy.calledOnce).to.be(true);
- });
-
- it('should call render complete listeners once per renderComplete event', async () => {
- const container = newContainer();
- const handler = loader.embedVisualizationWithSavedObject(container[0], createSavedObject(), {});
- const spy = sinon.spy();
- handler.addRenderCompleteListener(spy);
- expect(spy.notCalled).to.be(true);
- dispatchRenderComplete(container.find('[data-test-subj="visualizationLoader"]')[0]);
- dispatchRenderComplete(container.find('[data-test-subj="visualizationLoader"]')[0]);
- dispatchRenderComplete(container.find('[data-test-subj="visualizationLoader"]')[0]);
- expect(spy.callCount).to.be(3);
- });
-
- it('should successfully remove listeners from render complete', async () => {
- const container = newContainer();
- const handler = loader.embedVisualizationWithSavedObject(container[0], createSavedObject(), {});
- const spy = sinon.spy();
- handler.addRenderCompleteListener(spy);
- expect(spy.notCalled).to.be(true);
- dispatchRenderComplete(container.find('[data-test-subj="visualizationLoader"]')[0]);
- expect(spy.calledOnce).to.be(true);
- spy.resetHistory();
- handler.removeRenderCompleteListener(spy);
- dispatchRenderComplete(container.find('[data-test-subj="visualizationLoader"]')[0]);
- expect(spy.notCalled).to.be(true);
- });
-
-
- it('should allow updating and deleting data attributes', () => {
- const container = newContainer();
- const handler = loader.embedVisualizationWithSavedObject(container[0], createSavedObject(), {
- dataAttrs: {
- foo: 42
- }
- });
- expect(container.find('[data-test-subj="visualizationLoader"]').attr('data-foo')).to.be('42');
- handler.update({
- dataAttrs: {
- foo: null,
- added: 'value',
- }
- });
- expect(container.find('[data-test-subj="visualizationLoader"]')[0].hasAttribute('data-foo')).to.be(false);
- expect(container.find('[data-test-subj="visualizationLoader"]').attr('data-added')).to.be('value');
- });
-
- it('should allow updating the time range of the visualization', async () => {
- const spy = sandbox.spy(DataLoader.prototype, 'fetch');
-
- const handler = loader.embedVisualizationWithSavedObject(newContainer()[0], createSavedObject(), {
- timeRange: { from: 'now-7d', to: 'now' }
- });
-
- // Wait for the initial fetch and render to happen
- await timeout(150);
- spy.resetHistory();
-
- handler.update({
- timeRange: { from: 'now-10d/d', to: 'now' }
- });
-
- // Wait for fetch debounce to happen (as soon as we use lodash 4+ we could use fake timers here for the debounce)
- await timeout(150);
-
- sinon.assert.calledOnce(spy);
- sinon.assert.calledWith(spy, sinon.match({ timeRange: { from: 'now-10d/d', to: 'now' } }));
- });
-
- it('should not set forceFetch on uiState change', async () => {
- const spy = sandbox.spy(DataLoader.prototype, 'fetch');
-
- const uiState = new PersistedState();
- loader.embedVisualizationWithSavedObject(newContainer()[0], createSavedObject(), {
- timeRange: { from: 'now-7d', to: 'now' },
- uiState: uiState,
- });
-
- // Wait for the initial fetch and render to happen
- await timeout(150);
- spy.resetHistory();
-
- uiState.set('property', 'value');
-
- // Wait for fetch debounce to happen (as soon as we use lodash 4+ we could use fake timers here for the debounce)
- await timeout(150);
-
- sinon.assert.calledOnce(spy);
- sinon.assert.calledWith(spy, sinon.match({ forceFetch: false }));
- });
- });
-
- });
-});
diff --git a/src/legacy/ui/public/visualize/loader/embedded_visualize_handler.test.mocks.ts b/src/legacy/ui/public/visualize/loader/embedded_visualize_handler.test.mocks.ts
deleted file mode 100644
index 4ca90d6c6b61b..0000000000000
--- a/src/legacy/ui/public/visualize/loader/embedded_visualize_handler.test.mocks.ts
+++ /dev/null
@@ -1,73 +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.
- */
-
-jest.useFakeTimers();
-
-import { Subject } from 'rxjs';
-
-jest.mock('ui/notify', () => ({
- toastNotifications: jest.fn(),
-}));
-
-jest.mock('./utils', () => ({
- queryGeohashBounds: jest.fn(),
-}));
-
-jest.mock('./pipeline_helpers/utilities', () => ({
- getFormat: jest.fn(),
- getTableAggs: jest.fn(),
-}));
-
-const autoRefreshFetchSub = new Subject();
-
-export const timefilter = {
- _triggerAutoRefresh: () => {
- autoRefreshFetchSub.next();
- },
- getAutoRefreshFetch$: () => {
- return autoRefreshFetchSub.asObservable();
- },
-};
-jest.doMock('../../timefilter', () => ({ timefilter }));
-
-jest.mock('../../inspector', () => ({
- Inspector: {
- open: jest.fn(),
- isAvailable: jest.fn(),
- },
-}));
-
-export const mockDataLoaderFetch = jest.fn().mockReturnValue({
- as: 'visualization',
- value: {
- visType: 'histogram',
- visData: {},
- visConfig: {},
- params: {},
- },
-});
-const MockDataLoader = class {
- public async fetch(data: any) {
- return await mockDataLoaderFetch(data);
- }
-};
-
-jest.mock('./pipeline_data_loader', () => ({
- PipelineDataLoader: MockDataLoader,
-}));
diff --git a/src/legacy/ui/public/visualize/loader/embedded_visualize_handler.test.ts b/src/legacy/ui/public/visualize/loader/embedded_visualize_handler.test.ts
deleted file mode 100644
index c73f787457a03..0000000000000
--- a/src/legacy/ui/public/visualize/loader/embedded_visualize_handler.test.ts
+++ /dev/null
@@ -1,299 +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.
- */
-jest.mock('ui/new_platform');
-
-import { searchSourceMock } from '../../courier/search_source/mocks';
-import { mockDataLoaderFetch, timefilter } from './embedded_visualize_handler.test.mocks';
-
-import _ from 'lodash';
-// @ts-ignore
-import MockState from '../../../../../fixtures/mock_state';
-import { Vis } from '../../vis';
-import { VisResponseData } from './types';
-import { Inspector } from '../../inspector';
-import { EmbeddedVisualizeHandler, RequestHandlerParams } from './embedded_visualize_handler';
-import { AggConfigs } from 'ui/agg_types/agg_configs';
-
-jest.mock('plugins/interpreter/interpreter', () => ({
- getInterpreter: () => {
- return Promise.resolve();
- },
-}));
-
-jest.mock('../../../../core_plugins/interpreter/public/registries', () => ({
- registries: {
- renderers: {
- get: (name: string) => {
- return {
- render: async () => {
- return {};
- },
- };
- },
- },
- },
-}));
-
-describe('EmbeddedVisualizeHandler', () => {
- let handler: any;
- let div: HTMLElement;
- let dataLoaderParams: RequestHandlerParams;
- const mockVis: Vis = {
- title: 'My Vis',
- // @ts-ignore
- type: 'foo',
- getAggConfig: () => [],
- _setUiState: () => ({}),
- getUiState: () => new MockState(),
- on: () => ({}),
- off: () => ({}),
- removeListener: jest.fn(),
- API: {},
- };
-
- beforeEach(() => {
- jest.clearAllMocks();
-
- jest.spyOn(_, 'debounce').mockImplementation(
- // @ts-ignore
- (f: Function) => {
- // @ts-ignore
- f.cancel = () => {};
- return f;
- }
- );
-
- dataLoaderParams = {
- aggs: ([] as any) as AggConfigs,
- filters: undefined,
- forceFetch: false,
- inspectorAdapters: {},
- query: undefined,
- queryFilter: null,
- searchSource: searchSourceMock,
- timeRange: undefined,
- uiState: undefined,
- };
-
- div = document.createElement('div');
- handler = new EmbeddedVisualizeHandler(
- div,
- {
- vis: mockVis,
- title: 'My Vis',
- searchSource: searchSourceMock,
- destroy: () => ({}),
- copyOnSave: false,
- save: () => Promise.resolve('123'),
- },
- {
- autoFetch: true,
- Private: (provider: () => T) => provider(),
- queryFilter: null,
- }
- );
- });
-
- afterEach(() => {
- handler.destroy();
- });
-
- describe('autoFetch', () => {
- it('should trigger a reload when autoFetch=true and auto refresh happens', () => {
- const spy = jest.spyOn(handler, 'fetchAndRender');
- timefilter._triggerAutoRefresh();
- jest.runAllTimers();
- expect(spy).toHaveBeenCalledTimes(1);
- expect(spy).toHaveBeenCalledWith(true);
- });
-
- it('should not trigger a reload when autoFetch=false and auto refresh happens', () => {
- handler = new EmbeddedVisualizeHandler(
- div,
- {
- vis: mockVis,
- title: 'My Vis',
- searchSource: searchSourceMock,
- destroy: () => ({}),
- copyOnSave: false,
- save: () => Promise.resolve('123'),
- },
- {
- autoFetch: false,
- Private: (provider: () => T) => provider(),
- queryFilter: null,
- }
- );
- const spy = jest.spyOn(handler, 'fetchAndRender');
- timefilter._triggerAutoRefresh();
- jest.runAllTimers();
- expect(spy).not.toHaveBeenCalled();
- });
- });
-
- describe('getElement', () => {
- it('should return the provided html element', () => {
- expect(handler.getElement()).toBe(div);
- });
- });
-
- describe('update', () => {
- it('should add provided data- attributes to the html element', () => {
- const spy = jest.spyOn(handler, 'fetchAndRender');
- const params = {
- dataAttrs: { foo: 'bar' },
- };
- handler.update(params);
- expect(spy).not.toHaveBeenCalled();
- expect(handler.getElement()).toMatchSnapshot();
- });
-
- it('should remove null data- attributes from the html element', () => {
- const spy = jest.spyOn(handler, 'fetchAndRender');
- handler.update({
- dataAttrs: { foo: 'bar' },
- });
- const params = {
- dataAttrs: {
- foo: null,
- baz: 'qux',
- },
- };
- handler.update(params);
- expect(spy).not.toHaveBeenCalled();
- expect(handler.getElement()).toMatchSnapshot();
- });
-
- it('should call dataLoader.render with updated timeRange', () => {
- const params = { timeRange: { foo: 'bar' } };
- handler.update(params);
- expect(mockDataLoaderFetch).toHaveBeenCalled();
- const callIndex = mockDataLoaderFetch.mock.calls.length - 1;
- const { abortSignal, ...otherParams } = mockDataLoaderFetch.mock.calls[callIndex][0];
- expect(abortSignal).toBeInstanceOf(AbortSignal);
- expect(otherParams).toEqual({ ...dataLoaderParams, ...params });
- });
-
- it('should call dataLoader.render with updated filters', () => {
- const params = { filters: [{ meta: { disabled: false } }] };
- handler.update(params);
- expect(mockDataLoaderFetch).toHaveBeenCalled();
- const callIndex = mockDataLoaderFetch.mock.calls.length - 1;
- const { abortSignal, ...otherParams } = mockDataLoaderFetch.mock.calls[callIndex][0];
- expect(abortSignal).toBeInstanceOf(AbortSignal);
- expect(otherParams).toEqual({ ...dataLoaderParams, ...params });
- });
-
- it('should call dataLoader.render with updated query', () => {
- const params = { query: { foo: 'bar' } };
- handler.update(params);
- expect(mockDataLoaderFetch).toHaveBeenCalled();
- const callIndex = mockDataLoaderFetch.mock.calls.length - 1;
- const { abortSignal, ...otherParams } = mockDataLoaderFetch.mock.calls[callIndex][0];
- expect(abortSignal).toBeInstanceOf(AbortSignal);
- expect(otherParams).toEqual({ ...dataLoaderParams, ...params });
- });
- });
-
- describe('destroy', () => {
- it('should remove vis event listeners', () => {
- const spy = jest.spyOn(mockVis, 'removeListener');
- handler.destroy();
- expect(spy).toHaveBeenCalledTimes(2);
- expect(spy.mock.calls[0][0]).toBe('reload');
- expect(spy.mock.calls[1][0]).toBe('update');
- });
-
- it('should remove element event listeners', () => {
- const spy = jest.spyOn(handler.getElement(), 'removeEventListener');
- handler.destroy();
- expect(spy).toHaveBeenCalled();
- });
-
- it('should prevent subsequent renders', () => {
- const spy = jest.spyOn(handler, 'fetchAndRender');
- handler.destroy();
- expect(spy).not.toHaveBeenCalled();
- });
-
- it('should cancel debounced fetchAndRender', () => {
- const spy = jest.spyOn(handler.debouncedFetchAndRender, 'cancel');
- handler.destroy();
- expect(spy).toHaveBeenCalledTimes(1);
- });
-
- it('should call abort on controller', () => {
- handler.abortController = new AbortController();
- const spy = jest.spyOn(handler.abortController, 'abort');
- handler.destroy();
- expect(spy).toHaveBeenCalled();
- });
- });
-
- describe('openInspector', () => {
- it('calls Inspector.open()', () => {
- handler.openInspector();
- expect(Inspector.open).toHaveBeenCalledTimes(1);
- expect(Inspector.open).toHaveBeenCalledWith({}, { title: 'My Vis' });
- });
- });
-
- describe('hasInspector', () => {
- it('calls Inspector.isAvailable()', () => {
- handler.hasInspector();
- expect(Inspector.isAvailable).toHaveBeenCalledTimes(1);
- expect(Inspector.isAvailable).toHaveBeenCalledWith({});
- });
- });
-
- describe('reload', () => {
- it('should force fetch and render', () => {
- const spy = jest.spyOn(handler, 'fetchAndRender');
- handler.reload();
- expect(spy).toHaveBeenCalledTimes(1);
- expect(spy).toHaveBeenCalledWith(true);
- });
- });
-
- describe('data$', () => {
- it('observable can be used to get response data in the correct format', async () => {
- let response;
- handler.data$.subscribe((data: VisResponseData) => (response = data));
- await handler.fetch(true);
- jest.runAllTimers();
- expect(response).toMatchSnapshot();
- });
- });
-
- describe('render', () => {
- // TODO
- });
-
- describe('whenFirstRenderComplete', () => {
- // TODO
- });
-
- describe('addRenderCompleteListener', () => {
- // TODO
- });
-
- describe('removeRenderCompleteListener', () => {
- // TODO
- });
-});
diff --git a/src/legacy/ui/public/visualize/loader/embedded_visualize_handler.ts b/src/legacy/ui/public/visualize/loader/embedded_visualize_handler.ts
deleted file mode 100644
index fb16e095b3418..0000000000000
--- a/src/legacy/ui/public/visualize/loader/embedded_visualize_handler.ts
+++ /dev/null
@@ -1,553 +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 { EventEmitter } from 'events';
-import { debounce, forEach, get, isEqual } from 'lodash';
-import * as Rx from 'rxjs';
-import { share } from 'rxjs/operators';
-import { i18n } from '@kbn/i18n';
-import { toastNotifications } from 'ui/notify';
-// @ts-ignore untyped dependency
-import { AggConfigs } from 'ui/agg_types/agg_configs';
-import { SearchSource } from 'ui/courier';
-import { QueryFilter } from 'ui/filter_manager/query_filter';
-
-import { TimeRange, onlyDisabledFiltersChanged } from '../../../../../plugins/data/public';
-import { registries } from '../../../../core_plugins/interpreter/public/registries';
-import { Inspector } from '../../inspector';
-import { Adapters } from '../../inspector/types';
-import { PersistedState } from '../../persisted_state';
-import { IPrivate } from '../../private';
-import { RenderCompleteHelper } from '../../../../../plugins/kibana_utils/public';
-import { AppState } from '../../state_management/app_state';
-import { timefilter } from '../../timefilter';
-import { Vis } from '../../vis';
-// @ts-ignore untyped dependency
-import { VisFiltersProvider } from '../../vis/vis_filters';
-import { PipelineDataLoader } from './pipeline_data_loader';
-import { visualizationLoader } from './visualization_loader';
-import { Query } from '../../../../core_plugins/data/public';
-import { esFilters } from '../../../../../plugins/data/public';
-
-import { DataAdapter, RequestAdapter } from '../../inspector/adapters';
-
-import { getTableAggs } from './pipeline_helpers/utilities';
-import {
- VisResponseData,
- VisSavedObject,
- VisualizeLoaderParams,
- VisualizeUpdateParams,
-} from './types';
-import { queryGeohashBounds } from './utils';
-
-interface EmbeddedVisualizeHandlerParams extends VisualizeLoaderParams {
- Private: IPrivate;
- queryFilter: any;
- autoFetch?: boolean;
-}
-
-export interface RequestHandlerParams {
- searchSource: SearchSource;
- aggs: AggConfigs;
- timeRange?: TimeRange;
- query?: Query;
- filters?: esFilters.Filter[];
- forceFetch: boolean;
- queryFilter: QueryFilter;
- uiState?: PersistedState;
- partialRows?: boolean;
- inspectorAdapters: Adapters;
- metricsAtAllLevels?: boolean;
- visParams?: any;
- abortSignal?: AbortSignal;
-}
-
-const RENDER_COMPLETE_EVENT = 'render_complete';
-const DATA_SHARED_ITEM = 'data-shared-item';
-const LOADING_ATTRIBUTE = 'data-loading';
-const RENDERING_COUNT_ATTRIBUTE = 'data-rendering-count';
-
-/**
- * A handler to the embedded visualization. It offers several methods to interact
- * with the visualization.
- */
-export class EmbeddedVisualizeHandler {
- /**
- * This observable will emit every time new data is loaded for the
- * visualization. The emitted value is the loaded data after it has
- * been transformed by the visualization's response handler.
- * This should not be used by any plugin.
- * @ignore
- */
- public readonly data$: Rx.Observable;
- public readonly inspectorAdapters: Adapters = {};
- private vis: Vis;
- private handlers: any;
- private loaded: boolean = false;
- private destroyed: boolean = false;
-
- private listeners = new EventEmitter();
- private firstRenderComplete: Promise;
- private renderCompleteHelper: RenderCompleteHelper;
- private shouldForceNextFetch: boolean = false;
- private debouncedFetchAndRender = debounce(() => {
- if (this.destroyed) {
- return;
- }
-
- const forceFetch = this.shouldForceNextFetch;
- this.shouldForceNextFetch = false;
- this.fetch(forceFetch).then(this.render);
- }, 100);
-
- private dataLoaderParams: RequestHandlerParams;
- private readonly appState?: AppState;
- private uiState: PersistedState;
- private dataLoader: PipelineDataLoader;
- private dataSubject: Rx.Subject;
- private actions: any = {};
- private events$: Rx.Observable;
- private autoFetch: boolean;
- private abortController?: AbortController;
- private autoRefreshFetchSubscription: Rx.Subscription | undefined;
-
- constructor(
- private readonly element: HTMLElement,
- savedObject: VisSavedObject,
- params: EmbeddedVisualizeHandlerParams
- ) {
- const { searchSource, vis } = savedObject;
-
- const {
- appState,
- uiState,
- queryFilter,
- timeRange,
- filters,
- query,
- autoFetch = true,
- Private,
- } = params;
-
- this.dataLoaderParams = {
- searchSource,
- timeRange,
- query,
- queryFilter,
- filters,
- uiState,
- aggs: vis.getAggConfig(),
- forceFetch: false,
- inspectorAdapters: this.inspectorAdapters,
- };
-
- // Listen to the first RENDER_COMPLETE_EVENT to resolve this promise
- this.firstRenderComplete = new Promise(resolve => {
- this.listeners.once(RENDER_COMPLETE_EVENT, resolve);
- });
-
- element.setAttribute(LOADING_ATTRIBUTE, '');
- element.setAttribute(DATA_SHARED_ITEM, '');
- element.setAttribute(RENDERING_COUNT_ATTRIBUTE, '0');
-
- element.addEventListener('renderComplete', this.onRenderCompleteListener);
-
- this.autoFetch = autoFetch;
- this.appState = appState;
- this.vis = vis;
- if (uiState) {
- vis._setUiState(uiState);
- }
- this.uiState = this.vis.getUiState();
-
- this.handlers = {
- vis: this.vis,
- uiState: this.uiState,
- onDestroy: (fn: () => never) => (this.handlers.destroyFn = fn),
- };
-
- this.vis.on('update', this.handleVisUpdate);
- this.vis.on('reload', this.reload);
- this.uiState.on('change', this.onUiStateChange);
- if (autoFetch) {
- this.autoRefreshFetchSubscription = timefilter.getAutoRefreshFetch$().subscribe(this.reload);
- }
-
- // This is a hack to give maps visualizations access to data in the
- // globalState, since they can no longer access it via searchSource.
- // TODO: Remove this as a part of elastic/kibana#30593
- this.vis.API.getGeohashBounds = () => {
- return queryGeohashBounds(this.vis, {
- filters: this.dataLoaderParams.filters,
- query: this.dataLoaderParams.query,
- });
- };
-
- this.dataLoader = new PipelineDataLoader(vis);
- const visFilters: any = Private(VisFiltersProvider);
- this.renderCompleteHelper = new RenderCompleteHelper(element);
- this.inspectorAdapters = this.getActiveInspectorAdapters();
- this.vis.openInspector = this.openInspector;
- this.vis.hasInspector = this.hasInspector;
-
- // init default actions
- forEach(this.vis.type.events, (event, eventName) => {
- if (event.disabled || !eventName) {
- return;
- } else {
- this.actions[eventName] = event.defaultAction;
- }
- });
-
- this.handlers.eventsSubject = new Rx.Subject();
- this.vis.eventsSubject = this.handlers.eventsSubject;
- this.events$ = this.handlers.eventsSubject.asObservable().pipe(share());
- this.events$.subscribe(event => {
- if (this.actions[event.name]) {
- event.data.aggConfigs = getTableAggs(this.vis);
- const newFilters = this.actions[event.name](event.data) || [];
- if (event.name === 'brush') {
- const fieldName = newFilters[0].meta.key;
- const $state = this.vis.API.getAppState();
- const existingFilter = $state.filters.find(
- (filter: any) => filter.meta && filter.meta.key === fieldName
- );
- if (existingFilter) {
- Object.assign(existingFilter, newFilters[0]);
- }
- }
- visFilters.pushFilters(newFilters);
- }
- });
-
- this.dataSubject = new Rx.Subject();
- this.data$ = this.dataSubject.asObservable().pipe(share());
-
- this.render();
- }
-
- /**
- * Update properties of the embedded visualization. This method does not allow
- * updating all initial parameters, but only a subset of the ones allowed
- * in {@link VisualizeUpdateParams}.
- *
- * @param params The parameters that should be updated.
- */
- public update(params: VisualizeUpdateParams = {}) {
- // Apply data- attributes to the element if specified
- const dataAttrs = params.dataAttrs;
- if (dataAttrs) {
- Object.keys(dataAttrs).forEach(key => {
- if (dataAttrs[key] === null) {
- this.element.removeAttribute(`data-${key}`);
- return;
- }
-
- this.element.setAttribute(`data-${key}`, dataAttrs[key]);
- });
- }
-
- let fetchRequired = false;
- if (
- params.hasOwnProperty('timeRange') &&
- !isEqual(this.dataLoaderParams.timeRange, params.timeRange)
- ) {
- fetchRequired = true;
- this.dataLoaderParams.timeRange = params.timeRange;
- }
- if (
- params.hasOwnProperty('filters') &&
- !onlyDisabledFiltersChanged(this.dataLoaderParams.filters, params.filters)
- ) {
- fetchRequired = true;
- this.dataLoaderParams.filters = params.filters;
- }
- if (params.hasOwnProperty('query') && !isEqual(this.dataLoaderParams.query, params.query)) {
- fetchRequired = true;
- this.dataLoaderParams.query = params.query;
- }
-
- if (fetchRequired) {
- this.fetchAndRender();
- }
- }
-
- /**
- * Destroy the underlying Angular scope of the visualization. This should be
- * called whenever you remove the visualization.
- */
- public destroy(): void {
- this.destroyed = true;
- this.cancel();
- this.debouncedFetchAndRender.cancel();
- if (this.autoFetch) {
- if (this.autoRefreshFetchSubscription) this.autoRefreshFetchSubscription.unsubscribe();
- }
- this.vis.removeListener('reload', this.reload);
- this.vis.removeListener('update', this.handleVisUpdate);
- this.element.removeEventListener('renderComplete', this.onRenderCompleteListener);
- this.uiState.off('change', this.onUiStateChange);
- visualizationLoader.destroy(this.element);
- this.renderCompleteHelper.destroy();
- if (this.handlers.destroyFn) {
- this.handlers.destroyFn();
- }
- }
-
- /**
- * Return the actual DOM element (wrapped in jQuery) of the rendered visualization.
- * This is especially useful if you used `append: true` in the parameters where
- * the visualization will be appended to the specified container.
- */
- public getElement(): HTMLElement {
- return this.element;
- }
-
- /**
- * renders visualization with provided data
- * @param response: visualization data
- */
- public render = (response: VisResponseData | null = null): void => {
- const executeRenderer = this.rendererProvider(response);
- if (!executeRenderer) {
- return;
- }
-
- // TODO: we have this weird situation when we need to render first,
- // and then we call fetch and render... we need to get rid of that.
- executeRenderer().then(() => {
- if (!this.loaded) {
- this.loaded = true;
- if (this.autoFetch) {
- this.fetchAndRender();
- }
- }
- });
- };
-
- /**
- * Opens the inspector for the embedded visualization. This will return an
- * handler to the inspector to close and interact with it.
- * @return An inspector session to interact with the opened inspector.
- */
- public openInspector = () => {
- return Inspector.open(this.inspectorAdapters, {
- title: this.vis.title,
- });
- };
-
- public hasInspector = () => {
- return Inspector.isAvailable(this.inspectorAdapters);
- };
-
- /**
- * Returns a promise, that will resolve (without a value) once the first rendering of
- * the visualization has finished. If you want to listen to consecutive rendering
- * events, look into the `addRenderCompleteListener` method.
- *
- * @returns Promise, that resolves as soon as the visualization is done rendering
- * for the first time.
- */
- public whenFirstRenderComplete(): Promise {
- return this.firstRenderComplete;
- }
-
- /**
- * Adds a listener to be called whenever the visualization finished rendering.
- * This can be called multiple times, when the visualization rerenders, e.g. due
- * to new data.
- *
- * @param {function} listener The listener to be notified about complete renders.
- */
- public addRenderCompleteListener(listener: () => void) {
- this.listeners.addListener(RENDER_COMPLETE_EVENT, listener);
- }
-
- /**
- * Removes a previously registered render complete listener from this handler.
- * This listener will no longer be called when the visualization finished rendering.
- *
- * @param {function} listener The listener to remove from this handler.
- */
- public removeRenderCompleteListener(listener: () => void) {
- this.listeners.removeListener(RENDER_COMPLETE_EVENT, listener);
- }
-
- /**
- * Force the fetch of new data and renders the chart again.
- */
- public reload = () => {
- this.fetchAndRender(true);
- };
-
- private incrementRenderingCount = () => {
- const renderingCount = Number(this.element.getAttribute(RENDERING_COUNT_ATTRIBUTE) || 0);
- this.element.setAttribute(RENDERING_COUNT_ATTRIBUTE, `${renderingCount + 1}`);
- };
-
- private onRenderCompleteListener = () => {
- this.listeners.emit(RENDER_COMPLETE_EVENT);
- this.element.removeAttribute(LOADING_ATTRIBUTE);
- this.incrementRenderingCount();
- };
-
- private onUiStateChange = () => {
- this.fetchAndRender();
- };
-
- /**
- * Returns an object of all inspectors for this vis object.
- * This must only be called after this.type has properly be initialized,
- * since we need to read out data from the the vis type to check which
- * inspectors are available.
- */
- private getActiveInspectorAdapters = (): Adapters => {
- const adapters: Adapters = {};
- const { inspectorAdapters: typeAdapters } = this.vis.type;
-
- // Add the requests inspector adapters if the vis type explicitly requested it via
- // inspectorAdapters.requests: true in its definition or if it's using the courier
- // request handler, since that will automatically log its requests.
- if ((typeAdapters && typeAdapters.requests) || this.vis.type.requestHandler === 'courier') {
- adapters.requests = new RequestAdapter();
- }
-
- // Add the data inspector adapter if the vis type requested it or if the
- // vis is using courier, since we know that courier supports logging
- // its data.
- if ((typeAdapters && typeAdapters.data) || this.vis.type.requestHandler === 'courier') {
- adapters.data = new DataAdapter();
- }
-
- // Add all inspectors, that are explicitly registered with this vis type
- if (typeAdapters && typeAdapters.custom) {
- Object.entries(typeAdapters.custom).forEach(([key, Adapter]) => {
- adapters[key] = new (Adapter as any)();
- });
- }
-
- return adapters;
- };
-
- /**
- * Fetches new data and renders the chart. This will happen debounced for a couple
- * of milliseconds, to bundle fast successive calls into one fetch and render,
- * e.g. while resizing the window, this will be triggered constantly on the resize
- * event.
- *
- * @param forceFetch=false Whether the request handler should be signaled to forceFetch
- * (i.e. ignore caching in case it supports it). If at least one call to this
- * passed `true` the debounced fetch and render will be a force fetch.
- */
- private fetchAndRender = (forceFetch = false): void => {
- this.shouldForceNextFetch = forceFetch || this.shouldForceNextFetch;
- this.element.setAttribute(LOADING_ATTRIBUTE, '');
- this.debouncedFetchAndRender();
- };
-
- private handleVisUpdate = () => {
- if (this.appState) {
- this.appState.vis = this.vis.getState();
- this.appState.save();
- }
-
- this.fetchAndRender();
- };
-
- private cancel = () => {
- if (this.abortController) this.abortController.abort();
- };
-
- private fetch = (forceFetch: boolean = false) => {
- this.cancel();
- this.abortController = new AbortController();
- this.dataLoaderParams.abortSignal = this.abortController.signal;
- this.dataLoaderParams.aggs = this.vis.getAggConfig();
- this.dataLoaderParams.forceFetch = forceFetch;
- this.dataLoaderParams.inspectorAdapters = this.inspectorAdapters;
-
- this.vis.filters = { timeRange: this.dataLoaderParams.timeRange };
- this.vis.requestError = undefined;
- this.vis.showRequestError = false;
-
- return (
- this.dataLoader
- // Don't pass in this.dataLoaderParams directly because it may be modified async in another
- // call to fetch before the previous one has completed
- .fetch({ ...this.dataLoaderParams })
- .then(data => {
- // Pipeline responses never throw errors, so we need to check for
- // `type: 'error'`, and then throw so it can be caught below.
- // TODO: We should revisit this after we have fully migrated
- // to the new expression pipeline infrastructure.
- if (data && data.type === 'error') {
- throw data.error;
- }
-
- if (data && data.value) {
- this.dataSubject.next(data.value);
- }
- return data;
- })
- .catch(this.handleDataLoaderError)
- );
- };
-
- /**
- * When dataLoader returns an error, we need to make sure it surfaces in the UI.
- *
- * TODO: Eventually we should add some custom error messages for issues that are
- * frequently encountered by users.
- */
- private handleDataLoaderError = (error: any): void => {
- // If the data loader was aborted then no need to surface this error in the UI
- if (error && error.name === 'AbortError') return;
-
- // Cancel execution of pipeline expressions
- if (this.abortController) {
- this.abortController.abort();
- }
-
- this.vis.requestError = error;
- this.vis.showRequestError =
- error.type && ['NO_OP_SEARCH_STRATEGY', 'UNSUPPORTED_QUERY'].includes(error.type);
-
- toastNotifications.addDanger({
- title: i18n.translate('common.ui.visualize.dataLoaderError', {
- defaultMessage: 'Error in visualization',
- }),
- text: error.message,
- });
- };
-
- private rendererProvider = (response: VisResponseData | null) => {
- const renderer = registries.renderers.get(get(response || {}, 'as', 'visualization'));
-
- if (!renderer) {
- return null;
- }
-
- return () =>
- renderer.render(
- this.element,
- get(response, 'value', { visType: this.vis.type.name }),
- this.handlers
- );
- };
-}
diff --git a/src/legacy/ui/public/visualize/loader/pipeline_data_loader.ts b/src/legacy/ui/public/visualize/loader/pipeline_data_loader.ts
deleted file mode 100644
index c1aa6903abe88..0000000000000
--- a/src/legacy/ui/public/visualize/loader/pipeline_data_loader.ts
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { Vis } from '../../vis';
-import { buildPipeline, runPipeline } from './pipeline_helpers';
-import { RequestHandlerParams } from './embedded_visualize_handler';
-
-export class PipelineDataLoader {
- constructor(private readonly vis: Vis) {}
-
- public async fetch(params: RequestHandlerParams): Promise {
- this.vis.pipelineExpression = await buildPipeline(this.vis, params);
-
- return runPipeline(
- this.vis.pipelineExpression,
- { type: 'null' },
- {
- getInitialContext: () => ({
- type: 'kibana_context',
- query: params.query,
- timeRange: params.timeRange,
- filters: params.filters
- ? params.filters.filter(filter => !filter.meta.disabled)
- : undefined,
- }),
- inspectorAdapters: params.inspectorAdapters,
- abortSignal: params.abortSignal,
- }
- );
- }
-}
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 056f3e8cfc586..70e0c1f1382fa 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/pipeline_helpers/index.ts b/src/legacy/ui/public/visualize/loader/pipeline_helpers/index.ts
index 69c29339a8713..a1292c59ac61d 100644
--- a/src/legacy/ui/public/visualize/loader/pipeline_helpers/index.ts
+++ b/src/legacy/ui/public/visualize/loader/pipeline_helpers/index.ts
@@ -18,4 +18,3 @@
*/
export { buildPipeline } from './build_pipeline';
-export { runPipeline } from './run_pipeline';
diff --git a/src/legacy/ui/public/visualize/loader/pipeline_helpers/run_pipeline.ts b/src/legacy/ui/public/visualize/loader/pipeline_helpers/run_pipeline.ts
deleted file mode 100644
index 78a959b2b0f71..0000000000000
--- a/src/legacy/ui/public/visualize/loader/pipeline_helpers/run_pipeline.ts
+++ /dev/null
@@ -1,43 +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 { fromExpression } from '@kbn/interpreter/common';
-import { Adapters } from 'ui/inspector';
-import { getInterpreter } from '../../../../../core_plugins/interpreter/public/interpreter';
-import { KibanaContext } from '../../../../../core_plugins/interpreter/public';
-
-type getInitialContextFunction = () => KibanaContext;
-
-export interface RunPipelineHandlers {
- getInitialContext: getInitialContextFunction;
- inspectorAdapters?: Adapters;
- abortSignal?: AbortSignal;
-}
-
-export const runPipeline = async (
- expression: string,
- context: any,
- handlers: RunPipelineHandlers
-) => {
- const ast = fromExpression(expression);
- const { interpreter } = await getInterpreter();
- const pipelineResponse = await interpreter.interpretAst(ast, context, handlers as any);
- return pipelineResponse;
-};
diff --git a/src/legacy/ui/public/visualize/loader/types.ts b/src/legacy/ui/public/visualize/loader/types.ts
deleted file mode 100644
index 525ec35834ecd..0000000000000
--- a/src/legacy/ui/public/visualize/loader/types.ts
+++ /dev/null
@@ -1,104 +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 { TimeRange } from 'src/plugins/data/public';
-import { Query } from 'src/legacy/core_plugins/data/public';
-import { SavedObject } from 'ui/saved_objects/saved_object';
-import { VisResponseValue } from 'src/plugins/visualizations/public';
-import { SearchSource } from '../../courier';
-import { PersistedState } from '../../persisted_state';
-import { AppState } from '../../state_management/app_state';
-import { Vis } from '../../vis';
-import { esFilters } from '../../../../../plugins/data/public';
-
-export interface VisSavedObject extends SavedObject {
- vis: Vis;
- description?: string;
- searchSource: SearchSource;
- title: string;
- uiStateJSON?: string;
- destroy: () => void;
-}
-
-export interface VisResponseData {
- as: string;
- value: VisResponseValue;
-}
-
-/**
- * The parameters accepted by the embedVisualize calls.
- */
-export interface VisualizeLoaderParams {
- /**
- * An object with a from/to key, that must be either a date in ISO format, or a
- * valid datetime Elasticsearch expression, e.g.: { from: 'now-7d/d', to: 'now' }
- */
- timeRange?: TimeRange;
- /**
- * If set to true, the visualization will be appended to the passed element instead
- * of replacing all its content. (default: false)
- */
- append?: boolean;
- /**
- * If specified this CSS class (or classes with space separated) will be set to
- * the root visualize element.
- */
- cssClass?: string;
- /**
- * An object of key-value pairs, that will be set as data-{key}="{value}" attributes
- * on the visualization element.
- */
- dataAttrs?: { [key: string]: string };
- /**
- * Specifies the filters that should be applied to that visualization.
- */
- filters?: esFilters.Filter[];
- /**
- * The query that should apply to that visualization.
- */
- query?: Query;
- /**
- * The current uiState of the application. If you don't pass a uiState, the
- * visualization will creates it's own uiState to store information like whether
- * the legend is open or closed, but you don't have access to it from the outside.
- * Pass one in if you need that access, e.g. for saving that state.
- */
- uiState?: PersistedState;
- /**
- * The appState this visualization should use. If you don't specify it, the
- * global AppState (that is decoded in the URL) will be used. Usually you don't
- * need to overwrite this, unless you don't want the visualization to use the
- * global AppState.
- */
- appState?: AppState;
- /**
- * Whether or not the visualization should fetch its data automatically. If this is
- * set to `false` the loader won't trigger a fetch on embedding or when an auto refresh
- * cycle happens. Default value: `true`
- */
- autoFetch?: boolean;
-}
-
-/**
- * The subset of properties allowed to update on an already embedded visualization.
- */
-export type VisualizeUpdateParams = Pick<
- VisualizeLoaderParams,
- 'timeRange' | 'dataAttrs' | 'filters' | 'query'
->;
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 9f3aa190917d7..912afab74bef4 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
@@ -25,11 +25,13 @@ import { AggConfig } from 'ui/vis';
import { Query } from 'src/legacy/core_plugins/data/public';
import { timefilter } from 'ui/timefilter';
import { Vis } from '../../../vis';
+import { SearchSource } from '../../../courier';
import { esFilters } from '../../../../../../plugins/data/public';
interface QueryGeohashBoundsParams {
filters?: esFilters.Filter[];
query?: Query;
+ searchSource?: SearchSource;
}
/**
@@ -47,7 +49,9 @@ export async function queryGeohashBounds(vis: Vis, params: QueryGeohashBoundsPar
});
if (agg) {
- const searchSource = vis.searchSource.createChild();
+ const searchSource = params.searchSource
+ ? params.searchSource.createChild()
+ : new SearchSource();
searchSource.setField('size', 0);
searchSource.setField('aggs', () => {
const geoBoundsAgg = vis.getAggConfig().createAggConfig(
diff --git a/src/legacy/ui/public/visualize/loader/vis.js b/src/legacy/ui/public/visualize/loader/vis.js
index 85ab07528b846..1942fd58afebb 100644
--- a/src/legacy/ui/public/visualize/loader/vis.js
+++ b/src/legacy/ui/public/visualize/loader/vis.js
@@ -33,8 +33,7 @@ import { PersistedState } from '../../persisted_state';
import { start as visualizations } from '../../../../core_plugins/visualizations/public/np_ready/public/legacy';
-
-export function VisProvider(indexPatterns, getAppState) {
+export function VisProvider(getAppState) {
const visTypes = visualizations.types;
class Vis extends EventEmitter {
diff --git a/src/legacy/ui/public/visualize/loader/visualization_loader.tsx b/src/legacy/ui/public/visualize/loader/visualization_loader.tsx
deleted file mode 100644
index 307ef0354f451..0000000000000
--- a/src/legacy/ui/public/visualize/loader/visualization_loader.tsx
+++ /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 _ from 'lodash';
-import React from 'react';
-import { render, unmountComponentAtNode } from 'react-dom';
-
-import { PersistedState } from '../../persisted_state';
-import { Vis } from '../../vis';
-import { Visualization } from '../components/visualization';
-
-interface VisualizationLoaderParams {
- listenOnChange?: boolean;
-}
-
-function renderVisualization(
- element: HTMLElement,
- vis: Vis,
- visData: any,
- visParams: any,
- uiState: PersistedState,
- params: VisualizationLoaderParams
-) {
- return new Promise(resolve => {
- const listenOnChange = _.get(params, 'listenOnChange', false);
- render(
- ,
- element
- );
- });
-}
-
-function destroy(element?: HTMLElement) {
- if (element) {
- unmountComponentAtNode(element);
- }
-}
-
-export const visualizationLoader = {
- render: renderVisualization,
- destroy,
-};
diff --git a/src/legacy/ui/public/visualize/loader/visualize_loader.ts b/src/legacy/ui/public/visualize/loader/visualize_loader.ts
deleted file mode 100644
index 086b16711a581..0000000000000
--- a/src/legacy/ui/public/visualize/loader/visualize_loader.ts
+++ /dev/null
@@ -1,159 +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.
- */
-
-/**
- * IMPORTANT: If you make changes to this API, please make sure to check that
- * the docs (docs/development/visualize/development-create-visualization.asciidoc)
- * are up to date.
- */
-
-import chrome from '../../chrome';
-import { FilterBarQueryFilterProvider } from '../../filter_manager/query_filter';
-import { IPrivate } from '../../private';
-import { EmbeddedVisualizeHandler } from './embedded_visualize_handler';
-import { VisSavedObject, VisualizeLoaderParams } from './types';
-
-export class VisualizeLoader {
- constructor(private readonly savedVisualizations: any, private readonly Private: IPrivate) {}
-
- /**
- * Renders a saved visualization specified by its id into a DOM element.
- *
- * @param element The DOM element to render the visualization into.
- * You can alternatively pass a jQuery element instead.
- * @param id The id of the saved visualization. This is the id of the
- * saved object that is stored in the .kibana index.
- * @param params A list of parameters that will influence rendering.
- *
- * @return A promise that resolves to the
- * handler for this visualization as soon as the saved object could be found.
- */
- public async embedVisualizationWithId(
- element: HTMLElement,
- savedVisualizationId: string,
- params: VisualizeLoaderParams
- ) {
- return new Promise((resolve, reject) => {
- this.savedVisualizations.get(savedVisualizationId).then((savedObj: VisSavedObject) => {
- const handler = this.renderVis(element, savedObj, params);
- resolve(handler);
- }, reject);
- });
- }
-
- /**
- * Renders a saved visualization specified by its savedObject into a DOM element.
- * In most of the cases you will need this method, since it allows you to specify
- * filters, handlers, queries, etc. on the savedObject before rendering.
- *
- * We do not encourage you to use this method, since it will most likely be changed
- * or removed in a future version of Kibana. Rather embed a visualization by its id
- * via the {@link #embedVisualizationWithId} method.
- *
- * @deprecated You should rather embed by id, since this method will be removed in the future.
- * @param element The DOM element to render the visualization into.
- * You can alternatively pass a jQuery element instead.
- * @param savedObj The savedObject as it could be retrieved by the
- * `savedVisualizations` service.
- * @param params A list of parameters that will influence rendering.
- *
- * @return The handler to the visualization.
- */
- public embedVisualizationWithSavedObject(
- el: HTMLElement,
- savedObj: VisSavedObject,
- params: VisualizeLoaderParams
- ) {
- return this.renderVis(el, savedObj, params);
- }
-
- /**
- * Returns a promise, that resolves to a list of all saved visualizations.
- *
- * @return Resolves with a list of all saved visualizations as
- * returned by the `savedVisualizations` service in Kibana.
- */
- public getVisualizationList(): Promise {
- return this.savedVisualizations.find().then((result: any) => result.hits);
- }
-
- private renderVis(
- container: HTMLElement,
- savedObj: VisSavedObject,
- params: VisualizeLoaderParams
- ) {
- const { vis, description, searchSource } = savedObj;
-
- vis.description = description;
- vis.searchSource = searchSource;
-
- if (!params.append) {
- container.innerHTML = '';
- }
-
- const element = document.createElement('div');
- element.className = 'visualize';
- element.setAttribute('data-test-subj', 'visualizationLoader');
- container.appendChild(element);
- // We need the container to have display: flex so visualization will render correctly
- container.style.display = 'flex';
-
- // If params specified cssClass, we will set this to the element.
- if (params.cssClass) {
- params.cssClass.split(' ').forEach(cssClass => {
- element.classList.add(cssClass);
- });
- }
-
- // Apply data- attributes to the element if specified
- const dataAttrs = params.dataAttrs;
- if (dataAttrs) {
- Object.keys(dataAttrs).forEach(key => {
- element.setAttribute(`data-${key}`, dataAttrs[key]);
- });
- }
-
- const handlerParams = {
- ...params,
- // lets add query filter angular service to the params
- queryFilter: this.Private(FilterBarQueryFilterProvider),
- // lets add Private to the params, we'll need to pass it to visualize later
- Private: this.Private,
- };
-
- return new EmbeddedVisualizeHandler(element, savedObj, handlerParams);
- }
-}
-
-function VisualizeLoaderProvider(savedVisualizations: any, Private: IPrivate) {
- return new VisualizeLoader(savedVisualizations, Private);
-}
-
-/**
- * Returns a promise, that resolves with the visualize loader, once it's ready.
- * @return A promise, that resolves to the visualize loader.
- */
-function getVisualizeLoader(): Promise {
- return chrome.dangerouslyGetActiveInjector().then($injector => {
- const Private: IPrivate = $injector.get('Private');
- return Private(VisualizeLoaderProvider);
- });
-}
-
-export { getVisualizeLoader, VisualizeLoaderProvider };
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 02e5f45fae3bd..36efd0bcba676 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/plugin.tsx b/src/plugins/dashboard_embeddable_container/public/plugin.tsx
index eadf70a36416a..dbb5a06da9cd9 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 => (
{
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 15c5c9d4ad2e6..35110c924fe61 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 e4606695c0f6a..e207a3ff5961b 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 839e4f6359257..5a580db0c57b8 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 901dc724aa4e4..d2374162b195f 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 9008dc2a67294..017bb8e9cb7c5 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 d7931f191e52b..c2513a9dc0c5e 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 2814735061999..a242df4811c05 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 9eda75d8abd0b..ec8d8b006317f 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 1f2d8f914dde3..d838e54e9ead4 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 0000000000000..315894cd212c4
--- /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/plugins/data/public/index_patterns/index_pattern.stub.ts b/src/plugins/data/public/index_patterns/index_pattern.stub.ts
new file mode 100644
index 0000000000000..444e65cd0cd4b
--- /dev/null
+++ b/src/plugins/data/public/index_patterns/index_pattern.stub.ts
@@ -0,0 +1,28 @@
+/*
+ * 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 { IndexPattern } from '../../common';
+import { stubFields } from './field.stub';
+
+export const stubIndexPattern: IndexPattern = {
+ id: 'logstash-*',
+ fields: stubFields,
+ title: 'logstash-*',
+ timeFieldName: '@timestamp',
+};
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 3afa3891a24bb..1847296016c73 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/persisted_log/persisted_log.test.ts b/src/plugins/data/public/query/persisted_log/persisted_log.test.ts
index e0bc8f2c3525f..87c1ec29c1aee 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 deab9af4e3a01..fa7cdbcda3082 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 643ded120799e..d29f3b6882b26 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 0000000000000..27e6f9b48bbdd
--- /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 0000000000000..200e5bacb7f18
--- /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/table_list_view/index.js b/src/plugins/data/public/stubs.ts
similarity index 86%
rename from src/legacy/core_plugins/kibana/public/table_list_view/index.js
rename to src/plugins/data/public/stubs.ts
index ae3e5d022c725..40a5e7d18f8d9 100644
--- a/src/legacy/core_plugins/kibana/public/table_list_view/index.js
+++ b/src/plugins/data/public/stubs.ts
@@ -17,5 +17,5 @@
* under the License.
*/
-export { TableListView } from './table_list_view';
-
+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 eac380dde6a62..988b5fcd43fa8 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 13ccbbd9f3dde..7dc8ff0fe133d 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 03eaa5d9594d2..c769f64025b0e 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/embeddable/public/tests/customize_panel_modal.test.tsx b/src/plugins/embeddable/public/tests/customize_panel_modal.test.tsx
index b11bd167e15f2..70d7c99d3fb9d 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 417f3436a2c63..0c075c497a4d0 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 e288f61de8681..0bb89cc1af593 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 360182368ae63..3902b0615a33d 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 66c3e8d983f98..62867a0c07a6b 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/expression_types/number.ts b/src/plugins/expressions/public/expression_types/number.ts
index 8434536f8f6b8..52b2bb1ff3194 100644
--- a/src/plugins/expressions/public/expression_types/number.ts
+++ b/src/plugins/expressions/public/expression_types/number.ts
@@ -17,6 +17,7 @@
* under the License.
*/
+import { i18n } from '@kbn/i18n';
import { ExpressionType } from '../types';
import { Datatable } from './datatable';
import { Render } from './render';
@@ -28,7 +29,20 @@ export const number = (): ExpressionType => ({
from: {
null: () => 0,
boolean: b => Number(b),
- string: n => Number(n),
+ string: n => {
+ const value = Number(n);
+ if (Number.isNaN(value)) {
+ throw new Error(
+ i18n.translate('expressions_np.types.number.fromStringConversionErrorMessage', {
+ defaultMessage: 'Can\'t typecast "{string}" string to number',
+ values: {
+ string: n,
+ },
+ })
+ );
+ }
+ return value;
+ },
},
to: {
render: (value: number): Render<{ text: string }> => {
diff --git a/src/plugins/expressions/public/expression_types/tests/number.test.ts b/src/plugins/expressions/public/expression_types/tests/number.test.ts
new file mode 100644
index 0000000000000..3336a1384ea79
--- /dev/null
+++ b/src/plugins/expressions/public/expression_types/tests/number.test.ts
@@ -0,0 +1,28 @@
+/*
+ * 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 { number } from '../number';
+
+describe('number', () => {
+ it('should fail when typecasting not numeric string to number', () => {
+ expect(() => number().from!.string('123test', {})).toThrowErrorMatchingInlineSnapshot(
+ `"Can't typecast \\"123test\\" string to number"`
+ );
+ });
+});
diff --git a/src/plugins/expressions/public/interpreter_provider.ts b/src/plugins/expressions/public/interpreter_provider.ts
index cb84370ad69c5..15d6b1c025f54 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/expressions/public/types/index.ts b/src/plugins/expressions/public/types/index.ts
index 2d66216a9770b..87ef810682f60 100644
--- a/src/plugins/expressions/public/types/index.ts
+++ b/src/plugins/expressions/public/types/index.ts
@@ -91,6 +91,7 @@ export interface IExpressionLoaderParams {
customFunctions?: [];
customRenderers?: [];
extraHandlers?: Record;
+ inspectorAdapters?: Adapters;
}
export interface IInterpreterHandlers {
diff --git a/src/plugins/kibana_react/public/context/context.tsx b/src/plugins/kibana_react/public/context/context.tsx
index cbae5c4638ca2..cbf2ad07b463e 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 cd2ae89b05b5d..2d82f646c827b 100644
--- a/src/plugins/kibana_react/public/index.ts
+++ b/src/plugins/kibana_react/public/index.ts
@@ -23,3 +23,5 @@ export * from './context';
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 35c503d590b2c..4f64a2b95f512 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 28c1d5391d160..774f74863ee6f 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 e1e7f1c536342..bab710cdca595 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/legacy/ui/public/visualize/index.ts b/src/plugins/kibana_react/public/table_list_view/index.ts
similarity index 95%
rename from src/legacy/ui/public/visualize/index.ts
rename to src/plugins/kibana_react/public/table_list_view/index.ts
index 46a8968358294..d9a4db50ab7fb 100644
--- a/src/legacy/ui/public/visualize/index.ts
+++ b/src/plugins/kibana_react/public/table_list_view/index.ts
@@ -16,5 +16,4 @@
* specific language governing permissions and limitations
* under the License.
*/
-
-export * from './loader';
+export * from './table_list_view';
diff --git a/src/legacy/core_plugins/kibana/public/table_list_view/table_list_view.js b/src/plugins/kibana_react/public/table_list_view/table_list_view.tsx
similarity index 66%
rename from src/legacy/core_plugins/kibana/public/table_list_view/table_list_view.js
rename to src/plugins/kibana_react/public/table_list_view/table_list_view.tsx
index 3148a4a37c9c0..dde8efa7e1106 100644
--- a/src/legacy/core_plugins/kibana/public/table_list_view/table_list_view.js
+++ b/src/plugins/kibana_react/public/table_list_view/table_list_view.tsx
@@ -18,13 +18,12 @@
*/
import React from 'react';
-import PropTypes from 'prop-types';
-import { injectI18n, FormattedMessage } from '@kbn/i18n/react';
+import { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
-import _ from 'lodash';
-import { toastNotifications } from 'ui/notify';
+import { debounce, indexBy, sortBy, uniq } from 'lodash';
import {
EuiTitle,
+ // @ts-ignore
EuiInMemoryTable,
EuiPage,
EuiPageBody,
@@ -38,26 +37,67 @@ import {
EuiConfirmModal,
EuiCallOut,
} from '@elastic/eui';
-
-import { npStart } from 'ui/new_platform';
+import { ToastsStart, UiSettingsClientContract } from 'kibana/public';
+import { toMountPoint } from '../util';
export const EMPTY_FILTER = '';
+interface Column {
+ name: string;
+ width?: string;
+ actions?: object[];
+}
+
+interface Item {
+ id?: string;
+}
+
+export interface TableListViewProps {
+ createItem?(): void;
+ deleteItems?(items: object[]): Promise;
+ editItem?(item: object): void;
+ entityName: string;
+ entityNamePlural: string;
+ findItems(query: string): Promise<{ total: number; hits: object[] }>;
+ listingLimit: number;
+ initialFilter: string;
+ noItemsFragment: JSX.Element;
+ // update possible column types to something like (FieldDataColumn | ComputedColumn | ActionsColumn)[] when they have been added to EUI
+ tableColumns: Column[];
+ tableListTitle: string;
+ toastNotifications: ToastsStart;
+ uiSettings: UiSettingsClientContract;
+}
+
+export interface TableListViewState {
+ items: object[];
+ hasInitialFetchReturned: boolean;
+ isFetchingItems: boolean;
+ isDeletingItems: boolean;
+ showDeleteModal: boolean;
+ showLimitError: boolean;
+ filter: string;
+ selectedIds: string[];
+ totalItems: number;
+}
+
// saved object client does not support sorting by title because title is only mapped as analyzed
// the legacy implementation got around this by pulling `listingLimit` items and doing client side sorting
// and not supporting server-side paging.
// This component does not try to tackle these problems (yet) and is just feature matching the legacy component
// TODO support server side sorting/paging once title and description are sortable on the server.
-class TableListViewUi extends React.Component {
+class TableListView extends React.Component {
+ private pagination = {};
+ private _isMounted = false;
- constructor(props) {
+ constructor(props: TableListViewProps) {
super(props);
- const initialPageSize = npStart.core.uiSettings.get('savedObjects:perPage');
+ const initialPageSize = props.uiSettings.get('savedObjects:perPage');
this.pagination = {
initialPageIndex: 0,
initialPageSize,
- pageSizeOptions: _.uniq([10, 20, 50, initialPageSize]).sort(),
+ pageSizeOptions: uniq([10, 20, 50, initialPageSize]).sort(),
};
this.state = {
items: [],
@@ -67,10 +107,9 @@ class TableListViewUi extends React.Component {
isDeletingItems: false,
showDeleteModal: false,
showLimitError: false,
- filter: this.props.initialFilter,
+ filter: props.initialFilter,
selectedIds: [],
};
-
}
componentWillMount() {
@@ -86,7 +125,7 @@ class TableListViewUi extends React.Component {
this.fetchItems();
}
- debouncedFetch = _.debounce(async (filter) => {
+ debouncedFetch = debounce(async (filter: string) => {
const response = await this.props.findItems(filter);
if (!this._isMounted) {
@@ -100,7 +139,7 @@ class TableListViewUi extends React.Component {
this.setState({
hasInitialFetchReturned: true,
isFetchingItems: false,
- items: (!filter ? _.sortBy(response.hits, 'title') : response.hits),
+ items: !filter ? sortBy(response.hits, 'title') : response.hits,
totalItems: response.total,
showLimitError: response.total > this.props.listingLimit,
});
@@ -108,26 +147,29 @@ class TableListViewUi extends React.Component {
}, 300);
fetchItems = () => {
- this.setState({
- isFetchingItems: true,
- }, this.debouncedFetch.bind(null, this.state.filter));
- }
+ this.setState(
+ {
+ isFetchingItems: true,
+ },
+ this.debouncedFetch.bind(null, this.state.filter)
+ );
+ };
deleteSelectedItems = async () => {
- if (this.state.isDeletingItems) {
+ if (this.state.isDeletingItems || !this.props.deleteItems) {
return;
}
this.setState({
- isDeletingItems: true
+ isDeletingItems: true,
});
try {
- const itemsById = _.indexBy(this.state.items, 'id');
+ const itemsById = indexBy(this.state.items, 'id');
await this.props.deleteItems(this.state.selectedIds.map(id => itemsById[id]));
} catch (error) {
- toastNotifications.addDanger({
- title: (
+ this.props.toastNotifications.addDanger({
+ title: toMountPoint(
@@ -138,25 +180,28 @@ class TableListViewUi extends React.Component {
this.fetchItems();
this.setState({
isDeletingItems: false,
- selectedIds: []
+ selectedIds: [],
});
this.closeDeleteModal();
- }
+ };
closeDeleteModal = () => {
this.setState({ showDeleteModal: false });
- }
+ };
openDeleteModal = () => {
this.setState({ showDeleteModal: true });
- }
+ };
- setFilter(filter) {
+ setFilter({ queryText }: { queryText: string }) {
// If the user is searching, we want to clear the sort order so that
// results are ordered by Elasticsearch's relevance.
- this.setState({
- filter: filter.queryText,
- }, this.fetchItems);
+ this.setState(
+ {
+ filter: queryText,
+ },
+ this.fetchItems
+ );
}
hasNoItems() {
@@ -170,14 +215,14 @@ class TableListViewUi extends React.Component {
renderConfirmDeleteModal() {
let deleteButton = (
);
if (this.state.isDeletingItems) {
deleteButton = (
);
@@ -188,11 +233,14 @@ class TableListViewUi extends React.Component {
}
@@ -201,7 +249,7 @@ class TableListViewUi extends React.Component {
onConfirm={this.deleteSelectedItems}
cancelButtonText={
}
@@ -210,7 +258,7 @@ class TableListViewUi extends React.Component {
>
@@ -227,7 +275,7 @@ class TableListViewUi extends React.Component {
}
@@ -236,26 +284,22 @@ class TableListViewUi extends React.Component {
>
- listingLimit
-
- ),
+ listingLimitText: listingLimit,
advancedSettingsLink: (
- )
+ ),
}}
/>
@@ -268,18 +312,15 @@ class TableListViewUi extends React.Component {
renderNoItemsMessage() {
if (this.props.noItemsFragment) {
- return (
- this.props.noItemsFragment
- );
+ return this.props.noItemsFragment;
} else {
return (
);
-
}
}
@@ -302,11 +343,12 @@ class TableListViewUi extends React.Component {
data-test-subj="deleteSelectedItems"
>
@@ -314,25 +356,34 @@ class TableListViewUi extends React.Component {
}
renderTable() {
- const selection = this.props.deleteItems ? {
- onSelectionChange: (selection) => {
- this.setState({
- selectedIds: selection.map(item => { return item.id; })
- });
- }
- } : null;
-
- const actions = [{
- name: i18n.translate('kbn.table_list_view.listing.table.editActionName', {
- defaultMessage: 'Edit'
- }),
- description: i18n.translate('kbn.table_list_view.listing.table.editActionDescription', {
- defaultMessage: 'Edit'
- }),
- icon: 'pencil',
- type: 'icon',
- onClick: this.props.editItem
- }];
+ const selection = this.props.deleteItems
+ ? {
+ onSelectionChange: (obj: Item[]) => {
+ this.setState({
+ selectedIds: obj
+ .map(item => item.id)
+ .filter((id: undefined | string): id is string => Boolean(id)),
+ });
+ },
+ }
+ : null;
+
+ const actions = [
+ {
+ name: i18n.translate('kibana-react.tableListView.listing.table.editActionName', {
+ defaultMessage: 'Edit',
+ }),
+ description: i18n.translate(
+ 'kibana-react.tableListView.listing.table.editActionDescription',
+ {
+ defaultMessage: 'Edit',
+ }
+ ),
+ icon: 'pencil',
+ type: 'icon',
+ onClick: this.props.editItem,
+ },
+ ];
const search = {
onChange: this.setFilter.bind(this),
@@ -346,17 +397,17 @@ class TableListViewUi extends React.Component {
const columns = this.props.tableColumns.slice();
if (this.props.editItem) {
columns.push({
- name: i18n.translate('kbn.table_list_view.listing.table.actionTitle', {
- defaultMessage: 'Actions'
+ name: i18n.translate('kibana-react.tableListView.listing.table.actionTitle', {
+ defaultMessage: 'Actions',
}),
width: '100px',
- actions
+ actions,
});
}
const noItemsMessage = (
@@ -397,7 +448,7 @@ class TableListViewUi extends React.Component {
fill
>
@@ -412,14 +463,11 @@ class TableListViewUi extends React.Component {
-
- {this.props.tableListTitle}
-
+ {this.props.tableListTitle}
{createButton}
-
@@ -450,34 +498,10 @@ class TableListViewUi extends React.Component {
className="itemListing__page"
restrictWidth
>
-
- {this.renderPageContent()}
-
+ {this.renderPageContent()}
);
}
}
-TableListViewUi.propTypes = {
- tableColumns: PropTypes.array.isRequired,
-
- noItemsFragment: PropTypes.object,
-
- findItems: PropTypes.func.isRequired,
- deleteItems: PropTypes.func,
- createItem: PropTypes.func,
- editItem: PropTypes.func,
-
- listingLimit: PropTypes.number,
- initialFilter: PropTypes.string,
-
- entityName: PropTypes.string.isRequired,
- entityNamePlural: PropTypes.string.isRequired,
- tableListTitle: PropTypes.string.isRequired,
-};
-
-TableListViewUi.defaultProps = {
- initialFilter: EMPTY_FILTER,
-};
-
-export const TableListView = injectI18n(TableListViewUi);
+export { TableListView };
diff --git a/src/plugins/kibana_react/public/util/index.ts b/src/plugins/kibana_react/public/util/index.ts
index a9219196e84a9..1053ca01603e3 100644
--- a/src/plugins/kibana_react/public/util/index.ts
+++ b/src/plugins/kibana_react/public/util/index.ts
@@ -19,3 +19,4 @@
export * from './use_observable';
export * from './use_unmount';
+export * from './react_mount';
diff --git a/src/legacy/core_plugins/telemetry/server/telemetry_collection/get_stats.ts b/src/plugins/kibana_react/public/util/react_mount.tsx
similarity index 55%
rename from src/legacy/core_plugins/telemetry/server/telemetry_collection/get_stats.ts
rename to src/plugins/kibana_react/public/util/react_mount.tsx
index 024272e0f805c..e3f16e0e3bfa1 100644
--- a/src/legacy/core_plugins/telemetry/server/telemetry_collection/get_stats.ts
+++ b/src/plugins/kibana_react/public/util/react_mount.tsx
@@ -17,29 +17,24 @@
* under the License.
*/
-// @ts-ignore
-import { getLocalStats } from './get_local_stats';
+import React from 'react';
+import ReactDOM from 'react-dom';
+import { I18nProvider } from '@kbn/i18n/react';
+import { MountPoint } from 'kibana/public';
/**
- * Get the telemetry data.
+ * MountPoint converter for react nodes.
*
- * @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.
+ * @param node to get a mount point for
*/
-export async function getStats(
- req: any,
- config: any,
- start: string,
- end: string,
- unencrypted: boolean
-) {
- return [
- await getLocalStats(req, {
- useInternalUser: !unencrypted,
- }),
- ];
-}
+export const toMountPoint = (node: React.ReactNode): MountPoint => {
+ 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 0000000000000..45ad4cb407175
--- /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/legacy/core_plugins/kibana/public/home/components/telemetry_opt_in/index.ts b/src/plugins/newsfeed/constants.ts
similarity index 81%
rename from src/legacy/core_plugins/kibana/public/home/components/telemetry_opt_in/index.ts
rename to src/plugins/newsfeed/constants.ts
index 63636433bc00b..ddcbbb6cb1dbe 100644
--- a/src/legacy/core_plugins/kibana/public/home/components/telemetry_opt_in/index.ts
+++ b/src/plugins/newsfeed/constants.ts
@@ -16,8 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
-import { renderTelemetryOptInCard, Props } from './telemetry_opt_in_card';
-export const TelemetryOptInCard = (props: Props) => {
- return renderTelemetryOptInCard(props);
-};
+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 0000000000000..9d49b42424a06
--- /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 0000000000000..8764b7664d449
--- /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 0000000000000..2e88b0053535e
--- /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 0000000000000..33702df00a583
--- /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 0000000000000..cec18e0bdec43
--- /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 0000000000000..8a99abe18a75f
--- /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 0000000000000..ca449b8ee879e
--- /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 0000000000000..fcbc7970377d4
--- /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 0000000000000..da042f0fce7b6
--- /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 0000000000000..1217de60d9638
--- /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 0000000000000..4383b9e0f7dab
--- /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 0000000000000..6920dd9b2bccc
--- /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 0000000000000..5ea5e5b324717
--- /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 0000000000000..78485c6ee4f59
--- /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 31656df2cb644..f5a1e63617dfa 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 efa6be47b50c9..f0c86f2904638 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 44e4bef99bf62..58161e545bd06 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 0000000000000..beee9bb5c6069
--- /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 0000000000000..2a7ea3793324d
--- /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 0000000000000..5291b1031b0a9
--- /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/home/_newsfeed.ts b/test/functional/apps/home/_newsfeed.ts
new file mode 100644
index 0000000000000..35d7ac8adefa5
--- /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 17c93680088cb..f3f564fbd2919 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/_tile_map.js b/test/functional/apps/visualize/_tile_map.js
index 7719ecca56a65..0e580f6a7ab3f 100644
--- a/test/functional/apps/visualize/_tile_map.js
+++ b/test/functional/apps/visualize/_tile_map.js
@@ -221,7 +221,7 @@ export default function ({ getService, getPageObjects }) {
it('when not checked does not add filters to aggregation', async () => {
await PageObjects.visualize.toggleOpenEditor(2);
- await PageObjects.visualize.toggleIsFilteredByCollarCheckbox();
+ await PageObjects.visualize.setIsFilteredByCollarCheckbox(false);
await PageObjects.visualize.clickGo();
await inspector.open();
await inspector.expectTableHeaders(['geohash_grid', 'Count', 'Geo Centroid']);
@@ -229,7 +229,7 @@ export default function ({ getService, getPageObjects }) {
});
after(async () => {
- await PageObjects.visualize.toggleIsFilteredByCollarCheckbox();
+ await PageObjects.visualize.setIsFilteredByCollarCheckbox(true);
await PageObjects.visualize.clickGo();
});
});
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 b659d29b158b7..4088ab6193a59 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 ca141114f976d..af3a15e9b3015 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 1e8c454f42cfe..84562990191d1 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 0000000000000..24ff21f0b47de
--- /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 570511bee4bc5..4b65de57f12d8 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 67494f201adae..81d26a4b69478 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`);
@@ -1007,6 +1029,16 @@ export function VisualizePageProvider({ getService, getPageObjects, updateBaseli
await testSubjects.click('isFilteredByCollarCheckbox');
}
+ async setIsFilteredByCollarCheckbox(value = true) {
+ await retry.try(async () => {
+ const isChecked = await this.isSwitchChecked('isFilteredByCollarCheckbox');
+ if (isChecked !== value) {
+ await testSubjects.click('isFilteredByCollarCheckbox');
+ throw new Error('isFilteredByCollar not set correctly');
+ }
+ });
+ }
+
async getMarkdownData() {
const markdown = await retry.try(async () => find.byCssSelector('visualize'));
return await markdown.getVisibleText();
diff --git a/test/functional/services/global_nav.ts b/test/functional/services/global_nav.ts
index 164ea999fa279..df3aac67f22a1 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 b6b68cc0d3cf9..71e2711906fce 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 f134fde028e09..d6de0be0c172e 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 766e6168002c2..da1bb597f5730 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/interpreter_functional/plugins/kbn_tp_run_pipeline/public/app.js b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/app.js
index bd58184cd1185..b0db26c0c6743 100644
--- a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/app.js
+++ b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/app.js
@@ -24,9 +24,6 @@ import { uiModules } from 'ui/modules';
import chrome from 'ui/chrome';
import { RequestAdapter, DataAdapter } from 'ui/inspector/adapters';
-import { runPipeline } from 'ui/visualize/loader/pipeline_helpers';
-import { visualizationLoader } from 'ui/visualize/loader/visualization_loader';
-
import { registries } from 'plugins/interpreter/registries';
// This is required so some default styles and required scripts/Angular modules are loaded,
@@ -58,6 +55,17 @@ app.config(stateManagementConfigProvider =>
stateManagementConfigProvider.disable()
);
+import { fromExpression } from '@kbn/interpreter/common';
+import { getInterpreter } from '../../../../../src/legacy/core_plugins/interpreter/public/interpreter';
+
+const runPipeline = async (expression, context, handlers) => {
+ const ast = fromExpression(expression);
+ const { interpreter } = await getInterpreter();
+ const pipelineResponse = await interpreter.interpretAst(ast, context, handlers);
+ return pipelineResponse;
+};
+
+
function RootController($scope, $element) {
const domNode = $element[0];
@@ -67,7 +75,6 @@ function RootController($scope, $element) {
DataAdapter={DataAdapter}
runPipeline={runPipeline}
registries={registries}
- visualizationLoader={visualizationLoader}
/>, domNode);
// unmount react on controller destroy
diff --git a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/components/main.js b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/components/main.js
index 3b1744457c25a..62ba8dd16fef4 100644
--- a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/components/main.js
+++ b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/components/main.js
@@ -64,7 +64,6 @@ class Main extends React.Component {
this.setState({ expression: 'Renderer was not found in registry!\n\n' + JSON.stringify(context) });
return resolve();
}
- props.visualizationLoader.destroy(this.chartDiv);
const renderCompleteHandler = () => {
resolve('render complete');
this.chartDiv.removeEventListener('renderComplete', renderCompleteHandler);
diff --git a/test/interpreter_functional/test_suites/run_pipeline/index.js b/test/interpreter_functional/test_suites/run_pipeline/index.js
index 3c1ce2314f55f..ebc0568ebb955 100644
--- a/test/interpreter_functional/test_suites/run_pipeline/index.js
+++ b/test/interpreter_functional/test_suites/run_pipeline/index.js
@@ -25,7 +25,7 @@ export default function ({ getService, getPageObjects, loadTestFile }) {
const testSubjects = getService('testSubjects');
const PageObjects = getPageObjects(['common', 'header']);
- describe('runPipeline', function () {
+ describe.skip('runPipeline', function () {
this.tags(['skipFirefox']);
before(async () => {
diff --git a/test/plugin_functional/config.js b/test/plugin_functional/config.js
index e5ad767349358..a6316c607a7c7 100644
--- a/test/plugin_functional/config.js
+++ b/test/plugin_functional/config.js
@@ -32,7 +32,6 @@ export default async function ({ readConfigFile }) {
testFiles: [
require.resolve('./test_suites/app_plugins'),
require.resolve('./test_suites/custom_visualizations'),
- require.resolve('./test_suites/embedding_visualizations'),
require.resolve('./test_suites/panel_actions'),
require.resolve('./test_suites/search'),
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 0000000000000..a8a5616627726
--- /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 0000000000000..eff6c1e1f142a
--- /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 0000000000000..556a9ca140715
--- /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 0000000000000..6e9959ecbdf9e
--- /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/test/plugin_functional/plugins/core_plugin_chromeless/public/plugin.tsx b/test/plugin_functional/plugins/core_plugin_chromeless/public/plugin.tsx
new file mode 100644
index 0000000000000..03870410fb334
--- /dev/null
+++ b/test/plugin_functional/plugins/core_plugin_chromeless/public/plugin.tsx
@@ -0,0 +1,47 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { Plugin, CoreSetup } from 'kibana/public';
+
+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 {
+ getGreeting() {
+ return 'Hello from Plugin Chromeless!';
+ },
+ };
+ }
+
+ 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 0000000000000..5fcaeafbb0d85
--- /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 377163251010c..298eaaaf420e0 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 acb75b15196d6..d3f2360add6c0 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 7c5b6f6be58af..4d0444265825a 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 ef472b4026957..196e64af39985 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 277bb09ac745c..33e60128d0806 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/plugins/kbn_tp_visualize_embedding/package.json b/test/plugin_functional/plugins/kbn_tp_visualize_embedding/package.json
deleted file mode 100644
index f248a7e4d1f2d..0000000000000
--- a/test/plugin_functional/plugins/kbn_tp_visualize_embedding/package.json
+++ /dev/null
@@ -1,14 +0,0 @@
-{
- "name": "kbn_tp_visualize_embedding",
- "version": "1.0.0",
- "kibana": {
- "version": "kibana",
- "templateVersion": "1.0.0"
- },
- "license": "Apache-2.0",
- "dependencies": {
- "@elastic/eui": "14.8.0",
- "react": "^16.8.0",
- "react-dom": "^16.8.0"
- }
-}
diff --git a/test/plugin_functional/plugins/kbn_tp_visualize_embedding/public/app.js b/test/plugin_functional/plugins/kbn_tp_visualize_embedding/public/app.js
deleted file mode 100644
index 4463feac27513..0000000000000
--- a/test/plugin_functional/plugins/kbn_tp_visualize_embedding/public/app.js
+++ /dev/null
@@ -1,67 +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 React from 'react';
-import { render, unmountComponentAtNode } from 'react-dom';
-
-import { uiModules } from 'ui/modules';
-import chrome from 'ui/chrome';
-
-// This is required so some default styles and required scripts/Angular modules are loaded,
-// or the timezone setting is correctly applied.
-import 'ui/autoload/all';
-
-// These are all the required uiExports you need to import in case you want to embed visualizations.
-import 'uiExports/visTypes';
-import 'uiExports/visResponseHandlers';
-import 'uiExports/visRequestHandlers';
-import 'uiExports/visEditorTypes';
-import 'uiExports/visualize';
-import 'uiExports/savedObjectTypes';
-import 'uiExports/fieldFormats';
-import 'uiExports/search';
-
-import { Main } from './components/main';
-
-const app = uiModules.get('apps/firewallDemoPlugin', ['kibana']);
-
-app.config($locationProvider => {
- $locationProvider.html5Mode({
- enabled: false,
- requireBase: false,
- rewriteLinks: false,
- });
-});
-app.config(stateManagementConfigProvider =>
- stateManagementConfigProvider.disable()
-);
-
-function RootController($scope, $element) {
- const domNode = $element[0];
-
- // render react to DOM
- render(, domNode);
-
- // unmount react on controller destroy
- $scope.$on('$destroy', () => {
- unmountComponentAtNode(domNode);
- });
-}
-
-chrome.setRootController('firewallDemoPlugin', RootController);
diff --git a/test/plugin_functional/plugins/kbn_tp_visualize_embedding/public/components/main.js b/test/plugin_functional/plugins/kbn_tp_visualize_embedding/public/components/main.js
deleted file mode 100644
index 677708dfe6e97..0000000000000
--- a/test/plugin_functional/plugins/kbn_tp_visualize_embedding/public/components/main.js
+++ /dev/null
@@ -1,140 +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 React from 'react';
-import {
- EuiFlexGroup,
- EuiFlexItem,
- EuiFormRow,
- EuiLoadingChart,
- EuiPage,
- EuiPageBody,
- EuiPageContent,
- EuiPageContentBody,
- EuiPageContentHeader,
- EuiSelect,
-} from '@elastic/eui';
-
-import { embeddingSamples } from '../embedding';
-
-const VISUALIZATION_OPTIONS = [
- { value: '', text: '' },
- { value: 'timebased', text: 'Time based' },
- { value: 'timebased_with-filters', text: 'Time based (with filters)' },
- { value: 'timebased_no-datehistogram', text: 'Time based data without date histogram' }
-];
-
-class Main extends React.Component {
-
- chartDiv = React.createRef();
- state = {
- loading: false,
- selectedParams: null,
- selectedVis: null,
- };
-
- embedVisualization = async () => {
- if (this.handler) {
- // Whenever a visualization is about to be removed from DOM that you embedded,
- // you need to call `destroy` on the handler to make sure the visualization is
- // teared down correctly.
- this.handler.destroy();
- this.chartDiv.current.innerHTML = '';
- }
-
- const { selectedParams, selectedVis } = this.state;
- if (selectedParams && selectedVis) {
- this.setState({ loading: true });
- const sample = embeddingSamples.find(el => el.id === selectedParams);
- this.handler = await sample.run(this.chartDiv.current, selectedVis);
- // handler.whenFirstRenderComplete() will return a promise that resolves once the first
- // rendering after embedding has finished.
- await this.handler.whenFirstRenderComplete();
- this.setState({ loading: false });
- }
- }
-
- onChangeVisualization = async (ev) => {
- this.setState({
- selectedVis: ev.target.value,
- }, this.embedVisualization);
- };
-
- onSelectSample = async (ev) => {
- this.setState({
- selectedParams: ev.target.value,
- }, this.embedVisualization);
- };
-
- render() {
- const samples = [
- { value: '', text: '' },
- ...embeddingSamples.map(({ id, title }) => ({
- value: id,
- text: title,
- }))
- ];
-
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- { this.state.loading &&
-
-
-
- }
-
-
-
- {/*
- The element you want to render into should have its dimension set (via a fixed height, flexbox, absolute positioning, etc.),
- since the visualization will render with exactly the size of that element, i.e. the container size determines the
- visualization size.
- */}
-
-
-
-
-
- );
- }
-}
-
-export { Main };
diff --git a/test/plugin_functional/plugins/kbn_tp_visualize_embedding/public/embedding.js b/test/plugin_functional/plugins/kbn_tp_visualize_embedding/public/embedding.js
deleted file mode 100644
index 190e6331837b9..0000000000000
--- a/test/plugin_functional/plugins/kbn_tp_visualize_embedding/public/embedding.js
+++ /dev/null
@@ -1,187 +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.
- */
-
-/**
- * This files shows a couple of examples how to use the visualize loader API
- * to embed visualizations.
- */
-
-import { getVisualizeLoader } from 'ui/visualize';
-import chrome from 'ui/chrome';
-
-export const embeddingSamples = [
-
- {
- id: 'none',
- title: 'No parameters',
- async run(domNode, id) {
- // You always need to retrieve the visualize loader for embedding visualizations.
- const loader = await getVisualizeLoader();
- // Use the embedVisualizationWithId method to embed a visualization by its id. The id is the id of the
- // saved object in the .kibana index (you can find the id via Management -> Saved Objects).
- //
- // Pass in a DOM node that you want to embed that visualization into. Note: the loader will
- // use the size of that DOM node.
- //
- // The call will return a handler for the visualization with methods to interact with it.
- // Check the components/main.js file to see how this handler is used. Most important: you need to call
- // `destroy` on the handler once you are about to remove the visualization from the DOM.
- //
- // Note: If the visualization you want to embed contains date histograms with an auto interval, you need
- // to specify the timeRange parameter (see below).
- return loader.embedVisualizationWithId(domNode, id, {});
- }
- }, {
- id: 'timerange',
- title: 'timeRange',
- async run(domNode, id) {
- const loader = await getVisualizeLoader();
- // If you want to filter down the data to a specific time range, you can specify a
- // timeRange in the parameters to the embedding call.
- // You can either use an absolute time range as seen below. You can also specify
- // a datemath string, like "now-7d", "now-1w/w" for the from or to key.
- // You can also directly assign a moment JS or regular JavaScript Date object.
- return loader.embedVisualizationWithId(domNode, id, {
- timeRange: {
- from: '2015-09-20 20:00:00.000',
- to: '2015-09-21 20:00:00.000',
- }
- });
- }
- }, {
- id: 'query',
- title: 'query',
- async run(domNode, id) {
- const loader = await getVisualizeLoader();
- // You can specify a query that should filter down the data via the query parameter.
- // It must have a language key which must be one of the supported query languages of Kibana,
- // which are at the moment: 'lucene' or 'kquery'.
- // The query key must then hold the actual query in the specified language for filtering.
- return loader.embedVisualizationWithId(domNode, id, {
- query: {
- language: 'lucene',
- query: 'extension.raw:jpg',
- }
- });
- }
- }, {
- id: 'filters',
- title: 'filters',
- async run(domNode, id) {
- const loader = await getVisualizeLoader();
- // You can specify an array of filters that should apply to the query.
- // The format of a filter must match the format the filter bar is using internally.
- // This has a query key, which holds the query part of an Elasticsearch query
- // and a meta key allowing to set some meta values, most important for this API
- // the `negate` option to negate the filter.
- return loader.embedVisualizationWithId(domNode, id, {
- filters: [
- {
- query: {
- bool: {
- should: [
- { match_phrase: { 'extension.raw': 'jpg' } },
- { match_phrase: { 'extension.raw': 'png' } },
- ]
- }
- },
- meta: {
- negate: true
- }
- }
- ]
- });
- }
- }, {
- id: 'filters_query_timerange',
- title: 'filters & query & timeRange',
- async run(domNode, id) {
- const loader = await getVisualizeLoader();
- // You an of course combine timeRange, query and filters options all together
- // to filter the data in the embedded visualization.
- return loader.embedVisualizationWithId(domNode, id, {
- timeRange: {
- from: '2015-09-20 20:00:00.000',
- to: '2015-09-21 20:00:00.000',
- },
- query: {
- language: 'lucene',
- query: 'bytes:>2000'
- },
- filters: [
- {
- query: {
- bool: {
- should: [
- { match_phrase: { 'extension.raw': 'jpg' } },
- { match_phrase: { 'extension.raw': 'png' } },
- ]
- }
- },
- meta: {
- negate: true
- }
- }
- ]
- });
- }
- }, {
- id: 'savedobject_filter_query_timerange',
- title: 'filters & query & time (use saved object)',
- async run(domNode, id) {
- const loader = await getVisualizeLoader();
- // Besides embedding via the id of the visualizataion, the API offers the possibility to
- // embed via the saved visualization object.
- //
- // WE ADVISE YOU NOT TO USE THIS INSIDE ANY PLUGIN!
- //
- // Since the format of the saved visualization object will change in the future and because
- // this still requires you to talk to old Angular code, we do not encourage you to use this
- // way of embedding in any plugin. It's likely it will be removed or changed in a future version.
- const $injector = await chrome.dangerouslyGetActiveInjector();
- const savedVisualizations = $injector.get('savedVisualizations');
- const savedVis = await savedVisualizations.get(id);
- return loader.embedVisualizationWithSavedObject(domNode, savedVis, {
- timeRange: {
- from: '2015-09-20 20:00:00.000',
- to: '2015-09-21 20:00:00.000',
- },
- query: {
- language: 'lucene',
- query: 'bytes:>2000'
- },
- filters: [
- {
- query: {
- bool: {
- should: [
- { match_phrase: { 'extension.raw': 'jpg' } },
- { match_phrase: { 'extension.raw': 'png' } },
- ]
- }
- },
- meta: {
- negate: true
- }
- }
- ]
- });
- }
- }
-];
diff --git a/test/plugin_functional/test_suites/core_plugins/applications.ts b/test/plugin_functional/test_suites/core_plugins/applications.ts
index eec2ec019a515..138e20b987761 100644
--- a/test/plugin_functional/test_suites/core_plugins/applications.ts
+++ b/test/plugin_functional/test_suites/core_plugins/applications.ts
@@ -91,6 +91,18 @@ export default function({ getService, getPageObjects }: PluginFunctionalProvider
await testSubjects.existOrFail('fooAppPageA');
});
+ it('navigating to chromeless application hides chrome', async () => {
+ await appsMenu.clickLink('Chromeless');
+ await loadingScreenNotShown();
+ expect(await testSubjects.exists('headerGlobalNav')).to.be(false);
+ });
+
+ it('navigating away from chromeless application shows chrome', async () => {
+ await browser.goBack();
+ 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/test/plugin_functional/test_suites/embedding_visualizations/embed_by_id.js b/test/plugin_functional/test_suites/embedding_visualizations/embed_by_id.js
deleted file mode 100644
index c877ec2e5e025..0000000000000
--- a/test/plugin_functional/test_suites/embedding_visualizations/embed_by_id.js
+++ /dev/null
@@ -1,189 +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 { delay } from 'bluebird';
-
-export default function ({ getService }) {
- const testSubjects = getService('testSubjects');
- const find = getService('find');
- const table = getService('table');
- const retry = getService('retry');
-
- async function selectVis(id) {
- await testSubjects.click('visSelect');
- await find.clickByCssSelector(`option[value="${id}"]`);
- }
-
- async function selectParams(id) {
- await testSubjects.click('embeddingParamsSelect');
- await find.clickByCssSelector(`option[value="${id}"]`);
- await retry.try(async () => {
- await testSubjects.waitForDeleted('visLoadingIndicator');
- });
- await delay(1000);
- }
-
- async function getTableData() {
- const data = await table.getDataFromTestSubj('paginated-table-body');
- // Strip away empty rows (at the bottom)
- return data.filter(row => !row.every(cell => !cell.trim()));
- }
-
- describe('embed by id', function describeIndexTests() {
- describe('vis on timebased data without date histogram', () => {
- before(async () => {
- await selectVis('timebased_no-datehistogram');
- });
-
- it('should correctly embed', async () => {
- await selectParams('none');
- const data = await getTableData();
- expect(data).to.be.eql([
- ['jpg', '9,109'],
- ['css', '2,159'],
- ['png', '1,373'],
- ['gif', '918'],
- ['php', '445'],
- ]);
- });
-
- it('should correctly embed specifying a timeRange', async () => {
- await selectParams('timerange');
- const data = await getTableData();
- expect(data).to.be.eql([
- ['jpg', '3,005'],
- ['css', '720'],
- ['png', '455'],
- ['gif', '300'],
- ['php', '142'],
- ]);
- });
-
- it('should correctly embed specifying a query', async () => {
- await selectParams('query');
- const data = await getTableData();
- expect(data).to.be.eql([
- ['jpg', '9,109'],
- ]);
- });
-
- it('should correctly embed specifying filters', async () => {
- await selectParams('filters');
- const data = await getTableData();
- expect(data).to.be.eql([
- ['css', '2,159'],
- ['gif', '918'],
- ['php', '445'],
- ]);
- });
-
- it('should correctly embed specifying filters and query and timeRange', async () => {
- await selectParams('filters_query_timerange');
- const data = await getTableData();
- expect(data).to.be.eql([
- ['css', '678'],
- ['php', '110'],
- ]);
- });
- });
-
- describe('vis on timebased data with date histogram with interval auto', () => {
- before(async () => {
- await selectVis('timebased');
- });
-
- it('should correctly embed specifying a timeRange', async () => {
- await selectParams('timerange');
- const data = await getTableData();
- expect(data).to.be.eql([
- ['2015-09-20 20:00', '45.159KB', '5.65KB'],
- ['2015-09-21 00:00', '42.428KB', '5.345KB'],
- ['2015-09-21 04:00', '43.717KB', '5.35KB'],
- ['2015-09-21 08:00', '43.228KB', '5.538KB'],
- ['2015-09-21 12:00', '42.83KB', '5.669KB'],
- ['2015-09-21 16:00', '44.908KB', '5.673KB'],
- ]);
- });
-
- it('should correctly embed specifying filters and query and timeRange', async () => {
- await selectParams('filters_query_timerange');
- const data = await getTableData();
- expect(data).to.be.eql([
- ['2015-09-20 20:00', '45.391KB', '5.692KB'],
- ['2015-09-21 00:00', '46.57KB', '5.953KB'],
- ['2015-09-21 04:00', '47.339KB', '6.636KB'],
- ['2015-09-21 08:00', '40.5KB', '6.133KB'],
- ['2015-09-21 12:00', '41.31KB', '5.84KB'],
- ['2015-09-21 16:00', '48.012KB', '6.003KB'],
- ]);
- });
- });
-
- describe('vis on timebased data with date histogram with interval auto and saved filters', () => {
- before(async () => {
- await selectVis('timebased_with-filters');
- });
-
- it('should correctly embed specifying a timeRange', async () => {
- await selectParams('timerange');
- const data = await getTableData();
- expect(data).to.be.eql([
- ['2015-09-20 20:00', '21.221KB', '2.66KB'],
- ['2015-09-21 00:00', '22.054KB', '2.63KB'],
- ['2015-09-21 04:00', '15.592KB', '2.547KB'],
- ['2015-09-21 08:00', '4.656KB', '2.328KB'],
- ['2015-09-21 12:00', '17.887KB', '2.694KB'],
- ['2015-09-21 16:00', '20.533KB', '2.529KB'],
- ]);
- });
-
- it('should correctly embed specifying filters and query and timeRange', async () => {
- await selectParams('filters_query_timerange');
- const data = await getTableData();
- expect(data).to.be.eql([
- ['2015-09-20 20:00', '24.567KB', '3.498KB'],
- ['2015-09-21 00:00', '25.984KB', '3.589KB'],
- ['2015-09-21 04:00', '2.543KB', '2.543KB'],
- ['2015-09-21 12:00', '5.783KB', '2.927KB'],
- ['2015-09-21 16:00', '21.107KB', '3.44KB'],
- ]);
- });
- });
-
- describe('vis visa saved object on timebased data with date histogram with interval auto and saved filters', () => {
- before(async () => {
- await selectVis('timebased_with-filters');
- });
-
- it('should correctly embed specifying filters and query and timeRange', async () => {
- await selectParams('savedobject_filter_query_timerange');
- const data = await getTableData();
- expect(data).to.be.eql([
- ['2015-09-20 20:00', '24.567KB', '3.498KB'],
- ['2015-09-21 00:00', '25.984KB', '3.589KB'],
- ['2015-09-21 04:00', '2.543KB', '2.543KB'],
- ['2015-09-21 12:00', '5.783KB', '2.927KB'],
- ['2015-09-21 16:00', '21.107KB', '3.44KB'],
- ]);
- });
- });
- });
-
-}
diff --git a/test/plugin_functional/test_suites/embedding_visualizations/index.js b/test/plugin_functional/test_suites/embedding_visualizations/index.js
deleted file mode 100644
index b54a500fcd1f2..0000000000000
--- a/test/plugin_functional/test_suites/embedding_visualizations/index.js
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-export default function ({ getService, getPageObjects, loadTestFile }) {
- const browser = getService('browser');
- const appsMenu = getService('appsMenu');
- const esArchiver = getService('esArchiver');
- const kibanaServer = getService('kibanaServer');
- const PageObjects = getPageObjects(['common', 'header']);
-
- describe('embedding visualizations', function () {
- before(async () => {
- await esArchiver.loadIfNeeded('../functional/fixtures/es_archiver/logstash_functional');
- await esArchiver.load('../functional/fixtures/es_archiver/visualize_embedding');
- await kibanaServer.uiSettings.replace({
- 'dateFormat:tz': 'Australia/North',
- 'defaultIndex': 'logstash-*',
- 'format:bytes:defaultPattern': '0,0.[000]b'
- });
- await browser.setWindowSize(1300, 900);
- await PageObjects.common.navigateToApp('settings');
- await appsMenu.clickLink('Embedding Vis');
- });
-
- loadTestFile(require.resolve('./embed_by_id'));
- });
-}
diff --git a/test/ui_capabilities/newsfeed_err/config.ts b/test/ui_capabilities/newsfeed_err/config.ts
new file mode 100644
index 0000000000000..1f5f770e8447c
--- /dev/null
+++ b/test/ui_capabilities/newsfeed_err/config.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 { FtrConfigProviderContext } from '@kbn/test/types/ftr';
+// @ts-ignore untyped module
+import getFunctionalConfig from '../../functional/config';
+
+// eslint-disable-next-line import/no-default-export
+export default async ({ readConfigFile }: FtrConfigProviderContext) => {
+ const functionalConfig = await getFunctionalConfig({ readConfigFile });
+
+ return {
+ ...functionalConfig,
+
+ testFiles: [require.resolve('./test')],
+
+ kbnTestServer: {
+ ...functionalConfig.kbnTestServer,
+ serverArgs: [
+ ...functionalConfig.kbnTestServer.serverArgs,
+ `--newsfeed.service.pathTemplate=/api/_newsfeed-FTS-external-service-simulators/kibana/crash.json`,
+ ],
+ },
+
+ 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 0000000000000..2aa81f34028a0
--- /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 0fba7d2cefbd5..9d601e680cf87 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 a85da7a69534c..322966b3c982e 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/plugins/actions/server/lib/task_runner_factory.test.ts b/x-pack/legacy/plugins/actions/server/lib/task_runner_factory.test.ts
index 3e71725713070..a5bf42bc2cc01 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/actions/server/shim.ts b/x-pack/legacy/plugins/actions/server/shim.ts
index 0da6b84f2cc69..1af62d276f10b 100644
--- a/x-pack/legacy/plugins/actions/server/shim.ts
+++ b/x-pack/legacy/plugins/actions/server/shim.ts
@@ -42,7 +42,7 @@ export interface KibanaConfig {
*/
export type TaskManagerStartContract = Pick;
export type XPackMainPluginSetupContract = Pick;
-export type SecurityPluginSetupContract = Pick;
+export type SecurityPluginSetupContract = Pick;
export type SecurityPluginStartContract = Pick;
export type TaskManagerSetupContract = Pick<
TaskManager,
diff --git a/x-pack/legacy/plugins/alerting/server/shim.ts b/x-pack/legacy/plugins/alerting/server/shim.ts
index d86eab2038095..0ee1ef843d7d0 100644
--- a/x-pack/legacy/plugins/alerting/server/shim.ts
+++ b/x-pack/legacy/plugins/alerting/server/shim.ts
@@ -41,7 +41,7 @@ export interface Server extends Legacy.Server {
* Shim what we're thinking setup and start contracts will look like
*/
export type TaskManagerStartContract = Pick;
-export type SecurityPluginSetupContract = Pick;
+export type SecurityPluginSetupContract = Pick;
export type SecurityPluginStartContract = Pick;
export type XPackMainPluginSetupContract = Pick;
export type TaskManagerSetupContract = Pick<
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 0000000000000..ac43b700117c6
--- /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 9a8f11c6493c5..522f6d39ac71a 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/apm/common/transaction_types.ts b/x-pack/legacy/plugins/apm/common/transaction_types.ts
new file mode 100644
index 0000000000000..4dd59af63047d
--- /dev/null
+++ b/x-pack/legacy/plugins/apm/common/transaction_types.ts
@@ -0,0 +1,9 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+export const 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 556bce9d37bb5..bfbfb4bb99c6a 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 0b31798242fad..02296606b1c01 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 53f1893a168ac..69f0cf61af242 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 291208b2d9032..d52c869b95872 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 276d309cbb3e3..8005fc17f2a20 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 b69076b3a1f70..a118871a5e268 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 d03e70fc99cc6..0702e092a714f 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 db0ddb56e7088..fc86f4bb78afb 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 d81b7417570a5..f016052df56a2 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 7c0b6f24f87a7..9918f162a01f4 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 6f67b2458ea10..0aaeae3e4ce44 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 bdf895f423913..4398c129aa7b8 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 7e68b2f84eead..4378c7fdeee0c 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 ca14be237d22b..b7963b5c75a92 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 52afdffcb0839..d765a57a56a18 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 7b9586634c7d0..50c94fe88e6ad 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 12872fd64a3c4..adcce161c7ac1 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 40c6150149eb5..6176397170797 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 132067a6c32b7..94f30a8a2325a 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 b794332d4aa63..6b3fa1f0d98f7 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 ba74b0175ff71..bc6382841be3f 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 80a1b96efb3d6..2b0263f69db8f 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 a18882120fe75..a224df9e59e58 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 6db6e8848ef07..26cae303542a4 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 54106cce10bac..0000000000000
--- 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 754666b0a9fa2..640072d6ec4d8 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 8a91bd8781fe7..0000000000000
--- 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 b7081c43465bf..5bbd6be14a708 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 ee41599454dd6..9c111910f16f9 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 3af1d8c706f46..c685ffdd801dc 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 81dd8b34c8847..f164ca39d51c0 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 57de438be7f2a..f43b9ea11487a 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 3ec519d5e71b5..dcc034287863a 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 0b9407b288b1d..1aff1d772c5c2 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 861732ee03923..434eda8c0f46e 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 25a4f5141498f..23faa4b74cf8f 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 896363c054ba7..ed20a58b271e1 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 d5aa389cea335..52efc2b50305b 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 283f30b51441d..dd4d019ef7263 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 e5349edb67f30..b7b9c21172140 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 400bd0207771a..dcf7329b229d8 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 35d76d745cf4f..969bbc542f8a6 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 cd237a5264099..e942a26da373e 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 8de47c5c44144..e57e64942ab89 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 99553690359cf..ca10183bb259e 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 e092942a25ba6..ed6bdf203f2d4 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 67816d67a29a2..2648851789c66 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 a21c4f38ac30c..3d425415de832 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 cddc66e52cf70..3b9e80c901fe9 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 5056a100de3ce..0345b0815679f 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 5d10a4ae27060..a0149bec728c5 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 0458c8e4fedf0..e1cb1774469f2 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 b0461f5cb3b68..18fe547a34cf0 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 f969e4d6024ca..66f28a9bf6d44 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 100df4dc238fe..92e1284f3ed74 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 85d53925db86e..4b955c7a6e981 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 d25ad949d6dde..2867cef28d952 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 3c82a35ec7903..4afcf135a1a76 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 cde9fd1dd4ca9..0b5c29fc29857 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 a0ddffe044c15..77d96d3677494 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