diff --git a/src/plugins/data/public/ui/filter_bar/filter_options.tsx b/src/plugins/data/public/ui/filter_bar/filter_options.tsx
index 3fb7f198d5466a..b97e0e33f2400b 100644
--- a/src/plugins/data/public/ui/filter_bar/filter_options.tsx
+++ b/src/plugins/data/public/ui/filter_bar/filter_options.tsx
@@ -167,6 +167,7 @@ class FilterOptionsUI extends Component
{
anchorPosition="rightUp"
panelPaddingSize="none"
withTitle
+ repositionOnScroll
>
setIsPopoverOpen(false)}
withTitle
+ repositionOnScroll
>
{
- setActivePage(pageNumber);
- };
+ const handleTogglePopover = useCallback(() => setIsOpen((currentState) => !currentState), [
+ setIsOpen,
+ ]);
+
+ const handleClosePopover = useCallback(() => setIsOpen(false), []);
+
+ const handleSave = useCallback(() => {
+ handleClosePopover();
+ onSave();
+ }, [handleClosePopover, onSave]);
+
+ const handleSaveAsNew = useCallback(() => {
+ handleClosePopover();
+ onSaveAsNew();
+ }, [handleClosePopover, onSaveAsNew]);
+
+ const handleSelect = useCallback(
+ (savedQueryToSelect) => {
+ handleClosePopover();
+ onLoad(savedQueryToSelect);
+ },
+ [handleClosePopover, onLoad]
+ );
+
+ const handleDelete = useCallback(
+ (savedQueryToDelete: SavedQuery) => {
+ const onDeleteSavedQuery = async (savedQuery: SavedQuery) => {
+ cancelPendingListingRequest.current();
+ setSavedQueries(
+ savedQueries.filter((currentSavedQuery) => currentSavedQuery.id !== savedQuery.id)
+ );
+
+ if (loadedSavedQuery && loadedSavedQuery.id === savedQuery.id) {
+ onClearSavedQuery();
+ }
+
+ await savedQueryService.deleteSavedQuery(savedQuery.id);
+ setActivePage(0);
+ };
+
+ onDeleteSavedQuery(savedQueryToDelete);
+ handleClosePopover();
+ },
+ [handleClosePopover, loadedSavedQuery, onClearSavedQuery, savedQueries, savedQueryService]
+ );
const savedQueryDescriptionText = i18n.translate(
'data.search.searchBar.savedQueryDescriptionText',
@@ -113,25 +155,13 @@ export function SavedQueryManagementComponent({
}
);
- const onDeleteSavedQuery = async (savedQuery: SavedQuery) => {
- cancelPendingListingRequest.current();
- setSavedQueries(
- savedQueries.filter((currentSavedQuery) => currentSavedQuery.id !== savedQuery.id)
- );
-
- if (loadedSavedQuery && loadedSavedQuery.id === savedQuery.id) {
- onClearSavedQuery();
- }
-
- await savedQueryService.deleteSavedQuery(savedQuery.id);
- setActivePage(0);
+ const goToPage = (pageNumber: number) => {
+ setActivePage(pageNumber);
};
const savedQueryPopoverButton = (
{
- setIsOpen(!isOpen);
- }}
+ onClick={handleTogglePopover}
aria-label={i18n.translate('data.search.searchBar.savedQueryPopoverButtonText', {
defaultMessage: 'See saved queries',
})}
@@ -159,11 +189,8 @@ export function SavedQueryManagementComponent({
key={savedQuery.id}
savedQuery={savedQuery}
isSelected={!!loadedSavedQuery && loadedSavedQuery.id === savedQuery.id}
- onSelect={(savedQueryToSelect) => {
- onLoad(savedQueryToSelect);
- setIsOpen(false);
- }}
- onDelete={(savedQueryToDelete) => onDeleteSavedQuery(savedQueryToDelete)}
+ onSelect={handleSelect}
+ onDelete={handleDelete}
showWriteOperations={!!showSaveQuery}
/>
));
@@ -175,13 +202,12 @@ export function SavedQueryManagementComponent({
id="savedQueryPopover"
button={savedQueryPopoverButton}
isOpen={isOpen}
- closePopover={() => {
- setIsOpen(false);
- }}
+ closePopover={handleClosePopover}
anchorPosition="downLeft"
panelPaddingSize="none"
buffer={-8}
ownFocus
+ repositionOnScroll
>
onSave()}
+ onClick={handleSave}
aria-label={i18n.translate(
'data.search.searchBar.savedQueryPopoverSaveChangesButtonAriaLabel',
{
@@ -255,7 +281,7 @@ export function SavedQueryManagementComponent({
onSaveAsNew()}
+ onClick={handleSaveAsNew}
aria-label={i18n.translate(
'data.search.searchBar.savedQueryPopoverSaveAsNewButtonAriaLabel',
{
@@ -279,7 +305,7 @@ export function SavedQueryManagementComponent({
onSave()}
+ onClick={handleSave}
aria-label={i18n.translate(
'data.search.searchBar.savedQueryPopoverSaveButtonAriaLabel',
{ defaultMessage: 'Save a new saved query' }
@@ -298,7 +324,7 @@ export function SavedQueryManagementComponent({
onClearSavedQuery()}
+ onClick={onClearSavedQuery}
aria-label={i18n.translate(
'data.search.searchBar.savedQueryPopoverClearButtonAriaLabel',
{ defaultMessage: 'Clear current saved query' }
diff --git a/src/plugins/data/public/ui/search_bar/create_search_bar.tsx b/src/plugins/data/public/ui/search_bar/create_search_bar.tsx
index a0df7604f23aae..f8b7e4f4809112 100644
--- a/src/plugins/data/public/ui/search_bar/create_search_bar.tsx
+++ b/src/plugins/data/public/ui/search_bar/create_search_bar.tsx
@@ -17,6 +17,7 @@
* under the License.
*/
+import _ from 'lodash';
import React, { useState, useEffect, useRef } from 'react';
import { CoreStart } from 'src/core/public';
import { IStorageWrapper } from 'src/plugins/kibana_utils/public';
diff --git a/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_capabilities.ts b/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_capabilities.ts
index 502364cdcba327..b4b86b73a5f4a5 100644
--- a/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_capabilities.ts
+++ b/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_capabilities.ts
@@ -17,7 +17,7 @@
* under the License.
*/
-import { defaults, indexBy, sortBy } from 'lodash';
+import { defaults, keyBy, sortBy } from 'lodash';
import { LegacyAPICaller } from 'kibana/server';
import { callFieldCapsApi } from '../es_api';
@@ -44,7 +44,7 @@ export async function getFieldCapabilities(
metaFields: string[] = []
) {
const esFieldCaps: FieldCapsResponse = await callFieldCapsApi(callCluster, indices);
- const fieldsFromFieldCapsByName = indexBy(readFieldCapsResponse(esFieldCaps), 'name');
+ const fieldsFromFieldCapsByName = keyBy(readFieldCapsResponse(esFieldCaps), 'name');
const allFieldsUnsorted = Object.keys(fieldsFromFieldCapsByName)
.filter((name) => !name.startsWith('_'))
diff --git a/src/plugins/data/server/index_patterns/fetcher/lib/resolve_time_pattern.ts b/src/plugins/data/server/index_patterns/fetcher/lib/resolve_time_pattern.ts
index a01d34dbe9df6c..2e408d7569be56 100644
--- a/src/plugins/data/server/index_patterns/fetcher/lib/resolve_time_pattern.ts
+++ b/src/plugins/data/server/index_patterns/fetcher/lib/resolve_time_pattern.ts
@@ -46,7 +46,7 @@ export async function resolveTimePattern(callCluster: LegacyAPICaller, timePatte
[]
)
.sortBy((indexName: string) => indexName)
- .uniq(true)
+ .sortedUniq()
.map((indexName) => {
const parsed = moment(indexName, timePattern, true);
if (!parsed.isValid()) {
@@ -65,7 +65,7 @@ export async function resolveTimePattern(callCluster: LegacyAPICaller, timePatte
isMatch: indexName === parsed.format(timePattern),
};
})
- .sortByOrder(['valid', 'order'], ['desc', 'desc'])
+ .orderBy(['valid', 'order'], ['desc', 'desc'])
.value();
return {
diff --git a/src/plugins/data/server/saved_objects/index_pattern_migrations.ts b/src/plugins/data/server/saved_objects/index_pattern_migrations.ts
index 37819a13b65187..768041a376ad1d 100644
--- a/src/plugins/data/server/saved_objects/index_pattern_migrations.ts
+++ b/src/plugins/data/server/saved_objects/index_pattern_migrations.ts
@@ -55,6 +55,6 @@ const migrateSubTypeAndParentFieldProperties: SavedObjectMigrationFn =
};
export const indexPatternSavedObjectTypeMigrations = {
- '6.5.0': flow(migrateAttributeTypeAndAttributeTypeMeta),
- '7.6.0': flow(migrateSubTypeAndParentFieldProperties),
+ '6.5.0': flow(migrateAttributeTypeAndAttributeTypeMeta),
+ '7.6.0': flow(migrateSubTypeAndParentFieldProperties),
};
diff --git a/src/plugins/data/server/saved_objects/index_patterns.ts b/src/plugins/data/server/saved_objects/index_patterns.ts
index 902cf2988f429f..44d2813f6e3e81 100644
--- a/src/plugins/data/server/saved_objects/index_patterns.ts
+++ b/src/plugins/data/server/saved_objects/index_patterns.ts
@@ -54,5 +54,5 @@ export const indexPatternSavedObjectType: SavedObjectsType = {
typeMeta: { type: 'keyword' },
},
},
- migrations: indexPatternSavedObjectTypeMigrations,
+ migrations: indexPatternSavedObjectTypeMigrations as any,
};
diff --git a/src/plugins/data/server/saved_objects/search.ts b/src/plugins/data/server/saved_objects/search.ts
index 437c83f67bf5d0..16caaf05a0fc60 100644
--- a/src/plugins/data/server/saved_objects/search.ts
+++ b/src/plugins/data/server/saved_objects/search.ts
@@ -56,5 +56,5 @@ export const searchSavedObjectType: SavedObjectsType = {
version: { type: 'integer' },
},
},
- migrations: searchSavedObjectTypeMigrations,
+ migrations: searchSavedObjectTypeMigrations as any,
};
diff --git a/src/plugins/data/server/saved_objects/search_migrations.ts b/src/plugins/data/server/saved_objects/search_migrations.ts
index 2e37cd1255cee5..9bba429f8d71be 100644
--- a/src/plugins/data/server/saved_objects/search_migrations.ts
+++ b/src/plugins/data/server/saved_objects/search_migrations.ts
@@ -22,7 +22,7 @@ import { SavedObjectMigrationFn } from 'kibana/server';
import { DEFAULT_QUERY_LANGUAGE } from '../../common';
const migrateMatchAllQuery: SavedObjectMigrationFn = (doc) => {
- const searchSourceJSON = get(doc, 'attributes.kibanaSavedObjectMeta.searchSourceJSON');
+ const searchSourceJSON = get(doc, 'attributes.kibanaSavedObjectMeta.searchSourceJSON');
if (searchSourceJSON) {
let searchSource: any;
@@ -122,7 +122,7 @@ const migrateSearchSortToNestedArray: SavedObjectMigrationFn = (doc) =
};
export const searchSavedObjectTypeMigrations = {
- '6.7.2': flow>(migrateMatchAllQuery),
- '7.0.0': flow>(setNewReferences),
- '7.4.0': flow>(migrateSearchSortToNestedArray),
+ '6.7.2': flow(migrateMatchAllQuery),
+ '7.0.0': flow(setNewReferences),
+ '7.4.0': flow(migrateSearchSortToNestedArray),
};
diff --git a/src/plugins/discover/public/application/angular/context/query/actions.js b/src/plugins/discover/public/application/angular/context/query/actions.js
index 0e057e0a715c41..32fc2873d7f2a2 100644
--- a/src/plugins/discover/public/application/angular/context/query/actions.js
+++ b/src/plugins/discover/public/application/angular/context/query/actions.js
@@ -70,7 +70,7 @@ export function QueryActionsProvider(Promise) {
setLoadingStatus(state)('anchor');
return Promise.try(() =>
- fetchAnchor(indexPatternId, anchorId, [_.zipObject([sort]), { [tieBreakerField]: sort[1] }])
+ fetchAnchor(indexPatternId, anchorId, [_.fromPairs([sort]), { [tieBreakerField]: sort[1] }])
).then(
(anchorDocument) => {
setLoadedStatus(state)('anchor');
diff --git a/src/plugins/discover/public/application/angular/directives/histogram.tsx b/src/plugins/discover/public/application/angular/directives/histogram.tsx
index 9afe5e48bc5b82..4c39c8bb255422 100644
--- a/src/plugins/discover/public/application/angular/directives/histogram.tsx
+++ b/src/plugins/discover/public/application/angular/directives/histogram.tsx
@@ -40,12 +40,13 @@ import {
ElementClickListener,
XYChartElementEvent,
BrushEndListener,
+ Theme,
} from '@elastic/charts';
import { i18n } from '@kbn/i18n';
import { IUiSettingsClient } from 'kibana/public';
import { EuiChartThemeType } from '@elastic/eui/dist/eui_charts_theme';
-import { Subscription } from 'rxjs';
+import { Subscription, combineLatest } from 'rxjs';
import { getServices } from '../../../kibana_services';
import { Chart as IChart } from '../helpers/point_series';
@@ -56,6 +57,7 @@ export interface DiscoverHistogramProps {
interface DiscoverHistogramState {
chartsTheme: EuiChartThemeType['theme'];
+ chartsBaseTheme: Theme;
}
function findIntervalFromDuration(
@@ -126,18 +128,21 @@ export class DiscoverHistogram extends Component this.setState({ chartsTheme })
+ this.subscription = combineLatest(
+ getServices().theme.chartsTheme$,
+ getServices().theme.chartsBaseTheme$
+ ).subscribe(([chartsTheme, chartsBaseTheme]) =>
+ this.setState({ chartsTheme, chartsBaseTheme })
);
}
componentWillUnmount() {
if (this.subscription) {
this.subscription.unsubscribe();
- this.subscription = undefined;
}
}
@@ -204,7 +209,7 @@ export class DiscoverHistogram extends Component
({
value: value.toString(),
- text: padLeft(value, 2, '0'),
+ text: padStart(value, 2, '0'),
}));
const HOUR_OPTIONS = makeSequence(0, 23).map((value) => ({
value: value.toString(),
- text: padLeft(value, 2, '0'),
+ text: padStart(value, 2, '0'),
}));
const DAY_OPTIONS = makeSequence(1, 7).map((value) => ({
diff --git a/src/plugins/expressions/common/expression_functions/specs/kibana_context.ts b/src/plugins/expressions/common/expression_functions/specs/kibana_context.ts
index b8be273d7bbd32..2b7d1b8ed9d760 100644
--- a/src/plugins/expressions/common/expression_functions/specs/kibana_context.ts
+++ b/src/plugins/expressions/common/expression_functions/specs/kibana_context.ts
@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
-import { uniq } from 'lodash';
+import { uniqBy } from 'lodash';
import { i18n } from '@kbn/i18n';
import { ExpressionFunctionDefinition } from '../../expression_functions';
import { KibanaContext } from '../../expression_types';
@@ -40,7 +40,7 @@ const getParsedValue = (data: any, defaultValue: any) =>
typeof data === 'string' && data.length ? JSON.parse(data) || defaultValue : defaultValue;
const mergeQueries = (first: Query | Query[] = [], second: Query | Query[]) =>
- uniq(
+ uniqBy(
[...(Array.isArray(first) ? first : [first]), ...(Array.isArray(second) ? second : [second])],
(n: any) => JSON.stringify(n.query)
);
diff --git a/src/plugins/expressions/common/expression_types/specs/datatable.ts b/src/plugins/expressions/common/expression_types/specs/datatable.ts
index c113765f8e7e72..5cd53df663e1d4 100644
--- a/src/plugins/expressions/common/expression_types/specs/datatable.ts
+++ b/src/plugins/expressions/common/expression_types/specs/datatable.ts
@@ -20,7 +20,7 @@
import { map, pick, zipObject } from 'lodash';
import { ExpressionTypeDefinition } from '../types';
-import { PointSeries } from './pointseries';
+import { PointSeries, PointSeriesColumn } from './pointseries';
import { ExpressionValueRender } from './render';
const name = 'datatable';
@@ -109,8 +109,8 @@ export const datatable: ExpressionTypeDefinition ({
type: name,
rows: value.rows,
- columns: map(value.columns, (val, colName) => {
- return { name: colName!, type: val.type };
+ columns: map(value.columns, (val: PointSeriesColumn, colName) => {
+ return { name: colName, type: val.type };
}),
}),
},
diff --git a/src/plugins/expressions/common/expression_types/specs/kibana_datatable.ts b/src/plugins/expressions/common/expression_types/specs/kibana_datatable.ts
index 7f2f3c37c587c1..e226f3b124eed0 100644
--- a/src/plugins/expressions/common/expression_types/specs/kibana_datatable.ts
+++ b/src/plugins/expressions/common/expression_types/specs/kibana_datatable.ts
@@ -19,7 +19,7 @@
import { map } from 'lodash';
import { SerializedFieldFormat } from '../../types/common';
-import { Datatable, PointSeries } from '.';
+import { Datatable, PointSeries, PointSeriesColumn } from '.';
const name = 'kibana_datatable';
@@ -62,7 +62,7 @@ export const kibanaDatatable = {
};
},
pointseries: (context: PointSeries) => {
- const columns = map(context.columns, (column, n) => {
+ const columns = map(context.columns, (column: PointSeriesColumn, n) => {
return { id: n, name: n, ...column };
});
return {
diff --git a/src/plugins/expressions/public/loader.ts b/src/plugins/expressions/public/loader.ts
index 9428d7db1d9d0d..f957f10a9aeba1 100644
--- a/src/plugins/expressions/public/loader.ts
+++ b/src/plugins/expressions/public/loader.ts
@@ -19,6 +19,7 @@
import { BehaviorSubject, Observable, Subject } from 'rxjs';
import { filter, map } from 'rxjs/operators';
+import { defaults } from 'lodash';
import { Adapters } from '../../inspector/public';
import { IExpressionLoaderParams } from './types';
import { ExpressionAstExpression } from '../common';
@@ -168,7 +169,7 @@ export class ExpressionLoader {
}
if (params.searchContext) {
- this.params.searchContext = _.defaults(
+ this.params.searchContext = defaults(
{},
params.searchContext,
this.params.searchContext || {}
diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/tabs/utils.ts b/src/plugins/index_pattern_management/public/components/edit_index_pattern/tabs/utils.ts
index 52cd5b0c3f5bd3..5ab9c695caaa04 100644
--- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/tabs/utils.ts
+++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/tabs/utils.ts
@@ -17,7 +17,7 @@
* under the License.
*/
-import { Dictionary, countBy, defaults, unique } from 'lodash';
+import { Dictionary, countBy, defaults, uniq } from 'lodash';
import { i18n } from '@kbn/i18n';
import { IndexPattern, IndexPatternField } from '../../../../../../plugins/data/public';
import { IndexPatternManagementStart } from '../../../../../../plugins/index_pattern_management/public';
@@ -145,7 +145,7 @@ export function convertToEuiSelectOption(options: string[], type: string) {
]
: [];
return euiOptions.concat(
- unique(options).map((option) => {
+ uniq(options).map((option) => {
return {
value: option,
text: option,
diff --git a/src/plugins/input_control_vis/public/control/filter_manager/phrase_filter_manager.ts b/src/plugins/input_control_vis/public/control/filter_manager/phrase_filter_manager.ts
index 4eff5112c0c072..03ed6c5520decd 100644
--- a/src/plugins/input_control_vis/public/control/filter_manager/phrase_filter_manager.ts
+++ b/src/plugins/input_control_vis/public/control/filter_manager/phrase_filter_manager.ts
@@ -86,11 +86,11 @@ export class PhraseFilterManager extends FilterManager {
private getValueFromFilter(kbnFilter: PhraseFilter): any {
// bool filter - multiple phrase filters
if (_.has(kbnFilter, 'query.bool.should')) {
- return _.get(kbnFilter, 'query.bool.should')
- .map((kbnQueryFilter) => {
+ return _.get(kbnFilter, 'query.bool.should')
+ .map((kbnQueryFilter: PhraseFilter) => {
return this.getValueFromFilter(kbnQueryFilter);
})
- .filter((value) => {
+ .filter((value: any) => {
if (value) {
return true;
}
diff --git a/src/plugins/inspector/common/adapters/request/request_adapter.ts b/src/plugins/inspector/common/adapters/request/request_adapter.ts
index 70af6b5b51d182..af10d1b77b16d3 100644
--- a/src/plugins/inspector/common/adapters/request/request_adapter.ts
+++ b/src/plugins/inspector/common/adapters/request/request_adapter.ts
@@ -18,7 +18,6 @@
*/
import { EventEmitter } from 'events';
-import _ from 'lodash';
import uuid from 'uuid/v4';
import { RequestResponder } from './request_responder';
import { Request, RequestParams, RequestStatus } from './types';
diff --git a/src/plugins/inspector/public/views/data/lib/export_csv.ts b/src/plugins/inspector/public/views/data/lib/export_csv.ts
index c0e0153c6053ee..5a970cc6cff386 100644
--- a/src/plugins/inspector/public/views/data/lib/export_csv.ts
+++ b/src/plugins/inspector/public/views/data/lib/export_csv.ts
@@ -29,7 +29,7 @@ const allDoubleQuoteRE = /"/g;
function escape(val: string, quoteValues: boolean) {
if (isObject(val)) {
- val = val.valueOf();
+ val = (val as any).valueOf();
}
val = String(val);
diff --git a/src/plugins/kibana_legacy/public/forward_app/forward_app.ts b/src/plugins/kibana_legacy/public/forward_app/forward_app.ts
index 89018df1ca7e17..b425091dfbcd9b 100644
--- a/src/plugins/kibana_legacy/public/forward_app/forward_app.ts
+++ b/src/plugins/kibana_legacy/public/forward_app/forward_app.ts
@@ -20,9 +20,12 @@
import { App, AppMountParameters, CoreSetup } from 'kibana/public';
import { AppNavLinkStatus } from '../../../../core/public';
import { navigateToLegacyKibanaUrl } from './navigate_to_legacy_kibana_url';
-import { ForwardDefinition } from '../plugin';
+import { ForwardDefinition, KibanaLegacyStart } from '../plugin';
-export const createLegacyUrlForwardApp = (core: CoreSetup, forwards: ForwardDefinition[]): App => ({
+export const createLegacyUrlForwardApp = (
+ core: CoreSetup<{}, KibanaLegacyStart>,
+ forwards: ForwardDefinition[]
+): App => ({
id: 'kibana',
chromeless: true,
title: 'Legacy URL migration',
@@ -31,7 +34,8 @@ export const createLegacyUrlForwardApp = (core: CoreSetup, forwards: ForwardDefi
const hash = params.history.location.hash.substr(1);
if (!hash) {
- core.fatalErrors.add('Could not forward URL');
+ const [, , kibanaLegacyStart] = await core.getStartServices();
+ kibanaLegacyStart.navigateToDefaultApp();
}
const [
@@ -44,7 +48,8 @@ export const createLegacyUrlForwardApp = (core: CoreSetup, forwards: ForwardDefi
const result = await navigateToLegacyKibanaUrl(hash, forwards, basePath, application);
if (!result.navigated) {
- core.fatalErrors.add('Could not forward URL');
+ const [, , kibanaLegacyStart] = await core.getStartServices();
+ kibanaLegacyStart.navigateToDefaultApp();
}
return () => {};
diff --git a/src/plugins/kibana_react/public/table_list_view/table_list_view.tsx b/src/plugins/kibana_react/public/table_list_view/table_list_view.tsx
index 87fdf0730c8807..2fa1debf51b5c3 100644
--- a/src/plugins/kibana_react/public/table_list_view/table_list_view.tsx
+++ b/src/plugins/kibana_react/public/table_list_view/table_list_view.tsx
@@ -20,7 +20,7 @@
import React from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
-import { debounce, indexBy, sortBy, uniq } from 'lodash';
+import { debounce, keyBy, sortBy, uniq } from 'lodash';
import {
EuiTitle,
EuiInMemoryTable,
@@ -178,7 +178,7 @@ class TableListView extends React.Component itemsById[id]));
} catch (error) {
this.props.toastNotifications.addDanger({
diff --git a/src/plugins/kibana_react/public/validated_range/validated_dual_range.tsx b/src/plugins/kibana_react/public/validated_range/validated_dual_range.tsx
index 63b9b48ec809eb..45592c8a703af1 100644
--- a/src/plugins/kibana_react/public/validated_range/validated_dual_range.tsx
+++ b/src/plugins/kibana_react/public/validated_range/validated_dual_range.tsx
@@ -17,7 +17,7 @@
* under the License.
*/
import { i18n } from '@kbn/i18n';
-import React, { Component } from 'react';
+import React, { Component, ReactNode } from 'react';
import { EuiFormRow, EuiDualRange } from '@elastic/eui';
import { EuiFormRowDisplayKeys } from '@elastic/eui/src/components/form/form_row/form_row';
import { EuiDualRangeProps } from '@elastic/eui/src/components/form/range/dual_range';
@@ -32,7 +32,7 @@ export type ValueMember = EuiDualRangeProps['value'][0];
interface Props extends Omit {
value?: Value;
allowEmptyRange?: boolean;
- label?: string;
+ label?: string | ReactNode;
formRowDisplay?: EuiFormRowDisplayKeys;
onChange?: (val: [string, string]) => void;
min?: number;
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 a0de79da565e62..8d6a2d110efe0f 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
@@ -35,10 +35,12 @@ export function registerMappings(registerType: SavedObjectsServiceSetup['registe
hidden: false,
namespaceType: 'agnostic',
mappings: {
+ dynamic: false,
properties: {
- appId: { type: 'keyword' },
- numberOfClicks: { type: 'long' },
- minutesOnScreen: { type: 'float' },
+ // Disabled the mapping of these fields since they are not searched and we need to reduce the amount of indexed fields (#43673)
+ // appId: { type: 'keyword' },
+ // numberOfClicks: { type: 'long' },
+ // minutesOnScreen: { type: 'float' },
},
},
});
@@ -48,11 +50,13 @@ export function registerMappings(registerType: SavedObjectsServiceSetup['registe
hidden: false,
namespaceType: 'agnostic',
mappings: {
+ dynamic: false,
properties: {
timestamp: { type: 'date' },
- appId: { type: 'keyword' },
- numberOfClicks: { type: 'long' },
- minutesOnScreen: { type: 'float' },
+ // Disabled the mapping of these fields since they are not searched and we need to reduce the amount of indexed fields (#43673)
+ // appId: { type: 'keyword' },
+ // numberOfClicks: { type: 'long' },
+ // minutesOnScreen: { type: 'float' },
},
},
});
diff --git a/src/plugins/kibana_utils/common/url/encode_uri_query.ts b/src/plugins/kibana_utils/common/url/encode_uri_query.ts
index fb60f0ceff10f2..fe8cf12d0d6f20 100644
--- a/src/plugins/kibana_utils/common/url/encode_uri_query.ts
+++ b/src/plugins/kibana_utils/common/url/encode_uri_query.ts
@@ -45,7 +45,7 @@ export const encodeQuery = (
query: ParsedQuery,
encodeFunction: (val: string, pctEncodeSpaces?: boolean) => string = encodeUriQuery
) =>
- transform(query, (result, value, key) => {
+ transform(query, (result: any, value, key) => {
if (key) {
const singleValue = Array.isArray(value) ? value.join(',') : value;
diff --git a/src/plugins/navigation/public/top_nav_menu/top_nav_menu_item.tsx b/src/plugins/navigation/public/top_nav_menu/top_nav_menu_item.tsx
index 3a05ce59f5d134..1c5642f9b75b7e 100644
--- a/src/plugins/navigation/public/top_nav_menu/top_nav_menu_item.tsx
+++ b/src/plugins/navigation/public/top_nav_menu/top_nav_menu_item.tsx
@@ -17,7 +17,7 @@
* under the License.
*/
-import { capitalize, isFunction } from 'lodash';
+import { upperFirst, isFunction } from 'lodash';
import React, { MouseEvent } from 'react';
import { EuiButtonEmpty, EuiToolTip } from '@elastic/eui';
@@ -50,11 +50,11 @@ export function TopNavMenuItem(props: TopNavMenuData) {
const btn = props.emphasize ? (
- {capitalize(props.label || props.id!)}
+ {upperFirst(props.label || props.id!)}
) : (
- {capitalize(props.label || props.id!)}
+ {upperFirst(props.label || props.id!)}
);
diff --git a/src/plugins/saved_objects_management/public/lib/create_field_list.ts b/src/plugins/saved_objects_management/public/lib/create_field_list.ts
index 5f424751dd58ea..dcfb44d8a5224f 100644
--- a/src/plugins/saved_objects_management/public/lib/create_field_list.ts
+++ b/src/plugins/saved_objects_management/public/lib/create_field_list.ts
@@ -17,7 +17,7 @@
* under the License.
*/
-import { forOwn, indexBy, isNumber, isBoolean, isPlainObject, isString } from 'lodash';
+import { forOwn, keyBy, isNumber, isBoolean, isPlainObject, isString } from 'lodash';
import { SimpleSavedObject } from '../../../../core/public';
import { castEsToKbnFieldTypeName } from '../../../data/public';
import { ObjectField } from '../management_section/types';
@@ -93,9 +93,9 @@ const addFieldsFromClass = function (
Class: { mapping: Record; searchSource: any },
fields: ObjectField[]
) {
- const fieldMap = indexBy(fields, 'name');
+ const fieldMap = keyBy(fields, 'name');
- _.forOwn(Class.mapping, (esType, name) => {
+ forOwn(Class.mapping, (esType, name) => {
if (!name || fieldMap[name]) {
return;
}
diff --git a/src/plugins/saved_objects_management/public/management_section/saved_objects_table_page.tsx b/src/plugins/saved_objects_management/public/management_section/saved_objects_table_page.tsx
index 75692777f08bbb..dbbea4012aba9d 100644
--- a/src/plugins/saved_objects_management/public/management_section/saved_objects_table_page.tsx
+++ b/src/plugins/saved_objects_management/public/management_section/saved_objects_table_page.tsx
@@ -78,7 +78,7 @@ const SavedObjectsTablePage = ({
}}
canGoInApp={(savedObject) => {
const { inAppUrl } = savedObject.meta;
- return inAppUrl ? get(capabilities, inAppUrl.uiCapabilitiesPath) : false;
+ return inAppUrl ? Boolean(get(capabilities, inAppUrl.uiCapabilitiesPath)) : false;
}}
/>
);
diff --git a/src/plugins/telemetry/server/index.ts b/src/plugins/telemetry/server/index.ts
index d048c8f5e94279..42259d2e5187ce 100644
--- a/src/plugins/telemetry/server/index.ts
+++ b/src/plugins/telemetry/server/index.ts
@@ -47,4 +47,8 @@ export {
getLocalLicense,
getLocalStats,
TelemetryLocalStats,
+ DATA_TELEMETRY_ID,
+ DataTelemetryIndex,
+ DataTelemetryPayload,
+ buildDataTelemetryPayload,
} from './telemetry_collection';
diff --git a/src/plugins/telemetry/server/telemetry_collection/__tests__/get_local_stats.js b/src/plugins/telemetry/server/telemetry_collection/__tests__/get_local_stats.js
index e78b92498e6e78..8541745faea3b6 100644
--- a/src/plugins/telemetry/server/telemetry_collection/__tests__/get_local_stats.js
+++ b/src/plugins/telemetry/server/telemetry_collection/__tests__/get_local_stats.js
@@ -179,23 +179,36 @@ describe('get_local_stats', () => {
describe('handleLocalStats', () => {
it('returns expected object without xpack and kibana data', () => {
- const result = handleLocalStats(clusterInfo, clusterStatsWithNodesUsage, void 0, context);
+ const result = handleLocalStats(
+ clusterInfo,
+ clusterStatsWithNodesUsage,
+ void 0,
+ void 0,
+ context
+ );
expect(result.cluster_uuid).to.eql(combinedStatsResult.cluster_uuid);
expect(result.cluster_name).to.eql(combinedStatsResult.cluster_name);
expect(result.cluster_stats).to.eql(combinedStatsResult.cluster_stats);
expect(result.version).to.be('2.3.4');
expect(result.collection).to.be('local');
expect(result.license).to.be(undefined);
- expect(result.stack_stats).to.eql({ kibana: undefined });
+ expect(result.stack_stats).to.eql({ kibana: undefined, data: undefined });
});
it('returns expected object with xpack', () => {
- const result = handleLocalStats(clusterInfo, clusterStatsWithNodesUsage, void 0, context);
+ const result = handleLocalStats(
+ clusterInfo,
+ clusterStatsWithNodesUsage,
+ void 0,
+ void 0,
+ context
+ );
const { stack_stats: stack, ...cluster } = result;
expect(cluster.collection).to.be(combinedStatsResult.collection);
expect(cluster.cluster_uuid).to.be(combinedStatsResult.cluster_uuid);
expect(cluster.cluster_name).to.be(combinedStatsResult.cluster_name);
expect(stack.kibana).to.be(undefined); // not mocked for this test
+ expect(stack.data).to.be(undefined); // not mocked for this test
expect(cluster.version).to.eql(combinedStatsResult.version);
expect(cluster.cluster_stats).to.eql(combinedStatsResult.cluster_stats);
diff --git a/src/plugins/telemetry/server/telemetry_collection/get_data_telemetry/constants.ts b/src/plugins/telemetry/server/telemetry_collection/get_data_telemetry/constants.ts
new file mode 100644
index 00000000000000..2d0864b1cb75f8
--- /dev/null
+++ b/src/plugins/telemetry/server/telemetry_collection/get_data_telemetry/constants.ts
@@ -0,0 +1,136 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+export const DATA_TELEMETRY_ID = 'data';
+
+export const DATA_KNOWN_TYPES = ['logs', 'traces', 'metrics'] as const;
+
+export type DataTelemetryType = typeof DATA_KNOWN_TYPES[number];
+
+export type DataPatternName = typeof DATA_DATASETS_INDEX_PATTERNS[number]['patternName'];
+
+// TODO: Ideally this list should be updated from an external public URL (similar to the newsfeed)
+// But it's good to have a minimum list shipped with the build.
+export const DATA_DATASETS_INDEX_PATTERNS = [
+ // Enterprise Search - Elastic
+ { pattern: '.ent-search-*', patternName: 'enterprise-search' },
+ { pattern: '.app-search-*', patternName: 'app-search' },
+ // Enterprise Search - 3rd party
+ { pattern: '*magento2*', patternName: 'magento2' },
+ { pattern: '*magento*', patternName: 'magento' },
+ { pattern: '*shopify*', patternName: 'shopify' },
+ { pattern: '*wordpress*', patternName: 'wordpress' },
+ // { pattern: '*wp*', patternName: 'wordpress' }, // TODO: Too vague?
+ { pattern: '*drupal*', patternName: 'drupal' },
+ { pattern: '*joomla*', patternName: 'joomla' },
+ { pattern: '*search*', patternName: 'search' }, // TODO: Too vague?
+ // { pattern: '*wix*', patternName: 'wix' }, // TODO: Too vague?
+ { pattern: '*sharepoint*', patternName: 'sharepoint' },
+ { pattern: '*squarespace*', patternName: 'squarespace' },
+ // { pattern: '*aem*', patternName: 'aem' }, // TODO: Too vague?
+ { pattern: '*sitecore*', patternName: 'sitecore' },
+ { pattern: '*weebly*', patternName: 'weebly' },
+ { pattern: '*acquia*', patternName: 'acquia' },
+
+ // Observability - Elastic
+ { pattern: 'filebeat-*', patternName: 'filebeat', shipper: 'filebeat' },
+ { pattern: 'metricbeat-*', patternName: 'metricbeat', shipper: 'metricbeat' },
+ { pattern: 'apm-*', patternName: 'apm', shipper: 'apm' },
+ { pattern: 'functionbeat-*', patternName: 'functionbeat', shipper: 'functionbeat' },
+ { pattern: 'heartbeat-*', patternName: 'heartbeat', shipper: 'heartbeat' },
+ { pattern: 'logstash-*', patternName: 'logstash', shipper: 'logstash' },
+ // Observability - 3rd party
+ { pattern: 'fluentd*', patternName: 'fluentd' },
+ { pattern: 'telegraf*', patternName: 'telegraf' },
+ { pattern: 'prometheusbeat*', patternName: 'prometheusbeat' },
+ { pattern: 'fluentbit*', patternName: 'fluentbit' },
+ { pattern: '*nginx*', patternName: 'nginx' },
+ { pattern: '*apache*', patternName: 'apache' }, // Already in Security (keeping it in here for documentation)
+ // { pattern: '*logs*', patternName: 'third-party-logs' }, Disabled for now
+
+ // Security - Elastic
+ { pattern: 'logstash-*', patternName: 'logstash', shipper: 'logstash' },
+ { pattern: 'endgame-*', patternName: 'endgame', shipper: 'endgame' },
+ { pattern: 'logs-endpoint.*', patternName: 'logs-endpoint', shipper: 'endpoint' }, // It should be caught by the `mappings` logic, but just in case
+ { pattern: 'metrics-endpoint.*', patternName: 'metrics-endpoint', shipper: 'endpoint' }, // It should be caught by the `mappings` logic, but just in case
+ { pattern: '.siem-signals-*', patternName: 'siem-signals' },
+ { pattern: 'auditbeat-*', patternName: 'auditbeat', shipper: 'auditbeat' },
+ { pattern: 'winlogbeat-*', patternName: 'winlogbeat', shipper: 'winlogbeat' },
+ { pattern: 'packetbeat-*', patternName: 'packetbeat', shipper: 'packetbeat' },
+ { pattern: 'filebeat-*', patternName: 'filebeat', shipper: 'filebeat' },
+ // Security - 3rd party
+ { pattern: '*apache*', patternName: 'apache' }, // Already in Observability (keeping it in here for documentation)
+ { pattern: '*tomcat*', patternName: 'tomcat' },
+ { pattern: '*artifactory*', patternName: 'artifactory' },
+ { pattern: '*aruba*', patternName: 'aruba' },
+ { pattern: '*barracuda*', patternName: 'barracuda' },
+ { pattern: '*bluecoat*', patternName: 'bluecoat' },
+ { pattern: 'arcsight-*', patternName: 'arcsight', shipper: 'arcsight' },
+ // { pattern: '*cef*', patternName: 'cef' }, // Disabled because it's too vague
+ { pattern: '*checkpoint*', patternName: 'checkpoint' },
+ { pattern: '*cisco*', patternName: 'cisco' },
+ { pattern: '*citrix*', patternName: 'citrix' },
+ { pattern: '*cyberark*', patternName: 'cyberark' },
+ { pattern: '*cylance*', patternName: 'cylance' },
+ { pattern: '*fireeye*', patternName: 'fireeye' },
+ { pattern: '*fortinet*', patternName: 'fortinet' },
+ { pattern: '*infoblox*', patternName: 'infoblox' },
+ { pattern: '*kaspersky*', patternName: 'kaspersky' },
+ { pattern: '*mcafee*', patternName: 'mcafee' },
+ // paloaltonetworks
+ { pattern: '*paloaltonetworks*', patternName: 'paloaltonetworks' },
+ { pattern: 'pan-*', patternName: 'paloaltonetworks' },
+ { pattern: 'pan_*', patternName: 'paloaltonetworks' },
+ { pattern: 'pan.*', patternName: 'paloaltonetworks' },
+
+ // rsa
+ { pattern: 'rsa.*', patternName: 'rsa' },
+ { pattern: 'rsa-*', patternName: 'rsa' },
+ { pattern: 'rsa_*', patternName: 'rsa' },
+
+ // snort
+ { pattern: 'snort-*', patternName: 'snort' },
+ { pattern: 'logstash-snort*', patternName: 'snort' },
+
+ { pattern: '*sonicwall*', patternName: 'sonicwall' },
+ { pattern: '*sophos*', patternName: 'sophos' },
+
+ // squid
+ { pattern: 'squid-*', patternName: 'squid' },
+ { pattern: 'squid_*', patternName: 'squid' },
+ { pattern: 'squid.*', patternName: 'squid' },
+
+ { pattern: '*symantec*', patternName: 'symantec' },
+ { pattern: '*tippingpoint*', patternName: 'tippingpoint' },
+ { pattern: '*trendmicro*', patternName: 'trendmicro' },
+ { pattern: '*tripwire*', patternName: 'tripwire' },
+ { pattern: '*zscaler*', patternName: 'zscaler' },
+ { pattern: '*zeek*', patternName: 'zeek' },
+ { pattern: '*sigma_doc*', patternName: 'sigma_doc' },
+ // { pattern: '*bro*', patternName: 'bro' }, // Disabled because it's too vague
+ { pattern: 'ecs-corelight*', patternName: 'ecs-corelight' },
+ { pattern: '*suricata*', patternName: 'suricata' },
+ // { pattern: '*fsf*', patternName: 'fsf' }, // Disabled because it's too vague
+ { pattern: '*wazuh*', patternName: 'wazuh' },
+] as const;
+
+// Get the unique list of index patterns (some are duplicated for documentation purposes)
+export const DATA_DATASETS_INDEX_PATTERNS_UNIQUE = DATA_DATASETS_INDEX_PATTERNS.filter(
+ (entry, index, array) => !array.slice(0, index).find(({ pattern }) => entry.pattern === pattern)
+);
diff --git a/src/plugins/telemetry/server/telemetry_collection/get_data_telemetry/get_data_telemetry.test.ts b/src/plugins/telemetry/server/telemetry_collection/get_data_telemetry/get_data_telemetry.test.ts
new file mode 100644
index 00000000000000..8bffc5d012a741
--- /dev/null
+++ b/src/plugins/telemetry/server/telemetry_collection/get_data_telemetry/get_data_telemetry.test.ts
@@ -0,0 +1,251 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { buildDataTelemetryPayload, getDataTelemetry } from './get_data_telemetry';
+import { DATA_DATASETS_INDEX_PATTERNS, DATA_DATASETS_INDEX_PATTERNS_UNIQUE } from './constants';
+
+describe('get_data_telemetry', () => {
+ describe('DATA_DATASETS_INDEX_PATTERNS', () => {
+ DATA_DATASETS_INDEX_PATTERNS.forEach((entry, index, array) => {
+ describe(`Pattern ${entry.pattern}`, () => {
+ test('there should only be one in DATA_DATASETS_INDEX_PATTERNS_UNIQUE', () => {
+ expect(
+ DATA_DATASETS_INDEX_PATTERNS_UNIQUE.filter(({ pattern }) => pattern === entry.pattern)
+ ).toHaveLength(1);
+ });
+
+ // This test is to make us sure that we don't update one of the duplicated entries and forget about any other repeated ones
+ test('when a document is duplicated, the duplicates should be identical', () => {
+ array.slice(0, index).forEach((previousEntry) => {
+ if (entry.pattern === previousEntry.pattern) {
+ expect(entry).toStrictEqual(previousEntry);
+ }
+ });
+ });
+ });
+ });
+ });
+
+ describe('buildDataTelemetryPayload', () => {
+ test('return the base object when no indices provided', () => {
+ expect(buildDataTelemetryPayload([])).toStrictEqual([]);
+ });
+
+ test('return the base object when no matching indices provided', () => {
+ expect(
+ buildDataTelemetryPayload([
+ { name: 'no__way__this__can_match_anything', sizeInBytes: 10 },
+ { name: '.kibana-event-log-8.0.0' },
+ ])
+ ).toStrictEqual([]);
+ });
+
+ test('matches some indices and puts them in their own category', () => {
+ expect(
+ buildDataTelemetryPayload([
+ // APM Indices have known shipper (so we can infer the datasetType from mapping constant)
+ { name: 'apm-7.7.0-error-000001', shipper: 'apm', isECS: true },
+ { name: 'apm-7.7.0-metric-000001', shipper: 'apm', isECS: true },
+ { name: 'apm-7.7.0-onboarding-2020.05.17', shipper: 'apm', isECS: true },
+ { name: 'apm-7.7.0-profile-000001', shipper: 'apm', isECS: true },
+ { name: 'apm-7.7.0-span-000001', shipper: 'apm', isECS: true },
+ { name: 'apm-7.7.0-transaction-000001', shipper: 'apm', isECS: true },
+ // Packetbeat indices with known shipper (we can infer datasetType from mapping constant)
+ { name: 'packetbeat-7.7.0-2020.06.11-000001', shipper: 'packetbeat', isECS: true },
+ // Matching patterns from the list => known datasetName but the rest is unknown
+ { name: 'filebeat-12314', docCount: 100, sizeInBytes: 10 },
+ { name: 'metricbeat-1234', docCount: 100, sizeInBytes: 10, isECS: false },
+ { name: '.app-search-1234', docCount: 0 },
+ { name: 'logs-endpoint.1234', docCount: 0 }, // Matching pattern with a dot in the name
+ // New Indexing strategy: everything can be inferred from the constant_keyword values
+ {
+ name: 'logs-nginx.access-default-000001',
+ datasetName: 'nginx.access',
+ datasetType: 'logs',
+ shipper: 'filebeat',
+ isECS: true,
+ docCount: 1000,
+ sizeInBytes: 1000,
+ },
+ {
+ name: 'logs-nginx.access-default-000002',
+ datasetName: 'nginx.access',
+ datasetType: 'logs',
+ shipper: 'filebeat',
+ isECS: true,
+ docCount: 1000,
+ sizeInBytes: 60,
+ },
+ ])
+ ).toStrictEqual([
+ {
+ shipper: 'apm',
+ index_count: 6,
+ ecs_index_count: 6,
+ },
+ {
+ shipper: 'packetbeat',
+ index_count: 1,
+ ecs_index_count: 1,
+ },
+ {
+ pattern_name: 'filebeat',
+ shipper: 'filebeat',
+ index_count: 1,
+ doc_count: 100,
+ size_in_bytes: 10,
+ },
+ {
+ pattern_name: 'metricbeat',
+ shipper: 'metricbeat',
+ index_count: 1,
+ ecs_index_count: 0,
+ doc_count: 100,
+ size_in_bytes: 10,
+ },
+ {
+ pattern_name: 'app-search',
+ index_count: 1,
+ doc_count: 0,
+ },
+ {
+ pattern_name: 'logs-endpoint',
+ shipper: 'endpoint',
+ index_count: 1,
+ doc_count: 0,
+ },
+ {
+ dataset: { name: 'nginx.access', type: 'logs' },
+ shipper: 'filebeat',
+ index_count: 2,
+ ecs_index_count: 2,
+ doc_count: 2000,
+ size_in_bytes: 1060,
+ },
+ ]);
+ });
+ });
+
+ describe('getDataTelemetry', () => {
+ test('it returns the base payload (all 0s) because no indices are found', async () => {
+ const callCluster = mockCallCluster();
+ await expect(getDataTelemetry(callCluster)).resolves.toStrictEqual([]);
+ });
+
+ test('can only see the index mappings, but not the stats', async () => {
+ const callCluster = mockCallCluster(['filebeat-12314']);
+ await expect(getDataTelemetry(callCluster)).resolves.toStrictEqual([
+ {
+ pattern_name: 'filebeat',
+ shipper: 'filebeat',
+ index_count: 1,
+ ecs_index_count: 0,
+ },
+ ]);
+ });
+
+ test('can see the mappings and the stats', async () => {
+ const callCluster = mockCallCluster(
+ ['filebeat-12314'],
+ { isECS: true },
+ {
+ indices: {
+ 'filebeat-12314': { total: { docs: { count: 100 }, store: { size_in_bytes: 10 } } },
+ },
+ }
+ );
+ await expect(getDataTelemetry(callCluster)).resolves.toStrictEqual([
+ {
+ pattern_name: 'filebeat',
+ shipper: 'filebeat',
+ index_count: 1,
+ ecs_index_count: 1,
+ doc_count: 100,
+ size_in_bytes: 10,
+ },
+ ]);
+ });
+
+ test('find an index that does not match any index pattern but has mappings metadata', async () => {
+ const callCluster = mockCallCluster(
+ ['cannot_match_anything'],
+ { isECS: true, datasetType: 'traces', shipper: 'my-beat' },
+ {
+ indices: {
+ cannot_match_anything: {
+ total: { docs: { count: 100 }, store: { size_in_bytes: 10 } },
+ },
+ },
+ }
+ );
+ await expect(getDataTelemetry(callCluster)).resolves.toStrictEqual([
+ {
+ dataset: { name: undefined, type: 'traces' },
+ shipper: 'my-beat',
+ index_count: 1,
+ ecs_index_count: 1,
+ doc_count: 100,
+ size_in_bytes: 10,
+ },
+ ]);
+ });
+
+ test('return empty array when there is an error', async () => {
+ const callCluster = jest.fn().mockRejectedValue(new Error('Something went terribly wrong'));
+ await expect(getDataTelemetry(callCluster)).resolves.toStrictEqual([]);
+ });
+ });
+});
+
+function mockCallCluster(
+ indicesMappings: string[] = [],
+ { isECS = false, datasetName = '', datasetType = '', shipper = '' } = {},
+ indexStats: any = {}
+) {
+ return jest.fn().mockImplementation(async (method: string, opts: any) => {
+ if (method === 'indices.getMapping') {
+ return Object.fromEntries(
+ indicesMappings.map((index) => [
+ index,
+ {
+ mappings: {
+ ...(shipper && { _meta: { beat: shipper } }),
+ properties: {
+ ...(isECS && { ecs: { properties: { version: { type: 'keyword' } } } }),
+ ...((datasetType || datasetName) && {
+ dataset: {
+ properties: {
+ ...(datasetName && {
+ name: { type: 'constant_keyword', value: datasetName },
+ }),
+ ...(datasetType && {
+ type: { type: 'constant_keyword', value: datasetType },
+ }),
+ },
+ },
+ }),
+ },
+ },
+ },
+ ])
+ );
+ }
+ return indexStats;
+ });
+}
diff --git a/src/plugins/telemetry/server/telemetry_collection/get_data_telemetry/get_data_telemetry.ts b/src/plugins/telemetry/server/telemetry_collection/get_data_telemetry/get_data_telemetry.ts
new file mode 100644
index 00000000000000..cf906bc5c86cfc
--- /dev/null
+++ b/src/plugins/telemetry/server/telemetry_collection/get_data_telemetry/get_data_telemetry.ts
@@ -0,0 +1,253 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { LegacyAPICaller } from 'kibana/server';
+import {
+ DATA_DATASETS_INDEX_PATTERNS_UNIQUE,
+ DataPatternName,
+ DataTelemetryType,
+} from './constants';
+
+export interface DataTelemetryBasePayload {
+ index_count: number;
+ ecs_index_count?: number;
+ doc_count?: number;
+ size_in_bytes?: number;
+}
+
+export interface DataTelemetryDocument extends DataTelemetryBasePayload {
+ dataset?: {
+ name?: string;
+ type?: DataTelemetryType | 'unknown' | string; // The union of types is to help autocompletion with some known `dataset.type`s
+ };
+ shipper?: string;
+ pattern_name?: DataPatternName;
+}
+
+export type DataTelemetryPayload = DataTelemetryDocument[];
+
+export interface DataTelemetryIndex {
+ name: string;
+ datasetName?: string; // To be obtained from `mappings.dataset.name` if it's a constant keyword
+ datasetType?: string; // To be obtained from `mappings.dataset.type` if it's a constant keyword
+ shipper?: string; // To be obtained from `_meta.beat` if it's set
+ isECS?: boolean; // Optional because it can't be obtained via Monitoring.
+
+ // The fields below are optional because we might not be able to obtain them if the user does not
+ // have access to the index.
+ docCount?: number;
+ sizeInBytes?: number;
+}
+
+type AtLeastOne }> = Partial & U[keyof U];
+
+type DataDescriptor = AtLeastOne<{
+ datasetName: string;
+ datasetType: string;
+ shipper: string;
+ patternName: DataPatternName; // When found from the list of the index patterns
+}>;
+
+function findMatchingDescriptors({
+ name,
+ shipper,
+ datasetName,
+ datasetType,
+}: DataTelemetryIndex): DataDescriptor[] {
+ // If we already have the data from the indices' mappings...
+ if ([shipper, datasetName, datasetType].some(Boolean)) {
+ return [
+ {
+ ...(shipper && { shipper }),
+ ...(datasetName && { datasetName }),
+ ...(datasetType && { datasetType }),
+ } as AtLeastOne<{ datasetName: string; datasetType: string; shipper: string }>, // Using casting here because TS doesn't infer at least one exists from the if clause
+ ];
+ }
+
+ // Otherwise, try with the list of known index patterns
+ return DATA_DATASETS_INDEX_PATTERNS_UNIQUE.filter(({ pattern }) => {
+ if (!pattern.startsWith('.') && name.startsWith('.')) {
+ // avoid system indices caught by very fuzzy index patterns (i.e.: *log* would catch `.kibana-log-...`)
+ return false;
+ }
+ return new RegExp(`^${pattern.replace(/\./g, '\\.').replace(/\*/g, '.*')}$`).test(name);
+ });
+}
+
+function increaseCounters(
+ previousValue: DataTelemetryBasePayload = { index_count: 0 },
+ { isECS, docCount, sizeInBytes }: DataTelemetryIndex
+) {
+ return {
+ ...previousValue,
+ index_count: previousValue.index_count + 1,
+ ...(typeof isECS === 'boolean'
+ ? {
+ ecs_index_count: (previousValue.ecs_index_count || 0) + (isECS ? 1 : 0),
+ }
+ : {}),
+ ...(typeof docCount === 'number'
+ ? { doc_count: (previousValue.doc_count || 0) + docCount }
+ : {}),
+ ...(typeof sizeInBytes === 'number'
+ ? { size_in_bytes: (previousValue.size_in_bytes || 0) + sizeInBytes }
+ : {}),
+ };
+}
+
+export function buildDataTelemetryPayload(indices: DataTelemetryIndex[]): DataTelemetryPayload {
+ const startingDotPatternsUntilTheFirstAsterisk = DATA_DATASETS_INDEX_PATTERNS_UNIQUE.map(
+ ({ pattern }) => pattern.replace(/^\.(.+)\*.*$/g, '.$1')
+ ).filter(Boolean);
+
+ // Filter out the system indices unless they are required by the patterns
+ const indexCandidates = indices.filter(
+ ({ name }) =>
+ !(
+ name.startsWith('.') &&
+ !startingDotPatternsUntilTheFirstAsterisk.find((pattern) => name.startsWith(pattern))
+ )
+ );
+
+ const acc = new Map();
+
+ for (const indexCandidate of indexCandidates) {
+ const matchingDescriptors = findMatchingDescriptors(indexCandidate);
+ for (const { datasetName, datasetType, shipper, patternName } of matchingDescriptors) {
+ const key = `${datasetName}-${datasetType}-${shipper}-${patternName}`;
+ acc.set(key, {
+ ...((datasetName || datasetType) && { dataset: { name: datasetName, type: datasetType } }),
+ ...(shipper && { shipper }),
+ ...(patternName && { pattern_name: patternName }),
+ ...increaseCounters(acc.get(key), indexCandidate),
+ });
+ }
+ }
+
+ return [...acc.values()];
+}
+
+interface IndexStats {
+ indices: {
+ [indexName: string]: {
+ total: {
+ docs: {
+ count: number;
+ deleted: number;
+ };
+ store: {
+ size_in_bytes: number;
+ };
+ };
+ };
+ };
+}
+
+interface IndexMappings {
+ [indexName: string]: {
+ mappings: {
+ _meta?: {
+ beat?: string;
+ };
+ properties: {
+ dataset?: {
+ properties: {
+ name?: {
+ type: string;
+ value?: string;
+ };
+ type?: {
+ type: string;
+ value?: string;
+ };
+ };
+ };
+ ecs?: {
+ properties: {
+ version?: {
+ type: string;
+ };
+ };
+ };
+ };
+ };
+ };
+}
+
+export async function getDataTelemetry(callCluster: LegacyAPICaller) {
+ try {
+ const index = [
+ ...DATA_DATASETS_INDEX_PATTERNS_UNIQUE.map(({ pattern }) => pattern),
+ '*-*-*-*', // Include new indexing strategy indices {type}-{dataset}-{namespace}-{rollover_counter}
+ ];
+ const [indexMappings, indexStats]: [IndexMappings, IndexStats] = await Promise.all([
+ // GET */_mapping?filter_path=*.mappings._meta.beat,*.mappings.properties.ecs.properties.version.type,*.mappings.properties.dataset.properties.type.value,*.mappings.properties.dataset.properties.name.value
+ callCluster('indices.getMapping', {
+ index: '*', // Request all indices because filter_path already filters out the indices without any of those fields
+ filterPath: [
+ // _meta.beat tells the shipper
+ '*.mappings._meta.beat',
+ // Does it have `ecs.version` in the mappings? => It follows the ECS conventions
+ '*.mappings.properties.ecs.properties.version.type',
+
+ // Disable the fields below because they are still pending to be confirmed:
+ // https://github.com/elastic/ecs/pull/845
+ // TODO: Re-enable when the final fields are confirmed
+ // // If `dataset.type` is a `constant_keyword`, it can be reported as a type
+ // '*.mappings.properties.dataset.properties.type.value',
+ // // If `dataset.name` is a `constant_keyword`, it can be reported as the dataset
+ // '*.mappings.properties.dataset.properties.name.value',
+ ],
+ }),
+ // GET /_stats/docs,store?level=indices&filter_path=indices.*.total
+ callCluster('indices.stats', {
+ index,
+ level: 'indices',
+ metric: ['docs', 'store'],
+ filterPath: ['indices.*.total'],
+ }),
+ ]);
+
+ const indexNames = Object.keys({ ...indexMappings, ...indexStats?.indices });
+ const indices = indexNames.map((name) => {
+ const isECS = !!indexMappings[name]?.mappings?.properties.ecs?.properties.version?.type;
+ const shipper = indexMappings[name]?.mappings?._meta?.beat;
+ const datasetName = indexMappings[name]?.mappings?.properties.dataset?.properties.name?.value;
+ const datasetType = indexMappings[name]?.mappings?.properties.dataset?.properties.type?.value;
+
+ const stats = (indexStats?.indices || {})[name];
+ if (stats) {
+ return {
+ name,
+ datasetName,
+ datasetType,
+ shipper,
+ isECS,
+ docCount: stats.total?.docs?.count,
+ sizeInBytes: stats.total?.store?.size_in_bytes,
+ };
+ }
+ return { name, datasetName, datasetType, shipper, isECS };
+ });
+ return buildDataTelemetryPayload(indices);
+ } catch (e) {
+ return [];
+ }
+}
diff --git a/src/setup_node_env/harden.js b/src/plugins/telemetry/server/telemetry_collection/get_data_telemetry/index.ts
similarity index 80%
rename from src/setup_node_env/harden.js
rename to src/plugins/telemetry/server/telemetry_collection/get_data_telemetry/index.ts
index dead3db1d60b4d..d056d1c9f299f3 100644
--- a/src/setup_node_env/harden.js
+++ b/src/plugins/telemetry/server/telemetry_collection/get_data_telemetry/index.ts
@@ -17,8 +17,11 @@
* under the License.
*/
-var hook = require('require-in-the-middle');
+export { DATA_TELEMETRY_ID } from './constants';
-hook(['child_process'], function (exports, name) {
- return require(`./patches/${name}`)(exports); // eslint-disable-line import/no-dynamic-require
-});
+export {
+ DataTelemetryIndex,
+ DataTelemetryPayload,
+ getDataTelemetry,
+ buildDataTelemetryPayload,
+} from './get_data_telemetry';
diff --git a/src/plugins/telemetry/server/telemetry_collection/get_local_stats.ts b/src/plugins/telemetry/server/telemetry_collection/get_local_stats.ts
index b42edde2f55ca2..4d4031bb428baf 100644
--- a/src/plugins/telemetry/server/telemetry_collection/get_local_stats.ts
+++ b/src/plugins/telemetry/server/telemetry_collection/get_local_stats.ts
@@ -25,6 +25,7 @@ import { getClusterInfo, ESClusterInfo } from './get_cluster_info';
import { getClusterStats } from './get_cluster_stats';
import { getKibana, handleKibanaStats, KibanaUsageStats } from './get_kibana';
import { getNodesUsage } from './get_nodes_usage';
+import { getDataTelemetry, DATA_TELEMETRY_ID, DataTelemetryPayload } from './get_data_telemetry';
/**
* Handle the separate local calls by combining them into a single object response that looks like the
@@ -39,6 +40,7 @@ export function handleLocalStats(
{ cluster_name, cluster_uuid, version }: ESClusterInfo,
{ _nodes, cluster_name: clusterName, ...clusterStats }: any,
kibana: KibanaUsageStats,
+ dataTelemetry: DataTelemetryPayload,
context: StatsCollectionContext
) {
return {
@@ -49,6 +51,7 @@ export function handleLocalStats(
cluster_stats: clusterStats,
collection: 'local',
stack_stats: {
+ [DATA_TELEMETRY_ID]: dataTelemetry,
kibana: handleKibanaStats(context, kibana),
},
};
@@ -68,11 +71,12 @@ export const getLocalStats: StatsGetter<{}, TelemetryLocalStats> = async (
return await Promise.all(
clustersDetails.map(async (clustersDetail) => {
- const [clusterInfo, clusterStats, nodesUsage, kibana] = await Promise.all([
+ const [clusterInfo, clusterStats, nodesUsage, kibana, dataTelemetry] = await Promise.all([
getClusterInfo(callCluster), // cluster info
getClusterStats(callCluster), // cluster stats (not to be confused with cluster _state_)
getNodesUsage(callCluster), // nodes_usage info
getKibana(usageCollection, callCluster),
+ getDataTelemetry(callCluster),
]);
return handleLocalStats(
clusterInfo,
@@ -81,6 +85,7 @@ export const getLocalStats: StatsGetter<{}, TelemetryLocalStats> = async (
nodes: { ...clusterStats.nodes, usage: nodesUsage },
},
kibana,
+ dataTelemetry,
context
);
})
diff --git a/src/plugins/telemetry/server/telemetry_collection/index.ts b/src/plugins/telemetry/server/telemetry_collection/index.ts
index 377ddab7b877ce..40cbf0e4caa1d9 100644
--- a/src/plugins/telemetry/server/telemetry_collection/index.ts
+++ b/src/plugins/telemetry/server/telemetry_collection/index.ts
@@ -17,6 +17,12 @@
* under the License.
*/
+export {
+ DATA_TELEMETRY_ID,
+ DataTelemetryIndex,
+ DataTelemetryPayload,
+ buildDataTelemetryPayload,
+} from './get_data_telemetry';
export { getLocalStats, TelemetryLocalStats } from './get_local_stats';
export { getLocalLicense } from './get_local_license';
export { getClusterUuids } from './get_cluster_stats';
diff --git a/src/plugins/tile_map/public/index.scss b/src/plugins/tile_map/public/index.scss
index 4ce500b2da4d22..f4b86b0c31190b 100644
--- a/src/plugins/tile_map/public/index.scss
+++ b/src/plugins/tile_map/public/index.scss
@@ -1,5 +1,3 @@
-@import 'src/legacy/ui/public/styles/styling_constants';
-
// Prefix all styles with "tlm" to avoid conflicts.
// Examples
// tlmChart
diff --git a/src/plugins/vis_default_editor/public/components/agg_group.tsx b/src/plugins/vis_default_editor/public/components/agg_group.tsx
index 3030601236687d..4cde33b8fbc314 100644
--- a/src/plugins/vis_default_editor/public/components/agg_group.tsx
+++ b/src/plugins/vis_default_editor/public/components/agg_group.tsx
@@ -152,7 +152,7 @@ function DefaultEditorAggGroup({
{bucketsError && (
<>
- {bucketsError}
+ {bucketsError}
>
)}
diff --git a/src/plugins/vis_default_editor/public/components/agg_params_helper.ts b/src/plugins/vis_default_editor/public/components/agg_params_helper.ts
index 45abbf8d2b2dd3..ef2f937c8547cf 100644
--- a/src/plugins/vis_default_editor/public/components/agg_params_helper.ts
+++ b/src/plugins/vis_default_editor/public/components/agg_params_helper.ts
@@ -87,7 +87,7 @@ function getAggParamsToRender({
// should be refactored in the future to provide a more general way
// for visualization to override some agg config settings
if (agg.type.name === 'top_hits' && param.name === 'field') {
- const allowStrings = _.get(schema, `aggSettings[${agg.type.name}].allowStrings`, false);
+ const allowStrings = get(schema, `aggSettings[${agg.type.name}].allowStrings`, false);
if (!allowStrings) {
availableFields = availableFields.filter((field) => field.type === 'number');
}
@@ -111,7 +111,11 @@ function getAggParamsToRender({
const aggType = agg.type.type;
const aggName = agg.type.name;
const aggParams = get(aggParamsMap, [aggType, aggName], {});
- paramEditor = get(aggParams, param.name) || get(aggParamsMap, ['common', param.type]);
+ paramEditor = get(aggParams, param.name);
+ }
+
+ if (!paramEditor) {
+ paramEditor = get(aggParamsMap, ['common', param.type]);
}
// show params with an editor component
diff --git a/src/plugins/vis_default_editor/public/components/controls/components/input_list.tsx b/src/plugins/vis_default_editor/public/components/controls/components/input_list.tsx
index a0bc0d78a28895..37e95f2419b458 100644
--- a/src/plugins/vis_default_editor/public/components/controls/components/input_list.tsx
+++ b/src/plugins/vis_default_editor/public/components/controls/components/input_list.tsx
@@ -18,7 +18,7 @@
*/
import React, { useState, useEffect, Fragment, useCallback } from 'react';
-import { isEmpty, isEqual, mapValues, omit, pick } from 'lodash';
+import { isEmpty, isEqual, mapValues, omitBy, pick } from 'lodash';
import {
EuiButtonIcon,
EuiFlexGroup,
@@ -173,7 +173,7 @@ function InputList({ config, list, onChange, setValidity }: InputListProps) {
const model: InputObject = mapValues(pick(models[index], modelNames), 'model');
// we need to skip empty values since they are not stored in saved object
- return !isEqual(item, omit(model, isEmpty));
+ return !isEqual(item, omitBy(model, isEmpty));
})
) {
setModels(
diff --git a/src/plugins/vis_default_editor/public/components/controls/components/number_list/utils.ts b/src/plugins/vis_default_editor/public/components/controls/components/number_list/utils.ts
index 6eaef3050029af..a3998cbd5954be 100644
--- a/src/plugins/vis_default_editor/public/components/controls/components/number_list/utils.ts
+++ b/src/plugins/vis_default_editor/public/components/controls/components/number_list/utils.ts
@@ -105,7 +105,7 @@ function validateValueUnique(
}
function getNextModel(list: NumberRowModel[], range: NumberListRange): NumberRowModel {
- const lastValue = last(list).value;
+ const lastValue = (last(list) as NumberRowModel).value;
let next = Number(lastValue) ? Number(lastValue) + 1 : 1;
if (next >= range.max) {
diff --git a/src/plugins/vis_default_editor/public/components/controls/filters.tsx b/src/plugins/vis_default_editor/public/components/controls/filters.tsx
index 9a9933b5e1e833..04d0df27927fa7 100644
--- a/src/plugins/vis_default_editor/public/components/controls/filters.tsx
+++ b/src/plugins/vis_default_editor/public/components/controls/filters.tsx
@@ -43,7 +43,9 @@ function FiltersParamEditor({ agg, value = [], setValue }: AggParamEditorProps {
// set parsed values into model after initialization
- setValue(filters.map((filter) => omit({ ...filter, input: filter.input }, 'id')));
+ setValue(
+ filters.map((filter) => omit({ ...filter, input: filter.input }, 'id') as FilterValue)
+ );
});
useEffect(() => {
@@ -58,7 +60,7 @@ function FiltersParamEditor({ agg, value = [], setValue }: AggParamEditorProps {
// do not set internal id parameter into saved object
- setValue(updatedFilters.map((filter) => omit(filter, 'id')));
+ setValue(updatedFilters.map((filter) => omit(filter, 'id') as FilterValue));
setFilters(updatedFilters);
};
diff --git a/src/plugins/vis_default_editor/public/components/controls/number_interval.tsx b/src/plugins/vis_default_editor/public/components/controls/number_interval.tsx
index 0d21eb04c12b2a..f6354027ab01b8 100644
--- a/src/plugins/vis_default_editor/public/components/controls/number_interval.tsx
+++ b/src/plugins/vis_default_editor/public/components/controls/number_interval.tsx
@@ -56,7 +56,7 @@ function NumberIntervalParamEditor({
setValidity,
setValue,
}: AggParamEditorProps) {
- const base: number = get(editorConfig, 'interval.base');
+ const base: number = get(editorConfig, 'interval.base') as number;
const min = base || 0;
const isValid = value !== undefined && value >= min;
diff --git a/src/plugins/vis_default_editor/public/components/controls/sub_metric.tsx b/src/plugins/vis_default_editor/public/components/controls/sub_metric.tsx
index 361eeba9abdbf8..fc79ba703c2b4a 100644
--- a/src/plugins/vis_default_editor/public/components/controls/sub_metric.tsx
+++ b/src/plugins/vis_default_editor/public/components/controls/sub_metric.tsx
@@ -45,9 +45,10 @@ function SubMetricParamEditor({
defaultMessage: 'Bucket',
});
const type = aggParam.name;
+ const isCustomMetric = type === 'customMetric';
- const aggTitle = type === 'customMetric' ? metricTitle : bucketTitle;
- const aggGroup = type === 'customMetric' ? AggGroupNames.Metrics : AggGroupNames.Buckets;
+ const aggTitle = isCustomMetric ? metricTitle : bucketTitle;
+ const aggGroup = isCustomMetric ? AggGroupNames.Metrics : AggGroupNames.Buckets;
useMount(() => {
if (agg.params[type]) {
@@ -87,7 +88,7 @@ function SubMetricParamEditor({
setValidity={setValidity}
setTouched={setTouched}
schemas={schemas}
- hideCustomLabel={true}
+ hideCustomLabel={!isCustomMetric}
/>
>
);
diff --git a/src/plugins/vis_default_editor/public/components/controls/time_interval.tsx b/src/plugins/vis_default_editor/public/components/controls/time_interval.tsx
index 4af41f67bc524c..dd9e432fa512e1 100644
--- a/src/plugins/vis_default_editor/public/components/controls/time_interval.tsx
+++ b/src/plugins/vis_default_editor/public/components/controls/time_interval.tsx
@@ -107,7 +107,7 @@ function TimeIntervalParamEditor({
setTouched,
setValidity,
}: AggParamEditorProps) {
- const timeBase: string = get(editorConfig, 'interval.timeBase');
+ const timeBase: string = get(editorConfig, 'interval.timeBase') as string;
const options = timeBase
? []
: ((aggParam as any).options || []).reduce(
diff --git a/src/plugins/vis_default_editor/public/components/sidebar/data_tab.tsx b/src/plugins/vis_default_editor/public/components/sidebar/data_tab.tsx
index 26567d05e04276..b2c7bcafa15a33 100644
--- a/src/plugins/vis_default_editor/public/components/sidebar/data_tab.tsx
+++ b/src/plugins/vis_default_editor/public/components/sidebar/data_tab.tsx
@@ -74,7 +74,8 @@ function DefaultEditorDataTab({
),
[metricAggs]
);
- const lastParentPipelineAggTitle = lastParentPipelineAgg && lastParentPipelineAgg.type.title;
+ const lastParentPipelineAggTitle =
+ lastParentPipelineAgg && (lastParentPipelineAgg as IAggConfig).type.title;
const addSchema: AddSchema = useCallback((schema) => dispatch(addNewAgg(schema)), [dispatch]);
@@ -116,7 +117,7 @@ function DefaultEditorDataTab({
setValidity,
setTouched,
removeAgg: onAggRemove,
- };
+ } as any;
return (
<>
diff --git a/src/plugins/vis_default_editor/public/schemas.ts b/src/plugins/vis_default_editor/public/schemas.ts
index 54520b85cb5ec9..d95a6252331bfb 100644
--- a/src/plugins/vis_default_editor/public/schemas.ts
+++ b/src/plugins/vis_default_editor/public/schemas.ts
@@ -58,6 +58,7 @@ export class Schemas implements ISchemas {
>
) {
_(schemas || [])
+ .chain()
.map((schema) => {
if (!schema.name) throw new Error('all schema must have a unique name');
diff --git a/src/plugins/vis_type_metric/public/components/metric_vis_component.tsx b/src/plugins/vis_type_metric/public/components/metric_vis_component.tsx
index 5e8a463748188c..43858267626124 100644
--- a/src/plugins/vis_type_metric/public/components/metric_vis_component.tsx
+++ b/src/plugins/vis_type_metric/public/components/metric_vis_component.tsx
@@ -28,6 +28,7 @@ import { getHeatmapColors } from '../../../charts/public';
import { VisParams, MetricVisMetric } from '../types';
import { getFormatService } from '../services';
import { SchemaConfig, ExprVis } from '../../../visualizations/public';
+import { Range } from '../../../expressions/public';
export interface MetricVisComponentProps {
visParams: VisParams;
@@ -41,7 +42,7 @@ export class MetricVisComponent extends Component {
const config = this.props.visParams.metric;
const isPercentageMode = config.percentageMode;
const colorsRange = config.colorsRange;
- const max = last(colorsRange).to;
+ const max = (last(colorsRange) as Range).to;
const labels: string[] = [];
colorsRange.forEach((range: any) => {
@@ -111,7 +112,7 @@ export class MetricVisComponent extends Component {
const dimensions = this.props.visParams.dimensions;
const isPercentageMode = config.percentageMode;
const min = config.colorsRange[0].from;
- const max = last(config.colorsRange).to;
+ const max = (last(config.colorsRange) as Range).to;
const colors = this.getColors();
const labels = this.getLabels();
const metrics: MetricVisMetric[] = [];
diff --git a/src/plugins/vis_type_table/public/table_vis_type.ts b/src/plugins/vis_type_table/public/table_vis_type.ts
index c3bc72497007ea..80d53021b7866d 100644
--- a/src/plugins/vis_type_table/public/table_vis_type.ts
+++ b/src/plugins/vis_type_table/public/table_vis_type.ts
@@ -26,6 +26,7 @@ import { tableVisResponseHandler } from './table_vis_response_handler';
import tableVisTemplate from './table_vis.html';
import { TableOptions } from './components/table_vis_options_lazy';
import { getTableVisualizationControllerClass } from './vis_controller';
+import { VIS_EVENT_TO_TRIGGER } from '../../../plugins/visualizations/public';
export function getTableVisTypeDefinition(core: CoreSetup, context: PluginInitializerContext) {
return {
@@ -39,6 +40,9 @@ export function getTableVisTypeDefinition(core: CoreSetup, context: PluginInitia
defaultMessage: 'Display values in a table',
}),
visualization: getTableVisualizationControllerClass(core, context),
+ getSupportedTriggers: () => {
+ return [VIS_EVENT_TO_TRIGGER.filter];
+ },
visConfig: {
defaults: {
perPage: 10,
diff --git a/src/plugins/vis_type_tagcloud/public/tag_cloud_type.ts b/src/plugins/vis_type_tagcloud/public/tag_cloud_type.ts
index 5a8cc3004a3154..023489c6d2e876 100644
--- a/src/plugins/vis_type_tagcloud/public/tag_cloud_type.ts
+++ b/src/plugins/vis_type_tagcloud/public/tag_cloud_type.ts
@@ -22,6 +22,7 @@ import { i18n } from '@kbn/i18n';
import { Schemas } from '../../vis_default_editor/public';
import { TagCloudOptions } from './components/tag_cloud_options';
+import { VIS_EVENT_TO_TRIGGER } from '../../../plugins/visualizations/public';
// @ts-ignore
import { createTagCloudVisualization } from './components/tag_cloud_visualization';
@@ -31,6 +32,9 @@ export const createTagCloudVisTypeDefinition = (deps: TagCloudVisDependencies) =
name: 'tagcloud',
title: i18n.translate('visTypeTagCloud.vis.tagCloudTitle', { defaultMessage: 'Tag Cloud' }),
icon: 'visTagCloud',
+ getSupportedTriggers: () => {
+ return [VIS_EVENT_TO_TRIGGER.filter];
+ },
description: i18n.translate('visTypeTagCloud.vis.tagCloudDescription', {
defaultMessage: 'A group of words, sized according to their importance',
}),
diff --git a/src/plugins/vis_type_timelion/public/helpers/panel_utils.ts b/src/plugins/vis_type_timelion/public/helpers/panel_utils.ts
index db29d9112be8e8..860b4e9f2dbde1 100644
--- a/src/plugins/vis_type_timelion/public/helpers/panel_utils.ts
+++ b/src/plugins/vis_type_timelion/public/helpers/panel_utils.ts
@@ -17,7 +17,7 @@
* under the License.
*/
-import { cloneDeep, defaults, merge, compact } from 'lodash';
+import { cloneDeep, defaults, mergeWith, compact } from 'lodash';
import moment, { Moment } from 'moment-timezone';
import { TimefilterContract } from 'src/plugins/data/public';
@@ -91,7 +91,7 @@ function buildSeriesData(chart: Series[], options: jquery.flot.plotOptions) {
}
if (series._global) {
- merge(options, series._global, (objVal, srcVal) => {
+ mergeWith(options, series._global, (objVal, srcVal) => {
// This is kind of gross, it means that you can't replace a global value with a null
// best you can do is an empty string. Deal with it.
if (objVal == null) {
diff --git a/src/plugins/vis_type_timelion/public/timelion_vis_fn.ts b/src/plugins/vis_type_timelion/public/timelion_vis_fn.ts
index c02f43818af9c3..7be18a4774d94b 100644
--- a/src/plugins/vis_type_timelion/public/timelion_vis_fn.ts
+++ b/src/plugins/vis_type_timelion/public/timelion_vis_fn.ts
@@ -27,6 +27,7 @@ import {
import { getTimelionRequestHandler } from './helpers/timelion_request_handler';
import { TIMELION_VIS_NAME } from './timelion_vis_type';
import { TimelionVisDependencies } from './plugin';
+import { Filter, Query, TimeRange } from '../../data/common';
type Input = KibanaContext | null;
type Output = Promise>;
@@ -71,9 +72,9 @@ export const getTimelionVisualizationConfig = (
const visParams = { expression: args.expression, interval: args.interval };
const response = await timelionRequestHandler({
- timeRange: get(input, 'timeRange'),
- query: get(input, 'query'),
- filters: get(input, 'filters'),
+ timeRange: get(input, 'timeRange') as TimeRange,
+ query: get(input, 'query') as Query,
+ filters: get(input, 'filters') as Filter[],
visParams,
forceFetch: true,
});
diff --git a/src/plugins/vis_type_timelion/server/fit_functions/average.js b/src/plugins/vis_type_timelion/server/fit_functions/average.js
index 06db7bd0e9324c..09518a32864877 100644
--- a/src/plugins/vis_type_timelion/server/fit_functions/average.js
+++ b/src/plugins/vis_type_timelion/server/fit_functions/average.js
@@ -27,7 +27,7 @@ export default function average(dataTuples, targetTuples) {
// Phase 1: Downsample
// We necessarily won't well match the dataSource here as we don't know how much data
// they had when creating their own average
- const resultTimes = _.pluck(targetTuples, 0);
+ const resultTimes = _.map(targetTuples, 0);
const dataTuplesQueue = _.clone(dataTuples);
const resultValues = _.map(targetTuples, function (bucket) {
const time = bucket[0];
diff --git a/src/plugins/vis_type_timelion/server/handlers/chain_runner.js b/src/plugins/vis_type_timelion/server/handlers/chain_runner.js
index 59adea30730c73..2ee8deb4dd0493 100644
--- a/src/plugins/vis_type_timelion/server/handlers/chain_runner.js
+++ b/src/plugins/vis_type_timelion/server/handlers/chain_runner.js
@@ -132,7 +132,7 @@ export default function chainRunner(tlConfig) {
});
});
return Bluebird.all(seriesList).then(function (args) {
- const list = _.chain(args).pluck('list').flatten().value();
+ const list = _.chain(args).map('list').flatten().value();
const seriesList = _.merge.apply(this, _.flatten([{}, args]));
seriesList.list = list;
return seriesList;
diff --git a/src/plugins/vis_type_timelion/server/handlers/lib/validate_arg.js b/src/plugins/vis_type_timelion/server/handlers/lib/validate_arg.js
index 9b4fdddc2186e1..11004d2784d3c4 100644
--- a/src/plugins/vis_type_timelion/server/handlers/lib/validate_arg.js
+++ b/src/plugins/vis_type_timelion/server/handlers/lib/validate_arg.js
@@ -28,7 +28,7 @@ export default function validateArgFn(functionDef) {
const multi = argDef.multi;
const isCorrectType = (function () {
// If argument is not allow to be specified multiple times, we're dealing with a plain value for type
- if (!multi) return _.contains(required, type);
+ if (!multi) return _.includes(required, type);
// If it is, we'll get an array for type
return _.difference(type, required).length === 0;
})();
diff --git a/src/plugins/vis_type_timelion/server/lib/as_sorted.js b/src/plugins/vis_type_timelion/server/lib/as_sorted.js
index 536145a6b8dcda..6a2b7c0f5a9f5e 100644
--- a/src/plugins/vis_type_timelion/server/lib/as_sorted.js
+++ b/src/plugins/vis_type_timelion/server/lib/as_sorted.js
@@ -22,5 +22,5 @@ import unzipPairs from './unzip_pairs.js';
export default function asSorted(timeValObject, fn) {
const data = unzipPairs(timeValObject);
- return _.zipObject(fn(data));
+ return _.fromPairs(fn(data));
}
diff --git a/src/plugins/vis_type_timelion/server/lib/classes/timelion_function.js b/src/plugins/vis_type_timelion/server/lib/classes/timelion_function.js
index 83466e263cf2ff..3d53fc8d5bd092 100644
--- a/src/plugins/vis_type_timelion/server/lib/classes/timelion_function.js
+++ b/src/plugins/vis_type_timelion/server/lib/classes/timelion_function.js
@@ -25,7 +25,7 @@ export default class TimelionFunction {
constructor(name, config) {
this.name = name;
this.args = config.args || [];
- this.argsByName = _.indexBy(this.args, 'name');
+ this.argsByName = _.keyBy(this.args, 'name');
this.help = config.help || '';
this.aliases = config.aliases || [];
this.extended = config.extended || false;
diff --git a/src/plugins/vis_type_timelion/server/lib/load_functions.js b/src/plugins/vis_type_timelion/server/lib/load_functions.js
index d6cb63b7c427b1..699342cff6a796 100644
--- a/src/plugins/vis_type_timelion/server/lib/load_functions.js
+++ b/src/plugins/vis_type_timelion/server/lib/load_functions.js
@@ -47,7 +47,7 @@ export default function (directory) {
})
.value();
- const functions = _.zipObject(files.concat(directories));
+ const functions = _.fromPairs(files.concat(directories));
_.each(functions, function (func) {
_.assign(functions, processFunctionDefinition(func));
diff --git a/src/plugins/vis_type_timelion/server/lib/reduce.js b/src/plugins/vis_type_timelion/server/lib/reduce.js
index cc13b75fde12d8..1a5d78676fc720 100644
--- a/src/plugins/vis_type_timelion/server/lib/reduce.js
+++ b/src/plugins/vis_type_timelion/server/lib/reduce.js
@@ -42,7 +42,7 @@ async function pairwiseReduce(left, right, fn) {
if (allSeriesContainKey(left, 'split') && allSeriesContainKey(right, 'split')) {
pairwiseField = 'split';
}
- const indexedList = _.indexBy(right.list, pairwiseField);
+ const indexedList = _.keyBy(right.list, pairwiseField);
// ensure seriesLists contain same pairwise labels
left.list.forEach((leftSeries) => {
diff --git a/src/plugins/vis_type_timelion/server/lib/unzip_pairs.js b/src/plugins/vis_type_timelion/server/lib/unzip_pairs.js
index 7a34b5ec98ff03..412049c89ef2f1 100644
--- a/src/plugins/vis_type_timelion/server/lib/unzip_pairs.js
+++ b/src/plugins/vis_type_timelion/server/lib/unzip_pairs.js
@@ -21,7 +21,7 @@ import _ from 'lodash';
export default function unzipPairs(timeValObject) {
const paired = _.chain(timeValObject)
- .pairs()
+ .toPairs()
.map(function (point) {
return [parseInt(point[0], 10), point[1]];
})
diff --git a/src/plugins/vis_type_timelion/server/series_functions/es/lib/agg_response_to_series_list.js b/src/plugins/vis_type_timelion/server/series_functions/es/lib/agg_response_to_series_list.js
index 409372da24724b..fbae9c5afffe84 100644
--- a/src/plugins/vis_type_timelion/server/series_functions/es/lib/agg_response_to_series_list.js
+++ b/src/plugins/vis_type_timelion/server/series_functions/es/lib/agg_response_to_series_list.js
@@ -20,7 +20,7 @@
import _ from 'lodash';
export function timeBucketsToPairs(buckets) {
- const timestamps = _.pluck(buckets, 'key');
+ const timestamps = _.map(buckets, 'key');
const series = {};
_.each(buckets, function (bucket) {
_.forOwn(bucket, function (val, key) {
diff --git a/src/plugins/vis_type_timelion/server/series_functions/es/lib/build_request.js b/src/plugins/vis_type_timelion/server/series_functions/es/lib/build_request.js
index bc0e368fbdab17..e407636c41567e 100644
--- a/src/plugins/vis_type_timelion/server/series_functions/es/lib/build_request.js
+++ b/src/plugins/vis_type_timelion/server/series_functions/es/lib/build_request.js
@@ -50,7 +50,7 @@ export default function buildRequest(config, tlConfig, scriptedFields, timeout)
.map(function (q) {
return [q, { query_string: { query: q } }];
})
- .zipObject()
+ .fromPairs()
.value(),
},
aggs: {},
diff --git a/src/plugins/vis_type_timelion/server/series_functions/movingaverage.js b/src/plugins/vis_type_timelion/server/series_functions/movingaverage.js
index 108eb0c72f19d9..fdaa4dcd8c098e 100644
--- a/src/plugins/vis_type_timelion/server/series_functions/movingaverage.js
+++ b/src/plugins/vis_type_timelion/server/series_functions/movingaverage.js
@@ -81,7 +81,7 @@ export default new Chainable('movingaverage', {
}
_position = _position || defaultPosition;
- if (!_.contains(validPositions, _position)) {
+ if (!_.includes(validPositions, _position)) {
throw new Error(
i18n.translate(
'timelion.serverSideErrors.movingaverageFunction.notValidPositionErrorMessage',
diff --git a/src/plugins/vis_type_timelion/server/series_functions/movingstd.js b/src/plugins/vis_type_timelion/server/series_functions/movingstd.js
index a7ecb4d5b47386..2b9ab08f02edeb 100644
--- a/src/plugins/vis_type_timelion/server/series_functions/movingstd.js
+++ b/src/plugins/vis_type_timelion/server/series_functions/movingstd.js
@@ -61,7 +61,7 @@ export default new Chainable('movingstd', {
return alter(args, function (eachSeries, _window, _position) {
_position = _position || defaultPosition;
- if (!_.contains(positions, _position)) {
+ if (!_.includes(positions, _position)) {
throw new Error(
i18n.translate(
'timelion.serverSideErrors.movingstdFunction.notValidPositionErrorMessage',
diff --git a/src/plugins/vis_type_timelion/server/series_functions/points.js b/src/plugins/vis_type_timelion/server/series_functions/points.js
index bf797bb5aa3437..74d616cffd52d5 100644
--- a/src/plugins/vis_type_timelion/server/series_functions/points.js
+++ b/src/plugins/vis_type_timelion/server/series_functions/points.js
@@ -105,7 +105,7 @@ export default new Chainable('points', {
}
symbol = symbol || defaultSymbol;
- if (!_.contains(validSymbols, symbol)) {
+ if (!_.includes(validSymbols, symbol)) {
throw new Error(
i18n.translate('timelion.serverSideErrors.pointsFunction.notValidSymbolErrorMessage', {
defaultMessage: 'Valid symbols are: {validSymbols}',
diff --git a/src/plugins/vis_type_timelion/server/series_functions/static.test.js b/src/plugins/vis_type_timelion/server/series_functions/static.test.js
index 88ec9fecd904a1..36c5dc708f860f 100644
--- a/src/plugins/vis_type_timelion/server/series_functions/static.test.js
+++ b/src/plugins/vis_type_timelion/server/series_functions/static.test.js
@@ -26,7 +26,7 @@ import invoke from './helpers/invoke_series_fn.js';
describe('static.js', () => {
it('returns a series in which all numbers are the same', () => {
return invoke(fn, [5]).then((r) => {
- expect(_.unique(_.map(r.output.list[0].data, 1))).to.eql([5]);
+ expect(_.uniq(_.map(r.output.list[0].data, 1))).to.eql([5]);
});
});
diff --git a/src/plugins/vis_type_timeseries/public/application/components/aggs/agg_row.tsx b/src/plugins/vis_type_timeseries/public/application/components/aggs/agg_row.tsx
index 0363ba486a7753..fcb22a9e797078 100644
--- a/src/plugins/vis_type_timeseries/public/application/components/aggs/agg_row.tsx
+++ b/src/plugins/vis_type_timeseries/public/application/components/aggs/agg_row.tsx
@@ -39,7 +39,7 @@ interface AggRowProps {
export function AggRow(props: AggRowProps) {
let iconType = 'eyeClosed';
let iconColor = 'subdued';
- const lastSibling = last(props.siblings);
+ const lastSibling = last(props.siblings) as MetricsItemsSchema;
if (lastSibling.id === props.model.id) {
iconType = 'eye';
diff --git a/src/plugins/vis_type_timeseries/public/application/components/lib/series_change_handler.js b/src/plugins/vis_type_timeseries/public/application/components/lib/series_change_handler.js
index beb691f4b7117e..0638c6e67f5efd 100644
--- a/src/plugins/vis_type_timeseries/public/application/components/lib/series_change_handler.js
+++ b/src/plugins/vis_type_timeseries/public/application/components/lib/series_change_handler.js
@@ -31,7 +31,7 @@ export const seriesChangeHandler = (props, items) => (doc) => {
const metric = newMetricAggFn();
metric.type = doc.type;
const incompatPipelines = ['calculation', 'series_agg'];
- if (!_.contains(incompatPipelines, doc.type)) metric.field = doc.id;
+ if (!_.includes(incompatPipelines, doc.type)) metric.field = doc.id;
return metric;
});
} else {
diff --git a/src/plugins/vis_type_timeseries/public/application/components/vis_types/_vis_types.scss b/src/plugins/vis_type_timeseries/public/application/components/vis_types/_vis_types.scss
index 3db09bace079f1..c445d456a1703b 100644
--- a/src/plugins/vis_type_timeseries/public/application/components/vis_types/_vis_types.scss
+++ b/src/plugins/vis_type_timeseries/public/application/components/vis_types/_vis_types.scss
@@ -2,7 +2,11 @@
display: flex;
flex-direction: column;
flex: 1 1 100%;
- padding: $euiSizeS;
+
+ // border used in lieu of padding to prevent overlapping background-color
+ border-width: $euiSizeS;
+ border-style: solid;
+ border-color: transparent;
.tvbVisTimeSeries {
overflow: hidden;
diff --git a/src/plugins/vis_type_timeseries/public/application/components/vis_types/timeseries/vis.js b/src/plugins/vis_type_timeseries/public/application/components/vis_types/timeseries/vis.js
index ddfaf3c1428d9c..612a7a48bade17 100644
--- a/src/plugins/vis_type_timeseries/public/application/components/vis_types/timeseries/vis.js
+++ b/src/plugins/vis_type_timeseries/public/application/components/vis_types/timeseries/vis.js
@@ -34,7 +34,7 @@ import { getInterval } from '../../lib/get_interval';
import { areFieldsDifferent } from '../../lib/charts';
import { createXaxisFormatter } from '../../lib/create_xaxis_formatter';
import { STACKED_OPTIONS } from '../../../visualizations/constants';
-import { getCoreStart, getUISettings } from '../../../../services';
+import { getCoreStart } from '../../../../services';
export class TimeseriesVisualization extends Component {
static propTypes = {
@@ -154,7 +154,7 @@ export class TimeseriesVisualization extends Component {
const styles = reactCSS({
default: {
tvbVis: {
- backgroundColor: get(model, 'background_color'),
+ borderColor: get(model, 'background_color'),
},
},
});
@@ -237,7 +237,6 @@ export class TimeseriesVisualization extends Component {
}
});
- const darkMode = getUISettings().get('theme:darkMode');
return (
values.map(({ key, docs }) => ({
@@ -56,7 +56,6 @@ const handleCursorUpdate = (cursor) => {
};
export const TimeSeries = ({
- darkMode,
backgroundColor,
showGrid,
legend,
@@ -90,15 +89,15 @@ export const TimeSeries = ({
const timeZone = getTimezone(uiSettings);
const hasBarChart = series.some(({ bars }) => bars?.show);
- // compute the theme based on the bg color
- const theme = getTheme(darkMode, backgroundColor);
// apply legend style change if bgColor is configured
const classes = classNames('tvbVisTimeSeries', getChartClasses(backgroundColor));
// If the color isn't configured by the user, use the color mapping service
// to assign a color from the Kibana palette. Colors will be shared across the
// session, including dashboards.
- const { colors } = getChartsSetup();
+ const { colors, theme: themeService } = getChartsSetup();
+ const baseTheme = getBaseTheme(themeService.useChartsBaseTheme(), backgroundColor);
+
colors.mappedColors.mapKeys(series.filter(({ color }) => !color).map(({ label }) => label));
const onBrushEndListener = ({ x }) => {
@@ -118,7 +117,7 @@ export const TimeSeries = ({
onBrushEnd={onBrushEndListener}
animateData={false}
onPointerUpdate={handleCursorUpdate}
- theme={
+ theme={[
hasBarChart
? {}
: {
@@ -127,9 +126,14 @@ export const TimeSeries = ({
fill: '#F00',
},
},
- }
- }
- baseTheme={theme}
+ },
+ {
+ background: {
+ color: backgroundColor,
+ },
+ },
+ ]}
+ baseTheme={baseTheme}
tooltip={{
snap: true,
type: tooltipMode === 'show_focused' ? TooltipType.Follow : TooltipType.VerticalCursor,
@@ -269,7 +273,6 @@ TimeSeries.defaultProps = {
};
TimeSeries.propTypes = {
- darkMode: PropTypes.bool,
backgroundColor: PropTypes.string,
showGrid: PropTypes.bool,
legend: PropTypes.bool,
diff --git a/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/utils/theme.test.ts b/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/utils/theme.test.ts
index 57ca38168ac27f..d7e6560a8dc971 100644
--- a/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/utils/theme.test.ts
+++ b/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/utils/theme.test.ts
@@ -17,28 +17,30 @@
* under the License.
*/
-import { getTheme } from './theme';
+import { getBaseTheme } from './theme';
import { LIGHT_THEME, DARK_THEME } from '@elastic/charts';
describe('TSVB theme', () => {
it('should return the basic themes if no bg color is specified', () => {
// use original dark/light theme
- expect(getTheme(false)).toEqual(LIGHT_THEME);
- expect(getTheme(true)).toEqual(DARK_THEME);
+ expect(getBaseTheme(LIGHT_THEME)).toEqual(LIGHT_THEME);
+ expect(getBaseTheme(DARK_THEME)).toEqual(DARK_THEME);
// discard any wrong/missing bg color
- expect(getTheme(true, null)).toEqual(DARK_THEME);
- expect(getTheme(true, '')).toEqual(DARK_THEME);
- expect(getTheme(true, undefined)).toEqual(DARK_THEME);
+ expect(getBaseTheme(DARK_THEME, null)).toEqual(DARK_THEME);
+ expect(getBaseTheme(DARK_THEME, '')).toEqual(DARK_THEME);
+ expect(getBaseTheme(DARK_THEME, undefined)).toEqual(DARK_THEME);
});
it('should return a highcontrast color theme for a different background', () => {
// red use a near full-black color
- expect(getTheme(false, 'red').axes.axisTitleStyle.fill).toEqual('rgb(23,23,23)');
+ expect(getBaseTheme(LIGHT_THEME, 'red').axes.axisTitleStyle.fill).toEqual('rgb(23,23,23)');
// violet increased the text color to full white for higer contrast
- expect(getTheme(false, '#ba26ff').axes.axisTitleStyle.fill).toEqual('rgb(255,255,255)');
+ expect(getBaseTheme(LIGHT_THEME, '#ba26ff').axes.axisTitleStyle.fill).toEqual(
+ 'rgb(255,255,255)'
+ );
// light yellow, prefer the LIGHT_THEME fill color because already with a good contrast
- expect(getTheme(false, '#fff49f').axes.axisTitleStyle.fill).toEqual('#333');
+ expect(getBaseTheme(LIGHT_THEME, '#fff49f').axes.axisTitleStyle.fill).toEqual('#333');
});
});
diff --git a/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/utils/theme.ts b/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/utils/theme.ts
index 2694732aa381d2..0e13fd7ef68f96 100644
--- a/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/utils/theme.ts
+++ b/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/utils/theme.ts
@@ -94,9 +94,15 @@ function isValidColor(color: string | null | undefined): color is string {
}
}
-export function getTheme(darkMode: boolean, bgColor?: string | null): Theme {
+/**
+ * compute base chart theme based on the background color
+ *
+ * @param baseTheme
+ * @param bgColor
+ */
+export function getBaseTheme(baseTheme: Theme, bgColor?: string | null): Theme {
if (!isValidColor(bgColor)) {
- return darkMode ? DARK_THEME : LIGHT_THEME;
+ return baseTheme;
}
const bgLuminosity = computeRelativeLuminosity(bgColor);
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 fd20ff8b024b35..0f0d99bff6f1c6 100644
--- a/src/plugins/vis_type_timeseries/server/lib/get_fields.ts
+++ b/src/plugins/vis_type_timeseries/server/lib/get_fields.ts
@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
-import { uniq } from 'lodash';
+import { uniqBy } from 'lodash';
import { first, map } from 'rxjs/operators';
import { KibanaRequest, RequestHandlerContext } from 'kibana/server';
@@ -87,5 +87,5 @@ export async function getFields(
(field) => field.aggregatable && !indexPatterns.isNestedField(field)
);
- return uniq(fields, (field) => field.name);
+ return uniqBy(fields, (field) => field.name);
}
diff --git a/src/plugins/vis_type_timeseries/server/saved_objects/tsvb_telemetry.ts b/src/plugins/vis_type_timeseries/server/saved_objects/tsvb_telemetry.ts
index f6754e5fd9ca43..a9b542af68c9de 100644
--- a/src/plugins/vis_type_timeseries/server/saved_objects/tsvb_telemetry.ts
+++ b/src/plugins/vis_type_timeseries/server/saved_objects/tsvb_telemetry.ts
@@ -40,7 +40,7 @@ export const tsvbTelemetrySavedObjectType: SavedObjectsType = {
},
},
migrations: {
- '7.7.0': flow(resetCount),
- '7.8.0': flow(resetCount),
+ '7.7.0': flow(resetCount),
+ '7.8.0': flow(resetCount),
},
};
diff --git a/src/plugins/vis_type_vega/public/vega_fn.ts b/src/plugins/vis_type_vega/public/vega_fn.ts
index a9c915fcfb636a..6b1af6044a2c4a 100644
--- a/src/plugins/vis_type_vega/public/vega_fn.ts
+++ b/src/plugins/vis_type_vega/public/vega_fn.ts
@@ -22,6 +22,7 @@ import { i18n } from '@kbn/i18n';
import { ExpressionFunctionDefinition, KibanaContext, Render } from '../../expressions/public';
import { VegaVisualizationDependencies } from './plugin';
import { createVegaRequestHandler } from './vega_request_handler';
+import { TimeRange, Query } from '../../data/public';
type Input = KibanaContext | null;
type Output = Promise>;
@@ -58,9 +59,9 @@ export const createVegaFn = (
const vegaRequestHandler = createVegaRequestHandler(dependencies, context.abortSignal);
const response = await vegaRequestHandler({
- timeRange: get(input, 'timeRange'),
- query: get(input, 'query'),
- filters: get(input, 'filters'),
+ timeRange: get(input, 'timeRange') as TimeRange,
+ query: get(input, 'query') as Query,
+ filters: get(input, 'filters') as any,
visParams: { spec: args.spec },
});
diff --git a/src/plugins/vis_type_vislib/public/area.ts b/src/plugins/vis_type_vislib/public/area.ts
index c42962ad50a4b0..ec90fbd1746a15 100644
--- a/src/plugins/vis_type_vislib/public/area.ts
+++ b/src/plugins/vis_type_vislib/public/area.ts
@@ -40,6 +40,7 @@ import { getAreaOptionTabs, countLabel } from './utils/common_config';
import { createVislibVisController } from './vis_controller';
import { VisTypeVislibDependencies } from './plugin';
import { Rotates } from '../../charts/public';
+import { VIS_EVENT_TO_TRIGGER } from '../../../plugins/visualizations/public';
export const createAreaVisTypeDefinition = (deps: VisTypeVislibDependencies) => ({
name: 'area',
@@ -49,6 +50,9 @@ export const createAreaVisTypeDefinition = (deps: VisTypeVislibDependencies) =>
defaultMessage: 'Emphasize the quantity beneath a line chart',
}),
visualization: createVislibVisController(deps),
+ getSupportedTriggers: () => {
+ return [VIS_EVENT_TO_TRIGGER.filter, VIS_EVENT_TO_TRIGGER.brush];
+ },
visConfig: {
defaults: {
type: 'area',
diff --git a/src/plugins/vis_type_vislib/public/components/options/metrics_axes/utils.ts b/src/plugins/vis_type_vislib/public/components/options/metrics_axes/utils.ts
index 7c4f3b3ec8843d..708e8cf15f0296 100644
--- a/src/plugins/vis_type_vislib/public/components/options/metrics_axes/utils.ts
+++ b/src/plugins/vis_type_vislib/public/components/options/metrics_axes/utils.ts
@@ -17,7 +17,7 @@
* under the License.
*/
-import { capitalize } from 'lodash';
+import { upperFirst } from 'lodash';
import { BasicVislibParams, ValueAxis, SeriesParam } from '../../../types';
import { ChartModes, ChartTypes, InterpolationModes, Positions } from '../../../utils/collections';
@@ -67,7 +67,7 @@ const getUpdatedAxisName = (
axisPosition: ValueAxis['position'],
valueAxes: BasicVislibParams['valueAxes']
) => {
- const axisName = capitalize(axisPosition) + AXIS_PREFIX;
+ const axisName = upperFirst(axisPosition) + AXIS_PREFIX;
const nextAxisNameNumber = valueAxes.reduce(countNextAxisNumber(axisName, 'name'), 1);
return `${axisName}${nextAxisNameNumber}`;
diff --git a/src/plugins/vis_type_vislib/public/heatmap.ts b/src/plugins/vis_type_vislib/public/heatmap.ts
index ced7a38568ffd0..bd3d02029cb23a 100644
--- a/src/plugins/vis_type_vislib/public/heatmap.ts
+++ b/src/plugins/vis_type_vislib/public/heatmap.ts
@@ -28,6 +28,7 @@ import { TimeMarker } from './vislib/visualizations/time_marker';
import { CommonVislibParams, ValueAxis } from './types';
import { VisTypeVislibDependencies } from './plugin';
import { ColorSchemas, ColorSchemaParams } from '../../charts/public';
+import { VIS_EVENT_TO_TRIGGER } from '../../../plugins/visualizations/public';
export interface HeatmapVisParams extends CommonVislibParams, ColorSchemaParams {
type: 'heatmap';
@@ -48,6 +49,9 @@ export const createHeatmapVisTypeDefinition = (deps: VisTypeVislibDependencies)
description: i18n.translate('visTypeVislib.heatmap.heatmapDescription', {
defaultMessage: 'Shade cells within a matrix',
}),
+ getSupportedTriggers: () => {
+ return [VIS_EVENT_TO_TRIGGER.filter];
+ },
visualization: createVislibVisController(deps),
visConfig: {
defaults: {
diff --git a/src/plugins/vis_type_vislib/public/histogram.ts b/src/plugins/vis_type_vislib/public/histogram.ts
index 52242ad11e8f58..8aeeb4ec533abc 100644
--- a/src/plugins/vis_type_vislib/public/histogram.ts
+++ b/src/plugins/vis_type_vislib/public/histogram.ts
@@ -39,6 +39,7 @@ import { getAreaOptionTabs, countLabel } from './utils/common_config';
import { createVislibVisController } from './vis_controller';
import { VisTypeVislibDependencies } from './plugin';
import { Rotates } from '../../charts/public';
+import { VIS_EVENT_TO_TRIGGER } from '../../../plugins/visualizations/public';
export const createHistogramVisTypeDefinition = (deps: VisTypeVislibDependencies) => ({
name: 'histogram',
@@ -50,6 +51,9 @@ export const createHistogramVisTypeDefinition = (deps: VisTypeVislibDependencies
defaultMessage: 'Assign a continuous variable to each axis',
}),
visualization: createVislibVisController(deps),
+ getSupportedTriggers: () => {
+ return [VIS_EVENT_TO_TRIGGER.filter, VIS_EVENT_TO_TRIGGER.brush];
+ },
visConfig: {
defaults: {
type: 'histogram',
diff --git a/src/plugins/vis_type_vislib/public/horizontal_bar.ts b/src/plugins/vis_type_vislib/public/horizontal_bar.ts
index a58c15f136431e..702581828e60d0 100644
--- a/src/plugins/vis_type_vislib/public/horizontal_bar.ts
+++ b/src/plugins/vis_type_vislib/public/horizontal_bar.ts
@@ -37,6 +37,7 @@ import { getAreaOptionTabs, countLabel } from './utils/common_config';
import { createVislibVisController } from './vis_controller';
import { VisTypeVislibDependencies } from './plugin';
import { Rotates } from '../../charts/public';
+import { VIS_EVENT_TO_TRIGGER } from '../../../plugins/visualizations/public';
export const createHorizontalBarVisTypeDefinition = (deps: VisTypeVislibDependencies) => ({
name: 'horizontal_bar',
@@ -48,6 +49,9 @@ export const createHorizontalBarVisTypeDefinition = (deps: VisTypeVislibDependen
defaultMessage: 'Assign a continuous variable to each axis',
}),
visualization: createVislibVisController(deps),
+ getSupportedTriggers: () => {
+ return [VIS_EVENT_TO_TRIGGER.filter, VIS_EVENT_TO_TRIGGER.brush];
+ },
visConfig: {
defaults: {
type: 'histogram',
diff --git a/src/plugins/vis_type_vislib/public/line.ts b/src/plugins/vis_type_vislib/public/line.ts
index a94fd3f3945ab7..6e9190229114b5 100644
--- a/src/plugins/vis_type_vislib/public/line.ts
+++ b/src/plugins/vis_type_vislib/public/line.ts
@@ -38,6 +38,7 @@ import { getAreaOptionTabs, countLabel } from './utils/common_config';
import { createVislibVisController } from './vis_controller';
import { VisTypeVislibDependencies } from './plugin';
import { Rotates } from '../../charts/public';
+import { VIS_EVENT_TO_TRIGGER } from '../../../plugins/visualizations/public';
export const createLineVisTypeDefinition = (deps: VisTypeVislibDependencies) => ({
name: 'line',
@@ -47,6 +48,9 @@ export const createLineVisTypeDefinition = (deps: VisTypeVislibDependencies) =>
defaultMessage: 'Emphasize trends',
}),
visualization: createVislibVisController(deps),
+ getSupportedTriggers: () => {
+ return [VIS_EVENT_TO_TRIGGER.filter, VIS_EVENT_TO_TRIGGER.brush];
+ },
visConfig: {
defaults: {
type: 'line',
diff --git a/src/plugins/vis_type_vislib/public/pie.ts b/src/plugins/vis_type_vislib/public/pie.ts
index a68bc5893406f5..1e81dbdde3f685 100644
--- a/src/plugins/vis_type_vislib/public/pie.ts
+++ b/src/plugins/vis_type_vislib/public/pie.ts
@@ -26,6 +26,7 @@ import { getPositions, Positions } from './utils/collections';
import { createVislibVisController } from './vis_controller';
import { CommonVislibParams } from './types';
import { VisTypeVislibDependencies } from './plugin';
+import { VIS_EVENT_TO_TRIGGER } from '../../../plugins/visualizations/public';
export interface PieVisParams extends CommonVislibParams {
type: 'pie';
@@ -47,6 +48,9 @@ export const createPieVisTypeDefinition = (deps: VisTypeVislibDependencies) => (
defaultMessage: 'Compare parts of a whole',
}),
visualization: createVislibVisController(deps),
+ getSupportedTriggers: () => {
+ return [VIS_EVENT_TO_TRIGGER.filter];
+ },
visConfig: {
defaults: {
type: 'pie',
diff --git a/src/plugins/vis_type_vislib/public/vislib/components/labels/flatten_series.js b/src/plugins/vis_type_vislib/public/vislib/components/labels/flatten_series.js
index 87477332f76e5b..4d4660371eaa49 100644
--- a/src/plugins/vis_type_vislib/public/vislib/components/labels/flatten_series.js
+++ b/src/plugins/vis_type_vislib/public/vislib/components/labels/flatten_series.js
@@ -30,5 +30,5 @@ export function flattenSeries(obj) {
obj = obj.rows ? obj.rows : obj.columns;
- return _.chain(obj).pluck('series').flattenDeep().value();
+ return _.chain(obj).map('series').flattenDeep().value();
}
diff --git a/src/plugins/vis_type_vislib/public/vislib/components/labels/labels.test.js b/src/plugins/vis_type_vislib/public/vislib/components/labels/labels.test.js
index 5e78637ef0c025..f04d9d17eeccb4 100644
--- a/src/plugins/vis_type_vislib/public/vislib/components/labels/labels.test.js
+++ b/src/plugins/vis_type_vislib/public/vislib/components/labels/labels.test.js
@@ -166,9 +166,9 @@ describe('Vislib Labels Module Test Suite', function () {
seriesArr = Array.isArray(seriesLabels);
rowsArr = Array.isArray(rowsLabels);
uniqSeriesLabels = _.chain(rowsData.rows)
- .pluck('series')
+ .map('series')
.flattenDeep()
- .pluck('label')
+ .map('label')
.uniq()
.value();
});
diff --git a/src/plugins/vis_type_vislib/public/vislib/components/labels/uniq_labels.js b/src/plugins/vis_type_vislib/public/vislib/components/labels/uniq_labels.js
index 489cb81306b3dc..cf98425c04ce78 100644
--- a/src/plugins/vis_type_vislib/public/vislib/components/labels/uniq_labels.js
+++ b/src/plugins/vis_type_vislib/public/vislib/components/labels/uniq_labels.js
@@ -28,5 +28,5 @@ export function uniqLabels(arr) {
throw new TypeError('UniqLabelUtil expects an array of objects');
}
- return _(arr).pluck('label').unique().value();
+ return _(arr).map('label').uniq().value();
}
diff --git a/src/plugins/vis_type_vislib/public/vislib/components/legend/legend.tsx b/src/plugins/vis_type_vislib/public/vislib/components/legend/legend.tsx
index a2fe4d9249bd06..f7e44ed2787872 100644
--- a/src/plugins/vis_type_vislib/public/vislib/components/legend/legend.tsx
+++ b/src/plugins/vis_type_vislib/public/vislib/components/legend/legend.tsx
@@ -18,7 +18,7 @@
*/
import React, { BaseSyntheticEvent, KeyboardEvent, PureComponent } from 'react';
import classNames from 'classnames';
-import { compact, uniq, map, every, isUndefined } from 'lodash';
+import { compact, uniqBy, map, every, isUndefined } from 'lodash';
import { i18n } from '@kbn/i18n';
import { EuiPopoverProps, EuiIcon, keyCodes, htmlIdGenerator } from '@elastic/eui';
@@ -119,7 +119,7 @@ export class VisLegend extends PureComponent {
getSeriesLabels = (data: any[]) => {
const values = data.map((chart) => chart.series).reduce((a, b) => a.concat(b), []);
- return compact(uniq(values, 'label')).map((label: any) => ({
+ return compact(uniqBy(values, 'label')).map((label: any) => ({
...label,
values: [label.values[0].seriesRaw],
}));
diff --git a/src/plugins/vis_type_vislib/public/vislib/components/legend/pie_utils.ts b/src/plugins/vis_type_vislib/public/vislib/components/legend/pie_utils.ts
index 6b507862fb841f..da046af83a495d 100644
--- a/src/plugins/vis_type_vislib/public/vislib/components/legend/pie_utils.ts
+++ b/src/plugins/vis_type_vislib/public/vislib/components/legend/pie_utils.ts
@@ -39,7 +39,7 @@ export function getPieNames(data: any[]): string[] {
});
});
- return _.uniq(names, 'label');
+ return _.uniqBy(names, 'label');
}
/**
@@ -61,7 +61,7 @@ function getNames(data: any, columns: any): string[] {
.sortBy(function (obj) {
return obj.index;
})
- .unique(function (d) {
+ .uniqBy(function (d) {
return d.label;
})
.value();
diff --git a/src/plugins/vis_type_vislib/public/vislib/components/tooltip/position_tooltip.js b/src/plugins/vis_type_vislib/public/vislib/components/tooltip/position_tooltip.js
index e22105d5a086ff..5324dc5318be57 100644
--- a/src/plugins/vis_type_vislib/public/vislib/components/tooltip/position_tooltip.js
+++ b/src/plugins/vis_type_vislib/public/vislib/components/tooltip/position_tooltip.js
@@ -110,7 +110,7 @@ function getOverflow(size, pos, containers) {
}
function mergeOverflows(dest, src) {
- _.merge(dest, src, function (a, b) {
+ _.mergeWith(dest, src, function (a, b) {
if (a == null || b == null) return a || b;
if (a < 0 && b < 0) return Math.min(a, b);
return Math.max(a, b);
@@ -131,7 +131,7 @@ function pickPlacement(prop, pos, overflow, prev, pref, fallback, placement) {
const stash = '_' + prop;
// list of directions in order of preference
- const dirs = _.unique([prev[stash], pref, fallback].filter(Boolean));
+ const dirs = _.uniq([prev[stash], pref, fallback].filter(Boolean));
let dir;
let value;
diff --git a/src/plugins/vis_type_vislib/public/vislib/components/tooltip/tooltip.js b/src/plugins/vis_type_vislib/public/vislib/components/tooltip/tooltip.js
index 0bfcedc5e60555..bafc3199de8964 100644
--- a/src/plugins/vis_type_vislib/public/vislib/components/tooltip/tooltip.js
+++ b/src/plugins/vis_type_vislib/public/vislib/components/tooltip/tooltip.js
@@ -218,7 +218,7 @@ Tooltip.prototype.render = function () {
if (html) allContents.push({ id, html, order });
- const allHtml = _(allContents).sortBy('order').pluck('html').compact().join('\n');
+ const allHtml = _(allContents).sortBy('order').map('html').compact().join('\n');
if (allHtml) {
$tooltip.html(allHtml);
diff --git a/src/plugins/vis_type_vislib/public/vislib/components/zero_injection/flatten_data.js b/src/plugins/vis_type_vislib/public/vislib/components/zero_injection/flatten_data.js
index 3269f54a621d08..8b7a44d95bb3b0 100644
--- a/src/plugins/vis_type_vislib/public/vislib/components/zero_injection/flatten_data.js
+++ b/src/plugins/vis_type_vislib/public/vislib/components/zero_injection/flatten_data.js
@@ -35,9 +35,9 @@ export function flattenData(obj) {
}
return _(charts ? charts : [obj])
- .pluck('series')
+ .map('series')
.flattenDeep()
- .pluck('values')
+ .map('values')
.flattenDeep()
.filter(Boolean)
.value();
diff --git a/src/plugins/vis_type_vislib/public/vislib/helpers/point_series/_get_series.ts b/src/plugins/vis_type_vislib/public/vislib/helpers/point_series/_get_series.ts
index c5fb4761eb9ee0..8a1f80df9f4db1 100644
--- a/src/plugins/vis_type_vislib/public/vislib/helpers/point_series/_get_series.ts
+++ b/src/plugins/vis_type_vislib/public/vislib/helpers/point_series/_get_series.ts
@@ -71,7 +71,7 @@ export function getSeries(table: Table, chart: Chart) {
seriesLabel = prefix + seriesLabel;
}
- point.seriesId = seriesId;
+ (point.seriesId as string | number) = seriesId;
addToSiri(
seriesMap,
point,
diff --git a/src/plugins/vis_type_vislib/public/vislib/lib/data.js b/src/plugins/vis_type_vislib/public/vislib/lib/data.js
index 98d384f95a8394..3633063966e17e 100644
--- a/src/plugins/vis_type_vislib/public/vislib/lib/data.js
+++ b/src/plugins/vis_type_vislib/public/vislib/lib/data.js
@@ -248,7 +248,7 @@ export class Data {
const visData = this.getVisData();
return _.reduce(
- _.pluck(visData, 'geoJson.properties'),
+ _.map(visData, 'geoJson.properties'),
function (minMax, props) {
return {
min: Math.min(props.min, minMax.min),
@@ -312,7 +312,7 @@ export class Data {
* @returns {Array} Value objects
*/
flatten() {
- return _(this.chartData()).pluck('series').flattenDeep().pluck('values').flattenDeep().value();
+ return _(this.chartData()).map('series').flattenDeep().map('values').flattenDeep().value();
}
/**
@@ -383,7 +383,7 @@ export class Data {
.sortBy(function (obj) {
return obj.index;
})
- .unique(function (d) {
+ .uniqBy(function (d) {
return d.label;
})
.value();
@@ -452,7 +452,7 @@ export class Data {
});
});
- return _.uniq(names, 'label');
+ return _.uniqBy(names, 'label');
}
/**
diff --git a/src/plugins/vis_type_vislib/public/vislib/lib/dispatch.js b/src/plugins/vis_type_vislib/public/vislib/lib/dispatch.js
index 37f395aab40114..4c50472b9d11a8 100644
--- a/src/plugins/vis_type_vislib/public/vislib/lib/dispatch.js
+++ b/src/plugins/vis_type_vislib/public/vislib/lib/dispatch.js
@@ -18,7 +18,7 @@
*/
import d3 from 'd3';
-import { get, pull, restParam, size, reduce } from 'lodash';
+import { get, pull, rest, size, reduce } from 'lodash';
import $ from 'jquery';
import { DIMMING_OPACITY_SETTING } from '../../../common';
@@ -97,7 +97,7 @@ export class Dispatch {
* @param {*} [arg...] - any number of arguments that will be applied to each handler
* @return {Dispatch} - this, for chaining
*/
- emit = restParam(function (name, args) {
+ emit = rest(function (name, args) {
if (!this._listeners[name]) {
return this;
}
diff --git a/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts b/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts
index 26fdd665192a62..2f9cda32fccdc9 100644
--- a/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts
+++ b/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts
@@ -377,29 +377,6 @@ export class VisualizeEmbeddable extends Embeddable 0) {
const lastKey = partialPath.splice(partialPath.length - 1, 1)[0];
const statePath = [...this._path, partialPath];
- const stateVal = statePath.length > 0 ? get(stateTree, statePath) : stateTree;
+ const stateVal = statePath.length > 0 ? get(stateTree, statePath as string[]) : stateTree;
// if stateVal isn't an object, do nothing
if (!isPlainObject(stateVal)) return;
@@ -240,7 +249,7 @@ export class PersistedState extends EventEmitter {
// If `mergeMethod` is provided it is invoked to produce the merged values of the
// destination and source properties.
// If `mergeMethod` returns `undefined` the default merging method is used
- this._mergedState = merge(targetObj, sourceObj, mergeMethod);
+ this._mergedState = mergeWith(targetObj, sourceObj, mergeMethod);
// sanity check; verify that there are actually changes
if (isEqual(this._mergedState, this._defaultState)) this._changedState = {};
diff --git a/src/plugins/visualizations/public/saved_visualizations/find_list_items.ts b/src/plugins/visualizations/public/saved_visualizations/find_list_items.ts
index 0c27c3a2c7782f..60945b912e1b3c 100644
--- a/src/plugins/visualizations/public/saved_visualizations/find_list_items.ts
+++ b/src/plugins/visualizations/public/saved_visualizations/find_list_items.ts
@@ -49,7 +49,7 @@ export async function findListItems({
}, acc);
}, {} as { [visType: string]: VisualizationsAppExtension });
const searchOption = (field: string, ...defaults: string[]) =>
- _(extensions).pluck(field).concat(defaults).compact().flatten().uniq().value() as string[];
+ _(extensions).map(field).concat(defaults).compact().flatten().uniq().value() as string[];
const searchOptions = {
type: searchOption('docTypes', 'visualization'),
searchFields: searchOption('searchFields', 'title^3', 'description'),
diff --git a/src/plugins/visualizations/public/vis_types/base_vis_type.ts b/src/plugins/visualizations/public/vis_types/base_vis_type.ts
index 44b76a52b34fef..12b9a49580162c 100644
--- a/src/plugins/visualizations/public/vis_types/base_vis_type.ts
+++ b/src/plugins/visualizations/public/vis_types/base_vis_type.ts
@@ -19,11 +19,13 @@
import _ from 'lodash';
import { VisualizationControllerConstructor } from '../types';
+import { TriggerContextMapping } from '../../../ui_actions/public';
export interface BaseVisTypeOptions {
name: string;
title: string;
description?: string;
+ getSupportedTriggers?: () => Array;
icon?: string;
image?: string;
stage?: 'experimental' | 'beta' | 'production';
@@ -44,6 +46,7 @@ export class BaseVisType {
name: string;
title: string;
description: string;
+ getSupportedTriggers?: () => Array;
icon?: string;
image?: string;
stage: 'experimental' | 'beta' | 'production';
@@ -77,6 +80,7 @@ export class BaseVisType {
this.name = opts.name;
this.description = opts.description || '';
+ this.getSupportedTriggers = opts.getSupportedTriggers;
this.title = opts.title;
this.icon = opts.icon;
this.image = opts.image;
diff --git a/src/plugins/visualizations/public/vis_types/types_service.ts b/src/plugins/visualizations/public/vis_types/types_service.ts
index 321f96180fd68e..14c2a9c50ab0eb 100644
--- a/src/plugins/visualizations/public/vis_types/types_service.ts
+++ b/src/plugins/visualizations/public/vis_types/types_service.ts
@@ -23,11 +23,13 @@ import { visTypeAliasRegistry, VisTypeAlias } from './vis_type_alias_registry';
import { BaseVisType } from './base_vis_type';
// @ts-ignore
import { ReactVisType } from './react_vis_type';
+import { TriggerContextMapping } from '../../../ui_actions/public';
export interface VisType {
name: string;
title: string;
description?: string;
+ getSupportedTriggers?: () => Array;
visualization: any;
isAccessible?: boolean;
requestHandler: string | unknown;
diff --git a/src/plugins/visualizations/public/vis_types/vis_type_alias_registry.ts b/src/plugins/visualizations/public/vis_types/vis_type_alias_registry.ts
index bc80d549c81e6f..f6d27b54c7c640 100644
--- a/src/plugins/visualizations/public/vis_types/vis_type_alias_registry.ts
+++ b/src/plugins/visualizations/public/vis_types/vis_type_alias_registry.ts
@@ -16,6 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
+import { TriggerContextMapping } from '../../../ui_actions/public';
export interface VisualizationListItem {
editUrl: string;
@@ -26,6 +27,7 @@ export interface VisualizationListItem {
savedObjectType: string;
title: string;
description?: string;
+ getSupportedTriggers?: () => Array;
typeTitle: string;
image?: string;
}
@@ -53,6 +55,7 @@ export interface VisTypeAlias {
icon: string;
promotion?: VisTypeAliasPromotion;
description: string;
+ getSupportedTriggers?: () => Array;
stage: 'experimental' | 'beta' | 'production';
appExtensions?: {
diff --git a/src/plugins/visualizations/public/wizard/type_selection/type_selection.tsx b/src/plugins/visualizations/public/wizard/type_selection/type_selection.tsx
index 47757593958d6f..dc6ac4919a4c42 100644
--- a/src/plugins/visualizations/public/wizard/type_selection/type_selection.tsx
+++ b/src/plugins/visualizations/public/wizard/type_selection/type_selection.tsx
@@ -19,7 +19,7 @@
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
-import { sortByOrder } from 'lodash';
+import { orderBy } from 'lodash';
import React, { ChangeEvent } from 'react';
import {
@@ -201,7 +201,7 @@ class TypeSelection extends React.Component {
diff --git a/src/plugins/visualizations/server/saved_objects/visualization_migrations.ts b/src/plugins/visualizations/server/saved_objects/visualization_migrations.ts
index 27fe722019a27f..74881b9d99ae8c 100644
--- a/src/plugins/visualizations/server/saved_objects/visualization_migrations.ts
+++ b/src/plugins/visualizations/server/saved_objects/visualization_migrations.ts
@@ -65,7 +65,7 @@ const migrateIndexPattern: SavedObjectMigrationFn = (doc) => {
// [TSVB] Migrate percentile-rank aggregation (value -> values)
const migratePercentileRankAggregation: SavedObjectMigrationFn = (doc) => {
- const visStateJSON = get(doc, 'attributes.visState');
+ const visStateJSON = get(doc, 'attributes.visState');
let visState;
if (visStateJSON) {
@@ -101,7 +101,7 @@ const migratePercentileRankAggregation: SavedObjectMigrationFn = (doc)
// [TSVB] Remove stale opperator key
const migrateOperatorKeyTypo: SavedObjectMigrationFn = (doc) => {
- const visStateJSON = get(doc, 'attributes.visState');
+ const visStateJSON = get(doc, 'attributes.visState');
let visState;
if (visStateJSON) {
@@ -137,7 +137,7 @@ const migrateOperatorKeyTypo: SavedObjectMigrationFn = (doc) => {
* @see https://github.com/elastic/kibana/pull/58462/files#diff-ae69fe15b20a5099d038e9bbe2ed3849
**/
const migrateSplitByChartRow: SavedObjectMigrationFn = (doc) => {
- const visStateJSON = get(doc, 'attributes.visState');
+ const visStateJSON = get(doc, 'attributes.visState');
let visState: any;
if (visStateJSON) {
@@ -177,7 +177,7 @@ const migrateSplitByChartRow: SavedObjectMigrationFn = (doc) => {
// Migrate date histogram aggregation (remove customInterval)
const migrateDateHistogramAggregation: SavedObjectMigrationFn = (doc) => {
- const visStateJSON = get(doc, 'attributes.visState');
+ const visStateJSON = get(doc, 'attributes.visState');
let visState;
if (visStateJSON) {
@@ -219,7 +219,7 @@ const migrateDateHistogramAggregation: SavedObjectMigrationFn = (doc)
};
const removeDateHistogramTimeZones: SavedObjectMigrationFn = (doc) => {
- const visStateJSON = get(doc, 'attributes.visState');
+ const visStateJSON = get(doc, 'attributes.visState');
if (visStateJSON) {
let visState;
try {
@@ -251,7 +251,7 @@ const removeDateHistogramTimeZones: SavedObjectMigrationFn = (doc) =>
// migrate gauge verticalSplit to alignment
// https://github.com/elastic/kibana/issues/34636
const migrateGaugeVerticalSplitToAlignment: SavedObjectMigrationFn = (doc, logger) => {
- const visStateJSON = get(doc, 'attributes.visState');
+ const visStateJSON = get(doc, 'attributes.visState');
if (visStateJSON) {
try {
@@ -289,7 +289,7 @@ const transformFilterStringToQueryObject: SavedObjectMigrationFn = (do
// Migrate filters
// If any filters exist and they are a string, we assume it to be lucene and transform the filter into an object accordingly
const newDoc = cloneDeep(doc);
- const visStateJSON = get(doc, 'attributes.visState');
+ const visStateJSON = get(doc, 'attributes.visState');
if (visStateJSON) {
let visState;
try {
@@ -298,7 +298,7 @@ const transformFilterStringToQueryObject: SavedObjectMigrationFn = (do
// let it go, the data is invalid and we'll leave it as is
}
if (visState) {
- const visType = get(visState, 'params.type');
+ const visType = get(visState, 'params.type');
const tsvbTypes = ['metric', 'markdown', 'top_n', 'gauge', 'table', 'timeseries'];
if (tsvbTypes.indexOf(visType) === -1) {
// skip
@@ -373,7 +373,7 @@ const transformSplitFiltersStringToQueryObject: SavedObjectMigrationFn
// Migrate split_filters in TSVB objects that weren't migrated in 7.3
// If any filters exist and they are a string, we assume them to be lucene syntax and transform the filter into an object accordingly
const newDoc = cloneDeep(doc);
- const visStateJSON = get(doc, 'attributes.visState');
+ const visStateJSON = get(doc, 'attributes.visState');
if (visStateJSON) {
let visState;
try {
@@ -382,7 +382,7 @@ const transformSplitFiltersStringToQueryObject: SavedObjectMigrationFn
// let it go, the data is invalid and we'll leave it as is
}
if (visState) {
- const visType = get(visState, 'params.type');
+ const visType = get(visState, 'params.type');
const tsvbTypes = ['metric', 'markdown', 'top_n', 'gauge', 'table', 'timeseries'];
if (tsvbTypes.indexOf(visType) === -1) {
// skip
@@ -415,7 +415,7 @@ const transformSplitFiltersStringToQueryObject: SavedObjectMigrationFn
};
const migrateFiltersAggQuery: SavedObjectMigrationFn = (doc) => {
- const visStateJSON = get(doc, 'attributes.visState');
+ const visStateJSON = get(doc, 'attributes.visState');
if (visStateJSON) {
try {
@@ -447,7 +447,7 @@ const migrateFiltersAggQuery: SavedObjectMigrationFn = (doc) => {
};
const replaceMovAvgToMovFn: SavedObjectMigrationFn = (doc, logger) => {
- const visStateJSON = get(doc, 'attributes.visState');
+ const visStateJSON = get(doc, 'attributes.visState');
let visState;
if (visStateJSON) {
@@ -495,7 +495,7 @@ const replaceMovAvgToMovFn: SavedObjectMigrationFn = (doc, logger) =>
};
const migrateFiltersAggQueryStringQueries: SavedObjectMigrationFn = (doc, logger) => {
- const visStateJSON = get(doc, 'attributes.visState');
+ const visStateJSON = get(doc, 'attributes.visState');
if (visStateJSON) {
try {
@@ -533,7 +533,7 @@ const addDocReferences: SavedObjectMigrationFn = (doc) => ({
});
const migrateSavedSearch: SavedObjectMigrationFn = (doc) => {
- const savedSearchId = get(doc, 'attributes.savedSearchId');
+ const savedSearchId = get(doc, 'attributes.savedSearchId');
if (savedSearchId && doc.references) {
doc.references.push({
@@ -550,7 +550,7 @@ const migrateSavedSearch: SavedObjectMigrationFn = (doc) => {
};
const migrateControls: SavedObjectMigrationFn = (doc) => {
- const visStateJSON = get(doc, 'attributes.visState');
+ const visStateJSON = get(doc, 'attributes.visState');
if (visStateJSON) {
let visState;
@@ -617,7 +617,7 @@ const migrateTableSplits: SavedObjectMigrationFn = (doc) => {
};
const migrateMatchAllQuery: SavedObjectMigrationFn = (doc) => {
- const searchSourceJSON = get(doc, 'attributes.kibanaSavedObjectMeta.searchSourceJSON');
+ const searchSourceJSON = get(doc, 'attributes.kibanaSavedObjectMeta.searchSourceJSON');
if (searchSourceJSON) {
let searchSource: any;
@@ -651,7 +651,7 @@ const migrateMatchAllQuery: SavedObjectMigrationFn = (doc) => {
// [TSVB] Default color palette is changing, keep the default for older viz
const migrateTsvbDefaultColorPalettes: SavedObjectMigrationFn = (doc) => {
- const visStateJSON = get(doc, 'attributes.visState');
+ const visStateJSON = get(doc, 'attributes.visState');
let visState;
if (visStateJSON) {
@@ -693,30 +693,24 @@ export const visualizationSavedObjectTypeMigrations = {
* in that version. So we apply this twice, once with 6.7.2 and once with 7.0.1 while the backport to 6.7
* only contained the 6.7.2 migration and not the 7.0.1 migration.
*/
- '6.7.2': flow>(
- migrateMatchAllQuery,
- removeDateHistogramTimeZones
- ),
- '7.0.0': flow>(
+ '6.7.2': flow(migrateMatchAllQuery, removeDateHistogramTimeZones),
+ '7.0.0': flow(
addDocReferences,
migrateIndexPattern,
migrateSavedSearch,
migrateControls,
migrateTableSplits
),
- '7.0.1': flow>(removeDateHistogramTimeZones),
- '7.2.0': flow>(
- migratePercentileRankAggregation,
- migrateDateHistogramAggregation
- ),
- '7.3.0': flow>(
+ '7.0.1': flow(removeDateHistogramTimeZones),
+ '7.2.0': flow(migratePercentileRankAggregation, migrateDateHistogramAggregation),
+ '7.3.0': flow(
migrateGaugeVerticalSplitToAlignment,
transformFilterStringToQueryObject,
migrateFiltersAggQuery,
replaceMovAvgToMovFn
),
- '7.3.1': flow>(migrateFiltersAggQueryStringQueries),
- '7.4.2': flow>(transformSplitFiltersStringToQueryObject),
- '7.7.0': flow>(migrateOperatorKeyTypo, migrateSplitByChartRow),
- '7.8.0': flow>(migrateTsvbDefaultColorPalettes),
+ '7.3.1': flow(migrateFiltersAggQueryStringQueries),
+ '7.4.2': flow(transformSplitFiltersStringToQueryObject),
+ '7.7.0': flow(migrateOperatorKeyTypo, migrateSplitByChartRow),
+ '7.8.0': flow(migrateTsvbDefaultColorPalettes),
};
diff --git a/src/plugins/visualize/public/application/utils/create_visualize_app_state.ts b/src/plugins/visualize/public/application/utils/create_visualize_app_state.ts
index 1e05c48ba7daf1..52b7e3ede298b6 100644
--- a/src/plugins/visualize/public/application/utils/create_visualize_app_state.ts
+++ b/src/plugins/visualize/public/application/utils/create_visualize_app_state.ts
@@ -17,7 +17,7 @@
* under the License.
*/
-import { isFunction, omit, union } from 'lodash';
+import { isFunction, omitBy, union } from 'lodash';
import { migrateAppState } from './migrate_app_state';
import {
@@ -35,9 +35,9 @@ interface Arguments {
}
function toObject(state: PureVisState): PureVisState {
- return omit(state, (value, key: string) => {
+ return omitBy(state, (value, key: string) => {
return key.charAt(0) === '$' || key.charAt(0) === '_' || isFunction(value);
- });
+ }) as PureVisState;
}
export function createVisualizeAppState({ stateDefaults, kbnUrlStateStorage }: Arguments) {
diff --git a/src/plugins/visualize/public/application/utils/migrate_app_state.ts b/src/plugins/visualize/public/application/utils/migrate_app_state.ts
index f5f1a1785bbdf2..94eba5a6d7ce24 100644
--- a/src/plugins/visualize/public/application/utils/migrate_app_state.ts
+++ b/src/plugins/visualize/public/application/utils/migrate_app_state.ts
@@ -36,7 +36,7 @@ export function migrateAppState(appState: VisualizeAppState) {
return appState;
}
- const visAggs: any = get(appState, 'vis.aggs');
+ const visAggs: any = get(appState, 'vis.aggs');
if (visAggs) {
let splitCount = 0;
diff --git a/src/setup_node_env/patches/child_process.js b/src/setup_node_env/harden/child_process.js
similarity index 97%
rename from src/setup_node_env/patches/child_process.js
rename to src/setup_node_env/harden/child_process.js
index fb857b2092ee01..6b1ba779605c00 100644
--- a/src/setup_node_env/patches/child_process.js
+++ b/src/setup_node_env/harden/child_process.js
@@ -16,12 +16,13 @@
* specific language governing permissions and limitations
* under the License.
*/
+var hook = require('require-in-the-middle');
// Ensure, when spawning a new child process, that the `options` and the
// `options.env` object passed to the child process function doesn't inherit
// from `Object.prototype`. This protects against similar RCE vulnerabilities
// as described in CVE-2019-7609
-module.exports = function (cp) {
+hook(['child_process'], function (cp) {
// The `exec` function is currently just a wrapper around `execFile`. So for
// now there's no need to patch it. If this changes in the future, our tests
// will fail and we can uncomment the line below.
@@ -36,7 +37,7 @@ module.exports = function (cp) {
cp.spawnSync = new Proxy(cp.spawnSync, { apply: patchOptions(true) });
return cp;
-};
+});
function patchOptions(hasArgs) {
return function apply(target, thisArg, args) {
diff --git a/typings/lodash.topath/index.d.ts b/src/setup_node_env/harden/index.js
similarity index 87%
rename from typings/lodash.topath/index.d.ts
rename to src/setup_node_env/harden/index.js
index 3630a13f72c284..25cb3bcd78ffb6 100644
--- a/typings/lodash.topath/index.d.ts
+++ b/src/setup_node_env/harden/index.js
@@ -17,7 +17,5 @@
* under the License.
*/
-declare module 'lodash/internal/toPath' {
- function toPath(value: string | string[]): string[];
- export = toPath;
-}
+require('./child_process');
+require('./lodash_template');
diff --git a/src/setup_node_env/harden/lodash_template.js b/src/setup_node_env/harden/lodash_template.js
new file mode 100644
index 00000000000000..2add624f9f326e
--- /dev/null
+++ b/src/setup_node_env/harden/lodash_template.js
@@ -0,0 +1,68 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+var hook = require('require-in-the-middle');
+var isIterateeCall = require('lodash/_isIterateeCall');
+
+hook(['lodash'], function (lodash) {
+ lodash.template = createProxy(lodash.template);
+ return lodash;
+});
+
+hook(['lodash/template'], function (template) {
+ return createProxy(template);
+});
+
+hook(['lodash/fp'], function (fp) {
+ fp.template = createFpProxy(fp.template);
+ return fp;
+});
+
+hook(['lodash/fp/template'], function (template) {
+ return createFpProxy(template);
+});
+
+function createProxy(template) {
+ return new Proxy(template, {
+ apply: function (target, thisArg, args) {
+ if (args.length === 1 || isIterateeCall(args)) {
+ return target.apply(thisArg, [args[0], { sourceURL: '' }]);
+ }
+
+ var options = Object.assign({}, args[1]);
+ options.sourceURL = (options.sourceURL + '').replace(/\s/g, ' ');
+ var newArgs = args.slice(0); // copy
+ newArgs.splice(1, 1, options); // replace options in the copy
+ return target.apply(thisArg, newArgs);
+ },
+ });
+}
+
+function createFpProxy(template) {
+ // we have to do the require here, so that we get the patched version
+ var _ = require('lodash');
+ return new Proxy(template, {
+ apply: function (target, thisArg, args) {
+ // per https://github.com/lodash/lodash/wiki/FP-Guide
+ // > Iteratee arguments are capped to avoid gotchas with variadic iteratees.
+ // this means that we can't specify the options in the second argument to fp.template because it's ignored.
+ // Instead, we're going to use the non-FP _.template with only the first argument which has already been patched
+ return _.template(args[0]);
+ },
+ });
+}
diff --git a/src/test_utils/get_url.js b/src/test_utils/get_url.js
index fbe16e798fff98..182cb8e6e118db 100644
--- a/src/test_utils/get_url.js
+++ b/src/test_utils/get_url.js
@@ -44,7 +44,7 @@ export default function getUrl(config, app) {
}
getUrl.noAuth = function getUrlNoAuth(config, app) {
- config = _.pick(config, function (val, param) {
+ config = _.pickBy(config, function (val, param) {
return param !== 'auth';
});
return getUrl(config, app);
diff --git a/src/test_utils/kbn_server.ts b/src/test_utils/kbn_server.ts
index 12f7eb5a0a0431..6a20261421b5b8 100644
--- a/src/test_utils/kbn_server.ts
+++ b/src/test_utils/kbn_server.ts
@@ -217,7 +217,7 @@ export function createTestServers({
if (!adjustTimeout) {
throw new Error('adjustTimeout is required in order to avoid flaky tests');
}
- const license = get<'oss' | 'basic' | 'gold' | 'trial'>(settings, 'es.license', 'oss');
+ const license = get(settings, 'es.license', 'oss');
const usersToBeAdded = get(settings, 'users', []);
if (usersToBeAdded.length > 0) {
if (license !== 'trial') {
diff --git a/test/api_integration/apis/saved_objects/migrations.js b/test/api_integration/apis/saved_objects/migrations.js
index d0ff4cc06c57ec..9ea3cf087be909 100644
--- a/test/api_integration/apis/saved_objects/migrations.js
+++ b/test/api_integration/apis/saved_objects/migrations.js
@@ -293,7 +293,7 @@ export default ({ getService }) => {
// It only created the original and the dest
assert.deepEqual(
- _.pluck(
+ _.map(
await callCluster('cat.indices', { index: '.migration-c*', format: 'json' }),
'index'
).sort(),
diff --git a/test/api_integration/apis/telemetry/telemetry_local.js b/test/api_integration/apis/telemetry/telemetry_local.js
index e74cd180185ab3..88e6b3a29052e2 100644
--- a/test/api_integration/apis/telemetry/telemetry_local.js
+++ b/test/api_integration/apis/telemetry/telemetry_local.js
@@ -37,8 +37,17 @@ function flatKeys(source) {
export default function ({ getService }) {
const supertest = getService('supertest');
+ const es = getService('es');
describe('/api/telemetry/v2/clusters/_stats', () => {
+ before('create some telemetry-data tracked indices', async () => {
+ return es.indices.create({ index: 'filebeat-telemetry_tests_logs' });
+ });
+
+ after('cleanup telemetry-data tracked indices', () => {
+ return es.indices.delete({ index: 'filebeat-telemetry_tests_logs' });
+ });
+
it('should pull local stats and validate data types', async () => {
const timeRange = {
min: '2018-07-23T22:07:00Z',
@@ -71,6 +80,17 @@ export default function ({ getService }) {
expect(stats.stack_stats.kibana.plugins.csp.strict).to.be(true);
expect(stats.stack_stats.kibana.plugins.csp.warnLegacyBrowsers).to.be(true);
expect(stats.stack_stats.kibana.plugins.csp.rulesChangedFromDefault).to.be(false);
+
+ // Testing stack_stats.data
+ expect(stats.stack_stats.data).to.be.an('object');
+ expect(stats.stack_stats.data).to.be.an('array');
+ expect(stats.stack_stats.data[0]).to.be.an('object');
+ expect(stats.stack_stats.data[0].pattern_name).to.be('filebeat');
+ expect(stats.stack_stats.data[0].shipper).to.be('filebeat');
+ expect(stats.stack_stats.data[0].index_count).to.be(1);
+ expect(stats.stack_stats.data[0].doc_count).to.be(0);
+ expect(stats.stack_stats.data[0].ecs_index_count).to.be(0);
+ expect(stats.stack_stats.data[0].size_in_bytes).to.be.greaterThan(0);
});
it('should pull local stats and validate fields', async () => {
diff --git a/test/functional/apps/management/_import_objects.js b/test/functional/apps/management/_import_objects.js
index c69111be6972bb..03db3a2b108f2b 100644
--- a/test/functional/apps/management/_import_objects.js
+++ b/test/functional/apps/management/_import_objects.js
@@ -19,7 +19,7 @@
import expect from '@kbn/expect';
import path from 'path';
-import { indexBy } from 'lodash';
+import { keyBy } from 'lodash';
export default function ({ getService, getPageObjects }) {
const kibanaServer = getService('kibanaServer');
@@ -50,12 +50,12 @@ export default function ({ getService, getPageObjects }) {
await PageObjects.savedObjects.clickImportDone();
// get all the elements in the table, and index them by the 'title' visible text field
- const elements = indexBy(await PageObjects.savedObjects.getElementsInTable(), 'title');
+ const elements = keyBy(await PageObjects.savedObjects.getElementsInTable(), 'title');
log.debug("check that 'Log Agents' is in table as a visualization");
expect(elements['Log Agents'].objectType).to.eql('visualization');
await elements['logstash-*'].relationshipsElement.click();
- const flyout = indexBy(await PageObjects.savedObjects.getRelationshipFlyout(), 'title');
+ const flyout = keyBy(await PageObjects.savedObjects.getRelationshipFlyout(), 'title');
log.debug(
"check that 'Shared-Item Visualization AreaChart' shows 'logstash-*' as it's Parent"
);
@@ -150,7 +150,7 @@ export default function ({ getService, getPageObjects }) {
});
it('should not import saved objects linked to saved searches when saved search index pattern does not exist', async function () {
- const elements = indexBy(await PageObjects.savedObjects.getElementsInTable(), 'title');
+ const elements = keyBy(await PageObjects.savedObjects.getElementsInTable(), 'title');
await elements['logstash-*'].checkbox.click();
await PageObjects.savedObjects.clickDelete();
@@ -182,7 +182,7 @@ export default function ({ getService, getPageObjects }) {
it('should import saved objects with index patterns when index patterns does not exists', async () => {
// First, we need to delete the index pattern
- const elements = indexBy(await PageObjects.savedObjects.getElementsInTable(), 'title');
+ const elements = keyBy(await PageObjects.savedObjects.getElementsInTable(), 'title');
await elements['logstash-*'].checkbox.click();
await PageObjects.savedObjects.clickDelete();
@@ -321,7 +321,7 @@ export default function ({ getService, getPageObjects }) {
await PageObjects.savedObjects.clickImportDone();
// Second, we need to delete the index pattern
- const elements = indexBy(await PageObjects.savedObjects.getElementsInTable(), 'title');
+ const elements = keyBy(await PageObjects.savedObjects.getElementsInTable(), 'title');
await elements['logstash-*'].checkbox.click();
await PageObjects.savedObjects.clickDelete();
@@ -353,7 +353,7 @@ export default function ({ getService, getPageObjects }) {
it('should import saved objects with index patterns when index patterns does not exists', async () => {
// First, we need to delete the index pattern
- const elements = indexBy(await PageObjects.savedObjects.getElementsInTable(), 'title');
+ const elements = keyBy(await PageObjects.savedObjects.getElementsInTable(), 'title');
await elements['logstash-*'].checkbox.click();
await PageObjects.savedObjects.clickDelete();
diff --git a/test/functional/apps/visualize/_line_chart.js b/test/functional/apps/visualize/_line_chart.js
index 5c510617fbb017..a492f3858b524f 100644
--- a/test/functional/apps/visualize/_line_chart.js
+++ b/test/functional/apps/visualize/_line_chart.js
@@ -279,5 +279,79 @@ export default function ({ getService, getPageObjects }) {
expect(labels).to.eql(expectedLabels);
});
});
+
+ describe('pipeline aggregations', () => {
+ before(async () => {
+ log.debug('navigateToApp visualize');
+ await PageObjects.visualize.navigateToNewVisualization();
+ log.debug('clickLineChart');
+ await PageObjects.visualize.clickLineChart();
+ await PageObjects.visualize.clickNewSearch();
+ await PageObjects.timePicker.setDefaultAbsoluteRange();
+ });
+
+ describe('parent pipeline', () => {
+ it('should have an error if bucket is not selected', async () => {
+ await PageObjects.visEditor.clickMetricEditor();
+ log.debug('Metrics agg = Serial diff');
+ await PageObjects.visEditor.selectAggregation('Serial diff', 'metrics');
+ await testSubjects.existOrFail('bucketsError');
+ });
+
+ it('should apply with selected bucket', async () => {
+ log.debug('Bucket = X-axis');
+ await PageObjects.visEditor.clickBucket('X-axis');
+ log.debug('Aggregation = Date Histogram');
+ await PageObjects.visEditor.selectAggregation('Date Histogram');
+ await PageObjects.visEditor.clickGo();
+ const title = await PageObjects.visChart.getYAxisTitle();
+ expect(title).to.be('Serial Diff of Count');
+ });
+
+ it('should change y-axis label to custom', async () => {
+ log.debug('set custom label of y-axis to "Custom"');
+ await PageObjects.visEditor.setCustomLabel('Custom', 1);
+ await PageObjects.visEditor.clickGo();
+ const title = await PageObjects.visChart.getYAxisTitle();
+ expect(title).to.be('Custom');
+ });
+
+ it('should have advanced accordion and json input', async () => {
+ await testSubjects.click('advancedParams-1');
+ await testSubjects.existOrFail('advancedParams-1 > codeEditorContainer');
+ });
+ });
+
+ describe('sibling pipeline', () => {
+ it('should apply with selected bucket', async () => {
+ log.debug('Metrics agg = Average Bucket');
+ await PageObjects.visEditor.selectAggregation('Average Bucket', 'metrics');
+ await PageObjects.visEditor.clickGo();
+ const title = await PageObjects.visChart.getYAxisTitle();
+ expect(title).to.be('Overall Average of Count');
+ });
+
+ it('should change sub metric custom label and calculate y-axis title', async () => {
+ log.debug('set custom label of sub metric to "Cats"');
+ await PageObjects.visEditor.setCustomLabel('Cats', '1-metric');
+ await PageObjects.visEditor.clickGo();
+ const title = await PageObjects.visChart.getYAxisTitle();
+ expect(title).to.be('Overall Average of Cats');
+ });
+
+ it('should outer custom label', async () => {
+ log.debug('set custom label to "Custom"');
+ await PageObjects.visEditor.setCustomLabel('Custom', 1);
+ await PageObjects.visEditor.clickGo();
+ const title = await PageObjects.visChart.getYAxisTitle();
+ expect(title).to.be('Custom');
+ });
+
+ it('should have advanced accordion and json input', async () => {
+ await testSubjects.click('advancedParams-1');
+ await testSubjects.existOrFail('advancedParams-1 > codeEditorContainer');
+ });
+ });
+ });
});
}
diff --git a/test/functional/services/common/browser.ts b/test/functional/services/common/browser.ts
index d6a4fc91481de2..2d35551b04808f 100644
--- a/test/functional/services/common/browser.ts
+++ b/test/functional/services/common/browser.ts
@@ -17,7 +17,7 @@
* under the License.
*/
-import { cloneDeep } from 'lodash';
+import { cloneDeepWith } from 'lodash';
import { Key, Origin } from 'selenium-webdriver';
// @ts-ignore internal modules are not typed
import { LegacyActionSequence } from 'selenium-webdriver/lib/actions';
@@ -471,7 +471,7 @@ export async function BrowserProvider({ getService }: FtrProviderContext) {
): Promise {
return await driver.executeScript(
fn,
- ...cloneDeep(args, (arg) => {
+ ...cloneDeepWith(args, (arg) => {
if (arg instanceof WebElementWrapper) {
return arg._webElement;
}
@@ -501,7 +501,7 @@ export async function BrowserProvider({ getService }: FtrProviderContext) {
): Promise {
return await driver.executeAsyncScript(
fn,
- ...cloneDeep(args, (arg) => {
+ ...cloneDeepWith(args, (arg) => {
if (arg instanceof WebElementWrapper) {
return arg._webElement;
}
diff --git a/test/harden/lodash_template.js b/test/harden/lodash_template.js
new file mode 100644
index 00000000000000..170e3a8fba43e5
--- /dev/null
+++ b/test/harden/lodash_template.js
@@ -0,0 +1,181 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+require('../../src/setup_node_env');
+const _ = require('lodash');
+const template = require('lodash/template');
+const fp = require('lodash/fp');
+const fpTemplate = require('lodash/fp/template');
+const test = require('tape');
+
+Object.prototype.sourceURL = '\u2028\u2029\n;global.whoops=true'; // eslint-disable-line no-extend-native
+
+test.onFinish(() => {
+ delete Object.prototype.sourceURL;
+});
+
+test('test setup ok', (t) => {
+ t.equal({}.sourceURL, '\u2028\u2029\n;global.whoops=true');
+ t.end();
+});
+
+[_.template, template].forEach((fn) => {
+ test(`_.template('<%= foo %>')`, (t) => {
+ const output = fn('<%= foo %>')({ foo: 'bar' });
+ t.equal(output, 'bar');
+ t.equal(global.whoops, undefined);
+ t.end();
+ });
+
+ test(`_.template('<%= foo %>', {})`, (t) => {
+ const output = fn('<%= foo %>', Object.freeze({}))({ foo: 'bar' });
+ t.equal(output, 'bar');
+ t.equal(global.whoops, undefined);
+ t.end();
+ });
+
+ test(`_.template('<%= data.foo %>', { variable: 'data' })`, (t) => {
+ const output = fn('<%= data.foo %>', Object.freeze({ variable: 'data' }))({ foo: 'bar' });
+ t.equal(output, 'bar');
+ t.equal(global.whoops, undefined);
+ t.end();
+ });
+
+ test(`_.template('<%= foo %>', { sourceURL: '/foo/bar' })`, (t) => {
+ // throwing errors in the template and parsing the stack, which is a string, is super ugly, but all I know to do
+ const template = fn('<% throw new Error() %>', Object.freeze({ sourceURL: '/foo/bar' }));
+ t.plan(2);
+ try {
+ template();
+ } catch (err) {
+ const path = parsePathFromStack(err.stack);
+ t.equal(path, '/foo/bar');
+ t.equal(global.whoops, undefined);
+ }
+ });
+
+ test(`_.template('<%= foo %>', { sourceURL: '\\u2028\\u2029\\nglobal.whoops=true' })`, (t) => {
+ // throwing errors in the template and parsing the stack, which is a string, is super ugly, but all I know to do
+ const template = fn(
+ '<% throw new Error() %>',
+ Object.freeze({ sourceURL: '\u2028\u2029\nglobal.whoops=true' })
+ );
+ t.plan(2);
+ try {
+ template();
+ } catch (err) {
+ const path = parsePathFromStack(err.stack);
+ t.equal(path, 'global.whoops=true');
+ t.equal(global.whoops, undefined);
+ }
+ });
+
+ test(`_.template used as an iteratee call(`, (t) => {
+ const templateStrArr = ['<%= data.foo %>', 'example <%= data.foo %>'];
+ const output = _.map(templateStrArr, fn);
+
+ t.equal(output[0]({ data: { foo: 'bar' } }), 'bar');
+ t.equal(output[1]({ data: { foo: 'bar' } }), 'example bar');
+ t.equal(global.whoops, undefined);
+ t.end();
+ });
+});
+
+[fp.template, fpTemplate].forEach((fn) => {
+ test(`fp.template('<%= foo %>')`, (t) => {
+ const output = fn('<%= foo %>')({ foo: 'bar' });
+ t.equal(output, 'bar');
+ t.equal(global.whoops, undefined);
+ t.end();
+ });
+
+ test(`fp.template('<%= foo %>', {})`, (t) => {
+ // fp.template ignores the second argument, this is negligible in this situation since options is an empty object
+ const output = fn('<%= foo %>', Object.freeze({}))({ foo: 'bar' });
+ t.equal(output, 'bar');
+ t.equal(global.whoops, undefined);
+ t.end();
+ });
+
+ test(`fp.template('<%= data.foo %>', { variable: 'data' })`, (t) => {
+ // fp.template ignores the second argument, this causes an error to be thrown
+ t.plan(2);
+ try {
+ fn('<%= data.foo %>', Object.freeze({ variable: 'data' }))({ foo: 'bar' });
+ } catch (err) {
+ t.equal(err.message, 'data is not defined');
+ t.equal(global.whoops, undefined);
+ }
+ });
+
+ test(`fp.template('<%= foo %>', { sourceURL: '/foo/bar' })`, (t) => {
+ // fp.template ignores the second argument, the sourceURL is ignored
+ // throwing errors in the template and parsing the stack, which is a string, is super ugly, but all I know to do
+ // our patching to hard-code the sourceURL and use non-FP _.template does slightly alter the stack-traces but it's negligible
+ const template = fn('<% throw new Error() %>', Object.freeze({ sourceURL: '/foo/bar' }));
+ t.plan(3);
+ try {
+ template();
+ } catch (err) {
+ const path = parsePathFromStack(err.stack);
+ t.match(path, /^eval at /);
+ t.doesNotMatch(path, /\/foo\/bar/);
+ t.equal(global.whoops, undefined);
+ }
+ });
+
+ test(`fp.template('<%= foo %>', { sourceURL: '\\u2028\\u2029\\nglobal.whoops=true' })`, (t) => {
+ // fp.template ignores the second argument, the sourceURL is ignored
+ // throwing errors in the template and parsing the stack, which is a string, is super ugly, but all I know to do
+ // our patching to hard-code the sourceURL and use non-FP _.template does slightly alter the stack-traces but it's negligible
+ const template = fn(
+ '<% throw new Error() %>',
+ Object.freeze({ sourceURL: '\u2028\u2029\nglobal.whoops=true' })
+ );
+ t.plan(3);
+ try {
+ template();
+ } catch (err) {
+ const path = parsePathFromStack(err.stack);
+ t.match(path, /^eval at /);
+ t.doesNotMatch(path, /\/foo\/bar/);
+ t.equal(global.whoops, undefined);
+ }
+ });
+
+ test(`fp.template used as an iteratee call(`, (t) => {
+ const templateStrArr = ['<%= data.foo %>', 'example <%= data.foo %>'];
+ const output = fp.map(fn)(templateStrArr);
+
+ t.equal(output[0]({ data: { foo: 'bar' } }), 'bar');
+ t.equal(output[1]({ data: { foo: 'bar' } }), 'example bar');
+ t.equal(global.whoops, undefined);
+ t.end();
+ });
+});
+
+function parsePathFromStack(stack) {
+ const lines = stack.split('\n');
+ // the frame starts at the second line
+ const frame = lines[1];
+
+ // the path is in parathensis, and ends with a colon before the line/column numbers
+ const [, path] = /\(([^:]+)/.exec(frame);
+ return path;
+}
diff --git a/test/scripts/jenkins_visual_regression.sh b/test/scripts/jenkins_visual_regression.sh
index a32782deec65b9..17345d4301882b 100755
--- a/test/scripts/jenkins_visual_regression.sh
+++ b/test/scripts/jenkins_visual_regression.sh
@@ -11,7 +11,7 @@ mkdir -p "$installDir"
tar -xzf "$linuxBuild" -C "$installDir" --strip=1
echo " -> running visual regression tests from kibana directory"
-yarn percy exec -t 500 -- -- \
+yarn percy exec -t 10000 -- -- \
node scripts/functional_tests \
--debug --bail \
--kibana-install-dir "$installDir" \
diff --git a/test/scripts/jenkins_xpack_visual_regression.sh b/test/scripts/jenkins_xpack_visual_regression.sh
index b67c1c9060a6e2..36bf3409a5421e 100755
--- a/test/scripts/jenkins_xpack_visual_regression.sh
+++ b/test/scripts/jenkins_xpack_visual_regression.sh
@@ -13,7 +13,7 @@ tar -xzf "$linuxBuild" -C "$installDir" --strip=1
echo " -> running visual regression tests from x-pack directory"
cd "$XPACK_DIR"
-yarn percy exec -t 500 -- -- \
+yarn percy exec -t 10000 -- -- \
node scripts/functional_tests \
--debug --bail \
--kibana-install-dir "$installDir" \
diff --git a/test/tsconfig.json b/test/tsconfig.json
index a270144bd49fea..87e79b295315f0 100644
--- a/test/tsconfig.json
+++ b/test/tsconfig.json
@@ -14,7 +14,6 @@
"include": [
"**/*.ts",
"**/*.tsx",
- "../typings/lodash.topath/*.ts",
"../typings/elastic__node_crypto.d.ts",
"typings/**/*"
],
diff --git a/test/visual_regression/services/visual_testing/visual_testing.ts b/test/visual_regression/services/visual_testing/visual_testing.ts
index 3a71c3aa9d3d6b..e35ef41420dd69 100644
--- a/test/visual_regression/services/visual_testing/visual_testing.ts
+++ b/test/visual_regression/services/visual_testing/visual_testing.ts
@@ -19,7 +19,6 @@
import { postSnapshot } from '@percy/agent/dist/utils/sdk-utils';
import { Test } from 'mocha';
-import _ from 'lodash';
import testSubjSelector from '@kbn/test-subj-selector';
diff --git a/vars/kibanaCoverage.groovy b/vars/kibanaCoverage.groovy
index e511d7a8fc15ea..66ebe3478fbec6 100644
--- a/vars/kibanaCoverage.groovy
+++ b/vars/kibanaCoverage.groovy
@@ -1,3 +1,46 @@
+def downloadPrevious(title) {
+ def vaultSecret = 'secret/gce/elastic-bekitzur/service-account/kibana'
+
+ withGcpServiceAccount.fromVaultSecret(vaultSecret, 'value') {
+ kibanaPipeline.bash('''
+
+ gsutil -m cp -r gs://elastic-bekitzur-kibana-coverage-live/previous_pointer/previous.txt . || echo "### Previous Pointer NOT FOUND?"
+
+ if [ -e ./previous.txt ]; then
+ mv previous.txt downloaded_previous.txt
+ echo "### downloaded_previous.txt"
+ cat downloaded_previous.txt
+ fi
+
+ ''', title)
+
+ def previous = sh(script: 'cat downloaded_previous.txt', label: '### Capture Previous Sha', returnStdout: true).trim()
+
+ return previous
+ }
+}
+
+def uploadPrevious(title) {
+ def vaultSecret = 'secret/gce/elastic-bekitzur/service-account/kibana'
+
+ withGcpServiceAccount.fromVaultSecret(vaultSecret, 'value') {
+ kibanaPipeline.bash('''
+
+ collectPrevious() {
+ PREVIOUS=$(git log --pretty=format:%h -1)
+ echo "### PREVIOUS: ${PREVIOUS}"
+ echo $PREVIOUS > previous.txt
+ }
+ collectPrevious
+
+ gsutil cp previous.txt gs://elastic-bekitzur-kibana-coverage-live/previous_pointer/
+
+
+ ''', title)
+
+ }
+}
+
def uploadCoverageStaticSite(timestamp) {
def uploadPrefix = "gs://elastic-bekitzur-kibana-coverage-live/"
def uploadPrefixWithTimeStamp = "${uploadPrefix}${timestamp}/"
@@ -67,6 +110,7 @@ EOF
cat src/dev/code_coverage/www/index.html
''', "### Combine Index Partials")
}
+
def collectVcsInfo(title) {
kibanaPipeline.bash('''
predicate() {
@@ -125,31 +169,31 @@ def uploadCombinedReports() {
)
}
-def ingestData(jobName, buildNum, buildUrl, title) {
+def ingestData(jobName, buildNum, buildUrl, previousSha, title) {
kibanaPipeline.bash("""
source src/dev/ci_setup/setup_env.sh
yarn kbn bootstrap --prefer-offline
# Using existing target/kibana-coverage folder
- . src/dev/code_coverage/shell_scripts/ingest_coverage.sh '${jobName}' ${buildNum} '${buildUrl}'
+ . src/dev/code_coverage/shell_scripts/ingest_coverage.sh '${jobName}' ${buildNum} '${buildUrl}' ${previousSha}
""", title)
}
-def ingestWithVault(jobName, buildNum, buildUrl, title) {
+def ingestWithVault(jobName, buildNum, buildUrl, previousSha, title) {
def vaultSecret = 'secret/kibana-issues/prod/coverage/elasticsearch'
withVaultSecret(secret: vaultSecret, secret_field: 'host', variable_name: 'HOST_FROM_VAULT') {
withVaultSecret(secret: vaultSecret, secret_field: 'username', variable_name: 'USER_FROM_VAULT') {
withVaultSecret(secret: vaultSecret, secret_field: 'password', variable_name: 'PASS_FROM_VAULT') {
- ingestData(jobName, buildNum, buildUrl, title)
+ ingestData(jobName, buildNum, buildUrl, previousSha, title)
}
}
}
}
-def ingest(jobName, buildNumber, buildUrl, timestamp, title) {
+def ingest(jobName, buildNumber, buildUrl, timestamp, previousSha, title) {
withEnv([
"TIME_STAMP=${timestamp}",
]) {
- ingestWithVault(jobName, buildNumber, buildUrl, title)
+ ingestWithVault(jobName, buildNumber, buildUrl, previousSha, title)
}
}
diff --git a/x-pack/legacy/plugins/beats_management/server/lib/adapters/beats/elasticsearch_beats_adapter.ts b/x-pack/legacy/plugins/beats_management/server/lib/adapters/beats/elasticsearch_beats_adapter.ts
index 0600ed8e3fbf69..7c495ad605f6d1 100644
--- a/x-pack/legacy/plugins/beats_management/server/lib/adapters/beats/elasticsearch_beats_adapter.ts
+++ b/x-pack/legacy/plugins/beats_management/server/lib/adapters/beats/elasticsearch_beats_adapter.ts
@@ -38,7 +38,7 @@ export class ElasticsearchBeatsAdapter implements CMBeatsAdapter {
if (!response.found) {
return null;
}
- const beat = _get(response, '_source.beat');
+ const beat = _get(response, '_source.beat') as CMBeat;
beat.tags = beat.tags || [];
return beat;
}
@@ -101,7 +101,7 @@ export class ElasticsearchBeatsAdapter implements CMBeatsAdapter {
const response = await this.database.search(user, params);
- const beats = _get(response, 'hits.hits', []);
+ const beats = _get(response, 'hits.hits', []) as CMBeat[];
if (beats.length === 0) {
return [];
@@ -127,14 +127,12 @@ export class ElasticsearchBeatsAdapter implements CMBeatsAdapter {
const response = await this.database.search(user, params);
- const beats = _get(response, 'hits.hits', []);
+ const beats = _get(response, 'hits.hits', []) as CMBeat[];
if (beats.length === 0) {
return null;
}
- return omit(_get(formatWithTags(beats[0]), '_source.beat'), [
- 'access_token',
- ]);
+ return omit(_get(formatWithTags(beats[0]), '_source.beat'), ['access_token']) as CMBeat;
}
public async getAll(user: FrameworkUser, ESQuery?: any) {
@@ -171,7 +169,7 @@ export class ElasticsearchBeatsAdapter implements CMBeatsAdapter {
if (!response) {
return [];
}
- const beats = _get(response, 'hits.hits', []);
+ const beats = _get(response, 'hits.hits', []) as any;
return beats.map((beat: any) =>
formatWithTags(omit(beat._source.beat as CMBeat, ['access_token']) as CMBeat)
@@ -202,7 +200,7 @@ export class ElasticsearchBeatsAdapter implements CMBeatsAdapter {
index: INDEX_NAMES.BEATS,
refresh: 'wait_for',
});
- return _get(response, 'items', []).map((item: any, resultIdx: number) => ({
+ return (_get(response, 'items', []) as any).map((item: any, resultIdx: number) => ({
idxInRequest: removals[resultIdx].idxInRequest,
result: item.update.result,
status: item.update.status,
@@ -237,7 +235,7 @@ export class ElasticsearchBeatsAdapter implements CMBeatsAdapter {
refresh: 'wait_for',
});
// console.log(response.items[0].update.error);
- return _get(response, 'items', []).map((item: any, resultIdx: any) => ({
+ return (_get(response, 'items', []) as any).map((item: any, resultIdx: any) => ({
idxInRequest: assignments[resultIdx].idxInRequest,
result: item.update.result,
status: item.update.status,
diff --git a/x-pack/legacy/plugins/beats_management/server/lib/adapters/configuration_blocks/elasticsearch_configuration_block_adapter.ts b/x-pack/legacy/plugins/beats_management/server/lib/adapters/configuration_blocks/elasticsearch_configuration_block_adapter.ts
index 2bc6f187564472..ec559c3ee479c3 100644
--- a/x-pack/legacy/plugins/beats_management/server/lib/adapters/configuration_blocks/elasticsearch_configuration_block_adapter.ts
+++ b/x-pack/legacy/plugins/beats_management/server/lib/adapters/configuration_blocks/elasticsearch_configuration_block_adapter.ts
@@ -35,7 +35,7 @@ export class ElasticsearchConfigurationBlockAdapter implements ConfigurationBloc
};
const response = await this.database.search(user, params);
- const configs = get(response, 'hits.hits', []);
+ const configs = get(response, 'hits.hits', []);
return configs.map((tag: any) => ({ ...tag._source.tag, config: JSON.parse(tag._source.tag) }));
}
@@ -71,7 +71,7 @@ export class ElasticsearchConfigurationBlockAdapter implements ConfigurationBloc
} else {
response = await this.database.search(user, params);
}
- const configs = get(response, 'hits.hits', []);
+ const configs = get(response, 'hits.hits', []);
return {
blocks: configs.map((block: any) => ({
diff --git a/x-pack/legacy/plugins/beats_management/server/lib/adapters/tags/elasticsearch_tags_adapter.ts b/x-pack/legacy/plugins/beats_management/server/lib/adapters/tags/elasticsearch_tags_adapter.ts
index 4e032001809f26..b5be3cfa99e5df 100644
--- a/x-pack/legacy/plugins/beats_management/server/lib/adapters/tags/elasticsearch_tags_adapter.ts
+++ b/x-pack/legacy/plugins/beats_management/server/lib/adapters/tags/elasticsearch_tags_adapter.ts
@@ -43,7 +43,7 @@ export class ElasticsearchTagsAdapter implements CMTagsAdapter {
};
}
const response = await this.database.search(user, params);
- const tags = get(response, 'hits.hits', []);
+ const tags = get(response, 'hits.hits', []) as any;
return tags.map((tag: any) => ({ hasConfigurationBlocksTypes: [], ...tag._source.tag }));
}
@@ -63,7 +63,7 @@ export class ElasticsearchTagsAdapter implements CMTagsAdapter {
const beatsResponse = await this.database.search(user, params);
- const beats = get(beatsResponse, 'hits.hits', []).map(
+ const beats = (get(beatsResponse, 'hits.hits', []) as BeatTag[]).map(
(beat: any) => beat._source.beat
);
@@ -142,7 +142,7 @@ export class ElasticsearchTagsAdapter implements CMTagsAdapter {
};
const response = await this.database.index(user, params);
- return get(response, 'result');
+ return get(response, 'result') as string;
}
public async getWithoutConfigTypes(
@@ -172,7 +172,7 @@ export class ElasticsearchTagsAdapter implements CMTagsAdapter {
size: 10000,
};
const response = await this.database.search(user, params);
- const tags = get(response, 'hits.hits', []);
+ const tags = get(response, 'hits.hits', []) as any;
return tags.map((tag: any) => ({ hasConfigurationBlocksTypes: [], ...tag._source.tag }));
}
diff --git a/x-pack/legacy/plugins/beats_management/server/lib/adapters/tokens/elasticsearch_tokens_adapter.ts b/x-pack/legacy/plugins/beats_management/server/lib/adapters/tokens/elasticsearch_tokens_adapter.ts
index 4987e4dbd4e0a6..6c5125ea4e0eb5 100644
--- a/x-pack/legacy/plugins/beats_management/server/lib/adapters/tokens/elasticsearch_tokens_adapter.ts
+++ b/x-pack/legacy/plugins/beats_management/server/lib/adapters/tokens/elasticsearch_tokens_adapter.ts
@@ -34,10 +34,10 @@ export class ElasticsearchTokensAdapter implements CMTokensAdapter {
const response = await this.database.get(user, params);
- const tokenDetails = get(response, '_source.enrollment_token', {
+ const tokenDetails = get(response, '_source.enrollment_token', {
expires_on: '0',
token: null,
- });
+ }) as TokenEnrollmentData;
// Elasticsearch might return fast if the token is not found. OR it might return fast
// if the token *is* found. Either way, an attacker could using a timing attack to figure
diff --git a/x-pack/package.json b/x-pack/package.json
index 0a8bc6f1e6f58a..b721cb2fc563a5 100644
--- a/x-pack/package.json
+++ b/x-pack/package.json
@@ -81,7 +81,7 @@
"@types/jsdom": "^16.2.3",
"@types/json-stable-stringify": "^1.0.32",
"@types/jsonwebtoken": "^7.2.8",
- "@types/lodash": "^3.10.1",
+ "@types/lodash": "^4.14.155",
"@types/mapbox-gl": "^1.9.1",
"@types/memoize-one": "^4.1.0",
"@types/mime": "^2.0.1",
@@ -281,11 +281,7 @@
"json-stable-stringify": "^1.0.1",
"jsonwebtoken": "^8.5.1",
"jsts": "^1.6.2",
- "lodash": "npm:@elastic/lodash@3.10.1-kibana4",
- "lodash.keyby": "^4.6.0",
- "lodash.mean": "^4.1.0",
- "lodash.topath": "^4.5.2",
- "lodash.uniqby": "^4.7.0",
+ "lodash": "^4.17.15",
"lz-string": "^1.4.4",
"mapbox-gl": "^1.10.0",
"mapbox-gl-draw-rectangle-mode": "^1.0.4",
diff --git a/x-pack/plugins/actions/README.md b/x-pack/plugins/actions/README.md
index 605676cee363d1..494f2f38e8bffc 100644
--- a/x-pack/plugins/actions/README.md
+++ b/x-pack/plugins/actions/README.md
@@ -71,6 +71,7 @@ Table of Contents
- [`params`](#params-7)
- [`subActionParams (pushToService)`](#subactionparams-pushtoservice-1)
- [Command Line Utility](#command-line-utility)
+- [Developing New Action Types](#developing-new-action-types)
## Terminology
@@ -606,3 +607,39 @@ $ kbn-action create .slack "post to slack" '{"webhookUrl": "https://hooks.slack.
"version": "WzMsMV0="
}
```
+
+# Developing New Action Types
+
+When creating a new action type, your plugin will eventually call `server.plugins.actions.setup.registerType()` to register the type with the actions plugin, but there are some additional things to think about about and implement.
+
+Consider working with the alerting team on early structure /design feedback of new actions, especially as the APIs and infrastructure are still under development.
+
+## licensing
+
+Currently actions are licensed as "basic" if the action only interacts with the stack, eg the server log and es index actions. Other actions are at least "gold" level.
+
+## plugin location
+
+Currently actions that are licensed as "basic" **MUST** be implemented in the actions plugin, other actions can be implemented in any other plugin that pre-reqs the actions plugin. If the new action is generic across the stack, it probably belongs in the actions plugin, but if your action is very specific to a plugin/solution, it might be easiest to implement it in the plugin/solution. Keep in mind that if Kibana is run without the plugin being enabled, any actions defined in that plugin will not run, nor will those actions be available via APIs or UI.
+
+Actions that take URLs or hostnames should check that those values are whitelisted. The whitelisting utilities are currently internal to the actions plugin, and so such actions will need to be implemented in the actions plugin. Longer-term, we will expose these utilities so they can be used by alerts implemented in other plugins; see [issue #64659](https://github.com/elastic/kibana/issues/64659).
+
+## documentation
+
+You should also create some asciidoc for the new action type. An entry should be made in the action type index - [`docs/user/alerting/action-types.asciidoc`](../../../docs/user/alerting/action-types.asciidoc) which points to a new document for the action type that should be in the directory [`docs/user/alerting/action-types`](../../../docs/user/alerting/action-types).
+
+## tests
+
+The action type should have both jest tests and functional tests. For functional tests, if your action interacts with a 3rd party service via HTTP, you may be able to create a simulator for your service, to test with. See the existing functional test servers in the directory [`x-pack/test/alerting_api_integration/common/fixtures/plugins/actions_simulators/server`](../../test/alerting_api_integration/common/fixtures/plugins/actions_simulators/server)
+
+## action type config and secrets
+
+Action types must define `config` and `secrets` which are used to create connectors. This data should be described with `@kbn/config-schema` object schemas, and you **MUST NOT** use `schema.maybe()` to define properties.
+
+This is due to the fact that the structures are persisted in saved objects, which performs partial updates on the persisted data. If a property value is already persisted, but an update either doesn't include the property, or sets it to `undefined`, the persisted value will not be changed. Beyond this being a semantic error in general, it also ends up invalidating the encryption used to save secrets, and will render the secrets will not be able to be unencrypted later.
+
+Instead of `schema.maybe()`, use `schema.nullable()`, which is the same as `schema.maybe()` except that when passed an `undefined` value, the object returned from the validation will be set to `null`. The resulting type will be `property-type | null`, whereas with `schema.maybe()` it would be `property-type | undefined`.
+
+## user interface
+
+In order to make this action usable in the Kibana UI, you will need to provide all the UI editing aspects of the action. The existing action type user interfaces are defined in [`x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types`](../triggers_actions_ui/public/application/components/builtin_action_types). For more information, see the [UI documentation](../triggers_actions_ui/README.md#create-and-register-new-action-type-ui).
diff --git a/x-pack/plugins/actions/server/builtin_action_types/case/utils.ts b/x-pack/plugins/actions/server/builtin_action_types/case/utils.ts
index dd8d971b7df44f..2d81c2bf4e15fb 100644
--- a/x-pack/plugins/actions/server/builtin_action_types/case/utils.ts
+++ b/x-pack/plugins/actions/server/builtin_action_types/case/utils.ts
@@ -26,7 +26,7 @@ import {
ExecutorSubActionPushParams,
} from './types';
-import { transformers, Transformer } from './transformers';
+import { transformers } from './transformers';
import { SUPPORTED_SOURCE_FIELDS } from './constants';
@@ -205,7 +205,7 @@ export const transformFields = ({
currentIncident,
}: TransformFieldsArgs): Record => {
return fields.reduce((prev, cur) => {
- const transform = flow(...cur.pipes.map((p) => transformers[p]));
+ const transform = flow(...cur.pipes.map((p) => transformers[p]));
return {
...prev,
[cur.key]: transform({
@@ -228,7 +228,7 @@ export const transformFields = ({
export const transformComments = (comments: Comment[], pipes: string[]): Comment[] => {
return comments.map((c) => ({
...c,
- comment: flow(...pipes.map((p) => transformers[p]))({
+ comment: flow(...pipes.map((p) => transformers[p]))({
value: c.comment,
date: c.updatedAt ?? c.createdAt,
user:
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 5dff0629222005..aa546e08ea1ba0 100644
--- a/x-pack/plugins/actions/server/usage/actions_usage_collector.ts
+++ b/x-pack/plugins/actions/server/usage/actions_usage_collector.ts
@@ -20,7 +20,7 @@ export function createActionsUsageCollector(
try {
const doc = await getLatestTaskState(await taskManager);
// get the accumulated state from the recurring task
- const state: ActionsUsage = get(doc, 'state');
+ const state: ActionsUsage = get(doc, 'state') as ActionsUsage;
return {
...state,
diff --git a/x-pack/plugins/alerts/server/alerts_client.ts b/x-pack/plugins/alerts/server/alerts_client.ts
index 6b091a5a4503b8..e8e6f82f138828 100644
--- a/x-pack/plugins/alerts/server/alerts_client.ts
+++ b/x-pack/plugins/alerts/server/alerts_client.ts
@@ -5,7 +5,7 @@
*/
import Boom from 'boom';
-import { omit, isEqual, pluck } from 'lodash';
+import { omit, isEqual, map } from 'lodash';
import { i18n } from '@kbn/i18n';
import {
Logger,
@@ -647,7 +647,7 @@ export class AlertsClient {
private validateActions(alertType: AlertType, actions: NormalizedAlertAction[]): void {
const { actionGroups: alertTypeActionGroups } = alertType;
const usedAlertActionGroups = actions.map((action) => action.group);
- const availableAlertTypeActionGroups = new Set(pluck(alertTypeActionGroups, 'id'));
+ const availableAlertTypeActionGroups = new Set(map(alertTypeActionGroups, 'id'));
const invalidActionGroups = usedAlertActionGroups.filter(
(group) => !availableAlertTypeActionGroups.has(group)
);
diff --git a/x-pack/plugins/alerts/server/task_runner/create_execution_handler.ts b/x-pack/plugins/alerts/server/task_runner/create_execution_handler.ts
index 8d859a570ba91f..e1e1568d2f13cb 100644
--- a/x-pack/plugins/alerts/server/task_runner/create_execution_handler.ts
+++ b/x-pack/plugins/alerts/server/task_runner/create_execution_handler.ts
@@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { pluck } from 'lodash';
+import { map } from 'lodash';
import { AlertAction, State, Context, AlertType } from '../types';
import { Logger, KibanaRequest } from '../../../../../src/core/server';
import { transformActionParams } from './transform_action_params';
@@ -46,7 +46,7 @@ export function createExecutionHandler({
eventLogger,
request,
}: CreateExecutionHandlerOptions) {
- const alertTypeActionGroups = new Set(pluck(alertType.actionGroups, 'id'));
+ const alertTypeActionGroups = new Set(map(alertType.actionGroups, 'id'));
return async ({ actionGroup, context, state, alertInstanceId }: ExecutionHandlerOptions) => {
if (!alertTypeActionGroups.has(actionGroup)) {
logger.error(`Invalid action group "${actionGroup}" for alert "${alertType.id}".`);
diff --git a/x-pack/plugins/alerts/server/task_runner/task_runner.ts b/x-pack/plugins/alerts/server/task_runner/task_runner.ts
index 3512ab16a37125..3c66b57bb94162 100644
--- a/x-pack/plugins/alerts/server/task_runner/task_runner.ts
+++ b/x-pack/plugins/alerts/server/task_runner/task_runner.ts
@@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { pick, mapValues, omit, without } from 'lodash';
+import { pickBy, mapValues, omit, without } from 'lodash';
import { Logger, SavedObject, KibanaRequest } from '../../../../../src/core/server';
import { TaskRunnerContext } from './task_runner_factory';
import { ConcreteTaskInstance } from '../../../task_manager/server';
@@ -18,12 +18,11 @@ import {
IntervalSchedule,
Services,
AlertInfoParams,
- RawAlertInstance,
AlertTaskState,
+ RawAlertInstance,
} from '../types';
import { promiseResult, map, Resultable, asOk, asErr, resolveErr } from '../lib/result_type';
import { taskInstanceToAlertTaskInstance } from './alert_task_instance';
-import { AlertInstances } from '../alert_instance/alert_instance';
import { EVENT_LOG_ACTIONS } from '../plugin';
import { IEvent, IEventLogger, SAVED_OBJECT_REL_PRIMARY } from '../../../event_log/server';
import { isAlertSavedObjectNotFoundError } from '../lib/is_alert_not_found_error';
@@ -167,7 +166,7 @@ export class TaskRunner {
} = this.taskInstance;
const namespace = this.context.spaceIdToNamespace(spaceId);
- const alertInstances = mapValues(
+ const alertInstances = mapValues, AlertInstance>(
alertRawInstances,
(rawAlertInstance) => new AlertInstance(rawAlertInstance)
);
@@ -227,9 +226,8 @@ export class TaskRunner {
eventLogger.logEvent(event);
// Cleanup alert instances that are no longer scheduling actions to avoid over populating the alertInstances object
- const instancesWithScheduledActions = pick(
- alertInstances,
- (alertInstance: AlertInstance) => alertInstance.hasScheduledActions()
+ const instancesWithScheduledActions = pickBy(alertInstances, (alertInstance: AlertInstance) =>
+ alertInstance.hasScheduledActions()
);
const currentAlertInstanceIds = Object.keys(instancesWithScheduledActions);
generateNewAndResolvedInstanceEvents({
@@ -242,10 +240,7 @@ export class TaskRunner {
});
if (!muteAll) {
- const enabledAlertInstances = omit(
- instancesWithScheduledActions,
- ...mutedInstanceIds
- );
+ const enabledAlertInstances = omit(instancesWithScheduledActions, ...mutedInstanceIds);
await Promise.all(
Object.entries(enabledAlertInstances)
@@ -260,7 +255,7 @@ export class TaskRunner {
return {
alertTypeState: updatedAlertTypeState || undefined,
- alertInstances: mapValues(
+ alertInstances: mapValues, RawAlertInstance>(
instancesWithScheduledActions,
(alertInstance) => alertInstance.toRaw()
),
diff --git a/x-pack/plugins/alerts/server/task_runner/transform_action_params.ts b/x-pack/plugins/alerts/server/task_runner/transform_action_params.ts
index 64f846d13c0bfd..fa4a0e40ddee56 100644
--- a/x-pack/plugins/alerts/server/task_runner/transform_action_params.ts
+++ b/x-pack/plugins/alerts/server/task_runner/transform_action_params.ts
@@ -5,7 +5,7 @@
*/
import Mustache from 'mustache';
-import { isString, cloneDeep } from 'lodash';
+import { isString, cloneDeepWith } from 'lodash';
import { AlertActionParams, State, Context } from '../types';
interface TransformActionParamsOptions {
@@ -29,7 +29,7 @@ export function transformActionParams({
actionParams,
state,
}: TransformActionParamsOptions): AlertActionParams {
- const result = cloneDeep(actionParams, (value: unknown) => {
+ const result = cloneDeepWith(actionParams, (value: unknown) => {
if (!isString(value)) return;
// when the list of variables we pass in here changes,
diff --git a/x-pack/plugins/alerts/server/usage/alerts_usage_collector.ts b/x-pack/plugins/alerts/server/usage/alerts_usage_collector.ts
index 7491508ee0745a..64d3ad54a23186 100644
--- a/x-pack/plugins/alerts/server/usage/alerts_usage_collector.ts
+++ b/x-pack/plugins/alerts/server/usage/alerts_usage_collector.ts
@@ -20,7 +20,7 @@ export function createAlertsUsageCollector(
try {
const doc = await getLatestTaskState(await taskManager);
// get the accumulated state from the recurring task
- const state: AlertsUsage = get(doc, 'state');
+ const state: AlertsUsage = get(doc, 'state') as AlertsUsage;
return {
...state,
diff --git a/x-pack/plugins/apm/common/projections/util/merge_projection/index.ts b/x-pack/plugins/apm/common/projections/util/merge_projection/index.ts
index f3ae0752b908eb..9dc1c815bf1692 100644
--- a/x-pack/plugins/apm/common/projections/util/merge_projection/index.ts
+++ b/x-pack/plugins/apm/common/projections/util/merge_projection/index.ts
@@ -3,7 +3,7 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
-import { merge, isPlainObject, cloneDeep } from 'lodash';
+import { mergeWith, isPlainObject, cloneDeep } from 'lodash';
import { DeepPartial } from 'utility-types';
import { AggregationInputMap } from '../../../../typings/elasticsearch/aggregations';
import {
@@ -35,7 +35,7 @@ export function mergeProjection<
T extends Projection,
U extends SourceProjection
>(target: T, source: U): DeepMerge {
- return merge({}, cloneDeep(target), source, (a, b) => {
+ return mergeWith({}, cloneDeep(target), source, (a, b) => {
if (isPlainObject(a) && isPlainObject(b)) {
return undefined;
}
diff --git a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/index.tsx b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/index.tsx
index 7ee8dfa496b577..4e1af6e0dc2392 100644
--- a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/index.tsx
@@ -65,7 +65,7 @@ interface Props {
function getCurrentTab(
tabs: ErrorTab[] = [],
currentTabKey: string | undefined
-) {
+): ErrorTab | {} {
const selectedTab = tabs.find(({ key }) => key === currentTabKey);
return selectedTab ? selectedTab : first(tabs) || {};
}
@@ -78,7 +78,7 @@ export function DetailView({ errorGroup, urlParams, location }: Props) {
}
const tabs = getTabs(error);
- const currentTab = getCurrentTab(tabs, urlParams.detailTab);
+ const currentTab = getCurrentTab(tabs, urlParams.detailTab) as ErrorTab;
const errorUrl = error.error.page?.url || error.url?.full;
diff --git a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/Distribution/index.tsx b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/Distribution/index.tsx
index d71d5f2cb480de..3cd04ee032e561 100644
--- a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/Distribution/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/Distribution/index.tsx
@@ -10,7 +10,7 @@ import numeral from '@elastic/numeral';
import { i18n } from '@kbn/i18n';
import d3 from 'd3';
import { scaleUtc } from 'd3-scale';
-import mean from 'lodash.mean';
+import { mean } from 'lodash';
import React from 'react';
import { asRelativeDateTimeRange } from '../../../../utils/formatters';
import { getTimezoneOffsetInMs } from '../../../shared/charts/CustomPlot/getTimezoneOffsetInMs';
diff --git a/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/WatcherFlyout.tsx b/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/WatcherFlyout.tsx
index 8a3e2b1a02dac5..26cff5e71b610f 100644
--- a/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/WatcherFlyout.tsx
+++ b/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/WatcherFlyout.tsx
@@ -26,7 +26,7 @@ import {
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
-import { padLeft, range } from 'lodash';
+import { padStart, range } from 'lodash';
import moment from 'moment-timezone';
import React, { Component } from 'react';
import styled from 'styled-components';
@@ -288,7 +288,7 @@ export class WatcherFlyout extends Component<
// Generate UTC hours for Daily Report select field
const intervalHours = range(24).map((i) => {
- const hour = padLeft(i.toString(), 2, '0');
+ const hour = padStart(i.toString(), 2, '0');
return { value: `${hour}:00`, text: `${hour}:00 UTC` };
});
diff --git a/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/__test__/createErrorGroupWatch.test.ts b/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/__test__/createErrorGroupWatch.test.ts
index f0bc313ab46444..054476af28de1c 100644
--- a/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/__test__/createErrorGroupWatch.test.ts
+++ b/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/__test__/createErrorGroupWatch.test.ts
@@ -110,7 +110,7 @@ function renderMustache(
if (isObject(input)) {
return Object.keys(input).reduce((acc, key) => {
- const value = input[key];
+ const value = (input as any)[key];
return { ...acc, [key]: renderMustache(value, ctx) };
}, {});
diff --git a/x-pack/plugins/apm/public/components/app/TransactionDetails/index.tsx b/x-pack/plugins/apm/public/components/app/TransactionDetails/index.tsx
index c1bfce4cdca49e..620ae6708eda0f 100644
--- a/x-pack/plugins/apm/public/components/app/TransactionDetails/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/index.tsx
@@ -12,7 +12,6 @@ import {
EuiFlexGroup,
EuiFlexItem,
} from '@elastic/eui';
-import _ from 'lodash';
import React, { useMemo } from 'react';
import { useTransactionCharts } from '../../../hooks/useTransactionCharts';
import { useTransactionDistribution } from '../../../hooks/useTransactionDistribution';
diff --git a/x-pack/plugins/apm/public/components/shared/ManagedTable/index.tsx b/x-pack/plugins/apm/public/components/shared/ManagedTable/index.tsx
index ef7ebe684fadee..3dbb1b2faac020 100644
--- a/x-pack/plugins/apm/public/components/shared/ManagedTable/index.tsx
+++ b/x-pack/plugins/apm/public/components/shared/ManagedTable/index.tsx
@@ -5,7 +5,7 @@
*/
import { EuiBasicTable, EuiBasicTableColumn } from '@elastic/eui';
-import { sortByOrder } from 'lodash';
+import { orderBy } from 'lodash';
import React, { useMemo, useCallback, ReactNode } from 'react';
import { useUrlParams } from '../../../hooks/useUrlParams';
import { history } from '../../../utils/history';
@@ -58,9 +58,8 @@ function UnoptimizedManagedTable(props: Props) {
} = useUrlParams();
const renderedItems = useMemo(() => {
- // TODO: Use _.orderBy once we upgrade to lodash 4+
const sortedItems = sortItems
- ? sortByOrder(items, sortField, sortDirection)
+ ? orderBy(items, sortField, sortDirection as 'asc' | 'desc')
: items;
return sortedItems.slice(page * pageSize, (page + 1) * pageSize);
diff --git a/x-pack/plugins/apm/public/components/shared/Stacktrace/index.tsx b/x-pack/plugins/apm/public/components/shared/Stacktrace/index.tsx
index 01043f33ec7b7a..b37146f3b3be5b 100644
--- a/x-pack/plugins/apm/public/components/shared/Stacktrace/index.tsx
+++ b/x-pack/plugins/apm/public/components/shared/Stacktrace/index.tsx
@@ -87,7 +87,7 @@ export function getGroupedStackframes(stackframes: IStackframe[]) {
!stackframe.exclude_from_grouping;
// append to group
- if (shouldAppend) {
+ if (prevGroup && shouldAppend) {
prevGroup.stackframes.push(stackframe);
return acc;
}
diff --git a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/sections.ts b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/sections.ts
index 7f99939a0a0d0c..d3a9ade3925a12 100644
--- a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/sections.ts
+++ b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/sections.ts
@@ -5,7 +5,7 @@
*/
import { i18n } from '@kbn/i18n';
import { Location } from 'history';
-import { pick, isEmpty } from 'lodash';
+import { pickBy, isEmpty } from 'lodash';
import moment from 'moment';
import url from 'url';
import { Transaction } from '../../../../typings/es_schemas/ui/transaction';
@@ -63,13 +63,13 @@ export const getSections = ({
const uptimeLink = url.format({
pathname: basePath.prepend('/app/uptime'),
search: `?${fromQuery(
- pick(
+ pickBy(
{
dateRangeStart: urlParams.rangeFrom,
dateRangeEnd: urlParams.rangeTo,
search: `url.domain:"${transaction.url?.domain}"`,
},
- (val: string) => !isEmpty(val)
+ (val) => !isEmpty(val)
)
)}`,
});
diff --git a/x-pack/plugins/apm/public/components/shared/charts/ErrorRateChart/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/ErrorRateChart/index.tsx
index 7aafa9e1fdcec9..de60441f4faa0b 100644
--- a/x-pack/plugins/apm/public/components/shared/charts/ErrorRateChart/index.tsx
+++ b/x-pack/plugins/apm/public/components/shared/charts/ErrorRateChart/index.tsx
@@ -6,7 +6,7 @@
import { EuiTitle } from '@elastic/eui';
import theme from '@elastic/eui/dist/eui_theme_light.json';
import { i18n } from '@kbn/i18n';
-import mean from 'lodash.mean';
+import { mean } from 'lodash';
import React, { useCallback } from 'react';
import { useChartsSync } from '../../../../hooks/useChartsSync';
import { useFetcher } from '../../../../hooks/useFetcher';
diff --git a/x-pack/plugins/apm/public/components/shared/useDelayedVisibility/index.test.tsx b/x-pack/plugins/apm/public/components/shared/useDelayedVisibility/index.test.tsx
index 9f72ac6d5916e8..447e11eab5e412 100644
--- a/x-pack/plugins/apm/public/components/shared/useDelayedVisibility/index.test.tsx
+++ b/x-pack/plugins/apm/public/components/shared/useDelayedVisibility/index.test.tsx
@@ -58,28 +58,29 @@ describe.skip('useFetcher', () => {
expect(hook.result.current).toEqual(true);
});
- it('is true for minimum 1000ms', () => {
- hook = renderHook((isLoading) => useDelayedVisibility(isLoading), {
- initialProps: false,
- });
+ // Disabled because it's flaky: https://github.com/elastic/kibana/issues/66389
+ // it('is true for minimum 1000ms', () => {
+ // hook = renderHook((isLoading) => useDelayedVisibility(isLoading), {
+ // initialProps: false,
+ // });
- hook.rerender(true);
+ // hook.rerender(true);
- act(() => {
- jest.advanceTimersByTime(100);
- });
+ // act(() => {
+ // jest.advanceTimersByTime(100);
+ // });
- hook.rerender(false);
- act(() => {
- jest.advanceTimersByTime(900);
- });
+ // hook.rerender(false);
+ // act(() => {
+ // jest.advanceTimersByTime(900);
+ // });
- expect(hook.result.current).toEqual(true);
+ // expect(hook.result.current).toEqual(true);
- act(() => {
- jest.advanceTimersByTime(100);
- });
+ // act(() => {
+ // jest.advanceTimersByTime(100);
+ // });
- expect(hook.result.current).toEqual(false);
- });
+ // expect(hook.result.current).toEqual(false);
+ // });
});
diff --git a/x-pack/plugins/apm/public/context/LoadingIndicatorContext.tsx b/x-pack/plugins/apm/public/context/LoadingIndicatorContext.tsx
index a26653d3d5294b..99822c0bbc5cad 100644
--- a/x-pack/plugins/apm/public/context/LoadingIndicatorContext.tsx
+++ b/x-pack/plugins/apm/public/context/LoadingIndicatorContext.tsx
@@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { EuiPortal, EuiProgress } from '@elastic/eui';
-import { pick } from 'lodash';
+import { pickBy } from 'lodash';
import React, { Fragment, useMemo, useReducer } from 'react';
import { useDelayedVisibility } from '../components/shared/useDelayedVisibility';
@@ -26,7 +26,7 @@ function reducer(statuses: State, action: Action) {
// Return an object with only the ids with `true` as their value, so that ids
// that previously had `false` are removed and do not remain hanging around in
// the object.
- return pick(
+ return pickBy(
{ ...statuses, [action.id.toString()]: action.isLoading },
Boolean
);
diff --git a/x-pack/plugins/apm/public/context/UrlParamsContext/helpers.ts b/x-pack/plugins/apm/public/context/UrlParamsContext/helpers.ts
index 9ce993e8484884..d9781400f22725 100644
--- a/x-pack/plugins/apm/public/context/UrlParamsContext/helpers.ts
+++ b/x-pack/plugins/apm/public/context/UrlParamsContext/helpers.ts
@@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { compact, pick } from 'lodash';
+import { compact, pickBy } from 'lodash';
import datemath from '@elastic/datemath';
import { IUrlParams } from './types';
import { ProcessorEvent } from '../../../common/processor_event';
@@ -61,8 +61,8 @@ export function getPathAsArray(pathname: string = '') {
return compact(pathname.split('/'));
}
-export function removeUndefinedProps(obj: T): Partial {
- return pick(obj, (value) => value !== undefined);
+export function removeUndefinedProps(obj: T): Partial {
+ return pickBy(obj, (value) => value !== undefined);
}
export function getPathParams(pathname: string = ''): PathParams {
diff --git a/x-pack/plugins/apm/public/services/rest/observability.dashboard.test.ts b/x-pack/plugins/apm/public/services/rest/observability.dashboard.test.ts
index dbb5d6029d0f16..a14d827eeaec5f 100644
--- a/x-pack/plugins/apm/public/services/rest/observability.dashboard.test.ts
+++ b/x-pack/plugins/apm/public/services/rest/observability.dashboard.test.ts
@@ -58,7 +58,7 @@ describe('Observability dashboard data', () => {
transactions: {
type: 'number',
label: 'Transactions',
- value: 6,
+ value: 2,
color: '#6092c0',
},
},
@@ -115,5 +115,45 @@ describe('Observability dashboard data', () => {
},
});
});
+ it('returns transaction stat as 0 when y is undefined', async () => {
+ callApmApiMock.mockImplementation(() =>
+ Promise.resolve({
+ serviceCount: 0,
+ transactionCoordinates: [{ x: 1 }, { x: 2 }, { x: 3 }],
+ })
+ );
+ const response = await fetchLandingPageData(
+ {
+ startTime: '1',
+ endTime: '2',
+ bucketSize: '3',
+ },
+ { theme }
+ );
+ expect(response).toEqual({
+ title: 'APM',
+ appLink: '/app/apm',
+ stats: {
+ services: {
+ type: 'number',
+ label: 'Services',
+ value: 0,
+ },
+ transactions: {
+ type: 'number',
+ label: 'Transactions',
+ value: 0,
+ color: '#6092c0',
+ },
+ },
+ series: {
+ transactions: {
+ label: 'Transactions',
+ coordinates: [{ x: 1 }, { x: 2 }, { x: 3 }],
+ color: '#6092c0',
+ },
+ },
+ });
+ });
});
});
diff --git a/x-pack/plugins/apm/public/services/rest/observability_dashboard.ts b/x-pack/plugins/apm/public/services/rest/observability_dashboard.ts
index 2107565c5facf9..79ccf8dbd6f9ba 100644
--- a/x-pack/plugins/apm/public/services/rest/observability_dashboard.ts
+++ b/x-pack/plugins/apm/public/services/rest/observability_dashboard.ts
@@ -5,7 +5,7 @@
*/
import { i18n } from '@kbn/i18n';
-import { sum } from 'lodash';
+import { mean } from 'lodash';
import { Theme } from '@kbn/ui-shared-deps/theme';
import {
ApmFetchDataResponse,
@@ -48,7 +48,12 @@ export const fetchLandingPageData = async (
'xpack.apm.observabilityDashboard.stats.transactions',
{ defaultMessage: 'Transactions' }
),
- value: sum(transactionCoordinates.map((coordinates) => coordinates.y)),
+ value:
+ mean(
+ transactionCoordinates
+ .map(({ y }) => y)
+ .filter((y) => y && isFinite(y))
+ ) || 0,
color: theme.euiColorVis1,
},
},
diff --git a/x-pack/plugins/apm/server/lib/errors/distribution/__tests__/get_buckets.test.ts b/x-pack/plugins/apm/server/lib/errors/distribution/__tests__/get_buckets.test.ts
index 408cdd387cbd88..5f23a9329a5832 100644
--- a/x-pack/plugins/apm/server/lib/errors/distribution/__tests__/get_buckets.test.ts
+++ b/x-pack/plugins/apm/server/lib/errors/distribution/__tests__/get_buckets.test.ts
@@ -56,7 +56,6 @@ describe('timeseriesFetcher', () => {
apmAgentConfigurationIndex: '.apm-agent-configuration',
apmCustomLinkIndex: '.apm-custom-link',
},
- dynamicIndexPattern: null as any,
},
});
});
diff --git a/x-pack/plugins/apm/server/lib/helpers/convert_ui_filters/get_ui_filters_es.ts b/x-pack/plugins/apm/server/lib/helpers/convert_ui_filters/get_ui_filters_es.ts
index c9e9db13cecae0..b34d5535d58cc9 100644
--- a/x-pack/plugins/apm/server/lib/helpers/convert_ui_filters/get_ui_filters_es.ts
+++ b/x-pack/plugins/apm/server/lib/helpers/convert_ui_filters/get_ui_filters_es.ts
@@ -11,15 +11,9 @@ import {
localUIFilters,
localUIFilterNames,
} from '../../ui_filters/local_ui_filters/config';
-import {
- esKuery,
- IIndexPattern,
-} from '../../../../../../../src/plugins/data/server';
+import { esKuery } from '../../../../../../../src/plugins/data/server';
-export function getUiFiltersES(
- indexPattern: IIndexPattern | undefined,
- uiFilters: UIFilters
-) {
+export function getUiFiltersES(uiFilters: UIFilters) {
const { kuery, environment, ...localFilterValues } = uiFilters;
const mappedFilters = localUIFilterNames
.filter((name) => name in localFilterValues)
@@ -35,7 +29,7 @@ export function getUiFiltersES(
// remove undefined items from list
const esFilters = [
- getKueryUiFilterES(indexPattern, uiFilters.kuery),
+ getKueryUiFilterES(uiFilters.kuery),
getEnvironmentUiFilterES(uiFilters.environment),
]
.filter((filter) => !!filter)
@@ -44,14 +38,11 @@ export function getUiFiltersES(
return esFilters;
}
-function getKueryUiFilterES(
- indexPattern: IIndexPattern | undefined,
- kuery?: string
-) {
- if (!kuery || !indexPattern) {
+function getKueryUiFilterES(kuery?: string) {
+ if (!kuery) {
return;
}
const ast = esKuery.fromKueryExpression(kuery);
- return esKuery.toElasticsearchQuery(ast, indexPattern) as ESFilter;
+ return esKuery.toElasticsearchQuery(ast) as ESFilter;
}
diff --git a/x-pack/plugins/apm/server/lib/helpers/es_client.ts b/x-pack/plugins/apm/server/lib/helpers/es_client.ts
index 892f8f0ddd1051..2d730933e24731 100644
--- a/x-pack/plugins/apm/server/lib/helpers/es_client.ts
+++ b/x-pack/plugins/apm/server/lib/helpers/es_client.ts
@@ -19,11 +19,10 @@ import {
ESSearchRequest,
ESSearchResponse,
} from '../../../typings/elasticsearch';
-import { UI_SETTINGS } from '../../../../../../src/plugins/data/server';
import { OBSERVER_VERSION_MAJOR } from '../../../common/elasticsearch_fieldnames';
import { pickKeys } from '../../../common/utils/pick_keys';
import { APMRequestHandlerContext } from '../../routes/typings';
-import { getApmIndices } from '../settings/apm_indices/get_apm_indices';
+import { ApmIndicesConfig } from '../settings/apm_indices/get_apm_indices';
// `type` was deprecated in 7.0
export type APMIndexDocumentParams = Omit, 'type'>;
@@ -85,20 +84,19 @@ function addFilterForLegacyData(
}
// add additional params for search (aka: read) requests
-async function getParamsForSearchRequest(
- context: APMRequestHandlerContext,
- params: ESSearchRequest,
- apmOptions?: APMOptions
-) {
- const { uiSettings } = context.core;
- const [indices, includeFrozen] = await Promise.all([
- getApmIndices({
- savedObjectsClient: context.core.savedObjects.client,
- config: context.config,
- }),
- uiSettings.client.get(UI_SETTINGS.SEARCH_INCLUDE_FROZEN),
- ]);
-
+function getParamsForSearchRequest({
+ context,
+ params,
+ indices,
+ includeFrozen,
+ includeLegacyData,
+}: {
+ context: APMRequestHandlerContext;
+ params: ESSearchRequest;
+ indices: ApmIndicesConfig;
+ includeFrozen: boolean;
+ includeLegacyData?: boolean;
+}) {
// Get indices for legacy data filter (only those which apply)
const apmIndices = Object.values(
pickKeys(
@@ -112,7 +110,7 @@ async function getParamsForSearchRequest(
)
);
return {
- ...addFilterForLegacyData(apmIndices, params, apmOptions), // filter out pre-7.0 data
+ ...addFilterForLegacyData(apmIndices, params, { includeLegacyData }), // filter out pre-7.0 data
ignore_throttled: !includeFrozen, // whether to query frozen indices or not
};
}
@@ -123,6 +121,8 @@ interface APMOptions {
interface ClientCreateOptions {
clientAsInternalUser?: boolean;
+ indices: ApmIndicesConfig;
+ includeFrozen: boolean;
}
export type ESClient = ReturnType;
@@ -134,7 +134,7 @@ function formatObj(obj: Record) {
export function getESClient(
context: APMRequestHandlerContext,
request: KibanaRequest,
- { clientAsInternalUser = false }: ClientCreateOptions = {}
+ { clientAsInternalUser = false, indices, includeFrozen }: ClientCreateOptions
) {
const {
callAsCurrentUser,
@@ -194,11 +194,13 @@ export function getESClient(
params: TSearchRequest,
apmOptions?: APMOptions
): Promise> => {
- const nextParams = await getParamsForSearchRequest(
+ const nextParams = await getParamsForSearchRequest({
context,
params,
- apmOptions
- );
+ indices,
+ includeFrozen,
+ ...apmOptions,
+ });
return callEs('search', nextParams);
},
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 2dd8ed01082fd7..14c9378d991928 100644
--- a/x-pack/plugins/apm/server/lib/helpers/setup_request.ts
+++ b/x-pack/plugins/apm/server/lib/helpers/setup_request.ts
@@ -5,8 +5,8 @@
*/
import moment from 'moment';
+import { UI_SETTINGS } from '../../../../../../src/plugins/data/common';
import { KibanaRequest } from '../../../../../../src/core/server';
-import { IIndexPattern } from '../../../../../../src/plugins/data/common';
import { APMConfig } from '../..';
import {
getApmIndices,
@@ -18,17 +18,13 @@ import { getUiFiltersES } from './convert_ui_filters/get_ui_filters_es';
import { APMRequestHandlerContext } from '../../routes/typings';
import { getESClient } from './es_client';
import { ProcessorEvent } from '../../../common/processor_event';
-import { getDynamicIndexPattern } from '../index_pattern/get_dynamic_index_pattern';
-function decodeUiFilters(
- indexPattern: IIndexPattern | undefined,
- uiFiltersEncoded?: string
-) {
- if (!uiFiltersEncoded || !indexPattern) {
+function decodeUiFilters(uiFiltersEncoded?: string) {
+ if (!uiFiltersEncoded) {
return [];
}
const uiFilters = JSON.parse(uiFiltersEncoded);
- return getUiFiltersES(indexPattern, uiFilters);
+ return getUiFiltersES(uiFilters);
}
// Explicitly type Setup to prevent TS initialization errors
// https://github.com/microsoft/TypeScript/issues/34933
@@ -39,7 +35,6 @@ export interface Setup {
ml?: ReturnType;
config: APMConfig;
indices: ApmIndicesConfig;
- dynamicIndexPattern?: IIndexPattern;
}
export interface SetupTimeRange {
@@ -75,28 +70,33 @@ export async function setupRequest(
const { config } = context;
const { query } = context.params;
- const indices = await getApmIndices({
- savedObjectsClient: context.core.savedObjects.client,
- config,
- });
+ const [indices, includeFrozen] = await Promise.all([
+ getApmIndices({
+ savedObjectsClient: context.core.savedObjects.client,
+ config,
+ }),
+ context.core.uiSettings.client.get(UI_SETTINGS.SEARCH_INCLUDE_FROZEN),
+ ]);
- const dynamicIndexPattern = await getDynamicIndexPattern({
- context,
+ const createClientOptions = {
indices,
- processorEvent: query.processorEvent,
- });
+ includeFrozen,
+ };
- const uiFiltersES = decodeUiFilters(dynamicIndexPattern, query.uiFilters);
+ const uiFiltersES = decodeUiFilters(query.uiFilters);
const coreSetupRequest = {
indices,
- client: getESClient(context, request, { clientAsInternalUser: false }),
+ client: getESClient(context, request, {
+ clientAsInternalUser: false,
+ ...createClientOptions,
+ }),
internalClient: getESClient(context, request, {
clientAsInternalUser: true,
+ ...createClientOptions,
}),
ml: getMlSetup(context, request),
config,
- dynamicIndexPattern,
};
return {
diff --git a/x-pack/plugins/apm/server/lib/observability_dashboard/get_transaction_coordinates.ts b/x-pack/plugins/apm/server/lib/observability_dashboard/get_transaction_coordinates.ts
index e78a3c1cec24a5..0d1a4274c16dc9 100644
--- a/x-pack/plugins/apm/server/lib/observability_dashboard/get_transaction_coordinates.ts
+++ b/x-pack/plugins/apm/server/lib/observability_dashboard/get_transaction_coordinates.ts
@@ -41,17 +41,18 @@ export async function getTransactionCoordinates({
field: '@timestamp',
fixed_interval: bucketSize,
min_doc_count: 0,
- extended_bounds: { min: start, max: end },
},
},
},
},
});
+ const deltaAsMinutes = (end - start) / 1000 / 60;
+
return (
aggregations?.distribution.buckets.map((bucket) => ({
x: bucket.key,
- y: bucket.doc_count,
+ y: bucket.doc_count / deltaAsMinutes,
})) || []
);
}
diff --git a/x-pack/plugins/apm/server/lib/service_map/__snapshots__/get_service_map_from_trace_ids.test.ts.snap b/x-pack/plugins/apm/server/lib/service_map/__snapshots__/get_service_map_from_trace_ids.test.ts.snap
new file mode 100644
index 00000000000000..1f4a8a4367fad7
--- /dev/null
+++ b/x-pack/plugins/apm/server/lib/service_map/__snapshots__/get_service_map_from_trace_ids.test.ts.snap
@@ -0,0 +1,222 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`getConnections transforms a list of paths into a list of connections filtered by service.name and environment 1`] = `
+Array [
+ Object {
+ "destination": Object {
+ "agent.name": "nodejs",
+ "service.environment": "production",
+ "service.name": "opbeans-node",
+ },
+ "source": Object {
+ "agent.name": "python",
+ "service.environment": "production",
+ "service.name": "opbeans-python",
+ },
+ },
+ Object {
+ "destination": Object {
+ "span.destination.service.resource": "172.18.0.6:3000",
+ "span.subtype": "http",
+ "span.type": "external",
+ },
+ "source": Object {
+ "agent.name": "nodejs",
+ "service.environment": "production",
+ "service.name": "opbeans-node",
+ },
+ },
+ Object {
+ "destination": Object {
+ "agent.name": "python",
+ "service.environment": "production",
+ "service.name": "opbeans-python",
+ },
+ "source": Object {
+ "agent.name": "nodejs",
+ "service.environment": "production",
+ "service.name": "opbeans-node",
+ },
+ },
+ Object {
+ "destination": Object {
+ "agent.name": "ruby",
+ "service.environment": "production",
+ "service.name": "opbeans-ruby",
+ },
+ "source": Object {
+ "agent.name": "python",
+ "service.environment": "production",
+ "service.name": "opbeans-python",
+ },
+ },
+ Object {
+ "destination": Object {
+ "span.destination.service.resource": "postgresql",
+ "span.subtype": "postgresql",
+ "span.type": "db",
+ },
+ "source": Object {
+ "agent.name": "ruby",
+ "service.environment": "production",
+ "service.name": "opbeans-ruby",
+ },
+ },
+ Object {
+ "destination": Object {
+ "agent.name": "ruby",
+ "service.environment": "production",
+ "service.name": "opbeans-ruby",
+ },
+ "source": Object {
+ "agent.name": "nodejs",
+ "service.environment": "production",
+ "service.name": "opbeans-node",
+ },
+ },
+ Object {
+ "destination": Object {
+ "span.destination.service.resource": "opbeans-python:3000",
+ "span.subtype": "http_rb",
+ "span.type": "ext",
+ },
+ "source": Object {
+ "agent.name": "ruby",
+ "service.environment": "production",
+ "service.name": "opbeans-ruby",
+ },
+ },
+ Object {
+ "destination": Object {
+ "agent.name": "nodejs",
+ "service.environment": "production",
+ "service.name": "opbeans-node",
+ },
+ "source": Object {
+ "agent.name": "ruby",
+ "service.environment": "production",
+ "service.name": "opbeans-ruby",
+ },
+ },
+ Object {
+ "destination": Object {
+ "span.destination.service.resource": "opbeans-node:3000",
+ "span.subtype": "http",
+ "span.type": "external",
+ },
+ "source": Object {
+ "agent.name": "python",
+ "service.environment": "production",
+ "service.name": "opbeans-python",
+ },
+ },
+ Object {
+ "destination": Object {
+ "span.destination.service.resource": "172.18.0.7:3000",
+ "span.subtype": "http",
+ "span.type": "external",
+ },
+ "source": Object {
+ "agent.name": "nodejs",
+ "service.environment": "production",
+ "service.name": "opbeans-node",
+ },
+ },
+ Object {
+ "destination": Object {
+ "span.destination.service.resource": "postgresql",
+ "span.subtype": "postgresql",
+ "span.type": "db",
+ },
+ "source": Object {
+ "agent.name": "python",
+ "service.environment": "production",
+ "service.name": "opbeans-python",
+ },
+ },
+ Object {
+ "destination": Object {
+ "agent.name": "python",
+ "service.environment": "production",
+ "service.name": "opbeans-python",
+ },
+ "source": Object {
+ "agent.name": "ruby",
+ "service.environment": "production",
+ "service.name": "opbeans-ruby",
+ },
+ },
+ Object {
+ "destination": Object {
+ "span.destination.service.resource": "postgresql",
+ "span.subtype": "postgresql",
+ "span.type": "db",
+ },
+ "source": Object {
+ "agent.name": "nodejs",
+ "service.environment": "production",
+ "service.name": "opbeans-node",
+ },
+ },
+ Object {
+ "destination": Object {
+ "span.destination.service.resource": "93.184.216.34:80",
+ "span.subtype": "http",
+ "span.type": "external",
+ },
+ "source": Object {
+ "agent.name": "nodejs",
+ "service.environment": "production",
+ "service.name": "opbeans-node",
+ },
+ },
+ Object {
+ "destination": Object {
+ "span.destination.service.resource": "opbeans-ruby:3000",
+ "span.subtype": "http_rb",
+ "span.type": "ext",
+ },
+ "source": Object {
+ "agent.name": "ruby",
+ "service.environment": "production",
+ "service.name": "opbeans-ruby",
+ },
+ },
+ Object {
+ "destination": Object {
+ "span.destination.service.resource": "redis",
+ "span.subtype": "redis",
+ "span.type": "cache",
+ },
+ "source": Object {
+ "agent.name": "nodejs",
+ "service.environment": "production",
+ "service.name": "opbeans-node",
+ },
+ },
+ Object {
+ "destination": Object {
+ "span.destination.service.resource": "opbeans-node:3000",
+ "span.subtype": "http_rb",
+ "span.type": "ext",
+ },
+ "source": Object {
+ "agent.name": "ruby",
+ "service.environment": "production",
+ "service.name": "opbeans-ruby",
+ },
+ },
+ Object {
+ "destination": Object {
+ "span.destination.service.resource": "opbeans-ruby:3000",
+ "span.subtype": "http",
+ "span.type": "external",
+ },
+ "source": Object {
+ "agent.name": "python",
+ "service.environment": "production",
+ "service.name": "opbeans-python",
+ },
+ },
+]
+`;
diff --git a/x-pack/plugins/apm/server/lib/service_map/fetch_service_paths_from_trace_ids.ts b/x-pack/plugins/apm/server/lib/service_map/fetch_service_paths_from_trace_ids.ts
new file mode 100644
index 00000000000000..08c8aba5f02078
--- /dev/null
+++ b/x-pack/plugins/apm/server/lib/service_map/fetch_service_paths_from_trace_ids.ts
@@ -0,0 +1,232 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+import {
+ PROCESSOR_EVENT,
+ TRACE_ID,
+} from '../../../common/elasticsearch_fieldnames';
+import {
+ ConnectionNode,
+ ExternalConnectionNode,
+ ServiceConnectionNode,
+} from '../../../common/service_map';
+import { Setup } from '../helpers/setup_request';
+
+export async function fetchServicePathsFromTraceIds(
+ setup: Setup,
+ traceIds: string[]
+) {
+ const { indices, client } = setup;
+
+ const serviceMapParams = {
+ index: [
+ indices['apm_oss.spanIndices'],
+ indices['apm_oss.transactionIndices'],
+ ],
+ body: {
+ size: 0,
+ query: {
+ bool: {
+ filter: [
+ {
+ terms: {
+ [PROCESSOR_EVENT]: ['span', 'transaction'],
+ },
+ },
+ {
+ terms: {
+ [TRACE_ID]: traceIds,
+ },
+ },
+ ],
+ },
+ },
+ aggs: {
+ service_map: {
+ scripted_metric: {
+ init_script: {
+ lang: 'painless',
+ source: `state.eventsById = new HashMap();
+
+ String[] fieldsToCopy = new String[] {
+ 'parent.id',
+ 'service.name',
+ 'service.environment',
+ 'span.destination.service.resource',
+ 'trace.id',
+ 'processor.event',
+ 'span.type',
+ 'span.subtype',
+ 'agent.name'
+ };
+ state.fieldsToCopy = fieldsToCopy;`,
+ },
+ map_script: {
+ lang: 'painless',
+ source: `def id;
+ if (!doc['span.id'].empty) {
+ id = doc['span.id'].value;
+ } else {
+ id = doc['transaction.id'].value;
+ }
+
+ def copy = new HashMap();
+ copy.id = id;
+
+ for(key in state.fieldsToCopy) {
+ if (!doc[key].empty) {
+ copy[key] = doc[key].value;
+ }
+ }
+
+ state.eventsById[id] = copy`,
+ },
+ combine_script: {
+ lang: 'painless',
+ source: `return state.eventsById;`,
+ },
+ reduce_script: {
+ lang: 'painless',
+ source: `
+ def getDestination ( def event ) {
+ def destination = new HashMap();
+ destination['span.destination.service.resource'] = event['span.destination.service.resource'];
+ destination['span.type'] = event['span.type'];
+ destination['span.subtype'] = event['span.subtype'];
+ return destination;
+ }
+
+ def processAndReturnEvent(def context, def eventId) {
+ if (context.processedEvents[eventId] != null) {
+ return context.processedEvents[eventId];
+ }
+
+ def event = context.eventsById[eventId];
+
+ if (event == null) {
+ return null;
+ }
+
+ def service = new HashMap();
+ service['service.name'] = event['service.name'];
+ service['service.environment'] = event['service.environment'];
+ service['agent.name'] = event['agent.name'];
+
+ def basePath = new ArrayList();
+
+ def parentId = event['parent.id'];
+ def parent;
+
+ if (parentId != null && parentId != event['id']) {
+ parent = processAndReturnEvent(context, parentId);
+ if (parent != null) {
+ /* copy the path from the parent */
+ basePath.addAll(parent.path);
+ /* flag parent path for removal, as it has children */
+ context.locationsToRemove.add(parent.path);
+
+ /* if the parent has 'span.destination.service.resource' set, and the service is different,
+ we've discovered a service */
+
+ if (parent['span.destination.service.resource'] != null
+ && parent['span.destination.service.resource'] != ""
+ && (parent['service.name'] != event['service.name']
+ || parent['service.environment'] != event['service.environment']
+ )
+ ) {
+ def parentDestination = getDestination(parent);
+ context.externalToServiceMap.put(parentDestination, service);
+ }
+ }
+ }
+
+ def lastLocation = basePath.size() > 0 ? basePath[basePath.size() - 1] : null;
+
+ def currentLocation = service;
+
+ /* only add the current location to the path if it's different from the last one*/
+ if (lastLocation == null || !lastLocation.equals(currentLocation)) {
+ basePath.add(currentLocation);
+ }
+
+ /* if there is an outgoing span, create a new path */
+ if (event['span.destination.service.resource'] != null
+ && event['span.destination.service.resource'] != '') {
+ def outgoingLocation = getDestination(event);
+ def outgoingPath = new ArrayList(basePath);
+ outgoingPath.add(outgoingLocation);
+ context.paths.add(outgoingPath);
+ }
+
+ event.path = basePath;
+
+ context.processedEvents[eventId] = event;
+ return event;
+ }
+
+ def context = new HashMap();
+
+ context.processedEvents = new HashMap();
+ context.eventsById = new HashMap();
+
+ context.paths = new HashSet();
+ context.externalToServiceMap = new HashMap();
+ context.locationsToRemove = new HashSet();
+
+ for (state in states) {
+ context.eventsById.putAll(state);
+ }
+
+ for (entry in context.eventsById.entrySet()) {
+ processAndReturnEvent(context, entry.getKey());
+ }
+
+ def paths = new HashSet();
+
+ for(foundPath in context.paths) {
+ if (!context.locationsToRemove.contains(foundPath)) {
+ paths.add(foundPath);
+ }
+ }
+
+ def response = new HashMap();
+ response.paths = paths;
+
+ def discoveredServices = new HashSet();
+
+ for(entry in context.externalToServiceMap.entrySet()) {
+ def map = new HashMap();
+ map.from = entry.getKey();
+ map.to = entry.getValue();
+ discoveredServices.add(map);
+ }
+ response.discoveredServices = discoveredServices;
+
+ return response;`,
+ },
+ },
+ },
+ },
+ },
+ };
+
+ const serviceMapFromTraceIdsScriptResponse = await client.search(
+ serviceMapParams
+ );
+
+ return serviceMapFromTraceIdsScriptResponse as {
+ aggregations?: {
+ service_map: {
+ value: {
+ paths: ConnectionNode[][];
+ discoveredServices: Array<{
+ from: ExternalConnectionNode;
+ to: ServiceConnectionNode;
+ }>;
+ };
+ };
+ };
+ };
+}
diff --git a/x-pack/plugins/apm/server/lib/service_map/get_service_map_from_trace_ids.test.ts b/x-pack/plugins/apm/server/lib/service_map/get_service_map_from_trace_ids.test.ts
new file mode 100644
index 00000000000000..a3a7e5c995bfeb
--- /dev/null
+++ b/x-pack/plugins/apm/server/lib/service_map/get_service_map_from_trace_ids.test.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;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { getConnections } from './get_service_map_from_trace_ids';
+import serviceMapFromTraceIdsScriptResponse from './mock_responses/get_service_map_from_trace_ids_script_response.json';
+import { PromiseReturnType } from '../../../typings/common';
+import { fetchServicePathsFromTraceIds } from './fetch_service_paths_from_trace_ids';
+
+describe('getConnections', () => {
+ it('transforms a list of paths into a list of connections filtered by service.name and environment', () => {
+ const response = serviceMapFromTraceIdsScriptResponse as PromiseReturnType<
+ typeof fetchServicePathsFromTraceIds
+ >;
+ const serviceName = 'opbeans-node';
+ const environment = 'production';
+
+ const connections = getConnections(
+ response.aggregations?.service_map.value.paths,
+ serviceName,
+ environment
+ );
+
+ expect(connections).toMatchSnapshot();
+ });
+});
diff --git a/x-pack/plugins/apm/server/lib/service_map/get_service_map_from_trace_ids.ts b/x-pack/plugins/apm/server/lib/service_map/get_service_map_from_trace_ids.ts
index 01cbc1aa9b9895..f6e331a09fa651 100644
--- a/x-pack/plugins/apm/server/lib/service_map/get_service_map_from_trace_ids.ts
+++ b/x-pack/plugins/apm/server/lib/service_map/get_service_map_from_trace_ids.ts
@@ -3,237 +3,27 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
-import { find, uniq } from 'lodash';
+import { find, uniqBy } from 'lodash';
import {
- PROCESSOR_EVENT,
SERVICE_ENVIRONMENT,
SERVICE_NAME,
- TRACE_ID,
} from '../../../common/elasticsearch_fieldnames';
import {
Connection,
ConnectionNode,
- ExternalConnectionNode,
ServiceConnectionNode,
} from '../../../common/service_map';
import { Setup } from '../helpers/setup_request';
-
-export async function getServiceMapFromTraceIds({
- setup,
- traceIds,
- serviceName,
- environment,
-}: {
- setup: Setup;
- traceIds: string[];
- serviceName?: string;
- environment?: string;
-}) {
- const { indices, client } = setup;
-
- const serviceMapParams = {
- index: [
- indices['apm_oss.spanIndices'],
- indices['apm_oss.transactionIndices'],
- ],
- body: {
- size: 0,
- query: {
- bool: {
- filter: [
- {
- terms: {
- [PROCESSOR_EVENT]: ['span', 'transaction'],
- },
- },
- {
- terms: {
- [TRACE_ID]: traceIds,
- },
- },
- ],
- },
- },
- aggs: {
- service_map: {
- scripted_metric: {
- init_script: {
- lang: 'painless',
- source: `state.eventsById = new HashMap();
-
- String[] fieldsToCopy = new String[] {
- 'parent.id',
- 'service.name',
- 'service.environment',
- 'span.destination.service.resource',
- 'trace.id',
- 'processor.event',
- 'span.type',
- 'span.subtype',
- 'agent.name'
- };
- state.fieldsToCopy = fieldsToCopy;`,
- },
- map_script: {
- lang: 'painless',
- source: `def id;
- if (!doc['span.id'].empty) {
- id = doc['span.id'].value;
- } else {
- id = doc['transaction.id'].value;
- }
-
- def copy = new HashMap();
- copy.id = id;
-
- for(key in state.fieldsToCopy) {
- if (!doc[key].empty) {
- copy[key] = doc[key].value;
- }
- }
-
- state.eventsById[id] = copy`,
- },
- combine_script: {
- lang: 'painless',
- source: `return state.eventsById;`,
- },
- reduce_script: {
- lang: 'painless',
- source: `
- def getDestination ( def event ) {
- def destination = new HashMap();
- destination['span.destination.service.resource'] = event['span.destination.service.resource'];
- destination['span.type'] = event['span.type'];
- destination['span.subtype'] = event['span.subtype'];
- return destination;
- }
-
- def processAndReturnEvent(def context, def eventId) {
- if (context.processedEvents[eventId] != null) {
- return context.processedEvents[eventId];
- }
-
- def event = context.eventsById[eventId];
-
- if (event == null) {
- return null;
- }
-
- def service = new HashMap();
- service['service.name'] = event['service.name'];
- service['service.environment'] = event['service.environment'];
- service['agent.name'] = event['agent.name'];
-
- def basePath = new ArrayList();
-
- def parentId = event['parent.id'];
- def parent;
-
- if (parentId != null && parentId != event['id']) {
- parent = processAndReturnEvent(context, parentId);
- if (parent != null) {
- /* copy the path from the parent */
- basePath.addAll(parent.path);
- /* flag parent path for removal, as it has children */
- context.locationsToRemove.add(parent.path);
-
- /* if the parent has 'span.destination.service.resource' set, and the service is different,
- we've discovered a service */
-
- if (parent['span.destination.service.resource'] != null
- && parent['span.destination.service.resource'] != ""
- && (parent['service.name'] != event['service.name']
- || parent['service.environment'] != event['service.environment']
- )
- ) {
- def parentDestination = getDestination(parent);
- context.externalToServiceMap.put(parentDestination, service);
- }
- }
- }
-
- def lastLocation = basePath.size() > 0 ? basePath[basePath.size() - 1] : null;
-
- def currentLocation = service;
-
- /* only add the current location to the path if it's different from the last one*/
- if (lastLocation == null || !lastLocation.equals(currentLocation)) {
- basePath.add(currentLocation);
- }
-
- /* if there is an outgoing span, create a new path */
- if (event['span.destination.service.resource'] != null
- && event['span.destination.service.resource'] != '') {
- def outgoingLocation = getDestination(event);
- def outgoingPath = new ArrayList(basePath);
- outgoingPath.add(outgoingLocation);
- context.paths.add(outgoingPath);
- }
-
- event.path = basePath;
-
- context.processedEvents[eventId] = event;
- return event;
- }
-
- def context = new HashMap();
-
- context.processedEvents = new HashMap();
- context.eventsById = new HashMap();
-
- context.paths = new HashSet();
- context.externalToServiceMap = new HashMap();
- context.locationsToRemove = new HashSet();
-
- for (state in states) {
- context.eventsById.putAll(state);
- }
-
- for (entry in context.eventsById.entrySet()) {
- processAndReturnEvent(context, entry.getKey());
- }
-
- def paths = new HashSet();
-
- for(foundPath in context.paths) {
- if (!context.locationsToRemove.contains(foundPath)) {
- paths.add(foundPath);
- }
- }
-
- def response = new HashMap();
- response.paths = paths;
-
- def discoveredServices = new HashSet();
-
- for(entry in context.externalToServiceMap.entrySet()) {
- def map = new HashMap();
- map.from = entry.getKey();
- map.to = entry.getValue();
- discoveredServices.add(map);
- }
- response.discoveredServices = discoveredServices;
-
- return response;`,
- },
- },
- },
- },
- },
- };
-
- const serviceMapResponse = await client.search(serviceMapParams);
-
- const scriptResponse = serviceMapResponse.aggregations?.service_map.value as {
- paths: ConnectionNode[][];
- discoveredServices: Array<{
- from: ExternalConnectionNode;
- to: ServiceConnectionNode;
- }>;
- };
-
- let paths = scriptResponse.paths;
+import { fetchServicePathsFromTraceIds } from './fetch_service_paths_from_trace_ids';
+
+export function getConnections(
+ paths?: ConnectionNode[][],
+ serviceName?: string,
+ environment?: string
+) {
+ if (!paths) {
+ return [];
+ }
if (serviceName || environment) {
paths = paths.filter((path) => {
@@ -257,26 +47,51 @@ export async function getServiceMapFromTraceIds({
});
}
- const connections = uniq(
- paths.flatMap((path) => {
- return path.reduce((conns, location, index) => {
- const prev = path[index - 1];
- if (prev) {
- return conns.concat({
- source: prev,
- destination: location,
- });
- }
- return conns;
- }, [] as Connection[]);
- }, [] as Connection[]),
- (value, _index, array) => {
- return find(array, value);
- }
+ const connectionsArr = paths.flatMap((path) => {
+ return path.reduce((conns, location, index) => {
+ const prev = path[index - 1];
+ if (prev) {
+ return conns.concat({
+ source: prev,
+ destination: location,
+ });
+ }
+ return conns;
+ }, [] as Connection[]);
+ }, [] as Connection[]);
+
+ const connections = uniqBy(connectionsArr, (value) =>
+ find(connectionsArr, value)
);
+ return connections;
+}
+
+export async function getServiceMapFromTraceIds({
+ setup,
+ traceIds,
+ serviceName,
+ environment,
+}: {
+ setup: Setup;
+ traceIds: string[];
+ serviceName?: string;
+ environment?: string;
+}) {
+ const serviceMapFromTraceIdsScriptResponse = await fetchServicePathsFromTraceIds(
+ setup,
+ traceIds
+ );
+
+ const serviceMapScriptedAggValue =
+ serviceMapFromTraceIdsScriptResponse.aggregations?.service_map.value;
+
return {
- connections,
- discoveredServices: scriptResponse.discoveredServices,
+ connections: getConnections(
+ serviceMapScriptedAggValue?.paths,
+ serviceName,
+ environment
+ ),
+ discoveredServices: serviceMapScriptedAggValue?.discoveredServices ?? [],
};
}
diff --git a/x-pack/plugins/apm/server/lib/service_map/mock_responses/get_service_map_from_trace_ids_script_response.json b/x-pack/plugins/apm/server/lib/service_map/mock_responses/get_service_map_from_trace_ids_script_response.json
new file mode 100644
index 00000000000000..49d8efebbf43b1
--- /dev/null
+++ b/x-pack/plugins/apm/server/lib/service_map/mock_responses/get_service_map_from_trace_ids_script_response.json
@@ -0,0 +1,1165 @@
+{
+ "took": 43,
+ "timed_out": false,
+ "_shards": { "total": 6, "successful": 6, "skipped": 0, "failed": 0 },
+ "hits": {
+ "total": { "value": 465, "relation": "eq" },
+ "max_score": null,
+ "hits": []
+ },
+ "aggregations": {
+ "service_map": {
+ "value": {
+ "paths": [
+ [
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-python",
+ "agent.name": "python"
+ },
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-node",
+ "agent.name": "nodejs"
+ },
+ {
+ "span.subtype": "http",
+ "span.destination.service.resource": "172.18.0.6:3000",
+ "span.type": "external"
+ }
+ ],
+ [
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-node",
+ "agent.name": "nodejs"
+ },
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-python",
+ "agent.name": "python"
+ },
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-ruby",
+ "agent.name": "ruby"
+ },
+ {
+ "span.subtype": "postgresql",
+ "span.destination.service.resource": "postgresql",
+ "span.type": "db"
+ }
+ ],
+ [
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-node",
+ "agent.name": "nodejs"
+ },
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-ruby",
+ "agent.name": "ruby"
+ },
+ {
+ "span.subtype": "http_rb",
+ "span.destination.service.resource": "opbeans-python:3000",
+ "span.type": "ext"
+ }
+ ],
+ [
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-ruby",
+ "agent.name": "ruby"
+ },
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-node",
+ "agent.name": "nodejs"
+ },
+ {
+ "span.subtype": "http",
+ "span.destination.service.resource": "172.18.0.6:3000",
+ "span.type": "external"
+ }
+ ],
+ [
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-python",
+ "agent.name": "python"
+ },
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-node",
+ "agent.name": "nodejs"
+ },
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-python",
+ "agent.name": "python"
+ },
+ {
+ "span.subtype": "http",
+ "span.destination.service.resource": "opbeans-node:3000",
+ "span.type": "external"
+ }
+ ],
+ [
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-python",
+ "agent.name": "python"
+ },
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-node",
+ "agent.name": "nodejs"
+ },
+ {
+ "span.subtype": "http",
+ "span.destination.service.resource": "172.18.0.7:3000",
+ "span.type": "external"
+ }
+ ],
+ [
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-python",
+ "agent.name": "python"
+ },
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-node",
+ "agent.name": "nodejs"
+ },
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-ruby",
+ "agent.name": "ruby"
+ },
+ {
+ "span.subtype": "postgresql",
+ "span.destination.service.resource": "postgresql",
+ "span.type": "db"
+ }
+ ],
+ [
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-ruby",
+ "agent.name": "ruby"
+ },
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-python",
+ "agent.name": "python"
+ },
+ {
+ "span.subtype": "http",
+ "span.destination.service.resource": "opbeans-ruby:3000",
+ "span.type": "external"
+ }
+ ],
+ [
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-python",
+ "agent.name": "python"
+ },
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-ruby",
+ "agent.name": "ruby"
+ },
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-node",
+ "agent.name": "nodejs"
+ },
+ {
+ "span.subtype": "http",
+ "span.destination.service.resource": "172.18.0.7:3000",
+ "span.type": "external"
+ }
+ ],
+ [
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-python",
+ "agent.name": "python"
+ },
+ {
+ "span.subtype": "http",
+ "span.destination.service.resource": "opbeans-node:3000",
+ "span.type": "external"
+ }
+ ],
+ [
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-node",
+ "agent.name": "nodejs"
+ },
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-python",
+ "agent.name": "python"
+ },
+ {
+ "span.subtype": "postgresql",
+ "span.destination.service.resource": "postgresql",
+ "span.type": "db"
+ }
+ ],
+ [
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-node",
+ "agent.name": "nodejs"
+ },
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-ruby",
+ "agent.name": "ruby"
+ },
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-python",
+ "agent.name": "python"
+ },
+ {
+ "span.subtype": "http",
+ "span.destination.service.resource": "opbeans-node:3000",
+ "span.type": "external"
+ }
+ ],
+ [
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-python",
+ "agent.name": "python"
+ },
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-ruby",
+ "agent.name": "ruby"
+ },
+ {
+ "span.subtype": "http_rb",
+ "span.destination.service.resource": "opbeans-node:3000",
+ "span.type": "ext"
+ }
+ ],
+ [
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-ruby",
+ "agent.name": "ruby"
+ },
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-node",
+ "agent.name": "nodejs"
+ },
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-ruby",
+ "agent.name": "ruby"
+ },
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-node",
+ "agent.name": "nodejs"
+ },
+ {
+ "span.subtype": "postgresql",
+ "span.destination.service.resource": "postgresql",
+ "span.type": "db"
+ }
+ ],
+ [
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-python",
+ "agent.name": "python"
+ },
+ {
+ "span.subtype": "http",
+ "span.destination.service.resource": "opbeans-ruby:3000",
+ "span.type": "external"
+ }
+ ],
+ [
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-node",
+ "agent.name": "nodejs"
+ },
+ {
+ "span.subtype": "http",
+ "span.destination.service.resource": "172.18.0.7:3000",
+ "span.type": "external"
+ }
+ ],
+ [
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-node",
+ "agent.name": "nodejs"
+ },
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-ruby",
+ "agent.name": "ruby"
+ },
+ {
+ "span.subtype": "postgresql",
+ "span.destination.service.resource": "postgresql",
+ "span.type": "db"
+ }
+ ],
+ [
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-ruby",
+ "agent.name": "ruby"
+ },
+ {
+ "span.subtype": "http_rb",
+ "span.destination.service.resource": "opbeans-python:3000",
+ "span.type": "ext"
+ }
+ ],
+ [
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-node",
+ "agent.name": "nodejs"
+ },
+ {
+ "span.subtype": "http",
+ "span.destination.service.resource": "93.184.216.34:80",
+ "span.type": "external"
+ }
+ ],
+ [
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-node",
+ "agent.name": "nodejs"
+ },
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-ruby",
+ "agent.name": "ruby"
+ },
+ {
+ "span.subtype": "http_rb",
+ "span.destination.service.resource": "opbeans-ruby:3000",
+ "span.type": "ext"
+ }
+ ],
+ [
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-ruby",
+ "agent.name": "ruby"
+ },
+ {
+ "span.subtype": "http_rb",
+ "span.destination.service.resource": "opbeans-node:3000",
+ "span.type": "ext"
+ }
+ ],
+ [
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-node",
+ "agent.name": "nodejs"
+ },
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-ruby",
+ "agent.name": "ruby"
+ },
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-python",
+ "agent.name": "python"
+ },
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-node",
+ "agent.name": "nodejs"
+ },
+ {
+ "span.subtype": "postgresql",
+ "span.destination.service.resource": "postgresql",
+ "span.type": "db"
+ }
+ ],
+ [
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-ruby",
+ "agent.name": "ruby"
+ },
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-python",
+ "agent.name": "python"
+ },
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-ruby",
+ "agent.name": "ruby"
+ },
+ {
+ "span.subtype": "http_rb",
+ "span.destination.service.resource": "opbeans-python:3000",
+ "span.type": "ext"
+ }
+ ],
+ [
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-ruby",
+ "agent.name": "ruby"
+ },
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-node",
+ "agent.name": "nodejs"
+ },
+ {
+ "span.subtype": "http",
+ "span.destination.service.resource": "172.18.0.7:3000",
+ "span.type": "external"
+ }
+ ],
+ [
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-python",
+ "agent.name": "python"
+ },
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-ruby",
+ "agent.name": "ruby"
+ },
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-node",
+ "agent.name": "nodejs"
+ },
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-ruby",
+ "agent.name": "ruby"
+ },
+ {
+ "span.subtype": "postgresql",
+ "span.destination.service.resource": "postgresql",
+ "span.type": "db"
+ }
+ ],
+ [
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-node",
+ "agent.name": "nodejs"
+ },
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-python",
+ "agent.name": "python"
+ },
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-ruby",
+ "agent.name": "ruby"
+ },
+ {
+ "span.subtype": "http_rb",
+ "span.destination.service.resource": "opbeans-ruby:3000",
+ "span.type": "ext"
+ }
+ ],
+ [
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-ruby",
+ "agent.name": "ruby"
+ },
+ {
+ "span.subtype": "http_rb",
+ "span.destination.service.resource": "opbeans-ruby:3000",
+ "span.type": "ext"
+ }
+ ],
+ [
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-node",
+ "agent.name": "nodejs"
+ },
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-python",
+ "agent.name": "python"
+ },
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-ruby",
+ "agent.name": "ruby"
+ },
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-python",
+ "agent.name": "python"
+ },
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-node",
+ "agent.name": "nodejs"
+ },
+ {
+ "span.subtype": "redis",
+ "span.destination.service.resource": "redis",
+ "span.type": "cache"
+ }
+ ],
+ [
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-ruby",
+ "agent.name": "ruby"
+ },
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-node",
+ "agent.name": "nodejs"
+ },
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-python",
+ "agent.name": "python"
+ },
+ {
+ "span.subtype": "postgresql",
+ "span.destination.service.resource": "postgresql",
+ "span.type": "db"
+ }
+ ],
+ [
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-python",
+ "agent.name": "python"
+ },
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-ruby",
+ "agent.name": "ruby"
+ },
+ {
+ "span.subtype": "http_rb",
+ "span.destination.service.resource": "opbeans-python:3000",
+ "span.type": "ext"
+ }
+ ],
+ [
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-python",
+ "agent.name": "python"
+ },
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-node",
+ "agent.name": "nodejs"
+ },
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-python",
+ "agent.name": "python"
+ },
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-node",
+ "agent.name": "nodejs"
+ },
+ {
+ "span.subtype": "postgresql",
+ "span.destination.service.resource": "postgresql",
+ "span.type": "db"
+ }
+ ],
+ [
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-node",
+ "agent.name": "nodejs"
+ },
+ {
+ "span.subtype": "redis",
+ "span.destination.service.resource": "redis",
+ "span.type": "cache"
+ }
+ ],
+ [
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-python",
+ "agent.name": "python"
+ },
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-node",
+ "agent.name": "nodejs"
+ },
+ {
+ "span.subtype": "postgresql",
+ "span.destination.service.resource": "postgresql",
+ "span.type": "db"
+ }
+ ],
+ [
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-python",
+ "agent.name": "python"
+ },
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-ruby",
+ "agent.name": "ruby"
+ },
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-python",
+ "agent.name": "python"
+ },
+ {
+ "span.subtype": "http",
+ "span.destination.service.resource": "opbeans-node:3000",
+ "span.type": "external"
+ }
+ ],
+ [
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-ruby",
+ "agent.name": "ruby"
+ },
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-node",
+ "agent.name": "nodejs"
+ },
+ {
+ "span.subtype": "postgresql",
+ "span.destination.service.resource": "postgresql",
+ "span.type": "db"
+ }
+ ],
+ [
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-python",
+ "agent.name": "python"
+ },
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-ruby",
+ "agent.name": "ruby"
+ },
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-python",
+ "agent.name": "python"
+ },
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-node",
+ "agent.name": "nodejs"
+ },
+ {
+ "span.subtype": "postgresql",
+ "span.destination.service.resource": "postgresql",
+ "span.type": "db"
+ }
+ ],
+ [
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-node",
+ "agent.name": "nodejs"
+ },
+ {
+ "span.subtype": "postgresql",
+ "span.destination.service.resource": "postgresql",
+ "span.type": "db"
+ }
+ ],
+ [
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-ruby",
+ "agent.name": "ruby"
+ },
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-python",
+ "agent.name": "python"
+ },
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-ruby",
+ "agent.name": "ruby"
+ },
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-python",
+ "agent.name": "python"
+ },
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-node",
+ "agent.name": "nodejs"
+ },
+ {
+ "span.subtype": "postgresql",
+ "span.destination.service.resource": "postgresql",
+ "span.type": "db"
+ }
+ ],
+ [
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-node",
+ "agent.name": "nodejs"
+ },
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-ruby",
+ "agent.name": "ruby"
+ },
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-node",
+ "agent.name": "nodejs"
+ },
+ {
+ "span.subtype": "postgresql",
+ "span.destination.service.resource": "postgresql",
+ "span.type": "db"
+ }
+ ],
+ [
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-node",
+ "agent.name": "nodejs"
+ },
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-python",
+ "agent.name": "python"
+ },
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-ruby",
+ "agent.name": "ruby"
+ },
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-python",
+ "agent.name": "python"
+ },
+ {
+ "span.subtype": "http",
+ "span.destination.service.resource": "opbeans-node:3000",
+ "span.type": "external"
+ }
+ ],
+ [
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-node",
+ "agent.name": "nodejs"
+ },
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-ruby",
+ "agent.name": "ruby"
+ },
+ {
+ "span.subtype": "http_rb",
+ "span.destination.service.resource": "opbeans-node:3000",
+ "span.type": "ext"
+ }
+ ],
+ [
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-node",
+ "agent.name": "nodejs"
+ },
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-python",
+ "agent.name": "python"
+ },
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-ruby",
+ "agent.name": "ruby"
+ },
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-node",
+ "agent.name": "nodejs"
+ },
+ {
+ "span.subtype": "postgresql",
+ "span.destination.service.resource": "postgresql",
+ "span.type": "db"
+ }
+ ],
+ [
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-node",
+ "agent.name": "nodejs"
+ },
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-python",
+ "agent.name": "python"
+ },
+ {
+ "span.subtype": "http",
+ "span.destination.service.resource": "opbeans-ruby:3000",
+ "span.type": "external"
+ }
+ ],
+ [
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-ruby",
+ "agent.name": "ruby"
+ },
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-node",
+ "agent.name": "nodejs"
+ },
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-ruby",
+ "agent.name": "ruby"
+ },
+ {
+ "span.subtype": "http_rb",
+ "span.destination.service.resource": "opbeans-node:3000",
+ "span.type": "ext"
+ }
+ ],
+ [
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-node",
+ "agent.name": "nodejs"
+ },
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-python",
+ "agent.name": "python"
+ },
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-ruby",
+ "agent.name": "ruby"
+ },
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-python",
+ "agent.name": "python"
+ },
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-node",
+ "agent.name": "nodejs"
+ },
+ {
+ "span.subtype": "postgresql",
+ "span.destination.service.resource": "postgresql",
+ "span.type": "db"
+ }
+ ],
+ [
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-node",
+ "agent.name": "nodejs"
+ },
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-ruby",
+ "agent.name": "ruby"
+ },
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-node",
+ "agent.name": "nodejs"
+ },
+ {
+ "span.subtype": "redis",
+ "span.destination.service.resource": "redis",
+ "span.type": "cache"
+ }
+ ],
+ [
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-node",
+ "agent.name": "nodejs"
+ },
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-python",
+ "agent.name": "python"
+ },
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-ruby",
+ "agent.name": "ruby"
+ },
+ {
+ "span.subtype": "http_rb",
+ "span.destination.service.resource": "opbeans-python:3000",
+ "span.type": "ext"
+ }
+ ],
+ [
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-node",
+ "agent.name": "nodejs"
+ },
+ {
+ "span.subtype": "http",
+ "span.destination.service.resource": "172.18.0.6:3000",
+ "span.type": "external"
+ }
+ ],
+ [
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-python",
+ "agent.name": "python"
+ },
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-ruby",
+ "agent.name": "ruby"
+ },
+ {
+ "span.subtype": "http_rb",
+ "span.destination.service.resource": "opbeans-ruby:3000",
+ "span.type": "ext"
+ }
+ ],
+ [
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-node",
+ "agent.name": "nodejs"
+ },
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-python",
+ "agent.name": "python"
+ },
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-ruby",
+ "agent.name": "ruby"
+ },
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-node",
+ "agent.name": "nodejs"
+ },
+ {
+ "span.subtype": "redis",
+ "span.destination.service.resource": "redis",
+ "span.type": "cache"
+ }
+ ],
+ [
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-node",
+ "agent.name": "nodejs"
+ },
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-python",
+ "agent.name": "python"
+ },
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-ruby",
+ "agent.name": "ruby"
+ },
+ {
+ "span.subtype": "http_rb",
+ "span.destination.service.resource": "opbeans-node:3000",
+ "span.type": "ext"
+ }
+ ],
+ [
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-ruby",
+ "agent.name": "ruby"
+ },
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-node",
+ "agent.name": "nodejs"
+ },
+ {
+ "span.subtype": "redis",
+ "span.destination.service.resource": "redis",
+ "span.type": "cache"
+ }
+ ],
+ [
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-python",
+ "agent.name": "python"
+ },
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-node",
+ "agent.name": "nodejs"
+ },
+ {
+ "span.subtype": "redis",
+ "span.destination.service.resource": "redis",
+ "span.type": "cache"
+ }
+ ],
+ [
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-ruby",
+ "agent.name": "ruby"
+ },
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-python",
+ "agent.name": "python"
+ },
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-ruby",
+ "agent.name": "ruby"
+ },
+ {
+ "service.environment": "production",
+ "service.name": "opbeans-python",
+ "agent.name": "python"
+ },
+ {
+ "span.subtype": "http",
+ "span.destination.service.resource": "opbeans-node:3000",
+ "span.type": "external"
+ }
+ ]
+ ],
+ "discoveredServices": [
+ {
+ "from": {
+ "span.subtype": "http",
+ "span.destination.service.resource": "opbeans-node:3000",
+ "span.type": "external"
+ },
+ "to": {
+ "service.environment": "production",
+ "service.name": "opbeans-node",
+ "agent.name": "nodejs"
+ }
+ },
+ {
+ "from": {
+ "span.subtype": "http",
+ "span.destination.service.resource": "172.18.0.7:3000",
+ "span.type": "external"
+ },
+ "to": {
+ "service.environment": "production",
+ "service.name": "opbeans-ruby",
+ "agent.name": "ruby"
+ }
+ },
+ {
+ "from": {
+ "span.subtype": "http",
+ "span.destination.service.resource": "opbeans-ruby:3000",
+ "span.type": "external"
+ },
+ "to": {
+ "service.environment": "production",
+ "service.name": "opbeans-ruby",
+ "agent.name": "ruby"
+ }
+ },
+ {
+ "from": {
+ "span.subtype": "http_rb",
+ "span.destination.service.resource": "opbeans-node:3000",
+ "span.type": "ext"
+ },
+ "to": {
+ "service.environment": "production",
+ "service.name": "opbeans-node",
+ "agent.name": "nodejs"
+ }
+ },
+ {
+ "from": {
+ "span.subtype": "http_rb",
+ "span.destination.service.resource": "opbeans-python:3000",
+ "span.type": "ext"
+ },
+ "to": {
+ "service.environment": "production",
+ "service.name": "opbeans-python",
+ "agent.name": "python"
+ }
+ },
+ {
+ "from": {
+ "span.subtype": "http",
+ "span.destination.service.resource": "172.18.0.6:3000",
+ "span.type": "external"
+ },
+ "to": {
+ "service.environment": "production",
+ "service.name": "opbeans-python",
+ "agent.name": "python"
+ }
+ }
+ ]
+ }
+ }
+ }
+}
diff --git a/x-pack/plugins/apm/server/lib/service_map/transform_service_map_responses.ts b/x-pack/plugins/apm/server/lib/service_map/transform_service_map_responses.ts
index 835c00b8df239e..2e394f44b25b10 100644
--- a/x-pack/plugins/apm/server/lib/service_map/transform_service_map_responses.ts
+++ b/x-pack/plugins/apm/server/lib/service_map/transform_service_map_responses.ts
@@ -3,7 +3,7 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
-import { sortBy, pick, identity } from 'lodash';
+import { sortBy, pickBy, identity } from 'lodash';
import { ValuesType } from 'utility-types';
import {
SERVICE_NAME,
@@ -112,7 +112,7 @@ export function transformServiceMapResponses(response: ServiceMapResponse) {
id: matchedServiceNodes[0][SERVICE_NAME],
},
...matchedServiceNodes.map((serviceNode) =>
- pick(serviceNode, identity)
+ pickBy(serviceNode, identity)
)
),
};
diff --git a/x-pack/plugins/apm/server/lib/transaction_groups/transform.ts b/x-pack/plugins/apm/server/lib/transaction_groups/transform.ts
index 81dba39e9d7126..b04ff6764675df 100644
--- a/x-pack/plugins/apm/server/lib/transaction_groups/transform.ts
+++ b/x-pack/plugins/apm/server/lib/transaction_groups/transform.ts
@@ -5,7 +5,7 @@
*/
import moment from 'moment';
-import { sortByOrder } from 'lodash';
+import { orderBy } from 'lodash';
import { ESResponse } from './fetcher';
function calculateRelativeImpacts(items: ITransactionGroup[]) {
@@ -27,7 +27,7 @@ function calculateRelativeImpacts(items: ITransactionGroup[]) {
const getBuckets = (response: ESResponse) => {
if (response.aggregations) {
- return sortByOrder(
+ return orderBy(
response.aggregations.transaction_groups.buckets,
['sum.value'],
['desc']
diff --git a/x-pack/plugins/apm/server/lib/transactions/breakdown/index.ts b/x-pack/plugins/apm/server/lib/transactions/breakdown/index.ts
index 5af8b9f78cec1e..3c48c14c2a4714 100644
--- a/x-pack/plugins/apm/server/lib/transactions/breakdown/index.ts
+++ b/x-pack/plugins/apm/server/lib/transactions/breakdown/index.ts
@@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { flatten, sortByOrder, last } from 'lodash';
+import { flatten, orderBy, last } from 'lodash';
import {
SERVICE_NAME,
SPAN_SUBTYPE,
@@ -138,13 +138,13 @@ export async function getTransactionBreakdown({
};
const visibleKpis = resp.aggregations
- ? sortByOrder(formatBucket(resp.aggregations), 'percentage', 'desc').slice(
+ ? orderBy(formatBucket(resp.aggregations), 'percentage', 'desc').slice(
0,
MAX_KPIS
)
: [];
- const kpis = sortByOrder(visibleKpis, 'name').map((kpi, index) => {
+ const kpis = orderBy(visibleKpis, 'name').map((kpi, index) => {
return {
...kpi,
color: getVizColorForIndex(index),
@@ -186,8 +186,8 @@ export async function getTransactionBreakdown({
// is drawn correctly.
// If we set all values to 0, the chart always displays null values as 0,
// and the chart looks weird.
- const hasAnyValues = lastValues.some((value) => value.y !== null);
- const hasNullValues = lastValues.some((value) => value.y === null);
+ const hasAnyValues = lastValues.some((value) => value?.y !== null);
+ const hasNullValues = lastValues.some((value) => value?.y === null);
if (hasAnyValues && hasNullValues) {
Object.values(updatedSeries).forEach((series) => {
diff --git a/x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data/fetcher.test.ts b/x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data/fetcher.test.ts
index d1f473b485dc32..fb357040f5781d 100644
--- a/x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data/fetcher.test.ts
+++ b/x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data/fetcher.test.ts
@@ -44,7 +44,6 @@ describe('timeseriesFetcher', () => {
apmAgentConfigurationIndex: 'myIndex',
apmCustomLinkIndex: 'myIndex',
},
- dynamicIndexPattern: null as any,
},
});
});
diff --git a/x-pack/plugins/apm/server/lib/ui_filters/local_ui_filters/get_local_filter_query.ts b/x-pack/plugins/apm/server/lib/ui_filters/local_ui_filters/get_local_filter_query.ts
index 5fdd6de06089b4..1cecf14f2eeb8d 100644
--- a/x-pack/plugins/apm/server/lib/ui_filters/local_ui_filters/get_local_filter_query.ts
+++ b/x-pack/plugins/apm/server/lib/ui_filters/local_ui_filters/get_local_filter_query.ts
@@ -5,7 +5,6 @@
*/
import { omit } from 'lodash';
-import { IIndexPattern } from 'src/plugins/data/server';
import { mergeProjection } from '../../../../common/projections/util/merge_projection';
import { Projection } from '../../../../common/projections/typings';
import { UIFilters } from '../../../../typings/ui_filters';
@@ -13,18 +12,16 @@ import { getUiFiltersES } from '../../helpers/convert_ui_filters/get_ui_filters_
import { localUIFilters, LocalUIFilterName } from './config';
export const getLocalFilterQuery = ({
- indexPattern,
uiFilters,
projection,
localUIFilterName,
}: {
- indexPattern: IIndexPattern | undefined;
uiFilters: UIFilters;
projection: Projection;
localUIFilterName: LocalUIFilterName;
}) => {
const field = localUIFilters[localUIFilterName];
- const filter = getUiFiltersES(indexPattern, omit(uiFilters, field.name));
+ const filter = getUiFiltersES(omit(uiFilters, field.name));
const bucketCountAggregation = projection.body.aggs
? {
diff --git a/x-pack/plugins/apm/server/lib/ui_filters/local_ui_filters/index.ts b/x-pack/plugins/apm/server/lib/ui_filters/local_ui_filters/index.ts
index 967314644c246e..588d5c7896db99 100644
--- a/x-pack/plugins/apm/server/lib/ui_filters/local_ui_filters/index.ts
+++ b/x-pack/plugins/apm/server/lib/ui_filters/local_ui_filters/index.ts
@@ -3,7 +3,7 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
-import { cloneDeep, sortByOrder } from 'lodash';
+import { cloneDeep, orderBy } from 'lodash';
import { UIFilters } from '../../../../typings/ui_filters';
import { Projection } from '../../../../common/projections/typings';
import { PromiseReturnType } from '../../../../../observability/typings/common';
@@ -26,7 +26,7 @@ export async function getLocalUIFilters({
uiFilters: UIFilters;
localFilterNames: LocalUIFilterName[];
}) {
- const { client, dynamicIndexPattern } = setup;
+ const { client } = setup;
const projectionWithoutAggs = cloneDeep(projection);
@@ -35,7 +35,6 @@ export async function getLocalUIFilters({
return Promise.all(
localFilterNames.map(async (name) => {
const query = getLocalFilterQuery({
- indexPattern: dynamicIndexPattern,
uiFilters,
projection,
localUIFilterName: name,
@@ -48,7 +47,7 @@ export async function getLocalUIFilters({
return {
...filter,
- options: sortByOrder(
+ options: orderBy(
buckets.map((bucket) => {
return {
name: bucket.key as string,
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 b21f0ea8d32dbe..92f52dd1552d64 100644
--- a/x-pack/plugins/apm/server/routes/create_api/index.ts
+++ b/x-pack/plugins/apm/server/routes/create_api/index.ts
@@ -140,6 +140,7 @@ export function createApi() {
// 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.
+ // @ts-ignore
params: pick(parsedParams, ...Object.keys(params), 'query'),
config,
logger,
diff --git a/x-pack/plugins/apm/server/routes/index_pattern.ts b/x-pack/plugins/apm/server/routes/index_pattern.ts
index 018a14ac766892..18bc2986d40615 100644
--- a/x-pack/plugins/apm/server/routes/index_pattern.ts
+++ b/x-pack/plugins/apm/server/routes/index_pattern.ts
@@ -9,6 +9,8 @@ import { createRoute } from './create_route';
import { setupRequest } from '../lib/helpers/setup_request';
import { getInternalSavedObjectsClient } from '../lib/helpers/get_internal_saved_objects_client';
import { getApmIndexPatternTitle } from '../lib/index_pattern/get_apm_index_pattern_title';
+import { getDynamicIndexPattern } from '../lib/index_pattern/get_dynamic_index_pattern';
+import { getApmIndices } from '../lib/settings/apm_indices/get_apm_indices';
export const staticIndexPatternRoute = createRoute((core) => ({
method: 'POST',
@@ -34,8 +36,17 @@ export const dynamicIndexPatternRoute = createRoute(() => ({
]),
}),
},
- handler: async ({ context, request }) => {
- const { dynamicIndexPattern } = await setupRequest(context, request);
+ handler: async ({ context }) => {
+ const indices = await getApmIndices({
+ config: context.config,
+ savedObjectsClient: context.core.savedObjects.client,
+ });
+
+ const dynamicIndexPattern = await getDynamicIndexPattern({
+ context,
+ indices,
+ });
+
return { dynamicIndexPattern };
},
}));
diff --git a/x-pack/plugins/apm/server/routes/services.ts b/x-pack/plugins/apm/server/routes/services.ts
index 08eba00251e264..74ab717b8de592 100644
--- a/x-pack/plugins/apm/server/routes/services.ts
+++ b/x-pack/plugins/apm/server/routes/services.ts
@@ -6,7 +6,7 @@
import * as t from 'io-ts';
import Boom from 'boom';
-import { unique } from 'lodash';
+import { uniq } from 'lodash';
import { setupRequest } from '../lib/helpers/setup_request';
import { getServiceAgentName } from '../lib/services/get_service_agent_name';
import { getServices } from '../lib/services/get_services';
@@ -160,7 +160,7 @@ export const serviceAnnotationsCreateRoute = createRoute(() => ({
...body.service,
name: path.serviceName,
},
- tags: unique(['apm'].concat(body.tags ?? [])),
+ tags: uniq(['apm'].concat(body.tags ?? [])),
});
},
}));
diff --git a/x-pack/plugins/apm/server/routes/ui_filters.ts b/x-pack/plugins/apm/server/routes/ui_filters.ts
index 280645d4de8d05..a47d72751dfc47 100644
--- a/x-pack/plugins/apm/server/routes/ui_filters.ts
+++ b/x-pack/plugins/apm/server/routes/ui_filters.ts
@@ -97,10 +97,7 @@ function createLocalFiltersRoute<
query,
setup: {
...setup,
- uiFiltersES: getUiFiltersES(
- setup.dynamicIndexPattern,
- omit(parsedUiFilters, filterNames)
- ),
+ uiFiltersES: getUiFiltersES(omit(parsedUiFilters, filterNames)),
},
});
diff --git a/x-pack/plugins/beats_management/public/components/enroll_beats.tsx b/x-pack/plugins/beats_management/public/components/enroll_beats.tsx
index e609cd83587ce5..5bf0f51f48355d 100644
--- a/x-pack/plugins/beats_management/public/components/enroll_beats.tsx
+++ b/x-pack/plugins/beats_management/public/components/enroll_beats.tsx
@@ -18,7 +18,7 @@ import {
EuiTitle,
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
-import { capitalize } from 'lodash';
+import { upperFirst } from 'lodash';
import React from 'react';
import { CMBeat } from '../../../../legacy/plugins/beats_management/common/domain_types';
@@ -93,7 +93,7 @@ export class EnrollBeat extends React.Component
}
const cmdText = `${this.state.command
.replace('{{beatType}}', this.state.beatType)
- .replace('{{beatTypeInCaps}}', capitalize(this.state.beatType))} enroll ${
+ .replace('{{beatTypeInCaps}}', upperFirst(this.state.beatType))} enroll ${
window.location.protocol
}//${window.location.host}${this.props.frameworkBasePath} ${this.props.enrollmentToken}`;
@@ -183,7 +183,7 @@ export class EnrollBeat extends React.Component
id="xpack.beatsManagement.enrollBeat.yourBeatTypeHostTitle"
defaultMessage="On the host where your {beatType} is installed, run:"
values={{
- beatType: capitalize(this.state.beatType),
+ beatType: upperFirst(this.state.beatType),
}}
/>
@@ -220,7 +220,7 @@ export class EnrollBeat extends React.Component
id="xpack.beatsManagement.enrollBeat.waitingBeatTypeToEnrollTitle"
defaultMessage="Waiting for {beatType} to enroll…"
values={{
- beatType: capitalize(this.state.beatType),
+ beatType: upperFirst(this.state.beatType),
}}
/>
diff --git a/x-pack/plugins/beats_management/public/components/navigation/connected_link.tsx b/x-pack/plugins/beats_management/public/components/navigation/connected_link.tsx
index 947e22ee290895..ebac34afa016b9 100644
--- a/x-pack/plugins/beats_management/public/components/navigation/connected_link.tsx
+++ b/x-pack/plugins/beats_management/public/components/navigation/connected_link.tsx
@@ -3,6 +3,8 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
+
+import _ from 'lodash';
import React from 'react';
import { EuiLink } from '@elastic/eui';
diff --git a/x-pack/plugins/beats_management/public/components/table/table_type_configs.tsx b/x-pack/plugins/beats_management/public/components/table/table_type_configs.tsx
index 94e4ca46aec190..6bbf269711fbdc 100644
--- a/x-pack/plugins/beats_management/public/components/table/table_type_configs.tsx
+++ b/x-pack/plugins/beats_management/public/components/table/table_type_configs.tsx
@@ -6,7 +6,7 @@
import { EuiFlexGroup, EuiFlexItem, EuiHealth, EuiToolTip, IconColor } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
-import { sortBy, uniq } from 'lodash';
+import { sortBy, uniqBy } from 'lodash';
import moment from 'moment';
import React from 'react';
import {
@@ -226,7 +226,7 @@ export const BeatsTableType: TableType = {
// render: (tags?: BeatTag[]) =>
// tags && tags.length ? (
//
- // {moment(first(sortByOrder(tags, ['last_updated'], ['desc'])).last_updated).fromNow()}
+ // {moment(first(orderBy(tags, ['last_updated'], ['desc'])).last_updated).fromNow()}
//
// ) : null,
// sortable: true,
@@ -249,7 +249,7 @@ export const BeatsTableType: TableType = {
name: i18n.translate('xpack.beatsManagement.beatsTable.typeLabel', {
defaultMessage: 'Type',
}),
- options: uniq(
+ options: uniqBy(
data.map(({ type }: { type: any }) => ({ value: type })),
'value'
),
diff --git a/x-pack/plugins/beats_management/public/lib/adapters/beats/memory_beats_adapter.ts b/x-pack/plugins/beats_management/public/lib/adapters/beats/memory_beats_adapter.ts
index 8e3f58b18f3918..24a7e5c3af8fa8 100644
--- a/x-pack/plugins/beats_management/public/lib/adapters/beats/memory_beats_adapter.ts
+++ b/x-pack/plugins/beats_management/public/lib/adapters/beats/memory_beats_adapter.ts
@@ -32,14 +32,14 @@ export class MemoryBeatsAdapter implements CMBeatsAdapter {
}
public async getAll() {
- return this.beatsDB.map((beat: any) => omit(beat, ['access_token']));
+ return this.beatsDB.map((beat: any) => omit(beat, ['access_token'])) as CMBeat[];
}
public async getBeatsWithTag(tagId: string): Promise {
- return this.beatsDB.map((beat: any) => omit(beat, ['access_token']));
+ return this.beatsDB.map((beat: any) => omit(beat, ['access_token'])) as CMBeat[];
}
public async getBeatWithToken(enrollmentToken: string): Promise {
- return this.beatsDB.map((beat: any) => omit(beat, ['access_token']))[0];
+ return this.beatsDB.map((beat: any) => omit(beat, ['access_token']))[0] as CMBeat | null;
}
public async removeTagsFromBeats(
removals: BeatsTagAssignment[]
@@ -66,11 +66,11 @@ export class MemoryBeatsAdapter implements CMBeatsAdapter {
return beat;
});
- return response.map((item: CMBeat, resultIdx: number) => ({
+ return response.map((item: CMBeat, resultIdx: number) => ({
idxInRequest: removals[resultIdx].idxInRequest,
result: 'updated',
status: 200,
- }));
+ })) as any;
}
public async assignTagsToBeats(
diff --git a/x-pack/plugins/beats_management/public/lib/framework.ts b/x-pack/plugins/beats_management/public/lib/framework.ts
index 9e4271c683415d..63a81e0895348a 100644
--- a/x-pack/plugins/beats_management/public/lib/framework.ts
+++ b/x-pack/plugins/beats_management/public/lib/framework.ts
@@ -58,6 +58,6 @@ export class FrameworkLib {
public currentUserHasOneOfRoles(roles: string[]) {
// If the user has at least one of the roles requested, the returnd difference will be less
// then the orig array size. difference only compares based on the left side arg
- return difference(roles, get(this.currentUser, 'roles', [])).length < roles.length;
+ return difference(roles, get(this.currentUser, 'roles', []) as string[]).length < roles.length;
}
}
diff --git a/x-pack/plugins/canvas/.storybook/webpack.config.js b/x-pack/plugins/canvas/.storybook/webpack.config.js
index 45a5303d8b0db1..3148a6742f76a4 100644
--- a/x-pack/plugins/canvas/.storybook/webpack.config.js
+++ b/x-pack/plugins/canvas/.storybook/webpack.config.js
@@ -80,7 +80,7 @@ module.exports = async ({ config }) => {
prependData(loaderContext) {
return `@import ${stringifyRequest(
loaderContext,
- path.resolve(KIBANA_ROOT, 'src/legacy/ui/public/styles/_styling_constants.scss')
+ path.resolve(KIBANA_ROOT, 'src/legacy/ui/public/styles/_globals_v7light.scss')
)};\n`;
},
sassOptions: {
@@ -199,7 +199,6 @@ module.exports = async ({ config }) => {
config.resolve.alias['ui/url/absolute_to_parsed_url'] = path.resolve(__dirname, '../tasks/mocks/uiAbsoluteToParsedUrl');
config.resolve.alias['ui/chrome'] = path.resolve(__dirname, '../tasks/mocks/uiChrome');
config.resolve.alias.ui = path.resolve(KIBANA_ROOT, 'src/legacy/ui/public');
- config.resolve.alias['src/legacy/ui/public/styles/styling_constants'] = path.resolve(KIBANA_ROOT, 'src/legacy/ui/public/styles/_styling_constants.scss');
config.resolve.alias.ng_mock$ = path.resolve(KIBANA_ROOT, 'src/test_utils/public/ng_mock');
return config;
diff --git a/x-pack/plugins/canvas/.storybook/webpack.dll.config.js b/x-pack/plugins/canvas/.storybook/webpack.dll.config.js
index 0a648e861b386d..5fdc4519f3bd77 100644
--- a/x-pack/plugins/canvas/.storybook/webpack.dll.config.js
+++ b/x-pack/plugins/canvas/.storybook/webpack.dll.config.js
@@ -39,7 +39,6 @@ module.exports = {
'highlight.js',
'html-entities',
'jquery',
- 'lodash.clone',
'lodash',
'markdown-it',
'mocha',
diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/pie.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/pie.ts
index b568f18924869f..c32c553fffc1bc 100644
--- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/pie.ts
+++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/pie.ts
@@ -4,9 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { get, map, groupBy } from 'lodash';
-// @ts-expect-error lodash.keyby imports invalid member from @types/lodash
-import keyBy from 'lodash.keyby';
+import { get, keyBy, map, groupBy } from 'lodash';
// @ts-expect-error untyped local
import { getColorsFromPalette } from '../../../common/lib/get_colors_from_palette';
// @ts-expect-error untyped local
diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/plot/get_tick_hash.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/plot/get_tick_hash.ts
index 4839db047c8713..21166454e478ff 100644
--- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/plot/get_tick_hash.ts
+++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/plot/get_tick_hash.ts
@@ -20,11 +20,13 @@ export const getTickHash = (columns: PointSeriesColumns, rows: DatatableRow[]) =
};
if (get(columns, 'x.type') === 'string') {
- sortBy(rows, ['x']).forEach((row) => {
- if (!ticks.x.hash[row.x]) {
- ticks.x.hash[row.x] = ticks.x.counter++;
- }
- });
+ sortBy(rows, ['x'])
+ .reverse()
+ .forEach((row) => {
+ if (!ticks.x.hash[row.x]) {
+ ticks.x.hash[row.x] = ticks.x.counter++;
+ }
+ });
}
if (get(columns, 'y.type') === 'string') {
diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/plot/index.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/plot/index.ts
index 0b4583f4581aea..4ffd2ff3e0c968 100644
--- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/plot/index.ts
+++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/plot/index.ts
@@ -4,9 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-// @ts-expect-error no @typed def
-import keyBy from 'lodash.keyby';
-import { groupBy, get, set, map, sortBy } from 'lodash';
+import { groupBy, get, keyBy, set, map, sortBy } from 'lodash';
import { ExpressionFunctionDefinition, Style } from 'src/plugins/expressions';
// @ts-expect-error untyped local
import { getColorsFromPalette } from '../../../../common/lib/get_colors_from_palette';
diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/plot/series_style_to_flot.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/plot/series_style_to_flot.ts
index 6fbaee8736a505..e4b710240de195 100644
--- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/plot/series_style_to_flot.ts
+++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/plot/series_style_to_flot.ts
@@ -12,12 +12,12 @@ export const seriesStyleToFlot = (seriesStyle: SeriesStyle) => {
return {};
}
- const lines = get(seriesStyle, 'lines');
- const bars = get(seriesStyle, 'bars');
- const fill = get(seriesStyle, 'fill');
- const color = get(seriesStyle, 'color');
- const stack = get(seriesStyle, 'stack');
- const horizontal = get(seriesStyle, 'horizontalBars', false);
+ const lines = get(seriesStyle, 'lines');
+ const bars = get(seriesStyle, 'bars');
+ const fill = get(seriesStyle, 'fill');
+ const color = get(seriesStyle, 'color');
+ const stack = get(seriesStyle, 'stack');
+ const horizontal = get(seriesStyle, 'horizontalBars', false);
const flotStyle = {
numbers: {
diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/server/pointseries/index.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/server/pointseries/index.ts
index bae80d3c335104..f79f189f363d4b 100644
--- a/x-pack/plugins/canvas/canvas_plugin_src/functions/server/pointseries/index.ts
+++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/server/pointseries/index.ts
@@ -4,11 +4,9 @@
* you may not use this file except in compliance with the Elastic License.
*/
-// @ts-expect-error untyped library
-import uniqBy from 'lodash.uniqby';
-// @ts-expect-error untyped Elastic library
+// @ts-expect-error Untyped Elastic library
import { evaluate } from 'tinymath';
-import { groupBy, zipObject, omit } from 'lodash';
+import { groupBy, zipObject, omit, uniqBy } from 'lodash';
import moment from 'moment';
import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common';
import {
diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/time_filter.tsx b/x-pack/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/time_filter.tsx
index 8d28287b320667..487f17fb89d120 100644
--- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/time_filter.tsx
+++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/time_filter.tsx
@@ -34,9 +34,9 @@ export interface FilterMeta {
function getFilterMeta(filter: string): FilterMeta {
const ast = fromExpression(filter);
- const column = get(ast, 'chain[0].arguments.column[0]');
- const start = get(ast, 'chain[0].arguments.from[0]');
- const end = get(ast, 'chain[0].arguments.to[0]');
+ const column = get(ast, 'chain[0].arguments.column[0]') as string;
+ const start = get(ast, 'chain[0].arguments.from[0]') as string;
+ const end = get(ast, 'chain[0].arguments.to[0]') as string;
return { column, start, end };
}
diff --git a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/palette.tsx b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/palette.tsx
index a33d000a1f6563..8ae61f7197ee89 100644
--- a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/palette.tsx
+++ b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/palette.tsx
@@ -40,7 +40,7 @@ export const PaletteArgInput: FC = ({ onValueChange, argId, argValue, ren
return astObj;
}) as string[];
- const gradient = get(chain[0].arguments.gradient, '[0]');
+ const gradient = get(chain[0].arguments.gradient, '[0]') as boolean;
const palette = identifyPalette({ colors, gradient });
if (palette) {
diff --git a/x-pack/plugins/canvas/canvas_plugin_src/uis/views/plot.js b/x-pack/plugins/canvas/canvas_plugin_src/uis/views/plot.js
index 1449bddf322b53..05ecf467a1d351 100644
--- a/x-pack/plugins/canvas/canvas_plugin_src/uis/views/plot.js
+++ b/x-pack/plugins/canvas/canvas_plugin_src/uis/views/plot.js
@@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { map, uniq } from 'lodash';
+import { map, uniqBy } from 'lodash';
import { getState, getValue } from '../../../public/lib/resolved_arg';
import { legendOptions } from '../../../public/lib/legend_options';
import { ViewStrings } from '../../../i18n';
@@ -72,6 +72,6 @@ export const plot = () => ({
if (getState(context) !== 'ready') {
return { labels: [] };
}
- return { labels: uniq(map(getValue(context).rows, 'color').filter((v) => v !== undefined)) };
+ return { labels: uniqBy(map(getValue(context).rows, 'color').filter((v) => v !== undefined)) };
},
});
diff --git a/x-pack/plugins/canvas/common/lib/pivot_object_array.ts b/x-pack/plugins/canvas/common/lib/pivot_object_array.ts
index c098b7772ef111..2bc52fb0eaafc3 100644
--- a/x-pack/plugins/canvas/common/lib/pivot_object_array.ts
+++ b/x-pack/plugins/canvas/common/lib/pivot_object_array.ts
@@ -11,10 +11,7 @@ const isString = (val: any): boolean => typeof val === 'string';
export function pivotObjectArray<
RowType extends { [key: string]: any },
ReturnColumns extends string | number | symbol = keyof RowType
->(
- rows: RowType[],
- columns?: string[]
-): { [Column in ReturnColumns]: Column extends keyof RowType ? Array : never } {
+>(rows: RowType[], columns?: string[]): Record {
const columnNames = columns || Object.keys(rows[0]);
if (!columnNames.every(isString)) {
throw new Error('Columns should be an array of strings');
diff --git a/x-pack/plugins/canvas/public/components/enhance/error_boundary.tsx b/x-pack/plugins/canvas/public/components/enhance/error_boundary.tsx
index 134efe61c9dcb9..c0ed14965cbd39 100644
--- a/x-pack/plugins/canvas/public/components/enhance/error_boundary.tsx
+++ b/x-pack/plugins/canvas/public/components/enhance/error_boundary.tsx
@@ -4,38 +4,27 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import React, { Fragment, FunctionComponent, ReactChildren } from 'react';
+import React, { ErrorInfo, FC, ReactElement } from 'react';
import { withState, withHandlers, lifecycle, mapProps, compose } from 'recompose';
import PropTypes from 'prop-types';
import { omit } from 'lodash';
-type ResetErrorState = ({
- setError,
- setErrorInfo,
-}: {
- setError: Function;
- setErrorInfo: Function;
-}) => void;
-
interface Props {
- error: Error;
- errorInfo: any;
- resetErrorState: ResetErrorState;
+ error?: Error;
+ errorInfo?: ErrorInfo;
+ resetErrorState: (state: { error: Error; errorInfo: ErrorInfo }) => void;
+ setError: (error: Error | null) => void;
+ setErrorInfo: (info: ErrorInfo | null) => void;
+ children: (props: ChildrenProps) => ReactElement | null;
}
-interface ComponentProps extends Props {
- children: (props: Props) => ReactChildren;
-}
+type ComponentProps = Pick;
+type ChildrenProps = Omit;
-const ErrorBoundaryComponent: FunctionComponent = (props) => (
-
- {props.children({
- error: props.error,
- errorInfo: props.errorInfo,
- resetErrorState: props.resetErrorState,
- })}
-
-);
+const ErrorBoundaryComponent: FC = (props) => {
+ const { children, ...rest } = props;
+ return <>{children(rest)}>;
+};
ErrorBoundaryComponent.propTypes = {
children: PropTypes.func.isRequired,
@@ -44,33 +33,22 @@ ErrorBoundaryComponent.propTypes = {
resetErrorState: PropTypes.func.isRequired,
};
-interface HOCProps {
- setError: Function;
- setErrorInfo: Function;
-}
-
-interface HandlerProps {
- resetErrorState: ResetErrorState;
-}
-
-export const errorBoundaryHoc = compose(
+export const errorBoundaryHoc = compose>(
withState('error', 'setError', null),
withState('errorInfo', 'setErrorInfo', null),
- withHandlers({
+ withHandlers, Pick>({
resetErrorState: ({ setError, setErrorInfo }) => () => {
setError(null);
setErrorInfo(null);
},
}),
- lifecycle