diff --git a/docs/settings/search-sessions-settings.asciidoc b/docs/settings/search-sessions-settings.asciidoc
index cf64d08e4806c..abd6a8f12b568 100644
--- a/docs/settings/search-sessions-settings.asciidoc
+++ b/docs/settings/search-sessions-settings.asciidoc
@@ -11,15 +11,33 @@ Configure the search session settings in your `kibana.yml` configuration file.
[cols="2*<"]
|===
a| `xpack.data_enhanced.`
-`search.sessions.enabled`
+`search.sessions.enabled` {ess-icon}
| Set to `true` (default) to enable search sessions.
a| `xpack.data_enhanced.`
-`search.sessions.trackingInterval`
-| The frequency for updating the state of a search session. The default is 10s.
+`search.sessions.trackingInterval` {ess-icon}
+| The frequency for updating the state of a search session. The default is `10s`.
a| `xpack.data_enhanced.`
-`search.sessions.defaultExpiration`
+`search.sessions.pageSize` {ess-icon}
+| How many search sessions {kib} processes at once while monitoring
+session progress. The default is `100`.
+
+a| `xpack.data_enhanced.`
+`search.sessions.notTouchedTimeout` {ess-icon}
+| How long {kib} stores search results from unsaved sessions,
+after the last search in the session completes. The default is `5m`.
+
+a| `xpack.data_enhanced.`
+`search.sessions.notTouchedInProgressTimeout` {ess-icon}
+| How long a search session can run after a user navigates away without saving a session. The default is `1m`.
+
+a| `xpack.data_enhanced.`
+`search.sessions.maxUpdateRetries` {ess-icon}
+| How many retries {kib} can perform while attempting to save a search session. The default is `3`.
+
+a| `xpack.data_enhanced.`
+`search.sessions.defaultExpiration` {ess-icon}
| How long search session results are stored before they are deleted.
-Extending a search session resets the expiration by the same value. The default is 7d.
+Extending a search session resets the expiration by the same value. The default is `7d`.
|===
diff --git a/src/core/server/config/ensure_valid_configuration.test.ts b/src/core/server/config/ensure_valid_configuration.test.ts
index 474e8dd59b4c4..f1006f93dbc2d 100644
--- a/src/core/server/config/ensure_valid_configuration.test.ts
+++ b/src/core/server/config/ensure_valid_configuration.test.ts
@@ -16,14 +16,40 @@ describe('ensureValidConfiguration', () => {
beforeEach(() => {
jest.clearAllMocks();
configService = configServiceMock.create();
- configService.getUsedPaths.mockReturnValue(Promise.resolve(['core', 'elastic']));
+
+ configService.validate.mockResolvedValue();
+ configService.getUsedPaths.mockReturnValue(Promise.resolve([]));
});
- it('returns normally when there is no unused keys', async () => {
- configService.getUnusedPaths.mockResolvedValue([]);
+ it('returns normally when there is no unused keys and when the config validates', async () => {
await expect(ensureValidConfiguration(configService as any)).resolves.toBeUndefined();
});
+ it('throws when config validation fails', async () => {
+ configService.validate.mockImplementation(() => {
+ throw new Error('some message');
+ });
+
+ await expect(ensureValidConfiguration(configService as any)).rejects.toMatchInlineSnapshot(
+ `[Error: some message]`
+ );
+ });
+
+ it('throws a `CriticalError` with the correct processExitCode value when config validation fails', async () => {
+ expect.assertions(2);
+
+ configService.validate.mockImplementation(() => {
+ throw new Error('some message');
+ });
+
+ try {
+ await ensureValidConfiguration(configService as any);
+ } catch (e) {
+ expect(e).toBeInstanceOf(CriticalError);
+ expect(e.processExitCode).toEqual(78);
+ }
+ });
+
it('throws when there are some unused keys', async () => {
configService.getUnusedPaths.mockResolvedValue(['some.key', 'some.other.key']);
@@ -44,4 +70,18 @@ describe('ensureValidConfiguration', () => {
expect(e.processExitCode).toEqual(64);
}
});
+
+ it('does not throw when all unused keys are included in the ignored paths', async () => {
+ configService.getUnusedPaths.mockResolvedValue(['dev.someDevKey', 'elastic.apm.enabled']);
+
+ await expect(ensureValidConfiguration(configService as any)).resolves.toBeUndefined();
+ });
+
+ it('throws when only some keys are included in the ignored paths', async () => {
+ configService.getUnusedPaths.mockResolvedValue(['dev.someDevKey', 'some.key']);
+
+ await expect(ensureValidConfiguration(configService as any)).rejects.toMatchInlineSnapshot(
+ `[Error: Unknown configuration key(s): "some.key". Check for spelling errors and ensure that expected plugins are installed.]`
+ );
+ });
});
diff --git a/src/core/server/config/ensure_valid_configuration.ts b/src/core/server/config/ensure_valid_configuration.ts
index a33625cc0841d..c7a4721b7d2ae 100644
--- a/src/core/server/config/ensure_valid_configuration.ts
+++ b/src/core/server/config/ensure_valid_configuration.ts
@@ -9,22 +9,27 @@
import { ConfigService } from '@kbn/config';
import { CriticalError } from '../errors';
+const ignoredPaths = ['dev.', 'elastic.apm.'];
+
+const invalidConfigExitCode = 78;
+const legacyInvalidConfigExitCode = 64;
+
export async function ensureValidConfiguration(configService: ConfigService) {
- await configService.validate();
+ try {
+ await configService.validate();
+ } catch (e) {
+ throw new CriticalError(e.message, 'InvalidConfig', invalidConfigExitCode, e);
+ }
- const unusedConfigKeys = await configService.getUnusedPaths();
+ const unusedPaths = await configService.getUnusedPaths();
+ const unusedConfigKeys = unusedPaths.filter((unusedPath) => {
+ return !ignoredPaths.some((ignoredPath) => unusedPath.startsWith(ignoredPath));
+ });
if (unusedConfigKeys.length > 0) {
const message = `Unknown configuration key(s): ${unusedConfigKeys
.map((key) => `"${key}"`)
.join(', ')}. Check for spelling errors and ensure that expected plugins are installed.`;
- throw new InvalidConfigurationError(message);
- }
-}
-
-class InvalidConfigurationError extends CriticalError {
- constructor(message: string) {
- super(message, 'InvalidConfig', 64);
- Object.setPrototypeOf(this, InvalidConfigurationError.prototype);
+ throw new CriticalError(message, 'InvalidConfig', legacyInvalidConfigExitCode);
}
}
diff --git a/src/core/server/dev/dev_config.ts b/src/core/server/dev/dev_config.ts
deleted file mode 100644
index 2fec778d85713..0000000000000
--- a/src/core/server/dev/dev_config.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0 and the Server Side Public License, v 1; you may not use this file except
- * in compliance with, at your election, the Elastic License 2.0 or the Server
- * Side Public License, v 1.
- */
-
-import { schema } from '@kbn/config-schema';
-
-export const config = {
- path: 'dev',
- // dev configuration is validated by the dev cli.
- // we only need to register the `dev` schema to avoid failing core's config validation
- schema: schema.object({}, { unknowns: 'ignore' }),
-};
diff --git a/src/core/server/dev/index.ts b/src/core/server/dev/index.ts
deleted file mode 100644
index 70257d2a5e6c5..0000000000000
--- a/src/core/server/dev/index.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
- * 2.0 and the Server Side Public License, v 1; you may not use this file except
- * in compliance with, at your election, the Elastic License 2.0 or the Server
- * Side Public License, v 1.
- */
-
-export { config } from './dev_config';
diff --git a/src/core/server/server.ts b/src/core/server/server.ts
index b34d7fec3dcbf..45d11f9013fed 100644
--- a/src/core/server/server.ts
+++ b/src/core/server/server.ts
@@ -36,7 +36,6 @@ import { config as cspConfig } from './csp';
import { config as elasticsearchConfig } from './elasticsearch';
import { config as httpConfig } from './http';
import { config as loggingConfig } from './logging';
-import { config as devConfig } from './dev';
import { config as kibanaConfig } from './kibana_config';
import { savedObjectsConfig, savedObjectsMigrationConfig } from './saved_objects';
import { config as uiSettingsConfig } from './ui_settings';
@@ -303,7 +302,6 @@ export class Server {
loggingConfig,
httpConfig,
pluginsConfig,
- devConfig,
kibanaConfig,
savedObjectsConfig,
savedObjectsMigrationConfig,
diff --git a/src/plugins/discover/kibana.json b/src/plugins/discover/kibana.json
index 7db03f726e6f5..6ea22001f5d80 100644
--- a/src/plugins/discover/kibana.json
+++ b/src/plugins/discover/kibana.json
@@ -12,7 +12,8 @@
"urlForwarding",
"navigation",
"uiActions",
- "savedObjects"
+ "savedObjects",
+ "indexPatternFieldEditor"
],
"optionalPlugins": ["home", "share", "usageCollection"],
"requiredBundles": ["kibanaUtils", "home", "kibanaReact"]
diff --git a/src/plugins/discover/public/application/angular/discover.js b/src/plugins/discover/public/application/angular/discover.js
index 45382af098644..35a89eb45f35e 100644
--- a/src/plugins/discover/public/application/angular/discover.js
+++ b/src/plugins/discover/public/application/angular/discover.js
@@ -458,6 +458,13 @@ function discoverController($route, $scope) {
$scope.fetchStatus = fetchStatuses.COMPLETE;
}
+ $scope.refreshAppState = async () => {
+ $scope.hits = [];
+ $scope.rows = [];
+ $scope.fieldCounts = {};
+ await refetch$.next();
+ };
+
function getRequestResponder({ searchSessionId = null } = { searchSessionId: null }) {
inspectorAdapters.requests.reset();
const title = i18n.translate('discover.inspectorRequestDataTitle', {
diff --git a/src/plugins/discover/public/application/angular/discover_legacy.html b/src/plugins/discover/public/application/angular/discover_legacy.html
index f14800f81d08e..fadaffde5c5c3 100644
--- a/src/plugins/discover/public/application/angular/discover_legacy.html
+++ b/src/plugins/discover/public/application/angular/discover_legacy.html
@@ -16,6 +16,7 @@
top-nav-menu="topNavMenu"
use-new-fields-api="useNewFieldsApi"
unmapped-fields-config="unmappedFieldsConfig"
+ refresh-app-state="refreshAppState"
>
diff --git a/src/plugins/discover/public/application/angular/discover_state.ts b/src/plugins/discover/public/application/angular/discover_state.ts
index e7d5ed469525f..9ebeff69d7542 100644
--- a/src/plugins/discover/public/application/angular/discover_state.ts
+++ b/src/plugins/discover/public/application/angular/discover_state.ts
@@ -177,7 +177,7 @@ export function getState({
},
uiSettings
);
- // todo filter source depending on fields fetchinbg flag (if no columns remain and source fetching is enabled, use default columns)
+ // todo filter source depending on fields fetching flag (if no columns remain and source fetching is enabled, use default columns)
let previousAppState: AppState;
const appStateContainer = createStateContainer(initialAppState);
diff --git a/src/plugins/discover/public/application/angular/helpers/row_formatter.test.ts b/src/plugins/discover/public/application/angular/helpers/row_formatter.test.ts
index 050959dff98a4..4c6b9002ce867 100644
--- a/src/plugins/discover/public/application/angular/helpers/row_formatter.test.ts
+++ b/src/plugins/discover/public/application/angular/helpers/row_formatter.test.ts
@@ -90,6 +90,7 @@ describe('Row formatter', () => {
},
{
'object.value': [5, 10],
+ getByName: jest.fn(),
},
indexPattern
).trim()
@@ -107,7 +108,7 @@ describe('Row formatter', () => {
});
const formatted = formatTopLevelObject(
{ fields: { 'a.zzz': [100], 'a.ccc': [50] } },
- { 'a.zzz': [100], 'a.ccc': [50] },
+ { 'a.zzz': [100], 'a.ccc': [50], getByName: jest.fn() },
indexPattern
).trim();
expect(formatted.indexOf('a.ccc:')).toBeLessThan(formatted.indexOf('a.zzz:'));
@@ -134,6 +135,7 @@ describe('Row formatter', () => {
{
'object.value': [5, 10],
'object.keys': ['a', 'b'],
+ getByName: jest.fn(),
},
indexPattern
).trim()
@@ -154,6 +156,7 @@ describe('Row formatter', () => {
},
{
'object.value': [5, 10],
+ getByName: jest.fn(),
},
indexPattern
).trim()
diff --git a/src/plugins/discover/public/application/angular/helpers/row_formatter.ts b/src/plugins/discover/public/application/angular/helpers/row_formatter.ts
index a226cefb53960..02902b0634797 100644
--- a/src/plugins/discover/public/application/angular/helpers/row_formatter.ts
+++ b/src/plugins/discover/public/application/angular/helpers/row_formatter.ts
@@ -28,11 +28,13 @@ export const formatRow = (hit: Record, indexPattern: IndexPattern)
const highlights = hit?.highlight ?? {};
// Keys are sorted in the hits object
const formatted = indexPattern.formatHit(hit);
+ const fields = indexPattern.fields;
const highlightPairs: Array<[string, unknown]> = [];
const sourcePairs: Array<[string, unknown]> = [];
Object.entries(formatted).forEach(([key, val]) => {
+ const displayKey = fields.getByName ? fields.getByName(key)?.displayName : undefined;
const pairs = highlights[key] ? highlightPairs : sourcePairs;
- pairs.push([key, val]);
+ pairs.push([displayKey ? displayKey : key, val]);
});
return doTemplate({ defPairs: [...highlightPairs, ...sourcePairs] });
};
@@ -48,9 +50,11 @@ export const formatTopLevelObject = (
const sorted = Object.entries(fields).sort(([keyA], [keyB]) => keyA.localeCompare(keyB));
sorted.forEach(([key, values]) => {
const field = indexPattern.getFieldByName(key);
+ const displayKey = fields.getByName ? fields.getByName(key)?.displayName : undefined;
const formatter = field
? indexPattern.getFormatterForField(field)
: { convert: (v: string, ...rest: unknown[]) => String(v) };
+ if (!values.map) return;
const formatted = values
.map((val: unknown) =>
formatter.convert(val, 'html', {
@@ -61,7 +65,7 @@ export const formatTopLevelObject = (
)
.join(', ');
const pairs = highlights[key] ? highlightPairs : sourcePairs;
- pairs.push([key, formatted]);
+ pairs.push([displayKey ? displayKey : key, formatted]);
});
return doTemplate({ defPairs: [...highlightPairs, ...sourcePairs] });
};
diff --git a/src/plugins/discover/public/application/components/create_discover_directive.ts b/src/plugins/discover/public/application/components/create_discover_directive.ts
index 5abf87fdfbc08..cc88ef03c5d03 100644
--- a/src/plugins/discover/public/application/components/create_discover_directive.ts
+++ b/src/plugins/discover/public/application/components/create_discover_directive.ts
@@ -28,5 +28,6 @@ export function createDiscoverDirective(reactDirective: any) {
['updateQuery', { watchDepth: 'reference' }],
['updateSavedQueryId', { watchDepth: 'reference' }],
['unmappedFieldsConfig', { watchDepth: 'value' }],
+ ['refreshAppState', { watchDepth: 'reference' }],
]);
}
diff --git a/src/plugins/discover/public/application/components/discover.tsx b/src/plugins/discover/public/application/components/discover.tsx
index 9615a1c10ea8e..6b71bd892b520 100644
--- a/src/plugins/discover/public/application/components/discover.tsx
+++ b/src/plugins/discover/public/application/components/discover.tsx
@@ -68,6 +68,7 @@ export function Discover({
searchSource,
state,
unmappedFieldsConfig,
+ refreshAppState,
}: DiscoverProps) {
const [expandedDoc, setExpandedDoc] = useState(undefined);
const scrollableDesktop = useRef(null);
@@ -203,6 +204,12 @@ export function Discover({
[opts, state]
);
+ const onEditRuntimeField = () => {
+ if (refreshAppState) {
+ refreshAppState();
+ }
+ };
+
const columns = useMemo(() => {
if (!state.columns) {
return [];
@@ -245,6 +252,7 @@ export function Discover({
trackUiMetric={trackUiMetric}
unmappedFieldsConfig={unmappedFieldsConfig}
useNewFieldsApi={useNewFieldsApi}
+ onEditRuntimeField={onEditRuntimeField}
/>
diff --git a/src/plugins/discover/public/application/components/discover_grid/get_render_cell_value.tsx b/src/plugins/discover/public/application/components/discover_grid/get_render_cell_value.tsx
index dce0a82934c25..03203a79d9dd0 100644
--- a/src/plugins/discover/public/application/components/discover_grid/get_render_cell_value.tsx
+++ b/src/plugins/discover/public/application/components/discover_grid/get_render_cell_value.tsx
@@ -77,6 +77,9 @@ export const getRenderCellValueFn = (
const sourcePairs: Array<[string, string]> = [];
Object.entries(innerColumns).forEach(([key, values]) => {
const subField = indexPattern.getFieldByName(key);
+ const displayKey = indexPattern.fields.getByName
+ ? indexPattern.fields.getByName(key)?.displayName
+ : undefined;
const formatter = subField
? indexPattern.getFormatterForField(subField)
: { convert: (v: string, ...rest: unknown[]) => String(v) };
@@ -90,7 +93,7 @@ export const getRenderCellValueFn = (
)
.join(', ');
const pairs = highlights[key] ? highlightPairs : sourcePairs;
- pairs.push([key, formatted]);
+ pairs.push([displayKey ? displayKey : key, formatted]);
});
return (
@@ -130,7 +133,10 @@ export const getRenderCellValueFn = (
Object.entries(formatted).forEach(([key, val]) => {
const pairs = highlights[key] ? highlightPairs : sourcePairs;
- pairs.push([key, val as string]);
+ const displayKey = indexPattern.fields.getByName
+ ? indexPattern.fields.getByName(key)?.displayName
+ : undefined;
+ pairs.push([displayKey ? displayKey : key, val as string]);
});
return (
diff --git a/src/plugins/discover/public/application/components/sidebar/discover_field.tsx b/src/plugins/discover/public/application/components/sidebar/discover_field.tsx
index b0d71c774f445..a630ddda40f30 100644
--- a/src/plugins/discover/public/application/components/sidebar/discover_field.tsx
+++ b/src/plugins/discover/public/application/components/sidebar/discover_field.tsx
@@ -16,6 +16,8 @@ import {
EuiToolTip,
EuiTitle,
EuiIcon,
+ EuiFlexGroup,
+ EuiFlexItem,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { UiCounterMetricType } from '@kbn/analytics';
@@ -69,6 +71,8 @@ export interface DiscoverFieldProps {
trackUiMetric?: (metricType: UiCounterMetricType, eventName: string | string[]) => void;
multiFields?: Array<{ field: IndexPatternField; isSelected: boolean }>;
+
+ onEditField?: (fieldName: string) => void;
}
export function DiscoverField({
@@ -82,6 +86,7 @@ export function DiscoverField({
selected,
trackUiMetric,
multiFields,
+ onEditField,
}: DiscoverFieldProps) {
const addLabelAria = i18n.translate('discover.fieldChooser.discoverField.addButtonAriaLabel', {
defaultMessage: 'Add {field} to table',
@@ -250,7 +255,6 @@ export function DiscoverField({
};
const fieldInfoIcon = getFieldInfoIcon();
-
const shouldRenderMultiFields = !!multiFields;
const renderMultiFields = () => {
if (!multiFields) {
@@ -282,6 +286,35 @@ export function DiscoverField({
);
};
+ const isRuntimeField = Boolean(indexPattern.getFieldByName(field.name)?.runtimeField);
+ const isUnknownField = field.type === 'unknown' || field.type === 'unknown_selected';
+ const canEditField = onEditField && (!isUnknownField || isRuntimeField);
+ const displayNameGrow = canEditField ? 9 : 10;
+ const popoverTitle = (
+
+
+ {field.displayName}
+ {canEditField && (
+
+ {
+ if (onEditField) {
+ togglePopover();
+ onEditField(field.name);
+ }
+ }}
+ iconType="pencil"
+ data-test-subj={`discoverFieldListPanelEdit-${field.name}`}
+ aria-label={i18n.translate('discover.fieldChooser.discoverField.editFieldLabel', {
+ defaultMessage: 'Edit index pattern field',
+ })}
+ />
+
+ )}
+
+
+ );
+
return (
-
- {field.displayName}
-
+ {popoverTitle}
{i18n.translate('discover.fieldChooser.discoverField.fieldTopValuesLabel', {
diff --git a/src/plugins/discover/public/application/components/sidebar/discover_sidebar.test.tsx b/src/plugins/discover/public/application/components/sidebar/discover_sidebar.test.tsx
index 947972ce1cfc5..0b3f55b5630cc 100644
--- a/src/plugins/discover/public/application/components/sidebar/discover_sidebar.test.tsx
+++ b/src/plugins/discover/public/application/components/sidebar/discover_sidebar.test.tsx
@@ -48,6 +48,12 @@ const mockServices = ({
}
},
},
+ indexPatternFieldEditor: {
+ openEditor: jest.fn(),
+ userPermissions: {
+ editIndexPattern: jest.fn(),
+ },
+ },
} as unknown) as DiscoverServices;
jest.mock('../../../kibana_services', () => ({
@@ -102,6 +108,7 @@ function getCompProps(): DiscoverSidebarProps {
fieldFilter: getDefaultFieldFilter(),
setFieldFilter: jest.fn(),
setAppState: jest.fn(),
+ onEditRuntimeField: jest.fn(),
};
}
diff --git a/src/plugins/discover/public/application/components/sidebar/discover_sidebar.tsx b/src/plugins/discover/public/application/components/sidebar/discover_sidebar.tsx
index 1be42e1cd6b17..a3bf2e150d088 100644
--- a/src/plugins/discover/public/application/components/sidebar/discover_sidebar.tsx
+++ b/src/plugins/discover/public/application/components/sidebar/discover_sidebar.tsx
@@ -49,6 +49,17 @@ export interface DiscoverSidebarProps extends DiscoverSidebarResponsiveProps {
* Change current state of fieldFilter
*/
setFieldFilter: (next: FieldFilterState) => void;
+
+ /**
+ * Callback to close the flyout sidebar rendered in a flyout, close flyout
+ */
+ closeFlyout?: () => void;
+
+ /**
+ * Pass the reference to field editor component to the parent, so it can be properly unmounted
+ * @param ref reference to the field editor component
+ */
+ setFieldEditorRef?: (ref: () => void | undefined) => void;
}
export function DiscoverSidebar({
@@ -72,8 +83,14 @@ export function DiscoverSidebar({
useNewFieldsApi = false,
useFlyout = false,
unmappedFieldsConfig,
+ onEditRuntimeField,
+ setFieldEditorRef,
+ closeFlyout,
}: DiscoverSidebarProps) {
const [fields, setFields] = useState(null);
+ const { indexPatternFieldEditor } = services;
+ const indexPatternFieldEditPermission = indexPatternFieldEditor?.userPermissions.editIndexPattern();
+ const canEditIndexPatternField = !!indexPatternFieldEditPermission && useNewFieldsApi;
const [scrollContainer, setScrollContainer] = useState(null);
const [fieldsToRender, setFieldsToRender] = useState(FIELDS_PER_PAGE);
const [fieldsPerPage, setFieldsPerPage] = useState(FIELDS_PER_PAGE);
@@ -220,6 +237,27 @@ export function DiscoverSidebar({
return null;
}
+ const editField = (fieldName: string) => {
+ if (!canEditIndexPatternField) {
+ return;
+ }
+ const ref = indexPatternFieldEditor.openEditor({
+ ctx: {
+ indexPattern: selectedIndexPattern,
+ },
+ fieldName,
+ onSave: async () => {
+ onEditRuntimeField();
+ },
+ });
+ if (setFieldEditorRef) {
+ setFieldEditorRef(ref);
+ }
+ if (closeFlyout) {
+ closeFlyout();
+ }
+ };
+
if (useFlyout) {
return (
);
@@ -388,6 +427,7 @@ export function DiscoverSidebar({
getDetails={getDetailsByField}
trackUiMetric={trackUiMetric}
multiFields={multiFields?.get(field.name)}
+ onEditField={canEditIndexPatternField ? editField : undefined}
/>
);
@@ -414,6 +454,7 @@ export function DiscoverSidebar({
getDetails={getDetailsByField}
trackUiMetric={trackUiMetric}
multiFields={multiFields?.get(field.name)}
+ onEditField={canEditIndexPatternField ? editField : undefined}
/>
);
diff --git a/src/plugins/discover/public/application/components/sidebar/discover_sidebar_responsive.test.tsx b/src/plugins/discover/public/application/components/sidebar/discover_sidebar_responsive.test.tsx
index 79e8caabd4930..caec61cc501b9 100644
--- a/src/plugins/discover/public/application/components/sidebar/discover_sidebar_responsive.test.tsx
+++ b/src/plugins/discover/public/application/components/sidebar/discover_sidebar_responsive.test.tsx
@@ -102,6 +102,7 @@ function getCompProps(): DiscoverSidebarResponsiveProps {
setAppState: jest.fn(),
state: {},
trackUiMetric: jest.fn(),
+ onEditRuntimeField: jest.fn(),
};
}
diff --git a/src/plugins/discover/public/application/components/sidebar/discover_sidebar_responsive.tsx b/src/plugins/discover/public/application/components/sidebar/discover_sidebar_responsive.tsx
index 0808ef47c0dc1..6a16399f0e2e1 100644
--- a/src/plugins/discover/public/application/components/sidebar/discover_sidebar_responsive.tsx
+++ b/src/plugins/discover/public/application/components/sidebar/discover_sidebar_responsive.tsx
@@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
-import React, { useState } from 'react';
+import React, { useEffect, useRef, useState } from 'react';
import { sortBy } from 'lodash';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
@@ -121,6 +121,8 @@ export interface DiscoverSidebarResponsiveProps {
*/
showUnmappedFields: boolean;
};
+
+ onEditRuntimeField: () => void;
}
/**
@@ -132,15 +134,42 @@ export function DiscoverSidebarResponsive(props: DiscoverSidebarResponsiveProps)
const [fieldFilter, setFieldFilter] = useState(getDefaultFieldFilter());
const [isFlyoutVisible, setIsFlyoutVisible] = useState(false);
+ const closeFieldEditor = useRef<() => void | undefined>();
+
+ useEffect(() => {
+ const cleanup = () => {
+ if (closeFieldEditor?.current) {
+ closeFieldEditor?.current();
+ }
+ };
+ return () => {
+ // Make sure to close the editor when unmounting
+ cleanup();
+ };
+ }, []);
+
if (!props.selectedIndexPattern) {
return null;
}
+ const setFieldEditorRef = (ref: () => void | undefined) => {
+ closeFieldEditor.current = ref;
+ };
+
+ const closeFlyout = () => {
+ setIsFlyoutVisible(false);
+ };
+
return (
<>
{props.isClosed ? null : (
-
+
)}
@@ -215,6 +244,8 @@ export function DiscoverSidebarResponsive(props: DiscoverSidebarResponsiveProps)
fieldFilter={fieldFilter}
setFieldFilter={setFieldFilter}
alwaysShowActionButtons={true}
+ setFieldEditorRef={setFieldEditorRef}
+ closeFlyout={closeFlyout}
/>
diff --git a/src/plugins/discover/public/application/components/types.ts b/src/plugins/discover/public/application/components/types.ts
index 23a3cc9a9bc74..93620bc1d6bca 100644
--- a/src/plugins/discover/public/application/components/types.ts
+++ b/src/plugins/discover/public/application/components/types.ts
@@ -167,4 +167,6 @@ export interface DiscoverProps {
*/
showUnmappedFields: boolean;
};
+
+ refreshAppState?: () => void;
}
diff --git a/src/plugins/discover/public/build_services.ts b/src/plugins/discover/public/build_services.ts
index 252265692d203..cf95d5a85b9f2 100644
--- a/src/plugins/discover/public/build_services.ts
+++ b/src/plugins/discover/public/build_services.ts
@@ -34,6 +34,7 @@ import { getHistory } from './kibana_services';
import { KibanaLegacyStart } from '../../kibana_legacy/public';
import { UrlForwardingStart } from '../../url_forwarding/public';
import { NavigationPublicPluginStart } from '../../navigation/public';
+import { IndexPatternFieldEditorStart } from '../../index_pattern_field_editor/public';
export interface DiscoverServices {
addBasePath: (path: string) => string;
@@ -59,6 +60,7 @@ export interface DiscoverServices {
getEmbeddableInjector: any;
uiSettings: IUiSettingsClient;
trackUiMetric?: (metricType: UiCounterMetricType, eventName: string | string[]) => void;
+ indexPatternFieldEditor: IndexPatternFieldEditorStart;
}
export async function buildServices(
@@ -100,5 +102,6 @@ export async function buildServices(
toastNotifications: core.notifications.toasts,
uiSettings: core.uiSettings,
trackUiMetric: usageCollection?.reportUiCounter.bind(usageCollection, 'discover'),
+ indexPatternFieldEditor: plugins.indexPatternFieldEditor,
};
}
diff --git a/src/plugins/discover/public/plugin.tsx b/src/plugins/discover/public/plugin.tsx
index 0e0836e3d9573..692704c92356e 100644
--- a/src/plugins/discover/public/plugin.tsx
+++ b/src/plugins/discover/public/plugin.tsx
@@ -62,6 +62,7 @@ import {
import { SearchEmbeddableFactory } from './application/embeddable';
import { UsageCollectionSetup } from '../../usage_collection/public';
import { replaceUrlHashQuery } from '../../kibana_utils/public/';
+import { IndexPatternFieldEditorStart } from '../../../plugins/index_pattern_field_editor/public';
declare module '../../share/public' {
export interface UrlGeneratorStateMapping {
@@ -133,6 +134,7 @@ export interface DiscoverStartPlugins {
inspector: InspectorPublicPluginStart;
savedObjects: SavedObjectsStart;
usageCollection?: UsageCollectionSetup;
+ indexPatternFieldEditor: IndexPatternFieldEditorStart;
}
const innerAngularName = 'app/discover';
diff --git a/src/plugins/discover/tsconfig.json b/src/plugins/discover/tsconfig.json
index ec98199c3423e..c0179ad3c8d20 100644
--- a/src/plugins/discover/tsconfig.json
+++ b/src/plugins/discover/tsconfig.json
@@ -23,6 +23,7 @@
{ "path": "../usage_collection/tsconfig.json" },
{ "path": "../kibana_utils/tsconfig.json" },
{ "path": "../kibana_react/tsconfig.json" },
- { "path": "../kibana_legacy/tsconfig.json" }
+ { "path": "../kibana_legacy/tsconfig.json" },
+ { "path": "../index_pattern_field_editor/tsconfig.json"}
]
}
diff --git a/src/plugins/index_pattern_management/public/components/utils.ts b/src/plugins/index_pattern_management/public/components/utils.ts
index 5701a1e375204..68e78199798b4 100644
--- a/src/plugins/index_pattern_management/public/components/utils.ts
+++ b/src/plugins/index_pattern_management/public/components/utils.ts
@@ -14,7 +14,7 @@ export async function getIndexPatterns(
indexPatternManagementStart: IndexPatternManagementStart,
indexPatternsService: IndexPatternsContract
) {
- const existingIndexPatterns = await indexPatternsService.getIdsWithTitle();
+ const existingIndexPatterns = await indexPatternsService.getIdsWithTitle(true);
const indexPatternsListItems = await Promise.all(
existingIndexPatterns.map(async ({ id, title }) => {
const isDefault = defaultIndex === id;
diff --git a/src/plugins/vis_type_timeseries/common/calculate_label.test.ts b/src/plugins/vis_type_timeseries/common/calculate_label.test.ts
index d5277623a136d..eab9665436c01 100644
--- a/src/plugins/vis_type_timeseries/common/calculate_label.test.ts
+++ b/src/plugins/vis_type_timeseries/common/calculate_label.test.ts
@@ -8,6 +8,7 @@
import { calculateLabel } from './calculate_label';
import type { MetricsItemsSchema } from './types';
+import { SanitizedFieldType } from './types';
describe('calculateLabel(metric, metrics)', () => {
test('returns the metric.alias if set', () => {
@@ -82,4 +83,26 @@ describe('calculateLabel(metric, metrics)', () => {
expect(label).toEqual('Derivative of Outbound Traffic');
});
+
+ test('should throw an error if field not found', () => {
+ const metric = ({ id: 2, type: 'max', field: 3 } as unknown) as MetricsItemsSchema;
+ const metrics = ([
+ { id: 1, type: 'max', field: 'network.out.bytes', alias: 'Outbound Traffic' },
+ metric,
+ ] as unknown) as MetricsItemsSchema[];
+ const fields: SanitizedFieldType[] = [{ name: '2', label: '2', type: 'field' }];
+
+ expect(() => calculateLabel(metric, metrics, fields)).toThrowError('Field "3" not found');
+ });
+
+ test('should not throw an error if field not found (isThrowErrorOnFieldNotFound is false)', () => {
+ const metric = ({ id: 2, type: 'max', field: 3 } as unknown) as MetricsItemsSchema;
+ const metrics = ([
+ { id: 1, type: 'max', field: 'network.out.bytes', alias: 'Outbound Traffic' },
+ metric,
+ ] as unknown) as MetricsItemsSchema[];
+ const fields: SanitizedFieldType[] = [{ name: '2', label: '2', type: 'field' }];
+
+ expect(calculateLabel(metric, metrics, fields, false)).toBe('Max of 3');
+ });
});
diff --git a/src/plugins/vis_type_timeseries/common/calculate_label.ts b/src/plugins/vis_type_timeseries/common/calculate_label.ts
index 73b5d3f652644..bd1482e14f4f4 100644
--- a/src/plugins/vis_type_timeseries/common/calculate_label.ts
+++ b/src/plugins/vis_type_timeseries/common/calculate_label.ts
@@ -10,6 +10,7 @@ import { includes, startsWith } from 'lodash';
import { i18n } from '@kbn/i18n';
import { lookup } from './agg_lookup';
import { MetricsItemsSchema, SanitizedFieldType } from './types';
+import { extractFieldLabel } from './fields_utils';
const paths = [
'cumulative_sum',
@@ -26,14 +27,11 @@ const paths = [
'positive_only',
];
-export const extractFieldLabel = (fields: SanitizedFieldType[], name: string) => {
- return fields.find((f) => f.name === name)?.label ?? name;
-};
-
export const calculateLabel = (
metric: MetricsItemsSchema,
metrics: MetricsItemsSchema[] = [],
- fields: SanitizedFieldType[] = []
+ fields: SanitizedFieldType[] = [],
+ isThrowErrorOnFieldNotFound: boolean = true
): string => {
if (!metric) {
return i18n.translate('visTypeTimeseries.calculateLabel.unknownLabel', {
@@ -71,7 +69,7 @@ export const calculateLabel = (
if (metric.type === 'positive_rate') {
return i18n.translate('visTypeTimeseries.calculateLabel.positiveRateLabel', {
defaultMessage: 'Counter Rate of {field}',
- values: { field: extractFieldLabel(fields, metric.field!) },
+ values: { field: extractFieldLabel(fields, metric.field!, isThrowErrorOnFieldNotFound) },
});
}
if (metric.type === 'static') {
@@ -115,7 +113,7 @@ export const calculateLabel = (
defaultMessage: '{lookupMetricType} of {metricField}',
values: {
lookupMetricType: lookup[metric.type],
- metricField: extractFieldLabel(fields, metric.field!),
+ metricField: extractFieldLabel(fields, metric.field!, isThrowErrorOnFieldNotFound),
},
});
};
diff --git a/src/plugins/vis_type_timeseries/common/constants.ts b/src/plugins/vis_type_timeseries/common/constants.ts
index 66617c8518985..1debfaf951e99 100644
--- a/src/plugins/vis_type_timeseries/common/constants.ts
+++ b/src/plugins/vis_type_timeseries/common/constants.ts
@@ -13,3 +13,4 @@ export const ROUTES = {
VIS_DATA: '/api/metrics/vis/data',
FIELDS: '/api/metrics/fields',
};
+export const USE_KIBANA_INDEXES_KEY = 'use_kibana_indexes';
diff --git a/src/plugins/vis_type_timeseries/common/fields_utils.test.ts b/src/plugins/vis_type_timeseries/common/fields_utils.test.ts
index d1036aab2dc3e..9550697e22851 100644
--- a/src/plugins/vis_type_timeseries/common/fields_utils.test.ts
+++ b/src/plugins/vis_type_timeseries/common/fields_utils.test.ts
@@ -7,7 +7,7 @@
*/
import { toSanitizedFieldType } from './fields_utils';
-import type { FieldSpec, RuntimeField } from '../../data/common';
+import type { FieldSpec } from '../../data/common';
describe('fields_utils', () => {
describe('toSanitizedFieldType', () => {
@@ -34,17 +34,6 @@ describe('fields_utils', () => {
`);
});
- test('should filter runtime fields', async () => {
- const fields: FieldSpec[] = [
- {
- ...mockedField,
- runtimeField: {} as RuntimeField,
- },
- ];
-
- expect(toSanitizedFieldType(fields)).toMatchInlineSnapshot(`Array []`);
- });
-
test('should filter non-aggregatable fields', async () => {
const fields: FieldSpec[] = [
{
diff --git a/src/plugins/vis_type_timeseries/common/fields_utils.ts b/src/plugins/vis_type_timeseries/common/fields_utils.ts
index 04499d5320ab8..6a83dd323b3fd 100644
--- a/src/plugins/vis_type_timeseries/common/fields_utils.ts
+++ b/src/plugins/vis_type_timeseries/common/fields_utils.ts
@@ -6,17 +6,60 @@
* Side Public License, v 1.
*/
+import { i18n } from '@kbn/i18n';
import { FieldSpec } from '../../data/common';
import { isNestedField } from '../../data/common';
-import { SanitizedFieldType } from './types';
+import { FetchedIndexPattern, SanitizedFieldType } from './types';
-export const toSanitizedFieldType = (fields: FieldSpec[]) => {
- return fields
- .filter(
- (field) =>
- // Make sure to only include mapped fields, e.g. no index pattern runtime fields
- !field.runtimeField && field.aggregatable && !isNestedField(field)
- )
+export class FieldNotFoundError extends Error {
+ constructor(name: string) {
+ super(
+ i18n.translate('visTypeTimeseries.fields.fieldNotFound', {
+ defaultMessage: `Field "{field}" not found`,
+ values: { field: name },
+ })
+ );
+ }
+
+ public get name() {
+ return this.constructor.name;
+ }
+
+ public get body() {
+ return this.message;
+ }
+}
+
+export const extractFieldLabel = (
+ fields: SanitizedFieldType[],
+ name: string,
+ isThrowErrorOnFieldNotFound: boolean = true
+) => {
+ if (fields.length && name) {
+ const field = fields.find((f) => f.name === name);
+
+ if (field) {
+ return field.label || field.name;
+ }
+ if (isThrowErrorOnFieldNotFound) {
+ throw new FieldNotFoundError(name);
+ }
+ }
+ return name;
+};
+
+export function validateField(name: string, index: FetchedIndexPattern) {
+ if (name && index.indexPattern) {
+ const field = index.indexPattern.fields.find((f) => f.name === name);
+ if (!field) {
+ throw new FieldNotFoundError(name);
+ }
+ }
+}
+
+export const toSanitizedFieldType = (fields: FieldSpec[]) =>
+ fields
+ .filter((field) => field.aggregatable && !isNestedField(field))
.map(
(field) =>
({
@@ -25,4 +68,3 @@ export const toSanitizedFieldType = (fields: FieldSpec[]) => {
type: field.type,
} as SanitizedFieldType)
);
-};
diff --git a/src/plugins/vis_type_timeseries/common/index_patterns_utils.test.ts b/src/plugins/vis_type_timeseries/common/index_patterns_utils.test.ts
index 0428e6e80ae78..1111a9c525243 100644
--- a/src/plugins/vis_type_timeseries/common/index_patterns_utils.test.ts
+++ b/src/plugins/vis_type_timeseries/common/index_patterns_utils.test.ts
@@ -81,7 +81,7 @@ describe('fetchIndexPattern', () => {
});
describe('text-based index', () => {
- test('should return the Kibana index if it exists', async () => {
+ test('should return the Kibana index if it exists (fetchKibabaIndexForStringIndexes is true)', async () => {
mockedIndices = [
{
id: 'indexId',
@@ -89,7 +89,9 @@ describe('fetchIndexPattern', () => {
},
] as IndexPattern[];
- const value = await fetchIndexPattern('indexTitle', indexPatternsService);
+ const value = await fetchIndexPattern('indexTitle', indexPatternsService, {
+ fetchKibabaIndexForStringIndexes: true,
+ });
expect(value).toMatchInlineSnapshot(`
Object {
@@ -102,8 +104,10 @@ describe('fetchIndexPattern', () => {
`);
});
- test('should return only indexPatternString if Kibana index does not exist', async () => {
- const value = await fetchIndexPattern('indexTitle', indexPatternsService);
+ test('should return only indexPatternString if Kibana index does not exist (fetchKibabaIndexForStringIndexes is true)', async () => {
+ const value = await fetchIndexPattern('indexTitle', indexPatternsService, {
+ fetchKibabaIndexForStringIndexes: true,
+ });
expect(value).toMatchInlineSnapshot(`
Object {
diff --git a/src/plugins/vis_type_timeseries/common/index_patterns_utils.ts b/src/plugins/vis_type_timeseries/common/index_patterns_utils.ts
index af9f0750b2604..5dacad338e7a8 100644
--- a/src/plugins/vis_type_timeseries/common/index_patterns_utils.ts
+++ b/src/plugins/vis_type_timeseries/common/index_patterns_utils.ts
@@ -52,7 +52,12 @@ export const extractIndexPatternValues = (
export const fetchIndexPattern = async (
indexPatternValue: IndexPatternValue | undefined,
- indexPatternsService: Pick
+ indexPatternsService: Pick,
+ options: {
+ fetchKibabaIndexForStringIndexes: boolean;
+ } = {
+ fetchKibabaIndexForStringIndexes: false,
+ }
): Promise => {
let indexPattern: FetchedIndexPattern['indexPattern'];
let indexPatternString: string = '';
@@ -61,13 +66,16 @@ export const fetchIndexPattern = async (
indexPattern = await indexPatternsService.getDefault();
} else {
if (isStringTypeIndexPattern(indexPatternValue)) {
- indexPattern = (await indexPatternsService.find(indexPatternValue)).find(
- (index) => index.title === indexPatternValue
- );
-
+ if (options.fetchKibabaIndexForStringIndexes) {
+ indexPattern = (await indexPatternsService.find(indexPatternValue)).find(
+ (index) => index.title === indexPatternValue
+ );
+ }
if (!indexPattern) {
indexPatternString = indexPatternValue;
}
+
+ indexPatternString = indexPatternValue;
} else if (indexPatternValue.id) {
indexPattern = await indexPatternsService.get(indexPatternValue.id);
}
diff --git a/src/plugins/vis_type_timeseries/common/types.ts b/src/plugins/vis_type_timeseries/common/types.ts
index 74e247b7af06d..240b3e68cf65d 100644
--- a/src/plugins/vis_type_timeseries/common/types.ts
+++ b/src/plugins/vis_type_timeseries/common/types.ts
@@ -46,6 +46,7 @@ interface TableData {
export type SeriesData = {
type: Exclude;
uiRestrictions: TimeseriesUIRestrictions;
+ error?: string;
} & {
[key: string]: PanelSeries;
};
@@ -56,7 +57,7 @@ interface PanelSeries {
};
id: string;
series: PanelData[];
- error?: unknown;
+ error?: string;
}
export interface PanelData {
@@ -66,6 +67,7 @@ export interface PanelData {
seriesId: string;
splitByLabel: string;
isSplitByTerms: boolean;
+ error?: string;
}
export const isVisTableData = (data: TimeseriesVisData): data is TableData =>
diff --git a/src/plugins/vis_type_timeseries/public/application/components/aggs/field_select.tsx b/src/plugins/vis_type_timeseries/public/application/components/aggs/field_select.tsx
index 82989cc15d6c9..7d42eb3f40ac5 100644
--- a/src/plugins/vis_type_timeseries/public/application/components/aggs/field_select.tsx
+++ b/src/plugins/vis_type_timeseries/public/application/components/aggs/field_select.tsx
@@ -5,19 +5,26 @@
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
-
-import React from 'react';
import { i18n } from '@kbn/i18n';
-import { EuiComboBox, EuiComboBoxProps, EuiComboBoxOptionOption } from '@elastic/eui';
-import { METRIC_TYPES } from '../../../../common/metric_types';
+import React, { ReactNode, useContext } from 'react';
+import {
+ EuiComboBox,
+ EuiComboBoxProps,
+ EuiComboBoxOptionOption,
+ EuiFormRow,
+ htmlIdGenerator,
+} from '@elastic/eui';
import { getIndexPatternKey } from '../../../../common/index_patterns_utils';
import type { SanitizedFieldType, IndexPatternValue } from '../../../../common/types';
import type { TimeseriesUIRestrictions } from '../../../../common/ui_restrictions';
// @ts-ignore
import { isFieldEnabled } from '../../lib/check_ui_restrictions';
+import { PanelModelContext } from '../../contexts/panel_model_context';
+import { USE_KIBANA_INDEXES_KEY } from '../../../../common/constants';
interface FieldSelectProps {
+ label: string | ReactNode;
type: string;
fields: Record;
indexPattern: IndexPatternValue;
@@ -45,6 +52,7 @@ const sortByLabel = (a: EuiComboBoxOptionOption, b: EuiComboBoxOptionOpt
};
export function FieldSelect({
+ label,
type,
fields,
indexPattern = '',
@@ -56,11 +64,10 @@ export function FieldSelect({
uiRestrictions,
'data-test-subj': dataTestSubj = 'metricsIndexPatternFieldsSelect',
}: FieldSelectProps) {
- if (type === METRIC_TYPES.COUNT) {
- return null;
- }
+ const panelModel = useContext(PanelModelContext);
+ const htmlId = htmlIdGenerator();
- const selectedOptions: Array> = [];
+ let selectedOptions: Array> = [];
let newPlaceholder = placeholder;
const fieldsSelector = getIndexPatternKey(indexPattern);
@@ -112,19 +119,43 @@ export function FieldSelect({
}
});
- if (value && !selectedOptions.length) {
- onChange([]);
+ let isInvalid;
+
+ if (Boolean(panelModel?.[USE_KIBANA_INDEXES_KEY])) {
+ isInvalid = Boolean(value && fields[fieldsSelector] && !selectedOptions.length);
+
+ if (value && !selectedOptions.length) {
+ selectedOptions = [{ label: value!, id: 'INVALID_FIELD' }];
+ }
+ } else {
+ if (value && !selectedOptions.length) {
+ onChange([]);
+ }
}
return (
-
+
+
+
);
}
diff --git a/src/plugins/vis_type_timeseries/public/application/components/aggs/filter_ratio.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/filter_ratio.js
index 90353f9af8e35..c380b0e09e7d3 100644
--- a/src/plugins/vis_type_timeseries/public/application/components/aggs/filter_ratio.js
+++ b/src/plugins/vis_type_timeseries/public/application/components/aggs/filter_ratio.js
@@ -153,24 +153,20 @@ export const FilterRatioAgg = (props) => {
{model.metric_agg !== 'count' ? (
-
}
- >
-
-
+ fields={fields}
+ type={model.metric_agg}
+ restrict={getSupportedFieldsByMetricType(model.metric_agg)}
+ indexPattern={indexPattern}
+ value={model.field}
+ onChange={handleSelectChange('field')}
+ />
) : null}
diff --git a/src/plugins/vis_type_timeseries/public/application/components/aggs/metric_select.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/metric_select.js
index 964017cf886ec..7ce432a3bf676 100644
--- a/src/plugins/vis_type_timeseries/public/application/components/aggs/metric_select.js
+++ b/src/plugins/vis_type_timeseries/public/application/components/aggs/metric_select.js
@@ -70,7 +70,7 @@ export function MetricSelect(props) {
const percentileOptions = siblings
.filter((row) => /^percentile/.test(row.type))
.reduce((acc, row) => {
- const label = calculateLabel(row, calculatedMetrics, fields);
+ const label = calculateLabel(row, calculatedMetrics, fields, false);
switch (row.type) {
case METRIC_TYPES.PERCENTILE_RANK:
@@ -100,7 +100,7 @@ export function MetricSelect(props) {
}, []);
const options = siblings.filter(filterRows(includeSiblings)).map((row) => {
- const label = calculateLabel(row, calculatedMetrics, fields);
+ const label = calculateLabel(row, calculatedMetrics, fields, false);
return { value: row.id, label };
});
const allOptions = [...options, ...additionalOptions, ...percentileOptions];
diff --git a/src/plugins/vis_type_timeseries/public/application/components/aggs/percentile.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/percentile.js
index 77b2e2f020307..45bb5387c5cd3 100644
--- a/src/plugins/vis_type_timeseries/public/application/components/aggs/percentile.js
+++ b/src/plugins/vis_type_timeseries/public/application/components/aggs/percentile.js
@@ -78,24 +78,20 @@ export function PercentileAgg(props) {
/>
-
}
- >
-
-
+ fields={fields}
+ type={model.type}
+ restrict={RESTRICT_FIELDS}
+ indexPattern={indexPattern}
+ value={model.field}
+ onChange={handleSelectChange('field')}
+ />
{
/>
-
}
- >
-
-
+ fields={fields}
+ type={model.type}
+ restrict={RESTRICT_FIELDS}
+ indexPattern={indexPattern}
+ value={model.field ?? ''}
+ onChange={handleSelectChange('field')}
+ />
diff --git a/src/plugins/vis_type_timeseries/public/application/components/aggs/positive_rate.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/positive_rate.js
index 4b1528ca27081..09d9f2f1a62f2 100644
--- a/src/plugins/vis_type_timeseries/public/application/components/aggs/positive_rate.js
+++ b/src/plugins/vis_type_timeseries/public/application/components/aggs/positive_rate.js
@@ -99,27 +99,22 @@ export const PositiveRateAgg = (props) => {
/>
-
}
+ fields={props.fields}
+ type={model.type}
+ restrict={[KBN_FIELD_TYPES.NUMBER]}
+ indexPattern={indexPattern}
+ value={model.field}
+ onChange={handleSelectChange('field')}
+ uiRestrictions={props.uiRestrictions}
fullWidth
- >
-
-
+ />
-
}
+ fields={fields}
+ type={model.type}
+ restrict={restrictFields}
+ indexPattern={indexPattern}
+ value={model.field}
+ onChange={handleSelectChange('field')}
+ uiRestrictions={uiRestrictions}
fullWidth
- >
-
-
+ />
) : null}
diff --git a/src/plugins/vis_type_timeseries/public/application/components/aggs/std_deviation.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/std_deviation.js
index 749a97fa79f28..d4caa8a94652f 100644
--- a/src/plugins/vis_type_timeseries/public/application/components/aggs/std_deviation.js
+++ b/src/plugins/vis_type_timeseries/public/application/components/aggs/std_deviation.js
@@ -107,24 +107,20 @@ const StandardDeviationAggUi = (props) => {
/>
-
}
- >
-
-
+ fields={fields}
+ type={model.type}
+ restrict={RESTRICT_FIELDS}
+ indexPattern={indexPattern}
+ value={model.field}
+ onChange={handleSelectChange('field')}
+ />
{
/>
-
}
- >
-
-
+ fields={fields}
+ type={model.type}
+ restrict={aggWithOptionsRestrictFields}
+ indexPattern={indexPattern}
+ value={model.field}
+ onChange={handleSelectChange('field')}
+ />
@@ -223,23 +219,19 @@ const TopHitAggUi = (props) => {
-
}
- >
-
-
+ restrict={ORDER_DATE_RESTRICT_FIELDS}
+ value={model.order_by}
+ onChange={handleSelectChange('order_by')}
+ indexPattern={indexPattern}
+ fields={fields}
+ />
-
}
+ restrict={RESTRICT_FIELDS}
+ value={model.time_field}
+ onChange={this.handleChange(model, 'time_field')}
+ indexPattern={model.index_pattern}
+ fields={this.props.fields}
fullWidth
- >
-
-
+ />
diff --git a/src/plugins/vis_type_timeseries/public/application/components/index_pattern.js b/src/plugins/vis_type_timeseries/public/application/components/index_pattern.js
index 5a991238d10f8..e7a34c6e6596d 100644
--- a/src/plugins/vis_type_timeseries/public/application/components/index_pattern.js
+++ b/src/plugins/vis_type_timeseries/public/application/components/index_pattern.js
@@ -77,8 +77,8 @@ export const IndexPattern = ({
const intervalName = `${prefix}interval`;
const maxBarsName = `${prefix}max_bars`;
const dropBucketName = `${prefix}drop_last_bucket`;
- const defaultIndex = useContext(DefaultIndexPatternContext);
const updateControlValidity = useContext(FormValidationContext);
+ const defaultIndex = useContext(DefaultIndexPatternContext);
const uiRestrictions = get(useContext(VisDataContext), 'uiRestrictions');
const maxBarsUiSettings = config.get(UI_SETTINGS.HISTOGRAM_MAX_BARS);
@@ -192,22 +192,18 @@ export const IndexPattern = ({
/>
-
-
-
+ restrict={RESTRICT_FIELDS}
+ value={model[timeFieldName]}
+ disabled={disabled}
+ onChange={handleSelectChange(timeFieldName)}
+ indexPattern={model[indexPatternName]}
+ fields={fields}
+ placeholder={!model[indexPatternName] ? defaultIndex?.timeFieldName : undefined}
+ />
-
}
- >
-
-
+ fields={this.props.fields}
+ value={model.pivot_id}
+ indexPattern={model.index_pattern}
+ onChange={this.handlePivotChange}
+ uiRestrictions={this.context.uiRestrictions}
+ type={BUCKET_TYPES.TERMS}
+ />
diff --git a/src/plugins/vis_type_timeseries/public/application/components/splits/__snapshots__/terms.test.js.snap b/src/plugins/vis_type_timeseries/public/application/components/splits/__snapshots__/terms.test.js.snap
index 09cd6d550fd96..562c463f6c83c 100644
--- a/src/plugins/vis_type_timeseries/public/application/components/splits/__snapshots__/terms.test.js.snap
+++ b/src/plugins/vis_type_timeseries/public/application/components/splits/__snapshots__/terms.test.js.snap
@@ -26,13 +26,25 @@ exports[`src/legacy/core_plugins/metrics/public/components/splits/terms.test.js
-
}
- labelType="label"
- >
-
-
+ onChange={[Function]}
+ type="terms"
+ value="OriginCityName"
+ />
diff --git a/src/plugins/vis_type_timeseries/public/application/components/splits/terms.js b/src/plugins/vis_type_timeseries/public/application/components/splits/terms.js
index ab5342e925bd7..7db6a75e2392c 100644
--- a/src/plugins/vis_type_timeseries/public/application/components/splits/terms.js
+++ b/src/plugins/vis_type_timeseries/public/application/components/splits/terms.js
@@ -110,8 +110,7 @@ export const SplitByTermsUI = ({
-
}
- >
-
-
+ data-test-subj="groupByField"
+ indexPattern={indexPattern}
+ onChange={handleSelectChange('terms_field')}
+ value={model.terms_field}
+ fields={fields}
+ uiRestrictions={uiRestrictions}
+ type={'terms'}
+ />
diff --git a/src/plugins/vis_type_timeseries/public/application/components/vis_types/table/config.js b/src/plugins/vis_type_timeseries/public/application/components/vis_types/table/config.js
index 0ba8d3e855365..1940ac8b2e9b9 100644
--- a/src/plugins/vis_type_timeseries/public/application/components/vis_types/table/config.js
+++ b/src/plugins/vis_type_timeseries/public/application/components/vis_types/table/config.js
@@ -186,20 +186,16 @@ export class TableSeriesConfig extends Component {
-
}
- >
-
-
+ fields={this.props.fields}
+ indexPattern={this.props.panel.index_pattern}
+ value={model.aggregate_by}
+ onChange={handleSelectChange('aggregate_by')}
+ fullWidth
+ />
{
});
describe('text-based index', () => {
- test('should return the Kibana index if it exists', async () => {
- mockedIndices = [
- {
- id: 'indexId',
- title: 'indexTitle',
- },
- ] as IndexPattern[];
-
- const value = await cachedIndexPatternFetcher('indexTitle');
-
- expect(value).toMatchInlineSnapshot(`
- Object {
- "indexPattern": Object {
- "id": "indexId",
- "title": "indexTitle",
- },
- "indexPatternString": "indexTitle",
- }
- `);
- });
-
- test('should return only indexPatternString if Kibana index does not exist', async () => {
+ test('should return only indexPatternString', async () => {
const value = await cachedIndexPatternFetcher('indexTitle');
expect(value).toMatchInlineSnapshot(`
diff --git a/src/plugins/vis_type_timeseries/server/lib/search_strategies/lib/fields_fetcher.ts b/src/plugins/vis_type_timeseries/server/lib/search_strategies/lib/fields_fetcher.ts
index 9003eb7fc2ced..4b13e62430c47 100644
--- a/src/plugins/vis_type_timeseries/server/lib/search_strategies/lib/fields_fetcher.ts
+++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/lib/fields_fetcher.ts
@@ -6,10 +6,13 @@
* Side Public License, v 1.
*/
+import { getIndexPatternKey } from '../../../../common/index_patterns_utils';
+
import type { VisTypeTimeseriesVisDataRequest } from '../../../types';
import type { AbstractSearchStrategy, DefaultSearchCapabilities } from '../index';
import type { IndexPatternsService } from '../../../../../data/common';
import type { CachedIndexPatternFetcher } from './cached_index_pattern_fetcher';
+import type { IndexPatternValue } from '../../../../common/types';
export interface FieldsFetcherServices {
indexPatternsService: IndexPatternsService;
@@ -29,11 +32,13 @@ export const createFieldsFetcher = (
) => {
const fieldsCacheMap = new Map();
- return async (index: string) => {
- if (fieldsCacheMap.has(index)) {
- return fieldsCacheMap.get(index);
+ return async (indexPatternValue: IndexPatternValue) => {
+ const key = getIndexPatternKey(indexPatternValue);
+
+ if (fieldsCacheMap.has(key)) {
+ return fieldsCacheMap.get(key);
}
- const fetchedIndex = await cachedIndexPatternFetcher(index);
+ const fetchedIndex = await cachedIndexPatternFetcher(indexPatternValue);
const fields = await searchStrategy.getFieldsForWildcard(
fetchedIndex,
@@ -41,7 +46,7 @@ export const createFieldsFetcher = (
capabilities
);
- fieldsCacheMap.set(index, fields);
+ fieldsCacheMap.set(key, fields);
return fields;
};
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/annotations/build_request_body.ts b/src/plugins/vis_type_timeseries/server/lib/vis_data/annotations/build_request_body.ts
index 5a84598bb5ed2..1350e56b68f59 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/annotations/build_request_body.ts
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/annotations/build_request_body.ts
@@ -7,8 +7,8 @@
*/
import { IUiSettingsClient } from 'kibana/server';
-import { EsQueryConfig, IndexPattern } from 'src/plugins/data/server';
-import { AnnotationItemsSchema, PanelSchema } from '../../../../common/types';
+import { EsQueryConfig } from 'src/plugins/data/server';
+import { AnnotationItemsSchema, FetchedIndexPattern, PanelSchema } from '../../../../common/types';
import { VisTypeTimeseriesVisDataRequest } from '../../../types';
import { DefaultSearchCapabilities } from '../../search_strategies';
import { buildProcessorFunction } from '../build_processor_function';
@@ -17,16 +17,6 @@ import { processors } from '../request_processors/annotations';
/**
* Builds annotation request body
- *
- * @param {...args}: [
- * req: {Object} - a request object,
- * panel: {Object} - a panel object,
- * annotation: {Object} - an annotation object,
- * esQueryConfig: {Object} - es query config object,
- * indexPatternObject: {Object} - an index pattern object,
- * capabilities: {Object} - a search capabilities object
- * ]
- * @returns {Object} doc - processed body
*/
export async function buildAnnotationRequest(
...args: [
@@ -34,7 +24,7 @@ export async function buildAnnotationRequest(
PanelSchema,
AnnotationItemsSchema,
EsQueryConfig,
- IndexPattern | null | undefined,
+ FetchedIndexPattern,
DefaultSearchCapabilities,
IUiSettingsClient
]
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/annotations/get_request_params.ts b/src/plugins/vis_type_timeseries/server/lib/vis_data/annotations/get_request_params.ts
index 32086fbf4f5b4..40f1b4f2cc051 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/annotations/get_request_params.ts
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/annotations/get_request_params.ts
@@ -33,24 +33,23 @@ export async function getAnnotationRequestParams(
cachedIndexPatternFetcher,
}: AnnotationServices
) {
- const { indexPattern, indexPatternString } = await cachedIndexPatternFetcher(
- annotation.index_pattern
- );
+ const annotationIndex = await cachedIndexPatternFetcher(annotation.index_pattern);
const request = await buildAnnotationRequest(
req,
panel,
annotation,
esQueryConfig,
- indexPattern,
+ annotationIndex,
capabilities,
uiSettings
);
return {
- index: indexPatternString,
+ index: annotationIndex.indexPatternString,
body: {
...request,
+ runtime_mappings: annotationIndex.indexPattern?.getComputedFields().runtimeFields ?? {},
timeout: esShardTimeout > 0 ? `${esShardTimeout}ms` : undefined,
},
};
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.test.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.test.ts
similarity index 68%
rename from src/plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.test.js
rename to src/plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.test.ts
index ceb867e4e6d1e..7c0a0f5deb601 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.test.js
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.test.ts
@@ -7,25 +7,30 @@
*/
import { getIntervalAndTimefield } from './get_interval_and_timefield';
+import { FetchedIndexPattern, PanelSchema, SeriesItemsSchema } from '../../../common/types';
describe('getIntervalAndTimefield(panel, series)', () => {
+ const index: FetchedIndexPattern = {} as FetchedIndexPattern;
+
test('returns the panel interval and timefield', () => {
- const panel = { time_field: '@timestamp', interval: 'auto' };
- const series = {};
- expect(getIntervalAndTimefield(panel, series)).toEqual({
+ const panel = { time_field: '@timestamp', interval: 'auto' } as PanelSchema;
+ const series = {} as SeriesItemsSchema;
+
+ expect(getIntervalAndTimefield(panel, series, index)).toEqual({
timeField: '@timestamp',
interval: 'auto',
});
});
test('returns the series interval and timefield', () => {
- const panel = { time_field: '@timestamp', interval: 'auto' };
- const series = {
+ const panel = { time_field: '@timestamp', interval: 'auto' } as PanelSchema;
+ const series = ({
override_index_pattern: true,
series_interval: '1m',
series_time_field: 'time',
- };
- expect(getIntervalAndTimefield(panel, series)).toEqual({
+ } as unknown) as SeriesItemsSchema;
+
+ expect(getIntervalAndTimefield(panel, series, index)).toEqual({
timeField: 'time',
interval: '1m',
});
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.ts
similarity index 57%
rename from src/plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.js
rename to src/plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.ts
index ebab984ff25aa..e3d0cec1a6939 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.js
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.ts
@@ -7,28 +7,31 @@
*/
import { AUTO_INTERVAL } from '../../../common/constants';
+import { FetchedIndexPattern, PanelSchema, SeriesItemsSchema } from '../../../common/types';
+import { validateField } from '../../../common/fields_utils';
-const DEFAULT_TIME_FIELD = '@timestamp';
-
-export function getIntervalAndTimefield(panel, series = {}, indexPattern) {
- const getDefaultTimeField = () => indexPattern?.timeFieldName ?? DEFAULT_TIME_FIELD;
-
+export function getIntervalAndTimefield(
+ panel: PanelSchema,
+ series: SeriesItemsSchema,
+ index: FetchedIndexPattern
+) {
const timeField =
- (series.override_index_pattern && series.series_time_field) ||
- panel.time_field ||
- getDefaultTimeField();
+ (series.override_index_pattern ? series.series_time_field : panel.time_field) ||
+ index.indexPattern?.timeFieldName;
+
+ validateField(timeField!, index);
let interval = panel.interval;
let maxBars = panel.max_bars;
if (series.override_index_pattern) {
- interval = series.series_interval;
+ interval = series.series_interval || AUTO_INTERVAL;
maxBars = series.series_max_bars;
}
return {
+ maxBars,
timeField,
interval: interval || AUTO_INTERVAL,
- maxBars,
};
}
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/get_table_data.ts b/src/plugins/vis_type_timeseries/server/lib/vis_data/get_table_data.ts
index 0cc1188086b7b..b50fdb6b8226d 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/get_table_data.ts
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/get_table_data.ts
@@ -18,7 +18,7 @@ import { handleErrorResponse } from './handle_error_response';
import { processBucket } from './table/process_bucket';
import { createFieldsFetcher } from '../search_strategies/lib/fields_fetcher';
-import { extractFieldLabel } from '../../../common/calculate_label';
+import { extractFieldLabel } from '../../../common/fields_utils';
import type {
VisTypeTimeseriesRequestHandlerContext,
VisTypeTimeseriesRequestServices,
@@ -58,8 +58,8 @@ export async function getTableData(
});
const calculatePivotLabel = async () => {
- if (panel.pivot_id && panelIndex.indexPattern?.title) {
- const fields = await extractFields(panelIndex.indexPattern.title);
+ if (panel.pivot_id && panelIndex.indexPattern?.id) {
+ const fields = await extractFields({ id: panelIndex.indexPattern.id });
return extractFieldLabel(fields, panel.pivot_id);
}
@@ -68,7 +68,6 @@ export async function getTableData(
const meta = {
type: panel.type,
- pivot_label: panel.pivot_label || (await calculatePivotLabel()),
uiRestrictions: capabilities.uiRestrictions,
};
@@ -77,14 +76,17 @@ export async function getTableData(
req,
panel,
services.esQueryConfig,
- panelIndex.indexPattern,
+ panelIndex,
capabilities,
services.uiSettings
);
const [resp] = await searchStrategy.search(requestContext, req, [
{
- body,
+ body: {
+ ...body,
+ runtime_mappings: panelIndex.indexPattern?.getComputedFields().runtimeFields ?? {},
+ },
index: panelIndex.indexPatternString,
},
]);
@@ -101,6 +103,7 @@ export async function getTableData(
return {
...meta,
+ pivot_label: panel.pivot_label || (await calculatePivotLabel()),
series,
};
} catch (err) {
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_splits.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_splits.js
index 268c26115233e..27e7c5c908b9a 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_splits.js
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_splits.js
@@ -23,9 +23,8 @@ export async function getSplits(resp, panel, series, meta, extractFields) {
const color = new Color(series.color);
const metric = getLastMetric(series);
const buckets = _.get(resp, `aggregations.${series.id}.buckets`);
-
- const fieldsForMetaIndex = meta.index ? await extractFields(meta.index) : [];
- const splitByLabel = calculateLabel(metric, series.metrics, fieldsForMetaIndex);
+ const fieldsForSeries = meta.index ? await extractFields({ id: meta.index }) : [];
+ const splitByLabel = calculateLabel(metric, series.metrics, fieldsForSeries);
if (buckets) {
if (Array.isArray(buckets)) {
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/annotations/date_histogram.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/annotations/date_histogram.js
index 22a475a9997a7..f3ee416be81a8 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/annotations/date_histogram.js
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/annotations/date_histogram.js
@@ -10,6 +10,7 @@ import { overwrite } from '../../helpers';
import { getBucketSize } from '../../helpers/get_bucket_size';
import { getTimerange } from '../../helpers/get_timerange';
import { search, UI_SETTINGS } from '../../../../../../../plugins/data/server';
+import { validateField } from '../../../../../common/fields_utils';
const { dateHistogramInterval } = search.aggs;
@@ -18,13 +19,16 @@ export function dateHistogram(
panel,
annotation,
esQueryConfig,
- indexPattern,
+ annotationIndex,
capabilities,
uiSettings
) {
return (next) => async (doc) => {
const barTargetUiSettings = await uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET);
const timeField = annotation.time_field;
+
+ validateField(timeField, annotationIndex);
+
const { bucketSize, intervalString } = getBucketSize(
req,
'auto',
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/annotations/query.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/annotations/query.js
index e7270371a3fdc..46a3c369e548d 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/annotations/query.js
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/annotations/query.js
@@ -9,26 +9,30 @@
import { getBucketSize } from '../../helpers/get_bucket_size';
import { getTimerange } from '../../helpers/get_timerange';
import { esQuery, UI_SETTINGS } from '../../../../../../data/server';
+import { validateField } from '../../../../../common/fields_utils';
export function query(
req,
panel,
annotation,
esQueryConfig,
- indexPattern,
+ annotationIndex,
capabilities,
uiSettings
) {
return (next) => async (doc) => {
const barTargetUiSettings = await uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET);
- const timeField = annotation.time_field;
+ const timeField = (annotation.time_field || annotationIndex.indexPattern?.timeField) ?? '';
+
+ validateField(timeField, annotationIndex);
+
const { bucketSize } = getBucketSize(req, 'auto', capabilities, barTargetUiSettings);
const { from, to } = getTimerange(req);
doc.size = 0;
const queries = !annotation.ignore_global_filters ? req.body.query : [];
const filters = !annotation.ignore_global_filters ? req.body.filters : [];
- doc.query = esQuery.buildEsQuery(indexPattern, queries, filters, esQueryConfig);
+ doc.query = esQuery.buildEsQuery(annotationIndex.indexPattern, queries, filters, esQueryConfig);
const timerange = {
range: {
[timeField]: {
@@ -42,13 +46,18 @@ export function query(
if (annotation.query_string) {
doc.query.bool.must.push(
- esQuery.buildEsQuery(indexPattern, [annotation.query_string], [], esQueryConfig)
+ esQuery.buildEsQuery(
+ annotationIndex.indexPattern,
+ [annotation.query_string],
+ [],
+ esQueryConfig
+ )
);
}
if (!annotation.ignore_panel_filters && panel.filter) {
doc.query.bool.must.push(
- esQuery.buildEsQuery(indexPattern, [panel.filter], [], esQueryConfig)
+ esQuery.buildEsQuery(annotationIndex.indexPattern, [panel.filter], [], esQueryConfig)
);
}
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/annotations/top_hits.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/annotations/top_hits.js
index 2e759cb6b8b74..1b4434c4867c8 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/annotations/top_hits.js
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/annotations/top_hits.js
@@ -7,12 +7,15 @@
*/
import { overwrite } from '../../helpers';
+import { validateField } from '../../../../../common/fields_utils';
-export function topHits(req, panel, annotation) {
+export function topHits(req, panel, annotation, esQueryConfig, annotationIndex) {
return (next) => (doc) => {
const fields = (annotation.fields && annotation.fields.split(/[,\s]+/)) || [];
const timeField = annotation.time_field;
+ validateField(timeField, annotationIndex);
+
overwrite(doc, `aggs.${annotation.id}.aggs.hits.top_hits`, {
sort: [
{
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/date_histogram.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/date_histogram.js
index a9b4f99fdb693..41ed472c31936 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/date_histogram.js
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/date_histogram.js
@@ -12,6 +12,7 @@ import { offsetTime } from '../../offset_time';
import { getIntervalAndTimefield } from '../../get_interval_and_timefield';
import { isLastValueTimerangeMode } from '../../helpers/get_timerange_mode';
import { search, UI_SETTINGS } from '../../../../../../../plugins/data/server';
+
const { dateHistogramInterval } = search.aggs;
export function dateHistogram(
@@ -19,7 +20,7 @@ export function dateHistogram(
panel,
series,
esQueryConfig,
- indexPattern,
+ seriesIndex,
capabilities,
uiSettings
) {
@@ -27,7 +28,7 @@ export function dateHistogram(
const maxBarsUiSettings = await uiSettings.get(UI_SETTINGS.HISTOGRAM_MAX_BARS);
const barTargetUiSettings = await uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET);
- const { timeField, interval, maxBars } = getIntervalAndTimefield(panel, series, indexPattern);
+ const { timeField, interval, maxBars } = getIntervalAndTimefield(panel, series, seriesIndex);
const { bucketSize, intervalString } = getBucketSize(
req,
interval,
@@ -64,9 +65,9 @@ export function dateHistogram(
overwrite(doc, `aggs.${series.id}.meta`, {
timeField,
intervalString,
- index: indexPattern?.title,
bucketSize,
seriesId: series.id,
+ index: seriesIndex.indexPattern?.id,
});
return next(doc);
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/filter_ratios.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/filter_ratios.js
index 4639af9db83b8..d45943f6f21ac 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/filter_ratios.js
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/filter_ratios.js
@@ -12,19 +12,19 @@ import { esQuery } from '../../../../../../data/server';
const filter = (metric) => metric.type === 'filter_ratio';
-export function ratios(req, panel, series, esQueryConfig, indexPattern) {
+export function ratios(req, panel, series, esQueryConfig, seriesIndex) {
return (next) => (doc) => {
if (series.metrics.some(filter)) {
series.metrics.filter(filter).forEach((metric) => {
overwrite(
doc,
`aggs.${series.id}.aggs.timeseries.aggs.${metric.id}-numerator.filter`,
- esQuery.buildEsQuery(indexPattern, metric.numerator, [], esQueryConfig)
+ esQuery.buildEsQuery(seriesIndex.indexPattern, metric.numerator, [], esQueryConfig)
);
overwrite(
doc,
`aggs.${series.id}.aggs.timeseries.aggs.${metric.id}-denominator.filter`,
- esQuery.buildEsQuery(indexPattern, metric.denominator, [], esQueryConfig)
+ esQuery.buildEsQuery(seriesIndex.indexPattern, metric.denominator, [], esQueryConfig)
);
let numeratorPath = `${metric.id}-numerator>_count`;
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/filter_ratios.test.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/filter_ratios.test.js
index 345488ec01d5e..a93827ba82cd6 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/filter_ratios.test.js
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/filter_ratios.test.js
@@ -8,7 +8,7 @@
import { ratios } from './filter_ratios';
-describe('ratios(req, panel, series, esQueryConfig, indexPatternObject)', () => {
+describe('ratios(req, panel, series, esQueryConfig, seriesIndex)', () => {
let panel;
let series;
let req;
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/metric_buckets.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/metric_buckets.js
index 86b691f6496c9..29a11bf163e0b 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/metric_buckets.js
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/metric_buckets.js
@@ -17,14 +17,14 @@ export function metricBuckets(
panel,
series,
esQueryConfig,
- indexPattern,
+ seriesIndex,
capabilities,
uiSettings
) {
return (next) => async (doc) => {
const barTargetUiSettings = await uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET);
- const { interval } = getIntervalAndTimefield(panel, series, indexPattern);
+ const { interval } = getIntervalAndTimefield(panel, series, seriesIndex);
const { intervalString } = getBucketSize(req, interval, capabilities, barTargetUiSettings);
series.metrics
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/positive_rate.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/positive_rate.js
index ce61374c0b124..208321a98737e 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/positive_rate.js
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/positive_rate.js
@@ -56,14 +56,14 @@ export function positiveRate(
panel,
series,
esQueryConfig,
- indexPattern,
+ seriesIndex,
capabilities,
uiSettings
) {
return (next) => async (doc) => {
const barTargetUiSettings = await uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET);
- const { interval } = getIntervalAndTimefield(panel, series, indexPattern);
+ const { interval } = getIntervalAndTimefield(panel, series, seriesIndex);
const { intervalString } = getBucketSize(req, interval, capabilities, barTargetUiSettings);
if (series.metrics.some(filter)) {
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/query.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/query.js
index d0e92c9157cb5..a5f4e17289e06 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/query.js
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/query.js
@@ -10,16 +10,16 @@ import { offsetTime } from '../../offset_time';
import { getIntervalAndTimefield } from '../../get_interval_and_timefield';
import { esQuery } from '../../../../../../data/server';
-export function query(req, panel, series, esQueryConfig, indexPattern) {
+export function query(req, panel, series, esQueryConfig, seriesIndex) {
return (next) => (doc) => {
- const { timeField } = getIntervalAndTimefield(panel, series, indexPattern);
+ const { timeField } = getIntervalAndTimefield(panel, series, seriesIndex);
const { from, to } = offsetTime(req, series.offset_time);
doc.size = 0;
const ignoreGlobalFilter = panel.ignore_global_filter || series.ignore_global_filter;
const queries = !ignoreGlobalFilter ? req.body.query : [];
const filters = !ignoreGlobalFilter ? req.body.filters : [];
- doc.query = esQuery.buildEsQuery(indexPattern, queries, filters, esQueryConfig);
+ doc.query = esQuery.buildEsQuery(seriesIndex.indexPattern, queries, filters, esQueryConfig);
const timerange = {
range: {
@@ -34,13 +34,13 @@ export function query(req, panel, series, esQueryConfig, indexPattern) {
if (panel.filter) {
doc.query.bool.must.push(
- esQuery.buildEsQuery(indexPattern, [panel.filter], [], esQueryConfig)
+ esQuery.buildEsQuery(seriesIndex.indexPattern, [panel.filter], [], esQueryConfig)
);
}
if (series.filter) {
doc.query.bool.must.push(
- esQuery.buildEsQuery(indexPattern, [series.filter], [], esQueryConfig)
+ esQuery.buildEsQuery(seriesIndex.indexPattern, [series.filter], [], esQueryConfig)
);
}
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/query.test.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/query.test.js
index 2772aed822517..b3e88dbf1c6b9 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/query.test.js
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/query.test.js
@@ -8,15 +8,17 @@
import { query } from './query';
-describe('query(req, panel, series)', () => {
+describe('query', () => {
let panel;
let series;
let req;
+ let seriesIndex;
const config = {
allowLeadingWildcards: true,
queryStringOptions: { analyze_wildcard: true },
};
+
beforeEach(() => {
req = {
body: {
@@ -32,17 +34,18 @@ describe('query(req, panel, series)', () => {
interval: '10s',
};
series = { id: 'test' };
+ seriesIndex = {};
});
test('calls next when finished', () => {
const next = jest.fn();
- query(req, panel, series, config)(next)({});
+ query(req, panel, series, config, seriesIndex)(next)({});
expect(next.mock.calls.length).toEqual(1);
});
test('returns doc with query for timerange', () => {
const next = (doc) => doc;
- const doc = query(req, panel, series, config)(next)({});
+ const doc = query(req, panel, series, config, seriesIndex)(next)({});
expect(doc).toEqual({
size: 0,
query: {
@@ -69,7 +72,7 @@ describe('query(req, panel, series)', () => {
test('returns doc with query for timerange (offset by 1h)', () => {
series.offset_time = '1h';
const next = (doc) => doc;
- const doc = query(req, panel, series, config)(next)({});
+ const doc = query(req, panel, series, config, seriesIndex)(next)({});
expect(doc).toEqual({
size: 0,
query: {
@@ -108,7 +111,7 @@ describe('query(req, panel, series)', () => {
},
];
const next = (doc) => doc;
- const doc = query(req, panel, series, config)(next)({});
+ const doc = query(req, panel, series, config, seriesIndex)(next)({});
expect(doc).toEqual({
size: 0,
query: {
@@ -147,7 +150,7 @@ describe('query(req, panel, series)', () => {
test('returns doc with series filter', () => {
series.filter = { query: 'host:web-server', language: 'lucene' };
const next = (doc) => doc;
- const doc = query(req, panel, series, config)(next)({});
+ const doc = query(req, panel, series, config, seriesIndex)(next)({});
expect(doc).toEqual({
size: 0,
query: {
@@ -201,7 +204,7 @@ describe('query(req, panel, series)', () => {
];
panel.filter = { query: 'host:web-server', language: 'lucene' };
const next = (doc) => doc;
- const doc = query(req, panel, series, config)(next)({});
+ const doc = query(req, panel, series, config, seriesIndex)(next)({});
expect(doc).toEqual({
size: 0,
query: {
@@ -269,7 +272,7 @@ describe('query(req, panel, series)', () => {
panel.filter = { query: 'host:web-server', language: 'lucene' };
panel.ignore_global_filter = true;
const next = (doc) => doc;
- const doc = query(req, panel, series, config)(next)({});
+ const doc = query(req, panel, series, config, seriesIndex)(next)({});
expect(doc).toEqual({
size: 0,
query: {
@@ -325,7 +328,7 @@ describe('query(req, panel, series)', () => {
panel.filter = { query: 'host:web-server', language: 'lucene' };
series.ignore_global_filter = true;
const next = (doc) => doc;
- const doc = query(req, panel, series, config)(next)({});
+ const doc = query(req, panel, series, config, seriesIndex)(next)({});
expect(doc).toEqual({
size: 0,
query: {
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/sibling_buckets.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/sibling_buckets.js
index 401344d48f865..dbeb3b1393bd5 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/sibling_buckets.js
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/sibling_buckets.js
@@ -17,13 +17,13 @@ export function siblingBuckets(
panel,
series,
esQueryConfig,
- indexPattern,
+ seriesIndex,
capabilities,
uiSettings
) {
return (next) => async (doc) => {
const barTargetUiSettings = await uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET);
- const { interval } = getIntervalAndTimefield(panel, series, indexPattern);
+ const { interval } = getIntervalAndTimefield(panel, series, seriesIndex);
const { bucketSize } = getBucketSize(req, interval, capabilities, barTargetUiSettings);
series.metrics
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_filter.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_filter.js
index 25d62d4f7fe07..01e1b9f8d1dce 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_filter.js
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_filter.js
@@ -9,7 +9,7 @@
import { overwrite } from '../../helpers';
import { esQuery } from '../../../../../../data/server';
-export function splitByFilter(req, panel, series, esQueryConfig, indexPattern) {
+export function splitByFilter(req, panel, series, esQueryConfig, seriesIndex) {
return (next) => (doc) => {
if (series.split_mode !== 'filter') {
return next(doc);
@@ -18,7 +18,7 @@ export function splitByFilter(req, panel, series, esQueryConfig, indexPattern) {
overwrite(
doc,
`aggs.${series.id}.filter`,
- esQuery.buildEsQuery(indexPattern, [series.filter], [], esQueryConfig)
+ esQuery.buildEsQuery(seriesIndex.indexPattern, [series.filter], [], esQueryConfig)
);
return next(doc);
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_filter.test.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_filter.test.js
index ad6e84dbc7842..9722833837167 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_filter.test.js
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_filter.test.js
@@ -12,8 +12,12 @@ describe('splitByFilter(req, panel, series)', () => {
let panel;
let series;
let req;
+ let config;
+ let seriesIndex;
+
beforeEach(() => {
panel = {};
+ config = {};
series = {
id: 'test',
split_mode: 'filter',
@@ -27,17 +31,18 @@ describe('splitByFilter(req, panel, series)', () => {
},
},
};
+ seriesIndex = {};
});
test('calls next when finished', () => {
const next = jest.fn();
- splitByFilter(req, panel, series)(next)({});
+ splitByFilter(req, panel, series, config, seriesIndex)(next)({});
expect(next.mock.calls.length).toEqual(1);
});
test('returns a valid filter with a query_string', () => {
const next = (doc) => doc;
- const doc = splitByFilter(req, panel, series)(next)({});
+ const doc = splitByFilter(req, panel, series, config, seriesIndex)(next)({});
expect(doc).toEqual({
aggs: {
test: {
@@ -63,7 +68,7 @@ describe('splitByFilter(req, panel, series)', () => {
test('calls next and does not add a filter', () => {
series.split_mode = 'terms';
const next = jest.fn((doc) => doc);
- const doc = splitByFilter(req, panel, series)(next)({});
+ const doc = splitByFilter(req, panel, series, config, seriesIndex)(next)({});
expect(next.mock.calls.length).toEqual(1);
expect(doc).toEqual({});
});
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_filters.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_filters.js
index 237ed16e5a8b6..77b9ccc5880fe 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_filters.js
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_filters.js
@@ -9,11 +9,16 @@
import { overwrite } from '../../helpers';
import { esQuery } from '../../../../../../data/server';
-export function splitByFilters(req, panel, series, esQueryConfig, indexPattern) {
+export function splitByFilters(req, panel, series, esQueryConfig, seriesIndex) {
return (next) => (doc) => {
if (series.split_mode === 'filters' && series.split_filters) {
series.split_filters.forEach((filter) => {
- const builtEsQuery = esQuery.buildEsQuery(indexPattern, [filter.filter], [], esQueryConfig);
+ const builtEsQuery = esQuery.buildEsQuery(
+ seriesIndex.indexPattern,
+ [filter.filter],
+ [],
+ esQueryConfig
+ );
overwrite(doc, `aggs.${series.id}.filters.filters.${filter.id}`, builtEsQuery);
});
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_filters.test.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_filters.test.js
index fdcdfe45d2fd2..2a44bf2538a4b 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_filters.test.js
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_filters.test.js
@@ -12,7 +12,11 @@ describe('splitByFilters(req, panel, series)', () => {
let panel;
let series;
let req;
+ let config;
+ let seriesIndex;
+
beforeEach(() => {
+ config = {};
panel = {
time_field: 'timestamp',
};
@@ -43,17 +47,18 @@ describe('splitByFilters(req, panel, series)', () => {
},
},
};
+ seriesIndex = {};
});
test('calls next when finished', () => {
const next = jest.fn();
- splitByFilters(req, panel, series)(next)({});
+ splitByFilters(req, panel, series, config, seriesIndex)(next)({});
expect(next.mock.calls.length).toEqual(1);
});
test('returns a valid terms agg', () => {
const next = (doc) => doc;
- const doc = splitByFilters(req, panel, series)(next)({});
+ const doc = splitByFilters(req, panel, series, config, seriesIndex)(next)({});
expect(doc).toEqual({
aggs: {
test: {
@@ -97,7 +102,7 @@ describe('splitByFilters(req, panel, series)', () => {
test('calls next and does not add a terms agg', () => {
series.split_mode = 'everything';
const next = jest.fn((doc) => doc);
- const doc = splitByFilters(req, panel, series)(next)({});
+ const doc = splitByFilters(req, panel, series, config, seriesIndex)(next)({});
expect(next.mock.calls.length).toEqual(1);
expect(doc).toEqual({});
});
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_terms.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_terms.js
index 8f72bd2d12951..9c2bdbe03f886 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_terms.js
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_terms.js
@@ -10,13 +10,16 @@ import { overwrite } from '../../helpers';
import { basicAggs } from '../../../../../common/basic_aggs';
import { getBucketsPath } from '../../helpers/get_buckets_path';
import { bucketTransform } from '../../helpers/bucket_transform';
+import { validateField } from '../../../../../common/fields_utils';
-export function splitByTerms(req, panel, series) {
+export function splitByTerms(req, panel, series, esQueryConfig, seriesIndex) {
return (next) => (doc) => {
if (series.split_mode === 'terms' && series.terms_field) {
const termsField = series.terms_field;
const orderByTerms = series.terms_order_by;
+ validateField(termsField, seriesIndex);
+
const direction = series.terms_direction || 'desc';
const metric = series.metrics.find((item) => item.id === orderByTerms);
overwrite(doc, `aggs.${series.id}.terms.field`, termsField);
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_terms.test.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_terms.test.js
index 37d188c00eee3..984eb385ca4a6 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_terms.test.js
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_terms.test.js
@@ -8,11 +8,18 @@
import { splitByTerms } from './split_by_terms';
-describe('splitByTerms(req, panel, series)', () => {
+describe('splitByTerms', () => {
let panel;
let series;
let req;
+ let config;
+ let seriesIndex;
+
beforeEach(() => {
+ config = {
+ allowLeadingWildcards: true,
+ queryStringOptions: { analyze_wildcard: true },
+ };
panel = {
time_field: 'timestamp',
};
@@ -31,17 +38,18 @@ describe('splitByTerms(req, panel, series)', () => {
},
},
};
+ seriesIndex = {};
});
test('calls next when finished', () => {
const next = jest.fn();
- splitByTerms(req, panel, series)(next)({});
+ splitByTerms(req, panel, series, config, seriesIndex)(next)({});
expect(next.mock.calls.length).toEqual(1);
});
test('returns a valid terms agg', () => {
const next = (doc) => doc;
- const doc = splitByTerms(req, panel, series)(next)({});
+ const doc = splitByTerms(req, panel, series, config, seriesIndex)(next)({});
expect(doc).toEqual({
aggs: {
test: {
@@ -61,7 +69,7 @@ describe('splitByTerms(req, panel, series)', () => {
const next = (doc) => doc;
series.terms_order_by = '_key';
series.terms_direction = 'asc';
- const doc = splitByTerms(req, panel, series)(next)({});
+ const doc = splitByTerms(req, panel, series, config, seriesIndex)(next)({});
expect(doc).toEqual({
aggs: {
test: {
@@ -80,7 +88,7 @@ describe('splitByTerms(req, panel, series)', () => {
test('returns a valid terms agg with custom sort', () => {
series.terms_order_by = 'avgmetric';
const next = (doc) => doc;
- const doc = splitByTerms(req, panel, series)(next)({});
+ const doc = splitByTerms(req, panel, series, config, seriesIndex)(next)({});
expect(doc).toEqual({
aggs: {
test: {
@@ -106,7 +114,7 @@ describe('splitByTerms(req, panel, series)', () => {
test('calls next and does not add a terms agg', () => {
series.split_mode = 'everything';
const next = jest.fn((doc) => doc);
- const doc = splitByTerms(req, panel, series)(next)({});
+ const doc = splitByTerms(req, panel, series, config, seriesIndex)(next)({});
expect(next.mock.calls.length).toEqual(1);
expect(doc).toEqual({});
});
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/date_histogram.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/date_histogram.js
index aff1bd5041be5..4840e625383ca 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/date_histogram.js
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/date_histogram.js
@@ -13,15 +13,17 @@ import { getIntervalAndTimefield } from '../../get_interval_and_timefield';
import { getTimerange } from '../../helpers/get_timerange';
import { calculateAggRoot } from './calculate_agg_root';
import { search, UI_SETTINGS } from '../../../../../../../plugins/data/server';
+
const { dateHistogramInterval } = search.aggs;
-export function dateHistogram(req, panel, esQueryConfig, indexPattern, capabilities, uiSettings) {
+export function dateHistogram(req, panel, esQueryConfig, seriesIndex, capabilities, uiSettings) {
return (next) => async (doc) => {
const barTargetUiSettings = await uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET);
- const { timeField, interval } = getIntervalAndTimefield(panel, {}, indexPattern);
+ const { timeField, interval } = getIntervalAndTimefield(panel, {}, seriesIndex);
+
const meta = {
timeField,
- index: indexPattern?.title,
+ index: seriesIndex.indexPattern?.id,
};
const getDateHistogramForLastBucketMode = () => {
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/filter_ratios.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/filter_ratios.js
index abb5971908771..e15330334639f 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/filter_ratios.js
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/filter_ratios.js
@@ -13,7 +13,7 @@ import { calculateAggRoot } from './calculate_agg_root';
const filter = (metric) => metric.type === 'filter_ratio';
-export function ratios(req, panel, esQueryConfig, indexPattern) {
+export function ratios(req, panel, esQueryConfig, seriesIndex) {
return (next) => (doc) => {
panel.series.forEach((column) => {
const aggRoot = calculateAggRoot(doc, column);
@@ -22,12 +22,12 @@ export function ratios(req, panel, esQueryConfig, indexPattern) {
overwrite(
doc,
`${aggRoot}.timeseries.aggs.${metric.id}-numerator.filter`,
- esQuery.buildEsQuery(indexPattern, metric.numerator, [], esQueryConfig)
+ esQuery.buildEsQuery(seriesIndex.indexPattern, metric.numerator, [], esQueryConfig)
);
overwrite(
doc,
`${aggRoot}.timeseries.aggs.${metric.id}-denominator.filter`,
- esQuery.buildEsQuery(indexPattern, metric.denominator, [], esQueryConfig)
+ esQuery.buildEsQuery(seriesIndex.indexPattern, metric.denominator, [], esQueryConfig)
);
let numeratorPath = `${metric.id}-numerator>_count`;
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/metric_buckets.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/metric_buckets.js
index 5ce508bd9b279..421f9d2d75f0c 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/metric_buckets.js
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/metric_buckets.js
@@ -13,10 +13,10 @@ import { getIntervalAndTimefield } from '../../get_interval_and_timefield';
import { calculateAggRoot } from './calculate_agg_root';
import { UI_SETTINGS } from '../../../../../../data/common';
-export function metricBuckets(req, panel, esQueryConfig, indexPattern, capabilities, uiSettings) {
+export function metricBuckets(req, panel, esQueryConfig, seriesIndex, capabilities, uiSettings) {
return (next) => async (doc) => {
const barTargetUiSettings = await uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET);
- const { interval } = getIntervalAndTimefield(panel, {}, indexPattern);
+ const { interval } = getIntervalAndTimefield(panel, {}, seriesIndex);
const { intervalString } = getBucketSize(req, interval, capabilities, barTargetUiSettings);
panel.series.forEach((column) => {
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/positive_rate.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/positive_rate.js
index 176721e7b563a..3390362b56115 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/positive_rate.js
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/positive_rate.js
@@ -12,10 +12,10 @@ import { calculateAggRoot } from './calculate_agg_root';
import { createPositiveRate, filter } from '../series/positive_rate';
import { UI_SETTINGS } from '../../../../../../data/common';
-export function positiveRate(req, panel, esQueryConfig, indexPattern, capabilities, uiSettings) {
+export function positiveRate(req, panel, esQueryConfig, seriesIndex, capabilities, uiSettings) {
return (next) => async (doc) => {
const barTargetUiSettings = await uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET);
- const { interval } = getIntervalAndTimefield(panel, {}, indexPattern);
+ const { interval } = getIntervalAndTimefield(panel, {}, seriesIndex);
const { intervalString } = getBucketSize(req, interval, capabilities, barTargetUiSettings);
panel.series.forEach((column) => {
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/query.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/query.js
index 76df07b76e80e..66783e0cdfaef 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/query.js
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/query.js
@@ -10,16 +10,16 @@ import { getTimerange } from '../../helpers/get_timerange';
import { getIntervalAndTimefield } from '../../get_interval_and_timefield';
import { esQuery } from '../../../../../../data/server';
-export function query(req, panel, esQueryConfig, indexPattern) {
+export function query(req, panel, esQueryConfig, seriesIndex) {
return (next) => (doc) => {
- const { timeField } = getIntervalAndTimefield(panel, {}, indexPattern);
+ const { timeField } = getIntervalAndTimefield(panel, {}, seriesIndex);
const { from, to } = getTimerange(req);
doc.size = 0;
const queries = !panel.ignore_global_filter ? req.body.query : [];
const filters = !panel.ignore_global_filter ? req.body.filters : [];
- doc.query = esQuery.buildEsQuery(indexPattern, queries, filters, esQueryConfig);
+ doc.query = esQuery.buildEsQuery(seriesIndex.indexPattern, queries, filters, esQueryConfig);
const timerange = {
range: {
@@ -33,7 +33,7 @@ export function query(req, panel, esQueryConfig, indexPattern) {
doc.query.bool.must.push(timerange);
if (panel.filter) {
doc.query.bool.must.push(
- esQuery.buildEsQuery(indexPattern, [panel.filter], [], esQueryConfig)
+ esQuery.buildEsQuery(seriesIndex.indexPattern, [panel.filter], [], esQueryConfig)
);
}
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/sibling_buckets.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/sibling_buckets.js
index 5539f16df41e0..9b4b0f244fc2c 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/sibling_buckets.js
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/sibling_buckets.js
@@ -13,10 +13,10 @@ import { getIntervalAndTimefield } from '../../get_interval_and_timefield';
import { calculateAggRoot } from './calculate_agg_root';
import { UI_SETTINGS } from '../../../../../../data/common';
-export function siblingBuckets(req, panel, esQueryConfig, indexPattern, capabilities, uiSettings) {
+export function siblingBuckets(req, panel, esQueryConfig, seriesIndex, capabilities, uiSettings) {
return (next) => async (doc) => {
const barTargetUiSettings = await uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET);
- const { interval } = getIntervalAndTimefield(panel, {}, indexPattern);
+ const { interval } = getIntervalAndTimefield(panel, {}, seriesIndex);
const { bucketSize } = getBucketSize(req, interval, capabilities, barTargetUiSettings);
panel.series.forEach((column) => {
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/split_by_everything.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/split_by_everything.js
index 595d49ebbd836..cda022294507f 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/split_by_everything.js
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/split_by_everything.js
@@ -9,7 +9,7 @@
import { overwrite } from '../../helpers';
import { esQuery } from '../../../../../../data/server';
-export function splitByEverything(req, panel, esQueryConfig, indexPattern) {
+export function splitByEverything(req, panel, esQueryConfig, seriesIndex) {
return (next) => (doc) => {
panel.series
.filter((c) => !(c.aggregate_by && c.aggregate_function))
@@ -18,7 +18,7 @@ export function splitByEverything(req, panel, esQueryConfig, indexPattern) {
overwrite(
doc,
`aggs.pivot.aggs.${column.id}.filter`,
- esQuery.buildEsQuery(indexPattern, [column.filter], [], esQueryConfig)
+ esQuery.buildEsQuery(seriesIndex.indexPattern, [column.filter], [], esQueryConfig)
);
} else {
overwrite(doc, `aggs.pivot.aggs.${column.id}.filter.match_all`, {});
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/split_by_terms.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/split_by_terms.js
index b4e07455be0fb..b3afc334ac2dd 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/split_by_terms.js
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/split_by_terms.js
@@ -9,7 +9,7 @@
import { overwrite } from '../../helpers';
import { esQuery } from '../../../../../../data/server';
-export function splitByTerms(req, panel, esQueryConfig, indexPattern) {
+export function splitByTerms(req, panel, esQueryConfig, seriesIndex) {
return (next) => (doc) => {
panel.series
.filter((c) => c.aggregate_by && c.aggregate_function)
@@ -21,7 +21,7 @@ export function splitByTerms(req, panel, esQueryConfig, indexPattern) {
overwrite(
doc,
`aggs.pivot.aggs.${column.id}.column_filter.filter`,
- esQuery.buildEsQuery(indexPattern, [column.filter], [], esQueryConfig)
+ esQuery.buildEsQuery(seriesIndex.indexPattern, [column.filter], [], esQueryConfig)
);
}
});
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/series_agg.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/series_agg.js
index ba0271ba286a1..a803439c7581f 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/series_agg.js
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/series_agg.js
@@ -5,9 +5,8 @@
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
-
+import { last, first } from 'lodash';
import { SeriesAgg } from './_series_agg';
-import _ from 'lodash';
import { getDefaultDecoration } from '../../helpers/get_default_decoration';
import { calculateLabel } from '../../../../../common/calculate_label';
@@ -33,15 +32,14 @@ export function seriesAgg(resp, panel, series, meta, extractFields) {
return (fn && fn(acc)) || acc;
}, targetSeries);
- const fieldsForMetaIndex = meta.index ? await extractFields(meta.index) : [];
+ const fieldsForSeries = meta.index ? await extractFields({ id: meta.index }) : [];
results.push({
id: `${series.id}`,
label:
- series.label ||
- calculateLabel(_.last(series.metrics), series.metrics, fieldsForMetaIndex),
+ series.label || calculateLabel(last(series.metrics), series.metrics, fieldsForSeries),
color: series.color,
- data: _.first(data),
+ data: first(data),
...decoration,
});
}
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/table/series_agg.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/table/series_agg.js
index 9af05afd41182..ae4968e007b1d 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/table/series_agg.js
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/table/series_agg.js
@@ -7,7 +7,7 @@
*/
import { SeriesAgg } from './_series_agg';
-import _ from 'lodash';
+import { last, first } from 'lodash';
import { calculateLabel } from '../../../../../common/calculate_label';
export function seriesAgg(resp, panel, series, meta, extractFields) {
@@ -25,15 +25,13 @@ export function seriesAgg(resp, panel, series, meta, extractFields) {
});
const fn = SeriesAgg[series.aggregate_function];
const data = fn(targetSeries);
-
- const fieldsForMetaIndex = meta.index ? await extractFields(meta.index) : [];
+ const fieldsForSeries = meta.index ? await extractFields({ id: meta.index }) : [];
results.push({
id: `${series.id}`,
label:
- series.label ||
- calculateLabel(_.last(series.metrics), series.metrics, fieldsForMetaIndex),
- data: _.first(data),
+ series.label || calculateLabel(last(series.metrics), series.metrics, fieldsForSeries),
+ data: first(data),
});
}
return next(results);
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/series/build_request_body.ts b/src/plugins/vis_type_timeseries/server/lib/vis_data/series/build_request_body.ts
index bab3abe13bcb0..bc046cbdcf8aa 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/series/build_request_body.ts
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/series/build_request_body.ts
@@ -18,7 +18,7 @@ import { processors } from '../request_processors/series/index';
* panel: {Object} - a panel object,
* series: {Object} - an series object,
* esQueryConfig: {Object} - es query config object,
- * indexPatternObject: {Object} - an index pattern object,
+ * seriesIndex: {Object} - an index pattern object,
* capabilities: {Object} - a search capabilities object
* ]
* @returns {Object} doc - processed body
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/series/get_request_params.ts b/src/plugins/vis_type_timeseries/server/lib/vis_data/series/get_request_params.ts
index 1f2735da8fb06..827df30dacf6d 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/series/get_request_params.ts
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/series/get_request_params.ts
@@ -39,7 +39,7 @@ export async function getSeriesRequestParams(
panel,
series,
esQueryConfig,
- seriesIndex.indexPattern,
+ seriesIndex,
capabilities,
uiSettings
);
@@ -48,6 +48,7 @@ export async function getSeriesRequestParams(
index: seriesIndex.indexPatternString,
body: {
...request,
+ runtime_mappings: seriesIndex.indexPattern?.getComputedFields().runtimeFields ?? {},
timeout: esShardTimeout > 0 ? `${esShardTimeout}ms` : undefined,
},
};
diff --git a/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/__snapshots__/chart_options.test.tsx.snap b/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/__snapshots__/chart_options.test.tsx.snap
index 56f35ae021173..59a7cf966df91 100644
--- a/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/__snapshots__/chart_options.test.tsx.snap
+++ b/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/__snapshots__/chart_options.test.tsx.snap
@@ -54,6 +54,7 @@ exports[`ChartOptions component should init with the default set of props 1`] =
{
expect(setParamByIndex).toBeCalledWith('seriesParams', 0, paramName, ChartMode.Normal);
});
+
+ it('should set "stacked" mode and disabled control if the referenced axis is "percentage"', () => {
+ defaultProps.valueAxes[0].scale.mode = AxisMode.Percentage;
+ defaultProps.chart.mode = ChartMode.Normal;
+ const paramName = 'mode';
+ const comp = mount();
+
+ expect(setParamByIndex).toBeCalledWith('seriesParams', 0, paramName, ChartMode.Stacked);
+ expect(comp.find({ paramName }).prop('disabled')).toBeTruthy();
+ });
});
diff --git a/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/chart_options.tsx b/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/chart_options.tsx
index 6f0b4fc5c9d22..23452a87aae60 100644
--- a/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/chart_options.tsx
+++ b/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/chart_options.tsx
@@ -6,14 +6,14 @@
* Side Public License, v 1.
*/
-import React, { useMemo, useCallback } from 'react';
+import React, { useMemo, useCallback, useEffect, useState } from 'react';
import { i18n } from '@kbn/i18n';
import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui';
import { SelectOption } from '../../../../../../vis_default_editor/public';
-import { SeriesParam, ValueAxis } from '../../../../types';
+import { SeriesParam, ValueAxis, ChartMode, AxisMode } from '../../../../types';
import { LineOptions } from './line_options';
import { SetParamByIndex, ChangeValueAxis } from '.';
import { ChartType } from '../../../../../common';
@@ -38,6 +38,7 @@ function ChartOptions({
changeValueAxis,
setParamByIndex,
}: ChartOptionsParams) {
+ const [disabledMode, setDisabledMode] = useState(false);
const setChart: SetChart = useCallback(
(paramName, value) => {
setParamByIndex('seriesParams', index, paramName, value);
@@ -68,6 +69,20 @@ function ChartOptions({
[valueAxes]
);
+ useEffect(() => {
+ const valueAxisToMetric = valueAxes.find((valueAxis) => valueAxis.id === chart.valueAxis);
+ if (valueAxisToMetric) {
+ if (valueAxisToMetric.scale.mode === AxisMode.Percentage) {
+ setDisabledMode(true);
+ if (chart.mode !== ChartMode.Stacked) {
+ setChart('mode', ChartMode.Stacked);
+ }
+ } else if (disabledMode) {
+ setDisabledMode(false);
+ }
+ }
+ }, [valueAxes, chart, disabledMode, setChart, setDisabledMode]);
+
return (
<>
diff --git a/src/plugins/visualizations/public/components/visualization_container.tsx b/src/plugins/visualizations/public/components/visualization_container.tsx
index 3081c39530d75..063715b6438eb 100644
--- a/src/plugins/visualizations/public/components/visualization_container.tsx
+++ b/src/plugins/visualizations/public/components/visualization_container.tsx
@@ -10,6 +10,7 @@ import React, { ReactNode, Suspense } from 'react';
import { EuiLoadingChart } from '@elastic/eui';
import classNames from 'classnames';
import { VisualizationNoResults } from './visualization_noresults';
+import { VisualizationError } from './visualization_error';
import { IInterpreterRenderHandlers } from '../../../expressions/common';
interface VisualizationContainerProps {
@@ -18,6 +19,7 @@ interface VisualizationContainerProps {
children: ReactNode;
handlers: IInterpreterRenderHandlers;
showNoResult?: boolean;
+ error?: string;
}
export const VisualizationContainer = ({
@@ -26,6 +28,7 @@ export const VisualizationContainer = ({
children,
handlers,
showNoResult = false,
+ error,
}: VisualizationContainerProps) => {
const classes = classNames('visualization', className);
@@ -38,7 +41,13 @@ export const VisualizationContainer = ({
return (
- {showNoResult ? handlers.done()} /> : children}
+ {error ? (
+ handlers.done()} error={error} />
+ ) : showNoResult ? (
+ handlers.done()} />
+ ) : (
+ children
+ )}
);
diff --git a/src/plugins/visualizations/public/components/visualization_error.tsx b/src/plugins/visualizations/public/components/visualization_error.tsx
new file mode 100644
index 0000000000000..81600a4e3601c
--- /dev/null
+++ b/src/plugins/visualizations/public/components/visualization_error.tsx
@@ -0,0 +1,42 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { EuiEmptyPrompt } from '@elastic/eui';
+import React from 'react';
+
+interface VisualizationNoResultsProps {
+ onInit?: () => void;
+ error: string;
+}
+
+export class VisualizationError extends React.Component {
+ public render() {
+ return (
+ {this.props.error}
}
+ />
+ );
+ }
+
+ public componentDidMount() {
+ this.afterRender();
+ }
+
+ public componentDidUpdate() {
+ this.afterRender();
+ }
+
+ private afterRender() {
+ if (this.props.onInit) {
+ this.props.onInit();
+ }
+ }
+}
diff --git a/test/functional/apps/discover/_data_grid_context.ts b/test/functional/apps/discover/_data_grid_context.ts
index 326fba9e6c087..bc259c71b47b4 100644
--- a/test/functional/apps/discover/_data_grid_context.ts
+++ b/test/functional/apps/discover/_data_grid_context.ts
@@ -110,7 +110,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await alert?.accept();
expect(await browser.getCurrentUrl()).to.contain('#/context');
await PageObjects.header.waitUntilLoadingHasFinished();
- expect(await docTable.getRowsText()).to.have.length(6);
+ expect(await docTable.getBodyRows()).to.have.length(6);
});
});
}
diff --git a/test/functional/apps/discover/_discover_histogram.ts b/test/functional/apps/discover/_discover_histogram.ts
index 72deb74459ab9..e41422555f81d 100644
--- a/test/functional/apps/discover/_discover_histogram.ts
+++ b/test/functional/apps/discover/_discover_histogram.ts
@@ -21,9 +21,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
};
const testSubjects = getService('testSubjects');
const browser = getService('browser');
+ const retry = getService('retry');
- // FLAKY: https://github.com/elastic/kibana/issues/94532
- describe.skip('discover histogram', function describeIndexTests() {
+ describe('discover histogram', function describeIndexTests() {
before(async () => {
await esArchiver.loadIfNeeded('logstash_functional');
await esArchiver.load('long_window_logstash');
@@ -107,8 +107,11 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
canvasExists = await elasticChart.canvasExists();
expect(canvasExists).to.be(false);
await testSubjects.click('discoverChartToggle');
- canvasExists = await elasticChart.canvasExists();
- expect(canvasExists).to.be(true);
+ await retry.waitFor(`Discover histogram to be displayed`, async () => {
+ canvasExists = await elasticChart.canvasExists();
+ return canvasExists;
+ });
+
await PageObjects.discover.saveSearch('persisted hidden histogram');
await PageObjects.header.waitUntilLoadingHasFinished();
diff --git a/test/functional/apps/discover/_runtime_fields_editor.ts b/test/functional/apps/discover/_runtime_fields_editor.ts
new file mode 100644
index 0000000000000..729ad08db81aa
--- /dev/null
+++ b/test/functional/apps/discover/_runtime_fields_editor.ts
@@ -0,0 +1,47 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import expect from '@kbn/expect';
+import { FtrProviderContext } from './ftr_provider_context';
+
+export default function ({ getService, getPageObjects }: FtrProviderContext) {
+ const log = getService('log');
+ const kibanaServer = getService('kibanaServer');
+ const esArchiver = getService('esArchiver');
+ const fieldEditor = getService('fieldEditor');
+ const PageObjects = getPageObjects(['common', 'discover', 'header', 'timePicker']);
+ const defaultSettings = {
+ defaultIndex: 'logstash-*',
+ 'discover:searchFieldsFromSource': false,
+ };
+ describe('discover integration with runtime fields editor', function describeIndexTests() {
+ before(async function () {
+ await esArchiver.load('discover');
+ await esArchiver.loadIfNeeded('logstash_functional');
+ await kibanaServer.uiSettings.replace(defaultSettings);
+ log.debug('discover');
+ await PageObjects.common.navigateToApp('discover');
+ await PageObjects.timePicker.setDefaultAbsoluteRange();
+ });
+
+ after(async () => {
+ await kibanaServer.uiSettings.replace({ 'discover:searchFieldsFromSource': true });
+ });
+
+ it('allows adding custom label to existing fields', async function () {
+ await PageObjects.discover.clickFieldListItemAdd('bytes');
+ await PageObjects.discover.editField('bytes');
+ await fieldEditor.enableCustomLabel();
+ await fieldEditor.setCustomLabel('megabytes');
+ await fieldEditor.save();
+ await PageObjects.header.waitUntilLoadingHasFinished();
+ expect(await PageObjects.discover.getDocHeader()).to.have.string('megabytes');
+ expect((await PageObjects.discover.getAllFieldNames()).includes('megabytes')).to.be(true);
+ });
+ });
+}
diff --git a/test/functional/apps/discover/index.ts b/test/functional/apps/discover/index.ts
index e526cdaccbd4c..db76cd1c20c38 100644
--- a/test/functional/apps/discover/index.ts
+++ b/test/functional/apps/discover/index.ts
@@ -47,6 +47,7 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) {
loadTestFile(require.resolve('./_data_grid_doc_navigation'));
loadTestFile(require.resolve('./_data_grid_doc_table'));
loadTestFile(require.resolve('./_indexpattern_with_unmapped_fields'));
+ loadTestFile(require.resolve('./_runtime_fields_editor'));
loadTestFile(require.resolve('./_huge_fields'));
});
}
diff --git a/test/functional/apps/management/_create_index_pattern_wizard.js b/test/functional/apps/management/_create_index_pattern_wizard.js
index 8db11052d5ed0..306d251629396 100644
--- a/test/functional/apps/management/_create_index_pattern_wizard.js
+++ b/test/functional/apps/management/_create_index_pattern_wizard.js
@@ -12,7 +12,7 @@ export default function ({ getService, getPageObjects }) {
const kibanaServer = getService('kibanaServer');
const testSubjects = getService('testSubjects');
const es = getService('legacyEs');
- const PageObjects = getPageObjects(['settings', 'common']);
+ const PageObjects = getPageObjects(['settings', 'common', 'header']);
const security = getService('security');
describe('"Create Index Pattern" wizard', function () {
@@ -60,6 +60,12 @@ export default function ({ getService, getPageObjects }) {
await PageObjects.settings.createIndexPattern('alias1', false);
});
+ it('can delete an index pattern', async () => {
+ await PageObjects.settings.removeIndexPattern();
+ await PageObjects.header.waitUntilLoadingHasFinished();
+ await testSubjects.exists('indexPatternTable');
+ });
+
after(async () => {
await es.transport.request({
path: '/_aliases',
diff --git a/test/functional/page_objects/discover_page.ts b/test/functional/page_objects/discover_page.ts
index 32288239f9848..b4042e7072d7f 100644
--- a/test/functional/page_objects/discover_page.ts
+++ b/test/functional/page_objects/discover_page.ts
@@ -255,6 +255,14 @@ export function DiscoverPageProvider({ getService, getPageObjects }: FtrProvider
.map((field) => $(field).text());
}
+ public async editField(field: string) {
+ await retry.try(async () => {
+ await testSubjects.click(`field-${field}`);
+ await testSubjects.click(`discoverFieldListPanelEdit-${field}`);
+ await find.byClassName('indexPatternFieldEditor__form');
+ });
+ }
+
public async hasNoResults() {
return await testSubjects.exists('discoverNoResults');
}
diff --git a/test/functional/services/field_editor.ts b/test/functional/services/field_editor.ts
index 7d6dad4f7858e..342e2afec28d3 100644
--- a/test/functional/services/field_editor.ts
+++ b/test/functional/services/field_editor.ts
@@ -16,6 +16,12 @@ export function FieldEditorProvider({ getService }: FtrProviderContext) {
public async setName(name: string) {
await testSubjects.setValue('nameField > input', name);
}
+ public async enableCustomLabel() {
+ await testSubjects.setEuiSwitch('customLabelRow > toggle', 'check');
+ }
+ public async setCustomLabel(name: string) {
+ await testSubjects.setValue('customLabelRow > input', name);
+ }
public async enableValue() {
await testSubjects.setEuiSwitch('valueRow > toggle', 'check');
}
diff --git a/x-pack/plugins/embeddable_enhanced/public/plugin.ts b/x-pack/plugins/embeddable_enhanced/public/plugin.ts
index 96224644a457f..4b27b31ad3e0e 100644
--- a/x-pack/plugins/embeddable_enhanced/public/plugin.ts
+++ b/x-pack/plugins/embeddable_enhanced/public/plugin.ts
@@ -18,7 +18,6 @@ import {
defaultEmbeddableFactoryProvider,
EmbeddableContext,
PANEL_NOTIFICATION_TRIGGER,
- ViewMode,
} from '../../../../src/plugins/embeddable/public';
import { EnhancedEmbeddable } from './types';
import {
@@ -119,7 +118,6 @@ export class EmbeddableEnhancedPlugin
const dynamicActions = new DynamicActionManager({
isCompatible: async (context: unknown) => {
if (!this.isEmbeddableContext(context)) return false;
- if (context.embeddable.getInput().viewMode !== ViewMode.VIEW) return false;
return context.embeddable.runtimeId === embeddable.runtimeId;
},
storage,
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_form.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_form.tsx
index 238cba217da8e..a1ac30995f722 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_form.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_form.tsx
@@ -144,6 +144,7 @@ export const AgentPolicyForm: React.FunctionComponent = ({
isInvalid={Boolean(touchedFields[name] && validation[name])}
>
updateAgentPolicy({ [name]: e.target.value })}
@@ -283,7 +284,7 @@ export const AgentPolicyForm: React.FunctionComponent = ({
}}
/>
- {isEditing && 'id' in agentPolicy ? (
+ {isEditing && 'id' in agentPolicy && agentPolicy.is_managed !== true ? (
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/package_policies/package_policies_table.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/package_policies/package_policies_table.tsx
index db88de0ba720b..9e23fc775a213 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/package_policies/package_policies_table.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/package_policies/package_policies_table.tsx
@@ -167,42 +167,45 @@ export const PackagePoliciesTable: React.FunctionComponent = ({
}),
actions: [
{
- render: (packagePolicy: InMemoryPackagePolicy) => (
- {}}
- // key="packagePolicyView"
- // >
- //
- // ,
-
-
- ,
- // FIXME: implement Copy package policy action
- // {}} key="packagePolicyCopy">
- //
- // ,
+ render: (packagePolicy: InMemoryPackagePolicy) => {
+ const menuItems = [
+ // FIXME: implement View package policy action
+ // {}}
+ // key="packagePolicyView"
+ // >
+ //
+ // ,
+
+
+ ,
+ // FIXME: implement Copy package policy action
+ // {}} key="packagePolicyCopy">
+ //
+ // ,
+ ];
+
+ if (!agentPolicy.is_managed) {
+ menuItems.push(
{(deletePackagePoliciesPrompt) => {
return (
@@ -220,10 +223,11 @@ export const PackagePoliciesTable: React.FunctionComponent = ({
);
}}
- ,
- ]}
- />
- ),
+
+ );
+ }
+ return ;
+ },
},
],
},
@@ -244,19 +248,21 @@ export const PackagePoliciesTable: React.FunctionComponent = ({
}}
{...rest}
search={{
- toolsRight: [
-
-
- ,
- ],
+ toolsRight: agentPolicy.is_managed
+ ? []
+ : [
+
+
+ ,
+ ],
box: {
incremental: true,
schema: true,
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/index.tsx
index 350d6439c9d3d..3e6ca5944c380 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/index.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/index.tsx
@@ -12,6 +12,8 @@ import { FormattedMessage, FormattedDate } from '@kbn/i18n/react';
import {
EuiFlexGroup,
EuiFlexItem,
+ EuiIconTip,
+ EuiTitle,
EuiText,
EuiSpacer,
EuiButtonEmpty,
@@ -84,23 +86,42 @@ export const AgentPolicyDetailsPage: React.FunctionComponent = () => {
-
-
- {isLoading ? (
-
- ) : (
- (agentPolicy && agentPolicy.name) || (
-
+ ) : (
+
+
+
+
+ {(agentPolicy && agentPolicy.name) || (
+
+ )}
+
+
+
+ {agentPolicy?.is_managed && (
+
+
- )
+
)}
-
-
+
+ )}
{agentPolicy && agentPolicy.description ? (
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/index.tsx
index adeb56f489ea3..56b99f645f97c 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/index.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/index.tsx
@@ -194,17 +194,18 @@ export const AgentDetailsPage: React.FunctionComponent = () => {
),
},
{
- content: (
-
- ),
+ content:
+ isAgentPolicyLoading || agentPolicyData?.item?.is_managed ? undefined : (
+
+ ),
},
].map((item, index) => (
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/index.tsx
index 8e9c549fe5609..d01d290e129b8 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/index.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/index.tsx
@@ -341,9 +341,10 @@ export const AgentListPage: React.FunctionComponent<{}> = () => {
const isAgentSelectable = (agent: Agent) => {
if (!agent.active) return false;
+ if (!agent.policy_id) return true;
- const agentPolicy = agentPolicies.find((p) => p.id === agent.policy_id);
- const isManaged = agent.policy_id && agentPolicy?.is_managed === true;
+ const agentPolicy = agentPoliciesIndexedById[agent.policy_id];
+ const isManaged = agentPolicy?.is_managed === true;
return !isManaged;
};
diff --git a/x-pack/plugins/fleet/server/routes/enrollment_api_key/handler.ts b/x-pack/plugins/fleet/server/routes/enrollment_api_key/handler.ts
index c85dc06c38286..0959a9a88704a 100644
--- a/x-pack/plugins/fleet/server/routes/enrollment_api_key/handler.ts
+++ b/x-pack/plugins/fleet/server/routes/enrollment_api_key/handler.ts
@@ -67,10 +67,9 @@ export const postEnrollmentApiKeyHandler: RequestHandler<
export const deleteEnrollmentApiKeyHandler: RequestHandler<
TypeOf
> = async (context, request, response) => {
- const soClient = context.core.savedObjects.client;
const esClient = context.core.elasticsearch.client.asCurrentUser;
try {
- await APIKeyService.deleteEnrollmentApiKey(soClient, esClient, request.params.keyId);
+ await APIKeyService.deleteEnrollmentApiKey(esClient, request.params.keyId);
const body: DeleteEnrollmentAPIKeyResponse = { action: 'deleted' };
diff --git a/x-pack/plugins/fleet/server/services/agent_policy_update.ts b/x-pack/plugins/fleet/server/services/agent_policy_update.ts
index dc566b2c435a6..3f5f717c94597 100644
--- a/x-pack/plugins/fleet/server/services/agent_policy_update.ts
+++ b/x-pack/plugins/fleet/server/services/agent_policy_update.ts
@@ -56,6 +56,6 @@ export async function agentPolicyUpdateEventHandler(
if (action === 'deleted') {
await unenrollForAgentPolicyId(soClient, esClient, agentPolicyId);
- await deleteEnrollmentApiKeyForAgentPolicyId(soClient, esClient, agentPolicyId);
+ await deleteEnrollmentApiKeyForAgentPolicyId(esClient, agentPolicyId);
}
}
diff --git a/x-pack/plugins/fleet/server/services/api_keys/enrollment_api_key.ts b/x-pack/plugins/fleet/server/services/api_keys/enrollment_api_key.ts
index 7059cc96159b9..b8a24a006a674 100644
--- a/x-pack/plugins/fleet/server/services/api_keys/enrollment_api_key.ts
+++ b/x-pack/plugins/fleet/server/services/api_keys/enrollment_api_key.ts
@@ -17,7 +17,7 @@ import { ENROLLMENT_API_KEYS_INDEX } from '../../constants';
import { agentPolicyService } from '../agent_policy';
import { escapeSearchQueryPhrase } from '../saved_object';
-import { createAPIKey, invalidateAPIKeys } from './security';
+import { invalidateAPIKeys } from './security';
const uuidRegex = /^\([0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}\)$/;
@@ -77,14 +77,9 @@ export async function getEnrollmentAPIKey(
/**
* Invalidate an api key and mark it as inactive
- * @param soClient
* @param id
*/
-export async function deleteEnrollmentApiKey(
- soClient: SavedObjectsClientContract,
- esClient: ElasticsearchClient,
- id: string
-) {
+export async function deleteEnrollmentApiKey(esClient: ElasticsearchClient, id: string) {
const enrollmentApiKey = await getEnrollmentAPIKey(esClient, id);
await invalidateAPIKeys([enrollmentApiKey.api_key_id]);
@@ -102,7 +97,6 @@ export async function deleteEnrollmentApiKey(
}
export async function deleteEnrollmentApiKeyForAgentPolicyId(
- soClient: SavedObjectsClientContract,
esClient: ElasticsearchClient,
agentPolicyId: string
) {
@@ -120,7 +114,7 @@ export async function deleteEnrollmentApiKeyForAgentPolicyId(
}
for (const apiKey of items) {
- await deleteEnrollmentApiKey(soClient, esClient, apiKey.id);
+ await deleteEnrollmentApiKey(esClient, apiKey.id);
}
}
}
@@ -182,19 +176,37 @@ export async function generateEnrollmentAPIKey(
}
const name = providedKeyName ? `${providedKeyName} (${id})` : id;
- const key = await createAPIKey(soClient, name, {
- // Useless role to avoid to have the privilege of the user that created the key
- 'fleet-apikey-enroll': {
- cluster: [],
- applications: [
- {
- application: '.fleet',
- privileges: ['no-privileges'],
- resources: ['*'],
+
+ const { body: key } = await esClient.security
+ .createApiKey({
+ body: {
+ name,
+ // @ts-expect-error Metadata in api keys
+ metadata: {
+ managed_by: 'fleet',
+ managed: true,
+ type: 'enroll',
+ policy_id: data.agentPolicyId,
},
- ],
- },
- });
+ role_descriptors: {
+ // Useless role to avoid to have the privilege of the user that created the key
+ 'fleet-apikey-enroll': {
+ cluster: [],
+ index: [],
+ applications: [
+ {
+ application: '.fleet',
+ privileges: ['no-privileges'],
+ resources: ['*'],
+ },
+ ],
+ },
+ },
+ },
+ })
+ .catch((err) => {
+ throw new Error(`Impossible to create an api key: ${err.message}`);
+ });
if (!key) {
throw new Error(
diff --git a/x-pack/plugins/lens/public/datatable_visualization/visualization.tsx b/x-pack/plugins/lens/public/datatable_visualization/visualization.tsx
index 4094ecee74e1c..9bd482c73bff5 100644
--- a/x-pack/plugins/lens/public/datatable_visualization/visualization.tsx
+++ b/x-pack/plugins/lens/public/datatable_visualization/visualization.tsx
@@ -60,6 +60,7 @@ export const datatableVisualization: Visualization
groupLabel: i18n.translate('xpack.lens.datatable.groupLabel', {
defaultMessage: 'Tabular and single value',
}),
+ sortPriority: 1,
},
],
@@ -182,7 +183,7 @@ export const datatableVisualization: Visualization
{
groupId: 'rows',
groupLabel: i18n.translate('xpack.lens.datatable.breakdownRows', {
- defaultMessage: 'Split rows',
+ defaultMessage: 'Rows',
}),
groupTooltip: i18n.translate('xpack.lens.datatable.breakdownRows.description', {
defaultMessage:
@@ -209,7 +210,7 @@ export const datatableVisualization: Visualization
{
groupId: 'columns',
groupLabel: i18n.translate('xpack.lens.datatable.breakdownColumns', {
- defaultMessage: 'Split columns',
+ defaultMessage: 'Columns',
}),
groupTooltip: i18n.translate('xpack.lens.datatable.breakdownColumns.description', {
defaultMessage:
diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.tsx
index ef8c0798bb91e..5538dd26d0323 100644
--- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.tsx
+++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.tsx
@@ -219,12 +219,15 @@ export const ChartSwitch = memo(function ChartSwitch(props: Props) {
// reorganize visualizations in groups
const grouped: Record<
string,
- Array<
- VisualizationType & {
- visualizationId: string;
- selection: VisualizationSelection;
- }
- >
+ {
+ priority: number;
+ visualizations: Array<
+ VisualizationType & {
+ visualizationId: string;
+ selection: VisualizationSelection;
+ }
+ >;
+ }
> = {};
// Will need it later on to quickly pick up the metadata from it
const lookup: Record<
@@ -240,13 +243,17 @@ export const ChartSwitch = memo(function ChartSwitch(props: Props) {
visualizationType.label.toLowerCase().includes(lowercasedSearchTerm) ||
visualizationType.fullLabel?.toLowerCase().includes(lowercasedSearchTerm);
if (isSearchMatch) {
- grouped[visualizationType.groupLabel] = grouped[visualizationType.groupLabel] || [];
+ grouped[visualizationType.groupLabel] = grouped[visualizationType.groupLabel] || {
+ priority: 0,
+ visualizations: [],
+ };
const visualizationEntry = {
...visualizationType,
visualizationId,
selection: getSelection(visualizationId, visualizationType.id),
};
- grouped[visualizationType.groupLabel].push(visualizationEntry);
+ grouped[visualizationType.groupLabel].priority += visualizationType.sortPriority || 0;
+ grouped[visualizationType.groupLabel].visualizations.push(visualizationEntry);
lookup[`${visualizationId}:${visualizationType.id}`] = visualizationEntry;
}
}
@@ -254,9 +261,11 @@ export const ChartSwitch = memo(function ChartSwitch(props: Props) {
return {
visualizationTypes: Object.keys(grouped)
- .sort()
+ .sort((groupA, groupB) => {
+ return grouped[groupB].priority - grouped[groupA].priority;
+ })
.flatMap((group): SelectableEntry[] => {
- const visualizations = grouped[group];
+ const { visualizations } = grouped[group];
if (visualizations.length === 0) {
return [];
}
diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx
index 8a0b9922c736b..f9058b48dd1a8 100644
--- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx
+++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx
@@ -570,7 +570,7 @@ export const InnerVisualizationWrapper = ({
{
setLocalState((prevState: WorkspaceState) => ({
diff --git a/x-pack/plugins/lens/public/index.ts b/x-pack/plugins/lens/public/index.ts
index cedb648215c0e..fcfed9b9f1fc5 100644
--- a/x-pack/plugins/lens/public/index.ts
+++ b/x-pack/plugins/lens/public/index.ts
@@ -33,6 +33,7 @@ export type {
IndexPatternPersistedState,
PersistedIndexPatternLayer,
IndexPatternColumn,
+ FieldBasedIndexPatternColumn,
OperationType,
IncompleteColumn,
FiltersIndexPatternColumn,
diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/types.ts b/x-pack/plugins/lens/public/indexpattern_datasource/types.ts
index 79155184a5f6d..18f653c588ee8 100644
--- a/x-pack/plugins/lens/public/indexpattern_datasource/types.ts
+++ b/x-pack/plugins/lens/public/indexpattern_datasource/types.ts
@@ -11,6 +11,7 @@ import { IndexPatternAggRestrictions } from '../../../../../src/plugins/data/pub
import { DragDropIdentifier } from '../drag_drop/providers';
export {
+ FieldBasedIndexPatternColumn,
IndexPatternColumn,
OperationType,
IncompleteColumn,
diff --git a/x-pack/plugins/lens/public/metric_visualization/visualization.tsx b/x-pack/plugins/lens/public/metric_visualization/visualization.tsx
index 34b9e4d2b2526..e0977be7535af 100644
--- a/x-pack/plugins/lens/public/metric_visualization/visualization.tsx
+++ b/x-pack/plugins/lens/public/metric_visualization/visualization.tsx
@@ -55,6 +55,7 @@ export const metricVisualization: Visualization = {
groupLabel: i18n.translate('xpack.lens.metric.groupLabel', {
defaultMessage: 'Tabular and single value',
}),
+ sortPriority: 1,
},
],
diff --git a/x-pack/plugins/lens/public/types.ts b/x-pack/plugins/lens/public/types.ts
index 3d34d22c5048a..94b4433a82551 100644
--- a/x-pack/plugins/lens/public/types.ts
+++ b/x-pack/plugins/lens/public/types.ts
@@ -550,6 +550,11 @@ export interface VisualizationType {
* The group the visualization belongs to
*/
groupLabel: string;
+ /**
+ * The priority of the visualization in the list (global priority)
+ * Higher number means higher priority. When omitted defaults to 0
+ */
+ sortPriority?: number;
}
export interface Visualization {
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/apm/service_latency_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/apm/service_latency_config.ts
index 3fcf98f712bef..7af3252584819 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/apm/service_latency_config.ts
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/apm/service_latency_config.ts
@@ -8,7 +8,6 @@
import { ConfigProps, DataSeries } from '../../types';
import { FieldLabels } from '../constants';
import { buildPhraseFilter } from '../utils';
-import { OperationType } from '../../../../../../../lens/public';
export function getServiceLatencyLensConfig({ seriesId, indexPattern }: ConfigProps): DataSeries {
return {
@@ -20,11 +19,11 @@ export function getServiceLatencyLensConfig({ seriesId, indexPattern }: ConfigPr
sourceField: '@timestamp',
},
yAxisColumn: {
- operationType: 'average' as OperationType,
+ operationType: 'average',
sourceField: 'transaction.duration.us',
label: 'Latency',
},
- hasMetricType: true,
+ hasOperationType: true,
defaultFilters: [
'user_agent.name',
'user_agent.os.name',
@@ -37,7 +36,7 @@ export function getServiceLatencyLensConfig({ seriesId, indexPattern }: ConfigPr
'client.geo.country_name',
'user_agent.device.name',
],
- filters: [buildPhraseFilter('transaction.type', 'request', indexPattern)],
+ filters: buildPhraseFilter('transaction.type', 'request', indexPattern),
labels: { ...FieldLabels },
reportDefinitions: [
{
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/apm/service_throughput_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/apm/service_throughput_config.ts
index c0f3d6dc9b010..7b1d472ac8bbf 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/apm/service_throughput_config.ts
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/apm/service_throughput_config.ts
@@ -8,7 +8,6 @@
import { ConfigProps, DataSeries } from '../../types';
import { FieldLabels } from '../constants/constants';
import { buildPhraseFilter } from '../utils';
-import { OperationType } from '../../../../../../../lens/public';
export function getServiceThroughputLensConfig({
seriesId,
@@ -16,18 +15,18 @@ export function getServiceThroughputLensConfig({
}: ConfigProps): DataSeries {
return {
id: seriesId,
- reportType: 'service-latency',
+ reportType: 'service-throughput',
defaultSeriesType: 'line',
seriesTypes: ['line', 'bar'],
xAxisColumn: {
sourceField: '@timestamp',
},
yAxisColumn: {
- operationType: 'average' as OperationType,
+ operationType: 'average',
sourceField: 'transaction.duration.us',
label: 'Throughput',
},
- hasMetricType: true,
+ hasOperationType: true,
defaultFilters: [
'user_agent.name',
'user_agent.os.name',
@@ -40,7 +39,7 @@ export function getServiceThroughputLensConfig({
'client.geo.country_name',
'user_agent.device.name',
],
- filters: [buildPhraseFilter('transaction.type', 'request', indexPattern)],
+ filters: buildPhraseFilter('transaction.type', 'request', indexPattern),
labels: { ...FieldLabels },
reportDefinitions: [
{
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/constants.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/constants.ts
index ed849c1eb47b3..14cd24c42e6a2 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/constants.ts
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/constants.ts
@@ -8,6 +8,8 @@
import { AppDataType, ReportViewTypeId } from '../../types';
import { CLS_FIELD, FCP_FIELD, FID_FIELD, LCP_FIELD, TBT_FIELD } from './elasticsearch_fieldnames';
+export const DEFAULT_TIME = { from: 'now-1h', to: 'now' };
+
export const FieldLabels: Record = {
'user_agent.name': 'Browser family',
'user_agent.version': 'Browser version',
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/url_constants.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/url_constants.ts
index 5b99c19dbabb7..67d72a656744c 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/url_constants.ts
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/url_constants.ts
@@ -6,7 +6,7 @@
*/
export enum URL_KEYS {
- METRIC_TYPE = 'mt',
+ OPERATION_TYPE = 'op',
REPORT_TYPE = 'rt',
SERIES_TYPE = 'st',
BREAK_DOWN = 'bd',
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.test.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.test.ts
index 139f3ab0d82ed..0de78c45041d4 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.test.ts
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.test.ts
@@ -42,14 +42,18 @@ describe('Lens Attribute', () => {
it('should return expected field type', function () {
expect(JSON.stringify(lnsAttr.getFieldMeta('transaction.type'))).toEqual(
JSON.stringify({
- count: 0,
- name: 'transaction.type',
- type: 'string',
- esTypes: ['keyword'],
- scripted: false,
- searchable: true,
- aggregatable: true,
- readFromDocValues: true,
+ fieldMeta: {
+ count: 0,
+ name: 'transaction.type',
+ type: 'string',
+ esTypes: ['keyword'],
+ scripted: false,
+ searchable: true,
+ aggregatable: true,
+ readFromDocValues: true,
+ },
+ fieldName: 'transaction.type',
+ columnType: null,
})
);
});
@@ -57,14 +61,18 @@ describe('Lens Attribute', () => {
it('should return expected field type for custom field with default value', function () {
expect(JSON.stringify(lnsAttr.getFieldMeta('performance.metric'))).toEqual(
JSON.stringify({
- count: 0,
- name: 'transaction.duration.us',
- type: 'number',
- esTypes: ['long'],
- scripted: false,
- searchable: true,
- aggregatable: true,
- readFromDocValues: true,
+ fieldMeta: {
+ count: 0,
+ name: 'transaction.duration.us',
+ type: 'number',
+ esTypes: ['long'],
+ scripted: false,
+ searchable: true,
+ aggregatable: true,
+ readFromDocValues: true,
+ },
+ fieldName: 'transaction.duration.us',
+ columnType: null,
})
);
});
@@ -76,20 +84,45 @@ describe('Lens Attribute', () => {
expect(JSON.stringify(lnsAttr.getFieldMeta('performance.metric'))).toEqual(
JSON.stringify({
- count: 0,
- name: LCP_FIELD,
- type: 'number',
- esTypes: ['scaled_float'],
- scripted: false,
- searchable: true,
- aggregatable: true,
- readFromDocValues: true,
+ fieldMeta: {
+ count: 0,
+ name: LCP_FIELD,
+ type: 'number',
+ esTypes: ['scaled_float'],
+ scripted: false,
+ searchable: true,
+ aggregatable: true,
+ readFromDocValues: true,
+ },
+ fieldName: LCP_FIELD,
})
);
});
- it('should return expected number column', function () {
- expect(lnsAttr.getNumberColumn('transaction.duration.us')).toEqual({
+ it('should return expected number range column', function () {
+ expect(lnsAttr.getNumberRangeColumn('transaction.duration.us')).toEqual({
+ dataType: 'number',
+ isBucketed: true,
+ label: 'Page load time (Seconds)',
+ operationType: 'range',
+ params: {
+ maxBars: 'auto',
+ ranges: [
+ {
+ from: 0,
+ label: '',
+ to: 1000,
+ },
+ ],
+ type: 'histogram',
+ },
+ scale: 'interval',
+ sourceField: 'transaction.duration.us',
+ });
+ });
+
+ it('should return expected number operation column', function () {
+ expect(lnsAttr.getNumberRangeColumn('transaction.duration.us')).toEqual({
dataType: 'number',
isBucketed: true,
label: 'Page load time (Seconds)',
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts
index 589a93d160068..12a5b19fb02fc 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts
@@ -5,10 +5,14 @@
* 2.0.
*/
+import { i18n } from '@kbn/i18n';
+import { capitalize } from 'lodash';
import {
CountIndexPatternColumn,
DateHistogramIndexPatternColumn,
- LastValueIndexPatternColumn,
+ AvgIndexPatternColumn,
+ MedianIndexPatternColumn,
+ PercentileIndexPatternColumn,
OperationType,
PersistedIndexPatternLayer,
RangeIndexPatternColumn,
@@ -17,6 +21,8 @@ import {
XYState,
XYCurveType,
DataType,
+ OperationMetadata,
+ FieldBasedIndexPatternColumn,
} from '../../../../../../lens/public';
import {
buildPhraseFilter,
@@ -30,6 +36,15 @@ function getLayerReferenceName(layerId: string) {
return `indexpattern-datasource-layer-${layerId}`;
}
+function buildNumberColumn(sourceField: string) {
+ return {
+ sourceField,
+ dataType: 'number' as DataType,
+ isBucketed: false,
+ scale: 'ratio' as OperationMetadata['scale'],
+ };
+}
+
export class LensAttributes {
indexPattern: IndexPattern;
layers: Record;
@@ -44,7 +59,7 @@ export class LensAttributes {
reportViewConfig: DataSeries,
seriesType?: SeriesType,
filters?: UrlFilter[],
- metricType?: OperationType,
+ operationType?: OperationType,
reportDefinitions?: Record
) {
this.indexPattern = indexPattern;
@@ -52,8 +67,8 @@ export class LensAttributes {
this.filters = filters ?? [];
this.reportDefinitions = reportDefinitions ?? {};
- if (typeof reportViewConfig.yAxisColumn.operationType !== undefined && metricType) {
- reportViewConfig.yAxisColumn.operationType = metricType;
+ if (typeof reportViewConfig.yAxisColumn.operationType !== undefined && operationType) {
+ reportViewConfig.yAxisColumn.operationType = operationType as FieldBasedIndexPatternColumn['operationType'];
}
this.seriesType = seriesType ?? reportViewConfig.defaultSeriesType;
this.reportViewConfig = reportViewConfig;
@@ -93,7 +108,7 @@ export class LensAttributes {
this.visualization.layers[0].splitAccessor = undefined;
}
- getNumberColumn(sourceField: string): RangeIndexPatternColumn {
+ getNumberRangeColumn(sourceField: string): RangeIndexPatternColumn {
return {
sourceField,
label: this.reportViewConfig.labels[sourceField],
@@ -109,6 +124,38 @@ export class LensAttributes {
};
}
+ getNumberOperationColumn(
+ sourceField: string,
+ operationType: 'average' | 'median'
+ ): AvgIndexPatternColumn | MedianIndexPatternColumn {
+ return {
+ ...buildNumberColumn(sourceField),
+ label: i18n.translate('xpack.observability.expView.columns.operation.label', {
+ defaultMessage: '{operationType} of {sourceField}',
+ values: {
+ sourceField: this.reportViewConfig.labels[sourceField],
+ operationType: capitalize(operationType),
+ },
+ }),
+ operationType,
+ };
+ }
+
+ getPercentileNumberColumn(
+ sourceField: string,
+ percentileValue: string
+ ): PercentileIndexPatternColumn {
+ return {
+ ...buildNumberColumn(sourceField),
+ label: i18n.translate('xpack.observability.expView.columns.label', {
+ defaultMessage: '{percentileValue} percentile of {sourceField}',
+ values: { sourceField, percentileValue },
+ }),
+ operationType: 'percentile',
+ params: { percentile: Number(percentileValue.split('th')[0]) },
+ };
+ }
+
getDateHistogramColumn(sourceField: string): DateHistogramIndexPatternColumn {
return {
sourceField,
@@ -121,56 +168,89 @@ export class LensAttributes {
};
}
- getXAxis():
- | LastValueIndexPatternColumn
- | DateHistogramIndexPatternColumn
- | RangeIndexPatternColumn {
+ getXAxis() {
const { xAxisColumn } = this.reportViewConfig;
- const { type: fieldType, name: fieldName } = this.getFieldMeta(xAxisColumn.sourceField)!;
+ return this.getColumnBasedOnType(xAxisColumn.sourceField!);
+ }
+
+ getColumnBasedOnType(sourceField: string, operationType?: OperationType) {
+ const { fieldMeta, columnType, fieldName } = this.getFieldMeta(sourceField);
+ const { type: fieldType } = fieldMeta ?? {};
+
+ if (fieldName === 'Records') {
+ return this.getRecordsColumn();
+ }
if (fieldType === 'date') {
return this.getDateHistogramColumn(fieldName);
}
if (fieldType === 'number') {
- return this.getNumberColumn(fieldName);
+ if (columnType === 'operation' || operationType) {
+ if (operationType === 'median' || operationType === 'average') {
+ return this.getNumberOperationColumn(fieldName, operationType);
+ }
+ if (operationType?.includes('th')) {
+ return this.getPercentileNumberColumn(sourceField, operationType);
+ }
+ }
+ return this.getNumberRangeColumn(fieldName);
}
// FIXME review my approach again
return this.getDateHistogramColumn(fieldName);
}
- getFieldMeta(sourceField?: string) {
- let xAxisField = sourceField;
+ getCustomFieldName(sourceField: string) {
+ let fieldName = sourceField;
+ let columnType = null;
- if (xAxisField) {
- const rdf = this.reportViewConfig.reportDefinitions ?? [];
+ const rdf = this.reportViewConfig.reportDefinitions ?? [];
- const customField = rdf.find(({ field }) => field === xAxisField);
+ const customField = rdf.find(({ field }) => field === fieldName);
- if (customField) {
- if (this.reportDefinitions[xAxisField]) {
- xAxisField = this.reportDefinitions[xAxisField];
- } else if (customField.defaultValue) {
- xAxisField = customField.defaultValue;
- } else if (customField.options?.[0].field) {
- xAxisField = customField.options?.[0].field;
- }
+ if (customField) {
+ if (this.reportDefinitions[fieldName]) {
+ fieldName = this.reportDefinitions[fieldName];
+ if (customField?.options)
+ columnType = customField?.options?.find(({ field }) => field === fieldName)?.columnType;
+ } else if (customField.defaultValue) {
+ fieldName = customField.defaultValue;
+ } else if (customField.options?.[0].field) {
+ fieldName = customField.options?.[0].field;
+ columnType = customField.options?.[0].columnType;
}
-
- return this.indexPattern.getFieldByName(xAxisField);
}
+
+ return { fieldName, columnType };
+ }
+
+ getFieldMeta(sourceField: string) {
+ const { fieldName, columnType } = this.getCustomFieldName(sourceField);
+
+ const fieldMeta = this.indexPattern.getFieldByName(fieldName);
+
+ return { fieldMeta, fieldName, columnType };
}
getMainYAxis() {
+ const { sourceField, operationType, label } = this.reportViewConfig.yAxisColumn;
+
+ if (sourceField === 'Records' || !sourceField) {
+ return this.getRecordsColumn(label);
+ }
+
+ return this.getColumnBasedOnType(sourceField!, operationType);
+ }
+
+ getRecordsColumn(label?: string): CountIndexPatternColumn {
return {
dataType: 'number',
isBucketed: false,
- label: 'Count of records',
+ label: label || 'Count of records',
operationType: 'count',
scale: 'ratio',
sourceField: 'Records',
- ...this.reportViewConfig.yAxisColumn,
} as CountIndexPatternColumn;
}
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/logs/logs_frequency_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/logs/logs_frequency_config.ts
index 8a27d7ddd428b..9f8a336b59d34 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/logs/logs_frequency_config.ts
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/logs/logs_frequency_config.ts
@@ -24,7 +24,7 @@ export function getLogsFrequencyLensConfig({ seriesId }: Props): DataSeries {
yAxisColumn: {
operationType: 'count',
},
- hasMetricType: false,
+ hasOperationType: false,
defaultFilters: [],
breakdowns: ['agent.hostname'],
filters: [],
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/metrics/cpu_usage_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/metrics/cpu_usage_config.ts
index 6214975d8f1dd..d4b807de11f4e 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/metrics/cpu_usage_config.ts
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/metrics/cpu_usage_config.ts
@@ -7,7 +7,6 @@
import { DataSeries } from '../../types';
import { FieldLabels } from '../constants';
-import { OperationType } from '../../../../../../../lens/public';
interface Props {
seriesId: string;
@@ -23,11 +22,11 @@ export function getCPUUsageLensConfig({ seriesId }: Props): DataSeries {
sourceField: '@timestamp',
},
yAxisColumn: {
- operationType: 'average' as OperationType,
+ operationType: 'average',
sourceField: 'system.cpu.user.pct',
label: 'CPU Usage %',
},
- hasMetricType: true,
+ hasOperationType: true,
defaultFilters: [],
breakdowns: ['host.hostname'],
filters: [],
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/metrics/memory_usage_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/metrics/memory_usage_config.ts
index 6f46c175f7882..38d1c425fc09a 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/metrics/memory_usage_config.ts
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/metrics/memory_usage_config.ts
@@ -7,7 +7,6 @@
import { DataSeries } from '../../types';
import { FieldLabels } from '../constants';
-import { OperationType } from '../../../../../../../lens/public';
interface Props {
seriesId: string;
@@ -23,11 +22,11 @@ export function getMemoryUsageLensConfig({ seriesId }: Props): DataSeries {
sourceField: '@timestamp',
},
yAxisColumn: {
- operationType: 'average' as OperationType,
+ operationType: 'average',
sourceField: 'system.memory.used.pct',
label: 'Memory Usage %',
},
- hasMetricType: true,
+ hasOperationType: true,
defaultFilters: [],
breakdowns: ['host.hostname'],
filters: [],
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/metrics/network_activity_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/metrics/network_activity_config.ts
index 1bc9fed9c3f80..07a521225b38d 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/metrics/network_activity_config.ts
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/metrics/network_activity_config.ts
@@ -7,7 +7,6 @@
import { DataSeries } from '../../types';
import { FieldLabels } from '../constants';
-import { OperationType } from '../../../../../../../lens/public';
interface Props {
seriesId: string;
@@ -23,10 +22,10 @@ export function getNetworkActivityLensConfig({ seriesId }: Props): DataSeries {
sourceField: '@timestamp',
},
yAxisColumn: {
- operationType: 'average' as OperationType,
+ operationType: 'average',
sourceField: 'system.memory.used.pct',
},
- hasMetricType: true,
+ hasOperationType: true,
defaultFilters: [],
breakdowns: ['host.hostname'],
filters: [],
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/rum/kpi_trends_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/rum/kpi_trends_config.ts
index a1a3acd51f89c..cd38d912850cf 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/rum/kpi_trends_config.ts
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/rum/kpi_trends_config.ts
@@ -10,14 +10,21 @@ import { FieldLabels } from '../constants';
import { buildPhraseFilter } from '../utils';
import {
CLIENT_GEO_COUNTRY_NAME,
+ CLS_FIELD,
+ FCP_FIELD,
+ FID_FIELD,
+ LCP_FIELD,
PROCESSOR_EVENT,
SERVICE_ENVIRONMENT,
SERVICE_NAME,
+ TBT_FIELD,
+ TRANSACTION_DURATION,
TRANSACTION_TYPE,
USER_AGENT_DEVICE,
USER_AGENT_NAME,
USER_AGENT_OS,
USER_AGENT_VERSION,
+ TRANSACTION_TIME_TO_FIRST_BYTE,
} from '../constants/elasticsearch_fieldnames';
export function getKPITrendsLensConfig({ seriesId, indexPattern }: ConfigProps): DataSeries {
@@ -30,10 +37,10 @@ export function getKPITrendsLensConfig({ seriesId, indexPattern }: ConfigProps):
sourceField: '@timestamp',
},
yAxisColumn: {
- operationType: 'count',
- label: 'Page views',
+ sourceField: 'business.kpi',
+ operationType: 'median',
},
- hasMetricType: false,
+ hasOperationType: false,
defaultFilters: [
USER_AGENT_OS,
CLIENT_GEO_COUNTRY_NAME,
@@ -45,10 +52,10 @@ export function getKPITrendsLensConfig({ seriesId, indexPattern }: ConfigProps):
],
breakdowns: [USER_AGENT_NAME, USER_AGENT_OS, CLIENT_GEO_COUNTRY_NAME, USER_AGENT_DEVICE],
filters: [
- buildPhraseFilter(TRANSACTION_TYPE, 'page-load', indexPattern),
- buildPhraseFilter(PROCESSOR_EVENT, 'transaction', indexPattern),
+ ...buildPhraseFilter(TRANSACTION_TYPE, 'page-load', indexPattern),
+ ...buildPhraseFilter(PROCESSOR_EVENT, 'transaction', indexPattern),
],
- labels: { ...FieldLabels, SERVICE_NAME: 'Web Application' },
+ labels: { ...FieldLabels, [SERVICE_NAME]: 'Web Application' },
reportDefinitions: [
{
field: SERVICE_NAME,
@@ -58,14 +65,18 @@ export function getKPITrendsLensConfig({ seriesId, indexPattern }: ConfigProps):
field: SERVICE_ENVIRONMENT,
},
{
- field: 'Business.KPI',
+ field: 'business.kpi',
custom: true,
defaultValue: 'Records',
options: [
- {
- field: 'Records',
- label: 'Page views',
- },
+ { field: 'Records', label: 'Page views' },
+ { label: 'Page load time', field: TRANSACTION_DURATION, columnType: 'operation' },
+ { label: 'Backend time', field: TRANSACTION_TIME_TO_FIRST_BYTE, columnType: 'operation' },
+ { label: 'First contentful paint', field: FCP_FIELD, columnType: 'operation' },
+ { label: 'Total blocking time', field: TBT_FIELD, columnType: 'operation' },
+ { label: 'Largest contentful paint', field: LCP_FIELD, columnType: 'operation' },
+ { label: 'First input delay', field: FID_FIELD, columnType: 'operation' },
+ { label: 'Cumulative layout shift', field: CLS_FIELD, columnType: 'operation' },
],
},
],
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/rum/performance_dist_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/rum/performance_dist_config.ts
index 7005dea29d60d..4b6d5dd6e741b 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/rum/performance_dist_config.ts
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/rum/performance_dist_config.ts
@@ -19,6 +19,7 @@ import {
SERVICE_NAME,
TBT_FIELD,
TRANSACTION_DURATION,
+ TRANSACTION_TIME_TO_FIRST_BYTE,
TRANSACTION_TYPE,
USER_AGENT_DEVICE,
USER_AGENT_NAME,
@@ -36,10 +37,10 @@ export function getPerformanceDistLensConfig({ seriesId, indexPattern }: ConfigP
sourceField: 'performance.metric',
},
yAxisColumn: {
- operationType: 'count',
+ sourceField: 'Records',
label: 'Pages loaded',
},
- hasMetricType: false,
+ hasOperationType: false,
defaultFilters: [
USER_AGENT_OS,
CLIENT_GEO_COUNTRY_NAME,
@@ -64,6 +65,7 @@ export function getPerformanceDistLensConfig({ seriesId, indexPattern }: ConfigP
defaultValue: TRANSACTION_DURATION,
options: [
{ label: 'Page load time', field: TRANSACTION_DURATION },
+ { label: 'Backend time', field: TRANSACTION_TIME_TO_FIRST_BYTE },
{ label: 'First contentful paint', field: FCP_FIELD },
{ label: 'Total blocking time', field: TBT_FIELD },
// FIXME, review if we need these descriptions
@@ -74,8 +76,8 @@ export function getPerformanceDistLensConfig({ seriesId, indexPattern }: ConfigP
},
],
filters: [
- buildPhraseFilter(TRANSACTION_TYPE, 'page-load', indexPattern),
- buildPhraseFilter(PROCESSOR_EVENT, 'transaction', indexPattern),
+ ...buildPhraseFilter(TRANSACTION_TYPE, 'page-load', indexPattern),
+ ...buildPhraseFilter(PROCESSOR_EVENT, 'transaction', indexPattern),
],
labels: {
...FieldLabels,
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/field_formats.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/field_formats.ts
index 4f036f0b9be65..8dad1839f0bcd 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/field_formats.ts
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/field_formats.ts
@@ -16,6 +16,7 @@ export const syntheticsFieldFormats: FieldFormat[] = [
inputFormat: 'microseconds',
outputFormat: 'asMilliseconds',
outputPrecision: 0,
+ showSuffix: true,
},
},
},
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/monitor_duration_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/monitor_duration_config.ts
index f0ec3f0c31bef..efbc3d14441c2 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/monitor_duration_config.ts
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/monitor_duration_config.ts
@@ -6,8 +6,7 @@
*/
import { DataSeries } from '../../types';
-import { FieldLabels } from '../constants/constants';
-import { OperationType } from '../../../../../../../lens/public';
+import { FieldLabels } from '../constants';
interface Props {
seriesId: string;
@@ -23,11 +22,11 @@ export function getMonitorDurationConfig({ seriesId }: Props): DataSeries {
sourceField: '@timestamp',
},
yAxisColumn: {
- operationType: 'average' as OperationType,
+ operationType: 'average',
sourceField: 'monitor.duration.us',
label: 'Monitor duration (ms)',
},
- hasMetricType: true,
+ hasOperationType: true,
defaultFilters: ['monitor.type', 'observer.geo.name', 'tags'],
breakdowns: [
'observer.geo.name',
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/monitor_pings_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/monitor_pings_config.ts
index 40c9f5750fb4d..68a36dcdcaf85 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/monitor_pings_config.ts
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/monitor_pings_config.ts
@@ -25,7 +25,7 @@ export function getMonitorPingsConfig({ seriesId }: Props): DataSeries {
operationType: 'count',
label: 'Monitor pings',
},
- hasMetricType: false,
+ hasOperationType: false,
defaultFilters: ['observer.geo.name'],
breakdowns: ['monitor.status', 'observer.geo.name', 'monitor.type'],
filters: [],
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/utils.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/utils.ts
index c885673134786..c6b7b5d92d5f8 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/utils.ts
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/utils.ts
@@ -13,7 +13,7 @@ import { URL_KEYS } from './constants/url_constants';
export function convertToShortUrl(series: SeriesUrl) {
const {
- metric,
+ operationType,
seriesType,
reportType,
breakdown,
@@ -23,7 +23,7 @@ export function convertToShortUrl(series: SeriesUrl) {
} = series;
return {
- [URL_KEYS.METRIC_TYPE]: metric,
+ [URL_KEYS.OPERATION_TYPE]: operationType,
[URL_KEYS.REPORT_TYPE]: reportType,
[URL_KEYS.SERIES_TYPE]: seriesType,
[URL_KEYS.BREAK_DOWN]: breakdown,
@@ -49,6 +49,9 @@ export function createExploratoryViewUrl(allSeries: AllSeries, baseHref = '') {
}
export function buildPhraseFilter(field: string, value: any, indexPattern: IIndexPattern) {
- const fieldMeta = indexPattern.fields.find((fieldT) => fieldT.name === field)!;
- return esFilters.buildPhraseFilter(fieldMeta, value, indexPattern);
+ const fieldMeta = indexPattern.fields.find((fieldT) => fieldT.name === field);
+ if (fieldMeta) {
+ return [esFilters.buildPhraseFilter(fieldMeta, value, indexPattern)];
+ }
+ return [];
}
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.tsx
index 0e7bc80e8659c..6bc069aafa5b8 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.tsx
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.tsx
@@ -6,8 +6,7 @@
*/
import { i18n } from '@kbn/i18n';
import React, { useEffect, useState } from 'react';
-import styled from 'styled-components';
-import { EuiLoadingSpinner, EuiPanel, EuiTitle } from '@elastic/eui';
+import { EuiPanel, EuiTitle } from '@elastic/eui';
import { useKibana } from '../../../../../../../src/plugins/kibana_react/public';
import { ObservabilityPublicPluginsStart } from '../../../plugin';
import { ExploratoryViewHeader } from './header/header';
@@ -15,7 +14,6 @@ import { SeriesEditor } from './series_editor/series_editor';
import { useUrlStorage } from './hooks/use_url_storage';
import { useLensAttributes } from './hooks/use_lens_attributes';
import { EmptyView } from './components/empty_view';
-import { useIndexPatternContext } from './hooks/use_default_index_pattern';
import { TypedLensByValueInput } from '../../../../../lens/public';
export function ExploratoryView() {
@@ -27,15 +25,12 @@ export function ExploratoryView() {
null
);
- const { indexPattern } = useIndexPatternContext();
-
const LensComponent = lens?.EmbeddableComponent;
const { firstSeriesId: seriesId, firstSeries: series } = useUrlStorage();
const lensAttributesT = useLensAttributes({
seriesId,
- indexPattern,
});
useEffect(() => {
@@ -48,11 +43,6 @@ export function ExploratoryView() {
{lens ? (
<>
- {!indexPattern && (
-
-
-
- )}
{lensAttributes && seriesId && series?.reportType && series?.time ? (
);
}
-
-const SpinnerWrap = styled.div`
- height: 100vh;
- display: flex;
- justify-content: center;
- align-items: center;
-`;
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_default_index_pattern.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_default_index_pattern.tsx
index 7ead7d5e3cfad..c5a4d02492662 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_default_index_pattern.tsx
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_default_index_pattern.tsx
@@ -39,6 +39,7 @@ export function IndexPatternContextProvider({
} = useKibana();
const loadIndexPattern = async (dataType: AppDataType) => {
+ setIndexPattern(undefined);
const obsvIndexP = new ObservabilityIndexPatterns(data);
const indPattern = await obsvIndexP.getIndexPattern(dataType);
setIndexPattern(indPattern!);
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_init_exploratory_view.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_init_exploratory_view.ts
index 76fd64ef86736..de4343b290118 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_init_exploratory_view.ts
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_init_exploratory_view.ts
@@ -27,15 +27,17 @@ export const useInitExploratoryView = (storage: IKbnUrlStateStorage) => {
const firstSeries = allSeries[firstSeriesId];
+ let dataType: DataType = firstSeries?.dataType ?? 'rum';
+
+ if (firstSeries?.rt) {
+ dataType = ReportToDataTypeMap[firstSeries?.rt];
+ }
+
const { data: indexPattern, error } = useFetcher(() => {
const obsvIndexP = new ObservabilityIndexPatterns(data);
- let reportType: DataType = 'apm';
- if (firstSeries?.rt) {
- reportType = ReportToDataTypeMap[firstSeries?.rt];
- }
- return obsvIndexP.getIndexPattern(reportType);
- }, [firstSeries?.rt, data]);
+ return obsvIndexP.getIndexPattern(dataType);
+ }, [dataType, data]);
if (error) {
throw error;
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_lens_attributes.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_lens_attributes.ts
index 274542380c137..555b21618c4b2 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_lens_attributes.ts
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_lens_attributes.ts
@@ -11,12 +11,11 @@ import { LensAttributes } from '../configurations/lens_attributes';
import { useUrlStorage } from './use_url_storage';
import { getDefaultConfigs } from '../configurations/default_configs';
-import { IndexPattern } from '../../../../../../../../src/plugins/data/common';
import { DataSeries, SeriesUrl, UrlFilter } from '../types';
+import { useIndexPatternContext } from './use_default_index_pattern';
interface Props {
seriesId: string;
- indexPattern?: IndexPattern | null;
}
export const getFiltersFromDefs = (
@@ -39,12 +38,12 @@ export const getFiltersFromDefs = (
export const useLensAttributes = ({
seriesId,
- indexPattern,
}: Props): TypedLensByValueInput['attributes'] | null => {
const { series } = useUrlStorage(seriesId);
- const { breakdown, seriesType, metric: metricType, reportType, reportDefinitions = {} } =
- series ?? {};
+ const { breakdown, seriesType, operationType, reportType, reportDefinitions = {} } = series ?? {};
+
+ const { indexPattern } = useIndexPatternContext();
return useMemo(() => {
if (!indexPattern || !reportType) {
@@ -66,7 +65,7 @@ export const useLensAttributes = ({
dataViewConfig,
seriesType,
filters,
- metricType,
+ operationType,
reportDefinitions
);
@@ -79,7 +78,7 @@ export const useLensAttributes = ({
indexPattern,
breakdown,
seriesType,
- metricType,
+ operationType,
reportType,
reportDefinitions,
seriesId,
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_url_storage.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_url_storage.tsx
index 6256b3b134f8c..a4fe15025245a 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_url_storage.tsx
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_url_storage.tsx
@@ -26,9 +26,9 @@ export function UrlStorageContextProvider({
}
function convertFromShortUrl(newValue: ShortUrlSeries): SeriesUrl {
- const { mt, st, rt, bd, ft, time, rdf, ...restSeries } = newValue;
+ const { op, st, rt, bd, ft, time, rdf, ...restSeries } = newValue;
return {
- metric: mt,
+ operationType: op,
reportType: rt!,
seriesType: st,
breakdown: bd,
@@ -40,7 +40,7 @@ function convertFromShortUrl(newValue: ShortUrlSeries): SeriesUrl {
}
interface ShortUrlSeries {
- [URL_KEYS.METRIC_TYPE]?: OperationType;
+ [URL_KEYS.OPERATION_TYPE]?: OperationType;
[URL_KEYS.REPORT_TYPE]?: ReportViewTypeId;
[URL_KEYS.SERIES_TYPE]?: SeriesType;
[URL_KEYS.BREAK_DOWN]?: string;
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/chart_types.test.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/chart_types.test.tsx
similarity index 74%
rename from x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/chart_types.test.tsx
rename to x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/chart_types.test.tsx
index f291d0de4dac0..bac935dbecbe7 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/chart_types.test.tsx
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/chart_types.test.tsx
@@ -7,14 +7,14 @@
import React from 'react';
import { fireEvent, screen, waitFor } from '@testing-library/react';
-import { SeriesChartTypes, XYChartTypes } from './chart_types';
import { mockUrlStorage, render } from '../../rtl_helpers';
+import { SeriesChartTypesSelect, XYChartTypesSelect } from './chart_types';
-describe.skip('SeriesChartTypes', function () {
+describe.skip('SeriesChartTypesSelect', function () {
it('should render properly', async function () {
mockUrlStorage({});
- render();
+ render();
await waitFor(() => {
screen.getByText(/chart type/i);
@@ -24,7 +24,7 @@ describe.skip('SeriesChartTypes', function () {
it('should call set series on change', async function () {
const { setSeries } = mockUrlStorage({});
- render();
+ render();
await waitFor(() => {
screen.getByText(/chart type/i);
@@ -42,11 +42,11 @@ describe.skip('SeriesChartTypes', function () {
expect(setSeries).toHaveBeenCalledTimes(3);
});
- describe('XYChartTypes', function () {
+ describe('XYChartTypesSelect', function () {
it('should render properly', async function () {
mockUrlStorage({});
- render();
+ render();
await waitFor(() => {
screen.getByText(/chart type/i);
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/chart_types.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/chart_types.tsx
new file mode 100644
index 0000000000000..029c39df13aad
--- /dev/null
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/chart_types.tsx
@@ -0,0 +1,104 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React from 'react';
+import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiSuperSelect } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import { useKibana } from '../../../../../../../../../src/plugins/kibana_react/public';
+import { ObservabilityPublicPluginsStart } from '../../../../../plugin';
+import { useFetcher } from '../../../../..';
+import { useUrlStorage } from '../../hooks/use_url_storage';
+import { SeriesType } from '../../../../../../../lens/public';
+
+export function SeriesChartTypesSelect({
+ seriesId,
+ defaultChartType,
+}: {
+ seriesId: string;
+ defaultChartType: SeriesType;
+}) {
+ const { series, setSeries, allSeries } = useUrlStorage(seriesId);
+
+ const seriesType = series?.seriesType ?? defaultChartType;
+
+ const onChange = (value: SeriesType) => {
+ Object.keys(allSeries).forEach((seriesKey) => {
+ const seriesN = allSeries[seriesKey];
+
+ setSeries(seriesKey, { ...seriesN, seriesType: value });
+ });
+ };
+
+ return (
+
+ );
+}
+
+export interface XYChartTypesProps {
+ label?: string;
+ value: SeriesType;
+ includeChartTypes?: SeriesType[];
+ excludeChartTypes?: SeriesType[];
+ onChange: (value: SeriesType) => void;
+}
+
+export function XYChartTypesSelect({
+ onChange,
+ value,
+ includeChartTypes,
+ excludeChartTypes,
+}: XYChartTypesProps) {
+ const {
+ services: { lens },
+ } = useKibana();
+
+ const { data = [], loading } = useFetcher(() => lens.getXyVisTypes(), [lens]);
+
+ let vizTypes = data ?? [];
+
+ if ((excludeChartTypes ?? []).length > 0) {
+ vizTypes = vizTypes.filter(({ id }) => !excludeChartTypes?.includes(id as SeriesType));
+ }
+
+ if ((includeChartTypes ?? []).length > 0) {
+ vizTypes = vizTypes.filter(({ id }) => includeChartTypes?.includes(id as SeriesType));
+ }
+
+ const options = (vizTypes ?? []).map(({ id, fullLabel, label, icon }) => {
+ const LabelWithIcon = (
+
+
+
+
+ {fullLabel || label}
+
+ );
+ return {
+ value: id as SeriesType,
+ inputDisplay: LabelWithIcon,
+ dropdownDisplay: LabelWithIcon,
+ };
+ });
+
+ return (
+
+ );
+}
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/data_types_col.test.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/data_types_col.test.tsx
index 039cdfc9b73f5..41b9f7d22ba00 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/data_types_col.test.tsx
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/data_types_col.test.tsx
@@ -32,7 +32,7 @@ describe('DataTypesCol', function () {
});
it('should set series on change on already selected', function () {
- const { setSeries } = mockUrlStorage({
+ const { removeSeries } = mockUrlStorage({
data: {
[NEW_SERIES_KEY]: {
dataType: 'synthetics',
@@ -54,6 +54,6 @@ describe('DataTypesCol', function () {
fireEvent.click(button);
// undefined on click selected
- expect(setSeries).toHaveBeenCalledWith('newSeriesKey', { dataType: undefined });
+ expect(removeSeries).toHaveBeenCalledWith('newSeriesKey');
});
});
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/data_types_col.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/data_types_col.tsx
index b6464bbe3c6ed..d7e90d34a2596 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/data_types_col.tsx
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/data_types_col.tsx
@@ -20,15 +20,19 @@ export const dataTypes: Array<{ id: AppDataType; label: string }> = [
];
export function DataTypesCol() {
- const { series, setSeries } = useUrlStorage(NEW_SERIES_KEY);
+ const { series, setSeries, removeSeries } = useUrlStorage(NEW_SERIES_KEY);
- const { loadIndexPattern } = useIndexPatternContext();
+ const { loadIndexPattern, indexPattern } = useIndexPatternContext();
const onDataTypeChange = (dataType?: AppDataType) => {
if (dataType) {
loadIndexPattern(dataType);
}
- setSeries(NEW_SERIES_KEY, { dataType } as any);
+ if (!dataType) {
+ removeSeries(NEW_SERIES_KEY);
+ } else {
+ setSeries(NEW_SERIES_KEY, { dataType } as any);
+ }
};
const selectedDataType = series.dataType;
@@ -43,6 +47,8 @@ export function DataTypesCol() {
iconType="arrowRight"
color={selectedDataType === dataTypeId ? 'primary' : 'text'}
fill={selectedDataType === dataTypeId}
+ isDisabled={!indexPattern}
+ isLoading={!indexPattern && selectedDataType === dataTypeId}
onClick={() => {
onDataTypeChange(dataTypeId === selectedDataType ? undefined : dataTypeId);
}}
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/operation_type_select.test.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/operation_type_select.test.tsx
new file mode 100644
index 0000000000000..e05f91b4bb0bd
--- /dev/null
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/operation_type_select.test.tsx
@@ -0,0 +1,64 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React from 'react';
+import { fireEvent, screen } from '@testing-library/react';
+import { mockUrlStorage, render } from '../../rtl_helpers';
+import { OperationTypeSelect } from './operation_type_select';
+
+describe('OperationTypeSelect', function () {
+ it('should render properly', function () {
+ render();
+
+ screen.getByText('Select an option: , is selected');
+ });
+
+ it('should display selected value', function () {
+ mockUrlStorage({
+ data: {
+ 'performance-distribution': {
+ reportType: 'kpi',
+ operationType: 'median',
+ time: { from: 'now-15m', to: 'now' },
+ },
+ },
+ });
+
+ render();
+
+ screen.getByText('Median');
+ });
+
+ it('should call set series on change', function () {
+ const { setSeries } = mockUrlStorage({
+ data: {
+ 'series-id': {
+ reportType: 'kpi',
+ operationType: 'median',
+ time: { from: 'now-15m', to: 'now' },
+ },
+ },
+ });
+
+ render();
+
+ fireEvent.click(screen.getByTestId('operationTypeSelect'));
+
+ expect(setSeries).toHaveBeenCalledWith('series-id', {
+ operationType: 'median',
+ reportType: 'kpi',
+ time: { from: 'now-15m', to: 'now' },
+ });
+
+ fireEvent.click(screen.getByText('95th Percentile'));
+ expect(setSeries).toHaveBeenCalledWith('series-id', {
+ operationType: '95th',
+ reportType: 'kpi',
+ time: { from: 'now-15m', to: 'now' },
+ });
+ });
+});
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/operation_type_select.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/operation_type_select.tsx
new file mode 100644
index 0000000000000..46167af0b244a
--- /dev/null
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/operation_type_select.tsx
@@ -0,0 +1,82 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React, { useEffect } from 'react';
+import { i18n } from '@kbn/i18n';
+import { EuiSuperSelect } from '@elastic/eui';
+
+import { useUrlStorage } from '../../hooks/use_url_storage';
+import { OperationType } from '../../../../../../../lens/public';
+
+export function OperationTypeSelect({
+ seriesId,
+ defaultOperationType,
+}: {
+ seriesId: string;
+ defaultOperationType?: OperationType;
+}) {
+ const { series, setSeries } = useUrlStorage(seriesId);
+
+ const operationType = series?.operationType;
+
+ const onChange = (value: OperationType) => {
+ setSeries(seriesId, { ...series, operationType: value });
+ };
+
+ useEffect(() => {
+ setSeries(seriesId, { ...series, operationType: operationType || defaultOperationType });
+ }, [defaultOperationType, seriesId, operationType, setSeries, series]);
+
+ const options = [
+ {
+ value: 'average' as OperationType,
+ inputDisplay: i18n.translate('xpack.observability.expView.operationType.average', {
+ defaultMessage: 'Average',
+ }),
+ },
+ {
+ value: 'median' as OperationType,
+ inputDisplay: i18n.translate('xpack.observability.expView.operationType.median', {
+ defaultMessage: 'Median',
+ }),
+ },
+ {
+ value: '75th' as OperationType,
+ inputDisplay: i18n.translate('xpack.observability.expView.operationType.75thPercentile', {
+ defaultMessage: '75th Percentile',
+ }),
+ },
+ {
+ value: '90th' as OperationType,
+ inputDisplay: i18n.translate('xpack.observability.expView.operationType.90thPercentile', {
+ defaultMessage: '90th Percentile',
+ }),
+ },
+ {
+ value: '95th' as OperationType,
+ inputDisplay: i18n.translate('xpack.observability.expView.operationType.95thPercentile', {
+ defaultMessage: '95th Percentile',
+ }),
+ },
+ {
+ value: '99th' as OperationType,
+ inputDisplay: i18n.translate('xpack.observability.expView.operationType.99thPercentile', {
+ defaultMessage: '99th Percentile',
+ }),
+ },
+ ];
+
+ return (
+
+ );
+}
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_definition_col.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_definition_col.tsx
index b907efb57d5c2..a386b73a8f917 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_definition_col.tsx
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_definition_col.tsx
@@ -12,6 +12,8 @@ import { NEW_SERIES_KEY, useUrlStorage } from '../../hooks/use_url_storage';
import { CustomReportField } from '../custom_report_field';
import FieldValueSuggestions from '../../../field_value_suggestions';
import { DataSeries } from '../../types';
+import { SeriesChartTypesSelect } from './chart_types';
+import { OperationTypeSelect } from './operation_type_select';
export function ReportDefinitionCol({ dataViewSeries }: { dataViewSeries: DataSeries }) {
const { indexPattern } = useIndexPatternContext();
@@ -20,7 +22,14 @@ export function ReportDefinitionCol({ dataViewSeries }: { dataViewSeries: DataSe
const { reportDefinitions: rtd = {} } = series;
- const { reportDefinitions, labels, filters } = dataViewSeries;
+ const {
+ reportDefinitions,
+ labels,
+ filters,
+ defaultSeriesType,
+ hasOperationType,
+ yAxisColumn,
+ } = dataViewSeries;
const onChange = (field: string, value?: string) => {
if (!value) {
@@ -91,6 +100,17 @@ export function ReportDefinitionCol({ dataViewSeries }: { dataViewSeries: DataSe
)}
))}
+
+
+
+ {hasOperationType && (
+
+
+
+ )}
);
}
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_types_col.test.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_types_col.test.tsx
index 567e2654130e8..f845bf9885af9 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_types_col.test.tsx
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_types_col.test.tsx
@@ -10,6 +10,7 @@ import { fireEvent, screen } from '@testing-library/react';
import { mockUrlStorage, render } from '../../rtl_helpers';
import { ReportTypesCol, SELECTED_DATA_TYPE_FOR_REPORT } from './report_types_col';
import { ReportTypes } from '../series_builder';
+import { DEFAULT_TIME } from '../../configurations/constants';
describe('ReportTypesCol', function () {
it('should render properly', function () {
@@ -60,6 +61,9 @@ describe('ReportTypesCol', function () {
fireEvent.click(button);
// undefined on click selected
- expect(setSeries).toHaveBeenCalledWith('newSeriesKey', { dataType: 'synthetics' });
+ expect(setSeries).toHaveBeenCalledWith('newSeriesKey', {
+ dataType: 'synthetics',
+ time: DEFAULT_TIME,
+ });
});
});
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_types_col.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_types_col.tsx
index a473ddb570526..a8f98b98026b6 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_types_col.tsx
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_types_col.tsx
@@ -10,6 +10,8 @@ import { i18n } from '@kbn/i18n';
import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui';
import { ReportViewTypeId, SeriesUrl } from '../../types';
import { NEW_SERIES_KEY, useUrlStorage } from '../../hooks/use_url_storage';
+import { DEFAULT_TIME } from '../../configurations/constants';
+import { useIndexPatternContext } from '../../hooks/use_default_index_pattern';
interface Props {
reportTypes: Array<{ id: ReportViewTypeId; label: string }>;
@@ -21,6 +23,8 @@ export function ReportTypesCol({ reportTypes }: Props) {
setSeries,
} = useUrlStorage(NEW_SERIES_KEY);
+ const { indexPattern } = useIndexPatternContext();
+
return reportTypes?.length > 0 ? (
{reportTypes.map(({ id: reportType, label }) => (
@@ -31,16 +35,19 @@ export function ReportTypesCol({ reportTypes }: Props) {
iconType="arrowRight"
color={selectedReportType === reportType ? 'primary' : 'text'}
fill={selectedReportType === reportType}
+ isDisabled={!indexPattern}
onClick={() => {
if (reportType === selectedReportType) {
setSeries(NEW_SERIES_KEY, {
dataType: restSeries.dataType,
+ time: DEFAULT_TIME,
} as SeriesUrl);
} else {
setSeries(NEW_SERIES_KEY, {
...restSeries,
reportType,
reportDefinitions: {},
+ time: restSeries?.time ?? DEFAULT_TIME,
});
}
}}
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/series_builder.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/series_builder.tsx
index 053f301529635..2280109fdacdf 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/series_builder.tsx
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/series_builder.tsx
@@ -49,7 +49,14 @@ export const ReportTypes: Record {
@@ -145,7 +154,7 @@ export function SeriesBuilder() {
columns={columns}
cellProps={{ style: { borderRight: '1px solid #d3dae6' } }}
/>
-
+
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_date_picker/index.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_date_picker/index.tsx
index 922d33ffd39ac..960c2978287bc 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_date_picker/index.tsx
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_date_picker/index.tsx
@@ -10,6 +10,7 @@ import React, { useEffect } from 'react';
import { useHasData } from '../../../../hooks/use_has_data';
import { useUrlStorage } from '../hooks/use_url_storage';
import { useQuickTimeRanges } from '../../../../hooks/use_quick_time_ranges';
+import { DEFAULT_TIME } from '../configurations/constants';
export interface TimePickerTime {
from: string;
@@ -38,7 +39,7 @@ export function SeriesDatePicker({ seriesId }: Props) {
useEffect(() => {
if (!series || !series.time) {
- setSeries(seriesId, { ...series, time: { from: 'now-5h', to: 'now' } });
+ setSeries(seriesId, { ...series, time: DEFAULT_TIME });
}
}, [seriesId, series, setSeries]);
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_date_picker/series_date_picker.test.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_date_picker/series_date_picker.test.tsx
index acc9ba9658a08..8fe1d5ed9f2ac 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_date_picker/series_date_picker.test.tsx
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_date_picker/series_date_picker.test.tsx
@@ -9,6 +9,7 @@ import React from 'react';
import { mockUrlStorage, mockUseHasData, render } from '../rtl_helpers';
import { fireEvent, waitFor } from '@testing-library/react';
import { SeriesDatePicker } from './index';
+import { DEFAULT_TIME } from '../configurations/constants';
describe('SeriesDatePicker', function () {
it('should render properly', function () {
@@ -40,7 +41,7 @@ describe('SeriesDatePicker', function () {
expect(setSeries1).toHaveBeenCalledWith('uptime-pings-histogram', {
breakdown: 'monitor.status',
reportType: 'upp',
- time: { from: 'now-5h', to: 'now' },
+ time: DEFAULT_TIME,
});
});
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/actions_col.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/actions_col.tsx
index c6209381a4da1..fe54262e13844 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/actions_col.tsx
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/actions_col.tsx
@@ -8,8 +8,8 @@
import React from 'react';
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { DataSeries } from '../../types';
-import { SeriesChartTypes } from './chart_types';
-import { MetricSelection } from './metric_selection';
+import { OperationTypeSelect } from '../../series_builder/columns/operation_type_select';
+import { SeriesChartTypesSelect } from '../../series_builder/columns/chart_types';
interface Props {
series: DataSeries;
@@ -17,13 +17,13 @@ interface Props {
export function ActionsCol({ series }: Props) {
return (
-
+
-
+
- {series.hasMetricType && (
+ {series.hasOperationType && (
-
+
)}
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/chart_types.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/chart_types.tsx
deleted file mode 100644
index f83630cff414a..0000000000000
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/chart_types.tsx
+++ /dev/null
@@ -1,149 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import React, { useState } from 'react';
-
-import {
- EuiButton,
- EuiButtonGroup,
- EuiButtonIcon,
- EuiLoadingSpinner,
- EuiPopover,
-} from '@elastic/eui';
-import { i18n } from '@kbn/i18n';
-import styled from 'styled-components';
-import { useKibana } from '../../../../../../../../../src/plugins/kibana_react/public';
-import { ObservabilityPublicPluginsStart } from '../../../../../plugin';
-import { useFetcher } from '../../../../..';
-import { useUrlStorage } from '../../hooks/use_url_storage';
-import { SeriesType } from '../../../../../../../lens/public';
-
-export function SeriesChartTypes({
- seriesId,
- defaultChartType,
-}: {
- seriesId: string;
- defaultChartType: SeriesType;
-}) {
- const { series, setSeries, allSeries } = useUrlStorage(seriesId);
-
- const seriesType = series?.seriesType ?? defaultChartType;
-
- const onChange = (value: SeriesType) => {
- Object.keys(allSeries).forEach((seriesKey) => {
- const seriesN = allSeries[seriesKey];
-
- setSeries(seriesKey, { ...seriesN, seriesType: value });
- });
- };
-
- return (
-
- );
-}
-
-export interface XYChartTypesProps {
- onChange: (value: SeriesType) => void;
- value: SeriesType;
- label?: string;
- includeChartTypes?: string[];
- excludeChartTypes?: string[];
-}
-
-export function XYChartTypes({
- onChange,
- value,
- label,
- includeChartTypes,
- excludeChartTypes,
-}: XYChartTypesProps) {
- const [isOpen, setIsOpen] = useState(false);
-
- const {
- services: { lens },
- } = useKibana();
-
- const { data = [], loading } = useFetcher(() => lens.getXyVisTypes(), [lens]);
-
- let vizTypes = data ?? [];
-
- if ((excludeChartTypes ?? []).length > 0) {
- vizTypes = vizTypes.filter(({ id }) => !excludeChartTypes?.includes(id));
- }
-
- if ((includeChartTypes ?? []).length > 0) {
- vizTypes = vizTypes.filter(({ id }) => includeChartTypes?.includes(id));
- }
-
- return loading ? (
-
- ) : (
- id === value)?.icon}
- onClick={() => {
- setIsOpen((prevState) => !prevState);
- }}
- >
- {label}
-
- ) : (
- id === value)?.label}
- iconType={vizTypes.find(({ id }) => id === value)?.icon!}
- onClick={() => {
- setIsOpen((prevState) => !prevState);
- }}
- />
- )
- }
- closePopover={() => setIsOpen(false)}
- >
- ({
- id: t.id,
- label: t.label,
- title: t.label,
- iconType: t.icon || 'empty',
- 'data-test-subj': `lnsXY_seriesType-${t.id}`,
- }))}
- idSelected={value}
- onChange={(valueN: string) => {
- onChange(valueN as SeriesType);
- }}
- />
-
- );
-}
-
-const ButtonGroup = styled(EuiButtonGroup)`
- &&& {
- .euiButtonGroupButton-isSelected {
- background-color: #a5a9b1 !important;
- }
- }
-`;
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/metric_selection.test.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/metric_selection.test.tsx
deleted file mode 100644
index ced04f0a59c8c..0000000000000
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/metric_selection.test.tsx
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import React from 'react';
-import { fireEvent, screen } from '@testing-library/react';
-import { mockUrlStorage, render } from '../../rtl_helpers';
-import { MetricSelection } from './metric_selection';
-
-describe('MetricSelection', function () {
- it('should render properly', function () {
- render();
-
- screen.getByText('Average');
- });
-
- it('should display selected value', function () {
- mockUrlStorage({
- data: {
- 'performance-distribution': {
- reportType: 'kpi',
- metric: 'median',
- time: { from: 'now-15m', to: 'now' },
- },
- },
- });
-
- render();
-
- screen.getByText('Median');
- });
-
- it('should be disabled on disabled state', function () {
- render();
-
- const btn = screen.getByRole('button');
-
- expect(btn.classList).toContain('euiButton-isDisabled');
- });
-
- it('should call set series on change', function () {
- const { setSeries } = mockUrlStorage({
- data: {
- 'performance-distribution': {
- reportType: 'kpi',
- metric: 'median',
- time: { from: 'now-15m', to: 'now' },
- },
- },
- });
-
- render();
-
- fireEvent.click(screen.getByText('Median'));
-
- screen.getByText('Chart metric group');
-
- fireEvent.click(screen.getByText('95th Percentile'));
-
- expect(setSeries).toHaveBeenNthCalledWith(1, 'performance-distribution', {
- metric: '95th',
- reportType: 'kpi',
- time: { from: 'now-15m', to: 'now' },
- });
- // FIXME This is a bug in EUI EuiButtonGroup calls on change multiple times
- // This should be one https://github.com/elastic/eui/issues/4629
- expect(setSeries).toHaveBeenCalledTimes(3);
- });
-
- it('should call set series on change for all series', function () {
- const { setSeries } = mockUrlStorage({
- data: {
- 'page-views': {
- reportType: 'kpi',
- metric: 'median',
- time: { from: 'now-15m', to: 'now' },
- },
- 'performance-distribution': {
- reportType: 'kpi',
- metric: 'median',
- time: { from: 'now-15m', to: 'now' },
- },
- },
- });
-
- render();
-
- fireEvent.click(screen.getByText('Median'));
-
- screen.getByText('Chart metric group');
-
- fireEvent.click(screen.getByText('95th Percentile'));
-
- expect(setSeries).toHaveBeenNthCalledWith(1, 'page-views', {
- metric: '95th',
- reportType: 'kpi',
- time: { from: 'now-15m', to: 'now' },
- });
-
- expect(setSeries).toHaveBeenNthCalledWith(2, 'performance-distribution', {
- metric: '95th',
- reportType: 'kpi',
- time: { from: 'now-15m', to: 'now' },
- });
- // FIXME This is a bug in EUI EuiButtonGroup calls on change multiple times
- // This should be one https://github.com/elastic/eui/issues/4629
- expect(setSeries).toHaveBeenCalledTimes(6);
- });
-});
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/metric_selection.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/metric_selection.tsx
deleted file mode 100644
index fa4202d2c30ad..0000000000000
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/metric_selection.tsx
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import React, { useState } from 'react';
-import { i18n } from '@kbn/i18n';
-import { EuiButton, EuiButtonGroup, EuiPopover } from '@elastic/eui';
-import { useUrlStorage } from '../../hooks/use_url_storage';
-import { OperationType } from '../../../../../../../lens/public';
-
-const toggleButtons = [
- {
- id: `average`,
- label: i18n.translate('xpack.observability.expView.metricsSelect.average', {
- defaultMessage: 'Average',
- }),
- },
- {
- id: `median`,
- label: i18n.translate('xpack.observability.expView.metricsSelect.median', {
- defaultMessage: 'Median',
- }),
- },
- {
- id: `95th`,
- label: i18n.translate('xpack.observability.expView.metricsSelect.9thPercentile', {
- defaultMessage: '95th Percentile',
- }),
- },
- {
- id: `99th`,
- label: i18n.translate('xpack.observability.expView.metricsSelect.99thPercentile', {
- defaultMessage: '99th Percentile',
- }),
- },
-];
-
-export function MetricSelection({
- seriesId,
- isDisabled,
-}: {
- seriesId: string;
- isDisabled: boolean;
-}) {
- const { series, setSeries, allSeries } = useUrlStorage(seriesId);
-
- const [isOpen, setIsOpen] = useState(false);
-
- const [toggleIdSelected, setToggleIdSelected] = useState(series?.metric ?? 'average');
-
- const onChange = (optionId: OperationType) => {
- setToggleIdSelected(optionId);
-
- Object.keys(allSeries).forEach((seriesKey) => {
- const seriesN = allSeries[seriesKey];
-
- setSeries(seriesKey, { ...seriesN, metric: optionId });
- });
- };
- const button = (
- setIsOpen((prevState) => !prevState)}
- size="s"
- color="text"
- isDisabled={isDisabled}
- >
- {toggleButtons.find(({ id }) => id === toggleIdSelected)!.label}
-
- );
-
- return (
- setIsOpen(false)}>
- onChange(id as OperationType)}
- />
-
- );
-}
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/types.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/types.ts
index d673fc4d6f6ee..141dcecd0ba5b 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/types.ts
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/types.ts
@@ -9,9 +9,9 @@ import { PaletteOutput } from 'src/plugins/charts/public';
import {
LastValueIndexPatternColumn,
DateHistogramIndexPatternColumn,
+ FieldBasedIndexPatternColumn,
SeriesType,
OperationType,
- IndexPatternColumn,
} from '../../../../../lens/public';
import { PersistableFilter } from '../../../../../lens/common';
@@ -41,14 +41,19 @@ export interface ReportDefinition {
required?: boolean;
custom?: boolean;
defaultValue?: string;
- options?: Array<{ field: string; label: string; description?: string }>;
+ options?: Array<{
+ field: string;
+ label: string;
+ description?: string;
+ columnType?: 'range' | 'operation';
+ }>;
}
export interface DataSeries {
reportType: ReportViewType;
id: string;
xAxisColumn: Partial | Partial;
- yAxisColumn: Partial;
+ yAxisColumn: Partial;
breakdowns: string[];
defaultSeriesType: SeriesType;
@@ -57,7 +62,7 @@ export interface DataSeries {
filters?: PersistableFilter[];
reportDefinitions: ReportDefinition[];
labels: Record;
- hasMetricType: boolean;
+ hasOperationType: boolean;
palette?: PaletteOutput;
}
@@ -70,7 +75,7 @@ export interface SeriesUrl {
filters?: UrlFilter[];
seriesType?: SeriesType;
reportType: ReportViewTypeId;
- metric?: OperationType;
+ operationType?: OperationType;
dataType?: AppDataType;
reportDefinitions?: Record;
}
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/utils/observability_index_patterns.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/utils/observability_index_patterns.ts
index e0a2941b24d3c..527ef48364d22 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/utils/observability_index_patterns.ts
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/utils/observability_index_patterns.ts
@@ -47,12 +47,16 @@ const appToPatternMap: Record = {
};
export function isParamsSame(param1: IFieldFormat['_params'], param2: FieldFormatParams) {
- return (
+ const isSame =
param1?.inputFormat === param2?.inputFormat &&
param1?.outputFormat === param2?.outputFormat &&
- param1?.showSuffix === param2?.showSuffix &&
- param2?.outputPrecision === param1?.outputPrecision
- );
+ param1?.showSuffix === param2?.showSuffix;
+
+ if (param2.outputPrecision !== undefined) {
+ return param2?.outputPrecision === param1?.outputPrecision && isSame;
+ }
+
+ return isSame;
}
export class ObservabilityIndexPatterns {
diff --git a/x-pack/plugins/security/server/usage_collector/security_usage_collector.ts b/x-pack/plugins/security/server/usage_collector/security_usage_collector.ts
index a59951f5fcfe2..813e23a13ff37 100644
--- a/x-pack/plugins/security/server/usage_collector/security_usage_collector.ts
+++ b/x-pack/plugins/security/server/usage_collector/security_usage_collector.ts
@@ -53,26 +53,49 @@ export function registerSecurityUsageCollector({ usageCollection, config, licens
schema: {
auditLoggingEnabled: {
type: 'boolean',
+ _meta: {
+ description:
+ 'Indicates if audit logging is both enabled and supported by the current license.',
+ },
},
loginSelectorEnabled: {
type: 'boolean',
+ _meta: {
+ description: 'Indicates if the login selector UI is enabled.',
+ },
},
accessAgreementEnabled: {
type: 'boolean',
+ _meta: {
+ description:
+ 'Indicates if the access agreement UI is both enabled and supported by the current license.',
+ },
},
authProviderCount: {
type: 'long',
+ _meta: {
+ description:
+ 'The number of configured auth providers (including disabled auth providers).',
+ },
},
enabledAuthProviders: {
type: 'array',
items: {
type: 'keyword',
+ _meta: {
+ description:
+ 'The types of enabled auth providers (such as `saml`, `basic`, `pki`, etc).',
+ },
},
},
httpAuthSchemes: {
type: 'array',
items: {
type: 'keyword',
+ _meta: {
+ description:
+ 'The set of enabled http auth schemes. Used for api-based usage, and when credentials are provided via reverse-proxy.',
+ },
},
},
},
diff --git a/x-pack/plugins/security_solution/public/timelines/components/flyout/add_timeline_button/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/flyout/add_timeline_button/index.test.tsx
index f8913148c625b..84406aed3619f 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/flyout/add_timeline_button/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/flyout/add_timeline_button/index.test.tsx
@@ -35,7 +35,8 @@ jest.mock('../../../../common/components/inspect', () => ({
InspectButtonContainer: jest.fn(({ children }) => {children}
),
}));
-describe('AddTimelineButton', () => {
+// FLAKY: https://github.com/elastic/kibana/issues/96691
+describe.skip('AddTimelineButton', () => {
let wrapper: ReactWrapper;
const props = {
timelineId: TimelineId.active,
diff --git a/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout_internal.test.tsx b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout_internal.test.tsx
index e0326a6c9ff11..cb821061b9251 100644
--- a/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout_internal.test.tsx
+++ b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout_internal.test.tsx
@@ -112,7 +112,8 @@ const setup = async (opts: SetupOpts = {}) => {
return { wrapper, onClose, mockSpacesManager, mockToastNotifications, savedObjectToCopy };
};
-describe('CopyToSpaceFlyout', () => {
+// flaky https://github.com/elastic/kibana/issues/96708
+describe.skip('CopyToSpaceFlyout', () => {
it('waits for spaces to load', async () => {
const { wrapper } = await setup({ returnBeforeSpacesLoad: true });
diff --git a/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json b/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json
index e1e0711c2bb2c..3d302aa12832e 100644
--- a/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json
+++ b/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json
@@ -3896,27 +3896,45 @@
"security": {
"properties": {
"auditLoggingEnabled": {
- "type": "boolean"
+ "type": "boolean",
+ "_meta": {
+ "description": "Indicates if audit logging is both enabled and supported by the current license."
+ }
},
"loginSelectorEnabled": {
- "type": "boolean"
+ "type": "boolean",
+ "_meta": {
+ "description": "Indicates if the login selector UI is enabled."
+ }
},
"accessAgreementEnabled": {
- "type": "boolean"
+ "type": "boolean",
+ "_meta": {
+ "description": "Indicates if the access agreement UI is both enabled and supported by the current license."
+ }
},
"authProviderCount": {
- "type": "long"
+ "type": "long",
+ "_meta": {
+ "description": "The number of configured auth providers (including disabled auth providers)."
+ }
},
"enabledAuthProviders": {
"type": "array",
"items": {
- "type": "keyword"
+ "type": "keyword",
+ "_meta": {
+ "description": "The types of enabled auth providers (such as `saml`, `basic`, `pki`, etc)."
+ }
}
},
"httpAuthSchemes": {
"type": "array",
"items": {
- "type": "keyword"
+ "type": "keyword",
+ "_meta": {
+ "description": "The set of enabled http auth schemes. Used for api-based usage, and when credentials are provided via reverse-proxy."
+ }
}
}
}
diff --git a/x-pack/test/fleet_api_integration/apis/enrollment_api_keys/crud.ts b/x-pack/test/fleet_api_integration/apis/enrollment_api_keys/crud.ts
index 2569d9aef4b5b..d9946bb174f5d 100644
--- a/x-pack/test/fleet_api_integration/apis/enrollment_api_keys/crud.ts
+++ b/x-pack/test/fleet_api_integration/apis/enrollment_api_keys/crud.ts
@@ -115,6 +115,28 @@ export default function (providerContext: FtrProviderContext) {
expect(apiResponse.item).to.have.keys('id', 'api_key', 'api_key_id', 'name', 'policy_id');
});
+ it('should create an ES ApiKey with metadata', async () => {
+ const { body: apiResponse } = await supertest
+ .post(`/api/fleet/enrollment-api-keys`)
+ .set('kbn-xsrf', 'xxx')
+ .send({
+ policy_id: 'policy1',
+ })
+ .expect(200);
+
+ const { body: apiKeyRes } = await es.security.getApiKey({
+ id: apiResponse.item.api_key_id,
+ });
+
+ // @ts-expect-error Metadata not yet in the client type
+ expect(apiKeyRes.api_keys[0].metadata).eql({
+ policy_id: 'policy1',
+ managed_by: 'fleet',
+ managed: true,
+ type: 'enroll',
+ });
+ });
+
it('should create an ES ApiKey with limited privileges', async () => {
const { body: apiResponse } = await supertest
.post(`/api/fleet/enrollment-api-keys`)
@@ -162,33 +184,6 @@ export default function (providerContext: FtrProviderContext) {
},
});
});
-
- describe('It should handle error when the Fleet user is invalid', () => {
- before(async () => {});
- after(async () => {
- await getService('supertest')
- .post(`/api/fleet/agents/setup`)
- .set('kbn-xsrf', 'xxx')
- .send({ forceRecreate: true });
- });
-
- it('should not allow to create an enrollment api key if the Fleet admin user is invalid', async () => {
- await es.security.changePassword({
- username: 'fleet_enroll',
- body: {
- password: Buffer.from((Math.random() * 10000000).toString()).toString('base64'),
- },
- });
- const res = await supertest
- .post(`/api/fleet/enrollment-api-keys`)
- .set('kbn-xsrf', 'xxx')
- .send({
- policy_id: 'policy1',
- })
- .expect(400);
- expect(res.body.message).match(/Fleet Admin user is invalid/);
- });
- });
});
});
}
diff --git a/x-pack/test/functional/apps/rollup_job/tsvb.js b/x-pack/test/functional/apps/rollup_job/tsvb.js
index d0c7c86d6d5c3..891805acb3256 100644
--- a/x-pack/test/functional/apps/rollup_job/tsvb.js
+++ b/x-pack/test/functional/apps/rollup_job/tsvb.js
@@ -83,6 +83,7 @@ export default function ({ getService, getPageObjects }) {
);
await PageObjects.visualBuilder.clickPanelOptions('metric');
await PageObjects.visualBuilder.setIndexPatternValue(rollupTargetIndexName, false);
+ await PageObjects.visualBuilder.selectIndexPatternTimeField('@timestamp');
await PageObjects.visualBuilder.setMetricsDataTimerangeMode('Last value');
await PageObjects.visualBuilder.setIntervalValue('1d');
await PageObjects.visualBuilder.setDropLastBucket(false);