= {
and: (
{bothArgumentsText},
}}
description="Full text: ' Requires both arguments to be true'. See
- 'xpack.data.kueryAutocomplete.andOperatorDescription.bothArgumentsText' for 'both arguments' part."
+ 'data.kueryAutocomplete.andOperatorDescription.bothArgumentsText' for 'both arguments' part."
/>
),
or: (
= {
),
}}
description="Full text: 'Requires one or more arguments to be true'. See
- 'xpack.data.kueryAutocomplete.orOperatorDescription.oneOrMoreArgumentsText' for 'one or more arguments' part."
+ 'data.kueryAutocomplete.orOperatorDescription.oneOrMoreArgumentsText' for 'one or more arguments' part."
/>
),
diff --git a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/field.test.ts b/src/plugins/data/public/autocomplete/providers/kql_query_suggestion/field.test.ts
similarity index 97%
rename from x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/field.test.ts
rename to src/plugins/data/public/autocomplete/providers/kql_query_suggestion/field.test.ts
index afc55d13af9d9..f1eced06a33ea 100644
--- a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/field.test.ts
+++ b/src/plugins/data/public/autocomplete/providers/kql_query_suggestion/field.test.ts
@@ -1,8 +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
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
*/
import indexPatternResponse from './__fixtures__/index_pattern_response.json';
diff --git a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/field.tsx b/src/plugins/data/public/autocomplete/providers/kql_query_suggestion/field.tsx
similarity index 92%
rename from x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/field.tsx
rename to src/plugins/data/public/autocomplete/providers/kql_query_suggestion/field.tsx
index ac6f7de888320..5cafca168dfa2 100644
--- a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/field.tsx
+++ b/src/plugins/data/public/autocomplete/providers/kql_query_suggestion/field.tsx
@@ -1,8 +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
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
*/
import React from 'react';
@@ -22,7 +23,7 @@ const getDescription = (field: IFieldType) => {
return (
{field.name} }}
/>
diff --git a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/index.ts b/src/plugins/data/public/autocomplete/providers/kql_query_suggestion/index.ts
similarity index 85%
rename from x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/index.ts
rename to src/plugins/data/public/autocomplete/providers/kql_query_suggestion/index.ts
index 8b36480a35b17..c5c1626ae74f6 100644
--- a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/index.ts
+++ b/src/plugins/data/public/autocomplete/providers/kql_query_suggestion/index.ts
@@ -1,8 +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
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
*/
import { CoreSetup } from 'kibana/public';
@@ -17,6 +18,7 @@ import {
QuerySuggestion,
QuerySuggestionGetFnArgs,
QuerySuggestionGetFn,
+ DataPublicPluginStart,
} from '../../../../../../../src/plugins/data/public';
const cursorSymbol = '@kuery-cursor@';
@@ -26,7 +28,9 @@ const dedup = (suggestions: QuerySuggestion[]): QuerySuggestion[] =>
export const KUERY_LANGUAGE_NAME = 'kuery';
-export const setupKqlQuerySuggestionProvider = (core: CoreSetup): QuerySuggestionGetFn => {
+export const setupKqlQuerySuggestionProvider = (
+ core: CoreSetup
+): QuerySuggestionGetFn => {
const providers = {
field: setupGetFieldSuggestions(core),
value: setupGetValueSuggestions(core),
diff --git a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/lib/escape_kuery.test.ts b/src/plugins/data/public/autocomplete/providers/kql_query_suggestion/lib/escape_kuery.test.ts
similarity index 92%
rename from x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/lib/escape_kuery.test.ts
rename to src/plugins/data/public/autocomplete/providers/kql_query_suggestion/lib/escape_kuery.test.ts
index 0173617a99b1b..933449e779ef7 100644
--- a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/lib/escape_kuery.test.ts
+++ b/src/plugins/data/public/autocomplete/providers/kql_query_suggestion/lib/escape_kuery.test.ts
@@ -1,8 +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
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
*/
import { escapeQuotes, escapeKuery } from './escape_kuery';
diff --git a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/lib/escape_kuery.ts b/src/plugins/data/public/autocomplete/providers/kql_query_suggestion/lib/escape_kuery.ts
similarity index 85%
rename from x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/lib/escape_kuery.ts
rename to src/plugins/data/public/autocomplete/providers/kql_query_suggestion/lib/escape_kuery.ts
index 901e61bde455d..54f03803a893e 100644
--- a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/lib/escape_kuery.ts
+++ b/src/plugins/data/public/autocomplete/providers/kql_query_suggestion/lib/escape_kuery.ts
@@ -1,8 +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
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
*/
import { flow } from 'lodash';
diff --git a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/operator.test.ts b/src/plugins/data/public/autocomplete/providers/kql_query_suggestion/operator.test.ts
similarity index 95%
rename from x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/operator.test.ts
rename to src/plugins/data/public/autocomplete/providers/kql_query_suggestion/operator.test.ts
index bd021b0d0dac5..4debbc0843d51 100644
--- a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/operator.test.ts
+++ b/src/plugins/data/public/autocomplete/providers/kql_query_suggestion/operator.test.ts
@@ -1,8 +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
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
*/
import indexPatternResponse from './__fixtures__/index_pattern_response.json';
diff --git a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/operator.tsx b/src/plugins/data/public/autocomplete/providers/kql_query_suggestion/operator.tsx
similarity index 65%
rename from x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/operator.tsx
rename to src/plugins/data/public/autocomplete/providers/kql_query_suggestion/operator.tsx
index cfe935e4b1990..618e33ddf345a 100644
--- a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/operator.tsx
+++ b/src/plugins/data/public/autocomplete/providers/kql_query_suggestion/operator.tsx
@@ -1,8 +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
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
*/
import React from 'react';
@@ -15,44 +16,44 @@ import { QuerySuggestionTypes } from '../../../../../../../src/plugins/data/publ
const equalsText = (
);
const lessThanOrEqualToText = (
);
const greaterThanOrEqualToText = (
);
const lessThanText = (
);
const greaterThanText = (
);
const existsText = (
);
@@ -60,11 +61,11 @@ const operators = {
':': {
description: (
{equalsText} }}
description="Full text: 'equals some value'. See
- 'xpack.data.kueryAutocomplete.equalOperatorDescription.equalsText' for 'equals' part."
+ 'data.kueryAutocomplete.equalOperatorDescription.equalsText' for 'equals' part."
/>
),
fieldTypes: [
@@ -83,7 +84,7 @@ const operators = {
'<=': {
description: (
),
fieldTypes: ['number', 'number_range', 'date', 'date_range', 'ip', 'ip_range'],
@@ -99,7 +100,7 @@ const operators = {
'>=': {
description: (
),
fieldTypes: ['number', 'number_range', 'date', 'date_range', 'ip', 'ip_range'],
@@ -115,11 +116,11 @@ const operators = {
'<': {
description: (
{lessThanText} }}
description="Full text: 'is less than some value'. See
- 'xpack.data.kueryAutocomplete.lessThanOperatorDescription.lessThanText' for 'less than' part."
+ 'data.kueryAutocomplete.lessThanOperatorDescription.lessThanText' for 'less than' part."
/>
),
fieldTypes: ['number', 'number_range', 'date', 'date_range', 'ip', 'ip_range'],
@@ -127,13 +128,13 @@ const operators = {
'>': {
description: (
{greaterThanText},
}}
description="Full text: 'is greater than some value'. See
- 'xpack.data.kueryAutocomplete.greaterThanOperatorDescription.greaterThanText' for 'greater than' part."
+ 'data.kueryAutocomplete.greaterThanOperatorDescription.greaterThanText' for 'greater than' part."
/>
),
fieldTypes: ['number', 'number_range', 'date', 'date_range', 'ip', 'ip_range'],
@@ -141,11 +142,11 @@ const operators = {
': *': {
description: (
{existsText} }}
description="Full text: 'exists in any form'. See
- 'xpack.data.kueryAutocomplete.existOperatorDescription.existsText' for 'exists' part."
+ 'data.kueryAutocomplete.existOperatorDescription.existsText' for 'exists' part."
/>
),
fieldTypes: undefined,
diff --git a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/sort_prefix_first.test.ts b/src/plugins/data/public/autocomplete/providers/kql_query_suggestion/sort_prefix_first.test.ts
similarity index 92%
rename from x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/sort_prefix_first.test.ts
rename to src/plugins/data/public/autocomplete/providers/kql_query_suggestion/sort_prefix_first.test.ts
index aa236a45fa93c..f72fb75684105 100644
--- a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/sort_prefix_first.test.ts
+++ b/src/plugins/data/public/autocomplete/providers/kql_query_suggestion/sort_prefix_first.test.ts
@@ -1,8 +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
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
*/
import { sortPrefixFirst } from './sort_prefix_first';
diff --git a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/sort_prefix_first.ts b/src/plugins/data/public/autocomplete/providers/kql_query_suggestion/sort_prefix_first.ts
similarity index 76%
rename from x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/sort_prefix_first.ts
rename to src/plugins/data/public/autocomplete/providers/kql_query_suggestion/sort_prefix_first.ts
index c344197641ef4..25bc32d47f338 100644
--- a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/sort_prefix_first.ts
+++ b/src/plugins/data/public/autocomplete/providers/kql_query_suggestion/sort_prefix_first.ts
@@ -1,8 +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
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
*/
import { partition } from 'lodash';
diff --git a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/types.ts b/src/plugins/data/public/autocomplete/providers/kql_query_suggestion/types.ts
similarity index 65%
rename from x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/types.ts
rename to src/plugins/data/public/autocomplete/providers/kql_query_suggestion/types.ts
index b5abdbee51832..48e87a73f3671 100644
--- a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/types.ts
+++ b/src/plugins/data/public/autocomplete/providers/kql_query_suggestion/types.ts
@@ -1,17 +1,19 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
*/
import { CoreSetup } from 'kibana/public';
import {
+ DataPublicPluginStart,
KueryNode,
QuerySuggestionBasic,
QuerySuggestionGetFnArgs,
} from '../../../../../../../src/plugins/data/public';
export type KqlQuerySuggestionProvider = (
- core: CoreSetup
+ core: CoreSetup
) => (querySuggestionsGetFnArgs: QuerySuggestionGetFnArgs, kueryNode: KueryNode) => Promise;
diff --git a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/value.test.ts b/src/plugins/data/public/autocomplete/providers/kql_query_suggestion/value.test.ts
similarity index 93%
rename from x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/value.test.ts
rename to src/plugins/data/public/autocomplete/providers/kql_query_suggestion/value.test.ts
index 5744dad43dcdd..c434d9a8ef365 100644
--- a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/value.test.ts
+++ b/src/plugins/data/public/autocomplete/providers/kql_query_suggestion/value.test.ts
@@ -1,15 +1,15 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
*/
import { setupGetValueSuggestions } from './value';
import indexPatternResponse from './__fixtures__/index_pattern_response.json';
import { coreMock } from '../../../../../../../src/core/public/mocks';
import { QuerySuggestionGetFnArgs, KueryNode } from '../../../../../../../src/plugins/data/public';
-import { setAutocompleteService } from '../../../services';
const mockKueryNode = (kueryNode: Partial) => (kueryNode as unknown) as KueryNode;
@@ -19,11 +19,6 @@ describe('Kuery value suggestions', () => {
let autocompleteServiceMock: any;
beforeEach(() => {
- getSuggestions = setupGetValueSuggestions(coreMock.createSetup());
- querySuggestionsArgs = ({
- indexPatterns: [indexPatternResponse],
- } as unknown) as QuerySuggestionGetFnArgs;
-
autocompleteServiceMock = {
getValueSuggestions: jest.fn(({ field }) => {
let res: any[];
@@ -40,7 +35,16 @@ describe('Kuery value suggestions', () => {
return Promise.resolve(res);
}),
};
- setAutocompleteService(autocompleteServiceMock);
+
+ const coreSetup = coreMock.createSetup({
+ pluginStartContract: {
+ autocomplete: autocompleteServiceMock,
+ },
+ });
+ getSuggestions = setupGetValueSuggestions(coreSetup);
+ querySuggestionsArgs = ({
+ indexPatterns: [indexPatternResponse],
+ } as unknown) as QuerySuggestionGetFnArgs;
jest.clearAllMocks();
});
diff --git a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/value.ts b/src/plugins/data/public/autocomplete/providers/kql_query_suggestion/value.ts
similarity index 79%
rename from x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/value.ts
rename to src/plugins/data/public/autocomplete/providers/kql_query_suggestion/value.ts
index 92fd4d7b71bdc..f8fc9d165fc6b 100644
--- a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/value.ts
+++ b/src/plugins/data/public/autocomplete/providers/kql_query_suggestion/value.ts
@@ -1,15 +1,17 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
*/
import { flatten } from 'lodash';
+import { CoreSetup } from 'kibana/public';
import { escapeQuotes } from './lib/escape_kuery';
import { KqlQuerySuggestionProvider } from './types';
-import { getAutocompleteService } from '../../../services';
import {
+ DataPublicPluginStart,
IFieldType,
IIndexPattern,
QuerySuggestion,
@@ -26,7 +28,12 @@ const wrapAsSuggestions = (start: number, end: number, query: string, values: st
end,
}));
-export const setupGetValueSuggestions: KqlQuerySuggestionProvider = () => {
+export const setupGetValueSuggestions: KqlQuerySuggestionProvider = (
+ core: CoreSetup
+) => {
+ const autoCompleteServicePromise = core
+ .getStartServices()
+ .then(([_, __, dataStart]) => dataStart.autocomplete);
return async (
{ indexPatterns, boolFilter, useTimeRange, signal },
{ start, end, prefix, suffix, fieldName, nestedPath }
@@ -41,7 +48,7 @@ export const setupGetValueSuggestions: KqlQuerySuggestionProvider = () => {
});
const query = `${prefix}${suffix}`.trim();
- const { getValueSuggestions } = getAutocompleteService();
+ const { getValueSuggestions } = await autoCompleteServicePromise;
const data = await Promise.all(
indexPatternFieldEntries.map(([indexPattern, field]) =>
diff --git a/src/plugins/data/public/mocks.ts b/src/plugins/data/public/mocks.ts
index a3676c5116927..573820890de71 100644
--- a/src/plugins/data/public/mocks.ts
+++ b/src/plugins/data/public/mocks.ts
@@ -17,7 +17,6 @@ export type Setup = jest.Mocked>;
export type Start = jest.Mocked>;
const automcompleteSetupMock: jest.Mocked = {
- addQuerySuggestionProvider: jest.fn(),
getQuerySuggestions: jest.fn(),
};
diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md
index 4eae5629af3a6..95d7a35a45320 100644
--- a/src/plugins/data/public/public.api.md
+++ b/src/plugins/data/public/public.api.md
@@ -23,7 +23,7 @@ import * as CSS from 'csstype';
import { Datatable as Datatable_2 } from 'src/plugins/expressions';
import { Datatable as Datatable_3 } from 'src/plugins/expressions/common';
import { DatatableColumn as DatatableColumn_2 } from 'src/plugins/expressions';
-import { DatatableColumnType } from 'src/plugins/expressions/common';
+import { DatatableColumnType as DatatableColumnType_2 } from 'src/plugins/expressions/common';
import { DetailedPeerCertificate } from 'tls';
import { Ensure } from '@kbn/utility-types';
import { EnvironmentMode } from '@kbn/config';
@@ -85,8 +85,8 @@ import { RequestAdapter } from 'src/plugins/inspector/common';
import { RequestStatistics as RequestStatistics_2 } from 'src/plugins/inspector/common';
import { Required } from '@kbn/utility-types';
import * as Rx from 'rxjs';
-import { SavedObject } from 'kibana/server';
-import { SavedObject as SavedObject_2 } from 'src/core/server';
+import { SavedObject } from 'src/core/server';
+import { SavedObject as SavedObject_2 } from 'kibana/server';
import { SavedObjectReference } from 'src/core/types';
import { SavedObjectsClientContract } from 'src/core/public';
import { SavedObjectsFindOptions } from 'kibana/public';
@@ -188,7 +188,7 @@ export class AggConfig {
// @deprecated (undocumented)
toJSON(): AggConfigSerialized;
// Warning: (ae-forgotten-export) The symbol "SerializableState" needs to be exported by the entry point index.d.ts
- toSerializedFieldFormat(): {} | Ensure, SerializableState>;
+ toSerializedFieldFormat(): {} | Ensure, SerializableState_2>;
// (undocumented)
get type(): IAggType;
set type(type: IAggType);
@@ -272,9 +272,9 @@ export type AggConfigSerialized = Ensure<{
type: string;
enabled?: boolean;
id?: string;
- params?: {} | SerializableState;
+ params?: {} | SerializableState_2;
schema?: string;
-}, SerializableState>;
+}, SerializableState_2>;
// Warning: (ae-missing-release-tag) "AggFunctionsMapping" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
@@ -1604,7 +1604,7 @@ export class IndexPatternsService {
// Warning: (ae-forgotten-export) The symbol "IndexPatternSavedObjectAttrs" needs to be exported by the entry point index.d.ts
//
// (undocumented)
- getCache: () => Promise[] | null | undefined>;
+ getCache: () => Promise[] | null | undefined>;
getDefault: () => Promise;
getFieldsForIndexPattern: (indexPattern: IndexPattern | IndexPatternSpec, options?: GetFieldsOptions | undefined) => Promise;
// Warning: (ae-forgotten-export) The symbol "GetFieldsOptions" needs to be exported by the entry point index.d.ts
@@ -1616,7 +1616,7 @@ export class IndexPatternsService {
}>>;
getTitles: (refresh?: boolean) => Promise;
refreshFields: (indexPattern: IndexPattern) => Promise;
- savedObjectToSpec: (savedObject: SavedObject_2) => IndexPatternSpec;
+ savedObjectToSpec: (savedObject: SavedObject) => IndexPatternSpec;
setDefault: (id: string, force?: boolean) => Promise;
updateSavedObject(indexPattern: IndexPattern, saveAttempts?: number, ignoreErrors?: boolean): Promise;
}
diff --git a/src/plugins/data/tsconfig.json b/src/plugins/data/tsconfig.json
index 21560b1328840..9c95878af631e 100644
--- a/src/plugins/data/tsconfig.json
+++ b/src/plugins/data/tsconfig.json
@@ -7,7 +7,14 @@
"declaration": true,
"declarationMap": true
},
- "include": ["common/**/*", "public/**/*", "server/**/*", "config.ts", "common/**/*.json"],
+ "include": [
+ "common/**/*",
+ "public/**/*",
+ "server/**/*",
+ "config.ts",
+ "common/**/*.json",
+ "public/**/*.json"
+ ],
"references": [
{ "path": "../../core/tsconfig.json" },
{ "path": "../bfetch/tsconfig.json" },
@@ -16,6 +23,6 @@
{ "path": "../inspector/tsconfig.json" },
{ "path": "../usage_collection/tsconfig.json" },
{ "path": "../kibana_utils/tsconfig.json" },
- { "path": "../kibana_react/tsconfig.json" },
+ { "path": "../kibana_react/tsconfig.json" }
]
}
diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/shared_imports.ts b/src/plugins/es_ui_shared/static/forms/hook_form_lib/shared_imports.ts
index 18eccb4e87090..c57333d788ef5 100644
--- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/shared_imports.ts
+++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/shared_imports.ts
@@ -6,6 +6,8 @@
* Side Public License, v 1.
*/
+// eslint-disable-next-line import/no-extraneous-dependencies
export { registerTestBed, TestBed } from '@kbn/test/jest';
+// eslint-disable-next-line import/no-extraneous-dependencies
export { getRandomString } from '@kbn/test/jest';
diff --git a/src/plugins/home/server/services/sample_data/data_sets/ecommerce/saved_objects.ts b/src/plugins/home/server/services/sample_data/data_sets/ecommerce/saved_objects.ts
index 9b69dacd8fdb5..cfac42b97c686 100644
--- a/src/plugins/home/server/services/sample_data/data_sets/ecommerce/saved_objects.ts
+++ b/src/plugins/home/server/services/sample_data/data_sets/ecommerce/saved_objects.ts
@@ -109,7 +109,7 @@ export const getSavedObjects = (): SavedObject[] => [
defaultMessage: '[eCommerce] Promotion Tracking',
}),
visState:
- '{"title":"[eCommerce] Promotion Tracking","type":"metrics","params":{"id":"61ca57f0-469d-11e7-af02-69e470af7417","type":"timeseries","series":[{"id":"ea20ae70-b88d-11e8-a451-f37365e9f268","color":"rgba(240,138,217,1)","split_mode":"everything","metrics":[{"id":"ea20ae71-b88d-11e8-a451-f37365e9f268","type":"sum","field":"taxful_total_price"}],"separate_axis":0,"axis_position":"right","formatter":"number","chart_type":"line","line_width":1,"point_size":1,"fill":"0.7","stacked":"none","filter":"products.product_name:*trouser*","label":"Revenue Trousers","value_template":"${{value}}"},{"id":"062d77b0-b88e-11e8-a451-f37365e9f268","color":"rgba(191,240,129,1)","split_mode":"everything","metrics":[{"id":"062d77b1-b88e-11e8-a451-f37365e9f268","type":"sum","field":"taxful_total_price"}],"separate_axis":0,"axis_position":"right","formatter":"number","chart_type":"line","line_width":1,"point_size":1,"fill":"0.7","stacked":"none","filter":"products.product_name:*watch*","label":"Revenue Watches","value_template":"${{value}}"},{"id":"61ca57f1-469d-11e7-af02-69e470af7417","color":"rgba(23,233,230,1)","split_mode":"everything","metrics":[{"id":"61ca57f2-469d-11e7-af02-69e470af7417","type":"sum","field":"taxful_total_price"}],"separate_axis":0,"axis_position":"right","formatter":"number","chart_type":"line","line_width":1,"point_size":1,"fill":"0.7","stacked":"none","filter":"products.product_name:*bag*","label":"Revenue Bags","value_template":"${{value}}"},{"id":"faa2c170-b88d-11e8-a451-f37365e9f268","color":"rgba(235,186,180,1)","split_mode":"everything","metrics":[{"id":"faa2c171-b88d-11e8-a451-f37365e9f268","type":"sum","field":"taxful_total_price"}],"separate_axis":0,"axis_position":"right","formatter":"number","chart_type":"line","line_width":1,"point_size":1,"fill":"0.7","stacked":"none","filter":"products.product_name:*cocktail dress*","label":"Revenue Cocktail Dresses","value_template":"${{value}}"}],"time_field":"order_date","index_pattern":"kibana_sample_data_ecommerce","interval":">=12h","axis_position":"left","axis_formatter":"number","axis_scale":"normal","show_legend":1,"show_grid":1,"legend_position":"bottom","annotations":[{"fields":"taxful_total_price","template":"Ring the bell! ${{taxful_total_price}}","index_pattern":"kibana_sample_data_ecommerce","query_string":"taxful_total_price:>250","id":"c8c30be0-b88f-11e8-a451-f37365e9f268","color":"rgba(25,77,51,1)","time_field":"order_date","icon":"fa-bell","ignore_global_filters":1,"ignore_panel_filters":1}]},"aggs":[]}',
+ '{"title":"[eCommerce] Promotion Tracking","type":"metrics","params":{"id":"61ca57f0-469d-11e7-af02-69e470af7417","type":"timeseries","series":[{"id":"ea20ae70-b88d-11e8-a451-f37365e9f268","color":"rgba(240,138,217,1)","split_mode":"everything","metrics":[{"id":"ea20ae71-b88d-11e8-a451-f37365e9f268","type":"sum","field":"taxful_total_price"}],"separate_axis":0,"axis_position":"right","formatter":"number","chart_type":"line","line_width":1,"point_size":1,"fill":"0.7","stacked":"none","filter":"products.product_name:*trouser*","label":"Revenue Trousers","value_template":"${{value}}"},{"id":"062d77b0-b88e-11e8-a451-f37365e9f268","color":"rgba(191,240,129,1)","split_mode":"everything","metrics":[{"id":"062d77b1-b88e-11e8-a451-f37365e9f268","type":"sum","field":"taxful_total_price"}],"separate_axis":0,"axis_position":"right","formatter":"number","chart_type":"line","line_width":1,"point_size":1,"fill":"0.7","stacked":"none","filter":"products.product_name:*watch*","label":"Revenue Watches","value_template":"${{value}}"},{"id":"61ca57f1-469d-11e7-af02-69e470af7417","color":"rgba(23,233,230,1)","split_mode":"everything","metrics":[{"id":"61ca57f2-469d-11e7-af02-69e470af7417","type":"sum","field":"taxful_total_price"}],"separate_axis":0,"axis_position":"right","formatter":"number","chart_type":"line","line_width":1,"point_size":1,"fill":"0.7","stacked":"none","filter":"products.product_name:*bag*","label":"Revenue Bags","value_template":"${{value}}"},{"id":"faa2c170-b88d-11e8-a451-f37365e9f268","color":"rgba(235,186,180,1)","split_mode":"everything","metrics":[{"id":"faa2c171-b88d-11e8-a451-f37365e9f268","type":"sum","field":"taxful_total_price"}],"separate_axis":0,"axis_position":"right","formatter":"number","chart_type":"line","line_width":1,"point_size":1,"fill":"0.7","stacked":"none","filter":"products.product_name:*cocktail dress*","label":"Revenue Cocktail Dresses","value_template":"${{value}}"}],"time_field":"order_date","index_pattern_ref_name":"ref_1_index_pattern","interval":">=12h","use_kibana_indexes":true,"axis_position":"left","axis_formatter":"number","axis_scale":"normal","show_legend":1,"show_grid":1,"legend_position":"bottom","annotations":[{"fields":"taxful_total_price","template":"Ring the bell! ${{taxful_total_price}}","index_pattern_ref_name":"ref_2_index_pattern","query_string":"taxful_total_price:>250","id":"c8c30be0-b88f-11e8-a451-f37365e9f268","color":"rgba(25,77,51,1)","time_field":"order_date","icon":"fa-bell","ignore_global_filters":1,"ignore_panel_filters":1}]},"aggs":[]}',
uiStateJSON: '{}',
description: '',
version: 1,
@@ -117,7 +117,18 @@ export const getSavedObjects = (): SavedObject[] => [
searchSourceJSON: '{"query":{"query":"","language":"kuery"},"filter":[]}',
},
},
- references: [],
+ references: [
+ {
+ name: 'ref_1_index_pattern',
+ type: 'index_pattern',
+ id: 'ff959d40-b880-11e8-a6d9-e546fe2bba5f',
+ },
+ {
+ name: 'ref_2_index_pattern',
+ type: 'index_pattern',
+ id: 'ff959d40-b880-11e8-a6d9-e546fe2bba5f',
+ },
+ ],
},
{
id: '10f1a240-b891-11e8-a6d9-e546fe2bba5f',
@@ -152,7 +163,7 @@ export const getSavedObjects = (): SavedObject[] => [
defaultMessage: '[eCommerce] Sold Products per Day',
}),
visState:
- '{"title":"[eCommerce] Sold Products per Day","type":"metrics","params":{"id":"61ca57f0-469d-11e7-af02-69e470af7417","type":"gauge","series":[{"id":"61ca57f1-469d-11e7-af02-69e470af7417","color":"#68BC00","split_mode":"everything","metrics":[{"id":"61ca57f2-469d-11e7-af02-69e470af7417","type":"count"}],"separate_axis":0,"axis_position":"right","formatter":"number","chart_type":"line","line_width":1,"point_size":1,"fill":0.5,"stacked":"none","label":"Trxns / day"}],"time_field":"order_date","index_pattern":"kibana_sample_data_ecommerce","interval":"1d","axis_position":"left","axis_formatter":"number","axis_scale":"normal","show_legend":1,"show_grid":1,"gauge_color_rules":[{"value":150,"id":"6da070c0-b891-11e8-b645-195edeb9de84","gauge":"rgba(104,188,0,1)","operator":"gte"},{"value":150,"id":"9b0cdbc0-b891-11e8-b645-195edeb9de84","gauge":"rgba(244,78,59,1)","operator":"lt"}],"gauge_width":"15","gauge_inner_width":10,"gauge_style":"half","filter":"","gauge_max":"300"},"aggs":[]}',
+ '{"title":"[eCommerce] Sold Products per Day","type":"metrics","params":{"id":"61ca57f0-469d-11e7-af02-69e470af7417","type":"gauge","series":[{"id":"61ca57f1-469d-11e7-af02-69e470af7417","color":"#68BC00","split_mode":"everything","metrics":[{"id":"61ca57f2-469d-11e7-af02-69e470af7417","type":"count"}],"separate_axis":0,"axis_position":"right","formatter":"number","chart_type":"line","line_width":1,"point_size":1,"fill":0.5,"stacked":"none","label":"Trxns / day"}],"time_field":"order_date","index_pattern_ref_name":"ref_1_index_pattern","interval":"1d","axis_position":"left","axis_formatter":"number","axis_scale":"normal","show_legend":1,"show_grid":1,"gauge_color_rules":[{"value":150,"id":"6da070c0-b891-11e8-b645-195edeb9de84","gauge":"rgba(104,188,0,1)","operator":"gte"},{"value":150,"id":"9b0cdbc0-b891-11e8-b645-195edeb9de84","gauge":"rgba(244,78,59,1)","operator":"lt"}],"gauge_width":"15","gauge_inner_width":10,"gauge_style":"half","filter":"","gauge_max":"300","use_kibana_indexes":true},"aggs":[]}',
uiStateJSON: '{}',
description: '',
version: 1,
@@ -160,7 +171,13 @@ export const getSavedObjects = (): SavedObject[] => [
searchSourceJSON: '{"query":{"query":"","language":"kuery"},"filter":[]}',
},
},
- references: [],
+ references: [
+ {
+ name: 'ref_1_index_pattern',
+ type: 'index_pattern',
+ id: 'ff959d40-b880-11e8-a6d9-e546fe2bba5f',
+ },
+ ],
},
{
id: '4b3ec120-b892-11e8-a6d9-e546fe2bba5f',
diff --git a/src/plugins/home/server/services/sample_data/data_sets/flights/saved_objects.ts b/src/plugins/home/server/services/sample_data/data_sets/flights/saved_objects.ts
index b316835029d7c..f16c1c7104417 100644
--- a/src/plugins/home/server/services/sample_data/data_sets/flights/saved_objects.ts
+++ b/src/plugins/home/server/services/sample_data/data_sets/flights/saved_objects.ts
@@ -144,7 +144,7 @@ export const getSavedObjects = (): SavedObject[] => [
defaultMessage: '[Flights] Delays & Cancellations',
}),
visState:
- '{"title":"[Flights] Delays & Cancellations","type":"metrics","params":{"id":"61ca57f0-469d-11e7-af02-69e470af7417","type":"timeseries","series":[{"id":"61ca57f1-469d-11e7-af02-69e470af7417","color":"rgba(0,156,224,1)","split_mode":"everything","metrics":[{"id":"61ca57f2-469d-11e7-af02-69e470af7417","type":"filter_ratio","numerator":"FlightDelay:true"}],"separate_axis":0,"axis_position":"right","formatter":"percent","chart_type":"line","line_width":"2","point_size":"0","fill":0.5,"stacked":"none","label":"Percent Delays"}],"time_field":"timestamp","index_pattern":"kibana_sample_data_flights","interval":">=1h","axis_position":"left","axis_formatter":"number","show_legend":1,"show_grid":1,"annotations":[{"fields":"FlightDelay,Cancelled,Carrier","template":"{{Carrier}}: Flight Delayed and Cancelled!","index_pattern":"kibana_sample_data_flights","query_string":"FlightDelay:true AND Cancelled:true","id":"53b7dff0-4c89-11e8-a66a-6989ad5a0a39","color":"rgba(0,98,177,1)","time_field":"timestamp","icon":"fa-exclamation-triangle","ignore_global_filters":1,"ignore_panel_filters":1}],"legend_position":"bottom"},"aggs":[]}',
+ '{"title":"[Flights] Delays & Cancellations","type":"metrics","params":{"id":"61ca57f0-469d-11e7-af02-69e470af7417","type":"timeseries","series":[{"id":"61ca57f1-469d-11e7-af02-69e470af7417","color":"rgba(0,156,224,1)","split_mode":"everything","metrics":[{"id":"61ca57f2-469d-11e7-af02-69e470af7417","type":"filter_ratio","numerator":"FlightDelay:true"}],"separate_axis":0,"axis_position":"right","formatter":"percent","chart_type":"line","line_width":"2","point_size":"0","fill":0.5,"stacked":"none","label":"Percent Delays"}],"time_field":"timestamp","index_pattern_ref_name":"ref_1_index_pattern","interval":">=1h","axis_position":"left","axis_formatter":"number","show_legend":1,"show_grid":1,"annotations":[{"fields":"FlightDelay,Cancelled,Carrier","template":"{{Carrier}}: Flight Delayed and Cancelled!","index_pattern_ref_name":"ref_2_index_pattern","query_string":"FlightDelay:true AND Cancelled:true","id":"53b7dff0-4c89-11e8-a66a-6989ad5a0a39","color":"rgba(0,98,177,1)","time_field":"timestamp","icon":"fa-exclamation-triangle","ignore_global_filters":1,"ignore_panel_filters":1}],"legend_position":"bottom","use_kibana_indexes":true},"aggs":[]}',
uiStateJSON: '{}',
description: '',
version: 1,
@@ -152,7 +152,18 @@ export const getSavedObjects = (): SavedObject[] => [
searchSourceJSON: '{}',
},
},
- references: [],
+ references: [
+ {
+ name: 'ref_1_index_pattern',
+ type: 'index_pattern',
+ id: 'd3d7af60-4c81-11e8-b3d7-01146121b73d'
+ },
+ {
+ name: 'ref_2_index_pattern',
+ type: 'index_pattern',
+ id: 'd3d7af60-4c81-11e8-b3d7-01146121b73d'
+ }
+ ]
},
{
id: '9886b410-4c8b-11e8-b3d7-01146121b73d',
diff --git a/src/plugins/home/server/services/sample_data/data_sets/logs/saved_objects.ts b/src/plugins/home/server/services/sample_data/data_sets/logs/saved_objects.ts
index 0396cb58d3692..8a3469fe4f3c0 100644
--- a/src/plugins/home/server/services/sample_data/data_sets/logs/saved_objects.ts
+++ b/src/plugins/home/server/services/sample_data/data_sets/logs/saved_objects.ts
@@ -89,7 +89,7 @@ export const getSavedObjects = (): SavedObject[] => [
defaultMessage: '[Logs] Host, Visits and Bytes Table',
}),
visState:
- '{"title":"[Logs] Host, Visits and Bytes Table","type":"metrics","params":{"id":"61ca57f0-469d-11e7-af02-69e470af7417","type":"table","series":[{"id":"bd09d600-e5b1-11e7-bfc2-a1f7e71965a1","color":"#68BC00","split_mode":"everything","metrics":[{"id":"bd09d601-e5b1-11e7-bfc2-a1f7e71965a1","type":"sum","field":"bytes"},{"sigma":"","id":"c9514c90-e5b1-11e7-bfc2-a1f7e71965a1","type":"sum_bucket","field":"bd09d601-e5b1-11e7-bfc2-a1f7e71965a1"}],"seperate_axis":0,"axis_position":"right","formatter":"bytes","chart_type":"line","line_width":1,"point_size":1,"fill":0.5,"stacked":"none","color_rules":[{"id":"c0c668d0-e5b1-11e7-bfc2-a1f7e71965a1"}],"label":"Bytes (Total)"},{"id":"b7672c30-a6df-11e8-8b18-1da1dfc50975","color":"#68BC00","split_mode":"everything","metrics":[{"id":"b7672c31-a6df-11e8-8b18-1da1dfc50975","type":"sum","field":"bytes"}],"seperate_axis":0,"axis_position":"right","formatter":"bytes","chart_type":"line","line_width":1,"point_size":1,"fill":0.5,"stacked":"none","color_rules":[{"id":"c0c668d0-e5b1-11e7-bfc2-a1f7e71965a1"}],"label":"Bytes (Last Hour)"},{"id":"f2c20700-a6df-11e8-8b18-1da1dfc50975","color":"#68BC00","split_mode":"everything","metrics":[{"id":"f2c20701-a6df-11e8-8b18-1da1dfc50975","type":"cardinality","field":"ip"},{"sigma":"","id":"f46333e0-a6df-11e8-8b18-1da1dfc50975","type":"sum_bucket","field":"f2c20701-a6df-11e8-8b18-1da1dfc50975"}],"seperate_axis":0,"axis_position":"right","formatter":"number","chart_type":"line","line_width":1,"point_size":1,"fill":0.5,"stacked":"none","label":"Unique Visits (Total)","color_rules":[{"value":1000,"id":"2e963080-a6e0-11e8-8b18-1da1dfc50975","text":"rgba(211,49,21,1)","operator":"lt"},{"value":1000,"id":"3d4fb880-a6e0-11e8-8b18-1da1dfc50975","text":"rgba(252,196,0,1)","operator":"gte"},{"value":1500,"id":"435f8a20-a6e0-11e8-8b18-1da1dfc50975","text":"rgba(104,188,0,1)","operator":"gte"}],"offset_time":"","value_template":"","trend_arrows":1},{"id":"46fd7fc0-e5b1-11e7-bfc2-a1f7e71965a1","color":"#68BC00","split_mode":"everything","metrics":[{"id":"46fd7fc1-e5b1-11e7-bfc2-a1f7e71965a1","type":"cardinality","field":"ip"}],"seperate_axis":0,"axis_position":"right","formatter":"number","chart_type":"line","line_width":1,"point_size":1,"fill":0.5,"stacked":"none","label":"Unique Visits (Last Hour)","color_rules":[{"value":10,"id":"4e90aeb0-a6e0-11e8-8b18-1da1dfc50975","text":"rgba(211,49,21,1)","operator":"lt"},{"value":10,"id":"6d59b1c0-a6e0-11e8-8b18-1da1dfc50975","text":"rgba(252,196,0,1)","operator":"gte"},{"value":25,"id":"77578670-a6e0-11e8-8b18-1da1dfc50975","text":"rgba(104,188,0,1)","operator":"gte"}],"offset_time":"","value_template":"","trend_arrows":1}],"time_field":"timestamp","index_pattern":"kibana_sample_data_logs","interval":"1h","axis_position":"left","axis_formatter":"number","show_legend":1,"show_grid":1,"bar_color_rules":[{"id":"e9b4e490-e1c6-11e7-b4f6-0f68c45f7387"}],"pivot_id":"extension.keyword","pivot_label":"Type","drilldown_url":"","axis_scale":"normal"},"aggs":[]}',
+ '{"title":"[Logs] Host, Visits and Bytes Table","type":"metrics","params":{"id":"61ca57f0-469d-11e7-af02-69e470af7417","type":"table","series":[{"id":"bd09d600-e5b1-11e7-bfc2-a1f7e71965a1","color":"#68BC00","split_mode":"everything","metrics":[{"id":"bd09d601-e5b1-11e7-bfc2-a1f7e71965a1","type":"sum","field":"bytes"},{"sigma":"","id":"c9514c90-e5b1-11e7-bfc2-a1f7e71965a1","type":"sum_bucket","field":"bd09d601-e5b1-11e7-bfc2-a1f7e71965a1"}],"seperate_axis":0,"axis_position":"right","formatter":"bytes","chart_type":"line","line_width":1,"point_size":1,"fill":0.5,"stacked":"none","color_rules":[{"id":"c0c668d0-e5b1-11e7-bfc2-a1f7e71965a1"}],"label":"Bytes (Total)"},{"id":"b7672c30-a6df-11e8-8b18-1da1dfc50975","color":"#68BC00","split_mode":"everything","metrics":[{"id":"b7672c31-a6df-11e8-8b18-1da1dfc50975","type":"sum","field":"bytes"}],"seperate_axis":0,"axis_position":"right","formatter":"bytes","chart_type":"line","line_width":1,"point_size":1,"fill":0.5,"stacked":"none","color_rules":[{"id":"c0c668d0-e5b1-11e7-bfc2-a1f7e71965a1"}],"label":"Bytes (Last Hour)"},{"id":"f2c20700-a6df-11e8-8b18-1da1dfc50975","color":"#68BC00","split_mode":"everything","metrics":[{"id":"f2c20701-a6df-11e8-8b18-1da1dfc50975","type":"cardinality","field":"ip"},{"sigma":"","id":"f46333e0-a6df-11e8-8b18-1da1dfc50975","type":"sum_bucket","field":"f2c20701-a6df-11e8-8b18-1da1dfc50975"}],"seperate_axis":0,"axis_position":"right","formatter":"number","chart_type":"line","line_width":1,"point_size":1,"fill":0.5,"stacked":"none","label":"Unique Visits (Total)","color_rules":[{"value":1000,"id":"2e963080-a6e0-11e8-8b18-1da1dfc50975","text":"rgba(211,49,21,1)","operator":"lt"},{"value":1000,"id":"3d4fb880-a6e0-11e8-8b18-1da1dfc50975","text":"rgba(252,196,0,1)","operator":"gte"},{"value":1500,"id":"435f8a20-a6e0-11e8-8b18-1da1dfc50975","text":"rgba(104,188,0,1)","operator":"gte"}],"offset_time":"","value_template":"","trend_arrows":1},{"id":"46fd7fc0-e5b1-11e7-bfc2-a1f7e71965a1","color":"#68BC00","split_mode":"everything","metrics":[{"id":"46fd7fc1-e5b1-11e7-bfc2-a1f7e71965a1","type":"cardinality","field":"ip"}],"seperate_axis":0,"axis_position":"right","formatter":"number","chart_type":"line","line_width":1,"point_size":1,"fill":0.5,"stacked":"none","label":"Unique Visits (Last Hour)","color_rules":[{"value":10,"id":"4e90aeb0-a6e0-11e8-8b18-1da1dfc50975","text":"rgba(211,49,21,1)","operator":"lt"},{"value":10,"id":"6d59b1c0-a6e0-11e8-8b18-1da1dfc50975","text":"rgba(252,196,0,1)","operator":"gte"},{"value":25,"id":"77578670-a6e0-11e8-8b18-1da1dfc50975","text":"rgba(104,188,0,1)","operator":"gte"}],"offset_time":"","value_template":"","trend_arrows":1}],"time_field":"timestamp","index_pattern_ref_name":"ref_1_index_pattern","use_kibana_indexes": true,"interval":"1h","axis_position":"left","axis_formatter":"number","show_legend":1,"show_grid":1,"bar_color_rules":[{"id":"e9b4e490-e1c6-11e7-b4f6-0f68c45f7387"}],"pivot_id":"extension.keyword","pivot_label":"Type","drilldown_url":"","axis_scale":"normal"},"aggs":[]}',
uiStateJSON: '{}',
description: '',
version: 1,
@@ -97,7 +97,13 @@ export const getSavedObjects = (): SavedObject[] => [
searchSourceJSON: '{"query":{"query":"","language":"kuery"},"filter":[]}',
},
},
- references: [],
+ references: [
+ {
+ name: 'ref_1_index_pattern',
+ type: 'index_pattern',
+ id: '90943e30-9a47-11e8-b64d-95841ca0b247',
+ },
+ ],
},
{
id: '69a34b00-9ee8-11e7-8711-e7a007dcef99',
@@ -175,7 +181,7 @@ export const getSavedObjects = (): SavedObject[] => [
defaultMessage: '[Logs] Response Codes Over Time + Annotations',
}),
visState:
- '{"title":"[Logs] Response Codes Over Time + Annotations","type":"metrics","params":{"id":"61ca57f0-469d-11e7-af02-69e470af7417","type":"timeseries","series":[{"id":"61ca57f1-469d-11e7-af02-69e470af7417","color":"rgba(115,216,255,1)","split_mode":"terms","metrics":[{"id":"61ca57f2-469d-11e7-af02-69e470af7417","type":"cardinality","field":"ip"}],"seperate_axis":0,"axis_position":"right","formatter":"percent","chart_type":"line","line_width":"2","point_size":"0","fill":"0.5","stacked":"percent","terms_field":"response.keyword","terms_order_by":"61ca57f2-469d-11e7-af02-69e470af7417","label":"Response Code Count","split_color_mode":"gradient"}],"time_field":"timestamp","index_pattern":"kibana_sample_data_logs","interval":">=4h","axis_position":"left","axis_formatter":"number","show_legend":1,"show_grid":1,"annotations":[{"fields":"geo.src, host","template":"Security Error from {{geo.src}} on {{host}}","index_pattern":"kibana_sample_data_logs","query_string":"tags:error AND tags:security","id":"bd7548a0-2223-11e8-832f-d5027f3c8a47","color":"rgba(211,49,21,1)","time_field":"timestamp","icon":"fa-asterisk","ignore_global_filters":1,"ignore_panel_filters":1}],"legend_position":"bottom","axis_scale":"normal","drop_last_bucket":0},"aggs":[]}',
+ '{"title":"[Logs] Response Codes Over Time + Annotations","type":"metrics","params":{"id":"61ca57f0-469d-11e7-af02-69e470af7417","type":"timeseries","series":[{"id":"61ca57f1-469d-11e7-af02-69e470af7417","color":"rgba(115,216,255,1)","split_mode":"terms","metrics":[{"id":"61ca57f2-469d-11e7-af02-69e470af7417","type":"cardinality","field":"ip"}],"seperate_axis":0,"axis_position":"right","formatter":"percent","chart_type":"line","line_width":"2","point_size":"0","fill":"0.5","stacked":"percent","terms_field":"response.keyword","terms_order_by":"61ca57f2-469d-11e7-af02-69e470af7417","label":"Response Code Count","split_color_mode":"gradient"}],"time_field":"timestamp","index_pattern_ref_name":"ref_1_index_pattern","use_kibana_indexes":true,"interval":">=4h","axis_position":"left","axis_formatter":"number","show_legend":1,"show_grid":1,"annotations":[{"fields":"geo.src, host","template":"Security Error from {{geo.src}} on {{host}}","index_pattern_ref_name":"ref_2_index_pattern","query_string":"tags:error AND tags:security","id":"bd7548a0-2223-11e8-832f-d5027f3c8a47","color":"rgba(211,49,21,1)","time_field":"timestamp","icon":"fa-asterisk","ignore_global_filters":1,"ignore_panel_filters":1}],"legend_position":"bottom","axis_scale":"normal","drop_last_bucket":0},"aggs":[]}',
uiStateJSON: '{}',
description: '',
version: 1,
@@ -183,7 +189,18 @@ export const getSavedObjects = (): SavedObject[] => [
searchSourceJSON: '{"query":{"query":"","language":"kuery"},"filter":[]}',
},
},
- references: [],
+ references: [
+ {
+ name: 'ref_1_index_pattern',
+ type: 'index_pattern',
+ id: '90943e30-9a47-11e8-b64d-95841ca0b247',
+ },
+ {
+ name: 'ref_2_index_pattern',
+ type: 'index_pattern',
+ id: '90943e30-9a47-11e8-b64d-95841ca0b247',
+ },
+ ],
},
{
id: '24a3e970-4257-11e8-b3aa-73fdaf54bfc9',
diff --git a/src/plugins/index_pattern_field_editor/public/components/delete_field_modal.tsx b/src/plugins/index_pattern_field_editor/public/components/delete_field_modal.tsx
index fca3eaf10b1ef..73a4837d6e0cc 100644
--- a/src/plugins/index_pattern_field_editor/public/components/delete_field_modal.tsx
+++ b/src/plugins/index_pattern_field_editor/public/components/delete_field_modal.tsx
@@ -6,12 +6,13 @@
* Side Public License, v 1.
*/
-import React from 'react';
+import React, { useState } from 'react';
import { i18n } from '@kbn/i18n';
-import { EuiConfirmModal } from '@elastic/eui';
+import { EuiCallOut, EuiConfirmModal, EuiFieldText, EuiFormRow, EuiSpacer } from '@elastic/eui';
const geti18nTexts = (fieldsToDelete?: string[]) => {
let modalTitle = '';
+ let confirmButtonText = '';
if (fieldsToDelete) {
const isSingle = fieldsToDelete.length === 1;
@@ -19,27 +20,35 @@ const geti18nTexts = (fieldsToDelete?: string[]) => {
? i18n.translate(
'indexPatternFieldEditor.deleteRuntimeField.confirmModal.deleteSingleTitle',
{
- defaultMessage: `Remove field '{name}'?`,
+ defaultMessage: `Remove field '{name}'`,
values: { name: fieldsToDelete[0] },
}
)
: i18n.translate(
'indexPatternFieldEditor.deleteRuntimeField.confirmModal.deleteMultipleTitle',
{
- defaultMessage: `Remove {count} fields?`,
+ defaultMessage: `Remove {count} fields`,
values: { count: fieldsToDelete.length },
}
);
+ confirmButtonText = isSingle
+ ? i18n.translate(
+ 'indexPatternFieldEditor.deleteRuntimeField.confirmationModal.removeButtonLabel',
+ {
+ defaultMessage: `Remove field`,
+ }
+ )
+ : i18n.translate(
+ 'indexPatternFieldEditor.deleteRuntimeField.confirmationModal.removeMultipleButtonLabel',
+ {
+ defaultMessage: `Remove fields`,
+ }
+ );
}
return {
modalTitle,
- confirmButtonText: i18n.translate(
- 'indexPatternFieldEditor.deleteRuntimeField.confirmationModal.removeButtonLabel',
- {
- defaultMessage: 'Remove',
- }
- ),
+ confirmButtonText,
cancelButtonText: i18n.translate(
'indexPatternFieldEditor.deleteRuntimeField.confirmationModal.cancelButtonLabel',
{
@@ -52,6 +61,19 @@ const geti18nTexts = (fieldsToDelete?: string[]) => {
defaultMessage: 'You are about to remove these runtime fields:',
}
),
+ typeConfirm: i18n.translate(
+ 'indexPatternFieldEditor.deleteRuntimeField.confirmModal.typeConfirm',
+ {
+ defaultMessage: "Type 'REMOVE' to confirm",
+ }
+ ),
+ warningRemovingFields: i18n.translate(
+ 'indexPatternFieldEditor.deleteRuntimeField.confirmModal.warningRemovingFields',
+ {
+ defaultMessage:
+ 'Warning: Removing fields may break searches or visualizations that rely on this field.',
+ }
+ ),
};
};
@@ -65,6 +87,7 @@ export function DeleteFieldModal({ fieldsToDelete, closeModal, confirmDelete }:
const i18nTexts = geti18nTexts(fieldsToDelete);
const { modalTitle, confirmButtonText, cancelButtonText, warningMultipleFields } = i18nTexts;
const isMultiple = Boolean(fieldsToDelete.length > 1);
+ const [confirmContent, setConfirmContent] = useState();
return (
- {isMultiple && (
- <>
- {warningMultipleFields}
-
- {fieldsToDelete.map((fieldName) => (
- {fieldName}
- ))}
-
- >
- )}
+
+ {isMultiple && (
+ <>
+ {warningMultipleFields}
+
+ {fieldsToDelete.map((fieldName) => (
+ {fieldName}
+ ))}
+
+ >
+ )}
+
+
+
+ setConfirmContent(e.target.value)}
+ data-test-subj="deleteModalConfirmText"
+ />
+
);
}
diff --git a/src/plugins/index_pattern_field_editor/public/components/field_editor_flyout_content.test.ts b/src/plugins/index_pattern_field_editor/public/components/field_editor_flyout_content.test.ts
index e943dbdda998d..46414c264c6b7 100644
--- a/src/plugins/index_pattern_field_editor/public/components/field_editor_flyout_content.test.ts
+++ b/src/plugins/index_pattern_field_editor/public/components/field_editor_flyout_content.test.ts
@@ -68,7 +68,7 @@ describe(' ', () => {
const { find } = setup({ ...defaultProps, field });
- expect(find('flyoutTitle').text()).toBe(`Edit ${field.name} field`);
+ expect(find('flyoutTitle').text()).toBe(`Edit field 'foo'`);
expect(find('nameField.input').props().value).toBe(field.name);
expect(find('typeField').props().value).toBe(field.type);
expect(find('scriptField').props().value).toBe(field.script.source);
diff --git a/src/plugins/index_pattern_field_editor/public/components/field_editor_flyout_content.tsx b/src/plugins/index_pattern_field_editor/public/components/field_editor_flyout_content.tsx
index 1511836da85e7..486df1a7707af 100644
--- a/src/plugins/index_pattern_field_editor/public/components/field_editor_flyout_content.tsx
+++ b/src/plugins/index_pattern_field_editor/public/components/field_editor_flyout_content.tsx
@@ -8,6 +8,7 @@
import React, { useState, useCallback, useMemo } from 'react';
import { i18n } from '@kbn/i18n';
+import { FormattedMessage } from '@kbn/i18n/react';
import {
EuiFlyoutHeader,
EuiFlyoutBody,
@@ -19,6 +20,10 @@ import {
EuiButton,
EuiCallOut,
EuiSpacer,
+ EuiText,
+ EuiConfirmModal,
+ EuiFieldText,
+ EuiFormRow,
} from '@elastic/eui';
import { DocLinksStart, CoreStart } from 'src/core/public';
@@ -30,16 +35,6 @@ import type { Props as FieldEditorProps, FieldEditorFormState } from './field_ed
const geti18nTexts = (field?: Field) => {
return {
- flyoutTitle: field
- ? i18n.translate('indexPatternFieldEditor.editor.flyoutEditFieldTitle', {
- defaultMessage: 'Edit {fieldName} field',
- values: {
- fieldName: field.name,
- },
- })
- : i18n.translate('indexPatternFieldEditor.editor.flyoutDefaultTitle', {
- defaultMessage: 'Create field',
- }),
closeButtonLabel: i18n.translate('indexPatternFieldEditor.editor.flyoutCloseButtonLabel', {
defaultMessage: 'Close',
}),
@@ -49,6 +44,31 @@ const geti18nTexts = (field?: Field) => {
formErrorsCalloutTitle: i18n.translate('indexPatternFieldEditor.editor.validationErrorTitle', {
defaultMessage: 'Fix errors in form before continuing.',
}),
+ cancelButtonText: i18n.translate(
+ 'indexPatternFieldEditor.saveRuntimeField.confirmationModal.cancelButtonLabel',
+ {
+ defaultMessage: 'Cancel',
+ }
+ ),
+ confirmButtonText: i18n.translate(
+ 'indexPatternFieldEditor.deleteRuntimeField.confirmationModal.saveButtonLabel',
+ {
+ defaultMessage: 'Save',
+ }
+ ),
+ warningChangingFields: i18n.translate(
+ 'indexPatternFieldEditor.deleteRuntimeField.confirmModal.warningChangingFields',
+ {
+ defaultMessage:
+ 'Warning: Changing name or type may break searches or visualizations that rely on this field.',
+ }
+ ),
+ typeConfirm: i18n.translate(
+ 'indexPatternFieldEditor.saveRuntimeField.confirmModal.typeConfirm',
+ {
+ defaultMessage: "Type 'CHANGE' to continue:",
+ }
+ ),
};
};
@@ -97,6 +117,7 @@ const FieldEditorFlyoutContentComponent = ({
runtimeFieldValidator,
isSavingField,
}: Props) => {
+ const isEditingExistingField = !!field;
const i18nTexts = geti18nTexts(field);
const [formState, setFormState] = useState({
@@ -112,6 +133,8 @@ const FieldEditorFlyoutContentComponent = ({
);
const [isValidating, setIsValidating] = useState(false);
+ const [isModalVisible, setIsModalVisible] = useState(false);
+ const [confirmContent, setConfirmContent] = useState();
const { submit, isValid: isFormValid, isSubmitted } = formState;
const { fields } = indexPattern;
@@ -129,6 +152,8 @@ const FieldEditorFlyoutContentComponent = ({
const onClickSave = useCallback(async () => {
const { isValid, data } = await submit();
+ const nameChange = field?.name !== data.name;
+ const typeChange = field?.type !== data.type;
if (isValid) {
if (data.script) {
@@ -147,9 +172,13 @@ const FieldEditorFlyoutContentComponent = ({
}
}
- onSave(data);
+ if (isEditingExistingField && (nameChange || typeChange)) {
+ setIsModalVisible(true);
+ } else {
+ onSave(data);
+ }
}
- }, [onSave, submit, runtimeFieldValidator]);
+ }, [onSave, submit, runtimeFieldValidator, field, isEditingExistingField]);
const namesNotAllowed = useMemo(() => fields.map((fld) => fld.name), [fields]);
@@ -180,12 +209,70 @@ const FieldEditorFlyoutContentComponent = ({
[fieldTypeToProcess, namesNotAllowed, existingConcreteFields]
);
+ const modal = isModalVisible ? (
+ {
+ setIsModalVisible(false);
+ setConfirmContent('');
+ }}
+ onConfirm={async () => {
+ const { data } = await submit();
+ onSave(data);
+ }}
+ >
+
+
+
+ setConfirmContent(e.target.value)}
+ data-test-subj="saveModalConfirmText"
+ />
+
+
+ ) : null;
return (
<>
-
- {i18nTexts.flyoutTitle}
+
+
+ {field ? (
+
+ ) : (
+
+ )}
+
+
+
+ {indexPattern.title},
+ }}
+ />
+
+
@@ -246,6 +333,7 @@ const FieldEditorFlyoutContentComponent = ({
>
)}
+ {modal}
>
);
};
diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/create_index_pattern_wizard.tsx b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/create_index_pattern_wizard.tsx
index 9ff26decc1c6e..633906feb785b 100644
--- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/create_index_pattern_wizard.tsx
+++ b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/create_index_pattern_wizard.tsx
@@ -57,10 +57,11 @@ export class CreateIndexPatternWizard extends Component<
context.services.setBreadcrumbs(getCreateBreadcrumbs());
const type = new URLSearchParams(props.location.search).get('type') || undefined;
+ const indexPattern = new URLSearchParams(props.location.search).get('name') || '';
this.state = {
step: 1,
- indexPattern: '',
+ indexPattern,
allIndices: [],
remoteClustersExist: false,
isInitiallyLoadingIndices: true,
diff --git a/src/plugins/index_pattern_management/server/routes/resolve_index.ts b/src/plugins/index_pattern_management/server/routes/resolve_index.ts
index 851a2578231aa..22c214f2adee2 100644
--- a/src/plugins/index_pattern_management/server/routes/resolve_index.ts
+++ b/src/plugins/index_pattern_management/server/routes/resolve_index.ts
@@ -31,19 +31,11 @@ export function registerResolveIndexRoute(router: IRouter): void {
},
},
async (context, req, res) => {
- const queryString = req.query.expand_wildcards
- ? { expand_wildcards: req.query.expand_wildcards }
- : null;
- const result = await context.core.elasticsearch.legacy.client.callAsCurrentUser(
- 'transport.request',
- {
- method: 'GET',
- path: `/_resolve/index/${encodeURIComponent(req.params.query)}${
- queryString ? '?' + new URLSearchParams(queryString).toString() : ''
- }`,
- }
- );
- return res.ok({ body: result });
+ const { body } = await context.core.elasticsearch.client.asCurrentUser.indices.resolveIndex({
+ name: req.params.query,
+ expand_wildcards: req.query.expand_wildcards || 'open',
+ });
+ return res.ok({ body });
}
);
}
diff --git a/src/plugins/kibana_usage_collection/server/collectors/application_usage/constants.ts b/src/plugins/kibana_usage_collection/server/collectors/application_usage/constants.ts
index 1910ba054bf8e..f072f044925bf 100644
--- a/src/plugins/kibana_usage_collection/server/collectors/application_usage/constants.ts
+++ b/src/plugins/kibana_usage_collection/server/collectors/application_usage/constants.ts
@@ -12,15 +12,9 @@
export const ROLL_TOTAL_INDICES_INTERVAL = 24 * 60 * 60 * 1000;
/**
- * Roll daily indices every 30 minutes.
- * This means that, assuming a user can visit all the 44 apps we can possibly report
- * in the 3 minutes interval the browser reports to the server, up to 22 users can have the same
- * behaviour and we wouldn't need to paginate in the transactional documents (less than 10k docs).
- *
- * Based on a more normal expected use case, the users could visit up to 5 apps in those 3 minutes,
- * allowing up to 200 users before reaching the limit.
+ * Roll daily indices every 24h
*/
-export const ROLL_DAILY_INDICES_INTERVAL = 30 * 60 * 1000;
+export const ROLL_DAILY_INDICES_INTERVAL = 24 * 60 * 60 * 1000;
/**
* Start rolling indices after 5 minutes up
diff --git a/src/plugins/kibana_usage_collection/server/collectors/application_usage/index.ts b/src/plugins/kibana_usage_collection/server/collectors/application_usage/index.ts
index 676f5fddc16e1..2d2d07d9d1894 100644
--- a/src/plugins/kibana_usage_collection/server/collectors/application_usage/index.ts
+++ b/src/plugins/kibana_usage_collection/server/collectors/application_usage/index.ts
@@ -7,3 +7,4 @@
*/
export { registerApplicationUsageCollector } from './telemetry_application_usage_collector';
+export { rollDailyData as migrateTransactionalDocs } from './rollups';
diff --git a/src/plugins/kibana_usage_collection/server/collectors/application_usage/rollups.test.ts b/src/plugins/kibana_usage_collection/server/collectors/application_usage/rollups/daily.test.ts
similarity index 51%
rename from src/plugins/kibana_usage_collection/server/collectors/application_usage/rollups.test.ts
rename to src/plugins/kibana_usage_collection/server/collectors/application_usage/rollups/daily.test.ts
index 7d86bc41e0b90..5acd1fb9c9c3a 100644
--- a/src/plugins/kibana_usage_collection/server/collectors/application_usage/rollups.test.ts
+++ b/src/plugins/kibana_usage_collection/server/collectors/application_usage/rollups/daily.test.ts
@@ -6,21 +6,16 @@
* Side Public License, v 1.
*/
-import { rollDailyData, rollTotals } from './rollups';
-import { savedObjectsRepositoryMock, loggingSystemMock } from '../../../../../core/server/mocks';
-import { MAIN_APP_DEFAULT_VIEW_ID } from '../../../../usage_collection/common/constants';
-import { SavedObjectsErrorHelpers } from '../../../../../core/server';
-import {
- SAVED_OBJECTS_DAILY_TYPE,
- SAVED_OBJECTS_TOTAL_TYPE,
- SAVED_OBJECTS_TRANSACTIONAL_TYPE,
-} from './saved_objects_types';
+import { savedObjectsRepositoryMock, loggingSystemMock } from '../../../../../../core/server/mocks';
+import { SavedObjectsErrorHelpers } from '../../../../../../core/server';
+import { SAVED_OBJECTS_DAILY_TYPE, SAVED_OBJECTS_TRANSACTIONAL_TYPE } from '../saved_objects_types';
+import { rollDailyData } from './daily';
describe('rollDailyData', () => {
const logger = loggingSystemMock.createLogger();
- test('returns undefined if no savedObjectsClient initialised yet', async () => {
- await expect(rollDailyData(logger, undefined)).resolves.toBe(undefined);
+ test('returns false if no savedObjectsClient initialised yet', async () => {
+ await expect(rollDailyData(logger, undefined)).resolves.toBe(false);
});
test('handle empty results', async () => {
@@ -33,7 +28,7 @@ describe('rollDailyData', () => {
throw new Error(`Unexpected type [${type}]`);
}
});
- await expect(rollDailyData(logger, savedObjectClient)).resolves.toBe(undefined);
+ await expect(rollDailyData(logger, savedObjectClient)).resolves.toBe(true);
expect(savedObjectClient.get).not.toBeCalled();
expect(savedObjectClient.bulkCreate).not.toBeCalled();
expect(savedObjectClient.delete).not.toBeCalled();
@@ -101,7 +96,7 @@ describe('rollDailyData', () => {
throw SavedObjectsErrorHelpers.createGenericNotFoundError(type, id);
});
- await expect(rollDailyData(logger, savedObjectClient)).resolves.toBe(undefined);
+ await expect(rollDailyData(logger, savedObjectClient)).resolves.toBe(true);
expect(savedObjectClient.get).toHaveBeenCalledTimes(2);
expect(savedObjectClient.get).toHaveBeenNthCalledWith(
1,
@@ -196,7 +191,7 @@ describe('rollDailyData', () => {
throw new Error('Something went terribly wrong');
});
- await expect(rollDailyData(logger, savedObjectClient)).resolves.toBe(undefined);
+ await expect(rollDailyData(logger, savedObjectClient)).resolves.toBe(false);
expect(savedObjectClient.get).toHaveBeenCalledTimes(1);
expect(savedObjectClient.get).toHaveBeenCalledWith(
SAVED_OBJECTS_DAILY_TYPE,
@@ -206,185 +201,3 @@ describe('rollDailyData', () => {
expect(savedObjectClient.delete).toHaveBeenCalledTimes(0);
});
});
-
-describe('rollTotals', () => {
- const logger = loggingSystemMock.createLogger();
-
- test('returns undefined if no savedObjectsClient initialised yet', async () => {
- await expect(rollTotals(logger, undefined)).resolves.toBe(undefined);
- });
-
- test('handle empty results', async () => {
- const savedObjectClient = savedObjectsRepositoryMock.create();
- savedObjectClient.find.mockImplementation(async ({ type, page = 1, perPage = 10 }) => {
- switch (type) {
- case SAVED_OBJECTS_DAILY_TYPE:
- case SAVED_OBJECTS_TOTAL_TYPE:
- return { saved_objects: [], total: 0, page, per_page: perPage };
- default:
- throw new Error(`Unexpected type [${type}]`);
- }
- });
- await expect(rollTotals(logger, savedObjectClient)).resolves.toBe(undefined);
- expect(savedObjectClient.bulkCreate).toHaveBeenCalledTimes(0);
- expect(savedObjectClient.delete).toHaveBeenCalledTimes(0);
- });
-
- test('migrate some documents', async () => {
- const savedObjectClient = savedObjectsRepositoryMock.create();
- savedObjectClient.find.mockImplementation(async ({ type, page = 1, perPage = 10 }) => {
- switch (type) {
- case SAVED_OBJECTS_DAILY_TYPE:
- return {
- saved_objects: [
- {
- id: 'appId-2:2020-01-01',
- type,
- score: 0,
- references: [],
- attributes: {
- appId: 'appId-2',
- timestamp: '2020-01-01T10:31:00.000Z',
- minutesOnScreen: 0.5,
- numberOfClicks: 1,
- },
- },
- {
- id: 'appId-1:2020-01-01',
- type,
- score: 0,
- references: [],
- attributes: {
- appId: 'appId-1',
- timestamp: '2020-01-01T11:31:00.000Z',
- minutesOnScreen: 2.5,
- numberOfClicks: 2,
- },
- },
- {
- id: 'appId-1:2020-01-01:viewId-1',
- type,
- score: 0,
- references: [],
- attributes: {
- appId: 'appId-1',
- viewId: 'viewId-1',
- timestamp: '2020-01-01T11:31:00.000Z',
- minutesOnScreen: 1,
- numberOfClicks: 1,
- },
- },
- ],
- total: 3,
- page,
- per_page: perPage,
- };
- case SAVED_OBJECTS_TOTAL_TYPE:
- return {
- saved_objects: [
- {
- id: 'appId-1',
- type,
- score: 0,
- references: [],
- attributes: {
- appId: 'appId-1',
- minutesOnScreen: 0.5,
- numberOfClicks: 1,
- },
- },
- {
- id: 'appId-1___viewId-1',
- type,
- score: 0,
- references: [],
- attributes: {
- appId: 'appId-1',
- viewId: 'viewId-1',
- minutesOnScreen: 4,
- numberOfClicks: 2,
- },
- },
- {
- id: 'appId-2___viewId-1',
- type,
- score: 0,
- references: [],
- attributes: {
- appId: 'appId-2',
- viewId: 'viewId-1',
- minutesOnScreen: 1,
- numberOfClicks: 1,
- },
- },
- ],
- total: 3,
- page,
- per_page: perPage,
- };
- default:
- throw new Error(`Unexpected type [${type}]`);
- }
- });
- await expect(rollTotals(logger, savedObjectClient)).resolves.toBe(undefined);
- expect(savedObjectClient.bulkCreate).toHaveBeenCalledTimes(1);
- expect(savedObjectClient.bulkCreate).toHaveBeenCalledWith(
- [
- {
- type: SAVED_OBJECTS_TOTAL_TYPE,
- id: 'appId-1',
- attributes: {
- appId: 'appId-1',
- viewId: MAIN_APP_DEFAULT_VIEW_ID,
- minutesOnScreen: 3.0,
- numberOfClicks: 3,
- },
- },
- {
- type: SAVED_OBJECTS_TOTAL_TYPE,
- id: 'appId-1___viewId-1',
- attributes: {
- appId: 'appId-1',
- viewId: 'viewId-1',
- minutesOnScreen: 5.0,
- numberOfClicks: 3,
- },
- },
- {
- type: SAVED_OBJECTS_TOTAL_TYPE,
- id: 'appId-2___viewId-1',
- attributes: {
- appId: 'appId-2',
- viewId: 'viewId-1',
- minutesOnScreen: 1.0,
- numberOfClicks: 1,
- },
- },
- {
- type: SAVED_OBJECTS_TOTAL_TYPE,
- id: 'appId-2',
- attributes: {
- appId: 'appId-2',
- viewId: MAIN_APP_DEFAULT_VIEW_ID,
- minutesOnScreen: 0.5,
- numberOfClicks: 1,
- },
- },
- ],
- { overwrite: true }
- );
- expect(savedObjectClient.delete).toHaveBeenCalledTimes(3);
- expect(savedObjectClient.delete).toHaveBeenCalledWith(
- SAVED_OBJECTS_DAILY_TYPE,
- 'appId-2:2020-01-01'
- );
- expect(savedObjectClient.delete).toHaveBeenCalledWith(
- SAVED_OBJECTS_DAILY_TYPE,
- 'appId-1:2020-01-01'
- );
- expect(savedObjectClient.delete).toHaveBeenCalledWith(
- SAVED_OBJECTS_DAILY_TYPE,
- 'appId-1:2020-01-01:viewId-1'
- );
- });
-});
diff --git a/src/plugins/kibana_usage_collection/server/collectors/application_usage/rollups.ts b/src/plugins/kibana_usage_collection/server/collectors/application_usage/rollups/daily.ts
similarity index 55%
rename from src/plugins/kibana_usage_collection/server/collectors/application_usage/rollups.ts
rename to src/plugins/kibana_usage_collection/server/collectors/application_usage/rollups/daily.ts
index df7e7662b49cf..a7873c7d5dfe9 100644
--- a/src/plugins/kibana_usage_collection/server/collectors/application_usage/rollups.ts
+++ b/src/plugins/kibana_usage_collection/server/collectors/application_usage/rollups/daily.ts
@@ -6,18 +6,20 @@
* Side Public License, v 1.
*/
-import { ISavedObjectsRepository, SavedObject, Logger } from 'kibana/server';
import moment from 'moment';
+import type { Logger } from '@kbn/logging';
+import {
+ ISavedObjectsRepository,
+ SavedObject,
+ SavedObjectsErrorHelpers,
+} from '../../../../../../core/server';
+import { getDailyId } from '../../../../../usage_collection/common/application_usage';
import {
ApplicationUsageDaily,
- ApplicationUsageTotal,
ApplicationUsageTransactional,
SAVED_OBJECTS_DAILY_TYPE,
- SAVED_OBJECTS_TOTAL_TYPE,
SAVED_OBJECTS_TRANSACTIONAL_TYPE,
-} from './saved_objects_types';
-import { SavedObjectsErrorHelpers } from '../../../../../../src/core/server';
-import { MAIN_APP_DEFAULT_VIEW_ID } from '../../../../usage_collection/common/constants';
+} from '../saved_objects_types';
/**
* For Rolling the daily data, we only care about the stored attributes and the version (to avoid overwriting via concurrent requests)
@@ -27,18 +29,17 @@ type ApplicationUsageDailyWithVersion = Pick<
'version' | 'attributes'
>;
-export function serializeKey(appId: string, viewId: string) {
- return `${appId}___${viewId}`;
-}
-
/**
* Aggregates all the transactional events into daily aggregates
* @param logger
* @param savedObjectsClient
*/
-export async function rollDailyData(logger: Logger, savedObjectsClient?: ISavedObjectsRepository) {
+export async function rollDailyData(
+ logger: Logger,
+ savedObjectsClient?: ISavedObjectsRepository
+): Promise {
if (!savedObjectsClient) {
- return;
+ return false;
}
try {
@@ -58,10 +59,7 @@ export async function rollDailyData(logger: Logger, savedObjectsClient?: ISavedO
} = doc;
const dayId = moment(timestamp).format('YYYY-MM-DD');
- const dailyId =
- !viewId || viewId === MAIN_APP_DEFAULT_VIEW_ID
- ? `${appId}:${dayId}`
- : `${appId}:${dayId}:${viewId}`;
+ const dailyId = getDailyId({ dayId, appId, viewId });
const existingDoc =
toCreate.get(dailyId) ||
@@ -103,9 +101,11 @@ export async function rollDailyData(logger: Logger, savedObjectsClient?: ISavedO
}
}
} while (toCreate.size > 0);
+ return true;
} catch (err) {
logger.debug(`Failed to rollup transactional to daily entries`);
logger.debug(err);
+ return false;
}
}
@@ -125,7 +125,11 @@ async function getDailyDoc(
dayId: string
): Promise {
try {
- return await savedObjectsClient.get(SAVED_OBJECTS_DAILY_TYPE, id);
+ const { attributes, version } = await savedObjectsClient.get(
+ SAVED_OBJECTS_DAILY_TYPE,
+ id
+ );
+ return { attributes, version };
} catch (err) {
if (SavedObjectsErrorHelpers.isNotFoundError(err)) {
return {
@@ -142,91 +146,3 @@ async function getDailyDoc(
throw err;
}
}
-
-/**
- * Moves all the daily documents into aggregated "total" documents as we don't care about any granularity after 90 days
- * @param logger
- * @param savedObjectsClient
- */
-export async function rollTotals(logger: Logger, savedObjectsClient?: ISavedObjectsRepository) {
- if (!savedObjectsClient) {
- return;
- }
-
- try {
- const [
- { saved_objects: rawApplicationUsageTotals },
- { saved_objects: rawApplicationUsageDaily },
- ] = await Promise.all([
- savedObjectsClient.find({
- perPage: 10000,
- type: SAVED_OBJECTS_TOTAL_TYPE,
- }),
- savedObjectsClient.find({
- perPage: 10000,
- type: SAVED_OBJECTS_DAILY_TYPE,
- filter: `${SAVED_OBJECTS_DAILY_TYPE}.attributes.timestamp < now-90d`,
- }),
- ]);
-
- const existingTotals = rawApplicationUsageTotals.reduce(
- (
- acc,
- {
- attributes: { appId, viewId = MAIN_APP_DEFAULT_VIEW_ID, numberOfClicks, minutesOnScreen },
- }
- ) => {
- const key = viewId === MAIN_APP_DEFAULT_VIEW_ID ? appId : serializeKey(appId, viewId);
-
- return {
- ...acc,
- // No need to sum because there should be 1 document per appId only
- [key]: { appId, viewId, numberOfClicks, minutesOnScreen },
- };
- },
- {} as Record<
- string,
- { appId: string; viewId: string; minutesOnScreen: number; numberOfClicks: number }
- >
- );
-
- const totals = rawApplicationUsageDaily.reduce((acc, { attributes }) => {
- const {
- appId,
- viewId = MAIN_APP_DEFAULT_VIEW_ID,
- numberOfClicks,
- minutesOnScreen,
- } = attributes;
- const key = viewId === MAIN_APP_DEFAULT_VIEW_ID ? appId : serializeKey(appId, viewId);
- const existing = acc[key] || { minutesOnScreen: 0, numberOfClicks: 0 };
-
- return {
- ...acc,
- [key]: {
- appId,
- viewId,
- numberOfClicks: numberOfClicks + existing.numberOfClicks,
- minutesOnScreen: minutesOnScreen + existing.minutesOnScreen,
- },
- };
- }, existingTotals);
-
- await Promise.all([
- Object.entries(totals).length &&
- savedObjectsClient.bulkCreate(
- Object.entries(totals).map(([id, entry]) => ({
- type: SAVED_OBJECTS_TOTAL_TYPE,
- id,
- attributes: entry,
- })),
- { overwrite: true }
- ),
- ...rawApplicationUsageDaily.map(
- ({ id }) => savedObjectsClient.delete(SAVED_OBJECTS_DAILY_TYPE, id) // There is no bulkDelete :(
- ),
- ]);
- } catch (err) {
- logger.debug(`Failed to rollup daily entries to totals`);
- logger.debug(err);
- }
-}
diff --git a/src/plugins/kibana_usage_collection/server/collectors/application_usage/rollups/index.ts b/src/plugins/kibana_usage_collection/server/collectors/application_usage/rollups/index.ts
new file mode 100644
index 0000000000000..8f3d83613aa9d
--- /dev/null
+++ b/src/plugins/kibana_usage_collection/server/collectors/application_usage/rollups/index.ts
@@ -0,0 +1,11 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+export { rollDailyData } from './daily';
+export { rollTotals } from './total';
+export { serializeKey } from './utils';
diff --git a/src/plugins/kibana_usage_collection/server/collectors/application_usage/rollups/total.test.ts b/src/plugins/kibana_usage_collection/server/collectors/application_usage/rollups/total.test.ts
new file mode 100644
index 0000000000000..9fea955ab5d8a
--- /dev/null
+++ b/src/plugins/kibana_usage_collection/server/collectors/application_usage/rollups/total.test.ts
@@ -0,0 +1,194 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { savedObjectsRepositoryMock, loggingSystemMock } from '../../../../../../core/server/mocks';
+import { MAIN_APP_DEFAULT_VIEW_ID } from '../../../../../usage_collection/common/constants';
+import { SAVED_OBJECTS_DAILY_TYPE, SAVED_OBJECTS_TOTAL_TYPE } from '../saved_objects_types';
+import { rollTotals } from './total';
+
+describe('rollTotals', () => {
+ const logger = loggingSystemMock.createLogger();
+
+ test('returns undefined if no savedObjectsClient initialised yet', async () => {
+ await expect(rollTotals(logger, undefined)).resolves.toBe(undefined);
+ });
+
+ test('handle empty results', async () => {
+ const savedObjectClient = savedObjectsRepositoryMock.create();
+ savedObjectClient.find.mockImplementation(async ({ type, page = 1, perPage = 10 }) => {
+ switch (type) {
+ case SAVED_OBJECTS_DAILY_TYPE:
+ case SAVED_OBJECTS_TOTAL_TYPE:
+ return { saved_objects: [], total: 0, page, per_page: perPage };
+ default:
+ throw new Error(`Unexpected type [${type}]`);
+ }
+ });
+ await expect(rollTotals(logger, savedObjectClient)).resolves.toBe(undefined);
+ expect(savedObjectClient.bulkCreate).toHaveBeenCalledTimes(0);
+ expect(savedObjectClient.delete).toHaveBeenCalledTimes(0);
+ });
+
+ test('migrate some documents', async () => {
+ const savedObjectClient = savedObjectsRepositoryMock.create();
+ savedObjectClient.find.mockImplementation(async ({ type, page = 1, perPage = 10 }) => {
+ switch (type) {
+ case SAVED_OBJECTS_DAILY_TYPE:
+ return {
+ saved_objects: [
+ {
+ id: 'appId-2:2020-01-01',
+ type,
+ score: 0,
+ references: [],
+ attributes: {
+ appId: 'appId-2',
+ timestamp: '2020-01-01T10:31:00.000Z',
+ minutesOnScreen: 0.5,
+ numberOfClicks: 1,
+ },
+ },
+ {
+ id: 'appId-1:2020-01-01',
+ type,
+ score: 0,
+ references: [],
+ attributes: {
+ appId: 'appId-1',
+ timestamp: '2020-01-01T11:31:00.000Z',
+ minutesOnScreen: 2.5,
+ numberOfClicks: 2,
+ },
+ },
+ {
+ id: 'appId-1:2020-01-01:viewId-1',
+ type,
+ score: 0,
+ references: [],
+ attributes: {
+ appId: 'appId-1',
+ viewId: 'viewId-1',
+ timestamp: '2020-01-01T11:31:00.000Z',
+ minutesOnScreen: 1,
+ numberOfClicks: 1,
+ },
+ },
+ ],
+ total: 3,
+ page,
+ per_page: perPage,
+ };
+ case SAVED_OBJECTS_TOTAL_TYPE:
+ return {
+ saved_objects: [
+ {
+ id: 'appId-1',
+ type,
+ score: 0,
+ references: [],
+ attributes: {
+ appId: 'appId-1',
+ minutesOnScreen: 0.5,
+ numberOfClicks: 1,
+ },
+ },
+ {
+ id: 'appId-1___viewId-1',
+ type,
+ score: 0,
+ references: [],
+ attributes: {
+ appId: 'appId-1',
+ viewId: 'viewId-1',
+ minutesOnScreen: 4,
+ numberOfClicks: 2,
+ },
+ },
+ {
+ id: 'appId-2___viewId-1',
+ type,
+ score: 0,
+ references: [],
+ attributes: {
+ appId: 'appId-2',
+ viewId: 'viewId-1',
+ minutesOnScreen: 1,
+ numberOfClicks: 1,
+ },
+ },
+ ],
+ total: 3,
+ page,
+ per_page: perPage,
+ };
+ default:
+ throw new Error(`Unexpected type [${type}]`);
+ }
+ });
+ await expect(rollTotals(logger, savedObjectClient)).resolves.toBe(undefined);
+ expect(savedObjectClient.bulkCreate).toHaveBeenCalledTimes(1);
+ expect(savedObjectClient.bulkCreate).toHaveBeenCalledWith(
+ [
+ {
+ type: SAVED_OBJECTS_TOTAL_TYPE,
+ id: 'appId-1',
+ attributes: {
+ appId: 'appId-1',
+ viewId: MAIN_APP_DEFAULT_VIEW_ID,
+ minutesOnScreen: 3.0,
+ numberOfClicks: 3,
+ },
+ },
+ {
+ type: SAVED_OBJECTS_TOTAL_TYPE,
+ id: 'appId-1___viewId-1',
+ attributes: {
+ appId: 'appId-1',
+ viewId: 'viewId-1',
+ minutesOnScreen: 5.0,
+ numberOfClicks: 3,
+ },
+ },
+ {
+ type: SAVED_OBJECTS_TOTAL_TYPE,
+ id: 'appId-2___viewId-1',
+ attributes: {
+ appId: 'appId-2',
+ viewId: 'viewId-1',
+ minutesOnScreen: 1.0,
+ numberOfClicks: 1,
+ },
+ },
+ {
+ type: SAVED_OBJECTS_TOTAL_TYPE,
+ id: 'appId-2',
+ attributes: {
+ appId: 'appId-2',
+ viewId: MAIN_APP_DEFAULT_VIEW_ID,
+ minutesOnScreen: 0.5,
+ numberOfClicks: 1,
+ },
+ },
+ ],
+ { overwrite: true }
+ );
+ expect(savedObjectClient.delete).toHaveBeenCalledTimes(3);
+ expect(savedObjectClient.delete).toHaveBeenCalledWith(
+ SAVED_OBJECTS_DAILY_TYPE,
+ 'appId-2:2020-01-01'
+ );
+ expect(savedObjectClient.delete).toHaveBeenCalledWith(
+ SAVED_OBJECTS_DAILY_TYPE,
+ 'appId-1:2020-01-01'
+ );
+ expect(savedObjectClient.delete).toHaveBeenCalledWith(
+ SAVED_OBJECTS_DAILY_TYPE,
+ 'appId-1:2020-01-01:viewId-1'
+ );
+ });
+});
diff --git a/src/plugins/kibana_usage_collection/server/collectors/application_usage/rollups/total.ts b/src/plugins/kibana_usage_collection/server/collectors/application_usage/rollups/total.ts
new file mode 100644
index 0000000000000..e27c7b897d995
--- /dev/null
+++ b/src/plugins/kibana_usage_collection/server/collectors/application_usage/rollups/total.ts
@@ -0,0 +1,106 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import type { Logger } from '@kbn/logging';
+import type { ISavedObjectsRepository } from 'kibana/server';
+import { MAIN_APP_DEFAULT_VIEW_ID } from '../../../../../usage_collection/common/constants';
+import {
+ ApplicationUsageDaily,
+ ApplicationUsageTotal,
+ SAVED_OBJECTS_DAILY_TYPE,
+ SAVED_OBJECTS_TOTAL_TYPE,
+} from '../saved_objects_types';
+import { serializeKey } from './utils';
+
+/**
+ * Moves all the daily documents into aggregated "total" documents as we don't care about any granularity after 90 days
+ * @param logger
+ * @param savedObjectsClient
+ */
+export async function rollTotals(logger: Logger, savedObjectsClient?: ISavedObjectsRepository) {
+ if (!savedObjectsClient) {
+ return;
+ }
+
+ try {
+ const [
+ { saved_objects: rawApplicationUsageTotals },
+ { saved_objects: rawApplicationUsageDaily },
+ ] = await Promise.all([
+ savedObjectsClient.find({
+ perPage: 10000,
+ type: SAVED_OBJECTS_TOTAL_TYPE,
+ }),
+ savedObjectsClient.find({
+ perPage: 10000,
+ type: SAVED_OBJECTS_DAILY_TYPE,
+ filter: `${SAVED_OBJECTS_DAILY_TYPE}.attributes.timestamp < now-90d`,
+ }),
+ ]);
+
+ const existingTotals = rawApplicationUsageTotals.reduce(
+ (
+ acc,
+ {
+ attributes: { appId, viewId = MAIN_APP_DEFAULT_VIEW_ID, numberOfClicks, minutesOnScreen },
+ }
+ ) => {
+ const key = viewId === MAIN_APP_DEFAULT_VIEW_ID ? appId : serializeKey(appId, viewId);
+
+ return {
+ ...acc,
+ // No need to sum because there should be 1 document per appId only
+ [key]: { appId, viewId, numberOfClicks, minutesOnScreen },
+ };
+ },
+ {} as Record<
+ string,
+ { appId: string; viewId: string; minutesOnScreen: number; numberOfClicks: number }
+ >
+ );
+
+ const totals = rawApplicationUsageDaily.reduce((acc, { attributes }) => {
+ const {
+ appId,
+ viewId = MAIN_APP_DEFAULT_VIEW_ID,
+ numberOfClicks,
+ minutesOnScreen,
+ } = attributes;
+ const key = viewId === MAIN_APP_DEFAULT_VIEW_ID ? appId : serializeKey(appId, viewId);
+ const existing = acc[key] || { minutesOnScreen: 0, numberOfClicks: 0 };
+
+ return {
+ ...acc,
+ [key]: {
+ appId,
+ viewId,
+ numberOfClicks: numberOfClicks + existing.numberOfClicks,
+ minutesOnScreen: minutesOnScreen + existing.minutesOnScreen,
+ },
+ };
+ }, existingTotals);
+
+ await Promise.all([
+ Object.entries(totals).length &&
+ savedObjectsClient.bulkCreate(
+ Object.entries(totals).map(([id, entry]) => ({
+ type: SAVED_OBJECTS_TOTAL_TYPE,
+ id,
+ attributes: entry,
+ })),
+ { overwrite: true }
+ ),
+ ...rawApplicationUsageDaily.map(
+ ({ id }) => savedObjectsClient.delete(SAVED_OBJECTS_DAILY_TYPE, id) // There is no bulkDelete :(
+ ),
+ ]);
+ } catch (err) {
+ logger.debug(`Failed to rollup daily entries to totals`);
+ logger.debug(err);
+ }
+}
diff --git a/src/plugins/vis_type_timeseries/common/field_types.ts b/src/plugins/kibana_usage_collection/server/collectors/application_usage/rollups/utils.ts
similarity index 73%
rename from src/plugins/vis_type_timeseries/common/field_types.ts
rename to src/plugins/kibana_usage_collection/server/collectors/application_usage/rollups/utils.ts
index f9ebc83b4a5db..8be00e6287883 100644
--- a/src/plugins/vis_type_timeseries/common/field_types.ts
+++ b/src/plugins/kibana_usage_collection/server/collectors/application_usage/rollups/utils.ts
@@ -6,10 +6,6 @@
* Side Public License, v 1.
*/
-export enum FIELD_TYPES {
- BOOLEAN = 'boolean',
- DATE = 'date',
- GEO = 'geo_point',
- NUMBER = 'number',
- STRING = 'string',
+export function serializeKey(appId: string, viewId: string) {
+ return `${appId}___${viewId}`;
}
diff --git a/src/plugins/kibana_usage_collection/server/collectors/application_usage/saved_objects_types.ts b/src/plugins/kibana_usage_collection/server/collectors/application_usage/saved_objects_types.ts
index 9e71b5c3b032e..f2b996f3af97a 100644
--- a/src/plugins/kibana_usage_collection/server/collectors/application_usage/saved_objects_types.ts
+++ b/src/plugins/kibana_usage_collection/server/collectors/application_usage/saved_objects_types.ts
@@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
-import { SavedObjectAttributes, SavedObjectsServiceSetup } from 'kibana/server';
+import type { SavedObjectAttributes, SavedObjectsServiceSetup } from 'kibana/server';
/**
* Used for accumulating the totals of all the stats older than 90d
@@ -17,6 +17,7 @@ export interface ApplicationUsageTotal extends SavedObjectAttributes {
minutesOnScreen: number;
numberOfClicks: number;
}
+
export const SAVED_OBJECTS_TOTAL_TYPE = 'application_usage_totals';
/**
@@ -25,6 +26,8 @@ export const SAVED_OBJECTS_TOTAL_TYPE = 'application_usage_totals';
export interface ApplicationUsageTransactional extends ApplicationUsageTotal {
timestamp: string;
}
+
+/** @deprecated transactional type is no longer used, and only preserved for backward compatibility */
export const SAVED_OBJECTS_TRANSACTIONAL_TYPE = 'application_usage_transactional';
/**
@@ -62,6 +65,7 @@ export function registerMappings(registerType: SavedObjectsServiceSetup['registe
});
// Type for storing ApplicationUsageTransactional (declaring empty mappings because we don't use the internal fields for query/aggregations)
+ // Remark: this type is deprecated and only here for BWC reasons.
registerType({
name: SAVED_OBJECTS_TRANSACTIONAL_TYPE,
hidden: false,
diff --git a/src/plugins/kibana_usage_collection/server/collectors/application_usage/schema.ts b/src/plugins/kibana_usage_collection/server/collectors/application_usage/schema.ts
index 062d751ef454c..693e9132fe536 100644
--- a/src/plugins/kibana_usage_collection/server/collectors/application_usage/schema.ts
+++ b/src/plugins/kibana_usage_collection/server/collectors/application_usage/schema.ts
@@ -7,7 +7,7 @@
*/
import { MakeSchemaFrom } from 'src/plugins/usage_collection/server';
-import { ApplicationUsageTelemetryReport } from './telemetry_application_usage_collector';
+import { ApplicationUsageTelemetryReport } from './types';
const commonSchema: MakeSchemaFrom = {
appId: { type: 'keyword', _meta: { description: 'The application being tracked' } },
diff --git a/src/plugins/kibana_usage_collection/server/collectors/application_usage/telemetry_application_usage_collector.test.ts b/src/plugins/kibana_usage_collection/server/collectors/application_usage/telemetry_application_usage_collector.test.ts
index 3e8434d446033..f1b21af5506e6 100644
--- a/src/plugins/kibana_usage_collection/server/collectors/application_usage/telemetry_application_usage_collector.test.ts
+++ b/src/plugins/kibana_usage_collection/server/collectors/application_usage/telemetry_application_usage_collector.test.ts
@@ -11,74 +11,99 @@ import {
Collector,
createUsageCollectionSetupMock,
} from '../../../../usage_collection/server/usage_collection.mock';
-
+import { MAIN_APP_DEFAULT_VIEW_ID } from '../../../../usage_collection/common/constants';
import { createCollectorFetchContextMock } from 'src/plugins/usage_collection/server/mocks';
-import { ROLL_TOTAL_INDICES_INTERVAL, ROLL_INDICES_START } from './constants';
import {
registerApplicationUsageCollector,
transformByApplicationViews,
- ApplicationUsageViews,
} from './telemetry_application_usage_collector';
-import { MAIN_APP_DEFAULT_VIEW_ID } from '../../../../usage_collection/common/constants';
-import {
- SAVED_OBJECTS_DAILY_TYPE,
- SAVED_OBJECTS_TOTAL_TYPE,
- SAVED_OBJECTS_TRANSACTIONAL_TYPE,
-} from './saved_objects_types';
+import { ApplicationUsageViews } from './types';
-describe('telemetry_application_usage', () => {
- jest.useFakeTimers();
+import { SAVED_OBJECTS_DAILY_TYPE, SAVED_OBJECTS_TOTAL_TYPE } from './saved_objects_types';
- const logger = loggingSystemMock.createLogger();
+// use fake timers to avoid triggering rollups during tests
+jest.useFakeTimers();
+describe('telemetry_application_usage', () => {
+ let logger: ReturnType;
let collector: Collector;
+ let usageCollectionMock: ReturnType;
+ let savedObjectClient: ReturnType;
+ let getSavedObjectClient: jest.MockedFunction<() => undefined | typeof savedObjectClient>;
- const usageCollectionMock = createUsageCollectionSetupMock();
- usageCollectionMock.makeUsageCollector.mockImplementation((config) => {
- collector = new Collector(logger, config);
- return createUsageCollectionSetupMock().makeUsageCollector(config);
- });
-
- const getUsageCollector = jest.fn();
const registerType = jest.fn();
const mockedFetchContext = createCollectorFetchContextMock();
- beforeAll(() =>
- registerApplicationUsageCollector(logger, usageCollectionMock, registerType, getUsageCollector)
- );
- afterAll(() => jest.clearAllTimers());
+ beforeEach(() => {
+ logger = loggingSystemMock.createLogger();
+ usageCollectionMock = createUsageCollectionSetupMock();
+ savedObjectClient = savedObjectsRepositoryMock.create();
+ getSavedObjectClient = jest.fn().mockReturnValue(savedObjectClient);
+ usageCollectionMock.makeUsageCollector.mockImplementation((config) => {
+ collector = new Collector(logger, config);
+ return createUsageCollectionSetupMock().makeUsageCollector(config);
+ });
+ registerApplicationUsageCollector(
+ logger,
+ usageCollectionMock,
+ registerType,
+ getSavedObjectClient
+ );
+ });
+
+ afterEach(() => {
+ jest.clearAllTimers();
+ });
test('registered collector is set', () => {
expect(collector).not.toBeUndefined();
});
test('if no savedObjectClient initialised, return undefined', async () => {
+ getSavedObjectClient.mockReturnValue(undefined);
+
expect(collector.isReady()).toBe(false);
expect(await collector.fetch(mockedFetchContext)).toBeUndefined();
- jest.runTimersToTime(ROLL_INDICES_START);
});
- test('when savedObjectClient is initialised, return something', async () => {
- const savedObjectClient = savedObjectsRepositoryMock.create();
- savedObjectClient.find.mockImplementation(
- async () =>
- ({
- saved_objects: [],
- total: 0,
- } as any)
+ test('calls `savedObjectsClient.find` with the correct parameters', async () => {
+ savedObjectClient.find.mockResolvedValue({
+ saved_objects: [],
+ total: 0,
+ per_page: 20,
+ page: 0,
+ });
+
+ await collector.fetch(mockedFetchContext);
+
+ expect(savedObjectClient.find).toHaveBeenCalledTimes(2);
+
+ expect(savedObjectClient.find).toHaveBeenCalledWith(
+ expect.objectContaining({
+ type: SAVED_OBJECTS_TOTAL_TYPE,
+ })
+ );
+ expect(savedObjectClient.find).toHaveBeenCalledWith(
+ expect.objectContaining({
+ type: SAVED_OBJECTS_DAILY_TYPE,
+ })
);
- getUsageCollector.mockImplementation(() => savedObjectClient);
+ });
- jest.runTimersToTime(ROLL_TOTAL_INDICES_INTERVAL); // Force rollTotals to run
+ test('when savedObjectClient is initialised, return something', async () => {
+ savedObjectClient.find.mockResolvedValue({
+ saved_objects: [],
+ total: 0,
+ per_page: 20,
+ page: 0,
+ });
expect(collector.isReady()).toBe(true);
expect(await collector.fetch(mockedFetchContext)).toStrictEqual({});
expect(savedObjectClient.bulkCreate).not.toHaveBeenCalled();
});
- test('it only gets 10k even when there are more documents (ES limitation)', async () => {
- const savedObjectClient = savedObjectsRepositoryMock.create();
- const total = 10000;
+ test('it aggregates total and daily data', async () => {
savedObjectClient.find.mockImplementation(async (opts) => {
switch (opts.type) {
case SAVED_OBJECTS_TOTAL_TYPE:
@@ -95,18 +120,6 @@ describe('telemetry_application_usage', () => {
],
total: 1,
} as any;
- case SAVED_OBJECTS_TRANSACTIONAL_TYPE:
- const doc = {
- id: 'test-id',
- attributes: {
- appId: 'appId',
- timestamp: new Date().toISOString(),
- minutesOnScreen: 0.5,
- numberOfClicks: 1,
- },
- };
- const savedObjects = new Array(total).fill(doc);
- return { saved_objects: savedObjects, total: total + 1 };
case SAVED_OBJECTS_DAILY_TYPE:
return {
saved_objects: [
@@ -125,122 +138,21 @@ describe('telemetry_application_usage', () => {
}
});
- getUsageCollector.mockImplementation(() => savedObjectClient);
-
- jest.runTimersToTime(ROLL_TOTAL_INDICES_INTERVAL); // Force rollTotals to run
-
expect(await collector.fetch(mockedFetchContext)).toStrictEqual({
appId: {
appId: 'appId',
viewId: 'main',
- clicks_total: total + 1 + 10,
- clicks_7_days: total + 1,
- clicks_30_days: total + 1,
- clicks_90_days: total + 1,
- minutes_on_screen_total: (total + 1) * 0.5 + 10,
- minutes_on_screen_7_days: (total + 1) * 0.5,
- minutes_on_screen_30_days: (total + 1) * 0.5,
- minutes_on_screen_90_days: (total + 1) * 0.5,
+ clicks_total: 1 + 10,
+ clicks_7_days: 1,
+ clicks_30_days: 1,
+ clicks_90_days: 1,
+ minutes_on_screen_total: 0.5 + 10,
+ minutes_on_screen_7_days: 0.5,
+ minutes_on_screen_30_days: 0.5,
+ minutes_on_screen_90_days: 0.5,
views: [],
},
});
- expect(savedObjectClient.bulkCreate).toHaveBeenCalledWith(
- [
- {
- id: 'appId',
- type: SAVED_OBJECTS_TOTAL_TYPE,
- attributes: {
- appId: 'appId',
- viewId: 'main',
- minutesOnScreen: 10.5,
- numberOfClicks: 11,
- },
- },
- ],
- { overwrite: true }
- );
- expect(savedObjectClient.delete).toHaveBeenCalledTimes(1);
- expect(savedObjectClient.delete).toHaveBeenCalledWith(
- SAVED_OBJECTS_DAILY_TYPE,
- 'appId:YYYY-MM-DD'
- );
- });
-
- test('old transactional data not migrated yet', async () => {
- const savedObjectClient = savedObjectsRepositoryMock.create();
- savedObjectClient.find.mockImplementation(async (opts) => {
- switch (opts.type) {
- case SAVED_OBJECTS_TOTAL_TYPE:
- case SAVED_OBJECTS_DAILY_TYPE:
- return { saved_objects: [], total: 0 } as any;
- case SAVED_OBJECTS_TRANSACTIONAL_TYPE:
- return {
- saved_objects: [
- {
- id: 'test-id',
- attributes: {
- appId: 'appId',
- timestamp: new Date(0).toISOString(),
- minutesOnScreen: 0.5,
- numberOfClicks: 1,
- },
- },
- {
- id: 'test-id-2',
- attributes: {
- appId: 'appId',
- viewId: 'main',
- timestamp: new Date(0).toISOString(),
- minutesOnScreen: 2,
- numberOfClicks: 2,
- },
- },
- {
- id: 'test-id-3',
- attributes: {
- appId: 'appId',
- viewId: 'viewId-1',
- timestamp: new Date(0).toISOString(),
- minutesOnScreen: 1,
- numberOfClicks: 1,
- },
- },
- ],
- total: 1,
- };
- }
- });
-
- getUsageCollector.mockImplementation(() => savedObjectClient);
-
- expect(await collector.fetch(mockedFetchContext)).toStrictEqual({
- appId: {
- appId: 'appId',
- viewId: 'main',
- clicks_total: 3,
- clicks_7_days: 0,
- clicks_30_days: 0,
- clicks_90_days: 0,
- minutes_on_screen_total: 2.5,
- minutes_on_screen_7_days: 0,
- minutes_on_screen_30_days: 0,
- minutes_on_screen_90_days: 0,
- views: [
- {
- appId: 'appId',
- viewId: 'viewId-1',
- clicks_total: 1,
- clicks_7_days: 0,
- clicks_30_days: 0,
- clicks_90_days: 0,
- minutes_on_screen_total: 1,
- minutes_on_screen_7_days: 0,
- minutes_on_screen_30_days: 0,
- minutes_on_screen_90_days: 0,
- },
- ],
- },
- });
});
});
diff --git a/src/plugins/kibana_usage_collection/server/collectors/application_usage/telemetry_application_usage_collector.ts b/src/plugins/kibana_usage_collection/server/collectors/application_usage/telemetry_application_usage_collector.ts
index ee1b42e61a6ca..a01f1bca4f0e0 100644
--- a/src/plugins/kibana_usage_collection/server/collectors/application_usage/telemetry_application_usage_collector.ts
+++ b/src/plugins/kibana_usage_collection/server/collectors/application_usage/telemetry_application_usage_collector.ts
@@ -11,57 +11,21 @@ import { timer } from 'rxjs';
import { ISavedObjectsRepository, Logger, SavedObjectsServiceSetup } from 'kibana/server';
import { UsageCollectionSetup } from 'src/plugins/usage_collection/server';
import { MAIN_APP_DEFAULT_VIEW_ID } from '../../../../usage_collection/common/constants';
-import { serializeKey } from './rollups';
-
import {
ApplicationUsageDaily,
ApplicationUsageTotal,
- ApplicationUsageTransactional,
registerMappings,
SAVED_OBJECTS_DAILY_TYPE,
SAVED_OBJECTS_TOTAL_TYPE,
- SAVED_OBJECTS_TRANSACTIONAL_TYPE,
} from './saved_objects_types';
import { applicationUsageSchema } from './schema';
-import { rollDailyData, rollTotals } from './rollups';
+import { rollTotals, rollDailyData, serializeKey } from './rollups';
import {
ROLL_TOTAL_INDICES_INTERVAL,
ROLL_DAILY_INDICES_INTERVAL,
ROLL_INDICES_START,
} from './constants';
-
-export interface ApplicationViewUsage {
- appId: string;
- viewId: string;
- clicks_total: number;
- clicks_7_days: number;
- clicks_30_days: number;
- clicks_90_days: number;
- minutes_on_screen_total: number;
- minutes_on_screen_7_days: number;
- minutes_on_screen_30_days: number;
- minutes_on_screen_90_days: number;
-}
-
-export interface ApplicationUsageViews {
- [serializedKey: string]: ApplicationViewUsage;
-}
-
-export interface ApplicationUsageTelemetryReport {
- [appId: string]: {
- appId: string;
- viewId: string;
- clicks_total: number;
- clicks_7_days: number;
- clicks_30_days: number;
- clicks_90_days: number;
- minutes_on_screen_total: number;
- minutes_on_screen_7_days: number;
- minutes_on_screen_30_days: number;
- minutes_on_screen_90_days: number;
- views?: ApplicationViewUsage[];
- };
-}
+import { ApplicationUsageTelemetryReport, ApplicationUsageViews } from './types';
export const transformByApplicationViews = (
report: ApplicationUsageViews
@@ -92,6 +56,21 @@ export function registerApplicationUsageCollector(
) {
registerMappings(registerType);
+ timer(ROLL_INDICES_START, ROLL_TOTAL_INDICES_INTERVAL).subscribe(() =>
+ rollTotals(logger, getSavedObjectsClient())
+ );
+
+ const dailyRollingSub = timer(ROLL_INDICES_START, ROLL_DAILY_INDICES_INTERVAL).subscribe(
+ async () => {
+ const success = await rollDailyData(logger, getSavedObjectsClient());
+ // we only need to roll the transactional documents once to assure BWC
+ // once we rolling succeeds, we can stop.
+ if (success) {
+ dailyRollingSub.unsubscribe();
+ }
+ }
+ );
+
const collector = usageCollection.makeUsageCollector(
{
type: 'application_usage',
@@ -105,7 +84,6 @@ export function registerApplicationUsageCollector(
const [
{ saved_objects: rawApplicationUsageTotals },
{ saved_objects: rawApplicationUsageDaily },
- { saved_objects: rawApplicationUsageTransactional },
] = await Promise.all([
savedObjectsClient.find({
type: SAVED_OBJECTS_TOTAL_TYPE,
@@ -115,10 +93,6 @@ export function registerApplicationUsageCollector(
type: SAVED_OBJECTS_DAILY_TYPE,
perPage: 10000, // We can have up to 44 apps * 91 days = 4004 docs. This limit is OK
}),
- savedObjectsClient.find({
- type: SAVED_OBJECTS_TRANSACTIONAL_TYPE,
- perPage: 10000, // If we have more than those, we won't report the rest (they'll be rolled up to the daily soon enough to become a problem)
- }),
]);
const applicationUsageFromTotals = rawApplicationUsageTotals.reduce(
@@ -156,10 +130,7 @@ export function registerApplicationUsageCollector(
const nowMinus30 = moment().subtract(30, 'days');
const nowMinus90 = moment().subtract(90, 'days');
- const applicationUsage = [
- ...rawApplicationUsageDaily,
- ...rawApplicationUsageTransactional,
- ].reduce(
+ const applicationUsage = rawApplicationUsageDaily.reduce(
(
acc,
{
@@ -224,11 +195,4 @@ export function registerApplicationUsageCollector(
);
usageCollection.registerCollector(collector);
-
- timer(ROLL_INDICES_START, ROLL_DAILY_INDICES_INTERVAL).subscribe(() =>
- rollDailyData(logger, getSavedObjectsClient())
- );
- timer(ROLL_INDICES_START, ROLL_TOTAL_INDICES_INTERVAL).subscribe(() =>
- rollTotals(logger, getSavedObjectsClient())
- );
}
diff --git a/src/plugins/kibana_usage_collection/server/collectors/application_usage/types.ts b/src/plugins/kibana_usage_collection/server/collectors/application_usage/types.ts
new file mode 100644
index 0000000000000..bef835e922d8d
--- /dev/null
+++ b/src/plugins/kibana_usage_collection/server/collectors/application_usage/types.ts
@@ -0,0 +1,40 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+export interface ApplicationViewUsage {
+ appId: string;
+ viewId: string;
+ clicks_total: number;
+ clicks_7_days: number;
+ clicks_30_days: number;
+ clicks_90_days: number;
+ minutes_on_screen_total: number;
+ minutes_on_screen_7_days: number;
+ minutes_on_screen_30_days: number;
+ minutes_on_screen_90_days: number;
+}
+
+export interface ApplicationUsageViews {
+ [serializedKey: string]: ApplicationViewUsage;
+}
+
+export interface ApplicationUsageTelemetryReport {
+ [appId: string]: {
+ appId: string;
+ viewId: string;
+ clicks_total: number;
+ clicks_7_days: number;
+ clicks_30_days: number;
+ clicks_90_days: number;
+ minutes_on_screen_total: number;
+ minutes_on_screen_7_days: number;
+ minutes_on_screen_30_days: number;
+ minutes_on_screen_90_days: number;
+ views?: ApplicationViewUsage[];
+ };
+}
diff --git a/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts b/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts
index 5959eb6aca4d4..41bb7c07bda7e 100644
--- a/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts
+++ b/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts
@@ -412,4 +412,8 @@ export const stackManagementSchema: MakeSchemaFrom = {
type: 'boolean',
_meta: { description: 'Non-default value of setting.' },
},
+ 'observability:enableInspectEsQueries': {
+ type: 'boolean',
+ _meta: { description: 'Non-default value of setting.' },
+ },
};
diff --git a/src/plugins/kibana_usage_collection/server/collectors/management/types.ts b/src/plugins/kibana_usage_collection/server/collectors/management/types.ts
index fd63bb5bcaf43..c4a70f5065d8e 100644
--- a/src/plugins/kibana_usage_collection/server/collectors/management/types.ts
+++ b/src/plugins/kibana_usage_collection/server/collectors/management/types.ts
@@ -31,6 +31,7 @@ export interface UsageStats {
'apm:enableSignificantTerms': boolean;
'apm:enableServiceOverview': boolean;
'observability:enableAlertingExperience': boolean;
+ 'observability:enableInspectEsQueries': boolean;
'visualize:enableLabs': boolean;
'visualization:heatmap:maxBuckets': number;
'visualization:colorMapping': string;
diff --git a/src/plugins/telemetry/schema/oss_plugins.json b/src/plugins/telemetry/schema/oss_plugins.json
index 451b3ffe91535..3cc054bdcac88 100644
--- a/src/plugins/telemetry/schema/oss_plugins.json
+++ b/src/plugins/telemetry/schema/oss_plugins.json
@@ -8032,6 +8032,12 @@
"_meta": {
"description": "Non-default value of setting."
}
+ },
+ "observability:enableInspectEsQueries": {
+ "type": "boolean",
+ "_meta": {
+ "description": "Non-default value of setting."
+ }
}
}
},
diff --git a/src/plugins/timelion/server/plugin.ts b/src/plugins/timelion/server/plugin.ts
index 66348c572117d..226a978fe5d88 100644
--- a/src/plugins/timelion/server/plugin.ts
+++ b/src/plugins/timelion/server/plugin.ts
@@ -47,6 +47,7 @@ export class TimelionPlugin implements Plugin {
core.capabilities.registerProvider(() => ({
timelion: {
save: true,
+ show: true,
},
}));
core.savedObjects.registerType(timelionSheetSavedObjectType);
diff --git a/src/plugins/timelion/server/saved_objects/timelion_sheet.ts b/src/plugins/timelion/server/saved_objects/timelion_sheet.ts
index 52d7f59a7c734..231e049280bb1 100644
--- a/src/plugins/timelion/server/saved_objects/timelion_sheet.ts
+++ b/src/plugins/timelion/server/saved_objects/timelion_sheet.ts
@@ -12,6 +12,20 @@ export const timelionSheetSavedObjectType: SavedObjectsType = {
name: 'timelion-sheet',
hidden: false,
namespaceType: 'single',
+ management: {
+ icon: 'visTimelion',
+ defaultSearchField: 'title',
+ importableAndExportable: true,
+ getTitle(obj) {
+ return obj.attributes.title;
+ },
+ getInAppUrl(obj) {
+ return {
+ path: `/app/timelion#/${encodeURIComponent(obj.id)}`,
+ uiCapabilitiesPath: 'timelion.show',
+ };
+ },
+ },
mappings: {
properties: {
description: { type: 'text' },
diff --git a/src/plugins/vis_type_timeseries/public/application/components/query_bar_wrapper.js b/src/plugins/usage_collection/common/application_usage.ts
similarity index 50%
rename from src/plugins/vis_type_timeseries/public/application/components/query_bar_wrapper.js
rename to src/plugins/usage_collection/common/application_usage.ts
index af8404eb6da92..c9dd489000d35 100644
--- a/src/plugins/vis_type_timeseries/public/application/components/query_bar_wrapper.js
+++ b/src/plugins/usage_collection/common/application_usage.ts
@@ -6,12 +6,18 @@
* Side Public License, v 1.
*/
-import React, { useContext } from 'react';
-import { CoreStartContext } from '../contexts/query_input_bar_context';
-import { QueryStringInput } from '../../../../../plugins/data/public';
+import { MAIN_APP_DEFAULT_VIEW_ID } from './constants';
-export function QueryBarWrapper(props) {
- const coreStartContext = useContext(CoreStartContext);
-
- return ;
-}
+export const getDailyId = ({
+ appId,
+ dayId,
+ viewId,
+}: {
+ viewId: string;
+ appId: string;
+ dayId: string;
+}) => {
+ return !viewId || viewId === MAIN_APP_DEFAULT_VIEW_ID
+ ? `${appId}:${dayId}`
+ : `${appId}:${dayId}:${viewId}`;
+};
diff --git a/src/plugins/usage_collection/server/report/schema.ts b/src/plugins/usage_collection/server/report/schema.ts
index 93203a33cd1e1..350ec8d90e765 100644
--- a/src/plugins/usage_collection/server/report/schema.ts
+++ b/src/plugins/usage_collection/server/report/schema.ts
@@ -9,6 +9,13 @@
import { schema, TypeOf } from '@kbn/config-schema';
import { METRIC_TYPE } from '@kbn/analytics';
+const applicationUsageReportSchema = schema.object({
+ minutesOnScreen: schema.number(),
+ numberOfClicks: schema.number(),
+ appId: schema.string(),
+ viewId: schema.string(),
+});
+
export const reportSchema = schema.object({
reportVersion: schema.maybe(schema.oneOf([schema.literal(3)])),
userAgent: schema.maybe(
@@ -38,17 +45,8 @@ export const reportSchema = schema.object({
})
)
),
- application_usage: schema.maybe(
- schema.recordOf(
- schema.string(),
- schema.object({
- minutesOnScreen: schema.number(),
- numberOfClicks: schema.number(),
- appId: schema.string(),
- viewId: schema.string(),
- })
- )
- ),
+ application_usage: schema.maybe(schema.recordOf(schema.string(), applicationUsageReportSchema)),
});
export type ReportSchemaType = TypeOf;
+export type ApplicationUsageReport = TypeOf;
diff --git a/src/plugins/usage_collection/server/report/store_application_usage.test.ts b/src/plugins/usage_collection/server/report/store_application_usage.test.ts
new file mode 100644
index 0000000000000..c4c9e5746e6cb
--- /dev/null
+++ b/src/plugins/usage_collection/server/report/store_application_usage.test.ts
@@ -0,0 +1,115 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import moment from 'moment';
+import { savedObjectsRepositoryMock } from '../../../../core/server/mocks';
+import { getDailyId } from '../../common/application_usage';
+import { storeApplicationUsage } from './store_application_usage';
+import { ApplicationUsageReport } from './schema';
+
+const createReport = (parts: Partial): ApplicationUsageReport => ({
+ appId: 'appId',
+ viewId: 'viewId',
+ numberOfClicks: 0,
+ minutesOnScreen: 0,
+ ...parts,
+});
+
+describe('storeApplicationUsage', () => {
+ let repository: ReturnType;
+ let timestamp: Date;
+
+ beforeEach(() => {
+ repository = savedObjectsRepositoryMock.create();
+ timestamp = new Date();
+ });
+
+ it('does not call `repository.incrementUsageCounters` when the report list is empty', async () => {
+ await storeApplicationUsage(repository, [], timestamp);
+ expect(repository.incrementCounter).not.toHaveBeenCalled();
+ });
+
+ it('calls `repository.incrementUsageCounters` with the correct parameters', async () => {
+ const report = createReport({
+ appId: 'app1',
+ viewId: 'view1',
+ numberOfClicks: 2,
+ minutesOnScreen: 5,
+ });
+
+ await storeApplicationUsage(repository, [report], timestamp);
+
+ expect(repository.incrementCounter).toHaveBeenCalledTimes(1);
+
+ expect(repository.incrementCounter).toHaveBeenCalledWith(
+ ...expectedIncrementParams(report, timestamp)
+ );
+ });
+
+ it('aggregates reports with the same appId/viewId tuple', async () => {
+ const report1 = createReport({
+ appId: 'app1',
+ viewId: 'view1',
+ numberOfClicks: 2,
+ minutesOnScreen: 5,
+ });
+ const report2 = createReport({
+ appId: 'app1',
+ viewId: 'view2',
+ numberOfClicks: 1,
+ minutesOnScreen: 7,
+ });
+ const report3 = createReport({
+ appId: 'app1',
+ viewId: 'view1',
+ numberOfClicks: 3,
+ minutesOnScreen: 9,
+ });
+
+ await storeApplicationUsage(repository, [report1, report2, report3], timestamp);
+
+ expect(repository.incrementCounter).toHaveBeenCalledTimes(2);
+
+ expect(repository.incrementCounter).toHaveBeenCalledWith(
+ ...expectedIncrementParams(
+ {
+ appId: 'app1',
+ viewId: 'view1',
+ numberOfClicks: report1.numberOfClicks + report3.numberOfClicks,
+ minutesOnScreen: report1.minutesOnScreen + report3.minutesOnScreen,
+ },
+ timestamp
+ )
+ );
+ expect(repository.incrementCounter).toHaveBeenCalledWith(
+ ...expectedIncrementParams(report2, timestamp)
+ );
+ });
+});
+
+const expectedIncrementParams = (
+ { appId, viewId, minutesOnScreen, numberOfClicks }: ApplicationUsageReport,
+ timestamp: Date
+) => {
+ const dayId = moment(timestamp).format('YYYY-MM-DD');
+ return [
+ 'application_usage_daily',
+ getDailyId({ appId, viewId, dayId }),
+ [
+ { fieldName: 'numberOfClicks', incrementBy: numberOfClicks },
+ { fieldName: 'minutesOnScreen', incrementBy: minutesOnScreen },
+ ],
+ {
+ upsertAttributes: {
+ appId,
+ viewId,
+ timestamp: moment(`${moment(dayId).format('YYYY-MM-DD')}T00:00:00Z`).toISOString(),
+ },
+ },
+ ];
+};
diff --git a/src/plugins/usage_collection/server/report/store_application_usage.ts b/src/plugins/usage_collection/server/report/store_application_usage.ts
new file mode 100644
index 0000000000000..2058b054fda8c
--- /dev/null
+++ b/src/plugins/usage_collection/server/report/store_application_usage.ts
@@ -0,0 +1,87 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import moment from 'moment';
+import { Writable } from '@kbn/utility-types';
+import { ISavedObjectsRepository } from 'src/core/server';
+import { ApplicationUsageReport } from './schema';
+import { getDailyId } from '../../common/application_usage';
+
+type WritableApplicationUsageReport = Writable;
+
+export const storeApplicationUsage = async (
+ repository: ISavedObjectsRepository,
+ appUsages: ApplicationUsageReport[],
+ timestamp: Date
+) => {
+ if (!appUsages.length) {
+ return;
+ }
+
+ const dayId = getDayId(timestamp);
+ const aggregatedReports = aggregateAppUsages(appUsages);
+
+ return Promise.allSettled(
+ aggregatedReports.map(async (report) => incrementUsageCounters(repository, report, dayId))
+ );
+};
+
+const aggregateAppUsages = (appUsages: ApplicationUsageReport[]) => {
+ return [
+ ...appUsages
+ .reduce((map, appUsage) => {
+ const key = getKey(appUsage);
+ const aggregated: WritableApplicationUsageReport = map.get(key) ?? {
+ appId: appUsage.appId,
+ viewId: appUsage.viewId,
+ minutesOnScreen: 0,
+ numberOfClicks: 0,
+ };
+
+ aggregated.minutesOnScreen += appUsage.minutesOnScreen;
+ aggregated.numberOfClicks += appUsage.numberOfClicks;
+
+ map.set(key, aggregated);
+ return map;
+ }, new Map())
+ .values(),
+ ];
+};
+
+const incrementUsageCounters = (
+ repository: ISavedObjectsRepository,
+ { appId, viewId, numberOfClicks, minutesOnScreen }: WritableApplicationUsageReport,
+ dayId: string
+) => {
+ const dailyId = getDailyId({ appId, viewId, dayId });
+
+ return repository.incrementCounter(
+ 'application_usage_daily',
+ dailyId,
+ [
+ { fieldName: 'numberOfClicks', incrementBy: numberOfClicks },
+ { fieldName: 'minutesOnScreen', incrementBy: minutesOnScreen },
+ ],
+ {
+ upsertAttributes: {
+ appId,
+ viewId,
+ timestamp: getTimestamp(dayId),
+ },
+ }
+ );
+};
+
+const getKey = ({ viewId, appId }: ApplicationUsageReport) => `${appId}___${viewId}`;
+
+const getDayId = (timestamp: Date) => moment(timestamp).format('YYYY-MM-DD');
+
+const getTimestamp = (dayId: string) => {
+ // Concatenating the day in YYYY-MM-DD form to T00:00:00Z to reduce the TZ effects
+ return moment(`${moment(dayId).format('YYYY-MM-DD')}T00:00:00Z`).toISOString();
+};
diff --git a/src/plugins/usage_collection/server/report/store_report.test.mocks.ts b/src/plugins/usage_collection/server/report/store_report.test.mocks.ts
new file mode 100644
index 0000000000000..d151e7d7a5ddd
--- /dev/null
+++ b/src/plugins/usage_collection/server/report/store_report.test.mocks.ts
@@ -0,0 +1,12 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+export const storeApplicationUsageMock = jest.fn();
+jest.doMock('./store_application_usage', () => ({
+ storeApplicationUsage: storeApplicationUsageMock,
+}));
diff --git a/src/plugins/usage_collection/server/report/store_report.test.ts b/src/plugins/usage_collection/server/report/store_report.test.ts
index 7174a54067246..dfcdd1f8e7e42 100644
--- a/src/plugins/usage_collection/server/report/store_report.test.ts
+++ b/src/plugins/usage_collection/server/report/store_report.test.ts
@@ -6,6 +6,8 @@
* Side Public License, v 1.
*/
+import { storeApplicationUsageMock } from './store_report.test.mocks';
+
import { savedObjectsRepositoryMock } from '../../../../core/server/mocks';
import { storeReport } from './store_report';
import { ReportSchemaType } from './schema';
@@ -16,8 +18,17 @@ describe('store_report', () => {
const momentTimestamp = moment();
const date = momentTimestamp.format('DDMMYYYY');
+ let repository: ReturnType;
+
+ beforeEach(() => {
+ repository = savedObjectsRepositoryMock.create();
+ });
+
+ afterEach(() => {
+ storeApplicationUsageMock.mockReset();
+ });
+
test('stores report for all types of data', async () => {
- const savedObjectClient = savedObjectsRepositoryMock.create();
const report: ReportSchemaType = {
reportVersion: 3,
userAgent: {
@@ -53,9 +64,9 @@ describe('store_report', () => {
},
},
};
- await storeReport(savedObjectClient, report);
+ await storeReport(repository, report);
- expect(savedObjectClient.create).toHaveBeenCalledWith(
+ expect(repository.create).toHaveBeenCalledWith(
'ui-metric',
{ count: 1 },
{
@@ -63,51 +74,45 @@ describe('store_report', () => {
overwrite: true,
}
);
- expect(savedObjectClient.incrementCounter).toHaveBeenNthCalledWith(
+ expect(repository.incrementCounter).toHaveBeenNthCalledWith(
1,
'ui-metric',
'test-app-name:test-event-name',
[{ fieldName: 'count', incrementBy: 3 }]
);
- expect(savedObjectClient.incrementCounter).toHaveBeenNthCalledWith(
+ expect(repository.incrementCounter).toHaveBeenNthCalledWith(
2,
'ui-counter',
`test-app-name:${date}:${METRIC_TYPE.LOADED}:test-event-name`,
[{ fieldName: 'count', incrementBy: 1 }]
);
- expect(savedObjectClient.incrementCounter).toHaveBeenNthCalledWith(
+ expect(repository.incrementCounter).toHaveBeenNthCalledWith(
3,
'ui-counter',
`test-app-name:${date}:${METRIC_TYPE.CLICK}:test-event-name`,
[{ fieldName: 'count', incrementBy: 2 }]
);
- expect(savedObjectClient.bulkCreate).toHaveBeenNthCalledWith(1, [
- {
- type: 'application_usage_transactional',
- attributes: {
- numberOfClicks: 3,
- minutesOnScreen: 10,
- appId: 'appId',
- viewId: 'appId_view',
- timestamp: expect.any(Date),
- },
- },
- ]);
+
+ expect(storeApplicationUsageMock).toHaveBeenCalledTimes(1);
+ expect(storeApplicationUsageMock).toHaveBeenCalledWith(
+ repository,
+ Object.values(report.application_usage as Record),
+ expect.any(Date)
+ );
});
test('it should not fail if nothing to store', async () => {
- const savedObjectClient = savedObjectsRepositoryMock.create();
const report: ReportSchemaType = {
reportVersion: 3,
userAgent: void 0,
uiCounter: void 0,
application_usage: void 0,
};
- await storeReport(savedObjectClient, report);
+ await storeReport(repository, report);
- expect(savedObjectClient.bulkCreate).not.toHaveBeenCalled();
- expect(savedObjectClient.incrementCounter).not.toHaveBeenCalled();
- expect(savedObjectClient.create).not.toHaveBeenCalled();
- expect(savedObjectClient.create).not.toHaveBeenCalled();
+ expect(repository.bulkCreate).not.toHaveBeenCalled();
+ expect(repository.incrementCounter).not.toHaveBeenCalled();
+ expect(repository.create).not.toHaveBeenCalled();
+ expect(repository.create).not.toHaveBeenCalled();
});
});
diff --git a/src/plugins/usage_collection/server/report/store_report.ts b/src/plugins/usage_collection/server/report/store_report.ts
index c3e04990d5793..0545a54792d45 100644
--- a/src/plugins/usage_collection/server/report/store_report.ts
+++ b/src/plugins/usage_collection/server/report/store_report.ts
@@ -10,6 +10,7 @@ import { ISavedObjectsRepository } from 'src/core/server';
import moment from 'moment';
import { chain, sumBy } from 'lodash';
import { ReportSchemaType } from './schema';
+import { storeApplicationUsage } from './store_application_usage';
export async function storeReport(
internalRepository: ISavedObjectsRepository,
@@ -17,11 +18,11 @@ export async function storeReport(
) {
const uiCounters = report.uiCounter ? Object.entries(report.uiCounter) : [];
const userAgents = report.userAgent ? Object.entries(report.userAgent) : [];
- const appUsage = report.application_usage ? Object.values(report.application_usage) : [];
+ const appUsages = report.application_usage ? Object.values(report.application_usage) : [];
const momentTimestamp = moment();
- const timestamp = momentTimestamp.toDate();
const date = momentTimestamp.format('DDMMYYYY');
+ const timestamp = momentTimestamp.toDate();
return Promise.allSettled([
// User Agent
@@ -64,21 +65,6 @@ export async function storeReport(
];
}),
// Application Usage
- ...[
- (async () => {
- if (!appUsage.length) return [];
- const { saved_objects: savedObjects } = await internalRepository.bulkCreate(
- appUsage.map((metric) => ({
- type: 'application_usage_transactional',
- attributes: {
- ...metric,
- timestamp,
- },
- }))
- );
-
- return savedObjects;
- })(),
- ],
+ storeApplicationUsage(internalRepository, appUsages, timestamp),
]);
}
diff --git a/src/plugins/vis_type_timeseries/common/extract_index_patterns.test.ts b/src/plugins/vis_type_timeseries/common/extract_index_patterns.test.ts
deleted file mode 100644
index c4da2085855e6..0000000000000
--- a/src/plugins/vis_type_timeseries/common/extract_index_patterns.test.ts
+++ /dev/null
@@ -1,35 +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
- * 2.0 and the Server Side Public License, v 1; you may not use this file except
- * in compliance with, at your election, the Elastic License 2.0 or the Server
- * Side Public License, v 1.
- */
-
-import { extractIndexPatterns } from './extract_index_patterns';
-import { PanelSchema } from './types';
-
-describe('extractIndexPatterns(vis)', () => {
- let panel: PanelSchema;
-
- beforeEach(() => {
- panel = {
- index_pattern: '*',
- series: [
- {
- override_index_pattern: 1,
- series_index_pattern: 'example-1-*',
- },
- {
- override_index_pattern: 1,
- series_index_pattern: 'example-2-*',
- },
- ],
- annotations: [{ index_pattern: 'notes-*' }, { index_pattern: 'example-1-*' }],
- } as PanelSchema;
- });
-
- test('should return index patterns', () => {
- expect(extractIndexPatterns(panel, '')).toEqual(['*', 'example-1-*', 'example-2-*', 'notes-*']);
- });
-});
diff --git a/src/plugins/vis_type_timeseries/common/extract_index_patterns.ts b/src/plugins/vis_type_timeseries/common/extract_index_patterns.ts
deleted file mode 100644
index c716ae7abb821..0000000000000
--- a/src/plugins/vis_type_timeseries/common/extract_index_patterns.ts
+++ /dev/null
@@ -1,43 +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
- * 2.0 and the Server Side Public License, v 1; you may not use this file except
- * in compliance with, at your election, the Elastic License 2.0 or the Server
- * Side Public License, v 1.
- */
-
-import { uniq } from 'lodash';
-import { PanelSchema } from '../common/types';
-
-export function extractIndexPatterns(
- panel: PanelSchema,
- defaultIndex?: PanelSchema['default_index_pattern']
-) {
- const patterns: string[] = [];
-
- if (panel.index_pattern) {
- patterns.push(panel.index_pattern);
- }
-
- panel.series.forEach((series) => {
- const indexPattern = series.series_index_pattern;
- if (indexPattern && series.override_index_pattern) {
- patterns.push(indexPattern);
- }
- });
-
- if (panel.annotations) {
- panel.annotations.forEach((item) => {
- const indexPattern = item.index_pattern;
- if (indexPattern) {
- patterns.push(indexPattern);
- }
- });
- }
-
- if (patterns.length === 0 && defaultIndex) {
- patterns.push(defaultIndex);
- }
-
- return uniq(patterns).sort();
-}
diff --git a/src/plugins/vis_type_timeseries/common/fields_utils.test.ts b/src/plugins/vis_type_timeseries/common/fields_utils.test.ts
new file mode 100644
index 0000000000000..d1036aab2dc3e
--- /dev/null
+++ b/src/plugins/vis_type_timeseries/common/fields_utils.test.ts
@@ -0,0 +1,73 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { toSanitizedFieldType } from './fields_utils';
+import type { FieldSpec, RuntimeField } from '../../data/common';
+
+describe('fields_utils', () => {
+ describe('toSanitizedFieldType', () => {
+ const mockedField = {
+ lang: 'lang',
+ conflictDescriptions: {},
+ aggregatable: true,
+ name: 'name',
+ type: 'type',
+ esTypes: ['long', 'geo'],
+ } as FieldSpec;
+
+ test('should sanitize fields ', async () => {
+ const fields = [mockedField] as FieldSpec[];
+
+ expect(toSanitizedFieldType(fields)).toMatchInlineSnapshot(`
+ Array [
+ Object {
+ "label": "name",
+ "name": "name",
+ "type": "type",
+ },
+ ]
+ `);
+ });
+
+ test('should filter runtime fields', async () => {
+ const fields: FieldSpec[] = [
+ {
+ ...mockedField,
+ runtimeField: {} as RuntimeField,
+ },
+ ];
+
+ expect(toSanitizedFieldType(fields)).toMatchInlineSnapshot(`Array []`);
+ });
+
+ test('should filter non-aggregatable fields', async () => {
+ const fields: FieldSpec[] = [
+ {
+ ...mockedField,
+ aggregatable: false,
+ },
+ ];
+
+ expect(toSanitizedFieldType(fields)).toMatchInlineSnapshot(`Array []`);
+ });
+
+ test('should filter nested fields', async () => {
+ const fields: FieldSpec[] = [
+ {
+ ...mockedField,
+ subType: {
+ nested: {
+ path: 'path',
+ },
+ },
+ },
+ ];
+ expect(toSanitizedFieldType(fields)).toMatchInlineSnapshot(`Array []`);
+ });
+ });
+});
diff --git a/src/plugins/vis_type_timeseries/common/fields_utils.ts b/src/plugins/vis_type_timeseries/common/fields_utils.ts
new file mode 100644
index 0000000000000..04499d5320ab8
--- /dev/null
+++ b/src/plugins/vis_type_timeseries/common/fields_utils.ts
@@ -0,0 +1,28 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { FieldSpec } from '../../data/common';
+import { isNestedField } from '../../data/common';
+import { SanitizedFieldType } from './types';
+
+export const toSanitizedFieldType = (fields: FieldSpec[]) => {
+ return fields
+ .filter(
+ (field) =>
+ // Make sure to only include mapped fields, e.g. no index pattern runtime fields
+ !field.runtimeField && field.aggregatable && !isNestedField(field)
+ )
+ .map(
+ (field) =>
+ ({
+ name: field.name,
+ label: field.customLabel ?? field.name,
+ type: field.type,
+ } as SanitizedFieldType)
+ );
+};
diff --git a/src/plugins/vis_type_timeseries/common/index_patterns_utils.test.ts b/src/plugins/vis_type_timeseries/common/index_patterns_utils.test.ts
new file mode 100644
index 0000000000000..515fadffb6b32
--- /dev/null
+++ b/src/plugins/vis_type_timeseries/common/index_patterns_utils.test.ts
@@ -0,0 +1,139 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import {
+ extractIndexPatternValues,
+ isStringTypeIndexPattern,
+ fetchIndexPattern,
+} from './index_patterns_utils';
+import { PanelSchema } from './types';
+import { IndexPattern, IndexPatternsService } from '../../data/common';
+
+describe('isStringTypeIndexPattern', () => {
+ test('should returns true on string-based index', () => {
+ expect(isStringTypeIndexPattern('index')).toBeTruthy();
+ });
+ test('should returns false on object-based index', () => {
+ expect(isStringTypeIndexPattern({ id: 'id' })).toBeFalsy();
+ });
+});
+
+describe('extractIndexPatterns', () => {
+ let panel: PanelSchema;
+
+ beforeEach(() => {
+ panel = {
+ index_pattern: '*',
+ series: [
+ {
+ override_index_pattern: 1,
+ series_index_pattern: 'example-1-*',
+ },
+ {
+ override_index_pattern: 1,
+ series_index_pattern: 'example-2-*',
+ },
+ ],
+ annotations: [{ index_pattern: 'notes-*' }, { index_pattern: 'example-1-*' }],
+ } as PanelSchema;
+ });
+
+ test('should return index patterns', () => {
+ expect(extractIndexPatternValues(panel, '')).toEqual([
+ '*',
+ 'example-1-*',
+ 'example-2-*',
+ 'notes-*',
+ ]);
+ });
+});
+
+describe('fetchIndexPattern', () => {
+ let mockedIndices: IndexPattern[] | [];
+ let indexPatternsService: IndexPatternsService;
+
+ beforeEach(() => {
+ mockedIndices = [];
+
+ indexPatternsService = ({
+ getDefault: jest.fn(() => Promise.resolve({ id: 'default', title: 'index' })),
+ get: jest.fn(() => Promise.resolve(mockedIndices[0])),
+ find: jest.fn(() => Promise.resolve(mockedIndices || [])),
+ } as unknown) as IndexPatternsService;
+ });
+
+ test('should return default index on no input value', async () => {
+ const value = await fetchIndexPattern('', indexPatternsService);
+ expect(value).toMatchInlineSnapshot(`
+ Object {
+ "indexPattern": Object {
+ "id": "default",
+ "title": "index",
+ },
+ "indexPatternString": "index",
+ }
+ `);
+ });
+
+ describe('text-based index', () => {
+ test('should return the Kibana index if it exists', async () => {
+ mockedIndices = [
+ {
+ id: 'indexId',
+ title: 'indexTitle',
+ },
+ ] as IndexPattern[];
+
+ const value = await fetchIndexPattern('indexTitle', indexPatternsService);
+
+ expect(value).toMatchInlineSnapshot(`
+ Object {
+ "indexPattern": Object {
+ "id": "indexId",
+ "title": "indexTitle",
+ },
+ "indexPatternString": "indexTitle",
+ }
+ `);
+ });
+
+ test('should return only indexPatternString if Kibana index does not exist', async () => {
+ const value = await fetchIndexPattern('indexTitle', indexPatternsService);
+
+ expect(value).toMatchInlineSnapshot(`
+ Object {
+ "indexPattern": undefined,
+ "indexPatternString": "indexTitle",
+ }
+ `);
+ });
+ });
+
+ describe('object-based index', () => {
+ test('should return the Kibana index if it exists', async () => {
+ mockedIndices = [
+ {
+ id: 'indexId',
+ title: 'indexTitle',
+ },
+ ] as IndexPattern[];
+
+ const value = await fetchIndexPattern({ id: 'indexId' }, indexPatternsService);
+
+ expect(value).toMatchInlineSnapshot(`
+ Object {
+ "indexPattern": Object {
+ "id": "indexId",
+ "title": "indexTitle",
+ },
+ "indexPatternString": "indexTitle",
+ }
+ `);
+ });
+ });
+});
diff --git a/src/plugins/vis_type_timeseries/common/index_patterns_utils.ts b/src/plugins/vis_type_timeseries/common/index_patterns_utils.ts
new file mode 100644
index 0000000000000..398d1c30ed5a7
--- /dev/null
+++ b/src/plugins/vis_type_timeseries/common/index_patterns_utils.ts
@@ -0,0 +1,80 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { uniq } from 'lodash';
+import { PanelSchema, IndexPatternValue, FetchedIndexPattern } from '../common/types';
+import { IndexPatternsService } from '../../data/common';
+
+export const isStringTypeIndexPattern = (
+ indexPatternValue: IndexPatternValue
+): indexPatternValue is string => typeof indexPatternValue === 'string';
+
+export const getIndexPatternKey = (indexPatternValue: IndexPatternValue) =>
+ isStringTypeIndexPattern(indexPatternValue) ? indexPatternValue : indexPatternValue?.id ?? '';
+
+export const extractIndexPatternValues = (
+ panel: PanelSchema,
+ defaultIndex?: PanelSchema['default_index_pattern']
+) => {
+ const patterns: IndexPatternValue[] = [];
+
+ if (panel.index_pattern) {
+ patterns.push(panel.index_pattern);
+ }
+
+ panel.series.forEach((series) => {
+ const indexPattern = series.series_index_pattern;
+ if (indexPattern && series.override_index_pattern) {
+ patterns.push(indexPattern);
+ }
+ });
+
+ if (panel.annotations) {
+ panel.annotations.forEach((item) => {
+ const indexPattern = item.index_pattern;
+ if (indexPattern) {
+ patterns.push(indexPattern);
+ }
+ });
+ }
+
+ if (patterns.length === 0 && defaultIndex) {
+ patterns.push(defaultIndex);
+ }
+
+ return uniq(patterns).sort();
+};
+
+export const fetchIndexPattern = async (
+ indexPatternValue: IndexPatternValue | undefined,
+ indexPatternsService: Pick
+): Promise => {
+ let indexPattern: FetchedIndexPattern['indexPattern'];
+ let indexPatternString: string = '';
+
+ if (!indexPatternValue) {
+ indexPattern = await indexPatternsService.getDefault();
+ } else {
+ if (isStringTypeIndexPattern(indexPatternValue)) {
+ indexPattern = (await indexPatternsService.find(indexPatternValue)).find(
+ (index) => index.title === indexPatternValue
+ );
+
+ if (!indexPattern) {
+ indexPatternString = indexPatternValue;
+ }
+ } else if (indexPatternValue.id) {
+ indexPattern = await indexPatternsService.get(indexPatternValue.id);
+ }
+ }
+
+ return {
+ indexPattern,
+ indexPatternString: indexPattern?.title ?? indexPatternString,
+ };
+};
diff --git a/src/plugins/vis_type_timeseries/common/types.ts b/src/plugins/vis_type_timeseries/common/types.ts
index 7d93232f310c9..1fe6196ad545b 100644
--- a/src/plugins/vis_type_timeseries/common/types.ts
+++ b/src/plugins/vis_type_timeseries/common/types.ts
@@ -13,10 +13,12 @@ import {
seriesItems,
visPayloadSchema,
fieldObject,
+ indexPattern,
annotationsItems,
} from './vis_schema';
import { PANEL_TYPES } from './panel_types';
import { TimeseriesUIRestrictions } from './ui_restrictions';
+import { IndexPattern } from '../../data/common';
export type AnnotationItemsSchema = TypeOf;
export type SeriesItemsSchema = TypeOf;
@@ -24,6 +26,12 @@ export type MetricsItemsSchema = TypeOf;
export type PanelSchema = TypeOf;
export type VisPayload = TypeOf;
export type FieldObject = TypeOf;
+export type IndexPatternValue = TypeOf;
+
+export interface FetchedIndexPattern {
+ indexPattern: IndexPattern | undefined | null;
+ indexPatternString: string | undefined;
+}
export interface PanelData {
id: string;
diff --git a/src/plugins/vis_type_timeseries/common/vis_schema.ts b/src/plugins/vis_type_timeseries/common/vis_schema.ts
index a6bf70948bc1b..297b021fa9e77 100644
--- a/src/plugins/vis_type_timeseries/common/vis_schema.ts
+++ b/src/plugins/vis_type_timeseries/common/vis_schema.ts
@@ -28,7 +28,7 @@ const numberOptional = schema.maybe(schema.number());
const queryObject = schema.object({
language: schema.string(),
- query: schema.string(),
+ query: schema.oneOf([schema.string(), schema.any()]),
});
const stringOrNumberOptionalNullable = schema.nullable(
schema.oneOf([stringOptionalNullable, numberOptional])
@@ -37,6 +37,13 @@ const numberOptionalOrEmptyString = schema.maybe(
schema.oneOf([numberOptional, schema.literal('')])
);
+export const indexPattern = schema.oneOf([
+ schema.maybe(schema.string()),
+ schema.object({
+ id: schema.string(),
+ }),
+]);
+
export const fieldObject = stringOptionalNullable;
export const annotationsItems = schema.object({
@@ -47,7 +54,7 @@ export const annotationsItems = schema.object({
id: schema.string(),
ignore_global_filters: numberIntegerOptional,
ignore_panel_filters: numberIntegerOptional,
- index_pattern: stringOptionalNullable,
+ index_pattern: indexPattern,
query_string: schema.maybe(queryObject),
template: stringOptionalNullable,
time_field: fieldObject,
@@ -68,6 +75,7 @@ const gaugeColorRulesItems = schema.object({
operator: stringOptionalNullable,
value: schema.maybe(schema.nullable(schema.number())),
});
+
export const metricsItems = schema.object({
field: fieldObject,
id: stringRequired,
@@ -167,7 +175,7 @@ export const seriesItems = schema.object({
point_size: numberOptionalOrEmptyString,
separate_axis: numberIntegerOptional,
seperate_axis: numberIntegerOptional,
- series_index_pattern: stringOptionalNullable,
+ series_index_pattern: indexPattern,
series_max_bars: numberIntegerOptional,
series_time_field: fieldObject,
series_interval: stringOptionalNullable,
@@ -195,6 +203,7 @@ export const seriesItems = schema.object({
});
export const panel = schema.object({
+ use_kibana_indexes: schema.maybe(schema.boolean()),
annotations: schema.maybe(schema.arrayOf(annotationsItems)),
axis_formatter: stringRequired,
axis_position: stringRequired,
@@ -218,7 +227,7 @@ export const panel = schema.object({
id: stringRequired,
ignore_global_filters: numberOptional,
ignore_global_filter: numberOptional,
- index_pattern: stringRequired,
+ index_pattern: indexPattern,
max_bars: numberIntegerOptional,
interval: stringRequired,
isModelInvalid: schema.maybe(schema.boolean()),
diff --git a/src/plugins/vis_type_timeseries/public/application/components/aggs/field_select.tsx b/src/plugins/vis_type_timeseries/public/application/components/aggs/field_select.tsx
index 4fc7b89e23765..82989cc15d6c9 100644
--- a/src/plugins/vis_type_timeseries/public/application/components/aggs/field_select.tsx
+++ b/src/plugins/vis_type_timeseries/public/application/components/aggs/field_select.tsx
@@ -10,8 +10,8 @@ import React from 'react';
import { i18n } from '@kbn/i18n';
import { EuiComboBox, EuiComboBoxProps, EuiComboBoxOptionOption } from '@elastic/eui';
import { METRIC_TYPES } from '../../../../common/metric_types';
-
-import type { SanitizedFieldType } from '../../../../common/types';
+import { getIndexPatternKey } from '../../../../common/index_patterns_utils';
+import type { SanitizedFieldType, IndexPatternValue } from '../../../../common/types';
import type { TimeseriesUIRestrictions } from '../../../../common/ui_restrictions';
// @ts-ignore
@@ -20,7 +20,7 @@ import { isFieldEnabled } from '../../lib/check_ui_restrictions';
interface FieldSelectProps {
type: string;
fields: Record;
- indexPattern: string;
+ indexPattern: IndexPatternValue;
value?: string | null;
onChange: (options: Array>) => void;
disabled?: boolean;
@@ -62,8 +62,10 @@ export function FieldSelect({
const selectedOptions: Array> = [];
let newPlaceholder = placeholder;
+ const fieldsSelector = getIndexPatternKey(indexPattern);
+
const groupedOptions: EuiComboBoxProps['options'] = Object.values(
- (fields[indexPattern] || []).reduce>>(
+ (fields[fieldsSelector] || []).reduce>>(
(acc, field) => {
if (placeholder === field?.name) {
newPlaceholder = field.label ?? field.name;
diff --git a/src/plugins/vis_type_timeseries/public/application/components/annotations_editor.js b/src/plugins/vis_type_timeseries/public/application/components/annotations_editor.js
index f95eeb4816128..ab0db6daae18a 100644
--- a/src/plugins/vis_type_timeseries/public/application/components/annotations_editor.js
+++ b/src/plugins/vis_type_timeseries/public/application/components/annotations_editor.js
@@ -32,8 +32,8 @@ import {
EuiCode,
EuiText,
} from '@elastic/eui';
-import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
+import { IndexPatternSelect } from './lib/index_pattern_select';
function newAnnotation() {
return {
@@ -91,7 +91,6 @@ export class AnnotationsEditor extends Component {
const htmlId = htmlIdGenerator(model.id);
const handleAdd = collectionActions.handleAdd.bind(null, this.props, newAnnotation);
const handleDelete = collectionActions.handleDelete.bind(null, this.props, model);
- const defaultIndexPattern = this.props.model.default_index_pattern;
return (
@@ -108,30 +107,11 @@ export class AnnotationsEditor extends Component {
-
- }
- helpText={
- defaultIndexPattern &&
- !model.index_pattern &&
- i18n.translate('visTypeTimeseries.annotationsEditor.searchByDefaultIndex', {
- defaultMessage: 'Default index pattern is used. To query all indexes use *',
- })
- }
- fullWidth
- >
-
-
+
{
const config = getUISettings();
const timeFieldName = `${prefix}time_field`;
@@ -165,26 +167,13 @@ export const IndexPattern = ({
)}
-
-
-
+
- {allowLevelofDetail && (
+ {allowLevelOfDetail && (
>;
+
+/** @internal **/
+type SelectedOptions = EuiComboBoxProps['selectedOptions'];
+
+const toComboBoxOptions = (options: IdsWithTitle) =>
+ options.map(({ title, id }) => ({ label: title, id }));
+
+export const ComboBoxSelect = ({
+ fetchedIndex,
+ onIndexChange,
+ onModeChange,
+ disabled,
+ placeholder,
+ allowSwitchMode,
+ 'data-test-subj': dataTestSubj,
+}: SelectIndexComponentProps) => {
+ const [availableIndexes, setAvailableIndexes] = useState([]);
+ const [selectedOptions, setSelectedOptions] = useState([]);
+
+ const onComboBoxChange: EuiComboBoxProps['onChange'] = useCallback(
+ ([selected]) => {
+ onIndexChange(selected ? { id: selected.id } : '');
+ },
+ [onIndexChange]
+ );
+
+ useEffect(() => {
+ let options: SelectedOptions = [];
+ const { indexPattern, indexPatternString } = fetchedIndex;
+
+ if (indexPattern || indexPatternString) {
+ if (!indexPattern) {
+ options = [{ label: indexPatternString ?? '' }];
+ } else {
+ options = [
+ {
+ id: indexPattern.id,
+ label: indexPattern.title,
+ },
+ ];
+ }
+ }
+ setSelectedOptions(options);
+ }, [fetchedIndex]);
+
+ useEffect(() => {
+ async function fetchIndexes() {
+ setAvailableIndexes(await getDataStart().indexPatterns.getIdsWithTitle());
+ }
+
+ fetchIndexes();
+ }, []);
+
+ return (
+
+ ),
+ })}
+ />
+ );
+};
diff --git a/src/plugins/vis_type_timeseries/public/application/components/lib/index_pattern_select/field_text_select.tsx b/src/plugins/vis_type_timeseries/public/application/components/lib/index_pattern_select/field_text_select.tsx
new file mode 100644
index 0000000000000..86d1758932301
--- /dev/null
+++ b/src/plugins/vis_type_timeseries/public/application/components/lib/index_pattern_select/field_text_select.tsx
@@ -0,0 +1,66 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+import React, { useCallback, useState, useEffect } from 'react';
+import useDebounce from 'react-use/lib/useDebounce';
+
+import { EuiFieldText, EuiFieldTextProps } from '@elastic/eui';
+import { SwitchModePopover } from './switch_mode_popover';
+
+import type { SelectIndexComponentProps } from './types';
+
+export const FieldTextSelect = ({
+ fetchedIndex,
+ onIndexChange,
+ disabled,
+ placeholder,
+ onModeChange,
+ allowSwitchMode,
+ 'data-test-subj': dataTestSubj,
+}: SelectIndexComponentProps) => {
+ const [inputValue, setInputValue] = useState();
+ const { indexPatternString } = fetchedIndex;
+
+ const onFieldTextChange: EuiFieldTextProps['onChange'] = useCallback((e) => {
+ setInputValue(e.target.value);
+ }, []);
+
+ useEffect(() => {
+ if (inputValue === undefined) {
+ setInputValue(indexPatternString ?? '');
+ }
+ }, [indexPatternString, inputValue]);
+
+ useDebounce(
+ () => {
+ if (inputValue !== indexPatternString) {
+ onIndexChange(inputValue);
+ }
+ },
+ 150,
+ [inputValue, onIndexChange]
+ );
+
+ return (
+
+ ),
+ })}
+ />
+ );
+};
diff --git a/src/plugins/vis_type_timeseries/public/application/components/lib/index_pattern_select/index.ts b/src/plugins/vis_type_timeseries/public/application/components/lib/index_pattern_select/index.ts
new file mode 100644
index 0000000000000..584f13e7a025b
--- /dev/null
+++ b/src/plugins/vis_type_timeseries/public/application/components/lib/index_pattern_select/index.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
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+export { IndexPatternSelect } from './index_pattern_select';
diff --git a/src/plugins/vis_type_timeseries/public/application/components/lib/index_pattern_select/index_pattern_select.tsx b/src/plugins/vis_type_timeseries/public/application/components/lib/index_pattern_select/index_pattern_select.tsx
new file mode 100644
index 0000000000000..28b9c173a2b1b
--- /dev/null
+++ b/src/plugins/vis_type_timeseries/public/application/components/lib/index_pattern_select/index_pattern_select.tsx
@@ -0,0 +1,154 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import React, { useState, useContext, useCallback, useEffect } from 'react';
+import { i18n } from '@kbn/i18n';
+import { FormattedMessage } from '@kbn/i18n/react';
+
+import { EuiFormRow, EuiText, EuiLink, htmlIdGenerator } from '@elastic/eui';
+import { getCoreStart, getDataStart } from '../../../../services';
+import { PanelModelContext } from '../../../contexts/panel_model_context';
+
+import {
+ isStringTypeIndexPattern,
+ fetchIndexPattern,
+} from '../../../../../common/index_patterns_utils';
+
+import { FieldTextSelect } from './field_text_select';
+import { ComboBoxSelect } from './combo_box_select';
+
+import type { IndexPatternValue, FetchedIndexPattern } from '../../../../../common/types';
+
+const USE_KIBANA_INDEXES_KEY = 'use_kibana_indexes';
+
+interface IndexPatternSelectProps {
+ value: IndexPatternValue;
+ indexPatternName: string;
+ onChange: Function;
+ disabled?: boolean;
+ allowIndexSwitchingMode?: boolean;
+}
+
+const defaultIndexPatternHelpText = i18n.translate(
+ 'visTypeTimeseries.indexPatternSelect.defaultIndexPatternText',
+ {
+ defaultMessage: 'Default index pattern is used.',
+ }
+);
+
+const queryAllIndexesHelpText = i18n.translate(
+ 'visTypeTimeseries.indexPatternSelect.queryAllIndexesText',
+ {
+ defaultMessage: 'To query all indexes use *',
+ }
+);
+
+const indexPatternLabel = i18n.translate('visTypeTimeseries.indexPatternSelect.label', {
+ defaultMessage: 'Index pattern',
+});
+
+export const IndexPatternSelect = ({
+ value,
+ indexPatternName,
+ onChange,
+ disabled,
+ allowIndexSwitchingMode,
+}: IndexPatternSelectProps) => {
+ const htmlId = htmlIdGenerator();
+ const panelModel = useContext(PanelModelContext);
+ const [fetchedIndex, setFetchedIndex] = useState();
+ const useKibanaIndices = Boolean(panelModel?.[USE_KIBANA_INDEXES_KEY]);
+ const Component = useKibanaIndices ? ComboBoxSelect : FieldTextSelect;
+
+ const onIndexChange = useCallback(
+ (index: IndexPatternValue) => {
+ onChange({
+ [indexPatternName]: index,
+ });
+ },
+ [indexPatternName, onChange]
+ );
+
+ const onModeChange = useCallback(
+ (useKibanaIndexes: boolean, index?: FetchedIndexPattern) => {
+ onChange({
+ [USE_KIBANA_INDEXES_KEY]: useKibanaIndexes,
+ [indexPatternName]: index?.indexPattern?.id
+ ? {
+ id: index.indexPattern.id,
+ }
+ : '',
+ });
+ },
+ [onChange, indexPatternName]
+ );
+
+ const navigateToCreateIndexPatternPage = useCallback(() => {
+ const coreStart = getCoreStart();
+
+ coreStart.application.navigateToApp('management', {
+ path: `/kibana/indexPatterns/create?name=${fetchedIndex!.indexPatternString ?? ''}`,
+ });
+ }, [fetchedIndex]);
+
+ useEffect(() => {
+ async function fetchIndex() {
+ const { indexPatterns } = getDataStart();
+
+ setFetchedIndex(
+ value
+ ? await fetchIndexPattern(value, indexPatterns)
+ : {
+ indexPattern: undefined,
+ indexPatternString: undefined,
+ }
+ );
+ }
+
+ fetchIndex();
+ }, [value]);
+
+ if (!fetchedIndex) {
+ return null;
+ }
+
+ return (
+
+
+
+
+
+ ) : null
+ }
+ >
+
+
+ );
+};
diff --git a/src/plugins/vis_type_timeseries/public/application/components/lib/index_pattern_select/switch_mode_popover.tsx b/src/plugins/vis_type_timeseries/public/application/components/lib/index_pattern_select/switch_mode_popover.tsx
new file mode 100644
index 0000000000000..5f5506ce4a332
--- /dev/null
+++ b/src/plugins/vis_type_timeseries/public/application/components/lib/index_pattern_select/switch_mode_popover.tsx
@@ -0,0 +1,80 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import React, { useState, useCallback } from 'react';
+import { i18n } from '@kbn/i18n';
+import { FormattedMessage } from '@kbn/i18n/react';
+
+import {
+ EuiButtonIcon,
+ EuiPopover,
+ EuiPopoverTitle,
+ EuiSpacer,
+ EuiSwitch,
+ EuiText,
+} from '@elastic/eui';
+
+import type { PopoverProps } from './types';
+
+export const SwitchModePopover = ({ onModeChange, useKibanaIndices }: PopoverProps) => {
+ const [isPopoverOpen, setIsPopoverOpen] = useState(false);
+ const closePopover = useCallback(() => setIsPopoverOpen(false), []);
+ const onButtonClick = useCallback(() => setIsPopoverOpen((isOpen) => !isOpen), []);
+
+ const switchMode = useCallback(() => {
+ onModeChange(!useKibanaIndices);
+ }, [onModeChange, useKibanaIndices]);
+
+ return (
+
+ }
+ isOpen={isPopoverOpen}
+ closePopover={closePopover}
+ style={{ height: 'auto' }}
+ >
+
+
+ {i18n.translate('visTypeTimeseries.indexPatternSelect.switchModePopover.title', {
+ defaultMessage: 'Index pattern selection mode',
+ })}
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/src/plugins/vis_type_timeseries/public/application/components/lib/index_pattern_select/types.ts b/src/plugins/vis_type_timeseries/public/application/components/lib/index_pattern_select/types.ts
new file mode 100644
index 0000000000000..93b15402e3c24
--- /dev/null
+++ b/src/plugins/vis_type_timeseries/public/application/components/lib/index_pattern_select/types.ts
@@ -0,0 +1,28 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+import type { Assign } from '@kbn/utility-types';
+import type { FetchedIndexPattern, IndexPatternValue } from '../../../../../common/types';
+
+/** @internal **/
+export interface SelectIndexComponentProps {
+ fetchedIndex: FetchedIndexPattern;
+ onIndexChange: (value: IndexPatternValue) => void;
+ onModeChange: (useKibanaIndexes: boolean, index?: FetchedIndexPattern) => void;
+ 'data-test-subj': string;
+ placeholder?: string;
+ disabled?: boolean;
+ allowSwitchMode?: boolean;
+}
+
+/** @internal **/
+export type PopoverProps = Assign<
+ Pick,
+ {
+ useKibanaIndices: boolean;
+ }
+>;
diff --git a/src/plugins/vis_type_timeseries/public/application/components/panel_config/gauge.tsx b/src/plugins/vis_type_timeseries/public/application/components/panel_config/gauge.tsx
index e302bbb9adb0b..8a5077fca664c 100644
--- a/src/plugins/vis_type_timeseries/public/application/components/panel_config/gauge.tsx
+++ b/src/plugins/vis_type_timeseries/public/application/components/panel_config/gauge.tsx
@@ -29,12 +29,11 @@ import type { Writable } from '@kbn/utility-types';
// @ts-ignore
import { SeriesEditor } from '../series_editor';
-// @ts-ignore should be typed after https://github.com/elastic/kibana/pull/92812 to reduce conflicts
+// @ts-expect-error not typed yet
import { IndexPattern } from '../index_pattern';
import { createSelectHandler } from '../lib/create_select_handler';
import { ColorRules } from '../color_rules';
import { ColorPicker } from '../color_picker';
-// @ts-ignore this is typed in https://github.com/elastic/kibana/pull/92812, remove ignore after merging
import { QueryBarWrapper } from '../query_bar_wrapper';
import { getDefaultQueryLanguage } from '../lib/get_default_query_language';
import { YesNo } from '../yes_no';
@@ -128,6 +127,7 @@ export class GaugePanelConfig extends Component<
fields={this.props.fields}
model={this.props.model}
onChange={this.props.onChange}
+ allowIndexSwitchingMode={true}
/>
@@ -149,10 +149,10 @@ export class GaugePanelConfig extends Component<
language: model.filter?.language || getDefaultQueryLanguage(),
query: model.filter?.query || '',
}}
- onChange={(filter: PanelConfigProps['model']['filter']) =>
- this.props.onChange({ filter })
- }
- indexPatterns={[model.index_pattern || model.default_index_pattern]}
+ onChange={(filter) => {
+ this.props.onChange({ filter });
+ }}
+ indexPatterns={[model.index_pattern || model.default_index_pattern || '']}
/>
diff --git a/src/plugins/vis_type_timeseries/public/application/components/panel_config/markdown.tsx b/src/plugins/vis_type_timeseries/public/application/components/panel_config/markdown.tsx
index c0f7e1b7b4743..a9d9d01376608 100644
--- a/src/plugins/vis_type_timeseries/public/application/components/panel_config/markdown.tsx
+++ b/src/plugins/vis_type_timeseries/public/application/components/panel_config/markdown.tsx
@@ -31,14 +31,13 @@ import type { Writable } from '@kbn/utility-types';
// @ts-expect-error not typed yet
import { SeriesEditor } from '../series_editor';
-// @ts-ignore should be typed after https://github.com/elastic/kibana/pull/92812 to reduce conflicts
+// @ts-expect-error not typed yet
import { IndexPattern } from '../index_pattern';
import { createSelectHandler } from '../lib/create_select_handler';
import { ColorPicker } from '../color_picker';
import { YesNo } from '../yes_no';
// @ts-expect-error not typed yet
import { MarkdownEditor } from '../markdown_editor';
-// @ts-ignore this is typed in https://github.com/elastic/kibana/pull/92812, remove ignore after merging
import { QueryBarWrapper } from '../query_bar_wrapper';
import { getDefaultQueryLanguage } from '../lib/get_default_query_language';
import { VisDataContext } from '../../contexts/vis_data_context';
@@ -143,6 +142,7 @@ export class MarkdownPanelConfig extends Component<
fields={this.props.fields}
model={this.props.model}
onChange={this.props.onChange}
+ allowIndexSwitchingMode={true}
/>
@@ -161,13 +161,13 @@ export class MarkdownPanelConfig extends Component<
>
{
+ this.props.onChange({ filter });
}}
- onChange={(filter: PanelConfigProps['model']['filter']) =>
- this.props.onChange({ filter })
- }
- indexPatterns={[model.index_pattern || model.default_index_pattern]}
+ indexPatterns={[model.index_pattern || model.default_index_pattern || '']}
/>
diff --git a/src/plugins/vis_type_timeseries/public/application/components/panel_config/metric.tsx b/src/plugins/vis_type_timeseries/public/application/components/panel_config/metric.tsx
index ec11f94d245a0..1cc0e48f135c8 100644
--- a/src/plugins/vis_type_timeseries/public/application/components/panel_config/metric.tsx
+++ b/src/plugins/vis_type_timeseries/public/application/components/panel_config/metric.tsx
@@ -25,12 +25,10 @@ import { FormattedMessage } from '@kbn/i18n/react';
// @ts-expect-error
import { SeriesEditor } from '../series_editor';
-// @ts-ignore should be typed after https://github.com/elastic/kibana/pull/92812 to reduce conflicts
+// @ts-expect-error not typed yet
import { IndexPattern } from '../index_pattern';
import { ColorRules } from '../color_rules';
import { YesNo } from '../yes_no';
-
-// @ts-ignore this is typed in https://github.com/elastic/kibana/pull/92812, remove ignore after merging
import { QueryBarWrapper } from '../query_bar_wrapper';
import { getDefaultQueryLanguage } from '../lib/get_default_query_language';
import { limitOfSeries } from '../../../../common/ui_restrictions';
@@ -93,6 +91,7 @@ export class MetricPanelConfig extends Component<
fields={this.props.fields}
model={this.props.model}
onChange={this.props.onChange}
+ allowIndexSwitchingMode={true}
/>
@@ -111,13 +110,13 @@ export class MetricPanelConfig extends Component<
>
{
+ this.props.onChange({ filter });
}}
- onChange={(filter: PanelConfigProps['model']['filter']) =>
- this.props.onChange({ filter })
- }
- indexPatterns={[model.index_pattern || model.default_index_pattern]}
+ indexPatterns={[model.index_pattern || model.default_index_pattern || '']}
/>
diff --git a/src/plugins/vis_type_timeseries/public/application/components/panel_config/panel_config.tsx b/src/plugins/vis_type_timeseries/public/application/components/panel_config/panel_config.tsx
index abe807f6180b6..17810ac362618 100644
--- a/src/plugins/vis_type_timeseries/public/application/components/panel_config/panel_config.tsx
+++ b/src/plugins/vis_type_timeseries/public/application/components/panel_config/panel_config.tsx
@@ -12,6 +12,7 @@ import { FormattedMessage } from '@kbn/i18n/react';
import { TimeseriesVisData } from '../../../../common/types';
import { FormValidationContext } from '../../contexts/form_validation_context';
import { VisDataContext } from '../../contexts/vis_data_context';
+import { PanelModelContext } from '../../contexts/panel_model_context';
import { PanelConfigProps } from './types';
import { TimeseriesPanelConfig as timeseries } from './timeseries';
import { MetricPanelConfig as metric } from './metric';
@@ -61,11 +62,13 @@ export function PanelConfig(props: PanelConfigProps) {
if (Component) {
return (
-
-
-
-
-
+
+
+
+
+
+
+
);
}
diff --git a/src/plugins/vis_type_timeseries/public/application/components/panel_config/table.tsx b/src/plugins/vis_type_timeseries/public/application/components/panel_config/table.tsx
index 20e07be4e3fa4..01828eac74a0f 100644
--- a/src/plugins/vis_type_timeseries/public/application/components/panel_config/table.tsx
+++ b/src/plugins/vis_type_timeseries/public/application/components/panel_config/table.tsx
@@ -31,16 +31,17 @@ import { FormattedMessage } from '@kbn/i18n/react';
import { FieldSelect } from '../aggs/field_select';
// @ts-expect-error not typed yet
import { SeriesEditor } from '../series_editor';
-// @ts-ignore should be typed after https://github.com/elastic/kibana/pull/92812 to reduce conflicts
+// @ts-expect-error not typed yet
import { IndexPattern } from '../index_pattern';
import { YesNo } from '../yes_no';
-// @ts-ignore this is typed in https://github.com/elastic/kibana/pull/92812, remove ignore after merging
+
import { QueryBarWrapper } from '../query_bar_wrapper';
import { getDefaultQueryLanguage } from '../lib/get_default_query_language';
import { VisDataContext } from '../../contexts/vis_data_context';
import { BUCKET_TYPES } from '../../../../common/metric_types';
import { PanelConfigProps, PANEL_CONFIG_TABS } from './types';
import { TimeseriesVisParams } from '../../../types';
+import { getIndexPatternKey } from '../../../../common/index_patterns_utils';
export class TablePanelConfig extends Component<
PanelConfigProps,
@@ -66,7 +67,7 @@ export class TablePanelConfig extends Component<
handlePivotChange = (selectedOption: Array
>) => {
const { fields, model } = this.props;
const pivotId = get(selectedOption, '[0].value', null);
- const field = fields[model.index_pattern].find((f) => f.name === pivotId);
+ const field = fields[getIndexPatternKey(model.index_pattern)].find((f) => f.name === pivotId);
const pivotType = get(field, 'type', model.pivot_type);
this.props.onChange({
@@ -237,15 +238,13 @@ export class TablePanelConfig extends Component<
>
- this.props.onChange({ filter })
- }
- indexPatterns={[model.index_pattern || model.default_index_pattern]}
+ onChange={(filter) => {
+ this.props.onChange({ filter });
+ }}
+ indexPatterns={[model.index_pattern || model.default_index_pattern || '']}
/>
diff --git a/src/plugins/vis_type_timeseries/public/application/components/panel_config/timeseries.tsx b/src/plugins/vis_type_timeseries/public/application/components/panel_config/timeseries.tsx
index c211aafe57ac4..2e714b8db480b 100644
--- a/src/plugins/vis_type_timeseries/public/application/components/panel_config/timeseries.tsx
+++ b/src/plugins/vis_type_timeseries/public/application/components/panel_config/timeseries.tsx
@@ -27,15 +27,14 @@ import { i18n } from '@kbn/i18n';
// @ts-expect-error not typed yet
import { SeriesEditor } from '../series_editor';
-// @ts-ignore should be typed after https://github.com/elastic/kibana/pull/92812 to reduce conflicts
+// @ts-expect-error not typed yet
import { AnnotationsEditor } from '../annotations_editor';
-// @ts-ignore should be typed after https://github.com/elastic/kibana/pull/92812 to reduce conflicts
+// @ts-expect-error not typed yet
import { IndexPattern } from '../index_pattern';
import { createSelectHandler } from '../lib/create_select_handler';
import { ColorPicker } from '../color_picker';
import { YesNo } from '../yes_no';
import { getDefaultQueryLanguage } from '../lib/get_default_query_language';
-// @ts-ignore this is typed in https://github.com/elastic/kibana/pull/92812, remove ignore after merging
import { QueryBarWrapper } from '../query_bar_wrapper';
import { PanelConfigProps, PANEL_CONFIG_TABS } from './types';
import { TimeseriesVisParams } from '../../../types';
@@ -183,9 +182,9 @@ export class TimeseriesPanelConfig extends Component<
fields={this.props.fields}
model={this.props.model}
onChange={this.props.onChange}
- allowLevelofDetail={true}
+ allowLevelOfDetail={true}
+ allowIndexSwitchingMode={true}
/>
-
@@ -202,13 +201,13 @@ export class TimeseriesPanelConfig extends Component<
>
{
+ this.props.onChange({ filter });
}}
- onChange={(filter: PanelConfigProps['model']['filter']) =>
- this.props.onChange({ filter })
- }
- indexPatterns={[model.index_pattern || model.default_index_pattern]}
+ indexPatterns={[model.index_pattern || model.default_index_pattern || '']}
/>
diff --git a/src/plugins/vis_type_timeseries/public/application/components/panel_config/top_n.tsx b/src/plugins/vis_type_timeseries/public/application/components/panel_config/top_n.tsx
index 184063f88ef03..6252c8f1c31bb 100644
--- a/src/plugins/vis_type_timeseries/public/application/components/panel_config/top_n.tsx
+++ b/src/plugins/vis_type_timeseries/public/application/components/panel_config/top_n.tsx
@@ -33,7 +33,6 @@ import { ColorRules } from '../color_rules';
import { ColorPicker } from '../color_picker';
import { YesNo } from '../yes_no';
import { getDefaultQueryLanguage } from '../lib/get_default_query_language';
-// @ts-ignore this is typed in https://github.com/elastic/kibana/pull/92812, remove ignore after merging
import { QueryBarWrapper } from '../query_bar_wrapper';
import { PanelConfigProps, PANEL_CONFIG_TABS } from './types';
import { TimeseriesVisParams } from '../../../types';
@@ -120,6 +119,7 @@ export class TopNPanelConfig extends Component<
fields={this.props.fields}
model={this.props.model}
onChange={this.props.onChange}
+ allowIndexSwitchingMode={true}
/>
@@ -138,13 +138,13 @@ export class TopNPanelConfig extends Component<
>
- this.props.onChange({ filter })
- }
- indexPatterns={[model.index_pattern || model.default_index_pattern]}
+ onChange={(filter: PanelConfigProps['model']['filter']) => {
+ this.props.onChange({ filter });
+ }}
+ indexPatterns={[model.index_pattern || model.default_index_pattern || '']}
/>
diff --git a/src/plugins/vis_type_timeseries/public/application/components/query_bar_wrapper.tsx b/src/plugins/vis_type_timeseries/public/application/components/query_bar_wrapper.tsx
new file mode 100644
index 0000000000000..f9a5de313521a
--- /dev/null
+++ b/src/plugins/vis_type_timeseries/public/application/components/query_bar_wrapper.tsx
@@ -0,0 +1,57 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import React, { useContext, useEffect, useState } from 'react';
+
+import { CoreStartContext } from '../contexts/query_input_bar_context';
+import { IndexPatternValue } from '../../../common/types';
+
+import { QueryStringInput, QueryStringInputProps } from '../../../../../plugins/data/public';
+import { getDataStart } from '../../services';
+import { fetchIndexPattern, isStringTypeIndexPattern } from '../../../common/index_patterns_utils';
+
+type QueryBarWrapperProps = Pick & {
+ indexPatterns: IndexPatternValue[];
+};
+
+export function QueryBarWrapper({ query, onChange, indexPatterns }: QueryBarWrapperProps) {
+ const { indexPatterns: indexPatternsService } = getDataStart();
+ const [indexes, setIndexes] = useState([]);
+
+ const coreStartContext = useContext(CoreStartContext);
+
+ useEffect(() => {
+ async function fetchIndexes() {
+ const i: QueryStringInputProps['indexPatterns'] = [];
+
+ for (const index of indexPatterns ?? []) {
+ if (isStringTypeIndexPattern(index)) {
+ i.push(index);
+ } else if (index?.id) {
+ const fetchedIndex = await fetchIndexPattern(index, indexPatternsService);
+
+ if (fetchedIndex.indexPattern) {
+ i.push(fetchedIndex.indexPattern);
+ }
+ }
+ }
+ setIndexes(i);
+ }
+
+ fetchIndexes();
+ }, [indexPatterns, indexPatternsService]);
+
+ return (
+
+ );
+}
diff --git a/src/plugins/vis_type_timeseries/public/application/components/series_config.js b/src/plugins/vis_type_timeseries/public/application/components/series_config.js
index 4e48ed4406ea5..3185503acb569 100644
--- a/src/plugins/vis_type_timeseries/public/application/components/series_config.js
+++ b/src/plugins/vis_type_timeseries/public/application/components/series_config.js
@@ -137,5 +137,5 @@ SeriesConfig.propTypes = {
panel: PropTypes.object,
model: PropTypes.object,
onChange: PropTypes.func,
- indexPatternForQuery: PropTypes.string,
+ indexPatternForQuery: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
};
diff --git a/src/plugins/vis_type_timeseries/public/application/components/series_config_query_bar_with_ignore_global_filter.js b/src/plugins/vis_type_timeseries/public/application/components/series_config_query_bar_with_ignore_global_filter.js
index 0b67d52c23cd2..950101103b3a5 100644
--- a/src/plugins/vis_type_timeseries/public/application/components/series_config_query_bar_with_ignore_global_filter.js
+++ b/src/plugins/vis_type_timeseries/public/application/components/series_config_query_bar_with_ignore_global_filter.js
@@ -90,5 +90,5 @@ SeriesConfigQueryBarWithIgnoreGlobalFilter.propTypes = {
onChange: PropTypes.func,
model: PropTypes.object,
panel: PropTypes.object,
- indexPatternForQuery: PropTypes.string,
+ indexPatternForQuery: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
};
diff --git a/src/plugins/vis_type_timeseries/public/application/components/splits/terms.js b/src/plugins/vis_type_timeseries/public/application/components/splits/terms.js
index 5891320aa684f..b996abd6373ab 100644
--- a/src/plugins/vis_type_timeseries/public/application/components/splits/terms.js
+++ b/src/plugins/vis_type_timeseries/public/application/components/splits/terms.js
@@ -25,7 +25,7 @@ import {
EuiFieldText,
} from '@elastic/eui';
import { injectI18n, FormattedMessage } from '@kbn/i18n/react';
-import { FIELD_TYPES } from '../../../../common/field_types';
+import { KBN_FIELD_TYPES } from '../../../../../data/public';
import { STACKED_OPTIONS } from '../../visualizations/constants';
const DEFAULTS = { terms_direction: 'desc', terms_size: 10, terms_order_by: '_count' };
@@ -133,7 +133,7 @@ export const SplitByTermsUI = ({
- {selectedFieldType === FIELD_TYPES.STRING && (
+ {selectedFieldType === KBN_FIELD_TYPES.STRING && (
{
+ abortableFetchFields = (extractedIndexPatterns: IndexPatternValue[]) => {
this.abortControllerFetchFields?.abort();
this.abortControllerFetchFields = new AbortController();
@@ -202,7 +202,7 @@ export class VisEditor extends Component {
const defaultIndexTitle = index?.title ?? '';
- const indexPatterns = extractIndexPatterns(this.props.vis.params, defaultIndexTitle);
+ const indexPatterns = extractIndexPatternValues(this.props.vis.params, defaultIndexTitle);
const visFields = await fetchFields(indexPatterns);
this.setState((state) => ({
diff --git a/src/plugins/vis_type_timeseries/public/application/components/vis_types/gauge/series.js b/src/plugins/vis_type_timeseries/public/application/components/vis_types/gauge/series.js
index 2909167031d08..46cc8b6ebe635 100644
--- a/src/plugins/vis_type_timeseries/public/application/components/vis_types/gauge/series.js
+++ b/src/plugins/vis_type_timeseries/public/application/components/vis_types/gauge/series.js
@@ -198,7 +198,7 @@ GaugeSeriesUi.propTypes = {
visible: PropTypes.bool,
uiRestrictions: PropTypes.object,
dragHandleProps: PropTypes.object,
- indexPatternForQuery: PropTypes.string,
+ indexPatternForQuery: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
};
export const GaugeSeries = injectI18n(GaugeSeriesUi);
diff --git a/src/plugins/vis_type_timeseries/public/application/components/vis_types/markdown/series.js b/src/plugins/vis_type_timeseries/public/application/components/vis_types/markdown/series.js
index 6f00abe5aa2c0..f9817242a101a 100644
--- a/src/plugins/vis_type_timeseries/public/application/components/vis_types/markdown/series.js
+++ b/src/plugins/vis_type_timeseries/public/application/components/vis_types/markdown/series.js
@@ -200,7 +200,7 @@ MarkdownSeriesUi.propTypes = {
visible: PropTypes.bool,
uiRestrictions: PropTypes.object,
dragHandleProps: PropTypes.object,
- indexPatternForQuery: PropTypes.string,
+ indexPatternForQuery: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
};
export const MarkdownSeries = injectI18n(MarkdownSeriesUi);
diff --git a/src/plugins/vis_type_timeseries/public/application/components/vis_types/metric/series.js b/src/plugins/vis_type_timeseries/public/application/components/vis_types/metric/series.js
index 64425cf534226..5ec2378792812 100644
--- a/src/plugins/vis_type_timeseries/public/application/components/vis_types/metric/series.js
+++ b/src/plugins/vis_type_timeseries/public/application/components/vis_types/metric/series.js
@@ -211,7 +211,7 @@ MetricSeriesUi.propTypes = {
togglePanelActivation: PropTypes.func,
uiRestrictions: PropTypes.object,
dragHandleProps: PropTypes.object,
- indexPatternForQuery: PropTypes.string,
+ indexPatternForQuery: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
};
export const MetricSeries = injectI18n(MetricSeriesUi);
diff --git a/src/plugins/vis_type_timeseries/public/application/components/vis_types/table/config.js b/src/plugins/vis_type_timeseries/public/application/components/vis_types/table/config.js
index fecd6cde1dca8..0ba8d3e855365 100644
--- a/src/plugins/vis_type_timeseries/public/application/components/vis_types/table/config.js
+++ b/src/plugins/vis_type_timeseries/public/application/components/vis_types/table/config.js
@@ -9,6 +9,8 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import uuid from 'uuid';
+import { i18n } from '@kbn/i18n';
+
import { DataFormatPicker } from '../../data_format_picker';
import { createSelectHandler } from '../../lib/create_select_handler';
import { createTextHandler } from '../../lib/create_text_handler';
@@ -28,11 +30,11 @@ import {
EuiSpacer,
EuiTitle,
} from '@elastic/eui';
-import { FormattedMessage, injectI18n } from '@kbn/i18n/react';
+import { FormattedMessage } from '@kbn/i18n/react';
import { getDefaultQueryLanguage } from '../../lib/get_default_query_language';
-
import { QueryBarWrapper } from '../../query_bar_wrapper';
-class TableSeriesConfigUI extends Component {
+
+export class TableSeriesConfig extends Component {
UNSAFE_componentWillMount() {
const { model } = this.props;
if (!model.color_rules || (model.color_rules && model.color_rules.length === 0)) {
@@ -48,68 +50,58 @@ class TableSeriesConfigUI extends Component {
const handleSelectChange = createSelectHandler(this.props.onChange);
const handleTextChange = createTextHandler(this.props.onChange);
const htmlId = htmlIdGenerator();
- const { intl } = this.props;
const functionOptions = [
{
- label: intl.formatMessage({
- id: 'visTypeTimeseries.table.sumLabel',
+ label: i18n.translate('visTypeTimeseries.table.sumLabel', {
defaultMessage: 'Sum',
}),
value: 'sum',
},
{
- label: intl.formatMessage({
- id: 'visTypeTimeseries.table.maxLabel',
+ label: i18n.translate('visTypeTimeseries.table.maxLabel', {
defaultMessage: 'Max',
}),
value: 'max',
},
{
- label: intl.formatMessage({
- id: 'visTypeTimeseries.table.minLabel',
+ label: i18n.translate('visTypeTimeseries.table.minLabel', {
defaultMessage: 'Min',
}),
value: 'min',
},
{
- label: intl.formatMessage({
- id: 'visTypeTimeseries.table.avgLabel',
+ label: i18n.translate('visTypeTimeseries.table.avgLabel', {
defaultMessage: 'Avg',
}),
value: 'mean',
},
{
- label: intl.formatMessage({
- id: 'visTypeTimeseries.table.overallSumLabel',
+ label: i18n.translate('visTypeTimeseries.table.overallSumLabel', {
defaultMessage: 'Overall Sum',
}),
value: 'overall_sum',
},
{
- label: intl.formatMessage({
- id: 'visTypeTimeseries.table.overallMaxLabel',
+ label: i18n.translate('visTypeTimeseries.table.overallMaxLabel', {
defaultMessage: 'Overall Max',
}),
value: 'overall_max',
},
{
- label: intl.formatMessage({
- id: 'visTypeTimeseries.table.overallMinLabel',
+ label: i18n.translate('visTypeTimeseries.table.overallMinLabel', {
defaultMessage: 'Overall Min',
}),
value: 'overall_min',
},
{
- label: intl.formatMessage({
- id: 'visTypeTimeseries.table.overallAvgLabel',
+ label: i18n.translate('visTypeTimeseries.table.overallAvgLabel', {
defaultMessage: 'Overall Avg',
}),
value: 'overall_avg',
},
{
- label: intl.formatMessage({
- id: 'visTypeTimeseries.table.cumulativeSumLabel',
+ label: i18n.translate('visTypeTimeseries.table.cumulativeSumLabel', {
defaultMessage: 'Cumulative Sum',
}),
value: 'cumulative_sum',
@@ -170,11 +162,8 @@ class TableSeriesConfigUI extends Component {
>
this.props.onChange({ filter })}
indexPatterns={[this.props.indexPatternForQuery]}
@@ -259,11 +248,9 @@ class TableSeriesConfigUI extends Component {
}
}
-TableSeriesConfigUI.propTypes = {
+TableSeriesConfig.propTypes = {
fields: PropTypes.object,
model: PropTypes.object,
onChange: PropTypes.func,
- indexPatternForQuery: PropTypes.string,
+ indexPatternForQuery: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
};
-
-export const TableSeriesConfig = injectI18n(TableSeriesConfigUI);
diff --git a/src/plugins/vis_type_timeseries/public/application/components/vis_types/table/series.js b/src/plugins/vis_type_timeseries/public/application/components/vis_types/table/series.js
index a56afd1f817b3..acd2f4cc17d4a 100644
--- a/src/plugins/vis_type_timeseries/public/application/components/vis_types/table/series.js
+++ b/src/plugins/vis_type_timeseries/public/application/components/vis_types/table/series.js
@@ -186,7 +186,7 @@ TableSeriesUI.propTypes = {
togglePanelActivation: PropTypes.func,
uiRestrictions: PropTypes.object,
dragHandleProps: PropTypes.object,
- indexPatternForQuery: PropTypes.string,
+ indexPatternForQuery: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
};
export const TableSeries = injectI18n(TableSeriesUI);
diff --git a/src/plugins/vis_type_timeseries/public/application/components/vis_types/timeseries/config.js b/src/plugins/vis_type_timeseries/public/application/components/vis_types/timeseries/config.js
index 3df12dafd5a66..22bf2fa4ca708 100644
--- a/src/plugins/vis_type_timeseries/public/application/components/vis_types/timeseries/config.js
+++ b/src/plugins/vis_type_timeseries/public/application/components/vis_types/timeseries/config.js
@@ -542,7 +542,7 @@ export const TimeseriesConfig = injectI18n(function (props) {
{...props}
prefix="series_"
disabled={!model.override_index_pattern}
- allowLevelofDetail={true}
+ allowLevelOfDetail={true}
/>
@@ -555,6 +555,6 @@ TimeseriesConfig.propTypes = {
model: PropTypes.object,
panel: PropTypes.object,
onChange: PropTypes.func,
- indexPatternForQuery: PropTypes.string,
+ indexPatternForQuery: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
seriesQuantity: PropTypes.object,
};
diff --git a/src/plugins/vis_type_timeseries/public/application/components/vis_types/timeseries/series.js b/src/plugins/vis_type_timeseries/public/application/components/vis_types/timeseries/series.js
index 76df07ce7c8c4..bb10ac57c5ae9 100644
--- a/src/plugins/vis_type_timeseries/public/application/components/vis_types/timeseries/series.js
+++ b/src/plugins/vis_type_timeseries/public/application/components/vis_types/timeseries/series.js
@@ -209,7 +209,7 @@ TimeseriesSeriesUI.propTypes = {
togglePanelActivation: PropTypes.func,
uiRestrictions: PropTypes.object,
dragHandleProps: PropTypes.object,
- indexPatternForQuery: PropTypes.string,
+ indexPatternForQuery: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
seriesQuantity: PropTypes.object,
};
diff --git a/src/plugins/vis_type_timeseries/public/application/components/vis_types/top_n/series.js b/src/plugins/vis_type_timeseries/public/application/components/vis_types/top_n/series.js
index bfe446a8226e8..61bb7e2473dd9 100644
--- a/src/plugins/vis_type_timeseries/public/application/components/vis_types/top_n/series.js
+++ b/src/plugins/vis_type_timeseries/public/application/components/vis_types/top_n/series.js
@@ -200,5 +200,5 @@ TopNSeries.propTypes = {
togglePanelActivation: PropTypes.func,
uiRestrictions: PropTypes.object,
dragHandleProps: PropTypes.object,
- indexPatternForQuery: PropTypes.string,
+ indexPatternForQuery: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
};
diff --git a/src/plugins/vis_type_timeseries/public/application/contexts/panel_model_context.ts b/src/plugins/vis_type_timeseries/public/application/contexts/panel_model_context.ts
new file mode 100644
index 0000000000000..534f686ca13fc
--- /dev/null
+++ b/src/plugins/vis_type_timeseries/public/application/contexts/panel_model_context.ts
@@ -0,0 +1,12 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import React from 'react';
+import { PanelSchema } from '../../../common/types';
+
+export const PanelModelContext = React.createContext(null);
diff --git a/src/plugins/vis_type_timeseries/public/application/lib/fetch_fields.ts b/src/plugins/vis_type_timeseries/public/application/lib/fetch_fields.ts
index 088930f90a765..af3ddd643cac8 100644
--- a/src/plugins/vis_type_timeseries/public/application/lib/fetch_fields.ts
+++ b/src/plugins/vis_type_timeseries/public/application/lib/fetch_fields.ts
@@ -9,12 +9,14 @@
import { i18n } from '@kbn/i18n';
import { getCoreStart, getDataStart } from '../../services';
import { ROUTES } from '../../../common/constants';
-import { SanitizedFieldType } from '../../../common/types';
+import { SanitizedFieldType, IndexPatternValue } from '../../../common/types';
+import { getIndexPatternKey } from '../../../common/index_patterns_utils';
+import { toSanitizedFieldType } from '../../../common/fields_utils';
export type VisFields = Record;
export async function fetchFields(
- indexes: string[] = [],
+ indexes: IndexPatternValue[] = [],
signal?: AbortSignal
): Promise {
const patterns = Array.isArray(indexes) ? indexes : [indexes];
@@ -25,26 +27,33 @@ export async function fetchFields(
const defaultIndexPattern = await dataStart.indexPatterns.getDefault();
const indexFields = await Promise.all(
patterns.map(async (pattern) => {
- return coreStart.http.get(ROUTES.FIELDS, {
- query: {
- index: pattern,
- },
- signal,
- });
+ if (typeof pattern !== 'string' && pattern?.id) {
+ return toSanitizedFieldType(
+ (await dataStart.indexPatterns.get(pattern.id)).getNonScriptedFields()
+ );
+ } else {
+ return coreStart.http.get(ROUTES.FIELDS, {
+ query: {
+ index: `${pattern ?? ''}`,
+ },
+ signal,
+ });
+ }
})
);
const fields: VisFields = patterns.reduce(
(cumulatedFields, currentPattern, index) => ({
...cumulatedFields,
- [currentPattern]: indexFields[index],
+ [getIndexPatternKey(currentPattern)]: indexFields[index],
}),
{}
);
- if (defaultIndexPattern?.title && patterns.includes(defaultIndexPattern.title)) {
- fields[''] = fields[defaultIndexPattern.title];
+ if (defaultIndexPattern) {
+ fields[''] = toSanitizedFieldType(await defaultIndexPattern.getNonScriptedFields());
}
+
return fields;
} catch (error) {
if (error.name !== 'AbortError') {
diff --git a/src/plugins/vis_type_timeseries/public/metrics_type.ts b/src/plugins/vis_type_timeseries/public/metrics_type.ts
index 9e996fcc74833..5d5e082b2b7bb 100644
--- a/src/plugins/vis_type_timeseries/public/metrics_type.ts
+++ b/src/plugins/vis_type_timeseries/public/metrics_type.ts
@@ -10,6 +10,7 @@ import { i18n } from '@kbn/i18n';
import { TSVB_EDITOR_NAME } from './application';
import { PANEL_TYPES } from '../common/panel_types';
+import { isStringTypeIndexPattern } from '../common/index_patterns_utils';
import { toExpressionAst } from './to_ast';
import { VIS_EVENT_TO_TRIGGER, VisGroups, VisParams } from '../../visualizations/public';
import { getDataStart } from './services';
@@ -53,6 +54,7 @@ export const metricsVisDefinition = {
],
time_field: '',
index_pattern: '',
+ use_kibana_indexes: true,
interval: '',
axis_position: 'left',
axis_formatter: 'number',
@@ -77,7 +79,20 @@ export const metricsVisDefinition = {
inspectorAdapters: {},
getUsedIndexPattern: async (params: VisParams) => {
const { indexPatterns } = getDataStart();
+ const indexPatternValue = params.index_pattern;
- return params.index_pattern ? await indexPatterns.find(params.index_pattern) : [];
+ if (indexPatternValue) {
+ if (isStringTypeIndexPattern(indexPatternValue)) {
+ return await indexPatterns.find(indexPatternValue);
+ }
+
+ if (indexPatternValue.id) {
+ return [await indexPatterns.get(indexPatternValue.id)];
+ }
+ }
+
+ const defaultIndex = await indexPatterns.getDefault();
+
+ return defaultIndex ? [defaultIndex] : [];
},
};
diff --git a/src/plugins/vis_type_timeseries/server/lib/get_fields.ts b/src/plugins/vis_type_timeseries/server/lib/get_fields.ts
index f1bc5a11550e9..b0e85f8e44fbe 100644
--- a/src/plugins/vis_type_timeseries/server/lib/get_fields.ts
+++ b/src/plugins/vis_type_timeseries/server/lib/get_fields.ts
@@ -10,6 +10,7 @@ import { uniqBy } from 'lodash';
import { Framework } from '../plugin';
import { VisTypeTimeseriesFieldsRequest, VisTypeTimeseriesRequestHandlerContext } from '../types';
+import { getCachedIndexPatternFetcher } from './search_strategies/lib/cached_index_pattern_fetcher';
export async function getFields(
requestContext: VisTypeTimeseriesRequestHandlerContext,
@@ -17,26 +18,29 @@ export async function getFields(
framework: Framework,
indexPatternString: string
) {
+ const indexPatternsService = await framework.getIndexPatternsService(requestContext);
+ const cachedIndexPatternFetcher = getCachedIndexPatternFetcher(indexPatternsService);
+
if (!indexPatternString) {
- const indexPatternsService = await framework.getIndexPatternsService(requestContext);
const defaultIndexPattern = await indexPatternsService.getDefault();
indexPatternString = defaultIndexPattern?.title ?? '';
}
+ const fetchedIndex = await cachedIndexPatternFetcher(indexPatternString);
+
const {
searchStrategy,
capabilities,
} = (await framework.searchStrategyRegistry.getViableStrategy(
requestContext,
request,
- indexPatternString
+ fetchedIndex
))!;
const fields = await searchStrategy.getFieldsForWildcard(
- requestContext,
- request,
- indexPatternString,
+ fetchedIndex,
+ indexPatternsService,
capabilities
);
diff --git a/src/plugins/vis_type_timeseries/server/lib/get_vis_data.ts b/src/plugins/vis_type_timeseries/server/lib/get_vis_data.ts
index 0ad50a296b481..d91104fb299d7 100644
--- a/src/plugins/vis_type_timeseries/server/lib/get_vis_data.ts
+++ b/src/plugins/vis_type_timeseries/server/lib/get_vis_data.ts
@@ -19,6 +19,7 @@ import type {
import { getSeriesData } from './vis_data/get_series_data';
import { getTableData } from './vis_data/get_table_data';
import { getEsQueryConfig } from './vis_data/helpers/get_es_query_uisettings';
+import { getCachedIndexPatternFetcher } from './search_strategies/lib/cached_index_pattern_fetcher';
export async function getVisData(
requestContext: VisTypeTimeseriesRequestHandlerContext,
@@ -29,12 +30,14 @@ export async function getVisData(
const esShardTimeout = await framework.getEsShardTimeout();
const indexPatternsService = await framework.getIndexPatternsService(requestContext);
const esQueryConfig = await getEsQueryConfig(uiSettings);
+
const services: VisTypeTimeseriesRequestServices = {
esQueryConfig,
esShardTimeout,
indexPatternsService,
uiSettings,
searchStrategyRegistry: framework.searchStrategyRegistry,
+ cachedIndexPatternFetcher: getCachedIndexPatternFetcher(indexPatternsService),
};
const promises = request.body.panels.map((panel) => {
diff --git a/src/plugins/vis_type_timeseries/server/lib/search_strategies/lib/cached_index_pattern_fetcher.test.ts b/src/plugins/vis_type_timeseries/server/lib/search_strategies/lib/cached_index_pattern_fetcher.test.ts
new file mode 100644
index 0000000000000..aeaf3ca2cd327
--- /dev/null
+++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/lib/cached_index_pattern_fetcher.test.ts
@@ -0,0 +1,111 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { IndexPattern, IndexPatternsService } from 'src/plugins/data/server';
+import {
+ getCachedIndexPatternFetcher,
+ CachedIndexPatternFetcher,
+} from './cached_index_pattern_fetcher';
+
+describe('CachedIndexPatternFetcher', () => {
+ let mockedIndices: IndexPattern[] | [];
+ let cachedIndexPatternFetcher: CachedIndexPatternFetcher;
+
+ beforeEach(() => {
+ mockedIndices = [];
+
+ const indexPatternsService = ({
+ getDefault: jest.fn(() => Promise.resolve({ id: 'default', title: 'index' })),
+ get: jest.fn(() => Promise.resolve(mockedIndices[0])),
+ find: jest.fn(() => Promise.resolve(mockedIndices || [])),
+ } as unknown) as IndexPatternsService;
+
+ cachedIndexPatternFetcher = getCachedIndexPatternFetcher(indexPatternsService);
+ });
+
+ test('should return default index on no input value', async () => {
+ const value = await cachedIndexPatternFetcher('');
+ expect(value).toMatchInlineSnapshot(`
+ Object {
+ "indexPattern": Object {
+ "id": "default",
+ "title": "index",
+ },
+ "indexPatternString": "index",
+ }
+ `);
+ });
+
+ describe('text-based index', () => {
+ test('should return the Kibana index if it exists', async () => {
+ mockedIndices = [
+ {
+ id: 'indexId',
+ title: 'indexTitle',
+ },
+ ] as IndexPattern[];
+
+ const value = await cachedIndexPatternFetcher('indexTitle');
+
+ expect(value).toMatchInlineSnapshot(`
+ Object {
+ "indexPattern": Object {
+ "id": "indexId",
+ "title": "indexTitle",
+ },
+ "indexPatternString": "indexTitle",
+ }
+ `);
+ });
+
+ test('should return only indexPatternString if Kibana index does not exist', async () => {
+ const value = await cachedIndexPatternFetcher('indexTitle');
+
+ expect(value).toMatchInlineSnapshot(`
+ Object {
+ "indexPattern": undefined,
+ "indexPatternString": "indexTitle",
+ }
+ `);
+ });
+ });
+
+ describe('object-based index', () => {
+ test('should return the Kibana index if it exists', async () => {
+ mockedIndices = [
+ {
+ id: 'indexId',
+ title: 'indexTitle',
+ },
+ ] as IndexPattern[];
+
+ const value = await cachedIndexPatternFetcher({ id: 'indexId' });
+
+ expect(value).toMatchInlineSnapshot(`
+ Object {
+ "indexPattern": Object {
+ "id": "indexId",
+ "title": "indexTitle",
+ },
+ "indexPatternString": "indexTitle",
+ }
+ `);
+ });
+
+ test('should return default index if Kibana index not found', async () => {
+ const value = await cachedIndexPatternFetcher({ id: 'indexId' });
+
+ expect(value).toMatchInlineSnapshot(`
+ Object {
+ "indexPattern": undefined,
+ "indexPatternString": "",
+ }
+ `);
+ });
+ });
+});
diff --git a/src/plugins/vis_type_timeseries/server/lib/search_strategies/lib/cached_index_pattern_fetcher.ts b/src/plugins/vis_type_timeseries/server/lib/search_strategies/lib/cached_index_pattern_fetcher.ts
new file mode 100644
index 0000000000000..68cbd93cdc614
--- /dev/null
+++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/lib/cached_index_pattern_fetcher.ts
@@ -0,0 +1,32 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { getIndexPatternKey, fetchIndexPattern } from '../../../../common/index_patterns_utils';
+
+import type { IndexPatternsService } from '../../../../../data/server';
+import type { IndexPatternValue, FetchedIndexPattern } from '../../../../common/types';
+
+export const getCachedIndexPatternFetcher = (indexPatternsService: IndexPatternsService) => {
+ const cache = new Map();
+
+ return async (indexPatternValue: IndexPatternValue): Promise => {
+ const key = getIndexPatternKey(indexPatternValue);
+
+ if (cache.has(key)) {
+ return cache.get(key);
+ }
+
+ const fetchedIndex = fetchIndexPattern(indexPatternValue, indexPatternsService);
+
+ cache.set(indexPatternValue, fetchedIndex);
+
+ return fetchedIndex;
+ };
+};
+
+export type CachedIndexPatternFetcher = ReturnType;
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/fields_fetcher.ts b/src/plugins/vis_type_timeseries/server/lib/search_strategies/lib/fields_fetcher.ts
similarity index 57%
rename from src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/fields_fetcher.ts
rename to src/plugins/vis_type_timeseries/server/lib/search_strategies/lib/fields_fetcher.ts
index f95667612efa4..9003eb7fc2ced 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/fields_fetcher.ts
+++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/lib/fields_fetcher.ts
@@ -6,21 +6,26 @@
* Side Public License, v 1.
*/
-import {
- VisTypeTimeseriesRequestHandlerContext,
- VisTypeTimeseriesVisDataRequest,
-} from '../../../types';
-import { AbstractSearchStrategy, DefaultSearchCapabilities } from '../../search_strategies';
+import type { VisTypeTimeseriesVisDataRequest } from '../../../types';
+import type { AbstractSearchStrategy, DefaultSearchCapabilities } from '../index';
+import type { IndexPatternsService } from '../../../../../data/common';
+import type { CachedIndexPatternFetcher } from './cached_index_pattern_fetcher';
export interface FieldsFetcherServices {
- requestContext: VisTypeTimeseriesRequestHandlerContext;
+ indexPatternsService: IndexPatternsService;
+ cachedIndexPatternFetcher: CachedIndexPatternFetcher;
searchStrategy: AbstractSearchStrategy;
capabilities: DefaultSearchCapabilities;
}
export const createFieldsFetcher = (
req: VisTypeTimeseriesVisDataRequest,
- { capabilities, requestContext, searchStrategy }: FieldsFetcherServices
+ {
+ capabilities,
+ indexPatternsService,
+ searchStrategy,
+ cachedIndexPatternFetcher,
+ }: FieldsFetcherServices
) => {
const fieldsCacheMap = new Map();
@@ -28,11 +33,11 @@ export const createFieldsFetcher = (
if (fieldsCacheMap.has(index)) {
return fieldsCacheMap.get(index);
}
+ const fetchedIndex = await cachedIndexPatternFetcher(index);
const fields = await searchStrategy.getFieldsForWildcard(
- requestContext,
- req,
- index,
+ fetchedIndex,
+ indexPatternsService,
capabilities
);
diff --git a/src/plugins/vis_type_timeseries/server/lib/search_strategies/lib/get_index_pattern.ts b/src/plugins/vis_type_timeseries/server/lib/search_strategies/lib/get_index_pattern.ts
deleted file mode 100644
index 512494de290fd..0000000000000
--- a/src/plugins/vis_type_timeseries/server/lib/search_strategies/lib/get_index_pattern.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0 and the Server Side Public License, v 1; you may not use this file except
- * in compliance with, at your election, the Elastic License 2.0 or the Server
- * Side Public License, v 1.
- */
-
-import { IndexPatternsService, IndexPattern } from '../../../../../data/server';
-
-interface IndexPatternObjectDependencies {
- indexPatternsService: IndexPatternsService;
-}
-export async function getIndexPatternObject(
- indexPatternString: string,
- { indexPatternsService }: IndexPatternObjectDependencies
-) {
- let indexPatternObject: IndexPattern | undefined | null;
-
- if (!indexPatternString) {
- indexPatternObject = await indexPatternsService.getDefault();
- } else {
- indexPatternObject = (await indexPatternsService.find(indexPatternString)).find(
- (index) => index.title === indexPatternString
- );
- }
-
- return {
- indexPatternObject,
- indexPatternString: indexPatternObject?.title || indexPatternString || '',
- };
-}
diff --git a/src/plugins/vis_type_timeseries/server/lib/search_strategies/search_strategies_registry.test.ts b/src/plugins/vis_type_timeseries/server/lib/search_strategies/search_strategies_registry.test.ts
index f9a49bc322a29..a6e7c5b11ee64 100644
--- a/src/plugins/vis_type_timeseries/server/lib/search_strategies/search_strategies_registry.test.ts
+++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/search_strategies_registry.test.ts
@@ -10,29 +10,27 @@ import { get } from 'lodash';
import { SearchStrategyRegistry } from './search_strategy_registry';
import { AbstractSearchStrategy, DefaultSearchStrategy } from './strategies';
import { DefaultSearchCapabilities } from './capabilities/default_search_capabilities';
-import { Framework } from '../../plugin';
import { VisTypeTimeseriesRequest, VisTypeTimeseriesRequestHandlerContext } from '../../types';
const getPrivateField = (registry: SearchStrategyRegistry, field: string) =>
get(registry, field) as T;
class MockSearchStrategy extends AbstractSearchStrategy {
- checkForViability() {
- return Promise.resolve({
+ async checkForViability() {
+ return {
isViable: true,
capabilities: {},
- });
+ };
}
}
describe('SearchStrategyRegister', () => {
- const framework = {} as Framework;
const requestContext = {} as VisTypeTimeseriesRequestHandlerContext;
let registry: SearchStrategyRegistry;
beforeAll(() => {
registry = new SearchStrategyRegistry();
- registry.addStrategy(new DefaultSearchStrategy(framework));
+ registry.addStrategy(new DefaultSearchStrategy());
});
test('should init strategies register', () => {
@@ -47,12 +45,11 @@ describe('SearchStrategyRegister', () => {
test('should return a DefaultSearchStrategy instance', async () => {
const req = {} as VisTypeTimeseriesRequest;
- const indexPattern = '*';
const { searchStrategy, capabilities } = (await registry.getViableStrategy(
requestContext,
req,
- indexPattern
+ { indexPatternString: '*', indexPattern: undefined }
))!;
expect(searchStrategy instanceof DefaultSearchStrategy).toBe(true);
@@ -60,7 +57,7 @@ describe('SearchStrategyRegister', () => {
});
test('should add a strategy if it is an instance of AbstractSearchStrategy', () => {
- const anotherSearchStrategy = new MockSearchStrategy(framework);
+ const anotherSearchStrategy = new MockSearchStrategy();
const addedStrategies = registry.addStrategy(anotherSearchStrategy);
expect(addedStrategies.length).toEqual(2);
@@ -69,14 +66,13 @@ describe('SearchStrategyRegister', () => {
test('should return a MockSearchStrategy instance', async () => {
const req = {} as VisTypeTimeseriesRequest;
- const indexPattern = '*';
- const anotherSearchStrategy = new MockSearchStrategy(framework);
+ const anotherSearchStrategy = new MockSearchStrategy();
registry.addStrategy(anotherSearchStrategy);
const { searchStrategy, capabilities } = (await registry.getViableStrategy(
requestContext,
req,
- indexPattern
+ { indexPatternString: '*', indexPattern: undefined }
))!;
expect(searchStrategy instanceof MockSearchStrategy).toBe(true);
diff --git a/src/plugins/vis_type_timeseries/server/lib/search_strategies/search_strategy_registry.ts b/src/plugins/vis_type_timeseries/server/lib/search_strategies/search_strategy_registry.ts
index 11ff4b0a8a51f..4a013fd89735d 100644
--- a/src/plugins/vis_type_timeseries/server/lib/search_strategies/search_strategy_registry.ts
+++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/search_strategy_registry.ts
@@ -6,14 +6,10 @@
* Side Public License, v 1.
*/
-import { extractIndexPatterns } from '../../../common/extract_index_patterns';
-import { PanelSchema } from '../../../common/types';
-import {
- VisTypeTimeseriesRequest,
- VisTypeTimeseriesRequestHandlerContext,
- VisTypeTimeseriesVisDataRequest,
-} from '../../types';
+import { VisTypeTimeseriesRequest, VisTypeTimeseriesRequestHandlerContext } from '../../types';
import { AbstractSearchStrategy } from './strategies';
+import { FetchedIndexPattern } from '../../../common/types';
+
export class SearchStrategyRegistry {
private strategies: AbstractSearchStrategy[] = [];
@@ -27,13 +23,13 @@ export class SearchStrategyRegistry {
async getViableStrategy(
requestContext: VisTypeTimeseriesRequestHandlerContext,
req: VisTypeTimeseriesRequest,
- indexPattern: string
+ fetchedIndexPattern: FetchedIndexPattern
) {
for (const searchStrategy of this.strategies) {
const { isViable, capabilities } = await searchStrategy.checkForViability(
requestContext,
req,
- indexPattern
+ fetchedIndexPattern
);
if (isViable) {
@@ -44,14 +40,4 @@ export class SearchStrategyRegistry {
}
}
}
-
- async getViableStrategyForPanel(
- requestContext: VisTypeTimeseriesRequestHandlerContext,
- req: VisTypeTimeseriesVisDataRequest,
- panel: PanelSchema
- ) {
- const indexPattern = extractIndexPatterns(panel, panel.default_index_pattern).join(',');
-
- return this.getViableStrategy(requestContext, req, indexPattern);
- }
}
diff --git a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.test.ts b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.test.ts
index e7282eba58ec7..fb66e32447c22 100644
--- a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.test.ts
+++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.test.ts
@@ -6,48 +6,26 @@
* Side Public License, v 1.
*/
-const mockGetFieldsForWildcard = jest.fn(() => []);
-
-jest.mock('../../../../../data/server', () => ({
- indexPatterns: {
- isNestedField: jest.fn(() => false),
- },
- IndexPatternsFetcher: jest.fn().mockImplementation(() => ({
- getFieldsForWildcard: mockGetFieldsForWildcard,
- })),
-}));
+import { IndexPatternsService } from '../../../../../data/common';
import { from } from 'rxjs';
-import { AbstractSearchStrategy, toSanitizedFieldType } from './abstract_search_strategy';
+import { AbstractSearchStrategy } from './abstract_search_strategy';
import type { IFieldType } from '../../../../../data/common';
-import type { FieldSpec, RuntimeField } from '../../../../../data/common';
-import {
- VisTypeTimeseriesRequest,
+import type { CachedIndexPatternFetcher } from '../lib/cached_index_pattern_fetcher';
+import type {
VisTypeTimeseriesRequestHandlerContext,
VisTypeTimeseriesVisDataRequest,
} from '../../../types';
-import { Framework } from '../../../plugin';
-import { indexPatterns } from '../../../../../data/server';
class FooSearchStrategy extends AbstractSearchStrategy {}
describe('AbstractSearchStrategy', () => {
let abstractSearchStrategy: AbstractSearchStrategy;
let mockedFields: IFieldType[];
- let indexPattern: string;
let requestContext: VisTypeTimeseriesRequestHandlerContext;
- let framework: Framework;
beforeEach(() => {
mockedFields = [];
- framework = ({
- getIndexPatternsService: jest.fn(() =>
- Promise.resolve({
- find: jest.fn(() => []),
- getDefault: jest.fn(() => {}),
- })
- ),
- } as unknown) as Framework;
requestContext = ({
core: {
elasticsearch: {
@@ -60,7 +38,7 @@ describe('AbstractSearchStrategy', () => {
search: jest.fn().mockReturnValue(from(Promise.resolve({}))),
},
} as unknown) as VisTypeTimeseriesRequestHandlerContext;
- abstractSearchStrategy = new FooSearchStrategy(framework);
+ abstractSearchStrategy = new FooSearchStrategy();
});
test('should init an AbstractSearchStrategy instance', () => {
@@ -71,17 +49,15 @@ describe('AbstractSearchStrategy', () => {
test('should return fields for wildcard', async () => {
const fields = await abstractSearchStrategy.getFieldsForWildcard(
- requestContext,
- {} as VisTypeTimeseriesRequest,
- indexPattern
+ { indexPatternString: '', indexPattern: undefined },
+ ({
+ getDefault: jest.fn(),
+ getFieldsForWildcard: jest.fn(() => Promise.resolve(mockedFields)),
+ } as unknown) as IndexPatternsService,
+ (() => Promise.resolve({}) as unknown) as CachedIndexPatternFetcher
);
expect(fields).toEqual(mockedFields);
- expect(mockGetFieldsForWildcard).toHaveBeenCalledWith({
- pattern: indexPattern,
- metaFields: [],
- fieldCapsOptions: { allow_no_indices: true },
- });
});
test('should return response', async () => {
@@ -117,68 +93,4 @@ describe('AbstractSearchStrategy', () => {
}
);
});
-
- describe('toSanitizedFieldType', () => {
- const mockedField = {
- lang: 'lang',
- conflictDescriptions: {},
- aggregatable: true,
- name: 'name',
- type: 'type',
- esTypes: ['long', 'geo'],
- } as FieldSpec;
-
- test('should sanitize fields ', async () => {
- const fields = [mockedField] as FieldSpec[];
-
- expect(toSanitizedFieldType(fields)).toMatchInlineSnapshot(`
- Array [
- Object {
- "label": "name",
- "name": "name",
- "type": "type",
- },
- ]
- `);
- });
-
- test('should filter runtime fields', async () => {
- const fields: FieldSpec[] = [
- {
- ...mockedField,
- runtimeField: {} as RuntimeField,
- },
- ];
-
- expect(toSanitizedFieldType(fields)).toMatchInlineSnapshot(`Array []`);
- });
-
- test('should filter non-aggregatable fields', async () => {
- const fields: FieldSpec[] = [
- {
- ...mockedField,
- aggregatable: false,
- },
- ];
-
- expect(toSanitizedFieldType(fields)).toMatchInlineSnapshot(`Array []`);
- });
-
- test('should filter nested fields', async () => {
- const fields: FieldSpec[] = [
- {
- ...mockedField,
- subType: {
- nested: {
- path: 'path',
- },
- },
- },
- ];
- // @ts-expect-error
- indexPatterns.isNestedField.mockReturnValue(true);
-
- expect(toSanitizedFieldType(fields)).toMatchInlineSnapshot(`Array []`);
- });
- });
});
diff --git a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.ts b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.ts
index 5bc008091627f..26c3a6c7c8bf7 100644
--- a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.ts
+++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.ts
@@ -6,37 +6,17 @@
* Side Public License, v 1.
*/
-import { indexPatterns, IndexPatternsFetcher } from '../../../../../data/server';
+import { IndexPatternsService } from '../../../../../data/server';
+import { toSanitizedFieldType } from '../../../../common/fields_utils';
-import type { Framework } from '../../../plugin';
-import type { FieldSpec } from '../../../../../data/common';
-import type { SanitizedFieldType } from '../../../../common/types';
+import type { FetchedIndexPattern } from '../../../../common/types';
import type {
VisTypeTimeseriesRequest,
VisTypeTimeseriesRequestHandlerContext,
VisTypeTimeseriesVisDataRequest,
} from '../../../types';
-import { getIndexPatternObject } from '../lib/get_index_pattern';
-
-export const toSanitizedFieldType = (fields: FieldSpec[]) => {
- return fields
- .filter(
- (field) =>
- // Make sure to only include mapped fields, e.g. no index pattern runtime fields
- !field.runtimeField && field.aggregatable && !indexPatterns.isNestedField(field)
- )
- .map(
- (field) =>
- ({
- name: field.name,
- label: field.customLabel ?? field.name,
- type: field.type,
- } as SanitizedFieldType)
- );
-};
export abstract class AbstractSearchStrategy {
- constructor(private framework: Framework) {}
async search(
requestContext: VisTypeTimeseriesRequestHandlerContext,
req: VisTypeTimeseriesVisDataRequest,
@@ -66,35 +46,25 @@ export abstract class AbstractSearchStrategy {
checkForViability(
requestContext: VisTypeTimeseriesRequestHandlerContext,
req: VisTypeTimeseriesRequest,
- indexPattern: string
+ fetchedIndexPattern: FetchedIndexPattern
): Promise<{ isViable: boolean; capabilities: any }> {
throw new TypeError('Must override method');
}
async getFieldsForWildcard(
- requestContext: VisTypeTimeseriesRequestHandlerContext,
- req: VisTypeTimeseriesRequest,
- indexPattern: string,
+ fetchedIndexPattern: FetchedIndexPattern,
+ indexPatternsService: IndexPatternsService,
capabilities?: unknown,
options?: Partial<{
type: string;
rollupIndex: string;
}>
) {
- const indexPatternsFetcher = new IndexPatternsFetcher(
- requestContext.core.elasticsearch.client.asCurrentUser
- );
- const indexPatternsService = await this.framework.getIndexPatternsService(requestContext);
- const { indexPatternObject } = await getIndexPatternObject(indexPattern, {
- indexPatternsService,
- });
-
return toSanitizedFieldType(
- indexPatternObject
- ? indexPatternObject.getNonScriptedFields()
- : await indexPatternsFetcher!.getFieldsForWildcard({
- pattern: indexPattern,
- fieldCapsOptions: { allow_no_indices: true },
+ fetchedIndexPattern.indexPattern
+ ? fetchedIndexPattern.indexPattern.getNonScriptedFields()
+ : await indexPatternsService.getFieldsForWildcard({
+ pattern: fetchedIndexPattern.indexPatternString ?? '',
metaFields: [],
...options,
})
diff --git a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/default_search_strategy.test.ts b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/default_search_strategy.test.ts
index b9824355374e1..d7a4e6ddedc89 100644
--- a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/default_search_strategy.test.ts
+++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/default_search_strategy.test.ts
@@ -6,7 +6,6 @@
* Side Public License, v 1.
*/
-import { Framework } from '../../../plugin';
import {
VisTypeTimeseriesRequestHandlerContext,
VisTypeTimeseriesVisDataRequest,
@@ -14,14 +13,13 @@ import {
import { DefaultSearchStrategy } from './default_search_strategy';
describe('DefaultSearchStrategy', () => {
- const framework = {} as Framework;
const requestContext = {} as VisTypeTimeseriesRequestHandlerContext;
let defaultSearchStrategy: DefaultSearchStrategy;
let req: VisTypeTimeseriesVisDataRequest;
beforeEach(() => {
req = {} as VisTypeTimeseriesVisDataRequest;
- defaultSearchStrategy = new DefaultSearchStrategy(framework);
+ defaultSearchStrategy = new DefaultSearchStrategy();
});
test('should init an DefaultSearchStrategy instance', () => {
diff --git a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/default_search_strategy.ts b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/default_search_strategy.ts
index c925d8fcbb7c3..f95bf81b5c1d3 100644
--- a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/default_search_strategy.ts
+++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/default_search_strategy.ts
@@ -8,25 +8,30 @@
import { AbstractSearchStrategy } from './abstract_search_strategy';
import { DefaultSearchCapabilities } from '../capabilities/default_search_capabilities';
-import { VisTypeTimeseriesRequestHandlerContext, VisTypeTimeseriesRequest } from '../../../types';
+
+import type { IndexPatternsService } from '../../../../../data/server';
+import type { FetchedIndexPattern } from '../../../../common/types';
+import type {
+ VisTypeTimeseriesRequestHandlerContext,
+ VisTypeTimeseriesRequest,
+} from '../../../types';
export class DefaultSearchStrategy extends AbstractSearchStrategy {
- checkForViability(
+ async checkForViability(
requestContext: VisTypeTimeseriesRequestHandlerContext,
req: VisTypeTimeseriesRequest
) {
- return Promise.resolve({
+ return {
isViable: true,
capabilities: new DefaultSearchCapabilities(req),
- });
+ };
}
async getFieldsForWildcard(
- requestContext: VisTypeTimeseriesRequestHandlerContext,
- req: VisTypeTimeseriesRequest,
- indexPattern: string,
+ fetchedIndexPattern: FetchedIndexPattern,
+ indexPatternsService: IndexPatternsService,
capabilities?: unknown
) {
- return super.getFieldsForWildcard(requestContext, req, indexPattern, capabilities);
+ return super.getFieldsForWildcard(fetchedIndexPattern, indexPatternsService, capabilities);
}
}
diff --git a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/rollup_search_strategy.test.ts b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/rollup_search_strategy.test.ts
index 403013cfb9e10..c798f58b0b67b 100644
--- a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/rollup_search_strategy.test.ts
+++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/rollup_search_strategy.test.ts
@@ -7,8 +7,10 @@
*/
import { RollupSearchStrategy } from './rollup_search_strategy';
-import { Framework } from '../../../plugin';
-import {
+
+import type { IndexPatternsService } from '../../../../../data/common';
+import type { CachedIndexPatternFetcher } from '../lib/cached_index_pattern_fetcher';
+import type {
VisTypeTimeseriesRequestHandlerContext,
VisTypeTimeseriesVisDataRequest,
} from '../../../types';
@@ -49,12 +51,11 @@ describe('Rollup Search Strategy', () => {
},
},
} as unknown) as VisTypeTimeseriesRequestHandlerContext;
- const framework = {} as Framework;
const indexPattern = 'indexPattern';
test('should create instance of RollupSearchRequest', () => {
- const rollupSearchStrategy = new RollupSearchStrategy(framework);
+ const rollupSearchStrategy = new RollupSearchStrategy();
expect(rollupSearchStrategy).toBeDefined();
});
@@ -64,7 +65,7 @@ describe('Rollup Search Strategy', () => {
const rollupIndex = 'rollupIndex';
beforeEach(() => {
- rollupSearchStrategy = new RollupSearchStrategy(framework);
+ rollupSearchStrategy = new RollupSearchStrategy();
rollupSearchStrategy.getRollupData = jest.fn(() =>
Promise.resolve({
[rollupIndex]: {
@@ -99,7 +100,7 @@ describe('Rollup Search Strategy', () => {
const result = await rollupSearchStrategy.checkForViability(
requestContext,
{} as VisTypeTimeseriesVisDataRequest,
- (null as unknown) as string
+ { indexPatternString: (null as unknown) as string, indexPattern: undefined }
);
expect(result).toEqual({
@@ -113,7 +114,7 @@ describe('Rollup Search Strategy', () => {
let rollupSearchStrategy: RollupSearchStrategy;
beforeEach(() => {
- rollupSearchStrategy = new RollupSearchStrategy(framework);
+ rollupSearchStrategy = new RollupSearchStrategy();
});
test('should return rollup data', async () => {
@@ -140,7 +141,7 @@ describe('Rollup Search Strategy', () => {
const rollupIndex = 'rollupIndex';
beforeEach(() => {
- rollupSearchStrategy = new RollupSearchStrategy(framework);
+ rollupSearchStrategy = new RollupSearchStrategy();
fieldsCapabilities = {
[rollupIndex]: {
aggs: {
@@ -154,9 +155,9 @@ describe('Rollup Search Strategy', () => {
test('should return fields for wildcard', async () => {
const fields = await rollupSearchStrategy.getFieldsForWildcard(
- requestContext,
- {} as VisTypeTimeseriesVisDataRequest,
- indexPattern,
+ { indexPatternString: 'indexPattern', indexPattern: undefined },
+ {} as IndexPatternsService,
+ (() => Promise.resolve({}) as unknown) as CachedIndexPatternFetcher,
{
fieldsCapabilities,
rollupIndex,
diff --git a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/rollup_search_strategy.ts b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/rollup_search_strategy.ts
index 376d551624c8a..e6333ca420e0d 100644
--- a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/rollup_search_strategy.ts
+++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/rollup_search_strategy.ts
@@ -6,19 +6,20 @@
* Side Public License, v 1.
*/
-import { getCapabilitiesForRollupIndices } from '../../../../../data/server';
-import {
+import { getCapabilitiesForRollupIndices, IndexPatternsService } from '../../../../../data/server';
+import { AbstractSearchStrategy } from './abstract_search_strategy';
+import { RollupSearchCapabilities } from '../capabilities/rollup_search_capabilities';
+
+import type { FetchedIndexPattern } from '../../../../common/types';
+import type { CachedIndexPatternFetcher } from '../lib/cached_index_pattern_fetcher';
+import type {
VisTypeTimeseriesRequest,
VisTypeTimeseriesRequestHandlerContext,
VisTypeTimeseriesVisDataRequest,
} from '../../../types';
-import { AbstractSearchStrategy } from './abstract_search_strategy';
-import { RollupSearchCapabilities } from '../capabilities/rollup_search_capabilities';
const getRollupIndices = (rollupData: { [key: string]: any }) => Object.keys(rollupData);
const isIndexPatternContainsWildcard = (indexPattern: string) => indexPattern.includes('*');
-const isIndexPatternValid = (indexPattern: string) =>
- indexPattern && typeof indexPattern === 'string' && !isIndexPatternContainsWildcard(indexPattern);
export class RollupSearchStrategy extends AbstractSearchStrategy {
async search(
@@ -33,24 +34,33 @@ export class RollupSearchStrategy extends AbstractSearchStrategy {
requestContext: VisTypeTimeseriesRequestHandlerContext,
indexPattern: string
) {
- return requestContext.core.elasticsearch.client.asCurrentUser.rollup
- .getRollupIndexCaps({
+ try {
+ const {
+ body,
+ } = await requestContext.core.elasticsearch.client.asCurrentUser.rollup.getRollupIndexCaps({
index: indexPattern,
- })
- .then((data) => data.body)
- .catch(() => Promise.resolve({}));
+ });
+
+ return body;
+ } catch (e) {
+ return {};
+ }
}
async checkForViability(
requestContext: VisTypeTimeseriesRequestHandlerContext,
req: VisTypeTimeseriesRequest,
- indexPattern: string
+ { indexPatternString, indexPattern }: FetchedIndexPattern
) {
let isViable = false;
let capabilities = null;
- if (isIndexPatternValid(indexPattern)) {
- const rollupData = await this.getRollupData(requestContext, indexPattern);
+ if (
+ indexPatternString &&
+ !isIndexPatternContainsWildcard(indexPatternString) &&
+ (!indexPattern || indexPattern.type === 'rollup')
+ ) {
+ const rollupData = await this.getRollupData(requestContext, indexPatternString);
const rollupIndices = getRollupIndices(rollupData);
isViable = rollupIndices.length === 1;
@@ -70,14 +80,14 @@ export class RollupSearchStrategy extends AbstractSearchStrategy {
}
async getFieldsForWildcard(
- requestContext: VisTypeTimeseriesRequestHandlerContext,
- req: VisTypeTimeseriesRequest,
- indexPattern: string,
+ fetchedIndexPattern: FetchedIndexPattern,
+ indexPatternsService: IndexPatternsService,
+ getCachedIndexPatternFetcher: CachedIndexPatternFetcher,
capabilities?: unknown
) {
- return super.getFieldsForWildcard(requestContext, req, indexPattern, capabilities, {
+ return super.getFieldsForWildcard(fetchedIndexPattern, indexPatternsService, capabilities, {
type: 'rollup',
- rollupIndex: indexPattern,
+ rollupIndex: fetchedIndexPattern.indexPatternString,
});
}
}
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/annotations/get_request_params.ts b/src/plugins/vis_type_timeseries/server/lib/vis_data/annotations/get_request_params.ts
index c489a8d20b071..32086fbf4f5b4 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/annotations/get_request_params.ts
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/annotations/get_request_params.ts
@@ -8,7 +8,6 @@
import { AnnotationItemsSchema, PanelSchema } from 'src/plugins/vis_type_timeseries/common/types';
import { buildAnnotationRequest } from './build_request_body';
-import { getIndexPatternObject } from '../../search_strategies/lib/get_index_pattern';
import {
VisTypeTimeseriesRequestHandlerContext,
VisTypeTimeseriesRequestServices,
@@ -30,21 +29,20 @@ export async function getAnnotationRequestParams(
esShardTimeout,
esQueryConfig,
capabilities,
- indexPatternsService,
uiSettings,
+ cachedIndexPatternFetcher,
}: AnnotationServices
) {
- const {
- indexPatternObject,
- indexPatternString,
- } = await getIndexPatternObject(annotation.index_pattern!, { indexPatternsService });
+ const { indexPattern, indexPatternString } = await cachedIndexPatternFetcher(
+ annotation.index_pattern
+ );
const request = await buildAnnotationRequest(
req,
panel,
annotation,
esQueryConfig,
- indexPatternObject,
+ indexPattern,
capabilities,
uiSettings
);
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.js
index 9b371a8901e81..ebab984ff25aa 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.js
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.js
@@ -10,8 +10,8 @@ import { AUTO_INTERVAL } from '../../../common/constants';
const DEFAULT_TIME_FIELD = '@timestamp';
-export function getIntervalAndTimefield(panel, series = {}, indexPatternObject) {
- const getDefaultTimeField = () => indexPatternObject?.timeFieldName ?? DEFAULT_TIME_FIELD;
+export function getIntervalAndTimefield(panel, series = {}, indexPattern) {
+ const getDefaultTimeField = () => indexPattern?.timeFieldName ?? DEFAULT_TIME_FIELD;
const timeField =
(series.override_index_pattern && series.series_time_field) ||
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/get_series_data.ts b/src/plugins/vis_type_timeseries/server/lib/vis_data/get_series_data.ts
index f521de632b1f8..13dc1207f51de 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/get_series_data.ts
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/get_series_data.ts
@@ -21,6 +21,7 @@ import type {
VisTypeTimeseriesRequestServices,
} from '../../types';
import type { PanelSchema } from '../../../common/types';
+import { PANEL_TYPES } from '../../../common/panel_types';
export async function getSeriesData(
requestContext: VisTypeTimeseriesRequestHandlerContext,
@@ -28,10 +29,12 @@ export async function getSeriesData(
panel: PanelSchema,
services: VisTypeTimeseriesRequestServices
) {
- const strategy = await services.searchStrategyRegistry.getViableStrategyForPanel(
+ const panelIndex = await services.cachedIndexPatternFetcher(panel.index_pattern);
+
+ const strategy = await services.searchStrategyRegistry.getViableStrategy(
requestContext,
req,
- panel
+ panelIndex
);
if (!strategy) {
@@ -50,14 +53,15 @@ export async function getSeriesData(
try {
const bodiesPromises = getActiveSeries(panel).map((series) =>
- getSeriesRequestParams(req, panel, series, capabilities, services)
+ getSeriesRequestParams(req, panel, panelIndex, series, capabilities, services)
);
const searches = await Promise.all(bodiesPromises);
const data = await searchStrategy.search(requestContext, req, searches);
const handleResponseBodyFn = handleResponseBody(panel, req, {
- requestContext,
+ indexPatternsService: services.indexPatternsService,
+ cachedIndexPatternFetcher: services.cachedIndexPatternFetcher,
searchStrategy,
capabilities,
});
@@ -70,7 +74,7 @@ export async function getSeriesData(
let annotations = null;
- if (panel.annotations && panel.annotations.length) {
+ if (panel.type === PANEL_TYPES.TIMESERIES && panel.annotations && panel.annotations.length) {
annotations = await getAnnotations({
req,
panel,
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/get_table_data.ts b/src/plugins/vis_type_timeseries/server/lib/vis_data/get_table_data.ts
index a35a3246b0dd3..0cc1188086b7b 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/get_table_data.ts
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/get_table_data.ts
@@ -16,8 +16,8 @@ import { buildRequestBody } from './table/build_request_body';
import { handleErrorResponse } from './handle_error_response';
// @ts-expect-error
import { processBucket } from './table/process_bucket';
-import { getIndexPatternObject } from '../search_strategies/lib/get_index_pattern';
-import { createFieldsFetcher } from './helpers/fields_fetcher';
+
+import { createFieldsFetcher } from '../search_strategies/lib/fields_fetcher';
import { extractFieldLabel } from '../../../common/calculate_label';
import type {
VisTypeTimeseriesRequestHandlerContext,
@@ -32,12 +32,12 @@ export async function getTableData(
panel: PanelSchema,
services: VisTypeTimeseriesRequestServices
) {
- const panelIndexPattern = panel.index_pattern;
+ const panelIndex = await services.cachedIndexPatternFetcher(panel.index_pattern);
const strategy = await services.searchStrategyRegistry.getViableStrategy(
requestContext,
req,
- panelIndexPattern
+ panelIndex
);
if (!strategy) {
@@ -49,15 +49,17 @@ export async function getTableData(
}
const { searchStrategy, capabilities } = strategy;
- const { indexPatternObject } = await getIndexPatternObject(panelIndexPattern, {
+
+ const extractFields = createFieldsFetcher(req, {
indexPatternsService: services.indexPatternsService,
+ cachedIndexPatternFetcher: services.cachedIndexPatternFetcher,
+ searchStrategy,
+ capabilities,
});
- const extractFields = createFieldsFetcher(req, { requestContext, searchStrategy, capabilities });
-
const calculatePivotLabel = async () => {
- if (panel.pivot_id && indexPatternObject?.title) {
- const fields = await extractFields(indexPatternObject.title);
+ if (panel.pivot_id && panelIndex.indexPattern?.title) {
+ const fields = await extractFields(panelIndex.indexPattern.title);
return extractFieldLabel(fields, panel.pivot_id);
}
@@ -75,7 +77,7 @@ export async function getTableData(
req,
panel,
services.esQueryConfig,
- indexPatternObject,
+ panelIndex.indexPattern,
capabilities,
services.uiSettings
);
@@ -83,7 +85,7 @@ export async function getTableData(
const [resp] = await searchStrategy.search(requestContext, req, [
{
body,
- index: panelIndexPattern,
+ index: panelIndex.indexPatternString,
},
]);
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/annotations/date_histogram.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/annotations/date_histogram.js
index 0d100f6310b99..48b33c1e787e9 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/annotations/date_histogram.js
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/annotations/date_histogram.js
@@ -18,7 +18,7 @@ export function dateHistogram(
panel,
annotation,
esQueryConfig,
- indexPatternObject,
+ indexPattern,
capabilities,
uiSettings
) {
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/date_histogram.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/date_histogram.js
index 9ff0325b60e82..dab9a24d06c0f 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/date_histogram.js
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/date_histogram.js
@@ -19,7 +19,7 @@ export function dateHistogram(
panel,
series,
esQueryConfig,
- indexPatternObject,
+ indexPattern,
capabilities,
uiSettings
) {
@@ -27,11 +27,7 @@ export function dateHistogram(
const maxBarsUiSettings = await uiSettings.get(UI_SETTINGS.HISTOGRAM_MAX_BARS);
const barTargetUiSettings = await uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET);
- const { timeField, interval, maxBars } = getIntervalAndTimefield(
- panel,
- series,
- indexPatternObject
- );
+ const { timeField, interval, maxBars } = getIntervalAndTimefield(panel, series, indexPattern);
const { bucketSize, intervalString } = getBucketSize(
req,
interval,
@@ -68,7 +64,7 @@ export function dateHistogram(
overwrite(doc, `aggs.${series.id}.meta`, {
timeField,
intervalString,
- index: indexPatternObject?.title,
+ index: indexPattern?.title,
bucketSize,
seriesId: series.id,
});
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/date_histogram.test.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/date_histogram.test.js
index d653f6acf6f3e..945c57b2341f3 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/date_histogram.test.js
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/date_histogram.test.js
@@ -16,7 +16,7 @@ describe('dateHistogram(req, panel, series)', () => {
let req;
let capabilities;
let config;
- let indexPatternObject;
+ let indexPattern;
let uiSettings;
beforeEach(() => {
@@ -39,7 +39,7 @@ describe('dateHistogram(req, panel, series)', () => {
allowLeadingWildcards: true,
queryStringOptions: {},
};
- indexPatternObject = {};
+ indexPattern = {};
capabilities = new DefaultSearchCapabilities(req);
uiSettings = {
get: async (key) => (key === UI_SETTINGS.HISTOGRAM_MAX_BARS ? 100 : 50),
@@ -49,15 +49,9 @@ describe('dateHistogram(req, panel, series)', () => {
test('calls next when finished', async () => {
const next = jest.fn();
- await dateHistogram(
- req,
- panel,
- series,
- config,
- indexPatternObject,
- capabilities,
- uiSettings
- )(next)({});
+ await dateHistogram(req, panel, series, config, indexPattern, capabilities, uiSettings)(next)(
+ {}
+ );
expect(next.mock.calls.length).toEqual(1);
});
@@ -69,7 +63,7 @@ describe('dateHistogram(req, panel, series)', () => {
panel,
series,
config,
- indexPatternObject,
+ indexPattern,
capabilities,
uiSettings
)(next)({});
@@ -110,7 +104,7 @@ describe('dateHistogram(req, panel, series)', () => {
panel,
series,
config,
- indexPatternObject,
+ indexPattern,
capabilities,
uiSettings
)(next)({});
@@ -154,7 +148,7 @@ describe('dateHistogram(req, panel, series)', () => {
panel,
series,
config,
- indexPatternObject,
+ indexPattern,
capabilities,
uiSettings
)(next)({});
@@ -198,7 +192,7 @@ describe('dateHistogram(req, panel, series)', () => {
panel,
series,
config,
- indexPatternObject,
+ indexPattern,
capabilities,
uiSettings
)(next)({});
@@ -216,7 +210,7 @@ describe('dateHistogram(req, panel, series)', () => {
panel,
series,
config,
- indexPatternObject,
+ indexPattern,
capabilities,
uiSettings
)(next)({});
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/filter_ratios.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/filter_ratios.js
index 31ae988718a27..4639af9db83b8 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/filter_ratios.js
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/filter_ratios.js
@@ -12,19 +12,19 @@ import { esQuery } from '../../../../../../data/server';
const filter = (metric) => metric.type === 'filter_ratio';
-export function ratios(req, panel, series, esQueryConfig, indexPatternObject) {
+export function ratios(req, panel, series, esQueryConfig, indexPattern) {
return (next) => (doc) => {
if (series.metrics.some(filter)) {
series.metrics.filter(filter).forEach((metric) => {
overwrite(
doc,
`aggs.${series.id}.aggs.timeseries.aggs.${metric.id}-numerator.filter`,
- esQuery.buildEsQuery(indexPatternObject, metric.numerator, [], esQueryConfig)
+ esQuery.buildEsQuery(indexPattern, metric.numerator, [], esQueryConfig)
);
overwrite(
doc,
`aggs.${series.id}.aggs.timeseries.aggs.${metric.id}-denominator.filter`,
- esQuery.buildEsQuery(indexPatternObject, metric.denominator, [], esQueryConfig)
+ esQuery.buildEsQuery(indexPattern, metric.denominator, [], esQueryConfig)
);
let numeratorPath = `${metric.id}-numerator>_count`;
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/filter_ratios.test.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/filter_ratios.test.js
index 9e0dd4f76c13f..345488ec01d5e 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/filter_ratios.test.js
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/filter_ratios.test.js
@@ -13,7 +13,7 @@ describe('ratios(req, panel, series, esQueryConfig, indexPatternObject)', () =>
let series;
let req;
let esQueryConfig;
- let indexPatternObject;
+ let indexPattern;
beforeEach(() => {
panel = {
time_field: 'timestamp',
@@ -47,18 +47,18 @@ describe('ratios(req, panel, series, esQueryConfig, indexPatternObject)', () =>
queryStringOptions: { analyze_wildcard: true },
ignoreFilterIfFieldNotInIndex: false,
};
- indexPatternObject = {};
+ indexPattern = {};
});
test('calls next when finished', () => {
const next = jest.fn();
- ratios(req, panel, series, esQueryConfig, indexPatternObject)(next)({});
+ ratios(req, panel, series, esQueryConfig, indexPattern)(next)({});
expect(next.mock.calls.length).toEqual(1);
});
test('returns filter ratio aggs', () => {
const next = (doc) => doc;
- const doc = ratios(req, panel, series, esQueryConfig, indexPatternObject)(next)({});
+ const doc = ratios(req, panel, series, esQueryConfig, indexPattern)(next)({});
expect(doc).toEqual({
aggs: {
test: {
@@ -135,7 +135,7 @@ describe('ratios(req, panel, series, esQueryConfig, indexPatternObject)', () =>
test('returns empty object when field is not set', () => {
delete series.metrics[0].field;
const next = (doc) => doc;
- const doc = ratios(req, panel, series, esQueryConfig, indexPatternObject)(next)({});
+ const doc = ratios(req, panel, series, esQueryConfig, indexPattern)(next)({});
expect(doc).toEqual({
aggs: {
test: {
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/metric_buckets.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/metric_buckets.js
index 649b3cee6ea3e..86b691f6496c9 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/metric_buckets.js
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/metric_buckets.js
@@ -17,14 +17,14 @@ export function metricBuckets(
panel,
series,
esQueryConfig,
- indexPatternObject,
+ indexPattern,
capabilities,
uiSettings
) {
return (next) => async (doc) => {
const barTargetUiSettings = await uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET);
- const { interval } = getIntervalAndTimefield(panel, series, indexPatternObject);
+ const { interval } = getIntervalAndTimefield(panel, series, indexPattern);
const { intervalString } = getBucketSize(req, interval, capabilities, barTargetUiSettings);
series.metrics
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/positive_rate.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/positive_rate.js
index 1d67df7c92eb6..ce61374c0b124 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/positive_rate.js
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/positive_rate.js
@@ -56,14 +56,14 @@ export function positiveRate(
panel,
series,
esQueryConfig,
- indexPatternObject,
+ indexPattern,
capabilities,
uiSettings
) {
return (next) => async (doc) => {
const barTargetUiSettings = await uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET);
- const { interval } = getIntervalAndTimefield(panel, series, indexPatternObject);
+ const { interval } = getIntervalAndTimefield(panel, series, indexPattern);
const { intervalString } = getBucketSize(req, interval, capabilities, barTargetUiSettings);
if (series.metrics.some(filter)) {
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/query.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/query.js
index cb12aa3513b91..d0e92c9157cb5 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/query.js
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/query.js
@@ -10,16 +10,16 @@ import { offsetTime } from '../../offset_time';
import { getIntervalAndTimefield } from '../../get_interval_and_timefield';
import { esQuery } from '../../../../../../data/server';
-export function query(req, panel, series, esQueryConfig, indexPatternObject) {
+export function query(req, panel, series, esQueryConfig, indexPattern) {
return (next) => (doc) => {
- const { timeField } = getIntervalAndTimefield(panel, series, indexPatternObject);
+ const { timeField } = getIntervalAndTimefield(panel, series, indexPattern);
const { from, to } = offsetTime(req, series.offset_time);
doc.size = 0;
const ignoreGlobalFilter = panel.ignore_global_filter || series.ignore_global_filter;
const queries = !ignoreGlobalFilter ? req.body.query : [];
const filters = !ignoreGlobalFilter ? req.body.filters : [];
- doc.query = esQuery.buildEsQuery(indexPatternObject, queries, filters, esQueryConfig);
+ doc.query = esQuery.buildEsQuery(indexPattern, queries, filters, esQueryConfig);
const timerange = {
range: {
@@ -34,13 +34,13 @@ export function query(req, panel, series, esQueryConfig, indexPatternObject) {
if (panel.filter) {
doc.query.bool.must.push(
- esQuery.buildEsQuery(indexPatternObject, [panel.filter], [], esQueryConfig)
+ esQuery.buildEsQuery(indexPattern, [panel.filter], [], esQueryConfig)
);
}
if (series.filter) {
doc.query.bool.must.push(
- esQuery.buildEsQuery(indexPatternObject, [series.filter], [], esQueryConfig)
+ esQuery.buildEsQuery(indexPattern, [series.filter], [], esQueryConfig)
);
}
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/sibling_buckets.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/sibling_buckets.js
index 315ccdfc13a47..401344d48f865 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/sibling_buckets.js
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/sibling_buckets.js
@@ -17,13 +17,13 @@ export function siblingBuckets(
panel,
series,
esQueryConfig,
- indexPatternObject,
+ indexPattern,
capabilities,
uiSettings
) {
return (next) => async (doc) => {
const barTargetUiSettings = await uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET);
- const { interval } = getIntervalAndTimefield(panel, series, indexPatternObject);
+ const { interval } = getIntervalAndTimefield(panel, series, indexPattern);
const { bucketSize } = getBucketSize(req, interval, capabilities, barTargetUiSettings);
series.metrics
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/date_histogram.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/date_histogram.js
index 0ae6d113e28e4..5518065643172 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/date_histogram.js
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/date_histogram.js
@@ -15,20 +15,13 @@ import { calculateAggRoot } from './calculate_agg_root';
import { search, UI_SETTINGS } from '../../../../../../../plugins/data/server';
const { dateHistogramInterval } = search.aggs;
-export function dateHistogram(
- req,
- panel,
- esQueryConfig,
- indexPatternObject,
- capabilities,
- uiSettings
-) {
+export function dateHistogram(req, panel, esQueryConfig, indexPattern, capabilities, uiSettings) {
return (next) => async (doc) => {
const barTargetUiSettings = await uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET);
- const { timeField, interval } = getIntervalAndTimefield(panel, {}, indexPatternObject);
+ const { timeField, interval } = getIntervalAndTimefield(panel, {}, indexPattern);
const meta = {
timeField,
- index: indexPatternObject?.title,
+ index: indexPattern?.title,
};
const getDateHistogramForLastBucketMode = () => {
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/filter_ratios.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/filter_ratios.js
index 7b3ac16cd6561..abb5971908771 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/filter_ratios.js
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/filter_ratios.js
@@ -13,7 +13,7 @@ import { calculateAggRoot } from './calculate_agg_root';
const filter = (metric) => metric.type === 'filter_ratio';
-export function ratios(req, panel, esQueryConfig, indexPatternObject) {
+export function ratios(req, panel, esQueryConfig, indexPattern) {
return (next) => (doc) => {
panel.series.forEach((column) => {
const aggRoot = calculateAggRoot(doc, column);
@@ -22,12 +22,12 @@ export function ratios(req, panel, esQueryConfig, indexPatternObject) {
overwrite(
doc,
`${aggRoot}.timeseries.aggs.${metric.id}-numerator.filter`,
- esQuery.buildEsQuery(indexPatternObject, metric.numerator, [], esQueryConfig)
+ esQuery.buildEsQuery(indexPattern, metric.numerator, [], esQueryConfig)
);
overwrite(
doc,
`${aggRoot}.timeseries.aggs.${metric.id}-denominator.filter`,
- esQuery.buildEsQuery(indexPatternObject, metric.denominator, [], esQueryConfig)
+ esQuery.buildEsQuery(indexPattern, metric.denominator, [], esQueryConfig)
);
let numeratorPath = `${metric.id}-numerator>_count`;
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/metric_buckets.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/metric_buckets.js
index 53149a31603ef..5ce508bd9b279 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/metric_buckets.js
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/metric_buckets.js
@@ -13,17 +13,10 @@ import { getIntervalAndTimefield } from '../../get_interval_and_timefield';
import { calculateAggRoot } from './calculate_agg_root';
import { UI_SETTINGS } from '../../../../../../data/common';
-export function metricBuckets(
- req,
- panel,
- esQueryConfig,
- indexPatternObject,
- capabilities,
- uiSettings
-) {
+export function metricBuckets(req, panel, esQueryConfig, indexPattern, capabilities, uiSettings) {
return (next) => async (doc) => {
const barTargetUiSettings = await uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET);
- const { interval } = getIntervalAndTimefield(panel, {}, indexPatternObject);
+ const { interval } = getIntervalAndTimefield(panel, {}, indexPattern);
const { intervalString } = getBucketSize(req, interval, capabilities, barTargetUiSettings);
panel.series.forEach((column) => {
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/positive_rate.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/positive_rate.js
index 8c7a0f5e2367f..176721e7b563a 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/positive_rate.js
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/positive_rate.js
@@ -12,17 +12,10 @@ import { calculateAggRoot } from './calculate_agg_root';
import { createPositiveRate, filter } from '../series/positive_rate';
import { UI_SETTINGS } from '../../../../../../data/common';
-export function positiveRate(
- req,
- panel,
- esQueryConfig,
- indexPatternObject,
- capabilities,
- uiSettings
-) {
+export function positiveRate(req, panel, esQueryConfig, indexPattern, capabilities, uiSettings) {
return (next) => async (doc) => {
const barTargetUiSettings = await uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET);
- const { interval } = getIntervalAndTimefield(panel, {}, indexPatternObject);
+ const { interval } = getIntervalAndTimefield(panel, {}, indexPattern);
const { intervalString } = getBucketSize(req, interval, capabilities, barTargetUiSettings);
panel.series.forEach((column) => {
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/query.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/query.js
index a0118c5037d34..76df07b76e80e 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/query.js
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/query.js
@@ -10,16 +10,16 @@ import { getTimerange } from '../../helpers/get_timerange';
import { getIntervalAndTimefield } from '../../get_interval_and_timefield';
import { esQuery } from '../../../../../../data/server';
-export function query(req, panel, esQueryConfig, indexPatternObject) {
+export function query(req, panel, esQueryConfig, indexPattern) {
return (next) => (doc) => {
- const { timeField } = getIntervalAndTimefield(panel, {}, indexPatternObject);
+ const { timeField } = getIntervalAndTimefield(panel, {}, indexPattern);
const { from, to } = getTimerange(req);
doc.size = 0;
const queries = !panel.ignore_global_filter ? req.body.query : [];
const filters = !panel.ignore_global_filter ? req.body.filters : [];
- doc.query = esQuery.buildEsQuery(indexPatternObject, queries, filters, esQueryConfig);
+ doc.query = esQuery.buildEsQuery(indexPattern, queries, filters, esQueryConfig);
const timerange = {
range: {
@@ -33,7 +33,7 @@ export function query(req, panel, esQueryConfig, indexPatternObject) {
doc.query.bool.must.push(timerange);
if (panel.filter) {
doc.query.bool.must.push(
- esQuery.buildEsQuery(indexPatternObject, [panel.filter], [], esQueryConfig)
+ esQuery.buildEsQuery(indexPattern, [panel.filter], [], esQueryConfig)
);
}
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/sibling_buckets.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/sibling_buckets.js
index d205f0679a908..5539f16df41e0 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/sibling_buckets.js
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/sibling_buckets.js
@@ -13,17 +13,10 @@ import { getIntervalAndTimefield } from '../../get_interval_and_timefield';
import { calculateAggRoot } from './calculate_agg_root';
import { UI_SETTINGS } from '../../../../../../data/common';
-export function siblingBuckets(
- req,
- panel,
- esQueryConfig,
- indexPatternObject,
- capabilities,
- uiSettings
-) {
+export function siblingBuckets(req, panel, esQueryConfig, indexPattern, capabilities, uiSettings) {
return (next) => async (doc) => {
const barTargetUiSettings = await uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET);
- const { interval } = getIntervalAndTimefield(panel, {}, indexPatternObject);
+ const { interval } = getIntervalAndTimefield(panel, {}, indexPattern);
const { bucketSize } = getBucketSize(req, interval, capabilities, barTargetUiSettings);
panel.series.forEach((column) => {
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/series/build_request_body.test.ts b/src/plugins/vis_type_timeseries/server/lib/vis_data/series/build_request_body.test.ts
index 968fe01565b04..d97af8ac748f4 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/series/build_request_body.test.ts
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/series/build_request_body.test.ts
@@ -79,13 +79,13 @@ describe('buildRequestBody(req)', () => {
allowLeadingWildcards: true,
queryStringOptions: {},
};
- const indexPatternObject = {};
+ const indexPattern = {};
const doc = await buildRequestBody(
{ body },
panel,
series,
config,
- indexPatternObject,
+ indexPattern,
capabilities,
{
get: async () => 50,
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/series/get_request_params.ts b/src/plugins/vis_type_timeseries/server/lib/vis_data/series/get_request_params.ts
index ae846b5b4b817..1f2735da8fb06 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/series/get_request_params.ts
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/series/get_request_params.ts
@@ -6,43 +6,46 @@
* Side Public License, v 1.
*/
-import { PanelSchema, SeriesItemsSchema } from '../../../../common/types';
import { buildRequestBody } from './build_request_body';
-import { getIndexPatternObject } from '../../../lib/search_strategies/lib/get_index_pattern';
-import { VisTypeTimeseriesRequestServices, VisTypeTimeseriesVisDataRequest } from '../../../types';
-import { DefaultSearchCapabilities } from '../../search_strategies';
+
+import type { FetchedIndexPattern, PanelSchema, SeriesItemsSchema } from '../../../../common/types';
+import type {
+ VisTypeTimeseriesRequestServices,
+ VisTypeTimeseriesVisDataRequest,
+} from '../../../types';
+import type { DefaultSearchCapabilities } from '../../search_strategies';
export async function getSeriesRequestParams(
req: VisTypeTimeseriesVisDataRequest,
panel: PanelSchema,
+ panelIndex: FetchedIndexPattern,
series: SeriesItemsSchema,
capabilities: DefaultSearchCapabilities,
{
esQueryConfig,
esShardTimeout,
uiSettings,
- indexPatternsService,
+ cachedIndexPatternFetcher,
}: VisTypeTimeseriesRequestServices
) {
- const indexPattern =
- (series.override_index_pattern && series.series_index_pattern) || panel.index_pattern;
+ let seriesIndex = panelIndex;
- const { indexPatternObject, indexPatternString } = await getIndexPatternObject(indexPattern, {
- indexPatternsService,
- });
+ if (series.override_index_pattern) {
+ seriesIndex = await cachedIndexPatternFetcher(series.series_index_pattern ?? '');
+ }
const request = await buildRequestBody(
req,
panel,
series,
esQueryConfig,
- indexPatternObject,
+ seriesIndex.indexPattern,
capabilities,
uiSettings
);
return {
- index: indexPatternString,
+ index: seriesIndex.indexPatternString,
body: {
...request,
timeout: esShardTimeout > 0 ? `${esShardTimeout}ms` : undefined,
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/series/handle_response_body.ts b/src/plugins/vis_type_timeseries/server/lib/vis_data/series/handle_response_body.ts
index 22e0372c23526..49f1ec0f93de5 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/series/handle_response_body.ts
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/series/handle_response_body.ts
@@ -12,7 +12,10 @@ import { PanelSchema } from '../../../../common/types';
import { buildProcessorFunction } from '../build_processor_function';
// @ts-expect-error
import { processors } from '../response_processors/series';
-import { createFieldsFetcher, FieldsFetcherServices } from './../helpers/fields_fetcher';
+import {
+ createFieldsFetcher,
+ FieldsFetcherServices,
+} from '../../search_strategies/lib/fields_fetcher';
import { VisTypeTimeseriesVisDataRequest } from '../../../types';
export function handleResponseBody(
diff --git a/src/plugins/vis_type_timeseries/server/plugin.ts b/src/plugins/vis_type_timeseries/server/plugin.ts
index 71b76dddbca6a..95fdc59ceb232 100644
--- a/src/plugins/vis_type_timeseries/server/plugin.ts
+++ b/src/plugins/vis_type_timeseries/server/plugin.ts
@@ -111,8 +111,8 @@ export class VisTypeTimeseriesPlugin implements Plugin {
},
};
- searchStrategyRegistry.addStrategy(new DefaultSearchStrategy(framework));
- searchStrategyRegistry.addStrategy(new RollupSearchStrategy(framework));
+ searchStrategyRegistry.addStrategy(new DefaultSearchStrategy());
+ searchStrategyRegistry.addStrategy(new RollupSearchStrategy());
visDataRoutes(router, framework);
fieldsRoutes(router, framework);
diff --git a/src/plugins/vis_type_timeseries/server/types.ts b/src/plugins/vis_type_timeseries/server/types.ts
index da32669b3855d..3ab5d8ac6ea7a 100644
--- a/src/plugins/vis_type_timeseries/server/types.ts
+++ b/src/plugins/vis_type_timeseries/server/types.ts
@@ -12,8 +12,9 @@ import type {
EsQueryConfig,
IndexPatternsService,
} from '../../data/server';
-import { VisPayload } from '../common/types';
-import { SearchStrategyRegistry } from './lib/search_strategies';
+import type { VisPayload } from '../common/types';
+import type { SearchStrategyRegistry } from './lib/search_strategies';
+import type { CachedIndexPatternFetcher } from './lib/search_strategies/lib/cached_index_pattern_fetcher';
export type VisTypeTimeseriesRequestHandlerContext = DataRequestHandlerContext;
export type VisTypeTimeseriesRouter = IRouter;
@@ -29,4 +30,5 @@ export interface VisTypeTimeseriesRequestServices {
uiSettings: IUiSettingsClient;
indexPatternsService: IndexPatternsService;
searchStrategyRegistry: SearchStrategyRegistry;
+ cachedIndexPatternFetcher: CachedIndexPatternFetcher;
}
diff --git a/src/plugins/vis_type_vega/public/components/vega_help_menu.tsx b/src/plugins/vis_type_vega/public/components/vega_help_menu.tsx
index efb41c470024b..f5b0f614458fd 100644
--- a/src/plugins/vis_type_vega/public/components/vega_help_menu.tsx
+++ b/src/plugins/vis_type_vega/public/components/vega_help_menu.tsx
@@ -11,6 +11,8 @@ import { EuiButtonIcon, EuiContextMenuPanel, EuiContextMenuItem, EuiPopover } fr
import { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
+import { getDocLinks } from '../services';
+
function VegaHelpMenu() {
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
const onButtonClick = useCallback(() => setIsPopoverOpen(!isPopoverOpen), [isPopoverOpen]);
@@ -30,7 +32,7 @@ function VegaHelpMenu() {
const items = [
diff --git a/src/plugins/vis_type_vega/public/plugin.ts b/src/plugins/vis_type_vega/public/plugin.ts
index 0204c2c90b71b..f935362d21604 100644
--- a/src/plugins/vis_type_vega/public/plugin.ts
+++ b/src/plugins/vis_type_vega/public/plugin.ts
@@ -19,6 +19,7 @@ import {
setUISettings,
setInjectedMetadata,
setMapServiceSettings,
+ setDocLinks,
} from './services';
import { createVegaFn } from './vega_fn';
@@ -96,5 +97,6 @@ export class VegaPlugin implements Plugin {
setNotifications(core.notifications);
setData(data);
setInjectedMetadata(core.injectedMetadata);
+ setDocLinks(core.docLinks);
}
}
diff --git a/src/plugins/vis_type_vega/public/services.ts b/src/plugins/vis_type_vega/public/services.ts
index c47378282932b..f67fe4794e783 100644
--- a/src/plugins/vis_type_vega/public/services.ts
+++ b/src/plugins/vis_type_vega/public/services.ts
@@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
-import { CoreStart, NotificationsStart, IUiSettingsClient } from 'src/core/public';
+import { CoreStart, NotificationsStart, IUiSettingsClient, DocLinksStart } from 'src/core/public';
import { DataPublicPluginStart } from '../../data/public';
import { createGetterSetter } from '../../kibana_utils/public';
@@ -35,3 +35,5 @@ export const [getInjectedVars, setInjectedVars] = createGetterSetter<{
}>('InjectedVars');
export const getEnableExternalUrls = () => getInjectedVars().enableExternalUrls;
+
+export const [getDocLinks, setDocLinks] = createGetterSetter('docLinks');
diff --git a/src/plugins/visualizations/public/embeddable/visualize_embeddable_factory.tsx b/src/plugins/visualizations/public/embeddable/visualize_embeddable_factory.tsx
index 349e024f31c31..c2b9fcd77757a 100644
--- a/src/plugins/visualizations/public/embeddable/visualize_embeddable_factory.tsx
+++ b/src/plugins/visualizations/public/embeddable/visualize_embeddable_factory.tsx
@@ -12,6 +12,8 @@ import { first } from 'rxjs/operators';
import { EmbeddableStateWithType } from 'src/plugins/embeddable/common';
import { SavedObjectAttributes } from '../../../../core/public';
import { extractSearchSourceReferences } from '../../../data/public';
+import { SavedObjectReference } from '../../../../core/public';
+
import {
EmbeddableFactoryDefinition,
EmbeddableOutput,
@@ -38,6 +40,12 @@ import {
} from '../services';
import { showNewVisModal } from '../wizard';
import { convertToSerializedVis } from '../saved_visualizations/_saved_vis';
+import {
+ extractControlsReferences,
+ extractTimeSeriesReferences,
+ injectTimeSeriesReferences,
+ injectControlsReferences,
+} from '../saved_visualizations/saved_visualization_references';
import { createVisEmbeddableFromObject } from './create_vis_embeddable_from_object';
import { StartServicesGetter } from '../../../kibana_utils/public';
import { VisualizationsStartDeps } from '../plugin';
@@ -239,6 +247,19 @@ export class VisualizeEmbeddableFactory
);
}
+ public inject(_state: EmbeddableStateWithType, references: SavedObjectReference[]) {
+ const state = (_state as unknown) as VisualizeInput;
+
+ const { type, params } = state.savedVis ?? {};
+
+ if (type && params) {
+ injectControlsReferences(type, params, references);
+ injectTimeSeriesReferences(type, params, references);
+ }
+
+ return _state;
+ }
+
public extract(_state: EmbeddableStateWithType) {
const state = (_state as unknown) as VisualizeInput;
const references = [];
@@ -259,19 +280,11 @@ export class VisualizeEmbeddableFactory
});
}
- if (state.savedVis?.params.controls) {
- const controls = state.savedVis.params.controls;
- controls.forEach((control: Record, i: number) => {
- if (!control.indexPattern) {
- return;
- }
- control.indexPatternRefName = `control_${i}_index_pattern`;
- references.push({
- name: control.indexPatternRefName,
- type: 'index-pattern',
- id: control.indexPattern,
- });
- });
+ const { type, params } = state.savedVis ?? {};
+
+ if (type && params) {
+ extractControlsReferences(type, params, references, `control_${state.id}`);
+ extractTimeSeriesReferences(type, params, references, `metrics_${state.id}`);
}
return { state: _state, references };
diff --git a/src/plugins/visualizations/public/saved_visualizations/saved_visualization_references/controls_references.ts b/src/plugins/visualizations/public/saved_visualizations/saved_visualization_references/controls_references.ts
new file mode 100644
index 0000000000000..d116fd2e2e9a7
--- /dev/null
+++ b/src/plugins/visualizations/public/saved_visualizations/saved_visualization_references/controls_references.ts
@@ -0,0 +1,54 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { SavedObjectReference } from '../../../../../core/types';
+import { VisParams } from '../../../common';
+
+const isControlsVis = (visType: string) => visType === 'input_control_vis';
+
+export const extractControlsReferences = (
+ visType: string,
+ visParams: VisParams,
+ references: SavedObjectReference[] = [],
+ prefix: string = 'control'
+) => {
+ if (isControlsVis(visType)) {
+ (visParams?.controls ?? []).forEach((control: Record, i: number) => {
+ if (!control.indexPattern) {
+ return;
+ }
+ control.indexPatternRefName = `${prefix}_${i}_index_pattern`;
+ references.push({
+ name: control.indexPatternRefName,
+ type: 'index-pattern',
+ id: control.indexPattern,
+ });
+ delete control.indexPattern;
+ });
+ }
+};
+
+export const injectControlsReferences = (
+ visType: string,
+ visParams: VisParams,
+ references: SavedObjectReference[]
+) => {
+ if (isControlsVis(visType)) {
+ (visParams.controls ?? []).forEach((control: Record) => {
+ if (!control.indexPatternRefName) {
+ return;
+ }
+ const reference = references.find((ref) => ref.name === control.indexPatternRefName);
+ if (!reference) {
+ throw new Error(`Could not find index pattern reference "${control.indexPatternRefName}"`);
+ }
+ control.indexPattern = reference.id;
+ delete control.indexPatternRefName;
+ });
+ }
+};
diff --git a/src/plugins/visualizations/public/saved_visualizations/saved_visualization_references/index.ts b/src/plugins/visualizations/public/saved_visualizations/saved_visualization_references/index.ts
new file mode 100644
index 0000000000000..0acda1c0a0f80
--- /dev/null
+++ b/src/plugins/visualizations/public/saved_visualizations/saved_visualization_references/index.ts
@@ -0,0 +1,12 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+export { extractControlsReferences, injectControlsReferences } from './controls_references';
+export { extractTimeSeriesReferences, injectTimeSeriesReferences } from './timeseries_references';
+
+export { extractReferences, injectReferences } from './saved_visualization_references';
diff --git a/src/plugins/visualizations/public/saved_visualizations/saved_visualization_references.test.ts b/src/plugins/visualizations/public/saved_visualizations/saved_visualization_references/saved_visualization_references.test.ts
similarity index 69%
rename from src/plugins/visualizations/public/saved_visualizations/saved_visualization_references.test.ts
rename to src/plugins/visualizations/public/saved_visualizations/saved_visualization_references/saved_visualization_references.test.ts
index f81054febcc44..867febd2544b0 100644
--- a/src/plugins/visualizations/public/saved_visualizations/saved_visualization_references.test.ts
+++ b/src/plugins/visualizations/public/saved_visualizations/saved_visualization_references/saved_visualization_references.test.ts
@@ -7,8 +7,8 @@
*/
import { extractReferences, injectReferences } from './saved_visualization_references';
-import { VisSavedObject } from '../types';
-import { SavedVisState } from '../../common';
+import { VisSavedObject } from '../../types';
+import { SavedVisState } from '../../../common';
describe('extractReferences', () => {
test('extracts nothing if savedSearchId is empty', () => {
@@ -21,13 +21,13 @@ describe('extractReferences', () => {
};
const updatedDoc = extractReferences(doc);
expect(updatedDoc).toMatchInlineSnapshot(`
-Object {
- "attributes": Object {
- "foo": true,
- },
- "references": Array [],
-}
-`);
+ Object {
+ "attributes": Object {
+ "foo": true,
+ },
+ "references": Array [],
+ }
+ `);
});
test('extracts references from savedSearchId', () => {
@@ -41,20 +41,20 @@ Object {
};
const updatedDoc = extractReferences(doc);
expect(updatedDoc).toMatchInlineSnapshot(`
-Object {
- "attributes": Object {
- "foo": true,
- "savedSearchRefName": "search_0",
- },
- "references": Array [
- Object {
- "id": "123",
- "name": "search_0",
- "type": "search",
- },
- ],
-}
-`);
+ Object {
+ "attributes": Object {
+ "foo": true,
+ "savedSearchRefName": "search_0",
+ },
+ "references": Array [
+ Object {
+ "id": "123",
+ "name": "search_0",
+ "type": "search",
+ },
+ ],
+ }
+ `);
});
test('extracts references from controls', () => {
@@ -63,6 +63,7 @@ Object {
attributes: {
foo: true,
visState: JSON.stringify({
+ type: 'input_control_vis',
params: {
controls: [
{
@@ -81,20 +82,20 @@ Object {
const updatedDoc = extractReferences(doc);
expect(updatedDoc).toMatchInlineSnapshot(`
-Object {
- "attributes": Object {
- "foo": true,
- "visState": "{\\"params\\":{\\"controls\\":[{\\"bar\\":true,\\"indexPatternRefName\\":\\"control_0_index_pattern\\"},{\\"bar\\":false}]}}",
- },
- "references": Array [
- Object {
- "id": "pattern*",
- "name": "control_0_index_pattern",
- "type": "index-pattern",
- },
- ],
-}
-`);
+ Object {
+ "attributes": Object {
+ "foo": true,
+ "visState": "{\\"type\\":\\"input_control_vis\\",\\"params\\":{\\"controls\\":[{\\"bar\\":true,\\"indexPatternRefName\\":\\"control_0_index_pattern\\"},{\\"bar\\":false}]}}",
+ },
+ "references": Array [
+ Object {
+ "id": "pattern*",
+ "name": "control_0_index_pattern",
+ "type": "index-pattern",
+ },
+ ],
+ }
+ `);
});
});
@@ -106,11 +107,11 @@ describe('injectReferences', () => {
} as VisSavedObject;
injectReferences(context, []);
expect(context).toMatchInlineSnapshot(`
-Object {
- "id": "1",
- "title": "test",
-}
-`);
+ Object {
+ "id": "1",
+ "title": "test",
+ }
+ `);
});
test('injects references into context', () => {
@@ -119,6 +120,7 @@ Object {
title: 'test',
savedSearchRefName: 'search_0',
visState: ({
+ type: 'input_control_vis',
params: {
controls: [
{
@@ -146,25 +148,26 @@ Object {
];
injectReferences(context, references);
expect(context).toMatchInlineSnapshot(`
-Object {
- "id": "1",
- "savedSearchId": "123",
- "title": "test",
- "visState": Object {
- "params": Object {
- "controls": Array [
- Object {
- "foo": true,
- "indexPattern": "pattern*",
- },
- Object {
- "foo": false,
+ Object {
+ "id": "1",
+ "savedSearchId": "123",
+ "title": "test",
+ "visState": Object {
+ "params": Object {
+ "controls": Array [
+ Object {
+ "foo": true,
+ "indexPattern": "pattern*",
+ },
+ Object {
+ "foo": false,
+ },
+ ],
+ },
+ "type": "input_control_vis",
},
- ],
- },
- },
-}
-`);
+ }
+ `);
});
test(`fails when it can't find the saved search reference in the array`, () => {
@@ -183,6 +186,7 @@ Object {
id: '1',
title: 'test',
visState: ({
+ type: 'input_control_vis',
params: {
controls: [
{
diff --git a/src/plugins/visualizations/public/saved_visualizations/saved_visualization_references.ts b/src/plugins/visualizations/public/saved_visualizations/saved_visualization_references/saved_visualization_references.ts
similarity index 67%
rename from src/plugins/visualizations/public/saved_visualizations/saved_visualization_references.ts
rename to src/plugins/visualizations/public/saved_visualizations/saved_visualization_references/saved_visualization_references.ts
index 27b5a4542036b..6a4f9812db971 100644
--- a/src/plugins/visualizations/public/saved_visualizations/saved_visualization_references.ts
+++ b/src/plugins/visualizations/public/saved_visualizations/saved_visualization_references/saved_visualization_references.ts
@@ -10,13 +10,16 @@ import {
SavedObjectAttribute,
SavedObjectAttributes,
SavedObjectReference,
-} from '../../../../core/public';
-import { VisSavedObject } from '../types';
+} from '../../../../../core/public';
+import { SavedVisState, VisSavedObject } from '../../types';
import {
extractSearchSourceReferences,
injectSearchSourceReferences,
SearchSourceFields,
-} from '../../../data/public';
+} from '../../../../data/public';
+
+import { extractTimeSeriesReferences, injectTimeSeriesReferences } from './timeseries_references';
+import { extractControlsReferences, injectControlsReferences } from './controls_references';
export function extractReferences({
attributes,
@@ -49,20 +52,13 @@ export function extractReferences({
// Extract index patterns from controls
if (updatedAttributes.visState) {
- const visState = JSON.parse(String(updatedAttributes.visState));
- const controls = (visState.params && visState.params.controls) || [];
- controls.forEach((control: Record, i: number) => {
- if (!control.indexPattern) {
- return;
- }
- control.indexPatternRefName = `control_${i}_index_pattern`;
- updatedReferences.push({
- name: control.indexPatternRefName,
- type: 'index-pattern',
- id: control.indexPattern,
- });
- delete control.indexPattern;
- });
+ const visState = JSON.parse(String(updatedAttributes.visState)) as SavedVisState;
+
+ if (visState.type && visState.params) {
+ extractControlsReferences(visState.type, visState.params, updatedReferences);
+ extractTimeSeriesReferences(visState.type, visState.params, updatedReferences);
+ }
+
updatedAttributes.visState = JSON.stringify(visState);
}
@@ -89,18 +85,11 @@ export function injectReferences(savedObject: VisSavedObject, references: SavedO
savedObject.savedSearchId = savedSearchReference.id;
delete savedObject.savedSearchRefName;
}
- if (savedObject.visState) {
- const controls = (savedObject.visState.params && savedObject.visState.params.controls) || [];
- controls.forEach((control: Record) => {
- if (!control.indexPatternRefName) {
- return;
- }
- const reference = references.find((ref) => ref.name === control.indexPatternRefName);
- if (!reference) {
- throw new Error(`Could not find index pattern reference "${control.indexPatternRefName}"`);
- }
- control.indexPattern = reference.id;
- delete control.indexPatternRefName;
- });
+
+ const { type, params } = savedObject.visState ?? {};
+
+ if (type && params) {
+ injectControlsReferences(type, params, references);
+ injectTimeSeriesReferences(type, params, references);
}
}
diff --git a/src/plugins/visualizations/public/saved_visualizations/saved_visualization_references/timeseries_references.ts b/src/plugins/visualizations/public/saved_visualizations/saved_visualization_references/timeseries_references.ts
new file mode 100644
index 0000000000000..57706ee824e8d
--- /dev/null
+++ b/src/plugins/visualizations/public/saved_visualizations/saved_visualization_references/timeseries_references.ts
@@ -0,0 +1,83 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { SavedObjectReference } from '../../../../../core/types';
+import { VisParams } from '../../../common';
+
+/** @internal **/
+const REF_NAME_POSTFIX = '_ref_name';
+
+/** @internal **/
+const INDEX_PATTERN_REF_TYPE = 'index_pattern';
+
+/** @internal **/
+type Action = (object: Record, key: string) => void;
+
+const isMetricsVis = (visType: string) => visType === 'metrics';
+
+const doForExtractedIndices = (action: Action, visParams: VisParams) => {
+ action(visParams, 'index_pattern');
+
+ visParams.series.forEach((series: any) => {
+ if (series.override_index_pattern) {
+ action(series, 'series_index_pattern');
+ }
+ });
+
+ if (visParams.annotations) {
+ visParams.annotations.forEach((annotation: any) => {
+ action(annotation, 'index_pattern');
+ });
+ }
+};
+
+export const extractTimeSeriesReferences = (
+ visType: string,
+ visParams: VisParams,
+ references: SavedObjectReference[] = [],
+ prefix: string = 'metrics'
+) => {
+ let i = 0;
+ if (isMetricsVis(visType)) {
+ doForExtractedIndices((object, key) => {
+ if (object[key] && object[key].id) {
+ const name = `${prefix}_${i++}_index_pattern`;
+
+ object[key + REF_NAME_POSTFIX] = name;
+ references.push({
+ name,
+ type: INDEX_PATTERN_REF_TYPE,
+ id: object[key].id,
+ });
+ delete object[key];
+ }
+ }, visParams);
+ }
+};
+
+export const injectTimeSeriesReferences = (
+ visType: string,
+ visParams: VisParams,
+ references: SavedObjectReference[]
+) => {
+ if (isMetricsVis(visType)) {
+ doForExtractedIndices((object, key) => {
+ const refKey = key + REF_NAME_POSTFIX;
+
+ if (object[refKey]) {
+ const refValue = references.find((ref) => ref.name === object[refKey]);
+
+ if (refValue) {
+ object[key] = { id: refValue.id };
+ }
+
+ delete object[refKey];
+ }
+ }, visParams);
+ }
+};
diff --git a/src/plugins/visualizations/server/saved_objects/visualization_migrations.ts b/src/plugins/visualizations/server/saved_objects/visualization_migrations.ts
index afb59266d0dbf..ced33318413c5 100644
--- a/src/plugins/visualizations/server/saved_objects/visualization_migrations.ts
+++ b/src/plugins/visualizations/server/saved_objects/visualization_migrations.ts
@@ -790,6 +790,35 @@ const removeTSVBSearchSource: SavedObjectMigrationFn = (doc) => {
return doc;
};
+const addSupportOfDualIndexSelectionModeInTSVB: SavedObjectMigrationFn = (doc) => {
+ const visStateJSON = get(doc, 'attributes.visState');
+ let visState;
+
+ if (visStateJSON) {
+ try {
+ visState = JSON.parse(visStateJSON);
+ } catch (e) {
+ // Let it go, the data is invalid and we'll leave it as is
+ }
+ if (visState && visState.type === 'metrics') {
+ const { params } = visState;
+
+ if (typeof params?.index_pattern === 'string') {
+ params.use_kibana_indexes = false;
+ }
+
+ return {
+ ...doc,
+ attributes: {
+ ...doc.attributes,
+ visState: JSON.stringify(visState),
+ },
+ };
+ }
+ }
+ return doc;
+};
+
// [Data table visualization] Enable toolbar by default
const enableDataTableVisToolbar: SavedObjectMigrationFn = (doc) => {
let visState;
@@ -929,4 +958,5 @@ export const visualizationSavedObjectTypeMigrations = {
'7.10.0': flow(migrateFilterRatioQuery, removeTSVBSearchSource),
'7.11.0': flow(enableDataTableVisToolbar),
'7.12.0': flow(migrateVislibAreaLineBarTypes, migrateSchema),
+ '7.13.0': flow(addSupportOfDualIndexSelectionModeInTSVB),
};
diff --git a/src/setup_node_env/index.js b/src/setup_node_env/index.js
index 08664344db393..9ce60766997cc 100644
--- a/src/setup_node_env/index.js
+++ b/src/setup_node_env/index.js
@@ -7,4 +7,5 @@
*/
require('./no_transpilation');
+// eslint-disable-next-line import/no-extraneous-dependencies
require('@kbn/optimizer').registerNodeAutoTranspilation();
diff --git a/test/api_integration/apis/telemetry/telemetry_local.ts b/test/api_integration/apis/telemetry/telemetry_local.ts
index a7b4da566b143..d0a09ee58d335 100644
--- a/test/api_integration/apis/telemetry/telemetry_local.ts
+++ b/test/api_integration/apis/telemetry/telemetry_local.ts
@@ -156,7 +156,7 @@ export default function ({ getService }: FtrProviderContext) {
describe('application usage limits', () => {
function createSavedObject(viewId?: string) {
return supertest
- .post('/api/saved_objects/application_usage_transactional')
+ .post('/api/saved_objects/application_usage_daily')
.send({
attributes: {
appId: 'test-app',
@@ -184,7 +184,7 @@ export default function ({ getService }: FtrProviderContext) {
await Promise.all(
savedObjectIds.map((savedObjectId) => {
return supertest
- .delete(`/api/saved_objects/application_usage_transactional/${savedObjectId}`)
+ .delete(`/api/saved_objects/application_usage_daily/${savedObjectId}`)
.expect(200);
})
);
@@ -230,7 +230,7 @@ export default function ({ getService }: FtrProviderContext) {
.post('/api/saved_objects/_bulk_create')
.send(
new Array(10001).fill(0).map(() => ({
- type: 'application_usage_transactional',
+ type: 'application_usage_daily',
attributes: {
appId: 'test-app',
minutesOnScreen: 1,
@@ -248,13 +248,12 @@ export default function ({ getService }: FtrProviderContext) {
// The SavedObjects API does not allow bulk deleting, and deleting one by one takes ages and the tests timeout
await es.deleteByQuery({
index: '.kibana',
- body: { query: { term: { type: 'application_usage_transactional' } } },
+ body: { query: { term: { type: 'application_usage_daily' } } },
conflicts: 'proceed',
});
});
- // flaky https://github.com/elastic/kibana/issues/94513
- it.skip("should only use the first 10k docs for the application_usage data (they'll be rolled up in a later process)", async () => {
+ it("should only use the first 10k docs for the application_usage data (they'll be rolled up in a later process)", async () => {
const stats = await retrieveTelemetry(supertest);
expect(stats.stack_stats.kibana.plugins.application_usage).to.eql({
'test-app': {
diff --git a/test/functional/apps/discover/_saved_queries.ts b/test/functional/apps/discover/_saved_queries.ts
index 23f3af37bbdf6..9726b097c8f62 100644
--- a/test/functional/apps/discover/_saved_queries.ts
+++ b/test/functional/apps/discover/_saved_queries.ts
@@ -26,8 +26,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
const savedQueryManagementComponent = getService('savedQueryManagementComponent');
const testSubjects = getService('testSubjects');
- // FLAKY: https://github.com/elastic/kibana/issues/89477
- describe.skip('saved queries saved objects', function describeIndexTests() {
+ describe('saved queries saved objects', function describeIndexTests() {
before(async function () {
log.debug('load kibana index with default index pattern');
await esArchiver.load('discover');
@@ -120,6 +119,17 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
});
it('does not allow saving a query with a non-unique name', async () => {
+ // this check allows this test to run stand alone, also should fix occacional flakiness
+ const savedQueryExists = await savedQueryManagementComponent.savedQueryExist('OkResponse');
+ if (!savedQueryExists) {
+ await savedQueryManagementComponent.saveNewQuery(
+ 'OkResponse',
+ '200 responses for .jpg over 24 hours',
+ true,
+ true
+ );
+ await savedQueryManagementComponent.clearCurrentlyLoadedQuery();
+ }
await savedQueryManagementComponent.saveNewQueryWithNameError('OkResponse');
});
diff --git a/test/functional/apps/management/_runtime_fields.js b/test/functional/apps/management/_runtime_fields.js
index 4b3533f20c8dc..e3ff1819aed13 100644
--- a/test/functional/apps/management/_runtime_fields.js
+++ b/test/functional/apps/management/_runtime_fields.js
@@ -15,6 +15,7 @@ export default function ({ getService, getPageObjects }) {
const browser = getService('browser');
const retry = getService('retry');
const PageObjects = getPageObjects(['settings']);
+ const testSubjects = getService('testSubjects');
describe('runtime fields', function () {
this.tags(['skipFirefox']);
@@ -47,6 +48,20 @@ export default function ({ getService, getPageObjects }) {
expect(parseInt(await PageObjects.settings.getFieldsTabCount())).to.be(startingCount + 1);
});
});
+
+ it('should modify runtime field', async function () {
+ await PageObjects.settings.filterField(fieldName);
+ await testSubjects.click('editFieldFormat');
+ await PageObjects.settings.setFieldType('Long');
+ await PageObjects.settings.changeFieldScript('emit(6);');
+ await PageObjects.settings.clickSaveField();
+ await PageObjects.settings.confirmSave();
+ });
+
+ it('should delete runtime field', async function () {
+ await testSubjects.click('deleteField');
+ await PageObjects.settings.confirmDelete();
+ });
});
});
}
diff --git a/test/functional/apps/visualize/_tsvb_chart.ts b/test/functional/apps/visualize/_tsvb_chart.ts
index ba500904d75c7..80ab33170c396 100644
--- a/test/functional/apps/visualize/_tsvb_chart.ts
+++ b/test/functional/apps/visualize/_tsvb_chart.ts
@@ -107,33 +107,48 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
});
describe('switch index patterns', () => {
+ before(async () => {
+ await esArchiver.loadIfNeeded('index_pattern_without_timefield');
+ });
+
beforeEach(async () => {
- log.debug('Load kibana_sample_data_flights data');
- await esArchiver.loadIfNeeded('kibana_sample_data_flights');
await PageObjects.visualBuilder.resetPage();
await PageObjects.visualBuilder.clickMetric();
await PageObjects.visualBuilder.checkMetricTabIsPresent();
+ await PageObjects.timePicker.setAbsoluteRange(
+ 'Sep 22, 2019 @ 00:00:00.000',
+ 'Sep 23, 2019 @ 00:00:00.000'
+ );
});
+
after(async () => {
await security.testUser.restoreDefaults();
- await esArchiver.unload('kibana_sample_data_flights');
+ await esArchiver.unload('index_pattern_without_timefield');
});
- it('should be able to switch between index patterns', async () => {
- const value = await PageObjects.visualBuilder.getMetricValue();
- expect(value).to.eql('156');
+ const switchIndexTest = async (useKibanaIndexes: boolean) => {
await PageObjects.visualBuilder.clickPanelOptions('metric');
- const fromTime = 'Oct 22, 2018 @ 00:00:00.000';
- const toTime = 'Oct 28, 2018 @ 23:59:59.999';
- await PageObjects.timePicker.setAbsoluteRange(fromTime, toTime);
+ await PageObjects.visualBuilder.setIndexPatternValue('', false);
+
+ const value = await PageObjects.visualBuilder.getMetricValue();
+ expect(value).to.eql('0');
+
// Sometimes popovers take some time to appear in Firefox (#71979)
await retry.tryForTime(20000, async () => {
- await PageObjects.visualBuilder.setIndexPatternValue('kibana_sample_data_flights');
+ await PageObjects.visualBuilder.setIndexPatternValue('with-timefield', useKibanaIndexes);
await PageObjects.visualBuilder.waitForIndexPatternTimeFieldOptionsLoaded();
await PageObjects.visualBuilder.selectIndexPatternTimeField('timestamp');
});
const newValue = await PageObjects.visualBuilder.getMetricValue();
- expect(newValue).to.eql('18');
+ expect(newValue).to.eql('1');
+ };
+
+ it('should be able to switch using text mode selection', async () => {
+ await switchIndexTest(false);
+ });
+
+ it('should be able to switch combo box mode selection', async () => {
+ await switchIndexTest(true);
});
});
diff --git a/test/functional/page_objects/common_page.ts b/test/functional/page_objects/common_page.ts
index c6412f55dffbf..6d9641a1a920e 100644
--- a/test/functional/page_objects/common_page.ts
+++ b/test/functional/page_objects/common_page.ts
@@ -463,6 +463,21 @@ export function CommonPageProvider({ getService, getPageObjects }: FtrProviderCo
async getWelcomeText() {
return await testSubjects.getVisibleText('global-banner-item');
}
+
+ /**
+ * Clicks on an element, and validates that the desired effect has taken place
+ * by confirming the existence of a validator
+ */
+ async clickAndValidate(
+ clickTarget: string,
+ validator: string,
+ isValidatorCssString: boolean = false,
+ topOffset?: number
+ ) {
+ await testSubjects.click(clickTarget, undefined, topOffset);
+ const validate = isValidatorCssString ? find.byCssSelector : testSubjects.exists;
+ await validate(validator);
+ }
}
return new CommonPage();
diff --git a/test/functional/page_objects/settings_page.ts b/test/functional/page_objects/settings_page.ts
index 4151a8c1a1893..14bd002ec9487 100644
--- a/test/functional/page_objects/settings_page.ts
+++ b/test/functional/page_objects/settings_page.ts
@@ -502,6 +502,16 @@ export function SettingsPageProvider({ getService, getPageObjects }: FtrProvider
await this.closeIndexPatternFieldEditor();
}
+ public async confirmSave() {
+ await testSubjects.setValue('saveModalConfirmText', 'change');
+ await testSubjects.click('confirmModalConfirmButton');
+ }
+
+ public async confirmDelete() {
+ await testSubjects.setValue('deleteModalConfirmText', 'remove');
+ await testSubjects.click('confirmModalConfirmButton');
+ }
+
async closeIndexPatternFieldEditor() {
await retry.waitFor('field editor flyout to close', async () => {
return !(await testSubjects.exists('euiFlyoutCloseButton'));
@@ -543,6 +553,17 @@ export function SettingsPageProvider({ getService, getPageObjects }: FtrProvider
browser.pressKeys(script);
}
+ async changeFieldScript(script: string) {
+ log.debug('set script = ' + script);
+ const formatRow = await testSubjects.find('valueRow');
+ const getMonacoTextArea = async () => (await formatRow.findAllByCssSelector('textarea'))[0];
+ retry.waitFor('monaco editor is ready', async () => !!(await getMonacoTextArea()));
+ const monacoTextArea = await getMonacoTextArea();
+ await monacoTextArea.focus();
+ browser.pressKeys(browser.keys.DELETE.repeat(30));
+ browser.pressKeys(script);
+ }
+
async clickAddScriptedField() {
log.debug('click Add Scripted Field');
await testSubjects.click('addScriptedFieldLink');
diff --git a/test/functional/page_objects/visual_builder_page.ts b/test/functional/page_objects/visual_builder_page.ts
index d7bb84394ae3c..90873d72234d0 100644
--- a/test/functional/page_objects/visual_builder_page.ts
+++ b/test/functional/page_objects/visual_builder_page.ts
@@ -431,10 +431,34 @@ export function VisualBuilderPageProvider({ getService, getPageObjects }: FtrPro
await PageObjects.header.waitUntilLoadingHasFinished();
}
- public async setIndexPatternValue(value: string) {
- const el = await testSubjects.find('metricsIndexPatternInput');
- await el.clearValue();
- await el.type(value, { charByChar: true });
+ public async switchIndexPatternSelectionMode(useKibanaIndices: boolean) {
+ await testSubjects.click('switchIndexPatternSelectionModePopover');
+ await testSubjects.setEuiSwitch(
+ 'switchIndexPatternSelectionMode',
+ useKibanaIndices ? 'check' : 'uncheck'
+ );
+ }
+
+ public async setIndexPatternValue(value: string, useKibanaIndices?: boolean) {
+ const metricsIndexPatternInput = 'metricsIndexPatternInput';
+
+ if (useKibanaIndices !== undefined) {
+ await this.switchIndexPatternSelectionMode(useKibanaIndices);
+ }
+
+ if (useKibanaIndices === false) {
+ const el = await testSubjects.find(metricsIndexPatternInput);
+ await el.clearValue();
+ if (value) {
+ await el.type(value, { charByChar: true });
+ }
+ } else {
+ await comboBox.clearInputField(metricsIndexPatternInput);
+ if (value) {
+ await comboBox.setCustom(metricsIndexPatternInput, value);
+ }
+ }
+
await PageObjects.header.waitUntilLoadingHasFinished();
}
diff --git a/test/functional/services/common/find.ts b/test/functional/services/common/find.ts
index 2a86efad1ea9d..0cd4c14683f6e 100644
--- a/test/functional/services/common/find.ts
+++ b/test/functional/services/common/find.ts
@@ -79,11 +79,11 @@ export async function FindProvider({ getService }: FtrProviderContext) {
return wrap(await driver.switchTo().activeElement());
}
- public async setValue(selector: string, text: string): Promise {
+ public async setValue(selector: string, text: string, topOffset?: number): Promise {
log.debug(`Find.setValue('${selector}', '${text}')`);
return await retry.try(async () => {
const element = await this.byCssSelector(selector);
- await element.click();
+ await element.click(topOffset);
// in case the input element is actually a child of the testSubject, we
// call clearValue() and type() on the element that is focused after
@@ -413,14 +413,15 @@ export async function FindProvider({ getService }: FtrProviderContext) {
public async clickByCssSelector(
selector: string,
- timeout: number = defaultFindTimeout
+ timeout: number = defaultFindTimeout,
+ topOffset?: number
): Promise {
log.debug(`Find.clickByCssSelector('${selector}') with timeout=${timeout}`);
await retry.try(async () => {
const element = await this.byCssSelector(selector, timeout);
if (element) {
// await element.moveMouseTo();
- await element.click();
+ await element.click(topOffset);
} else {
throw new Error(`Element with css='${selector}' is not found`);
}
diff --git a/test/functional/services/common/test_subjects.ts b/test/functional/services/common/test_subjects.ts
index 28b37d9576e8c..111206ec9eafe 100644
--- a/test/functional/services/common/test_subjects.ts
+++ b/test/functional/services/common/test_subjects.ts
@@ -100,9 +100,13 @@ export function TestSubjectsProvider({ getService }: FtrProviderContext) {
await find.clickByCssSelectorWhenNotDisabled(testSubjSelector(selector), { timeout });
}
- public async click(selector: string, timeout: number = FIND_TIME): Promise {
+ public async click(
+ selector: string,
+ timeout: number = FIND_TIME,
+ topOffset?: number
+ ): Promise {
log.debug(`TestSubjects.click(${selector})`);
- await find.clickByCssSelector(testSubjSelector(selector), timeout);
+ await find.clickByCssSelector(testSubjSelector(selector), timeout, topOffset);
}
public async doubleClick(selector: string, timeout: number = FIND_TIME): Promise {
@@ -187,12 +191,13 @@ export function TestSubjectsProvider({ getService }: FtrProviderContext) {
public async setValue(
selector: string,
text: string,
- options: SetValueOptions = {}
+ options: SetValueOptions = {},
+ topOffset?: number
): Promise {
return await retry.try(async () => {
const { clearWithKeyboard = false, typeCharByChar = false } = options;
log.debug(`TestSubjects.setValue(${selector}, ${text})`);
- await this.click(selector);
+ await this.click(selector, undefined, topOffset);
// in case the input element is actually a child of the testSubject, we
// call clearValue() and type() on the element that is focused after
// clicking on the testSubject
diff --git a/test/functional/services/field_editor.ts b/test/functional/services/field_editor.ts
index 5cd1f2c4f6202..7d6dad4f7858e 100644
--- a/test/functional/services/field_editor.ts
+++ b/test/functional/services/field_editor.ts
@@ -10,7 +10,6 @@ import { FtrProviderContext } from '../ftr_provider_context';
export function FieldEditorProvider({ getService }: FtrProviderContext) {
const browser = getService('browser');
- const retry = getService('retry');
const testSubjects = getService('testSubjects');
class FieldEditor {
@@ -33,10 +32,17 @@ export function FieldEditorProvider({ getService }: FtrProviderContext) {
await browser.pressKeys(script);
}
public async save() {
- await retry.try(async () => {
- await testSubjects.click('fieldSaveButton');
- await testSubjects.missingOrFail('fieldSaveButton', { timeout: 2000 });
- });
+ await testSubjects.click('fieldSaveButton');
+ }
+
+ public async confirmSave() {
+ await testSubjects.setValue('saveModalConfirmText', 'change');
+ await testSubjects.click('confirmModalConfirmButton');
+ }
+
+ public async confirmDelete() {
+ await testSubjects.setValue('deleteModalConfirmText', 'remove');
+ await testSubjects.click('confirmModalConfirmButton');
}
}
diff --git a/test/functional/services/lib/web_element_wrapper/web_element_wrapper.ts b/test/functional/services/lib/web_element_wrapper/web_element_wrapper.ts
index 1a45aee877e1f..b1561b29342da 100644
--- a/test/functional/services/lib/web_element_wrapper/web_element_wrapper.ts
+++ b/test/functional/services/lib/web_element_wrapper/web_element_wrapper.ts
@@ -182,9 +182,9 @@ export class WebElementWrapper {
*
* @return {Promise}
*/
- public async click() {
+ public async click(topOffset?: number) {
await this.retryCall(async function click(wrapper) {
- await wrapper.scrollIntoViewIfNecessary();
+ await wrapper.scrollIntoViewIfNecessary(topOffset);
await wrapper._webElement.click();
});
}
@@ -693,11 +693,11 @@ export class WebElementWrapper {
* @nonstandard
* @return {Promise}
*/
- public async scrollIntoViewIfNecessary(): Promise {
+ public async scrollIntoViewIfNecessary(topOffset?: number): Promise {
await this.driver.executeScript(
scrollIntoViewIfNecessary,
this._webElement,
- this.fixedHeaderHeight
+ topOffset || this.fixedHeaderHeight
);
}
diff --git a/test/functional/services/saved_query_management_component.ts b/test/functional/services/saved_query_management_component.ts
index a39032af43295..7398e6ca8c12e 100644
--- a/test/functional/services/saved_query_management_component.ts
+++ b/test/functional/services/saved_query_management_component.ts
@@ -139,6 +139,13 @@ export function SavedQueryManagementComponentProvider({
await testSubjects.click('savedQueryFormSaveButton');
}
+ async savedQueryExist(title: string) {
+ await this.openSavedQueryManagementComponent();
+ const exists = testSubjects.exists(`~load-saved-query-${title}-button`);
+ await this.closeSavedQueryManagementComponent();
+ return exists;
+ }
+
async savedQueryExistOrFail(title: string) {
await this.openSavedQueryManagementComponent();
await testSubjects.existOrFail(`~load-saved-query-${title}-button`);
diff --git a/x-pack/plugins/actions/server/usage/actions_usage_collector.ts b/x-pack/plugins/actions/server/usage/actions_usage_collector.ts
index f8a91e3a0a67a..c338bbc998c49 100644
--- a/x-pack/plugins/actions/server/usage/actions_usage_collector.ts
+++ b/x-pack/plugins/actions/server/usage/actions_usage_collector.ts
@@ -23,6 +23,7 @@ const byTypeSchema: MakeSchemaFrom['count_by_type'] = {
__servicenow: { type: 'long' },
__jira: { type: 'long' },
__resilient: { type: 'long' },
+ __teams: { type: 'long' },
};
export function createActionsUsageCollector(
diff --git a/x-pack/plugins/alerting/server/usage/alerts_usage_collector.ts b/x-pack/plugins/alerting/server/usage/alerts_usage_collector.ts
index 884120d3d03df..59aeb4854d9f0 100644
--- a/x-pack/plugins/alerting/server/usage/alerts_usage_collector.ts
+++ b/x-pack/plugins/alerting/server/usage/alerts_usage_collector.ts
@@ -16,6 +16,7 @@ const byTypeSchema: MakeSchemaFrom['count_by_type'] = {
// Known alerts (searching the use of the alerts API `registerType`:
// Built-in
'__index-threshold': { type: 'long' },
+ '__es-query': { type: 'long' },
// APM
apm__error_rate: { type: 'long' }, // eslint-disable-line @typescript-eslint/naming-convention
apm__transaction_error_rate: { type: 'long' }, // eslint-disable-line @typescript-eslint/naming-convention
@@ -41,6 +42,10 @@ const byTypeSchema: MakeSchemaFrom['count_by_type'] = {
xpack__uptime__alerts__monitorStatus: { type: 'long' }, // eslint-disable-line @typescript-eslint/naming-convention
xpack__uptime__alerts__tls: { type: 'long' }, // eslint-disable-line @typescript-eslint/naming-convention
xpack__uptime__alerts__durationAnomaly: { type: 'long' }, // eslint-disable-line @typescript-eslint/naming-convention
+ // Maps
+ '__geo-containment': { type: 'long' },
+ // ML
+ xpack_ml_anomaly_detection_alert: { type: 'long' },
};
export function createAlertsUsageCollector(
diff --git a/x-pack/plugins/apm/common/apm_api/parse_endpoint.ts b/x-pack/plugins/apm/common/apm_api/parse_endpoint.ts
new file mode 100644
index 0000000000000..fb7ef6d36ce25
--- /dev/null
+++ b/x-pack/plugins/apm/common/apm_api/parse_endpoint.ts
@@ -0,0 +1,32 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+type Method = 'get' | 'post' | 'put' | 'delete';
+
+export function parseEndpoint(
+ endpoint: string,
+ pathParams: Record = {}
+) {
+ const [method, rawPathname] = endpoint.split(' ');
+
+ // replace template variables with path params
+ const pathname = Object.keys(pathParams).reduce((acc, paramName) => {
+ return acc.replace(`{${paramName}}`, pathParams[paramName]);
+ }, rawPathname);
+
+ return { method: parseMethod(method), pathname };
+}
+
+export function parseMethod(method: string) {
+ const res = method.trim().toLowerCase() as Method;
+
+ if (!['get', 'post', 'put', 'delete'].includes(res)) {
+ throw new Error('Endpoint was not prefixed with a valid HTTP method');
+ }
+
+ return res;
+}
diff --git a/x-pack/plugins/apm/common/runtime_types/strict_keys_rt/index.test.ts b/x-pack/plugins/apm/common/runtime_types/strict_keys_rt/index.test.ts
index 3316c74d52e38..4212e0430ff5f 100644
--- a/x-pack/plugins/apm/common/runtime_types/strict_keys_rt/index.test.ts
+++ b/x-pack/plugins/apm/common/runtime_types/strict_keys_rt/index.test.ts
@@ -45,10 +45,10 @@ describe('strictKeysRt', () => {
{
type: t.intersection([
t.type({ query: t.type({ bar: t.string }) }),
- t.partial({ query: t.partial({ _debug: t.boolean }) }),
+ t.partial({ query: t.partial({ _inspect: t.boolean }) }),
]),
- passes: [{ query: { bar: '', _debug: true } }],
- fails: [{ query: { _debug: true } }],
+ passes: [{ query: { bar: '', _inspect: true } }],
+ fails: [{ query: { _inspect: true } }],
},
];
@@ -91,12 +91,12 @@ describe('strictKeysRt', () => {
} as Record);
const typeB = t.partial({
- query: t.partial({ _debug: jsonRt.pipe(t.boolean) }),
+ query: t.partial({ _inspect: jsonRt.pipe(t.boolean) }),
});
const value = {
query: {
- _debug: 'true',
+ _inspect: 'true',
filterNames: JSON.stringify(['host', 'agentName']),
},
};
diff --git a/x-pack/plugins/apm/public/application/application.test.tsx b/x-pack/plugins/apm/public/application/application.test.tsx
index b785fcc7dab08..7df6ca343426c 100644
--- a/x-pack/plugins/apm/public/application/application.test.tsx
+++ b/x-pack/plugins/apm/public/application/application.test.tsx
@@ -8,7 +8,7 @@
import { act } from '@testing-library/react';
import { createMemoryHistory } from 'history';
import { Observable } from 'rxjs';
-import { AppMountParameters, CoreStart, HttpSetup } from 'src/core/public';
+import { AppMountParameters, CoreStart } from 'src/core/public';
import { mockApmPluginContextValue } from '../context/apm_plugin/mock_apm_plugin_context';
import { ApmPluginSetupDeps, ApmPluginStartDeps } from '../plugin';
import { createCallApmApi } from '../services/rest/createCallApmApi';
@@ -72,7 +72,7 @@ describe('renderApp', () => {
embeddable,
};
jest.spyOn(window, 'scrollTo').mockReturnValueOnce(undefined);
- createCallApmApi((core.http as unknown) as HttpSetup);
+ createCallApmApi((core as unknown) as CoreStart);
jest
.spyOn(window.console, 'warn')
diff --git a/x-pack/plugins/apm/public/application/csmApp.tsx b/x-pack/plugins/apm/public/application/csmApp.tsx
index 8ea4593bb89a7..787b15d0a5675 100644
--- a/x-pack/plugins/apm/public/application/csmApp.tsx
+++ b/x-pack/plugins/apm/public/application/csmApp.tsx
@@ -118,7 +118,7 @@ export const renderApp = (
) => {
const { element } = appMountParameters;
- createCallApmApi(core.http);
+ createCallApmApi(core);
// Automatically creates static index pattern and stores as saved object
createStaticIndexPattern().catch((e) => {
diff --git a/x-pack/plugins/apm/public/application/index.tsx b/x-pack/plugins/apm/public/application/index.tsx
index 64600dd500bd5..bc14bc1531686 100644
--- a/x-pack/plugins/apm/public/application/index.tsx
+++ b/x-pack/plugins/apm/public/application/index.tsx
@@ -120,7 +120,7 @@ export const renderApp = (
// render APM feedback link in global help menu
setHelpExtension(core);
setReadonlyBadge(core);
- createCallApmApi(core.http);
+ createCallApmApi(core);
// Automatically creates static index pattern and stores as saved object
createStaticIndexPattern().catch((e) => {
diff --git a/x-pack/plugins/apm/public/components/alerting/error_count_alert_trigger/index.tsx b/x-pack/plugins/apm/public/components/alerting/error_count_alert_trigger/index.tsx
index 29f74b26d310c..fdfed6eb0d685 100644
--- a/x-pack/plugins/apm/public/components/alerting/error_count_alert_trigger/index.tsx
+++ b/x-pack/plugins/apm/public/components/alerting/error_count_alert_trigger/index.tsx
@@ -107,7 +107,11 @@ export function ErrorCountAlertTrigger(props: Props) {
];
const chartPreview = (
-
+
);
return (
diff --git a/x-pack/plugins/apm/public/components/alerting/transaction_duration_alert_trigger/index.tsx b/x-pack/plugins/apm/public/components/alerting/transaction_duration_alert_trigger/index.tsx
index 11aab788ec7f4..b4c78b54f329b 100644
--- a/x-pack/plugins/apm/public/components/alerting/transaction_duration_alert_trigger/index.tsx
+++ b/x-pack/plugins/apm/public/components/alerting/transaction_duration_alert_trigger/index.tsx
@@ -13,7 +13,6 @@ import { useParams } from 'react-router-dom';
import { ForLastExpression } from '../../../../../triggers_actions_ui/public';
import { ENVIRONMENT_ALL } from '../../../../common/environment_filter_values';
import { getDurationFormatter } from '../../../../common/utils/formatters';
-import { TimeSeries } from '../../../../typings/timeseries';
import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context';
import { useUrlParams } from '../../../context/url_params_context/use_url_params';
import { useEnvironmentsFetcher } from '../../../hooks/use_environments_fetcher';
@@ -116,9 +115,9 @@ export function TransactionDurationAlertTrigger(props: Props) {
]
);
- const maxY = getMaxY([
- { data: data ?? [] } as TimeSeries<{ x: number; y: number | null }>,
- ]);
+ const latencyChartPreview = data?.latencyChartPreview ?? [];
+
+ const maxY = getMaxY([{ data: latencyChartPreview }]);
const formatter = getDurationFormatter(maxY);
const yTickFormat = getResponseTimeTickFormatter(formatter);
@@ -127,7 +126,7 @@ export function TransactionDurationAlertTrigger(props: Props) {
const chartPreview = (
diff --git a/x-pack/plugins/apm/public/components/alerting/transaction_error_rate_alert_trigger/index.tsx b/x-pack/plugins/apm/public/components/alerting/transaction_error_rate_alert_trigger/index.tsx
index de30af4a4707f..c6f9c4efd98b6 100644
--- a/x-pack/plugins/apm/public/components/alerting/transaction_error_rate_alert_trigger/index.tsx
+++ b/x-pack/plugins/apm/public/components/alerting/transaction_error_rate_alert_trigger/index.tsx
@@ -132,7 +132,7 @@ export function TransactionErrorRateAlertTrigger(props: Props) {
const chartPreview = (
asPercent(d, 1)}
threshold={thresholdAsPercent}
/>
diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/BreakdownSeries.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/BreakdownSeries.tsx
index 6c94b895f6924..db5932a96fb12 100644
--- a/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/BreakdownSeries.tsx
+++ b/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/BreakdownSeries.tsx
@@ -35,7 +35,7 @@ export function BreakdownSeries({
? EUI_CHARTS_THEME_DARK
: EUI_CHARTS_THEME_LIGHT;
- const { data, status } = useBreakdowns({
+ const { breakdowns, status } = useBreakdowns({
field,
value,
percentileRange,
@@ -49,7 +49,7 @@ export function BreakdownSeries({
// so don't user that here
return (
<>
- {data?.map(({ data: seriesData, name }, sortIndex) => (
+ {breakdowns.map(({ data: seriesData, name }, sortIndex) => (
diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/use_breakdowns.ts b/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/use_breakdowns.ts
index 5af7f0682db19..e21aaa08c432d 100644
--- a/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/use_breakdowns.ts
+++ b/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/use_breakdowns.ts
@@ -17,12 +17,10 @@ interface Props {
export const useBreakdowns = ({ percentileRange, field, value }: Props) => {
const { urlParams, uiFilters } = useUrlParams();
-
const { start, end, searchTerm } = urlParams;
-
const { min: minP, max: maxP } = percentileRange ?? {};
- return useFetcher(
+ const { data, status } = useFetcher(
(callApmApi) => {
if (start && end && field && value) {
return callApmApi({
@@ -47,4 +45,6 @@ export const useBreakdowns = ({ percentileRange, field, value }: Props) => {
},
[end, start, uiFilters, field, value, minP, maxP, searchTerm]
);
+
+ return { breakdowns: data?.pageLoadDistBreakdown ?? [], status };
};
diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/Panels/MainFilters.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/Panels/MainFilters.tsx
index e3e2a979c48d3..d04bcb79a53e1 100644
--- a/x-pack/plugins/apm/public/components/app/RumDashboard/Panels/MainFilters.tsx
+++ b/x-pack/plugins/apm/public/components/app/RumDashboard/Panels/MainFilters.tsx
@@ -38,6 +38,7 @@ export function MainFilters() {
[start, end]
);
+ const rumServiceNames = data?.rumServices ?? [];
const { isSmall } = useBreakPoints();
// on mobile we want it to take full width
@@ -48,7 +49,7 @@ export function MainFilters() {
diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/hooks/useLocalUIFilters.ts b/x-pack/plugins/apm/public/components/app/RumDashboard/hooks/useLocalUIFilters.ts
index c40f6ba2b8850..8ae4c9dc0e01d 100644
--- a/x-pack/plugins/apm/public/components/app/RumDashboard/hooks/useLocalUIFilters.ts
+++ b/x-pack/plugins/apm/public/components/app/RumDashboard/hooks/useLocalUIFilters.ts
@@ -68,7 +68,7 @@ export function useLocalUIFilters({
});
};
- const { data = getInitialData(filterNames), status } = useFetcher(
+ const { data, status } = useFetcher(
(callApmApi) => {
if (shouldFetch && urlParams.start && urlParams.end) {
return callApmApi({
@@ -96,7 +96,8 @@ export function useLocalUIFilters({
]
);
- const filters = data.map((filter) => ({
+ const localUiFilters = data?.localUiFilters ?? getInitialData(filterNames);
+ const filters = localUiFilters.map((filter) => ({
...filter,
value: values[filter.name] || [],
}));
diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/hooks/use_call_api.ts b/x-pack/plugins/apm/public/components/app/RumDashboard/hooks/use_call_api.ts
index 5b448871804eb..f932cec3cacb6 100644
--- a/x-pack/plugins/apm/public/components/app/RumDashboard/hooks/use_call_api.ts
+++ b/x-pack/plugins/apm/public/components/app/RumDashboard/hooks/use_call_api.ts
@@ -11,9 +11,9 @@ import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plug
import { FetchOptions } from '../../../../../common/fetch_options';
export function useCallApi() {
- const { http } = useApmPluginContext().core;
+ const { core } = useApmPluginContext();
return useMemo(() => {
- return (options: FetchOptions) => callApi(http, options);
- }, [http]);
+ return (options: FetchOptions) => callApi(core, options);
+ }, [core]);
}
diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Popover.stories.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Popover.stories.tsx
index d754710dc84fa..ac1846155569a 100644
--- a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Popover.stories.tsx
+++ b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Popover.stories.tsx
@@ -6,7 +6,7 @@
*/
import cytoscape from 'cytoscape';
-import { HttpSetup } from 'kibana/public';
+import { CoreStart } from 'kibana/public';
import React, { ComponentType } from 'react';
import { EuiThemeProvider } from '../../../../../../../../src/plugins/kibana_react/common';
import { MockApmPluginContextWrapper } from '../../../../context/apm_plugin/mock_apm_plugin_context';
@@ -21,19 +21,21 @@ export default {
component: Popover,
decorators: [
(Story: ComponentType) => {
- const httpMock = ({
- get: async () => ({
- avgCpuUsage: 0.32809666568309237,
- avgErrorRate: 0.556068173242986,
- avgMemoryUsage: 0.5504868173242986,
- transactionStats: {
- avgRequestsPerMinute: 164.47222031860858,
- avgTransactionDuration: 61634.38905590272,
- },
- }),
- } as unknown) as HttpSetup;
+ const coreMock = ({
+ http: {
+ get: async () => ({
+ avgCpuUsage: 0.32809666568309237,
+ avgErrorRate: 0.556068173242986,
+ avgMemoryUsage: 0.5504868173242986,
+ transactionStats: {
+ avgRequestsPerMinute: 164.47222031860858,
+ avgTransactionDuration: 61634.38905590272,
+ },
+ }),
+ },
+ } as unknown) as CoreStart;
- createCallApmApi(httpMock);
+ createCallApmApi(coreMock);
return (
diff --git a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/ServicePage/ServicePage.tsx b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/ServicePage/ServicePage.tsx
index e762f517ce1b5..71355a84d28d4 100644
--- a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/ServicePage/ServicePage.tsx
+++ b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/ServicePage/ServicePage.tsx
@@ -33,7 +33,7 @@ interface Props {
}
export function ServicePage({ newConfig, setNewConfig, onClickNext }: Props) {
- const { data: serviceNames = [], status: serviceNamesStatus } = useFetcher(
+ const { data: serviceNamesData, status: serviceNamesStatus } = useFetcher(
(callApmApi) => {
return callApmApi({
endpoint: 'GET /api/apm/settings/agent-configuration/services',
@@ -43,8 +43,9 @@ export function ServicePage({ newConfig, setNewConfig, onClickNext }: Props) {
[],
{ preservePreviousData: false }
);
+ const serviceNames = serviceNamesData?.serviceNames ?? [];
- const { data: environments = [], status: environmentStatus } = useFetcher(
+ const { data: environmentsData, status: environmentsStatus } = useFetcher(
(callApmApi) => {
if (newConfig.service.name) {
return callApmApi({
@@ -59,6 +60,8 @@ export function ServicePage({ newConfig, setNewConfig, onClickNext }: Props) {
{ preservePreviousData: false }
);
+ const environments = environmentsData?.environments ?? [];
+
const { status: agentNameStatus } = useFetcher(
async (callApmApi) => {
const serviceName = newConfig.service.name;
@@ -153,11 +156,11 @@ export function ServicePage({ newConfig, setNewConfig, onClickNext }: Props) {
'xpack.apm.agentConfig.servicePage.environment.fieldLabel',
{ defaultMessage: 'Service environment' }
)}
- isLoading={environmentStatus === FETCH_STATUS.LOADING}
+ isLoading={environmentsStatus === FETCH_STATUS.LOADING}
options={environmentOptions}
value={newConfig.service.environment}
disabled={
- !newConfig.service.name || environmentStatus === FETCH_STATUS.LOADING
+ !newConfig.service.name || environmentsStatus === FETCH_STATUS.LOADING
}
onChange={(e) => {
e.preventDefault();
diff --git a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/index.stories.tsx b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/index.stories.tsx
index 4d2754a677bf7..cd5fa5db89a31 100644
--- a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/index.stories.tsx
+++ b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/index.stories.tsx
@@ -7,7 +7,7 @@
import { storiesOf } from '@storybook/react';
import React from 'react';
-import { HttpSetup } from 'kibana/public';
+import { CoreStart } from 'kibana/public';
import { EuiThemeProvider } from '../../../../../../../../../src/plugins/kibana_react/common';
import { AgentConfiguration } from '../../../../../../common/agent_configuration/configuration_types';
import { FETCH_STATUS } from '../../../../../hooks/use_fetcher';
@@ -23,10 +23,10 @@ storiesOf(
module
)
.addDecorator((storyFn) => {
- const httpMock = {};
+ const coreMock = ({} as unknown) as CoreStart;
// mock
- createCallApmApi((httpMock as unknown) as HttpSetup);
+ createCallApmApi(coreMock);
const contextMock = {
core: {
diff --git a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/List/ConfirmDeleteModal.tsx b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/List/ConfirmDeleteModal.tsx
index 081a3dbc907c5..3e3bc892e6518 100644
--- a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/List/ConfirmDeleteModal.tsx
+++ b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/List/ConfirmDeleteModal.tsx
@@ -16,7 +16,7 @@ import {
} from '../../../../../services/rest/createCallApmApi';
import { useApmPluginContext } from '../../../../../context/apm_plugin/use_apm_plugin_context';
-type Config = APIReturnType<'GET /api/apm/settings/agent-configuration'>[0];
+type Config = APIReturnType<'GET /api/apm/settings/agent-configuration'>['configurations'][0];
interface Props {
config: Config;
diff --git a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/List/index.tsx b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/List/index.tsx
index bef0dfc22280c..c098be41968dd 100644
--- a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/List/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/List/index.tsx
@@ -32,15 +32,19 @@ import { ITableColumn, ManagedTable } from '../../../../shared/ManagedTable';
import { TimestampTooltip } from '../../../../shared/TimestampTooltip';
import { ConfirmDeleteModal } from './ConfirmDeleteModal';
-type Config = APIReturnType<'GET /api/apm/settings/agent-configuration'>[0];
+type Config = APIReturnType<'GET /api/apm/settings/agent-configuration'>['configurations'][0];
interface Props {
status: FETCH_STATUS;
- data: Config[];
+ configurations: Config[];
refetch: () => void;
}
-export function AgentConfigurationList({ status, data, refetch }: Props) {
+export function AgentConfigurationList({
+ status,
+ configurations,
+ refetch,
+}: Props) {
const { core } = useApmPluginContext();
const canSave = core.application.capabilities.apm.save;
const { basePath } = core.http;
@@ -113,7 +117,7 @@ export function AgentConfigurationList({ status, data, refetch }: Props) {
return failurePrompt;
}
- if (status === FETCH_STATUS.SUCCESS && isEmpty(data)) {
+ if (status === FETCH_STATUS.SUCCESS && isEmpty(configurations)) {
return emptyStatePrompt;
}
@@ -231,7 +235,7 @@ export function AgentConfigurationList({ status, data, refetch }: Props) {
}
columns={columns}
- items={data}
+ items={configurations}
initialSortField="service.name"
initialSortDirection="asc"
initialPageSize={20}
diff --git a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/index.tsx b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/index.tsx
index 8aa0c35f36717..3225951fd6c70 100644
--- a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/index.tsx
@@ -25,8 +25,10 @@ import { useFetcher } from '../../../../hooks/use_fetcher';
import { createAgentConfigurationHref } from '../../../shared/Links/apm/agentConfigurationLinks';
import { AgentConfigurationList } from './List';
+const INITIAL_DATA = { configurations: [] };
+
export function AgentConfigurations() {
- const { refetch, data = [], status } = useFetcher(
+ const { refetch, data = INITIAL_DATA, status } = useFetcher(
(callApmApi) =>
callApmApi({ endpoint: 'GET /api/apm/settings/agent-configuration' }),
[],
@@ -36,7 +38,7 @@ export function AgentConfigurations() {
useTrackPageview({ app: 'apm', path: 'agent_configuration' });
useTrackPageview({ app: 'apm', path: 'agent_configuration', delay: 15000 });
- const hasConfigurations = !isEmpty(data);
+ const hasConfigurations = !isEmpty(data.configurations);
return (
<>
@@ -72,7 +74,11 @@ export function AgentConfigurations() {
-
+
>
);
diff --git a/x-pack/plugins/apm/public/components/app/Settings/ApmIndices/index.tsx b/x-pack/plugins/apm/public/components/app/Settings/ApmIndices/index.tsx
index 9722c99990e3f..9d2b4bba22afb 100644
--- a/x-pack/plugins/apm/public/components/app/Settings/ApmIndices/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/Settings/ApmIndices/index.tsx
@@ -24,7 +24,10 @@ import React, { useEffect, useState } from 'react';
import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context';
import { useFetcher } from '../../../../hooks/use_fetcher';
import { clearCache } from '../../../../services/rest/callApi';
-import { callApmApi } from '../../../../services/rest/createCallApmApi';
+import {
+ APIReturnType,
+ callApmApi,
+} from '../../../../services/rest/createCallApmApi';
const APM_INDEX_LABELS = [
{
@@ -84,8 +87,10 @@ async function saveApmIndices({
clearCache();
}
+type ApiResponse = APIReturnType<`GET /api/apm/settings/apm-index-settings`>;
+
// avoid infinite loop by initializing the state outside the component
-const INITIAL_STATE = [] as [];
+const INITIAL_STATE: ApiResponse = { apmIndexSettings: [] };
export function ApmIndices() {
const { core } = useApmPluginContext();
@@ -108,7 +113,7 @@ export function ApmIndices() {
useEffect(() => {
setApmIndices(
- data.reduce(
+ data.apmIndexSettings.reduce(
(acc, { configurationName, savedValue }) => ({
...acc,
[configurationName]: savedValue,
@@ -190,7 +195,7 @@ export function ApmIndices() {
{APM_INDEX_LABELS.map(({ configurationName, label }) => {
- const matchedConfiguration = data.find(
+ const matchedConfiguration = data.apmIndexSettings.find(
({ configurationName: configName }) =>
configName === configurationName
);
diff --git a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/index.test.tsx b/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/index.test.tsx
index 0dbc8f6235342..77835afef863a 100644
--- a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/index.test.tsx
+++ b/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/index.test.tsx
@@ -24,20 +24,12 @@ import {
} from '../../../../../utils/testHelpers';
import * as saveCustomLink from './CreateEditCustomLinkFlyout/saveCustomLink';
-const data = [
- {
- id: '1',
- label: 'label 1',
- url: 'url 1',
- 'service.name': 'opbeans-java',
- },
- {
- id: '2',
- label: 'label 2',
- url: 'url 2',
- 'transaction.type': 'request',
- },
-];
+const data = {
+ customLinks: [
+ { id: '1', label: 'label 1', url: 'url 1', 'service.name': 'opbeans-java' },
+ { id: '2', label: 'label 2', url: 'url 2', 'transaction.type': 'request' },
+ ],
+};
function getMockAPMContext({ canSave }: { canSave: boolean }) {
return ({
@@ -69,7 +61,7 @@ describe('CustomLink', () => {
describe('empty prompt', () => {
beforeAll(() => {
jest.spyOn(hooks, 'useFetcher').mockReturnValue({
- data: [],
+ data: { customLinks: [] },
status: hooks.FETCH_STATUS.SUCCESS,
refetch: jest.fn(),
});
@@ -290,7 +282,7 @@ describe('CustomLink', () => {
describe('invalid license', () => {
beforeAll(() => {
jest.spyOn(hooks, 'useFetcher').mockReturnValue({
- data: [],
+ data: { customLinks: [] },
status: hooks.FETCH_STATUS.SUCCESS,
refetch: jest.fn(),
});
diff --git a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/index.tsx b/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/index.tsx
index 4b4bc2e8feeab..49fa3eab47862 100644
--- a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/index.tsx
@@ -35,7 +35,7 @@ export function CustomLinkOverview() {
CustomLink | undefined
>();
- const { data: customLinks = [], status, refetch } = useFetcher(
+ const { data, status, refetch } = useFetcher(
async (callApmApi) => {
if (hasValidLicense) {
return callApmApi({
@@ -46,6 +46,8 @@ export function CustomLinkOverview() {
[hasValidLicense]
);
+ const customLinks = data?.customLinks ?? [];
+
useEffect(() => {
if (customLinkSelected) {
setIsFlyoutOpen(true);
diff --git a/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/add_environments.tsx b/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/add_environments.tsx
index 6a11f862994e2..bf9062418313a 100644
--- a/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/add_environments.tsx
+++ b/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/add_environments.tsx
@@ -21,6 +21,7 @@ import {
EuiEmptyPrompt,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
+import { APIReturnType } from '../../../../services/rest/createCallApmApi';
import { ML_ERRORS } from '../../../../../common/anomaly_detection';
import { useFetcher, FETCH_STATUS } from '../../../../hooks/use_fetcher';
import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context';
@@ -33,6 +34,10 @@ interface Props {
onCreateJobSuccess: () => void;
onCancel: () => void;
}
+
+type ApiResponse = APIReturnType<'GET /api/apm/settings/anomaly-detection/environments'>;
+const INITIAL_DATA: ApiResponse = { environments: [] };
+
export function AddEnvironments({
currentEnvironments,
onCreateJobSuccess,
@@ -42,7 +47,7 @@ export function AddEnvironments({
const { anomalyDetectionJobsRefetch } = useAnomalyDetectionJobsContext();
const canCreateJob = !!application.capabilities.ml.canCreateJob;
const { toasts } = notifications;
- const { data = [], status } = useFetcher(
+ const { data = INITIAL_DATA, status } = useFetcher(
(callApmApi) =>
callApmApi({
endpoint: `GET /api/apm/settings/anomaly-detection/environments`,
@@ -51,7 +56,7 @@ export function AddEnvironments({
{ preservePreviousData: false }
);
- const environmentOptions = data.map((env) => ({
+ const environmentOptions = data.environments.map((env) => ({
label: getEnvironmentLabel(env),
value: env,
disabled: currentEnvironments.includes(env),
diff --git a/x-pack/plugins/apm/public/components/app/error_group_overview/List/index.tsx b/x-pack/plugins/apm/public/components/app/error_group_overview/List/index.tsx
index 66fb72975acea..f31354bc7aa3c 100644
--- a/x-pack/plugins/apm/public/components/app/error_group_overview/List/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/error_group_overview/List/index.tsx
@@ -49,10 +49,10 @@ const Culprit = euiStyled.div`
font-family: ${fontFamilyCode};
`;
-type ErrorGroupListAPIResponse = APIReturnType<'GET /api/apm/services/{serviceName}/errors'>;
+type ErrorGroupItem = APIReturnType<'GET /api/apm/services/{serviceName}/errors'>['errorGroups'][0];
interface Props {
- items: ErrorGroupListAPIResponse;
+ items: ErrorGroupItem[];
serviceName: string;
}
@@ -128,7 +128,7 @@ function ErrorGroupList({ items, serviceName }: Props) {
field: 'message',
sortable: false,
width: '50%',
- render: (message: string, item: ErrorGroupListAPIResponse[0]) => {
+ render: (message: string, item: ErrorGroupItem) => {
return (
diff --git a/x-pack/plugins/apm/public/components/app/service_node_overview/index.tsx b/x-pack/plugins/apm/public/components/app/service_node_overview/index.tsx
index f4870439fe478..fc218f3ba6df3 100644
--- a/x-pack/plugins/apm/public/components/app/service_node_overview/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/service_node_overview/index.tsx
@@ -39,7 +39,7 @@ function ServiceNodeOverview({ serviceName }: ServiceNodeOverviewProps) {
urlParams: { kuery, start, end },
} = useUrlParams();
- const { data: items = [] } = useFetcher(
+ const { data } = useFetcher(
(callApmApi) => {
if (!start || !end) {
return undefined;
@@ -61,6 +61,7 @@ function ServiceNodeOverview({ serviceName }: ServiceNodeOverviewProps) {
[kuery, serviceName, start, end]
);
+ const items = data?.serviceNodes ?? [];
const columns: Array> = [
{
name: (
diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview.test.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview.test.tsx
index f6ffec46f9f51..b30faac7a65af 100644
--- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview.test.tsx
+++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview.test.tsx
@@ -91,7 +91,7 @@ describe('ServiceOverview', () => {
isAggregationAccurate: true,
},
'GET /api/apm/services/{serviceName}/dependencies': [],
- 'GET /api/apm/services/{serviceName}/service_overview_instances': [],
+ 'GET /api/apm/services/{serviceName}/service_overview_instances/primary_statistics': [],
};
/* eslint-enable @typescript-eslint/naming-convention */
diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_dependencies_table/index.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_dependencies_table/index.tsx
index a4647bc148b1e..4ff42b151dc8e 100644
--- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_dependencies_table/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_dependencies_table/index.tsx
@@ -164,7 +164,7 @@ export function ServiceOverviewDependenciesTable({ serviceName }: Props) {
},
];
- const { data = [], status } = useFetcher(
+ const { data, status } = useFetcher(
(callApmApi) => {
if (!start || !end) {
return;
@@ -188,8 +188,10 @@ export function ServiceOverviewDependenciesTable({ serviceName }: Props) {
[start, end, serviceName, environment]
);
+ const serviceDependencies = data?.serviceDependencies ?? [];
+
// need top-level sortable fields for the managed table
- const items = data.map((item) => ({
+ const items = serviceDependencies.map((item) => ({
...item,
errorRateValue: item.errorRate.value,
latencyValue: item.latency.value,
diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_chart_and_table.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_chart_and_table.tsx
index 435def8bb9a91..13322b094c65e 100644
--- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_chart_and_table.tsx
+++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_chart_and_table.tsx
@@ -6,11 +6,18 @@
*/
import { EuiFlexItem, EuiPanel } from '@elastic/eui';
-import React from 'react';
+import { orderBy } from 'lodash';
+import React, { useState } from 'react';
+import uuid from 'uuid';
import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context';
import { useUrlParams } from '../../../context/url_params_context/use_url_params';
-import { useFetcher } from '../../../hooks/use_fetcher';
-import { ServiceOverviewInstancesTable } from './service_overview_instances_table';
+import { FETCH_STATUS, useFetcher } from '../../../hooks/use_fetcher';
+import { APIReturnType } from '../../../services/rest/createCallApmApi';
+import { getTimeRangeComparison } from '../../shared/time_comparison/get_time_range_comparison';
+import {
+ ServiceOverviewInstancesTable,
+ TableOptions,
+} from './service_overview_instances_table';
// We're hiding this chart until these issues are resolved in the 7.13 timeframe:
//
@@ -24,17 +31,77 @@ interface ServiceOverviewInstancesChartAndTableProps {
serviceName: string;
}
+export interface PrimaryStatsServiceInstanceItem {
+ serviceNodeName: string;
+ errorRate: number;
+ throughput: number;
+ latency: number;
+ cpuUsage: number;
+ memoryUsage: number;
+}
+
+const INITIAL_STATE_PRIMARY_STATS = {
+ primaryStatsItems: [] as PrimaryStatsServiceInstanceItem[],
+ primaryStatsRequestId: undefined,
+ primaryStatsItemCount: 0,
+};
+
+type ApiResponseComparisonStats = APIReturnType<'GET /api/apm/services/{serviceName}/service_overview_instances/comparison_statistics'>;
+
+const INITIAL_STATE_COMPARISON_STATISTICS: ApiResponseComparisonStats = {
+ currentPeriod: {},
+ previousPeriod: {},
+};
+
+export type SortField =
+ | 'serviceNodeName'
+ | 'latency'
+ | 'throughput'
+ | 'errorRate'
+ | 'cpuUsage'
+ | 'memoryUsage';
+
+export type SortDirection = 'asc' | 'desc';
+export const PAGE_SIZE = 5;
+const DEFAULT_SORT = {
+ direction: 'desc' as const,
+ field: 'throughput' as const,
+};
+
export function ServiceOverviewInstancesChartAndTable({
chartHeight,
serviceName,
}: ServiceOverviewInstancesChartAndTableProps) {
const { transactionType } = useApmServiceContext();
+ const [tableOptions, setTableOptions] = useState({
+ pageIndex: 0,
+ sort: DEFAULT_SORT,
+ });
+
+ const { pageIndex, sort } = tableOptions;
+ const { direction, field } = sort;
const {
- urlParams: { environment, kuery, latencyAggregationType, start, end },
+ urlParams: {
+ environment,
+ kuery,
+ latencyAggregationType,
+ start,
+ end,
+ comparisonType,
+ },
} = useUrlParams();
- const { data = [], status } = useFetcher(
+ const { comparisonStart, comparisonEnd } = getTimeRangeComparison({
+ start,
+ end,
+ comparisonType,
+ });
+
+ const {
+ data: primaryStatsData = INITIAL_STATE_PRIMARY_STATS,
+ status: primaryStatsStatus,
+ } = useFetcher(
(callApmApi) => {
if (!start || !end || !transactionType || !latencyAggregationType) {
return;
@@ -42,7 +109,7 @@ export function ServiceOverviewInstancesChartAndTable({
return callApmApi({
endpoint:
- 'GET /api/apm/services/{serviceName}/service_overview_instances',
+ 'GET /api/apm/services/{serviceName}/service_overview_instances/primary_statistics',
params: {
path: {
serviceName,
@@ -54,11 +121,32 @@ export function ServiceOverviewInstancesChartAndTable({
start,
end,
transactionType,
- numBuckets: 20,
},
},
+ }).then((response) => {
+ const primaryStatsItems = orderBy(
+ // need top-level sortable fields for the managed table
+ response.serviceInstances.map((item) => ({
+ ...item,
+ latency: item.latency ?? 0,
+ throughput: item.throughput ?? 0,
+ errorRate: item.errorRate ?? 0,
+ cpuUsage: item.cpuUsage ?? 0,
+ memoryUsage: item.memoryUsage ?? 0,
+ })),
+ field,
+ direction
+ ).slice(pageIndex * PAGE_SIZE, (pageIndex + 1) * PAGE_SIZE);
+
+ return {
+ primaryStatsRequestId: uuid(),
+ primaryStatsItems,
+ primaryStatsItemCount: response.serviceInstances.length,
+ };
});
},
+ // comparisonType is listed as dependency even thought it is not used. This is needed to trigger the comparison api when it is changed.
+ // eslint-disable-next-line react-hooks/exhaustive-deps
[
environment,
kuery,
@@ -67,24 +155,97 @@ export function ServiceOverviewInstancesChartAndTable({
end,
serviceName,
transactionType,
+ pageIndex,
+ field,
+ direction,
+ comparisonType,
]
);
+ const {
+ primaryStatsItems,
+ primaryStatsRequestId,
+ primaryStatsItemCount,
+ } = primaryStatsData;
+
+ const {
+ data: comparisonStatsData = INITIAL_STATE_COMPARISON_STATISTICS,
+ status: comparisonStatisticsStatus,
+ } = useFetcher(
+ (callApmApi) => {
+ if (
+ !start ||
+ !end ||
+ !transactionType ||
+ !latencyAggregationType ||
+ !primaryStatsItemCount
+ ) {
+ return;
+ }
+
+ return callApmApi({
+ endpoint:
+ 'GET /api/apm/services/{serviceName}/service_overview_instances/comparison_statistics',
+ params: {
+ path: {
+ serviceName,
+ },
+ query: {
+ environment,
+ kuery,
+ latencyAggregationType,
+ start,
+ end,
+ numBuckets: 20,
+ transactionType,
+ serviceNodeIds: JSON.stringify(
+ primaryStatsItems.map((item) => item.serviceNodeName)
+ ),
+ comparisonStart,
+ comparisonEnd,
+ },
+ },
+ });
+ },
+ // only fetches comparison statistics when requestId is invalidated by primary statistics api call
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ [primaryStatsRequestId],
+ { preservePreviousData: false }
+ );
+
return (
<>
{/*
*/}
{
+ setTableOptions({
+ pageIndex: newTableOptions.page?.index ?? 0,
+ sort: newTableOptions.sort
+ ? {
+ field: newTableOptions.sort.field as SortField,
+ direction: newTableOptions.sort.direction,
+ }
+ : DEFAULT_SORT,
+ });
+ }}
/>
diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/get_columns.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/get_columns.tsx
new file mode 100644
index 0000000000000..b88172a162063
--- /dev/null
+++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/get_columns.tsx
@@ -0,0 +1,211 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { EuiBasicTableColumn } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import React from 'react';
+import { LatencyAggregationType } from '../../../../../common/latency_aggregation_types';
+import { isJavaAgentName } from '../../../../../common/agent_name';
+import { UNIDENTIFIED_SERVICE_NODES_LABEL } from '../../../../../common/i18n';
+import { SERVICE_NODE_NAME_MISSING } from '../../../../../common/service_nodes';
+import {
+ asMillisecondDuration,
+ asPercent,
+ asTransactionRate,
+} from '../../../../../common/utils/formatters';
+import { APIReturnType } from '../../../../services/rest/createCallApmApi';
+import { px, unit } from '../../../../style/variables';
+import { SparkPlot } from '../../../shared/charts/spark_plot';
+import { MetricOverviewLink } from '../../../shared/Links/apm/MetricOverviewLink';
+import { ServiceNodeMetricOverviewLink } from '../../../shared/Links/apm/ServiceNodeMetricOverviewLink';
+import { TruncateWithTooltip } from '../../../shared/truncate_with_tooltip';
+import { getLatencyColumnLabel } from '../get_latency_column_label';
+import { PrimaryStatsServiceInstanceItem } from '../service_overview_instances_chart_and_table';
+
+type ServiceInstanceComparisonStatistics = APIReturnType<'GET /api/apm/services/{serviceName}/service_overview_instances/comparison_statistics'>;
+
+export function getColumns({
+ serviceName,
+ agentName,
+ latencyAggregationType,
+ comparisonStatsData,
+ comparisonEnabled,
+}: {
+ serviceName: string;
+ agentName?: string;
+ latencyAggregationType?: LatencyAggregationType;
+ comparisonStatsData?: ServiceInstanceComparisonStatistics;
+ comparisonEnabled?: boolean;
+}): Array> {
+ return [
+ {
+ field: 'serviceNodeName',
+ name: i18n.translate(
+ 'xpack.apm.serviceOverview.instancesTableColumnNodeName',
+ { defaultMessage: 'Node name' }
+ ),
+ render: (_, item) => {
+ const { serviceNodeName } = item;
+ const isMissingServiceNodeName =
+ serviceNodeName === SERVICE_NODE_NAME_MISSING;
+ const text = isMissingServiceNodeName
+ ? UNIDENTIFIED_SERVICE_NODES_LABEL
+ : serviceNodeName;
+
+ const link = isJavaAgentName(agentName) ? (
+
+ {text}
+
+ ) : (
+ ({
+ ...query,
+ kuery: isMissingServiceNodeName
+ ? `NOT (service.node.name:*)`
+ : `service.node.name:"${item.serviceNodeName}"`,
+ })}
+ >
+ {text}
+
+ );
+
+ return ;
+ },
+ sortable: true,
+ },
+ {
+ field: 'latencyValue',
+ name: getLatencyColumnLabel(latencyAggregationType),
+ width: px(unit * 10),
+ render: (_, { serviceNodeName, latency }) => {
+ const currentPeriodTimestamp =
+ comparisonStatsData?.currentPeriod?.[serviceNodeName]?.latency;
+ const previousPeriodTimestamp =
+ comparisonStatsData?.previousPeriod?.[serviceNodeName]?.latency;
+ return (
+
+ );
+ },
+ sortable: true,
+ },
+ {
+ field: 'throughputValue',
+ name: i18n.translate(
+ 'xpack.apm.serviceOverview.instancesTableColumnThroughput',
+ { defaultMessage: 'Throughput' }
+ ),
+ width: px(unit * 10),
+ render: (_, { serviceNodeName, throughput }) => {
+ const currentPeriodTimestamp =
+ comparisonStatsData?.currentPeriod?.[serviceNodeName]?.throughput;
+ const previousPeriodTimestamp =
+ comparisonStatsData?.previousPeriod?.[serviceNodeName]?.throughput;
+ return (
+
+ );
+ },
+ sortable: true,
+ },
+ {
+ field: 'errorRateValue',
+ name: i18n.translate(
+ 'xpack.apm.serviceOverview.instancesTableColumnErrorRate',
+ { defaultMessage: 'Error rate' }
+ ),
+ width: px(unit * 8),
+ render: (_, { serviceNodeName, errorRate }) => {
+ const currentPeriodTimestamp =
+ comparisonStatsData?.currentPeriod?.[serviceNodeName]?.errorRate;
+ const previousPeriodTimestamp =
+ comparisonStatsData?.previousPeriod?.[serviceNodeName]?.errorRate;
+ return (
+
+ );
+ },
+ sortable: true,
+ },
+ {
+ field: 'cpuUsageValue',
+ name: i18n.translate(
+ 'xpack.apm.serviceOverview.instancesTableColumnCpuUsage',
+ { defaultMessage: 'CPU usage (avg.)' }
+ ),
+ width: px(unit * 8),
+ render: (_, { serviceNodeName, cpuUsage }) => {
+ const currentPeriodTimestamp =
+ comparisonStatsData?.currentPeriod?.[serviceNodeName]?.cpuUsage;
+ const previousPeriodTimestamp =
+ comparisonStatsData?.previousPeriod?.[serviceNodeName]?.cpuUsage;
+ return (
+
+ );
+ },
+ sortable: true,
+ },
+ {
+ field: 'memoryUsageValue',
+ name: i18n.translate(
+ 'xpack.apm.serviceOverview.instancesTableColumnMemoryUsage',
+ { defaultMessage: 'Memory usage (avg.)' }
+ ),
+ width: px(unit * 9),
+ render: (_, { serviceNodeName, memoryUsage }) => {
+ const currentPeriodTimestamp =
+ comparisonStatsData?.currentPeriod?.[serviceNodeName]?.memoryUsage;
+ const previousPeriodTimestamp =
+ comparisonStatsData?.previousPeriod?.[serviceNodeName]?.memoryUsage;
+ return (
+
+ );
+ },
+ sortable: true,
+ },
+ ];
+}
diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/index.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/index.tsx
index 83ad506e8659b..28654acbefa46 100644
--- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/index.tsx
@@ -6,208 +6,82 @@
*/
import {
- EuiBasicTableColumn,
+ EuiBasicTable,
EuiFlexGroup,
EuiFlexItem,
- EuiInMemoryTable,
EuiTitle,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React from 'react';
-import { ValuesType } from 'utility-types';
-import { isJavaAgentName } from '../../../../../common/agent_name';
-import { UNIDENTIFIED_SERVICE_NODES_LABEL } from '../../../../../common/i18n';
-import { SERVICE_NODE_NAME_MISSING } from '../../../../../common/service_nodes';
-import {
- asMillisecondDuration,
- asPercent,
- asTransactionRate,
-} from '../../../../../common/utils/formatters';
import { useApmServiceContext } from '../../../../context/apm_service/use_apm_service_context';
import { useUrlParams } from '../../../../context/url_params_context/use_url_params';
import { FETCH_STATUS } from '../../../../hooks/use_fetcher';
import { APIReturnType } from '../../../../services/rest/createCallApmApi';
-import { px, unit } from '../../../../style/variables';
-import { SparkPlot } from '../../../shared/charts/spark_plot';
-import { MetricOverviewLink } from '../../../shared/Links/apm/MetricOverviewLink';
-import { ServiceNodeMetricOverviewLink } from '../../../shared/Links/apm/ServiceNodeMetricOverviewLink';
import { TableFetchWrapper } from '../../../shared/table_fetch_wrapper';
-import { TruncateWithTooltip } from '../../../shared/truncate_with_tooltip';
-import { getLatencyColumnLabel } from '../get_latency_column_label';
+import {
+ PAGE_SIZE,
+ PrimaryStatsServiceInstanceItem,
+ SortDirection,
+ SortField,
+} from '../service_overview_instances_chart_and_table';
import { ServiceOverviewTableContainer } from '../service_overview_table_container';
+import { getColumns } from './get_columns';
+
+type ServiceInstanceComparisonStatistics = APIReturnType<'GET /api/apm/services/{serviceName}/service_overview_instances/comparison_statistics'>;
-type ServiceInstanceItem = ValuesType<
- APIReturnType<'GET /api/apm/services/{serviceName}/service_overview_instances'>
->;
+export interface TableOptions {
+ pageIndex: number;
+ sort: {
+ direction: SortDirection;
+ field: SortField;
+ };
+}
interface Props {
- items?: ServiceInstanceItem[];
+ primaryStatsItems: PrimaryStatsServiceInstanceItem[];
serviceName: string;
- status: FETCH_STATUS;
+ primaryStatsStatus: FETCH_STATUS;
+ primaryStatsItemCount: number;
+ tableOptions: TableOptions;
+ onChangeTableOptions: (newTableOptions: {
+ page?: { index: number };
+ sort?: { field: string; direction: SortDirection };
+ }) => void;
+ comparisonStatsData?: ServiceInstanceComparisonStatistics;
+ isLoading: boolean;
}
-
export function ServiceOverviewInstancesTable({
- items = [],
+ primaryStatsItems = [],
+ primaryStatsItemCount,
serviceName,
- status,
+ primaryStatsStatus: status,
+ tableOptions,
+ onChangeTableOptions,
+ comparisonStatsData: comparisonStatsData,
+ isLoading,
}: Props) {
const { agentName } = useApmServiceContext();
const {
- urlParams: { latencyAggregationType },
+ urlParams: { latencyAggregationType, comparisonEnabled },
} = useUrlParams();
- const columns: Array> = [
- {
- field: 'name',
- name: i18n.translate(
- 'xpack.apm.serviceOverview.instancesTableColumnNodeName',
- {
- defaultMessage: 'Node name',
- }
- ),
- render: (_, item) => {
- const { serviceNodeName } = item;
- const isMissingServiceNodeName =
- serviceNodeName === SERVICE_NODE_NAME_MISSING;
- const text = isMissingServiceNodeName
- ? UNIDENTIFIED_SERVICE_NODES_LABEL
- : serviceNodeName;
-
- const link = isJavaAgentName(agentName) ? (
-
- {text}
-
- ) : (
- ({
- ...query,
- kuery: isMissingServiceNodeName
- ? `NOT (service.node.name:*)`
- : `service.node.name:"${item.serviceNodeName}"`,
- })}
- >
- {text}
-
- );
+ const { pageIndex, sort } = tableOptions;
+ const { direction, field } = sort;
- return ;
- },
- sortable: true,
- },
- {
- field: 'latencyValue',
- name: getLatencyColumnLabel(latencyAggregationType),
- width: px(unit * 10),
- render: (_, { latency }) => {
- return (
-
- );
- },
- sortable: true,
- },
- {
- field: 'throughputValue',
- name: i18n.translate(
- 'xpack.apm.serviceOverview.instancesTableColumnThroughput',
- { defaultMessage: 'Throughput' }
- ),
- width: px(unit * 10),
- render: (_, { throughput }) => {
- return (
-
- );
- },
- sortable: true,
- },
- {
- field: 'errorRateValue',
- name: i18n.translate(
- 'xpack.apm.serviceOverview.instancesTableColumnErrorRate',
- {
- defaultMessage: 'Error rate',
- }
- ),
- width: px(unit * 8),
- render: (_, { errorRate }) => {
- return (
-
- );
- },
- sortable: true,
- },
- {
- field: 'cpuUsageValue',
- name: i18n.translate(
- 'xpack.apm.serviceOverview.instancesTableColumnCpuUsage',
- {
- defaultMessage: 'CPU usage (avg.)',
- }
- ),
- width: px(unit * 8),
- render: (_, { cpuUsage }) => {
- return (
-
- );
- },
- sortable: true,
- },
- {
- field: 'memoryUsageValue',
- name: i18n.translate(
- 'xpack.apm.serviceOverview.instancesTableColumnMemoryUsage',
- {
- defaultMessage: 'Memory usage (avg.)',
- }
- ),
- width: px(unit * 9),
- render: (_, { memoryUsage }) => {
- return (
-
- );
- },
- sortable: true,
- },
- ];
+ const columns = getColumns({
+ agentName,
+ serviceName,
+ latencyAggregationType,
+ comparisonStatsData,
+ comparisonEnabled,
+ });
- // need top-level sortable fields for the managed table
- const tableItems = items.map((item) => ({
- ...item,
- latencyValue: item.latency?.value ?? 0,
- throughputValue: item.throughput?.value ?? 0,
- errorRateValue: item.errorRate?.value ?? 0,
- cpuUsageValue: item.cpuUsage?.value ?? 0,
- memoryUsageValue: item.memoryUsage?.value ?? 0,
- }));
-
- const isLoading = status === FETCH_STATUS.LOADING;
+ const pagination = {
+ pageIndex,
+ pageSize: PAGE_SIZE,
+ totalItemCount: primaryStatsItemCount,
+ hidePerPageOptions: true,
+ };
return (
@@ -223,24 +97,15 @@ export function ServiceOverviewInstancesTable({
-
diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/index.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/index.tsx
index 02f60eab2cb88..121b96b0361b2 100644
--- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/index.tsx
@@ -15,6 +15,7 @@ import { i18n } from '@kbn/i18n';
import { orderBy } from 'lodash';
import React, { useState } from 'react';
import uuid from 'uuid';
+import { APIReturnType } from '../../../../services/rest/createCallApmApi';
import { useApmServiceContext } from '../../../../context/apm_service/use_apm_service_context';
import { useUrlParams } from '../../../../context/url_params_context/use_url_params';
import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher';
@@ -28,8 +29,9 @@ interface Props {
serviceName: string;
}
+type ApiResponse = APIReturnType<'GET /api/apm/services/{serviceName}/transactions/groups/primary_statistics'>;
const INITIAL_STATE = {
- transactionGroups: [],
+ transactionGroups: [] as ApiResponse['transactionGroups'],
isAggregationAccurate: true,
requestId: '',
transactionGroupsTotalItems: 0,
diff --git a/x-pack/plugins/apm/public/components/app/service_profiling/index.tsx b/x-pack/plugins/apm/public/components/app/service_profiling/index.tsx
index 23adbb23b2322..94391b5b2fb06 100644
--- a/x-pack/plugins/apm/public/components/app/service_profiling/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/service_profiling/index.tsx
@@ -19,6 +19,7 @@ import {
} from '../../../../common/profiling';
import { useUrlParams } from '../../../context/url_params_context/use_url_params';
import { useFetcher } from '../../../hooks/use_fetcher';
+import { APIReturnType } from '../../../services/rest/createCallApmApi';
import { SearchBar } from '../../shared/search_bar';
import { ServiceProfilingFlamegraph } from './service_profiling_flamegraph';
import { ServiceProfilingTimeline } from './service_profiling_timeline';
@@ -28,6 +29,9 @@ interface ServiceProfilingProps {
environment?: string;
}
+type ApiResponse = APIReturnType<'GET /api/apm/services/{serviceName}/profiling/timeline'>;
+const DEFAULT_DATA: ApiResponse = { profilingTimeline: [] };
+
export function ServiceProfiling({
serviceName,
environment,
@@ -36,7 +40,7 @@ export function ServiceProfiling({
urlParams: { kuery, start, end },
} = useUrlParams();
- const { data = [] } = useFetcher(
+ const { data = DEFAULT_DATA } = useFetcher(
(callApmApi) => {
if (!start || !end) {
return;
@@ -58,14 +62,16 @@ export function ServiceProfiling({
[kuery, start, end, serviceName, environment]
);
+ const { profilingTimeline } = data;
+
const [valueType, setValueType] = useState();
useEffect(() => {
- if (!data.length) {
+ if (!profilingTimeline.length) {
return;
}
- const availableValueTypes = data.reduce((set, point) => {
+ const availableValueTypes = profilingTimeline.reduce((set, point) => {
(Object.keys(point.valueTypes).filter(
(type) => type !== 'unknown'
) as ProfilingValueType[])
@@ -80,7 +86,7 @@ export function ServiceProfiling({
if (!valueType || !availableValueTypes.has(valueType)) {
setValueType(Array.from(availableValueTypes)[0]);
}
- }, [data, valueType]);
+ }, [profilingTimeline, valueType]);
return (
<>
@@ -103,7 +109,7 @@ export function ServiceProfiling({
{
setValueType(type);
}}
diff --git a/x-pack/plugins/apm/public/components/shared/ApmHeader/apm_header.stories.tsx b/x-pack/plugins/apm/public/components/shared/ApmHeader/apm_header.stories.tsx
index 3cd858aceaa90..4bc9764b704b0 100644
--- a/x-pack/plugins/apm/public/components/shared/ApmHeader/apm_header.stories.tsx
+++ b/x-pack/plugins/apm/public/components/shared/ApmHeader/apm_header.stories.tsx
@@ -8,7 +8,7 @@
import { EuiTitle } from '@elastic/eui';
import React, { ComponentType } from 'react';
import { MemoryRouter } from 'react-router-dom';
-import { HttpSetup } from '../../../../../../../src/core/public';
+import { CoreStart } from '../../../../../../../src/core/public';
import { EuiThemeProvider } from '../../../../../../../src/plugins/kibana_react/common';
import { MockApmPluginContextWrapper } from '../../../context/apm_plugin/mock_apm_plugin_context';
import { MockUrlParamsContextProvider } from '../../../context/url_params_context/mock_url_params_context_provider';
@@ -20,7 +20,7 @@ export default {
component: ApmHeader,
decorators: [
(Story: ComponentType) => {
- createCallApmApi(({} as unknown) as HttpSetup);
+ createCallApmApi(({} as unknown) as CoreStart);
return (
diff --git a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/CustomLinkMenuSection/index.test.tsx b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/CustomLinkMenuSection/index.test.tsx
index 6f2910a2a5ef7..a624c220a0e4c 100644
--- a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/CustomLinkMenuSection/index.test.tsx
+++ b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/CustomLinkMenuSection/index.test.tsx
@@ -9,7 +9,6 @@ import { act, fireEvent, render } from '@testing-library/react';
import React, { ReactNode } from 'react';
import { MemoryRouter } from 'react-router-dom';
import { CustomLinkMenuSection } from '.';
-import { CustomLink as CustomLinkType } from '../../../../../common/custom_link/custom_link_types';
import { Transaction } from '../../../../../typings/es_schemas/ui/transaction';
import { MockApmPluginContextWrapper } from '../../../../context/apm_plugin/mock_apm_plugin_context';
import * as useFetcher from '../../../../hooks/use_fetcher';
@@ -40,7 +39,7 @@ const transaction = ({
describe('Custom links', () => {
it('shows empty message when no custom link is available', () => {
jest.spyOn(useFetcher, 'useFetcher').mockReturnValue({
- data: [],
+ data: { customLinks: [] },
status: useFetcher.FETCH_STATUS.SUCCESS,
refetch: jest.fn(),
});
@@ -58,7 +57,7 @@ describe('Custom links', () => {
it('shows loading while custom links are fetched', () => {
jest.spyOn(useFetcher, 'useFetcher').mockReturnValue({
- data: [],
+ data: { customLinks: [] },
status: useFetcher.FETCH_STATUS.LOADING,
refetch: jest.fn(),
});
@@ -71,12 +70,14 @@ describe('Custom links', () => {
});
it('shows first 3 custom links available', () => {
- const customLinks = [
- { id: '1', label: 'foo', url: 'foo' },
- { id: '2', label: 'bar', url: 'bar' },
- { id: '3', label: 'baz', url: 'baz' },
- { id: '4', label: 'qux', url: 'qux' },
- ] as CustomLinkType[];
+ const customLinks = {
+ customLinks: [
+ { id: '1', label: 'foo', url: 'foo' },
+ { id: '2', label: 'bar', url: 'bar' },
+ { id: '3', label: 'baz', url: 'baz' },
+ { id: '4', label: 'qux', url: 'qux' },
+ ],
+ };
jest.spyOn(useFetcher, 'useFetcher').mockReturnValue({
data: customLinks,
@@ -93,15 +94,17 @@ describe('Custom links', () => {
});
it('clicks "show all" and "show fewer"', () => {
- const customLinks = [
- { id: '1', label: 'foo', url: 'foo' },
- { id: '2', label: 'bar', url: 'bar' },
- { id: '3', label: 'baz', url: 'baz' },
- { id: '4', label: 'qux', url: 'qux' },
- ] as CustomLinkType[];
+ const data = {
+ customLinks: [
+ { id: '1', label: 'foo', url: 'foo' },
+ { id: '2', label: 'bar', url: 'bar' },
+ { id: '3', label: 'baz', url: 'baz' },
+ { id: '4', label: 'qux', url: 'qux' },
+ ],
+ };
jest.spyOn(useFetcher, 'useFetcher').mockReturnValue({
- data: customLinks,
+ data,
status: useFetcher.FETCH_STATUS.SUCCESS,
refetch: jest.fn(),
});
@@ -125,7 +128,7 @@ describe('Custom links', () => {
describe('create custom link buttons', () => {
it('shows create button below empty message', () => {
jest.spyOn(useFetcher, 'useFetcher').mockReturnValue({
- data: [],
+ data: { customLinks: [] },
status: useFetcher.FETCH_STATUS.SUCCESS,
refetch: jest.fn(),
});
@@ -140,15 +143,17 @@ describe('Custom links', () => {
});
it('shows create button besides the title', () => {
- const customLinks = [
- { id: '1', label: 'foo', url: 'foo' },
- { id: '2', label: 'bar', url: 'bar' },
- { id: '3', label: 'baz', url: 'baz' },
- { id: '4', label: 'qux', url: 'qux' },
- ] as CustomLinkType[];
+ const data = {
+ customLinks: [
+ { id: '1', label: 'foo', url: 'foo' },
+ { id: '2', label: 'bar', url: 'bar' },
+ { id: '3', label: 'baz', url: 'baz' },
+ { id: '4', label: 'qux', url: 'qux' },
+ ],
+ };
jest.spyOn(useFetcher, 'useFetcher').mockReturnValue({
- data: customLinks,
+ data,
status: useFetcher.FETCH_STATUS.SUCCESS,
refetch: jest.fn(),
});
diff --git a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/CustomLinkMenuSection/index.tsx b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/CustomLinkMenuSection/index.tsx
index 7d2e4a13278ec..cbbf34c78c4af 100644
--- a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/CustomLinkMenuSection/index.tsx
+++ b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/CustomLinkMenuSection/index.tsx
@@ -58,7 +58,7 @@ export function CustomLinkMenuSection({
[transaction]
);
- const { data: customLinks = [], status, refetch } = useFetcher(
+ const { data, status, refetch } = useFetcher(
(callApmApi) =>
callApmApi({
isCachable: false,
@@ -68,6 +68,8 @@ export function CustomLinkMenuSection({
[filters]
);
+ const customLinks = data?.customLinks ?? [];
+
return (
<>
{isCreateEditFlyoutOpen && (
diff --git a/x-pack/plugins/apm/public/components/shared/charts/helper/helper.test.ts b/x-pack/plugins/apm/public/components/shared/charts/helper/helper.test.ts
index b0ac35cc3667a..b8d67f71a9baa 100644
--- a/x-pack/plugins/apm/public/components/shared/charts/helper/helper.test.ts
+++ b/x-pack/plugins/apm/public/components/shared/charts/helper/helper.test.ts
@@ -7,7 +7,7 @@
import { onBrushEnd, isTimeseriesEmpty } from './helper';
import { History } from 'history';
-import { TimeSeries } from '../../../../../typings/timeseries';
+import { Coordinate, TimeSeries } from '../../../../../typings/timeseries';
describe('Chart helper', () => {
describe('onBrushEnd', () => {
@@ -52,7 +52,7 @@ describe('Chart helper', () => {
type: 'line',
color: 'red',
},
- ] as TimeSeries[];
+ ] as Array>;
expect(isTimeseriesEmpty(timeseries)).toBeTruthy();
});
it('returns true when y coordinate is null', () => {
@@ -63,7 +63,7 @@ describe('Chart helper', () => {
type: 'line',
color: 'red',
},
- ] as TimeSeries[];
+ ] as Array>;
expect(isTimeseriesEmpty(timeseries)).toBeTruthy();
});
it('returns true when y coordinate is undefined', () => {
@@ -74,7 +74,7 @@ describe('Chart helper', () => {
type: 'line',
color: 'red',
},
- ] as TimeSeries[];
+ ] as Array>;
expect(isTimeseriesEmpty(timeseries)).toBeTruthy();
});
it('returns false when at least one coordinate is filled', () => {
@@ -91,7 +91,7 @@ describe('Chart helper', () => {
type: 'line',
color: 'green',
},
- ] as TimeSeries[];
+ ] as Array>;
expect(isTimeseriesEmpty(timeseries)).toBeFalsy();
});
});
diff --git a/x-pack/plugins/apm/public/components/shared/charts/helper/helper.ts b/x-pack/plugins/apm/public/components/shared/charts/helper/helper.ts
index 3b93cb1f402e8..d94f2ce8f5c5d 100644
--- a/x-pack/plugins/apm/public/components/shared/charts/helper/helper.ts
+++ b/x-pack/plugins/apm/public/components/shared/charts/helper/helper.ts
@@ -7,7 +7,7 @@
import { XYBrushArea } from '@elastic/charts';
import { History } from 'history';
-import { TimeSeries } from '../../../../../typings/timeseries';
+import { Coordinate, TimeSeries } from '../../../../../typings/timeseries';
import { fromQuery, toQuery } from '../../Links/url_helpers';
export const onBrushEnd = ({
@@ -36,15 +36,12 @@ export const onBrushEnd = ({
}
};
-export function isTimeseriesEmpty(timeseries?: TimeSeries[]) {
+export function isTimeseriesEmpty(timeseries?: Array>) {
return (
!timeseries ||
timeseries
.map((serie) => serie.data)
.flat()
- .every(
- ({ y }: { x?: number | null; y?: number | null }) =>
- y === null || y === undefined
- )
+ .every(({ y }: Coordinate) => y === null || y === undefined)
);
}
diff --git a/x-pack/plugins/apm/public/components/shared/charts/instances_latency_distribution_chart/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/instances_latency_distribution_chart/index.tsx
index aa353b40d464a..5bcf0d161653e 100644
--- a/x-pack/plugins/apm/public/components/shared/charts/instances_latency_distribution_chart/index.tsx
+++ b/x-pack/plugins/apm/public/components/shared/charts/instances_latency_distribution_chart/index.tsx
@@ -23,13 +23,13 @@ import {
} from '../../../../../common/utils/formatters';
import { FETCH_STATUS } from '../../../../hooks/use_fetcher';
import { useTheme } from '../../../../hooks/use_theme';
-import { APIReturnType } from '../../../../services/rest/createCallApmApi';
+import { PrimaryStatsServiceInstanceItem } from '../../../app/service_overview/service_overview_instances_chart_and_table';
import { ChartContainer } from '../chart_container';
import { getResponseTimeTickFormatter } from '../transaction_charts/helper';
interface InstancesLatencyDistributionChartProps {
height: number;
- items?: APIReturnType<'GET /api/apm/services/{serviceName}/service_overview_instances'>;
+ items?: PrimaryStatsServiceInstanceItem[];
status: FETCH_STATUS;
}
@@ -44,15 +44,11 @@ export function InstancesLatencyDistributionChart({
const chartTheme = {
...useChartTheme(),
bubbleSeriesStyle: {
- point: {
- strokeWidth: 0,
- fill: theme.eui.euiColorVis1,
- radius: 4,
- },
+ point: { strokeWidth: 0, fill: theme.eui.euiColorVis1, radius: 4 },
},
};
- const maxLatency = Math.max(...items.map((item) => item.latency?.value ?? 0));
+ const maxLatency = Math.max(...items.map((item) => item.latency ?? 0));
const latencyFormatter = getDurationFormatter(maxLatency);
return (
@@ -79,9 +75,9 @@ export function InstancesLatencyDistributionChart({
'xpack.apm.instancesLatencyDistributionChartLegend',
{ defaultMessage: 'Instances' }
)}
- xAccessor={(item) => item.throughput?.value}
+ xAccessor={(item) => item.throughput}
xScaleType={ScaleType.Linear}
- yAccessors={[(item) => item.latency?.value]}
+ yAccessors={[(item) => item.latency]}
yScaleType={ScaleType.Linear}
/>
>;
/**
* Formatter for y-axis tick values
*/
@@ -85,12 +89,10 @@ export function TimeseriesChart({
const max = Math.max(...xValues);
const xFormatter = niceTimeFormatter([min, max]);
-
const isEmpty = isTimeseriesEmpty(timeseries);
-
const annotationColor = theme.eui.euiColorSecondary;
-
const allSeries = [...timeseries, ...(anomalyTimeseries?.boundaries ?? [])];
+ const xDomain = isEmpty ? { min: 0, max: 1 } : { min, max };
return (
@@ -111,7 +113,7 @@ export function TimeseriesChart({
showLegend
showLegendExtra
legendPosition={Position.Bottom}
- xDomain={{ min, max }}
+ xDomain={xDomain}
onLegendItemClick={(legend) => {
if (onToggleLegend) {
onToggleLegend(legend);
diff --git a/x-pack/plugins/apm/public/components/shared/charts/transaction_breakdown_chart/transaction_breakdown_chart_contents.tsx b/x-pack/plugins/apm/public/components/shared/charts/transaction_breakdown_chart/transaction_breakdown_chart_contents.tsx
index f55389ec2d5f7..23016cc5dd8e9 100644
--- a/x-pack/plugins/apm/public/components/shared/charts/transaction_breakdown_chart/transaction_breakdown_chart_contents.tsx
+++ b/x-pack/plugins/apm/public/components/shared/charts/transaction_breakdown_chart/transaction_breakdown_chart_contents.tsx
@@ -28,7 +28,7 @@ import {
asAbsoluteDateTime,
asPercent,
} from '../../../../../common/utils/formatters';
-import { TimeSeries } from '../../../../../typings/timeseries';
+import { Coordinate, TimeSeries } from '../../../../../typings/timeseries';
import { FETCH_STATUS } from '../../../../hooks/use_fetcher';
import { useTheme } from '../../../../hooks/use_theme';
import { useUrlParams } from '../../../../context/url_params_context/use_url_params';
@@ -42,7 +42,7 @@ interface Props {
fetchStatus: FETCH_STATUS;
height?: number;
showAnnotations: boolean;
- timeseries?: TimeSeries[];
+ timeseries?: Array>;
}
export function TransactionBreakdownChartContents({
diff --git a/x-pack/plugins/apm/public/components/shared/charts/transaction_charts/helper.tsx b/x-pack/plugins/apm/public/components/shared/charts/transaction_charts/helper.tsx
index 6c46580f4738e..31d18b7a9709d 100644
--- a/x-pack/plugins/apm/public/components/shared/charts/transaction_charts/helper.tsx
+++ b/x-pack/plugins/apm/public/components/shared/charts/transaction_charts/helper.tsx
@@ -6,14 +6,14 @@
*/
import { isFiniteNumber } from '../../../../../common/utils/is_finite_number';
-import { APMChartSpec, Coordinate } from '../../../../../typings/timeseries';
+import { Coordinate } from '../../../../../typings/timeseries';
import { TimeFormatter } from '../../../../../common/utils/formatters';
export function getResponseTimeTickFormatter(formatter: TimeFormatter) {
return (t: number) => formatter(t).formatted;
}
-export function getMaxY(specs?: Array>) {
+export function getMaxY(specs?: Array<{ data: Coordinate[] }>) {
const values = specs
?.flatMap((spec) => spec.data)
.map((coord) => coord.y)
diff --git a/x-pack/plugins/apm/public/components/shared/search_bar.tsx b/x-pack/plugins/apm/public/components/shared/search_bar.tsx
index 2bd3fef8c0e88..1018b9eca2119 100644
--- a/x-pack/plugins/apm/public/components/shared/search_bar.tsx
+++ b/x-pack/plugins/apm/public/components/shared/search_bar.tsx
@@ -7,14 +7,21 @@
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import React from 'react';
+import { i18n } from '@kbn/i18n';
+import { FormattedMessage } from '@kbn/i18n/react';
+import { EuiCallOut } from '@elastic/eui';
+import { EuiLink } from '@elastic/eui';
+import { enableInspectEsQueries } from '../../../../observability/public';
import { euiStyled } from '../../../../../../src/plugins/kibana_react/common';
import { px, unit } from '../../style/variables';
import { DatePicker } from './DatePicker';
import { KueryBar } from './KueryBar';
import { TimeComparison } from './time_comparison';
import { useBreakPoints } from '../../hooks/use_break_points';
+import { useKibanaUrl } from '../../hooks/useKibanaUrl';
+import { useApmPluginContext } from '../../context/apm_plugin/use_apm_plugin_context';
-const SearchBarFlexGroup = euiStyled(EuiFlexGroup)`
+const EuiFlexGroupSpaced = euiStyled(EuiFlexGroup)`
margin: ${({ theme }) =>
`${theme.eui.euiSizeS} ${theme.eui.euiSizeS} -${theme.eui.gutterTypes.gutterMedium} ${theme.eui.euiSizeS}`};
`;
@@ -29,6 +36,52 @@ function getRowDirection(showColumn: boolean) {
return showColumn ? 'column' : 'row';
}
+function DebugQueryCallout() {
+ const { uiSettings } = useApmPluginContext().core;
+ const advancedSettingsUrl = useKibanaUrl('/app/management/kibana/settings', {
+ query: {
+ query: 'category:(observability)',
+ },
+ });
+
+ if (!uiSettings.get(enableInspectEsQueries)) {
+ return null;
+ }
+
+ return (
+
+
+
+
+ {i18n.translate(
+ 'xpack.apm.searchBar.inspectEsQueriesEnabled.callout.description.advancedSettings',
+ { defaultMessage: 'Advanced Setting' }
+ )}
+
+ ),
+ }}
+ />
+
+
+
+ );
+}
+
export function SearchBar({
prepend,
showTimeComparison = false,
@@ -38,26 +91,29 @@ export function SearchBar({
const itemsStyle = { marginBottom: isLarge ? px(unit) : 0 };
return (
-
-
-
-
-
-
- {showTimeComparison && (
-
-
+ <>
+
+
+
+
+
+
+
+ {showTimeComparison && (
+
+
+
+ )}
+
+
- )}
-
-
-
-
-
-
+
+
+
+ >
);
}
diff --git a/x-pack/plugins/apm/public/context/apm_plugin/mock_apm_plugin_context.tsx b/x-pack/plugins/apm/public/context/apm_plugin/mock_apm_plugin_context.tsx
index 024deca558497..9a910787d5fe8 100644
--- a/x-pack/plugins/apm/public/context/apm_plugin/mock_apm_plugin_context.tsx
+++ b/x-pack/plugins/apm/public/context/apm_plugin/mock_apm_plugin_context.tsx
@@ -116,8 +116,8 @@ export function MockApmPluginContextWrapper({
children?: React.ReactNode;
value?: ApmPluginContextValue;
}) {
- if (value.core?.http) {
- createCallApmApi(value.core?.http);
+ if (value.core) {
+ createCallApmApi(value.core);
}
return (
{
if (start && end) {
return callApmApi({
@@ -51,9 +53,9 @@ export function useEnvironmentsFetcher({
);
const environmentOptions = useMemo(
- () => getEnvironmentOptions(environments),
- [environments]
+ () => getEnvironmentOptions(data.environments),
+ [data?.environments]
);
- return { environments, status, environmentOptions };
+ return { environments: data.environments, status, environmentOptions };
}
diff --git a/x-pack/plugins/apm/public/plugin.ts b/x-pack/plugins/apm/public/plugin.ts
index 4ddd10ecc1476..382053f133950 100644
--- a/x-pack/plugins/apm/public/plugin.ts
+++ b/x-pack/plugins/apm/public/plugin.ts
@@ -9,7 +9,7 @@ import { ConfigSchema } from '.';
import {
FetchDataParams,
HasDataParams,
- ObservabilityPluginSetup,
+ ObservabilityPublicSetup,
} from '../../observability/public';
import {
AppMountParameters,
@@ -52,7 +52,7 @@ export interface ApmPluginSetupDeps {
home?: HomePublicPluginSetup;
licensing: LicensingPluginSetup;
triggersActionsUi: TriggersAndActionsUIPublicPluginSetup;
- observability?: ObservabilityPluginSetup;
+ observability?: ObservabilityPublicSetup;
}
export interface ApmPluginStartDeps {
@@ -85,19 +85,19 @@ export class ApmPlugin implements Plugin {
const getApmDataHelper = async () => {
const {
fetchObservabilityOverviewPageData,
- hasData,
+ getHasData,
createCallApmApi,
} = await import('./services/rest/apm_observability_overview_fetchers');
// have to do this here as well in case app isn't mounted yet
- createCallApmApi(core.http);
+ createCallApmApi(core);
- return { fetchObservabilityOverviewPageData, hasData };
+ return { fetchObservabilityOverviewPageData, getHasData };
};
plugins.observability.dashboard.register({
appName: 'apm',
hasData: async () => {
const dataHelper = await getApmDataHelper();
- return await dataHelper.hasData();
+ return await dataHelper.getHasData();
},
fetchData: async (params: FetchDataParams) => {
const dataHelper = await getApmDataHelper();
@@ -112,7 +112,7 @@ export class ApmPlugin implements Plugin {
createCallApmApi,
} = await import('./components/app/RumDashboard/ux_overview_fetchers');
// have to do this here as well in case app isn't mounted yet
- createCallApmApi(core.http);
+ createCallApmApi(core);
return { fetchUxOverviewDate, hasRumData };
};
diff --git a/x-pack/plugins/apm/public/selectors/throughput_chart_selectors.ts b/x-pack/plugins/apm/public/selectors/throughput_chart_selectors.ts
index f9e72bff231f4..f334212536778 100644
--- a/x-pack/plugins/apm/public/selectors/throughput_chart_selectors.ts
+++ b/x-pack/plugins/apm/public/selectors/throughput_chart_selectors.ts
@@ -8,14 +8,14 @@
import { difference, zipObject } from 'lodash';
import { EuiTheme } from '../../../../../src/plugins/kibana_react/common';
import { asTransactionRate } from '../../common/utils/formatters';
-import { TimeSeries } from '../../typings/timeseries';
+import { Coordinate, TimeSeries } from '../../typings/timeseries';
import { APIReturnType } from '../services/rest/createCallApmApi';
import { httpStatusCodeToColor } from '../utils/httpStatusCodeToColor';
export type ThroughputChartsResponse = APIReturnType<'GET /api/apm/services/{serviceName}/transactions/charts/throughput'>;
export interface ThroughputChart {
- throughputTimeseries: TimeSeries[];
+ throughputTimeseries: Array>;
}
export function getThroughputChartSelector({
diff --git a/x-pack/plugins/apm/public/services/callApi.test.ts b/x-pack/plugins/apm/public/services/callApi.test.ts
index cdd9cb5b08a32..5f0be1b6fadbb 100644
--- a/x-pack/plugins/apm/public/services/callApi.test.ts
+++ b/x-pack/plugins/apm/public/services/callApi.test.ts
@@ -7,49 +7,51 @@
import { mockNow } from '../utils/testHelpers';
import { clearCache, callApi } from './rest/callApi';
-import { SessionStorageMock } from './__mocks__/SessionStorageMock';
-import { HttpSetup } from 'kibana/public';
+import { CoreStart, HttpSetup } from 'kibana/public';
-type HttpMock = HttpSetup & {
- get: jest.SpyInstance;
+type CoreMock = CoreStart & {
+ http: {
+ get: jest.SpyInstance;
+ };
};
describe('callApi', () => {
- let http: HttpMock;
+ let core: CoreMock;
beforeEach(() => {
- http = ({
- get: jest.fn().mockReturnValue({
- my_key: 'hello_world',
- }),
- } as unknown) as HttpMock;
-
- // @ts-expect-error
- global.sessionStorage = new SessionStorageMock();
+ core = ({
+ http: {
+ get: jest.fn().mockReturnValue({
+ my_key: 'hello_world',
+ }),
+ },
+ uiSettings: { get: () => false }, // disable `observability:enableInspectEsQueries` setting
+ } as unknown) as CoreMock;
});
afterEach(() => {
- http.get.mockClear();
+ core.http.get.mockClear();
clearCache();
});
- describe('apm_debug', () => {
+ describe('_inspect', () => {
beforeEach(() => {
- sessionStorage.setItem('apm_debug', 'true');
+ // @ts-expect-error
+ core.uiSettings.get = () => true; // enable `observability:enableInspectEsQueries` setting
});
it('should add debug param for APM endpoints', async () => {
- await callApi(http, { pathname: `/api/apm/status/server` });
+ await callApi(core, { pathname: `/api/apm/status/server` });
- expect(http.get).toHaveBeenCalledWith('/api/apm/status/server', {
- query: { _debug: true },
+ expect(core.http.get).toHaveBeenCalledWith('/api/apm/status/server', {
+ query: { _inspect: true },
});
});
it('should not add debug param for non-APM endpoints', async () => {
- await callApi(http, { pathname: `/api/kibana` });
+ await callApi(core, { pathname: `/api/kibana` });
- expect(http.get).toHaveBeenCalledWith('/api/kibana', { query: {} });
+ expect(core.http.get).toHaveBeenCalledWith('/api/kibana', { query: {} });
});
});
@@ -65,138 +67,138 @@ describe('callApi', () => {
describe('when the call does not contain start/end params', () => {
it('should not return cached response for identical calls', async () => {
- await callApi(http, { pathname: `/api/kibana`, query: { foo: 'bar' } });
- await callApi(http, { pathname: `/api/kibana`, query: { foo: 'bar' } });
- await callApi(http, { pathname: `/api/kibana`, query: { foo: 'bar' } });
+ await callApi(core, { pathname: `/api/kibana`, query: { foo: 'bar' } });
+ await callApi(core, { pathname: `/api/kibana`, query: { foo: 'bar' } });
+ await callApi(core, { pathname: `/api/kibana`, query: { foo: 'bar' } });
- expect(http.get).toHaveBeenCalledTimes(3);
+ expect(core.http.get).toHaveBeenCalledTimes(3);
});
});
describe('when the call contains start/end params', () => {
it('should return cached response for identical calls', async () => {
- await callApi(http, {
+ await callApi(core, {
pathname: `/api/kibana`,
query: { start: '2010', end: '2011' },
});
- await callApi(http, {
+ await callApi(core, {
pathname: `/api/kibana`,
query: { start: '2010', end: '2011' },
});
- await callApi(http, {
+ await callApi(core, {
pathname: `/api/kibana`,
query: { start: '2010', end: '2011' },
});
- expect(http.get).toHaveBeenCalledTimes(1);
+ expect(core.http.get).toHaveBeenCalledTimes(1);
});
it('should not return cached response for subsequent calls if arguments change', async () => {
- await callApi(http, {
+ await callApi(core, {
pathname: `/api/kibana`,
query: { start: '2010', end: '2011', foo: 'bar1' },
});
- await callApi(http, {
+ await callApi(core, {
pathname: `/api/kibana`,
query: { start: '2010', end: '2011', foo: 'bar2' },
});
- await callApi(http, {
+ await callApi(core, {
pathname: `/api/kibana`,
query: { start: '2010', end: '2011', foo: 'bar3' },
});
- expect(http.get).toHaveBeenCalledTimes(3);
+ expect(core.http.get).toHaveBeenCalledTimes(3);
});
it('should not return cached response if `end` is a future timestamp', async () => {
- await callApi(http, {
+ await callApi(core, {
pathname: `/api/kibana`,
query: { end: '2030' },
});
- await callApi(http, {
+ await callApi(core, {
pathname: `/api/kibana`,
query: { end: '2030' },
});
- await callApi(http, {
+ await callApi(core, {
pathname: `/api/kibana`,
query: { end: '2030' },
});
- expect(http.get).toHaveBeenCalledTimes(3);
+ expect(core.http.get).toHaveBeenCalledTimes(3);
});
it('should return cached response if calls contain `end` param in the past', async () => {
- await callApi(http, {
+ await callApi(core, {
pathname: `/api/kibana`,
query: { start: '2009', end: '2010' },
});
- await callApi(http, {
+ await callApi(core, {
pathname: `/api/kibana`,
query: { start: '2009', end: '2010' },
});
- await callApi(http, {
+ await callApi(core, {
pathname: `/api/kibana`,
query: { start: '2009', end: '2010' },
});
- expect(http.get).toHaveBeenCalledTimes(1);
+ expect(core.http.get).toHaveBeenCalledTimes(1);
});
it('should return cached response even if order of properties change', async () => {
- await callApi(http, {
+ await callApi(core, {
pathname: `/api/kibana`,
query: { end: '2010', start: '2009' },
});
- await callApi(http, {
+ await callApi(core, {
pathname: `/api/kibana`,
query: { start: '2009', end: '2010' },
});
- await callApi(http, {
+ await callApi(core, {
query: { start: '2009', end: '2010' },
pathname: `/api/kibana`,
});
- expect(http.get).toHaveBeenCalledTimes(1);
+ expect(core.http.get).toHaveBeenCalledTimes(1);
});
it('should not return cached response with `isCachable: false` option', async () => {
- await callApi(http, {
+ await callApi(core, {
isCachable: false,
pathname: `/api/kibana`,
query: { start: '2010', end: '2011' },
});
- await callApi(http, {
+ await callApi(core, {
isCachable: false,
pathname: `/api/kibana`,
query: { start: '2010', end: '2011' },
});
- await callApi(http, {
+ await callApi(core, {
isCachable: false,
pathname: `/api/kibana`,
query: { start: '2010', end: '2011' },
});
- expect(http.get).toHaveBeenCalledTimes(3);
+ expect(core.http.get).toHaveBeenCalledTimes(3);
});
it('should return cached response with `isCachable: true` option', async () => {
- await callApi(http, {
+ await callApi(core, {
isCachable: true,
pathname: `/api/kibana`,
query: { end: '2030' },
});
- await callApi(http, {
+ await callApi(core, {
isCachable: true,
pathname: `/api/kibana`,
query: { end: '2030' },
});
- await callApi(http, {
+ await callApi(core, {
isCachable: true,
pathname: `/api/kibana`,
query: { end: '2030' },
});
- expect(http.get).toHaveBeenCalledTimes(1);
+ expect(core.http.get).toHaveBeenCalledTimes(1);
});
});
});
diff --git a/x-pack/plugins/apm/public/services/callApmApi.test.ts b/x-pack/plugins/apm/public/services/callApmApi.test.ts
index 25d34b5d102f5..56146c49fc57d 100644
--- a/x-pack/plugins/apm/public/services/callApmApi.test.ts
+++ b/x-pack/plugins/apm/public/services/callApmApi.test.ts
@@ -7,7 +7,7 @@
import * as callApiExports from './rest/callApi';
import { createCallApmApi, callApmApi } from './rest/createCallApmApi';
-import { HttpSetup } from 'kibana/public';
+import { CoreStart } from 'kibana/public';
const callApi = jest
.spyOn(callApiExports, 'callApi')
@@ -15,7 +15,7 @@ const callApi = jest
describe('callApmApi', () => {
beforeEach(() => {
- createCallApmApi({} as HttpSetup);
+ createCallApmApi({} as CoreStart);
});
afterEach(() => {
@@ -79,7 +79,7 @@ describe('callApmApi', () => {
{},
expect.objectContaining({
pathname: '/api/apm',
- method: 'POST',
+ method: 'post',
body: {
foo: 'bar',
bar: 'foo',
diff --git a/x-pack/plugins/apm/public/services/rest/apm_observability_overview_fetchers.test.ts b/x-pack/plugins/apm/public/services/rest/apm_observability_overview_fetchers.test.ts
index b0bae6aa91a3d..1821e92ee5a78 100644
--- a/x-pack/plugins/apm/public/services/rest/apm_observability_overview_fetchers.test.ts
+++ b/x-pack/plugins/apm/public/services/rest/apm_observability_overview_fetchers.test.ts
@@ -8,7 +8,7 @@
import moment from 'moment';
import {
fetchObservabilityOverviewPageData,
- hasData,
+ getHasData,
} from './apm_observability_overview_fetchers';
import * as createCallApmApi from './createCallApmApi';
@@ -31,12 +31,12 @@ describe('Observability dashboard data', () => {
describe('hasData', () => {
it('returns false when no data is available', async () => {
callApmApiMock.mockImplementation(() => Promise.resolve(false));
- const response = await hasData();
+ const response = await getHasData();
expect(response).toBeFalsy();
});
it('returns true when data is available', async () => {
- callApmApiMock.mockImplementation(() => Promise.resolve(true));
- const response = await hasData();
+ callApmApiMock.mockResolvedValue({ hasData: true });
+ const response = await getHasData();
expect(response).toBeTruthy();
});
});
diff --git a/x-pack/plugins/apm/public/services/rest/apm_observability_overview_fetchers.ts b/x-pack/plugins/apm/public/services/rest/apm_observability_overview_fetchers.ts
index 6d630ede1cb11..55ead8d942aca 100644
--- a/x-pack/plugins/apm/public/services/rest/apm_observability_overview_fetchers.ts
+++ b/x-pack/plugins/apm/public/services/rest/apm_observability_overview_fetchers.ts
@@ -58,9 +58,11 @@ export const fetchObservabilityOverviewPageData = async ({
};
};
-export async function hasData() {
- return await callApmApi({
+export async function getHasData() {
+ const res = await callApmApi({
endpoint: 'GET /api/apm/observability_overview/has_data',
signal: null,
});
+
+ return res.hasData;
}
diff --git a/x-pack/plugins/apm/public/services/rest/callApi.ts b/x-pack/plugins/apm/public/services/rest/callApi.ts
index f5106fce78cc7..f623872303c5b 100644
--- a/x-pack/plugins/apm/public/services/rest/callApi.ts
+++ b/x-pack/plugins/apm/public/services/rest/callApi.ts
@@ -5,15 +5,19 @@
* 2.0.
*/
-import { HttpSetup } from 'kibana/public';
+import { CoreSetup, CoreStart } from 'kibana/public';
import { isString, startsWith } from 'lodash';
import LRU from 'lru-cache';
import hash from 'object-hash';
+import { enableInspectEsQueries } from '../../../../observability/public';
import { FetchOptions } from '../../../common/fetch_options';
-function fetchOptionsWithDebug(fetchOptions: FetchOptions) {
+function fetchOptionsWithDebug(
+ fetchOptions: FetchOptions,
+ inspectableEsQueriesEnabled: boolean
+) {
const debugEnabled =
- sessionStorage.getItem('apm_debug') === 'true' &&
+ inspectableEsQueriesEnabled &&
startsWith(fetchOptions.pathname, '/api/apm');
const { body, ...rest } = fetchOptions;
@@ -23,7 +27,7 @@ function fetchOptionsWithDebug(fetchOptions: FetchOptions) {
...(body !== undefined ? { body: JSON.stringify(body) } : {}),
query: {
...fetchOptions.query,
- ...(debugEnabled ? { _debug: true } : {}),
+ ...(debugEnabled ? { _inspect: true } : {}),
},
};
}
@@ -37,9 +41,12 @@ export function clearCache() {
export type CallApi = typeof callApi;
export async function callApi(
- http: HttpSetup,
+ { http, uiSettings }: CoreStart | CoreSetup,
fetchOptions: FetchOptions
): Promise {
+ const inspectableEsQueriesEnabled: boolean = uiSettings.get(
+ enableInspectEsQueries
+ );
const cacheKey = getCacheKey(fetchOptions);
const cacheResponse = cache.get(cacheKey);
if (cacheResponse) {
@@ -47,7 +54,8 @@ export async function callApi(
}
const { pathname, method = 'get', ...options } = fetchOptionsWithDebug(
- fetchOptions
+ fetchOptions,
+ inspectableEsQueriesEnabled
);
const lowercaseMethod = method.toLowerCase() as
diff --git a/x-pack/plugins/apm/public/services/rest/createCallApmApi.ts b/x-pack/plugins/apm/public/services/rest/createCallApmApi.ts
index c6d55a85dd70e..b0cce3296fe21 100644
--- a/x-pack/plugins/apm/public/services/rest/createCallApmApi.ts
+++ b/x-pack/plugins/apm/public/services/rest/createCallApmApi.ts
@@ -5,13 +5,14 @@
* 2.0.
*/
-import { HttpSetup } from 'kibana/public';
+import { CoreSetup, CoreStart } from 'kibana/public';
+import { parseEndpoint } from '../../../common/apm_api/parse_endpoint';
import { FetchOptions } from '../../../common/fetch_options';
import { callApi } from './callApi';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
-import { APMAPI } from '../../../server/routes/create_apm_api';
+import type { APMAPI } from '../../../server/routes/create_apm_api';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
-import { Client } from '../../../server/routes/typings';
+import type { Client } from '../../../server/routes/typings';
export type APMClient = Client;
export type AutoAbortedAPMClient = Client;
@@ -24,8 +25,8 @@ export type APMClientOptions = Omit<
signal: AbortSignal | null;
params?: {
body?: any;
- query?: any;
- path?: any;
+ query?: Record;
+ path?: Record;
};
};
@@ -35,23 +36,17 @@ export let callApmApi: APMClient = () => {
);
};
-export function createCallApmApi(http: HttpSetup) {
+export function createCallApmApi(core: CoreStart | CoreSetup) {
callApmApi = ((options: APMClientOptions) => {
- const { endpoint, params = {}, ...opts } = options;
+ const { endpoint, params, ...opts } = options;
+ const { method, pathname } = parseEndpoint(endpoint, params?.path);
- const path = (params.path || {}) as Record;
- const [method, pathname] = endpoint.split(' ');
-
- const formattedPathname = Object.keys(path).reduce((acc, paramName) => {
- return acc.replace(`{${paramName}}`, path[paramName]);
- }, pathname);
-
- return callApi(http, {
+ return callApi(core, {
...opts,
method,
- pathname: formattedPathname,
- body: params.body,
- query: params.query,
+ pathname,
+ body: params?.body,
+ query: params?.query,
});
}) as APMClient;
}
diff --git a/x-pack/plugins/apm/readme.md b/x-pack/plugins/apm/readme.md
index b35024844a892..ef2675f4f6c65 100644
--- a/x-pack/plugins/apm/readme.md
+++ b/x-pack/plugins/apm/readme.md
@@ -31,19 +31,19 @@ _Docker Compose is required_
## Testing
-### E2E (Cypress) tests
+### Cypress tests
```sh
-x-pack/plugins/apm/e2e/run-e2e.sh
+node x-pack/plugins/apm/scripts/ftr_e2e/cypress_run.js
```
_Starts Kibana (:5701), APM Server (:8201) and Elasticsearch (:9201). Ingests sample data into Elasticsearch via APM Server and runs the Cypress tests_
-### Unit testing
+### Jest tests
Note: Run the following commands from `kibana/x-pack/plugins/apm`.
-#### Run unit tests
+#### Run
```
npx jest --watch
@@ -82,8 +82,11 @@ For debugging access Elasticsearch on http://localhost:9220` (elastic/changeme)
### API integration tests
-Our tests are separated in two suites: one suite runs with a basic license, and the other
-with a trial license (the equivalent of gold+). This requires separate test servers and test runners.
+API tests are separated in two suites:
+ - a basic license test suite
+ - a trial license test suite (the equivalent of gold+)
+
+This requires separate test servers and test runners.
**Basic**
@@ -109,7 +112,10 @@ node scripts/functional_test_runner --config x-pack/test/apm_api_integration/tri
The API tests for "trial" are located in `x-pack/test/apm_api_integration/trial/tests`.
-For debugging access Elasticsearch on http://localhost:9220` (elastic/changeme)
+
+**API Test tips**
+ - For debugging access Elasticsearch on http://localhost:9220` (elastic/changeme)
+ - To update snapshots append `--updateSnapshots` to the functional_test_runner command
## Linting
@@ -154,10 +160,10 @@ The users will be created with the password specified in kibana.dev.yml for `ela
## Debugging Elasticsearch queries
-All APM api endpoints accept `_debug=true` as a query param that will result in the underlying ES query being outputted in the Kibana backend process.
+All APM api endpoints accept `_inspect=true` as a query param that will result in the underlying ES query being outputted in the Kibana backend process.
Example:
-`/api/apm/services/my_service?_debug=true`
+`/api/apm/services/my_service?_inspect=true`
## Storybook
diff --git a/x-pack/plugins/apm/server/lib/correlations/get_correlations_for_slow_transactions/get_latency_distribution.ts b/x-pack/plugins/apm/server/lib/correlations/get_correlations_for_slow_transactions/get_latency_distribution.ts
index 50613c10ff7c0..88b1cf3a344ed 100644
--- a/x-pack/plugins/apm/server/lib/correlations/get_correlations_for_slow_transactions/get_latency_distribution.ts
+++ b/x-pack/plugins/apm/server/lib/correlations/get_correlations_for_slow_transactions/get_latency_distribution.ts
@@ -106,7 +106,7 @@ export async function getLatencyDistribution({
type Agg = NonNullable;
if (!response.aggregations) {
- return;
+ return {};
}
function formatDistribution(distribution: Agg['distribution']) {
diff --git a/x-pack/plugins/apm/server/lib/correlations/process_significant_term_aggs.ts b/x-pack/plugins/apm/server/lib/correlations/process_significant_term_aggs.ts
index 94ed3dc3b6999..cc1e32e47973d 100644
--- a/x-pack/plugins/apm/server/lib/correlations/process_significant_term_aggs.ts
+++ b/x-pack/plugins/apm/server/lib/correlations/process_significant_term_aggs.ts
@@ -47,10 +47,15 @@ function getMaxImpactScore(scores: number[]) {
export function processSignificantTermAggs({
sigTermAggs,
}: {
- sigTermAggs: Record;
+ sigTermAggs: Record;
}) {
- const significantTerms = Object.entries(sigTermAggs).flatMap(
- ([fieldName, agg]) => {
+ const significantTerms = Object.entries(sigTermAggs)
+ // filter entries with buckets, i.e. Significant terms aggs
+ .filter((entry): entry is [string, SigTermAgg] => {
+ const [, agg] = entry;
+ return 'buckets' in agg;
+ })
+ .flatMap(([fieldName, agg]) => {
return agg.buckets.map((bucket) => ({
fieldName,
fieldValue: bucket.key,
@@ -58,8 +63,7 @@ export function processSignificantTermAggs({
valueCount: bucket.doc_count,
score: bucket.score,
}));
- }
- );
+ });
const maxImpactScore = getMaxImpactScore(
significantTerms.map(({ score }) => score)
diff --git a/x-pack/plugins/apm/server/lib/helpers/create_es_client/call_async_with_debug.ts b/x-pack/plugins/apm/server/lib/helpers/create_es_client/call_async_with_debug.ts
index aa41880fba444..1f0aa401bcab0 100644
--- a/x-pack/plugins/apm/server/lib/helpers/create_es_client/call_async_with_debug.ts
+++ b/x-pack/plugins/apm/server/lib/helpers/create_es_client/call_async_with_debug.ts
@@ -7,8 +7,10 @@
/* eslint-disable no-console */
+import { omit } from 'lodash';
import chalk from 'chalk';
import { KibanaRequest } from '../../../../../../../src/core/server';
+import { inspectableEsQueriesMap } from '../../../routes/create_api';
function formatObj(obj: Record) {
return JSON.stringify(obj, null, 2);
@@ -18,10 +20,18 @@ export async function callAsyncWithDebug({
cb,
getDebugMessage,
debug,
+ request,
+ requestType,
+ requestParams,
+ isCalledWithInternalUser,
}: {
cb: () => Promise;
getDebugMessage: () => { body: string; title: string };
debug: boolean;
+ request: KibanaRequest;
+ requestType: string;
+ requestParams: Record;
+ isCalledWithInternalUser: boolean; // only allow inspection of queries that were retrieved with credentials of the end user
}) {
if (!debug) {
return cb();
@@ -41,16 +51,27 @@ export async function callAsyncWithDebug({
if (debug) {
const highlightColor = esError ? 'bgRed' : 'inverse';
const diff = process.hrtime(startTime);
- const duration = `${Math.round(diff[0] * 1000 + diff[1] / 1e6)}ms`;
+ const duration = Math.round(diff[0] * 1000 + diff[1] / 1e6); // duration in ms
const { title, body } = getDebugMessage();
console.log(
- chalk.bold[highlightColor](`=== Debug: ${title} (${duration}) ===`)
+ chalk.bold[highlightColor](`=== Debug: ${title} (${duration}ms) ===`)
);
console.log(body);
console.log(`\n`);
+
+ const inspectableEsQueries = inspectableEsQueriesMap.get(request);
+ if (!isCalledWithInternalUser && inspectableEsQueries) {
+ inspectableEsQueries.push({
+ response: res,
+ duration,
+ requestType,
+ requestParams: omit(requestParams, 'headers'),
+ esError: esError?.response ?? esError?.message,
+ });
+ }
}
if (esError) {
@@ -62,13 +83,13 @@ export async function callAsyncWithDebug({
export const getDebugBody = (
params: Record,
- operationName: string
+ requestType: string
) => {
- if (operationName === 'search') {
+ if (requestType === 'search') {
return `GET ${params.index}/_search\n${formatObj(params.body)}`;
}
- return `${chalk.bold('ES operation:')} ${operationName}\n${chalk.bold(
+ return `${chalk.bold('ES operation:')} ${requestType}\n${chalk.bold(
'ES query:'
)}\n${formatObj(params)}`;
};
diff --git a/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.ts b/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.ts
index e20103cc6ddca..b8a14253a229a 100644
--- a/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.ts
+++ b/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.ts
@@ -93,6 +93,9 @@ export function createApmEventClient({
ignore_unavailable: true,
};
+ // only "search" operation is currently supported
+ const requestType = 'search';
+
return callAsyncWithDebug({
cb: () => {
const searchPromise = cancelEsRequestOnAbort(
@@ -103,10 +106,14 @@ export function createApmEventClient({
return unwrapEsResponse(searchPromise);
},
getDebugMessage: () => ({
- body: getDebugBody(searchParams, 'search'),
+ body: getDebugBody(searchParams, requestType),
title: getDebugTitle(request),
}),
+ isCalledWithInternalUser: false,
debug,
+ request,
+ requestType,
+ requestParams: searchParams,
});
},
};
diff --git a/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_internal_es_client/index.ts b/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_internal_es_client/index.ts
index 2e83baece01a9..45e17c1678518 100644
--- a/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_internal_es_client/index.ts
+++ b/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_internal_es_client/index.ts
@@ -40,10 +40,10 @@ export function createInternalESClient({
function callEs({
cb,
- operationName,
+ requestType,
params,
}: {
- operationName: string;
+ requestType: string;
cb: () => TransportRequestPromise;
params: Record;
}) {
@@ -51,9 +51,13 @@ export function createInternalESClient({
cb: () => unwrapEsResponse(cancelEsRequestOnAbort(cb(), request)),
getDebugMessage: () => ({
title: getDebugTitle(request),
- body: getDebugBody(params, operationName),
+ body: getDebugBody(params, requestType),
}),
- debug: context.params.query._debug,
+ debug: context.params.query._inspect,
+ isCalledWithInternalUser: true,
+ request,
+ requestType,
+ requestParams: params,
});
}
@@ -65,28 +69,28 @@ export function createInternalESClient({
params: TSearchRequest
): Promise> => {
return callEs({
- operationName: 'search',
+ requestType: 'search',
cb: () => asInternalUser.search(params),
params,
});
},
index: (params: APMIndexDocumentParams) => {
return callEs({
- operationName: 'index',
+ requestType: 'index',
cb: () => asInternalUser.index(params),
params,
});
},
delete: (params: DeleteRequest): Promise<{ result: string }> => {
return callEs({
- operationName: 'delete',
+ requestType: 'delete',
cb: () => asInternalUser.delete(params),
params,
});
},
indicesCreate: (params: CreateIndexRequest) => {
return callEs({
- operationName: 'indices.create',
+ requestType: 'indices.create',
cb: () => asInternalUser.indices.create(params),
params,
});
diff --git a/x-pack/plugins/apm/server/lib/helpers/input_validation.ts b/x-pack/plugins/apm/server/lib/helpers/input_validation.ts
index 5c188ff0d093e..0a34711b9b40d 100644
--- a/x-pack/plugins/apm/server/lib/helpers/input_validation.ts
+++ b/x-pack/plugins/apm/server/lib/helpers/input_validation.ts
@@ -14,7 +14,7 @@ export const withDefaultValidators = (
validators: { [key: string]: Schema } = {}
) => {
return Joi.object().keys({
- _debug: Joi.bool(),
+ _inspect: Joi.bool(),
start: dateValidation,
end: dateValidation,
uiFilters: Joi.string(),
diff --git a/x-pack/plugins/apm/server/lib/helpers/setup_request.test.ts b/x-pack/plugins/apm/server/lib/helpers/setup_request.test.ts
index 51f386d59c04a..c0707d0286180 100644
--- a/x-pack/plugins/apm/server/lib/helpers/setup_request.test.ts
+++ b/x-pack/plugins/apm/server/lib/helpers/setup_request.test.ts
@@ -51,7 +51,7 @@ function getMockRequest() {
) as APMConfig,
params: {
query: {
- _debug: false,
+ _inspect: false,
},
},
core: {
diff --git a/x-pack/plugins/apm/server/lib/helpers/setup_request.ts b/x-pack/plugins/apm/server/lib/helpers/setup_request.ts
index 60fb9a8bfa85a..fff661250c6df 100644
--- a/x-pack/plugins/apm/server/lib/helpers/setup_request.ts
+++ b/x-pack/plugins/apm/server/lib/helpers/setup_request.ts
@@ -45,7 +45,7 @@ export interface SetupTimeRange {
interface SetupRequestParams {
query?: {
- _debug?: boolean;
+ _inspect?: boolean;
/**
* Timestamp in ms since epoch
@@ -88,7 +88,7 @@ export async function setupRequest(
indices,
apmEventClient: createApmEventClient({
esClient: context.core.elasticsearch.client.asCurrentUser,
- debug: context.params.query._debug,
+ debug: context.params.query._inspect,
request,
indices,
options: { includeFrozen },
diff --git a/x-pack/plugins/apm/server/lib/index_pattern/create_static_index_pattern.ts b/x-pack/plugins/apm/server/lib/index_pattern/create_static_index_pattern.ts
index 8d0acb7f85f5d..0b7f82c0b8388 100644
--- a/x-pack/plugins/apm/server/lib/index_pattern/create_static_index_pattern.ts
+++ b/x-pack/plugins/apm/server/lib/index_pattern/create_static_index_pattern.ts
@@ -21,20 +21,20 @@ export async function createStaticIndexPattern(
setup: Setup,
context: APMRequestHandlerContext,
savedObjectsClient: InternalSavedObjectsClient
-): Promise {
+): Promise {
return withApmSpan('create_static_index_pattern', async () => {
const { config } = context;
// don't autocreate APM index pattern if it's been disabled via the config
if (!config['xpack.apm.autocreateApmIndexPattern']) {
- return;
+ return false;
}
// Discover and other apps will throw errors if an index pattern exists without having matching indices.
// The following ensures the index pattern is only created if APM data is found
const hasData = await hasHistoricalAgentData(setup);
if (!hasData) {
- return;
+ return false;
}
try {
@@ -49,12 +49,12 @@ export async function createStaticIndexPattern(
{ id: APM_STATIC_INDEX_PATTERN_ID, overwrite: false }
)
);
- return;
+ return true;
} catch (e) {
// if the index pattern (saved object) already exists a conflict error (code: 409) will be thrown
// that error should be silenced
if (SavedObjectsErrorHelpers.isConflictError(e)) {
- return;
+ return false;
}
throw e;
}
diff --git a/x-pack/plugins/apm/server/lib/observability_overview/has_data.ts b/x-pack/plugins/apm/server/lib/observability_overview/has_data.ts
index abdc8da78502c..bbe13874d7d3b 100644
--- a/x-pack/plugins/apm/server/lib/observability_overview/has_data.ts
+++ b/x-pack/plugins/apm/server/lib/observability_overview/has_data.ts
@@ -9,7 +9,7 @@ import { ProcessorEvent } from '../../../common/processor_event';
import { withApmSpan } from '../../utils/with_apm_span';
import { Setup } from '../helpers/setup_request';
-export function hasData({ setup }: { setup: Setup }) {
+export function getHasData({ setup }: { setup: Setup }) {
return withApmSpan('observability_overview_has_apm_data', async () => {
const { apmEventClient } = setup;
try {
diff --git a/x-pack/plugins/apm/server/lib/services/get_service_instances/comparison_statistics.ts b/x-pack/plugins/apm/server/lib/services/get_service_instances/comparison_statistics.ts
new file mode 100644
index 0000000000000..6fca42723b9cc
--- /dev/null
+++ b/x-pack/plugins/apm/server/lib/services/get_service_instances/comparison_statistics.ts
@@ -0,0 +1,166 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { keyBy } from 'lodash';
+import { Coordinate } from '../../../../typings/timeseries';
+import { LatencyAggregationType } from '../../../../common/latency_aggregation_types';
+import { joinByKey } from '../../../../common/utils/join_by_key';
+import { offsetPreviousPeriodCoordinates } from '../../../utils/offset_previous_period_coordinate';
+import { withApmSpan } from '../../../utils/with_apm_span';
+import { Setup, SetupTimeRange } from '../../helpers/setup_request';
+import { getServiceInstancesSystemMetricStatistics } from './get_service_instances_system_metric_statistics';
+import { getServiceInstancesTransactionStatistics } from './get_service_instances_transaction_statistics';
+
+interface ServiceInstanceComparisonStatisticsParams {
+ environment?: string;
+ kuery?: string;
+ latencyAggregationType: LatencyAggregationType;
+ setup: Setup;
+ serviceName: string;
+ transactionType: string;
+ searchAggregatedTransactions: boolean;
+ numBuckets: number;
+ start: number;
+ end: number;
+ serviceNodeIds: string[];
+}
+
+async function getServiceInstancesComparisonStatistics(
+ params: ServiceInstanceComparisonStatisticsParams
+): Promise<
+ Array<{
+ serviceNodeName: string;
+ errorRate?: Coordinate[];
+ latency?: Coordinate[];
+ throughput?: Coordinate[];
+ cpuUsage?: Coordinate[];
+ memoryUsage?: Coordinate[];
+ }>
+> {
+ return withApmSpan(
+ 'get_service_instances_comparison_statistics',
+ async () => {
+ const [transactionStats, systemMetricStats = []] = await Promise.all([
+ getServiceInstancesTransactionStatistics({
+ ...params,
+ isComparisonSearch: true,
+ }),
+ getServiceInstancesSystemMetricStatistics({
+ ...params,
+ isComparisonSearch: true,
+ }),
+ ]);
+
+ const stats = joinByKey(
+ [...transactionStats, ...systemMetricStats],
+ 'serviceNodeName'
+ );
+
+ return stats;
+ }
+ );
+}
+
+export async function getServiceInstancesComparisonStatisticsPeriods({
+ environment,
+ kuery,
+ latencyAggregationType,
+ setup,
+ serviceName,
+ transactionType,
+ searchAggregatedTransactions,
+ numBuckets,
+ serviceNodeIds,
+ comparisonStart,
+ comparisonEnd,
+}: {
+ environment?: string;
+ kuery?: string;
+ latencyAggregationType: LatencyAggregationType;
+ setup: Setup & SetupTimeRange;
+ serviceName: string;
+ transactionType: string;
+ searchAggregatedTransactions: boolean;
+ numBuckets: number;
+ serviceNodeIds: string[];
+ comparisonStart?: number;
+ comparisonEnd?: number;
+}) {
+ return withApmSpan(
+ 'get_service_instances_comparison_statistics_periods',
+ async () => {
+ const { start, end } = setup;
+
+ const commonParams = {
+ environment,
+ kuery,
+ latencyAggregationType,
+ setup,
+ serviceName,
+ transactionType,
+ searchAggregatedTransactions,
+ numBuckets,
+ serviceNodeIds,
+ };
+
+ const currentPeriodPromise = getServiceInstancesComparisonStatistics({
+ ...commonParams,
+ start,
+ end,
+ });
+
+ const previousPeriodPromise =
+ comparisonStart && comparisonEnd
+ ? getServiceInstancesComparisonStatistics({
+ ...commonParams,
+ start: comparisonStart,
+ end: comparisonEnd,
+ })
+ : [];
+ const [currentPeriod, previousPeriod] = await Promise.all([
+ currentPeriodPromise,
+ previousPeriodPromise,
+ ]);
+
+ const firtCurrentPeriod = currentPeriod.length
+ ? currentPeriod[0]
+ : undefined;
+
+ return {
+ currentPeriod: keyBy(currentPeriod, 'serviceNodeName'),
+ previousPeriod: keyBy(
+ previousPeriod.map((data) => {
+ return {
+ ...data,
+ cpuUsage: offsetPreviousPeriodCoordinates({
+ currentPeriodTimeseries: firtCurrentPeriod?.cpuUsage,
+ previousPeriodTimeseries: data.cpuUsage,
+ }),
+ errorRate: offsetPreviousPeriodCoordinates({
+ currentPeriodTimeseries: firtCurrentPeriod?.errorRate,
+ previousPeriodTimeseries: data.errorRate,
+ }),
+ latency: offsetPreviousPeriodCoordinates({
+ currentPeriodTimeseries: firtCurrentPeriod?.latency,
+ previousPeriodTimeseries: data.latency,
+ }),
+ memoryUsage: offsetPreviousPeriodCoordinates({
+ currentPeriodTimeseries: firtCurrentPeriod?.memoryUsage,
+ previousPeriodTimeseries: data.memoryUsage,
+ }),
+ throughput: offsetPreviousPeriodCoordinates({
+ currentPeriodTimeseries: firtCurrentPeriod?.throughput,
+ previousPeriodTimeseries: data.throughput,
+ }),
+ };
+ }),
+ 'serviceNodeName'
+ ),
+ };
+ }
+ );
+}
diff --git a/x-pack/plugins/apm/server/lib/services/get_service_instances/get_service_instance_system_metric_stats.ts b/x-pack/plugins/apm/server/lib/services/get_service_instances/get_service_instance_system_metric_stats.ts
deleted file mode 100644
index 6a72f817b3f69..0000000000000
--- a/x-pack/plugins/apm/server/lib/services/get_service_instances/get_service_instance_system_metric_stats.ts
+++ /dev/null
@@ -1,161 +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
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { AggregationOptionsByType } from '../../../../../../../typings/elasticsearch';
-import {
- environmentQuery,
- rangeQuery,
- kqlQuery,
-} from '../../../../server/utils/queries';
-import { SERVICE_NODE_NAME_MISSING } from '../../../../common/service_nodes';
-import {
- METRIC_CGROUP_MEMORY_USAGE_BYTES,
- METRIC_PROCESS_CPU_PERCENT,
- METRIC_SYSTEM_FREE_MEMORY,
- METRIC_SYSTEM_TOTAL_MEMORY,
- SERVICE_NAME,
- SERVICE_NODE_NAME,
-} from '../../../../common/elasticsearch_fieldnames';
-import { ProcessorEvent } from '../../../../common/processor_event';
-import { ServiceInstanceParams } from '.';
-import { getBucketSize } from '../../helpers/get_bucket_size';
-import {
- percentCgroupMemoryUsedScript,
- percentSystemMemoryUsedScript,
-} from '../../metrics/by_agent/shared/memory';
-import { withApmSpan } from '../../../utils/with_apm_span';
-
-export async function getServiceInstanceSystemMetricStats({
- environment,
- kuery,
- setup,
- serviceName,
- size,
- numBuckets,
-}: ServiceInstanceParams) {
- return withApmSpan('get_service_instance_system_metric_stats', async () => {
- const { apmEventClient, start, end } = setup;
-
- const { intervalString } = getBucketSize({ start, end, numBuckets });
-
- const systemMemoryFilter = {
- bool: {
- filter: [
- { exists: { field: METRIC_SYSTEM_FREE_MEMORY } },
- { exists: { field: METRIC_SYSTEM_TOTAL_MEMORY } },
- ],
- },
- };
-
- const cgroupMemoryFilter = {
- exists: { field: METRIC_CGROUP_MEMORY_USAGE_BYTES },
- };
-
- const cpuUsageFilter = { exists: { field: METRIC_PROCESS_CPU_PERCENT } };
-
- function withTimeseries(agg: T) {
- return {
- avg: { avg: agg },
- timeseries: {
- date_histogram: {
- field: '@timestamp',
- fixed_interval: intervalString,
- min_doc_count: 0,
- extended_bounds: {
- min: start,
- max: end,
- },
- },
- aggs: {
- avg: { avg: agg },
- },
- },
- };
- }
-
- const subAggs = {
- memory_usage_cgroup: {
- filter: cgroupMemoryFilter,
- aggs: withTimeseries({ script: percentCgroupMemoryUsedScript }),
- },
- memory_usage_system: {
- filter: systemMemoryFilter,
- aggs: withTimeseries({ script: percentSystemMemoryUsedScript }),
- },
- cpu_usage: {
- filter: cpuUsageFilter,
- aggs: withTimeseries({ field: METRIC_PROCESS_CPU_PERCENT }),
- },
- };
-
- const response = await apmEventClient.search({
- apm: {
- events: [ProcessorEvent.metric],
- },
- body: {
- size: 0,
- query: {
- bool: {
- filter: [
- { term: { [SERVICE_NAME]: serviceName } },
- ...rangeQuery(start, end),
- ...environmentQuery(environment),
- ...kqlQuery(kuery),
- ],
- should: [cgroupMemoryFilter, systemMemoryFilter, cpuUsageFilter],
- minimum_should_match: 1,
- },
- },
- aggs: {
- [SERVICE_NODE_NAME]: {
- terms: {
- field: SERVICE_NODE_NAME,
- missing: SERVICE_NODE_NAME_MISSING,
- size,
- },
- aggs: subAggs,
- },
- },
- },
- });
-
- return (
- response.aggregations?.[SERVICE_NODE_NAME].buckets.map(
- (serviceNodeBucket) => {
- const hasCGroupData =
- serviceNodeBucket.memory_usage_cgroup.avg.value !== null;
-
- const memoryMetricsKey = hasCGroupData
- ? 'memory_usage_cgroup'
- : 'memory_usage_system';
-
- return {
- serviceNodeName: String(serviceNodeBucket.key),
- cpuUsage: {
- value: serviceNodeBucket.cpu_usage.avg.value,
- timeseries: serviceNodeBucket.cpu_usage.timeseries.buckets.map(
- (dateBucket) => ({
- x: dateBucket.key,
- y: dateBucket.avg.value,
- })
- ),
- },
- memoryUsage: {
- value: serviceNodeBucket[memoryMetricsKey].avg.value,
- timeseries: serviceNodeBucket[
- memoryMetricsKey
- ].timeseries.buckets.map((dateBucket) => ({
- x: dateBucket.key,
- y: dateBucket.avg.value,
- })),
- },
- };
- }
- ) ?? []
- );
- });
-}
diff --git a/x-pack/plugins/apm/server/lib/services/get_service_instances/get_service_instance_transaction_stats.ts b/x-pack/plugins/apm/server/lib/services/get_service_instances/get_service_instance_transaction_stats.ts
deleted file mode 100644
index 94a5e54e9ace5..0000000000000
--- a/x-pack/plugins/apm/server/lib/services/get_service_instances/get_service_instance_transaction_stats.ts
+++ /dev/null
@@ -1,166 +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
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { EventOutcome } from '../../../../common/event_outcome';
-import {
- environmentQuery,
- rangeQuery,
- kqlQuery,
-} from '../../../../server/utils/queries';
-import { SERVICE_NODE_NAME_MISSING } from '../../../../common/service_nodes';
-import {
- EVENT_OUTCOME,
- SERVICE_NAME,
- SERVICE_NODE_NAME,
- TRANSACTION_TYPE,
-} from '../../../../common/elasticsearch_fieldnames';
-import { ServiceInstanceParams } from '.';
-import { getBucketSize } from '../../helpers/get_bucket_size';
-import {
- getProcessorEventForAggregatedTransactions,
- getTransactionDurationFieldForAggregatedTransactions,
-} from '../../helpers/aggregated_transactions';
-import { calculateThroughput } from '../../helpers/calculate_throughput';
-import { withApmSpan } from '../../../utils/with_apm_span';
-import {
- getLatencyAggregation,
- getLatencyValue,
-} from '../../helpers/latency_aggregation_type';
-
-export async function getServiceInstanceTransactionStats({
- environment,
- kuery,
- latencyAggregationType,
- setup,
- transactionType,
- serviceName,
- size,
- searchAggregatedTransactions,
- numBuckets,
-}: ServiceInstanceParams) {
- return withApmSpan('get_service_instance_transaction_stats', async () => {
- const { apmEventClient, start, end } = setup;
-
- const { intervalString, bucketSize } = getBucketSize({
- start,
- end,
- numBuckets,
- });
-
- const field = getTransactionDurationFieldForAggregatedTransactions(
- searchAggregatedTransactions
- );
-
- const subAggs = {
- ...getLatencyAggregation(latencyAggregationType, field),
- failures: {
- filter: {
- term: {
- [EVENT_OUTCOME]: EventOutcome.failure,
- },
- },
- },
- };
-
- const response = await apmEventClient.search({
- apm: {
- events: [
- getProcessorEventForAggregatedTransactions(
- searchAggregatedTransactions
- ),
- ],
- },
- body: {
- size: 0,
- query: {
- bool: {
- filter: [
- { term: { [SERVICE_NAME]: serviceName } },
- { term: { [TRANSACTION_TYPE]: transactionType } },
- ...rangeQuery(start, end),
- ...environmentQuery(environment),
- ...kqlQuery(kuery),
- ],
- },
- },
- aggs: {
- [SERVICE_NODE_NAME]: {
- terms: {
- field: SERVICE_NODE_NAME,
- missing: SERVICE_NODE_NAME_MISSING,
- size,
- },
- aggs: {
- ...subAggs,
- timeseries: {
- date_histogram: {
- field: '@timestamp',
- fixed_interval: intervalString,
- min_doc_count: 0,
- extended_bounds: {
- min: start,
- max: end,
- },
- },
- aggs: {
- ...subAggs,
- },
- },
- },
- },
- },
- },
- });
-
- const bucketSizeInMinutes = bucketSize / 60;
-
- return (
- response.aggregations?.[SERVICE_NODE_NAME].buckets.map(
- (serviceNodeBucket) => {
- const {
- doc_count: count,
- latency,
- key,
- failures,
- timeseries,
- } = serviceNodeBucket;
-
- return {
- serviceNodeName: String(key),
- errorRate: {
- value: failures.doc_count / count,
- timeseries: timeseries.buckets.map((dateBucket) => ({
- x: dateBucket.key,
- y: dateBucket.failures.doc_count / dateBucket.doc_count,
- })),
- },
- throughput: {
- value: calculateThroughput({ start, end, value: count }),
- timeseries: timeseries.buckets.map((dateBucket) => ({
- x: dateBucket.key,
- y: dateBucket.doc_count / bucketSizeInMinutes,
- })),
- },
- latency: {
- value: getLatencyValue({
- aggregation: latency,
- latencyAggregationType,
- }),
- timeseries: timeseries.buckets.map((dateBucket) => ({
- x: dateBucket.key,
- y: getLatencyValue({
- aggregation: dateBucket.latency,
- latencyAggregationType,
- }),
- })),
- },
- };
- }
- ) ?? []
- );
- });
-}
diff --git a/x-pack/plugins/apm/server/lib/services/get_service_instances/get_service_instances_system_metric_statistics.ts b/x-pack/plugins/apm/server/lib/services/get_service_instances/get_service_instances_system_metric_statistics.ts
new file mode 100644
index 0000000000000..1a33e9810dd5e
--- /dev/null
+++ b/x-pack/plugins/apm/server/lib/services/get_service_instances/get_service_instances_system_metric_statistics.ts
@@ -0,0 +1,208 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { AggregationOptionsByType } from 'typings/elasticsearch';
+import {
+ METRIC_CGROUP_MEMORY_USAGE_BYTES,
+ METRIC_PROCESS_CPU_PERCENT,
+ METRIC_SYSTEM_FREE_MEMORY,
+ METRIC_SYSTEM_TOTAL_MEMORY,
+ SERVICE_NAME,
+ SERVICE_NODE_NAME,
+} from '../../../../common/elasticsearch_fieldnames';
+import { ProcessorEvent } from '../../../../common/processor_event';
+import { SERVICE_NODE_NAME_MISSING } from '../../../../common/service_nodes';
+import { Coordinate } from '../../../../typings/timeseries';
+import { environmentQuery, kqlQuery, rangeQuery } from '../../../utils/queries';
+import { getBucketSize } from '../../helpers/get_bucket_size';
+import { Setup } from '../../helpers/setup_request';
+import {
+ percentCgroupMemoryUsedScript,
+ percentSystemMemoryUsedScript,
+} from '../../metrics/by_agent/shared/memory';
+import { withApmSpan } from '../../../utils/with_apm_span';
+
+interface ServiceInstanceSystemMetricPrimaryStatistics {
+ serviceNodeName: string;
+ cpuUsage: number | null;
+ memoryUsage: number | null;
+}
+
+interface ServiceInstanceSystemMetricComparisonStatistics {
+ serviceNodeName: string;
+ cpuUsage: Coordinate[];
+ memoryUsage: Coordinate[];
+}
+
+type ServiceInstanceSystemMetricStatistics = T extends true
+ ? ServiceInstanceSystemMetricComparisonStatistics
+ : ServiceInstanceSystemMetricPrimaryStatistics;
+
+export async function getServiceInstancesSystemMetricStatistics<
+ T extends true | false
+>({
+ environment,
+ kuery,
+ setup,
+ serviceName,
+ size,
+ start,
+ end,
+ serviceNodeIds,
+ numBuckets,
+ isComparisonSearch,
+}: {
+ setup: Setup;
+ serviceName: string;
+ start: number;
+ end: number;
+ numBuckets?: number;
+ serviceNodeIds?: string[];
+ environment?: string;
+ kuery?: string;
+ size?: number;
+ isComparisonSearch: T;
+}): Promise>> {
+ return withApmSpan(
+ 'get_service_instances_system_metric_statistics',
+ async () => {
+ const { apmEventClient } = setup;
+
+ const { intervalString } = getBucketSize({ start, end, numBuckets });
+
+ const systemMemoryFilter = {
+ bool: {
+ filter: [
+ { exists: { field: METRIC_SYSTEM_FREE_MEMORY } },
+ { exists: { field: METRIC_SYSTEM_TOTAL_MEMORY } },
+ ],
+ },
+ };
+
+ const cgroupMemoryFilter = {
+ exists: { field: METRIC_CGROUP_MEMORY_USAGE_BYTES },
+ };
+
+ const cpuUsageFilter = { exists: { field: METRIC_PROCESS_CPU_PERCENT } };
+
+ function withTimeseries(
+ agg: TParams
+ ) {
+ return {
+ ...(isComparisonSearch
+ ? {
+ avg: { avg: agg },
+ timeseries: {
+ date_histogram: {
+ field: '@timestamp',
+ fixed_interval: intervalString,
+ min_doc_count: 0,
+ extended_bounds: {
+ min: start,
+ max: end,
+ },
+ },
+ aggs: { avg: { avg: agg } },
+ },
+ }
+ : { avg: { avg: agg } }),
+ };
+ }
+
+ const subAggs = {
+ memory_usage_cgroup: {
+ filter: cgroupMemoryFilter,
+ aggs: withTimeseries({ script: percentCgroupMemoryUsedScript }),
+ },
+ memory_usage_system: {
+ filter: systemMemoryFilter,
+ aggs: withTimeseries({ script: percentSystemMemoryUsedScript }),
+ },
+ cpu_usage: {
+ filter: cpuUsageFilter,
+ aggs: withTimeseries({ field: METRIC_PROCESS_CPU_PERCENT }),
+ },
+ };
+
+ const response = await apmEventClient.search({
+ apm: {
+ events: [ProcessorEvent.metric],
+ },
+ body: {
+ size: 0,
+ query: {
+ bool: {
+ filter: [
+ { term: { [SERVICE_NAME]: serviceName } },
+ ...rangeQuery(start, end),
+ ...environmentQuery(environment),
+ ...kqlQuery(kuery),
+ ...(isComparisonSearch && serviceNodeIds
+ ? [{ terms: { [SERVICE_NODE_NAME]: serviceNodeIds } }]
+ : []),
+ ],
+ should: [cgroupMemoryFilter, systemMemoryFilter, cpuUsageFilter],
+ minimum_should_match: 1,
+ },
+ },
+ aggs: {
+ [SERVICE_NODE_NAME]: {
+ terms: {
+ field: SERVICE_NODE_NAME,
+ missing: SERVICE_NODE_NAME_MISSING,
+ ...(size ? { size } : {}),
+ ...(isComparisonSearch ? { include: serviceNodeIds } : {}),
+ },
+ aggs: subAggs,
+ },
+ },
+ },
+ });
+
+ return (
+ (response.aggregations?.[SERVICE_NODE_NAME].buckets.map(
+ (serviceNodeBucket) => {
+ const serviceNodeName = String(serviceNodeBucket.key);
+ const hasCGroupData =
+ serviceNodeBucket.memory_usage_cgroup.avg.value !== null;
+
+ const memoryMetricsKey = hasCGroupData
+ ? 'memory_usage_cgroup'
+ : 'memory_usage_system';
+
+ const cpuUsage =
+ // Timeseries is available when isComparisonSearch is true
+ 'timeseries' in serviceNodeBucket.cpu_usage
+ ? serviceNodeBucket.cpu_usage.timeseries.buckets.map(
+ (dateBucket) => ({
+ x: dateBucket.key,
+ y: dateBucket.avg.value,
+ })
+ )
+ : serviceNodeBucket.cpu_usage.avg.value;
+
+ const memoryUsageValue = serviceNodeBucket[memoryMetricsKey];
+ const memoryUsage =
+ // Timeseries is available when isComparisonSearch is true
+ 'timeseries' in memoryUsageValue
+ ? memoryUsageValue.timeseries.buckets.map((dateBucket) => ({
+ x: dateBucket.key,
+ y: dateBucket.avg.value,
+ }))
+ : serviceNodeBucket[memoryMetricsKey].avg.value;
+
+ return {
+ serviceNodeName,
+ cpuUsage,
+ memoryUsage,
+ };
+ }
+ ) as Array>) || []
+ );
+ }
+ );
+}
diff --git a/x-pack/plugins/apm/server/lib/services/get_service_instances/get_service_instances_transaction_statistics.ts b/x-pack/plugins/apm/server/lib/services/get_service_instances/get_service_instances_transaction_statistics.ts
new file mode 100644
index 0000000000000..ad54a231b52ef
--- /dev/null
+++ b/x-pack/plugins/apm/server/lib/services/get_service_instances/get_service_instances_transaction_statistics.ts
@@ -0,0 +1,202 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+import {
+ EVENT_OUTCOME,
+ SERVICE_NAME,
+ SERVICE_NODE_NAME,
+ TRANSACTION_TYPE,
+} from '../../../../common/elasticsearch_fieldnames';
+import { EventOutcome } from '../../../../common/event_outcome';
+import { LatencyAggregationType } from '../../../../common/latency_aggregation_types';
+import { SERVICE_NODE_NAME_MISSING } from '../../../../common/service_nodes';
+import { Coordinate } from '../../../../typings/timeseries';
+import { environmentQuery, kqlQuery, rangeQuery } from '../../../utils/queries';
+import {
+ getProcessorEventForAggregatedTransactions,
+ getTransactionDurationFieldForAggregatedTransactions,
+} from '../../helpers/aggregated_transactions';
+import { calculateThroughput } from '../../helpers/calculate_throughput';
+import { getBucketSize } from '../../helpers/get_bucket_size';
+import {
+ getLatencyAggregation,
+ getLatencyValue,
+} from '../../helpers/latency_aggregation_type';
+import { Setup } from '../../helpers/setup_request';
+import { withApmSpan } from '../../../utils/with_apm_span';
+
+interface ServiceInstanceTransactionPrimaryStatistics {
+ serviceNodeName: string;
+ errorRate: number;
+ latency: number;
+ throughput: number;
+}
+
+interface ServiceInstanceTransactionComparisonStatistics {
+ serviceNodeName: string;
+ errorRate: Coordinate[];
+ latency: Coordinate[];
+ throughput: Coordinate[];
+}
+
+type ServiceInstanceTransactionStatistics = T extends true
+ ? ServiceInstanceTransactionComparisonStatistics
+ : ServiceInstanceTransactionPrimaryStatistics;
+
+export async function getServiceInstancesTransactionStatistics<
+ T extends true | false
+>({
+ environment,
+ kuery,
+ latencyAggregationType,
+ setup,
+ transactionType,
+ serviceName,
+ size,
+ searchAggregatedTransactions,
+ start,
+ end,
+ serviceNodeIds,
+ numBuckets,
+ isComparisonSearch,
+}: {
+ latencyAggregationType: LatencyAggregationType;
+ setup: Setup;
+ serviceName: string;
+ transactionType: string;
+ searchAggregatedTransactions: boolean;
+ start: number;
+ end: number;
+ isComparisonSearch: T;
+ serviceNodeIds?: string[];
+ environment?: string;
+ kuery?: string;
+ size?: number;
+ numBuckets?: number;
+}): Promise>> {
+ return withApmSpan(
+ 'get_service_instances_transaction_statistics',
+ async () => {
+ const { apmEventClient } = setup;
+
+ const { intervalString, bucketSize } = getBucketSize({
+ start,
+ end,
+ numBuckets,
+ });
+
+ const field = getTransactionDurationFieldForAggregatedTransactions(
+ searchAggregatedTransactions
+ );
+
+ const subAggs = {
+ ...getLatencyAggregation(latencyAggregationType, field),
+ failures: {
+ filter: {
+ term: {
+ [EVENT_OUTCOME]: EventOutcome.failure,
+ },
+ },
+ },
+ };
+
+ const query = {
+ bool: {
+ filter: [
+ { term: { [SERVICE_NAME]: serviceName } },
+ { term: { [TRANSACTION_TYPE]: transactionType } },
+ ...rangeQuery(start, end),
+ ...environmentQuery(environment),
+ ...kqlQuery(kuery),
+ ...(isComparisonSearch && serviceNodeIds
+ ? [{ terms: { [SERVICE_NODE_NAME]: serviceNodeIds } }]
+ : []),
+ ],
+ },
+ };
+
+ const aggs = {
+ [SERVICE_NODE_NAME]: {
+ terms: {
+ field: SERVICE_NODE_NAME,
+ missing: SERVICE_NODE_NAME_MISSING,
+ ...(size ? { size } : {}),
+ ...(isComparisonSearch ? { include: serviceNodeIds } : {}),
+ },
+ aggs: isComparisonSearch
+ ? {
+ timeseries: {
+ date_histogram: {
+ field: '@timestamp',
+ fixed_interval: intervalString,
+ min_doc_count: 0,
+ extended_bounds: { min: start, max: end },
+ },
+ aggs: subAggs,
+ },
+ }
+ : subAggs,
+ },
+ };
+
+ const response = await apmEventClient.search({
+ apm: {
+ events: [
+ getProcessorEventForAggregatedTransactions(
+ searchAggregatedTransactions
+ ),
+ ],
+ },
+ body: { size: 0, query, aggs },
+ });
+
+ const bucketSizeInMinutes = bucketSize / 60;
+
+ return (
+ (response.aggregations?.[SERVICE_NODE_NAME].buckets.map(
+ (serviceNodeBucket) => {
+ const { doc_count: count, key } = serviceNodeBucket;
+ const serviceNodeName = String(key);
+
+ // Timeseries is returned when isComparisonSearch is true
+ if ('timeseries' in serviceNodeBucket) {
+ const { timeseries } = serviceNodeBucket;
+ return {
+ serviceNodeName,
+ errorRate: timeseries.buckets.map((dateBucket) => ({
+ x: dateBucket.key,
+ y: dateBucket.failures.doc_count / dateBucket.doc_count,
+ })),
+ throughput: timeseries.buckets.map((dateBucket) => ({
+ x: dateBucket.key,
+ y: dateBucket.doc_count / bucketSizeInMinutes,
+ })),
+ latency: timeseries.buckets.map((dateBucket) => ({
+ x: dateBucket.key,
+ y: getLatencyValue({
+ aggregation: dateBucket.latency,
+ latencyAggregationType,
+ }),
+ })),
+ };
+ } else {
+ const { failures, latency } = serviceNodeBucket;
+ return {
+ serviceNodeName,
+ errorRate: failures.doc_count / count,
+ latency: getLatencyValue({
+ aggregation: latency,
+ latencyAggregationType,
+ }),
+ throughput: calculateThroughput({ start, end, value: count }),
+ };
+ }
+ }
+ ) as Array>) || []
+ );
+ }
+ );
+}
diff --git a/x-pack/plugins/apm/server/lib/services/get_service_instances/index.ts b/x-pack/plugins/apm/server/lib/services/get_service_instances/primary_statistics.ts
similarity index 52%
rename from x-pack/plugins/apm/server/lib/services/get_service_instances/index.ts
rename to x-pack/plugins/apm/server/lib/services/get_service_instances/primary_statistics.ts
index 838753890a8cd..3cd98558eff02 100644
--- a/x-pack/plugins/apm/server/lib/services/get_service_instances/index.ts
+++ b/x-pack/plugins/apm/server/lib/services/get_service_instances/primary_statistics.ts
@@ -9,10 +9,10 @@ import { LatencyAggregationType } from '../../../../common/latency_aggregation_t
import { joinByKey } from '../../../../common/utils/join_by_key';
import { withApmSpan } from '../../../utils/with_apm_span';
import { Setup, SetupTimeRange } from '../../helpers/setup_request';
-import { getServiceInstanceSystemMetricStats } from './get_service_instance_system_metric_stats';
-import { getServiceInstanceTransactionStats } from './get_service_instance_transaction_stats';
+import { getServiceInstancesSystemMetricStatistics } from './get_service_instances_system_metric_statistics';
+import { getServiceInstancesTransactionStatistics } from './get_service_instances_transaction_statistics';
-export interface ServiceInstanceParams {
+interface ServiceInstancePrimaryStatisticsParams {
environment?: string;
kuery?: string;
latencyAggregationType: LatencyAggregationType;
@@ -21,21 +21,37 @@ export interface ServiceInstanceParams {
transactionType: string;
searchAggregatedTransactions: boolean;
size: number;
- numBuckets: number;
+ start: number;
+ end: number;
}
-export async function getServiceInstances(
- params: Omit
-) {
- return withApmSpan('get_service_instances', async () => {
+export async function getServiceInstancesPrimaryStatistics(
+ params: Omit
+): Promise<
+ Array<{
+ serviceNodeName: string;
+ errorRate?: number;
+ latency?: number;
+ throughput?: number;
+ cpuUsage?: number | null;
+ memoryUsage?: number | null;
+ }>
+> {
+ return withApmSpan('get_service_instances_primary_statistics', async () => {
const paramsForSubQueries = {
...params,
size: 50,
};
const [transactionStats, systemMetricStats] = await Promise.all([
- getServiceInstanceTransactionStats(paramsForSubQueries),
- getServiceInstanceSystemMetricStats(paramsForSubQueries),
+ getServiceInstancesTransactionStatistics({
+ ...paramsForSubQueries,
+ isComparisonSearch: false,
+ }),
+ getServiceInstancesSystemMetricStatistics({
+ ...paramsForSubQueries,
+ isComparisonSearch: false,
+ }),
]);
const stats = joinByKey(
diff --git a/x-pack/plugins/apm/server/routes/alerts/chart_preview.ts b/x-pack/plugins/apm/server/routes/alerts/chart_preview.ts
index 32f2238b0ddea..3bebcd49ec34a 100644
--- a/x-pack/plugins/apm/server/routes/alerts/chart_preview.ts
+++ b/x-pack/plugins/apm/server/routes/alerts/chart_preview.ts
@@ -35,12 +35,14 @@ export const transactionErrorRateChartPreview = createRoute({
options: { tags: ['access:apm'] },
handler: async ({ context, request }) => {
const setup = await setupRequest(context, request);
- const { _debug, ...alertParams } = context.params.query;
+ const { _inspect, ...alertParams } = context.params.query;
- return getTransactionErrorRateChartPreview({
+ const errorRateChartPreview = await getTransactionErrorRateChartPreview({
setup,
alertParams,
});
+
+ return { errorRateChartPreview };
},
});
@@ -50,11 +52,13 @@ export const transactionErrorCountChartPreview = createRoute({
options: { tags: ['access:apm'] },
handler: async ({ context, request }) => {
const setup = await setupRequest(context, request);
- const { _debug, ...alertParams } = context.params.query;
- return getTransactionErrorCountChartPreview({
+ const { _inspect, ...alertParams } = context.params.query;
+ const errorCountChartPreview = await getTransactionErrorCountChartPreview({
setup,
alertParams,
});
+
+ return { errorCountChartPreview };
},
});
@@ -64,11 +68,13 @@ export const transactionDurationChartPreview = createRoute({
options: { tags: ['access:apm'] },
handler: async ({ context, request }) => {
const setup = await setupRequest(context, request);
- const { _debug, ...alertParams } = context.params.query;
+ const { _inspect, ...alertParams } = context.params.query;
- return getTransactionDurationChartPreview({
+ const latencyChartPreview = await getTransactionDurationChartPreview({
alertParams,
setup,
});
+
+ return { latencyChartPreview };
},
});
diff --git a/x-pack/plugins/apm/server/routes/create_api/index.test.ts b/x-pack/plugins/apm/server/routes/create_api/index.test.ts
index 01d2797641805..9958b8dec0124 100644
--- a/x-pack/plugins/apm/server/routes/create_api/index.test.ts
+++ b/x-pack/plugins/apm/server/routes/create_api/index.test.ts
@@ -48,6 +48,49 @@ const getCoreMock = () => {
};
};
+const initApi = (params?: RouteParamsRT) => {
+ const { mock, context, createRouter, get, post } = getCoreMock();
+ const handlerMock = jest.fn();
+ createApi()
+ .add(() => ({
+ endpoint: 'GET /foo',
+ params,
+ options: { tags: ['access:apm'] },
+ handler: handlerMock,
+ }))
+ .init(mock, context);
+
+ const routeHandler = get.mock.calls[0][1];
+
+ const responseMock = {
+ ok: jest.fn(),
+ custom: jest.fn(),
+ };
+
+ const simulateRequest = (requestMock: any) => {
+ return routeHandler(
+ {},
+ {
+ // stub default values
+ params: {},
+ query: {},
+ body: null,
+ ...requestMock,
+ },
+ responseMock
+ );
+ };
+
+ return {
+ simulateRequest,
+ handlerMock,
+ createRouter,
+ get,
+ post,
+ responseMock,
+ };
+};
+
describe('createApi', () => {
it('registers a route with the server', () => {
const { mock, context, createRouter, post, get, put } = getCoreMock();
@@ -56,7 +99,7 @@ describe('createApi', () => {
.add(() => ({
endpoint: 'GET /foo',
options: { tags: ['access:apm'] },
- handler: async () => null,
+ handler: async () => ({}),
}))
.add(() => ({
endpoint: 'POST /bar',
@@ -64,21 +107,21 @@ describe('createApi', () => {
body: t.string,
}),
options: { tags: ['access:apm'] },
- handler: async () => null,
+ handler: async () => ({}),
}))
.add(() => ({
endpoint: 'PUT /baz',
options: {
tags: ['access:apm', 'access:apm_write'],
},
- handler: async () => null,
+ handler: async () => ({}),
}))
.add({
endpoint: 'GET /qux',
options: {
tags: ['access:apm', 'access:apm_write'],
},
- handler: async () => null,
+ handler: async () => ({}),
})
.init(mock, context);
@@ -122,102 +165,78 @@ describe('createApi', () => {
});
describe('when validating', () => {
- const initApi = (params?: RouteParamsRT) => {
- const { mock, context, createRouter, get, post } = getCoreMock();
- const handlerMock = jest.fn();
- createApi()
- .add(() => ({
- endpoint: 'GET /foo',
- params,
- options: { tags: ['access:apm'] },
- handler: handlerMock,
- }))
- .init(mock, context);
-
- const routeHandler = get.mock.calls[0][1];
-
- const responseMock = {
- ok: jest.fn(),
- internalError: jest.fn(),
- notFound: jest.fn(),
- forbidden: jest.fn(),
- badRequest: jest.fn(),
- };
-
- const simulate = (requestMock: any) => {
- return routeHandler(
- {},
- {
- // stub default values
- params: {},
- query: {},
- body: null,
- ...requestMock,
- },
- responseMock
- );
- };
-
- return { simulate, handlerMock, createRouter, get, post, responseMock };
- };
-
- it('adds a _debug query parameter by default', async () => {
- const { simulate, handlerMock, responseMock } = initApi();
-
- await simulate({ query: { _debug: 'true' } });
+ describe('_inspect', () => {
+ it('allows _inspect=true', async () => {
+ const { simulateRequest, handlerMock, responseMock } = initApi();
+ await simulateRequest({ query: { _inspect: 'true' } });
+
+ const params = handlerMock.mock.calls[0][0].context.params;
+ expect(params).toEqual({ query: { _inspect: true } });
+ expect(handlerMock).toHaveBeenCalledTimes(1);
+
+ // responds with ok
+ expect(responseMock.custom).not.toHaveBeenCalled();
+ expect(responseMock.ok).toHaveBeenCalledWith({
+ body: { _inspect: [] },
+ });
+ });
- expect(responseMock.badRequest).not.toHaveBeenCalled();
+ it('rejects _inspect=1', async () => {
+ const { simulateRequest, responseMock } = initApi();
+ await simulateRequest({ query: { _inspect: 1 } });
+
+ // responds with error handler
+ expect(responseMock.ok).not.toHaveBeenCalled();
+ expect(responseMock.custom).toHaveBeenCalledWith({
+ body: {
+ attributes: { _inspect: [] },
+ message:
+ 'Invalid value 1 supplied to : strict_keys/query: Partial<{| _inspect: pipe(JSON, boolean) |}>/_inspect: pipe(JSON, boolean)',
+ },
+ statusCode: 400,
+ });
+ });
- expect(handlerMock).toHaveBeenCalledTimes(1);
+ it('allows omitting _inspect', async () => {
+ const { simulateRequest, handlerMock, responseMock } = initApi();
+ await simulateRequest({ query: {} });
- expect(responseMock.ok).toHaveBeenCalled();
+ const params = handlerMock.mock.calls[0][0].context.params;
+ expect(params).toEqual({ query: { _inspect: false } });
+ expect(handlerMock).toHaveBeenCalledTimes(1);
- const params = handlerMock.mock.calls[0][0].context.params;
-
- expect(params).toEqual({
- query: {
- _debug: true,
- },
+ // responds with ok
+ expect(responseMock.custom).not.toHaveBeenCalled();
+ expect(responseMock.ok).toHaveBeenCalledWith({ body: {} });
});
-
- await simulate({
- query: {
- _debug: 1,
- },
- });
-
- expect(responseMock.badRequest).toHaveBeenCalled();
});
- it('throws if any parameters are used but no types are defined', async () => {
- const { simulate, responseMock } = initApi();
+ it('throws if unknown parameters are provided', async () => {
+ const { simulateRequest, responseMock } = initApi();
- await simulate({
- query: {
- _debug: true,
- extra: '',
- },
+ await simulateRequest({
+ query: { _inspect: true, extra: '' },
});
- expect(responseMock.badRequest).toHaveBeenCalledTimes(1);
+ expect(responseMock.custom).toHaveBeenCalledTimes(1);
- await simulate({
+ await simulateRequest({
body: { foo: 'bar' },
});
- expect(responseMock.badRequest).toHaveBeenCalledTimes(2);
+ expect(responseMock.custom).toHaveBeenCalledTimes(2);
- await simulate({
+ await simulateRequest({
params: {
foo: 'bar',
},
});
- expect(responseMock.badRequest).toHaveBeenCalledTimes(3);
+ expect(responseMock.custom).toHaveBeenCalledTimes(3);
});
it('validates path parameters', async () => {
- const { simulate, handlerMock, responseMock } = initApi(
+ const { simulateRequest, handlerMock, responseMock } = initApi(
t.type({
path: t.type({
foo: t.string,
@@ -225,7 +244,7 @@ describe('createApi', () => {
})
);
- await simulate({
+ await simulateRequest({
params: {
foo: 'bar',
},
@@ -234,7 +253,7 @@ describe('createApi', () => {
expect(handlerMock).toHaveBeenCalledTimes(1);
expect(responseMock.ok).toHaveBeenCalledTimes(1);
- expect(responseMock.badRequest).not.toHaveBeenCalled();
+ expect(responseMock.custom).not.toHaveBeenCalled();
const params = handlerMock.mock.calls[0][0].context.params;
@@ -243,48 +262,48 @@ describe('createApi', () => {
foo: 'bar',
},
query: {
- _debug: false,
+ _inspect: false,
},
});
- await simulate({
+ await simulateRequest({
params: {
bar: 'foo',
},
});
- expect(responseMock.badRequest).toHaveBeenCalledTimes(1);
+ expect(responseMock.custom).toHaveBeenCalledTimes(1);
- await simulate({
+ await simulateRequest({
params: {
foo: 9,
},
});
- expect(responseMock.badRequest).toHaveBeenCalledTimes(2);
+ expect(responseMock.custom).toHaveBeenCalledTimes(2);
- await simulate({
+ await simulateRequest({
params: {
foo: 'bar',
extra: '',
},
});
- expect(responseMock.badRequest).toHaveBeenCalledTimes(3);
+ expect(responseMock.custom).toHaveBeenCalledTimes(3);
});
it('validates body parameters', async () => {
- const { simulate, handlerMock, responseMock } = initApi(
+ const { simulateRequest, handlerMock, responseMock } = initApi(
t.type({
body: t.string,
})
);
- await simulate({
+ await simulateRequest({
body: '',
});
- expect(responseMock.badRequest).not.toHaveBeenCalled();
+ expect(responseMock.custom).not.toHaveBeenCalled();
expect(handlerMock).toHaveBeenCalledTimes(1);
expect(responseMock.ok).toHaveBeenCalledTimes(1);
@@ -293,19 +312,19 @@ describe('createApi', () => {
expect(params).toEqual({
body: '',
query: {
- _debug: false,
+ _inspect: false,
},
});
- await simulate({
+ await simulateRequest({
body: null,
});
- expect(responseMock.badRequest).toHaveBeenCalledTimes(1);
+ expect(responseMock.custom).toHaveBeenCalledTimes(1);
});
it('validates query parameters', async () => {
- const { simulate, handlerMock, responseMock } = initApi(
+ const { simulateRequest, handlerMock, responseMock } = initApi(
t.type({
query: t.type({
bar: t.string,
@@ -314,15 +333,15 @@ describe('createApi', () => {
})
);
- await simulate({
+ await simulateRequest({
query: {
bar: '',
- _debug: 'true',
+ _inspect: 'true',
filterNames: JSON.stringify(['hostName', 'agentName']),
},
});
- expect(responseMock.badRequest).not.toHaveBeenCalled();
+ expect(responseMock.custom).not.toHaveBeenCalled();
expect(handlerMock).toHaveBeenCalledTimes(1);
expect(responseMock.ok).toHaveBeenCalledTimes(1);
@@ -331,19 +350,19 @@ describe('createApi', () => {
expect(params).toEqual({
query: {
bar: '',
- _debug: true,
+ _inspect: true,
filterNames: ['hostName', 'agentName'],
},
});
- await simulate({
+ await simulateRequest({
query: {
bar: '',
foo: '',
},
});
- expect(responseMock.badRequest).toHaveBeenCalledTimes(1);
+ expect(responseMock.custom).toHaveBeenCalledTimes(1);
});
});
});
diff --git a/x-pack/plugins/apm/server/routes/create_api/index.ts b/x-pack/plugins/apm/server/routes/create_api/index.ts
index 46f2628cc73d5..13e70a2043cf0 100644
--- a/x-pack/plugins/apm/server/routes/create_api/index.ts
+++ b/x-pack/plugins/apm/server/routes/create_api/index.ts
@@ -11,19 +11,20 @@ import { schema } from '@kbn/config-schema';
import * as t from 'io-ts';
import { PathReporter } from 'io-ts/lib/PathReporter';
import { isLeft } from 'fp-ts/lib/Either';
-import { KibanaResponseFactory, RouteRegistrar } from 'src/core/server';
+import { KibanaRequest, RouteRegistrar } from 'src/core/server';
import { RequestAbortedError } from '@elastic/elasticsearch/lib/errors';
import agent from 'elastic-apm-node';
+import { parseMethod } from '../../../common/apm_api/parse_endpoint';
import { merge } from '../../../common/runtime_types/merge';
import { strictKeysRt } from '../../../common/runtime_types/strict_keys_rt';
import { APMConfig } from '../..';
-import { ServerAPI } from '../typings';
+import { InspectResponse, RouteParamsRT, ServerAPI } from '../typings';
import { jsonRt } from '../../../common/runtime_types/json_rt';
import type { ApmPluginRequestHandlerContext } from '../typings';
-const debugRt = t.exact(
+const inspectRt = t.exact(
t.partial({
- query: t.exact(t.partial({ _debug: jsonRt.pipe(t.boolean) })),
+ query: t.exact(t.partial({ _inspect: jsonRt.pipe(t.boolean) })),
})
);
@@ -32,6 +33,11 @@ type RouteOrRouteFactoryFn = Parameters['add']>[0];
const isNotEmpty = (val: any) =>
val !== undefined && val !== null && !(isPlainObject(val) && isEmpty(val));
+export const inspectableEsQueriesMap = new WeakMap<
+ KibanaRequest,
+ InspectResponse
+>();
+
export function createApi() {
const routes: RouteOrRouteFactoryFn[] = [];
const api: ServerAPI<{}> = {
@@ -58,24 +64,10 @@ export function createApi() {
const { params, endpoint, options, handler } = route;
const [method, path] = endpoint.split(' ');
-
- const typedRouterMethod = method.trim().toLowerCase() as
- | 'get'
- | 'post'
- | 'put'
- | 'delete';
-
- if (!['get', 'post', 'put', 'delete'].includes(typedRouterMethod)) {
- throw new Error(
- "Couldn't register route, as endpoint was not prefixed with a valid HTTP method"
- );
- }
+ const typedRouterMethod = parseMethod(method);
// For all runtime types with props, we create an exact
// version that will strip all keys that are unvalidated.
-
- const paramsRt = params ? merge([params, debugRt]) : debugRt;
-
const anyObject = schema.object({}, { unknowns: 'allow' });
(router[typedRouterMethod] as RouteRegistrar<
@@ -102,56 +94,52 @@ export function createApi() {
});
}
- try {
- const paramMap = pickBy(
- {
- path: request.params,
- body: request.body,
- query: {
- _debug: 'false',
- ...request.query,
- },
- },
- isNotEmpty
- );
-
- const result = strictKeysRt(paramsRt).decode(paramMap);
+ // init debug queries
+ inspectableEsQueriesMap.set(request, []);
- if (isLeft(result)) {
- throw Boom.badRequest(PathReporter.report(result)[0]);
- }
+ try {
+ const validParams = validateParams(request, params);
const data = await handler({
request,
context: {
...context,
plugins,
- // Only return values for parameters that have runtime types,
- // but always include query as _debug is always set even if
- // it's not defined in the route.
- params: mergeLodash(
- { query: { _debug: false } },
- pickBy(result.right, isNotEmpty)
- ),
+ params: validParams,
config,
logger,
},
});
- return response.ok({ body: data as any });
+ const body = { ...data };
+ if (validParams.query._inspect) {
+ body._inspect = inspectableEsQueriesMap.get(request);
+ }
+
+ // cleanup
+ inspectableEsQueriesMap.delete(request);
+
+ return response.ok({ body });
} catch (error) {
+ const opts = {
+ statusCode: 500,
+ body: {
+ message: error.message,
+ attributes: {
+ _inspect: inspectableEsQueriesMap.get(request),
+ },
+ },
+ };
+
if (Boom.isBoom(error)) {
- return convertBoomToKibanaResponse(error, response);
+ opts.statusCode = error.output.statusCode;
}
if (error instanceof RequestAbortedError) {
- return response.custom({
- statusCode: 499,
- body: {
- message: 'Client closed request',
- },
- });
+ opts.statusCode = 499;
+ opts.body.message = 'Client closed request';
}
- throw error;
+
+ return response.custom(opts);
}
}
);
@@ -162,22 +150,35 @@ export function createApi() {
return api;
}
-function convertBoomToKibanaResponse(
- error: Boom.Boom,
- response: KibanaResponseFactory
+function validateParams(
+ request: KibanaRequest,
+ params: RouteParamsRT | undefined
) {
- const opts = { body: { message: error.message } };
- switch (error.output.statusCode) {
- case 404:
- return response.notFound(opts);
-
- case 400:
- return response.badRequest(opts);
+ const paramsRt = params ? merge([params, inspectRt]) : inspectRt;
+ const paramMap = pickBy(
+ {
+ path: request.params,
+ body: request.body,
+ query: {
+ _inspect: 'false',
+ // @ts-ignore
+ ...request.query,
+ },
+ },
+ isNotEmpty
+ );
- case 403:
- return response.forbidden(opts);
+ const result = strictKeysRt(paramsRt).decode(paramMap);
- default:
- throw error;
+ if (isLeft(result)) {
+ throw Boom.badRequest(PathReporter.report(result)[0]);
}
+
+ // Only return values for parameters that have runtime types,
+ // but always include query as _inspect is always set even if
+ // it's not defined in the route.
+ return mergeLodash(
+ { query: { _inspect: false } },
+ pickBy(result.right, isNotEmpty)
+ );
}
diff --git a/x-pack/plugins/apm/server/routes/create_apm_api.ts b/x-pack/plugins/apm/server/routes/create_apm_api.ts
index 2bd7e25e848c8..2b5fb0b516ab5 100644
--- a/x-pack/plugins/apm/server/routes/create_apm_api.ts
+++ b/x-pack/plugins/apm/server/routes/create_apm_api.ts
@@ -30,7 +30,8 @@ import {
serviceDependenciesRoute,
serviceMetadataDetailsRoute,
serviceMetadataIconsRoute,
- serviceInstancesRoute,
+ serviceInstancesPrimaryStatisticsRoute,
+ serviceInstancesComparisonStatisticsRoute,
serviceProfilingStatisticsRoute,
serviceProfilingTimelineRoute,
} from './services';
@@ -134,7 +135,8 @@ const createApmApi = () => {
.add(serviceDependenciesRoute)
.add(serviceMetadataDetailsRoute)
.add(serviceMetadataIconsRoute)
- .add(serviceInstancesRoute)
+ .add(serviceInstancesPrimaryStatisticsRoute)
+ .add(serviceInstancesComparisonStatisticsRoute)
.add(serviceErrorGroupsComparisonStatisticsRoute)
.add(serviceProfilingTimelineRoute)
.add(serviceProfilingStatisticsRoute)
diff --git a/x-pack/plugins/apm/server/routes/create_route.ts b/x-pack/plugins/apm/server/routes/create_route.ts
index 4d30e706cdd5c..d74aac0992eb4 100644
--- a/x-pack/plugins/apm/server/routes/create_route.ts
+++ b/x-pack/plugins/apm/server/routes/create_route.ts
@@ -6,20 +6,20 @@
*/
import { CoreSetup } from 'src/core/server';
-import { Route, RouteParamsRT } from './typings';
+import { HandlerReturn, Route, RouteParamsRT } from './typings';
export function createRoute<
TEndpoint extends string,
- TRouteParamsRT extends RouteParamsRT | undefined = undefined,
- TReturn = unknown
+ TReturn extends HandlerReturn,
+ TRouteParamsRT extends RouteParamsRT | undefined = undefined
>(
route: Route
): Route;
export function createRoute<
TEndpoint extends string,
- TRouteParamsRT extends RouteParamsRT | undefined = undefined,
- TReturn = unknown
+ TReturn extends HandlerReturn,
+ TRouteParamsRT extends RouteParamsRT | undefined = undefined
>(
route: (core: CoreSetup) => Route
): (core: CoreSetup) => Route;
diff --git a/x-pack/plugins/apm/server/routes/environments.ts b/x-pack/plugins/apm/server/routes/environments.ts
index 448591f7e143f..4aa7d7e6d412f 100644
--- a/x-pack/plugins/apm/server/routes/environments.ts
+++ b/x-pack/plugins/apm/server/routes/environments.ts
@@ -30,10 +30,12 @@ export const environmentsRoute = createRoute({
setup
);
- return getEnvironments({
+ const environments = await getEnvironments({
setup,
serviceName,
searchAggregatedTransactions,
});
+
+ return { environments };
},
});
diff --git a/x-pack/plugins/apm/server/routes/errors.ts b/x-pack/plugins/apm/server/routes/errors.ts
index 710e614165aa5..f69d3fc9631d1 100644
--- a/x-pack/plugins/apm/server/routes/errors.ts
+++ b/x-pack/plugins/apm/server/routes/errors.ts
@@ -36,7 +36,7 @@ export const errorsRoute = createRoute({
const { serviceName } = params.path;
const { environment, kuery, sortField, sortDirection } = params.query;
- return getErrorGroups({
+ const errorGroups = await getErrorGroups({
environment,
kuery,
serviceName,
@@ -44,6 +44,8 @@ export const errorsRoute = createRoute({
sortDirection,
setup,
});
+
+ return { errorGroups };
},
});
diff --git a/x-pack/plugins/apm/server/routes/index_pattern.ts b/x-pack/plugins/apm/server/routes/index_pattern.ts
index ed1354a219164..fd7d2120ab6f5 100644
--- a/x-pack/plugins/apm/server/routes/index_pattern.ts
+++ b/x-pack/plugins/apm/server/routes/index_pattern.ts
@@ -21,10 +21,13 @@ export const staticIndexPatternRoute = createRoute((core) => ({
getInternalSavedObjectsClient(core),
]);
- await createStaticIndexPattern(setup, context, savedObjectsClient);
+ const didCreateIndexPattern = await createStaticIndexPattern(
+ setup,
+ context,
+ savedObjectsClient
+ );
- // send empty response regardless of outcome
- return undefined;
+ return { created: didCreateIndexPattern };
},
}));
@@ -41,6 +44,8 @@ export const apmIndexPatternTitleRoute = createRoute({
endpoint: 'GET /api/apm/index_pattern/title',
options: { tags: ['access:apm'] },
handler: async ({ context }) => {
- return getApmIndexPatternTitle(context);
+ return {
+ indexPatternTitle: getApmIndexPatternTitle(context),
+ };
},
});
diff --git a/x-pack/plugins/apm/server/routes/observability_overview.ts b/x-pack/plugins/apm/server/routes/observability_overview.ts
index 1a1fa799639bc..b9c0a76b6fb90 100644
--- a/x-pack/plugins/apm/server/routes/observability_overview.ts
+++ b/x-pack/plugins/apm/server/routes/observability_overview.ts
@@ -9,7 +9,7 @@ import * as t from 'io-ts';
import { setupRequest } from '../lib/helpers/setup_request';
import { getServiceCount } from '../lib/observability_overview/get_service_count';
import { getTransactionCoordinates } from '../lib/observability_overview/get_transaction_coordinates';
-import { hasData } from '../lib/observability_overview/has_data';
+import { getHasData } from '../lib/observability_overview/has_data';
import { createRoute } from './create_route';
import { rangeRt } from './default_api_types';
import { getSearchAggregatedTransactions } from '../lib/helpers/aggregated_transactions';
@@ -20,7 +20,8 @@ export const observabilityOverviewHasDataRoute = createRoute({
options: { tags: ['access:apm'] },
handler: async ({ context, request }) => {
const setup = await setupRequest(context, request);
- return await hasData({ setup });
+ const res = await getHasData({ setup });
+ return { hasData: res };
},
});
diff --git a/x-pack/plugins/apm/server/routes/rum_client.ts b/x-pack/plugins/apm/server/routes/rum_client.ts
index ecf56e2aec246..3156acb469a72 100644
--- a/x-pack/plugins/apm/server/routes/rum_client.ts
+++ b/x-pack/plugins/apm/server/routes/rum_client.ts
@@ -79,12 +79,14 @@ export const rumPageLoadDistributionRoute = createRoute({
query: { minPercentile, maxPercentile, urlQuery },
} = context.params;
- return getPageLoadDistribution({
+ const pageLoadDistribution = await getPageLoadDistribution({
setup,
minPercentile,
maxPercentile,
urlQuery,
});
+
+ return { pageLoadDistribution };
},
});
@@ -105,13 +107,15 @@ export const rumPageLoadDistBreakdownRoute = createRoute({
query: { minPercentile, maxPercentile, breakdown, urlQuery },
} = context.params;
- return getPageLoadDistBreakdown({
+ const pageLoadDistBreakdown = await getPageLoadDistBreakdown({
setup,
minPercentile: Number(minPercentile),
maxPercentile: Number(maxPercentile),
breakdown,
urlQuery,
});
+
+ return { pageLoadDistBreakdown };
},
});
@@ -145,7 +149,8 @@ export const rumServicesRoute = createRoute({
handler: async ({ context, request }) => {
const setup = await setupRequest(context, request);
- return getRumServices({ setup });
+ const rumServices = await getRumServices({ setup });
+ return { rumServices };
},
});
@@ -322,12 +327,14 @@ function createLocalFiltersRoute<
setup,
});
- return getLocalUIFilters({
+ const localUiFilters = await getLocalUIFilters({
projection,
setup,
uiFilters,
localFilterNames: filterNames,
});
+
+ return { localUiFilters };
},
});
}
diff --git a/x-pack/plugins/apm/server/routes/service_nodes.ts b/x-pack/plugins/apm/server/routes/service_nodes.ts
index e65b0b679da5a..e9060688c63a6 100644
--- a/x-pack/plugins/apm/server/routes/service_nodes.ts
+++ b/x-pack/plugins/apm/server/routes/service_nodes.ts
@@ -26,10 +26,7 @@ export const serviceNodesRoute = createRoute({
const { serviceName } = params.path;
const { kuery } = params.query;
- return getServiceNodes({
- kuery,
- setup,
- serviceName,
- });
+ const serviceNodes = await getServiceNodes({ kuery, setup, serviceName });
+ return { serviceNodes };
},
});
diff --git a/x-pack/plugins/apm/server/routes/services.ts b/x-pack/plugins/apm/server/routes/services.ts
index bac970416792b..b4d25ca8b2a06 100644
--- a/x-pack/plugins/apm/server/routes/services.ts
+++ b/x-pack/plugins/apm/server/routes/services.ts
@@ -8,7 +8,13 @@
import Boom from '@hapi/boom';
import * as t from 'io-ts';
import { uniq } from 'lodash';
+import {
+ LatencyAggregationType,
+ latencyAggregationTypeRt,
+} from '../../common/latency_aggregation_types';
+import { ProfilingValueType } from '../../common/profiling';
import { isoToEpochRt } from '../../common/runtime_types/iso_to_epoch_rt';
+import { jsonRt } from '../../common/runtime_types/json_rt';
import { toNumberRt } from '../../common/runtime_types/to_number_rt';
import { getSearchAggregatedTransactions } from '../lib/helpers/aggregated_transactions';
import { setupRequest } from '../lib/helpers/setup_request';
@@ -16,31 +22,26 @@ import { getServiceAnnotations } from '../lib/services/annotations';
import { getServices } from '../lib/services/get_services';
import { getServiceAgentName } from '../lib/services/get_service_agent_name';
import { getServiceDependencies } from '../lib/services/get_service_dependencies';
-import { getServiceErrorGroupPrimaryStatistics } from '../lib/services/get_service_error_groups/get_service_error_group_primary_statistics';
import { getServiceErrorGroupPeriods } from '../lib/services/get_service_error_groups/get_service_error_group_comparison_statistics';
-import { getServiceInstances } from '../lib/services/get_service_instances';
+import { getServiceErrorGroupPrimaryStatistics } from '../lib/services/get_service_error_groups/get_service_error_group_primary_statistics';
+import { getServiceInstancesComparisonStatisticsPeriods } from '../lib/services/get_service_instances/comparison_statistics';
+import { getServiceInstancesPrimaryStatistics } from '../lib/services/get_service_instances/primary_statistics';
import { getServiceMetadataDetails } from '../lib/services/get_service_metadata_details';
import { getServiceMetadataIcons } from '../lib/services/get_service_metadata_icons';
import { getServiceNodeMetadata } from '../lib/services/get_service_node_metadata';
import { getServiceTransactionTypes } from '../lib/services/get_service_transaction_types';
import { getThroughput } from '../lib/services/get_throughput';
-import { createRoute } from './create_route';
+import { getServiceProfilingStatistics } from '../lib/services/profiling/get_service_profiling_statistics';
+import { getServiceProfilingTimeline } from '../lib/services/profiling/get_service_profiling_timeline';
import { offsetPreviousPeriodCoordinates } from '../utils/offset_previous_period_coordinate';
-import { jsonRt } from '../../common/runtime_types/json_rt';
+import { withApmSpan } from '../utils/with_apm_span';
+import { createRoute } from './create_route';
import {
comparisonRangeRt,
environmentRt,
kueryRt,
rangeRt,
} from './default_api_types';
-import { withApmSpan } from '../utils/with_apm_span';
-import { getServiceProfilingStatistics } from '../lib/services/profiling/get_service_profiling_statistics';
-import { getServiceProfilingTimeline } from '../lib/services/profiling/get_service_profiling_timeline';
-import { ProfilingValueType } from '../../common/profiling';
-import {
- latencyAggregationTypeRt,
- LatencyAggregationType,
-} from '../../common/latency_aggregation_types';
export const servicesRoute = createRoute({
endpoint: 'GET /api/apm/services',
@@ -55,15 +56,13 @@ export const servicesRoute = createRoute({
setup
);
- const services = await getServices({
+ return getServices({
environment,
kuery,
setup,
searchAggregatedTransactions,
logger: context.logger,
});
-
- return services;
},
});
@@ -433,8 +432,9 @@ export const serviceThroughputRoute = createRoute({
},
});
-export const serviceInstancesRoute = createRoute({
- endpoint: 'GET /api/apm/services/{serviceName}/service_overview_instances',
+export const serviceInstancesPrimaryStatisticsRoute = createRoute({
+ endpoint:
+ 'GET /api/apm/services/{serviceName}/service_overview_instances/primary_statistics',
params: t.type({
path: t.type({
serviceName: t.string,
@@ -443,11 +443,60 @@ export const serviceInstancesRoute = createRoute({
t.type({
latencyAggregationType: latencyAggregationTypeRt,
transactionType: t.string,
+ }),
+ environmentRt,
+ kueryRt,
+ rangeRt,
+ ]),
+ }),
+ options: { tags: ['access:apm'] },
+ handler: async ({ context, request }) => {
+ const setup = await setupRequest(context, request);
+ const { serviceName } = context.params.path;
+ const { environment, kuery, transactionType } = context.params.query;
+ const latencyAggregationType = (context.params.query
+ .latencyAggregationType as unknown) as LatencyAggregationType;
+
+ const searchAggregatedTransactions = await getSearchAggregatedTransactions(
+ setup
+ );
+
+ const { start, end } = setup;
+
+ const serviceInstances = await getServiceInstancesPrimaryStatistics({
+ environment,
+ kuery,
+ latencyAggregationType,
+ serviceName,
+ setup,
+ transactionType,
+ searchAggregatedTransactions,
+ start,
+ end,
+ });
+
+ return { serviceInstances };
+ },
+});
+
+export const serviceInstancesComparisonStatisticsRoute = createRoute({
+ endpoint:
+ 'GET /api/apm/services/{serviceName}/service_overview_instances/comparison_statistics',
+ params: t.type({
+ path: t.type({
+ serviceName: t.string,
+ }),
+ query: t.intersection([
+ t.type({
+ latencyAggregationType: latencyAggregationTypeRt,
+ transactionType: t.string,
+ serviceNodeIds: jsonRt.pipe(t.array(t.string)),
numBuckets: toNumberRt,
}),
environmentRt,
kueryRt,
rangeRt,
+ comparisonRangeRt,
]),
}),
options: { tags: ['access:apm'] },
@@ -458,6 +507,9 @@ export const serviceInstancesRoute = createRoute({
environment,
kuery,
transactionType,
+ comparisonStart,
+ comparisonEnd,
+ serviceNodeIds,
numBuckets,
} = context.params.query;
const latencyAggregationType = (context.params.query
@@ -467,7 +519,7 @@ export const serviceInstancesRoute = createRoute({
setup
);
- return getServiceInstances({
+ return getServiceInstancesComparisonStatisticsPeriods({
environment,
kuery,
latencyAggregationType,
@@ -476,6 +528,9 @@ export const serviceInstancesRoute = createRoute({
transactionType,
searchAggregatedTransactions,
numBuckets,
+ serviceNodeIds,
+ comparisonStart,
+ comparisonEnd,
});
},
});
@@ -503,12 +558,14 @@ export const serviceDependenciesRoute = createRoute({
const { serviceName } = context.params.path;
const { environment, numBuckets } = context.params.query;
- return getServiceDependencies({
+ const serviceDependencies = await getServiceDependencies({
serviceName,
environment,
setup,
numBuckets,
});
+
+ return { serviceDependencies };
},
});
@@ -531,12 +588,14 @@ export const serviceProfilingTimelineRoute = createRoute({
query: { environment, kuery },
} = context.params;
- return getServiceProfilingTimeline({
+ const profilingTimeline = await getServiceProfilingTimeline({
kuery,
setup,
serviceName,
environment,
});
+
+ return { profilingTimeline };
},
});
diff --git a/x-pack/plugins/apm/server/routes/settings/agent_configuration.ts b/x-pack/plugins/apm/server/routes/settings/agent_configuration.ts
index e3ed398171d01..31e8d6cc1e9f0 100644
--- a/x-pack/plugins/apm/server/routes/settings/agent_configuration.ts
+++ b/x-pack/plugins/apm/server/routes/settings/agent_configuration.ts
@@ -31,7 +31,8 @@ export const agentConfigurationRoute = createRoute({
options: { tags: ['access:apm'] },
handler: async ({ context, request }) => {
const setup = await setupRequest(context, request);
- return await listConfigurations({ setup });
+ const configurations = await listConfigurations({ setup });
+ return { configurations };
},
});
@@ -204,10 +205,12 @@ export const listAgentConfigurationServicesRoute = createRoute({
const searchAggregatedTransactions = await getSearchAggregatedTransactions(
setup
);
- return await getServiceNames({
+ const serviceNames = await getServiceNames({
setup,
searchAggregatedTransactions,
});
+
+ return { serviceNames };
},
});
@@ -225,11 +228,13 @@ export const listAgentConfigurationEnvironmentsRoute = createRoute({
setup
);
- return await getEnvironments({
+ const environments = await getEnvironments({
serviceName,
setup,
searchAggregatedTransactions,
});
+
+ return { environments };
},
});
diff --git a/x-pack/plugins/apm/server/routes/settings/anomaly_detection.ts b/x-pack/plugins/apm/server/routes/settings/anomaly_detection.ts
index e5922d9ed3e94..de7f35c4081bc 100644
--- a/x-pack/plugins/apm/server/routes/settings/anomaly_detection.ts
+++ b/x-pack/plugins/apm/server/routes/settings/anomaly_detection.ts
@@ -71,6 +71,8 @@ export const createAnomalyDetectionJobsRoute = createRoute({
licensingPlugin: context.licensing,
featureName: 'ml',
});
+
+ return { jobCreated: true };
},
});
@@ -85,10 +87,12 @@ export const anomalyDetectionEnvironmentsRoute = createRoute({
setup
);
- return await getAllEnvironments({
+ const environments = await getAllEnvironments({
setup,
searchAggregatedTransactions,
includeMissing: true,
});
+
+ return { environments };
},
});
diff --git a/x-pack/plugins/apm/server/routes/settings/apm_indices.ts b/x-pack/plugins/apm/server/routes/settings/apm_indices.ts
index 0d47579f50aec..91057c97579e4 100644
--- a/x-pack/plugins/apm/server/routes/settings/apm_indices.ts
+++ b/x-pack/plugins/apm/server/routes/settings/apm_indices.ts
@@ -18,7 +18,8 @@ export const apmIndexSettingsRoute = createRoute({
endpoint: 'GET /api/apm/settings/apm-index-settings',
options: { tags: ['access:apm'] },
handler: async ({ context }) => {
- return await getApmIndexSettings({ context });
+ const apmIndexSettings = await getApmIndexSettings({ context });
+ return { apmIndexSettings };
},
});
diff --git a/x-pack/plugins/apm/server/routes/settings/custom_link.ts b/x-pack/plugins/apm/server/routes/settings/custom_link.ts
index fc217bef772d0..a6ab553f09419 100644
--- a/x-pack/plugins/apm/server/routes/settings/custom_link.ts
+++ b/x-pack/plugins/apm/server/routes/settings/custom_link.ts
@@ -52,7 +52,8 @@ export const listCustomLinksRoute = createRoute({
const { query } = context.params;
// picks only the items listed in FILTER_OPTIONS
const filters = pick(query, FILTER_OPTIONS);
- return await listCustomLinks({ setup, filters });
+ const customLinks = await listCustomLinks({ setup, filters });
+ return { customLinks };
},
});
diff --git a/x-pack/plugins/apm/server/routes/typings.ts b/x-pack/plugins/apm/server/routes/typings.ts
index 4d3e07040f76b..1575041fb2f45 100644
--- a/x-pack/plugins/apm/server/routes/typings.ts
+++ b/x-pack/plugins/apm/server/routes/typings.ts
@@ -13,7 +13,7 @@ import {
Logger,
} from 'src/core/server';
import { Observable } from 'rxjs';
-import { RequiredKeys } from 'utility-types';
+import { RequiredKeys, DeepPartial } from 'utility-types';
import { ObservabilityPluginSetup } from '../../../observability/server';
import { LicensingApiRequestHandlerContext } from '../../../licensing/server';
import { SecurityPluginSetup } from '../../../security/server';
@@ -21,6 +21,20 @@ import { MlPluginSetup } from '../../../ml/server';
import { FetchOptions } from '../../common/fetch_options';
import { APMConfig } from '..';
+export type HandlerReturn = Record;
+
+interface InspectQueryParam {
+ query: { _inspect: boolean };
+}
+
+export type InspectResponse = Array<{
+ response: any;
+ duration: number;
+ requestType: string;
+ requestParams: Record