-
+ {field ? (
+
+ ) : (
+
+ )}
|
{isCollapsible && (
)}
{displayUnderscoreWarning && }
+ {field ? null : {key}: }
void;
+ /**
+ * Counter how often data was fetched (used for testing)
+ */
+ fetchCounter: number;
+ /**
+ * Error in case of a failing document fetch
+ */
+ fetchError?: Error;
+ /**
+ * Statistics by fields calculated using the fetched documents
+ */
+ fieldCounts: Record ;
+ /**
+ * Histogram aggregation data
+ */
+ histogramData?: Chart;
+ /**
+ * Number of documents found by recent fetch
+ */
+ hits: number;
+ /**
+ * Current IndexPattern
+ */
+ indexPattern: IndexPattern;
+ /**
+ * Value needed for legacy "infinite" loading functionality
+ * Determins how much records are rendered using the legacy table
+ * Increased when scrolling down
+ */
+ minimumVisibleRows: number;
+ /**
+ * Function to add a column to state
+ */
+ onAddColumn: (column: string) => void;
+ /**
+ * Function to add a filter to state
+ */
+ onAddFilter: DocViewFilterFn;
+ /**
+ * Function to change the used time interval of the date histogram
+ */
+ onChangeInterval: (interval: string) => void;
+ /**
+ * Function to move a given column to a given index, used in legacy table
+ */
+ onMoveColumn: (columns: string, newIdx: number) => void;
+ /**
+ * Function to remove a given column from state
+ */
+ onRemoveColumn: (column: string) => void;
+ /**
+ * Function to replace columns in state
+ */
+ onSetColumns: (columns: string[]) => void;
+ /**
+ * Function to scroll down the legacy table to the bottom
+ */
+ onSkipBottomButtonClick: () => void;
+ /**
+ * Function to change sorting of the table, triggers a fetch
+ */
+ onSort: (sort: string[][]) => void;
+ opts: {
+ /**
+ * Date histogram aggregation config
+ */
+ chartAggConfigs?: AggConfigs;
+ /**
+ * Client of uiSettings
+ */
+ config: IUiSettingsClient;
+ /**
+ * Data plugin
+ */
+ data: DataPublicPluginStart;
+ /**
+ * Data plugin filter manager
+ */
+ filterManager: FilterManager;
+ /**
+ * List of available index patterns
+ */
+ indexPatternList: Array>;
+ /**
+ * The number of documents that can be displayed in the table/grid
+ */
+ sampleSize: number;
+ /**
+ * Current instance of SavedSearch
+ */
+ savedSearch: SavedSearch;
+ /**
+ * Function to set the header menu
+ */
+ setHeaderActionMenu: (menuMount: MountPoint | undefined) => void;
+ /**
+ * Timefield of the currently used index pattern
+ */
+ timefield: string;
+ /**
+ * Function to set the current state
+ */
+ setAppState: (state: Partial) => void;
+ };
+ /**
+ * Function to reset the current query
+ */
+ resetQuery: () => void;
+ /**
+ * Current state of the actual query, one of 'uninitialized', 'loading' ,'ready', 'none'
+ */
+ resultState: string;
+ /**
+ * Array of document of the recent successful search request
+ */
+ rows: ElasticSearchHit[];
+ /**
+ * Instance of SearchSource, the high level search API
+ */
+ searchSource: ISearchSource;
+ /**
+ * Function to change the current index pattern
+ */
+ setIndexPattern: (id: string) => void;
+ /**
+ * Current app state of URL
+ */
+ state: AppState;
+ /**
+ * Function to update the time filter
+ */
+ timefilterUpdateHandler: (ranges: { from: number; to: number }) => void;
+ /**
+ * Currently selected time range
+ */
+ timeRange?: { from: string; to: string };
+ /**
+ * Menu data of top navigation (New, save ...)
+ */
+ topNavMenu: TopNavMenuData[];
+ /**
+ * Function to update the actual query
+ */
+ updateQuery: (payload: { dateRange: TimeRange; query?: Query }, isUpdate?: boolean) => void;
+ /**
+ * Function to update the actual savedQuery id
+ */
+ updateSavedQueryId: (savedQueryId?: string) => void;
+}
diff --git a/src/plugins/discover/public/application/helpers/columns.test.ts b/src/plugins/discover/public/application/helpers/columns.test.ts
new file mode 100644
index 0000000000000..d455fd1f42c6d
--- /dev/null
+++ b/src/plugins/discover/public/application/helpers/columns.test.ts
@@ -0,0 +1,46 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * and the Server Side Public License, v 1; you may not use this file except in
+ * compliance with, at your election, the Elastic License or the Server Side
+ * Public License, v 1.
+ */
+
+import { getDisplayedColumns } from './columns';
+import { indexPatternWithTimefieldMock } from '../../__mocks__/index_pattern_with_timefield';
+import { indexPatternMock } from '../../__mocks__/index_pattern';
+
+describe('getDisplayedColumns', () => {
+ test('returns default columns given a index pattern without timefield', async () => {
+ const result = getDisplayedColumns([], indexPatternMock);
+ expect(result).toMatchInlineSnapshot(`
+ Array [
+ "_source",
+ ]
+ `);
+ });
+ test('returns default columns given a index pattern with timefield', async () => {
+ const result = getDisplayedColumns([], indexPatternWithTimefieldMock);
+ expect(result).toMatchInlineSnapshot(`
+ Array [
+ "_source",
+ ]
+ `);
+ });
+ test('returns default columns when just timefield is in state', async () => {
+ const result = getDisplayedColumns(['timestamp'], indexPatternWithTimefieldMock);
+ expect(result).toMatchInlineSnapshot(`
+ Array [
+ "_source",
+ ]
+ `);
+ });
+ test('returns columns given by argument, no fallback ', async () => {
+ const result = getDisplayedColumns(['test'], indexPatternWithTimefieldMock);
+ expect(result).toMatchInlineSnapshot(`
+ Array [
+ "test",
+ ]
+ `);
+ });
+});
diff --git a/src/plugins/discover/public/application/helpers/columns.ts b/src/plugins/discover/public/application/helpers/columns.ts
new file mode 100644
index 0000000000000..d2d47c932b7bd
--- /dev/null
+++ b/src/plugins/discover/public/application/helpers/columns.ts
@@ -0,0 +1,22 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * and the Server Side Public License, v 1; you may not use this file except in
+ * compliance with, at your election, the Elastic License or the Server Side
+ * Public License, v 1.
+ */
+import { IndexPattern } from '../../../../data/common';
+
+/**
+ * Function to provide fallback when
+ * 1) no columns are given
+ * 2) Just one column is given, which is the configured timefields
+ */
+export function getDisplayedColumns(stateColumns: string[] = [], indexPattern: IndexPattern) {
+ return stateColumns &&
+ stateColumns.length > 0 &&
+ // check if all columns where removed except the configured timeField (this can't be removed)
+ !(stateColumns.length === 1 && stateColumns[0] === indexPattern.timeFieldName)
+ ? stateColumns
+ : ['_source'];
+}
diff --git a/src/plugins/discover/public/get_inner_angular.ts b/src/plugins/discover/public/get_inner_angular.ts
index b27426a6c0621..4eda742d967f4 100644
--- a/src/plugins/discover/public/get_inner_angular.ts
+++ b/src/plugins/discover/public/get_inner_angular.ts
@@ -42,7 +42,6 @@ import {
} from '../../kibana_legacy/public';
import { DiscoverStartPlugins } from './plugin';
import { getScopedHistory } from './kibana_services';
-import { createDiscoverLegacyDirective } from './application/components/create_discover_legacy_directive';
import { createDiscoverDirective } from './application/components/create_discover_directive';
/**
@@ -124,7 +123,6 @@ export function initializeInnerAngularModule(
.config(watchMultiDecorator)
.run(registerListenEventListener)
.directive('renderComplete', createRenderCompleteDirective)
- .directive('discoverLegacy', createDiscoverLegacyDirective)
.directive('discover', createDiscoverDirective);
}
diff --git a/src/plugins/kibana_usage_collection/README.md b/src/plugins/kibana_usage_collection/README.md
index 69711d30cdc74..85d362cf0a9b1 100644
--- a/src/plugins/kibana_usage_collection/README.md
+++ b/src/plugins/kibana_usage_collection/README.md
@@ -6,6 +6,6 @@ This plugin registers the basic usage collectors from Kibana:
- UI Metrics
- Ops stats
- Number of Saved Objects per type
-- Non-default UI Settings
+- [User-changed UI Settings](./server/collectors/management/README.md)
- CSP configuration
- Core Metrics
diff --git a/src/plugins/kibana_usage_collection/common/constants.ts b/src/plugins/kibana_usage_collection/common/constants.ts
index 4505c59e0f630..052367765a6ec 100644
--- a/src/plugins/kibana_usage_collection/common/constants.ts
+++ b/src/plugins/kibana_usage_collection/common/constants.ts
@@ -13,3 +13,7 @@ export const PLUGIN_NAME = 'kibana_usage_collection';
* The type name used to publish Kibana usage stats in the formatted as bulk.
*/
export const KIBANA_STATS_TYPE = 'kibana_stats';
+/**
+ * Redacted keyword; used as a value for sensitive ui settings
+ */
+export const REDACTED_KEYWORD = '[REDACTED]';
diff --git a/src/plugins/kibana_usage_collection/server/collectors/management/README.md b/src/plugins/kibana_usage_collection/server/collectors/management/README.md
new file mode 100644
index 0000000000000..b539136d57b89
--- /dev/null
+++ b/src/plugins/kibana_usage_collection/server/collectors/management/README.md
@@ -0,0 +1,51 @@
+# User-changed UI Settings - Management Collector
+
+The Usage Collector `stack_management` reports user changed settings.
+All user changed UI Settings are automatically collected.
+
+After adding a new setting you will be required to do the following steps:
+
+1. Update the [schema](./schema.ts) to include the setting name and schema type.
+```
+export const stackManagementSchema: MakeSchemaFrom = {
+ 'MY_UI_SETTING': { type: 'keyword' },
+}
+```
+
+2. Update the [UsageStats interface](./types.ts) with the setting name and typescript type.
+```
+export interface UsageStats {
+ 'MY_UI_SETTING': string;
+}
+```
+3. Run the telemetry checker with `--fix` flag to automatically fix the mappings
+
+```
+node scripts/telemetry_check --fix
+```
+
+If you forget any of the steps our telemetry tools and tests will help you through the process!
+
+## Sensitive fields
+
+If the configured UI setting might contain user sensitive information simply add the property `sensitive: true` to the ui setting registration config.
+
+```
+uiSettings.register({
+ [NEWS_FEED_URL_SETTING]: {
+ name: i18n.translate('xpack.securitySolution.uiSettings.newsFeedUrl', {
+ defaultMessage: 'News feed URL',
+ }),
+ value: NEWS_FEED_URL_SETTING_DEFAULT,
+ sensitive: true,
+ description: i18n.translate('xpack.securitySolution.uiSettings.newsFeedUrlDescription', {
+ defaultMessage: 'News feed content will be retrieved from this URL ',
+ }),
+ category: [APP_ID],
+ requiresPageReload: true,
+ schema: schema.string(),
+ },
+}),
+```
+
+The value of any UI setting marked as `sensitive` will be reported as a keyword `[REDACTED]` instead of the actual value. This hides the actual sensitive information while giving us some intelligence over which fields the users are interactive with the most.
diff --git a/src/plugins/kibana_usage_collection/server/collectors/management/__snapshots__/index.test.ts.snap b/src/plugins/kibana_usage_collection/server/collectors/management/__snapshots__/index.test.ts.snap
deleted file mode 100644
index def230dea8d70..0000000000000
--- a/src/plugins/kibana_usage_collection/server/collectors/management/__snapshots__/index.test.ts.snap
+++ /dev/null
@@ -1,7 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`telemetry_application_usage_collector fetch() 1`] = `
-Object {
- "my-key": "my-value",
-}
-`;
diff --git a/src/plugins/kibana_usage_collection/server/collectors/management/index.test.ts b/src/plugins/kibana_usage_collection/server/collectors/management/index.test.ts
deleted file mode 100644
index 38baf02d6fe1b..0000000000000
--- a/src/plugins/kibana_usage_collection/server/collectors/management/index.test.ts
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * and the Server Side Public License, v 1; you may not use this file except in
- * compliance with, at your election, the Elastic License or the Server Side
- * Public License, v 1.
- */
-
-import { loggingSystemMock, uiSettingsServiceMock } from '../../../../../core/server/mocks';
-import {
- Collector,
- createUsageCollectionSetupMock,
- createCollectorFetchContextMock,
-} from '../../../../usage_collection/server/usage_collection.mock';
-
-import { registerManagementUsageCollector } from './';
-
-const logger = loggingSystemMock.createLogger();
-
-describe('telemetry_application_usage_collector', () => {
- let collector: Collector;
-
- const usageCollectionMock = createUsageCollectionSetupMock();
- usageCollectionMock.makeUsageCollector.mockImplementation((config) => {
- collector = new Collector(logger, config);
- return createUsageCollectionSetupMock().makeUsageCollector(config);
- });
-
- const uiSettingsClient = uiSettingsServiceMock.createClient();
- const getUiSettingsClient = jest.fn(() => uiSettingsClient);
- const mockedFetchContext = createCollectorFetchContextMock();
-
- beforeAll(() => {
- registerManagementUsageCollector(usageCollectionMock, getUiSettingsClient);
- });
-
- test('registered collector is set', () => {
- expect(collector).not.toBeUndefined();
- });
-
- test('isReady() => false if no client', () => {
- getUiSettingsClient.mockImplementationOnce(() => undefined as any);
- expect(collector.isReady()).toBe(false);
- });
-
- test('isReady() => true', () => {
- expect(collector.isReady()).toBe(true);
- });
-
- test('fetch()', async () => {
- uiSettingsClient.getUserProvided.mockImplementationOnce(async () => ({
- 'my-key': { userValue: 'my-value' },
- }));
- await expect(collector.fetch(mockedFetchContext)).resolves.toMatchSnapshot();
- });
-
- test('fetch() should not fail if invoked when not ready', async () => {
- getUiSettingsClient.mockImplementationOnce(() => undefined as any);
- await expect(collector.fetch(mockedFetchContext)).resolves.toBe(undefined);
- });
-});
diff --git a/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts b/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts
index 28eeb461f7a86..b644f282c1f36 100644
--- a/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts
+++ b/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts
@@ -7,18 +7,25 @@
*/
import { MakeSchemaFrom } from 'src/plugins/usage_collection/server';
-import { UsageStats } from './telemetry_management_collector';
+import { UsageStats } from './types';
-// Retrieved by changing all the current settings in Kibana (we'll need to revisit it in the future).
-// I would suggest we use flattened type for the mappings of this collector.
export const stackManagementSchema: MakeSchemaFrom = {
+ // sensitive
+ 'timelion:quandl.key': { type: 'keyword' },
+ 'securitySolution:defaultIndex': { type: 'keyword' },
+ 'securitySolution:newsFeedUrl': { type: 'keyword' },
+ 'xpackReporting:customPdfLogo': { type: 'keyword' },
+ 'notifications:banner': { type: 'keyword' },
+ 'timelion:graphite.url': { type: 'keyword' },
+ 'xpackDashboardMode:roles': { type: 'keyword' },
+ 'securitySolution:ipReputationLinks': { type: 'keyword' },
+ // non-sensitive
'visualize:enableLabs': { type: 'boolean' },
'visualization:heatmap:maxBuckets': { type: 'long' },
'visualization:colorMapping': { type: 'text' },
'visualization:regionmap:showWarnings': { type: 'boolean' },
'visualization:dimmingOpacity': { type: 'float' },
'visualization:tileMap:maxPrecision': { type: 'long' },
- 'securitySolution:ipReputationLinks': { type: 'text' },
'csv:separator': { type: 'keyword' },
'visualization:tileMap:WMSdefaults': { type: 'text' },
'timelion:target_buckets': { type: 'long' },
@@ -27,14 +34,11 @@ export const stackManagementSchema: MakeSchemaFrom = {
'timelion:min_interval': { type: 'keyword' },
'timelion:default_rows': { type: 'long' },
'timelion:default_columns': { type: 'long' },
- 'timelion:quandl.key': { type: 'keyword' },
'timelion:es.default_index': { type: 'keyword' },
'timelion:showTutorial': { type: 'boolean' },
'securitySolution:timeDefaults': { type: 'keyword' },
'securitySolution:defaultAnomalyScore': { type: 'long' },
- 'securitySolution:defaultIndex': { type: 'keyword' }, // it's an array
'securitySolution:refreshIntervalDefaults': { type: 'keyword' },
- 'securitySolution:newsFeedUrl': { type: 'keyword' },
'securitySolution:enableNewsFeed': { type: 'boolean' },
'search:includeFrozen': { type: 'boolean' },
'courier:maxConcurrentShardRequests': { type: 'long' },
@@ -43,21 +47,29 @@ export const stackManagementSchema: MakeSchemaFrom = {
'courier:customRequestPreference': { type: 'keyword' },
'courier:ignoreFilterIfFieldNotInIndex': { type: 'boolean' },
'rollups:enableIndexPatterns': { type: 'boolean' },
- 'xpackReporting:customPdfLogo': { type: 'text' },
'notifications:lifetime:warning': { type: 'long' },
'notifications:lifetime:banner': { type: 'long' },
'notifications:lifetime:info': { type: 'long' },
- 'notifications:banner': { type: 'text' },
'notifications:lifetime:error': { type: 'long' },
'doc_table:highlight': { type: 'boolean' },
'discover:searchOnPageLoad': { type: 'boolean' },
// eslint-disable-next-line @typescript-eslint/naming-convention
'doc_table:hideTimeColumn': { type: 'boolean' },
'discover:sampleSize': { type: 'long' },
- defaultColumns: { type: 'keyword' }, // it's an array
+ defaultColumns: {
+ type: 'array',
+ items: {
+ type: 'keyword',
+ },
+ },
'context:defaultSize': { type: 'long' },
'discover:aggs:terms:size': { type: 'long' },
- 'context:tieBreakerFields': { type: 'keyword' }, // it's an array
+ 'context:tieBreakerFields': {
+ type: 'array',
+ items: {
+ type: 'keyword',
+ },
+ },
'discover:sort:defaultOrder': { type: 'keyword' },
'context:step': { type: 'long' },
'accessibility:disableAnimations': { type: 'boolean' },
@@ -79,7 +91,12 @@ export const stackManagementSchema: MakeSchemaFrom = {
'query:queryString:options': { type: 'keyword' },
'metrics:max_buckets': { type: 'long' },
'query:allowLeadingWildcards': { type: 'boolean' },
- metaFields: { type: 'keyword' }, // it's an array
+ metaFields: {
+ type: 'array',
+ items: {
+ type: 'keyword',
+ },
+ },
'indexPattern:placeholder': { type: 'keyword' },
'histogram:barTarget': { type: 'long' },
'histogram:maxBars': { type: 'long' },
@@ -101,4 +118,14 @@ export const stackManagementSchema: MakeSchemaFrom = {
'csv:quoteValues': { type: 'boolean' },
'dateFormat:dow': { type: 'keyword' },
dateFormat: { type: 'keyword' },
+ 'autocomplete:useTimeRange': { type: 'boolean' },
+ 'search:timeout': { type: 'long' },
+ 'visualization:visualize:legacyChartsLibrary': { type: 'boolean' },
+ 'doc_table:legacy': { type: 'boolean' },
+ 'discover:modifyColumnsOnSwitch': { type: 'boolean' },
+ 'discover:searchFieldsFromSource': { type: 'boolean' },
+ 'securitySolution:rulesTableRefresh': { type: 'text' },
+ 'apm:enableSignificantTerms': { type: 'boolean' },
+ 'apm:enableServiceOverview': { type: 'boolean' },
+ 'apm:enableCorrelations': { type: 'boolean' },
};
diff --git a/src/plugins/kibana_usage_collection/server/collectors/management/telemetry_management_collector.test.ts b/src/plugins/kibana_usage_collection/server/collectors/management/telemetry_management_collector.test.ts
new file mode 100644
index 0000000000000..4bcd98f894e2a
--- /dev/null
+++ b/src/plugins/kibana_usage_collection/server/collectors/management/telemetry_management_collector.test.ts
@@ -0,0 +1,140 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * and the Server Side Public License, v 1; you may not use this file except in
+ * compliance with, at your election, the Elastic License or the Server Side
+ * Public License, v 1.
+ */
+
+import { loggingSystemMock, uiSettingsServiceMock } from '../../../../../core/server/mocks';
+import {
+ Collector,
+ createUsageCollectionSetupMock,
+ createCollectorFetchContextMock,
+} from '../../../../usage_collection/server/usage_collection.mock';
+
+import {
+ registerManagementUsageCollector,
+ createCollectorFetch,
+} from './telemetry_management_collector';
+
+const logger = loggingSystemMock.createLogger();
+
+describe('telemetry_application_usage_collector', () => {
+ let collector: Collector;
+
+ const usageCollectionMock = createUsageCollectionSetupMock();
+ usageCollectionMock.makeUsageCollector.mockImplementation((config) => {
+ collector = new Collector(logger, config);
+ return createUsageCollectionSetupMock().makeUsageCollector(config);
+ });
+
+ const uiSettingsClient = uiSettingsServiceMock.createClient();
+ const getUiSettingsClient = jest.fn(() => uiSettingsClient);
+ const mockedFetchContext = createCollectorFetchContextMock();
+
+ beforeAll(() => {
+ registerManagementUsageCollector(usageCollectionMock, getUiSettingsClient);
+ });
+
+ test('registered collector is set', () => {
+ expect(collector).not.toBeUndefined();
+ });
+
+ test('isReady() => false if no client', () => {
+ getUiSettingsClient.mockImplementationOnce(() => undefined as any);
+ expect(collector.isReady()).toBe(false);
+ });
+
+ test('isReady() => true', () => {
+ expect(collector.isReady()).toBe(true);
+ });
+
+ test('fetch()', async () => {
+ uiSettingsClient.getUserProvided.mockImplementationOnce(async () => ({
+ 'visualization:colorMapping': { userValue: 'red' },
+ }));
+ await expect(collector.fetch(mockedFetchContext)).resolves.toEqual({
+ 'visualization:colorMapping': 'red',
+ });
+ });
+
+ test('fetch() should not fail if invoked when not ready', async () => {
+ getUiSettingsClient.mockImplementationOnce(() => undefined as any);
+ await expect(collector.fetch(mockedFetchContext)).resolves.toBe(undefined);
+ });
+});
+
+describe('createCollectorFetch', () => {
+ const mockUserSettings = {
+ item1: { userValue: 'test' },
+ item2: { userValue: 123 },
+ item3: { userValue: false },
+ };
+
+ const mockIsSensitive = (key: string) => {
+ switch (key) {
+ case 'item1':
+ case 'item2':
+ return false;
+ case 'item3':
+ return true;
+ default:
+ throw new Error(`Unexpected ui setting: ${key}`);
+ }
+ };
+
+ it('returns #fetchUsageStats function', () => {
+ const getUiSettingsClient = jest.fn(() => undefined);
+ const fetchFunction = createCollectorFetch(getUiSettingsClient);
+ expect(typeof fetchFunction).toBe('function');
+ });
+
+ describe('#fetchUsageStats', () => {
+ it('returns undefined if no uiSettingsClient returned from getUiSettingsClient', async () => {
+ const getUiSettingsClient = jest.fn(() => undefined);
+ const fetchFunction = createCollectorFetch(getUiSettingsClient);
+ const result = await fetchFunction();
+ expect(result).toBe(undefined);
+ expect(getUiSettingsClient).toBeCalledTimes(1);
+ });
+
+ it('returns all user changed settings', async () => {
+ const uiSettingsClient = uiSettingsServiceMock.createClient();
+ const getUiSettingsClient = jest.fn(() => uiSettingsClient);
+ uiSettingsClient.getUserProvided.mockResolvedValue(mockUserSettings);
+ uiSettingsClient.isSensitive.mockImplementation(mockIsSensitive);
+ const fetchFunction = createCollectorFetch(getUiSettingsClient);
+ const result = await fetchFunction();
+ expect(typeof result).toBe('object');
+ expect(Object.keys(result!)).toEqual(Object.keys(mockUserSettings));
+ });
+
+ it('returns the actual values of non-sensitive settings', async () => {
+ const uiSettingsClient = uiSettingsServiceMock.createClient();
+ const getUiSettingsClient = jest.fn(() => uiSettingsClient);
+ uiSettingsClient.getUserProvided.mockResolvedValue(mockUserSettings);
+ uiSettingsClient.isSensitive.mockImplementation(mockIsSensitive);
+ const fetchFunction = createCollectorFetch(getUiSettingsClient);
+ const result = await fetchFunction();
+ expect(typeof result).toBe('object');
+ expect(result!).toMatchObject({
+ item1: 'test',
+ item2: 123,
+ });
+ });
+
+ it('returns [REDACTED] as a value for sensitive settings', async () => {
+ const uiSettingsClient = uiSettingsServiceMock.createClient();
+ const getUiSettingsClient = jest.fn(() => uiSettingsClient);
+ uiSettingsClient.getUserProvided.mockResolvedValue(mockUserSettings);
+ uiSettingsClient.isSensitive.mockImplementation(mockIsSensitive);
+ const fetchFunction = createCollectorFetch(getUiSettingsClient);
+ const result = await fetchFunction();
+ expect(typeof result).toBe('object');
+ expect(result!).toMatchObject({
+ item3: '[REDACTED]',
+ });
+ });
+ });
+});
diff --git a/src/plugins/kibana_usage_collection/server/collectors/management/telemetry_management_collector.ts b/src/plugins/kibana_usage_collection/server/collectors/management/telemetry_management_collector.ts
index c45f3d6139d95..651fbbd5a897a 100644
--- a/src/plugins/kibana_usage_collection/server/collectors/management/telemetry_management_collector.ts
+++ b/src/plugins/kibana_usage_collection/server/collectors/management/telemetry_management_collector.ts
@@ -9,12 +9,8 @@
import { IUiSettingsClient } from 'kibana/server';
import { UsageCollectionSetup } from 'src/plugins/usage_collection/server';
import { stackManagementSchema } from './schema';
-
-export interface UsageStats extends Record {
- // We don't support `type` yet. Only interfaces. So I added at least 1 known key to the generic
- // Record extension to avoid eslint reverting it back to a `type`
- 'visualize:enableLabs': boolean;
-}
+import { UsageStats } from './types';
+import { REDACTED_KEYWORD } from '../../../common/constants';
export function createCollectorFetch(getUiSettingsClient: () => IUiSettingsClient | undefined) {
return async function fetchUsageStats(): Promise {
@@ -23,11 +19,12 @@ export function createCollectorFetch(getUiSettingsClient: () => IUiSettingsClien
return;
}
- const user = await uiSettingsClient.getUserProvided();
- const modifiedEntries = Object.keys(user)
- .filter((key: string) => key !== 'buildNum')
- .reduce((obj: any, key: string) => {
- obj[key] = user[key].userValue;
+ const userProvided = await uiSettingsClient.getUserProvided();
+ const modifiedEntries = Object.entries(userProvided)
+ .filter(([key]) => key !== 'buildNum')
+ .reduce((obj: any, [key, { userValue }]) => {
+ const sensitive = uiSettingsClient.isSensitive(key);
+ obj[key] = sensitive ? REDACTED_KEYWORD : userValue;
return obj;
}, {});
diff --git a/src/plugins/kibana_usage_collection/server/collectors/management/types.ts b/src/plugins/kibana_usage_collection/server/collectors/management/types.ts
new file mode 100644
index 0000000000000..417841ee89569
--- /dev/null
+++ b/src/plugins/kibana_usage_collection/server/collectors/management/types.ts
@@ -0,0 +1,117 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * and the Server Side Public License, v 1; you may not use this file except in
+ * compliance with, at your election, the Elastic License or the Server Side
+ * Public License, v 1.
+ */
+
+export interface UsageStats {
+ /**
+ * sensitive settings
+ */
+ 'timelion:quandl.key': string;
+ 'securitySolution:defaultIndex': string;
+ 'securitySolution:newsFeedUrl': string;
+ 'xpackReporting:customPdfLogo': string;
+ 'notifications:banner': string;
+ 'timelion:graphite.url': string;
+ 'xpackDashboardMode:roles': string;
+ 'securitySolution:ipReputationLinks': string;
+ /**
+ * non-sensitive settings
+ */
+ 'autocomplete:useTimeRange': boolean;
+ 'search:timeout': number;
+ 'visualization:visualize:legacyChartsLibrary': boolean;
+ 'doc_table:legacy': boolean;
+ 'discover:modifyColumnsOnSwitch': boolean;
+ 'discover:searchFieldsFromSource': boolean;
+ 'securitySolution:rulesTableRefresh': string;
+ 'apm:enableSignificantTerms': boolean;
+ 'apm:enableServiceOverview': boolean;
+ 'apm:enableCorrelations': boolean;
+ 'visualize:enableLabs': boolean;
+ 'visualization:heatmap:maxBuckets': number;
+ 'visualization:colorMapping': string;
+ 'visualization:regionmap:showWarnings': boolean;
+ 'visualization:dimmingOpacity': number;
+ 'visualization:tileMap:maxPrecision': number;
+ 'csv:separator': string;
+ 'visualization:tileMap:WMSdefaults': string;
+ 'timelion:target_buckets': number;
+ 'timelion:max_buckets': number;
+ 'timelion:es.timefield': string;
+ 'timelion:min_interval': string;
+ 'timelion:default_rows': number;
+ 'timelion:default_columns': number;
+ 'timelion:es.default_index': string;
+ 'timelion:showTutorial': boolean;
+ 'securitySolution:timeDefaults': string;
+ 'securitySolution:defaultAnomalyScore': number;
+ 'securitySolution:refreshIntervalDefaults': string;
+ 'securitySolution:enableNewsFeed': boolean;
+ 'search:includeFrozen': boolean;
+ 'courier:maxConcurrentShardRequests': number;
+ 'courier:batchSearches': boolean;
+ 'courier:setRequestPreference': string;
+ 'courier:customRequestPreference': string;
+ 'courier:ignoreFilterIfFieldNotInIndex': boolean;
+ 'rollups:enableIndexPatterns': boolean;
+ 'notifications:lifetime:warning': number;
+ 'notifications:lifetime:banner': number;
+ 'notifications:lifetime:info': number;
+ 'notifications:lifetime:error': number;
+ 'doc_table:highlight': boolean;
+ 'discover:searchOnPageLoad': boolean;
+ // eslint-disable-next-line @typescript-eslint/naming-convention
+ 'doc_table:hideTimeColumn': boolean;
+ 'discover:sampleSize': number;
+ defaultColumns: string[];
+ 'context:defaultSize': number;
+ 'discover:aggs:terms:size': number;
+ 'context:tieBreakerFields': string[];
+ 'discover:sort:defaultOrder': string;
+ 'context:step': number;
+ 'accessibility:disableAnimations': boolean;
+ 'ml:fileDataVisualizerMaxFileSize': string;
+ 'ml:anomalyDetection:results:enableTimeDefaults': boolean;
+ 'ml:anomalyDetection:results:timeDefaults': string;
+ 'truncate:maxHeight': number;
+ 'timepicker:timeDefaults': string;
+ 'timepicker:refreshIntervalDefaults': string;
+ 'timepicker:quickRanges': string;
+ 'theme:version': string;
+ 'theme:darkMode': boolean;
+ 'state:storeInSessionStorage': boolean;
+ 'savedObjects:perPage': number;
+ 'search:queryLanguage': string;
+ 'shortDots:enable': boolean;
+ 'sort:options': string;
+ 'savedObjects:listingLimit': number;
+ 'query:queryString:options': string;
+ 'metrics:max_buckets': number;
+ 'query:allowLeadingWildcards': boolean;
+ metaFields: string[];
+ 'indexPattern:placeholder': string;
+ 'histogram:barTarget': number;
+ 'histogram:maxBars': number;
+ 'format:number:defaultLocale': string;
+ 'format:percent:defaultPattern': string;
+ 'format:number:defaultPattern': string;
+ 'history:limit': number;
+ 'format:defaultTypeMap': string;
+ 'format:currency:defaultPattern': string;
+ defaultIndex: string;
+ 'format:bytes:defaultPattern': string;
+ 'filters:pinnedByDefault': boolean;
+ 'filterEditor:suggestValues': boolean;
+ 'fields:popularLimit': number;
+ dateNanosFormat: string;
+ defaultRoute: string;
+ 'dateFormat:tz': string;
+ 'dateFormat:scaled': string;
+ 'csv:quoteValues': boolean;
+ 'dateFormat:dow': string;
+ dateFormat: string;
+}
diff --git a/src/plugins/lens_oss/README.md b/src/plugins/lens_oss/README.md
deleted file mode 100644
index 187da2497026e..0000000000000
--- a/src/plugins/lens_oss/README.md
+++ /dev/null
@@ -1,6 +0,0 @@
-# lens_oss
-
-The lens_oss plugin registers the lens visualization on OSS.
-It is registered as disabled. The x-pack plugin should unregister this.
-
-`visualizations.unregisterAlias('lensOss')`
\ No newline at end of file
diff --git a/src/plugins/lens_oss/common/constants.ts b/src/plugins/lens_oss/common/constants.ts
deleted file mode 100644
index 0ff5cdd78bb1b..0000000000000
--- a/src/plugins/lens_oss/common/constants.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * and the Server Side Public License, v 1; you may not use this file except in
- * compliance with, at your election, the Elastic License or the Server Side
- * Public License, v 1.
- */
-
-export const APP_NAME = 'lens';
-export const PLUGIN_ID_OSS = 'lensOss';
-export const APP_PATH = '#/';
-export const APP_ICON = 'lensApp';
diff --git a/src/plugins/lens_oss/kibana.json b/src/plugins/lens_oss/kibana.json
deleted file mode 100644
index 3e3d3585f37fb..0000000000000
--- a/src/plugins/lens_oss/kibana.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{
- "id": "lensOss",
- "version": "kibana",
- "ui": true,
- "server": true,
- "requiredPlugins": [
- "visualizations"
- ],
- "extraPublicDirs": ["common/constants"]
-}
diff --git a/src/plugins/lens_oss/public/plugin.ts b/src/plugins/lens_oss/public/plugin.ts
deleted file mode 100644
index 5a441614b7e24..0000000000000
--- a/src/plugins/lens_oss/public/plugin.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * and the Server Side Public License, v 1; you may not use this file except in
- * compliance with, at your election, the Elastic License or the Server Side
- * Public License, v 1.
- */
-
-import { DocLinksStart, CoreSetup } from 'src/core/public';
-import { VisualizationsSetup } from '../../visualizations/public';
-import { getLensAliasConfig } from './vis_type_alias';
-
-export interface LensPluginSetupDependencies {
- visualizations: VisualizationsSetup;
-}
-
-export interface LensPluginStartDependencies {
- docLinks: DocLinksStart;
-}
-
-export class LensOSSPlugin {
- setup(
- core: CoreSetup,
- { visualizations }: LensPluginSetupDependencies
- ) {
- core.getStartServices().then(([coreStart]) => {
- visualizations.registerAlias(getLensAliasConfig(coreStart.docLinks));
- });
- }
-
- start() {}
-}
diff --git a/src/plugins/lens_oss/public/vis_type_alias.ts b/src/plugins/lens_oss/public/vis_type_alias.ts
deleted file mode 100644
index b9806bbf3b4e5..0000000000000
--- a/src/plugins/lens_oss/public/vis_type_alias.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * and the Server Side Public License, v 1; you may not use this file except in
- * compliance with, at your election, the Elastic License or the Server Side
- * Public License, v 1.
- */
-
-import { i18n } from '@kbn/i18n';
-import { VisTypeAlias } from 'src/plugins/visualizations/public';
-import { DocLinksStart } from 'src/core/public';
-import { APP_NAME, PLUGIN_ID_OSS, APP_PATH, APP_ICON } from '../common';
-
-export const getLensAliasConfig = ({ links }: DocLinksStart): VisTypeAlias => ({
- aliasPath: APP_PATH,
- aliasApp: APP_NAME,
- name: PLUGIN_ID_OSS,
- title: i18n.translate('lensOss.visTypeAlias.title', {
- defaultMessage: 'Lens',
- }),
- description: i18n.translate('lensOss.visTypeAlias.description', {
- defaultMessage:
- 'Create visualizations with our drag-and-drop editor. Switch between visualization types at any time. Best for most visualizations.',
- }),
- icon: APP_ICON,
- stage: 'production',
- disabled: true,
- note: i18n.translate('lensOss.visTypeAlias.note', {
- defaultMessage: 'Recommended for most users.',
- }),
- promoTooltip: {
- description: i18n.translate('lensOss.visTypeAlias.promoTooltip.description', {
- defaultMessage: 'Try Lens for free with Elastic. Learn more.',
- }),
- link: `${links.visualize.lens}?blade=kibanaossvizwizard`,
- },
-});
diff --git a/src/plugins/lens_oss/server/index.ts b/src/plugins/lens_oss/server/index.ts
deleted file mode 100644
index d13a9b2caaeb2..0000000000000
--- a/src/plugins/lens_oss/server/index.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * and the Server Side Public License, v 1; you may not use this file except in
- * compliance with, at your election, the Elastic License or the Server Side
- * Public License, v 1.
- */
-
-import { PluginConfigDescriptor } from 'kibana/server';
-import { copyFromRoot } from '@kbn/config';
-import { configSchema, ConfigSchema } from '../config';
-
-export const config: PluginConfigDescriptor = {
- schema: configSchema,
- deprecations: () => [copyFromRoot('xpack.lens.enabled', 'lens_oss.enabled')],
-};
-
-export const plugin = () => ({
- setup() {},
- start() {},
-});
diff --git a/src/plugins/maps_legacy/public/index.ts b/src/plugins/maps_legacy/public/index.ts
index 95550fab1ba17..9268f14995f44 100644
--- a/src/plugins/maps_legacy/public/index.ts
+++ b/src/plugins/maps_legacy/public/index.ts
@@ -8,9 +8,7 @@
import { PluginInitializerContext } from 'kibana/public';
import { MapsLegacyPlugin } from './plugin';
-// @ts-ignore
import * as colorUtil from './map/color_util';
-// @ts-ignore
import { KibanaMapLayer } from './map/kibana_map_layer';
import {
VectorLayer,
@@ -19,7 +17,6 @@ import {
TmsLayer,
IServiceSettings,
} from './map/service_settings_types';
-// @ts-ignore
import { mapTooltipProvider } from './tooltip_provider';
import './map/index.scss';
diff --git a/src/plugins/maps_oss/public/index.ts b/src/plugins/maps_legacy/public/map/color_util.d.ts
similarity index 69%
rename from src/plugins/maps_oss/public/index.ts
rename to src/plugins/maps_legacy/public/map/color_util.d.ts
index 1d27dc4b6d996..9ec6b3c1fb007 100644
--- a/src/plugins/maps_oss/public/index.ts
+++ b/src/plugins/maps_legacy/public/map/color_util.d.ts
@@ -6,6 +6,6 @@
* Public License, v 1.
*/
-import { MapsOSSPlugin } from './plugin';
+export function getLegendColors(colorRamp: unknown, numLegendColors?: number): string[];
-export const plugin = () => new MapsOSSPlugin();
+export function getColor(colorRamp: unknown, i: number): string;
diff --git a/src/plugins/lens_oss/config.ts b/src/plugins/maps_legacy/public/map/kibana_map_layer.d.ts
similarity index 54%
rename from src/plugins/lens_oss/config.ts
rename to src/plugins/maps_legacy/public/map/kibana_map_layer.d.ts
index 58c50f0104f46..222cb6b215f9a 100644
--- a/src/plugins/lens_oss/config.ts
+++ b/src/plugins/maps_legacy/public/map/kibana_map_layer.d.ts
@@ -6,10 +6,20 @@
* Public License, v 1.
*/
-import { schema, TypeOf } from '@kbn/config-schema';
+export class KibanaMapLayer {
+ constructor();
-export const configSchema = schema.object({
- enabled: schema.boolean({ defaultValue: true }),
-});
+ getBounds(): Promise;
-export type ConfigSchema = TypeOf;
+ addToLeafletMap(leafletMap: unknown): void;
+
+ removeFromLeafletMap(leafletMap: unknown): void;
+
+ appendLegendContents(): void;
+
+ updateExtent(): void;
+
+ movePointer(): void;
+
+ getAttributions(): unknown;
+}
diff --git a/typings/@elastic/eui/lib/format.d.ts b/src/plugins/maps_legacy/public/tooltip_provider.d.ts
similarity index 79%
rename from typings/@elastic/eui/lib/format.d.ts
rename to src/plugins/maps_legacy/public/tooltip_provider.d.ts
index 4be830ec2d98c..4082a6ef83c4d 100644
--- a/typings/@elastic/eui/lib/format.d.ts
+++ b/src/plugins/maps_legacy/public/tooltip_provider.d.ts
@@ -6,4 +6,4 @@
* Public License, v 1.
*/
-export const dateFormatAliases: any;
+export function mapTooltipProvider(element: unknown, formatter: unknown): () => unknown;
diff --git a/src/plugins/lens_oss/tsconfig.json b/src/plugins/maps_legacy/tsconfig.json
similarity index 56%
rename from src/plugins/lens_oss/tsconfig.json
rename to src/plugins/maps_legacy/tsconfig.json
index d7bbc593fa87b..e7ea06706b64f 100644
--- a/src/plugins/lens_oss/tsconfig.json
+++ b/src/plugins/maps_legacy/tsconfig.json
@@ -7,14 +7,8 @@
"declaration": true,
"declarationMap": true
},
- "include": [
- "common/**/*",
- "public/**/*",
- "server/**/*",
- "*.ts"
- ],
+ "include": ["common/**/*", "public/**/*", "server/**/*", "config.ts"],
"references": [
- { "path": "../../core/tsconfig.json" },
- { "path": "../visualizations/tsconfig.json" }
+ { "path": "../vis_default_editor/tsconfig.json" },
]
}
diff --git a/src/plugins/maps_oss/README.md b/src/plugins/maps_oss/README.md
deleted file mode 100644
index ed91de500fbfb..0000000000000
--- a/src/plugins/maps_oss/README.md
+++ /dev/null
@@ -1,6 +0,0 @@
-# maps_oss
-
-The maps_oss plugin registers the maps visualization on OSS.
-It is registered as disabled. The x-pack plugin should unregister this.
-
-`visualizations.unregisterAlias('mapsOss')`
\ No newline at end of file
diff --git a/src/plugins/maps_oss/common/constants.ts b/src/plugins/maps_oss/common/constants.ts
deleted file mode 100644
index db29f541a03df..0000000000000
--- a/src/plugins/maps_oss/common/constants.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * and the Server Side Public License, v 1; you may not use this file except in
- * compliance with, at your election, the Elastic License or the Server Side
- * Public License, v 1.
- */
-
-export const APP_NAME = 'maps';
-export const PLUGIN_ID_OSS = 'mapsOss';
-export const APP_PATH = '/map';
-export const APP_ICON = 'gisApp';
diff --git a/src/plugins/maps_oss/config.ts b/src/plugins/maps_oss/config.ts
deleted file mode 100644
index 58c50f0104f46..0000000000000
--- a/src/plugins/maps_oss/config.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * and the Server Side Public License, v 1; you may not use this file except in
- * compliance with, at your election, the Elastic License or the Server Side
- * Public License, v 1.
- */
-
-import { schema, TypeOf } from '@kbn/config-schema';
-
-export const configSchema = schema.object({
- enabled: schema.boolean({ defaultValue: true }),
-});
-
-export type ConfigSchema = TypeOf;
diff --git a/src/plugins/maps_oss/kibana.json b/src/plugins/maps_oss/kibana.json
deleted file mode 100644
index 19770dcffaadd..0000000000000
--- a/src/plugins/maps_oss/kibana.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{
- "id": "mapsOss",
- "version": "kibana",
- "ui": true,
- "server": true,
- "requiredPlugins": [
- "visualizations"
- ],
- "extraPublicDirs": ["common/constants"]
-}
diff --git a/src/plugins/maps_oss/public/plugin.ts b/src/plugins/maps_oss/public/plugin.ts
deleted file mode 100644
index 5e27ae34257bf..0000000000000
--- a/src/plugins/maps_oss/public/plugin.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * and the Server Side Public License, v 1; you may not use this file except in
- * compliance with, at your election, the Elastic License or the Server Side
- * Public License, v 1.
- */
-
-import { DocLinksStart, CoreSetup } from 'src/core/public';
-import { VisualizationsSetup } from '../../visualizations/public';
-import { getMapsAliasConfig } from './vis_type_alias';
-
-export interface MapsPluginSetupDependencies {
- visualizations: VisualizationsSetup;
-}
-
-export interface MapsPluginStartDependencies {
- docLinks: DocLinksStart;
-}
-
-export class MapsOSSPlugin {
- setup(
- core: CoreSetup,
- { visualizations }: MapsPluginSetupDependencies
- ) {
- core.getStartServices().then(([coreStart]) => {
- visualizations.registerAlias(getMapsAliasConfig(coreStart.docLinks));
- });
- }
-
- start() {}
-}
diff --git a/src/plugins/maps_oss/public/vis_type_alias.ts b/src/plugins/maps_oss/public/vis_type_alias.ts
deleted file mode 100644
index a27c628755cf6..0000000000000
--- a/src/plugins/maps_oss/public/vis_type_alias.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * and the Server Side Public License, v 1; you may not use this file except in
- * compliance with, at your election, the Elastic License or the Server Side
- * Public License, v 1.
- */
-
-import { i18n } from '@kbn/i18n';
-import { VisTypeAlias } from 'src/plugins/visualizations/public';
-import { DocLinksStart } from 'src/core/public';
-import { APP_NAME, PLUGIN_ID_OSS, APP_PATH, APP_ICON } from '../common';
-
-export const getMapsAliasConfig = ({ links }: DocLinksStart): VisTypeAlias => ({
- aliasPath: APP_PATH,
- aliasApp: APP_NAME,
- name: PLUGIN_ID_OSS,
- title: i18n.translate('mapsOss.visTypeAlias.title', {
- defaultMessage: 'Maps',
- }),
- description: i18n.translate('mapsOss.visTypeAlias.description', {
- defaultMessage: 'Plot and style your geo data in a multi layer map.',
- }),
- icon: APP_ICON,
- stage: 'production',
- disabled: true,
- promoTooltip: {
- description: i18n.translate('mapsOss.visTypeAlias.promoTooltip.description', {
- defaultMessage: 'Try maps for free with Elastic. Learn more.',
- }),
- link: `${links.visualize.maps}?blade=kibanaossvizwizard`,
- },
-});
diff --git a/src/plugins/maps_oss/server/index.ts b/src/plugins/maps_oss/server/index.ts
deleted file mode 100644
index 8f07beee705a6..0000000000000
--- a/src/plugins/maps_oss/server/index.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * and the Server Side Public License, v 1; you may not use this file except in
- * compliance with, at your election, the Elastic License or the Server Side
- * Public License, v 1.
- */
-
-import { PluginConfigDescriptor } from 'kibana/server';
-import { copyFromRoot } from '@kbn/config';
-import { configSchema, ConfigSchema } from '../config';
-
-export const config: PluginConfigDescriptor = {
- schema: configSchema,
- deprecations: () => [copyFromRoot('xpack.maps.enabled', 'maps_oss.enabled')],
-};
-
-export const plugin = () => ({
- setup() {},
- start() {},
-});
diff --git a/src/plugins/region_map/tsconfig.json b/src/plugins/region_map/tsconfig.json
new file mode 100644
index 0000000000000..40f76ece2a6ff
--- /dev/null
+++ b/src/plugins/region_map/tsconfig.json
@@ -0,0 +1,15 @@
+{
+ "extends": "../../../tsconfig.base.json",
+ "compilerOptions": {
+ "composite": true,
+ "outDir": "./target/types",
+ "emitDeclarationOnly": true,
+ "declaration": true,
+ "declarationMap": true
+ },
+ "include": ["public/**/*", "server/**/*"],
+ "references": [
+ { "path": "../maps_legacy/tsconfig.json" },
+ { "path": "../vis_default_editor/tsconfig.json" },
+ ]
+}
diff --git a/src/plugins/telemetry/schema/oss_plugins.json b/src/plugins/telemetry/schema/oss_plugins.json
index 7bac6a809eca3..950fdf9405b75 100644
--- a/src/plugins/telemetry/schema/oss_plugins.json
+++ b/src/plugins/telemetry/schema/oss_plugins.json
@@ -4076,6 +4076,27 @@
},
"stack_management": {
"properties": {
+ "timelion:quandl.key": {
+ "type": "keyword"
+ },
+ "securitySolution:defaultIndex": {
+ "type": "keyword"
+ },
+ "securitySolution:newsFeedUrl": {
+ "type": "keyword"
+ },
+ "xpackReporting:customPdfLogo": {
+ "type": "keyword"
+ },
+ "notifications:banner": {
+ "type": "keyword"
+ },
+ "timelion:graphite.url": {
+ "type": "keyword"
+ },
+ "xpackDashboardMode:roles": {
+ "type": "keyword"
+ },
"visualize:enableLabs": {
"type": "boolean"
},
@@ -4095,7 +4116,7 @@
"type": "long"
},
"securitySolution:ipReputationLinks": {
- "type": "text"
+ "type": "keyword"
},
"csv:separator": {
"type": "keyword"
@@ -4121,9 +4142,6 @@
"timelion:default_columns": {
"type": "long"
},
- "timelion:quandl.key": {
- "type": "keyword"
- },
"timelion:es.default_index": {
"type": "keyword"
},
@@ -4136,15 +4154,9 @@
"securitySolution:defaultAnomalyScore": {
"type": "long"
},
- "securitySolution:defaultIndex": {
- "type": "keyword"
- },
"securitySolution:refreshIntervalDefaults": {
"type": "keyword"
},
- "securitySolution:newsFeedUrl": {
- "type": "keyword"
- },
"securitySolution:enableNewsFeed": {
"type": "boolean"
},
@@ -4169,9 +4181,6 @@
"rollups:enableIndexPatterns": {
"type": "boolean"
},
- "xpackReporting:customPdfLogo": {
- "type": "text"
- },
"notifications:lifetime:warning": {
"type": "long"
},
@@ -4181,9 +4190,6 @@
"notifications:lifetime:info": {
"type": "long"
},
- "notifications:banner": {
- "type": "text"
- },
"notifications:lifetime:error": {
"type": "long"
},
@@ -4200,7 +4206,10 @@
"type": "long"
},
"defaultColumns": {
- "type": "keyword"
+ "type": "array",
+ "items": {
+ "type": "keyword"
+ }
},
"context:defaultSize": {
"type": "long"
@@ -4209,7 +4218,10 @@
"type": "long"
},
"context:tieBreakerFields": {
- "type": "keyword"
+ "type": "array",
+ "items": {
+ "type": "keyword"
+ }
},
"discover:sort:defaultOrder": {
"type": "keyword"
@@ -4275,7 +4287,10 @@
"type": "boolean"
},
"metaFields": {
- "type": "keyword"
+ "type": "array",
+ "items": {
+ "type": "keyword"
+ }
},
"indexPattern:placeholder": {
"type": "keyword"
@@ -4339,6 +4354,36 @@
},
"dateFormat": {
"type": "keyword"
+ },
+ "autocomplete:useTimeRange": {
+ "type": "boolean"
+ },
+ "search:timeout": {
+ "type": "long"
+ },
+ "visualization:visualize:legacyChartsLibrary": {
+ "type": "boolean"
+ },
+ "doc_table:legacy": {
+ "type": "boolean"
+ },
+ "discover:modifyColumnsOnSwitch": {
+ "type": "boolean"
+ },
+ "discover:searchFieldsFromSource": {
+ "type": "boolean"
+ },
+ "securitySolution:rulesTableRefresh": {
+ "type": "text"
+ },
+ "apm:enableSignificantTerms": {
+ "type": "boolean"
+ },
+ "apm:enableServiceOverview": {
+ "type": "boolean"
+ },
+ "apm:enableCorrelations": {
+ "type": "boolean"
}
}
},
diff --git a/src/plugins/tile_map/tsconfig.json b/src/plugins/tile_map/tsconfig.json
new file mode 100644
index 0000000000000..40f76ece2a6ff
--- /dev/null
+++ b/src/plugins/tile_map/tsconfig.json
@@ -0,0 +1,15 @@
+{
+ "extends": "../../../tsconfig.base.json",
+ "compilerOptions": {
+ "composite": true,
+ "outDir": "./target/types",
+ "emitDeclarationOnly": true,
+ "declaration": true,
+ "declarationMap": true
+ },
+ "include": ["public/**/*", "server/**/*"],
+ "references": [
+ { "path": "../maps_legacy/tsconfig.json" },
+ { "path": "../vis_default_editor/tsconfig.json" },
+ ]
+}
diff --git a/src/plugins/usage_collection/server/collector/collector.ts b/src/plugins/usage_collection/server/collector/collector.ts
index ccc17ea1c5967..8e8a74902d479 100644
--- a/src/plugins/usage_collection/server/collector/collector.ts
+++ b/src/plugins/usage_collection/server/collector/collector.ts
@@ -14,17 +14,38 @@ import {
KibanaRequest,
} from 'src/core/server';
-export type AllowedSchemaNumberTypes = 'long' | 'integer' | 'short' | 'byte' | 'double' | 'float';
+export type AllowedSchemaNumberTypes =
+ | 'long'
+ | 'integer'
+ | 'short'
+ | 'byte'
+ | 'double'
+ | 'float'
+ | 'date';
+export type AllowedSchemaStringTypes = 'keyword' | 'text' | 'date';
+export type AllowedSchemaBooleanTypes = 'boolean';
-export type AllowedSchemaTypes = AllowedSchemaNumberTypes | 'keyword' | 'text' | 'boolean' | 'date';
+export type AllowedSchemaTypes =
+ | AllowedSchemaNumberTypes
+ | AllowedSchemaStringTypes
+ | AllowedSchemaBooleanTypes;
export interface SchemaField {
type: string;
}
+export type PossibleSchemaTypes = U extends string
+ ? AllowedSchemaStringTypes
+ : U extends number
+ ? AllowedSchemaNumberTypes
+ : U extends boolean
+ ? AllowedSchemaBooleanTypes
+ : // allow any schema type from the union if typescript is unable to resolve the exact U type
+ AllowedSchemaTypes;
+
export type RecursiveMakeSchemaFrom = U extends object
? MakeSchemaFrom
- : { type: AllowedSchemaTypes };
+ : { type: PossibleSchemaTypes };
// Using Required to enforce all optional keys in the object
export type MakeSchemaFrom = {
diff --git a/src/plugins/vis_type_timelion/server/plugin.ts b/src/plugins/vis_type_timelion/server/plugin.ts
index f999c1dfc773a..fca557efc01e3 100644
--- a/src/plugins/vis_type_timelion/server/plugin.ts
+++ b/src/plugins/vis_type_timelion/server/plugin.ts
@@ -173,6 +173,7 @@ export class Plugin {
defaultMessage: '{experimentalLabel} Your API key from www.quandl.com',
values: { experimentalLabel: `[${experimentalLabel}]` },
}),
+ sensitive: true,
category: ['timelion'],
schema: schema.string(),
},
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 839d2b1f57c34..cb6bc624801d9 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
@@ -31,11 +31,6 @@ export interface VisualizationsAppExtension {
toListItem: (savedObject: SavedObject) => VisualizationListItem;
}
-export interface VisTypeAliasPromoTooltip {
- description: string;
- link: string;
-}
-
export interface VisTypeAlias {
aliasPath: string;
aliasApp: string;
@@ -43,10 +38,8 @@ export interface VisTypeAlias {
title: string;
icon: string;
promotion?: boolean;
- promoTooltip?: VisTypeAliasPromoTooltip;
description: string;
note?: string;
- disabled?: boolean;
getSupportedTriggers?: () => string[];
stage: VisualizationStage;
diff --git a/src/plugins/visualizations/public/wizard/group_selection/group_selection.test.tsx b/src/plugins/visualizations/public/wizard/group_selection/group_selection.test.tsx
index 74163296e31fd..396be30aca6d0 100644
--- a/src/plugins/visualizations/public/wizard/group_selection/group_selection.test.tsx
+++ b/src/plugins/visualizations/public/wizard/group_selection/group_selection.test.tsx
@@ -39,11 +39,6 @@ describe('GroupSelection', () => {
title: 'Vis with alias Url',
aliasApp: 'aliasApp',
aliasPath: '#/aliasApp',
- disabled: true,
- promoTooltip: {
- description: 'Learn More',
- link: '#/anotherUrl',
- },
description: 'Vis with alias Url',
stage: 'production',
group: VisGroups.PROMOTED,
@@ -227,41 +222,6 @@ describe('GroupSelection', () => {
]);
});
- it('should render disabled aliases with a disabled class', () => {
- const wrapper = mountWithIntl(
-
- );
- expect(wrapper.find('[data-test-subj="visType-visWithAliasUrl"]').exists()).toBe(true);
- expect(
- wrapper
- .find('[data-test-subj="visType-visWithAliasUrl"]')
- .at(1)
- .hasClass('euiCard-isDisabled')
- ).toBe(true);
- });
-
- it('should render a basic badge with link for disabled aliases with promoTooltip', () => {
- const wrapper = mountWithIntl(
-
- );
- expect(wrapper.find('[data-test-subj="visTypeBadge"]').exists()).toBe(true);
- expect(wrapper.find('[data-test-subj="visTypeBadge"]').at(0).prop('tooltipContent')).toBe(
- 'Learn More'
- );
- });
-
it('should not show tools experimental visualizations if showExperimentalis false', () => {
const expVis = {
name: 'visExp',
diff --git a/src/plugins/visualizations/public/wizard/group_selection/group_selection.tsx b/src/plugins/visualizations/public/wizard/group_selection/group_selection.tsx
index 9b61b2c415e9f..594e37f6a7608 100644
--- a/src/plugins/visualizations/public/wizard/group_selection/group_selection.tsx
+++ b/src/plugins/visualizations/public/wizard/group_selection/group_selection.tsx
@@ -48,10 +48,6 @@ interface VisCardProps {
showExperimental?: boolean | undefined;
}
-function isVisTypeAlias(type: BaseVisType | VisTypeAlias): type is VisTypeAlias {
- return 'aliasPath' in type;
-}
-
function GroupSelection(props: GroupSelectionProps) {
const visualizeGuideLink = props.docLinks.links.dashboard.guide;
const promotedVisGroups = useMemo(
@@ -185,29 +181,8 @@ const VisGroup = ({ visType, onVisTypeSelected }: VisCardProps) => {
const onClick = useCallback(() => {
onVisTypeSelected(visType);
}, [onVisTypeSelected, visType]);
- const shouldDisableCard = isVisTypeAlias(visType) && visType.disabled;
- const betaBadgeContent =
- shouldDisableCard && 'promoTooltip' in visType ? (
-
-
-
- ) : undefined;
return (
- {betaBadgeContent}
{
}
onClick={onClick}
- isDisabled={shouldDisableCard}
data-test-subj={`visType-${visType.name}`}
data-vis-stage={!('aliasPath' in visType) ? visType.stage : 'alias'}
aria-label={`visType-${visType.name}`}
diff --git a/test/common/config.js b/test/common/config.js
index 8a42e6c87b214..b6d12444b7017 100644
--- a/test/common/config.js
+++ b/test/common/config.js
@@ -61,6 +61,8 @@ export default function () {
...(!!process.env.CODE_COVERAGE
? [`--plugin-path=${path.join(__dirname, 'fixtures', 'plugins', 'coverage')}`]
: []),
+ // Disable v2 migrations in tests for now
+ '--migrations.enableV2=false',
],
},
services,
diff --git a/test/functional/apps/dashboard/dashboard_save.ts b/test/functional/apps/dashboard/dashboard_save.ts
index 27cbba7db393d..e36136cd45141 100644
--- a/test/functional/apps/dashboard/dashboard_save.ts
+++ b/test/functional/apps/dashboard/dashboard_save.ts
@@ -12,7 +12,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
const PageObjects = getPageObjects(['dashboard', 'header']);
const listingTable = getService('listingTable');
- describe('dashboard save', function describeIndexTests() {
+ // FLAKY: https://github.com/elastic/kibana/issues/89476
+ describe.skip('dashboard save', function describeIndexTests() {
this.tags('includeFirefox');
const dashboardName = 'Dashboard Save Test';
const dashboardNameEnterKey = 'Dashboard Save Test with Enter Key';
diff --git a/test/functional/apps/discover/_data_grid.ts b/test/functional/apps/discover/_data_grid.ts
index 3bac05c5b18fc..1329e7657ad9c 100644
--- a/test/functional/apps/discover/_data_grid.ts
+++ b/test/functional/apps/discover/_data_grid.ts
@@ -38,7 +38,7 @@ export default function ({
const getTitles = async () =>
(await testSubjects.getVisibleText('dataGridHeader')).replace(/\s|\r?\n|\r/g, ' ');
- expect(await getTitles()).to.be('Time (@timestamp) _source');
+ expect(await getTitles()).to.be('Time (@timestamp) Document');
await PageObjects.discover.clickFieldListItemAdd('bytes');
expect(await getTitles()).to.be('Time (@timestamp) bytes');
@@ -50,7 +50,7 @@ export default function ({
expect(await getTitles()).to.be('Time (@timestamp) agent');
await PageObjects.discover.clickFieldListItemAdd('agent');
- expect(await getTitles()).to.be('Time (@timestamp) _source');
+ expect(await getTitles()).to.be('Time (@timestamp) Document');
});
});
}
diff --git a/test/functional/apps/discover/_data_grid_field_data.ts b/test/functional/apps/discover/_data_grid_field_data.ts
index bdbaacc33c1bd..3eec84ad3d7c0 100644
--- a/test/functional/apps/discover/_data_grid_field_data.ts
+++ b/test/functional/apps/discover/_data_grid_field_data.ts
@@ -57,7 +57,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
});
it('doc view should show Time and _source columns', async function () {
- const expectedHeader = 'Time (@timestamp) _source';
+ const expectedHeader = 'Time (@timestamp) Document';
const DocHeader = await dataGrid.getHeaderFields();
expect(DocHeader.join(' ')).to.be(expectedHeader);
});
diff --git a/test/functional/apps/discover/_saved_queries.ts b/test/functional/apps/discover/_saved_queries.ts
index 6e6c53ec04985..ec6c455ecc979 100644
--- a/test/functional/apps/discover/_saved_queries.ts
+++ b/test/functional/apps/discover/_saved_queries.ts
@@ -26,7 +26,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
const savedQueryManagementComponent = getService('savedQueryManagementComponent');
const testSubjects = getService('testSubjects');
- describe('saved queries saved objects', function describeIndexTests() {
+ // FLAKY: https://github.com/elastic/kibana/issues/89477
+ describe.skip('saved queries saved objects', function describeIndexTests() {
before(async function () {
log.debug('load kibana index with default index pattern');
await esArchiver.load('discover');
diff --git a/test/functional/apps/getting_started/_shakespeare.ts b/test/functional/apps/getting_started/_shakespeare.ts
index 95abbf9fa8a78..5a891af0de93d 100644
--- a/test/functional/apps/getting_started/_shakespeare.ts
+++ b/test/functional/apps/getting_started/_shakespeare.ts
@@ -30,8 +30,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
// https://www.elastic.co/guide/en/kibana/current/tutorial-load-dataset.html
- // Failing: See https://github.com/elastic/kibana/issues/82206
- describe.skip('Shakespeare', function describeIndexTests() {
+ describe('Shakespeare', function describeIndexTests() {
// index starts on the first "count" metric at 1
// Each new metric or aggregation added to a visualization gets the next index.
// So to modify a metric or aggregation tests need to keep track of the
diff --git a/test/functional/apps/home/_navigation.ts b/test/functional/apps/home/_navigation.ts
index c90398fa84afc..12f97a5349419 100644
--- a/test/functional/apps/home/_navigation.ts
+++ b/test/functional/apps/home/_navigation.ts
@@ -15,7 +15,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
const appsMenu = getService('appsMenu');
const esArchiver = getService('esArchiver');
- describe('Kibana browser back navigation should work', function describeIndexTests() {
+ // Failing: See https://github.com/elastic/kibana/issues/88826
+ describe.skip('Kibana browser back navigation should work', function describeIndexTests() {
before(async () => {
await esArchiver.loadIfNeeded('discover');
await esArchiver.loadIfNeeded('logstash_functional');
diff --git a/test/functional/apps/home/_sample_data.ts b/test/functional/apps/home/_sample_data.ts
index a9fe2026112b6..438dd6f8adce2 100644
--- a/test/functional/apps/home/_sample_data.ts
+++ b/test/functional/apps/home/_sample_data.ts
@@ -20,7 +20,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
const dashboardExpect = getService('dashboardExpect');
const PageObjects = getPageObjects(['common', 'header', 'home', 'dashboard', 'timePicker']);
- describe('sample data', function describeIndexTests() {
+ // Failing: See https://github.com/elastic/kibana/issues/89379
+ describe.skip('sample data', function describeIndexTests() {
before(async () => {
await security.testUser.setRoles(['kibana_admin', 'kibana_sample_admin']);
await PageObjects.common.navigateToUrl('home', '/tutorial_directory/sampleData', {
diff --git a/test/functional/apps/management/_import_objects.ts b/test/functional/apps/management/_import_objects.ts
index 07811c9c68e45..754406938e47b 100644
--- a/test/functional/apps/management/_import_objects.ts
+++ b/test/functional/apps/management/_import_objects.ts
@@ -23,7 +23,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
const testSubjects = getService('testSubjects');
const log = getService('log');
- describe('import objects', function describeIndexTests() {
+ // FLAKY: https://github.com/elastic/kibana/issues/89478
+ describe.skip('import objects', function describeIndexTests() {
describe('.ndjson file', () => {
beforeEach(async function () {
await kibanaServer.uiSettings.replace({});
diff --git a/test/functional/apps/management/_scripted_fields_preview.js b/test/functional/apps/management/_scripted_fields_preview.js
index 104d41b7e2a75..46619b89dfc59 100644
--- a/test/functional/apps/management/_scripted_fields_preview.js
+++ b/test/functional/apps/management/_scripted_fields_preview.js
@@ -13,7 +13,8 @@ export default function ({ getService, getPageObjects }) {
const PageObjects = getPageObjects(['settings']);
const SCRIPTED_FIELD_NAME = 'myScriptedField';
- describe('scripted fields preview', () => {
+ // FLAKY: https://github.com/elastic/kibana/issues/89475
+ describe.skip('scripted fields preview', () => {
before(async function () {
await browser.setWindowSize(1200, 800);
await PageObjects.settings.createIndexPattern();
diff --git a/test/functional/apps/management/_test_huge_fields.js b/test/functional/apps/management/_test_huge_fields.js
index ca95af8cb4205..2ab619276d2b9 100644
--- a/test/functional/apps/management/_test_huge_fields.js
+++ b/test/functional/apps/management/_test_huge_fields.js
@@ -13,7 +13,8 @@ export default function ({ getService, getPageObjects }) {
const security = getService('security');
const PageObjects = getPageObjects(['common', 'home', 'settings']);
- describe('test large number of fields', function () {
+ // Failing: See https://github.com/elastic/kibana/issues/89031
+ describe.skip('test large number of fields', function () {
this.tags(['skipCloud']);
const EXPECTED_FIELD_COUNT = '10006';
diff --git a/test/functional/apps/visualize/_chart_types.ts b/test/functional/apps/visualize/_chart_types.ts
index 55b68b7370148..69403f2090594 100644
--- a/test/functional/apps/visualize/_chart_types.ts
+++ b/test/functional/apps/visualize/_chart_types.ts
@@ -12,21 +12,17 @@ import expect from '@kbn/expect';
import { FtrProviderContext } from '../../ftr_provider_context';
export default function ({ getService, getPageObjects }: FtrProviderContext) {
- const deployment = getService('deployment');
const log = getService('log');
const PageObjects = getPageObjects(['visualize']);
- let isOss = true;
describe('chart types', function () {
before(async function () {
log.debug('navigateToApp visualize');
- isOss = await deployment.isOss();
await PageObjects.visualize.navigateToNewVisualization();
});
it('should show the promoted vis types for the first step', async function () {
const expectedChartTypes = ['Custom visualization', 'Lens', 'Maps', 'TSVB'];
- log.debug('oss= ' + isOss);
// find all the chart types and make sure there all there
const chartTypes = (await PageObjects.visualize.getPromotedVisTypes()).sort();
@@ -37,9 +33,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
it('should show the correct agg based chart types', async function () {
await PageObjects.visualize.clickAggBasedVisualizations();
- let expectedChartTypes = [
+ const expectedChartTypes = [
'Area',
- 'Coordinate Map',
'Data table',
'Gauge',
'Goal',
@@ -48,21 +43,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
'Line',
'Metric',
'Pie',
- 'Region Map',
'Tag cloud',
'Timelion',
'Vertical bar',
];
- if (!isOss) {
- expectedChartTypes = _.remove(expectedChartTypes, function (n) {
- return n !== 'Coordinate Map';
- });
- expectedChartTypes = _.remove(expectedChartTypes, function (n) {
- return n !== 'Region Map';
- });
- expectedChartTypes.sort();
- }
- log.debug('oss= ' + isOss);
// find all the chart types and make sure there all there
const chartTypes = (await PageObjects.visualize.getChartTypes()).sort();
diff --git a/test/functional/apps/visualize/index.ts b/test/functional/apps/visualize/index.ts
index 8dd2854419693..4170ada692e67 100644
--- a/test/functional/apps/visualize/index.ts
+++ b/test/functional/apps/visualize/index.ts
@@ -67,11 +67,15 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) {
this.tags('ciGroup9');
loadTestFile(require.resolve('./_embedding_chart'));
- loadTestFile(require.resolve('./_chart_types'));
loadTestFile(require.resolve('./_area_chart'));
loadTestFile(require.resolve('./_data_table'));
loadTestFile(require.resolve('./_data_table_nontimeindex'));
loadTestFile(require.resolve('./_data_table_notimeindex_filters'));
+
+ // this check is not needed when the CI doesn't run anymore for the OSS
+ if (!isOss) {
+ loadTestFile(require.resolve('./_chart_types'));
+ }
});
describe('', function () {
diff --git a/test/functional/page_objects/common_page.ts b/test/functional/page_objects/common_page.ts
index 1149f1b100788..ed817b8b55e80 100644
--- a/test/functional/page_objects/common_page.ts
+++ b/test/functional/page_objects/common_page.ts
@@ -376,7 +376,7 @@ export function CommonPageProvider({ getService, getPageObjects }: FtrProviderCo
}
async closeToast() {
- const toast = await find.byCssSelector('.euiToast', 2 * defaultFindTimeout);
+ const toast = await find.byCssSelector('.euiToast', 6 * defaultFindTimeout);
await toast.moveMouseTo();
const title = await (await find.byCssSelector('.euiToastHeader__title')).getVisibleText();
diff --git a/test/scripts/checks/test_projects.sh b/test/scripts/checks/test_projects.sh
index 56f15f6839e9d..be3fe4c4be9d0 100755
--- a/test/scripts/checks/test_projects.sh
+++ b/test/scripts/checks/test_projects.sh
@@ -3,4 +3,4 @@
source src/dev/ci_setup/setup_env.sh
checks-reporter-with-killswitch "Test Projects" \
- yarn kbn run test --exclude kibana --oss --skip-kibana-plugins
+ yarn kbn run test --exclude kibana --oss --skip-kibana-plugins --skip-missing
diff --git a/test/scripts/jenkins_unit.sh b/test/scripts/jenkins_unit.sh
index 6e28f9c3ef56a..9e387f97a016e 100755
--- a/test/scripts/jenkins_unit.sh
+++ b/test/scripts/jenkins_unit.sh
@@ -2,12 +2,6 @@
source test/scripts/jenkins_test_setup.sh
-rename_coverage_file() {
- test -f target/kibana-coverage/jest/coverage-final.json \
- && mv target/kibana-coverage/jest/coverage-final.json \
- target/kibana-coverage/jest/$1-coverage-final.json
-}
-
if [[ -z "$CODE_COVERAGE" ]] ; then
# Lint
./test/scripts/lint/eslint.sh
@@ -34,13 +28,8 @@ if [[ -z "$CODE_COVERAGE" ]] ; then
./test/scripts/checks/test_hardening.sh
else
echo " -> Running jest tests with coverage"
- node scripts/jest --ci --verbose --coverage --config jest.config.oss.js || true;
- rename_coverage_file "oss"
- echo ""
- echo ""
+ node scripts/jest --ci --verbose --maxWorkers=6 --coverage || true;
+
echo " -> Running jest integration tests with coverage"
- node --max-old-space-size=8192 scripts/jest_integration --ci --verbose --coverage || true;
- rename_coverage_file "oss-integration"
- echo ""
- echo ""
+ node scripts/jest_integration --ci --verbose --coverage || true;
fi
diff --git a/test/scripts/jenkins_xpack.sh b/test/scripts/jenkins_xpack.sh
deleted file mode 100755
index 66fb5ae5370bc..0000000000000
--- a/test/scripts/jenkins_xpack.sh
+++ /dev/null
@@ -1,23 +0,0 @@
-#!/usr/bin/env bash
-
-source test/scripts/jenkins_test_setup.sh
-
-if [[ -z "$CODE_COVERAGE" ]] ; then
- echo " -> Running jest tests"
-
- ./test/scripts/test/xpack_jest_unit.sh
-else
- echo " -> Build runtime for canvas"
- # build runtime for canvas
- echo "NODE_ENV=$NODE_ENV"
- node ./x-pack/plugins/canvas/scripts/shareable_runtime
- echo " -> Running jest tests with coverage"
- cd x-pack
- node --max-old-space-size=6144 scripts/jest --ci --verbose --maxWorkers=5 --coverage --config jest.config.js || true;
- # rename file in order to be unique one
- test -f ../target/kibana-coverage/jest/coverage-final.json \
- && mv ../target/kibana-coverage/jest/coverage-final.json \
- ../target/kibana-coverage/jest/xpack-coverage-final.json
- echo ""
- echo ""
-fi
diff --git a/test/scripts/test/jest_integration.sh b/test/scripts/test/jest_integration.sh
index 78ed804f88430..c48d9032466a3 100755
--- a/test/scripts/test/jest_integration.sh
+++ b/test/scripts/test/jest_integration.sh
@@ -3,4 +3,4 @@
source src/dev/ci_setup/setup_env.sh
checks-reporter-with-killswitch "Jest Integration Tests" \
- node scripts/jest_integration --ci --verbose
+ node scripts/jest_integration --ci --verbose --coverage
diff --git a/test/scripts/test/jest_unit.sh b/test/scripts/test/jest_unit.sh
index 88c0fe528b88c..06c159c0a4ace 100755
--- a/test/scripts/test/jest_unit.sh
+++ b/test/scripts/test/jest_unit.sh
@@ -3,4 +3,4 @@
source src/dev/ci_setup/setup_env.sh
checks-reporter-with-killswitch "Jest Unit Tests" \
- node scripts/jest --config jest.config.oss.js --ci --verbose --maxWorkers=5
+ node scripts/jest --ci --verbose --maxWorkers=6 --coverage
diff --git a/test/scripts/test/xpack_jest_unit.sh b/test/scripts/test/xpack_jest_unit.sh
deleted file mode 100755
index 33b1c8a2b5183..0000000000000
--- a/test/scripts/test/xpack_jest_unit.sh
+++ /dev/null
@@ -1,6 +0,0 @@
-#!/usr/bin/env bash
-
-source src/dev/ci_setup/setup_env.sh
-
-checks-reporter-with-killswitch "X-Pack Jest" \
- node scripts/jest x-pack --ci --verbose --maxWorkers=5
diff --git a/tsconfig.base.json b/tsconfig.base.json
index 247813da51cfb..f8e07911e71ce 100644
--- a/tsconfig.base.json
+++ b/tsconfig.base.json
@@ -14,6 +14,8 @@
"strict": true,
// save information about the project graph on disk
"incremental": true,
+ // Do not check d.ts files by default
+ "skipLibCheck": true,
// enables "core language features"
"lib": [
"esnext",
diff --git a/tsconfig.json b/tsconfig.json
index b6742bffeab55..bdd4ba296d1c9 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -26,10 +26,11 @@
"src/plugins/kibana_react/**/*",
"src/plugins/kibana_usage_collection/**/*",
"src/plugins/kibana_utils/**/*",
- "src/plugins/lens_oss/**/*",
"src/plugins/management/**/*",
+ "src/plugins/maps_legacy/**/*",
"src/plugins/navigation/**/*",
"src/plugins/newsfeed/**/*",
+ "src/plugins/region_map/**/*",
"src/plugins/saved_objects/**/*",
"src/plugins/saved_objects_management/**/*",
"src/plugins/saved_objects_tagging_oss/**/*",
@@ -38,6 +39,7 @@
"src/plugins/spaces_oss/**/*",
"src/plugins/telemetry/**/*",
"src/plugins/telemetry_collection_manager/**/*",
+ "src/plugins/tile_map/**/*",
"src/plugins/timelion/**/*",
"src/plugins/ui_actions/**/*",
"src/plugins/url_forwarding/**/*",
@@ -81,10 +83,11 @@
{ "path": "./src/plugins/kibana_react/tsconfig.json" },
{ "path": "./src/plugins/kibana_usage_collection/tsconfig.json" },
{ "path": "./src/plugins/kibana_utils/tsconfig.json" },
- { "path": "./src/plugins/lens_oss/tsconfig.json" },
{ "path": "./src/plugins/management/tsconfig.json" },
+ { "path": "./src/plugins/maps_legacy/tsconfig.json" },
{ "path": "./src/plugins/navigation/tsconfig.json" },
{ "path": "./src/plugins/newsfeed/tsconfig.json" },
+ { "path": "./src/plugins/region_map/tsconfig.json" },
{ "path": "./src/plugins/saved_objects/tsconfig.json" },
{ "path": "./src/plugins/saved_objects_management/tsconfig.json" },
{ "path": "./src/plugins/saved_objects_tagging_oss/tsconfig.json" },
@@ -93,6 +96,7 @@
{ "path": "./src/plugins/spaces_oss/tsconfig.json" },
{ "path": "./src/plugins/telemetry/tsconfig.json" },
{ "path": "./src/plugins/telemetry_collection_manager/tsconfig.json" },
+ { "path": "./src/plugins/tile_map/tsconfig.json" },
{ "path": "./src/plugins/timelion/tsconfig.json" },
{ "path": "./src/plugins/ui_actions/tsconfig.json" },
{ "path": "./src/plugins/url_forwarding/tsconfig.json" },
diff --git a/tsconfig.refs.json b/tsconfig.refs.json
index 1bce19e2ee44a..211a50ec1a539 100644
--- a/tsconfig.refs.json
+++ b/tsconfig.refs.json
@@ -22,10 +22,11 @@
{ "path": "./src/plugins/kibana_react/tsconfig.json" },
{ "path": "./src/plugins/kibana_usage_collection/tsconfig.json" },
{ "path": "./src/plugins/kibana_utils/tsconfig.json" },
- { "path": "./src/plugins/lens_oss/tsconfig.json" },
{ "path": "./src/plugins/management/tsconfig.json" },
+ { "path": "./src/plugins/maps_legacy/tsconfig.json" },
{ "path": "./src/plugins/navigation/tsconfig.json" },
{ "path": "./src/plugins/newsfeed/tsconfig.json" },
+ { "path": "./src/plugins/region_map/tsconfig.json" },
{ "path": "./src/plugins/saved_objects/tsconfig.json" },
{ "path": "./src/plugins/saved_objects_management/tsconfig.json" },
{ "path": "./src/plugins/saved_objects_tagging_oss/tsconfig.json" },
@@ -35,6 +36,7 @@
{ "path": "./src/plugins/spaces_oss/tsconfig.json" },
{ "path": "./src/plugins/telemetry/tsconfig.json" },
{ "path": "./src/plugins/telemetry_collection_manager/tsconfig.json" },
+ { "path": "./src/plugins/tile_map/tsconfig.json" },
{ "path": "./src/plugins/timelion/tsconfig.json" },
{ "path": "./src/plugins/ui_actions/tsconfig.json" },
{ "path": "./src/plugins/url_forwarding/tsconfig.json" },
diff --git a/typings/@elastic/eui/index.d.ts b/typings/@elastic/eui/index.d.ts
index 74f89608bc04f..e5cf7864a83b2 100644
--- a/typings/@elastic/eui/index.d.ts
+++ b/typings/@elastic/eui/index.d.ts
@@ -6,6 +6,12 @@
* Public License, v 1.
*/
-import { Direction } from '@elastic/eui/src/services/sort/sort_direction';
-
// TODO: Remove once typescript definitions are in EUI
+
+declare module '@elastic/eui/lib/services' {
+ export const RIGHT_ALIGNMENT: any;
+}
+
+declare module '@elastic/eui/lib/services/format' {
+ export const dateFormatAliases: any;
+}
diff --git a/typings/@elastic/eui/lib/services.d.ts b/typings/@elastic/eui/lib/services.d.ts
deleted file mode 100644
index a667d111ceb72..0000000000000
--- a/typings/@elastic/eui/lib/services.d.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * and the Server Side Public License, v 1; you may not use this file except in
- * compliance with, at your election, the Elastic License or the Server Side
- * Public License, v 1.
- */
-
-export const RIGHT_ALIGNMENT: any;
diff --git a/vars/kibanaCoverage.groovy b/vars/kibanaCoverage.groovy
index 609d8f78aeb96..e393f3a5d2150 100644
--- a/vars/kibanaCoverage.groovy
+++ b/vars/kibanaCoverage.groovy
@@ -197,13 +197,6 @@ def ingest(jobName, buildNumber, buildUrl, timestamp, previousSha, teamAssignmen
def runTests() {
parallel([
'kibana-intake-agent': workers.intake('kibana-intake', './test/scripts/jenkins_unit.sh'),
- 'x-pack-intake-agent': {
- withEnv([
- 'NODE_ENV=test' // Needed for jest tests only
- ]) {
- workers.intake('x-pack-intake', './test/scripts/jenkins_xpack.sh')()
- }
- },
'kibana-oss-agent' : workers.functional(
'kibana-oss-tests',
{ kibanaPipeline.buildOss() },
diff --git a/vars/kibanaPipeline.groovy b/vars/kibanaPipeline.groovy
index 93cb7a719bbe8..e49692568cec8 100644
--- a/vars/kibanaPipeline.groovy
+++ b/vars/kibanaPipeline.groovy
@@ -179,20 +179,21 @@ def uploadCoverageArtifacts(prefix, pattern) {
def withGcsArtifactUpload(workerName, closure) {
def uploadPrefix = "kibana-ci-artifacts/jobs/${env.JOB_NAME}/${BUILD_NUMBER}/${workerName}"
def ARTIFACT_PATTERNS = [
+ 'target/junit/**/*',
'target/kibana-*',
- 'target/test-metrics/*',
+ 'target/kibana-coverage/**/*',
'target/kibana-security-solution/**/*.png',
- 'target/junit/**/*',
+ 'target/test-metrics/*',
'target/test-suites-ci-plan.json',
- 'test/**/screenshots/session/*.png',
- 'test/**/screenshots/failure/*.png',
'test/**/screenshots/diff/*.png',
+ 'test/**/screenshots/failure/*.png',
+ 'test/**/screenshots/session/*.png',
'test/functional/failure_debug/html/*.html',
- 'x-pack/test/**/screenshots/session/*.png',
- 'x-pack/test/**/screenshots/failure/*.png',
'x-pack/test/**/screenshots/diff/*.png',
- 'x-pack/test/functional/failure_debug/html/*.html',
+ 'x-pack/test/**/screenshots/failure/*.png',
+ 'x-pack/test/**/screenshots/session/*.png',
'x-pack/test/functional/apps/reporting/reports/session/*.pdf',
+ 'x-pack/test/functional/failure_debug/html/*.html',
]
withEnv([
diff --git a/vars/tasks.groovy b/vars/tasks.groovy
index 3493a95f0bdce..74ad1267e9355 100644
--- a/vars/tasks.groovy
+++ b/vars/tasks.groovy
@@ -35,7 +35,6 @@ def test() {
kibanaPipeline.scriptTask('Jest Unit Tests', 'test/scripts/test/jest_unit.sh'),
kibanaPipeline.scriptTask('API Integration Tests', 'test/scripts/test/api_integration.sh'),
- kibanaPipeline.scriptTask('X-Pack Jest Unit Tests', 'test/scripts/test/xpack_jest_unit.sh'),
])
}
diff --git a/x-pack/.i18nrc.json b/x-pack/.i18nrc.json
index 6937862d20536..bfac437f3500a 100644
--- a/x-pack/.i18nrc.json
+++ b/x-pack/.i18nrc.json
@@ -20,7 +20,7 @@
"xpack.endpoint": "plugins/endpoint",
"xpack.enterpriseSearch": "plugins/enterprise_search",
"xpack.features": "plugins/features",
- "xpack.fileUpload": "plugins/file_upload",
+ "xpack.fileUpload": "plugins/maps_file_upload",
"xpack.globalSearch": ["plugins/global_search"],
"xpack.globalSearchBar": ["plugins/global_search_bar"],
"xpack.graph": ["plugins/graph"],
diff --git a/x-pack/plugins/actions/server/usage/actions_telemetry.test.ts b/x-pack/plugins/actions/server/usage/actions_telemetry.test.ts
index 1b0fe03633531..08a3fd007554e 100644
--- a/x-pack/plugins/actions/server/usage/actions_telemetry.test.ts
+++ b/x-pack/plugins/actions/server/usage/actions_telemetry.test.ts
@@ -4,16 +4,16 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { getTotalCount } from './actions_telemetry';
+import { getInUseTotalCount, getTotalCount } from './actions_telemetry';
describe('actions telemetry', () => {
- test('getTotalCount should replace action types names with . to __', async () => {
+ test('getTotalCount should replace first symbol . to __ for action types names', async () => {
const mockEsClient = jest.fn();
mockEsClient.mockReturnValue({
aggregations: {
byActionTypeId: {
value: {
- types: { '.index': 1, '.server-log': 1 },
+ types: { '.index': 1, '.server-log': 1, 'some.type': 1, 'another.type.': 1 },
},
},
},
@@ -56,6 +56,38 @@ describe('actions telemetry', () => {
updated_at: '2020-03-26T18:46:44.449Z',
},
},
+ {
+ _id: 'action:00000000-1',
+ _index: '.kibana_1',
+ _score: 0,
+ _source: {
+ action: {
+ actionTypeId: 'some.type',
+ config: {},
+ name: 'test type',
+ secrets: {},
+ },
+ references: [],
+ type: 'action',
+ updated_at: '2020-03-26T18:46:44.449Z',
+ },
+ },
+ {
+ _id: 'action:00000000-2',
+ _index: '.kibana_1',
+ _score: 0,
+ _source: {
+ action: {
+ actionTypeId: 'another.type.',
+ config: {},
+ name: 'test another type',
+ secrets: {},
+ },
+ references: [],
+ type: 'action',
+ updated_at: '2020-03-26T18:46:44.449Z',
+ },
+ },
],
},
});
@@ -69,6 +101,58 @@ Object {
"countByType": Object {
"__index": 1,
"__server-log": 1,
+ "another.type__": 1,
+ "some.type": 1,
+ },
+ "countTotal": 4,
+}
+`);
+ });
+
+ test('getInUseTotalCount', async () => {
+ const mockEsClient = jest.fn();
+ mockEsClient.mockReturnValue({
+ aggregations: {
+ refs: {
+ actionRefIds: {
+ value: {
+ connectorIds: { '1': 'action-0', '123': 'action-0' },
+ total: 2,
+ },
+ },
+ },
+ hits: {
+ hits: [],
+ },
+ },
+ });
+ const actionsBulkGet = jest.fn();
+ actionsBulkGet.mockReturnValue({
+ saved_objects: [
+ {
+ id: '1',
+ attributes: {
+ actionTypeId: '.server-log',
+ },
+ },
+ {
+ id: '123',
+ attributes: {
+ actionTypeId: '.slack',
+ },
+ },
+ ],
+ });
+ const telemetry = await getInUseTotalCount(mockEsClient, actionsBulkGet, 'test');
+
+ expect(mockEsClient).toHaveBeenCalledTimes(1);
+ expect(actionsBulkGet).toHaveBeenCalledTimes(1);
+
+ expect(telemetry).toMatchInlineSnapshot(`
+Object {
+ "countByType": Object {
+ "__server-log": 1,
+ "__slack": 1,
},
"countTotal": 2,
}
diff --git a/x-pack/plugins/actions/server/usage/actions_telemetry.ts b/x-pack/plugins/actions/server/usage/actions_telemetry.ts
index e3ff2552fed9c..cc49232150eee 100644
--- a/x-pack/plugins/actions/server/usage/actions_telemetry.ts
+++ b/x-pack/plugins/actions/server/usage/actions_telemetry.ts
@@ -4,7 +4,13 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { LegacyAPICaller } from 'kibana/server';
+import {
+ LegacyAPICaller,
+ SavedObjectsBaseOptions,
+ SavedObjectsBulkGetObject,
+ SavedObjectsBulkResponse,
+} from 'kibana/server';
+import { ActionResult } from '../types';
export async function getTotalCount(callCluster: LegacyAPICaller, kibanaIndex: string) {
const scriptedMetric = {
@@ -58,14 +64,23 @@ export async function getTotalCount(callCluster: LegacyAPICaller, kibanaIndex: s
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(obj: any, key: string) => ({
...obj,
- [key.replace('.', '__')]: searchResult.aggregations.byActionTypeId.value.types[key],
+ [replaceFirstAndLastDotSymbols(key)]: searchResult.aggregations.byActionTypeId.value.types[
+ key
+ ],
}),
{}
),
};
}
-export async function getInUseTotalCount(callCluster: LegacyAPICaller, kibanaIndex: string) {
+export async function getInUseTotalCount(
+ callCluster: LegacyAPICaller,
+ actionsBulkGet: (
+ objects?: SavedObjectsBulkGetObject[] | undefined,
+ options?: SavedObjectsBaseOptions | undefined
+ ) => Promise>>>,
+ kibanaIndex: string
+): Promise<{ countTotal: number; countByType: Record }> {
const scriptedMetric = {
scripted_metric: {
init_script: 'state.connectorIds = new HashMap(); state.total = 0;',
@@ -145,7 +160,32 @@ export async function getInUseTotalCount(callCluster: LegacyAPICaller, kibanaInd
},
});
- return actionResults.aggregations.refs.actionRefIds.value.total;
+ const bulkFilter = Object.entries(
+ actionResults.aggregations.refs.actionRefIds.value.connectorIds
+ ).map(([key]) => ({
+ id: key,
+ type: 'action',
+ fields: ['id', 'actionTypeId'],
+ }));
+ const actions = await actionsBulkGet(bulkFilter);
+ const countByType = actions.saved_objects.reduce(
+ (actionTypeCount: Record, action) => {
+ const alertTypeId = replaceFirstAndLastDotSymbols(action.attributes.actionTypeId);
+ const currentCount =
+ actionTypeCount[alertTypeId] !== undefined ? actionTypeCount[alertTypeId] : 0;
+ actionTypeCount[alertTypeId] = currentCount + 1;
+ return actionTypeCount;
+ },
+ {}
+ );
+ return { countTotal: actionResults.aggregations.refs.actionRefIds.value.total, countByType };
+}
+
+function replaceFirstAndLastDotSymbols(strToReplace: string) {
+ const hasFirstSymbolDot = strToReplace.startsWith('.');
+ const appliedString = hasFirstSymbolDot ? strToReplace.replace('.', '__') : strToReplace;
+ const hasLastSymbolDot = strToReplace.endsWith('.');
+ return hasLastSymbolDot ? `${appliedString.slice(0, -1)}__` : appliedString;
}
// TODO: Implement executions count telemetry with eventLog, when it will write to index
diff --git a/x-pack/plugins/actions/server/usage/task.ts b/x-pack/plugins/actions/server/usage/task.ts
index 176ba29ef748a..001db21ffebcc 100644
--- a/x-pack/plugins/actions/server/usage/task.ts
+++ b/x-pack/plugins/actions/server/usage/task.ts
@@ -4,13 +4,20 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { Logger, CoreSetup, LegacyAPICaller } from 'kibana/server';
+import {
+ Logger,
+ CoreSetup,
+ LegacyAPICaller,
+ SavedObjectsBulkGetObject,
+ SavedObjectsBaseOptions,
+} from 'kibana/server';
import moment from 'moment';
import {
RunContext,
TaskManagerSetupContract,
TaskManagerStartContract,
} from '../../../task_manager/server';
+import { ActionResult } from '../types';
import { getTotalCount, getInUseTotalCount } from './actions_telemetry';
export const TELEMETRY_TASK_TYPE = 'actions_telemetry';
@@ -66,19 +73,30 @@ export function telemetryTaskRunner(logger: Logger, core: CoreSetup, kibanaIndex
client.callAsInternalUser(...args)
);
};
+ const actionsBulkGet = (
+ objects?: SavedObjectsBulkGetObject[],
+ options?: SavedObjectsBaseOptions
+ ) => {
+ return core
+ .getStartServices()
+ .then(([{ savedObjects }]) =>
+ savedObjects.createInternalRepository(['action']).bulkGet(objects, options)
+ );
+ };
return {
async run() {
return Promise.all([
getTotalCount(callCluster, kibanaIndex),
- getInUseTotalCount(callCluster, kibanaIndex),
+ getInUseTotalCount(callCluster, actionsBulkGet, kibanaIndex),
])
- .then(([totalAggegations, countActiveTotal]) => {
+ .then(([totalAggegations, totalInUse]) => {
return {
state: {
runs: (state.runs || 0) + 1,
count_total: totalAggegations.countTotal,
count_by_type: totalAggegations.countByType,
- count_active_total: countActiveTotal,
+ count_active_total: totalInUse.countTotal,
+ count_active_by_type: totalInUse.countByType,
},
runAt: getNextMidnight(),
};
diff --git a/x-pack/plugins/alerts/server/usage/alerts_telemetry.test.ts b/x-pack/plugins/alerts/server/usage/alerts_telemetry.test.ts
index 171f80cf11cb8..843100194e4b6 100644
--- a/x-pack/plugins/alerts/server/usage/alerts_telemetry.test.ts
+++ b/x-pack/plugins/alerts/server/usage/alerts_telemetry.test.ts
@@ -7,13 +7,13 @@
import { getTotalCountInUse } from './alerts_telemetry';
describe('alerts telemetry', () => {
- test('getTotalCountInUse should replace action types names with . to __', async () => {
+ test('getTotalCountInUse should replace first "." symbol to "__" in alert types names', async () => {
const mockEsClient = jest.fn();
mockEsClient.mockReturnValue({
aggregations: {
byAlertTypeId: {
value: {
- types: { '.index-threshold': 2 },
+ types: { '.index-threshold': 2, 'logs.alert.document.count': 1, 'document.test.': 1 },
},
},
},
@@ -30,8 +30,10 @@ describe('alerts telemetry', () => {
Object {
"countByType": Object {
"__index-threshold": 2,
+ "document.test__": 1,
+ "logs.alert.document.count": 1,
},
- "countTotal": 2,
+ "countTotal": 4,
}
`);
});
diff --git a/x-pack/plugins/alerts/server/usage/alerts_telemetry.ts b/x-pack/plugins/alerts/server/usage/alerts_telemetry.ts
index 6edebb1decb61..72b189aa67f4b 100644
--- a/x-pack/plugins/alerts/server/usage/alerts_telemetry.ts
+++ b/x-pack/plugins/alerts/server/usage/alerts_telemetry.ts
@@ -250,7 +250,7 @@ export async function getTotalCountAggregations(callCluster: LegacyAPICaller, ki
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(obj: any, key: string) => ({
...obj,
- [key.replace('.', '__')]: results.aggregations.byAlertTypeId.value.types[key],
+ [replaceFirstAndLastDotSymbols(key)]: results.aggregations.byAlertTypeId.value.types[key],
}),
{}
),
@@ -310,11 +310,20 @@ export async function getTotalCountInUse(callCluster: LegacyAPICaller, kibanaIne
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(obj: any, key: string) => ({
...obj,
- [key.replace('.', '__')]: searchResult.aggregations.byAlertTypeId.value.types[key],
+ [replaceFirstAndLastDotSymbols(key)]: searchResult.aggregations.byAlertTypeId.value.types[
+ key
+ ],
}),
{}
),
};
}
+function replaceFirstAndLastDotSymbols(strToReplace: string) {
+ const hasFirstSymbolDot = strToReplace.startsWith('.');
+ const appliedString = hasFirstSymbolDot ? strToReplace.replace('.', '__') : strToReplace;
+ const hasLastSymbolDot = strToReplace.endsWith('.');
+ return hasLastSymbolDot ? `${appliedString.slice(0, -1)}__` : appliedString;
+}
+
// TODO: Implement executions count telemetry with eventLog, when it will write to index
diff --git a/x-pack/plugins/apm/public/components/shared/charts/transaction_error_rate_chart/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/transaction_error_rate_chart/index.tsx
index 90877a895b05b..d712fa27c75ac 100644
--- a/x-pack/plugins/apm/public/components/shared/charts/transaction_error_rate_chart/index.tsx
+++ b/x-pack/plugins/apm/public/components/shared/charts/transaction_error_rate_chart/index.tsx
@@ -77,7 +77,6 @@ export function TransactionErrorRateChart({
data: errorRates,
type: 'linemark',
color: theme.eui.euiColorVis7,
- hideLegend: true,
title: i18n.translate('xpack.apm.errorRate.chart.errorRate', {
defaultMessage: 'Error rate (avg.)',
}),
diff --git a/x-pack/plugins/apm/public/hooks/use_fetcher.tsx b/x-pack/plugins/apm/public/hooks/use_fetcher.tsx
index 8174f06e06b8b..2b58f30a9ec64 100644
--- a/x-pack/plugins/apm/public/hooks/use_fetcher.tsx
+++ b/x-pack/plugins/apm/public/hooks/use_fetcher.tsx
@@ -24,6 +24,21 @@ export interface FetcherResult {
error?: IHttpFetchError;
}
+function getDetailsFromErrorResponse(error: IHttpFetchError) {
+ const message = error.body?.message ?? error.response?.statusText;
+ return (
+ <>
+ {message} ({error.response?.status})
+
+ {i18n.translate('xpack.apm.fetcher.error.url', {
+ defaultMessage: `URL`,
+ })}
+
+ {error.response?.url}
+ >
+ );
+}
+
// fetcher functions can return undefined OR a promise. Previously we had a more simple type
// but it led to issues when using object destructuring with default values
type InferResponseType = Exclude extends Promise<
@@ -82,25 +97,14 @@ export function useFetcher(
if (!didCancel) {
const errorDetails =
- 'response' in err ? (
- <>
- {err.response?.statusText} ({err.response?.status})
-
- {i18n.translate('xpack.apm.fetcher.error.url', {
- defaultMessage: `URL`,
- })}
-
- {err.response?.url}
- >
- ) : (
- err.message
- );
+ 'response' in err ? getDetailsFromErrorResponse(err) : err.message;
if (showToastOnError) {
- notifications.toasts.addWarning({
+ notifications.toasts.addDanger({
title: i18n.translate('xpack.apm.fetcher.error.title', {
defaultMessage: `Error while fetching resource`,
}),
+
text: toMountPoint(
diff --git a/x-pack/plugins/apm/scripts/upload-telemetry-data/index.ts b/x-pack/plugins/apm/scripts/upload-telemetry-data/index.ts
index 8c64c37d9b7f7..e3221c17f3f2a 100644
--- a/x-pack/plugins/apm/scripts/upload-telemetry-data/index.ts
+++ b/x-pack/plugins/apm/scripts/upload-telemetry-data/index.ts
@@ -17,6 +17,8 @@ import { argv } from 'yargs';
import { Logger } from 'kibana/server';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { CollectTelemetryParams } from '../../server/lib/apm_telemetry/collect_data_telemetry';
+// eslint-disable-next-line @kbn/eslint/no-restricted-paths
+import { unwrapEsResponse } from '../../../observability/server/utils/unwrap_es_response';
import { downloadTelemetryTemplate } from '../shared/download-telemetry-template';
import { mergeApmTelemetryMapping } from '../../common/apm_telemetry';
import { generateSampleDocuments } from './generate-sample-documents';
@@ -80,18 +82,18 @@ async function uploadData() {
apmAgentConfigurationIndex: '.apm-agent-configuration',
},
search: (body) => {
- return client.search(body as any).then((res) => res.body as any);
+ return unwrapEsResponse(client.search(body));
},
indicesStats: (body) => {
- return client.indices.stats(body as any).then((res) => res.body);
+ return unwrapEsResponse(client.indices.stats(body));
},
transportRequest: ((params) => {
- return client.transport
- .request({
+ return unwrapEsResponse(
+ client.transport.request({
method: params.method,
path: params.path,
})
- .then((res) => res.body);
+ );
}) as CollectTelemetryParams['transportRequest'],
},
});
diff --git a/x-pack/plugins/apm/server/lib/apm_telemetry/collect_data_telemetry/index.ts b/x-pack/plugins/apm/server/lib/apm_telemetry/collect_data_telemetry/index.ts
index 730645c609cb6..90aad48fe20b9 100644
--- a/x-pack/plugins/apm/server/lib/apm_telemetry/collect_data_telemetry/index.ts
+++ b/x-pack/plugins/apm/server/lib/apm_telemetry/collect_data_telemetry/index.ts
@@ -4,8 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { merge } from 'lodash';
-import { Logger, LegacyCallAPIOptions } from 'kibana/server';
-import { IndicesStatsParams, Client } from 'elasticsearch';
+import { Logger } from 'kibana/server';
+import { RequestParams } from '@elastic/elasticsearch';
import {
ESSearchRequest,
ESSearchResponse,
@@ -20,9 +20,17 @@ type TelemetryTaskExecutor = (params: {
params: TSearchRequest
): Promise>;
indicesStats(
- params: IndicesStatsParams,
- options?: LegacyCallAPIOptions
- ): ReturnType;
+ params: RequestParams.IndicesStats
+ // promise returned by client has an abort property
+ // so we cannot use its ReturnType
+ ): Promise<{
+ _all?: {
+ total?: { store?: { size_in_bytes?: number }; docs?: { count?: number } };
+ };
+ _shards?: {
+ total?: number;
+ };
+ }>;
transportRequest: (params: {
path: string;
method: 'get';
diff --git a/x-pack/plugins/apm/server/lib/apm_telemetry/index.ts b/x-pack/plugins/apm/server/lib/apm_telemetry/index.ts
index 6d91e64be034d..98abff08dab5e 100644
--- a/x-pack/plugins/apm/server/lib/apm_telemetry/index.ts
+++ b/x-pack/plugins/apm/server/lib/apm_telemetry/index.ts
@@ -11,6 +11,7 @@ import {
Logger,
SavedObjectsErrorHelpers,
} from '../../../../../../src/core/server';
+import { unwrapEsResponse } from '../../../../observability/server';
import { APMConfig } from '../..';
import {
TaskManagerSetupContract,
@@ -65,27 +66,22 @@ export async function createApmTelemetry({
const collectAndStore = async () => {
const config = await config$.pipe(take(1)).toPromise();
const [{ elasticsearch }] = await core.getStartServices();
- const esClient = elasticsearch.legacy.client;
+ const esClient = elasticsearch.client;
const indices = await getApmIndices({
config,
savedObjectsClient,
});
- const search = esClient.callAsInternalUser.bind(
- esClient,
- 'search'
- ) as CollectTelemetryParams['search'];
+ const search: CollectTelemetryParams['search'] = (params) =>
+ unwrapEsResponse(esClient.asInternalUser.search(params));
- const indicesStats = esClient.callAsInternalUser.bind(
- esClient,
- 'indices.stats'
- ) as CollectTelemetryParams['indicesStats'];
+ const indicesStats: CollectTelemetryParams['indicesStats'] = (params) =>
+ unwrapEsResponse(esClient.asInternalUser.indices.stats(params));
- const transportRequest = esClient.callAsInternalUser.bind(
- esClient,
- 'transport.request'
- ) as CollectTelemetryParams['transportRequest'];
+ const transportRequest: CollectTelemetryParams['transportRequest'] = (
+ params
+ ) => unwrapEsResponse(esClient.asInternalUser.transport.request(params));
const dataTelemetry = await collectDataTelemetry({
search,
diff --git a/x-pack/plugins/apm/server/lib/helpers/create_es_client/call_client_with_debug.ts b/x-pack/plugins/apm/server/lib/helpers/create_es_client/call_async_with_debug.ts
similarity index 51%
rename from x-pack/plugins/apm/server/lib/helpers/create_es_client/call_client_with_debug.ts
rename to x-pack/plugins/apm/server/lib/helpers/create_es_client/call_async_with_debug.ts
index 9f7aaafbefb8c..9d612d82d99bb 100644
--- a/x-pack/plugins/apm/server/lib/helpers/create_es_client/call_client_with_debug.ts
+++ b/x-pack/plugins/apm/server/lib/helpers/create_es_client/call_async_with_debug.ts
@@ -7,34 +7,31 @@
/* eslint-disable no-console */
import chalk from 'chalk';
-import {
- LegacyAPICaller,
- KibanaRequest,
-} from '../../../../../../../src/core/server';
+import { KibanaRequest } from '../../../../../../../src/core/server';
function formatObj(obj: Record) {
return JSON.stringify(obj, null, 2);
}
-export async function callClientWithDebug({
- apiCaller,
- operationName,
- params,
+export async function callAsyncWithDebug({
+ cb,
+ getDebugMessage,
debug,
- request,
}: {
- apiCaller: LegacyAPICaller;
- operationName: string;
- params: Record;
+ cb: () => Promise;
+ getDebugMessage: () => { body: string; title: string };
debug: boolean;
- request: KibanaRequest;
}) {
+ if (!debug) {
+ return cb();
+ }
+
const startTime = process.hrtime();
let res: any;
let esError = null;
try {
- res = await apiCaller(operationName, params);
+ res = await cb();
} catch (e) {
// catch error and throw after outputting debug info
esError = e;
@@ -44,23 +41,14 @@ export async function callClientWithDebug({
const highlightColor = esError ? 'bgRed' : 'inverse';
const diff = process.hrtime(startTime);
const duration = `${Math.round(diff[0] * 1000 + diff[1] / 1e6)}ms`;
- const routeInfo = `${request.route.method.toUpperCase()} ${
- request.route.path
- }`;
+
+ const { title, body } = getDebugMessage();
console.log(
- chalk.bold[highlightColor](`=== Debug: ${routeInfo} (${duration}) ===`)
+ chalk.bold[highlightColor](`=== Debug: ${title} (${duration}) ===`)
);
- if (operationName === 'search') {
- console.log(`GET ${params.index}/_${operationName}`);
- console.log(formatObj(params.body));
- } else {
- console.log(chalk.bold('ES operation:'), operationName);
-
- console.log(chalk.bold('ES query:'));
- console.log(formatObj(params));
- }
+ console.log(body);
console.log(`\n`);
}
@@ -70,3 +58,19 @@ export async function callClientWithDebug({
return res;
}
+
+export const getDebugBody = (
+ params: Record,
+ operationName: string
+) => {
+ if (operationName === 'search') {
+ return `GET ${params.index}/_search\n${formatObj(params.body)}`;
+ }
+
+ return `${chalk.bold('ES operation:')} ${operationName}\n${chalk.bold(
+ 'ES query:'
+ )}\n${formatObj(params)}`;
+};
+
+export const getDebugTitle = (request: KibanaRequest) =>
+ `${request.route.method.toUpperCase()} ${request.route.path}`;
diff --git a/x-pack/plugins/apm/server/lib/helpers/create_es_client/cancel_es_request_on_abort.ts b/x-pack/plugins/apm/server/lib/helpers/create_es_client/cancel_es_request_on_abort.ts
new file mode 100644
index 0000000000000..e9b61a27f4380
--- /dev/null
+++ b/x-pack/plugins/apm/server/lib/helpers/create_es_client/cancel_es_request_on_abort.ts
@@ -0,0 +1,27 @@
+/*
+ * 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 { TransportRequestPromise } from '@elastic/elasticsearch/lib/Transport';
+import { KibanaRequest } from 'src/core/server';
+
+export function cancelEsRequestOnAbort>(
+ promise: T,
+ request: KibanaRequest
+) {
+ const subscription = request.events.aborted$.subscribe(() => {
+ promise.abort();
+ });
+
+ // using .catch() here means unsubscribe will be called
+ // after it has thrown an error, so we use .then(onSuccess, onFailure)
+ // syntax
+ promise.then(
+ () => subscription.unsubscribe(),
+ () => subscription.unsubscribe()
+ );
+
+ return promise;
+}
diff --git a/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.test.ts b/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.test.ts
new file mode 100644
index 0000000000000..f58e04061254d
--- /dev/null
+++ b/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.test.ts
@@ -0,0 +1,77 @@
+/*
+ * 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 { contextServiceMock } from 'src/core/server/mocks';
+import { createHttpServer } from 'src/core/server/test_utils';
+import supertest from 'supertest';
+import { createApmEventClient } from '.';
+
+describe('createApmEventClient', () => {
+ let server: ReturnType;
+
+ beforeEach(() => {
+ server = createHttpServer();
+ });
+
+ afterEach(async () => {
+ await server.stop();
+ });
+ it('cancels a search when a request is aborted', async () => {
+ const { server: innerServer, createRouter } = await server.setup({
+ context: contextServiceMock.createSetupContract(),
+ });
+ const router = createRouter('/');
+
+ const abort = jest.fn();
+ router.get(
+ { path: '/', validate: false },
+ async (context, request, res) => {
+ const eventClient = createApmEventClient({
+ esClient: {
+ search: () => {
+ return Object.assign(
+ new Promise((resolve) => setTimeout(resolve, 3000)),
+ { abort }
+ );
+ },
+ } as any,
+ debug: false,
+ request,
+ indices: {} as any,
+ options: {
+ includeFrozen: false,
+ },
+ });
+
+ await eventClient.search({
+ apm: {
+ events: [],
+ },
+ });
+
+ return res.ok({ body: 'ok' });
+ }
+ );
+
+ await server.start();
+
+ const incomingRequest = supertest(innerServer.listener)
+ .get('/')
+ // end required to send request
+ .end();
+
+ await new Promise((resolve) => {
+ setTimeout(() => {
+ incomingRequest.abort();
+ setTimeout(() => {
+ resolve(undefined);
+ }, 0);
+ }, 50);
+ });
+
+ expect(abort).toHaveBeenCalled();
+ });
+});
diff --git a/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.ts b/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.ts
index b7c38068eb93e..b2e25994d6fe6 100644
--- a/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.ts
+++ b/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.ts
@@ -5,10 +5,11 @@
*/
import { ValuesType } from 'utility-types';
+import { unwrapEsResponse } from '../../../../../../observability/server';
import { APMError } from '../../../../../typings/es_schemas/ui/apm_error';
import {
+ ElasticsearchClient,
KibanaRequest,
- LegacyScopedClusterClient,
} from '../../../../../../../../src/core/server';
import { ProcessorEvent } from '../../../../../common/processor_event';
import {
@@ -17,11 +18,16 @@ import {
} from '../../../../../../../typings/elasticsearch';
import { ApmIndicesConfig } from '../../../settings/apm_indices/get_apm_indices';
import { addFilterToExcludeLegacyData } from './add_filter_to_exclude_legacy_data';
-import { callClientWithDebug } from '../call_client_with_debug';
import { Transaction } from '../../../../../typings/es_schemas/ui/transaction';
import { Span } from '../../../../../typings/es_schemas/ui/span';
import { Metric } from '../../../../../typings/es_schemas/ui/metric';
import { unpackProcessorEvents } from './unpack_processor_events';
+import {
+ callAsyncWithDebug,
+ getDebugTitle,
+ getDebugBody,
+} from '../call_async_with_debug';
+import { cancelEsRequestOnAbort } from '../cancel_es_request_on_abort';
export type APMEventESSearchRequest = Omit & {
apm: {
@@ -59,10 +65,7 @@ export function createApmEventClient({
indices,
options: { includeFrozen } = { includeFrozen: false },
}: {
- esClient: Pick<
- LegacyScopedClusterClient,
- 'callAsInternalUser' | 'callAsCurrentUser'
- >;
+ esClient: ElasticsearchClient;
debug: boolean;
request: KibanaRequest;
indices: ApmIndicesConfig;
@@ -71,9 +74,9 @@ export function createApmEventClient({
};
}) {
return {
- search(
+ async search(
params: TParams,
- { includeLegacyData } = { includeLegacyData: false }
+ { includeLegacyData = false } = {}
): Promise> {
const withProcessorEventFilter = unpackProcessorEvents(params, indices);
@@ -81,15 +84,25 @@ export function createApmEventClient({
? addFilterToExcludeLegacyData(withProcessorEventFilter)
: withProcessorEventFilter;
- return callClientWithDebug({
- apiCaller: esClient.callAsCurrentUser,
- operationName: 'search',
- params: {
- ...withPossibleLegacyDataFilter,
- ignore_throttled: !includeFrozen,
- ignore_unavailable: true,
+ const searchParams = {
+ ...withPossibleLegacyDataFilter,
+ ignore_throttled: !includeFrozen,
+ ignore_unavailable: true,
+ };
+
+ return callAsyncWithDebug({
+ cb: () => {
+ const searchPromise = cancelEsRequestOnAbort(
+ esClient.search(searchParams),
+ request
+ );
+
+ return unwrapEsResponse(searchPromise);
},
- request,
+ getDebugMessage: () => ({
+ body: getDebugBody(searchParams, 'search'),
+ title: getDebugTitle(request),
+ }),
debug,
});
},
diff --git a/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_internal_es_client/index.ts b/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_internal_es_client/index.ts
index 8e74a7992e9ea..69f596520d216 100644
--- a/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_internal_es_client/index.ts
+++ b/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_internal_es_client/index.ts
@@ -3,23 +3,23 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
-
-import {
- IndexDocumentParams,
- IndicesCreateParams,
- DeleteDocumentResponse,
- DeleteDocumentParams,
-} from 'elasticsearch';
import { KibanaRequest } from 'src/core/server';
+import { RequestParams } from '@elastic/elasticsearch';
+import { TransportRequestPromise } from '@elastic/elasticsearch/lib/Transport';
+import { unwrapEsResponse } from '../../../../../../observability/server';
import { APMRequestHandlerContext } from '../../../../routes/typings';
import {
ESSearchResponse,
ESSearchRequest,
} from '../../../../../../../typings/elasticsearch';
-import { callClientWithDebug } from '../call_client_with_debug';
+import {
+ callAsyncWithDebug,
+ getDebugBody,
+ getDebugTitle,
+} from '../call_async_with_debug';
+import { cancelEsRequestOnAbort } from '../cancel_es_request_on_abort';
-// `type` was deprecated in 7.0
-export type APMIndexDocumentParams = Omit, 'type'>;
+export type APMIndexDocumentParams = RequestParams.Index;
export type APMInternalClient = ReturnType;
@@ -30,17 +30,26 @@ export function createInternalESClient({
context: APMRequestHandlerContext;
request: KibanaRequest;
}) {
- const { callAsInternalUser } = context.core.elasticsearch.legacy.client;
+ const { asInternalUser } = context.core.elasticsearch.client;
- const callEs = (operationName: string, params: Record) => {
- return callClientWithDebug({
- apiCaller: callAsInternalUser,
- operationName,
- params,
- request,
+ function callEs({
+ cb,
+ operationName,
+ params,
+ }: {
+ operationName: string;
+ cb: () => TransportRequestPromise;
+ params: Record;
+ }) {
+ return callAsyncWithDebug({
+ cb: () => unwrapEsResponse(cancelEsRequestOnAbort(cb(), request)),
+ getDebugMessage: () => ({
+ title: getDebugTitle(request),
+ body: getDebugBody(params, operationName),
+ }),
debug: context.params.query._debug,
});
- };
+ }
return {
search: async <
@@ -49,18 +58,32 @@ export function createInternalESClient({
>(
params: TSearchRequest
): Promise> => {
- return callEs('search', params);
+ return callEs({
+ operationName: 'search',
+ cb: () => asInternalUser.search(params),
+ params,
+ });
},
- index: (params: APMIndexDocumentParams) => {
- return callEs('index', params);
+ index: (params: APMIndexDocumentParams) => {
+ return callEs({
+ operationName: 'index',
+ cb: () => asInternalUser.index(params),
+ params,
+ });
},
- delete: (
- params: Omit
- ): Promise => {
- return callEs('delete', params);
+ delete: (params: RequestParams.Delete): Promise<{ result: string }> => {
+ return callEs({
+ operationName: 'delete',
+ cb: () => asInternalUser.delete(params),
+ params,
+ });
},
- indicesCreate: (params: IndicesCreateParams) => {
- return callEs('indices.create', params);
+ indicesCreate: (params: RequestParams.IndicesCreate) => {
+ return callEs({
+ operationName: 'indices.create',
+ cb: () => asInternalUser.indices.create(params),
+ params,
+ });
},
};
}
diff --git a/x-pack/plugins/apm/server/lib/helpers/setup_request.test.ts b/x-pack/plugins/apm/server/lib/helpers/setup_request.test.ts
index f2d291cd053bb..f00941d6e6800 100644
--- a/x-pack/plugins/apm/server/lib/helpers/setup_request.test.ts
+++ b/x-pack/plugins/apm/server/lib/helpers/setup_request.test.ts
@@ -31,6 +31,15 @@ jest.mock('../index_pattern/get_dynamic_index_pattern', () => ({
}));
function getMockRequest() {
+ const esClientMock = {
+ asCurrentUser: {
+ search: jest.fn().mockResolvedValue({ body: {} }),
+ },
+ asInternalUser: {
+ search: jest.fn().mockResolvedValue({ body: {} }),
+ },
+ };
+
const mockContext = ({
config: new Proxy(
{},
@@ -45,12 +54,7 @@ function getMockRequest() {
},
core: {
elasticsearch: {
- legacy: {
- client: {
- callAsCurrentUser: jest.fn(),
- callAsInternalUser: jest.fn(),
- },
- },
+ client: esClientMock,
},
uiSettings: {
client: {
@@ -69,12 +73,7 @@ function getMockRequest() {
} as unknown) as APMRequestHandlerContext & {
core: {
elasticsearch: {
- legacy: {
- client: {
- callAsCurrentUser: jest.Mock;
- callAsInternalUser: jest.Mock;
- };
- };
+ client: typeof esClientMock;
};
uiSettings: {
client: {
@@ -91,6 +90,11 @@ function getMockRequest() {
const mockRequest = ({
url: '',
+ events: {
+ aborted$: {
+ subscribe: jest.fn(),
+ },
+ },
} as unknown) as KibanaRequest;
return { mockContext, mockRequest };
@@ -106,8 +110,8 @@ describe('setupRequest', () => {
body: { foo: 'bar' },
});
expect(
- mockContext.core.elasticsearch.legacy.client.callAsCurrentUser
- ).toHaveBeenCalledWith('search', {
+ mockContext.core.elasticsearch.client.asCurrentUser.search
+ ).toHaveBeenCalledWith({
index: ['apm-*'],
body: {
foo: 'bar',
@@ -133,8 +137,8 @@ describe('setupRequest', () => {
body: { foo: 'bar' },
} as any);
expect(
- mockContext.core.elasticsearch.legacy.client.callAsInternalUser
- ).toHaveBeenCalledWith('search', {
+ mockContext.core.elasticsearch.client.asInternalUser.search
+ ).toHaveBeenCalledWith({
index: ['apm-*'],
body: {
foo: 'bar',
@@ -154,8 +158,8 @@ describe('setupRequest', () => {
body: { query: { bool: { filter: [{ term: 'someTerm' }] } } },
});
const params =
- mockContext.core.elasticsearch.legacy.client.callAsCurrentUser.mock
- .calls[0][1];
+ mockContext.core.elasticsearch.client.asCurrentUser.search.mock
+ .calls[0][0];
expect(params.body).toEqual({
query: {
bool: {
@@ -184,8 +188,8 @@ describe('setupRequest', () => {
}
);
const params =
- mockContext.core.elasticsearch.legacy.client.callAsCurrentUser.mock
- .calls[0][1];
+ mockContext.core.elasticsearch.client.asCurrentUser.search.mock
+ .calls[0][0];
expect(params.body).toEqual({
query: {
bool: {
@@ -214,8 +218,8 @@ describe('without a bool filter', () => {
},
});
const params =
- mockContext.core.elasticsearch.legacy.client.callAsCurrentUser.mock
- .calls[0][1];
+ mockContext.core.elasticsearch.client.asCurrentUser.search.mock
+ .calls[0][0];
expect(params.body).toEqual({
query: {
bool: {
@@ -245,8 +249,8 @@ describe('with includeFrozen=false', () => {
});
const params =
- mockContext.core.elasticsearch.legacy.client.callAsCurrentUser.mock
- .calls[0][1];
+ mockContext.core.elasticsearch.client.asCurrentUser.search.mock
+ .calls[0][0];
expect(params.ignore_throttled).toBe(true);
});
});
@@ -265,8 +269,8 @@ describe('with includeFrozen=true', () => {
});
const params =
- mockContext.core.elasticsearch.legacy.client.callAsCurrentUser.mock
- .calls[0][1];
+ mockContext.core.elasticsearch.client.asCurrentUser.search.mock
+ .calls[0][0];
expect(params.ignore_throttled).toBe(false);
});
});
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 47529de1042a1..947eb68e10093 100644
--- a/x-pack/plugins/apm/server/lib/helpers/setup_request.ts
+++ b/x-pack/plugins/apm/server/lib/helpers/setup_request.ts
@@ -86,7 +86,7 @@ export async function setupRequest(
const coreSetupRequest = {
indices,
apmEventClient: createApmEventClient({
- esClient: context.core.elasticsearch.legacy.client,
+ esClient: context.core.elasticsearch.client.asCurrentUser,
debug: context.params.query._debug,
request,
indices,
diff --git a/x-pack/plugins/apm/server/lib/services/annotations/get_stored_annotations.ts b/x-pack/plugins/apm/server/lib/services/annotations/get_stored_annotations.ts
index 3903298415aed..55395f3a4ca4e 100644
--- a/x-pack/plugins/apm/server/lib/services/annotations/get_stored_annotations.ts
+++ b/x-pack/plugins/apm/server/lib/services/annotations/get_stored_annotations.ts
@@ -4,7 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { LegacyAPICaller, Logger } from 'kibana/server';
+import { ElasticsearchClient, Logger } from 'kibana/server';
+import { unwrapEsResponse } from '../../../../../observability/server';
import { rangeFilter } from '../../../../common/utils/range_filter';
import { ESSearchResponse } from '../../../../../../typings/elasticsearch';
import { Annotation as ESAnnotation } from '../../../../../observability/common/annotations';
@@ -18,14 +19,14 @@ export async function getStoredAnnotations({
setup,
serviceName,
environment,
- apiCaller,
+ client,
annotationsClient,
logger,
}: {
setup: Setup & SetupTimeRange;
serviceName: string;
environment?: string;
- apiCaller: LegacyAPICaller;
+ client: ElasticsearchClient;
annotationsClient: ScopedAnnotationsClient;
logger: Logger;
}): Promise {
@@ -50,10 +51,12 @@ export async function getStoredAnnotations({
const response: ESSearchResponse<
ESAnnotation,
{ body: typeof body }
- > = (await apiCaller('search', {
- index: annotationsClient.index,
- body,
- })) as any;
+ > = await unwrapEsResponse(
+ client.search({
+ index: annotationsClient.index,
+ body,
+ })
+ );
return response.hits.hits.map((hit) => {
return {
diff --git a/x-pack/plugins/apm/server/lib/services/annotations/index.ts b/x-pack/plugins/apm/server/lib/services/annotations/index.ts
index 9516ed3777297..304485822be28 100644
--- a/x-pack/plugins/apm/server/lib/services/annotations/index.ts
+++ b/x-pack/plugins/apm/server/lib/services/annotations/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 { LegacyAPICaller, Logger } from 'kibana/server';
+import { ElasticsearchClient, Logger } from 'kibana/server';
import { ScopedAnnotationsClient } from '../../../../../observability/server';
import { getDerivedServiceAnnotations } from './get_derived_service_annotations';
import { Setup, SetupTimeRange } from '../../helpers/setup_request';
@@ -15,7 +15,7 @@ export async function getServiceAnnotations({
serviceName,
environment,
annotationsClient,
- apiCaller,
+ client,
logger,
}: {
serviceName: string;
@@ -23,7 +23,7 @@ export async function getServiceAnnotations({
setup: Setup & SetupTimeRange;
searchAggregatedTransactions: boolean;
annotationsClient?: ScopedAnnotationsClient;
- apiCaller: LegacyAPICaller;
+ client: ElasticsearchClient;
logger: Logger;
}) {
// start fetching derived annotations (based on transactions), but don't wait on it
@@ -41,7 +41,7 @@ export async function getServiceAnnotations({
serviceName,
environment,
annotationsClient,
- apiCaller,
+ client,
logger,
})
: [];
diff --git a/x-pack/plugins/apm/server/lib/settings/agent_configuration/create_agent_config_index.ts b/x-pack/plugins/apm/server/lib/settings/agent_configuration/create_agent_config_index.ts
index 83117db1167b5..190c99d0002d8 100644
--- a/x-pack/plugins/apm/server/lib/settings/agent_configuration/create_agent_config_index.ts
+++ b/x-pack/plugins/apm/server/lib/settings/agent_configuration/create_agent_config_index.ts
@@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { ILegacyClusterClient, Logger } from 'src/core/server';
+import { ElasticsearchClient, Logger } from 'src/core/server';
import {
createOrUpdateIndex,
MappingsDefinition,
@@ -13,18 +13,18 @@ import { APMConfig } from '../../..';
import { getApmIndicesConfig } from '../apm_indices/get_apm_indices';
export async function createApmAgentConfigurationIndex({
- esClient,
+ client,
config,
logger,
}: {
- esClient: ILegacyClusterClient;
+ client: ElasticsearchClient;
config: APMConfig;
logger: Logger;
}) {
const index = getApmIndicesConfig(config).apmAgentConfigurationIndex;
return createOrUpdateIndex({
index,
- apiCaller: esClient.callAsInternalUser,
+ client,
logger,
mappings,
});
diff --git a/x-pack/plugins/apm/server/lib/settings/custom_link/create_custom_link_index.ts b/x-pack/plugins/apm/server/lib/settings/custom_link/create_custom_link_index.ts
index 2bfe0d620e4e8..aa9e7411d1014 100644
--- a/x-pack/plugins/apm/server/lib/settings/custom_link/create_custom_link_index.ts
+++ b/x-pack/plugins/apm/server/lib/settings/custom_link/create_custom_link_index.ts
@@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { ILegacyClusterClient, Logger } from 'src/core/server';
+import { ElasticsearchClient, Logger } from 'src/core/server';
import {
createOrUpdateIndex,
MappingsDefinition,
@@ -13,18 +13,18 @@ import { APMConfig } from '../../..';
import { getApmIndicesConfig } from '../apm_indices/get_apm_indices';
export const createApmCustomLinkIndex = async ({
- esClient,
+ client,
config,
logger,
}: {
- esClient: ILegacyClusterClient;
+ client: ElasticsearchClient;
config: APMConfig;
logger: Logger;
}) => {
const index = getApmIndicesConfig(config).apmCustomLinkIndex;
return createOrUpdateIndex({
index,
- apiCaller: esClient.callAsInternalUser,
+ client,
logger,
mappings,
});
diff --git a/x-pack/plugins/apm/server/plugin.ts b/x-pack/plugins/apm/server/plugin.ts
index 09b75137e12df..3e01523aa8e31 100644
--- a/x-pack/plugins/apm/server/plugin.ts
+++ b/x-pack/plugins/apm/server/plugin.ts
@@ -173,7 +173,7 @@ export class APMPlugin implements Plugin {
context.core.uiSettings.client.get(UI_SETTINGS.SEARCH_INCLUDE_FROZEN),
]);
- const esClient = context.core.elasticsearch.legacy.client;
+ const esClient = context.core.elasticsearch.client.asCurrentUser;
return createApmEventClient({
debug: debug ?? false,
@@ -195,13 +195,13 @@ export class APMPlugin implements Plugin {
// create agent configuration index without blocking start lifecycle
createApmAgentConfigurationIndex({
- esClient: core.elasticsearch.legacy.client,
+ client: core.elasticsearch.client.asInternalUser,
config: this.currentConfig,
logger: this.logger,
});
// create custom action index without blocking start lifecycle
createApmCustomLinkIndex({
- esClient: core.elasticsearch.legacy.client,
+ client: core.elasticsearch.client.asInternalUser,
config: this.currentConfig,
logger: this.logger,
});
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 cfb31670bd521..721badf7fc025 100644
--- a/x-pack/plugins/apm/server/routes/create_api/index.ts
+++ b/x-pack/plugins/apm/server/routes/create_api/index.ts
@@ -147,7 +147,7 @@ function convertBoomToKibanaResponse(
error: Boom.Boom,
response: KibanaResponseFactory
) {
- const opts = { body: error.message };
+ const opts = { body: { message: error.message } };
switch (error.output.statusCode) {
case 404:
return response.notFound(opts);
@@ -159,9 +159,6 @@ function convertBoomToKibanaResponse(
return response.forbidden(opts);
default:
- return response.custom({
- statusCode: error.output.statusCode,
- ...opts,
- });
+ throw error;
}
}
diff --git a/x-pack/plugins/apm/server/routes/services.ts b/x-pack/plugins/apm/server/routes/services.ts
index bfc2ebf062ac3..ef74437f5f0e7 100644
--- a/x-pack/plugins/apm/server/routes/services.ts
+++ b/x-pack/plugins/apm/server/routes/services.ts
@@ -194,7 +194,7 @@ export const serviceAnnotationsRoute = createRoute({
serviceName,
environment,
annotationsClient,
- apiCaller: context.core.elasticsearch.legacy.client.callAsCurrentUser,
+ client: context.core.elasticsearch.client.asCurrentUser,
logger: context.logger,
});
},
diff --git a/x-pack/plugins/dashboard_mode/server/ui_settings.ts b/x-pack/plugins/dashboard_mode/server/ui_settings.ts
index f692ec8a33fc9..59de82cf7b3ab 100644
--- a/x-pack/plugins/dashboard_mode/server/ui_settings.ts
+++ b/x-pack/plugins/dashboard_mode/server/ui_settings.ts
@@ -22,6 +22,7 @@ export function getUiSettings(): Record> {
}),
value: [DASHBOARD_ONLY_USER_ROLE],
category: ['dashboard'],
+ sensitive: true,
deprecation: {
message: i18n.translate('xpack.dashboardMode.uiSettings.dashboardsOnlyRolesDeprecation', {
defaultMessage: 'This setting is deprecated and will be removed in Kibana 8.0.',
diff --git a/x-pack/plugins/data_enhanced/public/plugin.ts b/x-pack/plugins/data_enhanced/public/plugin.ts
index add7a966fee34..edcdea3997c8e 100644
--- a/x-pack/plugins/data_enhanced/public/plugin.ts
+++ b/x-pack/plugins/data_enhanced/public/plugin.ts
@@ -66,8 +66,8 @@ export class DataEnhancedPlugin
this.config = this.initializerContext.config.get();
if (this.config.search.sessions.enabled) {
- const { management: sessionsMgmtConfig } = this.config.search.sessions;
- registerSearchSessionsMgmt(core, sessionsMgmtConfig, { management });
+ const sessionsConfig = this.config.search.sessions;
+ registerSearchSessionsMgmt(core, sessionsConfig, { management });
}
}
diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/application/index.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/application/index.tsx
index 27f1482a4d20d..7347f070e91c3 100644
--- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/application/index.tsx
+++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/application/index.tsx
@@ -10,7 +10,7 @@ import type {
AppDependencies,
IManagementSectionsPluginsSetup,
IManagementSectionsPluginsStart,
- SessionsMgmtConfigSchema,
+ SessionsConfigSchema,
} from '../';
import { APP } from '../';
import { SearchSessionsMgmtAPI } from '../lib/api';
@@ -20,7 +20,7 @@ import { renderApp } from './render';
export class SearchSessionsMgmtApp {
constructor(
private coreSetup: CoreSetup,
- private config: SessionsMgmtConfigSchema,
+ private config: SessionsConfigSchema,
private params: ManagementAppMountParams,
private pluginsSetup: IManagementSectionsPluginsSetup
) {}
diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/extend_button.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/extend_button.tsx
index 4c8a7b0217688..d9c2bdabcbac1 100644
--- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/extend_button.tsx
+++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/extend_button.tsx
@@ -8,6 +8,8 @@ import { EuiConfirmModal, EuiOverlayMask } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import React, { useState } from 'react';
+import { Duration } from 'moment';
+import moment from 'moment';
import { SearchSessionsMgmtAPI } from '../../lib/api';
import { TableText } from '../';
import { OnActionComplete } from './types';
@@ -15,6 +17,8 @@ import { OnActionComplete } from './types';
interface ExtendButtonProps {
id: string;
name: string;
+ expires: string | null;
+ extendBy: Duration;
api: SearchSessionsMgmtAPI;
onActionComplete: OnActionComplete;
}
@@ -23,8 +27,11 @@ const ExtendConfirm = ({
onConfirmDismiss,
...props
}: ExtendButtonProps & { onConfirmDismiss: () => void }) => {
- const { id, name, api, onActionComplete } = props;
+ const { id, name, expires, api, extendBy, onActionComplete } = props;
const [isLoading, setIsLoading] = useState(false);
+ const extendByDuration = moment.duration(extendBy);
+
+ const newExpiration = moment(expires).add(extendByDuration);
const title = i18n.translate('xpack.data.mgmt.searchSessions.extendModal.title', {
defaultMessage: 'Extend search session expiration',
@@ -36,9 +43,10 @@ const ExtendConfirm = ({
defaultMessage: 'Cancel',
});
const message = i18n.translate('xpack.data.mgmt.searchSessions.extendModal.extendMessage', {
- defaultMessage: "When would you like the search session '{name}' to expire?",
+ defaultMessage: "The search session '{name}' expiration would be extended until {newExpires}.",
values: {
name,
+ newExpires: newExpiration.toLocaleString(),
},
});
@@ -49,7 +57,7 @@ const ExtendConfirm = ({
onCancel={onConfirmDismiss}
onConfirm={async () => {
setIsLoading(true);
- await api.sendExtend(id, '1');
+ await api.sendExtend(id, `${extendByDuration.asMilliseconds()}ms`);
onActionComplete();
}}
confirmButtonText={confirm}
diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/get_action.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/get_action.tsx
index 5bf0fbda5b5cc..c80cf6c244895 100644
--- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/get_action.tsx
+++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/get_action.tsx
@@ -17,7 +17,7 @@ import { ACTION, OnActionComplete } from './types';
export const getAction = (
api: SearchSessionsMgmtAPI,
actionType: string,
- { id, name, reloadUrl }: UISession,
+ { id, name, expires, reloadUrl }: UISession,
onActionComplete: OnActionComplete
): IClickActionDescriptor | null => {
switch (actionType) {
@@ -39,7 +39,16 @@ export const getAction = (
return {
iconType: extendSessionIcon,
textColor: 'default',
- label: ,
+ label: (
+
+ ),
};
default:
diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/main.test.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/main.test.tsx
index e01d1a28c5e54..14aea4bcbf59b 100644
--- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/main.test.tsx
+++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/main.test.tsx
@@ -12,7 +12,7 @@ import React from 'react';
import { act } from 'react-dom/test-utils';
import { coreMock } from 'src/core/public/mocks';
import { SessionsClient } from 'src/plugins/data/public/search';
-import { SessionsMgmtConfigSchema } from '..';
+import { SessionsConfigSchema } from '..';
import { SearchSessionsMgmtAPI } from '../lib/api';
import { AsyncSearchIntroDocumentation } from '../lib/documentation';
import { LocaleWrapper, mockUrls } from '../__mocks__';
@@ -20,7 +20,7 @@ import { SearchSessionsMgmtMain } from './main';
let mockCoreSetup: MockedKeys;
let mockCoreStart: MockedKeys;
-let mockConfig: SessionsMgmtConfigSchema;
+let mockConfig: SessionsConfigSchema;
let sessionsClient: SessionsClient;
let api: SearchSessionsMgmtAPI;
@@ -29,11 +29,14 @@ describe('Background Search Session Management Main', () => {
mockCoreSetup = coreMock.createSetup();
mockCoreStart = coreMock.createStart();
mockConfig = {
- expiresSoonWarning: moment.duration(1, 'days'),
- maxSessions: 2000,
- refreshInterval: moment.duration(1, 'seconds'),
- refreshTimeout: moment.duration(10, 'minutes'),
- };
+ defaultExpiration: moment.duration('7d'),
+ management: {
+ expiresSoonWarning: moment.duration(1, 'days'),
+ maxSessions: 2000,
+ refreshInterval: moment.duration(1, 'seconds'),
+ refreshTimeout: moment.duration(10, 'minutes'),
+ },
+ } as any;
sessionsClient = new SessionsClient({ http: mockCoreSetup.http });
diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/main.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/main.tsx
index 80c6a580dd183..cdf92d69f6438 100644
--- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/main.tsx
+++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/main.tsx
@@ -17,7 +17,7 @@ import {
import { FormattedMessage } from '@kbn/i18n/react';
import type { CoreStart, HttpStart } from 'kibana/public';
import React from 'react';
-import type { SessionsMgmtConfigSchema } from '../';
+import type { SessionsConfigSchema } from '../';
import type { SearchSessionsMgmtAPI } from '../lib/api';
import type { AsyncSearchIntroDocumentation } from '../lib/documentation';
import { TableText } from './';
@@ -29,7 +29,7 @@ interface Props {
api: SearchSessionsMgmtAPI;
http: HttpStart;
timezone: string;
- config: SessionsMgmtConfigSchema;
+ config: SessionsConfigSchema;
}
export function SearchSessionsMgmtMain({ documentation, ...tableProps }: Props) {
diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.test.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.test.tsx
index 51cec8f2afeff..a99fc26889a24 100644
--- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.test.tsx
+++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.test.tsx
@@ -13,14 +13,14 @@ import React from 'react';
import { coreMock } from 'src/core/public/mocks';
import { SessionsClient } from 'src/plugins/data/public/search';
import { SearchSessionStatus } from '../../../../../common/search';
-import { SessionsMgmtConfigSchema } from '../../';
+import { SessionsConfigSchema } from '../../';
import { SearchSessionsMgmtAPI } from '../../lib/api';
import { LocaleWrapper, mockUrls } from '../../__mocks__';
import { SearchSessionsMgmtTable } from './table';
let mockCoreSetup: MockedKeys;
let mockCoreStart: CoreStart;
-let mockConfig: SessionsMgmtConfigSchema;
+let mockConfig: SessionsConfigSchema;
let sessionsClient: SessionsClient;
let api: SearchSessionsMgmtAPI;
@@ -29,11 +29,14 @@ describe('Background Search Session Management Table', () => {
mockCoreSetup = coreMock.createSetup();
mockCoreStart = coreMock.createStart();
mockConfig = {
- expiresSoonWarning: moment.duration(1, 'days'),
- maxSessions: 2000,
- refreshInterval: moment.duration(1, 'seconds'),
- refreshTimeout: moment.duration(10, 'minutes'),
- };
+ defaultExpiration: moment.duration('7d'),
+ management: {
+ expiresSoonWarning: moment.duration(1, 'days'),
+ maxSessions: 2000,
+ refreshInterval: moment.duration(1, 'seconds'),
+ refreshTimeout: moment.duration(10, 'minutes'),
+ },
+ } as any;
sessionsClient = new SessionsClient({ http: mockCoreSetup.http });
api = new SearchSessionsMgmtAPI(sessionsClient, mockConfig, {
@@ -134,7 +137,10 @@ describe('Background Search Session Management Table', () => {
sessionsClient.find = jest.fn();
mockConfig = {
...mockConfig,
- refreshInterval: moment.duration(10, 'seconds'),
+ management: {
+ ...mockConfig.management,
+ refreshInterval: moment.duration(10, 'seconds'),
+ },
};
await act(async () => {
@@ -162,8 +168,11 @@ describe('Background Search Session Management Table', () => {
mockConfig = {
...mockConfig,
- refreshInterval: moment.duration(1, 'day'),
- refreshTimeout: moment.duration(2, 'days'),
+ management: {
+ ...mockConfig.management,
+ refreshInterval: moment.duration(1, 'day'),
+ refreshTimeout: moment.duration(2, 'days'),
+ },
};
await act(async () => {
diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.tsx
index f7aecdbd58a23..290fa4d74dfeb 100644
--- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.tsx
+++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.tsx
@@ -12,7 +12,7 @@ import React, { useCallback, useMemo, useRef, useEffect, useState } from 'react'
import useDebounce from 'react-use/lib/useDebounce';
import useInterval from 'react-use/lib/useInterval';
import { TableText } from '../';
-import { SessionsMgmtConfigSchema } from '../..';
+import { SessionsConfigSchema } from '../..';
import { SearchSessionsMgmtAPI } from '../../lib/api';
import { getColumns } from '../../lib/get_columns';
import { UISession } from '../../types';
@@ -26,7 +26,7 @@ interface Props {
core: CoreStart;
api: SearchSessionsMgmtAPI;
timezone: string;
- config: SessionsMgmtConfigSchema;
+ config: SessionsConfigSchema;
}
export function SearchSessionsMgmtTable({ core, api, timezone, config, ...props }: Props) {
@@ -35,9 +35,10 @@ export function SearchSessionsMgmtTable({ core, api, timezone, config, ...props
const [debouncedIsLoading, setDebouncedIsLoading] = useState(false);
const [pagination, setPagination] = useState({ pageIndex: 0 });
const showLatestResultsHandler = useRef();
- const refreshInterval = useMemo(() => moment.duration(config.refreshInterval).asMilliseconds(), [
- config.refreshInterval,
- ]);
+ const refreshInterval = useMemo(
+ () => moment.duration(config.management.refreshInterval).asMilliseconds(),
+ [config.management.refreshInterval]
+ );
// Debounce rendering the state of the Refresh button
useDebounce(
diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/index.ts b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/index.ts
index 76a5d440cd898..695252462794b 100644
--- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/index.ts
+++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/index.ts
@@ -33,7 +33,7 @@ export interface AppDependencies {
api: SearchSessionsMgmtAPI;
http: HttpStart;
i18n: I18nStart;
- config: SessionsMgmtConfigSchema;
+ config: SessionsConfigSchema;
}
export const APP = {
@@ -44,11 +44,11 @@ export const APP = {
}),
};
-export type SessionsMgmtConfigSchema = ConfigSchema['search']['sessions']['management'];
+export type SessionsConfigSchema = ConfigSchema['search']['sessions'];
export function registerSearchSessionsMgmt(
coreSetup: CoreSetup,
- config: SessionsMgmtConfigSchema,
+ config: SessionsConfigSchema,
services: IManagementSectionsPluginsSetup
) {
services.management.sections.section.kibana.registerApp({
diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.test.ts b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.test.ts
index 5b337dfd03eb1..068225d0df8c3 100644
--- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.test.ts
+++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.test.ts
@@ -11,14 +11,14 @@ import { coreMock } from 'src/core/public/mocks';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import type { SavedObjectsFindResponse } from 'src/core/server';
import { SessionsClient } from 'src/plugins/data/public/search';
-import type { SessionsMgmtConfigSchema } from '../';
+import type { SessionsConfigSchema } from '../';
import { SearchSessionStatus } from '../../../../common/search';
import { mockUrls } from '../__mocks__';
import { SearchSessionsMgmtAPI } from './api';
let mockCoreSetup: MockedKeys;
let mockCoreStart: MockedKeys;
-let mockConfig: SessionsMgmtConfigSchema;
+let mockConfig: SessionsConfigSchema;
let sessionsClient: SessionsClient;
describe('Search Sessions Management API', () => {
@@ -26,11 +26,14 @@ describe('Search Sessions Management API', () => {
mockCoreSetup = coreMock.createSetup();
mockCoreStart = coreMock.createStart();
mockConfig = {
- expiresSoonWarning: moment.duration('1d'),
- maxSessions: 2000,
- refreshInterval: moment.duration('1s'),
- refreshTimeout: moment.duration('10m'),
- };
+ defaultExpiration: moment.duration('7d'),
+ management: {
+ expiresSoonWarning: moment.duration(1, 'days'),
+ maxSessions: 2000,
+ refreshInterval: moment.duration(1, 'seconds'),
+ refreshTimeout: moment.duration(10, 'minutes'),
+ },
+ } as any;
sessionsClient = new SessionsClient({ http: mockCoreSetup.http });
});
@@ -93,8 +96,11 @@ describe('Search Sessions Management API', () => {
test('handle timeout error', async () => {
mockConfig = {
...mockConfig,
- refreshInterval: moment.duration(1, 'hours'),
- refreshTimeout: moment.duration(1, 'seconds'),
+ management: {
+ ...mockConfig.management,
+ refreshInterval: moment.duration(1, 'hours'),
+ refreshTimeout: moment.duration(1, 'seconds'),
+ },
};
sessionsClient.find = jest.fn().mockImplementation(async () => {
diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.ts b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.ts
index a2bd6b1a549be..c6a3d088b3cda 100644
--- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.ts
+++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.ts
@@ -10,7 +10,7 @@ import moment from 'moment';
import { from, race, timer } from 'rxjs';
import { mapTo, tap } from 'rxjs/operators';
import type { SharePluginStart } from 'src/plugins/share/public';
-import { SessionsMgmtConfigSchema } from '../';
+import { SessionsConfigSchema } from '../';
import type { ISessionsClient } from '../../../../../../../src/plugins/data/public';
import type { SearchSessionSavedObjectAttributes } from '../../../../common';
import { SearchSessionStatus } from '../../../../common/search';
@@ -47,10 +47,9 @@ async function getUrlFromState(
}
// Helper: factory for a function to map server objects to UI objects
-const mapToUISession = (
- urls: UrlGeneratorsStart,
- { expiresSoonWarning }: SessionsMgmtConfigSchema
-) => async (savedObject: SavedObject): Promise => {
+const mapToUISession = (urls: UrlGeneratorsStart, config: SessionsConfigSchema) => async (
+ savedObject: SavedObject
+): Promise => {
const {
name,
appId,
@@ -92,7 +91,7 @@ interface SearcgSessuibManagementDeps {
export class SearchSessionsMgmtAPI {
constructor(
private sessionsClient: ISessionsClient,
- private config: SessionsMgmtConfigSchema,
+ private config: SessionsConfigSchema,
private deps: SearcgSessuibManagementDeps
) {}
@@ -101,12 +100,14 @@ export class SearchSessionsMgmtAPI {
saved_objects: object[];
}
- const refreshTimeout = moment.duration(this.config.refreshTimeout);
+ const mgmtConfig = this.config.management;
+
+ const refreshTimeout = moment.duration(mgmtConfig.refreshTimeout);
const fetch$ = from(
this.sessionsClient.find({
page: 1,
- perPage: this.config.maxSessions,
+ perPage: mgmtConfig.maxSessions,
sortField: 'created',
sortOrder: 'asc',
})
@@ -149,6 +150,10 @@ export class SearchSessionsMgmtAPI {
this.deps.application.navigateToUrl(reloadUrl);
}
+ public getExtendByDuration() {
+ return this.config.defaultExpiration;
+ }
+
// Cancel and expire
public async sendCancel(id: string): Promise {
try {
diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.test.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.test.tsx
index ce441efea7385..ec4f2f63ceed1 100644
--- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.test.tsx
+++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.test.tsx
@@ -12,7 +12,7 @@ import moment from 'moment';
import { ReactElement } from 'react';
import { coreMock } from 'src/core/public/mocks';
import { SessionsClient } from 'src/plugins/data/public/search';
-import { SessionsMgmtConfigSchema } from '../';
+import { SessionsConfigSchema } from '../';
import { SearchSessionStatus } from '../../../../common/search';
import { OnActionComplete } from '../components';
import { UISession } from '../types';
@@ -22,7 +22,7 @@ import { getColumns } from './get_columns';
let mockCoreSetup: MockedKeys;
let mockCoreStart: CoreStart;
-let mockConfig: SessionsMgmtConfigSchema;
+let mockConfig: SessionsConfigSchema;
let api: SearchSessionsMgmtAPI;
let sessionsClient: SessionsClient;
let handleAction: OnActionComplete;
@@ -35,11 +35,14 @@ describe('Search Sessions Management table column factory', () => {
mockCoreSetup = coreMock.createSetup();
mockCoreStart = coreMock.createStart();
mockConfig = {
- expiresSoonWarning: moment.duration(1, 'days'),
- maxSessions: 2000,
- refreshInterval: moment.duration(1, 'seconds'),
- refreshTimeout: moment.duration(10, 'minutes'),
- };
+ defaultExpiration: moment.duration('7d'),
+ management: {
+ expiresSoonWarning: moment.duration(1, 'days'),
+ maxSessions: 2000,
+ refreshInterval: moment.duration(1, 'seconds'),
+ refreshTimeout: moment.duration(10, 'minutes'),
+ },
+ } as any;
sessionsClient = new SessionsClient({ http: mockCoreSetup.http });
api = new SearchSessionsMgmtAPI(sessionsClient, mockConfig, {
diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx
index 090336c37a98f..1ced354a28039 100644
--- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx
+++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx
@@ -20,7 +20,7 @@ import { capitalize } from 'lodash';
import React from 'react';
import { FormattedMessage } from 'react-intl';
import { RedirectAppLinks } from '../../../../../../../src/plugins/kibana_react/public';
-import { SessionsMgmtConfigSchema } from '../';
+import { SessionsConfigSchema } from '../';
import { SearchSessionStatus } from '../../../../common/search';
import { TableText } from '../components';
import { OnActionComplete, PopoverActionsMenu } from '../components';
@@ -45,7 +45,7 @@ function isSessionRestorable(status: SearchSessionStatus) {
export const getColumns = (
core: CoreStart,
api: SearchSessionsMgmtAPI,
- config: SessionsMgmtConfigSchema,
+ config: SessionsConfigSchema,
timezone: string,
onActionComplete: OnActionComplete
): Array> => {
diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_expiration_status.ts b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_expiration_status.ts
index 3c167d6dbe41a..5a52fce760d78 100644
--- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_expiration_status.ts
+++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_expiration_status.ts
@@ -6,9 +6,9 @@
import { i18n } from '@kbn/i18n';
import moment from 'moment';
-import { SessionsMgmtConfigSchema } from '../';
+import { SessionsConfigSchema } from '../';
-export const getExpirationStatus = (config: SessionsMgmtConfigSchema, expires: string | null) => {
+export const getExpirationStatus = (config: SessionsConfigSchema, expires: string | null) => {
const tNow = moment.utc().valueOf();
const tFuture = moment.utc(expires).valueOf();
@@ -16,7 +16,7 @@ export const getExpirationStatus = (config: SessionsMgmtConfigSchema, expires: s
// and the session was early expired when the browser refreshed the listing
const durationToExpire = moment.duration(tFuture - tNow);
const expiresInDays = Math.floor(durationToExpire.asDays());
- const sufficientDays = Math.ceil(moment.duration(config.expiresSoonWarning).asDays());
+ const sufficientDays = Math.ceil(moment.duration(config.management.expiresSoonWarning).asDays());
let toolTipContent = i18n.translate('xpack.data.mgmt.searchSessions.status.expiresSoonInDays', {
defaultMessage: 'Expires in {numDays} days',
diff --git a/x-pack/plugins/data_enhanced/public/search/ui/connected_search_session_indicator/connected_search_session_indicator.test.tsx b/x-pack/plugins/data_enhanced/public/search/ui/connected_search_session_indicator/connected_search_session_indicator.test.tsx
index 2c74f9c995a5a..f4bb7577bee53 100644
--- a/x-pack/plugins/data_enhanced/public/search/ui/connected_search_session_indicator/connected_search_session_indicator.test.tsx
+++ b/x-pack/plugins/data_enhanced/public/search/ui/connected_search_session_indicator/connected_search_session_indicator.test.tsx
@@ -28,6 +28,12 @@ timeFilter.getRefreshInterval.mockImplementation(() => refreshInterval$.getValue
beforeEach(() => {
refreshInterval$.next({ value: 0, pause: true });
+ sessionService.isSessionStorageReady.mockImplementation(() => true);
+ sessionService.getSearchSessionIndicatorUiConfig.mockImplementation(() => ({
+ isDisabled: () => ({
+ disabled: false,
+ }),
+ }));
});
test("shouldn't show indicator in case no active search session", async () => {
@@ -45,6 +51,22 @@ test("shouldn't show indicator in case no active search session", async () => {
expect(container).toMatchInlineSnapshot(``);
});
+test("shouldn't show indicator in case app hasn't opt-in", async () => {
+ const SearchSessionIndicator = createConnectedSearchSessionIndicator({
+ sessionService,
+ application: coreStart.application,
+ timeFilter,
+ });
+ const { getByTestId, container } = render();
+ sessionService.isSessionStorageReady.mockImplementation(() => false);
+
+ // make sure `searchSessionIndicator` isn't appearing after some time (lazy-loading)
+ await expect(
+ waitFor(() => getByTestId('searchSessionIndicator'), { timeout: 100 })
+ ).rejects.toThrow();
+ expect(container).toMatchInlineSnapshot(``);
+});
+
test('should show indicator in case there is an active search session', async () => {
const state$ = new BehaviorSubject(SearchSessionState.Loading);
const SearchSessionIndicator = createConnectedSearchSessionIndicator({
@@ -57,7 +79,7 @@ test('should show indicator in case there is an active search session', async ()
await waitFor(() => getByTestId('searchSessionIndicator'));
});
-test('should be disabled when permissions are off', async () => {
+test('should be disabled in case uiConfig says so ', async () => {
const state$ = new BehaviorSubject(SearchSessionState.Loading);
coreStart.application.currentAppId$ = new BehaviorSubject('discover');
(coreStart.application.capabilities as any) = {
@@ -65,6 +87,12 @@ test('should be disabled when permissions are off', async () => {
storeSearchSession: false,
},
};
+ sessionService.getSearchSessionIndicatorUiConfig.mockImplementation(() => ({
+ isDisabled: () => ({
+ disabled: true,
+ reasonText: 'reason',
+ }),
+ }));
const SearchSessionIndicator = createConnectedSearchSessionIndicator({
sessionService: { ...sessionService, state$ },
application: coreStart.application,
@@ -80,12 +108,7 @@ test('should be disabled when permissions are off', async () => {
test('should be disabled during auto-refresh', async () => {
const state$ = new BehaviorSubject(SearchSessionState.Loading);
- coreStart.application.currentAppId$ = new BehaviorSubject('discover');
- (coreStart.application.capabilities as any) = {
- discover: {
- storeSearchSession: true,
- },
- };
+
const SearchSessionIndicator = createConnectedSearchSessionIndicator({
sessionService: { ...sessionService, state$ },
application: coreStart.application,
diff --git a/x-pack/plugins/data_enhanced/public/search/ui/connected_search_session_indicator/connected_search_session_indicator.tsx b/x-pack/plugins/data_enhanced/public/search/ui/connected_search_session_indicator/connected_search_session_indicator.tsx
index 5c8c01064bff4..59c1bb4a223b1 100644
--- a/x-pack/plugins/data_enhanced/public/search/ui/connected_search_session_indicator/connected_search_session_indicator.tsx
+++ b/x-pack/plugins/data_enhanced/public/search/ui/connected_search_session_indicator/connected_search_session_indicator.tsx
@@ -29,35 +29,14 @@ export const createConnectedSearchSessionIndicator = ({
.getRefreshIntervalUpdate$()
.pipe(map(isAutoRefreshEnabled), distinctUntilChanged());
- const getCapabilitiesByAppId = (
- capabilities: ApplicationStart['capabilities'],
- appId?: string
- ) => {
- switch (appId) {
- case 'dashboards':
- return capabilities.dashboard;
- case 'discover':
- return capabilities.discover;
- default:
- return undefined;
- }
- };
-
return () => {
const state = useObservable(sessionService.state$.pipe(debounceTime(500)));
const autoRefreshEnabled = useObservable(isAutoRefreshEnabled$, isAutoRefreshEnabled());
- const appId = useObservable(application.currentAppId$, undefined);
+ const isDisabledByApp = sessionService.getSearchSessionIndicatorUiConfig().isDisabled();
let disabled = false;
let disabledReasonText: string = '';
- if (getCapabilitiesByAppId(application.capabilities, appId)?.storeSearchSession !== true) {
- disabled = true;
- disabledReasonText = i18n.translate('xpack.data.searchSessionIndicator.noCapability', {
- defaultMessage: "You don't have permissions to send to background.",
- });
- }
-
if (autoRefreshEnabled) {
disabled = true;
disabledReasonText = i18n.translate(
@@ -68,6 +47,12 @@ export const createConnectedSearchSessionIndicator = ({
);
}
+ if (isDisabledByApp.disabled) {
+ disabled = true;
+ disabledReasonText = isDisabledByApp.reasonText;
+ }
+
+ if (!sessionService.isSessionStorageReady()) return null;
if (!state) return null;
return (
diff --git a/x-pack/plugins/data_enhanced/server/search/session/monitoring_task.ts b/x-pack/plugins/data_enhanced/server/search/session/monitoring_task.ts
index d32dcf72a4205..332e69b119bb6 100644
--- a/x-pack/plugins/data_enhanced/server/search/session/monitoring_task.ts
+++ b/x-pack/plugins/data_enhanced/server/search/session/monitoring_task.ts
@@ -76,7 +76,7 @@ export async function scheduleSearchSessionsTasks(
params: {},
});
- logger.debug(`Background search task, scheduled to run`);
+ logger.debug(`Search sessions task, scheduled to run`);
} catch (e) {
logger.debug(`Error scheduling task, received ${e.message}`);
}
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/__mocks__/content_sources.mock.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/__mocks__/content_sources.mock.ts
index 0e0d1fa864033..efae95f83034e 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/__mocks__/content_sources.mock.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/__mocks__/content_sources.mock.ts
@@ -323,3 +323,14 @@ export const mostRecentIndexJob = {
activeReindexJobId: '123',
numDocumentsWithErrors: 1,
};
+
+export const contentItems = [
+ {
+ id: '1234',
+ last_updated: '2021-01-21',
+ },
+ {
+ id: '1235',
+ last_updated: '2021-01-20',
+ },
+];
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/__mocks__/meta.mock.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/__mocks__/meta.mock.ts
index e596ea5d7e948..acfbad1400c66 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/__mocks__/meta.mock.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/__mocks__/meta.mock.ts
@@ -6,10 +6,11 @@
import { DEFAULT_META } from '../../shared/constants';
-export const mockMeta = {
+export const meta = {
...DEFAULT_META,
page: {
current: 1,
+ size: 5,
total_results: 50,
total_pages: 5,
},
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.tsx
index 65a2c7a4a44dd..d10de7a770171 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.tsx
@@ -78,6 +78,12 @@ export const WorkplaceSearchConfigured: React.FC = (props) => {
{errorConnecting ? : }
+
+ {/* TODO: replace Layout with PrivateSourcesLayout (needs to be created) */}
+ >} restrictWidth readOnlyMode={readOnlyMode}>
+
+
+
} />}
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_content.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_content.test.tsx
index c445a7aec04f6..a404ae508c130 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_content.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_content.test.tsx
@@ -20,8 +20,8 @@ import {
EuiLink,
} from '@elastic/eui';
-import { mockMeta } from '../../../__mocks__/meta.mock';
-import { fullContentSources } from '../../../__mocks__/content_sources.mock';
+import { meta } from '../../../__mocks__/meta.mock';
+import { fullContentSources, contentItems } from '../../../__mocks__/content_sources.mock';
import { DEFAULT_META } from '../../../../shared/constants';
import { ComponentLoader } from '../../../components/shared/component_loader';
@@ -38,17 +38,8 @@ describe('SourceContent', () => {
const mockValues = {
contentSource: fullContentSources[0],
- contentMeta: mockMeta,
- contentItems: [
- {
- id: '1234',
- last_updated: '2021-01-21',
- },
- {
- id: '1235',
- last_updated: '2021-01-20',
- },
- ],
+ contentMeta: meta,
+ contentItems,
contentFilterValue: '',
dataLoading: false,
sectionLoading: false,
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/private_sources.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/private_sources.tsx
index a1a76c678866c..c11cdaa5ec36f 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/private_sources.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/private_sources.tsx
@@ -12,7 +12,7 @@ import { EuiCallOut, EuiEmptyPrompt, EuiSpacer, EuiPanel } from '@elastic/eui';
import { LicensingLogic } from '../../../../applications/shared/licensing';
-import { ADD_SOURCE_PATH } from '../../routes';
+import { ADD_SOURCE_PATH, getSourcesPath } from '../../routes';
import noSharedSourcesIcon from '../../assets/share_circle.svg';
@@ -74,12 +74,17 @@ export const PrivateSources: React.FC = () => {
sidebarLinks.push({
title: PRIVATE_LINK_TITLE,
iconType: 'plusInCircle',
- path: ADD_SOURCE_PATH,
+ path: getSourcesPath(ADD_SOURCE_PATH, false),
});
}
const headerAction = (
-
+
{PRIVATE_LINK_TITLE}
);
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.test.ts
new file mode 100644
index 0000000000000..a0efbfe4aca1d
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.test.ts
@@ -0,0 +1,444 @@
+/*
+ * 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 {
+ LogicMounter,
+ mockFlashMessageHelpers,
+ mockHttpValues,
+ mockKibanaValues,
+ expectedAsyncError,
+} from '../../../__mocks__';
+
+import { AppLogic } from '../../app_logic';
+jest.mock('../../app_logic', () => ({
+ AppLogic: { values: { isOrganization: true } },
+}));
+
+import {
+ fullContentSources,
+ sourceConfigData,
+ contentItems,
+} from '../../__mocks__/content_sources.mock';
+import { meta } from '../../__mocks__/meta.mock';
+
+import { DEFAULT_META } from '../../../shared/constants';
+import { NOT_FOUND_PATH } from '../../routes';
+
+import { SourceLogic } from './source_logic';
+
+describe('SourceLogic', () => {
+ const { http } = mockHttpValues;
+ const {
+ clearFlashMessages,
+ flashAPIErrors,
+ setSuccessMessage,
+ setQueuedSuccessMessage,
+ } = mockFlashMessageHelpers;
+ const { navigateToUrl } = mockKibanaValues;
+ const { mount, getListeners } = new LogicMounter(SourceLogic);
+
+ const contentSource = fullContentSources[0];
+
+ const defaultValues = {
+ contentSource: {},
+ contentItems: [],
+ sourceConfigData: {},
+ dataLoading: true,
+ sectionLoading: true,
+ buttonLoading: false,
+ contentMeta: DEFAULT_META,
+ contentFilterValue: '',
+ };
+
+ const searchServerResponse = {
+ results: contentItems,
+ meta,
+ };
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ mount();
+ });
+
+ it('has expected default values', () => {
+ expect(SourceLogic.values).toEqual(defaultValues);
+ });
+
+ describe('actions', () => {
+ it('onInitializeSource', () => {
+ SourceLogic.actions.onInitializeSource(contentSource);
+
+ expect(SourceLogic.values.contentSource).toEqual(contentSource);
+ expect(SourceLogic.values.dataLoading).toEqual(false);
+ });
+
+ it('onUpdateSourceName', () => {
+ const NAME = 'foo';
+ SourceLogic.actions.onInitializeSource(contentSource);
+ SourceLogic.actions.onUpdateSourceName(NAME);
+
+ expect(SourceLogic.values.contentSource).toEqual({
+ ...contentSource,
+ name: NAME,
+ });
+ expect(setSuccessMessage).toHaveBeenCalled();
+ });
+
+ it('setSourceConfigData', () => {
+ SourceLogic.actions.setSourceConfigData(sourceConfigData);
+
+ expect(SourceLogic.values.sourceConfigData).toEqual(sourceConfigData);
+ expect(SourceLogic.values.dataLoading).toEqual(false);
+ });
+
+ it('setSearchResults', () => {
+ SourceLogic.actions.setSearchResults(searchServerResponse);
+
+ expect(SourceLogic.values.contentItems).toEqual(contentItems);
+ expect(SourceLogic.values.contentMeta).toEqual(meta);
+ expect(SourceLogic.values.sectionLoading).toEqual(false);
+ });
+
+ it('setContentFilterValue', () => {
+ const VALUE = 'bar';
+ SourceLogic.actions.setSearchResults(searchServerResponse);
+ SourceLogic.actions.onInitializeSource(contentSource);
+ SourceLogic.actions.setContentFilterValue(VALUE);
+
+ expect(SourceLogic.values.contentMeta).toEqual({
+ ...meta,
+ page: {
+ ...meta.page,
+ current: DEFAULT_META.page.current,
+ },
+ });
+ expect(SourceLogic.values.contentFilterValue).toEqual(VALUE);
+ });
+
+ it('setActivePage', () => {
+ const PAGE = 2;
+ SourceLogic.actions.setSearchResults(searchServerResponse);
+ SourceLogic.actions.setActivePage(PAGE);
+
+ expect(SourceLogic.values.contentMeta).toEqual({
+ ...meta,
+ page: {
+ ...meta.page,
+ current: PAGE,
+ },
+ });
+ });
+
+ it('setButtonNotLoading', () => {
+ // Set button state to loading
+ SourceLogic.actions.removeContentSource(contentSource.id);
+ SourceLogic.actions.setButtonNotLoading();
+
+ expect(SourceLogic.values.buttonLoading).toEqual(false);
+ });
+ });
+
+ describe('listeners', () => {
+ describe('initializeSource', () => {
+ it('calls API and sets values (org)', async () => {
+ const onInitializeSourceSpy = jest.spyOn(SourceLogic.actions, 'onInitializeSource');
+ const promise = Promise.resolve(contentSource);
+ http.get.mockReturnValue(promise);
+ SourceLogic.actions.initializeSource(contentSource.id);
+
+ expect(http.get).toHaveBeenCalledWith('/api/workplace_search/org/sources/123');
+ await promise;
+ expect(onInitializeSourceSpy).toHaveBeenCalledWith(contentSource);
+ });
+
+ it('calls API and sets values (account)', async () => {
+ AppLogic.values.isOrganization = false;
+
+ const onInitializeSourceSpy = jest.spyOn(SourceLogic.actions, 'onInitializeSource');
+ const promise = Promise.resolve(contentSource);
+ http.get.mockReturnValue(promise);
+ SourceLogic.actions.initializeSource(contentSource.id);
+
+ expect(http.get).toHaveBeenCalledWith('/api/workplace_search/account/sources/123');
+ await promise;
+ expect(onInitializeSourceSpy).toHaveBeenCalledWith(contentSource);
+ });
+
+ it('handles federated source', async () => {
+ AppLogic.values.isOrganization = false;
+
+ const initializeFederatedSummarySpy = jest.spyOn(
+ SourceLogic.actions,
+ 'initializeFederatedSummary'
+ );
+ const promise = Promise.resolve({
+ ...contentSource,
+ isFederatedSource: true,
+ });
+ http.get.mockReturnValue(promise);
+ SourceLogic.actions.initializeSource(contentSource.id);
+
+ expect(http.get).toHaveBeenCalledWith('/api/workplace_search/account/sources/123');
+ await promise;
+ expect(initializeFederatedSummarySpy).toHaveBeenCalledWith(contentSource.id);
+ });
+
+ it('handles error', async () => {
+ const error = {
+ response: {
+ error: 'this is an error',
+ status: 400,
+ },
+ };
+ const promise = Promise.reject(error);
+ http.get.mockReturnValue(promise);
+ SourceLogic.actions.initializeSource(contentSource.id);
+ await expectedAsyncError(promise);
+
+ expect(flashAPIErrors).toHaveBeenCalledWith(error);
+ });
+
+ it('handles not found state', async () => {
+ const error = {
+ response: {
+ error: 'this is an error',
+ status: 404,
+ },
+ };
+ const promise = Promise.reject(error);
+ http.get.mockReturnValue(promise);
+ SourceLogic.actions.initializeSource(contentSource.id);
+ await expectedAsyncError(promise);
+
+ expect(navigateToUrl).toHaveBeenCalledWith(NOT_FOUND_PATH);
+ });
+ });
+
+ describe('initializeFederatedSummary', () => {
+ it('calls API and sets values', async () => {
+ const onUpdateSummarySpy = jest.spyOn(SourceLogic.actions, 'onUpdateSummary');
+ const promise = Promise.resolve(contentSource);
+ http.get.mockReturnValue(promise);
+ SourceLogic.actions.initializeFederatedSummary(contentSource.id);
+
+ expect(http.get).toHaveBeenCalledWith(
+ '/api/workplace_search/org/sources/123/federated_summary'
+ );
+ await promise;
+ expect(onUpdateSummarySpy).toHaveBeenCalledWith(contentSource.summary);
+ });
+
+ it('handles error', async () => {
+ const error = {
+ response: {
+ error: 'this is an error',
+ status: 400,
+ },
+ };
+ const promise = Promise.reject(error);
+ http.get.mockReturnValue(promise);
+ SourceLogic.actions.initializeFederatedSummary(contentSource.id);
+ await expectedAsyncError(promise);
+
+ expect(flashAPIErrors).toHaveBeenCalledWith(error);
+ });
+ });
+
+ describe('searchContentSourceDocuments', () => {
+ const mockBreakpoint = jest.fn();
+ const values = { contentMeta: meta, contentFilterValue: '' };
+ const actions = { setSearchResults: jest.fn() };
+ const { searchContentSourceDocuments } = getListeners({
+ values,
+ actions,
+ });
+
+ it('calls API and sets values (org)', async () => {
+ AppLogic.values.isOrganization = true;
+ const promise = Promise.resolve(searchServerResponse);
+ http.post.mockReturnValue(promise);
+
+ await searchContentSourceDocuments({ sourceId: contentSource.id }, mockBreakpoint);
+ expect(http.post).toHaveBeenCalledWith('/api/workplace_search/org/sources/123/documents', {
+ body: JSON.stringify({ query: '', page: meta.page }),
+ });
+
+ await promise;
+ expect(actions.setSearchResults).toHaveBeenCalledWith(searchServerResponse);
+ });
+
+ it('calls API and sets values (account)', async () => {
+ AppLogic.values.isOrganization = false;
+ const promise = Promise.resolve(searchServerResponse);
+ http.post.mockReturnValue(promise);
+
+ SourceLogic.actions.searchContentSourceDocuments(contentSource.id);
+ await searchContentSourceDocuments({ sourceId: contentSource.id }, mockBreakpoint);
+ expect(http.post).toHaveBeenCalledWith(
+ '/api/workplace_search/account/sources/123/documents',
+ {
+ body: JSON.stringify({ query: '', page: meta.page }),
+ }
+ );
+
+ await promise;
+ expect(actions.setSearchResults).toHaveBeenCalledWith(searchServerResponse);
+ });
+
+ it('handles error', async () => {
+ const error = {
+ response: {
+ error: 'this is an error',
+ status: 400,
+ },
+ };
+ const promise = Promise.reject(error);
+ http.post.mockReturnValue(promise);
+
+ await searchContentSourceDocuments({ sourceId: contentSource.id }, mockBreakpoint);
+ await expectedAsyncError(promise);
+
+ expect(flashAPIErrors).toHaveBeenCalledWith(error);
+ });
+ });
+
+ describe('updateContentSource', () => {
+ it('calls API and sets values (org)', async () => {
+ AppLogic.values.isOrganization = true;
+
+ const onUpdateSourceNameSpy = jest.spyOn(SourceLogic.actions, 'onUpdateSourceName');
+ const promise = Promise.resolve(contentSource);
+ http.patch.mockReturnValue(promise);
+ SourceLogic.actions.updateContentSource(contentSource.id, contentSource);
+
+ expect(http.patch).toHaveBeenCalledWith('/api/workplace_search/org/sources/123/settings', {
+ body: JSON.stringify({ content_source: contentSource }),
+ });
+ await promise;
+ expect(onUpdateSourceNameSpy).toHaveBeenCalledWith(contentSource.name);
+ });
+
+ it('calls API and sets values (account)', async () => {
+ AppLogic.values.isOrganization = false;
+
+ const onUpdateSourceNameSpy = jest.spyOn(SourceLogic.actions, 'onUpdateSourceName');
+ const promise = Promise.resolve(contentSource);
+ http.patch.mockReturnValue(promise);
+ SourceLogic.actions.updateContentSource(contentSource.id, contentSource);
+
+ expect(http.patch).toHaveBeenCalledWith(
+ '/api/workplace_search/account/sources/123/settings',
+ {
+ body: JSON.stringify({ content_source: contentSource }),
+ }
+ );
+ await promise;
+ expect(onUpdateSourceNameSpy).toHaveBeenCalledWith(contentSource.name);
+ });
+
+ it('handles error', async () => {
+ const error = {
+ response: {
+ error: 'this is an error',
+ status: 400,
+ },
+ };
+ const promise = Promise.reject(error);
+ http.patch.mockReturnValue(promise);
+ SourceLogic.actions.updateContentSource(contentSource.id, contentSource);
+ await expectedAsyncError(promise);
+
+ expect(flashAPIErrors).toHaveBeenCalledWith(error);
+ });
+ });
+
+ describe('removeContentSource', () => {
+ it('calls API and sets values (org)', async () => {
+ AppLogic.values.isOrganization = true;
+
+ const setButtonNotLoadingSpy = jest.spyOn(SourceLogic.actions, 'setButtonNotLoading');
+ const promise = Promise.resolve(contentSource);
+ http.delete.mockReturnValue(promise);
+ SourceLogic.actions.removeContentSource(contentSource.id);
+
+ expect(clearFlashMessages).toHaveBeenCalled();
+ expect(http.delete).toHaveBeenCalledWith('/api/workplace_search/org/sources/123');
+ await promise;
+ expect(setQueuedSuccessMessage).toHaveBeenCalled();
+ expect(setButtonNotLoadingSpy).toHaveBeenCalled();
+ });
+
+ it('calls API and sets values (account)', async () => {
+ AppLogic.values.isOrganization = false;
+
+ const setButtonNotLoadingSpy = jest.spyOn(SourceLogic.actions, 'setButtonNotLoading');
+ const promise = Promise.resolve(contentSource);
+ http.delete.mockReturnValue(promise);
+ SourceLogic.actions.removeContentSource(contentSource.id);
+
+ expect(clearFlashMessages).toHaveBeenCalled();
+ expect(http.delete).toHaveBeenCalledWith('/api/workplace_search/account/sources/123');
+ await promise;
+ expect(setButtonNotLoadingSpy).toHaveBeenCalled();
+ });
+
+ it('handles error', async () => {
+ const error = {
+ response: {
+ error: 'this is an error',
+ status: 400,
+ },
+ };
+ const promise = Promise.reject(error);
+ http.delete.mockReturnValue(promise);
+ SourceLogic.actions.removeContentSource(contentSource.id);
+ await expectedAsyncError(promise);
+
+ expect(flashAPIErrors).toHaveBeenCalledWith(error);
+ });
+ });
+
+ describe('getSourceConfigData', () => {
+ const serviceType = 'github';
+
+ it('calls API and sets values', async () => {
+ AppLogic.values.isOrganization = true;
+
+ const setSourceConfigDataSpy = jest.spyOn(SourceLogic.actions, 'setSourceConfigData');
+ const promise = Promise.resolve(contentSource);
+ http.get.mockReturnValue(promise);
+ SourceLogic.actions.getSourceConfigData(serviceType);
+
+ expect(http.get).toHaveBeenCalledWith(
+ `/api/workplace_search/org/settings/connectors/${serviceType}`
+ );
+ await promise;
+ expect(setSourceConfigDataSpy).toHaveBeenCalled();
+ });
+
+ it('handles error', async () => {
+ const error = {
+ response: {
+ error: 'this is an error',
+ status: 400,
+ },
+ };
+ const promise = Promise.reject(error);
+ http.get.mockReturnValue(promise);
+ SourceLogic.actions.getSourceConfigData(serviceType);
+ await expectedAsyncError(promise);
+
+ expect(flashAPIErrors).toHaveBeenCalledWith(error);
+ });
+ });
+
+ it('resetSourceState', () => {
+ SourceLogic.actions.resetSourceState();
+
+ expect(clearFlashMessages).toHaveBeenCalled();
+ });
+ });
+});
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.ts
index 2de70009c56a2..ba5c29c190f95 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.ts
@@ -126,29 +126,22 @@ export const SourceLogic = kea>({
onInitializeSource: () => false,
setSourceConfigData: () => false,
resetSourceState: () => false,
- setPreContentSourceConfigData: () => false,
},
],
buttonLoading: [
false,
{
setButtonNotLoading: () => false,
- setSourceConnectData: () => false,
setSourceConfigData: () => false,
resetSourceState: () => false,
removeContentSource: () => true,
- saveSourceConfig: () => true,
- getSourceConnectData: () => true,
- createContentSource: () => true,
},
],
sectionLoading: [
true,
{
searchContentSourceDocuments: () => true,
- getPreContentSourceConfigData: () => true,
setSearchResults: () => false,
- setPreContentSourceConfigData: () => false,
},
],
contentItems: [
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_logic.test.ts
new file mode 100644
index 0000000000000..11e3a52081637
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_logic.test.ts
@@ -0,0 +1,319 @@
+/*
+ * 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 {
+ LogicMounter,
+ mockFlashMessageHelpers,
+ mockHttpValues,
+ expectedAsyncError,
+} from '../../../__mocks__';
+
+import { AppLogic } from '../../app_logic';
+jest.mock('../../app_logic', () => ({
+ AppLogic: { values: { isOrganization: true } },
+}));
+
+import { configuredSources, contentSources } from '../../__mocks__/content_sources.mock';
+
+import { SourcesLogic, fetchSourceStatuses, POLLING_INTERVAL } from './sources_logic';
+
+describe('SourcesLogic', () => {
+ const { http } = mockHttpValues;
+ const { flashAPIErrors, setQueuedSuccessMessage } = mockFlashMessageHelpers;
+ const { mount, unmount } = new LogicMounter(SourcesLogic);
+
+ const contentSource = contentSources[0];
+
+ const defaultValues = {
+ contentSources: [],
+ privateContentSources: [],
+ sourceData: [],
+ availableSources: [],
+ configuredSources: [],
+ serviceTypes: [],
+ permissionsModal: null,
+ dataLoading: true,
+ serverStatuses: null,
+ };
+
+ const serverStatuses = [
+ {
+ id: '123',
+ name: 'my source',
+ service_type: 'github',
+ status: {
+ status: 'this is a thing',
+ synced_at: '2021-01-25',
+ error_reason: 1,
+ },
+ },
+ ];
+
+ const serverResponse = {
+ contentSources,
+ privateContentSources: contentSources,
+ serviceTypes: configuredSources,
+ };
+
+ beforeEach(() => {
+ jest.useFakeTimers();
+ jest.clearAllMocks();
+ mount();
+ });
+
+ it('has expected default values', () => {
+ expect(SourcesLogic.values).toEqual(defaultValues);
+ });
+
+ it('handles unmounting', async () => {
+ unmount();
+ expect(clearInterval).toHaveBeenCalled();
+ });
+
+ describe('actions', () => {
+ describe('onInitializeSources', () => {
+ it('sets values', () => {
+ SourcesLogic.actions.onInitializeSources(serverResponse);
+
+ expect(SourcesLogic.values.contentSources).toEqual(contentSources);
+ expect(SourcesLogic.values.privateContentSources).toEqual(contentSources);
+ expect(SourcesLogic.values.serviceTypes).toEqual(configuredSources);
+ expect(SourcesLogic.values.dataLoading).toEqual(false);
+ });
+
+ it('fallbacks', () => {
+ SourcesLogic.actions.onInitializeSources({
+ contentSources,
+ serviceTypes: undefined as any,
+ });
+
+ expect(SourcesLogic.values.serviceTypes).toEqual([]);
+ expect(SourcesLogic.values.privateContentSources).toEqual([]);
+ });
+ });
+
+ it('setServerSourceStatuses', () => {
+ SourcesLogic.actions.setServerSourceStatuses(serverStatuses);
+ const source = serverStatuses[0];
+
+ expect(SourcesLogic.values.serverStatuses).toEqual({
+ [source.id]: source.status.status,
+ });
+ });
+
+ it('onSetSearchability', () => {
+ const id = contentSources[0].id;
+ const updatedSources = [...contentSources];
+ updatedSources[0].searchable = false;
+ SourcesLogic.actions.onInitializeSources(serverResponse);
+ SourcesLogic.actions.onSetSearchability(id, false);
+
+ expect(SourcesLogic.values.contentSources).toEqual(updatedSources);
+ expect(SourcesLogic.values.privateContentSources).toEqual(updatedSources);
+ });
+
+ describe('setAddedSource', () => {
+ it('configured', () => {
+ const name = contentSources[0].name;
+ SourcesLogic.actions.setAddedSource(name, false, 'custom');
+
+ expect(SourcesLogic.values.permissionsModal).toEqual({
+ addedSourceName: name,
+ additionalConfiguration: false,
+ serviceType: 'custom',
+ });
+ expect(setQueuedSuccessMessage).toHaveBeenCalledWith('Successfully connected source. ');
+ });
+
+ it('unconfigured', () => {
+ const name = contentSources[0].name;
+ SourcesLogic.actions.setAddedSource(name, true, 'custom');
+
+ expect(SourcesLogic.values.permissionsModal).toEqual({
+ addedSourceName: name,
+ additionalConfiguration: true,
+ serviceType: 'custom',
+ });
+ expect(setQueuedSuccessMessage).toHaveBeenCalledWith(
+ 'Successfully connected source. This source requires additional configuration.'
+ );
+ });
+ });
+
+ it('resetPermissionsModal', () => {
+ SourcesLogic.actions.resetPermissionsModal();
+
+ expect(SourcesLogic.values.permissionsModal).toEqual(null);
+ });
+ });
+
+ describe('listeners', () => {
+ describe('initializeSources', () => {
+ it('calls API and sets values (org)', async () => {
+ AppLogic.values.isOrganization = true;
+ const pollForSourceStatusChangesSpy = jest.spyOn(
+ SourcesLogic.actions,
+ 'pollForSourceStatusChanges'
+ );
+ const onInitializeSourcesSpy = jest.spyOn(SourcesLogic.actions, 'onInitializeSources');
+ const promise = Promise.resolve(contentSources);
+ http.get.mockReturnValue(promise);
+ SourcesLogic.actions.initializeSources();
+
+ expect(http.get).toHaveBeenCalledWith('/api/workplace_search/org/sources');
+ await promise;
+ expect(pollForSourceStatusChangesSpy).toHaveBeenCalled();
+ expect(onInitializeSourcesSpy).toHaveBeenCalledWith(contentSources);
+ });
+
+ it('calls API (account)', async () => {
+ AppLogic.values.isOrganization = false;
+ const promise = Promise.resolve(contentSource);
+ http.get.mockReturnValue(promise);
+ SourcesLogic.actions.initializeSources();
+
+ expect(http.get).toHaveBeenCalledWith('/api/workplace_search/account/sources');
+ });
+
+ it('handles error', async () => {
+ const error = {
+ response: {
+ error: 'this is an error',
+ status: 400,
+ },
+ };
+ const promise = Promise.reject(error);
+ http.get.mockReturnValue(promise);
+ SourcesLogic.actions.initializeSources();
+ await expectedAsyncError(promise);
+
+ expect(flashAPIErrors).toHaveBeenCalledWith(error);
+ });
+ });
+
+ describe('setSourceSearchability', () => {
+ const id = contentSources[0].id;
+
+ it('calls API and sets values (org)', async () => {
+ AppLogic.values.isOrganization = true;
+ const onSetSearchability = jest.spyOn(SourcesLogic.actions, 'onSetSearchability');
+ const promise = Promise.resolve(contentSources);
+ http.put.mockReturnValue(promise);
+ SourcesLogic.actions.setSourceSearchability(id, true);
+
+ expect(http.put).toHaveBeenCalledWith('/api/workplace_search/org/sources/123/searchable', {
+ body: JSON.stringify({ searchable: true }),
+ });
+ await promise;
+ expect(onSetSearchability).toHaveBeenCalledWith(id, true);
+ });
+
+ it('calls API (account)', async () => {
+ AppLogic.values.isOrganization = false;
+ const promise = Promise.resolve(contentSource);
+ http.put.mockReturnValue(promise);
+ SourcesLogic.actions.setSourceSearchability(id, true);
+
+ expect(http.put).toHaveBeenCalledWith(
+ '/api/workplace_search/account/sources/123/searchable',
+ {
+ body: JSON.stringify({ searchable: true }),
+ }
+ );
+ });
+
+ it('handles error', async () => {
+ const error = {
+ response: {
+ error: 'this is an error',
+ status: 400,
+ },
+ };
+ const promise = Promise.reject(error);
+ http.put.mockReturnValue(promise);
+ SourcesLogic.actions.setSourceSearchability(id, true);
+ await expectedAsyncError(promise);
+
+ expect(flashAPIErrors).toHaveBeenCalledWith(error);
+ });
+ });
+
+ describe('pollForSourceStatusChanges', () => {
+ it('calls API and sets values', async () => {
+ AppLogic.values.isOrganization = true;
+ SourcesLogic.actions.setServerSourceStatuses(serverStatuses);
+
+ const setServerSourceStatusesSpy = jest.spyOn(
+ SourcesLogic.actions,
+ 'setServerSourceStatuses'
+ );
+ const promise = Promise.resolve(contentSources);
+ http.get.mockReturnValue(promise);
+ SourcesLogic.actions.pollForSourceStatusChanges();
+
+ jest.advanceTimersByTime(POLLING_INTERVAL);
+
+ expect(http.get).toHaveBeenCalledWith('/api/workplace_search/org/sources/status');
+ await promise;
+ expect(setServerSourceStatusesSpy).toHaveBeenCalledWith(contentSources);
+ });
+ });
+
+ it('resetSourcesState', () => {
+ SourcesLogic.actions.resetSourcesState();
+
+ expect(clearInterval).toHaveBeenCalled();
+ });
+ });
+
+ describe('selectors', () => {
+ it('availableSources & configuredSources have correct length', () => {
+ SourcesLogic.actions.onInitializeSources(serverResponse);
+
+ expect(SourcesLogic.values.availableSources).toHaveLength(1);
+ expect(SourcesLogic.values.configuredSources).toHaveLength(5);
+ });
+ });
+
+ describe('fetchSourceStatuses', () => {
+ it('calls API and sets values (org)', async () => {
+ const setServerSourceStatusesSpy = jest.spyOn(
+ SourcesLogic.actions,
+ 'setServerSourceStatuses'
+ );
+ const promise = Promise.resolve(contentSources);
+ http.get.mockReturnValue(promise);
+ fetchSourceStatuses(true);
+
+ expect(http.get).toHaveBeenCalledWith('/api/workplace_search/org/sources/status');
+ await promise;
+ expect(setServerSourceStatusesSpy).toHaveBeenCalledWith(contentSources);
+ });
+
+ it('calls API (account)', async () => {
+ const promise = Promise.resolve(contentSource);
+ http.get.mockReturnValue(promise);
+ fetchSourceStatuses(false);
+
+ expect(http.get).toHaveBeenCalledWith('/api/workplace_search/account/sources/status');
+ });
+
+ it('handles error', async () => {
+ const error = {
+ response: {
+ error: 'this is an error',
+ status: 400,
+ },
+ };
+ const promise = Promise.reject(error);
+ http.get.mockReturnValue(promise);
+ fetchSourceStatuses(true);
+ await expectedAsyncError(promise);
+
+ expect(flashAPIErrors).toHaveBeenCalledWith(error);
+ });
+ });
+});
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_logic.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_logic.ts
index 0a3d047796f49..57e1a97e7bdf6 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_logic.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_logic.ts
@@ -12,11 +12,7 @@ import { i18n } from '@kbn/i18n';
import { HttpLogic } from '../../../shared/http';
-import {
- flashAPIErrors,
- setQueuedSuccessMessage,
- clearFlashMessages,
-} from '../../../shared/flash_messages';
+import { flashAPIErrors, setQueuedSuccessMessage } from '../../../shared/flash_messages';
import { Connector, ContentSourceDetails, ContentSourceStatus, SourceDataItem } from '../../types';
@@ -40,7 +36,6 @@ export interface ISourcesActions {
additionalConfiguration: boolean,
serviceType: string
): { addedSourceName: string; additionalConfiguration: boolean; serviceType: string };
- resetFlashMessages(): void;
resetPermissionsModal(): void;
resetSourcesState(): void;
initializeSources(): void;
@@ -78,7 +73,7 @@ interface ISourcesServerResponse {
}
let pollingInterval: number;
-const POLLING_INTERVAL = 10000;
+export const POLLING_INTERVAL = 10000;
export const SourcesLogic = kea>({
path: ['enterprise_search', 'workplace_search', 'sources_logic'],
@@ -91,7 +86,6 @@ export const SourcesLogic = kea>(
additionalConfiguration: boolean,
serviceType: string
) => ({ addedSourceName, additionalConfiguration, serviceType }),
- resetFlashMessages: () => true,
resetPermissionsModal: () => true,
resetSourcesState: () => true,
initializeSources: () => true,
@@ -238,9 +232,6 @@ export const SourcesLogic = kea |